@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.
- package/README.md +72 -2
- package/bizar-dash/.bizar/activity.log +3 -0
- package/bizar-dash/.bizar/graph/.graphify_analysis.json +1540 -0
- package/bizar-dash/.bizar/graph/.graphify_labels.json +1 -0
- package/bizar-dash/.bizar/graph/GRAPH_REPORT.md +404 -0
- package/bizar-dash/.bizar/graph/cache/ast/v0.8.46/0661b816358db84dfdb7f10443db8a61152fd20b3fee950caf8fbf32920a1790.json +1 -0
- package/bizar-dash/.bizar/graph/cache/ast/v0.8.46/16da06d774142a6e5d74829ad982531286a6193ef798de8f593db029e123ec32.json +1 -0
- package/bizar-dash/.bizar/graph/cache/ast/v0.8.46/27e5697c01254c69c64c5a33896669073b40afb5ba88375102ae77fa5404136d.json +1 -0
- package/bizar-dash/.bizar/graph/cache/ast/v0.8.46/532997898027254aa4c0932243dc71d2f55888181232b65974961934997dc85b.json +1 -0
- package/bizar-dash/.bizar/graph/cache/ast/v0.8.46/84e3451a64c7f99f0cd6a88b0748d7426780486f53ab4935b7476b28c08f8293.json +1 -0
- package/bizar-dash/.bizar/graph/cache/ast/v0.8.46/88e5ea5f3e78fa69353fe9e6a8d1a05391d54605e25600cb125531eb5a6432ff.json +1 -0
- package/bizar-dash/.bizar/graph/cache/stat-index.json +1 -0
- package/bizar-dash/.bizar/graph/graph.html +307 -0
- package/bizar-dash/.bizar/graph/graph.json +40267 -0
- package/bizar-dash/.bizar/graph/manifest.json +762 -0
- package/bizar-dash/.graphifyignore +40 -0
- package/bizar-dash/.obsidian/README.md +14 -0
- package/bizar-dash/.obsidian/daily/2026-06-25.md +9 -0
- package/bizar-dash/.obsidian/vault.json +7 -0
- package/bizar-dash/.od-skills/web-prototype-e1429c1737/SKILL.md +103 -0
- package/bizar-dash/.od-skills/web-prototype-e1429c1737/assets/template.html +338 -0
- package/bizar-dash/.od-skills/web-prototype-e1429c1737/example.html +81 -0
- package/bizar-dash/.od-skills/web-prototype-e1429c1737/open-design.json +172 -0
- package/bizar-dash/.od-skills/web-prototype-e1429c1737/references/checklist.md +44 -0
- package/bizar-dash/.od-skills/web-prototype-e1429c1737/references/layouts.md +247 -0
- package/bizar-dash/CHANGELOG.md +474 -0
- package/bizar-dash/DESIGN.md +136 -0
- package/bizar-dash/dist/assets/main-B3RgW6FS.js +331 -0
- package/bizar-dash/dist/assets/main-B3RgW6FS.js.map +1 -0
- package/bizar-dash/dist/assets/main-DlJ8wgLR.css +1 -0
- package/bizar-dash/dist/assets/mobile-CsZQAswA.css +1 -0
- package/bizar-dash/dist/assets/mobile-DbxIw8BG.js +2 -0
- package/bizar-dash/dist/assets/mobile-DbxIw8BG.js.map +1 -0
- package/bizar-dash/dist/assets/mobile-Dvq2d53Y.js +354 -0
- package/bizar-dash/dist/assets/mobile-Dvq2d53Y.js.map +1 -0
- package/bizar-dash/dist/index.html +40 -0
- package/bizar-dash/dist/mobile.html +22 -0
- package/bizar-dash/plans/debug-plan/comments.json +1 -0
- package/bizar-dash/plans/debug-plan/meta.json +10 -0
- package/bizar-dash/plans/debug-plan/plan.json +174 -0
- package/bizar-dash/scripts/smoke-bg-retry.mjs +246 -0
- package/bizar-dash/src/cli/dashboard-ports.mjs +488 -0
- package/bizar-dash/src/cli.mjs +522 -0
- package/bizar-dash/src/server/activity-log.mjs +174 -0
- package/bizar-dash/src/server/agents-store.mjs +465 -0
- package/bizar-dash/src/server/api.mjs +138 -0
- package/bizar-dash/src/server/artifact-template.html +810 -0
- package/bizar-dash/src/server/artifacts-store.mjs +733 -0
- package/bizar-dash/src/server/auth.mjs +381 -0
- package/bizar-dash/src/server/background-store.mjs +447 -0
- package/bizar-dash/src/server/bg-poller.mjs +305 -0
- package/bizar-dash/src/server/bg-retry.mjs +574 -0
- package/bizar-dash/src/server/browser.mjs +40 -0
- package/bizar-dash/src/server/diagnostics-store.mjs +148 -0
- package/bizar-dash/src/server/dialog-poller.mjs +105 -0
- package/bizar-dash/src/server/dialog-store.mjs +177 -0
- package/bizar-dash/src/server/glyphs/mdx-compiler.mjs +583 -0
- package/bizar-dash/src/server/lib/path-safe.mjs +283 -0
- package/bizar-dash/src/server/memory-git.mjs +249 -0
- package/bizar-dash/src/server/memory-schema.mjs +145 -0
- package/bizar-dash/src/server/memory-secrets.mjs +84 -0
- package/bizar-dash/src/server/memory-store.mjs +424 -0
- package/bizar-dash/src/server/mod-security.mjs +398 -0
- package/bizar-dash/src/server/mods-loader.mjs +1067 -0
- package/bizar-dash/src/server/notifications-store.mjs +243 -0
- package/bizar-dash/src/server/obsidian-store.mjs +318 -0
- package/bizar-dash/src/server/opencode-runner.mjs +279 -0
- package/bizar-dash/src/server/pair-store.mjs +138 -0
- package/bizar-dash/src/server/projects-store.mjs +364 -0
- package/bizar-dash/src/server/providers-store.mjs +789 -0
- package/bizar-dash/src/server/routes/_shared.mjs +415 -0
- package/bizar-dash/src/server/routes/activity.mjs +110 -0
- package/bizar-dash/src/server/routes/agents.mjs +134 -0
- package/bizar-dash/src/server/routes/artifacts.mjs +279 -0
- package/bizar-dash/src/server/routes/auth.mjs +69 -0
- package/bizar-dash/src/server/routes/background.mjs +165 -0
- package/bizar-dash/src/server/routes/chat.mjs +485 -0
- package/bizar-dash/src/server/routes/config.mjs +129 -0
- package/bizar-dash/src/server/routes/diagnostics.mjs +50 -0
- package/bizar-dash/src/server/routes/dialogs.mjs +86 -0
- package/bizar-dash/src/server/routes/fs.mjs +429 -0
- package/bizar-dash/src/server/routes/history.mjs +78 -0
- package/bizar-dash/src/server/routes/memory.mjs +499 -0
- package/bizar-dash/src/server/routes/misc.mjs +89 -0
- package/bizar-dash/src/server/routes/mods.mjs +359 -0
- package/bizar-dash/src/server/routes/notifications.mjs +54 -0
- package/bizar-dash/src/server/routes/obsidian.mjs +239 -0
- package/bizar-dash/src/server/routes/opencode-sessions.mjs +55 -0
- package/bizar-dash/src/server/routes/overview.mjs +91 -0
- package/bizar-dash/src/server/routes/pair.mjs +67 -0
- package/bizar-dash/src/server/routes/projects.mjs +175 -0
- package/bizar-dash/src/server/routes/providers.mjs +47 -0
- package/bizar-dash/src/server/routes/schedules.mjs +104 -0
- package/bizar-dash/src/server/routes/settings.mjs +51 -0
- package/bizar-dash/src/server/routes/skills.mjs +76 -0
- package/bizar-dash/src/server/routes/tasks.mjs +468 -0
- package/bizar-dash/src/server/routes/themes.mjs +51 -0
- package/bizar-dash/src/server/routes-v2/auth.mjs +59 -0
- package/bizar-dash/src/server/routes-v2/events.mjs +107 -0
- package/bizar-dash/src/server/routes-v2/health.mjs +21 -0
- package/bizar-dash/src/server/routes-v2/index.mjs +90 -0
- package/bizar-dash/src/server/routes-v2/sessions.mjs +111 -0
- package/bizar-dash/src/server/schedules-runner.mjs +369 -0
- package/bizar-dash/src/server/schedules-store.mjs +268 -0
- package/bizar-dash/src/server/search-store.mjs +220 -0
- package/bizar-dash/src/server/serve-info.mjs +684 -0
- package/bizar-dash/src/server/server.mjs +796 -0
- package/bizar-dash/src/server/settings-store.mjs +45 -0
- package/bizar-dash/src/server/skills-store.mjs +300 -0
- package/bizar-dash/src/server/state.mjs +437 -0
- package/bizar-dash/src/server/tailscale-store.mjs +113 -0
- package/bizar-dash/src/server/task-delegator.mjs +824 -0
- package/bizar-dash/src/server/tasks-store.mjs +508 -0
- package/bizar-dash/src/server/tui.mjs +923 -0
- package/bizar-dash/src/server/update-store.mjs +168 -0
- package/bizar-dash/src/server/v2-auth-file.mjs +99 -0
- package/bizar-dash/src/server/v2-event-bus.mjs +128 -0
- package/bizar-dash/src/server/watcher.mjs +81 -0
- package/bizar-dash/src/server/yaml.mjs +238 -0
- package/bizar-dash/src/web/App.tsx +718 -0
- package/bizar-dash/src/web/MobileApp.tsx +408 -0
- package/bizar-dash/src/web/components/ArtifactCreateDialog.tsx +95 -0
- package/bizar-dash/src/web/components/ArtifactListDialog.tsx +55 -0
- package/bizar-dash/src/web/components/ArtifactViewer.tsx +143 -0
- package/bizar-dash/src/web/components/AuditDialog.tsx +79 -0
- package/bizar-dash/src/web/components/BgStatusBadge.tsx +32 -0
- package/bizar-dash/src/web/components/Button.tsx +56 -0
- package/bizar-dash/src/web/components/CanvasContextMenu.tsx +68 -0
- package/bizar-dash/src/web/components/Card.tsx +40 -0
- package/bizar-dash/src/web/components/CollapsibleSection.tsx +72 -0
- package/bizar-dash/src/web/components/CommandDialog.tsx +66 -0
- package/bizar-dash/src/web/components/EmptyState.tsx +30 -0
- package/bizar-dash/src/web/components/FileBrowser.tsx +633 -0
- package/bizar-dash/src/web/components/HelpDialog.tsx +55 -0
- package/bizar-dash/src/web/components/KillConfirmDialog.tsx +88 -0
- package/bizar-dash/src/web/components/Modal.tsx +215 -0
- package/bizar-dash/src/web/components/Notifications.tsx +241 -0
- package/bizar-dash/src/web/components/SearchModal.tsx +224 -0
- package/bizar-dash/src/web/components/Sidebar.tsx +66 -0
- package/bizar-dash/src/web/components/Spinner.tsx +19 -0
- package/bizar-dash/src/web/components/StatusBadge.tsx +25 -0
- package/bizar-dash/src/web/components/Tag.tsx +28 -0
- package/bizar-dash/src/web/components/Toast.tsx +150 -0
- package/bizar-dash/src/web/components/Topbar.tsx +368 -0
- package/bizar-dash/src/web/components/VisualPlanDialog.tsx +58 -0
- package/bizar-dash/src/web/components/chat/AgentChip.tsx +68 -0
- package/bizar-dash/src/web/components/chat/ChatBubble.tsx +60 -0
- package/bizar-dash/src/web/components/chat/ChatThread.tsx +88 -0
- package/bizar-dash/src/web/components/chat/ChatTopBar.tsx +50 -0
- package/bizar-dash/src/web/components/chat/Composer.tsx +164 -0
- package/bizar-dash/src/web/components/chat/ConfirmModal.tsx +54 -0
- package/bizar-dash/src/web/components/chat/EmptyState.tsx +21 -0
- package/bizar-dash/src/web/components/chat/FirstRunGreeting.tsx +18 -0
- package/bizar-dash/src/web/components/chat/FloatingComposer.tsx +32 -0
- package/bizar-dash/src/web/components/chat/InfoPanel.tsx +119 -0
- package/bizar-dash/src/web/components/chat/LoadingSkeleton.tsx +19 -0
- package/bizar-dash/src/web/components/chat/MessageBubble.tsx +60 -0
- package/bizar-dash/src/web/components/chat/SessionList.tsx +153 -0
- package/bizar-dash/src/web/components/chat/StreamingIndicator.tsx +9 -0
- package/bizar-dash/src/web/components/chat/SuggestionCards.tsx +55 -0
- package/bizar-dash/src/web/components/chat/WelcomeScreen.tsx +36 -0
- package/bizar-dash/src/web/components/chat/index.ts +20 -0
- package/bizar-dash/src/web/components/chat/useAutoGrowTextarea.ts +18 -0
- package/bizar-dash/src/web/components/chat/useChat.ts +219 -0
- package/bizar-dash/src/web/components/chat/useSlashCommands.ts +47 -0
- package/bizar-dash/src/web/index.html +37 -0
- package/bizar-dash/src/web/lib/api.ts +193 -0
- package/bizar-dash/src/web/lib/markdown.tsx +59 -0
- package/bizar-dash/src/web/lib/types.ts +668 -0
- package/bizar-dash/src/web/lib/utils.ts +114 -0
- package/bizar-dash/src/web/lib/ws.ts +176 -0
- package/bizar-dash/src/web/main.tsx +34 -0
- package/bizar-dash/src/web/mobile/MobileApp.tsx +278 -0
- package/bizar-dash/src/web/mobile/MobileBottomNav.tsx +38 -0
- package/bizar-dash/src/web/mobile/MobileTopbar.tsx +115 -0
- package/bizar-dash/src/web/mobile/components/MobileBottomSheet.tsx +182 -0
- package/bizar-dash/src/web/mobile/components/MobileListItem.tsx +64 -0
- package/bizar-dash/src/web/mobile/components/MobileModal.tsx +123 -0
- package/bizar-dash/src/web/mobile/views/MobileActivity.tsx +273 -0
- package/bizar-dash/src/web/mobile/views/MobileAgents.tsx +374 -0
- package/bizar-dash/src/web/mobile/views/MobileArtifactCanvas.tsx +439 -0
- package/bizar-dash/src/web/mobile/views/MobileArtifacts.tsx +143 -0
- package/bizar-dash/src/web/mobile/views/MobileChat.tsx +180 -0
- package/bizar-dash/src/web/mobile/views/MobileConfig.tsx +175 -0
- package/bizar-dash/src/web/mobile/views/MobileHistory.tsx +191 -0
- package/bizar-dash/src/web/mobile/views/MobileMods.tsx +149 -0
- package/bizar-dash/src/web/mobile/views/MobileMore.tsx +121 -0
- package/bizar-dash/src/web/mobile/views/MobileNotifications.tsx +137 -0
- package/bizar-dash/src/web/mobile/views/MobilePlans.tsx +26 -0
- package/bizar-dash/src/web/mobile/views/MobileSchedules.tsx +685 -0
- package/bizar-dash/src/web/mobile/views/MobileSearchModal.tsx +172 -0
- package/bizar-dash/src/web/mobile/views/MobileSettings.tsx +388 -0
- package/bizar-dash/src/web/mobile/views/MobileSkills.tsx +153 -0
- package/bizar-dash/src/web/mobile/views/MobileTasks.tsx +564 -0
- package/bizar-dash/src/web/mobile.html +20 -0
- package/bizar-dash/src/web/mobile.tsx +13 -0
- package/bizar-dash/src/web/styles/chat.css +198 -0
- package/bizar-dash/src/web/styles/glyphs.css +960 -0
- package/bizar-dash/src/web/styles/main.css +8855 -0
- package/bizar-dash/src/web/styles/mobile-chat.css +5 -0
- package/bizar-dash/src/web/styles/mobile.css +1960 -0
- package/bizar-dash/src/web/views/Activity.tsx +1203 -0
- package/bizar-dash/src/web/views/Agents.tsx +701 -0
- package/bizar-dash/src/web/views/Artifacts.tsx +1480 -0
- package/bizar-dash/src/web/views/BackgroundAgents.tsx +450 -0
- package/bizar-dash/src/web/views/Chat.tsx +190 -0
- package/bizar-dash/src/web/views/Config.tsx +1603 -0
- package/bizar-dash/src/web/views/History.tsx +335 -0
- package/bizar-dash/src/web/views/ModView.tsx +207 -0
- package/bizar-dash/src/web/views/Mods.tsx +693 -0
- package/bizar-dash/src/web/views/Overview.tsx +812 -0
- package/bizar-dash/src/web/views/Providers.tsx +302 -0
- package/bizar-dash/src/web/views/Schedules.tsx +802 -0
- package/bizar-dash/src/web/views/Settings.tsx +1787 -0
- package/bizar-dash/src/web/views/Skills.tsx +431 -0
- package/bizar-dash/src/web/views/Tasks.tsx +1523 -0
- package/bizar-dash/src/web/views/glyphs/GlyphRenderer.tsx +613 -0
- package/bizar-dash/src/web/views/glyphs/components.tsx +1098 -0
- package/bizar-dash/templates/mod/FORMAT.md +76 -0
- package/bizar-dash/templates/mod/hello-mod/README.md +19 -0
- package/bizar-dash/templates/mod/hello-mod/agents/greeter.md +8 -0
- package/bizar-dash/templates/mod/hello-mod/commands/hello.md +6 -0
- package/bizar-dash/templates/mod/hello-mod/mod.json +20 -0
- package/bizar-dash/templates/mod/hello-mod/routes/ping.mjs +9 -0
- package/bizar-dash/templates/mod/hello-mod/views/HelloView.tsx +10 -0
- package/bizar-dash/tests/dashboard-ports.test.mjs +138 -0
- package/bizar-dash/tests/graphify-mod-spawn.node.test.mjs +90 -0
- package/bizar-dash/tests/memory-git.test.mjs +138 -0
- package/bizar-dash/tests/memory-schema.test.mjs +198 -0
- package/bizar-dash/tests/memory-secrets.test.mjs +121 -0
- package/bizar-dash/tests/memory-store.test.mjs +390 -0
- package/bizar-dash/tests/memory-sync.test.mjs +141 -0
- package/bizar-dash/tests/mod-instructions.node.test.mjs +188 -0
- package/bizar-dash/tests/mod-security.test.mjs +273 -0
- package/bizar-dash/tests/mod-upgrade.node.test.mjs +357 -0
- package/bizar-dash/tests/no-agent-browser.node.test.mjs +98 -0
- package/bizar-dash/tests/obsidian-back-compat.test.mjs +199 -0
- package/bizar-dash/tests/opencode-runner.test.mjs +172 -0
- package/bizar-dash/tests/path-safe.test.mjs +108 -0
- package/bizar-dash/tests/providers-store-backup-keys.node.test.mjs +333 -0
- package/bizar-dash/tests/smoke-v2.mjs +212 -0
- package/bizar-dash/tests/strip-thinking.test.mjs +175 -0
- package/bizar-dash/tests/submit-feedback.test.mjs +353 -0
- package/bizar-dash/tests/tmux-wrap.test.mjs +145 -0
- package/bizar-dash/tests/yaml.test.mjs +128 -0
- package/bizar-dash/tsconfig.json +23 -0
- package/bizar-dash/vite.config.ts +27 -0
- package/cli/atomic.mjs +73 -0
- package/cli/bin.mjs +43 -46
- package/cli/copy.mjs +3 -0
- package/cli/dev-link.mjs +17 -1
- package/cli/init.mjs +135 -1
- package/cli/install.mjs +10 -23
- package/cli/memory-constants.mjs +59 -0
- package/cli/memory.mjs +757 -0
- package/cli/service.mjs +5 -6
- package/config/skills/obsidian/SKILL.md +213 -65
- package/install.sh +48 -18
- package/package.json +49 -14
- package/packages/sdk/LICENSE +21 -0
- package/packages/sdk/README.md +80 -0
- package/packages/sdk/src/client.ts +183 -0
- package/packages/sdk/src/errors.ts +120 -0
- package/packages/sdk/src/events.ts +153 -0
- package/packages/sdk/src/index.ts +63 -0
- package/packages/sdk/src/types.ts +176 -0
- package/packages/sdk/src/version.ts +4 -0
- package/packages/sdk/tests/client.test.ts +217 -0
- package/packages/sdk/tests/errors.test.ts +108 -0
- package/packages/sdk/tests/events.test.ts +139 -0
- package/packages/sdk/tests/fixtures/fetch-mock.ts +152 -0
- package/packages/sdk/tests/fixtures/sse-mock.ts +30 -0
- package/packages/sdk/tsconfig.json +25 -0
- package/packages/sdk/vitest.config.ts +9 -0
- package/plugins/bizar/LICENSE +21 -0
- package/plugins/bizar/README.md +448 -0
- package/plugins/bizar/dist/index.js +29901 -0
- package/plugins/bizar/index.ts +1484 -0
- package/plugins/bizar/scripts/check-forbidden-imports.sh +33 -0
- package/plugins/bizar/src/background-state.ts +551 -0
- package/plugins/bizar/src/background.ts +1250 -0
- package/plugins/bizar/src/commands-impl.ts +464 -0
- package/plugins/bizar/src/commands.ts +1127 -0
- package/plugins/bizar/src/dashboard-client.ts +233 -0
- package/plugins/bizar/src/event-stream.ts +606 -0
- package/plugins/bizar/src/fingerprint.ts +120 -0
- package/plugins/bizar/src/handoff.ts +79 -0
- package/plugins/bizar/src/http-client.ts +467 -0
- package/plugins/bizar/src/key-rotation.ts +218 -0
- package/plugins/bizar/src/logger.ts +144 -0
- package/plugins/bizar/src/loop.ts +176 -0
- package/plugins/bizar/src/opencode-runner.ts +390 -0
- package/plugins/bizar/src/options.ts +421 -0
- package/plugins/bizar/src/plan-fs.ts +323 -0
- package/plugins/bizar/src/reasoning-clean.ts +454 -0
- package/plugins/bizar/src/report.ts +178 -0
- package/plugins/bizar/src/research-prompt.ts +35 -0
- package/plugins/bizar/src/serve-info.ts +228 -0
- package/plugins/bizar/src/serve.ts +496 -0
- package/plugins/bizar/src/settings.ts +349 -0
- package/plugins/bizar/src/state.ts +298 -0
- package/plugins/bizar/src/tools/bg-collect.ts +104 -0
- package/plugins/bizar/src/tools/bg-get-comments.ts +239 -0
- package/plugins/bizar/src/tools/bg-kill.ts +87 -0
- package/plugins/bizar/src/tools/bg-spawn.ts +401 -0
- package/plugins/bizar/src/tools/bg-status.ts +99 -0
- package/plugins/bizar/src/tools/plan-action.ts +767 -0
- package/plugins/bizar/src/tools/read-glyph-feedback.ts +170 -0
- package/plugins/bizar/src/tools/wait-for-feedback.ts +402 -0
- package/plugins/bizar/tests/attach-handler-bug.test.ts +169 -0
- package/plugins/bizar/tests/background-state.test.ts +277 -0
- package/plugins/bizar/tests/background.test.ts +402 -0
- package/plugins/bizar/tests/block.test.ts +195 -0
- package/plugins/bizar/tests/canonical-key-order.test.ts +75 -0
- package/plugins/bizar/tests/commands-impl.test.ts +442 -0
- package/plugins/bizar/tests/commands.test.ts +548 -0
- package/plugins/bizar/tests/config.test.ts +122 -0
- package/plugins/bizar/tests/dashboard-client.test.ts +159 -0
- package/plugins/bizar/tests/dispose.test.ts +336 -0
- package/plugins/bizar/tests/event-stream.test.ts +409 -0
- package/plugins/bizar/tests/event.test.ts +262 -0
- package/plugins/bizar/tests/fingerprint.test.ts +162 -0
- package/plugins/bizar/tests/http-client.test.ts +404 -0
- package/plugins/bizar/tests/init-helpers.test.ts +203 -0
- package/plugins/bizar/tests/integration/slash-command.test.ts +348 -0
- package/plugins/bizar/tests/integration/tool-routing.test.ts +314 -0
- package/plugins/bizar/tests/key-rotation.test.ts +396 -0
- package/plugins/bizar/tests/loop.test.ts +397 -0
- package/plugins/bizar/tests/options.test.ts +276 -0
- package/plugins/bizar/tests/reasoning-clean.test.ts +422 -0
- package/plugins/bizar/tests/serve.test.ts +339 -0
- package/plugins/bizar/tests/settings.test.ts +351 -0
- package/plugins/bizar/tests/stall-think.test.ts +750 -0
- package/plugins/bizar/tests/state.test.ts +276 -0
- package/plugins/bizar/tests/tools/bg-collect.test.ts +337 -0
- package/plugins/bizar/tests/tools/bg-get-comments.test.ts +485 -0
- package/plugins/bizar/tests/tools/bg-kill.test.ts +235 -0
- package/plugins/bizar/tests/tools/bg-spawn-delegation.test.ts +147 -0
- package/plugins/bizar/tests/tools/bg-spawn.test.ts +311 -0
- package/plugins/bizar/tests/tools/bg-status.test.ts +217 -0
- package/plugins/bizar/tests/tools/opencode-runner.test.ts +115 -0
- package/plugins/bizar/tests/tools/plan-action.test.ts +599 -0
- package/plugins/bizar/tests/tools/read-glyph-feedback.test.ts +253 -0
- package/plugins/bizar/tests/tools/wait-for-feedback.test.ts +390 -0
- package/plugins/bizar/tests/update-deadlock.test.ts +145 -0
- 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
|
+
}
|