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
|
@@ -0,0 +1,151 @@
|
|
|
1
|
+
import fs from 'node:fs';
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
import { executePluginState, listPluginStates, PluginStateStore } from './plugin-state-store.mjs';
|
|
4
|
+
import { getUiChunk, UiChunkInputError } from './ui-chunks.mjs';
|
|
5
|
+
import { getPluginById } from './plugin-loader.mjs';
|
|
6
|
+
import { pluginActivation } from './plugin-state.mjs';
|
|
7
|
+
|
|
8
|
+
const OWNER = 'workspace-ui';
|
|
9
|
+
const SAVED_PAGES = 'workspace-pages';
|
|
10
|
+
const ID = /^[a-z][a-z0-9]*(?:-[a-z0-9]+)*$/;
|
|
11
|
+
const VERSION = /^[a-f0-9]{64}$/;
|
|
12
|
+
const MAX_PAGE_BLOCKS = 24;
|
|
13
|
+
|
|
14
|
+
function stateFile(brainDir, plugin) {
|
|
15
|
+
if (typeof plugin !== 'string' || plugin.length > 64 || !ID.test(plugin)) throw new UiChunkInputError('Invalid plugin ID.');
|
|
16
|
+
return path.join(brainDir, 'config', 'workspace-compositions', plugin + '.json');
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
function readCompositionState(brainDir, plugin, projectRoot) {
|
|
20
|
+
const store = new PluginStateStore(stateFile(brainDir, plugin), OWNER);
|
|
21
|
+
const owner = getPluginById(plugin, projectRoot);
|
|
22
|
+
if (!owner?.valid || !pluginActivation(owner, { projectRoot }).enabled) {
|
|
23
|
+
throw new UiChunkInputError('The destination plugin must be installed and enabled.');
|
|
24
|
+
}
|
|
25
|
+
const state = store.read({ keys: [] });
|
|
26
|
+
return { keys: state.keys, version: store.version };
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
function readPageBlocks(brainDir, keys, projectRoot) {
|
|
30
|
+
return keys.flatMap(key => {
|
|
31
|
+
const chunk = readPageBlock(brainDir, key, projectRoot);
|
|
32
|
+
if (!chunk) return [];
|
|
33
|
+
const { html: _html, ...metadata } = chunk;
|
|
34
|
+
return [metadata];
|
|
35
|
+
});
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export function readWorkspaceComposition(brainDir, plugin, projectRoot = process.cwd()) {
|
|
39
|
+
const { keys, version } = readCompositionState(brainDir, plugin, projectRoot);
|
|
40
|
+
return { chunks: readPageBlocks(brainDir, keys, projectRoot), version };
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
function readPageBlock(brainDir, key, projectRoot) {
|
|
44
|
+
if (typeof key !== 'string') return null;
|
|
45
|
+
const [owner, id, ...rest] = key.split(':');
|
|
46
|
+
return ID.test(owner) && typeof id === 'string' && ID.test(id) && !rest.length
|
|
47
|
+
? getUiChunk(owner, id, { surface: 'page', brainDir, projectRoot }) : null;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
export async function publishWorkspaceComposition(brainDir, plugin, keys, version, projectRoot = process.cwd()) {
|
|
51
|
+
const file = stateFile(brainDir, plugin);
|
|
52
|
+
const owner = getPluginById(plugin, projectRoot);
|
|
53
|
+
if (!owner?.valid || !pluginActivation(owner, { projectRoot }).enabled ||
|
|
54
|
+
!keys.length || keys.length > 3 || keys.some(key => !readPageBlock(brainDir, key, projectRoot))) {
|
|
55
|
+
throw new UiChunkInputError('The selected page blocks changed. Compose the view again.');
|
|
56
|
+
}
|
|
57
|
+
return executePluginState({ file, plugin: OWNER, action: 'write', value: { keys }, expectedVersion: version });
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
function pageDirectory(brainDir) {
|
|
61
|
+
const config = path.join(brainDir, 'config');
|
|
62
|
+
const directory = path.join(config, SAVED_PAGES);
|
|
63
|
+
for (const candidate of [config, directory]) {
|
|
64
|
+
const stat = fs.lstatSync(candidate, { throwIfNoEntry: false });
|
|
65
|
+
if (stat && !stat.isDirectory()) throw new Error('Unsafe saved page directory');
|
|
66
|
+
}
|
|
67
|
+
return directory;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
function savedPageFile(brainDir, id) {
|
|
71
|
+
if (typeof id !== 'string' || id.length > 64 || !ID.test(id)) throw new UiChunkInputError('Invalid saved page ID.');
|
|
72
|
+
return path.join(pageDirectory(brainDir), id + '.json');
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
function pageConflict(message) {
|
|
76
|
+
return Object.assign(new Error(message), { status: 409 });
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
export function listWorkspacePages(brainDir) {
|
|
80
|
+
return listPluginStates(pageDirectory(brainDir), SAVED_PAGES)
|
|
81
|
+
.map(({ value: { id, title }, version }) => ({ id, title, version }))
|
|
82
|
+
.sort((a, b) => a.title.localeCompare(b.title) || a.id.localeCompare(b.id));
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
export function readWorkspacePage(brainDir, id, projectRoot = process.cwd()) {
|
|
86
|
+
const store = new PluginStateStore(savedPageFile(brainDir, id), SAVED_PAGES);
|
|
87
|
+
const page = store.read(null);
|
|
88
|
+
if (!page) return null;
|
|
89
|
+
return { id, title: page.title, version: store.version, chunks: readPageBlocks(brainDir, page.keys, projectRoot) };
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
export function prepareWorkspacePage(brainDir, id, title) {
|
|
93
|
+
const file = savedPageFile(brainDir, id);
|
|
94
|
+
const name = typeof title === 'string' ? title.trim() : '';
|
|
95
|
+
if (!name || name.length > 120 || /[\u0000-\u001f\u007f-\u009f]/u.test(title)) {
|
|
96
|
+
throw new UiChunkInputError('Enter a saved page title of up to 120 characters.');
|
|
97
|
+
}
|
|
98
|
+
const store = new PluginStateStore(file, SAVED_PAGES);
|
|
99
|
+
if (store.read(null)) throw pageConflict('That saved page ID already exists.');
|
|
100
|
+
return { title: name, file };
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
function verifyPageBlocks(brainDir, keys, projectRoot) {
|
|
104
|
+
if (!Array.isArray(keys) || !keys.length || keys.length > MAX_PAGE_BLOCKS || new Set(keys).size !== keys.length) {
|
|
105
|
+
throw new UiChunkInputError(`Choose one to ${MAX_PAGE_BLOCKS} distinct page blocks.`);
|
|
106
|
+
}
|
|
107
|
+
const chunks = readPageBlocks(brainDir, keys, projectRoot);
|
|
108
|
+
if (chunks.length !== keys.length) throw pageConflict('The selected page blocks are no longer available.');
|
|
109
|
+
return chunks;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
export async function createWorkspacePage(brainDir, { id, title, keys }, projectRoot = process.cwd()) {
|
|
113
|
+
const page = prepareWorkspacePage(brainDir, id, title);
|
|
114
|
+
const chunks = verifyPageBlocks(brainDir, keys, projectRoot);
|
|
115
|
+
try {
|
|
116
|
+
const { version } = await executePluginState({
|
|
117
|
+
file: page.file, plugin: SAVED_PAGES, action: 'write',
|
|
118
|
+
value: { id, title: page.title, keys }, expectedVersion: null,
|
|
119
|
+
});
|
|
120
|
+
return { id, title: page.title, version, chunks };
|
|
121
|
+
} catch (error) {
|
|
122
|
+
if (error.message.startsWith('Plugin state version conflict')) throw pageConflict('That saved page ID already exists.');
|
|
123
|
+
throw error;
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
export async function updateWorkspacePage(brainDir, { id, keys, revision }, projectRoot = process.cwd()) {
|
|
128
|
+
if (typeof revision !== 'string' || !VERSION.test(revision)) {
|
|
129
|
+
throw new UiChunkInputError('Supply the current saved page revision.');
|
|
130
|
+
}
|
|
131
|
+
const file = savedPageFile(brainDir, id);
|
|
132
|
+
const store = new PluginStateStore(file, SAVED_PAGES);
|
|
133
|
+
const page = store.read(null);
|
|
134
|
+
if (!page) throw Object.assign(new Error('Saved page unavailable'), { status: 404 });
|
|
135
|
+
if (store.version !== revision) throw pageConflict('The saved page changed. Read its current revision before editing.');
|
|
136
|
+
const chunks = verifyPageBlocks(brainDir, keys, projectRoot);
|
|
137
|
+
const { version } = await executePluginState({
|
|
138
|
+
file, plugin: SAVED_PAGES, action: 'write',
|
|
139
|
+
value: { id, title: page.title, keys }, expectedVersion: revision,
|
|
140
|
+
});
|
|
141
|
+
return { id, title: page.title, version, chunks };
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
export async function saveWorkspaceCompositionPage(brainDir, { id, title, sourcePlugin, sourceVersion }, projectRoot = process.cwd()) {
|
|
145
|
+
if (typeof sourceVersion !== 'string' || !VERSION.test(sourceVersion)) {
|
|
146
|
+
throw new UiChunkInputError('Supply the published Home revision to save.');
|
|
147
|
+
}
|
|
148
|
+
const { keys, version } = readCompositionState(brainDir, sourcePlugin, projectRoot);
|
|
149
|
+
if (version !== sourceVersion) throw pageConflict('The plugin Home changed. Read its current revision before saving.');
|
|
150
|
+
return createWorkspacePage(brainDir, { id, title, keys }, projectRoot);
|
|
151
|
+
}
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
/** Private dashboard chrome settings, shared by the HTTP and CLI adapters. */
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
import { executePluginState, PluginStateStore } from './plugin-state-store.mjs';
|
|
4
|
+
|
|
5
|
+
const OWNER = 'workspace-ui';
|
|
6
|
+
const ID = /^[a-z][a-z0-9-]{0,63}$/;
|
|
7
|
+
const EMPTY = { html: '', chunks: [], panels: [], shortcuts: [] };
|
|
8
|
+
export const MAX_DRAWER_HTML_BYTES = 64 * 1024;
|
|
9
|
+
|
|
10
|
+
export class DrawerInputError extends Error {}
|
|
11
|
+
|
|
12
|
+
function stateFile(brainDir, edge) {
|
|
13
|
+
if (edge !== 'right' && edge !== 'bottom') throw new DrawerInputError('Choose the right or bottom drawer.');
|
|
14
|
+
return path.join(brainDir, 'config', `workspace-drawer-${edge}.json`);
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
function list(value, name, limit) {
|
|
18
|
+
if (!Array.isArray(value) || value.length > limit) throw new DrawerInputError(`${name} must contain at most ${limit} items.`);
|
|
19
|
+
return value;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
function shortcutHref(value) {
|
|
23
|
+
if (typeof value !== 'string' || value.length > 1024 || /[\u0000-\u0020\u007f\\]/.test(value)) {
|
|
24
|
+
throw new DrawerInputError('Use a dashboard path or an HTTPS address for each shortcut.');
|
|
25
|
+
}
|
|
26
|
+
const base = 'https://workspace.invalid';
|
|
27
|
+
let url;
|
|
28
|
+
try { url = new URL(value, base); } catch { throw new DrawerInputError('Invalid shortcut address.'); }
|
|
29
|
+
if (value.startsWith('/') && !value.startsWith('//') && url.origin === base && !url.pathname.startsWith('//') &&
|
|
30
|
+
!/%(?:2f|5c)/i.test(url.pathname)) {
|
|
31
|
+
return url.pathname + url.search + url.hash;
|
|
32
|
+
}
|
|
33
|
+
if (value.startsWith('https://') && !url.username && !url.password) return url.href;
|
|
34
|
+
throw new DrawerInputError('Use a dashboard path or an HTTPS address for each shortcut.');
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export function validateDrawerConfig(value) {
|
|
38
|
+
if (!value || typeof value !== 'object' || Array.isArray(value) || Object.keys(value).some(key => !Object.hasOwn(EMPTY, key))) {
|
|
39
|
+
throw new DrawerInputError('Invalid drawer configuration.');
|
|
40
|
+
}
|
|
41
|
+
const { html = '', chunks = [], panels = [], shortcuts = [] } = value;
|
|
42
|
+
if (typeof html !== 'string' || Buffer.byteLength(html, 'utf8') > MAX_DRAWER_HTML_BYTES) {
|
|
43
|
+
throw new DrawerInputError('Drawer HTML must be at most 64 KiB.');
|
|
44
|
+
}
|
|
45
|
+
const chunkIds = list(chunks, 'Chunks', 8).map(key => {
|
|
46
|
+
if (typeof key !== 'string' || key.split(':').length !== 2 || key.split(':').some(id => !ID.test(id))) {
|
|
47
|
+
throw new DrawerInputError('Each chunk needs a plugin ID and a chunk ID.');
|
|
48
|
+
}
|
|
49
|
+
return key;
|
|
50
|
+
});
|
|
51
|
+
const references = list(panels, 'Panels', 6).map(panel => {
|
|
52
|
+
if (typeof panel?.plugin !== 'string' || typeof panel?.element !== 'string' ||
|
|
53
|
+
!ID.test(panel.plugin) || !ID.test(panel.element)) throw new DrawerInputError('Invalid plugin panel reference.');
|
|
54
|
+
return { plugin: panel.plugin, element: panel.element };
|
|
55
|
+
});
|
|
56
|
+
const links = list(shortcuts, 'Shortcuts', 16).map(shortcut => {
|
|
57
|
+
const label = typeof shortcut?.label === 'string' ? shortcut.label.trim() : '';
|
|
58
|
+
if (!label || label.length > 80 || /[\u0000-\u001f\u007f]/.test(label)) throw new DrawerInputError('Give each shortcut a name of 1–80 characters.');
|
|
59
|
+
return { label, href: shortcutHref(shortcut.href) };
|
|
60
|
+
});
|
|
61
|
+
return { html, chunks: [...new Set(chunkIds)], panels: references, shortcuts: links };
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
export function readDrawerConfig(brainDir, edge) {
|
|
65
|
+
const store = new PluginStateStore(stateFile(brainDir, edge), OWNER);
|
|
66
|
+
const config = validateDrawerConfig(store.read(EMPTY));
|
|
67
|
+
return { config, version: store.version };
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
export async function writeDrawerConfig(brainDir, edge, config, version) {
|
|
71
|
+
if (version !== null && (typeof version !== 'string' || !/^[0-9a-f]{64}$/.test(version))) {
|
|
72
|
+
throw new DrawerInputError('Reload the drawer before saving.');
|
|
73
|
+
}
|
|
74
|
+
const value = validateDrawerConfig(config);
|
|
75
|
+
const result = await executePluginState({
|
|
76
|
+
file: stateFile(brainDir, edge), plugin: OWNER, action: 'write', value, expectedVersion: version,
|
|
77
|
+
});
|
|
78
|
+
return { config: value, version: result.version };
|
|
79
|
+
}
|
package/src/server/api.mjs
CHANGED
|
@@ -15,10 +15,10 @@ import { runInSandbox } from '../core/sandbox.mjs';
|
|
|
15
15
|
import { resolveAgentDir } from '../cli/agent-dir.mjs';
|
|
16
16
|
import { removeSessionEmbeddingFromIndex } from '../core/embeddings.mjs';
|
|
17
17
|
import path from 'path';
|
|
18
|
-
import os from 'os';
|
|
19
18
|
import fs from 'fs';
|
|
20
19
|
import matter from '../core/frontmatter.mjs';
|
|
21
20
|
import { agentDir, brainDir, surfaceDir, instructionsFilePath } from '../core/config.mjs';
|
|
21
|
+
import { resolveVaultFromQuery } from './routes/_shared.mjs';
|
|
22
22
|
|
|
23
23
|
const AGENT_DIR = process.env.AGENT_DIR || agentDir;
|
|
24
24
|
const BRAIN_DIR = process.env.TR_BRAIN || path.join(AGENT_DIR, 'skills', 'total-recall');
|
|
@@ -43,32 +43,9 @@ const MODEL_CATALOG_DIR = path.join(ROOT, 'models', 'catalog', 'total-recall');
|
|
|
43
43
|
* @returns {string} Absolute path to the memory-vault directory
|
|
44
44
|
*/
|
|
45
45
|
function resolveVaultDir(brainId) {
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
if (brainId.startsWith('project:')) {
|
|
51
|
-
const projectName = brainId.slice('project:'.length);
|
|
52
|
-
const globalBrainDir = path.join(os.homedir(), '.agent', 'skills', 'total-recall');
|
|
53
|
-
const registryPath = path.join(globalBrainDir, 'config', 'project-registry.json');
|
|
54
|
-
|
|
55
|
-
if (fs.existsSync(registryPath)) {
|
|
56
|
-
try {
|
|
57
|
-
const registry = JSON.parse(fs.readFileSync(registryPath, 'utf8'));
|
|
58
|
-
const project = registry.find(p => p.name === projectName);
|
|
59
|
-
if (project) {
|
|
60
|
-
const projectVaultDir = path.join(project.brainDir, 'memory-vault');
|
|
61
|
-
if (fs.existsSync(projectVaultDir)) {
|
|
62
|
-
return projectVaultDir;
|
|
63
|
-
}
|
|
64
|
-
}
|
|
65
|
-
} catch {
|
|
66
|
-
// Registry parse error — fall through to default
|
|
67
|
-
}
|
|
68
|
-
}
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
return VAULT_DIR;
|
|
46
|
+
return typeof brainId === 'string' && (brainId === 'global' || brainId.startsWith('project:'))
|
|
47
|
+
? resolveVaultFromQuery({ query: { brain: brainId } })
|
|
48
|
+
: VAULT_DIR;
|
|
72
49
|
}
|
|
73
50
|
|
|
74
51
|
function sha256(content) {
|
package/src/server/index.mjs
CHANGED
|
@@ -352,6 +352,8 @@ app.get('/health', requireAuthOrLocal, async (req, res) => {
|
|
|
352
352
|
cloudflare: cloudflareStatus,
|
|
353
353
|
emergency_alerts: emergencyAlerts || null,
|
|
354
354
|
fetch_gate,
|
|
355
|
+
// Whether the served dashboard is as new as this checkout's frontend sources.
|
|
356
|
+
dashboard: dashboardBundle.state(),
|
|
355
357
|
vfs: {
|
|
356
358
|
exists: vaultExists,
|
|
357
359
|
skill_exists: skillExists,
|
|
@@ -444,24 +446,30 @@ try {
|
|
|
444
446
|
|
|
445
447
|
const frontendDist = path.join(ROOT, 'frontend', 'dist');
|
|
446
448
|
|
|
447
|
-
//
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
449
|
+
// Live refresh for the memory page on this port (the /stream monitor listens on 127.0.0.1 only).
|
|
450
|
+
const { memoryEventsHandler } = await import('./memory-events.mjs');
|
|
451
|
+
app.get('/api/memory-events', requireAuth, memoryEventsHandler(logEvents));
|
|
452
|
+
|
|
453
|
+
// Keep the served dashboard as new as its sources. A missing or stale bundle is
|
|
454
|
+
// rebuilt in the background while the current one keeps serving; express.static
|
|
455
|
+
// resolves files per request, so the swapped-in build serves without a restart.
|
|
456
|
+
// Building only when index.html was missing left every later frontend commit unserved.
|
|
457
|
+
const { watchDashboardBundle } = await import('../core/dashboard-bundle.mjs');
|
|
458
|
+
const dashboardBundle = watchDashboardBundle({
|
|
459
|
+
root: ROOT,
|
|
460
|
+
report: (state) => {
|
|
461
|
+
if (state.status === 'current' && state.rebuilt) {
|
|
462
|
+
logger.info('server', `Dashboard bundle rebuilt from newer sources (${Math.round(state.durationMs / 1000)} s); serving it now.`);
|
|
463
|
+
} else if (state.status === 'current') {
|
|
464
|
+
logger.info('server', 'Dashboard bundle is current.');
|
|
465
|
+
} else if (state.retryInMs) {
|
|
466
|
+
logger.info('server', `Dashboard bundle is ${state.status}; rebuilding once the sources stop changing.`);
|
|
467
|
+
} else {
|
|
468
|
+
logger.error('server', `Dashboard bundle is ${state.status} and was not rebuilt: ${state.reason}.${state.build?.output ? ` Build output: ${state.build.output.slice(-1500)}` : ''}`);
|
|
462
469
|
}
|
|
463
|
-
}
|
|
464
|
-
}
|
|
470
|
+
},
|
|
471
|
+
});
|
|
472
|
+
dashboardBundle.check();
|
|
465
473
|
|
|
466
474
|
app.use(express.static(frontendDist));
|
|
467
475
|
|
|
@@ -803,8 +811,7 @@ app.get('/chat', (req, res) => {
|
|
|
803
811
|
|
|
804
812
|
// SPA fallback — serve index.html for all unmatched routes
|
|
805
813
|
app.get(/^(.*)$/, (req, res) => {
|
|
806
|
-
|
|
807
|
-
res.sendFile(indexPath, (err) => {
|
|
814
|
+
res.sendFile('index.html', { root: frontendDist }, (err) => {
|
|
808
815
|
if (err) {
|
|
809
816
|
// Frontend not built yet — return a helpful message
|
|
810
817
|
res.status(200).json({
|
|
@@ -814,7 +821,7 @@ app.get(/^(.*)$/, (req, res) => {
|
|
|
814
821
|
memory: 'GET /api/memory',
|
|
815
822
|
sandbox: 'POST /api/sandbox',
|
|
816
823
|
health: 'GET /health',
|
|
817
|
-
dashboard:
|
|
824
|
+
dashboard: `Dashboard bundle is ${dashboardBundle.state().status}${dashboardBundle.state().reason ? `: ${dashboardBundle.state().reason}` : ''}. The server rebuilds it from frontend/ in the background.`
|
|
818
825
|
}
|
|
819
826
|
});
|
|
820
827
|
}
|
|
@@ -874,7 +881,7 @@ const server = app.listen(PORT, HOST, () => {
|
|
|
874
881
|
|
|
875
882
|
// Only meaningful once the socket is up: asking before the callback fires
|
|
876
883
|
// reads an empty set, which is "unknown", so the watch never armed.
|
|
877
|
-
if (isReachableFromOtherDevices() === false) {
|
|
884
|
+
if (bindResolution.usedLoopbackFallback && isReachableFromOtherDevices() === false) {
|
|
878
885
|
logger.warn(
|
|
879
886
|
'server',
|
|
880
887
|
`Bound to loopback only — no other device can reach this brain at :${PORT}. `
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Live refresh for the memory page, on the dashboard's own port.
|
|
3
|
+
*
|
|
4
|
+
* The page listened to `/stream` on the dashboard origin, but the server
|
|
5
|
+
* serves `/stream` only on the 127.0.0.1 monitor port, so the dashboard got
|
|
6
|
+
* its HTML fallback and the live refresh never connected. This stream is
|
|
7
|
+
* authenticated like the rest of the dashboard API and carries only the
|
|
8
|
+
* subsystem name of the events that change what the page shows, never log
|
|
9
|
+
* contents.
|
|
10
|
+
*/
|
|
11
|
+
export const MEMORY_EVENT_SUBSYSTEMS = new Set(['conflict-detector', 'rebuild', 'session-watcher']);
|
|
12
|
+
const HEARTBEAT_MS = 25000;
|
|
13
|
+
|
|
14
|
+
/** @param {import('node:events').EventEmitter} events Emits 'log' entries ({ subsystem, ... }). */
|
|
15
|
+
export function memoryEventsHandler(events, { heartbeatMs = HEARTBEAT_MS } = {}) {
|
|
16
|
+
return (req, res) => {
|
|
17
|
+
res.writeHead(200, { 'Content-Type': 'text/event-stream', 'Cache-Control': 'no-cache', Connection: 'keep-alive' });
|
|
18
|
+
res.write(': connected\n\n');
|
|
19
|
+
const onLog = (entry) => {
|
|
20
|
+
if (MEMORY_EVENT_SUBSYSTEMS.has(entry?.subsystem)) res.write(`data: ${JSON.stringify({ subsystem: entry.subsystem })}\n\n`);
|
|
21
|
+
};
|
|
22
|
+
events.on('log', onLog);
|
|
23
|
+
// Comment lines keep proxies from closing an idle stream.
|
|
24
|
+
const heartbeat = setInterval(() => res.write(': ping\n\n'), heartbeatMs);
|
|
25
|
+
heartbeat.unref?.();
|
|
26
|
+
req.on('close', () => {
|
|
27
|
+
events.off('log', onLog);
|
|
28
|
+
clearInterval(heartbeat);
|
|
29
|
+
});
|
|
30
|
+
};
|
|
31
|
+
}
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
import fs from 'node:fs';
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
import { dashboardBrainDir, globalBrainDirFor, readBrainToggles, readRegisteredProjects } from '../core/brain-registry.mjs';
|
|
4
|
+
import { projectPluginsDir } from '../core/plugin-loader.mjs';
|
|
5
|
+
import { readProjectId } from '../core/project-id.mjs';
|
|
6
|
+
import { keyHasScope } from './keys.mjs';
|
|
7
|
+
|
|
8
|
+
const UUID = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/;
|
|
9
|
+
const PROJECT_SELECTOR = /^project:[^/\\:,\x00-\x1f\x7f]{1,160}$/u;
|
|
10
|
+
|
|
11
|
+
const targetError = (status, message) => Object.assign(new Error(message), { status });
|
|
12
|
+
|
|
13
|
+
function optionalDirectoriesAreLocal(brainDir) {
|
|
14
|
+
for (const name of ['plugins', 'memory-vault', 'memory-derived']) {
|
|
15
|
+
try {
|
|
16
|
+
if (!fs.lstatSync(path.join(brainDir, name)).isDirectory()) return false;
|
|
17
|
+
} catch (error) {
|
|
18
|
+
if (error.code !== 'ENOENT') return false;
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
return true;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
function resolveProject(row, sharedBrain, toggles) {
|
|
25
|
+
try {
|
|
26
|
+
if (typeof row.path !== 'string' || !path.isAbsolute(row.path) ||
|
|
27
|
+
typeof row.brainDir !== 'string' || !path.isAbsolute(row.brainDir)) return null;
|
|
28
|
+
const projectRoot = fs.realpathSync(row.path);
|
|
29
|
+
const brainDir = path.dirname(projectPluginsDir(projectRoot));
|
|
30
|
+
const identity = path.join(brainDir, 'config', 'brain.json');
|
|
31
|
+
if (brainDir === sharedBrain || fs.realpathSync(row.brainDir) !== brainDir ||
|
|
32
|
+
fs.realpathSync(brainDir) !== brainDir || fs.realpathSync(identity) !== identity ||
|
|
33
|
+
!fs.lstatSync(identity).isFile() || readProjectId(brainDir) !== row.project_id ||
|
|
34
|
+
toggles[path.resolve(row.brainDir)] === false || toggles[brainDir] === false ||
|
|
35
|
+
!optionalDirectoriesAreLocal(brainDir)) return null;
|
|
36
|
+
return { brainDir, projectRoot };
|
|
37
|
+
} catch {
|
|
38
|
+
return null;
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
function authorize(auth, scope) {
|
|
43
|
+
if (auth?.type === 'dashboard_session') return;
|
|
44
|
+
if (auth?.type !== 'pat' || !keyHasScope(auth, scope)) {
|
|
45
|
+
throw targetError(403, 'The token does not grant access to a selected brain.');
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/** Resolve selected IDs against server-owned registrations before plugin I/O. */
|
|
50
|
+
export function resolvePluginBrainTargets(ids, auth, { registryBrain = globalBrainDirFor() } = {}) {
|
|
51
|
+
if (!Array.isArray(ids) || !ids.length || ids.length > 64 ||
|
|
52
|
+
ids.some(id => typeof id !== 'string' || (id !== 'global' && !PROJECT_SELECTOR.test(id)))) {
|
|
53
|
+
throw targetError(400, 'Select up to 64 global or registered project brain IDs.');
|
|
54
|
+
}
|
|
55
|
+
let sharedBrain, rows, toggles, globalToggles;
|
|
56
|
+
try {
|
|
57
|
+
const projectRegistryBrain = dashboardBrainDir(process.env, registryBrain);
|
|
58
|
+
sharedBrain = fs.realpathSync(registryBrain);
|
|
59
|
+
globalToggles = ids.includes('global') ? readBrainToggles(registryBrain, { strict: true }) : {};
|
|
60
|
+
const needsProjects = ids.some(id => id !== 'global');
|
|
61
|
+
toggles = needsProjects ? readBrainToggles(projectRegistryBrain, { strict: true }) : {};
|
|
62
|
+
rows = needsProjects ? readRegisteredProjects(projectRegistryBrain) : [];
|
|
63
|
+
} catch {
|
|
64
|
+
throw targetError(503, 'Brain registry metadata is unavailable.');
|
|
65
|
+
}
|
|
66
|
+
const resolved = new Set();
|
|
67
|
+
return [...new Set(ids)].map(brainId => {
|
|
68
|
+
if (brainId === 'global') {
|
|
69
|
+
authorize(auth, 'brain:global');
|
|
70
|
+
if (globalToggles[path.resolve(registryBrain)] === false || globalToggles[sharedBrain] === false) {
|
|
71
|
+
throw targetError(404, 'The selected global brain is unavailable.');
|
|
72
|
+
}
|
|
73
|
+
return { brainId, name: 'Global Brain', brainDir: sharedBrain, projectRoot: null };
|
|
74
|
+
}
|
|
75
|
+
const selector = brainId.slice('project:'.length);
|
|
76
|
+
const matches = rows.filter(row => row.name === selector || row.project_id === selector);
|
|
77
|
+
if (matches.length > 1) throw targetError(409, 'A selected project brain ID is ambiguous.');
|
|
78
|
+
const row = matches[0];
|
|
79
|
+
if (typeof row?.project_id !== 'string' || !UUID.test(row.project_id) || rows.filter(candidate => candidate.project_id === row.project_id).length !== 1) {
|
|
80
|
+
throw targetError(404, 'A selected project brain is unavailable.');
|
|
81
|
+
}
|
|
82
|
+
authorize(auth, 'brain:project-' + row.project_id);
|
|
83
|
+
const target = resolveProject(row, sharedBrain, toggles);
|
|
84
|
+
if (!target) throw targetError(404, 'A selected project brain is unavailable.');
|
|
85
|
+
if (resolved.has(target.brainDir)) throw targetError(400, 'Select each project brain once.');
|
|
86
|
+
resolved.add(target.brainDir);
|
|
87
|
+
return { brainId, name: row.name || selector, ...target };
|
|
88
|
+
});
|
|
89
|
+
}
|
package/src/server/rest.mjs
CHANGED
|
@@ -121,6 +121,7 @@ import updateRouter from './routes/update.mjs';
|
|
|
121
121
|
import { rulesRouter } from './routes/rules.mjs';
|
|
122
122
|
import notificationsRouter from './routes/notifications.mjs';
|
|
123
123
|
import pluginsRouter from './routes/plugins.mjs';
|
|
124
|
+
import uiChunksRouter from './routes/ui-chunks.mjs';
|
|
124
125
|
// ollamaUrl removed — CLI agents replace Ollama
|
|
125
126
|
import {
|
|
126
127
|
AGENT_DIR,
|
|
@@ -235,6 +236,7 @@ router.use(ssssRouter);
|
|
|
235
236
|
router.use(rulesRouter);
|
|
236
237
|
router.use(notificationsRouter);
|
|
237
238
|
router.use(pluginsRouter);
|
|
239
|
+
router.use(uiChunksRouter);
|
|
238
240
|
for (const pluginRouter of await (await import('../core/plugin-runtime.mjs')).loadPluginRouters()) router.use(pluginRouter);
|
|
239
241
|
|
|
240
242
|
export { router as restRouter };
|
|
@@ -29,13 +29,14 @@ export function extractRoutes(router) {
|
|
|
29
29
|
|
|
30
30
|
for (const layer of stack) {
|
|
31
31
|
if (layer.route) {
|
|
32
|
-
|
|
32
|
+
// Express keeps aliases and regular expressions in Route.path.
|
|
33
|
+
const paths = [layer.route.path].flat(Infinity).map(String);
|
|
33
34
|
const methods = Object.keys(layer.route.methods)
|
|
34
35
|
.filter((method) => layer.route.methods[method])
|
|
35
36
|
.map((method) => method.toUpperCase());
|
|
36
37
|
|
|
37
38
|
for (const method of methods) {
|
|
38
|
-
routes.push({ method, path
|
|
39
|
+
for (const path of paths) routes.push({ method, path });
|
|
39
40
|
}
|
|
40
41
|
} else if (layer.name === 'router' && layer.handle && layer.handle.stack) {
|
|
41
42
|
// Sub-router mounted via router.use()
|
|
@@ -91,6 +91,14 @@
|
|
|
91
91
|
"method": "POST",
|
|
92
92
|
"path": "/api/daemon/restart"
|
|
93
93
|
},
|
|
94
|
+
{
|
|
95
|
+
"method": "GET",
|
|
96
|
+
"path": "/api/dashboard/drawers/:edge"
|
|
97
|
+
},
|
|
98
|
+
{
|
|
99
|
+
"method": "PUT",
|
|
100
|
+
"path": "/api/dashboard/drawers/:edge"
|
|
101
|
+
},
|
|
94
102
|
{
|
|
95
103
|
"method": "GET",
|
|
96
104
|
"path": "/api/dashboard/instructions"
|
|
@@ -467,10 +475,6 @@
|
|
|
467
475
|
"method": "POST",
|
|
468
476
|
"path": "/api/plugins/:id/branding"
|
|
469
477
|
},
|
|
470
|
-
{
|
|
471
|
-
"method": "PATCH",
|
|
472
|
-
"path": "/api/plugins/:id/card-face"
|
|
473
|
-
},
|
|
474
478
|
{
|
|
475
479
|
"method": "GET",
|
|
476
480
|
"path": "/api/plugins/:id/configuration"
|
|
@@ -843,6 +847,34 @@
|
|
|
843
847
|
"method": "GET",
|
|
844
848
|
"path": "/api/tts/status"
|
|
845
849
|
},
|
|
850
|
+
{
|
|
851
|
+
"method": "GET",
|
|
852
|
+
"path": "/api/ui-chunks"
|
|
853
|
+
},
|
|
854
|
+
{
|
|
855
|
+
"method": "GET",
|
|
856
|
+
"path": "/api/ui-chunks/:plugin/:id"
|
|
857
|
+
},
|
|
858
|
+
{
|
|
859
|
+
"method": "GET",
|
|
860
|
+
"path": "/api/ui-chunks/:plugin/composition/current"
|
|
861
|
+
},
|
|
862
|
+
{
|
|
863
|
+
"method": "GET",
|
|
864
|
+
"path": "/api/ui-chunks/:plugin/design/revision"
|
|
865
|
+
},
|
|
866
|
+
{
|
|
867
|
+
"method": "POST",
|
|
868
|
+
"path": "/api/ui-chunks/compose"
|
|
869
|
+
},
|
|
870
|
+
{
|
|
871
|
+
"method": "POST",
|
|
872
|
+
"path": "/api/ui-chunks/pool"
|
|
873
|
+
},
|
|
874
|
+
{
|
|
875
|
+
"method": "POST",
|
|
876
|
+
"path": "/api/ui-chunks/suggest"
|
|
877
|
+
},
|
|
846
878
|
{
|
|
847
879
|
"method": "GET",
|
|
848
880
|
"path": "/api/update/check"
|
|
@@ -939,6 +971,22 @@
|
|
|
939
971
|
"method": "POST",
|
|
940
972
|
"path": "/api/webhooks/test/:provider"
|
|
941
973
|
},
|
|
974
|
+
{
|
|
975
|
+
"method": "GET",
|
|
976
|
+
"path": "/api/workspace/pages"
|
|
977
|
+
},
|
|
978
|
+
{
|
|
979
|
+
"method": "POST",
|
|
980
|
+
"path": "/api/workspace/pages"
|
|
981
|
+
},
|
|
982
|
+
{
|
|
983
|
+
"method": "GET",
|
|
984
|
+
"path": "/api/workspace/pages/:id"
|
|
985
|
+
},
|
|
986
|
+
{
|
|
987
|
+
"method": "PUT",
|
|
988
|
+
"path": "/api/workspace/pages/:id"
|
|
989
|
+
},
|
|
942
990
|
{
|
|
943
991
|
"method": "POST",
|
|
944
992
|
"path": "/auth/change-password"
|
|
@@ -13,6 +13,7 @@ import { fileURLToPath } from 'node:url';
|
|
|
13
13
|
import { agentDir, brainDir, globalBrainDir } from '../../core/config.mjs';
|
|
14
14
|
import { resolveSurfaceDir } from '../../cli/agent-dir.mjs';
|
|
15
15
|
import { logger } from '../../core/logger.mjs';
|
|
16
|
+
import { dashboardBrainDir, readRegisteredProjects } from '../../core/brain-registry.mjs';
|
|
16
17
|
|
|
17
18
|
export { globalBrainDir };
|
|
18
19
|
export const AGENT_DIR = process.env.AGENT_DIR || agentDir;
|
|
@@ -33,7 +34,7 @@ export const MODEL_CATALOG_DIR = path.join(ROOT, 'models', 'catalog', 'total-rec
|
|
|
33
34
|
* Resolve a brain-specific vault directory from the request.
|
|
34
35
|
*
|
|
35
36
|
* Reads `req.query.brain` (query param) or `req.body?.brainId` (body field).
|
|
36
|
-
* -
|
|
37
|
+
* - Missing selection uses the default server vault. Explicit global selection follows the dashboard.
|
|
37
38
|
* - If it starts with `'project:'`, looks up the project brain in the
|
|
38
39
|
* project-registry.json (same resolution logic as GET /api/brains/:id/nodes).
|
|
39
40
|
* - Falls back to VAULT_DIR if the resolved path doesn't exist.
|
|
@@ -52,23 +53,15 @@ export function resolveVaultFromQuery(req, { strict = false } = {}) {
|
|
|
52
53
|
}
|
|
53
54
|
}
|
|
54
55
|
|
|
55
|
-
|
|
56
|
-
if (
|
|
57
|
-
return VAULT_DIR;
|
|
58
|
-
}
|
|
56
|
+
if (!brainId) return VAULT_DIR;
|
|
57
|
+
if (brainId === 'global') return path.join(dashboardBrainDir(process.env, BRAIN_DIR), 'memory-vault');
|
|
59
58
|
|
|
60
59
|
if (brainId.startsWith('project:')) {
|
|
61
60
|
const projectName = brainId.slice('project:'.length);
|
|
62
|
-
const registryPath = path.join(globalBrainDir, 'config', 'project-registry.json');
|
|
63
|
-
|
|
64
|
-
if (!fs.existsSync(registryPath)) {
|
|
65
|
-
if (strict) throw Object.assign(new Error('Selected brain is unavailable'), { status: 404 });
|
|
66
|
-
return VAULT_DIR;
|
|
67
|
-
}
|
|
68
|
-
|
|
69
61
|
try {
|
|
70
|
-
const registry =
|
|
71
|
-
const
|
|
62
|
+
const registry = readRegisteredProjects(dashboardBrainDir(process.env, globalBrainDir));
|
|
63
|
+
const matching = registry.filter(p => p.name === projectName || p.project_id === projectName);
|
|
64
|
+
const project = matching.length === 1 ? matching[0] : null;
|
|
72
65
|
if (project) {
|
|
73
66
|
const projectVaultDir = path.join(project.brainDir, 'memory-vault');
|
|
74
67
|
if (fs.existsSync(projectVaultDir)) {
|