@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,183 @@
|
|
|
1
|
+
// Model resolution utilities.
|
|
2
|
+
// getDefaultModel resolves the default model from OpenCode when no user preference is set.
|
|
3
|
+
|
|
4
|
+
import fs from 'node:fs'
|
|
5
|
+
import path from 'node:path'
|
|
6
|
+
import { xdgState } from 'xdg-basedir'
|
|
7
|
+
import * as errore from 'errore'
|
|
8
|
+
import { type initializeOpencodeForDirectory } from '../opencode.js'
|
|
9
|
+
import { createLogger, LogPrefix } from '../logger.js'
|
|
10
|
+
import type { ScheduledTaskScheduleKind } from '../database.js'
|
|
11
|
+
|
|
12
|
+
const sessionLogger = createLogger(LogPrefix.SESSION)
|
|
13
|
+
|
|
14
|
+
export type DefaultModelSource =
|
|
15
|
+
| 'opencode-config'
|
|
16
|
+
| 'opencode-recent'
|
|
17
|
+
| 'opencode-provider-default'
|
|
18
|
+
|
|
19
|
+
export type SessionStartSourceContext = {
|
|
20
|
+
scheduleKind: ScheduledTaskScheduleKind
|
|
21
|
+
scheduledTaskId?: number
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Read user's recent models from OpenCode TUI's state file.
|
|
26
|
+
* Uses same path as OpenCode: path.join(xdgState, "opencode", "model.json")
|
|
27
|
+
* Returns all recent models so we can iterate until finding a valid one.
|
|
28
|
+
* See: opensrc/repos/github.com/sst/opencode/packages/opencode/src/global/index.ts
|
|
29
|
+
*/
|
|
30
|
+
function getRecentModelsFromTuiState(): Array<{
|
|
31
|
+
providerID: string
|
|
32
|
+
modelID: string
|
|
33
|
+
}> {
|
|
34
|
+
if (!xdgState) {
|
|
35
|
+
return []
|
|
36
|
+
}
|
|
37
|
+
// Same path as OpenCode TUI: path.join(Global.Path.state, "model.json")
|
|
38
|
+
const modelJsonPath = path.join(xdgState, 'opencode', 'model.json')
|
|
39
|
+
|
|
40
|
+
const result = errore.tryFn(() => {
|
|
41
|
+
const content = fs.readFileSync(modelJsonPath, 'utf-8')
|
|
42
|
+
const data = JSON.parse(content) as {
|
|
43
|
+
recent?: Array<{ providerID: string; modelID: string }>
|
|
44
|
+
}
|
|
45
|
+
return data.recent ?? []
|
|
46
|
+
})
|
|
47
|
+
|
|
48
|
+
if (result instanceof Error) {
|
|
49
|
+
// File doesn't exist or is invalid - this is normal for fresh installs
|
|
50
|
+
return []
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
return result
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* Parse a model string in format "provider/model" into providerID and modelID.
|
|
58
|
+
*/
|
|
59
|
+
function parseModelString(
|
|
60
|
+
model: string,
|
|
61
|
+
): { providerID: string; modelID: string } | undefined {
|
|
62
|
+
const [providerID, ...modelParts] = model.split('/')
|
|
63
|
+
const modelID = modelParts.join('/')
|
|
64
|
+
if (!providerID || !modelID) {
|
|
65
|
+
return undefined
|
|
66
|
+
}
|
|
67
|
+
return { providerID, modelID }
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* Validate that a model is available (provider connected + model exists).
|
|
72
|
+
*/
|
|
73
|
+
function isModelValid(
|
|
74
|
+
model: { providerID: string; modelID: string },
|
|
75
|
+
connected: string[],
|
|
76
|
+
providers: Array<{ id: string; models?: Record<string, unknown> }>,
|
|
77
|
+
): boolean {
|
|
78
|
+
const isConnected = connected.includes(model.providerID)
|
|
79
|
+
const provider = providers.find((p) => {
|
|
80
|
+
return p.id === model.providerID
|
|
81
|
+
})
|
|
82
|
+
const modelExists = provider?.models && model.modelID in provider.models
|
|
83
|
+
return isConnected && !!modelExists
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
/**
|
|
87
|
+
* Get the default model from OpenCode when no user preference is set.
|
|
88
|
+
* Priority (matches OpenCode TUI behavior):
|
|
89
|
+
* 1. OpenCode config.model setting
|
|
90
|
+
* 2. User's recent models from TUI state (~/.local/state/opencode/model.json)
|
|
91
|
+
* 3. First connected provider's default model from API
|
|
92
|
+
* Returns the model and its source.
|
|
93
|
+
*/
|
|
94
|
+
export async function getDefaultModel({
|
|
95
|
+
getClient,
|
|
96
|
+
}: {
|
|
97
|
+
getClient: Awaited<ReturnType<typeof initializeOpencodeForDirectory>>
|
|
98
|
+
}): Promise<
|
|
99
|
+
| { providerID: string; modelID: string; source: DefaultModelSource }
|
|
100
|
+
| undefined
|
|
101
|
+
> {
|
|
102
|
+
if (getClient instanceof Error) {
|
|
103
|
+
return undefined
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
// Fetch connected providers to validate any model we return
|
|
107
|
+
const providersResponse = await errore.tryAsync(() => {
|
|
108
|
+
return getClient().provider.list({})
|
|
109
|
+
})
|
|
110
|
+
if (providersResponse instanceof Error) {
|
|
111
|
+
sessionLogger.log(
|
|
112
|
+
`[MODEL] Failed to fetch providers for default model:`,
|
|
113
|
+
providersResponse.message,
|
|
114
|
+
)
|
|
115
|
+
return undefined
|
|
116
|
+
}
|
|
117
|
+
if (!providersResponse.data) {
|
|
118
|
+
return undefined
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
const {
|
|
122
|
+
connected,
|
|
123
|
+
default: defaults,
|
|
124
|
+
all: providers,
|
|
125
|
+
} = providersResponse.data
|
|
126
|
+
if (connected.length === 0) {
|
|
127
|
+
sessionLogger.log(`[MODEL] No connected providers found`)
|
|
128
|
+
return undefined
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
// 1. Check OpenCode config.model setting (highest priority after user preference)
|
|
132
|
+
const configResponse = await errore.tryAsync(() => {
|
|
133
|
+
return getClient().config.get({})
|
|
134
|
+
})
|
|
135
|
+
if (!(configResponse instanceof Error) && configResponse.data?.model) {
|
|
136
|
+
const configModel = parseModelString(configResponse.data.model)
|
|
137
|
+
if (configModel && isModelValid(configModel, connected, providers)) {
|
|
138
|
+
sessionLogger.log(
|
|
139
|
+
`[MODEL] Using config model: ${configModel.providerID}/${configModel.modelID}`,
|
|
140
|
+
)
|
|
141
|
+
return { ...configModel, source: 'opencode-config' }
|
|
142
|
+
}
|
|
143
|
+
if (configModel) {
|
|
144
|
+
sessionLogger.log(
|
|
145
|
+
`[MODEL] Config model ${configResponse.data.model} not available, checking recent`,
|
|
146
|
+
)
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
// 2. Try to use user's recent models from TUI state (iterate until finding valid one)
|
|
151
|
+
const recentModels = getRecentModelsFromTuiState()
|
|
152
|
+
for (const recentModel of recentModels) {
|
|
153
|
+
if (isModelValid(recentModel, connected, providers)) {
|
|
154
|
+
sessionLogger.log(
|
|
155
|
+
`[MODEL] Using recent TUI model: ${recentModel.providerID}/${recentModel.modelID}`,
|
|
156
|
+
)
|
|
157
|
+
return { ...recentModel, source: 'opencode-recent' }
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
if (recentModels.length > 0) {
|
|
161
|
+
sessionLogger.log(`[MODEL] No valid recent TUI models found`)
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
// 3. Fall back to first connected provider's default model
|
|
165
|
+
const firstConnected = connected[0]
|
|
166
|
+
if (!firstConnected) {
|
|
167
|
+
return undefined
|
|
168
|
+
}
|
|
169
|
+
const defaultModelId = defaults[firstConnected]
|
|
170
|
+
if (!defaultModelId) {
|
|
171
|
+
sessionLogger.log(`[MODEL] No default model for provider ${firstConnected}`)
|
|
172
|
+
return undefined
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
sessionLogger.log(
|
|
176
|
+
`[MODEL] Using provider default: ${firstConnected}/${defaultModelId}`,
|
|
177
|
+
)
|
|
178
|
+
return {
|
|
179
|
+
providerID: firstConnected,
|
|
180
|
+
modelID: defaultModelId,
|
|
181
|
+
source: 'opencode-provider-default',
|
|
182
|
+
}
|
|
183
|
+
}
|
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
// Debug helper for writing raw OpenCode event stream entries as JSONL.
|
|
2
|
+
// When enabled, writes one file per session ID so event ordering and
|
|
3
|
+
// lifecycle behavior can be analyzed with jq.
|
|
4
|
+
|
|
5
|
+
import fs from 'node:fs'
|
|
6
|
+
import path from 'node:path'
|
|
7
|
+
import type { Event as OpenCodeEvent } from '@opencode-ai/sdk/v2'
|
|
8
|
+
import * as errore from 'errore'
|
|
9
|
+
import { getDataDir } from '../config.js'
|
|
10
|
+
|
|
11
|
+
let eventLogDirPromise: Promise<string> | null = null
|
|
12
|
+
let eventLogWriteDisabled = false
|
|
13
|
+
|
|
14
|
+
export function isOpencodeSessionEventLogEnabled(): boolean {
|
|
15
|
+
return process.env['KIMAKI_LOG_OPENCODE_SESSION_EVENTS'] === '1'
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export function getOpencodeEventSessionId(event: OpenCodeEvent): string | undefined {
|
|
19
|
+
switch (event.type) {
|
|
20
|
+
case 'message.updated':
|
|
21
|
+
return event.properties.info.sessionID
|
|
22
|
+
case 'message.part.updated':
|
|
23
|
+
return event.properties.part.sessionID
|
|
24
|
+
case 'message.part.delta':
|
|
25
|
+
case 'message.part.removed':
|
|
26
|
+
case 'session.status':
|
|
27
|
+
case 'session.idle':
|
|
28
|
+
case 'session.diff':
|
|
29
|
+
case 'permission.asked':
|
|
30
|
+
case 'permission.replied':
|
|
31
|
+
case 'question.asked':
|
|
32
|
+
case 'question.replied':
|
|
33
|
+
case 'question.rejected':
|
|
34
|
+
return event.properties.sessionID
|
|
35
|
+
case 'session.error':
|
|
36
|
+
return event.properties.sessionID
|
|
37
|
+
case 'session.created':
|
|
38
|
+
case 'session.updated':
|
|
39
|
+
case 'session.deleted':
|
|
40
|
+
return event.properties.info.id
|
|
41
|
+
default:
|
|
42
|
+
return undefined
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
function sanitizeSessionIdForFilename(sessionId: string): string {
|
|
47
|
+
return sessionId.replace(/[^a-zA-Z0-9._-]/g, '_')
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
async function resolveEventLogDirectory(): Promise<string> {
|
|
51
|
+
if (!eventLogDirPromise) {
|
|
52
|
+
eventLogDirPromise = (async () => {
|
|
53
|
+
const configuredEventLogDir = process.env['KIMAKI_OPENCODE_SESSION_EVENTS_DIR']
|
|
54
|
+
const baseDir = configuredEventLogDir || path.join(getDataDir(), 'opencode-session-events')
|
|
55
|
+
await fs.promises.mkdir(baseDir, { recursive: true })
|
|
56
|
+
return baseDir
|
|
57
|
+
})()
|
|
58
|
+
}
|
|
59
|
+
return eventLogDirPromise
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
export type OpencodeEventLogEntry = {
|
|
63
|
+
timestamp: number
|
|
64
|
+
threadId: string
|
|
65
|
+
projectDirectory: string
|
|
66
|
+
event: OpenCodeEvent
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
export function buildOpencodeEventLogLine({
|
|
70
|
+
timestamp,
|
|
71
|
+
threadId,
|
|
72
|
+
projectDirectory,
|
|
73
|
+
event,
|
|
74
|
+
}: {
|
|
75
|
+
timestamp: number
|
|
76
|
+
threadId: string
|
|
77
|
+
projectDirectory: string
|
|
78
|
+
event: OpenCodeEvent
|
|
79
|
+
}): OpencodeEventLogEntry {
|
|
80
|
+
return {
|
|
81
|
+
timestamp,
|
|
82
|
+
threadId,
|
|
83
|
+
projectDirectory,
|
|
84
|
+
event,
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
export async function appendOpencodeSessionEventLog(
|
|
89
|
+
entry: Omit<OpencodeEventLogEntry, 'timestamp'>,
|
|
90
|
+
): Promise<Error | null> {
|
|
91
|
+
if (!isOpencodeSessionEventLogEnabled() || eventLogWriteDisabled) {
|
|
92
|
+
return null
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
const sessionId = getOpencodeEventSessionId(entry.event)
|
|
96
|
+
if (!sessionId) {
|
|
97
|
+
return null
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
const logDirResult = await errore.tryAsync(() => {
|
|
101
|
+
return resolveEventLogDirectory()
|
|
102
|
+
})
|
|
103
|
+
if (logDirResult instanceof Error) {
|
|
104
|
+
eventLogWriteDisabled = true
|
|
105
|
+
return logDirResult
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
const safeSessionId = sanitizeSessionIdForFilename(sessionId)
|
|
109
|
+
const logFilePath = path.join(logDirResult, `${safeSessionId}.jsonl`)
|
|
110
|
+
|
|
111
|
+
const now = Date.now()
|
|
112
|
+
const line = `${JSON.stringify(
|
|
113
|
+
buildOpencodeEventLogLine({
|
|
114
|
+
timestamp: now,
|
|
115
|
+
threadId: entry.threadId,
|
|
116
|
+
projectDirectory: entry.projectDirectory,
|
|
117
|
+
event: entry.event,
|
|
118
|
+
}),
|
|
119
|
+
)}\n`
|
|
120
|
+
|
|
121
|
+
const appendResult = await errore.tryAsync(() => {
|
|
122
|
+
return fs.promises.appendFile(logFilePath, line, 'utf8')
|
|
123
|
+
})
|
|
124
|
+
if (appendResult instanceof Error) {
|
|
125
|
+
eventLogWriteDisabled = true
|
|
126
|
+
return appendResult
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
return null
|
|
130
|
+
}
|
|
@@ -0,0 +1,212 @@
|
|
|
1
|
+
// Per-thread state type, transition functions, and selectors.
|
|
2
|
+
// All transitions operate on the global store from ../store.js.
|
|
3
|
+
//
|
|
4
|
+
// ThreadRunState is a value-type: one entry per active thread in the
|
|
5
|
+
// global store's `threads` Map. Transition functions produce new Map +
|
|
6
|
+
// new ThreadRunState objects each time (immutable updates).
|
|
7
|
+
//
|
|
8
|
+
// Derived helpers (queue checks) compute from state and are never
|
|
9
|
+
// stored — they are always re-derived from ThreadRunState.
|
|
10
|
+
//
|
|
11
|
+
// STATE DISCIPLINE: keep as little state as possible. Before adding any new
|
|
12
|
+
// state field, ask if it can be derived from existing state instead.
|
|
13
|
+
|
|
14
|
+
import type { DiscordFileAttachment } from '../message-formatting.js'
|
|
15
|
+
import type { RepliedMessageContext } from '../system-message.js'
|
|
16
|
+
import { store } from '../store.js'
|
|
17
|
+
|
|
18
|
+
// ── Shared types ─────────────────────────────────────────────────
|
|
19
|
+
|
|
20
|
+
export type QueuedMessage = {
|
|
21
|
+
// The text content to send to the OpenCode session (user message or
|
|
22
|
+
// transcribed voice message). Always present.
|
|
23
|
+
prompt: string
|
|
24
|
+
// Discord user ID of the message author. Used for permission checks
|
|
25
|
+
// and attribution in the session start source tracking.
|
|
26
|
+
userId: string
|
|
27
|
+
// Discord display name. Used in runtime drain logging.
|
|
28
|
+
username: string
|
|
29
|
+
// Image/file attachments extracted from the Discord message. Sent as
|
|
30
|
+
// file parts alongside the prompt in the SDK call.
|
|
31
|
+
images?: DiscordFileAttachment[]
|
|
32
|
+
// Bot application ID. Used for model-preference resolution fallback
|
|
33
|
+
// (looking up channel/session model overrides keyed by appId).
|
|
34
|
+
appId?: string
|
|
35
|
+
// When set, dispatches via session.command() instead of session.prompt().
|
|
36
|
+
// Used by /queue-command and user-defined slash commands.
|
|
37
|
+
command?: { name: string; arguments: string }
|
|
38
|
+
// First-dispatch-only overrides — used when creating a new session.
|
|
39
|
+
// Subsequent queue drains ignore these since the session already exists.
|
|
40
|
+
// Set by --agent/--model/--permission flags on kimaki send or slash commands.
|
|
41
|
+
agent?: string
|
|
42
|
+
model?: string
|
|
43
|
+
// Raw permission rule strings ("tool:action" or "tool:pattern:action").
|
|
44
|
+
// Parsed and merged into session permissions on creation.
|
|
45
|
+
permissions?: string[]
|
|
46
|
+
// Injection guard scan patterns (e.g. "bash:*", "webfetch:*").
|
|
47
|
+
// Written to a temp config file after session creation so the plugin
|
|
48
|
+
// can check per-session whether to scan tool outputs.
|
|
49
|
+
injectionGuardPatterns?: string[]
|
|
50
|
+
// Discord message ID and thread ID of the source message. Embedded in
|
|
51
|
+
// <discord-user> synthetic context so the external sync loop can detect
|
|
52
|
+
// messages that originated from Discord and skip re-mirroring them.
|
|
53
|
+
sourceMessageId?: string
|
|
54
|
+
sourceThreadId?: string
|
|
55
|
+
repliedMessage?: RepliedMessageContext
|
|
56
|
+
// Tracking fields for scheduled tasks. Stored in the DB via
|
|
57
|
+
// setSessionStartSource() after the session is created, so the session
|
|
58
|
+
// list can show which sessions were started by scheduled tasks.
|
|
59
|
+
sessionStartScheduleKind?: 'at' | 'cron'
|
|
60
|
+
sessionStartScheduledTaskId?: number
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
// ── Per-thread state (value inside the Map) ──────────────────────
|
|
64
|
+
|
|
65
|
+
export type ThreadRunState = {
|
|
66
|
+
// OpenCode session ID for this thread. Set lazily by ensureSession()
|
|
67
|
+
// on first dispatch (not on thread creation). Persists across multiple
|
|
68
|
+
// prompt runs — the same session is reused for the thread's lifetime.
|
|
69
|
+
// Also stored in the DB (thread_sessions table) for recovery after restart.
|
|
70
|
+
// Changes: set on first dispatch, may be re-set if the old session is
|
|
71
|
+
// invalid and a new one is created. Never cleared except on dispose.
|
|
72
|
+
// Read by: dispatchPrompt, ensureSession, abortSessionViaApi, footer.
|
|
73
|
+
sessionId: string | undefined
|
|
74
|
+
|
|
75
|
+
// Stable first author for this thread runtime. Used for session-stable
|
|
76
|
+
// system prompt examples like `kimaki send --user ...` so notifications keep
|
|
77
|
+
// working without changing the cached system prompt on every follow-up.
|
|
78
|
+
sessionUsername: string | undefined
|
|
79
|
+
|
|
80
|
+
// FIFO queue of pending inputs waiting for kimaki-local dispatch.
|
|
81
|
+
// Normal user messages default to opencode queue mode; this queue is
|
|
82
|
+
// for explicit local-queue flows (for example /queue).
|
|
83
|
+
// Changes: enqueueItem (append), dequeueItem (head removal), clearQueueItems.
|
|
84
|
+
// Read by: runtime queue gating, hasQueue helpers, /queue command display.
|
|
85
|
+
queueItems: QueuedMessage[]
|
|
86
|
+
|
|
87
|
+
// Listener lifetime controller — scoped to the entire runtime lifetime,
|
|
88
|
+
// NOT per-prompt. Only aborted on dispose() or fatal error. Run abort
|
|
89
|
+
// never kills the listener — the SSE event loop stays alive across runs
|
|
90
|
+
// so subsequent prompts reuse the same listener.
|
|
91
|
+
// Changes: created in constructor, aborted only on dispose.
|
|
92
|
+
listenerController: AbortController | undefined
|
|
93
|
+
|
|
94
|
+
// Output dedup: tracks which part IDs have already been sent to Discord.
|
|
95
|
+
// Prevents resending the same tool output or text part on SSE reconnect.
|
|
96
|
+
// Lives at thread level because it accumulates
|
|
97
|
+
// across runs for the runtime's lifetime — never reset per-run.
|
|
98
|
+
// Changes: bootstrapped from DB on session resume, added on each part
|
|
99
|
+
// sent to Discord, removed on send failure, also updated in subtask flows.
|
|
100
|
+
// Read by: handleMainPart() dedup check, subtask routing.
|
|
101
|
+
sentPartIds: Set<string>
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
// ── Initial state factory ────────────────────────────────────────
|
|
105
|
+
|
|
106
|
+
export function initialThreadState(): ThreadRunState {
|
|
107
|
+
return {
|
|
108
|
+
sessionId: undefined,
|
|
109
|
+
sessionUsername: undefined,
|
|
110
|
+
queueItems: [],
|
|
111
|
+
listenerController: undefined,
|
|
112
|
+
sentPartIds: new Set(),
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
// ── Derived helpers (compute, never store) ───────────────────────
|
|
117
|
+
|
|
118
|
+
export function hasQueue(t: ThreadRunState): boolean {
|
|
119
|
+
return t.queueItems.length > 0
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
// ── Pure transition helpers ──────────────────────────────────────
|
|
123
|
+
// Immutable: produces new Map + new ThreadRunState object each time.
|
|
124
|
+
|
|
125
|
+
export function updateThread(
|
|
126
|
+
threadId: string,
|
|
127
|
+
updater: (t: ThreadRunState) => ThreadRunState,
|
|
128
|
+
): void {
|
|
129
|
+
store.setState((s) => {
|
|
130
|
+
const existing = s.threads.get(threadId)
|
|
131
|
+
if (!existing) {
|
|
132
|
+
return s
|
|
133
|
+
}
|
|
134
|
+
const newThreads = new Map(s.threads)
|
|
135
|
+
newThreads.set(threadId, updater(existing))
|
|
136
|
+
return { threads: newThreads }
|
|
137
|
+
})
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
export function ensureThread(threadId: string): void {
|
|
141
|
+
if (store.getState().threads.has(threadId)) {
|
|
142
|
+
return
|
|
143
|
+
}
|
|
144
|
+
store.setState((s) => {
|
|
145
|
+
const newThreads = new Map(s.threads)
|
|
146
|
+
newThreads.set(threadId, initialThreadState())
|
|
147
|
+
return { threads: newThreads }
|
|
148
|
+
})
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
export function removeThread(threadId: string): void {
|
|
152
|
+
store.setState((s) => {
|
|
153
|
+
if (!s.threads.has(threadId)) {
|
|
154
|
+
return s
|
|
155
|
+
}
|
|
156
|
+
const newThreads = new Map(s.threads)
|
|
157
|
+
newThreads.delete(threadId)
|
|
158
|
+
return { threads: newThreads }
|
|
159
|
+
})
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
export function setSessionId(threadId: string, sessionId: string): void {
|
|
163
|
+
updateThread(threadId, (t) => ({ ...t, sessionId }))
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
export function setSessionUsername(threadId: string, username: string): void {
|
|
167
|
+
updateThread(threadId, (t) => {
|
|
168
|
+
if (t.sessionUsername) {
|
|
169
|
+
return t
|
|
170
|
+
}
|
|
171
|
+
return { ...t, sessionUsername: username }
|
|
172
|
+
})
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
export function enqueueItem(threadId: string, item: QueuedMessage): void {
|
|
176
|
+
updateThread(threadId, (t) => ({
|
|
177
|
+
...t,
|
|
178
|
+
queueItems: [...t.queueItems, item],
|
|
179
|
+
}))
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
// Atomic dequeue: read + write in one setState call to prevent
|
|
183
|
+
// a concurrent enqueue between read and write from losing items.
|
|
184
|
+
export function dequeueItem(threadId: string): QueuedMessage | undefined {
|
|
185
|
+
let next: QueuedMessage | undefined
|
|
186
|
+
store.setState((s) => {
|
|
187
|
+
const t = s.threads.get(threadId)
|
|
188
|
+
if (!t || t.queueItems.length === 0) {
|
|
189
|
+
return s
|
|
190
|
+
}
|
|
191
|
+
const [head, ...rest] = t.queueItems
|
|
192
|
+
next = head
|
|
193
|
+
const newThreads = new Map(s.threads)
|
|
194
|
+
newThreads.set(threadId, { ...t, queueItems: rest })
|
|
195
|
+
return { threads: newThreads }
|
|
196
|
+
})
|
|
197
|
+
return next
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
export function clearQueueItems(threadId: string): void {
|
|
201
|
+
updateThread(threadId, (t) => ({ ...t, queueItems: [] }))
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
// ── Queries ──────────────────────────────────────────────────────
|
|
205
|
+
|
|
206
|
+
export function getThreadState(threadId: string): ThreadRunState | undefined {
|
|
207
|
+
return store.getState().threads.get(threadId)
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
export function getThreadIds(): string[] {
|
|
211
|
+
return [...store.getState().threads.keys()]
|
|
212
|
+
}
|