@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,7 +1,10 @@
|
|
|
1
|
-
import { existsSync } from "node:fs";
|
|
2
|
-
import { readdir, readFile } from "node:fs/promises";
|
|
3
1
|
import { basename, join, resolve } from "node:path";
|
|
2
|
+
import * as z from "zod/v4";
|
|
4
3
|
import { parse as parseYaml } from "yaml";
|
|
4
|
+
import { defineConfigDomain } from "../runtime/config/definition/definition.js";
|
|
5
|
+
import { resolveConfigDomain } from "../runtime/config/resolution/resolver.js";
|
|
6
|
+
import { resolveProjectContext } from "../workspaces/state/project-context.js";
|
|
7
|
+
import { projectExecutionRequirement } from "../runtime/security/project-execution-trust.js";
|
|
5
8
|
export const SUBAGENT_PROVIDERS = [
|
|
6
9
|
"codex",
|
|
7
10
|
"claude",
|
|
@@ -11,23 +14,125 @@ export const SUBAGENT_PROVIDERS = [
|
|
|
11
14
|
"copilot",
|
|
12
15
|
];
|
|
13
16
|
const FRONTMATTER_DELIMITER = "---";
|
|
14
|
-
const
|
|
17
|
+
const CANONICAL_PRIORITY = 100;
|
|
18
|
+
const LEGACY_PRIORITY = 0;
|
|
19
|
+
const LEGACY_DEPRECATION = {
|
|
20
|
+
since: "1.2.0",
|
|
21
|
+
removeIn: "1.4.0",
|
|
22
|
+
replacement: "subagents/",
|
|
23
|
+
};
|
|
24
|
+
const profileNameSchema = z.string().trim().min(1);
|
|
25
|
+
const providerSchema = z.enum(SUBAGENT_PROVIDERS);
|
|
26
|
+
const optionalNonEmptyString = z.string().trim().min(1).optional();
|
|
27
|
+
const activeProfileFrontmatterSchema = z.object({
|
|
28
|
+
name: profileNameSchema.optional(),
|
|
29
|
+
description: z.string().trim().min(1),
|
|
30
|
+
provider: providerSchema,
|
|
31
|
+
model: optionalNonEmptyString,
|
|
32
|
+
thinking: optionalNonEmptyString,
|
|
33
|
+
disabled: z.literal(false).optional(),
|
|
34
|
+
}).strict();
|
|
35
|
+
const disabledProfileFrontmatterSchema = z.object({
|
|
36
|
+
name: profileNameSchema.optional(),
|
|
37
|
+
description: optionalNonEmptyString,
|
|
38
|
+
provider: providerSchema.optional(),
|
|
39
|
+
model: optionalNonEmptyString,
|
|
40
|
+
thinking: optionalNonEmptyString,
|
|
41
|
+
disabled: z.literal(true),
|
|
42
|
+
}).strict();
|
|
43
|
+
const subagentProfileValueSchema = z.object({
|
|
44
|
+
description: z.string().trim().min(1),
|
|
45
|
+
provider: providerSchema,
|
|
46
|
+
model: optionalNonEmptyString,
|
|
47
|
+
thinking: optionalNonEmptyString,
|
|
48
|
+
body: z.string(),
|
|
49
|
+
}).strict();
|
|
50
|
+
const subagentProfileDisabledSchema = z.object({ disabled: z.literal(true) }).strict();
|
|
51
|
+
const subagentProfilesSchema = z.record(profileNameSchema, z.union([subagentProfileValueSchema, subagentProfileDisabledSchema]));
|
|
52
|
+
export const subagentProfilesConfigDefinition = defineConfigDomain({
|
|
53
|
+
domain: "subagents",
|
|
54
|
+
title: "ForgeRelay Subagent Profile configuration",
|
|
55
|
+
description: "Markdown Subagent Profiles resolved by profile name across user, Project, and Project Local scopes.",
|
|
56
|
+
schemaOutput: "none",
|
|
57
|
+
fields: {
|
|
58
|
+
profiles: {
|
|
59
|
+
schema: subagentProfilesSchema,
|
|
60
|
+
description: "Subagent Profiles keyed by effective profile name.",
|
|
61
|
+
required: true,
|
|
62
|
+
legalScopes: ["project-local", "project", "user"],
|
|
63
|
+
merge: "keyed",
|
|
64
|
+
reload: "hot",
|
|
65
|
+
sensitivity: "sensitive",
|
|
66
|
+
interpolation: "none",
|
|
67
|
+
builtIn: { kind: "none" },
|
|
68
|
+
executionEffect: (value) => isRecord(value) && value.disabled === true ? "none" : "process",
|
|
69
|
+
},
|
|
70
|
+
},
|
|
71
|
+
});
|
|
72
|
+
export async function resolveSubagentProfilesConfig(config, workspaceRoot) {
|
|
73
|
+
const project = await resolveProjectContext(config.configDir, workspaceRoot);
|
|
74
|
+
return resolveSubagentProfilesConfigSources({
|
|
75
|
+
configDir: config.configDir,
|
|
76
|
+
sourceRuntime: config.configRuntime.sources,
|
|
77
|
+
projectSharedConfigDir: project.sharedConfigDir,
|
|
78
|
+
projectLocalConfigDir: project.localConfigDir,
|
|
79
|
+
});
|
|
80
|
+
}
|
|
81
|
+
export function resolveSubagentProfilesConfigSources(input) {
|
|
82
|
+
const sources = [];
|
|
83
|
+
const refreshDiagnostics = [];
|
|
84
|
+
appendProfileDirectory(sources, refreshDiagnostics, input.sourceRuntime, "user", join(input.configDir, "agents"), false);
|
|
85
|
+
appendProfileDirectory(sources, refreshDiagnostics, input.sourceRuntime, "user", join(input.configDir, "subagents"), true);
|
|
86
|
+
if (input.projectSharedConfigDir) {
|
|
87
|
+
appendProfileDirectory(sources, refreshDiagnostics, input.sourceRuntime, "project", join(input.projectSharedConfigDir, "agents"), false);
|
|
88
|
+
appendProfileDirectory(sources, refreshDiagnostics, input.sourceRuntime, "project", join(input.projectSharedConfigDir, "subagents"), true);
|
|
89
|
+
}
|
|
90
|
+
if (input.projectLocalConfigDir) {
|
|
91
|
+
appendProfileDirectory(sources, refreshDiagnostics, input.sourceRuntime, "project-local", join(input.projectLocalConfigDir, "subagents"), true);
|
|
92
|
+
}
|
|
93
|
+
const resolution = resolveConfigDomain({
|
|
94
|
+
definition: subagentProfilesConfigDefinition,
|
|
95
|
+
sources,
|
|
96
|
+
});
|
|
97
|
+
resolution.diagnostics = mergeDiagnostics(resolution.diagnostics, refreshDiagnostics);
|
|
98
|
+
return resolution;
|
|
99
|
+
}
|
|
15
100
|
export async function loadSubagentProfiles(config, workspaceRoot) {
|
|
16
101
|
if (!config.subagents)
|
|
17
102
|
return [];
|
|
18
|
-
const
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
103
|
+
const project = await resolveProjectContext(config.configDir, workspaceRoot);
|
|
104
|
+
const resolution = resolveSubagentProfilesConfigSources({
|
|
105
|
+
configDir: config.configDir,
|
|
106
|
+
sourceRuntime: config.configRuntime.sources,
|
|
107
|
+
projectSharedConfigDir: project.sharedConfigDir,
|
|
108
|
+
projectLocalConfigDir: project.localConfigDir,
|
|
109
|
+
});
|
|
110
|
+
for (const diagnostic of resolution.diagnostics) {
|
|
111
|
+
if (diagnostic.severity !== "error" || diagnostic.diagnosticChanged === false)
|
|
112
|
+
continue;
|
|
113
|
+
console.warn(`Skipping invalid subagent profile ${diagnostic.source.location ?? diagnostic.source.id}: ${diagnostic.message}`);
|
|
27
114
|
}
|
|
28
|
-
return
|
|
29
|
-
.
|
|
30
|
-
|
|
115
|
+
return effectiveSubagentProfileEntries(resolution)
|
|
116
|
+
.map((entry) => {
|
|
117
|
+
const executionRequirement = projectExecutionRequirement({
|
|
118
|
+
projectId: project.id,
|
|
119
|
+
resolution,
|
|
120
|
+
entryKey: `profiles.${entry.name}`,
|
|
121
|
+
display: { kind: "subagent-profile", name: entry.name },
|
|
122
|
+
});
|
|
123
|
+
return {
|
|
124
|
+
name: entry.name,
|
|
125
|
+
description: entry.value.description,
|
|
126
|
+
provider: entry.value.provider,
|
|
127
|
+
...(entry.value.model ? { model: entry.value.model } : {}),
|
|
128
|
+
...(entry.value.thinking ? { thinking: entry.value.thinking } : {}),
|
|
129
|
+
filePath: entry.filePath,
|
|
130
|
+
body: entry.value.body,
|
|
131
|
+
disabled: false,
|
|
132
|
+
...(executionRequirement ? { executionRequirement } : {}),
|
|
133
|
+
};
|
|
134
|
+
})
|
|
135
|
+
.sort((left, right) => left.name.localeCompare(right.name));
|
|
31
136
|
}
|
|
32
137
|
export function summarizeSubagentProfile(profile) {
|
|
33
138
|
return {
|
|
@@ -46,31 +151,205 @@ export function formatAvailableSubagentProfile(profile) {
|
|
|
46
151
|
: "";
|
|
47
152
|
return `${profile.name} (${profile.provider}${model}${thinking}${availability})`;
|
|
48
153
|
}
|
|
49
|
-
|
|
154
|
+
function effectiveSubagentProfileEntries(resolution) {
|
|
155
|
+
const values = isRecord(resolution.values.profiles) ? resolution.values.profiles : {};
|
|
156
|
+
const result = [];
|
|
157
|
+
for (const [name, value] of Object.entries(values)) {
|
|
158
|
+
const parsed = subagentProfileValueSchema.safeParse(value);
|
|
159
|
+
const entry = resolution.entries[`profiles.${name}`];
|
|
160
|
+
if (!parsed.success || !entry || !entry.effective.source.location) {
|
|
161
|
+
throw new Error(`Subagent Profile resolver produced an invalid effective entry for ${name}.`);
|
|
162
|
+
}
|
|
163
|
+
result.push({
|
|
164
|
+
name,
|
|
165
|
+
value: parsed.data,
|
|
166
|
+
scope: entry.effective.source.scope,
|
|
167
|
+
filePath: entry.effective.source.location,
|
|
168
|
+
});
|
|
169
|
+
}
|
|
170
|
+
return result;
|
|
171
|
+
}
|
|
172
|
+
function appendProfileDirectory(sources, diagnostics, sourceRuntime, scope, directory, canonical) {
|
|
50
173
|
const resolvedDirectory = resolve(directory);
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
174
|
+
const refreshed = sourceRuntime.refreshDirectory({
|
|
175
|
+
key: `subagents:${canonical ? "canonical" : "legacy"}:${scope}:${resolvedDirectory}`,
|
|
176
|
+
directory: resolvedDirectory,
|
|
177
|
+
include: (name) => name.endsWith(".md"),
|
|
178
|
+
parse: (_name, content, filePath) => parseProfileSourceDocument(canonical, content, filePath),
|
|
179
|
+
parseIssue: (error) => ({
|
|
180
|
+
code: "invalid_source",
|
|
181
|
+
message: "Subagent Profile is invalid.",
|
|
182
|
+
...(error instanceof SubagentProfileSourceError && error.keyHint
|
|
183
|
+
? { keyHint: error.keyHint }
|
|
184
|
+
: {}),
|
|
185
|
+
}),
|
|
186
|
+
readIssue: { code: "invalid_source", message: "Subagent Profile could not be read." },
|
|
187
|
+
});
|
|
188
|
+
if (refreshed.state === "invalid" && refreshed.issue) {
|
|
189
|
+
diagnostics.push({
|
|
190
|
+
severity: "error",
|
|
191
|
+
code: refreshed.issue.code,
|
|
192
|
+
source: {
|
|
193
|
+
id: `${canonical ? "canonical" : "legacy"}:${scope}:subagents-directory`,
|
|
194
|
+
scope,
|
|
195
|
+
kind: "file",
|
|
196
|
+
location: resolvedDirectory,
|
|
197
|
+
priority: canonical ? CANONICAL_PRIORITY : LEGACY_PRIORITY,
|
|
198
|
+
},
|
|
199
|
+
message: refreshed.issue.message,
|
|
200
|
+
diagnosticChanged: refreshed.diagnosticChanged,
|
|
201
|
+
});
|
|
202
|
+
}
|
|
203
|
+
for (const unit of refreshed.units) {
|
|
204
|
+
const filePath = unit.status.path;
|
|
205
|
+
if (unit.value !== undefined) {
|
|
206
|
+
const source = profileSource(scope, filePath, unit.name, canonical, unit.value);
|
|
207
|
+
sources.push(source);
|
|
208
|
+
if (unit.status.state === "invalid" && unit.issue) {
|
|
209
|
+
diagnostics.push({
|
|
210
|
+
severity: "error",
|
|
211
|
+
code: unit.issue.code,
|
|
212
|
+
source: sourceReference(source),
|
|
213
|
+
message: unit.issue.message,
|
|
214
|
+
usingLastKnownGood: true,
|
|
215
|
+
diagnosticChanged: unit.status.diagnosticChanged,
|
|
216
|
+
});
|
|
217
|
+
}
|
|
59
218
|
continue;
|
|
60
|
-
const filePath = join(resolvedDirectory, entry.name);
|
|
61
|
-
try {
|
|
62
|
-
profiles.push(await loadProfileFile(filePath));
|
|
63
219
|
}
|
|
64
|
-
|
|
65
|
-
|
|
220
|
+
if (unit.status.state === "invalid") {
|
|
221
|
+
sources.push(invalidProfileSource(scope, filePath, profileKeyHintFromIssue(unit.issue) ?? basename(unit.name, ".md"), canonical, unit.issue?.message ?? "Subagent Profile is invalid.", unit.issue?.code));
|
|
66
222
|
}
|
|
67
223
|
}
|
|
68
|
-
return profiles;
|
|
69
224
|
}
|
|
70
|
-
|
|
71
|
-
const
|
|
225
|
+
function profileSource(scope, filePath, fileName, canonical, profile) {
|
|
226
|
+
const value = profile.disabled
|
|
227
|
+
? { disabled: true }
|
|
228
|
+
: {
|
|
229
|
+
description: profile.description,
|
|
230
|
+
provider: profile.provider,
|
|
231
|
+
...(profile.model ? { model: profile.model } : {}),
|
|
232
|
+
...(profile.thinking ? { thinking: profile.thinking } : {}),
|
|
233
|
+
body: profile.body,
|
|
234
|
+
};
|
|
235
|
+
return {
|
|
236
|
+
id: `${canonical ? "canonical" : "legacy"}:${scope}:subagent:${fileName}`,
|
|
237
|
+
scope,
|
|
238
|
+
kind: "file",
|
|
239
|
+
location: filePath,
|
|
240
|
+
priority: canonical ? CANONICAL_PRIORITY : LEGACY_PRIORITY,
|
|
241
|
+
normalized: true,
|
|
242
|
+
...(canonical ? { shadowsLowerPriorityKeys: [`profiles.${profile.name}`] } : { deprecation: LEGACY_DEPRECATION }),
|
|
243
|
+
value: { profiles: { [profile.name]: value } },
|
|
244
|
+
};
|
|
245
|
+
}
|
|
246
|
+
function invalidProfileSource(scope, filePath, key, canonical, message, code = "invalid_source") {
|
|
247
|
+
return {
|
|
248
|
+
id: `${canonical ? "canonical" : "legacy"}:${scope}:subagent:${basename(filePath)}`,
|
|
249
|
+
scope,
|
|
250
|
+
kind: "file",
|
|
251
|
+
location: filePath,
|
|
252
|
+
priority: canonical ? CANONICAL_PRIORITY : LEGACY_PRIORITY,
|
|
253
|
+
...(canonical ? { shadowsLowerPriorityKeys: [`profiles.${key}`] } : { deprecation: LEGACY_DEPRECATION }),
|
|
254
|
+
error: { code, message },
|
|
255
|
+
};
|
|
256
|
+
}
|
|
257
|
+
class SubagentProfileSourceError extends Error {
|
|
258
|
+
keyHint;
|
|
259
|
+
constructor(keyHint) {
|
|
260
|
+
super("Subagent Profile source is invalid.");
|
|
261
|
+
this.keyHint = keyHint;
|
|
262
|
+
this.name = "SubagentProfileSourceError";
|
|
263
|
+
}
|
|
264
|
+
}
|
|
265
|
+
function parseProfileSourceDocument(canonical, content, filePath) {
|
|
266
|
+
const keyHint = profileKeyHint(content, filePath);
|
|
267
|
+
try {
|
|
268
|
+
return canonical
|
|
269
|
+
? canonicalProfileFromDocument(content, filePath)
|
|
270
|
+
: legacyProfileFromDocument(content, filePath);
|
|
271
|
+
}
|
|
272
|
+
catch {
|
|
273
|
+
throw new SubagentProfileSourceError(keyHint);
|
|
274
|
+
}
|
|
275
|
+
}
|
|
276
|
+
function canonicalProfileFromDocument(content, filePath) {
|
|
277
|
+
const parsed = parseFrontmatter(content, filePath);
|
|
278
|
+
const frontmatter = z.union([
|
|
279
|
+
disabledProfileFrontmatterSchema,
|
|
280
|
+
activeProfileFrontmatterSchema,
|
|
281
|
+
]).parse(parsed.frontmatter);
|
|
282
|
+
const name = frontmatter.name ?? basename(filePath, ".md");
|
|
283
|
+
if (frontmatter.disabled === true) {
|
|
284
|
+
return {
|
|
285
|
+
name,
|
|
286
|
+
description: frontmatter.description ?? "Disabled Subagent Profile.",
|
|
287
|
+
provider: frontmatter.provider ?? "codex",
|
|
288
|
+
model: frontmatter.model,
|
|
289
|
+
thinking: frontmatter.thinking,
|
|
290
|
+
filePath,
|
|
291
|
+
body: parsed.body,
|
|
292
|
+
disabled: true,
|
|
293
|
+
};
|
|
294
|
+
}
|
|
295
|
+
return {
|
|
296
|
+
name,
|
|
297
|
+
description: frontmatter.description,
|
|
298
|
+
provider: frontmatter.provider,
|
|
299
|
+
model: frontmatter.model,
|
|
300
|
+
thinking: frontmatter.thinking,
|
|
301
|
+
filePath,
|
|
302
|
+
body: parsed.body,
|
|
303
|
+
disabled: false,
|
|
304
|
+
};
|
|
305
|
+
}
|
|
306
|
+
function legacyProfileFromDocument(content, filePath) {
|
|
72
307
|
const parsed = parseFrontmatter(content, filePath);
|
|
73
|
-
return
|
|
308
|
+
return legacyProfileFromFrontmatter(parsed.frontmatter, parsed.body, filePath);
|
|
309
|
+
}
|
|
310
|
+
export function canonicalSubagentProfileDocumentFromLegacy(content, filePath) {
|
|
311
|
+
const profile = legacyProfileFromDocument(content, filePath);
|
|
312
|
+
const frontmatter = [
|
|
313
|
+
`name: ${JSON.stringify(profile.name)}`,
|
|
314
|
+
`description: ${JSON.stringify(profile.description)}`,
|
|
315
|
+
`provider: ${profile.provider}`,
|
|
316
|
+
...(profile.model ? [`model: ${JSON.stringify(profile.model)}`] : []),
|
|
317
|
+
...(profile.thinking ? [`thinking: ${JSON.stringify(profile.thinking)}`] : []),
|
|
318
|
+
...(profile.disabled ? ["disabled: true"] : []),
|
|
319
|
+
];
|
|
320
|
+
return [FRONTMATTER_DELIMITER, ...frontmatter, FRONTMATTER_DELIMITER, profile.body, ""].join("\n");
|
|
321
|
+
}
|
|
322
|
+
function legacyProfileFromFrontmatter(frontmatter, body, filePath) {
|
|
323
|
+
const name = readString(frontmatter, "name") ?? basename(filePath, ".md");
|
|
324
|
+
const description = readString(frontmatter, "description");
|
|
325
|
+
const provider = readProvider(frontmatter, filePath);
|
|
326
|
+
if (!description) {
|
|
327
|
+
throw new Error(`Subagent profile is missing description: ${filePath}`);
|
|
328
|
+
}
|
|
329
|
+
return {
|
|
330
|
+
name,
|
|
331
|
+
description,
|
|
332
|
+
provider,
|
|
333
|
+
model: readString(frontmatter, "model"),
|
|
334
|
+
thinking: readString(frontmatter, "thinking"),
|
|
335
|
+
filePath,
|
|
336
|
+
body,
|
|
337
|
+
disabled: frontmatter.disabled === true,
|
|
338
|
+
};
|
|
339
|
+
}
|
|
340
|
+
function profileKeyHint(content, filePath) {
|
|
341
|
+
try {
|
|
342
|
+
return readString(parseFrontmatter(content, filePath).frontmatter, "name");
|
|
343
|
+
}
|
|
344
|
+
catch {
|
|
345
|
+
return undefined;
|
|
346
|
+
}
|
|
347
|
+
}
|
|
348
|
+
function profileKeyHintFromIssue(issue) {
|
|
349
|
+
if (!issue || !("keyHint" in issue) || typeof issue.keyHint !== "string")
|
|
350
|
+
return undefined;
|
|
351
|
+
const trimmed = issue.keyHint.trim();
|
|
352
|
+
return trimmed || undefined;
|
|
74
353
|
}
|
|
75
354
|
function parseFrontmatter(content, filePath) {
|
|
76
355
|
const normalized = content.replace(/^\uFEFF/, "");
|
|
@@ -100,36 +379,18 @@ function parseProfileYaml(source, filePath) {
|
|
|
100
379
|
}
|
|
101
380
|
return parsed;
|
|
102
381
|
}
|
|
103
|
-
function profileFromFrontmatter(frontmatter, body, filePath) {
|
|
104
|
-
const name = readString(frontmatter, "name") ?? basename(filePath, ".md");
|
|
105
|
-
const description = readString(frontmatter, "description");
|
|
106
|
-
const provider = readProvider(frontmatter, filePath);
|
|
107
|
-
if (!description) {
|
|
108
|
-
throw new Error(`Subagent profile is missing description: ${filePath}`);
|
|
109
|
-
}
|
|
110
|
-
return {
|
|
111
|
-
name,
|
|
112
|
-
description,
|
|
113
|
-
provider,
|
|
114
|
-
model: readString(frontmatter, "model"),
|
|
115
|
-
thinking: readString(frontmatter, "thinking"),
|
|
116
|
-
filePath,
|
|
117
|
-
body,
|
|
118
|
-
disabled: frontmatter.disabled === true,
|
|
119
|
-
};
|
|
120
|
-
}
|
|
121
382
|
function readProvider(frontmatter, filePath) {
|
|
122
383
|
const provider = readString(frontmatter, "provider");
|
|
123
384
|
if (!provider) {
|
|
124
385
|
throw new Error(`Subagent profile is missing provider: ${filePath}`);
|
|
125
386
|
}
|
|
126
|
-
if (!
|
|
387
|
+
if (!isSubagentProvider(provider)) {
|
|
127
388
|
throw new Error(`Subagent profile provider must be codex, claude, opencode, pi, cursor, or copilot: ${filePath}`);
|
|
128
389
|
}
|
|
129
390
|
return provider;
|
|
130
391
|
}
|
|
131
392
|
export function isSubagentProvider(value) {
|
|
132
|
-
return
|
|
393
|
+
return SUBAGENT_PROVIDERS.includes(value);
|
|
133
394
|
}
|
|
134
395
|
function readString(frontmatter, key) {
|
|
135
396
|
const value = frontmatter[key];
|
|
@@ -138,6 +399,28 @@ function readString(frontmatter, key) {
|
|
|
138
399
|
const trimmed = value.trim();
|
|
139
400
|
return trimmed || undefined;
|
|
140
401
|
}
|
|
402
|
+
function sourceReference(source) {
|
|
403
|
+
return {
|
|
404
|
+
id: source.id,
|
|
405
|
+
scope: source.scope,
|
|
406
|
+
kind: source.kind,
|
|
407
|
+
...(source.location ? { location: source.location } : {}),
|
|
408
|
+
priority: source.priority,
|
|
409
|
+
};
|
|
410
|
+
}
|
|
411
|
+
function mergeDiagnostics(left, right) {
|
|
412
|
+
const seen = new Set();
|
|
413
|
+
return [...left, ...right].filter((diagnostic) => {
|
|
414
|
+
const key = `${diagnostic.source.id}\0${diagnostic.code}\0${diagnostic.message}\0${diagnostic.usingLastKnownGood === true}`;
|
|
415
|
+
if (seen.has(key))
|
|
416
|
+
return false;
|
|
417
|
+
seen.add(key);
|
|
418
|
+
return true;
|
|
419
|
+
});
|
|
420
|
+
}
|
|
421
|
+
function isRecord(value) {
|
|
422
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
423
|
+
}
|
|
141
424
|
function errorMessage(error) {
|
|
142
425
|
return error instanceof Error ? error.message : String(error);
|
|
143
426
|
}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { HookRunner } from "../../mcp/hooks/hooks.js";
|
|
2
|
-
import { isSubagentProvider,
|
|
2
|
+
import { isSubagentProvider, } from "../profiles.js";
|
|
3
3
|
import { runSubagentProvider } from "../providers/registry.js";
|
|
4
4
|
import { SubagentDeliveryMailbox } from "./delivery-mailbox.js";
|
|
5
5
|
import { createSubagentSessionStore, } from "./store.js";
|
|
@@ -13,7 +13,7 @@ export async function executeSubagentRun(config, input, providerRunner = runSuba
|
|
|
13
13
|
if (record.activeRun?.id !== input.runId) {
|
|
14
14
|
throw new Error(`Subagent Run ${input.runId} is not active for Session ${record.id}.`);
|
|
15
15
|
}
|
|
16
|
-
const hooks = new HookRunner(
|
|
16
|
+
const hooks = new HookRunner({}, config.logging, process.env, undefined, config.commandShellRuntime, config.configDir, config.configRuntime.sources);
|
|
17
17
|
const hookInvocation = {
|
|
18
18
|
workspaceId: record.workspaceId,
|
|
19
19
|
workspaceRoot: record.workspaceRoot,
|
|
@@ -33,7 +33,7 @@ export async function executeSubagentRun(config, input, providerRunner = runSuba
|
|
|
33
33
|
try {
|
|
34
34
|
await hooks.run("SubagentStart", hookInvocation);
|
|
35
35
|
input.signal?.throwIfAborted();
|
|
36
|
-
result = await runSessionProvider(
|
|
36
|
+
result = await runSessionProvider(record, input.prompt, providerRunner, input.signal);
|
|
37
37
|
input.signal?.throwIfAborted();
|
|
38
38
|
}
|
|
39
39
|
catch (error) {
|
|
@@ -109,43 +109,14 @@ export async function executeSubagentSession(config, sessionId, prompt) {
|
|
|
109
109
|
store.close();
|
|
110
110
|
}
|
|
111
111
|
}
|
|
112
|
-
async function runSessionProvider(
|
|
112
|
+
async function runSessionProvider(session, prompt, providerRunner, signal) {
|
|
113
113
|
if (!isSubagentProvider(session.provider)) {
|
|
114
114
|
throw new Error(`Unknown subagent provider for Session ${session.id}: ${session.provider}`);
|
|
115
115
|
}
|
|
116
|
-
if (session.providerSessionId) {
|
|
117
|
-
return providerRunner(session.provider, {
|
|
118
|
-
prompt,
|
|
119
|
-
workspace: session.workspaceRoot,
|
|
120
|
-
providerSessionId: session.providerSessionId,
|
|
121
|
-
writeMode: "allowed",
|
|
122
|
-
model: session.model,
|
|
123
|
-
thinking: session.thinking,
|
|
124
|
-
signal,
|
|
125
|
-
});
|
|
126
|
-
}
|
|
127
|
-
if (session.profileName === session.provider) {
|
|
128
|
-
return providerRunner(session.provider, {
|
|
129
|
-
prompt,
|
|
130
|
-
workspace: session.workspaceRoot,
|
|
131
|
-
writeMode: "allowed",
|
|
132
|
-
model: session.model,
|
|
133
|
-
thinking: session.thinking,
|
|
134
|
-
signal,
|
|
135
|
-
});
|
|
136
|
-
}
|
|
137
|
-
const profiles = await loadSubagentProfiles(config, session.workspaceRoot);
|
|
138
|
-
const profile = profiles.find((candidate) => candidate.name === session.profileName);
|
|
139
|
-
if (!profile)
|
|
140
|
-
throw new Error(`Subagent profile not found: ${session.profileName}`);
|
|
141
|
-
return runSubagentProfile(profile, session, prompt, providerRunner, signal);
|
|
142
|
-
}
|
|
143
|
-
async function runSubagentProfile(profile, session, prompt, providerRunner, signal) {
|
|
144
|
-
const body = profile.body.trim();
|
|
145
|
-
const firstPrompt = body ? `${body}\n\nTask:\n${prompt}` : prompt;
|
|
146
116
|
return providerRunner(session.provider, {
|
|
147
|
-
prompt
|
|
117
|
+
prompt,
|
|
148
118
|
workspace: session.workspaceRoot,
|
|
119
|
+
...(session.providerSessionId ? { providerSessionId: session.providerSessionId } : {}),
|
|
149
120
|
writeMode: "allowed",
|
|
150
121
|
model: session.model,
|
|
151
122
|
thinking: session.thinking,
|
|
@@ -3,6 +3,7 @@ import { formatAvailableSubagentTargets, resolveSubagentTarget, } from "../cli-t
|
|
|
3
3
|
import { isSubagentProvider, loadSubagentProfiles, } from "../profiles.js";
|
|
4
4
|
import { assertSubagentProviderAvailable } from "../providers/availability.js";
|
|
5
5
|
import { subagentProviderContinuationSupported } from "../providers/continuation.js";
|
|
6
|
+
import { compatibilityAllowProjectExecutionTrustPolicy, } from "../../runtime/security/project-execution-trust.js";
|
|
6
7
|
import { createSubagentSessionStore, } from "./store.js";
|
|
7
8
|
export class SubagentSessionError extends Error {
|
|
8
9
|
code;
|
|
@@ -15,10 +16,12 @@ export class SubagentSessionError extends Error {
|
|
|
15
16
|
export class SubagentSessionManager {
|
|
16
17
|
config;
|
|
17
18
|
launcher;
|
|
19
|
+
projectExecutionTrustPolicy;
|
|
18
20
|
store;
|
|
19
|
-
constructor(config, launcher) {
|
|
21
|
+
constructor(config, launcher, projectExecutionTrustPolicy = compatibilityAllowProjectExecutionTrustPolicy) {
|
|
20
22
|
this.config = config;
|
|
21
23
|
this.launcher = launcher;
|
|
24
|
+
this.projectExecutionTrustPolicy = projectExecutionTrustPolicy;
|
|
22
25
|
this.store = createSubagentSessionStore(config);
|
|
23
26
|
}
|
|
24
27
|
list(scope = {}) {
|
|
@@ -33,6 +36,9 @@ export class SubagentSessionManager {
|
|
|
33
36
|
if (!target) {
|
|
34
37
|
throw new Error(`Unknown subagent profile or provider: ${input.target}. Available ${formatAvailableSubagentTargets(profiles)}`);
|
|
35
38
|
}
|
|
39
|
+
if (target.kind === "profile" && target.profile.executionRequirement) {
|
|
40
|
+
await this.projectExecutionTrustPolicy.authorize(target.profile.executionRequirement);
|
|
41
|
+
}
|
|
36
42
|
assertSubagentProviderAvailable(target.provider);
|
|
37
43
|
const runId = newRunId();
|
|
38
44
|
const startedAt = new Date().toISOString();
|
|
@@ -56,7 +62,7 @@ export class SubagentSessionManager {
|
|
|
56
62
|
sessionId: session.id,
|
|
57
63
|
runId,
|
|
58
64
|
...(input.activityId ? { activityId: input.activityId } : {}),
|
|
59
|
-
prompt: input.prompt,
|
|
65
|
+
prompt: subagentLaunchPrompt(target, input.prompt),
|
|
60
66
|
}));
|
|
61
67
|
return {
|
|
62
68
|
session: owned,
|
|
@@ -146,6 +152,12 @@ export class SubagentSessionManager {
|
|
|
146
152
|
return this.store.assignActiveRunOwner(session.id, run.id, owner);
|
|
147
153
|
}
|
|
148
154
|
}
|
|
155
|
+
function subagentLaunchPrompt(target, prompt) {
|
|
156
|
+
if (target.kind !== "profile")
|
|
157
|
+
return prompt;
|
|
158
|
+
const body = target.profile.body.trim();
|
|
159
|
+
return body ? `${body}\n\nTask:\n${prompt}` : prompt;
|
|
160
|
+
}
|
|
149
161
|
function newRunId() {
|
|
150
162
|
return `run_${randomUUID().replaceAll("-", "").slice(0, 12)}`;
|
|
151
163
|
}
|