@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.
- package/README.md +83 -16
- 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 +329 -0
- package/bizar-dash/src/server/memory-lightrag.mjs +767 -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 +471 -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 +658 -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 +2063 -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-cli.test.mjs +542 -0
- package/bizar-dash/tests/memory-config.test.mjs +422 -0
- package/bizar-dash/tests/memory-git.test.mjs +246 -0
- package/bizar-dash/tests/memory-lightrag.test.mjs +153 -0
- package/bizar-dash/tests/memory-protocol-drift.test.mjs +45 -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/banner.mjs +1 -1
- package/cli/bin.mjs +44 -47
- package/cli/bootstrap.mjs +1 -1
- package/cli/copy.mjs +25 -16
- package/cli/dev-link.mjs +17 -1
- package/cli/doctor.mjs +4 -4
- package/cli/doctor.test.mjs +2 -2
- package/cli/init.mjs +135 -1
- package/cli/install.mjs +31 -39
- package/cli/memory-constants.mjs +59 -0
- package/cli/memory.mjs +1436 -0
- package/cli/service.mjs +5 -6
- package/cli/utils.mjs +6 -3
- package/config/AGENTS.md +7 -7
- package/config/agents/_shared/AGENT_BASELINE.md +59 -61
- package/config/opencode.json +13 -38
- package/config/skills/memory-protocol/SKILL.md +105 -0
- package/config/skills/obsidian/SKILL.md +270 -65
- package/install.sh +59 -19
- 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 +1491 -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 +1168 -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/open-kb.ts +191 -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 +584 -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,812 @@
|
|
|
1
|
+
// src/views/Overview.tsx — v3.7.0 activity cards + SSE stream.
|
|
2
|
+
import { useEffect, useRef, useState } from 'react';
|
|
3
|
+
import {
|
|
4
|
+
Bot,
|
|
5
|
+
CheckSquare,
|
|
6
|
+
Folder,
|
|
7
|
+
LayoutDashboard,
|
|
8
|
+
Map,
|
|
9
|
+
MessageSquare,
|
|
10
|
+
RefreshCw,
|
|
11
|
+
PlayCircle,
|
|
12
|
+
ShieldCheck,
|
|
13
|
+
FileText,
|
|
14
|
+
Zap,
|
|
15
|
+
Plus,
|
|
16
|
+
Trash2,
|
|
17
|
+
Power,
|
|
18
|
+
Send,
|
|
19
|
+
Sparkles,
|
|
20
|
+
AlertOctagon,
|
|
21
|
+
AlertTriangle,
|
|
22
|
+
CheckCircle2,
|
|
23
|
+
Activity,
|
|
24
|
+
Puzzle,
|
|
25
|
+
Clock,
|
|
26
|
+
FolderSearch,
|
|
27
|
+
EyeOff,
|
|
28
|
+
Eye,
|
|
29
|
+
X,
|
|
30
|
+
} from 'lucide-react';
|
|
31
|
+
import { Card, CardTitle, CardMeta } from '../components/Card';
|
|
32
|
+
import { Button } from '../components/Button';
|
|
33
|
+
import { EmptyState } from '../components/EmptyState';
|
|
34
|
+
import { Spinner } from '../components/Spinner';
|
|
35
|
+
import { useToast } from '../components/Toast';
|
|
36
|
+
import { useModal } from '../components/Modal';
|
|
37
|
+
import { FileBrowser } from '../components/FileBrowser';
|
|
38
|
+
import { api } from '../lib/api';
|
|
39
|
+
import { formatRelative, formatTime } from '../lib/utils';
|
|
40
|
+
import type { Overview, Settings, Snapshot, ActivityItem, ProjectRecord, Mod, ScanResult, DirectoryListing } from '../lib/types';
|
|
41
|
+
import { cn } from '../lib/utils';
|
|
42
|
+
|
|
43
|
+
type Props = {
|
|
44
|
+
snapshot: Snapshot;
|
|
45
|
+
settings: Settings;
|
|
46
|
+
activeTab: string;
|
|
47
|
+
setActiveTab: (id: string) => void;
|
|
48
|
+
refreshSnapshot: () => Promise<void>;
|
|
49
|
+
};
|
|
50
|
+
|
|
51
|
+
export function Overview({
|
|
52
|
+
snapshot,
|
|
53
|
+
settings,
|
|
54
|
+
setActiveTab,
|
|
55
|
+
refreshSnapshot,
|
|
56
|
+
}: Props) {
|
|
57
|
+
const toast = useToast();
|
|
58
|
+
const modal = useModal();
|
|
59
|
+
const inputRef = useRef<HTMLTextAreaElement | null>(null);
|
|
60
|
+
const [overview, setOverview] = useState<Overview | null>(
|
|
61
|
+
snapshot.overview ?? null,
|
|
62
|
+
);
|
|
63
|
+
const [loading, setLoading] = useState(!snapshot.overview);
|
|
64
|
+
const [projects, setProjects] = useState<ProjectRecord[]>(snapshot.projects || []);
|
|
65
|
+
const [activeId, setActiveId] = useState<string | null>(
|
|
66
|
+
snapshot.activeProject?.id || null,
|
|
67
|
+
);
|
|
68
|
+
const [mods, setMods] = useState<Mod[]>(snapshot.mods || []);
|
|
69
|
+
const [submitting, setSubmitting] = useState(false);
|
|
70
|
+
// v3.7.0 — Live activity feed via SSE
|
|
71
|
+
const [activityItems, setActivityItems] = useState<ActivityItem[]>(
|
|
72
|
+
snapshot.overview?.recentActivity ?? [],
|
|
73
|
+
);
|
|
74
|
+
const [activityExpanded, setActivityExpanded] = useState(false);
|
|
75
|
+
// v3.15.0 — Hidden event keys are stored server-side; we just track
|
|
76
|
+
// the set in client state. Hiding does NOT delete — see Settings →
|
|
77
|
+
// Activity Log for the full feed.
|
|
78
|
+
const [hiddenKeys, setHiddenKeys] = useState<Set<string>>(new Set());
|
|
79
|
+
|
|
80
|
+
// v3.15.0 — Load hidden set once on mount
|
|
81
|
+
useEffect(() => {
|
|
82
|
+
let cancelled = false;
|
|
83
|
+
(async () => {
|
|
84
|
+
try {
|
|
85
|
+
const r = await api.get<{ hidden: string[] }>('/activity/hidden');
|
|
86
|
+
if (!cancelled) setHiddenKeys(new Set(r.hidden || []));
|
|
87
|
+
} catch { /* non-fatal */ }
|
|
88
|
+
})();
|
|
89
|
+
return () => { cancelled = true; };
|
|
90
|
+
}, []);
|
|
91
|
+
|
|
92
|
+
// v3.15.0 — Helper: stable key per activity item
|
|
93
|
+
const itemKey = (it: ActivityItem, idx: number): string => {
|
|
94
|
+
const k = `${it.kind || ''}|${it.ts || ''}|${(it.slug as string) || (it.title as string) || ''}|${idx}`;
|
|
95
|
+
// hash to a short key (matches server-side activityKey derivation)
|
|
96
|
+
let h = 0;
|
|
97
|
+
for (let i = 0; i < k.length; i++) h = ((h << 5) - h + k.charCodeAt(i)) | 0;
|
|
98
|
+
return Math.abs(h).toString(16).padStart(8, '0').slice(0, 16);
|
|
99
|
+
};
|
|
100
|
+
|
|
101
|
+
const onHide = async (key: string) => {
|
|
102
|
+
const next = new Set(hiddenKeys);
|
|
103
|
+
next.add(key);
|
|
104
|
+
setHiddenKeys(next);
|
|
105
|
+
try {
|
|
106
|
+
await api.post('/activity/hide', { keys: [key] });
|
|
107
|
+
} catch (err) {
|
|
108
|
+
// rollback on failure
|
|
109
|
+
const rollback = new Set(hiddenKeys);
|
|
110
|
+
setHiddenKeys(rollback);
|
|
111
|
+
toast.error(`Hide failed: ${(err as Error).message}`);
|
|
112
|
+
}
|
|
113
|
+
};
|
|
114
|
+
|
|
115
|
+
const onClearAll = async () => {
|
|
116
|
+
if (!confirm('Hide every recent activity item from the overview? The full log stays in Settings → Activity Log.')) return;
|
|
117
|
+
const all = activityItems.map((it, idx) => itemKey(it, idx));
|
|
118
|
+
const next = new Set(hiddenKeys);
|
|
119
|
+
all.forEach((k) => next.add(k));
|
|
120
|
+
setHiddenKeys(next);
|
|
121
|
+
try {
|
|
122
|
+
await api.post('/activity/hide', { keys: all });
|
|
123
|
+
toast.success(`Hidden ${all.length} item(s). Restore them in Settings → Activity Log.`);
|
|
124
|
+
} catch (err) {
|
|
125
|
+
toast.error(`Clear failed: ${(err as Error).message}`);
|
|
126
|
+
}
|
|
127
|
+
};
|
|
128
|
+
|
|
129
|
+
const onRestoreAll = async () => {
|
|
130
|
+
setHiddenKeys(new Set());
|
|
131
|
+
try {
|
|
132
|
+
await api.del('/activity/hide');
|
|
133
|
+
toast.success('All hidden activity restored to the overview.');
|
|
134
|
+
} catch (err) {
|
|
135
|
+
toast.error(`Restore failed: ${(err as Error).message}`);
|
|
136
|
+
}
|
|
137
|
+
};
|
|
138
|
+
|
|
139
|
+
// v3.7.0 — Sync initial snapshot data
|
|
140
|
+
useEffect(() => {
|
|
141
|
+
if (snapshot.overview) {
|
|
142
|
+
setOverview(snapshot.overview);
|
|
143
|
+
setActivityItems(snapshot.overview.recentActivity ?? []);
|
|
144
|
+
setLoading(false);
|
|
145
|
+
}
|
|
146
|
+
setProjects(snapshot.projects || []);
|
|
147
|
+
setActiveId(snapshot.activeProject?.id || null);
|
|
148
|
+
setMods(snapshot.mods || []);
|
|
149
|
+
}, [snapshot.overview, snapshot.projects, snapshot.activeProject, snapshot.mods]);
|
|
150
|
+
|
|
151
|
+
// v3.7.0 — Subscribe to SSE activity stream
|
|
152
|
+
// v3.6.0 — Append ?token=… for auth. EventSource can't set custom
|
|
153
|
+
// headers, so the server accepts the token via query string too.
|
|
154
|
+
useEffect(() => {
|
|
155
|
+
let es: EventSource;
|
|
156
|
+
try {
|
|
157
|
+
const tok = api.getToken();
|
|
158
|
+
const url = tok
|
|
159
|
+
? `/api/activity/stream?token=${encodeURIComponent(tok)}`
|
|
160
|
+
: '/api/activity/stream';
|
|
161
|
+
es = new EventSource(url);
|
|
162
|
+
es.addEventListener('snapshot', (e) => {
|
|
163
|
+
try {
|
|
164
|
+
const parsed = JSON.parse((e as MessageEvent).data) as { events: ActivityItem[]; generatedAt?: string };
|
|
165
|
+
setActivityItems(Array.isArray(parsed.events) ? parsed.events.slice(0, 50) : []);
|
|
166
|
+
} catch { /* ignore parse errors */ }
|
|
167
|
+
});
|
|
168
|
+
es.addEventListener('activity', (e) => {
|
|
169
|
+
try {
|
|
170
|
+
const entry = JSON.parse((e as MessageEvent).data) as ActivityItem;
|
|
171
|
+
setActivityItems((cur) => [entry, ...cur].slice(0, 50));
|
|
172
|
+
} catch { /* ignore parse errors */ }
|
|
173
|
+
});
|
|
174
|
+
} catch { /* SSE not available — fallback to snapshot data */ }
|
|
175
|
+
return () => { try { es?.close(); } catch { /* ignore */ } };
|
|
176
|
+
}, []);
|
|
177
|
+
|
|
178
|
+
const onRefresh = async () => {
|
|
179
|
+
toast.info('Refreshing…', 1500);
|
|
180
|
+
await refreshSnapshot();
|
|
181
|
+
try {
|
|
182
|
+
const data = await api.get<{ projects: ProjectRecord[]; active: string | null }>('/projects');
|
|
183
|
+
setProjects(data.projects || []);
|
|
184
|
+
setActiveId(data.active || null);
|
|
185
|
+
} catch { /* ignore */ }
|
|
186
|
+
};
|
|
187
|
+
|
|
188
|
+
const onAddProject = () => {
|
|
189
|
+
modal.open({
|
|
190
|
+
title: 'Add project',
|
|
191
|
+
children: (
|
|
192
|
+
<AddProjectDialog
|
|
193
|
+
settings={settings}
|
|
194
|
+
onAdd={async (path: string, name: string | null) => {
|
|
195
|
+
try {
|
|
196
|
+
const r = await api.post<ProjectRecord>('/projects', { path, name });
|
|
197
|
+
setProjects((cur) => [...cur.filter((p) => p.id !== r.id), r]);
|
|
198
|
+
toast.success('Project added.');
|
|
199
|
+
modal.close();
|
|
200
|
+
} catch (err) {
|
|
201
|
+
toast.error(`Add failed: ${(err as Error).message}`);
|
|
202
|
+
}
|
|
203
|
+
}}
|
|
204
|
+
/>
|
|
205
|
+
),
|
|
206
|
+
footer: (
|
|
207
|
+
<div className="modal-footer-actions">
|
|
208
|
+
<Button variant="ghost" onClick={() => modal.close()}>Cancel</Button>
|
|
209
|
+
</div>
|
|
210
|
+
),
|
|
211
|
+
});
|
|
212
|
+
};
|
|
213
|
+
|
|
214
|
+
const onUseCurrentDir = async () => {
|
|
215
|
+
try {
|
|
216
|
+
const data = await api.post<{ projects: ProjectRecord[]; active: string | null }>(
|
|
217
|
+
'/projects/auto-detect',
|
|
218
|
+
);
|
|
219
|
+
setProjects(data.projects || []);
|
|
220
|
+
setActiveId(data.active || null);
|
|
221
|
+
const newActive = data.projects?.find((p) => p.id === data.active);
|
|
222
|
+
toast.success(newActive ? `Active: ${newActive.name}` : 'Projects refreshed.');
|
|
223
|
+
await refreshSnapshot();
|
|
224
|
+
} catch (err) {
|
|
225
|
+
toast.error(`Auto-detect failed: ${(err as Error).message}`);
|
|
226
|
+
}
|
|
227
|
+
};
|
|
228
|
+
|
|
229
|
+
const onActivate = async (id: string) => {
|
|
230
|
+
try {
|
|
231
|
+
await api.post(`/projects/${encodeURIComponent(id)}/activate`);
|
|
232
|
+
setActiveId(id);
|
|
233
|
+
toast.success('Project activated.');
|
|
234
|
+
await refreshSnapshot();
|
|
235
|
+
} catch (err) {
|
|
236
|
+
toast.error(`Activate failed: ${(err as Error).message}`);
|
|
237
|
+
}
|
|
238
|
+
};
|
|
239
|
+
|
|
240
|
+
const onRemove = async (id: string) => {
|
|
241
|
+
if (!confirm(`Remove project "${id}" from the registry?`)) return;
|
|
242
|
+
try {
|
|
243
|
+
await api.del(`/projects/${encodeURIComponent(id)}`);
|
|
244
|
+
setProjects((cur) => cur.filter((p) => p.id !== id));
|
|
245
|
+
if (activeId === id) setActiveId(null);
|
|
246
|
+
toast.success('Project removed.');
|
|
247
|
+
} catch (err) {
|
|
248
|
+
toast.error(`Remove failed: ${(err as Error).message}`);
|
|
249
|
+
}
|
|
250
|
+
};
|
|
251
|
+
|
|
252
|
+
if (loading || !overview) {
|
|
253
|
+
return (
|
|
254
|
+
<div className="view-loading">
|
|
255
|
+
<Spinner size="lg" />
|
|
256
|
+
<p>Loading overview…</p>
|
|
257
|
+
</div>
|
|
258
|
+
);
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
return (
|
|
262
|
+
<div className="view view-overview">
|
|
263
|
+
{/* v3.4.0 — Hero takes the spotlight. No card wrapper. */}
|
|
264
|
+
<div className="overview-hero-noframe">
|
|
265
|
+
<h1>What do you want to do?</h1>
|
|
266
|
+
<p className="overview-hero-subtitle">
|
|
267
|
+
Describe what you want — Odin will split it into tasks, create a plan,
|
|
268
|
+
delegate to background agents, and track progress in real time.
|
|
269
|
+
</p>
|
|
270
|
+
<form
|
|
271
|
+
className="overview-hero-form-noframe"
|
|
272
|
+
onSubmit={async (e) => {
|
|
273
|
+
e.preventDefault();
|
|
274
|
+
const text = (inputRef.current?.value || '').trim();
|
|
275
|
+
if (!text) return;
|
|
276
|
+
setSubmitting(true);
|
|
277
|
+
try {
|
|
278
|
+
const r = await api.post<{ subtasks?: unknown[] }>('/tasks/submit', { title: text });
|
|
279
|
+
toast.success(`Odin split it into ${(r.subtasks?.length || 1)} task(s)`);
|
|
280
|
+
if (inputRef.current) inputRef.current.value = '';
|
|
281
|
+
await refreshSnapshot();
|
|
282
|
+
} catch (err) {
|
|
283
|
+
toast.error(`Failed: ${(err as Error).message}`);
|
|
284
|
+
} finally {
|
|
285
|
+
setSubmitting(false);
|
|
286
|
+
}
|
|
287
|
+
}}
|
|
288
|
+
>
|
|
289
|
+
<textarea
|
|
290
|
+
ref={inputRef}
|
|
291
|
+
className="overview-input-hero"
|
|
292
|
+
placeholder="e.g. Implement user authentication with email + password, including registration, login, password reset, and integration tests. Use Bcrypt, JWT tokens, and the existing API style."
|
|
293
|
+
disabled={submitting}
|
|
294
|
+
/>
|
|
295
|
+
<div style={{ display: 'flex', gap: 'var(--space-2)', alignItems: 'center', flexWrap: 'wrap' }}>
|
|
296
|
+
<Button
|
|
297
|
+
type="submit"
|
|
298
|
+
variant="primary"
|
|
299
|
+
size="lg"
|
|
300
|
+
disabled={submitting}
|
|
301
|
+
>
|
|
302
|
+
{submitting ? <Spinner size="sm" /> : <Send size={16} />}
|
|
303
|
+
Submit to Odin
|
|
304
|
+
</Button>
|
|
305
|
+
<span className="muted" style={{ fontSize: 12 }}>
|
|
306
|
+
<Sparkles size={12} style={{ display: 'inline', verticalAlign: -2, color: 'var(--accent)' }} />
|
|
307
|
+
{' '}Odin + 12 specialist agents available
|
|
308
|
+
</span>
|
|
309
|
+
</div>
|
|
310
|
+
</form>
|
|
311
|
+
|
|
312
|
+
<div className="overview-quick-actions-row">
|
|
313
|
+
{['Implement feature', 'Fix bug', 'Refactor', 'Investigate', 'Add tests', 'Document', 'Optimize', 'Deploy'].map((action) => (
|
|
314
|
+
<button
|
|
315
|
+
key={action}
|
|
316
|
+
type="button"
|
|
317
|
+
className="overview-quick-chip"
|
|
318
|
+
onClick={() => {
|
|
319
|
+
if (inputRef.current) {
|
|
320
|
+
inputRef.current.value = action;
|
|
321
|
+
inputRef.current.focus();
|
|
322
|
+
}
|
|
323
|
+
}}
|
|
324
|
+
>
|
|
325
|
+
{action}
|
|
326
|
+
</button>
|
|
327
|
+
))}
|
|
328
|
+
</div>
|
|
329
|
+
</div>
|
|
330
|
+
|
|
331
|
+
<div className="overview-feed">
|
|
332
|
+
<div className="overview-feed-head">
|
|
333
|
+
<h2>Recent activity</h2>
|
|
334
|
+
<div className="overview-feed-head-actions">
|
|
335
|
+
{hiddenKeys.size > 0 && (
|
|
336
|
+
<Button variant="ghost" size="sm" onClick={onRestoreAll} title="Restore hidden items to this overview">
|
|
337
|
+
<Eye size={12} /> Show {hiddenKeys.size} hidden
|
|
338
|
+
</Button>
|
|
339
|
+
)}
|
|
340
|
+
{activityItems.length > 8 && (
|
|
341
|
+
<Button variant="ghost" size="sm" onClick={() => setActivityExpanded((v) => !v)}>
|
|
342
|
+
{activityExpanded ? 'Show less' : 'Show all'}
|
|
343
|
+
</Button>
|
|
344
|
+
)}
|
|
345
|
+
{activityItems.length > 0 && (
|
|
346
|
+
<Button variant="ghost" size="sm" onClick={onClearAll} title="Hide every item from the overview (full log kept)">
|
|
347
|
+
<EyeOff size={12} /> Hide all
|
|
348
|
+
</Button>
|
|
349
|
+
)}
|
|
350
|
+
</div>
|
|
351
|
+
</div>
|
|
352
|
+
{hiddenKeys.size > 0 && (
|
|
353
|
+
<div className="activity-hidden-banner" role="status">
|
|
354
|
+
<span>
|
|
355
|
+
<EyeOff size={12} style={{ verticalAlign: -2, marginRight: 6 }} />
|
|
356
|
+
{hiddenKeys.size} item{hiddenKeys.size === 1 ? '' : 's'} hidden from the overview.
|
|
357
|
+
</span>
|
|
358
|
+
<Button variant="ghost" size="sm" onClick={onRestoreAll}>
|
|
359
|
+
<Eye size={12} /> Show them again
|
|
360
|
+
</Button>
|
|
361
|
+
</div>
|
|
362
|
+
)}
|
|
363
|
+
{activityItems.length === 0 ? (
|
|
364
|
+
<div className="muted" style={{ padding: '24px 0', fontSize: 13 }}>
|
|
365
|
+
No activity yet. Use the chat above or invoke a Bizar command to start a feed.
|
|
366
|
+
</div>
|
|
367
|
+
) : (
|
|
368
|
+
<div className={cn('activity-feed-list-wrap', !activityExpanded && 'activity-feed-list-wrap-collapsed')}>
|
|
369
|
+
<div className="activity-feed-list">
|
|
370
|
+
{activityItems.slice(0, 30).map((it, idx) => {
|
|
371
|
+
const k = itemKey(it, idx);
|
|
372
|
+
if (hiddenKeys.has(k)) return null;
|
|
373
|
+
return (
|
|
374
|
+
<ActivityFeedItem
|
|
375
|
+
key={`${it.ts}-${idx}`}
|
|
376
|
+
item={it}
|
|
377
|
+
activityKey={k}
|
|
378
|
+
onNavigate={setActiveTab}
|
|
379
|
+
onHide={onHide}
|
|
380
|
+
/>
|
|
381
|
+
);
|
|
382
|
+
})}
|
|
383
|
+
</div>
|
|
384
|
+
{!activityExpanded && activityItems.length > 8 && <div className="activity-feed-fade" aria-hidden="true" />}
|
|
385
|
+
</div>
|
|
386
|
+
)}
|
|
387
|
+
</div>
|
|
388
|
+
|
|
389
|
+
{/* v3.4.0 — Below-the-fold: projects + meta (compact) */}
|
|
390
|
+
<Card className="project-picker">
|
|
391
|
+
<CardTitle>
|
|
392
|
+
<Folder size={14} /> Projects
|
|
393
|
+
<Button variant="ghost" size="sm" style={{ marginLeft: 'auto' }} onClick={onAddProject}>
|
|
394
|
+
<Plus size={12} /> Add
|
|
395
|
+
</Button>
|
|
396
|
+
<Button variant="ghost" size="sm" onClick={onUseCurrentDir} title="Use the server's working directory">
|
|
397
|
+
<Plus size={12} /> Auto-detect
|
|
398
|
+
</Button>
|
|
399
|
+
{settings?.dashboard?.projectsDirectory && (
|
|
400
|
+
<Button
|
|
401
|
+
variant="ghost"
|
|
402
|
+
size="sm"
|
|
403
|
+
title={`Scan ${settings.dashboard.projectsDirectory} for projects`}
|
|
404
|
+
onClick={async () => {
|
|
405
|
+
try {
|
|
406
|
+
const r = await api.post<ScanResult>('/projects/scan');
|
|
407
|
+
if (r.error) {
|
|
408
|
+
toast.error(r.error);
|
|
409
|
+
} else {
|
|
410
|
+
toast.success(`Added ${r.added.length}, skipped ${r.skipped}.`);
|
|
411
|
+
}
|
|
412
|
+
await refreshSnapshot();
|
|
413
|
+
const data = await api.get<{ projects: ProjectRecord[]; active: string | null }>('/projects');
|
|
414
|
+
setProjects(data.projects || []);
|
|
415
|
+
setActiveId(data.active || null);
|
|
416
|
+
} catch (err) {
|
|
417
|
+
toast.error(`Scan failed: ${(err as Error).message}`);
|
|
418
|
+
}
|
|
419
|
+
}}
|
|
420
|
+
>
|
|
421
|
+
<FolderSearch size={12} /> Scan
|
|
422
|
+
</Button>
|
|
423
|
+
)}
|
|
424
|
+
<Button variant="ghost" size="sm" onClick={onRefresh} title="Refresh">
|
|
425
|
+
<RefreshCw size={12} />
|
|
426
|
+
</Button>
|
|
427
|
+
</CardTitle>
|
|
428
|
+
<CardMeta>
|
|
429
|
+
{projects.length} project{projects.length === 1 ? '' : 's'} ·
|
|
430
|
+
{' '}{overview.counts.agents} agents ·
|
|
431
|
+
{' '}{overview.counts.sessions} session{overview.counts.sessions === 1 ? '' : 's'}
|
|
432
|
+
</CardMeta>
|
|
433
|
+
{projects.length === 0 ? (
|
|
434
|
+
<EmptyState
|
|
435
|
+
icon={<Folder size={32} />}
|
|
436
|
+
title="No projects yet"
|
|
437
|
+
message="Add a project to start tracking its tasks, plans, and schedules."
|
|
438
|
+
action={
|
|
439
|
+
<div className="empty-state-actions">
|
|
440
|
+
<Button variant="primary" onClick={onUseCurrentDir}>
|
|
441
|
+
<Plus size={14} /> Use current directory
|
|
442
|
+
</Button>
|
|
443
|
+
<Button variant="secondary" onClick={onAddProject}>
|
|
444
|
+
Add by path…
|
|
445
|
+
</Button>
|
|
446
|
+
</div>
|
|
447
|
+
}
|
|
448
|
+
/>
|
|
449
|
+
) : (
|
|
450
|
+
<div className="project-grid">
|
|
451
|
+
{projects.map((p) => (
|
|
452
|
+
<ProjectCard
|
|
453
|
+
key={p.id}
|
|
454
|
+
project={p}
|
|
455
|
+
active={activeId === p.id}
|
|
456
|
+
onOpen={() => onActivate(p.id)}
|
|
457
|
+
onRemove={() => onRemove(p.id)}
|
|
458
|
+
/>
|
|
459
|
+
))}
|
|
460
|
+
</div>
|
|
461
|
+
)}
|
|
462
|
+
</Card>
|
|
463
|
+
|
|
464
|
+
<div className="overview-cols">
|
|
465
|
+
<Card>
|
|
466
|
+
<CardTitle>Mods</CardTitle>
|
|
467
|
+
<CardMeta>Extensions installed under <code>~/.config/bizar/mods/</code></CardMeta>
|
|
468
|
+
{mods.length === 0 ? (
|
|
469
|
+
<div className="muted">No mods installed.</div>
|
|
470
|
+
) : (
|
|
471
|
+
<ul className="mod-mini-list">
|
|
472
|
+
{mods.map((m) => (
|
|
473
|
+
<li key={m.id} className="mod-mini">
|
|
474
|
+
<span className="mod-mini-name">{m.name}</span>
|
|
475
|
+
<span className="mod-mini-meta">v{m.version} · {m.type}</span>
|
|
476
|
+
<span className={`mod-mini-pill ${m.enabled ? 'mod-mini-pill-on' : 'mod-mini-pill-off'}`}>
|
|
477
|
+
{m.enabled ? 'on' : 'off'}
|
|
478
|
+
</span>
|
|
479
|
+
</li>
|
|
480
|
+
))}
|
|
481
|
+
</ul>
|
|
482
|
+
)}
|
|
483
|
+
</Card>
|
|
484
|
+
|
|
485
|
+
<Card>
|
|
486
|
+
<CardTitle>Environment</CardTitle>
|
|
487
|
+
<CardMeta>Runtime + paths</CardMeta>
|
|
488
|
+
<dl className="env-table">
|
|
489
|
+
<dt>Node</dt>
|
|
490
|
+
<dd className="mono">{overview.versions.node}</dd>
|
|
491
|
+
<dt>Platform</dt>
|
|
492
|
+
<dd className="mono">{overview.versions.platform}</dd>
|
|
493
|
+
<dt>Project root</dt>
|
|
494
|
+
<dd className="mono ellipsis" title={overview.versions.projectRoot}>
|
|
495
|
+
{overview.versions.projectRoot}
|
|
496
|
+
</dd>
|
|
497
|
+
<dt>Bizar root</dt>
|
|
498
|
+
<dd className="mono ellipsis" title={overview.versions.bizarRoot}>
|
|
499
|
+
{overview.versions.bizarRoot}
|
|
500
|
+
</dd>
|
|
501
|
+
<dt>Generated</dt>
|
|
502
|
+
<dd className="mono tabular-nums">
|
|
503
|
+
{formatTime(overview.generatedAt)}
|
|
504
|
+
</dd>
|
|
505
|
+
</dl>
|
|
506
|
+
</Card>
|
|
507
|
+
</div>
|
|
508
|
+
</div>
|
|
509
|
+
);
|
|
510
|
+
}
|
|
511
|
+
|
|
512
|
+
// ─── AddProjectDialog ─────────────────────────────────────────────────────────
|
|
513
|
+
|
|
514
|
+
/**
|
|
515
|
+
* Pre-flight approach: before POST /projects we verify the selected path
|
|
516
|
+
* still exists (GET /fs?path=...). This avoids the ugly 404 toast when a
|
|
517
|
+
* directory is deleted between selection and submit.
|
|
518
|
+
*/
|
|
519
|
+
function AddProjectDialog({
|
|
520
|
+
settings,
|
|
521
|
+
onAdd,
|
|
522
|
+
}: {
|
|
523
|
+
settings: Settings;
|
|
524
|
+
onAdd: (path: string, name: string | null) => void;
|
|
525
|
+
}) {
|
|
526
|
+
const [path, setPath] = useState(settings?.dashboard?.projectsDirectory ?? '');
|
|
527
|
+
const [name, setName] = useState('');
|
|
528
|
+
const [preflighting, setPreflighting] = useState(false);
|
|
529
|
+
const [preflightError, setPreflightError] = useState<string | null>(null);
|
|
530
|
+
|
|
531
|
+
const handleAdd = async () => {
|
|
532
|
+
if (!path) return;
|
|
533
|
+
setPreflighting(true);
|
|
534
|
+
setPreflightError(null);
|
|
535
|
+
try {
|
|
536
|
+
// Pre-flight: verify the path still exists
|
|
537
|
+
await api.get<DirectoryListing>('/fs?path=' + encodeURIComponent(path));
|
|
538
|
+
// Path is valid — proceed with the POST
|
|
539
|
+
onAdd(path, name || null);
|
|
540
|
+
} catch (err) {
|
|
541
|
+
const apiErr = err as { status?: number; data?: { message?: string } };
|
|
542
|
+
if (apiErr.status === 404) {
|
|
543
|
+
setPreflightError('That folder no longer exists. Pick another.');
|
|
544
|
+
} else {
|
|
545
|
+
setPreflightError(
|
|
546
|
+
apiErr.data?.message ?? (err as Error).message ?? 'Validation failed.',
|
|
547
|
+
);
|
|
548
|
+
}
|
|
549
|
+
} finally {
|
|
550
|
+
setPreflighting(false);
|
|
551
|
+
}
|
|
552
|
+
};
|
|
553
|
+
|
|
554
|
+
return (
|
|
555
|
+
<div>
|
|
556
|
+
<label className="field-label">Folder</label>
|
|
557
|
+
<FileBrowser
|
|
558
|
+
value={path}
|
|
559
|
+
onChange={(p) => {
|
|
560
|
+
setPath(p);
|
|
561
|
+
setPreflightError(null);
|
|
562
|
+
}}
|
|
563
|
+
projectsDirectory={settings?.dashboard?.projectsDirectory}
|
|
564
|
+
height={320}
|
|
565
|
+
/>
|
|
566
|
+
{preflightError && (
|
|
567
|
+
<p className="field-help" style={{ color: 'var(--error)', marginTop: 4 }}>
|
|
568
|
+
{preflightError}
|
|
569
|
+
</p>
|
|
570
|
+
)}
|
|
571
|
+
<div style={{ marginTop: 'var(--space-3)' }}>
|
|
572
|
+
<label className="field-label" htmlFor="add-project-name">Name (optional)</label>
|
|
573
|
+
<input
|
|
574
|
+
id="add-project-name"
|
|
575
|
+
className="input"
|
|
576
|
+
type="text"
|
|
577
|
+
placeholder="My App"
|
|
578
|
+
value={name}
|
|
579
|
+
onChange={(e) => setName(e.target.value)}
|
|
580
|
+
/>
|
|
581
|
+
<p className="field-help" style={{ marginTop: 4 }}>
|
|
582
|
+
Display name for this project. Defaults to the folder name.
|
|
583
|
+
</p>
|
|
584
|
+
</div>
|
|
585
|
+
<div style={{ marginTop: 'var(--space-3)', display: 'flex', justifyContent: 'flex-end' }}>
|
|
586
|
+
<Button
|
|
587
|
+
variant="primary"
|
|
588
|
+
onClick={handleAdd}
|
|
589
|
+
disabled={!path || preflighting}
|
|
590
|
+
>
|
|
591
|
+
{preflighting ? <span className="btn-spinner" /> : null}
|
|
592
|
+
{preflighting ? 'Checking…' : 'Add'}
|
|
593
|
+
</Button>
|
|
594
|
+
</div>
|
|
595
|
+
</div>
|
|
596
|
+
);
|
|
597
|
+
}
|
|
598
|
+
|
|
599
|
+
function ProjectCard({
|
|
600
|
+
project,
|
|
601
|
+
active,
|
|
602
|
+
onOpen,
|
|
603
|
+
onRemove,
|
|
604
|
+
}: {
|
|
605
|
+
project: ProjectRecord;
|
|
606
|
+
active: boolean;
|
|
607
|
+
onOpen: () => void;
|
|
608
|
+
onRemove: () => void;
|
|
609
|
+
}) {
|
|
610
|
+
const statusColor = {
|
|
611
|
+
active: 'status-on',
|
|
612
|
+
inactive: 'status-neutral',
|
|
613
|
+
error: 'status-error',
|
|
614
|
+
}[project.status] || 'status-neutral';
|
|
615
|
+
return (
|
|
616
|
+
<div className={`project-card ${active ? 'project-card-active' : ''}`}>
|
|
617
|
+
<div className="project-card-head">
|
|
618
|
+
<span className={`project-card-status ${statusColor}`}>
|
|
619
|
+
{project.status}
|
|
620
|
+
</span>
|
|
621
|
+
<div className="project-card-name">{project.name}</div>
|
|
622
|
+
<button
|
|
623
|
+
type="button"
|
|
624
|
+
className="icon-btn"
|
|
625
|
+
aria-label="Remove project"
|
|
626
|
+
title="Remove"
|
|
627
|
+
onClick={(e) => {
|
|
628
|
+
e.stopPropagation();
|
|
629
|
+
onRemove();
|
|
630
|
+
}}
|
|
631
|
+
>
|
|
632
|
+
<Trash2 size={12} />
|
|
633
|
+
</button>
|
|
634
|
+
</div>
|
|
635
|
+
<div className="project-card-path mono ellipsis" title={project.path}>
|
|
636
|
+
{project.path}
|
|
637
|
+
</div>
|
|
638
|
+
<div className="project-card-meta">
|
|
639
|
+
{project.lastAccessed && (
|
|
640
|
+
<span className="muted">Last opened {formatRelative(project.lastAccessed)}</span>
|
|
641
|
+
)}
|
|
642
|
+
</div>
|
|
643
|
+
<div className="project-card-actions">
|
|
644
|
+
<Button variant={active ? 'ghost' : 'primary'} size="sm" onClick={onOpen}>
|
|
645
|
+
{active ? <><Power size={12} /> Active</> : <>Open</>}
|
|
646
|
+
</Button>
|
|
647
|
+
</div>
|
|
648
|
+
</div>
|
|
649
|
+
);
|
|
650
|
+
}
|
|
651
|
+
|
|
652
|
+
function sentenceCase(s: string): string {
|
|
653
|
+
if (!s) return s;
|
|
654
|
+
return s.charAt(0).toUpperCase() + s.slice(1);
|
|
655
|
+
}
|
|
656
|
+
|
|
657
|
+
function humanizeKind(kind: string): string {
|
|
658
|
+
const normalized = (kind || 'activity').trim().toLowerCase().replace(/[:_]/g, '.');
|
|
659
|
+
const [scopeRaw, actionRaw = 'updated'] = normalized.split('.');
|
|
660
|
+
const scopeMap: Record<string, string> = {
|
|
661
|
+
settings: 'Settings',
|
|
662
|
+
task: 'Task',
|
|
663
|
+
tasks: 'Task',
|
|
664
|
+
agent: 'Agent',
|
|
665
|
+
agents: 'Agent',
|
|
666
|
+
plan: 'Plan',
|
|
667
|
+
plans: 'Plan',
|
|
668
|
+
bg: 'Background job',
|
|
669
|
+
background: 'Background job',
|
|
670
|
+
session: 'Session',
|
|
671
|
+
mod: 'Mod',
|
|
672
|
+
mods: 'Mod',
|
|
673
|
+
provider: 'Provider',
|
|
674
|
+
providers: 'Provider',
|
|
675
|
+
};
|
|
676
|
+
const actionMap: Record<string, string> = {
|
|
677
|
+
create: 'created', created: 'created',
|
|
678
|
+
add: 'added', added: 'added',
|
|
679
|
+
update: 'updated', updated: 'updated',
|
|
680
|
+
delegate: 'delegated', delegated: 'delegated',
|
|
681
|
+
invoke: 'invoked', invoked: 'invoked',
|
|
682
|
+
restart: 'restarted', restarted: 'restarted',
|
|
683
|
+
delete: 'deleted', deleted: 'deleted',
|
|
684
|
+
remove: 'removed', removed: 'removed',
|
|
685
|
+
archive: 'archived', archived: 'archived',
|
|
686
|
+
restore: 'restored', restored: 'restored',
|
|
687
|
+
complete: 'completed', completed: 'completed',
|
|
688
|
+
fail: 'failed', failed: 'failed',
|
|
689
|
+
error: 'errored', stuck: 'marked stuck',
|
|
690
|
+
};
|
|
691
|
+
const scope = scopeMap[scopeRaw] || sentenceCase(scopeRaw.replace(/-/g, ' '));
|
|
692
|
+
const action = actionMap[actionRaw] || actionRaw.replace(/-/g, ' ');
|
|
693
|
+
return `${scope} ${action}`.trim();
|
|
694
|
+
}
|
|
695
|
+
|
|
696
|
+
function firstString(it: ActivityItem, keys: string[]): string {
|
|
697
|
+
for (const key of keys) {
|
|
698
|
+
const value = it[key];
|
|
699
|
+
if (typeof value === 'string' && value.trim()) return value.trim();
|
|
700
|
+
}
|
|
701
|
+
return '';
|
|
702
|
+
}
|
|
703
|
+
|
|
704
|
+
function formatActivitySummary(it: ActivityItem): string {
|
|
705
|
+
const direct = firstString(it, ['message', 'text', 'prompt', 'title', 'name']);
|
|
706
|
+
if (direct) return direct;
|
|
707
|
+
const parts: string[] = [];
|
|
708
|
+
const slug = firstString(it, ['slug']);
|
|
709
|
+
const agent = firstString(it, ['agent', 'author']);
|
|
710
|
+
const status = firstString(it, ['status']);
|
|
711
|
+
if (slug) parts.push(`Plan ${slug}`);
|
|
712
|
+
if (agent) parts.push(agent);
|
|
713
|
+
if (status) parts.push(status);
|
|
714
|
+
return parts.length ? parts.join(' · ') : 'No additional details.';
|
|
715
|
+
}
|
|
716
|
+
|
|
717
|
+
// v3.7.0 — Format a timestamp as a relative string ("2m ago", "1h ago")
|
|
718
|
+
function formatRelativeTime(ts: string): string {
|
|
719
|
+
return formatRelative(ts);
|
|
720
|
+
}
|
|
721
|
+
|
|
722
|
+
// v3.7.0 — Get severity color token from kind or status field
|
|
723
|
+
function activitySeverity(it: ActivityItem): 'error' | 'warning' | 'success' | 'info' {
|
|
724
|
+
const k = ((it.kind as string) || '').toLowerCase();
|
|
725
|
+
const s = ((it.status as string) || '').toLowerCase();
|
|
726
|
+
if (k.includes('error') || s === 'error' || s === 'failure') return 'error';
|
|
727
|
+
if (k.includes('warn') || s === 'blocked') return 'warning';
|
|
728
|
+
if (k.includes('done') || k.includes('success') || s === 'done') return 'success';
|
|
729
|
+
return 'info';
|
|
730
|
+
}
|
|
731
|
+
|
|
732
|
+
// v3.7.0 — Map activity kind to icon
|
|
733
|
+
function activityIcon(kind: string) {
|
|
734
|
+
const k = kind.toLowerCase();
|
|
735
|
+
if (k === 'task') return CheckSquare;
|
|
736
|
+
if (k === 'agent') return Bot;
|
|
737
|
+
if (k === 'plan') return Map;
|
|
738
|
+
if (k.includes('bg') || k.includes('background') || k.includes('job')) return Zap;
|
|
739
|
+
if (k === 'mod') return Puzzle;
|
|
740
|
+
if (k === 'skill') return Sparkles;
|
|
741
|
+
if (k.includes('error') || k.includes('failure')) return AlertOctagon;
|
|
742
|
+
if (k.includes('warn')) return AlertTriangle;
|
|
743
|
+
return Activity;
|
|
744
|
+
}
|
|
745
|
+
|
|
746
|
+
// v3.7.0 — Navigate to the relevant tab when a card is clicked
|
|
747
|
+
function activityNavTarget(kind: string): string | null {
|
|
748
|
+
const k = kind.toLowerCase();
|
|
749
|
+
if (k === 'task') return 'tasks';
|
|
750
|
+
if (k === 'agent') return 'agents';
|
|
751
|
+
if (k === 'plan') return 'artifacts';
|
|
752
|
+
if (k.includes('bg') || k.includes('background')) return 'activity';
|
|
753
|
+
if (k === 'mod') return 'mods';
|
|
754
|
+
if (k === 'skill') return 'skills';
|
|
755
|
+
return null;
|
|
756
|
+
}
|
|
757
|
+
|
|
758
|
+
// v3.7.0 — Activity card component
|
|
759
|
+
function ActivityFeedItem({
|
|
760
|
+
item,
|
|
761
|
+
activityKey,
|
|
762
|
+
onNavigate,
|
|
763
|
+
onHide,
|
|
764
|
+
}: {
|
|
765
|
+
item: ActivityItem;
|
|
766
|
+
activityKey: string;
|
|
767
|
+
onNavigate: (tab: string) => void;
|
|
768
|
+
onHide: (key: string) => void;
|
|
769
|
+
}) {
|
|
770
|
+
const severity = activitySeverity(item);
|
|
771
|
+
const Icon = activityIcon(item.kind || '');
|
|
772
|
+
const title = humanizeKind(item.kind || 'activity');
|
|
773
|
+
const msg = formatActivitySummary(item);
|
|
774
|
+
const navTarget = activityNavTarget(item.kind || '');
|
|
775
|
+
|
|
776
|
+
const accentColor =
|
|
777
|
+
severity === 'error' ? 'var(--error)' :
|
|
778
|
+
severity === 'warning' ? 'var(--warning)' :
|
|
779
|
+
severity === 'success' ? 'var(--success)' :
|
|
780
|
+
'var(--accent)';
|
|
781
|
+
|
|
782
|
+
return (
|
|
783
|
+
<div className={cn('activity-feed-row', `activity-feed-row-${severity}`)}>
|
|
784
|
+
<button
|
|
785
|
+
type="button"
|
|
786
|
+
className="activity-feed-row-main"
|
|
787
|
+
onClick={() => { if (navTarget) onNavigate(navTarget); }}
|
|
788
|
+
title={navTarget ? `Open ${navTarget}` : title}
|
|
789
|
+
>
|
|
790
|
+
<div className="activity-feed-icon" style={{ color: accentColor }}>
|
|
791
|
+
<Icon size={14} />
|
|
792
|
+
</div>
|
|
793
|
+
<div className="activity-feed-body">
|
|
794
|
+
<div className="activity-feed-title-row">
|
|
795
|
+
<div className="activity-feed-title">{title}</div>
|
|
796
|
+
<div className="activity-feed-time text-xs muted tabular-nums">{formatRelativeTime(item.ts)}</div>
|
|
797
|
+
</div>
|
|
798
|
+
<div className="activity-feed-summary text-sm">{msg}</div>
|
|
799
|
+
</div>
|
|
800
|
+
</button>
|
|
801
|
+
<button
|
|
802
|
+
type="button"
|
|
803
|
+
className="activity-feed-hide-btn"
|
|
804
|
+
onClick={() => onHide(activityKey)}
|
|
805
|
+
title="Hide this from the overview (kept in Settings → Activity Log)"
|
|
806
|
+
aria-label="Hide from overview"
|
|
807
|
+
>
|
|
808
|
+
<X size={12} />
|
|
809
|
+
</button>
|
|
810
|
+
</div>
|
|
811
|
+
);
|
|
812
|
+
}
|