@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,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
+ }