@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,78 @@
|
|
|
1
|
+
// /session-id command - Show current session ID and an opencode attach command.
|
|
2
|
+
import { ChannelType, MessageFlags, } from 'discord.js';
|
|
3
|
+
import { getThreadSession } from '../database.js';
|
|
4
|
+
import { resolveWorkingDirectory, SILENT_MESSAGE_FLAGS, } from '../discord-utils.js';
|
|
5
|
+
import { getOpencodeServerPort, initializeOpencodeForDirectory, } from '../opencode.js';
|
|
6
|
+
import { createLogger, LogPrefix } from '../logger.js';
|
|
7
|
+
const logger = createLogger(LogPrefix.SESSION);
|
|
8
|
+
function shellQuote(value) {
|
|
9
|
+
if (!value) {
|
|
10
|
+
return "''";
|
|
11
|
+
}
|
|
12
|
+
return `'${value.replaceAll("'", `'"'"'`)}'`;
|
|
13
|
+
}
|
|
14
|
+
export async function handleSessionIdCommand({ command, }) {
|
|
15
|
+
const channel = command.channel;
|
|
16
|
+
if (!channel) {
|
|
17
|
+
await command.reply({
|
|
18
|
+
content: 'This command can only be used in a channel',
|
|
19
|
+
flags: MessageFlags.Ephemeral | SILENT_MESSAGE_FLAGS,
|
|
20
|
+
});
|
|
21
|
+
return;
|
|
22
|
+
}
|
|
23
|
+
const isThread = [
|
|
24
|
+
ChannelType.PublicThread,
|
|
25
|
+
ChannelType.PrivateThread,
|
|
26
|
+
ChannelType.AnnouncementThread,
|
|
27
|
+
].includes(channel.type);
|
|
28
|
+
if (!isThread) {
|
|
29
|
+
await command.reply({
|
|
30
|
+
content: 'This command can only be used in a thread with an active session',
|
|
31
|
+
flags: MessageFlags.Ephemeral | SILENT_MESSAGE_FLAGS,
|
|
32
|
+
});
|
|
33
|
+
return;
|
|
34
|
+
}
|
|
35
|
+
const resolved = await resolveWorkingDirectory({
|
|
36
|
+
channel: channel,
|
|
37
|
+
});
|
|
38
|
+
if (!resolved) {
|
|
39
|
+
await command.reply({
|
|
40
|
+
content: 'Could not determine project directory for this channel',
|
|
41
|
+
flags: MessageFlags.Ephemeral | SILENT_MESSAGE_FLAGS,
|
|
42
|
+
});
|
|
43
|
+
return;
|
|
44
|
+
}
|
|
45
|
+
const { projectDirectory, workingDirectory } = resolved;
|
|
46
|
+
const sessionId = await getThreadSession(channel.id);
|
|
47
|
+
if (!sessionId) {
|
|
48
|
+
await command.reply({
|
|
49
|
+
content: 'No active session in this thread',
|
|
50
|
+
flags: MessageFlags.Ephemeral | SILENT_MESSAGE_FLAGS,
|
|
51
|
+
});
|
|
52
|
+
return;
|
|
53
|
+
}
|
|
54
|
+
await command.deferReply({ flags: SILENT_MESSAGE_FLAGS });
|
|
55
|
+
let port = getOpencodeServerPort(projectDirectory);
|
|
56
|
+
if (!port) {
|
|
57
|
+
const getClient = await initializeOpencodeForDirectory(projectDirectory);
|
|
58
|
+
if (getClient instanceof Error) {
|
|
59
|
+
await command.editReply({
|
|
60
|
+
content: `Session ID: \`${sessionId}\`\nFailed to resolve OpenCode server port: ${getClient.message}`,
|
|
61
|
+
});
|
|
62
|
+
return;
|
|
63
|
+
}
|
|
64
|
+
port = getOpencodeServerPort(projectDirectory);
|
|
65
|
+
}
|
|
66
|
+
if (!port) {
|
|
67
|
+
await command.editReply({
|
|
68
|
+
content: `Session ID: \`${sessionId}\`\nCould not determine OpenCode server port`,
|
|
69
|
+
});
|
|
70
|
+
return;
|
|
71
|
+
}
|
|
72
|
+
const attachUrl = `http://127.0.0.1:${port}`;
|
|
73
|
+
const attachCommand = `opencode attach ${attachUrl} --session ${sessionId} --dir ${shellQuote(workingDirectory)}`;
|
|
74
|
+
await command.editReply({
|
|
75
|
+
content: `**Session ID:** \`${sessionId}\`\n**Attach command:**\n\`\`\`bash\n${attachCommand}\n\`\`\``,
|
|
76
|
+
});
|
|
77
|
+
logger.log(`Session ID shown for thread ${channel.id}: ${sessionId}`);
|
|
78
|
+
}
|
|
@@ -0,0 +1,176 @@
|
|
|
1
|
+
// /new-session command - Start a new OpenCode session.
|
|
2
|
+
import { ChannelType } from 'discord.js';
|
|
3
|
+
import fs from 'node:fs';
|
|
4
|
+
import path from 'node:path';
|
|
5
|
+
import { getChannelDirectory } from '../database.js';
|
|
6
|
+
import { initializeOpencodeForDirectory } from '../opencode.js';
|
|
7
|
+
import { SILENT_MESSAGE_FLAGS, resolveProjectDirectoryFromAutocomplete } from '../discord-utils.js';
|
|
8
|
+
import { getOrCreateRuntime } from '../session-handler/thread-session-runtime.js';
|
|
9
|
+
import { createLogger, LogPrefix } from '../logger.js';
|
|
10
|
+
import * as errore from 'errore';
|
|
11
|
+
const logger = createLogger(LogPrefix.SESSION);
|
|
12
|
+
export async function handleSessionCommand({ command, appId, }) {
|
|
13
|
+
await command.deferReply();
|
|
14
|
+
const prompt = command.options.getString('prompt', true);
|
|
15
|
+
const filesString = command.options.getString('files') || '';
|
|
16
|
+
const agent = command.options.getString('agent') || undefined;
|
|
17
|
+
const channel = command.channel;
|
|
18
|
+
if (!channel || channel.type !== ChannelType.GuildText) {
|
|
19
|
+
await command.editReply('This command can only be used in text channels');
|
|
20
|
+
return;
|
|
21
|
+
}
|
|
22
|
+
const textChannel = channel;
|
|
23
|
+
const channelConfig = await getChannelDirectory(textChannel.id);
|
|
24
|
+
const projectDirectory = channelConfig?.directory;
|
|
25
|
+
if (!projectDirectory) {
|
|
26
|
+
await command.editReply('This channel is not configured with a project directory');
|
|
27
|
+
return;
|
|
28
|
+
}
|
|
29
|
+
if (!fs.existsSync(projectDirectory)) {
|
|
30
|
+
await command.editReply(`Directory does not exist: ${projectDirectory}`);
|
|
31
|
+
return;
|
|
32
|
+
}
|
|
33
|
+
try {
|
|
34
|
+
const getClient = await initializeOpencodeForDirectory(projectDirectory);
|
|
35
|
+
if (getClient instanceof Error) {
|
|
36
|
+
await command.editReply(getClient.message);
|
|
37
|
+
return;
|
|
38
|
+
}
|
|
39
|
+
const files = filesString
|
|
40
|
+
.split(',')
|
|
41
|
+
.map((f) => f.trim())
|
|
42
|
+
.filter((f) => f);
|
|
43
|
+
let fullPrompt = prompt;
|
|
44
|
+
if (files.length > 0) {
|
|
45
|
+
fullPrompt = `${prompt}\n\n@${files.join(' @')}`;
|
|
46
|
+
}
|
|
47
|
+
const starterMessage = await textChannel.send({
|
|
48
|
+
content: `🚀 **Starting OpenCode session**\n📝 ${prompt}${files.length > 0 ? `\n📎 Files: ${files.join(', ')}` : ''}`,
|
|
49
|
+
flags: SILENT_MESSAGE_FLAGS,
|
|
50
|
+
});
|
|
51
|
+
const thread = await starterMessage.startThread({
|
|
52
|
+
name: prompt.slice(0, 100),
|
|
53
|
+
autoArchiveDuration: 1440,
|
|
54
|
+
reason: 'OpenCode session',
|
|
55
|
+
});
|
|
56
|
+
// Add user to thread so it appears in their sidebar
|
|
57
|
+
await thread.members.add(command.user.id);
|
|
58
|
+
await command.editReply(`Created new session in ${thread.toString()}`);
|
|
59
|
+
const runtime = getOrCreateRuntime({
|
|
60
|
+
threadId: thread.id,
|
|
61
|
+
thread,
|
|
62
|
+
projectDirectory,
|
|
63
|
+
sdkDirectory: projectDirectory,
|
|
64
|
+
channelId: textChannel.id,
|
|
65
|
+
appId,
|
|
66
|
+
});
|
|
67
|
+
await runtime.enqueueIncoming({
|
|
68
|
+
prompt: fullPrompt,
|
|
69
|
+
userId: command.user.id,
|
|
70
|
+
username: command.user.displayName,
|
|
71
|
+
agent,
|
|
72
|
+
appId,
|
|
73
|
+
mode: 'opencode',
|
|
74
|
+
});
|
|
75
|
+
}
|
|
76
|
+
catch (error) {
|
|
77
|
+
logger.error('[SESSION] Error:', error);
|
|
78
|
+
await command.editReply(`Failed to create session: ${error instanceof Error ? error.message : 'Unknown error'}`);
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
async function handleAgentAutocomplete({ interaction, }) {
|
|
82
|
+
const focusedValue = interaction.options.getFocused();
|
|
83
|
+
// interaction.channel can be null when the channel isn't cached
|
|
84
|
+
// (common with gateway-proxy). Use channelId which is always available
|
|
85
|
+
// from the raw interaction payload.
|
|
86
|
+
const projectDirectory = await resolveProjectDirectoryFromAutocomplete(interaction);
|
|
87
|
+
if (!projectDirectory) {
|
|
88
|
+
await interaction.respond([]);
|
|
89
|
+
return;
|
|
90
|
+
}
|
|
91
|
+
try {
|
|
92
|
+
const getClient = await initializeOpencodeForDirectory(projectDirectory);
|
|
93
|
+
if (getClient instanceof Error) {
|
|
94
|
+
await interaction.respond([]);
|
|
95
|
+
return;
|
|
96
|
+
}
|
|
97
|
+
const agentsResponse = await getClient().app.agents({
|
|
98
|
+
directory: projectDirectory,
|
|
99
|
+
});
|
|
100
|
+
if (!agentsResponse.data || agentsResponse.data.length === 0) {
|
|
101
|
+
await interaction.respond([]);
|
|
102
|
+
return;
|
|
103
|
+
}
|
|
104
|
+
const agents = agentsResponse.data
|
|
105
|
+
.filter((a) => {
|
|
106
|
+
const hidden = a.hidden;
|
|
107
|
+
return (a.mode === 'primary' || a.mode === 'all') && !hidden;
|
|
108
|
+
})
|
|
109
|
+
.filter((a) => a.name.toLowerCase().includes(focusedValue.toLowerCase()))
|
|
110
|
+
.slice(0, 25);
|
|
111
|
+
const choices = agents.map((agent) => ({
|
|
112
|
+
name: agent.name.slice(0, 100),
|
|
113
|
+
value: agent.name,
|
|
114
|
+
}));
|
|
115
|
+
await interaction.respond(choices);
|
|
116
|
+
}
|
|
117
|
+
catch (error) {
|
|
118
|
+
logger.error('[AUTOCOMPLETE] Error fetching agents:', error);
|
|
119
|
+
await interaction.respond([]);
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
export async function handleSessionAutocomplete({ interaction, }) {
|
|
123
|
+
const focusedOption = interaction.options.getFocused(true);
|
|
124
|
+
if (focusedOption.name === 'agent') {
|
|
125
|
+
await handleAgentAutocomplete({ interaction });
|
|
126
|
+
return;
|
|
127
|
+
}
|
|
128
|
+
if (focusedOption.name !== 'files') {
|
|
129
|
+
return;
|
|
130
|
+
}
|
|
131
|
+
const focusedValue = focusedOption.value;
|
|
132
|
+
const parts = focusedValue.split(',');
|
|
133
|
+
const previousFiles = parts
|
|
134
|
+
.slice(0, -1)
|
|
135
|
+
.map((f) => f.trim())
|
|
136
|
+
.filter((f) => f);
|
|
137
|
+
const currentQuery = (parts[parts.length - 1] || '').trim();
|
|
138
|
+
const projectDirectory = await resolveProjectDirectoryFromAutocomplete(interaction);
|
|
139
|
+
if (!projectDirectory) {
|
|
140
|
+
await interaction.respond([]);
|
|
141
|
+
return;
|
|
142
|
+
}
|
|
143
|
+
try {
|
|
144
|
+
const getClient = await initializeOpencodeForDirectory(projectDirectory);
|
|
145
|
+
if (getClient instanceof Error) {
|
|
146
|
+
await interaction.respond([]);
|
|
147
|
+
return;
|
|
148
|
+
}
|
|
149
|
+
const response = await getClient().find.files({
|
|
150
|
+
query: currentQuery || '',
|
|
151
|
+
});
|
|
152
|
+
const files = response.data || [];
|
|
153
|
+
const prefix = previousFiles.length > 0 ? previousFiles.join(', ') + ', ' : '';
|
|
154
|
+
const choices = files
|
|
155
|
+
.map((file) => {
|
|
156
|
+
const fullValue = prefix + file;
|
|
157
|
+
const allFiles = [...previousFiles, file];
|
|
158
|
+
const allBasenames = allFiles.map((f) => f.split('/').pop() || f);
|
|
159
|
+
let displayName = allBasenames.join(', ');
|
|
160
|
+
if (displayName.length > 100) {
|
|
161
|
+
displayName = '…' + displayName.slice(-97);
|
|
162
|
+
}
|
|
163
|
+
return {
|
|
164
|
+
name: displayName,
|
|
165
|
+
value: fullValue,
|
|
166
|
+
};
|
|
167
|
+
})
|
|
168
|
+
.filter((choice) => choice.value.length <= 100)
|
|
169
|
+
.slice(0, 25);
|
|
170
|
+
await interaction.respond(choices);
|
|
171
|
+
}
|
|
172
|
+
catch (error) {
|
|
173
|
+
logger.error('[AUTOCOMPLETE] Error fetching files:', error);
|
|
174
|
+
await interaction.respond([]);
|
|
175
|
+
}
|
|
176
|
+
}
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
// /share command - Share the current session as a public URL.
|
|
2
|
+
import { ChannelType, MessageFlags, } from 'discord.js';
|
|
3
|
+
import { getThreadSession } from '../database.js';
|
|
4
|
+
import { initializeOpencodeForDirectory } from '../opencode.js';
|
|
5
|
+
import { resolveWorkingDirectory, SILENT_MESSAGE_FLAGS, } from '../discord-utils.js';
|
|
6
|
+
import { createLogger, LogPrefix } from '../logger.js';
|
|
7
|
+
const logger = createLogger(LogPrefix.SHARE);
|
|
8
|
+
export async function handleShareCommand({ command, }) {
|
|
9
|
+
const channel = command.channel;
|
|
10
|
+
if (!channel) {
|
|
11
|
+
await command.reply({
|
|
12
|
+
content: 'This command can only be used in a channel',
|
|
13
|
+
flags: MessageFlags.Ephemeral | SILENT_MESSAGE_FLAGS,
|
|
14
|
+
});
|
|
15
|
+
return;
|
|
16
|
+
}
|
|
17
|
+
const isThread = [
|
|
18
|
+
ChannelType.PublicThread,
|
|
19
|
+
ChannelType.PrivateThread,
|
|
20
|
+
ChannelType.AnnouncementThread,
|
|
21
|
+
].includes(channel.type);
|
|
22
|
+
if (!isThread) {
|
|
23
|
+
await command.reply({
|
|
24
|
+
content: 'This command can only be used in a thread with an active session',
|
|
25
|
+
flags: MessageFlags.Ephemeral | SILENT_MESSAGE_FLAGS,
|
|
26
|
+
});
|
|
27
|
+
return;
|
|
28
|
+
}
|
|
29
|
+
const resolved = await resolveWorkingDirectory({
|
|
30
|
+
channel: channel,
|
|
31
|
+
});
|
|
32
|
+
if (!resolved) {
|
|
33
|
+
await command.reply({
|
|
34
|
+
content: 'Could not determine project directory for this channel',
|
|
35
|
+
flags: MessageFlags.Ephemeral | SILENT_MESSAGE_FLAGS,
|
|
36
|
+
});
|
|
37
|
+
return;
|
|
38
|
+
}
|
|
39
|
+
const { projectDirectory } = resolved;
|
|
40
|
+
const sessionId = await getThreadSession(channel.id);
|
|
41
|
+
if (!sessionId) {
|
|
42
|
+
await command.reply({
|
|
43
|
+
content: 'No active session in this thread',
|
|
44
|
+
flags: MessageFlags.Ephemeral | SILENT_MESSAGE_FLAGS,
|
|
45
|
+
});
|
|
46
|
+
return;
|
|
47
|
+
}
|
|
48
|
+
const getClient = await initializeOpencodeForDirectory(projectDirectory);
|
|
49
|
+
if (getClient instanceof Error) {
|
|
50
|
+
await command.reply({
|
|
51
|
+
content: `Failed to share session: ${getClient.message}`,
|
|
52
|
+
flags: MessageFlags.Ephemeral | SILENT_MESSAGE_FLAGS,
|
|
53
|
+
});
|
|
54
|
+
return;
|
|
55
|
+
}
|
|
56
|
+
try {
|
|
57
|
+
const response = await getClient().session.share({
|
|
58
|
+
sessionID: sessionId,
|
|
59
|
+
});
|
|
60
|
+
if (!response.data?.share?.url) {
|
|
61
|
+
await command.reply({
|
|
62
|
+
content: 'Failed to generate share URL',
|
|
63
|
+
flags: MessageFlags.Ephemeral | SILENT_MESSAGE_FLAGS,
|
|
64
|
+
});
|
|
65
|
+
return;
|
|
66
|
+
}
|
|
67
|
+
await command.reply({
|
|
68
|
+
content: `🔗 **Session shared:** ${response.data.share.url}`,
|
|
69
|
+
flags: SILENT_MESSAGE_FLAGS,
|
|
70
|
+
});
|
|
71
|
+
logger.log(`Session ${sessionId} shared: ${response.data.share.url}`);
|
|
72
|
+
}
|
|
73
|
+
catch (error) {
|
|
74
|
+
logger.error('[SHARE] Error:', error);
|
|
75
|
+
await command.reply({
|
|
76
|
+
content: `Failed to share session: ${error instanceof Error ? error.message : 'Unknown error'}`,
|
|
77
|
+
flags: MessageFlags.Ephemeral | SILENT_MESSAGE_FLAGS,
|
|
78
|
+
});
|
|
79
|
+
}
|
|
80
|
+
}
|
|
@@ -0,0 +1,205 @@
|
|
|
1
|
+
// /tasks command — list all scheduled tasks sorted by next run time.
|
|
2
|
+
// Renders a markdown table that the CV2 pipeline auto-formats for Discord,
|
|
3
|
+
// including HTML-backed action buttons for cancellable tasks.
|
|
4
|
+
import { ButtonInteraction, ChatInputCommandInteraction, ComponentType, MessageFlags, } from 'discord.js';
|
|
5
|
+
import { cancelScheduledTask, listScheduledTasks, } from '../database.js';
|
|
6
|
+
import { splitTablesFromMarkdown } from '../format-tables.js';
|
|
7
|
+
import { buildHtmlActionCustomId, cancelHtmlActionsForOwner, registerHtmlAction, } from '../html-actions.js';
|
|
8
|
+
import { formatTimeAgo } from './worktrees.js';
|
|
9
|
+
function formatTimeUntil(date) {
|
|
10
|
+
const diffMs = date.getTime() - Date.now();
|
|
11
|
+
if (diffMs <= 0) {
|
|
12
|
+
return 'due now';
|
|
13
|
+
}
|
|
14
|
+
const totalSeconds = Math.floor(diffMs / 1000);
|
|
15
|
+
if (totalSeconds < 60) {
|
|
16
|
+
return `in ${totalSeconds}s`;
|
|
17
|
+
}
|
|
18
|
+
const totalMinutes = Math.floor(totalSeconds / 60);
|
|
19
|
+
if (totalMinutes < 60) {
|
|
20
|
+
return `in ${totalMinutes}m`;
|
|
21
|
+
}
|
|
22
|
+
const hours = Math.floor(totalMinutes / 60);
|
|
23
|
+
const minutes = totalMinutes % 60;
|
|
24
|
+
if (hours < 24) {
|
|
25
|
+
return minutes > 0 ? `in ${hours}h ${minutes}m` : `in ${hours}h`;
|
|
26
|
+
}
|
|
27
|
+
const days = Math.floor(hours / 24);
|
|
28
|
+
const remainingHours = hours % 24;
|
|
29
|
+
return remainingHours > 0 ? `in ${days}d ${remainingHours}h` : `in ${days}d`;
|
|
30
|
+
}
|
|
31
|
+
function scheduleLabel(task) {
|
|
32
|
+
if (task.schedule_kind === 'cron') {
|
|
33
|
+
return task.cron_expr || 'cron';
|
|
34
|
+
}
|
|
35
|
+
return 'one-time';
|
|
36
|
+
}
|
|
37
|
+
function canCancelTask(task) {
|
|
38
|
+
return task.status === 'planned' || task.status === 'running';
|
|
39
|
+
}
|
|
40
|
+
// Escape pipe chars and collapse whitespace so free-text fields don't break
|
|
41
|
+
// GFM table column alignment.
|
|
42
|
+
function sanitizeTableCell(value) {
|
|
43
|
+
return value.replaceAll('|', '\\|').replace(/\s+/g, ' ').trim();
|
|
44
|
+
}
|
|
45
|
+
function buildCancelButtonHtml({ buttonId }) {
|
|
46
|
+
return `<button id="${buttonId}" variant="secondary">Delete</button>`;
|
|
47
|
+
}
|
|
48
|
+
function buildActionCell(task) {
|
|
49
|
+
if (!canCancelTask(task)) {
|
|
50
|
+
return '-';
|
|
51
|
+
}
|
|
52
|
+
return buildCancelButtonHtml({ buttonId: `cancel-task-${task.id}` });
|
|
53
|
+
}
|
|
54
|
+
// Cap rows to avoid exceeding Discord's 40-component CV2 limit.
|
|
55
|
+
// Each cancellable row renders as text + action row + button (~4 components),
|
|
56
|
+
// so 10 rows is a safe ceiling.
|
|
57
|
+
const MAX_TASK_ROWS = 10;
|
|
58
|
+
function buildTaskTable({ tasks, }) {
|
|
59
|
+
const header = '| ID | Status | Prompt | Schedule | Next Run | Action |';
|
|
60
|
+
const separator = '|---|---|---|---|---|---|';
|
|
61
|
+
const rows = tasks.map((task) => {
|
|
62
|
+
const id = String(task.id);
|
|
63
|
+
const status = task.status;
|
|
64
|
+
const prompt = sanitizeTableCell(task.prompt_preview.length > 240
|
|
65
|
+
? task.prompt_preview.slice(0, 237) + '...'
|
|
66
|
+
: task.prompt_preview);
|
|
67
|
+
const schedule = sanitizeTableCell(scheduleLabel(task));
|
|
68
|
+
const nextRun = (() => {
|
|
69
|
+
if (task.status === 'completed' ||
|
|
70
|
+
task.status === 'cancelled' ||
|
|
71
|
+
task.status === 'failed') {
|
|
72
|
+
return task.last_run_at ? formatTimeAgo(task.last_run_at) : '-';
|
|
73
|
+
}
|
|
74
|
+
return formatTimeUntil(task.next_run_at);
|
|
75
|
+
})();
|
|
76
|
+
const action = buildActionCell(task);
|
|
77
|
+
return `| ${id} | ${status} | ${prompt} | ${schedule} | ${nextRun} | ${action} |`;
|
|
78
|
+
});
|
|
79
|
+
return [header, separator, ...rows].join('\n');
|
|
80
|
+
}
|
|
81
|
+
function getTasksActionOwnerKey({ userId, channelId, }) {
|
|
82
|
+
return `tasks:${userId}:${channelId}`;
|
|
83
|
+
}
|
|
84
|
+
async function renderTasksReply({ guildId, userId, channelId, showAll, notice, editReply, }) {
|
|
85
|
+
const ownerKey = getTasksActionOwnerKey({ userId, channelId });
|
|
86
|
+
cancelHtmlActionsForOwner(ownerKey);
|
|
87
|
+
const statuses = showAll
|
|
88
|
+
? undefined
|
|
89
|
+
: ['planned', 'running'];
|
|
90
|
+
const allTasks = await listScheduledTasks({ statuses });
|
|
91
|
+
if (allTasks.length === 0) {
|
|
92
|
+
const message = notice
|
|
93
|
+
? `${notice}\n\nNo scheduled tasks found.`
|
|
94
|
+
: 'No scheduled tasks found.';
|
|
95
|
+
const textDisplay = {
|
|
96
|
+
type: ComponentType.TextDisplay,
|
|
97
|
+
content: message,
|
|
98
|
+
};
|
|
99
|
+
await editReply({
|
|
100
|
+
components: [textDisplay],
|
|
101
|
+
flags: MessageFlags.IsComponentsV2,
|
|
102
|
+
});
|
|
103
|
+
return;
|
|
104
|
+
}
|
|
105
|
+
const tasks = allTasks.slice(0, MAX_TASK_ROWS);
|
|
106
|
+
const truncatedNotice = allTasks.length > MAX_TASK_ROWS
|
|
107
|
+
? `Showing ${MAX_TASK_ROWS}/${allTasks.length} tasks. Use \`otto task list\` for full list.`
|
|
108
|
+
: undefined;
|
|
109
|
+
const combinedNotice = [notice, truncatedNotice].filter(Boolean).join('\n');
|
|
110
|
+
const cancellableTasksByButtonId = new Map();
|
|
111
|
+
tasks.forEach((task) => {
|
|
112
|
+
if (!canCancelTask(task)) {
|
|
113
|
+
return;
|
|
114
|
+
}
|
|
115
|
+
cancellableTasksByButtonId.set(`cancel-task-${task.id}`, task);
|
|
116
|
+
});
|
|
117
|
+
const tableMarkdown = buildTaskTable({ tasks });
|
|
118
|
+
const markdown = combinedNotice
|
|
119
|
+
? `${combinedNotice}\n\n${tableMarkdown}`
|
|
120
|
+
: tableMarkdown;
|
|
121
|
+
const segments = splitTablesFromMarkdown(markdown, {
|
|
122
|
+
resolveButtonCustomId: ({ button }) => {
|
|
123
|
+
const task = cancellableTasksByButtonId.get(button.id);
|
|
124
|
+
if (!task) {
|
|
125
|
+
return new Error(`No task registered for button ${button.id}`);
|
|
126
|
+
}
|
|
127
|
+
const actionId = registerHtmlAction({
|
|
128
|
+
ownerKey,
|
|
129
|
+
threadId: String(task.id),
|
|
130
|
+
run: async ({ interaction }) => {
|
|
131
|
+
await handleCancelTaskAction({
|
|
132
|
+
interaction,
|
|
133
|
+
taskId: task.id,
|
|
134
|
+
showAll,
|
|
135
|
+
});
|
|
136
|
+
},
|
|
137
|
+
});
|
|
138
|
+
return buildHtmlActionCustomId(actionId);
|
|
139
|
+
},
|
|
140
|
+
});
|
|
141
|
+
const components = segments.flatMap((segment) => {
|
|
142
|
+
if (segment.type === 'components') {
|
|
143
|
+
return segment.components;
|
|
144
|
+
}
|
|
145
|
+
const textDisplay = {
|
|
146
|
+
type: ComponentType.TextDisplay,
|
|
147
|
+
content: segment.text,
|
|
148
|
+
};
|
|
149
|
+
return [textDisplay];
|
|
150
|
+
});
|
|
151
|
+
await editReply({
|
|
152
|
+
components,
|
|
153
|
+
flags: MessageFlags.IsComponentsV2,
|
|
154
|
+
});
|
|
155
|
+
}
|
|
156
|
+
async function handleCancelTaskAction({ interaction, taskId, showAll, }) {
|
|
157
|
+
const guildId = interaction.guildId;
|
|
158
|
+
if (!guildId) {
|
|
159
|
+
await interaction.editReply({
|
|
160
|
+
components: [
|
|
161
|
+
{
|
|
162
|
+
type: ComponentType.TextDisplay,
|
|
163
|
+
content: 'This action can only be used in a server.',
|
|
164
|
+
},
|
|
165
|
+
],
|
|
166
|
+
flags: MessageFlags.IsComponentsV2,
|
|
167
|
+
});
|
|
168
|
+
return;
|
|
169
|
+
}
|
|
170
|
+
const cancelled = await cancelScheduledTask(taskId);
|
|
171
|
+
const notice = cancelled
|
|
172
|
+
? `Cancelled task #${taskId}.`
|
|
173
|
+
: `Task #${taskId} not found or already finalized.`;
|
|
174
|
+
await renderTasksReply({
|
|
175
|
+
guildId,
|
|
176
|
+
userId: interaction.user.id,
|
|
177
|
+
channelId: interaction.channelId,
|
|
178
|
+
showAll,
|
|
179
|
+
notice,
|
|
180
|
+
editReply: (options) => {
|
|
181
|
+
return interaction.editReply(options);
|
|
182
|
+
},
|
|
183
|
+
});
|
|
184
|
+
}
|
|
185
|
+
export async function handleTasksCommand({ command, }) {
|
|
186
|
+
const guildId = command.guildId;
|
|
187
|
+
if (!guildId) {
|
|
188
|
+
await command.reply({
|
|
189
|
+
content: 'This command can only be used in a server.',
|
|
190
|
+
flags: MessageFlags.Ephemeral,
|
|
191
|
+
});
|
|
192
|
+
return;
|
|
193
|
+
}
|
|
194
|
+
const showAll = command.options.getBoolean('all') ?? false;
|
|
195
|
+
await command.deferReply({ flags: MessageFlags.Ephemeral });
|
|
196
|
+
await renderTasksReply({
|
|
197
|
+
guildId,
|
|
198
|
+
userId: command.user.id,
|
|
199
|
+
channelId: command.channelId,
|
|
200
|
+
showAll,
|
|
201
|
+
editReply: (options) => {
|
|
202
|
+
return command.editReply(options);
|
|
203
|
+
},
|
|
204
|
+
});
|
|
205
|
+
}
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import { ChatInputCommandInteraction, MessageFlags, } from 'discord.js';
|
|
2
|
+
import { getThreadDeletionSyncMode, resetThreadDeletionSyncMode, setThreadDeletionSyncMode, } from '../database.js';
|
|
3
|
+
import { createLogger, LogPrefix } from '../logger.js';
|
|
4
|
+
const threadDeletionSyncLogger = createLogger(LogPrefix.CLI);
|
|
5
|
+
export const THREAD_DELETION_SYNC_CHOICES = [
|
|
6
|
+
{ name: 'soft', value: 'soft' },
|
|
7
|
+
{ name: 'hard', value: 'hard' },
|
|
8
|
+
{ name: 'reset', value: 'reset' },
|
|
9
|
+
];
|
|
10
|
+
function getModeLabel(mode) {
|
|
11
|
+
if (mode === 'hard') {
|
|
12
|
+
return 'hard';
|
|
13
|
+
}
|
|
14
|
+
return 'soft';
|
|
15
|
+
}
|
|
16
|
+
export async function handleThreadDeletionSyncCommand({ command, appId, }) {
|
|
17
|
+
const selectedMode = command.options.getString('mode');
|
|
18
|
+
if (!selectedMode) {
|
|
19
|
+
const currentMode = await getThreadDeletionSyncMode({ appId });
|
|
20
|
+
await command.reply({
|
|
21
|
+
content: `Thread deletion sync mode is **${getModeLabel(currentMode)}**.`,
|
|
22
|
+
flags: MessageFlags.Ephemeral,
|
|
23
|
+
});
|
|
24
|
+
return;
|
|
25
|
+
}
|
|
26
|
+
if (selectedMode === 'reset') {
|
|
27
|
+
await resetThreadDeletionSyncMode({ appId });
|
|
28
|
+
threadDeletionSyncLogger.log(`[THREAD_DELETION_SYNC] Reset mode to default for app ${appId}`);
|
|
29
|
+
await command.reply({
|
|
30
|
+
content: 'Thread deletion sync mode reset to **soft** (default).\nSoft keeps the OpenCode session and only archives/aborts it.',
|
|
31
|
+
flags: MessageFlags.Ephemeral,
|
|
32
|
+
});
|
|
33
|
+
return;
|
|
34
|
+
}
|
|
35
|
+
if (selectedMode !== 'soft' && selectedMode !== 'hard') {
|
|
36
|
+
await command.reply({
|
|
37
|
+
content: `Invalid mode: ${selectedMode}`,
|
|
38
|
+
flags: MessageFlags.Ephemeral,
|
|
39
|
+
});
|
|
40
|
+
return;
|
|
41
|
+
}
|
|
42
|
+
await setThreadDeletionSyncMode({ appId, mode: selectedMode });
|
|
43
|
+
threadDeletionSyncLogger.log(`[THREAD_DELETION_SYNC] Set mode ${selectedMode} for app ${appId}`);
|
|
44
|
+
await command.reply({
|
|
45
|
+
content: selectedMode === 'hard'
|
|
46
|
+
? 'Thread deletion sync mode set to **hard**.\nDeleting a Discord thread will delete the mapped OpenCode session.'
|
|
47
|
+
: 'Thread deletion sync mode set to **soft**.\nDeleting a Discord thread will archive/abort the mapped OpenCode session.',
|
|
48
|
+
flags: MessageFlags.Ephemeral,
|
|
49
|
+
});
|
|
50
|
+
}
|