kimaki 0.30.1 → 1.0.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-ui.d.ts +33 -0
- package/dist/agent-ui.e2e.test.d.ts +1 -0
- package/dist/agent-ui.e2e.test.js +140 -0
- package/dist/agent-ui.e2e.test.js.map +1 -0
- package/dist/agent-ui.js +199 -0
- package/dist/agent-ui.js.map +1 -0
- package/dist/analytics.d.ts +42 -0
- package/dist/analytics.e2e.test.d.ts +1 -0
- package/dist/analytics.e2e.test.js +112 -0
- package/dist/analytics.e2e.test.js.map +1 -0
- package/dist/analytics.js +162 -221
- package/dist/analytics.js.map +1 -0
- package/dist/analytics.test.d.ts +1 -0
- package/dist/analytics.test.js +121 -157
- package/dist/analytics.test.js.map +1 -0
- package/dist/autostart.d.ts +7 -0
- package/dist/autostart.js +75 -0
- package/dist/autostart.js.map +1 -0
- package/dist/bot.d.ts +90 -0
- package/dist/bot.js +120 -0
- package/dist/bot.js.map +1 -0
- package/dist/btw.e2e.test.d.ts +1 -0
- package/dist/btw.e2e.test.js +102 -0
- package/dist/btw.e2e.test.js.map +1 -0
- package/dist/catalog-refresh.e2e.test.d.ts +1 -0
- package/dist/catalog-refresh.e2e.test.js +90 -0
- package/dist/catalog-refresh.e2e.test.js.map +1 -0
- package/dist/cli/bot.d.ts +5 -0
- package/dist/cli/bot.js +427 -0
- package/dist/cli/bot.js.map +1 -0
- package/dist/cli/discord.d.ts +3 -0
- package/dist/cli/discord.js +118 -0
- package/dist/cli/discord.js.map +1 -0
- package/dist/cli/project.d.ts +5 -0
- package/dist/cli/project.js +143 -0
- package/dist/cli/project.js.map +1 -0
- package/dist/cli/schedule.d.ts +3 -0
- package/dist/cli/schedule.js +156 -0
- package/dist/cli/schedule.js.map +1 -0
- package/dist/cli/session.d.ts +4 -0
- package/dist/cli/session.js +281 -0
- package/dist/cli/session.js.map +1 -0
- package/dist/cli/shared.d.ts +55 -0
- package/dist/cli/shared.js +138 -0
- package/dist/cli/shared.js.map +1 -0
- package/dist/cli-discord.e2e.test.d.ts +1 -0
- package/dist/cli-discord.e2e.test.js +64 -0
- package/dist/cli-discord.e2e.test.js.map +1 -0
- package/dist/cli-lock.e2e.test.d.ts +1 -0
- package/dist/cli-lock.e2e.test.js +520 -0
- package/dist/cli-lock.e2e.test.js.map +1 -0
- package/dist/cli-read.e2e.test.d.ts +1 -0
- package/dist/cli-read.e2e.test.js +81 -0
- package/dist/cli-read.e2e.test.js.map +1 -0
- package/dist/cli-sessions.e2e.test.d.ts +1 -0
- package/dist/cli-sessions.e2e.test.js +116 -0
- package/dist/cli-sessions.e2e.test.js.map +1 -0
- package/dist/cli.d.ts +2 -0
- package/dist/cli.js +34 -263
- package/dist/cli.js.map +1 -0
- package/dist/commands/login-commands.d.ts +59 -0
- package/dist/commands/login-commands.js +341 -0
- package/dist/commands/login-commands.js.map +1 -0
- package/dist/commands/preference-commands.d.ts +44 -0
- package/dist/commands/preference-commands.js +551 -0
- package/dist/commands/preference-commands.js.map +1 -0
- package/dist/commands/session-commands.d.ts +8 -0
- package/dist/commands/session-commands.js +267 -0
- package/dist/commands/session-commands.js.map +1 -0
- package/dist/commands/thread-commands.d.ts +16 -0
- package/dist/commands/thread-commands.js +285 -0
- package/dist/commands/thread-commands.js.map +1 -0
- package/dist/commands/worktree-commands.d.ts +41 -0
- package/dist/commands/worktree-commands.js +279 -0
- package/dist/commands/worktree-commands.js.map +1 -0
- package/dist/commands.e2e.test.d.ts +1 -0
- package/dist/commands.e2e.test.js +255 -0
- package/dist/commands.e2e.test.js.map +1 -0
- package/dist/credentials.d.ts +84 -0
- package/dist/credentials.js +320 -0
- package/dist/credentials.js.map +1 -0
- package/dist/db.d.ts +1532 -0
- package/dist/db.js +57 -245
- package/dist/db.js.map +1 -0
- package/dist/db.test.d.ts +1 -0
- package/dist/db.test.js +352 -673
- package/dist/db.test.js.map +1 -0
- package/dist/duration.d.ts +2 -0
- package/dist/duration.js +18 -0
- package/dist/duration.js.map +1 -0
- package/dist/effects.d.ts +15 -0
- package/dist/effects.js +291 -0
- package/dist/effects.js.map +1 -0
- package/dist/errors.d.ts +38 -0
- package/dist/errors.js +40 -170
- package/dist/errors.js.map +1 -0
- package/dist/event-loop.d.ts +40 -0
- package/dist/event-loop.js +414 -0
- package/dist/event-loop.js.map +1 -0
- package/dist/file-edit-log.d.ts +41 -0
- package/dist/file-edit-log.js +122 -164
- package/dist/file-edit-log.js.map +1 -0
- package/dist/file-edit-log.test.d.ts +1 -0
- package/dist/file-edit-log.test.js +68 -244
- package/dist/file-edit-log.test.js.map +1 -0
- package/dist/format-parts.d.ts +137 -0
- package/dist/format-parts.js +300 -0
- package/dist/format-parts.js.map +1 -0
- package/dist/format-parts.test.d.ts +1 -0
- package/dist/format-parts.test.js +144 -0
- package/dist/format-parts.test.js.map +1 -0
- package/dist/gateway-replay.e2e.test.d.ts +1 -0
- package/dist/gateway-replay.e2e.test.js +95 -0
- package/dist/gateway-replay.e2e.test.js.map +1 -0
- package/dist/harness.e2e.test.d.ts +1 -0
- package/dist/harness.e2e.test.js +107 -0
- package/dist/harness.e2e.test.js.map +1 -0
- package/dist/hello-thread.e2e.test.d.ts +1 -0
- package/dist/hello-thread.e2e.test.js +194 -0
- package/dist/hello-thread.e2e.test.js.map +1 -0
- package/dist/ingress.d.ts +7 -0
- package/dist/ingress.js +337 -0
- package/dist/ingress.js.map +1 -0
- package/dist/interaction-context.d.ts +45 -0
- package/dist/interaction-context.js +77 -0
- package/dist/interaction-context.js.map +1 -0
- package/dist/interrupt.e2e.test.d.ts +1 -0
- package/dist/interrupt.e2e.test.js +124 -0
- package/dist/interrupt.e2e.test.js.map +1 -0
- package/dist/legacy.e2e.test.d.ts +1 -0
- package/dist/legacy.e2e.test.js +85 -0
- package/dist/legacy.e2e.test.js.map +1 -0
- package/dist/live-call.d.ts +51 -0
- package/dist/live-call.js +207 -0
- package/dist/live-call.js.map +1 -0
- package/dist/lock-routes.d.ts +812 -0
- package/dist/lock-routes.js +635 -0
- package/dist/lock-routes.js.map +1 -0
- package/dist/lock-server.d.ts +36 -0
- package/dist/lock-server.js +223 -0
- package/dist/lock-server.js.map +1 -0
- package/dist/logger.d.ts +13 -0
- package/dist/logger.js +112 -162
- package/dist/logger.js.map +1 -0
- package/dist/login.e2e.test.d.ts +1 -0
- package/dist/login.e2e.test.js +83 -0
- package/dist/login.e2e.test.js.map +1 -0
- package/dist/main.d.ts +32 -0
- package/dist/main.js +190 -0
- package/dist/main.js.map +1 -0
- package/dist/markdown/components.d.ts +10 -0
- package/dist/markdown/components.js +77 -0
- package/dist/markdown/components.js.map +1 -0
- package/dist/markdown/markdown.test.d.ts +1 -0
- package/dist/markdown/markdown.test.js +393 -0
- package/dist/markdown/markdown.test.js.map +1 -0
- package/dist/markdown/render-markdown.d.ts +30 -0
- package/dist/markdown/render-markdown.js +265 -0
- package/dist/markdown/render-markdown.js.map +1 -0
- package/dist/message-tool.e2e.test.d.ts +1 -0
- package/dist/message-tool.e2e.test.js +39 -0
- package/dist/message-tool.e2e.test.js.map +1 -0
- package/dist/migrations.d.ts +8 -0
- package/dist/migrations.js +187 -0
- package/dist/migrations.js.map +1 -0
- package/dist/onboarding-cli.e2e.test.d.ts +1 -0
- package/dist/onboarding-cli.e2e.test.js +178 -0
- package/dist/onboarding-cli.e2e.test.js.map +1 -0
- package/dist/onboarding.d.ts +36 -0
- package/dist/onboarding.e2e.test.d.ts +1 -0
- package/dist/onboarding.e2e.test.js +148 -0
- package/dist/onboarding.e2e.test.js.map +1 -0
- package/dist/onboarding.js +261 -0
- package/dist/onboarding.js.map +1 -0
- package/dist/opencode-server.d.ts +54 -0
- package/dist/opencode-server.js +413 -0
- package/dist/opencode-server.js.map +1 -0
- package/dist/opencode-server.test.d.ts +1 -0
- package/dist/opencode-server.test.js +21 -0
- package/dist/opencode-server.test.js.map +1 -0
- package/dist/permissions.d.ts +31 -0
- package/dist/permissions.e2e.test.d.ts +1 -0
- package/dist/permissions.e2e.test.js +166 -0
- package/dist/permissions.e2e.test.js.map +1 -0
- package/dist/permissions.js +95 -0
- package/dist/permissions.js.map +1 -0
- package/dist/plugin/anthropic/index.d.ts +13 -0
- package/dist/plugin/anthropic/index.js +201 -0
- package/dist/plugin/anthropic/index.js.map +1 -0
- package/dist/plugin/anthropic/index.test.d.ts +1 -0
- package/dist/plugin/anthropic/index.test.js +39 -0
- package/dist/plugin/anthropic/index.test.js.map +1 -0
- package/dist/plugin/index.d.ts +3 -0
- package/dist/plugin/index.js +106 -0
- package/dist/plugin/index.js.map +1 -0
- package/dist/plugin/session-id/index.d.ts +3 -0
- package/dist/plugin/session-id/index.js +18 -0
- package/dist/plugin/session-id/index.js.map +1 -0
- package/dist/plugin.e2e.test.d.ts +1 -0
- package/dist/plugin.e2e.test.js +87 -0
- package/dist/plugin.e2e.test.js.map +1 -0
- package/dist/preference-commands.e2e.test.d.ts +1 -0
- package/dist/preference-commands.e2e.test.js +555 -0
- package/dist/preference-commands.e2e.test.js.map +1 -0
- package/dist/probe-live.tmp.d.ts +1 -0
- package/dist/probe-live.tmp.js +82 -0
- package/dist/probe-live.tmp.js.map +1 -0
- package/dist/project.d.ts +58 -0
- package/dist/project.js +169 -0
- package/dist/project.js.map +1 -0
- package/dist/prompt.d.ts +112 -0
- package/dist/prompt.js +578 -0
- package/dist/prompt.js.map +1 -0
- package/dist/questions.d.ts +59 -0
- package/dist/questions.e2e.test.d.ts +1 -0
- package/dist/questions.e2e.test.js +290 -0
- package/dist/questions.e2e.test.js.map +1 -0
- package/dist/questions.js +231 -0
- package/dist/questions.js.map +1 -0
- package/dist/queue.d.ts +45 -0
- package/dist/queue.e2e.test.d.ts +1 -0
- package/dist/queue.e2e.test.js +220 -0
- package/dist/queue.e2e.test.js.map +1 -0
- package/dist/queue.js +132 -0
- package/dist/queue.js.map +1 -0
- package/dist/remote-send.d.ts +32 -0
- package/dist/remote-send.e2e.test.d.ts +1 -0
- package/dist/remote-send.e2e.test.js +89 -0
- package/dist/remote-send.e2e.test.js.map +1 -0
- package/dist/remote-send.js +105 -0
- package/dist/remote-send.js.map +1 -0
- package/dist/renderer.e2e.test.d.ts +1 -0
- package/dist/renderer.e2e.test.js +357 -0
- package/dist/renderer.e2e.test.js.map +1 -0
- package/dist/restart.e2e.test.d.ts +1 -0
- package/dist/restart.e2e.test.js +68 -0
- package/dist/restart.e2e.test.js.map +1 -0
- package/dist/routes.d.ts +24 -0
- package/dist/routes.js +42 -0
- package/dist/routes.js.map +1 -0
- package/dist/routes.test.d.ts +1 -0
- package/dist/routes.test.js +79 -0
- package/dist/routes.test.js.map +1 -0
- package/dist/routines.e2e.test.d.ts +1 -0
- package/dist/routines.e2e.test.js +188 -0
- package/dist/routines.e2e.test.js.map +1 -0
- package/dist/scheduler.d.ts +116 -0
- package/dist/scheduler.e2e.test.d.ts +1 -0
- package/dist/scheduler.e2e.test.js +163 -0
- package/dist/scheduler.e2e.test.js.map +1 -0
- package/dist/scheduler.js +602 -0
- package/dist/scheduler.js.map +1 -0
- package/dist/scheduler.test.d.ts +1 -0
- package/dist/scheduler.test.js +180 -0
- package/dist/scheduler.test.js.map +1 -0
- package/dist/schema-sql.d.ts +1 -0
- package/dist/schema-sql.js +3 -0
- package/dist/schema-sql.js.map +1 -0
- package/dist/schema.d.ts +2968 -0
- package/dist/schema.js +95 -252
- package/dist/schema.js.map +1 -0
- package/dist/session-commands.e2e.test.d.ts +1 -0
- package/dist/session-commands.e2e.test.js +285 -0
- package/dist/session-commands.e2e.test.js.map +1 -0
- package/dist/session-events.d.ts +55 -0
- package/dist/session-events.js +190 -0
- package/dist/session-events.js.map +1 -0
- package/dist/session-ops.e2e.test.d.ts +1 -0
- package/dist/session-ops.e2e.test.js +114 -0
- package/dist/session-ops.e2e.test.js.map +1 -0
- package/dist/sessions.d.ts +121 -0
- package/dist/sessions.js +662 -0
- package/dist/sessions.js.map +1 -0
- package/dist/shell.e2e.test.d.ts +1 -0
- package/dist/shell.e2e.test.js +147 -0
- package/dist/shell.e2e.test.js.map +1 -0
- package/dist/slash-commands.d.ts +62 -0
- package/dist/slash-commands.js +475 -0
- package/dist/slash-commands.js.map +1 -0
- package/dist/slash-commands.test.d.ts +1 -0
- package/dist/slash-commands.test.js +87 -0
- package/dist/slash-commands.test.js.map +1 -0
- package/dist/sleep.e2e.test.d.ts +1 -0
- package/dist/sleep.e2e.test.js +115 -487
- package/dist/sleep.e2e.test.js.map +1 -0
- package/dist/sleeps.d.ts +37 -0
- package/dist/sleeps.js +220 -0
- package/dist/sleeps.js.map +1 -0
- package/dist/store.d.ts +10 -0
- package/dist/store.js +8 -28
- package/dist/store.js.map +1 -0
- package/dist/system-prompt.d.ts +65 -0
- package/dist/system-prompt.js +875 -0
- package/dist/system-prompt.js.map +1 -0
- package/dist/tasks.e2e.test.d.ts +1 -0
- package/dist/tasks.e2e.test.js +173 -0
- package/dist/tasks.e2e.test.js.map +1 -0
- package/dist/test/auth-plugin/index.d.ts +3 -0
- package/dist/test/auth-plugin/index.js +18 -0
- package/dist/test/auth-plugin/index.js.map +1 -0
- package/dist/test/fake-mcp-server.d.ts +1 -0
- package/dist/test/fake-mcp-server.js +45 -0
- package/dist/test/fake-mcp-server.js.map +1 -0
- package/dist/test/harness.d.ts +246 -0
- package/dist/test/harness.js +704 -0
- package/dist/test/harness.js.map +1 -0
- package/dist/test/replay.d.ts +14 -0
- package/dist/test/replay.js +75 -0
- package/dist/test/replay.js.map +1 -0
- package/dist/thread-reducer.d.ts +172 -0
- package/dist/thread-reducer.fixtures.test.d.ts +1 -0
- package/dist/thread-reducer.fixtures.test.js +360 -0
- package/dist/thread-reducer.fixtures.test.js.map +1 -0
- package/dist/thread-reducer.js +497 -0
- package/dist/thread-reducer.js.map +1 -0
- package/dist/thread-reducer.test.d.ts +1 -0
- package/dist/thread-reducer.test.js +369 -0
- package/dist/thread-reducer.test.js.map +1 -0
- package/dist/tts.test.d.ts +1 -0
- package/dist/tts.test.js +147 -0
- package/dist/tts.test.js.map +1 -0
- package/dist/voice-agent.d.ts +33 -0
- package/dist/voice-agent.js +159 -0
- package/dist/voice-agent.js.map +1 -0
- package/dist/voice-attachments.e2e.test.d.ts +1 -0
- package/dist/voice-attachments.e2e.test.js +253 -0
- package/dist/voice-attachments.e2e.test.js.map +1 -0
- package/dist/voice-call.d.ts +59 -0
- package/dist/voice-call.e2e.test.d.ts +1 -0
- package/dist/voice-call.e2e.test.js +259 -0
- package/dist/voice-call.e2e.test.js.map +1 -0
- package/dist/voice-call.js +760 -0
- package/dist/voice-call.js.map +1 -0
- package/dist/voice-openai.e2e.test.d.ts +1 -0
- package/dist/voice-openai.e2e.test.js +161 -0
- package/dist/voice-openai.e2e.test.js.map +1 -0
- package/dist/voice.d.ts +116 -0
- package/dist/voice.js +648 -646
- package/dist/voice.js.map +1 -0
- package/dist/working-directory.e2e.test.d.ts +1 -0
- package/dist/working-directory.e2e.test.js +231 -0
- package/dist/working-directory.e2e.test.js.map +1 -0
- package/dist/worktrees.d.ts +54 -0
- package/dist/worktrees.js +247 -709
- package/dist/worktrees.js.map +1 -0
- package/dist/worktrees.test.d.ts +1 -0
- package/dist/worktrees.test.js +97 -952
- package/dist/worktrees.test.js.map +1 -0
- package/package.json +40 -79
- package/src/agent-ui.e2e.test.ts +134 -0
- package/src/agent-ui.ts +216 -0
- package/src/analytics.e2e.test.ts +138 -0
- package/src/analytics.test.ts +122 -182
- package/src/analytics.ts +206 -256
- package/src/autostart.ts +78 -0
- package/src/bot.ts +194 -0
- package/src/btw.e2e.test.ts +130 -0
- package/src/catalog-refresh.e2e.test.ts +112 -0
- package/src/cli/bot.ts +419 -0
- package/src/cli/discord.ts +116 -0
- package/src/cli/project.ts +147 -0
- package/src/cli/schedule.ts +151 -0
- package/src/cli/session.ts +285 -0
- package/src/cli/shared.ts +139 -0
- package/src/cli-discord.e2e.test.ts +62 -0
- package/src/cli-lock.e2e.test.ts +530 -0
- package/src/cli-read.e2e.test.ts +78 -0
- package/src/cli-sessions.e2e.test.ts +122 -0
- package/src/cli.ts +35 -418
- package/src/commands/login-commands.ts +360 -0
- package/src/commands/preference-commands.ts +586 -0
- package/src/commands/session-commands.ts +289 -0
- package/src/commands/thread-commands.ts +324 -0
- package/src/commands/worktree-commands.ts +288 -0
- package/src/commands.e2e.test.ts +290 -0
- package/src/credentials.ts +425 -0
- package/src/db.test.ts +345 -748
- package/src/db.ts +66 -284
- package/src/duration.ts +18 -0
- package/src/effects.ts +303 -0
- package/src/errors.ts +39 -222
- package/src/event-loop.ts +462 -0
- package/src/file-edit-log.test.ts +68 -272
- package/src/file-edit-log.ts +108 -231
- package/src/fixtures/v1-schema.sql +241 -0
- package/src/fixtures/voice.ogg +0 -0
- package/src/format-parts.test.ts +150 -0
- package/src/format-parts.ts +405 -0
- package/src/gateway-replay.e2e.test.ts +116 -0
- package/src/harness.e2e.test.ts +124 -0
- package/src/hello-thread.e2e.test.ts +223 -0
- package/src/ingress.ts +334 -0
- package/src/interaction-context.ts +131 -0
- package/src/interrupt.e2e.test.ts +151 -0
- package/src/legacy.e2e.test.ts +105 -0
- package/src/live-call.ts +249 -0
- package/src/lock-routes.ts +625 -0
- package/src/lock-server.ts +233 -0
- package/src/logger.ts +120 -207
- package/src/login.e2e.test.ts +86 -0
- package/src/main.ts +230 -0
- package/src/markdown/components.ts +102 -0
- package/src/markdown/markdown.test.ts +414 -0
- package/src/markdown/render-markdown.ts +288 -0
- package/src/migrations.ts +198 -0
- package/src/onboarding-cli.e2e.test.ts +215 -0
- package/src/onboarding.e2e.test.ts +171 -0
- package/src/onboarding.ts +297 -0
- package/src/opencode-server.test.ts +24 -0
- package/src/opencode-server.ts +461 -0
- package/src/permissions.e2e.test.ts +191 -0
- package/src/permissions.ts +121 -0
- package/src/plugin/anthropic/index.test.ts +39 -0
- package/src/plugin/anthropic/index.ts +213 -0
- package/src/plugin/index.ts +93 -0
- package/src/plugin/session-id/index.ts +19 -0
- package/src/plugin.e2e.test.ts +112 -0
- package/src/preference-commands.e2e.test.ts +597 -0
- package/src/project.ts +216 -0
- package/src/prompt.ts +664 -0
- package/src/questions.e2e.test.ts +328 -0
- package/src/questions.ts +322 -0
- package/src/queue.e2e.test.ts +253 -0
- package/src/queue.ts +158 -0
- package/src/remote-send.e2e.test.ts +91 -0
- package/src/remote-send.ts +110 -0
- package/src/renderer.e2e.test.ts +390 -0
- package/src/restart.e2e.test.ts +90 -0
- package/src/routes.test.ts +81 -0
- package/src/routes.ts +48 -0
- package/src/routines.e2e.test.ts +210 -0
- package/src/scheduler.e2e.test.ts +182 -0
- package/src/scheduler.test.ts +183 -0
- package/src/scheduler.ts +648 -0
- package/src/schema-sql.ts +3 -0
- package/src/schema.ts +95 -271
- package/src/session-commands.e2e.test.ts +326 -0
- package/src/session-events.ts +218 -0
- package/src/session-ops.e2e.test.ts +141 -0
- package/src/sessions.ts +803 -0
- package/src/shell.e2e.test.ts +173 -0
- package/src/slash-commands.test.ts +89 -0
- package/src/slash-commands.ts +539 -0
- package/src/sleep.e2e.test.ts +135 -552
- package/src/sleeps.ts +230 -0
- package/src/store.ts +19 -217
- package/src/system-prompt.ts +944 -0
- package/src/test/auth-plugin/index.ts +17 -0
- package/src/test/fake-mcp-server.ts +49 -0
- package/src/test/harness.ts +957 -0
- package/src/test/replay.ts +90 -0
- package/src/thread-reducer.fixtures.test.ts +374 -0
- package/src/thread-reducer.test.ts +380 -0
- package/src/thread-reducer.ts +671 -0
- package/src/tts.test.ts +150 -0
- package/src/voice-agent.ts +196 -0
- package/src/voice-attachments.e2e.test.ts +298 -0
- package/src/voice-call.e2e.test.ts +293 -0
- package/src/voice-call.ts +764 -0
- package/src/voice-openai.e2e.test.ts +191 -0
- package/src/voice.ts +691 -890
- package/src/working-directory.e2e.test.ts +226 -0
- package/src/worktrees.test.ts +89 -1115
- package/src/worktrees.ts +195 -957
- package/bin.js +0 -2
- package/dist/agent-model.e2e.test.js +0 -1215
- package/dist/ai-tool-to-genai.js +0 -233
- package/dist/ai-tool-to-genai.test.js +0 -267
- package/dist/ai-tool.js +0 -6
- package/dist/anthropic-account-identity.js +0 -70
- package/dist/anthropic-account-identity.test.js +0 -38
- package/dist/anthropic-auth-plugin.js +0 -958
- package/dist/anthropic-auth-plugin.test.js +0 -115
- package/dist/anthropic-auth-state.js +0 -186
- package/dist/anthropic-auth-state.test.js +0 -203
- package/dist/bash-tool-schema-plugin.js +0 -50
- package/dist/bash-tool-schema-plugin.test.js +0 -68
- package/dist/bin.js +0 -151
- package/dist/bot-lifecycle.test.js +0 -206
- package/dist/btw-prefix-detection.js +0 -15
- package/dist/btw-prefix-detection.test.js +0 -93
- package/dist/cache-drift-plugin.js +0 -183
- package/dist/cache-rewrite.js +0 -39
- package/dist/cache-rewrite.test.js +0 -41
- package/dist/channel-management.js +0 -347
- package/dist/cli-commands/bot.js +0 -254
- package/dist/cli-commands/maintenance.js +0 -171
- package/dist/cli-commands/misc.js +0 -153
- package/dist/cli-commands/project.js +0 -510
- package/dist/cli-commands/send.js +0 -659
- package/dist/cli-commands/session.js +0 -889
- package/dist/cli-commands/task.js +0 -277
- package/dist/cli-commands/thread.js +0 -63
- package/dist/cli-commands/user.js +0 -145
- package/dist/cli-parsing.test.js +0 -226
- package/dist/cli-runner.js +0 -1579
- package/dist/cli-runner.test.js +0 -86
- package/dist/cli-send-thread.e2e.test.js +0 -470
- package/dist/cloudflare-transcription.js +0 -56
- package/dist/commands/abort.js +0 -84
- package/dist/commands/action-buttons.js +0 -248
- package/dist/commands/add-project.js +0 -111
- package/dist/commands/agent.js +0 -658
- package/dist/commands/ask-question.js +0 -371
- package/dist/commands/ask-question.test.js +0 -150
- package/dist/commands/btw.js +0 -167
- package/dist/commands/compact.js +0 -117
- package/dist/commands/context-usage.js +0 -147
- package/dist/commands/create-new-project.js +0 -138
- package/dist/commands/diff.js +0 -63
- package/dist/commands/file-upload.js +0 -280
- package/dist/commands/fork-subagent.js +0 -175
- package/dist/commands/fork.js +0 -260
- package/dist/commands/gemini-apikey.js +0 -123
- package/dist/commands/last-sessions.js +0 -129
- package/dist/commands/login.js +0 -893
- package/dist/commands/mcp.js +0 -239
- package/dist/commands/memory-snapshot.js +0 -24
- package/dist/commands/mention-mode.js +0 -42
- package/dist/commands/merge-worktree.js +0 -205
- package/dist/commands/model-variant.js +0 -390
- package/dist/commands/model.js +0 -957
- package/dist/commands/multioauth.js +0 -425
- package/dist/commands/new-worktree.js +0 -622
- package/dist/commands/paginated-select.js +0 -57
- package/dist/commands/permissions.js +0 -327
- package/dist/commands/queue.js +0 -340
- package/dist/commands/queue.test.js +0 -15
- package/dist/commands/remove-project.js +0 -111
- package/dist/commands/restart-opencode-server.js +0 -133
- package/dist/commands/resume.js +0 -148
- package/dist/commands/run-command.js +0 -79
- package/dist/commands/screenshare.js +0 -307
- package/dist/commands/screenshare.test.js +0 -20
- package/dist/commands/session-id.js +0 -78
- package/dist/commands/session.js +0 -243
- package/dist/commands/share.js +0 -80
- package/dist/commands/tasks.js +0 -296
- package/dist/commands/types.js +0 -2
- package/dist/commands/undo-redo.js +0 -313
- package/dist/commands/unset-model.js +0 -116
- package/dist/commands/upgrade.js +0 -41
- package/dist/commands/user-command.js +0 -196
- package/dist/commands/verbosity.js +0 -124
- package/dist/commands/vscode.js +0 -295
- package/dist/commands/worktrees.js +0 -548
- package/dist/condense-memory.js +0 -33
- package/dist/config.js +0 -126
- package/dist/context-awareness-plugin.js +0 -357
- package/dist/context-awareness-plugin.test.js +0 -126
- package/dist/cpu-profiler.js +0 -211
- package/dist/cpu-profiler.test.js +0 -160
- package/dist/critique-utils.js +0 -95
- package/dist/database.js +0 -1074
- package/dist/debounce-timeout.js +0 -28
- package/dist/debounced-process-flush.js +0 -77
- package/dist/default-channel-provisioning.test.js +0 -149
- package/dist/discord-bot.js +0 -1409
- package/dist/discord-command-registration.js +0 -519
- package/dist/discord-command-registration.test.js +0 -17
- package/dist/discord-urls.js +0 -81
- package/dist/discord-utils.js +0 -789
- package/dist/discord-utils.test.js +0 -400
- package/dist/escape-backticks.test.js +0 -429
- package/dist/event-stream-real-capture.e2e.test.js +0 -533
- package/dist/eventsource-parser.test.js +0 -327
- package/dist/exec-async.js +0 -38
- package/dist/external-directory-permissions.e2e.test.js +0 -86
- package/dist/external-directory-project-deny.e2e.test.js +0 -70
- package/dist/external-opencode-sync.js +0 -620
- package/dist/external-opencode-sync.test.js +0 -206
- package/dist/extract-sdk-error-message.test.js +0 -13
- package/dist/format-tables.js +0 -728
- package/dist/format-tables.test.js +0 -915
- package/dist/forum-sync/config.js +0 -79
- package/dist/forum-sync/discord-operations.js +0 -157
- package/dist/forum-sync/index.js +0 -5
- package/dist/forum-sync/markdown.js +0 -110
- package/dist/forum-sync/sync-to-discord.js +0 -417
- package/dist/forum-sync/sync-to-files.js +0 -190
- package/dist/forum-sync/types.js +0 -53
- package/dist/forum-sync/watchers.js +0 -322
- package/dist/gateway-proxy-reconnect.e2e.test.js +0 -414
- package/dist/gateway-proxy.e2e.test.js +0 -488
- package/dist/genai-worker-wrapper.js +0 -111
- package/dist/genai-worker.js +0 -309
- package/dist/genai.js +0 -232
- package/dist/git-worktree-core.js +0 -461
- package/dist/guild-categories.test.js +0 -225
- package/dist/heap-monitor.js +0 -122
- package/dist/hrana-server.js +0 -322
- package/dist/hrana-server.test.js +0 -99
- package/dist/html-actions.js +0 -123
- package/dist/html-actions.test.js +0 -70
- package/dist/html-components.js +0 -115
- package/dist/html-components.test.js +0 -34
- package/dist/image-optimizer-plugin.js +0 -153
- package/dist/image-utils.js +0 -112
- package/dist/interaction-handler.js +0 -578
- package/dist/ipc-polling.js +0 -245
- package/dist/ipc-tools-plugin.js +0 -251
- package/dist/kimaki-digital-twin.e2e.test.js +0 -161
- package/dist/kimaki-opencode-plugin-loading.e2e.test.js +0 -158
- package/dist/kimaki-opencode-plugin.js +0 -33
- package/dist/kimaki-opencode-plugin.test.js +0 -126
- package/dist/kimaki-workspace-adaptor.js +0 -118
- package/dist/limit-heading-depth.js +0 -25
- package/dist/limit-heading-depth.test.js +0 -105
- package/dist/list-channel-threads.js +0 -163
- package/dist/list-channel-threads.test.js +0 -80
- package/dist/markdown.js +0 -455
- package/dist/markdown.test.js +0 -411
- package/dist/memory-overview-plugin.js +0 -123
- package/dist/message-formatting.js +0 -763
- package/dist/message-formatting.test.js +0 -1132
- package/dist/message-preprocessing.js +0 -441
- package/dist/oauth-rotation-shared.js +0 -253
- package/dist/onboarding-tutorial.js +0 -163
- package/dist/onboarding-welcome.js +0 -37
- package/dist/openai-auth-plugin.js +0 -125
- package/dist/openai-auth-state.js +0 -237
- package/dist/openai-realtime.js +0 -224
- package/dist/opencode-command-detection.js +0 -65
- package/dist/opencode-command-detection.test.js +0 -240
- package/dist/opencode-command.js +0 -181
- package/dist/opencode-command.test.js +0 -218
- package/dist/opencode-interrupt-plugin.js +0 -253
- package/dist/opencode-interrupt-plugin.test.js +0 -293
- package/dist/opencode.js +0 -1217
- package/dist/parse-permission-rules.test.js +0 -117
- package/dist/patch-text-parser.js +0 -97
- package/dist/plugin-logger.js +0 -105
- package/dist/plugin-opencode-client.js +0 -54
- package/dist/privacy-sanitizer.js +0 -105
- package/dist/project-add.test.js +0 -62
- package/dist/question-abort-clears-dropdown.e2e.test.js +0 -89
- package/dist/question-abort-resume.e2e.test.js +0 -100
- package/dist/question-abort-retry-text-before-dropdown.e2e.test.js +0 -150
- package/dist/question-text-before-dropdown.e2e.test.js +0 -52
- package/dist/queue-advanced-abort.e2e.test.js +0 -348
- package/dist/queue-advanced-action-buttons.e2e.test.js +0 -209
- package/dist/queue-advanced-e2e-setup.js +0 -1043
- package/dist/queue-advanced-footer.e2e.test.js +0 -286
- package/dist/queue-advanced-model-switch.e2e.test.js +0 -297
- package/dist/queue-advanced-permissions-typing.e2e.test.js +0 -189
- package/dist/queue-advanced-question.e2e.test.js +0 -266
- package/dist/queue-advanced-typing-interrupt.e2e.test.js +0 -113
- package/dist/queue-advanced-typing.e2e.test.js +0 -151
- package/dist/queue-delete-message.e2e.test.js +0 -75
- package/dist/queue-drain-after-interactive-ui.e2e.test.js +0 -120
- package/dist/queue-interrupt-drain.e2e.test.js +0 -246
- package/dist/queue-question-select-drain.e2e.test.js +0 -162
- package/dist/runtime-idle-sweeper.js +0 -52
- package/dist/runtime-lifecycle.e2e.test.js +0 -570
- package/dist/sentry.js +0 -23
- package/dist/session-handler/agent-utils.js +0 -66
- package/dist/session-handler/event-stream-state.js +0 -1217
- package/dist/session-handler/event-stream-state.test.js +0 -3034
- package/dist/session-handler/global-event-listener.js +0 -212
- package/dist/session-handler/model-utils.js +0 -311
- package/dist/session-handler/model-utils.test.js +0 -445
- package/dist/session-handler/opencode-session-event-log.js +0 -121
- package/dist/session-handler/thread-ingress.test.js +0 -41
- package/dist/session-handler/thread-runtime-state.js +0 -209
- package/dist/session-handler/thread-session-runtime-summary.test.js +0 -121
- package/dist/session-handler/thread-session-runtime.js +0 -4252
- package/dist/session-handler.js +0 -9
- package/dist/session-search.js +0 -219
- package/dist/session-search.test.js +0 -287
- package/dist/session-sleep.test.js +0 -224
- package/dist/session-title-rename.test.js +0 -128
- package/dist/skill-filter.js +0 -43
- package/dist/skill-filter.test.js +0 -86
- package/dist/startup-service.js +0 -153
- package/dist/startup-time.e2e.test.js +0 -297
- package/dist/system-message.js +0 -984
- package/dist/system-message.test.js +0 -1093
- package/dist/task-id-plugin.js +0 -16
- package/dist/task-runner.js +0 -554
- package/dist/task-schedule.js +0 -298
- package/dist/task-schedule.test.js +0 -366
- package/dist/test-utils.js +0 -359
- package/dist/thinking-utils.js +0 -45
- package/dist/thinking-utils.test.js +0 -35
- package/dist/thread-message-queue.e2e.test.js +0 -1155
- package/dist/tools.js +0 -357
- package/dist/undo-redo.e2e.test.js +0 -171
- package/dist/unnest-code-blocks.js +0 -146
- package/dist/unnest-code-blocks.test.js +0 -673
- package/dist/upgrade.js +0 -114
- package/dist/utils.js +0 -148
- package/dist/voice-attachment.js +0 -56
- package/dist/voice-handler.js +0 -726
- package/dist/voice-message.e2e.test.js +0 -1263
- package/dist/voice-routing-input.e2e.test.js +0 -195
- package/dist/voice-routing-question.e2e.test.js +0 -125
- package/dist/voice.test.js +0 -352
- package/dist/wait-session.js +0 -193
- package/dist/websockify.js +0 -69
- package/dist/worker-types.js +0 -4
- package/dist/worktree-clone-isolation.e2e.test.js +0 -188
- package/dist/worktree-lifecycle.e2e.test.js +0 -735
- package/dist/worktree-utils.js +0 -3
- package/dist/xai-auth-plugin.js +0 -238
- package/dist/xai-auth-state.js +0 -234
- package/dist/xai-auth-state.test.js +0 -183
- package/dist/xml.js +0 -92
- package/dist/xml.test.js +0 -32
- package/skills/bump-holocron-consumers/SKILL.md +0 -137
- package/skills/critique/SKILL.md +0 -112
- package/skills/egaki/SKILL.md +0 -86
- package/skills/errore/SKILL.md +0 -501
- package/skills/goke/SKILL.md +0 -695
- package/skills/holocron/SKILL.md +0 -942
- package/skills/lintcn/SKILL.md +0 -873
- package/skills/new-skill/SKILL.md +0 -339
- package/skills/npm-package/SKILL.md +0 -692
- package/skills/opensrc/SKILL.md +0 -95
- package/skills/playwriter/SKILL.md +0 -35
- package/skills/profano/SKILL.md +0 -33
- package/skills/readme.md +0 -20
- package/skills/sigillo/SKILL.md +0 -370
- package/skills/spiceflow/SKILL.md +0 -80
- package/skills/strada/SKILL.md +0 -104
- package/skills/termcast/SKILL.md +0 -133
- package/skills/tuistory/SKILL.md +0 -237
- package/skills/usecomputer/SKILL.md +0 -264
- package/skills/zele/SKILL.md +0 -81
- package/src/agent-model.e2e.test.ts +0 -1548
- package/src/ai-tool-to-genai.test.ts +0 -296
- package/src/ai-tool-to-genai.ts +0 -283
- package/src/ai-tool.ts +0 -39
- package/src/anthropic-account-identity.test.ts +0 -52
- package/src/anthropic-account-identity.ts +0 -96
- package/src/anthropic-auth-plugin.test.ts +0 -135
- package/src/anthropic-auth-plugin.ts +0 -1180
- package/src/anthropic-auth-state.test.ts +0 -259
- package/src/anthropic-auth-state.ts +0 -272
- package/src/bash-tool-schema-plugin.test.ts +0 -84
- package/src/bash-tool-schema-plugin.ts +0 -77
- package/src/bin.ts +0 -173
- package/src/bot-lifecycle.test.ts +0 -252
- package/src/btw-prefix-detection.test.ts +0 -104
- package/src/btw-prefix-detection.ts +0 -19
- package/src/cache-drift-plugin.ts +0 -249
- package/src/cache-rewrite.test.ts +0 -49
- package/src/cache-rewrite.ts +0 -96
- package/src/channel-management.ts +0 -494
- package/src/cli-commands/bot.ts +0 -374
- package/src/cli-commands/maintenance.ts +0 -237
- package/src/cli-commands/misc.ts +0 -208
- package/src/cli-commands/project.ts +0 -680
- package/src/cli-commands/send.ts +0 -879
- package/src/cli-commands/session.ts +0 -1181
- package/src/cli-commands/task.ts +0 -358
- package/src/cli-commands/thread.ts +0 -86
- package/src/cli-commands/user.ts +0 -195
- package/src/cli-parsing.test.ts +0 -305
- package/src/cli-runner.test.ts +0 -129
- package/src/cli-runner.ts +0 -2141
- package/src/cli-send-thread.e2e.test.ts +0 -609
- package/src/cloudflare-transcription.ts +0 -76
- package/src/commands/abort.ts +0 -113
- package/src/commands/action-buttons.ts +0 -371
- package/src/commands/add-project.ts +0 -146
- package/src/commands/agent.ts +0 -911
- package/src/commands/ask-question.test.ts +0 -177
- package/src/commands/ask-question.ts +0 -532
- package/src/commands/btw.ts +0 -246
- package/src/commands/compact.ts +0 -154
- package/src/commands/context-usage.ts +0 -208
- package/src/commands/create-new-project.ts +0 -203
- package/src/commands/diff.ts +0 -91
- package/src/commands/file-upload.ts +0 -398
- package/src/commands/fork-subagent.ts +0 -262
- package/src/commands/fork.ts +0 -388
- package/src/commands/gemini-apikey.ts +0 -205
- package/src/commands/last-sessions.ts +0 -176
- package/src/commands/login.ts +0 -1182
- package/src/commands/mcp.ts +0 -307
- package/src/commands/memory-snapshot.ts +0 -30
- package/src/commands/mention-mode.ts +0 -66
- package/src/commands/merge-worktree.ts +0 -282
- package/src/commands/model-variant.ts +0 -522
- package/src/commands/model.ts +0 -1310
- package/src/commands/multioauth.ts +0 -508
- package/src/commands/new-worktree.ts +0 -832
- package/src/commands/paginated-select.ts +0 -81
- package/src/commands/permissions.ts +0 -458
- package/src/commands/queue.test.ts +0 -19
- package/src/commands/queue.ts +0 -455
- package/src/commands/remove-project.ts +0 -152
- package/src/commands/restart-opencode-server.ts +0 -173
- package/src/commands/resume.ts +0 -229
- package/src/commands/run-command.ts +0 -123
- package/src/commands/screenshare.test.ts +0 -30
- package/src/commands/screenshare.ts +0 -388
- package/src/commands/session-id.ts +0 -109
- package/src/commands/session.ts +0 -311
- package/src/commands/share.ts +0 -106
- package/src/commands/tasks.ts +0 -399
- package/src/commands/types.ts +0 -25
- package/src/commands/undo-redo.ts +0 -394
- package/src/commands/unset-model.ts +0 -152
- package/src/commands/upgrade.ts +0 -51
- package/src/commands/user-command.ts +0 -260
- package/src/commands/verbosity.ts +0 -172
- package/src/commands/vscode.ts +0 -378
- package/src/commands/worktrees.ts +0 -762
- package/src/condense-memory.ts +0 -36
- package/src/config.ts +0 -147
- package/src/context-awareness-plugin.test.ts +0 -144
- package/src/context-awareness-plugin.ts +0 -508
- package/src/cpu-profiler.test.ts +0 -183
- package/src/cpu-profiler.ts +0 -293
- package/src/critique-utils.ts +0 -139
- package/src/database.ts +0 -1484
- package/src/debounce-timeout.ts +0 -43
- package/src/debounced-process-flush.ts +0 -104
- package/src/default-channel-provisioning.test.ts +0 -156
- package/src/discord-bot.ts +0 -1836
- package/src/discord-command-registration.test.ts +0 -20
- package/src/discord-command-registration.ts +0 -704
- package/src/discord-urls.ts +0 -88
- package/src/discord-utils.test.ts +0 -470
- package/src/discord-utils.ts +0 -1079
- package/src/escape-backticks.test.ts +0 -469
- package/src/event-stream-real-capture.e2e.test.ts +0 -692
- package/src/eventsource-parser.test.ts +0 -351
- package/src/exec-async.ts +0 -44
- package/src/external-directory-permissions.e2e.test.ts +0 -103
- package/src/external-directory-project-deny.e2e.test.ts +0 -84
- package/src/external-opencode-sync.test.ts +0 -248
- package/src/external-opencode-sync.ts +0 -867
- package/src/extract-sdk-error-message.test.ts +0 -18
- package/src/format-tables.test.ts +0 -988
- package/src/format-tables.ts +0 -1031
- package/src/forum-sync/config.ts +0 -92
- package/src/forum-sync/discord-operations.ts +0 -245
- package/src/forum-sync/index.ts +0 -9
- package/src/forum-sync/markdown.ts +0 -172
- package/src/forum-sync/sync-to-discord.ts +0 -595
- package/src/forum-sync/sync-to-files.ts +0 -294
- package/src/forum-sync/types.ts +0 -175
- package/src/forum-sync/watchers.ts +0 -474
- package/src/gateway-proxy-reconnect.e2e.test.ts +0 -553
- package/src/gateway-proxy.e2e.test.ts +0 -648
- package/src/genai-worker-wrapper.ts +0 -164
- package/src/genai-worker.ts +0 -385
- package/src/genai.ts +0 -318
- package/src/git-worktree-core.ts +0 -616
- package/src/guild-categories.test.ts +0 -260
- package/src/heap-monitor.ts +0 -152
- package/src/hrana-server.test.ts +0 -130
- package/src/hrana-server.ts +0 -399
- package/src/html-actions.test.ts +0 -87
- package/src/html-actions.ts +0 -174
- package/src/html-components.test.ts +0 -38
- package/src/html-components.ts +0 -177
- package/src/image-optimizer-plugin.ts +0 -194
- package/src/image-utils.ts +0 -150
- package/src/interaction-handler.ts +0 -775
- package/src/ipc-polling.ts +0 -325
- package/src/ipc-tools-plugin.ts +0 -306
- package/src/kimaki-digital-twin.e2e.test.ts +0 -199
- package/src/kimaki-opencode-plugin-loading.e2e.test.ts +0 -208
- package/src/kimaki-opencode-plugin.test.ts +0 -142
- package/src/kimaki-opencode-plugin.ts +0 -34
- package/src/kimaki-workspace-adaptor.ts +0 -144
- package/src/limit-heading-depth.test.ts +0 -116
- package/src/limit-heading-depth.ts +0 -26
- package/src/list-channel-threads.test.ts +0 -90
- package/src/list-channel-threads.ts +0 -252
- package/src/markdown.test.ts +0 -498
- package/src/markdown.ts +0 -577
- package/src/memory-overview-plugin.ts +0 -159
- package/src/message-formatting.test.ts +0 -1401
- package/src/message-formatting.ts +0 -972
- package/src/message-preprocessing.ts +0 -579
- package/src/oauth-rotation-shared.ts +0 -332
- package/src/onboarding-tutorial.ts +0 -167
- package/src/onboarding-welcome.ts +0 -49
- package/src/openai-auth-plugin.ts +0 -158
- package/src/openai-auth-state.ts +0 -297
- package/src/openai-realtime.ts +0 -358
- package/src/opencode-command-detection.test.ts +0 -307
- package/src/opencode-command-detection.ts +0 -76
- package/src/opencode-command.test.ts +0 -290
- package/src/opencode-command.ts +0 -251
- package/src/opencode-interrupt-plugin.test.ts +0 -419
- package/src/opencode-interrupt-plugin.ts +0 -306
- package/src/opencode.ts +0 -1590
- package/src/parse-permission-rules.test.ts +0 -127
- package/src/patch-text-parser.ts +0 -107
- package/src/plugin-logger.ts +0 -139
- package/src/plugin-opencode-client.ts +0 -71
- package/src/privacy-sanitizer.ts +0 -142
- package/src/project-add.test.ts +0 -80
- package/src/question-abort-clears-dropdown.e2e.test.ts +0 -109
- package/src/question-abort-resume.e2e.test.ts +0 -121
- package/src/question-abort-retry-text-before-dropdown.e2e.test.ts +0 -172
- package/src/question-text-before-dropdown.e2e.test.ts +0 -67
- package/src/queue-advanced-abort.e2e.test.ts +0 -449
- package/src/queue-advanced-action-buttons.e2e.test.ts +0 -271
- package/src/queue-advanced-e2e-setup.ts +0 -1164
- package/src/queue-advanced-footer.e2e.test.ts +0 -357
- package/src/queue-advanced-model-switch.e2e.test.ts +0 -382
- package/src/queue-advanced-permissions-typing.e2e.test.ts +0 -256
- package/src/queue-advanced-question.e2e.test.ts +0 -322
- package/src/queue-advanced-typing-interrupt.e2e.test.ts +0 -147
- package/src/queue-advanced-typing.e2e.test.ts +0 -199
- package/src/queue-delete-message.e2e.test.ts +0 -102
- package/src/queue-drain-after-interactive-ui.e2e.test.ts +0 -152
- package/src/queue-interrupt-drain.e2e.test.ts +0 -288
- package/src/queue-question-select-drain.e2e.test.ts +0 -206
- package/src/runtime-idle-sweeper.ts +0 -76
- package/src/runtime-lifecycle.e2e.test.ts +0 -714
- package/src/schema.sql +0 -251
- package/src/sentry.ts +0 -26
- package/src/session-handler/agent-utils.ts +0 -99
- package/src/session-handler/event-stream-fixtures/real-session-action-buttons.jsonl +0 -45
- package/src/session-handler/event-stream-fixtures/real-session-footer-suppressed-on-pre-idle-interrupt.jsonl +0 -40
- package/src/session-handler/event-stream-fixtures/real-session-permission-external-file.jsonl +0 -23
- package/src/session-handler/event-stream-fixtures/real-session-task-normal.jsonl +0 -22
- package/src/session-handler/event-stream-fixtures/real-session-task-three-parallel-sleeps.jsonl +0 -277
- package/src/session-handler/event-stream-fixtures/real-session-task-user-interruption.jsonl +0 -46
- package/src/session-handler/event-stream-fixtures/session-abort-after-idle-race.jsonl +0 -21
- package/src/session-handler/event-stream-fixtures/session-concurrent-messages-serialized.jsonl +0 -56
- package/src/session-handler/event-stream-fixtures/session-explicit-abort.jsonl +0 -44
- package/src/session-handler/event-stream-fixtures/session-normal-completion.jsonl +0 -29
- package/src/session-handler/event-stream-fixtures/session-tool-call-noisy-stream.jsonl +0 -29
- package/src/session-handler/event-stream-fixtures/session-two-completions-same-session.jsonl +0 -50
- package/src/session-handler/event-stream-fixtures/session-user-interruption.jsonl +0 -59
- package/src/session-handler/event-stream-fixtures/session-voice-queued-followup.jsonl +0 -52
- package/src/session-handler/event-stream-state.test.ts +0 -3304
- package/src/session-handler/event-stream-state.ts +0 -1773
- package/src/session-handler/global-event-listener.ts +0 -258
- package/src/session-handler/model-utils.test.ts +0 -537
- package/src/session-handler/model-utils.ts +0 -489
- package/src/session-handler/opencode-session-event-log.ts +0 -159
- package/src/session-handler/thread-ingress.test.ts +0 -54
- package/src/session-handler/thread-runtime-state.ts +0 -351
- package/src/session-handler/thread-session-runtime-summary.test.ts +0 -169
- package/src/session-handler/thread-session-runtime.ts +0 -5608
- package/src/session-handler.ts +0 -15
- package/src/session-search.test.ts +0 -331
- package/src/session-search.ts +0 -382
- package/src/session-sleep.test.ts +0 -294
- package/src/session-title-rename.test.ts +0 -186
- package/src/skill-filter.test.ts +0 -110
- package/src/skill-filter.ts +0 -63
- package/src/startup-service.ts +0 -200
- package/src/startup-time.e2e.test.ts +0 -377
- package/src/system-message.test.ts +0 -1176
- package/src/system-message.ts +0 -1179
- package/src/task-id-plugin.ts +0 -17
- package/src/task-runner.ts +0 -768
- package/src/task-schedule.test.ts +0 -424
- package/src/task-schedule.ts +0 -470
- package/src/test-utils.ts +0 -506
- package/src/thinking-utils.test.ts +0 -46
- package/src/thinking-utils.ts +0 -82
- package/src/thread-message-queue.e2e.test.ts +0 -1395
- package/src/tools.ts +0 -430
- package/src/undici.d.ts +0 -12
- package/src/undo-redo.e2e.test.ts +0 -219
- package/src/unnest-code-blocks.test.ts +0 -713
- package/src/unnest-code-blocks.ts +0 -185
- package/src/upgrade.ts +0 -127
- package/src/utils.ts +0 -216
- package/src/voice-attachment.ts +0 -79
- package/src/voice-handler.ts +0 -999
- package/src/voice-message.e2e.test.ts +0 -1524
- package/src/voice-routing-input.e2e.test.ts +0 -203
- package/src/voice-routing-question.e2e.test.ts +0 -131
- package/src/voice.test.ts +0 -415
- package/src/wait-session.ts +0 -291
- package/src/websockify.ts +0 -101
- package/src/worker-types.ts +0 -64
- package/src/worktree-clone-isolation.e2e.test.ts +0 -213
- package/src/worktree-lifecycle.e2e.test.ts +0 -900
- package/src/worktree-utils.ts +0 -4
- package/src/xai-auth-plugin.ts +0 -303
- package/src/xai-auth-state.test.ts +0 -224
- package/src/xai-auth-state.ts +0 -294
- package/src/xml.test.ts +0 -38
- package/src/xml.ts +0 -121
|
@@ -1,1524 +0,0 @@
|
|
|
1
|
-
// E2e tests for voice message handling (audio attachment transcription).
|
|
2
|
-
// Uses deterministic transcription (store.test.deterministicTranscription) to
|
|
3
|
-
// bypass real AI model calls and control transcription output, timing, and
|
|
4
|
-
// queueMessage flag. Combined with opencode-deterministic-provider for session
|
|
5
|
-
// responses. Tests validate the full flow: attachment detection → transcription
|
|
6
|
-
// → session dispatch, including interrupt, queue, and race condition scenarios.
|
|
7
|
-
//
|
|
8
|
-
// Tests assert on both Discord messages (via digital twin) and session state
|
|
9
|
-
// transitions (via getThreadState from the zustand store).
|
|
10
|
-
|
|
11
|
-
import fs from 'node:fs'
|
|
12
|
-
import http from 'node:http'
|
|
13
|
-
|
|
14
|
-
import path from 'node:path'
|
|
15
|
-
import url from 'node:url'
|
|
16
|
-
import { describe, beforeAll, afterAll, beforeEach, test, expect } from 'vitest'
|
|
17
|
-
import { ChannelType, Client, GatewayIntentBits, Partials } from 'discord.js'
|
|
18
|
-
import { DigitalDiscord } from 'discord-digital-twin/src'
|
|
19
|
-
import {
|
|
20
|
-
buildDeterministicOpencodeConfig,
|
|
21
|
-
type DeterministicMatcher,
|
|
22
|
-
} from 'opencode-deterministic-provider'
|
|
23
|
-
import { setDataDir } from './config.js'
|
|
24
|
-
import { store, type DeterministicTranscriptionConfig } from './store.js'
|
|
25
|
-
import { startDiscordBot } from './discord-bot.js'
|
|
26
|
-
import {
|
|
27
|
-
setBotToken,
|
|
28
|
-
initDatabase,
|
|
29
|
-
closeDatabase,
|
|
30
|
-
setChannelDirectory,
|
|
31
|
-
setChannelVerbosity,
|
|
32
|
-
getThreadSession,
|
|
33
|
-
createPendingWorkspace,
|
|
34
|
-
setWorkspaceReady,
|
|
35
|
-
getThreadWorktreeOrWorkspace,
|
|
36
|
-
} from './database.js'
|
|
37
|
-
import { startHranaServer, stopHranaServer } from './hrana-server.js'
|
|
38
|
-
import { initializeOpencodeForDirectory, getOpencodeClient, stopOpencodeServer } from './opencode.js'
|
|
39
|
-
import type { Part, Message } from '@opencode-ai/sdk/v2'
|
|
40
|
-
import {
|
|
41
|
-
chooseLockPort,
|
|
42
|
-
cleanupTestSessions,
|
|
43
|
-
initTestGitRepo,
|
|
44
|
-
waitForFooterMessage,
|
|
45
|
-
waitForBotMessageContaining,
|
|
46
|
-
waitForThreadState,
|
|
47
|
-
} from './test-utils.js'
|
|
48
|
-
|
|
49
|
-
import { getThreadState } from './session-handler/thread-runtime-state.js'
|
|
50
|
-
|
|
51
|
-
const e2eTest = describe
|
|
52
|
-
|
|
53
|
-
// ── Helpers ──────────────────────────────────────────────────────
|
|
54
|
-
|
|
55
|
-
function createRunDirectories() {
|
|
56
|
-
const root = path.resolve(process.cwd(), 'tmp', 'voice-msg-e2e')
|
|
57
|
-
fs.mkdirSync(root, { recursive: true })
|
|
58
|
-
|
|
59
|
-
const dataDir = fs.mkdtempSync(path.join(root, 'data-'))
|
|
60
|
-
const projectDirectory = path.join(root, 'project')
|
|
61
|
-
fs.mkdirSync(projectDirectory, { recursive: true })
|
|
62
|
-
initTestGitRepo(projectDirectory)
|
|
63
|
-
|
|
64
|
-
return { root, dataDir, projectDirectory }
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
function createDiscordJsClient({ restUrl }: { restUrl: string }) {
|
|
68
|
-
return new Client({
|
|
69
|
-
intents: [
|
|
70
|
-
GatewayIntentBits.Guilds,
|
|
71
|
-
GatewayIntentBits.GuildMessages,
|
|
72
|
-
GatewayIntentBits.MessageContent,
|
|
73
|
-
GatewayIntentBits.GuildVoiceStates,
|
|
74
|
-
],
|
|
75
|
-
partials: [
|
|
76
|
-
Partials.Channel,
|
|
77
|
-
Partials.Message,
|
|
78
|
-
Partials.User,
|
|
79
|
-
Partials.ThreadMember,
|
|
80
|
-
],
|
|
81
|
-
rest: {
|
|
82
|
-
api: restUrl,
|
|
83
|
-
version: '10',
|
|
84
|
-
},
|
|
85
|
-
})
|
|
86
|
-
}
|
|
87
|
-
|
|
88
|
-
/** Set the deterministic transcription config in the store for the next voice message. */
|
|
89
|
-
function setDeterministicTranscription(config: DeterministicTranscriptionConfig | null) {
|
|
90
|
-
store.setState({
|
|
91
|
-
test: { deterministicTranscription: config },
|
|
92
|
-
})
|
|
93
|
-
}
|
|
94
|
-
|
|
95
|
-
// ── OpenCode session assertion helpers ───────────────────────────
|
|
96
|
-
// These verify what actually happened in the OpenCode session (prompts
|
|
97
|
-
// sent, aborts, responses) beyond just Discord messages and thread state.
|
|
98
|
-
|
|
99
|
-
type SessionMessage = { info: Message; parts: Part[] }
|
|
100
|
-
|
|
101
|
-
function getOpencodeClientForTest(projectDirectory: string) {
|
|
102
|
-
const client = getOpencodeClient(projectDirectory)
|
|
103
|
-
if (!client) {
|
|
104
|
-
throw new Error('OpenCode client not found for project directory')
|
|
105
|
-
}
|
|
106
|
-
return client
|
|
107
|
-
}
|
|
108
|
-
|
|
109
|
-
/** Extract text content from an array of parts (filters to TextPart only). */
|
|
110
|
-
function getTextFromParts(parts: Part[]): string[] {
|
|
111
|
-
return parts.flatMap((part) => {
|
|
112
|
-
if (part.type === 'text') {
|
|
113
|
-
return [part.text]
|
|
114
|
-
}
|
|
115
|
-
return []
|
|
116
|
-
})
|
|
117
|
-
}
|
|
118
|
-
|
|
119
|
-
/** Get all user-role messages' text parts joined. */
|
|
120
|
-
function getUserTexts(messages: SessionMessage[]): string[] {
|
|
121
|
-
return messages
|
|
122
|
-
.filter((m) => m.info.role === 'user')
|
|
123
|
-
.flatMap((m) => getTextFromParts(m.parts))
|
|
124
|
-
}
|
|
125
|
-
|
|
126
|
-
/** Get all assistant-role messages' text parts joined. */
|
|
127
|
-
function getAssistantTexts(messages: SessionMessage[]): string[] {
|
|
128
|
-
return messages
|
|
129
|
-
.filter((m) => m.info.role === 'assistant')
|
|
130
|
-
.flatMap((m) => getTextFromParts(m.parts))
|
|
131
|
-
}
|
|
132
|
-
|
|
133
|
-
/**
|
|
134
|
-
* Poll session.messages() until predicate returns true.
|
|
135
|
-
* Used to wait for async session updates (prompts dispatched, responses completed).
|
|
136
|
-
*/
|
|
137
|
-
async function waitForSessionMessages({
|
|
138
|
-
projectDirectory,
|
|
139
|
-
sessionID,
|
|
140
|
-
timeout,
|
|
141
|
-
predicate,
|
|
142
|
-
description,
|
|
143
|
-
}: {
|
|
144
|
-
projectDirectory: string
|
|
145
|
-
sessionID: string
|
|
146
|
-
timeout: number
|
|
147
|
-
predicate: (messages: SessionMessage[]) => boolean
|
|
148
|
-
description: string
|
|
149
|
-
}): Promise<SessionMessage[]> {
|
|
150
|
-
const client = getOpencodeClientForTest(projectDirectory)
|
|
151
|
-
const start = Date.now()
|
|
152
|
-
while (Date.now() - start < timeout) {
|
|
153
|
-
const result = await client.session.messages({
|
|
154
|
-
sessionID,
|
|
155
|
-
directory: projectDirectory,
|
|
156
|
-
})
|
|
157
|
-
const messages = result.data ?? []
|
|
158
|
-
if (predicate(messages)) {
|
|
159
|
-
return messages
|
|
160
|
-
}
|
|
161
|
-
await new Promise((resolve) => {
|
|
162
|
-
setTimeout(resolve, 100)
|
|
163
|
-
})
|
|
164
|
-
}
|
|
165
|
-
// Final attempt for error reporting
|
|
166
|
-
const finalResult = await client.session.messages({
|
|
167
|
-
sessionID,
|
|
168
|
-
directory: projectDirectory,
|
|
169
|
-
})
|
|
170
|
-
const finalMessages = finalResult.data ?? []
|
|
171
|
-
const userTexts = getUserTexts(finalMessages)
|
|
172
|
-
const assistantTexts = getAssistantTexts(finalMessages)
|
|
173
|
-
throw new Error(
|
|
174
|
-
`Timed out waiting for session messages (${description}). ` +
|
|
175
|
-
`User texts: ${JSON.stringify(userTexts.map((t) => t.slice(0, 80)))}. ` +
|
|
176
|
-
`Assistant texts: ${JSON.stringify(assistantTexts.map((t) => t.slice(0, 80)))}`,
|
|
177
|
-
)
|
|
178
|
-
}
|
|
179
|
-
|
|
180
|
-
// ── Deterministic provider matchers ──────────────────────────────
|
|
181
|
-
// The opencode session uses these to produce canned responses.
|
|
182
|
-
|
|
183
|
-
function createDeterministicMatchers(): DeterministicMatcher[] {
|
|
184
|
-
// Slow response: emits text-delta after 2s delay, giving voice messages
|
|
185
|
-
// time to arrive while the session is still "running".
|
|
186
|
-
// Uses latestUserTextIncludes (not rawPromptIncludes) so it only matches
|
|
187
|
-
// the current user message, not previous messages in session history.
|
|
188
|
-
const slowResponse: DeterministicMatcher = {
|
|
189
|
-
id: 'slow-response',
|
|
190
|
-
priority: 100,
|
|
191
|
-
when: {
|
|
192
|
-
latestUserTextIncludes: 'SLOW_RESPONSE_MARKER',
|
|
193
|
-
},
|
|
194
|
-
then: {
|
|
195
|
-
parts: [
|
|
196
|
-
{ type: 'stream-start', warnings: [] },
|
|
197
|
-
{ type: 'text-start', id: 'slow' },
|
|
198
|
-
{ type: 'text-delta', id: 'slow', delta: 'slow-response-done' },
|
|
199
|
-
{ type: 'text-end', id: 'slow' },
|
|
200
|
-
{
|
|
201
|
-
type: 'finish',
|
|
202
|
-
finishReason: 'stop',
|
|
203
|
-
usage: { inputTokens: 1, outputTokens: 1, totalTokens: 2 },
|
|
204
|
-
},
|
|
205
|
-
],
|
|
206
|
-
// 2s delay on the first text delta — keeps the session in "running" state
|
|
207
|
-
partDelaysMs: [0, 0, 2000, 0, 0],
|
|
208
|
-
},
|
|
209
|
-
}
|
|
210
|
-
|
|
211
|
-
// Fast response: completes almost immediately (~100ms)
|
|
212
|
-
const fastResponse: DeterministicMatcher = {
|
|
213
|
-
id: 'fast-response',
|
|
214
|
-
priority: 90,
|
|
215
|
-
when: {
|
|
216
|
-
latestUserTextIncludes: 'FAST_RESPONSE_MARKER',
|
|
217
|
-
},
|
|
218
|
-
then: {
|
|
219
|
-
parts: [
|
|
220
|
-
{ type: 'stream-start', warnings: [] },
|
|
221
|
-
{ type: 'text-start', id: 'fast' },
|
|
222
|
-
{ type: 'text-delta', id: 'fast', delta: 'fast-response-done' },
|
|
223
|
-
{ type: 'text-end', id: 'fast' },
|
|
224
|
-
{
|
|
225
|
-
type: 'finish',
|
|
226
|
-
finishReason: 'stop',
|
|
227
|
-
usage: { inputTokens: 1, outputTokens: 1, totalTokens: 2 },
|
|
228
|
-
},
|
|
229
|
-
],
|
|
230
|
-
partDelaysMs: [0, 100, 0, 0, 0],
|
|
231
|
-
},
|
|
232
|
-
}
|
|
233
|
-
|
|
234
|
-
// Default: matches any user message (fallback)
|
|
235
|
-
const defaultReply: DeterministicMatcher = {
|
|
236
|
-
id: 'default-reply',
|
|
237
|
-
priority: 1,
|
|
238
|
-
when: {
|
|
239
|
-
lastMessageRole: 'user',
|
|
240
|
-
},
|
|
241
|
-
then: {
|
|
242
|
-
parts: [
|
|
243
|
-
{ type: 'stream-start', warnings: [] },
|
|
244
|
-
{ type: 'text-start', id: 'default' },
|
|
245
|
-
{ type: 'text-delta', id: 'default', delta: 'session-reply' },
|
|
246
|
-
{ type: 'text-end', id: 'default' },
|
|
247
|
-
{
|
|
248
|
-
type: 'finish',
|
|
249
|
-
finishReason: 'stop',
|
|
250
|
-
usage: { inputTokens: 1, outputTokens: 1, totalTokens: 2 },
|
|
251
|
-
},
|
|
252
|
-
],
|
|
253
|
-
partDelaysMs: [0, 100, 0, 0, 0],
|
|
254
|
-
},
|
|
255
|
-
}
|
|
256
|
-
|
|
257
|
-
// Tool followup: when the last message is a tool result
|
|
258
|
-
const toolFollowup: DeterministicMatcher = {
|
|
259
|
-
id: 'tool-followup',
|
|
260
|
-
priority: 50,
|
|
261
|
-
when: {
|
|
262
|
-
lastMessageRole: 'tool',
|
|
263
|
-
},
|
|
264
|
-
then: {
|
|
265
|
-
parts: [
|
|
266
|
-
{ type: 'stream-start', warnings: [] },
|
|
267
|
-
{ type: 'text-start', id: 'tool-followup' },
|
|
268
|
-
{ type: 'text-delta', id: 'tool-followup', delta: 'tool done' },
|
|
269
|
-
{ type: 'text-end', id: 'tool-followup' },
|
|
270
|
-
{
|
|
271
|
-
type: 'finish',
|
|
272
|
-
finishReason: 'stop',
|
|
273
|
-
usage: { inputTokens: 1, outputTokens: 1, totalTokens: 2 },
|
|
274
|
-
},
|
|
275
|
-
],
|
|
276
|
-
},
|
|
277
|
-
}
|
|
278
|
-
|
|
279
|
-
return [slowResponse, fastResponse, toolFollowup, defaultReply]
|
|
280
|
-
}
|
|
281
|
-
|
|
282
|
-
// ── Test constants ───────────────────────────────────────────────
|
|
283
|
-
|
|
284
|
-
const TEST_USER_ID = '300000000000000777'
|
|
285
|
-
const TEXT_CHANNEL_ID = '300000000000000778'
|
|
286
|
-
|
|
287
|
-
// ── Test suite ───────────────────────────────────────────────────
|
|
288
|
-
|
|
289
|
-
e2eTest('voice message handling', () => {
|
|
290
|
-
let directories: ReturnType<typeof createRunDirectories>
|
|
291
|
-
let discord: DigitalDiscord
|
|
292
|
-
let botClient: Client
|
|
293
|
-
let previousDefaultVerbosity = store.getState().defaultVerbosity
|
|
294
|
-
let testStartTime = Date.now()
|
|
295
|
-
|
|
296
|
-
beforeAll(async () => {
|
|
297
|
-
testStartTime = Date.now()
|
|
298
|
-
directories = createRunDirectories()
|
|
299
|
-
const lockPort = chooseLockPort({ key: TEXT_CHANNEL_ID })
|
|
300
|
-
|
|
301
|
-
process.env['KIMAKI_LOCK_PORT'] = String(lockPort)
|
|
302
|
-
setDataDir(directories.dataDir)
|
|
303
|
-
previousDefaultVerbosity = store.getState().defaultVerbosity
|
|
304
|
-
store.setState({ defaultVerbosity: 'tools_and_text' })
|
|
305
|
-
|
|
306
|
-
const digitalDiscordDbPath = path.join(
|
|
307
|
-
directories.dataDir,
|
|
308
|
-
'digital-discord.db',
|
|
309
|
-
)
|
|
310
|
-
|
|
311
|
-
discord = new DigitalDiscord({
|
|
312
|
-
guild: {
|
|
313
|
-
name: 'Voice E2E Guild',
|
|
314
|
-
ownerId: TEST_USER_ID,
|
|
315
|
-
},
|
|
316
|
-
channels: [
|
|
317
|
-
{
|
|
318
|
-
id: TEXT_CHANNEL_ID,
|
|
319
|
-
name: 'voice-e2e',
|
|
320
|
-
type: ChannelType.GuildText,
|
|
321
|
-
},
|
|
322
|
-
],
|
|
323
|
-
users: [
|
|
324
|
-
{
|
|
325
|
-
id: TEST_USER_ID,
|
|
326
|
-
username: 'voice-tester',
|
|
327
|
-
},
|
|
328
|
-
],
|
|
329
|
-
dbUrl: `file:${digitalDiscordDbPath}`,
|
|
330
|
-
})
|
|
331
|
-
|
|
332
|
-
await discord.start()
|
|
333
|
-
|
|
334
|
-
const providerNpm = url
|
|
335
|
-
.pathToFileURL(
|
|
336
|
-
path.resolve(
|
|
337
|
-
process.cwd(),
|
|
338
|
-
'..',
|
|
339
|
-
'opencode-deterministic-provider',
|
|
340
|
-
'src',
|
|
341
|
-
'index.ts',
|
|
342
|
-
),
|
|
343
|
-
)
|
|
344
|
-
.toString()
|
|
345
|
-
|
|
346
|
-
const opencodeConfig = buildDeterministicOpencodeConfig({
|
|
347
|
-
providerName: 'deterministic-provider',
|
|
348
|
-
providerNpm,
|
|
349
|
-
model: 'deterministic-v2',
|
|
350
|
-
smallModel: 'deterministic-v2',
|
|
351
|
-
settings: {
|
|
352
|
-
strict: false,
|
|
353
|
-
matchers: createDeterministicMatchers(),
|
|
354
|
-
},
|
|
355
|
-
})
|
|
356
|
-
fs.writeFileSync(
|
|
357
|
-
path.join(directories.projectDirectory, 'opencode.json'),
|
|
358
|
-
JSON.stringify(opencodeConfig, null, 2),
|
|
359
|
-
)
|
|
360
|
-
|
|
361
|
-
const dbPath = path.join(directories.dataDir, 'discord-sessions.db')
|
|
362
|
-
const hranaResult = await startHranaServer({ dbPath })
|
|
363
|
-
if (hranaResult instanceof Error) {
|
|
364
|
-
throw hranaResult
|
|
365
|
-
}
|
|
366
|
-
process.env['KIMAKI_DB_URL'] = hranaResult
|
|
367
|
-
await initDatabase()
|
|
368
|
-
await setBotToken(discord.botUserId, discord.botToken)
|
|
369
|
-
|
|
370
|
-
await setChannelDirectory({
|
|
371
|
-
channelId: TEXT_CHANNEL_ID,
|
|
372
|
-
directory: directories.projectDirectory,
|
|
373
|
-
channelType: 'text',
|
|
374
|
-
})
|
|
375
|
-
await setChannelVerbosity(TEXT_CHANNEL_ID, 'tools_and_text')
|
|
376
|
-
|
|
377
|
-
botClient = createDiscordJsClient({ restUrl: discord.restUrl })
|
|
378
|
-
await startDiscordBot({
|
|
379
|
-
token: discord.botToken,
|
|
380
|
-
appId: discord.botUserId,
|
|
381
|
-
discordClient: botClient,
|
|
382
|
-
})
|
|
383
|
-
|
|
384
|
-
// Pre-warm the opencode server
|
|
385
|
-
const warmup = await initializeOpencodeForDirectory(
|
|
386
|
-
directories.projectDirectory,
|
|
387
|
-
)
|
|
388
|
-
if (warmup instanceof Error) {
|
|
389
|
-
throw warmup
|
|
390
|
-
}
|
|
391
|
-
}, 20_000)
|
|
392
|
-
|
|
393
|
-
afterAll(async () => {
|
|
394
|
-
// Reset deterministic transcription
|
|
395
|
-
setDeterministicTranscription(null)
|
|
396
|
-
|
|
397
|
-
if (directories) {
|
|
398
|
-
await cleanupTestSessions({
|
|
399
|
-
projectDirectory: directories.projectDirectory,
|
|
400
|
-
testStartTime,
|
|
401
|
-
})
|
|
402
|
-
}
|
|
403
|
-
|
|
404
|
-
if (botClient) {
|
|
405
|
-
void botClient.destroy()
|
|
406
|
-
}
|
|
407
|
-
|
|
408
|
-
await stopOpencodeServer()
|
|
409
|
-
await Promise.all([
|
|
410
|
-
closeDatabase().catch(() => {
|
|
411
|
-
return
|
|
412
|
-
}),
|
|
413
|
-
stopHranaServer().catch(() => {
|
|
414
|
-
return
|
|
415
|
-
}),
|
|
416
|
-
discord?.stop().catch(() => {
|
|
417
|
-
return
|
|
418
|
-
}),
|
|
419
|
-
])
|
|
420
|
-
|
|
421
|
-
delete process.env['KIMAKI_LOCK_PORT']
|
|
422
|
-
delete process.env['KIMAKI_DB_URL']
|
|
423
|
-
store.setState({ defaultVerbosity: previousDefaultVerbosity })
|
|
424
|
-
if (directories) {
|
|
425
|
-
fs.rmSync(directories.dataDir, { recursive: true, force: true })
|
|
426
|
-
}
|
|
427
|
-
}, 5_000)
|
|
428
|
-
|
|
429
|
-
beforeEach(() => {
|
|
430
|
-
// Reset deterministic transcription before each test to prevent leakage
|
|
431
|
-
// from a failed test that set it but didn't clean up
|
|
432
|
-
setDeterministicTranscription(null)
|
|
433
|
-
})
|
|
434
|
-
|
|
435
|
-
// ── Test 1: Voice message in a channel creates thread + session ──
|
|
436
|
-
|
|
437
|
-
// Vitest disallows inline snapshots in test.each; register separate tests instead.
|
|
438
|
-
for (const sessionAction of ['btw', 'new-session'] as const) {
|
|
439
|
-
test(
|
|
440
|
-
`voice ${sessionAction} routing creates a separate thread with the correct history`,
|
|
441
|
-
async () => {
|
|
442
|
-
await discord.channel(TEXT_CHANNEL_ID).user(TEST_USER_ID)
|
|
443
|
-
.sendMessage({ content: `Source history for voice ${sessionAction}` })
|
|
444
|
-
const thread = await discord.channel(TEXT_CHANNEL_ID).waitForThread({
|
|
445
|
-
timeout: 4_000,
|
|
446
|
-
predicate: (candidate) => Boolean(candidate.name?.includes(`Source history for voice ${sessionAction}`)),
|
|
447
|
-
})
|
|
448
|
-
await waitForFooterMessage({ discord, threadId: thread.id, timeout: 4_000 })
|
|
449
|
-
const sourceSessionId = await getThreadSession(thread.id)
|
|
450
|
-
await createPendingWorkspace({
|
|
451
|
-
threadId: thread.id,
|
|
452
|
-
workspaceType: 'worktree',
|
|
453
|
-
workspaceName: 'voice-workspace',
|
|
454
|
-
projectDirectory: directories.projectDirectory,
|
|
455
|
-
})
|
|
456
|
-
await setWorkspaceReady({
|
|
457
|
-
threadId: thread.id,
|
|
458
|
-
workspaceId: 'voice-workspace-id',
|
|
459
|
-
workspaceDirectory: directories.projectDirectory,
|
|
460
|
-
})
|
|
461
|
-
const prompt = `Explain voice routing ${sessionAction}`
|
|
462
|
-
setDeterministicTranscription({ transcription: prompt, queueMessage: false, sessionAction })
|
|
463
|
-
await discord.thread(thread.id).user(TEST_USER_ID).sendVoiceMessage()
|
|
464
|
-
const confirmation = sessionAction === 'btw' ? 'Session forked!' : 'Created new session in'
|
|
465
|
-
const routingMessages = await waitForBotMessageContaining({
|
|
466
|
-
discord,
|
|
467
|
-
threadId: thread.id,
|
|
468
|
-
text: confirmation,
|
|
469
|
-
timeout: 4_000,
|
|
470
|
-
})
|
|
471
|
-
// OpenCode can rename the destination before discovery; follow its confirmed ID.
|
|
472
|
-
const targetId = routingMessages.find((message) => message.content.includes(confirmation))
|
|
473
|
-
?.content.match(/<#(\d+)>/)?.[1]
|
|
474
|
-
if (!targetId) throw new Error('Expected destination thread link')
|
|
475
|
-
const target = await discord.channel(TEXT_CHANNEL_ID).waitForThread({
|
|
476
|
-
timeout: 4_000,
|
|
477
|
-
predicate: (candidate) => candidate.id === targetId,
|
|
478
|
-
})
|
|
479
|
-
await waitForFooterMessage({ discord, threadId: target.id, timeout: 4_000 })
|
|
480
|
-
const th = discord.thread(target.id)
|
|
481
|
-
const transcript = {
|
|
482
|
-
source: (await discord.thread(thread.id).text()).replaceAll(target.id, 'TARGET_THREAD'),
|
|
483
|
-
target: (await th.text()).replaceAll(thread.id, 'SOURCE_THREAD'),
|
|
484
|
-
}
|
|
485
|
-
if (sessionAction === 'btw') {
|
|
486
|
-
expect(transcript).toMatchInlineSnapshot(`
|
|
487
|
-
{
|
|
488
|
-
"source": "--- from: user (voice-tester)
|
|
489
|
-
Source history for voice btw
|
|
490
|
-
--- from: assistant (TestBot)
|
|
491
|
-
-# *using deterministic-provider/deterministic-v2*
|
|
492
|
-
> session-reply
|
|
493
|
-
-# *project ⋅ main ⋅ Ns ⋅ N% ⋅ deterministic-v2*
|
|
494
|
-
--- from: user (voice-tester)
|
|
495
|
-
[attachment: voice-message.ogg]
|
|
496
|
-
--- from: assistant (TestBot)
|
|
497
|
-
🎤 Transcribing voice message...
|
|
498
|
-
📝 **Transcribed message:** Explain voice routing btw
|
|
499
|
-
Session forked! Continue in <#TARGET_THREAD>",
|
|
500
|
-
"target": "--- from: assistant (TestBot)
|
|
501
|
-
Reusing context from <#SOURCE_THREAD> to answer prompt...
|
|
502
|
-
Explain voice routing btw
|
|
503
|
-
> session-reply
|
|
504
|
-
-# *project ⋅ main ⋅ Ns ⋅ N% ⋅ deterministic-v2*",
|
|
505
|
-
}
|
|
506
|
-
`)
|
|
507
|
-
} else {
|
|
508
|
-
expect(transcript).toMatchInlineSnapshot(`
|
|
509
|
-
{
|
|
510
|
-
"source": "--- from: user (voice-tester)
|
|
511
|
-
Source history for voice new-session
|
|
512
|
-
--- from: assistant (TestBot)
|
|
513
|
-
-# *using deterministic-provider/deterministic-v2*
|
|
514
|
-
> session-reply
|
|
515
|
-
-# *project ⋅ main ⋅ Ns ⋅ N% ⋅ deterministic-v2*
|
|
516
|
-
--- from: user (voice-tester)
|
|
517
|
-
[attachment: voice-message.ogg]
|
|
518
|
-
--- from: assistant (TestBot)
|
|
519
|
-
🎤 Transcribing voice message...
|
|
520
|
-
📝 **Transcribed message:** Explain voice routing new-session
|
|
521
|
-
Created new session in <#TARGET_THREAD>",
|
|
522
|
-
"target": "--- from: assistant (TestBot)
|
|
523
|
-
**Starting OpenCode session**
|
|
524
|
-
Explain voice routing new-session
|
|
525
|
-
-# *using deterministic-provider/deterministic-v2*
|
|
526
|
-
> session-reply
|
|
527
|
-
-# *project ⋅ main ⋅ Ns ⋅ N% ⋅ deterministic-v2*",
|
|
528
|
-
}
|
|
529
|
-
`)
|
|
530
|
-
}
|
|
531
|
-
const targetSessionId = await getThreadSession(target.id)
|
|
532
|
-
expect(targetSessionId).toBeTruthy()
|
|
533
|
-
expect(targetSessionId).not.toBe(sourceSessionId)
|
|
534
|
-
expect(await getThreadWorktreeOrWorkspace(target.id)).toMatchObject({
|
|
535
|
-
status: 'ready',
|
|
536
|
-
workspace_type: 'worktree',
|
|
537
|
-
workspace_name: 'voice-workspace',
|
|
538
|
-
workspace_id: 'voice-workspace-id',
|
|
539
|
-
workspace_directory: directories.projectDirectory,
|
|
540
|
-
})
|
|
541
|
-
const client = getOpencodeClientForTest(directories.projectDirectory)
|
|
542
|
-
const targetMessages = await client.session.messages({ sessionID: targetSessionId! })
|
|
543
|
-
const targetTexts = getUserTexts(targetMessages.data ?? []).join('\n')
|
|
544
|
-
expect(targetTexts).toContain(prompt)
|
|
545
|
-
expect(targetTexts).toContain(`Voice message transcription from Discord user:\n${prompt}`)
|
|
546
|
-
expect(targetTexts.includes(`Source history for voice ${sessionAction}`)).toBe(sessionAction === 'btw')
|
|
547
|
-
const sourceMessages = await client.session.messages({ sessionID: sourceSessionId! })
|
|
548
|
-
expect(getUserTexts(sourceMessages.data ?? []).join('\n')).not.toContain(prompt)
|
|
549
|
-
},
|
|
550
|
-
)
|
|
551
|
-
}
|
|
552
|
-
|
|
553
|
-
test(
|
|
554
|
-
'voice message in channel creates thread and starts session',
|
|
555
|
-
async () => {
|
|
556
|
-
setDeterministicTranscription({
|
|
557
|
-
transcription: 'Fix the login bug in auth.ts',
|
|
558
|
-
queueMessage: false,
|
|
559
|
-
})
|
|
560
|
-
|
|
561
|
-
// Send voice message in the text channel
|
|
562
|
-
await discord
|
|
563
|
-
.channel(TEXT_CHANNEL_ID)
|
|
564
|
-
.user(TEST_USER_ID)
|
|
565
|
-
.sendVoiceMessage()
|
|
566
|
-
|
|
567
|
-
// Thread should be created and renamed to the transcription text
|
|
568
|
-
const thread = await discord.channel(TEXT_CHANNEL_ID).waitForThread({
|
|
569
|
-
timeout: 4_000,
|
|
570
|
-
predicate: (t) => {
|
|
571
|
-
return t.name?.includes('Fix the login bug') ?? false
|
|
572
|
-
},
|
|
573
|
-
})
|
|
574
|
-
expect(thread).toBeDefined()
|
|
575
|
-
|
|
576
|
-
const th = discord.thread(thread.id)
|
|
577
|
-
|
|
578
|
-
// Bot should post "Transcribing..." then "Transcribed message: ..."
|
|
579
|
-
await waitForBotMessageContaining({
|
|
580
|
-
discord,
|
|
581
|
-
threadId: thread.id,
|
|
582
|
-
userId: TEST_USER_ID,
|
|
583
|
-
text: 'Transcribing voice message',
|
|
584
|
-
timeout: 4_000,
|
|
585
|
-
})
|
|
586
|
-
|
|
587
|
-
await waitForBotMessageContaining({
|
|
588
|
-
discord,
|
|
589
|
-
threadId: thread.id,
|
|
590
|
-
userId: TEST_USER_ID,
|
|
591
|
-
text: 'Fix the login bug in auth.ts',
|
|
592
|
-
timeout: 4_000,
|
|
593
|
-
})
|
|
594
|
-
|
|
595
|
-
// Session should get the transcribed prompt and respond
|
|
596
|
-
const sessionReply = await th.waitForBotReply({ timeout: 4_000 })
|
|
597
|
-
expect(sessionReply).toBeDefined()
|
|
598
|
-
|
|
599
|
-
await waitForFooterMessage({
|
|
600
|
-
discord,
|
|
601
|
-
threadId: thread.id,
|
|
602
|
-
timeout: 4_000,
|
|
603
|
-
})
|
|
604
|
-
|
|
605
|
-
// Assert thread state has a session and no queued messages after footer.
|
|
606
|
-
const finalState = await waitForThreadState({
|
|
607
|
-
threadId: thread.id,
|
|
608
|
-
predicate: (state) => {
|
|
609
|
-
return Boolean(state.sessionId) && state.queueItems.length === 0
|
|
610
|
-
},
|
|
611
|
-
timeout: 4_000,
|
|
612
|
-
description: 'voice turn settled with empty queue',
|
|
613
|
-
})
|
|
614
|
-
|
|
615
|
-
await waitForFooterMessage({
|
|
616
|
-
discord,
|
|
617
|
-
threadId: thread.id,
|
|
618
|
-
timeout: 4_000,
|
|
619
|
-
})
|
|
620
|
-
|
|
621
|
-
expect(await th.text()).toMatchInlineSnapshot(`
|
|
622
|
-
"--- from: user (voice-tester)
|
|
623
|
-
[attachment: voice-message.ogg]
|
|
624
|
-
--- from: assistant (TestBot)
|
|
625
|
-
🎤 Transcribing voice message...
|
|
626
|
-
📝 **Transcribed message:** Fix the login bug in auth.ts
|
|
627
|
-
-# *using deterministic-provider/deterministic-v2*
|
|
628
|
-
> session-reply
|
|
629
|
-
-# *project ⋅ main ⋅ Ns ⋅ N% ⋅ deterministic-v2*"
|
|
630
|
-
`)
|
|
631
|
-
expect(finalState.sessionId).toBeDefined()
|
|
632
|
-
|
|
633
|
-
// Verify OpenCode session received the transcribed voice message as a prompt
|
|
634
|
-
const messages = await waitForSessionMessages({
|
|
635
|
-
projectDirectory: directories.projectDirectory,
|
|
636
|
-
sessionID: finalState.sessionId!,
|
|
637
|
-
timeout: 4_000,
|
|
638
|
-
description: 'voice transcription prompt sent to session',
|
|
639
|
-
predicate: (all) => {
|
|
640
|
-
const userTexts = getUserTexts(all)
|
|
641
|
-
return userTexts.some((text) => text.includes('Fix the login bug in auth.ts'))
|
|
642
|
-
},
|
|
643
|
-
})
|
|
644
|
-
const userTexts = getUserTexts(messages)
|
|
645
|
-
expect(userTexts.some((t) => t.includes('Fix the login bug in auth.ts'))).toBe(true)
|
|
646
|
-
// Session should have at least one assistant response
|
|
647
|
-
const assistantTexts = getAssistantTexts(messages)
|
|
648
|
-
expect(assistantTexts.length).toBeGreaterThan(0)
|
|
649
|
-
},
|
|
650
|
-
8_000,
|
|
651
|
-
)
|
|
652
|
-
|
|
653
|
-
test(
|
|
654
|
-
'first voice message resumes after the user saves an API key',
|
|
655
|
-
async () => {
|
|
656
|
-
setDeterministicTranscription({
|
|
657
|
-
transcription: 'Resume the original voice note',
|
|
658
|
-
queueMessage: false,
|
|
659
|
-
requireApiKey: true,
|
|
660
|
-
})
|
|
661
|
-
|
|
662
|
-
await discord.channel(TEXT_CHANNEL_ID).user(TEST_USER_ID).sendVoiceMessage()
|
|
663
|
-
|
|
664
|
-
const thread = await discord.channel(TEXT_CHANNEL_ID).waitForThread({
|
|
665
|
-
timeout: 4_000,
|
|
666
|
-
predicate: (candidate) => candidate.name === 'Voice Message',
|
|
667
|
-
})
|
|
668
|
-
const th = discord.thread(thread.id)
|
|
669
|
-
const messages = await waitForBotMessageContaining({
|
|
670
|
-
discord,
|
|
671
|
-
threadId: thread.id,
|
|
672
|
-
userId: TEST_USER_ID,
|
|
673
|
-
text: 'Voice transcription requires an API key',
|
|
674
|
-
timeout: 4_000,
|
|
675
|
-
})
|
|
676
|
-
const keyMessage = messages.find((message) => {
|
|
677
|
-
return message.content.includes('Voice transcription requires an API key')
|
|
678
|
-
})
|
|
679
|
-
if (!keyMessage) throw new Error('Expected API key prompt message')
|
|
680
|
-
|
|
681
|
-
const buttonData = JSON.stringify(keyMessage.components)
|
|
682
|
-
const buttonCustomId = buttonData.match(
|
|
683
|
-
/"custom_id":"(transcription_apikey:[^"]+)"/,
|
|
684
|
-
)?.[1]
|
|
685
|
-
if (!buttonCustomId) throw new Error('Expected API key button custom id')
|
|
686
|
-
|
|
687
|
-
const buttonInteraction = await th.user(TEST_USER_ID).clickButton({
|
|
688
|
-
messageId: keyMessage.id,
|
|
689
|
-
customId: buttonCustomId,
|
|
690
|
-
})
|
|
691
|
-
await th.waitForInteractionAck({
|
|
692
|
-
interactionId: buttonInteraction.id,
|
|
693
|
-
timeout: 4_000,
|
|
694
|
-
})
|
|
695
|
-
const modalResponse = await th.getInteractionResponse(buttonInteraction.id)
|
|
696
|
-
const modalCustomId = modalResponse?.data?.match(
|
|
697
|
-
/"custom_id":"(transcription_apikey_modal:[^"]+)"/,
|
|
698
|
-
)?.[1]
|
|
699
|
-
if (!modalCustomId) throw new Error('Expected API key modal custom id')
|
|
700
|
-
|
|
701
|
-
const modalInteraction = await th.user(TEST_USER_ID).submitModal({
|
|
702
|
-
customId: modalCustomId,
|
|
703
|
-
fields: [{ customId: 'apikey', value: 'AIza-test-key' }],
|
|
704
|
-
})
|
|
705
|
-
await th.waitForInteractionAck({
|
|
706
|
-
interactionId: modalInteraction.id,
|
|
707
|
-
timeout: 4_000,
|
|
708
|
-
})
|
|
709
|
-
|
|
710
|
-
await waitForBotMessageContaining({
|
|
711
|
-
discord,
|
|
712
|
-
threadId: thread.id,
|
|
713
|
-
userId: TEST_USER_ID,
|
|
714
|
-
text: 'Resume the original voice note',
|
|
715
|
-
timeout: 4_000,
|
|
716
|
-
})
|
|
717
|
-
await waitForFooterMessage({
|
|
718
|
-
discord,
|
|
719
|
-
threadId: thread.id,
|
|
720
|
-
timeout: 4_000,
|
|
721
|
-
})
|
|
722
|
-
|
|
723
|
-
expect(await th.text()).toMatchInlineSnapshot(`
|
|
724
|
-
"--- from: user (voice-tester)
|
|
725
|
-
[attachment: voice-message.ogg]
|
|
726
|
-
--- from: assistant (TestBot)
|
|
727
|
-
🎤 Transcribing voice message...
|
|
728
|
-
Voice transcription requires an API key (OpenAI or Gemini). Set one to enable voice message transcription.
|
|
729
|
-
Gemini API key saved. Retrying the original voice message.
|
|
730
|
-
📝 **Transcribed message:** Resume the original voice note
|
|
731
|
-
-# *using deterministic-provider/deterministic-v2*
|
|
732
|
-
> session-reply
|
|
733
|
-
-# *project ⋅ main ⋅ Ns ⋅ N% ⋅ deterministic-v2*"
|
|
734
|
-
`)
|
|
735
|
-
|
|
736
|
-
const finalState = getThreadState(thread.id)
|
|
737
|
-
expect(finalState?.sessionId).toBeDefined()
|
|
738
|
-
},
|
|
739
|
-
10_000,
|
|
740
|
-
)
|
|
741
|
-
|
|
742
|
-
test(
|
|
743
|
-
'voice attachment without content type still transcribes and avoids empty prompt dispatch',
|
|
744
|
-
async () => {
|
|
745
|
-
setDeterministicTranscription({
|
|
746
|
-
transcription: 'Investigate the missing content type path',
|
|
747
|
-
queueMessage: false,
|
|
748
|
-
})
|
|
749
|
-
|
|
750
|
-
await discord.channel(TEXT_CHANNEL_ID).user(TEST_USER_ID).sendMessage({
|
|
751
|
-
content: '',
|
|
752
|
-
attachments: [
|
|
753
|
-
{
|
|
754
|
-
id: 'voice-no-content-type',
|
|
755
|
-
filename: 'voice-message.ogg',
|
|
756
|
-
size: 1024,
|
|
757
|
-
url: 'https://fake-cdn.discord.test/voice-no-content-type.ogg',
|
|
758
|
-
proxy_url: 'https://fake-cdn.discord.test/voice-no-content-type.ogg',
|
|
759
|
-
},
|
|
760
|
-
],
|
|
761
|
-
})
|
|
762
|
-
|
|
763
|
-
const thread = await discord.channel(TEXT_CHANNEL_ID).waitForThread({
|
|
764
|
-
timeout: 4_000,
|
|
765
|
-
predicate: (t) => {
|
|
766
|
-
return t.name?.includes('Investigate the missing content type path') ?? false
|
|
767
|
-
},
|
|
768
|
-
})
|
|
769
|
-
|
|
770
|
-
const th = discord.thread(thread.id)
|
|
771
|
-
|
|
772
|
-
await waitForBotMessageContaining({
|
|
773
|
-
discord,
|
|
774
|
-
threadId: thread.id,
|
|
775
|
-
userId: TEST_USER_ID,
|
|
776
|
-
text: 'Transcribing voice message',
|
|
777
|
-
timeout: 4_000,
|
|
778
|
-
})
|
|
779
|
-
|
|
780
|
-
await waitForBotMessageContaining({
|
|
781
|
-
discord,
|
|
782
|
-
threadId: thread.id,
|
|
783
|
-
userId: TEST_USER_ID,
|
|
784
|
-
text: 'Investigate the missing content type path',
|
|
785
|
-
timeout: 4_000,
|
|
786
|
-
})
|
|
787
|
-
|
|
788
|
-
await waitForFooterMessage({
|
|
789
|
-
discord,
|
|
790
|
-
threadId: thread.id,
|
|
791
|
-
timeout: 4_000,
|
|
792
|
-
})
|
|
793
|
-
|
|
794
|
-
const finalState = await waitForThreadState({
|
|
795
|
-
threadId: thread.id,
|
|
796
|
-
predicate: (state) => {
|
|
797
|
-
return Boolean(state.sessionId) && state.queueItems.length === 0
|
|
798
|
-
},
|
|
799
|
-
timeout: 4_000,
|
|
800
|
-
description: 'voice attachment without content type settled',
|
|
801
|
-
})
|
|
802
|
-
|
|
803
|
-
expect(await th.text()).toMatchInlineSnapshot(`
|
|
804
|
-
"--- from: user (voice-tester)
|
|
805
|
-
[attachment: voice-message.ogg]
|
|
806
|
-
--- from: assistant (TestBot)
|
|
807
|
-
🎤 Transcribing voice message...
|
|
808
|
-
📝 **Transcribed message:** Investigate the missing content type path
|
|
809
|
-
-# *using deterministic-provider/deterministic-v2*
|
|
810
|
-
> session-reply
|
|
811
|
-
-# *project ⋅ main ⋅ Ns ⋅ N% ⋅ deterministic-v2*"
|
|
812
|
-
`)
|
|
813
|
-
|
|
814
|
-
const messages = await waitForSessionMessages({
|
|
815
|
-
projectDirectory: directories.projectDirectory,
|
|
816
|
-
sessionID: finalState.sessionId!,
|
|
817
|
-
timeout: 4_000,
|
|
818
|
-
description: 'voice attachment without content type dispatched once',
|
|
819
|
-
predicate: (all) => {
|
|
820
|
-
const userTexts = getUserTexts(all)
|
|
821
|
-
return userTexts.some((text) => {
|
|
822
|
-
return text.includes('Investigate the missing content type path')
|
|
823
|
-
})
|
|
824
|
-
},
|
|
825
|
-
})
|
|
826
|
-
|
|
827
|
-
const userTexts = getUserTexts(messages)
|
|
828
|
-
expect(userTexts).not.toContain('')
|
|
829
|
-
expect(
|
|
830
|
-
userTexts.some((text) => {
|
|
831
|
-
return text.includes('Investigate the missing content type path')
|
|
832
|
-
}),
|
|
833
|
-
).toBe(true)
|
|
834
|
-
},
|
|
835
|
-
8_000,
|
|
836
|
-
)
|
|
837
|
-
|
|
838
|
-
// ── Test 2: Voice message in thread with idle session ──
|
|
839
|
-
|
|
840
|
-
test(
|
|
841
|
-
'voice message in thread with idle session starts new request',
|
|
842
|
-
async () => {
|
|
843
|
-
// 1. Create a session with a text message first
|
|
844
|
-
setDeterministicTranscription(null) // text message, no transcription
|
|
845
|
-
await discord.channel(TEXT_CHANNEL_ID).user(TEST_USER_ID).sendMessage({
|
|
846
|
-
content: 'FAST_RESPONSE_MARKER initial setup',
|
|
847
|
-
})
|
|
848
|
-
|
|
849
|
-
const thread = await discord.channel(TEXT_CHANNEL_ID).waitForThread({
|
|
850
|
-
timeout: 4_000,
|
|
851
|
-
predicate: (t) => {
|
|
852
|
-
return t.name?.includes('FAST_RESPONSE_MARKER') ?? false
|
|
853
|
-
},
|
|
854
|
-
})
|
|
855
|
-
|
|
856
|
-
const th = discord.thread(thread.id)
|
|
857
|
-
|
|
858
|
-
// Wait for the initial setup turn to fully complete before sending voice.
|
|
859
|
-
await waitForBotMessageContaining({
|
|
860
|
-
discord,
|
|
861
|
-
threadId: thread.id,
|
|
862
|
-
userId: TEST_USER_ID,
|
|
863
|
-
text: 'fast-response-done',
|
|
864
|
-
timeout: 4_000,
|
|
865
|
-
})
|
|
866
|
-
await waitForFooterMessage({
|
|
867
|
-
discord,
|
|
868
|
-
threadId: thread.id,
|
|
869
|
-
timeout: 4_000,
|
|
870
|
-
})
|
|
871
|
-
|
|
872
|
-
// 2. Now send a voice message to the idle session
|
|
873
|
-
setDeterministicTranscription({
|
|
874
|
-
transcription: 'Add error handling to the parser',
|
|
875
|
-
queueMessage: false,
|
|
876
|
-
})
|
|
877
|
-
|
|
878
|
-
await th.user(TEST_USER_ID).sendVoiceMessage()
|
|
879
|
-
|
|
880
|
-
// Bot should post transcription messages
|
|
881
|
-
await waitForBotMessageContaining({
|
|
882
|
-
discord,
|
|
883
|
-
threadId: thread.id,
|
|
884
|
-
userId: TEST_USER_ID,
|
|
885
|
-
text: 'Transcribing voice message',
|
|
886
|
-
timeout: 4_000,
|
|
887
|
-
})
|
|
888
|
-
|
|
889
|
-
await waitForBotMessageContaining({
|
|
890
|
-
discord,
|
|
891
|
-
threadId: thread.id,
|
|
892
|
-
userId: TEST_USER_ID,
|
|
893
|
-
text: 'Add error handling to the parser',
|
|
894
|
-
timeout: 4_000,
|
|
895
|
-
})
|
|
896
|
-
|
|
897
|
-
await waitForBotMessageContaining({
|
|
898
|
-
discord,
|
|
899
|
-
threadId: thread.id,
|
|
900
|
-
userId: TEST_USER_ID,
|
|
901
|
-
text: 'session-reply',
|
|
902
|
-
timeout: 4_000,
|
|
903
|
-
})
|
|
904
|
-
|
|
905
|
-
await waitForFooterMessage({
|
|
906
|
-
discord,
|
|
907
|
-
threadId: thread.id,
|
|
908
|
-
timeout: 4_000,
|
|
909
|
-
afterMessageIncludes: 'session-reply',
|
|
910
|
-
afterAuthorId: discord.botUserId,
|
|
911
|
-
})
|
|
912
|
-
|
|
913
|
-
const finalState = getThreadState(thread.id)
|
|
914
|
-
expect(await th.text()).toMatchInlineSnapshot(`
|
|
915
|
-
"--- from: user (voice-tester)
|
|
916
|
-
FAST_RESPONSE_MARKER initial setup
|
|
917
|
-
--- from: assistant (TestBot)
|
|
918
|
-
-# *using deterministic-provider/deterministic-v2*
|
|
919
|
-
> fast-response-done
|
|
920
|
-
-# *project ⋅ main ⋅ Ns ⋅ N% ⋅ deterministic-v2*
|
|
921
|
-
--- from: user (voice-tester)
|
|
922
|
-
[attachment: voice-message.ogg]
|
|
923
|
-
--- from: assistant (TestBot)
|
|
924
|
-
🎤 Transcribing voice message...
|
|
925
|
-
📝 **Transcribed message:** Add error handling to the parser
|
|
926
|
-
> session-reply
|
|
927
|
-
-# *project ⋅ main ⋅ Ns ⋅ N% ⋅ deterministic-v2*"
|
|
928
|
-
`)
|
|
929
|
-
expect(finalState?.sessionId).toBeDefined()
|
|
930
|
-
if (!finalState?.sessionId) {
|
|
931
|
-
throw new Error('Expected final state with sessionId')
|
|
932
|
-
}
|
|
933
|
-
expect(finalState.queueItems.length).toBe(0)
|
|
934
|
-
|
|
935
|
-
// Verify the same OpenCode session received both prompts:
|
|
936
|
-
// the initial text message AND the voice transcription
|
|
937
|
-
const messages = await waitForSessionMessages({
|
|
938
|
-
projectDirectory: directories.projectDirectory,
|
|
939
|
-
sessionID: finalState.sessionId,
|
|
940
|
-
timeout: 4_000,
|
|
941
|
-
description: 'idle session receives voice transcription prompt',
|
|
942
|
-
predicate: (all) => {
|
|
943
|
-
const userTexts = getUserTexts(all)
|
|
944
|
-
return (
|
|
945
|
-
userTexts.some((t) => t.includes('FAST_RESPONSE_MARKER initial setup')) &&
|
|
946
|
-
userTexts.some((t) => t.includes('Add error handling to the parser'))
|
|
947
|
-
)
|
|
948
|
-
},
|
|
949
|
-
})
|
|
950
|
-
const userTexts = getUserTexts(messages)
|
|
951
|
-
expect(userTexts.some((t) => t.includes('FAST_RESPONSE_MARKER initial setup'))).toBe(true)
|
|
952
|
-
expect(userTexts.some((t) => t.includes('Add error handling to the parser'))).toBe(true)
|
|
953
|
-
// Both prompts should have gotten assistant responses
|
|
954
|
-
const assistantTexts = getAssistantTexts(messages)
|
|
955
|
-
expect(assistantTexts.length).toBeGreaterThanOrEqual(2)
|
|
956
|
-
},
|
|
957
|
-
8_000,
|
|
958
|
-
)
|
|
959
|
-
|
|
960
|
-
// ── Test 3: Voice message queues behind running session (default) ──
|
|
961
|
-
|
|
962
|
-
test.skip(
|
|
963
|
-
'voice message with queueMessage=false queues behind running session',
|
|
964
|
-
async () => {
|
|
965
|
-
// 1. Start a session with a slow response
|
|
966
|
-
setDeterministicTranscription(null)
|
|
967
|
-
await discord.channel(TEXT_CHANNEL_ID).user(TEST_USER_ID).sendMessage({
|
|
968
|
-
content: 'SLOW_RESPONSE_MARKER start slow task',
|
|
969
|
-
})
|
|
970
|
-
|
|
971
|
-
const thread = await discord.channel(TEXT_CHANNEL_ID).waitForThread({
|
|
972
|
-
timeout: 4_000,
|
|
973
|
-
predicate: (t) => {
|
|
974
|
-
return t.name?.includes('SLOW_RESPONSE_MARKER') ?? false
|
|
975
|
-
},
|
|
976
|
-
})
|
|
977
|
-
|
|
978
|
-
const th = discord.thread(thread.id)
|
|
979
|
-
|
|
980
|
-
// 2. Send voice message while session is running (default: queue)
|
|
981
|
-
setDeterministicTranscription({
|
|
982
|
-
transcription: 'Stop and do this instead',
|
|
983
|
-
queueMessage: false,
|
|
984
|
-
})
|
|
985
|
-
|
|
986
|
-
await th.user(TEST_USER_ID).sendVoiceMessage()
|
|
987
|
-
|
|
988
|
-
// 3. Wait for transcription to appear first
|
|
989
|
-
await waitForBotMessageContaining({
|
|
990
|
-
discord,
|
|
991
|
-
threadId: thread.id,
|
|
992
|
-
userId: TEST_USER_ID,
|
|
993
|
-
text: 'Stop and do this instead',
|
|
994
|
-
timeout: 4_000,
|
|
995
|
-
})
|
|
996
|
-
|
|
997
|
-
// queueMessage=false no longer interrupts by default, so we should NOT
|
|
998
|
-
// receive the queued-position ack that queueMessage=true sends.
|
|
999
|
-
const afterTranscription = await th.getMessages()
|
|
1000
|
-
const hasQueuedAck = afterTranscription.some((m) => {
|
|
1001
|
-
return (
|
|
1002
|
-
m.author.id === discord.botUserId &&
|
|
1003
|
-
m.content.includes('Queued at position')
|
|
1004
|
-
)
|
|
1005
|
-
})
|
|
1006
|
-
expect(hasQueuedAck).toBe(false)
|
|
1007
|
-
|
|
1008
|
-
const midState = getThreadState(thread.id)
|
|
1009
|
-
expect(midState).toBeDefined()
|
|
1010
|
-
|
|
1011
|
-
// 4. Wait for both runs to finish (slow prompt + queued transcription)
|
|
1012
|
-
const finalState = await waitForThreadState({
|
|
1013
|
-
threadId: thread.id,
|
|
1014
|
-
predicate: (s) => {
|
|
1015
|
-
return s.queueItems.length === 0
|
|
1016
|
-
},
|
|
1017
|
-
timeout: 8_000,
|
|
1018
|
-
description: 'queue empty (default queued voice behavior)',
|
|
1019
|
-
})
|
|
1020
|
-
expect(finalState.sessionId).toBeDefined()
|
|
1021
|
-
expect(finalState.queueItems.length).toBe(0)
|
|
1022
|
-
|
|
1023
|
-
// Verify the OpenCode session processed both prompts sequentially.
|
|
1024
|
-
const messages = await waitForSessionMessages({
|
|
1025
|
-
projectDirectory: directories.projectDirectory,
|
|
1026
|
-
sessionID: finalState.sessionId!,
|
|
1027
|
-
timeout: 4_000,
|
|
1028
|
-
description: 'default queue: original prompt + voice prompt',
|
|
1029
|
-
predicate: (all) => {
|
|
1030
|
-
const userTexts = getUserTexts(all)
|
|
1031
|
-
const assistantTexts = getAssistantTexts(all)
|
|
1032
|
-
return (
|
|
1033
|
-
userTexts.some((t) => t.includes('SLOW_RESPONSE_MARKER start slow task')) &&
|
|
1034
|
-
userTexts.some((t) => t.includes('Stop and do this instead')) &&
|
|
1035
|
-
assistantTexts.some((t) => t.includes('slow-response-done')) &&
|
|
1036
|
-
assistantTexts.some((t) => t.includes('session-reply'))
|
|
1037
|
-
)
|
|
1038
|
-
},
|
|
1039
|
-
})
|
|
1040
|
-
const userTexts = getUserTexts(messages)
|
|
1041
|
-
// Both prompts were sent to the same session
|
|
1042
|
-
expect(userTexts.some((t) => t.includes('SLOW_RESPONSE_MARKER start slow task'))).toBe(true)
|
|
1043
|
-
expect(userTexts.some((t) => t.includes('Stop and do this instead'))).toBe(true)
|
|
1044
|
-
|
|
1045
|
-
const assistantTexts = getAssistantTexts(messages)
|
|
1046
|
-
expect(assistantTexts.some((t) => t.includes('slow-response-done'))).toBe(true)
|
|
1047
|
-
expect(assistantTexts.some((t) => t.includes('session-reply'))).toBe(true)
|
|
1048
|
-
},
|
|
1049
|
-
10_000,
|
|
1050
|
-
)
|
|
1051
|
-
|
|
1052
|
-
// ── Test 4: Voice message with queueMessage=true queues instead of interrupting ──
|
|
1053
|
-
|
|
1054
|
-
test(
|
|
1055
|
-
'voice message with queueMessage=true queues behind running session',
|
|
1056
|
-
async () => {
|
|
1057
|
-
// 1. Start a session with a slow response
|
|
1058
|
-
setDeterministicTranscription(null)
|
|
1059
|
-
await discord.channel(TEXT_CHANNEL_ID).user(TEST_USER_ID).sendMessage({
|
|
1060
|
-
content: 'SLOW_RESPONSE_MARKER start queued task',
|
|
1061
|
-
})
|
|
1062
|
-
|
|
1063
|
-
const thread = await discord.channel(TEXT_CHANNEL_ID).waitForThread({
|
|
1064
|
-
timeout: 4_000,
|
|
1065
|
-
predicate: (t) => {
|
|
1066
|
-
return t.name?.includes('start queued task') ?? false
|
|
1067
|
-
},
|
|
1068
|
-
})
|
|
1069
|
-
|
|
1070
|
-
const th = discord.thread(thread.id)
|
|
1071
|
-
|
|
1072
|
-
// 2. Send voice message with queueMessage=true (should NOT interrupt)
|
|
1073
|
-
setDeterministicTranscription({
|
|
1074
|
-
transcription: 'Queue this task for later',
|
|
1075
|
-
queueMessage: true,
|
|
1076
|
-
})
|
|
1077
|
-
|
|
1078
|
-
await waitForBotMessageContaining({
|
|
1079
|
-
discord,
|
|
1080
|
-
threadId: thread.id,
|
|
1081
|
-
userId: TEST_USER_ID,
|
|
1082
|
-
text: 'using deterministic-provider/deterministic-v2',
|
|
1083
|
-
timeout: 4_000,
|
|
1084
|
-
})
|
|
1085
|
-
|
|
1086
|
-
await th.user(TEST_USER_ID).sendVoiceMessage()
|
|
1087
|
-
|
|
1088
|
-
// 3. Transcription should appear, followed by queue notification
|
|
1089
|
-
await waitForBotMessageContaining({
|
|
1090
|
-
discord,
|
|
1091
|
-
threadId: thread.id,
|
|
1092
|
-
userId: TEST_USER_ID,
|
|
1093
|
-
text: 'Queue this task for later',
|
|
1094
|
-
timeout: 4_000,
|
|
1095
|
-
})
|
|
1096
|
-
|
|
1097
|
-
const messagesWithQueueAck = await waitForBotMessageContaining({
|
|
1098
|
-
discord,
|
|
1099
|
-
threadId: thread.id,
|
|
1100
|
-
userId: TEST_USER_ID,
|
|
1101
|
-
text: 'Queued at position',
|
|
1102
|
-
timeout: 4_000,
|
|
1103
|
-
})
|
|
1104
|
-
const queueAckMessage = messagesWithQueueAck.find((message) => {
|
|
1105
|
-
return (
|
|
1106
|
-
message.author.id === discord.botUserId
|
|
1107
|
-
&& message.content.includes('Queued at position')
|
|
1108
|
-
)
|
|
1109
|
-
})
|
|
1110
|
-
expect(queueAckMessage).toBeDefined()
|
|
1111
|
-
|
|
1112
|
-
// 4. queueMessage=true should not interrupt the in-flight response.
|
|
1113
|
-
await waitForBotMessageContaining({
|
|
1114
|
-
discord,
|
|
1115
|
-
threadId: thread.id,
|
|
1116
|
-
userId: TEST_USER_ID,
|
|
1117
|
-
text: 'slow-response-done',
|
|
1118
|
-
timeout: 4_000,
|
|
1119
|
-
})
|
|
1120
|
-
|
|
1121
|
-
if (!queueAckMessage) {
|
|
1122
|
-
throw new Error('Expected queue ack message')
|
|
1123
|
-
}
|
|
1124
|
-
|
|
1125
|
-
const dispatchPrefix = '» **voice-tester:** Voice message transcription from Discord user:'
|
|
1126
|
-
const messagesWithDispatch = await waitForBotMessageContaining({
|
|
1127
|
-
discord,
|
|
1128
|
-
threadId: thread.id,
|
|
1129
|
-
text: dispatchPrefix,
|
|
1130
|
-
afterMessageId: queueAckMessage.id,
|
|
1131
|
-
timeout: 8_000,
|
|
1132
|
-
})
|
|
1133
|
-
const dispatchMessage = messagesWithDispatch.find((message) => {
|
|
1134
|
-
return (
|
|
1135
|
-
message.author.id === discord.botUserId
|
|
1136
|
-
&& message.content.includes(dispatchPrefix)
|
|
1137
|
-
)
|
|
1138
|
-
})
|
|
1139
|
-
expect(dispatchMessage).toBeDefined()
|
|
1140
|
-
|
|
1141
|
-
if (!dispatchMessage) {
|
|
1142
|
-
throw new Error('Expected queued dispatch indicator message')
|
|
1143
|
-
}
|
|
1144
|
-
|
|
1145
|
-
await waitForBotMessageContaining({
|
|
1146
|
-
discord,
|
|
1147
|
-
threadId: thread.id,
|
|
1148
|
-
text: 'session-reply',
|
|
1149
|
-
afterMessageId: dispatchMessage.id,
|
|
1150
|
-
timeout: 8_000,
|
|
1151
|
-
})
|
|
1152
|
-
|
|
1153
|
-
// 5. Wait for the slow session to finish AND the queue to drain.
|
|
1154
|
-
// Using waitForThreadState with a compound predicate avoids matching
|
|
1155
|
-
// the transient 'idle' state from run A before run B starts.
|
|
1156
|
-
const finalState = await waitForThreadState({
|
|
1157
|
-
threadId: thread.id,
|
|
1158
|
-
predicate: (s) => {
|
|
1159
|
-
return s.queueItems.length === 0
|
|
1160
|
-
},
|
|
1161
|
-
timeout: 8_000,
|
|
1162
|
-
description: 'queue empty (both runs completed)',
|
|
1163
|
-
})
|
|
1164
|
-
|
|
1165
|
-
await waitForFooterMessage({
|
|
1166
|
-
discord,
|
|
1167
|
-
threadId: thread.id,
|
|
1168
|
-
timeout: 4_000,
|
|
1169
|
-
afterMessageIncludes: 'session-reply',
|
|
1170
|
-
afterAuthorId: discord.botUserId,
|
|
1171
|
-
})
|
|
1172
|
-
|
|
1173
|
-
expect(await th.text()).toMatchInlineSnapshot(`
|
|
1174
|
-
"--- from: user (voice-tester)
|
|
1175
|
-
SLOW_RESPONSE_MARKER start queued task
|
|
1176
|
-
--- from: assistant (TestBot)
|
|
1177
|
-
-# *using deterministic-provider/deterministic-v2*
|
|
1178
|
-
--- from: user (voice-tester)
|
|
1179
|
-
[attachment: voice-message.ogg]
|
|
1180
|
-
--- from: assistant (TestBot)
|
|
1181
|
-
🎤 Transcribing voice message...
|
|
1182
|
-
📝 **Transcribed message:** Queue this task for later
|
|
1183
|
-
-# Queued at position 1. Edit or delete your message to update the queue
|
|
1184
|
-
> slow-response-done
|
|
1185
|
-
-# *project ⋅ main ⋅ Ns ⋅ N% ⋅ deterministic-v2*
|
|
1186
|
-
» **voice-tester:** Voice message transcription from Discord user:
|
|
1187
|
-
Queue this task for later
|
|
1188
|
-
> session-reply
|
|
1189
|
-
-# *project ⋅ main ⋅ Ns ⋅ N% ⋅ deterministic-v2*"
|
|
1190
|
-
`)
|
|
1191
|
-
expect(finalState.queueItems.length).toBe(0)
|
|
1192
|
-
|
|
1193
|
-
// Verify the OpenCode session processed BOTH prompts sequentially:
|
|
1194
|
-
// the slow initial prompt completed, then the queued voice prompt ran
|
|
1195
|
-
const messages = await waitForSessionMessages({
|
|
1196
|
-
projectDirectory: directories.projectDirectory,
|
|
1197
|
-
sessionID: finalState.sessionId!,
|
|
1198
|
-
timeout: 4_000,
|
|
1199
|
-
description: 'queue: both prompts processed with responses',
|
|
1200
|
-
predicate: (all) => {
|
|
1201
|
-
const userTexts = getUserTexts(all)
|
|
1202
|
-
const assistantTexts = getAssistantTexts(all)
|
|
1203
|
-
return (
|
|
1204
|
-
userTexts.some((t) => t.includes('SLOW_RESPONSE_MARKER start queued task')) &&
|
|
1205
|
-
userTexts.some((t) => t.includes('Queue this task for later')) &&
|
|
1206
|
-
assistantTexts.some((t) => t.includes('slow-response-done')) &&
|
|
1207
|
-
assistantTexts.some((t) => t.includes('session-reply'))
|
|
1208
|
-
)
|
|
1209
|
-
},
|
|
1210
|
-
})
|
|
1211
|
-
const userTexts = getUserTexts(messages)
|
|
1212
|
-
const assistantTexts = getAssistantTexts(messages)
|
|
1213
|
-
// Both prompts sent to the session
|
|
1214
|
-
expect(userTexts.some((t) => t.includes('SLOW_RESPONSE_MARKER start queued task'))).toBe(true)
|
|
1215
|
-
expect(userTexts.some((t) => t.includes('Queue this task for later'))).toBe(true)
|
|
1216
|
-
// Both got responses (slow response + default reply for queued message)
|
|
1217
|
-
expect(assistantTexts.some((t) => t.includes('slow-response-done'))).toBe(true)
|
|
1218
|
-
expect(assistantTexts.some((t) => t.includes('session-reply'))).toBe(true)
|
|
1219
|
-
// No abort errors — the queue preserved the first run
|
|
1220
|
-
const abortedAssistant = messages.find((m) => {
|
|
1221
|
-
return m.info.role === 'assistant' && m.info.error?.name === 'MessageAbortedError'
|
|
1222
|
-
})
|
|
1223
|
-
expect(abortedAssistant).toBeUndefined()
|
|
1224
|
-
},
|
|
1225
|
-
12_000,
|
|
1226
|
-
)
|
|
1227
|
-
|
|
1228
|
-
// ── Test 5: Slow transcription finishes after session becomes idle (race condition) ──
|
|
1229
|
-
|
|
1230
|
-
test(
|
|
1231
|
-
'slow transcription completing after session finishes is handled correctly',
|
|
1232
|
-
async () => {
|
|
1233
|
-
// This tests the race condition where:
|
|
1234
|
-
// 1. Session starts with a fast response (~100ms)
|
|
1235
|
-
// 2. Voice message is sent simultaneously with slow transcription (500ms)
|
|
1236
|
-
// 3. The fast session finishes BEFORE transcription completes
|
|
1237
|
-
// 4. When transcription completes, the session is idle → should start new request
|
|
1238
|
-
|
|
1239
|
-
// 1. Start a session with a fast response
|
|
1240
|
-
setDeterministicTranscription(null)
|
|
1241
|
-
await discord.channel(TEXT_CHANNEL_ID).user(TEST_USER_ID).sendMessage({
|
|
1242
|
-
content: 'FAST_RESPONSE_MARKER quick task',
|
|
1243
|
-
})
|
|
1244
|
-
|
|
1245
|
-
const thread = await discord.channel(TEXT_CHANNEL_ID).waitForThread({
|
|
1246
|
-
timeout: 4_000,
|
|
1247
|
-
predicate: (t) => {
|
|
1248
|
-
return t.name?.includes('quick task') ?? false
|
|
1249
|
-
},
|
|
1250
|
-
})
|
|
1251
|
-
|
|
1252
|
-
const th = discord.thread(thread.id)
|
|
1253
|
-
|
|
1254
|
-
// Wait for the first run to complete before sending voice.
|
|
1255
|
-
await th.waitForBotReply({ timeout: 4_000 })
|
|
1256
|
-
await waitForFooterMessage({
|
|
1257
|
-
discord,
|
|
1258
|
-
threadId: thread.id,
|
|
1259
|
-
timeout: 4_000,
|
|
1260
|
-
})
|
|
1261
|
-
|
|
1262
|
-
// 2. Now send voice message with slow transcription
|
|
1263
|
-
// The fast response completes in ~100ms, but transcription takes 500ms.
|
|
1264
|
-
// By the time transcription returns, the session is already idle.
|
|
1265
|
-
setDeterministicTranscription({
|
|
1266
|
-
transcription: 'Delayed transcription result',
|
|
1267
|
-
queueMessage: false,
|
|
1268
|
-
delayMs: 500,
|
|
1269
|
-
})
|
|
1270
|
-
|
|
1271
|
-
await th.user(TEST_USER_ID).sendVoiceMessage()
|
|
1272
|
-
|
|
1273
|
-
// 3. The transcription should complete after the session finishes
|
|
1274
|
-
// and the transcribed message should be processed as a new request
|
|
1275
|
-
await waitForBotMessageContaining({
|
|
1276
|
-
discord,
|
|
1277
|
-
threadId: thread.id,
|
|
1278
|
-
userId: TEST_USER_ID,
|
|
1279
|
-
text: 'Delayed transcription result',
|
|
1280
|
-
timeout: 4_000,
|
|
1281
|
-
})
|
|
1282
|
-
|
|
1283
|
-
await waitForFooterMessage({
|
|
1284
|
-
discord,
|
|
1285
|
-
threadId: thread.id,
|
|
1286
|
-
timeout: 4_000,
|
|
1287
|
-
afterMessageIncludes: 'Delayed transcription result',
|
|
1288
|
-
afterAuthorId: discord.botUserId,
|
|
1289
|
-
})
|
|
1290
|
-
|
|
1291
|
-
// 4. Session should process the delayed transcription and settle.
|
|
1292
|
-
const finalState = await waitForThreadState({
|
|
1293
|
-
threadId: thread.id,
|
|
1294
|
-
predicate: (state) => {
|
|
1295
|
-
return Boolean(state.sessionId) && state.queueItems.length === 0
|
|
1296
|
-
},
|
|
1297
|
-
timeout: 4_000,
|
|
1298
|
-
description: 'delayed transcription settled with empty queue',
|
|
1299
|
-
})
|
|
1300
|
-
|
|
1301
|
-
expect(await th.text()).toMatchInlineSnapshot(`
|
|
1302
|
-
"--- from: user (voice-tester)
|
|
1303
|
-
FAST_RESPONSE_MARKER quick task
|
|
1304
|
-
--- from: assistant (TestBot)
|
|
1305
|
-
-# *using deterministic-provider/deterministic-v2*
|
|
1306
|
-
> fast-response-done
|
|
1307
|
-
-# *project ⋅ main ⋅ Ns ⋅ N% ⋅ deterministic-v2*
|
|
1308
|
-
--- from: user (voice-tester)
|
|
1309
|
-
[attachment: voice-message.ogg]
|
|
1310
|
-
--- from: assistant (TestBot)
|
|
1311
|
-
🎤 Transcribing voice message...
|
|
1312
|
-
📝 **Transcribed message:** Delayed transcription result
|
|
1313
|
-
> session-reply
|
|
1314
|
-
-# *project ⋅ main ⋅ Ns ⋅ N% ⋅ deterministic-v2*"
|
|
1315
|
-
`)
|
|
1316
|
-
expect(finalState.sessionId).toBeDefined()
|
|
1317
|
-
expect(finalState.queueItems.length).toBe(0)
|
|
1318
|
-
|
|
1319
|
-
// 5. Verify the OpenCode session processed both prompts on the same session:
|
|
1320
|
-
// the fast text message completed first, then the delayed voice transcription
|
|
1321
|
-
const sessionMessages = await waitForSessionMessages({
|
|
1322
|
-
projectDirectory: directories.projectDirectory,
|
|
1323
|
-
sessionID: finalState.sessionId!,
|
|
1324
|
-
timeout: 4_000,
|
|
1325
|
-
description: 'race: both prompts processed with responses on same session',
|
|
1326
|
-
predicate: (all) => {
|
|
1327
|
-
const userTexts = getUserTexts(all)
|
|
1328
|
-
const aTexts = getAssistantTexts(all)
|
|
1329
|
-
return (
|
|
1330
|
-
userTexts.some((t) => t.includes('FAST_RESPONSE_MARKER quick task')) &&
|
|
1331
|
-
userTexts.some((t) => t.includes('Delayed transcription result')) &&
|
|
1332
|
-
aTexts.length >= 2
|
|
1333
|
-
)
|
|
1334
|
-
},
|
|
1335
|
-
})
|
|
1336
|
-
const userTexts = getUserTexts(sessionMessages)
|
|
1337
|
-
expect(userTexts.some((t) => t.includes('FAST_RESPONSE_MARKER quick task'))).toBe(true)
|
|
1338
|
-
expect(userTexts.some((t) => t.includes('Delayed transcription result'))).toBe(true)
|
|
1339
|
-
// Both prompts got assistant responses (no aborts — second arrived after first finished)
|
|
1340
|
-
const assistantTexts = getAssistantTexts(sessionMessages)
|
|
1341
|
-
expect(assistantTexts.length).toBeGreaterThanOrEqual(2)
|
|
1342
|
-
const abortedAssistant = sessionMessages.find((m) => {
|
|
1343
|
-
return m.info.role === 'assistant' && m.info.error?.name === 'MessageAbortedError'
|
|
1344
|
-
})
|
|
1345
|
-
expect(abortedAssistant).toBeUndefined()
|
|
1346
|
-
},
|
|
1347
|
-
8_000,
|
|
1348
|
-
)
|
|
1349
|
-
|
|
1350
|
-
// ── Test 6: Slow transcription with queueMessage=true arriving after idle queue drain ──
|
|
1351
|
-
|
|
1352
|
-
test(
|
|
1353
|
-
'slow queued transcription completing after session idle is dispatched',
|
|
1354
|
-
async () => {
|
|
1355
|
-
// Reproduces the bug where a voice message with queueMessage=true has
|
|
1356
|
-
// slow transcription that completes after the session is already idle.
|
|
1357
|
-
// The message gets inserted into the local queue but was never drained
|
|
1358
|
-
// because handleSessionIdle() didn't call tryDrainQueue().
|
|
1359
|
-
//
|
|
1360
|
-
// 1. Send a fast text message → session starts and finishes quickly
|
|
1361
|
-
// 2. Send a voice message with queueMessage=true and slow transcription
|
|
1362
|
-
// 3. Fast session finishes → handleSessionIdle fires
|
|
1363
|
-
// 4. Transcription completes → enqueueViaLocalQueue adds item
|
|
1364
|
-
// 5. tryDrainQueue in handleSessionIdle should pick it up
|
|
1365
|
-
|
|
1366
|
-
// 1. Start a session with a fast response
|
|
1367
|
-
setDeterministicTranscription(null)
|
|
1368
|
-
await discord.channel(TEXT_CHANNEL_ID).user(TEST_USER_ID).sendMessage({
|
|
1369
|
-
content: 'FAST_RESPONSE_MARKER fast before queued voice',
|
|
1370
|
-
})
|
|
1371
|
-
|
|
1372
|
-
const thread = await discord.channel(TEXT_CHANNEL_ID).waitForThread({
|
|
1373
|
-
timeout: 4_000,
|
|
1374
|
-
predicate: (t) => {
|
|
1375
|
-
return t.name?.includes('fast before queued voice') ?? false
|
|
1376
|
-
},
|
|
1377
|
-
})
|
|
1378
|
-
|
|
1379
|
-
const th = discord.thread(thread.id)
|
|
1380
|
-
|
|
1381
|
-
// Wait for the first run to fully complete before sending the queued voice message.
|
|
1382
|
-
await th.waitForBotReply({ timeout: 4_000 })
|
|
1383
|
-
await waitForFooterMessage({
|
|
1384
|
-
discord,
|
|
1385
|
-
threadId: thread.id,
|
|
1386
|
-
timeout: 4_000,
|
|
1387
|
-
})
|
|
1388
|
-
|
|
1389
|
-
// 2. Send voice message with queueMessage=true AND slow transcription.
|
|
1390
|
-
// Session is already idle when this arrives. The transcription delay
|
|
1391
|
-
// means the message enters the local queue after idle has fired.
|
|
1392
|
-
setDeterministicTranscription({
|
|
1393
|
-
transcription: 'Queued voice after idle',
|
|
1394
|
-
queueMessage: true,
|
|
1395
|
-
delayMs: 500,
|
|
1396
|
-
})
|
|
1397
|
-
|
|
1398
|
-
await th.user(TEST_USER_ID).sendVoiceMessage()
|
|
1399
|
-
|
|
1400
|
-
// 3. The transcription should complete, and even though queueMessage=true
|
|
1401
|
-
// routes through the local queue, the item should be drained immediately
|
|
1402
|
-
// because the session is idle. No dispatch indicator (» prefix) appears
|
|
1403
|
-
// because the message is dispatched immediately by enqueueViaLocalQueue's
|
|
1404
|
-
// tryDrainQueue (showIndicator=false for first drain).
|
|
1405
|
-
await waitForBotMessageContaining({
|
|
1406
|
-
discord,
|
|
1407
|
-
threadId: thread.id,
|
|
1408
|
-
userId: TEST_USER_ID,
|
|
1409
|
-
text: 'Queued voice after idle',
|
|
1410
|
-
timeout: 4_000,
|
|
1411
|
-
})
|
|
1412
|
-
|
|
1413
|
-
// Wait for the queued message response and footer
|
|
1414
|
-
await waitForBotMessageContaining({
|
|
1415
|
-
discord,
|
|
1416
|
-
threadId: thread.id,
|
|
1417
|
-
text: 'session-reply',
|
|
1418
|
-
timeout: 4_000,
|
|
1419
|
-
})
|
|
1420
|
-
|
|
1421
|
-
await waitForFooterMessage({
|
|
1422
|
-
discord,
|
|
1423
|
-
threadId: thread.id,
|
|
1424
|
-
timeout: 4_000,
|
|
1425
|
-
afterMessageIncludes: 'session-reply',
|
|
1426
|
-
afterAuthorId: discord.botUserId,
|
|
1427
|
-
})
|
|
1428
|
-
|
|
1429
|
-
// 4. Final state: queue should be empty
|
|
1430
|
-
const finalState = await waitForThreadState({
|
|
1431
|
-
threadId: thread.id,
|
|
1432
|
-
predicate: (state) => {
|
|
1433
|
-
return Boolean(state.sessionId) && state.queueItems.length === 0
|
|
1434
|
-
},
|
|
1435
|
-
timeout: 4_000,
|
|
1436
|
-
description: 'queued voice after idle settled with empty queue',
|
|
1437
|
-
})
|
|
1438
|
-
|
|
1439
|
-
expect(await th.text()).toMatchInlineSnapshot(`
|
|
1440
|
-
"--- from: user (voice-tester)
|
|
1441
|
-
FAST_RESPONSE_MARKER fast before queued voice
|
|
1442
|
-
--- from: assistant (TestBot)
|
|
1443
|
-
-# *using deterministic-provider/deterministic-v2*
|
|
1444
|
-
> fast-response-done
|
|
1445
|
-
-# *project ⋅ main ⋅ Ns ⋅ N% ⋅ deterministic-v2*
|
|
1446
|
-
--- from: user (voice-tester)
|
|
1447
|
-
[attachment: voice-message.ogg]
|
|
1448
|
-
--- from: assistant (TestBot)
|
|
1449
|
-
🎤 Transcribing voice message...
|
|
1450
|
-
📝 **Transcribed message:** Queued voice after idle
|
|
1451
|
-
> session-reply
|
|
1452
|
-
-# *project ⋅ main ⋅ Ns ⋅ N% ⋅ deterministic-v2*"
|
|
1453
|
-
`)
|
|
1454
|
-
expect(finalState.sessionId).toBeDefined()
|
|
1455
|
-
expect(finalState.queueItems.length).toBe(0)
|
|
1456
|
-
|
|
1457
|
-
// 5. Verify the OpenCode session processed both prompts
|
|
1458
|
-
const sessionMessages = await waitForSessionMessages({
|
|
1459
|
-
projectDirectory: directories.projectDirectory,
|
|
1460
|
-
sessionID: finalState.sessionId!,
|
|
1461
|
-
timeout: 4_000,
|
|
1462
|
-
description: 'queued-voice-idle: both prompts processed',
|
|
1463
|
-
predicate: (all) => {
|
|
1464
|
-
const userTexts = getUserTexts(all)
|
|
1465
|
-
const aTexts = getAssistantTexts(all)
|
|
1466
|
-
return (
|
|
1467
|
-
userTexts.some((t) => t.includes('FAST_RESPONSE_MARKER fast before queued voice')) &&
|
|
1468
|
-
userTexts.some((t) => t.includes('Queued voice after idle')) &&
|
|
1469
|
-
aTexts.length >= 2
|
|
1470
|
-
)
|
|
1471
|
-
},
|
|
1472
|
-
})
|
|
1473
|
-
const userTexts = getUserTexts(sessionMessages)
|
|
1474
|
-
expect(userTexts.some((t) => t.includes('FAST_RESPONSE_MARKER fast before queued voice'))).toBe(true)
|
|
1475
|
-
expect(userTexts.some((t) => t.includes('Queued voice after idle'))).toBe(true)
|
|
1476
|
-
const assistantTexts = getAssistantTexts(sessionMessages)
|
|
1477
|
-
expect(assistantTexts.length).toBeGreaterThanOrEqual(2)
|
|
1478
|
-
},
|
|
1479
|
-
10_000,
|
|
1480
|
-
)
|
|
1481
|
-
|
|
1482
|
-
test.skipIf(!process.env.OPENAI_API_KEY)(
|
|
1483
|
-
'transcribes a real ogg voice note and starts a session',
|
|
1484
|
-
async () => {
|
|
1485
|
-
const audioPath = path.resolve(process.cwd(), 'scripts', 'example-audio.ogg')
|
|
1486
|
-
const audio = fs.readFileSync(audioPath)
|
|
1487
|
-
const server = http.createServer((req, res) => {
|
|
1488
|
-
if (req.url !== '/voice-message.ogg') {
|
|
1489
|
-
res.writeHead(404).end()
|
|
1490
|
-
return
|
|
1491
|
-
}
|
|
1492
|
-
res.writeHead(200, { 'content-type': 'audio/ogg' })
|
|
1493
|
-
res.end(audio)
|
|
1494
|
-
})
|
|
1495
|
-
await new Promise<void>((resolve) => {
|
|
1496
|
-
server.listen(0, '127.0.0.1', resolve)
|
|
1497
|
-
})
|
|
1498
|
-
const address = server.address()
|
|
1499
|
-
if (!address || typeof address === 'string') throw new Error('Audio server did not bind')
|
|
1500
|
-
const audioUrl = `http://127.0.0.1:${address.port}/voice-message.ogg`
|
|
1501
|
-
|
|
1502
|
-
setDeterministicTranscription(null)
|
|
1503
|
-
await discord.channel(TEXT_CHANNEL_ID).user(TEST_USER_ID).sendVoiceMessage({ url: audioUrl })
|
|
1504
|
-
|
|
1505
|
-
const thread = await discord.channel(TEXT_CHANNEL_ID).waitForThread({
|
|
1506
|
-
timeout: 20_000,
|
|
1507
|
-
predicate: (candidate) => Boolean(candidate.name?.toLowerCase().includes('hear me')),
|
|
1508
|
-
})
|
|
1509
|
-
await waitForFooterMessage({ discord, threadId: thread.id, timeout: 20_000 })
|
|
1510
|
-
expect(await discord.thread(thread.id).text()).toMatchInlineSnapshot(`
|
|
1511
|
-
"--- from: user (voice-tester)
|
|
1512
|
-
[attachment: voice-message.ogg]
|
|
1513
|
-
--- from: assistant (TestBot)
|
|
1514
|
-
🎤 Transcribing voice message...
|
|
1515
|
-
📝 **Transcribed message:** Yo, can you hear me loud and clear? What a shit model. Why are you so slow?
|
|
1516
|
-
-# *using deterministic-provider/deterministic-v2*
|
|
1517
|
-
> session-reply
|
|
1518
|
-
-# *project ⋅ main ⋅ Ns ⋅ N% ⋅ deterministic-v2* <@300000000000000777>"
|
|
1519
|
-
`)
|
|
1520
|
-
server.close()
|
|
1521
|
-
},
|
|
1522
|
-
30_000,
|
|
1523
|
-
)
|
|
1524
|
-
})
|