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