@polderlabs/bizar 3.23.1 → 4.0.0

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 (346) hide show
  1. package/README.md +72 -2
  2. package/bizar-dash/.bizar/activity.log +3 -0
  3. package/bizar-dash/.bizar/graph/.graphify_analysis.json +1540 -0
  4. package/bizar-dash/.bizar/graph/.graphify_labels.json +1 -0
  5. package/bizar-dash/.bizar/graph/GRAPH_REPORT.md +404 -0
  6. package/bizar-dash/.bizar/graph/cache/ast/v0.8.46/0661b816358db84dfdb7f10443db8a61152fd20b3fee950caf8fbf32920a1790.json +1 -0
  7. package/bizar-dash/.bizar/graph/cache/ast/v0.8.46/16da06d774142a6e5d74829ad982531286a6193ef798de8f593db029e123ec32.json +1 -0
  8. package/bizar-dash/.bizar/graph/cache/ast/v0.8.46/27e5697c01254c69c64c5a33896669073b40afb5ba88375102ae77fa5404136d.json +1 -0
  9. package/bizar-dash/.bizar/graph/cache/ast/v0.8.46/532997898027254aa4c0932243dc71d2f55888181232b65974961934997dc85b.json +1 -0
  10. package/bizar-dash/.bizar/graph/cache/ast/v0.8.46/84e3451a64c7f99f0cd6a88b0748d7426780486f53ab4935b7476b28c08f8293.json +1 -0
  11. package/bizar-dash/.bizar/graph/cache/ast/v0.8.46/88e5ea5f3e78fa69353fe9e6a8d1a05391d54605e25600cb125531eb5a6432ff.json +1 -0
  12. package/bizar-dash/.bizar/graph/cache/stat-index.json +1 -0
  13. package/bizar-dash/.bizar/graph/graph.html +307 -0
  14. package/bizar-dash/.bizar/graph/graph.json +40267 -0
  15. package/bizar-dash/.bizar/graph/manifest.json +762 -0
  16. package/bizar-dash/.graphifyignore +40 -0
  17. package/bizar-dash/.obsidian/README.md +14 -0
  18. package/bizar-dash/.obsidian/daily/2026-06-25.md +9 -0
  19. package/bizar-dash/.obsidian/vault.json +7 -0
  20. package/bizar-dash/.od-skills/web-prototype-e1429c1737/SKILL.md +103 -0
  21. package/bizar-dash/.od-skills/web-prototype-e1429c1737/assets/template.html +338 -0
  22. package/bizar-dash/.od-skills/web-prototype-e1429c1737/example.html +81 -0
  23. package/bizar-dash/.od-skills/web-prototype-e1429c1737/open-design.json +172 -0
  24. package/bizar-dash/.od-skills/web-prototype-e1429c1737/references/checklist.md +44 -0
  25. package/bizar-dash/.od-skills/web-prototype-e1429c1737/references/layouts.md +247 -0
  26. package/bizar-dash/CHANGELOG.md +474 -0
  27. package/bizar-dash/DESIGN.md +136 -0
  28. package/bizar-dash/dist/assets/main-B3RgW6FS.js +331 -0
  29. package/bizar-dash/dist/assets/main-B3RgW6FS.js.map +1 -0
  30. package/bizar-dash/dist/assets/main-DlJ8wgLR.css +1 -0
  31. package/bizar-dash/dist/assets/mobile-CsZQAswA.css +1 -0
  32. package/bizar-dash/dist/assets/mobile-DbxIw8BG.js +2 -0
  33. package/bizar-dash/dist/assets/mobile-DbxIw8BG.js.map +1 -0
  34. package/bizar-dash/dist/assets/mobile-Dvq2d53Y.js +354 -0
  35. package/bizar-dash/dist/assets/mobile-Dvq2d53Y.js.map +1 -0
  36. package/bizar-dash/dist/index.html +40 -0
  37. package/bizar-dash/dist/mobile.html +22 -0
  38. package/bizar-dash/plans/debug-plan/comments.json +1 -0
  39. package/bizar-dash/plans/debug-plan/meta.json +10 -0
  40. package/bizar-dash/plans/debug-plan/plan.json +174 -0
  41. package/bizar-dash/scripts/smoke-bg-retry.mjs +246 -0
  42. package/bizar-dash/src/cli/dashboard-ports.mjs +488 -0
  43. package/bizar-dash/src/cli.mjs +522 -0
  44. package/bizar-dash/src/server/activity-log.mjs +174 -0
  45. package/bizar-dash/src/server/agents-store.mjs +465 -0
  46. package/bizar-dash/src/server/api.mjs +138 -0
  47. package/bizar-dash/src/server/artifact-template.html +810 -0
  48. package/bizar-dash/src/server/artifacts-store.mjs +733 -0
  49. package/bizar-dash/src/server/auth.mjs +381 -0
  50. package/bizar-dash/src/server/background-store.mjs +447 -0
  51. package/bizar-dash/src/server/bg-poller.mjs +305 -0
  52. package/bizar-dash/src/server/bg-retry.mjs +574 -0
  53. package/bizar-dash/src/server/browser.mjs +40 -0
  54. package/bizar-dash/src/server/diagnostics-store.mjs +148 -0
  55. package/bizar-dash/src/server/dialog-poller.mjs +105 -0
  56. package/bizar-dash/src/server/dialog-store.mjs +177 -0
  57. package/bizar-dash/src/server/glyphs/mdx-compiler.mjs +583 -0
  58. package/bizar-dash/src/server/lib/path-safe.mjs +283 -0
  59. package/bizar-dash/src/server/memory-git.mjs +249 -0
  60. package/bizar-dash/src/server/memory-schema.mjs +145 -0
  61. package/bizar-dash/src/server/memory-secrets.mjs +84 -0
  62. package/bizar-dash/src/server/memory-store.mjs +424 -0
  63. package/bizar-dash/src/server/mod-security.mjs +398 -0
  64. package/bizar-dash/src/server/mods-loader.mjs +1067 -0
  65. package/bizar-dash/src/server/notifications-store.mjs +243 -0
  66. package/bizar-dash/src/server/obsidian-store.mjs +318 -0
  67. package/bizar-dash/src/server/opencode-runner.mjs +279 -0
  68. package/bizar-dash/src/server/pair-store.mjs +138 -0
  69. package/bizar-dash/src/server/projects-store.mjs +364 -0
  70. package/bizar-dash/src/server/providers-store.mjs +789 -0
  71. package/bizar-dash/src/server/routes/_shared.mjs +415 -0
  72. package/bizar-dash/src/server/routes/activity.mjs +110 -0
  73. package/bizar-dash/src/server/routes/agents.mjs +134 -0
  74. package/bizar-dash/src/server/routes/artifacts.mjs +279 -0
  75. package/bizar-dash/src/server/routes/auth.mjs +69 -0
  76. package/bizar-dash/src/server/routes/background.mjs +165 -0
  77. package/bizar-dash/src/server/routes/chat.mjs +485 -0
  78. package/bizar-dash/src/server/routes/config.mjs +129 -0
  79. package/bizar-dash/src/server/routes/diagnostics.mjs +50 -0
  80. package/bizar-dash/src/server/routes/dialogs.mjs +86 -0
  81. package/bizar-dash/src/server/routes/fs.mjs +429 -0
  82. package/bizar-dash/src/server/routes/history.mjs +78 -0
  83. package/bizar-dash/src/server/routes/memory.mjs +499 -0
  84. package/bizar-dash/src/server/routes/misc.mjs +89 -0
  85. package/bizar-dash/src/server/routes/mods.mjs +359 -0
  86. package/bizar-dash/src/server/routes/notifications.mjs +54 -0
  87. package/bizar-dash/src/server/routes/obsidian.mjs +239 -0
  88. package/bizar-dash/src/server/routes/opencode-sessions.mjs +55 -0
  89. package/bizar-dash/src/server/routes/overview.mjs +91 -0
  90. package/bizar-dash/src/server/routes/pair.mjs +67 -0
  91. package/bizar-dash/src/server/routes/projects.mjs +175 -0
  92. package/bizar-dash/src/server/routes/providers.mjs +47 -0
  93. package/bizar-dash/src/server/routes/schedules.mjs +104 -0
  94. package/bizar-dash/src/server/routes/settings.mjs +51 -0
  95. package/bizar-dash/src/server/routes/skills.mjs +76 -0
  96. package/bizar-dash/src/server/routes/tasks.mjs +468 -0
  97. package/bizar-dash/src/server/routes/themes.mjs +51 -0
  98. package/bizar-dash/src/server/routes-v2/auth.mjs +59 -0
  99. package/bizar-dash/src/server/routes-v2/events.mjs +107 -0
  100. package/bizar-dash/src/server/routes-v2/health.mjs +21 -0
  101. package/bizar-dash/src/server/routes-v2/index.mjs +90 -0
  102. package/bizar-dash/src/server/routes-v2/sessions.mjs +111 -0
  103. package/bizar-dash/src/server/schedules-runner.mjs +369 -0
  104. package/bizar-dash/src/server/schedules-store.mjs +268 -0
  105. package/bizar-dash/src/server/search-store.mjs +220 -0
  106. package/bizar-dash/src/server/serve-info.mjs +684 -0
  107. package/bizar-dash/src/server/server.mjs +796 -0
  108. package/bizar-dash/src/server/settings-store.mjs +45 -0
  109. package/bizar-dash/src/server/skills-store.mjs +300 -0
  110. package/bizar-dash/src/server/state.mjs +437 -0
  111. package/bizar-dash/src/server/tailscale-store.mjs +113 -0
  112. package/bizar-dash/src/server/task-delegator.mjs +824 -0
  113. package/bizar-dash/src/server/tasks-store.mjs +508 -0
  114. package/bizar-dash/src/server/tui.mjs +923 -0
  115. package/bizar-dash/src/server/update-store.mjs +168 -0
  116. package/bizar-dash/src/server/v2-auth-file.mjs +99 -0
  117. package/bizar-dash/src/server/v2-event-bus.mjs +128 -0
  118. package/bizar-dash/src/server/watcher.mjs +81 -0
  119. package/bizar-dash/src/server/yaml.mjs +238 -0
  120. package/bizar-dash/src/web/App.tsx +718 -0
  121. package/bizar-dash/src/web/MobileApp.tsx +408 -0
  122. package/bizar-dash/src/web/components/ArtifactCreateDialog.tsx +95 -0
  123. package/bizar-dash/src/web/components/ArtifactListDialog.tsx +55 -0
  124. package/bizar-dash/src/web/components/ArtifactViewer.tsx +143 -0
  125. package/bizar-dash/src/web/components/AuditDialog.tsx +79 -0
  126. package/bizar-dash/src/web/components/BgStatusBadge.tsx +32 -0
  127. package/bizar-dash/src/web/components/Button.tsx +56 -0
  128. package/bizar-dash/src/web/components/CanvasContextMenu.tsx +68 -0
  129. package/bizar-dash/src/web/components/Card.tsx +40 -0
  130. package/bizar-dash/src/web/components/CollapsibleSection.tsx +72 -0
  131. package/bizar-dash/src/web/components/CommandDialog.tsx +66 -0
  132. package/bizar-dash/src/web/components/EmptyState.tsx +30 -0
  133. package/bizar-dash/src/web/components/FileBrowser.tsx +633 -0
  134. package/bizar-dash/src/web/components/HelpDialog.tsx +55 -0
  135. package/bizar-dash/src/web/components/KillConfirmDialog.tsx +88 -0
  136. package/bizar-dash/src/web/components/Modal.tsx +215 -0
  137. package/bizar-dash/src/web/components/Notifications.tsx +241 -0
  138. package/bizar-dash/src/web/components/SearchModal.tsx +224 -0
  139. package/bizar-dash/src/web/components/Sidebar.tsx +66 -0
  140. package/bizar-dash/src/web/components/Spinner.tsx +19 -0
  141. package/bizar-dash/src/web/components/StatusBadge.tsx +25 -0
  142. package/bizar-dash/src/web/components/Tag.tsx +28 -0
  143. package/bizar-dash/src/web/components/Toast.tsx +150 -0
  144. package/bizar-dash/src/web/components/Topbar.tsx +368 -0
  145. package/bizar-dash/src/web/components/VisualPlanDialog.tsx +58 -0
  146. package/bizar-dash/src/web/components/chat/AgentChip.tsx +68 -0
  147. package/bizar-dash/src/web/components/chat/ChatBubble.tsx +60 -0
  148. package/bizar-dash/src/web/components/chat/ChatThread.tsx +88 -0
  149. package/bizar-dash/src/web/components/chat/ChatTopBar.tsx +50 -0
  150. package/bizar-dash/src/web/components/chat/Composer.tsx +164 -0
  151. package/bizar-dash/src/web/components/chat/ConfirmModal.tsx +54 -0
  152. package/bizar-dash/src/web/components/chat/EmptyState.tsx +21 -0
  153. package/bizar-dash/src/web/components/chat/FirstRunGreeting.tsx +18 -0
  154. package/bizar-dash/src/web/components/chat/FloatingComposer.tsx +32 -0
  155. package/bizar-dash/src/web/components/chat/InfoPanel.tsx +119 -0
  156. package/bizar-dash/src/web/components/chat/LoadingSkeleton.tsx +19 -0
  157. package/bizar-dash/src/web/components/chat/MessageBubble.tsx +60 -0
  158. package/bizar-dash/src/web/components/chat/SessionList.tsx +153 -0
  159. package/bizar-dash/src/web/components/chat/StreamingIndicator.tsx +9 -0
  160. package/bizar-dash/src/web/components/chat/SuggestionCards.tsx +55 -0
  161. package/bizar-dash/src/web/components/chat/WelcomeScreen.tsx +36 -0
  162. package/bizar-dash/src/web/components/chat/index.ts +20 -0
  163. package/bizar-dash/src/web/components/chat/useAutoGrowTextarea.ts +18 -0
  164. package/bizar-dash/src/web/components/chat/useChat.ts +219 -0
  165. package/bizar-dash/src/web/components/chat/useSlashCommands.ts +47 -0
  166. package/bizar-dash/src/web/index.html +37 -0
  167. package/bizar-dash/src/web/lib/api.ts +193 -0
  168. package/bizar-dash/src/web/lib/markdown.tsx +59 -0
  169. package/bizar-dash/src/web/lib/types.ts +668 -0
  170. package/bizar-dash/src/web/lib/utils.ts +114 -0
  171. package/bizar-dash/src/web/lib/ws.ts +176 -0
  172. package/bizar-dash/src/web/main.tsx +34 -0
  173. package/bizar-dash/src/web/mobile/MobileApp.tsx +278 -0
  174. package/bizar-dash/src/web/mobile/MobileBottomNav.tsx +38 -0
  175. package/bizar-dash/src/web/mobile/MobileTopbar.tsx +115 -0
  176. package/bizar-dash/src/web/mobile/components/MobileBottomSheet.tsx +182 -0
  177. package/bizar-dash/src/web/mobile/components/MobileListItem.tsx +64 -0
  178. package/bizar-dash/src/web/mobile/components/MobileModal.tsx +123 -0
  179. package/bizar-dash/src/web/mobile/views/MobileActivity.tsx +273 -0
  180. package/bizar-dash/src/web/mobile/views/MobileAgents.tsx +374 -0
  181. package/bizar-dash/src/web/mobile/views/MobileArtifactCanvas.tsx +439 -0
  182. package/bizar-dash/src/web/mobile/views/MobileArtifacts.tsx +143 -0
  183. package/bizar-dash/src/web/mobile/views/MobileChat.tsx +180 -0
  184. package/bizar-dash/src/web/mobile/views/MobileConfig.tsx +175 -0
  185. package/bizar-dash/src/web/mobile/views/MobileHistory.tsx +191 -0
  186. package/bizar-dash/src/web/mobile/views/MobileMods.tsx +149 -0
  187. package/bizar-dash/src/web/mobile/views/MobileMore.tsx +121 -0
  188. package/bizar-dash/src/web/mobile/views/MobileNotifications.tsx +137 -0
  189. package/bizar-dash/src/web/mobile/views/MobilePlans.tsx +26 -0
  190. package/bizar-dash/src/web/mobile/views/MobileSchedules.tsx +685 -0
  191. package/bizar-dash/src/web/mobile/views/MobileSearchModal.tsx +172 -0
  192. package/bizar-dash/src/web/mobile/views/MobileSettings.tsx +388 -0
  193. package/bizar-dash/src/web/mobile/views/MobileSkills.tsx +153 -0
  194. package/bizar-dash/src/web/mobile/views/MobileTasks.tsx +564 -0
  195. package/bizar-dash/src/web/mobile.html +20 -0
  196. package/bizar-dash/src/web/mobile.tsx +13 -0
  197. package/bizar-dash/src/web/styles/chat.css +198 -0
  198. package/bizar-dash/src/web/styles/glyphs.css +960 -0
  199. package/bizar-dash/src/web/styles/main.css +8855 -0
  200. package/bizar-dash/src/web/styles/mobile-chat.css +5 -0
  201. package/bizar-dash/src/web/styles/mobile.css +1960 -0
  202. package/bizar-dash/src/web/views/Activity.tsx +1203 -0
  203. package/bizar-dash/src/web/views/Agents.tsx +701 -0
  204. package/bizar-dash/src/web/views/Artifacts.tsx +1480 -0
  205. package/bizar-dash/src/web/views/BackgroundAgents.tsx +450 -0
  206. package/bizar-dash/src/web/views/Chat.tsx +190 -0
  207. package/bizar-dash/src/web/views/Config.tsx +1603 -0
  208. package/bizar-dash/src/web/views/History.tsx +335 -0
  209. package/bizar-dash/src/web/views/ModView.tsx +207 -0
  210. package/bizar-dash/src/web/views/Mods.tsx +693 -0
  211. package/bizar-dash/src/web/views/Overview.tsx +812 -0
  212. package/bizar-dash/src/web/views/Providers.tsx +302 -0
  213. package/bizar-dash/src/web/views/Schedules.tsx +802 -0
  214. package/bizar-dash/src/web/views/Settings.tsx +1787 -0
  215. package/bizar-dash/src/web/views/Skills.tsx +431 -0
  216. package/bizar-dash/src/web/views/Tasks.tsx +1523 -0
  217. package/bizar-dash/src/web/views/glyphs/GlyphRenderer.tsx +613 -0
  218. package/bizar-dash/src/web/views/glyphs/components.tsx +1098 -0
  219. package/bizar-dash/templates/mod/FORMAT.md +76 -0
  220. package/bizar-dash/templates/mod/hello-mod/README.md +19 -0
  221. package/bizar-dash/templates/mod/hello-mod/agents/greeter.md +8 -0
  222. package/bizar-dash/templates/mod/hello-mod/commands/hello.md +6 -0
  223. package/bizar-dash/templates/mod/hello-mod/mod.json +20 -0
  224. package/bizar-dash/templates/mod/hello-mod/routes/ping.mjs +9 -0
  225. package/bizar-dash/templates/mod/hello-mod/views/HelloView.tsx +10 -0
  226. package/bizar-dash/tests/dashboard-ports.test.mjs +138 -0
  227. package/bizar-dash/tests/graphify-mod-spawn.node.test.mjs +90 -0
  228. package/bizar-dash/tests/memory-git.test.mjs +138 -0
  229. package/bizar-dash/tests/memory-schema.test.mjs +198 -0
  230. package/bizar-dash/tests/memory-secrets.test.mjs +121 -0
  231. package/bizar-dash/tests/memory-store.test.mjs +390 -0
  232. package/bizar-dash/tests/memory-sync.test.mjs +141 -0
  233. package/bizar-dash/tests/mod-instructions.node.test.mjs +188 -0
  234. package/bizar-dash/tests/mod-security.test.mjs +273 -0
  235. package/bizar-dash/tests/mod-upgrade.node.test.mjs +357 -0
  236. package/bizar-dash/tests/no-agent-browser.node.test.mjs +98 -0
  237. package/bizar-dash/tests/obsidian-back-compat.test.mjs +199 -0
  238. package/bizar-dash/tests/opencode-runner.test.mjs +172 -0
  239. package/bizar-dash/tests/path-safe.test.mjs +108 -0
  240. package/bizar-dash/tests/providers-store-backup-keys.node.test.mjs +333 -0
  241. package/bizar-dash/tests/smoke-v2.mjs +212 -0
  242. package/bizar-dash/tests/strip-thinking.test.mjs +175 -0
  243. package/bizar-dash/tests/submit-feedback.test.mjs +353 -0
  244. package/bizar-dash/tests/tmux-wrap.test.mjs +145 -0
  245. package/bizar-dash/tests/yaml.test.mjs +128 -0
  246. package/bizar-dash/tsconfig.json +23 -0
  247. package/bizar-dash/vite.config.ts +27 -0
  248. package/cli/atomic.mjs +73 -0
  249. package/cli/bin.mjs +43 -46
  250. package/cli/copy.mjs +3 -0
  251. package/cli/dev-link.mjs +17 -1
  252. package/cli/init.mjs +135 -1
  253. package/cli/install.mjs +10 -23
  254. package/cli/memory-constants.mjs +59 -0
  255. package/cli/memory.mjs +757 -0
  256. package/cli/service.mjs +5 -6
  257. package/config/skills/obsidian/SKILL.md +213 -65
  258. package/install.sh +48 -18
  259. package/package.json +49 -14
  260. package/packages/sdk/LICENSE +21 -0
  261. package/packages/sdk/README.md +80 -0
  262. package/packages/sdk/src/client.ts +183 -0
  263. package/packages/sdk/src/errors.ts +120 -0
  264. package/packages/sdk/src/events.ts +153 -0
  265. package/packages/sdk/src/index.ts +63 -0
  266. package/packages/sdk/src/types.ts +176 -0
  267. package/packages/sdk/src/version.ts +4 -0
  268. package/packages/sdk/tests/client.test.ts +217 -0
  269. package/packages/sdk/tests/errors.test.ts +108 -0
  270. package/packages/sdk/tests/events.test.ts +139 -0
  271. package/packages/sdk/tests/fixtures/fetch-mock.ts +152 -0
  272. package/packages/sdk/tests/fixtures/sse-mock.ts +30 -0
  273. package/packages/sdk/tsconfig.json +25 -0
  274. package/packages/sdk/vitest.config.ts +9 -0
  275. package/plugins/bizar/LICENSE +21 -0
  276. package/plugins/bizar/README.md +448 -0
  277. package/plugins/bizar/dist/index.js +29901 -0
  278. package/plugins/bizar/index.ts +1484 -0
  279. package/plugins/bizar/scripts/check-forbidden-imports.sh +33 -0
  280. package/plugins/bizar/src/background-state.ts +551 -0
  281. package/plugins/bizar/src/background.ts +1250 -0
  282. package/plugins/bizar/src/commands-impl.ts +464 -0
  283. package/plugins/bizar/src/commands.ts +1127 -0
  284. package/plugins/bizar/src/dashboard-client.ts +233 -0
  285. package/plugins/bizar/src/event-stream.ts +606 -0
  286. package/plugins/bizar/src/fingerprint.ts +120 -0
  287. package/plugins/bizar/src/handoff.ts +79 -0
  288. package/plugins/bizar/src/http-client.ts +467 -0
  289. package/plugins/bizar/src/key-rotation.ts +218 -0
  290. package/plugins/bizar/src/logger.ts +144 -0
  291. package/plugins/bizar/src/loop.ts +176 -0
  292. package/plugins/bizar/src/opencode-runner.ts +390 -0
  293. package/plugins/bizar/src/options.ts +421 -0
  294. package/plugins/bizar/src/plan-fs.ts +323 -0
  295. package/plugins/bizar/src/reasoning-clean.ts +454 -0
  296. package/plugins/bizar/src/report.ts +178 -0
  297. package/plugins/bizar/src/research-prompt.ts +35 -0
  298. package/plugins/bizar/src/serve-info.ts +228 -0
  299. package/plugins/bizar/src/serve.ts +496 -0
  300. package/plugins/bizar/src/settings.ts +349 -0
  301. package/plugins/bizar/src/state.ts +298 -0
  302. package/plugins/bizar/src/tools/bg-collect.ts +104 -0
  303. package/plugins/bizar/src/tools/bg-get-comments.ts +239 -0
  304. package/plugins/bizar/src/tools/bg-kill.ts +87 -0
  305. package/plugins/bizar/src/tools/bg-spawn.ts +401 -0
  306. package/plugins/bizar/src/tools/bg-status.ts +99 -0
  307. package/plugins/bizar/src/tools/plan-action.ts +767 -0
  308. package/plugins/bizar/src/tools/read-glyph-feedback.ts +170 -0
  309. package/plugins/bizar/src/tools/wait-for-feedback.ts +402 -0
  310. package/plugins/bizar/tests/attach-handler-bug.test.ts +169 -0
  311. package/plugins/bizar/tests/background-state.test.ts +277 -0
  312. package/plugins/bizar/tests/background.test.ts +402 -0
  313. package/plugins/bizar/tests/block.test.ts +195 -0
  314. package/plugins/bizar/tests/canonical-key-order.test.ts +75 -0
  315. package/plugins/bizar/tests/commands-impl.test.ts +442 -0
  316. package/plugins/bizar/tests/commands.test.ts +548 -0
  317. package/plugins/bizar/tests/config.test.ts +122 -0
  318. package/plugins/bizar/tests/dashboard-client.test.ts +159 -0
  319. package/plugins/bizar/tests/dispose.test.ts +336 -0
  320. package/plugins/bizar/tests/event-stream.test.ts +409 -0
  321. package/plugins/bizar/tests/event.test.ts +262 -0
  322. package/plugins/bizar/tests/fingerprint.test.ts +162 -0
  323. package/plugins/bizar/tests/http-client.test.ts +404 -0
  324. package/plugins/bizar/tests/init-helpers.test.ts +203 -0
  325. package/plugins/bizar/tests/integration/slash-command.test.ts +348 -0
  326. package/plugins/bizar/tests/integration/tool-routing.test.ts +314 -0
  327. package/plugins/bizar/tests/key-rotation.test.ts +396 -0
  328. package/plugins/bizar/tests/loop.test.ts +397 -0
  329. package/plugins/bizar/tests/options.test.ts +276 -0
  330. package/plugins/bizar/tests/reasoning-clean.test.ts +422 -0
  331. package/plugins/bizar/tests/serve.test.ts +339 -0
  332. package/plugins/bizar/tests/settings.test.ts +351 -0
  333. package/plugins/bizar/tests/stall-think.test.ts +750 -0
  334. package/plugins/bizar/tests/state.test.ts +276 -0
  335. package/plugins/bizar/tests/tools/bg-collect.test.ts +337 -0
  336. package/plugins/bizar/tests/tools/bg-get-comments.test.ts +485 -0
  337. package/plugins/bizar/tests/tools/bg-kill.test.ts +235 -0
  338. package/plugins/bizar/tests/tools/bg-spawn-delegation.test.ts +147 -0
  339. package/plugins/bizar/tests/tools/bg-spawn.test.ts +311 -0
  340. package/plugins/bizar/tests/tools/bg-status.test.ts +217 -0
  341. package/plugins/bizar/tests/tools/opencode-runner.test.ts +115 -0
  342. package/plugins/bizar/tests/tools/plan-action.test.ts +599 -0
  343. package/plugins/bizar/tests/tools/read-glyph-feedback.test.ts +253 -0
  344. package/plugins/bizar/tests/tools/wait-for-feedback.test.ts +390 -0
  345. package/plugins/bizar/tests/update-deadlock.test.ts +145 -0
  346. package/plugins/bizar/tsconfig.json +29 -0
@@ -0,0 +1,465 @@
1
+ /**
2
+ * src/server/agents-store.mjs
3
+ *
4
+ * v3.0.0 — Editable agents.
5
+ * v3.1.0 — Real-time agent status, stuck detection, tags + category,
6
+ * tags + category exposed via list().
7
+ *
8
+ * Each agent is a markdown file with frontmatter at:
9
+ * ~/.config/opencode/agents/<name>.md
10
+ *
11
+ * The store reads / writes these files. The format is:
12
+ * ---
13
+ * description: ...
14
+ * model: ...
15
+ * mode: ...
16
+ * color: ...
17
+ * tools: ["bash","read","edit"]
18
+ * tags: ["reasoning","code"]
19
+ * category: "reasoning"
20
+ * permissions: {...}
21
+ * ---
22
+ * <prompt body>
23
+ */
24
+ import {
25
+ existsSync,
26
+ readFileSync,
27
+ writeFileSync,
28
+ renameSync,
29
+ readdirSync,
30
+ statSync,
31
+ mkdirSync,
32
+ unlinkSync,
33
+ } from 'node:fs';
34
+ import { join, dirname, basename } from 'node:path';
35
+ import { homedir } from 'node:os';
36
+
37
+ const HOME = homedir();
38
+ const AGENTS_DIR = join(HOME, '.config', 'opencode', 'agents');
39
+
40
+ // v3.2.0 — agent hierarchy. Odin sits at the top as router; Tyr/Thor/
41
+ // Hermod/Baldr/Mimir report directly to Odin; Forseti is peer-to-Odin
42
+ // for audits; Vidarr is the ultimate fallback. Level 0 = router,
43
+ // 1 = coordinator, 2 = worker, 3 = fallback.
44
+ const HIERARCHY = {
45
+ odin: { level: 0, parent: null, role: 'router' },
46
+ forseti: { level: 0, parent: null, role: 'auditor' },
47
+ tyr: { level: 1, parent: 'odin', role: 'implementer' },
48
+ thor: { level: 1, parent: 'odin', role: 'implementer' },
49
+ hermod: { level: 1, parent: 'odin', role: 'gitops' },
50
+ baldr: { level: 1, parent: 'odin', role: 'designer' },
51
+ mimir: { level: 1, parent: 'odin', role: 'researcher' },
52
+ heimdall: { level: 2, parent: 'thor', role: 'executor' },
53
+ frigg: { level: 2, parent: 'mimir', role: 'qa' },
54
+ vor: { level: 2, parent: 'mimir', role: 'clarifier' },
55
+ quick: { level: 2, parent: null, role: 'quick' },
56
+ 'semble-search': { level: 2, parent: 'mimir', role: 'search' },
57
+ vidarr: { level: 3, parent: null, role: 'fallback' },
58
+ };
59
+
60
+ // v3.1.0 — Runtime agent status. Persisted in memory; flushed to
61
+ // ~/.config/bizar/agent-status.json so the dashboard can show real
62
+ // activity even after a restart. This is the single source of truth
63
+ // for "is this agent currently working on a task?".
64
+ const STATUS_FILE = join(HOME, '.config', 'bizar', 'agent-status.json');
65
+ const _status = new Map(); // name -> { status, currentTaskId, lastSeen, heartbeat, currentTaskStartedAt, lastError }
66
+ let _statusLoaded = false;
67
+
68
+ function loadStatus() {
69
+ if (_statusLoaded) return;
70
+ _statusLoaded = true;
71
+ try {
72
+ if (existsSync(STATUS_FILE)) {
73
+ const raw = JSON.parse(readFileSync(STATUS_FILE, 'utf8'));
74
+ for (const [name, s] of Object.entries(raw || {})) {
75
+ _status.set(name, s);
76
+ }
77
+ }
78
+ } catch {
79
+ /* ignore */
80
+ }
81
+ }
82
+
83
+ function saveStatus() {
84
+ try {
85
+ mkdirSync(dirname(STATUS_FILE), { recursive: true });
86
+ const obj = {};
87
+ for (const [k, v] of _status.entries()) obj[k] = v;
88
+ const tmp = `${STATUS_FILE}.tmp.${process.pid}`;
89
+ writeFileSync(tmp, JSON.stringify(obj, null, 2) + '\n', 'utf8');
90
+ renameSync(tmp, STATUS_FILE);
91
+ } catch {
92
+ /* best effort */
93
+ }
94
+ }
95
+
96
+ function defaultStatus(name) {
97
+ return {
98
+ status: 'idle',
99
+ currentTaskId: null,
100
+ lastSeen: 0,
101
+ heartbeat: 0,
102
+ currentTaskStartedAt: 0,
103
+ lastError: null,
104
+ lastTask: null,
105
+ tasksTotal: 0,
106
+ tasksSucceeded: 0,
107
+ tasksFailed: 0,
108
+ successRate: 0,
109
+ };
110
+ }
111
+
112
+ function safeReadText(file, fallback = '') {
113
+ try {
114
+ if (!existsSync(file)) return fallback;
115
+ return readFileSync(file, 'utf8');
116
+ } catch {
117
+ return fallback;
118
+ }
119
+ }
120
+
121
+ function parseFrontmatter(raw) {
122
+ if (!raw.startsWith('---')) return { frontmatter: {}, body: raw };
123
+ const end = raw.indexOf('\n---', 3);
124
+ if (end === -1) return { frontmatter: {}, body: raw };
125
+ const fmBlock = raw.slice(3, end).trim();
126
+ const body = raw.slice(end + 4).replace(/^\s+/, '');
127
+ const frontmatter = {};
128
+ for (const line of fmBlock.split(/\r?\n/)) {
129
+ const m = /^([A-Za-z0-9_-]+)\s*:\s*(.*)$/.exec(line);
130
+ if (!m) continue;
131
+ const key = m[1];
132
+ let val = m[2].trim();
133
+ if (
134
+ (val.startsWith('"') && val.endsWith('"')) ||
135
+ (val.startsWith("'") && val.endsWith("'"))
136
+ ) {
137
+ val = val.slice(1, -1);
138
+ }
139
+ if (val.startsWith('[') && val.endsWith(']')) {
140
+ // v3.1.0 — Inline array: [a, "b", c]
141
+ const inner = val.slice(1, -1).trim();
142
+ frontmatter[key] = inner
143
+ ? inner
144
+ .split(',')
145
+ .map((x) => x.trim().replace(/^['"]|['"]$/g, ''))
146
+ .filter(Boolean)
147
+ : [];
148
+ } else {
149
+ frontmatter[key] = val;
150
+ }
151
+ }
152
+ return { frontmatter, body };
153
+ }
154
+
155
+ /** Serialize a frontmatter key — string is bare, value with special chars gets quoted. */
156
+ function fmVal(v) {
157
+ if (typeof v !== 'string') return JSON.stringify(v);
158
+ if (v === '') return '""';
159
+ if (/[:#\-?{}[\],&*!|>'"%@`]/.test(v) || v.includes(' ')) {
160
+ return JSON.stringify(v);
161
+ }
162
+ return v;
163
+ }
164
+
165
+ function serializeFrontmatter(fm) {
166
+ const lines = ['---'];
167
+ for (const [k, v] of Object.entries(fm)) {
168
+ if (v == null) continue;
169
+ if (Array.isArray(v)) {
170
+ lines.push(`${k}: [${v.map((x) => (typeof x === 'string' ? JSON.stringify(x) : x)).join(', ')}]`);
171
+ } else if (typeof v === 'object') {
172
+ lines.push(`${k}:`);
173
+ for (const [k2, v2] of Object.entries(v)) {
174
+ lines.push(` ${k2}: ${fmVal(String(v2))}`);
175
+ }
176
+ } else {
177
+ lines.push(`${k}: ${fmVal(String(v))}`);
178
+ }
179
+ }
180
+ lines.push('---');
181
+ return lines.join('\n') + '\n';
182
+ }
183
+
184
+ function readAgent(name) {
185
+ const file = join(AGENTS_DIR, `${name}.md`);
186
+ if (!existsSync(file)) return null;
187
+ const raw = safeReadText(file);
188
+ const { frontmatter, body } = parseFrontmatter(raw);
189
+ const st = statSync(file);
190
+ const status = _status.get(name) || defaultStatus(name);
191
+ return {
192
+ name,
193
+ description: frontmatter.description || '',
194
+ model: frontmatter.model || '',
195
+ mode: frontmatter.mode || 'subagent',
196
+ color: frontmatter.color || '',
197
+ tools: typeof frontmatter.tools === 'string'
198
+ ? frontmatter.tools.split(',').map((s) => s.trim()).filter(Boolean)
199
+ : Array.isArray(frontmatter.tools) ? frontmatter.tools : [],
200
+ tags: typeof frontmatter.tags === 'string'
201
+ ? frontmatter.tags.split(',').map((s) => s.trim()).filter(Boolean)
202
+ : Array.isArray(frontmatter.tags) ? frontmatter.tags : [],
203
+ category: frontmatter.category || '',
204
+ permissions: frontmatter.permissions || null,
205
+ prompt: body.trim(),
206
+ file,
207
+ path: file,
208
+ mtime: st.mtimeMs,
209
+ // v3.1.0 — runtime status, attached to the read-only snapshot.
210
+ status: status.status,
211
+ currentTaskId: status.currentTaskId || null,
212
+ currentTaskStartedAt: status.currentTaskStartedAt || 0,
213
+ lastSeen: status.lastSeen || 0,
214
+ heartbeat: status.heartbeat || 0,
215
+ lastError: status.lastError || null,
216
+ lastTask: status.lastTask || null,
217
+ successRate: status.successRate || 0,
218
+ tasksTotal: status.tasksTotal || 0,
219
+ tasksSucceeded: status.tasksSucceeded || 0,
220
+ tasksFailed: status.tasksFailed || 0,
221
+ isStuck: isStuck(status),
222
+ // v3.2.0 — hierarchy metadata.
223
+ level: HIERARCHY[name]?.level ?? 2,
224
+ parent: HIERARCHY[name]?.parent ?? null,
225
+ role: HIERARCHY[name]?.role ?? 'unknown',
226
+ };
227
+ }
228
+
229
+ /** Build a tree representation of the agent hierarchy. */
230
+ export function buildHierarchyTree(agents) {
231
+ const byParent = new Map();
232
+ for (const a of agents) {
233
+ const key = a.parent || '__root__';
234
+ if (!byParent.has(key)) byParent.set(key, []);
235
+ byParent.get(key).push(a);
236
+ }
237
+ const seen = new Set();
238
+ function node(name) {
239
+ if (seen.has(name)) return null;
240
+ seen.add(name);
241
+ const agent = agents.find((a) => a.name === name) || null;
242
+ const children = (byParent.get(name) || []).map((c) => node(c.name)).filter(Boolean);
243
+ return {
244
+ name,
245
+ agent,
246
+ children,
247
+ };
248
+ }
249
+ const roots = (byParent.get('__root__') || []).map((a) => node(a.name)).filter(Boolean);
250
+ return { roots, all: agents };
251
+ }
252
+
253
+ export const agentsStore = {
254
+ AGENTS_DIR,
255
+
256
+ ensure() {
257
+ mkdirSync(AGENTS_DIR, { recursive: true });
258
+ },
259
+
260
+ list() {
261
+ this.ensure();
262
+ const out = [];
263
+ for (const f of readdirSync(AGENTS_DIR)) {
264
+ if (!f.endsWith('.md')) continue;
265
+ const name = basename(f, '.md');
266
+ const agent = readAgent(name);
267
+ if (agent) out.push(agent);
268
+ }
269
+ out.sort((a, b) => a.name.localeCompare(b.name));
270
+ return out;
271
+ },
272
+
273
+ get(name) {
274
+ return readAgent(name);
275
+ },
276
+
277
+ create(input) {
278
+ if (!input || typeof input !== 'object') throw new Error('agent input required');
279
+ if (!input.name || !/^[a-z0-9][a-z0-9-]{0,63}$/i.test(input.name)) {
280
+ throw new Error('invalid agent name');
281
+ }
282
+ const file = join(AGENTS_DIR, `${input.name}.md`);
283
+ if (existsSync(file)) {
284
+ throw new Error(`agent "${input.name}" already exists`);
285
+ }
286
+ const data = {
287
+ name: input.name,
288
+ description: input.description || '',
289
+ model: input.model || '',
290
+ mode: input.mode || 'subagent',
291
+ color: input.color || '',
292
+ tools: Array.isArray(input.tools) ? input.tools : [],
293
+ tags: Array.isArray(input.tags) ? input.tags : [],
294
+ category: input.category || '',
295
+ permissions: input.permissions || null,
296
+ prompt: input.prompt || '',
297
+ };
298
+ this.ensure();
299
+ writeFileSync(file, serializeFrontmatter(data) + '\n' + data.prompt, 'utf8');
300
+ return readAgent(input.name);
301
+ },
302
+
303
+ update(name, patch) {
304
+ if (!patch || typeof patch !== 'object') throw new Error('agent patch required');
305
+ const cur = readAgent(name);
306
+ if (!cur) throw new Error(`agent "${name}" not found`);
307
+ const data = {
308
+ description: patch.description ?? cur.description,
309
+ model: patch.model ?? cur.model,
310
+ mode: patch.mode ?? cur.mode,
311
+ color: patch.color ?? cur.color,
312
+ tools: Array.isArray(patch.tools) ? patch.tools : cur.tools,
313
+ tags: Array.isArray(patch.tags) ? patch.tags : (cur.tags || []),
314
+ category: patch.category ?? cur.category ?? '',
315
+ permissions: patch.permissions ?? cur.permissions,
316
+ prompt: patch.prompt ?? cur.prompt,
317
+ };
318
+ writeFileSync(cur.file, serializeFrontmatter(data) + '\n' + data.prompt, 'utf8');
319
+ return readAgent(name);
320
+ },
321
+
322
+ delete(name) {
323
+ const file = join(AGENTS_DIR, `${name}.md`);
324
+ if (!existsSync(file)) return false;
325
+ unlinkSync(file);
326
+ _status.delete(name);
327
+ saveStatus();
328
+ return true;
329
+ },
330
+
331
+ /**
332
+ * v3.1.0 — Update the runtime status for a single agent. Used by
333
+ * the opencode plugin (via POST /api/agents/:name/status) and the
334
+ * task lifecycle (when an agent starts/finishes a task).
335
+ *
336
+ * Status: 'idle' | 'working' | 'error' | 'stuck'.
337
+ */
338
+ updateStatus(name, status, currentTaskId = null) {
339
+ loadStatus();
340
+ const prev = _status.get(name) || defaultStatus(name);
341
+ const now = Date.now();
342
+ const next = { ...prev };
343
+ if (status) next.status = status;
344
+ if (currentTaskId !== undefined) next.currentTaskId = currentTaskId;
345
+ next.lastSeen = now;
346
+ next.heartbeat = now;
347
+ if (status === 'working' && currentTaskId && currentTaskId !== prev.currentTaskId) {
348
+ next.currentTaskStartedAt = now;
349
+ }
350
+ if (status === 'idle' || status === 'error' || status === 'stuck') {
351
+ if (prev.currentTaskId) {
352
+ next.lastTask = {
353
+ id: prev.currentTaskId,
354
+ finishedAt: now,
355
+ status,
356
+ };
357
+ }
358
+ next.currentTaskId = null;
359
+ next.currentTaskStartedAt = 0;
360
+ }
361
+ if (status === 'error') {
362
+ next.lastError = { ts: now, message: typeof currentTaskId === 'string' ? currentTaskId : 'error' };
363
+ }
364
+ _status.set(name, next);
365
+ saveStatus();
366
+ return readAgent(name);
367
+ },
368
+
369
+ /** Heartbeat ping — keeps lastSeen fresh without changing status. */
370
+ heartbeat(name) {
371
+ loadStatus();
372
+ const cur = _status.get(name) || defaultStatus(name);
373
+ cur.heartbeat = Date.now();
374
+ cur.lastSeen = cur.heartbeat;
375
+ _status.set(name, cur);
376
+ saveStatus();
377
+ return readAgent(name);
378
+ },
379
+
380
+ /**
381
+ * Mark that an agent finished a task. Bumps the success/failure
382
+ * counters and clears the currentTaskId.
383
+ */
384
+ recordTaskResult(name, taskId, ok) {
385
+ loadStatus();
386
+ const cur = _status.get(name) || defaultStatus(name);
387
+ cur.tasksTotal += 1;
388
+ if (ok) cur.tasksSucceeded += 1;
389
+ else cur.tasksFailed += 1;
390
+ cur.successRate = cur.tasksTotal > 0 ? cur.tasksSucceeded / cur.tasksTotal : 0;
391
+ cur.lastTask = { id: taskId, finishedAt: Date.now(), status: ok ? 'success' : 'failed' };
392
+ cur.currentTaskId = null;
393
+ cur.currentTaskStartedAt = 0;
394
+ cur.status = 'idle';
395
+ cur.lastSeen = Date.now();
396
+ _status.set(name, cur);
397
+ saveStatus();
398
+ return readAgent(name);
399
+ },
400
+
401
+ /** Get raw status for a single agent. */
402
+ getStatus(name) {
403
+ loadStatus();
404
+ return _status.get(name) || defaultStatus(name);
405
+ },
406
+
407
+ /** Return the list of stuck agents (used by /api/agents/stuck). */
408
+ stuck() {
409
+ loadStatus();
410
+ const out = [];
411
+ for (const [name, s] of _status.entries()) {
412
+ if (isStuck(s)) {
413
+ out.push({ name, ...s });
414
+ }
415
+ }
416
+ return out;
417
+ },
418
+
419
+ /**
420
+ * v3.1.0 — "Restart" an agent. For this release this resets the
421
+ * runtime status (clears the current task, marks idle) and removes
422
+ * the stuck flag. A real process restart is documented as v3.2.
423
+ */
424
+ restart(name) {
425
+ loadStatus();
426
+ const cur = _status.get(name) || defaultStatus(name);
427
+ cur.status = 'idle';
428
+ cur.currentTaskId = null;
429
+ cur.currentTaskStartedAt = 0;
430
+ cur.lastSeen = Date.now();
431
+ cur.heartbeat = Date.now();
432
+ cur.lastError = null;
433
+ _status.set(name, cur);
434
+ saveStatus();
435
+ return readAgent(name);
436
+ },
437
+
438
+ STATUS_FILE,
439
+ };
440
+
441
+ // ── v3.1.0 — Stuck detection ────────────────────────────────────────
442
+ // A "working" agent is stuck if it has been on a single task for
443
+ // longer than STUCK_WORKING_MS without a heartbeat. An "idle" agent
444
+ // with a queued task is stuck if lastSeen is older than
445
+ // STUCK_IDLE_MS. Both thresholds are intentionally conservative
446
+ // for v3.1.0; settings overrides land in v3.2.
447
+
448
+ const STUCK_WORKING_MS = 10 * 60 * 1000; // 10 min
449
+ const STUCK_IDLE_MS = 5 * 60 * 1000; // 5 min
450
+
451
+ function isStuck(status) {
452
+ if (!status) return false;
453
+ const now = Date.now();
454
+ if (status.status === 'working' && status.currentTaskStartedAt) {
455
+ const elapsed = now - status.currentTaskStartedAt;
456
+ if (elapsed > STUCK_WORKING_MS) return true;
457
+ }
458
+ if (status.status === 'error' && status.lastError?.ts) {
459
+ const elapsed = now - new Date(status.lastError.ts).getTime();
460
+ if (elapsed > STUCK_WORKING_MS) return true;
461
+ }
462
+ return false;
463
+ }
464
+
465
+ export { isStuck };
@@ -0,0 +1,138 @@
1
+ /**
2
+ * src/server/api.mjs
3
+ *
4
+ * v3.6.0 — Thin composer for the dashboard REST surface.
5
+ *
6
+ * The previous api.mjs was a 2,395-line monolith; this file now just
7
+ * composes the per-domain routers in routes/*. Each router is created
8
+ * via a factory that receives shared dependencies (state, broadcast,
9
+ * projectRoot, watcher), so they can be developed and tested in
10
+ * isolation.
11
+ *
12
+ * Router order matters for Express — paths registered earlier win
13
+ * the match. Within each router file the ordering constraints are
14
+ * preserved (e.g. /tasks/bulk before /tasks/:id) and the composition
15
+ * below mirrors the original monolith's top-level ordering so that
16
+ * route precedence is unchanged.
17
+ *
18
+ * v3.24.0 — Exception: the memory router is lazy-imported with
19
+ * `await import('./routes/memory.mjs')` below instead of statically
20
+ * listed in the import block. This lets dashboards boot before the
21
+ * memory routes land during a parallel implementation rollout.
22
+ */
23
+ import express from 'express';
24
+ import { pairStore } from './pair-store.mjs';
25
+
26
+ import { createAuthRouter } from './routes/auth.mjs';
27
+ import { createOverviewRouter } from './routes/overview.mjs';
28
+ import { createProjectsRouter } from './routes/projects.mjs';
29
+ import { createFsRouter } from './routes/fs.mjs';
30
+ import { createTasksRouter } from './routes/tasks.mjs';
31
+ import { createArtifactsRouter } from './routes/artifacts.mjs';
32
+ import { createSchedulesRouter } from './routes/schedules.mjs';
33
+ import { createModsRouter } from './routes/mods.mjs';
34
+ import { createAgentsRouter } from './routes/agents.mjs';
35
+ import { createBackgroundRouter } from './routes/background.mjs';
36
+ import { createActivityRouter } from './routes/activity.mjs';
37
+ import { createHistoryRouter } from './routes/history.mjs';
38
+ import { createConfigRouter } from './routes/config.mjs';
39
+ import { createProvidersRouter } from './routes/providers.mjs';
40
+ import { createSettingsRouter } from './routes/settings.mjs';
41
+ import { createChatRouter } from './routes/chat.mjs';
42
+ import { createOpencodeSessionsRouter } from './routes/opencode-sessions.mjs';
43
+ import { createDialogsRouter } from './routes/dialogs.mjs';
44
+ import { createSkillsRouter } from './routes/skills.mjs';
45
+ import { createObsidianRouter } from './routes/obsidian.mjs';
46
+ import { createDiagnosticsRouter } from './routes/diagnostics.mjs';
47
+ import { createPairRouter } from './routes/pair.mjs';
48
+ import { createThemesRouter } from './routes/themes.mjs';
49
+ import { createNotificationsRouter } from './routes/notifications.mjs';
50
+ import { createMiscRouter } from './routes/misc.mjs';
51
+
52
+ /**
53
+ * @param {object} deps
54
+ * @param {object} deps.state
55
+ * @param {object} deps.watcher
56
+ * @param {string} deps.projectRoot
57
+ * @param {string} deps.opencodeConfigDir
58
+ * @param {string} deps.bizarRoot
59
+ * @param {Function} [deps.broadcast]
60
+ * @returns {import('express').Router}
61
+ */
62
+ export function createApiRouter({
63
+ state,
64
+ watcher,
65
+ projectRoot,
66
+ opencodeConfigDir,
67
+ bizarRoot,
68
+ broadcast = () => {},
69
+ }) {
70
+ const router = express.Router();
71
+
72
+ // v3.5.2 — Pair-token enrichment middleware. Does NOT block requests; the
73
+ // dashboard itself is unauthenticated. This just tags requests with
74
+ // req.pairToken / req.pairEntry so future handlers can recognize paired
75
+ // companion clients and (later) scope them to a single project.
76
+ router.use(pairStore.middleware);
77
+
78
+ // Compose each domain router in the order the original monolith
79
+ // declared them. Order is mostly cosmetic — Express path patterns
80
+ // don't overlap across domains — but it keeps the route table
81
+ // grep-able by anyone familiar with the original file.
82
+ router.use(createOverviewRouter({ state }));
83
+ router.use(createProjectsRouter({ state, broadcast, projectRoot }));
84
+ // v3.6.0 — Mounted right after projects so the file-browser endpoint
85
+ // is logically grouped with the project-management surface. Reads
86
+ // settings via the shared helper; needs no broadcast/projectRoot.
87
+ router.use(createFsRouter({ state }));
88
+ router.use(createTasksRouter({ state, broadcast, projectRoot }));
89
+ // v3.20.14 — pass projectRoot to the artifacts router so /api/artifacts
90
+ // returns the worktree artifacts (not just the global fallback).
91
+ // Previously this router was mounted without projectRoot, which meant
92
+ // artifactsStore.list(undefined) only checked ~/.config/opencode/artifacts
93
+ // (always empty for projects that have an artifacts/ folder) and the
94
+ // UI showed "0 artifacts" even though the snapshot had the full list.
95
+ router.use(createArtifactsRouter({ state, broadcast, projectRoot }));
96
+ router.use(createSchedulesRouter({ broadcast }));
97
+ router.use(createModsRouter());
98
+ router.use(createAgentsRouter({ state, broadcast }));
99
+ router.use(createBackgroundRouter({ broadcast }));
100
+ router.use(createActivityRouter({ state }));
101
+ router.use(createHistoryRouter({ projectRoot }));
102
+ router.use(createConfigRouter({ state, watcher }));
103
+ router.use(createProvidersRouter());
104
+ router.use(createSettingsRouter({ state, broadcast }));
105
+ router.use(createChatRouter({ state, broadcast }));
106
+ router.use(createOpencodeSessionsRouter());
107
+ router.use(createDialogsRouter({ broadcast }));
108
+ router.use(createSkillsRouter({ broadcast }));
109
+ router.use(createObsidianRouter({ projectRoot }));
110
+ // v3.24.0 — Bizar Memory Service (Phase 1). Lazy-imported so that if Thor's
111
+ // memory routes haven't landed yet, the dashboard still boots.
112
+ const { createMemoryRouter } = await import('./routes/memory.mjs');
113
+ router.use(createMemoryRouter({ projectRoot }));
114
+ router.use(createDiagnosticsRouter());
115
+ router.use(createPairRouter({ state, broadcast }));
116
+ router.use(createThemesRouter({ state }));
117
+ router.use(createNotificationsRouter({ broadcast }));
118
+ router.use(createArtifactsRouter({ state, broadcast, projectRoot }));
119
+ router.use(createMiscRouter({ state, broadcast }));
120
+
121
+ // /api/auth/* must be reachable WITHOUT the bearer token so a fresh
122
+ // client can probe whether auth is on. server.mjs adds requireAuth()
123
+ // as middleware ABOVE this router, so this is the one path the
124
+ // middleware's skipPaths lets through.
125
+ router.use(createAuthRouter());
126
+
127
+ // 404 catch-all for unknown /api paths. Returns the same shape the
128
+ // /api router has always returned so the frontend's error handler
129
+ // doesn't need to know which router rejected the request.
130
+ router.use((req, res) => {
131
+ res.status(404).json({
132
+ error: 'not_found',
133
+ message: `no route for ${req.method} ${req.originalUrl}`,
134
+ });
135
+ });
136
+
137
+ return router;
138
+ }