@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,357 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* mod-upgrade.node.test.mjs — Node-compatible test runner for the v3.20.5
|
|
3
|
+
* mod upgrade flow.
|
|
4
|
+
*
|
|
5
|
+
* Run with: node --test tests/mod-upgrade.node.test.mjs
|
|
6
|
+
*
|
|
7
|
+
* What this verifies:
|
|
8
|
+
* - upgradeFromRegistry() refuses if the mod is not installed
|
|
9
|
+
* - upgradeFromRegistry() backs up the existing folder when requested
|
|
10
|
+
* - upgradeFromRegistry() rolls back on install failure (backup exists)
|
|
11
|
+
* - The POST /api/mods/:id/upgrade route returns 404 for missing mods
|
|
12
|
+
* - The POST /api/mods/:id/upgrade route returns 200 with the new mod
|
|
13
|
+
* when the mod is upgraded end-to-end (with a stubbed fetchRegistry)
|
|
14
|
+
* - The /api/mods/:id/views/* route serves files from <mod>/views with
|
|
15
|
+
* application/javascript content-type
|
|
16
|
+
* - The /api/mods/:id/views/* route refuses non-JS-family extensions
|
|
17
|
+
*/
|
|
18
|
+
import { describe, it, before, after, beforeEach } from 'node:test';
|
|
19
|
+
import assert from 'node:assert/strict';
|
|
20
|
+
import {
|
|
21
|
+
existsSync,
|
|
22
|
+
mkdirSync,
|
|
23
|
+
rmSync,
|
|
24
|
+
writeFileSync,
|
|
25
|
+
readFileSync,
|
|
26
|
+
} from 'node:fs';
|
|
27
|
+
import { join, resolve } from 'node:path';
|
|
28
|
+
import { tmpdir } from 'node:os';
|
|
29
|
+
import http from 'node:http';
|
|
30
|
+
|
|
31
|
+
// ── Sandbox HOME before importing the loader ────────────────────────────
|
|
32
|
+
const SANDBOX_HOME = join(tmpdir(), `bizar-upgrade-test-${Date.now()}-${Math.random().toString(36).slice(2)}`);
|
|
33
|
+
process.env.HOME = SANDBOX_HOME;
|
|
34
|
+
process.env.XDG_CONFIG_HOME = join(SANDBOX_HOME, '.config');
|
|
35
|
+
|
|
36
|
+
const REPO = resolve(import.meta.dirname, '..');
|
|
37
|
+
const LOADER = join(REPO, 'src/server/mods-loader.mjs');
|
|
38
|
+
const ROUTES = join(REPO, 'src/server/routes/mods.mjs');
|
|
39
|
+
|
|
40
|
+
const loaderModule = await import(LOADER);
|
|
41
|
+
const { modsLoader } = loaderModule;
|
|
42
|
+
const { createModsRouter } = await import(ROUTES);
|
|
43
|
+
|
|
44
|
+
const TEST_MOD_ID = 'samplemod';
|
|
45
|
+
const OTHER_MOD_ID = 'othermod';
|
|
46
|
+
|
|
47
|
+
function buildTestMod(id, version = '1.0.0') {
|
|
48
|
+
// Build the source folder in a separate "sources" dir — installFromPath
|
|
49
|
+
// copies the source folder INTO ~/.config/bizar/mods/<id>, so the source
|
|
50
|
+
// must not be the destination.
|
|
51
|
+
const sourceDir = join(SANDBOX_HOME, 'sources', id);
|
|
52
|
+
rmSync(sourceDir, { recursive: true, force: true });
|
|
53
|
+
mkdirSync(sourceDir, { recursive: true });
|
|
54
|
+
writeFileSync(join(sourceDir, 'mod.json'), JSON.stringify({
|
|
55
|
+
id,
|
|
56
|
+
name: `Test ${id}`,
|
|
57
|
+
version,
|
|
58
|
+
description: `A test mod (${id})`,
|
|
59
|
+
permissions: [],
|
|
60
|
+
}, null, 2));
|
|
61
|
+
mkdirSync(join(sourceDir, 'agents'), { recursive: true });
|
|
62
|
+
writeFileSync(join(sourceDir, 'agents', 'thor.md'), `# Thor override for ${id}\n`);
|
|
63
|
+
mkdirSync(join(sourceDir, 'commands'), { recursive: true });
|
|
64
|
+
writeFileSync(join(sourceDir, 'commands', 'plan.md'), `# Plan command for ${id}\n`);
|
|
65
|
+
writeFileSync(join(sourceDir, 'INSTRUCTIONS.md'), `# INSTRUCTIONS for ${id}\n`);
|
|
66
|
+
return sourceDir;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
function buildViewFile(id, filename, content) {
|
|
70
|
+
// Views live in the INSTALLED mod folder, not the source.
|
|
71
|
+
const viewsDir = join(SANDBOX_HOME, '.config/bizar/mods', id, 'views');
|
|
72
|
+
mkdirSync(viewsDir, { recursive: true });
|
|
73
|
+
writeFileSync(join(viewsDir, filename), content);
|
|
74
|
+
writeFileSync(join(viewsDir, 'registry.json'), JSON.stringify({
|
|
75
|
+
views: [{ id: 'sample', label: 'Sample', component: filename }],
|
|
76
|
+
}, null, 2));
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
/**
|
|
80
|
+
* Build a fake registry "mod" folder on disk and return a function that
|
|
81
|
+
* installs it directly into ~/.config/bizar/mods/<id> when called. This
|
|
82
|
+
* lets tests exercise upgradeFromRegistry() without hitting the network.
|
|
83
|
+
*/
|
|
84
|
+
function buildFakeRegistryMod(id, version) {
|
|
85
|
+
const regDir = join(SANDBOX_HOME, 'fake-registry', id, version);
|
|
86
|
+
rmSync(regDir, { recursive: true, force: true });
|
|
87
|
+
mkdirSync(regDir, { recursive: true });
|
|
88
|
+
writeFileSync(join(regDir, 'mod.json'), JSON.stringify({
|
|
89
|
+
id,
|
|
90
|
+
name: `Test ${id}`,
|
|
91
|
+
version,
|
|
92
|
+
description: `Registry version ${version}`,
|
|
93
|
+
permissions: [],
|
|
94
|
+
}, null, 2));
|
|
95
|
+
mkdirSync(join(regDir, 'agents'), { recursive: true });
|
|
96
|
+
writeFileSync(join(regDir, 'agents', 'thor.md'), `# Thor v${version} for ${id}\n`);
|
|
97
|
+
mkdirSync(join(regDir, 'commands'), { recursive: true });
|
|
98
|
+
writeFileSync(join(regDir, 'commands', 'plan.md'), `# Plan v${version} for ${id}\n`);
|
|
99
|
+
writeFileSync(join(regDir, 'INSTRUCTIONS.md'), `# INSTRUCTIONS v${version} for ${id}\n`);
|
|
100
|
+
return regDir;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
/**
|
|
104
|
+
* Patch modsLoader.installFromRegistry to skip the network fetch and
|
|
105
|
+
* install from the local fake-registry folder. Returns a restore fn.
|
|
106
|
+
*/
|
|
107
|
+
function stubInstallFromRegistry(id, latestVersion, regDir) {
|
|
108
|
+
const realFetch = modsLoader.fetchRegistry;
|
|
109
|
+
const realInstall = modsLoader.installFromRegistry;
|
|
110
|
+
modsLoader.fetchRegistry = async () => ({
|
|
111
|
+
version: 99,
|
|
112
|
+
updatedAt: new Date().toISOString(),
|
|
113
|
+
mods: [{ id, name: `Test ${id}`, latest: latestVersion }],
|
|
114
|
+
});
|
|
115
|
+
modsLoader.installFromRegistry = async (rid, opts = {}) => {
|
|
116
|
+
assert.equal(rid, id, 'installFromRegistry called with wrong id');
|
|
117
|
+
const target = join(SANDBOX_HOME, '.config/bizar/mods', id);
|
|
118
|
+
if (existsSync(target)) {
|
|
119
|
+
throw new Error(`mod "${id}" already installed (stub check)`);
|
|
120
|
+
}
|
|
121
|
+
const { cpSync } = await import('node:fs');
|
|
122
|
+
mkdirSync(target, { recursive: true });
|
|
123
|
+
cpSync(regDir, target, { recursive: true });
|
|
124
|
+
// Mirror installFromUrl's behaviour: install instructions into opencode config.
|
|
125
|
+
const { modsLoader: _ignored } = await import(LOADER);
|
|
126
|
+
const innerLoader = await import(LOADER);
|
|
127
|
+
// Call the internal installModInstructions by going through reinstallInstructions
|
|
128
|
+
innerLoader.modsLoader.reinstallInstructions(id);
|
|
129
|
+
return innerLoader.modsLoader.get(id);
|
|
130
|
+
};
|
|
131
|
+
return () => {
|
|
132
|
+
modsLoader.fetchRegistry = realFetch;
|
|
133
|
+
modsLoader.installFromRegistry = realInstall;
|
|
134
|
+
};
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
before(() => {
|
|
138
|
+
mkdirSync(join(SANDBOX_HOME, '.config/opencode/agents'), { recursive: true });
|
|
139
|
+
mkdirSync(join(SANDBOX_HOME, '.config/opencode/commands'), { recursive: true });
|
|
140
|
+
mkdirSync(join(SANDBOX_HOME, '.opencode/skills'), { recursive: true });
|
|
141
|
+
mkdirSync(join(SANDBOX_HOME, '.config/bizar/mods'), { recursive: true });
|
|
142
|
+
// Pin the registry URL to a local stub we'll override per test.
|
|
143
|
+
});
|
|
144
|
+
|
|
145
|
+
after(() => {
|
|
146
|
+
try { rmSync(SANDBOX_HOME, { recursive: true, force: true }); } catch { /* ignore */ }
|
|
147
|
+
});
|
|
148
|
+
|
|
149
|
+
beforeEach(() => {
|
|
150
|
+
// Clear installed mods + opencode instruction prefixes between tests.
|
|
151
|
+
for (const prefix of [TEST_MOD_ID, OTHER_MOD_ID]) {
|
|
152
|
+
const modDir = join(SANDBOX_HOME, '.config/bizar/mods', prefix);
|
|
153
|
+
rmSync(modDir, { recursive: true, force: true });
|
|
154
|
+
}
|
|
155
|
+
// Reset real-fetch state by patching fetchRegistry to a controllable stub.
|
|
156
|
+
});
|
|
157
|
+
|
|
158
|
+
// ── 1. upgradeFromRegistry() — basic rejection ─────────────────────────
|
|
159
|
+
|
|
160
|
+
describe('upgradeFromRegistry — preconditions', () => {
|
|
161
|
+
it('throws when the mod is not installed', async () => {
|
|
162
|
+
await assert.rejects(
|
|
163
|
+
() => modsLoader.upgradeFromRegistry('never-installed-xyz', { url: 'file:///nope' }),
|
|
164
|
+
/not installed/i,
|
|
165
|
+
);
|
|
166
|
+
});
|
|
167
|
+
});
|
|
168
|
+
|
|
169
|
+
// ── 2. upgradeFromRegistry() — happy path with a stubbed registry ─────
|
|
170
|
+
|
|
171
|
+
describe('upgradeFromRegistry — end-to-end with stubbed registry', () => {
|
|
172
|
+
it('upgrades in place when the registry returns a newer version', async () => {
|
|
173
|
+
// Install v1.0.0 from a source folder.
|
|
174
|
+
const v1Source = buildTestMod(TEST_MOD_ID, '1.0.0');
|
|
175
|
+
const installed = modsLoader.installFromPath(v1Source);
|
|
176
|
+
assert.equal(installed.version, '1.0.0');
|
|
177
|
+
|
|
178
|
+
// Build a fake registry entry for v2.0.0 and stub installFromRegistry
|
|
179
|
+
// to copy from the local fake-registry folder instead of fetching.
|
|
180
|
+
const v2RegDir = buildFakeRegistryMod(TEST_MOD_ID, '2.0.0');
|
|
181
|
+
const restore = stubInstallFromRegistry(TEST_MOD_ID, '2.0.0', v2RegDir);
|
|
182
|
+
try {
|
|
183
|
+
const result = await modsLoader.upgradeFromRegistry(TEST_MOD_ID, {});
|
|
184
|
+
assert.equal(result.from, '1.0.0');
|
|
185
|
+
assert.equal(result.to, '2.0.0');
|
|
186
|
+
assert.equal(result.mod.id, TEST_MOD_ID);
|
|
187
|
+
assert.equal(result.mod.version, '2.0.0');
|
|
188
|
+
assert.equal(result.backupPath, null);
|
|
189
|
+
// The new instructions are installed in opencode config under the
|
|
190
|
+
// `<modId>__` prefix.
|
|
191
|
+
assert.ok(
|
|
192
|
+
existsSync(join(SANDBOX_HOME, '.config/opencode/agents', `${TEST_MOD_ID}__thor.md`)),
|
|
193
|
+
'agents/<modId>__thor.md should exist after upgrade',
|
|
194
|
+
);
|
|
195
|
+
assert.ok(
|
|
196
|
+
existsSync(join(SANDBOX_HOME, '.config/opencode/commands', `${TEST_MOD_ID}__plan.md`)),
|
|
197
|
+
'commands/<modId>__plan.md should exist after upgrade',
|
|
198
|
+
);
|
|
199
|
+
} finally {
|
|
200
|
+
restore();
|
|
201
|
+
}
|
|
202
|
+
});
|
|
203
|
+
|
|
204
|
+
it('creates a backup when { backup: true }', async () => {
|
|
205
|
+
const v1Source = buildTestMod(TEST_MOD_ID, '1.0.0');
|
|
206
|
+
modsLoader.installFromPath(v1Source);
|
|
207
|
+
|
|
208
|
+
const v2RegDir = buildFakeRegistryMod(TEST_MOD_ID, '2.5.0');
|
|
209
|
+
const restore = stubInstallFromRegistry(TEST_MOD_ID, '2.5.0', v2RegDir);
|
|
210
|
+
try {
|
|
211
|
+
const result = await modsLoader.upgradeFromRegistry(TEST_MOD_ID, { backup: true });
|
|
212
|
+
assert.ok(result.backupPath, 'backupPath should be set when backup:true');
|
|
213
|
+
assert.ok(existsSync(result.backupPath), 'backup folder should exist on disk');
|
|
214
|
+
assert.ok(
|
|
215
|
+
existsSync(join(result.backupPath, 'mod.json')),
|
|
216
|
+
'backup should contain the original mod.json',
|
|
217
|
+
);
|
|
218
|
+
const backupManifest = JSON.parse(readFileSync(join(result.backupPath, 'mod.json'), 'utf8'));
|
|
219
|
+
assert.equal(backupManifest.version, '1.0.0', 'backup should have the OLD version');
|
|
220
|
+
} finally {
|
|
221
|
+
restore();
|
|
222
|
+
}
|
|
223
|
+
});
|
|
224
|
+
});
|
|
225
|
+
|
|
226
|
+
// ── 3. POST /api/mods/:id/upgrade route ────────────────────────────────
|
|
227
|
+
|
|
228
|
+
async function startTestServer() {
|
|
229
|
+
const express = (await import('express')).default;
|
|
230
|
+
const app = express();
|
|
231
|
+
app.use(express.json({ limit: '2mb' }));
|
|
232
|
+
app.use('/api', createModsRouter());
|
|
233
|
+
const server = http.createServer(app);
|
|
234
|
+
await new Promise((resolve) => server.listen(0, '127.0.0.1', resolve));
|
|
235
|
+
const { port } = server.address();
|
|
236
|
+
return {
|
|
237
|
+
server,
|
|
238
|
+
baseUrl: `http://127.0.0.1:${port}`,
|
|
239
|
+
close: () => new Promise((resolve) => server.close(resolve)),
|
|
240
|
+
};
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
function request(baseUrl, path, { method = 'GET', body } = {}) {
|
|
244
|
+
return new Promise((resolve, reject) => {
|
|
245
|
+
const url = new URL(path, baseUrl);
|
|
246
|
+
const data = body ? JSON.stringify(body) : null;
|
|
247
|
+
const req = http.request({
|
|
248
|
+
method,
|
|
249
|
+
hostname: url.hostname,
|
|
250
|
+
port: url.port,
|
|
251
|
+
path: url.pathname + url.search,
|
|
252
|
+
headers: data ? { 'Content-Type': 'application/json', 'Content-Length': Buffer.byteLength(data) } : {},
|
|
253
|
+
}, (res) => {
|
|
254
|
+
let chunks = '';
|
|
255
|
+
res.on('data', (c) => chunks += c);
|
|
256
|
+
res.on('end', () => {
|
|
257
|
+
let json = null;
|
|
258
|
+
try { json = chunks ? JSON.parse(chunks) : null; } catch { /* keep raw */ }
|
|
259
|
+
resolve({ status: res.statusCode, headers: res.headers, body: chunks, json });
|
|
260
|
+
});
|
|
261
|
+
});
|
|
262
|
+
req.on('error', reject);
|
|
263
|
+
if (data) req.write(data);
|
|
264
|
+
req.end();
|
|
265
|
+
});
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
describe('POST /api/mods/:id/upgrade — HTTP', () => {
|
|
269
|
+
let serverHandle;
|
|
270
|
+
|
|
271
|
+
before(async () => { serverHandle = await startTestServer(); });
|
|
272
|
+
after(async () => { await serverHandle.close(); });
|
|
273
|
+
|
|
274
|
+
it('returns 404 when the mod is not installed', async () => {
|
|
275
|
+
const res = await request(serverHandle.baseUrl, '/api/mods/never-installed/upgrade', {
|
|
276
|
+
method: 'POST', body: {},
|
|
277
|
+
});
|
|
278
|
+
assert.equal(res.status, 404);
|
|
279
|
+
assert.equal(res.json.error, 'upgrade_failed');
|
|
280
|
+
});
|
|
281
|
+
|
|
282
|
+
it('upgrades and returns from/to versions (with stubbed fetchRegistry)', async () => {
|
|
283
|
+
const v1Source = buildTestMod(OTHER_MOD_ID, '1.0.0');
|
|
284
|
+
modsLoader.installFromPath(v1Source);
|
|
285
|
+
|
|
286
|
+
const v2RegDir = buildFakeRegistryMod(OTHER_MOD_ID, '1.5.0');
|
|
287
|
+
const restore = stubInstallFromRegistry(OTHER_MOD_ID, '1.5.0', v2RegDir);
|
|
288
|
+
try {
|
|
289
|
+
const res = await request(
|
|
290
|
+
serverHandle.baseUrl,
|
|
291
|
+
`/api/mods/${OTHER_MOD_ID}/upgrade`,
|
|
292
|
+
{ method: 'POST', body: {} },
|
|
293
|
+
);
|
|
294
|
+
assert.equal(res.status, 200, `unexpected status ${res.status}: ${res.body}`);
|
|
295
|
+
assert.equal(res.json.ok, true);
|
|
296
|
+
assert.equal(res.json.from, '1.0.0');
|
|
297
|
+
assert.equal(res.json.to, '1.5.0');
|
|
298
|
+
assert.equal(res.json.modId, OTHER_MOD_ID);
|
|
299
|
+
assert.equal(res.json.mod.version, '1.5.0');
|
|
300
|
+
} finally {
|
|
301
|
+
restore();
|
|
302
|
+
}
|
|
303
|
+
});
|
|
304
|
+
});
|
|
305
|
+
|
|
306
|
+
// ── 4. /api/mods/:id/views/* — content-type + path safety ──────────────
|
|
307
|
+
|
|
308
|
+
describe('GET /api/mods/:id/views/*', () => {
|
|
309
|
+
let serverHandle;
|
|
310
|
+
before(async () => { serverHandle = await startTestServer(); });
|
|
311
|
+
after(async () => { await serverHandle.close(); });
|
|
312
|
+
|
|
313
|
+
it('serves .js view files with application/javascript content-type', async () => {
|
|
314
|
+
const source = buildTestMod('viewmod', '1.0.0');
|
|
315
|
+
modsLoader.installFromPath(source);
|
|
316
|
+
buildViewFile('viewmod', 'SampleView.js',
|
|
317
|
+
'export default function SampleView() { return null; }\n');
|
|
318
|
+
|
|
319
|
+
const res = await request(serverHandle.baseUrl, '/api/mods/viewmod/views/SampleView.js');
|
|
320
|
+
assert.equal(res.status, 200);
|
|
321
|
+
assert.match(res.headers['content-type'] || '', /application\/javascript/);
|
|
322
|
+
assert.match(res.body, /export default function SampleView/);
|
|
323
|
+
});
|
|
324
|
+
|
|
325
|
+
it('rejects non-JS-family extensions', async () => {
|
|
326
|
+
const source = buildTestMod('viewmod2', '1.0.0');
|
|
327
|
+
modsLoader.installFromPath(source);
|
|
328
|
+
// Try to fetch a .html file from views/ — should be forbidden.
|
|
329
|
+
const viewsDir = join(SANDBOX_HOME, '.config/bizar/mods', 'viewmod2', 'views');
|
|
330
|
+
mkdirSync(viewsDir, { recursive: true });
|
|
331
|
+
writeFileSync(join(viewsDir, 'index.html'), '<!doctype html>');
|
|
332
|
+
|
|
333
|
+
const res = await request(serverHandle.baseUrl, '/api/mods/viewmod2/views/index.html');
|
|
334
|
+
assert.equal(res.status, 403);
|
|
335
|
+
});
|
|
336
|
+
|
|
337
|
+
it('returns 404 for missing files', async () => {
|
|
338
|
+
const source = buildTestMod('viewmod3', '1.0.0');
|
|
339
|
+
modsLoader.installFromPath(source);
|
|
340
|
+
|
|
341
|
+
const res = await request(serverHandle.baseUrl, '/api/mods/viewmod3/views/Missing.js');
|
|
342
|
+
assert.equal(res.status, 404);
|
|
343
|
+
});
|
|
344
|
+
|
|
345
|
+
it('refuses paths that escape the views/ directory', async () => {
|
|
346
|
+
const source = buildTestMod('viewmod4', '1.0.0');
|
|
347
|
+
modsLoader.installFromPath(source);
|
|
348
|
+
const res = await request(serverHandle.baseUrl, '/api/mods/viewmod4/views/..%2Fmod.json');
|
|
349
|
+
assert.ok([400, 403, 404].includes(res.status),
|
|
350
|
+
`expected 400/403/404 for path traversal, got ${res.status}`);
|
|
351
|
+
});
|
|
352
|
+
|
|
353
|
+
it('returns 404 for views of a non-installed mod', async () => {
|
|
354
|
+
const res = await request(serverHandle.baseUrl, '/api/mods/never-here/views/SampleView.js');
|
|
355
|
+
assert.equal(res.status, 404);
|
|
356
|
+
});
|
|
357
|
+
});
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* no-agent-browser.node.test.mjs — regression test for the v3.20.7 cleanup.
|
|
3
|
+
*
|
|
4
|
+
* The `agent-browser` MCP tool was the previous browser-automation path.
|
|
5
|
+
* v3.20.7 replaced it with `browser-harness` (the Python tool from
|
|
6
|
+
* https://github.com/browser-use/browser-harness). This test ensures
|
|
7
|
+
* no agent-browser references leak back into the shipped config or
|
|
8
|
+
* the install/bootstrap scripts.
|
|
9
|
+
*
|
|
10
|
+
* Allowed exceptions:
|
|
11
|
+
* - .bizar/AGENTS_SELF_IMPROVEMENT.md (historical rule entry documenting
|
|
12
|
+
* the v3.20.7 migration)
|
|
13
|
+
* - CHANGELOG.md (historical release notes)
|
|
14
|
+
*
|
|
15
|
+
* Run with: node --test tests/no-agent-browser.node.test.mjs
|
|
16
|
+
*/
|
|
17
|
+
import { describe, it } from 'node:test';
|
|
18
|
+
import assert from 'node:assert/strict';
|
|
19
|
+
import { readFileSync, readdirSync, statSync } from 'node:fs';
|
|
20
|
+
import { join, resolve } from 'node:path';
|
|
21
|
+
|
|
22
|
+
const REPO = resolve(import.meta.dirname, '..', '..');
|
|
23
|
+
|
|
24
|
+
const SCAN_DIRS = [
|
|
25
|
+
'config',
|
|
26
|
+
'cli',
|
|
27
|
+
'bizar-dash/src',
|
|
28
|
+
'install.sh',
|
|
29
|
+
];
|
|
30
|
+
const SCAN_EXTENSIONS = ['.mjs', '.ts', '.tsx', '.json', '.sh', '.md', '.mdx', '.md.template'];
|
|
31
|
+
const ALLOW_FILES = new Set([
|
|
32
|
+
'.bizar/AGENTS_SELF_IMPROVEMENT.md', // historical rule entry
|
|
33
|
+
'CHANGELOG.md', // historical release notes
|
|
34
|
+
]);
|
|
35
|
+
const PATTERNS = [
|
|
36
|
+
/agent[-_]?browser/i, // agent-browser, agent_browser, agentBrowser
|
|
37
|
+
/agent_browser_/i, // agent_browser_open etc.
|
|
38
|
+
];
|
|
39
|
+
|
|
40
|
+
function isAllowed(relPath) {
|
|
41
|
+
if (ALLOW_FILES.has(relPath)) return true;
|
|
42
|
+
return false;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
function* walkFiles(root) {
|
|
46
|
+
const stat = statSync(root);
|
|
47
|
+
if (stat.isFile()) {
|
|
48
|
+
if (SCAN_EXTENSIONS.some((ext) => root.endsWith(ext))) yield root;
|
|
49
|
+
return;
|
|
50
|
+
}
|
|
51
|
+
if (!stat.isDirectory()) return;
|
|
52
|
+
// Skip noise
|
|
53
|
+
if (root.includes('node_modules')) return;
|
|
54
|
+
if (root.includes('.git')) return;
|
|
55
|
+
for (const entry of readdirSync(root)) {
|
|
56
|
+
yield* walkFiles(join(root, entry));
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
describe('no agent-browser references in shipped code', () => {
|
|
61
|
+
const violations = [];
|
|
62
|
+
|
|
63
|
+
for (const target of SCAN_DIRS) {
|
|
64
|
+
const full = join(REPO, target);
|
|
65
|
+
let exists = true;
|
|
66
|
+
try { statSync(full); } catch { exists = false; }
|
|
67
|
+
if (!exists) continue;
|
|
68
|
+
for (const file of walkFiles(full)) {
|
|
69
|
+
const rel = file.slice(REPO.length + 1);
|
|
70
|
+
if (isAllowed(rel)) continue;
|
|
71
|
+
let text;
|
|
72
|
+
try { text = readFileSync(file, 'utf8'); } catch { continue; }
|
|
73
|
+
for (const pat of PATTERNS) {
|
|
74
|
+
const m = text.match(pat);
|
|
75
|
+
if (m) {
|
|
76
|
+
violations.push({ file: rel, match: m[0] });
|
|
77
|
+
break; // one violation per file is enough
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
it('reports zero violations', () => {
|
|
84
|
+
if (violations.length > 0) {
|
|
85
|
+
const msg = violations
|
|
86
|
+
.map((v) => ` ${v.file}: matched "${v.match}"`)
|
|
87
|
+
.join('\n');
|
|
88
|
+
assert.fail(`agent-browser references found in shipped code:\n${msg}`);
|
|
89
|
+
}
|
|
90
|
+
});
|
|
91
|
+
|
|
92
|
+
it('documents the explicit allow-list', () => {
|
|
93
|
+
// Sanity: the allow-list itself shouldn't be empty (we have real
|
|
94
|
+
// historical references that should be preserved).
|
|
95
|
+
assert.ok(ALLOW_FILES.size >= 2,
|
|
96
|
+
'expected ALLOW_FILES to include at least AGENTS_SELF_IMPROVEMENT.md + CHANGELOG.md');
|
|
97
|
+
});
|
|
98
|
+
});
|
|
@@ -0,0 +1,199 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* tests/obsidian-back-compat.test.mjs
|
|
3
|
+
*
|
|
4
|
+
* Tests for the backward-compatible obsidian.mjs rewrite.
|
|
5
|
+
* Verifies that the new router returns the same JSON shapes as the original.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import { describe, it, beforeEach, afterEach } from 'node:test';
|
|
9
|
+
import assert from 'node:assert';
|
|
10
|
+
import { tmpdir } from 'node:os';
|
|
11
|
+
import { join } from 'node:path';
|
|
12
|
+
import { mkdirSync, rmSync } from 'node:fs';
|
|
13
|
+
import http from 'node:http';
|
|
14
|
+
import express from 'express';
|
|
15
|
+
|
|
16
|
+
const SR = '../src/server'; // from tests/ → bizarre-dash/src/server
|
|
17
|
+
|
|
18
|
+
async function get(url, port) {
|
|
19
|
+
return new Promise((resolve, reject) => {
|
|
20
|
+
const req = http.request({ hostname: 'localhost', port, path: url, method: 'GET' }, (res) => {
|
|
21
|
+
let data = '';
|
|
22
|
+
res.on('data', (c) => { data += c; });
|
|
23
|
+
res.on('end', () => {
|
|
24
|
+
try { data = JSON.parse(data); } catch { /* not JSON */ }
|
|
25
|
+
resolve({ status: res.statusCode, body: data });
|
|
26
|
+
});
|
|
27
|
+
});
|
|
28
|
+
req.on('error', reject);
|
|
29
|
+
req.end();
|
|
30
|
+
});
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
async function post(url, port, payload) {
|
|
34
|
+
return new Promise((resolve, reject) => {
|
|
35
|
+
const body = JSON.stringify(payload);
|
|
36
|
+
const req = http.request(
|
|
37
|
+
{ hostname: 'localhost', port, path: url, method: 'POST', headers: { 'Content-Type': 'application/json', 'Content-Length': Buffer.byteLength(body) } },
|
|
38
|
+
(res) => {
|
|
39
|
+
let data = '';
|
|
40
|
+
res.on('data', (c) => { data += c; });
|
|
41
|
+
res.on('end', () => {
|
|
42
|
+
try { data = JSON.parse(data); } catch { /* not JSON */ }
|
|
43
|
+
resolve({ status: res.statusCode, body: data });
|
|
44
|
+
});
|
|
45
|
+
},
|
|
46
|
+
);
|
|
47
|
+
req.on('error', reject);
|
|
48
|
+
req.write(body);
|
|
49
|
+
req.end();
|
|
50
|
+
});
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
describe('obsidian-back-compat', () => {
|
|
54
|
+
let projectRoot;
|
|
55
|
+
let server;
|
|
56
|
+
let port;
|
|
57
|
+
|
|
58
|
+
beforeEach(() => {
|
|
59
|
+
projectRoot = join(tmpdir(), `obc-${Date.now()}-${Math.random().toString(36).slice(2)}`);
|
|
60
|
+
mkdirSync(projectRoot, { recursive: true });
|
|
61
|
+
mkdirSync(join(projectRoot, '.bizar'), { recursive: true });
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
afterEach(async () => {
|
|
65
|
+
if (server) {
|
|
66
|
+
await new Promise((r) => server.close(r));
|
|
67
|
+
server = null;
|
|
68
|
+
}
|
|
69
|
+
try { rmSync(projectRoot, { recursive: true, force: true }); } catch { /* ignore */ }
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
// ── With memory.json (memory service mode) ─────────────────────────────────
|
|
73
|
+
|
|
74
|
+
describe('memory service mode (memory.json exists)', () => {
|
|
75
|
+
it('GET /obsidian returns vaultStats shape', async () => {
|
|
76
|
+
const ms = await import(`${SR}/memory-store.mjs`);
|
|
77
|
+
ms.saveConfig(projectRoot, {
|
|
78
|
+
version: 1, backend: 'bizar-local', projectId: 'test',
|
|
79
|
+
memoryRepo: { mode: 'local-only', path: join(projectRoot, '.obsidian'), remote: null, branch: 'main', namespace: null },
|
|
80
|
+
namespaces: { project: 'projects/test', global: 'global/bizar', user: 'users/local' },
|
|
81
|
+
lightrag: { enabled: false, host: '127.0.0.1', port: 9621, workingDir: join(projectRoot, '.bizar', 'lightrag') },
|
|
82
|
+
git: { autoPullOnSessionStart: false, autoCommitOnMemoryWrite: false, autoPushOnSessionEnd: false, commitAuthor: 'Bizar Memory <bizar-memory@local>', commitMessageTemplate: 'memory(test): {summary}' },
|
|
83
|
+
});
|
|
84
|
+
ms.initVault(projectRoot);
|
|
85
|
+
ms.writeNote(projectRoot, 'test-note.md', {
|
|
86
|
+
frontmatter: { memory_id: 't1', type: 'session_summary', project_id: 'test', status: 'active', confidence: 'verified', created: new Date().toISOString(), updated: new Date().toISOString(), tags: [] },
|
|
87
|
+
body: 'Test body',
|
|
88
|
+
});
|
|
89
|
+
|
|
90
|
+
const app = express();
|
|
91
|
+
app.use(express.json());
|
|
92
|
+
const { createObsidianRouter } = await import(`${SR}/routes/obsidian.mjs`);
|
|
93
|
+
app.use(await createObsidianRouter({ projectRoot }));
|
|
94
|
+
server = app.listen(0);
|
|
95
|
+
port = server.address().port;
|
|
96
|
+
|
|
97
|
+
const { status, body } = await get('/obsidian', port);
|
|
98
|
+
assert.strictEqual(status, 200);
|
|
99
|
+
assert.strictEqual(typeof body.exists, 'boolean');
|
|
100
|
+
assert.strictEqual(typeof body.vaultDir, 'string');
|
|
101
|
+
assert.strictEqual(typeof body.noteCount, 'number');
|
|
102
|
+
assert.strictEqual(typeof body.totalSize, 'number');
|
|
103
|
+
});
|
|
104
|
+
|
|
105
|
+
it('GET /obsidian/notes returns legacy shape { notes: [{ path, relPath, mtime, size }]}', async () => {
|
|
106
|
+
const ms = await import(`${SR}/memory-store.mjs`);
|
|
107
|
+
ms.saveConfig(projectRoot, {
|
|
108
|
+
version: 1, backend: 'bizar-local', projectId: 'test',
|
|
109
|
+
memoryRepo: { mode: 'local-only', path: join(projectRoot, '.obsidian'), remote: null, branch: 'main', namespace: null },
|
|
110
|
+
namespaces: { project: 'projects/test', global: 'global/bizar', user: 'users/local' },
|
|
111
|
+
lightrag: { enabled: false, host: '127.0.0.1', port: 9621, workingDir: join(projectRoot, '.bizar', 'lightrag') },
|
|
112
|
+
git: { autoPullOnSessionStart: false, autoCommitOnMemoryWrite: false, autoPushOnSessionEnd: false, commitAuthor: 'Bizar Memory <bizar-memory@local>', commitMessageTemplate: 'memory(test): {summary}' },
|
|
113
|
+
});
|
|
114
|
+
ms.initVault(projectRoot);
|
|
115
|
+
ms.writeNote(projectRoot, 'lnt.md', {
|
|
116
|
+
frontmatter: { memory_id: 'lnt1', type: 'session_summary', project_id: 'test', status: 'active', confidence: 'verified', created: new Date().toISOString(), updated: new Date().toISOString(), tags: [] },
|
|
117
|
+
body: 'body',
|
|
118
|
+
});
|
|
119
|
+
|
|
120
|
+
const app = express();
|
|
121
|
+
app.use(express.json());
|
|
122
|
+
const { createObsidianRouter } = await import(`${SR}/routes/obsidian.mjs`);
|
|
123
|
+
app.use(await createObsidianRouter({ projectRoot }));
|
|
124
|
+
server = app.listen(0);
|
|
125
|
+
port = server.address().port;
|
|
126
|
+
|
|
127
|
+
const { status, body } = await get('/obsidian/notes', port);
|
|
128
|
+
assert.strictEqual(status, 200);
|
|
129
|
+
assert.ok(Array.isArray(body.notes));
|
|
130
|
+
assert.ok(body.notes.length > 0);
|
|
131
|
+
const note = body.notes[0];
|
|
132
|
+
assert.strictEqual(typeof note.path, 'string');
|
|
133
|
+
assert.strictEqual(typeof note.relPath, 'string');
|
|
134
|
+
assert.strictEqual(typeof note.mtime, 'number');
|
|
135
|
+
assert.strictEqual(typeof note.size, 'number');
|
|
136
|
+
assert.strictEqual(note.frontmatter, undefined); // NOT rich shape
|
|
137
|
+
});
|
|
138
|
+
|
|
139
|
+
it('POST /obsidian/notes returns 201 with FULL note shape', async () => {
|
|
140
|
+
const ms = await import(`${SR}/memory-store.mjs`);
|
|
141
|
+
ms.saveConfig(projectRoot, {
|
|
142
|
+
version: 1, backend: 'bizar-local', projectId: 'test',
|
|
143
|
+
memoryRepo: { mode: 'local-only', path: join(projectRoot, '.obsidian'), remote: null, branch: 'main', namespace: null },
|
|
144
|
+
namespaces: { project: 'projects/test', global: 'global/bizar', user: 'users/local' },
|
|
145
|
+
lightrag: { enabled: false, host: '127.0.0.1', port: 9621, workingDir: join(projectRoot, '.bizar', 'lightrag') },
|
|
146
|
+
git: { autoPullOnSessionStart: false, autoCommitOnMemoryWrite: false, autoPushOnSessionEnd: false, commitAuthor: 'Bizar Memory <bizar-memory@local>', commitMessageTemplate: 'memory(test): {summary}' },
|
|
147
|
+
});
|
|
148
|
+
ms.initVault(projectRoot);
|
|
149
|
+
|
|
150
|
+
const app = express();
|
|
151
|
+
app.use(express.json());
|
|
152
|
+
const { createObsidianRouter } = await import(`${SR}/routes/obsidian.mjs`);
|
|
153
|
+
app.use(await createObsidianRouter({ projectRoot }));
|
|
154
|
+
server = app.listen(0);
|
|
155
|
+
port = server.address().port;
|
|
156
|
+
|
|
157
|
+
const { status, body } = await post('/obsidian/notes', port, {
|
|
158
|
+
path: 'post-test.md',
|
|
159
|
+
frontmatter: { memory_id: 'pt1', type: 'session_summary', project_id: 'test', status: 'active', confidence: 'verified', created: new Date().toISOString(), updated: new Date().toISOString(), tags: [] },
|
|
160
|
+
body: 'Posted body content',
|
|
161
|
+
});
|
|
162
|
+
|
|
163
|
+
assert.strictEqual(status, 201);
|
|
164
|
+
assert.strictEqual(typeof body.relPath, 'string');
|
|
165
|
+
assert.strictEqual(typeof body.frontmatter, 'object');
|
|
166
|
+
assert.strictEqual(typeof body.body, 'string');
|
|
167
|
+
assert.strictEqual(typeof body.raw, 'string');
|
|
168
|
+
assert.strictEqual(typeof body.mtime, 'number');
|
|
169
|
+
assert.strictEqual(typeof body.size, 'number');
|
|
170
|
+
assert.strictEqual(body.relPath, 'post-test.md');
|
|
171
|
+
});
|
|
172
|
+
});
|
|
173
|
+
|
|
174
|
+
// ── Without memory.json (legacy mode) ───────────────────────────────────────
|
|
175
|
+
|
|
176
|
+
describe('legacy mode (no memory.json)', () => {
|
|
177
|
+
it('GET /obsidian returns legacy vaultStats shape', async () => {
|
|
178
|
+
const os = await import(`${SR}/obsidian-store.mjs`);
|
|
179
|
+
os.initObsidianVault(projectRoot);
|
|
180
|
+
os.writeVaultNote(projectRoot, 'legacy.md', {
|
|
181
|
+
frontmatter: { title: 'Legacy Note' },
|
|
182
|
+
body: 'Legacy body',
|
|
183
|
+
});
|
|
184
|
+
|
|
185
|
+
const app = express();
|
|
186
|
+
app.use(express.json());
|
|
187
|
+
const { createObsidianRouter } = await import(`${SR}/routes/obsidian.mjs`);
|
|
188
|
+
app.use(await createObsidianRouter({ projectRoot }));
|
|
189
|
+
server = app.listen(0);
|
|
190
|
+
port = server.address().port;
|
|
191
|
+
|
|
192
|
+
const { status, body } = await get('/obsidian', port);
|
|
193
|
+
assert.strictEqual(status, 200);
|
|
194
|
+
assert.strictEqual(body.exists, true);
|
|
195
|
+
assert.strictEqual(typeof body.vaultDir, 'string');
|
|
196
|
+
assert.strictEqual(typeof body.noteCount, 'number');
|
|
197
|
+
});
|
|
198
|
+
});
|
|
199
|
+
});
|