@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,326 @@
|
|
|
1
|
+
// IPC polling bridge between the opencode plugin and the Discord bot.
|
|
2
|
+
// The plugin inserts rows into ipc_requests (via Prisma). This module polls
|
|
3
|
+
// that table, claims pending rows atomically, and dispatches them by type.
|
|
4
|
+
// Replaces the old HTTP lock-server approach with DB-based IPC.
|
|
5
|
+
|
|
6
|
+
import * as errore from 'errore'
|
|
7
|
+
import { createTaggedError } from 'errore'
|
|
8
|
+
import type { Client } from 'discord.js'
|
|
9
|
+
import {
|
|
10
|
+
claimPendingIpcRequests,
|
|
11
|
+
completeIpcRequest,
|
|
12
|
+
cancelAllPendingIpcRequests,
|
|
13
|
+
cancelStaleProcessingRequests,
|
|
14
|
+
} from './database.js'
|
|
15
|
+
import { showFileUploadButton } from './commands/file-upload.js'
|
|
16
|
+
import { queueActionButtonsRequest } from './commands/action-buttons.js'
|
|
17
|
+
import type { ActionButtonColor } from './commands/action-buttons.js'
|
|
18
|
+
import { createLogger, LogPrefix } from './logger.js'
|
|
19
|
+
import { notifyError } from './sentry.js'
|
|
20
|
+
|
|
21
|
+
const ipcLogger = createLogger(LogPrefix.IPC)
|
|
22
|
+
|
|
23
|
+
// ── Tagged errors ────────────────────────────────────────────────────────
|
|
24
|
+
|
|
25
|
+
class IpcDispatchError extends createTaggedError({
|
|
26
|
+
name: 'IpcDispatchError',
|
|
27
|
+
message: 'IPC dispatch failed for request $requestId: $reason',
|
|
28
|
+
}) {}
|
|
29
|
+
|
|
30
|
+
// ── Button parsing ───────────────────────────────────────────────────────
|
|
31
|
+
|
|
32
|
+
const VALID_COLORS = new Set<ActionButtonColor>([
|
|
33
|
+
'white',
|
|
34
|
+
'blue',
|
|
35
|
+
'green',
|
|
36
|
+
'red',
|
|
37
|
+
])
|
|
38
|
+
|
|
39
|
+
type ParsedButton = { label: string; color?: ActionButtonColor }
|
|
40
|
+
|
|
41
|
+
function parseButtons(raw: unknown): ParsedButton[] {
|
|
42
|
+
if (!Array.isArray(raw)) return []
|
|
43
|
+
const results: ParsedButton[] = []
|
|
44
|
+
for (const value of raw) {
|
|
45
|
+
if (!value || typeof value !== 'object') continue
|
|
46
|
+
const label = (typeof value.label === 'string' ? value.label : '')
|
|
47
|
+
.trim()
|
|
48
|
+
.slice(0, 80)
|
|
49
|
+
if (!label) continue
|
|
50
|
+
const color =
|
|
51
|
+
typeof value.color === 'string' &&
|
|
52
|
+
VALID_COLORS.has(value.color as ActionButtonColor)
|
|
53
|
+
? (value.color as ActionButtonColor)
|
|
54
|
+
: undefined
|
|
55
|
+
results.push({ label, color })
|
|
56
|
+
if (results.length >= 3) break
|
|
57
|
+
}
|
|
58
|
+
return results
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
// ── Request dispatch ─────────────────────────────────────────────────────
|
|
62
|
+
|
|
63
|
+
type ClaimedRequest = {
|
|
64
|
+
id: string
|
|
65
|
+
type: string
|
|
66
|
+
session_id: string
|
|
67
|
+
thread_id: string
|
|
68
|
+
payload: string
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
async function dispatchRequest({
|
|
72
|
+
req,
|
|
73
|
+
discordClient,
|
|
74
|
+
}: {
|
|
75
|
+
req: ClaimedRequest
|
|
76
|
+
discordClient: Client
|
|
77
|
+
}) {
|
|
78
|
+
switch (req.type) {
|
|
79
|
+
case 'file_upload': {
|
|
80
|
+
const parsed = errore.try({
|
|
81
|
+
try: () =>
|
|
82
|
+
JSON.parse(req.payload) as {
|
|
83
|
+
prompt?: string
|
|
84
|
+
maxFiles?: number
|
|
85
|
+
directory?: string
|
|
86
|
+
},
|
|
87
|
+
catch: (e) =>
|
|
88
|
+
new IpcDispatchError({
|
|
89
|
+
requestId: req.id,
|
|
90
|
+
reason: 'Invalid payload JSON',
|
|
91
|
+
cause: e,
|
|
92
|
+
}),
|
|
93
|
+
})
|
|
94
|
+
if (parsed instanceof Error) {
|
|
95
|
+
await completeIpcRequest({
|
|
96
|
+
id: req.id,
|
|
97
|
+
response: JSON.stringify({ error: parsed.message }),
|
|
98
|
+
})
|
|
99
|
+
return parsed
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
const thread = await discordClient.channels
|
|
103
|
+
.fetch(req.thread_id)
|
|
104
|
+
.catch(
|
|
105
|
+
(e) =>
|
|
106
|
+
new IpcDispatchError({
|
|
107
|
+
requestId: req.id,
|
|
108
|
+
reason: 'Thread fetch failed',
|
|
109
|
+
cause: e,
|
|
110
|
+
}),
|
|
111
|
+
)
|
|
112
|
+
if (thread instanceof Error) {
|
|
113
|
+
await completeIpcRequest({
|
|
114
|
+
id: req.id,
|
|
115
|
+
response: JSON.stringify({ error: 'Thread not found' }),
|
|
116
|
+
})
|
|
117
|
+
return thread
|
|
118
|
+
}
|
|
119
|
+
if (!thread?.isThread()) {
|
|
120
|
+
await completeIpcRequest({
|
|
121
|
+
id: req.id,
|
|
122
|
+
response: JSON.stringify({ error: 'Thread not found' }),
|
|
123
|
+
})
|
|
124
|
+
return new IpcDispatchError({
|
|
125
|
+
requestId: req.id,
|
|
126
|
+
reason: 'Channel is not a thread',
|
|
127
|
+
})
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
// Fire-and-forget: showFileUploadButton waits for user interaction
|
|
131
|
+
// (button click + modal + file download) which can take minutes.
|
|
132
|
+
// Don't block the dispatch loop — complete the IPC request asynchronously.
|
|
133
|
+
showFileUploadButton({
|
|
134
|
+
thread,
|
|
135
|
+
sessionId: req.session_id,
|
|
136
|
+
directory: parsed.directory || '',
|
|
137
|
+
prompt: parsed.prompt || 'Please upload files',
|
|
138
|
+
maxFiles: Math.min(10, Math.max(1, parsed.maxFiles || 5)),
|
|
139
|
+
})
|
|
140
|
+
.then((filePaths) => {
|
|
141
|
+
return completeIpcRequest({
|
|
142
|
+
id: req.id,
|
|
143
|
+
response: JSON.stringify({ filePaths }),
|
|
144
|
+
})
|
|
145
|
+
})
|
|
146
|
+
.catch((e) => {
|
|
147
|
+
ipcLogger.error(
|
|
148
|
+
'[IPC] File upload error:',
|
|
149
|
+
e instanceof Error ? e.message : String(e),
|
|
150
|
+
)
|
|
151
|
+
return completeIpcRequest({
|
|
152
|
+
id: req.id,
|
|
153
|
+
response: JSON.stringify({
|
|
154
|
+
error: e instanceof Error ? e.message : 'File upload failed',
|
|
155
|
+
}),
|
|
156
|
+
})
|
|
157
|
+
})
|
|
158
|
+
.catch((e) => {
|
|
159
|
+
void notifyError(e, 'IPC file upload completion update failed')
|
|
160
|
+
})
|
|
161
|
+
return
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
case 'action_buttons': {
|
|
165
|
+
const parsed = errore.try({
|
|
166
|
+
try: () =>
|
|
167
|
+
JSON.parse(req.payload) as { buttons?: unknown; directory?: string },
|
|
168
|
+
catch: (e) =>
|
|
169
|
+
new IpcDispatchError({
|
|
170
|
+
requestId: req.id,
|
|
171
|
+
reason: 'Invalid payload JSON',
|
|
172
|
+
cause: e,
|
|
173
|
+
}),
|
|
174
|
+
})
|
|
175
|
+
if (parsed instanceof Error) {
|
|
176
|
+
await completeIpcRequest({
|
|
177
|
+
id: req.id,
|
|
178
|
+
response: JSON.stringify({ error: parsed.message }),
|
|
179
|
+
})
|
|
180
|
+
return parsed
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
const buttons = parseButtons(parsed.buttons)
|
|
184
|
+
if (buttons.length === 0) {
|
|
185
|
+
await completeIpcRequest({
|
|
186
|
+
id: req.id,
|
|
187
|
+
response: JSON.stringify({ error: 'No valid buttons' }),
|
|
188
|
+
})
|
|
189
|
+
return
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
const thread = await discordClient.channels
|
|
193
|
+
.fetch(req.thread_id)
|
|
194
|
+
.catch(
|
|
195
|
+
(e) =>
|
|
196
|
+
new IpcDispatchError({
|
|
197
|
+
requestId: req.id,
|
|
198
|
+
reason: 'Thread fetch failed',
|
|
199
|
+
cause: e,
|
|
200
|
+
}),
|
|
201
|
+
)
|
|
202
|
+
if (thread instanceof Error) {
|
|
203
|
+
await completeIpcRequest({
|
|
204
|
+
id: req.id,
|
|
205
|
+
response: JSON.stringify({ error: 'Thread not found' }),
|
|
206
|
+
})
|
|
207
|
+
return thread
|
|
208
|
+
}
|
|
209
|
+
if (!thread?.isThread()) {
|
|
210
|
+
await completeIpcRequest({
|
|
211
|
+
id: req.id,
|
|
212
|
+
response: JSON.stringify({ error: 'Thread not found' }),
|
|
213
|
+
})
|
|
214
|
+
return new IpcDispatchError({
|
|
215
|
+
requestId: req.id,
|
|
216
|
+
reason: 'Channel is not a thread',
|
|
217
|
+
})
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
queueActionButtonsRequest({
|
|
221
|
+
sessionId: req.session_id,
|
|
222
|
+
threadId: req.thread_id,
|
|
223
|
+
directory: parsed.directory || '',
|
|
224
|
+
buttons,
|
|
225
|
+
})
|
|
226
|
+
|
|
227
|
+
await completeIpcRequest({
|
|
228
|
+
id: req.id,
|
|
229
|
+
response: JSON.stringify({ ok: true }),
|
|
230
|
+
})
|
|
231
|
+
return
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
default: {
|
|
235
|
+
await completeIpcRequest({
|
|
236
|
+
id: req.id,
|
|
237
|
+
response: JSON.stringify({ error: `Unknown IPC type: ${req.type}` }),
|
|
238
|
+
})
|
|
239
|
+
return
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
// ── Polling lifecycle ────────────────────────────────────────────────────
|
|
245
|
+
|
|
246
|
+
let pollingInterval: ReturnType<typeof setInterval> | null = null
|
|
247
|
+
|
|
248
|
+
// Cancel requests stuck in 'processing' longer than 24 hours. Users often
|
|
249
|
+
// come back the next day to click permission/question/file-upload buttons,
|
|
250
|
+
// so we keep IPC rows alive for a full day. Checked every 30 seconds.
|
|
251
|
+
const STALE_TTL_MS = 24 * 60 * 60 * 1000
|
|
252
|
+
const STALE_CHECK_INTERVAL_MS = 30 * 1000
|
|
253
|
+
let lastStaleCheck = 0
|
|
254
|
+
|
|
255
|
+
/**
|
|
256
|
+
* Start polling the ipc_requests table for pending requests from the plugin.
|
|
257
|
+
* Claims rows atomically (pending -> processing) to prevent duplicate dispatch.
|
|
258
|
+
* Uses an in-flight guard to prevent overlapping poll ticks.
|
|
259
|
+
*/
|
|
260
|
+
export async function startIpcPolling({
|
|
261
|
+
discordClient,
|
|
262
|
+
}: {
|
|
263
|
+
discordClient: Client
|
|
264
|
+
}) {
|
|
265
|
+
// Clean up stale requests from previous runs before first poll tick
|
|
266
|
+
await cancelAllPendingIpcRequests().catch((e) => {
|
|
267
|
+
ipcLogger.warn('Failed to cancel stale IPC requests:', (e as Error).message)
|
|
268
|
+
void notifyError(e, 'Failed to cancel stale IPC requests')
|
|
269
|
+
})
|
|
270
|
+
|
|
271
|
+
let polling = false
|
|
272
|
+
pollingInterval = setInterval(async () => {
|
|
273
|
+
if (polling) return
|
|
274
|
+
polling = true
|
|
275
|
+
|
|
276
|
+
// Periodically sweep requests stuck in 'processing' past the TTL
|
|
277
|
+
const now = Date.now()
|
|
278
|
+
if (now - lastStaleCheck > STALE_CHECK_INTERVAL_MS) {
|
|
279
|
+
lastStaleCheck = now
|
|
280
|
+
await cancelStaleProcessingRequests({ ttlMs: STALE_TTL_MS }).catch(
|
|
281
|
+
(e) => {
|
|
282
|
+
ipcLogger.warn('Stale sweep failed:', (e as Error).message)
|
|
283
|
+
void notifyError(e, 'IPC stale sweep failed')
|
|
284
|
+
},
|
|
285
|
+
)
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
const claimed = await claimPendingIpcRequests().catch(
|
|
289
|
+
(e) =>
|
|
290
|
+
new IpcDispatchError({
|
|
291
|
+
requestId: 'poll',
|
|
292
|
+
reason: 'Claim failed',
|
|
293
|
+
cause: e,
|
|
294
|
+
}),
|
|
295
|
+
)
|
|
296
|
+
if (claimed instanceof Error) {
|
|
297
|
+
ipcLogger.error('IPC claim failed:', claimed.message)
|
|
298
|
+
void notifyError(claimed, 'IPC claim failed')
|
|
299
|
+
polling = false
|
|
300
|
+
return
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
for (const req of claimed) {
|
|
304
|
+
const result = await dispatchRequest({ req, discordClient }).catch(
|
|
305
|
+
(e) =>
|
|
306
|
+
new IpcDispatchError({
|
|
307
|
+
requestId: req.id,
|
|
308
|
+
reason: 'Dispatch threw',
|
|
309
|
+
cause: e,
|
|
310
|
+
}),
|
|
311
|
+
)
|
|
312
|
+
if (result instanceof Error) {
|
|
313
|
+
ipcLogger.error(`IPC dispatch error for ${req.type}:`, result.message)
|
|
314
|
+
void notifyError(result, `IPC dispatch error for ${req.type}`)
|
|
315
|
+
}
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
polling = false
|
|
319
|
+
}, 200)
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
export function stopIpcPolling() {
|
|
323
|
+
if (!pollingInterval) return
|
|
324
|
+
clearInterval(pollingInterval)
|
|
325
|
+
pollingInterval = null
|
|
326
|
+
}
|
|
@@ -0,0 +1,236 @@
|
|
|
1
|
+
// OpenCode plugin that provides IPC-based tools for Discord interaction:
|
|
2
|
+
// - kimaki_file_upload: prompts the Discord user to upload files via native picker
|
|
3
|
+
// - kimaki_action_buttons: shows clickable action buttons in the Discord thread
|
|
4
|
+
//
|
|
5
|
+
// Tools communicate with the bot process via IPC rows in SQLite (the plugin
|
|
6
|
+
// runs inside the OpenCode server process, not the bot process).
|
|
7
|
+
//
|
|
8
|
+
// Exported from kimaki-opencode-plugin.ts — each export is treated as a separate
|
|
9
|
+
// plugin by OpenCode's plugin loader.
|
|
10
|
+
|
|
11
|
+
import type { Plugin } from '@opencode-ai/plugin'
|
|
12
|
+
import type { ToolContext } from '@opencode-ai/plugin/tool'
|
|
13
|
+
import dedent from 'string-dedent'
|
|
14
|
+
import { z } from 'zod'
|
|
15
|
+
import { setDataDir } from './config.js'
|
|
16
|
+
import { createPluginLogger, setPluginLogFilePath } from './plugin-logger.js'
|
|
17
|
+
import { initSentry } from './sentry.js'
|
|
18
|
+
|
|
19
|
+
// Inlined from '@opencode-ai/plugin/tool' because the subpath value import
|
|
20
|
+
// fails at runtime in global npm installs (#35). Opencode loads this plugin
|
|
21
|
+
// file in its own process and resolves modules from kimaki's install dir,
|
|
22
|
+
// but the '/tool' subpath export isn't found by opencode's module resolver.
|
|
23
|
+
// The type-only imports above are fine (erased at compile time).
|
|
24
|
+
//
|
|
25
|
+
// NOTE: @opencode-ai/plugin bundles its own zod 4.1.x as a hard dependency
|
|
26
|
+
// while goke (used by cli.ts) requires zod 4.3.x. This version skew makes
|
|
27
|
+
// the Plugin return type structurally incompatible with our local tool()
|
|
28
|
+
// even though runtime behavior is identical. ipcToolsPlugin is cast to
|
|
29
|
+
// Plugin via unknown to bypass this purely type-level incompatibility.
|
|
30
|
+
function tool<Args extends z.ZodRawShape>(input: {
|
|
31
|
+
description: string
|
|
32
|
+
args: Args
|
|
33
|
+
execute(
|
|
34
|
+
args: z.infer<z.ZodObject<Args>>,
|
|
35
|
+
context: ToolContext,
|
|
36
|
+
): Promise<string>
|
|
37
|
+
}) {
|
|
38
|
+
return input
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
const logger = createPluginLogger('OPENCODE')
|
|
42
|
+
|
|
43
|
+
const FILE_UPLOAD_TIMEOUT_MS = 6 * 60 * 1000
|
|
44
|
+
const DEFAULT_FILE_UPLOAD_MAX_FILES = 5
|
|
45
|
+
const ACTION_BUTTON_TIMEOUT_MS = 30 * 1000
|
|
46
|
+
|
|
47
|
+
async function loadDatabaseModule() {
|
|
48
|
+
// The plugin-loading e2e test boots OpenCode directly without the bot-side
|
|
49
|
+
// Hrana env vars. Lazy-loading avoids pulling Prisma + libsql sqlite mode
|
|
50
|
+
// during plugin startup when no IPC tool is being executed yet.
|
|
51
|
+
return import('./database.js')
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
// @opencode-ai/plugin bundles zod 4.1.x as a hard dep; our code uses 4.3.x
|
|
55
|
+
// (required by goke for ~standard.jsonSchema). The Plugin return type is
|
|
56
|
+
// structurally incompatible due to _zod.version.minor skew even though
|
|
57
|
+
// runtime behavior is identical. `any` bypasses the type-level mismatch —
|
|
58
|
+
// opencode's plugin loader doesn't care about the zod version at runtime.
|
|
59
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
60
|
+
const ipcToolsPlugin: any = async () => {
|
|
61
|
+
initSentry()
|
|
62
|
+
|
|
63
|
+
const dataDir = process.env.KIMAKI_DATA_DIR
|
|
64
|
+
if (dataDir) {
|
|
65
|
+
setDataDir(dataDir)
|
|
66
|
+
setPluginLogFilePath(dataDir)
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
return {
|
|
70
|
+
tool: {
|
|
71
|
+
kimaki_file_upload: tool({
|
|
72
|
+
description:
|
|
73
|
+
'Prompt the Discord user to upload files using a native file picker modal. ' +
|
|
74
|
+
'The user sees a button, clicks it, and gets a file upload dialog. ' +
|
|
75
|
+
'Returns the local file paths of downloaded files in the project directory. ' +
|
|
76
|
+
'Use this when you need the user to provide files (images, documents, configs, etc.). ' +
|
|
77
|
+
'IMPORTANT: Always call this tool last in your message, after all text parts.',
|
|
78
|
+
args: {
|
|
79
|
+
prompt: z
|
|
80
|
+
.string()
|
|
81
|
+
.describe(
|
|
82
|
+
'Message shown to the user explaining what files to upload',
|
|
83
|
+
),
|
|
84
|
+
maxFiles: z
|
|
85
|
+
.number()
|
|
86
|
+
.min(1)
|
|
87
|
+
.max(10)
|
|
88
|
+
.optional()
|
|
89
|
+
.describe(
|
|
90
|
+
'Maximum number of files the user can upload (1-10, default 5)',
|
|
91
|
+
),
|
|
92
|
+
},
|
|
93
|
+
async execute({ prompt, maxFiles }, context) {
|
|
94
|
+
const { getPrisma, createIpcRequest, getIpcRequestById } = await loadDatabaseModule()
|
|
95
|
+
const prisma = await getPrisma()
|
|
96
|
+
const row = await prisma.thread_sessions.findFirst({
|
|
97
|
+
where: { session_id: context.sessionID },
|
|
98
|
+
select: { thread_id: true },
|
|
99
|
+
})
|
|
100
|
+
|
|
101
|
+
if (!row?.thread_id) {
|
|
102
|
+
return 'Could not find thread for current session'
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
const ipcRow = await createIpcRequest({
|
|
106
|
+
type: 'file_upload',
|
|
107
|
+
sessionId: context.sessionID,
|
|
108
|
+
threadId: row.thread_id,
|
|
109
|
+
payload: JSON.stringify({
|
|
110
|
+
prompt,
|
|
111
|
+
maxFiles: maxFiles || DEFAULT_FILE_UPLOAD_MAX_FILES,
|
|
112
|
+
directory: context.directory,
|
|
113
|
+
}),
|
|
114
|
+
})
|
|
115
|
+
|
|
116
|
+
const deadline = Date.now() + FILE_UPLOAD_TIMEOUT_MS
|
|
117
|
+
const POLL_INTERVAL_MS = 300
|
|
118
|
+
while (Date.now() < deadline) {
|
|
119
|
+
await new Promise((resolve) => {
|
|
120
|
+
setTimeout(resolve, POLL_INTERVAL_MS)
|
|
121
|
+
})
|
|
122
|
+
const updated = await getIpcRequestById({ id: ipcRow.id })
|
|
123
|
+
if (!updated || updated.status === 'cancelled') {
|
|
124
|
+
return 'File upload was cancelled'
|
|
125
|
+
}
|
|
126
|
+
if (updated.response) {
|
|
127
|
+
const parsed = JSON.parse(updated.response) as {
|
|
128
|
+
filePaths?: string[]
|
|
129
|
+
error?: string
|
|
130
|
+
}
|
|
131
|
+
if (parsed.error) {
|
|
132
|
+
return `File upload failed: ${parsed.error}`
|
|
133
|
+
}
|
|
134
|
+
const filePaths = parsed.filePaths || []
|
|
135
|
+
if (filePaths.length === 0) {
|
|
136
|
+
return 'No files were uploaded (user may have cancelled or sent a new message)'
|
|
137
|
+
}
|
|
138
|
+
return `Files uploaded successfully:\n${filePaths.join('\n')}`
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
return 'File upload timed out - user did not upload files within the time limit'
|
|
143
|
+
},
|
|
144
|
+
}),
|
|
145
|
+
kimaki_action_buttons: tool({
|
|
146
|
+
description: dedent`
|
|
147
|
+
Show action buttons in the current Discord thread for quick confirmations.
|
|
148
|
+
Use this when the user can respond by clicking one of up to 3 buttons.
|
|
149
|
+
Prefer a single button whenever possible.
|
|
150
|
+
Default color is white (same visual style as permission deny button).
|
|
151
|
+
If you need more than 3 options, use the question tool instead.
|
|
152
|
+
IMPORTANT: Always call this tool last in your message, after all text parts.
|
|
153
|
+
|
|
154
|
+
Examples:
|
|
155
|
+
- buttons: [{"label":"Yes, proceed"}]
|
|
156
|
+
- buttons: [{"label":"Approve","color":"green"}]
|
|
157
|
+
- buttons: [
|
|
158
|
+
{"label":"Confirm","color":"blue"},
|
|
159
|
+
{"label":"Cancel","color":"white"}
|
|
160
|
+
]
|
|
161
|
+
`,
|
|
162
|
+
args: {
|
|
163
|
+
buttons: z
|
|
164
|
+
.array(
|
|
165
|
+
z.object({
|
|
166
|
+
label: z
|
|
167
|
+
.string()
|
|
168
|
+
.min(1)
|
|
169
|
+
.max(80)
|
|
170
|
+
.describe('Button label shown to the user (1-80 chars)'),
|
|
171
|
+
color: z
|
|
172
|
+
.enum(['white', 'blue', 'green', 'red'])
|
|
173
|
+
.optional()
|
|
174
|
+
.describe(
|
|
175
|
+
'Optional button color. white is default and preferred for most confirmations.',
|
|
176
|
+
),
|
|
177
|
+
}),
|
|
178
|
+
)
|
|
179
|
+
.min(1)
|
|
180
|
+
.max(3)
|
|
181
|
+
.describe(
|
|
182
|
+
'Array of 1-3 action buttons. Prefer one button whenever possible.',
|
|
183
|
+
),
|
|
184
|
+
},
|
|
185
|
+
async execute({ buttons }, context) {
|
|
186
|
+
const { getPrisma, createIpcRequest, getIpcRequestById } = await loadDatabaseModule()
|
|
187
|
+
const prisma = await getPrisma()
|
|
188
|
+
const row = await prisma.thread_sessions.findFirst({
|
|
189
|
+
where: { session_id: context.sessionID },
|
|
190
|
+
select: { thread_id: true },
|
|
191
|
+
})
|
|
192
|
+
|
|
193
|
+
if (!row?.thread_id) {
|
|
194
|
+
return 'Could not find thread for current session'
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
const ipcRow = await createIpcRequest({
|
|
198
|
+
type: 'action_buttons',
|
|
199
|
+
sessionId: context.sessionID,
|
|
200
|
+
threadId: row.thread_id,
|
|
201
|
+
payload: JSON.stringify({
|
|
202
|
+
buttons,
|
|
203
|
+
directory: context.directory,
|
|
204
|
+
}),
|
|
205
|
+
})
|
|
206
|
+
|
|
207
|
+
const deadline = Date.now() + ACTION_BUTTON_TIMEOUT_MS
|
|
208
|
+
const POLL_INTERVAL_MS = 200
|
|
209
|
+
while (Date.now() < deadline) {
|
|
210
|
+
await new Promise((resolve) => {
|
|
211
|
+
setTimeout(resolve, POLL_INTERVAL_MS)
|
|
212
|
+
})
|
|
213
|
+
const updated = await getIpcRequestById({ id: ipcRow.id })
|
|
214
|
+
if (!updated || updated.status === 'cancelled') {
|
|
215
|
+
return 'Action button request was cancelled'
|
|
216
|
+
}
|
|
217
|
+
if (updated.response) {
|
|
218
|
+
const parsed = JSON.parse(updated.response) as {
|
|
219
|
+
ok?: boolean
|
|
220
|
+
error?: string
|
|
221
|
+
}
|
|
222
|
+
if (parsed.error) {
|
|
223
|
+
return `Action button request failed: ${parsed.error}`
|
|
224
|
+
}
|
|
225
|
+
return `Action button(s) shown: ${buttons.map((button) => button.label).join(', ')}`
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
return 'Action button request timed out'
|
|
230
|
+
},
|
|
231
|
+
}),
|
|
232
|
+
},
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
export { ipcToolsPlugin }
|