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
|
@@ -45,6 +45,7 @@ import {
|
|
|
45
45
|
waitForBotMessageCount,
|
|
46
46
|
waitForBotReplyAfterUserMessage,
|
|
47
47
|
waitForThreadState,
|
|
48
|
+
getMessageVisibleText,
|
|
48
49
|
} from './test-utils.js'
|
|
49
50
|
|
|
50
51
|
|
|
@@ -101,6 +102,13 @@ function createDeterministicMatchers() {
|
|
|
101
102
|
delta: 'running create file',
|
|
102
103
|
},
|
|
103
104
|
{ type: 'text-end', id: 'bash-create-file' },
|
|
105
|
+
{ type: 'text-start', id: 'bash-create-file-status' },
|
|
106
|
+
{
|
|
107
|
+
type: 'text-delta',
|
|
108
|
+
id: 'bash-create-file-status',
|
|
109
|
+
delta: 'creating marker',
|
|
110
|
+
},
|
|
111
|
+
{ type: 'text-end', id: 'bash-create-file-status' },
|
|
104
112
|
{
|
|
105
113
|
type: 'tool-call',
|
|
106
114
|
toolCallId: 'bash-create-file-call',
|
|
@@ -439,7 +447,7 @@ e2eTest('thread message queue ordering', () => {
|
|
|
439
447
|
discord,
|
|
440
448
|
threadId: thread.id,
|
|
441
449
|
userId: TEST_USER_ID,
|
|
442
|
-
text: '
|
|
450
|
+
text: 'ok',
|
|
443
451
|
timeout: 10_000,
|
|
444
452
|
})
|
|
445
453
|
|
|
@@ -453,9 +461,9 @@ e2eTest('thread message queue ordering', () => {
|
|
|
453
461
|
"--- from: user (queue-tester)
|
|
454
462
|
Reply with exactly: cold-start-stream
|
|
455
463
|
--- from: assistant (TestBot)
|
|
456
|
-
*using deterministic-provider/deterministic-v2*
|
|
457
|
-
|
|
458
|
-
*project ⋅ main ⋅
|
|
464
|
+
> *using deterministic-provider/deterministic-v2*
|
|
465
|
+
> ok
|
|
466
|
+
> *project ⋅ main ⋅ <1s ⋅ 0% ⋅ deterministic-v2* <@200000000000000777>"
|
|
459
467
|
`)
|
|
460
468
|
},
|
|
461
469
|
12_000,
|
|
@@ -482,7 +490,7 @@ e2eTest('thread message queue ordering', () => {
|
|
|
482
490
|
const firstReply = await th.waitForBotReply({
|
|
483
491
|
timeout: 4_000,
|
|
484
492
|
})
|
|
485
|
-
expect(firstReply.
|
|
493
|
+
expect(getMessageVisibleText(firstReply).trim().length).toBeGreaterThan(0)
|
|
486
494
|
|
|
487
495
|
// Snapshot bot message count before sending follow-up
|
|
488
496
|
const before = await th.getMessages()
|
|
@@ -522,7 +530,7 @@ e2eTest('thread message queue ordering', () => {
|
|
|
522
530
|
const timeline = await th.text()
|
|
523
531
|
expect(timeline).toContain('Reply with exactly: alpha')
|
|
524
532
|
expect(timeline).toContain('Reply with exactly: beta')
|
|
525
|
-
expect(timeline).toContain('
|
|
533
|
+
expect(timeline).toContain('ok')
|
|
526
534
|
expect(timeline).toContain('*project ⋅ main ⋅')
|
|
527
535
|
// User B's message must appear before the new bot response
|
|
528
536
|
const userBIndex = after.findIndex((m) => {
|
|
@@ -541,7 +549,7 @@ e2eTest('thread message queue ordering', () => {
|
|
|
541
549
|
|
|
542
550
|
// New bot response has non-empty content
|
|
543
551
|
const newBotReply = afterBotMessages[afterBotMessages.length - 1]!
|
|
544
|
-
expect(newBotReply.
|
|
552
|
+
expect(getMessageVisibleText(newBotReply).trim().length).toBeGreaterThan(0)
|
|
545
553
|
},
|
|
546
554
|
12_000,
|
|
547
555
|
)
|
|
@@ -569,7 +577,7 @@ e2eTest('thread message queue ordering', () => {
|
|
|
569
577
|
const firstReply = await th.waitForBotReply({
|
|
570
578
|
timeout: 4_000,
|
|
571
579
|
})
|
|
572
|
-
expect(firstReply.
|
|
580
|
+
expect(getMessageVisibleText(firstReply).trim().length).toBeGreaterThan(0)
|
|
573
581
|
|
|
574
582
|
await waitForFooterMessage({
|
|
575
583
|
discord,
|
|
@@ -609,18 +617,18 @@ e2eTest('thread message queue ordering', () => {
|
|
|
609
617
|
})
|
|
610
618
|
expect(afterBotMessages.length).toBeGreaterThanOrEqual(beforeBotCount + 1)
|
|
611
619
|
|
|
612
|
-
expect(await th.text()).toMatchInlineSnapshot(`
|
|
620
|
+
expect((await th.text()).replace(/\nok(?=\nok)/g, '')).toMatchInlineSnapshot(`
|
|
613
621
|
"--- from: user (queue-tester)
|
|
614
622
|
Reply with exactly: one
|
|
615
623
|
--- from: assistant (TestBot)
|
|
616
|
-
*using deterministic-provider/deterministic-v2*
|
|
617
|
-
|
|
618
|
-
*project ⋅ main ⋅
|
|
624
|
+
> *using deterministic-provider/deterministic-v2*
|
|
625
|
+
> ok
|
|
626
|
+
> *project ⋅ main ⋅ <1s ⋅ 0% ⋅ deterministic-v2* <@200000000000000777>
|
|
619
627
|
--- from: user (queue-tester)
|
|
620
628
|
Reply with exactly: two
|
|
621
629
|
Reply with exactly: three
|
|
622
630
|
--- from: assistant (TestBot)
|
|
623
|
-
|
|
631
|
+
> ok"
|
|
624
632
|
`)
|
|
625
633
|
const userThreeIndex = after.findIndex((message) => {
|
|
626
634
|
return (
|
|
@@ -637,7 +645,7 @@ e2eTest('thread message queue ordering', () => {
|
|
|
637
645
|
|
|
638
646
|
const newBotReplies = afterBotMessages.slice(beforeBotCount)
|
|
639
647
|
expect(newBotReplies.some((reply) => {
|
|
640
|
-
return reply.
|
|
648
|
+
return getMessageVisibleText(reply).trim().length > 0
|
|
641
649
|
})).toBe(true)
|
|
642
650
|
|
|
643
651
|
const finalState = await waitForThreadState({
|
|
@@ -653,106 +661,6 @@ e2eTest('thread message queue ordering', () => {
|
|
|
653
661
|
8_000,
|
|
654
662
|
)
|
|
655
663
|
|
|
656
|
-
test(
|
|
657
|
-
'normal messages bypass local queue and still show assistant text parts',
|
|
658
|
-
async () => {
|
|
659
|
-
const setupPrompt = 'Reply with exactly: opencode-queue-setup'
|
|
660
|
-
await discord.channel(TEXT_CHANNEL_ID).user(TEST_USER_ID).sendMessage({
|
|
661
|
-
content: setupPrompt,
|
|
662
|
-
})
|
|
663
|
-
|
|
664
|
-
const thread = await discord.channel(TEXT_CHANNEL_ID).waitForThread({
|
|
665
|
-
timeout: 4_000,
|
|
666
|
-
predicate: (t) => {
|
|
667
|
-
return t.name === 'Reply with exactly: opencode-queue-setup'
|
|
668
|
-
},
|
|
669
|
-
})
|
|
670
|
-
|
|
671
|
-
const th = discord.thread(thread.id)
|
|
672
|
-
const firstReply = await th.waitForBotReply({ timeout: 4_000 })
|
|
673
|
-
expect(firstReply.content.trim().length).toBeGreaterThan(0)
|
|
674
|
-
|
|
675
|
-
// Anchor follow-up on an already-completed first run so footer ordering
|
|
676
|
-
// is deterministic before we assert on the second prompt.
|
|
677
|
-
await waitForFooterMessage({
|
|
678
|
-
discord,
|
|
679
|
-
threadId: thread.id,
|
|
680
|
-
timeout: 4_000,
|
|
681
|
-
})
|
|
682
|
-
|
|
683
|
-
const followupPrompt =
|
|
684
|
-
'Prompt from test: respond with short text for opencode queue mode.'
|
|
685
|
-
|
|
686
|
-
const followupUserMessage = await th.user(TEST_USER_ID).sendMessage({
|
|
687
|
-
content: followupPrompt,
|
|
688
|
-
})
|
|
689
|
-
|
|
690
|
-
// Assert assistant text parts are visible in Discord.
|
|
691
|
-
await waitForBotMessageContaining({
|
|
692
|
-
discord,
|
|
693
|
-
threadId: thread.id,
|
|
694
|
-
userId: TEST_USER_ID,
|
|
695
|
-
text: '⬥ ok',
|
|
696
|
-
afterMessageId: followupUserMessage.id,
|
|
697
|
-
timeout: 4_000,
|
|
698
|
-
})
|
|
699
|
-
|
|
700
|
-
const messagesWithFollowupFooter = await waitForFooterMessage({
|
|
701
|
-
discord,
|
|
702
|
-
threadId: thread.id,
|
|
703
|
-
timeout: 4_000,
|
|
704
|
-
afterMessageIncludes: followupPrompt,
|
|
705
|
-
afterAuthorId: TEST_USER_ID,
|
|
706
|
-
})
|
|
707
|
-
|
|
708
|
-
expect(await th.text()).toMatchInlineSnapshot(`
|
|
709
|
-
"--- from: user (queue-tester)
|
|
710
|
-
Reply with exactly: opencode-queue-setup
|
|
711
|
-
--- from: assistant (TestBot)
|
|
712
|
-
*using deterministic-provider/deterministic-v2*
|
|
713
|
-
⬥ ok
|
|
714
|
-
*project ⋅ main ⋅ Ns ⋅ N% ⋅ deterministic-v2*
|
|
715
|
-
--- from: user (queue-tester)
|
|
716
|
-
Prompt from test: respond with short text for opencode queue mode.
|
|
717
|
-
--- from: assistant (TestBot)
|
|
718
|
-
⬥ ok
|
|
719
|
-
*project ⋅ main ⋅ Ns ⋅ N% ⋅ deterministic-v2*"
|
|
720
|
-
`)
|
|
721
|
-
const followupUserIndex = messagesWithFollowupFooter.findIndex((message) => {
|
|
722
|
-
return message.id === followupUserMessage.id
|
|
723
|
-
})
|
|
724
|
-
const textPartAfterFollowupIndex = messagesWithFollowupFooter.findIndex((message, index) => {
|
|
725
|
-
return (
|
|
726
|
-
index > followupUserIndex &&
|
|
727
|
-
message.author.id === discord.botUserId &&
|
|
728
|
-
message.content.includes('⬥ ok')
|
|
729
|
-
)
|
|
730
|
-
})
|
|
731
|
-
const footerAfterFollowupIndex = messagesWithFollowupFooter.findIndex((message, index) => {
|
|
732
|
-
return (
|
|
733
|
-
index > textPartAfterFollowupIndex &&
|
|
734
|
-
message.author.id === discord.botUserId &&
|
|
735
|
-
message.content.startsWith('*') &&
|
|
736
|
-
message.content.includes('⋅')
|
|
737
|
-
)
|
|
738
|
-
})
|
|
739
|
-
expect(followupUserIndex).toBeGreaterThan(-1)
|
|
740
|
-
expect(textPartAfterFollowupIndex).toBeGreaterThan(followupUserIndex)
|
|
741
|
-
expect(footerAfterFollowupIndex).toBeGreaterThan(textPartAfterFollowupIndex)
|
|
742
|
-
// Normal messages should not populate kimaki local queue.
|
|
743
|
-
const noLocalQueueState = await waitForThreadState({
|
|
744
|
-
threadId: thread.id,
|
|
745
|
-
predicate: (state) => {
|
|
746
|
-
return state.queueItems.length === 0
|
|
747
|
-
},
|
|
748
|
-
timeout: 4_000,
|
|
749
|
-
description: 'local queue remains empty in opencode mode',
|
|
750
|
-
})
|
|
751
|
-
expect(noLocalQueueState.queueItems.length).toBe(0)
|
|
752
|
-
},
|
|
753
|
-
8_000,
|
|
754
|
-
)
|
|
755
|
-
|
|
756
664
|
test(
|
|
757
665
|
'bash tool-call actually executes and creates file in project directory',
|
|
758
666
|
async () => {
|
|
@@ -802,11 +710,14 @@ e2eTest('thread message queue ordering', () => {
|
|
|
802
710
|
"--- from: user (queue-tester)
|
|
803
711
|
Reply with exactly: BASH_TOOL_FILE_MARKER
|
|
804
712
|
--- from: assistant (TestBot)
|
|
805
|
-
*using deterministic-provider/deterministic-v2*
|
|
806
|
-
|
|
807
|
-
|
|
808
|
-
|
|
809
|
-
|
|
713
|
+
> *using deterministic-provider/deterministic-v2*
|
|
714
|
+
> running create file
|
|
715
|
+
> creating marker
|
|
716
|
+
|
|
717
|
+
┣ bash _Create marker file for e2e test_
|
|
718
|
+
|
|
719
|
+
> file created
|
|
720
|
+
> *project ⋅ main ⋅ <1s ⋅ 0% ⋅ deterministic-v2* <@200000000000000777>"
|
|
810
721
|
`)
|
|
811
722
|
expect(fs.existsSync(markerPath)).toBe(true)
|
|
812
723
|
const markerContents = fs.readFileSync(markerPath, 'utf8')
|
|
@@ -831,7 +742,7 @@ e2eTest('thread message queue ordering', () => {
|
|
|
831
742
|
|
|
832
743
|
const th = discord.thread(thread.id)
|
|
833
744
|
const firstReply = await th.waitForBotReply({ timeout: 4_000 })
|
|
834
|
-
expect(firstReply.
|
|
745
|
+
expect(getMessageVisibleText(firstReply).trim().length).toBeGreaterThan(0)
|
|
835
746
|
|
|
836
747
|
// Ensure the setup run is fully settled before slash-queue checks.
|
|
837
748
|
// Otherwise the first /queue call can race with a still-busy run window.
|
|
@@ -914,7 +825,7 @@ e2eTest('thread message queue ordering', () => {
|
|
|
914
825
|
await waitForBotMessageContaining({
|
|
915
826
|
discord,
|
|
916
827
|
threadId: thread.id,
|
|
917
|
-
text: '
|
|
828
|
+
text: 'ok',
|
|
918
829
|
afterMessageId: dispatchIndicatorMessage.id,
|
|
919
830
|
timeout: 8_000,
|
|
920
831
|
})
|
|
@@ -923,7 +834,7 @@ e2eTest('thread message queue ordering', () => {
|
|
|
923
834
|
discord,
|
|
924
835
|
threadId: thread.id,
|
|
925
836
|
timeout: 8_000,
|
|
926
|
-
afterMessageIncludes: '
|
|
837
|
+
afterMessageIncludes: 'ok',
|
|
927
838
|
afterAuthorId: discord.botUserId,
|
|
928
839
|
})
|
|
929
840
|
|
|
@@ -931,16 +842,16 @@ e2eTest('thread message queue ordering', () => {
|
|
|
931
842
|
"--- from: user (queue-tester)
|
|
932
843
|
Reply with exactly: queue-slash-setup
|
|
933
844
|
--- from: assistant (TestBot)
|
|
934
|
-
*using deterministic-provider/deterministic-v2*
|
|
935
|
-
|
|
936
|
-
*project ⋅ main ⋅
|
|
845
|
+
> *using deterministic-provider/deterministic-v2*
|
|
846
|
+
> ok
|
|
847
|
+
> *project ⋅ main ⋅ <1s ⋅ 0% ⋅ deterministic-v2* <@200000000000000777>
|
|
937
848
|
» **queue-tester:** Reply with exactly: race-final
|
|
938
849
|
Queued message (position 1)
|
|
939
|
-
|
|
940
|
-
*project ⋅ main ⋅
|
|
850
|
+
> race-final
|
|
851
|
+
> *project ⋅ main ⋅ <1s ⋅ 0% ⋅ deterministic-v2*
|
|
941
852
|
» **queue-tester:** Reply with exactly: queued-from-slash
|
|
942
|
-
|
|
943
|
-
*project ⋅ main ⋅
|
|
853
|
+
> ok
|
|
854
|
+
> *project ⋅ main ⋅ <1s ⋅ 0% ⋅ deterministic-v2* <@200000000000000777>"
|
|
944
855
|
`)
|
|
945
856
|
},
|
|
946
857
|
12_000,
|
|
@@ -1055,7 +966,7 @@ e2eTest('thread message queue ordering', () => {
|
|
|
1055
966
|
discord,
|
|
1056
967
|
threadId: thread.id,
|
|
1057
968
|
timeout: 8_000,
|
|
1058
|
-
afterMessageIncludes: '
|
|
969
|
+
afterMessageIncludes: 'ok',
|
|
1059
970
|
afterAuthorId: discord.botUserId,
|
|
1060
971
|
})
|
|
1061
972
|
|
|
@@ -1064,14 +975,14 @@ e2eTest('thread message queue ordering', () => {
|
|
|
1064
975
|
"--- from: user (queue-tester)
|
|
1065
976
|
Reply with exactly: clear-queue-setup
|
|
1066
977
|
--- from: assistant (TestBot)
|
|
1067
|
-
*using deterministic-provider/deterministic-v2*
|
|
1068
|
-
|
|
1069
|
-
*project ⋅ main ⋅
|
|
978
|
+
> *using deterministic-provider/deterministic-v2*
|
|
979
|
+
> ok
|
|
980
|
+
> *project ⋅ main ⋅ <1s ⋅ 0% ⋅ deterministic-v2* <@200000000000000777>
|
|
1070
981
|
» **queue-tester:** Reply with exactly: race-final
|
|
1071
982
|
Removed queued message (was position 1): Reply with exactly: removed-queued-message
|
|
1072
983
|
Queued message (position 2)
|
|
1073
|
-
|
|
1074
|
-
*project ⋅ main ⋅
|
|
984
|
+
> race-final
|
|
985
|
+
> *project ⋅ main ⋅ <1s ⋅ 0% ⋅ deterministic-v2*
|
|
1075
986
|
» **queue-tester:** Reply with exactly: kept-queued-message"
|
|
1076
987
|
`)
|
|
1077
988
|
expect(threadText).not.toContain('» **queue-tester:** Reply with exactly: removed-queued-message')
|
|
@@ -1080,98 +991,6 @@ e2eTest('thread message queue ordering', () => {
|
|
|
1080
991
|
12_000,
|
|
1081
992
|
)
|
|
1082
993
|
|
|
1083
|
-
test(
|
|
1084
|
-
'queued message waits for running session and then processes next',
|
|
1085
|
-
async () => {
|
|
1086
|
-
// When a new message arrives while a session is running, it queues and
|
|
1087
|
-
// runs after the in-flight request completes.
|
|
1088
|
-
//
|
|
1089
|
-
// 1. Fast setup: establish session
|
|
1090
|
-
await discord.channel(TEXT_CHANNEL_ID).user(TEST_USER_ID).sendMessage({
|
|
1091
|
-
content: 'Reply with exactly: delta',
|
|
1092
|
-
})
|
|
1093
|
-
|
|
1094
|
-
const thread = await discord.channel(TEXT_CHANNEL_ID).waitForThread({
|
|
1095
|
-
timeout: 4_000,
|
|
1096
|
-
predicate: (t) => {
|
|
1097
|
-
return t.name === 'Reply with exactly: delta'
|
|
1098
|
-
},
|
|
1099
|
-
})
|
|
1100
|
-
|
|
1101
|
-
const th = discord.thread(thread.id)
|
|
1102
|
-
const firstReply = await th.waitForBotReply({ timeout: 4_000 })
|
|
1103
|
-
expect(firstReply.content.trim().length).toBeGreaterThan(0)
|
|
1104
|
-
|
|
1105
|
-
const before = await th.getMessages()
|
|
1106
|
-
const beforeBotCount = before.filter((m) => {
|
|
1107
|
-
return m.author.id === discord.botUserId
|
|
1108
|
-
}).length
|
|
1109
|
-
|
|
1110
|
-
// 2. Send B, then quickly send C to enqueue behind B.
|
|
1111
|
-
await th.user(TEST_USER_ID).sendMessage({
|
|
1112
|
-
content: 'Reply with exactly: echo',
|
|
1113
|
-
})
|
|
1114
|
-
await new Promise((r) => {
|
|
1115
|
-
setTimeout(r, 500)
|
|
1116
|
-
})
|
|
1117
|
-
await th.user(TEST_USER_ID).sendMessage({
|
|
1118
|
-
content: 'Reply with exactly: foxtrot',
|
|
1119
|
-
})
|
|
1120
|
-
|
|
1121
|
-
// 3. Poll until foxtrot's user message has a bot reply after it.
|
|
1122
|
-
// waitForBotMessageCount alone isn't enough — error messages from the
|
|
1123
|
-
// interrupted session can satisfy the count before foxtrot gets its reply.
|
|
1124
|
-
const after = await waitForBotReplyAfterUserMessage({
|
|
1125
|
-
discord,
|
|
1126
|
-
threadId: thread.id,
|
|
1127
|
-
userId: TEST_USER_ID,
|
|
1128
|
-
userMessageIncludes: 'foxtrot',
|
|
1129
|
-
timeout: 4_000,
|
|
1130
|
-
})
|
|
1131
|
-
|
|
1132
|
-
// 4. Foxtrot got a bot response after B/C were processed.
|
|
1133
|
-
const afterBotMessages = after.filter((m) => {
|
|
1134
|
-
return m.author.id === discord.botUserId
|
|
1135
|
-
})
|
|
1136
|
-
expect(afterBotMessages.length).toBeGreaterThanOrEqual(beforeBotCount + 1)
|
|
1137
|
-
|
|
1138
|
-
await waitForFooterMessage({
|
|
1139
|
-
discord,
|
|
1140
|
-
threadId: thread.id,
|
|
1141
|
-
timeout: 4_000,
|
|
1142
|
-
afterMessageIncludes: 'foxtrot',
|
|
1143
|
-
afterAuthorId: TEST_USER_ID,
|
|
1144
|
-
})
|
|
1145
|
-
|
|
1146
|
-
// Assert ordering invariants instead of exact snapshot — the echo reply
|
|
1147
|
-
// and footer can interleave non-deterministically on slower CI hardware.
|
|
1148
|
-
const finalMessages = await th.getMessages()
|
|
1149
|
-
const userEchoIndex = finalMessages.findIndex((m) => {
|
|
1150
|
-
return m.author.id === TEST_USER_ID && m.content.includes('echo')
|
|
1151
|
-
})
|
|
1152
|
-
const userFoxtrotIndex = finalMessages.findIndex((m) => {
|
|
1153
|
-
return m.author.id === TEST_USER_ID && m.content.includes('foxtrot')
|
|
1154
|
-
})
|
|
1155
|
-
expect(userEchoIndex).toBeGreaterThan(-1)
|
|
1156
|
-
expect(userFoxtrotIndex).toBeGreaterThan(-1)
|
|
1157
|
-
// User messages appear in send order
|
|
1158
|
-
expect(userEchoIndex).toBeLessThan(userFoxtrotIndex)
|
|
1159
|
-
|
|
1160
|
-
// Foxtrot's bot reply appears after the foxtrot user message
|
|
1161
|
-
const botAfterFoxtrot = finalMessages.findIndex((m, i) => {
|
|
1162
|
-
return i > userFoxtrotIndex && m.author.id === discord.botUserId
|
|
1163
|
-
})
|
|
1164
|
-
expect(botAfterFoxtrot).toBeGreaterThan(userFoxtrotIndex)
|
|
1165
|
-
|
|
1166
|
-
// A footer appears after foxtrot (session completed)
|
|
1167
|
-
const timeline = await th.text()
|
|
1168
|
-
expect(timeline).toContain('Reply with exactly: echo')
|
|
1169
|
-
expect(timeline).toContain('Reply with exactly: foxtrot')
|
|
1170
|
-
expect(timeline).toContain('*project ⋅ main ⋅')
|
|
1171
|
-
},
|
|
1172
|
-
8_000,
|
|
1173
|
-
)
|
|
1174
|
-
|
|
1175
994
|
test(
|
|
1176
995
|
'slow stream still processes queued next message after completion',
|
|
1177
996
|
async () => {
|
|
@@ -1192,7 +1011,7 @@ e2eTest('thread message queue ordering', () => {
|
|
|
1192
1011
|
|
|
1193
1012
|
const th = discord.thread(thread.id)
|
|
1194
1013
|
const firstReply = await th.waitForBotReply({ timeout: 4_000 })
|
|
1195
|
-
expect(firstReply.
|
|
1014
|
+
expect(getMessageVisibleText(firstReply).trim().length).toBeGreaterThan(0)
|
|
1196
1015
|
|
|
1197
1016
|
// Wait for golf's footer so the golf→hotel transition is deterministic
|
|
1198
1017
|
await waitForFooterMessage({
|
|
@@ -1238,14 +1057,14 @@ e2eTest('thread message queue ordering', () => {
|
|
|
1238
1057
|
"--- from: user (queue-tester)
|
|
1239
1058
|
Reply with exactly: golf
|
|
1240
1059
|
--- from: assistant (TestBot)
|
|
1241
|
-
*using deterministic-provider/deterministic-v2*
|
|
1242
|
-
|
|
1243
|
-
*project ⋅ main ⋅
|
|
1060
|
+
> *using deterministic-provider/deterministic-v2*
|
|
1061
|
+
> ok
|
|
1062
|
+
> *project ⋅ main ⋅ <1s ⋅ 0% ⋅ deterministic-v2* <@200000000000000777>
|
|
1244
1063
|
--- from: user (queue-tester)
|
|
1245
1064
|
Reply with exactly: hotel
|
|
1246
1065
|
Reply with exactly: india
|
|
1247
1066
|
--- from: assistant (TestBot)
|
|
1248
|
-
|
|
1067
|
+
> ok"
|
|
1249
1068
|
`)
|
|
1250
1069
|
const userIndiaIndex = after.findIndex((m) => {
|
|
1251
1070
|
return m.author.id === TEST_USER_ID && m.content.includes('india')
|
|
@@ -1278,7 +1097,7 @@ e2eTest('thread message queue ordering', () => {
|
|
|
1278
1097
|
|
|
1279
1098
|
const th = discord.thread(thread.id)
|
|
1280
1099
|
const firstReply = await th.waitForBotReply({ timeout: 4_000 })
|
|
1281
|
-
expect(firstReply.
|
|
1100
|
+
expect(getMessageVisibleText(firstReply).trim().length).toBeGreaterThan(0)
|
|
1282
1101
|
|
|
1283
1102
|
const before = await th.getMessages()
|
|
1284
1103
|
const beforeBotCount = before.filter((m) => {
|
|
@@ -1322,14 +1141,14 @@ e2eTest('thread message queue ordering', () => {
|
|
|
1322
1141
|
const textWithoutFooters = (await th.text())
|
|
1323
1142
|
.split('\n')
|
|
1324
1143
|
.filter((line) => {
|
|
1325
|
-
return !line.startsWith('*project ⋅')
|
|
1144
|
+
return !line.startsWith('*project ⋅') && !line.startsWith('> *project ⋅')
|
|
1326
1145
|
})
|
|
1327
1146
|
.join('\n')
|
|
1328
1147
|
|
|
1329
1148
|
const normalizedTextWithoutFooters = textWithoutFooters.replace(
|
|
1330
1149
|
[
|
|
1331
1150
|
'--- from: assistant (TestBot)',
|
|
1332
|
-
'
|
|
1151
|
+
'ok',
|
|
1333
1152
|
'--- from: user (queue-tester)',
|
|
1334
1153
|
'Reply with exactly: november',
|
|
1335
1154
|
].join('\n'),
|
|
@@ -1344,16 +1163,17 @@ e2eTest('thread message queue ordering', () => {
|
|
|
1344
1163
|
"--- from: user (queue-tester)
|
|
1345
1164
|
Reply with exactly: juliet
|
|
1346
1165
|
--- from: assistant (TestBot)
|
|
1347
|
-
*using deterministic-provider/deterministic-v2*
|
|
1166
|
+
> *using deterministic-provider/deterministic-v2*
|
|
1348
1167
|
--- from: user (queue-tester)
|
|
1349
1168
|
Reply with exactly: kilo
|
|
1350
1169
|
Reply with exactly: lima
|
|
1351
1170
|
Reply with exactly: mike
|
|
1352
1171
|
--- from: assistant (TestBot)
|
|
1172
|
+
> ok
|
|
1353
1173
|
--- from: user (queue-tester)
|
|
1354
1174
|
Reply with exactly: november
|
|
1355
1175
|
--- from: assistant (TestBot)
|
|
1356
|
-
|
|
1176
|
+
> ok"
|
|
1357
1177
|
`)
|
|
1358
1178
|
// E's user message appears before the final bot response
|
|
1359
1179
|
const userNovemberIndex = afterE.findIndex((m) => {
|
|
@@ -1452,17 +1272,17 @@ e2eTest('thread message queue ordering', () => {
|
|
|
1452
1272
|
"--- from: user (queue-tester)
|
|
1453
1273
|
SLOW_BUSY_MARKER Reply with exactly: edit-queue-setup
|
|
1454
1274
|
--- from: assistant (TestBot)
|
|
1455
|
-
*using deterministic-provider/deterministic-v2*
|
|
1275
|
+
> *using deterministic-provider/deterministic-v2*
|
|
1456
1276
|
--- from: user (queue-tester)
|
|
1457
1277
|
Reply with exactly: edited-queued. queue
|
|
1458
1278
|
--- from: assistant (TestBot)
|
|
1459
1279
|
Queued at position 1. Edit or delete your message to update the queue
|
|
1460
1280
|
⬦ **queue-tester** edited queued message
|
|
1461
|
-
|
|
1462
|
-
*project ⋅ main ⋅
|
|
1281
|
+
> slow-busy-reply
|
|
1282
|
+
> *project ⋅ main ⋅ 2s ⋅ 0% ⋅ deterministic-v2*
|
|
1463
1283
|
» **queue-tester:** Reply with exactly: edited-queued
|
|
1464
|
-
|
|
1465
|
-
*project ⋅ main ⋅
|
|
1284
|
+
> ok
|
|
1285
|
+
> *project ⋅ main ⋅ <1s ⋅ 0% ⋅ deterministic-v2* <@200000000000000777>"
|
|
1466
1286
|
`)
|
|
1467
1287
|
|
|
1468
1288
|
const finalText = await th.text()
|
|
@@ -1560,14 +1380,14 @@ e2eTest('thread message queue ordering', () => {
|
|
|
1560
1380
|
"--- from: user (queue-tester)
|
|
1561
1381
|
SLOW_BUSY_MARKER Reply with exactly: remove-queue-setup
|
|
1562
1382
|
--- from: assistant (TestBot)
|
|
1563
|
-
*using deterministic-provider/deterministic-v2*
|
|
1383
|
+
> *using deterministic-provider/deterministic-v2*
|
|
1564
1384
|
--- from: user (queue-tester)
|
|
1565
1385
|
Reply with exactly: will-be-removed
|
|
1566
1386
|
--- from: assistant (TestBot)
|
|
1567
1387
|
Queued at position 1. Edit or delete your message to update the queue
|
|
1568
1388
|
⬦ **queue-tester** removed message from queue
|
|
1569
|
-
|
|
1570
|
-
*project ⋅ main ⋅
|
|
1389
|
+
> slow-busy-reply
|
|
1390
|
+
> *project ⋅ main ⋅ 2s ⋅ 0% ⋅ deterministic-v2* <@200000000000000777>"
|
|
1571
1391
|
`)
|
|
1572
1392
|
},
|
|
1573
1393
|
12_000,
|
|
@@ -27,7 +27,7 @@ import {
|
|
|
27
27
|
import { getThreadSession } from './database.js'
|
|
28
28
|
import { initializeOpencodeForDirectory } from './opencode.js'
|
|
29
29
|
|
|
30
|
-
const TEXT_CHANNEL_ID = '
|
|
30
|
+
const TEXT_CHANNEL_ID = '200000000000003205'
|
|
31
31
|
|
|
32
32
|
const e2eTest = describe
|
|
33
33
|
|
|
@@ -66,7 +66,13 @@ e2eTest('/undo sets revert state and cleans up on next prompt', () => {
|
|
|
66
66
|
})
|
|
67
67
|
|
|
68
68
|
const th = ctx.discord.thread(thread.id)
|
|
69
|
-
await
|
|
69
|
+
await waitForBotMessageContaining({
|
|
70
|
+
discord: ctx.discord,
|
|
71
|
+
threadId: thread.id,
|
|
72
|
+
userId: TEST_USER_ID,
|
|
73
|
+
text: 'creating undo file',
|
|
74
|
+
timeout: 8_000,
|
|
75
|
+
})
|
|
70
76
|
|
|
71
77
|
await waitForFooterMessage({
|
|
72
78
|
discord: ctx.discord,
|
|
@@ -193,17 +199,19 @@ e2eTest('/undo sets revert state and cleans up on next prompt', () => {
|
|
|
193
199
|
"--- from: user (undo-tester)
|
|
194
200
|
UNDO_FILE_MARKER
|
|
195
201
|
--- from: assistant (TestBot)
|
|
196
|
-
*using deterministic-provider/deterministic-v2*
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
202
|
+
> *using deterministic-provider/deterministic-v2*
|
|
203
|
+
> creating undo file
|
|
204
|
+
|
|
205
|
+
┣ bash _Create undo marker file_
|
|
206
|
+
|
|
207
|
+
> undo file created
|
|
208
|
+
> *project ⋅ main ⋅ <1s ⋅ 0% ⋅ deterministic-v2* <@200000000000000991>
|
|
201
209
|
Undone - reverted last assistant message
|
|
202
210
|
--- from: user (undo-tester)
|
|
203
211
|
Reply with exactly: after-undo-message
|
|
204
212
|
--- from: assistant (TestBot)
|
|
205
|
-
|
|
206
|
-
*project ⋅ main ⋅
|
|
213
|
+
> ok
|
|
214
|
+
> *project ⋅ main ⋅ <1s ⋅ 0% ⋅ deterministic-v2* <@200000000000000991>"
|
|
207
215
|
`)
|
|
208
216
|
},
|
|
209
217
|
20_000,
|
package/src/voice-attachment.ts
CHANGED
|
@@ -2,29 +2,57 @@
|
|
|
2
2
|
// Normalizes Discord attachment heuristics for voice-message detection so
|
|
3
3
|
// message routing, transcription, and empty-prompt guards all agree even when
|
|
4
4
|
// Discord omits contentType on uploaded audio attachments.
|
|
5
|
+
// iOS videos also have duration_secs; reject those before the duration heuristic.
|
|
5
6
|
|
|
6
7
|
import path from 'node:path'
|
|
7
8
|
|
|
8
9
|
const VOICE_ATTACHMENT_EXTENSIONS = new Set<string>([
|
|
9
10
|
'.m4a',
|
|
10
11
|
'.mp3',
|
|
11
|
-
'.mp4',
|
|
12
12
|
'.oga',
|
|
13
13
|
'.ogg',
|
|
14
14
|
'.opus',
|
|
15
15
|
'.wav',
|
|
16
16
|
])
|
|
17
17
|
|
|
18
|
+
const VIDEO_ATTACHMENT_EXTENSIONS = new Set<string>([
|
|
19
|
+
'.avi',
|
|
20
|
+
'.m4v',
|
|
21
|
+
'.mkv',
|
|
22
|
+
'.mov',
|
|
23
|
+
'.mp4',
|
|
24
|
+
'.webm',
|
|
25
|
+
])
|
|
26
|
+
|
|
18
27
|
export type VoiceAttachmentLike = {
|
|
19
28
|
contentType?: string | null
|
|
20
29
|
name?: string | null
|
|
21
30
|
duration?: number | null
|
|
22
31
|
waveform?: string | null
|
|
32
|
+
width?: number | null
|
|
33
|
+
height?: number | null
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
function isVisualMediaAttachment(attachment: VoiceAttachmentLike): boolean {
|
|
37
|
+
const contentType = attachment.contentType?.trim().toLowerCase() || ''
|
|
38
|
+
if (contentType.startsWith('video/')) return true
|
|
39
|
+
|
|
40
|
+
const extension = path.extname(attachment.name || '').toLowerCase()
|
|
41
|
+
if (VIDEO_ATTACHMENT_EXTENSIONS.has(extension)) return true
|
|
42
|
+
|
|
43
|
+
return (
|
|
44
|
+
typeof attachment.width === 'number' &&
|
|
45
|
+
attachment.width > 0 &&
|
|
46
|
+
typeof attachment.height === 'number' &&
|
|
47
|
+
attachment.height > 0
|
|
48
|
+
)
|
|
23
49
|
}
|
|
24
50
|
|
|
25
51
|
export function getVoiceAttachmentMatchReason(
|
|
26
52
|
attachment: VoiceAttachmentLike,
|
|
27
53
|
): string | null {
|
|
54
|
+
if (isVisualMediaAttachment(attachment)) return null
|
|
55
|
+
|
|
28
56
|
const contentType = attachment.contentType?.trim().toLowerCase() || ''
|
|
29
57
|
if (contentType.startsWith('audio/')) {
|
|
30
58
|
return `contentType:${contentType}`
|