@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.
Files changed (483) hide show
  1. package/bin.js +2 -0
  2. package/dist/agent-model.e2e.test.js +755 -0
  3. package/dist/ai-tool-to-genai.js +233 -0
  4. package/dist/ai-tool-to-genai.test.js +267 -0
  5. package/dist/ai-tool.js +6 -0
  6. package/dist/anthropic-auth-plugin.js +728 -0
  7. package/dist/anthropic-auth-plugin.test.js +125 -0
  8. package/dist/anthropic-auth-state.js +231 -0
  9. package/dist/bin.js +90 -0
  10. package/dist/channel-management.js +227 -0
  11. package/dist/cli-parsing.test.js +137 -0
  12. package/dist/cli-send-thread.e2e.test.js +356 -0
  13. package/dist/cli.js +3276 -0
  14. package/dist/commands/abort.js +65 -0
  15. package/dist/commands/action-buttons.js +245 -0
  16. package/dist/commands/add-project.js +113 -0
  17. package/dist/commands/agent.js +335 -0
  18. package/dist/commands/ask-question.js +274 -0
  19. package/dist/commands/btw.js +116 -0
  20. package/dist/commands/compact.js +120 -0
  21. package/dist/commands/context-usage.js +140 -0
  22. package/dist/commands/create-new-project.js +130 -0
  23. package/dist/commands/diff.js +63 -0
  24. package/dist/commands/file-upload.js +275 -0
  25. package/dist/commands/fork.js +220 -0
  26. package/dist/commands/gemini-apikey.js +70 -0
  27. package/dist/commands/login.js +885 -0
  28. package/dist/commands/mcp.js +239 -0
  29. package/dist/commands/memory-snapshot.js +24 -0
  30. package/dist/commands/mention-mode.js +44 -0
  31. package/dist/commands/merge-worktree.js +159 -0
  32. package/dist/commands/model-variant.js +364 -0
  33. package/dist/commands/model.js +776 -0
  34. package/dist/commands/new-worktree.js +366 -0
  35. package/dist/commands/paginated-select.js +57 -0
  36. package/dist/commands/permissions.js +274 -0
  37. package/dist/commands/queue.js +206 -0
  38. package/dist/commands/remove-project.js +115 -0
  39. package/dist/commands/restart-opencode-server.js +127 -0
  40. package/dist/commands/resume.js +149 -0
  41. package/dist/commands/run-command.js +79 -0
  42. package/dist/commands/screenshare.js +303 -0
  43. package/dist/commands/screenshare.test.js +20 -0
  44. package/dist/commands/session-id.js +78 -0
  45. package/dist/commands/session.js +176 -0
  46. package/dist/commands/share.js +80 -0
  47. package/dist/commands/tasks.js +205 -0
  48. package/dist/commands/types.js +2 -0
  49. package/dist/commands/undo-redo.js +305 -0
  50. package/dist/commands/unset-model.js +138 -0
  51. package/dist/commands/upgrade.js +42 -0
  52. package/dist/commands/user-command.js +155 -0
  53. package/dist/commands/verbosity.js +125 -0
  54. package/dist/commands/worktree-settings.js +43 -0
  55. package/dist/commands/worktrees.js +410 -0
  56. package/dist/condense-memory.js +33 -0
  57. package/dist/config.js +94 -0
  58. package/dist/context-awareness-plugin.js +363 -0
  59. package/dist/context-awareness-plugin.test.js +124 -0
  60. package/dist/critique-utils.js +95 -0
  61. package/dist/database.js +1310 -0
  62. package/dist/db.js +251 -0
  63. package/dist/db.test.js +138 -0
  64. package/dist/debounce-timeout.js +28 -0
  65. package/dist/debounced-process-flush.js +77 -0
  66. package/dist/discord-bot.js +1008 -0
  67. package/dist/discord-command-registration.js +524 -0
  68. package/dist/discord-urls.js +81 -0
  69. package/dist/discord-utils.js +591 -0
  70. package/dist/discord-utils.test.js +134 -0
  71. package/dist/errors.js +157 -0
  72. package/dist/escape-backticks.test.js +429 -0
  73. package/dist/event-stream-real-capture.e2e.test.js +533 -0
  74. package/dist/eventsource-parser.test.js +327 -0
  75. package/dist/exec-async.js +26 -0
  76. package/dist/external-opencode-sync.js +480 -0
  77. package/dist/format-tables.js +302 -0
  78. package/dist/format-tables.test.js +308 -0
  79. package/dist/forum-sync/config.js +79 -0
  80. package/dist/forum-sync/discord-operations.js +154 -0
  81. package/dist/forum-sync/index.js +5 -0
  82. package/dist/forum-sync/markdown.js +113 -0
  83. package/dist/forum-sync/sync-to-discord.js +417 -0
  84. package/dist/forum-sync/sync-to-files.js +190 -0
  85. package/dist/forum-sync/types.js +53 -0
  86. package/dist/forum-sync/watchers.js +307 -0
  87. package/dist/gateway-proxy-reconnect.e2e.test.js +394 -0
  88. package/dist/gateway-proxy.e2e.test.js +483 -0
  89. package/dist/genai-worker-wrapper.js +111 -0
  90. package/dist/genai-worker.js +311 -0
  91. package/dist/genai.js +232 -0
  92. package/dist/generated/browser.js +17 -0
  93. package/dist/generated/client.js +37 -0
  94. package/dist/generated/commonInputTypes.js +10 -0
  95. package/dist/generated/enums.js +52 -0
  96. package/dist/generated/internal/class.js +49 -0
  97. package/dist/generated/internal/prismaNamespace.js +253 -0
  98. package/dist/generated/internal/prismaNamespaceBrowser.js +223 -0
  99. package/dist/generated/models/bot_api_keys.js +1 -0
  100. package/dist/generated/models/bot_tokens.js +1 -0
  101. package/dist/generated/models/channel_agents.js +1 -0
  102. package/dist/generated/models/channel_directories.js +1 -0
  103. package/dist/generated/models/channel_mention_mode.js +1 -0
  104. package/dist/generated/models/channel_models.js +1 -0
  105. package/dist/generated/models/channel_verbosity.js +1 -0
  106. package/dist/generated/models/channel_worktrees.js +1 -0
  107. package/dist/generated/models/forum_sync_configs.js +1 -0
  108. package/dist/generated/models/global_models.js +1 -0
  109. package/dist/generated/models/ipc_requests.js +1 -0
  110. package/dist/generated/models/part_messages.js +1 -0
  111. package/dist/generated/models/scheduled_tasks.js +1 -0
  112. package/dist/generated/models/session_agents.js +1 -0
  113. package/dist/generated/models/session_events.js +1 -0
  114. package/dist/generated/models/session_models.js +1 -0
  115. package/dist/generated/models/session_start_sources.js +1 -0
  116. package/dist/generated/models/thread_sessions.js +1 -0
  117. package/dist/generated/models/thread_worktrees.js +1 -0
  118. package/dist/generated/models.js +1 -0
  119. package/dist/heap-monitor.js +122 -0
  120. package/dist/hrana-server.js +263 -0
  121. package/dist/hrana-server.test.js +370 -0
  122. package/dist/html-actions.js +123 -0
  123. package/dist/html-actions.test.js +70 -0
  124. package/dist/html-components.js +117 -0
  125. package/dist/html-components.test.js +34 -0
  126. package/dist/image-optimizer-plugin.js +153 -0
  127. package/dist/image-utils.js +112 -0
  128. package/dist/interaction-handler.js +397 -0
  129. package/dist/ipc-polling.js +252 -0
  130. package/dist/ipc-tools-plugin.js +193 -0
  131. package/dist/kimaki-digital-twin.e2e.test.js +161 -0
  132. package/dist/kimaki-opencode-plugin-loading.e2e.test.js +87 -0
  133. package/dist/kimaki-opencode-plugin.js +17 -0
  134. package/dist/kimaki-opencode-plugin.test.js +98 -0
  135. package/dist/limit-heading-depth.js +25 -0
  136. package/dist/limit-heading-depth.test.js +105 -0
  137. package/dist/logger.js +165 -0
  138. package/dist/markdown.js +342 -0
  139. package/dist/markdown.test.js +257 -0
  140. package/dist/message-finish-field.e2e.test.js +165 -0
  141. package/dist/message-formatting.js +413 -0
  142. package/dist/message-formatting.test.js +73 -0
  143. package/dist/message-preprocessing.js +330 -0
  144. package/dist/onboarding-tutorial.js +172 -0
  145. package/dist/onboarding-welcome.js +37 -0
  146. package/dist/openai-realtime.js +224 -0
  147. package/dist/opencode-command-detection.js +65 -0
  148. package/dist/opencode-command-detection.test.js +240 -0
  149. package/dist/opencode-command.js +129 -0
  150. package/dist/opencode-command.test.js +48 -0
  151. package/dist/opencode-interrupt-plugin.js +361 -0
  152. package/dist/opencode-interrupt-plugin.test.js +458 -0
  153. package/dist/opencode.js +861 -0
  154. package/dist/otto/branding.js +22 -0
  155. package/dist/otto/index.js +21 -0
  156. package/dist/parse-permission-rules.test.js +117 -0
  157. package/dist/patch-text-parser.js +97 -0
  158. package/dist/plugin-logger.js +59 -0
  159. package/dist/privacy-sanitizer.js +105 -0
  160. package/dist/queue-advanced-abort.e2e.test.js +293 -0
  161. package/dist/queue-advanced-action-buttons.e2e.test.js +206 -0
  162. package/dist/queue-advanced-e2e-setup.js +786 -0
  163. package/dist/queue-advanced-footer.e2e.test.js +472 -0
  164. package/dist/queue-advanced-model-switch.e2e.test.js +299 -0
  165. package/dist/queue-advanced-permissions-typing.e2e.test.js +180 -0
  166. package/dist/queue-advanced-question.e2e.test.js +261 -0
  167. package/dist/queue-advanced-typing-interrupt.e2e.test.js +114 -0
  168. package/dist/queue-advanced-typing.e2e.test.js +153 -0
  169. package/dist/queue-drain-after-interactive-ui.e2e.test.js +119 -0
  170. package/dist/queue-interrupt-drain.e2e.test.js +135 -0
  171. package/dist/queue-question-select-drain.e2e.test.js +120 -0
  172. package/dist/runtime-idle-sweeper.js +52 -0
  173. package/dist/runtime-lifecycle.e2e.test.js +508 -0
  174. package/dist/sentry.js +23 -0
  175. package/dist/session-handler/agent-utils.js +67 -0
  176. package/dist/session-handler/event-stream-state.js +420 -0
  177. package/dist/session-handler/event-stream-state.test.js +563 -0
  178. package/dist/session-handler/model-utils.js +124 -0
  179. package/dist/session-handler/opencode-session-event-log.js +94 -0
  180. package/dist/session-handler/thread-runtime-state.js +104 -0
  181. package/dist/session-handler/thread-session-runtime.js +3258 -0
  182. package/dist/session-handler.js +9 -0
  183. package/dist/session-search.js +100 -0
  184. package/dist/session-search.test.js +40 -0
  185. package/dist/session-title-rename.test.js +80 -0
  186. package/dist/startup-service.js +153 -0
  187. package/dist/startup-time.e2e.test.js +296 -0
  188. package/dist/store.js +17 -0
  189. package/dist/system-message.js +613 -0
  190. package/dist/system-message.test.js +602 -0
  191. package/dist/task-runner.js +295 -0
  192. package/dist/task-schedule.js +209 -0
  193. package/dist/task-schedule.test.js +71 -0
  194. package/dist/test-utils.js +299 -0
  195. package/dist/thinking-utils.js +35 -0
  196. package/dist/thread-message-queue.e2e.test.js +999 -0
  197. package/dist/tools.js +357 -0
  198. package/dist/undo-redo.e2e.test.js +161 -0
  199. package/dist/unnest-code-blocks.js +146 -0
  200. package/dist/unnest-code-blocks.test.js +673 -0
  201. package/dist/upgrade.js +114 -0
  202. package/dist/utils.js +144 -0
  203. package/dist/voice-attachment.js +34 -0
  204. package/dist/voice-handler.js +646 -0
  205. package/dist/voice-message.e2e.test.js +1021 -0
  206. package/dist/voice.js +447 -0
  207. package/dist/voice.test.js +235 -0
  208. package/dist/wait-session.js +94 -0
  209. package/dist/websockify.js +69 -0
  210. package/dist/worker-types.js +4 -0
  211. package/dist/worktree-lifecycle.e2e.test.js +308 -0
  212. package/dist/worktree-utils.js +3 -0
  213. package/dist/worktrees.js +929 -0
  214. package/dist/worktrees.test.js +189 -0
  215. package/dist/xml.js +92 -0
  216. package/dist/xml.test.js +32 -0
  217. package/package.json +98 -0
  218. package/schema.prisma +295 -0
  219. package/skills/batch/SKILL.md +87 -0
  220. package/skills/critique/SKILL.md +112 -0
  221. package/skills/egaki/SKILL.md +100 -0
  222. package/skills/errore/SKILL.md +647 -0
  223. package/skills/event-sourcing-state/SKILL.md +252 -0
  224. package/skills/gitchamber/SKILL.md +93 -0
  225. package/skills/goke/SKILL.md +644 -0
  226. package/skills/jitter/EDITOR.md +219 -0
  227. package/skills/jitter/EXPORT-INTERNALS.md +309 -0
  228. package/skills/jitter/SKILL.md +158 -0
  229. package/skills/jitter/jitter-clipboard.json +1042 -0
  230. package/skills/jitter/package.json +14 -0
  231. package/skills/jitter/tsconfig.json +15 -0
  232. package/skills/jitter/utils/actions.ts +212 -0
  233. package/skills/jitter/utils/export.ts +114 -0
  234. package/skills/jitter/utils/index.ts +141 -0
  235. package/skills/jitter/utils/snapshot.ts +154 -0
  236. package/skills/jitter/utils/traverse.ts +246 -0
  237. package/skills/jitter/utils/types.ts +279 -0
  238. package/skills/jitter/utils/wait.ts +133 -0
  239. package/skills/lintcn/SKILL.md +873 -0
  240. package/skills/new-skill/SKILL.md +211 -0
  241. package/skills/npm-package/SKILL.md +239 -0
  242. package/skills/playwriter/SKILL.md +35 -0
  243. package/skills/proxyman/SKILL.md +215 -0
  244. package/skills/security-review/SKILL.md +208 -0
  245. package/skills/simplify/SKILL.md +58 -0
  246. package/skills/spiceflow/SKILL.md +14 -0
  247. package/skills/termcast/SKILL.md +945 -0
  248. package/skills/tuistory/SKILL.md +250 -0
  249. package/skills/usecomputer/SKILL.md +264 -0
  250. package/skills/x-articles/SKILL.md +554 -0
  251. package/skills/zele/SKILL.md +112 -0
  252. package/skills/zustand-centralized-state/SKILL.md +1004 -0
  253. package/src/agent-model.e2e.test.ts +976 -0
  254. package/src/ai-tool-to-genai.test.ts +296 -0
  255. package/src/ai-tool-to-genai.ts +283 -0
  256. package/src/ai-tool.ts +39 -0
  257. package/src/anthropic-auth-plugin.test.ts +159 -0
  258. package/src/anthropic-auth-plugin.ts +861 -0
  259. package/src/anthropic-auth-state.ts +282 -0
  260. package/src/bin.ts +111 -0
  261. package/src/channel-management.ts +334 -0
  262. package/src/cli-parsing.test.ts +195 -0
  263. package/src/cli-send-thread.e2e.test.ts +464 -0
  264. package/src/cli.ts +4581 -0
  265. package/src/commands/abort.ts +89 -0
  266. package/src/commands/action-buttons.ts +364 -0
  267. package/src/commands/add-project.ts +149 -0
  268. package/src/commands/agent.ts +473 -0
  269. package/src/commands/ask-question.ts +390 -0
  270. package/src/commands/btw.ts +164 -0
  271. package/src/commands/compact.ts +157 -0
  272. package/src/commands/context-usage.ts +199 -0
  273. package/src/commands/create-new-project.ts +190 -0
  274. package/src/commands/diff.ts +91 -0
  275. package/src/commands/file-upload.ts +389 -0
  276. package/src/commands/fork.ts +321 -0
  277. package/src/commands/gemini-apikey.ts +104 -0
  278. package/src/commands/login.ts +1173 -0
  279. package/src/commands/mcp.ts +307 -0
  280. package/src/commands/memory-snapshot.ts +30 -0
  281. package/src/commands/mention-mode.ts +68 -0
  282. package/src/commands/merge-worktree.ts +223 -0
  283. package/src/commands/model-variant.ts +483 -0
  284. package/src/commands/model.ts +1053 -0
  285. package/src/commands/new-worktree.ts +510 -0
  286. package/src/commands/paginated-select.ts +81 -0
  287. package/src/commands/permissions.ts +397 -0
  288. package/src/commands/queue.ts +271 -0
  289. package/src/commands/remove-project.ts +155 -0
  290. package/src/commands/restart-opencode-server.ts +162 -0
  291. package/src/commands/resume.ts +230 -0
  292. package/src/commands/run-command.ts +123 -0
  293. package/src/commands/screenshare.test.ts +30 -0
  294. package/src/commands/screenshare.ts +366 -0
  295. package/src/commands/session-id.ts +109 -0
  296. package/src/commands/session.ts +227 -0
  297. package/src/commands/share.ts +106 -0
  298. package/src/commands/tasks.ts +293 -0
  299. package/src/commands/types.ts +25 -0
  300. package/src/commands/undo-redo.ts +386 -0
  301. package/src/commands/unset-model.ts +173 -0
  302. package/src/commands/upgrade.ts +52 -0
  303. package/src/commands/user-command.ts +198 -0
  304. package/src/commands/verbosity.ts +173 -0
  305. package/src/commands/worktree-settings.ts +70 -0
  306. package/src/commands/worktrees.ts +552 -0
  307. package/src/condense-memory.ts +36 -0
  308. package/src/config.ts +111 -0
  309. package/src/context-awareness-plugin.test.ts +142 -0
  310. package/src/context-awareness-plugin.ts +510 -0
  311. package/src/critique-utils.ts +139 -0
  312. package/src/database.ts +1876 -0
  313. package/src/db.test.ts +162 -0
  314. package/src/db.ts +286 -0
  315. package/src/debounce-timeout.ts +43 -0
  316. package/src/debounced-process-flush.ts +104 -0
  317. package/src/discord-bot.ts +1330 -0
  318. package/src/discord-command-registration.ts +693 -0
  319. package/src/discord-urls.ts +88 -0
  320. package/src/discord-utils.test.ts +153 -0
  321. package/src/discord-utils.ts +800 -0
  322. package/src/errors.ts +201 -0
  323. package/src/escape-backticks.test.ts +469 -0
  324. package/src/event-stream-real-capture.e2e.test.ts +692 -0
  325. package/src/eventsource-parser.test.ts +351 -0
  326. package/src/exec-async.ts +35 -0
  327. package/src/external-opencode-sync.ts +685 -0
  328. package/src/format-tables.test.ts +335 -0
  329. package/src/format-tables.ts +445 -0
  330. package/src/forum-sync/config.ts +92 -0
  331. package/src/forum-sync/discord-operations.ts +241 -0
  332. package/src/forum-sync/index.ts +9 -0
  333. package/src/forum-sync/markdown.ts +172 -0
  334. package/src/forum-sync/sync-to-discord.ts +595 -0
  335. package/src/forum-sync/sync-to-files.ts +294 -0
  336. package/src/forum-sync/types.ts +175 -0
  337. package/src/forum-sync/watchers.ts +454 -0
  338. package/src/gateway-proxy-reconnect.e2e.test.ts +523 -0
  339. package/src/gateway-proxy.e2e.test.ts +640 -0
  340. package/src/genai-worker-wrapper.ts +164 -0
  341. package/src/genai-worker.ts +386 -0
  342. package/src/genai.ts +321 -0
  343. package/src/generated/browser.ts +114 -0
  344. package/src/generated/client.ts +138 -0
  345. package/src/generated/commonInputTypes.ts +736 -0
  346. package/src/generated/enums.ts +88 -0
  347. package/src/generated/internal/class.ts +384 -0
  348. package/src/generated/internal/prismaNamespace.ts +2386 -0
  349. package/src/generated/internal/prismaNamespaceBrowser.ts +326 -0
  350. package/src/generated/models/bot_api_keys.ts +1288 -0
  351. package/src/generated/models/bot_tokens.ts +1656 -0
  352. package/src/generated/models/channel_agents.ts +1256 -0
  353. package/src/generated/models/channel_directories.ts +1859 -0
  354. package/src/generated/models/channel_mention_mode.ts +1300 -0
  355. package/src/generated/models/channel_models.ts +1288 -0
  356. package/src/generated/models/channel_verbosity.ts +1228 -0
  357. package/src/generated/models/channel_worktrees.ts +1300 -0
  358. package/src/generated/models/forum_sync_configs.ts +1452 -0
  359. package/src/generated/models/global_models.ts +1288 -0
  360. package/src/generated/models/ipc_requests.ts +1485 -0
  361. package/src/generated/models/part_messages.ts +1302 -0
  362. package/src/generated/models/scheduled_tasks.ts +2320 -0
  363. package/src/generated/models/session_agents.ts +1086 -0
  364. package/src/generated/models/session_events.ts +1439 -0
  365. package/src/generated/models/session_models.ts +1114 -0
  366. package/src/generated/models/session_start_sources.ts +1408 -0
  367. package/src/generated/models/thread_sessions.ts +1781 -0
  368. package/src/generated/models/thread_worktrees.ts +1356 -0
  369. package/src/generated/models.ts +30 -0
  370. package/src/heap-monitor.ts +152 -0
  371. package/src/hrana-server.test.ts +434 -0
  372. package/src/hrana-server.ts +314 -0
  373. package/src/html-actions.test.ts +87 -0
  374. package/src/html-actions.ts +174 -0
  375. package/src/html-components.test.ts +38 -0
  376. package/src/html-components.ts +181 -0
  377. package/src/image-optimizer-plugin.ts +194 -0
  378. package/src/image-utils.ts +149 -0
  379. package/src/interaction-handler.ts +576 -0
  380. package/src/ipc-polling.ts +326 -0
  381. package/src/ipc-tools-plugin.ts +236 -0
  382. package/src/kimaki-digital-twin.e2e.test.ts +199 -0
  383. package/src/kimaki-opencode-plugin-loading.e2e.test.ts +109 -0
  384. package/src/kimaki-opencode-plugin.test.ts +108 -0
  385. package/src/kimaki-opencode-plugin.ts +18 -0
  386. package/src/limit-heading-depth.test.ts +116 -0
  387. package/src/limit-heading-depth.ts +26 -0
  388. package/src/logger.ts +208 -0
  389. package/src/markdown.test.ts +308 -0
  390. package/src/markdown.ts +410 -0
  391. package/src/message-finish-field.e2e.test.ts +192 -0
  392. package/src/message-formatting.test.ts +81 -0
  393. package/src/message-formatting.ts +533 -0
  394. package/src/message-preprocessing.ts +455 -0
  395. package/src/onboarding-tutorial.ts +176 -0
  396. package/src/onboarding-welcome.ts +49 -0
  397. package/src/openai-realtime.ts +358 -0
  398. package/src/opencode-command-detection.test.ts +307 -0
  399. package/src/opencode-command-detection.ts +76 -0
  400. package/src/opencode-command.test.ts +70 -0
  401. package/src/opencode-command.ts +188 -0
  402. package/src/opencode-interrupt-plugin.test.ts +677 -0
  403. package/src/opencode-interrupt-plugin.ts +477 -0
  404. package/src/opencode.ts +1110 -0
  405. package/src/otto/branding.ts +23 -0
  406. package/src/otto/index.ts +22 -0
  407. package/src/parse-permission-rules.test.ts +127 -0
  408. package/src/patch-text-parser.ts +107 -0
  409. package/src/plugin-logger.ts +68 -0
  410. package/src/privacy-sanitizer.ts +142 -0
  411. package/src/queue-advanced-abort.e2e.test.ts +382 -0
  412. package/src/queue-advanced-action-buttons.e2e.test.ts +268 -0
  413. package/src/queue-advanced-e2e-setup.ts +873 -0
  414. package/src/queue-advanced-footer.e2e.test.ts +576 -0
  415. package/src/queue-advanced-model-switch.e2e.test.ts +383 -0
  416. package/src/queue-advanced-permissions-typing.e2e.test.ts +245 -0
  417. package/src/queue-advanced-question.e2e.test.ts +316 -0
  418. package/src/queue-advanced-typing-interrupt.e2e.test.ts +146 -0
  419. package/src/queue-advanced-typing.e2e.test.ts +199 -0
  420. package/src/queue-drain-after-interactive-ui.e2e.test.ts +151 -0
  421. package/src/queue-interrupt-drain.e2e.test.ts +166 -0
  422. package/src/queue-question-select-drain.e2e.test.ts +152 -0
  423. package/src/runtime-idle-sweeper.ts +76 -0
  424. package/src/runtime-lifecycle.e2e.test.ts +641 -0
  425. package/src/schema.sql +173 -0
  426. package/src/sentry.ts +26 -0
  427. package/src/session-handler/agent-utils.ts +97 -0
  428. package/src/session-handler/event-stream-fixtures/real-session-action-buttons.jsonl +45 -0
  429. package/src/session-handler/event-stream-fixtures/real-session-footer-suppressed-on-pre-idle-interrupt.jsonl +40 -0
  430. package/src/session-handler/event-stream-fixtures/real-session-permission-external-file.jsonl +23 -0
  431. package/src/session-handler/event-stream-fixtures/real-session-task-normal.jsonl +22 -0
  432. package/src/session-handler/event-stream-fixtures/real-session-task-three-parallel-sleeps.jsonl +277 -0
  433. package/src/session-handler/event-stream-fixtures/real-session-task-user-interruption.jsonl +46 -0
  434. package/src/session-handler/event-stream-fixtures/session-abort-after-idle-race.jsonl +21 -0
  435. package/src/session-handler/event-stream-fixtures/session-concurrent-messages-serialized.jsonl +56 -0
  436. package/src/session-handler/event-stream-fixtures/session-explicit-abort.jsonl +44 -0
  437. package/src/session-handler/event-stream-fixtures/session-normal-completion.jsonl +29 -0
  438. package/src/session-handler/event-stream-fixtures/session-tool-call-noisy-stream.jsonl +29 -0
  439. package/src/session-handler/event-stream-fixtures/session-two-completions-same-session.jsonl +50 -0
  440. package/src/session-handler/event-stream-fixtures/session-user-interruption.jsonl +59 -0
  441. package/src/session-handler/event-stream-fixtures/session-voice-queued-followup.jsonl +52 -0
  442. package/src/session-handler/event-stream-state.test.ts +645 -0
  443. package/src/session-handler/event-stream-state.ts +608 -0
  444. package/src/session-handler/model-utils.ts +183 -0
  445. package/src/session-handler/opencode-session-event-log.ts +130 -0
  446. package/src/session-handler/thread-runtime-state.ts +212 -0
  447. package/src/session-handler/thread-session-runtime.ts +4281 -0
  448. package/src/session-handler.ts +15 -0
  449. package/src/session-search.test.ts +50 -0
  450. package/src/session-search.ts +148 -0
  451. package/src/session-title-rename.test.ts +112 -0
  452. package/src/startup-service.ts +200 -0
  453. package/src/startup-time.e2e.test.ts +373 -0
  454. package/src/store.ts +122 -0
  455. package/src/system-message.test.ts +612 -0
  456. package/src/system-message.ts +723 -0
  457. package/src/task-runner.ts +421 -0
  458. package/src/task-schedule.test.ts +84 -0
  459. package/src/task-schedule.ts +311 -0
  460. package/src/test-utils.ts +435 -0
  461. package/src/thinking-utils.ts +61 -0
  462. package/src/thread-message-queue.e2e.test.ts +1219 -0
  463. package/src/tools.ts +430 -0
  464. package/src/undici.d.ts +12 -0
  465. package/src/undo-redo.e2e.test.ts +209 -0
  466. package/src/unnest-code-blocks.test.ts +713 -0
  467. package/src/unnest-code-blocks.ts +185 -0
  468. package/src/upgrade.ts +127 -0
  469. package/src/utils.ts +212 -0
  470. package/src/voice-attachment.ts +51 -0
  471. package/src/voice-handler.ts +908 -0
  472. package/src/voice-message.e2e.test.ts +1255 -0
  473. package/src/voice.test.ts +281 -0
  474. package/src/voice.ts +627 -0
  475. package/src/wait-session.ts +147 -0
  476. package/src/websockify.ts +101 -0
  477. package/src/worker-types.ts +64 -0
  478. package/src/worktree-lifecycle.e2e.test.ts +391 -0
  479. package/src/worktree-utils.ts +4 -0
  480. package/src/worktrees.test.ts +223 -0
  481. package/src/worktrees.ts +1294 -0
  482. package/src/xml.test.ts +38 -0
  483. package/src/xml.ts +121 -0
@@ -0,0 +1,455 @@
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
+
10
+ import type { Message, ThreadChannel } from 'discord.js'
11
+ import type { DiscordFileAttachment } from './message-formatting.js'
12
+ import type { PreprocessResult } from './session-handler/thread-session-runtime.js'
13
+ import type { AgentInfo, RepliedMessageContext } from './system-message.js'
14
+ import {
15
+ resolveMentions,
16
+ getFileAttachments,
17
+ getTextAttachments,
18
+ } from './message-formatting.js'
19
+ import { processVoiceAttachment } from './voice-handler.js'
20
+ import { isVoiceAttachment } from './voice-attachment.js'
21
+ import { initializeOpencodeForDirectory } from './opencode.js'
22
+ import { getCompactSessionContext, getLastSessionId } from './markdown.js'
23
+ import { getThreadSession } from './database.js'
24
+ import * as errore from 'errore'
25
+ import { createLogger, LogPrefix } from './logger.js'
26
+ import { notifyError } from './sentry.js'
27
+
28
+ const logger = createLogger(LogPrefix.SESSION)
29
+ const voiceLogger = createLogger(LogPrefix.VOICE)
30
+
31
+ export const VOICE_MESSAGE_TRANSCRIPTION_PREFIX =
32
+ 'Voice message transcription from Discord user:\n'
33
+
34
+ /** Fetch available agents from OpenCode for voice transcription agent selection. */
35
+ async function fetchAvailableAgents(
36
+ getClient: Awaited<ReturnType<typeof initializeOpencodeForDirectory>>,
37
+ ): Promise<AgentInfo[]> {
38
+ if (getClient instanceof Error) {
39
+ return []
40
+ }
41
+ const result = await errore.tryAsync(() => {
42
+ return getClient().app.agents({})
43
+ })
44
+ if (result instanceof Error) {
45
+ return []
46
+ }
47
+ return (result.data || [])
48
+ .filter((a) => {
49
+ return (a.mode === 'primary' || a.mode === 'all') && !a.hidden
50
+ })
51
+ .map((a) => {
52
+ return { name: a.name, description: a.description }
53
+ })
54
+ }
55
+
56
+ export type { PreprocessResult }
57
+
58
+ // Matches punctuation + "queue" at the end of a message (case-insensitive).
59
+ // Supports any common punctuation before "queue" (. ! ? , ; :) and an optional
60
+ // trailing period: ". queue", "! queue", ". queue.", "!queue." etc.
61
+ // When present the suffix is stripped and the message is routed through
62
+ // kimaki's local queue (same as /queue command).
63
+ const QUEUE_SUFFIX_RE = /[.!?,;:]\s*queue\.?\s*$/i
64
+ const REPLIED_MESSAGE_TEXT_LIMIT = 1_000
65
+
66
+ function extractQueueSuffix(prompt: string): { prompt: string; forceQueue: boolean } {
67
+ if (!QUEUE_SUFFIX_RE.test(prompt)) {
68
+ return { prompt, forceQueue: false }
69
+ }
70
+ return { prompt: prompt.replace(QUEUE_SUFFIX_RE, '').trimEnd(), forceQueue: true }
71
+ }
72
+
73
+ function shouldSkipEmptyPrompt({
74
+ message,
75
+ prompt,
76
+ images,
77
+ hasVoiceAttachment,
78
+ }: {
79
+ message: Message
80
+ prompt: string
81
+ images?: DiscordFileAttachment[]
82
+ hasVoiceAttachment: boolean
83
+ }): boolean {
84
+ if (prompt.trim()) {
85
+ return false
86
+ }
87
+ if ((images?.length || 0) > 0) {
88
+ return false
89
+ }
90
+
91
+ const inferredVoiceAttachment = message.attachments.some((attachment) => {
92
+ return isVoiceAttachment(attachment)
93
+ })
94
+ if (!hasVoiceAttachment && !inferredVoiceAttachment && message.attachments.size === 0) {
95
+ return false
96
+ }
97
+
98
+ voiceLogger.warn(
99
+ `[INGRESS] Skipping empty prompt after preprocessing attachments=${message.attachments.size} hasVoiceAttachment=${hasVoiceAttachment} inferredVoiceAttachment=${inferredVoiceAttachment}`,
100
+ )
101
+ return true
102
+ }
103
+
104
+ async function getRepliedMessageContext({
105
+ message,
106
+ }: {
107
+ message: Message
108
+ }): Promise<RepliedMessageContext | undefined> {
109
+ if (!message.reference?.messageId) {
110
+ return undefined
111
+ }
112
+
113
+ const referencedMessage = await errore.tryAsync(() => {
114
+ return message.fetchReference()
115
+ })
116
+ if (referencedMessage instanceof Error) {
117
+ logger.warn(
118
+ `[INGRESS] Failed to fetch replied message ${message.reference.messageId} for ${message.id}: ${referencedMessage.message}`,
119
+ )
120
+ return undefined
121
+ }
122
+
123
+ const repliedText = resolveMentions(referencedMessage)
124
+ .trim()
125
+ .slice(0, REPLIED_MESSAGE_TEXT_LIMIT)
126
+ if (!repliedText) {
127
+ return undefined
128
+ }
129
+
130
+ return {
131
+ authorUsername: referencedMessage.author.username,
132
+ text: repliedText,
133
+ }
134
+ }
135
+
136
+ /**
137
+ * Pre-process a message in an existing thread (thread already has a session or
138
+ * needs a new one). Handles voice transcription, text/file attachments, and
139
+ * session context fetching for voice messages.
140
+ *
141
+ * For threads with an existing session, voice transcription is enriched with
142
+ * current + last session context (used by the transcription model to better
143
+ * understand domain-specific terms).
144
+ */
145
+ export async function preprocessExistingThreadMessage({
146
+ message,
147
+ thread,
148
+ projectDirectory,
149
+ channelId,
150
+ isCliInjected,
151
+ hasVoiceAttachment,
152
+ appId,
153
+ }: {
154
+ message: Message
155
+ thread: ThreadChannel
156
+ projectDirectory: string
157
+ channelId: string | undefined
158
+ isCliInjected: boolean
159
+ hasVoiceAttachment: boolean
160
+ appId: string | undefined
161
+ }): Promise<PreprocessResult> {
162
+ const sessionId = await getThreadSession(thread.id)
163
+
164
+ // ── No existing session: new session in an existing thread ──
165
+ if (!sessionId) {
166
+ return preprocessNewSessionMessage({
167
+ message,
168
+ thread,
169
+ projectDirectory,
170
+ hasVoiceAttachment,
171
+ appId,
172
+ })
173
+ }
174
+
175
+ // ── Existing session path ──
176
+ voiceLogger.log(`[SESSION] Found session ${sessionId} for thread ${thread.id}`)
177
+
178
+ let messageContent = isCliInjected
179
+ ? (message.content || '')
180
+ : resolveMentions(message)
181
+ const repliedMessage = await getRepliedMessageContext({ message })
182
+
183
+ // Fetch session context and available agents for voice transcription enrichment
184
+ let currentSessionContext: string | undefined
185
+ let lastSessionContext: string | undefined
186
+ let agents: AgentInfo[] = []
187
+
188
+ if (projectDirectory) {
189
+ try {
190
+ const getClient = await initializeOpencodeForDirectory(
191
+ projectDirectory,
192
+ { channelId },
193
+ )
194
+ if (getClient instanceof Error) {
195
+ voiceLogger.error(
196
+ `[SESSION] Failed to initialize OpenCode client:`,
197
+ getClient.message,
198
+ )
199
+ throw new Error(getClient.message)
200
+ }
201
+ const client = getClient()
202
+
203
+ const [sessionContextResult, lastSessionResult, fetchedAgents] = await Promise.all([
204
+ getCompactSessionContext({
205
+ client,
206
+ sessionId,
207
+ includeSystemPrompt: false,
208
+ maxMessages: 15,
209
+ }),
210
+ getLastSessionId({
211
+ client,
212
+ excludeSessionId: sessionId,
213
+ }),
214
+ fetchAvailableAgents(getClient),
215
+ ])
216
+
217
+ if (errore.isOk(sessionContextResult)) {
218
+ currentSessionContext = sessionContextResult
219
+ }
220
+ agents = fetchedAgents
221
+
222
+ const lastSessionId = errore.unwrapOr(lastSessionResult, null)
223
+ if (lastSessionId) {
224
+ const result = await getCompactSessionContext({
225
+ client,
226
+ sessionId: lastSessionId,
227
+ includeSystemPrompt: true,
228
+ maxMessages: 10,
229
+ })
230
+ if (errore.isOk(result)) {
231
+ lastSessionContext = result
232
+ }
233
+ }
234
+ } catch (e) {
235
+ voiceLogger.error(`Could not get session context:`, e)
236
+ void notifyError(e, 'Failed to get session context')
237
+ }
238
+ }
239
+
240
+ const voiceResult = await processVoiceAttachment({
241
+ message,
242
+ thread,
243
+ projectDirectory,
244
+ appId,
245
+ currentSessionContext,
246
+ lastSessionContext,
247
+ agents,
248
+ })
249
+ if (voiceResult) {
250
+ messageContent = `${VOICE_MESSAGE_TRANSCRIPTION_PREFIX}${voiceResult.transcription}`
251
+ }
252
+
253
+ // Voice transcription failed and no text — drop silently
254
+ if (hasVoiceAttachment && !voiceResult && !messageContent.trim()) {
255
+ return { prompt: '', mode: 'opencode', skip: true }
256
+ }
257
+
258
+ // Extract queue suffix from raw message content BEFORE appending text
259
+ // attachments. Otherwise a text file attachment pushes "? queue" away from
260
+ // the end of the string and the regex fails to match.
261
+ const qs = extractQueueSuffix(messageContent)
262
+
263
+ const fileAttachments = await getFileAttachments(message)
264
+ const textAttachmentsContent = await getTextAttachments(message)
265
+ const prompt = textAttachmentsContent
266
+ ? `${qs.prompt}\n\n${textAttachmentsContent}`
267
+ : qs.prompt
268
+
269
+ if (
270
+ shouldSkipEmptyPrompt({
271
+ message,
272
+ prompt,
273
+ images: fileAttachments,
274
+ hasVoiceAttachment,
275
+ })
276
+ ) {
277
+ return { prompt: '', mode: 'opencode', skip: true }
278
+ }
279
+
280
+ return {
281
+ prompt,
282
+ images: fileAttachments.length > 0 ? fileAttachments : undefined,
283
+ repliedMessage,
284
+ mode: qs.forceQueue || voiceResult?.queueMessage ? 'local-queue' : 'opencode',
285
+ agent: voiceResult?.agent,
286
+ }
287
+ }
288
+
289
+ /**
290
+ * Pre-process a message that starts a new session in a thread (no existing
291
+ * session). Handles starter message context, voice transcription, and
292
+ * text/file attachments.
293
+ */
294
+ export async function preprocessNewSessionMessage({
295
+ message,
296
+ thread,
297
+ projectDirectory,
298
+ hasVoiceAttachment,
299
+ appId,
300
+ }: {
301
+ message: Message
302
+ thread: ThreadChannel
303
+ projectDirectory: string
304
+ hasVoiceAttachment: boolean
305
+ appId?: string
306
+ }): Promise<PreprocessResult> {
307
+ logger.log(`No session for thread ${thread.id}, starting new session`)
308
+
309
+ // Fetch available agents only for voice messages to avoid unnecessary SDK
310
+ // roundtrips on plain text messages.
311
+ let agents: AgentInfo[] = []
312
+ if (hasVoiceAttachment && projectDirectory) {
313
+ try {
314
+ const getClient = await initializeOpencodeForDirectory(projectDirectory)
315
+ agents = await fetchAvailableAgents(getClient)
316
+ } catch (e) {
317
+ voiceLogger.error(`Could not fetch agents for voice transcription:`, e)
318
+ }
319
+ }
320
+
321
+ let prompt = resolveMentions(message)
322
+ const repliedMessage = await getRepliedMessageContext({ message })
323
+ const voiceResult = await processVoiceAttachment({
324
+ message,
325
+ thread,
326
+ projectDirectory,
327
+ appId,
328
+ agents,
329
+ })
330
+ if (voiceResult) {
331
+ prompt = `${VOICE_MESSAGE_TRANSCRIPTION_PREFIX}${voiceResult.transcription}`
332
+ }
333
+
334
+ // Voice transcription failed and no text — drop silently
335
+ if (hasVoiceAttachment && !voiceResult && !prompt.trim()) {
336
+ return { prompt: '', mode: 'opencode', skip: true }
337
+ }
338
+
339
+ // Fetch starter message for thread context
340
+ const starterMessage = await thread
341
+ .fetchStarterMessage()
342
+ .catch((error) => {
343
+ logger.warn(
344
+ `[SESSION] Failed to fetch starter message for thread ${thread.id}:`,
345
+ error instanceof Error ? error.stack : String(error),
346
+ )
347
+ return null
348
+ })
349
+ if (starterMessage && starterMessage.content !== message.content) {
350
+ const starterTextAttachments = await getTextAttachments(starterMessage)
351
+ const starterContent = resolveMentions(starterMessage)
352
+ const starterText = starterTextAttachments
353
+ ? `${starterContent}\n\n${starterTextAttachments}`
354
+ : starterContent
355
+ if (starterText) {
356
+ prompt = `Context from thread:\n${starterText}\n\nUser request:\n${prompt}`
357
+ }
358
+ }
359
+
360
+ const qs = extractQueueSuffix(prompt)
361
+ if (
362
+ shouldSkipEmptyPrompt({
363
+ message,
364
+ prompt: qs.prompt,
365
+ hasVoiceAttachment,
366
+ })
367
+ ) {
368
+ return { prompt: '', mode: 'opencode', skip: true }
369
+ }
370
+
371
+ return {
372
+ prompt: qs.prompt,
373
+ repliedMessage,
374
+ mode: qs.forceQueue || voiceResult?.queueMessage ? 'local-queue' : 'opencode',
375
+ agent: voiceResult?.agent,
376
+ }
377
+ }
378
+
379
+ /**
380
+ * Pre-process a message from a text channel (creates a new thread).
381
+ * Handles voice transcription and file/text attachments.
382
+ */
383
+ export async function preprocessNewThreadMessage({
384
+ message,
385
+ thread,
386
+ projectDirectory,
387
+ hasVoiceAttachment,
388
+ appId,
389
+ }: {
390
+ message: Message
391
+ thread: ThreadChannel
392
+ projectDirectory: string
393
+ hasVoiceAttachment: boolean
394
+ appId?: string
395
+ }): Promise<PreprocessResult> {
396
+ // Fetch available agents only for voice messages to avoid unnecessary SDK
397
+ // roundtrips on plain text messages.
398
+ let agents: AgentInfo[] = []
399
+ if (hasVoiceAttachment && projectDirectory) {
400
+ try {
401
+ const getClient = await initializeOpencodeForDirectory(projectDirectory)
402
+ agents = await fetchAvailableAgents(getClient)
403
+ } catch (e) {
404
+ voiceLogger.error(`Could not fetch agents for voice transcription:`, e)
405
+ }
406
+ }
407
+
408
+ let messageContent = resolveMentions(message)
409
+ const repliedMessage = await getRepliedMessageContext({ message })
410
+ const voiceResult = await processVoiceAttachment({
411
+ message,
412
+ thread,
413
+ projectDirectory,
414
+ isNewThread: true,
415
+ appId,
416
+ agents,
417
+ })
418
+ if (voiceResult) {
419
+ messageContent = `${VOICE_MESSAGE_TRANSCRIPTION_PREFIX}${voiceResult.transcription}`
420
+ }
421
+
422
+ // Voice transcription failed and no text — drop silently
423
+ if (hasVoiceAttachment && !voiceResult && !messageContent.trim()) {
424
+ return { prompt: '', mode: 'opencode', skip: true }
425
+ }
426
+
427
+ // Extract queue suffix from raw message content BEFORE appending text
428
+ // attachments (same fix as preprocessExistingThreadMessage).
429
+ const qs = extractQueueSuffix(messageContent)
430
+
431
+ const fileAttachments = await getFileAttachments(message)
432
+ const textAttachmentsContent = await getTextAttachments(message)
433
+ const prompt = textAttachmentsContent
434
+ ? `${qs.prompt}\n\n${textAttachmentsContent}`
435
+ : qs.prompt
436
+
437
+ if (
438
+ shouldSkipEmptyPrompt({
439
+ message,
440
+ prompt,
441
+ images: fileAttachments,
442
+ hasVoiceAttachment,
443
+ })
444
+ ) {
445
+ return { prompt: '', mode: 'opencode', skip: true }
446
+ }
447
+
448
+ return {
449
+ prompt,
450
+ images: fileAttachments.length > 0 ? fileAttachments : undefined,
451
+ repliedMessage,
452
+ mode: qs.forceQueue || voiceResult?.queueMessage ? 'local-queue' : 'opencode',
453
+ agent: voiceResult?.agent,
454
+ }
455
+ }
@@ -0,0 +1,176 @@
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
+
8
+ // Unique text used in the welcome message and detected by the plugin to
9
+ // trigger tutorial instruction injection. Shared constant so they can't
10
+ // drift out of sync.
11
+ export const TUTORIAL_WELCOME_TEXT =
12
+ 'Want to build an example browser game? Respond in this thread.'
13
+
14
+ const markdown = String.raw
15
+ const backticks = '```'
16
+
17
+ export const ONBOARDING_TUTORIAL_INSTRUCTIONS = markdown`
18
+ 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.
19
+
20
+ ## Prerequisites
21
+
22
+ Before doing anything else, check that these are installed:
23
+
24
+ **Bun** (v1.2 or later) — runtime and bundler:
25
+
26
+ ${backticks}bash
27
+ bun --version
28
+ ${backticks}
29
+
30
+ If missing or below 1.2, tell the user to install it: https://bun.sh — or run:
31
+
32
+ ${backticks}bash
33
+ curl -fsSL https://bun.sh/install | bash
34
+ ${backticks}
35
+
36
+ **tmux** — needed to run the dev server in the background with kimaki tunnel:
37
+
38
+ ${backticks}bash
39
+ tmux -V
40
+ ${backticks}
41
+
42
+ If missing, tell the user to install it: https://github.com/tmux/tmux/wiki/Installing — or:
43
+
44
+ ${backticks}bash
45
+ # macOS
46
+ brew install tmux
47
+
48
+ # Ubuntu/Debian
49
+ sudo apt-get install tmux
50
+ ${backticks}
51
+
52
+ Do NOT use Node.js, npm, or npx. Use Bun for everything.
53
+
54
+ ## Goal
55
+
56
+ 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.
57
+
58
+ ## Game idea
59
+
60
+ Build a "Space Dodge" game:
61
+ - The player controls a spaceship that flies forward through space
62
+ - Asteroids/obstacles come toward the player
63
+ - The player dodges left/right/up/down using arrow keys or WASD
64
+ - Touch/swipe controls for mobile — the user is on Discord and may open the link on their phone
65
+ - Score increases over time, speed gradually increases
66
+ - Particle effects for explosions when hit
67
+ - Starfield background for atmosphere
68
+ - Simple start screen and game over screen with score
69
+
70
+ If the game idea doesn't match what the user asked for, adapt to their request instead.
71
+
72
+ ## Project setup
73
+
74
+ Create these files:
75
+
76
+ **package.json** — install three as a dependency:
77
+ ${backticks}json
78
+ {
79
+ "dependencies": {
80
+ "three": "^0.170.0"
81
+ }
82
+ }
83
+ ${backticks}
84
+
85
+ Run bun install after creating it.
86
+
87
+ **tsconfig.json**:
88
+ ${backticks}json
89
+ {
90
+ "compilerOptions": {
91
+ "target": "ESNext",
92
+ "module": "ESNext",
93
+ "moduleResolution": "bundler",
94
+ "strict": true,
95
+ "jsx": "react-jsx",
96
+ "types": ["three"]
97
+ }
98
+ }
99
+ ${backticks}
100
+
101
+ **index.html** — the entry point, references the TypeScript source:
102
+ ${backticks}html
103
+ <!DOCTYPE html>
104
+ <html>
105
+ <head>
106
+ <meta charset="utf-8" />
107
+ <meta name="viewport" content="width=device-width, initial-scale=1" />
108
+ <title>Space Dodge</title>
109
+ <style>
110
+ body { margin: 0; overflow: hidden; }
111
+ canvas { display: block; }
112
+ </style>
113
+ </head>
114
+ <body>
115
+ <script type="module" src="./game.ts"></script>
116
+ </body>
117
+ </html>
118
+ ${backticks}
119
+
120
+ **game.ts** — all game logic in TypeScript, importing from "three":
121
+ ${backticks}ts
122
+ import * as THREE from "three"
123
+ // ... game code here
124
+ ${backticks}
125
+
126
+ 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.
127
+
128
+ **server.ts** — Bun fullstack dev server (reads port from PORT env var):
129
+ ${backticks}ts
130
+ import homepage from "./index.html"
131
+
132
+ Bun.serve({
133
+ port: Number(process.env.PORT) || 3000,
134
+ routes: { "/": homepage },
135
+ development: true,
136
+ })
137
+ ${backticks}
138
+
139
+ ## Dev server and tunnel
140
+
141
+ 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.
142
+
143
+ Pick a random port between 3000-9000 to avoid conflicts:
144
+
145
+ ${backticks}bash
146
+ PORT=$((RANDOM % 6000 + 3000))
147
+ tmux kill-session -t game-dev 2>/dev/null
148
+ tmux new-session -d -s game-dev -c "$PWD"
149
+ tmux send-keys -t game-dev "PORT=$PORT kimaki tunnel -p $PORT -- bun run server.ts" Enter
150
+ ${backticks}
151
+
152
+ Wait a moment, then get the tunnel URL:
153
+
154
+ ${backticks}bash
155
+ sleep 1 && tmux capture-pane -t game-dev -p
156
+ ${backticks}
157
+
158
+ If the tunnel URL is not visible yet, run the capture command again — it usually appears within a few seconds.
159
+
160
+ After the tunnel is up, also generate a diff URL so the user can browse the code:
161
+
162
+ ${backticks}bash
163
+ git init && git add -A && critique --url
164
+ ${backticks}
165
+
166
+ 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.
167
+
168
+ ## Behavior
169
+
170
+ - Be friendly and casual in the conversation. Sound like a helpful teammate, not a formal tutorial.
171
+ - Be concise and direct. Show the code, start the server, share the URL.
172
+ - 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.
173
+ - Never mention this is a tutorial, onboarding, or demo. Act like a normal coding session.
174
+ - Never apologize or over-explain. Just build and iterate.
175
+ - If the user asks to change something, do it quickly and share the updated URL.
176
+ `
@@ -0,0 +1,49 @@
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
+
8
+ import { ThreadAutoArchiveDuration, type TextChannel } from 'discord.js'
9
+ import { createLogger, LogPrefix } from './logger.js'
10
+ import { TUTORIAL_WELCOME_TEXT } from './onboarding-tutorial.js'
11
+
12
+ const logger = createLogger(LogPrefix.CHANNEL)
13
+
14
+ function buildWelcomeText(): string {
15
+ 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.
16
+ **What you can do:**
17
+ - Use \`/add-project\` to create a Discord channel linked to one OpenCode project (git repo)
18
+ - Collaborate with teammates in the same session
19
+ - Upload images and files, the bot can share screenshots back
20
+ ${TUTORIAL_WELCOME_TEXT}`
21
+ }
22
+
23
+ function buildThreadPrompt({ mentionUserId }: { mentionUserId?: string }): string {
24
+ const mentionSuffix = mentionUserId ? ` <@${mentionUserId}>` : ''
25
+ return `Want to build an example browser game? Respond in this thread.${mentionSuffix}`
26
+ }
27
+
28
+ export async function sendWelcomeMessage({
29
+ channel,
30
+ mentionUserId,
31
+ }: {
32
+ channel: TextChannel
33
+ mentionUserId?: string
34
+ }): Promise<void> {
35
+ try {
36
+ const message = await channel.send(buildWelcomeText())
37
+ const thread = await message.startThread({
38
+ name: 'Kimaki tutorial',
39
+ autoArchiveDuration: ThreadAutoArchiveDuration.OneDay,
40
+ reason: 'Onboarding tutorial thread',
41
+ })
42
+ await thread.send(buildThreadPrompt({ mentionUserId }))
43
+ logger.log(`Sent welcome message with thread to #${channel.name}`)
44
+ } catch (error) {
45
+ logger.warn(
46
+ `Failed to send welcome message to #${channel.name}: ${error instanceof Error ? error.stack : String(error)}`,
47
+ )
48
+ }
49
+ }