@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,413 @@
1
+ // OpenCode message part formatting for Discord.
2
+ // Converts SDK message parts (text, tools, reasoning) to Discord-friendly format,
3
+ // handles file attachments, and provides tool summary generation.
4
+ import * as errore from 'errore';
5
+ import { createLogger, LogPrefix } from './logger.js';
6
+ import { FetchError } from './errors.js';
7
+ import { processImage } from './image-utils.js';
8
+ import { parsePatchFileCounts } from './patch-text-parser.js';
9
+ const logger = createLogger(LogPrefix.FORMATTING);
10
+ /**
11
+ * Resolves Discord mentions in message content to human-readable names.
12
+ * Replaces <@userId> with @displayName, <@&roleId> with @roleName, <#channelId> with #channelName.
13
+ */
14
+ export function resolveMentions(message) {
15
+ let content = message.content || '';
16
+ // Replace user mentions <@userId> or <@!userId> with @displayName
17
+ for (const [userId, user] of message.mentions.users) {
18
+ const member = message.guild?.members.cache.get(userId);
19
+ const displayName = member?.displayName || user.displayName || user.username;
20
+ content = content.replace(new RegExp(`<@!?${userId}>`, 'g'), `@${displayName}`);
21
+ }
22
+ // Replace role mentions <@&roleId> with @roleName
23
+ for (const [roleId, role] of message.mentions.roles) {
24
+ content = content.replace(new RegExp(`<@&${roleId}>`, 'g'), `@${role.name}`);
25
+ }
26
+ // Replace channel mentions <#channelId> with #channelName
27
+ for (const [channelId, channel] of message.mentions.channels) {
28
+ const name = 'name' in channel ? channel.name : channelId;
29
+ content = content.replace(new RegExp(`<#${channelId}>`, 'g'), `#${name}`);
30
+ }
31
+ return content;
32
+ }
33
+ /**
34
+ * Escapes Discord inline markdown characters so dynamic content
35
+ * doesn't break formatting when wrapped in *, _, **, etc.
36
+ */
37
+ function escapeInlineMarkdown(text) {
38
+ return text.replace(/([*_~|`\\])/g, '\\$1');
39
+ }
40
+ // parsePatchCounts → imported from patch-text-parser.ts as parsePatchFileCounts
41
+ /**
42
+ * Normalize whitespace: convert newlines to spaces and collapse consecutive spaces.
43
+ */
44
+ function normalizeWhitespace(text) {
45
+ return text.replace(/[\r\n]+/g, ' ').replace(/\s+/g, ' ');
46
+ }
47
+ /**
48
+ * Collect renderable assistant parts from session messages as SessionChunks.
49
+ * Each non-empty formatted part becomes one chunk. Caller can batch them
50
+ * with batchChunksForDiscord() before sending.
51
+ *
52
+ * - skipPartIds: parts already synced (external sync). Skipped parts are
53
+ * not included in the result.
54
+ * - limit: max parts to include (from the end). Older parts are counted
55
+ * in skippedCount.
56
+ */
57
+ export function collectSessionChunks({ messages, skipPartIds, limit, }) {
58
+ const allChunks = [];
59
+ for (const message of messages) {
60
+ if (message.info.role !== 'assistant') {
61
+ continue;
62
+ }
63
+ for (const part of message.parts) {
64
+ if (skipPartIds?.has(part.id)) {
65
+ continue;
66
+ }
67
+ const content = formatPart(part);
68
+ if (!content.trim()) {
69
+ continue;
70
+ }
71
+ allChunks.push({ partIds: [part.id], content: content.trimEnd() });
72
+ }
73
+ }
74
+ if (limit !== undefined && allChunks.length > limit) {
75
+ return {
76
+ chunks: allChunks.slice(-limit),
77
+ skippedCount: allChunks.length - limit,
78
+ };
79
+ }
80
+ return { chunks: allChunks, skippedCount: 0 };
81
+ }
82
+ // Merge consecutive SessionChunks into as few Discord messages as possible,
83
+ // respecting the 2000 char limit.
84
+ const DISCORD_BATCH_MAX_LENGTH = 2000;
85
+ export function batchChunksForDiscord(chunks) {
86
+ if (chunks.length === 0) {
87
+ return [];
88
+ }
89
+ const batched = [];
90
+ let current = { partIds: [...chunks[0].partIds], content: chunks[0].content };
91
+ for (let i = 1; i < chunks.length; i++) {
92
+ const next = chunks[i];
93
+ const merged = current.content + '\n' + next.content;
94
+ if (merged.length <= DISCORD_BATCH_MAX_LENGTH) {
95
+ current = {
96
+ partIds: [...current.partIds, ...next.partIds],
97
+ content: merged,
98
+ };
99
+ }
100
+ else {
101
+ batched.push(current);
102
+ current = { partIds: [...next.partIds], content: next.content };
103
+ }
104
+ }
105
+ batched.push(current);
106
+ return batched;
107
+ }
108
+ export const TEXT_MIME_TYPES = [
109
+ 'text/',
110
+ 'application/json',
111
+ 'application/xml',
112
+ 'application/javascript',
113
+ 'application/typescript',
114
+ 'application/x-yaml',
115
+ 'application/toml',
116
+ ];
117
+ export function isTextMimeType(contentType) {
118
+ if (!contentType) {
119
+ return false;
120
+ }
121
+ return TEXT_MIME_TYPES.some((prefix) => contentType.startsWith(prefix));
122
+ }
123
+ export async function getTextAttachments(message) {
124
+ const textAttachments = Array.from(message.attachments.values()).filter((attachment) => isTextMimeType(attachment.contentType));
125
+ if (textAttachments.length === 0) {
126
+ return '';
127
+ }
128
+ const textContents = await Promise.all(textAttachments.map(async (attachment) => {
129
+ const response = await errore.tryAsync({
130
+ try: () => fetch(attachment.url),
131
+ catch: (e) => new FetchError({ url: attachment.url, cause: e }),
132
+ });
133
+ if (response instanceof Error) {
134
+ return `<attachment filename="${attachment.name}" error="${response.message}" />`;
135
+ }
136
+ if (!response.ok) {
137
+ return `<attachment filename="${attachment.name}" error="Failed to fetch: ${response.status}" />`;
138
+ }
139
+ const text = await response.text();
140
+ return `<attachment filename="${attachment.name}" mime="${attachment.contentType}">\n${text}\n</attachment>`;
141
+ }));
142
+ return textContents.join('\n\n');
143
+ }
144
+ export async function getFileAttachments(message) {
145
+ const fileAttachments = Array.from(message.attachments.values()).filter((attachment) => {
146
+ const contentType = attachment.contentType || '';
147
+ return (contentType.startsWith('image/') || contentType === 'application/pdf');
148
+ });
149
+ if (fileAttachments.length === 0) {
150
+ return [];
151
+ }
152
+ const results = await Promise.all(fileAttachments.map(async (attachment) => {
153
+ const response = await errore.tryAsync({
154
+ try: () => fetch(attachment.url),
155
+ catch: (e) => new FetchError({ url: attachment.url, cause: e }),
156
+ });
157
+ if (response instanceof Error) {
158
+ logger.error(`Error downloading attachment ${attachment.name}:`, response.message);
159
+ return null;
160
+ }
161
+ if (!response.ok) {
162
+ logger.error(`Failed to fetch attachment ${attachment.name}: ${response.status}`);
163
+ return null;
164
+ }
165
+ const rawBuffer = Buffer.from(await response.arrayBuffer());
166
+ const originalMime = attachment.contentType || 'application/octet-stream';
167
+ // Process image (resize if needed, convert to JPEG)
168
+ const { buffer, mime } = await processImage(rawBuffer, originalMime);
169
+ const base64 = buffer.toString('base64');
170
+ const dataUrl = `data:${mime};base64,${base64}`;
171
+ logger.log(`Attachment ${attachment.name}: ${rawBuffer.length} → ${buffer.length} bytes, ${mime}`);
172
+ return {
173
+ type: 'file',
174
+ mime,
175
+ filename: attachment.name,
176
+ url: dataUrl,
177
+ sourceUrl: attachment.url,
178
+ };
179
+ }));
180
+ return results.filter((r) => r !== null);
181
+ }
182
+ const MAX_BASH_COMMAND_INLINE_LENGTH = 100;
183
+ export function getToolSummaryText(part) {
184
+ if (part.type !== 'tool')
185
+ return '';
186
+ if (part.tool === 'edit') {
187
+ const filePath = part.state.input?.filePath || '';
188
+ const newString = part.state.input?.newString || '';
189
+ const oldString = part.state.input?.oldString || '';
190
+ const added = newString.split('\n').length;
191
+ const removed = oldString.split('\n').length;
192
+ const fileName = filePath.split('/').pop() || '';
193
+ return fileName
194
+ ? `*${escapeInlineMarkdown(fileName)}* (+${added}-${removed})`
195
+ : `(+${added}-${removed})`;
196
+ }
197
+ if (part.tool === 'apply_patch') {
198
+ // Only inputs are available when parts are sent during streaming (output/metadata not yet populated)
199
+ const patchText = part.state.input?.patchText || '';
200
+ if (!patchText) {
201
+ return '';
202
+ }
203
+ const patchCounts = parsePatchFileCounts(patchText);
204
+ return [...patchCounts.entries()]
205
+ .map(([filePath, { additions, deletions }]) => {
206
+ const fileName = filePath.split('/').pop() || '';
207
+ return fileName
208
+ ? `*${escapeInlineMarkdown(fileName)}* (+${additions}-${deletions})`
209
+ : `(+${additions}-${deletions})`;
210
+ })
211
+ .join(', ');
212
+ }
213
+ if (part.tool === 'write') {
214
+ const filePath = part.state.input?.filePath || '';
215
+ const content = part.state.input?.content || '';
216
+ const lines = content.split('\n').length;
217
+ const fileName = filePath.split('/').pop() || '';
218
+ return fileName
219
+ ? `*${escapeInlineMarkdown(fileName)}* (${lines} line${lines === 1 ? '' : 's'})`
220
+ : `(${lines} line${lines === 1 ? '' : 's'})`;
221
+ }
222
+ if (part.tool === 'webfetch') {
223
+ const url = part.state.input?.url || '';
224
+ const urlWithoutProtocol = url.replace(/^https?:\/\//, '');
225
+ return urlWithoutProtocol
226
+ ? `*${escapeInlineMarkdown(urlWithoutProtocol)}*`
227
+ : '';
228
+ }
229
+ if (part.tool === 'read') {
230
+ const filePath = part.state.input?.filePath || '';
231
+ const fileName = filePath.split('/').pop() || '';
232
+ return fileName ? `*${escapeInlineMarkdown(fileName)}*` : '';
233
+ }
234
+ if (part.tool === 'list') {
235
+ const path = part.state.input?.path || '';
236
+ const dirName = path.split('/').pop() || path;
237
+ return dirName ? `*${escapeInlineMarkdown(dirName)}*` : '';
238
+ }
239
+ if (part.tool === 'glob') {
240
+ const pattern = part.state.input?.pattern || '';
241
+ return pattern ? `*${escapeInlineMarkdown(pattern)}*` : '';
242
+ }
243
+ if (part.tool === 'grep') {
244
+ const pattern = part.state.input?.pattern || '';
245
+ return pattern ? `*${escapeInlineMarkdown(pattern)}*` : '';
246
+ }
247
+ if (part.tool === 'bash' ||
248
+ part.tool === 'todoread' ||
249
+ part.tool === 'todowrite') {
250
+ return '';
251
+ }
252
+ // Task tool display is handled via subtask part in session-handler (shows name + agent)
253
+ if (part.tool === 'task') {
254
+ return '';
255
+ }
256
+ if (part.tool === 'skill') {
257
+ const name = part.state.input?.name || '';
258
+ return name ? `_${escapeInlineMarkdown(name)}_` : '';
259
+ }
260
+ // File upload tool - show the prompt
261
+ if (part.tool.endsWith('kimaki_file_upload')) {
262
+ const prompt = part.state.input?.prompt || '';
263
+ return prompt ? `*${escapeInlineMarkdown(prompt.slice(0, 60))}*` : '';
264
+ }
265
+ if (!part.state.input)
266
+ return '';
267
+ const inputFields = Object.entries(part.state.input)
268
+ .map(([key, value]) => {
269
+ if (value === null || value === undefined)
270
+ return null;
271
+ const stringValue = typeof value === 'string' ? value : JSON.stringify(value);
272
+ const normalized = normalizeWhitespace(stringValue);
273
+ const truncatedValue = normalized.length > 50 ? normalized.slice(0, 50) + '…' : normalized;
274
+ return `${key}: ${truncatedValue}`;
275
+ })
276
+ .filter(Boolean);
277
+ if (inputFields.length === 0)
278
+ return '';
279
+ return `(${inputFields.join(', ')})`;
280
+ }
281
+ export function formatTodoList(part) {
282
+ if (part.type !== 'tool' || part.tool !== 'todowrite')
283
+ return '';
284
+ const todos = part.state.input?.todos || [];
285
+ const activeIndex = todos.findIndex((todo) => {
286
+ return todo.status === 'in_progress';
287
+ });
288
+ const activeTodo = todos[activeIndex];
289
+ if (activeIndex === -1 || !activeTodo)
290
+ return '';
291
+ // digit-with-period ⒈-⒛ for 1-20, fallback to regular number for 21+
292
+ const digitWithPeriod = '⒈⒉⒊⒋⒌⒍⒎⒏⒐⒑⒒⒓⒔⒕⒖⒗⒘⒙⒚⒛';
293
+ const todoNumber = activeIndex + 1;
294
+ const num = todoNumber <= 20 ? digitWithPeriod[todoNumber - 1] : `${todoNumber}.`;
295
+ const content = activeTodo.content.charAt(0).toLowerCase() + activeTodo.content.slice(1);
296
+ return `${num} **${escapeInlineMarkdown(content)}**`;
297
+ }
298
+ export function formatPart(part, prefix) {
299
+ const pfx = prefix ? `${prefix} ⋅ ` : '';
300
+ if (part.type === 'text') {
301
+ const text = part.text?.trim();
302
+ if (!text)
303
+ return '';
304
+ // For subtask text, always use bullet with prefix
305
+ if (prefix) {
306
+ return `⬥ ${pfx}${text}`;
307
+ }
308
+ const firstChar = text[0] || '';
309
+ const markdownStarters = ['#', '*', '_', '-', '>', '`', '[', '|'];
310
+ const startsWithMarkdown = markdownStarters.includes(firstChar) || /^\d+\./.test(text);
311
+ if (startsWithMarkdown) {
312
+ return `\n${text}`;
313
+ }
314
+ return `⬥ ${text}`;
315
+ }
316
+ if (part.type === 'reasoning') {
317
+ if (!part.text?.trim())
318
+ return '';
319
+ return `┣ ${pfx}thinking`;
320
+ }
321
+ if (part.type === 'file') {
322
+ return prefix
323
+ ? `📄 ${pfx}${part.filename || 'File'}`
324
+ : `📄 ${part.filename || 'File'}`;
325
+ }
326
+ if (part.type === 'step-start' ||
327
+ part.type === 'step-finish' ||
328
+ part.type === 'patch') {
329
+ return '';
330
+ }
331
+ if (part.type === 'agent') {
332
+ return `┣ ${pfx}agent ${part.id}`;
333
+ }
334
+ if (part.type === 'snapshot') {
335
+ return `┣ ${pfx}snapshot ${part.snapshot}`;
336
+ }
337
+ if (part.type === 'tool') {
338
+ if (part.tool === 'todowrite') {
339
+ const formatted = formatTodoList(part);
340
+ return prefix && formatted ? `┣ ${pfx}${formatted}` : formatted;
341
+ }
342
+ // Question tool is handled via Discord dropdowns, not text
343
+ if (part.tool === 'question') {
344
+ return '';
345
+ }
346
+ // File upload tool is handled via Discord button + modal, not text
347
+ if (part.tool.endsWith('kimaki_file_upload')) {
348
+ return '';
349
+ }
350
+ // Action buttons tool is handled via Discord buttons, not text
351
+ if (part.tool.endsWith('kimaki_action_buttons')) {
352
+ return '';
353
+ }
354
+ // Task tool display is handled in session-handler with proper label
355
+ if (part.tool === 'task') {
356
+ return '';
357
+ }
358
+ if (part.state.status === 'pending') {
359
+ if (part.tool !== 'bash') {
360
+ return '';
361
+ }
362
+ const command = part.state.input?.command || '';
363
+ const description = part.state.input?.description || '';
364
+ const isSingleLine = !command.includes('\n');
365
+ const toolTitle = isSingleLine && command.length <= MAX_BASH_COMMAND_INLINE_LENGTH
366
+ ? ` _${escapeInlineMarkdown(command)}_`
367
+ : description
368
+ ? ` _${escapeInlineMarkdown(description)}_`
369
+ : '';
370
+ return `┣ ${pfx}bash${toolTitle}`;
371
+ }
372
+ const summaryText = getToolSummaryText(part);
373
+ const stateTitle = 'title' in part.state ? part.state.title : undefined;
374
+ let toolTitle = '';
375
+ if (part.state.status === 'error') {
376
+ toolTitle = part.state.error || 'error';
377
+ }
378
+ else if (part.tool === 'bash') {
379
+ const command = part.state.input?.command || '';
380
+ const description = part.state.input?.description || '';
381
+ const isSingleLine = !command.includes('\n');
382
+ if (isSingleLine && command.length <= MAX_BASH_COMMAND_INLINE_LENGTH) {
383
+ toolTitle = `_${escapeInlineMarkdown(command)}_`;
384
+ }
385
+ else if (description) {
386
+ toolTitle = `_${escapeInlineMarkdown(description)}_`;
387
+ }
388
+ else if (stateTitle) {
389
+ toolTitle = `_${escapeInlineMarkdown(stateTitle)}_`;
390
+ }
391
+ }
392
+ else if (stateTitle) {
393
+ toolTitle = `_${escapeInlineMarkdown(stateTitle)}_`;
394
+ }
395
+ const icon = (() => {
396
+ if (part.state.status === 'error') {
397
+ return '⨯';
398
+ }
399
+ if (part.tool === 'edit' ||
400
+ part.tool === 'write' ||
401
+ part.tool === 'apply_patch') {
402
+ return '◼︎';
403
+ }
404
+ return '┣';
405
+ })();
406
+ const toolParts = [part.tool, toolTitle, summaryText]
407
+ .filter(Boolean)
408
+ .join(' ');
409
+ return `${icon} ${pfx}${toolParts}`;
410
+ }
411
+ logger.warn('Unknown part type:', part);
412
+ return '';
413
+ }
@@ -0,0 +1,73 @@
1
+ import { describe, test, expect } from 'vitest';
2
+ import { formatTodoList } from './message-formatting.js';
3
+ describe('formatTodoList', () => {
4
+ test('formats active todo with monospace numbers', () => {
5
+ const part = {
6
+ id: 'test',
7
+ type: 'tool',
8
+ tool: 'todowrite',
9
+ sessionID: 'ses_test',
10
+ messageID: 'msg_test',
11
+ callID: 'call_test',
12
+ state: {
13
+ status: 'completed',
14
+ input: {
15
+ todos: [
16
+ { content: 'First task', status: 'completed' },
17
+ { content: 'Second task', status: 'in_progress' },
18
+ { content: 'Third task', status: 'pending' },
19
+ ],
20
+ },
21
+ output: '',
22
+ title: 'todowrite',
23
+ metadata: {},
24
+ time: { start: 0, end: 0 },
25
+ },
26
+ };
27
+ expect(formatTodoList(part)).toMatchInlineSnapshot(`"⒉ **second task**"`);
28
+ });
29
+ test('formats double digit todo numbers', () => {
30
+ const todos = Array.from({ length: 12 }, (_, i) => ({
31
+ content: `Task ${i + 1}`,
32
+ status: i === 11 ? 'in_progress' : 'completed',
33
+ }));
34
+ const part = {
35
+ id: 'test',
36
+ type: 'tool',
37
+ tool: 'todowrite',
38
+ sessionID: 'ses_test',
39
+ messageID: 'msg_test',
40
+ callID: 'call_test',
41
+ state: {
42
+ status: 'completed',
43
+ input: { todos },
44
+ output: '',
45
+ title: 'todowrite',
46
+ metadata: {},
47
+ time: { start: 0, end: 0 },
48
+ },
49
+ };
50
+ expect(formatTodoList(part)).toMatchInlineSnapshot(`"⒓ **task 12**"`);
51
+ });
52
+ test('lowercases first letter of content', () => {
53
+ const part = {
54
+ id: 'test',
55
+ type: 'tool',
56
+ tool: 'todowrite',
57
+ sessionID: 'ses_test',
58
+ messageID: 'msg_test',
59
+ callID: 'call_test',
60
+ state: {
61
+ status: 'completed',
62
+ input: {
63
+ todos: [{ content: 'Fix the bug', status: 'in_progress' }],
64
+ },
65
+ output: '',
66
+ title: 'todowrite',
67
+ metadata: {},
68
+ time: { start: 0, end: 0 },
69
+ },
70
+ };
71
+ expect(formatTodoList(part)).toMatchInlineSnapshot(`"⒈ **fix the bug**"`);
72
+ });
73
+ });