@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,693 @@
|
|
|
1
|
+
// Discord slash command registration logic, extracted from cli.ts to avoid
|
|
2
|
+
// circular dependencies (cli → discord-bot → interaction-handler → command → cli).
|
|
3
|
+
// Imported by both cli.ts (startup registration) and restart-opencode-server.ts
|
|
4
|
+
// (post-restart re-registration).
|
|
5
|
+
|
|
6
|
+
import {
|
|
7
|
+
type REST,
|
|
8
|
+
Routes,
|
|
9
|
+
SlashCommandBuilder,
|
|
10
|
+
} from 'discord.js'
|
|
11
|
+
import type { Command as OpencodeCommand } from '@opencode-ai/sdk/v2'
|
|
12
|
+
import { createDiscordRest } from './discord-urls.js'
|
|
13
|
+
import { createLogger, LogPrefix } from './logger.js'
|
|
14
|
+
import { store, type RegisteredUserCommand } from './store.js'
|
|
15
|
+
import {
|
|
16
|
+
sanitizeAgentName,
|
|
17
|
+
buildQuickAgentCommandDescription,
|
|
18
|
+
} from './commands/agent.js'
|
|
19
|
+
|
|
20
|
+
const cliLogger = createLogger(LogPrefix.CLI)
|
|
21
|
+
|
|
22
|
+
// Commands to skip when registering user commands (reserved names)
|
|
23
|
+
export const SKIP_USER_COMMANDS = ['init']
|
|
24
|
+
|
|
25
|
+
export type AgentInfo = {
|
|
26
|
+
name: string
|
|
27
|
+
description?: string
|
|
28
|
+
mode: string
|
|
29
|
+
hidden?: boolean
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
function getDiscordCommandSuffix(
|
|
33
|
+
command: OpencodeCommand,
|
|
34
|
+
): '-cmd' | '-skill' | '-mcp-prompt' {
|
|
35
|
+
if (command.source === 'skill') {
|
|
36
|
+
return '-skill'
|
|
37
|
+
}
|
|
38
|
+
if (command.source === 'mcp') {
|
|
39
|
+
return '-mcp-prompt'
|
|
40
|
+
}
|
|
41
|
+
return '-cmd'
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
type DiscordCommandSummary = {
|
|
45
|
+
id: string
|
|
46
|
+
name: string
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
function isDiscordCommandSummary(value: unknown): value is DiscordCommandSummary {
|
|
50
|
+
if (typeof value !== 'object' || value === null) {
|
|
51
|
+
return false
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
const id = Reflect.get(value, 'id')
|
|
55
|
+
const name = Reflect.get(value, 'name')
|
|
56
|
+
return typeof id === 'string' && typeof name === 'string'
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
async function deleteLegacyGlobalCommands({
|
|
60
|
+
rest,
|
|
61
|
+
appId,
|
|
62
|
+
commandNames,
|
|
63
|
+
}: {
|
|
64
|
+
rest: REST
|
|
65
|
+
appId: string
|
|
66
|
+
commandNames: Set<string>
|
|
67
|
+
}) {
|
|
68
|
+
try {
|
|
69
|
+
const response = await rest.get(Routes.applicationCommands(appId))
|
|
70
|
+
if (!Array.isArray(response)) {
|
|
71
|
+
cliLogger.warn(
|
|
72
|
+
'COMMANDS: Unexpected global command payload while cleaning legacy global commands',
|
|
73
|
+
)
|
|
74
|
+
return
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
const legacyGlobalCommands = response
|
|
78
|
+
.filter(isDiscordCommandSummary)
|
|
79
|
+
.filter((command) => {
|
|
80
|
+
return commandNames.has(command.name)
|
|
81
|
+
})
|
|
82
|
+
|
|
83
|
+
if (legacyGlobalCommands.length === 0) {
|
|
84
|
+
return
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
const deletionResults = await Promise.allSettled(
|
|
88
|
+
legacyGlobalCommands.map(async (command) => {
|
|
89
|
+
await rest.delete(Routes.applicationCommand(appId, command.id))
|
|
90
|
+
return command
|
|
91
|
+
}),
|
|
92
|
+
)
|
|
93
|
+
|
|
94
|
+
const failedDeletions = deletionResults.filter((result) => {
|
|
95
|
+
return result.status === 'rejected'
|
|
96
|
+
})
|
|
97
|
+
if (failedDeletions.length > 0) {
|
|
98
|
+
cliLogger.warn(
|
|
99
|
+
`COMMANDS: Failed to delete ${failedDeletions.length} legacy global command(s)`,
|
|
100
|
+
)
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
const deletedCount = deletionResults.length - failedDeletions.length
|
|
104
|
+
if (deletedCount > 0) {
|
|
105
|
+
cliLogger.info(
|
|
106
|
+
`COMMANDS: Deleted ${deletedCount} legacy global command(s) to avoid guild/global duplicates`,
|
|
107
|
+
)
|
|
108
|
+
}
|
|
109
|
+
} catch (error) {
|
|
110
|
+
cliLogger.warn(
|
|
111
|
+
`COMMANDS: Could not clean legacy global commands: ${error instanceof Error ? error.stack : String(error)}`,
|
|
112
|
+
)
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
// Discord slash command descriptions must be 1-100 chars.
|
|
117
|
+
// Truncate to 100 so @sapphire/shapeshift validation never throws.
|
|
118
|
+
function truncateCommandDescription(description: string): string {
|
|
119
|
+
return description.slice(0, 100)
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
export async function registerCommands({
|
|
123
|
+
token,
|
|
124
|
+
appId,
|
|
125
|
+
guildIds,
|
|
126
|
+
userCommands = [],
|
|
127
|
+
agents = [],
|
|
128
|
+
}: {
|
|
129
|
+
token: string
|
|
130
|
+
appId: string
|
|
131
|
+
guildIds: string[]
|
|
132
|
+
userCommands?: OpencodeCommand[]
|
|
133
|
+
agents?: AgentInfo[]
|
|
134
|
+
}) {
|
|
135
|
+
const commands = [
|
|
136
|
+
new SlashCommandBuilder()
|
|
137
|
+
.setName('resume')
|
|
138
|
+
.setDescription(truncateCommandDescription('Resume an existing OpenCode session'))
|
|
139
|
+
.addStringOption((option) => {
|
|
140
|
+
option
|
|
141
|
+
.setName('session')
|
|
142
|
+
.setDescription(truncateCommandDescription('The session to resume'))
|
|
143
|
+
.setRequired(true)
|
|
144
|
+
.setAutocomplete(true)
|
|
145
|
+
|
|
146
|
+
return option
|
|
147
|
+
})
|
|
148
|
+
.setDMPermission(false)
|
|
149
|
+
.toJSON(),
|
|
150
|
+
new SlashCommandBuilder()
|
|
151
|
+
.setName('new-session')
|
|
152
|
+
.setDescription(truncateCommandDescription('Start a new OpenCode session'))
|
|
153
|
+
.addStringOption((option) => {
|
|
154
|
+
option
|
|
155
|
+
.setName('prompt')
|
|
156
|
+
.setDescription(truncateCommandDescription('Prompt content for the session'))
|
|
157
|
+
.setRequired(true)
|
|
158
|
+
|
|
159
|
+
return option
|
|
160
|
+
})
|
|
161
|
+
.addStringOption((option) => {
|
|
162
|
+
option
|
|
163
|
+
.setName('files')
|
|
164
|
+
.setDescription(
|
|
165
|
+
truncateCommandDescription('Files to mention (comma or space separated; autocomplete)'),
|
|
166
|
+
)
|
|
167
|
+
.setAutocomplete(true)
|
|
168
|
+
.setMaxLength(6000)
|
|
169
|
+
|
|
170
|
+
return option
|
|
171
|
+
})
|
|
172
|
+
.addStringOption((option) => {
|
|
173
|
+
option
|
|
174
|
+
.setName('agent')
|
|
175
|
+
.setDescription(truncateCommandDescription('Agent to use for this session'))
|
|
176
|
+
.setAutocomplete(true)
|
|
177
|
+
|
|
178
|
+
return option
|
|
179
|
+
})
|
|
180
|
+
.setDMPermission(false)
|
|
181
|
+
.toJSON(),
|
|
182
|
+
new SlashCommandBuilder()
|
|
183
|
+
.setName('new-worktree')
|
|
184
|
+
.setDescription(
|
|
185
|
+
truncateCommandDescription('Create a git worktree branch from origin/HEAD (or main). Optionally pick a base branch.'),
|
|
186
|
+
)
|
|
187
|
+
.addStringOption((option) => {
|
|
188
|
+
option
|
|
189
|
+
.setName('name')
|
|
190
|
+
.setDescription(
|
|
191
|
+
truncateCommandDescription('Name for worktree (optional in threads - uses thread name)'),
|
|
192
|
+
)
|
|
193
|
+
.setRequired(false)
|
|
194
|
+
|
|
195
|
+
return option
|
|
196
|
+
})
|
|
197
|
+
.addStringOption((option) => {
|
|
198
|
+
option
|
|
199
|
+
.setName('base-branch')
|
|
200
|
+
.setDescription(
|
|
201
|
+
truncateCommandDescription('Branch to create the worktree from (default: origin/HEAD or main)'),
|
|
202
|
+
)
|
|
203
|
+
.setRequired(false)
|
|
204
|
+
.setAutocomplete(true)
|
|
205
|
+
|
|
206
|
+
return option
|
|
207
|
+
})
|
|
208
|
+
.setDMPermission(false)
|
|
209
|
+
.toJSON(),
|
|
210
|
+
new SlashCommandBuilder()
|
|
211
|
+
.setName('merge-worktree')
|
|
212
|
+
.setDescription(
|
|
213
|
+
truncateCommandDescription('Squash-merge worktree into default branch. Aborts if main has uncommitted changes.'),
|
|
214
|
+
)
|
|
215
|
+
.addStringOption((option) => {
|
|
216
|
+
option
|
|
217
|
+
.setName('target-branch')
|
|
218
|
+
.setDescription(
|
|
219
|
+
truncateCommandDescription('Branch to merge into (default: origin/HEAD or main)'),
|
|
220
|
+
)
|
|
221
|
+
.setRequired(false)
|
|
222
|
+
.setAutocomplete(true)
|
|
223
|
+
|
|
224
|
+
return option
|
|
225
|
+
})
|
|
226
|
+
.setDMPermission(false)
|
|
227
|
+
.toJSON(),
|
|
228
|
+
new SlashCommandBuilder()
|
|
229
|
+
.setName('toggle-worktrees')
|
|
230
|
+
.setDescription(
|
|
231
|
+
truncateCommandDescription('Toggle automatic git worktree creation for new sessions in this channel'),
|
|
232
|
+
)
|
|
233
|
+
.setDMPermission(false)
|
|
234
|
+
.toJSON(),
|
|
235
|
+
new SlashCommandBuilder()
|
|
236
|
+
.setName('worktrees')
|
|
237
|
+
.setDescription(truncateCommandDescription('List all active worktree sessions'))
|
|
238
|
+
.setDMPermission(false)
|
|
239
|
+
.toJSON(),
|
|
240
|
+
new SlashCommandBuilder()
|
|
241
|
+
.setName('tasks')
|
|
242
|
+
.setDescription(truncateCommandDescription('List scheduled tasks created via send --send-at'))
|
|
243
|
+
.addBooleanOption((option) => {
|
|
244
|
+
return option
|
|
245
|
+
.setName('all')
|
|
246
|
+
.setDescription(
|
|
247
|
+
truncateCommandDescription('Include completed, cancelled, and failed tasks'),
|
|
248
|
+
)
|
|
249
|
+
})
|
|
250
|
+
.setDMPermission(false)
|
|
251
|
+
.toJSON(),
|
|
252
|
+
|
|
253
|
+
new SlashCommandBuilder()
|
|
254
|
+
.setName('add-project')
|
|
255
|
+
.setDescription(
|
|
256
|
+
truncateCommandDescription('Create Discord channels for a project. Use `npx kimaki project add` for unlisted projects'),
|
|
257
|
+
)
|
|
258
|
+
.addStringOption((option) => {
|
|
259
|
+
option
|
|
260
|
+
.setName('project')
|
|
261
|
+
.setDescription(
|
|
262
|
+
truncateCommandDescription('Recent OpenCode projects. Use `npx kimaki project add` if not listed'),
|
|
263
|
+
)
|
|
264
|
+
.setRequired(true)
|
|
265
|
+
.setAutocomplete(true)
|
|
266
|
+
|
|
267
|
+
return option
|
|
268
|
+
})
|
|
269
|
+
.setDMPermission(false)
|
|
270
|
+
.toJSON(),
|
|
271
|
+
new SlashCommandBuilder()
|
|
272
|
+
.setName('remove-project')
|
|
273
|
+
.setDescription(truncateCommandDescription('Remove Discord channels for a project'))
|
|
274
|
+
.addStringOption((option) => {
|
|
275
|
+
option
|
|
276
|
+
.setName('project')
|
|
277
|
+
.setDescription(truncateCommandDescription('Select a project to remove'))
|
|
278
|
+
.setRequired(true)
|
|
279
|
+
.setAutocomplete(true)
|
|
280
|
+
|
|
281
|
+
return option
|
|
282
|
+
})
|
|
283
|
+
.setDMPermission(false)
|
|
284
|
+
.toJSON(),
|
|
285
|
+
new SlashCommandBuilder()
|
|
286
|
+
.setName('create-new-project')
|
|
287
|
+
.setDescription(
|
|
288
|
+
truncateCommandDescription('Create a new project folder, initialize git, and start a session'),
|
|
289
|
+
)
|
|
290
|
+
.addStringOption((option) => {
|
|
291
|
+
option
|
|
292
|
+
.setName('name')
|
|
293
|
+
.setDescription(truncateCommandDescription('Name for the new project folder'))
|
|
294
|
+
.setRequired(true)
|
|
295
|
+
|
|
296
|
+
return option
|
|
297
|
+
})
|
|
298
|
+
.setDMPermission(false)
|
|
299
|
+
.toJSON(),
|
|
300
|
+
new SlashCommandBuilder()
|
|
301
|
+
.setName('abort')
|
|
302
|
+
.setDescription(truncateCommandDescription('Abort the current OpenCode request in this thread'))
|
|
303
|
+
.setDMPermission(false)
|
|
304
|
+
.toJSON(),
|
|
305
|
+
new SlashCommandBuilder()
|
|
306
|
+
.setName('compact')
|
|
307
|
+
.setDescription(
|
|
308
|
+
truncateCommandDescription('Compact the session context by summarizing conversation history'),
|
|
309
|
+
)
|
|
310
|
+
.setDMPermission(false)
|
|
311
|
+
.toJSON(),
|
|
312
|
+
|
|
313
|
+
new SlashCommandBuilder()
|
|
314
|
+
.setName('share')
|
|
315
|
+
.setDescription(truncateCommandDescription('Share the current session as a public URL'))
|
|
316
|
+
.setDMPermission(false)
|
|
317
|
+
.toJSON(),
|
|
318
|
+
new SlashCommandBuilder()
|
|
319
|
+
.setName('diff')
|
|
320
|
+
.setDescription(truncateCommandDescription('Show git diff as a shareable URL'))
|
|
321
|
+
.setDMPermission(false)
|
|
322
|
+
.toJSON(),
|
|
323
|
+
new SlashCommandBuilder()
|
|
324
|
+
.setName('fork')
|
|
325
|
+
.setDescription(truncateCommandDescription('Fork the session from a past user message'))
|
|
326
|
+
.setDMPermission(false)
|
|
327
|
+
.toJSON(),
|
|
328
|
+
new SlashCommandBuilder()
|
|
329
|
+
.setName('btw')
|
|
330
|
+
.setDescription(truncateCommandDescription('Ask something without polluting or blocking the current session'))
|
|
331
|
+
.addStringOption((option) => {
|
|
332
|
+
option
|
|
333
|
+
.setName('prompt')
|
|
334
|
+
.setDescription(truncateCommandDescription('The message to send in the forked session'))
|
|
335
|
+
.setRequired(true)
|
|
336
|
+
return option
|
|
337
|
+
})
|
|
338
|
+
.setDMPermission(false)
|
|
339
|
+
.toJSON(),
|
|
340
|
+
new SlashCommandBuilder()
|
|
341
|
+
.setName('model')
|
|
342
|
+
.setDescription(truncateCommandDescription('Set the preferred model for this channel or session'))
|
|
343
|
+
.setDMPermission(false)
|
|
344
|
+
.toJSON(),
|
|
345
|
+
new SlashCommandBuilder()
|
|
346
|
+
.setName('model-variant')
|
|
347
|
+
.setDescription(
|
|
348
|
+
truncateCommandDescription('Quickly change the thinking level variant for the current model'),
|
|
349
|
+
)
|
|
350
|
+
.setDMPermission(false)
|
|
351
|
+
.toJSON(),
|
|
352
|
+
new SlashCommandBuilder()
|
|
353
|
+
.setName('unset-model-override')
|
|
354
|
+
.setDescription(truncateCommandDescription('Remove model override and use default instead'))
|
|
355
|
+
.setDMPermission(false)
|
|
356
|
+
.toJSON(),
|
|
357
|
+
new SlashCommandBuilder()
|
|
358
|
+
.setName('login')
|
|
359
|
+
.setDescription(
|
|
360
|
+
truncateCommandDescription('Authenticate with an AI provider (OAuth or API key). Use this instead of /connect'),
|
|
361
|
+
)
|
|
362
|
+
.setDMPermission(false)
|
|
363
|
+
.toJSON(),
|
|
364
|
+
new SlashCommandBuilder()
|
|
365
|
+
.setName('agent')
|
|
366
|
+
.setDescription(truncateCommandDescription('Set the preferred agent for this channel or session'))
|
|
367
|
+
.setDMPermission(false)
|
|
368
|
+
.toJSON(),
|
|
369
|
+
new SlashCommandBuilder()
|
|
370
|
+
.setName('queue')
|
|
371
|
+
.setDescription(
|
|
372
|
+
truncateCommandDescription('Queue a message to be sent after the current response finishes'),
|
|
373
|
+
)
|
|
374
|
+
.addStringOption((option) => {
|
|
375
|
+
option
|
|
376
|
+
.setName('message')
|
|
377
|
+
.setDescription(truncateCommandDescription('The message to queue'))
|
|
378
|
+
.setRequired(true)
|
|
379
|
+
|
|
380
|
+
return option
|
|
381
|
+
})
|
|
382
|
+
.setDMPermission(false)
|
|
383
|
+
.toJSON(),
|
|
384
|
+
new SlashCommandBuilder()
|
|
385
|
+
.setName('clear-queue')
|
|
386
|
+
.setDescription(truncateCommandDescription('Clear all queued messages in this thread'))
|
|
387
|
+
.setDMPermission(false)
|
|
388
|
+
.toJSON(),
|
|
389
|
+
new SlashCommandBuilder()
|
|
390
|
+
.setName('queue-command')
|
|
391
|
+
.setDescription(
|
|
392
|
+
truncateCommandDescription('Queue a user command to run after the current response finishes'),
|
|
393
|
+
)
|
|
394
|
+
.addStringOption((option) => {
|
|
395
|
+
option
|
|
396
|
+
.setName('command')
|
|
397
|
+
.setDescription(truncateCommandDescription('The command to run'))
|
|
398
|
+
.setRequired(true)
|
|
399
|
+
.setAutocomplete(true)
|
|
400
|
+
return option
|
|
401
|
+
})
|
|
402
|
+
.addStringOption((option) => {
|
|
403
|
+
option
|
|
404
|
+
.setName('arguments')
|
|
405
|
+
.setDescription(truncateCommandDescription('Arguments to pass to the command'))
|
|
406
|
+
.setRequired(false)
|
|
407
|
+
return option
|
|
408
|
+
})
|
|
409
|
+
.setDMPermission(false)
|
|
410
|
+
.toJSON(),
|
|
411
|
+
new SlashCommandBuilder()
|
|
412
|
+
.setName('undo')
|
|
413
|
+
.setDescription(truncateCommandDescription('Undo the last assistant message (revert file changes)'))
|
|
414
|
+
.setDMPermission(false)
|
|
415
|
+
.toJSON(),
|
|
416
|
+
new SlashCommandBuilder()
|
|
417
|
+
.setName('redo')
|
|
418
|
+
.setDescription(truncateCommandDescription('Redo previously undone changes'))
|
|
419
|
+
.setDMPermission(false)
|
|
420
|
+
.toJSON(),
|
|
421
|
+
new SlashCommandBuilder()
|
|
422
|
+
.setName('verbosity')
|
|
423
|
+
.setDescription(truncateCommandDescription('Set output verbosity for this channel'))
|
|
424
|
+
.setDMPermission(false)
|
|
425
|
+
.toJSON(),
|
|
426
|
+
new SlashCommandBuilder()
|
|
427
|
+
.setName('restart-opencode-server')
|
|
428
|
+
.setDescription(
|
|
429
|
+
truncateCommandDescription('Restart opencode server and re-register slash commands'),
|
|
430
|
+
)
|
|
431
|
+
.setDMPermission(false)
|
|
432
|
+
.toJSON(),
|
|
433
|
+
new SlashCommandBuilder()
|
|
434
|
+
.setName('run-shell-command')
|
|
435
|
+
.setDescription(
|
|
436
|
+
truncateCommandDescription('Run a shell command in the project directory. Tip: prefix messages with ! as shortcut'),
|
|
437
|
+
)
|
|
438
|
+
.addStringOption((option) => {
|
|
439
|
+
option
|
|
440
|
+
.setName('command')
|
|
441
|
+
.setDescription(truncateCommandDescription('Command to run'))
|
|
442
|
+
.setRequired(true)
|
|
443
|
+
return option
|
|
444
|
+
})
|
|
445
|
+
.setDMPermission(false)
|
|
446
|
+
.toJSON(),
|
|
447
|
+
new SlashCommandBuilder()
|
|
448
|
+
.setName('context-usage')
|
|
449
|
+
.setDescription(
|
|
450
|
+
truncateCommandDescription('Show token usage and context window percentage for this session'),
|
|
451
|
+
)
|
|
452
|
+
.setDMPermission(false)
|
|
453
|
+
.toJSON(),
|
|
454
|
+
new SlashCommandBuilder()
|
|
455
|
+
.setName('session-id')
|
|
456
|
+
.setDescription(
|
|
457
|
+
truncateCommandDescription('Show current session ID and opencode attach command for this thread'),
|
|
458
|
+
)
|
|
459
|
+
.setDMPermission(false)
|
|
460
|
+
.toJSON(),
|
|
461
|
+
|
|
462
|
+
new SlashCommandBuilder()
|
|
463
|
+
.setName('upgrade-and-restart')
|
|
464
|
+
.setDescription(
|
|
465
|
+
truncateCommandDescription('Upgrade kimaki to the latest version and restart the bot'),
|
|
466
|
+
)
|
|
467
|
+
.setDMPermission(false)
|
|
468
|
+
.toJSON(),
|
|
469
|
+
new SlashCommandBuilder()
|
|
470
|
+
.setName('transcription-key')
|
|
471
|
+
.setDescription(
|
|
472
|
+
truncateCommandDescription('Set API key for voice message transcription (OpenAI or Gemini)'),
|
|
473
|
+
)
|
|
474
|
+
.setDMPermission(false)
|
|
475
|
+
.toJSON(),
|
|
476
|
+
new SlashCommandBuilder()
|
|
477
|
+
.setName('mcp')
|
|
478
|
+
.setDescription(truncateCommandDescription('List and manage MCP servers for this project'))
|
|
479
|
+
.setDMPermission(false)
|
|
480
|
+
.toJSON(),
|
|
481
|
+
new SlashCommandBuilder()
|
|
482
|
+
.setName('screenshare')
|
|
483
|
+
.setDescription(truncateCommandDescription('Start screen sharing via VNC tunnel (auto-stops after 30 minutes)'))
|
|
484
|
+
.setDMPermission(false)
|
|
485
|
+
.toJSON(),
|
|
486
|
+
new SlashCommandBuilder()
|
|
487
|
+
.setName('screenshare-stop')
|
|
488
|
+
.setDescription(truncateCommandDescription('Stop screen sharing'))
|
|
489
|
+
.setDMPermission(false)
|
|
490
|
+
.toJSON(),
|
|
491
|
+
]
|
|
492
|
+
|
|
493
|
+
// Dynamic commands are registered in priority order: agents → user commands → skills → MCP prompts.
|
|
494
|
+
// This ordering matters because we slice to MAX_DISCORD_COMMANDS (100) at the end,
|
|
495
|
+
// so lower-priority dynamic commands get trimmed first if the total exceeds the limit.
|
|
496
|
+
|
|
497
|
+
// 1. Agent-specific quick commands like /plan-agent, /build-agent
|
|
498
|
+
// Filter to primary/all mode agents (same as /agent command shows), excluding hidden agents
|
|
499
|
+
const primaryAgents = agents.filter(
|
|
500
|
+
(a) => (a.mode === 'primary' || a.mode === 'all') && !a.hidden,
|
|
501
|
+
)
|
|
502
|
+
for (const agent of primaryAgents) {
|
|
503
|
+
const sanitizedName = sanitizeAgentName(agent.name)
|
|
504
|
+
// Skip if sanitized name is empty or would create invalid command name
|
|
505
|
+
// Discord command names must start with a lowercase letter or number
|
|
506
|
+
if (!sanitizedName || !/^[a-z0-9]/.test(sanitizedName)) {
|
|
507
|
+
continue
|
|
508
|
+
}
|
|
509
|
+
// Truncate base name before appending suffix so the -agent suffix is never
|
|
510
|
+
// lost to Discord's 32-char command name limit.
|
|
511
|
+
const agentSuffix = '-agent'
|
|
512
|
+
const agentBaseName = sanitizedName.slice(0, 32 - agentSuffix.length)
|
|
513
|
+
const commandName = `${agentBaseName}${agentSuffix}`
|
|
514
|
+
const description = buildQuickAgentCommandDescription({
|
|
515
|
+
agentName: agent.name,
|
|
516
|
+
description: agent.description,
|
|
517
|
+
})
|
|
518
|
+
|
|
519
|
+
commands.push(
|
|
520
|
+
new SlashCommandBuilder()
|
|
521
|
+
.setName(commandName)
|
|
522
|
+
.setDescription(truncateCommandDescription(description))
|
|
523
|
+
.setDMPermission(false)
|
|
524
|
+
.toJSON(),
|
|
525
|
+
)
|
|
526
|
+
}
|
|
527
|
+
|
|
528
|
+
// 2. User-defined commands, skills, and MCP prompts (ordered by priority)
|
|
529
|
+
// Also populate registeredUserCommands in the store for /queue-command autocomplete
|
|
530
|
+
const newRegisteredCommands: RegisteredUserCommand[] = []
|
|
531
|
+
// Sort: regular commands first, then skills, then MCP prompts
|
|
532
|
+
const sourceOrder: Record<string, number> = { config: 0, skill: 1, mcp: 2 }
|
|
533
|
+
const sortedUserCommands = [...userCommands].sort((a, b) => {
|
|
534
|
+
return (sourceOrder[a.source || ''] ?? 0) - (sourceOrder[b.source || ''] ?? 0)
|
|
535
|
+
})
|
|
536
|
+
for (const cmd of sortedUserCommands) {
|
|
537
|
+
if (SKIP_USER_COMMANDS.includes(cmd.name)) {
|
|
538
|
+
continue
|
|
539
|
+
}
|
|
540
|
+
|
|
541
|
+
// Sanitize command name: oh-my-opencode uses MCP commands with colons and slashes,
|
|
542
|
+
// which Discord doesn't allow in command names.
|
|
543
|
+
// Discord command names: lowercase, alphanumeric and hyphens only, must start with letter/number.
|
|
544
|
+
const sanitizedName = cmd.name
|
|
545
|
+
.toLowerCase()
|
|
546
|
+
.replace(/[:/]/g, '-') // Replace : and / with hyphens first
|
|
547
|
+
.replace(/[^a-z0-9-]/g, '-') // Replace any other non-alphanumeric chars
|
|
548
|
+
.replace(/-+/g, '-') // Collapse multiple hyphens
|
|
549
|
+
.replace(/^-|-$/g, '') // Remove leading/trailing hyphens
|
|
550
|
+
|
|
551
|
+
// Skip if sanitized name is empty - would create invalid command name like "-cmd"
|
|
552
|
+
if (!sanitizedName) {
|
|
553
|
+
continue
|
|
554
|
+
}
|
|
555
|
+
|
|
556
|
+
const commandSuffix = getDiscordCommandSuffix(cmd)
|
|
557
|
+
|
|
558
|
+
// Truncate base name before appending suffix so the suffix is never
|
|
559
|
+
// lost to Discord's 32-char command name limit.
|
|
560
|
+
const baseName = sanitizedName.slice(0, 32 - commandSuffix.length)
|
|
561
|
+
const commandName = `${baseName}${commandSuffix}`
|
|
562
|
+
const description = cmd.description || `Run /${cmd.name} command`
|
|
563
|
+
|
|
564
|
+
newRegisteredCommands.push({
|
|
565
|
+
name: cmd.name,
|
|
566
|
+
discordCommandName: commandName,
|
|
567
|
+
description,
|
|
568
|
+
source: cmd.source,
|
|
569
|
+
})
|
|
570
|
+
|
|
571
|
+
commands.push(
|
|
572
|
+
new SlashCommandBuilder()
|
|
573
|
+
.setName(commandName)
|
|
574
|
+
.setDescription(truncateCommandDescription(description))
|
|
575
|
+
.addStringOption((option) => {
|
|
576
|
+
option
|
|
577
|
+
.setName('arguments')
|
|
578
|
+
.setDescription(truncateCommandDescription('Arguments to pass to the command'))
|
|
579
|
+
.setRequired(false)
|
|
580
|
+
return option
|
|
581
|
+
})
|
|
582
|
+
.setDMPermission(false)
|
|
583
|
+
.toJSON(),
|
|
584
|
+
)
|
|
585
|
+
}
|
|
586
|
+
store.setState({ registeredUserCommands: newRegisteredCommands })
|
|
587
|
+
|
|
588
|
+
// Discord allows max 100 guild commands. Slice to stay within the limit,
|
|
589
|
+
// trimming lowest-priority dynamic commands (MCP prompts, then skills) first.
|
|
590
|
+
const MAX_DISCORD_COMMANDS = 100
|
|
591
|
+
if (commands.length > MAX_DISCORD_COMMANDS) {
|
|
592
|
+
cliLogger.warn(
|
|
593
|
+
`COMMANDS: ${commands.length} commands exceed Discord limit of ${MAX_DISCORD_COMMANDS}, truncating to ${MAX_DISCORD_COMMANDS}`,
|
|
594
|
+
)
|
|
595
|
+
commands.length = MAX_DISCORD_COMMANDS
|
|
596
|
+
}
|
|
597
|
+
|
|
598
|
+
const rest = createDiscordRest(token)
|
|
599
|
+
const uniqueGuildIds = Array.from(new Set(guildIds.filter((guildId) => guildId)))
|
|
600
|
+
const guildCommandNames = new Set(
|
|
601
|
+
commands
|
|
602
|
+
.map((command) => {
|
|
603
|
+
return command.name
|
|
604
|
+
})
|
|
605
|
+
.filter((name): name is string => {
|
|
606
|
+
return typeof name === 'string'
|
|
607
|
+
}),
|
|
608
|
+
)
|
|
609
|
+
|
|
610
|
+
if (uniqueGuildIds.length === 0) {
|
|
611
|
+
cliLogger.warn('COMMANDS: No guilds available, skipping slash command registration')
|
|
612
|
+
return
|
|
613
|
+
}
|
|
614
|
+
|
|
615
|
+
try {
|
|
616
|
+
// PUT is a bulk overwrite: Discord matches by name, updates changed fields
|
|
617
|
+
// (description, options, etc.) in place, creates new commands, and deletes
|
|
618
|
+
// any not present in the body. No local diffing needed.
|
|
619
|
+
const results = await Promise.allSettled(
|
|
620
|
+
uniqueGuildIds.map(async (guildId) => {
|
|
621
|
+
const response = await rest.put(
|
|
622
|
+
Routes.applicationGuildCommands(appId, guildId),
|
|
623
|
+
{
|
|
624
|
+
body: commands,
|
|
625
|
+
},
|
|
626
|
+
)
|
|
627
|
+
|
|
628
|
+
const registeredCount = Array.isArray(response)
|
|
629
|
+
? response.length
|
|
630
|
+
: commands.length
|
|
631
|
+
|
|
632
|
+
return { guildId, registeredCount }
|
|
633
|
+
}),
|
|
634
|
+
)
|
|
635
|
+
|
|
636
|
+
const failedGuilds = results
|
|
637
|
+
.map((result, index) => {
|
|
638
|
+
if (result.status === 'fulfilled') {
|
|
639
|
+
return null
|
|
640
|
+
}
|
|
641
|
+
|
|
642
|
+
return {
|
|
643
|
+
guildId: uniqueGuildIds[index],
|
|
644
|
+
error:
|
|
645
|
+
result.reason instanceof Error
|
|
646
|
+
? result.reason.message
|
|
647
|
+
: String(result.reason),
|
|
648
|
+
}
|
|
649
|
+
})
|
|
650
|
+
.filter((value): value is { guildId: string; error: string } => {
|
|
651
|
+
return value !== null
|
|
652
|
+
})
|
|
653
|
+
|
|
654
|
+
if (failedGuilds.length > 0) {
|
|
655
|
+
failedGuilds.forEach((failure) => {
|
|
656
|
+
cliLogger.warn(
|
|
657
|
+
`COMMANDS: Failed to register slash commands for guild ${failure.guildId}: ${failure.error}`,
|
|
658
|
+
)
|
|
659
|
+
})
|
|
660
|
+
throw new Error(
|
|
661
|
+
`Failed to register slash commands for ${failedGuilds.length} guild(s)`,
|
|
662
|
+
)
|
|
663
|
+
}
|
|
664
|
+
|
|
665
|
+
const successfulGuilds = results.length
|
|
666
|
+
const firstRegisteredCount = results[0]
|
|
667
|
+
const registeredCommandCount =
|
|
668
|
+
firstRegisteredCount && firstRegisteredCount.status === 'fulfilled'
|
|
669
|
+
? firstRegisteredCount.value.registeredCount
|
|
670
|
+
: commands.length
|
|
671
|
+
|
|
672
|
+
// In gateway mode, global application routes (/applications/{app_id}/commands)
|
|
673
|
+
// are denied by the proxy (DeniedWithoutGuild). Legacy global commands only
|
|
674
|
+
// exist for self-hosted bots that previously registered commands globally.
|
|
675
|
+
const isGateway = store.getState().discordBaseUrl !== 'https://discord.com'
|
|
676
|
+
if (!isGateway) {
|
|
677
|
+
await deleteLegacyGlobalCommands({
|
|
678
|
+
rest,
|
|
679
|
+
appId,
|
|
680
|
+
commandNames: guildCommandNames,
|
|
681
|
+
})
|
|
682
|
+
}
|
|
683
|
+
|
|
684
|
+
cliLogger.info(
|
|
685
|
+
`COMMANDS: Successfully registered ${registeredCommandCount} slash commands for ${successfulGuilds} guild(s)`,
|
|
686
|
+
)
|
|
687
|
+
} catch (error) {
|
|
688
|
+
cliLogger.error(
|
|
689
|
+
'COMMANDS: Failed to register slash commands: ' + String(error),
|
|
690
|
+
)
|
|
691
|
+
throw error
|
|
692
|
+
}
|
|
693
|
+
}
|