@polderlabs/bizar 3.23.0 → 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,447 @@
1
+ /**
2
+ * src/server/background-store.mjs
3
+ *
4
+ * v3.2.0 — Bridge from the dashboard to the opencode plugin's
5
+ * background-agent infrastructure.
6
+ *
7
+ * What this store actually does:
8
+ * 1. Lists bg instances by walking the plugin's bg state directory
9
+ * (defaults to `~/.cache/bizar/bg/` per the plugin spec).
10
+ * 2. Optionally attaches tmux session hints when a tmux session
11
+ * named `bizar-bg-<id>` happens to exist. The plugin does NOT
12
+ * itself use tmux — instances are HTTP sessions — but tmux
13
+ * sessions may be attached by operators running the plugin
14
+ * under `bizar serve` for live monitoring. We surface them
15
+ * when present and degrade gracefully when not.
16
+ * 3. Supports best-effort `sendMessage` / `kill` via tmux when the
17
+ * session exists. Both operations are no-ops when there's no
18
+ * tmux session — they NEVER fail the request.
19
+ *
20
+ * All I/O is defensive: missing files, corrupt JSON, missing tmux —
21
+ * everything is handled silently and the API returns an empty list
22
+ * rather than a 500.
23
+ */
24
+
25
+ import {
26
+ readFileSync,
27
+ existsSync,
28
+ readdirSync,
29
+ statSync,
30
+ unlinkSync,
31
+ } from 'node:fs';
32
+ import { join } from 'node:path';
33
+ import { homedir } from 'node:os';
34
+ import { execFileSync } from 'node:child_process';
35
+
36
+ const HOME = homedir();
37
+
38
+ // Bg state directories, ordered by likelihood. First match wins.
39
+ // Matches plugins/bizar/src/background-state.ts (default `~/.cache/bizar`).
40
+ const BG_DIRS = [
41
+ join(HOME, '.cache', 'bizar', 'bg'),
42
+ join(HOME, '.config', 'opencode', 'bg'),
43
+ join(HOME, '.bizar', 'bg'),
44
+ ];
45
+
46
+ function pickBgDir() {
47
+ for (const dir of BG_DIRS) {
48
+ if (existsSync(dir)) return dir;
49
+ }
50
+ return BG_DIRS[0];
51
+ }
52
+
53
+ function safeReadJSON(file, fallback = null) {
54
+ try {
55
+ if (!existsSync(file)) return fallback;
56
+ const text = readFileSync(file, 'utf8');
57
+ if (!text.trim()) return fallback;
58
+ return JSON.parse(text);
59
+ } catch {
60
+ return null;
61
+ }
62
+ }
63
+
64
+ function tmuxSessionFor(instanceId) {
65
+ return `bizar-bg-${instanceId}`;
66
+ }
67
+
68
+ function tmuxHasSession(sessionName) {
69
+ try {
70
+ execFileSync('tmux', ['has-session', '-t', sessionName], { stdio: 'ignore' });
71
+ return true;
72
+ } catch {
73
+ return false;
74
+ }
75
+ }
76
+
77
+ export const backgroundStore = {
78
+ BG_DIRS,
79
+
80
+ /**
81
+ * List all bg instances across the candidate dirs. Each entry is
82
+ * the raw plugin state file enriched with tmux hints (when
83
+ * applicable).
84
+ */
85
+ list() {
86
+ const out = [];
87
+ for (const dir of BG_DIRS) {
88
+ if (!existsSync(dir)) continue;
89
+ let files;
90
+ try {
91
+ files = readdirSync(dir).filter((f) => f.endsWith('.json'));
92
+ } catch {
93
+ continue;
94
+ }
95
+ for (const f of files) {
96
+ const full = join(dir, f);
97
+ let st;
98
+ try {
99
+ st = statSync(full);
100
+ } catch {
101
+ continue;
102
+ }
103
+ const data = safeReadJSON(full, null);
104
+ if (!data) continue;
105
+ const instanceId = data.instanceId || f.replace(/\.json$/, '');
106
+ const sessionName = tmuxSessionFor(instanceId);
107
+ const tmuxActive = tmuxHasSession(sessionName);
108
+ out.push({
109
+ ...data,
110
+ _bgDir: dir,
111
+ _mtime: st.mtimeMs,
112
+ _file: full,
113
+ tmuxSession: sessionName,
114
+ tmuxActive,
115
+ });
116
+ }
117
+ }
118
+ // Sort: pending/running first, then by start time desc.
119
+ out.sort((a, b) => {
120
+ const order = { pending: 0, running: 1, done: 2, failed: 3, killed: 4, timed_out: 5 };
121
+ const ao = order[a.status] ?? 9;
122
+ const bo = order[b.status] ?? 9;
123
+ if (ao !== bo) return ao - bo;
124
+ return (b.startedAt || b._mtime || 0) - (a.startedAt || a._mtime || 0);
125
+ });
126
+ return out;
127
+ },
128
+
129
+ /**
130
+ * Read a single bg instance by id. Searches all candidate dirs.
131
+ */
132
+ get(instanceId) {
133
+ for (const dir of BG_DIRS) {
134
+ const file = join(dir, `${instanceId}.json`);
135
+ if (existsSync(file)) {
136
+ const data = safeReadJSON(file, null);
137
+ if (data) {
138
+ const sessionName = tmuxSessionFor(instanceId);
139
+ return {
140
+ ...data,
141
+ _bgDir: dir,
142
+ _file: file,
143
+ tmuxSession: sessionName,
144
+ tmuxActive: tmuxHasSession(sessionName),
145
+ };
146
+ }
147
+ }
148
+ }
149
+ return null;
150
+ },
151
+
152
+ /**
153
+ * Best-effort: send a line of text into the tmux session for an
154
+ * instance. No-op when there's no session.
155
+ */
156
+ sendMessage(instanceId, message) {
157
+ const session = tmuxSessionFor(instanceId);
158
+ if (!tmuxHasSession(session)) {
159
+ return { ok: false, error: 'no tmux session for instance', session };
160
+ }
161
+ try {
162
+ const msg = String(message || '');
163
+ // Use tmux's literal mode (`-l`) and pass the message as a single
164
+ // arg via execFileSync — no shell interpolation, so payloads like
165
+ // `"; cat /etc/passwd #` cannot break out.
166
+ execFileSync(
167
+ 'tmux',
168
+ ['send-keys', '-l', '-t', session, msg, 'Enter'],
169
+ { stdio: 'pipe', timeout: 5_000 },
170
+ );
171
+ return { ok: true, session };
172
+ } catch (err) {
173
+ return { ok: false, error: err.message, session };
174
+ }
175
+ },
176
+
177
+ /**
178
+ * Best-effort: kill the tmux session for an instance.
179
+ * The plugin's session is HTTP-based, so this only stops the
180
+ * terminal attach; the underlying opencode session continues
181
+ * unless the operator also aborts it.
182
+ */
183
+ async kill(instanceId) {
184
+ const steps = [];
185
+ const inst = this.get(instanceId);
186
+ if (!inst) {
187
+ return { ok: false, error: 'instance not found', instanceId, steps };
188
+ }
189
+
190
+ let ok = true;
191
+ const session = tmuxSessionFor(instanceId);
192
+
193
+ if (inst.sessionId) {
194
+ try {
195
+ const { readServeInfo, abortSession } = await import('./serve-info.mjs');
196
+ const serveInfo = readServeInfo();
197
+ if (!serveInfo) {
198
+ ok = false;
199
+ steps.push({ step: 'abort-session', ok: false, note: 'serve-info unavailable' });
200
+ } else {
201
+ const aborted = await abortSession(
202
+ serveInfo,
203
+ inst.sessionId,
204
+ inst.worktree || serveInfo.worktree,
205
+ );
206
+ steps.push({ step: 'abort-session', ...aborted });
207
+ if (!aborted.ok) ok = false;
208
+ }
209
+ } catch (err) {
210
+ ok = false;
211
+ steps.push({ step: 'abort-session', ok: false, error: err instanceof Error ? err.message : String(err) });
212
+ }
213
+ } else {
214
+ steps.push({ step: 'abort-session', ok: true, note: 'no opencode session id present' });
215
+ }
216
+
217
+ if (inst._file && existsSync(inst._file)) {
218
+ try {
219
+ unlinkSync(inst._file);
220
+ steps.push({ step: 'delete-state-file', ok: true, file: inst._file });
221
+ } catch (err) {
222
+ ok = false;
223
+ steps.push({ step: 'delete-state-file', ok: false, error: err instanceof Error ? err.message : String(err) });
224
+ }
225
+ } else {
226
+ steps.push({ step: 'delete-state-file', ok: true, note: 'state file already absent' });
227
+ }
228
+
229
+ if (tmuxHasSession(session)) {
230
+ try {
231
+ execFileSync('tmux', ['kill-session', '-t', session], { stdio: 'pipe', timeout: 5_000 });
232
+ steps.push({ step: 'kill-tmux', ok: true, session });
233
+ } catch (err) {
234
+ ok = false;
235
+ steps.push({ step: 'kill-tmux', ok: false, session, error: err instanceof Error ? err.message : String(err) });
236
+ }
237
+ } else {
238
+ steps.push({ step: 'kill-tmux', ok: true, session, note: 'no tmux session present' });
239
+ }
240
+
241
+ return { ok, instanceId, session, steps };
242
+ },
243
+
244
+ /**
245
+ * v3.5.5 — Return tmux session metadata for the API layer to
246
+ * expose as `GET /api/background/:id/tmux`. Always returns the
247
+ * computed session name so the UI can render an "Attach" button
248
+ * even when the session doesn't exist (the button just opens a
249
+ * new detached session that shares the same name).
250
+ *
251
+ * @param {string} instanceId
252
+ * @returns {{ exists: boolean, session: string, attachCommand: string, attachUrl: string }}
253
+ */
254
+ tmuxAttachInfo(instanceId) {
255
+ const session = tmuxSessionFor(instanceId);
256
+ return {
257
+ exists: tmuxHasSession(session),
258
+ session,
259
+ attachCommand: `tmux attach -t ${session}`,
260
+ attachUrl: `tmux://${session}`,
261
+ };
262
+ },
263
+
264
+ /**
265
+ * v3.5.5 — Spawn a tmux session that wraps the agent run. Used
266
+ * by the task delegator so operators can `tmux attach -t …` and
267
+ * watch the opencode process in real time. The session is named
268
+ * `bizar-bg-<id>` and runs the supplied shell command inside
269
+ * the optional `cwd`.
270
+ *
271
+ * Returns `{ ok, session, error? }`. Never throws — failures are
272
+ * logged and reported so the dispatch path can keep going.
273
+ *
274
+ * @param {string} instanceId
275
+ * @param {string} command
276
+ * @param {string} [cwd]
277
+ */
278
+ spawnTmuxFor(instanceId, command, cwd) {
279
+ const session = tmuxSessionFor(instanceId);
280
+ if (tmuxHasSession(session)) {
281
+ return { ok: true, session, note: 'session already existed' };
282
+ }
283
+ const spec =
284
+ command && typeof command === 'object' && !Array.isArray(command)
285
+ ? command
286
+ : { command, args: [] };
287
+ const executable = typeof spec.command === 'string' ? spec.command.trim() : '';
288
+ const args = Array.isArray(spec.args) ? spec.args.map((arg) => String(arg)) : [];
289
+ const workdir = cwd && typeof cwd === 'string' ? cwd : '';
290
+ if (!executable) {
291
+ return {
292
+ ok: false,
293
+ session,
294
+ error: 'command is required',
295
+ };
296
+ }
297
+ try {
298
+ const tmuxArgs = ['new-session', '-d', '-s', session, '-x', '220', '-y', '50'];
299
+ if (workdir) tmuxArgs.push('-c', workdir);
300
+ tmuxArgs.push(executable, ...args);
301
+ execFileSync(
302
+ 'tmux',
303
+ tmuxArgs,
304
+ { stdio: 'ignore', timeout: 5_000 },
305
+ );
306
+ return { ok: true, session };
307
+ } catch (err) {
308
+ return { ok: false, session, error: err instanceof Error ? err.message : String(err) };
309
+ }
310
+ },
311
+
312
+ /**
313
+ * Capture the tail of the tmux session for an instance.
314
+ */
315
+ captureOutput(instanceId, lines = 50) {
316
+ const session = tmuxSessionFor(instanceId);
317
+ if (!tmuxHasSession(session)) {
318
+ return { ok: false, error: 'no tmux session for instance', session, output: '' };
319
+ }
320
+ try {
321
+ const safeLines = String(Math.max(1, Math.min(500, lines)));
322
+ const out = execFileSync(
323
+ 'tmux',
324
+ ['capture-pane', '-t', session, '-p', '-S', `-${safeLines}`],
325
+ { encoding: 'utf8', timeout: 5_000 },
326
+ );
327
+ return { ok: true, session, output: out };
328
+ } catch (err) {
329
+ return { ok: false, error: err.message, session, output: '' };
330
+ }
331
+ },
332
+
333
+ /**
334
+ * v3.11.1 — Kill the tmux session for an instance if one exists.
335
+ * No-op when the session is absent or tmux is not installed. Returns
336
+ * `{ ok, session, killed, error? }` so callers can log a structured
337
+ * result. Used by the bg-retry janitor and `cleanup()` to ensure
338
+ * terminal instances don't leave dangling tmux panes.
339
+ *
340
+ * @param {string} instanceId
341
+ * @returns {{ ok: boolean, session: string, killed: boolean, error?: string }}
342
+ */
343
+ killTmuxFor(instanceId) {
344
+ const session = tmuxSessionFor(instanceId);
345
+ if (!tmuxHasSession(session)) {
346
+ return { ok: true, session, killed: false, note: 'no session present' };
347
+ }
348
+ try {
349
+ execFileSync('tmux', ['kill-session', '-t', session], { stdio: 'pipe', timeout: 5_000 });
350
+ return { ok: true, session, killed: true };
351
+ } catch (err) {
352
+ return {
353
+ ok: false,
354
+ session,
355
+ killed: false,
356
+ error: err instanceof Error ? err.message : String(err),
357
+ };
358
+ }
359
+ },
360
+
361
+ /** The attach command (for the UI to display). */
362
+ attachCommand(instanceId) {
363
+ return `tmux attach -t ${tmuxSessionFor(instanceId)}`;
364
+ },
365
+
366
+ /** Diagnostics: which bg dir is being used right now. */
367
+ status() {
368
+ const dir = pickBgDir();
369
+ const exists = existsSync(dir);
370
+ let count = 0;
371
+ if (exists) {
372
+ try {
373
+ count = readdirSync(dir).filter((f) => f.endsWith('.json')).length;
374
+ } catch {
375
+ count = 0;
376
+ }
377
+ }
378
+ return { dir, exists, count };
379
+ },
380
+
381
+ /**
382
+ * v0.5.5 — Delete state files for terminal instances older than
383
+ * `maxAgeDays`. Iterates all BG_DIRS. Non-terminal instances are
384
+ * NEVER removed. Returns the total deleted count.
385
+ *
386
+ * v3.11.1 — Also kills the tmux session for each terminal instance
387
+ * (even those NOT yet old enough for state-file deletion) so the
388
+ * operator's `tmux ls` doesn't grow unboundedly. Returns
389
+ * `{ deleted, tmuxKilled }`.
390
+ */
391
+ cleanup(maxAgeDays = 7) {
392
+ let deleted = 0;
393
+ let tmuxKilled = 0;
394
+ const cutoff = Date.now() - (maxAgeDays * 24 * 60 * 60 * 1000);
395
+ const TERMINAL = new Set(['done', 'failed', 'killed', 'timed_out']);
396
+ const seen = new Set();
397
+ for (const dir of BG_DIRS) {
398
+ if (!existsSync(dir)) continue;
399
+ let files;
400
+ try {
401
+ files = readdirSync(dir).filter((f) => f.endsWith('.json'));
402
+ } catch {
403
+ continue;
404
+ }
405
+ for (const f of files) {
406
+ const full = join(dir, f);
407
+ const data = safeReadJSON(full, null);
408
+ if (!data) continue;
409
+ if (!TERMINAL.has(data.status)) continue;
410
+
411
+ // Kill the tmux session for every terminal instance, regardless
412
+ // of age — prevents tmux-session pile-up in long-running hosts.
413
+ const instanceId = data.instanceId || f.replace(/\.json$/, '');
414
+ if (!seen.has(instanceId)) {
415
+ seen.add(instanceId);
416
+ const tmuxRes = this.killTmuxFor(instanceId);
417
+ if (tmuxRes.killed) tmuxKilled += 1;
418
+ }
419
+
420
+ const ts = new Date(data.updatedAt || data.completedAt || 0).getTime();
421
+ if (ts < cutoff) {
422
+ try {
423
+ unlinkSync(full);
424
+ deleted += 1;
425
+ } catch {
426
+ // race — file already gone, skip
427
+ }
428
+ }
429
+ }
430
+ }
431
+ return { deleted, tmuxKilled };
432
+ },
433
+
434
+ /**
435
+ * v0.5.5 — List all instances with summary status counts. Wraps
436
+ * `list()` and computes a breakdown.
437
+ */
438
+ listWithStatusCounts() {
439
+ const instances = this.list();
440
+ const counts = { pending: 0, running: 0, done: 0, failed: 0, killed: 0, timed_out: 0 };
441
+ for (const inst of instances) {
442
+ const s = inst.status;
443
+ if (s in counts) counts[s] += 1;
444
+ }
445
+ return { instances, counts, total: instances.length };
446
+ },
447
+ };