@otto-assistant/otto 0.1.1 → 0.7.15
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 +621 -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 +887 -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 +366 -0
- package/dist/commands/model.js +794 -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 +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 +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 +1117 -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 +751 -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 +1175 -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 +485 -0
- package/src/commands/model.ts +1078 -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 +1505 -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 +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 +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 +1453 -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/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,125 @@
|
|
|
1
|
+
// /verbosity command.
|
|
2
|
+
// Shows a dropdown to set output verbosity level for sessions in a channel.
|
|
3
|
+
// 'text_and_essential_tools' (default): shows text and essential tools (edits, custom MCP tools)
|
|
4
|
+
// 'tools_and_text': shows all output including tool executions
|
|
5
|
+
// 'text_only': only shows text responses
|
|
6
|
+
import { ChatInputCommandInteraction, StringSelectMenuInteraction, StringSelectMenuBuilder, ActionRowBuilder, MessageFlags, ChannelType, } from 'discord.js';
|
|
7
|
+
import { getChannelVerbosity, setChannelVerbosity, } from '../database.js';
|
|
8
|
+
import { getPrisma } from '../db.js';
|
|
9
|
+
import { store } from '../store.js';
|
|
10
|
+
import { createLogger, LogPrefix } from '../logger.js';
|
|
11
|
+
const verbosityLogger = createLogger(LogPrefix.VERBOSITY);
|
|
12
|
+
const VERBOSITY_OPTIONS = [
|
|
13
|
+
{
|
|
14
|
+
value: 'tools_and_text',
|
|
15
|
+
label: 'Tools and text',
|
|
16
|
+
description: 'All output including tool executions and status messages',
|
|
17
|
+
},
|
|
18
|
+
{
|
|
19
|
+
value: 'text_and_essential_tools',
|
|
20
|
+
label: 'Text and essential tools',
|
|
21
|
+
description: 'Text + essential tools (edits, custom MCP). Hides read/search.',
|
|
22
|
+
},
|
|
23
|
+
{
|
|
24
|
+
value: 'text_only',
|
|
25
|
+
label: 'Text only',
|
|
26
|
+
description: 'Only text responses. Hides all tools and status messages.',
|
|
27
|
+
},
|
|
28
|
+
];
|
|
29
|
+
function resolveChannelId(channel) {
|
|
30
|
+
if (!channel) {
|
|
31
|
+
return null;
|
|
32
|
+
}
|
|
33
|
+
if (channel.type === ChannelType.GuildText) {
|
|
34
|
+
return channel.id;
|
|
35
|
+
}
|
|
36
|
+
if (channel.type === ChannelType.PublicThread ||
|
|
37
|
+
channel.type === ChannelType.PrivateThread ||
|
|
38
|
+
channel.type === ChannelType.AnnouncementThread) {
|
|
39
|
+
return channel.parentId || channel.id;
|
|
40
|
+
}
|
|
41
|
+
return channel.id;
|
|
42
|
+
}
|
|
43
|
+
/**
|
|
44
|
+
* Check if there is a per-channel verbosity override in the DB.
|
|
45
|
+
* Returns the override value if it exists, null otherwise.
|
|
46
|
+
*/
|
|
47
|
+
async function getChannelVerbosityOverride(channelId) {
|
|
48
|
+
const prisma = await getPrisma();
|
|
49
|
+
const row = await prisma.channel_verbosity.findUnique({
|
|
50
|
+
where: { channel_id: channelId },
|
|
51
|
+
});
|
|
52
|
+
if (row?.verbosity) {
|
|
53
|
+
return row.verbosity;
|
|
54
|
+
}
|
|
55
|
+
return null;
|
|
56
|
+
}
|
|
57
|
+
/**
|
|
58
|
+
* Handle the /verbosity slash command.
|
|
59
|
+
* Shows a dropdown with the current verbosity level and available options.
|
|
60
|
+
*/
|
|
61
|
+
export async function handleVerbosityCommand({ command, }) {
|
|
62
|
+
verbosityLogger.log('[VERBOSITY] Command called');
|
|
63
|
+
const channelId = resolveChannelId(command.channel);
|
|
64
|
+
if (!channelId) {
|
|
65
|
+
await command.reply({
|
|
66
|
+
content: 'Could not determine channel.',
|
|
67
|
+
flags: MessageFlags.Ephemeral,
|
|
68
|
+
});
|
|
69
|
+
return;
|
|
70
|
+
}
|
|
71
|
+
const override = await getChannelVerbosityOverride(channelId);
|
|
72
|
+
const currentLevel = override || store.getState().defaultVerbosity;
|
|
73
|
+
const source = override ? 'channel override' : 'global default';
|
|
74
|
+
const options = VERBOSITY_OPTIONS.map((opt) => ({
|
|
75
|
+
label: opt.label,
|
|
76
|
+
value: opt.value,
|
|
77
|
+
description: opt.description,
|
|
78
|
+
default: opt.value === currentLevel,
|
|
79
|
+
}));
|
|
80
|
+
const selectMenu = new StringSelectMenuBuilder()
|
|
81
|
+
.setCustomId(`verbosity_select:${channelId}`)
|
|
82
|
+
.setPlaceholder('Select verbosity level')
|
|
83
|
+
.addOptions(options);
|
|
84
|
+
const actionRow = new ActionRowBuilder().addComponents(selectMenu);
|
|
85
|
+
await command.reply({
|
|
86
|
+
content: `**Verbosity**\nCurrent: \`${currentLevel}\` (${source})`,
|
|
87
|
+
components: [actionRow],
|
|
88
|
+
flags: MessageFlags.Ephemeral,
|
|
89
|
+
});
|
|
90
|
+
}
|
|
91
|
+
/**
|
|
92
|
+
* Handle the verbosity select menu interaction.
|
|
93
|
+
* Sets the selected verbosity level for the channel.
|
|
94
|
+
*/
|
|
95
|
+
export async function handleVerbositySelectMenu(interaction) {
|
|
96
|
+
const customId = interaction.customId;
|
|
97
|
+
if (!customId.startsWith('verbosity_select:')) {
|
|
98
|
+
return;
|
|
99
|
+
}
|
|
100
|
+
await interaction.deferUpdate();
|
|
101
|
+
const channelId = customId.replace('verbosity_select:', '');
|
|
102
|
+
const level = interaction.values[0];
|
|
103
|
+
if (!level) {
|
|
104
|
+
await interaction.editReply({
|
|
105
|
+
content: 'No level selected.',
|
|
106
|
+
components: [],
|
|
107
|
+
});
|
|
108
|
+
return;
|
|
109
|
+
}
|
|
110
|
+
const currentLevel = await getChannelVerbosity(channelId);
|
|
111
|
+
if (currentLevel === level) {
|
|
112
|
+
await interaction.editReply({
|
|
113
|
+
content: `Verbosity is already \`${level}\` for this channel.`,
|
|
114
|
+
components: [],
|
|
115
|
+
});
|
|
116
|
+
return;
|
|
117
|
+
}
|
|
118
|
+
await setChannelVerbosity(channelId, level);
|
|
119
|
+
verbosityLogger.log(`[VERBOSITY] Set channel ${channelId} to ${level}`);
|
|
120
|
+
const description = VERBOSITY_OPTIONS.find((o) => o.value === level)?.description || '';
|
|
121
|
+
await interaction.editReply({
|
|
122
|
+
content: `Verbosity set to \`${level}\` for this channel.\n${description}\nApplies immediately, including active sessions.`,
|
|
123
|
+
components: [],
|
|
124
|
+
});
|
|
125
|
+
}
|
|
@@ -0,0 +1,269 @@
|
|
|
1
|
+
import crypto from 'node:crypto';
|
|
2
|
+
import { spawn } from 'node:child_process';
|
|
3
|
+
import net from 'node:net';
|
|
4
|
+
import { ChannelType, MessageFlags, } from 'discord.js';
|
|
5
|
+
import { TunnelClient } from 'traforo/client';
|
|
6
|
+
import { resolveWorkingDirectory, SILENT_MESSAGE_FLAGS, } from '../discord-utils.js';
|
|
7
|
+
import { createLogger } from '../logger.js';
|
|
8
|
+
const logger = createLogger('VSCODE');
|
|
9
|
+
const SECURE_REPLY_FLAGS = MessageFlags.Ephemeral | SILENT_MESSAGE_FLAGS;
|
|
10
|
+
const MAX_SESSION_MINUTES = 30;
|
|
11
|
+
const MAX_SESSION_MS = MAX_SESSION_MINUTES * 60 * 1000;
|
|
12
|
+
const TUNNEL_BASE_DOMAIN = 'otto.dev';
|
|
13
|
+
const TUNNEL_ID_BYTES = 16;
|
|
14
|
+
const READY_TIMEOUT_MS = 60_000;
|
|
15
|
+
const LOCAL_HOST = '127.0.0.1';
|
|
16
|
+
const activeSessions = new Map();
|
|
17
|
+
export function createVscodeTunnelId() {
|
|
18
|
+
return crypto.randomBytes(TUNNEL_ID_BYTES).toString('hex');
|
|
19
|
+
}
|
|
20
|
+
export function buildCoderaftArgs({ port, workingDirectory, }) {
|
|
21
|
+
return [
|
|
22
|
+
'coderaft',
|
|
23
|
+
'--port',
|
|
24
|
+
String(port),
|
|
25
|
+
'--host',
|
|
26
|
+
LOCAL_HOST,
|
|
27
|
+
'--without-connection-token',
|
|
28
|
+
'--disable-workspace-trust',
|
|
29
|
+
'--default-folder',
|
|
30
|
+
workingDirectory,
|
|
31
|
+
];
|
|
32
|
+
}
|
|
33
|
+
function createPortWaiter({ port, process: proc, timeoutMs, }) {
|
|
34
|
+
return new Promise((resolve, reject) => {
|
|
35
|
+
const maxAttempts = Math.ceil(timeoutMs / 100);
|
|
36
|
+
let attempts = 0;
|
|
37
|
+
const check = () => {
|
|
38
|
+
if (proc.exitCode !== null) {
|
|
39
|
+
reject(new Error(`coderaft exited with code ${proc.exitCode} before becoming ready`));
|
|
40
|
+
return;
|
|
41
|
+
}
|
|
42
|
+
const socket = net.createConnection(port, LOCAL_HOST);
|
|
43
|
+
socket.on('connect', () => {
|
|
44
|
+
socket.destroy();
|
|
45
|
+
resolve();
|
|
46
|
+
});
|
|
47
|
+
socket.on('error', () => {
|
|
48
|
+
socket.destroy();
|
|
49
|
+
attempts += 1;
|
|
50
|
+
if (attempts >= maxAttempts) {
|
|
51
|
+
reject(new Error(`Port ${port} not reachable after ${timeoutMs}ms`));
|
|
52
|
+
return;
|
|
53
|
+
}
|
|
54
|
+
setTimeout(check, 100);
|
|
55
|
+
});
|
|
56
|
+
};
|
|
57
|
+
check();
|
|
58
|
+
});
|
|
59
|
+
}
|
|
60
|
+
function getAvailablePort() {
|
|
61
|
+
return new Promise((resolve, reject) => {
|
|
62
|
+
const server = net.createServer();
|
|
63
|
+
server.on('error', reject);
|
|
64
|
+
server.listen(0, LOCAL_HOST, () => {
|
|
65
|
+
const address = server.address();
|
|
66
|
+
if (!address || typeof address === 'string') {
|
|
67
|
+
server.close(() => {
|
|
68
|
+
reject(new Error('Failed to resolve an available port'));
|
|
69
|
+
});
|
|
70
|
+
return;
|
|
71
|
+
}
|
|
72
|
+
const port = address.port;
|
|
73
|
+
server.close((error) => {
|
|
74
|
+
if (error) {
|
|
75
|
+
reject(error);
|
|
76
|
+
return;
|
|
77
|
+
}
|
|
78
|
+
resolve(port);
|
|
79
|
+
});
|
|
80
|
+
});
|
|
81
|
+
});
|
|
82
|
+
}
|
|
83
|
+
function cleanupSession(session) {
|
|
84
|
+
clearTimeout(session.timeoutTimer);
|
|
85
|
+
try {
|
|
86
|
+
session.tunnelClient.close();
|
|
87
|
+
}
|
|
88
|
+
catch { }
|
|
89
|
+
if (session.coderaftProcess.exitCode === null) {
|
|
90
|
+
try {
|
|
91
|
+
session.coderaftProcess.kill('SIGTERM');
|
|
92
|
+
}
|
|
93
|
+
catch { }
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
export function getActiveVscodeSession({ sessionKey }) {
|
|
97
|
+
return activeSessions.get(sessionKey);
|
|
98
|
+
}
|
|
99
|
+
export function stopVscode({ sessionKey }) {
|
|
100
|
+
const session = activeSessions.get(sessionKey);
|
|
101
|
+
if (!session) {
|
|
102
|
+
return false;
|
|
103
|
+
}
|
|
104
|
+
activeSessions.delete(sessionKey);
|
|
105
|
+
cleanupSession(session);
|
|
106
|
+
logger.log(`VS Code stopped (key: ${sessionKey})`);
|
|
107
|
+
return true;
|
|
108
|
+
}
|
|
109
|
+
export async function startVscode({ sessionKey, startedBy, workingDirectory, }) {
|
|
110
|
+
const existing = activeSessions.get(sessionKey);
|
|
111
|
+
if (existing) {
|
|
112
|
+
return existing;
|
|
113
|
+
}
|
|
114
|
+
const port = await getAvailablePort();
|
|
115
|
+
const tunnelId = createVscodeTunnelId();
|
|
116
|
+
const args = buildCoderaftArgs({
|
|
117
|
+
port,
|
|
118
|
+
workingDirectory,
|
|
119
|
+
});
|
|
120
|
+
const coderaftProcess = spawn('bunx', args, {
|
|
121
|
+
cwd: workingDirectory,
|
|
122
|
+
stdio: ['ignore', 'pipe', 'pipe'],
|
|
123
|
+
env: {
|
|
124
|
+
...process.env,
|
|
125
|
+
PORT: String(port),
|
|
126
|
+
},
|
|
127
|
+
});
|
|
128
|
+
coderaftProcess.stdout?.on('data', (data) => {
|
|
129
|
+
logger.log(data.toString().trim());
|
|
130
|
+
});
|
|
131
|
+
coderaftProcess.stderr?.on('data', (data) => {
|
|
132
|
+
logger.error(data.toString().trim());
|
|
133
|
+
});
|
|
134
|
+
try {
|
|
135
|
+
await createPortWaiter({
|
|
136
|
+
port,
|
|
137
|
+
process: coderaftProcess,
|
|
138
|
+
timeoutMs: READY_TIMEOUT_MS,
|
|
139
|
+
});
|
|
140
|
+
}
|
|
141
|
+
catch (error) {
|
|
142
|
+
if (coderaftProcess.exitCode === null) {
|
|
143
|
+
coderaftProcess.kill('SIGTERM');
|
|
144
|
+
}
|
|
145
|
+
throw error;
|
|
146
|
+
}
|
|
147
|
+
const tunnelClient = new TunnelClient({
|
|
148
|
+
localPort: port,
|
|
149
|
+
localHost: LOCAL_HOST,
|
|
150
|
+
tunnelId,
|
|
151
|
+
baseDomain: TUNNEL_BASE_DOMAIN,
|
|
152
|
+
});
|
|
153
|
+
try {
|
|
154
|
+
await Promise.race([
|
|
155
|
+
tunnelClient.connect(),
|
|
156
|
+
new Promise((_, reject) => {
|
|
157
|
+
setTimeout(() => {
|
|
158
|
+
reject(new Error('Tunnel connection timed out after 15s'));
|
|
159
|
+
}, 15_000);
|
|
160
|
+
}),
|
|
161
|
+
]);
|
|
162
|
+
}
|
|
163
|
+
catch (error) {
|
|
164
|
+
tunnelClient.close();
|
|
165
|
+
if (coderaftProcess.exitCode === null) {
|
|
166
|
+
coderaftProcess.kill('SIGTERM');
|
|
167
|
+
}
|
|
168
|
+
throw error;
|
|
169
|
+
}
|
|
170
|
+
const url = tunnelClient.url;
|
|
171
|
+
const timeoutTimer = setTimeout(() => {
|
|
172
|
+
logger.log(`VS Code auto-stopped after ${MAX_SESSION_MINUTES} minutes (key: ${sessionKey})`);
|
|
173
|
+
stopVscode({ sessionKey });
|
|
174
|
+
}, MAX_SESSION_MS);
|
|
175
|
+
timeoutTimer.unref();
|
|
176
|
+
const session = {
|
|
177
|
+
coderaftProcess,
|
|
178
|
+
tunnelClient,
|
|
179
|
+
url,
|
|
180
|
+
workingDirectory,
|
|
181
|
+
startedBy,
|
|
182
|
+
startedAt: Date.now(),
|
|
183
|
+
timeoutTimer,
|
|
184
|
+
};
|
|
185
|
+
coderaftProcess.once('exit', (code, signal) => {
|
|
186
|
+
const current = activeSessions.get(sessionKey);
|
|
187
|
+
if (current !== session) {
|
|
188
|
+
return;
|
|
189
|
+
}
|
|
190
|
+
logger.log(`VS Code process exited (key: ${sessionKey}, code: ${code}, signal: ${signal ?? 'none'})`);
|
|
191
|
+
stopVscode({ sessionKey });
|
|
192
|
+
});
|
|
193
|
+
activeSessions.set(sessionKey, session);
|
|
194
|
+
logger.log(`VS Code started by ${startedBy}: ${url}`);
|
|
195
|
+
return session;
|
|
196
|
+
}
|
|
197
|
+
export async function handleVscodeCommand({ command, }) {
|
|
198
|
+
const channel = command.channel;
|
|
199
|
+
if (!channel) {
|
|
200
|
+
await command.reply({
|
|
201
|
+
content: 'This command can only be used in a channel.',
|
|
202
|
+
flags: SECURE_REPLY_FLAGS,
|
|
203
|
+
});
|
|
204
|
+
return;
|
|
205
|
+
}
|
|
206
|
+
const isThread = [
|
|
207
|
+
ChannelType.PublicThread,
|
|
208
|
+
ChannelType.PrivateThread,
|
|
209
|
+
ChannelType.AnnouncementThread,
|
|
210
|
+
].includes(channel.type);
|
|
211
|
+
const isTextChannel = channel.type === ChannelType.GuildText;
|
|
212
|
+
if (!isThread && !isTextChannel) {
|
|
213
|
+
await command.reply({
|
|
214
|
+
content: 'This command can only be used in a text channel or thread.',
|
|
215
|
+
flags: SECURE_REPLY_FLAGS,
|
|
216
|
+
});
|
|
217
|
+
return;
|
|
218
|
+
}
|
|
219
|
+
const resolved = await resolveWorkingDirectory({
|
|
220
|
+
channel: channel,
|
|
221
|
+
});
|
|
222
|
+
if (!resolved) {
|
|
223
|
+
await command.reply({
|
|
224
|
+
content: 'Could not determine project directory for this channel.',
|
|
225
|
+
flags: SECURE_REPLY_FLAGS,
|
|
226
|
+
});
|
|
227
|
+
return;
|
|
228
|
+
}
|
|
229
|
+
await command.deferReply({ flags: SECURE_REPLY_FLAGS });
|
|
230
|
+
const sessionKey = channel.id;
|
|
231
|
+
const existing = getActiveVscodeSession({ sessionKey });
|
|
232
|
+
if (existing) {
|
|
233
|
+
await command.editReply({
|
|
234
|
+
content: `VS Code is already running for this thread. ` +
|
|
235
|
+
`This unique tunnel auto-stops after ${MAX_SESSION_MINUTES} minutes from startup.\n` +
|
|
236
|
+
`${existing.url}`,
|
|
237
|
+
});
|
|
238
|
+
return;
|
|
239
|
+
}
|
|
240
|
+
try {
|
|
241
|
+
const session = await startVscode({
|
|
242
|
+
sessionKey,
|
|
243
|
+
startedBy: command.user.tag,
|
|
244
|
+
workingDirectory: resolved.workingDirectory,
|
|
245
|
+
});
|
|
246
|
+
await command.editReply({
|
|
247
|
+
content: `VS Code started for \`${session.workingDirectory}\`. ` +
|
|
248
|
+
`This unique tunnel auto-stops after ${MAX_SESSION_MINUTES} minutes, so open it before it expires.\n` +
|
|
249
|
+
`${session.url}`,
|
|
250
|
+
});
|
|
251
|
+
}
|
|
252
|
+
catch (error) {
|
|
253
|
+
logger.error('Failed to start VS Code:', error);
|
|
254
|
+
await command.editReply({
|
|
255
|
+
content: `Failed to start VS Code: ${error instanceof Error ? error.message : String(error)}`,
|
|
256
|
+
});
|
|
257
|
+
}
|
|
258
|
+
}
|
|
259
|
+
export function cleanupAllVscodeSessions() {
|
|
260
|
+
for (const sessionKey of activeSessions.keys()) {
|
|
261
|
+
stopVscode({ sessionKey });
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
function onProcessExit() {
|
|
265
|
+
cleanupAllVscodeSessions();
|
|
266
|
+
}
|
|
267
|
+
process.on('SIGINT', onProcessExit);
|
|
268
|
+
process.on('SIGTERM', onProcessExit);
|
|
269
|
+
process.on('exit', onProcessExit);
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
// /toggle-worktrees command.
|
|
2
|
+
// Allows per-channel opt-in for automatic worktree creation,
|
|
3
|
+
// as an alternative to the global --use-worktrees CLI flag.
|
|
4
|
+
import { ChatInputCommandInteraction, MessageFlags, ChannelType, } from 'discord.js';
|
|
5
|
+
import { getChannelWorktreesEnabled, setChannelWorktreesEnabled, } from '../database.js';
|
|
6
|
+
import { getOttoMetadata } from '../discord-utils.js';
|
|
7
|
+
import { createLogger, LogPrefix } from '../logger.js';
|
|
8
|
+
const worktreeSettingsLogger = createLogger(LogPrefix.WORKTREE);
|
|
9
|
+
/**
|
|
10
|
+
* Handle the /toggle-worktrees slash command.
|
|
11
|
+
* Toggles automatic worktree creation for new sessions in this channel.
|
|
12
|
+
*/
|
|
13
|
+
export async function handleToggleWorktreesCommand({ command, }) {
|
|
14
|
+
worktreeSettingsLogger.log('[TOGGLE_WORKTREES] Command called');
|
|
15
|
+
const channel = command.channel;
|
|
16
|
+
if (!channel || channel.type !== ChannelType.GuildText) {
|
|
17
|
+
await command.reply({
|
|
18
|
+
content: 'This command can only be used in text channels (not threads).',
|
|
19
|
+
flags: MessageFlags.Ephemeral,
|
|
20
|
+
});
|
|
21
|
+
return;
|
|
22
|
+
}
|
|
23
|
+
const textChannel = channel;
|
|
24
|
+
const metadata = await getOttoMetadata(textChannel);
|
|
25
|
+
if (!metadata.projectDirectory) {
|
|
26
|
+
await command.reply({
|
|
27
|
+
content: 'This channel is not configured with a project directory.\nUse `/add-project` to set up this channel.',
|
|
28
|
+
flags: MessageFlags.Ephemeral,
|
|
29
|
+
});
|
|
30
|
+
return;
|
|
31
|
+
}
|
|
32
|
+
const wasEnabled = await getChannelWorktreesEnabled(textChannel.id);
|
|
33
|
+
const nextEnabled = !wasEnabled;
|
|
34
|
+
await setChannelWorktreesEnabled(textChannel.id, nextEnabled);
|
|
35
|
+
const nextLabel = nextEnabled ? 'enabled' : 'disabled';
|
|
36
|
+
worktreeSettingsLogger.log(`[TOGGLE_WORKTREES] ${nextLabel.toUpperCase()} for channel ${textChannel.id}`);
|
|
37
|
+
await command.reply({
|
|
38
|
+
content: nextEnabled
|
|
39
|
+
? `Worktrees **enabled** for this channel.\n\nNew sessions started from messages in **#${textChannel.name}** will now automatically create git worktrees.\n\nNew setting for **#${textChannel.name}**: **enabled**.`
|
|
40
|
+
: `Worktrees **disabled** for this channel.\n\nNew sessions started from messages in **#${textChannel.name}** will use the main project directory.\n\nNew setting for **#${textChannel.name}**: **disabled**.`,
|
|
41
|
+
flags: MessageFlags.Ephemeral,
|
|
42
|
+
});
|
|
43
|
+
}
|