@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,386 @@
|
|
|
1
|
+
// Undo/Redo commands - /undo, /redo
|
|
2
|
+
|
|
3
|
+
import {
|
|
4
|
+
ChannelType,
|
|
5
|
+
MessageFlags,
|
|
6
|
+
type TextChannel,
|
|
7
|
+
type ThreadChannel,
|
|
8
|
+
} from 'discord.js'
|
|
9
|
+
import type { OpencodeClient } from '@opencode-ai/sdk/v2'
|
|
10
|
+
import type { CommandContext } from './types.js'
|
|
11
|
+
import { getThreadSession } from '../database.js'
|
|
12
|
+
import { initializeOpencodeForDirectory } from '../opencode.js'
|
|
13
|
+
import {
|
|
14
|
+
resolveWorkingDirectory,
|
|
15
|
+
SILENT_MESSAGE_FLAGS,
|
|
16
|
+
} from '../discord-utils.js'
|
|
17
|
+
import { createLogger, LogPrefix } from '../logger.js'
|
|
18
|
+
|
|
19
|
+
const logger = createLogger(LogPrefix.UNDO_REDO)
|
|
20
|
+
|
|
21
|
+
async function waitForSessionIdle({
|
|
22
|
+
client,
|
|
23
|
+
sessionId,
|
|
24
|
+
directory,
|
|
25
|
+
timeoutMs = 2_000,
|
|
26
|
+
}: {
|
|
27
|
+
client: OpencodeClient
|
|
28
|
+
sessionId: string
|
|
29
|
+
directory: string
|
|
30
|
+
timeoutMs?: number
|
|
31
|
+
}): Promise<void> {
|
|
32
|
+
const deadline = Date.now() + timeoutMs
|
|
33
|
+
while (Date.now() < deadline) {
|
|
34
|
+
const statusResponse = await client.session.status({ directory })
|
|
35
|
+
const sessionStatus = statusResponse.data?.[sessionId]
|
|
36
|
+
if (!sessionStatus || sessionStatus.type === 'idle') {
|
|
37
|
+
return
|
|
38
|
+
}
|
|
39
|
+
await new Promise((resolve) => {
|
|
40
|
+
setTimeout(resolve, 50)
|
|
41
|
+
})
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
export async function handleUndoCommand({
|
|
46
|
+
command,
|
|
47
|
+
}: CommandContext): Promise<void> {
|
|
48
|
+
const channel = command.channel
|
|
49
|
+
|
|
50
|
+
if (!channel) {
|
|
51
|
+
await command.reply({
|
|
52
|
+
content: 'This command can only be used in a channel',
|
|
53
|
+
flags: MessageFlags.Ephemeral | SILENT_MESSAGE_FLAGS,
|
|
54
|
+
})
|
|
55
|
+
return
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
const isThread = [
|
|
59
|
+
ChannelType.PublicThread,
|
|
60
|
+
ChannelType.PrivateThread,
|
|
61
|
+
ChannelType.AnnouncementThread,
|
|
62
|
+
].includes(channel.type)
|
|
63
|
+
|
|
64
|
+
if (!isThread) {
|
|
65
|
+
await command.reply({
|
|
66
|
+
content:
|
|
67
|
+
'This command can only be used in a thread with an active session',
|
|
68
|
+
flags: MessageFlags.Ephemeral | SILENT_MESSAGE_FLAGS,
|
|
69
|
+
})
|
|
70
|
+
return
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
const resolved = await resolveWorkingDirectory({
|
|
74
|
+
channel: channel as TextChannel | ThreadChannel,
|
|
75
|
+
})
|
|
76
|
+
|
|
77
|
+
if (!resolved) {
|
|
78
|
+
await command.reply({
|
|
79
|
+
content: 'Could not determine project directory for this channel',
|
|
80
|
+
flags: MessageFlags.Ephemeral | SILENT_MESSAGE_FLAGS,
|
|
81
|
+
})
|
|
82
|
+
return
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
const { projectDirectory, workingDirectory } = resolved
|
|
86
|
+
|
|
87
|
+
const sessionId = await getThreadSession(channel.id)
|
|
88
|
+
|
|
89
|
+
if (!sessionId) {
|
|
90
|
+
await command.reply({
|
|
91
|
+
content: 'No active session in this thread',
|
|
92
|
+
flags: MessageFlags.Ephemeral | SILENT_MESSAGE_FLAGS,
|
|
93
|
+
})
|
|
94
|
+
return
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
await command.deferReply({ flags: SILENT_MESSAGE_FLAGS })
|
|
98
|
+
|
|
99
|
+
const getClient = await initializeOpencodeForDirectory(projectDirectory)
|
|
100
|
+
if (getClient instanceof Error) {
|
|
101
|
+
await command.editReply(`Failed to undo: ${getClient.message}`)
|
|
102
|
+
return
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
try {
|
|
106
|
+
const client = getClient()
|
|
107
|
+
// Fetch session to check existing revert state
|
|
108
|
+
const sessionResponse = await client.session.get({
|
|
109
|
+
sessionID: sessionId,
|
|
110
|
+
directory: workingDirectory,
|
|
111
|
+
})
|
|
112
|
+
if (sessionResponse.error) {
|
|
113
|
+
await command.editReply(`Failed to undo: ${JSON.stringify(sessionResponse.error)}`)
|
|
114
|
+
return
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
// Abort if session is busy before reverting, matching TUI behavior
|
|
118
|
+
// (use-session-commands.tsx always aborts non-idle sessions before revert).
|
|
119
|
+
// session.status() returns a sparse map — only non-idle sessions have entries,
|
|
120
|
+
// so a missing key means idle.
|
|
121
|
+
const statusResponse = await client.session.status({
|
|
122
|
+
directory: workingDirectory,
|
|
123
|
+
})
|
|
124
|
+
const sessionStatus = statusResponse.data?.[sessionId]
|
|
125
|
+
if (sessionStatus && sessionStatus.type !== 'idle') {
|
|
126
|
+
await client.session.abort({
|
|
127
|
+
sessionID: sessionId,
|
|
128
|
+
directory: workingDirectory,
|
|
129
|
+
}).catch((error) => {
|
|
130
|
+
logger.warn(`[UNDO] abort failed for ${sessionId}`, error)
|
|
131
|
+
})
|
|
132
|
+
await waitForSessionIdle({
|
|
133
|
+
client,
|
|
134
|
+
sessionId,
|
|
135
|
+
directory: workingDirectory,
|
|
136
|
+
})
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
const messagesResponse = await client.session.messages({
|
|
140
|
+
sessionID: sessionId,
|
|
141
|
+
directory: workingDirectory,
|
|
142
|
+
})
|
|
143
|
+
if (messagesResponse.error) {
|
|
144
|
+
await command.editReply(`Failed to undo: ${JSON.stringify(messagesResponse.error)}`)
|
|
145
|
+
return
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
if (!messagesResponse.data || messagesResponse.data.length === 0) {
|
|
149
|
+
await command.editReply('No messages to undo')
|
|
150
|
+
return
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
// Follow the same approach as the OpenCode TUI (use-session-commands.tsx):
|
|
154
|
+
// find the last user message that is before the current revert point
|
|
155
|
+
// (or the last user message if no revert is active). This matches the
|
|
156
|
+
// TUI's `findLast(userMessages(), (x) => !revert || x.id < revert)`.
|
|
157
|
+
const currentRevert = sessionResponse.data?.revert?.messageID
|
|
158
|
+
const userMessages = messagesResponse.data.filter((m) => {
|
|
159
|
+
return m.info.role === 'user'
|
|
160
|
+
})
|
|
161
|
+
const targetUserMessage = [...userMessages].reverse().find((m) => {
|
|
162
|
+
return !currentRevert || m.info.id < currentRevert
|
|
163
|
+
})
|
|
164
|
+
|
|
165
|
+
if (!targetUserMessage) {
|
|
166
|
+
await command.editReply('No messages to undo')
|
|
167
|
+
return
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
const targetAssistantMessage = [...messagesResponse.data].reverse().find((m) => {
|
|
171
|
+
return m.info.role === 'assistant' && m.info.parentID === targetUserMessage.info.id
|
|
172
|
+
})
|
|
173
|
+
const revertMessageId = targetAssistantMessage?.info.id || targetUserMessage.info.id
|
|
174
|
+
|
|
175
|
+
// session.revert() reverts filesystem patches (file edits, writes) and
|
|
176
|
+
// marks the session with revert.messageID. Messages are NOT deleted — they
|
|
177
|
+
// get cleaned up automatically on the next promptAsync() call via
|
|
178
|
+
// SessionRevert.cleanup(). The model only sees messages before the revert
|
|
179
|
+
// point when processing the next prompt.
|
|
180
|
+
logger.log(`[UNDO] session.revert start messageId=${revertMessageId}`)
|
|
181
|
+
let response = await client.session.revert({
|
|
182
|
+
sessionID: sessionId,
|
|
183
|
+
directory: workingDirectory,
|
|
184
|
+
messageID: revertMessageId,
|
|
185
|
+
})
|
|
186
|
+
logger.log(`[UNDO] session.revert done error=${Boolean(response.error)}`)
|
|
187
|
+
|
|
188
|
+
if (response.error) {
|
|
189
|
+
logger.log('[UNDO] retry wait idle before revert retry')
|
|
190
|
+
await waitForSessionIdle({
|
|
191
|
+
client,
|
|
192
|
+
sessionId,
|
|
193
|
+
directory: workingDirectory,
|
|
194
|
+
})
|
|
195
|
+
logger.log('[UNDO] retry revert start')
|
|
196
|
+
response = await client.session.revert({
|
|
197
|
+
sessionID: sessionId,
|
|
198
|
+
directory: workingDirectory,
|
|
199
|
+
messageID: revertMessageId,
|
|
200
|
+
})
|
|
201
|
+
logger.log(`[UNDO] retry revert done error=${Boolean(response.error)}`)
|
|
202
|
+
if (response.error) {
|
|
203
|
+
await command.editReply(
|
|
204
|
+
`Failed to undo: ${JSON.stringify(response.error)}`,
|
|
205
|
+
)
|
|
206
|
+
return
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
const diffInfo = response.data?.revert?.diff
|
|
211
|
+
? `\n\`\`\`diff\n${response.data.revert.diff.slice(0, 1500)}\n\`\`\``
|
|
212
|
+
: ''
|
|
213
|
+
|
|
214
|
+
await command.editReply(`Undone - reverted last assistant message${diffInfo}`)
|
|
215
|
+
logger.log(
|
|
216
|
+
`Session ${sessionId} reverted at message ${revertMessageId}`,
|
|
217
|
+
)
|
|
218
|
+
} catch (error) {
|
|
219
|
+
logger.error('[UNDO] Error:', error)
|
|
220
|
+
await command.editReply(
|
|
221
|
+
`Failed to undo: ${error instanceof Error ? error.message : 'Unknown error'}`,
|
|
222
|
+
)
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
export async function handleRedoCommand({
|
|
227
|
+
command,
|
|
228
|
+
}: CommandContext): Promise<void> {
|
|
229
|
+
const channel = command.channel
|
|
230
|
+
|
|
231
|
+
if (!channel) {
|
|
232
|
+
await command.reply({
|
|
233
|
+
content: 'This command can only be used in a channel',
|
|
234
|
+
flags: MessageFlags.Ephemeral | SILENT_MESSAGE_FLAGS,
|
|
235
|
+
})
|
|
236
|
+
return
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
const isThread = [
|
|
240
|
+
ChannelType.PublicThread,
|
|
241
|
+
ChannelType.PrivateThread,
|
|
242
|
+
ChannelType.AnnouncementThread,
|
|
243
|
+
].includes(channel.type)
|
|
244
|
+
|
|
245
|
+
if (!isThread) {
|
|
246
|
+
await command.reply({
|
|
247
|
+
content:
|
|
248
|
+
'This command can only be used in a thread with an active session',
|
|
249
|
+
flags: MessageFlags.Ephemeral | SILENT_MESSAGE_FLAGS,
|
|
250
|
+
})
|
|
251
|
+
return
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
const resolved = await resolveWorkingDirectory({
|
|
255
|
+
channel: channel as TextChannel | ThreadChannel,
|
|
256
|
+
})
|
|
257
|
+
|
|
258
|
+
if (!resolved) {
|
|
259
|
+
await command.reply({
|
|
260
|
+
content: 'Could not determine project directory for this channel',
|
|
261
|
+
flags: MessageFlags.Ephemeral | SILENT_MESSAGE_FLAGS,
|
|
262
|
+
})
|
|
263
|
+
return
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
const { projectDirectory, workingDirectory } = resolved
|
|
267
|
+
|
|
268
|
+
const sessionId = await getThreadSession(channel.id)
|
|
269
|
+
|
|
270
|
+
if (!sessionId) {
|
|
271
|
+
await command.reply({
|
|
272
|
+
content: 'No active session in this thread',
|
|
273
|
+
flags: MessageFlags.Ephemeral | SILENT_MESSAGE_FLAGS,
|
|
274
|
+
})
|
|
275
|
+
return
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
await command.deferReply({ flags: SILENT_MESSAGE_FLAGS })
|
|
279
|
+
|
|
280
|
+
const getClient = await initializeOpencodeForDirectory(projectDirectory)
|
|
281
|
+
if (getClient instanceof Error) {
|
|
282
|
+
await command.editReply(`Failed to redo: ${getClient.message}`)
|
|
283
|
+
return
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
try {
|
|
287
|
+
const client = getClient()
|
|
288
|
+
|
|
289
|
+
// Fetch session to check existing revert state
|
|
290
|
+
const sessionResponse = await client.session.get({
|
|
291
|
+
sessionID: sessionId,
|
|
292
|
+
directory: workingDirectory,
|
|
293
|
+
})
|
|
294
|
+
if (sessionResponse.error) {
|
|
295
|
+
await command.editReply(`Failed to redo: ${JSON.stringify(sessionResponse.error)}`)
|
|
296
|
+
return
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
const revertMessageID = sessionResponse.data?.revert?.messageID
|
|
300
|
+
if (!revertMessageID) {
|
|
301
|
+
await command.editReply('Nothing to redo - no previous undo found')
|
|
302
|
+
return
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
// Abort if session is busy before reverting/unreverting — both enforce
|
|
306
|
+
// assertNotBusy in OpenCode and would fail with "Session is busy"
|
|
307
|
+
const redoStatusResponse = await client.session.status({
|
|
308
|
+
directory: workingDirectory,
|
|
309
|
+
})
|
|
310
|
+
const redoSessionStatus = redoStatusResponse.data?.[sessionId]
|
|
311
|
+
if (redoSessionStatus && redoSessionStatus.type !== 'idle') {
|
|
312
|
+
await client.session.abort({
|
|
313
|
+
sessionID: sessionId,
|
|
314
|
+
directory: workingDirectory,
|
|
315
|
+
}).catch((error) => {
|
|
316
|
+
logger.warn(`[REDO] abort failed for ${sessionId}`, error)
|
|
317
|
+
})
|
|
318
|
+
await waitForSessionIdle({
|
|
319
|
+
client,
|
|
320
|
+
sessionId,
|
|
321
|
+
directory: workingDirectory,
|
|
322
|
+
})
|
|
323
|
+
}
|
|
324
|
+
await new Promise((resolve) => {
|
|
325
|
+
setTimeout(resolve, 500)
|
|
326
|
+
})
|
|
327
|
+
|
|
328
|
+
// Follow the same approach as the OpenCode TUI (use-session-commands.tsx):
|
|
329
|
+
// find the next user message after the current revert point. If one exists,
|
|
330
|
+
// move the revert cursor forward to it (one step redo). If none exists,
|
|
331
|
+
// fully unrevert — we're at the end of the message history.
|
|
332
|
+
const messagesResponse = await client.session.messages({
|
|
333
|
+
sessionID: sessionId,
|
|
334
|
+
directory: workingDirectory,
|
|
335
|
+
})
|
|
336
|
+
if (messagesResponse.error) {
|
|
337
|
+
await command.editReply(`Failed to redo: ${JSON.stringify(messagesResponse.error)}`)
|
|
338
|
+
return
|
|
339
|
+
}
|
|
340
|
+
const userMessages = (messagesResponse.data ?? []).filter((m) => {
|
|
341
|
+
return m.info.role === 'user'
|
|
342
|
+
})
|
|
343
|
+
const nextMessage = userMessages.find((m) => {
|
|
344
|
+
return m.info.id > revertMessageID
|
|
345
|
+
})
|
|
346
|
+
|
|
347
|
+
if (!nextMessage) {
|
|
348
|
+
// No more messages after revert point — fully unrevert
|
|
349
|
+
const response = await client.session.unrevert({
|
|
350
|
+
sessionID: sessionId,
|
|
351
|
+
directory: workingDirectory,
|
|
352
|
+
})
|
|
353
|
+
if (response.error) {
|
|
354
|
+
await command.editReply(
|
|
355
|
+
`Failed to redo: ${JSON.stringify(response.error)}`,
|
|
356
|
+
)
|
|
357
|
+
return
|
|
358
|
+
}
|
|
359
|
+
await command.editReply('Restored - session fully back to previous state')
|
|
360
|
+
logger.log(`Session ${sessionId} unrevert completed`)
|
|
361
|
+
return
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
// Move revert cursor forward one step to the next user message
|
|
365
|
+
const response = await client.session.revert({
|
|
366
|
+
sessionID: sessionId,
|
|
367
|
+
directory: workingDirectory,
|
|
368
|
+
messageID: nextMessage.info.id,
|
|
369
|
+
})
|
|
370
|
+
|
|
371
|
+
if (response.error) {
|
|
372
|
+
await command.editReply(
|
|
373
|
+
`Failed to redo: ${JSON.stringify(response.error)}`,
|
|
374
|
+
)
|
|
375
|
+
return
|
|
376
|
+
}
|
|
377
|
+
|
|
378
|
+
await command.editReply('Restored one step forward')
|
|
379
|
+
logger.log(`Session ${sessionId} redo: moved revert to ${nextMessage.info.id}`)
|
|
380
|
+
} catch (error) {
|
|
381
|
+
logger.error('[REDO] Error:', error)
|
|
382
|
+
await command.editReply(
|
|
383
|
+
`Failed to redo: ${error instanceof Error ? error.message : 'Unknown error'}`,
|
|
384
|
+
)
|
|
385
|
+
}
|
|
386
|
+
}
|
|
@@ -0,0 +1,173 @@
|
|
|
1
|
+
// /unset-model-override command - Remove model overrides and use default instead.
|
|
2
|
+
|
|
3
|
+
import {
|
|
4
|
+
ChatInputCommandInteraction,
|
|
5
|
+
ChannelType,
|
|
6
|
+
type ThreadChannel,
|
|
7
|
+
type TextChannel,
|
|
8
|
+
MessageFlags,
|
|
9
|
+
} from 'discord.js'
|
|
10
|
+
import {
|
|
11
|
+
getChannelModel,
|
|
12
|
+
getSessionModel,
|
|
13
|
+
getThreadSession,
|
|
14
|
+
clearSessionModel,
|
|
15
|
+
} from '../database.js'
|
|
16
|
+
import { getPrisma } from '../db.js'
|
|
17
|
+
import { initializeOpencodeForDirectory } from '../opencode.js'
|
|
18
|
+
import { resolveTextChannel, getKimakiMetadata } from '../discord-utils.js'
|
|
19
|
+
import { getRuntime } from '../session-handler/thread-session-runtime.js'
|
|
20
|
+
import { getCurrentModelInfo } from './model.js'
|
|
21
|
+
import { createLogger, LogPrefix } from '../logger.js'
|
|
22
|
+
|
|
23
|
+
const unsetModelLogger = createLogger(LogPrefix.MODEL)
|
|
24
|
+
|
|
25
|
+
function formatModelSource(type: string, agentName?: string): string {
|
|
26
|
+
switch (type) {
|
|
27
|
+
case 'session':
|
|
28
|
+
return 'session override'
|
|
29
|
+
case 'agent':
|
|
30
|
+
return `agent "${agentName}"`
|
|
31
|
+
case 'channel':
|
|
32
|
+
return 'channel override'
|
|
33
|
+
case 'global':
|
|
34
|
+
return 'global default'
|
|
35
|
+
case 'opencode-config':
|
|
36
|
+
case 'opencode-recent':
|
|
37
|
+
case 'opencode-provider-default':
|
|
38
|
+
return 'opencode default'
|
|
39
|
+
default:
|
|
40
|
+
return 'none'
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* Handle the /unset-model-override slash command.
|
|
46
|
+
* In thread: clears session override if exists, otherwise channel override.
|
|
47
|
+
* In channel: clears channel override.
|
|
48
|
+
*/
|
|
49
|
+
export async function handleUnsetModelCommand({
|
|
50
|
+
interaction,
|
|
51
|
+
appId,
|
|
52
|
+
}: {
|
|
53
|
+
interaction: ChatInputCommandInteraction
|
|
54
|
+
appId: string
|
|
55
|
+
}): Promise<void> {
|
|
56
|
+
unsetModelLogger.log('[UNSET-MODEL] handleUnsetModelCommand called')
|
|
57
|
+
|
|
58
|
+
await interaction.deferReply({ flags: MessageFlags.Ephemeral })
|
|
59
|
+
|
|
60
|
+
const channel = interaction.channel
|
|
61
|
+
|
|
62
|
+
if (!channel) {
|
|
63
|
+
await interaction.editReply({
|
|
64
|
+
content: 'This command can only be used in a channel',
|
|
65
|
+
})
|
|
66
|
+
return
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
const isThread = [
|
|
70
|
+
ChannelType.PublicThread,
|
|
71
|
+
ChannelType.PrivateThread,
|
|
72
|
+
ChannelType.AnnouncementThread,
|
|
73
|
+
].includes(channel.type)
|
|
74
|
+
|
|
75
|
+
let projectDirectory: string | undefined
|
|
76
|
+
let targetChannelId: string
|
|
77
|
+
let sessionId: string | undefined
|
|
78
|
+
|
|
79
|
+
if (isThread) {
|
|
80
|
+
const thread = channel as ThreadChannel
|
|
81
|
+
const textChannel = await resolveTextChannel(thread)
|
|
82
|
+
const metadata = await getKimakiMetadata(textChannel)
|
|
83
|
+
projectDirectory = metadata.projectDirectory
|
|
84
|
+
targetChannelId = textChannel?.id || channel.id
|
|
85
|
+
sessionId = await getThreadSession(thread.id)
|
|
86
|
+
} else if (channel.type === ChannelType.GuildText) {
|
|
87
|
+
const textChannel = channel as TextChannel
|
|
88
|
+
const metadata = await getKimakiMetadata(textChannel)
|
|
89
|
+
projectDirectory = metadata.projectDirectory
|
|
90
|
+
targetChannelId = channel.id
|
|
91
|
+
} else {
|
|
92
|
+
await interaction.editReply({
|
|
93
|
+
content: 'This command can only be used in text channels or threads',
|
|
94
|
+
})
|
|
95
|
+
return
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
if (!projectDirectory) {
|
|
99
|
+
await interaction.editReply({
|
|
100
|
+
content: 'This channel is not configured with a project directory',
|
|
101
|
+
})
|
|
102
|
+
return
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
// Check what overrides exist
|
|
106
|
+
const [sessionPref, channelPref] = await Promise.all([
|
|
107
|
+
sessionId ? getSessionModel(sessionId) : Promise.resolve(undefined),
|
|
108
|
+
getChannelModel(targetChannelId),
|
|
109
|
+
])
|
|
110
|
+
|
|
111
|
+
let clearedType: 'session' | 'channel' | null = null
|
|
112
|
+
let clearedModel: string | undefined
|
|
113
|
+
|
|
114
|
+
if (isThread && sessionId && sessionPref) {
|
|
115
|
+
// In thread with session override: clear session
|
|
116
|
+
await clearSessionModel(sessionId)
|
|
117
|
+
clearedType = 'session'
|
|
118
|
+
clearedModel = sessionPref.modelId
|
|
119
|
+
unsetModelLogger.log(`[UNSET-MODEL] Cleared session model for ${sessionId}`)
|
|
120
|
+
} else if (channelPref) {
|
|
121
|
+
// Clear channel override
|
|
122
|
+
const prisma = await getPrisma()
|
|
123
|
+
await prisma.channel_models.deleteMany({
|
|
124
|
+
where: { channel_id: targetChannelId },
|
|
125
|
+
})
|
|
126
|
+
clearedType = 'channel'
|
|
127
|
+
clearedModel = channelPref.modelId
|
|
128
|
+
unsetModelLogger.log(
|
|
129
|
+
`[UNSET-MODEL] Cleared channel model for ${targetChannelId}`,
|
|
130
|
+
)
|
|
131
|
+
} else {
|
|
132
|
+
await interaction.editReply({
|
|
133
|
+
content: 'No model override to clear.',
|
|
134
|
+
})
|
|
135
|
+
return
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
// Get the new model that will be used
|
|
139
|
+
const getClient = await initializeOpencodeForDirectory(projectDirectory)
|
|
140
|
+
let newModelText = 'unknown'
|
|
141
|
+
|
|
142
|
+
if (!(getClient instanceof Error)) {
|
|
143
|
+
const newModelInfo = await getCurrentModelInfo({
|
|
144
|
+
sessionId,
|
|
145
|
+
channelId: targetChannelId,
|
|
146
|
+
appId,
|
|
147
|
+
getClient,
|
|
148
|
+
})
|
|
149
|
+
|
|
150
|
+
newModelText =
|
|
151
|
+
newModelInfo.type === 'none'
|
|
152
|
+
? 'none'
|
|
153
|
+
: `\`${newModelInfo.model}\` (${formatModelSource(newModelInfo.type, 'agentName' in newModelInfo ? newModelInfo.agentName : undefined)})`
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
// Check if there's a running request and abort+retry with new model (only for session changes in threads)
|
|
157
|
+
let retried = false
|
|
158
|
+
if (isThread && clearedType === 'session' && sessionId) {
|
|
159
|
+
const runtime = getRuntime(channel.id)
|
|
160
|
+
if (runtime) {
|
|
161
|
+
retried = await runtime.retryLastUserPrompt()
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
const clearedTypeText = clearedType === 'session' ? 'Session' : 'Channel'
|
|
166
|
+
const retriedText = retried
|
|
167
|
+
? '\n_Restarting current request with new model..._'
|
|
168
|
+
: ''
|
|
169
|
+
|
|
170
|
+
await interaction.editReply({
|
|
171
|
+
content: `${clearedTypeText} model override removed.\n**Was:** \`${clearedModel}\`\n**Now using:** ${newModelText}${retriedText}`,
|
|
172
|
+
})
|
|
173
|
+
}
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
// /upgrade-and-restart command - Upgrade kimaki to the latest version and restart the bot.
|
|
2
|
+
// Checks npm for a newer version, installs it globally, then spawns a new kimaki process.
|
|
3
|
+
// The new process kills the old one on startup (kimaki's single-instance lock).
|
|
4
|
+
|
|
5
|
+
import type { CommandContext } from './types.js'
|
|
6
|
+
import { SILENT_MESSAGE_FLAGS } from '../discord-utils.js'
|
|
7
|
+
import { createLogger, LogPrefix } from '../logger.js'
|
|
8
|
+
import { getCurrentVersion, upgrade } from '../upgrade.js'
|
|
9
|
+
import { spawn } from 'node:child_process'
|
|
10
|
+
|
|
11
|
+
const logger = createLogger(LogPrefix.CLI)
|
|
12
|
+
|
|
13
|
+
export async function handleUpgradeAndRestartCommand({
|
|
14
|
+
command,
|
|
15
|
+
}: CommandContext): Promise<void> {
|
|
16
|
+
await command.deferReply({ flags: SILENT_MESSAGE_FLAGS })
|
|
17
|
+
|
|
18
|
+
logger.log('[UPGRADE] /upgrade-and-restart triggered')
|
|
19
|
+
|
|
20
|
+
try {
|
|
21
|
+
const currentVersion = getCurrentVersion()
|
|
22
|
+
const newVersion = await upgrade()
|
|
23
|
+
|
|
24
|
+
if (!newVersion) {
|
|
25
|
+
await command.editReply({
|
|
26
|
+
content: `Already on latest version: **v${currentVersion}**`,
|
|
27
|
+
})
|
|
28
|
+
return
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
await command.editReply({
|
|
32
|
+
content: `Upgraded kimaki **v${currentVersion}** -> **v${newVersion}**. Restarting bot...`,
|
|
33
|
+
})
|
|
34
|
+
|
|
35
|
+
// Spawning bare `kimaki` works even if the user originally ran via npx/bunx:
|
|
36
|
+
// `npm i -g kimaki@latest` creates a global bin link, and npx resolves
|
|
37
|
+
// local -> global -> cache -> registry, so it prefers the global install.
|
|
38
|
+
// bunx shares the same global cache, so it also picks up the new version.
|
|
39
|
+
const child = spawn('kimaki', process.argv.slice(2), {
|
|
40
|
+
shell: true,
|
|
41
|
+
stdio: 'ignore',
|
|
42
|
+
detached: true,
|
|
43
|
+
})
|
|
44
|
+
child.unref()
|
|
45
|
+
logger.debug('Started new background kimaki')
|
|
46
|
+
} catch (error) {
|
|
47
|
+
logger.error('[UPGRADE] Failed:', error)
|
|
48
|
+
await command.editReply({
|
|
49
|
+
content: `Upgrade failed: ${error instanceof Error ? error.message : String(error)}`,
|
|
50
|
+
})
|
|
51
|
+
}
|
|
52
|
+
}
|