@otto-assistant/bridge 0.4.92

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (483) hide show
  1. package/bin.js +2 -0
  2. package/dist/agent-model.e2e.test.js +755 -0
  3. package/dist/ai-tool-to-genai.js +233 -0
  4. package/dist/ai-tool-to-genai.test.js +267 -0
  5. package/dist/ai-tool.js +6 -0
  6. package/dist/anthropic-auth-plugin.js +728 -0
  7. package/dist/anthropic-auth-plugin.test.js +125 -0
  8. package/dist/anthropic-auth-state.js +231 -0
  9. package/dist/bin.js +90 -0
  10. package/dist/channel-management.js +227 -0
  11. package/dist/cli-parsing.test.js +137 -0
  12. package/dist/cli-send-thread.e2e.test.js +356 -0
  13. package/dist/cli.js +3276 -0
  14. package/dist/commands/abort.js +65 -0
  15. package/dist/commands/action-buttons.js +245 -0
  16. package/dist/commands/add-project.js +113 -0
  17. package/dist/commands/agent.js +335 -0
  18. package/dist/commands/ask-question.js +274 -0
  19. package/dist/commands/btw.js +116 -0
  20. package/dist/commands/compact.js +120 -0
  21. package/dist/commands/context-usage.js +140 -0
  22. package/dist/commands/create-new-project.js +130 -0
  23. package/dist/commands/diff.js +63 -0
  24. package/dist/commands/file-upload.js +275 -0
  25. package/dist/commands/fork.js +220 -0
  26. package/dist/commands/gemini-apikey.js +70 -0
  27. package/dist/commands/login.js +885 -0
  28. package/dist/commands/mcp.js +239 -0
  29. package/dist/commands/memory-snapshot.js +24 -0
  30. package/dist/commands/mention-mode.js +44 -0
  31. package/dist/commands/merge-worktree.js +159 -0
  32. package/dist/commands/model-variant.js +364 -0
  33. package/dist/commands/model.js +776 -0
  34. package/dist/commands/new-worktree.js +366 -0
  35. package/dist/commands/paginated-select.js +57 -0
  36. package/dist/commands/permissions.js +274 -0
  37. package/dist/commands/queue.js +206 -0
  38. package/dist/commands/remove-project.js +115 -0
  39. package/dist/commands/restart-opencode-server.js +127 -0
  40. package/dist/commands/resume.js +149 -0
  41. package/dist/commands/run-command.js +79 -0
  42. package/dist/commands/screenshare.js +303 -0
  43. package/dist/commands/screenshare.test.js +20 -0
  44. package/dist/commands/session-id.js +78 -0
  45. package/dist/commands/session.js +176 -0
  46. package/dist/commands/share.js +80 -0
  47. package/dist/commands/tasks.js +205 -0
  48. package/dist/commands/types.js +2 -0
  49. package/dist/commands/undo-redo.js +305 -0
  50. package/dist/commands/unset-model.js +138 -0
  51. package/dist/commands/upgrade.js +42 -0
  52. package/dist/commands/user-command.js +155 -0
  53. package/dist/commands/verbosity.js +125 -0
  54. package/dist/commands/worktree-settings.js +43 -0
  55. package/dist/commands/worktrees.js +410 -0
  56. package/dist/condense-memory.js +33 -0
  57. package/dist/config.js +94 -0
  58. package/dist/context-awareness-plugin.js +363 -0
  59. package/dist/context-awareness-plugin.test.js +124 -0
  60. package/dist/critique-utils.js +95 -0
  61. package/dist/database.js +1310 -0
  62. package/dist/db.js +251 -0
  63. package/dist/db.test.js +138 -0
  64. package/dist/debounce-timeout.js +28 -0
  65. package/dist/debounced-process-flush.js +77 -0
  66. package/dist/discord-bot.js +1008 -0
  67. package/dist/discord-command-registration.js +524 -0
  68. package/dist/discord-urls.js +81 -0
  69. package/dist/discord-utils.js +591 -0
  70. package/dist/discord-utils.test.js +134 -0
  71. package/dist/errors.js +157 -0
  72. package/dist/escape-backticks.test.js +429 -0
  73. package/dist/event-stream-real-capture.e2e.test.js +533 -0
  74. package/dist/eventsource-parser.test.js +327 -0
  75. package/dist/exec-async.js +26 -0
  76. package/dist/external-opencode-sync.js +480 -0
  77. package/dist/format-tables.js +302 -0
  78. package/dist/format-tables.test.js +308 -0
  79. package/dist/forum-sync/config.js +79 -0
  80. package/dist/forum-sync/discord-operations.js +154 -0
  81. package/dist/forum-sync/index.js +5 -0
  82. package/dist/forum-sync/markdown.js +113 -0
  83. package/dist/forum-sync/sync-to-discord.js +417 -0
  84. package/dist/forum-sync/sync-to-files.js +190 -0
  85. package/dist/forum-sync/types.js +53 -0
  86. package/dist/forum-sync/watchers.js +307 -0
  87. package/dist/gateway-proxy-reconnect.e2e.test.js +394 -0
  88. package/dist/gateway-proxy.e2e.test.js +483 -0
  89. package/dist/genai-worker-wrapper.js +111 -0
  90. package/dist/genai-worker.js +311 -0
  91. package/dist/genai.js +232 -0
  92. package/dist/generated/browser.js +17 -0
  93. package/dist/generated/client.js +37 -0
  94. package/dist/generated/commonInputTypes.js +10 -0
  95. package/dist/generated/enums.js +52 -0
  96. package/dist/generated/internal/class.js +49 -0
  97. package/dist/generated/internal/prismaNamespace.js +253 -0
  98. package/dist/generated/internal/prismaNamespaceBrowser.js +223 -0
  99. package/dist/generated/models/bot_api_keys.js +1 -0
  100. package/dist/generated/models/bot_tokens.js +1 -0
  101. package/dist/generated/models/channel_agents.js +1 -0
  102. package/dist/generated/models/channel_directories.js +1 -0
  103. package/dist/generated/models/channel_mention_mode.js +1 -0
  104. package/dist/generated/models/channel_models.js +1 -0
  105. package/dist/generated/models/channel_verbosity.js +1 -0
  106. package/dist/generated/models/channel_worktrees.js +1 -0
  107. package/dist/generated/models/forum_sync_configs.js +1 -0
  108. package/dist/generated/models/global_models.js +1 -0
  109. package/dist/generated/models/ipc_requests.js +1 -0
  110. package/dist/generated/models/part_messages.js +1 -0
  111. package/dist/generated/models/scheduled_tasks.js +1 -0
  112. package/dist/generated/models/session_agents.js +1 -0
  113. package/dist/generated/models/session_events.js +1 -0
  114. package/dist/generated/models/session_models.js +1 -0
  115. package/dist/generated/models/session_start_sources.js +1 -0
  116. package/dist/generated/models/thread_sessions.js +1 -0
  117. package/dist/generated/models/thread_worktrees.js +1 -0
  118. package/dist/generated/models.js +1 -0
  119. package/dist/heap-monitor.js +122 -0
  120. package/dist/hrana-server.js +263 -0
  121. package/dist/hrana-server.test.js +370 -0
  122. package/dist/html-actions.js +123 -0
  123. package/dist/html-actions.test.js +70 -0
  124. package/dist/html-components.js +117 -0
  125. package/dist/html-components.test.js +34 -0
  126. package/dist/image-optimizer-plugin.js +153 -0
  127. package/dist/image-utils.js +112 -0
  128. package/dist/interaction-handler.js +397 -0
  129. package/dist/ipc-polling.js +252 -0
  130. package/dist/ipc-tools-plugin.js +193 -0
  131. package/dist/kimaki-digital-twin.e2e.test.js +161 -0
  132. package/dist/kimaki-opencode-plugin-loading.e2e.test.js +87 -0
  133. package/dist/kimaki-opencode-plugin.js +17 -0
  134. package/dist/kimaki-opencode-plugin.test.js +98 -0
  135. package/dist/limit-heading-depth.js +25 -0
  136. package/dist/limit-heading-depth.test.js +105 -0
  137. package/dist/logger.js +165 -0
  138. package/dist/markdown.js +342 -0
  139. package/dist/markdown.test.js +257 -0
  140. package/dist/message-finish-field.e2e.test.js +165 -0
  141. package/dist/message-formatting.js +413 -0
  142. package/dist/message-formatting.test.js +73 -0
  143. package/dist/message-preprocessing.js +330 -0
  144. package/dist/onboarding-tutorial.js +172 -0
  145. package/dist/onboarding-welcome.js +37 -0
  146. package/dist/openai-realtime.js +224 -0
  147. package/dist/opencode-command-detection.js +65 -0
  148. package/dist/opencode-command-detection.test.js +240 -0
  149. package/dist/opencode-command.js +129 -0
  150. package/dist/opencode-command.test.js +48 -0
  151. package/dist/opencode-interrupt-plugin.js +361 -0
  152. package/dist/opencode-interrupt-plugin.test.js +458 -0
  153. package/dist/opencode.js +861 -0
  154. package/dist/otto/branding.js +22 -0
  155. package/dist/otto/index.js +21 -0
  156. package/dist/parse-permission-rules.test.js +117 -0
  157. package/dist/patch-text-parser.js +97 -0
  158. package/dist/plugin-logger.js +59 -0
  159. package/dist/privacy-sanitizer.js +105 -0
  160. package/dist/queue-advanced-abort.e2e.test.js +293 -0
  161. package/dist/queue-advanced-action-buttons.e2e.test.js +206 -0
  162. package/dist/queue-advanced-e2e-setup.js +786 -0
  163. package/dist/queue-advanced-footer.e2e.test.js +472 -0
  164. package/dist/queue-advanced-model-switch.e2e.test.js +299 -0
  165. package/dist/queue-advanced-permissions-typing.e2e.test.js +180 -0
  166. package/dist/queue-advanced-question.e2e.test.js +261 -0
  167. package/dist/queue-advanced-typing-interrupt.e2e.test.js +114 -0
  168. package/dist/queue-advanced-typing.e2e.test.js +153 -0
  169. package/dist/queue-drain-after-interactive-ui.e2e.test.js +119 -0
  170. package/dist/queue-interrupt-drain.e2e.test.js +135 -0
  171. package/dist/queue-question-select-drain.e2e.test.js +120 -0
  172. package/dist/runtime-idle-sweeper.js +52 -0
  173. package/dist/runtime-lifecycle.e2e.test.js +508 -0
  174. package/dist/sentry.js +23 -0
  175. package/dist/session-handler/agent-utils.js +67 -0
  176. package/dist/session-handler/event-stream-state.js +420 -0
  177. package/dist/session-handler/event-stream-state.test.js +563 -0
  178. package/dist/session-handler/model-utils.js +124 -0
  179. package/dist/session-handler/opencode-session-event-log.js +94 -0
  180. package/dist/session-handler/thread-runtime-state.js +104 -0
  181. package/dist/session-handler/thread-session-runtime.js +3258 -0
  182. package/dist/session-handler.js +9 -0
  183. package/dist/session-search.js +100 -0
  184. package/dist/session-search.test.js +40 -0
  185. package/dist/session-title-rename.test.js +80 -0
  186. package/dist/startup-service.js +153 -0
  187. package/dist/startup-time.e2e.test.js +296 -0
  188. package/dist/store.js +17 -0
  189. package/dist/system-message.js +613 -0
  190. package/dist/system-message.test.js +602 -0
  191. package/dist/task-runner.js +295 -0
  192. package/dist/task-schedule.js +209 -0
  193. package/dist/task-schedule.test.js +71 -0
  194. package/dist/test-utils.js +299 -0
  195. package/dist/thinking-utils.js +35 -0
  196. package/dist/thread-message-queue.e2e.test.js +999 -0
  197. package/dist/tools.js +357 -0
  198. package/dist/undo-redo.e2e.test.js +161 -0
  199. package/dist/unnest-code-blocks.js +146 -0
  200. package/dist/unnest-code-blocks.test.js +673 -0
  201. package/dist/upgrade.js +114 -0
  202. package/dist/utils.js +144 -0
  203. package/dist/voice-attachment.js +34 -0
  204. package/dist/voice-handler.js +646 -0
  205. package/dist/voice-message.e2e.test.js +1021 -0
  206. package/dist/voice.js +447 -0
  207. package/dist/voice.test.js +235 -0
  208. package/dist/wait-session.js +94 -0
  209. package/dist/websockify.js +69 -0
  210. package/dist/worker-types.js +4 -0
  211. package/dist/worktree-lifecycle.e2e.test.js +308 -0
  212. package/dist/worktree-utils.js +3 -0
  213. package/dist/worktrees.js +929 -0
  214. package/dist/worktrees.test.js +189 -0
  215. package/dist/xml.js +92 -0
  216. package/dist/xml.test.js +32 -0
  217. package/package.json +98 -0
  218. package/schema.prisma +295 -0
  219. package/skills/batch/SKILL.md +87 -0
  220. package/skills/critique/SKILL.md +112 -0
  221. package/skills/egaki/SKILL.md +100 -0
  222. package/skills/errore/SKILL.md +647 -0
  223. package/skills/event-sourcing-state/SKILL.md +252 -0
  224. package/skills/gitchamber/SKILL.md +93 -0
  225. package/skills/goke/SKILL.md +644 -0
  226. package/skills/jitter/EDITOR.md +219 -0
  227. package/skills/jitter/EXPORT-INTERNALS.md +309 -0
  228. package/skills/jitter/SKILL.md +158 -0
  229. package/skills/jitter/jitter-clipboard.json +1042 -0
  230. package/skills/jitter/package.json +14 -0
  231. package/skills/jitter/tsconfig.json +15 -0
  232. package/skills/jitter/utils/actions.ts +212 -0
  233. package/skills/jitter/utils/export.ts +114 -0
  234. package/skills/jitter/utils/index.ts +141 -0
  235. package/skills/jitter/utils/snapshot.ts +154 -0
  236. package/skills/jitter/utils/traverse.ts +246 -0
  237. package/skills/jitter/utils/types.ts +279 -0
  238. package/skills/jitter/utils/wait.ts +133 -0
  239. package/skills/lintcn/SKILL.md +873 -0
  240. package/skills/new-skill/SKILL.md +211 -0
  241. package/skills/npm-package/SKILL.md +239 -0
  242. package/skills/playwriter/SKILL.md +35 -0
  243. package/skills/proxyman/SKILL.md +215 -0
  244. package/skills/security-review/SKILL.md +208 -0
  245. package/skills/simplify/SKILL.md +58 -0
  246. package/skills/spiceflow/SKILL.md +14 -0
  247. package/skills/termcast/SKILL.md +945 -0
  248. package/skills/tuistory/SKILL.md +250 -0
  249. package/skills/usecomputer/SKILL.md +264 -0
  250. package/skills/x-articles/SKILL.md +554 -0
  251. package/skills/zele/SKILL.md +112 -0
  252. package/skills/zustand-centralized-state/SKILL.md +1004 -0
  253. package/src/agent-model.e2e.test.ts +976 -0
  254. package/src/ai-tool-to-genai.test.ts +296 -0
  255. package/src/ai-tool-to-genai.ts +283 -0
  256. package/src/ai-tool.ts +39 -0
  257. package/src/anthropic-auth-plugin.test.ts +159 -0
  258. package/src/anthropic-auth-plugin.ts +861 -0
  259. package/src/anthropic-auth-state.ts +282 -0
  260. package/src/bin.ts +111 -0
  261. package/src/channel-management.ts +334 -0
  262. package/src/cli-parsing.test.ts +195 -0
  263. package/src/cli-send-thread.e2e.test.ts +464 -0
  264. package/src/cli.ts +4581 -0
  265. package/src/commands/abort.ts +89 -0
  266. package/src/commands/action-buttons.ts +364 -0
  267. package/src/commands/add-project.ts +149 -0
  268. package/src/commands/agent.ts +473 -0
  269. package/src/commands/ask-question.ts +390 -0
  270. package/src/commands/btw.ts +164 -0
  271. package/src/commands/compact.ts +157 -0
  272. package/src/commands/context-usage.ts +199 -0
  273. package/src/commands/create-new-project.ts +190 -0
  274. package/src/commands/diff.ts +91 -0
  275. package/src/commands/file-upload.ts +389 -0
  276. package/src/commands/fork.ts +321 -0
  277. package/src/commands/gemini-apikey.ts +104 -0
  278. package/src/commands/login.ts +1173 -0
  279. package/src/commands/mcp.ts +307 -0
  280. package/src/commands/memory-snapshot.ts +30 -0
  281. package/src/commands/mention-mode.ts +68 -0
  282. package/src/commands/merge-worktree.ts +223 -0
  283. package/src/commands/model-variant.ts +483 -0
  284. package/src/commands/model.ts +1053 -0
  285. package/src/commands/new-worktree.ts +510 -0
  286. package/src/commands/paginated-select.ts +81 -0
  287. package/src/commands/permissions.ts +397 -0
  288. package/src/commands/queue.ts +271 -0
  289. package/src/commands/remove-project.ts +155 -0
  290. package/src/commands/restart-opencode-server.ts +162 -0
  291. package/src/commands/resume.ts +230 -0
  292. package/src/commands/run-command.ts +123 -0
  293. package/src/commands/screenshare.test.ts +30 -0
  294. package/src/commands/screenshare.ts +366 -0
  295. package/src/commands/session-id.ts +109 -0
  296. package/src/commands/session.ts +227 -0
  297. package/src/commands/share.ts +106 -0
  298. package/src/commands/tasks.ts +293 -0
  299. package/src/commands/types.ts +25 -0
  300. package/src/commands/undo-redo.ts +386 -0
  301. package/src/commands/unset-model.ts +173 -0
  302. package/src/commands/upgrade.ts +52 -0
  303. package/src/commands/user-command.ts +198 -0
  304. package/src/commands/verbosity.ts +173 -0
  305. package/src/commands/worktree-settings.ts +70 -0
  306. package/src/commands/worktrees.ts +552 -0
  307. package/src/condense-memory.ts +36 -0
  308. package/src/config.ts +111 -0
  309. package/src/context-awareness-plugin.test.ts +142 -0
  310. package/src/context-awareness-plugin.ts +510 -0
  311. package/src/critique-utils.ts +139 -0
  312. package/src/database.ts +1876 -0
  313. package/src/db.test.ts +162 -0
  314. package/src/db.ts +286 -0
  315. package/src/debounce-timeout.ts +43 -0
  316. package/src/debounced-process-flush.ts +104 -0
  317. package/src/discord-bot.ts +1330 -0
  318. package/src/discord-command-registration.ts +693 -0
  319. package/src/discord-urls.ts +88 -0
  320. package/src/discord-utils.test.ts +153 -0
  321. package/src/discord-utils.ts +800 -0
  322. package/src/errors.ts +201 -0
  323. package/src/escape-backticks.test.ts +469 -0
  324. package/src/event-stream-real-capture.e2e.test.ts +692 -0
  325. package/src/eventsource-parser.test.ts +351 -0
  326. package/src/exec-async.ts +35 -0
  327. package/src/external-opencode-sync.ts +685 -0
  328. package/src/format-tables.test.ts +335 -0
  329. package/src/format-tables.ts +445 -0
  330. package/src/forum-sync/config.ts +92 -0
  331. package/src/forum-sync/discord-operations.ts +241 -0
  332. package/src/forum-sync/index.ts +9 -0
  333. package/src/forum-sync/markdown.ts +172 -0
  334. package/src/forum-sync/sync-to-discord.ts +595 -0
  335. package/src/forum-sync/sync-to-files.ts +294 -0
  336. package/src/forum-sync/types.ts +175 -0
  337. package/src/forum-sync/watchers.ts +454 -0
  338. package/src/gateway-proxy-reconnect.e2e.test.ts +523 -0
  339. package/src/gateway-proxy.e2e.test.ts +640 -0
  340. package/src/genai-worker-wrapper.ts +164 -0
  341. package/src/genai-worker.ts +386 -0
  342. package/src/genai.ts +321 -0
  343. package/src/generated/browser.ts +114 -0
  344. package/src/generated/client.ts +138 -0
  345. package/src/generated/commonInputTypes.ts +736 -0
  346. package/src/generated/enums.ts +88 -0
  347. package/src/generated/internal/class.ts +384 -0
  348. package/src/generated/internal/prismaNamespace.ts +2386 -0
  349. package/src/generated/internal/prismaNamespaceBrowser.ts +326 -0
  350. package/src/generated/models/bot_api_keys.ts +1288 -0
  351. package/src/generated/models/bot_tokens.ts +1656 -0
  352. package/src/generated/models/channel_agents.ts +1256 -0
  353. package/src/generated/models/channel_directories.ts +1859 -0
  354. package/src/generated/models/channel_mention_mode.ts +1300 -0
  355. package/src/generated/models/channel_models.ts +1288 -0
  356. package/src/generated/models/channel_verbosity.ts +1228 -0
  357. package/src/generated/models/channel_worktrees.ts +1300 -0
  358. package/src/generated/models/forum_sync_configs.ts +1452 -0
  359. package/src/generated/models/global_models.ts +1288 -0
  360. package/src/generated/models/ipc_requests.ts +1485 -0
  361. package/src/generated/models/part_messages.ts +1302 -0
  362. package/src/generated/models/scheduled_tasks.ts +2320 -0
  363. package/src/generated/models/session_agents.ts +1086 -0
  364. package/src/generated/models/session_events.ts +1439 -0
  365. package/src/generated/models/session_models.ts +1114 -0
  366. package/src/generated/models/session_start_sources.ts +1408 -0
  367. package/src/generated/models/thread_sessions.ts +1781 -0
  368. package/src/generated/models/thread_worktrees.ts +1356 -0
  369. package/src/generated/models.ts +30 -0
  370. package/src/heap-monitor.ts +152 -0
  371. package/src/hrana-server.test.ts +434 -0
  372. package/src/hrana-server.ts +314 -0
  373. package/src/html-actions.test.ts +87 -0
  374. package/src/html-actions.ts +174 -0
  375. package/src/html-components.test.ts +38 -0
  376. package/src/html-components.ts +181 -0
  377. package/src/image-optimizer-plugin.ts +194 -0
  378. package/src/image-utils.ts +149 -0
  379. package/src/interaction-handler.ts +576 -0
  380. package/src/ipc-polling.ts +326 -0
  381. package/src/ipc-tools-plugin.ts +236 -0
  382. package/src/kimaki-digital-twin.e2e.test.ts +199 -0
  383. package/src/kimaki-opencode-plugin-loading.e2e.test.ts +109 -0
  384. package/src/kimaki-opencode-plugin.test.ts +108 -0
  385. package/src/kimaki-opencode-plugin.ts +18 -0
  386. package/src/limit-heading-depth.test.ts +116 -0
  387. package/src/limit-heading-depth.ts +26 -0
  388. package/src/logger.ts +208 -0
  389. package/src/markdown.test.ts +308 -0
  390. package/src/markdown.ts +410 -0
  391. package/src/message-finish-field.e2e.test.ts +192 -0
  392. package/src/message-formatting.test.ts +81 -0
  393. package/src/message-formatting.ts +533 -0
  394. package/src/message-preprocessing.ts +455 -0
  395. package/src/onboarding-tutorial.ts +176 -0
  396. package/src/onboarding-welcome.ts +49 -0
  397. package/src/openai-realtime.ts +358 -0
  398. package/src/opencode-command-detection.test.ts +307 -0
  399. package/src/opencode-command-detection.ts +76 -0
  400. package/src/opencode-command.test.ts +70 -0
  401. package/src/opencode-command.ts +188 -0
  402. package/src/opencode-interrupt-plugin.test.ts +677 -0
  403. package/src/opencode-interrupt-plugin.ts +477 -0
  404. package/src/opencode.ts +1110 -0
  405. package/src/otto/branding.ts +23 -0
  406. package/src/otto/index.ts +22 -0
  407. package/src/parse-permission-rules.test.ts +127 -0
  408. package/src/patch-text-parser.ts +107 -0
  409. package/src/plugin-logger.ts +68 -0
  410. package/src/privacy-sanitizer.ts +142 -0
  411. package/src/queue-advanced-abort.e2e.test.ts +382 -0
  412. package/src/queue-advanced-action-buttons.e2e.test.ts +268 -0
  413. package/src/queue-advanced-e2e-setup.ts +873 -0
  414. package/src/queue-advanced-footer.e2e.test.ts +576 -0
  415. package/src/queue-advanced-model-switch.e2e.test.ts +383 -0
  416. package/src/queue-advanced-permissions-typing.e2e.test.ts +245 -0
  417. package/src/queue-advanced-question.e2e.test.ts +316 -0
  418. package/src/queue-advanced-typing-interrupt.e2e.test.ts +146 -0
  419. package/src/queue-advanced-typing.e2e.test.ts +199 -0
  420. package/src/queue-drain-after-interactive-ui.e2e.test.ts +151 -0
  421. package/src/queue-interrupt-drain.e2e.test.ts +166 -0
  422. package/src/queue-question-select-drain.e2e.test.ts +152 -0
  423. package/src/runtime-idle-sweeper.ts +76 -0
  424. package/src/runtime-lifecycle.e2e.test.ts +641 -0
  425. package/src/schema.sql +173 -0
  426. package/src/sentry.ts +26 -0
  427. package/src/session-handler/agent-utils.ts +97 -0
  428. package/src/session-handler/event-stream-fixtures/real-session-action-buttons.jsonl +45 -0
  429. package/src/session-handler/event-stream-fixtures/real-session-footer-suppressed-on-pre-idle-interrupt.jsonl +40 -0
  430. package/src/session-handler/event-stream-fixtures/real-session-permission-external-file.jsonl +23 -0
  431. package/src/session-handler/event-stream-fixtures/real-session-task-normal.jsonl +22 -0
  432. package/src/session-handler/event-stream-fixtures/real-session-task-three-parallel-sleeps.jsonl +277 -0
  433. package/src/session-handler/event-stream-fixtures/real-session-task-user-interruption.jsonl +46 -0
  434. package/src/session-handler/event-stream-fixtures/session-abort-after-idle-race.jsonl +21 -0
  435. package/src/session-handler/event-stream-fixtures/session-concurrent-messages-serialized.jsonl +56 -0
  436. package/src/session-handler/event-stream-fixtures/session-explicit-abort.jsonl +44 -0
  437. package/src/session-handler/event-stream-fixtures/session-normal-completion.jsonl +29 -0
  438. package/src/session-handler/event-stream-fixtures/session-tool-call-noisy-stream.jsonl +29 -0
  439. package/src/session-handler/event-stream-fixtures/session-two-completions-same-session.jsonl +50 -0
  440. package/src/session-handler/event-stream-fixtures/session-user-interruption.jsonl +59 -0
  441. package/src/session-handler/event-stream-fixtures/session-voice-queued-followup.jsonl +52 -0
  442. package/src/session-handler/event-stream-state.test.ts +645 -0
  443. package/src/session-handler/event-stream-state.ts +608 -0
  444. package/src/session-handler/model-utils.ts +183 -0
  445. package/src/session-handler/opencode-session-event-log.ts +130 -0
  446. package/src/session-handler/thread-runtime-state.ts +212 -0
  447. package/src/session-handler/thread-session-runtime.ts +4281 -0
  448. package/src/session-handler.ts +15 -0
  449. package/src/session-search.test.ts +50 -0
  450. package/src/session-search.ts +148 -0
  451. package/src/session-title-rename.test.ts +112 -0
  452. package/src/startup-service.ts +200 -0
  453. package/src/startup-time.e2e.test.ts +373 -0
  454. package/src/store.ts +122 -0
  455. package/src/system-message.test.ts +612 -0
  456. package/src/system-message.ts +723 -0
  457. package/src/task-runner.ts +421 -0
  458. package/src/task-schedule.test.ts +84 -0
  459. package/src/task-schedule.ts +311 -0
  460. package/src/test-utils.ts +435 -0
  461. package/src/thinking-utils.ts +61 -0
  462. package/src/thread-message-queue.e2e.test.ts +1219 -0
  463. package/src/tools.ts +430 -0
  464. package/src/undici.d.ts +12 -0
  465. package/src/undo-redo.e2e.test.ts +209 -0
  466. package/src/unnest-code-blocks.test.ts +713 -0
  467. package/src/unnest-code-blocks.ts +185 -0
  468. package/src/upgrade.ts +127 -0
  469. package/src/utils.ts +212 -0
  470. package/src/voice-attachment.ts +51 -0
  471. package/src/voice-handler.ts +908 -0
  472. package/src/voice-message.e2e.test.ts +1255 -0
  473. package/src/voice.test.ts +281 -0
  474. package/src/voice.ts +627 -0
  475. package/src/wait-session.ts +147 -0
  476. package/src/websockify.ts +101 -0
  477. package/src/worker-types.ts +64 -0
  478. package/src/worktree-lifecycle.e2e.test.ts +391 -0
  479. package/src/worktree-utils.ts +4 -0
  480. package/src/worktrees.test.ts +223 -0
  481. package/src/worktrees.ts +1294 -0
  482. package/src/xml.test.ts +38 -0
  483. package/src/xml.ts +121 -0
@@ -0,0 +1,30 @@
1
+
2
+ /* !!! This is code generated by Prisma. Do not edit directly. !!! */
3
+ /* eslint-disable */
4
+ // biome-ignore-all lint: generated file
5
+ // @ts-nocheck
6
+ /*
7
+ * This is a barrel export file for all models and their related types.
8
+ *
9
+ * 🟢 You can import this file directly.
10
+ */
11
+ export type * from './models/thread_sessions.js'
12
+ export type * from './models/session_events.js'
13
+ export type * from './models/part_messages.js'
14
+ export type * from './models/bot_tokens.js'
15
+ export type * from './models/channel_directories.js'
16
+ export type * from './models/bot_api_keys.js'
17
+ export type * from './models/thread_worktrees.js'
18
+ export type * from './models/channel_models.js'
19
+ export type * from './models/session_models.js'
20
+ export type * from './models/channel_agents.js'
21
+ export type * from './models/session_agents.js'
22
+ export type * from './models/channel_worktrees.js'
23
+ export type * from './models/channel_verbosity.js'
24
+ export type * from './models/channel_mention_mode.js'
25
+ export type * from './models/global_models.js'
26
+ export type * from './models/scheduled_tasks.js'
27
+ export type * from './models/session_start_sources.js'
28
+ export type * from './models/forum_sync_configs.js'
29
+ export type * from './models/ipc_requests.js'
30
+ export type * from './commonInputTypes.js'
@@ -0,0 +1,152 @@
1
+ // Heap memory monitor and snapshot writer.
2
+ // Periodically checks V8 heap usage and writes gzip-compressed .heapsnapshot.gz
3
+ // files to ~/.kimaki/heap-snapshots/ when memory usage is high.
4
+ // Also exposes writeHeapSnapshot() for on-demand snapshots via SIGUSR1.
5
+ //
6
+ // Snapshots use v8.getHeapSnapshot() streaming API piped through gzip for ~5-10x
7
+ // size reduction (heap snapshots are JSON, so they compress very well).
8
+ //
9
+ // Only active in development (detected by import.meta.filename ending in .ts).
10
+ // In production (compiled .js from npm), the monitor is a no-op to avoid filling
11
+ // user disks with multi-GB snapshot files.
12
+ //
13
+ // Threshold: 85% heap used -> write snapshot for debugging
14
+
15
+ import v8 from 'node:v8'
16
+ import fs from 'node:fs'
17
+ import path from 'node:path'
18
+ import zlib from 'node:zlib'
19
+ import { pipeline } from 'node:stream/promises'
20
+ import { fileURLToPath } from 'node:url'
21
+ import { getDataDir } from './config.js'
22
+ import { createLogger, LogPrefix } from './logger.js'
23
+
24
+ const logger = createLogger(LogPrefix.HEAP)
25
+
26
+ const SNAPSHOT_THRESHOLD = 0.85
27
+ const CHECK_INTERVAL_MS = 30_000
28
+ // After writing a snapshot, wait at least 5 minutes before writing another
29
+ const SNAPSHOT_COOLDOWN_MS = 5 * 60 * 1000
30
+
31
+ // Development detection: if this file is .ts we're running from source (tsx/ts-node).
32
+ // Compiled npm package runs from .js files.
33
+ const isDevelopment = fileURLToPath(import.meta.url).endsWith('.ts')
34
+
35
+ let lastSnapshotTime = 0
36
+ let monitorInterval: ReturnType<typeof setInterval> | null = null
37
+
38
+ function getHeapSnapshotDir(): string {
39
+ return path.join(getDataDir(), 'heap-snapshots')
40
+ }
41
+
42
+ function ensureSnapshotDir(): string {
43
+ const dir = getHeapSnapshotDir()
44
+ if (!fs.existsSync(dir)) {
45
+ fs.mkdirSync(dir, { recursive: true })
46
+ }
47
+ return dir
48
+ }
49
+
50
+ function getHeapStats(): { usedMB: number; limitMB: number; ratio: number } {
51
+ const stats = v8.getHeapStatistics()
52
+ const usedMB = stats.used_heap_size / 1024 / 1024
53
+ const limitMB = stats.heap_size_limit / 1024 / 1024
54
+ const ratio = stats.used_heap_size / stats.heap_size_limit
55
+ return { usedMB, limitMB, ratio }
56
+ }
57
+
58
+ /**
59
+ * Write a gzip-compressed V8 heap snapshot to ~/.kimaki/heap-snapshots/.
60
+ * Uses v8.getHeapSnapshot() streaming API piped through gzip for ~5-10x
61
+ * size reduction compared to v8.writeHeapSnapshot().
62
+ * Filename includes ISO date and current heap size for easy identification.
63
+ * Returns the snapshot file path.
64
+ */
65
+ export async function writeHeapSnapshot(): Promise<string> {
66
+ const dir = ensureSnapshotDir()
67
+ const { usedMB, limitMB, ratio } = getHeapStats()
68
+ const pct = (ratio * 100).toFixed(1)
69
+
70
+ const timestamp = new Date().toISOString().replace(/[:.]/g, '-')
71
+ const filename = `heap-${timestamp}-${Math.round(usedMB)}MB.heapsnapshot.gz`
72
+ const filepath = path.join(dir, filename)
73
+
74
+ logger.log(
75
+ `Writing compressed heap snapshot (${Math.round(usedMB)}MB / ${Math.round(limitMB)}MB, ${pct}%)`,
76
+ )
77
+
78
+ const snapshotStream = v8.getHeapSnapshot()
79
+ const gzipStream = zlib.createGzip({ level: zlib.constants.Z_BEST_SPEED })
80
+ const fileStream = fs.createWriteStream(filepath)
81
+
82
+ await pipeline(snapshotStream, gzipStream, fileStream)
83
+
84
+ const fileSizeMB = (fs.statSync(filepath).size / 1024 / 1024).toFixed(1)
85
+ logger.log(`Snapshot saved: ${filepath} (${fileSizeMB}MB compressed)`)
86
+
87
+ return filepath
88
+ }
89
+
90
+ async function checkHeapUsage(): Promise<void> {
91
+ const { usedMB, limitMB, ratio } = getHeapStats()
92
+ const pct = (ratio * 100).toFixed(1)
93
+
94
+ if (ratio >= SNAPSHOT_THRESHOLD) {
95
+ logger.warn(
96
+ `Heap at ${pct}% (${Math.round(usedMB)}MB / ${Math.round(limitMB)}MB) - exceeds snapshot threshold (${SNAPSHOT_THRESHOLD * 100}%)`,
97
+ )
98
+
99
+ const now = Date.now()
100
+ if (now - lastSnapshotTime >= SNAPSHOT_COOLDOWN_MS) {
101
+ lastSnapshotTime = now
102
+ try {
103
+ await writeHeapSnapshot()
104
+ } catch (e) {
105
+ logger.error(
106
+ 'Failed to write heap snapshot:',
107
+ e instanceof Error ? e.message : String(e),
108
+ )
109
+ }
110
+ } else {
111
+ logger.log('Snapshot cooldown active, skipping')
112
+ }
113
+ }
114
+ }
115
+
116
+ /**
117
+ * Start the periodic heap usage monitor.
118
+ * Checks every 30s and writes snapshots when threshold is exceeded.
119
+ * Only active in development (running from .ts source). In production
120
+ * (compiled .js from npm), this is a no-op to avoid filling user disks.
121
+ */
122
+ export function startHeapMonitor(): void {
123
+ if (!isDevelopment) {
124
+ return
125
+ }
126
+ if (monitorInterval) {
127
+ return
128
+ }
129
+
130
+ // Ensure the snapshot directory exists so V8's --diagnostic-dir has a valid target.
131
+ // Also needed for our own writeHeapSnapshot() calls.
132
+ ensureSnapshotDir()
133
+
134
+ const { usedMB, limitMB, ratio } = getHeapStats()
135
+ logger.log(
136
+ `Heap monitor started (${Math.round(usedMB)}MB / ${Math.round(limitMB)}MB, ${(ratio * 100).toFixed(1)}%) - ` +
137
+ `snapshot at ${SNAPSHOT_THRESHOLD * 100}%`,
138
+ )
139
+
140
+ monitorInterval = setInterval(() => {
141
+ void checkHeapUsage()
142
+ }, CHECK_INTERVAL_MS)
143
+ // Don't prevent process exit
144
+ monitorInterval.unref()
145
+ }
146
+
147
+ export function stopHeapMonitor(): void {
148
+ if (monitorInterval) {
149
+ clearInterval(monitorInterval)
150
+ monitorInterval = null
151
+ }
152
+ }
@@ -0,0 +1,434 @@
1
+ import fs from 'node:fs'
2
+ import http from 'node:http'
3
+ import path from 'node:path'
4
+ import crypto from 'node:crypto'
5
+ import { fileURLToPath } from 'node:url'
6
+ import { describe, test, expect, afterAll } from 'vitest'
7
+ import Database from 'libsql'
8
+ import { PrismaLibSql } from '@prisma/adapter-libsql'
9
+ import { PrismaClient } from './generated/client.js'
10
+ import {
11
+ createLibsqlHandler,
12
+ createLibsqlNodeHandler,
13
+ libsqlExecutor,
14
+ } from 'libsqlproxy'
15
+
16
+ const __filename = fileURLToPath(import.meta.url)
17
+ const __dirname = path.dirname(__filename)
18
+
19
+ async function migrateSchema(prisma: PrismaClient) {
20
+ const schemaPath = path.join(__dirname, '../src/schema.sql')
21
+ const sql = fs.readFileSync(schemaPath, 'utf-8')
22
+ const statements = sql
23
+ .split(';')
24
+ .map((s) =>
25
+ s
26
+ .split('\n')
27
+ .filter((line) => !line.trimStart().startsWith('--'))
28
+ .join('\n')
29
+ .trim(),
30
+ )
31
+ .filter(
32
+ (s) =>
33
+ s.length > 0 &&
34
+ !/^CREATE\s+TABLE\s+["']?sqlite_sequence["']?\s*\(/i.test(s),
35
+ )
36
+ .map((s) =>
37
+ s
38
+ .replace(
39
+ /^CREATE\s+UNIQUE\s+INDEX\b(?!\s+IF)/i,
40
+ 'CREATE UNIQUE INDEX IF NOT EXISTS',
41
+ )
42
+ .replace(/^CREATE\s+INDEX\b(?!\s+IF)/i, 'CREATE INDEX IF NOT EXISTS'),
43
+ )
44
+ for (const statement of statements) {
45
+ await prisma.$executeRawUnsafe(statement)
46
+ }
47
+ }
48
+
49
+ describe('hrana-server', () => {
50
+ let testServer: http.Server | null = null
51
+ let testDb: Database.Database | null = null
52
+ let prisma: PrismaClient | null = null
53
+ const dbPath = path.join(
54
+ process.cwd(),
55
+ `tmp/test-hrana-${crypto.randomUUID().slice(0, 8)}.db`,
56
+ )
57
+
58
+ afterAll(async () => {
59
+ if (prisma) await prisma.$disconnect()
60
+ if (testServer)
61
+ await new Promise<void>((resolve) => {
62
+ testServer!.close(() => {
63
+ resolve()
64
+ })
65
+ })
66
+ if (testDb) testDb.close()
67
+ try {
68
+ fs.unlinkSync(dbPath)
69
+ } catch (e) {
70
+ console.warn('cleanup:', dbPath, (e as Error).message)
71
+ }
72
+ try {
73
+ fs.unlinkSync(dbPath + '-wal')
74
+ } catch (e) {
75
+ console.warn('cleanup:', dbPath + '-wal', (e as Error).message)
76
+ }
77
+ try {
78
+ fs.unlinkSync(dbPath + '-shm')
79
+ } catch (e) {
80
+ console.warn('cleanup:', dbPath + '-shm', (e as Error).message)
81
+ }
82
+ })
83
+
84
+ test('prisma CRUD through hrana server', async () => {
85
+ fs.mkdirSync(path.dirname(dbPath), { recursive: true })
86
+
87
+ const database = new Database(dbPath)
88
+ database.exec('PRAGMA journal_mode = WAL')
89
+ database.exec('PRAGMA busy_timeout = 5000')
90
+ testDb = database
91
+
92
+ const port = 10000 + Math.floor(Math.random() * 50000)
93
+ await new Promise<void>((resolve, reject) => {
94
+ const hranaFetchHandler = createLibsqlHandler(libsqlExecutor(database))
95
+ const hranaNodeHandler = createLibsqlNodeHandler(hranaFetchHandler)
96
+ const srv = http.createServer(hranaNodeHandler)
97
+ srv.on('error', reject)
98
+ srv.listen(port, '127.0.0.1', () => {
99
+ testServer = srv
100
+ resolve()
101
+ })
102
+ })
103
+
104
+ const adapter = new PrismaLibSql({ url: `http://127.0.0.1:${port}` })
105
+ prisma = new PrismaClient({ adapter })
106
+ await migrateSchema(prisma)
107
+
108
+ // Create
109
+ const created = await prisma.thread_sessions.create({
110
+ data: {
111
+ thread_id: 'hrana-test-thread',
112
+ session_id: 'hrana-test-session',
113
+ },
114
+ })
115
+ expect(created.thread_id).toMatchInlineSnapshot(`"hrana-test-thread"`)
116
+ expect(created.session_id).toMatchInlineSnapshot(`"hrana-test-session"`)
117
+
118
+ // Read
119
+ const found = await prisma.thread_sessions.findUnique({
120
+ where: { thread_id: 'hrana-test-thread' },
121
+ })
122
+ expect(found?.session_id).toMatchInlineSnapshot(`"hrana-test-session"`)
123
+
124
+ // Update
125
+ await prisma.thread_sessions.update({
126
+ where: { thread_id: 'hrana-test-thread' },
127
+ data: { session_id: 'updated-session' },
128
+ })
129
+ const updated = await prisma.thread_sessions.findUnique({
130
+ where: { thread_id: 'hrana-test-thread' },
131
+ })
132
+ expect(updated?.session_id).toMatchInlineSnapshot(`"updated-session"`)
133
+
134
+ // Delete
135
+ await prisma.thread_sessions.delete({
136
+ where: { thread_id: 'hrana-test-thread' },
137
+ })
138
+ const deleted = await prisma.thread_sessions.findUnique({
139
+ where: { thread_id: 'hrana-test-thread' },
140
+ })
141
+ expect(deleted).toBeNull()
142
+ }, 30_000)
143
+
144
+ test('$executeRawUnsafe works for PRAGMAs', async () => {
145
+ if (!prisma) throw new Error('prisma not initialized')
146
+ const result = await prisma.$executeRawUnsafe('PRAGMA journal_mode')
147
+ expect(typeof result).toBe('number')
148
+ })
149
+
150
+ test('batch transaction via Prisma $transaction', async () => {
151
+ if (!prisma) throw new Error('prisma not initialized')
152
+
153
+ const [s1, s2] = await prisma.$transaction([
154
+ prisma.thread_sessions.create({
155
+ data: { thread_id: 'batch-1', session_id: 'sess-1' },
156
+ }),
157
+ prisma.thread_sessions.create({
158
+ data: { thread_id: 'batch-2', session_id: 'sess-2' },
159
+ }),
160
+ ])
161
+ expect(s1.thread_id).toMatchInlineSnapshot(`"batch-1"`)
162
+ expect(s2.thread_id).toMatchInlineSnapshot(`"batch-2"`)
163
+
164
+ const count = await prisma.thread_sessions.count({
165
+ where: { thread_id: { in: ['batch-1', 'batch-2'] } },
166
+ })
167
+ expect(count).toBe(2)
168
+
169
+ await prisma.thread_sessions.deleteMany({
170
+ where: { thread_id: { in: ['batch-1', 'batch-2'] } },
171
+ })
172
+ }, 30_000)
173
+
174
+ test('schema migration DDL via $executeRawUnsafe', async () => {
175
+ if (!prisma) throw new Error('prisma not initialized')
176
+
177
+ // CREATE TABLE IF NOT EXISTS is idempotent — running migrateSchema again
178
+ // should not throw even though tables already exist.
179
+ await migrateSchema(prisma)
180
+
181
+ // Verify DDL actually created the tables by querying sqlite_master
182
+ const tables = await prisma.$queryRawUnsafe<Array<{ name: string }>>(
183
+ `SELECT name FROM sqlite_master WHERE type='table' ORDER BY name`,
184
+ )
185
+ const tableNames = tables.map((t) => t.name)
186
+ expect(tableNames).toContain('thread_sessions')
187
+ expect(tableNames).toContain('ipc_requests')
188
+ expect(tableNames).toContain('scheduled_tasks')
189
+
190
+ // Also verify indexes were created
191
+ const indexes = await prisma.$queryRawUnsafe<Array<{ name: string }>>(
192
+ `SELECT name FROM sqlite_master WHERE type='index' AND name LIKE '%idx%' ORDER BY name`,
193
+ )
194
+ const indexNames = indexes.map((i) => i.name)
195
+ expect(indexNames).toContain('ipc_requests_status_created_at_idx')
196
+ expect(indexNames).toContain('scheduled_tasks_status_next_run_at_idx')
197
+
198
+ // Test CREATE INDEX IF NOT EXISTS is also idempotent
199
+ await prisma.$executeRawUnsafe(
200
+ `CREATE INDEX IF NOT EXISTS "ipc_requests_status_created_at_idx" ON "ipc_requests"("status", "created_at")`,
201
+ )
202
+ })
203
+
204
+ test('concurrent queries via Promise.all', async () => {
205
+ if (!prisma) throw new Error('prisma not initialized')
206
+
207
+ // Seed some data for concurrent reads
208
+ const threads = Array.from({ length: 5 }, (_, i) => ({
209
+ thread_id: `concurrent-${i}`,
210
+ session_id: `sess-concurrent-${i}`,
211
+ }))
212
+ for (const t of threads) {
213
+ await prisma.thread_sessions.create({ data: t })
214
+ }
215
+
216
+ // Simulate kimaki's pattern of parallel Prisma queries
217
+ const [allThreads, count, single, filtered] = await Promise.all([
218
+ prisma.thread_sessions.findMany({
219
+ where: { thread_id: { startsWith: 'concurrent-' } },
220
+ orderBy: { thread_id: 'asc' },
221
+ }),
222
+ prisma.thread_sessions.count({
223
+ where: { thread_id: { startsWith: 'concurrent-' } },
224
+ }),
225
+ prisma.thread_sessions.findUnique({
226
+ where: { thread_id: 'concurrent-2' },
227
+ }),
228
+ prisma.thread_sessions.findMany({
229
+ where: { thread_id: { in: ['concurrent-0', 'concurrent-4'] } },
230
+ orderBy: { thread_id: 'asc' },
231
+ }),
232
+ ])
233
+
234
+ expect(allThreads.length).toBe(5)
235
+ expect(count).toBe(5)
236
+ expect(single?.session_id).toMatchInlineSnapshot(`"sess-concurrent-2"`)
237
+ expect(filtered.map((f) => f.thread_id)).toMatchInlineSnapshot(`
238
+ [
239
+ "concurrent-0",
240
+ "concurrent-4",
241
+ ]
242
+ `)
243
+
244
+ // Cleanup
245
+ await prisma.thread_sessions.deleteMany({
246
+ where: { thread_id: { startsWith: 'concurrent-' } },
247
+ })
248
+ }, 30_000)
249
+
250
+ test('$queryRawUnsafe for PRAGMAs that return values', async () => {
251
+ if (!prisma) throw new Error('prisma not initialized')
252
+
253
+ // PRAGMA that returns a value — journal_mode should be WAL
254
+ const journalMode = await prisma.$queryRawUnsafe<
255
+ Array<{ journal_mode: string }>
256
+ >('PRAGMA journal_mode')
257
+ expect(journalMode[0]?.journal_mode).toMatchInlineSnapshot(`"wal"`)
258
+
259
+ // PRAGMA busy_timeout returns the current timeout value
260
+ const busyTimeout = await prisma.$queryRawUnsafe<
261
+ Array<{ busy_timeout: number }>
262
+ >('PRAGMA busy_timeout')
263
+ expect(busyTimeout[0]?.busy_timeout).toMatchInlineSnapshot(`undefined`)
264
+
265
+ // PRAGMA table_info returns column metadata
266
+ const tableInfo = await prisma.$queryRawUnsafe<
267
+ Array<{ name: string; type: string }>
268
+ >(`PRAGMA table_info('ipc_requests')`)
269
+ const colNames = tableInfo.map((c) => c.name)
270
+ expect(colNames).toMatchInlineSnapshot(`
271
+ [
272
+ "id",
273
+ "type",
274
+ "session_id",
275
+ "thread_id",
276
+ "payload",
277
+ "response",
278
+ "status",
279
+ "created_at",
280
+ "updated_at",
281
+ ]
282
+ `)
283
+ })
284
+
285
+ test('updateMany with complex WHERE using in operator', async () => {
286
+ if (!prisma) throw new Error('prisma not initialized')
287
+
288
+ // Seed: create a thread + multiple IPC requests in different statuses
289
+ // (mirrors kimaki's cancelAllPendingIpcRequests pattern)
290
+ await prisma.thread_sessions.create({
291
+ data: { thread_id: 'ipc-test-thread', session_id: 'ipc-test-session' },
292
+ })
293
+ const statuses = ['pending', 'pending', 'processing', 'completed'] as const
294
+ for (let i = 0; i < statuses.length; i++) {
295
+ await prisma.ipc_requests.create({
296
+ data: {
297
+ id: `ipc-req-${i}`,
298
+ type: 'file_upload',
299
+ session_id: 'ipc-test-session',
300
+ thread_id: 'ipc-test-thread',
301
+ payload: JSON.stringify({ prompt: `test-${i}` }),
302
+ status: statuses[i],
303
+ },
304
+ })
305
+ }
306
+
307
+ // updateMany with WHERE status IN ['pending', 'processing']
308
+ const result = await prisma.ipc_requests.updateMany({
309
+ where: { status: { in: ['pending', 'processing'] } },
310
+ data: {
311
+ status: 'cancelled',
312
+ response: JSON.stringify({ error: 'Bot shutting down' }),
313
+ },
314
+ })
315
+ expect(result.count).toBe(3)
316
+
317
+ // Verify: only 'completed' row is untouched
318
+ const remaining = await prisma.ipc_requests.findMany({
319
+ where: { thread_id: 'ipc-test-thread' },
320
+ orderBy: { id: 'asc' },
321
+ select: { id: true, status: true },
322
+ })
323
+ expect(remaining).toMatchInlineSnapshot(`
324
+ [
325
+ {
326
+ "id": "ipc-req-0",
327
+ "status": "cancelled",
328
+ },
329
+ {
330
+ "id": "ipc-req-1",
331
+ "status": "cancelled",
332
+ },
333
+ {
334
+ "id": "ipc-req-2",
335
+ "status": "cancelled",
336
+ },
337
+ {
338
+ "id": "ipc-req-3",
339
+ "status": "completed",
340
+ },
341
+ ]
342
+ `)
343
+
344
+ // Cleanup
345
+ await prisma.ipc_requests.deleteMany({
346
+ where: { thread_id: 'ipc-test-thread' },
347
+ })
348
+ await prisma.thread_sessions.delete({
349
+ where: { thread_id: 'ipc-test-thread' },
350
+ })
351
+ }, 30_000)
352
+
353
+ test('interactive $transaction (callback form)', async () => {
354
+ if (!prisma) throw new Error('prisma not initialized')
355
+
356
+ // Interactive transaction: reads and writes within the same tx callback.
357
+ // This exercises BEGIN/queries/COMMIT across multiple hrana pipeline
358
+ // requests with batons (stream continuity).
359
+ const result = await prisma.$transaction(async (tx) => {
360
+ await tx.thread_sessions.create({
361
+ data: { thread_id: 'tx-interactive-1', session_id: 'sess-tx-1' },
362
+ })
363
+ await tx.thread_sessions.create({
364
+ data: { thread_id: 'tx-interactive-2', session_id: 'sess-tx-2' },
365
+ })
366
+
367
+ // Read inside the same transaction — should see uncommitted rows
368
+ const count = await tx.thread_sessions.count({
369
+ where: { thread_id: { startsWith: 'tx-interactive-' } },
370
+ })
371
+
372
+ // Conditional write based on read
373
+ if (count === 2) {
374
+ await tx.thread_sessions.update({
375
+ where: { thread_id: 'tx-interactive-1' },
376
+ data: { session_id: 'sess-tx-1-updated' },
377
+ })
378
+ }
379
+
380
+ return tx.thread_sessions.findMany({
381
+ where: { thread_id: { startsWith: 'tx-interactive-' } },
382
+ orderBy: { thread_id: 'asc' },
383
+ select: { thread_id: true, session_id: true },
384
+ })
385
+ })
386
+
387
+ expect(result).toMatchInlineSnapshot(`
388
+ [
389
+ {
390
+ "session_id": "sess-tx-1-updated",
391
+ "thread_id": "tx-interactive-1",
392
+ },
393
+ {
394
+ "session_id": "sess-tx-2",
395
+ "thread_id": "tx-interactive-2",
396
+ },
397
+ ]
398
+ `)
399
+
400
+ // Verify committed outside transaction
401
+ const outside = await prisma.thread_sessions.count({
402
+ where: { thread_id: { startsWith: 'tx-interactive-' } },
403
+ })
404
+ expect(outside).toBe(2)
405
+
406
+ // Cleanup
407
+ await prisma.thread_sessions.deleteMany({
408
+ where: { thread_id: { startsWith: 'tx-interactive-' } },
409
+ })
410
+ }, 30_000)
411
+
412
+ test('interactive $transaction rolls back on error', async () => {
413
+ if (!prisma) throw new Error('prisma not initialized')
414
+
415
+ // Verify rollback: if the callback throws, no rows should be committed
416
+ const txError = await prisma
417
+ .$transaction(async (tx) => {
418
+ await tx.thread_sessions.create({
419
+ data: { thread_id: 'tx-rollback-1', session_id: 'sess-rollback' },
420
+ })
421
+ throw new Error('intentional rollback')
422
+ })
423
+ .catch((e: Error) => e)
424
+
425
+ expect(txError).toBeInstanceOf(Error)
426
+ expect((txError as Error).message).toContain('intentional rollback')
427
+
428
+ // Row should NOT exist — transaction was rolled back
429
+ const ghost = await prisma.thread_sessions.findUnique({
430
+ where: { thread_id: 'tx-rollback-1' },
431
+ })
432
+ expect(ghost).toBeNull()
433
+ }, 30_000)
434
+ })