@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,326 @@
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
+
6
+ import * as errore from 'errore'
7
+ import { createTaggedError } from 'errore'
8
+ import type { Client } from 'discord.js'
9
+ import {
10
+ claimPendingIpcRequests,
11
+ completeIpcRequest,
12
+ cancelAllPendingIpcRequests,
13
+ cancelStaleProcessingRequests,
14
+ } from './database.js'
15
+ import { showFileUploadButton } from './commands/file-upload.js'
16
+ import { queueActionButtonsRequest } from './commands/action-buttons.js'
17
+ import type { ActionButtonColor } from './commands/action-buttons.js'
18
+ import { createLogger, LogPrefix } from './logger.js'
19
+ import { notifyError } from './sentry.js'
20
+
21
+ const ipcLogger = createLogger(LogPrefix.IPC)
22
+
23
+ // ── Tagged errors ────────────────────────────────────────────────────────
24
+
25
+ class IpcDispatchError extends createTaggedError({
26
+ name: 'IpcDispatchError',
27
+ message: 'IPC dispatch failed for request $requestId: $reason',
28
+ }) {}
29
+
30
+ // ── Button parsing ───────────────────────────────────────────────────────
31
+
32
+ const VALID_COLORS = new Set<ActionButtonColor>([
33
+ 'white',
34
+ 'blue',
35
+ 'green',
36
+ 'red',
37
+ ])
38
+
39
+ type ParsedButton = { label: string; color?: ActionButtonColor }
40
+
41
+ function parseButtons(raw: unknown): ParsedButton[] {
42
+ if (!Array.isArray(raw)) return []
43
+ const results: ParsedButton[] = []
44
+ for (const value of raw) {
45
+ if (!value || typeof value !== 'object') continue
46
+ const label = (typeof value.label === 'string' ? value.label : '')
47
+ .trim()
48
+ .slice(0, 80)
49
+ if (!label) continue
50
+ const color =
51
+ typeof value.color === 'string' &&
52
+ VALID_COLORS.has(value.color as ActionButtonColor)
53
+ ? (value.color as ActionButtonColor)
54
+ : undefined
55
+ results.push({ label, color })
56
+ if (results.length >= 3) break
57
+ }
58
+ return results
59
+ }
60
+
61
+ // ── Request dispatch ─────────────────────────────────────────────────────
62
+
63
+ type ClaimedRequest = {
64
+ id: string
65
+ type: string
66
+ session_id: string
67
+ thread_id: string
68
+ payload: string
69
+ }
70
+
71
+ async function dispatchRequest({
72
+ req,
73
+ discordClient,
74
+ }: {
75
+ req: ClaimedRequest
76
+ discordClient: Client
77
+ }) {
78
+ switch (req.type) {
79
+ case 'file_upload': {
80
+ const parsed = errore.try({
81
+ try: () =>
82
+ JSON.parse(req.payload) as {
83
+ prompt?: string
84
+ maxFiles?: number
85
+ directory?: string
86
+ },
87
+ catch: (e) =>
88
+ new IpcDispatchError({
89
+ requestId: req.id,
90
+ reason: 'Invalid payload JSON',
91
+ cause: e,
92
+ }),
93
+ })
94
+ if (parsed instanceof Error) {
95
+ await completeIpcRequest({
96
+ id: req.id,
97
+ response: JSON.stringify({ error: parsed.message }),
98
+ })
99
+ return parsed
100
+ }
101
+
102
+ const thread = await discordClient.channels
103
+ .fetch(req.thread_id)
104
+ .catch(
105
+ (e) =>
106
+ new IpcDispatchError({
107
+ requestId: req.id,
108
+ reason: 'Thread fetch failed',
109
+ cause: e,
110
+ }),
111
+ )
112
+ if (thread instanceof Error) {
113
+ await completeIpcRequest({
114
+ id: req.id,
115
+ response: JSON.stringify({ error: 'Thread not found' }),
116
+ })
117
+ return thread
118
+ }
119
+ if (!thread?.isThread()) {
120
+ await completeIpcRequest({
121
+ id: req.id,
122
+ response: JSON.stringify({ error: 'Thread not found' }),
123
+ })
124
+ return new IpcDispatchError({
125
+ requestId: req.id,
126
+ reason: 'Channel is not a thread',
127
+ })
128
+ }
129
+
130
+ // Fire-and-forget: showFileUploadButton waits for user interaction
131
+ // (button click + modal + file download) which can take minutes.
132
+ // Don't block the dispatch loop — complete the IPC request asynchronously.
133
+ showFileUploadButton({
134
+ thread,
135
+ sessionId: req.session_id,
136
+ directory: parsed.directory || '',
137
+ prompt: parsed.prompt || 'Please upload files',
138
+ maxFiles: Math.min(10, Math.max(1, parsed.maxFiles || 5)),
139
+ })
140
+ .then((filePaths) => {
141
+ return completeIpcRequest({
142
+ id: req.id,
143
+ response: JSON.stringify({ filePaths }),
144
+ })
145
+ })
146
+ .catch((e) => {
147
+ ipcLogger.error(
148
+ '[IPC] File upload error:',
149
+ e instanceof Error ? e.message : String(e),
150
+ )
151
+ return completeIpcRequest({
152
+ id: req.id,
153
+ response: JSON.stringify({
154
+ error: e instanceof Error ? e.message : 'File upload failed',
155
+ }),
156
+ })
157
+ })
158
+ .catch((e) => {
159
+ void notifyError(e, 'IPC file upload completion update failed')
160
+ })
161
+ return
162
+ }
163
+
164
+ case 'action_buttons': {
165
+ const parsed = errore.try({
166
+ try: () =>
167
+ JSON.parse(req.payload) as { buttons?: unknown; directory?: string },
168
+ catch: (e) =>
169
+ new IpcDispatchError({
170
+ requestId: req.id,
171
+ reason: 'Invalid payload JSON',
172
+ cause: e,
173
+ }),
174
+ })
175
+ if (parsed instanceof Error) {
176
+ await completeIpcRequest({
177
+ id: req.id,
178
+ response: JSON.stringify({ error: parsed.message }),
179
+ })
180
+ return parsed
181
+ }
182
+
183
+ const buttons = parseButtons(parsed.buttons)
184
+ if (buttons.length === 0) {
185
+ await completeIpcRequest({
186
+ id: req.id,
187
+ response: JSON.stringify({ error: 'No valid buttons' }),
188
+ })
189
+ return
190
+ }
191
+
192
+ const thread = await discordClient.channels
193
+ .fetch(req.thread_id)
194
+ .catch(
195
+ (e) =>
196
+ new IpcDispatchError({
197
+ requestId: req.id,
198
+ reason: 'Thread fetch failed',
199
+ cause: e,
200
+ }),
201
+ )
202
+ if (thread instanceof Error) {
203
+ await completeIpcRequest({
204
+ id: req.id,
205
+ response: JSON.stringify({ error: 'Thread not found' }),
206
+ })
207
+ return thread
208
+ }
209
+ if (!thread?.isThread()) {
210
+ await completeIpcRequest({
211
+ id: req.id,
212
+ response: JSON.stringify({ error: 'Thread not found' }),
213
+ })
214
+ return new IpcDispatchError({
215
+ requestId: req.id,
216
+ reason: 'Channel is not a thread',
217
+ })
218
+ }
219
+
220
+ queueActionButtonsRequest({
221
+ sessionId: req.session_id,
222
+ threadId: req.thread_id,
223
+ directory: parsed.directory || '',
224
+ buttons,
225
+ })
226
+
227
+ await completeIpcRequest({
228
+ id: req.id,
229
+ response: JSON.stringify({ ok: true }),
230
+ })
231
+ return
232
+ }
233
+
234
+ default: {
235
+ await completeIpcRequest({
236
+ id: req.id,
237
+ response: JSON.stringify({ error: `Unknown IPC type: ${req.type}` }),
238
+ })
239
+ return
240
+ }
241
+ }
242
+ }
243
+
244
+ // ── Polling lifecycle ────────────────────────────────────────────────────
245
+
246
+ let pollingInterval: ReturnType<typeof setInterval> | null = null
247
+
248
+ // Cancel requests stuck in 'processing' longer than 24 hours. Users often
249
+ // come back the next day to click permission/question/file-upload buttons,
250
+ // so we keep IPC rows alive for a full day. Checked every 30 seconds.
251
+ const STALE_TTL_MS = 24 * 60 * 60 * 1000
252
+ const STALE_CHECK_INTERVAL_MS = 30 * 1000
253
+ let lastStaleCheck = 0
254
+
255
+ /**
256
+ * Start polling the ipc_requests table for pending requests from the plugin.
257
+ * Claims rows atomically (pending -> processing) to prevent duplicate dispatch.
258
+ * Uses an in-flight guard to prevent overlapping poll ticks.
259
+ */
260
+ export async function startIpcPolling({
261
+ discordClient,
262
+ }: {
263
+ discordClient: Client
264
+ }) {
265
+ // Clean up stale requests from previous runs before first poll tick
266
+ await cancelAllPendingIpcRequests().catch((e) => {
267
+ ipcLogger.warn('Failed to cancel stale IPC requests:', (e as Error).message)
268
+ void notifyError(e, 'Failed to cancel stale IPC requests')
269
+ })
270
+
271
+ let polling = false
272
+ pollingInterval = setInterval(async () => {
273
+ if (polling) return
274
+ polling = true
275
+
276
+ // Periodically sweep requests stuck in 'processing' past the TTL
277
+ const now = Date.now()
278
+ if (now - lastStaleCheck > STALE_CHECK_INTERVAL_MS) {
279
+ lastStaleCheck = now
280
+ await cancelStaleProcessingRequests({ ttlMs: STALE_TTL_MS }).catch(
281
+ (e) => {
282
+ ipcLogger.warn('Stale sweep failed:', (e as Error).message)
283
+ void notifyError(e, 'IPC stale sweep failed')
284
+ },
285
+ )
286
+ }
287
+
288
+ const claimed = await claimPendingIpcRequests().catch(
289
+ (e) =>
290
+ new IpcDispatchError({
291
+ requestId: 'poll',
292
+ reason: 'Claim failed',
293
+ cause: e,
294
+ }),
295
+ )
296
+ if (claimed instanceof Error) {
297
+ ipcLogger.error('IPC claim failed:', claimed.message)
298
+ void notifyError(claimed, 'IPC claim failed')
299
+ polling = false
300
+ return
301
+ }
302
+
303
+ for (const req of claimed) {
304
+ const result = await dispatchRequest({ req, discordClient }).catch(
305
+ (e) =>
306
+ new IpcDispatchError({
307
+ requestId: req.id,
308
+ reason: 'Dispatch threw',
309
+ cause: e,
310
+ }),
311
+ )
312
+ if (result instanceof Error) {
313
+ ipcLogger.error(`IPC dispatch error for ${req.type}:`, result.message)
314
+ void notifyError(result, `IPC dispatch error for ${req.type}`)
315
+ }
316
+ }
317
+
318
+ polling = false
319
+ }, 200)
320
+ }
321
+
322
+ export function stopIpcPolling() {
323
+ if (!pollingInterval) return
324
+ clearInterval(pollingInterval)
325
+ pollingInterval = null
326
+ }
@@ -0,0 +1,236 @@
1
+ // OpenCode plugin that provides IPC-based tools for Discord interaction:
2
+ // - kimaki_file_upload: prompts the Discord user to upload files via native picker
3
+ // - kimaki_action_buttons: shows clickable action buttons in the Discord thread
4
+ //
5
+ // Tools communicate with the bot process via IPC rows in SQLite (the plugin
6
+ // runs inside the OpenCode server process, not the bot process).
7
+ //
8
+ // Exported from kimaki-opencode-plugin.ts — each export is treated as a separate
9
+ // plugin by OpenCode's plugin loader.
10
+
11
+ import type { Plugin } from '@opencode-ai/plugin'
12
+ import type { ToolContext } from '@opencode-ai/plugin/tool'
13
+ import dedent from 'string-dedent'
14
+ import { z } from 'zod'
15
+ import { setDataDir } from './config.js'
16
+ import { createPluginLogger, setPluginLogFilePath } from './plugin-logger.js'
17
+ import { initSentry } from './sentry.js'
18
+
19
+ // Inlined from '@opencode-ai/plugin/tool' because the subpath value import
20
+ // fails at runtime in global npm installs (#35). Opencode loads this plugin
21
+ // file in its own process and resolves modules from kimaki's install dir,
22
+ // but the '/tool' subpath export isn't found by opencode's module resolver.
23
+ // The type-only imports above are fine (erased at compile time).
24
+ //
25
+ // NOTE: @opencode-ai/plugin bundles its own zod 4.1.x as a hard dependency
26
+ // while goke (used by cli.ts) requires zod 4.3.x. This version skew makes
27
+ // the Plugin return type structurally incompatible with our local tool()
28
+ // even though runtime behavior is identical. ipcToolsPlugin is cast to
29
+ // Plugin via unknown to bypass this purely type-level incompatibility.
30
+ function tool<Args extends z.ZodRawShape>(input: {
31
+ description: string
32
+ args: Args
33
+ execute(
34
+ args: z.infer<z.ZodObject<Args>>,
35
+ context: ToolContext,
36
+ ): Promise<string>
37
+ }) {
38
+ return input
39
+ }
40
+
41
+ const logger = createPluginLogger('OPENCODE')
42
+
43
+ const FILE_UPLOAD_TIMEOUT_MS = 6 * 60 * 1000
44
+ const DEFAULT_FILE_UPLOAD_MAX_FILES = 5
45
+ const ACTION_BUTTON_TIMEOUT_MS = 30 * 1000
46
+
47
+ async function loadDatabaseModule() {
48
+ // The plugin-loading e2e test boots OpenCode directly without the bot-side
49
+ // Hrana env vars. Lazy-loading avoids pulling Prisma + libsql sqlite mode
50
+ // during plugin startup when no IPC tool is being executed yet.
51
+ return import('./database.js')
52
+ }
53
+
54
+ // @opencode-ai/plugin bundles zod 4.1.x as a hard dep; our code uses 4.3.x
55
+ // (required by goke for ~standard.jsonSchema). The Plugin return type is
56
+ // structurally incompatible due to _zod.version.minor skew even though
57
+ // runtime behavior is identical. `any` bypasses the type-level mismatch —
58
+ // opencode's plugin loader doesn't care about the zod version at runtime.
59
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
60
+ const ipcToolsPlugin: any = async () => {
61
+ initSentry()
62
+
63
+ const dataDir = process.env.KIMAKI_DATA_DIR
64
+ if (dataDir) {
65
+ setDataDir(dataDir)
66
+ setPluginLogFilePath(dataDir)
67
+ }
68
+
69
+ return {
70
+ tool: {
71
+ kimaki_file_upload: tool({
72
+ description:
73
+ 'Prompt the Discord user to upload files using a native file picker modal. ' +
74
+ 'The user sees a button, clicks it, and gets a file upload dialog. ' +
75
+ 'Returns the local file paths of downloaded files in the project directory. ' +
76
+ 'Use this when you need the user to provide files (images, documents, configs, etc.). ' +
77
+ 'IMPORTANT: Always call this tool last in your message, after all text parts.',
78
+ args: {
79
+ prompt: z
80
+ .string()
81
+ .describe(
82
+ 'Message shown to the user explaining what files to upload',
83
+ ),
84
+ maxFiles: z
85
+ .number()
86
+ .min(1)
87
+ .max(10)
88
+ .optional()
89
+ .describe(
90
+ 'Maximum number of files the user can upload (1-10, default 5)',
91
+ ),
92
+ },
93
+ async execute({ prompt, maxFiles }, context) {
94
+ const { getPrisma, createIpcRequest, getIpcRequestById } = await loadDatabaseModule()
95
+ const prisma = await getPrisma()
96
+ const row = await prisma.thread_sessions.findFirst({
97
+ where: { session_id: context.sessionID },
98
+ select: { thread_id: true },
99
+ })
100
+
101
+ if (!row?.thread_id) {
102
+ return 'Could not find thread for current session'
103
+ }
104
+
105
+ const ipcRow = await createIpcRequest({
106
+ type: 'file_upload',
107
+ sessionId: context.sessionID,
108
+ threadId: row.thread_id,
109
+ payload: JSON.stringify({
110
+ prompt,
111
+ maxFiles: maxFiles || DEFAULT_FILE_UPLOAD_MAX_FILES,
112
+ directory: context.directory,
113
+ }),
114
+ })
115
+
116
+ const deadline = Date.now() + FILE_UPLOAD_TIMEOUT_MS
117
+ const POLL_INTERVAL_MS = 300
118
+ while (Date.now() < deadline) {
119
+ await new Promise((resolve) => {
120
+ setTimeout(resolve, POLL_INTERVAL_MS)
121
+ })
122
+ const updated = await getIpcRequestById({ id: ipcRow.id })
123
+ if (!updated || updated.status === 'cancelled') {
124
+ return 'File upload was cancelled'
125
+ }
126
+ if (updated.response) {
127
+ const parsed = JSON.parse(updated.response) as {
128
+ filePaths?: string[]
129
+ error?: string
130
+ }
131
+ if (parsed.error) {
132
+ return `File upload failed: ${parsed.error}`
133
+ }
134
+ const filePaths = parsed.filePaths || []
135
+ if (filePaths.length === 0) {
136
+ return 'No files were uploaded (user may have cancelled or sent a new message)'
137
+ }
138
+ return `Files uploaded successfully:\n${filePaths.join('\n')}`
139
+ }
140
+ }
141
+
142
+ return 'File upload timed out - user did not upload files within the time limit'
143
+ },
144
+ }),
145
+ kimaki_action_buttons: tool({
146
+ description: dedent`
147
+ Show action buttons in the current Discord thread for quick confirmations.
148
+ Use this when the user can respond by clicking one of up to 3 buttons.
149
+ Prefer a single button whenever possible.
150
+ Default color is white (same visual style as permission deny button).
151
+ If you need more than 3 options, use the question tool instead.
152
+ IMPORTANT: Always call this tool last in your message, after all text parts.
153
+
154
+ Examples:
155
+ - buttons: [{"label":"Yes, proceed"}]
156
+ - buttons: [{"label":"Approve","color":"green"}]
157
+ - buttons: [
158
+ {"label":"Confirm","color":"blue"},
159
+ {"label":"Cancel","color":"white"}
160
+ ]
161
+ `,
162
+ args: {
163
+ buttons: z
164
+ .array(
165
+ z.object({
166
+ label: z
167
+ .string()
168
+ .min(1)
169
+ .max(80)
170
+ .describe('Button label shown to the user (1-80 chars)'),
171
+ color: z
172
+ .enum(['white', 'blue', 'green', 'red'])
173
+ .optional()
174
+ .describe(
175
+ 'Optional button color. white is default and preferred for most confirmations.',
176
+ ),
177
+ }),
178
+ )
179
+ .min(1)
180
+ .max(3)
181
+ .describe(
182
+ 'Array of 1-3 action buttons. Prefer one button whenever possible.',
183
+ ),
184
+ },
185
+ async execute({ buttons }, context) {
186
+ const { getPrisma, createIpcRequest, getIpcRequestById } = await loadDatabaseModule()
187
+ const prisma = await getPrisma()
188
+ const row = await prisma.thread_sessions.findFirst({
189
+ where: { session_id: context.sessionID },
190
+ select: { thread_id: true },
191
+ })
192
+
193
+ if (!row?.thread_id) {
194
+ return 'Could not find thread for current session'
195
+ }
196
+
197
+ const ipcRow = await createIpcRequest({
198
+ type: 'action_buttons',
199
+ sessionId: context.sessionID,
200
+ threadId: row.thread_id,
201
+ payload: JSON.stringify({
202
+ buttons,
203
+ directory: context.directory,
204
+ }),
205
+ })
206
+
207
+ const deadline = Date.now() + ACTION_BUTTON_TIMEOUT_MS
208
+ const POLL_INTERVAL_MS = 200
209
+ while (Date.now() < deadline) {
210
+ await new Promise((resolve) => {
211
+ setTimeout(resolve, POLL_INTERVAL_MS)
212
+ })
213
+ const updated = await getIpcRequestById({ id: ipcRow.id })
214
+ if (!updated || updated.status === 'cancelled') {
215
+ return 'Action button request was cancelled'
216
+ }
217
+ if (updated.response) {
218
+ const parsed = JSON.parse(updated.response) as {
219
+ ok?: boolean
220
+ error?: string
221
+ }
222
+ if (parsed.error) {
223
+ return `Action button request failed: ${parsed.error}`
224
+ }
225
+ return `Action button(s) shown: ${buttons.map((button) => button.label).join(', ')}`
226
+ }
227
+ }
228
+
229
+ return 'Action button request timed out'
230
+ },
231
+ }),
232
+ },
233
+ }
234
+ }
235
+
236
+ export { ipcToolsPlugin }