@otto-assistant/bridge 0.4.92
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-auth-plugin.js +728 -0
- package/dist/anthropic-auth-plugin.test.js +125 -0
- package/dist/anthropic-auth-state.js +231 -0
- package/dist/bin.js +90 -0
- package/dist/channel-management.js +227 -0
- package/dist/cli-parsing.test.js +137 -0
- package/dist/cli-send-thread.e2e.test.js +356 -0
- package/dist/cli.js +3276 -0
- package/dist/commands/abort.js +65 -0
- package/dist/commands/action-buttons.js +245 -0
- package/dist/commands/add-project.js +113 -0
- package/dist/commands/agent.js +335 -0
- package/dist/commands/ask-question.js +274 -0
- package/dist/commands/btw.js +116 -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/file-upload.js +275 -0
- package/dist/commands/fork.js +220 -0
- package/dist/commands/gemini-apikey.js +70 -0
- package/dist/commands/login.js +885 -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 +159 -0
- package/dist/commands/model-variant.js +364 -0
- package/dist/commands/model.js +776 -0
- package/dist/commands/new-worktree.js +366 -0
- package/dist/commands/paginated-select.js +57 -0
- package/dist/commands/permissions.js +274 -0
- package/dist/commands/queue.js +206 -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/types.js +2 -0
- package/dist/commands/undo-redo.js +305 -0
- package/dist/commands/unset-model.js +138 -0
- package/dist/commands/upgrade.js +42 -0
- package/dist/commands/user-command.js +155 -0
- package/dist/commands/verbosity.js +125 -0
- package/dist/commands/worktree-settings.js +43 -0
- package/dist/commands/worktrees.js +410 -0
- package/dist/condense-memory.js +33 -0
- package/dist/config.js +94 -0
- package/dist/context-awareness-plugin.js +363 -0
- package/dist/context-awareness-plugin.test.js +124 -0
- package/dist/critique-utils.js +95 -0
- package/dist/database.js +1310 -0
- package/dist/db.js +251 -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 +1008 -0
- package/dist/discord-command-registration.js +524 -0
- package/dist/discord-urls.js +81 -0
- package/dist/discord-utils.js +591 -0
- package/dist/discord-utils.test.js +134 -0
- package/dist/errors.js +157 -0
- package/dist/escape-backticks.test.js +429 -0
- package/dist/event-stream-real-capture.e2e.test.js +533 -0
- package/dist/eventsource-parser.test.js +327 -0
- package/dist/exec-async.js +26 -0
- package/dist/external-opencode-sync.js +480 -0
- package/dist/format-tables.js +302 -0
- package/dist/format-tables.test.js +308 -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 +483 -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 +52 -0
- package/dist/generated/internal/class.js +49 -0
- package/dist/generated/internal/prismaNamespace.js +253 -0
- package/dist/generated/internal/prismaNamespaceBrowser.js +223 -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 +263 -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 +397 -0
- package/dist/ipc-polling.js +252 -0
- package/dist/ipc-tools-plugin.js +193 -0
- package/dist/kimaki-digital-twin.e2e.test.js +161 -0
- package/dist/kimaki-opencode-plugin-loading.e2e.test.js +87 -0
- package/dist/kimaki-opencode-plugin.js +17 -0
- package/dist/kimaki-opencode-plugin.test.js +98 -0
- package/dist/limit-heading-depth.js +25 -0
- package/dist/limit-heading-depth.test.js +105 -0
- package/dist/logger.js +165 -0
- package/dist/markdown.js +342 -0
- package/dist/markdown.test.js +257 -0
- package/dist/message-finish-field.e2e.test.js +165 -0
- package/dist/message-formatting.js +413 -0
- package/dist/message-formatting.test.js +73 -0
- package/dist/message-preprocessing.js +330 -0
- package/dist/onboarding-tutorial.js +172 -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 +129 -0
- package/dist/opencode-command.test.js +48 -0
- package/dist/opencode-interrupt-plugin.js +361 -0
- package/dist/opencode-interrupt-plugin.test.js +458 -0
- package/dist/opencode.js +861 -0
- package/dist/otto/branding.js +22 -0
- package/dist/otto/index.js +21 -0
- package/dist/parse-permission-rules.test.js +117 -0
- package/dist/patch-text-parser.js +97 -0
- package/dist/plugin-logger.js +59 -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 +786 -0
- package/dist/queue-advanced-footer.e2e.test.js +472 -0
- package/dist/queue-advanced-model-switch.e2e.test.js +299 -0
- package/dist/queue-advanced-permissions-typing.e2e.test.js +180 -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 +120 -0
- package/dist/runtime-idle-sweeper.js +52 -0
- package/dist/runtime-lifecycle.e2e.test.js +508 -0
- package/dist/sentry.js +23 -0
- package/dist/session-handler/agent-utils.js +67 -0
- package/dist/session-handler/event-stream-state.js +420 -0
- package/dist/session-handler/event-stream-state.test.js +563 -0
- package/dist/session-handler/model-utils.js +124 -0
- package/dist/session-handler/opencode-session-event-log.js +94 -0
- package/dist/session-handler/thread-runtime-state.js +104 -0
- package/dist/session-handler/thread-session-runtime.js +3258 -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 +80 -0
- package/dist/startup-service.js +153 -0
- package/dist/startup-time.e2e.test.js +296 -0
- package/dist/store.js +17 -0
- package/dist/system-message.js +613 -0
- package/dist/system-message.test.js +602 -0
- package/dist/task-runner.js +295 -0
- package/dist/task-schedule.js +209 -0
- package/dist/task-schedule.test.js +71 -0
- package/dist/test-utils.js +299 -0
- package/dist/thinking-utils.js +35 -0
- package/dist/thread-message-queue.e2e.test.js +999 -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 +114 -0
- package/dist/utils.js +144 -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 +447 -0
- package/dist/voice.test.js +235 -0
- package/dist/wait-session.js +94 -0
- package/dist/websockify.js +69 -0
- package/dist/worker-types.js +4 -0
- package/dist/worktree-lifecycle.e2e.test.js +308 -0
- package/dist/worktree-utils.js +3 -0
- package/dist/worktrees.js +929 -0
- package/dist/worktrees.test.js +189 -0
- package/dist/xml.js +92 -0
- package/dist/xml.test.js +32 -0
- package/package.json +98 -0
- package/schema.prisma +295 -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/gitchamber/SKILL.md +93 -0
- package/skills/goke/SKILL.md +644 -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/new-skill/SKILL.md +211 -0
- package/skills/npm-package/SKILL.md +239 -0
- package/skills/playwriter/SKILL.md +35 -0
- package/skills/proxyman/SKILL.md +215 -0
- package/skills/security-review/SKILL.md +208 -0
- package/skills/simplify/SKILL.md +58 -0
- package/skills/spiceflow/SKILL.md +14 -0
- package/skills/termcast/SKILL.md +945 -0
- package/skills/tuistory/SKILL.md +250 -0
- package/skills/usecomputer/SKILL.md +264 -0
- package/skills/x-articles/SKILL.md +554 -0
- package/skills/zele/SKILL.md +112 -0
- package/skills/zustand-centralized-state/SKILL.md +1004 -0
- package/src/agent-model.e2e.test.ts +976 -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-auth-plugin.test.ts +159 -0
- package/src/anthropic-auth-plugin.ts +861 -0
- package/src/anthropic-auth-state.ts +282 -0
- package/src/bin.ts +111 -0
- package/src/channel-management.ts +334 -0
- package/src/cli-parsing.test.ts +195 -0
- package/src/cli-send-thread.e2e.test.ts +464 -0
- package/src/cli.ts +4581 -0
- package/src/commands/abort.ts +89 -0
- package/src/commands/action-buttons.ts +364 -0
- package/src/commands/add-project.ts +149 -0
- package/src/commands/agent.ts +473 -0
- package/src/commands/ask-question.ts +390 -0
- package/src/commands/btw.ts +164 -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/file-upload.ts +389 -0
- package/src/commands/fork.ts +321 -0
- package/src/commands/gemini-apikey.ts +104 -0
- package/src/commands/login.ts +1173 -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 +223 -0
- package/src/commands/model-variant.ts +483 -0
- package/src/commands/model.ts +1053 -0
- package/src/commands/new-worktree.ts +510 -0
- package/src/commands/paginated-select.ts +81 -0
- package/src/commands/permissions.ts +397 -0
- package/src/commands/queue.ts +271 -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/types.ts +25 -0
- package/src/commands/undo-redo.ts +386 -0
- package/src/commands/unset-model.ts +173 -0
- package/src/commands/upgrade.ts +52 -0
- package/src/commands/user-command.ts +198 -0
- package/src/commands/verbosity.ts +173 -0
- package/src/commands/worktree-settings.ts +70 -0
- package/src/commands/worktrees.ts +552 -0
- package/src/condense-memory.ts +36 -0
- package/src/config.ts +111 -0
- package/src/context-awareness-plugin.test.ts +142 -0
- package/src/context-awareness-plugin.ts +510 -0
- package/src/critique-utils.ts +139 -0
- package/src/database.ts +1876 -0
- package/src/db.test.ts +162 -0
- package/src/db.ts +286 -0
- package/src/debounce-timeout.ts +43 -0
- package/src/debounced-process-flush.ts +104 -0
- package/src/discord-bot.ts +1330 -0
- package/src/discord-command-registration.ts +693 -0
- package/src/discord-urls.ts +88 -0
- package/src/discord-utils.test.ts +153 -0
- package/src/discord-utils.ts +800 -0
- package/src/errors.ts +201 -0
- package/src/escape-backticks.test.ts +469 -0
- package/src/event-stream-real-capture.e2e.test.ts +692 -0
- package/src/eventsource-parser.test.ts +351 -0
- package/src/exec-async.ts +35 -0
- package/src/external-opencode-sync.ts +685 -0
- package/src/format-tables.test.ts +335 -0
- package/src/format-tables.ts +445 -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 +640 -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 +736 -0
- package/src/generated/enums.ts +88 -0
- package/src/generated/internal/class.ts +384 -0
- package/src/generated/internal/prismaNamespace.ts +2386 -0
- package/src/generated/internal/prismaNamespaceBrowser.ts +326 -0
- package/src/generated/models/bot_api_keys.ts +1288 -0
- package/src/generated/models/bot_tokens.ts +1656 -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 +314 -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 +576 -0
- package/src/ipc-polling.ts +326 -0
- package/src/ipc-tools-plugin.ts +236 -0
- package/src/kimaki-digital-twin.e2e.test.ts +199 -0
- package/src/kimaki-opencode-plugin-loading.e2e.test.ts +109 -0
- package/src/kimaki-opencode-plugin.test.ts +108 -0
- package/src/kimaki-opencode-plugin.ts +18 -0
- package/src/limit-heading-depth.test.ts +116 -0
- package/src/limit-heading-depth.ts +26 -0
- package/src/logger.ts +208 -0
- package/src/markdown.test.ts +308 -0
- package/src/markdown.ts +410 -0
- package/src/message-finish-field.e2e.test.ts +192 -0
- package/src/message-formatting.test.ts +81 -0
- package/src/message-formatting.ts +533 -0
- package/src/message-preprocessing.ts +455 -0
- package/src/onboarding-tutorial.ts +176 -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 +188 -0
- package/src/opencode-interrupt-plugin.test.ts +677 -0
- package/src/opencode-interrupt-plugin.ts +477 -0
- package/src/opencode.ts +1110 -0
- package/src/otto/branding.ts +23 -0
- package/src/otto/index.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 +68 -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 +873 -0
- package/src/queue-advanced-footer.e2e.test.ts +576 -0
- package/src/queue-advanced-model-switch.e2e.test.ts +383 -0
- package/src/queue-advanced-permissions-typing.e2e.test.ts +245 -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 +152 -0
- package/src/runtime-idle-sweeper.ts +76 -0
- package/src/runtime-lifecycle.e2e.test.ts +641 -0
- package/src/schema.sql +173 -0
- package/src/sentry.ts +26 -0
- package/src/session-handler/agent-utils.ts +97 -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 +645 -0
- package/src/session-handler/event-stream-state.ts +608 -0
- package/src/session-handler/model-utils.ts +183 -0
- package/src/session-handler/opencode-session-event-log.ts +130 -0
- package/src/session-handler/thread-runtime-state.ts +212 -0
- package/src/session-handler/thread-session-runtime.ts +4281 -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 +112 -0
- package/src/startup-service.ts +200 -0
- package/src/startup-time.e2e.test.ts +373 -0
- package/src/store.ts +122 -0
- package/src/system-message.test.ts +612 -0
- package/src/system-message.ts +723 -0
- package/src/task-runner.ts +421 -0
- package/src/task-schedule.test.ts +84 -0
- package/src/task-schedule.ts +311 -0
- package/src/test-utils.ts +435 -0
- package/src/thinking-utils.ts +61 -0
- package/src/thread-message-queue.e2e.test.ts +1219 -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 +127 -0
- package/src/utils.ts +212 -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 +627 -0
- package/src/wait-session.ts +147 -0
- package/src/websockify.ts +101 -0
- package/src/worker-types.ts +64 -0
- package/src/worktree-lifecycle.e2e.test.ts +391 -0
- package/src/worktree-utils.ts +4 -0
- package/src/worktrees.test.ts +223 -0
- package/src/worktrees.ts +1294 -0
- package/src/xml.test.ts +38 -0
- package/src/xml.ts +121 -0
|
@@ -0,0 +1,241 @@
|
|
|
1
|
+
// Discord API operations for forum sync.
|
|
2
|
+
// Resolves forum channels, fetches threads (active + archived) with pagination,
|
|
3
|
+
// fetches thread messages, loads existing forum files from disk, and ensures directories.
|
|
4
|
+
|
|
5
|
+
import fs from 'node:fs'
|
|
6
|
+
import path from 'node:path'
|
|
7
|
+
import {
|
|
8
|
+
ChannelType,
|
|
9
|
+
type Client,
|
|
10
|
+
type ForumChannel,
|
|
11
|
+
type Message,
|
|
12
|
+
type ThreadChannel,
|
|
13
|
+
} from 'discord.js'
|
|
14
|
+
import { createLogger } from '../logger.js'
|
|
15
|
+
import { parseFrontmatter, getStringValue } from './markdown.js'
|
|
16
|
+
import {
|
|
17
|
+
DEFAULT_RATE_LIMIT_DELAY_MS,
|
|
18
|
+
ForumChannelResolveError,
|
|
19
|
+
ForumSyncOperationError,
|
|
20
|
+
delay,
|
|
21
|
+
type ExistingForumFile,
|
|
22
|
+
} from './types.js'
|
|
23
|
+
|
|
24
|
+
const forumLogger = createLogger('FORUM')
|
|
25
|
+
|
|
26
|
+
export function getCanonicalThreadFilePath({
|
|
27
|
+
outputDir,
|
|
28
|
+
threadId,
|
|
29
|
+
subfolder,
|
|
30
|
+
}: {
|
|
31
|
+
outputDir: string
|
|
32
|
+
threadId: string
|
|
33
|
+
subfolder?: string
|
|
34
|
+
}) {
|
|
35
|
+
if (subfolder) {
|
|
36
|
+
return path.join(outputDir, subfolder, `${threadId}.md`)
|
|
37
|
+
}
|
|
38
|
+
return path.join(outputDir, `${threadId}.md`)
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
export async function ensureDirectory({ directory }: { directory: string }) {
|
|
42
|
+
const result = await fs.promises.mkdir(directory, { recursive: true }).catch(
|
|
43
|
+
(cause) =>
|
|
44
|
+
new ForumSyncOperationError({
|
|
45
|
+
forumChannelId: 'unknown',
|
|
46
|
+
reason: directory,
|
|
47
|
+
cause,
|
|
48
|
+
}),
|
|
49
|
+
)
|
|
50
|
+
if (result instanceof Error) return result
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
export async function resolveForumChannel({
|
|
54
|
+
discordClient,
|
|
55
|
+
forumChannelId,
|
|
56
|
+
}: {
|
|
57
|
+
discordClient: Client
|
|
58
|
+
forumChannelId: string
|
|
59
|
+
}): Promise<ForumChannel | ForumChannelResolveError> {
|
|
60
|
+
const channel = await discordClient.channels
|
|
61
|
+
.fetch(forumChannelId)
|
|
62
|
+
.catch((cause) => new ForumChannelResolveError({ forumChannelId, cause }))
|
|
63
|
+
if (channel instanceof Error) return channel
|
|
64
|
+
|
|
65
|
+
if (!channel || channel.type !== ChannelType.GuildForum) {
|
|
66
|
+
return new ForumChannelResolveError({ forumChannelId })
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
return channel
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
export async function fetchForumThreads({
|
|
73
|
+
forumChannel,
|
|
74
|
+
}: {
|
|
75
|
+
forumChannel: ForumChannel
|
|
76
|
+
}): Promise<ThreadChannel[] | ForumSyncOperationError> {
|
|
77
|
+
const byId = new Map<string, ThreadChannel>()
|
|
78
|
+
|
|
79
|
+
const active = await forumChannel.threads.fetchActive().catch(
|
|
80
|
+
(cause) =>
|
|
81
|
+
new ForumSyncOperationError({
|
|
82
|
+
forumChannelId: forumChannel.id,
|
|
83
|
+
reason: 'fetchActive failed',
|
|
84
|
+
cause,
|
|
85
|
+
}),
|
|
86
|
+
)
|
|
87
|
+
if (active instanceof Error) return active
|
|
88
|
+
|
|
89
|
+
for (const [id, thread] of active.threads) {
|
|
90
|
+
byId.set(id, thread)
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
let before: Date | undefined
|
|
94
|
+
while (true) {
|
|
95
|
+
const archived = await forumChannel.threads
|
|
96
|
+
.fetchArchived({ type: 'public', limit: 100, before })
|
|
97
|
+
.catch(
|
|
98
|
+
(cause) =>
|
|
99
|
+
new ForumSyncOperationError({
|
|
100
|
+
forumChannelId: forumChannel.id,
|
|
101
|
+
reason: 'fetchArchived failed',
|
|
102
|
+
cause,
|
|
103
|
+
}),
|
|
104
|
+
)
|
|
105
|
+
if (archived instanceof Error) return archived
|
|
106
|
+
|
|
107
|
+
const threads = Array.from(archived.threads.values())
|
|
108
|
+
for (const thread of threads) {
|
|
109
|
+
byId.set(thread.id, thread)
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
if (!archived.hasMore || threads.length === 0) break
|
|
113
|
+
|
|
114
|
+
const timestamps = threads
|
|
115
|
+
.map((thread) => thread.archiveTimestamp ?? thread.createdTimestamp)
|
|
116
|
+
.filter((value): value is number => value !== null)
|
|
117
|
+
|
|
118
|
+
const oldestTimestamp = Math.min(...timestamps)
|
|
119
|
+
if (!Number.isFinite(oldestTimestamp)) break
|
|
120
|
+
|
|
121
|
+
before = new Date(oldestTimestamp - 1)
|
|
122
|
+
await delay({ ms: DEFAULT_RATE_LIMIT_DELAY_MS })
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
return Array.from(byId.values())
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
export async function fetchThreadMessages({
|
|
129
|
+
thread,
|
|
130
|
+
}: {
|
|
131
|
+
thread: ThreadChannel
|
|
132
|
+
}): Promise<Message[] | ForumSyncOperationError> {
|
|
133
|
+
const byId = new Map<string, Message>()
|
|
134
|
+
let before: string | undefined
|
|
135
|
+
|
|
136
|
+
while (true) {
|
|
137
|
+
const fetched = await thread.messages.fetch({ limit: 100, before }).catch(
|
|
138
|
+
(cause) =>
|
|
139
|
+
new ForumSyncOperationError({
|
|
140
|
+
forumChannelId: thread.parentId || 'unknown',
|
|
141
|
+
reason: `message fetch failed for thread ${thread.id}`,
|
|
142
|
+
cause,
|
|
143
|
+
}),
|
|
144
|
+
)
|
|
145
|
+
if (fetched instanceof Error) return fetched
|
|
146
|
+
|
|
147
|
+
const messages = Array.from(fetched.values())
|
|
148
|
+
for (const message of messages) {
|
|
149
|
+
byId.set(message.id, message)
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
if (messages.length < 100 || messages.length === 0) break
|
|
153
|
+
|
|
154
|
+
// Find oldest message for cursor - messages are sorted by Discord, last is oldest
|
|
155
|
+
const oldest = messages[messages.length - 1]
|
|
156
|
+
if (!oldest) break
|
|
157
|
+
|
|
158
|
+
before = oldest.id
|
|
159
|
+
await delay({ ms: DEFAULT_RATE_LIMIT_DELAY_MS })
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
return Array.from(byId.values()).sort(
|
|
163
|
+
(a, b) => a.createdTimestamp - b.createdTimestamp,
|
|
164
|
+
)
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
/**
|
|
168
|
+
* Recursively walks a directory collecting all .md files with their relative subfolder path.
|
|
169
|
+
*/
|
|
170
|
+
async function collectMarkdownFiles({
|
|
171
|
+
dir,
|
|
172
|
+
outputDir,
|
|
173
|
+
}: {
|
|
174
|
+
dir: string
|
|
175
|
+
outputDir: string
|
|
176
|
+
}): Promise<Array<{ filePath: string; subfolder?: string }>> {
|
|
177
|
+
if (!fs.existsSync(dir)) return []
|
|
178
|
+
|
|
179
|
+
const entries = await fs.promises.readdir(dir, { withFileTypes: true })
|
|
180
|
+
const relativeSub = path.relative(outputDir, dir)
|
|
181
|
+
const subfolder = relativeSub && relativeSub !== '.' ? relativeSub : undefined
|
|
182
|
+
|
|
183
|
+
const mdFiles: Array<{ filePath: string; subfolder?: string }> = entries
|
|
184
|
+
.filter((entry) => entry.isFile() && entry.name.endsWith('.md'))
|
|
185
|
+
.map((entry) => ({ filePath: path.join(dir, entry.name), subfolder }))
|
|
186
|
+
|
|
187
|
+
const subdirs = entries.filter((entry) => entry.isDirectory())
|
|
188
|
+
const nestedResults = await Promise.all(
|
|
189
|
+
subdirs.map((subdir) =>
|
|
190
|
+
collectMarkdownFiles({
|
|
191
|
+
dir: path.join(dir, subdir.name),
|
|
192
|
+
outputDir,
|
|
193
|
+
}),
|
|
194
|
+
),
|
|
195
|
+
)
|
|
196
|
+
|
|
197
|
+
return [...mdFiles, ...nestedResults.flat()]
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
export async function loadExistingForumFiles({
|
|
201
|
+
outputDir,
|
|
202
|
+
}: {
|
|
203
|
+
outputDir: string
|
|
204
|
+
}): Promise<ExistingForumFile[]> {
|
|
205
|
+
const markdownEntries = await collectMarkdownFiles({
|
|
206
|
+
dir: outputDir,
|
|
207
|
+
outputDir,
|
|
208
|
+
})
|
|
209
|
+
|
|
210
|
+
const loaded = await Promise.all(
|
|
211
|
+
markdownEntries.map(async ({ filePath, subfolder }) => {
|
|
212
|
+
const content = await fs.promises
|
|
213
|
+
.readFile(filePath, 'utf8')
|
|
214
|
+
.catch((cause) => {
|
|
215
|
+
forumLogger.warn(`Failed to read forum file ${filePath}:`, cause)
|
|
216
|
+
return null
|
|
217
|
+
})
|
|
218
|
+
if (content === null) return null
|
|
219
|
+
|
|
220
|
+
const parsed = parseFrontmatter({ markdown: content })
|
|
221
|
+
const threadIdFromFrontmatter = getStringValue({
|
|
222
|
+
value: parsed.frontmatter.threadId,
|
|
223
|
+
})
|
|
224
|
+
const threadIdFromFilename = path.basename(filePath, '.md')
|
|
225
|
+
const threadId =
|
|
226
|
+
threadIdFromFrontmatter ||
|
|
227
|
+
(/^\d+$/.test(threadIdFromFilename) ? threadIdFromFilename : '')
|
|
228
|
+
if (!threadId) return null
|
|
229
|
+
|
|
230
|
+
const result: ExistingForumFile = {
|
|
231
|
+
filePath,
|
|
232
|
+
threadId,
|
|
233
|
+
frontmatter: parsed.frontmatter,
|
|
234
|
+
subfolder,
|
|
235
|
+
}
|
|
236
|
+
return result
|
|
237
|
+
}),
|
|
238
|
+
)
|
|
239
|
+
|
|
240
|
+
return loaded.filter((item): item is ExistingForumFile => item !== null)
|
|
241
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
// Forum sync module entry point.
|
|
2
|
+
// Re-exports the public API for forum <-> markdown synchronization.
|
|
3
|
+
|
|
4
|
+
export {
|
|
5
|
+
startConfiguredForumSync,
|
|
6
|
+
stopConfiguredForumSync,
|
|
7
|
+
} from './watchers.js'
|
|
8
|
+
export { syncForumToFiles } from './sync-to-files.js'
|
|
9
|
+
export { syncFilesToForum } from './sync-to-discord.js'
|
|
@@ -0,0 +1,172 @@
|
|
|
1
|
+
// Markdown parsing, serialization, and section formatting for forum sync.
|
|
2
|
+
// Handles frontmatter extraction, message section building, and
|
|
3
|
+
// conversion between Discord messages and markdown format.
|
|
4
|
+
|
|
5
|
+
import YAML from 'yaml'
|
|
6
|
+
import * as errore from 'errore'
|
|
7
|
+
import type { Message } from 'discord.js'
|
|
8
|
+
import {
|
|
9
|
+
ForumFrontmatterParseError,
|
|
10
|
+
type ForumMarkdownFrontmatter,
|
|
11
|
+
type ForumMessageSection,
|
|
12
|
+
type ParsedMarkdownFile,
|
|
13
|
+
} from './types.js'
|
|
14
|
+
|
|
15
|
+
export function toStringArray({ value }: { value: unknown }): string[] {
|
|
16
|
+
if (!Array.isArray(value)) return []
|
|
17
|
+
return value.filter((item): item is string => typeof item === 'string')
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export function getStringValue({ value }: { value: unknown }): string {
|
|
21
|
+
if (typeof value !== 'string') return ''
|
|
22
|
+
return value
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export function parseFrontmatter({
|
|
26
|
+
markdown,
|
|
27
|
+
}: {
|
|
28
|
+
markdown: string
|
|
29
|
+
}): ParsedMarkdownFile {
|
|
30
|
+
if (!markdown.startsWith('---\n')) {
|
|
31
|
+
return { frontmatter: {}, body: markdown.trim() }
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
const end = markdown.indexOf('\n---\n', 4)
|
|
35
|
+
if (end === -1) {
|
|
36
|
+
return { frontmatter: {}, body: markdown.trim() }
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
const rawFrontmatter = markdown.slice(4, end)
|
|
40
|
+
const body = markdown.slice(end + 5).trim()
|
|
41
|
+
|
|
42
|
+
const parsed = errore.try({
|
|
43
|
+
try: () => YAML.parse(rawFrontmatter),
|
|
44
|
+
catch: (cause) =>
|
|
45
|
+
new ForumFrontmatterParseError({ reason: 'yaml parse failed', cause }),
|
|
46
|
+
})
|
|
47
|
+
|
|
48
|
+
if (parsed instanceof Error || !parsed || typeof parsed !== 'object') {
|
|
49
|
+
return { frontmatter: {}, body }
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
return { frontmatter: parsed as Record<string, unknown>, body }
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
export function stringifyFrontmatter({
|
|
56
|
+
frontmatter,
|
|
57
|
+
body,
|
|
58
|
+
}: {
|
|
59
|
+
frontmatter: ForumMarkdownFrontmatter
|
|
60
|
+
body: string
|
|
61
|
+
}) {
|
|
62
|
+
const yamlText = YAML.stringify(frontmatter, null, {
|
|
63
|
+
lineWidth: 120,
|
|
64
|
+
}).trim()
|
|
65
|
+
return `---\n${yamlText}\n---\n\n${body.trim()}\n`
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
export function splitSections({ body }: { body: string }) {
|
|
69
|
+
return body
|
|
70
|
+
.split(/\r?\n---\r?\n/g)
|
|
71
|
+
.map((part) => part.trim())
|
|
72
|
+
.filter((part) => part.length > 0)
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
export function extractStarterContent({ body }: { body: string }) {
|
|
76
|
+
const sections = splitSections({ body })
|
|
77
|
+
const firstSection = sections[0] || ''
|
|
78
|
+
const match = firstSection.match(
|
|
79
|
+
/^\*\*.+?\*\* \(\d+\) - .+?(?: \(edited .+?\))?\r?\n\r?\n([\s\S]*)$/,
|
|
80
|
+
)
|
|
81
|
+
if (!match) return body.trim()
|
|
82
|
+
return (match[1] || '').trim()
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
export function buildMessageSections({
|
|
86
|
+
messages,
|
|
87
|
+
}: {
|
|
88
|
+
messages: Message[]
|
|
89
|
+
}): ForumMessageSection[] {
|
|
90
|
+
return messages.map((message) => {
|
|
91
|
+
const attachmentLines = Array.from(message.attachments.values()).map(
|
|
92
|
+
(attachment) => `Attachment: ${attachment.url}`,
|
|
93
|
+
)
|
|
94
|
+
|
|
95
|
+
const contentParts: string[] = []
|
|
96
|
+
const trimmedContent = message.content.trim()
|
|
97
|
+
if (trimmedContent) {
|
|
98
|
+
contentParts.push(trimmedContent)
|
|
99
|
+
}
|
|
100
|
+
if (attachmentLines.length > 0) {
|
|
101
|
+
contentParts.push(attachmentLines.join('\n'))
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
const content =
|
|
105
|
+
contentParts.length > 0
|
|
106
|
+
? contentParts.join('\n\n')
|
|
107
|
+
: '_(no text content)_'
|
|
108
|
+
|
|
109
|
+
return {
|
|
110
|
+
messageId: message.id,
|
|
111
|
+
authorName: message.author.username,
|
|
112
|
+
authorId: message.author.id,
|
|
113
|
+
createdAt: new Date(message.createdTimestamp).toISOString(),
|
|
114
|
+
editedAt: message.editedTimestamp
|
|
115
|
+
? new Date(message.editedTimestamp).toISOString()
|
|
116
|
+
: null,
|
|
117
|
+
content,
|
|
118
|
+
} satisfies ForumMessageSection
|
|
119
|
+
})
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
export function formatMessageSection({
|
|
123
|
+
section,
|
|
124
|
+
}: {
|
|
125
|
+
section: ForumMessageSection
|
|
126
|
+
}) {
|
|
127
|
+
const editedSuffix = section.editedAt ? ` (edited ${section.editedAt})` : ''
|
|
128
|
+
return `**${section.authorName}** (${section.authorId}) - ${section.createdAt}${editedSuffix}\n\n${section.content}`
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
// Channel mention footer stored in the Discord starter message so
|
|
132
|
+
// projectChannelId survives a full re-sync from Discord (no local files).
|
|
133
|
+
// Uses <#id> so Discord renders it as a clickable channel link.
|
|
134
|
+
// Matches at start-of-string or after a newline so it works even when the
|
|
135
|
+
// footer is the only content in the message (e.g. empty body).
|
|
136
|
+
const PROJECT_CHANNEL_FOOTER_RE = /(?:^|\n)channel: <#(\d{17,20})>\s*$/
|
|
137
|
+
const MAX_STARTER_MESSAGE_LENGTH = 2_000
|
|
138
|
+
|
|
139
|
+
/** Append a channel mention footer, truncating the body so the total
|
|
140
|
+
* never exceeds Discord's 2000-char starter message limit. */
|
|
141
|
+
export function appendProjectChannelFooter({
|
|
142
|
+
content,
|
|
143
|
+
projectChannelId,
|
|
144
|
+
}: {
|
|
145
|
+
content: string
|
|
146
|
+
projectChannelId?: string
|
|
147
|
+
}): string {
|
|
148
|
+
if (!projectChannelId) return content
|
|
149
|
+
const footer = `\nchannel: <#${projectChannelId}>`
|
|
150
|
+
const maxContentLength = MAX_STARTER_MESSAGE_LENGTH - footer.length
|
|
151
|
+
const truncated =
|
|
152
|
+
content.length > maxContentLength
|
|
153
|
+
? content.slice(0, maxContentLength)
|
|
154
|
+
: content
|
|
155
|
+
return `${truncated}${footer}`
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
export function extractProjectChannelFromContent({
|
|
159
|
+
content,
|
|
160
|
+
}: {
|
|
161
|
+
content: string
|
|
162
|
+
}): {
|
|
163
|
+
cleanContent: string
|
|
164
|
+
projectChannelId?: string
|
|
165
|
+
} {
|
|
166
|
+
const match = content.match(PROJECT_CHANNEL_FOOTER_RE)
|
|
167
|
+
if (!match) return { cleanContent: content }
|
|
168
|
+
return {
|
|
169
|
+
cleanContent: content.replace(PROJECT_CHANNEL_FOOTER_RE, '').trim(),
|
|
170
|
+
projectChannelId: match[1],
|
|
171
|
+
}
|
|
172
|
+
}
|