@otto-assistant/otto 0.1.1 → 0.7.15

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 (637) 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-account-identity.js +62 -0
  7. package/dist/anthropic-account-identity.test.js +38 -0
  8. package/dist/anthropic-auth-plugin.js +917 -0
  9. package/dist/anthropic-auth-state.js +303 -0
  10. package/dist/anthropic-auth-state.test.js +150 -0
  11. package/dist/bin.js +152 -0
  12. package/dist/btw-prefix-detection.js +17 -0
  13. package/dist/btw-prefix-detection.test.js +63 -0
  14. package/dist/channel-management.js +259 -0
  15. package/dist/cli-parsing.test.js +142 -0
  16. package/dist/cli-send-thread.e2e.test.js +353 -0
  17. package/dist/cli-telegram-options.test.js +99 -0
  18. package/dist/cli.js +4210 -568
  19. package/dist/commands/abort.js +65 -0
  20. package/dist/commands/action-buttons.js +245 -0
  21. package/dist/commands/add-dir.js +124 -0
  22. package/dist/commands/add-dir.test.js +126 -0
  23. package/dist/commands/add-project.js +113 -0
  24. package/dist/commands/agent.js +355 -0
  25. package/dist/commands/ask-question.js +320 -0
  26. package/dist/commands/ask-question.test.js +92 -0
  27. package/dist/commands/btw.js +121 -0
  28. package/dist/commands/cli-commands-group-a.test.js +728 -0
  29. package/dist/commands/cli-commands-group-b.test.js +695 -0
  30. package/dist/commands/compact.js +120 -0
  31. package/dist/commands/context-usage.js +140 -0
  32. package/dist/commands/create-new-project.js +130 -0
  33. package/dist/commands/diff.js +63 -0
  34. package/dist/commands/discord-commands-group-a.test.js +621 -0
  35. package/dist/commands/discord-commands-group-b.test.js +595 -0
  36. package/dist/commands/discord-commands-group-c.test.js +739 -0
  37. package/dist/commands/file-upload.js +275 -0
  38. package/dist/commands/fork-subagent.js +177 -0
  39. package/dist/commands/fork.js +262 -0
  40. package/dist/commands/gemini-apikey.js +70 -0
  41. package/dist/commands/login.js +887 -0
  42. package/dist/commands/mcp.js +239 -0
  43. package/dist/commands/memory-snapshot.js +24 -0
  44. package/dist/commands/mention-mode.js +44 -0
  45. package/dist/commands/merge-worktree.js +162 -0
  46. package/dist/commands/model-variant.js +366 -0
  47. package/dist/commands/model.js +794 -0
  48. package/dist/commands/new-worktree.js +465 -0
  49. package/dist/commands/paginated-select.js +57 -0
  50. package/dist/commands/permissions.js +274 -0
  51. package/dist/commands/queue.js +223 -0
  52. package/dist/commands/remove-project.js +115 -0
  53. package/dist/commands/restart-opencode-server.js +127 -0
  54. package/dist/commands/resume.js +149 -0
  55. package/dist/commands/run-command.js +79 -0
  56. package/dist/commands/screenshare.js +303 -0
  57. package/dist/commands/screenshare.test.js +20 -0
  58. package/dist/commands/session-id.js +78 -0
  59. package/dist/commands/session.js +176 -0
  60. package/dist/commands/share.js +80 -0
  61. package/dist/commands/tasks.js +205 -0
  62. package/dist/commands/thread-deletion-sync.js +50 -0
  63. package/dist/commands/types.js +2 -0
  64. package/dist/commands/undo-redo.js +305 -0
  65. package/dist/commands/unset-model.js +139 -0
  66. package/dist/commands/upgrade.js +48 -0
  67. package/dist/commands/user-command.js +155 -0
  68. package/dist/commands/verbosity.js +125 -0
  69. package/dist/commands/vscode.js +269 -0
  70. package/dist/commands/worktree-settings.js +43 -0
  71. package/dist/commands/worktrees.js +468 -0
  72. package/dist/condense-memory.js +33 -0
  73. package/dist/config.js +100 -255
  74. package/dist/context-awareness-plugin.js +340 -0
  75. package/dist/context-awareness-plugin.test.js +126 -0
  76. package/dist/critique-utils.js +95 -0
  77. package/dist/database.js +1355 -0
  78. package/dist/db.js +260 -0
  79. package/dist/db.test.js +138 -0
  80. package/dist/debounce-timeout.js +28 -0
  81. package/dist/debounced-process-flush.js +77 -0
  82. package/dist/discord-bot.js +1124 -0
  83. package/dist/discord-command-registration.js +567 -0
  84. package/dist/discord-urls.js +82 -0
  85. package/dist/discord-utils.js +616 -0
  86. package/dist/discord-utils.test.js +134 -0
  87. package/dist/errors.js +157 -0
  88. package/dist/escape-backticks.test.js +429 -0
  89. package/dist/event-stream-real-capture.e2e.test.js +533 -0
  90. package/dist/eventsource-parser.test.js +327 -0
  91. package/dist/exec-async.js +26 -0
  92. package/dist/external-opencode-sync.js +480 -0
  93. package/dist/format-tables.js +491 -0
  94. package/dist/format-tables.test.js +478 -0
  95. package/dist/forum-sync/config.js +79 -0
  96. package/dist/forum-sync/discord-operations.js +154 -0
  97. package/dist/forum-sync/index.js +5 -0
  98. package/dist/forum-sync/markdown.js +113 -0
  99. package/dist/forum-sync/sync-to-discord.js +417 -0
  100. package/dist/forum-sync/sync-to-files.js +190 -0
  101. package/dist/forum-sync/types.js +53 -0
  102. package/dist/forum-sync/watchers.js +307 -0
  103. package/dist/gateway-proxy-reconnect.e2e.test.js +394 -0
  104. package/dist/gateway-proxy.e2e.test.js +485 -0
  105. package/dist/genai-worker-wrapper.js +111 -0
  106. package/dist/genai-worker.js +311 -0
  107. package/dist/genai.js +232 -0
  108. package/dist/generated/browser.js +17 -0
  109. package/dist/generated/client.js +37 -0
  110. package/dist/generated/commonInputTypes.js +10 -0
  111. package/dist/generated/enums.js +58 -0
  112. package/dist/generated/internal/class.js +49 -0
  113. package/dist/generated/internal/prismaNamespace.js +254 -0
  114. package/dist/generated/internal/prismaNamespaceBrowser.js +224 -0
  115. package/dist/generated/models/bot_api_keys.js +1 -0
  116. package/dist/generated/models/bot_tokens.js +1 -0
  117. package/dist/generated/models/channel_agents.js +1 -0
  118. package/dist/generated/models/channel_directories.js +1 -0
  119. package/dist/generated/models/channel_mention_mode.js +1 -0
  120. package/dist/generated/models/channel_models.js +1 -0
  121. package/dist/generated/models/channel_verbosity.js +1 -0
  122. package/dist/generated/models/channel_worktrees.js +1 -0
  123. package/dist/generated/models/forum_sync_configs.js +1 -0
  124. package/dist/generated/models/global_models.js +1 -0
  125. package/dist/generated/models/ipc_requests.js +1 -0
  126. package/dist/generated/models/part_messages.js +1 -0
  127. package/dist/generated/models/scheduled_tasks.js +1 -0
  128. package/dist/generated/models/session_agents.js +1 -0
  129. package/dist/generated/models/session_events.js +1 -0
  130. package/dist/generated/models/session_models.js +1 -0
  131. package/dist/generated/models/session_start_sources.js +1 -0
  132. package/dist/generated/models/thread_sessions.js +1 -0
  133. package/dist/generated/models/thread_worktrees.js +1 -0
  134. package/dist/generated/models.js +1 -0
  135. package/dist/heap-monitor.js +122 -0
  136. package/dist/hrana-server.js +251 -0
  137. package/dist/hrana-server.test.js +370 -0
  138. package/dist/html-actions.js +123 -0
  139. package/dist/html-actions.test.js +70 -0
  140. package/dist/html-components.js +117 -0
  141. package/dist/html-components.test.js +34 -0
  142. package/dist/image-optimizer-plugin.js +153 -0
  143. package/dist/image-utils.js +112 -0
  144. package/dist/interaction-handler.js +420 -0
  145. package/dist/ipc-polling.js +327 -0
  146. package/dist/ipc-tools-plugin.js +193 -0
  147. package/dist/ipc-utils.js +18 -0
  148. package/dist/limit-heading-depth.js +25 -0
  149. package/dist/limit-heading-depth.test.js +105 -0
  150. package/dist/logger.js +171 -0
  151. package/dist/markdown.js +342 -0
  152. package/dist/markdown.test.js +264 -0
  153. package/dist/memory-overview-plugin.js +128 -0
  154. package/dist/message-finish-field.e2e.test.js +168 -0
  155. package/dist/message-formatting.js +415 -0
  156. package/dist/message-formatting.test.js +115 -0
  157. package/dist/message-preprocessing.js +359 -0
  158. package/dist/onboarding-tutorial.js +163 -0
  159. package/dist/onboarding-welcome.js +37 -0
  160. package/dist/openai-realtime.js +224 -0
  161. package/dist/opencode-command-detection.js +65 -0
  162. package/dist/opencode-command-detection.test.js +240 -0
  163. package/dist/opencode-command.js +131 -0
  164. package/dist/opencode-command.test.js +48 -0
  165. package/dist/opencode-interrupt-plugin.js +388 -0
  166. package/dist/opencode-interrupt-plugin.test.js +463 -0
  167. package/dist/opencode.js +1117 -0
  168. package/dist/otto/branding.js +22 -0
  169. package/dist/otto/index.js +21 -0
  170. package/dist/otto-digital-twin.e2e.test.js +161 -0
  171. package/dist/otto-opencode-plugin-loading.e2e.test.js +94 -0
  172. package/dist/otto-opencode-plugin.js +21 -0
  173. package/dist/otto-opencode-plugin.test.js +98 -0
  174. package/dist/parse-permission-rules.test.js +117 -0
  175. package/dist/patch-text-parser.js +97 -0
  176. package/dist/plugin-logger.js +68 -0
  177. package/dist/privacy-sanitizer.js +105 -0
  178. package/dist/queue-advanced-abort.e2e.test.js +293 -0
  179. package/dist/queue-advanced-action-buttons.e2e.test.js +206 -0
  180. package/dist/queue-advanced-e2e-setup.js +790 -0
  181. package/dist/queue-advanced-footer.e2e.test.js +481 -0
  182. package/dist/queue-advanced-model-switch.e2e.test.js +299 -0
  183. package/dist/queue-advanced-permissions-typing.e2e.test.js +179 -0
  184. package/dist/queue-advanced-question.e2e.test.js +261 -0
  185. package/dist/queue-advanced-typing-interrupt.e2e.test.js +114 -0
  186. package/dist/queue-advanced-typing.e2e.test.js +153 -0
  187. package/dist/queue-drain-after-interactive-ui.e2e.test.js +119 -0
  188. package/dist/queue-interrupt-drain.e2e.test.js +135 -0
  189. package/dist/queue-question-select-drain.e2e.test.js +256 -0
  190. package/dist/runtime-idle-sweeper.js +52 -0
  191. package/dist/runtime-lifecycle.e2e.test.js +514 -0
  192. package/dist/sentry.js +23 -0
  193. package/dist/session-handler/agent-utils.js +67 -0
  194. package/dist/session-handler/event-stream-state.js +475 -0
  195. package/dist/session-handler/event-stream-state.test.js +632 -0
  196. package/dist/session-handler/model-utils.js +147 -0
  197. package/dist/session-handler/opencode-session-event-log.js +94 -0
  198. package/dist/session-handler/thread-runtime-state.js +131 -0
  199. package/dist/session-handler/thread-session-runtime.js +3390 -0
  200. package/dist/session-handler.js +9 -0
  201. package/dist/session-search.js +100 -0
  202. package/dist/session-search.test.js +40 -0
  203. package/dist/session-title-rename.test.js +92 -0
  204. package/dist/skill-filter.js +31 -0
  205. package/dist/skill-filter.test.js +65 -0
  206. package/dist/startup-service.js +153 -0
  207. package/dist/startup-time.e2e.test.js +296 -0
  208. package/dist/store.js +19 -0
  209. package/dist/subagent-rate-limit-plugin.js +175 -0
  210. package/dist/system-message.js +702 -0
  211. package/dist/system-message.test.js +697 -0
  212. package/dist/task-runner.js +530 -0
  213. package/dist/task-schedule.js +213 -0
  214. package/dist/task-schedule.test.js +71 -0
  215. package/dist/test-utils.js +313 -0
  216. package/dist/thinking-utils.js +35 -0
  217. package/dist/thread-message-queue.e2e.test.js +1111 -0
  218. package/dist/tools.js +357 -0
  219. package/dist/undo-redo.e2e.test.js +161 -0
  220. package/dist/unnest-code-blocks.js +146 -0
  221. package/dist/unnest-code-blocks.test.js +673 -0
  222. package/dist/upgrade.js +156 -0
  223. package/dist/utils.js +172 -0
  224. package/dist/utils.test.js +130 -0
  225. package/dist/voice-attachment.js +34 -0
  226. package/dist/voice-handler.js +646 -0
  227. package/dist/voice-message.e2e.test.js +1021 -0
  228. package/dist/voice.js +456 -0
  229. package/dist/voice.test.js +235 -0
  230. package/dist/wait-session.js +171 -0
  231. package/dist/websockify.js +69 -0
  232. package/dist/worker-types.js +4 -0
  233. package/dist/worktree-lifecycle.e2e.test.js +311 -0
  234. package/dist/worktree-utils.js +3 -0
  235. package/dist/worktrees.js +991 -0
  236. package/dist/worktrees.test.js +415 -0
  237. package/dist/xml.js +92 -0
  238. package/dist/xml.test.js +32 -0
  239. package/package.json +90 -38
  240. package/schema.prisma +303 -0
  241. package/skills/batch/SKILL.md +87 -0
  242. package/skills/critique/SKILL.md +112 -0
  243. package/skills/egaki/SKILL.md +100 -0
  244. package/skills/errore/SKILL.md +647 -0
  245. package/skills/event-sourcing-state/SKILL.md +252 -0
  246. package/skills/goke/SKILL.md +38 -0
  247. package/skills/jitter/EDITOR.md +219 -0
  248. package/skills/jitter/EXPORT-INTERNALS.md +309 -0
  249. package/skills/jitter/SKILL.md +158 -0
  250. package/skills/jitter/jitter-clipboard.json +1042 -0
  251. package/skills/jitter/package.json +14 -0
  252. package/skills/jitter/tsconfig.json +15 -0
  253. package/skills/jitter/utils/actions.ts +212 -0
  254. package/skills/jitter/utils/export.ts +114 -0
  255. package/skills/jitter/utils/index.ts +141 -0
  256. package/skills/jitter/utils/snapshot.ts +154 -0
  257. package/skills/jitter/utils/traverse.ts +246 -0
  258. package/skills/jitter/utils/types.ts +279 -0
  259. package/skills/jitter/utils/wait.ts +133 -0
  260. package/skills/lintcn/SKILL.md +873 -0
  261. package/skills/manual-kimaki-upstream-adapt/SKILL.md +114 -0
  262. package/skills/new-skill/SKILL.md +237 -0
  263. package/skills/npm-package/SKILL.md +617 -0
  264. package/skills/opensrc/SKILL.md +78 -0
  265. package/skills/otto-publish/SKILL.md +61 -0
  266. package/skills/playwriter/SKILL.md +35 -0
  267. package/skills/profano/SKILL.md +16 -0
  268. package/skills/proxyman/SKILL.md +215 -0
  269. package/skills/security-review/SKILL.md +208 -0
  270. package/skills/sigillo/SKILL.md +101 -0
  271. package/skills/simplify/SKILL.md +58 -0
  272. package/skills/spiceflow/SKILL.md +28 -0
  273. package/skills/termcast/SKILL.md +945 -0
  274. package/skills/tuistory/SKILL.md +98 -0
  275. package/skills/usecomputer/SKILL.md +264 -0
  276. package/skills/x-articles/SKILL.md +554 -0
  277. package/skills/zele/SKILL.md +49 -0
  278. package/skills/zustand-centralized-state/SKILL.md +1004 -0
  279. package/src/agent-model.e2e.test.ts +979 -0
  280. package/src/ai-tool-to-genai.test.ts +296 -0
  281. package/src/ai-tool-to-genai.ts +283 -0
  282. package/src/ai-tool.ts +39 -0
  283. package/src/anthropic-account-identity.test.ts +52 -0
  284. package/src/anthropic-account-identity.ts +77 -0
  285. package/src/anthropic-auth-plugin.ts +1139 -0
  286. package/src/anthropic-auth-state.test.ts +187 -0
  287. package/src/anthropic-auth-state.ts +386 -0
  288. package/src/bin.ts +182 -0
  289. package/src/btw-prefix-detection.test.ts +73 -0
  290. package/src/btw-prefix-detection.ts +23 -0
  291. package/src/channel-management.ts +376 -0
  292. package/src/cli-parsing.test.ts +197 -0
  293. package/src/cli-send-thread.e2e.test.ts +463 -0
  294. package/src/cli-telegram-options.test.ts +114 -0
  295. package/src/cli.ts +5718 -580
  296. package/src/commands/abort.ts +89 -0
  297. package/src/commands/action-buttons.ts +364 -0
  298. package/src/commands/add-dir.test.ts +154 -0
  299. package/src/commands/add-dir.ts +175 -0
  300. package/src/commands/add-project.ts +149 -0
  301. package/src/commands/agent.ts +496 -0
  302. package/src/commands/ask-question.test.ts +111 -0
  303. package/src/commands/ask-question.ts +455 -0
  304. package/src/commands/btw.ts +184 -0
  305. package/src/commands/cli-commands-group-a.test.ts +837 -0
  306. package/src/commands/cli-commands-group-b.test.ts +800 -0
  307. package/src/commands/compact.ts +157 -0
  308. package/src/commands/context-usage.ts +199 -0
  309. package/src/commands/create-new-project.ts +190 -0
  310. package/src/commands/diff.ts +91 -0
  311. package/src/commands/discord-commands-group-a.test.ts +751 -0
  312. package/src/commands/discord-commands-group-b.test.ts +648 -0
  313. package/src/commands/discord-commands-group-c.test.ts +882 -0
  314. package/src/commands/file-upload.ts +389 -0
  315. package/src/commands/fork-subagent.ts +263 -0
  316. package/src/commands/fork.ts +386 -0
  317. package/src/commands/gemini-apikey.ts +104 -0
  318. package/src/commands/login.ts +1175 -0
  319. package/src/commands/mcp.ts +307 -0
  320. package/src/commands/memory-snapshot.ts +30 -0
  321. package/src/commands/mention-mode.ts +68 -0
  322. package/src/commands/merge-worktree.ts +226 -0
  323. package/src/commands/model-variant.ts +485 -0
  324. package/src/commands/model.ts +1078 -0
  325. package/src/commands/new-worktree.ts +645 -0
  326. package/src/commands/paginated-select.ts +81 -0
  327. package/src/commands/permissions.ts +397 -0
  328. package/src/commands/queue.ts +293 -0
  329. package/src/commands/remove-project.ts +155 -0
  330. package/src/commands/restart-opencode-server.ts +162 -0
  331. package/src/commands/resume.ts +230 -0
  332. package/src/commands/run-command.ts +123 -0
  333. package/src/commands/screenshare.test.ts +30 -0
  334. package/src/commands/screenshare.ts +366 -0
  335. package/src/commands/session-id.ts +109 -0
  336. package/src/commands/session.ts +227 -0
  337. package/src/commands/share.ts +106 -0
  338. package/src/commands/tasks.ts +293 -0
  339. package/src/commands/thread-deletion-sync.ts +80 -0
  340. package/src/commands/types.ts +25 -0
  341. package/src/commands/undo-redo.ts +386 -0
  342. package/src/commands/unset-model.ts +174 -0
  343. package/src/commands/upgrade.ts +59 -0
  344. package/src/commands/user-command.ts +198 -0
  345. package/src/commands/verbosity.ts +173 -0
  346. package/src/commands/vscode.ts +342 -0
  347. package/src/commands/worktree-settings.ts +70 -0
  348. package/src/commands/worktrees.ts +645 -0
  349. package/src/condense-memory.ts +36 -0
  350. package/src/config.ts +103 -339
  351. package/src/context-awareness-plugin.test.ts +144 -0
  352. package/src/context-awareness-plugin.ts +469 -0
  353. package/src/critique-utils.ts +139 -0
  354. package/src/database.ts +1949 -0
  355. package/src/db.test.ts +162 -0
  356. package/src/db.ts +295 -0
  357. package/src/debounce-timeout.ts +43 -0
  358. package/src/debounced-process-flush.ts +104 -0
  359. package/src/discord-bot.ts +1505 -0
  360. package/src/discord-command-registration.ts +752 -0
  361. package/src/discord-urls.ts +89 -0
  362. package/src/discord-utils.test.ts +153 -0
  363. package/src/discord-utils.ts +846 -0
  364. package/src/errors.ts +201 -0
  365. package/src/escape-backticks.test.ts +469 -0
  366. package/src/event-stream-real-capture.e2e.test.ts +692 -0
  367. package/src/eventsource-parser.test.ts +351 -0
  368. package/src/exec-async.ts +35 -0
  369. package/src/external-opencode-sync.ts +685 -0
  370. package/src/format-tables.test.ts +515 -0
  371. package/src/format-tables.ts +718 -0
  372. package/src/forum-sync/config.ts +92 -0
  373. package/src/forum-sync/discord-operations.ts +241 -0
  374. package/src/forum-sync/index.ts +9 -0
  375. package/src/forum-sync/markdown.ts +172 -0
  376. package/src/forum-sync/sync-to-discord.ts +595 -0
  377. package/src/forum-sync/sync-to-files.ts +294 -0
  378. package/src/forum-sync/types.ts +175 -0
  379. package/src/forum-sync/watchers.ts +454 -0
  380. package/src/gateway-proxy-reconnect.e2e.test.ts +523 -0
  381. package/src/gateway-proxy.e2e.test.ts +644 -0
  382. package/src/genai-worker-wrapper.ts +164 -0
  383. package/src/genai-worker.ts +386 -0
  384. package/src/genai.ts +321 -0
  385. package/src/generated/browser.ts +114 -0
  386. package/src/generated/client.ts +138 -0
  387. package/src/generated/commonInputTypes.ts +770 -0
  388. package/src/generated/enums.ts +98 -0
  389. package/src/generated/internal/class.ts +384 -0
  390. package/src/generated/internal/prismaNamespace.ts +2394 -0
  391. package/src/generated/internal/prismaNamespaceBrowser.ts +327 -0
  392. package/src/generated/models/bot_api_keys.ts +1288 -0
  393. package/src/generated/models/bot_tokens.ts +1700 -0
  394. package/src/generated/models/channel_agents.ts +1256 -0
  395. package/src/generated/models/channel_directories.ts +1859 -0
  396. package/src/generated/models/channel_mention_mode.ts +1300 -0
  397. package/src/generated/models/channel_models.ts +1288 -0
  398. package/src/generated/models/channel_verbosity.ts +1228 -0
  399. package/src/generated/models/channel_worktrees.ts +1300 -0
  400. package/src/generated/models/forum_sync_configs.ts +1452 -0
  401. package/src/generated/models/global_models.ts +1288 -0
  402. package/src/generated/models/ipc_requests.ts +1485 -0
  403. package/src/generated/models/part_messages.ts +1302 -0
  404. package/src/generated/models/scheduled_tasks.ts +2320 -0
  405. package/src/generated/models/session_agents.ts +1086 -0
  406. package/src/generated/models/session_events.ts +1439 -0
  407. package/src/generated/models/session_models.ts +1114 -0
  408. package/src/generated/models/session_start_sources.ts +1408 -0
  409. package/src/generated/models/thread_sessions.ts +1781 -0
  410. package/src/generated/models/thread_worktrees.ts +1356 -0
  411. package/src/generated/models.ts +30 -0
  412. package/src/heap-monitor.ts +152 -0
  413. package/src/hrana-server.test.ts +434 -0
  414. package/src/hrana-server.ts +299 -0
  415. package/src/html-actions.test.ts +87 -0
  416. package/src/html-actions.ts +174 -0
  417. package/src/html-components.test.ts +38 -0
  418. package/src/html-components.ts +181 -0
  419. package/src/image-optimizer-plugin.ts +194 -0
  420. package/src/image-utils.ts +149 -0
  421. package/src/interaction-handler.ts +610 -0
  422. package/src/ipc-polling.ts +427 -0
  423. package/src/ipc-tools-plugin.ts +236 -0
  424. package/src/ipc-utils.ts +29 -0
  425. package/src/limit-heading-depth.test.ts +116 -0
  426. package/src/limit-heading-depth.ts +26 -0
  427. package/src/logger.ts +215 -0
  428. package/src/markdown.test.ts +315 -0
  429. package/src/markdown.ts +410 -0
  430. package/src/memory-overview-plugin.ts +163 -0
  431. package/src/message-finish-field.e2e.test.ts +195 -0
  432. package/src/message-formatting.test.ts +126 -0
  433. package/src/message-formatting.ts +535 -0
  434. package/src/message-preprocessing.ts +488 -0
  435. package/src/onboarding-tutorial.ts +167 -0
  436. package/src/onboarding-welcome.ts +49 -0
  437. package/src/openai-realtime.ts +358 -0
  438. package/src/opencode-command-detection.test.ts +307 -0
  439. package/src/opencode-command-detection.ts +76 -0
  440. package/src/opencode-command.test.ts +70 -0
  441. package/src/opencode-command.ts +191 -0
  442. package/src/opencode-interrupt-plugin.test.ts +682 -0
  443. package/src/opencode-interrupt-plugin.ts +507 -0
  444. package/src/opencode.ts +1453 -0
  445. package/src/otto/branding.ts +23 -0
  446. package/src/otto/index.ts +22 -0
  447. package/src/otto-digital-twin.e2e.test.ts +199 -0
  448. package/src/otto-opencode-plugin-loading.e2e.test.ts +117 -0
  449. package/src/otto-opencode-plugin.test.ts +108 -0
  450. package/src/otto-opencode-plugin.ts +22 -0
  451. package/src/parse-permission-rules.test.ts +127 -0
  452. package/src/patch-text-parser.ts +107 -0
  453. package/src/plugin-logger.ts +84 -0
  454. package/src/privacy-sanitizer.ts +142 -0
  455. package/src/queue-advanced-abort.e2e.test.ts +382 -0
  456. package/src/queue-advanced-action-buttons.e2e.test.ts +268 -0
  457. package/src/queue-advanced-e2e-setup.ts +877 -0
  458. package/src/queue-advanced-footer.e2e.test.ts +591 -0
  459. package/src/queue-advanced-model-switch.e2e.test.ts +383 -0
  460. package/src/queue-advanced-permissions-typing.e2e.test.ts +246 -0
  461. package/src/queue-advanced-question.e2e.test.ts +316 -0
  462. package/src/queue-advanced-typing-interrupt.e2e.test.ts +146 -0
  463. package/src/queue-advanced-typing.e2e.test.ts +199 -0
  464. package/src/queue-drain-after-interactive-ui.e2e.test.ts +151 -0
  465. package/src/queue-interrupt-drain.e2e.test.ts +166 -0
  466. package/src/queue-question-select-drain.e2e.test.ts +327 -0
  467. package/src/runtime-idle-sweeper.ts +76 -0
  468. package/src/runtime-lifecycle.e2e.test.ts +651 -0
  469. package/src/schema.sql +174 -0
  470. package/src/sentry.ts +26 -0
  471. package/src/session-handler/agent-utils.ts +99 -0
  472. package/src/session-handler/event-stream-fixtures/real-session-action-buttons.jsonl +45 -0
  473. package/src/session-handler/event-stream-fixtures/real-session-footer-suppressed-on-pre-idle-interrupt.jsonl +40 -0
  474. package/src/session-handler/event-stream-fixtures/real-session-permission-external-file.jsonl +23 -0
  475. package/src/session-handler/event-stream-fixtures/real-session-task-normal.jsonl +22 -0
  476. package/src/session-handler/event-stream-fixtures/real-session-task-three-parallel-sleeps.jsonl +277 -0
  477. package/src/session-handler/event-stream-fixtures/real-session-task-user-interruption.jsonl +46 -0
  478. package/src/session-handler/event-stream-fixtures/session-abort-after-idle-race.jsonl +21 -0
  479. package/src/session-handler/event-stream-fixtures/session-concurrent-messages-serialized.jsonl +56 -0
  480. package/src/session-handler/event-stream-fixtures/session-explicit-abort.jsonl +44 -0
  481. package/src/session-handler/event-stream-fixtures/session-normal-completion.jsonl +29 -0
  482. package/src/session-handler/event-stream-fixtures/session-tool-call-noisy-stream.jsonl +29 -0
  483. package/src/session-handler/event-stream-fixtures/session-two-completions-same-session.jsonl +50 -0
  484. package/src/session-handler/event-stream-fixtures/session-user-interruption.jsonl +59 -0
  485. package/src/session-handler/event-stream-fixtures/session-voice-queued-followup.jsonl +52 -0
  486. package/src/session-handler/event-stream-state.test.ts +717 -0
  487. package/src/session-handler/event-stream-state.ts +706 -0
  488. package/src/session-handler/model-utils.ts +217 -0
  489. package/src/session-handler/opencode-session-event-log.ts +130 -0
  490. package/src/session-handler/thread-runtime-state.ts +247 -0
  491. package/src/session-handler/thread-session-runtime.ts +4440 -0
  492. package/src/session-handler.ts +15 -0
  493. package/src/session-search.test.ts +50 -0
  494. package/src/session-search.ts +148 -0
  495. package/src/session-title-rename.test.ts +130 -0
  496. package/src/skill-filter.test.ts +83 -0
  497. package/src/skill-filter.ts +42 -0
  498. package/src/startup-service.ts +200 -0
  499. package/src/startup-time.e2e.test.ts +373 -0
  500. package/src/store.ts +139 -0
  501. package/src/subagent-rate-limit-plugin.ts +218 -0
  502. package/src/system-message.test.ts +710 -0
  503. package/src/system-message.ts +814 -0
  504. package/src/task-runner.ts +725 -0
  505. package/src/task-schedule.test.ts +84 -0
  506. package/src/task-schedule.ts +317 -0
  507. package/src/test-utils.ts +451 -0
  508. package/src/thinking-utils.ts +61 -0
  509. package/src/thread-message-queue.e2e.test.ts +1350 -0
  510. package/src/tools.ts +430 -0
  511. package/src/undici.d.ts +12 -0
  512. package/src/undo-redo.e2e.test.ts +209 -0
  513. package/src/unnest-code-blocks.test.ts +713 -0
  514. package/src/unnest-code-blocks.ts +185 -0
  515. package/src/upgrade.ts +185 -0
  516. package/src/utils.test.ts +155 -0
  517. package/src/utils.ts +265 -0
  518. package/src/voice-attachment.ts +51 -0
  519. package/src/voice-handler.ts +908 -0
  520. package/src/voice-message.e2e.test.ts +1255 -0
  521. package/src/voice.test.ts +281 -0
  522. package/src/voice.ts +638 -0
  523. package/src/wait-session.ts +273 -0
  524. package/src/websockify.ts +101 -0
  525. package/src/worker-types.ts +64 -0
  526. package/src/worktree-lifecycle.e2e.test.ts +396 -0
  527. package/src/worktree-utils.ts +4 -0
  528. package/src/worktrees.test.ts +489 -0
  529. package/src/worktrees.ts +1370 -0
  530. package/src/xml.test.ts +38 -0
  531. package/src/xml.ts +121 -0
  532. package/dist/cli.d.ts +0 -3
  533. package/dist/cli.d.ts.map +0 -1
  534. package/dist/cli.js.map +0 -1
  535. package/dist/config.d.ts +0 -39
  536. package/dist/config.d.ts.map +0 -1
  537. package/dist/config.js.map +0 -1
  538. package/dist/config.test.d.ts +0 -2
  539. package/dist/config.test.d.ts.map +0 -1
  540. package/dist/config.test.js +0 -202
  541. package/dist/config.test.js.map +0 -1
  542. package/dist/detect.d.ts +0 -9
  543. package/dist/detect.d.ts.map +0 -1
  544. package/dist/detect.js +0 -40
  545. package/dist/detect.js.map +0 -1
  546. package/dist/detect.test.d.ts +0 -2
  547. package/dist/detect.test.d.ts.map +0 -1
  548. package/dist/detect.test.js +0 -26
  549. package/dist/detect.test.js.map +0 -1
  550. package/dist/docker.d.ts +0 -7
  551. package/dist/docker.d.ts.map +0 -1
  552. package/dist/docker.js +0 -17
  553. package/dist/docker.js.map +0 -1
  554. package/dist/docker.test.d.ts +0 -2
  555. package/dist/docker.test.d.ts.map +0 -1
  556. package/dist/docker.test.js +0 -12
  557. package/dist/docker.test.js.map +0 -1
  558. package/dist/health.d.ts +0 -31
  559. package/dist/health.d.ts.map +0 -1
  560. package/dist/health.js +0 -117
  561. package/dist/health.js.map +0 -1
  562. package/dist/health.test.d.ts +0 -2
  563. package/dist/health.test.d.ts.map +0 -1
  564. package/dist/health.test.js +0 -52
  565. package/dist/health.test.js.map +0 -1
  566. package/dist/index.d.ts +0 -20
  567. package/dist/index.d.ts.map +0 -1
  568. package/dist/index.js +0 -15
  569. package/dist/index.js.map +0 -1
  570. package/dist/index.test.d.ts +0 -2
  571. package/dist/index.test.d.ts.map +0 -1
  572. package/dist/index.test.js +0 -8
  573. package/dist/index.test.js.map +0 -1
  574. package/dist/installer.d.ts +0 -10
  575. package/dist/installer.d.ts.map +0 -1
  576. package/dist/installer.js +0 -50
  577. package/dist/installer.js.map +0 -1
  578. package/dist/installer.test.d.ts +0 -2
  579. package/dist/installer.test.d.ts.map +0 -1
  580. package/dist/installer.test.js +0 -43
  581. package/dist/installer.test.js.map +0 -1
  582. package/dist/lifecycle.d.ts +0 -10
  583. package/dist/lifecycle.d.ts.map +0 -1
  584. package/dist/lifecycle.js +0 -45
  585. package/dist/lifecycle.js.map +0 -1
  586. package/dist/lifecycle.test.d.ts +0 -2
  587. package/dist/lifecycle.test.d.ts.map +0 -1
  588. package/dist/lifecycle.test.js +0 -20
  589. package/dist/lifecycle.test.js.map +0 -1
  590. package/dist/manifest.d.ts +0 -18
  591. package/dist/manifest.d.ts.map +0 -1
  592. package/dist/manifest.js +0 -30
  593. package/dist/manifest.js.map +0 -1
  594. package/dist/skills-baseline.d.ts +0 -7
  595. package/dist/skills-baseline.d.ts.map +0 -1
  596. package/dist/skills-baseline.js +0 -9
  597. package/dist/skills-baseline.js.map +0 -1
  598. package/dist/skills.d.ts +0 -110
  599. package/dist/skills.d.ts.map +0 -1
  600. package/dist/skills.js +0 -429
  601. package/dist/skills.js.map +0 -1
  602. package/dist/skills.test.d.ts +0 -2
  603. package/dist/skills.test.d.ts.map +0 -1
  604. package/dist/skills.test.js +0 -416
  605. package/dist/skills.test.js.map +0 -1
  606. package/dist/sync.d.ts +0 -10
  607. package/dist/sync.d.ts.map +0 -1
  608. package/dist/sync.js +0 -39
  609. package/dist/sync.js.map +0 -1
  610. package/dist/tenant.d.ts +0 -13
  611. package/dist/tenant.d.ts.map +0 -1
  612. package/dist/tenant.js +0 -105
  613. package/dist/tenant.js.map +0 -1
  614. package/dist/tenant.test.d.ts +0 -2
  615. package/dist/tenant.test.d.ts.map +0 -1
  616. package/dist/tenant.test.js +0 -37
  617. package/dist/tenant.test.js.map +0 -1
  618. package/src/config.test.ts +0 -237
  619. package/src/detect.test.ts +0 -29
  620. package/src/detect.ts +0 -52
  621. package/src/docker.test.ts +0 -12
  622. package/src/docker.ts +0 -23
  623. package/src/health.test.ts +0 -61
  624. package/src/health.ts +0 -158
  625. package/src/index.test.ts +0 -8
  626. package/src/index.ts +0 -62
  627. package/src/installer.test.ts +0 -52
  628. package/src/installer.ts +0 -62
  629. package/src/lifecycle.test.ts +0 -23
  630. package/src/lifecycle.ts +0 -49
  631. package/src/manifest.ts +0 -42
  632. package/src/skills-baseline.ts +0 -14
  633. package/src/skills.test.ts +0 -503
  634. package/src/skills.ts +0 -512
  635. package/src/sync.ts +0 -53
  636. package/src/tenant.test.ts +0 -49
  637. package/src/tenant.ts +0 -120
@@ -0,0 +1,106 @@
1
+ // /share command - Share the current session as a public URL.
2
+
3
+ import {
4
+ ChannelType,
5
+ MessageFlags,
6
+ type TextChannel,
7
+ type ThreadChannel,
8
+ } from 'discord.js'
9
+ import type { CommandContext } from './types.js'
10
+ import { getThreadSession } from '../database.js'
11
+ import { initializeOpencodeForDirectory } from '../opencode.js'
12
+ import {
13
+ resolveWorkingDirectory,
14
+ SILENT_MESSAGE_FLAGS,
15
+ } from '../discord-utils.js'
16
+ import { createLogger, LogPrefix } from '../logger.js'
17
+
18
+ const logger = createLogger(LogPrefix.SHARE)
19
+
20
+ export async function handleShareCommand({
21
+ command,
22
+ }: CommandContext): Promise<void> {
23
+ const channel = command.channel
24
+
25
+ if (!channel) {
26
+ await command.reply({
27
+ content: 'This command can only be used in a channel',
28
+ flags: MessageFlags.Ephemeral | SILENT_MESSAGE_FLAGS,
29
+ })
30
+ return
31
+ }
32
+
33
+ const isThread = [
34
+ ChannelType.PublicThread,
35
+ ChannelType.PrivateThread,
36
+ ChannelType.AnnouncementThread,
37
+ ].includes(channel.type)
38
+
39
+ if (!isThread) {
40
+ await command.reply({
41
+ content:
42
+ 'This command can only be used in a thread with an active session',
43
+ flags: MessageFlags.Ephemeral | SILENT_MESSAGE_FLAGS,
44
+ })
45
+ return
46
+ }
47
+
48
+ const resolved = await resolveWorkingDirectory({
49
+ channel: channel as TextChannel | ThreadChannel,
50
+ })
51
+
52
+ if (!resolved) {
53
+ await command.reply({
54
+ content: 'Could not determine project directory for this channel',
55
+ flags: MessageFlags.Ephemeral | SILENT_MESSAGE_FLAGS,
56
+ })
57
+ return
58
+ }
59
+
60
+ const { projectDirectory } = resolved
61
+
62
+ const sessionId = await getThreadSession(channel.id)
63
+
64
+ if (!sessionId) {
65
+ await command.reply({
66
+ content: 'No active session in this thread',
67
+ flags: MessageFlags.Ephemeral | SILENT_MESSAGE_FLAGS,
68
+ })
69
+ return
70
+ }
71
+
72
+ const getClient = await initializeOpencodeForDirectory(projectDirectory)
73
+ if (getClient instanceof Error) {
74
+ await command.reply({
75
+ content: `Failed to share session: ${getClient.message}`,
76
+ flags: MessageFlags.Ephemeral | SILENT_MESSAGE_FLAGS,
77
+ })
78
+ return
79
+ }
80
+
81
+ try {
82
+ const response = await getClient().session.share({
83
+ sessionID: sessionId,
84
+ })
85
+
86
+ if (!response.data?.share?.url) {
87
+ await command.reply({
88
+ content: 'Failed to generate share URL',
89
+ flags: MessageFlags.Ephemeral | SILENT_MESSAGE_FLAGS,
90
+ })
91
+ return
92
+ }
93
+
94
+ await command.reply({
95
+ content: `🔗 **Session shared:** ${response.data.share.url}`,
96
+ flags: SILENT_MESSAGE_FLAGS,
97
+ })
98
+ logger.log(`Session ${sessionId} shared: ${response.data.share.url}`)
99
+ } catch (error) {
100
+ logger.error('[SHARE] Error:', error)
101
+ await command.reply({
102
+ content: `Failed to share session: ${error instanceof Error ? error.message : 'Unknown error'}`,
103
+ flags: MessageFlags.Ephemeral | SILENT_MESSAGE_FLAGS,
104
+ })
105
+ }
106
+ }
@@ -0,0 +1,293 @@
1
+ // /tasks command — list all scheduled tasks sorted by next run time.
2
+ // Renders a markdown table that the CV2 pipeline auto-formats for Discord,
3
+ // including HTML-backed action buttons for cancellable tasks.
4
+
5
+ import {
6
+ ButtonInteraction,
7
+ ChatInputCommandInteraction,
8
+ ComponentType,
9
+ MessageFlags,
10
+ type APIMessageTopLevelComponent,
11
+ type APITextDisplayComponent,
12
+ type InteractionEditReplyOptions,
13
+ } from 'discord.js'
14
+ import {
15
+ cancelScheduledTask,
16
+ listScheduledTasks,
17
+ type ScheduledTask,
18
+ type ScheduledTaskStatus,
19
+ } from '../database.js'
20
+ import { splitTablesFromMarkdown } from '../format-tables.js'
21
+ import {
22
+ buildHtmlActionCustomId,
23
+ cancelHtmlActionsForOwner,
24
+ registerHtmlAction,
25
+ } from '../html-actions.js'
26
+ import { formatTimeAgo } from './worktrees.js'
27
+
28
+ function formatTimeUntil(date: Date): string {
29
+ const diffMs = date.getTime() - Date.now()
30
+ if (diffMs <= 0) {
31
+ return 'due now'
32
+ }
33
+ const totalSeconds = Math.floor(diffMs / 1000)
34
+ if (totalSeconds < 60) {
35
+ return `in ${totalSeconds}s`
36
+ }
37
+ const totalMinutes = Math.floor(totalSeconds / 60)
38
+ if (totalMinutes < 60) {
39
+ return `in ${totalMinutes}m`
40
+ }
41
+ const hours = Math.floor(totalMinutes / 60)
42
+ const minutes = totalMinutes % 60
43
+ if (hours < 24) {
44
+ return minutes > 0 ? `in ${hours}h ${minutes}m` : `in ${hours}h`
45
+ }
46
+ const days = Math.floor(hours / 24)
47
+ const remainingHours = hours % 24
48
+ return remainingHours > 0 ? `in ${days}d ${remainingHours}h` : `in ${days}d`
49
+ }
50
+
51
+ function scheduleLabel(task: ScheduledTask): string {
52
+ if (task.schedule_kind === 'cron') {
53
+ return task.cron_expr || 'cron'
54
+ }
55
+ return 'one-time'
56
+ }
57
+
58
+ function canCancelTask(task: ScheduledTask): boolean {
59
+ return task.status === 'planned' || task.status === 'running'
60
+ }
61
+
62
+ // Escape pipe chars and collapse whitespace so free-text fields don't break
63
+ // GFM table column alignment.
64
+ function sanitizeTableCell(value: string): string {
65
+ return value.replaceAll('|', '\\|').replace(/\s+/g, ' ').trim()
66
+ }
67
+
68
+ function buildCancelButtonHtml({ buttonId }: { buttonId: string }): string {
69
+ return `<button id="${buttonId}" variant="secondary">Delete</button>`
70
+ }
71
+
72
+ function buildActionCell(task: ScheduledTask): string {
73
+ if (!canCancelTask(task)) {
74
+ return '-'
75
+ }
76
+ return buildCancelButtonHtml({ buttonId: `cancel-task-${task.id}` })
77
+ }
78
+
79
+ // Cap rows to avoid exceeding Discord's 40-component CV2 limit.
80
+ // Each cancellable row renders as text + action row + button (~4 components),
81
+ // so 10 rows is a safe ceiling.
82
+ const MAX_TASK_ROWS = 10
83
+
84
+ function buildTaskTable({
85
+ tasks,
86
+ }: {
87
+ tasks: ScheduledTask[]
88
+ }): string {
89
+ const header = '| ID | Status | Prompt | Schedule | Next Run | Action |'
90
+ const separator = '|---|---|---|---|---|---|'
91
+ const rows = tasks.map((task) => {
92
+ const id = String(task.id)
93
+ const status = task.status
94
+ const prompt = sanitizeTableCell(
95
+ task.prompt_preview.length > 240
96
+ ? task.prompt_preview.slice(0, 237) + '...'
97
+ : task.prompt_preview,
98
+ )
99
+ const schedule = sanitizeTableCell(scheduleLabel(task))
100
+ const nextRun = (() => {
101
+ if (
102
+ task.status === 'completed' ||
103
+ task.status === 'cancelled' ||
104
+ task.status === 'failed'
105
+ ) {
106
+ return task.last_run_at ? formatTimeAgo(task.last_run_at) : '-'
107
+ }
108
+ return formatTimeUntil(task.next_run_at)
109
+ })()
110
+ const action = buildActionCell(task)
111
+ return `| ${id} | ${status} | ${prompt} | ${schedule} | ${nextRun} | ${action} |`
112
+ })
113
+ return [header, separator, ...rows].join('\n')
114
+ }
115
+
116
+ function getTasksActionOwnerKey({
117
+ userId,
118
+ channelId,
119
+ }: {
120
+ userId: string
121
+ channelId: string
122
+ }): string {
123
+ return `tasks:${userId}:${channelId}`
124
+ }
125
+
126
+ type TasksReplyTarget = {
127
+ guildId: string
128
+ userId: string
129
+ channelId: string
130
+ showAll: boolean
131
+ notice?: string
132
+ editReply: (
133
+ options: string | InteractionEditReplyOptions,
134
+ ) => Promise<unknown>
135
+ }
136
+
137
+ async function renderTasksReply({
138
+ guildId,
139
+ userId,
140
+ channelId,
141
+ showAll,
142
+ notice,
143
+ editReply,
144
+ }: TasksReplyTarget): Promise<void> {
145
+ const ownerKey = getTasksActionOwnerKey({ userId, channelId })
146
+ cancelHtmlActionsForOwner(ownerKey)
147
+
148
+ const statuses: ScheduledTaskStatus[] | undefined = showAll
149
+ ? undefined
150
+ : ['planned', 'running']
151
+ const allTasks = await listScheduledTasks({ statuses })
152
+ if (allTasks.length === 0) {
153
+ const message = notice
154
+ ? `${notice}\n\nNo scheduled tasks found.`
155
+ : 'No scheduled tasks found.'
156
+ const textDisplay: APITextDisplayComponent = {
157
+ type: ComponentType.TextDisplay,
158
+ content: message,
159
+ }
160
+ await editReply({
161
+ components: [textDisplay],
162
+ flags: MessageFlags.IsComponentsV2,
163
+ })
164
+ return
165
+ }
166
+
167
+ const tasks = allTasks.slice(0, MAX_TASK_ROWS)
168
+ const truncatedNotice =
169
+ allTasks.length > MAX_TASK_ROWS
170
+ ? `Showing ${MAX_TASK_ROWS}/${allTasks.length} tasks. Use \`otto task list\` for full list.`
171
+ : undefined
172
+ const combinedNotice = [notice, truncatedNotice].filter(Boolean).join('\n')
173
+
174
+ const cancellableTasksByButtonId = new Map<string, ScheduledTask>()
175
+ tasks.forEach((task) => {
176
+ if (!canCancelTask(task)) {
177
+ return
178
+ }
179
+ cancellableTasksByButtonId.set(`cancel-task-${task.id}`, task)
180
+ })
181
+
182
+ const tableMarkdown = buildTaskTable({ tasks })
183
+ const markdown = combinedNotice
184
+ ? `${combinedNotice}\n\n${tableMarkdown}`
185
+ : tableMarkdown
186
+ const segments = splitTablesFromMarkdown(markdown, {
187
+ resolveButtonCustomId: ({ button }) => {
188
+ const task = cancellableTasksByButtonId.get(button.id)
189
+ if (!task) {
190
+ return new Error(`No task registered for button ${button.id}`)
191
+ }
192
+
193
+ const actionId = registerHtmlAction({
194
+ ownerKey,
195
+ threadId: String(task.id),
196
+ run: async ({ interaction }) => {
197
+ await handleCancelTaskAction({
198
+ interaction,
199
+ taskId: task.id,
200
+ showAll,
201
+ })
202
+ },
203
+ })
204
+ return buildHtmlActionCustomId(actionId)
205
+ },
206
+ })
207
+
208
+ const components: APIMessageTopLevelComponent[] = segments.flatMap(
209
+ (segment) => {
210
+ if (segment.type === 'components') {
211
+ return segment.components
212
+ }
213
+ const textDisplay: APITextDisplayComponent = {
214
+ type: ComponentType.TextDisplay,
215
+ content: segment.text,
216
+ }
217
+ return [textDisplay]
218
+ },
219
+ )
220
+
221
+ await editReply({
222
+ components,
223
+ flags: MessageFlags.IsComponentsV2,
224
+ })
225
+ }
226
+
227
+ async function handleCancelTaskAction({
228
+ interaction,
229
+ taskId,
230
+ showAll,
231
+ }: {
232
+ interaction: ButtonInteraction
233
+ taskId: number
234
+ showAll: boolean
235
+ }): Promise<void> {
236
+ const guildId = interaction.guildId
237
+ if (!guildId) {
238
+ await interaction.editReply({
239
+ components: [
240
+ {
241
+ type: ComponentType.TextDisplay,
242
+ content: 'This action can only be used in a server.',
243
+ },
244
+ ],
245
+ flags: MessageFlags.IsComponentsV2,
246
+ })
247
+ return
248
+ }
249
+
250
+ const cancelled = await cancelScheduledTask(taskId)
251
+ const notice = cancelled
252
+ ? `Cancelled task #${taskId}.`
253
+ : `Task #${taskId} not found or already finalized.`
254
+
255
+ await renderTasksReply({
256
+ guildId,
257
+ userId: interaction.user.id,
258
+ channelId: interaction.channelId,
259
+ showAll,
260
+ notice,
261
+ editReply: (options) => {
262
+ return interaction.editReply(options)
263
+ },
264
+ })
265
+ }
266
+
267
+ export async function handleTasksCommand({
268
+ command,
269
+ }: {
270
+ command: ChatInputCommandInteraction
271
+ appId: string
272
+ }): Promise<void> {
273
+ const guildId = command.guildId
274
+ if (!guildId) {
275
+ await command.reply({
276
+ content: 'This command can only be used in a server.',
277
+ flags: MessageFlags.Ephemeral,
278
+ })
279
+ return
280
+ }
281
+
282
+ const showAll = command.options.getBoolean('all') ?? false
283
+ await command.deferReply({ flags: MessageFlags.Ephemeral })
284
+ await renderTasksReply({
285
+ guildId,
286
+ userId: command.user.id,
287
+ channelId: command.channelId,
288
+ showAll,
289
+ editReply: (options) => {
290
+ return command.editReply(options)
291
+ },
292
+ })
293
+ }
@@ -0,0 +1,80 @@
1
+ import {
2
+ ChatInputCommandInteraction,
3
+ MessageFlags,
4
+ } from 'discord.js'
5
+ import type { APIApplicationCommandOptionChoice } from 'discord.js'
6
+ import {
7
+ getThreadDeletionSyncMode,
8
+ resetThreadDeletionSyncMode,
9
+ setThreadDeletionSyncMode,
10
+ type ThreadDeletionSyncMode,
11
+ } from '../database.js'
12
+ import { createLogger, LogPrefix } from '../logger.js'
13
+
14
+ const threadDeletionSyncLogger = createLogger(LogPrefix.CLI)
15
+
16
+ export const THREAD_DELETION_SYNC_CHOICES: Array<
17
+ APIApplicationCommandOptionChoice<string>
18
+ > = [
19
+ { name: 'soft', value: 'soft' },
20
+ { name: 'hard', value: 'hard' },
21
+ { name: 'reset', value: 'reset' },
22
+ ]
23
+
24
+ function getModeLabel(mode: ThreadDeletionSyncMode): string {
25
+ if (mode === 'hard') {
26
+ return 'hard'
27
+ }
28
+ return 'soft'
29
+ }
30
+
31
+ export async function handleThreadDeletionSyncCommand({
32
+ command,
33
+ appId,
34
+ }: {
35
+ command: ChatInputCommandInteraction
36
+ appId: string
37
+ }): Promise<void> {
38
+ const selectedMode = command.options.getString('mode')
39
+ if (!selectedMode) {
40
+ const currentMode = await getThreadDeletionSyncMode({ appId })
41
+ await command.reply({
42
+ content: `Thread deletion sync mode is **${getModeLabel(currentMode)}**.`,
43
+ flags: MessageFlags.Ephemeral,
44
+ })
45
+ return
46
+ }
47
+
48
+ if (selectedMode === 'reset') {
49
+ await resetThreadDeletionSyncMode({ appId })
50
+ threadDeletionSyncLogger.log(
51
+ `[THREAD_DELETION_SYNC] Reset mode to default for app ${appId}`,
52
+ )
53
+ await command.reply({
54
+ content:
55
+ 'Thread deletion sync mode reset to **soft** (default).\nSoft keeps the OpenCode session and only archives/aborts it.',
56
+ flags: MessageFlags.Ephemeral,
57
+ })
58
+ return
59
+ }
60
+
61
+ if (selectedMode !== 'soft' && selectedMode !== 'hard') {
62
+ await command.reply({
63
+ content: `Invalid mode: ${selectedMode}`,
64
+ flags: MessageFlags.Ephemeral,
65
+ })
66
+ return
67
+ }
68
+
69
+ await setThreadDeletionSyncMode({ appId, mode: selectedMode })
70
+ threadDeletionSyncLogger.log(
71
+ `[THREAD_DELETION_SYNC] Set mode ${selectedMode} for app ${appId}`,
72
+ )
73
+ await command.reply({
74
+ content:
75
+ selectedMode === 'hard'
76
+ ? 'Thread deletion sync mode set to **hard**.\nDeleting a Discord thread will delete the mapped OpenCode session.'
77
+ : 'Thread deletion sync mode set to **soft**.\nDeleting a Discord thread will archive/abort the mapped OpenCode session.',
78
+ flags: MessageFlags.Ephemeral,
79
+ })
80
+ }
@@ -0,0 +1,25 @@
1
+ // Shared types for command handlers.
2
+
3
+ import type {
4
+ AutocompleteInteraction,
5
+ ChatInputCommandInteraction,
6
+ StringSelectMenuInteraction,
7
+ } from 'discord.js'
8
+
9
+ export type CommandContext = {
10
+ command: ChatInputCommandInteraction
11
+ appId: string
12
+ }
13
+
14
+ export type CommandHandler = (ctx: CommandContext) => Promise<void>
15
+
16
+ export type AutocompleteContext = {
17
+ interaction: AutocompleteInteraction
18
+ appId: string
19
+ }
20
+
21
+ export type AutocompleteHandler = (ctx: AutocompleteContext) => Promise<void>
22
+
23
+ export type SelectMenuHandler = (
24
+ interaction: StringSelectMenuInteraction,
25
+ ) => Promise<void>