@otto-assistant/bridge 0.4.92
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/bin.js +2 -0
- package/dist/agent-model.e2e.test.js +755 -0
- package/dist/ai-tool-to-genai.js +233 -0
- package/dist/ai-tool-to-genai.test.js +267 -0
- package/dist/ai-tool.js +6 -0
- package/dist/anthropic-auth-plugin.js +728 -0
- package/dist/anthropic-auth-plugin.test.js +125 -0
- package/dist/anthropic-auth-state.js +231 -0
- package/dist/bin.js +90 -0
- package/dist/channel-management.js +227 -0
- package/dist/cli-parsing.test.js +137 -0
- package/dist/cli-send-thread.e2e.test.js +356 -0
- package/dist/cli.js +3276 -0
- package/dist/commands/abort.js +65 -0
- package/dist/commands/action-buttons.js +245 -0
- package/dist/commands/add-project.js +113 -0
- package/dist/commands/agent.js +335 -0
- package/dist/commands/ask-question.js +274 -0
- package/dist/commands/btw.js +116 -0
- package/dist/commands/compact.js +120 -0
- package/dist/commands/context-usage.js +140 -0
- package/dist/commands/create-new-project.js +130 -0
- package/dist/commands/diff.js +63 -0
- package/dist/commands/file-upload.js +275 -0
- package/dist/commands/fork.js +220 -0
- package/dist/commands/gemini-apikey.js +70 -0
- package/dist/commands/login.js +885 -0
- package/dist/commands/mcp.js +239 -0
- package/dist/commands/memory-snapshot.js +24 -0
- package/dist/commands/mention-mode.js +44 -0
- package/dist/commands/merge-worktree.js +159 -0
- package/dist/commands/model-variant.js +364 -0
- package/dist/commands/model.js +776 -0
- package/dist/commands/new-worktree.js +366 -0
- package/dist/commands/paginated-select.js +57 -0
- package/dist/commands/permissions.js +274 -0
- package/dist/commands/queue.js +206 -0
- package/dist/commands/remove-project.js +115 -0
- package/dist/commands/restart-opencode-server.js +127 -0
- package/dist/commands/resume.js +149 -0
- package/dist/commands/run-command.js +79 -0
- package/dist/commands/screenshare.js +303 -0
- package/dist/commands/screenshare.test.js +20 -0
- package/dist/commands/session-id.js +78 -0
- package/dist/commands/session.js +176 -0
- package/dist/commands/share.js +80 -0
- package/dist/commands/tasks.js +205 -0
- package/dist/commands/types.js +2 -0
- package/dist/commands/undo-redo.js +305 -0
- package/dist/commands/unset-model.js +138 -0
- package/dist/commands/upgrade.js +42 -0
- package/dist/commands/user-command.js +155 -0
- package/dist/commands/verbosity.js +125 -0
- package/dist/commands/worktree-settings.js +43 -0
- package/dist/commands/worktrees.js +410 -0
- package/dist/condense-memory.js +33 -0
- package/dist/config.js +94 -0
- package/dist/context-awareness-plugin.js +363 -0
- package/dist/context-awareness-plugin.test.js +124 -0
- package/dist/critique-utils.js +95 -0
- package/dist/database.js +1310 -0
- package/dist/db.js +251 -0
- package/dist/db.test.js +138 -0
- package/dist/debounce-timeout.js +28 -0
- package/dist/debounced-process-flush.js +77 -0
- package/dist/discord-bot.js +1008 -0
- package/dist/discord-command-registration.js +524 -0
- package/dist/discord-urls.js +81 -0
- package/dist/discord-utils.js +591 -0
- package/dist/discord-utils.test.js +134 -0
- package/dist/errors.js +157 -0
- package/dist/escape-backticks.test.js +429 -0
- package/dist/event-stream-real-capture.e2e.test.js +533 -0
- package/dist/eventsource-parser.test.js +327 -0
- package/dist/exec-async.js +26 -0
- package/dist/external-opencode-sync.js +480 -0
- package/dist/format-tables.js +302 -0
- package/dist/format-tables.test.js +308 -0
- package/dist/forum-sync/config.js +79 -0
- package/dist/forum-sync/discord-operations.js +154 -0
- package/dist/forum-sync/index.js +5 -0
- package/dist/forum-sync/markdown.js +113 -0
- package/dist/forum-sync/sync-to-discord.js +417 -0
- package/dist/forum-sync/sync-to-files.js +190 -0
- package/dist/forum-sync/types.js +53 -0
- package/dist/forum-sync/watchers.js +307 -0
- package/dist/gateway-proxy-reconnect.e2e.test.js +394 -0
- package/dist/gateway-proxy.e2e.test.js +483 -0
- package/dist/genai-worker-wrapper.js +111 -0
- package/dist/genai-worker.js +311 -0
- package/dist/genai.js +232 -0
- package/dist/generated/browser.js +17 -0
- package/dist/generated/client.js +37 -0
- package/dist/generated/commonInputTypes.js +10 -0
- package/dist/generated/enums.js +52 -0
- package/dist/generated/internal/class.js +49 -0
- package/dist/generated/internal/prismaNamespace.js +253 -0
- package/dist/generated/internal/prismaNamespaceBrowser.js +223 -0
- package/dist/generated/models/bot_api_keys.js +1 -0
- package/dist/generated/models/bot_tokens.js +1 -0
- package/dist/generated/models/channel_agents.js +1 -0
- package/dist/generated/models/channel_directories.js +1 -0
- package/dist/generated/models/channel_mention_mode.js +1 -0
- package/dist/generated/models/channel_models.js +1 -0
- package/dist/generated/models/channel_verbosity.js +1 -0
- package/dist/generated/models/channel_worktrees.js +1 -0
- package/dist/generated/models/forum_sync_configs.js +1 -0
- package/dist/generated/models/global_models.js +1 -0
- package/dist/generated/models/ipc_requests.js +1 -0
- package/dist/generated/models/part_messages.js +1 -0
- package/dist/generated/models/scheduled_tasks.js +1 -0
- package/dist/generated/models/session_agents.js +1 -0
- package/dist/generated/models/session_events.js +1 -0
- package/dist/generated/models/session_models.js +1 -0
- package/dist/generated/models/session_start_sources.js +1 -0
- package/dist/generated/models/thread_sessions.js +1 -0
- package/dist/generated/models/thread_worktrees.js +1 -0
- package/dist/generated/models.js +1 -0
- package/dist/heap-monitor.js +122 -0
- package/dist/hrana-server.js +263 -0
- package/dist/hrana-server.test.js +370 -0
- package/dist/html-actions.js +123 -0
- package/dist/html-actions.test.js +70 -0
- package/dist/html-components.js +117 -0
- package/dist/html-components.test.js +34 -0
- package/dist/image-optimizer-plugin.js +153 -0
- package/dist/image-utils.js +112 -0
- package/dist/interaction-handler.js +397 -0
- package/dist/ipc-polling.js +252 -0
- package/dist/ipc-tools-plugin.js +193 -0
- package/dist/kimaki-digital-twin.e2e.test.js +161 -0
- package/dist/kimaki-opencode-plugin-loading.e2e.test.js +87 -0
- package/dist/kimaki-opencode-plugin.js +17 -0
- package/dist/kimaki-opencode-plugin.test.js +98 -0
- package/dist/limit-heading-depth.js +25 -0
- package/dist/limit-heading-depth.test.js +105 -0
- package/dist/logger.js +165 -0
- package/dist/markdown.js +342 -0
- package/dist/markdown.test.js +257 -0
- package/dist/message-finish-field.e2e.test.js +165 -0
- package/dist/message-formatting.js +413 -0
- package/dist/message-formatting.test.js +73 -0
- package/dist/message-preprocessing.js +330 -0
- package/dist/onboarding-tutorial.js +172 -0
- package/dist/onboarding-welcome.js +37 -0
- package/dist/openai-realtime.js +224 -0
- package/dist/opencode-command-detection.js +65 -0
- package/dist/opencode-command-detection.test.js +240 -0
- package/dist/opencode-command.js +129 -0
- package/dist/opencode-command.test.js +48 -0
- package/dist/opencode-interrupt-plugin.js +361 -0
- package/dist/opencode-interrupt-plugin.test.js +458 -0
- package/dist/opencode.js +861 -0
- package/dist/otto/branding.js +22 -0
- package/dist/otto/index.js +21 -0
- package/dist/parse-permission-rules.test.js +117 -0
- package/dist/patch-text-parser.js +97 -0
- package/dist/plugin-logger.js +59 -0
- package/dist/privacy-sanitizer.js +105 -0
- package/dist/queue-advanced-abort.e2e.test.js +293 -0
- package/dist/queue-advanced-action-buttons.e2e.test.js +206 -0
- package/dist/queue-advanced-e2e-setup.js +786 -0
- package/dist/queue-advanced-footer.e2e.test.js +472 -0
- package/dist/queue-advanced-model-switch.e2e.test.js +299 -0
- package/dist/queue-advanced-permissions-typing.e2e.test.js +180 -0
- package/dist/queue-advanced-question.e2e.test.js +261 -0
- package/dist/queue-advanced-typing-interrupt.e2e.test.js +114 -0
- package/dist/queue-advanced-typing.e2e.test.js +153 -0
- package/dist/queue-drain-after-interactive-ui.e2e.test.js +119 -0
- package/dist/queue-interrupt-drain.e2e.test.js +135 -0
- package/dist/queue-question-select-drain.e2e.test.js +120 -0
- package/dist/runtime-idle-sweeper.js +52 -0
- package/dist/runtime-lifecycle.e2e.test.js +508 -0
- package/dist/sentry.js +23 -0
- package/dist/session-handler/agent-utils.js +67 -0
- package/dist/session-handler/event-stream-state.js +420 -0
- package/dist/session-handler/event-stream-state.test.js +563 -0
- package/dist/session-handler/model-utils.js +124 -0
- package/dist/session-handler/opencode-session-event-log.js +94 -0
- package/dist/session-handler/thread-runtime-state.js +104 -0
- package/dist/session-handler/thread-session-runtime.js +3258 -0
- package/dist/session-handler.js +9 -0
- package/dist/session-search.js +100 -0
- package/dist/session-search.test.js +40 -0
- package/dist/session-title-rename.test.js +80 -0
- package/dist/startup-service.js +153 -0
- package/dist/startup-time.e2e.test.js +296 -0
- package/dist/store.js +17 -0
- package/dist/system-message.js +613 -0
- package/dist/system-message.test.js +602 -0
- package/dist/task-runner.js +295 -0
- package/dist/task-schedule.js +209 -0
- package/dist/task-schedule.test.js +71 -0
- package/dist/test-utils.js +299 -0
- package/dist/thinking-utils.js +35 -0
- package/dist/thread-message-queue.e2e.test.js +999 -0
- package/dist/tools.js +357 -0
- package/dist/undo-redo.e2e.test.js +161 -0
- package/dist/unnest-code-blocks.js +146 -0
- package/dist/unnest-code-blocks.test.js +673 -0
- package/dist/upgrade.js +114 -0
- package/dist/utils.js +144 -0
- package/dist/voice-attachment.js +34 -0
- package/dist/voice-handler.js +646 -0
- package/dist/voice-message.e2e.test.js +1021 -0
- package/dist/voice.js +447 -0
- package/dist/voice.test.js +235 -0
- package/dist/wait-session.js +94 -0
- package/dist/websockify.js +69 -0
- package/dist/worker-types.js +4 -0
- package/dist/worktree-lifecycle.e2e.test.js +308 -0
- package/dist/worktree-utils.js +3 -0
- package/dist/worktrees.js +929 -0
- package/dist/worktrees.test.js +189 -0
- package/dist/xml.js +92 -0
- package/dist/xml.test.js +32 -0
- package/package.json +98 -0
- package/schema.prisma +295 -0
- package/skills/batch/SKILL.md +87 -0
- package/skills/critique/SKILL.md +112 -0
- package/skills/egaki/SKILL.md +100 -0
- package/skills/errore/SKILL.md +647 -0
- package/skills/event-sourcing-state/SKILL.md +252 -0
- package/skills/gitchamber/SKILL.md +93 -0
- package/skills/goke/SKILL.md +644 -0
- package/skills/jitter/EDITOR.md +219 -0
- package/skills/jitter/EXPORT-INTERNALS.md +309 -0
- package/skills/jitter/SKILL.md +158 -0
- package/skills/jitter/jitter-clipboard.json +1042 -0
- package/skills/jitter/package.json +14 -0
- package/skills/jitter/tsconfig.json +15 -0
- package/skills/jitter/utils/actions.ts +212 -0
- package/skills/jitter/utils/export.ts +114 -0
- package/skills/jitter/utils/index.ts +141 -0
- package/skills/jitter/utils/snapshot.ts +154 -0
- package/skills/jitter/utils/traverse.ts +246 -0
- package/skills/jitter/utils/types.ts +279 -0
- package/skills/jitter/utils/wait.ts +133 -0
- package/skills/lintcn/SKILL.md +873 -0
- package/skills/new-skill/SKILL.md +211 -0
- package/skills/npm-package/SKILL.md +239 -0
- package/skills/playwriter/SKILL.md +35 -0
- package/skills/proxyman/SKILL.md +215 -0
- package/skills/security-review/SKILL.md +208 -0
- package/skills/simplify/SKILL.md +58 -0
- package/skills/spiceflow/SKILL.md +14 -0
- package/skills/termcast/SKILL.md +945 -0
- package/skills/tuistory/SKILL.md +250 -0
- package/skills/usecomputer/SKILL.md +264 -0
- package/skills/x-articles/SKILL.md +554 -0
- package/skills/zele/SKILL.md +112 -0
- package/skills/zustand-centralized-state/SKILL.md +1004 -0
- package/src/agent-model.e2e.test.ts +976 -0
- package/src/ai-tool-to-genai.test.ts +296 -0
- package/src/ai-tool-to-genai.ts +283 -0
- package/src/ai-tool.ts +39 -0
- package/src/anthropic-auth-plugin.test.ts +159 -0
- package/src/anthropic-auth-plugin.ts +861 -0
- package/src/anthropic-auth-state.ts +282 -0
- package/src/bin.ts +111 -0
- package/src/channel-management.ts +334 -0
- package/src/cli-parsing.test.ts +195 -0
- package/src/cli-send-thread.e2e.test.ts +464 -0
- package/src/cli.ts +4581 -0
- package/src/commands/abort.ts +89 -0
- package/src/commands/action-buttons.ts +364 -0
- package/src/commands/add-project.ts +149 -0
- package/src/commands/agent.ts +473 -0
- package/src/commands/ask-question.ts +390 -0
- package/src/commands/btw.ts +164 -0
- package/src/commands/compact.ts +157 -0
- package/src/commands/context-usage.ts +199 -0
- package/src/commands/create-new-project.ts +190 -0
- package/src/commands/diff.ts +91 -0
- package/src/commands/file-upload.ts +389 -0
- package/src/commands/fork.ts +321 -0
- package/src/commands/gemini-apikey.ts +104 -0
- package/src/commands/login.ts +1173 -0
- package/src/commands/mcp.ts +307 -0
- package/src/commands/memory-snapshot.ts +30 -0
- package/src/commands/mention-mode.ts +68 -0
- package/src/commands/merge-worktree.ts +223 -0
- package/src/commands/model-variant.ts +483 -0
- package/src/commands/model.ts +1053 -0
- package/src/commands/new-worktree.ts +510 -0
- package/src/commands/paginated-select.ts +81 -0
- package/src/commands/permissions.ts +397 -0
- package/src/commands/queue.ts +271 -0
- package/src/commands/remove-project.ts +155 -0
- package/src/commands/restart-opencode-server.ts +162 -0
- package/src/commands/resume.ts +230 -0
- package/src/commands/run-command.ts +123 -0
- package/src/commands/screenshare.test.ts +30 -0
- package/src/commands/screenshare.ts +366 -0
- package/src/commands/session-id.ts +109 -0
- package/src/commands/session.ts +227 -0
- package/src/commands/share.ts +106 -0
- package/src/commands/tasks.ts +293 -0
- package/src/commands/types.ts +25 -0
- package/src/commands/undo-redo.ts +386 -0
- package/src/commands/unset-model.ts +173 -0
- package/src/commands/upgrade.ts +52 -0
- package/src/commands/user-command.ts +198 -0
- package/src/commands/verbosity.ts +173 -0
- package/src/commands/worktree-settings.ts +70 -0
- package/src/commands/worktrees.ts +552 -0
- package/src/condense-memory.ts +36 -0
- package/src/config.ts +111 -0
- package/src/context-awareness-plugin.test.ts +142 -0
- package/src/context-awareness-plugin.ts +510 -0
- package/src/critique-utils.ts +139 -0
- package/src/database.ts +1876 -0
- package/src/db.test.ts +162 -0
- package/src/db.ts +286 -0
- package/src/debounce-timeout.ts +43 -0
- package/src/debounced-process-flush.ts +104 -0
- package/src/discord-bot.ts +1330 -0
- package/src/discord-command-registration.ts +693 -0
- package/src/discord-urls.ts +88 -0
- package/src/discord-utils.test.ts +153 -0
- package/src/discord-utils.ts +800 -0
- package/src/errors.ts +201 -0
- package/src/escape-backticks.test.ts +469 -0
- package/src/event-stream-real-capture.e2e.test.ts +692 -0
- package/src/eventsource-parser.test.ts +351 -0
- package/src/exec-async.ts +35 -0
- package/src/external-opencode-sync.ts +685 -0
- package/src/format-tables.test.ts +335 -0
- package/src/format-tables.ts +445 -0
- package/src/forum-sync/config.ts +92 -0
- package/src/forum-sync/discord-operations.ts +241 -0
- package/src/forum-sync/index.ts +9 -0
- package/src/forum-sync/markdown.ts +172 -0
- package/src/forum-sync/sync-to-discord.ts +595 -0
- package/src/forum-sync/sync-to-files.ts +294 -0
- package/src/forum-sync/types.ts +175 -0
- package/src/forum-sync/watchers.ts +454 -0
- package/src/gateway-proxy-reconnect.e2e.test.ts +523 -0
- package/src/gateway-proxy.e2e.test.ts +640 -0
- package/src/genai-worker-wrapper.ts +164 -0
- package/src/genai-worker.ts +386 -0
- package/src/genai.ts +321 -0
- package/src/generated/browser.ts +114 -0
- package/src/generated/client.ts +138 -0
- package/src/generated/commonInputTypes.ts +736 -0
- package/src/generated/enums.ts +88 -0
- package/src/generated/internal/class.ts +384 -0
- package/src/generated/internal/prismaNamespace.ts +2386 -0
- package/src/generated/internal/prismaNamespaceBrowser.ts +326 -0
- package/src/generated/models/bot_api_keys.ts +1288 -0
- package/src/generated/models/bot_tokens.ts +1656 -0
- package/src/generated/models/channel_agents.ts +1256 -0
- package/src/generated/models/channel_directories.ts +1859 -0
- package/src/generated/models/channel_mention_mode.ts +1300 -0
- package/src/generated/models/channel_models.ts +1288 -0
- package/src/generated/models/channel_verbosity.ts +1228 -0
- package/src/generated/models/channel_worktrees.ts +1300 -0
- package/src/generated/models/forum_sync_configs.ts +1452 -0
- package/src/generated/models/global_models.ts +1288 -0
- package/src/generated/models/ipc_requests.ts +1485 -0
- package/src/generated/models/part_messages.ts +1302 -0
- package/src/generated/models/scheduled_tasks.ts +2320 -0
- package/src/generated/models/session_agents.ts +1086 -0
- package/src/generated/models/session_events.ts +1439 -0
- package/src/generated/models/session_models.ts +1114 -0
- package/src/generated/models/session_start_sources.ts +1408 -0
- package/src/generated/models/thread_sessions.ts +1781 -0
- package/src/generated/models/thread_worktrees.ts +1356 -0
- package/src/generated/models.ts +30 -0
- package/src/heap-monitor.ts +152 -0
- package/src/hrana-server.test.ts +434 -0
- package/src/hrana-server.ts +314 -0
- package/src/html-actions.test.ts +87 -0
- package/src/html-actions.ts +174 -0
- package/src/html-components.test.ts +38 -0
- package/src/html-components.ts +181 -0
- package/src/image-optimizer-plugin.ts +194 -0
- package/src/image-utils.ts +149 -0
- package/src/interaction-handler.ts +576 -0
- package/src/ipc-polling.ts +326 -0
- package/src/ipc-tools-plugin.ts +236 -0
- package/src/kimaki-digital-twin.e2e.test.ts +199 -0
- package/src/kimaki-opencode-plugin-loading.e2e.test.ts +109 -0
- package/src/kimaki-opencode-plugin.test.ts +108 -0
- package/src/kimaki-opencode-plugin.ts +18 -0
- package/src/limit-heading-depth.test.ts +116 -0
- package/src/limit-heading-depth.ts +26 -0
- package/src/logger.ts +208 -0
- package/src/markdown.test.ts +308 -0
- package/src/markdown.ts +410 -0
- package/src/message-finish-field.e2e.test.ts +192 -0
- package/src/message-formatting.test.ts +81 -0
- package/src/message-formatting.ts +533 -0
- package/src/message-preprocessing.ts +455 -0
- package/src/onboarding-tutorial.ts +176 -0
- package/src/onboarding-welcome.ts +49 -0
- package/src/openai-realtime.ts +358 -0
- package/src/opencode-command-detection.test.ts +307 -0
- package/src/opencode-command-detection.ts +76 -0
- package/src/opencode-command.test.ts +70 -0
- package/src/opencode-command.ts +188 -0
- package/src/opencode-interrupt-plugin.test.ts +677 -0
- package/src/opencode-interrupt-plugin.ts +477 -0
- package/src/opencode.ts +1110 -0
- package/src/otto/branding.ts +23 -0
- package/src/otto/index.ts +22 -0
- package/src/parse-permission-rules.test.ts +127 -0
- package/src/patch-text-parser.ts +107 -0
- package/src/plugin-logger.ts +68 -0
- package/src/privacy-sanitizer.ts +142 -0
- package/src/queue-advanced-abort.e2e.test.ts +382 -0
- package/src/queue-advanced-action-buttons.e2e.test.ts +268 -0
- package/src/queue-advanced-e2e-setup.ts +873 -0
- package/src/queue-advanced-footer.e2e.test.ts +576 -0
- package/src/queue-advanced-model-switch.e2e.test.ts +383 -0
- package/src/queue-advanced-permissions-typing.e2e.test.ts +245 -0
- package/src/queue-advanced-question.e2e.test.ts +316 -0
- package/src/queue-advanced-typing-interrupt.e2e.test.ts +146 -0
- package/src/queue-advanced-typing.e2e.test.ts +199 -0
- package/src/queue-drain-after-interactive-ui.e2e.test.ts +151 -0
- package/src/queue-interrupt-drain.e2e.test.ts +166 -0
- package/src/queue-question-select-drain.e2e.test.ts +152 -0
- package/src/runtime-idle-sweeper.ts +76 -0
- package/src/runtime-lifecycle.e2e.test.ts +641 -0
- package/src/schema.sql +173 -0
- package/src/sentry.ts +26 -0
- package/src/session-handler/agent-utils.ts +97 -0
- package/src/session-handler/event-stream-fixtures/real-session-action-buttons.jsonl +45 -0
- package/src/session-handler/event-stream-fixtures/real-session-footer-suppressed-on-pre-idle-interrupt.jsonl +40 -0
- package/src/session-handler/event-stream-fixtures/real-session-permission-external-file.jsonl +23 -0
- package/src/session-handler/event-stream-fixtures/real-session-task-normal.jsonl +22 -0
- package/src/session-handler/event-stream-fixtures/real-session-task-three-parallel-sleeps.jsonl +277 -0
- package/src/session-handler/event-stream-fixtures/real-session-task-user-interruption.jsonl +46 -0
- package/src/session-handler/event-stream-fixtures/session-abort-after-idle-race.jsonl +21 -0
- package/src/session-handler/event-stream-fixtures/session-concurrent-messages-serialized.jsonl +56 -0
- package/src/session-handler/event-stream-fixtures/session-explicit-abort.jsonl +44 -0
- package/src/session-handler/event-stream-fixtures/session-normal-completion.jsonl +29 -0
- package/src/session-handler/event-stream-fixtures/session-tool-call-noisy-stream.jsonl +29 -0
- package/src/session-handler/event-stream-fixtures/session-two-completions-same-session.jsonl +50 -0
- package/src/session-handler/event-stream-fixtures/session-user-interruption.jsonl +59 -0
- package/src/session-handler/event-stream-fixtures/session-voice-queued-followup.jsonl +52 -0
- package/src/session-handler/event-stream-state.test.ts +645 -0
- package/src/session-handler/event-stream-state.ts +608 -0
- package/src/session-handler/model-utils.ts +183 -0
- package/src/session-handler/opencode-session-event-log.ts +130 -0
- package/src/session-handler/thread-runtime-state.ts +212 -0
- package/src/session-handler/thread-session-runtime.ts +4281 -0
- package/src/session-handler.ts +15 -0
- package/src/session-search.test.ts +50 -0
- package/src/session-search.ts +148 -0
- package/src/session-title-rename.test.ts +112 -0
- package/src/startup-service.ts +200 -0
- package/src/startup-time.e2e.test.ts +373 -0
- package/src/store.ts +122 -0
- package/src/system-message.test.ts +612 -0
- package/src/system-message.ts +723 -0
- package/src/task-runner.ts +421 -0
- package/src/task-schedule.test.ts +84 -0
- package/src/task-schedule.ts +311 -0
- package/src/test-utils.ts +435 -0
- package/src/thinking-utils.ts +61 -0
- package/src/thread-message-queue.e2e.test.ts +1219 -0
- package/src/tools.ts +430 -0
- package/src/undici.d.ts +12 -0
- package/src/undo-redo.e2e.test.ts +209 -0
- package/src/unnest-code-blocks.test.ts +713 -0
- package/src/unnest-code-blocks.ts +185 -0
- package/src/upgrade.ts +127 -0
- package/src/utils.ts +212 -0
- package/src/voice-attachment.ts +51 -0
- package/src/voice-handler.ts +908 -0
- package/src/voice-message.e2e.test.ts +1255 -0
- package/src/voice.test.ts +281 -0
- package/src/voice.ts +627 -0
- package/src/wait-session.ts +147 -0
- package/src/websockify.ts +101 -0
- package/src/worker-types.ts +64 -0
- package/src/worktree-lifecycle.e2e.test.ts +391 -0
- package/src/worktree-utils.ts +4 -0
- package/src/worktrees.test.ts +223 -0
- package/src/worktrees.ts +1294 -0
- package/src/xml.test.ts +38 -0
- package/src/xml.ts +121 -0
|
@@ -0,0 +1,464 @@
|
|
|
1
|
+
// E2e test for `kimaki send --channel` flow.
|
|
2
|
+
// Reproduces the race condition where the bot's MessageCreate GuildText handler
|
|
3
|
+
// tries to call startThread() on the same message that the CLI already created
|
|
4
|
+
// a thread for via REST, causing DiscordAPIError[160004].
|
|
5
|
+
//
|
|
6
|
+
// The test simulates the exact flow: bot posts a starter message with a
|
|
7
|
+
// `start: true` embed marker, then creates a thread on that message via REST.
|
|
8
|
+
// The ThreadCreate handler should pick it up and start a session. The
|
|
9
|
+
// MessageCreate handler must NOT try to startThread() on the same message.
|
|
10
|
+
//
|
|
11
|
+
// Uses opencode-deterministic-provider (no real LLM calls).
|
|
12
|
+
// Poll timeouts: 4s max, 100ms interval.
|
|
13
|
+
|
|
14
|
+
import fs from 'node:fs'
|
|
15
|
+
import path from 'node:path'
|
|
16
|
+
import url from 'node:url'
|
|
17
|
+
import { describe, beforeAll, afterAll, test, expect } from 'vitest'
|
|
18
|
+
import {
|
|
19
|
+
ChannelType,
|
|
20
|
+
Client,
|
|
21
|
+
GatewayIntentBits,
|
|
22
|
+
Partials,
|
|
23
|
+
Routes,
|
|
24
|
+
} from 'discord.js'
|
|
25
|
+
import { DigitalDiscord } from 'discord-digital-twin/src'
|
|
26
|
+
import {
|
|
27
|
+
buildDeterministicOpencodeConfig,
|
|
28
|
+
type DeterministicMatcher,
|
|
29
|
+
} from 'opencode-deterministic-provider'
|
|
30
|
+
import { setDataDir } from './config.js'
|
|
31
|
+
import { store } from './store.js'
|
|
32
|
+
import { startDiscordBot } from './discord-bot.js'
|
|
33
|
+
import {
|
|
34
|
+
setBotToken,
|
|
35
|
+
initDatabase,
|
|
36
|
+
closeDatabase,
|
|
37
|
+
setChannelDirectory,
|
|
38
|
+
setChannelVerbosity,
|
|
39
|
+
type VerbosityLevel,
|
|
40
|
+
} from './database.js'
|
|
41
|
+
import { startHranaServer, stopHranaServer } from './hrana-server.js'
|
|
42
|
+
import {
|
|
43
|
+
initializeOpencodeForDirectory,
|
|
44
|
+
stopOpencodeServer,
|
|
45
|
+
} from './opencode.js'
|
|
46
|
+
import {
|
|
47
|
+
chooseLockPort,
|
|
48
|
+
cleanupTestSessions,
|
|
49
|
+
initTestGitRepo,
|
|
50
|
+
waitForBotMessageContaining,
|
|
51
|
+
waitForFooterMessage,
|
|
52
|
+
} from './test-utils.js'
|
|
53
|
+
import YAML from 'yaml'
|
|
54
|
+
import type { ThreadStartMarker } from './system-message.js'
|
|
55
|
+
|
|
56
|
+
const TEST_USER_ID = '200000000000000830'
|
|
57
|
+
const TEXT_CHANNEL_ID = '200000000000000831'
|
|
58
|
+
const BOT_USER_ID = '200000000000000832'
|
|
59
|
+
|
|
60
|
+
function createRunDirectories() {
|
|
61
|
+
const root = path.resolve(process.cwd(), 'tmp', 'cli-send-thread-e2e')
|
|
62
|
+
fs.mkdirSync(root, { recursive: true })
|
|
63
|
+
const dataDir = fs.mkdtempSync(path.join(root, 'data-'))
|
|
64
|
+
const projectDirectory = path.join(root, 'project')
|
|
65
|
+
fs.mkdirSync(projectDirectory, { recursive: true })
|
|
66
|
+
initTestGitRepo(projectDirectory)
|
|
67
|
+
return { root, dataDir, projectDirectory }
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
function createDiscordJsClient({ restUrl }: { restUrl: string }) {
|
|
71
|
+
return new Client({
|
|
72
|
+
intents: [
|
|
73
|
+
GatewayIntentBits.Guilds,
|
|
74
|
+
GatewayIntentBits.GuildMessages,
|
|
75
|
+
GatewayIntentBits.MessageContent,
|
|
76
|
+
GatewayIntentBits.GuildVoiceStates,
|
|
77
|
+
],
|
|
78
|
+
partials: [
|
|
79
|
+
Partials.Channel,
|
|
80
|
+
Partials.Message,
|
|
81
|
+
Partials.User,
|
|
82
|
+
Partials.ThreadMember,
|
|
83
|
+
],
|
|
84
|
+
rest: {
|
|
85
|
+
api: restUrl,
|
|
86
|
+
version: '10',
|
|
87
|
+
},
|
|
88
|
+
})
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
function createDeterministicMatchers(): DeterministicMatcher[] {
|
|
92
|
+
const userReplyMatcher: DeterministicMatcher = {
|
|
93
|
+
id: 'user-reply',
|
|
94
|
+
priority: 10,
|
|
95
|
+
when: {
|
|
96
|
+
lastMessageRole: 'user',
|
|
97
|
+
latestUserTextIncludes: 'Reply with exactly:',
|
|
98
|
+
},
|
|
99
|
+
then: {
|
|
100
|
+
parts: [
|
|
101
|
+
{ type: 'stream-start', warnings: [] },
|
|
102
|
+
{ type: 'text-start', id: 'default-reply' },
|
|
103
|
+
{ type: 'text-delta', id: 'default-reply', delta: 'ok' },
|
|
104
|
+
{ type: 'text-end', id: 'default-reply' },
|
|
105
|
+
{
|
|
106
|
+
type: 'finish',
|
|
107
|
+
finishReason: 'stop',
|
|
108
|
+
usage: { inputTokens: 1, outputTokens: 1, totalTokens: 2 },
|
|
109
|
+
},
|
|
110
|
+
],
|
|
111
|
+
partDelaysMs: [0, 100, 0, 0, 0],
|
|
112
|
+
},
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
// Catch-all: any user message gets a reply
|
|
116
|
+
const catchAll: DeterministicMatcher = {
|
|
117
|
+
id: 'catch-all',
|
|
118
|
+
priority: 0,
|
|
119
|
+
when: { lastMessageRole: 'user' },
|
|
120
|
+
then: {
|
|
121
|
+
parts: [
|
|
122
|
+
{ type: 'stream-start', warnings: [] },
|
|
123
|
+
{ type: 'text-start', id: 'catch' },
|
|
124
|
+
{ type: 'text-delta', id: 'catch', delta: 'caught-by-model' },
|
|
125
|
+
{ type: 'text-end', id: 'catch' },
|
|
126
|
+
{
|
|
127
|
+
type: 'finish',
|
|
128
|
+
finishReason: 'stop',
|
|
129
|
+
usage: { inputTokens: 1, outputTokens: 1, totalTokens: 2 },
|
|
130
|
+
},
|
|
131
|
+
],
|
|
132
|
+
},
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
return [userReplyMatcher, catchAll]
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
describe('kimaki send --channel thread creation', () => {
|
|
139
|
+
let directories: ReturnType<typeof createRunDirectories>
|
|
140
|
+
let discord: DigitalDiscord
|
|
141
|
+
let botClient: Client
|
|
142
|
+
let previousDefaultVerbosity: VerbosityLevel | null = null
|
|
143
|
+
let testStartTime = Date.now()
|
|
144
|
+
|
|
145
|
+
beforeAll(async () => {
|
|
146
|
+
testStartTime = Date.now()
|
|
147
|
+
directories = createRunDirectories()
|
|
148
|
+
const lockPort = chooseLockPort({ key: TEXT_CHANNEL_ID })
|
|
149
|
+
|
|
150
|
+
process.env['KIMAKI_LOCK_PORT'] = String(lockPort)
|
|
151
|
+
setDataDir(directories.dataDir)
|
|
152
|
+
previousDefaultVerbosity = store.getState().defaultVerbosity
|
|
153
|
+
store.setState({ defaultVerbosity: 'tools_and_text' })
|
|
154
|
+
|
|
155
|
+
const digitalDiscordDbPath = path.join(
|
|
156
|
+
directories.dataDir,
|
|
157
|
+
'digital-discord.db',
|
|
158
|
+
)
|
|
159
|
+
|
|
160
|
+
discord = new DigitalDiscord({
|
|
161
|
+
botUser: { id: BOT_USER_ID },
|
|
162
|
+
guild: {
|
|
163
|
+
name: 'CLI Send E2E Guild',
|
|
164
|
+
// Use bot as guild owner so bot-authored messages pass
|
|
165
|
+
// hasKimakiBotPermission (owner check). This matches production where
|
|
166
|
+
// the bot typically has admin or is the app owner. Without this, the
|
|
167
|
+
// MessageCreate handler drops bot messages before reaching the GuildText
|
|
168
|
+
// path, hiding the race condition we're testing.
|
|
169
|
+
ownerId: BOT_USER_ID,
|
|
170
|
+
},
|
|
171
|
+
channels: [
|
|
172
|
+
{
|
|
173
|
+
id: TEXT_CHANNEL_ID,
|
|
174
|
+
name: 'cli-send-e2e',
|
|
175
|
+
type: ChannelType.GuildText,
|
|
176
|
+
},
|
|
177
|
+
],
|
|
178
|
+
users: [
|
|
179
|
+
{
|
|
180
|
+
id: TEST_USER_ID,
|
|
181
|
+
username: 'cli-send-tester',
|
|
182
|
+
},
|
|
183
|
+
],
|
|
184
|
+
dbUrl: `file:${digitalDiscordDbPath}`,
|
|
185
|
+
})
|
|
186
|
+
|
|
187
|
+
await discord.start()
|
|
188
|
+
|
|
189
|
+
const providerNpm = url
|
|
190
|
+
.pathToFileURL(
|
|
191
|
+
path.resolve(
|
|
192
|
+
process.cwd(),
|
|
193
|
+
'..',
|
|
194
|
+
'opencode-deterministic-provider',
|
|
195
|
+
'src',
|
|
196
|
+
'index.ts',
|
|
197
|
+
),
|
|
198
|
+
)
|
|
199
|
+
.toString()
|
|
200
|
+
|
|
201
|
+
const opencodeConfig = buildDeterministicOpencodeConfig({
|
|
202
|
+
providerName: 'deterministic-provider',
|
|
203
|
+
providerNpm,
|
|
204
|
+
model: 'deterministic-v2',
|
|
205
|
+
smallModel: 'deterministic-v2',
|
|
206
|
+
settings: {
|
|
207
|
+
strict: false,
|
|
208
|
+
matchers: createDeterministicMatchers(),
|
|
209
|
+
},
|
|
210
|
+
})
|
|
211
|
+
fs.writeFileSync(
|
|
212
|
+
path.join(directories.projectDirectory, 'opencode.json'),
|
|
213
|
+
JSON.stringify(opencodeConfig, null, 2),
|
|
214
|
+
)
|
|
215
|
+
|
|
216
|
+
const dbPath = path.join(directories.dataDir, 'discord-sessions.db')
|
|
217
|
+
const hranaResult = await startHranaServer({ dbPath })
|
|
218
|
+
if (hranaResult instanceof Error) {
|
|
219
|
+
throw hranaResult
|
|
220
|
+
}
|
|
221
|
+
process.env['KIMAKI_DB_URL'] = hranaResult
|
|
222
|
+
await initDatabase()
|
|
223
|
+
await setBotToken(discord.botUserId, discord.botToken)
|
|
224
|
+
|
|
225
|
+
await setChannelDirectory({
|
|
226
|
+
channelId: TEXT_CHANNEL_ID,
|
|
227
|
+
directory: directories.projectDirectory,
|
|
228
|
+
channelType: 'text',
|
|
229
|
+
})
|
|
230
|
+
await setChannelVerbosity(TEXT_CHANNEL_ID, 'tools_and_text')
|
|
231
|
+
|
|
232
|
+
botClient = createDiscordJsClient({ restUrl: discord.restUrl })
|
|
233
|
+
await startDiscordBot({
|
|
234
|
+
token: discord.botToken,
|
|
235
|
+
appId: discord.botUserId,
|
|
236
|
+
discordClient: botClient,
|
|
237
|
+
})
|
|
238
|
+
|
|
239
|
+
// Pre-warm the opencode server
|
|
240
|
+
const warmup = await initializeOpencodeForDirectory(
|
|
241
|
+
directories.projectDirectory,
|
|
242
|
+
)
|
|
243
|
+
if (warmup instanceof Error) {
|
|
244
|
+
throw warmup
|
|
245
|
+
}
|
|
246
|
+
}, 60_000)
|
|
247
|
+
|
|
248
|
+
afterAll(async () => {
|
|
249
|
+
if (directories) {
|
|
250
|
+
await cleanupTestSessions({
|
|
251
|
+
projectDirectory: directories.projectDirectory,
|
|
252
|
+
testStartTime,
|
|
253
|
+
})
|
|
254
|
+
}
|
|
255
|
+
if (botClient) {
|
|
256
|
+
botClient.destroy()
|
|
257
|
+
}
|
|
258
|
+
await stopOpencodeServer()
|
|
259
|
+
await Promise.all([
|
|
260
|
+
closeDatabase().catch(() => {
|
|
261
|
+
return
|
|
262
|
+
}),
|
|
263
|
+
stopHranaServer().catch(() => {
|
|
264
|
+
return
|
|
265
|
+
}),
|
|
266
|
+
discord?.stop().catch(() => {
|
|
267
|
+
return
|
|
268
|
+
}),
|
|
269
|
+
])
|
|
270
|
+
delete process.env['KIMAKI_LOCK_PORT']
|
|
271
|
+
delete process.env['KIMAKI_DB_URL']
|
|
272
|
+
if (previousDefaultVerbosity) {
|
|
273
|
+
store.setState({ defaultVerbosity: previousDefaultVerbosity })
|
|
274
|
+
}
|
|
275
|
+
if (directories) {
|
|
276
|
+
fs.rmSync(directories.dataDir, { recursive: true, force: true })
|
|
277
|
+
}
|
|
278
|
+
}, 10_000)
|
|
279
|
+
|
|
280
|
+
test(
|
|
281
|
+
'kimaki send --prompt "/hello-test-cmd" falls through as text when registeredUserCommands is empty (repro #97)',
|
|
282
|
+
async () => {
|
|
283
|
+
// Reproduce GitHub #97: when registeredUserCommands is empty (gateway mode
|
|
284
|
+
// startup race, or backgroundInit not complete), the prompt "/hello-test-cmd"
|
|
285
|
+
// is NOT detected as a command and is sent to the model as plain text.
|
|
286
|
+
|
|
287
|
+
const prevCommands = store.getState().registeredUserCommands
|
|
288
|
+
// Ensure store is empty — this is the bug condition
|
|
289
|
+
store.setState({ registeredUserCommands: [] })
|
|
290
|
+
|
|
291
|
+
try {
|
|
292
|
+
const prompt = '/hello-test-cmd'
|
|
293
|
+
const embedMarker: ThreadStartMarker = {
|
|
294
|
+
start: true,
|
|
295
|
+
username: 'cli-send-tester',
|
|
296
|
+
userId: TEST_USER_ID,
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
const starterMessage = (await botClient.rest.post(
|
|
300
|
+
Routes.channelMessages(TEXT_CHANNEL_ID),
|
|
301
|
+
{
|
|
302
|
+
body: {
|
|
303
|
+
content: prompt,
|
|
304
|
+
embeds: [
|
|
305
|
+
{ color: 0x2b2d31, footer: { text: YAML.stringify(embedMarker) } },
|
|
306
|
+
],
|
|
307
|
+
},
|
|
308
|
+
},
|
|
309
|
+
)) as { id: string }
|
|
310
|
+
|
|
311
|
+
await new Promise((resolve) => {
|
|
312
|
+
setTimeout(resolve, 200)
|
|
313
|
+
})
|
|
314
|
+
|
|
315
|
+
const threadData = (await botClient.rest.post(
|
|
316
|
+
Routes.threads(TEXT_CHANNEL_ID, starterMessage.id),
|
|
317
|
+
{
|
|
318
|
+
body: { name: 'cmd-detection-test', auto_archive_duration: 1440 },
|
|
319
|
+
},
|
|
320
|
+
)) as { id: string }
|
|
321
|
+
|
|
322
|
+
await botClient.rest.put(
|
|
323
|
+
Routes.threadMembers(threadData.id, TEST_USER_ID),
|
|
324
|
+
)
|
|
325
|
+
|
|
326
|
+
// Wait for any bot reply AFTER the starter message
|
|
327
|
+
await waitForBotMessageContaining({
|
|
328
|
+
discord,
|
|
329
|
+
threadId: threadData.id,
|
|
330
|
+
userId: discord.botUserId,
|
|
331
|
+
text: '',
|
|
332
|
+
afterMessageId: starterMessage.id,
|
|
333
|
+
timeout: 4_000,
|
|
334
|
+
})
|
|
335
|
+
|
|
336
|
+
const messages = await discord.thread(threadData.id).getMessages()
|
|
337
|
+
const botReplies = messages.filter((m) => {
|
|
338
|
+
return m.author.id === discord.botUserId && m.id !== starterMessage.id
|
|
339
|
+
})
|
|
340
|
+
|
|
341
|
+
const allContent = botReplies.map((m) => {
|
|
342
|
+
return m.content.slice(0, 200)
|
|
343
|
+
})
|
|
344
|
+
expect(allContent).toMatchInlineSnapshot(`
|
|
345
|
+
[
|
|
346
|
+
"✗ opencode session error: Command not found: "hello-test". Available commands: init, review, goke, security-review, jitter, proxyman, gitchamber, event-sourcing-state, usecomputer, spiceflow, batch, x",
|
|
347
|
+
"✗ OpenCode API error: Command not found: "hello-test". Available commands: init, review, goke, security-review, jitter, proxyman, gitchamber, event-sourcing-state, usecomputer, spiceflow, batch, x-art",
|
|
348
|
+
]
|
|
349
|
+
`)
|
|
350
|
+
} finally {
|
|
351
|
+
store.setState({ registeredUserCommands: prevCommands })
|
|
352
|
+
}
|
|
353
|
+
},
|
|
354
|
+
15_000,
|
|
355
|
+
)
|
|
356
|
+
|
|
357
|
+
test(
|
|
358
|
+
'bot-posted starter message with start marker creates thread without DiscordAPIError[160004]',
|
|
359
|
+
async () => {
|
|
360
|
+
// Simulate what `kimaki send --channel` does:
|
|
361
|
+
// 1. Bot posts a starter message with `start: true` embed marker
|
|
362
|
+
// 2. Bot creates a thread on that message via REST
|
|
363
|
+
// The ThreadCreate handler should pick it up. The MessageCreate GuildText
|
|
364
|
+
// handler must NOT try to startThread() on the same message (race).
|
|
365
|
+
|
|
366
|
+
const prompt = 'Reply with exactly: cli-send-test'
|
|
367
|
+
const embedMarker: ThreadStartMarker = {
|
|
368
|
+
start: true,
|
|
369
|
+
username: 'cli-send-tester',
|
|
370
|
+
userId: TEST_USER_ID,
|
|
371
|
+
}
|
|
372
|
+
|
|
373
|
+
// Step 1: Bot posts the starter message (same as CLI's sendDiscordMessageWithOptionalAttachment)
|
|
374
|
+
const starterMessage = (await botClient.rest.post(
|
|
375
|
+
Routes.channelMessages(TEXT_CHANNEL_ID),
|
|
376
|
+
{
|
|
377
|
+
body: {
|
|
378
|
+
content: prompt,
|
|
379
|
+
embeds: [
|
|
380
|
+
{ color: 0x2b2d31, footer: { text: YAML.stringify(embedMarker) } },
|
|
381
|
+
],
|
|
382
|
+
},
|
|
383
|
+
},
|
|
384
|
+
)) as { id: string }
|
|
385
|
+
|
|
386
|
+
// Give the bot's MessageCreate handler time to process the starter
|
|
387
|
+
// message. Without the fix, the handler enters the GuildText path and
|
|
388
|
+
// tries to startThread() on this message, which races the CLI's thread
|
|
389
|
+
// creation below. The digital twin enforces Discord's 160004 uniqueness
|
|
390
|
+
// constraint, so the second startThread call fails.
|
|
391
|
+
await new Promise((resolve) => {
|
|
392
|
+
setTimeout(resolve, 200)
|
|
393
|
+
})
|
|
394
|
+
|
|
395
|
+
// Verify the MessageCreate handler did NOT create a thread on this
|
|
396
|
+
// message. If the handler ignored the start marker (correct behavior),
|
|
397
|
+
// no thread exists yet and the REST call below succeeds.
|
|
398
|
+
const threadsBeforeCliCreate = await discord
|
|
399
|
+
.channel(TEXT_CHANNEL_ID)
|
|
400
|
+
.getThreads()
|
|
401
|
+
const preExistingThread = threadsBeforeCliCreate.find((t) => {
|
|
402
|
+
return t.name?.includes('cli-send-test')
|
|
403
|
+
})
|
|
404
|
+
// This is the core regression assertion: without the fix in discord-bot.ts
|
|
405
|
+
// (skipping start markers in the GuildText handler), the MessageCreate
|
|
406
|
+
// handler would create a thread here, and the CLI's REST call below would
|
|
407
|
+
// fail with 160004.
|
|
408
|
+
expect(preExistingThread).toBeUndefined()
|
|
409
|
+
|
|
410
|
+
// Step 2: Bot creates a thread on the starter message (same as CLI's Routes.threads call)
|
|
411
|
+
const threadData = (await botClient.rest.post(
|
|
412
|
+
Routes.threads(TEXT_CHANNEL_ID, starterMessage.id),
|
|
413
|
+
{
|
|
414
|
+
body: {
|
|
415
|
+
name: 'cli-send-test',
|
|
416
|
+
auto_archive_duration: 1440,
|
|
417
|
+
},
|
|
418
|
+
},
|
|
419
|
+
)) as { id: string; name: string }
|
|
420
|
+
|
|
421
|
+
// Add test user to thread
|
|
422
|
+
await botClient.rest.put(
|
|
423
|
+
Routes.threadMembers(threadData.id, TEST_USER_ID),
|
|
424
|
+
)
|
|
425
|
+
|
|
426
|
+
// Wait for the bot to reply with the ⬥ prefix (proves ThreadCreate
|
|
427
|
+
// handler picked up the starter message and started a session)
|
|
428
|
+
await waitForBotMessageContaining({
|
|
429
|
+
discord,
|
|
430
|
+
threadId: threadData.id,
|
|
431
|
+
userId: discord.botUserId,
|
|
432
|
+
text: '⬥',
|
|
433
|
+
timeout: 4_000,
|
|
434
|
+
})
|
|
435
|
+
|
|
436
|
+
// Wait for footer message (proves session completed successfully)
|
|
437
|
+
await waitForFooterMessage({
|
|
438
|
+
discord,
|
|
439
|
+
threadId: threadData.id,
|
|
440
|
+
timeout: 4_000,
|
|
441
|
+
afterMessageIncludes: '⬥',
|
|
442
|
+
afterAuthorId: discord.botUserId,
|
|
443
|
+
})
|
|
444
|
+
|
|
445
|
+
// Verify no DiscordAPIError[160004] or other errors in the thread.
|
|
446
|
+
// Before the fix, the MessageCreate GuildText handler would race the
|
|
447
|
+
// CLI's thread creation and produce an error message here.
|
|
448
|
+
const messages = await discord.thread(threadData.id).getMessages()
|
|
449
|
+
const errorMessages = messages.filter((m) => {
|
|
450
|
+
return m.content.includes('Error:') || m.content.includes('160004')
|
|
451
|
+
})
|
|
452
|
+
expect(errorMessages).toHaveLength(0)
|
|
453
|
+
|
|
454
|
+
// Verify at least one ⬥ reply exists (session produced output)
|
|
455
|
+
const botReplies = messages.filter((m) => {
|
|
456
|
+
return (
|
|
457
|
+
m.author.id === discord.botUserId && m.content.startsWith('⬥')
|
|
458
|
+
)
|
|
459
|
+
})
|
|
460
|
+
expect(botReplies.length).toBeGreaterThanOrEqual(1)
|
|
461
|
+
},
|
|
462
|
+
15_000,
|
|
463
|
+
)
|
|
464
|
+
})
|