kimaki 0.31.0 → 1.1.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 -223
- 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 +87 -0
- package/dist/autostart.js.map +1 -0
- package/dist/bot.d.ts +91 -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/builtin-skills.d.ts +18 -0
- package/dist/builtin-skills.js +51 -0
- package/dist/builtin-skills.js.map +1 -0
- package/dist/builtin-skills.test.d.ts +1 -0
- package/dist/builtin-skills.test.js +28 -0
- package/dist/builtin-skills.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 +434 -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 +245 -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 +82 -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 +86 -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 +565 -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 +351 -682
- 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 +315 -0
- package/dist/effects.js.map +1 -0
- package/dist/errors.d.ts +38 -0
- package/dist/errors.js +40 -175
- package/dist/errors.js.map +1 -0
- package/dist/event-loop.d.ts +41 -0
- package/dist/event-loop.js +418 -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 +33 -0
- package/dist/main.js +261 -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 +417 -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 +124 -0
- package/dist/plugin/index.js.map +1 -0
- package/dist/plugin/read-media.d.ts +23 -0
- package/dist/plugin/read-media.js +72 -0
- package/dist/plugin/read-media.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 +92 -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 +577 -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 +83 -0
- package/dist/session-events.js +260 -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 +663 -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 +476 -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 +76 -0
- package/dist/system-prompt.js +852 -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 +256 -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 +619 -717
- 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 +38 -73
- package/skills/kimaki-debug/SKILL.md +44 -0
- package/skills/kimaki-tunnel/SKILL.md +34 -0
- 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 -258
- package/src/autostart.ts +90 -0
- package/src/bot.ts +196 -0
- package/src/btw.e2e.test.ts +130 -0
- package/src/builtin-skills.test.ts +29 -0
- package/src/builtin-skills.ts +59 -0
- package/src/catalog-refresh.e2e.test.ts +112 -0
- package/src/cli/bot.ts +425 -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 +252 -0
- package/src/cli/shared.ts +139 -0
- package/src/cli-discord.e2e.test.ts +79 -0
- package/src/cli-lock.e2e.test.ts +530 -0
- package/src/cli-read.e2e.test.ts +83 -0
- package/src/cli-sessions.e2e.test.ts +120 -0
- package/src/cli.ts +35 -418
- package/src/commands/login-commands.ts +360 -0
- package/src/commands/preference-commands.ts +599 -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 +344 -759
- package/src/db.ts +66 -284
- package/src/duration.ts +18 -0
- package/src/effects.ts +337 -0
- package/src/errors.ts +39 -228
- package/src/event-loop.ts +471 -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 +238 -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 +464 -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 +112 -0
- package/src/plugin.e2e.test.ts +117 -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 +301 -0
- package/src/session-ops.e2e.test.ts +141 -0
- package/src/sessions.ts +804 -0
- package/src/shell.e2e.test.ts +173 -0
- package/src/slash-commands.test.ts +89 -0
- package/src/slash-commands.ts +540 -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 +923 -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 +301 -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 +668 -988
- 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 -1223
- 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 -24
- package/dist/btw-prefix-detection.test.js +0 -130
- 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 -1591
- package/dist/cli-runner.test.js +0 -119
- 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 -275
- package/dist/commands/compact.js +0 -117
- package/dist/commands/context-usage.js +0 -194
- package/dist/commands/context-usage.test.js +0 -49
- 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 -291
- 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 -1012
- package/dist/commands/multioauth.js +0 -425
- package/dist/commands/new-worktree.js +0 -625
- package/dist/commands/paginated-select.js +0 -57
- package/dist/commands/permissions.js +0 -327
- package/dist/commands/queue.js +0 -347
- 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 -248
- 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 -1432
- 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 -788
- package/dist/message-formatting.test.js +0 -1132
- package/dist/message-preprocessing.js +0 -419
- 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 -216
- package/dist/queue-drain-after-interactive-ui.e2e.test.js +0 -120
- package/dist/queue-interrupt-drain.e2e.test.js +0 -256
- 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 -65
- package/dist/session-handler/event-stream-state.js +0 -1225
- package/dist/session-handler/event-stream-state.test.js +0 -3160
- 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 -4320
- 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 -806
- package/dist/system-message.test.js +0 -917
- 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 -385
- package/dist/thinking-utils.js +0 -45
- package/dist/thinking-utils.test.js +0 -35
- package/dist/thread-message-queue.e2e.test.js +0 -1227
- 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 -727
- package/dist/voice-message.e2e.test.js +0 -1254
- package/dist/voice-routing-input.e2e.test.js +0 -195
- package/dist/voice-routing-question.e2e.test.js +0 -182
- package/dist/voice.test.js +0 -500
- 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 -116
- 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 -102
- 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 -1557
- 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 -142
- package/src/btw-prefix-detection.ts +0 -30
- 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 -885
- 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 -166
- package/src/cli-runner.ts +0 -2157
- 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 -356
- package/src/commands/compact.ts +0 -154
- package/src/commands/context-usage.test.ts +0 -57
- package/src/commands/context-usage.ts +0 -267
- 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 -421
- 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 -1377
- package/src/commands/multioauth.ts +0 -508
- package/src/commands/new-worktree.ts +0 -835
- 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 -471
- 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 -319
- 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 -1852
- 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 -999
- package/src/message-preprocessing.ts +0 -557
- 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 -262
- package/src/queue-drain-after-interactive-ui.e2e.test.ts +0 -152
- package/src/queue-interrupt-drain.e2e.test.ts +0 -299
- package/src/queue-question-select-drain.e2e.test.ts +0 -209
- 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 -104
- 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 -3431
- package/src/session-handler/event-stream-state.ts +0 -1792
- 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 -358
- package/src/session-handler/thread-session-runtime-summary.test.ts +0 -169
- package/src/session-handler/thread-session-runtime.ts +0 -5696
- 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 -1013
- package/src/system-message.ts +0 -1043
- 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 -549
- package/src/thinking-utils.test.ts +0 -46
- package/src/thinking-utils.ts +0 -82
- package/src/thread-message-queue.e2e.test.ts +0 -1468
- 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 -1000
- package/src/voice-message.e2e.test.ts +0 -1513
- package/src/voice-routing-input.e2e.test.ts +0 -203
- package/src/voice-routing-question.e2e.test.ts +0 -188
- package/src/voice.test.ts +0 -565
- 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,1468 +0,0 @@
|
|
|
1
|
-
// E2e tests for basic per-thread message queue ordering.
|
|
2
|
-
// Advanced interrupt/abort/retry tests are in thread-queue-advanced.e2e.test.ts.
|
|
3
|
-
//
|
|
4
|
-
// Uses opencode-deterministic-provider which returns canned responses instantly
|
|
5
|
-
// (no real LLM calls), so poll timeouts can be aggressive (4s). The only real
|
|
6
|
-
// latency is OpenCode server startup (beforeAll) and intentional partDelaysMs
|
|
7
|
-
// in matchers (100ms for user-reply).
|
|
8
|
-
//
|
|
9
|
-
// If total duration of a file exceeds ~10s, split into a new test file
|
|
10
|
-
// so vitest can parallelize across files.
|
|
11
|
-
|
|
12
|
-
import fs from 'node:fs'
|
|
13
|
-
import path from 'node:path'
|
|
14
|
-
import url from 'node:url'
|
|
15
|
-
import { describe, beforeAll, afterAll, test, expect } from 'vitest'
|
|
16
|
-
import { ChannelType, Client, GatewayIntentBits, Partials } from 'discord.js'
|
|
17
|
-
import { DigitalDiscord } from 'discord-digital-twin/src'
|
|
18
|
-
import {
|
|
19
|
-
buildDeterministicOpencodeConfig,
|
|
20
|
-
type DeterministicMatcher,
|
|
21
|
-
} from 'opencode-deterministic-provider'
|
|
22
|
-
import {
|
|
23
|
-
setDataDir,
|
|
24
|
-
} from './config.js'
|
|
25
|
-
import { store } from './store.js'
|
|
26
|
-
import { startDiscordBot } from './discord-bot.js'
|
|
27
|
-
import {
|
|
28
|
-
setBotToken,
|
|
29
|
-
initDatabase,
|
|
30
|
-
closeDatabase,
|
|
31
|
-
setChannelDirectory,
|
|
32
|
-
setChannelVerbosity,
|
|
33
|
-
getChannelVerbosity,
|
|
34
|
-
type VerbosityLevel,
|
|
35
|
-
} from './database.js'
|
|
36
|
-
import { startHranaServer, stopHranaServer } from './hrana-server.js'
|
|
37
|
-
import { initializeOpencodeForDirectory, stopOpencodeServer } from './opencode.js'
|
|
38
|
-
import {
|
|
39
|
-
chooseLockPort,
|
|
40
|
-
cleanupTestSessions,
|
|
41
|
-
initTestGitRepo,
|
|
42
|
-
waitForFooterMessage,
|
|
43
|
-
waitForBotMessageContaining,
|
|
44
|
-
waitForMessageById,
|
|
45
|
-
waitForBotMessageCount,
|
|
46
|
-
waitForBotReplyAfterUserMessage,
|
|
47
|
-
waitForBotReplyTo,
|
|
48
|
-
waitForThreadState,
|
|
49
|
-
getMessageVisibleText,
|
|
50
|
-
} from './test-utils.js'
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
const e2eTest = describe
|
|
54
|
-
|
|
55
|
-
function createRunDirectories() {
|
|
56
|
-
const root = path.resolve(process.cwd(), 'tmp', 'thread-queue-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
|
-
function createDeterministicMatchers() {
|
|
89
|
-
const bashCreateFileMatcher: DeterministicMatcher = {
|
|
90
|
-
id: 'bash-create-file',
|
|
91
|
-
priority: 130,
|
|
92
|
-
when: {
|
|
93
|
-
lastMessageRole: 'user',
|
|
94
|
-
rawPromptIncludes: 'BASH_TOOL_FILE_MARKER',
|
|
95
|
-
},
|
|
96
|
-
then: {
|
|
97
|
-
parts: [
|
|
98
|
-
{ type: 'stream-start', warnings: [] },
|
|
99
|
-
{ type: 'text-start', id: 'bash-create-file' },
|
|
100
|
-
{
|
|
101
|
-
type: 'text-delta',
|
|
102
|
-
id: 'bash-create-file',
|
|
103
|
-
delta: 'running create file',
|
|
104
|
-
},
|
|
105
|
-
{ type: 'text-end', id: 'bash-create-file' },
|
|
106
|
-
{ type: 'text-start', id: 'bash-create-file-status' },
|
|
107
|
-
{
|
|
108
|
-
type: 'text-delta',
|
|
109
|
-
id: 'bash-create-file-status',
|
|
110
|
-
delta: 'creating marker',
|
|
111
|
-
},
|
|
112
|
-
{ type: 'text-end', id: 'bash-create-file-status' },
|
|
113
|
-
{
|
|
114
|
-
type: 'tool-call',
|
|
115
|
-
toolCallId: 'bash-create-file-call',
|
|
116
|
-
toolName: 'bash',
|
|
117
|
-
input: JSON.stringify({
|
|
118
|
-
command: 'mkdir -p tmp && printf "created" > tmp/bash-tool-executed.txt',
|
|
119
|
-
description: 'Create marker file for e2e test',
|
|
120
|
-
hasSideEffect: true,
|
|
121
|
-
}),
|
|
122
|
-
},
|
|
123
|
-
{
|
|
124
|
-
type: 'finish',
|
|
125
|
-
finishReason: 'tool-calls',
|
|
126
|
-
usage: {
|
|
127
|
-
inputTokens: 1,
|
|
128
|
-
outputTokens: 1,
|
|
129
|
-
totalTokens: 2,
|
|
130
|
-
},
|
|
131
|
-
},
|
|
132
|
-
],
|
|
133
|
-
},
|
|
134
|
-
}
|
|
135
|
-
|
|
136
|
-
const bashCreateFileFollowupMatcher: DeterministicMatcher = {
|
|
137
|
-
id: 'bash-create-file-followup',
|
|
138
|
-
priority: 120,
|
|
139
|
-
when: {
|
|
140
|
-
lastMessageRole: 'tool',
|
|
141
|
-
rawPromptIncludes: 'BASH_TOOL_FILE_MARKER',
|
|
142
|
-
},
|
|
143
|
-
then: {
|
|
144
|
-
parts: [
|
|
145
|
-
{ type: 'stream-start', warnings: [] },
|
|
146
|
-
{ type: 'text-start', id: 'bash-create-file-followup' },
|
|
147
|
-
{
|
|
148
|
-
type: 'text-delta',
|
|
149
|
-
id: 'bash-create-file-followup',
|
|
150
|
-
delta: 'file created',
|
|
151
|
-
},
|
|
152
|
-
{ type: 'text-end', id: 'bash-create-file-followup' },
|
|
153
|
-
{
|
|
154
|
-
type: 'finish',
|
|
155
|
-
finishReason: 'stop',
|
|
156
|
-
usage: {
|
|
157
|
-
inputTokens: 1,
|
|
158
|
-
outputTokens: 1,
|
|
159
|
-
totalTokens: 2,
|
|
160
|
-
},
|
|
161
|
-
},
|
|
162
|
-
],
|
|
163
|
-
},
|
|
164
|
-
}
|
|
165
|
-
|
|
166
|
-
const raceFinalReplyMatcher: DeterministicMatcher = {
|
|
167
|
-
id: 'race-final-reply',
|
|
168
|
-
priority: 110,
|
|
169
|
-
when: {
|
|
170
|
-
latestUserTextIncludes: 'Reply with exactly: race-final',
|
|
171
|
-
},
|
|
172
|
-
then: {
|
|
173
|
-
parts: [
|
|
174
|
-
{ type: 'stream-start', warnings: [] },
|
|
175
|
-
{ type: 'text-start', id: 'race-final' },
|
|
176
|
-
{ type: 'text-delta', id: 'race-final', delta: 'race-final' },
|
|
177
|
-
{ type: 'text-end', id: 'race-final' },
|
|
178
|
-
{
|
|
179
|
-
type: 'finish',
|
|
180
|
-
finishReason: 'stop',
|
|
181
|
-
usage: {
|
|
182
|
-
inputTokens: 1,
|
|
183
|
-
outputTokens: 1,
|
|
184
|
-
totalTokens: 2,
|
|
185
|
-
},
|
|
186
|
-
},
|
|
187
|
-
],
|
|
188
|
-
// Delay first output to widen the stale-idle window. The race happens
|
|
189
|
-
// in <1ms; 500ms is plenty to keep the window reliably open.
|
|
190
|
-
partDelaysMs: [0, 500, 0, 0, 0],
|
|
191
|
-
},
|
|
192
|
-
}
|
|
193
|
-
|
|
194
|
-
// Slow matcher for "hotel" so the 200ms sleep in the queueing test
|
|
195
|
-
// guarantees "india" arrives while hotel is still streaming.
|
|
196
|
-
const hotelSlowMatcher: DeterministicMatcher = {
|
|
197
|
-
id: 'hotel-slow-reply',
|
|
198
|
-
priority: 20,
|
|
199
|
-
when: {
|
|
200
|
-
latestUserTextIncludes: 'Reply with exactly: hotel',
|
|
201
|
-
},
|
|
202
|
-
then: {
|
|
203
|
-
parts: [
|
|
204
|
-
{ type: 'stream-start', warnings: [] },
|
|
205
|
-
{ type: 'text-start', id: 'hotel-reply' },
|
|
206
|
-
{ type: 'text-delta', id: 'hotel-reply', delta: 'ok' },
|
|
207
|
-
{ type: 'text-end', id: 'hotel-reply' },
|
|
208
|
-
{
|
|
209
|
-
type: 'finish',
|
|
210
|
-
finishReason: 'stop',
|
|
211
|
-
usage: { inputTokens: 1, outputTokens: 1, totalTokens: 2 },
|
|
212
|
-
},
|
|
213
|
-
],
|
|
214
|
-
partDelaysMs: [0, 100, 300, 0, 0],
|
|
215
|
-
},
|
|
216
|
-
}
|
|
217
|
-
|
|
218
|
-
const userReplyMatcher: DeterministicMatcher = {
|
|
219
|
-
id: 'user-reply',
|
|
220
|
-
priority: 10,
|
|
221
|
-
when: {
|
|
222
|
-
lastMessageRole: 'user',
|
|
223
|
-
rawPromptIncludes: 'Reply with exactly:',
|
|
224
|
-
},
|
|
225
|
-
then: {
|
|
226
|
-
parts: [
|
|
227
|
-
{ type: 'stream-start', warnings: [] },
|
|
228
|
-
{ type: 'text-start', id: 'default-reply' },
|
|
229
|
-
{ type: 'text-delta', id: 'default-reply', delta: 'ok' },
|
|
230
|
-
{ type: 'text-end', id: 'default-reply' },
|
|
231
|
-
{
|
|
232
|
-
type: 'finish',
|
|
233
|
-
finishReason: 'stop',
|
|
234
|
-
usage: {
|
|
235
|
-
inputTokens: 1,
|
|
236
|
-
outputTokens: 1,
|
|
237
|
-
totalTokens: 2,
|
|
238
|
-
},
|
|
239
|
-
},
|
|
240
|
-
],
|
|
241
|
-
partDelaysMs: [0, 100, 0, 0, 0],
|
|
242
|
-
},
|
|
243
|
-
}
|
|
244
|
-
|
|
245
|
-
// Matcher that keeps the session busy for 2s after the first text part.
|
|
246
|
-
// Used by queue-edit tests to ensure the follow-up message lands in the
|
|
247
|
-
// local queue before the session finishes.
|
|
248
|
-
const slowBusyMatcher: DeterministicMatcher = {
|
|
249
|
-
id: 'slow-busy-reply',
|
|
250
|
-
priority: 115,
|
|
251
|
-
when: {
|
|
252
|
-
latestUserTextIncludes: 'SLOW_BUSY_MARKER',
|
|
253
|
-
},
|
|
254
|
-
then: {
|
|
255
|
-
parts: [
|
|
256
|
-
{ type: 'stream-start', warnings: [] },
|
|
257
|
-
{ type: 'text-start', id: 'slow-busy' },
|
|
258
|
-
{ type: 'text-delta', id: 'slow-busy', delta: 'slow-busy-reply' },
|
|
259
|
-
{ type: 'text-end', id: 'slow-busy' },
|
|
260
|
-
{
|
|
261
|
-
type: 'finish',
|
|
262
|
-
finishReason: 'stop',
|
|
263
|
-
usage: { inputTokens: 1, outputTokens: 1, totalTokens: 2 },
|
|
264
|
-
},
|
|
265
|
-
],
|
|
266
|
-
partDelaysMs: [0, 100, 0, 0, 2_000],
|
|
267
|
-
},
|
|
268
|
-
}
|
|
269
|
-
|
|
270
|
-
return [
|
|
271
|
-
bashCreateFileMatcher,
|
|
272
|
-
bashCreateFileFollowupMatcher,
|
|
273
|
-
raceFinalReplyMatcher,
|
|
274
|
-
slowBusyMatcher,
|
|
275
|
-
hotelSlowMatcher,
|
|
276
|
-
userReplyMatcher,
|
|
277
|
-
]
|
|
278
|
-
}
|
|
279
|
-
|
|
280
|
-
const TEST_USER_ID = '200000000000000777'
|
|
281
|
-
const TEXT_CHANNEL_ID = '200000000000000778'
|
|
282
|
-
|
|
283
|
-
e2eTest('thread message queue ordering', () => {
|
|
284
|
-
let directories: ReturnType<typeof createRunDirectories>
|
|
285
|
-
let discord: DigitalDiscord
|
|
286
|
-
let botClient: Client
|
|
287
|
-
let previousDefaultVerbosity: VerbosityLevel | null =
|
|
288
|
-
null
|
|
289
|
-
let testStartTime = Date.now()
|
|
290
|
-
|
|
291
|
-
beforeAll(async () => {
|
|
292
|
-
testStartTime = Date.now()
|
|
293
|
-
directories = createRunDirectories()
|
|
294
|
-
const lockPort = chooseLockPort({ key: TEXT_CHANNEL_ID })
|
|
295
|
-
|
|
296
|
-
process.env['KIMAKI_LOCK_PORT'] = String(lockPort)
|
|
297
|
-
setDataDir(directories.dataDir)
|
|
298
|
-
previousDefaultVerbosity = store.getState().defaultVerbosity
|
|
299
|
-
store.setState({ defaultVerbosity: 'tools_and_text' })
|
|
300
|
-
|
|
301
|
-
const digitalDiscordDbPath = path.join(
|
|
302
|
-
directories.dataDir,
|
|
303
|
-
'digital-discord.db',
|
|
304
|
-
)
|
|
305
|
-
|
|
306
|
-
discord = new DigitalDiscord({
|
|
307
|
-
guild: {
|
|
308
|
-
name: 'Queue E2E Guild',
|
|
309
|
-
ownerId: TEST_USER_ID,
|
|
310
|
-
},
|
|
311
|
-
channels: [
|
|
312
|
-
{
|
|
313
|
-
id: TEXT_CHANNEL_ID,
|
|
314
|
-
name: 'queue-e2e',
|
|
315
|
-
type: ChannelType.GuildText,
|
|
316
|
-
},
|
|
317
|
-
],
|
|
318
|
-
users: [
|
|
319
|
-
{
|
|
320
|
-
id: TEST_USER_ID,
|
|
321
|
-
username: 'queue-tester',
|
|
322
|
-
},
|
|
323
|
-
],
|
|
324
|
-
dbUrl: `file:${digitalDiscordDbPath}`,
|
|
325
|
-
})
|
|
326
|
-
|
|
327
|
-
await discord.start()
|
|
328
|
-
|
|
329
|
-
const providerNpm = url
|
|
330
|
-
.pathToFileURL(
|
|
331
|
-
path.resolve(
|
|
332
|
-
process.cwd(),
|
|
333
|
-
'..',
|
|
334
|
-
'opencode-deterministic-provider',
|
|
335
|
-
'src',
|
|
336
|
-
'index.ts',
|
|
337
|
-
),
|
|
338
|
-
)
|
|
339
|
-
.toString()
|
|
340
|
-
|
|
341
|
-
const opencodeConfig = buildDeterministicOpencodeConfig({
|
|
342
|
-
providerName: 'deterministic-provider',
|
|
343
|
-
providerNpm,
|
|
344
|
-
model: 'deterministic-v2',
|
|
345
|
-
smallModel: 'deterministic-v2',
|
|
346
|
-
settings: {
|
|
347
|
-
strict: false,
|
|
348
|
-
matchers: createDeterministicMatchers(),
|
|
349
|
-
},
|
|
350
|
-
})
|
|
351
|
-
fs.writeFileSync(
|
|
352
|
-
path.join(directories.projectDirectory, 'opencode.json'),
|
|
353
|
-
JSON.stringify(opencodeConfig, null, 2),
|
|
354
|
-
)
|
|
355
|
-
|
|
356
|
-
const dbPath = path.join(directories.dataDir, 'discord-sessions.db')
|
|
357
|
-
const hranaResult = await startHranaServer({ dbPath })
|
|
358
|
-
if (hranaResult instanceof Error) {
|
|
359
|
-
throw hranaResult
|
|
360
|
-
}
|
|
361
|
-
process.env['KIMAKI_DB_URL'] = hranaResult
|
|
362
|
-
await initDatabase()
|
|
363
|
-
await setBotToken(discord.botUserId, discord.botToken)
|
|
364
|
-
|
|
365
|
-
await setChannelDirectory({
|
|
366
|
-
channelId: TEXT_CHANNEL_ID,
|
|
367
|
-
directory: directories.projectDirectory,
|
|
368
|
-
channelType: 'text',
|
|
369
|
-
})
|
|
370
|
-
await setChannelVerbosity(TEXT_CHANNEL_ID, 'tools_and_text')
|
|
371
|
-
const channelVerbosity = await getChannelVerbosity(TEXT_CHANNEL_ID)
|
|
372
|
-
expect(channelVerbosity).toBe('tools_and_text')
|
|
373
|
-
|
|
374
|
-
botClient = createDiscordJsClient({ restUrl: discord.restUrl })
|
|
375
|
-
await startDiscordBot({
|
|
376
|
-
token: discord.botToken,
|
|
377
|
-
appId: discord.botUserId,
|
|
378
|
-
discordClient: botClient,
|
|
379
|
-
})
|
|
380
|
-
|
|
381
|
-
// Pre-warm the opencode server so the first test doesn't include
|
|
382
|
-
// server startup time (~3-4s) inside its 4s poll timeouts.
|
|
383
|
-
const warmup = await initializeOpencodeForDirectory(
|
|
384
|
-
directories.projectDirectory,
|
|
385
|
-
)
|
|
386
|
-
if (warmup instanceof Error) {
|
|
387
|
-
throw warmup
|
|
388
|
-
}
|
|
389
|
-
}, 60_000)
|
|
390
|
-
|
|
391
|
-
afterAll(async () => {
|
|
392
|
-
if (directories) {
|
|
393
|
-
await cleanupTestSessions({
|
|
394
|
-
projectDirectory: directories.projectDirectory,
|
|
395
|
-
testStartTime,
|
|
396
|
-
})
|
|
397
|
-
}
|
|
398
|
-
|
|
399
|
-
if (botClient) {
|
|
400
|
-
void botClient.destroy()
|
|
401
|
-
}
|
|
402
|
-
|
|
403
|
-
await stopOpencodeServer()
|
|
404
|
-
await Promise.all([
|
|
405
|
-
closeDatabase().catch(() => {
|
|
406
|
-
return
|
|
407
|
-
}),
|
|
408
|
-
stopHranaServer().catch(() => {
|
|
409
|
-
return
|
|
410
|
-
}),
|
|
411
|
-
discord?.stop().catch(() => {
|
|
412
|
-
return
|
|
413
|
-
}),
|
|
414
|
-
])
|
|
415
|
-
|
|
416
|
-
delete process.env['KIMAKI_LOCK_PORT']
|
|
417
|
-
delete process.env['KIMAKI_DB_URL']
|
|
418
|
-
if (previousDefaultVerbosity) {
|
|
419
|
-
store.setState({ defaultVerbosity: previousDefaultVerbosity })
|
|
420
|
-
}
|
|
421
|
-
if (directories) {
|
|
422
|
-
fs.rmSync(directories.dataDir, { recursive: true, force: true })
|
|
423
|
-
}
|
|
424
|
-
}, 20_000)
|
|
425
|
-
|
|
426
|
-
test(
|
|
427
|
-
'first prompt after cold opencode server start still streams text parts',
|
|
428
|
-
async () => {
|
|
429
|
-
// Reproduce cold-start path: clear in-memory server/client registry so
|
|
430
|
-
// runtime startEventListener() runs once before initialize and exits with
|
|
431
|
-
// "No OpenCode client". The first prompt must still show text parts.
|
|
432
|
-
await stopOpencodeServer()
|
|
433
|
-
|
|
434
|
-
const prompt = 'Reply with exactly: cold-start-stream'
|
|
435
|
-
|
|
436
|
-
await discord.channel(TEXT_CHANNEL_ID).user(TEST_USER_ID).sendMessage({
|
|
437
|
-
content: prompt,
|
|
438
|
-
})
|
|
439
|
-
|
|
440
|
-
const thread = await discord.channel(TEXT_CHANNEL_ID).waitForThread({
|
|
441
|
-
timeout: 4_000,
|
|
442
|
-
predicate: (t) => {
|
|
443
|
-
return t.name === prompt
|
|
444
|
-
},
|
|
445
|
-
})
|
|
446
|
-
|
|
447
|
-
await waitForBotMessageContaining({
|
|
448
|
-
discord,
|
|
449
|
-
threadId: thread.id,
|
|
450
|
-
userId: TEST_USER_ID,
|
|
451
|
-
text: 'ok',
|
|
452
|
-
timeout: 10_000,
|
|
453
|
-
})
|
|
454
|
-
|
|
455
|
-
await waitForFooterMessage({
|
|
456
|
-
discord,
|
|
457
|
-
threadId: thread.id,
|
|
458
|
-
timeout: 4_000,
|
|
459
|
-
})
|
|
460
|
-
|
|
461
|
-
expect(await discord.thread(thread.id).text()).toMatchInlineSnapshot(`
|
|
462
|
-
"--- from: user (queue-tester)
|
|
463
|
-
Reply with exactly: cold-start-stream
|
|
464
|
-
--- from: assistant (TestBot)
|
|
465
|
-
-# *using deterministic-provider/deterministic-v2*
|
|
466
|
-
ok
|
|
467
|
-
-# *project ⋅ main ⋅ Ns ⋅ N% ⋅ deterministic-v2*"
|
|
468
|
-
`)
|
|
469
|
-
},
|
|
470
|
-
12_000,
|
|
471
|
-
)
|
|
472
|
-
|
|
473
|
-
test(
|
|
474
|
-
'text message during active session gets processed',
|
|
475
|
-
async () => {
|
|
476
|
-
// 1. Send initial message to text channel → thread created + session established
|
|
477
|
-
await discord.channel(TEXT_CHANNEL_ID).user(TEST_USER_ID).sendMessage({
|
|
478
|
-
content: 'Reply with exactly: alpha',
|
|
479
|
-
})
|
|
480
|
-
|
|
481
|
-
const thread = await discord.channel(TEXT_CHANNEL_ID).waitForThread({
|
|
482
|
-
timeout: 4_000,
|
|
483
|
-
predicate: (t) => {
|
|
484
|
-
return t.name === 'Reply with exactly: alpha'
|
|
485
|
-
},
|
|
486
|
-
})
|
|
487
|
-
|
|
488
|
-
const th = discord.thread(thread.id)
|
|
489
|
-
|
|
490
|
-
// Wait for the first bot reply so session is fully established in DB
|
|
491
|
-
const firstReply = await th.waitForBotReply({
|
|
492
|
-
timeout: 4_000,
|
|
493
|
-
})
|
|
494
|
-
expect(getMessageVisibleText(firstReply).trim().length).toBeGreaterThan(0)
|
|
495
|
-
|
|
496
|
-
// Snapshot bot message count before sending follow-up
|
|
497
|
-
const before = await th.getMessages()
|
|
498
|
-
const beforeBotCount = before.filter((m) => {
|
|
499
|
-
return m.author.id === discord.botUserId
|
|
500
|
-
}).length
|
|
501
|
-
|
|
502
|
-
// 2. Send follow-up message B into the thread — serialized by runtime's enqueueIncoming
|
|
503
|
-
await th.user(TEST_USER_ID).sendMessage({
|
|
504
|
-
content: 'Reply with exactly: beta',
|
|
505
|
-
})
|
|
506
|
-
|
|
507
|
-
// 3. Wait for exactly 1 new bot message (the response to B)
|
|
508
|
-
const after = await waitForBotMessageCount({
|
|
509
|
-
discord,
|
|
510
|
-
threadId: thread.id,
|
|
511
|
-
count: beforeBotCount + 1,
|
|
512
|
-
timeout: 4_000,
|
|
513
|
-
})
|
|
514
|
-
|
|
515
|
-
// 4. Verify at least 1 new bot message appeared for the follow-up.
|
|
516
|
-
// The bot may send additional messages per session (error reactions,
|
|
517
|
-
// session notifications) so we check >= not exact equality.
|
|
518
|
-
const afterBotMessages = after.filter((m) => {
|
|
519
|
-
return m.author.id === discord.botUserId
|
|
520
|
-
})
|
|
521
|
-
expect(afterBotMessages.length).toBeGreaterThanOrEqual(beforeBotCount + 1)
|
|
522
|
-
|
|
523
|
-
await waitForFooterMessage({
|
|
524
|
-
discord,
|
|
525
|
-
threadId: thread.id,
|
|
526
|
-
timeout: 8_000,
|
|
527
|
-
afterMessageIncludes: 'beta',
|
|
528
|
-
afterAuthorId: TEST_USER_ID,
|
|
529
|
-
})
|
|
530
|
-
|
|
531
|
-
const timeline = await th.text()
|
|
532
|
-
expect(timeline).toContain('Reply with exactly: alpha')
|
|
533
|
-
expect(timeline).toContain('Reply with exactly: beta')
|
|
534
|
-
expect(timeline).toContain('ok')
|
|
535
|
-
expect(timeline).toContain('*project ⋅ main ⋅')
|
|
536
|
-
// User B's message must appear before the new bot response
|
|
537
|
-
const userBIndex = after.findIndex((m) => {
|
|
538
|
-
return (
|
|
539
|
-
m.author.id === TEST_USER_ID &&
|
|
540
|
-
m.content.includes('beta')
|
|
541
|
-
)
|
|
542
|
-
})
|
|
543
|
-
const lastBotIndex = after.findLastIndex((m) => {
|
|
544
|
-
return m.author.id === discord.botUserId
|
|
545
|
-
})
|
|
546
|
-
|
|
547
|
-
expect(userBIndex).toBeGreaterThan(-1)
|
|
548
|
-
expect(lastBotIndex).toBeGreaterThan(-1)
|
|
549
|
-
expect(userBIndex).toBeLessThan(lastBotIndex)
|
|
550
|
-
|
|
551
|
-
// New bot response has non-empty content
|
|
552
|
-
const newBotReply = afterBotMessages[afterBotMessages.length - 1]!
|
|
553
|
-
expect(getMessageVisibleText(newBotReply).trim().length).toBeGreaterThan(0)
|
|
554
|
-
},
|
|
555
|
-
12_000,
|
|
556
|
-
)
|
|
557
|
-
|
|
558
|
-
test('btw queue forks after the current turn and includes its answer', async () => {
|
|
559
|
-
const start = 'Reply with exactly: SLOW_BUSY_MARKER queued-fork-source'
|
|
560
|
-
await discord.channel(TEXT_CHANNEL_ID).user(TEST_USER_ID).sendMessage({ content: start })
|
|
561
|
-
const source = await discord.channel(TEXT_CHANNEL_ID).waitForThread({
|
|
562
|
-
timeout: 4_000,
|
|
563
|
-
predicate: (thread) => thread.name === start,
|
|
564
|
-
})
|
|
565
|
-
const th = discord.thread(source.id)
|
|
566
|
-
await waitForBotMessageContaining({
|
|
567
|
-
discord, threadId: source.id, userId: TEST_USER_ID,
|
|
568
|
-
text: 'slow-busy-reply', timeout: 10_000,
|
|
569
|
-
})
|
|
570
|
-
await th.user(TEST_USER_ID).sendMessage({
|
|
571
|
-
content: 'Reply with exactly: earlier-queue. queue',
|
|
572
|
-
})
|
|
573
|
-
await th.user(TEST_USER_ID).sendMessage({
|
|
574
|
-
content: 'Reply with exactly: queued-fork-answer. btw queue',
|
|
575
|
-
})
|
|
576
|
-
|
|
577
|
-
const before = await th.text()
|
|
578
|
-
expect(before).not.toContain('Session forked!')
|
|
579
|
-
|
|
580
|
-
const fork = await discord.channel(TEXT_CHANNEL_ID).waitForThread({
|
|
581
|
-
timeout: 8_000,
|
|
582
|
-
predicate: (thread) => thread.id !== source.id && (thread.name?.startsWith('btw:') ?? false),
|
|
583
|
-
})
|
|
584
|
-
await waitForBotMessageContaining({
|
|
585
|
-
discord, threadId: fork.id, userId: TEST_USER_ID,
|
|
586
|
-
text: 'ok', timeout: 8_000,
|
|
587
|
-
})
|
|
588
|
-
await waitForFooterMessage({ discord, threadId: fork.id, timeout: 8_000 })
|
|
589
|
-
await waitForBotMessageContaining({
|
|
590
|
-
discord, threadId: source.id, userId: TEST_USER_ID,
|
|
591
|
-
text: 'Session forked!', timeout: 8_000,
|
|
592
|
-
})
|
|
593
|
-
const sourceText = await th.text()
|
|
594
|
-
expect(sourceText.replace(/<#[0-9]+>/g, '<#FORK_THREAD>')).toMatchInlineSnapshot(`
|
|
595
|
-
"--- from: user (queue-tester)
|
|
596
|
-
Reply with exactly: SLOW_BUSY_MARKER queued-fork-source
|
|
597
|
-
--- from: assistant (TestBot)
|
|
598
|
-
-# *using deterministic-provider/deterministic-v2*
|
|
599
|
-
slow-busy-reply
|
|
600
|
-
--- from: user (queue-tester)
|
|
601
|
-
Reply with exactly: earlier-queue. queue
|
|
602
|
-
Reply with exactly: queued-fork-answer. btw queue
|
|
603
|
-
--- from: assistant (TestBot)
|
|
604
|
-
-# Queued at position 1. Edit or delete your message to update the queue
|
|
605
|
-
-# Queued at position 2. Edit or delete your message to update the queue
|
|
606
|
-
-# *project ⋅ main ⋅ Ns ⋅ N% ⋅ deterministic-v2*
|
|
607
|
-
-# Executing queued prompt
|
|
608
|
-
ok
|
|
609
|
-
-# *project ⋅ main ⋅ Ns ⋅ N% ⋅ deterministic-v2*
|
|
610
|
-
-# Executing queued prompt
|
|
611
|
-
Session forked! Continue in <#FORK_THREAD>"
|
|
612
|
-
`)
|
|
613
|
-
expect(sourceText.indexOf('*project ⋅ main ⋅')).toBeLessThan(sourceText.indexOf('Session forked!'))
|
|
614
|
-
expect(sourceText.indexOf('earlier-queue')).toBeLessThan(sourceText.indexOf('Session forked!'))
|
|
615
|
-
expect((await discord.thread(fork.id).text()).replace(/<#[0-9]+>/g, '<#SOURCE_THREAD>')).toMatchInlineSnapshot(`
|
|
616
|
-
"--- from: assistant (TestBot)
|
|
617
|
-
Reusing context from <#SOURCE_THREAD> to answer prompt...
|
|
618
|
-
Reply with exactly: queued-fork-answer
|
|
619
|
-
ok
|
|
620
|
-
-# *project ⋅ main ⋅ Ns ⋅ N% ⋅ deterministic-v2*"
|
|
621
|
-
`)
|
|
622
|
-
|
|
623
|
-
await discord.thread(fork.id).user(TEST_USER_ID).runSlashCommand({
|
|
624
|
-
name: 'queue',
|
|
625
|
-
options: [{ name: 'message', type: 3, value: 'Reply with exactly: nested-fork. btw' }],
|
|
626
|
-
})
|
|
627
|
-
const nested = await discord.channel(TEXT_CHANNEL_ID).waitForThread({
|
|
628
|
-
timeout: 8_000,
|
|
629
|
-
predicate: (thread) => thread.id !== source.id && thread.id !== fork.id && (thread.name?.startsWith('btw:') ?? false),
|
|
630
|
-
})
|
|
631
|
-
await waitForBotMessageContaining({
|
|
632
|
-
discord, threadId: nested.id, userId: TEST_USER_ID,
|
|
633
|
-
text: 'ok', timeout: 8_000,
|
|
634
|
-
})
|
|
635
|
-
await waitForFooterMessage({ discord, threadId: nested.id, timeout: 8_000 })
|
|
636
|
-
expect((await discord.thread(nested.id).text()).replace(/<#[0-9]+>/g, '<#SOURCE_THREAD>')).toMatchInlineSnapshot(`
|
|
637
|
-
"--- from: assistant (TestBot)
|
|
638
|
-
Reusing context from <#SOURCE_THREAD> to answer prompt...
|
|
639
|
-
Reply with exactly: nested-fork
|
|
640
|
-
ok
|
|
641
|
-
-# *project ⋅ main ⋅ Ns ⋅ N% ⋅ deterministic-v2*"
|
|
642
|
-
`)
|
|
643
|
-
}, 16_000)
|
|
644
|
-
|
|
645
|
-
test(
|
|
646
|
-
'two rapid text messages in thread — both processed in order',
|
|
647
|
-
async () => {
|
|
648
|
-
// 1. Send initial message to text channel → thread + session established
|
|
649
|
-
await discord.channel(TEXT_CHANNEL_ID).user(TEST_USER_ID).sendMessage({
|
|
650
|
-
content: 'Reply with exactly: one',
|
|
651
|
-
})
|
|
652
|
-
|
|
653
|
-
const thread = await discord.channel(TEXT_CHANNEL_ID).waitForThread({
|
|
654
|
-
timeout: 4_000,
|
|
655
|
-
predicate: (t) => {
|
|
656
|
-
return t.name === 'Reply with exactly: one'
|
|
657
|
-
},
|
|
658
|
-
})
|
|
659
|
-
|
|
660
|
-
const th = discord.thread(thread.id)
|
|
661
|
-
|
|
662
|
-
// Wait for the first bot reply AND its footer so the first response
|
|
663
|
-
// cycle is fully complete before sending follow-ups. Without this,
|
|
664
|
-
// the footer for "one" can still be in-flight when the snapshot runs.
|
|
665
|
-
const firstReply = await th.waitForBotReply({
|
|
666
|
-
timeout: 4_000,
|
|
667
|
-
})
|
|
668
|
-
expect(getMessageVisibleText(firstReply).trim().length).toBeGreaterThan(0)
|
|
669
|
-
|
|
670
|
-
await waitForFooterMessage({
|
|
671
|
-
discord,
|
|
672
|
-
threadId: thread.id,
|
|
673
|
-
timeout: 4_000,
|
|
674
|
-
afterMessageIncludes: 'one',
|
|
675
|
-
afterAuthorId: TEST_USER_ID,
|
|
676
|
-
})
|
|
677
|
-
|
|
678
|
-
// Snapshot bot message count before sending follow-ups
|
|
679
|
-
const before = await th.getMessages()
|
|
680
|
-
const beforeBotCount = before.filter((m) => {
|
|
681
|
-
return m.author.id === discord.botUserId
|
|
682
|
-
}).length
|
|
683
|
-
|
|
684
|
-
// 2. Rapidly send messages B and C. With opencode queue mode,
|
|
685
|
-
// both messages are serialized by opencode's per-session loop.
|
|
686
|
-
await th.user(TEST_USER_ID).sendMessage({
|
|
687
|
-
content: 'Reply with exactly: two',
|
|
688
|
-
})
|
|
689
|
-
await th.user(TEST_USER_ID).sendMessage({
|
|
690
|
-
content: 'Reply with exactly: three',
|
|
691
|
-
})
|
|
692
|
-
|
|
693
|
-
// 3. Wait for a bot reply after message C.
|
|
694
|
-
const after = await waitForBotReplyAfterUserMessage({
|
|
695
|
-
discord,
|
|
696
|
-
threadId: thread.id,
|
|
697
|
-
userId: TEST_USER_ID,
|
|
698
|
-
userMessageIncludes: 'three',
|
|
699
|
-
timeout: 4_000,
|
|
700
|
-
})
|
|
701
|
-
|
|
702
|
-
// 4. Verify the latest user message got a bot reply.
|
|
703
|
-
const afterBotMessages = after.filter((m) => {
|
|
704
|
-
return m.author.id === discord.botUserId
|
|
705
|
-
})
|
|
706
|
-
expect(afterBotMessages.length).toBeGreaterThanOrEqual(beforeBotCount + 1)
|
|
707
|
-
|
|
708
|
-
expect((await th.text()).replace(/\nok(?=\nok)/g, '')).toMatchInlineSnapshot(`
|
|
709
|
-
"--- from: user (queue-tester)
|
|
710
|
-
Reply with exactly: one
|
|
711
|
-
--- from: assistant (TestBot)
|
|
712
|
-
-# *using deterministic-provider/deterministic-v2*
|
|
713
|
-
ok
|
|
714
|
-
-# *project ⋅ main ⋅ Ns ⋅ N% ⋅ deterministic-v2*
|
|
715
|
-
--- from: user (queue-tester)
|
|
716
|
-
Reply with exactly: two
|
|
717
|
-
Reply with exactly: three
|
|
718
|
-
--- from: assistant (TestBot)
|
|
719
|
-
ok"
|
|
720
|
-
`)
|
|
721
|
-
const userThreeIndex = after.findIndex((message) => {
|
|
722
|
-
return (
|
|
723
|
-
message.author.id === TEST_USER_ID &&
|
|
724
|
-
message.content.includes('three')
|
|
725
|
-
)
|
|
726
|
-
})
|
|
727
|
-
expect(userThreeIndex).toBeGreaterThan(-1)
|
|
728
|
-
|
|
729
|
-
const botAfterThreeIndex = after.findIndex((message, index) => {
|
|
730
|
-
return index > userThreeIndex && message.author.id === discord.botUserId
|
|
731
|
-
})
|
|
732
|
-
expect(botAfterThreeIndex).toBeGreaterThan(userThreeIndex)
|
|
733
|
-
|
|
734
|
-
const newBotReplies = afterBotMessages.slice(beforeBotCount)
|
|
735
|
-
expect(newBotReplies.some((reply) => {
|
|
736
|
-
return getMessageVisibleText(reply).trim().length > 0
|
|
737
|
-
})).toBe(true)
|
|
738
|
-
|
|
739
|
-
const finalState = await waitForThreadState({
|
|
740
|
-
threadId: thread.id,
|
|
741
|
-
predicate: (state) => {
|
|
742
|
-
return state.queueItems.length === 0
|
|
743
|
-
},
|
|
744
|
-
timeout: 4_000,
|
|
745
|
-
description: 'queue empty after rapid interrupts',
|
|
746
|
-
})
|
|
747
|
-
expect(finalState.queueItems.length).toBe(0)
|
|
748
|
-
},
|
|
749
|
-
8_000,
|
|
750
|
-
)
|
|
751
|
-
|
|
752
|
-
test(
|
|
753
|
-
'bash tool-call actually executes and creates file in project directory',
|
|
754
|
-
async () => {
|
|
755
|
-
const markerRelativePath = path.join('tmp', 'bash-tool-executed.txt')
|
|
756
|
-
const markerPath = path.join(directories.projectDirectory, markerRelativePath)
|
|
757
|
-
fs.rmSync(markerPath, { force: true })
|
|
758
|
-
const existingThreadIds = new Set(
|
|
759
|
-
(await discord.channel(TEXT_CHANNEL_ID).getThreads()).map((thread) => {
|
|
760
|
-
return thread.id
|
|
761
|
-
}),
|
|
762
|
-
)
|
|
763
|
-
|
|
764
|
-
const prompt = 'Reply with exactly: BASH_TOOL_FILE_MARKER'
|
|
765
|
-
await discord.channel(TEXT_CHANNEL_ID).user(TEST_USER_ID).sendMessage({
|
|
766
|
-
content: prompt,
|
|
767
|
-
})
|
|
768
|
-
|
|
769
|
-
const thread = await discord.channel(TEXT_CHANNEL_ID).waitForThread({
|
|
770
|
-
timeout: 6_000,
|
|
771
|
-
predicate: (t) => {
|
|
772
|
-
return !existingThreadIds.has(t.id)
|
|
773
|
-
},
|
|
774
|
-
})
|
|
775
|
-
|
|
776
|
-
await waitForBotMessageContaining({
|
|
777
|
-
discord,
|
|
778
|
-
threadId: thread.id,
|
|
779
|
-
userId: TEST_USER_ID,
|
|
780
|
-
text: 'running create file',
|
|
781
|
-
timeout: 6_000,
|
|
782
|
-
})
|
|
783
|
-
|
|
784
|
-
await waitForFooterMessage({
|
|
785
|
-
discord,
|
|
786
|
-
threadId: thread.id,
|
|
787
|
-
timeout: 6_000,
|
|
788
|
-
})
|
|
789
|
-
|
|
790
|
-
const deadline = Date.now() + 4_000
|
|
791
|
-
while (!fs.existsSync(markerPath) && Date.now() < deadline) {
|
|
792
|
-
await new Promise((resolve) => {
|
|
793
|
-
setTimeout(resolve, 100)
|
|
794
|
-
})
|
|
795
|
-
}
|
|
796
|
-
|
|
797
|
-
expect(await discord.thread(thread.id).text()).toMatchInlineSnapshot(`
|
|
798
|
-
"--- from: user (queue-tester)
|
|
799
|
-
Reply with exactly: BASH_TOOL_FILE_MARKER
|
|
800
|
-
--- from: assistant (TestBot)
|
|
801
|
-
-# *using deterministic-provider/deterministic-v2*
|
|
802
|
-
> running create file
|
|
803
|
-
> creating marker
|
|
804
|
-
|
|
805
|
-
-# ┣ bash _Create marker file for e2e test_
|
|
806
|
-
|
|
807
|
-
file created
|
|
808
|
-
-# *project ⋅ main ⋅ Ns ⋅ N% ⋅ deterministic-v2*"
|
|
809
|
-
`)
|
|
810
|
-
expect(fs.existsSync(markerPath)).toBe(true)
|
|
811
|
-
const markerContents = fs.readFileSync(markerPath, 'utf8')
|
|
812
|
-
expect(markerContents).toBe('created')
|
|
813
|
-
},
|
|
814
|
-
8_000,
|
|
815
|
-
)
|
|
816
|
-
|
|
817
|
-
test(
|
|
818
|
-
'/queue shows queued status first, then dispatch indicator when dequeued',
|
|
819
|
-
async () => {
|
|
820
|
-
await discord.channel(TEXT_CHANNEL_ID).user(TEST_USER_ID).sendMessage({
|
|
821
|
-
content: 'Reply with exactly: queue-slash-setup',
|
|
822
|
-
})
|
|
823
|
-
|
|
824
|
-
const thread = await discord.channel(TEXT_CHANNEL_ID).waitForThread({
|
|
825
|
-
timeout: 4_000,
|
|
826
|
-
predicate: (t) => {
|
|
827
|
-
return t.name === 'Reply with exactly: queue-slash-setup'
|
|
828
|
-
},
|
|
829
|
-
})
|
|
830
|
-
|
|
831
|
-
const th = discord.thread(thread.id)
|
|
832
|
-
const firstReply = await th.waitForBotReply({ timeout: 4_000 })
|
|
833
|
-
expect(getMessageVisibleText(firstReply).trim().length).toBeGreaterThan(0)
|
|
834
|
-
|
|
835
|
-
// Ensure the setup run is fully settled before slash-queue checks.
|
|
836
|
-
// Otherwise the first /queue call can race with a still-busy run window.
|
|
837
|
-
await waitForFooterMessage({
|
|
838
|
-
discord,
|
|
839
|
-
threadId: thread.id,
|
|
840
|
-
timeout: 4_000,
|
|
841
|
-
})
|
|
842
|
-
|
|
843
|
-
// Start a non-interrupting queued slash message while idle so it
|
|
844
|
-
// dispatches immediately and keeps the runtime active.
|
|
845
|
-
const { id: firstQueueInteractionId } = await th.user(TEST_USER_ID)
|
|
846
|
-
.runSlashCommand({
|
|
847
|
-
name: 'queue',
|
|
848
|
-
options: [{ name: 'message', type: 3, value: 'Reply with exactly: race-final' }],
|
|
849
|
-
})
|
|
850
|
-
|
|
851
|
-
const firstQueueAck = await th.waitForInteractionAck({
|
|
852
|
-
interactionId: firstQueueInteractionId,
|
|
853
|
-
timeout: 4_000,
|
|
854
|
-
})
|
|
855
|
-
if (!firstQueueAck.messageId) {
|
|
856
|
-
throw new Error('Expected first /queue response message id')
|
|
857
|
-
}
|
|
858
|
-
|
|
859
|
-
const firstQueueAckMessage = await waitForMessageById({
|
|
860
|
-
discord,
|
|
861
|
-
threadId: thread.id,
|
|
862
|
-
messageId: firstQueueAck.messageId,
|
|
863
|
-
timeout: 4_000,
|
|
864
|
-
})
|
|
865
|
-
expect(firstQueueAckMessage.content).toContain('» **queue-tester:** Reply with exactly: race-final')
|
|
866
|
-
|
|
867
|
-
const queuedPrompt = 'Reply with exactly: queued-from-slash'
|
|
868
|
-
const { id: interactionId } = await th.user(TEST_USER_ID).runSlashCommand({
|
|
869
|
-
name: 'queue',
|
|
870
|
-
options: [{ name: 'message', type: 3, value: queuedPrompt }],
|
|
871
|
-
})
|
|
872
|
-
|
|
873
|
-
const queuedAck = await th.waitForInteractionAck({ interactionId, timeout: 4_000 })
|
|
874
|
-
if (!queuedAck.messageId) {
|
|
875
|
-
throw new Error('Expected queued /queue response message id')
|
|
876
|
-
}
|
|
877
|
-
|
|
878
|
-
const queuedStatusMessage = await waitForMessageById({
|
|
879
|
-
discord,
|
|
880
|
-
threadId: thread.id,
|
|
881
|
-
messageId: queuedAck.messageId,
|
|
882
|
-
timeout: 4_000,
|
|
883
|
-
})
|
|
884
|
-
expect(queuedStatusMessage.content.startsWith('-# Queued message')).toBe(true)
|
|
885
|
-
|
|
886
|
-
// The drain indicator is a silent reply to the /queue ack.
|
|
887
|
-
const dispatchIndicatorMessage = await waitForBotReplyTo({
|
|
888
|
-
discord,
|
|
889
|
-
threadId: thread.id,
|
|
890
|
-
replyToMessageId: queuedStatusMessage.id,
|
|
891
|
-
timeout: 8_000,
|
|
892
|
-
})
|
|
893
|
-
expect(dispatchIndicatorMessage.content).toBe('-# Executing queued prompt')
|
|
894
|
-
|
|
895
|
-
await waitForBotMessageContaining({
|
|
896
|
-
discord,
|
|
897
|
-
threadId: thread.id,
|
|
898
|
-
text: 'ok',
|
|
899
|
-
afterMessageId: dispatchIndicatorMessage.id,
|
|
900
|
-
timeout: 8_000,
|
|
901
|
-
})
|
|
902
|
-
|
|
903
|
-
await waitForFooterMessage({
|
|
904
|
-
discord,
|
|
905
|
-
threadId: thread.id,
|
|
906
|
-
timeout: 8_000,
|
|
907
|
-
afterMessageIncludes: 'ok',
|
|
908
|
-
afterAuthorId: discord.botUserId,
|
|
909
|
-
})
|
|
910
|
-
|
|
911
|
-
expect(await th.text()).toMatchInlineSnapshot(`
|
|
912
|
-
"--- from: user (queue-tester)
|
|
913
|
-
Reply with exactly: queue-slash-setup
|
|
914
|
-
--- from: assistant (TestBot)
|
|
915
|
-
-# *using deterministic-provider/deterministic-v2*
|
|
916
|
-
ok
|
|
917
|
-
-# *project ⋅ main ⋅ Ns ⋅ N% ⋅ deterministic-v2*
|
|
918
|
-
» **queue-tester:** Reply with exactly: race-final
|
|
919
|
-
-# Queued message (position 2)
|
|
920
|
-
race-final
|
|
921
|
-
-# *project ⋅ main ⋅ Ns ⋅ N% ⋅ deterministic-v2*
|
|
922
|
-
-# Executing queued prompt
|
|
923
|
-
ok
|
|
924
|
-
-# *project ⋅ main ⋅ Ns ⋅ N% ⋅ deterministic-v2*"
|
|
925
|
-
`)
|
|
926
|
-
},
|
|
927
|
-
12_000,
|
|
928
|
-
)
|
|
929
|
-
|
|
930
|
-
test(
|
|
931
|
-
'Remove from queue button clears only that queued message',
|
|
932
|
-
async () => {
|
|
933
|
-
await discord.channel(TEXT_CHANNEL_ID).user(TEST_USER_ID).sendMessage({
|
|
934
|
-
content: 'Reply with exactly: clear-queue-setup',
|
|
935
|
-
})
|
|
936
|
-
|
|
937
|
-
const thread = await discord.channel(TEXT_CHANNEL_ID).waitForThread({
|
|
938
|
-
timeout: 4_000,
|
|
939
|
-
predicate: (t) => {
|
|
940
|
-
return t.name === 'Reply with exactly: clear-queue-setup'
|
|
941
|
-
},
|
|
942
|
-
})
|
|
943
|
-
|
|
944
|
-
const th = discord.thread(thread.id)
|
|
945
|
-
await th.waitForBotReply({ timeout: 4_000 })
|
|
946
|
-
await waitForFooterMessage({
|
|
947
|
-
discord,
|
|
948
|
-
threadId: thread.id,
|
|
949
|
-
timeout: 4_000,
|
|
950
|
-
})
|
|
951
|
-
|
|
952
|
-
await th.user(TEST_USER_ID).runSlashCommand({
|
|
953
|
-
name: 'queue',
|
|
954
|
-
options: [{ name: 'message', type: 3, value: 'Reply with exactly: race-final' }],
|
|
955
|
-
})
|
|
956
|
-
|
|
957
|
-
const { id: secondQueueInteractionId } = await th.user(TEST_USER_ID)
|
|
958
|
-
.runSlashCommand({
|
|
959
|
-
name: 'queue',
|
|
960
|
-
options: [{ name: 'message', type: 3, value: 'Reply with exactly: removed-queued-message' }],
|
|
961
|
-
})
|
|
962
|
-
const secondQueueAck = await th.waitForInteractionAck({
|
|
963
|
-
interactionId: secondQueueInteractionId,
|
|
964
|
-
timeout: 4_000,
|
|
965
|
-
})
|
|
966
|
-
if (!secondQueueAck.messageId) {
|
|
967
|
-
throw new Error('Expected second /queue response message id')
|
|
968
|
-
}
|
|
969
|
-
|
|
970
|
-
const secondQueueAckMessage = await waitForMessageById({
|
|
971
|
-
discord,
|
|
972
|
-
threadId: thread.id,
|
|
973
|
-
messageId: secondQueueAck.messageId,
|
|
974
|
-
timeout: 4_000,
|
|
975
|
-
})
|
|
976
|
-
// The first /queue item is still in flight, so it holds position 1.
|
|
977
|
-
expect(secondQueueAckMessage.content).toContain('Queued message (position 2)')
|
|
978
|
-
|
|
979
|
-
const { id: thirdQueueInteractionId } = await th.user(TEST_USER_ID).runSlashCommand({
|
|
980
|
-
name: 'queue',
|
|
981
|
-
options: [{ name: 'message', type: 3, value: 'Reply with exactly: kept-queued-message' }],
|
|
982
|
-
})
|
|
983
|
-
const thirdQueueAck = await th.waitForInteractionAck({
|
|
984
|
-
interactionId: thirdQueueInteractionId,
|
|
985
|
-
timeout: 4_000,
|
|
986
|
-
})
|
|
987
|
-
if (!thirdQueueAck.messageId) {
|
|
988
|
-
throw new Error('Expected third /queue response message id')
|
|
989
|
-
}
|
|
990
|
-
|
|
991
|
-
const thirdQueueAckMessage = await waitForMessageById({
|
|
992
|
-
discord,
|
|
993
|
-
threadId: thread.id,
|
|
994
|
-
messageId: thirdQueueAck.messageId,
|
|
995
|
-
timeout: 4_000,
|
|
996
|
-
})
|
|
997
|
-
expect(thirdQueueAckMessage.content).toContain('Queued message (position 3)')
|
|
998
|
-
|
|
999
|
-
const serializedComponents = JSON.stringify(secondQueueAckMessage.components)
|
|
1000
|
-
const customIdMatch = serializedComponents.match(
|
|
1001
|
-
/"custom_id"\s*:\s*"(queue_remove:[^"]+)"/,
|
|
1002
|
-
)
|
|
1003
|
-
if (!customIdMatch?.[1]) {
|
|
1004
|
-
throw new Error(
|
|
1005
|
-
`Expected Remove from queue button on queue ack: ${serializedComponents}`,
|
|
1006
|
-
)
|
|
1007
|
-
}
|
|
1008
|
-
|
|
1009
|
-
const removeInteraction = await th.user(TEST_USER_ID).clickButton({
|
|
1010
|
-
messageId: secondQueueAckMessage.id,
|
|
1011
|
-
customId: customIdMatch[1],
|
|
1012
|
-
})
|
|
1013
|
-
await th.waitForInteractionAck({
|
|
1014
|
-
interactionId: removeInteraction.id,
|
|
1015
|
-
timeout: 4_000,
|
|
1016
|
-
})
|
|
1017
|
-
|
|
1018
|
-
const removeAckMessage = await waitForMessageById({
|
|
1019
|
-
discord,
|
|
1020
|
-
threadId: thread.id,
|
|
1021
|
-
messageId: secondQueueAckMessage.id,
|
|
1022
|
-
timeout: 4_000,
|
|
1023
|
-
})
|
|
1024
|
-
expect(removeAckMessage.content).toContain('Removed queued message')
|
|
1025
|
-
expect(removeAckMessage.content).toContain('removed-queued-message')
|
|
1026
|
-
|
|
1027
|
-
const indicator = await waitForBotReplyTo({
|
|
1028
|
-
discord,
|
|
1029
|
-
threadId: thread.id,
|
|
1030
|
-
replyToMessageId: thirdQueueAckMessage.id,
|
|
1031
|
-
timeout: 8_000,
|
|
1032
|
-
})
|
|
1033
|
-
expect(indicator.content).toBe('-# Executing queued prompt')
|
|
1034
|
-
|
|
1035
|
-
await waitForFooterMessage({
|
|
1036
|
-
discord,
|
|
1037
|
-
threadId: thread.id,
|
|
1038
|
-
timeout: 8_000,
|
|
1039
|
-
afterMessageId: indicator.id,
|
|
1040
|
-
})
|
|
1041
|
-
|
|
1042
|
-
const threadText = await th.text()
|
|
1043
|
-
expect(threadText).toMatchInlineSnapshot(`
|
|
1044
|
-
"--- from: user (queue-tester)
|
|
1045
|
-
Reply with exactly: clear-queue-setup
|
|
1046
|
-
--- from: assistant (TestBot)
|
|
1047
|
-
-# *using deterministic-provider/deterministic-v2*
|
|
1048
|
-
ok
|
|
1049
|
-
-# *project ⋅ main ⋅ Ns ⋅ N% ⋅ deterministic-v2*
|
|
1050
|
-
-# Removed queued message: Reply with exactly: removed-queued-message
|
|
1051
|
-
» **queue-tester:** Reply with exactly: race-final
|
|
1052
|
-
-# Queued message (position 3)
|
|
1053
|
-
race-final
|
|
1054
|
-
-# *project ⋅ main ⋅ Ns ⋅ N% ⋅ deterministic-v2*
|
|
1055
|
-
-# Executing queued prompt
|
|
1056
|
-
ok
|
|
1057
|
-
-# *project ⋅ main ⋅ Ns ⋅ N% ⋅ deterministic-v2*"
|
|
1058
|
-
`)
|
|
1059
|
-
// The removed item never drained, so nothing replies to its ack.
|
|
1060
|
-
const finalMessages = await th.getMessages()
|
|
1061
|
-
expect(finalMessages.some((message) => {
|
|
1062
|
-
return message.message_reference?.message_id === secondQueueAckMessage.id
|
|
1063
|
-
})).toBe(false)
|
|
1064
|
-
},
|
|
1065
|
-
12_000,
|
|
1066
|
-
)
|
|
1067
|
-
|
|
1068
|
-
test(
|
|
1069
|
-
'slow stream still processes queued next message after completion',
|
|
1070
|
-
async () => {
|
|
1071
|
-
// A message sent mid-stream queues and runs after the in-flight request
|
|
1072
|
-
// completes (no auto-interrupt).
|
|
1073
|
-
|
|
1074
|
-
// 1. Fast setup: establish session
|
|
1075
|
-
await discord.channel(TEXT_CHANNEL_ID).user(TEST_USER_ID).sendMessage({
|
|
1076
|
-
content: 'Reply with exactly: golf',
|
|
1077
|
-
})
|
|
1078
|
-
|
|
1079
|
-
const thread = await discord.channel(TEXT_CHANNEL_ID).waitForThread({
|
|
1080
|
-
timeout: 4_000,
|
|
1081
|
-
predicate: (t) => {
|
|
1082
|
-
return t.name === 'Reply with exactly: golf'
|
|
1083
|
-
},
|
|
1084
|
-
})
|
|
1085
|
-
|
|
1086
|
-
const th = discord.thread(thread.id)
|
|
1087
|
-
const firstReply = await th.waitForBotReply({ timeout: 4_000 })
|
|
1088
|
-
expect(getMessageVisibleText(firstReply).trim().length).toBeGreaterThan(0)
|
|
1089
|
-
|
|
1090
|
-
// Wait for golf's footer so the golf→hotel transition is deterministic
|
|
1091
|
-
await waitForFooterMessage({
|
|
1092
|
-
discord,
|
|
1093
|
-
threadId: thread.id,
|
|
1094
|
-
timeout: 4_000,
|
|
1095
|
-
afterMessageIncludes: 'ok',
|
|
1096
|
-
afterAuthorId: discord.botUserId,
|
|
1097
|
-
})
|
|
1098
|
-
|
|
1099
|
-
const before = await th.getMessages()
|
|
1100
|
-
const beforeBotCount = before.filter((m) => {
|
|
1101
|
-
return m.author.id === discord.botUserId
|
|
1102
|
-
}).length
|
|
1103
|
-
|
|
1104
|
-
// 2. Start request B (hotel, slow matcher ~400ms), then send C while B
|
|
1105
|
-
// is still in progress.
|
|
1106
|
-
await th.user(TEST_USER_ID).sendMessage({
|
|
1107
|
-
content: 'Reply with exactly: hotel',
|
|
1108
|
-
})
|
|
1109
|
-
|
|
1110
|
-
// 3. Wait briefly for B to start, then send C to queue behind it
|
|
1111
|
-
await new Promise((r) => {
|
|
1112
|
-
setTimeout(r, 200)
|
|
1113
|
-
})
|
|
1114
|
-
await th.user(TEST_USER_ID).sendMessage({
|
|
1115
|
-
content: 'Reply with exactly: india',
|
|
1116
|
-
})
|
|
1117
|
-
|
|
1118
|
-
// 4. B completes, then C gets processed.
|
|
1119
|
-
// Poll until india's user message has a bot reply after it.
|
|
1120
|
-
const after = await waitForBotReplyAfterUserMessage({
|
|
1121
|
-
discord,
|
|
1122
|
-
threadId: thread.id,
|
|
1123
|
-
userId: TEST_USER_ID,
|
|
1124
|
-
userMessageIncludes: 'india',
|
|
1125
|
-
timeout: 4_000,
|
|
1126
|
-
})
|
|
1127
|
-
|
|
1128
|
-
// C's user message appears before its bot response.
|
|
1129
|
-
// We assert on india's reply existence.
|
|
1130
|
-
expect(await th.text()).toMatchInlineSnapshot(`
|
|
1131
|
-
"--- from: user (queue-tester)
|
|
1132
|
-
Reply with exactly: golf
|
|
1133
|
-
--- from: assistant (TestBot)
|
|
1134
|
-
-# *using deterministic-provider/deterministic-v2*
|
|
1135
|
-
ok
|
|
1136
|
-
-# *project ⋅ main ⋅ Ns ⋅ N% ⋅ deterministic-v2*
|
|
1137
|
-
--- from: user (queue-tester)
|
|
1138
|
-
Reply with exactly: hotel
|
|
1139
|
-
Reply with exactly: india
|
|
1140
|
-
--- from: assistant (TestBot)
|
|
1141
|
-
> ok"
|
|
1142
|
-
`)
|
|
1143
|
-
const userIndiaIndex = after.findIndex((m) => {
|
|
1144
|
-
return m.author.id === TEST_USER_ID && m.content.includes('india')
|
|
1145
|
-
})
|
|
1146
|
-
expect(userIndiaIndex).toBeGreaterThan(-1)
|
|
1147
|
-
const botAfterIndia = after.findIndex((m, i) => {
|
|
1148
|
-
return i > userIndiaIndex && m.author.id === discord.botUserId
|
|
1149
|
-
})
|
|
1150
|
-
expect(botAfterIndia).toBeGreaterThan(userIndiaIndex)
|
|
1151
|
-
},
|
|
1152
|
-
8_000,
|
|
1153
|
-
)
|
|
1154
|
-
|
|
1155
|
-
test(
|
|
1156
|
-
'queue drains correctly after bursty queued messages',
|
|
1157
|
-
async () => {
|
|
1158
|
-
// Verifies the queue doesn't get stuck after multiple rapid messages.
|
|
1159
|
-
|
|
1160
|
-
// 1. Fast setup: establish session
|
|
1161
|
-
await discord.channel(TEXT_CHANNEL_ID).user(TEST_USER_ID).sendMessage({
|
|
1162
|
-
content: 'Reply with exactly: juliet',
|
|
1163
|
-
})
|
|
1164
|
-
|
|
1165
|
-
const thread = await discord.channel(TEXT_CHANNEL_ID).waitForThread({
|
|
1166
|
-
timeout: 4_000,
|
|
1167
|
-
predicate: (t) => {
|
|
1168
|
-
return t.name === 'Reply with exactly: juliet'
|
|
1169
|
-
},
|
|
1170
|
-
})
|
|
1171
|
-
|
|
1172
|
-
const th = discord.thread(thread.id)
|
|
1173
|
-
const firstReply = await th.waitForBotReply({ timeout: 4_000 })
|
|
1174
|
-
expect(getMessageVisibleText(firstReply).trim().length).toBeGreaterThan(0)
|
|
1175
|
-
|
|
1176
|
-
const before = await th.getMessages()
|
|
1177
|
-
const beforeBotCount = before.filter((m) => {
|
|
1178
|
-
return m.author.id === discord.botUserId
|
|
1179
|
-
}).length
|
|
1180
|
-
|
|
1181
|
-
// 2. Rapidly send B, C, D back-to-back to avoid timing windows where
|
|
1182
|
-
// one run can finish between sends and reorder transcript lines.
|
|
1183
|
-
await th.user(TEST_USER_ID).sendMessage({
|
|
1184
|
-
content: 'Reply with exactly: kilo',
|
|
1185
|
-
})
|
|
1186
|
-
await th.user(TEST_USER_ID).sendMessage({
|
|
1187
|
-
content: 'Reply with exactly: lima',
|
|
1188
|
-
})
|
|
1189
|
-
await th.user(TEST_USER_ID).sendMessage({
|
|
1190
|
-
content: 'Reply with exactly: mike',
|
|
1191
|
-
})
|
|
1192
|
-
|
|
1193
|
-
// 3. Wait until the last burst message (mike) has a bot reply after it.
|
|
1194
|
-
const afterBurst = await waitForBotReplyAfterUserMessage({
|
|
1195
|
-
discord,
|
|
1196
|
-
threadId: thread.id,
|
|
1197
|
-
userId: TEST_USER_ID,
|
|
1198
|
-
userMessageIncludes: 'mike',
|
|
1199
|
-
timeout: 4_000,
|
|
1200
|
-
})
|
|
1201
|
-
|
|
1202
|
-
// 4. Queue should be clean — send E and verify it also gets processed
|
|
1203
|
-
await th.user(TEST_USER_ID).sendMessage({
|
|
1204
|
-
content: 'Reply with exactly: november',
|
|
1205
|
-
})
|
|
1206
|
-
|
|
1207
|
-
const afterE = await waitForBotReplyAfterUserMessage({
|
|
1208
|
-
discord,
|
|
1209
|
-
threadId: thread.id,
|
|
1210
|
-
userId: TEST_USER_ID,
|
|
1211
|
-
userMessageIncludes: 'november',
|
|
1212
|
-
timeout: 4_000,
|
|
1213
|
-
})
|
|
1214
|
-
|
|
1215
|
-
const textWithoutFooters = (await th.text())
|
|
1216
|
-
.split('\n')
|
|
1217
|
-
.filter((line) => {
|
|
1218
|
-
return !line.startsWith('*project ⋅') && !line.startsWith('> *project ⋅') && !line.startsWith('-# *project ⋅')
|
|
1219
|
-
})
|
|
1220
|
-
.join('\n')
|
|
1221
|
-
|
|
1222
|
-
const normalizedTextWithoutFooters = textWithoutFooters.replace(
|
|
1223
|
-
[
|
|
1224
|
-
'--- from: assistant (TestBot)',
|
|
1225
|
-
'ok',
|
|
1226
|
-
'--- from: user (queue-tester)',
|
|
1227
|
-
'Reply with exactly: november',
|
|
1228
|
-
].join('\n'),
|
|
1229
|
-
[
|
|
1230
|
-
'--- from: assistant (TestBot)',
|
|
1231
|
-
'--- from: user (queue-tester)',
|
|
1232
|
-
'Reply with exactly: november',
|
|
1233
|
-
].join('\n'),
|
|
1234
|
-
)
|
|
1235
|
-
|
|
1236
|
-
expect(normalizedTextWithoutFooters).toMatchInlineSnapshot(`
|
|
1237
|
-
"--- from: user (queue-tester)
|
|
1238
|
-
Reply with exactly: juliet
|
|
1239
|
-
--- from: assistant (TestBot)
|
|
1240
|
-
-# *using deterministic-provider/deterministic-v2*
|
|
1241
|
-
--- from: user (queue-tester)
|
|
1242
|
-
Reply with exactly: kilo
|
|
1243
|
-
Reply with exactly: lima
|
|
1244
|
-
Reply with exactly: mike
|
|
1245
|
-
--- from: assistant (TestBot)
|
|
1246
|
-
--- from: user (queue-tester)
|
|
1247
|
-
Reply with exactly: november
|
|
1248
|
-
--- from: assistant (TestBot)"
|
|
1249
|
-
`)
|
|
1250
|
-
// E's user message appears before the final bot response
|
|
1251
|
-
const userNovemberIndex = afterE.findIndex((m) => {
|
|
1252
|
-
return m.author.id === TEST_USER_ID && m.content.includes('november')
|
|
1253
|
-
})
|
|
1254
|
-
expect(userNovemberIndex).toBeGreaterThan(-1)
|
|
1255
|
-
const lastBotIndex = afterE.findLastIndex((m) => {
|
|
1256
|
-
return m.author.id === discord.botUserId
|
|
1257
|
-
})
|
|
1258
|
-
expect(userNovemberIndex).toBeLessThan(lastBotIndex)
|
|
1259
|
-
},
|
|
1260
|
-
8_000,
|
|
1261
|
-
)
|
|
1262
|
-
|
|
1263
|
-
test(
|
|
1264
|
-
'editing a queued message updates its prompt before dispatch',
|
|
1265
|
-
async () => {
|
|
1266
|
-
// 1. Start a session with a slow matcher (2s busy) to keep it busy.
|
|
1267
|
-
await discord.channel(TEXT_CHANNEL_ID).user(TEST_USER_ID).sendMessage({
|
|
1268
|
-
content: 'SLOW_BUSY_MARKER Reply with exactly: edit-queue-setup',
|
|
1269
|
-
})
|
|
1270
|
-
|
|
1271
|
-
const thread = await discord.channel(TEXT_CHANNEL_ID).waitForThread({
|
|
1272
|
-
timeout: 4_000,
|
|
1273
|
-
predicate: (t) => {
|
|
1274
|
-
return t.name === 'SLOW_BUSY_MARKER Reply with exactly: edit-queue-setup'
|
|
1275
|
-
},
|
|
1276
|
-
})
|
|
1277
|
-
|
|
1278
|
-
const th = discord.thread(thread.id)
|
|
1279
|
-
|
|
1280
|
-
// 2. Wait until the bot has replied, then queue while the slow stream is busy.
|
|
1281
|
-
await th.waitForBotReply({ timeout: 4_000 })
|
|
1282
|
-
const queuedMsg = await th.user(TEST_USER_ID).sendMessage({
|
|
1283
|
-
content: 'Reply with exactly: original-queued. queue',
|
|
1284
|
-
})
|
|
1285
|
-
|
|
1286
|
-
// 3. Verify the message landed in the local queue.
|
|
1287
|
-
const queuedState = await waitForThreadState({
|
|
1288
|
-
threadId: thread.id,
|
|
1289
|
-
predicate: (state) => {
|
|
1290
|
-
return state.queueItems.length > 0
|
|
1291
|
-
},
|
|
1292
|
-
timeout: 4_000,
|
|
1293
|
-
description: 'queue has item from suffix message',
|
|
1294
|
-
})
|
|
1295
|
-
expect(queuedState.queueItems.length).toBeGreaterThanOrEqual(1)
|
|
1296
|
-
const queueItem = queuedState.queueItems.find((item) => {
|
|
1297
|
-
return item.sourceMessageId === queuedMsg.id
|
|
1298
|
-
})
|
|
1299
|
-
expect(queueItem).toBeTruthy()
|
|
1300
|
-
expect(queueItem!.prompt).toContain('original-queued')
|
|
1301
|
-
|
|
1302
|
-
// 4. Edit the message while it's still in the queue.
|
|
1303
|
-
await th.user(TEST_USER_ID).editMessage({
|
|
1304
|
-
messageId: queuedMsg.id,
|
|
1305
|
-
content: 'Reply with exactly: edited-queued. queue',
|
|
1306
|
-
})
|
|
1307
|
-
|
|
1308
|
-
// 5. Verify the queue item was updated.
|
|
1309
|
-
const updatedState = await waitForThreadState({
|
|
1310
|
-
threadId: thread.id,
|
|
1311
|
-
predicate: (state) => {
|
|
1312
|
-
return state.queueItems.some((item) => {
|
|
1313
|
-
return item.prompt.includes('edited-queued')
|
|
1314
|
-
})
|
|
1315
|
-
},
|
|
1316
|
-
timeout: 4_000,
|
|
1317
|
-
description: 'queue item updated after edit',
|
|
1318
|
-
})
|
|
1319
|
-
const updatedItem = updatedState.queueItems.find((item) => {
|
|
1320
|
-
return item.sourceMessageId === queuedMsg.id
|
|
1321
|
-
})
|
|
1322
|
-
expect(updatedItem).toBeTruthy()
|
|
1323
|
-
expect(updatedItem!.prompt).toContain('edited-queued')
|
|
1324
|
-
expect(updatedItem!.prompt).not.toContain('original-queued')
|
|
1325
|
-
|
|
1326
|
-
// 6. Wait for the queue to drain and verify the edited prompt was dispatched.
|
|
1327
|
-
// The drain indicator is a silent reply to the user's queued message.
|
|
1328
|
-
const indicator = await waitForBotReplyTo({
|
|
1329
|
-
discord,
|
|
1330
|
-
threadId: thread.id,
|
|
1331
|
-
replyToMessageId: queuedMsg.id,
|
|
1332
|
-
timeout: 8_000,
|
|
1333
|
-
})
|
|
1334
|
-
expect(indicator.content).toBe('-# Executing queued prompt')
|
|
1335
|
-
|
|
1336
|
-
await waitForFooterMessage({
|
|
1337
|
-
discord,
|
|
1338
|
-
threadId: thread.id,
|
|
1339
|
-
timeout: 8_000,
|
|
1340
|
-
afterMessageId: indicator.id,
|
|
1341
|
-
})
|
|
1342
|
-
|
|
1343
|
-
expect(await th.text()).toMatchInlineSnapshot(`
|
|
1344
|
-
"--- from: user (queue-tester)
|
|
1345
|
-
SLOW_BUSY_MARKER Reply with exactly: edit-queue-setup
|
|
1346
|
-
--- from: assistant (TestBot)
|
|
1347
|
-
-# *using deterministic-provider/deterministic-v2*
|
|
1348
|
-
--- from: user (queue-tester)
|
|
1349
|
-
Reply with exactly: edited-queued. queue
|
|
1350
|
-
--- from: assistant (TestBot)
|
|
1351
|
-
-# Queued at position 1. Edit or delete your message to update the queue
|
|
1352
|
-
-# **queue-tester** edited queued message
|
|
1353
|
-
slow-busy-reply
|
|
1354
|
-
-# *project ⋅ main ⋅ Ns ⋅ N% ⋅ deterministic-v2*
|
|
1355
|
-
-# Executing queued prompt
|
|
1356
|
-
ok
|
|
1357
|
-
-# *project ⋅ main ⋅ Ns ⋅ N% ⋅ deterministic-v2*"
|
|
1358
|
-
`)
|
|
1359
|
-
|
|
1360
|
-
const finalText = await th.text()
|
|
1361
|
-
expect(finalText).toContain('edited-queued')
|
|
1362
|
-
expect(finalText).not.toContain('original-queued')
|
|
1363
|
-
},
|
|
1364
|
-
12_000,
|
|
1365
|
-
)
|
|
1366
|
-
|
|
1367
|
-
test(
|
|
1368
|
-
'editing a queued message to remove queue suffix removes it from queue',
|
|
1369
|
-
async () => {
|
|
1370
|
-
// 1. Start a session with a slow matcher (2s busy) to keep it busy.
|
|
1371
|
-
await discord.channel(TEXT_CHANNEL_ID).user(TEST_USER_ID).sendMessage({
|
|
1372
|
-
content: 'SLOW_BUSY_MARKER Reply with exactly: remove-queue-setup',
|
|
1373
|
-
})
|
|
1374
|
-
|
|
1375
|
-
const thread = await discord.channel(TEXT_CHANNEL_ID).waitForThread({
|
|
1376
|
-
timeout: 4_000,
|
|
1377
|
-
predicate: (t) => {
|
|
1378
|
-
return t.name === 'SLOW_BUSY_MARKER Reply with exactly: remove-queue-setup'
|
|
1379
|
-
},
|
|
1380
|
-
})
|
|
1381
|
-
|
|
1382
|
-
const th = discord.thread(thread.id)
|
|
1383
|
-
|
|
1384
|
-
// Wait for the bot to start replying so the session is busy and
|
|
1385
|
-
// the user message appears after the first bot message in the thread.
|
|
1386
|
-
await th.waitForBotReply({ timeout: 4_000 })
|
|
1387
|
-
|
|
1388
|
-
// 2. Queue a message with queue suffix while session is busy.
|
|
1389
|
-
const queuedMsg = await th.user(TEST_USER_ID).sendMessage({
|
|
1390
|
-
content: 'Reply with exactly: will-be-removed. queue',
|
|
1391
|
-
})
|
|
1392
|
-
|
|
1393
|
-
// 3. Verify the message is in the queue.
|
|
1394
|
-
await waitForThreadState({
|
|
1395
|
-
threadId: thread.id,
|
|
1396
|
-
predicate: (state) => {
|
|
1397
|
-
return state.queueItems.some((item) => {
|
|
1398
|
-
return item.sourceMessageId === queuedMsg.id
|
|
1399
|
-
})
|
|
1400
|
-
},
|
|
1401
|
-
timeout: 4_000,
|
|
1402
|
-
description: 'queue has item to be removed',
|
|
1403
|
-
})
|
|
1404
|
-
|
|
1405
|
-
// 4. Edit the message to remove the queue suffix.
|
|
1406
|
-
await th.user(TEST_USER_ID).editMessage({
|
|
1407
|
-
messageId: queuedMsg.id,
|
|
1408
|
-
content: 'Reply with exactly: will-be-removed',
|
|
1409
|
-
})
|
|
1410
|
-
|
|
1411
|
-
// 5. Verify the item was removed from the queue.
|
|
1412
|
-
// Poll briefly since the MessageUpdate event is async.
|
|
1413
|
-
for (let i = 0; i < 20; i++) {
|
|
1414
|
-
const state = await waitForThreadState({
|
|
1415
|
-
threadId: thread.id,
|
|
1416
|
-
predicate: () => true,
|
|
1417
|
-
timeout: 100,
|
|
1418
|
-
description: 'check queue after edit',
|
|
1419
|
-
})
|
|
1420
|
-
const stillQueued = state.queueItems.some((item) => {
|
|
1421
|
-
return item.sourceMessageId === queuedMsg.id
|
|
1422
|
-
})
|
|
1423
|
-
if (!stillQueued) break
|
|
1424
|
-
await new Promise((r) => setTimeout(r, 50))
|
|
1425
|
-
}
|
|
1426
|
-
|
|
1427
|
-
const finalState = await waitForThreadState({
|
|
1428
|
-
threadId: thread.id,
|
|
1429
|
-
predicate: () => true,
|
|
1430
|
-
timeout: 100,
|
|
1431
|
-
description: 'final queue check',
|
|
1432
|
-
})
|
|
1433
|
-
const removedItem = finalState.queueItems.find((item) => {
|
|
1434
|
-
return item.sourceMessageId === queuedMsg.id
|
|
1435
|
-
})
|
|
1436
|
-
expect(removedItem).toBeUndefined()
|
|
1437
|
-
|
|
1438
|
-
// 6. Wait for the slow session to finish and verify the removed
|
|
1439
|
-
// message was never dispatched as a queue drain (no drain indicator).
|
|
1440
|
-
await waitForFooterMessage({
|
|
1441
|
-
discord,
|
|
1442
|
-
threadId: thread.id,
|
|
1443
|
-
timeout: 8_000,
|
|
1444
|
-
})
|
|
1445
|
-
|
|
1446
|
-
const finalText = await th.text()
|
|
1447
|
-
// The user message text appears in the thread, but the queue dispatch
|
|
1448
|
-
// indicator should NOT appear because the item was removed from the
|
|
1449
|
-
// queue before drain.
|
|
1450
|
-
expect(finalText).not.toContain('Executing queued prompt')
|
|
1451
|
-
expect(finalText).toMatchInlineSnapshot(`
|
|
1452
|
-
"--- from: user (queue-tester)
|
|
1453
|
-
SLOW_BUSY_MARKER Reply with exactly: remove-queue-setup
|
|
1454
|
-
--- from: assistant (TestBot)
|
|
1455
|
-
-# *using deterministic-provider/deterministic-v2*
|
|
1456
|
-
--- from: user (queue-tester)
|
|
1457
|
-
Reply with exactly: will-be-removed
|
|
1458
|
-
--- from: assistant (TestBot)
|
|
1459
|
-
-# Queued at position 1. Edit or delete your message to update the queue
|
|
1460
|
-
-# **queue-tester** removed message from queue
|
|
1461
|
-
slow-busy-reply
|
|
1462
|
-
-# *project ⋅ main ⋅ Ns ⋅ N% ⋅ deterministic-v2*"
|
|
1463
|
-
`)
|
|
1464
|
-
},
|
|
1465
|
-
12_000,
|
|
1466
|
-
)
|
|
1467
|
-
|
|
1468
|
-
})
|