@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,89 @@
1
+ // /abort command - Abort the current OpenCode request in this thread.
2
+
3
+ import {
4
+ ChannelType,
5
+ MessageFlags,
6
+ type TextChannel,
7
+ type ThreadChannel,
8
+ } from 'discord.js'
9
+ import type { CommandContext } from './types.js'
10
+ import { getThreadSession } from '../database.js'
11
+ import { initializeOpencodeForDirectory } from '../opencode.js'
12
+ import {
13
+ resolveWorkingDirectory,
14
+ SILENT_MESSAGE_FLAGS,
15
+ } from '../discord-utils.js'
16
+ import { getRuntime } from '../session-handler/thread-session-runtime.js'
17
+ import { createLogger, LogPrefix } from '../logger.js'
18
+
19
+ const logger = createLogger(LogPrefix.ABORT)
20
+
21
+ export async function handleAbortCommand({
22
+ command,
23
+ }: CommandContext): Promise<void> {
24
+ const channel = command.channel
25
+
26
+ if (!channel) {
27
+ await command.reply({
28
+ content: 'This command can only be used in a channel',
29
+ flags: MessageFlags.Ephemeral | SILENT_MESSAGE_FLAGS,
30
+ })
31
+ return
32
+ }
33
+
34
+ const isThread = [
35
+ ChannelType.PublicThread,
36
+ ChannelType.PrivateThread,
37
+ ChannelType.AnnouncementThread,
38
+ ].includes(channel.type)
39
+
40
+ if (!isThread) {
41
+ await command.reply({
42
+ content:
43
+ 'This command can only be used in a thread with an active session',
44
+ flags: MessageFlags.Ephemeral | SILENT_MESSAGE_FLAGS,
45
+ })
46
+ return
47
+ }
48
+
49
+ await command.deferReply({ flags: SILENT_MESSAGE_FLAGS })
50
+
51
+ const resolved = await resolveWorkingDirectory({
52
+ channel: channel as TextChannel | ThreadChannel,
53
+ })
54
+
55
+ if (!resolved) {
56
+ await command.editReply('Could not determine project directory for this channel')
57
+ return
58
+ }
59
+
60
+ const { projectDirectory } = resolved
61
+
62
+ const sessionId = await getThreadSession(channel.id)
63
+
64
+ if (!sessionId) {
65
+ await command.editReply('No active session in this thread')
66
+ return
67
+ }
68
+
69
+ // abortActiveRun delegates to session.abort(), run settlement stays event-driven.
70
+ const runtime = getRuntime(channel.id)
71
+ if (runtime) {
72
+ runtime.abortActiveRun('user-requested')
73
+ } else {
74
+ // No runtime but session exists — fall back to direct API abort
75
+ const getClient = await initializeOpencodeForDirectory(projectDirectory)
76
+ if (getClient instanceof Error) {
77
+ await command.editReply(`Failed to abort: ${getClient.message}`)
78
+ return
79
+ }
80
+ try {
81
+ await getClient().session.abort({ sessionID: sessionId })
82
+ } catch (error) {
83
+ logger.error('[ABORT] API abort failed:', error)
84
+ }
85
+ }
86
+
87
+ await command.editReply('Request **aborted**')
88
+ logger.log(`Session ${sessionId} aborted by user`)
89
+ }
@@ -0,0 +1,364 @@
1
+ // Action button tool handler - Shows Discord buttons for quick model actions.
2
+ // Used by the kimaki_action_buttons tool to render up to 3 buttons and route
3
+ // button clicks back into the session as a new user message.
4
+
5
+ import {
6
+ ActionRowBuilder,
7
+ ButtonBuilder,
8
+ ButtonStyle,
9
+ MessageFlags,
10
+ type ButtonInteraction,
11
+ type ThreadChannel,
12
+ } from 'discord.js'
13
+ import crypto from 'node:crypto'
14
+ import { getThreadSession } from '../database.js'
15
+ import {
16
+ NOTIFY_MESSAGE_FLAGS,
17
+ SILENT_MESSAGE_FLAGS,
18
+ resolveWorkingDirectory,
19
+ sendThreadMessage,
20
+ } from '../discord-utils.js'
21
+ import { createLogger } from '../logger.js'
22
+ import { notifyError } from '../sentry.js'
23
+ import {
24
+ getOrCreateRuntime,
25
+ } from '../session-handler/thread-session-runtime.js'
26
+
27
+ const logger = createLogger('ACT_BTN')
28
+ const PENDING_TTL_MS = 24 * 60 * 60 * 1000
29
+
30
+ export type ActionButtonColor = 'white' | 'blue' | 'green' | 'red'
31
+
32
+ export type ActionButtonOption = {
33
+ label: string
34
+ color?: ActionButtonColor
35
+ }
36
+
37
+ export type ActionButtonsRequest = {
38
+ sessionId: string
39
+ threadId: string
40
+ directory: string
41
+ buttons: ActionButtonOption[]
42
+ }
43
+
44
+ type PendingActionButtonsContext = {
45
+ sessionId: string
46
+ directory: string
47
+ thread: ThreadChannel
48
+ buttons: ActionButtonOption[]
49
+ contextHash: string
50
+ messageId?: string
51
+ resolved: boolean
52
+ timer: ReturnType<typeof setTimeout>
53
+ }
54
+
55
+ export const pendingActionButtonContexts = new Map<
56
+ string,
57
+ PendingActionButtonsContext
58
+ >()
59
+ const pendingActionButtonRequests = new Map<string, ActionButtonsRequest>()
60
+ const pendingActionButtonRequestWaiters = new Map<
61
+ string,
62
+ (request: ActionButtonsRequest) => void
63
+ >()
64
+
65
+ export function queueActionButtonsRequest(request: ActionButtonsRequest): void {
66
+ pendingActionButtonRequests.set(request.sessionId, request)
67
+ const waiter = pendingActionButtonRequestWaiters.get(request.sessionId)
68
+ if (!waiter) {
69
+ return
70
+ }
71
+ pendingActionButtonRequestWaiters.delete(request.sessionId)
72
+ waiter(request)
73
+ }
74
+
75
+ export async function waitForQueuedActionButtonsRequest({
76
+ sessionId,
77
+ timeoutMs,
78
+ }: {
79
+ sessionId: string
80
+ timeoutMs: number
81
+ }): Promise<ActionButtonsRequest | undefined> {
82
+ const queued = pendingActionButtonRequests.get(sessionId)
83
+ if (queued) {
84
+ pendingActionButtonRequests.delete(sessionId)
85
+ return queued
86
+ }
87
+
88
+ return await new Promise<ActionButtonsRequest | undefined>((resolve) => {
89
+ const timeout = setTimeout(() => {
90
+ const currentWaiter = pendingActionButtonRequestWaiters.get(sessionId)
91
+ if (!currentWaiter || currentWaiter !== onRequest) {
92
+ return
93
+ }
94
+ pendingActionButtonRequestWaiters.delete(sessionId)
95
+ resolve(undefined)
96
+ }, timeoutMs)
97
+
98
+ const onRequest = (request: ActionButtonsRequest) => {
99
+ clearTimeout(timeout)
100
+ pendingActionButtonRequests.delete(sessionId)
101
+ resolve(request)
102
+ }
103
+
104
+ pendingActionButtonRequestWaiters.set(sessionId, onRequest)
105
+ })
106
+ }
107
+
108
+ function toButtonStyle(color?: ActionButtonColor): ButtonStyle {
109
+ if (color === 'blue') {
110
+ return ButtonStyle.Primary
111
+ }
112
+ if (color === 'green') {
113
+ return ButtonStyle.Success
114
+ }
115
+ if (color === 'red') {
116
+ return ButtonStyle.Danger
117
+ }
118
+ return ButtonStyle.Secondary
119
+ }
120
+
121
+ function resolveContext(context: PendingActionButtonsContext): boolean {
122
+ if (context.resolved) {
123
+ return false
124
+ }
125
+ context.resolved = true
126
+ clearTimeout(context.timer)
127
+ pendingActionButtonContexts.delete(context.contextHash)
128
+ return true
129
+ }
130
+
131
+ function updateButtonMessage({
132
+ context,
133
+ status,
134
+ }: {
135
+ context: PendingActionButtonsContext
136
+ status: string
137
+ }): void {
138
+ if (!context.messageId) {
139
+ return
140
+ }
141
+ context.thread.messages
142
+ .fetch(context.messageId)
143
+ .then((message) => {
144
+ return message.edit({
145
+ content: `**Action Required**\n${status}`,
146
+ components: [],
147
+ })
148
+ })
149
+ .catch(() => {})
150
+ }
151
+
152
+ async function sendClickedActionToModel({
153
+ interaction,
154
+ thread,
155
+ prompt,
156
+ }: {
157
+ interaction: ButtonInteraction
158
+ thread: ThreadChannel
159
+ prompt: string
160
+ }): Promise<void> {
161
+ const resolved = await resolveWorkingDirectory({ channel: thread })
162
+ if (!resolved) {
163
+ throw new Error('Could not resolve project directory for thread')
164
+ }
165
+
166
+ const username = interaction.user.globalName || interaction.user.username
167
+
168
+ // Action button clicks use opencode queue mode.
169
+ const runtime = getOrCreateRuntime({
170
+ threadId: thread.id,
171
+ thread,
172
+ projectDirectory: resolved.projectDirectory,
173
+ sdkDirectory: resolved.workingDirectory,
174
+ channelId: thread.parentId || thread.id,
175
+ })
176
+ await runtime.enqueueIncoming({
177
+ prompt,
178
+ userId: interaction.user.id,
179
+ username,
180
+ mode: 'opencode',
181
+ })
182
+ }
183
+
184
+ export async function showActionButtons({
185
+ thread,
186
+ sessionId,
187
+ directory,
188
+ buttons,
189
+ silent,
190
+ }: {
191
+ thread: ThreadChannel
192
+ sessionId: string
193
+ directory: string
194
+ buttons: ActionButtonOption[]
195
+ /** Suppress notification when queue has pending items */
196
+ silent?: boolean
197
+ }): Promise<void> {
198
+ const safeButtons = buttons
199
+ .slice(0, 3)
200
+ .map((button) => {
201
+ return {
202
+ label: button.label.trim().slice(0, 80),
203
+ color: button.color,
204
+ }
205
+ })
206
+ .filter((button) => {
207
+ return button.label.length > 0
208
+ })
209
+
210
+ if (safeButtons.length === 0) {
211
+ throw new Error('No valid buttons to display')
212
+ }
213
+
214
+ const contextHash = crypto.randomBytes(8).toString('hex')
215
+ const timer = setTimeout(() => {
216
+ const current = pendingActionButtonContexts.get(contextHash)
217
+ if (!current || current.resolved) {
218
+ return
219
+ }
220
+ resolveContext(current)
221
+ updateButtonMessage({ context: current, status: '_Expired_' })
222
+ }, PENDING_TTL_MS)
223
+
224
+ const context: PendingActionButtonsContext = {
225
+ sessionId,
226
+ directory,
227
+ thread,
228
+ buttons: safeButtons,
229
+ contextHash,
230
+ resolved: false,
231
+ timer,
232
+ }
233
+
234
+ pendingActionButtonContexts.set(contextHash, context)
235
+
236
+ const row = new ActionRowBuilder<ButtonBuilder>().addComponents(
237
+ ...safeButtons.map((button, index) => {
238
+ return new ButtonBuilder()
239
+ .setCustomId(`action_button:${contextHash}:${index}`)
240
+ .setLabel(button.label)
241
+ .setStyle(toButtonStyle(button.color))
242
+ }),
243
+ )
244
+
245
+ try {
246
+ const message = await thread.send({
247
+ content: '**Action Required**',
248
+ components: [row],
249
+ flags: silent ? SILENT_MESSAGE_FLAGS : NOTIFY_MESSAGE_FLAGS,
250
+ })
251
+
252
+ context.messageId = message.id
253
+ logger.log(
254
+ `Showed ${safeButtons.length} action button(s) for session ${sessionId}`,
255
+ )
256
+ } catch (error) {
257
+ clearTimeout(timer)
258
+ pendingActionButtonContexts.delete(contextHash)
259
+ throw new Error('Failed to send action buttons', { cause: error })
260
+ }
261
+ }
262
+
263
+ export async function handleActionButton(
264
+ interaction: ButtonInteraction,
265
+ ): Promise<void> {
266
+ const customId = interaction.customId
267
+ if (!customId.startsWith('action_button:')) {
268
+ return
269
+ }
270
+
271
+ const [, contextHash, indexPart] = customId.split(':')
272
+ if (!contextHash || !indexPart) {
273
+ await interaction.reply({
274
+ content: 'Invalid action button.',
275
+ flags: MessageFlags.Ephemeral,
276
+ })
277
+ return
278
+ }
279
+
280
+ const context = pendingActionButtonContexts.get(contextHash)
281
+ if (!context || context.resolved) {
282
+ await interaction.reply({
283
+ content: 'This action is no longer available.',
284
+ flags: MessageFlags.Ephemeral,
285
+ })
286
+ return
287
+ }
288
+
289
+ const buttonIndex = Number.parseInt(indexPart, 10)
290
+ const button = context.buttons[buttonIndex]
291
+ if (!button) {
292
+ await interaction.reply({
293
+ content: 'This action is no longer available.',
294
+ flags: MessageFlags.Ephemeral,
295
+ })
296
+ return
297
+ }
298
+
299
+ await interaction.deferUpdate()
300
+ const claimed = resolveContext(context)
301
+ if (!claimed) {
302
+ return
303
+ }
304
+
305
+ const thread = interaction.channel
306
+ if (!thread?.isThread()) {
307
+ logger.warn('[ACTION] Button clicked outside thread channel')
308
+ await interaction.editReply({
309
+ content: '**Action Required**\n_This action is no longer available._',
310
+ components: [],
311
+ })
312
+ return
313
+ }
314
+
315
+ const currentSessionId = await getThreadSession(thread.id)
316
+ if (!currentSessionId || currentSessionId !== context.sessionId) {
317
+ await interaction.editReply({
318
+ content: '**Action Required**\n_Expired due to session change._',
319
+ components: [],
320
+ })
321
+ return
322
+ }
323
+
324
+ await interaction.editReply({
325
+ content: `**Action Required**\n_Selected: ${button.label}_`,
326
+ components: [],
327
+ })
328
+
329
+ const prompt = `User clicked: ${button.label}`
330
+
331
+ try {
332
+ await sendClickedActionToModel({
333
+ interaction,
334
+ thread,
335
+ prompt,
336
+ })
337
+ } catch (error) {
338
+ logger.error('[ACTION] Failed to send click to model:', error)
339
+ void notifyError(error, 'Action button click send to model failed')
340
+ await sendThreadMessage(
341
+ thread,
342
+ `Failed to send action click: ${error instanceof Error ? error.message : String(error)}`,
343
+ { flags: NOTIFY_MESSAGE_FLAGS },
344
+ )
345
+ }
346
+ }
347
+
348
+ /**
349
+ * Dismiss pending action buttons for a thread (e.g. user sent a new message).
350
+ * Removes buttons from the message and cleans up context.
351
+ */
352
+ export function cancelPendingActionButtons(threadId: string): boolean {
353
+ for (const [, ctx] of pendingActionButtonContexts) {
354
+ if (ctx.thread.id !== threadId) {
355
+ continue
356
+ }
357
+ if (!resolveContext(ctx)) {
358
+ continue
359
+ }
360
+ updateButtonMessage({ context: ctx, status: '_Buttons dismissed._' })
361
+ return true
362
+ }
363
+ return false
364
+ }
@@ -0,0 +1,149 @@
1
+ // /add-project command - Create Discord channels for an existing OpenCode project.
2
+
3
+ import fs from 'node:fs'
4
+ import path from 'node:path'
5
+ import type { CommandContext, AutocompleteContext } from './types.js'
6
+ import {
7
+ findChannelsByDirectory,
8
+ getAllTextChannelDirectories,
9
+ } from '../database.js'
10
+ import { initializeOpencodeForDirectory } from '../opencode.js'
11
+ import { createProjectChannels } from '../channel-management.js'
12
+ import { createLogger, LogPrefix } from '../logger.js'
13
+ import { abbreviatePath } from '../utils.js'
14
+ import * as errore from 'errore'
15
+
16
+ const logger = createLogger(LogPrefix.ADD_PROJECT)
17
+
18
+ export async function handleAddProjectCommand({
19
+ command,
20
+ }: CommandContext): Promise<void> {
21
+ await command.deferReply()
22
+
23
+ const projectId = command.options.getString('project', true)
24
+ const guild = command.guild
25
+
26
+ if (!guild) {
27
+ await command.editReply('This command can only be used in a guild')
28
+ return
29
+ }
30
+
31
+ try {
32
+ const currentDir = process.cwd()
33
+ const getClient = await initializeOpencodeForDirectory(currentDir)
34
+ if (getClient instanceof Error) {
35
+ await command.editReply(getClient.message)
36
+ return
37
+ }
38
+
39
+ const projectsResponse = await getClient().project.list({})
40
+ if (!projectsResponse.data) {
41
+ await command.editReply('Failed to fetch projects')
42
+ return
43
+ }
44
+
45
+ const project = projectsResponse.data.find((p) => p.id === projectId)
46
+
47
+ if (!project) {
48
+ await command.editReply('Project not found')
49
+ return
50
+ }
51
+
52
+ const directory = project.worktree
53
+
54
+ if (!fs.existsSync(directory)) {
55
+ await command.editReply(`Directory does not exist: ${directory}`)
56
+ return
57
+ }
58
+
59
+ const existingChannels = await findChannelsByDirectory({
60
+ directory,
61
+ channelType: 'text',
62
+ })
63
+
64
+ if (existingChannels.length > 0) {
65
+ await command.editReply(
66
+ `A channel already exists for this directory: <#${existingChannels[0]!.channel_id}>`,
67
+ )
68
+ return
69
+ }
70
+
71
+ const { textChannelId, voiceChannelId, channelName } =
72
+ await createProjectChannels({
73
+ guild,
74
+ projectDirectory: directory,
75
+ botName: command.client.user?.username,
76
+ })
77
+
78
+ const voiceInfo = voiceChannelId ? `\nšŸ”Š Voice: <#${voiceChannelId}>` : ''
79
+ await command.editReply(
80
+ `āœ… Created channels for project:\nšŸ“ Text: <#${textChannelId}>${voiceInfo}\nšŸ“ Directory: \`${directory}\``,
81
+ )
82
+
83
+ logger.log(`Created channels for project ${channelName} at ${directory}`)
84
+ } catch (error) {
85
+ logger.error('[ADD-PROJECT] Error:', error)
86
+ await command.editReply(
87
+ `Failed to create channels: ${error instanceof Error ? error.message : 'Unknown error'}`,
88
+ )
89
+ }
90
+ }
91
+
92
+ export async function handleAddProjectAutocomplete({
93
+ interaction,
94
+ }: AutocompleteContext): Promise<void> {
95
+ const focusedValue = interaction.options.getFocused()
96
+
97
+ try {
98
+ const currentDir = process.cwd()
99
+ const getClient = await initializeOpencodeForDirectory(currentDir)
100
+ if (getClient instanceof Error) {
101
+ await interaction.respond([])
102
+ return
103
+ }
104
+
105
+ const projectsResponse = await getClient().project.list({})
106
+ if (!projectsResponse.data) {
107
+ await interaction.respond([])
108
+ return
109
+ }
110
+
111
+ const existingDirs = await getAllTextChannelDirectories()
112
+ const existingDirSet = new Set(existingDirs)
113
+
114
+ const availableProjects = projectsResponse.data.filter((project) => {
115
+ if (existingDirSet.has(project.worktree)) {
116
+ return false
117
+ }
118
+ if (path.basename(project.worktree).startsWith('opencode-test-')) {
119
+ return false
120
+ }
121
+ return true
122
+ })
123
+
124
+ const projects = availableProjects
125
+ .filter((project) => {
126
+ const baseName = path.basename(project.worktree)
127
+ const searchText = `${baseName} ${project.worktree}`.toLowerCase()
128
+ return searchText.includes(focusedValue.toLowerCase())
129
+ })
130
+ .sort((a, b) => {
131
+ const aTime = a.time.initialized || a.time.created
132
+ const bTime = b.time.initialized || b.time.created
133
+ return bTime - aTime
134
+ })
135
+ .slice(0, 25)
136
+ .map((project) => {
137
+ const name = `${path.basename(project.worktree)} (${abbreviatePath(project.worktree)})`
138
+ return {
139
+ name: name.length > 100 ? name.slice(0, 99) + '…' : name,
140
+ value: project.id,
141
+ }
142
+ })
143
+
144
+ await interaction.respond(projects)
145
+ } catch (error) {
146
+ logger.error('[AUTOCOMPLETE] Error fetching projects:', error)
147
+ await interaction.respond([])
148
+ }
149
+ }