@otto-assistant/otto 0.1.2 → 0.7.16
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/bin.js +2 -0
- package/dist/agent-model.e2e.test.js +755 -0
- package/dist/ai-tool-to-genai.js +233 -0
- package/dist/ai-tool-to-genai.test.js +267 -0
- package/dist/ai-tool.js +6 -0
- package/dist/anthropic-account-identity.js +62 -0
- package/dist/anthropic-account-identity.test.js +38 -0
- package/dist/anthropic-auth-plugin.js +917 -0
- package/dist/anthropic-auth-state.js +303 -0
- package/dist/anthropic-auth-state.test.js +150 -0
- package/dist/bin.js +152 -0
- package/dist/btw-prefix-detection.js +17 -0
- package/dist/btw-prefix-detection.test.js +63 -0
- package/dist/channel-management.js +259 -0
- package/dist/cli-parsing.test.js +142 -0
- package/dist/cli-send-thread.e2e.test.js +353 -0
- package/dist/cli-telegram-options.test.js +99 -0
- package/dist/cli.js +4210 -568
- package/dist/commands/abort.js +65 -0
- package/dist/commands/action-buttons.js +245 -0
- package/dist/commands/add-dir.js +124 -0
- package/dist/commands/add-dir.test.js +126 -0
- package/dist/commands/add-project.js +113 -0
- package/dist/commands/agent.js +355 -0
- package/dist/commands/ask-question.js +320 -0
- package/dist/commands/ask-question.test.js +92 -0
- package/dist/commands/btw.js +121 -0
- package/dist/commands/cli-commands-group-a.test.js +728 -0
- package/dist/commands/cli-commands-group-b.test.js +695 -0
- package/dist/commands/compact.js +120 -0
- package/dist/commands/context-usage.js +140 -0
- package/dist/commands/create-new-project.js +130 -0
- package/dist/commands/diff.js +63 -0
- package/dist/commands/discord-commands-group-a.test.js +655 -0
- package/dist/commands/discord-commands-group-b.test.js +595 -0
- package/dist/commands/discord-commands-group-c.test.js +739 -0
- package/dist/commands/file-upload.js +275 -0
- package/dist/commands/fork-subagent.js +177 -0
- package/dist/commands/fork.js +262 -0
- package/dist/commands/gemini-apikey.js +70 -0
- package/dist/commands/login.js +893 -0
- package/dist/commands/mcp.js +239 -0
- package/dist/commands/memory-snapshot.js +24 -0
- package/dist/commands/mention-mode.js +44 -0
- package/dist/commands/merge-worktree.js +162 -0
- package/dist/commands/model-variant.js +369 -0
- package/dist/commands/model.js +798 -0
- package/dist/commands/new-worktree.js +465 -0
- package/dist/commands/paginated-select.js +57 -0
- package/dist/commands/permissions.js +274 -0
- package/dist/commands/queue.js +223 -0
- package/dist/commands/remove-project.js +115 -0
- package/dist/commands/restart-opencode-server.js +127 -0
- package/dist/commands/resume.js +149 -0
- package/dist/commands/run-command.js +79 -0
- package/dist/commands/screenshare.js +303 -0
- package/dist/commands/screenshare.test.js +20 -0
- package/dist/commands/session-id.js +78 -0
- package/dist/commands/session.js +176 -0
- package/dist/commands/share.js +80 -0
- package/dist/commands/tasks.js +205 -0
- package/dist/commands/thread-deletion-sync.js +50 -0
- package/dist/commands/types.js +2 -0
- package/dist/commands/undo-redo.js +305 -0
- package/dist/commands/unset-model.js +139 -0
- package/dist/commands/upgrade.js +48 -0
- package/dist/commands/user-command.js +155 -0
- package/dist/commands/verbosity.js +125 -0
- package/dist/commands/vscode.js +269 -0
- package/dist/commands/worktree-settings.js +43 -0
- package/dist/commands/worktrees.js +468 -0
- package/dist/condense-memory.js +33 -0
- package/dist/config.js +100 -255
- package/dist/context-awareness-plugin.js +340 -0
- package/dist/context-awareness-plugin.test.js +126 -0
- package/dist/critique-utils.js +95 -0
- package/dist/database.js +1355 -0
- package/dist/db.js +260 -0
- package/dist/db.test.js +138 -0
- package/dist/debounce-timeout.js +28 -0
- package/dist/debounced-process-flush.js +77 -0
- package/dist/discord-bot.js +1124 -0
- package/dist/discord-command-registration.js +567 -0
- package/dist/discord-urls.js +82 -0
- package/dist/discord-utils.js +616 -0
- package/dist/discord-utils.test.js +134 -0
- package/dist/errors.js +179 -0
- package/dist/escape-backticks.test.js +429 -0
- package/dist/event-stream-real-capture.e2e.test.js +533 -0
- package/dist/eventsource-parser.test.js +327 -0
- package/dist/exec-async.js +26 -0
- package/dist/external-opencode-sync.js +480 -0
- package/dist/format-tables.js +491 -0
- package/dist/format-tables.test.js +478 -0
- package/dist/forum-sync/config.js +79 -0
- package/dist/forum-sync/discord-operations.js +154 -0
- package/dist/forum-sync/index.js +5 -0
- package/dist/forum-sync/markdown.js +113 -0
- package/dist/forum-sync/sync-to-discord.js +417 -0
- package/dist/forum-sync/sync-to-files.js +190 -0
- package/dist/forum-sync/types.js +53 -0
- package/dist/forum-sync/watchers.js +307 -0
- package/dist/gateway-proxy-reconnect.e2e.test.js +394 -0
- package/dist/gateway-proxy.e2e.test.js +485 -0
- package/dist/genai-worker-wrapper.js +111 -0
- package/dist/genai-worker.js +311 -0
- package/dist/genai.js +232 -0
- package/dist/generated/browser.js +17 -0
- package/dist/generated/client.js +37 -0
- package/dist/generated/commonInputTypes.js +10 -0
- package/dist/generated/enums.js +58 -0
- package/dist/generated/internal/class.js +49 -0
- package/dist/generated/internal/prismaNamespace.js +254 -0
- package/dist/generated/internal/prismaNamespaceBrowser.js +224 -0
- package/dist/generated/models/bot_api_keys.js +1 -0
- package/dist/generated/models/bot_tokens.js +1 -0
- package/dist/generated/models/channel_agents.js +1 -0
- package/dist/generated/models/channel_directories.js +1 -0
- package/dist/generated/models/channel_mention_mode.js +1 -0
- package/dist/generated/models/channel_models.js +1 -0
- package/dist/generated/models/channel_verbosity.js +1 -0
- package/dist/generated/models/channel_worktrees.js +1 -0
- package/dist/generated/models/forum_sync_configs.js +1 -0
- package/dist/generated/models/global_models.js +1 -0
- package/dist/generated/models/ipc_requests.js +1 -0
- package/dist/generated/models/part_messages.js +1 -0
- package/dist/generated/models/scheduled_tasks.js +1 -0
- package/dist/generated/models/session_agents.js +1 -0
- package/dist/generated/models/session_events.js +1 -0
- package/dist/generated/models/session_models.js +1 -0
- package/dist/generated/models/session_start_sources.js +1 -0
- package/dist/generated/models/thread_sessions.js +1 -0
- package/dist/generated/models/thread_worktrees.js +1 -0
- package/dist/generated/models.js +1 -0
- package/dist/heap-monitor.js +122 -0
- package/dist/hrana-server.js +251 -0
- package/dist/hrana-server.test.js +370 -0
- package/dist/html-actions.js +123 -0
- package/dist/html-actions.test.js +70 -0
- package/dist/html-components.js +117 -0
- package/dist/html-components.test.js +34 -0
- package/dist/image-optimizer-plugin.js +153 -0
- package/dist/image-utils.js +112 -0
- package/dist/interaction-handler.js +420 -0
- package/dist/ipc-polling.js +327 -0
- package/dist/ipc-tools-plugin.js +193 -0
- package/dist/ipc-utils.js +18 -0
- package/dist/limit-heading-depth.js +25 -0
- package/dist/limit-heading-depth.test.js +105 -0
- package/dist/logger.js +171 -0
- package/dist/markdown.js +342 -0
- package/dist/markdown.test.js +264 -0
- package/dist/memory-overview-plugin.js +128 -0
- package/dist/message-finish-field.e2e.test.js +168 -0
- package/dist/message-formatting.js +415 -0
- package/dist/message-formatting.test.js +115 -0
- package/dist/message-preprocessing.js +359 -0
- package/dist/onboarding-tutorial.js +163 -0
- package/dist/onboarding-welcome.js +37 -0
- package/dist/openai-realtime.js +224 -0
- package/dist/opencode-command-detection.js +65 -0
- package/dist/opencode-command-detection.test.js +240 -0
- package/dist/opencode-command.js +131 -0
- package/dist/opencode-command.test.js +48 -0
- package/dist/opencode-interrupt-plugin.js +388 -0
- package/dist/opencode-interrupt-plugin.test.js +463 -0
- package/dist/opencode.js +1124 -0
- package/dist/otto/branding.js +22 -0
- package/dist/otto/index.js +21 -0
- package/dist/otto-digital-twin.e2e.test.js +161 -0
- package/dist/otto-opencode-plugin-loading.e2e.test.js +94 -0
- package/dist/otto-opencode-plugin.js +21 -0
- package/dist/otto-opencode-plugin.test.js +98 -0
- package/dist/parse-permission-rules.test.js +117 -0
- package/dist/patch-text-parser.js +97 -0
- package/dist/plugin-logger.js +68 -0
- package/dist/privacy-sanitizer.js +105 -0
- package/dist/queue-advanced-abort.e2e.test.js +293 -0
- package/dist/queue-advanced-action-buttons.e2e.test.js +206 -0
- package/dist/queue-advanced-e2e-setup.js +790 -0
- package/dist/queue-advanced-footer.e2e.test.js +481 -0
- package/dist/queue-advanced-model-switch.e2e.test.js +299 -0
- package/dist/queue-advanced-permissions-typing.e2e.test.js +179 -0
- package/dist/queue-advanced-question.e2e.test.js +261 -0
- package/dist/queue-advanced-typing-interrupt.e2e.test.js +114 -0
- package/dist/queue-advanced-typing.e2e.test.js +153 -0
- package/dist/queue-drain-after-interactive-ui.e2e.test.js +119 -0
- package/dist/queue-interrupt-drain.e2e.test.js +135 -0
- package/dist/queue-question-select-drain.e2e.test.js +256 -0
- package/dist/runtime-idle-sweeper.js +52 -0
- package/dist/runtime-lifecycle.e2e.test.js +514 -0
- package/dist/sentry.js +23 -0
- package/dist/session-handler/agent-utils.js +67 -0
- package/dist/session-handler/event-stream-state.js +475 -0
- package/dist/session-handler/event-stream-state.test.js +632 -0
- package/dist/session-handler/model-utils.js +147 -0
- package/dist/session-handler/opencode-session-event-log.js +94 -0
- package/dist/session-handler/thread-runtime-state.js +131 -0
- package/dist/session-handler/thread-session-runtime.js +3390 -0
- package/dist/session-handler.js +9 -0
- package/dist/session-search.js +100 -0
- package/dist/session-search.test.js +40 -0
- package/dist/session-title-rename.test.js +92 -0
- package/dist/skill-filter.js +31 -0
- package/dist/skill-filter.test.js +65 -0
- package/dist/startup-service.js +153 -0
- package/dist/startup-time.e2e.test.js +296 -0
- package/dist/store.js +19 -0
- package/dist/subagent-rate-limit-plugin.js +175 -0
- package/dist/system-message.js +702 -0
- package/dist/system-message.test.js +697 -0
- package/dist/task-runner.js +530 -0
- package/dist/task-schedule.js +213 -0
- package/dist/task-schedule.test.js +71 -0
- package/dist/test-utils.js +313 -0
- package/dist/thinking-utils.js +35 -0
- package/dist/thread-message-queue.e2e.test.js +1111 -0
- package/dist/tools.js +357 -0
- package/dist/undo-redo.e2e.test.js +161 -0
- package/dist/unnest-code-blocks.js +146 -0
- package/dist/unnest-code-blocks.test.js +673 -0
- package/dist/upgrade.js +156 -0
- package/dist/utils.js +172 -0
- package/dist/utils.test.js +130 -0
- package/dist/voice-attachment.js +34 -0
- package/dist/voice-handler.js +646 -0
- package/dist/voice-message.e2e.test.js +1021 -0
- package/dist/voice.js +456 -0
- package/dist/voice.test.js +235 -0
- package/dist/wait-session.js +171 -0
- package/dist/websockify.js +69 -0
- package/dist/worker-types.js +4 -0
- package/dist/worktree-lifecycle.e2e.test.js +311 -0
- package/dist/worktree-utils.js +3 -0
- package/dist/worktrees.js +991 -0
- package/dist/worktrees.test.js +415 -0
- package/dist/xml.js +92 -0
- package/dist/xml.test.js +32 -0
- package/package.json +90 -38
- package/schema.prisma +303 -0
- package/skills/batch/SKILL.md +87 -0
- package/skills/critique/SKILL.md +112 -0
- package/skills/egaki/SKILL.md +100 -0
- package/skills/errore/SKILL.md +647 -0
- package/skills/event-sourcing-state/SKILL.md +252 -0
- package/skills/goke/SKILL.md +38 -0
- package/skills/jitter/EDITOR.md +219 -0
- package/skills/jitter/EXPORT-INTERNALS.md +309 -0
- package/skills/jitter/SKILL.md +158 -0
- package/skills/jitter/jitter-clipboard.json +1042 -0
- package/skills/jitter/package.json +14 -0
- package/skills/jitter/tsconfig.json +15 -0
- package/skills/jitter/utils/actions.ts +212 -0
- package/skills/jitter/utils/export.ts +114 -0
- package/skills/jitter/utils/index.ts +141 -0
- package/skills/jitter/utils/snapshot.ts +154 -0
- package/skills/jitter/utils/traverse.ts +246 -0
- package/skills/jitter/utils/types.ts +279 -0
- package/skills/jitter/utils/wait.ts +133 -0
- package/skills/lintcn/SKILL.md +873 -0
- package/skills/manual-kimaki-upstream-adapt/SKILL.md +114 -0
- package/skills/new-skill/SKILL.md +237 -0
- package/skills/npm-package/SKILL.md +617 -0
- package/skills/opensrc/SKILL.md +78 -0
- package/skills/otto-publish/SKILL.md +61 -0
- package/skills/playwriter/SKILL.md +35 -0
- package/skills/profano/SKILL.md +16 -0
- package/skills/proxyman/SKILL.md +215 -0
- package/skills/security-review/SKILL.md +208 -0
- package/skills/sigillo/SKILL.md +101 -0
- package/skills/simplify/SKILL.md +58 -0
- package/skills/spiceflow/SKILL.md +28 -0
- package/skills/termcast/SKILL.md +945 -0
- package/skills/tuistory/SKILL.md +98 -0
- package/skills/usecomputer/SKILL.md +264 -0
- package/skills/x-articles/SKILL.md +554 -0
- package/skills/zele/SKILL.md +49 -0
- package/skills/zustand-centralized-state/SKILL.md +1004 -0
- package/src/agent-model.e2e.test.ts +979 -0
- package/src/ai-tool-to-genai.test.ts +296 -0
- package/src/ai-tool-to-genai.ts +283 -0
- package/src/ai-tool.ts +39 -0
- package/src/anthropic-account-identity.test.ts +52 -0
- package/src/anthropic-account-identity.ts +77 -0
- package/src/anthropic-auth-plugin.ts +1139 -0
- package/src/anthropic-auth-state.test.ts +187 -0
- package/src/anthropic-auth-state.ts +386 -0
- package/src/bin.ts +182 -0
- package/src/btw-prefix-detection.test.ts +73 -0
- package/src/btw-prefix-detection.ts +23 -0
- package/src/channel-management.ts +376 -0
- package/src/cli-parsing.test.ts +197 -0
- package/src/cli-send-thread.e2e.test.ts +463 -0
- package/src/cli-telegram-options.test.ts +114 -0
- package/src/cli.ts +5718 -580
- package/src/commands/abort.ts +89 -0
- package/src/commands/action-buttons.ts +364 -0
- package/src/commands/add-dir.test.ts +154 -0
- package/src/commands/add-dir.ts +175 -0
- package/src/commands/add-project.ts +149 -0
- package/src/commands/agent.ts +496 -0
- package/src/commands/ask-question.test.ts +111 -0
- package/src/commands/ask-question.ts +455 -0
- package/src/commands/btw.ts +184 -0
- package/src/commands/cli-commands-group-a.test.ts +837 -0
- package/src/commands/cli-commands-group-b.test.ts +800 -0
- package/src/commands/compact.ts +157 -0
- package/src/commands/context-usage.ts +199 -0
- package/src/commands/create-new-project.ts +190 -0
- package/src/commands/diff.ts +91 -0
- package/src/commands/discord-commands-group-a.test.ts +789 -0
- package/src/commands/discord-commands-group-b.test.ts +648 -0
- package/src/commands/discord-commands-group-c.test.ts +882 -0
- package/src/commands/file-upload.ts +389 -0
- package/src/commands/fork-subagent.ts +263 -0
- package/src/commands/fork.ts +386 -0
- package/src/commands/gemini-apikey.ts +104 -0
- package/src/commands/login.ts +1181 -0
- package/src/commands/mcp.ts +307 -0
- package/src/commands/memory-snapshot.ts +30 -0
- package/src/commands/mention-mode.ts +68 -0
- package/src/commands/merge-worktree.ts +226 -0
- package/src/commands/model-variant.ts +488 -0
- package/src/commands/model.ts +1082 -0
- package/src/commands/new-worktree.ts +645 -0
- package/src/commands/paginated-select.ts +81 -0
- package/src/commands/permissions.ts +397 -0
- package/src/commands/queue.ts +293 -0
- package/src/commands/remove-project.ts +155 -0
- package/src/commands/restart-opencode-server.ts +162 -0
- package/src/commands/resume.ts +230 -0
- package/src/commands/run-command.ts +123 -0
- package/src/commands/screenshare.test.ts +30 -0
- package/src/commands/screenshare.ts +366 -0
- package/src/commands/session-id.ts +109 -0
- package/src/commands/session.ts +227 -0
- package/src/commands/share.ts +106 -0
- package/src/commands/tasks.ts +293 -0
- package/src/commands/thread-deletion-sync.ts +80 -0
- package/src/commands/types.ts +25 -0
- package/src/commands/undo-redo.ts +386 -0
- package/src/commands/unset-model.ts +174 -0
- package/src/commands/upgrade.ts +59 -0
- package/src/commands/user-command.ts +198 -0
- package/src/commands/verbosity.ts +173 -0
- package/src/commands/vscode.ts +342 -0
- package/src/commands/worktree-settings.ts +70 -0
- package/src/commands/worktrees.ts +645 -0
- package/src/condense-memory.ts +36 -0
- package/src/config.ts +103 -339
- package/src/context-awareness-plugin.test.ts +144 -0
- package/src/context-awareness-plugin.ts +469 -0
- package/src/critique-utils.ts +139 -0
- package/src/database.ts +1949 -0
- package/src/db.test.ts +162 -0
- package/src/db.ts +295 -0
- package/src/debounce-timeout.ts +43 -0
- package/src/debounced-process-flush.ts +104 -0
- package/src/discord-bot.ts +1507 -0
- package/src/discord-command-registration.ts +752 -0
- package/src/discord-urls.ts +89 -0
- package/src/discord-utils.test.ts +153 -0
- package/src/discord-utils.ts +846 -0
- package/src/errors.ts +232 -0
- package/src/escape-backticks.test.ts +469 -0
- package/src/event-stream-real-capture.e2e.test.ts +692 -0
- package/src/eventsource-parser.test.ts +351 -0
- package/src/exec-async.ts +35 -0
- package/src/external-opencode-sync.ts +685 -0
- package/src/format-tables.test.ts +515 -0
- package/src/format-tables.ts +718 -0
- package/src/forum-sync/config.ts +92 -0
- package/src/forum-sync/discord-operations.ts +241 -0
- package/src/forum-sync/index.ts +9 -0
- package/src/forum-sync/markdown.ts +172 -0
- package/src/forum-sync/sync-to-discord.ts +595 -0
- package/src/forum-sync/sync-to-files.ts +294 -0
- package/src/forum-sync/types.ts +175 -0
- package/src/forum-sync/watchers.ts +454 -0
- package/src/gateway-proxy-reconnect.e2e.test.ts +523 -0
- package/src/gateway-proxy.e2e.test.ts +644 -0
- package/src/genai-worker-wrapper.ts +164 -0
- package/src/genai-worker.ts +386 -0
- package/src/genai.ts +321 -0
- package/src/generated/browser.ts +114 -0
- package/src/generated/client.ts +138 -0
- package/src/generated/commonInputTypes.ts +770 -0
- package/src/generated/enums.ts +98 -0
- package/src/generated/internal/class.ts +384 -0
- package/src/generated/internal/prismaNamespace.ts +2394 -0
- package/src/generated/internal/prismaNamespaceBrowser.ts +327 -0
- package/src/generated/models/bot_api_keys.ts +1288 -0
- package/src/generated/models/bot_tokens.ts +1700 -0
- package/src/generated/models/channel_agents.ts +1256 -0
- package/src/generated/models/channel_directories.ts +1859 -0
- package/src/generated/models/channel_mention_mode.ts +1300 -0
- package/src/generated/models/channel_models.ts +1288 -0
- package/src/generated/models/channel_verbosity.ts +1228 -0
- package/src/generated/models/channel_worktrees.ts +1300 -0
- package/src/generated/models/forum_sync_configs.ts +1452 -0
- package/src/generated/models/global_models.ts +1288 -0
- package/src/generated/models/ipc_requests.ts +1485 -0
- package/src/generated/models/part_messages.ts +1302 -0
- package/src/generated/models/scheduled_tasks.ts +2320 -0
- package/src/generated/models/session_agents.ts +1086 -0
- package/src/generated/models/session_events.ts +1439 -0
- package/src/generated/models/session_models.ts +1114 -0
- package/src/generated/models/session_start_sources.ts +1408 -0
- package/src/generated/models/thread_sessions.ts +1781 -0
- package/src/generated/models/thread_worktrees.ts +1356 -0
- package/src/generated/models.ts +30 -0
- package/src/heap-monitor.ts +152 -0
- package/src/hrana-server.test.ts +434 -0
- package/src/hrana-server.ts +299 -0
- package/src/html-actions.test.ts +87 -0
- package/src/html-actions.ts +174 -0
- package/src/html-components.test.ts +38 -0
- package/src/html-components.ts +181 -0
- package/src/image-optimizer-plugin.ts +194 -0
- package/src/image-utils.ts +149 -0
- package/src/interaction-handler.ts +610 -0
- package/src/ipc-polling.ts +427 -0
- package/src/ipc-tools-plugin.ts +236 -0
- package/src/ipc-utils.ts +29 -0
- package/src/limit-heading-depth.test.ts +116 -0
- package/src/limit-heading-depth.ts +26 -0
- package/src/logger.ts +215 -0
- package/src/markdown.test.ts +315 -0
- package/src/markdown.ts +410 -0
- package/src/memory-overview-plugin.ts +163 -0
- package/src/message-finish-field.e2e.test.ts +195 -0
- package/src/message-formatting.test.ts +126 -0
- package/src/message-formatting.ts +535 -0
- package/src/message-preprocessing.ts +488 -0
- package/src/onboarding-tutorial.ts +167 -0
- package/src/onboarding-welcome.ts +49 -0
- package/src/openai-realtime.ts +358 -0
- package/src/opencode-command-detection.test.ts +307 -0
- package/src/opencode-command-detection.ts +76 -0
- package/src/opencode-command.test.ts +70 -0
- package/src/opencode-command.ts +191 -0
- package/src/opencode-interrupt-plugin.test.ts +682 -0
- package/src/opencode-interrupt-plugin.ts +507 -0
- package/src/opencode.ts +1462 -0
- package/src/otto/branding.ts +23 -0
- package/src/otto/index.ts +22 -0
- package/src/otto-digital-twin.e2e.test.ts +199 -0
- package/src/otto-opencode-plugin-loading.e2e.test.ts +117 -0
- package/src/otto-opencode-plugin.test.ts +108 -0
- package/src/otto-opencode-plugin.ts +22 -0
- package/src/parse-permission-rules.test.ts +127 -0
- package/src/patch-text-parser.ts +107 -0
- package/src/plugin-logger.ts +84 -0
- package/src/privacy-sanitizer.ts +142 -0
- package/src/queue-advanced-abort.e2e.test.ts +382 -0
- package/src/queue-advanced-action-buttons.e2e.test.ts +268 -0
- package/src/queue-advanced-e2e-setup.ts +877 -0
- package/src/queue-advanced-footer.e2e.test.ts +591 -0
- package/src/queue-advanced-model-switch.e2e.test.ts +383 -0
- package/src/queue-advanced-permissions-typing.e2e.test.ts +246 -0
- package/src/queue-advanced-question.e2e.test.ts +316 -0
- package/src/queue-advanced-typing-interrupt.e2e.test.ts +146 -0
- package/src/queue-advanced-typing.e2e.test.ts +199 -0
- package/src/queue-drain-after-interactive-ui.e2e.test.ts +151 -0
- package/src/queue-interrupt-drain.e2e.test.ts +166 -0
- package/src/queue-question-select-drain.e2e.test.ts +327 -0
- package/src/runtime-idle-sweeper.ts +76 -0
- package/src/runtime-lifecycle.e2e.test.ts +651 -0
- package/src/schema.sql +174 -0
- package/src/sentry.ts +26 -0
- package/src/session-handler/agent-utils.ts +99 -0
- package/src/session-handler/event-stream-fixtures/real-session-action-buttons.jsonl +45 -0
- package/src/session-handler/event-stream-fixtures/real-session-footer-suppressed-on-pre-idle-interrupt.jsonl +40 -0
- package/src/session-handler/event-stream-fixtures/real-session-permission-external-file.jsonl +23 -0
- package/src/session-handler/event-stream-fixtures/real-session-task-normal.jsonl +22 -0
- package/src/session-handler/event-stream-fixtures/real-session-task-three-parallel-sleeps.jsonl +277 -0
- package/src/session-handler/event-stream-fixtures/real-session-task-user-interruption.jsonl +46 -0
- package/src/session-handler/event-stream-fixtures/session-abort-after-idle-race.jsonl +21 -0
- package/src/session-handler/event-stream-fixtures/session-concurrent-messages-serialized.jsonl +56 -0
- package/src/session-handler/event-stream-fixtures/session-explicit-abort.jsonl +44 -0
- package/src/session-handler/event-stream-fixtures/session-normal-completion.jsonl +29 -0
- package/src/session-handler/event-stream-fixtures/session-tool-call-noisy-stream.jsonl +29 -0
- package/src/session-handler/event-stream-fixtures/session-two-completions-same-session.jsonl +50 -0
- package/src/session-handler/event-stream-fixtures/session-user-interruption.jsonl +59 -0
- package/src/session-handler/event-stream-fixtures/session-voice-queued-followup.jsonl +52 -0
- package/src/session-handler/event-stream-state.test.ts +717 -0
- package/src/session-handler/event-stream-state.ts +706 -0
- package/src/session-handler/model-utils.ts +217 -0
- package/src/session-handler/opencode-session-event-log.ts +130 -0
- package/src/session-handler/thread-runtime-state.ts +247 -0
- package/src/session-handler/thread-session-runtime.ts +4440 -0
- package/src/session-handler.ts +15 -0
- package/src/session-search.test.ts +50 -0
- package/src/session-search.ts +148 -0
- package/src/session-title-rename.test.ts +130 -0
- package/src/skill-filter.test.ts +83 -0
- package/src/skill-filter.ts +42 -0
- package/src/startup-service.ts +200 -0
- package/src/startup-time.e2e.test.ts +373 -0
- package/src/store.ts +139 -0
- package/src/subagent-rate-limit-plugin.ts +218 -0
- package/src/system-message.test.ts +710 -0
- package/src/system-message.ts +814 -0
- package/src/task-runner.ts +725 -0
- package/src/task-schedule.test.ts +84 -0
- package/src/task-schedule.ts +317 -0
- package/src/test-utils.ts +451 -0
- package/src/thinking-utils.ts +61 -0
- package/src/thread-message-queue.e2e.test.ts +1350 -0
- package/src/tools.ts +430 -0
- package/src/undici.d.ts +12 -0
- package/src/undo-redo.e2e.test.ts +209 -0
- package/src/unnest-code-blocks.test.ts +713 -0
- package/src/unnest-code-blocks.ts +185 -0
- package/src/upgrade.ts +185 -0
- package/src/utils.test.ts +155 -0
- package/src/utils.ts +265 -0
- package/src/voice-attachment.ts +51 -0
- package/src/voice-handler.ts +908 -0
- package/src/voice-message.e2e.test.ts +1255 -0
- package/src/voice.test.ts +281 -0
- package/src/voice.ts +638 -0
- package/src/wait-session.ts +273 -0
- package/src/websockify.ts +101 -0
- package/src/worker-types.ts +64 -0
- package/src/worktree-lifecycle.e2e.test.ts +396 -0
- package/src/worktree-utils.ts +4 -0
- package/src/worktrees.test.ts +489 -0
- package/src/worktrees.ts +1370 -0
- package/src/xml.test.ts +38 -0
- package/src/xml.ts +121 -0
- package/README.md +0 -142
- package/dist/cli.d.ts +0 -3
- package/dist/cli.d.ts.map +0 -1
- package/dist/cli.js.map +0 -1
- package/dist/config.d.ts +0 -39
- package/dist/config.d.ts.map +0 -1
- package/dist/config.js.map +0 -1
- package/dist/config.test.d.ts +0 -2
- package/dist/config.test.d.ts.map +0 -1
- package/dist/config.test.js +0 -202
- package/dist/config.test.js.map +0 -1
- package/dist/detect.d.ts +0 -9
- package/dist/detect.d.ts.map +0 -1
- package/dist/detect.js +0 -40
- package/dist/detect.js.map +0 -1
- package/dist/detect.test.d.ts +0 -2
- package/dist/detect.test.d.ts.map +0 -1
- package/dist/detect.test.js +0 -26
- package/dist/detect.test.js.map +0 -1
- package/dist/docker.d.ts +0 -7
- package/dist/docker.d.ts.map +0 -1
- package/dist/docker.js +0 -17
- package/dist/docker.js.map +0 -1
- package/dist/docker.test.d.ts +0 -2
- package/dist/docker.test.d.ts.map +0 -1
- package/dist/docker.test.js +0 -12
- package/dist/docker.test.js.map +0 -1
- package/dist/health.d.ts +0 -31
- package/dist/health.d.ts.map +0 -1
- package/dist/health.js +0 -117
- package/dist/health.js.map +0 -1
- package/dist/health.test.d.ts +0 -2
- package/dist/health.test.d.ts.map +0 -1
- package/dist/health.test.js +0 -52
- package/dist/health.test.js.map +0 -1
- package/dist/index.d.ts +0 -20
- package/dist/index.d.ts.map +0 -1
- package/dist/index.js +0 -15
- package/dist/index.js.map +0 -1
- package/dist/index.test.d.ts +0 -2
- package/dist/index.test.d.ts.map +0 -1
- package/dist/index.test.js +0 -8
- package/dist/index.test.js.map +0 -1
- package/dist/installer.d.ts +0 -10
- package/dist/installer.d.ts.map +0 -1
- package/dist/installer.js +0 -50
- package/dist/installer.js.map +0 -1
- package/dist/installer.test.d.ts +0 -2
- package/dist/installer.test.d.ts.map +0 -1
- package/dist/installer.test.js +0 -43
- package/dist/installer.test.js.map +0 -1
- package/dist/lifecycle.d.ts +0 -10
- package/dist/lifecycle.d.ts.map +0 -1
- package/dist/lifecycle.js +0 -45
- package/dist/lifecycle.js.map +0 -1
- package/dist/lifecycle.test.d.ts +0 -2
- package/dist/lifecycle.test.d.ts.map +0 -1
- package/dist/lifecycle.test.js +0 -20
- package/dist/lifecycle.test.js.map +0 -1
- package/dist/manifest.d.ts +0 -18
- package/dist/manifest.d.ts.map +0 -1
- package/dist/manifest.js +0 -30
- package/dist/manifest.js.map +0 -1
- package/dist/skills-baseline.d.ts +0 -7
- package/dist/skills-baseline.d.ts.map +0 -1
- package/dist/skills-baseline.js +0 -9
- package/dist/skills-baseline.js.map +0 -1
- package/dist/skills.d.ts +0 -110
- package/dist/skills.d.ts.map +0 -1
- package/dist/skills.js +0 -429
- package/dist/skills.js.map +0 -1
- package/dist/skills.test.d.ts +0 -2
- package/dist/skills.test.d.ts.map +0 -1
- package/dist/skills.test.js +0 -416
- package/dist/skills.test.js.map +0 -1
- package/dist/sync.d.ts +0 -10
- package/dist/sync.d.ts.map +0 -1
- package/dist/sync.js +0 -39
- package/dist/sync.js.map +0 -1
- package/dist/tenant.d.ts +0 -13
- package/dist/tenant.d.ts.map +0 -1
- package/dist/tenant.js +0 -105
- package/dist/tenant.js.map +0 -1
- package/dist/tenant.test.d.ts +0 -2
- package/dist/tenant.test.d.ts.map +0 -1
- package/dist/tenant.test.js +0 -37
- package/dist/tenant.test.js.map +0 -1
- package/src/config.test.ts +0 -237
- package/src/detect.test.ts +0 -29
- package/src/detect.ts +0 -52
- package/src/docker.test.ts +0 -12
- package/src/docker.ts +0 -23
- package/src/health.test.ts +0 -61
- package/src/health.ts +0 -158
- package/src/index.test.ts +0 -8
- package/src/index.ts +0 -62
- package/src/installer.test.ts +0 -52
- package/src/installer.ts +0 -62
- package/src/lifecycle.test.ts +0 -23
- package/src/lifecycle.ts +0 -49
- package/src/manifest.ts +0 -42
- package/src/skills-baseline.ts +0 -14
- package/src/skills.test.ts +0 -503
- package/src/skills.ts +0 -512
- package/src/sync.ts +0 -53
- package/src/tenant.test.ts +0 -49
- package/src/tenant.ts +0 -120
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Otto branding overrides
|
|
3
|
+
*
|
|
4
|
+
* These values can be used throughout the distribution
|
|
5
|
+
* to customize the CLI appearance and messaging.
|
|
6
|
+
*/
|
|
7
|
+
export const OTTO_BRANDING = {
|
|
8
|
+
/** Display name shown in UI */
|
|
9
|
+
name: "Otto",
|
|
10
|
+
/** Short description */
|
|
11
|
+
tagline: "AI agent distribution",
|
|
12
|
+
/** Organization URL */
|
|
13
|
+
homepage: "https://github.com/otto-assistant/otto",
|
|
14
|
+
/** Version of the otto distribution (updated by CI) */
|
|
15
|
+
ottoVersion: "0.1.0",
|
|
16
|
+
};
|
|
17
|
+
/**
|
|
18
|
+
* Check if running as Otto distribution
|
|
19
|
+
*/
|
|
20
|
+
export function isOttoDistribution() {
|
|
21
|
+
return true; // This file only exists in the Otto fork
|
|
22
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Otto distribution extensions
|
|
3
|
+
*
|
|
4
|
+
* Custom branding, features, and integrations for the Otto AI distribution.
|
|
5
|
+
* This module is imported by the upstream entry point.
|
|
6
|
+
* Upstream code is NOT modified beyond a single import line at the end of cli.ts.
|
|
7
|
+
*/
|
|
8
|
+
export { OTTO_BRANDING } from "./branding.js";
|
|
9
|
+
/**
|
|
10
|
+
* Otto-specific configuration for the distribution
|
|
11
|
+
*/
|
|
12
|
+
export const OTTO_CONFIG = {
|
|
13
|
+
/** GitHub organization */
|
|
14
|
+
org: "otto-assistant",
|
|
15
|
+
/** Upstream repository */
|
|
16
|
+
upstream: "otto-assistant/otto",
|
|
17
|
+
/** Distribution name */
|
|
18
|
+
distributionName: "Otto",
|
|
19
|
+
/** Homepage */
|
|
20
|
+
homepage: "https://github.com/otto-assistant/otto",
|
|
21
|
+
};
|
|
@@ -0,0 +1,161 @@
|
|
|
1
|
+
// End-to-end test using discord-digital-twin + real Otto bot runtime.
|
|
2
|
+
// Verifies onboarding channel creation, message -> thread creation, and assistant reply.
|
|
3
|
+
import fs from 'node:fs';
|
|
4
|
+
import path from 'node:path';
|
|
5
|
+
import { expect, test } from 'vitest';
|
|
6
|
+
import { ChannelType, Client, GatewayIntentBits, Partials } from 'discord.js';
|
|
7
|
+
import { DigitalDiscord } from 'discord-digital-twin/src';
|
|
8
|
+
import { CachedOpencodeProviderProxy } from 'opencode-cached-provider';
|
|
9
|
+
import { setDataDir } from './config.js';
|
|
10
|
+
import { startDiscordBot } from './discord-bot.js';
|
|
11
|
+
import { setBotToken, initDatabase, closeDatabase, setChannelDirectory, } from './database.js';
|
|
12
|
+
import { startHranaServer, stopHranaServer } from './hrana-server.js';
|
|
13
|
+
import { cleanupTestSessions, chooseLockPort, initTestGitRepo } from './test-utils.js';
|
|
14
|
+
import { stopOpencodeServer } from './opencode.js';
|
|
15
|
+
const geminiApiKey = process.env['GEMINI_API_KEY'] ||
|
|
16
|
+
process.env['GOOGLE_GENERATIVE_AI_API_KEY'] ||
|
|
17
|
+
'';
|
|
18
|
+
const geminiModel = process.env['GEMINI_FLASH_MODEL'] || 'gemini-2.5-flash';
|
|
19
|
+
const e2eTest = geminiApiKey.length > 0 ? test : test.skip;
|
|
20
|
+
function createRunDirectories() {
|
|
21
|
+
const root = path.resolve(process.cwd(), 'tmp', 'otto-digital-twin-e2e');
|
|
22
|
+
fs.mkdirSync(root, { recursive: true });
|
|
23
|
+
const dataDir = fs.mkdtempSync(path.join(root, 'data-'));
|
|
24
|
+
const projectDirectory = path.join(root, 'project');
|
|
25
|
+
const providerCacheDbPath = path.join(root, 'provider-cache.db');
|
|
26
|
+
fs.mkdirSync(projectDirectory, { recursive: true });
|
|
27
|
+
initTestGitRepo(projectDirectory);
|
|
28
|
+
return {
|
|
29
|
+
root,
|
|
30
|
+
dataDir,
|
|
31
|
+
projectDirectory,
|
|
32
|
+
providerCacheDbPath,
|
|
33
|
+
};
|
|
34
|
+
}
|
|
35
|
+
function createDiscordJsClient({ restUrl }) {
|
|
36
|
+
return new Client({
|
|
37
|
+
intents: [
|
|
38
|
+
GatewayIntentBits.Guilds,
|
|
39
|
+
GatewayIntentBits.GuildMessages,
|
|
40
|
+
GatewayIntentBits.MessageContent,
|
|
41
|
+
GatewayIntentBits.GuildVoiceStates,
|
|
42
|
+
],
|
|
43
|
+
partials: [
|
|
44
|
+
Partials.Channel,
|
|
45
|
+
Partials.Message,
|
|
46
|
+
Partials.User,
|
|
47
|
+
Partials.ThreadMember,
|
|
48
|
+
],
|
|
49
|
+
rest: {
|
|
50
|
+
api: restUrl,
|
|
51
|
+
version: '10',
|
|
52
|
+
},
|
|
53
|
+
});
|
|
54
|
+
}
|
|
55
|
+
e2eTest('onboarding then message creates thread and assistant reply via digital twin', async () => {
|
|
56
|
+
const testStartTime = Date.now();
|
|
57
|
+
const directories = createRunDirectories();
|
|
58
|
+
const lockPort = chooseLockPort({ key: 'otto-digital-twin-e2e' });
|
|
59
|
+
process.env['OTTO_LOCK_PORT'] = String(lockPort);
|
|
60
|
+
setDataDir(directories.dataDir);
|
|
61
|
+
const proxy = new CachedOpencodeProviderProxy({
|
|
62
|
+
cacheDbPath: directories.providerCacheDbPath,
|
|
63
|
+
targetBaseUrl: 'https://generativelanguage.googleapis.com/v1beta',
|
|
64
|
+
apiKey: geminiApiKey,
|
|
65
|
+
cacheMethods: ['POST'],
|
|
66
|
+
});
|
|
67
|
+
const testUserId = '100000000000000777';
|
|
68
|
+
const textChannelId = '100000000000000778';
|
|
69
|
+
const digitalDiscordDbPath = path.join(directories.dataDir, 'digital-discord.db');
|
|
70
|
+
const discord = new DigitalDiscord({
|
|
71
|
+
guild: {
|
|
72
|
+
name: 'Otto E2E Guild',
|
|
73
|
+
ownerId: testUserId,
|
|
74
|
+
},
|
|
75
|
+
channels: [
|
|
76
|
+
{
|
|
77
|
+
id: textChannelId,
|
|
78
|
+
name: 'otto-e2e',
|
|
79
|
+
type: ChannelType.GuildText,
|
|
80
|
+
},
|
|
81
|
+
],
|
|
82
|
+
users: [
|
|
83
|
+
{
|
|
84
|
+
id: testUserId,
|
|
85
|
+
username: 'e2e-user',
|
|
86
|
+
},
|
|
87
|
+
],
|
|
88
|
+
dbUrl: `file:${digitalDiscordDbPath}`,
|
|
89
|
+
});
|
|
90
|
+
let botClient = null;
|
|
91
|
+
try {
|
|
92
|
+
await Promise.all([proxy.start(), discord.start()]);
|
|
93
|
+
const opencodeConfig = proxy.buildOpencodeConfig({
|
|
94
|
+
providerName: 'cached-google',
|
|
95
|
+
providerNpm: '@ai-sdk/google',
|
|
96
|
+
model: geminiModel,
|
|
97
|
+
smallModel: geminiModel,
|
|
98
|
+
});
|
|
99
|
+
fs.writeFileSync(path.join(directories.projectDirectory, 'opencode.json'), JSON.stringify(opencodeConfig, null, 2));
|
|
100
|
+
const dbPath = path.join(directories.dataDir, 'discord-sessions.db');
|
|
101
|
+
const hranaResult = await startHranaServer({ dbPath });
|
|
102
|
+
if (hranaResult instanceof Error) {
|
|
103
|
+
throw hranaResult;
|
|
104
|
+
}
|
|
105
|
+
process.env['OTTO_DB_URL'] = hranaResult;
|
|
106
|
+
await initDatabase();
|
|
107
|
+
await setBotToken(discord.botUserId, discord.botToken);
|
|
108
|
+
await setChannelDirectory({
|
|
109
|
+
channelId: textChannelId,
|
|
110
|
+
directory: directories.projectDirectory,
|
|
111
|
+
channelType: 'text',
|
|
112
|
+
});
|
|
113
|
+
botClient = createDiscordJsClient({ restUrl: discord.restUrl });
|
|
114
|
+
await startDiscordBot({
|
|
115
|
+
token: discord.botToken,
|
|
116
|
+
appId: discord.botUserId,
|
|
117
|
+
discordClient: botClient,
|
|
118
|
+
});
|
|
119
|
+
await discord.channel(textChannelId).user(testUserId).sendMessage({
|
|
120
|
+
content: 'Reply with exactly: otto digital twin ok',
|
|
121
|
+
});
|
|
122
|
+
const createdThread = await discord.channel(textChannelId).waitForThread({
|
|
123
|
+
timeout: 60_000,
|
|
124
|
+
predicate: (thread) => {
|
|
125
|
+
return thread.name === 'Reply with exactly: otto digital twin ok';
|
|
126
|
+
},
|
|
127
|
+
});
|
|
128
|
+
const botReply = await discord.thread(createdThread.id).waitForBotReply({
|
|
129
|
+
timeout: 120_000,
|
|
130
|
+
});
|
|
131
|
+
expect(createdThread.id.length).toBeGreaterThan(0);
|
|
132
|
+
expect(botReply.content.trim().length).toBeGreaterThan(0);
|
|
133
|
+
}
|
|
134
|
+
finally {
|
|
135
|
+
await cleanupTestSessions({
|
|
136
|
+
projectDirectory: directories.projectDirectory,
|
|
137
|
+
testStartTime,
|
|
138
|
+
});
|
|
139
|
+
if (botClient) {
|
|
140
|
+
botClient.destroy();
|
|
141
|
+
}
|
|
142
|
+
await stopOpencodeServer();
|
|
143
|
+
await Promise.all([
|
|
144
|
+
closeDatabase().catch(() => {
|
|
145
|
+
return;
|
|
146
|
+
}),
|
|
147
|
+
stopHranaServer().catch(() => {
|
|
148
|
+
return;
|
|
149
|
+
}),
|
|
150
|
+
proxy.stop().catch(() => {
|
|
151
|
+
return;
|
|
152
|
+
}),
|
|
153
|
+
discord.stop().catch(() => {
|
|
154
|
+
return;
|
|
155
|
+
}),
|
|
156
|
+
]);
|
|
157
|
+
delete process.env['OTTO_LOCK_PORT'];
|
|
158
|
+
delete process.env['OTTO_DB_URL'];
|
|
159
|
+
fs.rmSync(directories.dataDir, { recursive: true, force: true });
|
|
160
|
+
}
|
|
161
|
+
}, 360_000);
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
// E2e test for OpenCode plugin loading.
|
|
2
|
+
// Spawns `opencode serve` directly with our plugin in OPENCODE_CONFIG_CONTENT,
|
|
3
|
+
// waits for the health endpoint, then checks stderr for plugin errors.
|
|
4
|
+
// No Discord infrastructure needed — just the OpenCode server process.
|
|
5
|
+
import { spawn } from 'node:child_process';
|
|
6
|
+
import fs from 'node:fs';
|
|
7
|
+
import path from 'node:path';
|
|
8
|
+
import { fileURLToPath } from 'node:url';
|
|
9
|
+
import { test, expect } from 'vitest';
|
|
10
|
+
import { resolveOpencodeCommand } from './opencode.js';
|
|
11
|
+
import { getSpawnCommandAndArgs } from './opencode-command.js';
|
|
12
|
+
import { chooseLockPort } from './test-utils.js';
|
|
13
|
+
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
14
|
+
async function waitForHealth({ port, maxAttempts = 30, }) {
|
|
15
|
+
for (let i = 0; i < maxAttempts; i++) {
|
|
16
|
+
try {
|
|
17
|
+
const response = await fetch(`http://127.0.0.1:${port}/api/health`);
|
|
18
|
+
if (response.status < 500) {
|
|
19
|
+
return true;
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
catch {
|
|
23
|
+
// connection refused, retry
|
|
24
|
+
}
|
|
25
|
+
await new Promise((resolve) => {
|
|
26
|
+
setTimeout(resolve, 1000);
|
|
27
|
+
});
|
|
28
|
+
}
|
|
29
|
+
return false;
|
|
30
|
+
}
|
|
31
|
+
test('opencode server loads plugin without errors', async () => {
|
|
32
|
+
const projectDir = path.resolve(process.cwd(), 'tmp', 'plugin-loading-e2e');
|
|
33
|
+
fs.mkdirSync(projectDir, { recursive: true });
|
|
34
|
+
const port = chooseLockPort({ key: 'opencode-plugin-loading-e2e' });
|
|
35
|
+
const pluginPath = new URL('../src/otto-opencode-plugin.ts', import.meta.url).href;
|
|
36
|
+
const stderrLines = [];
|
|
37
|
+
const isolatedOpencodeRoot = path.join(projectDir, 'opencode-test-home');
|
|
38
|
+
const xdgDirectories = {
|
|
39
|
+
OPENCODE_CONFIG_DIR: path.join(isolatedOpencodeRoot, '.opencode-otto'),
|
|
40
|
+
XDG_CONFIG_HOME: path.join(isolatedOpencodeRoot, '.config'),
|
|
41
|
+
XDG_DATA_HOME: path.join(isolatedOpencodeRoot, '.local', 'share'),
|
|
42
|
+
XDG_CACHE_HOME: path.join(isolatedOpencodeRoot, '.cache'),
|
|
43
|
+
XDG_STATE_HOME: path.join(isolatedOpencodeRoot, '.local', 'state'),
|
|
44
|
+
};
|
|
45
|
+
fs.mkdirSync(isolatedOpencodeRoot, { recursive: true });
|
|
46
|
+
Object.values(xdgDirectories).forEach((directory) => {
|
|
47
|
+
fs.mkdirSync(directory, { recursive: true });
|
|
48
|
+
});
|
|
49
|
+
const { command, args, windowsVerbatimArguments, } = getSpawnCommandAndArgs({
|
|
50
|
+
resolvedCommand: resolveOpencodeCommand(),
|
|
51
|
+
baseArgs: ['serve', '--port', port.toString(), '--print-logs', '--log-level', 'DEBUG'],
|
|
52
|
+
});
|
|
53
|
+
const serverProcess = spawn(command, args, {
|
|
54
|
+
stdio: 'pipe',
|
|
55
|
+
cwd: projectDir,
|
|
56
|
+
windowsVerbatimArguments,
|
|
57
|
+
env: {
|
|
58
|
+
...process.env,
|
|
59
|
+
OPENCODE_CONFIG_CONTENT: JSON.stringify({
|
|
60
|
+
$schema: 'https://opencode.ai/config.json',
|
|
61
|
+
lsp: false,
|
|
62
|
+
formatter: false,
|
|
63
|
+
plugin: [pluginPath],
|
|
64
|
+
}),
|
|
65
|
+
OPENCODE_TEST_HOME: isolatedOpencodeRoot,
|
|
66
|
+
...xdgDirectories,
|
|
67
|
+
},
|
|
68
|
+
});
|
|
69
|
+
serverProcess.stderr?.on('data', (data) => {
|
|
70
|
+
stderrLines.push(...data.toString().split('\n').filter(Boolean));
|
|
71
|
+
});
|
|
72
|
+
try {
|
|
73
|
+
const healthy = await waitForHealth({ port });
|
|
74
|
+
expect(healthy).toBe(true);
|
|
75
|
+
// Check no plugin-related errors in stderr
|
|
76
|
+
const pluginErrorPatterns = [
|
|
77
|
+
/plugin.*error/i,
|
|
78
|
+
/failed to load plugin/i,
|
|
79
|
+
/cannot find module/i,
|
|
80
|
+
/ERR_MODULE_NOT_FOUND/i,
|
|
81
|
+
/plugin.*failed/i,
|
|
82
|
+
/plugin.*crash/i,
|
|
83
|
+
];
|
|
84
|
+
const errorLines = stderrLines.filter((line) => {
|
|
85
|
+
return pluginErrorPatterns.some((pattern) => {
|
|
86
|
+
return pattern.test(line);
|
|
87
|
+
});
|
|
88
|
+
});
|
|
89
|
+
expect(errorLines).toEqual([]);
|
|
90
|
+
}
|
|
91
|
+
finally {
|
|
92
|
+
serverProcess.kill('SIGTERM');
|
|
93
|
+
}
|
|
94
|
+
}, 60_000);
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
// OpenCode plugin entry point for Otto Discord bot.
|
|
2
|
+
// Each export is treated as a separate plugin by OpenCode's plugin loader.
|
|
3
|
+
// CRITICAL: never export utility functions from this file — only plugin
|
|
4
|
+
// initializer functions. OpenCode calls every export as a plugin.
|
|
5
|
+
//
|
|
6
|
+
// Plugins are split into focused modules:
|
|
7
|
+
// - ipc-tools-plugin: file upload + action buttons (IPC-based Discord tools)
|
|
8
|
+
// - context-awareness-plugin: branch, pwd, memory reminder, onboarding tutorial
|
|
9
|
+
// - memory-overview-plugin: frozen MEMORY.md heading overview per session
|
|
10
|
+
// - opencode-interrupt-plugin: interrupt queued messages at step boundaries
|
|
11
|
+
// - subagent-rate-limit-plugin: aborts only task subagents after rate limits
|
|
12
|
+
// - kitty-graphics-plugin: extract Kitty Graphics Protocol images from bash output
|
|
13
|
+
export { ipcToolsPlugin } from './ipc-tools-plugin.js';
|
|
14
|
+
export { contextAwarenessPlugin } from './context-awareness-plugin.js';
|
|
15
|
+
export { memoryOverviewPlugin } from './memory-overview-plugin.js';
|
|
16
|
+
export { interruptOpencodeSessionOnUserMessage } from './opencode-interrupt-plugin.js';
|
|
17
|
+
export { anthropicAuthPlugin } from './anthropic-auth-plugin.js';
|
|
18
|
+
export { imageOptimizerPlugin } from './image-optimizer-plugin.js';
|
|
19
|
+
export { subagentRateLimitPlugin } from './subagent-rate-limit-plugin.js';
|
|
20
|
+
export { kittyGraphicsPlugin } from 'kitty-graphics-agent';
|
|
21
|
+
export { injectionGuardInternal as injectionGuard } from 'opencode-injection-guard';
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
import { test, expect, describe } from 'vitest';
|
|
2
|
+
import { condenseMemoryMd } from './condense-memory.js';
|
|
3
|
+
describe('condenseMemoryMd', () => {
|
|
4
|
+
test('multiple headings with body content', () => {
|
|
5
|
+
const content = [
|
|
6
|
+
'# Project Overview',
|
|
7
|
+
'',
|
|
8
|
+
'This is a big project with many things.',
|
|
9
|
+
'It does X, Y, and Z.',
|
|
10
|
+
'',
|
|
11
|
+
'## Auth Architecture',
|
|
12
|
+
'',
|
|
13
|
+
'JWT tokens with 15min expiry.',
|
|
14
|
+
'Refresh tokens in httpOnly cookies.',
|
|
15
|
+
'Session stored in Redis.',
|
|
16
|
+
'',
|
|
17
|
+
'## User Preferences',
|
|
18
|
+
'',
|
|
19
|
+
'- kebab-case filenames',
|
|
20
|
+
'- errore-style errors',
|
|
21
|
+
'- no emojis',
|
|
22
|
+
'',
|
|
23
|
+
'### API Conventions',
|
|
24
|
+
'',
|
|
25
|
+
'All routes return { data, error }.',
|
|
26
|
+
'Use spiceflow for the server.',
|
|
27
|
+
'',
|
|
28
|
+
].join('\n');
|
|
29
|
+
expect(condenseMemoryMd(content)).toMatchInlineSnapshot(`
|
|
30
|
+
"1: # Project Overview
|
|
31
|
+
...
|
|
32
|
+
6: ## Auth Architecture
|
|
33
|
+
...
|
|
34
|
+
12: ## User Preferences
|
|
35
|
+
...
|
|
36
|
+
18: ### API Conventions
|
|
37
|
+
..."
|
|
38
|
+
`);
|
|
39
|
+
});
|
|
40
|
+
test('body text before first heading', () => {
|
|
41
|
+
const content = [
|
|
42
|
+
'Some preamble notes.',
|
|
43
|
+
'',
|
|
44
|
+
'# First Heading',
|
|
45
|
+
'',
|
|
46
|
+
'Content here.',
|
|
47
|
+
'',
|
|
48
|
+
].join('\n');
|
|
49
|
+
expect(condenseMemoryMd(content)).toMatchInlineSnapshot(`
|
|
50
|
+
"...
|
|
51
|
+
3: # First Heading
|
|
52
|
+
..."
|
|
53
|
+
`);
|
|
54
|
+
});
|
|
55
|
+
test('no headings at all', () => {
|
|
56
|
+
const content = 'Just some notes.\nMore notes.\n';
|
|
57
|
+
expect(condenseMemoryMd(content)).toMatchInlineSnapshot(`"..."`);
|
|
58
|
+
});
|
|
59
|
+
test('empty content', () => {
|
|
60
|
+
expect(condenseMemoryMd('')).toMatchInlineSnapshot(`""`);
|
|
61
|
+
});
|
|
62
|
+
test('consecutive headings without body', () => {
|
|
63
|
+
const content = [
|
|
64
|
+
'# H1',
|
|
65
|
+
'## H2',
|
|
66
|
+
'### H3',
|
|
67
|
+
'',
|
|
68
|
+
'Some body.',
|
|
69
|
+
'',
|
|
70
|
+
].join('\n');
|
|
71
|
+
expect(condenseMemoryMd(content)).toMatchInlineSnapshot(`
|
|
72
|
+
"1: # H1
|
|
73
|
+
2: ## H2
|
|
74
|
+
3: ### H3
|
|
75
|
+
..."
|
|
76
|
+
`);
|
|
77
|
+
});
|
|
78
|
+
test('heading with code block body', () => {
|
|
79
|
+
const content = [
|
|
80
|
+
'# Config',
|
|
81
|
+
'',
|
|
82
|
+
'```json',
|
|
83
|
+
'{ "key": "value" }',
|
|
84
|
+
'```',
|
|
85
|
+
'',
|
|
86
|
+
'## Notes',
|
|
87
|
+
'',
|
|
88
|
+
'Some text.',
|
|
89
|
+
'',
|
|
90
|
+
].join('\n');
|
|
91
|
+
expect(condenseMemoryMd(content)).toMatchInlineSnapshot(`
|
|
92
|
+
"1: # Config
|
|
93
|
+
...
|
|
94
|
+
7: ## Notes
|
|
95
|
+
..."
|
|
96
|
+
`);
|
|
97
|
+
});
|
|
98
|
+
});
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
// Tests for parsePermissionRules() from opencode.ts
|
|
2
|
+
import { describe, test, expect } from 'vitest';
|
|
3
|
+
import { parsePermissionRules } from './opencode.js';
|
|
4
|
+
describe('parsePermissionRules', () => {
|
|
5
|
+
test('simple tool:action format', () => {
|
|
6
|
+
expect(parsePermissionRules(['bash:deny'])).toMatchInlineSnapshot(`
|
|
7
|
+
[
|
|
8
|
+
{
|
|
9
|
+
"action": "deny",
|
|
10
|
+
"pattern": "*",
|
|
11
|
+
"permission": "bash",
|
|
12
|
+
},
|
|
13
|
+
]
|
|
14
|
+
`);
|
|
15
|
+
});
|
|
16
|
+
test('multiple rules', () => {
|
|
17
|
+
expect(parsePermissionRules(['bash:deny', 'edit:deny', 'read:allow'])).toMatchInlineSnapshot(`
|
|
18
|
+
[
|
|
19
|
+
{
|
|
20
|
+
"action": "deny",
|
|
21
|
+
"pattern": "*",
|
|
22
|
+
"permission": "bash",
|
|
23
|
+
},
|
|
24
|
+
{
|
|
25
|
+
"action": "deny",
|
|
26
|
+
"pattern": "*",
|
|
27
|
+
"permission": "edit",
|
|
28
|
+
},
|
|
29
|
+
{
|
|
30
|
+
"action": "allow",
|
|
31
|
+
"pattern": "*",
|
|
32
|
+
"permission": "read",
|
|
33
|
+
},
|
|
34
|
+
]
|
|
35
|
+
`);
|
|
36
|
+
});
|
|
37
|
+
test('tool:pattern:action format', () => {
|
|
38
|
+
expect(parsePermissionRules(['bash:git *:allow'])).toMatchInlineSnapshot(`
|
|
39
|
+
[
|
|
40
|
+
{
|
|
41
|
+
"action": "allow",
|
|
42
|
+
"pattern": "git *",
|
|
43
|
+
"permission": "bash",
|
|
44
|
+
},
|
|
45
|
+
]
|
|
46
|
+
`);
|
|
47
|
+
});
|
|
48
|
+
test('wildcard permission', () => {
|
|
49
|
+
expect(parsePermissionRules(['*:deny'])).toMatchInlineSnapshot(`
|
|
50
|
+
[
|
|
51
|
+
{
|
|
52
|
+
"action": "deny",
|
|
53
|
+
"pattern": "*",
|
|
54
|
+
"permission": "*",
|
|
55
|
+
},
|
|
56
|
+
]
|
|
57
|
+
`);
|
|
58
|
+
});
|
|
59
|
+
test('case-insensitive action', () => {
|
|
60
|
+
expect(parsePermissionRules(['bash:DENY', 'edit:Allow'])).toMatchInlineSnapshot(`
|
|
61
|
+
[
|
|
62
|
+
{
|
|
63
|
+
"action": "deny",
|
|
64
|
+
"pattern": "*",
|
|
65
|
+
"permission": "bash",
|
|
66
|
+
},
|
|
67
|
+
{
|
|
68
|
+
"action": "allow",
|
|
69
|
+
"pattern": "*",
|
|
70
|
+
"permission": "edit",
|
|
71
|
+
},
|
|
72
|
+
]
|
|
73
|
+
`);
|
|
74
|
+
});
|
|
75
|
+
test('trims whitespace', () => {
|
|
76
|
+
expect(parsePermissionRules([' bash : deny '])).toMatchInlineSnapshot(`
|
|
77
|
+
[
|
|
78
|
+
{
|
|
79
|
+
"action": "deny",
|
|
80
|
+
"pattern": "*",
|
|
81
|
+
"permission": "bash",
|
|
82
|
+
},
|
|
83
|
+
]
|
|
84
|
+
`);
|
|
85
|
+
});
|
|
86
|
+
test('skips invalid entries', () => {
|
|
87
|
+
expect(parsePermissionRules(['', 'bash', 'bash:invalid', ':deny'])).toMatchInlineSnapshot(`[]`);
|
|
88
|
+
});
|
|
89
|
+
test('handles non-array input defensively', () => {
|
|
90
|
+
expect(parsePermissionRules(undefined)).toMatchInlineSnapshot(`[]`);
|
|
91
|
+
expect(parsePermissionRules(null)).toMatchInlineSnapshot(`[]`);
|
|
92
|
+
expect(parsePermissionRules('bash:deny')).toMatchInlineSnapshot(`[]`);
|
|
93
|
+
expect(parsePermissionRules(123)).toMatchInlineSnapshot(`[]`);
|
|
94
|
+
});
|
|
95
|
+
test('handles non-string array items', () => {
|
|
96
|
+
expect(parsePermissionRules([123, null, 'bash:deny'])).toMatchInlineSnapshot(`
|
|
97
|
+
[
|
|
98
|
+
{
|
|
99
|
+
"action": "deny",
|
|
100
|
+
"pattern": "*",
|
|
101
|
+
"permission": "bash",
|
|
102
|
+
},
|
|
103
|
+
]
|
|
104
|
+
`);
|
|
105
|
+
});
|
|
106
|
+
test('ask action', () => {
|
|
107
|
+
expect(parsePermissionRules(['webfetch:ask'])).toMatchInlineSnapshot(`
|
|
108
|
+
[
|
|
109
|
+
{
|
|
110
|
+
"action": "ask",
|
|
111
|
+
"pattern": "*",
|
|
112
|
+
"permission": "webfetch",
|
|
113
|
+
},
|
|
114
|
+
]
|
|
115
|
+
`);
|
|
116
|
+
});
|
|
117
|
+
});
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
// Shared apply_patch text parsing utilities.
|
|
2
|
+
// Used by diff-patch-plugin.ts (file path extraction for snapshots) and
|
|
3
|
+
// message-formatting.ts (per-file addition/deletion counts for Discord display).
|
|
4
|
+
//
|
|
5
|
+
// The apply_patch tool uses three path header formats:
|
|
6
|
+
// *** Add File: path — new file
|
|
7
|
+
// *** Update File: path — existing file edit
|
|
8
|
+
// *** Delete File: path — file removal
|
|
9
|
+
// *** Move to: path — rename destination
|
|
10
|
+
// --- a/path / +++ b/path — unified diff headers (fallback)
|
|
11
|
+
/**
|
|
12
|
+
* Extract all file paths referenced in a patchText string.
|
|
13
|
+
* Handles custom apply_patch headers, move targets, and unified diff headers.
|
|
14
|
+
* Returns deduplicated paths.
|
|
15
|
+
*/
|
|
16
|
+
export function extractPatchFilePaths(patchText) {
|
|
17
|
+
const custom = [
|
|
18
|
+
...patchText.matchAll(/^\*\*\* (?:Add|Update|Delete) File:\s+(.+)$/gm),
|
|
19
|
+
].map((m) => {
|
|
20
|
+
return (m[1] ?? '').trim();
|
|
21
|
+
});
|
|
22
|
+
const moved = [
|
|
23
|
+
...patchText.matchAll(/^\*\*\* Move to:\s+(.+)$/gm),
|
|
24
|
+
].map((m) => {
|
|
25
|
+
return (m[1] ?? '').trim();
|
|
26
|
+
});
|
|
27
|
+
const unified = [
|
|
28
|
+
...patchText.matchAll(/^(?:---|\+\+\+) [ab]\/(.+)$/gm),
|
|
29
|
+
].map((m) => {
|
|
30
|
+
return (m[1] ?? '').trim();
|
|
31
|
+
});
|
|
32
|
+
const all = [...custom, ...moved, ...unified].filter(Boolean);
|
|
33
|
+
return all.filter((v, i, a) => {
|
|
34
|
+
return a.indexOf(v) === i;
|
|
35
|
+
});
|
|
36
|
+
}
|
|
37
|
+
/**
|
|
38
|
+
* Parse a patchText string and count additions/deletions per file.
|
|
39
|
+
* Patch format uses `*** Add File:`, `*** Update File:`, `*** Delete File:` headers,
|
|
40
|
+
* with diff lines prefixed by `+` (addition) or `-` (deletion) inside `@@` hunks.
|
|
41
|
+
*/
|
|
42
|
+
export function parsePatchFileCounts(patchText) {
|
|
43
|
+
const counts = new Map();
|
|
44
|
+
const lines = patchText.split('\n');
|
|
45
|
+
let currentFile = '';
|
|
46
|
+
let currentType = '';
|
|
47
|
+
let inHunk = false;
|
|
48
|
+
for (const line of lines) {
|
|
49
|
+
const addMatch = line.match(/^\*\*\* Add File:\s*(.+)/);
|
|
50
|
+
const updateMatch = line.match(/^\*\*\* Update File:\s*(.+)/);
|
|
51
|
+
const deleteMatch = line.match(/^\*\*\* Delete File:\s*(.+)/);
|
|
52
|
+
if (addMatch || updateMatch || deleteMatch) {
|
|
53
|
+
const match = addMatch || updateMatch || deleteMatch;
|
|
54
|
+
currentFile = (match?.[1] ?? '').trim();
|
|
55
|
+
currentType = addMatch ? 'add' : updateMatch ? 'update' : 'delete';
|
|
56
|
+
counts.set(currentFile, { additions: 0, deletions: 0 });
|
|
57
|
+
inHunk = false;
|
|
58
|
+
continue;
|
|
59
|
+
}
|
|
60
|
+
if (line.startsWith('@@')) {
|
|
61
|
+
inHunk = true;
|
|
62
|
+
continue;
|
|
63
|
+
}
|
|
64
|
+
if (line.startsWith('*** ')) {
|
|
65
|
+
inHunk = false;
|
|
66
|
+
continue;
|
|
67
|
+
}
|
|
68
|
+
if (!currentFile) {
|
|
69
|
+
continue;
|
|
70
|
+
}
|
|
71
|
+
const entry = counts.get(currentFile);
|
|
72
|
+
if (!entry) {
|
|
73
|
+
continue;
|
|
74
|
+
}
|
|
75
|
+
if (currentType === 'add') {
|
|
76
|
+
// all content lines in Add File are additions
|
|
77
|
+
if (line.length > 0 && !line.startsWith('*** ')) {
|
|
78
|
+
entry.additions++;
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
else if (currentType === 'delete') {
|
|
82
|
+
// all content lines in Delete File are deletions
|
|
83
|
+
if (line.length > 0 && !line.startsWith('*** ')) {
|
|
84
|
+
entry.deletions++;
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
else if (inHunk) {
|
|
88
|
+
if (line.startsWith('+')) {
|
|
89
|
+
entry.additions++;
|
|
90
|
+
}
|
|
91
|
+
else if (line.startsWith('-')) {
|
|
92
|
+
entry.deletions++;
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
return counts;
|
|
97
|
+
}
|