@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,30 @@
|
|
|
1
|
+
|
|
2
|
+
/* !!! This is code generated by Prisma. Do not edit directly. !!! */
|
|
3
|
+
/* eslint-disable */
|
|
4
|
+
// biome-ignore-all lint: generated file
|
|
5
|
+
// @ts-nocheck
|
|
6
|
+
/*
|
|
7
|
+
* This is a barrel export file for all models and their related types.
|
|
8
|
+
*
|
|
9
|
+
* 🟢 You can import this file directly.
|
|
10
|
+
*/
|
|
11
|
+
export type * from './models/thread_sessions.js'
|
|
12
|
+
export type * from './models/session_events.js'
|
|
13
|
+
export type * from './models/part_messages.js'
|
|
14
|
+
export type * from './models/bot_tokens.js'
|
|
15
|
+
export type * from './models/channel_directories.js'
|
|
16
|
+
export type * from './models/bot_api_keys.js'
|
|
17
|
+
export type * from './models/thread_worktrees.js'
|
|
18
|
+
export type * from './models/channel_models.js'
|
|
19
|
+
export type * from './models/session_models.js'
|
|
20
|
+
export type * from './models/channel_agents.js'
|
|
21
|
+
export type * from './models/session_agents.js'
|
|
22
|
+
export type * from './models/channel_worktrees.js'
|
|
23
|
+
export type * from './models/channel_verbosity.js'
|
|
24
|
+
export type * from './models/channel_mention_mode.js'
|
|
25
|
+
export type * from './models/global_models.js'
|
|
26
|
+
export type * from './models/scheduled_tasks.js'
|
|
27
|
+
export type * from './models/session_start_sources.js'
|
|
28
|
+
export type * from './models/forum_sync_configs.js'
|
|
29
|
+
export type * from './models/ipc_requests.js'
|
|
30
|
+
export type * from './commonInputTypes.js'
|
|
@@ -0,0 +1,152 @@
|
|
|
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
|
+
|
|
15
|
+
import v8 from 'node:v8'
|
|
16
|
+
import fs from 'node:fs'
|
|
17
|
+
import path from 'node:path'
|
|
18
|
+
import zlib from 'node:zlib'
|
|
19
|
+
import { pipeline } from 'node:stream/promises'
|
|
20
|
+
import { fileURLToPath } from 'node:url'
|
|
21
|
+
import { getDataDir } from './config.js'
|
|
22
|
+
import { createLogger, LogPrefix } from './logger.js'
|
|
23
|
+
|
|
24
|
+
const logger = createLogger(LogPrefix.HEAP)
|
|
25
|
+
|
|
26
|
+
const SNAPSHOT_THRESHOLD = 0.85
|
|
27
|
+
const CHECK_INTERVAL_MS = 30_000
|
|
28
|
+
// After writing a snapshot, wait at least 5 minutes before writing another
|
|
29
|
+
const SNAPSHOT_COOLDOWN_MS = 5 * 60 * 1000
|
|
30
|
+
|
|
31
|
+
// Development detection: if this file is .ts we're running from source (tsx/ts-node).
|
|
32
|
+
// Compiled npm package runs from .js files.
|
|
33
|
+
const isDevelopment = fileURLToPath(import.meta.url).endsWith('.ts')
|
|
34
|
+
|
|
35
|
+
let lastSnapshotTime = 0
|
|
36
|
+
let monitorInterval: ReturnType<typeof setInterval> | null = null
|
|
37
|
+
|
|
38
|
+
function getHeapSnapshotDir(): string {
|
|
39
|
+
return path.join(getDataDir(), 'heap-snapshots')
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
function ensureSnapshotDir(): string {
|
|
43
|
+
const dir = getHeapSnapshotDir()
|
|
44
|
+
if (!fs.existsSync(dir)) {
|
|
45
|
+
fs.mkdirSync(dir, { recursive: true })
|
|
46
|
+
}
|
|
47
|
+
return dir
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
function getHeapStats(): { usedMB: number; limitMB: number; ratio: number } {
|
|
51
|
+
const stats = v8.getHeapStatistics()
|
|
52
|
+
const usedMB = stats.used_heap_size / 1024 / 1024
|
|
53
|
+
const limitMB = stats.heap_size_limit / 1024 / 1024
|
|
54
|
+
const ratio = stats.used_heap_size / stats.heap_size_limit
|
|
55
|
+
return { usedMB, limitMB, ratio }
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* Write a gzip-compressed V8 heap snapshot to ~/.kimaki/heap-snapshots/.
|
|
60
|
+
* Uses v8.getHeapSnapshot() streaming API piped through gzip for ~5-10x
|
|
61
|
+
* size reduction compared to v8.writeHeapSnapshot().
|
|
62
|
+
* Filename includes ISO date and current heap size for easy identification.
|
|
63
|
+
* Returns the snapshot file path.
|
|
64
|
+
*/
|
|
65
|
+
export async function writeHeapSnapshot(): Promise<string> {
|
|
66
|
+
const dir = ensureSnapshotDir()
|
|
67
|
+
const { usedMB, limitMB, ratio } = getHeapStats()
|
|
68
|
+
const pct = (ratio * 100).toFixed(1)
|
|
69
|
+
|
|
70
|
+
const timestamp = new Date().toISOString().replace(/[:.]/g, '-')
|
|
71
|
+
const filename = `heap-${timestamp}-${Math.round(usedMB)}MB.heapsnapshot.gz`
|
|
72
|
+
const filepath = path.join(dir, filename)
|
|
73
|
+
|
|
74
|
+
logger.log(
|
|
75
|
+
`Writing compressed heap snapshot (${Math.round(usedMB)}MB / ${Math.round(limitMB)}MB, ${pct}%)`,
|
|
76
|
+
)
|
|
77
|
+
|
|
78
|
+
const snapshotStream = v8.getHeapSnapshot()
|
|
79
|
+
const gzipStream = zlib.createGzip({ level: zlib.constants.Z_BEST_SPEED })
|
|
80
|
+
const fileStream = fs.createWriteStream(filepath)
|
|
81
|
+
|
|
82
|
+
await pipeline(snapshotStream, gzipStream, fileStream)
|
|
83
|
+
|
|
84
|
+
const fileSizeMB = (fs.statSync(filepath).size / 1024 / 1024).toFixed(1)
|
|
85
|
+
logger.log(`Snapshot saved: ${filepath} (${fileSizeMB}MB compressed)`)
|
|
86
|
+
|
|
87
|
+
return filepath
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
async function checkHeapUsage(): Promise<void> {
|
|
91
|
+
const { usedMB, limitMB, ratio } = getHeapStats()
|
|
92
|
+
const pct = (ratio * 100).toFixed(1)
|
|
93
|
+
|
|
94
|
+
if (ratio >= SNAPSHOT_THRESHOLD) {
|
|
95
|
+
logger.warn(
|
|
96
|
+
`Heap at ${pct}% (${Math.round(usedMB)}MB / ${Math.round(limitMB)}MB) - exceeds snapshot threshold (${SNAPSHOT_THRESHOLD * 100}%)`,
|
|
97
|
+
)
|
|
98
|
+
|
|
99
|
+
const now = Date.now()
|
|
100
|
+
if (now - lastSnapshotTime >= SNAPSHOT_COOLDOWN_MS) {
|
|
101
|
+
lastSnapshotTime = now
|
|
102
|
+
try {
|
|
103
|
+
await writeHeapSnapshot()
|
|
104
|
+
} catch (e) {
|
|
105
|
+
logger.error(
|
|
106
|
+
'Failed to write heap snapshot:',
|
|
107
|
+
e instanceof Error ? e.message : String(e),
|
|
108
|
+
)
|
|
109
|
+
}
|
|
110
|
+
} else {
|
|
111
|
+
logger.log('Snapshot cooldown active, skipping')
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
/**
|
|
117
|
+
* Start the periodic heap usage monitor.
|
|
118
|
+
* Checks every 30s and writes snapshots when threshold is exceeded.
|
|
119
|
+
* Only active in development (running from .ts source). In production
|
|
120
|
+
* (compiled .js from npm), this is a no-op to avoid filling user disks.
|
|
121
|
+
*/
|
|
122
|
+
export function startHeapMonitor(): void {
|
|
123
|
+
if (!isDevelopment) {
|
|
124
|
+
return
|
|
125
|
+
}
|
|
126
|
+
if (monitorInterval) {
|
|
127
|
+
return
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
// Ensure the snapshot directory exists so V8's --diagnostic-dir has a valid target.
|
|
131
|
+
// Also needed for our own writeHeapSnapshot() calls.
|
|
132
|
+
ensureSnapshotDir()
|
|
133
|
+
|
|
134
|
+
const { usedMB, limitMB, ratio } = getHeapStats()
|
|
135
|
+
logger.log(
|
|
136
|
+
`Heap monitor started (${Math.round(usedMB)}MB / ${Math.round(limitMB)}MB, ${(ratio * 100).toFixed(1)}%) - ` +
|
|
137
|
+
`snapshot at ${SNAPSHOT_THRESHOLD * 100}%`,
|
|
138
|
+
)
|
|
139
|
+
|
|
140
|
+
monitorInterval = setInterval(() => {
|
|
141
|
+
void checkHeapUsage()
|
|
142
|
+
}, CHECK_INTERVAL_MS)
|
|
143
|
+
// Don't prevent process exit
|
|
144
|
+
monitorInterval.unref()
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
export function stopHeapMonitor(): void {
|
|
148
|
+
if (monitorInterval) {
|
|
149
|
+
clearInterval(monitorInterval)
|
|
150
|
+
monitorInterval = null
|
|
151
|
+
}
|
|
152
|
+
}
|
|
@@ -0,0 +1,434 @@
|
|
|
1
|
+
import fs from 'node:fs'
|
|
2
|
+
import http from 'node:http'
|
|
3
|
+
import path from 'node:path'
|
|
4
|
+
import crypto from 'node:crypto'
|
|
5
|
+
import { fileURLToPath } from 'node:url'
|
|
6
|
+
import { describe, test, expect, afterAll } from 'vitest'
|
|
7
|
+
import Database from 'libsql'
|
|
8
|
+
import { PrismaLibSql } from '@prisma/adapter-libsql'
|
|
9
|
+
import { PrismaClient } from './generated/client.js'
|
|
10
|
+
import {
|
|
11
|
+
createLibsqlHandler,
|
|
12
|
+
createLibsqlNodeHandler,
|
|
13
|
+
libsqlExecutor,
|
|
14
|
+
} from 'libsqlproxy'
|
|
15
|
+
|
|
16
|
+
const __filename = fileURLToPath(import.meta.url)
|
|
17
|
+
const __dirname = path.dirname(__filename)
|
|
18
|
+
|
|
19
|
+
async function migrateSchema(prisma: PrismaClient) {
|
|
20
|
+
const schemaPath = path.join(__dirname, '../src/schema.sql')
|
|
21
|
+
const sql = fs.readFileSync(schemaPath, 'utf-8')
|
|
22
|
+
const statements = sql
|
|
23
|
+
.split(';')
|
|
24
|
+
.map((s) =>
|
|
25
|
+
s
|
|
26
|
+
.split('\n')
|
|
27
|
+
.filter((line) => !line.trimStart().startsWith('--'))
|
|
28
|
+
.join('\n')
|
|
29
|
+
.trim(),
|
|
30
|
+
)
|
|
31
|
+
.filter(
|
|
32
|
+
(s) =>
|
|
33
|
+
s.length > 0 &&
|
|
34
|
+
!/^CREATE\s+TABLE\s+["']?sqlite_sequence["']?\s*\(/i.test(s),
|
|
35
|
+
)
|
|
36
|
+
.map((s) =>
|
|
37
|
+
s
|
|
38
|
+
.replace(
|
|
39
|
+
/^CREATE\s+UNIQUE\s+INDEX\b(?!\s+IF)/i,
|
|
40
|
+
'CREATE UNIQUE INDEX IF NOT EXISTS',
|
|
41
|
+
)
|
|
42
|
+
.replace(/^CREATE\s+INDEX\b(?!\s+IF)/i, 'CREATE INDEX IF NOT EXISTS'),
|
|
43
|
+
)
|
|
44
|
+
for (const statement of statements) {
|
|
45
|
+
await prisma.$executeRawUnsafe(statement)
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
describe('hrana-server', () => {
|
|
50
|
+
let testServer: http.Server | null = null
|
|
51
|
+
let testDb: Database.Database | null = null
|
|
52
|
+
let prisma: PrismaClient | null = null
|
|
53
|
+
const dbPath = path.join(
|
|
54
|
+
process.cwd(),
|
|
55
|
+
`tmp/test-hrana-${crypto.randomUUID().slice(0, 8)}.db`,
|
|
56
|
+
)
|
|
57
|
+
|
|
58
|
+
afterAll(async () => {
|
|
59
|
+
if (prisma) await prisma.$disconnect()
|
|
60
|
+
if (testServer)
|
|
61
|
+
await new Promise<void>((resolve) => {
|
|
62
|
+
testServer!.close(() => {
|
|
63
|
+
resolve()
|
|
64
|
+
})
|
|
65
|
+
})
|
|
66
|
+
if (testDb) testDb.close()
|
|
67
|
+
try {
|
|
68
|
+
fs.unlinkSync(dbPath)
|
|
69
|
+
} catch (e) {
|
|
70
|
+
console.warn('cleanup:', dbPath, (e as Error).message)
|
|
71
|
+
}
|
|
72
|
+
try {
|
|
73
|
+
fs.unlinkSync(dbPath + '-wal')
|
|
74
|
+
} catch (e) {
|
|
75
|
+
console.warn('cleanup:', dbPath + '-wal', (e as Error).message)
|
|
76
|
+
}
|
|
77
|
+
try {
|
|
78
|
+
fs.unlinkSync(dbPath + '-shm')
|
|
79
|
+
} catch (e) {
|
|
80
|
+
console.warn('cleanup:', dbPath + '-shm', (e as Error).message)
|
|
81
|
+
}
|
|
82
|
+
})
|
|
83
|
+
|
|
84
|
+
test('prisma CRUD through hrana server', async () => {
|
|
85
|
+
fs.mkdirSync(path.dirname(dbPath), { recursive: true })
|
|
86
|
+
|
|
87
|
+
const database = new Database(dbPath)
|
|
88
|
+
database.exec('PRAGMA journal_mode = WAL')
|
|
89
|
+
database.exec('PRAGMA busy_timeout = 5000')
|
|
90
|
+
testDb = database
|
|
91
|
+
|
|
92
|
+
const port = 10000 + Math.floor(Math.random() * 50000)
|
|
93
|
+
await new Promise<void>((resolve, reject) => {
|
|
94
|
+
const hranaFetchHandler = createLibsqlHandler(libsqlExecutor(database))
|
|
95
|
+
const hranaNodeHandler = createLibsqlNodeHandler(hranaFetchHandler)
|
|
96
|
+
const srv = http.createServer(hranaNodeHandler)
|
|
97
|
+
srv.on('error', reject)
|
|
98
|
+
srv.listen(port, '127.0.0.1', () => {
|
|
99
|
+
testServer = srv
|
|
100
|
+
resolve()
|
|
101
|
+
})
|
|
102
|
+
})
|
|
103
|
+
|
|
104
|
+
const adapter = new PrismaLibSql({ url: `http://127.0.0.1:${port}` })
|
|
105
|
+
prisma = new PrismaClient({ adapter })
|
|
106
|
+
await migrateSchema(prisma)
|
|
107
|
+
|
|
108
|
+
// Create
|
|
109
|
+
const created = await prisma.thread_sessions.create({
|
|
110
|
+
data: {
|
|
111
|
+
thread_id: 'hrana-test-thread',
|
|
112
|
+
session_id: 'hrana-test-session',
|
|
113
|
+
},
|
|
114
|
+
})
|
|
115
|
+
expect(created.thread_id).toMatchInlineSnapshot(`"hrana-test-thread"`)
|
|
116
|
+
expect(created.session_id).toMatchInlineSnapshot(`"hrana-test-session"`)
|
|
117
|
+
|
|
118
|
+
// Read
|
|
119
|
+
const found = await prisma.thread_sessions.findUnique({
|
|
120
|
+
where: { thread_id: 'hrana-test-thread' },
|
|
121
|
+
})
|
|
122
|
+
expect(found?.session_id).toMatchInlineSnapshot(`"hrana-test-session"`)
|
|
123
|
+
|
|
124
|
+
// Update
|
|
125
|
+
await prisma.thread_sessions.update({
|
|
126
|
+
where: { thread_id: 'hrana-test-thread' },
|
|
127
|
+
data: { session_id: 'updated-session' },
|
|
128
|
+
})
|
|
129
|
+
const updated = await prisma.thread_sessions.findUnique({
|
|
130
|
+
where: { thread_id: 'hrana-test-thread' },
|
|
131
|
+
})
|
|
132
|
+
expect(updated?.session_id).toMatchInlineSnapshot(`"updated-session"`)
|
|
133
|
+
|
|
134
|
+
// Delete
|
|
135
|
+
await prisma.thread_sessions.delete({
|
|
136
|
+
where: { thread_id: 'hrana-test-thread' },
|
|
137
|
+
})
|
|
138
|
+
const deleted = await prisma.thread_sessions.findUnique({
|
|
139
|
+
where: { thread_id: 'hrana-test-thread' },
|
|
140
|
+
})
|
|
141
|
+
expect(deleted).toBeNull()
|
|
142
|
+
}, 30_000)
|
|
143
|
+
|
|
144
|
+
test('$executeRawUnsafe works for PRAGMAs', async () => {
|
|
145
|
+
if (!prisma) throw new Error('prisma not initialized')
|
|
146
|
+
const result = await prisma.$executeRawUnsafe('PRAGMA journal_mode')
|
|
147
|
+
expect(typeof result).toBe('number')
|
|
148
|
+
})
|
|
149
|
+
|
|
150
|
+
test('batch transaction via Prisma $transaction', async () => {
|
|
151
|
+
if (!prisma) throw new Error('prisma not initialized')
|
|
152
|
+
|
|
153
|
+
const [s1, s2] = await prisma.$transaction([
|
|
154
|
+
prisma.thread_sessions.create({
|
|
155
|
+
data: { thread_id: 'batch-1', session_id: 'sess-1' },
|
|
156
|
+
}),
|
|
157
|
+
prisma.thread_sessions.create({
|
|
158
|
+
data: { thread_id: 'batch-2', session_id: 'sess-2' },
|
|
159
|
+
}),
|
|
160
|
+
])
|
|
161
|
+
expect(s1.thread_id).toMatchInlineSnapshot(`"batch-1"`)
|
|
162
|
+
expect(s2.thread_id).toMatchInlineSnapshot(`"batch-2"`)
|
|
163
|
+
|
|
164
|
+
const count = await prisma.thread_sessions.count({
|
|
165
|
+
where: { thread_id: { in: ['batch-1', 'batch-2'] } },
|
|
166
|
+
})
|
|
167
|
+
expect(count).toBe(2)
|
|
168
|
+
|
|
169
|
+
await prisma.thread_sessions.deleteMany({
|
|
170
|
+
where: { thread_id: { in: ['batch-1', 'batch-2'] } },
|
|
171
|
+
})
|
|
172
|
+
}, 30_000)
|
|
173
|
+
|
|
174
|
+
test('schema migration DDL via $executeRawUnsafe', async () => {
|
|
175
|
+
if (!prisma) throw new Error('prisma not initialized')
|
|
176
|
+
|
|
177
|
+
// CREATE TABLE IF NOT EXISTS is idempotent — running migrateSchema again
|
|
178
|
+
// should not throw even though tables already exist.
|
|
179
|
+
await migrateSchema(prisma)
|
|
180
|
+
|
|
181
|
+
// Verify DDL actually created the tables by querying sqlite_master
|
|
182
|
+
const tables = await prisma.$queryRawUnsafe<Array<{ name: string }>>(
|
|
183
|
+
`SELECT name FROM sqlite_master WHERE type='table' ORDER BY name`,
|
|
184
|
+
)
|
|
185
|
+
const tableNames = tables.map((t) => t.name)
|
|
186
|
+
expect(tableNames).toContain('thread_sessions')
|
|
187
|
+
expect(tableNames).toContain('ipc_requests')
|
|
188
|
+
expect(tableNames).toContain('scheduled_tasks')
|
|
189
|
+
|
|
190
|
+
// Also verify indexes were created
|
|
191
|
+
const indexes = await prisma.$queryRawUnsafe<Array<{ name: string }>>(
|
|
192
|
+
`SELECT name FROM sqlite_master WHERE type='index' AND name LIKE '%idx%' ORDER BY name`,
|
|
193
|
+
)
|
|
194
|
+
const indexNames = indexes.map((i) => i.name)
|
|
195
|
+
expect(indexNames).toContain('ipc_requests_status_created_at_idx')
|
|
196
|
+
expect(indexNames).toContain('scheduled_tasks_status_next_run_at_idx')
|
|
197
|
+
|
|
198
|
+
// Test CREATE INDEX IF NOT EXISTS is also idempotent
|
|
199
|
+
await prisma.$executeRawUnsafe(
|
|
200
|
+
`CREATE INDEX IF NOT EXISTS "ipc_requests_status_created_at_idx" ON "ipc_requests"("status", "created_at")`,
|
|
201
|
+
)
|
|
202
|
+
})
|
|
203
|
+
|
|
204
|
+
test('concurrent queries via Promise.all', async () => {
|
|
205
|
+
if (!prisma) throw new Error('prisma not initialized')
|
|
206
|
+
|
|
207
|
+
// Seed some data for concurrent reads
|
|
208
|
+
const threads = Array.from({ length: 5 }, (_, i) => ({
|
|
209
|
+
thread_id: `concurrent-${i}`,
|
|
210
|
+
session_id: `sess-concurrent-${i}`,
|
|
211
|
+
}))
|
|
212
|
+
for (const t of threads) {
|
|
213
|
+
await prisma.thread_sessions.create({ data: t })
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
// Simulate kimaki's pattern of parallel Prisma queries
|
|
217
|
+
const [allThreads, count, single, filtered] = await Promise.all([
|
|
218
|
+
prisma.thread_sessions.findMany({
|
|
219
|
+
where: { thread_id: { startsWith: 'concurrent-' } },
|
|
220
|
+
orderBy: { thread_id: 'asc' },
|
|
221
|
+
}),
|
|
222
|
+
prisma.thread_sessions.count({
|
|
223
|
+
where: { thread_id: { startsWith: 'concurrent-' } },
|
|
224
|
+
}),
|
|
225
|
+
prisma.thread_sessions.findUnique({
|
|
226
|
+
where: { thread_id: 'concurrent-2' },
|
|
227
|
+
}),
|
|
228
|
+
prisma.thread_sessions.findMany({
|
|
229
|
+
where: { thread_id: { in: ['concurrent-0', 'concurrent-4'] } },
|
|
230
|
+
orderBy: { thread_id: 'asc' },
|
|
231
|
+
}),
|
|
232
|
+
])
|
|
233
|
+
|
|
234
|
+
expect(allThreads.length).toBe(5)
|
|
235
|
+
expect(count).toBe(5)
|
|
236
|
+
expect(single?.session_id).toMatchInlineSnapshot(`"sess-concurrent-2"`)
|
|
237
|
+
expect(filtered.map((f) => f.thread_id)).toMatchInlineSnapshot(`
|
|
238
|
+
[
|
|
239
|
+
"concurrent-0",
|
|
240
|
+
"concurrent-4",
|
|
241
|
+
]
|
|
242
|
+
`)
|
|
243
|
+
|
|
244
|
+
// Cleanup
|
|
245
|
+
await prisma.thread_sessions.deleteMany({
|
|
246
|
+
where: { thread_id: { startsWith: 'concurrent-' } },
|
|
247
|
+
})
|
|
248
|
+
}, 30_000)
|
|
249
|
+
|
|
250
|
+
test('$queryRawUnsafe for PRAGMAs that return values', async () => {
|
|
251
|
+
if (!prisma) throw new Error('prisma not initialized')
|
|
252
|
+
|
|
253
|
+
// PRAGMA that returns a value — journal_mode should be WAL
|
|
254
|
+
const journalMode = await prisma.$queryRawUnsafe<
|
|
255
|
+
Array<{ journal_mode: string }>
|
|
256
|
+
>('PRAGMA journal_mode')
|
|
257
|
+
expect(journalMode[0]?.journal_mode).toMatchInlineSnapshot(`"wal"`)
|
|
258
|
+
|
|
259
|
+
// PRAGMA busy_timeout returns the current timeout value
|
|
260
|
+
const busyTimeout = await prisma.$queryRawUnsafe<
|
|
261
|
+
Array<{ busy_timeout: number }>
|
|
262
|
+
>('PRAGMA busy_timeout')
|
|
263
|
+
expect(busyTimeout[0]?.busy_timeout).toMatchInlineSnapshot(`undefined`)
|
|
264
|
+
|
|
265
|
+
// PRAGMA table_info returns column metadata
|
|
266
|
+
const tableInfo = await prisma.$queryRawUnsafe<
|
|
267
|
+
Array<{ name: string; type: string }>
|
|
268
|
+
>(`PRAGMA table_info('ipc_requests')`)
|
|
269
|
+
const colNames = tableInfo.map((c) => c.name)
|
|
270
|
+
expect(colNames).toMatchInlineSnapshot(`
|
|
271
|
+
[
|
|
272
|
+
"id",
|
|
273
|
+
"type",
|
|
274
|
+
"session_id",
|
|
275
|
+
"thread_id",
|
|
276
|
+
"payload",
|
|
277
|
+
"response",
|
|
278
|
+
"status",
|
|
279
|
+
"created_at",
|
|
280
|
+
"updated_at",
|
|
281
|
+
]
|
|
282
|
+
`)
|
|
283
|
+
})
|
|
284
|
+
|
|
285
|
+
test('updateMany with complex WHERE using in operator', async () => {
|
|
286
|
+
if (!prisma) throw new Error('prisma not initialized')
|
|
287
|
+
|
|
288
|
+
// Seed: create a thread + multiple IPC requests in different statuses
|
|
289
|
+
// (mirrors kimaki's cancelAllPendingIpcRequests pattern)
|
|
290
|
+
await prisma.thread_sessions.create({
|
|
291
|
+
data: { thread_id: 'ipc-test-thread', session_id: 'ipc-test-session' },
|
|
292
|
+
})
|
|
293
|
+
const statuses = ['pending', 'pending', 'processing', 'completed'] as const
|
|
294
|
+
for (let i = 0; i < statuses.length; i++) {
|
|
295
|
+
await prisma.ipc_requests.create({
|
|
296
|
+
data: {
|
|
297
|
+
id: `ipc-req-${i}`,
|
|
298
|
+
type: 'file_upload',
|
|
299
|
+
session_id: 'ipc-test-session',
|
|
300
|
+
thread_id: 'ipc-test-thread',
|
|
301
|
+
payload: JSON.stringify({ prompt: `test-${i}` }),
|
|
302
|
+
status: statuses[i],
|
|
303
|
+
},
|
|
304
|
+
})
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
// updateMany with WHERE status IN ['pending', 'processing']
|
|
308
|
+
const result = await prisma.ipc_requests.updateMany({
|
|
309
|
+
where: { status: { in: ['pending', 'processing'] } },
|
|
310
|
+
data: {
|
|
311
|
+
status: 'cancelled',
|
|
312
|
+
response: JSON.stringify({ error: 'Bot shutting down' }),
|
|
313
|
+
},
|
|
314
|
+
})
|
|
315
|
+
expect(result.count).toBe(3)
|
|
316
|
+
|
|
317
|
+
// Verify: only 'completed' row is untouched
|
|
318
|
+
const remaining = await prisma.ipc_requests.findMany({
|
|
319
|
+
where: { thread_id: 'ipc-test-thread' },
|
|
320
|
+
orderBy: { id: 'asc' },
|
|
321
|
+
select: { id: true, status: true },
|
|
322
|
+
})
|
|
323
|
+
expect(remaining).toMatchInlineSnapshot(`
|
|
324
|
+
[
|
|
325
|
+
{
|
|
326
|
+
"id": "ipc-req-0",
|
|
327
|
+
"status": "cancelled",
|
|
328
|
+
},
|
|
329
|
+
{
|
|
330
|
+
"id": "ipc-req-1",
|
|
331
|
+
"status": "cancelled",
|
|
332
|
+
},
|
|
333
|
+
{
|
|
334
|
+
"id": "ipc-req-2",
|
|
335
|
+
"status": "cancelled",
|
|
336
|
+
},
|
|
337
|
+
{
|
|
338
|
+
"id": "ipc-req-3",
|
|
339
|
+
"status": "completed",
|
|
340
|
+
},
|
|
341
|
+
]
|
|
342
|
+
`)
|
|
343
|
+
|
|
344
|
+
// Cleanup
|
|
345
|
+
await prisma.ipc_requests.deleteMany({
|
|
346
|
+
where: { thread_id: 'ipc-test-thread' },
|
|
347
|
+
})
|
|
348
|
+
await prisma.thread_sessions.delete({
|
|
349
|
+
where: { thread_id: 'ipc-test-thread' },
|
|
350
|
+
})
|
|
351
|
+
}, 30_000)
|
|
352
|
+
|
|
353
|
+
test('interactive $transaction (callback form)', async () => {
|
|
354
|
+
if (!prisma) throw new Error('prisma not initialized')
|
|
355
|
+
|
|
356
|
+
// Interactive transaction: reads and writes within the same tx callback.
|
|
357
|
+
// This exercises BEGIN/queries/COMMIT across multiple hrana pipeline
|
|
358
|
+
// requests with batons (stream continuity).
|
|
359
|
+
const result = await prisma.$transaction(async (tx) => {
|
|
360
|
+
await tx.thread_sessions.create({
|
|
361
|
+
data: { thread_id: 'tx-interactive-1', session_id: 'sess-tx-1' },
|
|
362
|
+
})
|
|
363
|
+
await tx.thread_sessions.create({
|
|
364
|
+
data: { thread_id: 'tx-interactive-2', session_id: 'sess-tx-2' },
|
|
365
|
+
})
|
|
366
|
+
|
|
367
|
+
// Read inside the same transaction — should see uncommitted rows
|
|
368
|
+
const count = await tx.thread_sessions.count({
|
|
369
|
+
where: { thread_id: { startsWith: 'tx-interactive-' } },
|
|
370
|
+
})
|
|
371
|
+
|
|
372
|
+
// Conditional write based on read
|
|
373
|
+
if (count === 2) {
|
|
374
|
+
await tx.thread_sessions.update({
|
|
375
|
+
where: { thread_id: 'tx-interactive-1' },
|
|
376
|
+
data: { session_id: 'sess-tx-1-updated' },
|
|
377
|
+
})
|
|
378
|
+
}
|
|
379
|
+
|
|
380
|
+
return tx.thread_sessions.findMany({
|
|
381
|
+
where: { thread_id: { startsWith: 'tx-interactive-' } },
|
|
382
|
+
orderBy: { thread_id: 'asc' },
|
|
383
|
+
select: { thread_id: true, session_id: true },
|
|
384
|
+
})
|
|
385
|
+
})
|
|
386
|
+
|
|
387
|
+
expect(result).toMatchInlineSnapshot(`
|
|
388
|
+
[
|
|
389
|
+
{
|
|
390
|
+
"session_id": "sess-tx-1-updated",
|
|
391
|
+
"thread_id": "tx-interactive-1",
|
|
392
|
+
},
|
|
393
|
+
{
|
|
394
|
+
"session_id": "sess-tx-2",
|
|
395
|
+
"thread_id": "tx-interactive-2",
|
|
396
|
+
},
|
|
397
|
+
]
|
|
398
|
+
`)
|
|
399
|
+
|
|
400
|
+
// Verify committed outside transaction
|
|
401
|
+
const outside = await prisma.thread_sessions.count({
|
|
402
|
+
where: { thread_id: { startsWith: 'tx-interactive-' } },
|
|
403
|
+
})
|
|
404
|
+
expect(outside).toBe(2)
|
|
405
|
+
|
|
406
|
+
// Cleanup
|
|
407
|
+
await prisma.thread_sessions.deleteMany({
|
|
408
|
+
where: { thread_id: { startsWith: 'tx-interactive-' } },
|
|
409
|
+
})
|
|
410
|
+
}, 30_000)
|
|
411
|
+
|
|
412
|
+
test('interactive $transaction rolls back on error', async () => {
|
|
413
|
+
if (!prisma) throw new Error('prisma not initialized')
|
|
414
|
+
|
|
415
|
+
// Verify rollback: if the callback throws, no rows should be committed
|
|
416
|
+
const txError = await prisma
|
|
417
|
+
.$transaction(async (tx) => {
|
|
418
|
+
await tx.thread_sessions.create({
|
|
419
|
+
data: { thread_id: 'tx-rollback-1', session_id: 'sess-rollback' },
|
|
420
|
+
})
|
|
421
|
+
throw new Error('intentional rollback')
|
|
422
|
+
})
|
|
423
|
+
.catch((e: Error) => e)
|
|
424
|
+
|
|
425
|
+
expect(txError).toBeInstanceOf(Error)
|
|
426
|
+
expect((txError as Error).message).toContain('intentional rollback')
|
|
427
|
+
|
|
428
|
+
// Row should NOT exist — transaction was rolled back
|
|
429
|
+
const ghost = await prisma.thread_sessions.findUnique({
|
|
430
|
+
where: { thread_id: 'tx-rollback-1' },
|
|
431
|
+
})
|
|
432
|
+
expect(ghost).toBeNull()
|
|
433
|
+
}, 30_000)
|
|
434
|
+
})
|