@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,386 @@
|
|
|
1
|
+
// /fork command - Fork the session from a past user message.
|
|
2
|
+
|
|
3
|
+
import {
|
|
4
|
+
ChatInputCommandInteraction,
|
|
5
|
+
StringSelectMenuInteraction,
|
|
6
|
+
StringSelectMenuBuilder,
|
|
7
|
+
ActionRowBuilder,
|
|
8
|
+
ChannelType,
|
|
9
|
+
ThreadAutoArchiveDuration,
|
|
10
|
+
type ThreadChannel,
|
|
11
|
+
MessageFlags,
|
|
12
|
+
} from 'discord.js'
|
|
13
|
+
import {
|
|
14
|
+
getThreadSession,
|
|
15
|
+
setThreadSession,
|
|
16
|
+
setPartMessagesBatch,
|
|
17
|
+
} from '../database.js'
|
|
18
|
+
import { initializeOpencodeForDirectory } from '../opencode.js'
|
|
19
|
+
import {
|
|
20
|
+
resolveWorkingDirectory,
|
|
21
|
+
resolveTextChannel,
|
|
22
|
+
sendThreadMessage,
|
|
23
|
+
} from '../discord-utils.js'
|
|
24
|
+
import {
|
|
25
|
+
collectSessionChunks,
|
|
26
|
+
batchChunksForDiscord,
|
|
27
|
+
} from '../message-formatting.js'
|
|
28
|
+
import { createLogger, LogPrefix } from '../logger.js'
|
|
29
|
+
import * as errore from 'errore'
|
|
30
|
+
|
|
31
|
+
const sessionLogger = createLogger(LogPrefix.SESSION)
|
|
32
|
+
const forkLogger = createLogger(LogPrefix.FORK)
|
|
33
|
+
|
|
34
|
+
function isTruthy<T>(value: T): value is NonNullable<T> {
|
|
35
|
+
return Boolean(value)
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
function getThreadChannelFromCommand(
|
|
39
|
+
interaction: ChatInputCommandInteraction,
|
|
40
|
+
): ThreadChannel | Error {
|
|
41
|
+
return getThreadChannel(interaction.channel)
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
function getThreadChannel(
|
|
45
|
+
channel: ChatInputCommandInteraction['channel'] | StringSelectMenuInteraction['channel'],
|
|
46
|
+
): ThreadChannel | Error {
|
|
47
|
+
if (!channel) {
|
|
48
|
+
return new Error('This command can only be used in a channel')
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
if (
|
|
52
|
+
channel.type !== ChannelType.PublicThread
|
|
53
|
+
&& channel.type !== ChannelType.PrivateThread
|
|
54
|
+
&& channel.type !== ChannelType.AnnouncementThread
|
|
55
|
+
) {
|
|
56
|
+
return new Error('This command can only be used in a thread with an active session')
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
return channel
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
function parsePersistedEventRows({
|
|
63
|
+
rows,
|
|
64
|
+
}: {
|
|
65
|
+
rows: Array<{ event_json: string; timestamp: bigint; event_index: number; id: number }>
|
|
66
|
+
}) {
|
|
67
|
+
return rows.flatMap((row) => {
|
|
68
|
+
const parsed = errore.try({
|
|
69
|
+
try: () => {
|
|
70
|
+
return JSON.parse(row.event_json)
|
|
71
|
+
},
|
|
72
|
+
catch: (error) => {
|
|
73
|
+
return new Error('Failed to parse persisted event JSON', {
|
|
74
|
+
cause: error,
|
|
75
|
+
})
|
|
76
|
+
},
|
|
77
|
+
})
|
|
78
|
+
if (parsed instanceof Error) {
|
|
79
|
+
forkLogger.warn(
|
|
80
|
+
`[fork] Skipping invalid persisted event row ${row.id}: ${parsed.message}`,
|
|
81
|
+
)
|
|
82
|
+
return []
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
return [{
|
|
86
|
+
event: parsed,
|
|
87
|
+
timestamp: Number(row.timestamp),
|
|
88
|
+
eventIndex: Number(row.event_index),
|
|
89
|
+
}]
|
|
90
|
+
})
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
function truncateLabelPart(text: string, maxLength: number): string {
|
|
94
|
+
if (text.length <= maxLength) {
|
|
95
|
+
return text
|
|
96
|
+
}
|
|
97
|
+
if (maxLength <= 1) {
|
|
98
|
+
return text.slice(0, maxLength)
|
|
99
|
+
}
|
|
100
|
+
return `${text.slice(0, maxLength - 1)}…`
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
function getSubagentOptionLabel({
|
|
104
|
+
subagentType,
|
|
105
|
+
description,
|
|
106
|
+
}: {
|
|
107
|
+
subagentType?: string
|
|
108
|
+
description?: string
|
|
109
|
+
}): string {
|
|
110
|
+
const agent = truncateLabelPart(subagentType || 'task', 24)
|
|
111
|
+
const cleanedDescription = description?.trim() || 'No description'
|
|
112
|
+
const descriptionBudget = Math.max(1, 100 - agent.length - 3)
|
|
113
|
+
const truncatedDescription = truncateLabelPart(
|
|
114
|
+
cleanedDescription,
|
|
115
|
+
descriptionBudget,
|
|
116
|
+
)
|
|
117
|
+
return `${agent} · ${truncatedDescription}`
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
export async function handleForkCommand(
|
|
121
|
+
interaction: ChatInputCommandInteraction,
|
|
122
|
+
): Promise<void> {
|
|
123
|
+
const threadChannel = getThreadChannelFromCommand(interaction)
|
|
124
|
+
if (threadChannel instanceof Error) {
|
|
125
|
+
await interaction.reply({
|
|
126
|
+
content: threadChannel.message,
|
|
127
|
+
flags: MessageFlags.Ephemeral,
|
|
128
|
+
})
|
|
129
|
+
return
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
const resolved = await resolveWorkingDirectory({
|
|
133
|
+
channel: threadChannel,
|
|
134
|
+
})
|
|
135
|
+
|
|
136
|
+
if (!resolved) {
|
|
137
|
+
await interaction.reply({
|
|
138
|
+
content: 'Could not determine project directory for this channel',
|
|
139
|
+
flags: MessageFlags.Ephemeral,
|
|
140
|
+
})
|
|
141
|
+
return
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
const { projectDirectory } = resolved
|
|
145
|
+
|
|
146
|
+
const sessionId = await getThreadSession(threadChannel.id)
|
|
147
|
+
|
|
148
|
+
if (!sessionId) {
|
|
149
|
+
await interaction.reply({
|
|
150
|
+
content: 'No active session in this thread',
|
|
151
|
+
flags: MessageFlags.Ephemeral,
|
|
152
|
+
})
|
|
153
|
+
return
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
// Defer reply before API calls to avoid 3-second timeout
|
|
157
|
+
await interaction.deferReply({ flags: MessageFlags.Ephemeral })
|
|
158
|
+
|
|
159
|
+
const getClient = await initializeOpencodeForDirectory(projectDirectory)
|
|
160
|
+
if (getClient instanceof Error) {
|
|
161
|
+
await interaction.editReply({
|
|
162
|
+
content: `Failed to load messages: ${getClient.message}`,
|
|
163
|
+
})
|
|
164
|
+
return
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
try {
|
|
168
|
+
const messagesResponse = await getClient().session.messages({
|
|
169
|
+
sessionID: sessionId,
|
|
170
|
+
})
|
|
171
|
+
|
|
172
|
+
if (!messagesResponse.data) {
|
|
173
|
+
await interaction.editReply({
|
|
174
|
+
content: 'Failed to fetch session messages',
|
|
175
|
+
})
|
|
176
|
+
return
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
const userMessages = messagesResponse.data.filter(
|
|
180
|
+
(m: { info: { role: string } }) => m.info.role === 'user',
|
|
181
|
+
)
|
|
182
|
+
|
|
183
|
+
if (userMessages.length === 0) {
|
|
184
|
+
await interaction.editReply({
|
|
185
|
+
content: 'No user messages found in this session',
|
|
186
|
+
})
|
|
187
|
+
return
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
const recentMessages = userMessages.slice(-25)
|
|
191
|
+
|
|
192
|
+
// Filter out synthetic parts (branch context, memory reminders, etc.)
|
|
193
|
+
// injected by the opencode plugin — they clutter the dropdown preview.
|
|
194
|
+
const options = recentMessages
|
|
195
|
+
.map(
|
|
196
|
+
(
|
|
197
|
+
m: {
|
|
198
|
+
parts: Array<{ type: string; text?: string; synthetic?: boolean }>
|
|
199
|
+
info: { id: string; time: { created: number } }
|
|
200
|
+
},
|
|
201
|
+
index: number,
|
|
202
|
+
) => {
|
|
203
|
+
const textPart = m.parts.find((p) => {
|
|
204
|
+
return p.type === 'text' && !p.synthetic && typeof p.text === 'string'
|
|
205
|
+
})
|
|
206
|
+
if (!textPart?.text) {
|
|
207
|
+
return null
|
|
208
|
+
}
|
|
209
|
+
const preview = textPart.text.slice(0, 80)
|
|
210
|
+
const label = `${index + 1}. ${preview}${preview.length >= 80 ? '...' : ''}`
|
|
211
|
+
|
|
212
|
+
return {
|
|
213
|
+
label: label.slice(0, 100),
|
|
214
|
+
value: m.info.id,
|
|
215
|
+
description: new Date(m.info.time.created)
|
|
216
|
+
.toLocaleString()
|
|
217
|
+
.slice(0, 50),
|
|
218
|
+
}
|
|
219
|
+
},
|
|
220
|
+
)
|
|
221
|
+
.filter(isTruthy)
|
|
222
|
+
|
|
223
|
+
const selectMenu = new StringSelectMenuBuilder()
|
|
224
|
+
// Discord component custom_id max length is 100 chars.
|
|
225
|
+
// Avoid embedding long directory paths (or base64 of them) in the custom ID.
|
|
226
|
+
// handleForkSelectMenu resolves the directory from the current thread instead.
|
|
227
|
+
.setCustomId(`fork_select:${sessionId}`)
|
|
228
|
+
.setPlaceholder('Select a message to fork from')
|
|
229
|
+
.addOptions(options)
|
|
230
|
+
|
|
231
|
+
const actionRow =
|
|
232
|
+
new ActionRowBuilder<StringSelectMenuBuilder>().addComponents(selectMenu)
|
|
233
|
+
|
|
234
|
+
await interaction.editReply({
|
|
235
|
+
content:
|
|
236
|
+
'**Fork Session**\nSelect the user message to fork from. The forked session will continue as if you had not sent that message:',
|
|
237
|
+
components: [actionRow],
|
|
238
|
+
})
|
|
239
|
+
} catch (error) {
|
|
240
|
+
forkLogger.error('Error loading messages:', error)
|
|
241
|
+
await interaction.editReply({
|
|
242
|
+
content: `Failed to load messages: ${error instanceof Error ? error.message : 'Unknown error'}`,
|
|
243
|
+
})
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
export async function handleForkSelectMenu(
|
|
248
|
+
interaction: StringSelectMenuInteraction,
|
|
249
|
+
): Promise<void> {
|
|
250
|
+
const customId = interaction.customId
|
|
251
|
+
|
|
252
|
+
if (!customId.startsWith('fork_select:')) {
|
|
253
|
+
return
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
const [, sessionId] = customId.split(':')
|
|
257
|
+
if (!sessionId) {
|
|
258
|
+
await interaction.reply({
|
|
259
|
+
content: 'Invalid selection data',
|
|
260
|
+
flags: MessageFlags.Ephemeral,
|
|
261
|
+
})
|
|
262
|
+
return
|
|
263
|
+
}
|
|
264
|
+
const selectedMessageId = interaction.values[0]
|
|
265
|
+
|
|
266
|
+
if (!selectedMessageId) {
|
|
267
|
+
await interaction.reply({
|
|
268
|
+
content: 'No message selected',
|
|
269
|
+
flags: MessageFlags.Ephemeral,
|
|
270
|
+
})
|
|
271
|
+
return
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
await interaction.deferReply()
|
|
275
|
+
|
|
276
|
+
const threadChannel = getThreadChannel(interaction.channel)
|
|
277
|
+
if (threadChannel instanceof Error) {
|
|
278
|
+
await interaction.editReply(threadChannel.message)
|
|
279
|
+
return
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
const resolved = await resolveWorkingDirectory({
|
|
283
|
+
channel: threadChannel,
|
|
284
|
+
})
|
|
285
|
+
if (!resolved) {
|
|
286
|
+
await interaction.editReply(
|
|
287
|
+
'Could not determine project directory for this channel',
|
|
288
|
+
)
|
|
289
|
+
return
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
const { projectDirectory } = resolved
|
|
293
|
+
|
|
294
|
+
const getClient = await initializeOpencodeForDirectory(projectDirectory)
|
|
295
|
+
if (getClient instanceof Error) {
|
|
296
|
+
await interaction.editReply(`Failed to fork session: ${getClient.message}`)
|
|
297
|
+
return
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
try {
|
|
301
|
+
const forkResponse = await getClient().session.fork({
|
|
302
|
+
sessionID: sessionId,
|
|
303
|
+
messageID: selectedMessageId,
|
|
304
|
+
})
|
|
305
|
+
|
|
306
|
+
if (!forkResponse.data) {
|
|
307
|
+
await interaction.editReply('Failed to fork session')
|
|
308
|
+
return
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
const forkedSession = forkResponse.data
|
|
312
|
+
const parentChannel = getThreadChannel(interaction.channel)
|
|
313
|
+
if (parentChannel instanceof Error) {
|
|
314
|
+
await interaction.editReply(parentChannel.message)
|
|
315
|
+
return
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
const textChannel = await resolveTextChannel(parentChannel)
|
|
319
|
+
|
|
320
|
+
if (!textChannel) {
|
|
321
|
+
await interaction.editReply('Could not resolve parent text channel')
|
|
322
|
+
return
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
const thread = await textChannel.threads.create({
|
|
326
|
+
name: `Fork: ${forkedSession.title}`.slice(0, 100),
|
|
327
|
+
autoArchiveDuration: ThreadAutoArchiveDuration.OneDay,
|
|
328
|
+
reason: `Forked from session ${sessionId}`,
|
|
329
|
+
})
|
|
330
|
+
|
|
331
|
+
// Claim the forked session immediately so external polling does not race
|
|
332
|
+
// and create a duplicate Sync thread before the rest of this setup runs.
|
|
333
|
+
await setThreadSession(thread.id, forkedSession.id)
|
|
334
|
+
|
|
335
|
+
// Add user to thread so it appears in their sidebar
|
|
336
|
+
await thread.members.add(interaction.user.id)
|
|
337
|
+
|
|
338
|
+
sessionLogger.log(
|
|
339
|
+
`Created forked session ${forkedSession.id} in thread ${thread.id}`,
|
|
340
|
+
)
|
|
341
|
+
|
|
342
|
+
await sendThreadMessage(
|
|
343
|
+
thread,
|
|
344
|
+
`**Forked session created!**\nFrom: \`${sessionId}\`\nNew session: \`${forkedSession.id}\``,
|
|
345
|
+
)
|
|
346
|
+
|
|
347
|
+
// Fetch and display the last assistant messages from the forked session
|
|
348
|
+
const messagesResponse = await getClient().session.messages({
|
|
349
|
+
sessionID: forkedSession.id,
|
|
350
|
+
})
|
|
351
|
+
|
|
352
|
+
if (messagesResponse.data) {
|
|
353
|
+
const { chunks } = collectSessionChunks({
|
|
354
|
+
messages: messagesResponse.data,
|
|
355
|
+
limit: 30,
|
|
356
|
+
})
|
|
357
|
+
const batched = batchChunksForDiscord(chunks)
|
|
358
|
+
for (const batch of batched) {
|
|
359
|
+
const discordMessage = await sendThreadMessage(thread, batch.content)
|
|
360
|
+
await setPartMessagesBatch(
|
|
361
|
+
batch.partIds.map((partId) => ({
|
|
362
|
+
partId,
|
|
363
|
+
messageId: discordMessage.id,
|
|
364
|
+
threadId: thread.id,
|
|
365
|
+
})),
|
|
366
|
+
)
|
|
367
|
+
}
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
await sendThreadMessage(
|
|
371
|
+
thread,
|
|
372
|
+
`You can now continue the conversation from this point.`,
|
|
373
|
+
)
|
|
374
|
+
|
|
375
|
+
await interaction.editReply(
|
|
376
|
+
`Session forked! Continue in ${thread.toString()}`,
|
|
377
|
+
)
|
|
378
|
+
} catch (error) {
|
|
379
|
+
forkLogger.error('Error forking session:', error)
|
|
380
|
+
await interaction.editReply(
|
|
381
|
+
`Failed to fork session: ${error instanceof Error ? error.message : 'Unknown error'}`,
|
|
382
|
+
)
|
|
383
|
+
}
|
|
384
|
+
}
|
|
385
|
+
|
|
386
|
+
export { getThreadChannel, parsePersistedEventRows }
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
// Transcription API key button, slash command, and modal handlers.
|
|
2
|
+
// Auto-detects provider from key prefix: sk-* = OpenAI, otherwise Gemini.
|
|
3
|
+
|
|
4
|
+
import {
|
|
5
|
+
ActionRowBuilder,
|
|
6
|
+
type ButtonInteraction,
|
|
7
|
+
type ChatInputCommandInteraction,
|
|
8
|
+
ModalBuilder,
|
|
9
|
+
type ModalSubmitInteraction,
|
|
10
|
+
TextInputBuilder,
|
|
11
|
+
TextInputStyle,
|
|
12
|
+
MessageFlags,
|
|
13
|
+
} from 'discord.js'
|
|
14
|
+
import { setGeminiApiKey, setOpenAIApiKey } from '../database.js'
|
|
15
|
+
|
|
16
|
+
function buildTranscriptionApiKeyModal(appId: string): ModalBuilder {
|
|
17
|
+
const modal = new ModalBuilder()
|
|
18
|
+
.setCustomId(`transcription_apikey_modal:${appId}`)
|
|
19
|
+
.setTitle('Transcription API Key')
|
|
20
|
+
|
|
21
|
+
const apiKeyInput = new TextInputBuilder()
|
|
22
|
+
.setCustomId('apikey')
|
|
23
|
+
.setLabel('OpenAI or Gemini API Key')
|
|
24
|
+
.setPlaceholder('sk-... or AIza...')
|
|
25
|
+
.setStyle(TextInputStyle.Short)
|
|
26
|
+
.setRequired(true)
|
|
27
|
+
|
|
28
|
+
const actionRow = new ActionRowBuilder<TextInputBuilder>().addComponents(
|
|
29
|
+
apiKeyInput,
|
|
30
|
+
)
|
|
31
|
+
modal.addComponents(actionRow)
|
|
32
|
+
return modal
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export async function handleTranscriptionApiKeyButton(
|
|
36
|
+
interaction: ButtonInteraction,
|
|
37
|
+
): Promise<void> {
|
|
38
|
+
if (!interaction.customId.startsWith('transcription_apikey:')) return
|
|
39
|
+
|
|
40
|
+
const appId = interaction.customId
|
|
41
|
+
.slice('transcription_apikey:'.length)
|
|
42
|
+
.trim()
|
|
43
|
+
if (!appId) {
|
|
44
|
+
await interaction.reply({
|
|
45
|
+
content: 'Missing app id for API key setup.',
|
|
46
|
+
flags: MessageFlags.Ephemeral,
|
|
47
|
+
})
|
|
48
|
+
return
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
await interaction.showModal(buildTranscriptionApiKeyModal(appId))
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
export async function handleTranscriptionApiKeyCommand({
|
|
55
|
+
interaction,
|
|
56
|
+
appId,
|
|
57
|
+
}: {
|
|
58
|
+
interaction: ChatInputCommandInteraction
|
|
59
|
+
appId: string
|
|
60
|
+
}): Promise<void> {
|
|
61
|
+
await interaction.showModal(buildTranscriptionApiKeyModal(appId))
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
export async function handleTranscriptionApiKeyModalSubmit(
|
|
65
|
+
interaction: ModalSubmitInteraction,
|
|
66
|
+
): Promise<void> {
|
|
67
|
+
if (!interaction.customId.startsWith('transcription_apikey_modal:')) return
|
|
68
|
+
|
|
69
|
+
const appId = interaction.customId
|
|
70
|
+
.slice('transcription_apikey_modal:'.length)
|
|
71
|
+
.trim()
|
|
72
|
+
|
|
73
|
+
await interaction.deferReply({ flags: MessageFlags.Ephemeral })
|
|
74
|
+
|
|
75
|
+
if (!appId) {
|
|
76
|
+
await interaction.editReply({
|
|
77
|
+
content: 'Missing app id for API key setup.',
|
|
78
|
+
})
|
|
79
|
+
return
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
const apiKey = interaction.fields.getTextInputValue('apikey').trim()
|
|
83
|
+
if (!apiKey) {
|
|
84
|
+
await interaction.editReply({
|
|
85
|
+
content: 'API key is required.',
|
|
86
|
+
})
|
|
87
|
+
return
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
// Auto-detect provider from key prefix
|
|
91
|
+
if (apiKey.startsWith('sk-')) {
|
|
92
|
+
await setOpenAIApiKey(appId, apiKey)
|
|
93
|
+
await interaction.editReply({
|
|
94
|
+
content:
|
|
95
|
+
'OpenAI API key saved. Voice messages will be transcribed with OpenAI.',
|
|
96
|
+
})
|
|
97
|
+
} else {
|
|
98
|
+
await setGeminiApiKey(appId, apiKey)
|
|
99
|
+
await interaction.editReply({
|
|
100
|
+
content:
|
|
101
|
+
'Gemini API key saved. Voice messages will be transcribed with Gemini.',
|
|
102
|
+
})
|
|
103
|
+
}
|
|
104
|
+
}
|