kimaki 0.31.0 → 1.1.0

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 (1001) hide show
  1. package/dist/agent-ui.d.ts +33 -0
  2. package/dist/agent-ui.e2e.test.d.ts +1 -0
  3. package/dist/agent-ui.e2e.test.js +140 -0
  4. package/dist/agent-ui.e2e.test.js.map +1 -0
  5. package/dist/agent-ui.js +199 -0
  6. package/dist/agent-ui.js.map +1 -0
  7. package/dist/analytics.d.ts +42 -0
  8. package/dist/analytics.e2e.test.d.ts +1 -0
  9. package/dist/analytics.e2e.test.js +112 -0
  10. package/dist/analytics.e2e.test.js.map +1 -0
  11. package/dist/analytics.js +162 -223
  12. package/dist/analytics.js.map +1 -0
  13. package/dist/analytics.test.d.ts +1 -0
  14. package/dist/analytics.test.js +121 -157
  15. package/dist/analytics.test.js.map +1 -0
  16. package/dist/autostart.d.ts +7 -0
  17. package/dist/autostart.js +87 -0
  18. package/dist/autostart.js.map +1 -0
  19. package/dist/bot.d.ts +91 -0
  20. package/dist/bot.js +120 -0
  21. package/dist/bot.js.map +1 -0
  22. package/dist/btw.e2e.test.d.ts +1 -0
  23. package/dist/btw.e2e.test.js +102 -0
  24. package/dist/btw.e2e.test.js.map +1 -0
  25. package/dist/builtin-skills.d.ts +18 -0
  26. package/dist/builtin-skills.js +51 -0
  27. package/dist/builtin-skills.js.map +1 -0
  28. package/dist/builtin-skills.test.d.ts +1 -0
  29. package/dist/builtin-skills.test.js +28 -0
  30. package/dist/builtin-skills.test.js.map +1 -0
  31. package/dist/catalog-refresh.e2e.test.d.ts +1 -0
  32. package/dist/catalog-refresh.e2e.test.js +90 -0
  33. package/dist/catalog-refresh.e2e.test.js.map +1 -0
  34. package/dist/cli/bot.d.ts +5 -0
  35. package/dist/cli/bot.js +434 -0
  36. package/dist/cli/bot.js.map +1 -0
  37. package/dist/cli/discord.d.ts +3 -0
  38. package/dist/cli/discord.js +118 -0
  39. package/dist/cli/discord.js.map +1 -0
  40. package/dist/cli/project.d.ts +5 -0
  41. package/dist/cli/project.js +143 -0
  42. package/dist/cli/project.js.map +1 -0
  43. package/dist/cli/schedule.d.ts +3 -0
  44. package/dist/cli/schedule.js +156 -0
  45. package/dist/cli/schedule.js.map +1 -0
  46. package/dist/cli/session.d.ts +4 -0
  47. package/dist/cli/session.js +245 -0
  48. package/dist/cli/session.js.map +1 -0
  49. package/dist/cli/shared.d.ts +55 -0
  50. package/dist/cli/shared.js +138 -0
  51. package/dist/cli/shared.js.map +1 -0
  52. package/dist/cli-discord.e2e.test.d.ts +1 -0
  53. package/dist/cli-discord.e2e.test.js +82 -0
  54. package/dist/cli-discord.e2e.test.js.map +1 -0
  55. package/dist/cli-lock.e2e.test.d.ts +1 -0
  56. package/dist/cli-lock.e2e.test.js +520 -0
  57. package/dist/cli-lock.e2e.test.js.map +1 -0
  58. package/dist/cli-read.e2e.test.d.ts +1 -0
  59. package/dist/cli-read.e2e.test.js +86 -0
  60. package/dist/cli-read.e2e.test.js.map +1 -0
  61. package/dist/cli-sessions.e2e.test.d.ts +1 -0
  62. package/dist/cli-sessions.e2e.test.js +116 -0
  63. package/dist/cli-sessions.e2e.test.js.map +1 -0
  64. package/dist/cli.d.ts +2 -0
  65. package/dist/cli.js +34 -263
  66. package/dist/cli.js.map +1 -0
  67. package/dist/commands/login-commands.d.ts +59 -0
  68. package/dist/commands/login-commands.js +341 -0
  69. package/dist/commands/login-commands.js.map +1 -0
  70. package/dist/commands/preference-commands.d.ts +44 -0
  71. package/dist/commands/preference-commands.js +565 -0
  72. package/dist/commands/preference-commands.js.map +1 -0
  73. package/dist/commands/session-commands.d.ts +8 -0
  74. package/dist/commands/session-commands.js +267 -0
  75. package/dist/commands/session-commands.js.map +1 -0
  76. package/dist/commands/thread-commands.d.ts +16 -0
  77. package/dist/commands/thread-commands.js +285 -0
  78. package/dist/commands/thread-commands.js.map +1 -0
  79. package/dist/commands/worktree-commands.d.ts +41 -0
  80. package/dist/commands/worktree-commands.js +279 -0
  81. package/dist/commands/worktree-commands.js.map +1 -0
  82. package/dist/commands.e2e.test.d.ts +1 -0
  83. package/dist/commands.e2e.test.js +255 -0
  84. package/dist/commands.e2e.test.js.map +1 -0
  85. package/dist/credentials.d.ts +84 -0
  86. package/dist/credentials.js +320 -0
  87. package/dist/credentials.js.map +1 -0
  88. package/dist/db.d.ts +1532 -0
  89. package/dist/db.js +57 -245
  90. package/dist/db.js.map +1 -0
  91. package/dist/db.test.d.ts +1 -0
  92. package/dist/db.test.js +351 -682
  93. package/dist/db.test.js.map +1 -0
  94. package/dist/duration.d.ts +2 -0
  95. package/dist/duration.js +18 -0
  96. package/dist/duration.js.map +1 -0
  97. package/dist/effects.d.ts +15 -0
  98. package/dist/effects.js +315 -0
  99. package/dist/effects.js.map +1 -0
  100. package/dist/errors.d.ts +38 -0
  101. package/dist/errors.js +40 -175
  102. package/dist/errors.js.map +1 -0
  103. package/dist/event-loop.d.ts +41 -0
  104. package/dist/event-loop.js +418 -0
  105. package/dist/event-loop.js.map +1 -0
  106. package/dist/file-edit-log.d.ts +41 -0
  107. package/dist/file-edit-log.js +122 -164
  108. package/dist/file-edit-log.js.map +1 -0
  109. package/dist/file-edit-log.test.d.ts +1 -0
  110. package/dist/file-edit-log.test.js +68 -244
  111. package/dist/file-edit-log.test.js.map +1 -0
  112. package/dist/format-parts.d.ts +137 -0
  113. package/dist/format-parts.js +300 -0
  114. package/dist/format-parts.js.map +1 -0
  115. package/dist/format-parts.test.d.ts +1 -0
  116. package/dist/format-parts.test.js +144 -0
  117. package/dist/format-parts.test.js.map +1 -0
  118. package/dist/gateway-replay.e2e.test.d.ts +1 -0
  119. package/dist/gateway-replay.e2e.test.js +95 -0
  120. package/dist/gateway-replay.e2e.test.js.map +1 -0
  121. package/dist/harness.e2e.test.d.ts +1 -0
  122. package/dist/harness.e2e.test.js +107 -0
  123. package/dist/harness.e2e.test.js.map +1 -0
  124. package/dist/hello-thread.e2e.test.d.ts +1 -0
  125. package/dist/hello-thread.e2e.test.js +194 -0
  126. package/dist/hello-thread.e2e.test.js.map +1 -0
  127. package/dist/ingress.d.ts +7 -0
  128. package/dist/ingress.js +337 -0
  129. package/dist/ingress.js.map +1 -0
  130. package/dist/interaction-context.d.ts +45 -0
  131. package/dist/interaction-context.js +77 -0
  132. package/dist/interaction-context.js.map +1 -0
  133. package/dist/interrupt.e2e.test.d.ts +1 -0
  134. package/dist/interrupt.e2e.test.js +124 -0
  135. package/dist/interrupt.e2e.test.js.map +1 -0
  136. package/dist/legacy.e2e.test.d.ts +1 -0
  137. package/dist/legacy.e2e.test.js +85 -0
  138. package/dist/legacy.e2e.test.js.map +1 -0
  139. package/dist/live-call.d.ts +51 -0
  140. package/dist/live-call.js +207 -0
  141. package/dist/live-call.js.map +1 -0
  142. package/dist/lock-routes.d.ts +812 -0
  143. package/dist/lock-routes.js +635 -0
  144. package/dist/lock-routes.js.map +1 -0
  145. package/dist/lock-server.d.ts +36 -0
  146. package/dist/lock-server.js +223 -0
  147. package/dist/lock-server.js.map +1 -0
  148. package/dist/logger.d.ts +13 -0
  149. package/dist/logger.js +112 -162
  150. package/dist/logger.js.map +1 -0
  151. package/dist/login.e2e.test.d.ts +1 -0
  152. package/dist/login.e2e.test.js +83 -0
  153. package/dist/login.e2e.test.js.map +1 -0
  154. package/dist/main.d.ts +33 -0
  155. package/dist/main.js +261 -0
  156. package/dist/main.js.map +1 -0
  157. package/dist/markdown/components.d.ts +10 -0
  158. package/dist/markdown/components.js +77 -0
  159. package/dist/markdown/components.js.map +1 -0
  160. package/dist/markdown/markdown.test.d.ts +1 -0
  161. package/dist/markdown/markdown.test.js +393 -0
  162. package/dist/markdown/markdown.test.js.map +1 -0
  163. package/dist/markdown/render-markdown.d.ts +30 -0
  164. package/dist/markdown/render-markdown.js +265 -0
  165. package/dist/markdown/render-markdown.js.map +1 -0
  166. package/dist/message-tool.e2e.test.d.ts +1 -0
  167. package/dist/message-tool.e2e.test.js +39 -0
  168. package/dist/message-tool.e2e.test.js.map +1 -0
  169. package/dist/migrations.d.ts +8 -0
  170. package/dist/migrations.js +187 -0
  171. package/dist/migrations.js.map +1 -0
  172. package/dist/onboarding-cli.e2e.test.d.ts +1 -0
  173. package/dist/onboarding-cli.e2e.test.js +178 -0
  174. package/dist/onboarding-cli.e2e.test.js.map +1 -0
  175. package/dist/onboarding.d.ts +36 -0
  176. package/dist/onboarding.e2e.test.d.ts +1 -0
  177. package/dist/onboarding.e2e.test.js +148 -0
  178. package/dist/onboarding.e2e.test.js.map +1 -0
  179. package/dist/onboarding.js +261 -0
  180. package/dist/onboarding.js.map +1 -0
  181. package/dist/opencode-server.d.ts +54 -0
  182. package/dist/opencode-server.js +417 -0
  183. package/dist/opencode-server.js.map +1 -0
  184. package/dist/opencode-server.test.d.ts +1 -0
  185. package/dist/opencode-server.test.js +21 -0
  186. package/dist/opencode-server.test.js.map +1 -0
  187. package/dist/permissions.d.ts +31 -0
  188. package/dist/permissions.e2e.test.d.ts +1 -0
  189. package/dist/permissions.e2e.test.js +166 -0
  190. package/dist/permissions.e2e.test.js.map +1 -0
  191. package/dist/permissions.js +95 -0
  192. package/dist/permissions.js.map +1 -0
  193. package/dist/plugin/anthropic/index.d.ts +13 -0
  194. package/dist/plugin/anthropic/index.js +201 -0
  195. package/dist/plugin/anthropic/index.js.map +1 -0
  196. package/dist/plugin/anthropic/index.test.d.ts +1 -0
  197. package/dist/plugin/anthropic/index.test.js +39 -0
  198. package/dist/plugin/anthropic/index.test.js.map +1 -0
  199. package/dist/plugin/index.d.ts +3 -0
  200. package/dist/plugin/index.js +124 -0
  201. package/dist/plugin/index.js.map +1 -0
  202. package/dist/plugin/read-media.d.ts +23 -0
  203. package/dist/plugin/read-media.js +72 -0
  204. package/dist/plugin/read-media.js.map +1 -0
  205. package/dist/plugin/session-id/index.d.ts +3 -0
  206. package/dist/plugin/session-id/index.js +18 -0
  207. package/dist/plugin/session-id/index.js.map +1 -0
  208. package/dist/plugin.e2e.test.d.ts +1 -0
  209. package/dist/plugin.e2e.test.js +92 -0
  210. package/dist/plugin.e2e.test.js.map +1 -0
  211. package/dist/preference-commands.e2e.test.d.ts +1 -0
  212. package/dist/preference-commands.e2e.test.js +555 -0
  213. package/dist/preference-commands.e2e.test.js.map +1 -0
  214. package/dist/probe-live.tmp.d.ts +1 -0
  215. package/dist/probe-live.tmp.js +82 -0
  216. package/dist/probe-live.tmp.js.map +1 -0
  217. package/dist/project.d.ts +58 -0
  218. package/dist/project.js +169 -0
  219. package/dist/project.js.map +1 -0
  220. package/dist/prompt.d.ts +112 -0
  221. package/dist/prompt.js +577 -0
  222. package/dist/prompt.js.map +1 -0
  223. package/dist/questions.d.ts +59 -0
  224. package/dist/questions.e2e.test.d.ts +1 -0
  225. package/dist/questions.e2e.test.js +290 -0
  226. package/dist/questions.e2e.test.js.map +1 -0
  227. package/dist/questions.js +231 -0
  228. package/dist/questions.js.map +1 -0
  229. package/dist/queue.d.ts +45 -0
  230. package/dist/queue.e2e.test.d.ts +1 -0
  231. package/dist/queue.e2e.test.js +220 -0
  232. package/dist/queue.e2e.test.js.map +1 -0
  233. package/dist/queue.js +132 -0
  234. package/dist/queue.js.map +1 -0
  235. package/dist/remote-send.d.ts +32 -0
  236. package/dist/remote-send.e2e.test.d.ts +1 -0
  237. package/dist/remote-send.e2e.test.js +89 -0
  238. package/dist/remote-send.e2e.test.js.map +1 -0
  239. package/dist/remote-send.js +105 -0
  240. package/dist/remote-send.js.map +1 -0
  241. package/dist/renderer.e2e.test.d.ts +1 -0
  242. package/dist/renderer.e2e.test.js +357 -0
  243. package/dist/renderer.e2e.test.js.map +1 -0
  244. package/dist/restart.e2e.test.d.ts +1 -0
  245. package/dist/restart.e2e.test.js +68 -0
  246. package/dist/restart.e2e.test.js.map +1 -0
  247. package/dist/routes.d.ts +24 -0
  248. package/dist/routes.js +42 -0
  249. package/dist/routes.js.map +1 -0
  250. package/dist/routes.test.d.ts +1 -0
  251. package/dist/routes.test.js +79 -0
  252. package/dist/routes.test.js.map +1 -0
  253. package/dist/routines.e2e.test.d.ts +1 -0
  254. package/dist/routines.e2e.test.js +188 -0
  255. package/dist/routines.e2e.test.js.map +1 -0
  256. package/dist/scheduler.d.ts +116 -0
  257. package/dist/scheduler.e2e.test.d.ts +1 -0
  258. package/dist/scheduler.e2e.test.js +163 -0
  259. package/dist/scheduler.e2e.test.js.map +1 -0
  260. package/dist/scheduler.js +602 -0
  261. package/dist/scheduler.js.map +1 -0
  262. package/dist/scheduler.test.d.ts +1 -0
  263. package/dist/scheduler.test.js +180 -0
  264. package/dist/scheduler.test.js.map +1 -0
  265. package/dist/schema-sql.d.ts +1 -0
  266. package/dist/schema-sql.js +3 -0
  267. package/dist/schema-sql.js.map +1 -0
  268. package/dist/schema.d.ts +2968 -0
  269. package/dist/schema.js +95 -252
  270. package/dist/schema.js.map +1 -0
  271. package/dist/session-commands.e2e.test.d.ts +1 -0
  272. package/dist/session-commands.e2e.test.js +285 -0
  273. package/dist/session-commands.e2e.test.js.map +1 -0
  274. package/dist/session-events.d.ts +83 -0
  275. package/dist/session-events.js +260 -0
  276. package/dist/session-events.js.map +1 -0
  277. package/dist/session-ops.e2e.test.d.ts +1 -0
  278. package/dist/session-ops.e2e.test.js +114 -0
  279. package/dist/session-ops.e2e.test.js.map +1 -0
  280. package/dist/sessions.d.ts +121 -0
  281. package/dist/sessions.js +663 -0
  282. package/dist/sessions.js.map +1 -0
  283. package/dist/shell.e2e.test.d.ts +1 -0
  284. package/dist/shell.e2e.test.js +147 -0
  285. package/dist/shell.e2e.test.js.map +1 -0
  286. package/dist/slash-commands.d.ts +62 -0
  287. package/dist/slash-commands.js +476 -0
  288. package/dist/slash-commands.js.map +1 -0
  289. package/dist/slash-commands.test.d.ts +1 -0
  290. package/dist/slash-commands.test.js +87 -0
  291. package/dist/slash-commands.test.js.map +1 -0
  292. package/dist/sleep.e2e.test.d.ts +1 -0
  293. package/dist/sleep.e2e.test.js +115 -487
  294. package/dist/sleep.e2e.test.js.map +1 -0
  295. package/dist/sleeps.d.ts +37 -0
  296. package/dist/sleeps.js +220 -0
  297. package/dist/sleeps.js.map +1 -0
  298. package/dist/store.d.ts +10 -0
  299. package/dist/store.js +8 -28
  300. package/dist/store.js.map +1 -0
  301. package/dist/system-prompt.d.ts +76 -0
  302. package/dist/system-prompt.js +852 -0
  303. package/dist/system-prompt.js.map +1 -0
  304. package/dist/tasks.e2e.test.d.ts +1 -0
  305. package/dist/tasks.e2e.test.js +173 -0
  306. package/dist/tasks.e2e.test.js.map +1 -0
  307. package/dist/test/auth-plugin/index.d.ts +3 -0
  308. package/dist/test/auth-plugin/index.js +18 -0
  309. package/dist/test/auth-plugin/index.js.map +1 -0
  310. package/dist/test/fake-mcp-server.d.ts +1 -0
  311. package/dist/test/fake-mcp-server.js +45 -0
  312. package/dist/test/fake-mcp-server.js.map +1 -0
  313. package/dist/test/harness.d.ts +246 -0
  314. package/dist/test/harness.js +704 -0
  315. package/dist/test/harness.js.map +1 -0
  316. package/dist/test/replay.d.ts +14 -0
  317. package/dist/test/replay.js +75 -0
  318. package/dist/test/replay.js.map +1 -0
  319. package/dist/thread-reducer.d.ts +172 -0
  320. package/dist/thread-reducer.fixtures.test.d.ts +1 -0
  321. package/dist/thread-reducer.fixtures.test.js +360 -0
  322. package/dist/thread-reducer.fixtures.test.js.map +1 -0
  323. package/dist/thread-reducer.js +497 -0
  324. package/dist/thread-reducer.js.map +1 -0
  325. package/dist/thread-reducer.test.d.ts +1 -0
  326. package/dist/thread-reducer.test.js +369 -0
  327. package/dist/thread-reducer.test.js.map +1 -0
  328. package/dist/tts.test.d.ts +1 -0
  329. package/dist/tts.test.js +147 -0
  330. package/dist/tts.test.js.map +1 -0
  331. package/dist/voice-agent.d.ts +33 -0
  332. package/dist/voice-agent.js +159 -0
  333. package/dist/voice-agent.js.map +1 -0
  334. package/dist/voice-attachments.e2e.test.d.ts +1 -0
  335. package/dist/voice-attachments.e2e.test.js +256 -0
  336. package/dist/voice-attachments.e2e.test.js.map +1 -0
  337. package/dist/voice-call.d.ts +59 -0
  338. package/dist/voice-call.e2e.test.d.ts +1 -0
  339. package/dist/voice-call.e2e.test.js +259 -0
  340. package/dist/voice-call.e2e.test.js.map +1 -0
  341. package/dist/voice-call.js +760 -0
  342. package/dist/voice-call.js.map +1 -0
  343. package/dist/voice-openai.e2e.test.d.ts +1 -0
  344. package/dist/voice-openai.e2e.test.js +161 -0
  345. package/dist/voice-openai.e2e.test.js.map +1 -0
  346. package/dist/voice.d.ts +116 -0
  347. package/dist/voice.js +619 -717
  348. package/dist/voice.js.map +1 -0
  349. package/dist/working-directory.e2e.test.d.ts +1 -0
  350. package/dist/working-directory.e2e.test.js +231 -0
  351. package/dist/working-directory.e2e.test.js.map +1 -0
  352. package/dist/worktrees.d.ts +54 -0
  353. package/dist/worktrees.js +247 -709
  354. package/dist/worktrees.js.map +1 -0
  355. package/dist/worktrees.test.d.ts +1 -0
  356. package/dist/worktrees.test.js +97 -952
  357. package/dist/worktrees.test.js.map +1 -0
  358. package/package.json +38 -73
  359. package/skills/kimaki-debug/SKILL.md +44 -0
  360. package/skills/kimaki-tunnel/SKILL.md +34 -0
  361. package/src/agent-ui.e2e.test.ts +134 -0
  362. package/src/agent-ui.ts +216 -0
  363. package/src/analytics.e2e.test.ts +138 -0
  364. package/src/analytics.test.ts +122 -182
  365. package/src/analytics.ts +206 -258
  366. package/src/autostart.ts +90 -0
  367. package/src/bot.ts +196 -0
  368. package/src/btw.e2e.test.ts +130 -0
  369. package/src/builtin-skills.test.ts +29 -0
  370. package/src/builtin-skills.ts +59 -0
  371. package/src/catalog-refresh.e2e.test.ts +112 -0
  372. package/src/cli/bot.ts +425 -0
  373. package/src/cli/discord.ts +116 -0
  374. package/src/cli/project.ts +147 -0
  375. package/src/cli/schedule.ts +151 -0
  376. package/src/cli/session.ts +252 -0
  377. package/src/cli/shared.ts +139 -0
  378. package/src/cli-discord.e2e.test.ts +79 -0
  379. package/src/cli-lock.e2e.test.ts +530 -0
  380. package/src/cli-read.e2e.test.ts +83 -0
  381. package/src/cli-sessions.e2e.test.ts +120 -0
  382. package/src/cli.ts +35 -418
  383. package/src/commands/login-commands.ts +360 -0
  384. package/src/commands/preference-commands.ts +599 -0
  385. package/src/commands/session-commands.ts +289 -0
  386. package/src/commands/thread-commands.ts +324 -0
  387. package/src/commands/worktree-commands.ts +288 -0
  388. package/src/commands.e2e.test.ts +290 -0
  389. package/src/credentials.ts +425 -0
  390. package/src/db.test.ts +344 -759
  391. package/src/db.ts +66 -284
  392. package/src/duration.ts +18 -0
  393. package/src/effects.ts +337 -0
  394. package/src/errors.ts +39 -228
  395. package/src/event-loop.ts +471 -0
  396. package/src/file-edit-log.test.ts +68 -272
  397. package/src/file-edit-log.ts +108 -231
  398. package/src/fixtures/v1-schema.sql +241 -0
  399. package/src/fixtures/voice.ogg +0 -0
  400. package/src/format-parts.test.ts +150 -0
  401. package/src/format-parts.ts +405 -0
  402. package/src/gateway-replay.e2e.test.ts +116 -0
  403. package/src/harness.e2e.test.ts +124 -0
  404. package/src/hello-thread.e2e.test.ts +223 -0
  405. package/src/ingress.ts +334 -0
  406. package/src/interaction-context.ts +131 -0
  407. package/src/interrupt.e2e.test.ts +151 -0
  408. package/src/legacy.e2e.test.ts +105 -0
  409. package/src/live-call.ts +249 -0
  410. package/src/lock-routes.ts +625 -0
  411. package/src/lock-server.ts +233 -0
  412. package/src/logger.ts +120 -207
  413. package/src/login.e2e.test.ts +86 -0
  414. package/src/main.ts +238 -0
  415. package/src/markdown/components.ts +102 -0
  416. package/src/markdown/markdown.test.ts +414 -0
  417. package/src/markdown/render-markdown.ts +288 -0
  418. package/src/migrations.ts +198 -0
  419. package/src/onboarding-cli.e2e.test.ts +215 -0
  420. package/src/onboarding.e2e.test.ts +171 -0
  421. package/src/onboarding.ts +297 -0
  422. package/src/opencode-server.test.ts +24 -0
  423. package/src/opencode-server.ts +464 -0
  424. package/src/permissions.e2e.test.ts +191 -0
  425. package/src/permissions.ts +121 -0
  426. package/src/plugin/anthropic/index.test.ts +39 -0
  427. package/src/plugin/anthropic/index.ts +213 -0
  428. package/src/plugin/index.ts +112 -0
  429. package/src/plugin.e2e.test.ts +117 -0
  430. package/src/preference-commands.e2e.test.ts +597 -0
  431. package/src/project.ts +216 -0
  432. package/src/prompt.ts +664 -0
  433. package/src/questions.e2e.test.ts +328 -0
  434. package/src/questions.ts +322 -0
  435. package/src/queue.e2e.test.ts +253 -0
  436. package/src/queue.ts +158 -0
  437. package/src/remote-send.e2e.test.ts +91 -0
  438. package/src/remote-send.ts +110 -0
  439. package/src/renderer.e2e.test.ts +390 -0
  440. package/src/restart.e2e.test.ts +90 -0
  441. package/src/routes.test.ts +81 -0
  442. package/src/routes.ts +48 -0
  443. package/src/routines.e2e.test.ts +210 -0
  444. package/src/scheduler.e2e.test.ts +182 -0
  445. package/src/scheduler.test.ts +183 -0
  446. package/src/scheduler.ts +648 -0
  447. package/src/schema-sql.ts +3 -0
  448. package/src/schema.ts +95 -271
  449. package/src/session-commands.e2e.test.ts +326 -0
  450. package/src/session-events.ts +301 -0
  451. package/src/session-ops.e2e.test.ts +141 -0
  452. package/src/sessions.ts +804 -0
  453. package/src/shell.e2e.test.ts +173 -0
  454. package/src/slash-commands.test.ts +89 -0
  455. package/src/slash-commands.ts +540 -0
  456. package/src/sleep.e2e.test.ts +135 -552
  457. package/src/sleeps.ts +230 -0
  458. package/src/store.ts +19 -217
  459. package/src/system-prompt.ts +923 -0
  460. package/src/test/auth-plugin/index.ts +17 -0
  461. package/src/test/fake-mcp-server.ts +49 -0
  462. package/src/test/harness.ts +957 -0
  463. package/src/test/replay.ts +90 -0
  464. package/src/thread-reducer.fixtures.test.ts +374 -0
  465. package/src/thread-reducer.test.ts +380 -0
  466. package/src/thread-reducer.ts +671 -0
  467. package/src/tts.test.ts +150 -0
  468. package/src/voice-agent.ts +196 -0
  469. package/src/voice-attachments.e2e.test.ts +301 -0
  470. package/src/voice-call.e2e.test.ts +293 -0
  471. package/src/voice-call.ts +764 -0
  472. package/src/voice-openai.e2e.test.ts +191 -0
  473. package/src/voice.ts +668 -988
  474. package/src/working-directory.e2e.test.ts +226 -0
  475. package/src/worktrees.test.ts +89 -1115
  476. package/src/worktrees.ts +195 -957
  477. package/bin.js +0 -2
  478. package/dist/agent-model.e2e.test.js +0 -1223
  479. package/dist/ai-tool-to-genai.js +0 -233
  480. package/dist/ai-tool-to-genai.test.js +0 -267
  481. package/dist/ai-tool.js +0 -6
  482. package/dist/anthropic-account-identity.js +0 -70
  483. package/dist/anthropic-account-identity.test.js +0 -38
  484. package/dist/anthropic-auth-plugin.js +0 -958
  485. package/dist/anthropic-auth-plugin.test.js +0 -115
  486. package/dist/anthropic-auth-state.js +0 -186
  487. package/dist/anthropic-auth-state.test.js +0 -203
  488. package/dist/bash-tool-schema-plugin.js +0 -50
  489. package/dist/bash-tool-schema-plugin.test.js +0 -68
  490. package/dist/bin.js +0 -151
  491. package/dist/bot-lifecycle.test.js +0 -206
  492. package/dist/btw-prefix-detection.js +0 -24
  493. package/dist/btw-prefix-detection.test.js +0 -130
  494. package/dist/cache-drift-plugin.js +0 -183
  495. package/dist/cache-rewrite.js +0 -39
  496. package/dist/cache-rewrite.test.js +0 -41
  497. package/dist/channel-management.js +0 -347
  498. package/dist/cli-commands/bot.js +0 -254
  499. package/dist/cli-commands/maintenance.js +0 -171
  500. package/dist/cli-commands/misc.js +0 -153
  501. package/dist/cli-commands/project.js +0 -510
  502. package/dist/cli-commands/send.js +0 -659
  503. package/dist/cli-commands/session.js +0 -889
  504. package/dist/cli-commands/task.js +0 -277
  505. package/dist/cli-commands/thread.js +0 -63
  506. package/dist/cli-commands/user.js +0 -145
  507. package/dist/cli-parsing.test.js +0 -226
  508. package/dist/cli-runner.js +0 -1591
  509. package/dist/cli-runner.test.js +0 -119
  510. package/dist/cli-send-thread.e2e.test.js +0 -470
  511. package/dist/cloudflare-transcription.js +0 -56
  512. package/dist/commands/abort.js +0 -84
  513. package/dist/commands/action-buttons.js +0 -248
  514. package/dist/commands/add-project.js +0 -111
  515. package/dist/commands/agent.js +0 -658
  516. package/dist/commands/ask-question.js +0 -371
  517. package/dist/commands/ask-question.test.js +0 -150
  518. package/dist/commands/btw.js +0 -275
  519. package/dist/commands/compact.js +0 -117
  520. package/dist/commands/context-usage.js +0 -194
  521. package/dist/commands/context-usage.test.js +0 -49
  522. package/dist/commands/create-new-project.js +0 -138
  523. package/dist/commands/diff.js +0 -63
  524. package/dist/commands/file-upload.js +0 -280
  525. package/dist/commands/fork-subagent.js +0 -175
  526. package/dist/commands/fork.js +0 -291
  527. package/dist/commands/gemini-apikey.js +0 -123
  528. package/dist/commands/last-sessions.js +0 -129
  529. package/dist/commands/login.js +0 -893
  530. package/dist/commands/mcp.js +0 -239
  531. package/dist/commands/memory-snapshot.js +0 -24
  532. package/dist/commands/mention-mode.js +0 -42
  533. package/dist/commands/merge-worktree.js +0 -205
  534. package/dist/commands/model-variant.js +0 -390
  535. package/dist/commands/model.js +0 -1012
  536. package/dist/commands/multioauth.js +0 -425
  537. package/dist/commands/new-worktree.js +0 -625
  538. package/dist/commands/paginated-select.js +0 -57
  539. package/dist/commands/permissions.js +0 -327
  540. package/dist/commands/queue.js +0 -347
  541. package/dist/commands/queue.test.js +0 -15
  542. package/dist/commands/remove-project.js +0 -111
  543. package/dist/commands/restart-opencode-server.js +0 -133
  544. package/dist/commands/resume.js +0 -148
  545. package/dist/commands/run-command.js +0 -79
  546. package/dist/commands/screenshare.js +0 -307
  547. package/dist/commands/screenshare.test.js +0 -20
  548. package/dist/commands/session-id.js +0 -78
  549. package/dist/commands/session.js +0 -248
  550. package/dist/commands/share.js +0 -80
  551. package/dist/commands/tasks.js +0 -296
  552. package/dist/commands/types.js +0 -2
  553. package/dist/commands/undo-redo.js +0 -313
  554. package/dist/commands/unset-model.js +0 -116
  555. package/dist/commands/upgrade.js +0 -41
  556. package/dist/commands/user-command.js +0 -196
  557. package/dist/commands/verbosity.js +0 -124
  558. package/dist/commands/vscode.js +0 -295
  559. package/dist/commands/worktrees.js +0 -548
  560. package/dist/condense-memory.js +0 -33
  561. package/dist/config.js +0 -126
  562. package/dist/context-awareness-plugin.js +0 -357
  563. package/dist/context-awareness-plugin.test.js +0 -126
  564. package/dist/cpu-profiler.js +0 -211
  565. package/dist/cpu-profiler.test.js +0 -160
  566. package/dist/critique-utils.js +0 -95
  567. package/dist/database.js +0 -1074
  568. package/dist/debounce-timeout.js +0 -28
  569. package/dist/debounced-process-flush.js +0 -77
  570. package/dist/default-channel-provisioning.test.js +0 -149
  571. package/dist/discord-bot.js +0 -1432
  572. package/dist/discord-command-registration.js +0 -519
  573. package/dist/discord-command-registration.test.js +0 -17
  574. package/dist/discord-urls.js +0 -81
  575. package/dist/discord-utils.js +0 -789
  576. package/dist/discord-utils.test.js +0 -400
  577. package/dist/escape-backticks.test.js +0 -429
  578. package/dist/event-stream-real-capture.e2e.test.js +0 -533
  579. package/dist/eventsource-parser.test.js +0 -327
  580. package/dist/exec-async.js +0 -38
  581. package/dist/external-directory-permissions.e2e.test.js +0 -86
  582. package/dist/external-directory-project-deny.e2e.test.js +0 -70
  583. package/dist/external-opencode-sync.js +0 -620
  584. package/dist/external-opencode-sync.test.js +0 -206
  585. package/dist/extract-sdk-error-message.test.js +0 -13
  586. package/dist/format-tables.js +0 -728
  587. package/dist/format-tables.test.js +0 -915
  588. package/dist/forum-sync/config.js +0 -79
  589. package/dist/forum-sync/discord-operations.js +0 -157
  590. package/dist/forum-sync/index.js +0 -5
  591. package/dist/forum-sync/markdown.js +0 -110
  592. package/dist/forum-sync/sync-to-discord.js +0 -417
  593. package/dist/forum-sync/sync-to-files.js +0 -190
  594. package/dist/forum-sync/types.js +0 -53
  595. package/dist/forum-sync/watchers.js +0 -322
  596. package/dist/gateway-proxy-reconnect.e2e.test.js +0 -414
  597. package/dist/gateway-proxy.e2e.test.js +0 -488
  598. package/dist/genai-worker-wrapper.js +0 -111
  599. package/dist/genai-worker.js +0 -309
  600. package/dist/genai.js +0 -232
  601. package/dist/git-worktree-core.js +0 -461
  602. package/dist/guild-categories.test.js +0 -225
  603. package/dist/heap-monitor.js +0 -122
  604. package/dist/hrana-server.js +0 -322
  605. package/dist/hrana-server.test.js +0 -99
  606. package/dist/html-actions.js +0 -123
  607. package/dist/html-actions.test.js +0 -70
  608. package/dist/html-components.js +0 -115
  609. package/dist/html-components.test.js +0 -34
  610. package/dist/image-optimizer-plugin.js +0 -153
  611. package/dist/image-utils.js +0 -112
  612. package/dist/interaction-handler.js +0 -578
  613. package/dist/ipc-polling.js +0 -245
  614. package/dist/ipc-tools-plugin.js +0 -251
  615. package/dist/kimaki-digital-twin.e2e.test.js +0 -161
  616. package/dist/kimaki-opencode-plugin-loading.e2e.test.js +0 -158
  617. package/dist/kimaki-opencode-plugin.js +0 -33
  618. package/dist/kimaki-opencode-plugin.test.js +0 -126
  619. package/dist/kimaki-workspace-adaptor.js +0 -118
  620. package/dist/limit-heading-depth.js +0 -25
  621. package/dist/limit-heading-depth.test.js +0 -105
  622. package/dist/list-channel-threads.js +0 -163
  623. package/dist/list-channel-threads.test.js +0 -80
  624. package/dist/markdown.js +0 -455
  625. package/dist/markdown.test.js +0 -411
  626. package/dist/memory-overview-plugin.js +0 -123
  627. package/dist/message-formatting.js +0 -788
  628. package/dist/message-formatting.test.js +0 -1132
  629. package/dist/message-preprocessing.js +0 -419
  630. package/dist/oauth-rotation-shared.js +0 -253
  631. package/dist/onboarding-tutorial.js +0 -163
  632. package/dist/onboarding-welcome.js +0 -37
  633. package/dist/openai-auth-plugin.js +0 -125
  634. package/dist/openai-auth-state.js +0 -237
  635. package/dist/openai-realtime.js +0 -224
  636. package/dist/opencode-command-detection.js +0 -65
  637. package/dist/opencode-command-detection.test.js +0 -240
  638. package/dist/opencode-command.js +0 -181
  639. package/dist/opencode-command.test.js +0 -218
  640. package/dist/opencode-interrupt-plugin.js +0 -253
  641. package/dist/opencode-interrupt-plugin.test.js +0 -293
  642. package/dist/opencode.js +0 -1217
  643. package/dist/parse-permission-rules.test.js +0 -117
  644. package/dist/patch-text-parser.js +0 -97
  645. package/dist/plugin-logger.js +0 -105
  646. package/dist/plugin-opencode-client.js +0 -54
  647. package/dist/privacy-sanitizer.js +0 -105
  648. package/dist/project-add.test.js +0 -62
  649. package/dist/question-abort-clears-dropdown.e2e.test.js +0 -89
  650. package/dist/question-abort-resume.e2e.test.js +0 -100
  651. package/dist/question-abort-retry-text-before-dropdown.e2e.test.js +0 -150
  652. package/dist/question-text-before-dropdown.e2e.test.js +0 -52
  653. package/dist/queue-advanced-abort.e2e.test.js +0 -348
  654. package/dist/queue-advanced-action-buttons.e2e.test.js +0 -209
  655. package/dist/queue-advanced-e2e-setup.js +0 -1043
  656. package/dist/queue-advanced-footer.e2e.test.js +0 -286
  657. package/dist/queue-advanced-model-switch.e2e.test.js +0 -297
  658. package/dist/queue-advanced-permissions-typing.e2e.test.js +0 -189
  659. package/dist/queue-advanced-question.e2e.test.js +0 -266
  660. package/dist/queue-advanced-typing-interrupt.e2e.test.js +0 -113
  661. package/dist/queue-advanced-typing.e2e.test.js +0 -151
  662. package/dist/queue-delete-message.e2e.test.js +0 -216
  663. package/dist/queue-drain-after-interactive-ui.e2e.test.js +0 -120
  664. package/dist/queue-interrupt-drain.e2e.test.js +0 -256
  665. package/dist/queue-question-select-drain.e2e.test.js +0 -162
  666. package/dist/runtime-idle-sweeper.js +0 -52
  667. package/dist/runtime-lifecycle.e2e.test.js +0 -570
  668. package/dist/sentry.js +0 -23
  669. package/dist/session-handler/agent-utils.js +0 -65
  670. package/dist/session-handler/event-stream-state.js +0 -1225
  671. package/dist/session-handler/event-stream-state.test.js +0 -3160
  672. package/dist/session-handler/global-event-listener.js +0 -212
  673. package/dist/session-handler/model-utils.js +0 -311
  674. package/dist/session-handler/model-utils.test.js +0 -445
  675. package/dist/session-handler/opencode-session-event-log.js +0 -121
  676. package/dist/session-handler/thread-ingress.test.js +0 -41
  677. package/dist/session-handler/thread-runtime-state.js +0 -209
  678. package/dist/session-handler/thread-session-runtime-summary.test.js +0 -121
  679. package/dist/session-handler/thread-session-runtime.js +0 -4320
  680. package/dist/session-handler.js +0 -9
  681. package/dist/session-search.js +0 -219
  682. package/dist/session-search.test.js +0 -287
  683. package/dist/session-sleep.test.js +0 -224
  684. package/dist/session-title-rename.test.js +0 -128
  685. package/dist/skill-filter.js +0 -43
  686. package/dist/skill-filter.test.js +0 -86
  687. package/dist/startup-service.js +0 -153
  688. package/dist/startup-time.e2e.test.js +0 -297
  689. package/dist/system-message.js +0 -806
  690. package/dist/system-message.test.js +0 -917
  691. package/dist/task-id-plugin.js +0 -16
  692. package/dist/task-runner.js +0 -554
  693. package/dist/task-schedule.js +0 -298
  694. package/dist/task-schedule.test.js +0 -366
  695. package/dist/test-utils.js +0 -385
  696. package/dist/thinking-utils.js +0 -45
  697. package/dist/thinking-utils.test.js +0 -35
  698. package/dist/thread-message-queue.e2e.test.js +0 -1227
  699. package/dist/tools.js +0 -357
  700. package/dist/undo-redo.e2e.test.js +0 -171
  701. package/dist/unnest-code-blocks.js +0 -146
  702. package/dist/unnest-code-blocks.test.js +0 -673
  703. package/dist/upgrade.js +0 -114
  704. package/dist/utils.js +0 -148
  705. package/dist/voice-attachment.js +0 -56
  706. package/dist/voice-handler.js +0 -727
  707. package/dist/voice-message.e2e.test.js +0 -1254
  708. package/dist/voice-routing-input.e2e.test.js +0 -195
  709. package/dist/voice-routing-question.e2e.test.js +0 -182
  710. package/dist/voice.test.js +0 -500
  711. package/dist/wait-session.js +0 -193
  712. package/dist/websockify.js +0 -69
  713. package/dist/worker-types.js +0 -4
  714. package/dist/worktree-clone-isolation.e2e.test.js +0 -188
  715. package/dist/worktree-lifecycle.e2e.test.js +0 -735
  716. package/dist/worktree-utils.js +0 -3
  717. package/dist/xai-auth-plugin.js +0 -238
  718. package/dist/xai-auth-state.js +0 -234
  719. package/dist/xai-auth-state.test.js +0 -183
  720. package/dist/xml.js +0 -92
  721. package/dist/xml.test.js +0 -32
  722. package/skills/bump-holocron-consumers/SKILL.md +0 -137
  723. package/skills/critique/SKILL.md +0 -116
  724. package/skills/egaki/SKILL.md +0 -86
  725. package/skills/errore/SKILL.md +0 -501
  726. package/skills/goke/SKILL.md +0 -695
  727. package/skills/holocron/SKILL.md +0 -942
  728. package/skills/lintcn/SKILL.md +0 -873
  729. package/skills/new-skill/SKILL.md +0 -339
  730. package/skills/npm-package/SKILL.md +0 -692
  731. package/skills/opensrc/SKILL.md +0 -102
  732. package/skills/playwriter/SKILL.md +0 -35
  733. package/skills/profano/SKILL.md +0 -33
  734. package/skills/readme.md +0 -20
  735. package/skills/sigillo/SKILL.md +0 -370
  736. package/skills/spiceflow/SKILL.md +0 -80
  737. package/skills/strada/SKILL.md +0 -104
  738. package/skills/termcast/SKILL.md +0 -133
  739. package/skills/tuistory/SKILL.md +0 -237
  740. package/skills/usecomputer/SKILL.md +0 -264
  741. package/skills/zele/SKILL.md +0 -81
  742. package/src/agent-model.e2e.test.ts +0 -1557
  743. package/src/ai-tool-to-genai.test.ts +0 -296
  744. package/src/ai-tool-to-genai.ts +0 -283
  745. package/src/ai-tool.ts +0 -39
  746. package/src/anthropic-account-identity.test.ts +0 -52
  747. package/src/anthropic-account-identity.ts +0 -96
  748. package/src/anthropic-auth-plugin.test.ts +0 -135
  749. package/src/anthropic-auth-plugin.ts +0 -1180
  750. package/src/anthropic-auth-state.test.ts +0 -259
  751. package/src/anthropic-auth-state.ts +0 -272
  752. package/src/bash-tool-schema-plugin.test.ts +0 -84
  753. package/src/bash-tool-schema-plugin.ts +0 -77
  754. package/src/bin.ts +0 -173
  755. package/src/bot-lifecycle.test.ts +0 -252
  756. package/src/btw-prefix-detection.test.ts +0 -142
  757. package/src/btw-prefix-detection.ts +0 -30
  758. package/src/cache-drift-plugin.ts +0 -249
  759. package/src/cache-rewrite.test.ts +0 -49
  760. package/src/cache-rewrite.ts +0 -96
  761. package/src/channel-management.ts +0 -494
  762. package/src/cli-commands/bot.ts +0 -374
  763. package/src/cli-commands/maintenance.ts +0 -237
  764. package/src/cli-commands/misc.ts +0 -208
  765. package/src/cli-commands/project.ts +0 -680
  766. package/src/cli-commands/send.ts +0 -885
  767. package/src/cli-commands/session.ts +0 -1181
  768. package/src/cli-commands/task.ts +0 -358
  769. package/src/cli-commands/thread.ts +0 -86
  770. package/src/cli-commands/user.ts +0 -195
  771. package/src/cli-parsing.test.ts +0 -305
  772. package/src/cli-runner.test.ts +0 -166
  773. package/src/cli-runner.ts +0 -2157
  774. package/src/cli-send-thread.e2e.test.ts +0 -609
  775. package/src/cloudflare-transcription.ts +0 -76
  776. package/src/commands/abort.ts +0 -113
  777. package/src/commands/action-buttons.ts +0 -371
  778. package/src/commands/add-project.ts +0 -146
  779. package/src/commands/agent.ts +0 -911
  780. package/src/commands/ask-question.test.ts +0 -177
  781. package/src/commands/ask-question.ts +0 -532
  782. package/src/commands/btw.ts +0 -356
  783. package/src/commands/compact.ts +0 -154
  784. package/src/commands/context-usage.test.ts +0 -57
  785. package/src/commands/context-usage.ts +0 -267
  786. package/src/commands/create-new-project.ts +0 -203
  787. package/src/commands/diff.ts +0 -91
  788. package/src/commands/file-upload.ts +0 -398
  789. package/src/commands/fork-subagent.ts +0 -262
  790. package/src/commands/fork.ts +0 -421
  791. package/src/commands/gemini-apikey.ts +0 -205
  792. package/src/commands/last-sessions.ts +0 -176
  793. package/src/commands/login.ts +0 -1182
  794. package/src/commands/mcp.ts +0 -307
  795. package/src/commands/memory-snapshot.ts +0 -30
  796. package/src/commands/mention-mode.ts +0 -66
  797. package/src/commands/merge-worktree.ts +0 -282
  798. package/src/commands/model-variant.ts +0 -522
  799. package/src/commands/model.ts +0 -1377
  800. package/src/commands/multioauth.ts +0 -508
  801. package/src/commands/new-worktree.ts +0 -835
  802. package/src/commands/paginated-select.ts +0 -81
  803. package/src/commands/permissions.ts +0 -458
  804. package/src/commands/queue.test.ts +0 -19
  805. package/src/commands/queue.ts +0 -471
  806. package/src/commands/remove-project.ts +0 -152
  807. package/src/commands/restart-opencode-server.ts +0 -173
  808. package/src/commands/resume.ts +0 -229
  809. package/src/commands/run-command.ts +0 -123
  810. package/src/commands/screenshare.test.ts +0 -30
  811. package/src/commands/screenshare.ts +0 -388
  812. package/src/commands/session-id.ts +0 -109
  813. package/src/commands/session.ts +0 -319
  814. package/src/commands/share.ts +0 -106
  815. package/src/commands/tasks.ts +0 -399
  816. package/src/commands/types.ts +0 -25
  817. package/src/commands/undo-redo.ts +0 -394
  818. package/src/commands/unset-model.ts +0 -152
  819. package/src/commands/upgrade.ts +0 -51
  820. package/src/commands/user-command.ts +0 -260
  821. package/src/commands/verbosity.ts +0 -172
  822. package/src/commands/vscode.ts +0 -378
  823. package/src/commands/worktrees.ts +0 -762
  824. package/src/condense-memory.ts +0 -36
  825. package/src/config.ts +0 -147
  826. package/src/context-awareness-plugin.test.ts +0 -144
  827. package/src/context-awareness-plugin.ts +0 -508
  828. package/src/cpu-profiler.test.ts +0 -183
  829. package/src/cpu-profiler.ts +0 -293
  830. package/src/critique-utils.ts +0 -139
  831. package/src/database.ts +0 -1484
  832. package/src/debounce-timeout.ts +0 -43
  833. package/src/debounced-process-flush.ts +0 -104
  834. package/src/default-channel-provisioning.test.ts +0 -156
  835. package/src/discord-bot.ts +0 -1852
  836. package/src/discord-command-registration.test.ts +0 -20
  837. package/src/discord-command-registration.ts +0 -704
  838. package/src/discord-urls.ts +0 -88
  839. package/src/discord-utils.test.ts +0 -470
  840. package/src/discord-utils.ts +0 -1079
  841. package/src/escape-backticks.test.ts +0 -469
  842. package/src/event-stream-real-capture.e2e.test.ts +0 -692
  843. package/src/eventsource-parser.test.ts +0 -351
  844. package/src/exec-async.ts +0 -44
  845. package/src/external-directory-permissions.e2e.test.ts +0 -103
  846. package/src/external-directory-project-deny.e2e.test.ts +0 -84
  847. package/src/external-opencode-sync.test.ts +0 -248
  848. package/src/external-opencode-sync.ts +0 -867
  849. package/src/extract-sdk-error-message.test.ts +0 -18
  850. package/src/format-tables.test.ts +0 -988
  851. package/src/format-tables.ts +0 -1031
  852. package/src/forum-sync/config.ts +0 -92
  853. package/src/forum-sync/discord-operations.ts +0 -245
  854. package/src/forum-sync/index.ts +0 -9
  855. package/src/forum-sync/markdown.ts +0 -172
  856. package/src/forum-sync/sync-to-discord.ts +0 -595
  857. package/src/forum-sync/sync-to-files.ts +0 -294
  858. package/src/forum-sync/types.ts +0 -175
  859. package/src/forum-sync/watchers.ts +0 -474
  860. package/src/gateway-proxy-reconnect.e2e.test.ts +0 -553
  861. package/src/gateway-proxy.e2e.test.ts +0 -648
  862. package/src/genai-worker-wrapper.ts +0 -164
  863. package/src/genai-worker.ts +0 -385
  864. package/src/genai.ts +0 -318
  865. package/src/git-worktree-core.ts +0 -616
  866. package/src/guild-categories.test.ts +0 -260
  867. package/src/heap-monitor.ts +0 -152
  868. package/src/hrana-server.test.ts +0 -130
  869. package/src/hrana-server.ts +0 -399
  870. package/src/html-actions.test.ts +0 -87
  871. package/src/html-actions.ts +0 -174
  872. package/src/html-components.test.ts +0 -38
  873. package/src/html-components.ts +0 -177
  874. package/src/image-optimizer-plugin.ts +0 -194
  875. package/src/image-utils.ts +0 -150
  876. package/src/interaction-handler.ts +0 -775
  877. package/src/ipc-polling.ts +0 -325
  878. package/src/ipc-tools-plugin.ts +0 -306
  879. package/src/kimaki-digital-twin.e2e.test.ts +0 -199
  880. package/src/kimaki-opencode-plugin-loading.e2e.test.ts +0 -208
  881. package/src/kimaki-opencode-plugin.test.ts +0 -142
  882. package/src/kimaki-opencode-plugin.ts +0 -34
  883. package/src/kimaki-workspace-adaptor.ts +0 -144
  884. package/src/limit-heading-depth.test.ts +0 -116
  885. package/src/limit-heading-depth.ts +0 -26
  886. package/src/list-channel-threads.test.ts +0 -90
  887. package/src/list-channel-threads.ts +0 -252
  888. package/src/markdown.test.ts +0 -498
  889. package/src/markdown.ts +0 -577
  890. package/src/memory-overview-plugin.ts +0 -159
  891. package/src/message-formatting.test.ts +0 -1401
  892. package/src/message-formatting.ts +0 -999
  893. package/src/message-preprocessing.ts +0 -557
  894. package/src/oauth-rotation-shared.ts +0 -332
  895. package/src/onboarding-tutorial.ts +0 -167
  896. package/src/onboarding-welcome.ts +0 -49
  897. package/src/openai-auth-plugin.ts +0 -158
  898. package/src/openai-auth-state.ts +0 -297
  899. package/src/openai-realtime.ts +0 -358
  900. package/src/opencode-command-detection.test.ts +0 -307
  901. package/src/opencode-command-detection.ts +0 -76
  902. package/src/opencode-command.test.ts +0 -290
  903. package/src/opencode-command.ts +0 -251
  904. package/src/opencode-interrupt-plugin.test.ts +0 -419
  905. package/src/opencode-interrupt-plugin.ts +0 -306
  906. package/src/opencode.ts +0 -1590
  907. package/src/parse-permission-rules.test.ts +0 -127
  908. package/src/patch-text-parser.ts +0 -107
  909. package/src/plugin-logger.ts +0 -139
  910. package/src/plugin-opencode-client.ts +0 -71
  911. package/src/privacy-sanitizer.ts +0 -142
  912. package/src/project-add.test.ts +0 -80
  913. package/src/question-abort-clears-dropdown.e2e.test.ts +0 -109
  914. package/src/question-abort-resume.e2e.test.ts +0 -121
  915. package/src/question-abort-retry-text-before-dropdown.e2e.test.ts +0 -172
  916. package/src/question-text-before-dropdown.e2e.test.ts +0 -67
  917. package/src/queue-advanced-abort.e2e.test.ts +0 -449
  918. package/src/queue-advanced-action-buttons.e2e.test.ts +0 -271
  919. package/src/queue-advanced-e2e-setup.ts +0 -1164
  920. package/src/queue-advanced-footer.e2e.test.ts +0 -357
  921. package/src/queue-advanced-model-switch.e2e.test.ts +0 -382
  922. package/src/queue-advanced-permissions-typing.e2e.test.ts +0 -256
  923. package/src/queue-advanced-question.e2e.test.ts +0 -322
  924. package/src/queue-advanced-typing-interrupt.e2e.test.ts +0 -147
  925. package/src/queue-advanced-typing.e2e.test.ts +0 -199
  926. package/src/queue-delete-message.e2e.test.ts +0 -262
  927. package/src/queue-drain-after-interactive-ui.e2e.test.ts +0 -152
  928. package/src/queue-interrupt-drain.e2e.test.ts +0 -299
  929. package/src/queue-question-select-drain.e2e.test.ts +0 -209
  930. package/src/runtime-idle-sweeper.ts +0 -76
  931. package/src/runtime-lifecycle.e2e.test.ts +0 -714
  932. package/src/schema.sql +0 -251
  933. package/src/sentry.ts +0 -26
  934. package/src/session-handler/agent-utils.ts +0 -104
  935. package/src/session-handler/event-stream-fixtures/real-session-action-buttons.jsonl +0 -45
  936. package/src/session-handler/event-stream-fixtures/real-session-footer-suppressed-on-pre-idle-interrupt.jsonl +0 -40
  937. package/src/session-handler/event-stream-fixtures/real-session-permission-external-file.jsonl +0 -23
  938. package/src/session-handler/event-stream-fixtures/real-session-task-normal.jsonl +0 -22
  939. package/src/session-handler/event-stream-fixtures/real-session-task-three-parallel-sleeps.jsonl +0 -277
  940. package/src/session-handler/event-stream-fixtures/real-session-task-user-interruption.jsonl +0 -46
  941. package/src/session-handler/event-stream-fixtures/session-abort-after-idle-race.jsonl +0 -21
  942. package/src/session-handler/event-stream-fixtures/session-concurrent-messages-serialized.jsonl +0 -56
  943. package/src/session-handler/event-stream-fixtures/session-explicit-abort.jsonl +0 -44
  944. package/src/session-handler/event-stream-fixtures/session-normal-completion.jsonl +0 -29
  945. package/src/session-handler/event-stream-fixtures/session-tool-call-noisy-stream.jsonl +0 -29
  946. package/src/session-handler/event-stream-fixtures/session-two-completions-same-session.jsonl +0 -50
  947. package/src/session-handler/event-stream-fixtures/session-user-interruption.jsonl +0 -59
  948. package/src/session-handler/event-stream-fixtures/session-voice-queued-followup.jsonl +0 -52
  949. package/src/session-handler/event-stream-state.test.ts +0 -3431
  950. package/src/session-handler/event-stream-state.ts +0 -1792
  951. package/src/session-handler/global-event-listener.ts +0 -258
  952. package/src/session-handler/model-utils.test.ts +0 -537
  953. package/src/session-handler/model-utils.ts +0 -489
  954. package/src/session-handler/opencode-session-event-log.ts +0 -159
  955. package/src/session-handler/thread-ingress.test.ts +0 -54
  956. package/src/session-handler/thread-runtime-state.ts +0 -358
  957. package/src/session-handler/thread-session-runtime-summary.test.ts +0 -169
  958. package/src/session-handler/thread-session-runtime.ts +0 -5696
  959. package/src/session-handler.ts +0 -15
  960. package/src/session-search.test.ts +0 -331
  961. package/src/session-search.ts +0 -382
  962. package/src/session-sleep.test.ts +0 -294
  963. package/src/session-title-rename.test.ts +0 -186
  964. package/src/skill-filter.test.ts +0 -110
  965. package/src/skill-filter.ts +0 -63
  966. package/src/startup-service.ts +0 -200
  967. package/src/startup-time.e2e.test.ts +0 -377
  968. package/src/system-message.test.ts +0 -1013
  969. package/src/system-message.ts +0 -1043
  970. package/src/task-id-plugin.ts +0 -17
  971. package/src/task-runner.ts +0 -768
  972. package/src/task-schedule.test.ts +0 -424
  973. package/src/task-schedule.ts +0 -470
  974. package/src/test-utils.ts +0 -549
  975. package/src/thinking-utils.test.ts +0 -46
  976. package/src/thinking-utils.ts +0 -82
  977. package/src/thread-message-queue.e2e.test.ts +0 -1468
  978. package/src/tools.ts +0 -430
  979. package/src/undici.d.ts +0 -12
  980. package/src/undo-redo.e2e.test.ts +0 -219
  981. package/src/unnest-code-blocks.test.ts +0 -713
  982. package/src/unnest-code-blocks.ts +0 -185
  983. package/src/upgrade.ts +0 -127
  984. package/src/utils.ts +0 -216
  985. package/src/voice-attachment.ts +0 -79
  986. package/src/voice-handler.ts +0 -1000
  987. package/src/voice-message.e2e.test.ts +0 -1513
  988. package/src/voice-routing-input.e2e.test.ts +0 -203
  989. package/src/voice-routing-question.e2e.test.ts +0 -188
  990. package/src/voice.test.ts +0 -565
  991. package/src/wait-session.ts +0 -291
  992. package/src/websockify.ts +0 -101
  993. package/src/worker-types.ts +0 -64
  994. package/src/worktree-clone-isolation.e2e.test.ts +0 -213
  995. package/src/worktree-lifecycle.e2e.test.ts +0 -900
  996. package/src/worktree-utils.ts +0 -4
  997. package/src/xai-auth-plugin.ts +0 -303
  998. package/src/xai-auth-state.test.ts +0 -224
  999. package/src/xai-auth-state.ts +0 -294
  1000. package/src/xml.test.ts +0 -38
  1001. package/src/xml.ts +0 -121
@@ -1,1254 +0,0 @@
1
- // E2e tests for voice message handling (audio attachment transcription).
2
- // Uses deterministic transcription (store.test.deterministicTranscription) to
3
- // bypass real AI model calls and control transcription output, timing, and
4
- // queueMessage flag. Combined with opencode-deterministic-provider for session
5
- // responses. Tests validate the full flow: attachment detection → transcription
6
- // → session dispatch, including interrupt, queue, and race condition scenarios.
7
- //
8
- // Tests assert on both Discord messages (via digital twin) and session state
9
- // transitions (via getThreadState from the zustand store).
10
- import fs from 'node:fs';
11
- import http from 'node:http';
12
- import path from 'node:path';
13
- import url from 'node:url';
14
- import { describe, beforeAll, afterAll, beforeEach, test, expect } from 'vitest';
15
- import { ChannelType, Client, GatewayIntentBits, Partials } from 'discord.js';
16
- import { DigitalDiscord } from 'discord-digital-twin/src';
17
- import { buildDeterministicOpencodeConfig, } from 'opencode-deterministic-provider';
18
- import { setDataDir } from './config.js';
19
- import { store } from './store.js';
20
- import { startDiscordBot } from './discord-bot.js';
21
- import { setBotToken, initDatabase, closeDatabase, setChannelDirectory, setChannelVerbosity, getThreadSession, createPendingWorkspace, setWorkspaceReady, getThreadWorktreeOrWorkspace, } from './database.js';
22
- import { startHranaServer, stopHranaServer } from './hrana-server.js';
23
- import { initializeOpencodeForDirectory, getOpencodeClient, stopOpencodeServer } from './opencode.js';
24
- import { chooseLockPort, cleanupTestSessions, initTestGitRepo, waitForFooterMessage, waitForBotMessageContaining, waitForBotReplyTo, waitForThreadState, } from './test-utils.js';
25
- import { getThreadState } from './session-handler/thread-runtime-state.js';
26
- const e2eTest = describe;
27
- // ── Helpers ──────────────────────────────────────────────────────
28
- function createRunDirectories() {
29
- const root = path.resolve(process.cwd(), 'tmp', 'voice-msg-e2e');
30
- fs.mkdirSync(root, { recursive: true });
31
- const dataDir = fs.mkdtempSync(path.join(root, 'data-'));
32
- const projectDirectory = path.join(root, 'project');
33
- fs.mkdirSync(projectDirectory, { recursive: true });
34
- initTestGitRepo(projectDirectory);
35
- return { root, dataDir, projectDirectory };
36
- }
37
- function createDiscordJsClient({ restUrl }) {
38
- return new Client({
39
- intents: [
40
- GatewayIntentBits.Guilds,
41
- GatewayIntentBits.GuildMessages,
42
- GatewayIntentBits.MessageContent,
43
- GatewayIntentBits.GuildVoiceStates,
44
- ],
45
- partials: [
46
- Partials.Channel,
47
- Partials.Message,
48
- Partials.User,
49
- Partials.ThreadMember,
50
- ],
51
- rest: {
52
- api: restUrl,
53
- version: '10',
54
- },
55
- });
56
- }
57
- /** Set the deterministic transcription config in the store for the next voice message. */
58
- function setDeterministicTranscription(config) {
59
- store.setState({
60
- test: { deterministicTranscription: config },
61
- });
62
- }
63
- function getOpencodeClientForTest(projectDirectory) {
64
- const client = getOpencodeClient(projectDirectory);
65
- if (!client) {
66
- throw new Error('OpenCode client not found for project directory');
67
- }
68
- return client;
69
- }
70
- /** Extract text content from an array of parts (filters to TextPart only). */
71
- function getTextFromParts(parts) {
72
- return parts.flatMap((part) => {
73
- if (part.type === 'text') {
74
- return [part.text];
75
- }
76
- return [];
77
- });
78
- }
79
- /** Get all user-role messages' text parts joined. */
80
- function getUserTexts(messages) {
81
- return messages
82
- .filter((m) => m.info.role === 'user')
83
- .flatMap((m) => getTextFromParts(m.parts));
84
- }
85
- /** Get all assistant-role messages' text parts joined. */
86
- function getAssistantTexts(messages) {
87
- return messages
88
- .filter((m) => m.info.role === 'assistant')
89
- .flatMap((m) => getTextFromParts(m.parts));
90
- }
91
- /**
92
- * Poll session.messages() until predicate returns true.
93
- * Used to wait for async session updates (prompts dispatched, responses completed).
94
- */
95
- async function waitForSessionMessages({ projectDirectory, sessionID, timeout, predicate, description, }) {
96
- const client = getOpencodeClientForTest(projectDirectory);
97
- const start = Date.now();
98
- while (Date.now() - start < timeout) {
99
- const result = await client.session.messages({
100
- sessionID,
101
- directory: projectDirectory,
102
- });
103
- const messages = result.data ?? [];
104
- if (predicate(messages)) {
105
- return messages;
106
- }
107
- await new Promise((resolve) => {
108
- setTimeout(resolve, 100);
109
- });
110
- }
111
- // Final attempt for error reporting
112
- const finalResult = await client.session.messages({
113
- sessionID,
114
- directory: projectDirectory,
115
- });
116
- const finalMessages = finalResult.data ?? [];
117
- const userTexts = getUserTexts(finalMessages);
118
- const assistantTexts = getAssistantTexts(finalMessages);
119
- throw new Error(`Timed out waiting for session messages (${description}). ` +
120
- `User texts: ${JSON.stringify(userTexts.map((t) => t.slice(0, 80)))}. ` +
121
- `Assistant texts: ${JSON.stringify(assistantTexts.map((t) => t.slice(0, 80)))}`);
122
- }
123
- // ── Deterministic provider matchers ──────────────────────────────
124
- // The opencode session uses these to produce canned responses.
125
- function createDeterministicMatchers() {
126
- // Slow response: emits text-delta after 2s delay, giving voice messages
127
- // time to arrive while the session is still "running".
128
- // Uses latestUserTextIncludes (not rawPromptIncludes) so it only matches
129
- // the current user message, not previous messages in session history.
130
- const slowResponse = {
131
- id: 'slow-response',
132
- priority: 100,
133
- when: {
134
- latestUserTextIncludes: 'SLOW_RESPONSE_MARKER',
135
- },
136
- then: {
137
- parts: [
138
- { type: 'stream-start', warnings: [] },
139
- { type: 'text-start', id: 'slow' },
140
- { type: 'text-delta', id: 'slow', delta: 'slow-response-done' },
141
- { type: 'text-end', id: 'slow' },
142
- {
143
- type: 'finish',
144
- finishReason: 'stop',
145
- usage: { inputTokens: 1, outputTokens: 1, totalTokens: 2 },
146
- },
147
- ],
148
- // 2s delay on the first text delta — keeps the session in "running" state
149
- partDelaysMs: [0, 0, 2000, 0, 0],
150
- },
151
- };
152
- // Fast response: completes almost immediately (~100ms)
153
- const fastResponse = {
154
- id: 'fast-response',
155
- priority: 90,
156
- when: {
157
- latestUserTextIncludes: 'FAST_RESPONSE_MARKER',
158
- },
159
- then: {
160
- parts: [
161
- { type: 'stream-start', warnings: [] },
162
- { type: 'text-start', id: 'fast' },
163
- { type: 'text-delta', id: 'fast', delta: 'fast-response-done' },
164
- { type: 'text-end', id: 'fast' },
165
- {
166
- type: 'finish',
167
- finishReason: 'stop',
168
- usage: { inputTokens: 1, outputTokens: 1, totalTokens: 2 },
169
- },
170
- ],
171
- partDelaysMs: [0, 100, 0, 0, 0],
172
- },
173
- };
174
- // Default: matches any user message (fallback)
175
- const defaultReply = {
176
- id: 'default-reply',
177
- priority: 1,
178
- when: {
179
- lastMessageRole: 'user',
180
- },
181
- then: {
182
- parts: [
183
- { type: 'stream-start', warnings: [] },
184
- { type: 'text-start', id: 'default' },
185
- { type: 'text-delta', id: 'default', delta: 'session-reply' },
186
- { type: 'text-end', id: 'default' },
187
- {
188
- type: 'finish',
189
- finishReason: 'stop',
190
- usage: { inputTokens: 1, outputTokens: 1, totalTokens: 2 },
191
- },
192
- ],
193
- partDelaysMs: [0, 100, 0, 0, 0],
194
- },
195
- };
196
- // Tool followup: when the last message is a tool result
197
- const toolFollowup = {
198
- id: 'tool-followup',
199
- priority: 50,
200
- when: {
201
- lastMessageRole: 'tool',
202
- },
203
- then: {
204
- parts: [
205
- { type: 'stream-start', warnings: [] },
206
- { type: 'text-start', id: 'tool-followup' },
207
- { type: 'text-delta', id: 'tool-followup', delta: 'tool done' },
208
- { type: 'text-end', id: 'tool-followup' },
209
- {
210
- type: 'finish',
211
- finishReason: 'stop',
212
- usage: { inputTokens: 1, outputTokens: 1, totalTokens: 2 },
213
- },
214
- ],
215
- },
216
- };
217
- return [slowResponse, fastResponse, toolFollowup, defaultReply];
218
- }
219
- // ── Test constants ───────────────────────────────────────────────
220
- const TEST_USER_ID = '300000000000000777';
221
- const TEXT_CHANNEL_ID = '300000000000000778';
222
- // ── Test suite ───────────────────────────────────────────────────
223
- e2eTest('voice message handling', () => {
224
- let directories;
225
- let discord;
226
- let botClient;
227
- let previousDefaultVerbosity = store.getState().defaultVerbosity;
228
- let testStartTime = Date.now();
229
- beforeAll(async () => {
230
- testStartTime = Date.now();
231
- directories = createRunDirectories();
232
- const lockPort = chooseLockPort({ key: TEXT_CHANNEL_ID });
233
- process.env['KIMAKI_LOCK_PORT'] = String(lockPort);
234
- setDataDir(directories.dataDir);
235
- previousDefaultVerbosity = store.getState().defaultVerbosity;
236
- store.setState({ defaultVerbosity: 'tools_and_text' });
237
- const digitalDiscordDbPath = path.join(directories.dataDir, 'digital-discord.db');
238
- discord = new DigitalDiscord({
239
- guild: {
240
- name: 'Voice E2E Guild',
241
- ownerId: TEST_USER_ID,
242
- },
243
- channels: [
244
- {
245
- id: TEXT_CHANNEL_ID,
246
- name: 'voice-e2e',
247
- type: ChannelType.GuildText,
248
- },
249
- ],
250
- users: [
251
- {
252
- id: TEST_USER_ID,
253
- username: 'voice-tester',
254
- },
255
- ],
256
- dbUrl: `file:${digitalDiscordDbPath}`,
257
- });
258
- await discord.start();
259
- const providerNpm = url
260
- .pathToFileURL(path.resolve(process.cwd(), '..', 'opencode-deterministic-provider', 'src', 'index.ts'))
261
- .toString();
262
- const opencodeConfig = buildDeterministicOpencodeConfig({
263
- providerName: 'deterministic-provider',
264
- providerNpm,
265
- model: 'deterministic-v2',
266
- smallModel: 'deterministic-v2',
267
- settings: {
268
- strict: false,
269
- matchers: createDeterministicMatchers(),
270
- },
271
- });
272
- fs.writeFileSync(path.join(directories.projectDirectory, 'opencode.json'), JSON.stringify(opencodeConfig, null, 2));
273
- const dbPath = path.join(directories.dataDir, 'discord-sessions.db');
274
- const hranaResult = await startHranaServer({ dbPath });
275
- if (hranaResult instanceof Error) {
276
- throw hranaResult;
277
- }
278
- process.env['KIMAKI_DB_URL'] = hranaResult;
279
- await initDatabase();
280
- await setBotToken(discord.botUserId, discord.botToken);
281
- await setChannelDirectory({
282
- channelId: TEXT_CHANNEL_ID,
283
- directory: directories.projectDirectory,
284
- channelType: 'text',
285
- });
286
- await setChannelVerbosity(TEXT_CHANNEL_ID, 'tools_and_text');
287
- botClient = createDiscordJsClient({ restUrl: discord.restUrl });
288
- await startDiscordBot({
289
- token: discord.botToken,
290
- appId: discord.botUserId,
291
- discordClient: botClient,
292
- });
293
- // Pre-warm the opencode server
294
- const warmup = await initializeOpencodeForDirectory(directories.projectDirectory);
295
- if (warmup instanceof Error) {
296
- throw warmup;
297
- }
298
- }, 20_000);
299
- afterAll(async () => {
300
- // Reset deterministic transcription
301
- setDeterministicTranscription(null);
302
- if (directories) {
303
- await cleanupTestSessions({
304
- projectDirectory: directories.projectDirectory,
305
- testStartTime,
306
- });
307
- }
308
- if (botClient) {
309
- void botClient.destroy();
310
- }
311
- await stopOpencodeServer();
312
- await Promise.all([
313
- closeDatabase().catch(() => {
314
- return;
315
- }),
316
- stopHranaServer().catch(() => {
317
- return;
318
- }),
319
- discord?.stop().catch(() => {
320
- return;
321
- }),
322
- ]);
323
- delete process.env['KIMAKI_LOCK_PORT'];
324
- delete process.env['KIMAKI_DB_URL'];
325
- store.setState({ defaultVerbosity: previousDefaultVerbosity });
326
- if (directories) {
327
- fs.rmSync(directories.dataDir, { recursive: true, force: true });
328
- }
329
- }, 5_000);
330
- beforeEach(() => {
331
- // Reset deterministic transcription before each test to prevent leakage
332
- // from a failed test that set it but didn't clean up
333
- setDeterministicTranscription(null);
334
- });
335
- // ── Test 1: Voice message in a channel creates thread + session ──
336
- // Vitest disallows inline snapshots in test.each; register separate tests instead.
337
- for (const sessionAction of ['btw', 'new-session']) {
338
- test(`voice ${sessionAction} routing creates a separate thread with the correct history`, async () => {
339
- await discord.channel(TEXT_CHANNEL_ID).user(TEST_USER_ID)
340
- .sendMessage({ content: `Source history for voice ${sessionAction}` });
341
- const thread = await discord.channel(TEXT_CHANNEL_ID).waitForThread({
342
- timeout: 4_000,
343
- predicate: (candidate) => Boolean(candidate.name?.includes(`Source history for voice ${sessionAction}`)),
344
- });
345
- await waitForFooterMessage({ discord, threadId: thread.id, timeout: 4_000 });
346
- const sourceSessionId = await getThreadSession(thread.id);
347
- await createPendingWorkspace({
348
- threadId: thread.id,
349
- workspaceType: 'worktree',
350
- workspaceName: 'voice-workspace',
351
- projectDirectory: directories.projectDirectory,
352
- });
353
- await setWorkspaceReady({
354
- threadId: thread.id,
355
- workspaceId: 'voice-workspace-id',
356
- workspaceDirectory: directories.projectDirectory,
357
- });
358
- const prompt = `Explain voice routing ${sessionAction}`;
359
- setDeterministicTranscription({ transcription: prompt, queueMessage: false, sessionAction });
360
- await discord.thread(thread.id).user(TEST_USER_ID).sendVoiceMessage();
361
- const confirmation = sessionAction === 'btw' ? 'Session forked!' : 'Created new session in';
362
- const routingMessages = await waitForBotMessageContaining({
363
- discord,
364
- threadId: thread.id,
365
- text: confirmation,
366
- timeout: 4_000,
367
- });
368
- // OpenCode can rename the destination before discovery; follow its confirmed ID.
369
- const targetId = routingMessages.find((message) => message.content.includes(confirmation))
370
- ?.content.match(/<#(\d+)>/)?.[1];
371
- if (!targetId)
372
- throw new Error('Expected destination thread link');
373
- const target = await discord.channel(TEXT_CHANNEL_ID).waitForThread({
374
- timeout: 4_000,
375
- predicate: (candidate) => candidate.id === targetId,
376
- });
377
- await waitForFooterMessage({ discord, threadId: target.id, timeout: 4_000 });
378
- const th = discord.thread(target.id);
379
- const transcript = {
380
- source: (await discord.thread(thread.id).text()).replaceAll(target.id, 'TARGET_THREAD'),
381
- target: (await th.text()).replaceAll(thread.id, 'SOURCE_THREAD'),
382
- };
383
- if (sessionAction === 'btw') {
384
- expect(transcript).toMatchInlineSnapshot(`
385
- {
386
- "source": "--- from: user (voice-tester)
387
- Source history for voice btw
388
- --- from: assistant (TestBot)
389
- -# *using deterministic-provider/deterministic-v2*
390
- session-reply
391
- -# *project ⋅ main ⋅ Ns ⋅ N% ⋅ deterministic-v2*
392
- --- from: user (voice-tester)
393
- [attachment: voice-message.ogg]
394
- --- from: assistant (TestBot)
395
- 🎤 Transcribing voice message...
396
- 📝 **Transcribed message:** Explain voice routing btw
397
- Session forked! Continue in <#TARGET_THREAD>",
398
- "target": "--- from: assistant (TestBot)
399
- Reusing context from <#SOURCE_THREAD> to answer prompt...
400
- Explain voice routing btw
401
- session-reply
402
- -# *project ⋅ main ⋅ Ns ⋅ N% ⋅ deterministic-v2*",
403
- }
404
- `);
405
- }
406
- else {
407
- expect(transcript).toMatchInlineSnapshot(`
408
- {
409
- "source": "--- from: user (voice-tester)
410
- Source history for voice new-session
411
- --- from: assistant (TestBot)
412
- -# *using deterministic-provider/deterministic-v2*
413
- session-reply
414
- -# *project ⋅ main ⋅ Ns ⋅ N% ⋅ deterministic-v2*
415
- --- from: user (voice-tester)
416
- [attachment: voice-message.ogg]
417
- --- from: assistant (TestBot)
418
- 🎤 Transcribing voice message...
419
- 📝 **Transcribed message:** Explain voice routing new-session
420
- Created new session in <#TARGET_THREAD>",
421
- "target": "--- from: assistant (TestBot)
422
- **Starting OpenCode session**
423
- Explain voice routing new-session
424
- -# *using deterministic-provider/deterministic-v2*
425
- session-reply
426
- -# *project ⋅ main ⋅ Ns ⋅ N% ⋅ deterministic-v2*",
427
- }
428
- `);
429
- }
430
- const targetSessionId = await getThreadSession(target.id);
431
- expect(targetSessionId).toBeTruthy();
432
- expect(targetSessionId).not.toBe(sourceSessionId);
433
- expect(await getThreadWorktreeOrWorkspace(target.id)).toMatchObject({
434
- status: 'ready',
435
- workspace_type: 'worktree',
436
- workspace_name: 'voice-workspace',
437
- workspace_id: 'voice-workspace-id',
438
- workspace_directory: directories.projectDirectory,
439
- });
440
- const client = getOpencodeClientForTest(directories.projectDirectory);
441
- const targetMessages = await client.session.messages({ sessionID: targetSessionId });
442
- const targetTexts = getUserTexts(targetMessages.data ?? []).join('\n');
443
- expect(targetTexts).toContain(prompt);
444
- expect(targetTexts).toContain(`Voice message transcription from Discord user:\n${prompt}`);
445
- expect(targetTexts.includes(`Source history for voice ${sessionAction}`)).toBe(sessionAction === 'btw');
446
- const sourceMessages = await client.session.messages({ sessionID: sourceSessionId });
447
- expect(getUserTexts(sourceMessages.data ?? []).join('\n')).not.toContain(prompt);
448
- });
449
- }
450
- test('voice message in channel creates thread and starts session', async () => {
451
- setDeterministicTranscription({
452
- transcription: 'Fix the login bug in auth.ts',
453
- queueMessage: false,
454
- });
455
- // Send voice message in the text channel
456
- await discord
457
- .channel(TEXT_CHANNEL_ID)
458
- .user(TEST_USER_ID)
459
- .sendVoiceMessage();
460
- // Thread should be created and renamed to the transcription text
461
- const thread = await discord.channel(TEXT_CHANNEL_ID).waitForThread({
462
- timeout: 4_000,
463
- predicate: (t) => {
464
- return t.name?.includes('Fix the login bug') ?? false;
465
- },
466
- });
467
- expect(thread).toBeDefined();
468
- const th = discord.thread(thread.id);
469
- // Bot should post "Transcribing..." then "Transcribed message: ..."
470
- await waitForBotMessageContaining({
471
- discord,
472
- threadId: thread.id,
473
- userId: TEST_USER_ID,
474
- text: 'Transcribing voice message',
475
- timeout: 4_000,
476
- });
477
- await waitForBotMessageContaining({
478
- discord,
479
- threadId: thread.id,
480
- userId: TEST_USER_ID,
481
- text: 'Fix the login bug in auth.ts',
482
- timeout: 4_000,
483
- });
484
- // Session should get the transcribed prompt and respond
485
- const sessionReply = await th.waitForBotReply({ timeout: 4_000 });
486
- expect(sessionReply).toBeDefined();
487
- await waitForFooterMessage({
488
- discord,
489
- threadId: thread.id,
490
- timeout: 4_000,
491
- });
492
- // Assert thread state has a session and no queued messages after footer.
493
- const finalState = await waitForThreadState({
494
- threadId: thread.id,
495
- predicate: (state) => {
496
- return Boolean(state.sessionId) && state.queueItems.length === 0;
497
- },
498
- timeout: 4_000,
499
- description: 'voice turn settled with empty queue',
500
- });
501
- await waitForFooterMessage({
502
- discord,
503
- threadId: thread.id,
504
- timeout: 4_000,
505
- });
506
- expect(await th.text()).toMatchInlineSnapshot(`
507
- "--- from: user (voice-tester)
508
- [attachment: voice-message.ogg]
509
- --- from: assistant (TestBot)
510
- 🎤 Transcribing voice message...
511
- 📝 **Transcribed message:** Fix the login bug in auth.ts
512
- -# *using deterministic-provider/deterministic-v2*
513
- session-reply
514
- -# *project ⋅ main ⋅ Ns ⋅ N% ⋅ deterministic-v2*"
515
- `);
516
- expect(finalState.sessionId).toBeDefined();
517
- // Verify OpenCode session received the transcribed voice message as a prompt
518
- const messages = await waitForSessionMessages({
519
- projectDirectory: directories.projectDirectory,
520
- sessionID: finalState.sessionId,
521
- timeout: 4_000,
522
- description: 'voice transcription prompt sent to session',
523
- predicate: (all) => {
524
- const userTexts = getUserTexts(all);
525
- return userTexts.some((text) => text.includes('Fix the login bug in auth.ts'));
526
- },
527
- });
528
- const userTexts = getUserTexts(messages);
529
- expect(userTexts.some((t) => t.includes('Fix the login bug in auth.ts'))).toBe(true);
530
- // Session should have at least one assistant response
531
- const assistantTexts = getAssistantTexts(messages);
532
- expect(assistantTexts.length).toBeGreaterThan(0);
533
- }, 8_000);
534
- test('first voice message resumes after the user saves an API key', async () => {
535
- setDeterministicTranscription({
536
- transcription: 'Resume the original voice note',
537
- queueMessage: false,
538
- requireApiKey: true,
539
- });
540
- await discord.channel(TEXT_CHANNEL_ID).user(TEST_USER_ID).sendVoiceMessage();
541
- const thread = await discord.channel(TEXT_CHANNEL_ID).waitForThread({
542
- timeout: 4_000,
543
- predicate: (candidate) => candidate.name === 'Voice Message',
544
- });
545
- const th = discord.thread(thread.id);
546
- const messages = await waitForBotMessageContaining({
547
- discord,
548
- threadId: thread.id,
549
- userId: TEST_USER_ID,
550
- text: 'Voice transcription requires an API key',
551
- timeout: 4_000,
552
- });
553
- const keyMessage = messages.find((message) => {
554
- return message.content.includes('Voice transcription requires an API key');
555
- });
556
- if (!keyMessage)
557
- throw new Error('Expected API key prompt message');
558
- const buttonData = JSON.stringify(keyMessage.components);
559
- const buttonCustomId = buttonData.match(/"custom_id":"(transcription_apikey:[^"]+)"/)?.[1];
560
- if (!buttonCustomId)
561
- throw new Error('Expected API key button custom id');
562
- const buttonInteraction = await th.user(TEST_USER_ID).clickButton({
563
- messageId: keyMessage.id,
564
- customId: buttonCustomId,
565
- });
566
- await th.waitForInteractionAck({
567
- interactionId: buttonInteraction.id,
568
- timeout: 4_000,
569
- });
570
- const modalResponse = await th.getInteractionResponse(buttonInteraction.id);
571
- const modalCustomId = modalResponse?.data?.match(/"custom_id":"(transcription_apikey_modal:[^"]+)"/)?.[1];
572
- if (!modalCustomId)
573
- throw new Error('Expected API key modal custom id');
574
- const modalInteraction = await th.user(TEST_USER_ID).submitModal({
575
- customId: modalCustomId,
576
- fields: [{ customId: 'apikey', value: 'AIza-test-key' }],
577
- });
578
- await th.waitForInteractionAck({
579
- interactionId: modalInteraction.id,
580
- timeout: 4_000,
581
- });
582
- await waitForBotMessageContaining({
583
- discord,
584
- threadId: thread.id,
585
- userId: TEST_USER_ID,
586
- text: 'Resume the original voice note',
587
- timeout: 4_000,
588
- });
589
- await waitForFooterMessage({
590
- discord,
591
- threadId: thread.id,
592
- timeout: 4_000,
593
- });
594
- expect(await th.text()).toMatchInlineSnapshot(`
595
- "--- from: user (voice-tester)
596
- [attachment: voice-message.ogg]
597
- --- from: assistant (TestBot)
598
- 🎤 Transcribing voice message...
599
- Voice transcription requires an API key (OpenAI or Gemini). Set one to enable voice message transcription.
600
- Gemini API key saved. Retrying the original voice message.
601
- 📝 **Transcribed message:** Resume the original voice note
602
- -# *using deterministic-provider/deterministic-v2*
603
- session-reply
604
- -# *project ⋅ main ⋅ Ns ⋅ N% ⋅ deterministic-v2*"
605
- `);
606
- const finalState = getThreadState(thread.id);
607
- expect(finalState?.sessionId).toBeDefined();
608
- }, 10_000);
609
- test('voice attachment without content type still transcribes and avoids empty prompt dispatch', async () => {
610
- setDeterministicTranscription({
611
- transcription: 'Investigate the missing content type path',
612
- queueMessage: false,
613
- });
614
- await discord.channel(TEXT_CHANNEL_ID).user(TEST_USER_ID).sendMessage({
615
- content: '',
616
- attachments: [
617
- {
618
- id: 'voice-no-content-type',
619
- filename: 'voice-message.ogg',
620
- size: 1024,
621
- url: 'https://fake-cdn.discord.test/voice-no-content-type.ogg',
622
- proxy_url: 'https://fake-cdn.discord.test/voice-no-content-type.ogg',
623
- },
624
- ],
625
- });
626
- const thread = await discord.channel(TEXT_CHANNEL_ID).waitForThread({
627
- timeout: 4_000,
628
- predicate: (t) => {
629
- return t.name?.includes('Investigate the missing content type path') ?? false;
630
- },
631
- });
632
- const th = discord.thread(thread.id);
633
- await waitForBotMessageContaining({
634
- discord,
635
- threadId: thread.id,
636
- userId: TEST_USER_ID,
637
- text: 'Transcribing voice message',
638
- timeout: 4_000,
639
- });
640
- await waitForBotMessageContaining({
641
- discord,
642
- threadId: thread.id,
643
- userId: TEST_USER_ID,
644
- text: 'Investigate the missing content type path',
645
- timeout: 4_000,
646
- });
647
- await waitForFooterMessage({
648
- discord,
649
- threadId: thread.id,
650
- timeout: 4_000,
651
- });
652
- const finalState = await waitForThreadState({
653
- threadId: thread.id,
654
- predicate: (state) => {
655
- return Boolean(state.sessionId) && state.queueItems.length === 0;
656
- },
657
- timeout: 4_000,
658
- description: 'voice attachment without content type settled',
659
- });
660
- expect(await th.text()).toMatchInlineSnapshot(`
661
- "--- from: user (voice-tester)
662
- [attachment: voice-message.ogg]
663
- --- from: assistant (TestBot)
664
- 🎤 Transcribing voice message...
665
- 📝 **Transcribed message:** Investigate the missing content type path
666
- -# *using deterministic-provider/deterministic-v2*
667
- session-reply
668
- -# *project ⋅ main ⋅ Ns ⋅ N% ⋅ deterministic-v2*"
669
- `);
670
- const messages = await waitForSessionMessages({
671
- projectDirectory: directories.projectDirectory,
672
- sessionID: finalState.sessionId,
673
- timeout: 4_000,
674
- description: 'voice attachment without content type dispatched once',
675
- predicate: (all) => {
676
- const userTexts = getUserTexts(all);
677
- return userTexts.some((text) => {
678
- return text.includes('Investigate the missing content type path');
679
- });
680
- },
681
- });
682
- const userTexts = getUserTexts(messages);
683
- expect(userTexts).not.toContain('');
684
- expect(userTexts.some((text) => {
685
- return text.includes('Investigate the missing content type path');
686
- })).toBe(true);
687
- }, 8_000);
688
- // ── Test 2: Voice message in thread with idle session ──
689
- test('voice message in thread with idle session starts new request', async () => {
690
- // 1. Create a session with a text message first
691
- setDeterministicTranscription(null); // text message, no transcription
692
- await discord.channel(TEXT_CHANNEL_ID).user(TEST_USER_ID).sendMessage({
693
- content: 'FAST_RESPONSE_MARKER initial setup',
694
- });
695
- const thread = await discord.channel(TEXT_CHANNEL_ID).waitForThread({
696
- timeout: 4_000,
697
- predicate: (t) => {
698
- return t.name?.includes('FAST_RESPONSE_MARKER') ?? false;
699
- },
700
- });
701
- const th = discord.thread(thread.id);
702
- // Wait for the initial setup turn to fully complete before sending voice.
703
- await waitForBotMessageContaining({
704
- discord,
705
- threadId: thread.id,
706
- userId: TEST_USER_ID,
707
- text: 'fast-response-done',
708
- timeout: 4_000,
709
- });
710
- await waitForFooterMessage({
711
- discord,
712
- threadId: thread.id,
713
- timeout: 4_000,
714
- });
715
- // 2. Now send a voice message to the idle session
716
- setDeterministicTranscription({
717
- transcription: 'Add error handling to the parser',
718
- queueMessage: false,
719
- });
720
- await th.user(TEST_USER_ID).sendVoiceMessage();
721
- // Bot should post transcription messages
722
- await waitForBotMessageContaining({
723
- discord,
724
- threadId: thread.id,
725
- userId: TEST_USER_ID,
726
- text: 'Transcribing voice message',
727
- timeout: 4_000,
728
- });
729
- await waitForBotMessageContaining({
730
- discord,
731
- threadId: thread.id,
732
- userId: TEST_USER_ID,
733
- text: 'Add error handling to the parser',
734
- timeout: 4_000,
735
- });
736
- await waitForBotMessageContaining({
737
- discord,
738
- threadId: thread.id,
739
- userId: TEST_USER_ID,
740
- text: 'session-reply',
741
- timeout: 4_000,
742
- });
743
- await waitForFooterMessage({
744
- discord,
745
- threadId: thread.id,
746
- timeout: 4_000,
747
- afterMessageIncludes: 'session-reply',
748
- afterAuthorId: discord.botUserId,
749
- });
750
- const finalState = getThreadState(thread.id);
751
- expect(await th.text()).toMatchInlineSnapshot(`
752
- "--- from: user (voice-tester)
753
- FAST_RESPONSE_MARKER initial setup
754
- --- from: assistant (TestBot)
755
- -# *using deterministic-provider/deterministic-v2*
756
- fast-response-done
757
- -# *project ⋅ main ⋅ Ns ⋅ N% ⋅ deterministic-v2*
758
- --- from: user (voice-tester)
759
- [attachment: voice-message.ogg]
760
- --- from: assistant (TestBot)
761
- 🎤 Transcribing voice message...
762
- 📝 **Transcribed message:** Add error handling to the parser
763
- session-reply
764
- -# *project ⋅ main ⋅ Ns ⋅ N% ⋅ deterministic-v2*"
765
- `);
766
- expect(finalState?.sessionId).toBeDefined();
767
- if (!finalState?.sessionId) {
768
- throw new Error('Expected final state with sessionId');
769
- }
770
- expect(finalState.queueItems.length).toBe(0);
771
- // Verify the same OpenCode session received both prompts:
772
- // the initial text message AND the voice transcription
773
- const messages = await waitForSessionMessages({
774
- projectDirectory: directories.projectDirectory,
775
- sessionID: finalState.sessionId,
776
- timeout: 4_000,
777
- description: 'idle session receives voice transcription prompt',
778
- predicate: (all) => {
779
- const userTexts = getUserTexts(all);
780
- return (userTexts.some((t) => t.includes('FAST_RESPONSE_MARKER initial setup')) &&
781
- userTexts.some((t) => t.includes('Add error handling to the parser')));
782
- },
783
- });
784
- const userTexts = getUserTexts(messages);
785
- expect(userTexts.some((t) => t.includes('FAST_RESPONSE_MARKER initial setup'))).toBe(true);
786
- expect(userTexts.some((t) => t.includes('Add error handling to the parser'))).toBe(true);
787
- // Both prompts should have gotten assistant responses
788
- const assistantTexts = getAssistantTexts(messages);
789
- expect(assistantTexts.length).toBeGreaterThanOrEqual(2);
790
- }, 8_000);
791
- // ── Test 3: Voice message queues behind running session (default) ──
792
- test.skip('voice message with queueMessage=false queues behind running session', async () => {
793
- // 1. Start a session with a slow response
794
- setDeterministicTranscription(null);
795
- await discord.channel(TEXT_CHANNEL_ID).user(TEST_USER_ID).sendMessage({
796
- content: 'SLOW_RESPONSE_MARKER start slow task',
797
- });
798
- const thread = await discord.channel(TEXT_CHANNEL_ID).waitForThread({
799
- timeout: 4_000,
800
- predicate: (t) => {
801
- return t.name?.includes('SLOW_RESPONSE_MARKER') ?? false;
802
- },
803
- });
804
- const th = discord.thread(thread.id);
805
- // 2. Send voice message while session is running (default: queue)
806
- setDeterministicTranscription({
807
- transcription: 'Stop and do this instead',
808
- queueMessage: false,
809
- });
810
- await th.user(TEST_USER_ID).sendVoiceMessage();
811
- // 3. Wait for transcription to appear first
812
- await waitForBotMessageContaining({
813
- discord,
814
- threadId: thread.id,
815
- userId: TEST_USER_ID,
816
- text: 'Stop and do this instead',
817
- timeout: 4_000,
818
- });
819
- // queueMessage=false no longer interrupts by default, so we should NOT
820
- // receive the queued-position ack that queueMessage=true sends.
821
- const afterTranscription = await th.getMessages();
822
- const hasQueuedAck = afterTranscription.some((m) => {
823
- return (m.author.id === discord.botUserId &&
824
- m.content.includes('Queued at position'));
825
- });
826
- expect(hasQueuedAck).toBe(false);
827
- const midState = getThreadState(thread.id);
828
- expect(midState).toBeDefined();
829
- // 4. Wait for both runs to finish (slow prompt + queued transcription)
830
- const finalState = await waitForThreadState({
831
- threadId: thread.id,
832
- predicate: (s) => {
833
- return s.queueItems.length === 0;
834
- },
835
- timeout: 8_000,
836
- description: 'queue empty (default queued voice behavior)',
837
- });
838
- expect(finalState.sessionId).toBeDefined();
839
- expect(finalState.queueItems.length).toBe(0);
840
- // Verify the OpenCode session processed both prompts sequentially.
841
- const messages = await waitForSessionMessages({
842
- projectDirectory: directories.projectDirectory,
843
- sessionID: finalState.sessionId,
844
- timeout: 4_000,
845
- description: 'default queue: original prompt + voice prompt',
846
- predicate: (all) => {
847
- const userTexts = getUserTexts(all);
848
- const assistantTexts = getAssistantTexts(all);
849
- return (userTexts.some((t) => t.includes('SLOW_RESPONSE_MARKER start slow task')) &&
850
- userTexts.some((t) => t.includes('Stop and do this instead')) &&
851
- assistantTexts.some((t) => t.includes('slow-response-done')) &&
852
- assistantTexts.some((t) => t.includes('session-reply')));
853
- },
854
- });
855
- const userTexts = getUserTexts(messages);
856
- // Both prompts were sent to the same session
857
- expect(userTexts.some((t) => t.includes('SLOW_RESPONSE_MARKER start slow task'))).toBe(true);
858
- expect(userTexts.some((t) => t.includes('Stop and do this instead'))).toBe(true);
859
- const assistantTexts = getAssistantTexts(messages);
860
- expect(assistantTexts.some((t) => t.includes('slow-response-done'))).toBe(true);
861
- expect(assistantTexts.some((t) => t.includes('session-reply'))).toBe(true);
862
- }, 10_000);
863
- // ── Test 4: Voice message with queueMessage=true queues instead of interrupting ──
864
- test('voice message with queueMessage=true queues behind running session', async () => {
865
- // 1. Start a session with a slow response
866
- setDeterministicTranscription(null);
867
- await discord.channel(TEXT_CHANNEL_ID).user(TEST_USER_ID).sendMessage({
868
- content: 'SLOW_RESPONSE_MARKER start queued task',
869
- });
870
- const thread = await discord.channel(TEXT_CHANNEL_ID).waitForThread({
871
- timeout: 4_000,
872
- predicate: (t) => {
873
- return t.name?.includes('start queued task') ?? false;
874
- },
875
- });
876
- const th = discord.thread(thread.id);
877
- // 2. Send voice message with queueMessage=true (should NOT interrupt)
878
- setDeterministicTranscription({
879
- transcription: 'Queue this task for later',
880
- queueMessage: true,
881
- });
882
- await waitForBotMessageContaining({
883
- discord,
884
- threadId: thread.id,
885
- userId: TEST_USER_ID,
886
- text: 'using deterministic-provider/deterministic-v2',
887
- timeout: 4_000,
888
- });
889
- const voiceMessage = await th.user(TEST_USER_ID).sendVoiceMessage();
890
- // 3. Transcription should appear, followed by queue notification
891
- await waitForBotMessageContaining({
892
- discord,
893
- threadId: thread.id,
894
- userId: TEST_USER_ID,
895
- text: 'Queue this task for later',
896
- timeout: 4_000,
897
- });
898
- const messagesWithQueueAck = await waitForBotMessageContaining({
899
- discord,
900
- threadId: thread.id,
901
- userId: TEST_USER_ID,
902
- text: 'Queued at position',
903
- timeout: 4_000,
904
- });
905
- const queueAckMessage = messagesWithQueueAck.find((message) => {
906
- return (message.author.id === discord.botUserId
907
- && message.content.includes('Queued at position'));
908
- });
909
- expect(queueAckMessage).toBeDefined();
910
- // 4. queueMessage=true should not interrupt the in-flight response.
911
- await waitForBotMessageContaining({
912
- discord,
913
- threadId: thread.id,
914
- userId: TEST_USER_ID,
915
- text: 'slow-response-done',
916
- timeout: 4_000,
917
- });
918
- if (!queueAckMessage) {
919
- throw new Error('Expected queue ack message');
920
- }
921
- // The drain indicator is a silent reply to the voice message.
922
- const dispatchMessage = await waitForBotReplyTo({
923
- discord,
924
- threadId: thread.id,
925
- replyToMessageId: voiceMessage.id,
926
- timeout: 8_000,
927
- });
928
- expect(dispatchMessage.content).toBe('-# Executing queued prompt');
929
- await waitForBotMessageContaining({
930
- discord,
931
- threadId: thread.id,
932
- text: 'session-reply',
933
- afterMessageId: dispatchMessage.id,
934
- timeout: 8_000,
935
- });
936
- // 5. Wait for the slow session to finish AND the queue to drain.
937
- // Using waitForThreadState with a compound predicate avoids matching
938
- // the transient 'idle' state from run A before run B starts.
939
- const finalState = await waitForThreadState({
940
- threadId: thread.id,
941
- predicate: (s) => {
942
- return s.queueItems.length === 0;
943
- },
944
- timeout: 8_000,
945
- description: 'queue empty (both runs completed)',
946
- });
947
- await waitForFooterMessage({
948
- discord,
949
- threadId: thread.id,
950
- timeout: 4_000,
951
- afterMessageIncludes: 'session-reply',
952
- afterAuthorId: discord.botUserId,
953
- });
954
- expect(await th.text()).toMatchInlineSnapshot(`
955
- "--- from: user (voice-tester)
956
- SLOW_RESPONSE_MARKER start queued task
957
- --- from: assistant (TestBot)
958
- -# *using deterministic-provider/deterministic-v2*
959
- --- from: user (voice-tester)
960
- [attachment: voice-message.ogg]
961
- --- from: assistant (TestBot)
962
- 🎤 Transcribing voice message...
963
- 📝 **Transcribed message:** Queue this task for later
964
- -# Queued at position 1. Edit or delete your message to update the queue
965
- slow-response-done
966
- -# *project ⋅ main ⋅ Ns ⋅ N% ⋅ deterministic-v2*
967
- -# Executing queued prompt
968
- session-reply
969
- -# *project ⋅ main ⋅ Ns ⋅ N% ⋅ deterministic-v2*"
970
- `);
971
- expect(finalState.queueItems.length).toBe(0);
972
- // Verify the OpenCode session processed BOTH prompts sequentially:
973
- // the slow initial prompt completed, then the queued voice prompt ran
974
- const messages = await waitForSessionMessages({
975
- projectDirectory: directories.projectDirectory,
976
- sessionID: finalState.sessionId,
977
- timeout: 4_000,
978
- description: 'queue: both prompts processed with responses',
979
- predicate: (all) => {
980
- const userTexts = getUserTexts(all);
981
- const assistantTexts = getAssistantTexts(all);
982
- return (userTexts.some((t) => t.includes('SLOW_RESPONSE_MARKER start queued task')) &&
983
- userTexts.some((t) => t.includes('Queue this task for later')) &&
984
- assistantTexts.some((t) => t.includes('slow-response-done')) &&
985
- assistantTexts.some((t) => t.includes('session-reply')));
986
- },
987
- });
988
- const userTexts = getUserTexts(messages);
989
- const assistantTexts = getAssistantTexts(messages);
990
- // Both prompts sent to the session
991
- expect(userTexts.some((t) => t.includes('SLOW_RESPONSE_MARKER start queued task'))).toBe(true);
992
- expect(userTexts.some((t) => t.includes('Queue this task for later'))).toBe(true);
993
- // Both got responses (slow response + default reply for queued message)
994
- expect(assistantTexts.some((t) => t.includes('slow-response-done'))).toBe(true);
995
- expect(assistantTexts.some((t) => t.includes('session-reply'))).toBe(true);
996
- // No abort errors — the queue preserved the first run
997
- const abortedAssistant = messages.find((m) => {
998
- return m.info.role === 'assistant' && m.info.error?.name === 'MessageAbortedError';
999
- });
1000
- expect(abortedAssistant).toBeUndefined();
1001
- }, 12_000);
1002
- // ── Test 5: Slow transcription finishes after session becomes idle (race condition) ──
1003
- test('slow transcription completing after session finishes is handled correctly', async () => {
1004
- // This tests the race condition where:
1005
- // 1. Session starts with a fast response (~100ms)
1006
- // 2. Voice message is sent simultaneously with slow transcription (500ms)
1007
- // 3. The fast session finishes BEFORE transcription completes
1008
- // 4. When transcription completes, the session is idle → should start new request
1009
- // 1. Start a session with a fast response
1010
- setDeterministicTranscription(null);
1011
- await discord.channel(TEXT_CHANNEL_ID).user(TEST_USER_ID).sendMessage({
1012
- content: 'FAST_RESPONSE_MARKER quick task',
1013
- });
1014
- const thread = await discord.channel(TEXT_CHANNEL_ID).waitForThread({
1015
- timeout: 4_000,
1016
- predicate: (t) => {
1017
- return t.name?.includes('quick task') ?? false;
1018
- },
1019
- });
1020
- const th = discord.thread(thread.id);
1021
- // Wait for the first run to complete before sending voice.
1022
- await th.waitForBotReply({ timeout: 4_000 });
1023
- await waitForFooterMessage({
1024
- discord,
1025
- threadId: thread.id,
1026
- timeout: 4_000,
1027
- });
1028
- // 2. Now send voice message with slow transcription
1029
- // The fast response completes in ~100ms, but transcription takes 500ms.
1030
- // By the time transcription returns, the session is already idle.
1031
- setDeterministicTranscription({
1032
- transcription: 'Delayed transcription result',
1033
- queueMessage: false,
1034
- delayMs: 500,
1035
- });
1036
- await th.user(TEST_USER_ID).sendVoiceMessage();
1037
- // 3. The transcription should complete after the session finishes
1038
- // and the transcribed message should be processed as a new request
1039
- await waitForBotMessageContaining({
1040
- discord,
1041
- threadId: thread.id,
1042
- userId: TEST_USER_ID,
1043
- text: 'Delayed transcription result',
1044
- timeout: 4_000,
1045
- });
1046
- await waitForFooterMessage({
1047
- discord,
1048
- threadId: thread.id,
1049
- timeout: 4_000,
1050
- afterMessageIncludes: 'Delayed transcription result',
1051
- afterAuthorId: discord.botUserId,
1052
- });
1053
- // 4. Session should process the delayed transcription and settle.
1054
- const finalState = await waitForThreadState({
1055
- threadId: thread.id,
1056
- predicate: (state) => {
1057
- return Boolean(state.sessionId) && state.queueItems.length === 0;
1058
- },
1059
- timeout: 4_000,
1060
- description: 'delayed transcription settled with empty queue',
1061
- });
1062
- expect(await th.text()).toMatchInlineSnapshot(`
1063
- "--- from: user (voice-tester)
1064
- FAST_RESPONSE_MARKER quick task
1065
- --- from: assistant (TestBot)
1066
- -# *using deterministic-provider/deterministic-v2*
1067
- fast-response-done
1068
- -# *project ⋅ main ⋅ Ns ⋅ N% ⋅ deterministic-v2*
1069
- --- from: user (voice-tester)
1070
- [attachment: voice-message.ogg]
1071
- --- from: assistant (TestBot)
1072
- 🎤 Transcribing voice message...
1073
- 📝 **Transcribed message:** Delayed transcription result
1074
- session-reply
1075
- -# *project ⋅ main ⋅ Ns ⋅ N% ⋅ deterministic-v2*"
1076
- `);
1077
- expect(finalState.sessionId).toBeDefined();
1078
- expect(finalState.queueItems.length).toBe(0);
1079
- // 5. Verify the OpenCode session processed both prompts on the same session:
1080
- // the fast text message completed first, then the delayed voice transcription
1081
- const sessionMessages = await waitForSessionMessages({
1082
- projectDirectory: directories.projectDirectory,
1083
- sessionID: finalState.sessionId,
1084
- timeout: 4_000,
1085
- description: 'race: both prompts processed with responses on same session',
1086
- predicate: (all) => {
1087
- const userTexts = getUserTexts(all);
1088
- const aTexts = getAssistantTexts(all);
1089
- return (userTexts.some((t) => t.includes('FAST_RESPONSE_MARKER quick task')) &&
1090
- userTexts.some((t) => t.includes('Delayed transcription result')) &&
1091
- aTexts.length >= 2);
1092
- },
1093
- });
1094
- const userTexts = getUserTexts(sessionMessages);
1095
- expect(userTexts.some((t) => t.includes('FAST_RESPONSE_MARKER quick task'))).toBe(true);
1096
- expect(userTexts.some((t) => t.includes('Delayed transcription result'))).toBe(true);
1097
- // Both prompts got assistant responses (no aborts — second arrived after first finished)
1098
- const assistantTexts = getAssistantTexts(sessionMessages);
1099
- expect(assistantTexts.length).toBeGreaterThanOrEqual(2);
1100
- const abortedAssistant = sessionMessages.find((m) => {
1101
- return m.info.role === 'assistant' && m.info.error?.name === 'MessageAbortedError';
1102
- });
1103
- expect(abortedAssistant).toBeUndefined();
1104
- }, 8_000);
1105
- // ── Test 6: Slow transcription with queueMessage=true arriving after idle queue drain ──
1106
- test('slow queued transcription completing after session idle is dispatched', async () => {
1107
- // Reproduces the bug where a voice message with queueMessage=true has
1108
- // slow transcription that completes after the session is already idle.
1109
- // The message gets inserted into the local queue but was never drained
1110
- // because handleSessionIdle() didn't call tryDrainQueue().
1111
- //
1112
- // 1. Send a fast text message → session starts and finishes quickly
1113
- // 2. Send a voice message with queueMessage=true and slow transcription
1114
- // 3. Fast session finishes → handleSessionIdle fires
1115
- // 4. Transcription completes → enqueueViaLocalQueue adds item
1116
- // 5. tryDrainQueue in handleSessionIdle should pick it up
1117
- // 1. Start a session with a fast response
1118
- setDeterministicTranscription(null);
1119
- await discord.channel(TEXT_CHANNEL_ID).user(TEST_USER_ID).sendMessage({
1120
- content: 'FAST_RESPONSE_MARKER fast before queued voice',
1121
- });
1122
- const thread = await discord.channel(TEXT_CHANNEL_ID).waitForThread({
1123
- timeout: 4_000,
1124
- predicate: (t) => {
1125
- return t.name?.includes('fast before queued voice') ?? false;
1126
- },
1127
- });
1128
- const th = discord.thread(thread.id);
1129
- // Wait for the first run to fully complete before sending the queued voice message.
1130
- await th.waitForBotReply({ timeout: 4_000 });
1131
- await waitForFooterMessage({
1132
- discord,
1133
- threadId: thread.id,
1134
- timeout: 4_000,
1135
- });
1136
- // 2. Send voice message with queueMessage=true AND slow transcription.
1137
- // Session is already idle when this arrives. The transcription delay
1138
- // means the message enters the local queue after idle has fired.
1139
- setDeterministicTranscription({
1140
- transcription: 'Queued voice after idle',
1141
- queueMessage: true,
1142
- delayMs: 500,
1143
- });
1144
- await th.user(TEST_USER_ID).sendVoiceMessage();
1145
- // 3. The transcription should complete, and even though queueMessage=true
1146
- // routes through the local queue, the item should be drained immediately
1147
- // because the session is idle. No dispatch indicator (» prefix) appears
1148
- // because the message is dispatched immediately by enqueueViaLocalQueue's
1149
- // tryDrainQueue (showIndicator=false for first drain).
1150
- await waitForBotMessageContaining({
1151
- discord,
1152
- threadId: thread.id,
1153
- userId: TEST_USER_ID,
1154
- text: 'Queued voice after idle',
1155
- timeout: 4_000,
1156
- });
1157
- // Wait for the queued message response and footer
1158
- await waitForBotMessageContaining({
1159
- discord,
1160
- threadId: thread.id,
1161
- text: 'session-reply',
1162
- timeout: 4_000,
1163
- });
1164
- await waitForFooterMessage({
1165
- discord,
1166
- threadId: thread.id,
1167
- timeout: 4_000,
1168
- afterMessageIncludes: 'session-reply',
1169
- afterAuthorId: discord.botUserId,
1170
- });
1171
- // 4. Final state: queue should be empty
1172
- const finalState = await waitForThreadState({
1173
- threadId: thread.id,
1174
- predicate: (state) => {
1175
- return Boolean(state.sessionId) && state.queueItems.length === 0;
1176
- },
1177
- timeout: 4_000,
1178
- description: 'queued voice after idle settled with empty queue',
1179
- });
1180
- expect(await th.text()).toMatchInlineSnapshot(`
1181
- "--- from: user (voice-tester)
1182
- FAST_RESPONSE_MARKER fast before queued voice
1183
- --- from: assistant (TestBot)
1184
- -# *using deterministic-provider/deterministic-v2*
1185
- fast-response-done
1186
- -# *project ⋅ main ⋅ Ns ⋅ N% ⋅ deterministic-v2*
1187
- --- from: user (voice-tester)
1188
- [attachment: voice-message.ogg]
1189
- --- from: assistant (TestBot)
1190
- 🎤 Transcribing voice message...
1191
- 📝 **Transcribed message:** Queued voice after idle
1192
- session-reply
1193
- -# *project ⋅ main ⋅ Ns ⋅ N% ⋅ deterministic-v2*"
1194
- `);
1195
- expect(finalState.sessionId).toBeDefined();
1196
- expect(finalState.queueItems.length).toBe(0);
1197
- // 5. Verify the OpenCode session processed both prompts
1198
- const sessionMessages = await waitForSessionMessages({
1199
- projectDirectory: directories.projectDirectory,
1200
- sessionID: finalState.sessionId,
1201
- timeout: 4_000,
1202
- description: 'queued-voice-idle: both prompts processed',
1203
- predicate: (all) => {
1204
- const userTexts = getUserTexts(all);
1205
- const aTexts = getAssistantTexts(all);
1206
- return (userTexts.some((t) => t.includes('FAST_RESPONSE_MARKER fast before queued voice')) &&
1207
- userTexts.some((t) => t.includes('Queued voice after idle')) &&
1208
- aTexts.length >= 2);
1209
- },
1210
- });
1211
- const userTexts = getUserTexts(sessionMessages);
1212
- expect(userTexts.some((t) => t.includes('FAST_RESPONSE_MARKER fast before queued voice'))).toBe(true);
1213
- expect(userTexts.some((t) => t.includes('Queued voice after idle'))).toBe(true);
1214
- const assistantTexts = getAssistantTexts(sessionMessages);
1215
- expect(assistantTexts.length).toBeGreaterThanOrEqual(2);
1216
- }, 10_000);
1217
- test.skipIf(!process.env.OPENAI_API_KEY)('transcribes a real ogg voice note and starts a session', async () => {
1218
- const audioPath = path.resolve(process.cwd(), 'scripts', 'example-audio.ogg');
1219
- const audio = fs.readFileSync(audioPath);
1220
- const server = http.createServer((req, res) => {
1221
- if (req.url !== '/voice-message.ogg') {
1222
- res.writeHead(404).end();
1223
- return;
1224
- }
1225
- res.writeHead(200, { 'content-type': 'audio/ogg' });
1226
- res.end(audio);
1227
- });
1228
- await new Promise((resolve) => {
1229
- server.listen(0, '127.0.0.1', resolve);
1230
- });
1231
- const address = server.address();
1232
- if (!address || typeof address === 'string')
1233
- throw new Error('Audio server did not bind');
1234
- const audioUrl = `http://127.0.0.1:${address.port}/voice-message.ogg`;
1235
- setDeterministicTranscription(null);
1236
- await discord.channel(TEXT_CHANNEL_ID).user(TEST_USER_ID).sendVoiceMessage({ url: audioUrl });
1237
- const thread = await discord.channel(TEXT_CHANNEL_ID).waitForThread({
1238
- timeout: 20_000,
1239
- predicate: (candidate) => Boolean(candidate.name?.toLowerCase().includes('hear me')),
1240
- });
1241
- await waitForFooterMessage({ discord, threadId: thread.id, timeout: 20_000 });
1242
- expect(await discord.thread(thread.id).text()).toMatchInlineSnapshot(`
1243
- "--- from: user (voice-tester)
1244
- [attachment: voice-message.ogg]
1245
- --- from: assistant (TestBot)
1246
- 🎤 Transcribing voice message...
1247
- 📝 **Transcribed message:** Yo, can you hear me loud and clear? What a shit model. Why are you so slow?
1248
- -# *using deterministic-provider/deterministic-v2*
1249
- > session-reply
1250
- -# *project ⋅ main ⋅ Ns ⋅ N% ⋅ deterministic-v2* <@300000000000000777>"
1251
- `);
1252
- server.close();
1253
- }, 30_000);
1254
- });