@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,713 @@
1
+ import { test, expect } from 'vitest'
2
+ import { unnestCodeBlocksFromLists } from './unnest-code-blocks.js'
3
+
4
+ // Discord markdown quirks (as of 2026-02):
5
+ // - Fenced code blocks nested inside list indentation often don't render at all.
6
+ // (Discord effectively wants fences at the start of the line.)
7
+ // - If a list line is accidentally concatenated with the next list marker or a fence,
8
+ // e.g. `**Title**- bullet` or `Text:```ts`, Discord won't parse it as a list/code block.
9
+ // These tests lock down that `unnestCodeBlocksFromLists()` produces Discord-friendly output.
10
+
11
+ test('basic - single item with code block', () => {
12
+ const input = `- Item 1
13
+ \`\`\`js
14
+ const x = 1
15
+ \`\`\``
16
+ const result = unnestCodeBlocksFromLists(input)
17
+ expect(result).toMatchInlineSnapshot(`
18
+ "- Item 1
19
+
20
+ \`\`\`js
21
+ const x = 1
22
+ \`\`\`"
23
+ `)
24
+ })
25
+
26
+ test('multiple items - code in middle item only', () => {
27
+ const input = `- Item 1
28
+ - Item 2
29
+ \`\`\`js
30
+ const x = 1
31
+ \`\`\`
32
+ - Item 3`
33
+ const result = unnestCodeBlocksFromLists(input)
34
+ expect(result).toMatchInlineSnapshot(`
35
+ "- Item 1
36
+ - Item 2
37
+
38
+ \`\`\`js
39
+ const x = 1
40
+ \`\`\`
41
+ - Item 3"
42
+ `)
43
+ })
44
+
45
+ test('multiple code blocks in one item', () => {
46
+ const input = `- Item with two code blocks
47
+ \`\`\`js
48
+ const a = 1
49
+ \`\`\`
50
+ \`\`\`python
51
+ b = 2
52
+ \`\`\``
53
+ const result = unnestCodeBlocksFromLists(input)
54
+ expect(result).toMatchInlineSnapshot(`
55
+ "- Item with two code blocks
56
+
57
+ \`\`\`js
58
+ const a = 1
59
+ \`\`\`
60
+ \`\`\`python
61
+ b = 2
62
+ \`\`\`"
63
+ `)
64
+ })
65
+
66
+ test('nested list with code', () => {
67
+ const input = `- Item 1
68
+ - Nested item
69
+ \`\`\`js
70
+ const x = 1
71
+ \`\`\`
72
+ - Item 2`
73
+ const result = unnestCodeBlocksFromLists(input)
74
+ expect(result).toMatchInlineSnapshot(`
75
+ "- Item 1
76
+ - Nested item
77
+
78
+ \`\`\`js
79
+ const x = 1
80
+ \`\`\`
81
+ - Item 2"
82
+ `)
83
+ })
84
+
85
+ test('ordered list preserves numbering', () => {
86
+ const input = `1. First item
87
+ \`\`\`js
88
+ const a = 1
89
+ \`\`\`
90
+ 2. Second item
91
+ 3. Third item`
92
+ const result = unnestCodeBlocksFromLists(input)
93
+ expect(result).toMatchInlineSnapshot(`
94
+ "1. First item
95
+
96
+ \`\`\`js
97
+ const a = 1
98
+ \`\`\`
99
+ 2. Second item
100
+ 3. Third item"
101
+ `)
102
+ })
103
+
104
+ test('list without code blocks unchanged', () => {
105
+ const input = `- Item 1
106
+ - Item 2
107
+ - Item 3`
108
+ const result = unnestCodeBlocksFromLists(input)
109
+ expect(result).toMatchInlineSnapshot(`
110
+ "- Item 1
111
+ - Item 2
112
+ - Item 3"
113
+ `)
114
+ })
115
+
116
+ test('mixed - some items have code, some dont', () => {
117
+ const input = `- Normal item
118
+ - Item with code
119
+ \`\`\`js
120
+ const x = 1
121
+ \`\`\`
122
+ - Another normal item
123
+ - Another with code
124
+ \`\`\`python
125
+ y = 2
126
+ \`\`\``
127
+ const result = unnestCodeBlocksFromLists(input)
128
+ expect(result).toMatchInlineSnapshot(`
129
+ "- Normal item
130
+ - Item with code
131
+
132
+ \`\`\`js
133
+ const x = 1
134
+ \`\`\`
135
+ - Another normal item
136
+ - Another with code
137
+
138
+ \`\`\`python
139
+ y = 2
140
+ \`\`\`"
141
+ `)
142
+ })
143
+
144
+ test('text before and after code in same item', () => {
145
+ const input = `- Start text
146
+ \`\`\`js
147
+ const x = 1
148
+ \`\`\`
149
+ End text`
150
+ const result = unnestCodeBlocksFromLists(input)
151
+ expect(result).toMatchInlineSnapshot(`
152
+ "- Start text
153
+
154
+ \`\`\`js
155
+ const x = 1
156
+ \`\`\`
157
+ - End text"
158
+ `)
159
+ })
160
+
161
+ test('preserves content outside lists', () => {
162
+ const input = `# Heading
163
+
164
+ Some paragraph text.
165
+
166
+ - List item
167
+ \`\`\`js
168
+ const x = 1
169
+ \`\`\`
170
+
171
+ More text after.`
172
+ const result = unnestCodeBlocksFromLists(input)
173
+ expect(result).toMatchInlineSnapshot(`
174
+ "# Heading
175
+
176
+ Some paragraph text.
177
+
178
+ - List item
179
+
180
+ \`\`\`js
181
+ const x = 1
182
+ \`\`\`
183
+
184
+ More text after."
185
+ `)
186
+ })
187
+
188
+ test('code block at root level unchanged', () => {
189
+ const input = `\`\`\`js
190
+ const x = 1
191
+ \`\`\``
192
+ const result = unnestCodeBlocksFromLists(input)
193
+ expect(result).toMatchInlineSnapshot(`
194
+ "\`\`\`js
195
+ const x = 1
196
+ \`\`\`"
197
+ `)
198
+ })
199
+
200
+ test('handles code block without language', () => {
201
+ const input = `- Item
202
+ \`\`\`
203
+ plain code
204
+ \`\`\``
205
+ const result = unnestCodeBlocksFromLists(input)
206
+ expect(result).toMatchInlineSnapshot(`
207
+ "- Item
208
+
209
+ \`\`\`
210
+ plain code
211
+ \`\`\`"
212
+ `)
213
+ })
214
+
215
+ test('handles empty list item with code', () => {
216
+ const input = `- \`\`\`js
217
+ const x = 1
218
+ \`\`\``
219
+ const result = unnestCodeBlocksFromLists(input)
220
+ expect(result).toMatchInlineSnapshot(`
221
+ "\`\`\`js
222
+ const x = 1
223
+ \`\`\`"
224
+ `)
225
+ })
226
+
227
+ test('numbered list with text after code block', () => {
228
+ const input = `1. First item
229
+ \`\`\`js
230
+ const a = 1
231
+ \`\`\`
232
+ Text after the code
233
+ 2. Second item`
234
+ const result = unnestCodeBlocksFromLists(input)
235
+ expect(result).toMatchInlineSnapshot(`
236
+ "1. First item
237
+
238
+ \`\`\`js
239
+ const a = 1
240
+ \`\`\`
241
+ - Text after the code
242
+ 2. Second item"
243
+ `)
244
+ })
245
+
246
+ test('numbered list with multiple code blocks and text between', () => {
247
+ const input = `1. First item
248
+ \`\`\`js
249
+ const a = 1
250
+ \`\`\`
251
+ Middle text
252
+ \`\`\`python
253
+ b = 2
254
+ \`\`\`
255
+ Final text
256
+ 2. Second item`
257
+ const result = unnestCodeBlocksFromLists(input)
258
+ expect(result).toMatchInlineSnapshot(`
259
+ "1. First item
260
+
261
+ \`\`\`js
262
+ const a = 1
263
+ \`\`\`
264
+ - Middle text
265
+
266
+ \`\`\`python
267
+ b = 2
268
+ \`\`\`
269
+ - Final text
270
+ 2. Second item"
271
+ `)
272
+ })
273
+
274
+ test('unordered list with multiple code blocks and text between', () => {
275
+ const input = `- First item
276
+ \`\`\`js
277
+ const a = 1
278
+ \`\`\`
279
+ Middle text
280
+ \`\`\`python
281
+ b = 2
282
+ \`\`\`
283
+ Final text
284
+ - Second item`
285
+ const result = unnestCodeBlocksFromLists(input)
286
+ expect(result).toMatchInlineSnapshot(`
287
+ "- First item
288
+
289
+ \`\`\`js
290
+ const a = 1
291
+ \`\`\`
292
+ - Middle text
293
+
294
+ \`\`\`python
295
+ b = 2
296
+ \`\`\`
297
+ - Final text
298
+ - Second item"
299
+ `)
300
+ })
301
+
302
+ test('numbered list starting from 5', () => {
303
+ const input = `5. Fifth item
304
+ \`\`\`js
305
+ code
306
+ \`\`\`
307
+ Text after
308
+ 6. Sixth item`
309
+ const result = unnestCodeBlocksFromLists(input)
310
+ expect(result).toMatchInlineSnapshot(`
311
+ "5. Fifth item
312
+
313
+ \`\`\`js
314
+ code
315
+ \`\`\`
316
+ - Text after
317
+ 6. Sixth item"
318
+ `)
319
+ })
320
+
321
+ test('deeply nested list with code', () => {
322
+ const input = `- Level 1
323
+ - Level 2
324
+ - Level 3
325
+ \`\`\`js
326
+ deep code
327
+ \`\`\`
328
+ Text after deep code
329
+ - Another level 3
330
+ - Back to level 2`
331
+ const result = unnestCodeBlocksFromLists(input)
332
+ expect(result).toMatchInlineSnapshot(`
333
+ "- Level 1
334
+ - Level 2
335
+ - Level 3
336
+
337
+ \`\`\`js
338
+ deep code
339
+ \`\`\`
340
+ - Text after deep code
341
+ - Another level 3
342
+ - Back to level 2"
343
+ `)
344
+ })
345
+
346
+ test('nested numbered list inside unordered with code', () => {
347
+ const input = `- Unordered item
348
+ 1. Nested numbered
349
+ \`\`\`js
350
+ code
351
+ \`\`\`
352
+ Text after
353
+ 2. Second nested
354
+ - Another unordered`
355
+ const result = unnestCodeBlocksFromLists(input)
356
+ expect(result).toMatchInlineSnapshot(`
357
+ "- Unordered item
358
+ 1. Nested numbered
359
+
360
+ \`\`\`js
361
+ code
362
+ \`\`\`
363
+ - Text after
364
+ 2. Second nested
365
+ - Another unordered"
366
+ `)
367
+ })
368
+
369
+ test('code block at end of numbered item no text after', () => {
370
+ const input = `1. First with text
371
+ \`\`\`js
372
+ code here
373
+ \`\`\`
374
+ 2. Second item
375
+ 3. Third item`
376
+ const result = unnestCodeBlocksFromLists(input)
377
+ expect(result).toMatchInlineSnapshot(`
378
+ "1. First with text
379
+
380
+ \`\`\`js
381
+ code here
382
+ \`\`\`
383
+ 2. Second item
384
+ 3. Third item"
385
+ `)
386
+ })
387
+
388
+ test('multiple items each with code and text after', () => {
389
+ const input = `1. First
390
+ \`\`\`js
391
+ code1
392
+ \`\`\`
393
+ After first
394
+ 2. Second
395
+ \`\`\`python
396
+ code2
397
+ \`\`\`
398
+ After second
399
+ 3. Third no code`
400
+ const result = unnestCodeBlocksFromLists(input)
401
+ expect(result).toMatchInlineSnapshot(`
402
+ "1. First
403
+
404
+ \`\`\`js
405
+ code1
406
+ \`\`\`
407
+ - After first
408
+ 2. Second
409
+
410
+ \`\`\`python
411
+ code2
412
+ \`\`\`
413
+ - After second
414
+ 3. Third no code"
415
+ `)
416
+ })
417
+
418
+ test('code block immediately after list marker', () => {
419
+ const input = `1. \`\`\`js
420
+ immediate code
421
+ \`\`\`
422
+ 2. Normal item`
423
+ const result = unnestCodeBlocksFromLists(input)
424
+ expect(result).toMatchInlineSnapshot(`
425
+ "\`\`\`js
426
+ immediate code
427
+ \`\`\`
428
+ 2. Normal item"
429
+ `)
430
+ })
431
+
432
+ test('code block with filename metadata', () => {
433
+ const input = `- Item with code
434
+ \`\`\`tsx filename=example.tsx
435
+ const x = 1
436
+ \`\`\``
437
+ const result = unnestCodeBlocksFromLists(input)
438
+ expect(result).toMatchInlineSnapshot(`
439
+ "- Item with code
440
+
441
+ \`\`\`tsx filename=example.tsx
442
+ const x = 1
443
+ \`\`\`"
444
+ `)
445
+ })
446
+
447
+ test('numbered list with filename metadata code block', () => {
448
+ const input = `1. First item
449
+ \`\`\`tsx filename=app.tsx
450
+ export default function App() {}
451
+ \`\`\`
452
+ 2. Second item`
453
+ const result = unnestCodeBlocksFromLists(input)
454
+ expect(result).toMatchInlineSnapshot(`
455
+ "1. First item
456
+
457
+ \`\`\`tsx filename=app.tsx
458
+ export default function App() {}
459
+ \`\`\`
460
+ 2. Second item"
461
+ `)
462
+ })
463
+
464
+ test('inline fence in list item stays inline (discord formatting issue)', () => {
465
+ const input = `- File: playwriter/src/aria-snapshot.ts
466
+ - Add helper function (~line 477, after isTextRole):\`\`\`ts
467
+ function isSubstringOfAny(needle: string, haystack: Set<string>): boolean {
468
+ for (const str of haystack) {
469
+ if (str.includes(needle)) {
470
+ return true
471
+ }
472
+ }
473
+ return false
474
+ }
475
+ \`\`\``
476
+ const result = unnestCodeBlocksFromLists(input)
477
+ expect(result).toMatchInlineSnapshot(`
478
+ "- File: playwriter/src/aria-snapshot.ts
479
+ - Add helper function (~line 477, after isTextRole):\`\`\`ts
480
+ function isSubstringOfAny(needle: string, haystack: Set<string>): boolean {
481
+ for (const str of haystack) {
482
+ if (str.includes(needle)) {
483
+ return true
484
+ }
485
+ }
486
+ return false
487
+ }
488
+ \`\`\`"
489
+ `)
490
+ })
491
+
492
+ test('numbered list with ) delimiter and code block after continuation text', () => {
493
+ const input = `What to test (no mocks, real processes):
494
+
495
+ 1) **"Older client must not kill newer server"**
496
+ - Start a tiny HTTP server on an ephemeral port that serves \`/version\` as something **higher than** our current version.
497
+ - Run \`ensureRelayServer({ restartOnVersionMismatch: true })\` pointed at that port.
498
+ - Assert:
499
+ - the server is **still listening** afterward (port not killed)
500
+ - no relay is spawned / no kill attempted
501
+ This directly exercises:
502
+ \`\`\`ts
503
+ if (serverVersion !== null && compareVersions(serverVersion, VERSION) > 0) return
504
+ \`\`\`
505
+
506
+ 2) **"Newer client may restart older server (when allowed)"**
507
+ - Start a tiny HTTP server that returns a **lower** \`/version\`.
508
+ - Call \`ensureRelayServer({ restartOnVersionMismatch: true })\`.
509
+ - Assert the old server gets killed (port frees).`
510
+ const result = unnestCodeBlocksFromLists(input)
511
+
512
+ expect('\n' + result).toMatchInlineSnapshot(`
513
+ "
514
+ What to test (no mocks, real processes):
515
+
516
+ 1) **"Older client must not kill newer server"**
517
+ - Start a tiny HTTP server on an ephemeral port that serves \`/version\` as something **higher than** our current version.
518
+ - Run \`ensureRelayServer({ restartOnVersionMismatch: true })\` pointed at that port.
519
+ - Assert:
520
+ - the server is **still listening** afterward (port not killed)
521
+ - no relay is spawned / no kill attempted
522
+ This directly exercises:
523
+ \`\`\`ts
524
+ if (serverVersion !== null && compareVersions(serverVersion, VERSION) > 0) return
525
+ \`\`\`
526
+
527
+ 2) **"Newer client may restart older server (when allowed)"**
528
+ - Start a tiny HTTP server that returns a **lower** \`/version\`.
529
+ - Call \`ensureRelayServer({ restartOnVersionMismatch: true })\`.
530
+ - Assert the old server gets killed (port frees)."
531
+ `)
532
+
533
+ // Desired Discord formatting:
534
+ // - Preserve newline between the "1) ..." line and the nested "- Start..." list
535
+ // - Keep fenced code blocks on their own lines (not glued to surrounding text)
536
+ expect(result).toContain('1) **"Older client must not kill newer server"**\n')
537
+ expect(result).toContain('\n- Start a tiny HTTP server')
538
+ expect(result).toContain('\nThis directly exercises:\n')
539
+ expect(result).toMatch(/\n```ts\n[\s\S]*\n```\n/)
540
+
541
+ // Regression: these are the two failure modes seen in the session message
542
+ expect(result).not.toContain(
543
+ '**"Older client must not kill newer server"**- Start',
544
+ )
545
+ expect(result).not.toContain('exercises:```')
546
+ })
547
+
548
+ test('unordered list with blank line before fenced code block', () => {
549
+ const input = `- Item with spacing
550
+
551
+ \`\`\`ts
552
+ const x = 1
553
+ \`\`\`
554
+ - Next item`
555
+ const result = unnestCodeBlocksFromLists(input)
556
+ expect('\n' + result).toMatchInlineSnapshot(`
557
+ "
558
+ - Item with spacing
559
+
560
+ \`\`\`ts
561
+ const x = 1
562
+ \`\`\`
563
+ - Next item"
564
+ `)
565
+ })
566
+
567
+ test('ordered list item containing fenced code and trailing paragraph', () => {
568
+ const input = `1) Item title
569
+ \`\`\`js
570
+ console.log('hi')
571
+ \`\`\`
572
+ trailing text
573
+ 2) Second item`
574
+ const result = unnestCodeBlocksFromLists(input)
575
+ expect('\n' + result).toMatchInlineSnapshot(`
576
+ "
577
+ 1. Item title
578
+
579
+ \`\`\`js
580
+ console.log('hi')
581
+ \`\`\`
582
+ - trailing text
583
+ 2) Second item"
584
+ `)
585
+ })
586
+
587
+ test('two top-level lists back-to-back (ensure newline between them)', () => {
588
+ const input = `- a
589
+ - b
590
+ 1) c
591
+ - d`
592
+ const result = unnestCodeBlocksFromLists(input)
593
+ expect('\n' + result).toMatchInlineSnapshot(`
594
+ "
595
+ - a
596
+ - b
597
+ 1) c
598
+ - d"
599
+ `)
600
+ })
601
+
602
+ test('top-level list followed by top-level fenced code then paragraph', () => {
603
+ const input = `- Item
604
+ \`\`\`ts
605
+ type X = { a: 1 }
606
+ \`\`\`
607
+ After.`
608
+ const result = unnestCodeBlocksFromLists(input)
609
+ expect('\n' + result).toMatchInlineSnapshot(`
610
+ "
611
+ - Item
612
+ \`\`\`ts
613
+ type X = { a: 1 }
614
+ \`\`\`
615
+ After."
616
+ `)
617
+ })
618
+
619
+ test('task list item with fenced code', () => {
620
+ const input = `- [ ] Do thing
621
+ \`\`\`sh
622
+ echo hi
623
+ \`\`\``
624
+ const result = unnestCodeBlocksFromLists(input)
625
+ expect('\n' + result).toMatchInlineSnapshot(`
626
+ "
627
+ - [ ] Do thing
628
+
629
+ \`\`\`sh
630
+ echo hi
631
+ \`\`\`"
632
+ `)
633
+ })
634
+
635
+ test('checked task list item keeps a single checkbox marker', () => {
636
+ const input = `- [x] Ship fix
637
+ \`\`\`ts
638
+ console.log('done')
639
+ \`\`\``
640
+ const result = unnestCodeBlocksFromLists(input)
641
+ expect('\n' + result).toMatchInlineSnapshot(`
642
+ "
643
+ - [x] Ship fix
644
+
645
+ \`\`\`ts
646
+ console.log('done')
647
+ \`\`\`"
648
+ `)
649
+ })
650
+
651
+ test('task list item with trailing text keeps one checkbox marker after hoisting code', () => {
652
+ const input = `- [ ] Do thing
653
+ \`\`\`sh
654
+ echo hi
655
+ \`\`\`
656
+ then report back`
657
+ const result = unnestCodeBlocksFromLists(input)
658
+ expect('\n' + result).toMatchInlineSnapshot(`
659
+ "
660
+ - [ ] Do thing
661
+
662
+ \`\`\`sh
663
+ echo hi
664
+ \`\`\`
665
+ - then report back"
666
+ `)
667
+ })
668
+
669
+ test('fenced code block indented more than list marker', () => {
670
+ const input = `- Item
671
+ \`\`\`ts
672
+ const x = 1
673
+ \`\`\`
674
+ - Next`
675
+ const result = unnestCodeBlocksFromLists(input)
676
+ expect('\n' + result).toMatchInlineSnapshot(`
677
+ "
678
+ - Item
679
+
680
+ \`\`\`ts
681
+ const x = 1
682
+ \`\`\`
683
+ - Next"
684
+ `)
685
+ })
686
+
687
+ test('ordered list with multiple paragraphs and code', () => {
688
+ const input = `1) Title
689
+ First paragraph.
690
+
691
+ Second paragraph.
692
+
693
+ \`\`\`ts
694
+ const x = 1
695
+ \`\`\`
696
+
697
+ After code.
698
+ 2) Next`
699
+ const result = unnestCodeBlocksFromLists(input)
700
+ expect('\n' + result).toMatchInlineSnapshot(`
701
+ "
702
+ 1. Title
703
+ First paragraph.
704
+
705
+ Second paragraph.
706
+
707
+ \`\`\`ts
708
+ const x = 1
709
+ \`\`\`
710
+ - After code.
711
+ 2) Next"
712
+ `)
713
+ })