@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,468 @@
|
|
|
1
|
+
// /worktrees command — list all git worktrees for the current channel's project.
|
|
2
|
+
// Uses `git worktree list --porcelain` as source of truth, enriched with
|
|
3
|
+
// DB metadata (thread link, created_at) when available. Shows otto-created,
|
|
4
|
+
// opencode-created, and manually created worktrees in a single table.
|
|
5
|
+
// Renders a markdown table that the CV2 pipeline auto-formats for Discord,
|
|
6
|
+
// including HTML-backed action buttons for deletable worktrees.
|
|
7
|
+
import { ButtonInteraction, ChatInputCommandInteraction, ChannelType, ComponentType, MessageFlags, } from 'discord.js';
|
|
8
|
+
import { deleteThreadWorktree, } from '../database.js';
|
|
9
|
+
import { getPrisma } from '../db.js';
|
|
10
|
+
import { splitTablesFromMarkdown } from '../format-tables.js';
|
|
11
|
+
import { buildHtmlActionCustomId, cancelHtmlActionsForOwner, registerHtmlAction, } from '../html-actions.js';
|
|
12
|
+
import * as errore from 'errore';
|
|
13
|
+
import crypto from 'node:crypto';
|
|
14
|
+
import { GitCommandError } from '../errors.js';
|
|
15
|
+
import { resolveWorkingDirectory } from '../discord-utils.js';
|
|
16
|
+
import { deleteWorktree, git, getDefaultBranch, listGitWorktrees, } from '../worktrees.js';
|
|
17
|
+
import path from 'node:path';
|
|
18
|
+
// Extracts the git stderr from a deleteWorktree error via errore.findCause.
|
|
19
|
+
// Chain: Error { cause: GitCommandError { cause: CommandError { stderr } } }.
|
|
20
|
+
export function extractGitStderr(error) {
|
|
21
|
+
const gitErr = errore.findCause(error, GitCommandError);
|
|
22
|
+
const stderr = gitErr?.cause?.stderr?.trim();
|
|
23
|
+
if (stderr && stderr.length > 0) {
|
|
24
|
+
return stderr;
|
|
25
|
+
}
|
|
26
|
+
return undefined;
|
|
27
|
+
}
|
|
28
|
+
export function formatTimeAgo(date) {
|
|
29
|
+
const diffMs = Date.now() - date.getTime();
|
|
30
|
+
if (diffMs < 0) {
|
|
31
|
+
return 'just now';
|
|
32
|
+
}
|
|
33
|
+
const totalSeconds = Math.floor(diffMs / 1000);
|
|
34
|
+
if (totalSeconds < 60) {
|
|
35
|
+
return `${totalSeconds}s ago`;
|
|
36
|
+
}
|
|
37
|
+
const totalMinutes = Math.floor(totalSeconds / 60);
|
|
38
|
+
if (totalMinutes < 60) {
|
|
39
|
+
return `${totalMinutes}m ago`;
|
|
40
|
+
}
|
|
41
|
+
const hours = Math.floor(totalMinutes / 60);
|
|
42
|
+
const minutes = totalMinutes % 60;
|
|
43
|
+
if (hours < 24) {
|
|
44
|
+
return minutes > 0 ? `${hours}h ${minutes}m ago` : `${hours}h ago`;
|
|
45
|
+
}
|
|
46
|
+
const days = Math.floor(hours / 24);
|
|
47
|
+
const remainingHours = hours % 24;
|
|
48
|
+
return remainingHours > 0 ? `${days}d ${remainingHours}h ago` : `${days}d ago`;
|
|
49
|
+
}
|
|
50
|
+
// Stable button ID derived from directory path via sha1 hash.
|
|
51
|
+
// Avoids collisions that truncated path suffixes can cause.
|
|
52
|
+
function worktreeButtonKey(directory) {
|
|
53
|
+
return crypto.createHash('sha1').update(directory).digest('hex').slice(0, 12);
|
|
54
|
+
}
|
|
55
|
+
// 5s timeout per git call — prevents hangs from deleted dirs, git locks, slow disks.
|
|
56
|
+
// Returns null on timeout/error so the table shows "unknown" for that worktree.
|
|
57
|
+
const GIT_CMD_TIMEOUT = 5_000;
|
|
58
|
+
const GLOBAL_TIMEOUT = 10_000;
|
|
59
|
+
// Detect worktree source from branch name and directory path.
|
|
60
|
+
// opencode/otto-* and legacy opencode/otto-* branches → otto,
|
|
61
|
+
// opencode worktree paths → opencode, else manual.
|
|
62
|
+
function detectWorktreeSource({ branch, directory, }) {
|
|
63
|
+
if (branch?.startsWith('opencode/otto-') || branch?.startsWith('opencode/otto-')) {
|
|
64
|
+
return 'otto';
|
|
65
|
+
}
|
|
66
|
+
// opencode stores worktrees under ~/.local/share/opencode/worktree/
|
|
67
|
+
if (directory.includes('/opencode/worktree/')) {
|
|
68
|
+
return 'opencode';
|
|
69
|
+
}
|
|
70
|
+
return 'manual';
|
|
71
|
+
}
|
|
72
|
+
// Checks dirty state and commits ahead of default branch in parallel.
|
|
73
|
+
// Returns null when the directory is missing / git commands fail / timeout.
|
|
74
|
+
async function getWorktreeGitStatus({ directory, defaultBranch, }) {
|
|
75
|
+
try {
|
|
76
|
+
// Use raw git calls so errors/timeouts are visible — isDirty() swallows
|
|
77
|
+
// errors and returns false, which would render "merged" instead of "unknown".
|
|
78
|
+
const [statusResult, aheadResult] = await Promise.all([
|
|
79
|
+
git(directory, 'status --porcelain', { timeout: GIT_CMD_TIMEOUT }),
|
|
80
|
+
git(directory, `rev-list --count "${defaultBranch}..HEAD"`, {
|
|
81
|
+
timeout: GIT_CMD_TIMEOUT,
|
|
82
|
+
}),
|
|
83
|
+
]);
|
|
84
|
+
if (statusResult instanceof Error || aheadResult instanceof Error) {
|
|
85
|
+
return null;
|
|
86
|
+
}
|
|
87
|
+
const aheadCount = parseInt(aheadResult, 10);
|
|
88
|
+
if (!Number.isFinite(aheadCount)) {
|
|
89
|
+
return null;
|
|
90
|
+
}
|
|
91
|
+
return { dirty: statusResult.length > 0, aheadCount };
|
|
92
|
+
}
|
|
93
|
+
catch {
|
|
94
|
+
return null;
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
function buildWorktreeTable({ rows, gitStatuses, guildId, }) {
|
|
98
|
+
const header = '| Source | Name | Status | Created | Folder | Action |';
|
|
99
|
+
const separator = '|---|---|---|---|---|---|';
|
|
100
|
+
const tableRows = rows.map((row, i) => {
|
|
101
|
+
const sourceCell = (() => {
|
|
102
|
+
if (row.threadId && row.guildId) {
|
|
103
|
+
const threadLink = `[${row.source}](https://discord.com/channels/${row.guildId}/${row.threadId})`;
|
|
104
|
+
return threadLink;
|
|
105
|
+
}
|
|
106
|
+
return row.source;
|
|
107
|
+
})();
|
|
108
|
+
const name = row.name;
|
|
109
|
+
const gs = gitStatuses[i] ?? null;
|
|
110
|
+
const status = (() => {
|
|
111
|
+
if (row.dbStatus !== 'ready') {
|
|
112
|
+
return row.dbStatus;
|
|
113
|
+
}
|
|
114
|
+
if (row.locked) {
|
|
115
|
+
return 'locked';
|
|
116
|
+
}
|
|
117
|
+
if (row.prunable) {
|
|
118
|
+
return 'prunable';
|
|
119
|
+
}
|
|
120
|
+
if (!gs) {
|
|
121
|
+
return 'unknown';
|
|
122
|
+
}
|
|
123
|
+
const parts = [];
|
|
124
|
+
if (gs.dirty) {
|
|
125
|
+
parts.push('dirty');
|
|
126
|
+
}
|
|
127
|
+
if (gs.aheadCount > 0) {
|
|
128
|
+
parts.push(`${gs.aheadCount} ahead`);
|
|
129
|
+
}
|
|
130
|
+
else {
|
|
131
|
+
parts.push('merged');
|
|
132
|
+
}
|
|
133
|
+
return parts.join(', ');
|
|
134
|
+
})();
|
|
135
|
+
const created = row.createdAt ? formatTimeAgo(row.createdAt) : '-';
|
|
136
|
+
const folder = row.directory;
|
|
137
|
+
const action = buildActionCell({ row, gitStatus: gs });
|
|
138
|
+
return `| ${sourceCell} | ${name} | ${status} | ${created} | ${folder} | ${action} |`;
|
|
139
|
+
});
|
|
140
|
+
return [header, separator, ...tableRows].join('\n');
|
|
141
|
+
}
|
|
142
|
+
function buildActionCell({ row, gitStatus, }) {
|
|
143
|
+
if (!canDeleteWorktree({ row, gitStatus })) {
|
|
144
|
+
return '-';
|
|
145
|
+
}
|
|
146
|
+
return buildDeleteButtonHtml({
|
|
147
|
+
buttonId: `del-wt-${worktreeButtonKey(row.directory)}`,
|
|
148
|
+
});
|
|
149
|
+
}
|
|
150
|
+
function buildDeleteButtonHtml({ buttonId, }) {
|
|
151
|
+
return `<button id="${buttonId}" variant="secondary">Delete</button>`;
|
|
152
|
+
}
|
|
153
|
+
function canDeleteWorktree({ row, gitStatus, }) {
|
|
154
|
+
if (row.dbStatus !== 'ready') {
|
|
155
|
+
return false;
|
|
156
|
+
}
|
|
157
|
+
if (row.locked) {
|
|
158
|
+
return false;
|
|
159
|
+
}
|
|
160
|
+
if (!gitStatus) {
|
|
161
|
+
return false;
|
|
162
|
+
}
|
|
163
|
+
if (gitStatus.dirty) {
|
|
164
|
+
return false;
|
|
165
|
+
}
|
|
166
|
+
return gitStatus.aheadCount === 0;
|
|
167
|
+
}
|
|
168
|
+
// Resolves git statuses for all worktrees within a single global deadline.
|
|
169
|
+
async function resolveGitStatuses({ rows, projectDirectory, timeout, }) {
|
|
170
|
+
const nullFallback = rows.map(() => null);
|
|
171
|
+
let timer;
|
|
172
|
+
const deadline = new Promise((resolve) => {
|
|
173
|
+
timer = setTimeout(() => {
|
|
174
|
+
resolve(nullFallback);
|
|
175
|
+
}, timeout);
|
|
176
|
+
});
|
|
177
|
+
const work = (async () => {
|
|
178
|
+
const defaultBranch = await getDefaultBranch(projectDirectory, {
|
|
179
|
+
timeout: GIT_CMD_TIMEOUT,
|
|
180
|
+
});
|
|
181
|
+
return Promise.all(rows.map((row) => {
|
|
182
|
+
if (row.dbStatus !== 'ready' || row.locked || row.prunable) {
|
|
183
|
+
return null;
|
|
184
|
+
}
|
|
185
|
+
return getWorktreeGitStatus({ directory: row.directory, defaultBranch });
|
|
186
|
+
}));
|
|
187
|
+
})();
|
|
188
|
+
try {
|
|
189
|
+
return await Promise.race([work, deadline]);
|
|
190
|
+
}
|
|
191
|
+
finally {
|
|
192
|
+
clearTimeout(timer);
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
// Merge git worktrees with DB metadata into unified WorktreeRows.
|
|
196
|
+
// Git is the source of truth for what exists on disk. DB rows that aren't
|
|
197
|
+
// in the git list (pending/error) are appended at the end.
|
|
198
|
+
async function buildWorktreeRows({ projectDirectory, gitWorktrees, }) {
|
|
199
|
+
const prisma = await getPrisma();
|
|
200
|
+
const dbWorktrees = await prisma.thread_worktrees.findMany({
|
|
201
|
+
where: { project_directory: projectDirectory },
|
|
202
|
+
});
|
|
203
|
+
// Index DB worktrees by directory for fast lookup
|
|
204
|
+
const dbByDirectory = new Map();
|
|
205
|
+
for (const dbWt of dbWorktrees) {
|
|
206
|
+
if (dbWt.worktree_directory) {
|
|
207
|
+
dbByDirectory.set(dbWt.worktree_directory, dbWt);
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
// Track which DB rows got matched so we can append unmatched ones
|
|
211
|
+
const matchedDbThreadIds = new Set();
|
|
212
|
+
// Build rows from git worktrees (the source of truth for on-disk state).
|
|
213
|
+
// Use real DB status when available — a git-visible worktree whose DB row
|
|
214
|
+
// is still 'pending' means setup hasn't finished (race window).
|
|
215
|
+
const gitRows = gitWorktrees.map((gw) => {
|
|
216
|
+
const dbMatch = dbByDirectory.get(gw.directory);
|
|
217
|
+
if (dbMatch) {
|
|
218
|
+
matchedDbThreadIds.add(dbMatch.thread_id);
|
|
219
|
+
}
|
|
220
|
+
const source = detectWorktreeSource({
|
|
221
|
+
branch: gw.branch,
|
|
222
|
+
directory: gw.directory,
|
|
223
|
+
});
|
|
224
|
+
const name = gw.branch ?? path.basename(gw.directory);
|
|
225
|
+
const dbStatus = (() => {
|
|
226
|
+
if (!dbMatch) {
|
|
227
|
+
return 'ready';
|
|
228
|
+
}
|
|
229
|
+
if (dbMatch.status === 'error') {
|
|
230
|
+
return 'error';
|
|
231
|
+
}
|
|
232
|
+
if (dbMatch.status === 'pending') {
|
|
233
|
+
return 'pending';
|
|
234
|
+
}
|
|
235
|
+
return 'ready';
|
|
236
|
+
})();
|
|
237
|
+
return {
|
|
238
|
+
directory: gw.directory,
|
|
239
|
+
branch: gw.branch,
|
|
240
|
+
name,
|
|
241
|
+
threadId: dbMatch?.thread_id ?? null,
|
|
242
|
+
guildId: null, // filled in by caller
|
|
243
|
+
createdAt: dbMatch?.created_at ?? null,
|
|
244
|
+
source,
|
|
245
|
+
dbStatus,
|
|
246
|
+
locked: gw.locked,
|
|
247
|
+
prunable: gw.prunable,
|
|
248
|
+
};
|
|
249
|
+
});
|
|
250
|
+
// Append DB-only worktrees (pending/error/stale — not visible to git).
|
|
251
|
+
// Preserve actual DB status so stale 'ready' rows show as 'ready' (missing).
|
|
252
|
+
const dbOnlyRows = dbWorktrees
|
|
253
|
+
.filter((dbWt) => {
|
|
254
|
+
return !matchedDbThreadIds.has(dbWt.thread_id);
|
|
255
|
+
})
|
|
256
|
+
.map((dbWt) => {
|
|
257
|
+
const dbStatus = (() => {
|
|
258
|
+
if (dbWt.status === 'error') {
|
|
259
|
+
return 'error';
|
|
260
|
+
}
|
|
261
|
+
if (dbWt.status === 'pending') {
|
|
262
|
+
return 'pending';
|
|
263
|
+
}
|
|
264
|
+
return 'ready';
|
|
265
|
+
})();
|
|
266
|
+
return {
|
|
267
|
+
directory: dbWt.worktree_directory ?? dbWt.project_directory,
|
|
268
|
+
branch: null,
|
|
269
|
+
name: dbWt.worktree_name,
|
|
270
|
+
threadId: dbWt.thread_id,
|
|
271
|
+
guildId: null,
|
|
272
|
+
createdAt: dbWt.created_at,
|
|
273
|
+
source: 'otto',
|
|
274
|
+
dbStatus,
|
|
275
|
+
locked: false,
|
|
276
|
+
prunable: false,
|
|
277
|
+
};
|
|
278
|
+
});
|
|
279
|
+
return [...gitRows, ...dbOnlyRows];
|
|
280
|
+
}
|
|
281
|
+
function getWorktreesActionOwnerKey({ userId, channelId, }) {
|
|
282
|
+
return `worktrees:${userId}:${channelId}`;
|
|
283
|
+
}
|
|
284
|
+
function isProjectChannel(channel) {
|
|
285
|
+
if (!channel) {
|
|
286
|
+
return false;
|
|
287
|
+
}
|
|
288
|
+
return [
|
|
289
|
+
ChannelType.GuildText,
|
|
290
|
+
ChannelType.PublicThread,
|
|
291
|
+
ChannelType.PrivateThread,
|
|
292
|
+
ChannelType.AnnouncementThread,
|
|
293
|
+
].includes(channel.type);
|
|
294
|
+
}
|
|
295
|
+
async function renderWorktreesReply({ guildId, userId, channelId, projectDirectory, notice, editReply, }) {
|
|
296
|
+
const ownerKey = getWorktreesActionOwnerKey({ userId, channelId });
|
|
297
|
+
cancelHtmlActionsForOwner(ownerKey);
|
|
298
|
+
const gitWorktrees = await listGitWorktrees({
|
|
299
|
+
projectDirectory,
|
|
300
|
+
timeout: GIT_CMD_TIMEOUT,
|
|
301
|
+
});
|
|
302
|
+
// On git failure, fall back to empty list (DB-only rows still shown)
|
|
303
|
+
const gitList = gitWorktrees instanceof Error ? [] : gitWorktrees;
|
|
304
|
+
const rows = await buildWorktreeRows({ projectDirectory, gitWorktrees: gitList });
|
|
305
|
+
// Inject guildId into all rows for thread link rendering
|
|
306
|
+
for (const row of rows) {
|
|
307
|
+
row.guildId = guildId;
|
|
308
|
+
}
|
|
309
|
+
if (rows.length === 0) {
|
|
310
|
+
const message = notice
|
|
311
|
+
? `${notice}\n\nNo worktrees found.`
|
|
312
|
+
: 'No worktrees found.';
|
|
313
|
+
const textDisplay = {
|
|
314
|
+
type: ComponentType.TextDisplay,
|
|
315
|
+
content: message,
|
|
316
|
+
};
|
|
317
|
+
await editReply({
|
|
318
|
+
components: [textDisplay],
|
|
319
|
+
flags: MessageFlags.IsComponentsV2,
|
|
320
|
+
});
|
|
321
|
+
return;
|
|
322
|
+
}
|
|
323
|
+
const gitStatuses = await resolveGitStatuses({
|
|
324
|
+
rows,
|
|
325
|
+
projectDirectory,
|
|
326
|
+
timeout: GLOBAL_TIMEOUT,
|
|
327
|
+
});
|
|
328
|
+
// Map deletable worktrees by button ID for the HTML action resolver.
|
|
329
|
+
// Uses the same worktreeButtonKey() as buildActionCell.
|
|
330
|
+
const deletableRowsByButtonId = new Map();
|
|
331
|
+
rows.forEach((row, index) => {
|
|
332
|
+
const gitStatus = gitStatuses[index] ?? null;
|
|
333
|
+
if (!canDeleteWorktree({ row, gitStatus })) {
|
|
334
|
+
return;
|
|
335
|
+
}
|
|
336
|
+
deletableRowsByButtonId.set(`del-wt-${worktreeButtonKey(row.directory)}`, row);
|
|
337
|
+
});
|
|
338
|
+
const tableMarkdown = buildWorktreeTable({
|
|
339
|
+
rows,
|
|
340
|
+
gitStatuses,
|
|
341
|
+
guildId,
|
|
342
|
+
});
|
|
343
|
+
const markdown = notice ? `${notice}\n\n${tableMarkdown}` : tableMarkdown;
|
|
344
|
+
const segments = splitTablesFromMarkdown(markdown, {
|
|
345
|
+
resolveButtonCustomId: ({ button }) => {
|
|
346
|
+
const row = deletableRowsByButtonId.get(button.id);
|
|
347
|
+
if (!row) {
|
|
348
|
+
return new Error(`No worktree registered for button ${button.id}`);
|
|
349
|
+
}
|
|
350
|
+
const actionId = registerHtmlAction({
|
|
351
|
+
ownerKey,
|
|
352
|
+
threadId: row.threadId ?? row.directory,
|
|
353
|
+
run: async ({ interaction }) => {
|
|
354
|
+
await handleDeleteWorktreeAction({
|
|
355
|
+
interaction,
|
|
356
|
+
row,
|
|
357
|
+
projectDirectory,
|
|
358
|
+
});
|
|
359
|
+
},
|
|
360
|
+
});
|
|
361
|
+
return buildHtmlActionCustomId(actionId);
|
|
362
|
+
},
|
|
363
|
+
});
|
|
364
|
+
const components = segments.flatMap((segment) => {
|
|
365
|
+
if (segment.type === 'components') {
|
|
366
|
+
return segment.components;
|
|
367
|
+
}
|
|
368
|
+
const textDisplay = {
|
|
369
|
+
type: ComponentType.TextDisplay,
|
|
370
|
+
content: segment.text,
|
|
371
|
+
};
|
|
372
|
+
return [textDisplay];
|
|
373
|
+
});
|
|
374
|
+
await editReply({
|
|
375
|
+
components,
|
|
376
|
+
flags: MessageFlags.IsComponentsV2,
|
|
377
|
+
});
|
|
378
|
+
}
|
|
379
|
+
async function handleDeleteWorktreeAction({ interaction, row, projectDirectory, }) {
|
|
380
|
+
const guildId = interaction.guildId;
|
|
381
|
+
if (!guildId) {
|
|
382
|
+
await interaction.editReply({
|
|
383
|
+
components: [
|
|
384
|
+
{
|
|
385
|
+
type: ComponentType.TextDisplay,
|
|
386
|
+
content: 'This action can only be used in a server.',
|
|
387
|
+
},
|
|
388
|
+
],
|
|
389
|
+
flags: MessageFlags.IsComponentsV2,
|
|
390
|
+
});
|
|
391
|
+
return;
|
|
392
|
+
}
|
|
393
|
+
// Pass branch name for branch cleanup. Empty string for detached HEAD
|
|
394
|
+
// worktrees so deleteWorktree skips the `git branch -d` step.
|
|
395
|
+
const displayName = row.branch ?? row.name;
|
|
396
|
+
const deleteResult = await deleteWorktree({
|
|
397
|
+
projectDirectory,
|
|
398
|
+
worktreeDirectory: row.directory,
|
|
399
|
+
worktreeName: row.branch ?? '',
|
|
400
|
+
});
|
|
401
|
+
if (deleteResult instanceof Error) {
|
|
402
|
+
const gitStderr = extractGitStderr(deleteResult);
|
|
403
|
+
const detail = gitStderr
|
|
404
|
+
? `\`\`\`\n${gitStderr}\n\`\`\``
|
|
405
|
+
: deleteResult.message;
|
|
406
|
+
await interaction
|
|
407
|
+
.followUp({
|
|
408
|
+
content: `Failed to delete \`${displayName}\`\n${detail}`,
|
|
409
|
+
flags: MessageFlags.Ephemeral,
|
|
410
|
+
})
|
|
411
|
+
.catch(() => {
|
|
412
|
+
return undefined;
|
|
413
|
+
});
|
|
414
|
+
return;
|
|
415
|
+
}
|
|
416
|
+
// Clean up DB row if this was an otto-tracked worktree
|
|
417
|
+
if (row.threadId) {
|
|
418
|
+
await deleteThreadWorktree(row.threadId);
|
|
419
|
+
}
|
|
420
|
+
await renderWorktreesReply({
|
|
421
|
+
guildId,
|
|
422
|
+
userId: interaction.user.id,
|
|
423
|
+
channelId: interaction.channelId,
|
|
424
|
+
projectDirectory,
|
|
425
|
+
notice: `Deleted \`${displayName}\`.`,
|
|
426
|
+
editReply: (options) => {
|
|
427
|
+
return interaction.editReply(options);
|
|
428
|
+
},
|
|
429
|
+
});
|
|
430
|
+
}
|
|
431
|
+
export async function handleWorktreesCommand({ command, }) {
|
|
432
|
+
const channel = command.channel;
|
|
433
|
+
const guildId = command.guildId;
|
|
434
|
+
if (!guildId || !channel) {
|
|
435
|
+
await command.reply({
|
|
436
|
+
content: 'This command can only be used in a server channel.',
|
|
437
|
+
flags: MessageFlags.Ephemeral,
|
|
438
|
+
});
|
|
439
|
+
return;
|
|
440
|
+
}
|
|
441
|
+
if (!isProjectChannel(channel)) {
|
|
442
|
+
await command.reply({
|
|
443
|
+
content: 'This command can only be used in a project channel or thread.',
|
|
444
|
+
flags: MessageFlags.Ephemeral,
|
|
445
|
+
});
|
|
446
|
+
return;
|
|
447
|
+
}
|
|
448
|
+
const resolved = await resolveWorkingDirectory({
|
|
449
|
+
channel: channel,
|
|
450
|
+
});
|
|
451
|
+
if (!resolved) {
|
|
452
|
+
await command.reply({
|
|
453
|
+
content: 'Could not determine the project folder for this channel.',
|
|
454
|
+
flags: MessageFlags.Ephemeral,
|
|
455
|
+
});
|
|
456
|
+
return;
|
|
457
|
+
}
|
|
458
|
+
await command.deferReply({ flags: MessageFlags.Ephemeral });
|
|
459
|
+
await renderWorktreesReply({
|
|
460
|
+
guildId,
|
|
461
|
+
userId: command.user.id,
|
|
462
|
+
channelId: command.channelId,
|
|
463
|
+
projectDirectory: resolved.projectDirectory,
|
|
464
|
+
editReply: (options) => {
|
|
465
|
+
return command.editReply(options);
|
|
466
|
+
},
|
|
467
|
+
});
|
|
468
|
+
}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
// Utility to condense MEMORY.md into a line-numbered table of contents.
|
|
2
|
+
// Separated from otto-opencode-plugin.ts because OpenCode's plugin loader calls
|
|
3
|
+
// every exported function in the module as a plugin initializer — exporting
|
|
4
|
+
// this utility from the plugin entry file caused it to be invoked with a
|
|
5
|
+
// PluginInput object instead of a string, crashing inside marked's Lexer.
|
|
6
|
+
import { Lexer } from 'marked';
|
|
7
|
+
/**
|
|
8
|
+
* Condense MEMORY.md into a line-numbered table of contents.
|
|
9
|
+
* Parses markdown AST with marked's Lexer, emits each heading prefixed by
|
|
10
|
+
* its source line number, and collapses non-heading content to `...`.
|
|
11
|
+
* The agent can then use Read with offset/limit to read specific sections.
|
|
12
|
+
*/
|
|
13
|
+
export function condenseMemoryMd(content) {
|
|
14
|
+
const tokens = new Lexer().lex(content);
|
|
15
|
+
const lines = [];
|
|
16
|
+
let charOffset = 0;
|
|
17
|
+
let lastWasEllipsis = false;
|
|
18
|
+
for (const token of tokens) {
|
|
19
|
+
// Compute 1-based line number from character offset
|
|
20
|
+
const lineNumber = content.slice(0, charOffset).split('\n').length;
|
|
21
|
+
if (token.type === 'heading') {
|
|
22
|
+
const prefix = '#'.repeat(token.depth);
|
|
23
|
+
lines.push(`${lineNumber}: ${prefix} ${token.text}`);
|
|
24
|
+
lastWasEllipsis = false;
|
|
25
|
+
}
|
|
26
|
+
else if (!lastWasEllipsis) {
|
|
27
|
+
lines.push('...');
|
|
28
|
+
lastWasEllipsis = true;
|
|
29
|
+
}
|
|
30
|
+
charOffset += token.raw.length;
|
|
31
|
+
}
|
|
32
|
+
return lines.join('\n');
|
|
33
|
+
}
|