@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,435 @@
1
+ // Shared e2e test utilities for session cleanup, server cleanup, and
2
+ // Discord message polling helpers.
3
+ // Uses directory + start timestamp double-filter to ensure we only
4
+ // delete sessions created by this specific test run, never real user sessions.
5
+ //
6
+ // Prefers using the existing opencode client (already running server) to avoid
7
+ // spawning a new server process during teardown. Falls back to initializing
8
+ // a new server only if no existing client is available.
9
+
10
+ import { execSync } from 'node:child_process'
11
+ import fs from 'node:fs'
12
+ import path from 'node:path'
13
+ import type { APIMessage } from 'discord.js'
14
+
15
+ /**
16
+ * Deterministic port from a string key (channel ID, test file name, etc.).
17
+ * Uses a hash to pick a stable port in range 53000-54999, avoiding overlap
18
+ * with queue-advanced tests (51000-52999) and getLockPort (30000-39999).
19
+ * Replaces the old TOCTOU-prone pattern of binding port 0, reading the
20
+ * assigned port, closing, then rebinding — which races under parallel vitest.
21
+ */
22
+ export function chooseLockPort({ key }: { key: string }): number {
23
+ let hash = 0
24
+ for (let i = 0; i < key.length; i++) {
25
+ const char = key.charCodeAt(i)
26
+ hash = (hash << 5) - hash + char
27
+ hash |= 0
28
+ }
29
+ return 53_000 + (Math.abs(hash) % 2_000)
30
+ }
31
+ /**
32
+ * Initialize a git repo with a `main` branch and empty initial commit.
33
+ * E2e tests create project directories under tmp/ which inherit the parent
34
+ * repo's git state. On CI (detached HEAD), `git symbolic-ref --short HEAD`
35
+ * returns empty, breaking footer snapshots that expect a branch name.
36
+ * Calling this in each test project directory gives it its own repo on `main`.
37
+ */
38
+ export function initTestGitRepo(directory: string): void {
39
+ const isRepo = fs.existsSync(path.join(directory, '.git'))
40
+ if (isRepo) {
41
+ return
42
+ }
43
+ execSync('git init -b main', { cwd: directory, stdio: 'pipe' })
44
+ execSync('git config user.email "test@test.com"', { cwd: directory, stdio: 'pipe' })
45
+ execSync('git config user.name "Test"', { cwd: directory, stdio: 'pipe' })
46
+ execSync('git commit --allow-empty -m "init"', { cwd: directory, stdio: 'pipe' })
47
+ }
48
+
49
+ import type { DigitalDiscord } from 'discord-digital-twin/src'
50
+ import {
51
+ getOpencodeClient,
52
+ initializeOpencodeForDirectory,
53
+ } from './opencode.js'
54
+ import {
55
+ getThreadState,
56
+ type ThreadRunState,
57
+ } from './session-handler/thread-runtime-state.js'
58
+
59
+ /**
60
+ * Delete all opencode sessions created during a test run.
61
+ * Uses directory + start timestamp to scope strictly to test sessions.
62
+ * Prefers the existing in-memory client to avoid spawning a new server in teardown.
63
+ * Errors are caught silently — cleanup should never fail tests.
64
+ */
65
+ export async function cleanupTestSessions({
66
+ projectDirectory,
67
+ testStartTime,
68
+ }: {
69
+ projectDirectory: string
70
+ testStartTime: number
71
+ }) {
72
+ // Prefer existing client to avoid spawning a new server during teardown
73
+ const existingClient = getOpencodeClient(projectDirectory)
74
+ const client = existingClient || await (async () => {
75
+ const getClient = await initializeOpencodeForDirectory(projectDirectory).catch(() => {
76
+ return null
77
+ })
78
+ if (!getClient || getClient instanceof Error) return null
79
+ return getClient()
80
+ })()
81
+ if (!client) return
82
+
83
+ const listResult = await client.session.list({
84
+ directory: projectDirectory,
85
+ start: testStartTime,
86
+ limit: 1000,
87
+ }).catch(() => {
88
+ return null
89
+ })
90
+ const sessions = listResult?.data ?? []
91
+ await Promise.all(
92
+ sessions.map((s) => {
93
+ return client.session.delete({
94
+ sessionID: s.id,
95
+ directory: projectDirectory,
96
+ }).catch(() => {
97
+ return
98
+ })
99
+ }),
100
+ )
101
+ }
102
+
103
+ // ── Discord message polling helpers ──────────────────────────────
104
+ // Used by e2e tests to wait for bot responses. All poll at 100ms
105
+ // intervals with configurable timeouts.
106
+
107
+ /** Poll getMessages until we see at least `count` bot messages. */
108
+ export async function waitForBotMessageCount({
109
+ discord,
110
+ threadId,
111
+ count,
112
+ timeout,
113
+ }: {
114
+ discord: DigitalDiscord
115
+ threadId: string
116
+ count: number
117
+ timeout: number
118
+ }): Promise<APIMessage[]> {
119
+ const start = Date.now()
120
+ while (Date.now() - start < timeout) {
121
+ const messages = await discord.thread(threadId).getMessages()
122
+ const botMessages = messages.filter((m) => {
123
+ return m.author.id === discord.botUserId
124
+ })
125
+ if (botMessages.length >= count) {
126
+ return messages
127
+ }
128
+ await new Promise((r) => {
129
+ setTimeout(r, 100)
130
+ })
131
+ }
132
+ throw new Error(
133
+ `Timed out waiting for ${count} bot messages in thread ${threadId}`,
134
+ )
135
+ }
136
+
137
+ /**
138
+ * Poll until a bot message appears after a user message containing the given text.
139
+ * Content-aware: finds the user message by content, then checks for a bot reply after it.
140
+ */
141
+ export async function waitForBotReplyAfterUserMessage({
142
+ discord,
143
+ threadId,
144
+ userId,
145
+ userMessageIncludes,
146
+ timeout,
147
+ }: {
148
+ discord: DigitalDiscord
149
+ threadId: string
150
+ userId: string
151
+ userMessageIncludes: string
152
+ timeout: number
153
+ }): Promise<APIMessage[]> {
154
+ const start = Date.now()
155
+ while (Date.now() - start < timeout) {
156
+ const messages = await discord.thread(threadId).getMessages()
157
+ const userMessageIndex = messages.findIndex((message) => {
158
+ return (
159
+ message.author.id === userId &&
160
+ message.content.includes(userMessageIncludes)
161
+ )
162
+ })
163
+ const botReplyIndex = messages.findIndex((message, index) => {
164
+ return index > userMessageIndex && message.author.id === discord.botUserId
165
+ })
166
+ if (userMessageIndex >= 0 && botReplyIndex >= 0) {
167
+ return messages
168
+ }
169
+ await new Promise((resolve) => {
170
+ setTimeout(resolve, 100)
171
+ })
172
+ }
173
+ throw new Error(
174
+ `Timed out waiting for bot reply after user message containing "${userMessageIncludes}" in thread ${threadId}`,
175
+ )
176
+ }
177
+
178
+ /**
179
+ * Poll until a bot message containing specific text appears.
180
+ * Optionally scoped to appear after a specific user message.
181
+ */
182
+ export async function waitForBotMessageContaining({
183
+ discord,
184
+ threadId,
185
+ userId,
186
+ text,
187
+ afterUserMessageIncludes,
188
+ afterMessageId,
189
+ timeout,
190
+ }: {
191
+ discord: DigitalDiscord
192
+ threadId: string
193
+ userId?: string
194
+ text: string
195
+ afterUserMessageIncludes?: string
196
+ afterMessageId?: string
197
+ timeout: number
198
+ }): Promise<APIMessage[]> {
199
+ const start = Date.now()
200
+ let lastMessages: APIMessage[] = []
201
+ while (Date.now() - start < timeout) {
202
+ const messages = await discord.thread(threadId).getMessages()
203
+ lastMessages = messages
204
+ const afterIndex = (() => {
205
+ if (afterMessageId) {
206
+ return messages.findLastIndex((message) => {
207
+ return message.id === afterMessageId
208
+ })
209
+ }
210
+ if (afterUserMessageIncludes && userId) {
211
+ return messages.findLastIndex((message) => {
212
+ return (
213
+ message.author.id === userId &&
214
+ message.content.includes(afterUserMessageIncludes)
215
+ )
216
+ })
217
+ }
218
+ return -1
219
+ })()
220
+ // If the anchor user message hasn't appeared yet, skip this iteration
221
+ // to avoid false-positives from old bot messages matching `text`.
222
+ if ((afterUserMessageIncludes || afterMessageId) && afterIndex === -1) {
223
+ await new Promise((resolve) => {
224
+ setTimeout(resolve, 100)
225
+ })
226
+ continue
227
+ }
228
+ const match = messages.find((message, index) => {
229
+ if ((afterUserMessageIncludes || afterMessageId) && afterIndex >= 0 && index <= afterIndex) {
230
+ return false
231
+ }
232
+ return (
233
+ message.author.id === discord.botUserId &&
234
+ message.content.includes(text)
235
+ )
236
+ })
237
+ if (match) {
238
+ return messages
239
+ }
240
+ await new Promise((resolve) => {
241
+ setTimeout(resolve, 100)
242
+ })
243
+ }
244
+ const recent = lastMessages
245
+ .slice(-12)
246
+ .map((message) => {
247
+ const role = message.author.id === discord.botUserId ? 'bot' : 'user'
248
+ return `${role}: ${message.content.slice(0, 120)}`
249
+ })
250
+ .join('\n')
251
+ throw new Error(
252
+ `Timed out waiting for bot message containing "${text}" in thread ${threadId}. Recent messages:\n${recent}`,
253
+ )
254
+ }
255
+
256
+ /** Poll until a specific message id appears in thread history. */
257
+ export async function waitForMessageById({
258
+ discord,
259
+ threadId,
260
+ messageId,
261
+ timeout,
262
+ }: {
263
+ discord: DigitalDiscord
264
+ threadId: string
265
+ messageId: string
266
+ timeout: number
267
+ }): Promise<APIMessage> {
268
+ const start = Date.now()
269
+ while (Date.now() - start < timeout) {
270
+ const messages = await discord.thread(threadId).getMessages()
271
+ const message = messages.find((candidate) => {
272
+ return candidate.id === messageId
273
+ })
274
+ if (message) {
275
+ return message
276
+ }
277
+ await new Promise((resolve) => {
278
+ setTimeout(resolve, 100)
279
+ })
280
+ }
281
+
282
+ throw new Error(
283
+ `Timed out waiting for message ${messageId} in thread ${threadId}`,
284
+ )
285
+ }
286
+
287
+ function isFooterMessage({
288
+ message,
289
+ botUserId,
290
+ }: {
291
+ message: APIMessage
292
+ botUserId: string
293
+ }): boolean {
294
+ if (message.author.id !== botUserId) {
295
+ return false
296
+ }
297
+ if (!message.content.startsWith('*')) {
298
+ return false
299
+ }
300
+ return message.content.includes('⋅')
301
+ }
302
+
303
+ /**
304
+ * Poll until a footer message appears, optionally after an anchor message.
305
+ * Useful for stabilizing snapshots by waiting for run completion metadata.
306
+ */
307
+ export async function waitForFooterMessage({
308
+ discord,
309
+ threadId,
310
+ timeout,
311
+ afterMessageIncludes,
312
+ afterAuthorId,
313
+ }: {
314
+ discord: DigitalDiscord
315
+ threadId: string
316
+ timeout: number
317
+ afterMessageIncludes?: string
318
+ afterAuthorId?: string
319
+ }): Promise<APIMessage[]> {
320
+ const start = Date.now()
321
+ let lastMessages: APIMessage[] = []
322
+ while (Date.now() - start < timeout) {
323
+ const messages = await discord.thread(threadId).getMessages()
324
+ lastMessages = messages
325
+ const afterIndex = afterMessageIncludes
326
+ ? messages.findLastIndex((message) => {
327
+ if (!message.content.includes(afterMessageIncludes)) {
328
+ return false
329
+ }
330
+ if (!afterAuthorId) {
331
+ return true
332
+ }
333
+ return message.author.id === afterAuthorId
334
+ })
335
+ : -1
336
+ if (afterMessageIncludes && afterIndex === -1) {
337
+ await new Promise((resolve) => {
338
+ setTimeout(resolve, 100)
339
+ })
340
+ continue
341
+ }
342
+ const footer = messages.find((message, index) => {
343
+ if (afterIndex >= 0 && index <= afterIndex) {
344
+ return false
345
+ }
346
+ return isFooterMessage({ message, botUserId: discord.botUserId })
347
+ })
348
+ if (footer) {
349
+ return messages
350
+ }
351
+ await new Promise((resolve) => {
352
+ setTimeout(resolve, 100)
353
+ })
354
+ }
355
+
356
+ const recent = lastMessages
357
+ .slice(-12)
358
+ .map((message) => {
359
+ const role = message.author.id === discord.botUserId ? 'bot' : 'user'
360
+ return `${role}: ${message.content.slice(0, 120)}`
361
+ })
362
+ .join('\n')
363
+ const anchorText = afterMessageIncludes || 'start'
364
+ throw new Error(
365
+ `Timed out waiting for footer after "${anchorText}" in thread ${threadId}. Recent messages:\n${recent}`,
366
+ )
367
+ }
368
+
369
+ // ── Thread state polling helpers ─────────────────────────────────
370
+ // Used by e2e tests to assert on queue and session-state snapshots.
371
+
372
+ /**
373
+ * Poll until thread has at least `count` items in its queue.
374
+ */
375
+ export async function waitForThreadQueueLength({
376
+ threadId,
377
+ count,
378
+ timeout,
379
+ }: {
380
+ threadId: string
381
+ count: number
382
+ timeout: number
383
+ }): Promise<ThreadRunState> {
384
+ const start = Date.now()
385
+ while (Date.now() - start < timeout) {
386
+ const state = getThreadState(threadId)
387
+ if (state && state.queueItems.length >= count) {
388
+ return state
389
+ }
390
+ await new Promise((resolve) => {
391
+ setTimeout(resolve, 50)
392
+ })
393
+ }
394
+ const finalState = getThreadState(threadId)
395
+ const currentLength = finalState?.queueItems.length ?? 0
396
+ throw new Error(
397
+ `Timed out waiting for thread ${threadId} queue length >= ${count}. Current length: ${currentLength}`,
398
+ )
399
+ }
400
+
401
+ /**
402
+ * Poll until a custom predicate on ThreadRunState returns true.
403
+ * Use this for compound assertions against thread state snapshots.
404
+ */
405
+ export async function waitForThreadState({
406
+ threadId,
407
+ predicate,
408
+ timeout,
409
+ description,
410
+ }: {
411
+ threadId: string
412
+ predicate: (state: ThreadRunState) => boolean
413
+ timeout: number
414
+ /** Human-readable description for timeout error messages */
415
+ description?: string
416
+ }): Promise<ThreadRunState> {
417
+ const start = Date.now()
418
+ while (Date.now() - start < timeout) {
419
+ const state = getThreadState(threadId)
420
+ if (state && predicate(state)) {
421
+ return state
422
+ }
423
+ await new Promise((resolve) => {
424
+ setTimeout(resolve, 50)
425
+ })
426
+ }
427
+ const finalState = getThreadState(threadId)
428
+ const desc = description ?? 'custom predicate'
429
+ const queueLen = finalState?.queueItems.length ?? 0
430
+ const sessionId = finalState?.sessionId ?? 'none'
431
+ throw new Error(
432
+ `Timed out waiting for thread ${threadId} (${desc}). ` +
433
+ `Current: queue=${queueLen}, sessionId=${sessionId}`,
434
+ )
435
+ }
@@ -0,0 +1,61 @@
1
+ // Utilities for extracting and matching model variant (thinking level) values
2
+ // from the provider.list() API response. Used by model selector and session handler
3
+ // to validate variant preferences against what the current model actually supports.
4
+
5
+ export type ThinkingProvider = {
6
+ id: string
7
+ models?: Record<string, unknown>
8
+ }
9
+
10
+ function getModelVariants(model: unknown): Record<string, unknown> | undefined {
11
+ if (!model || typeof model !== 'object') {
12
+ return undefined
13
+ }
14
+
15
+ const variants = (model as { variants?: unknown }).variants
16
+ if (!variants || typeof variants !== 'object') {
17
+ return undefined
18
+ }
19
+
20
+ return variants as Record<string, unknown>
21
+ }
22
+
23
+ export function getThinkingValuesForModel({
24
+ providers,
25
+ providerId,
26
+ modelId,
27
+ }: {
28
+ providers: ThinkingProvider[]
29
+ providerId: string
30
+ modelId: string
31
+ }): string[] {
32
+ const provider = providers.find((candidateProvider) => {
33
+ return candidateProvider.id === providerId
34
+ })
35
+ const model = provider?.models?.[modelId]
36
+ const variants = getModelVariants(model)
37
+ if (!variants) {
38
+ return []
39
+ }
40
+
41
+ return Object.keys(variants).filter((variant) => {
42
+ return variant.trim().length > 0
43
+ })
44
+ }
45
+
46
+ export function matchThinkingValue({
47
+ requestedValue,
48
+ availableValues,
49
+ }: {
50
+ requestedValue: string
51
+ availableValues: string[]
52
+ }): string | undefined {
53
+ const normalizedRequestedValue = requestedValue.trim().toLowerCase()
54
+ if (!normalizedRequestedValue) {
55
+ return undefined
56
+ }
57
+
58
+ return availableValues.find((availableValue) => {
59
+ return availableValue.toLowerCase() === normalizedRequestedValue
60
+ })
61
+ }