@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,718 @@
1
+ // Markdown table formatter for Discord.
2
+ // Converts GFM tables to Discord Components V2 (ContainerBuilder with TextDisplay
3
+ // key-value pairs and Separators between row groups). Large tables are split
4
+ // across multiple Container components to stay within the 40-component limit.
5
+
6
+ import { Lexer, type Token, type Tokens } from 'marked'
7
+ import {
8
+ ButtonStyle,
9
+ ComponentType,
10
+ SeparatorSpacingSize,
11
+ type APIActionRowComponent,
12
+ type APIButtonComponent,
13
+ type APIComponentInContainer,
14
+ type APIContainerComponent,
15
+ type APITextDisplayComponent,
16
+ type APISeparatorComponent,
17
+ type APIMessageTopLevelComponent,
18
+ } from 'discord.js'
19
+ import {
20
+ parseInlineHtmlRenderables,
21
+ type HtmlButtonRenderable,
22
+ type HtmlRenderable,
23
+ } from './html-components.js'
24
+
25
+ export type ContentSegment =
26
+ | { type: 'text'; text: string }
27
+ | { type: 'components'; components: APIMessageTopLevelComponent[] }
28
+
29
+ type TableRenderOptions = {
30
+ resolveButtonCustomId?: ({
31
+ button,
32
+ }: {
33
+ button: HtmlButtonRenderable
34
+ }) => string | Error
35
+ }
36
+
37
+ type RenderedTableCell =
38
+ | { type: 'text'; text: string }
39
+ | {
40
+ type: 'button'
41
+ label: string
42
+ customId: string
43
+ variant: HtmlButtonRenderable['variant']
44
+ disabled: boolean
45
+ }
46
+
47
+ type RenderedTableRow = {
48
+ components: Array<
49
+ APITextDisplayComponent | APIActionRowComponent<APIButtonComponent>
50
+ >
51
+ componentCost: number
52
+ }
53
+
54
+ type CalloutDescriptor = {
55
+ accentColor?: number
56
+ }
57
+
58
+ // Max 40 components per message (nested components count toward the limit).
59
+ // Row cost is dynamic now because a table row can render as a plain TextDisplay
60
+ // or as a TextDisplay plus an Action Row holding one or more buttons.
61
+ const MAX_COMPONENTS = 40
62
+
63
+ /**
64
+ * Split markdown into text and table component segments.
65
+ * Tables are rendered as CV2 Container components with bold key-value TextDisplay
66
+ * pairs. Large tables are split across multiple component segments.
67
+ */
68
+ export function splitTablesFromMarkdown(
69
+ markdown: string,
70
+ options: TableRenderOptions = {},
71
+ ): ContentSegment[] {
72
+ const blocks = splitMarkdownByCallouts({ markdown })
73
+ return blocks.flatMap((block) => {
74
+ if (block.type === 'callout') {
75
+ const innerSegments = splitTablesFromMarkdown(block.content, options)
76
+ return buildCalloutSegments({
77
+ segments: innerSegments,
78
+ callout: block.callout,
79
+ })
80
+ }
81
+
82
+ return splitTableSegmentsFromText({
83
+ markdown: block.text,
84
+ options,
85
+ })
86
+ })
87
+ }
88
+
89
+ type MarkdownBlock =
90
+ | { type: 'text'; text: string }
91
+ | { type: 'callout'; content: string; callout: CalloutDescriptor }
92
+
93
+ function splitMarkdownByCallouts({
94
+ markdown,
95
+ }: {
96
+ markdown: string
97
+ }): MarkdownBlock[] {
98
+ const lines = markdown.match(/.*(?:\n|$)/g)?.filter((line) => {
99
+ return line.length > 0
100
+ }) ?? [markdown]
101
+ const blocks: MarkdownBlock[] = []
102
+ let textBuffer = ''
103
+
104
+ for (let index = 0; index < lines.length; index++) {
105
+ const line = lines[index]!
106
+ const callout = parseCalloutOpenLine({ line })
107
+ if (!callout) {
108
+ textBuffer += line
109
+ continue
110
+ }
111
+
112
+ if (textBuffer.length > 0) {
113
+ blocks.push({ type: 'text', text: textBuffer })
114
+ textBuffer = ''
115
+ }
116
+
117
+ const body = collectCalloutBodyFromLines({
118
+ lines,
119
+ startIndex: index,
120
+ })
121
+ if (body instanceof Error) {
122
+ textBuffer += line
123
+ continue
124
+ }
125
+
126
+ blocks.push({
127
+ type: 'callout',
128
+ content: body.content,
129
+ callout,
130
+ })
131
+ index = body.endIndex
132
+ }
133
+
134
+ if (textBuffer.length > 0) {
135
+ blocks.push({ type: 'text', text: textBuffer })
136
+ }
137
+
138
+ return blocks
139
+ }
140
+
141
+ function splitTableSegmentsFromText({
142
+ markdown,
143
+ options,
144
+ }: {
145
+ markdown: string
146
+ options: TableRenderOptions
147
+ }): ContentSegment[] {
148
+ const lexer = new Lexer()
149
+ return splitTokensIntoSegments({
150
+ tokens: lexer.lex(markdown),
151
+ options,
152
+ })
153
+ }
154
+
155
+ function splitTokensIntoSegments({
156
+ tokens,
157
+ options,
158
+ }: {
159
+ tokens: Token[]
160
+ options: TableRenderOptions
161
+ }): ContentSegment[] {
162
+ const segments: ContentSegment[] = []
163
+ let textBuffer = ''
164
+ const isTableToken = (token: Token): token is Tokens.Table => {
165
+ return (
166
+ token.type === 'table' &&
167
+ Object.hasOwn(token, 'header') &&
168
+ Object.hasOwn(token, 'rows')
169
+ )
170
+ }
171
+
172
+ for (const token of tokens) {
173
+ if (isTableToken(token)) {
174
+ if (textBuffer.trim()) {
175
+ segments.push({ type: 'text', text: textBuffer })
176
+ textBuffer = ''
177
+ }
178
+ const componentSegments = buildTableComponents(token, options)
179
+ segments.push(...componentSegments)
180
+ } else {
181
+ textBuffer += token.raw
182
+ }
183
+ }
184
+
185
+ if (textBuffer.trim()) {
186
+ segments.push({ type: 'text', text: textBuffer })
187
+ }
188
+
189
+ return segments
190
+ }
191
+
192
+ function buildCalloutSegments({
193
+ segments,
194
+ callout,
195
+ }: {
196
+ segments: ContentSegment[]
197
+ callout: CalloutDescriptor
198
+ }): ContentSegment[] {
199
+ const children = flattenCalloutChildren({ segments })
200
+ if (children.length === 0) {
201
+ return []
202
+ }
203
+
204
+ const chunks = chunkCalloutChildrenByComponentLimit({ children })
205
+ return chunks.map((chunk) => {
206
+ const container: APIContainerComponent = {
207
+ type: ComponentType.Container,
208
+ ...(callout.accentColor !== undefined
209
+ ? { accent_color: callout.accentColor }
210
+ : {}),
211
+ components: chunk,
212
+ }
213
+ const components: APIMessageTopLevelComponent[] = [container]
214
+ return {
215
+ type: 'components' as const,
216
+ components,
217
+ }
218
+ })
219
+ }
220
+
221
+ function flattenCalloutChildren({
222
+ segments,
223
+ }: {
224
+ segments: ContentSegment[]
225
+ }): APIComponentInContainer[] {
226
+ return segments.flatMap((segment) => {
227
+ if (segment.type === 'text') {
228
+ if (!segment.text.trim()) {
229
+ return []
230
+ }
231
+ return [
232
+ {
233
+ type: ComponentType.TextDisplay,
234
+ content: segment.text.trim(),
235
+ } satisfies APITextDisplayComponent,
236
+ ]
237
+ }
238
+
239
+ return segment.components.flatMap((component) => {
240
+ if (component.type !== ComponentType.Container) {
241
+ return []
242
+ }
243
+ return component.components
244
+ })
245
+ })
246
+ }
247
+
248
+ function chunkCalloutChildrenByComponentLimit({
249
+ children,
250
+ }: {
251
+ children: APIComponentInContainer[]
252
+ }): APIComponentInContainer[][] {
253
+ const chunks: APIComponentInContainer[][] = []
254
+ let currentChunk: APIComponentInContainer[] = []
255
+
256
+ for (const child of children) {
257
+ if (currentChunk.length > 0 && currentChunk.length + 2 > MAX_COMPONENTS) {
258
+ chunks.push(currentChunk)
259
+ currentChunk = []
260
+ }
261
+ currentChunk.push(child)
262
+ }
263
+
264
+ if (currentChunk.length > 0) {
265
+ chunks.push(currentChunk)
266
+ }
267
+
268
+ return chunks
269
+ }
270
+
271
+ function collectCalloutBodyFromLines({
272
+ lines,
273
+ startIndex,
274
+ }: {
275
+ lines: string[]
276
+ startIndex: number
277
+ }): { content: string; endIndex: number } | Error {
278
+ let depth = 0
279
+ const contentLines: string[] = []
280
+
281
+ for (let index = startIndex; index < lines.length; index++) {
282
+ const line = lines[index]!
283
+ const nestedCallout = parseCalloutOpenLine({ line })
284
+ if (nestedCallout) {
285
+ if (depth > 0) {
286
+ contentLines.push(line)
287
+ }
288
+ depth += 1
289
+ continue
290
+ }
291
+
292
+ if (/^<\/callout>$/i.test(line.trim())) {
293
+ depth -= 1
294
+ if (depth === 0) {
295
+ return {
296
+ content: contentLines.join(''),
297
+ endIndex: index,
298
+ }
299
+ }
300
+ contentLines.push(line)
301
+ continue
302
+ }
303
+
304
+ if (depth > 0) {
305
+ contentLines.push(line)
306
+ }
307
+ }
308
+
309
+ return new Error('Unclosed <callout> block')
310
+ }
311
+
312
+ function parseCalloutOpenLine({
313
+ line,
314
+ }: {
315
+ line: string
316
+ }): CalloutDescriptor | null {
317
+ const match = line.trim().match(/^<callout(?:\s+[^>]*)?>$/i)
318
+ if (!match) {
319
+ return null
320
+ }
321
+
322
+ const accentValue = line.match(/\baccent=(['"])(.*?)\1/i)?.[2]?.trim()
323
+ const accentColor = accentValue
324
+ ? parseAccentColor({ value: accentValue })
325
+ : undefined
326
+
327
+ return {
328
+ accentColor: accentColor instanceof Error ? undefined : accentColor,
329
+ }
330
+ }
331
+
332
+ function parseAccentColor({
333
+ value,
334
+ }: {
335
+ value: string
336
+ }): number | Error {
337
+ const hex = value.trim().toLowerCase()
338
+ if (/^#[0-9a-f]{6}$/.test(hex)) {
339
+ return Number.parseInt(hex.slice(1), 16)
340
+ }
341
+ if (/^#[0-9a-f]{3}$/.test(hex)) {
342
+ const expanded = hex
343
+ .slice(1)
344
+ .split('')
345
+ .map((char) => {
346
+ return `${char}${char}`
347
+ })
348
+ .join('')
349
+ return Number.parseInt(expanded, 16)
350
+ }
351
+ if (/^\d+$/.test(hex)) {
352
+ return Number.parseInt(hex, 10)
353
+ }
354
+ return new Error(`Unsupported callout accent color: ${value}`)
355
+ }
356
+
357
+ /**
358
+ * Build CV2 components for a table. Plain rows render as one TextDisplay with
359
+ * bold key-value lines. Rows with resolved button cells render as a TextDisplay
360
+ * plus an Action Row so wide tables do not violate Section's 1-3 text child
361
+ * limit. Large tables are split into multiple Containers using a dynamic
362
+ * component-budget check.
363
+ */
364
+ export function buildTableComponents(
365
+ table: Tokens.Table,
366
+ options: TableRenderOptions = {},
367
+ ): ContentSegment[] {
368
+ const headers = table.header.map((cell) => {
369
+ return extractCellText(cell.tokens)
370
+ })
371
+ const rows = table.rows.map((row) => {
372
+ return buildRenderedRow({
373
+ headers,
374
+ row,
375
+ options,
376
+ })
377
+ })
378
+
379
+ const chunks = chunkRowsByComponentLimit({ rows })
380
+
381
+ return chunks.map((chunkRows) => {
382
+ const children: Array<
383
+ | APITextDisplayComponent
384
+ | APIActionRowComponent<APIButtonComponent>
385
+ | APISeparatorComponent
386
+ > = []
387
+
388
+ for (let i = 0; i < chunkRows.length; i++) {
389
+ if (i > 0) {
390
+ children.push({
391
+ type: ComponentType.Separator,
392
+ divider: true,
393
+ spacing: SeparatorSpacingSize.Small,
394
+ })
395
+ }
396
+ children.push(...chunkRows[i]!.components)
397
+ }
398
+
399
+ const container: APIContainerComponent = {
400
+ type: ComponentType.Container,
401
+ components: children,
402
+ }
403
+ const components: APIMessageTopLevelComponent[] = [container]
404
+
405
+ return {
406
+ type: 'components' as const,
407
+ components,
408
+ }
409
+ })
410
+ }
411
+
412
+ function buildRenderedRow({
413
+ headers,
414
+ row,
415
+ options,
416
+ }: {
417
+ headers: string[]
418
+ row: Tokens.TableCell[]
419
+ options: TableRenderOptions
420
+ }): RenderedTableRow {
421
+ const renderedCells = row.map((cell) => {
422
+ return renderTableCell({ cell, options })
423
+ })
424
+ const buttonCellCount = renderedCells.filter((cell) => {
425
+ return cell.type === 'button'
426
+ }).length
427
+
428
+ if (buttonCellCount > 0) {
429
+ return buildButtonRow({
430
+ headers,
431
+ cells: renderedCells,
432
+ })
433
+ }
434
+
435
+ return buildTextRow({
436
+ headers,
437
+ cells: renderedCells,
438
+ })
439
+ }
440
+
441
+ function buildTextRow({
442
+ headers,
443
+ cells,
444
+ }: {
445
+ headers: string[]
446
+ cells: RenderedTableCell[]
447
+ }): RenderedTableRow {
448
+ const lines = headers.map((key, index) => {
449
+ const cell = cells[index]
450
+ const value = cell ? getRenderedCellText({ cell }) : ''
451
+ return `**${key}** ${value}`
452
+ })
453
+
454
+ return {
455
+ components: [
456
+ {
457
+ type: ComponentType.TextDisplay,
458
+ content: lines.join('\n'),
459
+ },
460
+ ],
461
+ componentCost: 1,
462
+ }
463
+ }
464
+
465
+ function buildButtonRow({
466
+ headers,
467
+ cells,
468
+ }: {
469
+ headers: string[]
470
+ cells: RenderedTableCell[]
471
+ }): RenderedTableRow {
472
+ const buttonCells = cells.filter((cell) => {
473
+ return cell.type === 'button'
474
+ })
475
+ if (buttonCells.length === 0 || buttonCells.length > 5) {
476
+ return buildTextRow({ headers, cells })
477
+ }
478
+
479
+ const lines = headers.flatMap((header, index) => {
480
+ const cell = cells[index]
481
+ if (!cell || cell.type === 'button') {
482
+ return []
483
+ }
484
+
485
+ return [`**${header}** ${cell.text}`]
486
+ })
487
+ if (lines.length === 0) {
488
+ return buildTextRow({ headers, cells })
489
+ }
490
+
491
+ const buttons: APIButtonComponent[] = buttonCells.map((buttonCell) => {
492
+ return {
493
+ type: ComponentType.Button,
494
+ custom_id: buttonCell.customId,
495
+ label: buttonCell.label,
496
+ style: toButtonStyle({ variant: buttonCell.variant }),
497
+ disabled: buttonCell.disabled,
498
+ }
499
+ })
500
+
501
+ const actionRow: APIActionRowComponent<APIButtonComponent> = {
502
+ type: ComponentType.ActionRow,
503
+ components: buttons,
504
+ }
505
+
506
+ return {
507
+ components: [
508
+ {
509
+ type: ComponentType.TextDisplay,
510
+ content: lines.join('\n'),
511
+ },
512
+ actionRow,
513
+ ],
514
+ componentCost: 2 + buttons.length,
515
+ }
516
+ }
517
+
518
+ function chunkRowsByComponentLimit({
519
+ rows,
520
+ }: {
521
+ rows: RenderedTableRow[]
522
+ }): RenderedTableRow[][] {
523
+ const chunks: RenderedTableRow[][] = []
524
+ let currentChunk: RenderedTableRow[] = []
525
+ let currentCost = 1
526
+
527
+ for (const row of rows) {
528
+ const separatorCost = currentChunk.length > 0 ? 1 : 0
529
+ const nextCost = currentCost + separatorCost + row.componentCost
530
+
531
+ if (currentChunk.length > 0 && nextCost > MAX_COMPONENTS) {
532
+ chunks.push(currentChunk)
533
+ currentChunk = [row]
534
+ currentCost = 1 + row.componentCost
535
+ continue
536
+ }
537
+
538
+ currentChunk.push(row)
539
+ currentCost = nextCost
540
+ }
541
+
542
+ if (currentChunk.length > 0) {
543
+ chunks.push(currentChunk)
544
+ }
545
+
546
+ return chunks
547
+ }
548
+
549
+ function renderTableCell({
550
+ cell,
551
+ options,
552
+ }: {
553
+ cell: Tokens.TableCell
554
+ options: TableRenderOptions
555
+ }): RenderedTableCell {
556
+ const hasHtmlToken = cell.tokens.some((token) => {
557
+ return token.type === 'html'
558
+ })
559
+ if (!hasHtmlToken) {
560
+ return {
561
+ type: 'text',
562
+ text: extractCellText(cell.tokens),
563
+ }
564
+ }
565
+
566
+ const renderables = parseInlineHtmlRenderables({ html: cell.text })
567
+ if (renderables instanceof Error) {
568
+ return {
569
+ type: 'text',
570
+ text: extractRenderableText({ renderables: undefined, fallbackText: cell.text }),
571
+ }
572
+ }
573
+
574
+ const buttonRenderables = renderables.filter((renderable) => {
575
+ return renderable.type === 'button'
576
+ })
577
+ if (buttonRenderables.length !== 1) {
578
+ return {
579
+ type: 'text',
580
+ text: extractRenderableText({ renderables, fallbackText: cell.text }),
581
+ }
582
+ }
583
+
584
+ const hasNonWhitespaceText = renderables.some((renderable) => {
585
+ if (renderable.type !== 'text') {
586
+ return false
587
+ }
588
+ return renderable.text.trim().length > 0
589
+ })
590
+ if (hasNonWhitespaceText) {
591
+ return {
592
+ type: 'text',
593
+ text: extractRenderableText({ renderables, fallbackText: cell.text }),
594
+ }
595
+ }
596
+
597
+ const button = buttonRenderables[0]!
598
+ const customId = options.resolveButtonCustomId?.({ button })
599
+ if (!customId || customId instanceof Error) {
600
+ return {
601
+ type: 'text',
602
+ text: button.label,
603
+ }
604
+ }
605
+
606
+ return {
607
+ type: 'button',
608
+ label: button.label,
609
+ customId,
610
+ variant: button.variant,
611
+ disabled: button.disabled,
612
+ }
613
+ }
614
+
615
+ function getRenderedCellText({
616
+ cell,
617
+ }: {
618
+ cell: RenderedTableCell
619
+ }): string {
620
+ if (cell.type === 'button') {
621
+ return cell.label
622
+ }
623
+ return cell.text
624
+ }
625
+
626
+ function extractRenderableText({
627
+ renderables,
628
+ fallbackText,
629
+ }: {
630
+ renderables?: HtmlRenderable[]
631
+ fallbackText: string
632
+ }): string {
633
+ if (!renderables) {
634
+ return fallbackText.replace(/\s+/g, ' ').trim()
635
+ }
636
+
637
+ const text = renderables
638
+ .map((renderable) => {
639
+ if (renderable.type === 'button') {
640
+ return renderable.label
641
+ }
642
+ return renderable.text
643
+ })
644
+ .join(' ')
645
+ .replace(/\s+/g, ' ')
646
+ .trim()
647
+
648
+ if (text.length > 0) {
649
+ return text
650
+ }
651
+
652
+ return fallbackText.replace(/\s+/g, ' ').trim()
653
+ }
654
+
655
+ function toButtonStyle({
656
+ variant,
657
+ }: {
658
+ variant: HtmlButtonRenderable['variant']
659
+ }):
660
+ | ButtonStyle.Primary
661
+ | ButtonStyle.Secondary
662
+ | ButtonStyle.Success
663
+ | ButtonStyle.Danger {
664
+ if (variant === 'primary') {
665
+ return ButtonStyle.Primary
666
+ }
667
+ if (variant === 'success') {
668
+ return ButtonStyle.Success
669
+ }
670
+ if (variant === 'danger') {
671
+ return ButtonStyle.Danger
672
+ }
673
+ return ButtonStyle.Secondary
674
+ }
675
+
676
+ function extractCellText(tokens: Token[]): string {
677
+ const parts: string[] = []
678
+ for (const token of tokens) {
679
+ parts.push(extractTokenText(token))
680
+ }
681
+ return parts.join('').trim()
682
+ }
683
+
684
+ function extractTokenText(token: Token): string {
685
+ switch (token.type) {
686
+ case 'text':
687
+ case 'codespan':
688
+ case 'escape':
689
+ return token.text
690
+ case 'link':
691
+ return token.href
692
+ case 'image':
693
+ return token.href
694
+ case 'strong':
695
+ case 'em':
696
+ case 'del':
697
+ return token.tokens ? extractCellText(token.tokens) : token.text
698
+ case 'br':
699
+ return ' '
700
+ default: {
701
+ const nestedTokens = Reflect.get(token, 'tokens')
702
+ if (Array.isArray(nestedTokens)) {
703
+ return extractCellText(nestedTokens.filter((value): value is Token => {
704
+ return (
705
+ typeof value === 'object' &&
706
+ value !== null &&
707
+ typeof Reflect.get(value, 'type') === 'string'
708
+ )
709
+ }))
710
+ }
711
+ const text = Reflect.get(token, 'text')
712
+ if (typeof text === 'string') {
713
+ return text
714
+ }
715
+ return ''
716
+ }
717
+ }
718
+ }