total-recall-brain 3.45.0 → 3.46.1
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/bin/total-recall.mjs +7 -2
- package/frontend/dist/assets/index-BRYE1jmv.css +1 -0
- package/frontend/dist/assets/index-D1goIPjC.js +5598 -0
- package/frontend/dist/index.html +2 -2
- package/frontend/dist/sw.js +5 -4
- package/frontend/src/App.tsx +183 -236
- package/frontend/src/api/auth-inventory.ts +62 -0
- package/frontend/src/api/memory.ts +13 -8
- package/frontend/src/api/plugin-credentials.ts +23 -0
- package/frontend/src/api/plugins.ts +37 -53
- package/frontend/src/api/workspace-drawers.ts +97 -0
- package/frontend/src/components/BrainSelector.tsx +9 -3
- package/frontend/src/components/plugins/PluginPanel.tsx +83 -42
- package/frontend/src/components/plugins/PluginSettings.tsx +10 -20
- package/frontend/src/components/plugins/PluginWorkspace.tsx +367 -0
- package/frontend/src/components/plugins/ScopedPluginCommands.tsx +142 -0
- package/frontend/src/components/plugins/plugin-frame.ts +44 -33
- package/frontend/src/components/plugins/scoped-plugin-operations.ts +28 -0
- package/frontend/src/components/workspace/DrawerChat.tsx +110 -0
- package/frontend/src/components/workspace/DrawerLayout.tsx +196 -0
- package/frontend/src/components/workspace/DrawerMarkup.tsx +49 -0
- package/frontend/src/components/workspace/PluginChunkLibrary.tsx +92 -0
- package/frontend/src/components/workspace/PluginHome.tsx +76 -0
- package/frontend/src/components/workspace/WorkspaceDrawers.tsx +96 -0
- package/frontend/src/components/workspace/WorkspacePage.tsx +95 -0
- package/frontend/src/components/workspace/usePluginDesign.ts +38 -0
- package/frontend/src/index.css +409 -136
- package/frontend/src/pages/HelpPage.tsx +15 -46
- package/frontend/src/pages/MemoryPage.tsx +19 -36
- package/frontend/src/pages/SettingsPage.tsx +1 -1
- package/metadata.plugin.schema.json +53 -227
- package/package.json +1 -1
- package/scaffold/.agent/skills/plugin/SKILL.md +13 -11
- package/scaffold/.agent/skills/plugin/evals/README.md +14 -21
- package/scaffold/.agent/skills/plugin/evals/cases/01-fabricated-seed.mjs +15 -18
- package/scaffold/.agent/skills/plugin/evals/cases/02-reassuring-default.mjs +19 -24
- package/scaffold/.agent/skills/plugin/evals/cases/03-declared-template.mjs +12 -20
- package/scaffold/.agent/skills/plugin/evals/cases/04-home-collects-credential.mjs +35 -0
- package/scaffold/.agent/skills/plugin/evals/cases/05-host-leak-in-scaffold.mjs +8 -28
- package/scaffold/.agent/skills/plugin/evals/cases/06-correct-plugin.mjs +85 -47
- package/scaffold/.agent/skills/plugin/evals/run.mjs +17 -18
- package/scaffold/.agent/skills/plugin/prompts/05-credentials.md +4 -4
- package/scaffold/.agent/skills/plugin/prompts/07-scaffold.md +2 -2
- package/scaffold/.agent/skills/plugin/prompts/08-verify.md +4 -4
- package/scaffold/.agent/skills/plugin/references/anti-patterns.md +2 -2
- package/scaffold/.agent/skills/plugin/references/generative-home.md +38 -0
- package/scaffold/.agent/skills/plugin/references/scaffold.md +5 -5
- package/scaffold/.agent/skills/plugin/references/surfaces.md +13 -4
- package/scaffold/.agent/skills/plugin/scripts/record.mjs +75 -153
- package/scaffold/.agent/skills/plugin/subagents/plugin-reviewer.md +4 -4
- package/scaffold/.agent/skills/total-recall/plugins/auth/DESIGN.md +76 -0
- package/scaffold/.agent/skills/total-recall/plugins/auth/README.md +3 -1
- package/scaffold/.agent/skills/total-recall/plugins/auth/cli.mjs +20 -15
- package/scaffold/.agent/skills/total-recall/plugins/auth/package-lock.json +6 -6
- package/scaffold/.agent/skills/total-recall/plugins/auth/package.json +2 -1
- package/scaffold/.agent/skills/total-recall/plugins/auth/plugin.json +13 -51
- package/scaffold/.agent/skills/total-recall/plugins/auth/ui/chunks/access-overview.md +17 -0
- package/scaffold/.agent/skills/total-recall/plugins/auth/ui/chunks/agent-login.md +20 -0
- package/scaffold/.agent/skills/total-recall/plugins/auth/ui/chunks/baseline-audit.md +22 -0
- package/scaffold/.agent/skills/total-recall/plugins/auth/ui/chunks/credential-repository-binding.md +17 -0
- package/scaffold/.agent/skills/total-recall/plugins/auth/ui/chunks/credential-store-maintenance.md +17 -0
- package/scaffold/.agent/skills/total-recall/plugins/auth/ui/chunks/dashboard-password.md +18 -0
- package/scaffold/.agent/skills/total-recall/plugins/auth/ui/chunks/dashboard-sessions.md +18 -0
- package/scaffold/.agent/skills/total-recall/plugins/auth/ui/chunks/endpoint-probe.md +23 -0
- package/scaffold/.agent/skills/total-recall/plugins/auth/ui/chunks/google-sign-in.md +21 -0
- package/scaffold/.agent/skills/total-recall/plugins/auth/ui/chunks/mesh-machines.md +16 -0
- package/scaffold/.agent/skills/total-recall/plugins/auth/ui/chunks/passkey-enrollment.md +21 -0
- package/scaffold/.agent/skills/total-recall/plugins/auth/ui/chunks/registered-brains.md +16 -0
- package/scaffold/.agent/skills/total-recall/plugins/auth/ui/chunks/scoped-access-keys.md +17 -0
- package/scaffold/.agent/skills/total-recall/plugins/auth/ui/chunks/secret-inventory.md +25 -0
- package/scaffold/.agent/skills/total-recall/plugins/auth/ui/chunks/signing-key-rotation.md +22 -0
- package/scaffold/.agent/skills/total-recall/plugins/auth/ui/home.js +375 -0
- package/scaffold/.agent/skills/total-recall/plugins/automations/{ui/DESIGN.md → DESIGN.md} +32 -21
- package/scaffold/.agent/skills/total-recall/plugins/automations/adapters.mjs +17 -1
- package/scaffold/.agent/skills/total-recall/plugins/automations/cli.mjs +10 -1
- package/scaffold/.agent/skills/total-recall/plugins/automations/package-lock.json +6 -6
- package/scaffold/.agent/skills/total-recall/plugins/automations/package.json +4 -3
- package/scaffold/.agent/skills/total-recall/plugins/automations/plugin.json +11 -27
- package/scaffold/.agent/skills/total-recall/plugins/automations/ui/automations.js +2 -2
- package/scaffold/.agent/skills/total-recall/plugins/automations/ui/chunks/control-ledger.md +42 -0
- package/scaffold/.agent/skills/total-recall/plugins/automations/ui/chunks/local-inventory-triage.md +17 -0
- package/scaffold/.agent/skills/total-recall/plugins/automations/ui/chunks/pending-control.md +39 -0
- package/scaffold/.agent/skills/total-recall/plugins/automations/ui/chunks/run-evidence.md +41 -0
- package/scaffold/.agent/skills/total-recall/plugins/automations/ui/chunks/schedule-vs-run-evidence.md +17 -0
- package/scaffold/.agent/skills/total-recall/plugins/automations/ui/chunks/source-coverage.md +41 -0
- package/scaffold/.agent/skills/total-recall/plugins/automations/ui/{card.js → home.js} +32 -27
- package/scaffold/.agent/skills/total-recall/plugins/code-quality/DESIGN.md +53 -0
- package/scaffold/.agent/skills/total-recall/plugins/code-quality/cli.mjs +23 -0
- package/scaffold/.agent/skills/total-recall/plugins/code-quality/commands/run-core.mjs +4 -2
- package/scaffold/.agent/skills/total-recall/plugins/code-quality/core/check.mjs +4 -2
- package/scaffold/.agent/skills/total-recall/plugins/code-quality/package-lock.json +6 -6
- package/scaffold/.agent/skills/total-recall/plugins/code-quality/package.json +2 -1
- package/scaffold/.agent/skills/total-recall/plugins/code-quality/plugin.json +40 -40
- package/scaffold/.agent/skills/total-recall/plugins/code-quality/ui/chunks/config.md +15 -0
- package/scaffold/.agent/skills/total-recall/plugins/code-quality/ui/chunks/detect.md +15 -0
- package/scaffold/.agent/skills/total-recall/plugins/code-quality/ui/chunks/gate.md +15 -0
- package/scaffold/.agent/skills/total-recall/plugins/code-quality/ui/chunks/init.md +15 -0
- package/scaffold/.agent/skills/total-recall/plugins/code-quality/ui/chunks/report.md +17 -0
- package/scaffold/.agent/skills/total-recall/plugins/code-quality/ui/chunks/run.md +17 -0
- package/scaffold/.agent/skills/total-recall/plugins/code-quality/ui/chunks/snapshot.md +15 -0
- package/scaffold/.agent/skills/total-recall/plugins/code-quality/ui/code-quality-report.mjs +55 -23
- package/scaffold/.agent/skills/total-recall/plugins/code-quality/ui/home.mjs +214 -0
- package/scaffold/.agent/skills/total-recall/plugins/composable-cli/DESIGN.md +146 -0
- package/scaffold/.agent/skills/total-recall/plugins/composable-cli/package-lock.json +6 -6
- package/scaffold/.agent/skills/total-recall/plugins/composable-cli/package.json +2 -1
- package/scaffold/.agent/skills/total-recall/plugins/composable-cli/plugin.json +51 -34
- package/scaffold/.agent/skills/total-recall/plugins/composable-cli/src/command.mjs +1 -1
- package/scaffold/.agent/skills/total-recall/plugins/composable-cli/ui/chunks/create.md +35 -0
- package/scaffold/.agent/skills/total-recall/plugins/composable-cli/ui/chunks/history.md +35 -0
- package/scaffold/.agent/skills/total-recall/plugins/composable-cli/ui/chunks/library.md +49 -0
- package/scaffold/.agent/skills/total-recall/plugins/composable-cli/ui/chunks/list.md +42 -0
- package/scaffold/.agent/skills/total-recall/plugins/composable-cli/ui/chunks/promote.md +40 -0
- package/scaffold/.agent/skills/total-recall/plugins/composable-cli/ui/chunks/read.md +25 -0
- package/scaffold/.agent/skills/total-recall/plugins/composable-cli/ui/chunks/remove.md +26 -0
- package/scaffold/.agent/skills/total-recall/plugins/composable-cli/ui/chunks/rollback.md +26 -0
- package/scaffold/.agent/skills/total-recall/plugins/composable-cli/ui/chunks/show.md +53 -0
- package/scaffold/.agent/skills/total-recall/plugins/composable-cli/ui/chunks/status.md +40 -0
- package/scaffold/.agent/skills/total-recall/plugins/composable-cli/ui/chunks/test-path.md +38 -0
- package/scaffold/.agent/skills/total-recall/plugins/composable-cli/ui/chunks/test.md +46 -0
- package/scaffold/.agent/skills/total-recall/plugins/composable-cli/ui/chunks/update.md +32 -0
- package/scaffold/.agent/skills/total-recall/plugins/composable-cli/ui/home.js +533 -0
- package/scaffold/.agent/skills/total-recall/plugins/embeddings/DESIGN.md +110 -0
- package/scaffold/.agent/skills/total-recall/plugins/embeddings/package-lock.json +6 -6
- package/scaffold/.agent/skills/total-recall/plugins/embeddings/package.json +2 -1
- package/scaffold/.agent/skills/total-recall/plugins/embeddings/plugin.json +13 -10
- package/scaffold/.agent/skills/total-recall/plugins/embeddings/ui/chunks/benchmark.md +49 -0
- package/scaffold/.agent/skills/total-recall/plugins/embeddings/ui/chunks/coverage.md +53 -0
- package/scaffold/.agent/skills/total-recall/plugins/embeddings/ui/chunks/models.md +43 -0
- package/scaffold/.agent/skills/total-recall/plugins/embeddings/ui/chunks/pull.md +41 -0
- package/scaffold/.agent/skills/total-recall/plugins/embeddings/ui/chunks/status.md +59 -0
- package/scaffold/.agent/skills/total-recall/plugins/embeddings/ui/home.js +353 -0
- package/scaffold/.agent/skills/total-recall/plugins/git/DESIGN.md +31 -0
- package/scaffold/.agent/skills/total-recall/plugins/git/README.md +25 -1
- package/scaffold/.agent/skills/total-recall/plugins/git/cli.mjs +5 -1
- package/scaffold/.agent/skills/total-recall/plugins/git/package-lock.json +6 -6
- package/scaffold/.agent/skills/total-recall/plugins/git/package.json +2 -1
- package/scaffold/.agent/skills/total-recall/plugins/git/plugin.json +17 -27
- package/scaffold/.agent/skills/total-recall/plugins/git/src/ops/commit-memory.mjs +30 -6
- package/scaffold/.agent/skills/total-recall/plugins/git/src/ops/fleet.mjs +24 -6
- package/scaffold/.agent/skills/total-recall/plugins/git/src/ops/secret-firewall.mjs +33 -7
- package/scaffold/.agent/skills/total-recall/plugins/git/src/ops/undo.mjs +29 -5
- package/scaffold/.agent/skills/total-recall/plugins/git/src/ops/unmerged.mjs +182 -0
- package/scaffold/.agent/skills/total-recall/plugins/git/ui/chunks/change-comparison.md +27 -0
- package/scaffold/.agent/skills/total-recall/plugins/git/ui/chunks/commit-identity.md +22 -0
- package/scaffold/.agent/skills/total-recall/plugins/git/ui/chunks/create-worktree.md +25 -0
- package/scaffold/.agent/skills/total-recall/plugins/git/ui/chunks/registered-worktrees.md +22 -0
- package/scaffold/.agent/skills/total-recall/plugins/git/ui/chunks/remove-worktree.md +23 -0
- package/scaffold/.agent/skills/total-recall/plugins/git/ui/chunks/repository-ledger.md +25 -0
- package/scaffold/.agent/skills/total-recall/plugins/git/ui/chunks/unmerged-work.md +22 -0
- package/scaffold/.agent/skills/total-recall/plugins/git/ui/chunks/upstream-divergence.md +17 -0
- package/scaffold/.agent/skills/total-recall/plugins/git/ui/chunks/worktree-conflict-triage.md +17 -0
- package/scaffold/.agent/skills/total-recall/plugins/git/ui/chunks/worktree-terminal.md +22 -0
- package/scaffold/.agent/skills/total-recall/plugins/git/ui/home.js +273 -0
- package/scaffold/.agent/skills/total-recall/plugins/mesh/DESIGN.md +35 -0
- package/scaffold/.agent/skills/total-recall/plugins/mesh/README.md +4 -5
- package/scaffold/.agent/skills/total-recall/plugins/mesh/cli.mjs +9 -3
- package/scaffold/.agent/skills/total-recall/plugins/mesh/package-lock.json +6 -6
- package/scaffold/.agent/skills/total-recall/plugins/mesh/package.json +2 -1
- package/scaffold/.agent/skills/total-recall/plugins/mesh/plugin.json +37 -39
- package/scaffold/.agent/skills/total-recall/plugins/mesh/src/cli/mesh.mjs +5 -3
- package/scaffold/.agent/skills/total-recall/plugins/mesh/ui/chunks/active-device-dispatch.md +22 -0
- package/scaffold/.agent/skills/total-recall/plugins/mesh/ui/chunks/control-probe.md +25 -0
- package/scaffold/.agent/skills/total-recall/plugins/mesh/ui/chunks/control-server.md +22 -0
- package/scaffold/.agent/skills/total-recall/plugins/mesh/ui/chunks/credential-readiness.md +23 -0
- package/scaffold/.agent/skills/total-recall/plugins/mesh/ui/chunks/elected-leader.md +22 -0
- package/scaffold/.agent/skills/total-recall/plugins/mesh/ui/chunks/enrollment-key.md +22 -0
- package/scaffold/.agent/skills/total-recall/plugins/mesh/ui/chunks/file-receipt.md +25 -0
- package/scaffold/.agent/skills/total-recall/plugins/mesh/ui/chunks/fleet-execution.md +23 -0
- package/scaffold/.agent/skills/total-recall/plugins/mesh/ui/chunks/fleet-presence.md +23 -0
- package/scaffold/.agent/skills/total-recall/plugins/mesh/ui/chunks/interactive-ssh.md +22 -0
- package/scaffold/.agent/skills/total-recall/plugins/mesh/ui/chunks/local-enrollment.md +23 -0
- package/scaffold/.agent/skills/total-recall/plugins/mesh/ui/chunks/mesh-policy.md +23 -0
- package/scaffold/.agent/skills/total-recall/plugins/mesh/ui/chunks/network-atlas.md +25 -0
- package/scaffold/.agent/skills/total-recall/plugins/mesh/ui/chunks/node-roster.md +26 -0
- package/scaffold/.agent/skills/total-recall/plugins/mesh/ui/chunks/node-selection-proof.md +17 -0
- package/scaffold/.agent/skills/total-recall/plugins/mesh/ui/chunks/owner-observations.md +23 -0
- package/scaffold/.agent/skills/total-recall/plugins/mesh/ui/chunks/peer-path.md +23 -0
- package/scaffold/.agent/skills/total-recall/plugins/mesh/ui/chunks/pinned-leader.md +24 -0
- package/scaffold/.agent/skills/total-recall/plugins/mesh/ui/chunks/reachability-evidence-ladder.md +17 -0
- package/scaffold/.agent/skills/total-recall/plugins/mesh/ui/chunks/recorded-login.md +23 -0
- package/scaffold/.agent/skills/total-recall/plugins/mesh/ui/chunks/remote-execution.md +22 -0
- package/scaffold/.agent/skills/total-recall/plugins/mesh/ui/chunks/secret-grants.md +26 -0
- package/scaffold/.agent/skills/total-recall/plugins/mesh/ui/{card.js → home.js} +93 -22
- package/scaffold/.agent/skills/total-recall/plugins/meta-harness/DESIGN.md +86 -0
- package/scaffold/.agent/skills/total-recall/plugins/meta-harness/README.md +19 -0
- package/scaffold/.agent/skills/total-recall/plugins/meta-harness/cli.mjs +5 -1
- package/scaffold/.agent/skills/total-recall/plugins/meta-harness/package-lock.json +6 -6
- package/scaffold/.agent/skills/total-recall/plugins/meta-harness/package.json +2 -1
- package/scaffold/.agent/skills/total-recall/plugins/meta-harness/plugin.json +43 -22
- package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/chat.mjs +80 -0
- package/scaffold/.agent/skills/total-recall/plugins/meta-harness/ui/chunks/adapter-docs-freshness.md +16 -0
- package/scaffold/.agent/skills/total-recall/plugins/meta-harness/ui/chunks/chat-handoff.md +26 -0
- package/scaffold/.agent/skills/total-recall/plugins/meta-harness/ui/chunks/fleet-maintenance.md +21 -0
- package/scaffold/.agent/skills/total-recall/plugins/meta-harness/ui/chunks/harness-inventory.md +21 -0
- package/scaffold/.agent/skills/total-recall/plugins/meta-harness/ui/chunks/included-access.md +18 -0
- package/scaffold/.agent/skills/total-recall/plugins/meta-harness/ui/chunks/included-usage-verification.md +15 -0
- package/scaffold/.agent/skills/total-recall/plugins/meta-harness/ui/chunks/independent-review.md +17 -0
- package/scaffold/.agent/skills/total-recall/plugins/meta-harness/ui/chunks/managed-execution.md +20 -0
- package/scaffold/.agent/skills/total-recall/plugins/meta-harness/ui/chunks/official-documentation.md +18 -0
- package/scaffold/.agent/skills/total-recall/plugins/meta-harness/ui/chunks/quota-recovery.md +21 -0
- package/scaffold/.agent/skills/total-recall/plugins/meta-harness/ui/chunks/review-routing-decision.md +16 -0
- package/scaffold/.agent/skills/total-recall/plugins/meta-harness/ui/chunks/route-work.md +18 -0
- package/scaffold/.agent/skills/total-recall/plugins/meta-harness/ui/chunks/routing-evidence.md +18 -0
- package/scaffold/.agent/skills/total-recall/plugins/meta-harness/ui/chunks/run-ledger.md +18 -0
- package/scaffold/.agent/skills/total-recall/plugins/meta-harness/ui/home.js +198 -0
- package/scaffold/.agent/skills/total-recall/plugins/openwiki/DESIGN.md +75 -0
- package/scaffold/.agent/skills/total-recall/plugins/openwiki/README.md +2 -0
- package/scaffold/.agent/skills/total-recall/plugins/openwiki/cli.mjs +17 -14
- package/scaffold/.agent/skills/total-recall/plugins/openwiki/package-lock.json +6 -6
- package/scaffold/.agent/skills/total-recall/plugins/openwiki/package.json +2 -1
- package/scaffold/.agent/skills/total-recall/plugins/openwiki/plugin.json +10 -44
- package/scaffold/.agent/skills/total-recall/plugins/openwiki/src/generate.mjs +5 -4
- package/scaffold/.agent/skills/total-recall/plugins/openwiki/src/graph.mjs +3 -4
- package/scaffold/.agent/skills/total-recall/plugins/openwiki/src/ingest.mjs +23 -18
- package/scaffold/.agent/skills/total-recall/plugins/openwiki/src/mesh-vaults.mjs +8 -3
- package/scaffold/.agent/skills/total-recall/plugins/openwiki/src/refresh.mjs +6 -3
- package/scaffold/.agent/skills/total-recall/plugins/openwiki/src/search.mjs +3 -5
- package/scaffold/.agent/skills/total-recall/plugins/openwiki/src/token.mjs +21 -47
- package/scaffold/.agent/skills/total-recall/plugins/openwiki/src/upstream-watcher.mjs +17 -6
- package/scaffold/.agent/skills/total-recall/plugins/openwiki/ui/chunks/auto-refresh.md +18 -0
- package/scaffold/.agent/skills/total-recall/plugins/openwiki/ui/chunks/bootstrap-pages.md +18 -0
- package/scaffold/.agent/skills/total-recall/plugins/openwiki/ui/chunks/generate-wiki.md +21 -0
- package/scaffold/.agent/skills/total-recall/plugins/openwiki/ui/chunks/generator-settings.md +18 -0
- package/scaffold/.agent/skills/total-recall/plugins/openwiki/ui/chunks/host-probe.md +18 -0
- package/scaffold/.agent/skills/total-recall/plugins/openwiki/ui/chunks/ingest-preview.md +18 -0
- package/scaffold/.agent/skills/total-recall/plugins/openwiki/ui/chunks/link-hygiene.md +21 -0
- package/scaffold/.agent/skills/total-recall/plugins/openwiki/ui/chunks/link-topology.md +19 -0
- package/scaffold/.agent/skills/total-recall/plugins/openwiki/ui/chunks/passage-search.md +19 -0
- package/scaffold/.agent/skills/total-recall/plugins/openwiki/ui/chunks/propagation-plan.md +18 -0
- package/scaffold/.agent/skills/total-recall/plugins/openwiki/ui/chunks/provider-credentials.md +18 -0
- package/scaffold/.agent/skills/total-recall/plugins/openwiki/ui/chunks/refresh-report.md +18 -0
- package/scaffold/.agent/skills/total-recall/plugins/openwiki/ui/chunks/task-evidence.md +22 -0
- package/scaffold/.agent/skills/total-recall/plugins/openwiki/ui/chunks/upstream-record.md +18 -0
- package/scaffold/.agent/skills/total-recall/plugins/openwiki/ui/chunks/watch-upstream.md +18 -0
- package/scaffold/.agent/skills/total-recall/plugins/openwiki/ui/chunks/wiki-atlas.md +19 -0
- package/scaffold/.agent/skills/total-recall/plugins/openwiki/ui/panel.js +261 -513
- package/scaffold/.agent/skills/total-recall/plugins/security/DESIGN.md +84 -0
- package/scaffold/.agent/skills/total-recall/plugins/security/README.md +4 -2
- package/scaffold/.agent/skills/total-recall/plugins/security/cli.mjs +7 -19
- package/scaffold/.agent/skills/total-recall/plugins/security/package-lock.json +6 -6
- package/scaffold/.agent/skills/total-recall/plugins/security/package.json +2 -3
- package/scaffold/.agent/skills/total-recall/plugins/security/plugin.json +19 -52
- package/scaffold/.agent/skills/total-recall/plugins/security/ui/chunks/device-evidence.md +21 -0
- package/scaffold/.agent/skills/total-recall/plugins/security/ui/chunks/evidence-desk.md +18 -0
- package/scaffold/.agent/skills/total-recall/plugins/security/ui/chunks/guided-device-review.md +17 -0
- package/scaffold/.agent/skills/total-recall/plugins/security/ui/chunks/host-and-network.md +21 -0
- package/scaffold/.agent/skills/total-recall/plugins/security/ui/chunks/https-headers.md +17 -0
- package/scaffold/.agent/skills/total-recall/plugins/security/ui/chunks/mesh-coverage.md +18 -0
- package/scaffold/.agent/skills/total-recall/plugins/security/ui/chunks/private-security-tools.md +16 -0
- package/scaffold/.agent/skills/total-recall/plugins/security/ui/chunks/single-origin-header-check.md +17 -0
- package/scaffold/.agent/skills/total-recall/plugins/security/ui/chunks/source-review.md +21 -0
- package/scaffold/.agent/skills/total-recall/plugins/security/ui/chunks/ssh-permission-plan.md +17 -0
- package/scaffold/.agent/skills/total-recall/plugins/security/ui/home.js +283 -0
- package/scaffold/.agent/skills/total-recall/plugins/ssss/DESIGN.md +93 -0
- package/scaffold/.agent/skills/total-recall/plugins/ssss/package-lock.json +2 -2
- package/scaffold/.agent/skills/total-recall/plugins/ssss/package.json +2 -1
- package/scaffold/.agent/skills/total-recall/plugins/ssss/plugin.json +5 -3
- package/scaffold/.agent/skills/total-recall/plugins/ssss/ui/chunks/conformance.md +26 -0
- package/scaffold/.agent/skills/total-recall/plugins/ssss/ui/chunks/registry.md +37 -0
- package/scaffold/.agent/skills/total-recall/plugins/ssss/ui/chunks/repair.md +42 -0
- package/scaffold/.agent/skills/total-recall/plugins/ssss/ui/chunks/scaffold.md +33 -0
- package/scaffold/.agent/skills/total-recall/plugins/ssss/ui/chunks/status.md +43 -0
- package/scaffold/.agent/skills/total-recall/plugins/ssss/ui/chunks/validate.md +34 -0
- package/scaffold/.agent/skills/total-recall/plugins/ssss/ui/chunks/validation-diagnostics.md +17 -0
- package/scaffold/.agent/skills/total-recall/plugins/ssss/ui/chunks/workspace-registry-preflight.md +17 -0
- package/scaffold/.agent/skills/total-recall/plugins/ssss/ui/home.js +348 -0
- package/scaffold/.agent/skills/total-recall/references/operations.md +2 -0
- package/src/cli/api.mjs +3 -3
- package/src/cli/brief.mjs +3 -0
- package/src/cli/config.mjs +16 -0
- package/src/cli/connect.mjs +2 -7
- package/src/cli/plugin/chunks.mjs +145 -0
- package/src/cli/plugin/create.mjs +24 -52
- package/src/cli/plugin/index.mjs +20 -9
- package/src/cli/setup.mjs +2 -9
- package/src/cli/start.mjs +1 -0
- package/src/cli/startup.mjs +18 -0
- package/src/core/app-deploy/design-tokens.mjs +14 -9
- package/src/core/app-deploy/ui-elements.mjs +1 -1
- package/src/core/brain-registry.mjs +5 -0
- package/src/core/brain-updates.mjs +5 -1
- package/src/core/daemon-control.mjs +5 -1
- package/src/core/dashboard-bundle.mjs +261 -0
- package/src/core/default-plugin-refresh.mjs +39 -0
- package/src/core/ensure-dependencies.mjs +19 -15
- package/src/core/memory-brain.mjs +30 -8
- package/src/core/plugin-activation.mjs +63 -20
- package/src/core/plugin-contracts/{card.mjs → manifest.mjs} +23 -57
- package/src/core/plugin-contracts/startup-check.mjs +49 -0
- package/src/core/plugin-contracts/ui.mjs +9 -3
- package/src/core/plugin-distribution.mjs +6 -6
- package/src/core/plugin-lifecycle.mjs +58 -0
- package/src/core/plugin-loader.mjs +9 -5
- package/src/core/plugin-runner.mjs +21 -10
- package/src/core/plugin-startup-checks.mjs +44 -0
- package/src/core/plugin-state.mjs +19 -5
- package/src/core/plugin-store.mjs +30 -11
- package/src/core/project-id.mjs +15 -5
- package/src/core/required-plugins.mjs +31 -6
- package/src/core/schema.mjs +2 -0
- package/src/core/ssss-operation-service.mjs +1 -0
- package/src/core/startup-health.mjs +28 -3
- package/src/core/testing/plugin-fixture.mjs +22 -21
- package/src/core/throttled-fetch.mjs +25 -18
- package/src/core/ui-chunk-choice.mjs +77 -0
- package/src/core/ui-chunk.registry.json +23 -0
- package/src/core/ui-chunks.mjs +434 -0
- package/src/core/vault-cache.mjs +58 -59
- package/src/core/workspace-compositions.mjs +151 -0
- package/src/core/workspace-drawers.mjs +79 -0
- package/src/server/api.mjs +4 -27
- package/src/server/index.mjs +28 -21
- package/src/server/memory-events.mjs +31 -0
- package/src/server/plugin-brain-targets.mjs +89 -0
- package/src/server/rest.mjs +2 -0
- package/src/server/route-inventory.mjs +3 -2
- package/src/server/route-manifest.json +52 -4
- package/src/server/routes/_shared.mjs +7 -14
- package/src/server/routes/brains.mjs +29 -44
- package/src/server/routes/dashboard.mjs +26 -0
- package/src/server/routes/plugins.mjs +154 -45
- package/src/server/routes/ui-chunks.mjs +192 -0
- package/frontend/dist/assets/index-DACh1dhx.js +0 -5613
- package/frontend/dist/assets/index-DtwsDyFM.css +0 -1
- package/frontend/src/components/plugins/card/CardEngine.tsx +0 -108
- package/frontend/src/components/plugins/card/CardScenes.tsx +0 -103
- package/frontend/src/components/plugins/card/ExecutionPicker.tsx +0 -64
- package/frontend/src/components/plugins/card/args.ts +0 -28
- package/frontend/src/components/plugins/card/card.css +0 -32
- package/frontend/src/components/plugins/card/cli-line.ts +0 -13
- package/frontend/src/components/plugins/card/contract.ts +0 -91
- package/frontend/src/components/plugins/card/format.ts +0 -49
- package/frontend/src/components/plugins/card/runner-context.ts +0 -11
- package/frontend/src/components/plugins/card/runner.tsx +0 -43
- package/frontend/src/components/plugins/card/theme.ts +0 -35
- package/frontend/src/components/plugins/card/use-source.ts +0 -22
- package/frontend/src/components/plugins/card/widgets.tsx +0 -94
- package/frontend/src/components/plugins/workspace/WorkspaceHost.tsx +0 -23
- package/frontend/src/pages/PluginsPage.tsx +0 -1719
- package/scaffold/.agent/skills/plugin/evals/cases/04-card-collects-credential.mjs +0 -47
- package/scaffold/.agent/skills/plugin/prompts/06-card.md +0 -46
- package/scaffold/.agent/skills/plugin/references/card.md +0 -90
- package/scaffold/.agent/skills/plugin/subagents/card-designer.md +0 -60
- package/scaffold/.agent/skills/total-recall/plugins/auth/ui/DESIGN.md +0 -67
- package/scaffold/.agent/skills/total-recall/plugins/auth/ui/card.js +0 -1179
- package/scaffold/.agent/skills/total-recall/plugins/code-quality/ui/DESIGN.md +0 -32
- package/scaffold/.agent/skills/total-recall/plugins/code-quality/ui/card.js +0 -588
- package/scaffold/.agent/skills/total-recall/plugins/composable-cli/ui/card.js +0 -575
- package/scaffold/.agent/skills/total-recall/plugins/embeddings/ui/DESIGN.md +0 -32
- package/scaffold/.agent/skills/total-recall/plugins/embeddings/ui/card.js +0 -482
- package/scaffold/.agent/skills/total-recall/plugins/git/ui/DESIGN.md +0 -100
- package/scaffold/.agent/skills/total-recall/plugins/git/ui/card.js +0 -637
- package/scaffold/.agent/skills/total-recall/plugins/mesh/ui/DESIGN.md +0 -217
- package/scaffold/.agent/skills/total-recall/plugins/meta-harness/ui/DESIGN.md +0 -32
- package/scaffold/.agent/skills/total-recall/plugins/meta-harness/ui/card.js +0 -1276
- package/scaffold/.agent/skills/total-recall/plugins/openwiki/ui/DESIGN.md +0 -45
- package/scaffold/.agent/skills/total-recall/plugins/openwiki/ui/card.js +0 -1401
- package/scaffold/.agent/skills/total-recall/plugins/security/scripts/build-ui.mjs +0 -4
- package/scaffold/.agent/skills/total-recall/plugins/security/ui/DESIGN.md +0 -41
- package/scaffold/.agent/skills/total-recall/plugins/security/ui/card.js +0 -396
- package/scaffold/.agent/skills/total-recall/plugins/security/ui/panel.js +0 -98
- package/scaffold/.agent/skills/total-recall/plugins/ssss/ui/DESIGN.md +0 -15
- package/scaffold/.agent/skills/total-recall/plugins/ssss/ui/card.js +0 -182
|
@@ -12,14 +12,10 @@ import fs from 'node:fs';
|
|
|
12
12
|
import path from 'node:path';
|
|
13
13
|
import os from 'node:os';
|
|
14
14
|
import { requireAuth, requireScope } from '../auth.mjs';
|
|
15
|
+
import { dashboardBrainDir, readRegisteredProjects } from '../../core/brain-registry.mjs';
|
|
15
16
|
import { getNodes } from '../../core/vault-cache.mjs';
|
|
16
17
|
import { logger } from '../../core/logger.mjs';
|
|
17
|
-
import {
|
|
18
|
-
BRAIN_DIR,
|
|
19
|
-
VAULT_DIR,
|
|
20
|
-
DERIVED_DIR,
|
|
21
|
-
serverError,
|
|
22
|
-
} from './_shared.mjs';
|
|
18
|
+
import { serverError } from './_shared.mjs';
|
|
23
19
|
|
|
24
20
|
const router = Router();
|
|
25
21
|
|
|
@@ -49,8 +45,9 @@ function countMd(dir) {
|
|
|
49
45
|
* List all known brains (global + registered projects + optional tenant) with state metadata.
|
|
50
46
|
*/
|
|
51
47
|
router.get('/api/brains', requireAuth, requireScope('ssss:read'), async (req, res) => {
|
|
48
|
+
res.set('Cache-Control', 'private, no-store');
|
|
52
49
|
try {
|
|
53
|
-
const globalBrainDir =
|
|
50
|
+
const globalBrainDir = dashboardBrainDir();
|
|
54
51
|
const brains = [];
|
|
55
52
|
|
|
56
53
|
// Global brain
|
|
@@ -75,35 +72,27 @@ router.get('/api/brains', requireAuth, requireScope('ssss:read'), async (req, re
|
|
|
75
72
|
});
|
|
76
73
|
|
|
77
74
|
// Project brains from registry
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
layer: 'project',
|
|
96
|
-
path: project.brainDir,
|
|
97
|
-
project_root: project.path,
|
|
98
|
-
exists: true,
|
|
99
|
-
node_count: nodeCount,
|
|
100
|
-
registered_at: project.registered_at,
|
|
101
|
-
last_compiled: project.last_compiled || getLastModified(path.join(project.brainDir, 'memory-derived', 'graph-index.jsonl')),
|
|
102
|
-
});
|
|
103
|
-
}
|
|
104
|
-
} catch (err) {
|
|
105
|
-
logger.error('server', 'Failed to read project registry', { error: err.message });
|
|
75
|
+
try {
|
|
76
|
+
const registry = readRegisteredProjects(globalBrainDir);
|
|
77
|
+
for (const project of registry) {
|
|
78
|
+
if (!project.name || !project.path || !project.brainDir || !fs.existsSync(project.path)) continue;
|
|
79
|
+
const duplicateName = registry.some(other => other !== project && other.name === project.name);
|
|
80
|
+
if (duplicateName && !project.project_id) continue;
|
|
81
|
+
brains.push({
|
|
82
|
+
id: `project:${duplicateName ? project.project_id : project.name}`,
|
|
83
|
+
name: project.name,
|
|
84
|
+
layer: 'project',
|
|
85
|
+
path: project.brainDir,
|
|
86
|
+
project_root: project.path,
|
|
87
|
+
exists: true,
|
|
88
|
+
node_count: countMd(path.join(project.brainDir, 'memory-vault')),
|
|
89
|
+
registered_at: project.registered_at,
|
|
90
|
+
last_compiled: project.last_compiled || getLastModified(path.join(project.brainDir, 'memory-derived', 'graph-index.jsonl')),
|
|
91
|
+
});
|
|
106
92
|
}
|
|
93
|
+
} catch (err) {
|
|
94
|
+
logger.error('server', 'Failed to read project registry', { error: err.message });
|
|
95
|
+
return res.status(503).json({ error: 'Brain registry metadata is unavailable' });
|
|
107
96
|
}
|
|
108
97
|
|
|
109
98
|
// Optional remote/tenant vault from config
|
|
@@ -145,22 +134,18 @@ router.get('/api/brains', requireAuth, requireScope('ssss:read'), async (req, re
|
|
|
145
134
|
* List all memory nodes for a specific brain.
|
|
146
135
|
*/
|
|
147
136
|
router.get('/api/brains/:id/nodes', requireAuth, requireScope('ssss:read', 'memory:read'), async (req, res) => {
|
|
137
|
+
res.set('Cache-Control', 'private, no-store');
|
|
148
138
|
try {
|
|
149
139
|
const brainId = req.params.id;
|
|
150
140
|
let vaultDir;
|
|
151
141
|
|
|
152
142
|
if (brainId === 'global') {
|
|
153
|
-
|
|
154
|
-
vaultDir = path.join(brainDir, 'memory-vault');
|
|
143
|
+
vaultDir = path.join(dashboardBrainDir(), 'memory-vault');
|
|
155
144
|
} else if (brainId.startsWith('project:')) {
|
|
156
145
|
const projectName = brainId.slice('project:'.length);
|
|
157
|
-
const
|
|
158
|
-
const
|
|
159
|
-
|
|
160
|
-
return res.status(404).json({ error: 'Project registry not found' });
|
|
161
|
-
}
|
|
162
|
-
const registry = JSON.parse(fs.readFileSync(registryPath, 'utf8'));
|
|
163
|
-
const project = registry.find(p => p.name === projectName);
|
|
146
|
+
const registry = readRegisteredProjects(dashboardBrainDir());
|
|
147
|
+
const matching = registry.filter(p => p.name === projectName || p.project_id === projectName);
|
|
148
|
+
const project = matching.length === 1 ? matching[0] : null;
|
|
164
149
|
if (!project) {
|
|
165
150
|
return res.status(404).json({ error: `Project "${projectName}" not found in registry` });
|
|
166
151
|
}
|
|
@@ -4,8 +4,10 @@ import path from 'node:path';
|
|
|
4
4
|
import { fileURLToPath } from 'node:url';
|
|
5
5
|
import { requireAuth, requireScope } from '../auth.mjs';
|
|
6
6
|
import { getNodes } from '../../core/vault-cache.mjs';
|
|
7
|
+
import { DrawerInputError, readDrawerConfig, writeDrawerConfig } from '../../core/workspace-drawers.mjs';
|
|
7
8
|
import {
|
|
8
9
|
VAULT_DIR,
|
|
10
|
+
BRAIN_DIR,
|
|
9
11
|
INSTRUCTIONS,
|
|
10
12
|
ROOT as SHARED_ROOT,
|
|
11
13
|
resolveVaultFromQuery,
|
|
@@ -17,6 +19,30 @@ const router = Router();
|
|
|
17
19
|
// (breaks on Windows and on paths with encoded characters).
|
|
18
20
|
const ROOT = process.env.TR_ROOT || SHARED_ROOT || path.join(path.dirname(fileURLToPath(import.meta.url)), '..', '..', '..');
|
|
19
21
|
|
|
22
|
+
router.get('/api/dashboard/drawers/:edge', requireAuth, requireScope('config:read'), (req, res) => {
|
|
23
|
+
res.set('Cache-Control', 'no-store');
|
|
24
|
+
try {
|
|
25
|
+
return res.json(readDrawerConfig(BRAIN_DIR, req.params.edge));
|
|
26
|
+
} catch (error) {
|
|
27
|
+
if (error instanceof DrawerInputError) return res.status(400).json({ error: error.message });
|
|
28
|
+
return res.status(500).json({ error: 'Could not read the saved drawer.' });
|
|
29
|
+
}
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
router.put('/api/dashboard/drawers/:edge', requireAuth, requireScope('config:write'), async (req, res) => {
|
|
33
|
+
res.set('Cache-Control', 'no-store');
|
|
34
|
+
if (!req.is('application/json')) return res.status(415).json({ error: 'Use JSON to save the drawer.' });
|
|
35
|
+
try {
|
|
36
|
+
return res.json(await writeDrawerConfig(BRAIN_DIR, req.params.edge, req.body?.config, req.body?.version));
|
|
37
|
+
} catch (error) {
|
|
38
|
+
if (error instanceof DrawerInputError) return res.status(400).json({ error: error.message });
|
|
39
|
+
if (error.message.startsWith('Plugin state version conflict')) {
|
|
40
|
+
return res.status(409).json({ error: 'The drawer changed elsewhere. Reload it before saving.' });
|
|
41
|
+
}
|
|
42
|
+
return res.status(500).json({ error: 'Could not save the drawer.' });
|
|
43
|
+
}
|
|
44
|
+
});
|
|
45
|
+
|
|
20
46
|
router.get('/api/dashboard/instructions', requireAuth, requireScope('instructions:read'), (req, res) => {
|
|
21
47
|
const vaultDir = resolveVaultFromQuery(req);
|
|
22
48
|
const paths = pathsForVault(vaultDir);
|
|
@@ -3,7 +3,8 @@ import fs from "node:fs";
|
|
|
3
3
|
import path from "node:path";
|
|
4
4
|
import matter from "../../core/frontmatter.mjs";
|
|
5
5
|
import { requireAuth, requireScope } from "../auth.mjs";
|
|
6
|
-
import {
|
|
6
|
+
import { resolvePluginBrainTargets } from '../plugin-brain-targets.mjs';
|
|
7
|
+
import { discoverPlugins, getPluginById } from "../../core/plugin-loader.mjs";
|
|
7
8
|
import {
|
|
8
9
|
listInstalledPlugins,
|
|
9
10
|
listAvailableBundled,
|
|
@@ -18,20 +19,25 @@ import {
|
|
|
18
19
|
import { listPeerPlugins } from "../../core/plugin-peers.mjs";
|
|
19
20
|
import { runPluginCommand, validatePluginSecretInput } from "../../core/plugin-runner.mjs";
|
|
20
21
|
import { readPluginRecord, patchPluginRecord, emit, vaultFor } from '../../core/plugin-store.mjs';
|
|
21
|
-
import { setPluginActive } from '../../core/plugin-activation.mjs';
|
|
22
|
+
import { activatePluginOnUse, setPluginActive } from '../../core/plugin-activation.mjs';
|
|
22
23
|
import { pluginActivation } from '../../core/plugin-state.mjs';
|
|
24
|
+
import { REQUIRED_INSTALLATION_PLUGIN_IDS } from '../../core/default-plugins.mjs';
|
|
23
25
|
import { configurationValues } from '../../core/plugin-contracts/configuration.mjs';
|
|
24
26
|
import { writeNodeValidatedAsync } from "../../core/validated-write.mjs";
|
|
25
27
|
import { walkMd } from "../../core/vault.mjs";
|
|
26
28
|
import { serverError, badRequest, VAULT_DIR, BRAIN_DIR, globalBrainDir, sanitizeNode } from "./_shared.mjs";
|
|
27
29
|
|
|
28
30
|
const router = Router();
|
|
31
|
+
let activationTail = Promise.resolve();
|
|
29
32
|
|
|
30
|
-
//
|
|
31
|
-
//
|
|
32
|
-
// runner — at any directory on disk.
|
|
33
|
+
// Omitted selectors use the serving project. Explicit selectors resolve only
|
|
34
|
+
// through registered brains, without accepting host paths from a request.
|
|
33
35
|
const projectRoot = () => process.cwd();
|
|
34
36
|
|
|
37
|
+
function surfaceTarget(req, id) {
|
|
38
|
+
return id === undefined ? { brainDir: BRAIN_DIR, projectRoot: projectRoot() } : resolvePluginBrainTargets([id], req.auth)[0];
|
|
39
|
+
}
|
|
40
|
+
|
|
35
41
|
router.get('/api/plugins/execution-targets', requireAuth, requireScope('config:read'), async (req, res) => {
|
|
36
42
|
try {
|
|
37
43
|
const { loadPluginModule } = await import('../../core/plugin-runtime.mjs');
|
|
@@ -43,19 +49,81 @@ router.get('/api/plugins/execution-targets', requireAuth, requireScope('config:r
|
|
|
43
49
|
|
|
44
50
|
router.put('/api/plugins/:id/activation', requireAuth, requireScope('config:write'), async (req, res) => {
|
|
45
51
|
try {
|
|
46
|
-
const
|
|
47
|
-
|
|
48
|
-
|
|
52
|
+
const { enabled, brainIds, mode } = req.body || {};
|
|
53
|
+
if (req.query.brain !== undefined || req.body?.brainId !== undefined) {
|
|
54
|
+
return badRequest(res, 'Use the brainIds array to target plugin activation.');
|
|
55
|
+
}
|
|
56
|
+
if (mode === undefined && typeof enabled !== 'boolean') return badRequest(res, 'Plugin activation requires a boolean');
|
|
57
|
+
if (mode !== undefined && (enabled !== undefined || !['on-use', 'use'].includes(mode) ||
|
|
58
|
+
!Array.isArray(brainIds) || (mode === 'use' && brainIds.length !== 1))) {
|
|
59
|
+
return badRequest(res, 'On use requires project brain IDs. Opening a plugin requires one project brain ID.');
|
|
60
|
+
}
|
|
61
|
+
// Global changes rebuild project surfaces too, so serialize writes and rebuilds together.
|
|
62
|
+
const change = activationTail.then(async () => {
|
|
63
|
+
if (brainIds === undefined) {
|
|
64
|
+
const result = await setPluginActive(req.params.id, enabled, { projectRoot: projectRoot() });
|
|
65
|
+
const cleanup = reportedCleanup(result);
|
|
66
|
+
const success = result.compilation.ok && cleanup?.ok !== false;
|
|
67
|
+
return { status: success ? 200 : 500, body: { ...result, success, cleanup, ...(cleanup?.error ? { error: cleanup.error } : {}) } };
|
|
68
|
+
}
|
|
69
|
+
const targets = resolvePluginBrainTargets(brainIds, req.auth);
|
|
70
|
+
if (mode && targets.some(target => target.projectRoot === null)) {
|
|
71
|
+
return { status: 400, body: { error: 'On use is available for individual project brains.' } };
|
|
72
|
+
}
|
|
73
|
+
const results = [];
|
|
74
|
+
for (const target of targets) {
|
|
75
|
+
results.push(await updateSelectedPluginActivation(req.params.id, enabled, mode, target));
|
|
76
|
+
}
|
|
77
|
+
return { status: 200, body: { success: results.every(result => result.success), results } };
|
|
78
|
+
});
|
|
79
|
+
activationTail = change.catch(() => {});
|
|
80
|
+
const { status, body } = await change;
|
|
81
|
+
res.status(status).json(body);
|
|
82
|
+
} catch (err) { res.status(err.status || 400).json({ error: err.message }); }
|
|
49
83
|
});
|
|
50
84
|
|
|
85
|
+
function reportedCleanup(result) {
|
|
86
|
+
if (!result.cleanup) return undefined;
|
|
87
|
+
const { ok, state } = result.cleanup;
|
|
88
|
+
return { ok, state, ...(ok ? {} : { error: 'The plugin is off, but cleanup did not finish. Retry cleanup.' }) };
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
async function updateSelectedPluginActivation(id, enabled, mode, { brainId, name, projectRoot }) {
|
|
92
|
+
const identity = { brainId, name };
|
|
93
|
+
const failure = error => ({ ...identity, success: false, error });
|
|
94
|
+
try {
|
|
95
|
+
const plugin = getPluginById(id, projectRoot);
|
|
96
|
+
if (!plugin?.valid) return failure('Plugin is unavailable in this brain.');
|
|
97
|
+
if ((enabled === false || mode === 'on-use') && REQUIRED_INSTALLATION_PLUGIN_IDS.includes(id)) {
|
|
98
|
+
return failure('Required plugins cannot be disabled.');
|
|
99
|
+
}
|
|
100
|
+
const result = mode === 'use'
|
|
101
|
+
? await activatePluginOnUse(id, { projectRoot })
|
|
102
|
+
: await setPluginActive(id, mode ?? enabled, { projectRoot, scope: projectRoot === null ? 'shared' : 'project' });
|
|
103
|
+
if (!result) {
|
|
104
|
+
const activation = pluginActivation(plugin, { projectRoot });
|
|
105
|
+
return activation.enabled ? { ...identity, success: true, activation } : failure('This plugin is not set to open on use.');
|
|
106
|
+
}
|
|
107
|
+
if (mode === 'use' && !result.activation.enabled) return failure('The plugin was switched off before it could open.');
|
|
108
|
+
const cleanup = reportedCleanup(result);
|
|
109
|
+
const error = cleanup?.error || (result.compilation.ok ? undefined : 'Activation was saved, but the workspace could not be rebuilt.');
|
|
110
|
+
return { ...identity, success: !error, activation: result.activation, cleanup, error };
|
|
111
|
+
} catch {
|
|
112
|
+
return failure('Could not complete activation in this brain.');
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
|
|
51
116
|
router.post('/api/plugins/:id/credentials', requireAuth, requireScope('config:write'), async (req, res) => {
|
|
117
|
+
let target;
|
|
118
|
+
try { target = surfaceTarget(req, req.query.brain); }
|
|
119
|
+
catch (error) { return res.status(error.status || 400).json({ error: 'Could not select the credential workspace.' }); }
|
|
52
120
|
try {
|
|
53
|
-
const plugin = getPluginById(req.params.id, projectRoot
|
|
121
|
+
const plugin = getPluginById(req.params.id, target.projectRoot);
|
|
54
122
|
if (!plugin) return res.status(404).json({ error: 'Plugin not installed' });
|
|
55
123
|
const { key, value } = req.body || {};
|
|
56
124
|
if (typeof key !== 'string' || typeof value !== 'string' || value.length > 16384 || !plugin.manifest.secrets?.some(secret => secret.key === key)) return badRequest(res, 'Invalid declared credential');
|
|
57
125
|
const { loadPluginModule } = await import('../../core/plugin-runtime.mjs');
|
|
58
|
-
const owner = await loadPluginModule(plugin.id, 'credentials');
|
|
126
|
+
const owner = await loadPluginModule(plugin.id, 'credentials', { projectRoot: target.projectRoot });
|
|
59
127
|
const result = await owner.saveCredential({ key, value, configuration: readPluginRecord(plugin)?.configuration || {}, brainDir: process.env.TR_SECRETS_BRAIN || globalBrainDir || BRAIN_DIR });
|
|
60
128
|
res.json(result);
|
|
61
129
|
} catch (error) {
|
|
@@ -86,15 +154,21 @@ router.put('/api/plugins/:id/configuration', requireAuth, requireScope('config:w
|
|
|
86
154
|
/** Serve only modules declared by an installed plugin's UI manifest. */
|
|
87
155
|
router.get('/api/plugins/:id/ui/:element', requireAuth, requireScope('config:read'), (req, res) => {
|
|
88
156
|
try {
|
|
89
|
-
const
|
|
90
|
-
|
|
157
|
+
const target = surfaceTarget(req, req.query.brain);
|
|
158
|
+
const plugin = getPluginById(req.params.id, target.projectRoot);
|
|
159
|
+
if (!plugin?.valid || (req.query.brain !== undefined && !pluginActivation(plugin, target).enabled)) {
|
|
160
|
+
return res.status(404).json({ error: 'Plugin unavailable' });
|
|
161
|
+
}
|
|
91
162
|
const element = plugin.manifest?.ui?.elements?.find((item) => item.id === req.params.element);
|
|
92
163
|
if (!element) return res.status(404).json({ error: 'UI element unavailable' });
|
|
93
164
|
const root = fs.realpathSync(plugin.dir);
|
|
94
165
|
const file = fs.realpathSync(path.resolve(root, element.module));
|
|
95
166
|
if (!file.startsWith(root + path.sep)) return res.status(400).json({ error: 'Invalid UI module path' });
|
|
96
167
|
res.type('text/javascript').set('X-Content-Type-Options', 'nosniff').send(fs.readFileSync(file, 'utf8'));
|
|
97
|
-
} catch (err) {
|
|
168
|
+
} catch (err) {
|
|
169
|
+
if ([400, 403, 404, 409, 503].includes(err.status)) return res.status(err.status).json({ error: err.message });
|
|
170
|
+
serverError(res, err);
|
|
171
|
+
}
|
|
98
172
|
});
|
|
99
173
|
|
|
100
174
|
/** Serve only the logo a plugin declares in branding.logo, never an arbitrary file. */
|
|
@@ -121,15 +195,45 @@ router.get('/api/plugins/:id/logo', requireAuth, requireScope('config:read'), (r
|
|
|
121
195
|
* Installed plugins (project + global): manifest facts, provenance, content
|
|
122
196
|
* hash, sharing state and scheduled-task history. Nothing else.
|
|
123
197
|
*/
|
|
124
|
-
router.get("/api/plugins", requireAuth, requireScope("config:read"), (
|
|
198
|
+
router.get("/api/plugins", requireAuth, requireScope("config:read"), (req, res) => {
|
|
125
199
|
try {
|
|
126
|
-
const
|
|
200
|
+
const selection = req.query.brain;
|
|
201
|
+
if (selection !== undefined && typeof selection !== 'string') return badRequest(res, 'Use one comma-separated brain selector.');
|
|
202
|
+
const plugins = selection === undefined
|
|
203
|
+
? listInstalledPlugins(projectRoot())
|
|
204
|
+
: selectedPluginInventory(resolvePluginBrainTargets(selection.split(','), req.auth));
|
|
127
205
|
res.json({ success: true, count: plugins.length, plugins });
|
|
128
206
|
} catch (err) {
|
|
207
|
+
if (err.status) return res.status(err.status).json({ error: err.message });
|
|
129
208
|
serverError(res, err);
|
|
130
209
|
}
|
|
131
210
|
});
|
|
132
211
|
|
|
212
|
+
function selectedPluginInventory(targets) {
|
|
213
|
+
const descriptions = new Map();
|
|
214
|
+
const serving = new Map(discoverPlugins(projectRoot()).map(plugin => [plugin.id, plugin]));
|
|
215
|
+
const catalogs = targets.map(target => new Map(
|
|
216
|
+
listInstalledPlugins(target.projectRoot, { descriptions }).map(plugin => [plugin.id, plugin]),
|
|
217
|
+
));
|
|
218
|
+
const ids = new Set(catalogs.flatMap(catalog => [...catalog.keys()]));
|
|
219
|
+
return [...ids].map(id => {
|
|
220
|
+
const entries = catalogs.map(catalog => catalog.get(id));
|
|
221
|
+
const representative = entries.find(plugin => plugin?.valid) || entries.find(Boolean);
|
|
222
|
+
const activationByBrain = entries.map((plugin, index) => {
|
|
223
|
+
const { brainId, name } = targets[index];
|
|
224
|
+
const state = !plugin ? 'unavailable' : !plugin.valid ? 'error' : (plugin.activation?.state || 'error');
|
|
225
|
+
const mode = state === 'inactive' && plugin.activation?.mode;
|
|
226
|
+
return { brainId, name, state, enabled: state === 'active', required: !!plugin?.activation?.required, ...(mode ? { mode } : {}) };
|
|
227
|
+
});
|
|
228
|
+
const first = activationByBrain[0];
|
|
229
|
+
const state = activationByBrain.every(item => item.state === first.state && item.mode === first.mode) ? first.state : 'mixed';
|
|
230
|
+
const activation = { state, enabled: state === 'active', required: activationByBrain.some(item => item.required),
|
|
231
|
+
...(state !== 'mixed' && first.mode ? { mode: first.mode } : {}) };
|
|
232
|
+
return { ...representative, activation, activationByBrain,
|
|
233
|
+
servingAvailable: Boolean(serving.get(id) && serving.get(id).dir === representative.dir) };
|
|
234
|
+
});
|
|
235
|
+
}
|
|
236
|
+
|
|
133
237
|
/**
|
|
134
238
|
* GET /api/plugins/available
|
|
135
239
|
* Plugins bundled with this Total Recall package, installable on any node.
|
|
@@ -277,28 +381,7 @@ router.get("/api/plugins/:id", requireAuth, requireScope("config:read"), (req, r
|
|
|
277
381
|
try {
|
|
278
382
|
const plugin = getPluginById(req.params.id, projectRoot());
|
|
279
383
|
if (!plugin) return res.status(404).json({ success: false, error: `Plugin ${req.params.id} not found` });
|
|
280
|
-
res.json({ success: true, plugin: { ...describePlugin(plugin), manifest: plugin.manifest } });
|
|
281
|
-
} catch (err) {
|
|
282
|
-
serverError(res, err);
|
|
283
|
-
}
|
|
284
|
-
});
|
|
285
|
-
|
|
286
|
-
/**
|
|
287
|
-
* PATCH /api/plugins/:id/card-face
|
|
288
|
-
* Customizes card face skin, theme, colors, headline, quick actions, and metrics.
|
|
289
|
-
*/
|
|
290
|
-
router.patch("/api/plugins/:id/card-face", requireAuth, requireScope("config:write"), (req, res) => {
|
|
291
|
-
try {
|
|
292
|
-
const plugin = getPluginById(req.params.id, projectRoot());
|
|
293
|
-
if (!plugin) return res.status(404).json({ success: false, error: `Plugin ${req.params.id} not found` });
|
|
294
|
-
const manifestPath = path.join(plugin.dir, "plugin.json");
|
|
295
|
-
if (!fs.existsSync(manifestPath)) return res.status(404).json({ success: false, error: "Manifest not found" });
|
|
296
|
-
const manifest = JSON.parse(fs.readFileSync(manifestPath, "utf8"));
|
|
297
|
-
const { card_face = {}, branding = {} } = req.body || {};
|
|
298
|
-
manifest.card_face = { ...(manifest.card_face || {}), ...card_face };
|
|
299
|
-
manifest.branding = { ...(manifest.branding || {}), ...branding };
|
|
300
|
-
fs.writeFileSync(manifestPath, JSON.stringify(manifest, null, 2) + "\n", "utf8");
|
|
301
|
-
res.json({ success: true, card_face: manifest.card_face, branding: manifest.branding });
|
|
384
|
+
res.json({ success: true, plugin: { ...describePlugin(plugin, projectRoot()), manifest: plugin.manifest } });
|
|
302
385
|
} catch (err) {
|
|
303
386
|
serverError(res, err);
|
|
304
387
|
}
|
|
@@ -312,16 +395,37 @@ router.patch("/api/plugins/:id/card-face", requireAuth, requireScope("config:wri
|
|
|
312
395
|
* write-level regardless of that declaration.
|
|
313
396
|
*/
|
|
314
397
|
router.post("/api/plugins/:id/run", requireAuth, (req, res, next) => {
|
|
315
|
-
const { subcommand, args = [], stdin, execution } = req.body || {};
|
|
398
|
+
const { subcommand, args = [], stdin, execution, brainId } = req.body || {};
|
|
316
399
|
try {
|
|
317
|
-
const
|
|
318
|
-
const
|
|
400
|
+
const target = surfaceTarget(req, brainId);
|
|
401
|
+
const plugin = getPluginById(req.params.id, target.projectRoot);
|
|
402
|
+
if (brainId !== undefined && execution) {
|
|
403
|
+
return res.status(400).json({ success: false, error: 'Use the selected brain with local plugin execution.' });
|
|
404
|
+
}
|
|
405
|
+
if (brainId !== undefined && ['on', 'off'].includes(subcommand || args?.[0])) {
|
|
406
|
+
return res.status(400).json({ success: false, error: 'Use the selected brain activation control.' });
|
|
407
|
+
}
|
|
408
|
+
const declared = plugin?.valid ? plugin.manifest?.cli?.subcommands || [] : [];
|
|
409
|
+
const operation = declared.find(item => item.name === subcommand);
|
|
410
|
+
const activation = ['on', 'off'].includes(subcommand) && Array.isArray(args) && !args.length && stdin === undefined && !execution;
|
|
411
|
+
const browserBlocked = item => item.sensitive_output === true || item.terminal_only === true;
|
|
412
|
+
if (operation ? browserBlocked(operation) : !activation && declared.some(browserBlocked)) {
|
|
413
|
+
return res.status(403).json({ success: false, error: 'Use the owning CLI or account controls for this operation.' });
|
|
414
|
+
}
|
|
415
|
+
const terminalFlags = new Set((operation ? [operation] : declared).flatMap(item => item.terminal_only_args ?? []));
|
|
416
|
+
if (Array.isArray(args) && args.some(arg => typeof arg === 'string' && terminalFlags.has(arg.split('=', 1)[0]))) {
|
|
417
|
+
return res.status(403).json({ success: false, error: 'Use the owning CLI for terminal-only arguments.' });
|
|
418
|
+
}
|
|
319
419
|
res.locals.executionPlugin = plugin;
|
|
420
|
+
res.locals.executionTarget = target;
|
|
320
421
|
const read = typeof subcommand === 'string' && Array.isArray(args) && args.every(arg => typeof arg === 'string') &&
|
|
321
422
|
operation?.effect === 'read' && stdin === undefined && !execution &&
|
|
322
423
|
!operation.args?.some(arg => arg.secret);
|
|
323
424
|
return requireScope(read ? 'config:read' : 'config:write')(req, res, next);
|
|
324
|
-
} catch (error) {
|
|
425
|
+
} catch (error) {
|
|
426
|
+
if ([400, 403, 404, 409, 503].includes(error.status)) return res.status(error.status).json({ error: error.message });
|
|
427
|
+
badRequest(res, error.message);
|
|
428
|
+
}
|
|
325
429
|
}, async (req, res) => {
|
|
326
430
|
try {
|
|
327
431
|
const { subcommand = "", args = [], stdin, execution } = req.body || {};
|
|
@@ -329,21 +433,26 @@ router.post("/api/plugins/:id/run", requireAuth, (req, res, next) => {
|
|
|
329
433
|
return badRequest(res, "subcommand must be a string and args an array of strings");
|
|
330
434
|
}
|
|
331
435
|
const plugin = res.locals.executionPlugin;
|
|
436
|
+
const target = res.locals.executionTarget;
|
|
437
|
+
const cwd = target.projectRoot ?? target.brainDir;
|
|
332
438
|
if (!plugin) return res.status(404).json({ success: false, error: `Plugin ${req.params.id} not found` });
|
|
333
439
|
if (!plugin.valid) return badRequest(res, `Plugin ${plugin.id} has an invalid manifest: ${plugin.errors.join("; ")}`);
|
|
334
440
|
if (!plugin.manifest?.cli?.handler) return badRequest(res, `Plugin ${plugin.id} does not declare a CLI handler`);
|
|
441
|
+
if (req.body?.brainId !== undefined && !pluginActivation(plugin, target).enabled) {
|
|
442
|
+
return res.status(403).json({ success: false, error: 'Plugin is off in the selected brain.' });
|
|
443
|
+
}
|
|
335
444
|
validatePluginSecretInput(plugin, subcommand, stdin);
|
|
336
445
|
|
|
337
446
|
if (execution) {
|
|
338
447
|
if (stdin !== undefined) return badRequest(res, 'Credential operations require direct local execution');
|
|
339
|
-
if (!pluginActivation(plugin,
|
|
448
|
+
if (!pluginActivation(plugin, target).enabled && !['on', 'off', 'status', 'health', 'jobs', '--help', 'help'].includes(subcommand)) return badRequest(res, `Plugin ${plugin.id} is inactive`);
|
|
340
449
|
const { loadPluginModule } = await import('../../core/plugin-runtime.mjs');
|
|
341
|
-
const owner = await loadPluginModule('meta-harness', 'command-executor', { activeRequired: true });
|
|
342
|
-
const result = await owner.executeCommand({ plugin, subcommand, args, execution, projectRoot:
|
|
450
|
+
const owner = await loadPluginModule('meta-harness', 'command-executor', { activeRequired: true, projectRoot: target.projectRoot });
|
|
451
|
+
const result = await owner.executeCommand({ plugin, subcommand, args, execution, projectRoot: cwd });
|
|
343
452
|
return res.json({ success: result.ok, pluginId: plugin.id, ...result });
|
|
344
453
|
}
|
|
345
454
|
|
|
346
|
-
const result = await runPluginCommand(plugin, { subcommand, args, ...(stdin !== undefined ? { stdinText: stdin } : {}), cwd: projectRoot
|
|
455
|
+
const result = await runPluginCommand(plugin, { subcommand, args, ...(stdin !== undefined ? { stdinText: stdin } : {}), cwd, projectRoot: target.projectRoot, secretsBrainDir: process.env.TR_SECRETS_BRAIN || globalBrainDir || BRAIN_DIR, maxOutputBytes: 4 * 1024 * 1024 });
|
|
347
456
|
res.json({ success: result.ok, pluginId: plugin.id, ...result });
|
|
348
457
|
} catch (err) {
|
|
349
458
|
badRequest(res, err.message);
|
|
@@ -0,0 +1,192 @@
|
|
|
1
|
+
import { Router } from 'express';
|
|
2
|
+
import { addWorkspaceUiChunk, getUiChunk, readPluginDesign, searchUiChunks, UiChunkInputError } from '../../core/ui-chunks.mjs';
|
|
3
|
+
import { chooseUiComposition } from '../../core/ui-chunk-choice.mjs';
|
|
4
|
+
import {
|
|
5
|
+
createWorkspacePage, listWorkspacePages, prepareWorkspacePage, publishWorkspaceComposition,
|
|
6
|
+
readWorkspaceComposition, readWorkspacePage, saveWorkspaceCompositionPage,
|
|
7
|
+
updateWorkspacePage,
|
|
8
|
+
} from '../../core/workspace-compositions.mjs';
|
|
9
|
+
import { requireAuth, requireScope } from '../auth.mjs';
|
|
10
|
+
import { resolvePluginBrainTargets } from '../plugin-brain-targets.mjs';
|
|
11
|
+
import { BRAIN_DIR } from './_shared.mjs';
|
|
12
|
+
|
|
13
|
+
const router = Router();
|
|
14
|
+
|
|
15
|
+
function workspaceTarget(req, brainId) {
|
|
16
|
+
return brainId === undefined
|
|
17
|
+
? { brainDir: BRAIN_DIR, projectRoot: process.cwd() }
|
|
18
|
+
: resolvePluginBrainTargets([brainId], req.auth)[0];
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
function sendScopeError(res, error) {
|
|
22
|
+
if ([400, 403, 404, 409, 503].includes(error.status)) {
|
|
23
|
+
res.status(error.status).json({ error: error.message });
|
|
24
|
+
return true;
|
|
25
|
+
}
|
|
26
|
+
return false;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
function sendWorkspaceData(res, read, resource = 'UI chunk') {
|
|
30
|
+
res.set('Cache-Control', 'private, no-store').set('X-Content-Type-Options', 'nosniff');
|
|
31
|
+
try {
|
|
32
|
+
const result = read();
|
|
33
|
+
if (!result) return res.status(404).json({ error: `${resource} unavailable` });
|
|
34
|
+
return res.json(result);
|
|
35
|
+
} catch (error) {
|
|
36
|
+
if (error instanceof UiChunkInputError) return res.status(400).json({ error: error.message });
|
|
37
|
+
if (sendScopeError(res, error)) return;
|
|
38
|
+
return res.status(500).json({ error: resource === 'UI chunk' ? 'Could not read the UI chunk catalog' : 'Could not read saved pages' });
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
router.get('/api/ui-chunks', requireAuth, requireScope('config:read'), (req, res) => {
|
|
43
|
+
const { plugin, surface, q, limit, offset } = req.query;
|
|
44
|
+
return sendWorkspaceData(res, () => {
|
|
45
|
+
if (limit !== undefined && (typeof limit !== 'string' || !/^\d{1,3}$/.test(limit))) {
|
|
46
|
+
throw new UiChunkInputError('Limit must be between 1 and 200.');
|
|
47
|
+
}
|
|
48
|
+
if (offset !== undefined && (typeof offset !== 'string' || !/^\d{1,16}$/.test(offset))) {
|
|
49
|
+
throw new UiChunkInputError('Offset must be a nonnegative integer.');
|
|
50
|
+
}
|
|
51
|
+
return searchUiChunks({
|
|
52
|
+
...workspaceTarget(req, req.query.brain), plugin, surface, query: q,
|
|
53
|
+
limit: limit === undefined ? undefined : Number(limit),
|
|
54
|
+
offset: offset === undefined ? undefined : Number(offset),
|
|
55
|
+
});
|
|
56
|
+
});
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
router.get('/api/ui-chunks/:plugin/:id', requireAuth, requireScope('config:read'), (req, res) =>
|
|
60
|
+
sendWorkspaceData(res, () => {
|
|
61
|
+
const chunk = getUiChunk(req.params.plugin, req.params.id, { ...workspaceTarget(req, req.query.brain), surface: req.query.surface });
|
|
62
|
+
return chunk ? { chunk } : null;
|
|
63
|
+
}));
|
|
64
|
+
|
|
65
|
+
router.get('/api/ui-chunks/:plugin/design/revision', requireAuth, requireScope('config:read'), (req, res) =>
|
|
66
|
+
sendWorkspaceData(res, () => {
|
|
67
|
+
const { revision } = req.query;
|
|
68
|
+
if (revision !== undefined && (typeof revision !== 'string' || !/^[a-f0-9]{64}$/.test(revision))) {
|
|
69
|
+
throw new UiChunkInputError('Invalid design revision.');
|
|
70
|
+
}
|
|
71
|
+
return readPluginDesign(req.params.plugin, { ...workspaceTarget(req, req.query.brain), revision });
|
|
72
|
+
}));
|
|
73
|
+
|
|
74
|
+
router.get('/api/ui-chunks/:plugin/composition/current', requireAuth, requireScope('config:read'), (req, res) =>
|
|
75
|
+
sendWorkspaceData(res, () => {
|
|
76
|
+
const { brainDir, projectRoot } = workspaceTarget(req, req.query.brain);
|
|
77
|
+
return readWorkspaceComposition(brainDir, req.params.plugin, projectRoot);
|
|
78
|
+
}));
|
|
79
|
+
|
|
80
|
+
function workspacePagePath(id, brain) {
|
|
81
|
+
return '/memory?' + new URLSearchParams({ page: id, brain });
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
router.get('/api/workspace/pages', requireAuth, requireScope('config:read'), (req, res) =>
|
|
85
|
+
sendWorkspaceData(res, () => {
|
|
86
|
+
const { brainDir } = resolvePluginBrainTargets([req.query.brain], req.auth)[0];
|
|
87
|
+
return { pages: listWorkspacePages(brainDir) };
|
|
88
|
+
}, 'Saved page'));
|
|
89
|
+
|
|
90
|
+
router.get('/api/workspace/pages/:id', requireAuth, requireScope('config:read'), (req, res) =>
|
|
91
|
+
sendWorkspaceData(res, () => {
|
|
92
|
+
const { brainDir, projectRoot } = resolvePluginBrainTargets([req.query.brain], req.auth)[0];
|
|
93
|
+
return readWorkspacePage(brainDir, req.params.id, projectRoot);
|
|
94
|
+
}, 'Saved page'));
|
|
95
|
+
|
|
96
|
+
router.post('/api/workspace/pages', requireAuth, requireScope('config:write'), async (req, res) => {
|
|
97
|
+
res.set('Cache-Control', 'private, no-store');
|
|
98
|
+
if (!req.is('application/json')) return res.status(415).json({ error: 'Use JSON to save a page.' });
|
|
99
|
+
try {
|
|
100
|
+
const { id, title, brainId, sourcePlugin, sourceVersion, keys } = req.body ?? {};
|
|
101
|
+
const { brainDir, projectRoot } = resolvePluginBrainTargets([brainId], req.auth)[0];
|
|
102
|
+
if (keys !== undefined && (sourcePlugin !== undefined || sourceVersion !== undefined)) {
|
|
103
|
+
throw new UiChunkInputError('Supply either ordered block keys or a source Home revision.');
|
|
104
|
+
}
|
|
105
|
+
const page = keys === undefined
|
|
106
|
+
? await saveWorkspaceCompositionPage(brainDir, { id, title, sourcePlugin, sourceVersion }, projectRoot)
|
|
107
|
+
: await createWorkspacePage(brainDir, { id, title, keys }, projectRoot);
|
|
108
|
+
return res.status(201).json({ ...page, path: workspacePagePath(id, brainId) });
|
|
109
|
+
} catch (error) {
|
|
110
|
+
if (error instanceof UiChunkInputError) return res.status(400).json({ error: error.message });
|
|
111
|
+
if (sendScopeError(res, error)) return;
|
|
112
|
+
return res.status(500).json({ error: 'Could not save the page.' });
|
|
113
|
+
}
|
|
114
|
+
});
|
|
115
|
+
|
|
116
|
+
router.put('/api/workspace/pages/:id', requireAuth, requireScope('config:write'), async (req, res) => {
|
|
117
|
+
res.set('Cache-Control', 'private, no-store');
|
|
118
|
+
if (!req.is('application/json')) return res.status(415).json({ error: 'Use JSON to edit a page.' });
|
|
119
|
+
try {
|
|
120
|
+
const { brainId, revision, keys } = req.body ?? {};
|
|
121
|
+
const { id } = req.params;
|
|
122
|
+
const { brainDir, projectRoot } = resolvePluginBrainTargets([brainId], req.auth)[0];
|
|
123
|
+
const page = await updateWorkspacePage(brainDir, { id, revision, keys }, projectRoot);
|
|
124
|
+
return res.json({ ...page, path: workspacePagePath(id, brainId) });
|
|
125
|
+
} catch (error) {
|
|
126
|
+
if (error instanceof UiChunkInputError) return res.status(400).json({ error: error.message });
|
|
127
|
+
if (sendScopeError(res, error)) return;
|
|
128
|
+
if (error.message.startsWith('Plugin state version conflict')) {
|
|
129
|
+
return res.status(409).json({ error: 'The saved page changed. Read its current revision before editing.' });
|
|
130
|
+
}
|
|
131
|
+
return res.status(500).json({ error: 'Could not edit the saved page.' });
|
|
132
|
+
}
|
|
133
|
+
});
|
|
134
|
+
|
|
135
|
+
router.post('/api/ui-chunks/pool', requireAuth, requireScope('config:write'), async (req, res) => {
|
|
136
|
+
res.set('Cache-Control', 'private, no-store');
|
|
137
|
+
if (!req.is('application/json')) return res.status(415).json({ error: 'Use JSON to add a block.' });
|
|
138
|
+
try {
|
|
139
|
+
const { brainDir, projectRoot } = workspaceTarget(req, req.body?.brainId);
|
|
140
|
+
return res.status(201).json(await addWorkspaceUiChunk(brainDir, req.body ?? {}, projectRoot));
|
|
141
|
+
} catch (error) {
|
|
142
|
+
if (error instanceof UiChunkInputError) return res.status(400).json({ error: error.message });
|
|
143
|
+
if (sendScopeError(res, error)) return;
|
|
144
|
+
if (error.message.startsWith('Plugin state version conflict')) return res.status(409).json({ error: 'That workspace block ID already exists.' });
|
|
145
|
+
return res.status(500).json({ error: 'Could not add the block to the pool.' });
|
|
146
|
+
}
|
|
147
|
+
});
|
|
148
|
+
|
|
149
|
+
router.post(['/api/ui-chunks/suggest', '/api/ui-chunks/compose'], requireAuth, requireScope('config:write'), async (req, res) => {
|
|
150
|
+
res.set('Cache-Control', 'private, no-store');
|
|
151
|
+
if (!req.is('application/json')) return res.status(415).json({ error: 'Use JSON to request a block suggestion.' });
|
|
152
|
+
const { plugin, query, brainId, pageId, pageTitle } = req.body ?? {};
|
|
153
|
+
if (typeof plugin !== 'string' || typeof query !== 'string' || !query.trim()) {
|
|
154
|
+
return res.status(400).json({ error: 'Choose a plugin and describe what you want to do.' });
|
|
155
|
+
}
|
|
156
|
+
try {
|
|
157
|
+
const publish = req.path === '/api/ui-chunks/compose';
|
|
158
|
+
const named = pageId !== undefined || pageTitle !== undefined;
|
|
159
|
+
if (named && !publish) throw new UiChunkInputError('Use compose to publish a saved page.');
|
|
160
|
+
const resolveDestination = () => named
|
|
161
|
+
? resolvePluginBrainTargets([brainId], req.auth)[0]
|
|
162
|
+
: workspaceTarget(req, brainId);
|
|
163
|
+
const target = resolveDestination();
|
|
164
|
+
if (named) prepareWorkspacePage(target.brainDir, pageId, pageTitle);
|
|
165
|
+
const current = publish && !named ? readWorkspaceComposition(target.brainDir, plugin, target.projectRoot) : null;
|
|
166
|
+
const { chunks } = searchUiChunks({ ...target, plugin: publish ? undefined : plugin, surface: 'page', query, limit: 20 });
|
|
167
|
+
const selection = await chooseUiComposition(chunks, query.trim(), undefined, target);
|
|
168
|
+
if (!publish) return res.json({ chunks, selection });
|
|
169
|
+
if (selection.source !== 'decision' || !selection.keys.length) {
|
|
170
|
+
return res.status(422).json({ error: 'Jev did not select a page to publish.', selection });
|
|
171
|
+
}
|
|
172
|
+
const destination = resolveDestination();
|
|
173
|
+
if (target.brainDir !== destination.brainDir || target.projectRoot !== destination.projectRoot) {
|
|
174
|
+
return res.status(409).json({ error: 'The destination brain changed while Jev was composing.' });
|
|
175
|
+
}
|
|
176
|
+
if (named) {
|
|
177
|
+
const page = await createWorkspacePage(destination.brainDir, { id: pageId, title: pageTitle, keys: selection.keys }, destination.projectRoot);
|
|
178
|
+
return res.json({ selection, version: page.version, path: workspacePagePath(pageId, brainId) });
|
|
179
|
+
}
|
|
180
|
+
const state = await publishWorkspaceComposition(target.brainDir, plugin, selection.keys, current.version, target.projectRoot);
|
|
181
|
+
const params = new URLSearchParams({ plugin });
|
|
182
|
+
if (destination.brainId) params.set('brain', destination.brainId);
|
|
183
|
+
return res.json({ selection, version: state.version, path: '/memory?' + params });
|
|
184
|
+
} catch (error) {
|
|
185
|
+
if (error instanceof UiChunkInputError) return res.status(400).json({ error: error.message });
|
|
186
|
+
if (sendScopeError(res, error)) return;
|
|
187
|
+
if (error.message.startsWith('Plugin state version conflict')) return res.status(409).json({ error: 'The page changed while Jev was composing it.' });
|
|
188
|
+
return res.status(500).json({ error: 'Could not suggest a page block.' });
|
|
189
|
+
}
|
|
190
|
+
});
|
|
191
|
+
|
|
192
|
+
export default router;
|