@inharness-ai/claude4spec 1.0.21 → 1.0.22
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/.claude/skills/c4s-brief-implementer/SKILL.md +120 -53
- package/CHANGELOG.md +13 -0
- package/dist/bin/c4s/commands/agent.d.ts +18 -0
- package/dist/bin/c4s/commands/agent.js +102 -0
- package/dist/bin/c4s/commands/agent.js.map +1 -0
- package/dist/bin/c4s/commands/ask.d.ts +9 -8
- package/dist/bin/c4s/commands/ask.js +23 -31
- package/dist/bin/c4s/commands/ask.js.map +1 -1
- package/dist/bin/c4s/commands/catalog.d.ts +1 -1
- package/dist/bin/c4s/commands/catalog.js +2 -2
- package/dist/bin/c4s/commands/catalog.js.map +1 -1
- package/dist/bin/c4s/commands/describe.d.ts +1 -1
- package/dist/bin/c4s/commands/describe.js +2 -2
- package/dist/bin/c4s/commands/describe.js.map +1 -1
- package/dist/bin/c4s/commands/detail.d.ts +1 -1
- package/dist/bin/c4s/commands/detail.js +2 -2
- package/dist/bin/c4s/commands/detail.js.map +1 -1
- package/dist/bin/c4s/commands/element-list.d.ts +1 -1
- package/dist/bin/c4s/commands/element-list.js +2 -2
- package/dist/bin/c4s/commands/element-list.js.map +1 -1
- package/dist/bin/c4s/commands/find-references.js +1 -1
- package/dist/bin/c4s/commands/find-references.js.map +1 -1
- package/dist/bin/c4s/commands/inline-mention.d.ts +1 -1
- package/dist/bin/c4s/commands/inline-mention.js +2 -2
- package/dist/bin/c4s/commands/inline-mention.js.map +1 -1
- package/dist/bin/c4s/commands/list-slugs.d.ts +1 -1
- package/dist/bin/c4s/commands/list-slugs.js +2 -2
- package/dist/bin/c4s/commands/list-slugs.js.map +1 -1
- package/dist/bin/c4s/commands/list-tags.d.ts +1 -1
- package/dist/bin/c4s/commands/list-tags.js +2 -2
- package/dist/bin/c4s/commands/list-tags.js.map +1 -1
- package/dist/bin/c4s/commands/plugins.d.ts +9 -0
- package/dist/bin/c4s/commands/plugins.js +81 -0
- package/dist/bin/c4s/commands/plugins.js.map +1 -0
- package/dist/bin/c4s/commands/resolve.d.ts +1 -1
- package/dist/bin/c4s/commands/resolve.js +2 -2
- package/dist/bin/c4s/commands/resolve.js.map +1 -1
- package/dist/bin/c4s/commands/single-element.d.ts +1 -1
- package/dist/bin/c4s/commands/single-element.js +2 -2
- package/dist/bin/c4s/commands/single-element.js.map +1 -1
- package/dist/bin/c4s/commands/tagged-list-mixed.d.ts +1 -1
- package/dist/bin/c4s/commands/tagged-list-mixed.js +2 -2
- package/dist/bin/c4s/commands/tagged-list-mixed.js.map +1 -1
- package/dist/bin/c4s/commands/tagged-list.d.ts +1 -1
- package/dist/bin/c4s/commands/tagged-list.js +2 -2
- package/dist/bin/c4s/commands/tagged-list.js.map +1 -1
- package/dist/bin/c4s/context.d.ts +3 -3
- package/dist/bin/c4s/context.js +7 -4
- package/dist/bin/c4s/context.js.map +1 -1
- package/dist/bin/c4s/errors.d.ts +1 -1
- package/dist/bin/c4s/errors.js.map +1 -1
- package/dist/bin/c4s-mcp.js +6 -2
- package/dist/bin/c4s-mcp.js.map +1 -1
- package/dist/bin/c4s.js +19 -4
- package/dist/bin/c4s.js.map +1 -1
- package/dist/bin/gitignore.js +7 -11
- package/dist/bin/gitignore.js.map +1 -1
- package/dist/client/assets/{arc-Coa9fbDO.js → arc-C9oTB3b1.js} +1 -1
- package/dist/client/assets/{architectureDiagram-3BPJPVTR-VJZcRMEN.js → architectureDiagram-3BPJPVTR-Bno5ViqK.js} +1 -1
- package/dist/client/assets/{blockDiagram-GPEHLZMM-C7dU2lW1.js → blockDiagram-GPEHLZMM-CJYBoeWQ.js} +1 -1
- package/dist/client/assets/{c4Diagram-AAUBKEIU-CG_BBm_5.js → c4Diagram-AAUBKEIU-DaaqWE6s.js} +1 -1
- package/dist/client/assets/channel-Cei_E79M.js +1 -0
- package/dist/client/assets/{chunk-2J33WTMH-BnS95YpK.js → chunk-2J33WTMH-P_I0OPMy.js} +1 -1
- package/dist/client/assets/{chunk-4BX2VUAB-COr8w3X3.js → chunk-4BX2VUAB-CCySMlh5.js} +1 -1
- package/dist/client/assets/{chunk-55IACEB6-CxrIbZhB.js → chunk-55IACEB6-CEUyqKGF.js} +1 -1
- package/dist/client/assets/{chunk-727SXJPM-DRchPPRe.js → chunk-727SXJPM-CoTSYOVz.js} +1 -1
- package/dist/client/assets/{chunk-AQP2D5EJ-_SfN7mED.js → chunk-AQP2D5EJ-DB0nMxPr.js} +1 -1
- package/dist/client/assets/{chunk-FMBD7UC4-p2tIiP1D.js → chunk-FMBD7UC4-PG3m6-_a.js} +1 -1
- package/dist/client/assets/{chunk-ND2GUHAM-B1mbl2hP.js → chunk-ND2GUHAM-CBNk6IaV.js} +1 -1
- package/dist/client/assets/{chunk-QZHKN3VN-DBUpeqvS.js → chunk-QZHKN3VN-CLFa0WvN.js} +1 -1
- package/dist/client/assets/classDiagram-4FO5ZUOK-DUe_fAto.js +1 -0
- package/dist/client/assets/classDiagram-v2-Q7XG4LA2-DUe_fAto.js +1 -0
- package/dist/client/assets/{cose-bilkent-S5V4N54A-BBsY5aKD.js → cose-bilkent-S5V4N54A-BDwDDxHN.js} +1 -1
- package/dist/client/assets/{dagre-BM42HDAG-b-vu4RC9.js → dagre-BM42HDAG-DR-1e8N5.js} +1 -1
- package/dist/client/assets/{diagram-2AECGRRQ-j-DW9qSf.js → diagram-2AECGRRQ-C-tHgBWt.js} +1 -1
- package/dist/client/assets/{diagram-5GNKFQAL-DHX_WYWQ.js → diagram-5GNKFQAL-uLmoKaJL.js} +1 -1
- package/dist/client/assets/{diagram-KO2AKTUF-DxdQok_M.js → diagram-KO2AKTUF-8Fd-xuOy.js} +1 -1
- package/dist/client/assets/{diagram-LMA3HP47-BbTIX5c1.js → diagram-LMA3HP47-C1gniWsn.js} +1 -1
- package/dist/client/assets/{diagram-OG6HWLK6-BQ9ODAlH.js → diagram-OG6HWLK6-BMWjZbeC.js} +1 -1
- package/dist/client/assets/{erDiagram-TEJ5UH35-6lwAL1y9.js → erDiagram-TEJ5UH35-CIUO8hrR.js} +1 -1
- package/dist/client/assets/{flowDiagram-I6XJVG4X-ioSHb5-t.js → flowDiagram-I6XJVG4X-Ccw5YEf3.js} +1 -1
- package/dist/client/assets/{ganttDiagram-6RSMTGT7-CXFQp9Nj.js → ganttDiagram-6RSMTGT7-BAvlf8cY.js} +1 -1
- package/dist/client/assets/{gitGraphDiagram-PVQCEYII-C1pVD_NQ.js → gitGraphDiagram-PVQCEYII-ChCkdYar.js} +1 -1
- package/dist/client/assets/{index-JaAxQJXh.js → index-BotCWOKC.js} +1 -1
- package/dist/client/assets/index-CtdzGyqj.js +805 -0
- package/dist/client/assets/{index-hz5T_W2e.js → index-DtpKHGD1.js} +1 -1
- package/dist/client/assets/{infoDiagram-5YYISTIA-C2Guev-g.js → infoDiagram-5YYISTIA-CZMGwNyB.js} +1 -1
- package/dist/client/assets/{ishikawaDiagram-YF4QCWOH-D3v8VBb2.js → ishikawaDiagram-YF4QCWOH-CubjZGk6.js} +1 -1
- package/dist/client/assets/{journeyDiagram-JHISSGLW-DX1AW_Ap.js → journeyDiagram-JHISSGLW-DzwOyVzB.js} +1 -1
- package/dist/client/assets/{kanban-definition-UN3LZRKU-CzXThvq2.js → kanban-definition-UN3LZRKU-CXznoTY9.js} +1 -1
- package/dist/client/assets/{linear-CVw4gl5i.js → linear-EDwTFRrU.js} +1 -1
- package/dist/client/assets/{mermaid.core-C29eoXRj.js → mermaid.core-Lae2097R.js} +4 -4
- package/dist/client/assets/{mindmap-definition-RKZ34NQL-DEEGVQVA.js → mindmap-definition-RKZ34NQL-DFjIW41g.js} +1 -1
- package/dist/client/assets/{pieDiagram-4H26LBE5-Dme-VM6m.js → pieDiagram-4H26LBE5-CZDLR1jd.js} +1 -1
- package/dist/client/assets/{quadrantDiagram-W4KKPZXB-CURFNo24.js → quadrantDiagram-W4KKPZXB-CTGDzbqp.js} +1 -1
- package/dist/client/assets/{requirementDiagram-4Y6WPE33-DxMZWXME.js → requirementDiagram-4Y6WPE33-CeH0NbMv.js} +1 -1
- package/dist/client/assets/{sankeyDiagram-5OEKKPKP-BW64D4_P.js → sankeyDiagram-5OEKKPKP-B6ZTjurV.js} +1 -1
- package/dist/client/assets/{sequenceDiagram-3UESZ5HK-BATRPm8Q.js → sequenceDiagram-3UESZ5HK-DcTRztVx.js} +1 -1
- package/dist/client/assets/{stateDiagram-AJRCARHV-pgHVMnO3.js → stateDiagram-AJRCARHV-nUgRVZSA.js} +1 -1
- package/dist/client/assets/stateDiagram-v2-BHNVJYJU-D9pJuQbI.js +1 -0
- package/dist/client/assets/{timeline-definition-PNZ67QCA-qBxJZPWd.js → timeline-definition-PNZ67QCA-CXm5n4rV.js} +1 -1
- package/dist/client/assets/{vennDiagram-CIIHVFJN-BZJK9hqJ.js → vennDiagram-CIIHVFJN-EmNJBYNd.js} +1 -1
- package/dist/client/assets/{wardley-L42UT6IY-BobtgEXg.js → wardley-L42UT6IY-MLrlp8H-.js} +1 -1
- package/dist/client/assets/{wardleyDiagram-YWT4CUSO-DuWfX_-P.js → wardleyDiagram-YWT4CUSO-5QoES25n.js} +1 -1
- package/dist/client/assets/{xychartDiagram-2RQKCTM6-DCw1iURo.js → xychartDiagram-2RQKCTM6-CbDEQbY0.js} +1 -1
- package/dist/client/index.html +1 -1
- package/dist/core/agent/run-agent.d.ts +61 -0
- package/dist/core/agent/run-agent.js +212 -0
- package/dist/core/agent/run-agent.js.map +1 -0
- package/dist/core/ask/run-ask.d.ts +1 -1
- package/dist/core/ask/run-ask.js +14 -5
- package/dist/core/ask/run-ask.js.map +1 -1
- package/dist/core/workspace/resolve.d.ts +2 -0
- package/dist/core/workspace/resolve.js +2 -1
- package/dist/core/workspace/resolve.js.map +1 -1
- package/dist/plugin-types/ambient.d.ts +30 -0
- package/dist/plugin-types/plugin-runtime.d.ts +172 -0
- package/dist/plugin-types/ui.d.ts +154 -0
- package/dist/server/config.d.ts +12 -0
- package/dist/server/config.js +26 -0
- package/dist/server/config.js.map +1 -1
- package/dist/server/core/plugin-host/__fixtures__/plugins.d.ts +14 -0
- package/dist/server/core/plugin-host/__fixtures__/plugins.js +56 -0
- package/dist/server/core/plugin-host/__fixtures__/plugins.js.map +1 -0
- package/dist/server/core/plugin-host/base-packages.d.ts +14 -0
- package/dist/server/core/plugin-host/base-packages.js +26 -0
- package/dist/server/core/plugin-host/base-packages.js.map +1 -0
- package/dist/server/core/plugin-host/cache-bust.d.ts +10 -0
- package/dist/server/core/plugin-host/cache-bust.js +21 -0
- package/dist/server/core/plugin-host/cache-bust.js.map +1 -0
- package/dist/server/core/plugin-host/cli-engine.d.ts +7 -2
- package/dist/server/core/plugin-host/cli-engine.js +11 -3
- package/dist/server/core/plugin-host/cli-engine.js.map +1 -1
- package/dist/server/core/plugin-host/cli-plugins.d.ts +23 -0
- package/dist/server/core/plugin-host/cli-plugins.js +49 -0
- package/dist/server/core/plugin-host/cli-plugins.js.map +1 -0
- package/dist/server/core/plugin-host/cross-cutting.d.ts +41 -4
- package/dist/server/core/plugin-host/cross-cutting.js +52 -4
- package/dist/server/core/plugin-host/cross-cutting.js.map +1 -1
- package/dist/server/core/plugin-host/frontend-assets.d.ts +100 -0
- package/dist/server/core/plugin-host/frontend-assets.js +262 -0
- package/dist/server/core/plugin-host/frontend-assets.js.map +1 -0
- package/dist/server/core/plugin-host/frontend-manifest.d.ts +33 -0
- package/dist/server/core/plugin-host/frontend-manifest.js +52 -0
- package/dist/server/core/plugin-host/frontend-manifest.js.map +1 -0
- package/dist/server/core/plugin-host/loader.d.ts +107 -0
- package/dist/server/core/plugin-host/loader.js +196 -0
- package/dist/server/core/plugin-host/loader.js.map +1 -0
- package/dist/server/core/plugin-host/manifest-adapter.d.ts +30 -0
- package/dist/server/core/plugin-host/manifest-adapter.js +119 -0
- package/dist/server/core/plugin-host/manifest-adapter.js.map +1 -0
- package/dist/server/core/plugin-host/overlay-loader.d.ts +60 -0
- package/dist/server/core/plugin-host/overlay-loader.js +243 -0
- package/dist/server/core/plugin-host/overlay-loader.js.map +1 -0
- package/dist/server/core/plugin-host/plugin-watcher.d.ts +41 -0
- package/dist/server/core/plugin-host/plugin-watcher.js +95 -0
- package/dist/server/core/plugin-host/plugin-watcher.js.map +1 -0
- package/dist/server/core/plugin-host/project-host.d.ts +8 -2
- package/dist/server/core/plugin-host/project-host.js +56 -6
- package/dist/server/core/plugin-host/project-host.js.map +1 -1
- package/dist/server/core/plugin-host/registry.d.ts +26 -2
- package/dist/server/core/plugin-host/registry.js +107 -2
- package/dist/server/core/plugin-host/registry.js.map +1 -1
- package/dist/server/core/plugin-host/runtime-shims.d.ts +26 -0
- package/dist/server/core/plugin-host/runtime-shims.js +96 -0
- package/dist/server/core/plugin-host/runtime-shims.js.map +1 -0
- package/dist/server/core/plugin-host/types.d.ts +105 -3
- package/dist/server/db/migrations/042_chat_thread_drop_context_type_check.sql +61 -0
- package/dist/server/external-skills/brief-implementer-template.d.ts +1 -1
- package/dist/server/external-skills/brief-implementer-template.js +5 -2
- package/dist/server/external-skills/brief-implementer-template.js.map +1 -1
- package/dist/server/external-skills/spec-reader-template.d.ts +1 -1
- package/dist/server/external-skills/spec-reader-template.js +14 -0
- package/dist/server/external-skills/spec-reader-template.js.map +1 -1
- package/dist/server/index.js +82 -1
- package/dist/server/index.js.map +1 -1
- package/dist/server/mcp/c4s-tools.d.ts +6 -1
- package/dist/server/mcp/c4s-tools.js +18 -13
- package/dist/server/mcp/c4s-tools.js.map +1 -1
- package/dist/server/plugin-runtime/index.d.ts +17 -0
- package/dist/server/plugin-runtime/index.js +16 -0
- package/dist/server/plugin-runtime/index.js.map +1 -0
- package/dist/server/plugin-runtime/ui.d.ts +15 -0
- package/dist/server/plugin-runtime/ui.js +15 -0
- package/dist/server/plugin-runtime/ui.js.map +1 -0
- package/dist/server/routes/agent-turn.d.ts +8 -1
- package/dist/server/routes/agent-turn.js +67 -39
- package/dist/server/routes/agent-turn.js.map +1 -1
- package/dist/server/routes/config.d.ts +8 -0
- package/dist/server/routes/config.js +41 -3
- package/dist/server/routes/config.js.map +1 -1
- package/dist/server/routes/plugins.d.ts +43 -0
- package/dist/server/routes/plugins.js +93 -0
- package/dist/server/routes/plugins.js.map +1 -0
- package/dist/server/routes/threads.js +21 -3
- package/dist/server/routes/threads.js.map +1 -1
- package/dist/server/serialization/registerAll.d.ts +4 -0
- package/dist/server/serialization/registerAll.js +4 -2
- package/dist/server/serialization/registerAll.js.map +1 -1
- package/dist/server/services/chat-context.d.ts +34 -0
- package/dist/server/services/chat-context.js +58 -15
- package/dist/server/services/chat-context.js.map +1 -1
- package/dist/server/services/chat.d.ts +6 -0
- package/dist/server/services/chat.js +18 -2
- package/dist/server/services/chat.js.map +1 -1
- package/dist/server/services/entity-writer.d.ts +2 -3
- package/dist/server/services/entity-writer.js.map +1 -1
- package/dist/server/services/skill-registry.d.ts +31 -2
- package/dist/server/services/skill-registry.js +73 -1
- package/dist/server/services/skill-registry.js.map +1 -1
- package/dist/server/workspace/context-cache.d.ts +9 -0
- package/dist/server/workspace/context-cache.js +14 -0
- package/dist/server/workspace/context-cache.js.map +1 -1
- package/dist/server/workspace/project-context.d.ts +3 -0
- package/dist/server/workspace/project-context.js +106 -4
- package/dist/server/workspace/project-context.js.map +1 -1
- package/dist/server/workspace/registry.d.ts +28 -1
- package/dist/server/workspace/registry.js +57 -1
- package/dist/server/workspace/registry.js.map +1 -1
- package/dist/server/workspace/types.d.ts +15 -0
- package/dist/shared/entities.d.ts +4 -1
- package/dist/shared/entities.js.map +1 -1
- package/dist/shared/plugin-host/frontend-manifest.d.ts +56 -0
- package/dist/shared/plugin-host/frontend-manifest.js +89 -0
- package/dist/shared/plugin-host/frontend-manifest.js.map +1 -0
- package/dist/shared/plugin-host/host-api.d.ts +60 -0
- package/dist/shared/plugin-host/host-api.js +67 -0
- package/dist/shared/plugin-host/host-api.js.map +1 -0
- package/dist/shared/plugin-host/manifest.d.ts +194 -0
- package/dist/shared/plugin-host/manifest.js +52 -0
- package/dist/shared/plugin-host/manifest.js.map +1 -0
- package/dist/shared/plugin-host/ui-kit-surface.d.ts +22 -0
- package/dist/shared/plugin-host/ui-kit-surface.js +25 -0
- package/dist/shared/plugin-host/ui-kit-surface.js.map +1 -0
- package/dist/shared/types.d.ts +5 -0
- package/package.json +18 -3
- package/dist/client/assets/channel-D5gzBvo7.js +0 -1
- package/dist/client/assets/classDiagram-4FO5ZUOK-CT-amIxa.js +0 -1
- package/dist/client/assets/classDiagram-v2-Q7XG4LA2-CT-amIxa.js +0 -1
- package/dist/client/assets/index-DyAnw7YN.js +0 -798
- package/dist/client/assets/stateDiagram-v2-BHNVJYJU-szm_LIuD.js +0 -1
|
@@ -1,14 +1,62 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Cross-cutting routes owned by the plugin host (M13)
|
|
3
|
-
*
|
|
4
|
-
*
|
|
2
|
+
* Cross-cutting routes owned by the plugin host (M13), mounted on the PER-PROJECT
|
|
3
|
+
* router (so paths resolve under `/api/projects/:id`). Provides overlay-aware
|
|
4
|
+
* diagnostics + the machine-local trust gate:
|
|
5
|
+
*
|
|
6
|
+
* GET /_meta/entities → activation partition (active/inactive/unknown)
|
|
7
|
+
* GET /_meta/plugins → base ∪ overlay package diagnostics + shadow report
|
|
8
|
+
* POST /trust-plugins → persist trustProjectPlugins, trigger context rebuild
|
|
5
9
|
*/
|
|
6
10
|
import { Router } from 'express';
|
|
7
|
-
|
|
11
|
+
import { HOST_API_VERSION } from '../../../shared/plugin-host/manifest.js';
|
|
12
|
+
export function pluginHostRouter(deps) {
|
|
13
|
+
const { host, registry, workspace, projectId, basePackages, overlayRecords, localPluginsPresent, trust, onContextConfigChanged, } = deps;
|
|
8
14
|
const router = Router();
|
|
9
15
|
router.get('/_meta/entities', (_req, res) => {
|
|
10
16
|
res.json(host.partition());
|
|
11
17
|
});
|
|
18
|
+
// M33: settings/commands of all loaded+trusted plugins in the
|
|
19
|
+
// effective pool — axis B, deliberately NOT filtered by `config.entities`
|
|
20
|
+
// (so a plugin's Settings section + slash-commands survive deactivation of
|
|
21
|
+
// its entity types). The Settings panel renders one section per plugin under
|
|
22
|
+
// `config.plugins[<name>]`; the editor registers each command as a slash
|
|
23
|
+
// extension.
|
|
24
|
+
router.get('/_meta/plugin-settings', (_req, res) => {
|
|
25
|
+
res.json({ sections: host.listSettings() });
|
|
26
|
+
});
|
|
27
|
+
router.get('/_meta/plugin-commands', (_req, res) => {
|
|
28
|
+
res.json({ commands: host.listCommands() });
|
|
29
|
+
});
|
|
30
|
+
router.get('/_meta/plugins', (_req, res) => {
|
|
31
|
+
const baseOriginForType = (type) => basePackages.find((p) => p.contributedTypes?.includes(type))?.package ?? '@c4s/builtin';
|
|
32
|
+
const shadowed = host.shadowReport().map((s) => ({
|
|
33
|
+
type: s.type,
|
|
34
|
+
overlayOrigin: s.overlayOrigin,
|
|
35
|
+
baseOrigin: baseOriginForType(s.type),
|
|
36
|
+
}));
|
|
37
|
+
const response = {
|
|
38
|
+
hostApiVersion: HOST_API_VERSION,
|
|
39
|
+
localPluginsPresent,
|
|
40
|
+
trust,
|
|
41
|
+
packages: [...basePackages, ...overlayRecords],
|
|
42
|
+
shadowed,
|
|
43
|
+
};
|
|
44
|
+
res.json(response);
|
|
45
|
+
});
|
|
46
|
+
// M33 trust gate. Persists the decision per (workspace × project) in
|
|
47
|
+
// `~/.claude4spec/` (never the repo) and rebuilds the context so the overlay
|
|
48
|
+
// (un)loads without a process restart. The client refetches activation after.
|
|
49
|
+
router.post('/trust-plugins', (req, res) => {
|
|
50
|
+
const value = req.body?.trust;
|
|
51
|
+
if (typeof value !== 'boolean') {
|
|
52
|
+
return res
|
|
53
|
+
.status(400)
|
|
54
|
+
.json({ error: { code: 'VALIDATION', message: 'trust must be a boolean' } });
|
|
55
|
+
}
|
|
56
|
+
registry.setProjectTrust(workspace, projectId, value);
|
|
57
|
+
onContextConfigChanged?.();
|
|
58
|
+
res.json({ trust: value });
|
|
59
|
+
});
|
|
12
60
|
return router;
|
|
13
61
|
}
|
|
14
62
|
//# sourceMappingURL=cross-cutting.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"cross-cutting.js","sourceRoot":"","sources":["../../../../src/server/core/plugin-host/cross-cutting.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"cross-cutting.js","sourceRoot":"","sources":["../../../../src/server/core/plugin-host/cross-cutting.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* M33 — plugin frontend assets, two tiers.
|
|
3
|
+
*
|
|
4
|
+
* The serving layer for "Option B" plugin frontends. This express-free module
|
|
5
|
+
* owns the single path/trust convention shared by the serving route
|
|
6
|
+
* (`routes/plugins.ts`) and the manifest builder (`frontend-manifest.ts`), so
|
|
7
|
+
* the two cannot diverge.
|
|
8
|
+
*
|
|
9
|
+
* • Overlay tier (phase 2): a TRUSTED project ships a precompiled bundle at
|
|
10
|
+
* `<cwd>/.claude4spec/plugins/<name>/dist/frontend.js` (+ optional
|
|
11
|
+
* `frontend.css`). Gated by `trustProjectPlugins` (per workspace × project,
|
|
12
|
+
* machine-local) — the CALLER resolves the gate and passes `trusted`; an
|
|
13
|
+
* untrusted/undecided project emits nothing, exactly like the overlay loader
|
|
14
|
+
* refuses to run untrusted project code.
|
|
15
|
+
*
|
|
16
|
+
* • Workspace tier (phase 3): a workspace/npm plugin (declared in
|
|
17
|
+
* `~/.claude4spec/workspaces.json`, installed under `node_modules`) serves
|
|
18
|
+
* its own `dist/frontend.js` (+ optional `dist/frontend.css`) UNGATED — a
|
|
19
|
+
* consciously-installed base package is trusted by virtue of npm install,
|
|
20
|
+
* parity with the backend base tier. Traversal-guarded by the passed
|
|
21
|
+
* workspace package allowlist (the same list `loadWorkspacePlugins` uses).
|
|
22
|
+
*/
|
|
23
|
+
/**
|
|
24
|
+
* Walk up from `startDir` to the NEAREST ancestor that owns a readable
|
|
25
|
+
* `package.json` — the package root — or `null` if none within 12 levels. The
|
|
26
|
+
* caller has already proved the package's identity (`import.meta.resolve`
|
|
27
|
+
* resolved a DECLARED subpath of `packageName`), so we deliberately do NOT gate
|
|
28
|
+
* on the manifest `name`: a workspace plugin whose install/dir name differs from
|
|
29
|
+
* its real (e.g. scoped) `package.json` `name` must still resolve to its root.
|
|
30
|
+
* The 12-level bound is a cheap runaway guard for a pathological deep tree.
|
|
31
|
+
*/
|
|
32
|
+
export declare function nearestPackageRoot(startDir: string): string | null;
|
|
33
|
+
/** The two precompiled assets a plugin frontend bundle may ship. */
|
|
34
|
+
export type FrontendAssetFile = 'frontend.js' | 'frontend.css';
|
|
35
|
+
/**
|
|
36
|
+
* Resolve the absolute path to stream for `GET /api/plugins/<name>/<file>`, or
|
|
37
|
+
* `null` when the request must 404. Returns null when:
|
|
38
|
+
* - the trust gate is off (`!trusted`) — never emit project-committed code,
|
|
39
|
+
* - `name` is not an actual overlay package — also guards path traversal, as
|
|
40
|
+
* only directory names enumerated under `.claude4spec/plugins/` are eligible,
|
|
41
|
+
* - the `dist/<file>` does not exist — a present, trusted project never
|
|
42
|
+
* fabricates a bundle.
|
|
43
|
+
*/
|
|
44
|
+
export declare function resolveFrontendAsset(cwd: string, trusted: boolean, name: string, file: FrontendAssetFile): string | null;
|
|
45
|
+
/** One plugin (either tier) with a built frontend bundle. */
|
|
46
|
+
export interface FrontendBundle {
|
|
47
|
+
/** The manifest `name`: overlay = dir under `.claude4spec/plugins/`; workspace = npm package name. */
|
|
48
|
+
name: string;
|
|
49
|
+
/** Version from the package's `package.json`, or `'0.0.0'` if unreadable. */
|
|
50
|
+
version: string;
|
|
51
|
+
/** True when the bundle also ships a precompiled `dist/frontend.css`. */
|
|
52
|
+
hasCss: boolean;
|
|
53
|
+
}
|
|
54
|
+
/**
|
|
55
|
+
* Project-local plugins that ship a built `dist/frontend.js`, for the frontend
|
|
56
|
+
* manifest. Pure FS read; the caller gates by trust before calling (an
|
|
57
|
+
* untrusted project advertises nothing). A package without a built bundle is
|
|
58
|
+
* simply omitted — the build is the implementer's responsibility, not the host's.
|
|
59
|
+
*/
|
|
60
|
+
export declare function enumerateFrontendBundles(cwd: string): FrontendBundle[];
|
|
61
|
+
/** Resolve an installed workspace plugin package's absolute root dir, or null. */
|
|
62
|
+
export type WorkspaceRootResolver = (packageName: string) => string | null;
|
|
63
|
+
/**
|
|
64
|
+
* Resolve the absolute path to stream for `GET /api/plugins/<name>/<file>` from a
|
|
65
|
+
* workspace/npm plugin, or `null` when the request must 404. UNGATED (a base
|
|
66
|
+
* install is trusted) — but `packageName` MUST be in `allowedPackages` (the
|
|
67
|
+
* resolved workspace plugin list), which both guards path traversal and bounds
|
|
68
|
+
* serving to actually-declared plugins. The JS path comes from the package's
|
|
69
|
+
* `exports['./frontend']` (fallback `dist/frontend.js`); CSS is its sibling
|
|
70
|
+
* `frontend.css`. Returns null when the package is unresolvable or the file is
|
|
71
|
+
* absent (a plugin without a built bundle simply serves nothing).
|
|
72
|
+
*/
|
|
73
|
+
export declare function resolveWorkspaceFrontendAsset(packageName: string, file: FrontendAssetFile, allowedPackages: readonly string[], resolveRoot?: WorkspaceRootResolver): string | null;
|
|
74
|
+
/**
|
|
75
|
+
* Workspace/npm plugins that ship a resolvable built `frontend` entry, for the
|
|
76
|
+
* frontend manifest. UNGATED (parity with the backend base tier). A package
|
|
77
|
+
* without a built bundle (or unresolvable) is simply omitted. `resolveRoot` is a
|
|
78
|
+
* test seam — production passes the default `import.meta.resolve`-based resolver.
|
|
79
|
+
*
|
|
80
|
+
* `packageNames` is the resolved workspace allowlist, so every name is trusted by
|
|
81
|
+
* construction (the traversal guard in `resolveWorkspaceFrontendAsset` exists for
|
|
82
|
+
* the user-supplied route `:name`). Each package is resolved + its `package.json`
|
|
83
|
+
* read exactly ONCE here (this runs per `GET /frontend-manifest`).
|
|
84
|
+
*/
|
|
85
|
+
export declare function enumerateWorkspaceFrontendBundles(packageNames: readonly string[], resolveRoot?: WorkspaceRootResolver): FrontendBundle[];
|
|
86
|
+
/** Content type for a served asset name, or `null` when the extension is not allowed. */
|
|
87
|
+
export declare function assetContentType(asset: string): string | null;
|
|
88
|
+
/**
|
|
89
|
+
* Overlay tier (trust-gated): resolve any allowlisted sibling of the project-local
|
|
90
|
+
* bundle at `<cwd>/.claude4spec/plugins/<name>/dist/<asset>`. Same gate + package
|
|
91
|
+
* guard as `resolveFrontendAsset`, generalized from the two fixed filenames.
|
|
92
|
+
*/
|
|
93
|
+
export declare function resolveOverlayAsset(cwd: string, trusted: boolean, name: string, asset: string): string | null;
|
|
94
|
+
/**
|
|
95
|
+
* Workspace tier (ungated, allowlist-bounded): resolve any allowlisted sibling of
|
|
96
|
+
* a workspace/npm plugin's resolved `frontend` entry (i.e. files in the same
|
|
97
|
+
* `dist/` dir). Generalizes `resolveWorkspaceFrontendAsset` from the two fixed
|
|
98
|
+
* filenames so code-split chunks (`dto-*.js`) and source maps resolve.
|
|
99
|
+
*/
|
|
100
|
+
export declare function resolveWorkspaceAsset(packageName: string, asset: string, allowedPackages: readonly string[], resolveRoot?: WorkspaceRootResolver): string | null;
|
|
@@ -0,0 +1,262 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* M33 — plugin frontend assets, two tiers.
|
|
3
|
+
*
|
|
4
|
+
* The serving layer for "Option B" plugin frontends. This express-free module
|
|
5
|
+
* owns the single path/trust convention shared by the serving route
|
|
6
|
+
* (`routes/plugins.ts`) and the manifest builder (`frontend-manifest.ts`), so
|
|
7
|
+
* the two cannot diverge.
|
|
8
|
+
*
|
|
9
|
+
* • Overlay tier (phase 2): a TRUSTED project ships a precompiled bundle at
|
|
10
|
+
* `<cwd>/.claude4spec/plugins/<name>/dist/frontend.js` (+ optional
|
|
11
|
+
* `frontend.css`). Gated by `trustProjectPlugins` (per workspace × project,
|
|
12
|
+
* machine-local) — the CALLER resolves the gate and passes `trusted`; an
|
|
13
|
+
* untrusted/undecided project emits nothing, exactly like the overlay loader
|
|
14
|
+
* refuses to run untrusted project code.
|
|
15
|
+
*
|
|
16
|
+
* • Workspace tier (phase 3): a workspace/npm plugin (declared in
|
|
17
|
+
* `~/.claude4spec/workspaces.json`, installed under `node_modules`) serves
|
|
18
|
+
* its own `dist/frontend.js` (+ optional `dist/frontend.css`) UNGATED — a
|
|
19
|
+
* consciously-installed base package is trusted by virtue of npm install,
|
|
20
|
+
* parity with the backend base tier. Traversal-guarded by the passed
|
|
21
|
+
* workspace package allowlist (the same list `loadWorkspacePlugins` uses).
|
|
22
|
+
*/
|
|
23
|
+
import fs from 'node:fs';
|
|
24
|
+
import path from 'node:path';
|
|
25
|
+
import { fileURLToPath } from 'node:url';
|
|
26
|
+
import { enumerateOverlayPackages, projectPluginsDir, resolveExportsEntry, } from './overlay-loader.js';
|
|
27
|
+
/** Read + parse a `package.json`, or `null` when missing/unreadable/invalid. */
|
|
28
|
+
function readPackageJson(pkgJsonPath) {
|
|
29
|
+
try {
|
|
30
|
+
return JSON.parse(fs.readFileSync(pkgJsonPath, 'utf8'));
|
|
31
|
+
}
|
|
32
|
+
catch {
|
|
33
|
+
return null;
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
/** A manifest's declared `version`, falling back to `'0.0.0'`. */
|
|
37
|
+
function packageVersion(pkg) {
|
|
38
|
+
return pkg && typeof pkg.version === 'string' ? pkg.version : '0.0.0';
|
|
39
|
+
}
|
|
40
|
+
/**
|
|
41
|
+
* Walk up from `startDir` to the NEAREST ancestor that owns a readable
|
|
42
|
+
* `package.json` — the package root — or `null` if none within 12 levels. The
|
|
43
|
+
* caller has already proved the package's identity (`import.meta.resolve`
|
|
44
|
+
* resolved a DECLARED subpath of `packageName`), so we deliberately do NOT gate
|
|
45
|
+
* on the manifest `name`: a workspace plugin whose install/dir name differs from
|
|
46
|
+
* its real (e.g. scoped) `package.json` `name` must still resolve to its root.
|
|
47
|
+
* The 12-level bound is a cheap runaway guard for a pathological deep tree.
|
|
48
|
+
*/
|
|
49
|
+
export function nearestPackageRoot(startDir) {
|
|
50
|
+
let dir = startDir;
|
|
51
|
+
for (let i = 0; i < 12; i++) {
|
|
52
|
+
if (readPackageJson(path.join(dir, 'package.json')))
|
|
53
|
+
return dir;
|
|
54
|
+
const parent = path.dirname(dir);
|
|
55
|
+
if (parent === dir)
|
|
56
|
+
break;
|
|
57
|
+
dir = parent;
|
|
58
|
+
}
|
|
59
|
+
return null;
|
|
60
|
+
}
|
|
61
|
+
/** Absolute path of a plugin's precompiled `dist/<file>`. */
|
|
62
|
+
function bundleAssetPath(cwd, name, file) {
|
|
63
|
+
return path.join(projectPluginsDir(cwd), name, 'dist', file);
|
|
64
|
+
}
|
|
65
|
+
/**
|
|
66
|
+
* Resolve the absolute path to stream for `GET /api/plugins/<name>/<file>`, or
|
|
67
|
+
* `null` when the request must 404. Returns null when:
|
|
68
|
+
* - the trust gate is off (`!trusted`) — never emit project-committed code,
|
|
69
|
+
* - `name` is not an actual overlay package — also guards path traversal, as
|
|
70
|
+
* only directory names enumerated under `.claude4spec/plugins/` are eligible,
|
|
71
|
+
* - the `dist/<file>` does not exist — a present, trusted project never
|
|
72
|
+
* fabricates a bundle.
|
|
73
|
+
*/
|
|
74
|
+
export function resolveFrontendAsset(cwd, trusted, name, file) {
|
|
75
|
+
if (!trusted)
|
|
76
|
+
return null;
|
|
77
|
+
if (!enumerateOverlayPackages(cwd).includes(name))
|
|
78
|
+
return null;
|
|
79
|
+
const abs = bundleAssetPath(cwd, name, file);
|
|
80
|
+
return fs.existsSync(abs) ? abs : null;
|
|
81
|
+
}
|
|
82
|
+
/** Read an overlay package's declared version, falling back to `'0.0.0'`. */
|
|
83
|
+
function readBundleVersion(cwd, name) {
|
|
84
|
+
return packageVersion(readPackageJson(path.join(projectPluginsDir(cwd), name, 'package.json')));
|
|
85
|
+
}
|
|
86
|
+
/**
|
|
87
|
+
* Project-local plugins that ship a built `dist/frontend.js`, for the frontend
|
|
88
|
+
* manifest. Pure FS read; the caller gates by trust before calling (an
|
|
89
|
+
* untrusted project advertises nothing). A package without a built bundle is
|
|
90
|
+
* simply omitted — the build is the implementer's responsibility, not the host's.
|
|
91
|
+
*/
|
|
92
|
+
export function enumerateFrontendBundles(cwd) {
|
|
93
|
+
const bundles = [];
|
|
94
|
+
for (const name of enumerateOverlayPackages(cwd)) {
|
|
95
|
+
if (!fs.existsSync(bundleAssetPath(cwd, name, 'frontend.js')))
|
|
96
|
+
continue;
|
|
97
|
+
bundles.push({
|
|
98
|
+
name,
|
|
99
|
+
version: readBundleVersion(cwd, name),
|
|
100
|
+
hasCss: fs.existsSync(bundleAssetPath(cwd, name, 'frontend.css')),
|
|
101
|
+
});
|
|
102
|
+
}
|
|
103
|
+
return bundles;
|
|
104
|
+
}
|
|
105
|
+
/**
|
|
106
|
+
* Default resolver. Two reasons `createRequire(...).resolve` is wrong here:
|
|
107
|
+
* 1. `require.resolve('<pkg>/package.json')` — a package with an `exports` map
|
|
108
|
+
* blocks undeclared subpaths like `./package.json`; and
|
|
109
|
+
* 2. `require.resolve` uses CJS conditions (`require`/`node`/`default`), but a
|
|
110
|
+
* modern plugin's `exports` is import-only (`{ types, import }`) ⇒ "No
|
|
111
|
+
* exports main defined".
|
|
112
|
+
* So resolve a DECLARED ESM subpath with `import.meta.resolve` (which uses the
|
|
113
|
+
* `import` condition) — that resolution alone PROVES identity — then walk up to
|
|
114
|
+
* the nearest directory owning a `package.json` (the package root) via
|
|
115
|
+
* `nearestPackageRoot`. We do NOT re-check that `package.json`'s `name` equals
|
|
116
|
+
* the requested id: a plugin installed under an unscoped dir name whose real
|
|
117
|
+
* `name` is scoped (`@scope/pkg`) would otherwise never match and silently lose
|
|
118
|
+
* its frontend. Try `./frontend` first (the only subpath we serve), then `.`.
|
|
119
|
+
*/
|
|
120
|
+
const defaultWorkspaceRoot = (packageName) => {
|
|
121
|
+
let entryUrl;
|
|
122
|
+
for (const spec of [`${packageName}/frontend`, packageName]) {
|
|
123
|
+
try {
|
|
124
|
+
entryUrl = import.meta.resolve(spec);
|
|
125
|
+
break;
|
|
126
|
+
}
|
|
127
|
+
catch {
|
|
128
|
+
/* try the next entry specifier */
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
if (!entryUrl)
|
|
132
|
+
return null;
|
|
133
|
+
return nearestPackageRoot(path.dirname(fileURLToPath(entryUrl)));
|
|
134
|
+
};
|
|
135
|
+
/**
|
|
136
|
+
* The package's declared `./frontend` import target (relative to its root), or
|
|
137
|
+
* the `dist/frontend.js` convention when `exports` does not declare one. Reuses
|
|
138
|
+
* the overlay loader's condition handling so the two tiers can't drift.
|
|
139
|
+
*/
|
|
140
|
+
function workspaceFrontendEntryRel(pkg) {
|
|
141
|
+
const sub = pkg?.exports?.['./frontend'];
|
|
142
|
+
return resolveExportsEntry(sub) ?? 'dist/frontend.js';
|
|
143
|
+
}
|
|
144
|
+
/** Absolute path of a workspace package's `frontend.js`, given its parsed manifest. */
|
|
145
|
+
function workspaceFrontendJs(root, pkg) {
|
|
146
|
+
return path.resolve(root, workspaceFrontendEntryRel(pkg));
|
|
147
|
+
}
|
|
148
|
+
/**
|
|
149
|
+
* Resolve the absolute path to stream for `GET /api/plugins/<name>/<file>` from a
|
|
150
|
+
* workspace/npm plugin, or `null` when the request must 404. UNGATED (a base
|
|
151
|
+
* install is trusted) — but `packageName` MUST be in `allowedPackages` (the
|
|
152
|
+
* resolved workspace plugin list), which both guards path traversal and bounds
|
|
153
|
+
* serving to actually-declared plugins. The JS path comes from the package's
|
|
154
|
+
* `exports['./frontend']` (fallback `dist/frontend.js`); CSS is its sibling
|
|
155
|
+
* `frontend.css`. Returns null when the package is unresolvable or the file is
|
|
156
|
+
* absent (a plugin without a built bundle simply serves nothing).
|
|
157
|
+
*/
|
|
158
|
+
export function resolveWorkspaceFrontendAsset(packageName, file, allowedPackages, resolveRoot = defaultWorkspaceRoot) {
|
|
159
|
+
if (!allowedPackages.includes(packageName))
|
|
160
|
+
return null;
|
|
161
|
+
const root = resolveRoot(packageName);
|
|
162
|
+
if (!root)
|
|
163
|
+
return null;
|
|
164
|
+
const frontendAbs = workspaceFrontendJs(root, readPackageJson(path.join(root, 'package.json')));
|
|
165
|
+
const abs = file === 'frontend.js' ? frontendAbs : path.join(path.dirname(frontendAbs), 'frontend.css');
|
|
166
|
+
return fs.existsSync(abs) ? abs : null;
|
|
167
|
+
}
|
|
168
|
+
/**
|
|
169
|
+
* Workspace/npm plugins that ship a resolvable built `frontend` entry, for the
|
|
170
|
+
* frontend manifest. UNGATED (parity with the backend base tier). A package
|
|
171
|
+
* without a built bundle (or unresolvable) is simply omitted. `resolveRoot` is a
|
|
172
|
+
* test seam — production passes the default `import.meta.resolve`-based resolver.
|
|
173
|
+
*
|
|
174
|
+
* `packageNames` is the resolved workspace allowlist, so every name is trusted by
|
|
175
|
+
* construction (the traversal guard in `resolveWorkspaceFrontendAsset` exists for
|
|
176
|
+
* the user-supplied route `:name`). Each package is resolved + its `package.json`
|
|
177
|
+
* read exactly ONCE here (this runs per `GET /frontend-manifest`).
|
|
178
|
+
*/
|
|
179
|
+
export function enumerateWorkspaceFrontendBundles(packageNames, resolveRoot = defaultWorkspaceRoot) {
|
|
180
|
+
const bundles = [];
|
|
181
|
+
for (const name of packageNames) {
|
|
182
|
+
const root = resolveRoot(name);
|
|
183
|
+
if (!root)
|
|
184
|
+
continue;
|
|
185
|
+
const pkg = readPackageJson(path.join(root, 'package.json'));
|
|
186
|
+
const frontendAbs = workspaceFrontendJs(root, pkg);
|
|
187
|
+
if (!fs.existsSync(frontendAbs))
|
|
188
|
+
continue;
|
|
189
|
+
bundles.push({
|
|
190
|
+
name,
|
|
191
|
+
version: packageVersion(pkg),
|
|
192
|
+
hasCss: fs.existsSync(path.join(path.dirname(frontendAbs), 'frontend.css')),
|
|
193
|
+
});
|
|
194
|
+
}
|
|
195
|
+
return bundles;
|
|
196
|
+
}
|
|
197
|
+
// ─── Generalized dist asset serving (native-ESM siblings) ───────────────────
|
|
198
|
+
//
|
|
199
|
+
// A plugin's `frontend.js` is served as native ESM (m33l5fe0 — the host does NOT
|
|
200
|
+
// re-bundle), so its RELATIVE imports resolve against the module URL: a code-split
|
|
201
|
+
// chunk `./dto-*.js` becomes `GET /api/plugins/<name>/dto-*.js`. Serving only
|
|
202
|
+
// `frontend.js`/`frontend.css` (the original two-file enum) left those siblings
|
|
203
|
+
// 404 and broke any code-split bundle (the first real plugin tripped on it). So
|
|
204
|
+
// the host serves any allowlisted asset that physically sits next to the entry,
|
|
205
|
+
// strictly contained in the bundle dir, under the SAME trust gating as the entry.
|
|
206
|
+
/** Asset extensions a plugin bundle dir may serve, with their content types. */
|
|
207
|
+
const ASSET_CONTENT_TYPE = {
|
|
208
|
+
'.js': 'text/javascript',
|
|
209
|
+
'.css': 'text/css',
|
|
210
|
+
'.map': 'application/json',
|
|
211
|
+
};
|
|
212
|
+
/** Content type for a served asset name, or `null` when the extension is not allowed. */
|
|
213
|
+
export function assetContentType(asset) {
|
|
214
|
+
return ASSET_CONTENT_TYPE[path.extname(asset).toLowerCase()] ?? null;
|
|
215
|
+
}
|
|
216
|
+
/**
|
|
217
|
+
* Resolve a single flat `asset` strictly within `distDir`. Returns `null` (→ 404)
|
|
218
|
+
* on a disallowed extension, any path separator / traversal in the name, an
|
|
219
|
+
* escape outside `distDir`, or a missing file. Vite library output is flat
|
|
220
|
+
* (`frontend.js`, `dto-*.js`, `*.map` all in the bundle root), so a single
|
|
221
|
+
* path-segment name with no separators is exactly what a relative ESM import
|
|
222
|
+
* requests — and rejecting separators is the simplest sound traversal guard.
|
|
223
|
+
*/
|
|
224
|
+
function resolveWithinDist(distDir, asset) {
|
|
225
|
+
if (!assetContentType(asset))
|
|
226
|
+
return null;
|
|
227
|
+
if (asset.includes('/') || asset.includes('\\') || asset.includes('..'))
|
|
228
|
+
return null;
|
|
229
|
+
const abs = path.join(distDir, asset);
|
|
230
|
+
const rel = path.relative(distDir, abs);
|
|
231
|
+
if (rel === '' || rel.startsWith('..') || path.isAbsolute(rel))
|
|
232
|
+
return null;
|
|
233
|
+
return fs.existsSync(abs) ? abs : null;
|
|
234
|
+
}
|
|
235
|
+
/**
|
|
236
|
+
* Overlay tier (trust-gated): resolve any allowlisted sibling of the project-local
|
|
237
|
+
* bundle at `<cwd>/.claude4spec/plugins/<name>/dist/<asset>`. Same gate + package
|
|
238
|
+
* guard as `resolveFrontendAsset`, generalized from the two fixed filenames.
|
|
239
|
+
*/
|
|
240
|
+
export function resolveOverlayAsset(cwd, trusted, name, asset) {
|
|
241
|
+
if (!trusted)
|
|
242
|
+
return null;
|
|
243
|
+
if (!enumerateOverlayPackages(cwd).includes(name))
|
|
244
|
+
return null;
|
|
245
|
+
return resolveWithinDist(path.join(projectPluginsDir(cwd), name, 'dist'), asset);
|
|
246
|
+
}
|
|
247
|
+
/**
|
|
248
|
+
* Workspace tier (ungated, allowlist-bounded): resolve any allowlisted sibling of
|
|
249
|
+
* a workspace/npm plugin's resolved `frontend` entry (i.e. files in the same
|
|
250
|
+
* `dist/` dir). Generalizes `resolveWorkspaceFrontendAsset` from the two fixed
|
|
251
|
+
* filenames so code-split chunks (`dto-*.js`) and source maps resolve.
|
|
252
|
+
*/
|
|
253
|
+
export function resolveWorkspaceAsset(packageName, asset, allowedPackages, resolveRoot = defaultWorkspaceRoot) {
|
|
254
|
+
if (!allowedPackages.includes(packageName))
|
|
255
|
+
return null;
|
|
256
|
+
const root = resolveRoot(packageName);
|
|
257
|
+
if (!root)
|
|
258
|
+
return null;
|
|
259
|
+
const frontendAbs = workspaceFrontendJs(root, readPackageJson(path.join(root, 'package.json')));
|
|
260
|
+
return resolveWithinDist(path.dirname(frontendAbs), asset);
|
|
261
|
+
}
|
|
262
|
+
//# sourceMappingURL=frontend-assets.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"frontend-assets.js","sourceRoot":"","sources":["../../../../src/server/core/plugin-host/frontend-assets.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* M33: build the `GET /api/plugins/frontend-manifest` payload.
|
|
3
|
+
*
|
|
4
|
+
* Emits the fixed peer import map + host API version and the active plugins to
|
|
5
|
+
* boot ("Option B"). Two tiers feed `plugins[]` (see `buildFrontendManifest`):
|
|
6
|
+
* the workspace/npm tier (ungated, every project) and the project-local overlay
|
|
7
|
+
* tier (only when the primary project is trusted) — each pointing the client at
|
|
8
|
+
* the serving routes in `routes/plugins.ts`. No tiers active ⇒ `plugins: []`, so
|
|
9
|
+
* the import map + shim plumbing still stands up end-to-end with nothing to load.
|
|
10
|
+
*/
|
|
11
|
+
import type { FrontendManifestResponse } from '../../../shared/plugin-host/frontend-manifest.js';
|
|
12
|
+
import { type WorkspaceRootResolver } from './frontend-assets.js';
|
|
13
|
+
import type { PluginRegistry } from './types.js';
|
|
14
|
+
/**
|
|
15
|
+
* Serving context for the process's primary project. Absent on workspace-only
|
|
16
|
+
* starts (nothing registered) ⇒ only workspace-tier frontends are advertised.
|
|
17
|
+
*/
|
|
18
|
+
export interface FrontendManifestServing {
|
|
19
|
+
cwd: string;
|
|
20
|
+
/** `trustProjectPlugins === true` for the primary project. Untrusted ⇒ no overlay plugins. */
|
|
21
|
+
trusted: boolean;
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* Build the boot manifest. `plugins[]` is base ∪ overlay:
|
|
25
|
+
* • workspace tier (`workspacePackages`) — ALWAYS advertised, ungated (a base
|
|
26
|
+
* npm install is trusted), present for every project of the process; and
|
|
27
|
+
* • overlay tier — the primary project's project-local bundles, ONLY when that
|
|
28
|
+
* project is trusted.
|
|
29
|
+
* De-duped by `name`: an overlay bundle overrides a workspace bundle of the same
|
|
30
|
+
* name (the project-local copy wins). CSS only when the bundle ships it, so a
|
|
31
|
+
* style-less plugin omits `css` and the client never fetches it.
|
|
32
|
+
*/
|
|
33
|
+
export declare function buildFrontendManifest(_registry: PluginRegistry, serving?: FrontendManifestServing, workspacePackages?: readonly string[], workspaceRootResolver?: WorkspaceRootResolver): FrontendManifestResponse;
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* M33: build the `GET /api/plugins/frontend-manifest` payload.
|
|
3
|
+
*
|
|
4
|
+
* Emits the fixed peer import map + host API version and the active plugins to
|
|
5
|
+
* boot ("Option B"). Two tiers feed `plugins[]` (see `buildFrontendManifest`):
|
|
6
|
+
* the workspace/npm tier (ungated, every project) and the project-local overlay
|
|
7
|
+
* tier (only when the primary project is trusted) — each pointing the client at
|
|
8
|
+
* the serving routes in `routes/plugins.ts`. No tiers active ⇒ `plugins: []`, so
|
|
9
|
+
* the import map + shim plumbing still stands up end-to-end with nothing to load.
|
|
10
|
+
*/
|
|
11
|
+
import { HOST_API_VERSION } from '../../../shared/plugin-host/manifest.js';
|
|
12
|
+
import { buildImportMap } from './runtime-shims.js';
|
|
13
|
+
import { enumerateFrontendBundles, enumerateWorkspaceFrontendBundles, } from './frontend-assets.js';
|
|
14
|
+
/** One bundle → the manifest entry pointing at the (tier-agnostic) serving routes. */
|
|
15
|
+
function toEntry(b) {
|
|
16
|
+
return {
|
|
17
|
+
name: b.name,
|
|
18
|
+
version: b.version,
|
|
19
|
+
entry: `/api/plugins/${b.name}/frontend.js`,
|
|
20
|
+
...(b.hasCss ? { css: `/api/plugins/${b.name}/frontend.css` } : {}),
|
|
21
|
+
};
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* Build the boot manifest. `plugins[]` is base ∪ overlay:
|
|
25
|
+
* • workspace tier (`workspacePackages`) — ALWAYS advertised, ungated (a base
|
|
26
|
+
* npm install is trusted), present for every project of the process; and
|
|
27
|
+
* • overlay tier — the primary project's project-local bundles, ONLY when that
|
|
28
|
+
* project is trusted.
|
|
29
|
+
* De-duped by `name`: an overlay bundle overrides a workspace bundle of the same
|
|
30
|
+
* name (the project-local copy wins). CSS only when the bundle ships it, so a
|
|
31
|
+
* style-less plugin omits `css` and the client never fetches it.
|
|
32
|
+
*/
|
|
33
|
+
export function buildFrontendManifest(_registry, serving, workspacePackages = [], workspaceRootResolver) {
|
|
34
|
+
const byName = new Map();
|
|
35
|
+
for (const b of enumerateWorkspaceFrontendBundles(workspacePackages, workspaceRootResolver))
|
|
36
|
+
byName.set(b.name, toEntry(b));
|
|
37
|
+
if (serving?.trusted) {
|
|
38
|
+
for (const b of enumerateFrontendBundles(serving.cwd))
|
|
39
|
+
byName.set(b.name, toEntry(b));
|
|
40
|
+
}
|
|
41
|
+
const plugins = [...byName.values()];
|
|
42
|
+
// `importMap` is informational here (diagnostics / future non-head consumers).
|
|
43
|
+
// The AUTHORITATIVE copy the browser uses is injected server-side into the SPA
|
|
44
|
+
// `<head>` (see `injectImportMap` in server/index.ts) — both derive from the
|
|
45
|
+
// same `buildImportMap()`, so the two channels cannot diverge.
|
|
46
|
+
return {
|
|
47
|
+
hostApiVersion: HOST_API_VERSION,
|
|
48
|
+
importMap: buildImportMap(),
|
|
49
|
+
plugins,
|
|
50
|
+
};
|
|
51
|
+
}
|
|
52
|
+
//# sourceMappingURL=frontend-manifest.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"frontend-manifest.js","sourceRoot":"","sources":["../../../../src/server/core/plugin-host/frontend-manifest.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAEH,OAAO,EAAE,gBAAgB,EAAE,MAAM,yCAAyC,CAAC;AAK3E,OAAO,EAAE,cAAc,EAAE,MAAM,oBAAoB,CAAC;AACpD,OAAO,EACL,wBAAwB,EACxB,iCAAiC,GAGlC,MAAM,sBAAsB,CAAC;AAa9B,sFAAsF;AACtF,SAAS,OAAO,CAAC,CAAiB;IAChC,OAAO;QACL,IAAI,EAAE,CAAC,CAAC,IAAI;QACZ,OAAO,EAAE,CAAC,CAAC,OAAO;QAClB,KAAK,EAAE,gBAAgB,CAAC,CAAC,IAAI,cAAc;QAC3C,GAAG,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,GAAG,EAAE,gBAAgB,CAAC,CAAC,IAAI,eAAe,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;KACpE,CAAC;AACJ,CAAC;AAED;;;;;;;;;GASG;AACH,MAAM,UAAU,qBAAqB,CACnC,SAAyB,EACzB,OAAiC,EACjC,oBAAuC,EAAE,EACzC,qBAA6C;IAE7C,MAAM,MAAM,GAAG,IAAI,GAAG,EAA+B,CAAC;IACtD,KAAK,MAAM,CAAC,IAAI,iCAAiC,CAAC,iBAAiB,EAAE,qBAAqB,CAAC;QACzF,MAAM,CAAC,GAAG,CAAC,CAAC,CAAC,IAAI,EAAE,OAAO,CAAC,CAAC,CAAC,CAAC,CAAC;IACjC,IAAI,OAAO,EAAE,OAAO,EAAE,CAAC;QACrB,KAAK,MAAM,CAAC,IAAI,wBAAwB,CAAC,OAAO,CAAC,GAAG,CAAC;YAAE,MAAM,CAAC,GAAG,CAAC,CAAC,CAAC,IAAI,EAAE,OAAO,CAAC,CAAC,CAAC,CAAC,CAAC;IACxF,CAAC;IACD,MAAM,OAAO,GAA0B,CAAC,GAAG,MAAM,CAAC,MAAM,EAAE,CAAC,CAAC;IAE5D,+EAA+E;IAC/E,+EAA+E;IAC/E,6EAA6E;IAC7E,+DAA+D;IAC/D,OAAO;QACL,cAAc,EAAE,gBAAgB;QAChC,SAAS,EAAE,cAAc,EAAE;QAC3B,OAAO;KACR,CAAC;AACJ,CAAC"}
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* M33 plugin loader — the single mechanism that turns workspace-declared npm
|
|
3
|
+
* packages into registered host capabilities, run identically from every entry
|
|
4
|
+
* point (server, `c4s`, `c4s-mcp`, `c4s-reader`).
|
|
5
|
+
*
|
|
6
|
+
* Four steps, per package, each isolated in its own try/catch so a single bad
|
|
7
|
+
* plugin is skipped — never crashing the process:
|
|
8
|
+
* 1. Dynamic import — `await import(pkg)` (native ESM).
|
|
9
|
+
* 2. Manifest extraction — `mod.manifest` or `mod.default`.
|
|
10
|
+
* 3. Validation gate — hostApiVersion range + engines.node.
|
|
11
|
+
* 4. Registration — `registry.registerPlugin(manifest)`.
|
|
12
|
+
*/
|
|
13
|
+
import type { PluginManifest } from '../../../shared/plugin-host/manifest.js';
|
|
14
|
+
import { type PluginMigrationInfo } from '../../../shared/plugin-host/host-api.js';
|
|
15
|
+
import type { PluginRegistry } from './types.js';
|
|
16
|
+
/**
|
|
17
|
+
* `incompatible` (M33) is distinct from `skipped`: it means the package
|
|
18
|
+
* was built against an incompatible MAJOR Host API and carries a `migration`
|
|
19
|
+
* descriptor (a repair path), whereas `skipped` is an environment problem (an
|
|
20
|
+
* `engines` miss) with no migration path.
|
|
21
|
+
*/
|
|
22
|
+
export type PluginLoadStatus = 'loaded' | 'skipped' | 'incompatible' | 'failed';
|
|
23
|
+
export type PluginLoadCode = 'PLUGIN_HOST_API_MISMATCH' | 'PLUGIN_ENGINE_UNSATISFIED' | 'PLUGIN_IMPORT_FAILED' | 'PLUGIN_INVALID_MANIFEST' | 'PLUGIN_TYPE_CONFLICT' | 'PLUGIN_PROJECT_UNTRUSTED';
|
|
24
|
+
/** Which layer a record belongs to — base (workspace/npm) vs overlay (project-local). */
|
|
25
|
+
export type PluginLayer = 'base' | 'overlay';
|
|
26
|
+
/**
|
|
27
|
+
* Per-package outcome. Adds `layer` (base/overlay), `trust`, `origin`
|
|
28
|
+
* (project-local source path), and `shadows`/`shadowedTypes` — all optional so
|
|
29
|
+
* base records keep their shape.
|
|
30
|
+
*/
|
|
31
|
+
export interface PluginLoadRecord {
|
|
32
|
+
/** Source identifier: the npm package name, or a synthetic id for built-ins. */
|
|
33
|
+
package: string;
|
|
34
|
+
status: PluginLoadStatus;
|
|
35
|
+
/** Machine code for skipped/failed packages. */
|
|
36
|
+
code?: PluginLoadCode;
|
|
37
|
+
/** Human-readable explanation for skipped/failed packages. */
|
|
38
|
+
reason?: string;
|
|
39
|
+
manifestName?: string;
|
|
40
|
+
manifestVersion?: string;
|
|
41
|
+
/** Entity types this package contributed (only when `loaded`). */
|
|
42
|
+
contributedTypes?: string[];
|
|
43
|
+
/** Which layer this record came from (base/overlay). */
|
|
44
|
+
layer?: PluginLayer;
|
|
45
|
+
/** Trust state of the project-local layer (overlay only). */
|
|
46
|
+
trust?: 'trusted' | 'untrusted';
|
|
47
|
+
/** Source path under `<cwd>/.claude4spec/plugins/` (overlay only). */
|
|
48
|
+
origin?: string;
|
|
49
|
+
/**
|
|
50
|
+
* M33: present on `incompatible` records — the repair path (target
|
|
51
|
+
* Host API version, applicable migration descriptors, shim availability).
|
|
52
|
+
*/
|
|
53
|
+
migration?: PluginMigrationInfo;
|
|
54
|
+
}
|
|
55
|
+
export interface PluginLoadResult {
|
|
56
|
+
records: PluginLoadRecord[];
|
|
57
|
+
}
|
|
58
|
+
/** Module importer — overridable in tests; defaults to native dynamic import. */
|
|
59
|
+
export type PluginImporter = (specifier: string) => Promise<unknown>;
|
|
60
|
+
/** Pull the `PluginManifest` off a freshly-imported module namespace. Shared by
|
|
61
|
+
* the workspace loader and the project-local overlay loader. */
|
|
62
|
+
export declare function extractManifest(mod: unknown): PluginManifest | null;
|
|
63
|
+
/** A structurally-valid manifest carries at least string `name` + `hostApiVersion`. */
|
|
64
|
+
export declare function isValidManifestShape(manifest: PluginManifest | null): manifest is PluginManifest;
|
|
65
|
+
/**
|
|
66
|
+
* Outcome of the compatibility gate. `status` lets the caller record an
|
|
67
|
+
* `incompatible` (major Host API mismatch — carries a `migration` repair path)
|
|
68
|
+
* distinctly from a `skipped` (host-api same-major miss, or an `engines` miss —
|
|
69
|
+
* environment problems with no migration path).
|
|
70
|
+
*/
|
|
71
|
+
export interface GateResult {
|
|
72
|
+
status: 'skipped' | 'incompatible';
|
|
73
|
+
code: PluginLoadCode;
|
|
74
|
+
reason: string;
|
|
75
|
+
migration?: PluginMigrationInfo;
|
|
76
|
+
}
|
|
77
|
+
/**
|
|
78
|
+
* Compatibility gate shared by all layers: hostApiVersion range + engines.node.
|
|
79
|
+
* Returns `null` when the manifest may register; otherwise the gate result.
|
|
80
|
+
* A MAJOR Host API mismatch → `incompatible` + a migration descriptor; an
|
|
81
|
+
* `engines` miss (or a same-major host-api miss) → `skipped`.
|
|
82
|
+
*/
|
|
83
|
+
export declare function gateManifest(manifest: PluginManifest): GateResult | null;
|
|
84
|
+
/**
|
|
85
|
+
* Load and register the given workspace plugin packages onto `registry`.
|
|
86
|
+
* Returns one record per package for the diagnostics route. Never throws.
|
|
87
|
+
*/
|
|
88
|
+
export declare function loadWorkspacePlugins(registry: PluginRegistry, packageNames: string[], importer?: PluginImporter): Promise<PluginLoadResult>;
|
|
89
|
+
/** Seams for `reloadPlugin` — overridable in tests. */
|
|
90
|
+
export interface ReloadPluginOptions {
|
|
91
|
+
importer?: PluginImporter;
|
|
92
|
+
resolveEntry?: (pkg: string) => string | null;
|
|
93
|
+
cacheBust?: (entry: string) => string;
|
|
94
|
+
}
|
|
95
|
+
/**
|
|
96
|
+
* M33 — hot-reload pipeline for ONE base (workspace/npm) package.
|
|
97
|
+
*
|
|
98
|
+
* Atomicity: the failure modes the brief names "old stays" — import failure,
|
|
99
|
+
* missing manifest export, incompatible major — are all checked BEFORE any
|
|
100
|
+
* teardown, so the previously-registered version remains registered and the
|
|
101
|
+
* caller surfaces a warning. Only once a fresh, compatible manifest is in hand
|
|
102
|
+
* do we tear the old version down (`unregisterPlugin` → its `onUnregister`) and
|
|
103
|
+
* register the new one — the brief's "onUnregister old, then register new".
|
|
104
|
+
*
|
|
105
|
+
* Returns the per-package record (`loaded` / `skipped` / `failed`). Never throws.
|
|
106
|
+
*/
|
|
107
|
+
export declare function reloadPlugin(registry: PluginRegistry, pkg: string, opts?: ReloadPluginOptions): Promise<PluginLoadRecord>;
|