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,261 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The dashboard bundle a source checkout serves, kept as new as its sources.
|
|
3
|
+
*
|
|
4
|
+
* The server used to build `frontend/dist` only when `index.html` was missing,
|
|
5
|
+
* and auto-pull rebuilt it only when a pull from origin changed `frontend/`.
|
|
6
|
+
* A local merge or commit refreshed nothing, so the served dashboard silently
|
|
7
|
+
* stayed on an old build: the plugin workspace drawers committed on
|
|
8
|
+
* 2026-11-08 never appeared at localhost:3000 because the bundle there was
|
|
9
|
+
* built on 2026-11-09 from code without them, and nothing ever looked again.
|
|
10
|
+
*
|
|
11
|
+
* Here "stale" has one definition, shared with the publish gate
|
|
12
|
+
* (`scripts/check-dist-freshness.mjs`): a source file newer than the newest
|
|
13
|
+
* bundle file. The server checks on start and on an interval, rebuilds in the
|
|
14
|
+
* background with the frontend's own build script, and keeps serving the
|
|
15
|
+
* current bundle until the new one is complete. It never installs
|
|
16
|
+
* dependencies: missing build tools are reported, not repaired behind the
|
|
17
|
+
* operator's back. An npm tarball gives every file the same mtime, so an
|
|
18
|
+
* installed package is never stale and never builds.
|
|
19
|
+
*/
|
|
20
|
+
import { spawn } from 'node:child_process';
|
|
21
|
+
import fs from 'node:fs';
|
|
22
|
+
import { readdir, stat } from 'node:fs/promises';
|
|
23
|
+
import path from 'node:path';
|
|
24
|
+
|
|
25
|
+
const IGNORED_DIRS = new Set(['node_modules', '.git', 'dist']);
|
|
26
|
+
|
|
27
|
+
/** Sources must be untouched this long before a rebuild, so a half-written edit is not built. */
|
|
28
|
+
export const QUIET_MS = 60_000;
|
|
29
|
+
export const CHECK_INTERVAL_MS = 5 * 60_000;
|
|
30
|
+
export const BUILD_TIMEOUT_MS = 10 * 60_000;
|
|
31
|
+
const OUTPUT_TAIL_BYTES = 4096;
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Newest mtime under a file or directory, and the path carrying it.
|
|
35
|
+
* Missing paths contribute nothing rather than failing: vite.config.ts and
|
|
36
|
+
* friends are allowed not to exist.
|
|
37
|
+
*/
|
|
38
|
+
export async function newestUnder(target) {
|
|
39
|
+
let info;
|
|
40
|
+
try {
|
|
41
|
+
info = await stat(target);
|
|
42
|
+
} catch {
|
|
43
|
+
return null;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
if (info.isFile()) return { path: target, mtimeMs: info.mtimeMs };
|
|
47
|
+
|
|
48
|
+
let best = null;
|
|
49
|
+
const entries = await readdir(target, { withFileTypes: true });
|
|
50
|
+
for (const entry of entries) {
|
|
51
|
+
if (entry.isDirectory() && IGNORED_DIRS.has(entry.name)) continue;
|
|
52
|
+
const found = await newestUnder(path.join(target, entry.name));
|
|
53
|
+
if (found && (!best || found.mtimeMs > best.mtimeMs)) best = found;
|
|
54
|
+
}
|
|
55
|
+
return best;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* Whether the built bundle can be trusted to represent the sources.
|
|
60
|
+
* Returns { ok, reason, bundle, source }; never throws, never prints, so the
|
|
61
|
+
* decision can be tested apart from how it is reported.
|
|
62
|
+
*/
|
|
63
|
+
export async function checkDistFreshness({ distDir, sourcePaths }) {
|
|
64
|
+
const bundle = await newestUnder(distDir);
|
|
65
|
+
if (!bundle) return { ok: false, reason: 'missing-bundle', bundle: null, source: null };
|
|
66
|
+
|
|
67
|
+
let source = null;
|
|
68
|
+
for (const candidate of sourcePaths) {
|
|
69
|
+
const found = await newestUnder(candidate);
|
|
70
|
+
if (found && (!source || found.mtimeMs > source.mtimeMs)) source = found;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
if (source && source.mtimeMs > bundle.mtimeMs) {
|
|
74
|
+
return { ok: false, reason: 'stale-bundle', bundle, source };
|
|
75
|
+
}
|
|
76
|
+
return { ok: true, reason: 'current', bundle, source };
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
/** Every input whose change changes the built dashboard. */
|
|
80
|
+
export function dashboardTargets(root) {
|
|
81
|
+
const frontend = path.join(root, 'frontend');
|
|
82
|
+
let tsconfigs = [];
|
|
83
|
+
try {
|
|
84
|
+
tsconfigs = fs.readdirSync(frontend).filter(name => /^tsconfig.*\.json$/.test(name)).map(name => path.join(frontend, name));
|
|
85
|
+
} catch { /* no frontend directory: nothing to compare */ }
|
|
86
|
+
return {
|
|
87
|
+
distDir: path.join(frontend, 'dist'),
|
|
88
|
+
sourcePaths: [
|
|
89
|
+
path.join(frontend, 'src'),
|
|
90
|
+
path.join(frontend, 'public'),
|
|
91
|
+
path.join(frontend, 'index.html'),
|
|
92
|
+
path.join(frontend, 'vite.config.ts'),
|
|
93
|
+
path.join(frontend, 'package.json'),
|
|
94
|
+
...tsconfigs,
|
|
95
|
+
],
|
|
96
|
+
};
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
/** The build script needs vite and the TypeScript compiler, hoisted to the root or local to the frontend. */
|
|
100
|
+
export function buildToolsPresent(root) {
|
|
101
|
+
const frontend = path.join(root, 'frontend');
|
|
102
|
+
const has = rel => [root, frontend].some(dir => fs.existsSync(path.join(dir, 'node_modules', rel)));
|
|
103
|
+
return fs.existsSync(path.join(frontend, 'package.json')) && has(path.join('vite', 'bin', 'vite.js')) && has(path.join('typescript', 'bin', 'tsc'));
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
/** npm installed beside the running node (Homebrew, nvm, system installs), else from PATH. */
|
|
107
|
+
function npmBinary() {
|
|
108
|
+
const beside = path.join(path.dirname(process.execPath), process.platform === 'win32' ? 'npm.cmd' : 'npm');
|
|
109
|
+
return fs.existsSync(beside) ? beside : 'npm';
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
/**
|
|
113
|
+
* Run the frontend's own `build` script into `outDir`. npm appends the
|
|
114
|
+
* arguments after `--` to the script, so they reach `vite build`.
|
|
115
|
+
* @returns {Promise<{ code: number|null, output: string, timedOut: boolean }>}
|
|
116
|
+
*/
|
|
117
|
+
export function runFrontendBuild({ frontendDir, outDir, timeoutMs = BUILD_TIMEOUT_MS }) {
|
|
118
|
+
return new Promise(resolve => {
|
|
119
|
+
let output = '';
|
|
120
|
+
let timedOut = false;
|
|
121
|
+
const keep = chunk => { output = (output + chunk).slice(-OUTPUT_TAIL_BYTES); };
|
|
122
|
+
const env = { ...process.env, PATH: [path.dirname(process.execPath), process.env.PATH].filter(Boolean).join(path.delimiter) };
|
|
123
|
+
let child;
|
|
124
|
+
try {
|
|
125
|
+
child = spawn(npmBinary(), ['run', 'build', '--', '--outDir', outDir, '--emptyOutDir'], {
|
|
126
|
+
cwd: frontendDir, env, stdio: ['ignore', 'pipe', 'pipe'], shell: process.platform === 'win32', detached: process.platform !== 'win32',
|
|
127
|
+
});
|
|
128
|
+
} catch (error) {
|
|
129
|
+
resolve({ code: null, output: error.message, timedOut: false });
|
|
130
|
+
return;
|
|
131
|
+
}
|
|
132
|
+
child.stdout.on('data', keep);
|
|
133
|
+
child.stderr.on('data', keep);
|
|
134
|
+
const timer = setTimeout(() => {
|
|
135
|
+
timedOut = true;
|
|
136
|
+
// The build runs in its own process group: end tsc and vite with npm.
|
|
137
|
+
try { process.kill(-child.pid, 'SIGTERM'); } catch { child.kill('SIGTERM'); }
|
|
138
|
+
}, timeoutMs);
|
|
139
|
+
timer.unref?.();
|
|
140
|
+
child.on('error', error => { clearTimeout(timer); resolve({ code: null, output: `${output}\n${error.message}`.trim(), timedOut }); });
|
|
141
|
+
child.on('close', code => { clearTimeout(timer); resolve({ code, output, timedOut }); });
|
|
142
|
+
});
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
function processAlive(pid) {
|
|
146
|
+
if (!Number.isInteger(pid) || pid <= 0) return false;
|
|
147
|
+
try { process.kill(pid, 0); return true; } catch (error) { return error.code === 'EPERM'; }
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
/**
|
|
151
|
+
* Hold the build lock, or report who holds it. A lock left by a process that
|
|
152
|
+
* no longer exists is reclaimed once.
|
|
153
|
+
*/
|
|
154
|
+
function acquireLock(file) {
|
|
155
|
+
for (let attempt = 0; attempt < 2; attempt++) {
|
|
156
|
+
try {
|
|
157
|
+
const fd = fs.openSync(file, 'wx', 0o600);
|
|
158
|
+
fs.writeSync(fd, `${process.pid}\n`);
|
|
159
|
+
fs.closeSync(fd);
|
|
160
|
+
return { held: true, release: () => { try { if (Number(fs.readFileSync(file, 'utf8').trim()) === process.pid) fs.unlinkSync(file); } catch { /* already gone */ } } };
|
|
161
|
+
} catch (error) {
|
|
162
|
+
if (error.code !== 'EEXIST') throw error;
|
|
163
|
+
const owner = Number(String(fs.readFileSync(file, 'utf8')).trim());
|
|
164
|
+
if (processAlive(owner)) return { held: false, owner };
|
|
165
|
+
fs.rmSync(file, { force: true });
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
return { held: false, owner: null };
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
/**
|
|
172
|
+
* Build the dashboard beside the served one and swap it in. The served bundle
|
|
173
|
+
* stays in place until the new one is complete; a failed build leaves it
|
|
174
|
+
* untouched.
|
|
175
|
+
* @returns {Promise<{ ok: boolean, status: string, reason?: string, durationMs?: number, output?: string }>}
|
|
176
|
+
*/
|
|
177
|
+
export async function rebuildDashboard({ root, run = runFrontendBuild, timeoutMs = BUILD_TIMEOUT_MS } = {}) {
|
|
178
|
+
const frontendDir = path.join(root, 'frontend');
|
|
179
|
+
const dist = path.join(frontendDir, 'dist');
|
|
180
|
+
const next = path.join(frontendDir, 'dist.next');
|
|
181
|
+
const prev = path.join(frontendDir, 'dist.prev');
|
|
182
|
+
const lock = acquireLock(path.join(frontendDir, '.dist-build.lock'));
|
|
183
|
+
if (!lock.held) return { ok: false, status: 'building-elsewhere', reason: `another process (pid ${lock.owner ?? 'unknown'}) is building the dashboard` };
|
|
184
|
+
const started = Date.now();
|
|
185
|
+
try {
|
|
186
|
+
fs.rmSync(next, { recursive: true, force: true });
|
|
187
|
+
const result = await run({ frontendDir, outDir: next, timeoutMs });
|
|
188
|
+
const built = result.code === 0 && !result.timedOut && fs.existsSync(path.join(next, 'index.html'));
|
|
189
|
+
if (!built) {
|
|
190
|
+
fs.rmSync(next, { recursive: true, force: true });
|
|
191
|
+
const reason = result.timedOut ? `build timed out after ${Math.round(timeoutMs / 1000)} s`
|
|
192
|
+
: result.code === 0 ? 'build finished without index.html' : `build exited ${result.code ?? 'without a status'}`;
|
|
193
|
+
return { ok: false, status: 'failed', reason, durationMs: Date.now() - started, output: result.output };
|
|
194
|
+
}
|
|
195
|
+
// Two renames: the served directory is absent only between them.
|
|
196
|
+
fs.rmSync(prev, { recursive: true, force: true });
|
|
197
|
+
const hadDist = fs.existsSync(dist);
|
|
198
|
+
if (hadDist) fs.renameSync(dist, prev);
|
|
199
|
+
try {
|
|
200
|
+
fs.renameSync(next, dist);
|
|
201
|
+
} catch (error) {
|
|
202
|
+
if (hadDist) fs.renameSync(prev, dist);
|
|
203
|
+
throw error;
|
|
204
|
+
}
|
|
205
|
+
fs.rmSync(prev, { recursive: true, force: true });
|
|
206
|
+
return { ok: true, status: 'built', durationMs: Date.now() - started };
|
|
207
|
+
} finally {
|
|
208
|
+
lock.release();
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
/**
|
|
213
|
+
* Check once and rebuild when needed. Returns the state to report and, when
|
|
214
|
+
* the sources are still being edited, how long to wait before checking again.
|
|
215
|
+
*/
|
|
216
|
+
export async function refreshDashboard({ root, now = Date.now, quietMs = QUIET_MS, rebuild = rebuildDashboard, toolsPresent = buildToolsPresent } = {}) {
|
|
217
|
+
const freshness = await checkDistFreshness(dashboardTargets(root));
|
|
218
|
+
if (freshness.ok) return { status: 'current', bundleBuiltAt: new Date(freshness.bundle.mtimeMs).toISOString() };
|
|
219
|
+
const state = freshness.reason === 'missing-bundle' ? 'missing' : 'stale';
|
|
220
|
+
if (!toolsPresent(root)) {
|
|
221
|
+
return { status: state, reason: `the build tools are not installed; run npm ci in ${root}`, source: freshness.source?.path ?? null };
|
|
222
|
+
}
|
|
223
|
+
if (freshness.source && now() - freshness.source.mtimeMs < quietMs) {
|
|
224
|
+
return { status: state, reason: 'sources are still changing', source: freshness.source.path, retryInMs: quietMs - (now() - freshness.source.mtimeMs) + 1000 };
|
|
225
|
+
}
|
|
226
|
+
const build = await rebuild({ root });
|
|
227
|
+
if (build.ok) return { status: 'current', rebuilt: true, durationMs: build.durationMs, was: state };
|
|
228
|
+
return { status: state, reason: build.reason, build };
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
/**
|
|
232
|
+
* Keep a serving checkout's bundle current: check now, after the sources go
|
|
233
|
+
* quiet, and every `intervalMs`. `report` receives each state change.
|
|
234
|
+
* @returns {{ state: () => object, check: () => Promise<object>, stop: () => void }}
|
|
235
|
+
*/
|
|
236
|
+
export function watchDashboardBundle({ root, report = () => {}, intervalMs = CHECK_INTERVAL_MS, refresh = refreshDashboard } = {}) {
|
|
237
|
+
let current = { status: 'unchecked' };
|
|
238
|
+
let running = null;
|
|
239
|
+
let retry = null;
|
|
240
|
+
let lastKey = '';
|
|
241
|
+
const check = () => {
|
|
242
|
+
if (running) return running;
|
|
243
|
+
running = (async () => {
|
|
244
|
+
try {
|
|
245
|
+
current = { ...await refresh({ root }), checkedAt: new Date().toISOString() };
|
|
246
|
+
} catch (error) {
|
|
247
|
+
current = { status: 'error', reason: error.message, checkedAt: new Date().toISOString() };
|
|
248
|
+
}
|
|
249
|
+
const key = `${current.status}|${current.reason ?? ''}|${current.rebuilt ?? ''}`;
|
|
250
|
+
if (key !== lastKey || current.rebuilt) report(current);
|
|
251
|
+
lastKey = key;
|
|
252
|
+
clearTimeout(retry);
|
|
253
|
+
if (current.retryInMs) { retry = setTimeout(check, current.retryInMs); retry.unref?.(); }
|
|
254
|
+
return current;
|
|
255
|
+
})().finally(() => { running = null; });
|
|
256
|
+
return running;
|
|
257
|
+
};
|
|
258
|
+
const interval = setInterval(check, intervalMs);
|
|
259
|
+
interval.unref?.();
|
|
260
|
+
return { state: () => current, check, stop: () => { clearInterval(interval); clearTimeout(retry); } };
|
|
261
|
+
}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A release that changes the plugin contract refuses the shared default plugins the previous release
|
|
3
|
+
* installed, and with them every command that loads one, `update` included: the very command that would
|
|
4
|
+
* replace them. So a machine moving to such a release could not update itself. When a command fails that
|
|
5
|
+
* way, refresh the defaults from this package's bundled copies (a link is its owner's checkout and is left
|
|
6
|
+
* alone) and run the command again, once.
|
|
7
|
+
*/
|
|
8
|
+
import { spawnSync } from 'node:child_process';
|
|
9
|
+
import path from 'node:path';
|
|
10
|
+
import { ensureRequiredPlugins } from './required-plugins.mjs';
|
|
11
|
+
import { globalPluginsDir } from './plugin-loader.mjs';
|
|
12
|
+
|
|
13
|
+
const REFUSAL = /\bRequired (?:plugin [\w-]+|[\w-]+ plugin) is missing or invalid\b/;
|
|
14
|
+
export const REFRESHED_ENV = 'TR_DEFAULT_PLUGINS_REFRESHED';
|
|
15
|
+
|
|
16
|
+
export function isDefaultPluginRefusal(error) {
|
|
17
|
+
return REFUSAL.test(String(error?.message || ''));
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
/** Returns the rerun's exit status, or null when this error is not one a refresh can fix. */
|
|
21
|
+
export function rerunAfterDefaultRefresh(error, {
|
|
22
|
+
env = process.env, argv = process.argv, execPath = process.execPath, ensure = ensureRequiredPlugins,
|
|
23
|
+
sharedBrainDir = path.dirname(globalPluginsDir()), run = spawnSync, log = (line) => console.error(line),
|
|
24
|
+
} = {}) {
|
|
25
|
+
if (!isDefaultPluginRefusal(error) || env[REFRESHED_ENV] === '1') return null;
|
|
26
|
+
try {
|
|
27
|
+
ensure(sharedBrainDir, { sharedBrainDir });
|
|
28
|
+
} catch (refreshError) {
|
|
29
|
+
log(`The default plugins could not be refreshed from this release: ${refreshError.message}`);
|
|
30
|
+
return null;
|
|
31
|
+
}
|
|
32
|
+
log('Refreshed the default plugins from this release; running the command again.');
|
|
33
|
+
const result = run(execPath, argv.slice(1), { stdio: 'inherit', env: { ...env, [REFRESHED_ENV]: '1' } });
|
|
34
|
+
if (result.error) {
|
|
35
|
+
log(`The command could not be run again: ${result.error.message}`);
|
|
36
|
+
return 1;
|
|
37
|
+
}
|
|
38
|
+
return result.status ?? 1;
|
|
39
|
+
}
|
|
@@ -22,25 +22,29 @@ const RUNNING_PACKAGE_ROOT = (() => {
|
|
|
22
22
|
})();
|
|
23
23
|
|
|
24
24
|
/**
|
|
25
|
-
*
|
|
26
|
-
*
|
|
27
|
-
* is
|
|
25
|
+
* The tree matches the lockfile when every locked package is on disk at its locked version
|
|
26
|
+
* (optional packages may be absent on this platform; workspace links are not copies; an absent
|
|
27
|
+
* peer is npm's call). It reads each package's own package.json, never npm's record of the tree
|
|
28
|
+
* (node_modules/.package-lock.json): `npm install --package-lock-only`, which the brain updater
|
|
29
|
+
* runs, rewrites that record to the new versions without installing them. On 2026-11-12 21 linked
|
|
30
|
+
* plugins held @ssss/cli 0.14.4 under a record and lock that both said 0.14.5, so every check that
|
|
31
|
+
* trusted the record, and npm itself, reported the stale tree as current.
|
|
28
32
|
*/
|
|
29
33
|
export function npmTreeMatchesLock(root) {
|
|
30
|
-
let lock
|
|
31
|
-
try {
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
} catch { return false; }
|
|
35
|
-
if (!lock || !installed) return false;
|
|
36
|
-
for (const [key, entry] of Object.entries(installed)) {
|
|
37
|
-
if (!key || entry.link) continue;
|
|
38
|
-
if (!lock[key] || lock[key].version !== entry.version) return false;
|
|
39
|
-
}
|
|
34
|
+
let lock;
|
|
35
|
+
try { lock = JSON.parse(fs.readFileSync(path.join(root, 'package-lock.json'), 'utf8')).packages; }
|
|
36
|
+
catch { return false; }
|
|
37
|
+
if (!lock || !fs.existsSync(path.join(root, 'node_modules'))) return false;
|
|
40
38
|
for (const [key, entry] of Object.entries(lock)) {
|
|
41
39
|
if (!key.startsWith('node_modules/') && !key.includes('/node_modules/')) continue;
|
|
42
|
-
if (entry.optional || entry.link
|
|
43
|
-
|
|
40
|
+
if (entry.optional || entry.link) continue;
|
|
41
|
+
let version;
|
|
42
|
+
try { version = JSON.parse(fs.readFileSync(path.join(root, key, 'package.json'), 'utf8')).version; }
|
|
43
|
+
catch {
|
|
44
|
+
if (entry.peer) continue;
|
|
45
|
+
return false;
|
|
46
|
+
}
|
|
47
|
+
if (entry.version && version !== entry.version) return false;
|
|
44
48
|
}
|
|
45
49
|
return true;
|
|
46
50
|
}
|
|
@@ -5,6 +5,7 @@ import { writeFileSecure } from './secure-file.mjs';
|
|
|
5
5
|
import { ensureProjectId } from './project-id.mjs';
|
|
6
6
|
|
|
7
7
|
function ensureDir(dir) { fs.mkdirSync(dir, { recursive: true }); }
|
|
8
|
+
const UUID = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/;
|
|
8
9
|
|
|
9
10
|
export const VAULT_CATEGORIES = Object.freeze([
|
|
10
11
|
'invariants',
|
|
@@ -18,17 +19,33 @@ export const VAULT_CATEGORIES = Object.freeze([
|
|
|
18
19
|
'lore',
|
|
19
20
|
]);
|
|
20
21
|
|
|
22
|
+
function readBrainConfig(file) {
|
|
23
|
+
let current;
|
|
24
|
+
try { current = JSON.parse(fs.readFileSync(file, 'utf8')); }
|
|
25
|
+
catch (error) {
|
|
26
|
+
if (error.code === 'ENOENT') return {};
|
|
27
|
+
throw error;
|
|
28
|
+
}
|
|
29
|
+
if (!current || typeof current !== 'object' || Array.isArray(current)) {
|
|
30
|
+
throw new Error('Existing brain identity must be an object');
|
|
31
|
+
}
|
|
32
|
+
return current;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/** Update connection settings without replacing the existing brain identity. */
|
|
36
|
+
export function writeBrainConnection(brainDir, fields) {
|
|
37
|
+
const file = path.join(brainDir, 'config', 'brain.json');
|
|
38
|
+
const current = readBrainConfig(file);
|
|
39
|
+
ensureDir(path.dirname(file));
|
|
40
|
+
writeFileSecure(file, JSON.stringify({ ...current, ...fields }, null, 2), { encoding: 'utf8', mode: 0o600 });
|
|
41
|
+
return file;
|
|
42
|
+
}
|
|
43
|
+
|
|
21
44
|
export function writeBrainIdentity(brainDir, { name, role = 'project', tags = [] } = {}) {
|
|
22
45
|
const configDir = path.join(brainDir, 'config');
|
|
23
46
|
ensureDir(configDir);
|
|
24
47
|
const brainJsonPath = path.join(configDir, 'brain.json');
|
|
25
|
-
|
|
26
|
-
if (fs.existsSync(brainJsonPath)) {
|
|
27
|
-
current = JSON.parse(fs.readFileSync(brainJsonPath, 'utf8'));
|
|
28
|
-
if (!current || typeof current !== 'object' || Array.isArray(current)) {
|
|
29
|
-
throw new Error('Existing brain identity must be an object');
|
|
30
|
-
}
|
|
31
|
-
}
|
|
48
|
+
const current = readBrainConfig(brainJsonPath);
|
|
32
49
|
const next = {
|
|
33
50
|
...current,
|
|
34
51
|
name: name || current.name || path.basename(path.resolve(brainDir, '../../..')),
|
|
@@ -54,12 +71,17 @@ export function registerProjectBrain(globalBrainDir, entry) {
|
|
|
54
71
|
|
|
55
72
|
const abs = path.resolve(entry.path);
|
|
56
73
|
const idx = list.findIndex((p) => path.resolve(p.path || '') === abs);
|
|
74
|
+
const existing = list[idx];
|
|
75
|
+
const candidateId = existing?.project_id;
|
|
76
|
+
const fallbackId = typeof candidateId === 'string' && UUID.test(candidateId) &&
|
|
77
|
+
existing.brainDir === entry.brainDir && list.filter(record => record.project_id === candidateId).length === 1
|
|
78
|
+
? candidateId : undefined;
|
|
57
79
|
const now = new Date().toISOString();
|
|
58
80
|
const row = {
|
|
59
81
|
name: entry.name,
|
|
60
82
|
path: abs,
|
|
61
83
|
brainDir: entry.brainDir,
|
|
62
|
-
project_id: ensureProjectId(entry.brainDir).project_id,
|
|
84
|
+
project_id: ensureProjectId(entry.brainDir, { fallbackId }).project_id,
|
|
63
85
|
full_brain: true,
|
|
64
86
|
layer: 'project',
|
|
65
87
|
tags: entry.tags || ['project-brain'],
|
|
@@ -1,7 +1,10 @@
|
|
|
1
1
|
/** Host activation policy; never pretends a provider or external daemon is healthy. */
|
|
2
|
-
import
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
import { getPlugin, globalPluginsDir, projectPluginsDir, vaultForPluginsDir } from './plugin-loader.mjs';
|
|
4
|
+
import { patchPluginRecord, patchProjectActivationRecord, recordPath, emit, vaultFor, recompileAfterPluginMutation } from './plugin-store.mjs';
|
|
5
|
+
import { patchVfsDocument } from './ssss-operation-service.mjs';
|
|
6
|
+
import { pluginActivation, readActivationRecord, usesSharedPluginPolicy } from './plugin-state.mjs';
|
|
7
|
+
import { moveRepositoryPluginState, withRepositoryPluginLifecycle } from './plugin-lifecycle.mjs';
|
|
5
8
|
import { REQUIRED_INSTALLATION_PLUGIN_IDS } from './default-plugins.mjs';
|
|
6
9
|
export { pluginActivation };
|
|
7
10
|
|
|
@@ -14,38 +17,78 @@ export { pluginActivation };
|
|
|
14
17
|
* following the shared setting (`clear: true`).
|
|
15
18
|
*/
|
|
16
19
|
export async function setPluginActive(id, enabled, { projectRoot = process.cwd(), scope = 'project', clear = false } = {}) {
|
|
17
|
-
|
|
20
|
+
const onUse = enabled === 'on-use';
|
|
21
|
+
if (onUse && (scope !== 'project' || !projectRoot || projectPluginsDir(projectRoot) === globalPluginsDir() || clear)) {
|
|
22
|
+
throw new Error('On use is available for individual repository brains.');
|
|
23
|
+
}
|
|
24
|
+
if ((enabled === false || onUse) && REQUIRED_INSTALLATION_PLUGIN_IDS.includes(id)) {
|
|
18
25
|
throw new Error('Required plugin ' + id + ' cannot be disabled');
|
|
19
26
|
}
|
|
20
27
|
if (clear === true) {
|
|
21
28
|
const plugin = getPlugin(id, projectRoot);
|
|
22
29
|
if (!plugin?.valid) throw new Error(`Plugin ${id} is missing or invalid`);
|
|
23
|
-
await
|
|
24
|
-
|
|
30
|
+
await withRepositoryPluginLifecycle(projectRoot, id, async brain => {
|
|
31
|
+
if (!usesSharedPluginPolicy(plugin) || pluginActivation(plugin, { projectRoot: null }).enabled) {
|
|
32
|
+
moveRepositoryPluginState(brain, id, 'restore');
|
|
33
|
+
}
|
|
34
|
+
await patchProjectActivationRecord(id, projectRoot, null);
|
|
35
|
+
await emit(vaultForPluginsDir(projectPluginsDir(projectRoot)), id, 'activation-cleared', {});
|
|
36
|
+
});
|
|
25
37
|
const compilation = await recompileAfterPluginMutation(projectRoot, false);
|
|
26
38
|
return { pluginId: id, activation: pluginActivation(plugin, { projectRoot }), scope: 'shared', compilation };
|
|
27
39
|
}
|
|
28
|
-
if (
|
|
40
|
+
if (onUse) enabled = false;
|
|
41
|
+
if (typeof enabled !== 'boolean') throw new Error('Plugin activation requires a boolean or On use');
|
|
29
42
|
const plugin = getPlugin(id, projectRoot);
|
|
30
43
|
if (!plugin?.valid) throw new Error(`Plugin ${id} is missing or invalid`);
|
|
31
44
|
if (scope === 'shared') {
|
|
32
|
-
if (plugin
|
|
33
|
-
await patchPluginRecord(plugin, { runtime_enabled: enabled });
|
|
45
|
+
if (!usesSharedPluginPolicy(plugin)) throw new Error(`Plugin ${id} is a ${plugin.scope} copy without a shared setting to change`);
|
|
46
|
+
await patchPluginRecord(plugin, { runtime_enabled: enabled, runtime_on_use: false });
|
|
34
47
|
await emit(vaultFor(plugin), id, enabled ? 'activated' : 'deactivated', { enabled, scope: 'shared' });
|
|
35
48
|
const compilation = await recompileAfterPluginMutation(projectRoot, true);
|
|
36
49
|
return { pluginId: id, activation: pluginActivation(plugin, { projectRoot }), scope: 'shared', compilation };
|
|
37
50
|
}
|
|
38
51
|
// Repository scope. A repository-owned copy already is that repository's setting.
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
52
|
+
const event = onUse ? 'activation-on-use' : enabled ? 'activated' : 'deactivated';
|
|
53
|
+
const shared = usesSharedPluginPolicy(plugin);
|
|
54
|
+
const resultScope = shared ? 'project' : plugin.scope;
|
|
55
|
+
const vaultRoot = shared ? vaultForPluginsDir(projectPluginsDir(projectRoot)) : vaultFor(plugin);
|
|
56
|
+
const cleanup = await withRepositoryPluginLifecycle(projectRoot, id, async brain => {
|
|
57
|
+
if (onUse && !brain) throw new Error('On use is available for individual repository brains.');
|
|
58
|
+
let result = enabled ? moveRepositoryPluginState(brain, id, 'restore') : undefined;
|
|
59
|
+
if (shared) await patchProjectActivationRecord(id, projectRoot, enabled, { manifest: plugin.manifest, onUse });
|
|
60
|
+
else await patchPluginRecord(plugin, { runtime_enabled: enabled, runtime_on_use: onUse });
|
|
61
|
+
if (!enabled && !onUse) {
|
|
62
|
+
try { result = moveRepositoryPluginState(brain, id, 'archive'); }
|
|
63
|
+
catch (error) {
|
|
64
|
+
result = { ok: false, state: 'pending', error: error.code ? `Could not archive repository plugin state (${error.code}).` : error.message };
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
await emit(vaultRoot, id, event, { enabled, scope: resultScope, ...(result ? { cleanup: result.state } : {}) });
|
|
68
|
+
return result;
|
|
69
|
+
});
|
|
70
|
+
const compilation = await recompileAfterPluginMutation(projectRoot, false);
|
|
71
|
+
return { pluginId: id, activation: pluginActivation(plugin, { projectRoot }), scope: resultScope, compilation, ...(cleanup ? { cleanup } : {}) };
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
/** Claim one armed project policy through the SSSS compare-and-set writer. */
|
|
75
|
+
export async function activatePluginOnUse(id, { projectRoot = process.cwd() } = {}) {
|
|
76
|
+
if (!projectRoot || projectPluginsDir(projectRoot) === globalPluginsDir()) return null;
|
|
77
|
+
const plugin = getPlugin(id, projectRoot);
|
|
78
|
+
if (!plugin?.valid || pluginActivation(plugin, { projectRoot }).mode !== 'on-use') return null;
|
|
79
|
+
const vaultRoot = usesSharedPluginPolicy(plugin) ? vaultForPluginsDir(projectPluginsDir(projectRoot)) : vaultFor(plugin);
|
|
80
|
+
const cleanup = await withRepositoryPluginLifecycle(projectRoot, id, async brain => {
|
|
81
|
+
if (!brain) return null;
|
|
82
|
+
const current = readActivationRecord(path.join(vaultRoot, recordPath(id)), id, { withHash: true });
|
|
83
|
+
if (current?.record.runtime_on_use !== true) return null;
|
|
84
|
+
const result = moveRepositoryPluginState(brain, id, 'restore');
|
|
85
|
+
await patchVfsDocument(recordPath(id), { runtime_enabled: true, runtime_on_use: false, timestamp: new Date().toISOString() }, {
|
|
86
|
+
vaultRoot, expectedBeforeHash: current.hash, intent: 'Activate plugin on deliberate use',
|
|
87
|
+
});
|
|
88
|
+
await emit(vaultRoot, id, 'activated-on-use', { enabled: true, scope: 'project', cleanup: result.state });
|
|
89
|
+
return result;
|
|
90
|
+
});
|
|
91
|
+
if (!cleanup) return null;
|
|
49
92
|
const compilation = await recompileAfterPluginMutation(projectRoot, false);
|
|
50
|
-
return { pluginId: id, activation: pluginActivation(plugin, { projectRoot }), scope: 'project', compilation };
|
|
93
|
+
return { pluginId: id, activation: pluginActivation(plugin, { projectRoot }), scope: 'project', compilation, cleanup };
|
|
51
94
|
}
|