kimaki 0.14.0 → 0.16.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/cli-commands/send.js +46 -15
- package/dist/cli-commands/session.js +53 -11
- package/dist/cli-runner.js +29 -3
- package/dist/commands/abort.js +9 -6
- package/dist/commands/add-dir.js +1 -1
- package/dist/commands/btw.js +27 -16
- package/dist/commands/compact.js +1 -1
- package/dist/commands/context-usage.js +16 -9
- package/dist/commands/fork.js +9 -11
- package/dist/commands/login.js +2 -9
- package/dist/commands/new-worktree.js +116 -129
- package/dist/commands/permissions.js +10 -6
- package/dist/commands/remove-project.js +5 -9
- package/dist/commands/undo-redo.js +17 -9
- package/dist/context-awareness-plugin.js +135 -147
- package/dist/discord-bot.js +38 -16
- package/dist/discord-command-registration.js +8 -0
- package/dist/discord-utils.js +23 -36
- package/dist/errors.js +48 -30
- package/dist/external-opencode-sync.js +2 -4
- package/dist/forum-sync/markdown.js +1 -4
- package/dist/genai-worker.js +3 -5
- package/dist/hrana-server.js +5 -8
- package/dist/html-components.js +2 -4
- package/dist/ipc-polling.js +11 -18
- package/dist/markdown.js +95 -100
- package/dist/memory-overview-plugin.js +45 -50
- package/dist/message-formatting.js +4 -9
- package/dist/message-preprocessing.js +5 -6
- package/dist/openai-auth-plugin.js +1 -0
- package/dist/opencode.js +50 -62
- package/dist/plugin-logger.js +37 -0
- package/dist/plugin-opencode-client.js +11 -0
- package/dist/session-handler/agent-utils.js +3 -4
- package/dist/session-handler/global-event-listener.js +8 -7
- package/dist/session-handler/model-utils.js +7 -10
- package/dist/session-handler/opencode-session-event-log.js +5 -7
- package/dist/session-handler/thread-session-runtime.js +163 -229
- package/dist/skill-filter.js +12 -0
- package/dist/skill-filter.test.js +22 -1
- package/dist/subagent-rate-limit-plugin.js +18 -20
- package/dist/system-message.js +10 -1
- package/dist/system-message.test.js +10 -1
- package/dist/task-runner.js +4 -8
- package/dist/task-schedule.js +21 -34
- package/dist/thread-message-queue.e2e.test.js +3 -0
- package/dist/voice-handler.js +10 -17
- package/dist/voice.js +8 -13
- package/dist/worktrees.js +40 -76
- package/package.json +8 -8
- package/skills/holocron/SKILL.md +8 -0
- package/src/cli-commands/send.ts +50 -15
- package/src/cli-commands/session.ts +66 -7
- package/src/cli-runner.ts +32 -2
- package/src/commands/abort.ts +9 -6
- package/src/commands/add-dir.ts +1 -1
- package/src/commands/btw.ts +34 -24
- package/src/commands/compact.ts +1 -1
- package/src/commands/context-usage.ts +18 -9
- package/src/commands/fork.ts +7 -6
- package/src/commands/login.ts +2 -8
- package/src/commands/new-worktree.ts +65 -85
- package/src/commands/permissions.ts +9 -8
- package/src/commands/remove-project.ts +6 -9
- package/src/commands/undo-redo.ts +17 -9
- package/src/context-awareness-plugin.ts +25 -38
- package/src/discord-bot.ts +46 -18
- package/src/discord-command-registration.ts +11 -0
- package/src/discord-utils.ts +16 -29
- package/src/errors.ts +49 -30
- package/src/external-opencode-sync.ts +2 -6
- package/src/forum-sync/markdown.ts +4 -4
- package/src/genai-worker.ts +4 -5
- package/src/hrana-server.ts +4 -4
- package/src/html-components.ts +2 -6
- package/src/ipc-polling.ts +9 -10
- package/src/markdown.ts +103 -110
- package/src/memory-overview-plugin.ts +9 -13
- package/src/message-formatting.ts +5 -9
- package/src/message-preprocessing.ts +5 -6
- package/src/openai-auth-plugin.ts +1 -0
- package/src/opencode.ts +34 -38
- package/src/plugin-logger.ts +55 -0
- package/src/plugin-opencode-client.ts +11 -0
- package/src/queue-advanced-e2e-setup.ts +3 -0
- package/src/session-handler/agent-utils.ts +4 -4
- package/src/session-handler/global-event-listener.ts +9 -7
- package/src/session-handler/model-utils.ts +7 -12
- package/src/session-handler/opencode-session-event-log.ts +6 -7
- package/src/session-handler/thread-session-runtime.ts +173 -232
- package/src/skill-filter.test.ts +28 -1
- package/src/skill-filter.ts +21 -0
- package/src/subagent-rate-limit-plugin.ts +6 -7
- package/src/system-message.test.ts +10 -1
- package/src/system-message.ts +10 -1
- package/src/task-runner.ts +4 -12
- package/src/task-schedule.ts +16 -24
- package/src/thread-message-queue.e2e.test.ts +4 -0
- package/src/voice-handler.ts +9 -16
- package/src/voice.ts +7 -12
- package/src/worktrees.ts +52 -106
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
// Creates thread immediately, then worktree in background so user can type
|
|
4
4
|
import { ChannelType, REST, } from 'discord.js';
|
|
5
5
|
import fs from 'node:fs';
|
|
6
|
+
import { OpenCodeSdkError } from '../errors.js';
|
|
6
7
|
import { createPendingWorktree, setWorktreeReady, setWorktreeError, getChannelDirectory, getThreadSession, setThreadSession, } from '../database.js';
|
|
7
8
|
import { SILENT_MESSAGE_FLAGS, reactToThread, resolveProjectDirectoryFromAutocomplete, resolveTextChannel, sendThreadMessage, } from '../discord-utils.js';
|
|
8
9
|
import { createLogger, LogPrefix } from '../logger.js';
|
|
@@ -149,72 +150,67 @@ async function getProjectDirectoryFromChannel(channel) {
|
|
|
149
150
|
* Never throws — all internal errors are caught and returned as Error values.
|
|
150
151
|
*/
|
|
151
152
|
export async function createWorktreeInBackground({ thread, starterMessage, worktreeName, projectDirectory, baseBranch, rest, }) {
|
|
152
|
-
return
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
}
|
|
177
|
-
}
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
return worktreeResult;
|
|
185
|
-
}
|
|
186
|
-
// Success - update database and edit starter message
|
|
187
|
-
await setWorktreeReady({
|
|
188
|
-
threadId: thread.id,
|
|
189
|
-
worktreeDirectory: worktreeResult.directory,
|
|
190
|
-
});
|
|
191
|
-
// React with tree emoji to mark as worktree thread
|
|
192
|
-
await reactToThread({
|
|
193
|
-
rest,
|
|
194
|
-
threadId: thread.id,
|
|
195
|
-
channelId: thread.parentId || undefined,
|
|
196
|
-
emoji: '🌳',
|
|
197
|
-
});
|
|
198
|
-
editStatus(`🌳 **Worktree: ${worktreeName}**\n` +
|
|
199
|
-
`📁 \`${worktreeResult.directory}\`\n` +
|
|
200
|
-
`🌿 Branch: \`${worktreeResult.branch}\``);
|
|
153
|
+
return (async () => {
|
|
154
|
+
logger.log(`Creating worktree "${worktreeName}" for project ${projectDirectory}${baseBranch ? ` from ${baseBranch}` : ''}`);
|
|
155
|
+
// Serialize status message edits so onProgress can't overwrite the
|
|
156
|
+
// final success/error edit even if Discord's API is slow.
|
|
157
|
+
let editChain = Promise.resolve();
|
|
158
|
+
const editStatus = (content) => {
|
|
159
|
+
editChain = editChain
|
|
160
|
+
.then(async () => {
|
|
161
|
+
await starterMessage?.edit(content);
|
|
162
|
+
})
|
|
163
|
+
.catch(() => { });
|
|
164
|
+
};
|
|
165
|
+
// DB pending entry must complete before git creation so error paths
|
|
166
|
+
// (setWorktreeError) can find the row reliably
|
|
167
|
+
await createPendingWorktree({
|
|
168
|
+
threadId: thread.id,
|
|
169
|
+
worktreeName,
|
|
170
|
+
projectDirectory,
|
|
171
|
+
});
|
|
172
|
+
const worktreeResult = await createWorktreeWithSubmodules({
|
|
173
|
+
directory: projectDirectory,
|
|
174
|
+
name: worktreeName,
|
|
175
|
+
baseBranch,
|
|
176
|
+
onProgress: (phase) => {
|
|
177
|
+
editStatus(`🌳 **Worktree: ${worktreeName}**\n${phase}`);
|
|
178
|
+
},
|
|
179
|
+
});
|
|
180
|
+
if (worktreeResult instanceof Error) {
|
|
181
|
+
const errorMsg = worktreeResult.message;
|
|
182
|
+
logger.error('[WORKTREE] Creation failed:', worktreeResult);
|
|
183
|
+
await setWorktreeError({ threadId: thread.id, errorMessage: errorMsg });
|
|
184
|
+
editStatus(`🌳 **Worktree: ${worktreeName}**\n❌ ${errorMsg}`);
|
|
201
185
|
await editChain;
|
|
202
|
-
return worktreeResult
|
|
203
|
-
}
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
186
|
+
return worktreeResult;
|
|
187
|
+
}
|
|
188
|
+
// DB ready update is critical; reaction is best-effort
|
|
189
|
+
await setWorktreeReady({
|
|
190
|
+
threadId: thread.id,
|
|
191
|
+
worktreeDirectory: worktreeResult.directory,
|
|
192
|
+
});
|
|
193
|
+
void reactToThread({
|
|
194
|
+
rest,
|
|
195
|
+
threadId: thread.id,
|
|
196
|
+
channelId: thread.parentId || undefined,
|
|
197
|
+
emoji: '🌳',
|
|
198
|
+
}).catch(() => { });
|
|
199
|
+
editStatus(`🌳 **Worktree: ${worktreeName}**\n` +
|
|
200
|
+
`📁 \`${worktreeResult.directory}\`\n` +
|
|
201
|
+
`🌿 Branch: \`${worktreeResult.branch}\``);
|
|
202
|
+
await editChain;
|
|
203
|
+
return worktreeResult.directory;
|
|
204
|
+
})().catch((e) => {
|
|
205
|
+
logger.error('[WORKTREE] Unexpected error in createWorktreeInBackground:', e);
|
|
206
|
+
return new Error(`Worktree creation failed: ${e instanceof Error ? e.message : String(e)}`, { cause: e });
|
|
208
207
|
});
|
|
209
208
|
}
|
|
210
209
|
async function findExistingWorktreePath({ projectDirectory, worktreeName, }) {
|
|
211
|
-
const listResult = await
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
});
|
|
215
|
-
if (errore.isError(listResult)) {
|
|
210
|
+
const listResult = await execAsync('git worktree list --porcelain', { cwd: projectDirectory })
|
|
211
|
+
.catch((e) => new WorktreeError('Failed to list worktrees', { cause: e }));
|
|
212
|
+
if (listResult instanceof Error)
|
|
216
213
|
return listResult;
|
|
217
|
-
}
|
|
218
214
|
const lines = listResult.stdout.split('\n');
|
|
219
215
|
let currentPath = '';
|
|
220
216
|
const branchRef = `refs/heads/${worktreeName}`;
|
|
@@ -268,18 +264,15 @@ export async function handleNewWorktreeCommand({ command, appId, }) {
|
|
|
268
264
|
await command.editReply(projectDirectory.message);
|
|
269
265
|
return;
|
|
270
266
|
}
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
rawBaseBranch,
|
|
274
|
-
|
|
267
|
+
// Parallelize: base branch validation and existing worktree check are independent
|
|
268
|
+
const [baseBranch, existingWorktree] = await Promise.all([
|
|
269
|
+
resolveRequestedWorktreeBaseRef({ projectDirectory, rawBaseBranch }),
|
|
270
|
+
findExistingWorktreePath({ projectDirectory, worktreeName }),
|
|
271
|
+
]);
|
|
275
272
|
if (baseBranch instanceof Error) {
|
|
276
273
|
await command.editReply(`Invalid base branch: \`${rawBaseBranch}\``);
|
|
277
274
|
return;
|
|
278
275
|
}
|
|
279
|
-
const existingWorktree = await findExistingWorktreePath({
|
|
280
|
-
projectDirectory,
|
|
281
|
-
worktreeName,
|
|
282
|
-
});
|
|
283
276
|
if (errore.isError(existingWorktree)) {
|
|
284
277
|
await command.editReply(existingWorktree.message);
|
|
285
278
|
return;
|
|
@@ -289,30 +282,29 @@ export async function handleNewWorktreeCommand({ command, appId, }) {
|
|
|
289
282
|
return;
|
|
290
283
|
}
|
|
291
284
|
// Create thread immediately so user can start typing
|
|
292
|
-
const result = await
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
});
|
|
309
|
-
if (
|
|
285
|
+
const result = await (async () => {
|
|
286
|
+
const starterMessage = await channel.send({
|
|
287
|
+
content: worktreeCreatingMessage(worktreeName),
|
|
288
|
+
flags: SILENT_MESSAGE_FLAGS,
|
|
289
|
+
});
|
|
290
|
+
const thread = await starterMessage.startThread({
|
|
291
|
+
name: `${WORKTREE_PREFIX}worktree: ${worktreeName}`,
|
|
292
|
+
autoArchiveDuration: 1440,
|
|
293
|
+
reason: 'Worktree session',
|
|
294
|
+
});
|
|
295
|
+
// Parallelize: member add and editReply are independent
|
|
296
|
+
await Promise.all([
|
|
297
|
+
thread.members.add(command.user.id),
|
|
298
|
+
command.editReply(`Creating worktree in ${thread.toString()}`),
|
|
299
|
+
]);
|
|
300
|
+
return { thread, starterMessage };
|
|
301
|
+
})().catch((e) => new WorktreeError('Failed to create thread', { cause: e }));
|
|
302
|
+
if (result instanceof Error) {
|
|
310
303
|
logger.error('[NEW-WORKTREE] Error:', result.cause);
|
|
311
304
|
await command.editReply(result.message);
|
|
312
305
|
return;
|
|
313
306
|
}
|
|
314
307
|
const { thread, starterMessage } = result;
|
|
315
|
-
await command.editReply(`Creating worktree in ${thread.toString()}`);
|
|
316
308
|
// Create worktree in background (don't await)
|
|
317
309
|
void createWorktreeInBackground({
|
|
318
310
|
thread,
|
|
@@ -353,18 +345,18 @@ async function handleWorktreeInThread({ command, thread, appId, }) {
|
|
|
353
345
|
await command.editReply(projectDirectory.message);
|
|
354
346
|
return;
|
|
355
347
|
}
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
348
|
+
// Parallelize: base branch validation, existing worktree check, and parent channel
|
|
349
|
+
// resolve are all independent. resolveTextChannel fetches the parent from Discord
|
|
350
|
+
// cache/API which can overlap with the git operations.
|
|
351
|
+
const [baseBranch, existingWorktreePath, textChannel] = await Promise.all([
|
|
352
|
+
resolveRequestedWorktreeBaseRef({ projectDirectory, rawBaseBranch }),
|
|
353
|
+
findExistingWorktreePath({ projectDirectory, worktreeName }),
|
|
354
|
+
resolveTextChannel(thread),
|
|
355
|
+
]);
|
|
360
356
|
if (baseBranch instanceof Error) {
|
|
361
357
|
await command.editReply(`Invalid base branch: \`${rawBaseBranch}\``);
|
|
362
358
|
return;
|
|
363
359
|
}
|
|
364
|
-
const existingWorktreePath = await findExistingWorktreePath({
|
|
365
|
-
projectDirectory,
|
|
366
|
-
worktreeName,
|
|
367
|
-
});
|
|
368
360
|
if (errore.isError(existingWorktreePath)) {
|
|
369
361
|
await command.editReply(existingWorktreePath.message);
|
|
370
362
|
return;
|
|
@@ -373,33 +365,33 @@ async function handleWorktreeInThread({ command, thread, appId, }) {
|
|
|
373
365
|
await command.editReply(`Worktree \`${worktreeName}\` already exists at \`${existingWorktreePath}\``);
|
|
374
366
|
return;
|
|
375
367
|
}
|
|
376
|
-
const textChannel = await resolveTextChannel(thread);
|
|
377
368
|
if (!textChannel) {
|
|
378
369
|
await command.editReply('Could not resolve parent text channel');
|
|
379
370
|
return;
|
|
380
371
|
}
|
|
381
|
-
const threadResult = await
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
372
|
+
const threadResult = await (async () => {
|
|
373
|
+
const worktreeThread = await textChannel.threads.create({
|
|
374
|
+
name: `${WORKTREE_PREFIX}worktree: ${worktreeName}`.slice(0, 100),
|
|
375
|
+
autoArchiveDuration: 1440,
|
|
376
|
+
reason: `Worktree fork from thread ${thread.id}`,
|
|
377
|
+
});
|
|
378
|
+
// Parallelize: member add and status message send are independent
|
|
379
|
+
const [, statusMessage] = await Promise.all([
|
|
380
|
+
worktreeThread.members.add(command.user.id),
|
|
381
|
+
worktreeThread.send({
|
|
390
382
|
content: worktreeCreatingMessage(worktreeName),
|
|
391
383
|
flags: SILENT_MESSAGE_FLAGS,
|
|
392
|
-
})
|
|
393
|
-
|
|
394
|
-
}
|
|
395
|
-
|
|
396
|
-
});
|
|
384
|
+
}),
|
|
385
|
+
]);
|
|
386
|
+
return { worktreeThread, statusMessage };
|
|
387
|
+
})().catch((e) => new WorktreeError('Failed to create worktree thread', { cause: e }));
|
|
397
388
|
if (threadResult instanceof Error) {
|
|
398
389
|
await command.editReply(threadResult.message);
|
|
399
390
|
return;
|
|
400
391
|
}
|
|
401
392
|
const { worktreeThread, statusMessage } = threadResult;
|
|
402
|
-
|
|
393
|
+
// Fire-and-forget: don't block background worktree creation on editReply
|
|
394
|
+
void command.editReply(`Creating worktree in ${worktreeThread.toString()}`).catch(() => { });
|
|
403
395
|
void createWorktreeInBackground({
|
|
404
396
|
thread: worktreeThread,
|
|
405
397
|
starterMessage: statusMessage,
|
|
@@ -409,9 +401,8 @@ async function handleWorktreeInThread({ command, thread, appId, }) {
|
|
|
409
401
|
rest: command.client.rest,
|
|
410
402
|
})
|
|
411
403
|
.then(async (result) => {
|
|
412
|
-
if (result instanceof Error)
|
|
404
|
+
if (result instanceof Error)
|
|
413
405
|
return;
|
|
414
|
-
}
|
|
415
406
|
const sourceSessionId = await getThreadSession(thread.id);
|
|
416
407
|
if (!sourceSessionId) {
|
|
417
408
|
await sendThreadMessage(worktreeThread, 'Worktree is ready. Send a message here to start a fresh session in this checkout.');
|
|
@@ -425,12 +416,10 @@ async function handleWorktreeInThread({ command, thread, appId, }) {
|
|
|
425
416
|
await sendThreadMessage(worktreeThread, `✗ Worktree is ready, but failed to initialize OpenCode for context reuse: ${getClient.message}`);
|
|
426
417
|
return;
|
|
427
418
|
}
|
|
428
|
-
const forkResponse = await
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
});
|
|
433
|
-
});
|
|
419
|
+
const forkResponse = await getClient().session.fork({
|
|
420
|
+
sessionID: sourceSessionId,
|
|
421
|
+
directory: result,
|
|
422
|
+
}).catch((e) => new OpenCodeSdkError({ operation: 'session.fork', cause: e }));
|
|
434
423
|
if (forkResponse instanceof Error) {
|
|
435
424
|
logger.error('[NEW-WORKTREE] Failed to fork session into worktree:', forkResponse);
|
|
436
425
|
void notifyError(forkResponse, 'Failed to fork session into worktree');
|
|
@@ -445,16 +434,14 @@ async function handleWorktreeInThread({ command, thread, appId, }) {
|
|
|
445
434
|
await sendThreadMessage(worktreeThread, `✗ Worktree is ready, but failed to reuse session context there: ${error.message}`);
|
|
446
435
|
return;
|
|
447
436
|
}
|
|
448
|
-
const permissionResponse = await
|
|
449
|
-
|
|
450
|
-
|
|
437
|
+
const permissionResponse = await getClient().session.update({
|
|
438
|
+
sessionID: forkedSession.id,
|
|
439
|
+
directory: result,
|
|
440
|
+
permission: buildSessionPermissions({
|
|
451
441
|
directory: result,
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
}),
|
|
456
|
-
});
|
|
457
|
-
});
|
|
442
|
+
originalRepoDirectory: projectDirectory,
|
|
443
|
+
}),
|
|
444
|
+
}).catch((e) => new OpenCodeSdkError({ operation: 'session.update', cause: e }));
|
|
458
445
|
if (permissionResponse instanceof Error || permissionResponse.error) {
|
|
459
446
|
const error = permissionResponse instanceof Error
|
|
460
447
|
? permissionResponse
|
|
@@ -1,6 +1,11 @@
|
|
|
1
1
|
// Permission button handler - Shows buttons for permission requests.
|
|
2
2
|
// When OpenCode asks for permission, this module renders 3 buttons:
|
|
3
3
|
// Accept, Accept Always, and Deny.
|
|
4
|
+
//
|
|
5
|
+
// The `directory` stored in PendingPermissionContext is the session directory
|
|
6
|
+
// (sdkDirectory), which equals the worktree path for worktree threads.
|
|
7
|
+
// This is used for both getOpencodeClient() (so the client header matches)
|
|
8
|
+
// and for explicit `directory` params in SDK calls.
|
|
4
9
|
import { ButtonBuilder, ButtonStyle, ActionRowBuilder, MessageFlags, } from 'discord.js';
|
|
5
10
|
import crypto from 'node:crypto';
|
|
6
11
|
import { getOpencodeClient } from '../opencode.js';
|
|
@@ -77,13 +82,12 @@ function takePendingPermissionContext(contextHash) {
|
|
|
77
82
|
* Displays 3 buttons in a row: Accept, Accept Always, Deny.
|
|
78
83
|
* Returns the message ID and context hash for tracking.
|
|
79
84
|
*/
|
|
80
|
-
export async function showPermissionButtons({ thread, permission, directory,
|
|
85
|
+
export async function showPermissionButtons({ thread, permission, directory, subtaskLabel, }) {
|
|
81
86
|
const contextHash = crypto.randomBytes(8).toString('hex');
|
|
82
87
|
const context = {
|
|
83
88
|
permission,
|
|
84
89
|
requestIds: [permission.id],
|
|
85
90
|
directory,
|
|
86
|
-
permissionDirectory,
|
|
87
91
|
thread,
|
|
88
92
|
contextHash,
|
|
89
93
|
};
|
|
@@ -111,7 +115,7 @@ export async function showPermissionButtons({ thread, permission, directory, per
|
|
|
111
115
|
await Promise.all(requestIds.map((requestId) => {
|
|
112
116
|
return client.permission.reply({
|
|
113
117
|
requestID: requestId,
|
|
114
|
-
directory: ctx.
|
|
118
|
+
directory: ctx.directory,
|
|
115
119
|
reply: 'reject',
|
|
116
120
|
message: timeoutFeedback,
|
|
117
121
|
});
|
|
@@ -207,7 +211,7 @@ export async function cancelPendingPermission(threadId) {
|
|
|
207
211
|
const result = await Promise.all(requestIds.map((requestId) => {
|
|
208
212
|
return client.permission.reply({
|
|
209
213
|
requestID: requestId,
|
|
210
|
-
directory: pendingContext.
|
|
214
|
+
directory: pendingContext.directory,
|
|
211
215
|
reply: 'reject',
|
|
212
216
|
});
|
|
213
217
|
})).then(() => {
|
|
@@ -266,7 +270,7 @@ export async function handlePermissionButton(interaction) {
|
|
|
266
270
|
await Promise.all(requestIds.map((requestId) => {
|
|
267
271
|
return permClient.permission.reply({
|
|
268
272
|
requestID: requestId,
|
|
269
|
-
directory: context.
|
|
273
|
+
directory: context.directory,
|
|
270
274
|
reply: response,
|
|
271
275
|
});
|
|
272
276
|
}));
|
|
@@ -274,7 +278,7 @@ export async function handlePermissionButton(interaction) {
|
|
|
274
278
|
const resumed = await resumeSessionIfIdleAfterPermission({
|
|
275
279
|
client: permClient,
|
|
276
280
|
sessionId: context.permission.sessionID,
|
|
277
|
-
directory: context.
|
|
281
|
+
directory: context.directory,
|
|
278
282
|
});
|
|
279
283
|
if (resumed instanceof Error) {
|
|
280
284
|
logger.error('Failed to resume idle session after permission:', resumed);
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
// /remove-project command - Remove Discord channels for a project.
|
|
2
2
|
import path from 'node:path';
|
|
3
|
-
import * as errore from 'errore';
|
|
4
3
|
import { findChannelsByDirectory, deleteChannelDirectoriesByDirectory, getAllTextChannelDirectories, } from '../database.js';
|
|
4
|
+
import { DiscordOperationError } from '../errors.js';
|
|
5
5
|
import { createLogger, LogPrefix } from '../logger.js';
|
|
6
6
|
import { abbreviatePath } from '../utils.js';
|
|
7
7
|
const logger = createLogger(LogPrefix.REMOVE_PROJECT);
|
|
@@ -23,10 +23,8 @@ export async function handleRemoveProjectCommand({ command, appId, }) {
|
|
|
23
23
|
const deletedChannels = [];
|
|
24
24
|
const failedChannels = [];
|
|
25
25
|
for (const { channel_id, channel_type } of channels) {
|
|
26
|
-
const channel = await
|
|
27
|
-
|
|
28
|
-
catch: (e) => e,
|
|
29
|
-
});
|
|
26
|
+
const channel = await guild.channels.fetch(channel_id)
|
|
27
|
+
.catch((e) => new DiscordOperationError({ operation: 'fetchChannel', cause: e }));
|
|
30
28
|
if (channel instanceof Error) {
|
|
31
29
|
logger.error(`Failed to fetch channel ${channel_id}:`, channel);
|
|
32
30
|
failedChannels.push(`${channel_type}: ${channel_id}`);
|
|
@@ -80,10 +78,8 @@ export async function handleRemoveProjectAutocomplete({ interaction, appId, }) {
|
|
|
80
78
|
// Filter to only channels that exist in this guild
|
|
81
79
|
const projectsInGuild = [];
|
|
82
80
|
for (const { directory, channel_id } of allChannels) {
|
|
83
|
-
const channel = await
|
|
84
|
-
|
|
85
|
-
catch: (e) => e,
|
|
86
|
-
});
|
|
81
|
+
const channel = await guild.channels.fetch(channel_id)
|
|
82
|
+
.catch((e) => new DiscordOperationError({ operation: 'fetchChannel', cause: e }));
|
|
87
83
|
if (channel instanceof Error) {
|
|
88
84
|
// Channel not in this guild, skip
|
|
89
85
|
continue;
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
// Undo/Redo commands - /undo, /redo
|
|
2
2
|
import { ChannelType, MessageFlags, } from 'discord.js';
|
|
3
3
|
import { getThreadSession } from '../database.js';
|
|
4
|
-
import { initializeOpencodeForDirectory } from '../opencode.js';
|
|
4
|
+
import { getOpencodeClient, initializeOpencodeForDirectory } from '../opencode.js';
|
|
5
5
|
import { resolveWorkingDirectory, SILENT_MESSAGE_FLAGS, } from '../discord-utils.js';
|
|
6
6
|
import { createLogger, LogPrefix } from '../logger.js';
|
|
7
7
|
const logger = createLogger(LogPrefix.UNDO_REDO);
|
|
@@ -59,13 +59,17 @@ export async function handleUndoCommand({ command, }) {
|
|
|
59
59
|
return;
|
|
60
60
|
}
|
|
61
61
|
await command.deferReply();
|
|
62
|
-
const
|
|
63
|
-
if (
|
|
64
|
-
await command.editReply(`Failed to undo: ${
|
|
62
|
+
const serverResult = await initializeOpencodeForDirectory(projectDirectory);
|
|
63
|
+
if (serverResult instanceof Error) {
|
|
64
|
+
await command.editReply(`Failed to undo: ${serverResult.message}`);
|
|
65
65
|
return;
|
|
66
66
|
}
|
|
67
67
|
try {
|
|
68
|
-
const client =
|
|
68
|
+
const client = getOpencodeClient(workingDirectory);
|
|
69
|
+
if (!client) {
|
|
70
|
+
await command.editReply('Failed to get OpenCode client');
|
|
71
|
+
return;
|
|
72
|
+
}
|
|
69
73
|
// Fetch session to check existing revert state
|
|
70
74
|
const sessionResponse = await client.session.get({
|
|
71
75
|
sessionID: sessionId,
|
|
@@ -210,13 +214,17 @@ export async function handleRedoCommand({ command, }) {
|
|
|
210
214
|
return;
|
|
211
215
|
}
|
|
212
216
|
await command.deferReply();
|
|
213
|
-
const
|
|
214
|
-
if (
|
|
215
|
-
await command.editReply(`Failed to redo: ${
|
|
217
|
+
const serverResult = await initializeOpencodeForDirectory(projectDirectory);
|
|
218
|
+
if (serverResult instanceof Error) {
|
|
219
|
+
await command.editReply(`Failed to redo: ${serverResult.message}`);
|
|
216
220
|
return;
|
|
217
221
|
}
|
|
218
222
|
try {
|
|
219
|
-
const client =
|
|
223
|
+
const client = getOpencodeClient(workingDirectory);
|
|
224
|
+
if (!client) {
|
|
225
|
+
await command.editReply('Failed to get OpenCode client');
|
|
226
|
+
return;
|
|
227
|
+
}
|
|
220
228
|
// Fetch session to check existing revert state
|
|
221
229
|
const sessionResponse = await client.session.get({
|
|
222
230
|
sessionID: sessionId,
|