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
|
@@ -9,6 +9,7 @@ import {
|
|
|
9
9
|
type ThreadChannel,
|
|
10
10
|
type Message,
|
|
11
11
|
} from 'discord.js'
|
|
12
|
+
import crypto from 'node:crypto'
|
|
12
13
|
import fs from 'node:fs'
|
|
13
14
|
import { OpenCodeSdkError } from '../errors.js'
|
|
14
15
|
import type { CommandContext } from './types.js'
|
|
@@ -18,7 +19,6 @@ import {
|
|
|
18
19
|
setWorkspaceError,
|
|
19
20
|
getChannelDirectory,
|
|
20
21
|
getThreadSession,
|
|
21
|
-
getThreadWorktreeOrWorkspace,
|
|
22
22
|
setThreadSession,
|
|
23
23
|
} from '../database.js'
|
|
24
24
|
import {
|
|
@@ -32,10 +32,17 @@ import { createLogger, LogPrefix } from '../logger.js'
|
|
|
32
32
|
import { notifyError } from '../sentry.js'
|
|
33
33
|
import {
|
|
34
34
|
execAsync,
|
|
35
|
+
getManagedWorktreeDirectory,
|
|
35
36
|
listBranchesByLastCommit,
|
|
36
37
|
resolveBestBaseRef,
|
|
37
38
|
validateBranchRef,
|
|
38
39
|
} from '../worktrees.js'
|
|
40
|
+
import {
|
|
41
|
+
KIMAKI_WORKTREE_ADAPTER_TYPE,
|
|
42
|
+
removeWorktreeFromOwnRepository,
|
|
43
|
+
resolveGitCommit,
|
|
44
|
+
validateWorktreeIdentity,
|
|
45
|
+
} from '../git-worktree-core.js'
|
|
39
46
|
import { getOrCreateRuntime } from '../session-handler/thread-session-runtime.js'
|
|
40
47
|
import {
|
|
41
48
|
buildSessionPermissions,
|
|
@@ -47,8 +54,6 @@ import * as errore from 'errore'
|
|
|
47
54
|
import { copyCurrentSessionModel } from './model.js'
|
|
48
55
|
|
|
49
56
|
const logger = createLogger(LogPrefix.WORKTREE)
|
|
50
|
-
const DEFAULT_WORKTREE_BASE_REF = 'HEAD'
|
|
51
|
-
|
|
52
57
|
async function resolveRequestedWorktreeBaseRef({
|
|
53
58
|
projectDirectory,
|
|
54
59
|
rawBaseBranch,
|
|
@@ -59,7 +64,7 @@ async function resolveRequestedWorktreeBaseRef({
|
|
|
59
64
|
if (!rawBaseBranch) {
|
|
60
65
|
// Default to the current local HEAD so worktrees can branch from
|
|
61
66
|
// unpublished commits in the main checkout.
|
|
62
|
-
return
|
|
67
|
+
return resolveGitCommit({ directory: projectDirectory, ref: 'HEAD' })
|
|
63
68
|
}
|
|
64
69
|
|
|
65
70
|
const validated = await validateBranchRef({
|
|
@@ -77,7 +82,7 @@ async function resolveRequestedWorktreeBaseRef({
|
|
|
77
82
|
if (bestRef !== validated) {
|
|
78
83
|
logger.log(`Base branch resolved: ${validated} → ${bestRef} (remote is ahead)`)
|
|
79
84
|
}
|
|
80
|
-
return bestRef
|
|
85
|
+
return resolveGitCommit({ directory: projectDirectory, ref: bestRef })
|
|
81
86
|
}
|
|
82
87
|
|
|
83
88
|
/** Status message shown while a worktree is being created. */
|
|
@@ -211,37 +216,95 @@ async function getProjectDirectoryFromChannel(
|
|
|
211
216
|
/**
|
|
212
217
|
* Try creating a worktree via the OpenCode workspace SDK.
|
|
213
218
|
* Returns the workspace directory on success, or an Error if the workspace
|
|
214
|
-
* feature is not available or the creation fails.
|
|
215
|
-
* direct git path on Error.
|
|
219
|
+
* feature is not available or the creation fails.
|
|
216
220
|
*/
|
|
217
|
-
async function tryWorkspaceCreate({
|
|
218
|
-
threadId,
|
|
221
|
+
export async function tryWorkspaceCreate({
|
|
219
222
|
worktreeName,
|
|
220
223
|
projectDirectory,
|
|
221
|
-
|
|
224
|
+
baseCommit,
|
|
222
225
|
}: {
|
|
223
|
-
threadId: string
|
|
224
226
|
worktreeName: string
|
|
225
227
|
projectDirectory: string
|
|
226
|
-
|
|
228
|
+
baseCommit: string
|
|
227
229
|
}): Promise<{ directory: string; workspaceId: string } | Error> {
|
|
228
230
|
const getClient = await initializeOpencodeForDirectory(projectDirectory)
|
|
229
231
|
if (getClient instanceof Error) return getClient
|
|
230
232
|
|
|
231
233
|
const client = getClient()
|
|
234
|
+
const workspaceId = `wrk_${crypto.randomUUID()}`
|
|
235
|
+
const managedDirectory = getManagedWorktreeDirectory({
|
|
236
|
+
directory: projectDirectory,
|
|
237
|
+
name: worktreeName,
|
|
238
|
+
})
|
|
239
|
+
const cleanupFailedWorkspace = async (worktreeDirectory: string) => {
|
|
240
|
+
const removeResponse = await client.experimental.workspace.remove({
|
|
241
|
+
id: workspaceId,
|
|
242
|
+
directory: projectDirectory,
|
|
243
|
+
}).catch((e) => new OpenCodeSdkError({ operation: 'workspace.remove', cause: e }))
|
|
244
|
+
const sdkCleanupError = (() => {
|
|
245
|
+
if (removeResponse instanceof Error) return removeResponse
|
|
246
|
+
if (removeResponse.error) {
|
|
247
|
+
return new Error(`Workspace cleanup failed: ${JSON.stringify(removeResponse.error)}`)
|
|
248
|
+
}
|
|
249
|
+
return undefined
|
|
250
|
+
})()
|
|
251
|
+
const gitCleanupError = fs.existsSync(worktreeDirectory)
|
|
252
|
+
? await removeWorktreeFromOwnRepository({
|
|
253
|
+
worktreeDirectory,
|
|
254
|
+
branchName: worktreeName,
|
|
255
|
+
})
|
|
256
|
+
: undefined
|
|
257
|
+
return sdkCleanupError ?? gitCleanupError
|
|
258
|
+
}
|
|
259
|
+
|
|
232
260
|
const response = await client.experimental.workspace.create({
|
|
261
|
+
id: workspaceId,
|
|
233
262
|
directory: projectDirectory,
|
|
234
|
-
type:
|
|
263
|
+
type: KIMAKI_WORKTREE_ADAPTER_TYPE,
|
|
235
264
|
branch: worktreeName,
|
|
236
|
-
extra:
|
|
265
|
+
extra: {
|
|
266
|
+
projectDirectory,
|
|
267
|
+
baseCommit,
|
|
268
|
+
},
|
|
237
269
|
}).catch((e) => new OpenCodeSdkError({ operation: 'workspace.create', cause: e }))
|
|
238
|
-
if (response instanceof Error
|
|
239
|
-
|
|
240
|
-
|
|
270
|
+
if (response instanceof Error || response.error) {
|
|
271
|
+
const creationError = response instanceof Error
|
|
272
|
+
? response
|
|
273
|
+
: new Error(`Workspace creation failed: ${JSON.stringify(response.error)}`)
|
|
274
|
+
const cleanupError = await cleanupFailedWorkspace(managedDirectory)
|
|
275
|
+
if (cleanupError instanceof Error) {
|
|
276
|
+
return new Error(`${creationError.message}; cleanup failed: ${cleanupError.message}`, {
|
|
277
|
+
cause: creationError,
|
|
278
|
+
})
|
|
279
|
+
}
|
|
280
|
+
return creationError
|
|
241
281
|
}
|
|
242
282
|
const workspace = response.data
|
|
243
283
|
if (!workspace?.directory || !workspace.id) {
|
|
244
|
-
|
|
284
|
+
const creationError = new Error('Workspace SDK returned no directory or ID')
|
|
285
|
+
const cleanupError = await cleanupFailedWorkspace(managedDirectory)
|
|
286
|
+
if (cleanupError instanceof Error) {
|
|
287
|
+
return new Error(`${creationError.message}; cleanup failed: ${cleanupError.message}`, {
|
|
288
|
+
cause: creationError,
|
|
289
|
+
})
|
|
290
|
+
}
|
|
291
|
+
return creationError
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
const identityResult = await validateWorktreeIdentity({
|
|
295
|
+
projectDirectory,
|
|
296
|
+
worktreeDirectory: workspace.directory,
|
|
297
|
+
baseCommit,
|
|
298
|
+
})
|
|
299
|
+
if (identityResult instanceof Error) {
|
|
300
|
+
const cleanupError = await cleanupFailedWorkspace(workspace.directory)
|
|
301
|
+
if (cleanupError instanceof Error) {
|
|
302
|
+
return new Error(
|
|
303
|
+
`${identityResult.message}; cleanup failed: ${cleanupError.message}`,
|
|
304
|
+
{ cause: identityResult },
|
|
305
|
+
)
|
|
306
|
+
}
|
|
307
|
+
return identityResult
|
|
245
308
|
}
|
|
246
309
|
return { directory: workspace.directory, workspaceId: workspace.id }
|
|
247
310
|
}
|
|
@@ -256,6 +319,7 @@ async function tryWorkspaceCreate({
|
|
|
256
319
|
* starterMessage is optional — if omitted, status edits are skipped (creation
|
|
257
320
|
* still proceeds). This keeps worktree creation independent of Discord message
|
|
258
321
|
* delivery, so a transient send failure never silently skips the worktree.
|
|
322
|
+
* beforeReady can bind a session before incoming messages may use the checkout.
|
|
259
323
|
*
|
|
260
324
|
* Returns the worktree directory on success, or an Error on failure.
|
|
261
325
|
* Never throws — all internal errors are caught and returned as Error values.
|
|
@@ -265,21 +329,22 @@ export async function createWorktreeInBackground({
|
|
|
265
329
|
starterMessage,
|
|
266
330
|
worktreeName,
|
|
267
331
|
projectDirectory,
|
|
268
|
-
|
|
332
|
+
baseCommit,
|
|
269
333
|
rest,
|
|
334
|
+
beforeReady,
|
|
270
335
|
}: {
|
|
271
336
|
thread: ThreadChannel
|
|
272
337
|
starterMessage?: Message
|
|
273
338
|
worktreeName: string
|
|
274
339
|
projectDirectory: string
|
|
275
|
-
|
|
340
|
+
baseCommit?: string
|
|
276
341
|
rest: REST
|
|
342
|
+
beforeReady?: (workspace: {
|
|
343
|
+
directory: string
|
|
344
|
+
workspaceId: string
|
|
345
|
+
}) => Promise<void>
|
|
277
346
|
}): Promise<string | Error> {
|
|
278
347
|
return (async () => {
|
|
279
|
-
logger.log(
|
|
280
|
-
`Creating worktree "${worktreeName}" for project ${projectDirectory}${baseBranch ? ` from ${baseBranch}` : ''}`,
|
|
281
|
-
)
|
|
282
|
-
|
|
283
348
|
// Serialize status message edits so onProgress can't overwrite the
|
|
284
349
|
// final success/error edit even if Discord's API is slow.
|
|
285
350
|
let editChain: Promise<void> = Promise.resolve()
|
|
@@ -300,11 +365,26 @@ export async function createWorktreeInBackground({
|
|
|
300
365
|
projectDirectory,
|
|
301
366
|
})
|
|
302
367
|
|
|
368
|
+
const resolvedBaseCommit = baseCommit
|
|
369
|
+
? baseCommit
|
|
370
|
+
: await resolveGitCommit({ directory: projectDirectory, ref: 'HEAD' })
|
|
371
|
+
if (resolvedBaseCommit instanceof Error) {
|
|
372
|
+
await setWorkspaceError({
|
|
373
|
+
threadId: thread.id,
|
|
374
|
+
errorMessage: resolvedBaseCommit.message,
|
|
375
|
+
})
|
|
376
|
+
editStatus(`🌳 **Worktree: ${worktreeName}**\n❌ ${resolvedBaseCommit.message}`)
|
|
377
|
+
await editChain
|
|
378
|
+
return resolvedBaseCommit
|
|
379
|
+
}
|
|
380
|
+
logger.log(
|
|
381
|
+
`Creating worktree "${worktreeName}" for project ${projectDirectory} from ${resolvedBaseCommit}`,
|
|
382
|
+
)
|
|
383
|
+
|
|
303
384
|
const workspaceResult = await tryWorkspaceCreate({
|
|
304
|
-
threadId: thread.id,
|
|
305
385
|
worktreeName,
|
|
306
386
|
projectDirectory,
|
|
307
|
-
|
|
387
|
+
baseCommit: resolvedBaseCommit,
|
|
308
388
|
})
|
|
309
389
|
|
|
310
390
|
if (workspaceResult instanceof Error) {
|
|
@@ -316,6 +396,20 @@ export async function createWorktreeInBackground({
|
|
|
316
396
|
return workspaceResult
|
|
317
397
|
}
|
|
318
398
|
|
|
399
|
+
const preparationResult = beforeReady
|
|
400
|
+
? await beforeReady(workspaceResult).catch(
|
|
401
|
+
(e) => new WorktreeError('Failed to prepare worktree session', { cause: e }),
|
|
402
|
+
)
|
|
403
|
+
: undefined
|
|
404
|
+
if (preparationResult instanceof Error) {
|
|
405
|
+
logger.error('[WORKTREE] Session preparation failed:', preparationResult)
|
|
406
|
+
void notifyError(preparationResult, 'Worktree session preparation failed')
|
|
407
|
+
await sendThreadMessage(
|
|
408
|
+
thread,
|
|
409
|
+
`Worktree is ready, but failed to prepare its session: ${preparationResult.message}`,
|
|
410
|
+
)
|
|
411
|
+
}
|
|
412
|
+
|
|
319
413
|
await setWorkspaceReady({
|
|
320
414
|
threadId: thread.id,
|
|
321
415
|
workspaceId: workspaceResult.workspaceId,
|
|
@@ -434,11 +528,11 @@ export async function handleNewWorktreeCommand({
|
|
|
434
528
|
}
|
|
435
529
|
|
|
436
530
|
// Parallelize: base branch validation and existing worktree check are independent
|
|
437
|
-
const [
|
|
531
|
+
const [baseCommit, existingWorktree] = await Promise.all([
|
|
438
532
|
resolveRequestedWorktreeBaseRef({ projectDirectory, rawBaseBranch }),
|
|
439
533
|
findExistingWorktreePath({ projectDirectory, worktreeName }),
|
|
440
534
|
])
|
|
441
|
-
if (
|
|
535
|
+
if (baseCommit instanceof Error) {
|
|
442
536
|
await command.editReply(`Invalid base branch: \`${rawBaseBranch}\``)
|
|
443
537
|
return
|
|
444
538
|
}
|
|
@@ -489,7 +583,7 @@ export async function handleNewWorktreeCommand({
|
|
|
489
583
|
starterMessage,
|
|
490
584
|
worktreeName,
|
|
491
585
|
projectDirectory,
|
|
492
|
-
|
|
586
|
+
baseCommit,
|
|
493
587
|
rest: command.client.rest,
|
|
494
588
|
}).catch((e) => {
|
|
495
589
|
logger.error('[NEW-WORKTREE] Background error:', e)
|
|
@@ -543,12 +637,12 @@ async function handleWorktreeInThread({
|
|
|
543
637
|
// Parallelize: base branch validation, existing worktree check, and parent channel
|
|
544
638
|
// resolve are all independent. resolveTextChannel fetches the parent from Discord
|
|
545
639
|
// cache/API which can overlap with the git operations.
|
|
546
|
-
const [
|
|
640
|
+
const [baseCommit, existingWorktreePath, textChannel] = await Promise.all([
|
|
547
641
|
resolveRequestedWorktreeBaseRef({ projectDirectory, rawBaseBranch }),
|
|
548
642
|
findExistingWorktreePath({ projectDirectory, worktreeName }),
|
|
549
643
|
resolveTextChannel(thread),
|
|
550
644
|
])
|
|
551
|
-
if (
|
|
645
|
+
if (baseCommit instanceof Error) {
|
|
552
646
|
await command.editReply(`Invalid base branch: \`${rawBaseBranch}\``)
|
|
553
647
|
return
|
|
554
648
|
}
|
|
@@ -600,11 +694,9 @@ async function handleWorktreeInThread({
|
|
|
600
694
|
starterMessage: statusMessage,
|
|
601
695
|
worktreeName,
|
|
602
696
|
projectDirectory,
|
|
603
|
-
|
|
697
|
+
baseCommit,
|
|
604
698
|
rest: command.client.rest,
|
|
605
|
-
|
|
606
|
-
.then(async (result) => {
|
|
607
|
-
if (result instanceof Error) return
|
|
699
|
+
beforeReady: async ({ directory, workspaceId }) => {
|
|
608
700
|
const sourceSessionId = await getThreadSession(thread.id)
|
|
609
701
|
if (!sourceSessionId) {
|
|
610
702
|
await sendThreadMessage(
|
|
@@ -614,16 +706,7 @@ async function handleWorktreeInThread({
|
|
|
614
706
|
return
|
|
615
707
|
}
|
|
616
708
|
|
|
617
|
-
const
|
|
618
|
-
if (!workspace?.workspace_id) {
|
|
619
|
-
await sendThreadMessage(
|
|
620
|
-
worktreeThread,
|
|
621
|
-
'✗ Worktree is ready, but OpenCode returned no workspace ID for context reuse.',
|
|
622
|
-
)
|
|
623
|
-
return
|
|
624
|
-
}
|
|
625
|
-
|
|
626
|
-
const getClient = await initializeOpencodeForDirectory(result, {
|
|
709
|
+
const getClient = await initializeOpencodeForDirectory(directory, {
|
|
627
710
|
originalRepoDirectory: projectDirectory,
|
|
628
711
|
channelId: parent.id,
|
|
629
712
|
})
|
|
@@ -637,8 +720,8 @@ async function handleWorktreeInThread({
|
|
|
637
720
|
|
|
638
721
|
const forkResponse = await getClient().session.fork({
|
|
639
722
|
sessionID: sourceSessionId,
|
|
640
|
-
directory
|
|
641
|
-
workspace:
|
|
723
|
+
directory,
|
|
724
|
+
workspace: workspaceId,
|
|
642
725
|
}).catch((e) => new OpenCodeSdkError({ operation: 'session.fork', cause: e }))
|
|
643
726
|
if (forkResponse instanceof Error) {
|
|
644
727
|
logger.error('[NEW-WORKTREE] Failed to fork session into worktree:', forkResponse)
|
|
@@ -673,9 +756,9 @@ async function handleWorktreeInThread({
|
|
|
673
756
|
|
|
674
757
|
const permissionResponse = await getClient().session.update({
|
|
675
758
|
sessionID: forkedSession.id,
|
|
676
|
-
directory
|
|
759
|
+
directory,
|
|
677
760
|
permission: buildSessionPermissions({
|
|
678
|
-
directory
|
|
761
|
+
directory,
|
|
679
762
|
originalRepoDirectory: projectDirectory,
|
|
680
763
|
}),
|
|
681
764
|
}).catch((e) => new OpenCodeSdkError({ operation: 'session.update', cause: e }))
|
|
@@ -697,7 +780,7 @@ async function handleWorktreeInThread({
|
|
|
697
780
|
threadId: worktreeThread.id,
|
|
698
781
|
thread: worktreeThread,
|
|
699
782
|
projectDirectory,
|
|
700
|
-
sdkDirectory:
|
|
783
|
+
sdkDirectory: directory,
|
|
701
784
|
channelId: parent.id,
|
|
702
785
|
appId,
|
|
703
786
|
})
|
|
@@ -705,11 +788,11 @@ async function handleWorktreeInThread({
|
|
|
705
788
|
worktreeThread,
|
|
706
789
|
`Reusing context from <#${thread.id}> in worktree session \`${forkedSession.id}\`.`,
|
|
707
790
|
)
|
|
708
|
-
}
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
791
|
+
},
|
|
792
|
+
}).catch((e) => {
|
|
793
|
+
logger.error('[NEW-WORKTREE] Background error:', e)
|
|
794
|
+
void notifyError(e, 'Background worktree creation failed (in-thread)')
|
|
795
|
+
})
|
|
713
796
|
}
|
|
714
797
|
|
|
715
798
|
/**
|
package/src/commands/queue.ts
CHANGED
|
@@ -24,6 +24,7 @@ import {
|
|
|
24
24
|
registerHtmlAction,
|
|
25
25
|
} from '../html-actions.js'
|
|
26
26
|
import { createLogger, LogPrefix } from '../logger.js'
|
|
27
|
+
import { QUEUE_PREFIX } from '../message-formatting.js'
|
|
27
28
|
import { store } from '../store.js'
|
|
28
29
|
|
|
29
30
|
const logger = createLogger(LogPrefix.QUEUE)
|
|
@@ -157,7 +158,7 @@ export async function handleQueueCommand({
|
|
|
157
158
|
return
|
|
158
159
|
}
|
|
159
160
|
|
|
160
|
-
const responseText =
|
|
161
|
+
const responseText = `${QUEUE_PREFIX}**${command.user.displayName}:** ${message.slice(0, 1000)}${message.length > 1000 ? '...' : ''}`
|
|
161
162
|
await command.reply({
|
|
162
163
|
content: responseText,
|
|
163
164
|
flags: SILENT_MESSAGE_FLAGS,
|
|
@@ -352,7 +353,7 @@ export async function handleQueueCommandCommand({
|
|
|
352
353
|
return
|
|
353
354
|
}
|
|
354
355
|
|
|
355
|
-
const responseText =
|
|
356
|
+
const responseText = `${QUEUE_PREFIX}**${command.user.displayName}:** ${displayText}`
|
|
356
357
|
await command.reply({
|
|
357
358
|
content: responseText,
|
|
358
359
|
flags: SILENT_MESSAGE_FLAGS,
|
package/src/commands/resume.ts
CHANGED
|
@@ -17,6 +17,7 @@ import {
|
|
|
17
17
|
import { initializeOpencodeForDirectory } from '../opencode.js'
|
|
18
18
|
import {
|
|
19
19
|
sendThreadMessage,
|
|
20
|
+
sendSessionPartBatches,
|
|
20
21
|
resolveProjectDirectoryFromAutocomplete,
|
|
21
22
|
NOTIFY_MESSAGE_FLAGS,
|
|
22
23
|
} from '../discord-utils.js'
|
|
@@ -135,12 +136,12 @@ export async function handleResumeCommand({
|
|
|
135
136
|
}
|
|
136
137
|
|
|
137
138
|
const batched = batchChunksForDiscord(chunks)
|
|
138
|
-
|
|
139
|
-
|
|
139
|
+
const sent = await sendSessionPartBatches({ thread, batches: batched })
|
|
140
|
+
for (const batch of sent) {
|
|
140
141
|
await setPartMessagesBatch(
|
|
141
142
|
batch.partIds.map((partId) => ({
|
|
142
143
|
partId,
|
|
143
|
-
messageId:
|
|
144
|
+
messageId: batch.message.id,
|
|
144
145
|
threadId: thread.id,
|
|
145
146
|
})),
|
|
146
147
|
)
|
package/src/commands/session.ts
CHANGED
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
// Works in both text channels and threads. When used in a thread, the new
|
|
3
3
|
// session inherits the same working directory (worktree/workspace) so the
|
|
4
4
|
// user stays in the same folder context.
|
|
5
|
+
// Also owns the fresh-thread creation flow used by voice routing.
|
|
5
6
|
|
|
6
7
|
import { ChannelType, type TextChannel, type ThreadChannel } from 'discord.js'
|
|
7
8
|
import fs from 'node:fs'
|
|
@@ -15,6 +16,8 @@ import {
|
|
|
15
16
|
} from '../database.js'
|
|
16
17
|
import { initializeOpencodeForDirectory } from '../opencode.js'
|
|
17
18
|
import {
|
|
19
|
+
sendThreadMessage,
|
|
20
|
+
splitMarkdownForDiscord,
|
|
18
21
|
SILENT_MESSAGE_FLAGS,
|
|
19
22
|
resolveProjectDirectoryFromAutocomplete,
|
|
20
23
|
resolveWorkingDirectory,
|
|
@@ -22,10 +25,64 @@ import {
|
|
|
22
25
|
} from '../discord-utils.js'
|
|
23
26
|
import { getOrCreateRuntime } from '../session-handler/thread-session-runtime.js'
|
|
24
27
|
import { createLogger, LogPrefix } from '../logger.js'
|
|
25
|
-
import
|
|
28
|
+
import { DiscordOperationError } from '../errors.js'
|
|
26
29
|
|
|
27
30
|
const logger = createLogger(LogPrefix.SESSION)
|
|
28
31
|
|
|
32
|
+
// Create only: callers confirm the destination before dispatching the full prompt.
|
|
33
|
+
export async function createNewSessionThread({
|
|
34
|
+
textChannel,
|
|
35
|
+
projectDirectory,
|
|
36
|
+
sourceWorkspace,
|
|
37
|
+
prompt,
|
|
38
|
+
files = [],
|
|
39
|
+
userId,
|
|
40
|
+
}: {
|
|
41
|
+
textChannel: TextChannel
|
|
42
|
+
projectDirectory: string
|
|
43
|
+
sourceWorkspace?: Awaited<ReturnType<typeof getThreadWorktreeOrWorkspace>>
|
|
44
|
+
prompt: string
|
|
45
|
+
files?: string[]
|
|
46
|
+
userId: string
|
|
47
|
+
}): Promise<ThreadChannel | Error> {
|
|
48
|
+
const getClient = await initializeOpencodeForDirectory(projectDirectory)
|
|
49
|
+
if (getClient instanceof Error) return getClient
|
|
50
|
+
|
|
51
|
+
const [starter, ...remaining] = splitMarkdownForDiscord({
|
|
52
|
+
content: `**Starting OpenCode session**\n${prompt}${files.length > 0 ? `\nFiles: ${files.join(', ')}` : ''}`,
|
|
53
|
+
maxLength: 2000,
|
|
54
|
+
})
|
|
55
|
+
if (!starter) return new Error('No session request to send. Retry with /new-session and a prompt.')
|
|
56
|
+
const starterMessage = await textChannel.send({ content: starter, flags: SILENT_MESSAGE_FLAGS })
|
|
57
|
+
const thread = await starterMessage.startThread({
|
|
58
|
+
name: prompt.slice(0, 100),
|
|
59
|
+
autoArchiveDuration: 1440,
|
|
60
|
+
reason: 'OpenCode session',
|
|
61
|
+
})
|
|
62
|
+
for (const chunk of remaining) {
|
|
63
|
+
await sendThreadMessage(thread, chunk)
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
// Persist the directory association for later commands and bot restarts.
|
|
67
|
+
if (sourceWorkspace?.status === 'ready' && sourceWorkspace.workspace_directory) {
|
|
68
|
+
await createPendingWorkspace({
|
|
69
|
+
threadId: thread.id,
|
|
70
|
+
workspaceType: sourceWorkspace.workspace_type,
|
|
71
|
+
workspaceName: sourceWorkspace.workspace_name ?? '',
|
|
72
|
+
projectDirectory,
|
|
73
|
+
})
|
|
74
|
+
await setWorkspaceReady({
|
|
75
|
+
threadId: thread.id,
|
|
76
|
+
workspaceId: sourceWorkspace.workspace_id ?? undefined,
|
|
77
|
+
workspaceDirectory: sourceWorkspace.workspace_directory,
|
|
78
|
+
})
|
|
79
|
+
}
|
|
80
|
+
await thread.members.add(userId).catch((error) => {
|
|
81
|
+
logger.warn('Could not add session member:', error)
|
|
82
|
+
})
|
|
83
|
+
return thread
|
|
84
|
+
}
|
|
85
|
+
|
|
29
86
|
export async function handleSessionCommand({
|
|
30
87
|
command,
|
|
31
88
|
appId,
|
|
@@ -90,77 +147,44 @@ export async function handleSessionCommand({
|
|
|
90
147
|
return
|
|
91
148
|
}
|
|
92
149
|
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
return
|
|
98
|
-
}
|
|
99
|
-
|
|
100
|
-
const files = filesString
|
|
101
|
-
.split(',')
|
|
102
|
-
.map((f) => f.trim())
|
|
103
|
-
.filter((f) => f)
|
|
104
|
-
|
|
105
|
-
let fullPrompt = prompt
|
|
106
|
-
if (files.length > 0) {
|
|
107
|
-
fullPrompt = `${prompt}\n\n@${files.join(' @')}`
|
|
108
|
-
}
|
|
109
|
-
|
|
110
|
-
const starterMessage = await textChannel.send({
|
|
111
|
-
content: `**Starting OpenCode session**\n${prompt}${files.length > 0 ? `\nFiles: ${files.join(', ')}` : ''}`,
|
|
112
|
-
flags: SILENT_MESSAGE_FLAGS,
|
|
113
|
-
})
|
|
114
|
-
|
|
115
|
-
const thread = await starterMessage.startThread({
|
|
116
|
-
name: prompt.slice(0, 100),
|
|
117
|
-
autoArchiveDuration: 1440,
|
|
118
|
-
reason: 'OpenCode session',
|
|
119
|
-
})
|
|
120
|
-
|
|
121
|
-
// Persist workspace association so commands in the new thread resolve the
|
|
122
|
-
// correct working directory, and the runtime survives bot restarts.
|
|
123
|
-
if (sourceWorkspace?.status === 'ready' && sourceWorkspace.workspace_directory) {
|
|
124
|
-
await createPendingWorkspace({
|
|
125
|
-
threadId: thread.id,
|
|
126
|
-
workspaceType: sourceWorkspace.workspace_type,
|
|
127
|
-
workspaceName: sourceWorkspace.workspace_name ?? '',
|
|
128
|
-
projectDirectory,
|
|
129
|
-
})
|
|
130
|
-
await setWorkspaceReady({
|
|
131
|
-
threadId: thread.id,
|
|
132
|
-
workspaceId: sourceWorkspace.workspace_id ?? undefined,
|
|
133
|
-
workspaceDirectory: sourceWorkspace.workspace_directory,
|
|
134
|
-
})
|
|
135
|
-
}
|
|
136
|
-
|
|
137
|
-
// Add user to thread so it appears in their sidebar
|
|
138
|
-
await thread.members.add(command.user.id)
|
|
139
|
-
|
|
140
|
-
await command.editReply(`Created new session in ${thread.toString()}`)
|
|
150
|
+
const files = filesString
|
|
151
|
+
.split(',')
|
|
152
|
+
.map((f) => f.trim())
|
|
153
|
+
.filter((f) => f)
|
|
141
154
|
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
agent,
|
|
155
|
-
appId,
|
|
156
|
-
mode: 'opencode',
|
|
157
|
-
})
|
|
158
|
-
} catch (error) {
|
|
159
|
-
logger.error('[SESSION] Error:', error)
|
|
160
|
-
await command.editReply(
|
|
161
|
-
`Failed to create session: ${error instanceof Error ? error.message : 'Unknown error'}`,
|
|
162
|
-
)
|
|
155
|
+
const thread = await createNewSessionThread({
|
|
156
|
+
textChannel,
|
|
157
|
+
projectDirectory,
|
|
158
|
+
sourceWorkspace,
|
|
159
|
+
prompt,
|
|
160
|
+
files,
|
|
161
|
+
userId: command.user.id,
|
|
162
|
+
}).catch((cause) => new DiscordOperationError({ operation: 'createSessionThread', cause }))
|
|
163
|
+
if (thread instanceof Error) {
|
|
164
|
+
logger.error('New session creation failed:', thread)
|
|
165
|
+
await command.editReply(`${thread.message}. Retry with /new-session.`)
|
|
166
|
+
return
|
|
163
167
|
}
|
|
168
|
+
await command.editReply(`Created new session in ${thread.toString()}`)
|
|
169
|
+
const runtime = getOrCreateRuntime({
|
|
170
|
+
threadId: thread.id,
|
|
171
|
+
thread,
|
|
172
|
+
projectDirectory,
|
|
173
|
+
sdkDirectory,
|
|
174
|
+
channelId: textChannel.id,
|
|
175
|
+
appId,
|
|
176
|
+
})
|
|
177
|
+
await runtime.enqueueIncoming({
|
|
178
|
+
prompt: files.length > 0 ? `${prompt}\n\n@${files.join(' @')}` : prompt,
|
|
179
|
+
userId: command.user.id,
|
|
180
|
+
username: command.user.displayName,
|
|
181
|
+
agent,
|
|
182
|
+
appId,
|
|
183
|
+
mode: 'opencode',
|
|
184
|
+
}).catch(async (error) => {
|
|
185
|
+
logger.error('New session dispatch failed:', error)
|
|
186
|
+
await sendThreadMessage(thread, 'Could not send the request to OpenCode. Send your request again in this thread.')
|
|
187
|
+
})
|
|
164
188
|
}
|
|
165
189
|
|
|
166
190
|
async function handleAgentAutocomplete({
|
|
@@ -213,12 +213,12 @@ export const handleUserCommand: CommandHandler = async ({
|
|
|
213
213
|
})
|
|
214
214
|
}
|
|
215
215
|
|
|
216
|
-
const
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
216
|
+
const worktreeResult = worktreePromise ? await worktreePromise : projectDirectory
|
|
217
|
+
if (worktreeResult instanceof Error) {
|
|
218
|
+
await command.editReply(`Worktree creation failed: ${worktreeResult.message}`)
|
|
219
|
+
return
|
|
220
|
+
}
|
|
221
|
+
const sessionDirectory = worktreeResult
|
|
222
222
|
|
|
223
223
|
await command.editReply(
|
|
224
224
|
`Started /${commandName} in ${newThread.toString()}`,
|
|
@@ -28,13 +28,13 @@ import {
|
|
|
28
28
|
cancelHtmlActionsForOwner,
|
|
29
29
|
registerHtmlAction,
|
|
30
30
|
} from '../html-actions.js'
|
|
31
|
-
import * as errore from 'errore'
|
|
32
31
|
import crypto from 'node:crypto'
|
|
33
|
-
import {
|
|
32
|
+
import { OpenCodeSdkError } from '../errors.js'
|
|
34
33
|
import { resolveTextChannel, resolveWorkingDirectory } from '../discord-utils.js'
|
|
35
34
|
import { initializeOpencodeForDirectory } from '../opencode.js'
|
|
36
35
|
import {
|
|
37
36
|
deleteWorktree,
|
|
37
|
+
extractGitExecOutput,
|
|
38
38
|
git,
|
|
39
39
|
getDefaultBranch,
|
|
40
40
|
listGitWorktrees,
|
|
@@ -42,15 +42,9 @@ import {
|
|
|
42
42
|
} from '../worktrees.js'
|
|
43
43
|
import path from 'node:path'
|
|
44
44
|
|
|
45
|
-
// Extracts the git stderr from a deleteWorktree error via errore.findCause.
|
|
46
|
-
// Chain: Error { cause: GitCommandError { cause: CommandError { stderr } } }.
|
|
47
45
|
export function extractGitStderr(error: Error): string | undefined {
|
|
48
|
-
const
|
|
49
|
-
|
|
50
|
-
if (stderr && stderr.length > 0) {
|
|
51
|
-
return stderr
|
|
52
|
-
}
|
|
53
|
-
return undefined
|
|
46
|
+
const stderr = extractGitExecOutput(error).stderr
|
|
47
|
+
return stderr.length > 0 ? stderr : undefined
|
|
54
48
|
}
|
|
55
49
|
|
|
56
50
|
export function formatTimeAgo(date: Date): string {
|
|
@@ -152,8 +146,7 @@ async function getWorktreeGitStatus({
|
|
|
152
146
|
defaultBranch: string
|
|
153
147
|
}): Promise<WorktreeGitStatus | null> {
|
|
154
148
|
try {
|
|
155
|
-
// Use raw git calls so
|
|
156
|
-
// errors and returns false, which would render "merged" instead of "unknown".
|
|
149
|
+
// Use raw git calls so status and ahead-count share one timeout window.
|
|
157
150
|
const [statusResult, aheadResult] = await Promise.all([
|
|
158
151
|
git(directory, 'status --porcelain', { timeout: GIT_CMD_TIMEOUT }),
|
|
159
152
|
git(directory, `rev-list --count "${defaultBranch}..HEAD"`, {
|