@otto-assistant/bridge 0.4.101 → 0.4.103
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 +1 -0
- package/dist/anthropic-auth-plugin.js +22 -1
- package/dist/anthropic-auth-state.js +31 -0
- package/dist/btw-prefix-detection.js +17 -0
- package/dist/btw-prefix-detection.test.js +63 -0
- package/dist/cli.js +101 -15
- package/dist/commands/agent.js +21 -2
- package/dist/commands/ask-question.js +50 -4
- package/dist/commands/ask-question.test.js +92 -0
- package/dist/commands/btw.js +71 -66
- package/dist/commands/new-worktree.js +92 -35
- package/dist/commands/queue.js +17 -0
- package/dist/commands/worktrees.js +196 -139
- package/dist/context-awareness-plugin.js +16 -8
- package/dist/context-awareness-plugin.test.js +4 -2
- package/dist/discord-bot.js +35 -2
- package/dist/discord-command-registration.js +9 -2
- package/dist/memory-overview-plugin.js +3 -1
- package/dist/opencode.js +24 -1
- package/dist/queue-question-select-drain.e2e.test.js +135 -10
- package/dist/session-handler/thread-runtime-state.js +27 -0
- package/dist/session-handler/thread-session-runtime.js +58 -28
- package/dist/session-title-rename.test.js +12 -0
- package/dist/skill-filter.js +31 -0
- package/dist/skill-filter.test.js +65 -0
- package/dist/store.js +2 -0
- package/dist/system-message.js +12 -3
- package/dist/system-message.test.js +10 -6
- package/dist/thread-message-queue.e2e.test.js +109 -0
- package/dist/worktree-lifecycle.e2e.test.js +4 -1
- package/dist/worktrees.js +106 -12
- package/dist/worktrees.test.js +232 -6
- package/package.json +2 -2
- package/skills/goke/SKILL.md +13 -619
- package/skills/new-skill/SKILL.md +34 -10
- package/skills/npm-package/SKILL.md +336 -2
- package/skills/profano/SKILL.md +24 -0
- package/skills/zele/SKILL.md +50 -21
- package/src/agent-model.e2e.test.ts +1 -0
- package/src/anthropic-auth-plugin.ts +24 -4
- package/src/anthropic-auth-state.ts +45 -0
- package/src/btw-prefix-detection.test.ts +73 -0
- package/src/btw-prefix-detection.ts +23 -0
- package/src/cli.ts +138 -46
- package/src/commands/agent.ts +24 -2
- package/src/commands/ask-question.test.ts +111 -0
- package/src/commands/ask-question.ts +69 -4
- package/src/commands/btw.ts +105 -85
- package/src/commands/new-worktree.ts +107 -40
- package/src/commands/queue.ts +22 -0
- package/src/commands/worktrees.ts +246 -154
- package/src/context-awareness-plugin.test.ts +4 -2
- package/src/context-awareness-plugin.ts +16 -8
- package/src/discord-bot.ts +40 -2
- package/src/discord-command-registration.ts +12 -2
- package/src/memory-overview-plugin.ts +3 -1
- package/src/opencode.ts +31 -1
- package/src/queue-question-select-drain.e2e.test.ts +174 -10
- package/src/session-handler/thread-runtime-state.ts +36 -1
- package/src/session-handler/thread-session-runtime.ts +72 -32
- package/src/session-title-rename.test.ts +18 -0
- package/src/skill-filter.test.ts +83 -0
- package/src/skill-filter.ts +42 -0
- package/src/store.ts +17 -0
- package/src/system-message.test.ts +10 -6
- package/src/system-message.ts +12 -3
- package/src/thread-message-queue.e2e.test.ts +126 -0
- package/src/worktree-lifecycle.e2e.test.ts +6 -1
- package/src/worktrees.test.ts +274 -9
- package/src/worktrees.ts +144 -23
package/dist/commands/btw.js
CHANGED
|
@@ -9,6 +9,64 @@ import { resolveWorkingDirectory, resolveTextChannel, sendThreadMessage, } from
|
|
|
9
9
|
import { getOrCreateRuntime } from '../session-handler/thread-session-runtime.js';
|
|
10
10
|
import { createLogger, LogPrefix } from '../logger.js';
|
|
11
11
|
const logger = createLogger(LogPrefix.FORK);
|
|
12
|
+
export async function forkSessionToBtwThread({ sourceThread, projectDirectory, prompt, userId, username, appId, }) {
|
|
13
|
+
const sessionId = await getThreadSession(sourceThread.id);
|
|
14
|
+
if (!sessionId) {
|
|
15
|
+
return new Error('No active session in this thread');
|
|
16
|
+
}
|
|
17
|
+
const getClient = await initializeOpencodeForDirectory(projectDirectory);
|
|
18
|
+
if (getClient instanceof Error) {
|
|
19
|
+
return new Error(`Failed to fork session: ${getClient.message}`, {
|
|
20
|
+
cause: getClient,
|
|
21
|
+
});
|
|
22
|
+
}
|
|
23
|
+
const forkResponse = await getClient().session.fork({
|
|
24
|
+
sessionID: sessionId,
|
|
25
|
+
});
|
|
26
|
+
if (!forkResponse.data) {
|
|
27
|
+
return new Error('Failed to fork session');
|
|
28
|
+
}
|
|
29
|
+
const textChannel = await resolveTextChannel(sourceThread);
|
|
30
|
+
if (!textChannel) {
|
|
31
|
+
return new Error('Could not resolve parent text channel');
|
|
32
|
+
}
|
|
33
|
+
const forkedSession = forkResponse.data;
|
|
34
|
+
const thread = await textChannel.threads.create({
|
|
35
|
+
name: `btw: ${prompt}`.slice(0, 100),
|
|
36
|
+
autoArchiveDuration: ThreadAutoArchiveDuration.OneDay,
|
|
37
|
+
reason: `btw fork from session ${sessionId}`,
|
|
38
|
+
});
|
|
39
|
+
await setThreadSession(thread.id, forkedSession.id);
|
|
40
|
+
await thread.members.add(userId);
|
|
41
|
+
logger.log(`Created btw fork session ${forkedSession.id} in thread ${thread.id} from ${sessionId}`);
|
|
42
|
+
const sourceThreadLink = `<#${sourceThread.id}>`;
|
|
43
|
+
await sendThreadMessage(thread, `Reusing context from ${sourceThreadLink} to answer prompt...\n${prompt}`);
|
|
44
|
+
const wrappedPrompt = [
|
|
45
|
+
`The user asked a side question while you were working on another task.`,
|
|
46
|
+
`This is a forked session whose ONLY goal is to answer this question.`,
|
|
47
|
+
`Do NOT continue, resume, or reference the previous task. Only answer the question below.\n`,
|
|
48
|
+
prompt,
|
|
49
|
+
].join('\n');
|
|
50
|
+
const runtime = getOrCreateRuntime({
|
|
51
|
+
threadId: thread.id,
|
|
52
|
+
thread,
|
|
53
|
+
projectDirectory,
|
|
54
|
+
sdkDirectory: projectDirectory,
|
|
55
|
+
channelId: textChannel.id,
|
|
56
|
+
appId,
|
|
57
|
+
});
|
|
58
|
+
await runtime.enqueueIncoming({
|
|
59
|
+
prompt: wrappedPrompt,
|
|
60
|
+
userId,
|
|
61
|
+
username,
|
|
62
|
+
appId,
|
|
63
|
+
mode: 'opencode',
|
|
64
|
+
});
|
|
65
|
+
return {
|
|
66
|
+
thread,
|
|
67
|
+
forkedSessionId: forkedSession.id,
|
|
68
|
+
};
|
|
69
|
+
}
|
|
12
70
|
export async function handleBtwCommand({ command, appId, }) {
|
|
13
71
|
const channel = command.channel;
|
|
14
72
|
if (!channel) {
|
|
@@ -18,21 +76,19 @@ export async function handleBtwCommand({ command, appId, }) {
|
|
|
18
76
|
});
|
|
19
77
|
return;
|
|
20
78
|
}
|
|
21
|
-
|
|
22
|
-
ChannelType.
|
|
23
|
-
ChannelType.
|
|
24
|
-
ChannelType.AnnouncementThread,
|
|
25
|
-
].includes(channel.type);
|
|
26
|
-
if (!isThread) {
|
|
79
|
+
if (channel.type !== ChannelType.PublicThread
|
|
80
|
+
&& channel.type !== ChannelType.PrivateThread
|
|
81
|
+
&& channel.type !== ChannelType.AnnouncementThread) {
|
|
27
82
|
await command.reply({
|
|
28
83
|
content: 'This command can only be used in a thread with an active session',
|
|
29
84
|
flags: MessageFlags.Ephemeral,
|
|
30
85
|
});
|
|
31
86
|
return;
|
|
32
87
|
}
|
|
88
|
+
const threadChannel = channel;
|
|
33
89
|
const prompt = command.options.getString('prompt', true);
|
|
34
90
|
const resolved = await resolveWorkingDirectory({
|
|
35
|
-
channel:
|
|
91
|
+
channel: threadChannel,
|
|
36
92
|
});
|
|
37
93
|
if (!resolved) {
|
|
38
94
|
await command.reply({
|
|
@@ -42,72 +98,21 @@ export async function handleBtwCommand({ command, appId, }) {
|
|
|
42
98
|
return;
|
|
43
99
|
}
|
|
44
100
|
const { projectDirectory } = resolved;
|
|
45
|
-
const sessionId = await getThreadSession(channel.id);
|
|
46
|
-
if (!sessionId) {
|
|
47
|
-
await command.reply({
|
|
48
|
-
content: 'No active session in this thread',
|
|
49
|
-
flags: MessageFlags.Ephemeral,
|
|
50
|
-
});
|
|
51
|
-
return;
|
|
52
|
-
}
|
|
53
101
|
await command.deferReply({ flags: MessageFlags.Ephemeral });
|
|
54
|
-
const getClient = await initializeOpencodeForDirectory(projectDirectory);
|
|
55
|
-
if (getClient instanceof Error) {
|
|
56
|
-
await command.editReply({
|
|
57
|
-
content: `Failed to fork session: ${getClient.message}`,
|
|
58
|
-
});
|
|
59
|
-
return;
|
|
60
|
-
}
|
|
61
102
|
try {
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
sessionID: sessionId,
|
|
65
|
-
});
|
|
66
|
-
if (!forkResponse.data) {
|
|
67
|
-
await command.editReply('Failed to fork session');
|
|
68
|
-
return;
|
|
69
|
-
}
|
|
70
|
-
const forkedSession = forkResponse.data;
|
|
71
|
-
const textChannel = await resolveTextChannel(channel);
|
|
72
|
-
if (!textChannel) {
|
|
73
|
-
await command.editReply('Could not resolve parent text channel');
|
|
74
|
-
return;
|
|
75
|
-
}
|
|
76
|
-
const threadName = `btw: ${prompt}`.slice(0, 100);
|
|
77
|
-
const thread = await textChannel.threads.create({
|
|
78
|
-
name: threadName,
|
|
79
|
-
autoArchiveDuration: ThreadAutoArchiveDuration.OneDay,
|
|
80
|
-
reason: `btw fork from session ${sessionId}`,
|
|
81
|
-
});
|
|
82
|
-
// Claim the forked session immediately so external polling does not race
|
|
83
|
-
await setThreadSession(thread.id, forkedSession.id);
|
|
84
|
-
await thread.members.add(command.user.id);
|
|
85
|
-
logger.log(`Created btw fork session ${forkedSession.id} in thread ${thread.id} from ${sessionId}`);
|
|
86
|
-
// Short status message with prompt instead of replaying past messages
|
|
87
|
-
const sourceThreadLink = `<#${channel.id}>`;
|
|
88
|
-
await sendThreadMessage(thread, `Reusing context from ${sourceThreadLink} to answer prompt...\n${prompt}`);
|
|
89
|
-
const wrappedPrompt = [
|
|
90
|
-
`The user asked a side question while you were working on another task.`,
|
|
91
|
-
`This is a forked session whose ONLY goal is to answer this question.`,
|
|
92
|
-
`Do NOT continue, resume, or reference the previous task. Only answer the question below.\n`,
|
|
93
|
-
prompt,
|
|
94
|
-
].join('\n');
|
|
95
|
-
const runtime = getOrCreateRuntime({
|
|
96
|
-
threadId: thread.id,
|
|
97
|
-
thread,
|
|
103
|
+
const result = await forkSessionToBtwThread({
|
|
104
|
+
sourceThread: threadChannel,
|
|
98
105
|
projectDirectory,
|
|
99
|
-
|
|
100
|
-
channelId: textChannel.id,
|
|
101
|
-
appId,
|
|
102
|
-
});
|
|
103
|
-
await runtime.enqueueIncoming({
|
|
104
|
-
prompt: wrappedPrompt,
|
|
106
|
+
prompt,
|
|
105
107
|
userId: command.user.id,
|
|
106
108
|
username: command.user.displayName,
|
|
107
109
|
appId,
|
|
108
|
-
mode: 'opencode',
|
|
109
110
|
});
|
|
110
|
-
|
|
111
|
+
if (result instanceof Error) {
|
|
112
|
+
await command.editReply(result.message);
|
|
113
|
+
return;
|
|
114
|
+
}
|
|
115
|
+
await command.editReply(`Session forked! Continue in ${result.thread.toString()}`);
|
|
111
116
|
}
|
|
112
117
|
catch (error) {
|
|
113
118
|
logger.error('Error in /btw:', error);
|
|
@@ -11,6 +11,18 @@ import { createWorktreeWithSubmodules, execAsync, listBranchesByLastCommit, vali
|
|
|
11
11
|
import { WORKTREE_PREFIX } from './merge-worktree.js';
|
|
12
12
|
import * as errore from 'errore';
|
|
13
13
|
const logger = createLogger(LogPrefix.WORKTREE);
|
|
14
|
+
const DEFAULT_WORKTREE_BASE_REF = 'HEAD';
|
|
15
|
+
async function resolveRequestedWorktreeBaseRef({ projectDirectory, rawBaseBranch, }) {
|
|
16
|
+
if (!rawBaseBranch) {
|
|
17
|
+
// Default to the current local HEAD so worktrees can branch from
|
|
18
|
+
// unpublished commits in the main checkout.
|
|
19
|
+
return DEFAULT_WORKTREE_BASE_REF;
|
|
20
|
+
}
|
|
21
|
+
return validateBranchRef({
|
|
22
|
+
directory: projectDirectory,
|
|
23
|
+
ref: rawBaseBranch,
|
|
24
|
+
});
|
|
25
|
+
}
|
|
14
26
|
/** Status message shown while a worktree is being created. */
|
|
15
27
|
export function worktreeCreatingMessage(worktreeName) {
|
|
16
28
|
return `🌳 **Creating worktree: ${worktreeName}**\n⏳ Setting up...`;
|
|
@@ -22,24 +34,78 @@ class WorktreeError extends Error {
|
|
|
22
34
|
}
|
|
23
35
|
}
|
|
24
36
|
/**
|
|
25
|
-
*
|
|
26
|
-
*
|
|
27
|
-
*
|
|
37
|
+
* Lowercase, collapse whitespace to dashes, drop non-[a-z0-9-] chars.
|
|
38
|
+
* Does NOT add the `opencode/kimaki-` prefix — callers do that so they can
|
|
39
|
+
* optionally compress the slug first for auto-derived names.
|
|
28
40
|
*/
|
|
29
|
-
export function
|
|
30
|
-
|
|
41
|
+
export function slugifyWorktreeName(name) {
|
|
42
|
+
return name
|
|
31
43
|
.toLowerCase()
|
|
32
44
|
.trim()
|
|
33
45
|
.replace(/\s+/g, '-')
|
|
34
46
|
.replace(/[^a-z0-9-]/g, '');
|
|
35
|
-
|
|
47
|
+
}
|
|
48
|
+
/**
|
|
49
|
+
* Compress a slug by stripping vowels from each dash-separated word, but
|
|
50
|
+
* keeping the first character so the word stays recognizable.
|
|
51
|
+
* Only applied to slugs longer than 20 chars — short names are left alone.
|
|
52
|
+
*
|
|
53
|
+
* "configurable-sidebar-width-by-component" → "cnfgrbl-sdbr-wdth-by-cmpnnt"
|
|
54
|
+
*
|
|
55
|
+
* Used ONLY for auto-derived worktree names (thread name, prompt slug)
|
|
56
|
+
* so long Discord titles don't produce 80-char folder paths that make
|
|
57
|
+
* the agent lazy and reuse the previous worktree. User-provided names
|
|
58
|
+
* via `--worktree <name>` or `/new-worktree name:` are never compressed.
|
|
59
|
+
*/
|
|
60
|
+
export function shortenWorktreeSlug(slug) {
|
|
61
|
+
if (slug.length <= 20) {
|
|
62
|
+
return slug;
|
|
63
|
+
}
|
|
64
|
+
const shortened = slug
|
|
65
|
+
.split('-')
|
|
66
|
+
.map((word) => {
|
|
67
|
+
if (!word) {
|
|
68
|
+
return word;
|
|
69
|
+
}
|
|
70
|
+
const first = word[0];
|
|
71
|
+
const rest = word.slice(1).replace(/[aeiou]/g, '');
|
|
72
|
+
return first + rest;
|
|
73
|
+
})
|
|
74
|
+
.join('-');
|
|
75
|
+
return shortened || slug;
|
|
76
|
+
}
|
|
77
|
+
/**
|
|
78
|
+
* Format worktree name: lowercase, spaces to dashes, remove special chars, add opencode/kimaki- prefix.
|
|
79
|
+
* "My Feature" → "opencode/kimaki-my-feature"
|
|
80
|
+
* Returns empty string if no valid name can be extracted.
|
|
81
|
+
*
|
|
82
|
+
* This is the "explicit" path used when the user provides a specific name.
|
|
83
|
+
* The slug is NOT compressed — if you ask for `my-long-explicit-branch-name`
|
|
84
|
+
* you get `opencode/kimaki-my-long-explicit-branch-name` verbatim.
|
|
85
|
+
*/
|
|
86
|
+
export function formatWorktreeName(name) {
|
|
87
|
+
const slug = slugifyWorktreeName(name);
|
|
88
|
+
if (!slug) {
|
|
89
|
+
return '';
|
|
90
|
+
}
|
|
91
|
+
return `opencode/kimaki-${slug}`;
|
|
92
|
+
}
|
|
93
|
+
/**
|
|
94
|
+
* Format an auto-derived worktree name (from a Discord thread title or a
|
|
95
|
+
* prompt). Same as formatWorktreeName but compresses slugs longer than 20
|
|
96
|
+
* chars by stripping vowels so the on-disk folder name stays short.
|
|
97
|
+
*/
|
|
98
|
+
export function formatAutoWorktreeName(name) {
|
|
99
|
+
const slug = slugifyWorktreeName(name);
|
|
100
|
+
if (!slug) {
|
|
36
101
|
return '';
|
|
37
102
|
}
|
|
38
|
-
return `opencode/kimaki-${
|
|
103
|
+
return `opencode/kimaki-${shortenWorktreeSlug(slug)}`;
|
|
39
104
|
}
|
|
40
105
|
/**
|
|
41
106
|
* Derive worktree name from thread name.
|
|
42
107
|
* Handles existing "⬦ worktree: opencode/kimaki-name" format or uses thread name directly.
|
|
108
|
+
* Uses formatAutoWorktreeName so long thread titles get vowel-compressed.
|
|
43
109
|
*/
|
|
44
110
|
function deriveWorktreeNameFromThread(threadName) {
|
|
45
111
|
// Handle existing "⬦ worktree: opencode/kimaki-name" format
|
|
@@ -50,10 +116,10 @@ function deriveWorktreeNameFromThread(threadName) {
|
|
|
50
116
|
if (extractedName.startsWith('opencode/kimaki-')) {
|
|
51
117
|
return extractedName;
|
|
52
118
|
}
|
|
53
|
-
return
|
|
119
|
+
return formatAutoWorktreeName(extractedName);
|
|
54
120
|
}
|
|
55
|
-
// Use thread name directly
|
|
56
|
-
return
|
|
121
|
+
// Use thread name directly (compressed if > 20 chars)
|
|
122
|
+
return formatAutoWorktreeName(threadName);
|
|
57
123
|
}
|
|
58
124
|
/**
|
|
59
125
|
* Get project directory from database.
|
|
@@ -169,10 +235,9 @@ export async function handleNewWorktreeCommand({ command, }) {
|
|
|
169
235
|
await command.editReply('Cannot determine channel');
|
|
170
236
|
return;
|
|
171
237
|
}
|
|
172
|
-
const isThread = channel.type === ChannelType.PublicThread ||
|
|
173
|
-
channel.type === ChannelType.PrivateThread;
|
|
174
238
|
// Handle command in existing thread - attach worktree to this thread
|
|
175
|
-
if (
|
|
239
|
+
if (channel.type === ChannelType.PublicThread ||
|
|
240
|
+
channel.type === ChannelType.PrivateThread) {
|
|
176
241
|
await handleWorktreeInThread({
|
|
177
242
|
command,
|
|
178
243
|
thread: channel,
|
|
@@ -201,17 +266,13 @@ export async function handleNewWorktreeCommand({ command, }) {
|
|
|
201
266
|
await command.editReply(projectDirectory.message);
|
|
202
267
|
return;
|
|
203
268
|
}
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
});
|
|
210
|
-
|
|
211
|
-
await command.editReply(`Invalid base branch: \`${baseBranch}\``);
|
|
212
|
-
return;
|
|
213
|
-
}
|
|
214
|
-
baseBranch = validated;
|
|
269
|
+
const baseBranch = await resolveRequestedWorktreeBaseRef({
|
|
270
|
+
projectDirectory,
|
|
271
|
+
rawBaseBranch,
|
|
272
|
+
});
|
|
273
|
+
if (baseBranch instanceof Error) {
|
|
274
|
+
await command.editReply(`Invalid base branch: \`${rawBaseBranch}\``);
|
|
275
|
+
return;
|
|
215
276
|
}
|
|
216
277
|
const existingWorktree = await findExistingWorktreePath({
|
|
217
278
|
projectDirectory,
|
|
@@ -294,17 +355,13 @@ async function handleWorktreeInThread({ command, thread, }) {
|
|
|
294
355
|
await command.editReply(projectDirectory.message);
|
|
295
356
|
return;
|
|
296
357
|
}
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
});
|
|
303
|
-
|
|
304
|
-
await command.editReply(`Invalid base branch: \`${baseBranch}\``);
|
|
305
|
-
return;
|
|
306
|
-
}
|
|
307
|
-
baseBranch = validated;
|
|
358
|
+
const baseBranch = await resolveRequestedWorktreeBaseRef({
|
|
359
|
+
projectDirectory,
|
|
360
|
+
rawBaseBranch,
|
|
361
|
+
});
|
|
362
|
+
if (baseBranch instanceof Error) {
|
|
363
|
+
await command.editReply(`Invalid base branch: \`${rawBaseBranch}\``);
|
|
364
|
+
return;
|
|
308
365
|
}
|
|
309
366
|
const existingWorktreePath = await findExistingWorktreePath({
|
|
310
367
|
projectDirectory,
|
package/dist/commands/queue.js
CHANGED
|
@@ -72,6 +72,7 @@ export async function handleQueueCommand({ command, appId, }) {
|
|
|
72
72
|
}
|
|
73
73
|
export async function handleClearQueueCommand({ command, }) {
|
|
74
74
|
const channel = command.channel;
|
|
75
|
+
const position = command.options.getInteger('position') ?? undefined;
|
|
75
76
|
if (!channel) {
|
|
76
77
|
await command.reply({
|
|
77
78
|
content: 'This command can only be used in a channel',
|
|
@@ -100,6 +101,22 @@ export async function handleClearQueueCommand({ command, }) {
|
|
|
100
101
|
});
|
|
101
102
|
return;
|
|
102
103
|
}
|
|
104
|
+
if (position !== undefined) {
|
|
105
|
+
const removed = runtime?.removeQueuePosition(position);
|
|
106
|
+
if (!removed) {
|
|
107
|
+
await command.reply({
|
|
108
|
+
content: `No queued message at position ${position}`,
|
|
109
|
+
flags: MessageFlags.Ephemeral | SILENT_MESSAGE_FLAGS,
|
|
110
|
+
});
|
|
111
|
+
return;
|
|
112
|
+
}
|
|
113
|
+
await command.reply({
|
|
114
|
+
content: `Cleared queued message at position ${position}`,
|
|
115
|
+
flags: SILENT_MESSAGE_FLAGS,
|
|
116
|
+
});
|
|
117
|
+
logger.log(`[QUEUE] User ${command.user.displayName} cleared queued position ${position} in thread ${channel.id}`);
|
|
118
|
+
return;
|
|
119
|
+
}
|
|
103
120
|
runtime?.clearQueue();
|
|
104
121
|
await command.reply({
|
|
105
122
|
content: `Cleared ${queueLength} queued message${queueLength > 1 ? 's' : ''}`,
|