@akira-tl/forgerelay 1.1.2 → 1.2.0
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/CHANGELOG.md +38 -0
- package/README.md +14 -0
- package/dist/activity/runtime/mcp-query-tools.js +15 -5
- package/dist/cli/config/inspect.js +331 -0
- package/dist/cli/config/migrate.js +351 -0
- package/dist/cli/init/setup-config.js +41 -0
- package/dist/cli/init.js +128 -130
- package/dist/cli/maintenance.js +15 -5
- package/dist/cli/mcp/external-mcp.js +14 -10
- package/dist/cli/mcp/status.js +11 -6
- package/dist/cli.js +40 -7
- package/dist/lsp/language-server-config.js +66 -144
- package/dist/lsp/runtime/manager.js +35 -4
- package/dist/mcp/hooks/config.js +141 -0
- package/dist/mcp/hooks/external-mcp-transform.js +22 -13
- package/dist/mcp/hooks/hook-cli.js +62 -52
- package/dist/mcp/hooks/hooks.js +111 -85
- package/dist/mcp/operations/external-mcp/external-mcp-runtime.js +8 -5
- package/dist/mcp/operations/external-mcp/external-mcp.js +9 -2
- package/dist/mcp/server/core/capability-support.js +7 -0
- package/dist/runtime/config/config.js +142 -64
- package/dist/runtime/config/definition/catalog.js +12 -0
- package/dist/runtime/config/definition/definition.js +115 -0
- package/dist/runtime/config/definition/external-mcp.js +58 -0
- package/dist/runtime/config/definition/general-config.js +258 -0
- package/dist/runtime/config/definition/language-servers.js +116 -0
- package/dist/runtime/config/definition/schema.js +113 -0
- package/dist/runtime/config/definition/types.js +2 -0
- package/dist/runtime/config/external-mcp-auth-store.js +105 -29
- package/dist/runtime/config/external-mcp-config.js +39 -0
- package/dist/runtime/config/external-mcp-registry.js +284 -106
- package/dist/runtime/config/inspection/live.js +77 -0
- package/dist/runtime/config/resolution/general.js +93 -0
- package/dist/runtime/config/resolution/hooks.js +315 -0
- package/dist/runtime/config/resolution/language-servers.js +237 -0
- package/dist/runtime/config/resolution/project-sources.js +159 -0
- package/dist/runtime/config/resolution/resolver.js +485 -0
- package/dist/runtime/config/resolution/types.js +1 -0
- package/dist/runtime/config/runtime/config-runtime.js +74 -0
- package/dist/runtime/config/runtime/source-refresh.js +263 -0
- package/dist/runtime/config/user-config.js +38 -31
- package/dist/runtime/security/project-execution-trust.js +34 -0
- package/dist/runtime/testing/server-fixture.js +9 -8
- package/dist/server.js +10 -6
- package/dist/subagents/profiles.js +336 -53
- package/dist/subagents/sessions/execution.js +6 -35
- package/dist/subagents/sessions/manager.js +14 -2
- package/dist/ui/.vite/manifest.json +275 -275
- package/dist/ui/activity-panel-app.html +1 -1
- package/dist/ui/assets/{activity-panel-app-Bw7ZFX2g.js → activity-panel-app-BeDiwd7D.js} +3 -3
- package/dist/ui/assets/{angular-html-DeUNP12X.js → angular-html-DuoCE7nn.js} +1 -1
- package/dist/ui/assets/{angular-ts-B3v8FKi_.js → angular-ts-D5tGk0hL.js} +1 -1
- package/dist/ui/assets/{apl-Bs3iQm4P.js → apl-lciOTPbw.js} +1 -1
- package/dist/ui/assets/{astro-D6GHecBY.js → astro-vJbqfNnJ.js} +1 -1
- package/dist/ui/assets/{blade-BMNbVOHv.js → blade-C7655Q4K.js} +1 -1
- package/dist/ui/assets/{c-BquuA6bv.js → c-DdHp1Jrw.js} +1 -1
- package/dist/ui/assets/{cobol-BuZ6kfps.js → cobol-DgvzQxZP.js} +1 -1
- package/dist/ui/assets/{coffee-DgC5uBzL.js → coffee-BIjVUOUd.js} +1 -1
- package/dist/ui/assets/{cpp-BdvWqCaB.js → cpp-CU8tGzvI.js} +1 -1
- package/dist/ui/assets/{crystal-0Q5qBvNI.js → crystal-CUQAwsg9.js} +1 -1
- package/dist/ui/assets/{css-Be_T8idh.js → css-DtMTqD3F.js} +1 -1
- package/dist/ui/assets/{edge-DaGLdgh_.js → edge-DoCX9RnL.js} +1 -1
- package/dist/ui/assets/{elixir-CMgiFIzB.js → elixir-DJxV9_U6.js} +1 -1
- package/dist/ui/assets/{elm-B-ufMlMC.js → elm-DIgWqK4r.js} +1 -1
- package/dist/ui/assets/{erb-CaGiOZM-.js → erb-BHxP0c_e.js} +1 -1
- package/dist/ui/assets/{git-rebase-CpwUWCzK.js → git-rebase-HbI_Vo8d.js} +1 -1
- package/dist/ui/assets/{glimmer-js-BRxccvnE.js → glimmer-js-CthY4Ty_.js} +1 -1
- package/dist/ui/assets/{glimmer-ts-CgQbPzlT.js → glimmer-ts-B2-_A1Xz.js} +1 -1
- package/dist/ui/assets/{glsl-D7JmHFr0.js → glsl-D9V9gq98.js} +1 -1
- package/dist/ui/assets/{graphql-C8n0eUBK.js → graphql-CddeAaS3.js} +1 -1
- package/dist/ui/assets/{hack-lVN7KVQv.js → hack-D_loGkOT.js} +1 -1
- package/dist/ui/assets/{haml-h18lG0pS.js → haml-BoWDczd0.js} +1 -1
- package/dist/ui/assets/{handlebars-lRFjRPfj.js → handlebars-ZoSOvw9y.js} +1 -1
- package/dist/ui/assets/{heavy-payload-Cqx9MOkH.js → heavy-payload-DO34LwoZ.js} +3 -3
- package/dist/ui/assets/{html-DtILqTE1.js → html-CE-obXVo.js} +1 -1
- package/dist/ui/assets/{html-derivative-CSTSmPup.js → html-derivative-Cd-smAbW.js} +1 -1
- package/dist/ui/assets/{http-DNI8fsbY.js → http-CZrkbrSl.js} +1 -1
- package/dist/ui/assets/{hurl-DgRi60JG.js → hurl-CxTuxBt1.js} +1 -1
- package/dist/ui/assets/{java-CW9pZ5bh.js → java-BshLjCat.js} +1 -1
- package/dist/ui/assets/{javascript-Cvg2UrWL.js → javascript-D2pxQTiX.js} +1 -1
- package/dist/ui/assets/{jinja-CZw_S1FB.js → jinja-kbfPP7-F.js} +1 -1
- package/dist/ui/assets/{jison-C56u_FZv.js → jison-C2mt9l1M.js} +1 -1
- package/dist/ui/assets/{json-DW2UTv1q.js → json-DU_rg_AU.js} +1 -1
- package/dist/ui/assets/{jsx-Zj5gobdD.js → jsx-D_8aQMlx.js} +1 -1
- package/dist/ui/assets/{julia-Cvcrre-L.js → julia-BmefTaVV.js} +1 -1
- package/dist/ui/assets/{just-8CZ2u6Tj.js → just-DXV8vk1S.js} +1 -1
- package/dist/ui/assets/{latex-D289AXWG.js → latex-Cjm2MvGW.js} +1 -1
- package/dist/ui/assets/{liquid-CFEX3SkZ.js → liquid-DyQDlo0u.js} +1 -1
- package/dist/ui/assets/{lua-BKpJ0ybO.js → lua-B2A1vzzy.js} +1 -1
- package/dist/ui/assets/{marko-D9maZ0a6.js → marko-B9vOkXOU.js} +1 -1
- package/dist/ui/assets/{mdc-rM9l0qWR.js → mdc-D42m6spU.js} +1 -1
- package/dist/ui/assets/{nginx-C9w6p2C9.js → nginx-TpfVxSUY.js} +1 -1
- package/dist/ui/assets/{nim-BRePHk0J.js → nim-CJr5T1FK.js} +1 -1
- package/dist/ui/assets/{perl-CskTmiPQ.js → perl-cJZcsdm9.js} +1 -1
- package/dist/ui/assets/{php-DCU_FvNI.js → php-CSZL5KRT.js} +1 -1
- package/dist/ui/assets/{pug-XLaVxTvv.js → pug-B1eMKzuA.js} +1 -1
- package/dist/ui/assets/{qml-CaONKSl-.js → qml-Dl2_7zc9.js} +1 -1
- package/dist/ui/assets/{r-BCVR0ZE3.js → r-F0aaO9gG.js} +1 -1
- package/dist/ui/assets/{razor-DDnGXxnU.js → razor-BPnkSMCw.js} +1 -1
- package/dist/ui/assets/{regexp-DWGTYYVx.js → regexp-BnGHMFbv.js} +1 -1
- package/dist/ui/assets/{rst-Dz3GWvQm.js → rst-B3CO7WYO.js} +1 -1
- package/dist/ui/assets/{ruby-DC3jGtMY.js → ruby-B6VMzT1T.js} +1 -1
- package/dist/ui/assets/{sas-7mNpvsjZ.js → sas-Df2lHVuU.js} +1 -1
- package/dist/ui/assets/{scss-uBsoCJVy.js → scss-4XxI6sgu.js} +1 -1
- package/dist/ui/assets/{shellscript-BCN9N_Jf.js → shellscript-DYASCN4r.js} +1 -1
- package/dist/ui/assets/{shellsession-Hsw46VXV.js → shellsession-Br6AGJO_.js} +1 -1
- package/dist/ui/assets/{soy-D6KV69HU.js → soy-p3ZNq1We.js} +1 -1
- package/dist/ui/assets/{sql-CVzVQkJh.js → sql-CAdv2OXL.js} +1 -1
- package/dist/ui/assets/{stata-BwUjXNhI.js → stata-Dx7yUbG_.js} +1 -1
- package/dist/ui/assets/{surrealql-CgloWqRa.js → surrealql-6YsE8HQa.js} +1 -1
- package/dist/ui/assets/{svelte-DcC3n6IC.js → svelte-YgzedwpD.js} +1 -1
- package/dist/ui/assets/{templ-DOFfnWTJ.js → templ-Bahqx3uD.js} +1 -1
- package/dist/ui/assets/{tex-ln89VPaX.js → tex-o6fTQjkH.js} +1 -1
- package/dist/ui/assets/{ts-tags-CWMzCstH.js → ts-tags-BO-Co0Ju.js} +1 -1
- package/dist/ui/assets/{tsx-DgzfiGeK.js → tsx-DtQe5AoH.js} +1 -1
- package/dist/ui/assets/{twig-Bi2itZL9.js → twig-BcUs6Kqc.js} +1 -1
- package/dist/ui/assets/{typescript-CQ24f4zW.js → typescript-BPtM8h4u.js} +1 -1
- package/dist/ui/assets/{vue-BS8jE5u6.js → vue-5KkGUzJJ.js} +1 -1
- package/dist/ui/assets/{vue-html-o38xqhyB.js → vue-html-Df_Xfsz9.js} +1 -1
- package/dist/ui/assets/{vue-vine-D2NvKv50.js → vue-vine-hUIe_u_Y.js} +1 -1
- package/dist/ui/assets/{xml-_44rx9_2.js → xml-C3Jy7as5.js} +1 -1
- package/dist/ui/assets/{xsl-B5QWQPoi.js → xsl-BnF9OuFo.js} +1 -1
- package/dist/ui/assets/{yaml-CKL2pEWe.js → yaml-DyrFgCou.js} +1 -1
- package/dist/workspaces/conversation-test-support.js +3 -11
- package/dist/workspaces/sessions.js +2 -0
- package/dist/workspaces/state/project-context.js +221 -0
- package/dist/workspaces.js +3 -1
- package/docs/chatgpt-coding-workflow.md +8 -3
- package/docs/configuration.md +99 -41
- package/docs/gotchas.md +24 -5
- package/docs/security.md +14 -3
- package/docs/setup.md +12 -7
- package/package.json +12 -3
- package/schemas/v1/config.project-local.schema.json +14 -0
- package/schemas/v1/config.project.schema.json +14 -0
- package/schemas/v1/config.user.schema.json +538 -0
- package/schemas/v1/hooks.project-local.schema.json +118 -0
- package/schemas/v1/hooks.project.schema.json +118 -0
- package/schemas/v1/hooks.user.schema.json +118 -0
- package/schemas/v1/language-servers.project-local.schema.json +230 -0
- package/schemas/v1/language-servers.project.schema.json +230 -0
- package/schemas/v1/language-servers.user.schema.json +230 -0
- package/schemas/v1/mcp.project-local.schema.json +143 -0
- package/schemas/v1/mcp.project.schema.json +143 -0
- package/schemas/v1/mcp.user.schema.json +143 -0
- package/scripts/ci/architecture.mjs +54 -4
- package/scripts/ci/cmd-acceptance.mjs +9 -9
- package/scripts/ci/config-v2-product-acceptance.mjs +639 -0
- package/scripts/ci/gates/acceptance-artifact.mjs +66 -0
- package/scripts/ci/gates/core-contract.mjs +27 -0
- package/scripts/ci/gates/platform-tests.mjs +38 -0
- package/scripts/ci/gates/prepare-windows-product.mjs +57 -0
- package/scripts/ci/gates/test-shard.mjs +71 -0
- package/scripts/ci/powershell51-acceptance.mjs +9 -9
- package/scripts/ci/pwsh-acceptance.mjs +9 -9
- package/scripts/ci/verify.mjs +2 -0
- package/scripts/ci/windows-product-acceptance.mjs +9 -10
- package/scripts/config/schema-text.mjs +4 -0
- package/scripts/config/schema-text.test.mjs +18 -0
- package/scripts/config/schema.mjs +74 -0
- package/scripts/lsp/interop.mjs +4 -1
- package/scripts/release/pack.mjs +64 -4
- package/scripts/release/release-gate.test.mjs +93 -27
- package/scripts/release-parity.mjs +19 -2
- package/templates/instructions/cmd.md +12 -0
- package/templates/instructions/fish.md +11 -0
- package/templates/instructions/powershell.md +14 -0
- package/templates/instructions/pwsh.md +14 -0
- package/templates/instructions/zsh.md +11 -0
- package/dist/runtime/instructions/powershell-skill.js +0 -65
|
@@ -1,90 +1,44 @@
|
|
|
1
|
-
import { access,
|
|
1
|
+
import { access, realpath } from "node:fs/promises";
|
|
2
2
|
import { constants } from "node:fs";
|
|
3
3
|
import { createHash } from "node:crypto";
|
|
4
4
|
import { delimiter, dirname, extname, isAbsolute, join, relative, resolve, sep } from "node:path";
|
|
5
|
-
import {
|
|
5
|
+
import { resolveProjectContext } from "../workspaces/state/project-context.js";
|
|
6
|
+
import { effectiveLanguageServerEntries, resolveLanguageServersConfig, } from "../runtime/config/resolution/language-servers.js";
|
|
7
|
+
import { projectExecutionRequirement } from "../runtime/security/project-execution-trust.js";
|
|
6
8
|
export class LanguageServerConfigurationError extends Error {
|
|
7
9
|
code;
|
|
8
|
-
|
|
10
|
+
availableDefinitionFingerprints;
|
|
11
|
+
constructor(code, message, availableDefinitionFingerprints) {
|
|
9
12
|
super(message);
|
|
10
13
|
this.code = code;
|
|
14
|
+
this.availableDefinitionFingerprints = availableDefinitionFingerprints;
|
|
11
15
|
this.name = "LanguageServerConfigurationError";
|
|
12
16
|
}
|
|
13
17
|
}
|
|
14
|
-
const
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
extensions: z.array(z.string().regex(/^\./)).min(1).optional(),
|
|
21
|
-
languageIdByExtension: z.record(z.string().regex(/^\./), z.string().min(1)).optional(),
|
|
22
|
-
projectMarkers: z.array(z.string().min(1)).optional(),
|
|
23
|
-
}).strict();
|
|
24
|
-
const configSchema = z.record(z.string().min(1), definitionSchema);
|
|
25
|
-
const BUILTIN_DEFINITIONS = {
|
|
26
|
-
typescript: {
|
|
27
|
-
executableCandidates: ["typescript-language-server"],
|
|
28
|
-
args: ["--stdio"],
|
|
29
|
-
languages: ["typescript", "typescriptreact", "javascript", "javascriptreact"],
|
|
30
|
-
extensions: [".ts", ".tsx", ".js", ".jsx", ".mjs", ".cjs"],
|
|
31
|
-
languageIdByExtension: {
|
|
32
|
-
".ts": "typescript",
|
|
33
|
-
".tsx": "typescriptreact",
|
|
34
|
-
".js": "javascript",
|
|
35
|
-
".jsx": "javascriptreact",
|
|
36
|
-
".mjs": "javascript",
|
|
37
|
-
".cjs": "javascript",
|
|
38
|
-
},
|
|
39
|
-
projectMarkers: ["tsconfig.json", "jsconfig.json", "package.json"],
|
|
40
|
-
},
|
|
41
|
-
pyright: {
|
|
42
|
-
executableCandidates: ["pyright-langserver"],
|
|
43
|
-
args: ["--stdio"],
|
|
44
|
-
languages: ["python"],
|
|
45
|
-
extensions: [".py", ".pyi"],
|
|
46
|
-
languageIdByExtension: { ".py": "python", ".pyi": "python" },
|
|
47
|
-
projectMarkers: ["pyrightconfig.json", "pyproject.toml", "setup.cfg", "setup.py"],
|
|
48
|
-
},
|
|
49
|
-
"rust-analyzer": {
|
|
50
|
-
executableCandidates: ["rust-analyzer"],
|
|
51
|
-
languages: ["rust"],
|
|
52
|
-
extensions: [".rs"],
|
|
53
|
-
languageIdByExtension: { ".rs": "rust" },
|
|
54
|
-
projectMarkers: ["Cargo.toml"],
|
|
55
|
-
},
|
|
56
|
-
gopls: {
|
|
57
|
-
executableCandidates: ["gopls"],
|
|
58
|
-
languages: ["go"],
|
|
59
|
-
extensions: [".go"],
|
|
60
|
-
languageIdByExtension: { ".go": "go" },
|
|
61
|
-
projectMarkers: ["go.work", "go.mod"],
|
|
62
|
-
},
|
|
63
|
-
clangd: {
|
|
64
|
-
executableCandidates: ["clangd"],
|
|
65
|
-
languages: ["c", "cpp", "objective-c", "objective-cpp"],
|
|
66
|
-
extensions: [".c", ".cc", ".cpp", ".cxx", ".h", ".hh", ".hpp", ".hxx", ".m", ".mm"],
|
|
67
|
-
languageIdByExtension: {
|
|
68
|
-
".c": "c",
|
|
69
|
-
".cc": "cpp",
|
|
70
|
-
".cpp": "cpp",
|
|
71
|
-
".cxx": "cpp",
|
|
72
|
-
".h": "cpp",
|
|
73
|
-
".hh": "cpp",
|
|
74
|
-
".hpp": "cpp",
|
|
75
|
-
".hxx": "cpp",
|
|
76
|
-
".m": "objective-c",
|
|
77
|
-
".mm": "objective-cpp",
|
|
78
|
-
},
|
|
79
|
-
projectMarkers: ["compile_commands.json", "compile_flags.txt", ".clangd"],
|
|
80
|
-
},
|
|
18
|
+
const BUILTIN_EXECUTABLE_CANDIDATES = {
|
|
19
|
+
typescript: ["typescript-language-server"],
|
|
20
|
+
pyright: ["pyright-langserver"],
|
|
21
|
+
"rust-analyzer": ["rust-analyzer"],
|
|
22
|
+
gopls: ["gopls"],
|
|
23
|
+
clangd: ["clangd"],
|
|
81
24
|
};
|
|
82
25
|
export async function resolveLanguageProject(input) {
|
|
83
26
|
const workspaceRoot = await canonicalWorkspaceRoot(input.workspaceRoot);
|
|
84
27
|
const sourcePath = await resolveWorkspaceSourcePath(workspaceRoot, input.sourcePath);
|
|
85
|
-
const
|
|
86
|
-
const
|
|
87
|
-
|
|
28
|
+
const environment = input.env ?? process.env;
|
|
29
|
+
const project = input.configDir
|
|
30
|
+
? await resolveProjectContext(input.configDir, workspaceRoot)
|
|
31
|
+
: undefined;
|
|
32
|
+
const resolution = await resolveLanguageServersConfig({
|
|
33
|
+
...(input.configDir ? { configDir: input.configDir } : {}),
|
|
34
|
+
...(project ? { project } : {}),
|
|
35
|
+
projectSharedConfigDir: join(workspaceRoot, ".forgerelay"),
|
|
36
|
+
environment,
|
|
37
|
+
...(input.sourceRuntime ? { sourceRuntime: input.sourceRuntime } : {}),
|
|
38
|
+
});
|
|
39
|
+
const errors = resolution.diagnostics.filter((diagnostic) => diagnostic.severity === "error" && diagnostic.usingLastKnownGood !== true);
|
|
40
|
+
const definitions = await materializeResolvedDefinitions(resolution, environment, project?.id);
|
|
41
|
+
const availableDefinitionFingerprints = Object.fromEntries(definitions.map((definition) => [definition.id, definition.configFingerprint]));
|
|
88
42
|
const extension = extname(sourcePath).toLowerCase();
|
|
89
43
|
const candidates = [];
|
|
90
44
|
for (const definition of definitions) {
|
|
@@ -93,96 +47,49 @@ export async function resolveLanguageProject(input) {
|
|
|
93
47
|
const projectRoot = await findLanguageProjectRoot(workspaceRoot, dirname(sourcePath), definition.projectMarkers);
|
|
94
48
|
if (!projectRoot)
|
|
95
49
|
continue;
|
|
96
|
-
candidates.push({ definition, projectRoot });
|
|
50
|
+
candidates.push({ definition, projectRoot, availableDefinitionFingerprints });
|
|
97
51
|
}
|
|
98
52
|
if (candidates.length === 0) {
|
|
99
|
-
|
|
53
|
+
if (errors.length > 0) {
|
|
54
|
+
throw new LanguageServerConfigurationError("code.configuration_invalid", `Invalid Language-server configuration: ${errors.map((diagnostic) => `${diagnostic.source.location ?? diagnostic.source.id}: ${diagnostic.message}`).join("; ")}`, availableDefinitionFingerprints);
|
|
55
|
+
}
|
|
56
|
+
throw new LanguageServerConfigurationError("code.language_service_unavailable", `No available Language-server definition matches ${relative(workspaceRoot, sourcePath) || "."}.`, availableDefinitionFingerprints);
|
|
100
57
|
}
|
|
101
|
-
const sourceRank = { builtin: 0, global: 1, project: 2 };
|
|
58
|
+
const sourceRank = { builtin: 0, global: 1, project: 2, "project-local": 3 };
|
|
102
59
|
const highestRank = Math.max(...candidates.map((candidate) => sourceRank[candidate.definition.source]));
|
|
103
60
|
const highest = candidates.filter((candidate) => sourceRank[candidate.definition.source] === highestRank);
|
|
104
61
|
const deepestLength = Math.max(...highest.map((candidate) => candidate.projectRoot.length));
|
|
105
62
|
const nearest = highest.filter((candidate) => candidate.projectRoot.length === deepestLength);
|
|
106
63
|
if (nearest.length !== 1) {
|
|
107
|
-
throw new LanguageServerConfigurationError("code.configuration_ambiguous", `Multiple Language-server definitions match ${relative(workspaceRoot, sourcePath)} at the same priority: ${nearest.map((candidate) => candidate.definition.id).join(", ")}
|
|
64
|
+
throw new LanguageServerConfigurationError("code.configuration_ambiguous", `Multiple Language-server definitions match ${relative(workspaceRoot, sourcePath)} at the same priority: ${nearest.map((candidate) => candidate.definition.id).join(", ")}.`, availableDefinitionFingerprints);
|
|
108
65
|
}
|
|
109
66
|
return nearest[0];
|
|
110
67
|
}
|
|
111
|
-
|
|
112
|
-
const path = join(workspaceRoot, ".forgerelay", "language-servers.json");
|
|
113
|
-
try {
|
|
114
|
-
return parseLanguageServerConfig(JSON.parse(await readFile(path, "utf8")), path);
|
|
115
|
-
}
|
|
116
|
-
catch (error) {
|
|
117
|
-
if (isMissingFile(error))
|
|
118
|
-
return {};
|
|
119
|
-
if (error instanceof LanguageServerConfigurationError)
|
|
120
|
-
throw error;
|
|
121
|
-
const reason = error instanceof Error ? error.message : String(error);
|
|
122
|
-
throw new LanguageServerConfigurationError("code.configuration_invalid", `Unable to load Language-server configuration at ${path}: ${reason}`);
|
|
123
|
-
}
|
|
124
|
-
}
|
|
125
|
-
export function parseLanguageServerConfig(value, label) {
|
|
126
|
-
const parsed = configSchema.safeParse(value ?? {});
|
|
127
|
-
if (!parsed.success) {
|
|
128
|
-
const details = parsed.error.issues
|
|
129
|
-
.map((issue) => `${issue.path.join(".") || "config"}: ${issue.message}`)
|
|
130
|
-
.join("; ");
|
|
131
|
-
throw new LanguageServerConfigurationError("code.configuration_invalid", `Invalid Language-server configuration in ${label}: ${details}`);
|
|
132
|
-
}
|
|
133
|
-
return parsed.data;
|
|
134
|
-
}
|
|
135
|
-
async function effectiveDefinitions(globalConfig, projectConfig, env) {
|
|
136
|
-
const ids = new Set([
|
|
137
|
-
...Object.keys(BUILTIN_DEFINITIONS),
|
|
138
|
-
...Object.keys(globalConfig),
|
|
139
|
-
...Object.keys(projectConfig),
|
|
140
|
-
]);
|
|
68
|
+
async function materializeResolvedDefinitions(resolution, env, projectId) {
|
|
141
69
|
const definitions = [];
|
|
142
|
-
for (const
|
|
143
|
-
const builtin = BUILTIN_DEFINITIONS[id];
|
|
144
|
-
const global = globalConfig[id];
|
|
145
|
-
const project = projectConfig[id];
|
|
146
|
-
const source = project
|
|
147
|
-
? "project"
|
|
148
|
-
: global
|
|
149
|
-
? "global"
|
|
150
|
-
: "builtin";
|
|
70
|
+
for (const entry of effectiveLanguageServerEntries(resolution)) {
|
|
151
71
|
const merged = {
|
|
152
|
-
id,
|
|
153
|
-
source,
|
|
154
|
-
...
|
|
155
|
-
...(global ?? {}),
|
|
156
|
-
...(project ?? {}),
|
|
157
|
-
env: {
|
|
158
|
-
...(builtin?.env ?? {}),
|
|
159
|
-
...(global?.env ?? {}),
|
|
160
|
-
...(project?.env ?? {}),
|
|
161
|
-
},
|
|
162
|
-
languageIdByExtension: {
|
|
163
|
-
...(builtin?.languageIdByExtension ?? {}),
|
|
164
|
-
...(global?.languageIdByExtension ?? {}),
|
|
165
|
-
...(project?.languageIdByExtension ?? {}),
|
|
166
|
-
},
|
|
72
|
+
id: entry.id,
|
|
73
|
+
source: sourceForScope(entry.scope),
|
|
74
|
+
...entry.value,
|
|
167
75
|
};
|
|
168
|
-
if (merged.enabled === false)
|
|
169
|
-
continue;
|
|
170
76
|
const languages = merged.languages ?? [];
|
|
171
|
-
const extensions = merged.extensions?.map((
|
|
77
|
+
const extensions = merged.extensions?.map((value) => value.toLowerCase()) ?? [];
|
|
172
78
|
if (languages.length === 0 || extensions.length === 0)
|
|
173
79
|
continue;
|
|
174
|
-
const languageIdByExtension = normalizeLanguageIds(id, merged, languages, extensions);
|
|
80
|
+
const languageIdByExtension = normalizeLanguageIds(entry.id, merged, languages, extensions);
|
|
175
81
|
let command = merged.command;
|
|
176
|
-
|
|
177
|
-
|
|
82
|
+
const executableCandidates = BUILTIN_EXECUTABLE_CANDIDATES[entry.id];
|
|
83
|
+
if (!command && executableCandidates) {
|
|
84
|
+
command = await findExecutable(executableCandidates, env);
|
|
178
85
|
if (!command)
|
|
179
86
|
continue;
|
|
180
87
|
}
|
|
181
88
|
if (!command) {
|
|
182
|
-
throw new LanguageServerConfigurationError("code.configuration_invalid", `Language-server definition ${id} requires a command.`);
|
|
89
|
+
throw new LanguageServerConfigurationError("code.configuration_invalid", `Language-server definition ${entry.id} requires a command.`);
|
|
183
90
|
}
|
|
184
91
|
const normalized = {
|
|
185
|
-
id,
|
|
92
|
+
id: entry.id,
|
|
186
93
|
command,
|
|
187
94
|
args: merged.args ?? [],
|
|
188
95
|
env: merged.env ?? {},
|
|
@@ -190,15 +97,33 @@ async function effectiveDefinitions(globalConfig, projectConfig, env) {
|
|
|
190
97
|
extensions,
|
|
191
98
|
languageIdByExtension,
|
|
192
99
|
projectMarkers: merged.projectMarkers ?? [],
|
|
193
|
-
source,
|
|
100
|
+
source: merged.source,
|
|
194
101
|
};
|
|
102
|
+
const configFingerprint = createHash("sha256").update(JSON.stringify(normalized)).digest("hex");
|
|
103
|
+
const executionRequirement = projectId ? projectExecutionRequirement({
|
|
104
|
+
projectId,
|
|
105
|
+
resolution,
|
|
106
|
+
entryKey: `servers.${entry.id}`,
|
|
107
|
+
display: { kind: "language-server", name: entry.id },
|
|
108
|
+
}) : undefined;
|
|
195
109
|
definitions.push({
|
|
196
110
|
...normalized,
|
|
197
|
-
|
|
111
|
+
...(executionRequirement ? { executionRequirement } : {}),
|
|
112
|
+
configFingerprint,
|
|
113
|
+
fingerprint: configFingerprint,
|
|
198
114
|
});
|
|
199
115
|
}
|
|
200
116
|
return definitions;
|
|
201
117
|
}
|
|
118
|
+
function sourceForScope(scope) {
|
|
119
|
+
if (scope === "built-in")
|
|
120
|
+
return "builtin";
|
|
121
|
+
if (scope === "user")
|
|
122
|
+
return "global";
|
|
123
|
+
if (scope === "project" || scope === "project-local")
|
|
124
|
+
return scope;
|
|
125
|
+
throw new Error(`Unsupported Language Server config scope: ${scope}`);
|
|
126
|
+
}
|
|
202
127
|
function normalizeLanguageIds(id, definition, languages, extensions) {
|
|
203
128
|
const mapping = Object.fromEntries(Object.entries(definition.languageIdByExtension ?? {})
|
|
204
129
|
.map(([extension, languageId]) => [extension.toLowerCase(), languageId]));
|
|
@@ -308,6 +233,3 @@ function isWithin(root, candidate) {
|
|
|
308
233
|
const rel = relative(root, candidate);
|
|
309
234
|
return rel === "" || (!rel.startsWith(`..${sep}`) && rel !== ".." && !isAbsolute(rel));
|
|
310
235
|
}
|
|
311
|
-
function isMissingFile(error) {
|
|
312
|
-
return error instanceof Error && "code" in error && error.code === "ENOENT";
|
|
313
|
-
}
|
|
@@ -4,6 +4,7 @@ import { resolve, sep } from "node:path";
|
|
|
4
4
|
import { CodeIntelligenceError, LanguageService, languageServiceKey, } from "../code-intelligence.js";
|
|
5
5
|
import { LanguageServerConfigurationError, resolveLanguageProject, } from "../language-server-config.js";
|
|
6
6
|
import { managedLanguageServerIdForCommand, managedLanguageServerRuntimeIdentity, managedTypeScriptTsserverPath, withManagedLanguageServerPath, } from "./managed-language-servers.js";
|
|
7
|
+
import { compatibilityAllowProjectExecutionTrustPolicy, } from "../../runtime/security/project-execution-trust.js";
|
|
7
8
|
const LANGUAGE_SERVICE_IDLE_MS = 10 * 60 * 1_000;
|
|
8
9
|
const LANGUAGE_SERVICE_CLEANUP_INTERVAL_MS = 60 * 1_000;
|
|
9
10
|
const MAX_LANGUAGE_SERVICES = 16;
|
|
@@ -26,8 +27,10 @@ export class CodeIntelligenceManager {
|
|
|
26
27
|
cleanupTimer;
|
|
27
28
|
policy;
|
|
28
29
|
crashCooldownMs;
|
|
30
|
+
projectExecutionTrustPolicy;
|
|
29
31
|
constructor(config, options = {}) {
|
|
30
32
|
this.config = config;
|
|
33
|
+
this.projectExecutionTrustPolicy = options.projectExecutionTrustPolicy ?? compatibilityAllowProjectExecutionTrustPolicy;
|
|
31
34
|
this.crashCooldownMs = positiveInteger(options.crashCooldownMs, LANGUAGE_SERVICE_CRASH_COOLDOWN_MS, "crashCooldownMs");
|
|
32
35
|
this.policy = {
|
|
33
36
|
idleMs: positiveInteger(options.idleMs, LANGUAGE_SERVICE_IDLE_MS, "idleMs"),
|
|
@@ -47,24 +50,31 @@ export class CodeIntelligenceManager {
|
|
|
47
50
|
this.cleanupTimer.unref();
|
|
48
51
|
}
|
|
49
52
|
async run(workspaceRoot, input, options = {}) {
|
|
53
|
+
const canonicalWorkspaceRoot = await realpath(resolve(workspaceRoot));
|
|
54
|
+
this.assertWorkspaceRootAvailable(canonicalWorkspaceRoot);
|
|
50
55
|
let project;
|
|
51
|
-
let canonicalWorkspaceRoot;
|
|
52
56
|
try {
|
|
53
|
-
canonicalWorkspaceRoot = await realpath(resolve(workspaceRoot));
|
|
54
|
-
this.assertWorkspaceRootAvailable(canonicalWorkspaceRoot);
|
|
55
57
|
project = withManagedLanguageServerRuntime(await resolveLanguageProject({
|
|
56
58
|
workspaceRoot: canonicalWorkspaceRoot,
|
|
57
59
|
sourcePath: input.path,
|
|
58
|
-
|
|
60
|
+
configDir: this.config.configDir,
|
|
59
61
|
env: withManagedLanguageServerPath(process.env, this.config.configDir),
|
|
62
|
+
sourceRuntime: this.config.configRuntime.sources,
|
|
60
63
|
}), this.config.configDir);
|
|
61
64
|
}
|
|
62
65
|
catch (error) {
|
|
63
66
|
if (error instanceof LanguageServerConfigurationError) {
|
|
67
|
+
if (error.availableDefinitionFingerprints) {
|
|
68
|
+
await this.invalidateUnavailableServices(canonicalWorkspaceRoot, error.availableDefinitionFingerprints);
|
|
69
|
+
}
|
|
64
70
|
throw new CodeIntelligenceError(error.code, error.message);
|
|
65
71
|
}
|
|
66
72
|
throw error;
|
|
67
73
|
}
|
|
74
|
+
if (project.definition.executionRequirement) {
|
|
75
|
+
await this.projectExecutionTrustPolicy.authorize(project.definition.executionRequirement);
|
|
76
|
+
}
|
|
77
|
+
await this.invalidateUnavailableServices(canonicalWorkspaceRoot, project.availableDefinitionFingerprints);
|
|
68
78
|
await this.invalidateChangedServices(project);
|
|
69
79
|
const identity = languageServiceKey(project);
|
|
70
80
|
this.assertNotCoolingDown(identity, project);
|
|
@@ -217,6 +227,27 @@ export class CodeIntelligenceManager {
|
|
|
217
227
|
}
|
|
218
228
|
throw new CodeIntelligenceError("code.language_service_cooldown", `Language server ${project.definition.id} is cooling down after repeated crashes; retry in ${remaining}ms.`);
|
|
219
229
|
}
|
|
230
|
+
async invalidateUnavailableServices(workspaceRoot, availableDefinitionFingerprints) {
|
|
231
|
+
const root = resolve(workspaceRoot);
|
|
232
|
+
const invalidated = [];
|
|
233
|
+
for (const [key, service] of this.services) {
|
|
234
|
+
if (resolve(service.workspaceRoot) !== root)
|
|
235
|
+
continue;
|
|
236
|
+
const currentFingerprint = availableDefinitionFingerprints[service.project.definition.id];
|
|
237
|
+
if (currentFingerprint === service.project.definition.configFingerprint)
|
|
238
|
+
continue;
|
|
239
|
+
this.invalidatedServiceKeys.add(key);
|
|
240
|
+
this.crashStates.delete(key);
|
|
241
|
+
if (service.isIdle)
|
|
242
|
+
invalidated.push([key, service]);
|
|
243
|
+
}
|
|
244
|
+
for (const [key, service] of invalidated) {
|
|
245
|
+
if (this.services.get(key) === service)
|
|
246
|
+
this.services.delete(key);
|
|
247
|
+
this.invalidatedServiceKeys.delete(key);
|
|
248
|
+
await service.shutdown();
|
|
249
|
+
}
|
|
250
|
+
}
|
|
220
251
|
async invalidateChangedServices(project) {
|
|
221
252
|
const root = resolve(project.projectRoot);
|
|
222
253
|
const invalidated = [];
|
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
import * as z from "zod/v4";
|
|
2
|
+
import { defineConfigDomain } from "../../runtime/config/definition/definition.js";
|
|
3
|
+
export const HOOK_EVENTS = [
|
|
4
|
+
"WorkspaceOpen",
|
|
5
|
+
"BeforeTool",
|
|
6
|
+
"AfterTool",
|
|
7
|
+
"AfterToolFailure",
|
|
8
|
+
"ExternalMcpBeforeForward",
|
|
9
|
+
"ExternalMcpAfterForward",
|
|
10
|
+
"AfterFileChange",
|
|
11
|
+
"BeforeWorktreeClose",
|
|
12
|
+
"AfterWorktreeClose",
|
|
13
|
+
"SubagentStart",
|
|
14
|
+
"SubagentStop",
|
|
15
|
+
];
|
|
16
|
+
const schemaMetadata = {
|
|
17
|
+
$schema: z.string().optional().describe("Editor-only JSON Schema URL. Ignored by ForgeRelay resolution."),
|
|
18
|
+
};
|
|
19
|
+
const nonEmptyString = z.string().trim().min(1);
|
|
20
|
+
const regexString = z.string().min(1).refine((value) => {
|
|
21
|
+
try {
|
|
22
|
+
new RegExp(value);
|
|
23
|
+
return true;
|
|
24
|
+
}
|
|
25
|
+
catch {
|
|
26
|
+
return false;
|
|
27
|
+
}
|
|
28
|
+
}, "Must be a valid regular expression.");
|
|
29
|
+
export const hookMatcherSchema = z.object({
|
|
30
|
+
tool: nonEmptyString.optional(),
|
|
31
|
+
commandRegex: regexString.optional(),
|
|
32
|
+
pathRegex: regexString.optional(),
|
|
33
|
+
provider: nonEmptyString.optional(),
|
|
34
|
+
workspaceMode: z.enum(["checkout", "worktree"]).optional(),
|
|
35
|
+
capability: nonEmptyString.optional(),
|
|
36
|
+
externalServer: nonEmptyString.optional(),
|
|
37
|
+
externalTool: nonEmptyString.optional(),
|
|
38
|
+
}).strict();
|
|
39
|
+
export const hookEntrySchema = z.object({
|
|
40
|
+
event: z.enum(HOOK_EVENTS),
|
|
41
|
+
matcher: hookMatcherSchema.optional(),
|
|
42
|
+
command: nonEmptyString,
|
|
43
|
+
timeoutSeconds: z.number().int().min(1).max(300).optional(),
|
|
44
|
+
report: z.boolean().optional(),
|
|
45
|
+
}).strict();
|
|
46
|
+
const resolvedHookEntrySchema = hookEntrySchema.extend({
|
|
47
|
+
name: nonEmptyString.optional(),
|
|
48
|
+
order: z.number().int().nonnegative().optional(),
|
|
49
|
+
});
|
|
50
|
+
export const hookDisabledEntrySchema = z.object({ disabled: z.literal(true) }).strict();
|
|
51
|
+
export const hookCanonicalFileSchema = z.union([
|
|
52
|
+
hookEntrySchema.extend(schemaMetadata),
|
|
53
|
+
hookDisabledEntrySchema.extend(schemaMetadata),
|
|
54
|
+
]);
|
|
55
|
+
export const hookEntriesSchema = z.record(z.string().min(1), z.union([
|
|
56
|
+
hookDisabledEntrySchema,
|
|
57
|
+
z.array(resolvedHookEntrySchema).min(1),
|
|
58
|
+
]));
|
|
59
|
+
const legacyHookHandlerSchema = z.object({
|
|
60
|
+
name: nonEmptyString.optional(),
|
|
61
|
+
command: nonEmptyString,
|
|
62
|
+
timeoutSeconds: z.number().int().min(1).max(300).optional(),
|
|
63
|
+
report: z.boolean().optional(),
|
|
64
|
+
}).strict();
|
|
65
|
+
const legacyDirectHookRuleSchema = legacyHookHandlerSchema.extend({
|
|
66
|
+
matcher: hookMatcherSchema.optional(),
|
|
67
|
+
});
|
|
68
|
+
const legacyHookRuleSchema = z.union([
|
|
69
|
+
legacyDirectHookRuleSchema,
|
|
70
|
+
z.object({
|
|
71
|
+
matcher: hookMatcherSchema.optional(),
|
|
72
|
+
handlers: z.array(legacyHookHandlerSchema).min(1),
|
|
73
|
+
}).strict(),
|
|
74
|
+
]);
|
|
75
|
+
const legacyHookConfigSchema = z.object(Object.fromEntries(HOOK_EVENTS.map((event) => [event, z.array(legacyHookRuleSchema).optional()]))).strict();
|
|
76
|
+
export function normalizeLegacyHookEntries(value) {
|
|
77
|
+
const parsed = legacyHookConfigSchema.parse(value ?? {});
|
|
78
|
+
const hooks = {};
|
|
79
|
+
let order = 0;
|
|
80
|
+
for (const event of HOOK_EVENTS) {
|
|
81
|
+
for (const [ruleIndex, rule] of (parsed[event] ?? []).entries()) {
|
|
82
|
+
const matcher = rule.matcher;
|
|
83
|
+
const handlers = "handlers" in rule ? rule.handlers : [rule];
|
|
84
|
+
for (const [handlerIndex, handler] of handlers.entries()) {
|
|
85
|
+
const key = handler.name ?? legacyHookKey(event, ruleIndex, handlerIndex);
|
|
86
|
+
const entry = {
|
|
87
|
+
event,
|
|
88
|
+
...(matcher ? { matcher } : {}),
|
|
89
|
+
command: handler.command,
|
|
90
|
+
...(handler.name ? { name: handler.name } : {}),
|
|
91
|
+
order,
|
|
92
|
+
...(handler.timeoutSeconds === undefined ? {} : { timeoutSeconds: handler.timeoutSeconds }),
|
|
93
|
+
...(handler.report === undefined ? {} : { report: handler.report }),
|
|
94
|
+
};
|
|
95
|
+
order += 1;
|
|
96
|
+
const current = hooks[key];
|
|
97
|
+
if (current && !Array.isArray(current)) {
|
|
98
|
+
throw new Error(`Legacy Hook ${key} conflicts with a disabled Hook entry.`);
|
|
99
|
+
}
|
|
100
|
+
hooks[key] = [...(current ?? []), entry];
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
return hooks;
|
|
105
|
+
}
|
|
106
|
+
function legacyHookKey(event, ruleIndex, handlerIndex) {
|
|
107
|
+
return `@legacy/${event}/${String(ruleIndex + 1).padStart(4, "0")}/${String(handlerIndex + 1).padStart(4, "0")}`;
|
|
108
|
+
}
|
|
109
|
+
const HOOK_SCOPES = ["project-local", "project", "user"];
|
|
110
|
+
export const hooksConfigDefinition = defineConfigDomain({
|
|
111
|
+
domain: "hooks",
|
|
112
|
+
title: "ForgeRelay Lifecycle Hook configuration",
|
|
113
|
+
description: "Lifecycle Hook files composed by stable file name across user, project, and Project Local scopes.",
|
|
114
|
+
fileShape: {
|
|
115
|
+
kind: "keyed-entry",
|
|
116
|
+
field: "hooks",
|
|
117
|
+
fileSchema: hookCanonicalFileSchema,
|
|
118
|
+
normalizeEntry: (value) => {
|
|
119
|
+
if (isRecord(value) && value.disabled === true)
|
|
120
|
+
return { disabled: true };
|
|
121
|
+
return [value];
|
|
122
|
+
},
|
|
123
|
+
},
|
|
124
|
+
fields: {
|
|
125
|
+
hooks: {
|
|
126
|
+
schema: hookEntriesSchema,
|
|
127
|
+
description: "Lifecycle Hooks keyed by stable Hook name.",
|
|
128
|
+
required: true,
|
|
129
|
+
legalScopes: HOOK_SCOPES,
|
|
130
|
+
merge: "keyed",
|
|
131
|
+
reload: "hot",
|
|
132
|
+
sensitivity: "sensitive",
|
|
133
|
+
interpolation: "none",
|
|
134
|
+
builtIn: { kind: "none" },
|
|
135
|
+
executionEffect: (value) => isRecord(value) && value.disabled === true ? "none" : "process",
|
|
136
|
+
},
|
|
137
|
+
},
|
|
138
|
+
});
|
|
139
|
+
function isRecord(value) {
|
|
140
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
141
|
+
}
|
|
@@ -1,9 +1,11 @@
|
|
|
1
1
|
import { performance } from "node:perf_hooks";
|
|
2
|
+
import { ConfigSourceRuntime } from "../../runtime/config/runtime/source-refresh.js";
|
|
2
3
|
import { commandPreview, logEvent, workspaceLogLabel } from "../../runtime/logging/logger.js";
|
|
3
4
|
import { resolveCompatibilityCommandShellRuntime, snapshotCommandShellRuntime, } from "../../runtime/shell/command-shell-runtime.js";
|
|
5
|
+
import { compatibilityAllowProjectExecutionTrustPolicy, } from "../../runtime/security/project-execution-trust.js";
|
|
4
6
|
import { resolveShellCommandForRuntime } from "../process/process-platform.js";
|
|
5
7
|
import { executeHookCommand } from "./command-runner.js";
|
|
6
|
-
import {
|
|
8
|
+
import { resolveHookExecutionPlan, } from "./hooks.js";
|
|
7
9
|
export class ExternalMcpTransformError extends Error {
|
|
8
10
|
server;
|
|
9
11
|
tool;
|
|
@@ -23,12 +25,18 @@ export class ExternalMcpTransformRunner {
|
|
|
23
25
|
hooks;
|
|
24
26
|
logging;
|
|
25
27
|
baseEnv;
|
|
28
|
+
configDir;
|
|
29
|
+
sourceRuntime;
|
|
30
|
+
projectExecutionTrustPolicy;
|
|
26
31
|
commandShellRuntime;
|
|
27
32
|
maxCaptureBytes;
|
|
28
|
-
constructor(hooks, logging, baseEnv = process.env, commandShellRuntime, mediaMaxBytes = 20 * 1024 * 1024) {
|
|
33
|
+
constructor(hooks, logging, baseEnv = process.env, commandShellRuntime, mediaMaxBytes = 20 * 1024 * 1024, configDir, sourceRuntime = new ConfigSourceRuntime(), projectExecutionTrustPolicy = compatibilityAllowProjectExecutionTrustPolicy) {
|
|
29
34
|
this.hooks = hooks;
|
|
30
35
|
this.logging = logging;
|
|
31
36
|
this.baseEnv = baseEnv;
|
|
37
|
+
this.configDir = configDir;
|
|
38
|
+
this.sourceRuntime = sourceRuntime;
|
|
39
|
+
this.projectExecutionTrustPolicy = projectExecutionTrustPolicy;
|
|
32
40
|
this.commandShellRuntime = snapshotCommandShellRuntime(commandShellRuntime ?? resolveCompatibilityCommandShellRuntime(process.platform, baseEnv));
|
|
33
41
|
this.maxCaptureBytes = Math.max(MIN_TRANSFORM_CAPTURE_BYTES, Math.ceil((mediaMaxBytes * 4) / 3) + TRANSFORM_CAPTURE_OVERHEAD_BYTES);
|
|
34
42
|
}
|
|
@@ -49,21 +57,20 @@ export class ExternalMcpTransformRunner {
|
|
|
49
57
|
workspaceMode: context.workspaceMode,
|
|
50
58
|
payload: transformMetadata(context, phase),
|
|
51
59
|
};
|
|
52
|
-
const
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
return [];
|
|
60
|
-
return rule.handlers.map((handler) => ({ scope, handler }));
|
|
60
|
+
const plan = await resolveHookExecutionPlan({
|
|
61
|
+
event,
|
|
62
|
+
invocation,
|
|
63
|
+
legacyUser: this.hooks,
|
|
64
|
+
...(this.configDir ? { configDir: this.configDir } : {}),
|
|
65
|
+
...(context.project ? { project: context.project } : {}),
|
|
66
|
+
sourceRuntime: this.sourceRuntime,
|
|
61
67
|
});
|
|
68
|
+
const handlers = plan.handlers;
|
|
62
69
|
let value = initialValue;
|
|
63
70
|
const transforms = [];
|
|
64
71
|
for (const [index, entry] of handlers.entries()) {
|
|
65
72
|
signal?.throwIfAborted();
|
|
66
|
-
const execution = await this.runHandler(event, phase, context, entry.handler, entry.scope, index, value, signal);
|
|
73
|
+
const execution = await this.runHandler(event, phase, context, entry.handler, entry.scope, index, value, entry.executionRequirement, signal);
|
|
67
74
|
value = execution.value;
|
|
68
75
|
transforms.push({
|
|
69
76
|
phase,
|
|
@@ -74,7 +81,7 @@ export class ExternalMcpTransformRunner {
|
|
|
74
81
|
}
|
|
75
82
|
return { value, transforms };
|
|
76
83
|
}
|
|
77
|
-
async runHandler(event, phase, context, handler, scope, index, currentValue, signal) {
|
|
84
|
+
async runHandler(event, phase, context, handler, scope, index, currentValue, executionRequirement, signal) {
|
|
78
85
|
const startedAt = performance.now();
|
|
79
86
|
const name = handler.name ?? `${event} handler ${index + 1}`;
|
|
80
87
|
const shell = resolveShellCommandForRuntime(handler.command, this.commandShellRuntime);
|
|
@@ -95,6 +102,8 @@ export class ExternalMcpTransformRunner {
|
|
|
95
102
|
};
|
|
96
103
|
const stdin = JSON.stringify(transformInput(phase, context, currentValue));
|
|
97
104
|
try {
|
|
105
|
+
if (executionRequirement)
|
|
106
|
+
await this.projectExecutionTrustPolicy.authorize(executionRequirement);
|
|
98
107
|
const result = await executeHookCommand({
|
|
99
108
|
executable: shell.executable,
|
|
100
109
|
args: shell.args,
|