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
|
@@ -17,7 +17,7 @@ import { buildDeterministicOpencodeConfig, } from 'opencode-deterministic-provid
|
|
|
17
17
|
import { setDataDir } from './config.js';
|
|
18
18
|
import { store } from './store.js';
|
|
19
19
|
import { startDiscordBot } from './discord-bot.js';
|
|
20
|
-
import { setBotToken, initDatabase, closeDatabase, setChannelDirectory, setChannelVerbosity, } from './database.js';
|
|
20
|
+
import { setBotToken, initDatabase, closeDatabase, setChannelDirectory, setChannelVerbosity, getThreadSession, createPendingWorkspace, setWorkspaceReady, getThreadWorktreeOrWorkspace, } from './database.js';
|
|
21
21
|
import { startHranaServer, stopHranaServer } from './hrana-server.js';
|
|
22
22
|
import { initializeOpencodeForDirectory, getOpencodeClient, stopOpencodeServer } from './opencode.js';
|
|
23
23
|
import { chooseLockPort, cleanupTestSessions, initTestGitRepo, waitForFooterMessage, waitForBotMessageContaining, waitForThreadState, } from './test-utils.js';
|
|
@@ -332,6 +332,120 @@ e2eTest('voice message handling', () => {
|
|
|
332
332
|
setDeterministicTranscription(null);
|
|
333
333
|
});
|
|
334
334
|
// ── Test 1: Voice message in a channel creates thread + session ──
|
|
335
|
+
// Vitest disallows inline snapshots in test.each; register separate tests instead.
|
|
336
|
+
for (const sessionAction of ['btw', 'new-session']) {
|
|
337
|
+
test(`voice ${sessionAction} routing creates a separate thread with the correct history`, async () => {
|
|
338
|
+
await discord.channel(TEXT_CHANNEL_ID).user(TEST_USER_ID)
|
|
339
|
+
.sendMessage({ content: `Source history for voice ${sessionAction}` });
|
|
340
|
+
const thread = await discord.channel(TEXT_CHANNEL_ID).waitForThread({
|
|
341
|
+
timeout: 4_000,
|
|
342
|
+
predicate: (candidate) => Boolean(candidate.name?.includes(`Source history for voice ${sessionAction}`)),
|
|
343
|
+
});
|
|
344
|
+
await waitForFooterMessage({ discord, threadId: thread.id, timeout: 4_000 });
|
|
345
|
+
const sourceSessionId = await getThreadSession(thread.id);
|
|
346
|
+
await createPendingWorkspace({
|
|
347
|
+
threadId: thread.id,
|
|
348
|
+
workspaceType: 'worktree',
|
|
349
|
+
workspaceName: 'voice-workspace',
|
|
350
|
+
projectDirectory: directories.projectDirectory,
|
|
351
|
+
});
|
|
352
|
+
await setWorkspaceReady({
|
|
353
|
+
threadId: thread.id,
|
|
354
|
+
workspaceId: 'voice-workspace-id',
|
|
355
|
+
workspaceDirectory: directories.projectDirectory,
|
|
356
|
+
});
|
|
357
|
+
const prompt = `Explain voice routing ${sessionAction}`;
|
|
358
|
+
setDeterministicTranscription({ transcription: prompt, queueMessage: false, sessionAction });
|
|
359
|
+
await discord.thread(thread.id).user(TEST_USER_ID).sendVoiceMessage();
|
|
360
|
+
const confirmation = sessionAction === 'btw' ? 'Session forked!' : 'Created new session in';
|
|
361
|
+
const routingMessages = await waitForBotMessageContaining({
|
|
362
|
+
discord,
|
|
363
|
+
threadId: thread.id,
|
|
364
|
+
text: confirmation,
|
|
365
|
+
timeout: 4_000,
|
|
366
|
+
});
|
|
367
|
+
// OpenCode can rename the destination before discovery; follow its confirmed ID.
|
|
368
|
+
const targetId = routingMessages.find((message) => message.content.includes(confirmation))
|
|
369
|
+
?.content.match(/<#(\d+)>/)?.[1];
|
|
370
|
+
if (!targetId)
|
|
371
|
+
throw new Error('Expected destination thread link');
|
|
372
|
+
const target = await discord.channel(TEXT_CHANNEL_ID).waitForThread({
|
|
373
|
+
timeout: 4_000,
|
|
374
|
+
predicate: (candidate) => candidate.id === targetId,
|
|
375
|
+
});
|
|
376
|
+
await waitForFooterMessage({ discord, threadId: target.id, timeout: 4_000 });
|
|
377
|
+
const th = discord.thread(target.id);
|
|
378
|
+
const transcript = {
|
|
379
|
+
source: (await discord.thread(thread.id).text()).replaceAll(target.id, 'TARGET_THREAD'),
|
|
380
|
+
target: (await th.text()).replaceAll(thread.id, 'SOURCE_THREAD'),
|
|
381
|
+
};
|
|
382
|
+
if (sessionAction === 'btw') {
|
|
383
|
+
expect(transcript).toMatchInlineSnapshot(`
|
|
384
|
+
{
|
|
385
|
+
"source": "--- from: user (voice-tester)
|
|
386
|
+
Source history for voice btw
|
|
387
|
+
--- from: assistant (TestBot)
|
|
388
|
+
> *using deterministic-provider/deterministic-v2*
|
|
389
|
+
> session-reply
|
|
390
|
+
> *project ⋅ main ⋅ <1s ⋅ 0% ⋅ deterministic-v2* <@300000000000000777>
|
|
391
|
+
--- from: user (voice-tester)
|
|
392
|
+
[attachment: voice-message.ogg]
|
|
393
|
+
--- from: assistant (TestBot)
|
|
394
|
+
🎤 Transcribing voice message...
|
|
395
|
+
📝 **Transcribed message:** Explain voice routing btw
|
|
396
|
+
Session forked! Continue in <#TARGET_THREAD>",
|
|
397
|
+
"target": "--- from: assistant (TestBot)
|
|
398
|
+
Reusing context from <#SOURCE_THREAD> to answer prompt...
|
|
399
|
+
Explain voice routing btw
|
|
400
|
+
> session-reply
|
|
401
|
+
> *project ⋅ main ⋅ <1s ⋅ 0% ⋅ deterministic-v2* <@300000000000000777>",
|
|
402
|
+
}
|
|
403
|
+
`);
|
|
404
|
+
}
|
|
405
|
+
else {
|
|
406
|
+
expect(transcript).toMatchInlineSnapshot(`
|
|
407
|
+
{
|
|
408
|
+
"source": "--- from: user (voice-tester)
|
|
409
|
+
Source history for voice new-session
|
|
410
|
+
--- from: assistant (TestBot)
|
|
411
|
+
> *using deterministic-provider/deterministic-v2*
|
|
412
|
+
> session-reply
|
|
413
|
+
> *project ⋅ main ⋅ <1s ⋅ 0% ⋅ deterministic-v2* <@300000000000000777>
|
|
414
|
+
--- from: user (voice-tester)
|
|
415
|
+
[attachment: voice-message.ogg]
|
|
416
|
+
--- from: assistant (TestBot)
|
|
417
|
+
🎤 Transcribing voice message...
|
|
418
|
+
📝 **Transcribed message:** Explain voice routing new-session
|
|
419
|
+
Created new session in <#TARGET_THREAD>",
|
|
420
|
+
"target": "--- from: assistant (TestBot)
|
|
421
|
+
**Starting OpenCode session**
|
|
422
|
+
Explain voice routing new-session
|
|
423
|
+
> *using deterministic-provider/deterministic-v2*
|
|
424
|
+
> session-reply
|
|
425
|
+
> *project ⋅ main ⋅ <1s ⋅ 0% ⋅ deterministic-v2* <@300000000000000777>",
|
|
426
|
+
}
|
|
427
|
+
`);
|
|
428
|
+
}
|
|
429
|
+
const targetSessionId = await getThreadSession(target.id);
|
|
430
|
+
expect(targetSessionId).toBeTruthy();
|
|
431
|
+
expect(targetSessionId).not.toBe(sourceSessionId);
|
|
432
|
+
expect(await getThreadWorktreeOrWorkspace(target.id)).toMatchObject({
|
|
433
|
+
status: 'ready',
|
|
434
|
+
workspace_type: 'worktree',
|
|
435
|
+
workspace_name: 'voice-workspace',
|
|
436
|
+
workspace_id: 'voice-workspace-id',
|
|
437
|
+
workspace_directory: directories.projectDirectory,
|
|
438
|
+
});
|
|
439
|
+
const client = getOpencodeClientForTest(directories.projectDirectory);
|
|
440
|
+
const targetMessages = await client.session.messages({ sessionID: targetSessionId });
|
|
441
|
+
const targetTexts = getUserTexts(targetMessages.data ?? []).join('\n');
|
|
442
|
+
expect(targetTexts).toContain(prompt);
|
|
443
|
+
expect(targetTexts).toContain(`Voice message transcription from Discord user:\n${prompt}`);
|
|
444
|
+
expect(targetTexts.includes(`Source history for voice ${sessionAction}`)).toBe(sessionAction === 'btw');
|
|
445
|
+
const sourceMessages = await client.session.messages({ sessionID: sourceSessionId });
|
|
446
|
+
expect(getUserTexts(sourceMessages.data ?? []).join('\n')).not.toContain(prompt);
|
|
447
|
+
});
|
|
448
|
+
}
|
|
335
449
|
test('voice message in channel creates thread and starts session', async () => {
|
|
336
450
|
setDeterministicTranscription({
|
|
337
451
|
transcription: 'Fix the login bug in auth.ts',
|
|
@@ -394,9 +508,9 @@ e2eTest('voice message handling', () => {
|
|
|
394
508
|
--- from: assistant (TestBot)
|
|
395
509
|
🎤 Transcribing voice message...
|
|
396
510
|
📝 **Transcribed message:** Fix the login bug in auth.ts
|
|
397
|
-
*using deterministic-provider/deterministic-v2*
|
|
398
|
-
|
|
399
|
-
*project ⋅ main ⋅
|
|
511
|
+
> *using deterministic-provider/deterministic-v2*
|
|
512
|
+
> session-reply
|
|
513
|
+
> *project ⋅ main ⋅ <1s ⋅ 0% ⋅ deterministic-v2* <@300000000000000777>"
|
|
400
514
|
`);
|
|
401
515
|
expect(finalState.sessionId).toBeDefined();
|
|
402
516
|
// Verify OpenCode session received the transcribed voice message as a prompt
|
|
@@ -484,9 +598,9 @@ e2eTest('voice message handling', () => {
|
|
|
484
598
|
Voice transcription requires an API key (OpenAI or Gemini). Set one to enable voice message transcription.
|
|
485
599
|
Gemini API key saved. Retrying the original voice message.
|
|
486
600
|
📝 **Transcribed message:** Resume the original voice note
|
|
487
|
-
*using deterministic-provider/deterministic-v2*
|
|
488
|
-
|
|
489
|
-
*project ⋅ main ⋅
|
|
601
|
+
> *using deterministic-provider/deterministic-v2*
|
|
602
|
+
> session-reply
|
|
603
|
+
> *project ⋅ main ⋅ <1s ⋅ 0% ⋅ deterministic-v2* <@300000000000000777>"
|
|
490
604
|
`);
|
|
491
605
|
const finalState = getThreadState(thread.id);
|
|
492
606
|
expect(finalState?.sessionId).toBeDefined();
|
|
@@ -548,9 +662,9 @@ e2eTest('voice message handling', () => {
|
|
|
548
662
|
--- from: assistant (TestBot)
|
|
549
663
|
🎤 Transcribing voice message...
|
|
550
664
|
📝 **Transcribed message:** Investigate the missing content type path
|
|
551
|
-
*using deterministic-provider/deterministic-v2*
|
|
552
|
-
|
|
553
|
-
*project ⋅ main ⋅
|
|
665
|
+
> *using deterministic-provider/deterministic-v2*
|
|
666
|
+
> session-reply
|
|
667
|
+
> *project ⋅ main ⋅ <1s ⋅ 0% ⋅ deterministic-v2* <@300000000000000777>"
|
|
554
668
|
`);
|
|
555
669
|
const messages = await waitForSessionMessages({
|
|
556
670
|
projectDirectory: directories.projectDirectory,
|
|
@@ -637,16 +751,16 @@ e2eTest('voice message handling', () => {
|
|
|
637
751
|
"--- from: user (voice-tester)
|
|
638
752
|
FAST_RESPONSE_MARKER initial setup
|
|
639
753
|
--- from: assistant (TestBot)
|
|
640
|
-
*using deterministic-provider/deterministic-v2*
|
|
641
|
-
|
|
642
|
-
*project ⋅ main ⋅
|
|
754
|
+
> *using deterministic-provider/deterministic-v2*
|
|
755
|
+
> fast-response-done
|
|
756
|
+
> *project ⋅ main ⋅ <1s ⋅ 0% ⋅ deterministic-v2* <@300000000000000777>
|
|
643
757
|
--- from: user (voice-tester)
|
|
644
758
|
[attachment: voice-message.ogg]
|
|
645
759
|
--- from: assistant (TestBot)
|
|
646
760
|
🎤 Transcribing voice message...
|
|
647
761
|
📝 **Transcribed message:** Add error handling to the parser
|
|
648
|
-
|
|
649
|
-
*project ⋅ main ⋅
|
|
762
|
+
> session-reply
|
|
763
|
+
> *project ⋅ main ⋅ <1s ⋅ 0% ⋅ deterministic-v2* <@300000000000000777>"
|
|
650
764
|
`);
|
|
651
765
|
expect(finalState?.sessionId).toBeDefined();
|
|
652
766
|
if (!finalState?.sessionId) {
|
|
@@ -848,19 +962,19 @@ e2eTest('voice message handling', () => {
|
|
|
848
962
|
"--- from: user (voice-tester)
|
|
849
963
|
SLOW_RESPONSE_MARKER start queued task
|
|
850
964
|
--- from: assistant (TestBot)
|
|
851
|
-
*using deterministic-provider/deterministic-v2*
|
|
965
|
+
> *using deterministic-provider/deterministic-v2*
|
|
852
966
|
--- from: user (voice-tester)
|
|
853
967
|
[attachment: voice-message.ogg]
|
|
854
968
|
--- from: assistant (TestBot)
|
|
855
969
|
🎤 Transcribing voice message...
|
|
856
970
|
📝 **Transcribed message:** Queue this task for later
|
|
857
971
|
Queued at position 1. Edit or delete your message to update the queue
|
|
858
|
-
|
|
859
|
-
*project ⋅ main ⋅
|
|
972
|
+
> slow-response-done
|
|
973
|
+
> *project ⋅ main ⋅ 2s ⋅ 0% ⋅ deterministic-v2*
|
|
860
974
|
» **voice-tester:** Voice message transcription from Discord user:
|
|
861
975
|
Queue this task for later
|
|
862
|
-
|
|
863
|
-
*project ⋅ main ⋅
|
|
976
|
+
> session-reply
|
|
977
|
+
> *project ⋅ main ⋅ <1s ⋅ 0% ⋅ deterministic-v2* <@300000000000000777>"
|
|
864
978
|
`);
|
|
865
979
|
expect(finalState.queueItems.length).toBe(0);
|
|
866
980
|
// Verify the OpenCode session processed BOTH prompts sequentially:
|
|
@@ -957,16 +1071,16 @@ e2eTest('voice message handling', () => {
|
|
|
957
1071
|
"--- from: user (voice-tester)
|
|
958
1072
|
FAST_RESPONSE_MARKER quick task
|
|
959
1073
|
--- from: assistant (TestBot)
|
|
960
|
-
*using deterministic-provider/deterministic-v2*
|
|
961
|
-
|
|
962
|
-
*project ⋅ main ⋅
|
|
1074
|
+
> *using deterministic-provider/deterministic-v2*
|
|
1075
|
+
> fast-response-done
|
|
1076
|
+
> *project ⋅ main ⋅ <1s ⋅ 0% ⋅ deterministic-v2* <@300000000000000777>
|
|
963
1077
|
--- from: user (voice-tester)
|
|
964
1078
|
[attachment: voice-message.ogg]
|
|
965
1079
|
--- from: assistant (TestBot)
|
|
966
1080
|
🎤 Transcribing voice message...
|
|
967
1081
|
📝 **Transcribed message:** Delayed transcription result
|
|
968
|
-
|
|
969
|
-
*project ⋅ main ⋅
|
|
1082
|
+
> session-reply
|
|
1083
|
+
> *project ⋅ main ⋅ <1s ⋅ 0% ⋅ deterministic-v2* <@300000000000000777>"
|
|
970
1084
|
`);
|
|
971
1085
|
expect(finalState.sessionId).toBeDefined();
|
|
972
1086
|
expect(finalState.queueItems.length).toBe(0);
|
|
@@ -1075,16 +1189,16 @@ e2eTest('voice message handling', () => {
|
|
|
1075
1189
|
"--- from: user (voice-tester)
|
|
1076
1190
|
FAST_RESPONSE_MARKER fast before queued voice
|
|
1077
1191
|
--- from: assistant (TestBot)
|
|
1078
|
-
*using deterministic-provider/deterministic-v2*
|
|
1079
|
-
|
|
1080
|
-
*project ⋅ main ⋅
|
|
1192
|
+
> *using deterministic-provider/deterministic-v2*
|
|
1193
|
+
> fast-response-done
|
|
1194
|
+
> *project ⋅ main ⋅ <1s ⋅ 0% ⋅ deterministic-v2* <@300000000000000777>
|
|
1081
1195
|
--- from: user (voice-tester)
|
|
1082
1196
|
[attachment: voice-message.ogg]
|
|
1083
1197
|
--- from: assistant (TestBot)
|
|
1084
1198
|
🎤 Transcribing voice message...
|
|
1085
1199
|
📝 **Transcribed message:** Queued voice after idle
|
|
1086
|
-
|
|
1087
|
-
*project ⋅ main ⋅
|
|
1200
|
+
> session-reply
|
|
1201
|
+
> *project ⋅ main ⋅ <1s ⋅ 0% ⋅ deterministic-v2* <@300000000000000777>"
|
|
1088
1202
|
`);
|
|
1089
1203
|
expect(finalState.sessionId).toBeDefined();
|
|
1090
1204
|
expect(finalState.queueItems.length).toBe(0);
|
|
@@ -0,0 +1,195 @@
|
|
|
1
|
+
// Cover long routed prompts and rejected voice routes without an existing session.
|
|
2
|
+
import { afterEach, describe, expect, test } from 'vitest';
|
|
3
|
+
import { ChannelType } from 'discord.js';
|
|
4
|
+
import { setupQueueAdvancedSuite, TEST_USER_ID } from './queue-advanced-e2e-setup.js';
|
|
5
|
+
import { waitForBotMessageContaining, waitForFooterMessage } from './test-utils.js';
|
|
6
|
+
import { getThreadSession } from './database.js';
|
|
7
|
+
import { getOpencodeClient } from './opencode.js';
|
|
8
|
+
import { store } from './store.js';
|
|
9
|
+
const TEXT_CHANNEL_ID = '200000000000001084';
|
|
10
|
+
describe('voice routing input boundaries', () => {
|
|
11
|
+
const ctx = setupQueueAdvancedSuite({
|
|
12
|
+
channelId: TEXT_CHANNEL_ID,
|
|
13
|
+
channelName: 'voice-routing-input',
|
|
14
|
+
dirName: 'voice-routing-input',
|
|
15
|
+
username: 'voice-input-tester',
|
|
16
|
+
});
|
|
17
|
+
afterEach(() => {
|
|
18
|
+
store.setState({ test: { deterministicTranscription: null } });
|
|
19
|
+
});
|
|
20
|
+
test('fresh voice route splits display and preserves the full model prompt', async () => {
|
|
21
|
+
const channel = await ctx.botClient.channels.fetch(TEXT_CHANNEL_ID);
|
|
22
|
+
if (channel?.type !== ChannelType.GuildText)
|
|
23
|
+
throw new Error('Expected text channel');
|
|
24
|
+
const source = await channel.threads.create({ name: 'Long voice source' });
|
|
25
|
+
const prompt = `Long voice request ${'Z'.repeat(2100)} END_OF_REQUEST`;
|
|
26
|
+
const caption = 'Keep the existing public API unchanged.';
|
|
27
|
+
store.setState({
|
|
28
|
+
test: {
|
|
29
|
+
deterministicTranscription: {
|
|
30
|
+
transcription: prompt,
|
|
31
|
+
queueMessage: false,
|
|
32
|
+
sessionAction: 'new-session',
|
|
33
|
+
},
|
|
34
|
+
},
|
|
35
|
+
});
|
|
36
|
+
await ctx.discord.thread(source.id).user(TEST_USER_ID).sendVoiceMessage({ content: caption });
|
|
37
|
+
const routingMessages = await waitForBotMessageContaining({
|
|
38
|
+
discord: ctx.discord,
|
|
39
|
+
threadId: source.id,
|
|
40
|
+
text: 'Created new session in',
|
|
41
|
+
timeout: 4_000,
|
|
42
|
+
});
|
|
43
|
+
const targetId = routingMessages.find((message) => message.content.includes('Created new session in'))
|
|
44
|
+
?.content.match(/<#(\d+)>/)?.[1];
|
|
45
|
+
if (!targetId)
|
|
46
|
+
throw new Error('Expected destination thread link');
|
|
47
|
+
const target = await ctx.discord.channel(TEXT_CHANNEL_ID).waitForThread({
|
|
48
|
+
timeout: 4_000,
|
|
49
|
+
predicate: (thread) => thread.id === targetId,
|
|
50
|
+
});
|
|
51
|
+
await waitForFooterMessage({ discord: ctx.discord, threadId: target.id, timeout: 4_000 });
|
|
52
|
+
expect((await ctx.discord.channel(TEXT_CHANNEL_ID).text()).replace(/Z+/g, '[DETAILS]'))
|
|
53
|
+
.toMatchInlineSnapshot(`
|
|
54
|
+
"--- from: assistant (TestBot)
|
|
55
|
+
**Starting OpenCode session**
|
|
56
|
+
Keep the existing public API unchanged.
|
|
57
|
+
|
|
58
|
+
"
|
|
59
|
+
`);
|
|
60
|
+
expect((await ctx.discord.thread(target.id).text()).replace(/Z+/g, '[DETAILS]'))
|
|
61
|
+
.toMatchInlineSnapshot(`
|
|
62
|
+
"--- from: assistant (TestBot)
|
|
63
|
+
**Starting OpenCode session**
|
|
64
|
+
Keep the existing public API unchanged.
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
Long voice request [DETAILS]
|
|
68
|
+
[DETAILS] END_OF_REQUEST
|
|
69
|
+
> *using deterministic-provider/deterministic-v2*
|
|
70
|
+
> ok
|
|
71
|
+
> *project ⋅ main ⋅ <1s ⋅ 0% ⋅ deterministic-v2* <@200000000000000991>"
|
|
72
|
+
`);
|
|
73
|
+
const channelMessages = await ctx.discord.channel(TEXT_CHANNEL_ID).getMessages();
|
|
74
|
+
expect(channelMessages).toHaveLength(1);
|
|
75
|
+
expect(channelMessages.every((message) => message.content.length <= 2000)).toBe(true);
|
|
76
|
+
const threadText = await ctx.discord.thread(target.id).text();
|
|
77
|
+
expect(threadText).toContain(caption);
|
|
78
|
+
expect(threadText.replace(/\s/g, '')).toContain(prompt.replace(/\s/g, ''));
|
|
79
|
+
expect(threadText.match(/Z/g)).toHaveLength(2100);
|
|
80
|
+
const sessionId = await getThreadSession(target.id);
|
|
81
|
+
if (!sessionId)
|
|
82
|
+
throw new Error('Expected target session');
|
|
83
|
+
const client = getOpencodeClient(ctx.directories.projectDirectory);
|
|
84
|
+
if (!client)
|
|
85
|
+
throw new Error('Expected OpenCode client');
|
|
86
|
+
const messages = await client.session.messages({ sessionID: sessionId });
|
|
87
|
+
const userTexts = messages.data
|
|
88
|
+
?.filter((message) => message.info.role === 'user')
|
|
89
|
+
.flatMap((message) => message.parts.flatMap((part) => (part.type === 'text' ? [part.text] : [])));
|
|
90
|
+
expect(userTexts?.join('\n')).toContain(`Voice message transcription from Discord user:\n${prompt}`);
|
|
91
|
+
expect(userTexts?.join('\n')).toContain(caption);
|
|
92
|
+
expect(await getThreadSession(source.id)).toBeFalsy();
|
|
93
|
+
});
|
|
94
|
+
test('unsupported contextual route keeps the transcript and never dispatches raw text', async () => {
|
|
95
|
+
const channel = await ctx.botClient.channels.fetch(TEXT_CHANNEL_ID);
|
|
96
|
+
if (channel?.type !== ChannelType.GuildText)
|
|
97
|
+
throw new Error('Expected text channel');
|
|
98
|
+
const source = await channel.threads.create({ name: 'Unsupported voice source' });
|
|
99
|
+
const th = ctx.discord.thread(source.id);
|
|
100
|
+
store.setState({
|
|
101
|
+
test: {
|
|
102
|
+
deterministicTranscription: {
|
|
103
|
+
transcription: 'Explain the parser in a contextual side chat',
|
|
104
|
+
queueMessage: false,
|
|
105
|
+
sessionAction: 'btw',
|
|
106
|
+
},
|
|
107
|
+
},
|
|
108
|
+
});
|
|
109
|
+
await th.user(TEST_USER_ID).sendVoiceMessage({ content: 'Do not dispatch this raw text' });
|
|
110
|
+
await waitForBotMessageContaining({
|
|
111
|
+
discord: ctx.discord,
|
|
112
|
+
threadId: source.id,
|
|
113
|
+
text: 'Nothing was sent to the agent',
|
|
114
|
+
timeout: 4_000,
|
|
115
|
+
});
|
|
116
|
+
expect(await th.text()).toMatchInlineSnapshot(`
|
|
117
|
+
"--- from: user (voice-input-tester)
|
|
118
|
+
Do not dispatch this raw text
|
|
119
|
+
[attachment: voice-message.ogg]
|
|
120
|
+
--- from: assistant (TestBot)
|
|
121
|
+
🎤 Transcribing voice message...
|
|
122
|
+
📝 **Transcribed message:** Explain the parser in a contextual side chat
|
|
123
|
+
A contextual side chat needs an existing session. Nothing was sent to the agent. Retry from a session, or ask to create a fresh chat with this request."
|
|
124
|
+
`);
|
|
125
|
+
for (let index = 0; index < 10; index++) {
|
|
126
|
+
await new Promise((resolve) => setTimeout(resolve, 20));
|
|
127
|
+
expect(await getThreadSession(source.id)).toBeFalsy();
|
|
128
|
+
}
|
|
129
|
+
});
|
|
130
|
+
test('routing-only audio does not start a junk turn', async () => {
|
|
131
|
+
const channel = await ctx.botClient.channels.fetch(TEXT_CHANNEL_ID);
|
|
132
|
+
if (channel?.type !== ChannelType.GuildText)
|
|
133
|
+
throw new Error('Expected text channel');
|
|
134
|
+
const source = await channel.threads.create({ name: 'Empty voice source' });
|
|
135
|
+
const th = ctx.discord.thread(source.id);
|
|
136
|
+
store.setState({
|
|
137
|
+
test: {
|
|
138
|
+
deterministicTranscription: {
|
|
139
|
+
transcription: '',
|
|
140
|
+
queueMessage: false,
|
|
141
|
+
sessionAction: 'new-session',
|
|
142
|
+
},
|
|
143
|
+
},
|
|
144
|
+
});
|
|
145
|
+
await th.user(TEST_USER_ID).sendVoiceMessage({ content: 'Create a new chat' });
|
|
146
|
+
await waitForBotMessageContaining({
|
|
147
|
+
discord: ctx.discord,
|
|
148
|
+
threadId: source.id,
|
|
149
|
+
text: 'No request was transcribed',
|
|
150
|
+
timeout: 4_000,
|
|
151
|
+
});
|
|
152
|
+
expect(await th.text()).toMatchInlineSnapshot(`
|
|
153
|
+
"--- from: user (voice-input-tester)
|
|
154
|
+
Create a new chat
|
|
155
|
+
[attachment: voice-message.ogg]
|
|
156
|
+
--- from: assistant (TestBot)
|
|
157
|
+
🎤 Transcribing voice message...
|
|
158
|
+
No request was transcribed. Record another voice message with the task to send."
|
|
159
|
+
`);
|
|
160
|
+
for (let index = 0; index < 10; index++) {
|
|
161
|
+
await new Promise((resolve) => setTimeout(resolve, 20));
|
|
162
|
+
expect(await getThreadSession(source.id)).toBeFalsy();
|
|
163
|
+
}
|
|
164
|
+
});
|
|
165
|
+
test('/new-session confirms the destination and sends the prompt', async () => {
|
|
166
|
+
const channel = await ctx.botClient.channels.fetch(TEXT_CHANNEL_ID);
|
|
167
|
+
if (channel?.type !== ChannelType.GuildText)
|
|
168
|
+
throw new Error('Expected text channel');
|
|
169
|
+
const source = await channel.threads.create({ name: 'Slash source' });
|
|
170
|
+
const th = ctx.discord.thread(source.id);
|
|
171
|
+
const interaction = await th.user(TEST_USER_ID).runSlashCommand({
|
|
172
|
+
name: 'new-session',
|
|
173
|
+
options: [{ name: 'prompt', type: 3, value: 'Slash fresh request' }],
|
|
174
|
+
});
|
|
175
|
+
await th.waitForInteractionAck({ interactionId: interaction.id, timeout: 4_000 });
|
|
176
|
+
const target = await ctx.discord.channel(TEXT_CHANNEL_ID).waitForThread({
|
|
177
|
+
timeout: 4_000,
|
|
178
|
+
predicate: (thread) => thread.name === 'Slash fresh request',
|
|
179
|
+
});
|
|
180
|
+
await waitForFooterMessage({ discord: ctx.discord, threadId: target.id, timeout: 4_000 });
|
|
181
|
+
expect((await th.text({ showInteractions: true })).replaceAll(target.id, 'TARGET_THREAD')).toMatchInlineSnapshot(`
|
|
182
|
+
"[user interaction]
|
|
183
|
+
--- from: assistant (TestBot)
|
|
184
|
+
Created new session in <#TARGET_THREAD>"
|
|
185
|
+
`);
|
|
186
|
+
expect(await ctx.discord.thread(target.id).text()).toMatchInlineSnapshot(`
|
|
187
|
+
"--- from: assistant (TestBot)
|
|
188
|
+
**Starting OpenCode session**
|
|
189
|
+
Slash fresh request
|
|
190
|
+
> *using deterministic-provider/deterministic-v2*
|
|
191
|
+
> ok
|
|
192
|
+
> *project ⋅ main ⋅ <1s ⋅ 0% ⋅ deterministic-v2* <@200000000000000991>"
|
|
193
|
+
`);
|
|
194
|
+
});
|
|
195
|
+
});
|
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
// Voice routes must leave the source question answerable without aborting its run.
|
|
2
|
+
import { afterEach, describe, expect, test } from 'vitest';
|
|
3
|
+
import { setupQueueAdvancedSuite, TEST_USER_ID } from './queue-advanced-e2e-setup.js';
|
|
4
|
+
import { waitForBotMessageContaining, waitForFooterMessage } from './test-utils.js';
|
|
5
|
+
import { store } from './store.js';
|
|
6
|
+
const TEXT_CHANNEL_ID = '200000000000001083';
|
|
7
|
+
describe('voice routing preserves source questions', () => {
|
|
8
|
+
const ctx = setupQueueAdvancedSuite({
|
|
9
|
+
channelId: TEXT_CHANNEL_ID,
|
|
10
|
+
channelName: 'voice-routing-question',
|
|
11
|
+
dirName: 'voice-routing-question',
|
|
12
|
+
username: 'voice-question-tester',
|
|
13
|
+
});
|
|
14
|
+
afterEach(() => {
|
|
15
|
+
store.setState({ test: { deterministicTranscription: null } });
|
|
16
|
+
});
|
|
17
|
+
// Separate registrations permit distinct inline snapshots with Vitest 3.
|
|
18
|
+
for (const sessionAction of ['btw', 'new-session']) {
|
|
19
|
+
test(`${sessionAction} preserves the source dropdown`, async () => {
|
|
20
|
+
const marker = `QUESTION_SELECT_QUEUE_MARKER voice ${sessionAction}`;
|
|
21
|
+
await ctx.discord.channel(TEXT_CHANNEL_ID).user(TEST_USER_ID).sendMessage({ content: marker });
|
|
22
|
+
const source = await ctx.discord.channel(TEXT_CHANNEL_ID).waitForThread({
|
|
23
|
+
timeout: 4_000,
|
|
24
|
+
predicate: (thread) => thread.name === marker,
|
|
25
|
+
});
|
|
26
|
+
const th = ctx.discord.thread(source.id);
|
|
27
|
+
const messages = await waitForBotMessageContaining({
|
|
28
|
+
discord: ctx.discord,
|
|
29
|
+
threadId: source.id,
|
|
30
|
+
text: 'How to proceed?',
|
|
31
|
+
timeout: 4_000,
|
|
32
|
+
});
|
|
33
|
+
const question = messages.find((message) => message.content.includes('How to proceed?'));
|
|
34
|
+
if (!question)
|
|
35
|
+
throw new Error('Expected question message');
|
|
36
|
+
const customId = JSON.stringify(question.components).match(/"custom_id":"(ask_question:[^"]+)"/)?.[1];
|
|
37
|
+
if (!customId)
|
|
38
|
+
throw new Error('Expected question dropdown');
|
|
39
|
+
const prompt = `Explain routing without changing the source ${sessionAction}`;
|
|
40
|
+
store.setState({
|
|
41
|
+
test: {
|
|
42
|
+
deterministicTranscription: {
|
|
43
|
+
transcription: prompt,
|
|
44
|
+
queueMessage: false,
|
|
45
|
+
sessionAction,
|
|
46
|
+
},
|
|
47
|
+
},
|
|
48
|
+
});
|
|
49
|
+
await th.user(TEST_USER_ID).sendVoiceMessage();
|
|
50
|
+
const confirmation = sessionAction === 'btw' ? 'Session forked!' : 'Created new session in';
|
|
51
|
+
const routingMessages = await waitForBotMessageContaining({
|
|
52
|
+
discord: ctx.discord,
|
|
53
|
+
threadId: source.id,
|
|
54
|
+
text: confirmation,
|
|
55
|
+
timeout: 4_000,
|
|
56
|
+
});
|
|
57
|
+
const targetId = routingMessages.find((message) => message.content.includes(confirmation))
|
|
58
|
+
?.content.match(/<#(\d+)>/)?.[1];
|
|
59
|
+
if (!targetId)
|
|
60
|
+
throw new Error('Expected destination thread link');
|
|
61
|
+
const target = await ctx.discord.channel(TEXT_CHANNEL_ID).waitForThread({
|
|
62
|
+
timeout: 4_000,
|
|
63
|
+
predicate: (thread) => thread.id === targetId,
|
|
64
|
+
});
|
|
65
|
+
await waitForFooterMessage({ discord: ctx.discord, threadId: target.id, timeout: 4_000 });
|
|
66
|
+
const interaction = await th.user(TEST_USER_ID).selectMenu({
|
|
67
|
+
messageId: question.id,
|
|
68
|
+
customId,
|
|
69
|
+
values: ['0'],
|
|
70
|
+
});
|
|
71
|
+
await th.waitForInteractionAck({ interactionId: interaction.id, timeout: 4_000 });
|
|
72
|
+
await waitForBotMessageContaining({
|
|
73
|
+
discord: ctx.discord,
|
|
74
|
+
threadId: source.id,
|
|
75
|
+
text: '» **voice-question-tester:** Alpha',
|
|
76
|
+
timeout: 4_000,
|
|
77
|
+
});
|
|
78
|
+
await waitForFooterMessage({ discord: ctx.discord, threadId: source.id, timeout: 4_000 });
|
|
79
|
+
const transcript = (await th.text({ showInteractions: true })).replaceAll(target.id, 'TARGET_THREAD');
|
|
80
|
+
if (sessionAction === 'btw') {
|
|
81
|
+
expect(transcript).toMatchInlineSnapshot(`
|
|
82
|
+
"--- from: user (voice-question-tester)
|
|
83
|
+
QUESTION_SELECT_QUEUE_MARKER voice btw
|
|
84
|
+
--- from: assistant (TestBot)
|
|
85
|
+
> *using deterministic-provider/deterministic-v2*
|
|
86
|
+
**Select action**
|
|
87
|
+
How to proceed?
|
|
88
|
+
✓ _Alpha_
|
|
89
|
+
--- from: user (voice-question-tester)
|
|
90
|
+
[attachment: voice-message.ogg]
|
|
91
|
+
--- from: assistant (TestBot)
|
|
92
|
+
🎤 Transcribing voice message...
|
|
93
|
+
📝 **Transcribed message:** Explain routing without changing the source btw
|
|
94
|
+
Session forked! Continue in <#TARGET_THREAD>
|
|
95
|
+
[user selects dropdown: 0]
|
|
96
|
+
» **voice-question-tester:** Alpha
|
|
97
|
+
> tool done
|
|
98
|
+
> *project ⋅ main ⋅ 1s ⋅ 0% ⋅ deterministic-v2* <@200000000000000991>"
|
|
99
|
+
`);
|
|
100
|
+
}
|
|
101
|
+
else {
|
|
102
|
+
expect(transcript).toMatchInlineSnapshot(`
|
|
103
|
+
"--- from: user (voice-question-tester)
|
|
104
|
+
QUESTION_SELECT_QUEUE_MARKER voice new-session
|
|
105
|
+
--- from: assistant (TestBot)
|
|
106
|
+
> *using deterministic-provider/deterministic-v2*
|
|
107
|
+
**Select action**
|
|
108
|
+
How to proceed?
|
|
109
|
+
✓ _Alpha_
|
|
110
|
+
--- from: user (voice-question-tester)
|
|
111
|
+
[attachment: voice-message.ogg]
|
|
112
|
+
--- from: assistant (TestBot)
|
|
113
|
+
🎤 Transcribing voice message...
|
|
114
|
+
📝 **Transcribed message:** Explain routing without changing the source new-session
|
|
115
|
+
Created new session in <#TARGET_THREAD>
|
|
116
|
+
[user selects dropdown: 0]
|
|
117
|
+
» **voice-question-tester:** Alpha
|
|
118
|
+
> tool done
|
|
119
|
+
> *project ⋅ main ⋅ <1s ⋅ 0% ⋅ deterministic-v2* <@200000000000000991>"
|
|
120
|
+
`);
|
|
121
|
+
}
|
|
122
|
+
expect(transcript).not.toContain('expired');
|
|
123
|
+
});
|
|
124
|
+
}
|
|
125
|
+
});
|