kimaki 0.26.0 → 0.28.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/agent-model.e2e.test.js +250 -164
- package/dist/anthropic-account-identity.js +9 -1
- package/dist/anthropic-auth-plugin.js +10 -13
- package/dist/anthropic-auth-plugin.test.js +17 -0
- package/dist/anthropic-auth-state.js +1 -0
- package/dist/bash-tool-schema-plugin.js +32 -0
- package/dist/bash-tool-schema-plugin.test.js +49 -0
- package/dist/bin.js +73 -19
- package/dist/cli-commands/maintenance.js +46 -7
- package/dist/cli-commands/project.js +11 -27
- package/dist/cli-commands/send.js +30 -1
- package/dist/cli-commands/session.js +322 -90
- package/dist/cli-commands/task.js +34 -15
- package/dist/cli-parsing.test.js +83 -4
- package/dist/cli-runner.js +44 -8
- package/dist/cli-runner.test.js +27 -0
- package/dist/cli-send-thread.e2e.test.js +9 -9
- package/dist/cli.js +32 -0
- package/dist/commands/abort.js +5 -1
- package/dist/commands/action-buttons.js +2 -1
- package/dist/commands/add-project.js +4 -7
- package/dist/commands/agent.js +164 -32
- package/dist/commands/ask-question.js +23 -33
- package/dist/commands/ask-question.test.js +43 -1
- package/dist/commands/btw.js +28 -6
- package/dist/commands/file-upload.js +2 -1
- package/dist/commands/fork-subagent.js +2 -4
- package/dist/commands/fork.js +4 -4
- package/dist/commands/last-sessions.js +17 -10
- package/dist/commands/login.js +6 -0
- package/dist/commands/merge-worktree.js +47 -14
- package/dist/commands/model-variant.js +8 -1
- package/dist/commands/model.js +16 -9
- package/dist/commands/multioauth.js +1 -0
- package/dist/commands/new-worktree.js +170 -102
- package/dist/commands/queue.js +3 -2
- package/dist/commands/resume.js +4 -4
- package/dist/commands/session.js +78 -63
- package/dist/commands/user-command.js +6 -8
- package/dist/commands/worktrees.js +5 -13
- package/dist/config.js +16 -1
- package/dist/cpu-profiler.js +211 -0
- package/dist/cpu-profiler.test.js +160 -0
- package/dist/database.js +152 -3
- package/dist/db.js +33 -0
- package/dist/db.test.js +267 -1
- package/dist/default-channel-provisioning.test.js +148 -0
- package/dist/discord-bot.js +517 -488
- package/dist/discord-command-registration.js +25 -10
- package/dist/discord-command-registration.test.js +17 -0
- package/dist/discord-utils.js +97 -5
- package/dist/discord-utils.test.js +151 -1
- package/dist/errors.js +10 -0
- package/dist/exec-async.js +17 -5
- package/dist/external-directory-permissions.e2e.test.js +11 -7
- package/dist/external-directory-project-deny.e2e.test.js +5 -3
- package/dist/external-opencode-sync.js +94 -17
- package/dist/external-opencode-sync.test.js +206 -0
- package/dist/extract-sdk-error-message.test.js +13 -0
- package/dist/file-edit-log.js +197 -0
- package/dist/file-edit-log.test.js +258 -0
- package/dist/format-tables.js +52 -0
- package/dist/format-tables.test.js +93 -0
- package/dist/gateway-proxy-reconnect.e2e.test.js +28 -8
- package/dist/gateway-proxy.e2e.test.js +18 -18
- package/dist/git-worktree-core.js +182 -17
- package/dist/interaction-handler.js +416 -398
- package/dist/ipc-tools-plugin.js +68 -3
- package/dist/kimaki-opencode-plugin-loading.e2e.test.js +122 -58
- package/dist/kimaki-opencode-plugin.js +9 -3
- package/dist/kimaki-workspace-adaptor.js +33 -10
- package/dist/logger.js +11 -1
- package/dist/markdown.js +3 -1
- package/dist/markdown.test.js +11 -3
- package/dist/message-formatting.js +181 -51
- package/dist/message-formatting.test.js +529 -22
- package/dist/message-preprocessing.js +105 -1
- package/dist/oauth-rotation-shared.js +10 -0
- package/dist/openai-auth-plugin.js +1 -0
- package/dist/openai-auth-state.js +1 -0
- package/dist/opencode-command.js +28 -0
- package/dist/opencode-command.test.js +109 -1
- package/dist/opencode-interrupt-plugin.js +4 -0
- package/dist/opencode-interrupt-plugin.test.js +24 -0
- package/dist/opencode.js +82 -15
- package/dist/project-add.test.js +62 -0
- package/dist/question-abort-clears-dropdown.e2e.test.js +89 -0
- package/dist/question-abort-retry-text-before-dropdown.e2e.test.js +150 -0
- package/dist/question-text-before-dropdown.e2e.test.js +52 -0
- package/dist/queue-advanced-abort.e2e.test.js +8 -10
- package/dist/queue-advanced-action-buttons.e2e.test.js +8 -8
- package/dist/queue-advanced-e2e-setup.js +78 -5
- package/dist/queue-advanced-footer.e2e.test.js +60 -269
- package/dist/queue-advanced-model-switch.e2e.test.js +9 -11
- package/dist/queue-advanced-permissions-typing.e2e.test.js +15 -15
- package/dist/queue-advanced-question.e2e.test.js +32 -27
- package/dist/queue-advanced-typing-interrupt.e2e.test.js +10 -12
- package/dist/queue-advanced-typing.e2e.test.js +7 -9
- package/dist/queue-delete-message.e2e.test.js +3 -3
- package/dist/queue-drain-after-interactive-ui.e2e.test.js +5 -5
- package/dist/queue-interrupt-drain.e2e.test.js +122 -12
- package/dist/queue-question-select-drain.e2e.test.js +34 -132
- package/dist/runtime-lifecycle.e2e.test.js +21 -51
- package/dist/schema.js +35 -1
- package/dist/session-handler/event-stream-state.js +391 -50
- package/dist/session-handler/event-stream-state.test.js +736 -8
- package/dist/session-handler/model-utils.js +172 -5
- package/dist/session-handler/model-utils.test.js +445 -0
- package/dist/session-handler/opencode-session-event-log.js +30 -1
- package/dist/session-handler/thread-ingress.test.js +41 -0
- package/dist/session-handler/thread-runtime-state.js +2 -0
- package/dist/session-handler/thread-session-runtime-summary.test.js +121 -0
- package/dist/session-handler/thread-session-runtime.js +574 -299
- package/dist/session-search.js +123 -4
- package/dist/session-search.test.js +248 -1
- package/dist/session-sleep.test.js +224 -0
- package/dist/session-title-rename.test.js +37 -39
- package/dist/sleep.e2e.test.js +494 -0
- package/dist/startup-time.e2e.test.js +2 -1
- package/dist/store.js +3 -0
- package/dist/system-message.js +102 -16
- package/dist/system-message.test.js +192 -15
- package/dist/task-runner.js +96 -5
- package/dist/task-schedule.js +87 -1
- package/dist/task-schedule.test.js +149 -1
- package/dist/test-utils.js +17 -9
- package/dist/thinking-utils.js +10 -0
- package/dist/thinking-utils.test.js +35 -0
- package/dist/thread-message-queue.e2e.test.js +65 -213
- package/dist/undo-redo.e2e.test.js +17 -9
- package/dist/voice-attachment.js +23 -1
- package/dist/voice-handler.js +21 -4
- package/dist/voice-message.e2e.test.js +144 -30
- package/dist/voice-routing-input.e2e.test.js +195 -0
- package/dist/voice-routing-question.e2e.test.js +125 -0
- package/dist/voice.js +28 -8
- package/dist/voice.test.js +96 -30
- package/dist/worktree-clone-isolation.e2e.test.js +188 -0
- package/dist/worktree-lifecycle.e2e.test.js +55 -36
- package/dist/worktrees.js +226 -443
- package/dist/worktrees.test.js +435 -58
- package/dist/xai-auth-plugin.js +1 -0
- package/dist/xai-auth-state.js +1 -0
- package/package.json +8 -6
- package/skills/bump-holocron-consumers/SKILL.md +137 -0
- package/skills/critique/SKILL.md +4 -4
- package/skills/errore/SKILL.md +60 -218
- package/skills/goke/SKILL.md +150 -10
- package/skills/holocron/SKILL.md +154 -17
- package/skills/npm-package/SKILL.md +55 -35
- package/skills/profano/SKILL.md +17 -2
- package/skills/spiceflow/SKILL.md +39 -3
- package/skills/strada/SKILL.md +39 -0
- package/skills/zele/SKILL.md +19 -0
- package/src/agent-model.e2e.test.ts +287 -186
- package/src/anthropic-account-identity.ts +20 -1
- package/src/anthropic-auth-plugin.test.ts +19 -0
- package/src/anthropic-auth-plugin.ts +12 -16
- package/src/anthropic-auth-state.ts +1 -0
- package/src/bash-tool-schema-plugin.test.ts +55 -0
- package/src/bash-tool-schema-plugin.ts +46 -0
- package/src/bin.ts +78 -26
- package/src/cli-commands/maintenance.ts +59 -13
- package/src/cli-commands/project.ts +14 -35
- package/src/cli-commands/send.ts +31 -1
- package/src/cli-commands/session.ts +374 -114
- package/src/cli-commands/task.ts +34 -15
- package/src/cli-parsing.test.ts +103 -4
- package/src/cli-runner.test.ts +49 -0
- package/src/cli-runner.ts +44 -7
- package/src/cli-send-thread.e2e.test.ts +9 -11
- package/src/cli.ts +53 -0
- package/src/commands/abort.ts +6 -1
- package/src/commands/action-buttons.ts +2 -1
- package/src/commands/add-project.ts +4 -8
- package/src/commands/agent.ts +205 -29
- package/src/commands/ask-question.test.ts +48 -1
- package/src/commands/ask-question.ts +23 -33
- package/src/commands/btw.ts +40 -5
- package/src/commands/file-upload.ts +2 -1
- package/src/commands/fork-subagent.ts +2 -3
- package/src/commands/fork.ts +4 -3
- package/src/commands/last-sessions.ts +10 -4
- package/src/commands/login.ts +6 -0
- package/src/commands/merge-worktree.ts +54 -16
- package/src/commands/model-variant.ts +9 -1
- package/src/commands/model.ts +20 -9
- package/src/commands/multioauth.ts +1 -0
- package/src/commands/new-worktree.ts +138 -55
- package/src/commands/queue.ts +3 -2
- package/src/commands/resume.ts +4 -3
- package/src/commands/session.ts +94 -70
- package/src/commands/user-command.ts +6 -6
- package/src/commands/worktrees.ts +5 -12
- package/src/config.ts +18 -1
- package/src/cpu-profiler.test.ts +183 -0
- package/src/cpu-profiler.ts +293 -0
- package/src/database.ts +235 -3
- package/src/db.test.ts +293 -0
- package/src/db.ts +38 -0
- package/src/default-channel-provisioning.test.ts +155 -0
- package/src/discord-bot.ts +78 -36
- package/src/discord-command-registration.test.ts +20 -0
- package/src/discord-command-registration.ts +42 -12
- package/src/discord-utils.test.ts +176 -0
- package/src/discord-utils.ts +152 -7
- package/src/errors.ts +12 -0
- package/src/exec-async.ts +20 -11
- package/src/external-directory-permissions.e2e.test.ts +11 -7
- package/src/external-directory-project-deny.e2e.test.ts +5 -3
- package/src/external-opencode-sync.test.ts +248 -0
- package/src/external-opencode-sync.ts +122 -19
- package/src/extract-sdk-error-message.test.ts +18 -0
- package/src/file-edit-log.test.ts +287 -0
- package/src/file-edit-log.ts +283 -0
- package/src/format-tables.test.ts +101 -0
- package/src/format-tables.ts +75 -0
- package/src/gateway-proxy-reconnect.e2e.test.ts +38 -8
- package/src/gateway-proxy.e2e.test.ts +18 -17
- package/src/git-worktree-core.ts +247 -20
- package/src/interaction-handler.ts +36 -13
- package/src/ipc-tools-plugin.ts +82 -5
- package/src/kimaki-opencode-plugin-loading.e2e.test.ts +188 -97
- package/src/kimaki-opencode-plugin.ts +9 -3
- package/src/kimaki-workspace-adaptor.ts +40 -11
- package/src/logger.ts +14 -4
- package/src/markdown.test.ts +13 -3
- package/src/markdown.ts +4 -1
- package/src/message-formatting.test.ts +672 -25
- package/src/message-formatting.ts +242 -53
- package/src/message-preprocessing.ts +116 -1
- package/src/oauth-rotation-shared.ts +10 -0
- package/src/openai-auth-plugin.ts +1 -0
- package/src/openai-auth-state.ts +1 -0
- package/src/opencode-command.test.ts +140 -0
- package/src/opencode-command.ts +38 -0
- package/src/opencode-interrupt-plugin.test.ts +32 -0
- package/src/opencode-interrupt-plugin.ts +3 -0
- package/src/opencode.ts +115 -15
- package/src/project-add.test.ts +80 -0
- package/src/question-abort-clears-dropdown.e2e.test.ts +109 -0
- package/src/question-abort-retry-text-before-dropdown.e2e.test.ts +172 -0
- package/src/question-text-before-dropdown.e2e.test.ts +67 -0
- package/src/queue-advanced-abort.e2e.test.ts +8 -9
- package/src/queue-advanced-action-buttons.e2e.test.ts +8 -8
- package/src/queue-advanced-e2e-setup.ts +83 -4
- package/src/queue-advanced-footer.e2e.test.ts +67 -315
- package/src/queue-advanced-model-switch.e2e.test.ts +9 -10
- package/src/queue-advanced-permissions-typing.e2e.test.ts +15 -15
- package/src/queue-advanced-question.e2e.test.ts +36 -30
- package/src/queue-advanced-typing-interrupt.e2e.test.ts +11 -11
- package/src/queue-advanced-typing.e2e.test.ts +8 -8
- package/src/queue-delete-message.e2e.test.ts +3 -3
- package/src/queue-drain-after-interactive-ui.e2e.test.ts +5 -5
- package/src/queue-interrupt-drain.e2e.test.ts +133 -12
- package/src/queue-question-select-drain.e2e.test.ts +36 -161
- package/src/runtime-lifecycle.e2e.test.ts +21 -54
- package/src/schema.sql +15 -2
- package/src/schema.ts +37 -1
- package/src/session-handler/event-stream-state.test.ts +790 -7
- package/src/session-handler/event-stream-state.ts +568 -53
- package/src/session-handler/model-utils.test.ts +537 -0
- package/src/session-handler/model-utils.ts +281 -5
- package/src/session-handler/opencode-session-event-log.ts +31 -1
- package/src/session-handler/thread-ingress.test.ts +54 -0
- package/src/session-handler/thread-runtime-state.ts +3 -0
- package/src/session-handler/thread-session-runtime-summary.test.ts +169 -0
- package/src/session-handler/thread-session-runtime.ts +712 -358
- package/src/session-search.test.ts +281 -0
- package/src/session-search.ts +241 -7
- package/src/session-sleep.test.ts +294 -0
- package/src/session-title-rename.test.ts +54 -48
- package/src/sleep.e2e.test.ts +565 -0
- package/src/startup-time.e2e.test.ts +5 -1
- package/src/store.ts +22 -0
- package/src/system-message.test.ts +226 -15
- package/src/system-message.ts +130 -16
- package/src/task-runner.ts +138 -3
- package/src/task-schedule.test.ts +170 -0
- package/src/task-schedule.ts +141 -1
- package/src/test-utils.ts +21 -10
- package/src/thinking-utils.test.ts +46 -0
- package/src/thinking-utils.ts +21 -0
- package/src/thread-message-queue.e2e.test.ts +65 -245
- package/src/undo-redo.e2e.test.ts +17 -9
- package/src/voice-attachment.ts +29 -1
- package/src/voice-handler.ts +23 -4
- package/src/voice-message.e2e.test.ts +149 -29
- package/src/voice-routing-input.e2e.test.ts +203 -0
- package/src/voice-routing-question.e2e.test.ts +131 -0
- package/src/voice.test.ts +107 -29
- package/src/voice.ts +34 -6
- package/src/worktree-clone-isolation.e2e.test.ts +213 -0
- package/src/worktree-lifecycle.e2e.test.ts +55 -35
- package/src/worktrees.test.ts +505 -64
- package/src/worktrees.ts +257 -603
- package/src/xai-auth-plugin.ts +1 -0
- package/src/xai-auth-state.ts +1 -0
- package/dist/message-finish-field.e2e.test.js +0 -169
- package/dist/subagent-rate-limit-plugin.js +0 -184
- package/src/message-finish-field.e2e.test.ts +0 -196
- package/src/subagent-rate-limit-plugin.ts +0 -228
package/dist/commands/login.js
CHANGED
|
@@ -12,6 +12,7 @@ import { ChatInputCommandInteraction, StringSelectMenuInteraction, StringSelectM
|
|
|
12
12
|
import crypto from 'node:crypto';
|
|
13
13
|
import { initializeOpencodeForDirectory, getOpencodeServerPort, getOpencodeServerAuthHeaders, } from '../opencode.js';
|
|
14
14
|
import { resolveTextChannel, getKimakiMetadata } from '../discord-utils.js';
|
|
15
|
+
import { clearModelListCache } from '../session-handler/model-utils.js';
|
|
15
16
|
import { createLogger, LogPrefix } from '../logger.js';
|
|
16
17
|
import { buildPaginatedOptions, parsePaginationValue } from './paginated-select.js';
|
|
17
18
|
const loginLogger = createLogger(LogPrefix.LOGIN);
|
|
@@ -31,7 +32,9 @@ function createContextHash(context) {
|
|
|
31
32
|
// ── Provider popularity order ───────────────────────────────────
|
|
32
33
|
// Discord select menus cap at 25 options, so we show popular ones first.
|
|
33
34
|
// IDs sourced from opencode's provider.list() API (scripts/list-providers.ts).
|
|
35
|
+
// `subrouter` leads: it pools every subscription and cycles across them.
|
|
34
36
|
const PROVIDER_POPULARITY_ORDER = [
|
|
37
|
+
'subrouter',
|
|
35
38
|
'anthropic',
|
|
36
39
|
'openai',
|
|
37
40
|
'google',
|
|
@@ -691,6 +694,7 @@ export async function handleOAuthCodeModalSubmit(interaction) {
|
|
|
691
694
|
return;
|
|
692
695
|
}
|
|
693
696
|
await getClient().instance.dispose({ directory: ctx.dir });
|
|
697
|
+
clearModelListCache();
|
|
694
698
|
pendingLoginContexts.delete(hash);
|
|
695
699
|
await interaction.editReply({
|
|
696
700
|
content: `✅ **Successfully authenticated with ${ctx.providerName}!**\n\nYou can now use models from this provider.`,
|
|
@@ -736,6 +740,7 @@ export async function handleApiKeyModalSubmit(interaction) {
|
|
|
736
740
|
});
|
|
737
741
|
// Dispose to refresh provider state so new credentials are recognized
|
|
738
742
|
await getClient().instance.dispose({ directory: ctx.dir });
|
|
743
|
+
clearModelListCache();
|
|
739
744
|
await interaction.editReply({
|
|
740
745
|
content: `✅ **Successfully authenticated with ${ctx.providerName}!**\n\nYou can now use models from this provider.`,
|
|
741
746
|
});
|
|
@@ -870,6 +875,7 @@ async function startOAuthFlow(interaction, ctx, hash) {
|
|
|
870
875
|
return;
|
|
871
876
|
}
|
|
872
877
|
await getClient().instance.dispose({ directory: ctx.dir });
|
|
878
|
+
clearModelListCache();
|
|
873
879
|
pendingLoginContexts.delete(hash);
|
|
874
880
|
await interaction.editReply({
|
|
875
881
|
content: `✅ **Successfully authenticated with ${ctx.providerName}!**\n\nYou can now use models from this provider.`,
|
|
@@ -1,23 +1,30 @@
|
|
|
1
|
-
// /merge-worktree command -
|
|
2
|
-
//
|
|
3
|
-
// Preserves all commits (no squash). On rebase conflicts, asks the AI model
|
|
4
|
-
// in the thread to resolve them.
|
|
1
|
+
// /merge-worktree command - Rebase or squash worktree commits into a target branch.
|
|
2
|
+
// On rebase conflicts, asks the AI model in the thread to resolve them.
|
|
5
3
|
import {} from 'discord.js';
|
|
6
4
|
import { getThreadWorktreeOrWorkspace, getThreadSession, getChannelDirectory, } from '../database.js';
|
|
7
5
|
import { createLogger, LogPrefix } from '../logger.js';
|
|
8
6
|
import { notifyError } from '../sentry.js';
|
|
9
|
-
import { mergeWorktree, listBranchesByLastCommit, validateBranchRef } from '../worktrees.js';
|
|
7
|
+
import { mergeWorktree, listBranchesByLastCommit, validateBranchRef, formatMergeWorktreeError, } from '../worktrees.js';
|
|
10
8
|
import { sendThreadMessage, resolveWorkingDirectory, resolveProjectDirectoryFromAutocomplete, } from '../discord-utils.js';
|
|
11
9
|
import { getOrCreateRuntime, } from '../session-handler/thread-session-runtime.js';
|
|
12
10
|
import { RebaseConflictError, DirtyWorktreeError, TargetDirtyWorktreeError, NothingToMergeError, } from '../errors.js';
|
|
11
|
+
import { WORKTREE_PREFIX } from '../message-formatting.js';
|
|
13
12
|
const logger = createLogger(LogPrefix.WORKTREE);
|
|
14
|
-
|
|
15
|
-
|
|
13
|
+
function quoteShellArg(value) {
|
|
14
|
+
return `'${value.replaceAll("'", "'\\''")}'`;
|
|
15
|
+
}
|
|
16
|
+
export { WORKTREE_PREFIX };
|
|
17
|
+
function worktreePrefixLength(name) {
|
|
18
|
+
if (name.startsWith(WORKTREE_PREFIX))
|
|
19
|
+
return WORKTREE_PREFIX.length;
|
|
20
|
+
return 0;
|
|
21
|
+
}
|
|
16
22
|
async function removeWorktreePrefixFromTitle(thread) {
|
|
17
|
-
|
|
23
|
+
const prefixLength = worktreePrefixLength(thread.name);
|
|
24
|
+
if (!prefixLength) {
|
|
18
25
|
return;
|
|
19
26
|
}
|
|
20
|
-
const newName = thread.name.slice(
|
|
27
|
+
const newName = thread.name.slice(prefixLength);
|
|
21
28
|
const timeoutMs = 5000;
|
|
22
29
|
await Promise.race([
|
|
23
30
|
thread.setName(newName).catch((e) => {
|
|
@@ -73,6 +80,14 @@ export async function handleMergeWorktreeCommand({ command, appId, }) {
|
|
|
73
80
|
await command.editReply(`Worktree is not ready (status: ${info.status})${info.error_message ? `: ${info.error_message}` : ''}`);
|
|
74
81
|
return;
|
|
75
82
|
}
|
|
83
|
+
const strategyOption = command.options.getString('strategy') || 'rebase';
|
|
84
|
+
if (strategyOption !== 'rebase' && strategyOption !== 'squash') {
|
|
85
|
+
await command.editReply(`Invalid merge strategy: \`${strategyOption}\``);
|
|
86
|
+
return;
|
|
87
|
+
}
|
|
88
|
+
const strategyName = strategyOption === 'squash'
|
|
89
|
+
? 'Squash into one commit'
|
|
90
|
+
: 'Keep commits (rebase)';
|
|
76
91
|
const rawTargetBranch = command.options.getString('target-branch') || undefined;
|
|
77
92
|
let targetBranch = rawTargetBranch;
|
|
78
93
|
if (targetBranch) {
|
|
@@ -91,6 +106,7 @@ export async function handleMergeWorktreeCommand({ command, appId, }) {
|
|
|
91
106
|
mainRepoDir: info.project_directory,
|
|
92
107
|
worktreeName: info.workspace_name,
|
|
93
108
|
targetBranch,
|
|
109
|
+
strategy: strategyOption,
|
|
94
110
|
onProgress: (msg) => {
|
|
95
111
|
logger.log(`[merge] ${msg}`);
|
|
96
112
|
},
|
|
@@ -106,11 +122,22 @@ export async function handleMergeWorktreeCommand({ command, appId, }) {
|
|
|
106
122
|
}
|
|
107
123
|
if (result instanceof NothingToMergeError) {
|
|
108
124
|
void removeWorktreePrefixFromTitle(thread);
|
|
109
|
-
await command.editReply(
|
|
125
|
+
await command.editReply(formatMergeWorktreeError(result));
|
|
110
126
|
return;
|
|
111
127
|
}
|
|
112
128
|
if (result instanceof RebaseConflictError) {
|
|
113
|
-
|
|
129
|
+
const prefixLength = worktreePrefixLength(thread.name);
|
|
130
|
+
const mergedThreadName = prefixLength
|
|
131
|
+
? thread.name.slice(prefixLength)
|
|
132
|
+
: thread.name;
|
|
133
|
+
const mergeCommand = [
|
|
134
|
+
'kimaki merge-worktree',
|
|
135
|
+
`--strategy ${strategyOption}`,
|
|
136
|
+
`--target-branch ${quoteShellArg(String(result.target))}`,
|
|
137
|
+
`--thread ${quoteShellArg(thread.id)}`,
|
|
138
|
+
`--thread-name ${quoteShellArg(mergedThreadName)}`,
|
|
139
|
+
].join(' ');
|
|
140
|
+
await command.editReply(`Rebase conflict detected. Asking the model to resolve it and retry the merge with **${strategyName}**.`);
|
|
114
141
|
await sendPromptToModel({
|
|
115
142
|
prompt: [
|
|
116
143
|
`A rebase conflict occurred while merging this worktree into \`${result.target}\`.`,
|
|
@@ -124,7 +151,8 @@ export async function handleMergeWorktreeCommand({ command, appId, }) {
|
|
|
124
151
|
'6. Stage resolved files with `git add`',
|
|
125
152
|
'7. Continue the rebase with `git rebase --continue`',
|
|
126
153
|
'8. If git reports more conflicts, repeat steps 1-7 until the rebase finishes (no more rebase in progress, `git status` is clean)',
|
|
127
|
-
|
|
154
|
+
`9. Once the rebase is fully complete, run \`${mergeCommand}\` yourself`,
|
|
155
|
+
'10. If that command fails because the target changed or is not ready, stop and report the failure. The user can run it again later.',
|
|
128
156
|
].join('\n'),
|
|
129
157
|
thread,
|
|
130
158
|
projectDirectory: info.project_directory,
|
|
@@ -133,11 +161,16 @@ export async function handleMergeWorktreeCommand({ command, appId, }) {
|
|
|
133
161
|
});
|
|
134
162
|
return;
|
|
135
163
|
}
|
|
136
|
-
|
|
164
|
+
const formatted = formatMergeWorktreeError(result);
|
|
165
|
+
logger.error(formatted);
|
|
166
|
+
await command.editReply(formatted);
|
|
137
167
|
return;
|
|
138
168
|
}
|
|
139
169
|
void removeWorktreePrefixFromTitle(thread);
|
|
140
|
-
|
|
170
|
+
const mergeSummary = strategyOption === 'squash'
|
|
171
|
+
? `Squashed \`${result.branchName}\` into \`${result.defaultBranch}\` @ ${result.shortSha} (${result.commitCount} source commit${result.commitCount === 1 ? '' : 's'} → 1)`
|
|
172
|
+
: `Merged \`${result.branchName}\` into \`${result.defaultBranch}\` @ ${result.shortSha} (${result.commitCount} commit${result.commitCount === 1 ? '' : 's'})`;
|
|
173
|
+
await command.editReply(`${mergeSummary}\nWorktree now at detached HEAD.`);
|
|
141
174
|
}
|
|
142
175
|
/**
|
|
143
176
|
* Autocomplete handler for /merge-worktree target-branch option.
|
|
@@ -13,6 +13,7 @@ import { initializeOpencodeForDirectory } from '../opencode.js';
|
|
|
13
13
|
import { resolveTextChannel, getKimakiMetadata } from '../discord-utils.js';
|
|
14
14
|
import { getCurrentModelInfo, ensureSessionPreferencesSnapshot, applyToCurrentSession, } from './model.js';
|
|
15
15
|
import { getThinkingValuesForModel } from '../thinking-utils.js';
|
|
16
|
+
import { resolveDisplayedModelId } from '../session-handler/model-utils.js';
|
|
16
17
|
import { createLogger, LogPrefix } from '../logger.js';
|
|
17
18
|
const logger = createLogger(LogPrefix.MODEL);
|
|
18
19
|
const pendingVariantContexts = new Map();
|
|
@@ -137,6 +138,12 @@ export async function showModelVariantPicker({ channel, appId, editReply, }) {
|
|
|
137
138
|
const { providerID, modelID, model: fullModelId } = currentModelInfo;
|
|
138
139
|
const sourceLabel = formatSourceLabel(currentModelInfo);
|
|
139
140
|
const variantLabel = cascadeVariant ? ` (${cascadeVariant})` : '';
|
|
141
|
+
const displayedModelId = (await resolveDisplayedModelId({
|
|
142
|
+
providers: providersResponse.data.all,
|
|
143
|
+
providerID,
|
|
144
|
+
modelID,
|
|
145
|
+
sessionID: sessionId,
|
|
146
|
+
})) ?? fullModelId;
|
|
140
147
|
const provider = providersResponse.data.all.find((p) => {
|
|
141
148
|
return p.id === providerID;
|
|
142
149
|
});
|
|
@@ -146,7 +153,7 @@ export async function showModelVariantPicker({ channel, appId, editReply, }) {
|
|
|
146
153
|
providerId: providerID,
|
|
147
154
|
modelId: modelID,
|
|
148
155
|
});
|
|
149
|
-
const statusText = `**Current model:** \`${
|
|
156
|
+
const statusText = `**Current model:** \`${displayedModelId}\`${variantLabel} — ${sourceLabel}`;
|
|
150
157
|
if (variants.length === 0) {
|
|
151
158
|
await editReply({
|
|
152
159
|
content: `${statusText}\nThis model doesn't support thinking level variants.`,
|
package/dist/commands/model.js
CHANGED
|
@@ -4,7 +4,7 @@ import crypto from 'node:crypto';
|
|
|
4
4
|
import { setChannelModel, setSessionModel, setSessionAgent, getChannelModel, getSessionModel, getSessionAgent, getChannelAgent, getThreadSession, getGlobalModel, setGlobalModel, getVariantCascade, } from '../database.js';
|
|
5
5
|
import { initializeOpencodeForDirectory } from '../opencode.js';
|
|
6
6
|
import { resolveTextChannel, getKimakiMetadata } from '../discord-utils.js';
|
|
7
|
-
import { getDefaultModel } from '../session-handler/model-utils.js';
|
|
7
|
+
import { getDefaultModel, resolveDisplayedModelId, } from '../session-handler/model-utils.js';
|
|
8
8
|
import { getRuntime } from '../session-handler/thread-session-runtime.js';
|
|
9
9
|
import { getThinkingValuesForModel } from '../thinking-utils.js';
|
|
10
10
|
import { buildHtmlActionCustomId, cancelHtmlActionsForOwner, registerHtmlAction, } from '../html-actions.js';
|
|
@@ -321,22 +321,29 @@ export async function handleModelCommand({ interaction, appId, }) {
|
|
|
321
321
|
});
|
|
322
322
|
return;
|
|
323
323
|
}
|
|
324
|
-
const currentModelText = (() => {
|
|
324
|
+
const currentModelText = await (async () => {
|
|
325
|
+
if (currentModelInfo.type === 'none') {
|
|
326
|
+
return '**Current:** none';
|
|
327
|
+
}
|
|
328
|
+
const displayed = (await resolveDisplayedModelId({
|
|
329
|
+
providers: allProviders,
|
|
330
|
+
providerID: currentModelInfo.providerID,
|
|
331
|
+
modelID: currentModelInfo.modelID,
|
|
332
|
+
sessionID: sessionId,
|
|
333
|
+
})) ?? currentModelInfo.model;
|
|
325
334
|
switch (currentModelInfo.type) {
|
|
326
335
|
case 'session':
|
|
327
|
-
return `**Current (this thread):** \`${
|
|
336
|
+
return `**Current (this thread):** \`${displayed}\``;
|
|
328
337
|
case 'agent':
|
|
329
|
-
return `**Current (agent "${currentModelInfo.agentName}"):** \`${
|
|
338
|
+
return `**Current (agent "${currentModelInfo.agentName}"):** \`${displayed}\``;
|
|
330
339
|
case 'channel':
|
|
331
|
-
return `**Current (channel override):** \`${
|
|
340
|
+
return `**Current (channel override):** \`${displayed}\``;
|
|
332
341
|
case 'global':
|
|
333
|
-
return `**Current (global default):** \`${
|
|
342
|
+
return `**Current (global default):** \`${displayed}\``;
|
|
334
343
|
case 'opencode-config':
|
|
335
344
|
case 'opencode-recent':
|
|
336
345
|
case 'opencode-provider-default':
|
|
337
|
-
return `**Current (opencode default):** \`${
|
|
338
|
-
case 'none':
|
|
339
|
-
return '**Current:** none';
|
|
346
|
+
return `**Current (opencode default):** \`${displayed}\``;
|
|
340
347
|
}
|
|
341
348
|
})();
|
|
342
349
|
const variantText = (() => {
|
|
@@ -2,25 +2,26 @@
|
|
|
2
2
|
// Uses OpenCode SDK v2 to create worktrees with kimaki- prefix
|
|
3
3
|
// Creates thread immediately, then worktree in background so user can type
|
|
4
4
|
import { ChannelType, REST, } from 'discord.js';
|
|
5
|
+
import crypto from 'node:crypto';
|
|
5
6
|
import fs from 'node:fs';
|
|
6
7
|
import { OpenCodeSdkError } from '../errors.js';
|
|
7
|
-
import { createPendingWorkspace, setWorkspaceReady, setWorkspaceError, getChannelDirectory, getThreadSession,
|
|
8
|
+
import { createPendingWorkspace, setWorkspaceReady, setWorkspaceError, getChannelDirectory, getThreadSession, setThreadSession, } from '../database.js';
|
|
8
9
|
import { SILENT_MESSAGE_FLAGS, reactToThread, resolveProjectDirectoryFromAutocomplete, resolveTextChannel, sendThreadMessage, } from '../discord-utils.js';
|
|
9
10
|
import { createLogger, LogPrefix } from '../logger.js';
|
|
10
11
|
import { notifyError } from '../sentry.js';
|
|
11
|
-
import { execAsync, listBranchesByLastCommit, resolveBestBaseRef, validateBranchRef, } from '../worktrees.js';
|
|
12
|
+
import { execAsync, getManagedWorktreeDirectory, listBranchesByLastCommit, resolveBestBaseRef, validateBranchRef, } from '../worktrees.js';
|
|
13
|
+
import { KIMAKI_WORKTREE_ADAPTER_TYPE, removeWorktreeFromOwnRepository, resolveGitCommit, validateWorktreeIdentity, } from '../git-worktree-core.js';
|
|
12
14
|
import { getOrCreateRuntime } from '../session-handler/thread-session-runtime.js';
|
|
13
15
|
import { buildSessionPermissions, initializeOpencodeForDirectory, } from '../opencode.js';
|
|
14
16
|
import { WORKTREE_PREFIX } from './merge-worktree.js';
|
|
15
17
|
import * as errore from 'errore';
|
|
16
18
|
import { copyCurrentSessionModel } from './model.js';
|
|
17
19
|
const logger = createLogger(LogPrefix.WORKTREE);
|
|
18
|
-
const DEFAULT_WORKTREE_BASE_REF = 'HEAD';
|
|
19
20
|
async function resolveRequestedWorktreeBaseRef({ projectDirectory, rawBaseBranch, }) {
|
|
20
21
|
if (!rawBaseBranch) {
|
|
21
22
|
// Default to the current local HEAD so worktrees can branch from
|
|
22
23
|
// unpublished commits in the main checkout.
|
|
23
|
-
return
|
|
24
|
+
return resolveGitCommit({ directory: projectDirectory, ref: 'HEAD' });
|
|
24
25
|
}
|
|
25
26
|
const validated = await validateBranchRef({
|
|
26
27
|
directory: projectDirectory,
|
|
@@ -37,7 +38,7 @@ async function resolveRequestedWorktreeBaseRef({ projectDirectory, rawBaseBranch
|
|
|
37
38
|
if (bestRef !== validated) {
|
|
38
39
|
logger.log(`Base branch resolved: ${validated} → ${bestRef} (remote is ahead)`);
|
|
39
40
|
}
|
|
40
|
-
return bestRef;
|
|
41
|
+
return resolveGitCommit({ directory: projectDirectory, ref: bestRef });
|
|
41
42
|
}
|
|
42
43
|
/** Status message shown while a worktree is being created. */
|
|
43
44
|
export function worktreeCreatingMessage(worktreeName) {
|
|
@@ -153,28 +154,83 @@ async function getProjectDirectoryFromChannel(channel) {
|
|
|
153
154
|
/**
|
|
154
155
|
* Try creating a worktree via the OpenCode workspace SDK.
|
|
155
156
|
* Returns the workspace directory on success, or an Error if the workspace
|
|
156
|
-
* feature is not available or the creation fails.
|
|
157
|
-
* direct git path on Error.
|
|
157
|
+
* feature is not available or the creation fails.
|
|
158
158
|
*/
|
|
159
|
-
async function tryWorkspaceCreate({
|
|
159
|
+
export async function tryWorkspaceCreate({ worktreeName, projectDirectory, baseCommit, }) {
|
|
160
160
|
const getClient = await initializeOpencodeForDirectory(projectDirectory);
|
|
161
161
|
if (getClient instanceof Error)
|
|
162
162
|
return getClient;
|
|
163
163
|
const client = getClient();
|
|
164
|
+
const workspaceId = `wrk_${crypto.randomUUID()}`;
|
|
165
|
+
const managedDirectory = getManagedWorktreeDirectory({
|
|
166
|
+
directory: projectDirectory,
|
|
167
|
+
name: worktreeName,
|
|
168
|
+
});
|
|
169
|
+
const cleanupFailedWorkspace = async (worktreeDirectory) => {
|
|
170
|
+
const removeResponse = await client.experimental.workspace.remove({
|
|
171
|
+
id: workspaceId,
|
|
172
|
+
directory: projectDirectory,
|
|
173
|
+
}).catch((e) => new OpenCodeSdkError({ operation: 'workspace.remove', cause: e }));
|
|
174
|
+
const sdkCleanupError = (() => {
|
|
175
|
+
if (removeResponse instanceof Error)
|
|
176
|
+
return removeResponse;
|
|
177
|
+
if (removeResponse.error) {
|
|
178
|
+
return new Error(`Workspace cleanup failed: ${JSON.stringify(removeResponse.error)}`);
|
|
179
|
+
}
|
|
180
|
+
return undefined;
|
|
181
|
+
})();
|
|
182
|
+
const gitCleanupError = fs.existsSync(worktreeDirectory)
|
|
183
|
+
? await removeWorktreeFromOwnRepository({
|
|
184
|
+
worktreeDirectory,
|
|
185
|
+
branchName: worktreeName,
|
|
186
|
+
})
|
|
187
|
+
: undefined;
|
|
188
|
+
return sdkCleanupError ?? gitCleanupError;
|
|
189
|
+
};
|
|
164
190
|
const response = await client.experimental.workspace.create({
|
|
191
|
+
id: workspaceId,
|
|
165
192
|
directory: projectDirectory,
|
|
166
|
-
type:
|
|
193
|
+
type: KIMAKI_WORKTREE_ADAPTER_TYPE,
|
|
167
194
|
branch: worktreeName,
|
|
168
|
-
extra:
|
|
195
|
+
extra: {
|
|
196
|
+
projectDirectory,
|
|
197
|
+
baseCommit,
|
|
198
|
+
},
|
|
169
199
|
}).catch((e) => new OpenCodeSdkError({ operation: 'workspace.create', cause: e }));
|
|
170
|
-
if (response instanceof Error)
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
200
|
+
if (response instanceof Error || response.error) {
|
|
201
|
+
const creationError = response instanceof Error
|
|
202
|
+
? response
|
|
203
|
+
: new Error(`Workspace creation failed: ${JSON.stringify(response.error)}`);
|
|
204
|
+
const cleanupError = await cleanupFailedWorkspace(managedDirectory);
|
|
205
|
+
if (cleanupError instanceof Error) {
|
|
206
|
+
return new Error(`${creationError.message}; cleanup failed: ${cleanupError.message}`, {
|
|
207
|
+
cause: creationError,
|
|
208
|
+
});
|
|
209
|
+
}
|
|
210
|
+
return creationError;
|
|
174
211
|
}
|
|
175
212
|
const workspace = response.data;
|
|
176
213
|
if (!workspace?.directory || !workspace.id) {
|
|
177
|
-
|
|
214
|
+
const creationError = new Error('Workspace SDK returned no directory or ID');
|
|
215
|
+
const cleanupError = await cleanupFailedWorkspace(managedDirectory);
|
|
216
|
+
if (cleanupError instanceof Error) {
|
|
217
|
+
return new Error(`${creationError.message}; cleanup failed: ${cleanupError.message}`, {
|
|
218
|
+
cause: creationError,
|
|
219
|
+
});
|
|
220
|
+
}
|
|
221
|
+
return creationError;
|
|
222
|
+
}
|
|
223
|
+
const identityResult = await validateWorktreeIdentity({
|
|
224
|
+
projectDirectory,
|
|
225
|
+
worktreeDirectory: workspace.directory,
|
|
226
|
+
baseCommit,
|
|
227
|
+
});
|
|
228
|
+
if (identityResult instanceof Error) {
|
|
229
|
+
const cleanupError = await cleanupFailedWorkspace(workspace.directory);
|
|
230
|
+
if (cleanupError instanceof Error) {
|
|
231
|
+
return new Error(`${identityResult.message}; cleanup failed: ${cleanupError.message}`, { cause: identityResult });
|
|
232
|
+
}
|
|
233
|
+
return identityResult;
|
|
178
234
|
}
|
|
179
235
|
return { directory: workspace.directory, workspaceId: workspace.id };
|
|
180
236
|
}
|
|
@@ -188,13 +244,13 @@ async function tryWorkspaceCreate({ threadId, worktreeName, projectDirectory, ba
|
|
|
188
244
|
* starterMessage is optional — if omitted, status edits are skipped (creation
|
|
189
245
|
* still proceeds). This keeps worktree creation independent of Discord message
|
|
190
246
|
* delivery, so a transient send failure never silently skips the worktree.
|
|
247
|
+
* beforeReady can bind a session before incoming messages may use the checkout.
|
|
191
248
|
*
|
|
192
249
|
* Returns the worktree directory on success, or an Error on failure.
|
|
193
250
|
* Never throws — all internal errors are caught and returned as Error values.
|
|
194
251
|
*/
|
|
195
|
-
export async function createWorktreeInBackground({ thread, starterMessage, worktreeName, projectDirectory,
|
|
252
|
+
export async function createWorktreeInBackground({ thread, starterMessage, worktreeName, projectDirectory, baseCommit, rest, beforeReady, }) {
|
|
196
253
|
return (async () => {
|
|
197
|
-
logger.log(`Creating worktree "${worktreeName}" for project ${projectDirectory}${baseBranch ? ` from ${baseBranch}` : ''}`);
|
|
198
254
|
// Serialize status message edits so onProgress can't overwrite the
|
|
199
255
|
// final success/error edit even if Discord's API is slow.
|
|
200
256
|
let editChain = Promise.resolve();
|
|
@@ -213,11 +269,23 @@ export async function createWorktreeInBackground({ thread, starterMessage, workt
|
|
|
213
269
|
workspaceName: worktreeName,
|
|
214
270
|
projectDirectory,
|
|
215
271
|
});
|
|
272
|
+
const resolvedBaseCommit = baseCommit
|
|
273
|
+
? baseCommit
|
|
274
|
+
: await resolveGitCommit({ directory: projectDirectory, ref: 'HEAD' });
|
|
275
|
+
if (resolvedBaseCommit instanceof Error) {
|
|
276
|
+
await setWorkspaceError({
|
|
277
|
+
threadId: thread.id,
|
|
278
|
+
errorMessage: resolvedBaseCommit.message,
|
|
279
|
+
});
|
|
280
|
+
editStatus(`🌳 **Worktree: ${worktreeName}**\n❌ ${resolvedBaseCommit.message}`);
|
|
281
|
+
await editChain;
|
|
282
|
+
return resolvedBaseCommit;
|
|
283
|
+
}
|
|
284
|
+
logger.log(`Creating worktree "${worktreeName}" for project ${projectDirectory} from ${resolvedBaseCommit}`);
|
|
216
285
|
const workspaceResult = await tryWorkspaceCreate({
|
|
217
|
-
threadId: thread.id,
|
|
218
286
|
worktreeName,
|
|
219
287
|
projectDirectory,
|
|
220
|
-
|
|
288
|
+
baseCommit: resolvedBaseCommit,
|
|
221
289
|
});
|
|
222
290
|
if (workspaceResult instanceof Error) {
|
|
223
291
|
const errorMsg = workspaceResult.message;
|
|
@@ -227,6 +295,14 @@ export async function createWorktreeInBackground({ thread, starterMessage, workt
|
|
|
227
295
|
await editChain;
|
|
228
296
|
return workspaceResult;
|
|
229
297
|
}
|
|
298
|
+
const preparationResult = beforeReady
|
|
299
|
+
? await beforeReady(workspaceResult).catch((e) => new WorktreeError('Failed to prepare worktree session', { cause: e }))
|
|
300
|
+
: undefined;
|
|
301
|
+
if (preparationResult instanceof Error) {
|
|
302
|
+
logger.error('[WORKTREE] Session preparation failed:', preparationResult);
|
|
303
|
+
void notifyError(preparationResult, 'Worktree session preparation failed');
|
|
304
|
+
await sendThreadMessage(thread, `Worktree is ready, but failed to prepare its session: ${preparationResult.message}`);
|
|
305
|
+
}
|
|
230
306
|
await setWorkspaceReady({
|
|
231
307
|
threadId: thread.id,
|
|
232
308
|
workspaceId: workspaceResult.workspaceId,
|
|
@@ -308,11 +384,11 @@ export async function handleNewWorktreeCommand({ command, appId, }) {
|
|
|
308
384
|
return;
|
|
309
385
|
}
|
|
310
386
|
// Parallelize: base branch validation and existing worktree check are independent
|
|
311
|
-
const [
|
|
387
|
+
const [baseCommit, existingWorktree] = await Promise.all([
|
|
312
388
|
resolveRequestedWorktreeBaseRef({ projectDirectory, rawBaseBranch }),
|
|
313
389
|
findExistingWorktreePath({ projectDirectory, worktreeName }),
|
|
314
390
|
]);
|
|
315
|
-
if (
|
|
391
|
+
if (baseCommit instanceof Error) {
|
|
316
392
|
await command.editReply(`Invalid base branch: \`${rawBaseBranch}\``);
|
|
317
393
|
return;
|
|
318
394
|
}
|
|
@@ -354,7 +430,7 @@ export async function handleNewWorktreeCommand({ command, appId, }) {
|
|
|
354
430
|
starterMessage,
|
|
355
431
|
worktreeName,
|
|
356
432
|
projectDirectory,
|
|
357
|
-
|
|
433
|
+
baseCommit,
|
|
358
434
|
rest: command.client.rest,
|
|
359
435
|
}).catch((e) => {
|
|
360
436
|
logger.error('[NEW-WORKTREE] Background error:', e);
|
|
@@ -391,12 +467,12 @@ async function handleWorktreeInThread({ command, thread, appId, }) {
|
|
|
391
467
|
// Parallelize: base branch validation, existing worktree check, and parent channel
|
|
392
468
|
// resolve are all independent. resolveTextChannel fetches the parent from Discord
|
|
393
469
|
// cache/API which can overlap with the git operations.
|
|
394
|
-
const [
|
|
470
|
+
const [baseCommit, existingWorktreePath, textChannel] = await Promise.all([
|
|
395
471
|
resolveRequestedWorktreeBaseRef({ projectDirectory, rawBaseBranch }),
|
|
396
472
|
findExistingWorktreePath({ projectDirectory, worktreeName }),
|
|
397
473
|
resolveTextChannel(thread),
|
|
398
474
|
]);
|
|
399
|
-
if (
|
|
475
|
+
if (baseCommit instanceof Error) {
|
|
400
476
|
await command.editReply(`Invalid base branch: \`${rawBaseBranch}\``);
|
|
401
477
|
return;
|
|
402
478
|
}
|
|
@@ -440,86 +516,78 @@ async function handleWorktreeInThread({ command, thread, appId, }) {
|
|
|
440
516
|
starterMessage: statusMessage,
|
|
441
517
|
worktreeName,
|
|
442
518
|
projectDirectory,
|
|
443
|
-
|
|
519
|
+
baseCommit,
|
|
444
520
|
rest: command.client.rest,
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
await
|
|
452
|
-
return;
|
|
453
|
-
}
|
|
454
|
-
const workspace = await getThreadWorktreeOrWorkspace(worktreeThread.id);
|
|
455
|
-
if (!workspace?.workspace_id) {
|
|
456
|
-
await sendThreadMessage(worktreeThread, '✗ Worktree is ready, but OpenCode returned no workspace ID for context reuse.');
|
|
457
|
-
return;
|
|
458
|
-
}
|
|
459
|
-
const getClient = await initializeOpencodeForDirectory(result, {
|
|
460
|
-
originalRepoDirectory: projectDirectory,
|
|
461
|
-
channelId: parent.id,
|
|
462
|
-
});
|
|
463
|
-
if (getClient instanceof Error) {
|
|
464
|
-
await sendThreadMessage(worktreeThread, `✗ Worktree is ready, but failed to initialize OpenCode for context reuse: ${getClient.message}`);
|
|
465
|
-
return;
|
|
466
|
-
}
|
|
467
|
-
const forkResponse = await getClient().session.fork({
|
|
468
|
-
sessionID: sourceSessionId,
|
|
469
|
-
directory: result,
|
|
470
|
-
workspace: workspace.workspace_id,
|
|
471
|
-
}).catch((e) => new OpenCodeSdkError({ operation: 'session.fork', cause: e }));
|
|
472
|
-
if (forkResponse instanceof Error) {
|
|
473
|
-
logger.error('[NEW-WORKTREE] Failed to fork session into worktree:', forkResponse);
|
|
474
|
-
void notifyError(forkResponse, 'Failed to fork session into worktree');
|
|
475
|
-
await sendThreadMessage(worktreeThread, `✗ Worktree is ready, but failed to reuse session context there: ${forkResponse.message}`);
|
|
476
|
-
return;
|
|
477
|
-
}
|
|
478
|
-
const forkedSession = forkResponse.data;
|
|
479
|
-
if (!forkedSession) {
|
|
480
|
-
const error = new Error('OpenCode did not return a forked session');
|
|
481
|
-
logger.error('[NEW-WORKTREE] Failed to fork session into worktree:', error);
|
|
482
|
-
void notifyError(error, 'Failed to fork session into worktree');
|
|
483
|
-
await sendThreadMessage(worktreeThread, `✗ Worktree is ready, but failed to reuse session context there: ${error.message}`);
|
|
484
|
-
return;
|
|
485
|
-
}
|
|
486
|
-
await copyCurrentSessionModel({
|
|
487
|
-
sourceSessionId,
|
|
488
|
-
targetSessionId: forkedSession.id,
|
|
489
|
-
channelId: parent.id,
|
|
490
|
-
appId,
|
|
491
|
-
getClient,
|
|
492
|
-
directory: projectDirectory,
|
|
493
|
-
});
|
|
494
|
-
const permissionResponse = await getClient().session.update({
|
|
495
|
-
sessionID: forkedSession.id,
|
|
496
|
-
directory: result,
|
|
497
|
-
permission: buildSessionPermissions({
|
|
498
|
-
directory: result,
|
|
521
|
+
beforeReady: async ({ directory, workspaceId }) => {
|
|
522
|
+
const sourceSessionId = await getThreadSession(thread.id);
|
|
523
|
+
if (!sourceSessionId) {
|
|
524
|
+
await sendThreadMessage(worktreeThread, 'Worktree is ready. Send a message here to start a fresh session in this checkout.');
|
|
525
|
+
return;
|
|
526
|
+
}
|
|
527
|
+
const getClient = await initializeOpencodeForDirectory(directory, {
|
|
499
528
|
originalRepoDirectory: projectDirectory,
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
529
|
+
channelId: parent.id,
|
|
530
|
+
});
|
|
531
|
+
if (getClient instanceof Error) {
|
|
532
|
+
await sendThreadMessage(worktreeThread, `✗ Worktree is ready, but failed to initialize OpenCode for context reuse: ${getClient.message}`);
|
|
533
|
+
return;
|
|
534
|
+
}
|
|
535
|
+
const forkResponse = await getClient().session.fork({
|
|
536
|
+
sessionID: sourceSessionId,
|
|
537
|
+
directory,
|
|
538
|
+
workspace: workspaceId,
|
|
539
|
+
}).catch((e) => new OpenCodeSdkError({ operation: 'session.fork', cause: e }));
|
|
540
|
+
if (forkResponse instanceof Error) {
|
|
541
|
+
logger.error('[NEW-WORKTREE] Failed to fork session into worktree:', forkResponse);
|
|
542
|
+
void notifyError(forkResponse, 'Failed to fork session into worktree');
|
|
543
|
+
await sendThreadMessage(worktreeThread, `✗ Worktree is ready, but failed to reuse session context there: ${forkResponse.message}`);
|
|
544
|
+
return;
|
|
545
|
+
}
|
|
546
|
+
const forkedSession = forkResponse.data;
|
|
547
|
+
if (!forkedSession) {
|
|
548
|
+
const error = new Error('OpenCode did not return a forked session');
|
|
549
|
+
logger.error('[NEW-WORKTREE] Failed to fork session into worktree:', error);
|
|
550
|
+
void notifyError(error, 'Failed to fork session into worktree');
|
|
551
|
+
await sendThreadMessage(worktreeThread, `✗ Worktree is ready, but failed to reuse session context there: ${error.message}`);
|
|
552
|
+
return;
|
|
553
|
+
}
|
|
554
|
+
await copyCurrentSessionModel({
|
|
555
|
+
sourceSessionId,
|
|
556
|
+
targetSessionId: forkedSession.id,
|
|
557
|
+
channelId: parent.id,
|
|
558
|
+
appId,
|
|
559
|
+
getClient,
|
|
560
|
+
directory: projectDirectory,
|
|
561
|
+
});
|
|
562
|
+
const permissionResponse = await getClient().session.update({
|
|
563
|
+
sessionID: forkedSession.id,
|
|
564
|
+
directory,
|
|
565
|
+
permission: buildSessionPermissions({
|
|
566
|
+
directory,
|
|
567
|
+
originalRepoDirectory: projectDirectory,
|
|
568
|
+
}),
|
|
569
|
+
}).catch((e) => new OpenCodeSdkError({ operation: 'session.update', cause: e }));
|
|
570
|
+
if (permissionResponse instanceof Error || permissionResponse.error) {
|
|
571
|
+
const error = permissionResponse instanceof Error
|
|
572
|
+
? permissionResponse
|
|
573
|
+
: new Error('OpenCode rejected forked session permission update');
|
|
574
|
+
logger.error('[NEW-WORKTREE] Failed to update forked session permissions:', error);
|
|
575
|
+
void notifyError(error, 'Failed to update forked session permissions');
|
|
576
|
+
await sendThreadMessage(worktreeThread, `✗ Worktree is ready, but failed to update forked session permissions: ${error.message}`);
|
|
577
|
+
return;
|
|
578
|
+
}
|
|
579
|
+
await setThreadSession(worktreeThread.id, forkedSession.id);
|
|
580
|
+
getOrCreateRuntime({
|
|
581
|
+
threadId: worktreeThread.id,
|
|
582
|
+
thread: worktreeThread,
|
|
583
|
+
projectDirectory,
|
|
584
|
+
sdkDirectory: directory,
|
|
585
|
+
channelId: parent.id,
|
|
586
|
+
appId,
|
|
587
|
+
});
|
|
588
|
+
await sendThreadMessage(worktreeThread, `Reusing context from <#${thread.id}> in worktree session \`${forkedSession.id}\`.`);
|
|
589
|
+
},
|
|
590
|
+
}).catch((e) => {
|
|
523
591
|
logger.error('[NEW-WORKTREE] Background error:', e);
|
|
524
592
|
void notifyError(e, 'Background worktree creation failed (in-thread)');
|
|
525
593
|
});
|
package/dist/commands/queue.js
CHANGED
|
@@ -6,6 +6,7 @@ import { resolveWorkingDirectory, SILENT_MESSAGE_FLAGS, } from '../discord-utils
|
|
|
6
6
|
import { getOrCreateRuntime, getRuntime, } from '../session-handler/thread-session-runtime.js';
|
|
7
7
|
import { buildHtmlActionCustomId, registerHtmlAction, } from '../html-actions.js';
|
|
8
8
|
import { createLogger, LogPrefix } from '../logger.js';
|
|
9
|
+
import { QUEUE_PREFIX } from '../message-formatting.js';
|
|
9
10
|
import { store } from '../store.js';
|
|
10
11
|
const logger = createLogger(LogPrefix.QUEUE);
|
|
11
12
|
function buildQueueRemoveRow({ threadId, queueId, position, }) {
|
|
@@ -108,7 +109,7 @@ export async function handleQueueCommand({ command, appId, }) {
|
|
|
108
109
|
});
|
|
109
110
|
return;
|
|
110
111
|
}
|
|
111
|
-
const responseText =
|
|
112
|
+
const responseText = `${QUEUE_PREFIX}**${command.user.displayName}:** ${message.slice(0, 1000)}${message.length > 1000 ? '...' : ''}`;
|
|
112
113
|
await command.reply({
|
|
113
114
|
content: responseText,
|
|
114
115
|
flags: SILENT_MESSAGE_FLAGS,
|
|
@@ -264,7 +265,7 @@ export async function handleQueueCommandCommand({ command, appId, }) {
|
|
|
264
265
|
logger.log(`[QUEUE] User ${command.user.displayName} queued command /${commandName} in thread ${channel.id}`);
|
|
265
266
|
return;
|
|
266
267
|
}
|
|
267
|
-
const responseText =
|
|
268
|
+
const responseText = `${QUEUE_PREFIX}**${command.user.displayName}:** ${displayText}`;
|
|
268
269
|
await command.reply({
|
|
269
270
|
content: responseText,
|
|
270
271
|
flags: SILENT_MESSAGE_FLAGS,
|