@otto-assistant/otto 0.1.2 → 0.7.16
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-account-identity.js +62 -0
- package/dist/anthropic-account-identity.test.js +38 -0
- package/dist/anthropic-auth-plugin.js +917 -0
- package/dist/anthropic-auth-state.js +303 -0
- package/dist/anthropic-auth-state.test.js +150 -0
- package/dist/bin.js +152 -0
- package/dist/btw-prefix-detection.js +17 -0
- package/dist/btw-prefix-detection.test.js +63 -0
- package/dist/channel-management.js +259 -0
- package/dist/cli-parsing.test.js +142 -0
- package/dist/cli-send-thread.e2e.test.js +353 -0
- package/dist/cli-telegram-options.test.js +99 -0
- package/dist/cli.js +4210 -568
- package/dist/commands/abort.js +65 -0
- package/dist/commands/action-buttons.js +245 -0
- package/dist/commands/add-dir.js +124 -0
- package/dist/commands/add-dir.test.js +126 -0
- package/dist/commands/add-project.js +113 -0
- package/dist/commands/agent.js +355 -0
- package/dist/commands/ask-question.js +320 -0
- package/dist/commands/ask-question.test.js +92 -0
- package/dist/commands/btw.js +121 -0
- package/dist/commands/cli-commands-group-a.test.js +728 -0
- package/dist/commands/cli-commands-group-b.test.js +695 -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/discord-commands-group-a.test.js +655 -0
- package/dist/commands/discord-commands-group-b.test.js +595 -0
- package/dist/commands/discord-commands-group-c.test.js +739 -0
- package/dist/commands/file-upload.js +275 -0
- package/dist/commands/fork-subagent.js +177 -0
- package/dist/commands/fork.js +262 -0
- package/dist/commands/gemini-apikey.js +70 -0
- package/dist/commands/login.js +893 -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 +162 -0
- package/dist/commands/model-variant.js +369 -0
- package/dist/commands/model.js +798 -0
- package/dist/commands/new-worktree.js +465 -0
- package/dist/commands/paginated-select.js +57 -0
- package/dist/commands/permissions.js +274 -0
- package/dist/commands/queue.js +223 -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/thread-deletion-sync.js +50 -0
- package/dist/commands/types.js +2 -0
- package/dist/commands/undo-redo.js +305 -0
- package/dist/commands/unset-model.js +139 -0
- package/dist/commands/upgrade.js +48 -0
- package/dist/commands/user-command.js +155 -0
- package/dist/commands/verbosity.js +125 -0
- package/dist/commands/vscode.js +269 -0
- package/dist/commands/worktree-settings.js +43 -0
- package/dist/commands/worktrees.js +468 -0
- package/dist/condense-memory.js +33 -0
- package/dist/config.js +100 -255
- package/dist/context-awareness-plugin.js +340 -0
- package/dist/context-awareness-plugin.test.js +126 -0
- package/dist/critique-utils.js +95 -0
- package/dist/database.js +1355 -0
- package/dist/db.js +260 -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 +1124 -0
- package/dist/discord-command-registration.js +567 -0
- package/dist/discord-urls.js +82 -0
- package/dist/discord-utils.js +616 -0
- package/dist/discord-utils.test.js +134 -0
- package/dist/errors.js +179 -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 +491 -0
- package/dist/format-tables.test.js +478 -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 +485 -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 +58 -0
- package/dist/generated/internal/class.js +49 -0
- package/dist/generated/internal/prismaNamespace.js +254 -0
- package/dist/generated/internal/prismaNamespaceBrowser.js +224 -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 +251 -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 +420 -0
- package/dist/ipc-polling.js +327 -0
- package/dist/ipc-tools-plugin.js +193 -0
- package/dist/ipc-utils.js +18 -0
- package/dist/limit-heading-depth.js +25 -0
- package/dist/limit-heading-depth.test.js +105 -0
- package/dist/logger.js +171 -0
- package/dist/markdown.js +342 -0
- package/dist/markdown.test.js +264 -0
- package/dist/memory-overview-plugin.js +128 -0
- package/dist/message-finish-field.e2e.test.js +168 -0
- package/dist/message-formatting.js +415 -0
- package/dist/message-formatting.test.js +115 -0
- package/dist/message-preprocessing.js +359 -0
- package/dist/onboarding-tutorial.js +163 -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 +131 -0
- package/dist/opencode-command.test.js +48 -0
- package/dist/opencode-interrupt-plugin.js +388 -0
- package/dist/opencode-interrupt-plugin.test.js +463 -0
- package/dist/opencode.js +1124 -0
- package/dist/otto/branding.js +22 -0
- package/dist/otto/index.js +21 -0
- package/dist/otto-digital-twin.e2e.test.js +161 -0
- package/dist/otto-opencode-plugin-loading.e2e.test.js +94 -0
- package/dist/otto-opencode-plugin.js +21 -0
- package/dist/otto-opencode-plugin.test.js +98 -0
- package/dist/parse-permission-rules.test.js +117 -0
- package/dist/patch-text-parser.js +97 -0
- package/dist/plugin-logger.js +68 -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 +790 -0
- package/dist/queue-advanced-footer.e2e.test.js +481 -0
- package/dist/queue-advanced-model-switch.e2e.test.js +299 -0
- package/dist/queue-advanced-permissions-typing.e2e.test.js +179 -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 +256 -0
- package/dist/runtime-idle-sweeper.js +52 -0
- package/dist/runtime-lifecycle.e2e.test.js +514 -0
- package/dist/sentry.js +23 -0
- package/dist/session-handler/agent-utils.js +67 -0
- package/dist/session-handler/event-stream-state.js +475 -0
- package/dist/session-handler/event-stream-state.test.js +632 -0
- package/dist/session-handler/model-utils.js +147 -0
- package/dist/session-handler/opencode-session-event-log.js +94 -0
- package/dist/session-handler/thread-runtime-state.js +131 -0
- package/dist/session-handler/thread-session-runtime.js +3390 -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 +92 -0
- package/dist/skill-filter.js +31 -0
- package/dist/skill-filter.test.js +65 -0
- package/dist/startup-service.js +153 -0
- package/dist/startup-time.e2e.test.js +296 -0
- package/dist/store.js +19 -0
- package/dist/subagent-rate-limit-plugin.js +175 -0
- package/dist/system-message.js +702 -0
- package/dist/system-message.test.js +697 -0
- package/dist/task-runner.js +530 -0
- package/dist/task-schedule.js +213 -0
- package/dist/task-schedule.test.js +71 -0
- package/dist/test-utils.js +313 -0
- package/dist/thinking-utils.js +35 -0
- package/dist/thread-message-queue.e2e.test.js +1111 -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 +156 -0
- package/dist/utils.js +172 -0
- package/dist/utils.test.js +130 -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 +456 -0
- package/dist/voice.test.js +235 -0
- package/dist/wait-session.js +171 -0
- package/dist/websockify.js +69 -0
- package/dist/worker-types.js +4 -0
- package/dist/worktree-lifecycle.e2e.test.js +311 -0
- package/dist/worktree-utils.js +3 -0
- package/dist/worktrees.js +991 -0
- package/dist/worktrees.test.js +415 -0
- package/dist/xml.js +92 -0
- package/dist/xml.test.js +32 -0
- package/package.json +90 -38
- package/schema.prisma +303 -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/goke/SKILL.md +38 -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/manual-kimaki-upstream-adapt/SKILL.md +114 -0
- package/skills/new-skill/SKILL.md +237 -0
- package/skills/npm-package/SKILL.md +617 -0
- package/skills/opensrc/SKILL.md +78 -0
- package/skills/otto-publish/SKILL.md +61 -0
- package/skills/playwriter/SKILL.md +35 -0
- package/skills/profano/SKILL.md +16 -0
- package/skills/proxyman/SKILL.md +215 -0
- package/skills/security-review/SKILL.md +208 -0
- package/skills/sigillo/SKILL.md +101 -0
- package/skills/simplify/SKILL.md +58 -0
- package/skills/spiceflow/SKILL.md +28 -0
- package/skills/termcast/SKILL.md +945 -0
- package/skills/tuistory/SKILL.md +98 -0
- package/skills/usecomputer/SKILL.md +264 -0
- package/skills/x-articles/SKILL.md +554 -0
- package/skills/zele/SKILL.md +49 -0
- package/skills/zustand-centralized-state/SKILL.md +1004 -0
- package/src/agent-model.e2e.test.ts +979 -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-account-identity.test.ts +52 -0
- package/src/anthropic-account-identity.ts +77 -0
- package/src/anthropic-auth-plugin.ts +1139 -0
- package/src/anthropic-auth-state.test.ts +187 -0
- package/src/anthropic-auth-state.ts +386 -0
- package/src/bin.ts +182 -0
- package/src/btw-prefix-detection.test.ts +73 -0
- package/src/btw-prefix-detection.ts +23 -0
- package/src/channel-management.ts +376 -0
- package/src/cli-parsing.test.ts +197 -0
- package/src/cli-send-thread.e2e.test.ts +463 -0
- package/src/cli-telegram-options.test.ts +114 -0
- package/src/cli.ts +5718 -580
- package/src/commands/abort.ts +89 -0
- package/src/commands/action-buttons.ts +364 -0
- package/src/commands/add-dir.test.ts +154 -0
- package/src/commands/add-dir.ts +175 -0
- package/src/commands/add-project.ts +149 -0
- package/src/commands/agent.ts +496 -0
- package/src/commands/ask-question.test.ts +111 -0
- package/src/commands/ask-question.ts +455 -0
- package/src/commands/btw.ts +184 -0
- package/src/commands/cli-commands-group-a.test.ts +837 -0
- package/src/commands/cli-commands-group-b.test.ts +800 -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/discord-commands-group-a.test.ts +789 -0
- package/src/commands/discord-commands-group-b.test.ts +648 -0
- package/src/commands/discord-commands-group-c.test.ts +882 -0
- package/src/commands/file-upload.ts +389 -0
- package/src/commands/fork-subagent.ts +263 -0
- package/src/commands/fork.ts +386 -0
- package/src/commands/gemini-apikey.ts +104 -0
- package/src/commands/login.ts +1181 -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 +226 -0
- package/src/commands/model-variant.ts +488 -0
- package/src/commands/model.ts +1082 -0
- package/src/commands/new-worktree.ts +645 -0
- package/src/commands/paginated-select.ts +81 -0
- package/src/commands/permissions.ts +397 -0
- package/src/commands/queue.ts +293 -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/thread-deletion-sync.ts +80 -0
- package/src/commands/types.ts +25 -0
- package/src/commands/undo-redo.ts +386 -0
- package/src/commands/unset-model.ts +174 -0
- package/src/commands/upgrade.ts +59 -0
- package/src/commands/user-command.ts +198 -0
- package/src/commands/verbosity.ts +173 -0
- package/src/commands/vscode.ts +342 -0
- package/src/commands/worktree-settings.ts +70 -0
- package/src/commands/worktrees.ts +645 -0
- package/src/condense-memory.ts +36 -0
- package/src/config.ts +103 -339
- package/src/context-awareness-plugin.test.ts +144 -0
- package/src/context-awareness-plugin.ts +469 -0
- package/src/critique-utils.ts +139 -0
- package/src/database.ts +1949 -0
- package/src/db.test.ts +162 -0
- package/src/db.ts +295 -0
- package/src/debounce-timeout.ts +43 -0
- package/src/debounced-process-flush.ts +104 -0
- package/src/discord-bot.ts +1507 -0
- package/src/discord-command-registration.ts +752 -0
- package/src/discord-urls.ts +89 -0
- package/src/discord-utils.test.ts +153 -0
- package/src/discord-utils.ts +846 -0
- package/src/errors.ts +232 -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 +515 -0
- package/src/format-tables.ts +718 -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 +644 -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 +770 -0
- package/src/generated/enums.ts +98 -0
- package/src/generated/internal/class.ts +384 -0
- package/src/generated/internal/prismaNamespace.ts +2394 -0
- package/src/generated/internal/prismaNamespaceBrowser.ts +327 -0
- package/src/generated/models/bot_api_keys.ts +1288 -0
- package/src/generated/models/bot_tokens.ts +1700 -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 +299 -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 +610 -0
- package/src/ipc-polling.ts +427 -0
- package/src/ipc-tools-plugin.ts +236 -0
- package/src/ipc-utils.ts +29 -0
- package/src/limit-heading-depth.test.ts +116 -0
- package/src/limit-heading-depth.ts +26 -0
- package/src/logger.ts +215 -0
- package/src/markdown.test.ts +315 -0
- package/src/markdown.ts +410 -0
- package/src/memory-overview-plugin.ts +163 -0
- package/src/message-finish-field.e2e.test.ts +195 -0
- package/src/message-formatting.test.ts +126 -0
- package/src/message-formatting.ts +535 -0
- package/src/message-preprocessing.ts +488 -0
- package/src/onboarding-tutorial.ts +167 -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 +191 -0
- package/src/opencode-interrupt-plugin.test.ts +682 -0
- package/src/opencode-interrupt-plugin.ts +507 -0
- package/src/opencode.ts +1462 -0
- package/src/otto/branding.ts +23 -0
- package/src/otto/index.ts +22 -0
- package/src/otto-digital-twin.e2e.test.ts +199 -0
- package/src/otto-opencode-plugin-loading.e2e.test.ts +117 -0
- package/src/otto-opencode-plugin.test.ts +108 -0
- package/src/otto-opencode-plugin.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 +84 -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 +877 -0
- package/src/queue-advanced-footer.e2e.test.ts +591 -0
- package/src/queue-advanced-model-switch.e2e.test.ts +383 -0
- package/src/queue-advanced-permissions-typing.e2e.test.ts +246 -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 +327 -0
- package/src/runtime-idle-sweeper.ts +76 -0
- package/src/runtime-lifecycle.e2e.test.ts +651 -0
- package/src/schema.sql +174 -0
- package/src/sentry.ts +26 -0
- package/src/session-handler/agent-utils.ts +99 -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 +717 -0
- package/src/session-handler/event-stream-state.ts +706 -0
- package/src/session-handler/model-utils.ts +217 -0
- package/src/session-handler/opencode-session-event-log.ts +130 -0
- package/src/session-handler/thread-runtime-state.ts +247 -0
- package/src/session-handler/thread-session-runtime.ts +4440 -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 +130 -0
- package/src/skill-filter.test.ts +83 -0
- package/src/skill-filter.ts +42 -0
- package/src/startup-service.ts +200 -0
- package/src/startup-time.e2e.test.ts +373 -0
- package/src/store.ts +139 -0
- package/src/subagent-rate-limit-plugin.ts +218 -0
- package/src/system-message.test.ts +710 -0
- package/src/system-message.ts +814 -0
- package/src/task-runner.ts +725 -0
- package/src/task-schedule.test.ts +84 -0
- package/src/task-schedule.ts +317 -0
- package/src/test-utils.ts +451 -0
- package/src/thinking-utils.ts +61 -0
- package/src/thread-message-queue.e2e.test.ts +1350 -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 +185 -0
- package/src/utils.test.ts +155 -0
- package/src/utils.ts +265 -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 +638 -0
- package/src/wait-session.ts +273 -0
- package/src/websockify.ts +101 -0
- package/src/worker-types.ts +64 -0
- package/src/worktree-lifecycle.e2e.test.ts +396 -0
- package/src/worktree-utils.ts +4 -0
- package/src/worktrees.test.ts +489 -0
- package/src/worktrees.ts +1370 -0
- package/src/xml.test.ts +38 -0
- package/src/xml.ts +121 -0
- package/README.md +0 -142
- package/dist/cli.d.ts +0 -3
- package/dist/cli.d.ts.map +0 -1
- package/dist/cli.js.map +0 -1
- package/dist/config.d.ts +0 -39
- package/dist/config.d.ts.map +0 -1
- package/dist/config.js.map +0 -1
- package/dist/config.test.d.ts +0 -2
- package/dist/config.test.d.ts.map +0 -1
- package/dist/config.test.js +0 -202
- package/dist/config.test.js.map +0 -1
- package/dist/detect.d.ts +0 -9
- package/dist/detect.d.ts.map +0 -1
- package/dist/detect.js +0 -40
- package/dist/detect.js.map +0 -1
- package/dist/detect.test.d.ts +0 -2
- package/dist/detect.test.d.ts.map +0 -1
- package/dist/detect.test.js +0 -26
- package/dist/detect.test.js.map +0 -1
- package/dist/docker.d.ts +0 -7
- package/dist/docker.d.ts.map +0 -1
- package/dist/docker.js +0 -17
- package/dist/docker.js.map +0 -1
- package/dist/docker.test.d.ts +0 -2
- package/dist/docker.test.d.ts.map +0 -1
- package/dist/docker.test.js +0 -12
- package/dist/docker.test.js.map +0 -1
- package/dist/health.d.ts +0 -31
- package/dist/health.d.ts.map +0 -1
- package/dist/health.js +0 -117
- package/dist/health.js.map +0 -1
- package/dist/health.test.d.ts +0 -2
- package/dist/health.test.d.ts.map +0 -1
- package/dist/health.test.js +0 -52
- package/dist/health.test.js.map +0 -1
- package/dist/index.d.ts +0 -20
- package/dist/index.d.ts.map +0 -1
- package/dist/index.js +0 -15
- package/dist/index.js.map +0 -1
- package/dist/index.test.d.ts +0 -2
- package/dist/index.test.d.ts.map +0 -1
- package/dist/index.test.js +0 -8
- package/dist/index.test.js.map +0 -1
- package/dist/installer.d.ts +0 -10
- package/dist/installer.d.ts.map +0 -1
- package/dist/installer.js +0 -50
- package/dist/installer.js.map +0 -1
- package/dist/installer.test.d.ts +0 -2
- package/dist/installer.test.d.ts.map +0 -1
- package/dist/installer.test.js +0 -43
- package/dist/installer.test.js.map +0 -1
- package/dist/lifecycle.d.ts +0 -10
- package/dist/lifecycle.d.ts.map +0 -1
- package/dist/lifecycle.js +0 -45
- package/dist/lifecycle.js.map +0 -1
- package/dist/lifecycle.test.d.ts +0 -2
- package/dist/lifecycle.test.d.ts.map +0 -1
- package/dist/lifecycle.test.js +0 -20
- package/dist/lifecycle.test.js.map +0 -1
- package/dist/manifest.d.ts +0 -18
- package/dist/manifest.d.ts.map +0 -1
- package/dist/manifest.js +0 -30
- package/dist/manifest.js.map +0 -1
- package/dist/skills-baseline.d.ts +0 -7
- package/dist/skills-baseline.d.ts.map +0 -1
- package/dist/skills-baseline.js +0 -9
- package/dist/skills-baseline.js.map +0 -1
- package/dist/skills.d.ts +0 -110
- package/dist/skills.d.ts.map +0 -1
- package/dist/skills.js +0 -429
- package/dist/skills.js.map +0 -1
- package/dist/skills.test.d.ts +0 -2
- package/dist/skills.test.d.ts.map +0 -1
- package/dist/skills.test.js +0 -416
- package/dist/skills.test.js.map +0 -1
- package/dist/sync.d.ts +0 -10
- package/dist/sync.d.ts.map +0 -1
- package/dist/sync.js +0 -39
- package/dist/sync.js.map +0 -1
- package/dist/tenant.d.ts +0 -13
- package/dist/tenant.d.ts.map +0 -1
- package/dist/tenant.js +0 -105
- package/dist/tenant.js.map +0 -1
- package/dist/tenant.test.d.ts +0 -2
- package/dist/tenant.test.d.ts.map +0 -1
- package/dist/tenant.test.js +0 -37
- package/dist/tenant.test.js.map +0 -1
- package/src/config.test.ts +0 -237
- package/src/detect.test.ts +0 -29
- package/src/detect.ts +0 -52
- package/src/docker.test.ts +0 -12
- package/src/docker.ts +0 -23
- package/src/health.test.ts +0 -61
- package/src/health.ts +0 -158
- package/src/index.test.ts +0 -8
- package/src/index.ts +0 -62
- package/src/installer.test.ts +0 -52
- package/src/installer.ts +0 -62
- package/src/lifecycle.test.ts +0 -23
- package/src/lifecycle.ts +0 -49
- package/src/manifest.ts +0 -42
- package/src/skills-baseline.ts +0 -14
- package/src/skills.test.ts +0 -503
- package/src/skills.ts +0 -512
- package/src/sync.ts +0 -53
- package/src/tenant.test.ts +0 -49
- package/src/tenant.ts +0 -120
|
@@ -0,0 +1,293 @@
|
|
|
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 otto 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
|
+
const position = command.options.getInteger('position') ?? undefined
|
|
104
|
+
|
|
105
|
+
if (!channel) {
|
|
106
|
+
await command.reply({
|
|
107
|
+
content: 'This command can only be used in a channel',
|
|
108
|
+
flags: MessageFlags.Ephemeral | SILENT_MESSAGE_FLAGS,
|
|
109
|
+
})
|
|
110
|
+
return
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
const isThread = [
|
|
114
|
+
ChannelType.PublicThread,
|
|
115
|
+
ChannelType.PrivateThread,
|
|
116
|
+
ChannelType.AnnouncementThread,
|
|
117
|
+
].includes(channel.type)
|
|
118
|
+
|
|
119
|
+
if (!isThread) {
|
|
120
|
+
await command.reply({
|
|
121
|
+
content: 'This command can only be used in a thread',
|
|
122
|
+
flags: MessageFlags.Ephemeral | SILENT_MESSAGE_FLAGS,
|
|
123
|
+
})
|
|
124
|
+
return
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
const runtime = getRuntime(channel.id)
|
|
128
|
+
const queueLength = runtime?.getQueueLength() ?? 0
|
|
129
|
+
|
|
130
|
+
if (queueLength === 0) {
|
|
131
|
+
await command.reply({
|
|
132
|
+
content: 'No messages in queue',
|
|
133
|
+
flags: MessageFlags.Ephemeral | SILENT_MESSAGE_FLAGS,
|
|
134
|
+
})
|
|
135
|
+
return
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
if (position !== undefined) {
|
|
139
|
+
const removed = runtime?.removeQueuePosition(position)
|
|
140
|
+
if (!removed) {
|
|
141
|
+
await command.reply({
|
|
142
|
+
content: `No queued message at position ${position}`,
|
|
143
|
+
flags: MessageFlags.Ephemeral | SILENT_MESSAGE_FLAGS,
|
|
144
|
+
})
|
|
145
|
+
return
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
await command.reply({
|
|
149
|
+
content: `Cleared queued message at position ${position}`,
|
|
150
|
+
flags: SILENT_MESSAGE_FLAGS,
|
|
151
|
+
})
|
|
152
|
+
|
|
153
|
+
logger.log(
|
|
154
|
+
`[QUEUE] User ${command.user.displayName} cleared queued position ${position} in thread ${channel.id}`,
|
|
155
|
+
)
|
|
156
|
+
return
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
runtime?.clearQueue()
|
|
160
|
+
|
|
161
|
+
await command.reply({
|
|
162
|
+
content: `Cleared ${queueLength} queued message${queueLength > 1 ? 's' : ''}`,
|
|
163
|
+
flags: SILENT_MESSAGE_FLAGS,
|
|
164
|
+
})
|
|
165
|
+
|
|
166
|
+
logger.log(
|
|
167
|
+
`[QUEUE] User ${command.user.displayName} cleared queue in thread ${channel.id}`,
|
|
168
|
+
)
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
export async function handleQueueCommandCommand({
|
|
172
|
+
command,
|
|
173
|
+
appId,
|
|
174
|
+
}: CommandContext): Promise<void> {
|
|
175
|
+
const commandName = command.options.getString('command', true)
|
|
176
|
+
const args = command.options.getString('arguments') || ''
|
|
177
|
+
const channel = command.channel
|
|
178
|
+
|
|
179
|
+
if (!channel) {
|
|
180
|
+
await command.reply({
|
|
181
|
+
content: 'This command can only be used in a channel',
|
|
182
|
+
flags: MessageFlags.Ephemeral | SILENT_MESSAGE_FLAGS,
|
|
183
|
+
})
|
|
184
|
+
return
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
const isThread = [
|
|
188
|
+
ChannelType.PublicThread,
|
|
189
|
+
ChannelType.PrivateThread,
|
|
190
|
+
ChannelType.AnnouncementThread,
|
|
191
|
+
].includes(channel.type)
|
|
192
|
+
|
|
193
|
+
if (!isThread) {
|
|
194
|
+
await command.reply({
|
|
195
|
+
content:
|
|
196
|
+
'This command can only be used in a thread with an active session',
|
|
197
|
+
flags: MessageFlags.Ephemeral | SILENT_MESSAGE_FLAGS,
|
|
198
|
+
})
|
|
199
|
+
return
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
const sessionId = await getThreadSession(channel.id)
|
|
203
|
+
|
|
204
|
+
if (!sessionId) {
|
|
205
|
+
await command.reply({
|
|
206
|
+
content:
|
|
207
|
+
'No active session in this thread. Send a message directly instead.',
|
|
208
|
+
flags: MessageFlags.Ephemeral | SILENT_MESSAGE_FLAGS,
|
|
209
|
+
})
|
|
210
|
+
return
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
// Validate command exists in registered user commands
|
|
214
|
+
const isKnownCommand = store.getState().registeredUserCommands.some((cmd) => {
|
|
215
|
+
return cmd.name === commandName
|
|
216
|
+
})
|
|
217
|
+
if (!isKnownCommand) {
|
|
218
|
+
await command.reply({
|
|
219
|
+
content: `Unknown command: /${commandName}. Use autocomplete to pick from available commands.`,
|
|
220
|
+
flags: MessageFlags.Ephemeral | SILENT_MESSAGE_FLAGS,
|
|
221
|
+
})
|
|
222
|
+
return
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
const commandPayload = { name: commandName, arguments: args }
|
|
226
|
+
const displayText = `/${commandName}`
|
|
227
|
+
const thread = channel as ThreadChannel
|
|
228
|
+
|
|
229
|
+
const resolved = await resolveWorkingDirectory({ channel: thread })
|
|
230
|
+
if (!resolved) {
|
|
231
|
+
await command.reply({
|
|
232
|
+
content: 'Could not determine project directory',
|
|
233
|
+
flags: MessageFlags.Ephemeral | SILENT_MESSAGE_FLAGS,
|
|
234
|
+
})
|
|
235
|
+
return
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
const runtime = getOrCreateRuntime({
|
|
239
|
+
threadId: thread.id,
|
|
240
|
+
thread,
|
|
241
|
+
projectDirectory: resolved.projectDirectory,
|
|
242
|
+
sdkDirectory: resolved.workingDirectory,
|
|
243
|
+
channelId: thread.parentId || thread.id,
|
|
244
|
+
appId,
|
|
245
|
+
})
|
|
246
|
+
|
|
247
|
+
// /queue-command explicitly uses otto local queue mode.
|
|
248
|
+
const enqueueResult = await runtime.enqueueIncoming({
|
|
249
|
+
prompt: '',
|
|
250
|
+
userId: command.user.id,
|
|
251
|
+
username: command.user.displayName,
|
|
252
|
+
appId,
|
|
253
|
+
command: commandPayload,
|
|
254
|
+
mode: 'local-queue',
|
|
255
|
+
})
|
|
256
|
+
|
|
257
|
+
const responseText = enqueueResult.queued
|
|
258
|
+
? `Queued message${enqueueResult.position ? ` (position ${enqueueResult.position})` : ''}`
|
|
259
|
+
: `» **${command.user.displayName}:** ${displayText}`
|
|
260
|
+
|
|
261
|
+
await command.reply({
|
|
262
|
+
content: responseText,
|
|
263
|
+
flags: SILENT_MESSAGE_FLAGS,
|
|
264
|
+
})
|
|
265
|
+
|
|
266
|
+
logger.log(
|
|
267
|
+
`[QUEUE] User ${command.user.displayName} queued command /${commandName} in thread ${channel.id}`,
|
|
268
|
+
)
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
export async function handleQueueCommandAutocomplete({
|
|
272
|
+
interaction,
|
|
273
|
+
}: AutocompleteContext): Promise<void> {
|
|
274
|
+
const focused = interaction.options.getFocused(true)
|
|
275
|
+
|
|
276
|
+
if (focused.name !== 'command') {
|
|
277
|
+
await interaction.respond([])
|
|
278
|
+
return
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
const query = focused.value.toLowerCase()
|
|
282
|
+
const choices = store.getState().registeredUserCommands
|
|
283
|
+
.filter((cmd) => {
|
|
284
|
+
return cmd.name.toLowerCase().includes(query)
|
|
285
|
+
})
|
|
286
|
+
.slice(0, 25)
|
|
287
|
+
.map((cmd) => ({
|
|
288
|
+
name: `/${cmd.name} [${cmd.source === 'skill' ? 'skill' : cmd.source === 'mcp' ? 'mcp' : 'cmd'}] - ${cmd.description}`.slice(0, 100),
|
|
289
|
+
value: cmd.name.slice(0, 100),
|
|
290
|
+
}))
|
|
291
|
+
|
|
292
|
+
await interaction.respond(choices)
|
|
293
|
+
}
|
|
@@ -0,0 +1,155 @@
|
|
|
1
|
+
// /remove-project command - Remove Discord channels for a project.
|
|
2
|
+
|
|
3
|
+
import path from 'node:path'
|
|
4
|
+
import * as errore from 'errore'
|
|
5
|
+
import type { CommandContext, AutocompleteContext } from './types.js'
|
|
6
|
+
import {
|
|
7
|
+
findChannelsByDirectory,
|
|
8
|
+
deleteChannelDirectoriesByDirectory,
|
|
9
|
+
getAllTextChannelDirectories,
|
|
10
|
+
} from '../database.js'
|
|
11
|
+
import { createLogger, LogPrefix } from '../logger.js'
|
|
12
|
+
import { abbreviatePath } from '../utils.js'
|
|
13
|
+
|
|
14
|
+
const logger = createLogger(LogPrefix.REMOVE_PROJECT)
|
|
15
|
+
|
|
16
|
+
export async function handleRemoveProjectCommand({
|
|
17
|
+
command,
|
|
18
|
+
appId,
|
|
19
|
+
}: CommandContext): Promise<void> {
|
|
20
|
+
await command.deferReply()
|
|
21
|
+
|
|
22
|
+
const directory = command.options.getString('project', true)
|
|
23
|
+
const guild = command.guild
|
|
24
|
+
|
|
25
|
+
if (!guild) {
|
|
26
|
+
await command.editReply('This command can only be used in a guild')
|
|
27
|
+
return
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
try {
|
|
31
|
+
// Get channel IDs for this directory
|
|
32
|
+
const channels = await findChannelsByDirectory({ directory })
|
|
33
|
+
|
|
34
|
+
if (channels.length === 0) {
|
|
35
|
+
await command.editReply(
|
|
36
|
+
`No channels found for directory: \`${directory}\``,
|
|
37
|
+
)
|
|
38
|
+
return
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
const deletedChannels: string[] = []
|
|
42
|
+
const failedChannels: string[] = []
|
|
43
|
+
|
|
44
|
+
for (const { channel_id, channel_type } of channels as Array<{
|
|
45
|
+
channel_id: string
|
|
46
|
+
channel_type: string
|
|
47
|
+
}>) {
|
|
48
|
+
const channel = await errore.tryAsync({
|
|
49
|
+
try: () => guild.channels.fetch(channel_id),
|
|
50
|
+
catch: (e) => e as Error,
|
|
51
|
+
})
|
|
52
|
+
|
|
53
|
+
if (channel instanceof Error) {
|
|
54
|
+
logger.error(`Failed to fetch channel ${channel_id}:`, channel)
|
|
55
|
+
failedChannels.push(`${channel_type}: ${channel_id}`)
|
|
56
|
+
continue
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
if (channel) {
|
|
60
|
+
try {
|
|
61
|
+
await channel.delete(`Removed by /remove-project command`)
|
|
62
|
+
deletedChannels.push(`${channel_type}: ${channel_id}`)
|
|
63
|
+
} catch (error) {
|
|
64
|
+
logger.error(`Failed to delete channel ${channel_id}:`, error)
|
|
65
|
+
failedChannels.push(`${channel_type}: ${channel_id}`)
|
|
66
|
+
}
|
|
67
|
+
} else {
|
|
68
|
+
deletedChannels.push(`${channel_type}: ${channel_id} (already deleted)`)
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
// Remove from database
|
|
73
|
+
await deleteChannelDirectoriesByDirectory(directory)
|
|
74
|
+
|
|
75
|
+
const projectName = path.basename(directory)
|
|
76
|
+
let message = `Removed project **${projectName}**\n`
|
|
77
|
+
message += `Directory: \`${directory}\`\n\n`
|
|
78
|
+
|
|
79
|
+
if (deletedChannels.length > 0) {
|
|
80
|
+
message += `Deleted channels:\n${deletedChannels.map((c) => `- ${c}`).join('\n')}`
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
if (failedChannels.length > 0) {
|
|
84
|
+
message += `\n\nFailed to delete (may be in another server):\n${failedChannels.map((c) => `- ${c}`).join('\n')}`
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
await command.editReply(message)
|
|
88
|
+
logger.log(`Removed project ${projectName} at ${directory}`)
|
|
89
|
+
} catch (error) {
|
|
90
|
+
logger.error('[REMOVE-PROJECT] Error:', error)
|
|
91
|
+
await command.editReply(
|
|
92
|
+
`Failed to remove project: ${error instanceof Error ? error.message : 'Unknown error'}`,
|
|
93
|
+
)
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
export async function handleRemoveProjectAutocomplete({
|
|
98
|
+
interaction,
|
|
99
|
+
appId,
|
|
100
|
+
}: AutocompleteContext): Promise<void> {
|
|
101
|
+
const focusedValue = interaction.options.getFocused()
|
|
102
|
+
const guild = interaction.guild
|
|
103
|
+
|
|
104
|
+
if (!guild) {
|
|
105
|
+
await interaction.respond([])
|
|
106
|
+
return
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
try {
|
|
110
|
+
// Get all directories with channels
|
|
111
|
+
const allChannels = (await findChannelsByDirectory({
|
|
112
|
+
channelType: 'text',
|
|
113
|
+
})) as Array<{
|
|
114
|
+
directory: string
|
|
115
|
+
channel_id: string
|
|
116
|
+
}>
|
|
117
|
+
|
|
118
|
+
// Filter to only channels that exist in this guild
|
|
119
|
+
const projectsInGuild: { directory: string; channelId: string }[] = []
|
|
120
|
+
|
|
121
|
+
for (const { directory, channel_id } of allChannels) {
|
|
122
|
+
const channel = await errore.tryAsync({
|
|
123
|
+
try: () => guild.channels.fetch(channel_id),
|
|
124
|
+
catch: (e) => e as Error,
|
|
125
|
+
})
|
|
126
|
+
if (channel instanceof Error) {
|
|
127
|
+
// Channel not in this guild, skip
|
|
128
|
+
continue
|
|
129
|
+
}
|
|
130
|
+
if (channel) {
|
|
131
|
+
projectsInGuild.push({ directory, channelId: channel_id })
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
const projects = projectsInGuild
|
|
136
|
+
.filter(({ directory }) => {
|
|
137
|
+
const baseName = path.basename(directory)
|
|
138
|
+
const searchText = `${baseName} ${directory}`.toLowerCase()
|
|
139
|
+
return searchText.includes(focusedValue.toLowerCase())
|
|
140
|
+
})
|
|
141
|
+
.slice(0, 25)
|
|
142
|
+
.map(({ directory }) => {
|
|
143
|
+
const name = `${path.basename(directory)} (${abbreviatePath(directory)})`
|
|
144
|
+
return {
|
|
145
|
+
name: name.length > 100 ? name.slice(0, 99) + '...' : name,
|
|
146
|
+
value: directory,
|
|
147
|
+
}
|
|
148
|
+
})
|
|
149
|
+
|
|
150
|
+
await interaction.respond(projects)
|
|
151
|
+
} catch (error) {
|
|
152
|
+
logger.error('[AUTOCOMPLETE] Error fetching projects:', error)
|
|
153
|
+
await interaction.respond([])
|
|
154
|
+
}
|
|
155
|
+
}
|
|
@@ -0,0 +1,162 @@
|
|
|
1
|
+
// /restart-opencode-server command - Restart the single shared opencode server
|
|
2
|
+
// and re-register Discord slash commands.
|
|
3
|
+
// Used for resolving opencode state issues, internal bugs, refreshing auth state,
|
|
4
|
+
// plugins, and picking up new/changed slash commands or agents. Aborts in-progress
|
|
5
|
+
// sessions in this channel before restarting. Note: since there is one shared server,
|
|
6
|
+
// this restart affects all projects. Other runtimes reconnect through their listener
|
|
7
|
+
// backoff loop once the shared server comes back.
|
|
8
|
+
|
|
9
|
+
import {
|
|
10
|
+
ChannelType,
|
|
11
|
+
MessageFlags,
|
|
12
|
+
type ThreadChannel,
|
|
13
|
+
type TextChannel,
|
|
14
|
+
} from 'discord.js'
|
|
15
|
+
import type { Command as OpencodeCommand } from '@opencode-ai/sdk/v2'
|
|
16
|
+
import type { CommandContext } from './types.js'
|
|
17
|
+
import { initializeOpencodeForDirectory, restartOpencodeServer } from '../opencode.js'
|
|
18
|
+
import {
|
|
19
|
+
resolveWorkingDirectory,
|
|
20
|
+
SILENT_MESSAGE_FLAGS,
|
|
21
|
+
} from '../discord-utils.js'
|
|
22
|
+
import { createLogger, LogPrefix } from '../logger.js'
|
|
23
|
+
import { disposeRuntimesForDirectory } from '../session-handler/thread-session-runtime.js'
|
|
24
|
+
import { registerCommands, type AgentInfo } from '../discord-command-registration.js'
|
|
25
|
+
|
|
26
|
+
const logger = createLogger(LogPrefix.OPENCODE)
|
|
27
|
+
|
|
28
|
+
export async function handleRestartOpencodeServerCommand({
|
|
29
|
+
command,
|
|
30
|
+
appId,
|
|
31
|
+
}: CommandContext): Promise<void> {
|
|
32
|
+
const channel = command.channel
|
|
33
|
+
|
|
34
|
+
if (!channel) {
|
|
35
|
+
await command.reply({
|
|
36
|
+
content: 'This command can only be used in a channel',
|
|
37
|
+
flags: MessageFlags.Ephemeral | SILENT_MESSAGE_FLAGS,
|
|
38
|
+
})
|
|
39
|
+
return
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
const isThread = [
|
|
43
|
+
ChannelType.PublicThread,
|
|
44
|
+
ChannelType.PrivateThread,
|
|
45
|
+
ChannelType.AnnouncementThread,
|
|
46
|
+
].includes(channel.type)
|
|
47
|
+
|
|
48
|
+
const isTextChannel = channel.type === ChannelType.GuildText
|
|
49
|
+
|
|
50
|
+
if (!isThread && !isTextChannel) {
|
|
51
|
+
await command.reply({
|
|
52
|
+
content: 'This command can only be used in text channels or threads',
|
|
53
|
+
flags: MessageFlags.Ephemeral | SILENT_MESSAGE_FLAGS,
|
|
54
|
+
})
|
|
55
|
+
return
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
const resolved = await resolveWorkingDirectory({
|
|
59
|
+
channel: channel as TextChannel | ThreadChannel,
|
|
60
|
+
})
|
|
61
|
+
|
|
62
|
+
if (!resolved) {
|
|
63
|
+
await command.reply({
|
|
64
|
+
content: 'Could not determine project directory for this channel',
|
|
65
|
+
flags: MessageFlags.Ephemeral | SILENT_MESSAGE_FLAGS,
|
|
66
|
+
})
|
|
67
|
+
return
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
const { projectDirectory } = resolved
|
|
71
|
+
|
|
72
|
+
// Defer reply since restart may take a moment
|
|
73
|
+
await command.deferReply({ flags: SILENT_MESSAGE_FLAGS })
|
|
74
|
+
|
|
75
|
+
// Dispose all runtimes for this directory/channel scope.
|
|
76
|
+
// disposeRuntimesForDirectory aborts active runs, kills listeners, and
|
|
77
|
+
// removes runtimes from the registry. Scoped by channelId so runtimes
|
|
78
|
+
// in other channels sharing the same project directory are not affected.
|
|
79
|
+
const parentChannelId = isThread
|
|
80
|
+
? (channel as ThreadChannel).parentId
|
|
81
|
+
: channel.id
|
|
82
|
+
const abortedCount = disposeRuntimesForDirectory({
|
|
83
|
+
directory: projectDirectory,
|
|
84
|
+
channelId: parentChannelId || undefined,
|
|
85
|
+
})
|
|
86
|
+
|
|
87
|
+
logger.log(`[RESTART] Restarting shared opencode server`)
|
|
88
|
+
|
|
89
|
+
const result = await restartOpencodeServer()
|
|
90
|
+
|
|
91
|
+
if (result instanceof Error) {
|
|
92
|
+
logger.error('[RESTART] Failed:', result)
|
|
93
|
+
await command.editReply({
|
|
94
|
+
content: `Failed to restart opencode server: ${result.message}`,
|
|
95
|
+
})
|
|
96
|
+
return
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
const abortMsg =
|
|
100
|
+
abortedCount > 0
|
|
101
|
+
? ` (aborted ${abortedCount} active session${abortedCount > 1 ? 's' : ''})`
|
|
102
|
+
: ''
|
|
103
|
+
await command.editReply({
|
|
104
|
+
content: `Opencode server **restarted** successfully${abortMsg}. Re-registering slash commands...`,
|
|
105
|
+
})
|
|
106
|
+
logger.log('[RESTART] Shared opencode server restarted')
|
|
107
|
+
|
|
108
|
+
// Re-register Discord slash commands after restart so new/changed
|
|
109
|
+
// commands, agents, and plugins are picked up immediately.
|
|
110
|
+
const token = command.client.token
|
|
111
|
+
if (!token) {
|
|
112
|
+
logger.error('[RESTART] No bot token available, skipping command registration')
|
|
113
|
+
await command.editReply({
|
|
114
|
+
content: `Opencode server **restarted**${abortMsg}, but slash command re-registration skipped (no bot token)`,
|
|
115
|
+
})
|
|
116
|
+
return
|
|
117
|
+
}
|
|
118
|
+
const guildIds = [...command.client.guilds.cache.keys()]
|
|
119
|
+
|
|
120
|
+
const opencodeResult = await initializeOpencodeForDirectory(projectDirectory)
|
|
121
|
+
const [userCommands, agents]: [OpencodeCommand[], AgentInfo[]] =
|
|
122
|
+
await (async (): Promise<[OpencodeCommand[], AgentInfo[]]> => {
|
|
123
|
+
if (opencodeResult instanceof Error) {
|
|
124
|
+
logger.warn('[RESTART] OpenCode init failed, registering without user commands:', opencodeResult.message)
|
|
125
|
+
return [[], []]
|
|
126
|
+
}
|
|
127
|
+
const getClient = opencodeResult
|
|
128
|
+
const [cmds, ags] = await Promise.all([
|
|
129
|
+
getClient()
|
|
130
|
+
.command.list({ directory: projectDirectory })
|
|
131
|
+
.then((r) => r.data || [])
|
|
132
|
+
.catch((e) => {
|
|
133
|
+
logger.warn('[RESTART] Failed to load user commands:', e instanceof Error ? e.stack : String(e))
|
|
134
|
+
return [] as OpencodeCommand[]
|
|
135
|
+
}),
|
|
136
|
+
getClient()
|
|
137
|
+
.app.agents({ directory: projectDirectory })
|
|
138
|
+
.then((r) => r.data || [])
|
|
139
|
+
.catch((e) => {
|
|
140
|
+
logger.warn('[RESTART] Failed to load agents:', e instanceof Error ? e.stack : String(e))
|
|
141
|
+
return [] as AgentInfo[]
|
|
142
|
+
}),
|
|
143
|
+
])
|
|
144
|
+
return [cmds, ags]
|
|
145
|
+
})()
|
|
146
|
+
|
|
147
|
+
const registerResult = await registerCommands({ token, appId, guildIds, userCommands, agents })
|
|
148
|
+
.then(() => null)
|
|
149
|
+
.catch((e: unknown) => (e instanceof Error ? e : new Error(String(e))))
|
|
150
|
+
if (registerResult instanceof Error) {
|
|
151
|
+
logger.error('[RESTART] Failed to re-register commands:', registerResult.message)
|
|
152
|
+
await command.editReply({
|
|
153
|
+
content: `Opencode server **restarted**${abortMsg}, but slash command re-registration failed: ${registerResult.message}`,
|
|
154
|
+
})
|
|
155
|
+
return
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
logger.log('[RESTART] Slash commands re-registered')
|
|
159
|
+
await command.editReply({
|
|
160
|
+
content: `Opencode server **restarted** and slash commands **re-registered**${abortMsg}`,
|
|
161
|
+
})
|
|
162
|
+
}
|