@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,330 @@
|
|
|
1
|
+
// Message pre-processing pipeline for incoming Discord messages.
|
|
2
|
+
// Extracts prompt text, voice transcription, file/text attachments, and
|
|
3
|
+
// session context from a Discord Message before handing off to the runtime.
|
|
4
|
+
//
|
|
5
|
+
// This module exists so discord-bot.ts stays a thin event router and the
|
|
6
|
+
// expensive async work (voice transcription, context fetch, attachment
|
|
7
|
+
// download) runs inside the runtime's serialized preprocessChain —
|
|
8
|
+
// preserving arrival order without a separate threadIngressQueue.
|
|
9
|
+
import { resolveMentions, getFileAttachments, getTextAttachments, } from './message-formatting.js';
|
|
10
|
+
import { processVoiceAttachment } from './voice-handler.js';
|
|
11
|
+
import { isVoiceAttachment } from './voice-attachment.js';
|
|
12
|
+
import { initializeOpencodeForDirectory } from './opencode.js';
|
|
13
|
+
import { getCompactSessionContext, getLastSessionId } from './markdown.js';
|
|
14
|
+
import { getThreadSession } from './database.js';
|
|
15
|
+
import * as errore from 'errore';
|
|
16
|
+
import { createLogger, LogPrefix } from './logger.js';
|
|
17
|
+
import { notifyError } from './sentry.js';
|
|
18
|
+
const logger = createLogger(LogPrefix.SESSION);
|
|
19
|
+
const voiceLogger = createLogger(LogPrefix.VOICE);
|
|
20
|
+
export const VOICE_MESSAGE_TRANSCRIPTION_PREFIX = 'Voice message transcription from Discord user:\n';
|
|
21
|
+
/** Fetch available agents from OpenCode for voice transcription agent selection. */
|
|
22
|
+
async function fetchAvailableAgents(getClient) {
|
|
23
|
+
if (getClient instanceof Error) {
|
|
24
|
+
return [];
|
|
25
|
+
}
|
|
26
|
+
const result = await errore.tryAsync(() => {
|
|
27
|
+
return getClient().app.agents({});
|
|
28
|
+
});
|
|
29
|
+
if (result instanceof Error) {
|
|
30
|
+
return [];
|
|
31
|
+
}
|
|
32
|
+
return (result.data || [])
|
|
33
|
+
.filter((a) => {
|
|
34
|
+
return (a.mode === 'primary' || a.mode === 'all') && !a.hidden;
|
|
35
|
+
})
|
|
36
|
+
.map((a) => {
|
|
37
|
+
return { name: a.name, description: a.description };
|
|
38
|
+
});
|
|
39
|
+
}
|
|
40
|
+
// Matches punctuation + "queue" at the end of a message (case-insensitive).
|
|
41
|
+
// Supports any common punctuation before "queue" (. ! ? , ; :) and an optional
|
|
42
|
+
// trailing period: ". queue", "! queue", ". queue.", "!queue." etc.
|
|
43
|
+
// When present the suffix is stripped and the message is routed through
|
|
44
|
+
// kimaki's local queue (same as /queue command).
|
|
45
|
+
const QUEUE_SUFFIX_RE = /[.!?,;:]\s*queue\.?\s*$/i;
|
|
46
|
+
const REPLIED_MESSAGE_TEXT_LIMIT = 1_000;
|
|
47
|
+
function extractQueueSuffix(prompt) {
|
|
48
|
+
if (!QUEUE_SUFFIX_RE.test(prompt)) {
|
|
49
|
+
return { prompt, forceQueue: false };
|
|
50
|
+
}
|
|
51
|
+
return { prompt: prompt.replace(QUEUE_SUFFIX_RE, '').trimEnd(), forceQueue: true };
|
|
52
|
+
}
|
|
53
|
+
function shouldSkipEmptyPrompt({ message, prompt, images, hasVoiceAttachment, }) {
|
|
54
|
+
if (prompt.trim()) {
|
|
55
|
+
return false;
|
|
56
|
+
}
|
|
57
|
+
if ((images?.length || 0) > 0) {
|
|
58
|
+
return false;
|
|
59
|
+
}
|
|
60
|
+
const inferredVoiceAttachment = message.attachments.some((attachment) => {
|
|
61
|
+
return isVoiceAttachment(attachment);
|
|
62
|
+
});
|
|
63
|
+
if (!hasVoiceAttachment && !inferredVoiceAttachment && message.attachments.size === 0) {
|
|
64
|
+
return false;
|
|
65
|
+
}
|
|
66
|
+
voiceLogger.warn(`[INGRESS] Skipping empty prompt after preprocessing attachments=${message.attachments.size} hasVoiceAttachment=${hasVoiceAttachment} inferredVoiceAttachment=${inferredVoiceAttachment}`);
|
|
67
|
+
return true;
|
|
68
|
+
}
|
|
69
|
+
async function getRepliedMessageContext({ message, }) {
|
|
70
|
+
if (!message.reference?.messageId) {
|
|
71
|
+
return undefined;
|
|
72
|
+
}
|
|
73
|
+
const referencedMessage = await errore.tryAsync(() => {
|
|
74
|
+
return message.fetchReference();
|
|
75
|
+
});
|
|
76
|
+
if (referencedMessage instanceof Error) {
|
|
77
|
+
logger.warn(`[INGRESS] Failed to fetch replied message ${message.reference.messageId} for ${message.id}: ${referencedMessage.message}`);
|
|
78
|
+
return undefined;
|
|
79
|
+
}
|
|
80
|
+
const repliedText = resolveMentions(referencedMessage)
|
|
81
|
+
.trim()
|
|
82
|
+
.slice(0, REPLIED_MESSAGE_TEXT_LIMIT);
|
|
83
|
+
if (!repliedText) {
|
|
84
|
+
return undefined;
|
|
85
|
+
}
|
|
86
|
+
return {
|
|
87
|
+
authorUsername: referencedMessage.author.username,
|
|
88
|
+
text: repliedText,
|
|
89
|
+
};
|
|
90
|
+
}
|
|
91
|
+
/**
|
|
92
|
+
* Pre-process a message in an existing thread (thread already has a session or
|
|
93
|
+
* needs a new one). Handles voice transcription, text/file attachments, and
|
|
94
|
+
* session context fetching for voice messages.
|
|
95
|
+
*
|
|
96
|
+
* For threads with an existing session, voice transcription is enriched with
|
|
97
|
+
* current + last session context (used by the transcription model to better
|
|
98
|
+
* understand domain-specific terms).
|
|
99
|
+
*/
|
|
100
|
+
export async function preprocessExistingThreadMessage({ message, thread, projectDirectory, channelId, isCliInjected, hasVoiceAttachment, appId, }) {
|
|
101
|
+
const sessionId = await getThreadSession(thread.id);
|
|
102
|
+
// ── No existing session: new session in an existing thread ──
|
|
103
|
+
if (!sessionId) {
|
|
104
|
+
return preprocessNewSessionMessage({
|
|
105
|
+
message,
|
|
106
|
+
thread,
|
|
107
|
+
projectDirectory,
|
|
108
|
+
hasVoiceAttachment,
|
|
109
|
+
appId,
|
|
110
|
+
});
|
|
111
|
+
}
|
|
112
|
+
// ── Existing session path ──
|
|
113
|
+
voiceLogger.log(`[SESSION] Found session ${sessionId} for thread ${thread.id}`);
|
|
114
|
+
let messageContent = isCliInjected
|
|
115
|
+
? (message.content || '')
|
|
116
|
+
: resolveMentions(message);
|
|
117
|
+
const repliedMessage = await getRepliedMessageContext({ message });
|
|
118
|
+
// Fetch session context and available agents for voice transcription enrichment
|
|
119
|
+
let currentSessionContext;
|
|
120
|
+
let lastSessionContext;
|
|
121
|
+
let agents = [];
|
|
122
|
+
if (projectDirectory) {
|
|
123
|
+
try {
|
|
124
|
+
const getClient = await initializeOpencodeForDirectory(projectDirectory, { channelId });
|
|
125
|
+
if (getClient instanceof Error) {
|
|
126
|
+
voiceLogger.error(`[SESSION] Failed to initialize OpenCode client:`, getClient.message);
|
|
127
|
+
throw new Error(getClient.message);
|
|
128
|
+
}
|
|
129
|
+
const client = getClient();
|
|
130
|
+
const [sessionContextResult, lastSessionResult, fetchedAgents] = await Promise.all([
|
|
131
|
+
getCompactSessionContext({
|
|
132
|
+
client,
|
|
133
|
+
sessionId,
|
|
134
|
+
includeSystemPrompt: false,
|
|
135
|
+
maxMessages: 15,
|
|
136
|
+
}),
|
|
137
|
+
getLastSessionId({
|
|
138
|
+
client,
|
|
139
|
+
excludeSessionId: sessionId,
|
|
140
|
+
}),
|
|
141
|
+
fetchAvailableAgents(getClient),
|
|
142
|
+
]);
|
|
143
|
+
if (errore.isOk(sessionContextResult)) {
|
|
144
|
+
currentSessionContext = sessionContextResult;
|
|
145
|
+
}
|
|
146
|
+
agents = fetchedAgents;
|
|
147
|
+
const lastSessionId = errore.unwrapOr(lastSessionResult, null);
|
|
148
|
+
if (lastSessionId) {
|
|
149
|
+
const result = await getCompactSessionContext({
|
|
150
|
+
client,
|
|
151
|
+
sessionId: lastSessionId,
|
|
152
|
+
includeSystemPrompt: true,
|
|
153
|
+
maxMessages: 10,
|
|
154
|
+
});
|
|
155
|
+
if (errore.isOk(result)) {
|
|
156
|
+
lastSessionContext = result;
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
catch (e) {
|
|
161
|
+
voiceLogger.error(`Could not get session context:`, e);
|
|
162
|
+
void notifyError(e, 'Failed to get session context');
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
const voiceResult = await processVoiceAttachment({
|
|
166
|
+
message,
|
|
167
|
+
thread,
|
|
168
|
+
projectDirectory,
|
|
169
|
+
appId,
|
|
170
|
+
currentSessionContext,
|
|
171
|
+
lastSessionContext,
|
|
172
|
+
agents,
|
|
173
|
+
});
|
|
174
|
+
if (voiceResult) {
|
|
175
|
+
messageContent = `${VOICE_MESSAGE_TRANSCRIPTION_PREFIX}${voiceResult.transcription}`;
|
|
176
|
+
}
|
|
177
|
+
// Voice transcription failed and no text — drop silently
|
|
178
|
+
if (hasVoiceAttachment && !voiceResult && !messageContent.trim()) {
|
|
179
|
+
return { prompt: '', mode: 'opencode', skip: true };
|
|
180
|
+
}
|
|
181
|
+
// Extract queue suffix from raw message content BEFORE appending text
|
|
182
|
+
// attachments. Otherwise a text file attachment pushes "? queue" away from
|
|
183
|
+
// the end of the string and the regex fails to match.
|
|
184
|
+
const qs = extractQueueSuffix(messageContent);
|
|
185
|
+
const fileAttachments = await getFileAttachments(message);
|
|
186
|
+
const textAttachmentsContent = await getTextAttachments(message);
|
|
187
|
+
const prompt = textAttachmentsContent
|
|
188
|
+
? `${qs.prompt}\n\n${textAttachmentsContent}`
|
|
189
|
+
: qs.prompt;
|
|
190
|
+
if (shouldSkipEmptyPrompt({
|
|
191
|
+
message,
|
|
192
|
+
prompt,
|
|
193
|
+
images: fileAttachments,
|
|
194
|
+
hasVoiceAttachment,
|
|
195
|
+
})) {
|
|
196
|
+
return { prompt: '', mode: 'opencode', skip: true };
|
|
197
|
+
}
|
|
198
|
+
return {
|
|
199
|
+
prompt,
|
|
200
|
+
images: fileAttachments.length > 0 ? fileAttachments : undefined,
|
|
201
|
+
repliedMessage,
|
|
202
|
+
mode: qs.forceQueue || voiceResult?.queueMessage ? 'local-queue' : 'opencode',
|
|
203
|
+
agent: voiceResult?.agent,
|
|
204
|
+
};
|
|
205
|
+
}
|
|
206
|
+
/**
|
|
207
|
+
* Pre-process a message that starts a new session in a thread (no existing
|
|
208
|
+
* session). Handles starter message context, voice transcription, and
|
|
209
|
+
* text/file attachments.
|
|
210
|
+
*/
|
|
211
|
+
export async function preprocessNewSessionMessage({ message, thread, projectDirectory, hasVoiceAttachment, appId, }) {
|
|
212
|
+
logger.log(`No session for thread ${thread.id}, starting new session`);
|
|
213
|
+
// Fetch available agents only for voice messages to avoid unnecessary SDK
|
|
214
|
+
// roundtrips on plain text messages.
|
|
215
|
+
let agents = [];
|
|
216
|
+
if (hasVoiceAttachment && projectDirectory) {
|
|
217
|
+
try {
|
|
218
|
+
const getClient = await initializeOpencodeForDirectory(projectDirectory);
|
|
219
|
+
agents = await fetchAvailableAgents(getClient);
|
|
220
|
+
}
|
|
221
|
+
catch (e) {
|
|
222
|
+
voiceLogger.error(`Could not fetch agents for voice transcription:`, e);
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
let prompt = resolveMentions(message);
|
|
226
|
+
const repliedMessage = await getRepliedMessageContext({ message });
|
|
227
|
+
const voiceResult = await processVoiceAttachment({
|
|
228
|
+
message,
|
|
229
|
+
thread,
|
|
230
|
+
projectDirectory,
|
|
231
|
+
appId,
|
|
232
|
+
agents,
|
|
233
|
+
});
|
|
234
|
+
if (voiceResult) {
|
|
235
|
+
prompt = `${VOICE_MESSAGE_TRANSCRIPTION_PREFIX}${voiceResult.transcription}`;
|
|
236
|
+
}
|
|
237
|
+
// Voice transcription failed and no text — drop silently
|
|
238
|
+
if (hasVoiceAttachment && !voiceResult && !prompt.trim()) {
|
|
239
|
+
return { prompt: '', mode: 'opencode', skip: true };
|
|
240
|
+
}
|
|
241
|
+
// Fetch starter message for thread context
|
|
242
|
+
const starterMessage = await thread
|
|
243
|
+
.fetchStarterMessage()
|
|
244
|
+
.catch((error) => {
|
|
245
|
+
logger.warn(`[SESSION] Failed to fetch starter message for thread ${thread.id}:`, error instanceof Error ? error.stack : String(error));
|
|
246
|
+
return null;
|
|
247
|
+
});
|
|
248
|
+
if (starterMessage && starterMessage.content !== message.content) {
|
|
249
|
+
const starterTextAttachments = await getTextAttachments(starterMessage);
|
|
250
|
+
const starterContent = resolveMentions(starterMessage);
|
|
251
|
+
const starterText = starterTextAttachments
|
|
252
|
+
? `${starterContent}\n\n${starterTextAttachments}`
|
|
253
|
+
: starterContent;
|
|
254
|
+
if (starterText) {
|
|
255
|
+
prompt = `Context from thread:\n${starterText}\n\nUser request:\n${prompt}`;
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
const qs = extractQueueSuffix(prompt);
|
|
259
|
+
if (shouldSkipEmptyPrompt({
|
|
260
|
+
message,
|
|
261
|
+
prompt: qs.prompt,
|
|
262
|
+
hasVoiceAttachment,
|
|
263
|
+
})) {
|
|
264
|
+
return { prompt: '', mode: 'opencode', skip: true };
|
|
265
|
+
}
|
|
266
|
+
return {
|
|
267
|
+
prompt: qs.prompt,
|
|
268
|
+
repliedMessage,
|
|
269
|
+
mode: qs.forceQueue || voiceResult?.queueMessage ? 'local-queue' : 'opencode',
|
|
270
|
+
agent: voiceResult?.agent,
|
|
271
|
+
};
|
|
272
|
+
}
|
|
273
|
+
/**
|
|
274
|
+
* Pre-process a message from a text channel (creates a new thread).
|
|
275
|
+
* Handles voice transcription and file/text attachments.
|
|
276
|
+
*/
|
|
277
|
+
export async function preprocessNewThreadMessage({ message, thread, projectDirectory, hasVoiceAttachment, appId, }) {
|
|
278
|
+
// Fetch available agents only for voice messages to avoid unnecessary SDK
|
|
279
|
+
// roundtrips on plain text messages.
|
|
280
|
+
let agents = [];
|
|
281
|
+
if (hasVoiceAttachment && projectDirectory) {
|
|
282
|
+
try {
|
|
283
|
+
const getClient = await initializeOpencodeForDirectory(projectDirectory);
|
|
284
|
+
agents = await fetchAvailableAgents(getClient);
|
|
285
|
+
}
|
|
286
|
+
catch (e) {
|
|
287
|
+
voiceLogger.error(`Could not fetch agents for voice transcription:`, e);
|
|
288
|
+
}
|
|
289
|
+
}
|
|
290
|
+
let messageContent = resolveMentions(message);
|
|
291
|
+
const repliedMessage = await getRepliedMessageContext({ message });
|
|
292
|
+
const voiceResult = await processVoiceAttachment({
|
|
293
|
+
message,
|
|
294
|
+
thread,
|
|
295
|
+
projectDirectory,
|
|
296
|
+
isNewThread: true,
|
|
297
|
+
appId,
|
|
298
|
+
agents,
|
|
299
|
+
});
|
|
300
|
+
if (voiceResult) {
|
|
301
|
+
messageContent = `${VOICE_MESSAGE_TRANSCRIPTION_PREFIX}${voiceResult.transcription}`;
|
|
302
|
+
}
|
|
303
|
+
// Voice transcription failed and no text — drop silently
|
|
304
|
+
if (hasVoiceAttachment && !voiceResult && !messageContent.trim()) {
|
|
305
|
+
return { prompt: '', mode: 'opencode', skip: true };
|
|
306
|
+
}
|
|
307
|
+
// Extract queue suffix from raw message content BEFORE appending text
|
|
308
|
+
// attachments (same fix as preprocessExistingThreadMessage).
|
|
309
|
+
const qs = extractQueueSuffix(messageContent);
|
|
310
|
+
const fileAttachments = await getFileAttachments(message);
|
|
311
|
+
const textAttachmentsContent = await getTextAttachments(message);
|
|
312
|
+
const prompt = textAttachmentsContent
|
|
313
|
+
? `${qs.prompt}\n\n${textAttachmentsContent}`
|
|
314
|
+
: qs.prompt;
|
|
315
|
+
if (shouldSkipEmptyPrompt({
|
|
316
|
+
message,
|
|
317
|
+
prompt,
|
|
318
|
+
images: fileAttachments,
|
|
319
|
+
hasVoiceAttachment,
|
|
320
|
+
})) {
|
|
321
|
+
return { prompt: '', mode: 'opencode', skip: true };
|
|
322
|
+
}
|
|
323
|
+
return {
|
|
324
|
+
prompt,
|
|
325
|
+
images: fileAttachments.length > 0 ? fileAttachments : undefined,
|
|
326
|
+
repliedMessage,
|
|
327
|
+
mode: qs.forceQueue || voiceResult?.queueMessage ? 'local-queue' : 'opencode',
|
|
328
|
+
agent: voiceResult?.agent,
|
|
329
|
+
};
|
|
330
|
+
}
|
|
@@ -0,0 +1,172 @@
|
|
|
1
|
+
// Onboarding tutorial system instructions injected by the plugin when the
|
|
2
|
+
// user starts a 3D game tutorial session. The `markdown` tag is a no-op
|
|
3
|
+
// identity function — it exists only for editor syntax highlighting.
|
|
4
|
+
//
|
|
5
|
+
// This file has no discord.js deps so it can be safely imported by both
|
|
6
|
+
// the welcome message (discord side) and the opencode plugin.
|
|
7
|
+
// Unique text used in the welcome message and detected by the plugin to
|
|
8
|
+
// trigger tutorial instruction injection. Shared constant so they can't
|
|
9
|
+
// drift out of sync.
|
|
10
|
+
export const TUTORIAL_WELCOME_TEXT = 'Want to build an example browser game? Respond in this thread.';
|
|
11
|
+
const markdown = String.raw;
|
|
12
|
+
const backticks = '```';
|
|
13
|
+
export const ONBOARDING_TUTORIAL_INSTRUCTIONS = markdown `
|
|
14
|
+
You are helping a new user try Kimaki for the first time. The default suggestion is building a 3D game, but if the user asks to build something else, build that instead. Adapt all instructions below to whatever the user wants.
|
|
15
|
+
|
|
16
|
+
## Prerequisites
|
|
17
|
+
|
|
18
|
+
Before doing anything else, check that these are installed:
|
|
19
|
+
|
|
20
|
+
**Bun** (v1.2 or later) — runtime and bundler:
|
|
21
|
+
|
|
22
|
+
${backticks}bash
|
|
23
|
+
bun --version
|
|
24
|
+
${backticks}
|
|
25
|
+
|
|
26
|
+
If missing or below 1.2, tell the user to install it: https://bun.sh — or run:
|
|
27
|
+
|
|
28
|
+
${backticks}bash
|
|
29
|
+
curl -fsSL https://bun.sh/install | bash
|
|
30
|
+
${backticks}
|
|
31
|
+
|
|
32
|
+
**tmux** — needed to run the dev server in the background with kimaki tunnel:
|
|
33
|
+
|
|
34
|
+
${backticks}bash
|
|
35
|
+
tmux -V
|
|
36
|
+
${backticks}
|
|
37
|
+
|
|
38
|
+
If missing, tell the user to install it: https://github.com/tmux/tmux/wiki/Installing — or:
|
|
39
|
+
|
|
40
|
+
${backticks}bash
|
|
41
|
+
# macOS
|
|
42
|
+
brew install tmux
|
|
43
|
+
|
|
44
|
+
# Ubuntu/Debian
|
|
45
|
+
sudo apt-get install tmux
|
|
46
|
+
${backticks}
|
|
47
|
+
|
|
48
|
+
Do NOT use Node.js, npm, or npx. Use Bun for everything.
|
|
49
|
+
|
|
50
|
+
## Goal
|
|
51
|
+
|
|
52
|
+
Build a simple but visually impressive 3D game using Three.js that runs in the browser. The user should be able to play it within a few minutes of starting. If the user asked for something different, build that instead.
|
|
53
|
+
|
|
54
|
+
## Game idea
|
|
55
|
+
|
|
56
|
+
Build a "Space Dodge" game:
|
|
57
|
+
- The player controls a spaceship that flies forward through space
|
|
58
|
+
- Asteroids/obstacles come toward the player
|
|
59
|
+
- The player dodges left/right/up/down using arrow keys or WASD
|
|
60
|
+
- Touch/swipe controls for mobile — the user is on Discord and may open the link on their phone
|
|
61
|
+
- Score increases over time, speed gradually increases
|
|
62
|
+
- Particle effects for explosions when hit
|
|
63
|
+
- Starfield background for atmosphere
|
|
64
|
+
- Simple start screen and game over screen with score
|
|
65
|
+
|
|
66
|
+
If the game idea doesn't match what the user asked for, adapt to their request instead.
|
|
67
|
+
|
|
68
|
+
## Project setup
|
|
69
|
+
|
|
70
|
+
Create these files:
|
|
71
|
+
|
|
72
|
+
**package.json** — install three as a dependency:
|
|
73
|
+
${backticks}json
|
|
74
|
+
{
|
|
75
|
+
"dependencies": {
|
|
76
|
+
"three": "^0.170.0"
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
${backticks}
|
|
80
|
+
|
|
81
|
+
Run bun install after creating it.
|
|
82
|
+
|
|
83
|
+
**tsconfig.json**:
|
|
84
|
+
${backticks}json
|
|
85
|
+
{
|
|
86
|
+
"compilerOptions": {
|
|
87
|
+
"target": "ESNext",
|
|
88
|
+
"module": "ESNext",
|
|
89
|
+
"moduleResolution": "bundler",
|
|
90
|
+
"strict": true,
|
|
91
|
+
"jsx": "react-jsx",
|
|
92
|
+
"types": ["three"]
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
${backticks}
|
|
96
|
+
|
|
97
|
+
**index.html** — the entry point, references the TypeScript source:
|
|
98
|
+
${backticks}html
|
|
99
|
+
<!DOCTYPE html>
|
|
100
|
+
<html>
|
|
101
|
+
<head>
|
|
102
|
+
<meta charset="utf-8" />
|
|
103
|
+
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
|
104
|
+
<title>Space Dodge</title>
|
|
105
|
+
<style>
|
|
106
|
+
body { margin: 0; overflow: hidden; }
|
|
107
|
+
canvas { display: block; }
|
|
108
|
+
</style>
|
|
109
|
+
</head>
|
|
110
|
+
<body>
|
|
111
|
+
<script type="module" src="./game.ts"></script>
|
|
112
|
+
</body>
|
|
113
|
+
</html>
|
|
114
|
+
${backticks}
|
|
115
|
+
|
|
116
|
+
**game.ts** — all game logic in TypeScript, importing from "three":
|
|
117
|
+
${backticks}ts
|
|
118
|
+
import * as THREE from "three"
|
|
119
|
+
// ... game code here
|
|
120
|
+
${backticks}
|
|
121
|
+
|
|
122
|
+
Write the full game code in game.ts. Import Three.js with normal imports (Bun bundles it automatically). Add basic mobile touch controls (swipe to move) so it works on phones too.
|
|
123
|
+
|
|
124
|
+
**server.ts** — Bun fullstack dev server (reads port from PORT env var):
|
|
125
|
+
${backticks}ts
|
|
126
|
+
import homepage from "./index.html"
|
|
127
|
+
|
|
128
|
+
Bun.serve({
|
|
129
|
+
port: Number(process.env.PORT) || 3000,
|
|
130
|
+
routes: { "/": homepage },
|
|
131
|
+
development: true,
|
|
132
|
+
})
|
|
133
|
+
${backticks}
|
|
134
|
+
|
|
135
|
+
## Dev server and tunnel
|
|
136
|
+
|
|
137
|
+
After creating all files and running bun install, start the dev server and expose it via kimaki tunnel so the user can play immediately from their browser or phone. The user is on Discord, not at a terminal — localhost URLs are useless to them.
|
|
138
|
+
|
|
139
|
+
Pick a random port between 3000-9000 to avoid conflicts:
|
|
140
|
+
|
|
141
|
+
${backticks}bash
|
|
142
|
+
PORT=$((RANDOM % 6000 + 3000))
|
|
143
|
+
tmux kill-session -t game-dev 2>/dev/null
|
|
144
|
+
tmux new-session -d -s game-dev -c "$PWD"
|
|
145
|
+
tmux send-keys -t game-dev "PORT=$PORT kimaki tunnel -p $PORT -- bun run server.ts" Enter
|
|
146
|
+
${backticks}
|
|
147
|
+
|
|
148
|
+
Wait a moment, then get the tunnel URL:
|
|
149
|
+
|
|
150
|
+
${backticks}bash
|
|
151
|
+
sleep 1 && tmux capture-pane -t game-dev -p
|
|
152
|
+
${backticks}
|
|
153
|
+
|
|
154
|
+
If the tunnel URL is not visible yet, run the capture command again — it usually appears within a few seconds.
|
|
155
|
+
|
|
156
|
+
After the tunnel is up, also generate a diff URL so the user can browse the code:
|
|
157
|
+
|
|
158
|
+
${backticks}bash
|
|
159
|
+
git init && git add -A && critique --url
|
|
160
|
+
${backticks}
|
|
161
|
+
|
|
162
|
+
Share both URLs prominently so the user can play the game and browse the code. Put each URL on its own line, not buried in text.
|
|
163
|
+
|
|
164
|
+
## Behavior
|
|
165
|
+
|
|
166
|
+
- Be friendly and casual in the conversation. Sound like a helpful teammate, not a formal tutorial.
|
|
167
|
+
- Be concise and direct. Show the code, start the server, share the URL.
|
|
168
|
+
- After the game is working and the user can play it, ask what they'd like to change by using the question tool. Do not present a manual numbered list in plain text. Offer a few short button/dropdown options like sound effects, ship design, leaderboard, power-ups, and multiplayer, while still allowing a custom answer.
|
|
169
|
+
- Never mention this is a tutorial, onboarding, or demo. Act like a normal coding session.
|
|
170
|
+
- Never apologize or over-explain. Just build and iterate.
|
|
171
|
+
- If the user asks to change something, do it quickly and share the updated URL.
|
|
172
|
+
`;
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
// Onboarding welcome message for the default kimaki channel.
|
|
2
|
+
// Sends a message explaining what Kimaki is, then creates a thread from it
|
|
3
|
+
// so the user can respond there to start a tutorial session.
|
|
4
|
+
// Sends a smaller follow-up message inside the thread with the installer
|
|
5
|
+
// mention so the notification is less noisy.
|
|
6
|
+
// Posted once when the default channel is first created.
|
|
7
|
+
import { ThreadAutoArchiveDuration } from 'discord.js';
|
|
8
|
+
import { createLogger, LogPrefix } from './logger.js';
|
|
9
|
+
import { TUTORIAL_WELCOME_TEXT } from './onboarding-tutorial.js';
|
|
10
|
+
const logger = createLogger(LogPrefix.CHANNEL);
|
|
11
|
+
function buildWelcomeText() {
|
|
12
|
+
return `**Kimaki** lets you code from Discord. Send a message in any project channel and an AI agent edits code, runs commands, and searches your codebase — all on your machine.
|
|
13
|
+
**What you can do:**
|
|
14
|
+
- Use \`/add-project\` to create a Discord channel linked to one OpenCode project (git repo)
|
|
15
|
+
- Collaborate with teammates in the same session
|
|
16
|
+
- Upload images and files, the bot can share screenshots back
|
|
17
|
+
${TUTORIAL_WELCOME_TEXT}`;
|
|
18
|
+
}
|
|
19
|
+
function buildThreadPrompt({ mentionUserId }) {
|
|
20
|
+
const mentionSuffix = mentionUserId ? ` <@${mentionUserId}>` : '';
|
|
21
|
+
return `Want to build an example browser game? Respond in this thread.${mentionSuffix}`;
|
|
22
|
+
}
|
|
23
|
+
export async function sendWelcomeMessage({ channel, mentionUserId, }) {
|
|
24
|
+
try {
|
|
25
|
+
const message = await channel.send(buildWelcomeText());
|
|
26
|
+
const thread = await message.startThread({
|
|
27
|
+
name: 'Kimaki tutorial',
|
|
28
|
+
autoArchiveDuration: ThreadAutoArchiveDuration.OneDay,
|
|
29
|
+
reason: 'Onboarding tutorial thread',
|
|
30
|
+
});
|
|
31
|
+
await thread.send(buildThreadPrompt({ mentionUserId }));
|
|
32
|
+
logger.log(`Sent welcome message with thread to #${channel.name}`);
|
|
33
|
+
}
|
|
34
|
+
catch (error) {
|
|
35
|
+
logger.warn(`Failed to send welcome message to #${channel.name}: ${error instanceof Error ? error.stack : String(error)}`);
|
|
36
|
+
}
|
|
37
|
+
}
|