@hcu-lab.me/mcp-device 1.0.9 → 1.0.12
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/PRIVACY.md +21 -0
- package/README.md +58 -3
- package/SECURITY.md +67 -0
- package/config/revoked-approvals.json +1 -0
- package/control-plane/config/mcp-servers.example.toml +52 -0
- package/control-plane/config/mcp-servers.toml +52 -0
- package/control-plane/contracts/device-public-contract-v1.json +733 -0
- package/control-plane/scripts/GATEWAY-LICENSE.txt +7 -0
- package/control-plane/scripts/account-http.mjs +220 -0
- package/control-plane/scripts/account-store.mjs +368 -0
- package/control-plane/scripts/admin.mjs +68 -0
- package/control-plane/scripts/auth-session.mjs +411 -0
- package/control-plane/scripts/authenticated-mcp-wrapper.mjs +1281 -0
- package/control-plane/scripts/benchmark-command.mjs +9 -0
- package/control-plane/scripts/benchmark-mcp-catalog.mjs +174 -0
- package/control-plane/scripts/catalog-budget.mjs +87 -0
- package/control-plane/scripts/check-device-public-contract.mjs +20 -0
- package/control-plane/scripts/custom-tools/index.mjs +221 -0
- package/control-plane/scripts/custom-tools/path-utils.mjs +117 -0
- package/control-plane/scripts/custom-tools/response-utils.mjs +34 -0
- package/control-plane/scripts/dashboard-http.mjs +598 -0
- package/control-plane/scripts/device-access-policy.mjs +205 -0
- package/control-plane/scripts/device-admin.mjs +43 -0
- package/control-plane/scripts/device-audit.mjs +63 -0
- package/control-plane/scripts/device-broker.mjs +1461 -0
- package/control-plane/scripts/device-execution-tool-definitions.mjs +217 -0
- package/control-plane/scripts/device-inventory.mjs +128 -0
- package/control-plane/scripts/device-pairing-http.mjs +109 -0
- package/control-plane/scripts/device-pairing-store.mjs +245 -0
- package/control-plane/scripts/device-public-contract.mjs +165 -0
- package/control-plane/scripts/device-release.mjs +95 -0
- package/control-plane/scripts/device-secure-transport.mjs +128 -0
- package/control-plane/scripts/device-store.mjs +283 -0
- package/control-plane/scripts/device-usage.mjs +657 -0
- package/control-plane/scripts/embedded-device.mjs +149 -0
- package/control-plane/scripts/enduser-ui.mjs +45 -0
- package/control-plane/scripts/external-tool-broker.mjs +179 -0
- package/control-plane/scripts/oauth-authorization-server.mjs +589 -0
- package/control-plane/scripts/operator-dashboard-http.mjs +81 -0
- package/control-plane/scripts/project-inspection.mjs +227 -0
- package/control-plane/scripts/projects/project-id.mjs +69 -0
- package/control-plane/scripts/projects/trusted-roots-projects.mjs +375 -0
- package/control-plane/scripts/prompts/index.mjs +60 -0
- package/control-plane/scripts/remote-process-sessions.mjs +78 -0
- package/control-plane/scripts/remote-tool-result.mjs +9 -0
- package/control-plane/scripts/resources/index.mjs +166 -0
- package/control-plane/scripts/runtime-profile.mjs +54 -0
- package/control-plane/scripts/shell-tool-descriptor.mjs +17 -0
- package/control-plane/scripts/skill-source-safety.mjs +46 -0
- package/control-plane/scripts/skills/README.md +70 -0
- package/control-plane/scripts/skills/_upstream_licenses/anthropic-THIRD_PARTY_NOTICES.md +405 -0
- package/control-plane/scripts/skills/_upstream_licenses/hallmark-LICENSE.txt +21 -0
- package/control-plane/scripts/skills/_upstream_licenses/ponytail-LICENSE.txt +21 -0
- package/control-plane/scripts/skills/_upstream_licenses/stitch-skills-LICENSE.txt +201 -0
- package/control-plane/scripts/skills/_upstream_licenses/superpowers-LICENSE.txt +21 -0
- package/control-plane/scripts/skills/algorithmic-art/LICENSE.txt +202 -0
- package/control-plane/scripts/skills/algorithmic-art/SKILL.md +405 -0
- package/control-plane/scripts/skills/algorithmic-art/templates/generator_template.js +223 -0
- package/control-plane/scripts/skills/algorithmic-art/templates/viewer.html +599 -0
- package/control-plane/scripts/skills/brainstorming/SKILL.md +285 -0
- package/control-plane/scripts/skills/brainstorming/scripts/frame-template.html +213 -0
- package/control-plane/scripts/skills/brainstorming/scripts/helper.js +167 -0
- package/control-plane/scripts/skills/brainstorming/scripts/server.cjs +723 -0
- package/control-plane/scripts/skills/brainstorming/scripts/start-server.sh +209 -0
- package/control-plane/scripts/skills/brainstorming/scripts/stop-server.sh +120 -0
- package/control-plane/scripts/skills/brainstorming/spec-document-reviewer-prompt.md +49 -0
- package/control-plane/scripts/skills/brainstorming/visual-companion.md +299 -0
- package/control-plane/scripts/skills/brand-guidelines/LICENSE.txt +202 -0
- package/control-plane/scripts/skills/brand-guidelines/SKILL.md +73 -0
- package/control-plane/scripts/skills/claude-api/LICENSE.txt +202 -0
- package/control-plane/scripts/skills/claude-api/SKILL.md +570 -0
- package/control-plane/scripts/skills/claude-api/csharp/claude-api/README.md +362 -0
- package/control-plane/scripts/skills/claude-api/csharp/claude-api/batches.md +14 -0
- package/control-plane/scripts/skills/claude-api/csharp/claude-api/files-api.md +25 -0
- package/control-plane/scripts/skills/claude-api/csharp/claude-api/streaming.md +28 -0
- package/control-plane/scripts/skills/claude-api/csharp/claude-api/tool-use.md +164 -0
- package/control-plane/scripts/skills/claude-api/curl/examples.md +256 -0
- package/control-plane/scripts/skills/claude-api/curl/managed-agents.md +371 -0
- package/control-plane/scripts/skills/claude-api/go/claude-api/README.md +188 -0
- package/control-plane/scripts/skills/claude-api/go/claude-api/files-api.md +23 -0
- package/control-plane/scripts/skills/claude-api/go/claude-api/streaming.md +43 -0
- package/control-plane/scripts/skills/claude-api/go/claude-api/tool-use.md +220 -0
- package/control-plane/scripts/skills/claude-api/go/managed-agents/README.md +564 -0
- package/control-plane/scripts/skills/claude-api/java/claude-api/README.md +238 -0
- package/control-plane/scripts/skills/claude-api/java/claude-api/files-api.md +27 -0
- package/control-plane/scripts/skills/claude-api/java/claude-api/streaming.md +24 -0
- package/control-plane/scripts/skills/claude-api/java/claude-api/tool-use.md +228 -0
- package/control-plane/scripts/skills/claude-api/java/managed-agents/README.md +443 -0
- package/control-plane/scripts/skills/claude-api/php/claude-api/README.md +174 -0
- package/control-plane/scripts/skills/claude-api/php/claude-api/batches.md +15 -0
- package/control-plane/scripts/skills/claude-api/php/claude-api/files-api.md +13 -0
- package/control-plane/scripts/skills/claude-api/php/claude-api/streaming.md +27 -0
- package/control-plane/scripts/skills/claude-api/php/claude-api/tool-use.md +253 -0
- package/control-plane/scripts/skills/claude-api/php/managed-agents/README.md +443 -0
- package/control-plane/scripts/skills/claude-api/python/claude-api/README.md +565 -0
- package/control-plane/scripts/skills/claude-api/python/claude-api/batches.md +198 -0
- package/control-plane/scripts/skills/claude-api/python/claude-api/files-api.md +170 -0
- package/control-plane/scripts/skills/claude-api/python/claude-api/sdk-upgrade.md +286 -0
- package/control-plane/scripts/skills/claude-api/python/claude-api/streaming.md +179 -0
- package/control-plane/scripts/skills/claude-api/python/claude-api/tool-use.md +627 -0
- package/control-plane/scripts/skills/claude-api/python/managed-agents/README.md +335 -0
- package/control-plane/scripts/skills/claude-api/ruby/claude-api/README.md +134 -0
- package/control-plane/scripts/skills/claude-api/ruby/claude-api/streaming.md +16 -0
- package/control-plane/scripts/skills/claude-api/ruby/claude-api/tool-use.md +41 -0
- package/control-plane/scripts/skills/claude-api/ruby/managed-agents/README.md +394 -0
- package/control-plane/scripts/skills/claude-api/shared/admin-api.md +179 -0
- package/control-plane/scripts/skills/claude-api/shared/agent-design.md +101 -0
- package/control-plane/scripts/skills/claude-api/shared/anthropic-cli.md +269 -0
- package/control-plane/scripts/skills/claude-api/shared/claude-platform-on-aws.md +59 -0
- package/control-plane/scripts/skills/claude-api/shared/cost-optimization.md +233 -0
- package/control-plane/scripts/skills/claude-api/shared/error-codes.md +262 -0
- package/control-plane/scripts/skills/claude-api/shared/live-sources.md +166 -0
- package/control-plane/scripts/skills/claude-api/shared/managed-agents-api-reference.md +455 -0
- package/control-plane/scripts/skills/claude-api/shared/managed-agents-client-patterns.md +220 -0
- package/control-plane/scripts/skills/claude-api/shared/managed-agents-core.md +359 -0
- package/control-plane/scripts/skills/claude-api/shared/managed-agents-environments.md +226 -0
- package/control-plane/scripts/skills/claude-api/shared/managed-agents-events.md +290 -0
- package/control-plane/scripts/skills/claude-api/shared/managed-agents-memory.md +201 -0
- package/control-plane/scripts/skills/claude-api/shared/managed-agents-multiagent.md +260 -0
- package/control-plane/scripts/skills/claude-api/shared/managed-agents-onboarding.md +82 -0
- package/control-plane/scripts/skills/claude-api/shared/managed-agents-outcomes.md +108 -0
- package/control-plane/scripts/skills/claude-api/shared/managed-agents-overview.md +77 -0
- package/control-plane/scripts/skills/claude-api/shared/managed-agents-scheduled-deployments.md +156 -0
- package/control-plane/scripts/skills/claude-api/shared/managed-agents-self-hosted-sandboxes.md +282 -0
- package/control-plane/scripts/skills/claude-api/shared/managed-agents-tools.md +504 -0
- package/control-plane/scripts/skills/claude-api/shared/managed-agents-webhooks.md +143 -0
- package/control-plane/scripts/skills/claude-api/shared/model-migration.md +1872 -0
- package/control-plane/scripts/skills/claude-api/shared/models.md +142 -0
- package/control-plane/scripts/skills/claude-api/shared/platform-availability.md +52 -0
- package/control-plane/scripts/skills/claude-api/shared/prompt-audit.md +223 -0
- package/control-plane/scripts/skills/claude-api/shared/prompt-caching.md +292 -0
- package/control-plane/scripts/skills/claude-api/shared/token-counting.md +56 -0
- package/control-plane/scripts/skills/claude-api/shared/tool-use-concepts.md +526 -0
- package/control-plane/scripts/skills/claude-api/typescript/claude-api/README.md +405 -0
- package/control-plane/scripts/skills/claude-api/typescript/claude-api/batches.md +106 -0
- package/control-plane/scripts/skills/claude-api/typescript/claude-api/files-api.md +98 -0
- package/control-plane/scripts/skills/claude-api/typescript/claude-api/streaming.md +178 -0
- package/control-plane/scripts/skills/claude-api/typescript/claude-api/tool-use.md +586 -0
- package/control-plane/scripts/skills/claude-api/typescript/managed-agents/README.md +359 -0
- package/control-plane/scripts/skills/dispatching-parallel-agents/SKILL.md +167 -0
- package/control-plane/scripts/skills/enhance-prompt/README.md +34 -0
- package/control-plane/scripts/skills/enhance-prompt/SKILL.md +204 -0
- package/control-plane/scripts/skills/enhance-prompt/references/KEYWORDS.md +114 -0
- package/control-plane/scripts/skills/executing-plans/SKILL.md +371 -0
- package/control-plane/scripts/skills/executing-plans/scripts/task-done +52 -0
- package/control-plane/scripts/skills/executing-plans/scripts/task-start +28 -0
- package/control-plane/scripts/skills/extract-design-md/SKILL.md +358 -0
- package/control-plane/scripts/skills/extract-design-md/examples/DESIGN.md +210 -0
- package/control-plane/scripts/skills/extract-design-md/references/angular.md +122 -0
- package/control-plane/scripts/skills/extract-design-md/references/plain-css.md +161 -0
- package/control-plane/scripts/skills/extract-design-md/references/react-tailwind.md +157 -0
- package/control-plane/scripts/skills/extract-design-md/references/svelte.md +101 -0
- package/control-plane/scripts/skills/extract-design-md/references/vue.md +107 -0
- package/control-plane/scripts/skills/finishing-a-development-branch/SKILL.md +225 -0
- package/control-plane/scripts/skills/frontend-design/LICENSE.txt +177 -0
- package/control-plane/scripts/skills/frontend-design/SKILL.md +71 -0
- package/control-plane/scripts/skills/hallmark/SKILL.md +558 -0
- package/control-plane/scripts/skills/hallmark/references/anti-patterns.md +418 -0
- package/control-plane/scripts/skills/hallmark/references/assets.md +406 -0
- package/control-plane/scripts/skills/hallmark/references/color.md +95 -0
- package/control-plane/scripts/skills/hallmark/references/component-cookbook.md +263 -0
- package/control-plane/scripts/skills/hallmark/references/components/c1-outlined-chip.md +12 -0
- package/control-plane/scripts/skills/hallmark/references/components/c2-inline-form-as-cta.md +16 -0
- package/control-plane/scripts/skills/hallmark/references/components/c3-typographic-link.md +8 -0
- package/control-plane/scripts/skills/hallmark/references/components/c4-sticky-bottom-bar.md +16 -0
- package/control-plane/scripts/skills/hallmark/references/components/f1-bento-grid.md +20 -0
- package/control-plane/scripts/skills/hallmark/references/components/f2-sticky-scroll-stack.md +20 -0
- package/control-plane/scripts/skills/hallmark/references/components/f3-tabular-spec-sheet.md +11 -0
- package/control-plane/scripts/skills/hallmark/references/components/f4-step-sequence.md +11 -0
- package/control-plane/scripts/skills/hallmark/references/components/f5-annotated-screenshot.md +11 -0
- package/control-plane/scripts/skills/hallmark/references/components/f6-product-card-grid.md +41 -0
- package/control-plane/scripts/skills/hallmark/references/components/ft1-mast-headed.md +13 -0
- package/control-plane/scripts/skills/hallmark/references/components/ft2-inline-rule-single-line.md +10 -0
- package/control-plane/scripts/skills/hallmark/references/components/ft3-index-style-category-list.md +12 -0
- package/control-plane/scripts/skills/hallmark/references/components/ft4-dense-typographic.md +10 -0
- package/control-plane/scripts/skills/hallmark/references/components/ft5-statement.md +21 -0
- package/control-plane/scripts/skills/hallmark/references/components/ft6-letter-close.md +19 -0
- package/control-plane/scripts/skills/hallmark/references/components/ft7-newsletter-first.md +27 -0
- package/control-plane/scripts/skills/hallmark/references/components/ft8-marquee-scroll.md +25 -0
- package/control-plane/scripts/skills/hallmark/references/components/h1-marquee.md +15 -0
- package/control-plane/scripts/skills/hallmark/references/components/h2-split-diptych.md +15 -0
- package/control-plane/scripts/skills/hallmark/references/components/h3-quote-led.md +11 -0
- package/control-plane/scripts/skills/hallmark/references/components/h4-stat-led.md +14 -0
- package/control-plane/scripts/skills/hallmark/references/components/h5-letter-hero.md +11 -0
- package/control-plane/scripts/skills/hallmark/references/components/h6-photographic-fold.md +16 -0
- package/control-plane/scripts/skills/hallmark/references/components/h7-demo-video-clipped-by-viewport-edge.md +27 -0
- package/control-plane/scripts/skills/hallmark/references/components/h8-mockup-split-browser-framed.md +23 -0
- package/control-plane/scripts/skills/hallmark/references/components/h9-custom-illustration-centerpiece.md +27 -0
- package/control-plane/scripts/skills/hallmark/references/components/n1-wordmark-2-links.md +12 -0
- package/control-plane/scripts/skills/hallmark/references/components/n10-floating-on-scroll-morph.md +19 -0
- package/control-plane/scripts/skills/hallmark/references/components/n11-mega-menu.md +40 -0
- package/control-plane/scripts/skills/hallmark/references/components/n12-banner-retract.md +34 -0
- package/control-plane/scripts/skills/hallmark/references/components/n13-inline-cmdk-pill.md +39 -0
- package/control-plane/scripts/skills/hallmark/references/components/n1b-saas-three-section.md +35 -0
- package/control-plane/scripts/skills/hallmark/references/components/n2-floating-chip.md +14 -0
- package/control-plane/scripts/skills/hallmark/references/components/n3-side-rail.md +14 -0
- package/control-plane/scripts/skills/hallmark/references/components/n4-hidden-behind-k.md +9 -0
- package/control-plane/scripts/skills/hallmark/references/components/n5-floating-pill.md +28 -0
- package/control-plane/scripts/skills/hallmark/references/components/n6-newspaper-masthead.md +24 -0
- package/control-plane/scripts/skills/hallmark/references/components/n7-brutal-slab.md +22 -0
- package/control-plane/scripts/skills/hallmark/references/components/n8-terminal-command.md +21 -0
- package/control-plane/scripts/skills/hallmark/references/components/n9-edge-aligned-minimal.md +17 -0
- package/control-plane/scripts/skills/hallmark/references/components/s1-left-margin-numbered.md +15 -0
- package/control-plane/scripts/skills/hallmark/references/components/s2-hanging.md +13 -0
- package/control-plane/scripts/skills/hallmark/references/components/s3-sticky-pinned.md +19 -0
- package/control-plane/scripts/skills/hallmark/references/components/s4-inline-no-break.md +11 -0
- package/control-plane/scripts/skills/hallmark/references/components/s5-bottom-anchored.md +13 -0
- package/control-plane/scripts/skills/hallmark/references/components/t1-pull-quote-with-marginalia.md +12 -0
- package/control-plane/scripts/skills/hallmark/references/components/t2-logo-wall-hairline.md +19 -0
- package/control-plane/scripts/skills/hallmark/references/components/t3-single-huge-quote.md +11 -0
- package/control-plane/scripts/skills/hallmark/references/components/t4-numbered-stat-strip.md +14 -0
- package/control-plane/scripts/skills/hallmark/references/contract.md +25 -0
- package/control-plane/scripts/skills/hallmark/references/copy.md +182 -0
- package/control-plane/scripts/skills/hallmark/references/custom-craft.md +626 -0
- package/control-plane/scripts/skills/hallmark/references/custom-theme.md +367 -0
- package/control-plane/scripts/skills/hallmark/references/design-md.md +116 -0
- package/control-plane/scripts/skills/hallmark/references/export-formats.md +329 -0
- package/control-plane/scripts/skills/hallmark/references/floating-nav.md +89 -0
- package/control-plane/scripts/skills/hallmark/references/genres/atmospheric.md +69 -0
- package/control-plane/scripts/skills/hallmark/references/genres/editorial.md +72 -0
- package/control-plane/scripts/skills/hallmark/references/genres/modern-minimal.md +71 -0
- package/control-plane/scripts/skills/hallmark/references/genres/playful.md +70 -0
- package/control-plane/scripts/skills/hallmark/references/hero-enrichment.md +475 -0
- package/control-plane/scripts/skills/hallmark/references/imagery-kit.md +170 -0
- package/control-plane/scripts/skills/hallmark/references/interaction-and-states.md +208 -0
- package/control-plane/scripts/skills/hallmark/references/layout-and-space.md +112 -0
- package/control-plane/scripts/skills/hallmark/references/macrostructures/01-bento-grid.md +35 -0
- package/control-plane/scripts/skills/hallmark/references/macrostructures/02-long-document.md +34 -0
- package/control-plane/scripts/skills/hallmark/references/macrostructures/03-marquee-hero.md +31 -0
- package/control-plane/scripts/skills/hallmark/references/macrostructures/04-stat-led.md +34 -0
- package/control-plane/scripts/skills/hallmark/references/macrostructures/05-workbench.md +32 -0
- package/control-plane/scripts/skills/hallmark/references/macrostructures/06-conversational-faq.md +33 -0
- package/control-plane/scripts/skills/hallmark/references/macrostructures/07-manifesto.md +32 -0
- package/control-plane/scripts/skills/hallmark/references/macrostructures/08-photographic.md +34 -0
- package/control-plane/scripts/skills/hallmark/references/macrostructures/09-quote-led.md +32 -0
- package/control-plane/scripts/skills/hallmark/references/macrostructures/10-specimen.md +32 -0
- package/control-plane/scripts/skills/hallmark/references/macrostructures/11-catalogue.md +23 -0
- package/control-plane/scripts/skills/hallmark/references/macrostructures/12-letter.md +23 -0
- package/control-plane/scripts/skills/hallmark/references/macrostructures/13-index-first.md +23 -0
- package/control-plane/scripts/skills/hallmark/references/macrostructures/14-narrative-workflow.md +23 -0
- package/control-plane/scripts/skills/hallmark/references/macrostructures/15-split-studio.md +23 -0
- package/control-plane/scripts/skills/hallmark/references/macrostructures/16-feature-stack.md +23 -0
- package/control-plane/scripts/skills/hallmark/references/macrostructures/17-type-specimen.md +23 -0
- package/control-plane/scripts/skills/hallmark/references/macrostructures/18-portfolio-grid.md +23 -0
- package/control-plane/scripts/skills/hallmark/references/macrostructures/19-map-diagram.md +23 -0
- package/control-plane/scripts/skills/hallmark/references/macrostructures/20-ecosystem-index.md +23 -0
- package/control-plane/scripts/skills/hallmark/references/macrostructures/21-component-playground.md +23 -0
- package/control-plane/scripts/skills/hallmark/references/macrostructures.md +89 -0
- package/control-plane/scripts/skills/hallmark/references/microinteractions.md +258 -0
- package/control-plane/scripts/skills/hallmark/references/motion.md +109 -0
- package/control-plane/scripts/skills/hallmark/references/preview-examples.md +49 -0
- package/control-plane/scripts/skills/hallmark/references/responsive.md +138 -0
- package/control-plane/scripts/skills/hallmark/references/slop-test.md +192 -0
- package/control-plane/scripts/skills/hallmark/references/structure.md +160 -0
- package/control-plane/scripts/skills/hallmark/references/study.md +509 -0
- package/control-plane/scripts/skills/hallmark/references/themes/carnival.md +301 -0
- package/control-plane/scripts/skills/hallmark/references/themes/cobalt.md +146 -0
- package/control-plane/scripts/skills/hallmark/references/themes/grid.md +161 -0
- package/control-plane/scripts/skills/hallmark/references/themes/hum.md +403 -0
- package/control-plane/scripts/skills/hallmark/references/themes/lumen.md +478 -0
- package/control-plane/scripts/skills/hallmark/references/typography.md +243 -0
- package/control-plane/scripts/skills/hallmark/references/upstream/recipes.md +186 -0
- package/control-plane/scripts/skills/hallmark/references/upstream/study-examples.md +176 -0
- package/control-plane/scripts/skills/hallmark/references/upstream/tokens.css +1221 -0
- package/control-plane/scripts/skills/hallmark/references/verbs/audit.md +25 -0
- package/control-plane/scripts/skills/hallmark/references/verbs/redesign.md +269 -0
- package/control-plane/scripts/skills/human-comms/SKILL.md +29 -0
- package/control-plane/scripts/skills/index.mjs +432 -0
- package/control-plane/scripts/skills/internal-comms/LICENSE.txt +202 -0
- package/control-plane/scripts/skills/internal-comms/SKILL.md +32 -0
- package/control-plane/scripts/skills/internal-comms/examples/3p-updates.md +47 -0
- package/control-plane/scripts/skills/internal-comms/examples/company-newsletter.md +65 -0
- package/control-plane/scripts/skills/internal-comms/examples/faq-answers.md +30 -0
- package/control-plane/scripts/skills/internal-comms/examples/general-comms.md +16 -0
- package/control-plane/scripts/skills/mcp-adapter.mjs +94 -0
- package/control-plane/scripts/skills/mcp-builder/LICENSE.txt +202 -0
- package/control-plane/scripts/skills/mcp-builder/SKILL.md +236 -0
- package/control-plane/scripts/skills/mcp-builder/reference/evaluation.md +602 -0
- package/control-plane/scripts/skills/mcp-builder/reference/mcp_best_practices.md +249 -0
- package/control-plane/scripts/skills/mcp-builder/reference/node_mcp_server.md +970 -0
- package/control-plane/scripts/skills/mcp-builder/reference/python_mcp_server.md +719 -0
- package/control-plane/scripts/skills/mcp-builder/scripts/connections.py +151 -0
- package/control-plane/scripts/skills/mcp-builder/scripts/evaluation.py +373 -0
- package/control-plane/scripts/skills/mcp-builder/scripts/example_evaluation.xml +22 -0
- package/control-plane/scripts/skills/mcp-builder/scripts/requirements.txt +2 -0
- package/control-plane/scripts/skills/ponytail/SKILL.md +120 -0
- package/control-plane/scripts/skills/ponytail-audit/SKILL.md +41 -0
- package/control-plane/scripts/skills/ponytail-debt/SKILL.md +44 -0
- package/control-plane/scripts/skills/ponytail-gain/SKILL.md +50 -0
- package/control-plane/scripts/skills/ponytail-help/SKILL.md +71 -0
- package/control-plane/scripts/skills/ponytail-review/SKILL.md +57 -0
- package/control-plane/scripts/skills/receiving-code-review/SKILL.md +205 -0
- package/control-plane/scripts/skills/requesting-code-review/SKILL.md +95 -0
- package/control-plane/scripts/skills/requesting-code-review/code-reviewer.md +198 -0
- package/control-plane/scripts/skills/skill-creator/LICENSE.txt +202 -0
- package/control-plane/scripts/skills/skill-creator/SKILL.md +485 -0
- package/control-plane/scripts/skills/skill-creator/agents/analyzer.md +274 -0
- package/control-plane/scripts/skills/skill-creator/agents/comparator.md +202 -0
- package/control-plane/scripts/skills/skill-creator/agents/grader.md +223 -0
- package/control-plane/scripts/skills/skill-creator/assets/eval_review.html +146 -0
- package/control-plane/scripts/skills/skill-creator/eval-viewer/generate_review.py +471 -0
- package/control-plane/scripts/skills/skill-creator/eval-viewer/viewer.html +1325 -0
- package/control-plane/scripts/skills/skill-creator/references/schemas.md +430 -0
- package/control-plane/scripts/skills/skill-creator/scripts/__init__.py +0 -0
- package/control-plane/scripts/skills/skill-creator/scripts/aggregate_benchmark.py +401 -0
- package/control-plane/scripts/skills/skill-creator/scripts/generate_report.py +326 -0
- package/control-plane/scripts/skills/skill-creator/scripts/improve_description.py +247 -0
- package/control-plane/scripts/skills/skill-creator/scripts/package_skill.py +136 -0
- package/control-plane/scripts/skills/skill-creator/scripts/quick_validate.py +103 -0
- package/control-plane/scripts/skills/skill-creator/scripts/run_eval.py +310 -0
- package/control-plane/scripts/skills/skill-creator/scripts/run_loop.py +328 -0
- package/control-plane/scripts/skills/skill-creator/scripts/utils.py +47 -0
- package/control-plane/scripts/skills/slack-gif-creator/LICENSE.txt +202 -0
- package/control-plane/scripts/skills/slack-gif-creator/SKILL.md +254 -0
- package/control-plane/scripts/skills/slack-gif-creator/core/easing.py +234 -0
- package/control-plane/scripts/skills/slack-gif-creator/core/frame_composer.py +176 -0
- package/control-plane/scripts/skills/slack-gif-creator/core/gif_builder.py +269 -0
- package/control-plane/scripts/skills/slack-gif-creator/core/validators.py +136 -0
- package/control-plane/scripts/skills/slack-gif-creator/requirements.txt +4 -0
- package/control-plane/scripts/skills/sources.json +401 -0
- package/control-plane/scripts/skills/sources.lock.json +416 -0
- package/control-plane/scripts/skills/subagent-driven-development/SKILL.md +570 -0
- package/control-plane/scripts/skills/subagent-driven-development/implementer-prompt.md +154 -0
- package/control-plane/scripts/skills/subagent-driven-development/re-review-prompt.md +115 -0
- package/control-plane/scripts/skills/subagent-driven-development/scripts/review-package +53 -0
- package/control-plane/scripts/skills/subagent-driven-development/scripts/sdd-workspace +82 -0
- package/control-plane/scripts/skills/subagent-driven-development/scripts/task-brief +43 -0
- package/control-plane/scripts/skills/subagent-driven-development/task-reviewer-prompt.md +207 -0
- package/control-plane/scripts/skills/systematic-debugging/CREATION-LOG.md +119 -0
- package/control-plane/scripts/skills/systematic-debugging/SKILL.md +283 -0
- package/control-plane/scripts/skills/systematic-debugging/condition-based-waiting-example.ts +158 -0
- package/control-plane/scripts/skills/systematic-debugging/condition-based-waiting.md +115 -0
- package/control-plane/scripts/skills/systematic-debugging/defense-in-depth.md +122 -0
- package/control-plane/scripts/skills/systematic-debugging/find-polluter.sh +72 -0
- package/control-plane/scripts/skills/systematic-debugging/root-cause-tracing.md +169 -0
- package/control-plane/scripts/skills/systematic-debugging/test-academic.md +14 -0
- package/control-plane/scripts/skills/systematic-debugging/test-pressure-1.md +58 -0
- package/control-plane/scripts/skills/systematic-debugging/test-pressure-2.md +68 -0
- package/control-plane/scripts/skills/systematic-debugging/test-pressure-3.md +69 -0
- package/control-plane/scripts/skills/test-driven-development/SKILL.md +330 -0
- package/control-plane/scripts/skills/test-driven-development/writing-good-tests.md +198 -0
- package/control-plane/scripts/skills/theme-factory/LICENSE.txt +202 -0
- package/control-plane/scripts/skills/theme-factory/SKILL.md +59 -0
- package/control-plane/scripts/skills/theme-factory/theme-showcase.pdf +0 -0
- package/control-plane/scripts/skills/theme-factory/themes/arctic-frost.md +19 -0
- package/control-plane/scripts/skills/theme-factory/themes/botanical-garden.md +19 -0
- package/control-plane/scripts/skills/theme-factory/themes/desert-rose.md +19 -0
- package/control-plane/scripts/skills/theme-factory/themes/forest-canopy.md +19 -0
- package/control-plane/scripts/skills/theme-factory/themes/golden-hour.md +19 -0
- package/control-plane/scripts/skills/theme-factory/themes/midnight-galaxy.md +19 -0
- package/control-plane/scripts/skills/theme-factory/themes/modern-minimalist.md +19 -0
- package/control-plane/scripts/skills/theme-factory/themes/ocean-depths.md +19 -0
- package/control-plane/scripts/skills/theme-factory/themes/sunset-boulevard.md +19 -0
- package/control-plane/scripts/skills/theme-factory/themes/tech-innovation.md +19 -0
- package/control-plane/scripts/skills/using-git-worktrees/SKILL.md +167 -0
- package/control-plane/scripts/skills/verification-before-completion/SKILL.md +120 -0
- package/control-plane/scripts/skills/web-artifacts-builder/LICENSE.txt +202 -0
- package/control-plane/scripts/skills/web-artifacts-builder/SKILL.md +74 -0
- package/control-plane/scripts/skills/web-artifacts-builder/scripts/bundle-artifact.sh +54 -0
- package/control-plane/scripts/skills/web-artifacts-builder/scripts/init-artifact.sh +322 -0
- package/control-plane/scripts/skills/web-artifacts-builder/scripts/shadcn-components.tar.gz +0 -0
- package/control-plane/scripts/skills/webapp-testing/LICENSE.txt +202 -0
- package/control-plane/scripts/skills/webapp-testing/SKILL.md +96 -0
- package/control-plane/scripts/skills/webapp-testing/examples/console_logging.py +35 -0
- package/control-plane/scripts/skills/webapp-testing/examples/element_discovery.py +40 -0
- package/control-plane/scripts/skills/webapp-testing/examples/static_html_automation.py +33 -0
- package/control-plane/scripts/skills/webapp-testing/scripts/with_server.py +106 -0
- package/control-plane/scripts/skills/writing-plans/SKILL.md +192 -0
- package/control-plane/scripts/skills/writing-plans/plan-document-reviewer-prompt.md +49 -0
- package/control-plane/scripts/skills/writing-skills/SKILL.md +681 -0
- package/control-plane/scripts/skills/writing-skills/anthropic-best-practices.md +1150 -0
- package/control-plane/scripts/skills/writing-skills/examples/CLAUDE_MD_TESTING.md +189 -0
- package/control-plane/scripts/skills/writing-skills/graphviz-conventions.dot +172 -0
- package/control-plane/scripts/skills/writing-skills/persuasion-principles.md +187 -0
- package/control-plane/scripts/skills/writing-skills/render-graphs.js +169 -0
- package/control-plane/scripts/skills/writing-skills/testing-skills-with-subagents.md +384 -0
- package/control-plane/scripts/smoke-mcp-schemas.mjs +30 -0
- package/control-plane/scripts/surface-config.mjs +45 -0
- package/control-plane/scripts/sync-skills.mjs +370 -0
- package/control-plane/scripts/tool-metrics.mjs +74 -0
- package/control-plane/scripts/tool-risk.mjs +156 -0
- package/control-plane/scripts/tool-surface-stability.mjs +38 -0
- package/control-plane/scripts/upstreams/catalog-cache.mjs +28 -0
- package/control-plane/scripts/upstreams/config.mjs +158 -0
- package/control-plane/scripts/upstreams/diagnostics.mjs +21 -0
- package/control-plane/scripts/upstreams/http-client.mjs +67 -0
- package/control-plane/scripts/upstreams/manager.mjs +535 -0
- package/control-plane/scripts/upstreams/names.mjs +55 -0
- package/control-plane/scripts/upstreams/presets.mjs +60 -0
- package/control-plane/scripts/upstreams/resource-uri.mjs +29 -0
- package/control-plane/scripts/upstreams/stdio-client.mjs +73 -0
- package/control-plane/scripts/workspace-registry.mjs +147 -0
- package/dist/context/authorization.d.ts +5 -0
- package/dist/context/authorization.js +70 -0
- package/dist/context/capture.d.ts +46 -0
- package/dist/context/capture.js +187 -0
- package/dist/context/cli.d.ts +47 -0
- package/dist/context/cli.js +287 -0
- package/dist/context/device-adapter.d.ts +102 -0
- package/dist/context/device-adapter.js +79 -0
- package/dist/context/gateway.d.ts +354 -0
- package/dist/context/gateway.js +50 -0
- package/dist/context/graph.d.ts +63 -0
- package/dist/context/graph.js +214 -0
- package/dist/context/importer.d.ts +8 -0
- package/dist/context/importer.js +69 -0
- package/dist/context/indexer.d.ts +14 -0
- package/dist/context/indexer.js +57 -0
- package/dist/context/migrations/index.d.ts +4 -0
- package/dist/context/migrations/index.js +87 -0
- package/dist/context/redact.d.ts +15 -0
- package/dist/context/redact.js +43 -0
- package/dist/context/repositories.d.ts +9 -0
- package/dist/context/repositories.js +45 -0
- package/dist/context/search.d.ts +63 -0
- package/dist/context/search.js +207 -0
- package/dist/context/service.d.ts +278 -0
- package/dist/context/service.js +160 -0
- package/dist/context/store.d.ts +134 -0
- package/dist/context/store.js +722 -0
- package/dist/context/text.d.ts +4 -0
- package/dist/context/text.js +14 -0
- package/dist/context/tool-contract.d.ts +534 -0
- package/dist/context/tool-contract.js +45 -0
- package/dist/device/core.d.ts +29 -0
- package/dist/device/core.js +66 -0
- package/dist/device/device.d.ts +5 -0
- package/dist/device/device.js +94 -37
- package/dist/device/gateway-channel.d.ts +8 -1
- package/dist/device/gateway-channel.js +51 -3
- package/dist/device/gateway-tool-adapter.d.ts +11 -3
- package/dist/device/gateway-tool-adapter.js +141 -98
- package/dist/device/official-trust.d.ts +12 -0
- package/dist/device/official-trust.js +25 -1
- package/dist/device/runtime-owner.js +9 -1
- package/dist/distribution.d.ts +4 -0
- package/dist/distribution.js +8 -0
- package/dist/handlers/filesystem-handlers.js +53 -37
- package/dist/handlers/search-handlers.d.ts +1 -0
- package/dist/handlers/search-handlers.js +51 -1
- package/dist/log-palace/cli.d.ts +7 -0
- package/dist/log-palace/cli.js +89 -0
- package/dist/log-palace/formats.d.ts +26 -0
- package/dist/log-palace/formats.js +126 -0
- package/dist/log-palace/store.d.ts +129 -0
- package/dist/log-palace/store.js +856 -0
- package/dist/mcp-device.d.ts +1 -1
- package/dist/mcp-device.js +28 -6
- package/dist/npm-scripts/remote.js +1 -0
- package/dist/platform/activity-journal.d.ts +16 -0
- package/dist/platform/activity-journal.js +68 -0
- package/dist/platform/cli.d.ts +1 -0
- package/dist/platform/cli.js +300 -0
- package/dist/platform/coordination-lock.d.ts +45 -0
- package/dist/platform/coordination-lock.js +233 -0
- package/dist/platform/device-runtime.d.ts +16 -0
- package/dist/platform/device-runtime.js +83 -0
- package/dist/platform/knowledge-contract.d.ts +46 -0
- package/dist/platform/knowledge-contract.js +1 -0
- package/dist/platform/knowledge-mcp.d.ts +29 -0
- package/dist/platform/knowledge-mcp.js +95 -0
- package/dist/platform/knowledge-router.d.ts +64 -0
- package/dist/platform/knowledge-router.js +320 -0
- package/dist/platform/plugin-child.d.ts +1 -0
- package/dist/platform/plugin-child.js +115 -0
- package/dist/platform/plugin-contract.d.ts +76 -0
- package/dist/platform/plugin-contract.js +53 -0
- package/dist/platform/plugin-driver.d.ts +8 -0
- package/dist/platform/plugin-driver.js +443 -0
- package/dist/platform/plugin-host.d.ts +88 -0
- package/dist/platform/plugin-host.js +817 -0
- package/dist/platform/plugin-policy.d.ts +3 -0
- package/dist/platform/plugin-policy.js +29 -0
- package/dist/platform/plugin-services.d.ts +14 -0
- package/dist/platform/plugin-services.js +32 -0
- package/dist/platform/process-identity.d.ts +9 -0
- package/dist/platform/process-identity.js +68 -0
- package/dist/platform/runtime-lease.d.ts +4 -0
- package/dist/platform/runtime-lease.js +58 -0
- package/dist/platform/runtime-root.d.ts +9 -0
- package/dist/platform/runtime-root.js +61 -0
- package/dist/platform/state-backup.d.ts +92 -0
- package/dist/platform/state-backup.js +399 -0
- package/dist/plugins/reference/plugin.d.ts +3 -0
- package/dist/plugins/reference/plugin.js +13 -0
- package/dist/search-manager.d.ts +6 -0
- package/dist/search-manager.js +123 -0
- package/dist/server.d.ts +13 -0
- package/dist/server.js +7 -3
- package/dist/terminal-manager.d.ts +16 -0
- package/dist/terminal-manager.js +137 -42
- package/dist/tool-dispatcher.d.ts +11 -0
- package/dist/tool-dispatcher.js +228 -0
- package/dist/tools/edit.js +72 -1
- package/dist/tools/filesystem.d.ts +1 -1
- package/dist/tools/filesystem.js +2 -2
- package/dist/tools/improved-process-tools.js +91 -108
- package/dist/tools/schemas.d.ts +94 -2
- package/dist/tools/schemas.js +20 -6
- package/dist/types.d.ts +8 -0
- package/dist/utils/output-budget.d.ts +6 -0
- package/dist/utils/output-budget.js +15 -0
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/package.json +29 -6
- package/skills/mcp-device-context/SKILL.md +40 -0
|
@@ -0,0 +1,817 @@
|
|
|
1
|
+
import fs from 'node:fs/promises';
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
import { randomUUID } from 'node:crypto';
|
|
4
|
+
import { CapabilityError, compatible, validateManifest } from './plugin-contract.js';
|
|
5
|
+
import { createPluginDriver } from './plugin-driver.js';
|
|
6
|
+
import { coordinationOwnerAbandoned, processAppearsDead, repairCoordinationLock, withCoordinationLock } from './coordination-lock.js';
|
|
7
|
+
import { processBirth, processNamespace } from './process-identity.js';
|
|
8
|
+
function canonical(value) {
|
|
9
|
+
if (Array.isArray(value))
|
|
10
|
+
return value.map(canonical);
|
|
11
|
+
if (value && typeof value === 'object')
|
|
12
|
+
return Object.fromEntries(Object.entries(value)
|
|
13
|
+
.filter(([, item]) => item !== undefined).sort(([a], [b]) => a.localeCompare(b))
|
|
14
|
+
.map(([key, item]) => [key, canonical(item)]));
|
|
15
|
+
return value;
|
|
16
|
+
}
|
|
17
|
+
const sameValue = (a, b) => JSON.stringify(canonical(a)) === JSON.stringify(canonical(b));
|
|
18
|
+
const activeUpgradeTokens = new Set();
|
|
19
|
+
function bundleContract(manifest, options = {}) {
|
|
20
|
+
const { entry: _entry, version: _version, ...rest } = manifest;
|
|
21
|
+
const contract = options.ignoreVersion ? rest : { ...rest, version: manifest.version };
|
|
22
|
+
return JSON.stringify(canonical(contract));
|
|
23
|
+
}
|
|
24
|
+
async function pathExists(filename) {
|
|
25
|
+
try {
|
|
26
|
+
await fs.lstat(filename);
|
|
27
|
+
return true;
|
|
28
|
+
}
|
|
29
|
+
catch (error) {
|
|
30
|
+
if (error.code === 'ENOENT')
|
|
31
|
+
return false;
|
|
32
|
+
throw error;
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
async function renameWithRetry(source, target) {
|
|
36
|
+
for (let attempt = 0;; attempt++) {
|
|
37
|
+
try {
|
|
38
|
+
await fs.rename(source, target);
|
|
39
|
+
return;
|
|
40
|
+
}
|
|
41
|
+
catch (error) {
|
|
42
|
+
if (process.platform !== 'win32' || attempt >= 7 || !['EPERM', 'EBUSY', 'EACCES'].includes(String(error.code)))
|
|
43
|
+
throw error;
|
|
44
|
+
await new Promise(resolve => setTimeout(resolve, Math.min(250, 20 * (2 ** attempt))));
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
async function writeJsonAtomic(filename, value) {
|
|
49
|
+
await fs.mkdir(path.dirname(filename), { recursive: true });
|
|
50
|
+
const temporary = filename + '.' + randomUUID() + '.tmp';
|
|
51
|
+
const text = JSON.stringify(value, null, 2) + '\n';
|
|
52
|
+
if (Buffer.byteLength(text) > 2 * 1024 * 1024)
|
|
53
|
+
throw new CapabilityError('PLUGIN_UPGRADE_INTENT_INVALID', 'Plugin upgrade intent exceeds the bounded metadata limit');
|
|
54
|
+
try {
|
|
55
|
+
const handle = await fs.open(temporary, 'wx', 0o600);
|
|
56
|
+
try {
|
|
57
|
+
await handle.writeFile(text);
|
|
58
|
+
await handle.sync();
|
|
59
|
+
}
|
|
60
|
+
finally {
|
|
61
|
+
await handle.close();
|
|
62
|
+
}
|
|
63
|
+
await renameWithRetry(temporary, filename);
|
|
64
|
+
}
|
|
65
|
+
finally {
|
|
66
|
+
await fs.rm(temporary, { force: true });
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
/** Private plugin state; neither accounts nor Device identity are owned here. */
|
|
70
|
+
export class PluginHost {
|
|
71
|
+
notifyCapabilitiesChanged() { for (const listener of this.listeners) {
|
|
72
|
+
try {
|
|
73
|
+
listener();
|
|
74
|
+
}
|
|
75
|
+
catch { /* observers do not own lifecycle */ }
|
|
76
|
+
} }
|
|
77
|
+
onCapabilitiesChanged(listener) { this.listeners.add(listener); return () => this.listeners.delete(listener); }
|
|
78
|
+
constructor(options) {
|
|
79
|
+
this.options = options;
|
|
80
|
+
this.registry = { version: 1, revision: 0, plugins: {} };
|
|
81
|
+
this.drivers = new Map();
|
|
82
|
+
this.driverIntent = new WeakMap();
|
|
83
|
+
this.refreshedRevision = -1;
|
|
84
|
+
// Faulted or disabled drivers remain owned until process/resource release is proved.
|
|
85
|
+
this.ownedDrivers = new Map();
|
|
86
|
+
this.dependencies = new Map();
|
|
87
|
+
this.started = false;
|
|
88
|
+
this.registrySavePending = false;
|
|
89
|
+
this.pendingFaults = new Map();
|
|
90
|
+
this.startupSkipIds = new Set();
|
|
91
|
+
this.operations = Promise.resolve();
|
|
92
|
+
this.listeners = new Set();
|
|
93
|
+
this.root = path.resolve(options.stateRoot, 'plugins');
|
|
94
|
+
this.registryPath = path.join(this.root, 'registry.json');
|
|
95
|
+
}
|
|
96
|
+
pluginRoot(id) { return path.join(this.root, id); }
|
|
97
|
+
upgradeIntentPath(id) { return path.join(this.pluginRoot(id), 'upgrade-intent.json'); }
|
|
98
|
+
validateUpgradeIntent(value, id) {
|
|
99
|
+
const intent = value;
|
|
100
|
+
const phases = ['preparing', 'candidate-ready', 'swapping', 'swapped', 'rollback', 'committed'];
|
|
101
|
+
if (!intent || intent.version !== 1 || intent.id !== id || !phases.includes(intent.phase) ||
|
|
102
|
+
!Number.isSafeInteger(intent.pid) || intent.pid < 1 || !Number.isFinite(intent.startedAt) ||
|
|
103
|
+
(intent.processBirth !== undefined && (typeof intent.processBirth !== 'string' || intent.processBirth.length > 256)) ||
|
|
104
|
+
(intent.namespace !== undefined && (!intent.namespace || typeof intent.namespace.platform !== 'string' || typeof intent.namespace.hostname !== 'string')) ||
|
|
105
|
+
!/^[a-f0-9-]{36}$/i.test(intent.token) || intent.candidate !== `upgrade-candidate-${intent.token}` ||
|
|
106
|
+
intent.backup !== `upgrade-backup-${intent.token}` || intent.failed !== `upgrade-failed-${intent.token}`)
|
|
107
|
+
throw new CapabilityError('PLUGIN_UPGRADE_RECOVERY_REQUIRED', `Invalid upgrade intent for ${id}`);
|
|
108
|
+
validateManifest(intent.previous?.manifest);
|
|
109
|
+
validateManifest(intent.next?.manifest);
|
|
110
|
+
if (intent.previous.manifest.id !== id || intent.next.manifest.id !== id)
|
|
111
|
+
throw new CapabilityError('PLUGIN_UPGRADE_RECOVERY_REQUIRED', `Upgrade intent identity mismatch for ${id}`);
|
|
112
|
+
return intent;
|
|
113
|
+
}
|
|
114
|
+
async writeUpgradeIntent(intent) {
|
|
115
|
+
await writeJsonAtomic(this.upgradeIntentPath(intent.id), intent);
|
|
116
|
+
}
|
|
117
|
+
upgradePaths(intent) {
|
|
118
|
+
const root = this.pluginRoot(intent.id);
|
|
119
|
+
return { data: path.join(root, 'data'), candidate: path.join(root, intent.candidate), backup: path.join(root, intent.backup), failed: path.join(root, intent.failed), intentPath: this.upgradeIntentPath(intent.id) };
|
|
120
|
+
}
|
|
121
|
+
recordGenerationMatches(actual, expected) {
|
|
122
|
+
// Activation may transiently change enabled/state/schema while validating a
|
|
123
|
+
// new generation. Manifest plus provenance identifies the generation; the
|
|
124
|
+
// complete prior record is restored on rollback.
|
|
125
|
+
return !!actual && sameValue(actual.manifest, expected.manifest) && actual.installationSource === expected.installationSource;
|
|
126
|
+
}
|
|
127
|
+
async rollbackUpgradeIntent(intent) {
|
|
128
|
+
const paths = this.upgradePaths(intent), current = this.registry.plugins[intent.id];
|
|
129
|
+
if (!this.recordGenerationMatches(current, intent.previous) && !this.recordGenerationMatches(current, intent.next))
|
|
130
|
+
throw new CapabilityError('PLUGIN_UPGRADE_RECOVERY_REQUIRED', `Plugin ${intent.id} changed outside its pending upgrade`);
|
|
131
|
+
intent.phase = 'rollback';
|
|
132
|
+
await this.writeUpgradeIntent(intent);
|
|
133
|
+
const backup = await pathExists(paths.backup), data = await pathExists(paths.data), failed = await pathExists(paths.failed);
|
|
134
|
+
if (backup) {
|
|
135
|
+
if (data) {
|
|
136
|
+
if (failed)
|
|
137
|
+
throw new CapabilityError('PLUGIN_UPGRADE_RECOVERY_REQUIRED', `Ambiguous rollback trees for ${intent.id}`);
|
|
138
|
+
await renameWithRetry(paths.data, paths.failed);
|
|
139
|
+
}
|
|
140
|
+
await renameWithRetry(paths.backup, paths.data);
|
|
141
|
+
}
|
|
142
|
+
else if (!(data && (failed || this.recordGenerationMatches(current, intent.previous)))) {
|
|
143
|
+
throw new CapabilityError('PLUGIN_UPGRADE_RECOVERY_REQUIRED', `Rollback backup is unavailable for ${intent.id}`);
|
|
144
|
+
}
|
|
145
|
+
this.registry.plugins[intent.id] = structuredClone(intent.previous);
|
|
146
|
+
await this.save();
|
|
147
|
+
await fs.rm(paths.failed, { recursive: true, force: true });
|
|
148
|
+
await fs.rm(paths.candidate, { recursive: true, force: true });
|
|
149
|
+
await fs.rm(paths.backup, { recursive: true, force: true });
|
|
150
|
+
await fs.rm(paths.intentPath, { force: true });
|
|
151
|
+
}
|
|
152
|
+
async recoverUpgradeIntent(intent) {
|
|
153
|
+
const paths = this.upgradePaths(intent);
|
|
154
|
+
if (activeUpgradeTokens.has(intent.token))
|
|
155
|
+
throw new CapabilityError('PLUGIN_UPGRADE_BUSY', `Plugin ${intent.id} is being upgraded in this process`);
|
|
156
|
+
if (intent.pid !== process.pid) {
|
|
157
|
+
// Namespace-bearing intents are never recovered from PID liveness alone.
|
|
158
|
+
// When birth identity is absent we can still prove abandonment only if the
|
|
159
|
+
// recorded namespace matches and that PID no longer exists there.
|
|
160
|
+
const abandoned = intent.namespace
|
|
161
|
+
? await coordinationOwnerAbandoned({ pid: intent.pid, token: intent.token, ...(intent.processBirth ? { processBirth: intent.processBirth } : {}), namespace: intent.namespace })
|
|
162
|
+
: processAppearsDead(intent.pid);
|
|
163
|
+
if (!abandoned)
|
|
164
|
+
throw new CapabilityError('PLUGIN_UPGRADE_BUSY', `Plugin ${intent.id} is being upgraded by a live or uninspectable owner PID ${intent.pid}`);
|
|
165
|
+
}
|
|
166
|
+
if (intent.phase === 'committed') {
|
|
167
|
+
if (!this.recordGenerationMatches(this.registry.plugins[intent.id], intent.next) || !await pathExists(paths.data))
|
|
168
|
+
throw new CapabilityError('PLUGIN_UPGRADE_RECOVERY_REQUIRED', `Committed upgrade state is incomplete for ${intent.id}`);
|
|
169
|
+
await fs.rm(paths.backup, { recursive: true, force: true });
|
|
170
|
+
await fs.rm(paths.candidate, { recursive: true, force: true });
|
|
171
|
+
await fs.rm(paths.failed, { recursive: true, force: true });
|
|
172
|
+
await fs.rm(paths.intentPath, { force: true });
|
|
173
|
+
return;
|
|
174
|
+
}
|
|
175
|
+
if (intent.phase === 'preparing' || intent.phase === 'candidate-ready') {
|
|
176
|
+
if (await pathExists(paths.backup) || !await pathExists(paths.data)) {
|
|
177
|
+
await this.rollbackUpgradeIntent(intent);
|
|
178
|
+
return;
|
|
179
|
+
}
|
|
180
|
+
if (!this.recordGenerationMatches(this.registry.plugins[intent.id], intent.previous))
|
|
181
|
+
throw new CapabilityError('PLUGIN_UPGRADE_RECOVERY_REQUIRED', `Prepared upgrade registry changed for ${intent.id}`);
|
|
182
|
+
await fs.rm(paths.candidate, { recursive: true, force: true });
|
|
183
|
+
await fs.rm(paths.failed, { recursive: true, force: true });
|
|
184
|
+
await fs.rm(paths.intentPath, { force: true });
|
|
185
|
+
return;
|
|
186
|
+
}
|
|
187
|
+
if (intent.phase === 'swapping' && !await pathExists(paths.backup) && await pathExists(paths.data) && await pathExists(paths.candidate)) {
|
|
188
|
+
if (!this.recordGenerationMatches(this.registry.plugins[intent.id], intent.previous))
|
|
189
|
+
throw new CapabilityError('PLUGIN_UPGRADE_RECOVERY_REQUIRED', `Upgrade registry changed before swap for ${intent.id}`);
|
|
190
|
+
await fs.rm(paths.candidate, { recursive: true, force: true });
|
|
191
|
+
await fs.rm(paths.intentPath, { force: true });
|
|
192
|
+
return;
|
|
193
|
+
}
|
|
194
|
+
await this.rollbackUpgradeIntent(intent);
|
|
195
|
+
}
|
|
196
|
+
async recoverUpgradeIntents() {
|
|
197
|
+
for (const id of Object.keys(this.registry.plugins)) {
|
|
198
|
+
const filename = this.upgradeIntentPath(id);
|
|
199
|
+
if (!await pathExists(filename))
|
|
200
|
+
continue;
|
|
201
|
+
const stat = await fs.lstat(filename);
|
|
202
|
+
if (!stat.isFile() || stat.isSymbolicLink() || stat.size > 2 * 1024 * 1024)
|
|
203
|
+
throw new CapabilityError('PLUGIN_UPGRADE_RECOVERY_REQUIRED', `Unsafe upgrade intent for ${id}`);
|
|
204
|
+
const intent = this.validateUpgradeIntent(JSON.parse(await fs.readFile(filename, 'utf8')), id);
|
|
205
|
+
await this.recoverUpgradeIntent(intent);
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
serial(operation) {
|
|
209
|
+
const next = this.operations.then(operation, operation);
|
|
210
|
+
this.operations = next.catch(() => { });
|
|
211
|
+
return next;
|
|
212
|
+
}
|
|
213
|
+
async load() {
|
|
214
|
+
try {
|
|
215
|
+
const parsed = JSON.parse(await fs.readFile(this.registryPath, 'utf8'));
|
|
216
|
+
if (parsed.version !== 1 || !Number.isSafeInteger(parsed.revision) || !parsed.plugins)
|
|
217
|
+
throw new Error('Unsupported plugin registry');
|
|
218
|
+
for (const [id, record] of Object.entries(parsed.plugins)) {
|
|
219
|
+
validateManifest(record.manifest);
|
|
220
|
+
if (id !== record.manifest.id)
|
|
221
|
+
throw new Error('Plugin registry identity mismatch');
|
|
222
|
+
}
|
|
223
|
+
for (const [id, pending] of this.pendingFaults) {
|
|
224
|
+
const current = parsed.plugins[id];
|
|
225
|
+
const sameActivationIntent = current && ['installed', 'enabled'].includes(current.state)
|
|
226
|
+
&& ['installed', 'enabled'].includes(pending.previous.state)
|
|
227
|
+
&& sameValue({ ...current, state: 'installed' }, { ...pending.previous, state: 'installed' });
|
|
228
|
+
if (sameValue(current, pending.previous) || sameValue(current, pending.fault) || sameActivationIntent) {
|
|
229
|
+
parsed.plugins[id] = structuredClone(pending.fault);
|
|
230
|
+
this.registrySavePending = true;
|
|
231
|
+
}
|
|
232
|
+
else {
|
|
233
|
+
// A changed record (including an explicit enable of identical state)
|
|
234
|
+
// owns newer intent. Unrelated registry revisions do not clear faults.
|
|
235
|
+
this.pendingFaults.delete(id);
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
this.registry = parsed;
|
|
239
|
+
await this.recoverUpgradeIntents();
|
|
240
|
+
}
|
|
241
|
+
catch (error) {
|
|
242
|
+
if (error.code !== 'ENOENT')
|
|
243
|
+
throw error;
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
registryLockOptions() {
|
|
247
|
+
return {
|
|
248
|
+
attempts: 100, staleMs: 5000, retainAbandoned: true, messagePrefix: 'Plugin registry',
|
|
249
|
+
codes: { busy: 'PLUGIN_REGISTRY_BUSY', active: 'PLUGIN_REGISTRY_LOCK_ACTIVE', recoveryRequired: 'PLUGIN_REGISTRY_RECOVERY_REQUIRED' }
|
|
250
|
+
};
|
|
251
|
+
}
|
|
252
|
+
async repairRegistryLock() {
|
|
253
|
+
await fs.mkdir(this.root, { recursive: true });
|
|
254
|
+
return repairCoordinationLock(path.join(this.root, 'registry.lock'), { ...this.registryLockOptions(), breakLock: true });
|
|
255
|
+
}
|
|
256
|
+
async save() {
|
|
257
|
+
await fs.mkdir(this.root, { recursive: true });
|
|
258
|
+
const lockPath = path.join(this.root, 'registry.lock');
|
|
259
|
+
await withCoordinationLock(lockPath, async () => {
|
|
260
|
+
const temporary = this.registryPath + '.' + randomUUID() + '.tmp';
|
|
261
|
+
try {
|
|
262
|
+
let currentRevision = 0;
|
|
263
|
+
try {
|
|
264
|
+
currentRevision = JSON.parse(await fs.readFile(this.registryPath, 'utf8')).revision;
|
|
265
|
+
}
|
|
266
|
+
catch (error) {
|
|
267
|
+
if (error.code !== 'ENOENT')
|
|
268
|
+
throw error;
|
|
269
|
+
}
|
|
270
|
+
if (currentRevision !== this.registry.revision)
|
|
271
|
+
throw new CapabilityError('PLUGIN_REGISTRY_CHANGED', 'Reload plugin registry before retrying the lifecycle change');
|
|
272
|
+
const updated = { ...this.registry, revision: currentRevision + 1 };
|
|
273
|
+
const handle = await fs.open(temporary, 'wx', 0o600);
|
|
274
|
+
try {
|
|
275
|
+
await handle.writeFile(JSON.stringify(updated, null, 2) + '\n');
|
|
276
|
+
await handle.sync();
|
|
277
|
+
}
|
|
278
|
+
finally {
|
|
279
|
+
await handle.close();
|
|
280
|
+
}
|
|
281
|
+
await renameWithRetry(temporary, this.registryPath);
|
|
282
|
+
this.registry.revision = updated.revision;
|
|
283
|
+
this.notifyCapabilitiesChanged();
|
|
284
|
+
// Rename has already published the revision even if directory sync fails.
|
|
285
|
+
// Never retry from the old revision in that case. Windows cannot open
|
|
286
|
+
// directory handles this way; POSIX sync failures remain observable.
|
|
287
|
+
if (process.platform !== 'win32') {
|
|
288
|
+
const directory = await fs.open(this.root, 'r');
|
|
289
|
+
try {
|
|
290
|
+
await directory.sync();
|
|
291
|
+
}
|
|
292
|
+
finally {
|
|
293
|
+
await directory.close();
|
|
294
|
+
}
|
|
295
|
+
}
|
|
296
|
+
this.pendingFaults.clear();
|
|
297
|
+
}
|
|
298
|
+
finally {
|
|
299
|
+
await fs.rm(temporary, { force: true });
|
|
300
|
+
}
|
|
301
|
+
}, this.registryLockOptions());
|
|
302
|
+
}
|
|
303
|
+
dataRoot(id) { if (!/^[a-z][a-z0-9-]{0,63}$/.test(id))
|
|
304
|
+
throw new CapabilityError('PLUGIN_ID_INVALID', 'Invalid plugin id'); return path.join(this.root, id, 'data'); }
|
|
305
|
+
list() { return Object.values(this.registry.plugins).map(record => ({ ...record, manifest: structuredClone(record.manifest) })).sort((a, b) => a.manifest.id.localeCompare(b.manifest.id)); }
|
|
306
|
+
async install(manifest, options = {}) {
|
|
307
|
+
return this.serial(async () => {
|
|
308
|
+
validateManifest(manifest);
|
|
309
|
+
await this.load();
|
|
310
|
+
const prior = this.registry.plugins[manifest.id];
|
|
311
|
+
if (prior?.installed) {
|
|
312
|
+
if (prior.manifest.version === manifest.version && prior.manifest.entry === manifest.entry) {
|
|
313
|
+
if (options.enabled === true)
|
|
314
|
+
await this.enableInstalled(manifest.id);
|
|
315
|
+
return;
|
|
316
|
+
}
|
|
317
|
+
throw new CapabilityError('PLUGIN_UPGRADE_REQUIRED', 'Use upgrade to replace an installed plugin');
|
|
318
|
+
}
|
|
319
|
+
this.registry.plugins[manifest.id] = { manifest: structuredClone(manifest), installed: true, installationSource: options.installationSource ?? 'manual', enabled: options.enabled ?? false, explicitlyEnabled: options.enabled === true, schemaVersion: prior?.schemaVersion ?? manifest.dataSchemaVersion, state: options.enabled ? 'installed' : 'disabled', settings: options.settings ?? prior?.settings, lifecycleToken: randomUUID() };
|
|
320
|
+
await this.save();
|
|
321
|
+
if (this.started && options.enabled)
|
|
322
|
+
await this.activate(manifest.id, new Set(), true);
|
|
323
|
+
});
|
|
324
|
+
}
|
|
325
|
+
async ensureBundled(manifests) {
|
|
326
|
+
return this.serial(async () => {
|
|
327
|
+
await this.load();
|
|
328
|
+
for (const manifest of manifests) {
|
|
329
|
+
validateManifest(manifest);
|
|
330
|
+
const record = this.registry.plugins[manifest.id];
|
|
331
|
+
if (!record) {
|
|
332
|
+
this.registry.plugins[manifest.id] = {
|
|
333
|
+
manifest: structuredClone(manifest), installed: true, enabled: true,
|
|
334
|
+
installationSource: 'distribution', schemaVersion: manifest.dataSchemaVersion, state: 'installed'
|
|
335
|
+
};
|
|
336
|
+
await this.save();
|
|
337
|
+
if (this.started)
|
|
338
|
+
await this.activate(manifest.id, new Set()).catch(() => { });
|
|
339
|
+
continue;
|
|
340
|
+
}
|
|
341
|
+
// A tombstone or user-managed installation is never claimed by the distribution.
|
|
342
|
+
if (!record.installed || record.installationSource !== 'distribution')
|
|
343
|
+
continue;
|
|
344
|
+
const runtimeContractMatches = bundleContract(record.manifest, { ignoreVersion: true }) === bundleContract(manifest, { ignoreVersion: true })
|
|
345
|
+
&& record.schemaVersion === manifest.dataSchemaVersion;
|
|
346
|
+
if (!runtimeContractMatches) {
|
|
347
|
+
const driver = this.drivers.get(manifest.id);
|
|
348
|
+
this.drivers.delete(manifest.id);
|
|
349
|
+
this.dependencies.delete(manifest.id);
|
|
350
|
+
if (driver)
|
|
351
|
+
this.notifyCapabilitiesChanged();
|
|
352
|
+
await this.stopDriver(driver).catch(() => { });
|
|
353
|
+
const gate = { version: manifest.version, dataSchemaVersion: manifest.dataSchemaVersion };
|
|
354
|
+
const state = record.enabled ? 'faulted' : 'disabled';
|
|
355
|
+
const error = 'PLUGIN_UPGRADE_REQUIRED: Bundled contract changed; run plugins upgrade ' + manifest.id + ' --bundled before loading this installation';
|
|
356
|
+
const changed = JSON.stringify(record.bundleUpgradeRequired) !== JSON.stringify(gate)
|
|
357
|
+
|| record.state !== state || record.error !== error;
|
|
358
|
+
record.bundleUpgradeRequired = gate;
|
|
359
|
+
record.state = state;
|
|
360
|
+
record.error = error;
|
|
361
|
+
if (changed)
|
|
362
|
+
await this.save();
|
|
363
|
+
continue;
|
|
364
|
+
}
|
|
365
|
+
const needsRepin = bundleContract(record.manifest) !== bundleContract(manifest)
|
|
366
|
+
|| record.manifest.entry !== manifest.entry || Boolean(record.bundleUpgradeRequired);
|
|
367
|
+
if (!needsRepin)
|
|
368
|
+
continue;
|
|
369
|
+
const driver = this.drivers.get(manifest.id);
|
|
370
|
+
this.drivers.delete(manifest.id);
|
|
371
|
+
this.dependencies.delete(manifest.id);
|
|
372
|
+
if (driver)
|
|
373
|
+
this.notifyCapabilitiesChanged();
|
|
374
|
+
await this.stopDriver(driver).catch(() => { });
|
|
375
|
+
// Version-only updates and package relocation keep the exact runtime/data contract.
|
|
376
|
+
let oldEntryMissing = false;
|
|
377
|
+
try {
|
|
378
|
+
await fs.access(record.manifest.entry);
|
|
379
|
+
}
|
|
380
|
+
catch (error) {
|
|
381
|
+
oldEntryMissing = error.code === 'ENOENT';
|
|
382
|
+
}
|
|
383
|
+
const recoveredMissingEntry = oldEntryMissing && record.state === 'faulted' && record.error?.startsWith('ENOENT:');
|
|
384
|
+
const hadUpgradeGate = Boolean(record.bundleUpgradeRequired);
|
|
385
|
+
record.manifest = structuredClone(manifest);
|
|
386
|
+
delete record.bundleUpgradeRequired;
|
|
387
|
+
if (driver || recoveredMissingEntry || hadUpgradeGate) {
|
|
388
|
+
record.state = record.enabled ? 'installed' : 'disabled';
|
|
389
|
+
delete record.error;
|
|
390
|
+
}
|
|
391
|
+
await this.save();
|
|
392
|
+
if (this.started && record.enabled)
|
|
393
|
+
await this.activate(manifest.id, new Set()).catch(() => { });
|
|
394
|
+
}
|
|
395
|
+
for (const id of [...this.drivers.keys()])
|
|
396
|
+
if (!this.dependenciesAvailable(id))
|
|
397
|
+
await this.markFault(id, new CapabilityError('CAPABILITY_DEPENDENCY_UNAVAILABLE', 'Bundled provider was replaced'));
|
|
398
|
+
});
|
|
399
|
+
}
|
|
400
|
+
candidates(name, version = '*', healthyOnly = true) {
|
|
401
|
+
const result = [];
|
|
402
|
+
for (const [id, record] of Object.entries(this.registry.plugins)) {
|
|
403
|
+
if (!record.installed || !record.enabled || record.state === 'faulted' || record.bundleUpgradeRequired || (healthyOnly && (!this.drivers.has(id) || record.state !== 'enabled' || !this.dependenciesAvailable(id))))
|
|
404
|
+
continue;
|
|
405
|
+
for (const capability of record.manifest.capabilities)
|
|
406
|
+
if (capability.name === name && compatible(capability.version, version))
|
|
407
|
+
result.push({ id, capability });
|
|
408
|
+
}
|
|
409
|
+
return result.sort((a, b) => (b.capability.priority ?? 0) - (a.capability.priority ?? 0) || a.id.localeCompare(b.id));
|
|
410
|
+
}
|
|
411
|
+
capabilities() {
|
|
412
|
+
const names = new Set();
|
|
413
|
+
for (const id of this.drivers.keys()) {
|
|
414
|
+
const record = this.registry.plugins[id];
|
|
415
|
+
if (record?.state !== 'enabled' || record.bundleUpgradeRequired || !this.dependenciesAvailable(id))
|
|
416
|
+
continue;
|
|
417
|
+
for (const cap of record.manifest.capabilities)
|
|
418
|
+
if (!['augmenter', 'observer'].includes(cap.role ?? 'provider'))
|
|
419
|
+
names.add(cap.name);
|
|
420
|
+
}
|
|
421
|
+
return [...names].sort();
|
|
422
|
+
}
|
|
423
|
+
advertisements() {
|
|
424
|
+
return this.capabilities().flatMap(name => this.candidates(name).filter(c => ['provider', 'fallback'].includes(c.capability.role ?? 'provider')).slice(0, 1).map(c => ({ ...c.capability })));
|
|
425
|
+
}
|
|
426
|
+
diagnostics() {
|
|
427
|
+
const names = new Set(Object.values(this.registry.plugins).flatMap(r => r.manifest.capabilities.map(c => c.name)));
|
|
428
|
+
return [...names].flatMap(name => {
|
|
429
|
+
const providers = this.candidates(name).filter(c => (c.capability.role ?? 'provider') === 'provider');
|
|
430
|
+
return providers.length > 1 && (providers[0].capability.priority ?? 0) === (providers[1].capability.priority ?? 0) ? [{ capability: name, code: 'PRIORITY_TIE_LEXICAL_ID', providers: providers.map(p => p.id) }] : [];
|
|
431
|
+
});
|
|
432
|
+
}
|
|
433
|
+
assertDependencyPath(id, visiting) {
|
|
434
|
+
if (visiting.has(id))
|
|
435
|
+
throw new CapabilityError('CAPABILITY_CYCLE', `Capability dependency cycle at ${id}`);
|
|
436
|
+
const next = new Set(visiting).add(id);
|
|
437
|
+
for (const dependency of this.dependencies.get(id)?.values() ?? [])
|
|
438
|
+
this.assertDependencyPath(dependency.providerId, next);
|
|
439
|
+
}
|
|
440
|
+
dependenciesAvailable(id, visiting = new Set()) {
|
|
441
|
+
const record = this.registry.plugins[id];
|
|
442
|
+
if (!record || record.bundleUpgradeRequired || visiting.has(id))
|
|
443
|
+
return false;
|
|
444
|
+
const next = new Set(visiting).add(id);
|
|
445
|
+
return (record.manifest.requires ?? []).every(requirement => {
|
|
446
|
+
const binding = this.dependencies.get(id)?.get(requirement.name);
|
|
447
|
+
return binding && binding.requiredVersion === requirement.version
|
|
448
|
+
&& this.drivers.get(binding.providerId) === binding.driver
|
|
449
|
+
&& this.registry.plugins[binding.providerId]?.state === 'enabled'
|
|
450
|
+
&& this.dependenciesAvailable(binding.providerId, next);
|
|
451
|
+
});
|
|
452
|
+
}
|
|
453
|
+
async activate(id, visiting, explicit = false) {
|
|
454
|
+
const record = this.registry.plugins[id];
|
|
455
|
+
if (!explicit && !this.mayAutoActivate(id, record))
|
|
456
|
+
throw new CapabilityError('PLUGIN_DISABLED', `Plugin ${id} is suppressed by the active startup policy`);
|
|
457
|
+
if (record?.bundleUpgradeRequired)
|
|
458
|
+
throw new CapabilityError('PLUGIN_UPGRADE_REQUIRED', 'Explicitly upgrade the changed bundled plugin before enabling it');
|
|
459
|
+
if (record?.state === 'faulted')
|
|
460
|
+
throw new CapabilityError('PLUGIN_FAULTED', record.error ?? 'Explicitly enable the faulted plugin before retrying it');
|
|
461
|
+
if (this.drivers.has(id)) {
|
|
462
|
+
this.assertDependencyPath(id, visiting);
|
|
463
|
+
return;
|
|
464
|
+
}
|
|
465
|
+
if (!record?.installed || !record.enabled)
|
|
466
|
+
throw new CapabilityError('PLUGIN_DISABLED', 'Plugin is not enabled');
|
|
467
|
+
if (visiting.has(id))
|
|
468
|
+
throw new CapabilityError('CAPABILITY_CYCLE', `Capability dependency cycle at ${id}`);
|
|
469
|
+
const next = new Set(visiting).add(id);
|
|
470
|
+
const bindings = new Map();
|
|
471
|
+
try {
|
|
472
|
+
for (const requirement of record.manifest.requires ?? []) {
|
|
473
|
+
const providers = this.candidates(requirement.name, requirement.version, false)
|
|
474
|
+
.filter(c => ['provider', 'fallback'].includes(c.capability.role ?? 'provider') && this.mayAutoActivate(c.id, this.registry.plugins[c.id]));
|
|
475
|
+
if (!providers.length)
|
|
476
|
+
throw new CapabilityError('CAPABILITY_DEPENDENCY_MISSING', `No compatible provider for ${requirement.name}`);
|
|
477
|
+
const provider = providers[0];
|
|
478
|
+
await this.activate(provider.id, next, false);
|
|
479
|
+
const providerDriver = this.drivers.get(provider.id);
|
|
480
|
+
if (!providerDriver || !this.dependenciesAvailable(provider.id))
|
|
481
|
+
throw new CapabilityError('CAPABILITY_DEPENDENCY_UNAVAILABLE', 'Required provider is unavailable');
|
|
482
|
+
bindings.set(requirement.name, { providerId: provider.id, driver: providerDriver, requiredVersion: requirement.version });
|
|
483
|
+
}
|
|
484
|
+
await this.stopOwnedDrivers(id);
|
|
485
|
+
await fs.access(record.manifest.entry);
|
|
486
|
+
await fs.mkdir(this.dataRoot(id), { recursive: true });
|
|
487
|
+
const driver = createPluginDriver(record.manifest, {
|
|
488
|
+
deviceId: this.options.deviceId, dataRoot: this.dataRoot(id), projectRoots: this.options.projectRoots ?? [], sourceRoots: this.options.sourceRoots ?? [], previousSchemaVersion: record.schemaVersion, settings: record.settings
|
|
489
|
+
}, error => { void this.serial(() => this.drivers.get(id) === driver ? this.markFault(id, error) : Promise.resolve()); }, async (name, input, options) => {
|
|
490
|
+
const binding = this.dependencies.get(id)?.get(name);
|
|
491
|
+
if (this.drivers.get(id) !== driver || !binding || binding.requiredVersion !== options.version
|
|
492
|
+
|| this.drivers.get(binding.providerId) !== binding.driver || !this.dependenciesAvailable(binding.providerId))
|
|
493
|
+
throw new CapabilityError('CAPABILITY_DEPENDENCY_UNAVAILABLE', 'Bound dependency provider is unavailable');
|
|
494
|
+
// Call the pinned provider directly; dependency services do not re-enter
|
|
495
|
+
// augmenters/observers or select a new edge during an active invocation.
|
|
496
|
+
return binding.driver.invoke(name, input, options.signal);
|
|
497
|
+
});
|
|
498
|
+
// Pin what this generation actually consumed, independent of later loads.
|
|
499
|
+
this.driverIntent.set(driver, structuredClone({ manifest: record.manifest, settings: record.settings, lifecycleToken: record.lifecycleToken }));
|
|
500
|
+
this.dependencies.set(id, bindings);
|
|
501
|
+
this.drivers.set(id, driver);
|
|
502
|
+
this.ownedDrivers.set(driver, id);
|
|
503
|
+
await driver.start();
|
|
504
|
+
record.state = 'enabled';
|
|
505
|
+
record.schemaVersion = record.manifest.dataSchemaVersion;
|
|
506
|
+
delete record.error;
|
|
507
|
+
try {
|
|
508
|
+
await this.save();
|
|
509
|
+
this.registrySavePending = false;
|
|
510
|
+
}
|
|
511
|
+
catch (error) {
|
|
512
|
+
// The child is already healthy. A metadata rename failure must not turn
|
|
513
|
+
// a running provider into a product fault. Preserve the in-memory state
|
|
514
|
+
// and retry persistence from refresh before loading older metadata.
|
|
515
|
+
this.registrySavePending = true;
|
|
516
|
+
this.notifyCapabilitiesChanged();
|
|
517
|
+
process.stderr.write(`[plugins] ${id} started; registry persistence will retry: ${error.message}\n`);
|
|
518
|
+
}
|
|
519
|
+
}
|
|
520
|
+
catch (error) {
|
|
521
|
+
await this.markFault(id, error);
|
|
522
|
+
throw error;
|
|
523
|
+
}
|
|
524
|
+
}
|
|
525
|
+
async stopDriver(driver) {
|
|
526
|
+
if (!driver)
|
|
527
|
+
return;
|
|
528
|
+
await driver.stop();
|
|
529
|
+
this.ownedDrivers.delete(driver);
|
|
530
|
+
}
|
|
531
|
+
async stopOwnedDrivers(id) {
|
|
532
|
+
await Promise.all([...this.ownedDrivers].filter(([, owner]) => owner === id).map(([driver]) => this.stopDriver(driver)));
|
|
533
|
+
}
|
|
534
|
+
mayAutoActivate(id, record) {
|
|
535
|
+
if (!this.startupSkipIds.has(id))
|
|
536
|
+
return true;
|
|
537
|
+
return Boolean(record?.enabled && (record.explicitlyEnabled === true || record.installationSource === 'manual'
|
|
538
|
+
|| (record.explicitlyEnabled === undefined && record.lifecycleToken)));
|
|
539
|
+
}
|
|
540
|
+
async markFault(id, error) {
|
|
541
|
+
const driver = this.drivers.get(id);
|
|
542
|
+
this.drivers.delete(id);
|
|
543
|
+
this.dependencies.delete(id);
|
|
544
|
+
if (driver)
|
|
545
|
+
this.notifyCapabilitiesChanged();
|
|
546
|
+
const record = this.registry.plugins[id];
|
|
547
|
+
if (record) {
|
|
548
|
+
const previous = this.pendingFaults.get(id)?.previous ?? structuredClone(record);
|
|
549
|
+
record.state = 'faulted';
|
|
550
|
+
record.error = `${error.code ?? 'PLUGIN_FAULT'}: ${error.message}`;
|
|
551
|
+
this.pendingFaults.set(id, { previous, fault: structuredClone(record) });
|
|
552
|
+
try {
|
|
553
|
+
await this.save();
|
|
554
|
+
}
|
|
555
|
+
catch {
|
|
556
|
+
this.registrySavePending = true;
|
|
557
|
+
}
|
|
558
|
+
}
|
|
559
|
+
await this.stopDriver(driver).catch(() => { });
|
|
560
|
+
// Dependency loss degrades only dependents, not ordinary Device tools.
|
|
561
|
+
for (const dependent of [...this.drivers.keys()]) {
|
|
562
|
+
if (!this.dependenciesAvailable(dependent))
|
|
563
|
+
await this.markFault(dependent, new CapabilityError('CAPABILITY_DEPENDENCY_UNAVAILABLE', `A required capability became unavailable`));
|
|
564
|
+
}
|
|
565
|
+
}
|
|
566
|
+
async start(options = {}) {
|
|
567
|
+
const startupSkipIds = new Set(options.skipIds ?? []);
|
|
568
|
+
await this.load();
|
|
569
|
+
this.startupSkipIds = startupSkipIds;
|
|
570
|
+
this.started = true;
|
|
571
|
+
for (const [id, record] of Object.entries(this.registry.plugins))
|
|
572
|
+
if (record.installed && record.enabled && record.state !== 'faulted' && this.mayAutoActivate(id, record))
|
|
573
|
+
await this.activate(id, new Set()).catch(() => { });
|
|
574
|
+
// Lifecycle changes from the CLI are atomically picked up without a second runtime.
|
|
575
|
+
this.refreshTimer = setInterval(() => { void this.serial(() => this.refresh()).catch(() => { }); }, 1000);
|
|
576
|
+
this.refreshTimer.unref();
|
|
577
|
+
}
|
|
578
|
+
async refresh() {
|
|
579
|
+
if (this.registrySavePending) {
|
|
580
|
+
try {
|
|
581
|
+
await this.save();
|
|
582
|
+
this.registrySavePending = false;
|
|
583
|
+
}
|
|
584
|
+
catch (error) {
|
|
585
|
+
if (error.code !== 'PLUGIN_REGISTRY_CHANGED')
|
|
586
|
+
return;
|
|
587
|
+
// A newer lifecycle revision owns intent. Do not overwrite it with the
|
|
588
|
+
// stale post-start snapshot or let persistence retries starve refresh.
|
|
589
|
+
this.registrySavePending = false;
|
|
590
|
+
}
|
|
591
|
+
}
|
|
592
|
+
await this.load();
|
|
593
|
+
if (this.pendingFaults.size) {
|
|
594
|
+
try {
|
|
595
|
+
await this.save();
|
|
596
|
+
this.registrySavePending = false;
|
|
597
|
+
}
|
|
598
|
+
catch {
|
|
599
|
+
this.registrySavePending = true;
|
|
600
|
+
}
|
|
601
|
+
}
|
|
602
|
+
if (this.refreshedRevision === this.registry.revision)
|
|
603
|
+
return;
|
|
604
|
+
for (const [id, driver] of [...this.drivers]) {
|
|
605
|
+
const current = this.registry.plugins[id];
|
|
606
|
+
if (!current?.installed || !current.enabled || current.state === 'faulted' || current.bundleUpgradeRequired
|
|
607
|
+
|| !sameValue(this.driverIntent.get(driver), { manifest: current.manifest, settings: current.settings, lifecycleToken: current.lifecycleToken })) {
|
|
608
|
+
this.drivers.delete(id);
|
|
609
|
+
this.dependencies.delete(id);
|
|
610
|
+
this.notifyCapabilitiesChanged();
|
|
611
|
+
await this.stopDriver(driver);
|
|
612
|
+
}
|
|
613
|
+
else
|
|
614
|
+
current.state = 'enabled';
|
|
615
|
+
}
|
|
616
|
+
for (const [id, current] of Object.entries(this.registry.plugins))
|
|
617
|
+
if (current.installed && current.enabled && current.state !== 'faulted' && !this.drivers.has(id)
|
|
618
|
+
&& this.mayAutoActivate(id, current))
|
|
619
|
+
await this.activate(id, new Set()).catch(() => { });
|
|
620
|
+
for (const id of [...this.drivers.keys()]) {
|
|
621
|
+
if (!this.dependenciesAvailable(id))
|
|
622
|
+
await this.markFault(id, new CapabilityError('CAPABILITY_DEPENDENCY_UNAVAILABLE', 'A provider was disabled by another local process'));
|
|
623
|
+
}
|
|
624
|
+
this.refreshedRevision = this.registry.revision;
|
|
625
|
+
this.notifyCapabilitiesChanged();
|
|
626
|
+
}
|
|
627
|
+
async enable(id) {
|
|
628
|
+
return this.serial(async () => { await this.load(); await this.enableInstalled(id); });
|
|
629
|
+
}
|
|
630
|
+
async enableInstalled(id) {
|
|
631
|
+
const record = this.registry.plugins[id];
|
|
632
|
+
if (!record?.installed)
|
|
633
|
+
throw new CapabilityError('PLUGIN_NOT_INSTALLED', 'Plugin is not installed');
|
|
634
|
+
if (record.bundleUpgradeRequired)
|
|
635
|
+
throw new CapabilityError('PLUGIN_UPGRADE_REQUIRED', 'Explicitly upgrade the changed bundled plugin before enabling it');
|
|
636
|
+
record.enabled = true;
|
|
637
|
+
record.explicitlyEnabled = true;
|
|
638
|
+
record.state = 'installed';
|
|
639
|
+
record.lifecycleToken = randomUUID();
|
|
640
|
+
delete record.error;
|
|
641
|
+
await this.save();
|
|
642
|
+
if (this.started)
|
|
643
|
+
await this.activate(id, new Set(), true);
|
|
644
|
+
}
|
|
645
|
+
async disable(id) {
|
|
646
|
+
return this.serial(async () => { await this.load(); const record = this.registry.plugins[id]; if (!record)
|
|
647
|
+
throw new CapabilityError('PLUGIN_NOT_FOUND', 'Unknown plugin'); const driver = this.drivers.get(id); this.drivers.delete(id); this.dependencies.delete(id); if (driver)
|
|
648
|
+
this.notifyCapabilitiesChanged(); await this.stopOwnedDrivers(id); record.enabled = false; record.state = 'disabled'; record.lifecycleToken = randomUUID(); delete record.error; await this.save(); for (const dependent of [...this.drivers.keys()]) {
|
|
649
|
+
if (!this.dependenciesAvailable(dependent))
|
|
650
|
+
await this.markFault(dependent, new CapabilityError('CAPABILITY_DEPENDENCY_UNAVAILABLE', 'Required provider disabled'));
|
|
651
|
+
} });
|
|
652
|
+
}
|
|
653
|
+
async uninstall(id) {
|
|
654
|
+
await this.disable(id);
|
|
655
|
+
return this.serial(async () => { await this.load(); const record = this.registry.plugins[id]; record.installed = false; record.state = 'uninstalled'; await this.save(); });
|
|
656
|
+
}
|
|
657
|
+
async purge(id) {
|
|
658
|
+
return this.serial(async () => { await this.load(); const record = this.registry.plugins[id]; if (record?.enabled || this.drivers.has(id))
|
|
659
|
+
throw new CapabilityError('PLUGIN_STILL_ENABLED', 'Disable plugin before explicitly purging its data'); await fs.rm(this.dataRoot(id), { recursive: true, force: true }); });
|
|
660
|
+
}
|
|
661
|
+
async configure(id, settings) {
|
|
662
|
+
return this.serial(async () => { await this.load(); const record = this.registry.plugins[id]; if (!record)
|
|
663
|
+
throw new CapabilityError('PLUGIN_NOT_FOUND', 'Unknown plugin'); if (this.drivers.has(id))
|
|
664
|
+
throw new CapabilityError('PLUGIN_STILL_ENABLED', 'Disable plugin before changing settings'); record.settings = structuredClone(settings); await this.save(); });
|
|
665
|
+
}
|
|
666
|
+
async upgrade(manifest, options = {}) {
|
|
667
|
+
return this.serial(async () => {
|
|
668
|
+
validateManifest(manifest);
|
|
669
|
+
await this.load();
|
|
670
|
+
const old = this.registry.plugins[manifest.id];
|
|
671
|
+
if (!old?.installed)
|
|
672
|
+
throw new CapabilityError('PLUGIN_NOT_INSTALLED', 'Install the plugin first');
|
|
673
|
+
const previous = structuredClone(old), next = structuredClone(old), token = randomUUID();
|
|
674
|
+
const [birth, namespace] = await Promise.all([processBirth(process.pid), processNamespace()]);
|
|
675
|
+
next.manifest = structuredClone(manifest);
|
|
676
|
+
next.installationSource = options.installationSource ?? old.installationSource;
|
|
677
|
+
next.state = 'installed';
|
|
678
|
+
delete next.error;
|
|
679
|
+
delete next.bundleUpgradeRequired;
|
|
680
|
+
const intent = { version: 1, id: manifest.id, token, pid: process.pid, startedAt: Date.now(), ...(birth ? { processBirth: birth } : {}), ...(namespace ? { namespace } : {}), phase: 'preparing',
|
|
681
|
+
candidate: `upgrade-candidate-${token}`, backup: `upgrade-backup-${token}`, failed: `upgrade-failed-${token}`, previous, next };
|
|
682
|
+
const paths = this.upgradePaths(intent), driver = this.drivers.get(manifest.id);
|
|
683
|
+
this.drivers.delete(manifest.id);
|
|
684
|
+
this.dependencies.delete(manifest.id);
|
|
685
|
+
if (driver)
|
|
686
|
+
this.notifyCapabilitiesChanged();
|
|
687
|
+
await this.stopOwnedDrivers(manifest.id);
|
|
688
|
+
await fs.mkdir(paths.data, { recursive: true });
|
|
689
|
+
activeUpgradeTokens.add(token);
|
|
690
|
+
try {
|
|
691
|
+
await this.writeUpgradeIntent(intent);
|
|
692
|
+
await fs.cp(paths.data, paths.candidate, { recursive: true, force: false, errorOnExist: true });
|
|
693
|
+
intent.phase = 'candidate-ready';
|
|
694
|
+
await this.writeUpgradeIntent(intent);
|
|
695
|
+
intent.phase = 'swapping';
|
|
696
|
+
await this.writeUpgradeIntent(intent);
|
|
697
|
+
await renameWithRetry(paths.data, paths.backup);
|
|
698
|
+
await renameWithRetry(paths.candidate, paths.data);
|
|
699
|
+
intent.phase = 'swapped';
|
|
700
|
+
await this.writeUpgradeIntent(intent);
|
|
701
|
+
this.registry.plugins[manifest.id] = structuredClone(next);
|
|
702
|
+
await this.save();
|
|
703
|
+
const current = this.registry.plugins[manifest.id], shouldEnable = previous.enabled;
|
|
704
|
+
current.enabled = true;
|
|
705
|
+
await this.activate(manifest.id, new Set());
|
|
706
|
+
if (!this.started || !shouldEnable) {
|
|
707
|
+
const validationDriver = this.drivers.get(manifest.id);
|
|
708
|
+
this.drivers.delete(manifest.id);
|
|
709
|
+
this.dependencies.delete(manifest.id);
|
|
710
|
+
if (validationDriver)
|
|
711
|
+
this.notifyCapabilitiesChanged();
|
|
712
|
+
await this.stopDriver(validationDriver);
|
|
713
|
+
current.enabled = shouldEnable;
|
|
714
|
+
current.state = shouldEnable ? 'installed' : 'disabled';
|
|
715
|
+
await this.save();
|
|
716
|
+
}
|
|
717
|
+
intent.next = structuredClone(this.registry.plugins[manifest.id]);
|
|
718
|
+
intent.phase = 'committed';
|
|
719
|
+
await this.writeUpgradeIntent(intent);
|
|
720
|
+
await fs.rm(paths.backup, { recursive: true, force: true });
|
|
721
|
+
await fs.rm(paths.failed, { recursive: true, force: true });
|
|
722
|
+
await fs.rm(paths.intentPath, { force: true });
|
|
723
|
+
}
|
|
724
|
+
catch (error) {
|
|
725
|
+
this.drivers.delete(manifest.id);
|
|
726
|
+
this.dependencies.delete(manifest.id);
|
|
727
|
+
await this.stopOwnedDrivers(manifest.id).catch(() => { });
|
|
728
|
+
activeUpgradeTokens.delete(token);
|
|
729
|
+
try {
|
|
730
|
+
if (await pathExists(paths.intentPath)) {
|
|
731
|
+
const saved = this.validateUpgradeIntent(JSON.parse(await fs.readFile(paths.intentPath, 'utf8')), manifest.id), committed = saved.phase === 'committed';
|
|
732
|
+
await this.recoverUpgradeIntent(saved);
|
|
733
|
+
if (committed)
|
|
734
|
+
return;
|
|
735
|
+
}
|
|
736
|
+
else
|
|
737
|
+
this.registry.plugins[manifest.id] = previous;
|
|
738
|
+
if (this.started && previous.enabled)
|
|
739
|
+
await this.activate(manifest.id, new Set()).catch(() => { });
|
|
740
|
+
}
|
|
741
|
+
catch (recoveryError) {
|
|
742
|
+
throw new CapabilityError('PLUGIN_UPGRADE_RECOVERY_REQUIRED', `Upgrade failed and recovery requires inspection: ${String(recoveryError.message ?? recoveryError)}`);
|
|
743
|
+
}
|
|
744
|
+
throw error;
|
|
745
|
+
}
|
|
746
|
+
finally {
|
|
747
|
+
activeUpgradeTokens.delete(token);
|
|
748
|
+
for (const dependent of [...this.drivers.keys()]) {
|
|
749
|
+
if (!this.dependenciesAvailable(dependent))
|
|
750
|
+
await this.markFault(dependent, new CapabilityError('CAPABILITY_DEPENDENCY_UNAVAILABLE', 'Pinned provider changed during upgrade'));
|
|
751
|
+
}
|
|
752
|
+
}
|
|
753
|
+
});
|
|
754
|
+
}
|
|
755
|
+
unavailableCapability(name) {
|
|
756
|
+
const records = Object.values(this.registry.plugins).filter(record => record.manifest.capabilities.some(capability => capability.name === name))
|
|
757
|
+
.sort((a, b) => (Math.max(...b.manifest.capabilities.filter(c => c.name === name).map(c => c.priority ?? 0)) -
|
|
758
|
+
Math.max(...a.manifest.capabilities.filter(c => c.name === name).map(c => c.priority ?? 0))));
|
|
759
|
+
const record = records[0];
|
|
760
|
+
if (!record)
|
|
761
|
+
return new CapabilityError('CAPABILITY_UNAVAILABLE', `No compatible provider for ${name}`);
|
|
762
|
+
if (!record.installed || record.state === 'uninstalled')
|
|
763
|
+
return new CapabilityError('PLUGIN_UNINSTALLED', `Plugin ${record.manifest.id} is uninstalled`);
|
|
764
|
+
if (record.bundleUpgradeRequired)
|
|
765
|
+
return new CapabilityError('PLUGIN_UPGRADE_REQUIRED', record.error ?? `Plugin ${record.manifest.id} requires an explicit bundled upgrade`);
|
|
766
|
+
if (!record.enabled || record.state === 'disabled')
|
|
767
|
+
return new CapabilityError('PLUGIN_DISABLED', `Plugin ${record.manifest.id} is disabled`);
|
|
768
|
+
if (record.state === 'faulted')
|
|
769
|
+
return new CapabilityError('PLUGIN_FAULTED', record.error ?? `Plugin ${record.manifest.id} is faulted`);
|
|
770
|
+
return new CapabilityError('CAPABILITY_UNAVAILABLE', `No healthy compatible provider for ${name}`);
|
|
771
|
+
}
|
|
772
|
+
async invoke(name, input, options = {}) {
|
|
773
|
+
const candidates = this.candidates(name, options.version ?? '*');
|
|
774
|
+
const providers = candidates.filter(c => (c.capability.role ?? 'provider') === 'provider');
|
|
775
|
+
const fallback = candidates.filter(c => c.capability.role === 'fallback');
|
|
776
|
+
let result, succeeded = false, lastError;
|
|
777
|
+
for (const candidate of [...providers, ...fallback]) {
|
|
778
|
+
try {
|
|
779
|
+
result = await this.drivers.get(candidate.id).invoke(name, input, options.signal);
|
|
780
|
+
succeeded = true;
|
|
781
|
+
break;
|
|
782
|
+
}
|
|
783
|
+
catch (error) {
|
|
784
|
+
lastError = error;
|
|
785
|
+
if (options.signal?.aborted)
|
|
786
|
+
throw error;
|
|
787
|
+
}
|
|
788
|
+
}
|
|
789
|
+
if (!succeeded)
|
|
790
|
+
throw lastError ?? this.unavailableCapability(name);
|
|
791
|
+
for (const candidate of candidates.filter(c => c.capability.role === 'augmenter')) {
|
|
792
|
+
try {
|
|
793
|
+
result = await this.drivers.get(candidate.id)?.invoke(name, { input, result }, options.signal) ?? result;
|
|
794
|
+
}
|
|
795
|
+
catch { /* provider result remains usable */ }
|
|
796
|
+
}
|
|
797
|
+
for (const candidate of candidates.filter(c => c.capability.role === 'observer')) {
|
|
798
|
+
try {
|
|
799
|
+
await this.drivers.get(candidate.id)?.invoke(name, { input, result }, options.signal);
|
|
800
|
+
}
|
|
801
|
+
catch { /* observations cannot invalidate a provider result */ }
|
|
802
|
+
}
|
|
803
|
+
return result;
|
|
804
|
+
}
|
|
805
|
+
async close() {
|
|
806
|
+
if (this.refreshTimer)
|
|
807
|
+
clearInterval(this.refreshTimer);
|
|
808
|
+
this.started = false;
|
|
809
|
+
await this.operations;
|
|
810
|
+
this.drivers.clear();
|
|
811
|
+
this.dependencies.clear();
|
|
812
|
+
const released = await Promise.allSettled([...this.ownedDrivers.keys()].map(driver => this.stopDriver(driver)));
|
|
813
|
+
const failures = released.filter((result) => result.status === 'rejected');
|
|
814
|
+
if (failures.length)
|
|
815
|
+
throw new CapabilityError('PLUGIN_TERMINATION_FAILED', `Cannot prove ${failures.length} plugin driver(s) released their resources; runtime ownership must remain held`);
|
|
816
|
+
}
|
|
817
|
+
}
|