@polderlabs/bizar 3.23.1 → 4.2.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 (361) hide show
  1. package/README.md +83 -16
  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 +329 -0
  60. package/bizar-dash/src/server/memory-lightrag.mjs +767 -0
  61. package/bizar-dash/src/server/memory-schema.mjs +145 -0
  62. package/bizar-dash/src/server/memory-secrets.mjs +84 -0
  63. package/bizar-dash/src/server/memory-store.mjs +471 -0
  64. package/bizar-dash/src/server/mod-security.mjs +398 -0
  65. package/bizar-dash/src/server/mods-loader.mjs +1067 -0
  66. package/bizar-dash/src/server/notifications-store.mjs +243 -0
  67. package/bizar-dash/src/server/obsidian-store.mjs +318 -0
  68. package/bizar-dash/src/server/opencode-runner.mjs +279 -0
  69. package/bizar-dash/src/server/pair-store.mjs +138 -0
  70. package/bizar-dash/src/server/projects-store.mjs +364 -0
  71. package/bizar-dash/src/server/providers-store.mjs +789 -0
  72. package/bizar-dash/src/server/routes/_shared.mjs +415 -0
  73. package/bizar-dash/src/server/routes/activity.mjs +110 -0
  74. package/bizar-dash/src/server/routes/agents.mjs +134 -0
  75. package/bizar-dash/src/server/routes/artifacts.mjs +279 -0
  76. package/bizar-dash/src/server/routes/auth.mjs +69 -0
  77. package/bizar-dash/src/server/routes/background.mjs +165 -0
  78. package/bizar-dash/src/server/routes/chat.mjs +485 -0
  79. package/bizar-dash/src/server/routes/config.mjs +129 -0
  80. package/bizar-dash/src/server/routes/diagnostics.mjs +50 -0
  81. package/bizar-dash/src/server/routes/dialogs.mjs +86 -0
  82. package/bizar-dash/src/server/routes/fs.mjs +429 -0
  83. package/bizar-dash/src/server/routes/history.mjs +78 -0
  84. package/bizar-dash/src/server/routes/memory.mjs +658 -0
  85. package/bizar-dash/src/server/routes/misc.mjs +89 -0
  86. package/bizar-dash/src/server/routes/mods.mjs +359 -0
  87. package/bizar-dash/src/server/routes/notifications.mjs +54 -0
  88. package/bizar-dash/src/server/routes/obsidian.mjs +239 -0
  89. package/bizar-dash/src/server/routes/opencode-sessions.mjs +55 -0
  90. package/bizar-dash/src/server/routes/overview.mjs +91 -0
  91. package/bizar-dash/src/server/routes/pair.mjs +67 -0
  92. package/bizar-dash/src/server/routes/projects.mjs +175 -0
  93. package/bizar-dash/src/server/routes/providers.mjs +47 -0
  94. package/bizar-dash/src/server/routes/schedules.mjs +104 -0
  95. package/bizar-dash/src/server/routes/settings.mjs +51 -0
  96. package/bizar-dash/src/server/routes/skills.mjs +76 -0
  97. package/bizar-dash/src/server/routes/tasks.mjs +468 -0
  98. package/bizar-dash/src/server/routes/themes.mjs +51 -0
  99. package/bizar-dash/src/server/routes-v2/auth.mjs +59 -0
  100. package/bizar-dash/src/server/routes-v2/events.mjs +107 -0
  101. package/bizar-dash/src/server/routes-v2/health.mjs +21 -0
  102. package/bizar-dash/src/server/routes-v2/index.mjs +90 -0
  103. package/bizar-dash/src/server/routes-v2/sessions.mjs +111 -0
  104. package/bizar-dash/src/server/schedules-runner.mjs +369 -0
  105. package/bizar-dash/src/server/schedules-store.mjs +268 -0
  106. package/bizar-dash/src/server/search-store.mjs +220 -0
  107. package/bizar-dash/src/server/serve-info.mjs +684 -0
  108. package/bizar-dash/src/server/server.mjs +796 -0
  109. package/bizar-dash/src/server/settings-store.mjs +45 -0
  110. package/bizar-dash/src/server/skills-store.mjs +300 -0
  111. package/bizar-dash/src/server/state.mjs +437 -0
  112. package/bizar-dash/src/server/tailscale-store.mjs +113 -0
  113. package/bizar-dash/src/server/task-delegator.mjs +824 -0
  114. package/bizar-dash/src/server/tasks-store.mjs +508 -0
  115. package/bizar-dash/src/server/tui.mjs +923 -0
  116. package/bizar-dash/src/server/update-store.mjs +168 -0
  117. package/bizar-dash/src/server/v2-auth-file.mjs +99 -0
  118. package/bizar-dash/src/server/v2-event-bus.mjs +128 -0
  119. package/bizar-dash/src/server/watcher.mjs +81 -0
  120. package/bizar-dash/src/server/yaml.mjs +238 -0
  121. package/bizar-dash/src/web/App.tsx +718 -0
  122. package/bizar-dash/src/web/MobileApp.tsx +408 -0
  123. package/bizar-dash/src/web/components/ArtifactCreateDialog.tsx +95 -0
  124. package/bizar-dash/src/web/components/ArtifactListDialog.tsx +55 -0
  125. package/bizar-dash/src/web/components/ArtifactViewer.tsx +143 -0
  126. package/bizar-dash/src/web/components/AuditDialog.tsx +79 -0
  127. package/bizar-dash/src/web/components/BgStatusBadge.tsx +32 -0
  128. package/bizar-dash/src/web/components/Button.tsx +56 -0
  129. package/bizar-dash/src/web/components/CanvasContextMenu.tsx +68 -0
  130. package/bizar-dash/src/web/components/Card.tsx +40 -0
  131. package/bizar-dash/src/web/components/CollapsibleSection.tsx +72 -0
  132. package/bizar-dash/src/web/components/CommandDialog.tsx +66 -0
  133. package/bizar-dash/src/web/components/EmptyState.tsx +30 -0
  134. package/bizar-dash/src/web/components/FileBrowser.tsx +633 -0
  135. package/bizar-dash/src/web/components/HelpDialog.tsx +55 -0
  136. package/bizar-dash/src/web/components/KillConfirmDialog.tsx +88 -0
  137. package/bizar-dash/src/web/components/Modal.tsx +215 -0
  138. package/bizar-dash/src/web/components/Notifications.tsx +241 -0
  139. package/bizar-dash/src/web/components/SearchModal.tsx +224 -0
  140. package/bizar-dash/src/web/components/Sidebar.tsx +66 -0
  141. package/bizar-dash/src/web/components/Spinner.tsx +19 -0
  142. package/bizar-dash/src/web/components/StatusBadge.tsx +25 -0
  143. package/bizar-dash/src/web/components/Tag.tsx +28 -0
  144. package/bizar-dash/src/web/components/Toast.tsx +150 -0
  145. package/bizar-dash/src/web/components/Topbar.tsx +368 -0
  146. package/bizar-dash/src/web/components/VisualPlanDialog.tsx +58 -0
  147. package/bizar-dash/src/web/components/chat/AgentChip.tsx +68 -0
  148. package/bizar-dash/src/web/components/chat/ChatBubble.tsx +60 -0
  149. package/bizar-dash/src/web/components/chat/ChatThread.tsx +88 -0
  150. package/bizar-dash/src/web/components/chat/ChatTopBar.tsx +50 -0
  151. package/bizar-dash/src/web/components/chat/Composer.tsx +164 -0
  152. package/bizar-dash/src/web/components/chat/ConfirmModal.tsx +54 -0
  153. package/bizar-dash/src/web/components/chat/EmptyState.tsx +21 -0
  154. package/bizar-dash/src/web/components/chat/FirstRunGreeting.tsx +18 -0
  155. package/bizar-dash/src/web/components/chat/FloatingComposer.tsx +32 -0
  156. package/bizar-dash/src/web/components/chat/InfoPanel.tsx +119 -0
  157. package/bizar-dash/src/web/components/chat/LoadingSkeleton.tsx +19 -0
  158. package/bizar-dash/src/web/components/chat/MessageBubble.tsx +60 -0
  159. package/bizar-dash/src/web/components/chat/SessionList.tsx +153 -0
  160. package/bizar-dash/src/web/components/chat/StreamingIndicator.tsx +9 -0
  161. package/bizar-dash/src/web/components/chat/SuggestionCards.tsx +55 -0
  162. package/bizar-dash/src/web/components/chat/WelcomeScreen.tsx +36 -0
  163. package/bizar-dash/src/web/components/chat/index.ts +20 -0
  164. package/bizar-dash/src/web/components/chat/useAutoGrowTextarea.ts +18 -0
  165. package/bizar-dash/src/web/components/chat/useChat.ts +219 -0
  166. package/bizar-dash/src/web/components/chat/useSlashCommands.ts +47 -0
  167. package/bizar-dash/src/web/index.html +37 -0
  168. package/bizar-dash/src/web/lib/api.ts +193 -0
  169. package/bizar-dash/src/web/lib/markdown.tsx +59 -0
  170. package/bizar-dash/src/web/lib/types.ts +668 -0
  171. package/bizar-dash/src/web/lib/utils.ts +114 -0
  172. package/bizar-dash/src/web/lib/ws.ts +176 -0
  173. package/bizar-dash/src/web/main.tsx +34 -0
  174. package/bizar-dash/src/web/mobile/MobileApp.tsx +278 -0
  175. package/bizar-dash/src/web/mobile/MobileBottomNav.tsx +38 -0
  176. package/bizar-dash/src/web/mobile/MobileTopbar.tsx +115 -0
  177. package/bizar-dash/src/web/mobile/components/MobileBottomSheet.tsx +182 -0
  178. package/bizar-dash/src/web/mobile/components/MobileListItem.tsx +64 -0
  179. package/bizar-dash/src/web/mobile/components/MobileModal.tsx +123 -0
  180. package/bizar-dash/src/web/mobile/views/MobileActivity.tsx +273 -0
  181. package/bizar-dash/src/web/mobile/views/MobileAgents.tsx +374 -0
  182. package/bizar-dash/src/web/mobile/views/MobileArtifactCanvas.tsx +439 -0
  183. package/bizar-dash/src/web/mobile/views/MobileArtifacts.tsx +143 -0
  184. package/bizar-dash/src/web/mobile/views/MobileChat.tsx +180 -0
  185. package/bizar-dash/src/web/mobile/views/MobileConfig.tsx +175 -0
  186. package/bizar-dash/src/web/mobile/views/MobileHistory.tsx +191 -0
  187. package/bizar-dash/src/web/mobile/views/MobileMods.tsx +149 -0
  188. package/bizar-dash/src/web/mobile/views/MobileMore.tsx +121 -0
  189. package/bizar-dash/src/web/mobile/views/MobileNotifications.tsx +137 -0
  190. package/bizar-dash/src/web/mobile/views/MobilePlans.tsx +26 -0
  191. package/bizar-dash/src/web/mobile/views/MobileSchedules.tsx +685 -0
  192. package/bizar-dash/src/web/mobile/views/MobileSearchModal.tsx +172 -0
  193. package/bizar-dash/src/web/mobile/views/MobileSettings.tsx +388 -0
  194. package/bizar-dash/src/web/mobile/views/MobileSkills.tsx +153 -0
  195. package/bizar-dash/src/web/mobile/views/MobileTasks.tsx +564 -0
  196. package/bizar-dash/src/web/mobile.html +20 -0
  197. package/bizar-dash/src/web/mobile.tsx +13 -0
  198. package/bizar-dash/src/web/styles/chat.css +198 -0
  199. package/bizar-dash/src/web/styles/glyphs.css +960 -0
  200. package/bizar-dash/src/web/styles/main.css +8855 -0
  201. package/bizar-dash/src/web/styles/mobile-chat.css +5 -0
  202. package/bizar-dash/src/web/styles/mobile.css +1960 -0
  203. package/bizar-dash/src/web/views/Activity.tsx +1203 -0
  204. package/bizar-dash/src/web/views/Agents.tsx +701 -0
  205. package/bizar-dash/src/web/views/Artifacts.tsx +1480 -0
  206. package/bizar-dash/src/web/views/BackgroundAgents.tsx +450 -0
  207. package/bizar-dash/src/web/views/Chat.tsx +190 -0
  208. package/bizar-dash/src/web/views/Config.tsx +2063 -0
  209. package/bizar-dash/src/web/views/History.tsx +335 -0
  210. package/bizar-dash/src/web/views/ModView.tsx +207 -0
  211. package/bizar-dash/src/web/views/Mods.tsx +693 -0
  212. package/bizar-dash/src/web/views/Overview.tsx +812 -0
  213. package/bizar-dash/src/web/views/Providers.tsx +302 -0
  214. package/bizar-dash/src/web/views/Schedules.tsx +802 -0
  215. package/bizar-dash/src/web/views/Settings.tsx +1787 -0
  216. package/bizar-dash/src/web/views/Skills.tsx +431 -0
  217. package/bizar-dash/src/web/views/Tasks.tsx +1523 -0
  218. package/bizar-dash/src/web/views/glyphs/GlyphRenderer.tsx +613 -0
  219. package/bizar-dash/src/web/views/glyphs/components.tsx +1098 -0
  220. package/bizar-dash/templates/mod/FORMAT.md +76 -0
  221. package/bizar-dash/templates/mod/hello-mod/README.md +19 -0
  222. package/bizar-dash/templates/mod/hello-mod/agents/greeter.md +8 -0
  223. package/bizar-dash/templates/mod/hello-mod/commands/hello.md +6 -0
  224. package/bizar-dash/templates/mod/hello-mod/mod.json +20 -0
  225. package/bizar-dash/templates/mod/hello-mod/routes/ping.mjs +9 -0
  226. package/bizar-dash/templates/mod/hello-mod/views/HelloView.tsx +10 -0
  227. package/bizar-dash/tests/dashboard-ports.test.mjs +138 -0
  228. package/bizar-dash/tests/graphify-mod-spawn.node.test.mjs +90 -0
  229. package/bizar-dash/tests/memory-cli.test.mjs +542 -0
  230. package/bizar-dash/tests/memory-config.test.mjs +422 -0
  231. package/bizar-dash/tests/memory-git.test.mjs +246 -0
  232. package/bizar-dash/tests/memory-lightrag.test.mjs +153 -0
  233. package/bizar-dash/tests/memory-protocol-drift.test.mjs +45 -0
  234. package/bizar-dash/tests/memory-schema.test.mjs +198 -0
  235. package/bizar-dash/tests/memory-secrets.test.mjs +121 -0
  236. package/bizar-dash/tests/memory-store.test.mjs +390 -0
  237. package/bizar-dash/tests/memory-sync.test.mjs +141 -0
  238. package/bizar-dash/tests/mod-instructions.node.test.mjs +188 -0
  239. package/bizar-dash/tests/mod-security.test.mjs +273 -0
  240. package/bizar-dash/tests/mod-upgrade.node.test.mjs +357 -0
  241. package/bizar-dash/tests/no-agent-browser.node.test.mjs +98 -0
  242. package/bizar-dash/tests/obsidian-back-compat.test.mjs +199 -0
  243. package/bizar-dash/tests/opencode-runner.test.mjs +172 -0
  244. package/bizar-dash/tests/path-safe.test.mjs +108 -0
  245. package/bizar-dash/tests/providers-store-backup-keys.node.test.mjs +333 -0
  246. package/bizar-dash/tests/smoke-v2.mjs +212 -0
  247. package/bizar-dash/tests/strip-thinking.test.mjs +175 -0
  248. package/bizar-dash/tests/submit-feedback.test.mjs +353 -0
  249. package/bizar-dash/tests/tmux-wrap.test.mjs +145 -0
  250. package/bizar-dash/tests/yaml.test.mjs +128 -0
  251. package/bizar-dash/tsconfig.json +23 -0
  252. package/bizar-dash/vite.config.ts +27 -0
  253. package/cli/atomic.mjs +73 -0
  254. package/cli/banner.mjs +1 -1
  255. package/cli/bin.mjs +44 -47
  256. package/cli/bootstrap.mjs +1 -1
  257. package/cli/copy.mjs +25 -16
  258. package/cli/dev-link.mjs +17 -1
  259. package/cli/doctor.mjs +4 -4
  260. package/cli/doctor.test.mjs +2 -2
  261. package/cli/init.mjs +135 -1
  262. package/cli/install.mjs +31 -39
  263. package/cli/memory-constants.mjs +59 -0
  264. package/cli/memory.mjs +1436 -0
  265. package/cli/service.mjs +5 -6
  266. package/cli/utils.mjs +6 -3
  267. package/config/AGENTS.md +7 -7
  268. package/config/agents/_shared/AGENT_BASELINE.md +59 -61
  269. package/config/opencode.json +13 -38
  270. package/config/skills/memory-protocol/SKILL.md +105 -0
  271. package/config/skills/obsidian/SKILL.md +270 -65
  272. package/install.sh +59 -19
  273. package/package.json +49 -14
  274. package/packages/sdk/LICENSE +21 -0
  275. package/packages/sdk/README.md +80 -0
  276. package/packages/sdk/src/client.ts +183 -0
  277. package/packages/sdk/src/errors.ts +120 -0
  278. package/packages/sdk/src/events.ts +153 -0
  279. package/packages/sdk/src/index.ts +63 -0
  280. package/packages/sdk/src/types.ts +176 -0
  281. package/packages/sdk/src/version.ts +4 -0
  282. package/packages/sdk/tests/client.test.ts +217 -0
  283. package/packages/sdk/tests/errors.test.ts +108 -0
  284. package/packages/sdk/tests/events.test.ts +139 -0
  285. package/packages/sdk/tests/fixtures/fetch-mock.ts +152 -0
  286. package/packages/sdk/tests/fixtures/sse-mock.ts +30 -0
  287. package/packages/sdk/tsconfig.json +25 -0
  288. package/packages/sdk/vitest.config.ts +9 -0
  289. package/plugins/bizar/LICENSE +21 -0
  290. package/plugins/bizar/README.md +448 -0
  291. package/plugins/bizar/dist/index.js +29901 -0
  292. package/plugins/bizar/index.ts +1491 -0
  293. package/plugins/bizar/scripts/check-forbidden-imports.sh +33 -0
  294. package/plugins/bizar/src/background-state.ts +551 -0
  295. package/plugins/bizar/src/background.ts +1250 -0
  296. package/plugins/bizar/src/commands-impl.ts +464 -0
  297. package/plugins/bizar/src/commands.ts +1168 -0
  298. package/plugins/bizar/src/dashboard-client.ts +233 -0
  299. package/plugins/bizar/src/event-stream.ts +606 -0
  300. package/plugins/bizar/src/fingerprint.ts +120 -0
  301. package/plugins/bizar/src/handoff.ts +79 -0
  302. package/plugins/bizar/src/http-client.ts +467 -0
  303. package/plugins/bizar/src/key-rotation.ts +218 -0
  304. package/plugins/bizar/src/logger.ts +144 -0
  305. package/plugins/bizar/src/loop.ts +176 -0
  306. package/plugins/bizar/src/opencode-runner.ts +390 -0
  307. package/plugins/bizar/src/options.ts +421 -0
  308. package/plugins/bizar/src/plan-fs.ts +323 -0
  309. package/plugins/bizar/src/reasoning-clean.ts +454 -0
  310. package/plugins/bizar/src/report.ts +178 -0
  311. package/plugins/bizar/src/research-prompt.ts +35 -0
  312. package/plugins/bizar/src/serve-info.ts +228 -0
  313. package/plugins/bizar/src/serve.ts +496 -0
  314. package/plugins/bizar/src/settings.ts +349 -0
  315. package/plugins/bizar/src/state.ts +298 -0
  316. package/plugins/bizar/src/tools/bg-collect.ts +104 -0
  317. package/plugins/bizar/src/tools/bg-get-comments.ts +239 -0
  318. package/plugins/bizar/src/tools/bg-kill.ts +87 -0
  319. package/plugins/bizar/src/tools/bg-spawn.ts +401 -0
  320. package/plugins/bizar/src/tools/bg-status.ts +99 -0
  321. package/plugins/bizar/src/tools/open-kb.ts +191 -0
  322. package/plugins/bizar/src/tools/plan-action.ts +767 -0
  323. package/plugins/bizar/src/tools/read-glyph-feedback.ts +170 -0
  324. package/plugins/bizar/src/tools/wait-for-feedback.ts +402 -0
  325. package/plugins/bizar/tests/attach-handler-bug.test.ts +169 -0
  326. package/plugins/bizar/tests/background-state.test.ts +277 -0
  327. package/plugins/bizar/tests/background.test.ts +402 -0
  328. package/plugins/bizar/tests/block.test.ts +195 -0
  329. package/plugins/bizar/tests/canonical-key-order.test.ts +75 -0
  330. package/plugins/bizar/tests/commands-impl.test.ts +442 -0
  331. package/plugins/bizar/tests/commands.test.ts +584 -0
  332. package/plugins/bizar/tests/config.test.ts +122 -0
  333. package/plugins/bizar/tests/dashboard-client.test.ts +159 -0
  334. package/plugins/bizar/tests/dispose.test.ts +336 -0
  335. package/plugins/bizar/tests/event-stream.test.ts +409 -0
  336. package/plugins/bizar/tests/event.test.ts +262 -0
  337. package/plugins/bizar/tests/fingerprint.test.ts +162 -0
  338. package/plugins/bizar/tests/http-client.test.ts +404 -0
  339. package/plugins/bizar/tests/init-helpers.test.ts +203 -0
  340. package/plugins/bizar/tests/integration/slash-command.test.ts +348 -0
  341. package/plugins/bizar/tests/integration/tool-routing.test.ts +314 -0
  342. package/plugins/bizar/tests/key-rotation.test.ts +396 -0
  343. package/plugins/bizar/tests/loop.test.ts +397 -0
  344. package/plugins/bizar/tests/options.test.ts +276 -0
  345. package/plugins/bizar/tests/reasoning-clean.test.ts +422 -0
  346. package/plugins/bizar/tests/serve.test.ts +339 -0
  347. package/plugins/bizar/tests/settings.test.ts +351 -0
  348. package/plugins/bizar/tests/stall-think.test.ts +750 -0
  349. package/plugins/bizar/tests/state.test.ts +276 -0
  350. package/plugins/bizar/tests/tools/bg-collect.test.ts +337 -0
  351. package/plugins/bizar/tests/tools/bg-get-comments.test.ts +485 -0
  352. package/plugins/bizar/tests/tools/bg-kill.test.ts +235 -0
  353. package/plugins/bizar/tests/tools/bg-spawn-delegation.test.ts +147 -0
  354. package/plugins/bizar/tests/tools/bg-spawn.test.ts +311 -0
  355. package/plugins/bizar/tests/tools/bg-status.test.ts +217 -0
  356. package/plugins/bizar/tests/tools/opencode-runner.test.ts +115 -0
  357. package/plugins/bizar/tests/tools/plan-action.test.ts +599 -0
  358. package/plugins/bizar/tests/tools/read-glyph-feedback.test.ts +253 -0
  359. package/plugins/bizar/tests/tools/wait-for-feedback.test.ts +390 -0
  360. package/plugins/bizar/tests/update-deadlock.test.ts +145 -0
  361. package/plugins/bizar/tsconfig.json +29 -0
@@ -0,0 +1,279 @@
1
+ /**
2
+ * src/server/routes/artifacts.mjs
3
+ *
4
+ * /api/artifacts — list
5
+ * /api/artifacts/:slug — read
6
+ * /api/artifacts (POST) — create
7
+ * /api/artifacts/:slug (PUT) — update meta
8
+ * /api/artifacts/:slug (DELETE) — remove
9
+ * /api/artifacts/:slug/canvas (GET) — read canvas
10
+ * /api/artifacts/:slug/canvas (PUT) — save canvas
11
+ * /api/artifacts/:slug/elements (POST) — add element
12
+ * /api/artifacts/:slug/elements/:id (PUT) — update element
13
+ * /api/artifacts/:slug/elements/:id (DELETE) — remove element
14
+ * /api/artifacts/:slug/position (PUT) — bulk position update
15
+ * /api/artifacts/:slug/connections (POST) — add connection
16
+ * /api/artifacts/:slug/connections/:id (DELETE) — remove connection
17
+ * /api/artifacts/:slug/elements/:id/comments (POST) — comment on element
18
+ * /api/artifacts/:slug/elements/:id/comments/:cid (DELETE) — remove element comment
19
+ * /api/artifacts/:slug/comments (POST) — canvas-level comment
20
+ * /api/artifacts/:slug/comments/:cid (DELETE) — remove canvas-level comment
21
+ * /api/artifacts/:slug/questions/:qid/respond (POST) — agent answers a question element
22
+ * /api/artifacts/:slug/submit (POST) — submit feedback for review (writes feedback.md, sets status=review)
23
+ * /api/artifacts/:slug/render (GET) — compile MDX → JSON glyph blocks
24
+ */
25
+ import { Router } from 'express';
26
+ import { artifactsStore } from '../artifacts-store.mjs';
27
+ import { notificationsStore } from '../notifications-store.mjs';
28
+ import { wrap } from './_shared.mjs';
29
+ import { compileGlyphMdx } from '../glyphs/mdx-compiler.mjs';
30
+
31
+ /**
32
+ * @param {object} deps
33
+ * @param {object} deps.state
34
+ * @param {Function} deps.broadcast
35
+ * @param {string} deps.projectRoot
36
+ * @returns {import('express').Router}
37
+ */
38
+ export function createArtifactsRouter({ state, broadcast, projectRoot }) {
39
+ const router = Router();
40
+
41
+ router.get('/artifacts', wrap(async (_req, res) => {
42
+ res.json({ artifacts: artifactsStore.list(projectRoot) });
43
+ }));
44
+
45
+ router.get('/artifacts/:slug', wrap(async (req, res) => {
46
+ const artifact = artifactsStore.get(req.params.slug, projectRoot);
47
+ if (!artifact) {
48
+ res.status(404).json({ error: 'not_found' });
49
+ return;
50
+ }
51
+ res.json(artifact);
52
+ }));
53
+
54
+ // v3.21.0 — Compile the artifact's MDX into a JSON-serializable
55
+ // glyph structure for the new Glyphs renderer. Returns frontmatter
56
+ // + a flat list of typed blocks, ready for the client-side block
57
+ // component map to render.
58
+ router.get('/artifacts/:slug/render', wrap(async (req, res) => {
59
+ const artifact = artifactsStore.get(req.params.slug, projectRoot);
60
+ if (!artifact) {
61
+ res.status(404).json({ error: 'not_found' });
62
+ return;
63
+ }
64
+ const compiled = await compileGlyphMdx(artifact.planMdx || '');
65
+ res.json({ slug: req.params.slug, ...compiled });
66
+ }));
67
+
68
+ // v3.1.0 — Full artifact CRUD + canvas editing.
69
+ router.post('/artifacts', wrap(async (req, res) => {
70
+ const slug = (req.body?.slug || '').trim();
71
+ try {
72
+ const artifact = artifactsStore.create(slug, req.body || {}, projectRoot);
73
+ broadcast({ type: 'artifact:change', slug });
74
+ res.status(201).json(artifact);
75
+ } catch (err) {
76
+ res.status(err.status || 500).json({ error: 'create_failed', message: err.message });
77
+ }
78
+ }));
79
+
80
+ router.put('/artifacts/:slug', wrap(async (req, res) => {
81
+ const artifact = artifactsStore.updateMeta(req.params.slug, req.body || {}, projectRoot);
82
+ if (!artifact) {
83
+ res.status(404).json({ error: 'not_found' });
84
+ return;
85
+ }
86
+ broadcast({ type: 'artifact:change', slug: req.params.slug });
87
+ res.json(artifact);
88
+ }));
89
+
90
+ router.delete('/artifacts/:slug', wrap(async (req, res) => {
91
+ const ok = artifactsStore.delete(req.params.slug, projectRoot);
92
+ if (!ok) {
93
+ res.status(404).json({ error: 'not_found' });
94
+ return;
95
+ }
96
+ broadcast({ type: 'artifact:change', slug: req.params.slug, deleted: true });
97
+ res.status(204).end();
98
+ }));
99
+
100
+ // ── /api/artifacts/:slug/canvas ──────────────────────────────────────────
101
+ router.get('/artifacts/:slug/canvas', wrap(async (req, res) => {
102
+ const canvas = artifactsStore.getCanvas(req.params.slug, projectRoot);
103
+ if (!canvas) {
104
+ res.status(404).json({ error: 'not_found' });
105
+ return;
106
+ }
107
+ res.json({ canvas });
108
+ }));
109
+
110
+ router.put('/artifacts/:slug/canvas', wrap(async (req, res) => {
111
+ const artifact = artifactsStore.saveCanvas(req.params.slug, req.body?.canvas, projectRoot);
112
+ if (!artifact) {
113
+ res.status(404).json({ error: 'not_found' });
114
+ return;
115
+ }
116
+ broadcast({ type: 'artifact:change', slug: req.params.slug });
117
+ res.json(artifact);
118
+ }));
119
+
120
+ // ── /api/artifacts/:slug/elements ────────────────────────────────────────
121
+ router.post('/artifacts/:slug/elements', wrap(async (req, res) => {
122
+ const out = artifactsStore.addElement(req.params.slug, req.body || {}, projectRoot);
123
+ if (!out) {
124
+ res.status(404).json({ error: 'not_found' });
125
+ return;
126
+ }
127
+ broadcast({ type: 'artifact:change', slug: req.params.slug });
128
+ res.status(201).json(out);
129
+ }));
130
+
131
+ router.put('/artifacts/:slug/elements/:id', wrap(async (req, res) => {
132
+ const out = artifactsStore.updateElement(req.params.slug, req.params.id, req.body || {}, projectRoot);
133
+ if (!out) {
134
+ res.status(404).json({ error: 'not_found' });
135
+ return;
136
+ }
137
+ broadcast({ type: 'artifact:change', slug: req.params.slug });
138
+ res.json(out);
139
+ }));
140
+
141
+ router.delete('/artifacts/:slug/elements/:id', wrap(async (req, res) => {
142
+ const out = artifactsStore.deleteElement(req.params.slug, req.params.id, projectRoot);
143
+ if (!out) {
144
+ res.status(404).json({ error: 'not_found' });
145
+ return;
146
+ }
147
+ broadcast({ type: 'artifact:change', slug: req.params.slug });
148
+ res.status(204).end();
149
+ }));
150
+
151
+ // ── /api/artifacts/:slug/position — bulk update positions (drag end) ─────
152
+ router.put('/artifacts/:slug/position', wrap(async (req, res) => {
153
+ const out = artifactsStore.updatePositions(req.params.slug, req.body?.positions, projectRoot);
154
+ if (!out) {
155
+ res.status(404).json({ error: 'not_found' });
156
+ return;
157
+ }
158
+ broadcast({ type: 'artifact:change', slug: req.params.slug });
159
+ res.json(out);
160
+ }));
161
+
162
+ // ── /api/artifacts/:slug/connections ─────────────────────────────────────
163
+ router.post('/artifacts/:slug/connections', wrap(async (req, res) => {
164
+ const out = artifactsStore.addConnection(req.params.slug, req.body || {}, projectRoot);
165
+ if (!out) {
166
+ res.status(400).json({ error: 'bad_request', message: 'from and to are required' });
167
+ return;
168
+ }
169
+ broadcast({ type: 'artifact:change', slug: req.params.slug });
170
+ res.status(201).json(out);
171
+ }));
172
+
173
+ router.delete('/artifacts/:slug/connections/:id', wrap(async (req, res) => {
174
+ const out = artifactsStore.deleteConnection(req.params.slug, req.params.id, projectRoot);
175
+ if (!out) {
176
+ res.status(404).json({ error: 'not_found' });
177
+ return;
178
+ }
179
+ broadcast({ type: 'artifact:change', slug: req.params.slug });
180
+ res.status(204).end();
181
+ }));
182
+
183
+ // ── /api/artifacts/:slug/elements/:id/comments ───────────────────────────
184
+ router.post('/artifacts/:slug/elements/:id/comments', wrap(async (req, res) => {
185
+ const out = artifactsStore.addComment(req.params.slug, req.params.id, req.body || {}, projectRoot);
186
+ if (!out) {
187
+ res.status(400).json({ error: 'bad_request', message: 'comment text is required' });
188
+ return;
189
+ }
190
+ broadcast({ type: 'artifact:change', slug: req.params.slug });
191
+ res.status(201).json(out);
192
+ }));
193
+
194
+ router.delete('/artifacts/:slug/elements/:id/comments/:cid', wrap(async (req, res) => {
195
+ const out = artifactsStore.deleteComment(req.params.slug, req.params.cid, projectRoot);
196
+ if (!out) {
197
+ res.status(404).json({ error: 'not_found' });
198
+ return;
199
+ }
200
+ broadcast({ type: 'artifact:change', slug: req.params.slug });
201
+ res.status(204).end();
202
+ }));
203
+
204
+ // Canvas-level comments (no elementId) — POST /artifacts/:slug/comments
205
+ router.post('/artifacts/:slug/comments', wrap(async (req, res) => {
206
+ const out = artifactsStore.addComment(req.params.slug, null, req.body || {}, projectRoot);
207
+ if (!out) {
208
+ res.status(400).json({ error: 'bad_request', message: 'comment text is required' });
209
+ return;
210
+ }
211
+ broadcast({ type: 'artifact:change', slug: req.params.slug });
212
+ res.status(201).json(out);
213
+ }));
214
+
215
+ router.delete('/artifacts/:slug/comments/:cid', wrap(async (req, res) => {
216
+ const out = artifactsStore.deleteComment(req.params.slug, req.params.cid, projectRoot);
217
+ if (!out) {
218
+ res.status(404).json({ error: 'not_found' });
219
+ return;
220
+ }
221
+ broadcast({ type: 'artifact:change', slug: req.params.slug });
222
+ res.status(204).end();
223
+ }));
224
+
225
+ // ── /api/artifacts/:slug/submit (v3.22.0) ─────────────────────────────
226
+ // "Submit to agent" — collect free-placed comments + OpenQuestion
227
+ // answers, write a structured `feedback.md` the agent can read,
228
+ // mark the artifact `status: review`, and return the summary.
229
+ router.post('/artifacts/:slug/submit', wrap(async (req, res) => {
230
+ const result = artifactsStore.submitFeedback(
231
+ req.params.slug,
232
+ req.body || {},
233
+ projectRoot,
234
+ );
235
+ if (!result.ok) {
236
+ res.status(result.error === 'invalid_slug' ? 400 : 404).json({
237
+ error: result.error || 'not_found',
238
+ });
239
+ return;
240
+ }
241
+ broadcast({ type: 'artifact:change', slug: req.params.slug });
242
+ res.json(result);
243
+ }));
244
+
245
+ // ── /api/artifacts/:slug/questions/:qid/respond (v3.3.0) ──────────────
246
+ // Agent-side integration for the question element: an agent posts
247
+ // a question via the artifact's canvas; the user clicks a choice in
248
+ // the dashboard, this endpoint records the choice, marks the
249
+ // question resolved, and broadcasts the answer so the agent
250
+ // (when its plugin is updated) can pick it up.
251
+ router.post('/artifacts/:slug/questions/:qid/respond', wrap(async (req, res) => {
252
+ const { choiceId, text } = req.body || {};
253
+ const out = artifactsStore.respondToQuestion(
254
+ req.params.slug,
255
+ req.params.qid,
256
+ { choiceId, text },
257
+ projectRoot,
258
+ );
259
+ if (!out) {
260
+ res.status(404).json({ error: 'not_found' });
261
+ return;
262
+ }
263
+ // Also drop a notification so the agent can re-poll.
264
+ try {
265
+ notificationsStore.add({
266
+ severity: 'info',
267
+ source: 'artifact',
268
+ title: `Artifact: ${req.params.slug}`,
269
+ message: `Question answered: choice=${choiceId || 'freeform'}`,
270
+ link: `/artifacts/${req.params.slug}`,
271
+ meta: { slug: req.params.slug, qid: req.params.qid, choiceId, text },
272
+ }, { broadcast });
273
+ } catch { /* best-effort */ }
274
+ broadcast({ type: 'artifact:change', slug: req.params.slug });
275
+ res.json(out);
276
+ }));
277
+
278
+ return router;
279
+ }
@@ -0,0 +1,69 @@
1
+ /**
2
+ * src/server/routes/auth.mjs
3
+ *
4
+ * v3.6.0 — Auth-related routes: status, reveal, regenerate.
5
+ *
6
+ * /api/auth/status — UNAUTHED. Returns { required: true } so a
7
+ * client can detect that auth is on and ask the
8
+ * operator to paste a token.
9
+ * /api/auth/reveal — AUTHED. Returns the current token. Only useful
10
+ * to someone who already has it (chicken-and-egg
11
+ * by design — initial token comes from server
12
+ * stderr on first boot).
13
+ * /api/auth/regenerate — AUTHED. Mints a new token, invalidating the
14
+ * old one immediately. The response body carries
15
+ * the new token ONCE — the caller must persist
16
+ * it client-side.
17
+ */
18
+ import { Router } from 'express';
19
+ import {
20
+ getDirectPeerAddress,
21
+ getOrCreateSecret,
22
+ isAuthRequired,
23
+ isLoopback,
24
+ regenerateSecret,
25
+ } from '../auth.mjs';
26
+ import { wrap } from './_shared.mjs';
27
+
28
+ /**
29
+ * @returns {import('express').Router}
30
+ */
31
+ export function createAuthRouter() {
32
+ const router = Router();
33
+
34
+ // Unauthed status probe. Tells the client whether auth is required
35
+ // and whether the current connection is loopback (in which case
36
+ // the server is auto-trusting it and the client doesn't need to
37
+ // send a token). This is how the dashboard "just works" in a
38
+ // browser on the same machine as the server. Reverse-proxy access
39
+ // still reports the direct peer so operators can tell whether a
40
+ // bearer token will be required.
41
+ router.get('/auth/status', (req, res) => {
42
+ const trusted = isLoopback(req);
43
+ res.json({
44
+ required: isAuthRequired(req),
45
+ loopback: trusted,
46
+ // Surface the direct peer for the Settings UI to display
47
+ // (helps the operator debug if the auto-trust isn't kicking
48
+ // in as expected — e.g. they're hitting a different bind).
49
+ peer: getDirectPeerAddress(req),
50
+ });
51
+ });
52
+
53
+ // AUTHED — returns the current token. The caller is presumed to
54
+ // already have it (that's why the auth middleware let them in);
55
+ // we just hand it back so the UI can render a "Copy token" button.
56
+ router.get('/auth/reveal', wrap((_req, res) => {
57
+ res.json({ token: getOrCreateSecret() });
58
+ }));
59
+
60
+ // AUTHED — rotate the token. Anything still holding the old token
61
+ // will start getting 401s immediately. The new token is in the
62
+ // response body; the caller MUST save it.
63
+ router.post('/auth/regenerate', wrap((_req, res) => {
64
+ const newToken = regenerateSecret();
65
+ res.json({ token: newToken });
66
+ }));
67
+
68
+ return router;
69
+ }
@@ -0,0 +1,165 @@
1
+ /**
2
+ * src/server/routes/background.mjs
3
+ *
4
+ * /api/background — list
5
+ * /api/background/:id — single instance
6
+ * /api/background/:id/output — tail captured output
7
+ * /api/background/:id/tmux — tmux attach metadata
8
+ * /api/background/:id/message (POST) — send a follow-up message
9
+ * /api/background/:id/retry (POST) — manual unstick (v3.11.0)
10
+ * /api/background/:id (DELETE) — kill
11
+ *
12
+ * Backed by the opencode-plugin's bg instance store. Imports the
13
+ * store lazily so this module loads even when the plugin is offline.
14
+ */
15
+ import { Router } from 'express';
16
+ import { wrap } from './_shared.mjs';
17
+
18
+ /**
19
+ * @param {object} deps
20
+ * @param {Function} deps.broadcast
21
+ * @returns {import('express').Router}
22
+ */
23
+ export function createBackgroundRouter({ broadcast }) {
24
+ const router = Router();
25
+
26
+ router.get('/background', wrap(async (_req, res) => {
27
+ const { backgroundStore } = await import('../background-store.mjs');
28
+ const instances = await backgroundStore.list();
29
+ res.json({ instances, status: backgroundStore.status() });
30
+ }));
31
+
32
+ router.get('/background/:id', wrap(async (req, res) => {
33
+ const { backgroundStore } = await import('../background-store.mjs');
34
+ const inst = backgroundStore.get(req.params.id);
35
+ if (!inst) {
36
+ res.status(404).json({ error: 'not_found' });
37
+ return;
38
+ }
39
+ res.json(inst);
40
+ }));
41
+
42
+ router.get('/background/:id/output', wrap(async (req, res) => {
43
+ const { backgroundStore } = await import('../background-store.mjs');
44
+ const lines = Math.min(500, Math.max(1, parseInt(req.query.lines || '50', 10) || 50));
45
+ const result = backgroundStore.captureOutput(req.params.id, lines);
46
+ res.json(result);
47
+ }));
48
+
49
+ // v3.5.5 — Tmux session metadata. The UI uses this to render an
50
+ // "Attach" button next to a running bg instance. Returns the
51
+ // computed session name, the local attach command, and whether
52
+ // the session actually exists right now. Always 200 — the caller
53
+ // can tell `exists: false` apart from a missing instance.
54
+ router.get('/background/:id/tmux', wrap(async (req, res) => {
55
+ const { backgroundStore } = await import('../background-store.mjs');
56
+ const info = backgroundStore.tmuxAttachInfo(req.params.id);
57
+ res.json(info);
58
+ }));
59
+
60
+ router.post('/background/:id/message', wrap(async (req, res) => {
61
+ const { backgroundStore } = await import('../background-store.mjs');
62
+ const message = (req.body?.message || '').toString();
63
+ if (!message.trim()) {
64
+ res.status(400).json({ error: 'bad_request', message: 'message required' });
65
+ return;
66
+ }
67
+ const result = backgroundStore.sendMessage(req.params.id, message);
68
+ res.json(result);
69
+ }));
70
+
71
+ // v3.11.0 — Manual unstick for a bg instance stuck in
72
+ // `dispatchPending: true`. The periodic retry loop covers most
73
+ // cases, but operators (and the UI) sometimes want to recover a
74
+ // specific instance immediately without waiting for the next tick.
75
+ //
76
+ // Behavior:
77
+ // - Resets `dispatchPending: true`, `retryCount: 0` so the
78
+ // instance qualifies for a fresh retry.
79
+ // - Calls `retryDispatchOnce(instanceId)` synchronously.
80
+ // - Returns the same shape the periodic loop logs:
81
+ // `{ ok, reason?, retryCount?, sessionId?, logPath? }`.
82
+ //
83
+ // This endpoint NEVER deletes the bg state file. A failed retry
84
+ // leaves the instance in a recoverable state.
85
+ router.post('/background/:id/retry', wrap(async (req, res) => {
86
+ const id = req.params.id;
87
+ const { readBgInstance, listBgInstances } = await import('../task-delegator.mjs');
88
+ const inst = readBgInstance(id);
89
+ if (!inst) {
90
+ res.status(404).json({ ok: false, error: 'not_found', message: `bg instance ${id} not found` });
91
+ return;
92
+ }
93
+ // Reset retry bookkeeping so a manual retry starts from a clean
94
+ // slate. The atomic rewrite goes through the same path the
95
+ // periodic loop uses; failures here are reported back to the
96
+ // caller.
97
+ try {
98
+ const fs = await import('node:fs');
99
+ const file = inst._file;
100
+ if (file && fs.existsSync(file)) {
101
+ const fresh = { ...inst, dispatchPending: true, retryCount: 0, lastRetryAt: Date.now() };
102
+ delete fresh._file;
103
+ delete fresh._bgDir;
104
+ delete fresh._mtime;
105
+ const tmp = `${file}.tmp`;
106
+ fs.writeFileSync(tmp, JSON.stringify(fresh, null, 2), 'utf8');
107
+ try {
108
+ fs.renameSync(tmp, file);
109
+ } catch {
110
+ fs.writeFileSync(file, JSON.stringify(fresh, null, 2), 'utf8');
111
+ }
112
+ }
113
+ } catch {
114
+ /* best-effort reset; retryDispatchOnce still runs */
115
+ }
116
+ const { retryDispatchOnce } = await import('../bg-retry.mjs');
117
+ const result = await retryDispatchOnce(id);
118
+ if (result.ok) {
119
+ broadcast({ type: 'background:change', action: 'retry', id, sessionId: result.sessionId });
120
+ // Re-list so the UI snapshot picks up the new sessionId.
121
+ const refreshed = (listBgInstances() || []).find((i) => i.instanceId === id) || null;
122
+ res.json({ ...result, instance: refreshed });
123
+ return;
124
+ }
125
+ res.json({ ...result, instance: inst });
126
+ }));
127
+
128
+ router.delete('/background/:id', wrap(async (req, res) => {
129
+ const { backgroundStore } = await import('../background-store.mjs');
130
+ // v3.5.4 (bug #3) — `kill()` is now async (it awaits the abortSession
131
+ // HTTP call to opencode serve, then deletes the state file, then
132
+ // best-effort tmux). The result includes a `steps[]` array so the UI
133
+ // can report exactly what happened.
134
+ //
135
+ // We always return 200 — the result body's `ok` distinguishes
136
+ // success from partial failure (e.g. abort succeeded but tmux kill
137
+ // did not). Returning 502 would force the fetch wrapper into its
138
+ // error branch and we'd lose the `steps[]` diagnostic.
139
+ const result = await backgroundStore.kill(req.params.id);
140
+ if (result.ok) {
141
+ broadcast({ type: 'background:change', action: 'kill', id: req.params.id });
142
+ }
143
+ res.json(result);
144
+ }));
145
+
146
+ // v0.5.5 — Cleanup old terminal instances. The UI calls this from
147
+ // the Settings "Background Agents" card.
148
+ router.post('/background/cleanup', wrap(async (req, res) => {
149
+ const { backgroundStore } = await import('../background-store.mjs');
150
+ const maxAgeDays = Number(req.body?.maxAgeDays) || 7;
151
+ const result = backgroundStore.cleanup(maxAgeDays);
152
+ broadcast({ type: 'background:cleanup', deleted: result.deleted });
153
+ res.json(result);
154
+ }));
155
+
156
+ // v0.5.5 — Summary with status counts. The UI calls this for the
157
+ // Background Agents overview in Settings.
158
+ router.get('/background/summary', wrap(async (_req, res) => {
159
+ const { backgroundStore } = await import('../background-store.mjs');
160
+ const summary = backgroundStore.listWithStatusCounts();
161
+ res.json(summary);
162
+ }));
163
+
164
+ return router;
165
+ }