@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,307 @@
1
+ // /mcp command - List and toggle MCP servers for the current project.
2
+ // Uses OpenCode SDK mcp.status/connect/disconnect to manage servers.
3
+ // MCP state is project-scoped (per channel), not per thread or session.
4
+ // No database storage needed — state lives in OpenCode's config.
5
+
6
+ import crypto from 'node:crypto'
7
+ import {
8
+ MessageFlags,
9
+ StringSelectMenuBuilder,
10
+ ActionRowBuilder,
11
+ ChannelType,
12
+ type StringSelectMenuInteraction,
13
+ type TextChannel,
14
+ type ThreadChannel,
15
+ } from 'discord.js'
16
+ import type { McpStatus } from '@opencode-ai/sdk/v2'
17
+ import type { CommandContext } from './types.js'
18
+ import { initializeOpencodeForDirectory } from '../opencode.js'
19
+ import {
20
+ resolveWorkingDirectory,
21
+ SILENT_MESSAGE_FLAGS,
22
+ } from '../discord-utils.js'
23
+ import { createLogger, LogPrefix } from '../logger.js'
24
+
25
+ const logger = createLogger(LogPrefix.MCP)
26
+
27
+ // Short-lived context map: contextHash → projectDirectory.
28
+ // Avoids embedding long directory paths in Discord customId (100 char limit).
29
+ // Entries auto-expire after 5 minutes to prevent unbounded growth from
30
+ // abandoned menus (user runs /mcp but never clicks the select menu).
31
+ const MCP_CONTEXT_TTL_MS = 5 * 60_000
32
+ const pendingMcpContexts = new Map<string, string>()
33
+
34
+ const STATUS_LABELS: Record<string, string> = {
35
+ connected: 'connected',
36
+ disabled: 'disabled',
37
+ failed: 'failed',
38
+ needs_auth: 'needs auth',
39
+ needs_client_registration: 'needs registration',
40
+ }
41
+
42
+ function formatStatusLabel(status: string): string {
43
+ return STATUS_LABELS[status] || status
44
+ }
45
+
46
+ /** Extract error string from McpStatus using discriminated union narrowing. */
47
+ function getStatusError(info: McpStatus): string | undefined {
48
+ if (info.status === 'failed') {
49
+ return info.error
50
+ }
51
+ if (info.status === 'needs_client_registration') {
52
+ return info.error
53
+ }
54
+ return undefined
55
+ }
56
+
57
+ /** Build a one-line description for a server entry in the list. */
58
+ export function formatServerLine({
59
+ name,
60
+ status,
61
+ error,
62
+ }: {
63
+ name: string
64
+ status: string
65
+ error?: string
66
+ }): string {
67
+ const label = formatStatusLabel(status)
68
+ const errorSuffix = error ? ` — ${error}` : ''
69
+ return `\`${label}\` **${name}**${errorSuffix}`
70
+ }
71
+
72
+ /** Determine the select menu option label for toggling a server. */
73
+ export function toggleActionLabel(status: string): string {
74
+ if (status === 'connected') {
75
+ return 'disconnect'
76
+ }
77
+ if (status === 'failed') {
78
+ return 'reconnect'
79
+ }
80
+ return 'connect'
81
+ }
82
+
83
+ export async function handleMcpCommand({
84
+ command,
85
+ }: CommandContext): Promise<void> {
86
+ const channel = command.channel
87
+ if (!channel) {
88
+ await command.reply({
89
+ content: 'This command can only be used in a channel.',
90
+ flags: MessageFlags.Ephemeral | SILENT_MESSAGE_FLAGS,
91
+ })
92
+ return
93
+ }
94
+
95
+ const isThread = [
96
+ ChannelType.PublicThread,
97
+ ChannelType.PrivateThread,
98
+ ChannelType.AnnouncementThread,
99
+ ].includes(channel.type)
100
+ const isTextChannel = channel.type === ChannelType.GuildText
101
+
102
+ if (!isThread && !isTextChannel) {
103
+ await command.reply({
104
+ content: 'This command can only be used in text channels or threads.',
105
+ flags: MessageFlags.Ephemeral | SILENT_MESSAGE_FLAGS,
106
+ })
107
+ return
108
+ }
109
+
110
+ const resolved = await resolveWorkingDirectory({
111
+ channel: channel as TextChannel | ThreadChannel,
112
+ })
113
+ if (!resolved) {
114
+ await command.reply({
115
+ content: 'Could not determine project directory for this channel.',
116
+ flags: MessageFlags.Ephemeral | SILENT_MESSAGE_FLAGS,
117
+ })
118
+ return
119
+ }
120
+
121
+ const { projectDirectory } = resolved
122
+
123
+ await command.deferReply({ flags: MessageFlags.Ephemeral | SILENT_MESSAGE_FLAGS })
124
+
125
+ const getClient = await initializeOpencodeForDirectory(projectDirectory)
126
+ if (getClient instanceof Error) {
127
+ await command.editReply({
128
+ content: `Failed to connect to OpenCode server: ${getClient.message}`,
129
+ })
130
+ return
131
+ }
132
+
133
+ const client = getClient()
134
+ const { data, error } = await client.mcp.status({
135
+ directory: projectDirectory,
136
+ })
137
+
138
+ if (error || !data) {
139
+ await command.editReply({
140
+ content: 'Failed to fetch MCP server status.',
141
+ })
142
+ return
143
+ }
144
+
145
+ const servers = Object.entries(data)
146
+ if (servers.length === 0) {
147
+ await command.editReply({
148
+ content:
149
+ 'No MCP servers configured for this project.\nAdd MCP servers in your project\'s `opencode.json` configuration.',
150
+ })
151
+ return
152
+ }
153
+
154
+ const lines = servers.map(([name, info]) => {
155
+ return formatServerLine({ name, status: info.status, error: getStatusError(info) })
156
+ })
157
+
158
+ const content = `**MCP Servers** (project-wide)\n${lines.join('\n')}`
159
+
160
+ const contextHash = crypto.randomBytes(8).toString('hex')
161
+ pendingMcpContexts.set(contextHash, projectDirectory)
162
+ setTimeout(() => {
163
+ pendingMcpContexts.delete(contextHash)
164
+ }, MCP_CONTEXT_TTL_MS)
165
+
166
+ // Discord select option limits: label max 100 chars, description max 100 chars
167
+ const options = servers.map(([name, info]) => ({
168
+ label: name.slice(0, 100),
169
+ value: name.slice(0, 100),
170
+ description: `${formatStatusLabel(info.status)} — click to ${toggleActionLabel(info.status)}`.slice(0, 100),
171
+ }))
172
+
173
+ const selectMenu = new StringSelectMenuBuilder()
174
+ .setCustomId(`mcp_toggle:${contextHash}`)
175
+ .setPlaceholder('Select MCP server to toggle')
176
+ .addOptions(options.slice(0, 25)) // Discord max 25 options
177
+
178
+ const actionRow =
179
+ new ActionRowBuilder<StringSelectMenuBuilder>().addComponents(selectMenu)
180
+
181
+ await command.editReply({
182
+ content,
183
+ components: [actionRow],
184
+ })
185
+ }
186
+
187
+ export async function handleMcpSelectMenu(
188
+ interaction: StringSelectMenuInteraction,
189
+ ): Promise<void> {
190
+ const customId = interaction.customId
191
+ if (!customId.startsWith('mcp_toggle:')) {
192
+ return
193
+ }
194
+
195
+ await interaction.deferUpdate()
196
+
197
+ const contextHash = customId.slice('mcp_toggle:'.length)
198
+ const projectDirectory = pendingMcpContexts.get(contextHash)
199
+
200
+ if (!projectDirectory) {
201
+ await interaction.editReply({
202
+ content: 'Session expired. Run `/mcp` again.',
203
+ components: [],
204
+ })
205
+ return
206
+ }
207
+
208
+ const serverName = interaction.values[0]
209
+ if (!serverName) {
210
+ await interaction.editReply({
211
+ content: 'No server selected.',
212
+ components: [],
213
+ })
214
+ return
215
+ }
216
+
217
+ pendingMcpContexts.delete(contextHash)
218
+
219
+ const getClient = await initializeOpencodeForDirectory(projectDirectory)
220
+ if (getClient instanceof Error) {
221
+ await interaction.editReply({
222
+ content: `Failed to connect to OpenCode server: ${getClient.message}`,
223
+ components: [],
224
+ })
225
+ return
226
+ }
227
+
228
+ const client = getClient()
229
+
230
+ const { data: statusData, error: statusError } = await client.mcp.status({
231
+ directory: projectDirectory,
232
+ })
233
+
234
+ if (statusError || !statusData) {
235
+ await interaction.editReply({
236
+ content: 'Failed to refresh MCP server status.',
237
+ components: [],
238
+ })
239
+ return
240
+ }
241
+
242
+ if (!statusData[serverName]) {
243
+ await interaction.editReply({
244
+ content: `Server **${serverName}** not found.`,
245
+ components: [],
246
+ })
247
+ return
248
+ }
249
+
250
+ const serverInfo = statusData[serverName]
251
+
252
+ if (serverInfo.status === 'connected') {
253
+ const { error } = await client.mcp.disconnect({
254
+ name: serverName,
255
+ directory: projectDirectory,
256
+ })
257
+ if (error) {
258
+ logger.error(`[MCP] Failed to disconnect ${serverName}:`, error)
259
+ await interaction.editReply({
260
+ content: `Failed to disconnect **${serverName}**.`,
261
+ components: [],
262
+ })
263
+ return
264
+ }
265
+ logger.log(`[MCP] Disconnected server: ${serverName}`)
266
+ await interaction.editReply({
267
+ content: `**${serverName}** disconnected`,
268
+ components: [],
269
+ })
270
+ return
271
+ }
272
+
273
+ if (serverInfo.status === 'needs_auth') {
274
+ await interaction.editReply({
275
+ content: `**${serverName}** needs authentication.\nRun \`opencode\` in the project directory to complete the OAuth flow.`,
276
+ components: [],
277
+ })
278
+ return
279
+ }
280
+
281
+ if (serverInfo.status === 'needs_client_registration') {
282
+ await interaction.editReply({
283
+ content: `**${serverName}** needs client registration.${serverInfo.error ? `\n${serverInfo.error}` : ''}`,
284
+ components: [],
285
+ })
286
+ return
287
+ }
288
+
289
+ // Connect (handles disabled and failed)
290
+ const { error } = await client.mcp.connect({
291
+ name: serverName,
292
+ directory: projectDirectory,
293
+ })
294
+ if (error) {
295
+ logger.error(`[MCP] Failed to connect ${serverName}:`, error)
296
+ await interaction.editReply({
297
+ content: `Failed to connect **${serverName}**.`,
298
+ components: [],
299
+ })
300
+ return
301
+ }
302
+ logger.log(`[MCP] Connected server: ${serverName}`)
303
+ await interaction.editReply({
304
+ content: `**${serverName}** connected`,
305
+ components: [],
306
+ })
307
+ }
@@ -0,0 +1,30 @@
1
+ // /memory-snapshot command - Write a V8 heap snapshot and show the file path.
2
+ // Reuses writeHeapSnapshot() from heap-monitor.ts which writes gzip-compressed
3
+ // .heapsnapshot.gz files to ~/.kimaki/heap-snapshots/.
4
+
5
+ import { MessageFlags } from 'discord.js'
6
+ import type { CommandContext } from './types.js'
7
+ import { writeHeapSnapshot } from '../heap-monitor.js'
8
+ import { SILENT_MESSAGE_FLAGS } from '../discord-utils.js'
9
+ import { createLogger, LogPrefix } from '../logger.js'
10
+
11
+ const logger = createLogger(LogPrefix.HEAP)
12
+
13
+ export async function handleMemorySnapshotCommand({
14
+ command,
15
+ }: CommandContext): Promise<void> {
16
+ await command.deferReply({ flags: SILENT_MESSAGE_FLAGS })
17
+
18
+ try {
19
+ const filepath = await writeHeapSnapshot()
20
+ await command.editReply({
21
+ content: `Heap snapshot written:\n\`${filepath}\``,
22
+ })
23
+ logger.log(`Memory snapshot requested via /memory-snapshot: ${filepath}`)
24
+ } catch (e) {
25
+ const msg = e instanceof Error ? e.message : String(e)
26
+ await command.editReply({
27
+ content: `Failed to write heap snapshot: ${msg}`,
28
+ })
29
+ }
30
+ }
@@ -0,0 +1,68 @@
1
+ // /toggle-mention-mode command.
2
+ // Toggles mention-only mode for a channel.
3
+ // When enabled, bot only responds to messages that @mention it.
4
+ // Messages in threads are not affected - they always work without mentions.
5
+
6
+ import {
7
+ ChatInputCommandInteraction,
8
+ MessageFlags,
9
+ ChannelType,
10
+ type TextChannel,
11
+ } from 'discord.js'
12
+ import { getChannelMentionMode, setChannelMentionMode } from '../database.js'
13
+ import { getKimakiMetadata } from '../discord-utils.js'
14
+ import { createLogger, LogPrefix } from '../logger.js'
15
+
16
+ const mentionModeLogger = createLogger(LogPrefix.CLI)
17
+
18
+ /**
19
+ * Handle the /toggle-mention-mode slash command.
20
+ * Toggles whether the bot only responds when @mentioned in this channel.
21
+ */
22
+ export async function handleToggleMentionModeCommand({
23
+ command,
24
+ }: {
25
+ command: ChatInputCommandInteraction
26
+ appId: string
27
+ }): Promise<void> {
28
+ mentionModeLogger.log('[TOGGLE_MENTION_MODE] Command called')
29
+
30
+ const channel = command.channel
31
+
32
+ if (!channel || channel.type !== ChannelType.GuildText) {
33
+ await command.reply({
34
+ content: 'This command can only be used in text channels (not threads).',
35
+ flags: MessageFlags.Ephemeral,
36
+ })
37
+ return
38
+ }
39
+
40
+ const textChannel = channel as TextChannel
41
+ const metadata = await getKimakiMetadata(textChannel)
42
+
43
+ if (!metadata.projectDirectory) {
44
+ await command.reply({
45
+ content:
46
+ 'This channel is not configured with a project directory.\nUse `/add-project` to set up this channel.',
47
+ flags: MessageFlags.Ephemeral,
48
+ })
49
+ return
50
+ }
51
+
52
+ const wasEnabled = await getChannelMentionMode(textChannel.id)
53
+ const nextEnabled = !wasEnabled
54
+ await setChannelMentionMode(textChannel.id, nextEnabled)
55
+
56
+ const nextLabel = nextEnabled ? 'enabled' : 'disabled'
57
+
58
+ mentionModeLogger.log(
59
+ `[TOGGLE_MENTION_MODE] ${nextLabel.toUpperCase()} for channel ${textChannel.id}`,
60
+ )
61
+
62
+ await command.reply({
63
+ content: nextEnabled
64
+ ? `Mention mode **enabled** for this channel.\nThe bot will only start new sessions when @mentioned.\nMessages in existing threads are not affected.`
65
+ : `Mention mode **disabled** for this channel.\nThe bot will respond to all messages in **#${textChannel.name}**.`,
66
+ flags: MessageFlags.Ephemeral,
67
+ })
68
+ }
@@ -0,0 +1,223 @@
1
+ // /merge-worktree command - Merge worktree commits into default branch.
2
+ // Pipeline: rebase worktree commits onto target -> local fast-forward push.
3
+ // Preserves all commits (no squash). On rebase conflicts, asks the AI model
4
+ // in the thread to resolve them.
5
+
6
+ import { type TextChannel, type ThreadChannel } from 'discord.js'
7
+ import type { AutocompleteContext, CommandContext } from './types.js'
8
+ import {
9
+ getThreadWorktree,
10
+ getThreadSession,
11
+ getChannelDirectory,
12
+ } from '../database.js'
13
+ import { createLogger, LogPrefix } from '../logger.js'
14
+ import { notifyError } from '../sentry.js'
15
+ import { mergeWorktree, listBranchesByLastCommit, validateBranchRef } from '../worktrees.js'
16
+ import {
17
+ sendThreadMessage,
18
+ resolveWorkingDirectory,
19
+ resolveProjectDirectoryFromAutocomplete,
20
+ } from '../discord-utils.js'
21
+ import {
22
+ getOrCreateRuntime,
23
+ } from '../session-handler/thread-session-runtime.js'
24
+ import { RebaseConflictError, DirtyWorktreeError } from '../errors.js'
25
+
26
+ const logger = createLogger(LogPrefix.WORKTREE)
27
+
28
+ /** Worktree thread title prefix - indicates unmerged worktree */
29
+ export const WORKTREE_PREFIX = '⬦ '
30
+
31
+ async function removeWorktreePrefixFromTitle(
32
+ thread: ThreadChannel,
33
+ ): Promise<void> {
34
+ if (!thread.name.startsWith(WORKTREE_PREFIX)) {
35
+ return
36
+ }
37
+ const newName = thread.name.slice(WORKTREE_PREFIX.length)
38
+ const timeoutMs = 5000
39
+ await Promise.race([
40
+ thread.setName(newName).catch((e) => {
41
+ logger.warn(
42
+ `Failed to update thread title: ${e instanceof Error ? e.message : String(e)}`,
43
+ )
44
+ }),
45
+ new Promise<void>((resolve) => {
46
+ setTimeout(() => {
47
+ logger.warn(`Thread title update timed out after ${timeoutMs}ms`)
48
+ resolve()
49
+ }, timeoutMs)
50
+ }),
51
+ ])
52
+ }
53
+
54
+ /**
55
+ * Send a prompt to the AI model in the thread.
56
+ * If a session is actively streaming, queues it. Otherwise sends directly.
57
+ * Routes through ThreadSessionRuntime.
58
+ */
59
+ async function sendPromptToModel({
60
+ prompt,
61
+ thread,
62
+ projectDirectory,
63
+ command,
64
+ appId,
65
+ }: {
66
+ prompt: string
67
+ thread: ThreadChannel
68
+ projectDirectory: string
69
+ command: CommandContext['command']
70
+ appId?: string
71
+ }): Promise<void> {
72
+ const resolved = await resolveWorkingDirectory({ channel: thread })
73
+
74
+ // Merge prompts use opencode queue mode.
75
+ const runtime = getOrCreateRuntime({
76
+ threadId: thread.id,
77
+ thread,
78
+ projectDirectory: resolved?.projectDirectory || projectDirectory,
79
+ sdkDirectory: resolved?.workingDirectory || projectDirectory,
80
+ channelId: thread.parentId || thread.id,
81
+ appId,
82
+ })
83
+ await runtime.enqueueIncoming({
84
+ prompt,
85
+ userId: command.user.id,
86
+ username: command.user.displayName,
87
+ appId,
88
+ mode: 'opencode',
89
+ })
90
+ }
91
+
92
+ export async function handleMergeWorktreeCommand({
93
+ command,
94
+ appId,
95
+ }: CommandContext): Promise<void> {
96
+ await command.deferReply()
97
+
98
+ const channel = command.channel
99
+ if (!channel || !channel.isThread()) {
100
+ await command.editReply('This command can only be used in a thread')
101
+ return
102
+ }
103
+
104
+ const thread = channel as ThreadChannel
105
+ const worktreeInfo = await getThreadWorktree(thread.id)
106
+ if (!worktreeInfo) {
107
+ await command.editReply('This thread is not associated with a worktree')
108
+ return
109
+ }
110
+
111
+ if (worktreeInfo.status !== 'ready' || !worktreeInfo.worktree_directory) {
112
+ await command.editReply(
113
+ `Worktree is not ready (status: ${worktreeInfo.status})${worktreeInfo.error_message ? `: ${worktreeInfo.error_message}` : ''}`,
114
+ )
115
+ return
116
+ }
117
+
118
+
119
+
120
+ const rawTargetBranch = command.options.getString('target-branch') || undefined
121
+ let targetBranch = rawTargetBranch
122
+ if (targetBranch) {
123
+ const validated = await validateBranchRef({
124
+ directory: worktreeInfo.project_directory,
125
+ ref: targetBranch,
126
+ })
127
+ if (validated instanceof Error) {
128
+ await command.editReply(`Invalid target branch: \`${targetBranch}\``)
129
+ return
130
+ }
131
+ targetBranch = validated
132
+ }
133
+
134
+ const result = await mergeWorktree({
135
+ worktreeDir: worktreeInfo.worktree_directory,
136
+ mainRepoDir: worktreeInfo.project_directory,
137
+ worktreeName: worktreeInfo.worktree_name,
138
+ targetBranch,
139
+ onProgress: (msg) => {
140
+ logger.log(`[merge] ${msg}`)
141
+ },
142
+ })
143
+
144
+ if (result instanceof Error) {
145
+ if (result instanceof DirtyWorktreeError) {
146
+ await command.editReply(
147
+ 'Merge failed: uncommitted changes in the worktree. Commit changes first, then run `/merge-worktree` again.',
148
+ )
149
+ return
150
+ }
151
+
152
+ if (result instanceof RebaseConflictError) {
153
+ await command.editReply(
154
+ 'Rebase conflict detected. Asking the model to resolve...',
155
+ )
156
+ await sendPromptToModel({
157
+ prompt: [
158
+ 'A rebase conflict occurred while merging this worktree into the default branch.',
159
+ 'Rebasing multiple commits can pause on each commit that conflicts, so you may need to repeat the resolve/continue loop several times.',
160
+ 'Please resolve the rebase conflicts:',
161
+ '1. Check `git status` to see which files have conflicts',
162
+ '2. Edit the conflicted files to resolve the merge markers',
163
+ '3. Stage resolved files with `git add`',
164
+ '4. Continue the rebase with `git rebase --continue`',
165
+ '5. If git reports more conflicts, repeat steps 1-4 until the rebase finishes (no more MERGE markers, `git status` shows no rebase in progress)',
166
+ '6. Once the rebase is fully complete, tell me so I can run `/merge-worktree` again',
167
+ ].join('\n'),
168
+ thread,
169
+ projectDirectory: worktreeInfo.project_directory,
170
+ command,
171
+ appId,
172
+ })
173
+ return
174
+ }
175
+
176
+ await command.editReply(`Merge failed: ${result.message}`)
177
+ return
178
+ }
179
+
180
+ void removeWorktreePrefixFromTitle(thread)
181
+ await command.editReply(
182
+ `Merged \`${result.branchName}\` into \`${result.defaultBranch}\` @ ${result.shortSha} (${result.commitCount} commit${result.commitCount === 1 ? '' : 's'})\nWorktree now at detached HEAD.`,
183
+ )
184
+ }
185
+
186
+ /**
187
+ * Autocomplete handler for /merge-worktree target-branch option.
188
+ * Lists local branches only (no remotes) sorted by most recent commit date.
189
+ * Resolves directory from the thread's worktree info or parent channel.
190
+ */
191
+ export async function handleMergeWorktreeAutocomplete({
192
+ interaction,
193
+ }: AutocompleteContext): Promise<void> {
194
+ try {
195
+ const focusedValue = interaction.options.getFocused()
196
+
197
+ // interaction.channel can be null when the channel isn't cached
198
+ // (common with gateway-proxy). Use channelId which is always available
199
+ // from the raw interaction payload.
200
+ const projectDirectory = await resolveProjectDirectoryFromAutocomplete(interaction)
201
+
202
+ if (!projectDirectory) {
203
+ await interaction.respond([])
204
+ return
205
+ }
206
+
207
+ // Local branches only — merge targets must be local refs
208
+ const branches = await listBranchesByLastCommit({
209
+ directory: projectDirectory,
210
+ query: focusedValue,
211
+ includeRemote: false,
212
+ })
213
+
214
+ await interaction.respond(
215
+ branches.map((name) => {
216
+ return { name, value: name }
217
+ }),
218
+ )
219
+ } catch (e) {
220
+ logger.error('[MERGE-WORKTREE] Autocomplete error:', e)
221
+ await interaction.respond([]).catch(() => {})
222
+ }
223
+ }