kimaki 0.28.0 → 0.30.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 +33 -33
- package/dist/anthropic-account-identity.js +1 -1
- package/dist/anthropic-auth-plugin.js +5 -5
- package/dist/anthropic-auth-plugin.test.js +30 -2
- package/dist/anthropic-auth-state.js +1 -1
- package/dist/bash-tool-schema-plugin.js +18 -0
- package/dist/bash-tool-schema-plugin.test.js +20 -1
- package/dist/bin.js +7 -3
- package/dist/bot-lifecycle.test.js +206 -0
- package/dist/cache-drift-plugin.js +25 -18
- package/dist/cache-rewrite.js +39 -0
- package/dist/cache-rewrite.test.js +41 -0
- package/dist/channel-management.js +113 -51
- package/dist/cli-commands/misc.js +4 -1
- package/dist/cli-commands/project.js +7 -4
- package/dist/cli-commands/session.js +118 -36
- package/dist/cli-commands/thread.js +63 -0
- package/dist/cli-parsing.test.js +3 -3
- package/dist/cli-runner.js +11 -1
- package/dist/cli-send-thread.e2e.test.js +2 -2
- package/dist/cli.js +6 -4
- package/dist/commands/ask-question.js +72 -23
- package/dist/commands/queue.js +84 -36
- package/dist/commands/queue.test.js +15 -0
- package/dist/commands/restart-opencode-server.js +7 -1
- package/dist/commands/session-id.js +5 -5
- package/dist/database.js +149 -6
- package/dist/db.js +34 -0
- package/dist/db.test.js +199 -1
- package/dist/default-channel-provisioning.test.js +2 -1
- package/dist/discord-bot.js +220 -146
- package/dist/discord-command-registration.js +1 -1
- package/dist/external-directory-permissions.e2e.test.js +3 -3
- package/dist/external-directory-project-deny.e2e.test.js +1 -1
- package/dist/forum-sync/watchers.js +15 -0
- package/dist/gateway-proxy.e2e.test.js +9 -9
- package/dist/guild-categories.test.js +225 -0
- package/dist/hrana-server.js +68 -14
- package/dist/interaction-handler.js +5 -1
- package/dist/kimaki-opencode-plugin.js +2 -0
- package/dist/kimaki-opencode-plugin.test.js +28 -0
- package/dist/list-channel-threads.js +163 -0
- package/dist/list-channel-threads.test.js +80 -0
- package/dist/markdown.js +164 -102
- package/dist/markdown.test.js +88 -20
- package/dist/message-formatting.js +29 -18
- package/dist/message-formatting.test.js +111 -3
- package/dist/opencode.js +27 -12
- package/dist/question-abort-clears-dropdown.e2e.test.js +1 -1
- package/dist/question-abort-resume.e2e.test.js +100 -0
- package/dist/question-abort-retry-text-before-dropdown.e2e.test.js +1 -1
- package/dist/question-text-before-dropdown.e2e.test.js +1 -1
- package/dist/queue-advanced-abort.e2e.test.js +3 -3
- package/dist/queue-advanced-action-buttons.e2e.test.js +5 -5
- package/dist/queue-advanced-e2e-setup.js +63 -0
- package/dist/queue-advanced-footer.e2e.test.js +18 -18
- package/dist/queue-advanced-model-switch.e2e.test.js +3 -4
- package/dist/queue-advanced-permissions-typing.e2e.test.js +2 -2
- package/dist/queue-advanced-typing-interrupt.e2e.test.js +3 -3
- package/dist/queue-delete-message.e2e.test.js +4 -4
- package/dist/queue-drain-after-interactive-ui.e2e.test.js +3 -3
- package/dist/queue-interrupt-drain.e2e.test.js +10 -10
- package/dist/queue-question-select-drain.e2e.test.js +5 -5
- package/dist/runtime-lifecycle.e2e.test.js +95 -11
- package/dist/schema.js +21 -0
- package/dist/session-handler/event-stream-state.js +240 -6
- package/dist/session-handler/event-stream-state.test.js +1116 -130
- package/dist/session-handler/thread-runtime-state.js +4 -0
- package/dist/session-handler/thread-session-runtime.js +499 -89
- package/dist/sleep.e2e.test.js +7 -7
- package/dist/store.js +1 -1
- package/dist/system-message.js +64 -23
- package/dist/system-message.test.js +69 -23
- package/dist/task-id-plugin.js +16 -0
- package/dist/task-runner.js +7 -0
- package/dist/task-schedule.test.js +1 -1
- package/dist/test-utils.js +5 -3
- package/dist/thread-message-queue.e2e.test.js +30 -31
- package/dist/undo-redo.e2e.test.js +3 -3
- package/dist/voice-message.e2e.test.js +64 -26
- package/dist/voice-routing-input.e2e.test.js +4 -4
- package/dist/voice-routing-question.e2e.test.js +4 -4
- package/dist/voice.js +107 -21
- package/dist/voice.test.js +52 -1
- package/dist/wait-session.js +24 -2
- package/dist/worktree-lifecycle.e2e.test.js +8 -8
- package/package.json +7 -7
- package/skills/egaki/SKILL.md +14 -0
- package/skills/holocron/SKILL.md +125 -42
- package/skills/opensrc/SKILL.md +20 -3
- package/skills/spiceflow/SKILL.md +53 -172
- package/skills/strada/SKILL.md +61 -232
- package/src/agent-model.e2e.test.ts +33 -33
- package/src/anthropic-account-identity.ts +1 -1
- package/src/anthropic-auth-plugin.test.ts +37 -2
- package/src/anthropic-auth-plugin.ts +9 -7
- package/src/anthropic-auth-state.ts +1 -0
- package/src/bash-tool-schema-plugin.test.ts +29 -0
- package/src/bash-tool-schema-plugin.ts +31 -0
- package/src/bin.ts +7 -3
- package/src/bot-lifecycle.test.ts +252 -0
- package/src/cache-drift-plugin.ts +34 -24
- package/src/cache-rewrite.test.ts +49 -0
- package/src/cache-rewrite.ts +96 -0
- package/src/channel-management.ts +144 -65
- package/src/cli-commands/misc.ts +4 -1
- package/src/cli-commands/project.ts +9 -4
- package/src/cli-commands/session.ts +167 -38
- package/src/cli-commands/thread.ts +86 -0
- package/src/cli-parsing.test.ts +3 -3
- package/src/cli-runner.ts +11 -0
- package/src/cli-send-thread.e2e.test.ts +2 -2
- package/src/cli.ts +8 -6
- package/src/commands/ask-question.ts +88 -25
- package/src/commands/queue.test.ts +19 -0
- package/src/commands/queue.ts +111 -45
- package/src/commands/restart-opencode-server.ts +12 -1
- package/src/commands/session-id.ts +5 -5
- package/src/database.ts +219 -6
- package/src/db.test.ts +236 -0
- package/src/db.ts +38 -0
- package/src/default-channel-provisioning.test.ts +2 -1
- package/src/discord-bot.ts +272 -185
- package/src/discord-command-registration.ts +3 -1
- package/src/external-directory-permissions.e2e.test.ts +3 -3
- package/src/external-directory-project-deny.e2e.test.ts +1 -1
- package/src/forum-sync/watchers.ts +20 -0
- package/src/gateway-proxy.e2e.test.ts +9 -9
- package/src/guild-categories.test.ts +260 -0
- package/src/hrana-server.ts +96 -16
- package/src/interaction-handler.ts +7 -0
- package/src/kimaki-opencode-plugin.test.ts +34 -0
- package/src/kimaki-opencode-plugin.ts +2 -0
- package/src/list-channel-threads.test.ts +90 -0
- package/src/list-channel-threads.ts +252 -0
- package/src/markdown.test.ts +108 -22
- package/src/markdown.ts +216 -103
- package/src/message-formatting.test.ts +129 -2
- package/src/message-formatting.ts +28 -14
- package/src/opencode.ts +28 -13
- package/src/question-abort-clears-dropdown.e2e.test.ts +1 -1
- package/src/question-abort-resume.e2e.test.ts +121 -0
- package/src/question-abort-retry-text-before-dropdown.e2e.test.ts +1 -1
- package/src/question-text-before-dropdown.e2e.test.ts +1 -1
- package/src/queue-advanced-abort.e2e.test.ts +3 -3
- package/src/queue-advanced-action-buttons.e2e.test.ts +5 -5
- package/src/queue-advanced-e2e-setup.ts +65 -0
- package/src/queue-advanced-footer.e2e.test.ts +18 -18
- package/src/queue-advanced-model-switch.e2e.test.ts +3 -4
- package/src/queue-advanced-permissions-typing.e2e.test.ts +2 -2
- package/src/queue-advanced-typing-interrupt.e2e.test.ts +3 -3
- package/src/queue-delete-message.e2e.test.ts +4 -4
- package/src/queue-drain-after-interactive-ui.e2e.test.ts +3 -3
- package/src/queue-interrupt-drain.e2e.test.ts +10 -10
- package/src/queue-question-select-drain.e2e.test.ts +5 -5
- package/src/runtime-lifecycle.e2e.test.ts +104 -10
- package/src/schema.sql +16 -0
- package/src/schema.ts +23 -0
- package/src/session-handler/event-stream-state.test.ts +1184 -133
- package/src/session-handler/event-stream-state.ts +322 -8
- package/src/session-handler/thread-runtime-state.ts +5 -0
- package/src/session-handler/thread-session-runtime.ts +597 -95
- package/src/sleep.e2e.test.ts +7 -7
- package/src/store.ts +2 -2
- package/src/system-message.test.ts +70 -23
- package/src/system-message.ts +67 -22
- package/src/task-id-plugin.ts +17 -0
- package/src/task-runner.ts +8 -0
- package/src/task-schedule.test.ts +1 -1
- package/src/test-utils.ts +5 -3
- package/src/thread-message-queue.e2e.test.ts +30 -31
- package/src/undo-redo.e2e.test.ts +3 -3
- package/src/voice-message.e2e.test.ts +70 -26
- package/src/voice-routing-input.e2e.test.ts +4 -4
- package/src/voice-routing-question.e2e.test.ts +4 -4
- package/src/voice.test.ts +56 -0
- package/src/voice.ts +135 -25
- package/src/wait-session.ts +25 -2
- package/src/worktree-lifecycle.e2e.test.ts +8 -8
package/dist/discord-bot.js
CHANGED
|
@@ -8,12 +8,12 @@
|
|
|
8
8
|
// suppresses these when shuttingDown is already set, and we removeAllListeners()
|
|
9
9
|
// before destroying the client to prevent late events from becoming uncaught.
|
|
10
10
|
import { DiscordOperationError } from './errors.js';
|
|
11
|
-
import { initDatabase, closeDatabase, getThreadWorktreeOrWorkspace, getThreadSession, getChannelWorktreesEnabled, getChannelMentionMode, getChannelDirectory, cancelAllPendingIpcRequests, consumeSessionSleepWake, deleteChannelDirectoryById, createPendingWorkspace, setWorkspaceReady, } from './database.js';
|
|
11
|
+
import { initDatabase, closeDatabase, getThreadWorktreeOrWorkspace, getThreadSession, getChannelWorktreesEnabled, getChannelMentionMode, getChannelDirectory, cancelAllPendingIpcRequests, cleanupDeletedThread, clearGuildCategoryByChannelId, consumeSessionSleepWake, deleteForumSyncConfig, deleteChannelDirectoryById, findChannelsByDirectory, isCurrentThreadSessionBinding, createPendingWorkspace, setWorkspaceReady, } from './database.js';
|
|
12
12
|
import { stopOpencodeServer, } from './opencode.js';
|
|
13
13
|
import { formatAutoWorktreeName, createWorktreeInBackground, worktreeCreatingMessage } from './commands/new-worktree.js';
|
|
14
14
|
import { resolveSessionWorkingDirectory, git, isGitRepositoryRoot } from './worktrees.js';
|
|
15
15
|
import { WORKTREE_PREFIX } from './commands/merge-worktree.js';
|
|
16
|
-
import { STATUS_PREFIX } from './message-formatting.js';
|
|
16
|
+
import { asSystemLine, STATUS_PREFIX } from './message-formatting.js';
|
|
17
17
|
import { escapeBackticksInCodeBlocks, splitMarkdownForDiscord, sendThreadMessage, SILENT_MESSAGE_FLAGS, NOTIFY_MESSAGE_FLAGS, reactToThread, stripMentions, hasKimakiBotPermission, hasNoKimakiRole, resolveGuildMessageMember, } from './discord-utils.js';
|
|
18
18
|
import { getOpencodeSystemMessage, isInjectedPromptMarker, } from './system-message.js';
|
|
19
19
|
import YAML from 'yaml';
|
|
@@ -30,7 +30,7 @@ import { cancelHtmlActionsForThread } from './html-actions.js';
|
|
|
30
30
|
import { ensureKimakiCategory, ensureKimakiAudioCategory, createProjectChannels, getChannelsWithDescriptions, } from './channel-management.js';
|
|
31
31
|
import { voiceConnections, cleanupVoiceConnection, registerVoiceStateHandler, } from './voice-handler.js';
|
|
32
32
|
import {} from './session-handler/model-utils.js';
|
|
33
|
-
import { getRuntime, getOrCreateRuntime, disposeRuntime, reserveThreadIngress, runInThreadIngressSlot, } from './session-handler/thread-session-runtime.js';
|
|
33
|
+
import { getRuntime, getOrCreateRuntime, disposeRuntime, getRuntimeThreadIdsForChannel, restorePersistedLocalQueues, reserveThreadIngress, runInThreadIngressSlot, } from './session-handler/thread-session-runtime.js';
|
|
34
34
|
import { runShellCommand } from './commands/run-command.js';
|
|
35
35
|
import { registerInteractionHandler } from './interaction-handler.js';
|
|
36
36
|
import { getDiscordRestApiUrl } from './discord-urls.js';
|
|
@@ -42,6 +42,7 @@ import { startRuntimeIdleSweeper } from './runtime-idle-sweeper.js';
|
|
|
42
42
|
import { getDefaultKimakiDirectory, getUserProjectCount, } from './channel-management.js';
|
|
43
43
|
import { store } from './store.js';
|
|
44
44
|
import { startExternalOpencodeSessionSync, stopExternalOpencodeSessionSync, } from './external-opencode-sync.js';
|
|
45
|
+
import { stopForumSyncForChannel } from './forum-sync/watchers.js';
|
|
45
46
|
export { initDatabase, closeDatabase, getChannelDirectory, } from './database.js';
|
|
46
47
|
export { initializeOpencodeForDirectory, assertCompatibleOpencodeVersion } from './opencode.js';
|
|
47
48
|
export { escapeBackticksInCodeBlocks, splitMarkdownForDiscord, } from './discord-utils.js';
|
|
@@ -182,7 +183,56 @@ export async function startDiscordBot({ token, appId, discordClient, useWorktree
|
|
|
182
183
|
discordClient = await createDiscordClient();
|
|
183
184
|
}
|
|
184
185
|
store.setState({ useWorktrees: Boolean(useWorktrees) });
|
|
186
|
+
activeDiscordClient = discordClient;
|
|
187
|
+
registerBotLifecycleHandlers();
|
|
185
188
|
let currentAppId = appId;
|
|
189
|
+
const cleanupDeletedDiscordThread = async (threadId) => {
|
|
190
|
+
const abortActiveRun = await isCurrentThreadSessionBinding(threadId);
|
|
191
|
+
disposeRuntime(threadId, { abortActiveRun });
|
|
192
|
+
await cleanupDeletedThread(threadId);
|
|
193
|
+
};
|
|
194
|
+
const cleanupDeletedDiscordChannel = async (channelId) => {
|
|
195
|
+
const mapping = await getChannelDirectory(channelId);
|
|
196
|
+
const preserveMapping = mapping?.directory === getDefaultKimakiDirectory();
|
|
197
|
+
const threadIds = getRuntimeThreadIdsForChannel(channelId);
|
|
198
|
+
await Promise.all(threadIds.map(cleanupDeletedDiscordThread));
|
|
199
|
+
await stopForumSyncForChannel(channelId);
|
|
200
|
+
await clearGuildCategoryByChannelId(channelId);
|
|
201
|
+
if (currentAppId) {
|
|
202
|
+
await deleteForumSyncConfig({
|
|
203
|
+
appId: currentAppId,
|
|
204
|
+
forumChannelId: channelId,
|
|
205
|
+
});
|
|
206
|
+
}
|
|
207
|
+
const deleted = await deleteChannelDirectoryById(channelId, {
|
|
208
|
+
preserveMapping,
|
|
209
|
+
});
|
|
210
|
+
if (preserveMapping) {
|
|
211
|
+
discordLogger.log(`Preserved deleted default channel ${channelId} as a tombstone`);
|
|
212
|
+
return;
|
|
213
|
+
}
|
|
214
|
+
if (deleted) {
|
|
215
|
+
discordLogger.log(`Cleaned up deleted channel ${channelId}`);
|
|
216
|
+
}
|
|
217
|
+
};
|
|
218
|
+
const reconcileDeletedDiscordChannels = async (c) => {
|
|
219
|
+
const mappings = await findChannelsByDirectory({});
|
|
220
|
+
for (const mapping of mappings) {
|
|
221
|
+
const channel = await c.channels
|
|
222
|
+
.fetch(mapping.channel_id, { force: true })
|
|
223
|
+
.catch((error) => {
|
|
224
|
+
const code = error instanceof Error ? Reflect.get(error, 'code') : undefined;
|
|
225
|
+
const status = error instanceof Error ? Reflect.get(error, 'status') : undefined;
|
|
226
|
+
if (code === 10003 || status === 404)
|
|
227
|
+
return null;
|
|
228
|
+
discordLogger.warn(`Could not verify channel ${mapping.channel_id}; preserving its SQLite mapping: ${formatErrorWithStack(error)}`);
|
|
229
|
+
return undefined;
|
|
230
|
+
});
|
|
231
|
+
if (channel !== null)
|
|
232
|
+
continue;
|
|
233
|
+
await cleanupDeletedDiscordChannel(mapping.channel_id);
|
|
234
|
+
}
|
|
235
|
+
};
|
|
186
236
|
const setupHandlers = async (c) => {
|
|
187
237
|
discordLogger.log(`Discord bot logged in as ${c.user.tag}`);
|
|
188
238
|
discordLogger.log(`Connected to ${c.guilds.cache.size} guild(s)`);
|
|
@@ -213,7 +263,14 @@ export async function startDiscordBot({ token, appId, discordClient, useWorktree
|
|
|
213
263
|
})();
|
|
214
264
|
registerInteractionHandler({ discordClient: c, appId: currentAppId });
|
|
215
265
|
registerVoiceStateHandler({ discordClient: c, appId: currentAppId });
|
|
266
|
+
await reconcileDeletedDiscordChannels(c);
|
|
216
267
|
startExternalOpencodeSessionSync({ discordClient: c });
|
|
268
|
+
await restorePersistedLocalQueues({
|
|
269
|
+
discordClient: c,
|
|
270
|
+
appId: currentAppId,
|
|
271
|
+
}).catch((error) => {
|
|
272
|
+
discordLogger.warn(`Failed to restore persisted local queues: ${error instanceof Error ? error.stack : String(error)}`);
|
|
273
|
+
});
|
|
217
274
|
// Channel logging is informational only; do it in background so startup stays responsive.
|
|
218
275
|
void (async () => {
|
|
219
276
|
for (const guild of c.guilds.cache.values()) {
|
|
@@ -230,18 +287,15 @@ export async function startDiscordBot({ token, appId, discordClient, useWorktree
|
|
|
230
287
|
discordLogger.warn(`Background guild channel scan failed: ${error instanceof Error ? error.stack : String(error)}`);
|
|
231
288
|
});
|
|
232
289
|
};
|
|
233
|
-
//
|
|
234
|
-
//
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
void setupHandlers(readyClient).catch((error) => {
|
|
241
|
-
discordLogger.error(`[GATEWAY] ClientReady handler failed: ${formatErrorWithStack(error)}`);
|
|
290
|
+
// Keep startup ordered so stale targets are removed before queues and
|
|
291
|
+
// scheduled tasks can deliver work to them.
|
|
292
|
+
const setupPromise = discordClient.isReady()
|
|
293
|
+
? setupHandlers(discordClient)
|
|
294
|
+
: new Promise((resolve, reject) => {
|
|
295
|
+
discordClient.once(Events.ClientReady, (readyClient) => {
|
|
296
|
+
void setupHandlers(readyClient).then(resolve, reject);
|
|
242
297
|
});
|
|
243
298
|
});
|
|
244
|
-
}
|
|
245
299
|
discordClient.on(Events.Error, (error) => {
|
|
246
300
|
discordLogger.error('[GATEWAY] Client error:', formatErrorWithStack(error));
|
|
247
301
|
});
|
|
@@ -688,7 +742,7 @@ export async function startDiscordBot({ token, appId, discordClient, useWorktree
|
|
|
688
742
|
});
|
|
689
743
|
// Notify when a voice message was queued instead of sent immediately
|
|
690
744
|
if (enqueueResult.queued && enqueueResult.position) {
|
|
691
|
-
await sendThreadMessage(thread, `Queued at position ${enqueueResult.position}. Edit or delete your message to update the queue`);
|
|
745
|
+
await sendThreadMessage(thread, asSystemLine(`Queued at position ${enqueueResult.position}. Edit or delete your message to update the queue`));
|
|
692
746
|
}
|
|
693
747
|
}
|
|
694
748
|
if (channel.type === ChannelType.GuildText) {
|
|
@@ -896,7 +950,7 @@ export async function startDiscordBot({ token, appId, discordClient, useWorktree
|
|
|
896
950
|
const { prompt, forceQueue } = extractQueueSuffix(resolveMentions(message));
|
|
897
951
|
// If the edit removed the queue suffix, remove the item from the queue.
|
|
898
952
|
// If the suffix is still present, update the prompt.
|
|
899
|
-
const result = runtime.updateQueuedMessage(message.id, forceQueue ? prompt : '');
|
|
953
|
+
const result = await runtime.updateQueuedMessage(message.id, forceQueue ? prompt : '');
|
|
900
954
|
if (result.found && channel.isThread()) {
|
|
901
955
|
const displayName = message.member?.displayName ?? message.author.displayName;
|
|
902
956
|
if (result.removed) {
|
|
@@ -924,7 +978,7 @@ export async function startDiscordBot({ token, appId, discordClient, useWorktree
|
|
|
924
978
|
const runtime = getRuntime(channel.id);
|
|
925
979
|
if (!runtime)
|
|
926
980
|
return;
|
|
927
|
-
const removed = runtime.removeQueuedMessage(message.id);
|
|
981
|
+
const removed = await runtime.removeQueuedMessage(message.id);
|
|
928
982
|
if (!removed)
|
|
929
983
|
return;
|
|
930
984
|
discordLogger.log(`[MESSAGE_DELETE] Removed queued message ${message.id} in thread ${channel.id}`);
|
|
@@ -1144,25 +1198,15 @@ export async function startDiscordBot({ token, appId, discordClient, useWorktree
|
|
|
1144
1198
|
// Dispose runtime when a thread is deleted so memory is freed immediately
|
|
1145
1199
|
// instead of waiting for the idle sweeper (1 hour default).
|
|
1146
1200
|
discordClient.on(Events.ThreadDelete, (thread) => {
|
|
1147
|
-
|
|
1201
|
+
void cleanupDeletedDiscordThread(thread.id).catch((error) => {
|
|
1202
|
+
notifyError(error instanceof Error ? error : new Error(String(error)), `Failed to clean up deleted thread ${thread.id}`);
|
|
1203
|
+
});
|
|
1148
1204
|
});
|
|
1149
|
-
// Clean up SQLite
|
|
1150
|
-
//
|
|
1151
|
-
// channel are disposed by their own ThreadDelete events from Discord.
|
|
1205
|
+
// Clean up SQLite and active child runtimes without relying on Discord to
|
|
1206
|
+
// send separate ThreadDelete events for every child.
|
|
1152
1207
|
discordClient.on(Events.ChannelDelete, async (channel) => {
|
|
1153
1208
|
try {
|
|
1154
|
-
|
|
1155
|
-
// channel_directories row as a tombstone so we don't recreate it.
|
|
1156
|
-
const mapping = await getChannelDirectory(channel.id);
|
|
1157
|
-
const defaultDir = getDefaultKimakiDirectory();
|
|
1158
|
-
if (mapping && mapping.directory === defaultDir) {
|
|
1159
|
-
discordLogger.log(`Preserving channel_directories row for deleted default channel ${channel.id} as tombstone`);
|
|
1160
|
-
return;
|
|
1161
|
-
}
|
|
1162
|
-
const deleted = await deleteChannelDirectoryById(channel.id);
|
|
1163
|
-
if (deleted) {
|
|
1164
|
-
discordLogger.log(`Cleaned up channel_directories for deleted channel ${channel.id}`);
|
|
1165
|
-
}
|
|
1209
|
+
await cleanupDeletedDiscordChannel(channel.id);
|
|
1166
1210
|
}
|
|
1167
1211
|
catch (error) {
|
|
1168
1212
|
notifyError(error instanceof Error ? error : new Error(String(error)), `Failed to clean up channel_directories for deleted channel ${channel.id}`);
|
|
@@ -1174,10 +1218,19 @@ export async function startDiscordBot({ token, appId, discordClient, useWorktree
|
|
|
1174
1218
|
if (!discordClient.isReady()) {
|
|
1175
1219
|
await discordClient.login(token);
|
|
1176
1220
|
}
|
|
1221
|
+
await setupPromise;
|
|
1222
|
+
// A stop signal during setup: do not start workers against closing resources.
|
|
1223
|
+
if (global.shuttingDown) {
|
|
1224
|
+
return;
|
|
1225
|
+
}
|
|
1177
1226
|
startHeapMonitor();
|
|
1178
1227
|
startStdinCpuProfListener();
|
|
1179
1228
|
const stopTaskRunner = startTaskRunner({ token });
|
|
1180
1229
|
const stopRuntimeIdleSweeper = startRuntimeIdleSweeper();
|
|
1230
|
+
stopBackgroundWorkers = async () => {
|
|
1231
|
+
await stopRuntimeIdleSweeper();
|
|
1232
|
+
await stopTaskRunner();
|
|
1233
|
+
};
|
|
1181
1234
|
// Prevent discord.js from permanently killing the REST token on 401.
|
|
1182
1235
|
// @discordjs/rest calls setToken(null) whenever it receives a 401 response.
|
|
1183
1236
|
// The gateway proxy now returns 503 for stale-DB rejections (not 401), but
|
|
@@ -1192,117 +1245,6 @@ export async function startDiscordBot({ token, appId, discordClient, useWorktree
|
|
|
1192
1245
|
}
|
|
1193
1246
|
return originalSetToken(newToken);
|
|
1194
1247
|
};
|
|
1195
|
-
const handleShutdown = async (signal, { skipExit = false } = {}) => {
|
|
1196
|
-
discordLogger.log(`Received ${signal}, cleaning up...`);
|
|
1197
|
-
if (global.shuttingDown) {
|
|
1198
|
-
discordLogger.log('Already shutting down, ignoring duplicate signal');
|
|
1199
|
-
return;
|
|
1200
|
-
}
|
|
1201
|
-
global.shuttingDown = true;
|
|
1202
|
-
try {
|
|
1203
|
-
stopStdinCpuProfListener();
|
|
1204
|
-
const flushed = await flushCpuProfiling();
|
|
1205
|
-
if (flushed instanceof Error) {
|
|
1206
|
-
discordLogger.warn('Failed to flush CPU profile on shutdown:', flushed.message);
|
|
1207
|
-
}
|
|
1208
|
-
await stopRuntimeIdleSweeper();
|
|
1209
|
-
await stopTaskRunner();
|
|
1210
|
-
await flushDebouncedProcessCallbacks().catch((error) => {
|
|
1211
|
-
discordLogger.warn('Failed to flush debounced process callbacks:', error instanceof Error ? error.stack : String(error));
|
|
1212
|
-
});
|
|
1213
|
-
await flushAnalytics();
|
|
1214
|
-
// Cancel pending IPC requests so plugin tools don't hang
|
|
1215
|
-
await cancelAllPendingIpcRequests().catch((e) => {
|
|
1216
|
-
discordLogger.warn('Failed to cancel pending IPC requests:', e.message);
|
|
1217
|
-
});
|
|
1218
|
-
const cleanupPromises = [];
|
|
1219
|
-
for (const [guildId] of voiceConnections) {
|
|
1220
|
-
voiceLogger.log(`[SHUTDOWN] Cleaning up voice connection for guild ${guildId}`);
|
|
1221
|
-
cleanupPromises.push(cleanupVoiceConnection(guildId));
|
|
1222
|
-
}
|
|
1223
|
-
if (cleanupPromises.length > 0) {
|
|
1224
|
-
voiceLogger.log(`[SHUTDOWN] Waiting for ${cleanupPromises.length} voice connection(s) to clean up...`);
|
|
1225
|
-
await Promise.allSettled(cleanupPromises);
|
|
1226
|
-
discordLogger.log(`All voice connections cleaned up`);
|
|
1227
|
-
}
|
|
1228
|
-
voiceLogger.log('[SHUTDOWN] Stopping OpenCode server');
|
|
1229
|
-
stopExternalOpencodeSessionSync();
|
|
1230
|
-
await stopOpencodeServer();
|
|
1231
|
-
discordLogger.log('Closing database...');
|
|
1232
|
-
await closeDatabase();
|
|
1233
|
-
discordLogger.log('Stopping hrana server...');
|
|
1234
|
-
await stopHranaServer();
|
|
1235
|
-
discordLogger.log('Destroying Discord client...');
|
|
1236
|
-
// Remove all listeners before destroy to prevent late-arriving shard
|
|
1237
|
-
// errors (from pending DNS lookups, WebSocket frames) from becoming
|
|
1238
|
-
// uncaught exceptions after the client's internal handlers are torn down.
|
|
1239
|
-
discordClient.removeAllListeners();
|
|
1240
|
-
void discordClient.destroy();
|
|
1241
|
-
discordLogger.log('Cleanup complete.');
|
|
1242
|
-
if (!skipExit) {
|
|
1243
|
-
process.exit(0);
|
|
1244
|
-
}
|
|
1245
|
-
}
|
|
1246
|
-
catch (error) {
|
|
1247
|
-
voiceLogger.error('[SHUTDOWN] Error during cleanup:', error);
|
|
1248
|
-
if (!skipExit) {
|
|
1249
|
-
process.exit(1);
|
|
1250
|
-
}
|
|
1251
|
-
}
|
|
1252
|
-
};
|
|
1253
|
-
process.on('SIGTERM', async () => {
|
|
1254
|
-
try {
|
|
1255
|
-
await handleShutdown('SIGTERM');
|
|
1256
|
-
}
|
|
1257
|
-
catch (error) {
|
|
1258
|
-
voiceLogger.error('[SIGTERM] Error during shutdown:', error);
|
|
1259
|
-
process.exit(1);
|
|
1260
|
-
}
|
|
1261
|
-
});
|
|
1262
|
-
process.on('SIGINT', async () => {
|
|
1263
|
-
try {
|
|
1264
|
-
await handleShutdown('SIGINT');
|
|
1265
|
-
}
|
|
1266
|
-
catch (error) {
|
|
1267
|
-
voiceLogger.error('[SIGINT] Error during shutdown:', error);
|
|
1268
|
-
process.exit(1);
|
|
1269
|
-
}
|
|
1270
|
-
});
|
|
1271
|
-
process.on('SIGUSR1', () => {
|
|
1272
|
-
discordLogger.log('Received SIGUSR1, writing heap snapshot...');
|
|
1273
|
-
writeHeapSnapshot().catch((e) => {
|
|
1274
|
-
discordLogger.error('Failed to write heap snapshot:', e instanceof Error ? e.message : String(e));
|
|
1275
|
-
});
|
|
1276
|
-
});
|
|
1277
|
-
// Self-restart: die so the bin.ts wrapper restarts us with exponential
|
|
1278
|
-
// backoff and crash-loop detection. process.exit() can hang joining native
|
|
1279
|
-
// worker threads after Discord gateway failures, so SIGKILL instead.
|
|
1280
|
-
// When running without the wrapper (e.g. `tsx src/cli.ts`), the process
|
|
1281
|
-
// just dies — use `tsx src/bin.ts` for auto-restart support.
|
|
1282
|
-
let selfRestarting = false;
|
|
1283
|
-
async function selfRestart(reason) {
|
|
1284
|
-
if (selfRestarting) {
|
|
1285
|
-
discordLogger.log(`Self-restart already in progress, ignoring duplicate reason: ${reason}`);
|
|
1286
|
-
return;
|
|
1287
|
-
}
|
|
1288
|
-
selfRestarting = true;
|
|
1289
|
-
discordLogger.log(`Self-restarting (reason: ${reason})...`);
|
|
1290
|
-
setTimeout(() => process.kill(process.pid, 'SIGKILL'), 15_000);
|
|
1291
|
-
try {
|
|
1292
|
-
await handleShutdown(reason, { skipExit: true });
|
|
1293
|
-
}
|
|
1294
|
-
catch (error) {
|
|
1295
|
-
voiceLogger.error(`[${reason}] Error during shutdown:`, error);
|
|
1296
|
-
}
|
|
1297
|
-
if (!process.env.__KIMAKI_CHILD) {
|
|
1298
|
-
discordLogger.warn('No restart wrapper detected. Run via `tsx src/bin.ts` (dev) or `kimaki` (npm) for auto-restart on crash.');
|
|
1299
|
-
}
|
|
1300
|
-
process.kill(process.pid, 'SIGKILL');
|
|
1301
|
-
}
|
|
1302
|
-
process.on('SIGUSR2', () => {
|
|
1303
|
-
discordLogger.log('Received SIGUSR2, restarting after cleanup...');
|
|
1304
|
-
void selfRestart('SIGUSR2');
|
|
1305
|
-
});
|
|
1306
1248
|
process.on('uncaughtException', (error) => {
|
|
1307
1249
|
// During self-restart or shutdown, discord.js can still fire errors from
|
|
1308
1250
|
// pending async operations (DNS lookups, WebSocket frames) after the client
|
|
@@ -1314,9 +1256,7 @@ export async function startDiscordBot({ token, appId, discordClient, useWorktree
|
|
|
1314
1256
|
}
|
|
1315
1257
|
discordLogger.error('Uncaught exception:', formatErrorWithStack(error));
|
|
1316
1258
|
notifyError(error, 'Uncaught exception in bot process');
|
|
1317
|
-
void
|
|
1318
|
-
discordLogger.error('[uncaughtException] shutdown failed:', formatErrorWithStack(shutdownError));
|
|
1319
|
-
});
|
|
1259
|
+
void shutdownBot('uncaughtException', { skipExit: true });
|
|
1320
1260
|
setTimeout(() => {
|
|
1321
1261
|
process.exit(1);
|
|
1322
1262
|
}, 250).unref();
|
|
@@ -1333,3 +1273,137 @@ export async function startDiscordBot({ token, appId, discordClient, useWorktree
|
|
|
1333
1273
|
void notifyError(error, 'Unhandled rejection in bot process');
|
|
1334
1274
|
});
|
|
1335
1275
|
}
|
|
1276
|
+
// ── Process lifecycle ────────────────────────────────────────────────
|
|
1277
|
+
// cli-runner installs these handlers right after binding the hrana lock port,
|
|
1278
|
+
// long before Discord is ready. Before this, SIGINT/SIGTERM during startup only
|
|
1279
|
+
// hit the opencode.ts fallback listener, which does not exit, so the process
|
|
1280
|
+
// kept the lock port forever and every next start failed with EADDRINUSE.
|
|
1281
|
+
// Every cleanup step below must therefore be safe at any startup phase.
|
|
1282
|
+
const SHUTDOWN_DEADLINE_MS = 15_000;
|
|
1283
|
+
let activeDiscordClient = null;
|
|
1284
|
+
let stopBackgroundWorkers = null;
|
|
1285
|
+
let selfRestarting = false;
|
|
1286
|
+
let lifecycleHandlersRegistered = false;
|
|
1287
|
+
async function shutdownBot(reason, { skipExit = false } = {}) {
|
|
1288
|
+
discordLogger.log(`Received ${reason}, cleaning up...`);
|
|
1289
|
+
if (global.shuttingDown) {
|
|
1290
|
+
discordLogger.log('Already shutting down, ignoring duplicate signal');
|
|
1291
|
+
return;
|
|
1292
|
+
}
|
|
1293
|
+
global.shuttingDown = true;
|
|
1294
|
+
// Cleanup or process.exit() can hang (native worker threads after gateway
|
|
1295
|
+
// failures). Never let a stuck shutdown hold the lock port. Kept ref'd so a
|
|
1296
|
+
// drained event loop cannot exit with code 0 and skip the wrapper restart.
|
|
1297
|
+
setTimeout(() => {
|
|
1298
|
+
process.kill(process.pid, 'SIGKILL');
|
|
1299
|
+
}, SHUTDOWN_DEADLINE_MS);
|
|
1300
|
+
try {
|
|
1301
|
+
stopStdinCpuProfListener();
|
|
1302
|
+
const flushed = await flushCpuProfiling();
|
|
1303
|
+
if (flushed instanceof Error) {
|
|
1304
|
+
discordLogger.warn('Failed to flush CPU profile on shutdown:', flushed.message);
|
|
1305
|
+
}
|
|
1306
|
+
await stopBackgroundWorkers?.();
|
|
1307
|
+
await flushDebouncedProcessCallbacks().catch((error) => {
|
|
1308
|
+
discordLogger.warn('Failed to flush debounced process callbacks:', error instanceof Error ? error.stack : String(error));
|
|
1309
|
+
});
|
|
1310
|
+
await flushAnalytics();
|
|
1311
|
+
// Cancel pending IPC requests so plugin tools don't hang
|
|
1312
|
+
await cancelAllPendingIpcRequests().catch((e) => {
|
|
1313
|
+
discordLogger.warn('Failed to cancel pending IPC requests:', e.message);
|
|
1314
|
+
});
|
|
1315
|
+
const cleanupPromises = [];
|
|
1316
|
+
for (const [guildId] of voiceConnections) {
|
|
1317
|
+
voiceLogger.log(`[SHUTDOWN] Cleaning up voice connection for guild ${guildId}`);
|
|
1318
|
+
cleanupPromises.push(cleanupVoiceConnection(guildId));
|
|
1319
|
+
}
|
|
1320
|
+
if (cleanupPromises.length > 0) {
|
|
1321
|
+
voiceLogger.log(`[SHUTDOWN] Waiting for ${cleanupPromises.length} voice connection(s) to clean up...`);
|
|
1322
|
+
await Promise.allSettled(cleanupPromises);
|
|
1323
|
+
discordLogger.log(`All voice connections cleaned up`);
|
|
1324
|
+
}
|
|
1325
|
+
voiceLogger.log('[SHUTDOWN] Stopping OpenCode server');
|
|
1326
|
+
stopExternalOpencodeSessionSync();
|
|
1327
|
+
await stopOpencodeServer();
|
|
1328
|
+
discordLogger.log('Closing database...');
|
|
1329
|
+
await closeDatabase();
|
|
1330
|
+
discordLogger.log('Stopping hrana server...');
|
|
1331
|
+
await stopHranaServer();
|
|
1332
|
+
if (activeDiscordClient) {
|
|
1333
|
+
discordLogger.log('Destroying Discord client...');
|
|
1334
|
+
// Remove all listeners before destroy to prevent late-arriving shard
|
|
1335
|
+
// errors (from pending DNS lookups, WebSocket frames) from becoming
|
|
1336
|
+
// uncaught exceptions after the client's internal handlers are torn down.
|
|
1337
|
+
activeDiscordClient.removeAllListeners();
|
|
1338
|
+
void activeDiscordClient.destroy();
|
|
1339
|
+
}
|
|
1340
|
+
discordLogger.log('Cleanup complete.');
|
|
1341
|
+
if (!skipExit) {
|
|
1342
|
+
process.exit(0);
|
|
1343
|
+
}
|
|
1344
|
+
}
|
|
1345
|
+
catch (error) {
|
|
1346
|
+
voiceLogger.error('[SHUTDOWN] Error during cleanup:', error);
|
|
1347
|
+
if (!skipExit) {
|
|
1348
|
+
process.exit(1);
|
|
1349
|
+
}
|
|
1350
|
+
}
|
|
1351
|
+
}
|
|
1352
|
+
// Self-restart: die so the bin.ts wrapper restarts us with exponential
|
|
1353
|
+
// backoff and crash-loop detection. process.exit() can hang joining native
|
|
1354
|
+
// worker threads after Discord gateway failures, so SIGKILL instead.
|
|
1355
|
+
// When running without the wrapper (e.g. `tsx src/cli.ts`), the process
|
|
1356
|
+
// just dies — use `tsx src/bin.ts` for auto-restart support.
|
|
1357
|
+
async function selfRestart(reason) {
|
|
1358
|
+
if (selfRestarting) {
|
|
1359
|
+
discordLogger.log(`Self-restart already in progress, ignoring duplicate reason: ${reason}`);
|
|
1360
|
+
return;
|
|
1361
|
+
}
|
|
1362
|
+
selfRestarting = true;
|
|
1363
|
+
discordLogger.log(`Self-restarting (reason: ${reason})...`);
|
|
1364
|
+
await shutdownBot(reason, { skipExit: true });
|
|
1365
|
+
if (!process.env.__KIMAKI_CHILD) {
|
|
1366
|
+
discordLogger.warn('No restart wrapper detected. Run via `tsx src/bin.ts` (dev) or `kimaki` (npm) for auto-restart on crash.');
|
|
1367
|
+
}
|
|
1368
|
+
process.kill(process.pid, 'SIGKILL');
|
|
1369
|
+
}
|
|
1370
|
+
/** Idempotent. Call as soon as the process owns the lock port. */
|
|
1371
|
+
export function registerBotLifecycleHandlers() {
|
|
1372
|
+
if (lifecycleHandlersRegistered) {
|
|
1373
|
+
return;
|
|
1374
|
+
}
|
|
1375
|
+
lifecycleHandlersRegistered = true;
|
|
1376
|
+
// SIGHUP: closing the terminal must stop the bot, not leave it orphaned.
|
|
1377
|
+
for (const signal of ['SIGINT', 'SIGTERM', 'SIGHUP']) {
|
|
1378
|
+
process.on(signal, () => {
|
|
1379
|
+
void shutdownBot(signal);
|
|
1380
|
+
});
|
|
1381
|
+
}
|
|
1382
|
+
process.on('SIGUSR1', () => {
|
|
1383
|
+
discordLogger.log('Received SIGUSR1, writing heap snapshot...');
|
|
1384
|
+
writeHeapSnapshot().catch((e) => {
|
|
1385
|
+
discordLogger.error('Failed to write heap snapshot:', e instanceof Error ? e.message : String(e));
|
|
1386
|
+
});
|
|
1387
|
+
});
|
|
1388
|
+
process.on('SIGUSR2', () => {
|
|
1389
|
+
discordLogger.log('Received SIGUSR2, restarting after cleanup...');
|
|
1390
|
+
void selfRestart('SIGUSR2');
|
|
1391
|
+
});
|
|
1392
|
+
// bin.ts spawns us with an IPC channel. The OS closes it when the wrapper
|
|
1393
|
+
// dies for any reason (including SIGKILL), so an orphaned child exits instead
|
|
1394
|
+
// of holding the lock port. Guarded by __KIMAKI_CHILD so vitest workers,
|
|
1395
|
+
// which also have an IPC channel, are not affected.
|
|
1396
|
+
if (!process.env.__KIMAKI_CHILD) {
|
|
1397
|
+
return;
|
|
1398
|
+
}
|
|
1399
|
+
// The wrapper can die before we get here (e.g. during the eviction wait);
|
|
1400
|
+
// 'disconnect' is not replayed for late listeners.
|
|
1401
|
+
if (!process.connected) {
|
|
1402
|
+
void shutdownBot('wrapper-disconnect');
|
|
1403
|
+
return;
|
|
1404
|
+
}
|
|
1405
|
+
process.on('disconnect', () => {
|
|
1406
|
+
void shutdownBot('wrapper-disconnect');
|
|
1407
|
+
});
|
|
1408
|
+
process.channel?.unref();
|
|
1409
|
+
}
|
|
@@ -335,7 +335,7 @@ export async function registerCommands({ token, appId, guildIds, userCommands =
|
|
|
335
335
|
.toJSON(),
|
|
336
336
|
new SlashCommandBuilder()
|
|
337
337
|
.setName('session-id')
|
|
338
|
-
.setDescription(truncateCommandDescription('Show current session ID and opencode attach command for this thread'))
|
|
338
|
+
.setDescription(truncateCommandDescription('Show current session ID, Discord thread ID, and opencode attach command for this thread'))
|
|
339
339
|
.setDMPermission(false)
|
|
340
340
|
.toJSON(),
|
|
341
341
|
new SlashCommandBuilder()
|
|
@@ -64,13 +64,13 @@ describe('external directory permissions', () => {
|
|
|
64
64
|
"--- from: user (external-directory-tester)
|
|
65
65
|
EXTERNAL_DIRECTORY_PROBE_MARKER first
|
|
66
66
|
--- from: assistant (TestBot)
|
|
67
|
-
|
|
67
|
+
-# *using deterministic-provider/deterministic-v2*
|
|
68
68
|
> reading external directory
|
|
69
69
|
|
|
70
70
|
┣ read *probe.txt*
|
|
71
71
|
|
|
72
72
|
> external-directory-probe-done
|
|
73
|
-
|
|
73
|
+
-# *project ⋅ main ⋅ Ns ⋅ N% ⋅ deterministic-v2*
|
|
74
74
|
--- from: user (external-directory-tester)
|
|
75
75
|
EXTERNAL_DIRECTORY_PROBE_MARKER followup
|
|
76
76
|
--- from: assistant (TestBot)
|
|
@@ -79,7 +79,7 @@ describe('external directory permissions', () => {
|
|
|
79
79
|
┣ read *probe.txt*
|
|
80
80
|
|
|
81
81
|
> external-directory-probe-done
|
|
82
|
-
|
|
82
|
+
-# *project ⋅ main ⋅ Ns ⋅ N% ⋅ deterministic-v2*"
|
|
83
83
|
`);
|
|
84
84
|
expect(text).not.toContain('Permission Required');
|
|
85
85
|
});
|
|
@@ -54,7 +54,7 @@ describe('external directory project deny', () => {
|
|
|
54
54
|
"--- from: user (external-directory-deny-tester)
|
|
55
55
|
EXTERNAL_DIRECTORY_PROBE_MARKER denied
|
|
56
56
|
--- from: assistant (TestBot)
|
|
57
|
-
|
|
57
|
+
-# *using deterministic-provider/deterministic-v2*
|
|
58
58
|
> reading external directory
|
|
59
59
|
|
|
60
60
|
┣ read *probe.txt*
|
|
@@ -251,6 +251,21 @@ export async function stopConfiguredForumSync() {
|
|
|
251
251
|
});
|
|
252
252
|
}));
|
|
253
253
|
}
|
|
254
|
+
export async function stopForumSyncForChannel(forumChannelId) {
|
|
255
|
+
const runtimeState = forumStateById.get(forumChannelId);
|
|
256
|
+
if (runtimeState?.discordDebounceTimer) {
|
|
257
|
+
clearTimeout(runtimeState.discordDebounceTimer);
|
|
258
|
+
}
|
|
259
|
+
if (runtimeState?.fileDebounceTimer) {
|
|
260
|
+
clearTimeout(runtimeState.fileDebounceTimer);
|
|
261
|
+
}
|
|
262
|
+
forumStateById.delete(forumChannelId);
|
|
263
|
+
const unsubscribe = watcherUnsubscribeByForumId.get(forumChannelId);
|
|
264
|
+
watcherUnsubscribeByForumId.delete(forumChannelId);
|
|
265
|
+
await unsubscribe?.().catch((cause) => {
|
|
266
|
+
forumLogger.warn(`Failed to unsubscribe deleted forum ${forumChannelId}:`, cause);
|
|
267
|
+
});
|
|
268
|
+
}
|
|
254
269
|
export async function startConfiguredForumSync({ discordClient, appId, }) {
|
|
255
270
|
const loadedConfig = await readForumSyncConfig({ appId });
|
|
256
271
|
if (loadedConfig instanceof Error)
|
|
@@ -323,9 +323,9 @@ describeIf('gateway-proxy e2e', () => {
|
|
|
323
323
|
"--- from: user (proxy-tester)
|
|
324
324
|
hello from gateway proxy test
|
|
325
325
|
--- from: assistant (TestBot)
|
|
326
|
-
|
|
326
|
+
-# *using deterministic-provider/deterministic-v2*
|
|
327
327
|
> gateway-proxy-reply
|
|
328
|
-
|
|
328
|
+
-# *project ⋅ main ⋅ Ns ⋅ N% ⋅ deterministic-v2*"
|
|
329
329
|
`);
|
|
330
330
|
expect(reply).toBeDefined();
|
|
331
331
|
expect(getMessageVisibleText(reply).trim().length).toBeGreaterThan(0);
|
|
@@ -350,14 +350,14 @@ describeIf('gateway-proxy e2e', () => {
|
|
|
350
350
|
"--- from: user (proxy-tester)
|
|
351
351
|
hello from gateway proxy test
|
|
352
352
|
--- from: assistant (TestBot)
|
|
353
|
-
|
|
353
|
+
-# *using deterministic-provider/deterministic-v2*
|
|
354
354
|
> gateway-proxy-reply
|
|
355
|
-
|
|
355
|
+
-# *project ⋅ main ⋅ Ns ⋅ N% ⋅ deterministic-v2*
|
|
356
356
|
--- from: user (proxy-tester)
|
|
357
357
|
follow up through proxy
|
|
358
358
|
--- from: assistant (TestBot)
|
|
359
359
|
> gateway-proxy-reply
|
|
360
|
-
|
|
360
|
+
-# *project ⋅ main ⋅ Ns ⋅ N% ⋅ deterministic-v2*"
|
|
361
361
|
`);
|
|
362
362
|
expect(reply).toBeDefined();
|
|
363
363
|
expect(getMessageVisibleText(reply).trim().length).toBeGreaterThan(0);
|
|
@@ -382,14 +382,14 @@ describeIf('gateway-proxy e2e', () => {
|
|
|
382
382
|
"--- from: user (proxy-tester)
|
|
383
383
|
hello from gateway proxy test
|
|
384
384
|
--- from: assistant (TestBot)
|
|
385
|
-
|
|
385
|
+
-# *using deterministic-provider/deterministic-v2*
|
|
386
386
|
> gateway-proxy-reply
|
|
387
|
-
|
|
387
|
+
-# *project ⋅ main ⋅ Ns ⋅ N% ⋅ deterministic-v2*
|
|
388
388
|
--- from: user (proxy-tester)
|
|
389
389
|
follow up through proxy
|
|
390
390
|
--- from: assistant (TestBot)
|
|
391
391
|
> gateway-proxy-reply
|
|
392
|
-
|
|
392
|
+
-# *project ⋅ main ⋅ Ns ⋅ N% ⋅ deterministic-v2*
|
|
393
393
|
--- from: user (proxy-tester)
|
|
394
394
|
!echo proxy-shell-test
|
|
395
395
|
--- from: assistant (TestBot)
|
|
@@ -417,7 +417,7 @@ describeIf('gateway-proxy e2e', () => {
|
|
|
417
417
|
"--- from: user (proxy-tester)
|
|
418
418
|
second message through proxy
|
|
419
419
|
--- from: assistant (TestBot)
|
|
420
|
-
|
|
420
|
+
-# *using deterministic-provider/deterministic-v2*"
|
|
421
421
|
`);
|
|
422
422
|
expect(reply).toBeDefined();
|
|
423
423
|
expect(getMessageVisibleText(reply).trim().length).toBeGreaterThan(0);
|