@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
package/src/errors.ts ADDED
@@ -0,0 +1,201 @@
1
+ // TaggedError definitions for type-safe error handling with errore.
2
+ // Errors are grouped by category: infrastructure, domain, and validation.
3
+ // Use errore.matchError() for exhaustive error handling in command handlers.
4
+
5
+ import { AbortError, createTaggedError } from 'errore'
6
+
7
+ // ═══════════════════════════════════════════════════════════════════════════
8
+ // INFRASTRUCTURE ERRORS - Server, filesystem, external services
9
+ // ═══════════════════════════════════════════════════════════════════════════
10
+
11
+ export class DirectoryNotAccessibleError extends createTaggedError({
12
+ name: 'DirectoryNotAccessibleError',
13
+ message: 'Directory does not exist or is not accessible: $directory',
14
+ }) {}
15
+
16
+ export class ServerStartError extends createTaggedError({
17
+ name: 'ServerStartError',
18
+ message: 'Server failed to start on port $port: $reason',
19
+ }) {}
20
+
21
+ export class ServerNotReadyError extends createTaggedError({
22
+ name: 'ServerNotReadyError',
23
+ message:
24
+ 'OpenCode client for directory "$directory" is not available because the shared server is not ready',
25
+ }) {}
26
+
27
+ export class ApiKeyMissingError extends createTaggedError({
28
+ name: 'ApiKeyMissingError',
29
+ message: '$service API key is required',
30
+ }) {}
31
+
32
+ // ═══════════════════════════════════════════════════════════════════════════
33
+ // ABORT ERRORS - Session cancellation with typed reasons
34
+ // ═══════════════════════════════════════════════════════════════════════════
35
+
36
+ // Extends errore.AbortError so errore.isAbortError() detects it in cause chains.
37
+ // Use reason field instead of string matching to identify abort cause.
38
+ export class SessionAbortError extends createTaggedError({
39
+ name: 'SessionAbortError',
40
+ message: 'Session aborted: $reason',
41
+ extends: AbortError,
42
+ }) {}
43
+
44
+ // ═══════════════════════════════════════════════════════════════════════════
45
+ // DOMAIN ERRORS - Sessions, messages, transcription
46
+ // ═══════════════════════════════════════════════════════════════════════════
47
+
48
+ export class SessionNotFoundError extends createTaggedError({
49
+ name: 'SessionNotFoundError',
50
+ message: 'Session $sessionId not found',
51
+ }) {}
52
+
53
+ export class SessionCreateError extends createTaggedError({
54
+ name: 'SessionCreateError',
55
+ }) {}
56
+
57
+ export class MessagesNotFoundError extends createTaggedError({
58
+ name: 'MessagesNotFoundError',
59
+ message: 'No messages found for session $sessionId',
60
+ }) {}
61
+
62
+ export class TranscriptionError extends createTaggedError({
63
+ name: 'TranscriptionError',
64
+ message: 'Transcription failed: $reason',
65
+ }) {}
66
+
67
+ export class GrepSearchError extends createTaggedError({
68
+ name: 'GrepSearchError',
69
+ message: 'Grep search failed for pattern: $pattern',
70
+ }) {}
71
+
72
+ export class GlobSearchError extends createTaggedError({
73
+ name: 'GlobSearchError',
74
+ message: 'Glob search failed for pattern: $pattern',
75
+ }) {}
76
+
77
+ // ═══════════════════════════════════════════════════════════════════════════
78
+ // VALIDATION ERRORS - Input validation, format checks
79
+ // ═══════════════════════════════════════════════════════════════════════════
80
+
81
+ export class InvalidAudioFormatError extends createTaggedError({
82
+ name: 'InvalidAudioFormatError',
83
+ message: 'Invalid audio format',
84
+ }) {}
85
+
86
+ export class EmptyTranscriptionError extends createTaggedError({
87
+ name: 'EmptyTranscriptionError',
88
+ message: 'Model returned empty transcription',
89
+ }) {}
90
+
91
+ export class NoResponseContentError extends createTaggedError({
92
+ name: 'NoResponseContentError',
93
+ message: 'No response content from model',
94
+ }) {}
95
+
96
+ export class NoToolResponseError extends createTaggedError({
97
+ name: 'NoToolResponseError',
98
+ message: 'No valid tool responses',
99
+ }) {}
100
+
101
+ // ═══════════════════════════════════════════════════════════════════════════
102
+ // NETWORK ERRORS - Fetch and HTTP
103
+ // ═══════════════════════════════════════════════════════════════════════════
104
+
105
+ export class FetchError extends createTaggedError({
106
+ name: 'FetchError',
107
+ message: 'Fetch failed for $url',
108
+ }) {}
109
+
110
+ // ═══════════════════════════════════════════════════════════════════════════
111
+ // API ERRORS - External service responses
112
+ // ═══════════════════════════════════════════════════════════════════════════
113
+
114
+ export class DiscordApiError extends createTaggedError({
115
+ name: 'DiscordApiError',
116
+ message: 'Discord API error: $status $body',
117
+ }) {}
118
+
119
+ export class OpenCodeApiError extends createTaggedError({
120
+ name: 'OpenCodeApiError',
121
+ message: 'OpenCode API error ($status): $body',
122
+ }) {}
123
+
124
+ // ═══════════════════════════════════════════════════════════════════════════
125
+ // MERGE/WORKTREE ERRORS
126
+ // ═══════════════════════════════════════════════════════════════════════════
127
+
128
+ export class DirtyWorktreeError extends createTaggedError({
129
+ name: 'DirtyWorktreeError',
130
+ message:
131
+ 'Uncommitted changes in worktree. Commit all changes before merging.',
132
+ }) {}
133
+
134
+ export class NothingToMergeError extends createTaggedError({
135
+ name: 'NothingToMergeError',
136
+ message: 'No commits to merge -- branch is already up to date with $target',
137
+ }) {}
138
+
139
+ export class RebaseConflictError extends createTaggedError({
140
+ name: 'RebaseConflictError',
141
+ message:
142
+ 'Rebase conflict while rebasing onto $target. Resolve conflicts, then run merge again.',
143
+ }) {}
144
+
145
+ export class RebaseError extends createTaggedError({
146
+ name: 'RebaseError',
147
+ message: 'Rebase onto $target failed',
148
+ }) {}
149
+
150
+ export class NotFastForwardError extends createTaggedError({
151
+ name: 'NotFastForwardError',
152
+ message: 'Cannot fast-forward: $target has commits not in this branch',
153
+ }) {}
154
+
155
+ export class ConflictingFilesError extends createTaggedError({
156
+ name: 'ConflictingFilesError',
157
+ message:
158
+ 'Cannot merge: $target worktree has uncommitted changes in overlapping files. Commit changes in main worktree first, then run `/merge-worktree` again.',
159
+ }) {}
160
+
161
+ export class PushError extends createTaggedError({
162
+ name: 'PushError',
163
+ message: 'Push to $target failed',
164
+ }) {}
165
+
166
+ export class GitCommandError extends createTaggedError({
167
+ name: 'GitCommandError',
168
+ message: 'Git command failed: $command',
169
+ }) {}
170
+
171
+ // ═══════════════════════════════════════════════════════════════════════════
172
+ // UNION TYPES - For function signatures
173
+ // ═══════════════════════════════════════════════════════════════════════════
174
+
175
+ export type TranscriptionErrors =
176
+ | ApiKeyMissingError
177
+ | InvalidAudioFormatError
178
+ | TranscriptionError
179
+ | EmptyTranscriptionError
180
+ | NoResponseContentError
181
+ | NoToolResponseError
182
+
183
+ export type OpenCodeErrors =
184
+ | DirectoryNotAccessibleError
185
+ | ServerStartError
186
+ | ServerNotReadyError
187
+
188
+ export type SessionErrors =
189
+ | SessionNotFoundError
190
+ | MessagesNotFoundError
191
+ | OpenCodeApiError
192
+
193
+ export type MergeWorktreeErrors =
194
+ | DirtyWorktreeError
195
+ | NothingToMergeError
196
+ | RebaseConflictError
197
+ | RebaseError
198
+ | NotFastForwardError
199
+ | ConflictingFilesError
200
+ | PushError
201
+ | GitCommandError
@@ -0,0 +1,469 @@
1
+ import { test, expect } from 'vitest'
2
+ import { Lexer } from 'marked'
3
+ import {
4
+ escapeBackticksInCodeBlocks,
5
+ splitMarkdownForDiscord,
6
+ } from './discord-utils.js'
7
+
8
+ test('escapes single backticks in code blocks', () => {
9
+ const input = '```js\nconst x = `hello`\n```'
10
+ const result = escapeBackticksInCodeBlocks(input)
11
+
12
+ expect(result).toMatchInlineSnapshot(`
13
+ "\`\`\`js
14
+ const x = \\\`hello\\\`
15
+ \`\`\`
16
+ "
17
+ `)
18
+ })
19
+
20
+ test('escapes backticks in code blocks with language', () => {
21
+ const input =
22
+ '```typescript\nconst greeting = `Hello, ${name}!`\nconst inline = `test`\n```'
23
+ const result = escapeBackticksInCodeBlocks(input)
24
+
25
+ expect(result).toMatchInlineSnapshot(`
26
+ "\`\`\`typescript
27
+ const greeting = \\\`Hello, \${name}!\\\`
28
+ const inline = \\\`test\\\`
29
+ \`\`\`
30
+ "
31
+ `)
32
+ })
33
+
34
+ test('does not escape backticks outside code blocks', () => {
35
+ const input =
36
+ 'This is `inline code` and this is a code block:\n```\nconst x = `template`\n```'
37
+ const result = escapeBackticksInCodeBlocks(input)
38
+
39
+ expect(result).toMatchInlineSnapshot(`
40
+ "This is \`inline code\` and this is a code block:
41
+ \`\`\`
42
+ const x = \\\`template\\\`
43
+ \`\`\`
44
+ "
45
+ `)
46
+ })
47
+
48
+ test('handles multiple code blocks', () => {
49
+ const input = `First block:
50
+ \`\`\`js
51
+ const a = \`test\`
52
+ \`\`\`
53
+
54
+ Some text with \`inline\` code
55
+
56
+ Second block:
57
+ \`\`\`python
58
+ name = f\`hello {world}\`
59
+ \`\`\``
60
+
61
+ const result = escapeBackticksInCodeBlocks(input)
62
+
63
+ expect(result).toMatchInlineSnapshot(`
64
+ "First block:
65
+ \`\`\`js
66
+ const a = \\\`test\\\`
67
+ \`\`\`
68
+
69
+
70
+ Some text with \`inline\` code
71
+
72
+ Second block:
73
+ \`\`\`python
74
+ name = f\\\`hello {world}\\\`
75
+ \`\`\`
76
+ "
77
+ `)
78
+ })
79
+
80
+ test('handles code blocks without language', () => {
81
+ const input = '```\nconst x = `value`\n```'
82
+ const result = escapeBackticksInCodeBlocks(input)
83
+
84
+ expect(result).toMatchInlineSnapshot(`
85
+ "\`\`\`
86
+ const x = \\\`value\\\`
87
+ \`\`\`
88
+ "
89
+ `)
90
+ })
91
+
92
+ test('handles nested backticks in code blocks', () => {
93
+ const input = '```js\nconst nested = `outer ${`inner`} text`\n```'
94
+ const result = escapeBackticksInCodeBlocks(input)
95
+
96
+ expect(result).toMatchInlineSnapshot(`
97
+ "\`\`\`js
98
+ const nested = \\\`outer \${\\\`inner\\\`} text\\\`
99
+ \`\`\`
100
+ "
101
+ `)
102
+ })
103
+
104
+ test('preserves markdown outside code blocks', () => {
105
+ const input = `# Heading
106
+
107
+ This is **bold** and *italic* text
108
+
109
+ \`\`\`js
110
+ const code = \`with template\`
111
+ \`\`\`
112
+
113
+ - List item 1
114
+ - List item 2`
115
+
116
+ const result = escapeBackticksInCodeBlocks(input)
117
+
118
+ expect(result).toMatchInlineSnapshot(`
119
+ "# Heading
120
+
121
+ This is **bold** and *italic* text
122
+
123
+ \`\`\`js
124
+ const code = \\\`with template\\\`
125
+ \`\`\`
126
+
127
+
128
+ - List item 1
129
+ - List item 2"
130
+ `)
131
+ })
132
+
133
+ test('does not escape code block delimiter backticks', () => {
134
+ const input = '```js\nconst x = `hello`\n```'
135
+ const result = escapeBackticksInCodeBlocks(input)
136
+
137
+ expect(result.startsWith('```')).toBe(true)
138
+ expect(result.endsWith('```\n')).toBe(true)
139
+ expect(result).toContain('\\`hello\\`')
140
+ expect(result).not.toContain('\\`\\`\\`js')
141
+ expect(result).not.toContain('\\`\\`\\`\n')
142
+
143
+ expect(result).toMatchInlineSnapshot(`
144
+ "\`\`\`js
145
+ const x = \\\`hello\\\`
146
+ \`\`\`
147
+ "
148
+ `)
149
+ })
150
+
151
+ test('splitMarkdownForDiscord returns single chunk for short content', () => {
152
+ const result = splitMarkdownForDiscord({
153
+ content: 'Hello world',
154
+ maxLength: 100,
155
+ })
156
+ expect(result).toMatchInlineSnapshot(`
157
+ [
158
+ "Hello world",
159
+ ]
160
+ `)
161
+ })
162
+
163
+ test('splitMarkdownForDiscord splits at line boundaries', () => {
164
+ const result = splitMarkdownForDiscord({
165
+ content: 'Line 1\nLine 2\nLine 3\nLine 4',
166
+ maxLength: 15,
167
+ })
168
+ expect(result).toMatchInlineSnapshot(`
169
+ [
170
+ "Line 1
171
+ Line 2
172
+ ",
173
+ "Line 3
174
+ Line 4",
175
+ ]
176
+ `)
177
+ })
178
+
179
+ test('splitMarkdownForDiscord preserves code blocks when not split', () => {
180
+ const result = splitMarkdownForDiscord({
181
+ content: '```js\nconst x = 1\n```',
182
+ maxLength: 100,
183
+ })
184
+ expect(result).toMatchInlineSnapshot(`
185
+ [
186
+ "\`\`\`js
187
+ const x = 1
188
+ \`\`\`",
189
+ ]
190
+ `)
191
+ })
192
+
193
+ test('splitMarkdownForDiscord adds closing and opening fences when splitting code block', () => {
194
+ const result = splitMarkdownForDiscord({
195
+ content: '```js\nline1\nline2\nline3\nline4\n```',
196
+ maxLength: 20,
197
+ })
198
+ expect(result).toMatchInlineSnapshot(`
199
+ [
200
+ "\`\`\`js
201
+ line1
202
+ \`\`\`
203
+ ",
204
+ "\`\`\`js
205
+ line2
206
+ \`\`\`
207
+ ",
208
+ "\`\`\`js
209
+ line3
210
+ \`\`\`
211
+ ",
212
+ "\`\`\`js
213
+ line4
214
+ \`\`\`
215
+ ",
216
+ ]
217
+ `)
218
+ })
219
+
220
+ test('splitMarkdownForDiscord handles code block with language', () => {
221
+ const result = splitMarkdownForDiscord({
222
+ content: '```typescript\nconst a = 1\nconst b = 2\n```',
223
+ maxLength: 30,
224
+ })
225
+ expect(result).toMatchInlineSnapshot(`
226
+ [
227
+ "\`\`\`typescript
228
+ const a = 1
229
+ \`\`\`
230
+ ",
231
+ "\`\`\`typescript
232
+ const b = 2
233
+ \`\`\`
234
+ ",
235
+ ]
236
+ `)
237
+ })
238
+
239
+ test('splitMarkdownForDiscord handles mixed content with code blocks', () => {
240
+ const result = splitMarkdownForDiscord({
241
+ content: 'Text before\n```js\ncode\n```\nText after',
242
+ maxLength: 25,
243
+ })
244
+ expect(result).toMatchInlineSnapshot(`
245
+ [
246
+ "Text before
247
+ \`\`\`js
248
+ \`\`\`
249
+ ",
250
+ "\`\`\`js
251
+ code
252
+ \`\`\`
253
+ Text after",
254
+ ]
255
+ `)
256
+ })
257
+
258
+ test('splitMarkdownForDiscord handles code block without language', () => {
259
+ const result = splitMarkdownForDiscord({
260
+ content: '```\nline1\nline2\n```',
261
+ maxLength: 12,
262
+ })
263
+ expect(result).toMatchInlineSnapshot(`
264
+ [
265
+ "\`\`\`
266
+ \`\`\`
267
+ ",
268
+ "\`\`\`
269
+ line1
270
+ \`\`\`
271
+ ",
272
+ "\`\`\`
273
+ line2
274
+ \`\`\`
275
+ ",
276
+ ]
277
+ `)
278
+ })
279
+
280
+ test('splitMarkdownForDiscord handles multiple consecutive code blocks', () => {
281
+ const result = splitMarkdownForDiscord({
282
+ content: '```js\nfoo\n```\n```py\nbar\n```',
283
+ maxLength: 20,
284
+ })
285
+ expect(result).toMatchInlineSnapshot(`
286
+ [
287
+ "\`\`\`js
288
+ foo
289
+ \`\`\`
290
+ \`\`\`py
291
+ \`\`\`
292
+ ",
293
+ "\`\`\`py
294
+ bar
295
+ \`\`\`
296
+ ",
297
+ ]
298
+ `)
299
+ })
300
+
301
+ test('splitMarkdownForDiscord handles empty code block', () => {
302
+ const result = splitMarkdownForDiscord({
303
+ content: 'before\n```\n```\nafter',
304
+ maxLength: 50,
305
+ })
306
+ expect(result).toMatchInlineSnapshot(`
307
+ [
308
+ "before
309
+ \`\`\`
310
+ \`\`\`
311
+ after",
312
+ ]
313
+ `)
314
+ })
315
+
316
+ test('splitMarkdownForDiscord handles content exactly at maxLength', () => {
317
+ const result = splitMarkdownForDiscord({
318
+ content: '12345678901234567890',
319
+ maxLength: 20,
320
+ })
321
+ expect(result).toMatchInlineSnapshot(`
322
+ [
323
+ "12345678901234567890",
324
+ ]
325
+ `)
326
+ })
327
+
328
+ test('splitMarkdownForDiscord handles code block only', () => {
329
+ const result = splitMarkdownForDiscord({
330
+ content: '```ts\nconst x = 1\n```',
331
+ maxLength: 15,
332
+ })
333
+ expect(result).toMatchInlineSnapshot(`
334
+ [
335
+ "\`\`\`ts
336
+ \`\`\`
337
+ ",
338
+ "\`\`\`ts
339
+ const x = 1
340
+ \`\`\`
341
+ ",
342
+ ]
343
+ `)
344
+ })
345
+
346
+ test('splitMarkdownForDiscord handles code block at start with text after', () => {
347
+ const result = splitMarkdownForDiscord({
348
+ content: '```js\ncode\n```\nSome text after',
349
+ maxLength: 20,
350
+ })
351
+ expect(result).toMatchInlineSnapshot(`
352
+ [
353
+ "\`\`\`js
354
+ code
355
+ \`\`\`
356
+ ",
357
+ "Some text after",
358
+ ]
359
+ `)
360
+ })
361
+
362
+ test('splitMarkdownForDiscord handles text before code block at end', () => {
363
+ const result = splitMarkdownForDiscord({
364
+ content: 'Some text before\n```js\ncode\n```',
365
+ maxLength: 25,
366
+ })
367
+ expect(result).toMatchInlineSnapshot(`
368
+ [
369
+ "Some text before
370
+ \`\`\`js
371
+ \`\`\`
372
+ ",
373
+ "\`\`\`js
374
+ code
375
+ \`\`\`
376
+ ",
377
+ ]
378
+ `)
379
+ })
380
+
381
+ test('splitMarkdownForDiscord handles very long line inside code block', () => {
382
+ const result = splitMarkdownForDiscord({
383
+ content: '```js\nshort\nveryverylonglinethatexceedsmaxlength\nshort\n```',
384
+ maxLength: 25,
385
+ })
386
+ expect(result).toMatchInlineSnapshot(`
387
+ [
388
+ "\`\`\`js
389
+ short
390
+ \`\`\`
391
+ ",
392
+ "\`\`\`js
393
+ veryverylo\`\`\`
394
+ ",
395
+ "\`\`\`js
396
+ nglinethat\`\`\`
397
+ ",
398
+ "\`\`\`js
399
+ exceedsmax\`\`\`
400
+ ",
401
+ "\`\`\`js
402
+ length
403
+ \`\`\`
404
+ ",
405
+ "short
406
+ \`\`\`
407
+ ",
408
+ ]
409
+ `)
410
+ })
411
+
412
+ test('splitMarkdownForDiscord handles realistic long markdown with code block', () => {
413
+ const content = `Here is some explanation text before the code.
414
+
415
+ \`\`\`typescript
416
+ export function calculateTotal(items: Item[]): number {
417
+ let total = 0
418
+ for (const item of items) {
419
+ total += item.price * item.quantity
420
+ }
421
+ return total
422
+ }
423
+
424
+ export function formatCurrency(amount: number): string {
425
+ return new Intl.NumberFormat('en-US', {
426
+ style: 'currency',
427
+ currency: 'USD',
428
+ }).format(amount)
429
+ }
430
+ \`\`\`
431
+
432
+ And here is some text after the code block.`
433
+
434
+ const result = splitMarkdownForDiscord({
435
+ content,
436
+ maxLength: 200,
437
+ })
438
+ expect(result).toMatchInlineSnapshot(`
439
+ [
440
+ "Here is some explanation text before the code.
441
+
442
+ \`\`\`typescript
443
+ export function calculateTotal(items: Item[]): number {
444
+ let total = 0
445
+ for (const item of items) {
446
+ \`\`\`
447
+ ",
448
+ "\`\`\`typescript
449
+ total += item.price * item.quantity
450
+ }
451
+ return total
452
+ }
453
+
454
+ export function formatCurrency(amount: number): string {
455
+ return new Intl.NumberFormat('en-US', {
456
+ \`\`\`
457
+ ",
458
+ "\`\`\`typescript
459
+ style: 'currency',
460
+ currency: 'USD',
461
+ }).format(amount)
462
+ }
463
+ \`\`\`
464
+
465
+
466
+ And here is some text after the code block.",
467
+ ]
468
+ `)
469
+ })