@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,397 @@
1
+ // Discord slash command and interaction handler.
2
+ // Processes all slash commands (/session, /resume, /fork, /model, /abort, etc.)
3
+ // and manages autocomplete, select menu interactions for the bot.
4
+ import { Events, MessageFlags, } from 'discord.js';
5
+ import { handleSessionCommand, handleSessionAutocomplete, } from './commands/session.js';
6
+ import { handleNewWorktreeCommand, handleNewWorktreeAutocomplete, } from './commands/new-worktree.js';
7
+ import { handleMergeWorktreeCommand, handleMergeWorktreeAutocomplete, } from './commands/merge-worktree.js';
8
+ import { handleToggleWorktreesCommand } from './commands/worktree-settings.js';
9
+ import { handleWorktreesCommand } from './commands/worktrees.js';
10
+ import { handleTasksCommand } from './commands/tasks.js';
11
+ import { handleResumeCommand, handleResumeAutocomplete, } from './commands/resume.js';
12
+ import { handleAddProjectCommand, handleAddProjectAutocomplete, } from './commands/add-project.js';
13
+ import { handleRemoveProjectCommand, handleRemoveProjectAutocomplete, } from './commands/remove-project.js';
14
+ import { handleCreateNewProjectCommand } from './commands/create-new-project.js';
15
+ import { handlePermissionButton } from './commands/permissions.js';
16
+ import { handleAbortCommand } from './commands/abort.js';
17
+ import { handleCompactCommand } from './commands/compact.js';
18
+ import { handleShareCommand } from './commands/share.js';
19
+ import { handleDiffCommand } from './commands/diff.js';
20
+ import { handleForkCommand, handleForkSelectMenu } from './commands/fork.js';
21
+ import { handleBtwCommand } from './commands/btw.js';
22
+ import { handleModelCommand, handleProviderSelectMenu, handleModelSelectMenu, handleModelScopeSelectMenu, } from './commands/model.js';
23
+ import { handleUnsetModelCommand } from './commands/unset-model.js';
24
+ import { handleLoginCommand, handleLoginSelect, handleLoginTextButton, handleLoginTextModalSubmit, handleLoginApiKeyButton, handleOAuthCodeButton, handleOAuthCodeModalSubmit, handleApiKeyModalSubmit, } from './commands/login.js';
25
+ import { handleTranscriptionApiKeyButton, handleTranscriptionApiKeyCommand, handleTranscriptionApiKeyModalSubmit, } from './commands/gemini-apikey.js';
26
+ import { handleAgentCommand, handleAgentSelectMenu, handleQuickAgentCommand, } from './commands/agent.js';
27
+ import { handleAskQuestionSelectMenu } from './commands/ask-question.js';
28
+ import { handleFileUploadButton, handleFileUploadModalSubmit, } from './commands/file-upload.js';
29
+ import { handleActionButton } from './commands/action-buttons.js';
30
+ import { handleHtmlActionButton } from './html-actions.js';
31
+ import { handleQueueCommand, handleClearQueueCommand, handleQueueCommandCommand, handleQueueCommandAutocomplete, } from './commands/queue.js';
32
+ import { handleUndoCommand, handleRedoCommand } from './commands/undo-redo.js';
33
+ import { handleUserCommand } from './commands/user-command.js';
34
+ import { handleVerbosityCommand, handleVerbositySelectMenu, } from './commands/verbosity.js';
35
+ import { handleRestartOpencodeServerCommand } from './commands/restart-opencode-server.js';
36
+ import { handleRunCommand } from './commands/run-command.js';
37
+ import { handleContextUsageCommand } from './commands/context-usage.js';
38
+ import { handleSessionIdCommand } from './commands/session-id.js';
39
+ import { handleUpgradeAndRestartCommand } from './commands/upgrade.js';
40
+ import { handleMcpCommand, handleMcpSelectMenu } from './commands/mcp.js';
41
+ import { handleScreenshareCommand, handleScreenshareStopCommand, } from './commands/screenshare.js';
42
+ import { handleModelVariantSelectMenu } from './commands/model.js';
43
+ import { handleModelVariantCommand, handleVariantQuickSelectMenu, handleVariantScopeSelectMenu, } from './commands/model-variant.js';
44
+ import { hasKimakiBotPermission } from './discord-utils.js';
45
+ import { createLogger, LogPrefix } from './logger.js';
46
+ import { notifyError } from './sentry.js';
47
+ const interactionLogger = createLogger(LogPrefix.INTERACTION);
48
+ export function registerInteractionHandler({ discordClient, appId, }) {
49
+ interactionLogger.log('[REGISTER] Interaction handler registered');
50
+ discordClient.on(Events.InteractionCreate, async (interaction) => {
51
+ try {
52
+ interactionLogger.log(`[INTERACTION] Received: ${interaction.type} - ${interaction.isChatInputCommand()
53
+ ? interaction.commandName
54
+ : interaction.isAutocomplete()
55
+ ? `autocomplete:${interaction.commandName}`
56
+ : 'other'}`);
57
+ if (interaction.isAutocomplete()) {
58
+ switch (interaction.commandName) {
59
+ case 'new-session':
60
+ await handleSessionAutocomplete({ interaction, appId });
61
+ return;
62
+ case 'resume':
63
+ await handleResumeAutocomplete({ interaction, appId });
64
+ return;
65
+ case 'add-project':
66
+ await handleAddProjectAutocomplete({ interaction, appId });
67
+ return;
68
+ case 'remove-project':
69
+ await handleRemoveProjectAutocomplete({ interaction, appId });
70
+ return;
71
+ case 'queue-command':
72
+ await handleQueueCommandAutocomplete({ interaction, appId });
73
+ return;
74
+ case 'new-worktree':
75
+ await handleNewWorktreeAutocomplete({ interaction, appId });
76
+ return;
77
+ case 'merge-worktree':
78
+ await handleMergeWorktreeAutocomplete({ interaction, appId });
79
+ return;
80
+ default:
81
+ await interaction.respond([]);
82
+ return;
83
+ }
84
+ }
85
+ if (interaction.isChatInputCommand()) {
86
+ interactionLogger.log(`[COMMAND] Processing: ${interaction.commandName}`);
87
+ if (!hasKimakiBotPermission(interaction.member, interaction.guild)) {
88
+ await interaction.reply({
89
+ content: `You don't have permission to use this command.\nTo use Kimaki, ask a server admin to give you the **Kimaki** role.`,
90
+ flags: MessageFlags.Ephemeral,
91
+ });
92
+ return;
93
+ }
94
+ switch (interaction.commandName) {
95
+ case 'new-session':
96
+ await handleSessionCommand({ command: interaction, appId });
97
+ return;
98
+ case 'new-worktree':
99
+ await handleNewWorktreeCommand({ command: interaction, appId });
100
+ return;
101
+ case 'merge-worktree':
102
+ await handleMergeWorktreeCommand({ command: interaction, appId });
103
+ return;
104
+ case 'toggle-worktrees':
105
+ await handleToggleWorktreesCommand({
106
+ command: interaction,
107
+ appId,
108
+ });
109
+ return;
110
+ case 'worktrees':
111
+ await handleWorktreesCommand({
112
+ command: interaction,
113
+ appId,
114
+ });
115
+ return;
116
+ case 'tasks':
117
+ await handleTasksCommand({
118
+ command: interaction,
119
+ appId,
120
+ });
121
+ return;
122
+ case 'resume':
123
+ await handleResumeCommand({ command: interaction, appId });
124
+ return;
125
+ case 'add-project':
126
+ await handleAddProjectCommand({ command: interaction, appId });
127
+ return;
128
+ case 'remove-project':
129
+ await handleRemoveProjectCommand({ command: interaction, appId });
130
+ return;
131
+ case 'create-new-project':
132
+ await handleCreateNewProjectCommand({
133
+ command: interaction,
134
+ appId,
135
+ });
136
+ return;
137
+ case 'abort':
138
+ await handleAbortCommand({ command: interaction, appId });
139
+ return;
140
+ case 'compact':
141
+ await handleCompactCommand({ command: interaction, appId });
142
+ return;
143
+ case 'share':
144
+ await handleShareCommand({ command: interaction, appId });
145
+ return;
146
+ case 'diff':
147
+ await handleDiffCommand({ command: interaction, appId });
148
+ return;
149
+ case 'fork':
150
+ await handleForkCommand(interaction);
151
+ return;
152
+ case 'btw':
153
+ await handleBtwCommand({ command: interaction, appId });
154
+ return;
155
+ case 'model':
156
+ await handleModelCommand({ interaction, appId });
157
+ return;
158
+ case 'model-variant':
159
+ await handleModelVariantCommand({ interaction, appId });
160
+ return;
161
+ case 'unset-model-override':
162
+ await handleUnsetModelCommand({ interaction, appId });
163
+ return;
164
+ case 'login':
165
+ await handleLoginCommand({ interaction, appId });
166
+ return;
167
+ case 'agent':
168
+ await handleAgentCommand({ interaction, appId });
169
+ return;
170
+ case 'queue':
171
+ await handleQueueCommand({ command: interaction, appId });
172
+ return;
173
+ case 'clear-queue':
174
+ await handleClearQueueCommand({ command: interaction, appId });
175
+ return;
176
+ case 'queue-command':
177
+ await handleQueueCommandCommand({ command: interaction, appId });
178
+ return;
179
+ case 'undo':
180
+ await handleUndoCommand({ command: interaction, appId });
181
+ return;
182
+ case 'redo':
183
+ await handleRedoCommand({ command: interaction, appId });
184
+ return;
185
+ case 'verbosity':
186
+ await handleVerbosityCommand({ command: interaction, appId });
187
+ return;
188
+ case 'restart-opencode-server':
189
+ await handleRestartOpencodeServerCommand({
190
+ command: interaction,
191
+ appId,
192
+ });
193
+ return;
194
+ case 'run-shell-command':
195
+ await handleRunCommand({ command: interaction, appId });
196
+ return;
197
+ case 'context-usage':
198
+ await handleContextUsageCommand({ command: interaction, appId });
199
+ return;
200
+ case 'session-id':
201
+ await handleSessionIdCommand({ command: interaction, appId });
202
+ return;
203
+ case 'upgrade-and-restart':
204
+ await handleUpgradeAndRestartCommand({
205
+ command: interaction,
206
+ appId,
207
+ });
208
+ return;
209
+ case 'transcription-key':
210
+ await handleTranscriptionApiKeyCommand({
211
+ interaction,
212
+ appId,
213
+ });
214
+ return;
215
+ case 'mcp':
216
+ await handleMcpCommand({ command: interaction, appId });
217
+ return;
218
+ case 'screenshare':
219
+ await handleScreenshareCommand({ command: interaction, appId });
220
+ return;
221
+ case 'screenshare-stop':
222
+ await handleScreenshareStopCommand({
223
+ command: interaction,
224
+ appId,
225
+ });
226
+ return;
227
+ }
228
+ // Handle quick agent commands (ending with -agent suffix, but not the base /agent command)
229
+ if (interaction.commandName.endsWith('-agent') &&
230
+ interaction.commandName !== 'agent') {
231
+ await handleQuickAgentCommand({ command: interaction, appId });
232
+ return;
233
+ }
234
+ // Handle user-defined commands (ending with -cmd, -skill, or -mcp-prompt suffix)
235
+ if (interaction.commandName.endsWith('-cmd') ||
236
+ interaction.commandName.endsWith('-skill') ||
237
+ interaction.commandName.endsWith('-mcp-prompt')) {
238
+ await handleUserCommand({ command: interaction, appId });
239
+ return;
240
+ }
241
+ return;
242
+ }
243
+ if (interaction.isButton()) {
244
+ if (!hasKimakiBotPermission(interaction.member, interaction.guild)) {
245
+ await interaction.reply({
246
+ content: `You don't have permission to use this.\nTo use Kimaki, ask a server admin to give you the **Kimaki** role.`,
247
+ flags: MessageFlags.Ephemeral,
248
+ });
249
+ return;
250
+ }
251
+ const customId = interaction.customId;
252
+ if (customId.startsWith('transcription_apikey:')) {
253
+ await handleTranscriptionApiKeyButton(interaction);
254
+ return;
255
+ }
256
+ if (customId.startsWith('permission_once:') ||
257
+ customId.startsWith('permission_always:') ||
258
+ customId.startsWith('permission_reject:')) {
259
+ await handlePermissionButton(interaction);
260
+ return;
261
+ }
262
+ if (customId.startsWith('file_upload_btn:')) {
263
+ await handleFileUploadButton(interaction);
264
+ return;
265
+ }
266
+ if (customId.startsWith('login_text_btn:')) {
267
+ await handleLoginTextButton(interaction);
268
+ return;
269
+ }
270
+ if (customId.startsWith('login_apikey_btn:')) {
271
+ await handleLoginApiKeyButton(interaction);
272
+ return;
273
+ }
274
+ if (customId.startsWith('login_oauth_code_btn:')) {
275
+ await handleOAuthCodeButton(interaction);
276
+ return;
277
+ }
278
+ if (customId.startsWith('action_button:')) {
279
+ await handleActionButton(interaction);
280
+ return;
281
+ }
282
+ if (customId.startsWith('html_action:')) {
283
+ await handleHtmlActionButton(interaction);
284
+ return;
285
+ }
286
+ return;
287
+ }
288
+ if (interaction.isStringSelectMenu()) {
289
+ if (!hasKimakiBotPermission(interaction.member, interaction.guild)) {
290
+ await interaction.reply({
291
+ content: `You don't have permission to use this.\nTo use Kimaki, ask a server admin to give you the **Kimaki** role.`,
292
+ flags: MessageFlags.Ephemeral,
293
+ });
294
+ return;
295
+ }
296
+ const customId = interaction.customId;
297
+ if (customId.startsWith('fork_select:')) {
298
+ await handleForkSelectMenu(interaction);
299
+ return;
300
+ }
301
+ if (customId.startsWith('model_provider:')) {
302
+ await handleProviderSelectMenu(interaction);
303
+ return;
304
+ }
305
+ if (customId.startsWith('model_select:')) {
306
+ await handleModelSelectMenu(interaction);
307
+ return;
308
+ }
309
+ if (customId.startsWith('model_scope:')) {
310
+ await handleModelScopeSelectMenu(interaction);
311
+ return;
312
+ }
313
+ if (customId.startsWith('model_variant:')) {
314
+ await handleModelVariantSelectMenu(interaction);
315
+ return;
316
+ }
317
+ if (customId.startsWith('variant_quick:')) {
318
+ await handleVariantQuickSelectMenu(interaction);
319
+ return;
320
+ }
321
+ if (customId.startsWith('variant_scope:')) {
322
+ await handleVariantScopeSelectMenu(interaction);
323
+ return;
324
+ }
325
+ if (customId.startsWith('agent_select:')) {
326
+ await handleAgentSelectMenu(interaction);
327
+ return;
328
+ }
329
+ if (customId.startsWith('verbosity_select:')) {
330
+ await handleVerbositySelectMenu(interaction);
331
+ return;
332
+ }
333
+ if (customId.startsWith('ask_question:')) {
334
+ await handleAskQuestionSelectMenu(interaction);
335
+ return;
336
+ }
337
+ if (customId.startsWith('mcp_toggle:')) {
338
+ await handleMcpSelectMenu(interaction);
339
+ return;
340
+ }
341
+ if (customId.startsWith('login_select:')) {
342
+ await handleLoginSelect(interaction);
343
+ return;
344
+ }
345
+ return;
346
+ }
347
+ if (interaction.isModalSubmit()) {
348
+ if (!hasKimakiBotPermission(interaction.member, interaction.guild)) {
349
+ await interaction.reply({
350
+ content: `You don't have permission to use this.\nTo use Kimaki, ask a server admin to give you the **Kimaki** role.`,
351
+ flags: MessageFlags.Ephemeral,
352
+ });
353
+ return;
354
+ }
355
+ const customId = interaction.customId;
356
+ if (customId.startsWith('login_apikey:')) {
357
+ await handleApiKeyModalSubmit(interaction);
358
+ return;
359
+ }
360
+ if (customId.startsWith('login_text:')) {
361
+ await handleLoginTextModalSubmit(interaction);
362
+ return;
363
+ }
364
+ if (customId.startsWith('login_oauth_code:')) {
365
+ await handleOAuthCodeModalSubmit(interaction);
366
+ return;
367
+ }
368
+ if (customId.startsWith('transcription_apikey_modal:')) {
369
+ await handleTranscriptionApiKeyModalSubmit(interaction);
370
+ return;
371
+ }
372
+ if (customId.startsWith('file_upload_modal:')) {
373
+ await handleFileUploadModalSubmit(interaction);
374
+ return;
375
+ }
376
+ return;
377
+ }
378
+ }
379
+ catch (error) {
380
+ interactionLogger.error('[INTERACTION] Error handling interaction:', error);
381
+ void notifyError(error, 'Interaction handler error');
382
+ try {
383
+ if (interaction.isRepliable() &&
384
+ !interaction.replied &&
385
+ !interaction.deferred) {
386
+ await interaction.reply({
387
+ content: 'An error occurred processing this command.',
388
+ flags: MessageFlags.Ephemeral,
389
+ });
390
+ }
391
+ }
392
+ catch (replyError) {
393
+ interactionLogger.error('[INTERACTION] Failed to send error reply:', replyError);
394
+ }
395
+ }
396
+ });
397
+ }
@@ -0,0 +1,252 @@
1
+ // IPC polling bridge between the opencode plugin and the Discord bot.
2
+ // The plugin inserts rows into ipc_requests (via Prisma). This module polls
3
+ // that table, claims pending rows atomically, and dispatches them by type.
4
+ // Replaces the old HTTP lock-server approach with DB-based IPC.
5
+ import * as errore from 'errore';
6
+ import { createTaggedError } from 'errore';
7
+ import { claimPendingIpcRequests, completeIpcRequest, cancelAllPendingIpcRequests, cancelStaleProcessingRequests, } from './database.js';
8
+ import { showFileUploadButton } from './commands/file-upload.js';
9
+ import { queueActionButtonsRequest } from './commands/action-buttons.js';
10
+ import { createLogger, LogPrefix } from './logger.js';
11
+ import { notifyError } from './sentry.js';
12
+ const ipcLogger = createLogger(LogPrefix.IPC);
13
+ // ── Tagged errors ────────────────────────────────────────────────────────
14
+ class IpcDispatchError extends createTaggedError({
15
+ name: 'IpcDispatchError',
16
+ message: 'IPC dispatch failed for request $requestId: $reason',
17
+ }) {
18
+ }
19
+ // ── Button parsing ───────────────────────────────────────────────────────
20
+ const VALID_COLORS = new Set([
21
+ 'white',
22
+ 'blue',
23
+ 'green',
24
+ 'red',
25
+ ]);
26
+ function parseButtons(raw) {
27
+ if (!Array.isArray(raw))
28
+ return [];
29
+ const results = [];
30
+ for (const value of raw) {
31
+ if (!value || typeof value !== 'object')
32
+ continue;
33
+ const label = (typeof value.label === 'string' ? value.label : '')
34
+ .trim()
35
+ .slice(0, 80);
36
+ if (!label)
37
+ continue;
38
+ const color = typeof value.color === 'string' &&
39
+ VALID_COLORS.has(value.color)
40
+ ? value.color
41
+ : undefined;
42
+ results.push({ label, color });
43
+ if (results.length >= 3)
44
+ break;
45
+ }
46
+ return results;
47
+ }
48
+ async function dispatchRequest({ req, discordClient, }) {
49
+ switch (req.type) {
50
+ case 'file_upload': {
51
+ const parsed = errore.try({
52
+ try: () => JSON.parse(req.payload),
53
+ catch: (e) => new IpcDispatchError({
54
+ requestId: req.id,
55
+ reason: 'Invalid payload JSON',
56
+ cause: e,
57
+ }),
58
+ });
59
+ if (parsed instanceof Error) {
60
+ await completeIpcRequest({
61
+ id: req.id,
62
+ response: JSON.stringify({ error: parsed.message }),
63
+ });
64
+ return parsed;
65
+ }
66
+ const thread = await discordClient.channels
67
+ .fetch(req.thread_id)
68
+ .catch((e) => new IpcDispatchError({
69
+ requestId: req.id,
70
+ reason: 'Thread fetch failed',
71
+ cause: e,
72
+ }));
73
+ if (thread instanceof Error) {
74
+ await completeIpcRequest({
75
+ id: req.id,
76
+ response: JSON.stringify({ error: 'Thread not found' }),
77
+ });
78
+ return thread;
79
+ }
80
+ if (!thread?.isThread()) {
81
+ await completeIpcRequest({
82
+ id: req.id,
83
+ response: JSON.stringify({ error: 'Thread not found' }),
84
+ });
85
+ return new IpcDispatchError({
86
+ requestId: req.id,
87
+ reason: 'Channel is not a thread',
88
+ });
89
+ }
90
+ // Fire-and-forget: showFileUploadButton waits for user interaction
91
+ // (button click + modal + file download) which can take minutes.
92
+ // Don't block the dispatch loop — complete the IPC request asynchronously.
93
+ showFileUploadButton({
94
+ thread,
95
+ sessionId: req.session_id,
96
+ directory: parsed.directory || '',
97
+ prompt: parsed.prompt || 'Please upload files',
98
+ maxFiles: Math.min(10, Math.max(1, parsed.maxFiles || 5)),
99
+ })
100
+ .then((filePaths) => {
101
+ return completeIpcRequest({
102
+ id: req.id,
103
+ response: JSON.stringify({ filePaths }),
104
+ });
105
+ })
106
+ .catch((e) => {
107
+ ipcLogger.error('[IPC] File upload error:', e instanceof Error ? e.message : String(e));
108
+ return completeIpcRequest({
109
+ id: req.id,
110
+ response: JSON.stringify({
111
+ error: e instanceof Error ? e.message : 'File upload failed',
112
+ }),
113
+ });
114
+ })
115
+ .catch((e) => {
116
+ void notifyError(e, 'IPC file upload completion update failed');
117
+ });
118
+ return;
119
+ }
120
+ case 'action_buttons': {
121
+ const parsed = errore.try({
122
+ try: () => JSON.parse(req.payload),
123
+ catch: (e) => new IpcDispatchError({
124
+ requestId: req.id,
125
+ reason: 'Invalid payload JSON',
126
+ cause: e,
127
+ }),
128
+ });
129
+ if (parsed instanceof Error) {
130
+ await completeIpcRequest({
131
+ id: req.id,
132
+ response: JSON.stringify({ error: parsed.message }),
133
+ });
134
+ return parsed;
135
+ }
136
+ const buttons = parseButtons(parsed.buttons);
137
+ if (buttons.length === 0) {
138
+ await completeIpcRequest({
139
+ id: req.id,
140
+ response: JSON.stringify({ error: 'No valid buttons' }),
141
+ });
142
+ return;
143
+ }
144
+ const thread = await discordClient.channels
145
+ .fetch(req.thread_id)
146
+ .catch((e) => new IpcDispatchError({
147
+ requestId: req.id,
148
+ reason: 'Thread fetch failed',
149
+ cause: e,
150
+ }));
151
+ if (thread instanceof Error) {
152
+ await completeIpcRequest({
153
+ id: req.id,
154
+ response: JSON.stringify({ error: 'Thread not found' }),
155
+ });
156
+ return thread;
157
+ }
158
+ if (!thread?.isThread()) {
159
+ await completeIpcRequest({
160
+ id: req.id,
161
+ response: JSON.stringify({ error: 'Thread not found' }),
162
+ });
163
+ return new IpcDispatchError({
164
+ requestId: req.id,
165
+ reason: 'Channel is not a thread',
166
+ });
167
+ }
168
+ queueActionButtonsRequest({
169
+ sessionId: req.session_id,
170
+ threadId: req.thread_id,
171
+ directory: parsed.directory || '',
172
+ buttons,
173
+ });
174
+ await completeIpcRequest({
175
+ id: req.id,
176
+ response: JSON.stringify({ ok: true }),
177
+ });
178
+ return;
179
+ }
180
+ default: {
181
+ await completeIpcRequest({
182
+ id: req.id,
183
+ response: JSON.stringify({ error: `Unknown IPC type: ${req.type}` }),
184
+ });
185
+ return;
186
+ }
187
+ }
188
+ }
189
+ // ── Polling lifecycle ────────────────────────────────────────────────────
190
+ let pollingInterval = null;
191
+ // Cancel requests stuck in 'processing' longer than 24 hours. Users often
192
+ // come back the next day to click permission/question/file-upload buttons,
193
+ // so we keep IPC rows alive for a full day. Checked every 30 seconds.
194
+ const STALE_TTL_MS = 24 * 60 * 60 * 1000;
195
+ const STALE_CHECK_INTERVAL_MS = 30 * 1000;
196
+ let lastStaleCheck = 0;
197
+ /**
198
+ * Start polling the ipc_requests table for pending requests from the plugin.
199
+ * Claims rows atomically (pending -> processing) to prevent duplicate dispatch.
200
+ * Uses an in-flight guard to prevent overlapping poll ticks.
201
+ */
202
+ export async function startIpcPolling({ discordClient, }) {
203
+ // Clean up stale requests from previous runs before first poll tick
204
+ await cancelAllPendingIpcRequests().catch((e) => {
205
+ ipcLogger.warn('Failed to cancel stale IPC requests:', e.message);
206
+ void notifyError(e, 'Failed to cancel stale IPC requests');
207
+ });
208
+ let polling = false;
209
+ pollingInterval = setInterval(async () => {
210
+ if (polling)
211
+ return;
212
+ polling = true;
213
+ // Periodically sweep requests stuck in 'processing' past the TTL
214
+ const now = Date.now();
215
+ if (now - lastStaleCheck > STALE_CHECK_INTERVAL_MS) {
216
+ lastStaleCheck = now;
217
+ await cancelStaleProcessingRequests({ ttlMs: STALE_TTL_MS }).catch((e) => {
218
+ ipcLogger.warn('Stale sweep failed:', e.message);
219
+ void notifyError(e, 'IPC stale sweep failed');
220
+ });
221
+ }
222
+ const claimed = await claimPendingIpcRequests().catch((e) => new IpcDispatchError({
223
+ requestId: 'poll',
224
+ reason: 'Claim failed',
225
+ cause: e,
226
+ }));
227
+ if (claimed instanceof Error) {
228
+ ipcLogger.error('IPC claim failed:', claimed.message);
229
+ void notifyError(claimed, 'IPC claim failed');
230
+ polling = false;
231
+ return;
232
+ }
233
+ for (const req of claimed) {
234
+ const result = await dispatchRequest({ req, discordClient }).catch((e) => new IpcDispatchError({
235
+ requestId: req.id,
236
+ reason: 'Dispatch threw',
237
+ cause: e,
238
+ }));
239
+ if (result instanceof Error) {
240
+ ipcLogger.error(`IPC dispatch error for ${req.type}:`, result.message);
241
+ void notifyError(result, `IPC dispatch error for ${req.type}`);
242
+ }
243
+ }
244
+ polling = false;
245
+ }, 200);
246
+ }
247
+ export function stopIpcPolling() {
248
+ if (!pollingInterval)
249
+ return;
250
+ clearInterval(pollingInterval);
251
+ pollingInterval = null;
252
+ }