@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,386 @@
1
+ // Undo/Redo commands - /undo, /redo
2
+
3
+ import {
4
+ ChannelType,
5
+ MessageFlags,
6
+ type TextChannel,
7
+ type ThreadChannel,
8
+ } from 'discord.js'
9
+ import type { OpencodeClient } from '@opencode-ai/sdk/v2'
10
+ import type { CommandContext } from './types.js'
11
+ import { getThreadSession } from '../database.js'
12
+ import { initializeOpencodeForDirectory } from '../opencode.js'
13
+ import {
14
+ resolveWorkingDirectory,
15
+ SILENT_MESSAGE_FLAGS,
16
+ } from '../discord-utils.js'
17
+ import { createLogger, LogPrefix } from '../logger.js'
18
+
19
+ const logger = createLogger(LogPrefix.UNDO_REDO)
20
+
21
+ async function waitForSessionIdle({
22
+ client,
23
+ sessionId,
24
+ directory,
25
+ timeoutMs = 2_000,
26
+ }: {
27
+ client: OpencodeClient
28
+ sessionId: string
29
+ directory: string
30
+ timeoutMs?: number
31
+ }): Promise<void> {
32
+ const deadline = Date.now() + timeoutMs
33
+ while (Date.now() < deadline) {
34
+ const statusResponse = await client.session.status({ directory })
35
+ const sessionStatus = statusResponse.data?.[sessionId]
36
+ if (!sessionStatus || sessionStatus.type === 'idle') {
37
+ return
38
+ }
39
+ await new Promise((resolve) => {
40
+ setTimeout(resolve, 50)
41
+ })
42
+ }
43
+ }
44
+
45
+ export async function handleUndoCommand({
46
+ command,
47
+ }: CommandContext): Promise<void> {
48
+ const channel = command.channel
49
+
50
+ if (!channel) {
51
+ await command.reply({
52
+ content: 'This command can only be used in a channel',
53
+ flags: MessageFlags.Ephemeral | SILENT_MESSAGE_FLAGS,
54
+ })
55
+ return
56
+ }
57
+
58
+ const isThread = [
59
+ ChannelType.PublicThread,
60
+ ChannelType.PrivateThread,
61
+ ChannelType.AnnouncementThread,
62
+ ].includes(channel.type)
63
+
64
+ if (!isThread) {
65
+ await command.reply({
66
+ content:
67
+ 'This command can only be used in a thread with an active session',
68
+ flags: MessageFlags.Ephemeral | SILENT_MESSAGE_FLAGS,
69
+ })
70
+ return
71
+ }
72
+
73
+ const resolved = await resolveWorkingDirectory({
74
+ channel: channel as TextChannel | ThreadChannel,
75
+ })
76
+
77
+ if (!resolved) {
78
+ await command.reply({
79
+ content: 'Could not determine project directory for this channel',
80
+ flags: MessageFlags.Ephemeral | SILENT_MESSAGE_FLAGS,
81
+ })
82
+ return
83
+ }
84
+
85
+ const { projectDirectory, workingDirectory } = resolved
86
+
87
+ const sessionId = await getThreadSession(channel.id)
88
+
89
+ if (!sessionId) {
90
+ await command.reply({
91
+ content: 'No active session in this thread',
92
+ flags: MessageFlags.Ephemeral | SILENT_MESSAGE_FLAGS,
93
+ })
94
+ return
95
+ }
96
+
97
+ await command.deferReply({ flags: SILENT_MESSAGE_FLAGS })
98
+
99
+ const getClient = await initializeOpencodeForDirectory(projectDirectory)
100
+ if (getClient instanceof Error) {
101
+ await command.editReply(`Failed to undo: ${getClient.message}`)
102
+ return
103
+ }
104
+
105
+ try {
106
+ const client = getClient()
107
+ // Fetch session to check existing revert state
108
+ const sessionResponse = await client.session.get({
109
+ sessionID: sessionId,
110
+ directory: workingDirectory,
111
+ })
112
+ if (sessionResponse.error) {
113
+ await command.editReply(`Failed to undo: ${JSON.stringify(sessionResponse.error)}`)
114
+ return
115
+ }
116
+
117
+ // Abort if session is busy before reverting, matching TUI behavior
118
+ // (use-session-commands.tsx always aborts non-idle sessions before revert).
119
+ // session.status() returns a sparse map — only non-idle sessions have entries,
120
+ // so a missing key means idle.
121
+ const statusResponse = await client.session.status({
122
+ directory: workingDirectory,
123
+ })
124
+ const sessionStatus = statusResponse.data?.[sessionId]
125
+ if (sessionStatus && sessionStatus.type !== 'idle') {
126
+ await client.session.abort({
127
+ sessionID: sessionId,
128
+ directory: workingDirectory,
129
+ }).catch((error) => {
130
+ logger.warn(`[UNDO] abort failed for ${sessionId}`, error)
131
+ })
132
+ await waitForSessionIdle({
133
+ client,
134
+ sessionId,
135
+ directory: workingDirectory,
136
+ })
137
+ }
138
+
139
+ const messagesResponse = await client.session.messages({
140
+ sessionID: sessionId,
141
+ directory: workingDirectory,
142
+ })
143
+ if (messagesResponse.error) {
144
+ await command.editReply(`Failed to undo: ${JSON.stringify(messagesResponse.error)}`)
145
+ return
146
+ }
147
+
148
+ if (!messagesResponse.data || messagesResponse.data.length === 0) {
149
+ await command.editReply('No messages to undo')
150
+ return
151
+ }
152
+
153
+ // Follow the same approach as the OpenCode TUI (use-session-commands.tsx):
154
+ // find the last user message that is before the current revert point
155
+ // (or the last user message if no revert is active). This matches the
156
+ // TUI's `findLast(userMessages(), (x) => !revert || x.id < revert)`.
157
+ const currentRevert = sessionResponse.data?.revert?.messageID
158
+ const userMessages = messagesResponse.data.filter((m) => {
159
+ return m.info.role === 'user'
160
+ })
161
+ const targetUserMessage = [...userMessages].reverse().find((m) => {
162
+ return !currentRevert || m.info.id < currentRevert
163
+ })
164
+
165
+ if (!targetUserMessage) {
166
+ await command.editReply('No messages to undo')
167
+ return
168
+ }
169
+
170
+ const targetAssistantMessage = [...messagesResponse.data].reverse().find((m) => {
171
+ return m.info.role === 'assistant' && m.info.parentID === targetUserMessage.info.id
172
+ })
173
+ const revertMessageId = targetAssistantMessage?.info.id || targetUserMessage.info.id
174
+
175
+ // session.revert() reverts filesystem patches (file edits, writes) and
176
+ // marks the session with revert.messageID. Messages are NOT deleted — they
177
+ // get cleaned up automatically on the next promptAsync() call via
178
+ // SessionRevert.cleanup(). The model only sees messages before the revert
179
+ // point when processing the next prompt.
180
+ logger.log(`[UNDO] session.revert start messageId=${revertMessageId}`)
181
+ let response = await client.session.revert({
182
+ sessionID: sessionId,
183
+ directory: workingDirectory,
184
+ messageID: revertMessageId,
185
+ })
186
+ logger.log(`[UNDO] session.revert done error=${Boolean(response.error)}`)
187
+
188
+ if (response.error) {
189
+ logger.log('[UNDO] retry wait idle before revert retry')
190
+ await waitForSessionIdle({
191
+ client,
192
+ sessionId,
193
+ directory: workingDirectory,
194
+ })
195
+ logger.log('[UNDO] retry revert start')
196
+ response = await client.session.revert({
197
+ sessionID: sessionId,
198
+ directory: workingDirectory,
199
+ messageID: revertMessageId,
200
+ })
201
+ logger.log(`[UNDO] retry revert done error=${Boolean(response.error)}`)
202
+ if (response.error) {
203
+ await command.editReply(
204
+ `Failed to undo: ${JSON.stringify(response.error)}`,
205
+ )
206
+ return
207
+ }
208
+ }
209
+
210
+ const diffInfo = response.data?.revert?.diff
211
+ ? `\n\`\`\`diff\n${response.data.revert.diff.slice(0, 1500)}\n\`\`\``
212
+ : ''
213
+
214
+ await command.editReply(`Undone - reverted last assistant message${diffInfo}`)
215
+ logger.log(
216
+ `Session ${sessionId} reverted at message ${revertMessageId}`,
217
+ )
218
+ } catch (error) {
219
+ logger.error('[UNDO] Error:', error)
220
+ await command.editReply(
221
+ `Failed to undo: ${error instanceof Error ? error.message : 'Unknown error'}`,
222
+ )
223
+ }
224
+ }
225
+
226
+ export async function handleRedoCommand({
227
+ command,
228
+ }: CommandContext): Promise<void> {
229
+ const channel = command.channel
230
+
231
+ if (!channel) {
232
+ await command.reply({
233
+ content: 'This command can only be used in a channel',
234
+ flags: MessageFlags.Ephemeral | SILENT_MESSAGE_FLAGS,
235
+ })
236
+ return
237
+ }
238
+
239
+ const isThread = [
240
+ ChannelType.PublicThread,
241
+ ChannelType.PrivateThread,
242
+ ChannelType.AnnouncementThread,
243
+ ].includes(channel.type)
244
+
245
+ if (!isThread) {
246
+ await command.reply({
247
+ content:
248
+ 'This command can only be used in a thread with an active session',
249
+ flags: MessageFlags.Ephemeral | SILENT_MESSAGE_FLAGS,
250
+ })
251
+ return
252
+ }
253
+
254
+ const resolved = await resolveWorkingDirectory({
255
+ channel: channel as TextChannel | ThreadChannel,
256
+ })
257
+
258
+ if (!resolved) {
259
+ await command.reply({
260
+ content: 'Could not determine project directory for this channel',
261
+ flags: MessageFlags.Ephemeral | SILENT_MESSAGE_FLAGS,
262
+ })
263
+ return
264
+ }
265
+
266
+ const { projectDirectory, workingDirectory } = resolved
267
+
268
+ const sessionId = await getThreadSession(channel.id)
269
+
270
+ if (!sessionId) {
271
+ await command.reply({
272
+ content: 'No active session in this thread',
273
+ flags: MessageFlags.Ephemeral | SILENT_MESSAGE_FLAGS,
274
+ })
275
+ return
276
+ }
277
+
278
+ await command.deferReply({ flags: SILENT_MESSAGE_FLAGS })
279
+
280
+ const getClient = await initializeOpencodeForDirectory(projectDirectory)
281
+ if (getClient instanceof Error) {
282
+ await command.editReply(`Failed to redo: ${getClient.message}`)
283
+ return
284
+ }
285
+
286
+ try {
287
+ const client = getClient()
288
+
289
+ // Fetch session to check existing revert state
290
+ const sessionResponse = await client.session.get({
291
+ sessionID: sessionId,
292
+ directory: workingDirectory,
293
+ })
294
+ if (sessionResponse.error) {
295
+ await command.editReply(`Failed to redo: ${JSON.stringify(sessionResponse.error)}`)
296
+ return
297
+ }
298
+
299
+ const revertMessageID = sessionResponse.data?.revert?.messageID
300
+ if (!revertMessageID) {
301
+ await command.editReply('Nothing to redo - no previous undo found')
302
+ return
303
+ }
304
+
305
+ // Abort if session is busy before reverting/unreverting — both enforce
306
+ // assertNotBusy in OpenCode and would fail with "Session is busy"
307
+ const redoStatusResponse = await client.session.status({
308
+ directory: workingDirectory,
309
+ })
310
+ const redoSessionStatus = redoStatusResponse.data?.[sessionId]
311
+ if (redoSessionStatus && redoSessionStatus.type !== 'idle') {
312
+ await client.session.abort({
313
+ sessionID: sessionId,
314
+ directory: workingDirectory,
315
+ }).catch((error) => {
316
+ logger.warn(`[REDO] abort failed for ${sessionId}`, error)
317
+ })
318
+ await waitForSessionIdle({
319
+ client,
320
+ sessionId,
321
+ directory: workingDirectory,
322
+ })
323
+ }
324
+ await new Promise((resolve) => {
325
+ setTimeout(resolve, 500)
326
+ })
327
+
328
+ // Follow the same approach as the OpenCode TUI (use-session-commands.tsx):
329
+ // find the next user message after the current revert point. If one exists,
330
+ // move the revert cursor forward to it (one step redo). If none exists,
331
+ // fully unrevert — we're at the end of the message history.
332
+ const messagesResponse = await client.session.messages({
333
+ sessionID: sessionId,
334
+ directory: workingDirectory,
335
+ })
336
+ if (messagesResponse.error) {
337
+ await command.editReply(`Failed to redo: ${JSON.stringify(messagesResponse.error)}`)
338
+ return
339
+ }
340
+ const userMessages = (messagesResponse.data ?? []).filter((m) => {
341
+ return m.info.role === 'user'
342
+ })
343
+ const nextMessage = userMessages.find((m) => {
344
+ return m.info.id > revertMessageID
345
+ })
346
+
347
+ if (!nextMessage) {
348
+ // No more messages after revert point — fully unrevert
349
+ const response = await client.session.unrevert({
350
+ sessionID: sessionId,
351
+ directory: workingDirectory,
352
+ })
353
+ if (response.error) {
354
+ await command.editReply(
355
+ `Failed to redo: ${JSON.stringify(response.error)}`,
356
+ )
357
+ return
358
+ }
359
+ await command.editReply('Restored - session fully back to previous state')
360
+ logger.log(`Session ${sessionId} unrevert completed`)
361
+ return
362
+ }
363
+
364
+ // Move revert cursor forward one step to the next user message
365
+ const response = await client.session.revert({
366
+ sessionID: sessionId,
367
+ directory: workingDirectory,
368
+ messageID: nextMessage.info.id,
369
+ })
370
+
371
+ if (response.error) {
372
+ await command.editReply(
373
+ `Failed to redo: ${JSON.stringify(response.error)}`,
374
+ )
375
+ return
376
+ }
377
+
378
+ await command.editReply('Restored one step forward')
379
+ logger.log(`Session ${sessionId} redo: moved revert to ${nextMessage.info.id}`)
380
+ } catch (error) {
381
+ logger.error('[REDO] Error:', error)
382
+ await command.editReply(
383
+ `Failed to redo: ${error instanceof Error ? error.message : 'Unknown error'}`,
384
+ )
385
+ }
386
+ }
@@ -0,0 +1,173 @@
1
+ // /unset-model-override command - Remove model overrides and use default instead.
2
+
3
+ import {
4
+ ChatInputCommandInteraction,
5
+ ChannelType,
6
+ type ThreadChannel,
7
+ type TextChannel,
8
+ MessageFlags,
9
+ } from 'discord.js'
10
+ import {
11
+ getChannelModel,
12
+ getSessionModel,
13
+ getThreadSession,
14
+ clearSessionModel,
15
+ } from '../database.js'
16
+ import { getPrisma } from '../db.js'
17
+ import { initializeOpencodeForDirectory } from '../opencode.js'
18
+ import { resolveTextChannel, getKimakiMetadata } from '../discord-utils.js'
19
+ import { getRuntime } from '../session-handler/thread-session-runtime.js'
20
+ import { getCurrentModelInfo } from './model.js'
21
+ import { createLogger, LogPrefix } from '../logger.js'
22
+
23
+ const unsetModelLogger = createLogger(LogPrefix.MODEL)
24
+
25
+ function formatModelSource(type: string, agentName?: string): string {
26
+ switch (type) {
27
+ case 'session':
28
+ return 'session override'
29
+ case 'agent':
30
+ return `agent "${agentName}"`
31
+ case 'channel':
32
+ return 'channel override'
33
+ case 'global':
34
+ return 'global default'
35
+ case 'opencode-config':
36
+ case 'opencode-recent':
37
+ case 'opencode-provider-default':
38
+ return 'opencode default'
39
+ default:
40
+ return 'none'
41
+ }
42
+ }
43
+
44
+ /**
45
+ * Handle the /unset-model-override slash command.
46
+ * In thread: clears session override if exists, otherwise channel override.
47
+ * In channel: clears channel override.
48
+ */
49
+ export async function handleUnsetModelCommand({
50
+ interaction,
51
+ appId,
52
+ }: {
53
+ interaction: ChatInputCommandInteraction
54
+ appId: string
55
+ }): Promise<void> {
56
+ unsetModelLogger.log('[UNSET-MODEL] handleUnsetModelCommand called')
57
+
58
+ await interaction.deferReply({ flags: MessageFlags.Ephemeral })
59
+
60
+ const channel = interaction.channel
61
+
62
+ if (!channel) {
63
+ await interaction.editReply({
64
+ content: 'This command can only be used in a channel',
65
+ })
66
+ return
67
+ }
68
+
69
+ const isThread = [
70
+ ChannelType.PublicThread,
71
+ ChannelType.PrivateThread,
72
+ ChannelType.AnnouncementThread,
73
+ ].includes(channel.type)
74
+
75
+ let projectDirectory: string | undefined
76
+ let targetChannelId: string
77
+ let sessionId: string | undefined
78
+
79
+ if (isThread) {
80
+ const thread = channel as ThreadChannel
81
+ const textChannel = await resolveTextChannel(thread)
82
+ const metadata = await getKimakiMetadata(textChannel)
83
+ projectDirectory = metadata.projectDirectory
84
+ targetChannelId = textChannel?.id || channel.id
85
+ sessionId = await getThreadSession(thread.id)
86
+ } else if (channel.type === ChannelType.GuildText) {
87
+ const textChannel = channel as TextChannel
88
+ const metadata = await getKimakiMetadata(textChannel)
89
+ projectDirectory = metadata.projectDirectory
90
+ targetChannelId = channel.id
91
+ } else {
92
+ await interaction.editReply({
93
+ content: 'This command can only be used in text channels or threads',
94
+ })
95
+ return
96
+ }
97
+
98
+ if (!projectDirectory) {
99
+ await interaction.editReply({
100
+ content: 'This channel is not configured with a project directory',
101
+ })
102
+ return
103
+ }
104
+
105
+ // Check what overrides exist
106
+ const [sessionPref, channelPref] = await Promise.all([
107
+ sessionId ? getSessionModel(sessionId) : Promise.resolve(undefined),
108
+ getChannelModel(targetChannelId),
109
+ ])
110
+
111
+ let clearedType: 'session' | 'channel' | null = null
112
+ let clearedModel: string | undefined
113
+
114
+ if (isThread && sessionId && sessionPref) {
115
+ // In thread with session override: clear session
116
+ await clearSessionModel(sessionId)
117
+ clearedType = 'session'
118
+ clearedModel = sessionPref.modelId
119
+ unsetModelLogger.log(`[UNSET-MODEL] Cleared session model for ${sessionId}`)
120
+ } else if (channelPref) {
121
+ // Clear channel override
122
+ const prisma = await getPrisma()
123
+ await prisma.channel_models.deleteMany({
124
+ where: { channel_id: targetChannelId },
125
+ })
126
+ clearedType = 'channel'
127
+ clearedModel = channelPref.modelId
128
+ unsetModelLogger.log(
129
+ `[UNSET-MODEL] Cleared channel model for ${targetChannelId}`,
130
+ )
131
+ } else {
132
+ await interaction.editReply({
133
+ content: 'No model override to clear.',
134
+ })
135
+ return
136
+ }
137
+
138
+ // Get the new model that will be used
139
+ const getClient = await initializeOpencodeForDirectory(projectDirectory)
140
+ let newModelText = 'unknown'
141
+
142
+ if (!(getClient instanceof Error)) {
143
+ const newModelInfo = await getCurrentModelInfo({
144
+ sessionId,
145
+ channelId: targetChannelId,
146
+ appId,
147
+ getClient,
148
+ })
149
+
150
+ newModelText =
151
+ newModelInfo.type === 'none'
152
+ ? 'none'
153
+ : `\`${newModelInfo.model}\` (${formatModelSource(newModelInfo.type, 'agentName' in newModelInfo ? newModelInfo.agentName : undefined)})`
154
+ }
155
+
156
+ // Check if there's a running request and abort+retry with new model (only for session changes in threads)
157
+ let retried = false
158
+ if (isThread && clearedType === 'session' && sessionId) {
159
+ const runtime = getRuntime(channel.id)
160
+ if (runtime) {
161
+ retried = await runtime.retryLastUserPrompt()
162
+ }
163
+ }
164
+
165
+ const clearedTypeText = clearedType === 'session' ? 'Session' : 'Channel'
166
+ const retriedText = retried
167
+ ? '\n_Restarting current request with new model..._'
168
+ : ''
169
+
170
+ await interaction.editReply({
171
+ content: `${clearedTypeText} model override removed.\n**Was:** \`${clearedModel}\`\n**Now using:** ${newModelText}${retriedText}`,
172
+ })
173
+ }
@@ -0,0 +1,52 @@
1
+ // /upgrade-and-restart command - Upgrade kimaki to the latest version and restart the bot.
2
+ // Checks npm for a newer version, installs it globally, then spawns a new kimaki process.
3
+ // The new process kills the old one on startup (kimaki's single-instance lock).
4
+
5
+ import type { CommandContext } from './types.js'
6
+ import { SILENT_MESSAGE_FLAGS } from '../discord-utils.js'
7
+ import { createLogger, LogPrefix } from '../logger.js'
8
+ import { getCurrentVersion, upgrade } from '../upgrade.js'
9
+ import { spawn } from 'node:child_process'
10
+
11
+ const logger = createLogger(LogPrefix.CLI)
12
+
13
+ export async function handleUpgradeAndRestartCommand({
14
+ command,
15
+ }: CommandContext): Promise<void> {
16
+ await command.deferReply({ flags: SILENT_MESSAGE_FLAGS })
17
+
18
+ logger.log('[UPGRADE] /upgrade-and-restart triggered')
19
+
20
+ try {
21
+ const currentVersion = getCurrentVersion()
22
+ const newVersion = await upgrade()
23
+
24
+ if (!newVersion) {
25
+ await command.editReply({
26
+ content: `Already on latest version: **v${currentVersion}**`,
27
+ })
28
+ return
29
+ }
30
+
31
+ await command.editReply({
32
+ content: `Upgraded kimaki **v${currentVersion}** -> **v${newVersion}**. Restarting bot...`,
33
+ })
34
+
35
+ // Spawning bare `kimaki` works even if the user originally ran via npx/bunx:
36
+ // `npm i -g kimaki@latest` creates a global bin link, and npx resolves
37
+ // local -> global -> cache -> registry, so it prefers the global install.
38
+ // bunx shares the same global cache, so it also picks up the new version.
39
+ const child = spawn('kimaki', process.argv.slice(2), {
40
+ shell: true,
41
+ stdio: 'ignore',
42
+ detached: true,
43
+ })
44
+ child.unref()
45
+ logger.debug('Started new background kimaki')
46
+ } catch (error) {
47
+ logger.error('[UPGRADE] Failed:', error)
48
+ await command.editReply({
49
+ content: `Upgrade failed: ${error instanceof Error ? error.message : String(error)}`,
50
+ })
51
+ }
52
+ }