@otto-assistant/otto 0.1.2 → 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 (638) 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/README.md +0 -142
  533. package/dist/cli.d.ts +0 -3
  534. package/dist/cli.d.ts.map +0 -1
  535. package/dist/cli.js.map +0 -1
  536. package/dist/config.d.ts +0 -39
  537. package/dist/config.d.ts.map +0 -1
  538. package/dist/config.js.map +0 -1
  539. package/dist/config.test.d.ts +0 -2
  540. package/dist/config.test.d.ts.map +0 -1
  541. package/dist/config.test.js +0 -202
  542. package/dist/config.test.js.map +0 -1
  543. package/dist/detect.d.ts +0 -9
  544. package/dist/detect.d.ts.map +0 -1
  545. package/dist/detect.js +0 -40
  546. package/dist/detect.js.map +0 -1
  547. package/dist/detect.test.d.ts +0 -2
  548. package/dist/detect.test.d.ts.map +0 -1
  549. package/dist/detect.test.js +0 -26
  550. package/dist/detect.test.js.map +0 -1
  551. package/dist/docker.d.ts +0 -7
  552. package/dist/docker.d.ts.map +0 -1
  553. package/dist/docker.js +0 -17
  554. package/dist/docker.js.map +0 -1
  555. package/dist/docker.test.d.ts +0 -2
  556. package/dist/docker.test.d.ts.map +0 -1
  557. package/dist/docker.test.js +0 -12
  558. package/dist/docker.test.js.map +0 -1
  559. package/dist/health.d.ts +0 -31
  560. package/dist/health.d.ts.map +0 -1
  561. package/dist/health.js +0 -117
  562. package/dist/health.js.map +0 -1
  563. package/dist/health.test.d.ts +0 -2
  564. package/dist/health.test.d.ts.map +0 -1
  565. package/dist/health.test.js +0 -52
  566. package/dist/health.test.js.map +0 -1
  567. package/dist/index.d.ts +0 -20
  568. package/dist/index.d.ts.map +0 -1
  569. package/dist/index.js +0 -15
  570. package/dist/index.js.map +0 -1
  571. package/dist/index.test.d.ts +0 -2
  572. package/dist/index.test.d.ts.map +0 -1
  573. package/dist/index.test.js +0 -8
  574. package/dist/index.test.js.map +0 -1
  575. package/dist/installer.d.ts +0 -10
  576. package/dist/installer.d.ts.map +0 -1
  577. package/dist/installer.js +0 -50
  578. package/dist/installer.js.map +0 -1
  579. package/dist/installer.test.d.ts +0 -2
  580. package/dist/installer.test.d.ts.map +0 -1
  581. package/dist/installer.test.js +0 -43
  582. package/dist/installer.test.js.map +0 -1
  583. package/dist/lifecycle.d.ts +0 -10
  584. package/dist/lifecycle.d.ts.map +0 -1
  585. package/dist/lifecycle.js +0 -45
  586. package/dist/lifecycle.js.map +0 -1
  587. package/dist/lifecycle.test.d.ts +0 -2
  588. package/dist/lifecycle.test.d.ts.map +0 -1
  589. package/dist/lifecycle.test.js +0 -20
  590. package/dist/lifecycle.test.js.map +0 -1
  591. package/dist/manifest.d.ts +0 -18
  592. package/dist/manifest.d.ts.map +0 -1
  593. package/dist/manifest.js +0 -30
  594. package/dist/manifest.js.map +0 -1
  595. package/dist/skills-baseline.d.ts +0 -7
  596. package/dist/skills-baseline.d.ts.map +0 -1
  597. package/dist/skills-baseline.js +0 -9
  598. package/dist/skills-baseline.js.map +0 -1
  599. package/dist/skills.d.ts +0 -110
  600. package/dist/skills.d.ts.map +0 -1
  601. package/dist/skills.js +0 -429
  602. package/dist/skills.js.map +0 -1
  603. package/dist/skills.test.d.ts +0 -2
  604. package/dist/skills.test.d.ts.map +0 -1
  605. package/dist/skills.test.js +0 -416
  606. package/dist/skills.test.js.map +0 -1
  607. package/dist/sync.d.ts +0 -10
  608. package/dist/sync.d.ts.map +0 -1
  609. package/dist/sync.js +0 -39
  610. package/dist/sync.js.map +0 -1
  611. package/dist/tenant.d.ts +0 -13
  612. package/dist/tenant.d.ts.map +0 -1
  613. package/dist/tenant.js +0 -105
  614. package/dist/tenant.js.map +0 -1
  615. package/dist/tenant.test.d.ts +0 -2
  616. package/dist/tenant.test.d.ts.map +0 -1
  617. package/dist/tenant.test.js +0 -37
  618. package/dist/tenant.test.js.map +0 -1
  619. package/src/config.test.ts +0 -237
  620. package/src/detect.test.ts +0 -29
  621. package/src/detect.ts +0 -52
  622. package/src/docker.test.ts +0 -12
  623. package/src/docker.ts +0 -23
  624. package/src/health.test.ts +0 -61
  625. package/src/health.ts +0 -158
  626. package/src/index.test.ts +0 -8
  627. package/src/index.ts +0 -62
  628. package/src/installer.test.ts +0 -52
  629. package/src/installer.ts +0 -62
  630. package/src/lifecycle.test.ts +0 -23
  631. package/src/lifecycle.ts +0 -49
  632. package/src/manifest.ts +0 -42
  633. package/src/skills-baseline.ts +0 -14
  634. package/src/skills.test.ts +0 -503
  635. package/src/skills.ts +0 -512
  636. package/src/sync.ts +0 -53
  637. package/src/tenant.test.ts +0 -49
  638. package/src/tenant.ts +0 -120
@@ -0,0 +1,22 @@
1
+ /**
2
+ * Otto branding overrides
3
+ *
4
+ * These values can be used throughout the distribution
5
+ * to customize the CLI appearance and messaging.
6
+ */
7
+ export const OTTO_BRANDING = {
8
+ /** Display name shown in UI */
9
+ name: "Otto",
10
+ /** Short description */
11
+ tagline: "AI agent distribution",
12
+ /** Organization URL */
13
+ homepage: "https://github.com/otto-assistant/otto",
14
+ /** Version of the otto distribution (updated by CI) */
15
+ ottoVersion: "0.1.0",
16
+ };
17
+ /**
18
+ * Check if running as Otto distribution
19
+ */
20
+ export function isOttoDistribution() {
21
+ return true; // This file only exists in the Otto fork
22
+ }
@@ -0,0 +1,21 @@
1
+ /**
2
+ * Otto distribution extensions
3
+ *
4
+ * Custom branding, features, and integrations for the Otto AI distribution.
5
+ * This module is imported by the upstream entry point.
6
+ * Upstream code is NOT modified beyond a single import line at the end of cli.ts.
7
+ */
8
+ export { OTTO_BRANDING } from "./branding.js";
9
+ /**
10
+ * Otto-specific configuration for the distribution
11
+ */
12
+ export const OTTO_CONFIG = {
13
+ /** GitHub organization */
14
+ org: "otto-assistant",
15
+ /** Upstream repository */
16
+ upstream: "otto-assistant/otto",
17
+ /** Distribution name */
18
+ distributionName: "Otto",
19
+ /** Homepage */
20
+ homepage: "https://github.com/otto-assistant/otto",
21
+ };
@@ -0,0 +1,161 @@
1
+ // End-to-end test using discord-digital-twin + real Otto bot runtime.
2
+ // Verifies onboarding channel creation, message -> thread creation, and assistant reply.
3
+ import fs from 'node:fs';
4
+ import path from 'node:path';
5
+ import { expect, test } from 'vitest';
6
+ import { ChannelType, Client, GatewayIntentBits, Partials } from 'discord.js';
7
+ import { DigitalDiscord } from 'discord-digital-twin/src';
8
+ import { CachedOpencodeProviderProxy } from 'opencode-cached-provider';
9
+ import { setDataDir } from './config.js';
10
+ import { startDiscordBot } from './discord-bot.js';
11
+ import { setBotToken, initDatabase, closeDatabase, setChannelDirectory, } from './database.js';
12
+ import { startHranaServer, stopHranaServer } from './hrana-server.js';
13
+ import { cleanupTestSessions, chooseLockPort, initTestGitRepo } from './test-utils.js';
14
+ import { stopOpencodeServer } from './opencode.js';
15
+ const geminiApiKey = process.env['GEMINI_API_KEY'] ||
16
+ process.env['GOOGLE_GENERATIVE_AI_API_KEY'] ||
17
+ '';
18
+ const geminiModel = process.env['GEMINI_FLASH_MODEL'] || 'gemini-2.5-flash';
19
+ const e2eTest = geminiApiKey.length > 0 ? test : test.skip;
20
+ function createRunDirectories() {
21
+ const root = path.resolve(process.cwd(), 'tmp', 'otto-digital-twin-e2e');
22
+ fs.mkdirSync(root, { recursive: true });
23
+ const dataDir = fs.mkdtempSync(path.join(root, 'data-'));
24
+ const projectDirectory = path.join(root, 'project');
25
+ const providerCacheDbPath = path.join(root, 'provider-cache.db');
26
+ fs.mkdirSync(projectDirectory, { recursive: true });
27
+ initTestGitRepo(projectDirectory);
28
+ return {
29
+ root,
30
+ dataDir,
31
+ projectDirectory,
32
+ providerCacheDbPath,
33
+ };
34
+ }
35
+ function createDiscordJsClient({ restUrl }) {
36
+ return new Client({
37
+ intents: [
38
+ GatewayIntentBits.Guilds,
39
+ GatewayIntentBits.GuildMessages,
40
+ GatewayIntentBits.MessageContent,
41
+ GatewayIntentBits.GuildVoiceStates,
42
+ ],
43
+ partials: [
44
+ Partials.Channel,
45
+ Partials.Message,
46
+ Partials.User,
47
+ Partials.ThreadMember,
48
+ ],
49
+ rest: {
50
+ api: restUrl,
51
+ version: '10',
52
+ },
53
+ });
54
+ }
55
+ e2eTest('onboarding then message creates thread and assistant reply via digital twin', async () => {
56
+ const testStartTime = Date.now();
57
+ const directories = createRunDirectories();
58
+ const lockPort = chooseLockPort({ key: 'otto-digital-twin-e2e' });
59
+ process.env['OTTO_LOCK_PORT'] = String(lockPort);
60
+ setDataDir(directories.dataDir);
61
+ const proxy = new CachedOpencodeProviderProxy({
62
+ cacheDbPath: directories.providerCacheDbPath,
63
+ targetBaseUrl: 'https://generativelanguage.googleapis.com/v1beta',
64
+ apiKey: geminiApiKey,
65
+ cacheMethods: ['POST'],
66
+ });
67
+ const testUserId = '100000000000000777';
68
+ const textChannelId = '100000000000000778';
69
+ const digitalDiscordDbPath = path.join(directories.dataDir, 'digital-discord.db');
70
+ const discord = new DigitalDiscord({
71
+ guild: {
72
+ name: 'Otto E2E Guild',
73
+ ownerId: testUserId,
74
+ },
75
+ channels: [
76
+ {
77
+ id: textChannelId,
78
+ name: 'otto-e2e',
79
+ type: ChannelType.GuildText,
80
+ },
81
+ ],
82
+ users: [
83
+ {
84
+ id: testUserId,
85
+ username: 'e2e-user',
86
+ },
87
+ ],
88
+ dbUrl: `file:${digitalDiscordDbPath}`,
89
+ });
90
+ let botClient = null;
91
+ try {
92
+ await Promise.all([proxy.start(), discord.start()]);
93
+ const opencodeConfig = proxy.buildOpencodeConfig({
94
+ providerName: 'cached-google',
95
+ providerNpm: '@ai-sdk/google',
96
+ model: geminiModel,
97
+ smallModel: geminiModel,
98
+ });
99
+ fs.writeFileSync(path.join(directories.projectDirectory, 'opencode.json'), JSON.stringify(opencodeConfig, null, 2));
100
+ const dbPath = path.join(directories.dataDir, 'discord-sessions.db');
101
+ const hranaResult = await startHranaServer({ dbPath });
102
+ if (hranaResult instanceof Error) {
103
+ throw hranaResult;
104
+ }
105
+ process.env['OTTO_DB_URL'] = hranaResult;
106
+ await initDatabase();
107
+ await setBotToken(discord.botUserId, discord.botToken);
108
+ await setChannelDirectory({
109
+ channelId: textChannelId,
110
+ directory: directories.projectDirectory,
111
+ channelType: 'text',
112
+ });
113
+ botClient = createDiscordJsClient({ restUrl: discord.restUrl });
114
+ await startDiscordBot({
115
+ token: discord.botToken,
116
+ appId: discord.botUserId,
117
+ discordClient: botClient,
118
+ });
119
+ await discord.channel(textChannelId).user(testUserId).sendMessage({
120
+ content: 'Reply with exactly: otto digital twin ok',
121
+ });
122
+ const createdThread = await discord.channel(textChannelId).waitForThread({
123
+ timeout: 60_000,
124
+ predicate: (thread) => {
125
+ return thread.name === 'Reply with exactly: otto digital twin ok';
126
+ },
127
+ });
128
+ const botReply = await discord.thread(createdThread.id).waitForBotReply({
129
+ timeout: 120_000,
130
+ });
131
+ expect(createdThread.id.length).toBeGreaterThan(0);
132
+ expect(botReply.content.trim().length).toBeGreaterThan(0);
133
+ }
134
+ finally {
135
+ await cleanupTestSessions({
136
+ projectDirectory: directories.projectDirectory,
137
+ testStartTime,
138
+ });
139
+ if (botClient) {
140
+ botClient.destroy();
141
+ }
142
+ await stopOpencodeServer();
143
+ await Promise.all([
144
+ closeDatabase().catch(() => {
145
+ return;
146
+ }),
147
+ stopHranaServer().catch(() => {
148
+ return;
149
+ }),
150
+ proxy.stop().catch(() => {
151
+ return;
152
+ }),
153
+ discord.stop().catch(() => {
154
+ return;
155
+ }),
156
+ ]);
157
+ delete process.env['OTTO_LOCK_PORT'];
158
+ delete process.env['OTTO_DB_URL'];
159
+ fs.rmSync(directories.dataDir, { recursive: true, force: true });
160
+ }
161
+ }, 360_000);
@@ -0,0 +1,94 @@
1
+ // E2e test for OpenCode plugin loading.
2
+ // Spawns `opencode serve` directly with our plugin in OPENCODE_CONFIG_CONTENT,
3
+ // waits for the health endpoint, then checks stderr for plugin errors.
4
+ // No Discord infrastructure needed — just the OpenCode server process.
5
+ import { spawn } from 'node:child_process';
6
+ import fs from 'node:fs';
7
+ import path from 'node:path';
8
+ import { fileURLToPath } from 'node:url';
9
+ import { test, expect } from 'vitest';
10
+ import { resolveOpencodeCommand } from './opencode.js';
11
+ import { getSpawnCommandAndArgs } from './opencode-command.js';
12
+ import { chooseLockPort } from './test-utils.js';
13
+ const __dirname = path.dirname(fileURLToPath(import.meta.url));
14
+ async function waitForHealth({ port, maxAttempts = 30, }) {
15
+ for (let i = 0; i < maxAttempts; i++) {
16
+ try {
17
+ const response = await fetch(`http://127.0.0.1:${port}/api/health`);
18
+ if (response.status < 500) {
19
+ return true;
20
+ }
21
+ }
22
+ catch {
23
+ // connection refused, retry
24
+ }
25
+ await new Promise((resolve) => {
26
+ setTimeout(resolve, 1000);
27
+ });
28
+ }
29
+ return false;
30
+ }
31
+ test('opencode server loads plugin without errors', async () => {
32
+ const projectDir = path.resolve(process.cwd(), 'tmp', 'plugin-loading-e2e');
33
+ fs.mkdirSync(projectDir, { recursive: true });
34
+ const port = chooseLockPort({ key: 'opencode-plugin-loading-e2e' });
35
+ const pluginPath = new URL('../src/otto-opencode-plugin.ts', import.meta.url).href;
36
+ const stderrLines = [];
37
+ const isolatedOpencodeRoot = path.join(projectDir, 'opencode-test-home');
38
+ const xdgDirectories = {
39
+ OPENCODE_CONFIG_DIR: path.join(isolatedOpencodeRoot, '.opencode-otto'),
40
+ XDG_CONFIG_HOME: path.join(isolatedOpencodeRoot, '.config'),
41
+ XDG_DATA_HOME: path.join(isolatedOpencodeRoot, '.local', 'share'),
42
+ XDG_CACHE_HOME: path.join(isolatedOpencodeRoot, '.cache'),
43
+ XDG_STATE_HOME: path.join(isolatedOpencodeRoot, '.local', 'state'),
44
+ };
45
+ fs.mkdirSync(isolatedOpencodeRoot, { recursive: true });
46
+ Object.values(xdgDirectories).forEach((directory) => {
47
+ fs.mkdirSync(directory, { recursive: true });
48
+ });
49
+ const { command, args, windowsVerbatimArguments, } = getSpawnCommandAndArgs({
50
+ resolvedCommand: resolveOpencodeCommand(),
51
+ baseArgs: ['serve', '--port', port.toString(), '--print-logs', '--log-level', 'DEBUG'],
52
+ });
53
+ const serverProcess = spawn(command, args, {
54
+ stdio: 'pipe',
55
+ cwd: projectDir,
56
+ windowsVerbatimArguments,
57
+ env: {
58
+ ...process.env,
59
+ OPENCODE_CONFIG_CONTENT: JSON.stringify({
60
+ $schema: 'https://opencode.ai/config.json',
61
+ lsp: false,
62
+ formatter: false,
63
+ plugin: [pluginPath],
64
+ }),
65
+ OPENCODE_TEST_HOME: isolatedOpencodeRoot,
66
+ ...xdgDirectories,
67
+ },
68
+ });
69
+ serverProcess.stderr?.on('data', (data) => {
70
+ stderrLines.push(...data.toString().split('\n').filter(Boolean));
71
+ });
72
+ try {
73
+ const healthy = await waitForHealth({ port });
74
+ expect(healthy).toBe(true);
75
+ // Check no plugin-related errors in stderr
76
+ const pluginErrorPatterns = [
77
+ /plugin.*error/i,
78
+ /failed to load plugin/i,
79
+ /cannot find module/i,
80
+ /ERR_MODULE_NOT_FOUND/i,
81
+ /plugin.*failed/i,
82
+ /plugin.*crash/i,
83
+ ];
84
+ const errorLines = stderrLines.filter((line) => {
85
+ return pluginErrorPatterns.some((pattern) => {
86
+ return pattern.test(line);
87
+ });
88
+ });
89
+ expect(errorLines).toEqual([]);
90
+ }
91
+ finally {
92
+ serverProcess.kill('SIGTERM');
93
+ }
94
+ }, 60_000);
@@ -0,0 +1,21 @@
1
+ // OpenCode plugin entry point for Otto Discord bot.
2
+ // Each export is treated as a separate plugin by OpenCode's plugin loader.
3
+ // CRITICAL: never export utility functions from this file — only plugin
4
+ // initializer functions. OpenCode calls every export as a plugin.
5
+ //
6
+ // Plugins are split into focused modules:
7
+ // - ipc-tools-plugin: file upload + action buttons (IPC-based Discord tools)
8
+ // - context-awareness-plugin: branch, pwd, memory reminder, onboarding tutorial
9
+ // - memory-overview-plugin: frozen MEMORY.md heading overview per session
10
+ // - opencode-interrupt-plugin: interrupt queued messages at step boundaries
11
+ // - subagent-rate-limit-plugin: aborts only task subagents after rate limits
12
+ // - kitty-graphics-plugin: extract Kitty Graphics Protocol images from bash output
13
+ export { ipcToolsPlugin } from './ipc-tools-plugin.js';
14
+ export { contextAwarenessPlugin } from './context-awareness-plugin.js';
15
+ export { memoryOverviewPlugin } from './memory-overview-plugin.js';
16
+ export { interruptOpencodeSessionOnUserMessage } from './opencode-interrupt-plugin.js';
17
+ export { anthropicAuthPlugin } from './anthropic-auth-plugin.js';
18
+ export { imageOptimizerPlugin } from './image-optimizer-plugin.js';
19
+ export { subagentRateLimitPlugin } from './subagent-rate-limit-plugin.js';
20
+ export { kittyGraphicsPlugin } from 'kitty-graphics-agent';
21
+ export { injectionGuardInternal as injectionGuard } from 'opencode-injection-guard';
@@ -0,0 +1,98 @@
1
+ import { test, expect, describe } from 'vitest';
2
+ import { condenseMemoryMd } from './condense-memory.js';
3
+ describe('condenseMemoryMd', () => {
4
+ test('multiple headings with body content', () => {
5
+ const content = [
6
+ '# Project Overview',
7
+ '',
8
+ 'This is a big project with many things.',
9
+ 'It does X, Y, and Z.',
10
+ '',
11
+ '## Auth Architecture',
12
+ '',
13
+ 'JWT tokens with 15min expiry.',
14
+ 'Refresh tokens in httpOnly cookies.',
15
+ 'Session stored in Redis.',
16
+ '',
17
+ '## User Preferences',
18
+ '',
19
+ '- kebab-case filenames',
20
+ '- errore-style errors',
21
+ '- no emojis',
22
+ '',
23
+ '### API Conventions',
24
+ '',
25
+ 'All routes return { data, error }.',
26
+ 'Use spiceflow for the server.',
27
+ '',
28
+ ].join('\n');
29
+ expect(condenseMemoryMd(content)).toMatchInlineSnapshot(`
30
+ "1: # Project Overview
31
+ ...
32
+ 6: ## Auth Architecture
33
+ ...
34
+ 12: ## User Preferences
35
+ ...
36
+ 18: ### API Conventions
37
+ ..."
38
+ `);
39
+ });
40
+ test('body text before first heading', () => {
41
+ const content = [
42
+ 'Some preamble notes.',
43
+ '',
44
+ '# First Heading',
45
+ '',
46
+ 'Content here.',
47
+ '',
48
+ ].join('\n');
49
+ expect(condenseMemoryMd(content)).toMatchInlineSnapshot(`
50
+ "...
51
+ 3: # First Heading
52
+ ..."
53
+ `);
54
+ });
55
+ test('no headings at all', () => {
56
+ const content = 'Just some notes.\nMore notes.\n';
57
+ expect(condenseMemoryMd(content)).toMatchInlineSnapshot(`"..."`);
58
+ });
59
+ test('empty content', () => {
60
+ expect(condenseMemoryMd('')).toMatchInlineSnapshot(`""`);
61
+ });
62
+ test('consecutive headings without body', () => {
63
+ const content = [
64
+ '# H1',
65
+ '## H2',
66
+ '### H3',
67
+ '',
68
+ 'Some body.',
69
+ '',
70
+ ].join('\n');
71
+ expect(condenseMemoryMd(content)).toMatchInlineSnapshot(`
72
+ "1: # H1
73
+ 2: ## H2
74
+ 3: ### H3
75
+ ..."
76
+ `);
77
+ });
78
+ test('heading with code block body', () => {
79
+ const content = [
80
+ '# Config',
81
+ '',
82
+ '```json',
83
+ '{ "key": "value" }',
84
+ '```',
85
+ '',
86
+ '## Notes',
87
+ '',
88
+ 'Some text.',
89
+ '',
90
+ ].join('\n');
91
+ expect(condenseMemoryMd(content)).toMatchInlineSnapshot(`
92
+ "1: # Config
93
+ ...
94
+ 7: ## Notes
95
+ ..."
96
+ `);
97
+ });
98
+ });
@@ -0,0 +1,117 @@
1
+ // Tests for parsePermissionRules() from opencode.ts
2
+ import { describe, test, expect } from 'vitest';
3
+ import { parsePermissionRules } from './opencode.js';
4
+ describe('parsePermissionRules', () => {
5
+ test('simple tool:action format', () => {
6
+ expect(parsePermissionRules(['bash:deny'])).toMatchInlineSnapshot(`
7
+ [
8
+ {
9
+ "action": "deny",
10
+ "pattern": "*",
11
+ "permission": "bash",
12
+ },
13
+ ]
14
+ `);
15
+ });
16
+ test('multiple rules', () => {
17
+ expect(parsePermissionRules(['bash:deny', 'edit:deny', 'read:allow'])).toMatchInlineSnapshot(`
18
+ [
19
+ {
20
+ "action": "deny",
21
+ "pattern": "*",
22
+ "permission": "bash",
23
+ },
24
+ {
25
+ "action": "deny",
26
+ "pattern": "*",
27
+ "permission": "edit",
28
+ },
29
+ {
30
+ "action": "allow",
31
+ "pattern": "*",
32
+ "permission": "read",
33
+ },
34
+ ]
35
+ `);
36
+ });
37
+ test('tool:pattern:action format', () => {
38
+ expect(parsePermissionRules(['bash:git *:allow'])).toMatchInlineSnapshot(`
39
+ [
40
+ {
41
+ "action": "allow",
42
+ "pattern": "git *",
43
+ "permission": "bash",
44
+ },
45
+ ]
46
+ `);
47
+ });
48
+ test('wildcard permission', () => {
49
+ expect(parsePermissionRules(['*:deny'])).toMatchInlineSnapshot(`
50
+ [
51
+ {
52
+ "action": "deny",
53
+ "pattern": "*",
54
+ "permission": "*",
55
+ },
56
+ ]
57
+ `);
58
+ });
59
+ test('case-insensitive action', () => {
60
+ expect(parsePermissionRules(['bash:DENY', 'edit:Allow'])).toMatchInlineSnapshot(`
61
+ [
62
+ {
63
+ "action": "deny",
64
+ "pattern": "*",
65
+ "permission": "bash",
66
+ },
67
+ {
68
+ "action": "allow",
69
+ "pattern": "*",
70
+ "permission": "edit",
71
+ },
72
+ ]
73
+ `);
74
+ });
75
+ test('trims whitespace', () => {
76
+ expect(parsePermissionRules([' bash : deny '])).toMatchInlineSnapshot(`
77
+ [
78
+ {
79
+ "action": "deny",
80
+ "pattern": "*",
81
+ "permission": "bash",
82
+ },
83
+ ]
84
+ `);
85
+ });
86
+ test('skips invalid entries', () => {
87
+ expect(parsePermissionRules(['', 'bash', 'bash:invalid', ':deny'])).toMatchInlineSnapshot(`[]`);
88
+ });
89
+ test('handles non-array input defensively', () => {
90
+ expect(parsePermissionRules(undefined)).toMatchInlineSnapshot(`[]`);
91
+ expect(parsePermissionRules(null)).toMatchInlineSnapshot(`[]`);
92
+ expect(parsePermissionRules('bash:deny')).toMatchInlineSnapshot(`[]`);
93
+ expect(parsePermissionRules(123)).toMatchInlineSnapshot(`[]`);
94
+ });
95
+ test('handles non-string array items', () => {
96
+ expect(parsePermissionRules([123, null, 'bash:deny'])).toMatchInlineSnapshot(`
97
+ [
98
+ {
99
+ "action": "deny",
100
+ "pattern": "*",
101
+ "permission": "bash",
102
+ },
103
+ ]
104
+ `);
105
+ });
106
+ test('ask action', () => {
107
+ expect(parsePermissionRules(['webfetch:ask'])).toMatchInlineSnapshot(`
108
+ [
109
+ {
110
+ "action": "ask",
111
+ "pattern": "*",
112
+ "permission": "webfetch",
113
+ },
114
+ ]
115
+ `);
116
+ });
117
+ });
@@ -0,0 +1,97 @@
1
+ // Shared apply_patch text parsing utilities.
2
+ // Used by diff-patch-plugin.ts (file path extraction for snapshots) and
3
+ // message-formatting.ts (per-file addition/deletion counts for Discord display).
4
+ //
5
+ // The apply_patch tool uses three path header formats:
6
+ // *** Add File: path — new file
7
+ // *** Update File: path — existing file edit
8
+ // *** Delete File: path — file removal
9
+ // *** Move to: path — rename destination
10
+ // --- a/path / +++ b/path — unified diff headers (fallback)
11
+ /**
12
+ * Extract all file paths referenced in a patchText string.
13
+ * Handles custom apply_patch headers, move targets, and unified diff headers.
14
+ * Returns deduplicated paths.
15
+ */
16
+ export function extractPatchFilePaths(patchText) {
17
+ const custom = [
18
+ ...patchText.matchAll(/^\*\*\* (?:Add|Update|Delete) File:\s+(.+)$/gm),
19
+ ].map((m) => {
20
+ return (m[1] ?? '').trim();
21
+ });
22
+ const moved = [
23
+ ...patchText.matchAll(/^\*\*\* Move to:\s+(.+)$/gm),
24
+ ].map((m) => {
25
+ return (m[1] ?? '').trim();
26
+ });
27
+ const unified = [
28
+ ...patchText.matchAll(/^(?:---|\+\+\+) [ab]\/(.+)$/gm),
29
+ ].map((m) => {
30
+ return (m[1] ?? '').trim();
31
+ });
32
+ const all = [...custom, ...moved, ...unified].filter(Boolean);
33
+ return all.filter((v, i, a) => {
34
+ return a.indexOf(v) === i;
35
+ });
36
+ }
37
+ /**
38
+ * Parse a patchText string and count additions/deletions per file.
39
+ * Patch format uses `*** Add File:`, `*** Update File:`, `*** Delete File:` headers,
40
+ * with diff lines prefixed by `+` (addition) or `-` (deletion) inside `@@` hunks.
41
+ */
42
+ export function parsePatchFileCounts(patchText) {
43
+ const counts = new Map();
44
+ const lines = patchText.split('\n');
45
+ let currentFile = '';
46
+ let currentType = '';
47
+ let inHunk = false;
48
+ for (const line of lines) {
49
+ const addMatch = line.match(/^\*\*\* Add File:\s*(.+)/);
50
+ const updateMatch = line.match(/^\*\*\* Update File:\s*(.+)/);
51
+ const deleteMatch = line.match(/^\*\*\* Delete File:\s*(.+)/);
52
+ if (addMatch || updateMatch || deleteMatch) {
53
+ const match = addMatch || updateMatch || deleteMatch;
54
+ currentFile = (match?.[1] ?? '').trim();
55
+ currentType = addMatch ? 'add' : updateMatch ? 'update' : 'delete';
56
+ counts.set(currentFile, { additions: 0, deletions: 0 });
57
+ inHunk = false;
58
+ continue;
59
+ }
60
+ if (line.startsWith('@@')) {
61
+ inHunk = true;
62
+ continue;
63
+ }
64
+ if (line.startsWith('*** ')) {
65
+ inHunk = false;
66
+ continue;
67
+ }
68
+ if (!currentFile) {
69
+ continue;
70
+ }
71
+ const entry = counts.get(currentFile);
72
+ if (!entry) {
73
+ continue;
74
+ }
75
+ if (currentType === 'add') {
76
+ // all content lines in Add File are additions
77
+ if (line.length > 0 && !line.startsWith('*** ')) {
78
+ entry.additions++;
79
+ }
80
+ }
81
+ else if (currentType === 'delete') {
82
+ // all content lines in Delete File are deletions
83
+ if (line.length > 0 && !line.startsWith('*** ')) {
84
+ entry.deletions++;
85
+ }
86
+ }
87
+ else if (inHunk) {
88
+ if (line.startsWith('+')) {
89
+ entry.additions++;
90
+ }
91
+ else if (line.startsWith('-')) {
92
+ entry.deletions++;
93
+ }
94
+ }
95
+ }
96
+ return counts;
97
+ }