@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,397 @@
|
|
|
1
|
+
// Permission button handler - Shows buttons for permission requests.
|
|
2
|
+
// When OpenCode asks for permission, this module renders 3 buttons:
|
|
3
|
+
// Accept, Accept Always, and Deny.
|
|
4
|
+
|
|
5
|
+
import {
|
|
6
|
+
ButtonBuilder,
|
|
7
|
+
ButtonStyle,
|
|
8
|
+
type ButtonInteraction,
|
|
9
|
+
ActionRowBuilder,
|
|
10
|
+
type ThreadChannel,
|
|
11
|
+
MessageFlags,
|
|
12
|
+
} from 'discord.js'
|
|
13
|
+
import crypto from 'node:crypto'
|
|
14
|
+
import type { PermissionRequest } from '@opencode-ai/sdk/v2'
|
|
15
|
+
import { getOpencodeClient } from '../opencode.js'
|
|
16
|
+
import { NOTIFY_MESSAGE_FLAGS } from '../discord-utils.js'
|
|
17
|
+
import { createLogger, LogPrefix } from '../logger.js'
|
|
18
|
+
|
|
19
|
+
const logger = createLogger(LogPrefix.PERMISSIONS)
|
|
20
|
+
|
|
21
|
+
function wildcardMatch({
|
|
22
|
+
value,
|
|
23
|
+
pattern,
|
|
24
|
+
}: {
|
|
25
|
+
value: string
|
|
26
|
+
pattern: string
|
|
27
|
+
}): boolean {
|
|
28
|
+
let escapedPattern = pattern
|
|
29
|
+
.replace(/[.+^${}()|[\]\\]/g, '\\$&')
|
|
30
|
+
.replace(/\*/g, '.*')
|
|
31
|
+
.replace(/\?/g, '.')
|
|
32
|
+
|
|
33
|
+
if (escapedPattern.endsWith(' .*')) {
|
|
34
|
+
escapedPattern = escapedPattern.slice(0, -3) + '( .*)?'
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
return new RegExp(`^${escapedPattern}$`, 's').test(value)
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
export function arePatternsCoveredBy({
|
|
41
|
+
patterns,
|
|
42
|
+
coveringPatterns,
|
|
43
|
+
}: {
|
|
44
|
+
patterns: string[]
|
|
45
|
+
coveringPatterns: string[]
|
|
46
|
+
}): boolean {
|
|
47
|
+
return patterns.every((pattern) => {
|
|
48
|
+
return coveringPatterns.some((coveringPattern) => {
|
|
49
|
+
return wildcardMatch({ value: pattern, pattern: coveringPattern })
|
|
50
|
+
})
|
|
51
|
+
})
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
export function compactPermissionPatterns(patterns: string[]): string[] {
|
|
55
|
+
const uniquePatterns = Array.from(new Set(patterns))
|
|
56
|
+
return uniquePatterns.filter((pattern, index) => {
|
|
57
|
+
return !uniquePatterns.some((candidate, candidateIndex) => {
|
|
58
|
+
if (candidateIndex === index) {
|
|
59
|
+
return false
|
|
60
|
+
}
|
|
61
|
+
return wildcardMatch({ value: pattern, pattern: candidate })
|
|
62
|
+
})
|
|
63
|
+
})
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
type PendingPermissionContext = {
|
|
67
|
+
permission: PermissionRequest
|
|
68
|
+
requestIds: string[]
|
|
69
|
+
directory: string
|
|
70
|
+
permissionDirectory: string
|
|
71
|
+
thread: ThreadChannel
|
|
72
|
+
contextHash: string
|
|
73
|
+
messageId?: string
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
// Store pending permission contexts by hash.
|
|
77
|
+
// TTL prevents unbounded growth if user never clicks a permission button.
|
|
78
|
+
const PERMISSION_CONTEXT_TTL_MS = 10 * 60 * 1000
|
|
79
|
+
export const pendingPermissionContexts = new Map<
|
|
80
|
+
string,
|
|
81
|
+
PendingPermissionContext
|
|
82
|
+
>()
|
|
83
|
+
|
|
84
|
+
// Atomic take: removes context from Map and returns it. Only the first caller
|
|
85
|
+
// (TTL expiry or button click) wins, preventing duplicate permission replies.
|
|
86
|
+
function takePendingPermissionContext(contextHash: string): PendingPermissionContext | undefined {
|
|
87
|
+
const ctx = pendingPermissionContexts.get(contextHash)
|
|
88
|
+
if (!ctx) {
|
|
89
|
+
return undefined
|
|
90
|
+
}
|
|
91
|
+
pendingPermissionContexts.delete(contextHash)
|
|
92
|
+
return ctx
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
/**
|
|
96
|
+
* Show permission buttons for a permission request.
|
|
97
|
+
* Displays 3 buttons in a row: Accept, Accept Always, Deny.
|
|
98
|
+
* Returns the message ID and context hash for tracking.
|
|
99
|
+
*/
|
|
100
|
+
export async function showPermissionButtons({
|
|
101
|
+
thread,
|
|
102
|
+
permission,
|
|
103
|
+
directory,
|
|
104
|
+
permissionDirectory,
|
|
105
|
+
subtaskLabel,
|
|
106
|
+
}: {
|
|
107
|
+
thread: ThreadChannel
|
|
108
|
+
permission: PermissionRequest
|
|
109
|
+
directory: string
|
|
110
|
+
permissionDirectory: string
|
|
111
|
+
subtaskLabel?: string
|
|
112
|
+
}): Promise<{ messageId: string; contextHash: string }> {
|
|
113
|
+
const contextHash = crypto.randomBytes(8).toString('hex')
|
|
114
|
+
|
|
115
|
+
const context: PendingPermissionContext = {
|
|
116
|
+
permission,
|
|
117
|
+
requestIds: [permission.id],
|
|
118
|
+
directory,
|
|
119
|
+
permissionDirectory,
|
|
120
|
+
thread,
|
|
121
|
+
contextHash,
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
pendingPermissionContexts.set(contextHash, context)
|
|
125
|
+
// Auto-reject on TTL expiry so the OpenCode session doesn't hang forever
|
|
126
|
+
// waiting for a permission reply that will never come. Uses atomic take
|
|
127
|
+
// so only one of TTL-expiry or button-click can win.
|
|
128
|
+
setTimeout(async () => {
|
|
129
|
+
const ctx = takePendingPermissionContext(contextHash)
|
|
130
|
+
if (!ctx) {
|
|
131
|
+
return
|
|
132
|
+
}
|
|
133
|
+
const client = getOpencodeClient(ctx.directory)
|
|
134
|
+
if (client) {
|
|
135
|
+
const requestIds = ctx.requestIds.length > 0
|
|
136
|
+
? ctx.requestIds
|
|
137
|
+
: [ctx.permission.id]
|
|
138
|
+
await Promise.all(
|
|
139
|
+
requestIds.map((requestId) => {
|
|
140
|
+
return client.permission.reply({
|
|
141
|
+
requestID: requestId,
|
|
142
|
+
directory: ctx.permissionDirectory,
|
|
143
|
+
reply: 'reject',
|
|
144
|
+
})
|
|
145
|
+
}),
|
|
146
|
+
).catch((error) => {
|
|
147
|
+
logger.error('Failed to auto-reject expired permission:', error)
|
|
148
|
+
})
|
|
149
|
+
}
|
|
150
|
+
}, PERMISSION_CONTEXT_TTL_MS).unref()
|
|
151
|
+
|
|
152
|
+
const patternStr = compactPermissionPatterns(permission.patterns).join(', ')
|
|
153
|
+
|
|
154
|
+
// Build 3 buttons for permission actions
|
|
155
|
+
const acceptButton = new ButtonBuilder()
|
|
156
|
+
.setCustomId(`permission_once:${contextHash}`)
|
|
157
|
+
.setLabel('Accept')
|
|
158
|
+
.setStyle(ButtonStyle.Success)
|
|
159
|
+
|
|
160
|
+
const acceptAlwaysButton = new ButtonBuilder()
|
|
161
|
+
.setCustomId(`permission_always:${contextHash}`)
|
|
162
|
+
.setLabel('Accept Always')
|
|
163
|
+
.setStyle(ButtonStyle.Success)
|
|
164
|
+
|
|
165
|
+
const denyButton = new ButtonBuilder()
|
|
166
|
+
.setCustomId(`permission_reject:${contextHash}`)
|
|
167
|
+
.setLabel('Deny')
|
|
168
|
+
.setStyle(ButtonStyle.Secondary)
|
|
169
|
+
|
|
170
|
+
const actionRow = new ActionRowBuilder<ButtonBuilder>().addComponents(
|
|
171
|
+
acceptButton,
|
|
172
|
+
acceptAlwaysButton,
|
|
173
|
+
denyButton,
|
|
174
|
+
)
|
|
175
|
+
|
|
176
|
+
const subtaskLine = subtaskLabel ? `**From:** \`${subtaskLabel}\`\n` : ''
|
|
177
|
+
const externalDirLine =
|
|
178
|
+
permission.permission === 'external_directory'
|
|
179
|
+
? `Agent is accessing files outside the project. [Learn more](https://opencode.ai/docs/permissions/#external-directories)\n`
|
|
180
|
+
: ''
|
|
181
|
+
const fullContent =
|
|
182
|
+
`⚠️ **Permission Required**\n` +
|
|
183
|
+
subtaskLine +
|
|
184
|
+
`**Type:** \`${permission.permission}\`\n` +
|
|
185
|
+
externalDirLine +
|
|
186
|
+
(patternStr ? `**Pattern:** \`${patternStr}\`` : '')
|
|
187
|
+
const permissionMessage = await thread.send({
|
|
188
|
+
content: fullContent.slice(0, 1900),
|
|
189
|
+
components: [actionRow],
|
|
190
|
+
flags: NOTIFY_MESSAGE_FLAGS | MessageFlags.SuppressEmbeds,
|
|
191
|
+
})
|
|
192
|
+
|
|
193
|
+
context.messageId = permissionMessage.id
|
|
194
|
+
|
|
195
|
+
logger.log(`Showed permission buttons for ${permission.id}`)
|
|
196
|
+
|
|
197
|
+
return { messageId: permissionMessage.id, contextHash }
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
function updatePermissionMessage({
|
|
201
|
+
context,
|
|
202
|
+
status,
|
|
203
|
+
}: {
|
|
204
|
+
context: PendingPermissionContext
|
|
205
|
+
status: string
|
|
206
|
+
}): void {
|
|
207
|
+
if (!context.messageId) {
|
|
208
|
+
return
|
|
209
|
+
}
|
|
210
|
+
context.thread.messages
|
|
211
|
+
.fetch(context.messageId)
|
|
212
|
+
.then((message) => {
|
|
213
|
+
const patternStr = compactPermissionPatterns(context.permission.patterns).join(', ')
|
|
214
|
+
const externalDirLine =
|
|
215
|
+
context.permission.permission === 'external_directory'
|
|
216
|
+
? 'Agent is accessing files outside the project. [Learn more](https://opencode.ai/docs/permissions/#external-directories)\n'
|
|
217
|
+
: ''
|
|
218
|
+
return message.edit({
|
|
219
|
+
content:
|
|
220
|
+
`⚠️ **Permission Required**\n` +
|
|
221
|
+
`**Type:** \`${context.permission.permission}\`\n` +
|
|
222
|
+
externalDirLine +
|
|
223
|
+
(patternStr ? `**Pattern:** \`${patternStr}\`\n` : '') +
|
|
224
|
+
status,
|
|
225
|
+
components: [],
|
|
226
|
+
})
|
|
227
|
+
})
|
|
228
|
+
.catch((error) => {
|
|
229
|
+
logger.error('Failed to update permission message:', error)
|
|
230
|
+
})
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
export async function cancelPendingPermission(threadId: string): Promise<boolean> {
|
|
234
|
+
const contexts = Array.from(pendingPermissionContexts.values()).filter((context) => {
|
|
235
|
+
return context.thread.id === threadId
|
|
236
|
+
})
|
|
237
|
+
|
|
238
|
+
if (contexts.length === 0) {
|
|
239
|
+
return false
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
let cancelledCount = 0
|
|
243
|
+
for (const context of contexts) {
|
|
244
|
+
const pendingContext = takePendingPermissionContext(context.contextHash)
|
|
245
|
+
if (!pendingContext) {
|
|
246
|
+
continue
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
const client = getOpencodeClient(pendingContext.directory)
|
|
250
|
+
if (!client) {
|
|
251
|
+
pendingPermissionContexts.set(pendingContext.contextHash, pendingContext)
|
|
252
|
+
logger.error('Failed to dismiss pending permission: OpenCode server not found')
|
|
253
|
+
continue
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
const requestIds = pendingContext.requestIds.length > 0
|
|
257
|
+
? pendingContext.requestIds
|
|
258
|
+
: [pendingContext.permission.id]
|
|
259
|
+
|
|
260
|
+
const result = await Promise.all(
|
|
261
|
+
requestIds.map((requestId) => {
|
|
262
|
+
return client.permission.reply({
|
|
263
|
+
requestID: requestId,
|
|
264
|
+
directory: pendingContext.permissionDirectory,
|
|
265
|
+
reply: 'reject',
|
|
266
|
+
})
|
|
267
|
+
}),
|
|
268
|
+
).then(() => {
|
|
269
|
+
return 'ok' as const
|
|
270
|
+
}).catch((error) => {
|
|
271
|
+
pendingPermissionContexts.set(pendingContext.contextHash, pendingContext)
|
|
272
|
+
logger.error('Failed to dismiss pending permission:', error)
|
|
273
|
+
return 'error' as const
|
|
274
|
+
})
|
|
275
|
+
|
|
276
|
+
if (result === 'error') {
|
|
277
|
+
continue
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
updatePermissionMessage({
|
|
281
|
+
context: pendingContext,
|
|
282
|
+
status: '_Permission dismissed - user sent a new message._',
|
|
283
|
+
})
|
|
284
|
+
cancelledCount++
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
if (cancelledCount > 0) {
|
|
288
|
+
logger.log(`Dismissed ${cancelledCount} pending permission request(s) for thread ${threadId}`)
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
return cancelledCount > 0
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
/**
|
|
295
|
+
* Handle button click for permission.
|
|
296
|
+
*/
|
|
297
|
+
export async function handlePermissionButton(
|
|
298
|
+
interaction: ButtonInteraction,
|
|
299
|
+
): Promise<void> {
|
|
300
|
+
const customId = interaction.customId
|
|
301
|
+
|
|
302
|
+
// Extract action and hash from customId (e.g., "permission_once:abc123")
|
|
303
|
+
const [actionPart, contextHash] = customId.split(':')
|
|
304
|
+
if (!actionPart || !contextHash) {
|
|
305
|
+
return
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
const response = actionPart.replace('permission_', '') as
|
|
309
|
+
| 'once'
|
|
310
|
+
| 'always'
|
|
311
|
+
| 'reject'
|
|
312
|
+
|
|
313
|
+
// Atomic take: if TTL already expired and auto-rejected, context is gone.
|
|
314
|
+
const context = takePendingPermissionContext(contextHash)
|
|
315
|
+
|
|
316
|
+
if (!context) {
|
|
317
|
+
await interaction.update({ components: [] })
|
|
318
|
+
return
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
await interaction.deferUpdate()
|
|
322
|
+
|
|
323
|
+
try {
|
|
324
|
+
const permClient = getOpencodeClient(context.directory)
|
|
325
|
+
if (!permClient) {
|
|
326
|
+
throw new Error('OpenCode server not found for directory')
|
|
327
|
+
}
|
|
328
|
+
const requestIds =
|
|
329
|
+
context.requestIds.length > 0
|
|
330
|
+
? context.requestIds
|
|
331
|
+
: [context.permission.id]
|
|
332
|
+
await Promise.all(
|
|
333
|
+
requestIds.map((requestId) => {
|
|
334
|
+
return permClient.permission.reply({
|
|
335
|
+
requestID: requestId,
|
|
336
|
+
directory: context.permissionDirectory,
|
|
337
|
+
reply: response,
|
|
338
|
+
})
|
|
339
|
+
}),
|
|
340
|
+
)
|
|
341
|
+
|
|
342
|
+
// Context already removed by takePendingPermissionContext above.
|
|
343
|
+
|
|
344
|
+
// Update message: show result and remove dropdown
|
|
345
|
+
const resultText = (() => {
|
|
346
|
+
switch (response) {
|
|
347
|
+
case 'once':
|
|
348
|
+
return '✅ Permission **accepted**'
|
|
349
|
+
case 'always':
|
|
350
|
+
return '✅ Permission **accepted** (auto-approve similar requests)'
|
|
351
|
+
case 'reject':
|
|
352
|
+
return '❌ Permission **rejected**'
|
|
353
|
+
}
|
|
354
|
+
})()
|
|
355
|
+
|
|
356
|
+
updatePermissionMessage({
|
|
357
|
+
context,
|
|
358
|
+
status: resultText,
|
|
359
|
+
})
|
|
360
|
+
|
|
361
|
+
logger.log(
|
|
362
|
+
`Permission ${context.permission.id} ${response} (${requestIds.length} request(s))`,
|
|
363
|
+
)
|
|
364
|
+
} catch (error) {
|
|
365
|
+
logger.error('Error handling permission:', error)
|
|
366
|
+
await interaction.editReply({
|
|
367
|
+
content: `Failed to process permission: ${error instanceof Error ? error.message : 'Unknown error'}`,
|
|
368
|
+
components: [],
|
|
369
|
+
})
|
|
370
|
+
}
|
|
371
|
+
}
|
|
372
|
+
|
|
373
|
+
export function addPermissionRequestToContext({
|
|
374
|
+
contextHash,
|
|
375
|
+
requestId,
|
|
376
|
+
}: {
|
|
377
|
+
contextHash: string
|
|
378
|
+
requestId: string
|
|
379
|
+
}): boolean {
|
|
380
|
+
const context = pendingPermissionContexts.get(contextHash)
|
|
381
|
+
if (!context) {
|
|
382
|
+
return false
|
|
383
|
+
}
|
|
384
|
+
if (context.requestIds.includes(requestId)) {
|
|
385
|
+
return false
|
|
386
|
+
}
|
|
387
|
+
context.requestIds = [...context.requestIds, requestId]
|
|
388
|
+
pendingPermissionContexts.set(contextHash, context)
|
|
389
|
+
return true
|
|
390
|
+
}
|
|
391
|
+
|
|
392
|
+
/**
|
|
393
|
+
* Clean up a pending permission context (e.g., on auto-reject).
|
|
394
|
+
*/
|
|
395
|
+
export function cleanupPermissionContext(contextHash: string): void {
|
|
396
|
+
pendingPermissionContexts.delete(contextHash)
|
|
397
|
+
}
|
|
@@ -0,0 +1,271 @@
|
|
|
1
|
+
// Queue commands - /queue, /queue-command, /clear-queue
|
|
2
|
+
|
|
3
|
+
import { ChannelType, MessageFlags, type ThreadChannel } from 'discord.js'
|
|
4
|
+
import type { AutocompleteContext, CommandContext } from './types.js'
|
|
5
|
+
import { getThreadSession } from '../database.js'
|
|
6
|
+
import {
|
|
7
|
+
resolveWorkingDirectory,
|
|
8
|
+
sendThreadMessage,
|
|
9
|
+
SILENT_MESSAGE_FLAGS,
|
|
10
|
+
} from '../discord-utils.js'
|
|
11
|
+
import {
|
|
12
|
+
getRuntime,
|
|
13
|
+
getOrCreateRuntime,
|
|
14
|
+
} from '../session-handler/thread-session-runtime.js'
|
|
15
|
+
import { createLogger, LogPrefix } from '../logger.js'
|
|
16
|
+
import { notifyError } from '../sentry.js'
|
|
17
|
+
import { store } from '../store.js'
|
|
18
|
+
|
|
19
|
+
const logger = createLogger(LogPrefix.QUEUE)
|
|
20
|
+
|
|
21
|
+
export async function handleQueueCommand({
|
|
22
|
+
command,
|
|
23
|
+
appId,
|
|
24
|
+
}: CommandContext): Promise<void> {
|
|
25
|
+
const message = command.options.getString('message', true)
|
|
26
|
+
const channel = command.channel
|
|
27
|
+
|
|
28
|
+
if (!channel) {
|
|
29
|
+
await command.reply({
|
|
30
|
+
content: 'This command can only be used in a channel',
|
|
31
|
+
flags: MessageFlags.Ephemeral | SILENT_MESSAGE_FLAGS,
|
|
32
|
+
})
|
|
33
|
+
return
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
const isThread = [
|
|
37
|
+
ChannelType.PublicThread,
|
|
38
|
+
ChannelType.PrivateThread,
|
|
39
|
+
ChannelType.AnnouncementThread,
|
|
40
|
+
].includes(channel.type)
|
|
41
|
+
|
|
42
|
+
if (!isThread) {
|
|
43
|
+
await command.reply({
|
|
44
|
+
content:
|
|
45
|
+
'This command can only be used in a thread with an active session',
|
|
46
|
+
flags: MessageFlags.Ephemeral | SILENT_MESSAGE_FLAGS,
|
|
47
|
+
})
|
|
48
|
+
return
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
const thread = channel as ThreadChannel
|
|
52
|
+
const sessionId = await getThreadSession(thread.id)
|
|
53
|
+
if (!sessionId) {
|
|
54
|
+
await command.reply({
|
|
55
|
+
content:
|
|
56
|
+
'No active session in this thread. Send a message directly instead.',
|
|
57
|
+
flags: MessageFlags.Ephemeral | SILENT_MESSAGE_FLAGS,
|
|
58
|
+
})
|
|
59
|
+
return
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
const resolved = await resolveWorkingDirectory({ channel: thread })
|
|
63
|
+
if (!resolved) {
|
|
64
|
+
await command.reply({
|
|
65
|
+
content: 'Could not determine project directory',
|
|
66
|
+
flags: MessageFlags.Ephemeral | SILENT_MESSAGE_FLAGS,
|
|
67
|
+
})
|
|
68
|
+
return
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
const runtime = getOrCreateRuntime({
|
|
72
|
+
threadId: thread.id,
|
|
73
|
+
thread,
|
|
74
|
+
projectDirectory: resolved.projectDirectory,
|
|
75
|
+
sdkDirectory: resolved.workingDirectory,
|
|
76
|
+
channelId: thread.parentId || thread.id,
|
|
77
|
+
appId,
|
|
78
|
+
})
|
|
79
|
+
|
|
80
|
+
// /queue explicitly uses kimaki local queue mode.
|
|
81
|
+
const enqueueResult = await runtime.enqueueIncoming({
|
|
82
|
+
prompt: message,
|
|
83
|
+
userId: command.user.id,
|
|
84
|
+
username: command.user.displayName,
|
|
85
|
+
appId,
|
|
86
|
+
mode: 'local-queue',
|
|
87
|
+
})
|
|
88
|
+
|
|
89
|
+
const responseText = enqueueResult.queued
|
|
90
|
+
? `Queued message${enqueueResult.position ? ` (position ${enqueueResult.position})` : ''}`
|
|
91
|
+
: `» **${command.user.displayName}:** ${message.slice(0, 1000)}${message.length > 1000 ? '...' : ''}`
|
|
92
|
+
|
|
93
|
+
await command.reply({
|
|
94
|
+
content: responseText,
|
|
95
|
+
flags: SILENT_MESSAGE_FLAGS,
|
|
96
|
+
})
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
export async function handleClearQueueCommand({
|
|
100
|
+
command,
|
|
101
|
+
}: CommandContext): Promise<void> {
|
|
102
|
+
const channel = command.channel
|
|
103
|
+
|
|
104
|
+
if (!channel) {
|
|
105
|
+
await command.reply({
|
|
106
|
+
content: 'This command can only be used in a channel',
|
|
107
|
+
flags: MessageFlags.Ephemeral | SILENT_MESSAGE_FLAGS,
|
|
108
|
+
})
|
|
109
|
+
return
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
const isThread = [
|
|
113
|
+
ChannelType.PublicThread,
|
|
114
|
+
ChannelType.PrivateThread,
|
|
115
|
+
ChannelType.AnnouncementThread,
|
|
116
|
+
].includes(channel.type)
|
|
117
|
+
|
|
118
|
+
if (!isThread) {
|
|
119
|
+
await command.reply({
|
|
120
|
+
content: 'This command can only be used in a thread',
|
|
121
|
+
flags: MessageFlags.Ephemeral | SILENT_MESSAGE_FLAGS,
|
|
122
|
+
})
|
|
123
|
+
return
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
const runtime = getRuntime(channel.id)
|
|
127
|
+
const queueLength = runtime?.getQueueLength() ?? 0
|
|
128
|
+
|
|
129
|
+
if (queueLength === 0) {
|
|
130
|
+
await command.reply({
|
|
131
|
+
content: 'No messages in queue',
|
|
132
|
+
flags: MessageFlags.Ephemeral | SILENT_MESSAGE_FLAGS,
|
|
133
|
+
})
|
|
134
|
+
return
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
runtime?.clearQueue()
|
|
138
|
+
|
|
139
|
+
await command.reply({
|
|
140
|
+
content: `Cleared ${queueLength} queued message${queueLength > 1 ? 's' : ''}`,
|
|
141
|
+
flags: SILENT_MESSAGE_FLAGS,
|
|
142
|
+
})
|
|
143
|
+
|
|
144
|
+
logger.log(
|
|
145
|
+
`[QUEUE] User ${command.user.displayName} cleared queue in thread ${channel.id}`,
|
|
146
|
+
)
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
export async function handleQueueCommandCommand({
|
|
150
|
+
command,
|
|
151
|
+
appId,
|
|
152
|
+
}: CommandContext): Promise<void> {
|
|
153
|
+
const commandName = command.options.getString('command', true)
|
|
154
|
+
const args = command.options.getString('arguments') || ''
|
|
155
|
+
const channel = command.channel
|
|
156
|
+
|
|
157
|
+
if (!channel) {
|
|
158
|
+
await command.reply({
|
|
159
|
+
content: 'This command can only be used in a channel',
|
|
160
|
+
flags: MessageFlags.Ephemeral | SILENT_MESSAGE_FLAGS,
|
|
161
|
+
})
|
|
162
|
+
return
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
const isThread = [
|
|
166
|
+
ChannelType.PublicThread,
|
|
167
|
+
ChannelType.PrivateThread,
|
|
168
|
+
ChannelType.AnnouncementThread,
|
|
169
|
+
].includes(channel.type)
|
|
170
|
+
|
|
171
|
+
if (!isThread) {
|
|
172
|
+
await command.reply({
|
|
173
|
+
content:
|
|
174
|
+
'This command can only be used in a thread with an active session',
|
|
175
|
+
flags: MessageFlags.Ephemeral | SILENT_MESSAGE_FLAGS,
|
|
176
|
+
})
|
|
177
|
+
return
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
const sessionId = await getThreadSession(channel.id)
|
|
181
|
+
|
|
182
|
+
if (!sessionId) {
|
|
183
|
+
await command.reply({
|
|
184
|
+
content:
|
|
185
|
+
'No active session in this thread. Send a message directly instead.',
|
|
186
|
+
flags: MessageFlags.Ephemeral | SILENT_MESSAGE_FLAGS,
|
|
187
|
+
})
|
|
188
|
+
return
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
// Validate command exists in registered user commands
|
|
192
|
+
const isKnownCommand = store.getState().registeredUserCommands.some((cmd) => {
|
|
193
|
+
return cmd.name === commandName
|
|
194
|
+
})
|
|
195
|
+
if (!isKnownCommand) {
|
|
196
|
+
await command.reply({
|
|
197
|
+
content: `Unknown command: /${commandName}. Use autocomplete to pick from available commands.`,
|
|
198
|
+
flags: MessageFlags.Ephemeral | SILENT_MESSAGE_FLAGS,
|
|
199
|
+
})
|
|
200
|
+
return
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
const commandPayload = { name: commandName, arguments: args }
|
|
204
|
+
const displayText = `/${commandName}`
|
|
205
|
+
const thread = channel as ThreadChannel
|
|
206
|
+
|
|
207
|
+
const resolved = await resolveWorkingDirectory({ channel: thread })
|
|
208
|
+
if (!resolved) {
|
|
209
|
+
await command.reply({
|
|
210
|
+
content: 'Could not determine project directory',
|
|
211
|
+
flags: MessageFlags.Ephemeral | SILENT_MESSAGE_FLAGS,
|
|
212
|
+
})
|
|
213
|
+
return
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
const runtime = getOrCreateRuntime({
|
|
217
|
+
threadId: thread.id,
|
|
218
|
+
thread,
|
|
219
|
+
projectDirectory: resolved.projectDirectory,
|
|
220
|
+
sdkDirectory: resolved.workingDirectory,
|
|
221
|
+
channelId: thread.parentId || thread.id,
|
|
222
|
+
appId,
|
|
223
|
+
})
|
|
224
|
+
|
|
225
|
+
// /queue-command explicitly uses kimaki local queue mode.
|
|
226
|
+
const enqueueResult = await runtime.enqueueIncoming({
|
|
227
|
+
prompt: '',
|
|
228
|
+
userId: command.user.id,
|
|
229
|
+
username: command.user.displayName,
|
|
230
|
+
appId,
|
|
231
|
+
command: commandPayload,
|
|
232
|
+
mode: 'local-queue',
|
|
233
|
+
})
|
|
234
|
+
|
|
235
|
+
const responseText = enqueueResult.queued
|
|
236
|
+
? `Queued message${enqueueResult.position ? ` (position ${enqueueResult.position})` : ''}`
|
|
237
|
+
: `» **${command.user.displayName}:** ${displayText}`
|
|
238
|
+
|
|
239
|
+
await command.reply({
|
|
240
|
+
content: responseText,
|
|
241
|
+
flags: SILENT_MESSAGE_FLAGS,
|
|
242
|
+
})
|
|
243
|
+
|
|
244
|
+
logger.log(
|
|
245
|
+
`[QUEUE] User ${command.user.displayName} queued command /${commandName} in thread ${channel.id}`,
|
|
246
|
+
)
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
export async function handleQueueCommandAutocomplete({
|
|
250
|
+
interaction,
|
|
251
|
+
}: AutocompleteContext): Promise<void> {
|
|
252
|
+
const focused = interaction.options.getFocused(true)
|
|
253
|
+
|
|
254
|
+
if (focused.name !== 'command') {
|
|
255
|
+
await interaction.respond([])
|
|
256
|
+
return
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
const query = focused.value.toLowerCase()
|
|
260
|
+
const choices = store.getState().registeredUserCommands
|
|
261
|
+
.filter((cmd) => {
|
|
262
|
+
return cmd.name.toLowerCase().includes(query)
|
|
263
|
+
})
|
|
264
|
+
.slice(0, 25)
|
|
265
|
+
.map((cmd) => ({
|
|
266
|
+
name: `/${cmd.name} [${cmd.source === 'skill' ? 'skill' : cmd.source === 'mcp' ? 'mcp' : 'cmd'}] - ${cmd.description}`.slice(0, 100),
|
|
267
|
+
value: cmd.name.slice(0, 100),
|
|
268
|
+
}))
|
|
269
|
+
|
|
270
|
+
await interaction.respond(choices)
|
|
271
|
+
}
|