@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,223 @@
|
|
|
1
|
+
/* !!! This is code generated by Prisma. Do not edit directly. !!! */
|
|
2
|
+
/* eslint-disable */
|
|
3
|
+
// biome-ignore-all lint: generated file
|
|
4
|
+
// @ts-nocheck
|
|
5
|
+
/*
|
|
6
|
+
* WARNING: This is an internal file that is subject to change!
|
|
7
|
+
*
|
|
8
|
+
* 🛑 Under no circumstances should you import this file directly! 🛑
|
|
9
|
+
*
|
|
10
|
+
* All exports from this file are wrapped under a `Prisma` namespace object in the browser.ts file.
|
|
11
|
+
* While this enables partial backward compatibility, it is not part of the stable public API.
|
|
12
|
+
*
|
|
13
|
+
* If you are looking for your Models, Enums, and Input Types, please import them from the respective
|
|
14
|
+
* model files in the `model` directory!
|
|
15
|
+
*/
|
|
16
|
+
import * as runtime from "@prisma/client/runtime/index-browser";
|
|
17
|
+
export const Decimal = runtime.Decimal;
|
|
18
|
+
export const NullTypes = {
|
|
19
|
+
DbNull: runtime.NullTypes.DbNull,
|
|
20
|
+
JsonNull: runtime.NullTypes.JsonNull,
|
|
21
|
+
AnyNull: runtime.NullTypes.AnyNull,
|
|
22
|
+
};
|
|
23
|
+
/**
|
|
24
|
+
* Helper for filtering JSON entries that have `null` on the database (empty on the db)
|
|
25
|
+
*
|
|
26
|
+
* @see https://www.prisma.io/docs/concepts/components/prisma-client/working-with-fields/working-with-json-fields#filtering-on-a-json-field
|
|
27
|
+
*/
|
|
28
|
+
export const DbNull = runtime.DbNull;
|
|
29
|
+
/**
|
|
30
|
+
* Helper for filtering JSON entries that have JSON `null` values (not empty on the db)
|
|
31
|
+
*
|
|
32
|
+
* @see https://www.prisma.io/docs/concepts/components/prisma-client/working-with-fields/working-with-json-fields#filtering-on-a-json-field
|
|
33
|
+
*/
|
|
34
|
+
export const JsonNull = runtime.JsonNull;
|
|
35
|
+
/**
|
|
36
|
+
* Helper for filtering JSON entries that are `Prisma.DbNull` or `Prisma.JsonNull`
|
|
37
|
+
*
|
|
38
|
+
* @see https://www.prisma.io/docs/concepts/components/prisma-client/working-with-fields/working-with-json-fields#filtering-on-a-json-field
|
|
39
|
+
*/
|
|
40
|
+
export const AnyNull = runtime.AnyNull;
|
|
41
|
+
export const ModelName = {
|
|
42
|
+
thread_sessions: 'thread_sessions',
|
|
43
|
+
session_events: 'session_events',
|
|
44
|
+
part_messages: 'part_messages',
|
|
45
|
+
bot_tokens: 'bot_tokens',
|
|
46
|
+
channel_directories: 'channel_directories',
|
|
47
|
+
bot_api_keys: 'bot_api_keys',
|
|
48
|
+
thread_worktrees: 'thread_worktrees',
|
|
49
|
+
channel_models: 'channel_models',
|
|
50
|
+
session_models: 'session_models',
|
|
51
|
+
channel_agents: 'channel_agents',
|
|
52
|
+
session_agents: 'session_agents',
|
|
53
|
+
channel_worktrees: 'channel_worktrees',
|
|
54
|
+
channel_verbosity: 'channel_verbosity',
|
|
55
|
+
channel_mention_mode: 'channel_mention_mode',
|
|
56
|
+
global_models: 'global_models',
|
|
57
|
+
scheduled_tasks: 'scheduled_tasks',
|
|
58
|
+
session_start_sources: 'session_start_sources',
|
|
59
|
+
forum_sync_configs: 'forum_sync_configs',
|
|
60
|
+
ipc_requests: 'ipc_requests'
|
|
61
|
+
};
|
|
62
|
+
/*
|
|
63
|
+
* Enums
|
|
64
|
+
*/
|
|
65
|
+
export const TransactionIsolationLevel = runtime.makeStrictEnum({
|
|
66
|
+
Serializable: 'Serializable'
|
|
67
|
+
});
|
|
68
|
+
export const Thread_sessionsScalarFieldEnum = {
|
|
69
|
+
thread_id: 'thread_id',
|
|
70
|
+
session_id: 'session_id',
|
|
71
|
+
source: 'source',
|
|
72
|
+
created_at: 'created_at'
|
|
73
|
+
};
|
|
74
|
+
export const Session_eventsScalarFieldEnum = {
|
|
75
|
+
id: 'id',
|
|
76
|
+
session_id: 'session_id',
|
|
77
|
+
thread_id: 'thread_id',
|
|
78
|
+
timestamp: 'timestamp',
|
|
79
|
+
event_index: 'event_index',
|
|
80
|
+
event_json: 'event_json'
|
|
81
|
+
};
|
|
82
|
+
export const Part_messagesScalarFieldEnum = {
|
|
83
|
+
part_id: 'part_id',
|
|
84
|
+
message_id: 'message_id',
|
|
85
|
+
thread_id: 'thread_id',
|
|
86
|
+
created_at: 'created_at'
|
|
87
|
+
};
|
|
88
|
+
export const Bot_tokensScalarFieldEnum = {
|
|
89
|
+
app_id: 'app_id',
|
|
90
|
+
token: 'token',
|
|
91
|
+
bot_mode: 'bot_mode',
|
|
92
|
+
client_id: 'client_id',
|
|
93
|
+
client_secret: 'client_secret',
|
|
94
|
+
proxy_url: 'proxy_url',
|
|
95
|
+
created_at: 'created_at',
|
|
96
|
+
last_used_at: 'last_used_at'
|
|
97
|
+
};
|
|
98
|
+
export const Channel_directoriesScalarFieldEnum = {
|
|
99
|
+
channel_id: 'channel_id',
|
|
100
|
+
directory: 'directory',
|
|
101
|
+
channel_type: 'channel_type',
|
|
102
|
+
created_at: 'created_at'
|
|
103
|
+
};
|
|
104
|
+
export const Bot_api_keysScalarFieldEnum = {
|
|
105
|
+
app_id: 'app_id',
|
|
106
|
+
gemini_api_key: 'gemini_api_key',
|
|
107
|
+
openai_api_key: 'openai_api_key',
|
|
108
|
+
xai_api_key: 'xai_api_key',
|
|
109
|
+
created_at: 'created_at'
|
|
110
|
+
};
|
|
111
|
+
export const Thread_worktreesScalarFieldEnum = {
|
|
112
|
+
thread_id: 'thread_id',
|
|
113
|
+
worktree_name: 'worktree_name',
|
|
114
|
+
worktree_directory: 'worktree_directory',
|
|
115
|
+
project_directory: 'project_directory',
|
|
116
|
+
status: 'status',
|
|
117
|
+
error_message: 'error_message',
|
|
118
|
+
created_at: 'created_at'
|
|
119
|
+
};
|
|
120
|
+
export const Channel_modelsScalarFieldEnum = {
|
|
121
|
+
channel_id: 'channel_id',
|
|
122
|
+
model_id: 'model_id',
|
|
123
|
+
variant: 'variant',
|
|
124
|
+
created_at: 'created_at',
|
|
125
|
+
updated_at: 'updated_at'
|
|
126
|
+
};
|
|
127
|
+
export const Session_modelsScalarFieldEnum = {
|
|
128
|
+
session_id: 'session_id',
|
|
129
|
+
model_id: 'model_id',
|
|
130
|
+
variant: 'variant',
|
|
131
|
+
created_at: 'created_at'
|
|
132
|
+
};
|
|
133
|
+
export const Channel_agentsScalarFieldEnum = {
|
|
134
|
+
channel_id: 'channel_id',
|
|
135
|
+
agent_name: 'agent_name',
|
|
136
|
+
created_at: 'created_at',
|
|
137
|
+
updated_at: 'updated_at'
|
|
138
|
+
};
|
|
139
|
+
export const Session_agentsScalarFieldEnum = {
|
|
140
|
+
session_id: 'session_id',
|
|
141
|
+
agent_name: 'agent_name',
|
|
142
|
+
created_at: 'created_at'
|
|
143
|
+
};
|
|
144
|
+
export const Channel_worktreesScalarFieldEnum = {
|
|
145
|
+
channel_id: 'channel_id',
|
|
146
|
+
enabled: 'enabled',
|
|
147
|
+
created_at: 'created_at',
|
|
148
|
+
updated_at: 'updated_at'
|
|
149
|
+
};
|
|
150
|
+
export const Channel_verbosityScalarFieldEnum = {
|
|
151
|
+
channel_id: 'channel_id',
|
|
152
|
+
verbosity: 'verbosity',
|
|
153
|
+
updated_at: 'updated_at'
|
|
154
|
+
};
|
|
155
|
+
export const Channel_mention_modeScalarFieldEnum = {
|
|
156
|
+
channel_id: 'channel_id',
|
|
157
|
+
enabled: 'enabled',
|
|
158
|
+
created_at: 'created_at',
|
|
159
|
+
updated_at: 'updated_at'
|
|
160
|
+
};
|
|
161
|
+
export const Global_modelsScalarFieldEnum = {
|
|
162
|
+
app_id: 'app_id',
|
|
163
|
+
model_id: 'model_id',
|
|
164
|
+
variant: 'variant',
|
|
165
|
+
created_at: 'created_at',
|
|
166
|
+
updated_at: 'updated_at'
|
|
167
|
+
};
|
|
168
|
+
export const Scheduled_tasksScalarFieldEnum = {
|
|
169
|
+
id: 'id',
|
|
170
|
+
status: 'status',
|
|
171
|
+
schedule_kind: 'schedule_kind',
|
|
172
|
+
run_at: 'run_at',
|
|
173
|
+
cron_expr: 'cron_expr',
|
|
174
|
+
timezone: 'timezone',
|
|
175
|
+
next_run_at: 'next_run_at',
|
|
176
|
+
running_started_at: 'running_started_at',
|
|
177
|
+
last_run_at: 'last_run_at',
|
|
178
|
+
last_error: 'last_error',
|
|
179
|
+
attempts: 'attempts',
|
|
180
|
+
payload_json: 'payload_json',
|
|
181
|
+
prompt_preview: 'prompt_preview',
|
|
182
|
+
channel_id: 'channel_id',
|
|
183
|
+
thread_id: 'thread_id',
|
|
184
|
+
session_id: 'session_id',
|
|
185
|
+
project_directory: 'project_directory',
|
|
186
|
+
created_at: 'created_at',
|
|
187
|
+
updated_at: 'updated_at'
|
|
188
|
+
};
|
|
189
|
+
export const Session_start_sourcesScalarFieldEnum = {
|
|
190
|
+
session_id: 'session_id',
|
|
191
|
+
schedule_kind: 'schedule_kind',
|
|
192
|
+
scheduled_task_id: 'scheduled_task_id',
|
|
193
|
+
created_at: 'created_at',
|
|
194
|
+
updated_at: 'updated_at'
|
|
195
|
+
};
|
|
196
|
+
export const Forum_sync_configsScalarFieldEnum = {
|
|
197
|
+
id: 'id',
|
|
198
|
+
app_id: 'app_id',
|
|
199
|
+
forum_channel_id: 'forum_channel_id',
|
|
200
|
+
output_dir: 'output_dir',
|
|
201
|
+
direction: 'direction',
|
|
202
|
+
created_at: 'created_at',
|
|
203
|
+
updated_at: 'updated_at'
|
|
204
|
+
};
|
|
205
|
+
export const Ipc_requestsScalarFieldEnum = {
|
|
206
|
+
id: 'id',
|
|
207
|
+
type: 'type',
|
|
208
|
+
session_id: 'session_id',
|
|
209
|
+
thread_id: 'thread_id',
|
|
210
|
+
payload: 'payload',
|
|
211
|
+
response: 'response',
|
|
212
|
+
status: 'status',
|
|
213
|
+
created_at: 'created_at',
|
|
214
|
+
updated_at: 'updated_at'
|
|
215
|
+
};
|
|
216
|
+
export const SortOrder = {
|
|
217
|
+
asc: 'asc',
|
|
218
|
+
desc: 'desc'
|
|
219
|
+
};
|
|
220
|
+
export const NullsOrder = {
|
|
221
|
+
first: 'first',
|
|
222
|
+
last: 'last'
|
|
223
|
+
};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
// Heap memory monitor and snapshot writer.
|
|
2
|
+
// Periodically checks V8 heap usage and writes gzip-compressed .heapsnapshot.gz
|
|
3
|
+
// files to ~/.kimaki/heap-snapshots/ when memory usage is high.
|
|
4
|
+
// Also exposes writeHeapSnapshot() for on-demand snapshots via SIGUSR1.
|
|
5
|
+
//
|
|
6
|
+
// Snapshots use v8.getHeapSnapshot() streaming API piped through gzip for ~5-10x
|
|
7
|
+
// size reduction (heap snapshots are JSON, so they compress very well).
|
|
8
|
+
//
|
|
9
|
+
// Only active in development (detected by import.meta.filename ending in .ts).
|
|
10
|
+
// In production (compiled .js from npm), the monitor is a no-op to avoid filling
|
|
11
|
+
// user disks with multi-GB snapshot files.
|
|
12
|
+
//
|
|
13
|
+
// Threshold: 85% heap used -> write snapshot for debugging
|
|
14
|
+
import v8 from 'node:v8';
|
|
15
|
+
import fs from 'node:fs';
|
|
16
|
+
import path from 'node:path';
|
|
17
|
+
import zlib from 'node:zlib';
|
|
18
|
+
import { pipeline } from 'node:stream/promises';
|
|
19
|
+
import { fileURLToPath } from 'node:url';
|
|
20
|
+
import { getDataDir } from './config.js';
|
|
21
|
+
import { createLogger, LogPrefix } from './logger.js';
|
|
22
|
+
const logger = createLogger(LogPrefix.HEAP);
|
|
23
|
+
const SNAPSHOT_THRESHOLD = 0.85;
|
|
24
|
+
const CHECK_INTERVAL_MS = 30_000;
|
|
25
|
+
// After writing a snapshot, wait at least 5 minutes before writing another
|
|
26
|
+
const SNAPSHOT_COOLDOWN_MS = 5 * 60 * 1000;
|
|
27
|
+
// Development detection: if this file is .ts we're running from source (tsx/ts-node).
|
|
28
|
+
// Compiled npm package runs from .js files.
|
|
29
|
+
const isDevelopment = fileURLToPath(import.meta.url).endsWith('.ts');
|
|
30
|
+
let lastSnapshotTime = 0;
|
|
31
|
+
let monitorInterval = null;
|
|
32
|
+
function getHeapSnapshotDir() {
|
|
33
|
+
return path.join(getDataDir(), 'heap-snapshots');
|
|
34
|
+
}
|
|
35
|
+
function ensureSnapshotDir() {
|
|
36
|
+
const dir = getHeapSnapshotDir();
|
|
37
|
+
if (!fs.existsSync(dir)) {
|
|
38
|
+
fs.mkdirSync(dir, { recursive: true });
|
|
39
|
+
}
|
|
40
|
+
return dir;
|
|
41
|
+
}
|
|
42
|
+
function getHeapStats() {
|
|
43
|
+
const stats = v8.getHeapStatistics();
|
|
44
|
+
const usedMB = stats.used_heap_size / 1024 / 1024;
|
|
45
|
+
const limitMB = stats.heap_size_limit / 1024 / 1024;
|
|
46
|
+
const ratio = stats.used_heap_size / stats.heap_size_limit;
|
|
47
|
+
return { usedMB, limitMB, ratio };
|
|
48
|
+
}
|
|
49
|
+
/**
|
|
50
|
+
* Write a gzip-compressed V8 heap snapshot to ~/.kimaki/heap-snapshots/.
|
|
51
|
+
* Uses v8.getHeapSnapshot() streaming API piped through gzip for ~5-10x
|
|
52
|
+
* size reduction compared to v8.writeHeapSnapshot().
|
|
53
|
+
* Filename includes ISO date and current heap size for easy identification.
|
|
54
|
+
* Returns the snapshot file path.
|
|
55
|
+
*/
|
|
56
|
+
export async function writeHeapSnapshot() {
|
|
57
|
+
const dir = ensureSnapshotDir();
|
|
58
|
+
const { usedMB, limitMB, ratio } = getHeapStats();
|
|
59
|
+
const pct = (ratio * 100).toFixed(1);
|
|
60
|
+
const timestamp = new Date().toISOString().replace(/[:.]/g, '-');
|
|
61
|
+
const filename = `heap-${timestamp}-${Math.round(usedMB)}MB.heapsnapshot.gz`;
|
|
62
|
+
const filepath = path.join(dir, filename);
|
|
63
|
+
logger.log(`Writing compressed heap snapshot (${Math.round(usedMB)}MB / ${Math.round(limitMB)}MB, ${pct}%)`);
|
|
64
|
+
const snapshotStream = v8.getHeapSnapshot();
|
|
65
|
+
const gzipStream = zlib.createGzip({ level: zlib.constants.Z_BEST_SPEED });
|
|
66
|
+
const fileStream = fs.createWriteStream(filepath);
|
|
67
|
+
await pipeline(snapshotStream, gzipStream, fileStream);
|
|
68
|
+
const fileSizeMB = (fs.statSync(filepath).size / 1024 / 1024).toFixed(1);
|
|
69
|
+
logger.log(`Snapshot saved: ${filepath} (${fileSizeMB}MB compressed)`);
|
|
70
|
+
return filepath;
|
|
71
|
+
}
|
|
72
|
+
async function checkHeapUsage() {
|
|
73
|
+
const { usedMB, limitMB, ratio } = getHeapStats();
|
|
74
|
+
const pct = (ratio * 100).toFixed(1);
|
|
75
|
+
if (ratio >= SNAPSHOT_THRESHOLD) {
|
|
76
|
+
logger.warn(`Heap at ${pct}% (${Math.round(usedMB)}MB / ${Math.round(limitMB)}MB) - exceeds snapshot threshold (${SNAPSHOT_THRESHOLD * 100}%)`);
|
|
77
|
+
const now = Date.now();
|
|
78
|
+
if (now - lastSnapshotTime >= SNAPSHOT_COOLDOWN_MS) {
|
|
79
|
+
lastSnapshotTime = now;
|
|
80
|
+
try {
|
|
81
|
+
await writeHeapSnapshot();
|
|
82
|
+
}
|
|
83
|
+
catch (e) {
|
|
84
|
+
logger.error('Failed to write heap snapshot:', e instanceof Error ? e.message : String(e));
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
else {
|
|
88
|
+
logger.log('Snapshot cooldown active, skipping');
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
/**
|
|
93
|
+
* Start the periodic heap usage monitor.
|
|
94
|
+
* Checks every 30s and writes snapshots when threshold is exceeded.
|
|
95
|
+
* Only active in development (running from .ts source). In production
|
|
96
|
+
* (compiled .js from npm), this is a no-op to avoid filling user disks.
|
|
97
|
+
*/
|
|
98
|
+
export function startHeapMonitor() {
|
|
99
|
+
if (!isDevelopment) {
|
|
100
|
+
return;
|
|
101
|
+
}
|
|
102
|
+
if (monitorInterval) {
|
|
103
|
+
return;
|
|
104
|
+
}
|
|
105
|
+
// Ensure the snapshot directory exists so V8's --diagnostic-dir has a valid target.
|
|
106
|
+
// Also needed for our own writeHeapSnapshot() calls.
|
|
107
|
+
ensureSnapshotDir();
|
|
108
|
+
const { usedMB, limitMB, ratio } = getHeapStats();
|
|
109
|
+
logger.log(`Heap monitor started (${Math.round(usedMB)}MB / ${Math.round(limitMB)}MB, ${(ratio * 100).toFixed(1)}%) - ` +
|
|
110
|
+
`snapshot at ${SNAPSHOT_THRESHOLD * 100}%`);
|
|
111
|
+
monitorInterval = setInterval(() => {
|
|
112
|
+
void checkHeapUsage();
|
|
113
|
+
}, CHECK_INTERVAL_MS);
|
|
114
|
+
// Don't prevent process exit
|
|
115
|
+
monitorInterval.unref();
|
|
116
|
+
}
|
|
117
|
+
export function stopHeapMonitor() {
|
|
118
|
+
if (monitorInterval) {
|
|
119
|
+
clearInterval(monitorInterval);
|
|
120
|
+
monitorInterval = null;
|
|
121
|
+
}
|
|
122
|
+
}
|
|
@@ -0,0 +1,263 @@
|
|
|
1
|
+
// In-process HTTP server speaking the Hrana v2 protocol.
|
|
2
|
+
// Backed by the `libsql` npm package (better-sqlite3 API).
|
|
3
|
+
// Binds to the fixed lock port for single-instance enforcement.
|
|
4
|
+
//
|
|
5
|
+
// Protocol logic is implemented in the `libsqlproxy` package.
|
|
6
|
+
// This file handles: server lifecycle, single-instance enforcement,
|
|
7
|
+
// auth, and kimaki-specific endpoints (/kimaki/wake, /health).
|
|
8
|
+
//
|
|
9
|
+
// Hrana v2 protocol spec ("Hrana over HTTP"):
|
|
10
|
+
// https://github.com/tursodatabase/libsql/blob/main/docs/HTTP_V2_SPEC.md
|
|
11
|
+
import fs from 'node:fs';
|
|
12
|
+
import http from 'node:http';
|
|
13
|
+
import path from 'node:path';
|
|
14
|
+
import crypto from 'node:crypto';
|
|
15
|
+
import Database from 'libsql';
|
|
16
|
+
import * as errore from 'errore';
|
|
17
|
+
import { createLibsqlHandler, createLibsqlNodeHandler, libsqlExecutor, } from 'libsqlproxy';
|
|
18
|
+
import { createLogger, LogPrefix } from './logger.js';
|
|
19
|
+
import { ServerStartError, FetchError } from './errors.js';
|
|
20
|
+
import { getLockPort } from './config.js';
|
|
21
|
+
import { store } from './store.js';
|
|
22
|
+
const hranaLogger = createLogger(LogPrefix.DB);
|
|
23
|
+
let db = null;
|
|
24
|
+
let server = null;
|
|
25
|
+
let hranaUrl = null;
|
|
26
|
+
let discordGatewayReady = false;
|
|
27
|
+
let readyWaiters = [];
|
|
28
|
+
export function markDiscordGatewayReady() {
|
|
29
|
+
if (discordGatewayReady) {
|
|
30
|
+
return;
|
|
31
|
+
}
|
|
32
|
+
discordGatewayReady = true;
|
|
33
|
+
for (const resolve of readyWaiters) {
|
|
34
|
+
resolve();
|
|
35
|
+
}
|
|
36
|
+
readyWaiters = [];
|
|
37
|
+
}
|
|
38
|
+
async function waitForDiscordGatewayReady({ timeoutMs }) {
|
|
39
|
+
if (discordGatewayReady) {
|
|
40
|
+
return true;
|
|
41
|
+
}
|
|
42
|
+
const readyPromise = new Promise((resolve) => {
|
|
43
|
+
readyWaiters.push(() => {
|
|
44
|
+
resolve(true);
|
|
45
|
+
});
|
|
46
|
+
});
|
|
47
|
+
const timeoutPromise = new Promise((resolve) => {
|
|
48
|
+
setTimeout(() => {
|
|
49
|
+
resolve(false);
|
|
50
|
+
}, timeoutMs);
|
|
51
|
+
});
|
|
52
|
+
return Promise.race([readyPromise, timeoutPromise]);
|
|
53
|
+
}
|
|
54
|
+
function getRequestAuthToken(req) {
|
|
55
|
+
const authorizationHeader = req.headers.authorization;
|
|
56
|
+
if (typeof authorizationHeader === 'string' && authorizationHeader.startsWith('Bearer ')) {
|
|
57
|
+
return authorizationHeader.slice('Bearer '.length);
|
|
58
|
+
}
|
|
59
|
+
return null;
|
|
60
|
+
}
|
|
61
|
+
// Timing-safe comparison to prevent timing attacks when the hrana server
|
|
62
|
+
// is internet-facing (bindAll=true / KIMAKI_INTERNET_REACHABLE_URL set).
|
|
63
|
+
function isAuthorizedRequest(req) {
|
|
64
|
+
const expectedToken = store.getState().gatewayToken;
|
|
65
|
+
if (!expectedToken) {
|
|
66
|
+
return false;
|
|
67
|
+
}
|
|
68
|
+
const providedToken = getRequestAuthToken(req);
|
|
69
|
+
if (!providedToken) {
|
|
70
|
+
return false;
|
|
71
|
+
}
|
|
72
|
+
const expectedBuf = Buffer.from(expectedToken, 'utf8');
|
|
73
|
+
const providedBuf = Buffer.from(providedToken, 'utf8');
|
|
74
|
+
if (expectedBuf.length !== providedBuf.length) {
|
|
75
|
+
return false;
|
|
76
|
+
}
|
|
77
|
+
return crypto.timingSafeEqual(expectedBuf, providedBuf);
|
|
78
|
+
}
|
|
79
|
+
function ensureServiceAuthTokenInStore() {
|
|
80
|
+
const existingToken = store.getState().gatewayToken;
|
|
81
|
+
if (existingToken) {
|
|
82
|
+
return existingToken;
|
|
83
|
+
}
|
|
84
|
+
const generatedToken = `${crypto.randomUUID()}:${crypto.randomBytes(32).toString('hex')}`;
|
|
85
|
+
store.setState({ gatewayToken: generatedToken });
|
|
86
|
+
return generatedToken;
|
|
87
|
+
}
|
|
88
|
+
/**
|
|
89
|
+
* Get the Hrana HTTP URL for injecting into plugin child processes.
|
|
90
|
+
* Returns null if the server hasn't been started yet.
|
|
91
|
+
* Only used for KIMAKI_DB_URL env var in opencode.ts — the bot process
|
|
92
|
+
* itself always uses direct file: access via Prisma.
|
|
93
|
+
*/
|
|
94
|
+
export function getHranaUrl() {
|
|
95
|
+
return hranaUrl;
|
|
96
|
+
}
|
|
97
|
+
/**
|
|
98
|
+
* Start the in-process Hrana v2 server on the fixed lock port.
|
|
99
|
+
* Handles single-instance enforcement: if the port is occupied, kills the
|
|
100
|
+
* existing process first.
|
|
101
|
+
*/
|
|
102
|
+
export async function startHranaServer({ dbPath, bindAll = false, }) {
|
|
103
|
+
if (server && db && hranaUrl)
|
|
104
|
+
return hranaUrl;
|
|
105
|
+
const port = getLockPort();
|
|
106
|
+
const bindHost = bindAll ? '0.0.0.0' : '127.0.0.1';
|
|
107
|
+
const serviceAuthToken = ensureServiceAuthTokenInStore();
|
|
108
|
+
process.env.KIMAKI_DB_AUTH_TOKEN = serviceAuthToken;
|
|
109
|
+
fs.mkdirSync(path.dirname(dbPath), { recursive: true });
|
|
110
|
+
await evictExistingInstance({ port });
|
|
111
|
+
hranaLogger.log(`Starting hrana server on ${bindHost}:${port} with db: ${dbPath}`);
|
|
112
|
+
const database = new Database(dbPath);
|
|
113
|
+
database.exec('PRAGMA journal_mode = WAL');
|
|
114
|
+
database.exec('PRAGMA busy_timeout = 5000');
|
|
115
|
+
db = database;
|
|
116
|
+
// Create the Hrana handler using libsqlproxy
|
|
117
|
+
const hranaFetchHandler = createLibsqlHandler(libsqlExecutor(database));
|
|
118
|
+
const hranaNodeHandler = createLibsqlNodeHandler(hranaFetchHandler);
|
|
119
|
+
// Combined handler: kimaki-specific endpoints + hrana protocol
|
|
120
|
+
const handler = async (req, res) => {
|
|
121
|
+
const pathname = new URL(req.url || '/', 'http://localhost').pathname;
|
|
122
|
+
if (pathname === '/kimaki/wake') {
|
|
123
|
+
if (req.method !== 'POST') {
|
|
124
|
+
res.writeHead(405, { 'content-type': 'application/json' });
|
|
125
|
+
res.end(JSON.stringify({ error: 'method_not_allowed' }));
|
|
126
|
+
return;
|
|
127
|
+
}
|
|
128
|
+
if (!isAuthorizedRequest(req)) {
|
|
129
|
+
res.writeHead(401, { 'content-type': 'application/json' });
|
|
130
|
+
res.end(JSON.stringify({ error: 'unauthorized' }));
|
|
131
|
+
return;
|
|
132
|
+
}
|
|
133
|
+
const isReady = await waitForDiscordGatewayReady({ timeoutMs: 30_000 });
|
|
134
|
+
if (!isReady) {
|
|
135
|
+
res.writeHead(504, { 'content-type': 'application/json' });
|
|
136
|
+
res.end(JSON.stringify({ ready: false, error: 'timeout_waiting_for_discord_ready' }));
|
|
137
|
+
return;
|
|
138
|
+
}
|
|
139
|
+
res.writeHead(200, { 'content-type': 'application/json' });
|
|
140
|
+
res.end(JSON.stringify({ ready: true }));
|
|
141
|
+
return;
|
|
142
|
+
}
|
|
143
|
+
// Health check — no auth required
|
|
144
|
+
if (pathname === '/health') {
|
|
145
|
+
res.writeHead(200, { 'content-type': 'application/json' });
|
|
146
|
+
res.end(JSON.stringify({ status: 'ok', pid: process.pid }));
|
|
147
|
+
return;
|
|
148
|
+
}
|
|
149
|
+
// Hrana routes: /v2, /v2/pipeline — require auth
|
|
150
|
+
if (pathname === '/v2' || pathname === '/v2/pipeline') {
|
|
151
|
+
if (!isAuthorizedRequest(req)) {
|
|
152
|
+
res.writeHead(401, { 'content-type': 'application/json' });
|
|
153
|
+
res.end(JSON.stringify({ error: 'unauthorized' }));
|
|
154
|
+
return;
|
|
155
|
+
}
|
|
156
|
+
hranaNodeHandler(req, res);
|
|
157
|
+
return;
|
|
158
|
+
}
|
|
159
|
+
res.writeHead(404);
|
|
160
|
+
res.end();
|
|
161
|
+
};
|
|
162
|
+
const started = await new Promise((resolve) => {
|
|
163
|
+
const srv = http.createServer(handler);
|
|
164
|
+
srv.on('error', (err) => {
|
|
165
|
+
resolve(new ServerStartError({
|
|
166
|
+
port,
|
|
167
|
+
reason: err.code === 'EADDRINUSE'
|
|
168
|
+
? `Port ${port} still in use after eviction`
|
|
169
|
+
: err.message,
|
|
170
|
+
}));
|
|
171
|
+
});
|
|
172
|
+
srv.listen(port, bindHost, () => {
|
|
173
|
+
server = srv;
|
|
174
|
+
resolve(true);
|
|
175
|
+
});
|
|
176
|
+
});
|
|
177
|
+
if (started instanceof Error) {
|
|
178
|
+
database.close();
|
|
179
|
+
db = null;
|
|
180
|
+
return started;
|
|
181
|
+
}
|
|
182
|
+
hranaUrl = `http://127.0.0.1:${port}`;
|
|
183
|
+
hranaLogger.log(`Hrana server ready at ${hranaUrl}`);
|
|
184
|
+
return hranaUrl;
|
|
185
|
+
}
|
|
186
|
+
/**
|
|
187
|
+
* Stop the Hrana server and close the database.
|
|
188
|
+
*/
|
|
189
|
+
export async function stopHranaServer() {
|
|
190
|
+
if (server) {
|
|
191
|
+
hranaLogger.log('Stopping hrana server...');
|
|
192
|
+
await new Promise((resolve) => {
|
|
193
|
+
server.close(() => {
|
|
194
|
+
resolve();
|
|
195
|
+
});
|
|
196
|
+
});
|
|
197
|
+
server = null;
|
|
198
|
+
}
|
|
199
|
+
if (db) {
|
|
200
|
+
db.close();
|
|
201
|
+
db = null;
|
|
202
|
+
}
|
|
203
|
+
hranaUrl = null;
|
|
204
|
+
discordGatewayReady = false;
|
|
205
|
+
readyWaiters = [];
|
|
206
|
+
hranaLogger.log('Hrana server stopped');
|
|
207
|
+
}
|
|
208
|
+
// ── Single-instance enforcement ──────────────────────────────────────
|
|
209
|
+
/**
|
|
210
|
+
* Evict a previous kimaki instance on the lock port.
|
|
211
|
+
* Fetches /health to get the running process PID, then kills it directly.
|
|
212
|
+
* No lsof/netstat/spawnSync needed — the PID comes from the health response.
|
|
213
|
+
*/
|
|
214
|
+
export async function evictExistingInstance({ port }) {
|
|
215
|
+
const url = `http://127.0.0.1:${port}/health`;
|
|
216
|
+
const probe = await fetch(url, { signal: AbortSignal.timeout(1000) }).catch((e) => new FetchError({ url, cause: e }));
|
|
217
|
+
if (probe instanceof Error)
|
|
218
|
+
return;
|
|
219
|
+
const body = await probe.json().catch((e) => new FetchError({ url, cause: e }));
|
|
220
|
+
if (body instanceof Error)
|
|
221
|
+
return;
|
|
222
|
+
const targetPid = body.pid;
|
|
223
|
+
if (!targetPid || targetPid === process.pid)
|
|
224
|
+
return;
|
|
225
|
+
hranaLogger.log(`Evicting existing kimaki process (PID: ${targetPid}) on port ${port}`);
|
|
226
|
+
const killResult = errore.try({
|
|
227
|
+
try: () => {
|
|
228
|
+
process.kill(targetPid, 'SIGTERM');
|
|
229
|
+
},
|
|
230
|
+
catch: (e) => new Error('Failed to send SIGTERM to existing kimaki process', {
|
|
231
|
+
cause: e,
|
|
232
|
+
}),
|
|
233
|
+
});
|
|
234
|
+
if (killResult instanceof Error) {
|
|
235
|
+
hranaLogger.log(`Failed to kill PID ${targetPid}: ${killResult.message}`);
|
|
236
|
+
return;
|
|
237
|
+
}
|
|
238
|
+
await new Promise((resolve) => {
|
|
239
|
+
setTimeout(resolve, 1000);
|
|
240
|
+
});
|
|
241
|
+
// Verify it's gone — if still alive, escalate to SIGKILL
|
|
242
|
+
const secondProbe = await fetch(url, {
|
|
243
|
+
signal: AbortSignal.timeout(500),
|
|
244
|
+
}).catch((e) => new FetchError({ url, cause: e }));
|
|
245
|
+
if (secondProbe instanceof Error)
|
|
246
|
+
return;
|
|
247
|
+
hranaLogger.log(`PID ${targetPid} still alive after SIGTERM, sending SIGKILL`);
|
|
248
|
+
const forceKillResult = errore.try({
|
|
249
|
+
try: () => {
|
|
250
|
+
process.kill(targetPid, 'SIGKILL');
|
|
251
|
+
},
|
|
252
|
+
catch: (e) => new Error('Failed to send SIGKILL to existing kimaki process', {
|
|
253
|
+
cause: e,
|
|
254
|
+
}),
|
|
255
|
+
});
|
|
256
|
+
if (forceKillResult instanceof Error) {
|
|
257
|
+
hranaLogger.log(`Failed to force-kill PID ${targetPid}: ${forceKillResult.message}`);
|
|
258
|
+
return;
|
|
259
|
+
}
|
|
260
|
+
await new Promise((resolve) => {
|
|
261
|
+
setTimeout(resolve, 1000);
|
|
262
|
+
});
|
|
263
|
+
}
|