@otto-assistant/otto 0.1.2 → 0.7.16
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/bin.js +2 -0
- package/dist/agent-model.e2e.test.js +755 -0
- package/dist/ai-tool-to-genai.js +233 -0
- package/dist/ai-tool-to-genai.test.js +267 -0
- package/dist/ai-tool.js +6 -0
- package/dist/anthropic-account-identity.js +62 -0
- package/dist/anthropic-account-identity.test.js +38 -0
- package/dist/anthropic-auth-plugin.js +917 -0
- package/dist/anthropic-auth-state.js +303 -0
- package/dist/anthropic-auth-state.test.js +150 -0
- package/dist/bin.js +152 -0
- package/dist/btw-prefix-detection.js +17 -0
- package/dist/btw-prefix-detection.test.js +63 -0
- package/dist/channel-management.js +259 -0
- package/dist/cli-parsing.test.js +142 -0
- package/dist/cli-send-thread.e2e.test.js +353 -0
- package/dist/cli-telegram-options.test.js +99 -0
- package/dist/cli.js +4210 -568
- package/dist/commands/abort.js +65 -0
- package/dist/commands/action-buttons.js +245 -0
- package/dist/commands/add-dir.js +124 -0
- package/dist/commands/add-dir.test.js +126 -0
- package/dist/commands/add-project.js +113 -0
- package/dist/commands/agent.js +355 -0
- package/dist/commands/ask-question.js +320 -0
- package/dist/commands/ask-question.test.js +92 -0
- package/dist/commands/btw.js +121 -0
- package/dist/commands/cli-commands-group-a.test.js +728 -0
- package/dist/commands/cli-commands-group-b.test.js +695 -0
- package/dist/commands/compact.js +120 -0
- package/dist/commands/context-usage.js +140 -0
- package/dist/commands/create-new-project.js +130 -0
- package/dist/commands/diff.js +63 -0
- package/dist/commands/discord-commands-group-a.test.js +655 -0
- package/dist/commands/discord-commands-group-b.test.js +595 -0
- package/dist/commands/discord-commands-group-c.test.js +739 -0
- package/dist/commands/file-upload.js +275 -0
- package/dist/commands/fork-subagent.js +177 -0
- package/dist/commands/fork.js +262 -0
- package/dist/commands/gemini-apikey.js +70 -0
- package/dist/commands/login.js +893 -0
- package/dist/commands/mcp.js +239 -0
- package/dist/commands/memory-snapshot.js +24 -0
- package/dist/commands/mention-mode.js +44 -0
- package/dist/commands/merge-worktree.js +162 -0
- package/dist/commands/model-variant.js +369 -0
- package/dist/commands/model.js +798 -0
- package/dist/commands/new-worktree.js +465 -0
- package/dist/commands/paginated-select.js +57 -0
- package/dist/commands/permissions.js +274 -0
- package/dist/commands/queue.js +223 -0
- package/dist/commands/remove-project.js +115 -0
- package/dist/commands/restart-opencode-server.js +127 -0
- package/dist/commands/resume.js +149 -0
- package/dist/commands/run-command.js +79 -0
- package/dist/commands/screenshare.js +303 -0
- package/dist/commands/screenshare.test.js +20 -0
- package/dist/commands/session-id.js +78 -0
- package/dist/commands/session.js +176 -0
- package/dist/commands/share.js +80 -0
- package/dist/commands/tasks.js +205 -0
- package/dist/commands/thread-deletion-sync.js +50 -0
- package/dist/commands/types.js +2 -0
- package/dist/commands/undo-redo.js +305 -0
- package/dist/commands/unset-model.js +139 -0
- package/dist/commands/upgrade.js +48 -0
- package/dist/commands/user-command.js +155 -0
- package/dist/commands/verbosity.js +125 -0
- package/dist/commands/vscode.js +269 -0
- package/dist/commands/worktree-settings.js +43 -0
- package/dist/commands/worktrees.js +468 -0
- package/dist/condense-memory.js +33 -0
- package/dist/config.js +100 -255
- package/dist/context-awareness-plugin.js +340 -0
- package/dist/context-awareness-plugin.test.js +126 -0
- package/dist/critique-utils.js +95 -0
- package/dist/database.js +1355 -0
- package/dist/db.js +260 -0
- package/dist/db.test.js +138 -0
- package/dist/debounce-timeout.js +28 -0
- package/dist/debounced-process-flush.js +77 -0
- package/dist/discord-bot.js +1124 -0
- package/dist/discord-command-registration.js +567 -0
- package/dist/discord-urls.js +82 -0
- package/dist/discord-utils.js +616 -0
- package/dist/discord-utils.test.js +134 -0
- package/dist/errors.js +179 -0
- package/dist/escape-backticks.test.js +429 -0
- package/dist/event-stream-real-capture.e2e.test.js +533 -0
- package/dist/eventsource-parser.test.js +327 -0
- package/dist/exec-async.js +26 -0
- package/dist/external-opencode-sync.js +480 -0
- package/dist/format-tables.js +491 -0
- package/dist/format-tables.test.js +478 -0
- package/dist/forum-sync/config.js +79 -0
- package/dist/forum-sync/discord-operations.js +154 -0
- package/dist/forum-sync/index.js +5 -0
- package/dist/forum-sync/markdown.js +113 -0
- package/dist/forum-sync/sync-to-discord.js +417 -0
- package/dist/forum-sync/sync-to-files.js +190 -0
- package/dist/forum-sync/types.js +53 -0
- package/dist/forum-sync/watchers.js +307 -0
- package/dist/gateway-proxy-reconnect.e2e.test.js +394 -0
- package/dist/gateway-proxy.e2e.test.js +485 -0
- package/dist/genai-worker-wrapper.js +111 -0
- package/dist/genai-worker.js +311 -0
- package/dist/genai.js +232 -0
- package/dist/generated/browser.js +17 -0
- package/dist/generated/client.js +37 -0
- package/dist/generated/commonInputTypes.js +10 -0
- package/dist/generated/enums.js +58 -0
- package/dist/generated/internal/class.js +49 -0
- package/dist/generated/internal/prismaNamespace.js +254 -0
- package/dist/generated/internal/prismaNamespaceBrowser.js +224 -0
- package/dist/generated/models/bot_api_keys.js +1 -0
- package/dist/generated/models/bot_tokens.js +1 -0
- package/dist/generated/models/channel_agents.js +1 -0
- package/dist/generated/models/channel_directories.js +1 -0
- package/dist/generated/models/channel_mention_mode.js +1 -0
- package/dist/generated/models/channel_models.js +1 -0
- package/dist/generated/models/channel_verbosity.js +1 -0
- package/dist/generated/models/channel_worktrees.js +1 -0
- package/dist/generated/models/forum_sync_configs.js +1 -0
- package/dist/generated/models/global_models.js +1 -0
- package/dist/generated/models/ipc_requests.js +1 -0
- package/dist/generated/models/part_messages.js +1 -0
- package/dist/generated/models/scheduled_tasks.js +1 -0
- package/dist/generated/models/session_agents.js +1 -0
- package/dist/generated/models/session_events.js +1 -0
- package/dist/generated/models/session_models.js +1 -0
- package/dist/generated/models/session_start_sources.js +1 -0
- package/dist/generated/models/thread_sessions.js +1 -0
- package/dist/generated/models/thread_worktrees.js +1 -0
- package/dist/generated/models.js +1 -0
- package/dist/heap-monitor.js +122 -0
- package/dist/hrana-server.js +251 -0
- package/dist/hrana-server.test.js +370 -0
- package/dist/html-actions.js +123 -0
- package/dist/html-actions.test.js +70 -0
- package/dist/html-components.js +117 -0
- package/dist/html-components.test.js +34 -0
- package/dist/image-optimizer-plugin.js +153 -0
- package/dist/image-utils.js +112 -0
- package/dist/interaction-handler.js +420 -0
- package/dist/ipc-polling.js +327 -0
- package/dist/ipc-tools-plugin.js +193 -0
- package/dist/ipc-utils.js +18 -0
- package/dist/limit-heading-depth.js +25 -0
- package/dist/limit-heading-depth.test.js +105 -0
- package/dist/logger.js +171 -0
- package/dist/markdown.js +342 -0
- package/dist/markdown.test.js +264 -0
- package/dist/memory-overview-plugin.js +128 -0
- package/dist/message-finish-field.e2e.test.js +168 -0
- package/dist/message-formatting.js +415 -0
- package/dist/message-formatting.test.js +115 -0
- package/dist/message-preprocessing.js +359 -0
- package/dist/onboarding-tutorial.js +163 -0
- package/dist/onboarding-welcome.js +37 -0
- package/dist/openai-realtime.js +224 -0
- package/dist/opencode-command-detection.js +65 -0
- package/dist/opencode-command-detection.test.js +240 -0
- package/dist/opencode-command.js +131 -0
- package/dist/opencode-command.test.js +48 -0
- package/dist/opencode-interrupt-plugin.js +388 -0
- package/dist/opencode-interrupt-plugin.test.js +463 -0
- package/dist/opencode.js +1124 -0
- package/dist/otto/branding.js +22 -0
- package/dist/otto/index.js +21 -0
- package/dist/otto-digital-twin.e2e.test.js +161 -0
- package/dist/otto-opencode-plugin-loading.e2e.test.js +94 -0
- package/dist/otto-opencode-plugin.js +21 -0
- package/dist/otto-opencode-plugin.test.js +98 -0
- package/dist/parse-permission-rules.test.js +117 -0
- package/dist/patch-text-parser.js +97 -0
- package/dist/plugin-logger.js +68 -0
- package/dist/privacy-sanitizer.js +105 -0
- package/dist/queue-advanced-abort.e2e.test.js +293 -0
- package/dist/queue-advanced-action-buttons.e2e.test.js +206 -0
- package/dist/queue-advanced-e2e-setup.js +790 -0
- package/dist/queue-advanced-footer.e2e.test.js +481 -0
- package/dist/queue-advanced-model-switch.e2e.test.js +299 -0
- package/dist/queue-advanced-permissions-typing.e2e.test.js +179 -0
- package/dist/queue-advanced-question.e2e.test.js +261 -0
- package/dist/queue-advanced-typing-interrupt.e2e.test.js +114 -0
- package/dist/queue-advanced-typing.e2e.test.js +153 -0
- package/dist/queue-drain-after-interactive-ui.e2e.test.js +119 -0
- package/dist/queue-interrupt-drain.e2e.test.js +135 -0
- package/dist/queue-question-select-drain.e2e.test.js +256 -0
- package/dist/runtime-idle-sweeper.js +52 -0
- package/dist/runtime-lifecycle.e2e.test.js +514 -0
- package/dist/sentry.js +23 -0
- package/dist/session-handler/agent-utils.js +67 -0
- package/dist/session-handler/event-stream-state.js +475 -0
- package/dist/session-handler/event-stream-state.test.js +632 -0
- package/dist/session-handler/model-utils.js +147 -0
- package/dist/session-handler/opencode-session-event-log.js +94 -0
- package/dist/session-handler/thread-runtime-state.js +131 -0
- package/dist/session-handler/thread-session-runtime.js +3390 -0
- package/dist/session-handler.js +9 -0
- package/dist/session-search.js +100 -0
- package/dist/session-search.test.js +40 -0
- package/dist/session-title-rename.test.js +92 -0
- package/dist/skill-filter.js +31 -0
- package/dist/skill-filter.test.js +65 -0
- package/dist/startup-service.js +153 -0
- package/dist/startup-time.e2e.test.js +296 -0
- package/dist/store.js +19 -0
- package/dist/subagent-rate-limit-plugin.js +175 -0
- package/dist/system-message.js +702 -0
- package/dist/system-message.test.js +697 -0
- package/dist/task-runner.js +530 -0
- package/dist/task-schedule.js +213 -0
- package/dist/task-schedule.test.js +71 -0
- package/dist/test-utils.js +313 -0
- package/dist/thinking-utils.js +35 -0
- package/dist/thread-message-queue.e2e.test.js +1111 -0
- package/dist/tools.js +357 -0
- package/dist/undo-redo.e2e.test.js +161 -0
- package/dist/unnest-code-blocks.js +146 -0
- package/dist/unnest-code-blocks.test.js +673 -0
- package/dist/upgrade.js +156 -0
- package/dist/utils.js +172 -0
- package/dist/utils.test.js +130 -0
- package/dist/voice-attachment.js +34 -0
- package/dist/voice-handler.js +646 -0
- package/dist/voice-message.e2e.test.js +1021 -0
- package/dist/voice.js +456 -0
- package/dist/voice.test.js +235 -0
- package/dist/wait-session.js +171 -0
- package/dist/websockify.js +69 -0
- package/dist/worker-types.js +4 -0
- package/dist/worktree-lifecycle.e2e.test.js +311 -0
- package/dist/worktree-utils.js +3 -0
- package/dist/worktrees.js +991 -0
- package/dist/worktrees.test.js +415 -0
- package/dist/xml.js +92 -0
- package/dist/xml.test.js +32 -0
- package/package.json +90 -38
- package/schema.prisma +303 -0
- package/skills/batch/SKILL.md +87 -0
- package/skills/critique/SKILL.md +112 -0
- package/skills/egaki/SKILL.md +100 -0
- package/skills/errore/SKILL.md +647 -0
- package/skills/event-sourcing-state/SKILL.md +252 -0
- package/skills/goke/SKILL.md +38 -0
- package/skills/jitter/EDITOR.md +219 -0
- package/skills/jitter/EXPORT-INTERNALS.md +309 -0
- package/skills/jitter/SKILL.md +158 -0
- package/skills/jitter/jitter-clipboard.json +1042 -0
- package/skills/jitter/package.json +14 -0
- package/skills/jitter/tsconfig.json +15 -0
- package/skills/jitter/utils/actions.ts +212 -0
- package/skills/jitter/utils/export.ts +114 -0
- package/skills/jitter/utils/index.ts +141 -0
- package/skills/jitter/utils/snapshot.ts +154 -0
- package/skills/jitter/utils/traverse.ts +246 -0
- package/skills/jitter/utils/types.ts +279 -0
- package/skills/jitter/utils/wait.ts +133 -0
- package/skills/lintcn/SKILL.md +873 -0
- package/skills/manual-kimaki-upstream-adapt/SKILL.md +114 -0
- package/skills/new-skill/SKILL.md +237 -0
- package/skills/npm-package/SKILL.md +617 -0
- package/skills/opensrc/SKILL.md +78 -0
- package/skills/otto-publish/SKILL.md +61 -0
- package/skills/playwriter/SKILL.md +35 -0
- package/skills/profano/SKILL.md +16 -0
- package/skills/proxyman/SKILL.md +215 -0
- package/skills/security-review/SKILL.md +208 -0
- package/skills/sigillo/SKILL.md +101 -0
- package/skills/simplify/SKILL.md +58 -0
- package/skills/spiceflow/SKILL.md +28 -0
- package/skills/termcast/SKILL.md +945 -0
- package/skills/tuistory/SKILL.md +98 -0
- package/skills/usecomputer/SKILL.md +264 -0
- package/skills/x-articles/SKILL.md +554 -0
- package/skills/zele/SKILL.md +49 -0
- package/skills/zustand-centralized-state/SKILL.md +1004 -0
- package/src/agent-model.e2e.test.ts +979 -0
- package/src/ai-tool-to-genai.test.ts +296 -0
- package/src/ai-tool-to-genai.ts +283 -0
- package/src/ai-tool.ts +39 -0
- package/src/anthropic-account-identity.test.ts +52 -0
- package/src/anthropic-account-identity.ts +77 -0
- package/src/anthropic-auth-plugin.ts +1139 -0
- package/src/anthropic-auth-state.test.ts +187 -0
- package/src/anthropic-auth-state.ts +386 -0
- package/src/bin.ts +182 -0
- package/src/btw-prefix-detection.test.ts +73 -0
- package/src/btw-prefix-detection.ts +23 -0
- package/src/channel-management.ts +376 -0
- package/src/cli-parsing.test.ts +197 -0
- package/src/cli-send-thread.e2e.test.ts +463 -0
- package/src/cli-telegram-options.test.ts +114 -0
- package/src/cli.ts +5718 -580
- package/src/commands/abort.ts +89 -0
- package/src/commands/action-buttons.ts +364 -0
- package/src/commands/add-dir.test.ts +154 -0
- package/src/commands/add-dir.ts +175 -0
- package/src/commands/add-project.ts +149 -0
- package/src/commands/agent.ts +496 -0
- package/src/commands/ask-question.test.ts +111 -0
- package/src/commands/ask-question.ts +455 -0
- package/src/commands/btw.ts +184 -0
- package/src/commands/cli-commands-group-a.test.ts +837 -0
- package/src/commands/cli-commands-group-b.test.ts +800 -0
- package/src/commands/compact.ts +157 -0
- package/src/commands/context-usage.ts +199 -0
- package/src/commands/create-new-project.ts +190 -0
- package/src/commands/diff.ts +91 -0
- package/src/commands/discord-commands-group-a.test.ts +789 -0
- package/src/commands/discord-commands-group-b.test.ts +648 -0
- package/src/commands/discord-commands-group-c.test.ts +882 -0
- package/src/commands/file-upload.ts +389 -0
- package/src/commands/fork-subagent.ts +263 -0
- package/src/commands/fork.ts +386 -0
- package/src/commands/gemini-apikey.ts +104 -0
- package/src/commands/login.ts +1181 -0
- package/src/commands/mcp.ts +307 -0
- package/src/commands/memory-snapshot.ts +30 -0
- package/src/commands/mention-mode.ts +68 -0
- package/src/commands/merge-worktree.ts +226 -0
- package/src/commands/model-variant.ts +488 -0
- package/src/commands/model.ts +1082 -0
- package/src/commands/new-worktree.ts +645 -0
- package/src/commands/paginated-select.ts +81 -0
- package/src/commands/permissions.ts +397 -0
- package/src/commands/queue.ts +293 -0
- package/src/commands/remove-project.ts +155 -0
- package/src/commands/restart-opencode-server.ts +162 -0
- package/src/commands/resume.ts +230 -0
- package/src/commands/run-command.ts +123 -0
- package/src/commands/screenshare.test.ts +30 -0
- package/src/commands/screenshare.ts +366 -0
- package/src/commands/session-id.ts +109 -0
- package/src/commands/session.ts +227 -0
- package/src/commands/share.ts +106 -0
- package/src/commands/tasks.ts +293 -0
- package/src/commands/thread-deletion-sync.ts +80 -0
- package/src/commands/types.ts +25 -0
- package/src/commands/undo-redo.ts +386 -0
- package/src/commands/unset-model.ts +174 -0
- package/src/commands/upgrade.ts +59 -0
- package/src/commands/user-command.ts +198 -0
- package/src/commands/verbosity.ts +173 -0
- package/src/commands/vscode.ts +342 -0
- package/src/commands/worktree-settings.ts +70 -0
- package/src/commands/worktrees.ts +645 -0
- package/src/condense-memory.ts +36 -0
- package/src/config.ts +103 -339
- package/src/context-awareness-plugin.test.ts +144 -0
- package/src/context-awareness-plugin.ts +469 -0
- package/src/critique-utils.ts +139 -0
- package/src/database.ts +1949 -0
- package/src/db.test.ts +162 -0
- package/src/db.ts +295 -0
- package/src/debounce-timeout.ts +43 -0
- package/src/debounced-process-flush.ts +104 -0
- package/src/discord-bot.ts +1507 -0
- package/src/discord-command-registration.ts +752 -0
- package/src/discord-urls.ts +89 -0
- package/src/discord-utils.test.ts +153 -0
- package/src/discord-utils.ts +846 -0
- package/src/errors.ts +232 -0
- package/src/escape-backticks.test.ts +469 -0
- package/src/event-stream-real-capture.e2e.test.ts +692 -0
- package/src/eventsource-parser.test.ts +351 -0
- package/src/exec-async.ts +35 -0
- package/src/external-opencode-sync.ts +685 -0
- package/src/format-tables.test.ts +515 -0
- package/src/format-tables.ts +718 -0
- package/src/forum-sync/config.ts +92 -0
- package/src/forum-sync/discord-operations.ts +241 -0
- package/src/forum-sync/index.ts +9 -0
- package/src/forum-sync/markdown.ts +172 -0
- package/src/forum-sync/sync-to-discord.ts +595 -0
- package/src/forum-sync/sync-to-files.ts +294 -0
- package/src/forum-sync/types.ts +175 -0
- package/src/forum-sync/watchers.ts +454 -0
- package/src/gateway-proxy-reconnect.e2e.test.ts +523 -0
- package/src/gateway-proxy.e2e.test.ts +644 -0
- package/src/genai-worker-wrapper.ts +164 -0
- package/src/genai-worker.ts +386 -0
- package/src/genai.ts +321 -0
- package/src/generated/browser.ts +114 -0
- package/src/generated/client.ts +138 -0
- package/src/generated/commonInputTypes.ts +770 -0
- package/src/generated/enums.ts +98 -0
- package/src/generated/internal/class.ts +384 -0
- package/src/generated/internal/prismaNamespace.ts +2394 -0
- package/src/generated/internal/prismaNamespaceBrowser.ts +327 -0
- package/src/generated/models/bot_api_keys.ts +1288 -0
- package/src/generated/models/bot_tokens.ts +1700 -0
- package/src/generated/models/channel_agents.ts +1256 -0
- package/src/generated/models/channel_directories.ts +1859 -0
- package/src/generated/models/channel_mention_mode.ts +1300 -0
- package/src/generated/models/channel_models.ts +1288 -0
- package/src/generated/models/channel_verbosity.ts +1228 -0
- package/src/generated/models/channel_worktrees.ts +1300 -0
- package/src/generated/models/forum_sync_configs.ts +1452 -0
- package/src/generated/models/global_models.ts +1288 -0
- package/src/generated/models/ipc_requests.ts +1485 -0
- package/src/generated/models/part_messages.ts +1302 -0
- package/src/generated/models/scheduled_tasks.ts +2320 -0
- package/src/generated/models/session_agents.ts +1086 -0
- package/src/generated/models/session_events.ts +1439 -0
- package/src/generated/models/session_models.ts +1114 -0
- package/src/generated/models/session_start_sources.ts +1408 -0
- package/src/generated/models/thread_sessions.ts +1781 -0
- package/src/generated/models/thread_worktrees.ts +1356 -0
- package/src/generated/models.ts +30 -0
- package/src/heap-monitor.ts +152 -0
- package/src/hrana-server.test.ts +434 -0
- package/src/hrana-server.ts +299 -0
- package/src/html-actions.test.ts +87 -0
- package/src/html-actions.ts +174 -0
- package/src/html-components.test.ts +38 -0
- package/src/html-components.ts +181 -0
- package/src/image-optimizer-plugin.ts +194 -0
- package/src/image-utils.ts +149 -0
- package/src/interaction-handler.ts +610 -0
- package/src/ipc-polling.ts +427 -0
- package/src/ipc-tools-plugin.ts +236 -0
- package/src/ipc-utils.ts +29 -0
- package/src/limit-heading-depth.test.ts +116 -0
- package/src/limit-heading-depth.ts +26 -0
- package/src/logger.ts +215 -0
- package/src/markdown.test.ts +315 -0
- package/src/markdown.ts +410 -0
- package/src/memory-overview-plugin.ts +163 -0
- package/src/message-finish-field.e2e.test.ts +195 -0
- package/src/message-formatting.test.ts +126 -0
- package/src/message-formatting.ts +535 -0
- package/src/message-preprocessing.ts +488 -0
- package/src/onboarding-tutorial.ts +167 -0
- package/src/onboarding-welcome.ts +49 -0
- package/src/openai-realtime.ts +358 -0
- package/src/opencode-command-detection.test.ts +307 -0
- package/src/opencode-command-detection.ts +76 -0
- package/src/opencode-command.test.ts +70 -0
- package/src/opencode-command.ts +191 -0
- package/src/opencode-interrupt-plugin.test.ts +682 -0
- package/src/opencode-interrupt-plugin.ts +507 -0
- package/src/opencode.ts +1462 -0
- package/src/otto/branding.ts +23 -0
- package/src/otto/index.ts +22 -0
- package/src/otto-digital-twin.e2e.test.ts +199 -0
- package/src/otto-opencode-plugin-loading.e2e.test.ts +117 -0
- package/src/otto-opencode-plugin.test.ts +108 -0
- package/src/otto-opencode-plugin.ts +22 -0
- package/src/parse-permission-rules.test.ts +127 -0
- package/src/patch-text-parser.ts +107 -0
- package/src/plugin-logger.ts +84 -0
- package/src/privacy-sanitizer.ts +142 -0
- package/src/queue-advanced-abort.e2e.test.ts +382 -0
- package/src/queue-advanced-action-buttons.e2e.test.ts +268 -0
- package/src/queue-advanced-e2e-setup.ts +877 -0
- package/src/queue-advanced-footer.e2e.test.ts +591 -0
- package/src/queue-advanced-model-switch.e2e.test.ts +383 -0
- package/src/queue-advanced-permissions-typing.e2e.test.ts +246 -0
- package/src/queue-advanced-question.e2e.test.ts +316 -0
- package/src/queue-advanced-typing-interrupt.e2e.test.ts +146 -0
- package/src/queue-advanced-typing.e2e.test.ts +199 -0
- package/src/queue-drain-after-interactive-ui.e2e.test.ts +151 -0
- package/src/queue-interrupt-drain.e2e.test.ts +166 -0
- package/src/queue-question-select-drain.e2e.test.ts +327 -0
- package/src/runtime-idle-sweeper.ts +76 -0
- package/src/runtime-lifecycle.e2e.test.ts +651 -0
- package/src/schema.sql +174 -0
- package/src/sentry.ts +26 -0
- package/src/session-handler/agent-utils.ts +99 -0
- package/src/session-handler/event-stream-fixtures/real-session-action-buttons.jsonl +45 -0
- package/src/session-handler/event-stream-fixtures/real-session-footer-suppressed-on-pre-idle-interrupt.jsonl +40 -0
- package/src/session-handler/event-stream-fixtures/real-session-permission-external-file.jsonl +23 -0
- package/src/session-handler/event-stream-fixtures/real-session-task-normal.jsonl +22 -0
- package/src/session-handler/event-stream-fixtures/real-session-task-three-parallel-sleeps.jsonl +277 -0
- package/src/session-handler/event-stream-fixtures/real-session-task-user-interruption.jsonl +46 -0
- package/src/session-handler/event-stream-fixtures/session-abort-after-idle-race.jsonl +21 -0
- package/src/session-handler/event-stream-fixtures/session-concurrent-messages-serialized.jsonl +56 -0
- package/src/session-handler/event-stream-fixtures/session-explicit-abort.jsonl +44 -0
- package/src/session-handler/event-stream-fixtures/session-normal-completion.jsonl +29 -0
- package/src/session-handler/event-stream-fixtures/session-tool-call-noisy-stream.jsonl +29 -0
- package/src/session-handler/event-stream-fixtures/session-two-completions-same-session.jsonl +50 -0
- package/src/session-handler/event-stream-fixtures/session-user-interruption.jsonl +59 -0
- package/src/session-handler/event-stream-fixtures/session-voice-queued-followup.jsonl +52 -0
- package/src/session-handler/event-stream-state.test.ts +717 -0
- package/src/session-handler/event-stream-state.ts +706 -0
- package/src/session-handler/model-utils.ts +217 -0
- package/src/session-handler/opencode-session-event-log.ts +130 -0
- package/src/session-handler/thread-runtime-state.ts +247 -0
- package/src/session-handler/thread-session-runtime.ts +4440 -0
- package/src/session-handler.ts +15 -0
- package/src/session-search.test.ts +50 -0
- package/src/session-search.ts +148 -0
- package/src/session-title-rename.test.ts +130 -0
- package/src/skill-filter.test.ts +83 -0
- package/src/skill-filter.ts +42 -0
- package/src/startup-service.ts +200 -0
- package/src/startup-time.e2e.test.ts +373 -0
- package/src/store.ts +139 -0
- package/src/subagent-rate-limit-plugin.ts +218 -0
- package/src/system-message.test.ts +710 -0
- package/src/system-message.ts +814 -0
- package/src/task-runner.ts +725 -0
- package/src/task-schedule.test.ts +84 -0
- package/src/task-schedule.ts +317 -0
- package/src/test-utils.ts +451 -0
- package/src/thinking-utils.ts +61 -0
- package/src/thread-message-queue.e2e.test.ts +1350 -0
- package/src/tools.ts +430 -0
- package/src/undici.d.ts +12 -0
- package/src/undo-redo.e2e.test.ts +209 -0
- package/src/unnest-code-blocks.test.ts +713 -0
- package/src/unnest-code-blocks.ts +185 -0
- package/src/upgrade.ts +185 -0
- package/src/utils.test.ts +155 -0
- package/src/utils.ts +265 -0
- package/src/voice-attachment.ts +51 -0
- package/src/voice-handler.ts +908 -0
- package/src/voice-message.e2e.test.ts +1255 -0
- package/src/voice.test.ts +281 -0
- package/src/voice.ts +638 -0
- package/src/wait-session.ts +273 -0
- package/src/websockify.ts +101 -0
- package/src/worker-types.ts +64 -0
- package/src/worktree-lifecycle.e2e.test.ts +396 -0
- package/src/worktree-utils.ts +4 -0
- package/src/worktrees.test.ts +489 -0
- package/src/worktrees.ts +1370 -0
- package/src/xml.test.ts +38 -0
- package/src/xml.ts +121 -0
- package/README.md +0 -142
- package/dist/cli.d.ts +0 -3
- package/dist/cli.d.ts.map +0 -1
- package/dist/cli.js.map +0 -1
- package/dist/config.d.ts +0 -39
- package/dist/config.d.ts.map +0 -1
- package/dist/config.js.map +0 -1
- package/dist/config.test.d.ts +0 -2
- package/dist/config.test.d.ts.map +0 -1
- package/dist/config.test.js +0 -202
- package/dist/config.test.js.map +0 -1
- package/dist/detect.d.ts +0 -9
- package/dist/detect.d.ts.map +0 -1
- package/dist/detect.js +0 -40
- package/dist/detect.js.map +0 -1
- package/dist/detect.test.d.ts +0 -2
- package/dist/detect.test.d.ts.map +0 -1
- package/dist/detect.test.js +0 -26
- package/dist/detect.test.js.map +0 -1
- package/dist/docker.d.ts +0 -7
- package/dist/docker.d.ts.map +0 -1
- package/dist/docker.js +0 -17
- package/dist/docker.js.map +0 -1
- package/dist/docker.test.d.ts +0 -2
- package/dist/docker.test.d.ts.map +0 -1
- package/dist/docker.test.js +0 -12
- package/dist/docker.test.js.map +0 -1
- package/dist/health.d.ts +0 -31
- package/dist/health.d.ts.map +0 -1
- package/dist/health.js +0 -117
- package/dist/health.js.map +0 -1
- package/dist/health.test.d.ts +0 -2
- package/dist/health.test.d.ts.map +0 -1
- package/dist/health.test.js +0 -52
- package/dist/health.test.js.map +0 -1
- package/dist/index.d.ts +0 -20
- package/dist/index.d.ts.map +0 -1
- package/dist/index.js +0 -15
- package/dist/index.js.map +0 -1
- package/dist/index.test.d.ts +0 -2
- package/dist/index.test.d.ts.map +0 -1
- package/dist/index.test.js +0 -8
- package/dist/index.test.js.map +0 -1
- package/dist/installer.d.ts +0 -10
- package/dist/installer.d.ts.map +0 -1
- package/dist/installer.js +0 -50
- package/dist/installer.js.map +0 -1
- package/dist/installer.test.d.ts +0 -2
- package/dist/installer.test.d.ts.map +0 -1
- package/dist/installer.test.js +0 -43
- package/dist/installer.test.js.map +0 -1
- package/dist/lifecycle.d.ts +0 -10
- package/dist/lifecycle.d.ts.map +0 -1
- package/dist/lifecycle.js +0 -45
- package/dist/lifecycle.js.map +0 -1
- package/dist/lifecycle.test.d.ts +0 -2
- package/dist/lifecycle.test.d.ts.map +0 -1
- package/dist/lifecycle.test.js +0 -20
- package/dist/lifecycle.test.js.map +0 -1
- package/dist/manifest.d.ts +0 -18
- package/dist/manifest.d.ts.map +0 -1
- package/dist/manifest.js +0 -30
- package/dist/manifest.js.map +0 -1
- package/dist/skills-baseline.d.ts +0 -7
- package/dist/skills-baseline.d.ts.map +0 -1
- package/dist/skills-baseline.js +0 -9
- package/dist/skills-baseline.js.map +0 -1
- package/dist/skills.d.ts +0 -110
- package/dist/skills.d.ts.map +0 -1
- package/dist/skills.js +0 -429
- package/dist/skills.js.map +0 -1
- package/dist/skills.test.d.ts +0 -2
- package/dist/skills.test.d.ts.map +0 -1
- package/dist/skills.test.js +0 -416
- package/dist/skills.test.js.map +0 -1
- package/dist/sync.d.ts +0 -10
- package/dist/sync.d.ts.map +0 -1
- package/dist/sync.js +0 -39
- package/dist/sync.js.map +0 -1
- package/dist/tenant.d.ts +0 -13
- package/dist/tenant.d.ts.map +0 -1
- package/dist/tenant.js +0 -105
- package/dist/tenant.js.map +0 -1
- package/dist/tenant.test.d.ts +0 -2
- package/dist/tenant.test.d.ts.map +0 -1
- package/dist/tenant.test.js +0 -37
- package/dist/tenant.test.js.map +0 -1
- package/src/config.test.ts +0 -237
- package/src/detect.test.ts +0 -29
- package/src/detect.ts +0 -52
- package/src/docker.test.ts +0 -12
- package/src/docker.ts +0 -23
- package/src/health.test.ts +0 -61
- package/src/health.ts +0 -158
- package/src/index.test.ts +0 -8
- package/src/index.ts +0 -62
- package/src/installer.test.ts +0 -52
- package/src/installer.ts +0 -62
- package/src/lifecycle.test.ts +0 -23
- package/src/lifecycle.ts +0 -49
- package/src/manifest.ts +0 -42
- package/src/skills-baseline.ts +0 -14
- package/src/skills.test.ts +0 -503
- package/src/skills.ts +0 -512
- package/src/sync.ts +0 -53
- package/src/tenant.test.ts +0 -49
- package/src/tenant.ts +0 -120
package/dist/upgrade.js
ADDED
|
@@ -0,0 +1,156 @@
|
|
|
1
|
+
// Otto self-upgrade utilities.
|
|
2
|
+
// Detects the package manager used to install otto, checks npm for newer versions,
|
|
3
|
+
// and runs the global upgrade command. Used by both CLI `otto upgrade` and
|
|
4
|
+
// the Discord `/upgrade-and-restart` command, plus background auto-upgrade on startup.
|
|
5
|
+
import fs from 'node:fs';
|
|
6
|
+
import { createRequire } from 'node:module';
|
|
7
|
+
import { createLogger, LogPrefix } from './logger.js';
|
|
8
|
+
import { execAsync } from './worktrees.js';
|
|
9
|
+
const logger = createLogger(LogPrefix.CLI);
|
|
10
|
+
// Detects which package manager globally installed otto, used to run the
|
|
11
|
+
// correct `<pm> i -g @otto-assistant/otto@latest` upgrade command.
|
|
12
|
+
//
|
|
13
|
+
// Detection order:
|
|
14
|
+
// 1. npm_config_user_agent — set by npx/bunx/pnpm dlx, reliable for those cases
|
|
15
|
+
// 2. Realpath of the running script — resolve symlinks and check if the path
|
|
16
|
+
// lives under a known PM global directory (e.g. ~/.bun, ~/Library/pnpm,
|
|
17
|
+
// /usr/local/lib/node_modules). Inspired by sindresorhus/global-directory.
|
|
18
|
+
// 3. process.versions.bun — if the runtime itself is Bun, likely bun ecosystem
|
|
19
|
+
// 4. Default to npm — safest fallback since npm is the most common global installer
|
|
20
|
+
export function detectPm() {
|
|
21
|
+
const ua = process.env.npm_config_user_agent;
|
|
22
|
+
if (ua?.startsWith('bun/')) {
|
|
23
|
+
return 'bun';
|
|
24
|
+
}
|
|
25
|
+
if (ua?.startsWith('pnpm/')) {
|
|
26
|
+
return 'pnpm';
|
|
27
|
+
}
|
|
28
|
+
if (ua?.startsWith('npm/')) {
|
|
29
|
+
return 'npm';
|
|
30
|
+
}
|
|
31
|
+
const scriptPath = resolveScriptRealpath();
|
|
32
|
+
if (scriptPath) {
|
|
33
|
+
const p = scriptPath.toLowerCase();
|
|
34
|
+
// bun global installs live under ~/.bun or $BUN_INSTALL
|
|
35
|
+
if (p.includes('.bun/') || p.includes('/bun/install/')) {
|
|
36
|
+
return 'bun';
|
|
37
|
+
}
|
|
38
|
+
// pnpm global installs live under ~/Library/pnpm, ~/.local/share/pnpm, or $PNPM_HOME
|
|
39
|
+
if (p.includes('/pnpm/')) {
|
|
40
|
+
return 'pnpm';
|
|
41
|
+
}
|
|
42
|
+
// npm global installs typically live under lib/node_modules/@otto-assistant without
|
|
43
|
+
// any pnpm or bun path segments, so if we reach here it's likely npm
|
|
44
|
+
}
|
|
45
|
+
if (process.versions.bun) {
|
|
46
|
+
return 'bun';
|
|
47
|
+
}
|
|
48
|
+
return 'npm';
|
|
49
|
+
}
|
|
50
|
+
function resolveScriptRealpath() {
|
|
51
|
+
try {
|
|
52
|
+
const script = process.argv[1];
|
|
53
|
+
if (!script) {
|
|
54
|
+
return null;
|
|
55
|
+
}
|
|
56
|
+
return fs.realpathSync(script);
|
|
57
|
+
}
|
|
58
|
+
catch {
|
|
59
|
+
return null;
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
function getCliPackageJson() {
|
|
63
|
+
const require = createRequire(import.meta.url);
|
|
64
|
+
return require('../package.json');
|
|
65
|
+
}
|
|
66
|
+
export function getCurrentVersion() {
|
|
67
|
+
return getCliPackageJson().version;
|
|
68
|
+
}
|
|
69
|
+
function getPackageName() {
|
|
70
|
+
return getCliPackageJson().name;
|
|
71
|
+
}
|
|
72
|
+
export async function getLatestNpmVersion() {
|
|
73
|
+
try {
|
|
74
|
+
const packageName = encodeURIComponent(getPackageName());
|
|
75
|
+
const res = await fetch(`https://registry.npmjs.org/${packageName}/latest`, {
|
|
76
|
+
signal: AbortSignal.timeout(15_000),
|
|
77
|
+
});
|
|
78
|
+
if (!res.ok) {
|
|
79
|
+
return null;
|
|
80
|
+
}
|
|
81
|
+
const data = (await res.json());
|
|
82
|
+
return data.version;
|
|
83
|
+
}
|
|
84
|
+
catch {
|
|
85
|
+
return null;
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
const LEGACY_PACKAGE_NAME = '@otto-assistant/bridge';
|
|
89
|
+
const CURRENT_PACKAGE_NAME = '@otto-assistant/otto';
|
|
90
|
+
// Returns true if running under the old @otto-assistant/bridge package name.
|
|
91
|
+
// When true, `upgrade()` will migrate to @otto-assistant/otto automatically.
|
|
92
|
+
export function isLegacyPackageName() {
|
|
93
|
+
return getPackageName() === LEGACY_PACKAGE_NAME;
|
|
94
|
+
}
|
|
95
|
+
// Returns the new version string if upgraded (or migrated), null if already up to date.
|
|
96
|
+
// If running as the legacy @otto-assistant/bridge package, migrates to
|
|
97
|
+
// @otto-assistant/otto by installing the new package name globally.
|
|
98
|
+
export async function upgrade() {
|
|
99
|
+
const current = getCurrentVersion();
|
|
100
|
+
// Migrate legacy package name: install @otto-assistant/otto and remove @otto-assistant/bridge
|
|
101
|
+
if (isLegacyPackageName()) {
|
|
102
|
+
const latest = await fetch(`https://registry.npmjs.org/${encodeURIComponent(CURRENT_PACKAGE_NAME)}/latest`, { signal: AbortSignal.timeout(15_000) })
|
|
103
|
+
.then((r) => (r.ok ? r.json() : null))
|
|
104
|
+
.then((d) => d?.version ?? null)
|
|
105
|
+
.catch(() => null);
|
|
106
|
+
const pm = detectPm();
|
|
107
|
+
logger.log(`Migrating from ${LEGACY_PACKAGE_NAME} to ${CURRENT_PACKAGE_NAME}${latest ? ` v${latest}` : ''} using ${pm}...`);
|
|
108
|
+
await execAsync(`${pm} i -g ${CURRENT_PACKAGE_NAME}@latest`, {
|
|
109
|
+
timeout: 120_000,
|
|
110
|
+
});
|
|
111
|
+
// Best-effort removal of the old package — non-fatal if it fails
|
|
112
|
+
await execAsync(`${pm} remove -g ${LEGACY_PACKAGE_NAME}`, {
|
|
113
|
+
timeout: 60_000,
|
|
114
|
+
}).catch(() => { });
|
|
115
|
+
return latest ?? current;
|
|
116
|
+
}
|
|
117
|
+
const latest = await getLatestNpmVersion();
|
|
118
|
+
if (!latest) {
|
|
119
|
+
throw new Error('Failed to check latest version from npm');
|
|
120
|
+
}
|
|
121
|
+
if (current === latest) {
|
|
122
|
+
return null;
|
|
123
|
+
}
|
|
124
|
+
const pm = detectPm();
|
|
125
|
+
const packageName = getPackageName();
|
|
126
|
+
logger.log(`Upgrading ${packageName} from v${current} to v${latest} using ${pm}...`);
|
|
127
|
+
await execAsync(`${pm} i -g ${packageName}@latest`, { timeout: 120_000 });
|
|
128
|
+
return latest;
|
|
129
|
+
}
|
|
130
|
+
// Fire-and-forget background upgrade check on bot startup.
|
|
131
|
+
// Only upgrades if a newer version is available. Errors are silently ignored.
|
|
132
|
+
export async function backgroundUpgradeOtto() {
|
|
133
|
+
try {
|
|
134
|
+
// Silently migrate legacy package name in the background
|
|
135
|
+
if (isLegacyPackageName()) {
|
|
136
|
+
const pm = detectPm();
|
|
137
|
+
logger.debug(`Background migration from ${LEGACY_PACKAGE_NAME} to ${CURRENT_PACKAGE_NAME}`);
|
|
138
|
+
await execAsync(`${pm} i -g ${CURRENT_PACKAGE_NAME}@latest`, { timeout: 120_000 });
|
|
139
|
+
await execAsync(`${pm} remove -g ${LEGACY_PACKAGE_NAME}`, { timeout: 60_000 }).catch(() => { });
|
|
140
|
+
return;
|
|
141
|
+
}
|
|
142
|
+
const current = getCurrentVersion();
|
|
143
|
+
const latest = await getLatestNpmVersion();
|
|
144
|
+
if (!latest || current === latest) {
|
|
145
|
+
return;
|
|
146
|
+
}
|
|
147
|
+
const pm = detectPm();
|
|
148
|
+
const packageName = getPackageName();
|
|
149
|
+
logger.debug(`Background ${packageName} upgrade started: v${current} -> v${latest}`);
|
|
150
|
+
await execAsync(`${pm} i -g ${packageName}@latest`, { timeout: 120_000 });
|
|
151
|
+
logger.debug(`Background ${packageName} upgrade completed: v${latest}`);
|
|
152
|
+
}
|
|
153
|
+
catch {
|
|
154
|
+
// silently ignored, non-critical
|
|
155
|
+
}
|
|
156
|
+
}
|
package/dist/utils.js
ADDED
|
@@ -0,0 +1,172 @@
|
|
|
1
|
+
// General utility functions for the bot.
|
|
2
|
+
// Includes Discord OAuth URL generation, array deduplication,
|
|
3
|
+
// abort error detection, and date/time formatting helpers.
|
|
4
|
+
import os from 'node:os';
|
|
5
|
+
// Use namespace import for CJS interop — discord.js is CJS and its named
|
|
6
|
+
// exports aren't detectable by all ESM loaders (e.g. tsx/esbuild) because
|
|
7
|
+
// discord.js uses tslib's __exportStar which is opaque to static analysis.
|
|
8
|
+
import * as discord from 'discord.js';
|
|
9
|
+
const { PermissionsBitField } = discord;
|
|
10
|
+
import * as errore from 'errore';
|
|
11
|
+
export function generateBotInstallUrl({ clientId, permissions = [
|
|
12
|
+
PermissionsBitField.Flags.ViewChannel,
|
|
13
|
+
PermissionsBitField.Flags.ManageChannels,
|
|
14
|
+
PermissionsBitField.Flags.SendMessages,
|
|
15
|
+
PermissionsBitField.Flags.SendMessagesInThreads,
|
|
16
|
+
PermissionsBitField.Flags.CreatePublicThreads,
|
|
17
|
+
PermissionsBitField.Flags.ManageThreads,
|
|
18
|
+
PermissionsBitField.Flags.ReadMessageHistory,
|
|
19
|
+
PermissionsBitField.Flags.AddReactions,
|
|
20
|
+
PermissionsBitField.Flags.ManageMessages,
|
|
21
|
+
PermissionsBitField.Flags.UseExternalEmojis,
|
|
22
|
+
PermissionsBitField.Flags.AttachFiles,
|
|
23
|
+
PermissionsBitField.Flags.Connect,
|
|
24
|
+
PermissionsBitField.Flags.Speak,
|
|
25
|
+
PermissionsBitField.Flags.ManageRoles,
|
|
26
|
+
PermissionsBitField.Flags.ManageEvents,
|
|
27
|
+
PermissionsBitField.Flags.CreateEvents,
|
|
28
|
+
], scopes = ['bot', 'applications.commands', 'identify', 'email'], guildId, disableGuildSelect = false, state, redirectUri, responseType, }) {
|
|
29
|
+
const permissionsBitField = new PermissionsBitField(permissions);
|
|
30
|
+
const permissionsValue = permissionsBitField.bitfield.toString();
|
|
31
|
+
const url = new URL('https://discord.com/api/oauth2/authorize');
|
|
32
|
+
url.searchParams.set('client_id', clientId);
|
|
33
|
+
url.searchParams.set('permissions', permissionsValue);
|
|
34
|
+
url.searchParams.set('scope', scopes.join(' '));
|
|
35
|
+
if (guildId) {
|
|
36
|
+
url.searchParams.set('guild_id', guildId);
|
|
37
|
+
}
|
|
38
|
+
if (disableGuildSelect) {
|
|
39
|
+
url.searchParams.set('disable_guild_select', 'true');
|
|
40
|
+
}
|
|
41
|
+
if (state) {
|
|
42
|
+
url.searchParams.set('state', state);
|
|
43
|
+
}
|
|
44
|
+
if (redirectUri) {
|
|
45
|
+
url.searchParams.set('redirect_uri', redirectUri);
|
|
46
|
+
}
|
|
47
|
+
if (responseType) {
|
|
48
|
+
url.searchParams.set('response_type', responseType);
|
|
49
|
+
}
|
|
50
|
+
return url.toString();
|
|
51
|
+
}
|
|
52
|
+
export const OTTO_GATEWAY_APP_ID = process.env.OTTO_GATEWAY_APP_ID || '1477605701202481173';
|
|
53
|
+
export const OTTO_WEBSITE_URL = process.env.OTTO_WEBSITE_URL || 'https://otto.dev';
|
|
54
|
+
export function getGatewayPlatformDisplayName({ gatewayPlatform, }) {
|
|
55
|
+
if (gatewayPlatform === 'telegram') {
|
|
56
|
+
return 'Telegram';
|
|
57
|
+
}
|
|
58
|
+
return 'Discord';
|
|
59
|
+
}
|
|
60
|
+
export function getGatewayInstallTargetName({ gatewayPlatform, }) {
|
|
61
|
+
if (gatewayPlatform === 'telegram') {
|
|
62
|
+
return 'chat';
|
|
63
|
+
}
|
|
64
|
+
return 'server';
|
|
65
|
+
}
|
|
66
|
+
export function shouldRegisterSlashCommands({ gatewayPlatform, }) {
|
|
67
|
+
return gatewayPlatform !== 'telegram';
|
|
68
|
+
}
|
|
69
|
+
export function generateDiscordInstallUrlForBot({ appId, mode, clientId, clientSecret, gatewayPlatform = 'discord', gatewayCallbackUrl, reachableUrl, }) {
|
|
70
|
+
if (mode !== 'gateway') {
|
|
71
|
+
return generateBotInstallUrl({ clientId: appId });
|
|
72
|
+
}
|
|
73
|
+
if (!clientId || !clientSecret) {
|
|
74
|
+
return new Error('Gateway credentials are missing from local database');
|
|
75
|
+
}
|
|
76
|
+
// In gateway mode, redirect to the website install route for the selected
|
|
77
|
+
// platform. Discord uses better-auth OAuth; Telegram uses manual token/chat
|
|
78
|
+
// submission in the install form.
|
|
79
|
+
// Use an otto-specific callback field name to avoid ambiguity with
|
|
80
|
+
// better-auth's own callbackURL state field.
|
|
81
|
+
const installPath = gatewayPlatform === 'telegram' ? '/telegram-install' : '/discord-install';
|
|
82
|
+
const url = new URL(`${OTTO_WEBSITE_URL}${installPath}`);
|
|
83
|
+
url.searchParams.set('clientId', clientId);
|
|
84
|
+
url.searchParams.set('clientSecret', clientSecret);
|
|
85
|
+
if (gatewayCallbackUrl) {
|
|
86
|
+
url.searchParams.set('ottoCallbackUrl', gatewayCallbackUrl);
|
|
87
|
+
}
|
|
88
|
+
if (reachableUrl) {
|
|
89
|
+
url.searchParams.set('reachableUrl', reachableUrl);
|
|
90
|
+
}
|
|
91
|
+
return url.toString();
|
|
92
|
+
}
|
|
93
|
+
export function buildOpenUrlCommand({ platform, url, }) {
|
|
94
|
+
if (platform === 'win32') {
|
|
95
|
+
return { command: 'cmd', args: ['/c', 'start', '', url] };
|
|
96
|
+
}
|
|
97
|
+
if (platform === 'darwin') {
|
|
98
|
+
return { command: 'open', args: [url] };
|
|
99
|
+
}
|
|
100
|
+
return { command: 'xdg-open', args: [url] };
|
|
101
|
+
}
|
|
102
|
+
export function deduplicateByKey(arr, keyFn) {
|
|
103
|
+
const seen = new Set();
|
|
104
|
+
return arr.filter((item) => {
|
|
105
|
+
const key = keyFn(item);
|
|
106
|
+
if (seen.has(key)) {
|
|
107
|
+
return false;
|
|
108
|
+
}
|
|
109
|
+
seen.add(key);
|
|
110
|
+
return true;
|
|
111
|
+
});
|
|
112
|
+
}
|
|
113
|
+
// Delegates to errore.isAbortError (walks cause chain for AbortError instances),
|
|
114
|
+
// then falls back to opencode server-specific abort patterns that aren't
|
|
115
|
+
// errore.AbortError but still represent aborted operations.
|
|
116
|
+
export function isAbortError(error) {
|
|
117
|
+
if (errore.isAbortError(error))
|
|
118
|
+
return true;
|
|
119
|
+
if (!(error instanceof Error))
|
|
120
|
+
return false;
|
|
121
|
+
return (error.name === 'MessageAbortedError' ||
|
|
122
|
+
error.message?.includes('aborted') === true);
|
|
123
|
+
}
|
|
124
|
+
const rtf = new Intl.RelativeTimeFormat('en', { numeric: 'auto' });
|
|
125
|
+
const TIME_DIVISIONS = [
|
|
126
|
+
{ amount: 60, name: 'seconds' },
|
|
127
|
+
{ amount: 60, name: 'minutes' },
|
|
128
|
+
{ amount: 24, name: 'hours' },
|
|
129
|
+
{ amount: 7, name: 'days' },
|
|
130
|
+
{ amount: 4.34524, name: 'weeks' },
|
|
131
|
+
{ amount: 12, name: 'months' },
|
|
132
|
+
{ amount: Number.POSITIVE_INFINITY, name: 'years' },
|
|
133
|
+
];
|
|
134
|
+
export function formatDistanceToNow(date) {
|
|
135
|
+
let duration = (date.getTime() - Date.now()) / 1000;
|
|
136
|
+
for (const division of TIME_DIVISIONS) {
|
|
137
|
+
if (Math.abs(duration) < division.amount) {
|
|
138
|
+
return rtf.format(Math.round(duration), division.name);
|
|
139
|
+
}
|
|
140
|
+
duration /= division.amount;
|
|
141
|
+
}
|
|
142
|
+
return rtf.format(Math.round(duration), 'years');
|
|
143
|
+
}
|
|
144
|
+
const dtf = new Intl.DateTimeFormat('en-US', {
|
|
145
|
+
month: 'short',
|
|
146
|
+
day: 'numeric',
|
|
147
|
+
year: 'numeric',
|
|
148
|
+
hour: 'numeric',
|
|
149
|
+
minute: '2-digit',
|
|
150
|
+
hour12: true,
|
|
151
|
+
});
|
|
152
|
+
export function formatDateTime(date) {
|
|
153
|
+
return dtf.format(date);
|
|
154
|
+
}
|
|
155
|
+
// Comprehensive ANSI escape sequence regex covering CSI, OSC, and related sequences.
|
|
156
|
+
// Valid string terminator sequences are BEL, ESC\, and 0x9c.
|
|
157
|
+
const ANSI_REGEX = (() => {
|
|
158
|
+
const ST = '(?:\\u0007|\\u001B\\u005C|\\u009C)';
|
|
159
|
+
const osc = `(?:\\u001B\\][\\s\\S]*?${ST})`;
|
|
160
|
+
const csi = '[\\u001B\\u009B][[\\]()#;?]*(?:\\d{1,4}(?:[;:]\\d{0,4})*)?[\\dA-PR-TZcf-nq-uy=><~]';
|
|
161
|
+
return new RegExp(`${osc}|${csi}`, 'g');
|
|
162
|
+
})();
|
|
163
|
+
export function stripAnsi(str) {
|
|
164
|
+
return str.replace(ANSI_REGEX, '');
|
|
165
|
+
}
|
|
166
|
+
export function abbreviatePath(fullPath) {
|
|
167
|
+
const home = os.homedir();
|
|
168
|
+
if (fullPath.startsWith(home)) {
|
|
169
|
+
return '~' + fullPath.slice(home.length);
|
|
170
|
+
}
|
|
171
|
+
return fullPath;
|
|
172
|
+
}
|
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
import { describe, expect, test } from 'vitest';
|
|
2
|
+
import { buildOpenUrlCommand, generateDiscordInstallUrlForBot, getGatewayInstallTargetName, getGatewayPlatformDisplayName, shouldRegisterSlashCommands, } from './utils.js';
|
|
3
|
+
describe('generateDiscordInstallUrlForBot', () => {
|
|
4
|
+
test('uses telegram install path in gateway mode', () => {
|
|
5
|
+
const result = generateDiscordInstallUrlForBot({
|
|
6
|
+
appId: 'app-id',
|
|
7
|
+
mode: 'gateway',
|
|
8
|
+
clientId: 'client-id',
|
|
9
|
+
clientSecret: 'client-secret',
|
|
10
|
+
gatewayPlatform: 'telegram',
|
|
11
|
+
});
|
|
12
|
+
expect(result).toBeTypeOf('string');
|
|
13
|
+
if (typeof result !== 'string') {
|
|
14
|
+
throw new Error('Expected install URL string');
|
|
15
|
+
}
|
|
16
|
+
const url = new URL(result);
|
|
17
|
+
expect(url.pathname).toBe('/telegram-install');
|
|
18
|
+
expect(url.searchParams.get('clientId')).toBe('client-id');
|
|
19
|
+
expect(url.searchParams.get('clientSecret')).toBe('client-secret');
|
|
20
|
+
});
|
|
21
|
+
test('uses discord install path by default in gateway mode', () => {
|
|
22
|
+
const result = generateDiscordInstallUrlForBot({
|
|
23
|
+
appId: 'app-id',
|
|
24
|
+
mode: 'gateway',
|
|
25
|
+
clientId: 'client-id',
|
|
26
|
+
clientSecret: 'client-secret',
|
|
27
|
+
});
|
|
28
|
+
expect(result).toBeTypeOf('string');
|
|
29
|
+
if (typeof result !== 'string') {
|
|
30
|
+
throw new Error('Expected install URL string');
|
|
31
|
+
}
|
|
32
|
+
const url = new URL(result);
|
|
33
|
+
expect(url.pathname).toBe('/discord-install');
|
|
34
|
+
expect(url.searchParams.get('clientId')).toBe('client-id');
|
|
35
|
+
expect(url.searchParams.get('clientSecret')).toBe('client-secret');
|
|
36
|
+
});
|
|
37
|
+
test('includes callback and reachable URL query params when provided', () => {
|
|
38
|
+
const callbackUrl = 'https://example.com/callback?foo=bar';
|
|
39
|
+
const reachableUrl = 'https://example-ngrok.test';
|
|
40
|
+
const result = generateDiscordInstallUrlForBot({
|
|
41
|
+
appId: 'app-id',
|
|
42
|
+
mode: 'gateway',
|
|
43
|
+
clientId: 'client-id',
|
|
44
|
+
clientSecret: 'client-secret',
|
|
45
|
+
gatewayPlatform: 'telegram',
|
|
46
|
+
gatewayCallbackUrl: callbackUrl,
|
|
47
|
+
reachableUrl,
|
|
48
|
+
});
|
|
49
|
+
expect(result).toBeTypeOf('string');
|
|
50
|
+
if (typeof result !== 'string') {
|
|
51
|
+
throw new Error('Expected install URL string');
|
|
52
|
+
}
|
|
53
|
+
const url = new URL(result);
|
|
54
|
+
expect(url.searchParams.get('ottoCallbackUrl')).toBe(callbackUrl);
|
|
55
|
+
expect(url.searchParams.get('reachableUrl')).toBe(reachableUrl);
|
|
56
|
+
});
|
|
57
|
+
test('returns error when gateway credentials are missing', () => {
|
|
58
|
+
const result = generateDiscordInstallUrlForBot({
|
|
59
|
+
appId: 'app-id',
|
|
60
|
+
mode: 'gateway',
|
|
61
|
+
clientId: null,
|
|
62
|
+
clientSecret: null,
|
|
63
|
+
gatewayPlatform: 'telegram',
|
|
64
|
+
});
|
|
65
|
+
expect(result).toBeInstanceOf(Error);
|
|
66
|
+
if (!(result instanceof Error)) {
|
|
67
|
+
throw new Error('Expected Error result');
|
|
68
|
+
}
|
|
69
|
+
expect(result.message).toContain('Gateway credentials are missing');
|
|
70
|
+
});
|
|
71
|
+
});
|
|
72
|
+
describe('gateway platform helpers', () => {
|
|
73
|
+
test('returns display names and install target names', () => {
|
|
74
|
+
expect(getGatewayPlatformDisplayName({ gatewayPlatform: 'discord' })).toBe('Discord');
|
|
75
|
+
expect(getGatewayPlatformDisplayName({ gatewayPlatform: 'telegram' })).toBe('Telegram');
|
|
76
|
+
expect(getGatewayInstallTargetName({ gatewayPlatform: 'discord' })).toBe('server');
|
|
77
|
+
expect(getGatewayInstallTargetName({ gatewayPlatform: 'telegram' })).toBe('chat');
|
|
78
|
+
});
|
|
79
|
+
test('registers slash commands only for discord gateway', () => {
|
|
80
|
+
expect(shouldRegisterSlashCommands({ gatewayPlatform: 'discord' })).toBe(true);
|
|
81
|
+
expect(shouldRegisterSlashCommands({ gatewayPlatform: 'telegram' })).toBe(false);
|
|
82
|
+
});
|
|
83
|
+
});
|
|
84
|
+
describe('buildOpenUrlCommand', () => {
|
|
85
|
+
test('uses cmd start on windows', () => {
|
|
86
|
+
const result = buildOpenUrlCommand({
|
|
87
|
+
platform: 'win32',
|
|
88
|
+
url: 'https://example.com/path?x=1',
|
|
89
|
+
});
|
|
90
|
+
expect(result).toMatchInlineSnapshot(`
|
|
91
|
+
{
|
|
92
|
+
"args": [
|
|
93
|
+
"/c",
|
|
94
|
+
"start",
|
|
95
|
+
"",
|
|
96
|
+
"https://example.com/path?x=1",
|
|
97
|
+
],
|
|
98
|
+
"command": "cmd",
|
|
99
|
+
}
|
|
100
|
+
`);
|
|
101
|
+
});
|
|
102
|
+
test('uses open on macOS', () => {
|
|
103
|
+
const result = buildOpenUrlCommand({
|
|
104
|
+
platform: 'darwin',
|
|
105
|
+
url: 'https://example.com/path?x=1',
|
|
106
|
+
});
|
|
107
|
+
expect(result).toMatchInlineSnapshot(`
|
|
108
|
+
{
|
|
109
|
+
"args": [
|
|
110
|
+
"https://example.com/path?x=1",
|
|
111
|
+
],
|
|
112
|
+
"command": "open",
|
|
113
|
+
}
|
|
114
|
+
`);
|
|
115
|
+
});
|
|
116
|
+
test('uses xdg-open on linux', () => {
|
|
117
|
+
const result = buildOpenUrlCommand({
|
|
118
|
+
platform: 'linux',
|
|
119
|
+
url: 'https://example.com/path?x=1',
|
|
120
|
+
});
|
|
121
|
+
expect(result).toMatchInlineSnapshot(`
|
|
122
|
+
{
|
|
123
|
+
"args": [
|
|
124
|
+
"https://example.com/path?x=1",
|
|
125
|
+
],
|
|
126
|
+
"command": "xdg-open",
|
|
127
|
+
}
|
|
128
|
+
`);
|
|
129
|
+
});
|
|
130
|
+
});
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
// Voice attachment detection helpers.
|
|
2
|
+
// Normalizes Discord attachment heuristics for voice-message detection so
|
|
3
|
+
// message routing, transcription, and empty-prompt guards all agree even when
|
|
4
|
+
// Discord omits contentType on uploaded audio attachments.
|
|
5
|
+
import path from 'node:path';
|
|
6
|
+
const VOICE_ATTACHMENT_EXTENSIONS = new Set([
|
|
7
|
+
'.m4a',
|
|
8
|
+
'.mp3',
|
|
9
|
+
'.mp4',
|
|
10
|
+
'.oga',
|
|
11
|
+
'.ogg',
|
|
12
|
+
'.opus',
|
|
13
|
+
'.wav',
|
|
14
|
+
]);
|
|
15
|
+
export function getVoiceAttachmentMatchReason(attachment) {
|
|
16
|
+
const contentType = attachment.contentType?.trim().toLowerCase() || '';
|
|
17
|
+
if (contentType.startsWith('audio/')) {
|
|
18
|
+
return `contentType:${contentType}`;
|
|
19
|
+
}
|
|
20
|
+
if (typeof attachment.duration === 'number' && attachment.duration > 0) {
|
|
21
|
+
return `duration:${attachment.duration}`;
|
|
22
|
+
}
|
|
23
|
+
if (attachment.waveform?.trim()) {
|
|
24
|
+
return 'waveform';
|
|
25
|
+
}
|
|
26
|
+
const extension = path.extname(attachment.name || '').toLowerCase();
|
|
27
|
+
if (VOICE_ATTACHMENT_EXTENSIONS.has(extension)) {
|
|
28
|
+
return `extension:${extension}`;
|
|
29
|
+
}
|
|
30
|
+
return null;
|
|
31
|
+
}
|
|
32
|
+
export function isVoiceAttachment(attachment) {
|
|
33
|
+
return getVoiceAttachmentMatchReason(attachment) !== null;
|
|
34
|
+
}
|