@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,183 @@
1
+ // Model resolution utilities.
2
+ // getDefaultModel resolves the default model from OpenCode when no user preference is set.
3
+
4
+ import fs from 'node:fs'
5
+ import path from 'node:path'
6
+ import { xdgState } from 'xdg-basedir'
7
+ import * as errore from 'errore'
8
+ import { type initializeOpencodeForDirectory } from '../opencode.js'
9
+ import { createLogger, LogPrefix } from '../logger.js'
10
+ import type { ScheduledTaskScheduleKind } from '../database.js'
11
+
12
+ const sessionLogger = createLogger(LogPrefix.SESSION)
13
+
14
+ export type DefaultModelSource =
15
+ | 'opencode-config'
16
+ | 'opencode-recent'
17
+ | 'opencode-provider-default'
18
+
19
+ export type SessionStartSourceContext = {
20
+ scheduleKind: ScheduledTaskScheduleKind
21
+ scheduledTaskId?: number
22
+ }
23
+
24
+ /**
25
+ * Read user's recent models from OpenCode TUI's state file.
26
+ * Uses same path as OpenCode: path.join(xdgState, "opencode", "model.json")
27
+ * Returns all recent models so we can iterate until finding a valid one.
28
+ * See: opensrc/repos/github.com/sst/opencode/packages/opencode/src/global/index.ts
29
+ */
30
+ function getRecentModelsFromTuiState(): Array<{
31
+ providerID: string
32
+ modelID: string
33
+ }> {
34
+ if (!xdgState) {
35
+ return []
36
+ }
37
+ // Same path as OpenCode TUI: path.join(Global.Path.state, "model.json")
38
+ const modelJsonPath = path.join(xdgState, 'opencode', 'model.json')
39
+
40
+ const result = errore.tryFn(() => {
41
+ const content = fs.readFileSync(modelJsonPath, 'utf-8')
42
+ const data = JSON.parse(content) as {
43
+ recent?: Array<{ providerID: string; modelID: string }>
44
+ }
45
+ return data.recent ?? []
46
+ })
47
+
48
+ if (result instanceof Error) {
49
+ // File doesn't exist or is invalid - this is normal for fresh installs
50
+ return []
51
+ }
52
+
53
+ return result
54
+ }
55
+
56
+ /**
57
+ * Parse a model string in format "provider/model" into providerID and modelID.
58
+ */
59
+ function parseModelString(
60
+ model: string,
61
+ ): { providerID: string; modelID: string } | undefined {
62
+ const [providerID, ...modelParts] = model.split('/')
63
+ const modelID = modelParts.join('/')
64
+ if (!providerID || !modelID) {
65
+ return undefined
66
+ }
67
+ return { providerID, modelID }
68
+ }
69
+
70
+ /**
71
+ * Validate that a model is available (provider connected + model exists).
72
+ */
73
+ function isModelValid(
74
+ model: { providerID: string; modelID: string },
75
+ connected: string[],
76
+ providers: Array<{ id: string; models?: Record<string, unknown> }>,
77
+ ): boolean {
78
+ const isConnected = connected.includes(model.providerID)
79
+ const provider = providers.find((p) => {
80
+ return p.id === model.providerID
81
+ })
82
+ const modelExists = provider?.models && model.modelID in provider.models
83
+ return isConnected && !!modelExists
84
+ }
85
+
86
+ /**
87
+ * Get the default model from OpenCode when no user preference is set.
88
+ * Priority (matches OpenCode TUI behavior):
89
+ * 1. OpenCode config.model setting
90
+ * 2. User's recent models from TUI state (~/.local/state/opencode/model.json)
91
+ * 3. First connected provider's default model from API
92
+ * Returns the model and its source.
93
+ */
94
+ export async function getDefaultModel({
95
+ getClient,
96
+ }: {
97
+ getClient: Awaited<ReturnType<typeof initializeOpencodeForDirectory>>
98
+ }): Promise<
99
+ | { providerID: string; modelID: string; source: DefaultModelSource }
100
+ | undefined
101
+ > {
102
+ if (getClient instanceof Error) {
103
+ return undefined
104
+ }
105
+
106
+ // Fetch connected providers to validate any model we return
107
+ const providersResponse = await errore.tryAsync(() => {
108
+ return getClient().provider.list({})
109
+ })
110
+ if (providersResponse instanceof Error) {
111
+ sessionLogger.log(
112
+ `[MODEL] Failed to fetch providers for default model:`,
113
+ providersResponse.message,
114
+ )
115
+ return undefined
116
+ }
117
+ if (!providersResponse.data) {
118
+ return undefined
119
+ }
120
+
121
+ const {
122
+ connected,
123
+ default: defaults,
124
+ all: providers,
125
+ } = providersResponse.data
126
+ if (connected.length === 0) {
127
+ sessionLogger.log(`[MODEL] No connected providers found`)
128
+ return undefined
129
+ }
130
+
131
+ // 1. Check OpenCode config.model setting (highest priority after user preference)
132
+ const configResponse = await errore.tryAsync(() => {
133
+ return getClient().config.get({})
134
+ })
135
+ if (!(configResponse instanceof Error) && configResponse.data?.model) {
136
+ const configModel = parseModelString(configResponse.data.model)
137
+ if (configModel && isModelValid(configModel, connected, providers)) {
138
+ sessionLogger.log(
139
+ `[MODEL] Using config model: ${configModel.providerID}/${configModel.modelID}`,
140
+ )
141
+ return { ...configModel, source: 'opencode-config' }
142
+ }
143
+ if (configModel) {
144
+ sessionLogger.log(
145
+ `[MODEL] Config model ${configResponse.data.model} not available, checking recent`,
146
+ )
147
+ }
148
+ }
149
+
150
+ // 2. Try to use user's recent models from TUI state (iterate until finding valid one)
151
+ const recentModels = getRecentModelsFromTuiState()
152
+ for (const recentModel of recentModels) {
153
+ if (isModelValid(recentModel, connected, providers)) {
154
+ sessionLogger.log(
155
+ `[MODEL] Using recent TUI model: ${recentModel.providerID}/${recentModel.modelID}`,
156
+ )
157
+ return { ...recentModel, source: 'opencode-recent' }
158
+ }
159
+ }
160
+ if (recentModels.length > 0) {
161
+ sessionLogger.log(`[MODEL] No valid recent TUI models found`)
162
+ }
163
+
164
+ // 3. Fall back to first connected provider's default model
165
+ const firstConnected = connected[0]
166
+ if (!firstConnected) {
167
+ return undefined
168
+ }
169
+ const defaultModelId = defaults[firstConnected]
170
+ if (!defaultModelId) {
171
+ sessionLogger.log(`[MODEL] No default model for provider ${firstConnected}`)
172
+ return undefined
173
+ }
174
+
175
+ sessionLogger.log(
176
+ `[MODEL] Using provider default: ${firstConnected}/${defaultModelId}`,
177
+ )
178
+ return {
179
+ providerID: firstConnected,
180
+ modelID: defaultModelId,
181
+ source: 'opencode-provider-default',
182
+ }
183
+ }
@@ -0,0 +1,130 @@
1
+ // Debug helper for writing raw OpenCode event stream entries as JSONL.
2
+ // When enabled, writes one file per session ID so event ordering and
3
+ // lifecycle behavior can be analyzed with jq.
4
+
5
+ import fs from 'node:fs'
6
+ import path from 'node:path'
7
+ import type { Event as OpenCodeEvent } from '@opencode-ai/sdk/v2'
8
+ import * as errore from 'errore'
9
+ import { getDataDir } from '../config.js'
10
+
11
+ let eventLogDirPromise: Promise<string> | null = null
12
+ let eventLogWriteDisabled = false
13
+
14
+ export function isOpencodeSessionEventLogEnabled(): boolean {
15
+ return process.env['KIMAKI_LOG_OPENCODE_SESSION_EVENTS'] === '1'
16
+ }
17
+
18
+ export function getOpencodeEventSessionId(event: OpenCodeEvent): string | undefined {
19
+ switch (event.type) {
20
+ case 'message.updated':
21
+ return event.properties.info.sessionID
22
+ case 'message.part.updated':
23
+ return event.properties.part.sessionID
24
+ case 'message.part.delta':
25
+ case 'message.part.removed':
26
+ case 'session.status':
27
+ case 'session.idle':
28
+ case 'session.diff':
29
+ case 'permission.asked':
30
+ case 'permission.replied':
31
+ case 'question.asked':
32
+ case 'question.replied':
33
+ case 'question.rejected':
34
+ return event.properties.sessionID
35
+ case 'session.error':
36
+ return event.properties.sessionID
37
+ case 'session.created':
38
+ case 'session.updated':
39
+ case 'session.deleted':
40
+ return event.properties.info.id
41
+ default:
42
+ return undefined
43
+ }
44
+ }
45
+
46
+ function sanitizeSessionIdForFilename(sessionId: string): string {
47
+ return sessionId.replace(/[^a-zA-Z0-9._-]/g, '_')
48
+ }
49
+
50
+ async function resolveEventLogDirectory(): Promise<string> {
51
+ if (!eventLogDirPromise) {
52
+ eventLogDirPromise = (async () => {
53
+ const configuredEventLogDir = process.env['KIMAKI_OPENCODE_SESSION_EVENTS_DIR']
54
+ const baseDir = configuredEventLogDir || path.join(getDataDir(), 'opencode-session-events')
55
+ await fs.promises.mkdir(baseDir, { recursive: true })
56
+ return baseDir
57
+ })()
58
+ }
59
+ return eventLogDirPromise
60
+ }
61
+
62
+ export type OpencodeEventLogEntry = {
63
+ timestamp: number
64
+ threadId: string
65
+ projectDirectory: string
66
+ event: OpenCodeEvent
67
+ }
68
+
69
+ export function buildOpencodeEventLogLine({
70
+ timestamp,
71
+ threadId,
72
+ projectDirectory,
73
+ event,
74
+ }: {
75
+ timestamp: number
76
+ threadId: string
77
+ projectDirectory: string
78
+ event: OpenCodeEvent
79
+ }): OpencodeEventLogEntry {
80
+ return {
81
+ timestamp,
82
+ threadId,
83
+ projectDirectory,
84
+ event,
85
+ }
86
+ }
87
+
88
+ export async function appendOpencodeSessionEventLog(
89
+ entry: Omit<OpencodeEventLogEntry, 'timestamp'>,
90
+ ): Promise<Error | null> {
91
+ if (!isOpencodeSessionEventLogEnabled() || eventLogWriteDisabled) {
92
+ return null
93
+ }
94
+
95
+ const sessionId = getOpencodeEventSessionId(entry.event)
96
+ if (!sessionId) {
97
+ return null
98
+ }
99
+
100
+ const logDirResult = await errore.tryAsync(() => {
101
+ return resolveEventLogDirectory()
102
+ })
103
+ if (logDirResult instanceof Error) {
104
+ eventLogWriteDisabled = true
105
+ return logDirResult
106
+ }
107
+
108
+ const safeSessionId = sanitizeSessionIdForFilename(sessionId)
109
+ const logFilePath = path.join(logDirResult, `${safeSessionId}.jsonl`)
110
+
111
+ const now = Date.now()
112
+ const line = `${JSON.stringify(
113
+ buildOpencodeEventLogLine({
114
+ timestamp: now,
115
+ threadId: entry.threadId,
116
+ projectDirectory: entry.projectDirectory,
117
+ event: entry.event,
118
+ }),
119
+ )}\n`
120
+
121
+ const appendResult = await errore.tryAsync(() => {
122
+ return fs.promises.appendFile(logFilePath, line, 'utf8')
123
+ })
124
+ if (appendResult instanceof Error) {
125
+ eventLogWriteDisabled = true
126
+ return appendResult
127
+ }
128
+
129
+ return null
130
+ }
@@ -0,0 +1,212 @@
1
+ // Per-thread state type, transition functions, and selectors.
2
+ // All transitions operate on the global store from ../store.js.
3
+ //
4
+ // ThreadRunState is a value-type: one entry per active thread in the
5
+ // global store's `threads` Map. Transition functions produce new Map +
6
+ // new ThreadRunState objects each time (immutable updates).
7
+ //
8
+ // Derived helpers (queue checks) compute from state and are never
9
+ // stored — they are always re-derived from ThreadRunState.
10
+ //
11
+ // STATE DISCIPLINE: keep as little state as possible. Before adding any new
12
+ // state field, ask if it can be derived from existing state instead.
13
+
14
+ import type { DiscordFileAttachment } from '../message-formatting.js'
15
+ import type { RepliedMessageContext } from '../system-message.js'
16
+ import { store } from '../store.js'
17
+
18
+ // ── Shared types ─────────────────────────────────────────────────
19
+
20
+ export type QueuedMessage = {
21
+ // The text content to send to the OpenCode session (user message or
22
+ // transcribed voice message). Always present.
23
+ prompt: string
24
+ // Discord user ID of the message author. Used for permission checks
25
+ // and attribution in the session start source tracking.
26
+ userId: string
27
+ // Discord display name. Used in runtime drain logging.
28
+ username: string
29
+ // Image/file attachments extracted from the Discord message. Sent as
30
+ // file parts alongside the prompt in the SDK call.
31
+ images?: DiscordFileAttachment[]
32
+ // Bot application ID. Used for model-preference resolution fallback
33
+ // (looking up channel/session model overrides keyed by appId).
34
+ appId?: string
35
+ // When set, dispatches via session.command() instead of session.prompt().
36
+ // Used by /queue-command and user-defined slash commands.
37
+ command?: { name: string; arguments: string }
38
+ // First-dispatch-only overrides — used when creating a new session.
39
+ // Subsequent queue drains ignore these since the session already exists.
40
+ // Set by --agent/--model/--permission flags on kimaki send or slash commands.
41
+ agent?: string
42
+ model?: string
43
+ // Raw permission rule strings ("tool:action" or "tool:pattern:action").
44
+ // Parsed and merged into session permissions on creation.
45
+ permissions?: string[]
46
+ // Injection guard scan patterns (e.g. "bash:*", "webfetch:*").
47
+ // Written to a temp config file after session creation so the plugin
48
+ // can check per-session whether to scan tool outputs.
49
+ injectionGuardPatterns?: string[]
50
+ // Discord message ID and thread ID of the source message. Embedded in
51
+ // <discord-user> synthetic context so the external sync loop can detect
52
+ // messages that originated from Discord and skip re-mirroring them.
53
+ sourceMessageId?: string
54
+ sourceThreadId?: string
55
+ repliedMessage?: RepliedMessageContext
56
+ // Tracking fields for scheduled tasks. Stored in the DB via
57
+ // setSessionStartSource() after the session is created, so the session
58
+ // list can show which sessions were started by scheduled tasks.
59
+ sessionStartScheduleKind?: 'at' | 'cron'
60
+ sessionStartScheduledTaskId?: number
61
+ }
62
+
63
+ // ── Per-thread state (value inside the Map) ──────────────────────
64
+
65
+ export type ThreadRunState = {
66
+ // OpenCode session ID for this thread. Set lazily by ensureSession()
67
+ // on first dispatch (not on thread creation). Persists across multiple
68
+ // prompt runs — the same session is reused for the thread's lifetime.
69
+ // Also stored in the DB (thread_sessions table) for recovery after restart.
70
+ // Changes: set on first dispatch, may be re-set if the old session is
71
+ // invalid and a new one is created. Never cleared except on dispose.
72
+ // Read by: dispatchPrompt, ensureSession, abortSessionViaApi, footer.
73
+ sessionId: string | undefined
74
+
75
+ // Stable first author for this thread runtime. Used for session-stable
76
+ // system prompt examples like `kimaki send --user ...` so notifications keep
77
+ // working without changing the cached system prompt on every follow-up.
78
+ sessionUsername: string | undefined
79
+
80
+ // FIFO queue of pending inputs waiting for kimaki-local dispatch.
81
+ // Normal user messages default to opencode queue mode; this queue is
82
+ // for explicit local-queue flows (for example /queue).
83
+ // Changes: enqueueItem (append), dequeueItem (head removal), clearQueueItems.
84
+ // Read by: runtime queue gating, hasQueue helpers, /queue command display.
85
+ queueItems: QueuedMessage[]
86
+
87
+ // Listener lifetime controller — scoped to the entire runtime lifetime,
88
+ // NOT per-prompt. Only aborted on dispose() or fatal error. Run abort
89
+ // never kills the listener — the SSE event loop stays alive across runs
90
+ // so subsequent prompts reuse the same listener.
91
+ // Changes: created in constructor, aborted only on dispose.
92
+ listenerController: AbortController | undefined
93
+
94
+ // Output dedup: tracks which part IDs have already been sent to Discord.
95
+ // Prevents resending the same tool output or text part on SSE reconnect.
96
+ // Lives at thread level because it accumulates
97
+ // across runs for the runtime's lifetime — never reset per-run.
98
+ // Changes: bootstrapped from DB on session resume, added on each part
99
+ // sent to Discord, removed on send failure, also updated in subtask flows.
100
+ // Read by: handleMainPart() dedup check, subtask routing.
101
+ sentPartIds: Set<string>
102
+ }
103
+
104
+ // ── Initial state factory ────────────────────────────────────────
105
+
106
+ export function initialThreadState(): ThreadRunState {
107
+ return {
108
+ sessionId: undefined,
109
+ sessionUsername: undefined,
110
+ queueItems: [],
111
+ listenerController: undefined,
112
+ sentPartIds: new Set(),
113
+ }
114
+ }
115
+
116
+ // ── Derived helpers (compute, never store) ───────────────────────
117
+
118
+ export function hasQueue(t: ThreadRunState): boolean {
119
+ return t.queueItems.length > 0
120
+ }
121
+
122
+ // ── Pure transition helpers ──────────────────────────────────────
123
+ // Immutable: produces new Map + new ThreadRunState object each time.
124
+
125
+ export function updateThread(
126
+ threadId: string,
127
+ updater: (t: ThreadRunState) => ThreadRunState,
128
+ ): void {
129
+ store.setState((s) => {
130
+ const existing = s.threads.get(threadId)
131
+ if (!existing) {
132
+ return s
133
+ }
134
+ const newThreads = new Map(s.threads)
135
+ newThreads.set(threadId, updater(existing))
136
+ return { threads: newThreads }
137
+ })
138
+ }
139
+
140
+ export function ensureThread(threadId: string): void {
141
+ if (store.getState().threads.has(threadId)) {
142
+ return
143
+ }
144
+ store.setState((s) => {
145
+ const newThreads = new Map(s.threads)
146
+ newThreads.set(threadId, initialThreadState())
147
+ return { threads: newThreads }
148
+ })
149
+ }
150
+
151
+ export function removeThread(threadId: string): void {
152
+ store.setState((s) => {
153
+ if (!s.threads.has(threadId)) {
154
+ return s
155
+ }
156
+ const newThreads = new Map(s.threads)
157
+ newThreads.delete(threadId)
158
+ return { threads: newThreads }
159
+ })
160
+ }
161
+
162
+ export function setSessionId(threadId: string, sessionId: string): void {
163
+ updateThread(threadId, (t) => ({ ...t, sessionId }))
164
+ }
165
+
166
+ export function setSessionUsername(threadId: string, username: string): void {
167
+ updateThread(threadId, (t) => {
168
+ if (t.sessionUsername) {
169
+ return t
170
+ }
171
+ return { ...t, sessionUsername: username }
172
+ })
173
+ }
174
+
175
+ export function enqueueItem(threadId: string, item: QueuedMessage): void {
176
+ updateThread(threadId, (t) => ({
177
+ ...t,
178
+ queueItems: [...t.queueItems, item],
179
+ }))
180
+ }
181
+
182
+ // Atomic dequeue: read + write in one setState call to prevent
183
+ // a concurrent enqueue between read and write from losing items.
184
+ export function dequeueItem(threadId: string): QueuedMessage | undefined {
185
+ let next: QueuedMessage | undefined
186
+ store.setState((s) => {
187
+ const t = s.threads.get(threadId)
188
+ if (!t || t.queueItems.length === 0) {
189
+ return s
190
+ }
191
+ const [head, ...rest] = t.queueItems
192
+ next = head
193
+ const newThreads = new Map(s.threads)
194
+ newThreads.set(threadId, { ...t, queueItems: rest })
195
+ return { threads: newThreads }
196
+ })
197
+ return next
198
+ }
199
+
200
+ export function clearQueueItems(threadId: string): void {
201
+ updateThread(threadId, (t) => ({ ...t, queueItems: [] }))
202
+ }
203
+
204
+ // ── Queries ──────────────────────────────────────────────────────
205
+
206
+ export function getThreadState(threadId: string): ThreadRunState | undefined {
207
+ return store.getState().threads.get(threadId)
208
+ }
209
+
210
+ export function getThreadIds(): string[] {
211
+ return [...store.getState().threads.keys()]
212
+ }