@evo-dev/core 0.0.1-alpha.2 → 0.0.1-alpha.20
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/assets/skills/coding/knowledge-distillation/SKILL.md +5 -3
- package/assets/team/agents/code-reviewer.md +48 -0
- package/assets/team/agents/docs-maintainer.md +51 -0
- package/assets/team/agents/implementation-engineer.md +51 -0
- package/assets/team/agents/product-scope-analyst.md +58 -0
- package/assets/team/agents/release-engineer.md +55 -0
- package/assets/team/agents/security-boundary-reviewer.md +50 -0
- package/assets/team/agents/solution-architect.md +51 -0
- package/assets/team/agents/verification-engineer.md +51 -0
- package/assets/team/team.md +102 -0
- package/dist/assets/index.js +5 -5
- package/dist/config/index.js +793 -241
- package/dist/index.js +20840 -12908
- package/dist/plugins/index.js +13 -13
- package/package.json +1 -1
- package/src/agents/index.ts +1 -265
- package/src/code-agent-traces/index.ts +11 -12
- package/src/config/index.ts +2 -0
- package/src/config/settings.ts +116 -7
- package/src/config/store.ts +1 -1
- package/src/daemon/index.ts +1 -41
- package/src/evolution/candidates/index.ts +730 -0
- package/src/evolution/control/index.ts +20 -0
- package/src/evolution/evidence/analysis.ts +533 -0
- package/src/evolution/evidence/index.ts +3 -0
- package/src/evolution/evidence/session-memory/analysis.ts +287 -0
- package/src/evolution/evidence/session-memory/constants.ts +9 -0
- package/src/evolution/evidence/session-memory/index.ts +9 -0
- package/src/evolution/evidence/session-memory/paths.ts +29 -0
- package/src/evolution/evidence/session-memory/policy.ts +39 -0
- package/src/evolution/evidence/session-memory/retention.ts +643 -0
- package/src/evolution/evidence/session-memory/segment.ts +216 -0
- package/src/evolution/evidence/session-memory/semantic-packet.ts +408 -0
- package/src/evolution/evidence/session-memory/sensitivity.ts +335 -0
- package/src/evolution/evidence/session-memory/state-machine.ts +249 -0
- package/src/evolution/evidence/session-memory/storage.ts +744 -0
- package/src/evolution/evidence/session-memory/types.ts +296 -0
- package/src/evolution/evidence/session-memory/updater.ts +199 -0
- package/src/evolution/formatters.ts +169 -0
- package/src/evolution/imports/apply.ts +435 -0
- package/src/evolution/imports/diff.ts +472 -0
- package/src/evolution/imports/index.ts +7 -0
- package/src/evolution/imports/materialize.ts +640 -0
- package/src/evolution/imports/paths.ts +129 -0
- package/src/evolution/imports/stage.ts +414 -0
- package/src/evolution/imports/storage.ts +952 -0
- package/src/evolution/imports/types.ts +226 -0
- package/src/evolution/index.ts +19 -2827
- package/src/evolution/knowledge/change-store.ts +558 -0
- package/src/evolution/knowledge/changes.ts +459 -0
- package/src/evolution/knowledge/freshness.ts +69 -0
- package/src/{knowledge → evolution/knowledge}/index.ts +1532 -206
- package/src/evolution/knowledge/review.ts +446 -0
- package/src/evolution/knowledge/support.ts +135 -0
- package/src/evolution/paths.ts +44 -0
- package/src/evolution/processor/distillation.ts +518 -0
- package/src/evolution/processor/index.ts +3 -0
- package/src/evolution/processor/process.ts +594 -0
- package/src/{learning → evolution/review}/index.ts +10 -14
- package/src/evolution/schema.ts +639 -0
- package/src/evolution/shared.ts +1053 -0
- package/src/evolution/triggers/classification.ts +102 -0
- package/src/evolution/triggers/index.ts +295 -0
- package/src/hooks/index.ts +281 -197
- package/src/index.ts +15 -4
- package/src/projects/index.ts +934 -0
- package/src/runtime-logs/index.ts +100 -13
- package/src/team/index.ts +582 -3
- package/src/utils/errors.ts +13 -0
- package/src/utils/fs.ts +40 -0
- package/src/utils/hash.ts +9 -0
- package/src/utils/ids.ts +12 -0
- package/src/utils/index.ts +7 -0
- package/src/utils/parsing.ts +11 -0
- package/src/utils/text.ts +18 -0
- package/src/utils/time.ts +5 -0
- package/src/workflow/index.ts +3 -21
- package/src/project/index.ts +0 -507
- package/src/task/index.ts +0 -840
package/src/team/index.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { spawn } from "node:child_process";
|
|
2
2
|
import { appendFile, cp, mkdir, readFile, readdir, stat, writeFile } from "node:fs/promises";
|
|
3
|
-
import { basename, dirname, join } from "node:path";
|
|
3
|
+
import { basename, dirname, extname, isAbsolute, join, relative, resolve } from "node:path";
|
|
4
4
|
import { resolveEvoDevPaths } from "../config/paths.ts";
|
|
5
5
|
import {
|
|
6
6
|
createDefaultSettings,
|
|
@@ -12,7 +12,7 @@ import {
|
|
|
12
12
|
formatScopedKnowledgePromptBlock,
|
|
13
13
|
hasContextInjectionReceipt,
|
|
14
14
|
writeContextInjectionReceipt,
|
|
15
|
-
} from "../knowledge/index.ts";
|
|
15
|
+
} from "../evolution/knowledge/index.ts";
|
|
16
16
|
import { resolveProjectLogKey } from "../runtime-logs/index.ts";
|
|
17
17
|
import { renderTeamRoleStartupPrompt } from "./prompts.ts";
|
|
18
18
|
|
|
@@ -161,7 +161,7 @@ export interface TeamRoleDefinition {
|
|
|
161
161
|
prompt: string;
|
|
162
162
|
permissions: TeamRolePermissions;
|
|
163
163
|
teamPolicy: TeamRolePolicy;
|
|
164
|
-
source: "builtin" | "global";
|
|
164
|
+
source: "builtin" | "global" | "overlay";
|
|
165
165
|
}
|
|
166
166
|
|
|
167
167
|
export interface ResolvedTeamRole extends TeamRoleDefinition {
|
|
@@ -178,6 +178,59 @@ export interface TeamNativeAgentBinding {
|
|
|
178
178
|
updatedAt: string;
|
|
179
179
|
}
|
|
180
180
|
|
|
181
|
+
export type TeamOverlaySource = "repo" | "global" | "builtin";
|
|
182
|
+
|
|
183
|
+
export interface TeamDefinition {
|
|
184
|
+
version: 1;
|
|
185
|
+
name: string;
|
|
186
|
+
description: string;
|
|
187
|
+
agents: Record<string, string>;
|
|
188
|
+
body: string;
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
export interface TeamOverlayResolution {
|
|
192
|
+
source: TeamOverlaySource;
|
|
193
|
+
teamPath: string | null;
|
|
194
|
+
definition: TeamDefinition;
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
export interface TeamAgentReference {
|
|
198
|
+
roleId: string;
|
|
199
|
+
reference: string;
|
|
200
|
+
sourcePath: string;
|
|
201
|
+
scope: "repo" | "global";
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
export interface TeamOverlayAgentSummary {
|
|
205
|
+
roleId: string;
|
|
206
|
+
name: string;
|
|
207
|
+
description: string;
|
|
208
|
+
sourcePath: string;
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
export interface TeamAgentDefinition {
|
|
212
|
+
roleId: string;
|
|
213
|
+
name: string;
|
|
214
|
+
description: string;
|
|
215
|
+
runtime: TeamAgentRuntime | null;
|
|
216
|
+
model: string | null;
|
|
217
|
+
thinkingLevel: string | null;
|
|
218
|
+
writeMode: TeamWriteMode | null;
|
|
219
|
+
skills: string[];
|
|
220
|
+
sourcePath: string;
|
|
221
|
+
markdown: string;
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
export interface DefaultTeamOverlayFileResult {
|
|
225
|
+
sourcePath: string;
|
|
226
|
+
targetPath: string;
|
|
227
|
+
written: boolean;
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
export interface EnsureDefaultTeamOverlayResult {
|
|
231
|
+
files: DefaultTeamOverlayFileResult[];
|
|
232
|
+
}
|
|
233
|
+
|
|
181
234
|
export interface TeamRoleBindingRecord {
|
|
182
235
|
version: 1;
|
|
183
236
|
roleId: string;
|
|
@@ -654,6 +707,22 @@ const BUILT_IN_ROLE_PROMPTS: Record<
|
|
|
654
707
|
"You are the implementation executor for this EvoDev team run. Keep changes scoped, avoid taking conductor decisions, and report changed files plus verification evidence.",
|
|
655
708
|
},
|
|
656
709
|
};
|
|
710
|
+
const BUILT_IN_TEAM_DEFINITION: TeamDefinition = {
|
|
711
|
+
version: 1,
|
|
712
|
+
name: "builtin-minimal-team",
|
|
713
|
+
description: "Built-in minimal EvoDev team fallback.",
|
|
714
|
+
agents: {
|
|
715
|
+
executor: "builtin:executor",
|
|
716
|
+
reviewer: "builtin:reviewer",
|
|
717
|
+
tester: "builtin:tester",
|
|
718
|
+
},
|
|
719
|
+
body: [
|
|
720
|
+
"# Built-in Minimal Team",
|
|
721
|
+
"",
|
|
722
|
+
"Use role agents only when delegation improves correctness, coverage, safety, or latency.",
|
|
723
|
+
"Spawn roles on demand and send self-contained assignments through Teams MCP.",
|
|
724
|
+
].join("\n"),
|
|
725
|
+
};
|
|
657
726
|
|
|
658
727
|
export function createTeamRunStore(homeDir?: string): TeamRunStore {
|
|
659
728
|
const paths = resolveTeamRunPaths(homeDir);
|
|
@@ -1908,6 +1977,216 @@ export async function listTeamAgents(input: TeamStatusInput = {}): Promise<TeamA
|
|
|
1908
1977
|
}));
|
|
1909
1978
|
}
|
|
1910
1979
|
|
|
1980
|
+
export async function resolveTeamOverlay(input: {
|
|
1981
|
+
homeDir?: string;
|
|
1982
|
+
repoRoot: string;
|
|
1983
|
+
}): Promise<TeamOverlayResolution> {
|
|
1984
|
+
const repoTeamPath = join(input.repoRoot, ".evodev", "team", "team.md");
|
|
1985
|
+
if (await pathExists(repoTeamPath)) {
|
|
1986
|
+
return {
|
|
1987
|
+
source: "repo",
|
|
1988
|
+
teamPath: repoTeamPath,
|
|
1989
|
+
definition: parseTeamDefinitionMarkdown(await readFile(repoTeamPath, "utf8")),
|
|
1990
|
+
};
|
|
1991
|
+
}
|
|
1992
|
+
|
|
1993
|
+
const globalTeamPath = resolveGlobalTeamMarkdownPath(input.homeDir);
|
|
1994
|
+
if (await pathExists(globalTeamPath)) {
|
|
1995
|
+
return {
|
|
1996
|
+
source: "global",
|
|
1997
|
+
teamPath: globalTeamPath,
|
|
1998
|
+
definition: parseTeamDefinitionMarkdown(await readFile(globalTeamPath, "utf8")),
|
|
1999
|
+
};
|
|
2000
|
+
}
|
|
2001
|
+
|
|
2002
|
+
return {
|
|
2003
|
+
source: "builtin",
|
|
2004
|
+
teamPath: null,
|
|
2005
|
+
definition: BUILT_IN_TEAM_DEFINITION,
|
|
2006
|
+
};
|
|
2007
|
+
}
|
|
2008
|
+
|
|
2009
|
+
export async function ensureDefaultTeamOverlay(input: {
|
|
2010
|
+
homeDir?: string;
|
|
2011
|
+
assetsRootDir: string;
|
|
2012
|
+
}): Promise<EnsureDefaultTeamOverlayResult> {
|
|
2013
|
+
const assets = await listDefaultTeamOverlayAssets(input.assetsRootDir);
|
|
2014
|
+
const paths = resolveEvoDevPaths(input.homeDir);
|
|
2015
|
+
const files: DefaultTeamOverlayFileResult[] = [];
|
|
2016
|
+
|
|
2017
|
+
for (const asset of assets) {
|
|
2018
|
+
const targetPath =
|
|
2019
|
+
asset.kind === "team"
|
|
2020
|
+
? join(paths.rootDir, "team", "team.md")
|
|
2021
|
+
: join(paths.rootDir, "team", "agents", asset.name);
|
|
2022
|
+
const content = await readFile(asset.sourcePath, "utf8");
|
|
2023
|
+
files.push({
|
|
2024
|
+
sourcePath: asset.sourcePath,
|
|
2025
|
+
targetPath,
|
|
2026
|
+
written: await writeTextFileIfMissing(targetPath, content),
|
|
2027
|
+
});
|
|
2028
|
+
}
|
|
2029
|
+
|
|
2030
|
+
return { files };
|
|
2031
|
+
}
|
|
2032
|
+
|
|
2033
|
+
export function parseTeamDefinitionMarkdown(content: string): TeamDefinition {
|
|
2034
|
+
const markdown = parseMarkdownWithFrontmatter(content);
|
|
2035
|
+
const frontmatter = markdown.frontmatter;
|
|
2036
|
+
const version = frontmatter.version;
|
|
2037
|
+
if (version !== 1) throw new Error("team.md frontmatter version must be 1.");
|
|
2038
|
+
const agents = parseTeamDefinitionAgents(frontmatter.agents);
|
|
2039
|
+
|
|
2040
|
+
return {
|
|
2041
|
+
version: 1,
|
|
2042
|
+
name: optionalString(frontmatter.name) ?? "evodev-team",
|
|
2043
|
+
description: optionalString(frontmatter.description) ?? "EvoDev team overlay.",
|
|
2044
|
+
agents,
|
|
2045
|
+
body: markdown.body.trim(),
|
|
2046
|
+
};
|
|
2047
|
+
}
|
|
2048
|
+
|
|
2049
|
+
export function resolveTeamAgentReference(input: {
|
|
2050
|
+
homeDir?: string;
|
|
2051
|
+
repoRoot: string;
|
|
2052
|
+
roleId: string;
|
|
2053
|
+
reference: string;
|
|
2054
|
+
}): TeamAgentReference {
|
|
2055
|
+
assertSafeId(input.roleId, "roleId");
|
|
2056
|
+
const reference = input.reference.trim();
|
|
2057
|
+
if (reference.startsWith("global:")) {
|
|
2058
|
+
const name = reference.slice("global:".length);
|
|
2059
|
+
assertSafeId(name, "global agent name");
|
|
2060
|
+
return {
|
|
2061
|
+
roleId: input.roleId,
|
|
2062
|
+
reference,
|
|
2063
|
+
sourcePath: join(resolveEvoDevPaths(input.homeDir).rootDir, "team", "agents", `${name}.md`),
|
|
2064
|
+
scope: "global",
|
|
2065
|
+
};
|
|
2066
|
+
}
|
|
2067
|
+
|
|
2068
|
+
if (reference.includes(":")) {
|
|
2069
|
+
throw new Error(`Unsupported team agent reference for ${input.roleId}: ${reference}`);
|
|
2070
|
+
}
|
|
2071
|
+
if (isAbsolute(reference)) {
|
|
2072
|
+
throw new Error(`Team agent path for ${input.roleId} must be repo-relative.`);
|
|
2073
|
+
}
|
|
2074
|
+
const sourcePath = resolve(input.repoRoot, reference);
|
|
2075
|
+
const relativePath = relative(input.repoRoot, sourcePath);
|
|
2076
|
+
if (
|
|
2077
|
+
relativePath === "" ||
|
|
2078
|
+
relativePath.startsWith("..") ||
|
|
2079
|
+
isAbsolute(relativePath) ||
|
|
2080
|
+
extname(sourcePath) !== ".md"
|
|
2081
|
+
) {
|
|
2082
|
+
throw new Error(`Team agent path for ${input.roleId} must be a repo-local Markdown file.`);
|
|
2083
|
+
}
|
|
2084
|
+
|
|
2085
|
+
return {
|
|
2086
|
+
roleId: input.roleId,
|
|
2087
|
+
reference,
|
|
2088
|
+
sourcePath,
|
|
2089
|
+
scope: "repo",
|
|
2090
|
+
};
|
|
2091
|
+
}
|
|
2092
|
+
|
|
2093
|
+
export async function readTeamAgentSummary(input: {
|
|
2094
|
+
homeDir?: string;
|
|
2095
|
+
repoRoot: string;
|
|
2096
|
+
roleId: string;
|
|
2097
|
+
reference: string;
|
|
2098
|
+
}): Promise<TeamOverlayAgentSummary> {
|
|
2099
|
+
const reference = resolveTeamAgentReference(input);
|
|
2100
|
+
const markdown = await readTeamAgentMarkdown(reference);
|
|
2101
|
+
const parsed = parseMarkdownWithFrontmatter(markdown);
|
|
2102
|
+
return {
|
|
2103
|
+
roleId: input.roleId,
|
|
2104
|
+
name: optionalString(parsed.frontmatter.name) ?? defaultRoleName(input.roleId),
|
|
2105
|
+
description:
|
|
2106
|
+
optionalString(parsed.frontmatter.description) ?? `EvoDev ${input.roleId} role agent.`,
|
|
2107
|
+
sourcePath: reference.sourcePath,
|
|
2108
|
+
};
|
|
2109
|
+
}
|
|
2110
|
+
|
|
2111
|
+
export async function readTeamAgentDefinition(input: {
|
|
2112
|
+
homeDir?: string;
|
|
2113
|
+
repoRoot: string;
|
|
2114
|
+
roleId: string;
|
|
2115
|
+
reference: string;
|
|
2116
|
+
}): Promise<TeamAgentDefinition> {
|
|
2117
|
+
const reference = resolveTeamAgentReference(input);
|
|
2118
|
+
const markdown = await readTeamAgentMarkdown(reference);
|
|
2119
|
+
const parsed = parseMarkdownWithFrontmatter(markdown);
|
|
2120
|
+
const evodev = isRecord(parsed.frontmatter.evodev) ? parsed.frontmatter.evodev : {};
|
|
2121
|
+
return {
|
|
2122
|
+
roleId: input.roleId,
|
|
2123
|
+
name: optionalString(parsed.frontmatter.name) ?? defaultRoleName(input.roleId),
|
|
2124
|
+
description:
|
|
2125
|
+
optionalString(parsed.frontmatter.description) ?? `EvoDev ${input.roleId} role agent.`,
|
|
2126
|
+
runtime: optionalRuntime(evodev.runtime),
|
|
2127
|
+
model: optionalNullableString(parsed.frontmatter.model) ?? null,
|
|
2128
|
+
thinkingLevel:
|
|
2129
|
+
optionalNullableString(evodev.thinking) ??
|
|
2130
|
+
optionalNullableString(evodev.thinkingLevel) ??
|
|
2131
|
+
null,
|
|
2132
|
+
writeMode: optionalWriteMode(evodev.writeMode),
|
|
2133
|
+
skills: parseStringList(evodev.skills),
|
|
2134
|
+
sourcePath: reference.sourcePath,
|
|
2135
|
+
markdown,
|
|
2136
|
+
};
|
|
2137
|
+
}
|
|
2138
|
+
|
|
2139
|
+
export async function renderMainTeamOverlayContext(input: {
|
|
2140
|
+
homeDir?: string;
|
|
2141
|
+
repoRoot: string;
|
|
2142
|
+
overlay?: TeamOverlayResolution;
|
|
2143
|
+
}): Promise<string> {
|
|
2144
|
+
const overlay = input.overlay ?? (await resolveTeamOverlay(input));
|
|
2145
|
+
const summaries = await readTeamOverlayAgentSummaries({
|
|
2146
|
+
homeDir: input.homeDir,
|
|
2147
|
+
repoRoot: input.repoRoot,
|
|
2148
|
+
overlay,
|
|
2149
|
+
});
|
|
2150
|
+
const roleLines =
|
|
2151
|
+
summaries.length === 0
|
|
2152
|
+
? ["- none declared"]
|
|
2153
|
+
: summaries.map((summary) => `- ${summary.roleId}: ${summary.name} - ${summary.description}`);
|
|
2154
|
+
const source =
|
|
2155
|
+
overlay.teamPath === null
|
|
2156
|
+
? `${overlay.source} fallback`
|
|
2157
|
+
: `${overlay.source}: ${overlay.teamPath}`;
|
|
2158
|
+
|
|
2159
|
+
return [
|
|
2160
|
+
"EvoDev team overlay:",
|
|
2161
|
+
`Team: ${overlay.definition.name}`,
|
|
2162
|
+
`Description: ${overlay.definition.description}`,
|
|
2163
|
+
`Source: ${source}`,
|
|
2164
|
+
"",
|
|
2165
|
+
"Team strategy:",
|
|
2166
|
+
overlay.definition.body || "(none)",
|
|
2167
|
+
"",
|
|
2168
|
+
"Declared role agents:",
|
|
2169
|
+
...roleLines,
|
|
2170
|
+
"",
|
|
2171
|
+
"Overlay rules:",
|
|
2172
|
+
"- Declared role agents are spawned only on demand.",
|
|
2173
|
+
"- Delegate by role id; role agent Markdown is loaded only inside the spawned role.",
|
|
2174
|
+
].join("\n");
|
|
2175
|
+
}
|
|
2176
|
+
|
|
2177
|
+
export function renderRoleAgentDefinitionContext(input: {
|
|
2178
|
+
definition: TeamAgentDefinition;
|
|
2179
|
+
}): string {
|
|
2180
|
+
return [
|
|
2181
|
+
"EvoDev role agent Markdown definition:",
|
|
2182
|
+
`Source path: ${input.definition.sourcePath}`,
|
|
2183
|
+
"Use this Markdown as the role-specific operating definition for this role only.",
|
|
2184
|
+
"<evodev-agent-markdown>",
|
|
2185
|
+
input.definition.markdown.trimEnd(),
|
|
2186
|
+
"</evodev-agent-markdown>",
|
|
2187
|
+
].join("\n");
|
|
2188
|
+
}
|
|
2189
|
+
|
|
1911
2190
|
export async function resolveTeamRole(input: {
|
|
1912
2191
|
homeDir?: string;
|
|
1913
2192
|
repoRoot: string;
|
|
@@ -1920,6 +2199,37 @@ export async function resolveTeamRole(input: {
|
|
|
1920
2199
|
}): Promise<ResolvedTeamRole> {
|
|
1921
2200
|
assertSafeId(input.roleId, "roleId");
|
|
1922
2201
|
const settings = await readSettingsOrDefault(input.homeDir);
|
|
2202
|
+
const overlay = await resolveTeamOverlay({ homeDir: input.homeDir, repoRoot: input.repoRoot });
|
|
2203
|
+
if (overlay.source !== "builtin" && input.roleId !== "main") {
|
|
2204
|
+
const reference = overlay.definition.agents[input.roleId];
|
|
2205
|
+
if (reference === undefined) {
|
|
2206
|
+
throw new Error(
|
|
2207
|
+
`Role ${input.roleId} is not declared in team overlay ${overlay.teamPath ?? overlay.source}.`,
|
|
2208
|
+
);
|
|
2209
|
+
}
|
|
2210
|
+
const agent = await readTeamAgentDefinition({
|
|
2211
|
+
homeDir: input.homeDir,
|
|
2212
|
+
repoRoot: input.repoRoot,
|
|
2213
|
+
roleId: input.roleId,
|
|
2214
|
+
reference,
|
|
2215
|
+
});
|
|
2216
|
+
return {
|
|
2217
|
+
version: 1,
|
|
2218
|
+
roleId: input.roleId,
|
|
2219
|
+
roleName: agent.name,
|
|
2220
|
+
description: agent.description,
|
|
2221
|
+
runtime: agent.runtime ?? settings.teamRuntime.defaultRuntime,
|
|
2222
|
+
model: agent.model ?? settings.teamRuntime.defaultModel,
|
|
2223
|
+
thinkingLevel: agent.thinkingLevel ?? settings.teamRuntime.defaultThinkingLevel,
|
|
2224
|
+
prompt: renderRoleAgentDefinitionContext({ definition: agent }),
|
|
2225
|
+
permissions: parseRolePermissions({ writeMode: agent.writeMode ?? undefined }, false),
|
|
2226
|
+
teamPolicy: parseRolePolicy(undefined, settings.teamRuntime.recordTranscript),
|
|
2227
|
+
source: "overlay",
|
|
2228
|
+
sourcePath: agent.sourcePath,
|
|
2229
|
+
nativeAgent: null,
|
|
2230
|
+
};
|
|
2231
|
+
}
|
|
2232
|
+
|
|
1923
2233
|
const globalRolePath = join(
|
|
1924
2234
|
resolveEvoDevPaths(input.homeDir).roleAgentsDir,
|
|
1925
2235
|
`${input.roleId}.json`,
|
|
@@ -1942,12 +2252,24 @@ export async function resolveTeamRole(input: {
|
|
|
1942
2252
|
defaultThinkingLevel: settings.teamRuntime.defaultThinkingLevel,
|
|
1943
2253
|
recordTranscript: settings.teamRuntime.recordTranscript,
|
|
1944
2254
|
});
|
|
2255
|
+
const prompt =
|
|
2256
|
+
input.roleId === "main"
|
|
2257
|
+
? appendPromptBlock(
|
|
2258
|
+
parsed.prompt,
|
|
2259
|
+
await renderMainTeamOverlayContext({
|
|
2260
|
+
homeDir: input.homeDir,
|
|
2261
|
+
repoRoot: input.repoRoot,
|
|
2262
|
+
overlay,
|
|
2263
|
+
}),
|
|
2264
|
+
)
|
|
2265
|
+
: parsed.prompt;
|
|
1945
2266
|
|
|
1946
2267
|
return {
|
|
1947
2268
|
...parsed,
|
|
1948
2269
|
runtime: input.overrides?.runtime ?? nativeAgent?.target ?? parsed.runtime,
|
|
1949
2270
|
model: input.overrides?.model ?? parsed.model,
|
|
1950
2271
|
thinkingLevel: input.overrides?.thinkingLevel ?? parsed.thinkingLevel,
|
|
2272
|
+
prompt,
|
|
1951
2273
|
sourcePath,
|
|
1952
2274
|
nativeAgent,
|
|
1953
2275
|
};
|
|
@@ -2522,6 +2844,9 @@ function resolveRuntimeCommand(
|
|
|
2522
2844
|
function buildCodexArgs(role: ResolvedTeamRole, startupPrompt: string): string[] {
|
|
2523
2845
|
const args = ["--no-alt-screen"];
|
|
2524
2846
|
if (role.model !== null) args.push("--model", role.model);
|
|
2847
|
+
if (role.thinkingLevel !== null) {
|
|
2848
|
+
args.push("--config", `model_reasoning_effort=${JSON.stringify(role.thinkingLevel)}`);
|
|
2849
|
+
}
|
|
2525
2850
|
if (shouldPassVisibleStartupPrompt(role)) args.push(startupPrompt);
|
|
2526
2851
|
return args;
|
|
2527
2852
|
}
|
|
@@ -2533,6 +2858,9 @@ function buildCodexResumeArgs(
|
|
|
2533
2858
|
): string[] {
|
|
2534
2859
|
const args = ["--no-alt-screen"];
|
|
2535
2860
|
if (role.model !== null) args.push("--model", role.model);
|
|
2861
|
+
if (role.thinkingLevel !== null) {
|
|
2862
|
+
args.push("--config", `model_reasoning_effort=${JSON.stringify(role.thinkingLevel)}`);
|
|
2863
|
+
}
|
|
2536
2864
|
args.push("resume");
|
|
2537
2865
|
if (mode.type === "resume") {
|
|
2538
2866
|
args.push(mode.sessionId);
|
|
@@ -2614,6 +2942,239 @@ function createBuiltInRole(roleId: string, runtime: TeamAgentRuntime): unknown {
|
|
|
2614
2942
|
};
|
|
2615
2943
|
}
|
|
2616
2944
|
|
|
2945
|
+
async function readTeamOverlayAgentSummaries(input: {
|
|
2946
|
+
homeDir?: string;
|
|
2947
|
+
repoRoot: string;
|
|
2948
|
+
overlay: TeamOverlayResolution;
|
|
2949
|
+
}): Promise<TeamOverlayAgentSummary[]> {
|
|
2950
|
+
const entries = Object.entries(input.overlay.definition.agents);
|
|
2951
|
+
if (input.overlay.source === "builtin") {
|
|
2952
|
+
return entries.map(([roleId]) => {
|
|
2953
|
+
const builtin = BUILT_IN_ROLE_PROMPTS[roleId];
|
|
2954
|
+
return {
|
|
2955
|
+
roleId,
|
|
2956
|
+
name: builtin?.roleName ?? defaultRoleName(roleId),
|
|
2957
|
+
description: builtin?.description ?? `EvoDev ${roleId} role agent.`,
|
|
2958
|
+
sourcePath: "builtin",
|
|
2959
|
+
};
|
|
2960
|
+
});
|
|
2961
|
+
}
|
|
2962
|
+
|
|
2963
|
+
return Promise.all(
|
|
2964
|
+
entries.map(([roleId, reference]) =>
|
|
2965
|
+
readTeamAgentSummary({
|
|
2966
|
+
homeDir: input.homeDir,
|
|
2967
|
+
repoRoot: input.repoRoot,
|
|
2968
|
+
roleId,
|
|
2969
|
+
reference,
|
|
2970
|
+
}),
|
|
2971
|
+
),
|
|
2972
|
+
);
|
|
2973
|
+
}
|
|
2974
|
+
|
|
2975
|
+
async function listDefaultTeamOverlayAssets(
|
|
2976
|
+
assetsRootDir: string,
|
|
2977
|
+
): Promise<
|
|
2978
|
+
Array<
|
|
2979
|
+
| { kind: "team"; sourcePath: string; name: "team.md" }
|
|
2980
|
+
| { kind: "agent"; sourcePath: string; name: string }
|
|
2981
|
+
>
|
|
2982
|
+
> {
|
|
2983
|
+
const teamPath = join(assetsRootDir, "team", "team.md");
|
|
2984
|
+
const agentsDir = join(assetsRootDir, "team", "agents");
|
|
2985
|
+
await assertReadableFile(teamPath, "Default team asset");
|
|
2986
|
+
|
|
2987
|
+
let entries: Array<{ name: string; isFile(): boolean }>;
|
|
2988
|
+
try {
|
|
2989
|
+
entries = (await readdir(agentsDir, { withFileTypes: true })) as Array<{
|
|
2990
|
+
name: string;
|
|
2991
|
+
isFile(): boolean;
|
|
2992
|
+
}>;
|
|
2993
|
+
} catch (error) {
|
|
2994
|
+
throw new Error(
|
|
2995
|
+
`Cannot read default team agents directory ${agentsDir}: ${describeError(error)}`,
|
|
2996
|
+
);
|
|
2997
|
+
}
|
|
2998
|
+
|
|
2999
|
+
const agentFiles = entries
|
|
3000
|
+
.filter((entry) => entry.isFile() && extname(entry.name) === ".md")
|
|
3001
|
+
.map((entry) => entry.name)
|
|
3002
|
+
.sort();
|
|
3003
|
+
if (agentFiles.length === 0) {
|
|
3004
|
+
throw new Error(`Default team agents directory has no Markdown files: ${agentsDir}`);
|
|
3005
|
+
}
|
|
3006
|
+
|
|
3007
|
+
return [
|
|
3008
|
+
{ kind: "team", sourcePath: teamPath, name: "team.md" },
|
|
3009
|
+
...agentFiles.map((name) => ({
|
|
3010
|
+
kind: "agent" as const,
|
|
3011
|
+
sourcePath: join(agentsDir, name),
|
|
3012
|
+
name,
|
|
3013
|
+
})),
|
|
3014
|
+
];
|
|
3015
|
+
}
|
|
3016
|
+
|
|
3017
|
+
async function assertReadableFile(path: string, label: string): Promise<void> {
|
|
3018
|
+
try {
|
|
3019
|
+
const info = await stat(path);
|
|
3020
|
+
if (!info.isFile()) throw new Error("not a file");
|
|
3021
|
+
} catch (error) {
|
|
3022
|
+
throw new Error(`${label} is not readable at ${path}: ${describeError(error)}`);
|
|
3023
|
+
}
|
|
3024
|
+
}
|
|
3025
|
+
|
|
3026
|
+
async function writeTextFileIfMissing(path: string, content: string): Promise<boolean> {
|
|
3027
|
+
try {
|
|
3028
|
+
await readFile(path, "utf8");
|
|
3029
|
+
return false;
|
|
3030
|
+
} catch (error) {
|
|
3031
|
+
if (!isNotFoundError(error)) {
|
|
3032
|
+
throw new Error(`Cannot inspect ${path}: ${describeError(error)}`);
|
|
3033
|
+
}
|
|
3034
|
+
}
|
|
3035
|
+
|
|
3036
|
+
await mkdir(dirname(path), { recursive: true });
|
|
3037
|
+
await writeFile(path, content.endsWith("\n") ? content : `${content}\n`, "utf8");
|
|
3038
|
+
return true;
|
|
3039
|
+
}
|
|
3040
|
+
|
|
3041
|
+
function parseTeamDefinitionAgents(value: unknown): Record<string, string> {
|
|
3042
|
+
if (value === undefined) return {};
|
|
3043
|
+
if (!isRecord(value)) throw new Error("team.md agents must be a role-id map.");
|
|
3044
|
+
const agents: Record<string, string> = {};
|
|
3045
|
+
for (const [roleId, reference] of Object.entries(value)) {
|
|
3046
|
+
assertSafeId(roleId, "team.md agents roleId");
|
|
3047
|
+
if (typeof reference !== "string" || reference.trim() === "") {
|
|
3048
|
+
throw new Error(`team.md agent reference for ${roleId} must be a non-empty string.`);
|
|
3049
|
+
}
|
|
3050
|
+
agents[roleId] = reference.trim();
|
|
3051
|
+
}
|
|
3052
|
+
return agents;
|
|
3053
|
+
}
|
|
3054
|
+
|
|
3055
|
+
async function readTeamAgentMarkdown(reference: TeamAgentReference): Promise<string> {
|
|
3056
|
+
if (extname(reference.sourcePath) !== ".md") {
|
|
3057
|
+
throw new Error(`Team agent file for ${reference.roleId} must be Markdown.`);
|
|
3058
|
+
}
|
|
3059
|
+
try {
|
|
3060
|
+
return await readFile(reference.sourcePath, "utf8");
|
|
3061
|
+
} catch (error) {
|
|
3062
|
+
if (isNotFoundError(error)) {
|
|
3063
|
+
throw new Error(`Team agent file not found for ${reference.roleId}: ${reference.sourcePath}`);
|
|
3064
|
+
}
|
|
3065
|
+
throw new Error(
|
|
3066
|
+
`Cannot read team agent file for ${reference.roleId}: ${reference.sourcePath}: ${describeError(
|
|
3067
|
+
error,
|
|
3068
|
+
)}`,
|
|
3069
|
+
);
|
|
3070
|
+
}
|
|
3071
|
+
}
|
|
3072
|
+
|
|
3073
|
+
function parseMarkdownWithFrontmatter(content: string): {
|
|
3074
|
+
frontmatter: Record<string, unknown>;
|
|
3075
|
+
body: string;
|
|
3076
|
+
} {
|
|
3077
|
+
const text = content.startsWith("\uFEFF") ? content.slice(1) : content;
|
|
3078
|
+
const lines = text.split(/\r?\n/);
|
|
3079
|
+
if (lines[0]?.trim() !== "---") return { frontmatter: {}, body: text };
|
|
3080
|
+
const end = lines.findIndex((line, index) => index > 0 && line.trim() === "---");
|
|
3081
|
+
if (end < 0) throw new Error("Markdown frontmatter is not closed.");
|
|
3082
|
+
return {
|
|
3083
|
+
frontmatter: parseSimpleYaml(lines.slice(1, end).join("\n")),
|
|
3084
|
+
body: lines.slice(end + 1).join("\n"),
|
|
3085
|
+
};
|
|
3086
|
+
}
|
|
3087
|
+
|
|
3088
|
+
function parseSimpleYaml(content: string): Record<string, unknown> {
|
|
3089
|
+
const root: Record<string, unknown> = {};
|
|
3090
|
+
const stack: Array<{ indent: number; value: Record<string, unknown> | unknown[] }> = [
|
|
3091
|
+
{ indent: -1, value: root },
|
|
3092
|
+
];
|
|
3093
|
+
const lines = content.split(/\r?\n/);
|
|
3094
|
+
|
|
3095
|
+
for (let index = 0; index < lines.length; index += 1) {
|
|
3096
|
+
const rawLine = lines[index] ?? "";
|
|
3097
|
+
if (rawLine.trim() === "" || rawLine.trimStart().startsWith("#")) continue;
|
|
3098
|
+
const indent = rawLine.match(/^ */)?.[0].length ?? 0;
|
|
3099
|
+
const trimmed = rawLine.trim();
|
|
3100
|
+
while (stack.length > 1 && indent <= stack[stack.length - 1].indent) stack.pop();
|
|
3101
|
+
const parent = stack[stack.length - 1].value;
|
|
3102
|
+
|
|
3103
|
+
if (trimmed.startsWith("- ")) {
|
|
3104
|
+
if (!Array.isArray(parent)) throw new Error("Invalid YAML list item placement.");
|
|
3105
|
+
parent.push(parseYamlScalar(trimmed.slice(2).trim()));
|
|
3106
|
+
continue;
|
|
3107
|
+
}
|
|
3108
|
+
|
|
3109
|
+
const separator = trimmed.indexOf(":");
|
|
3110
|
+
if (separator <= 0) throw new Error(`Invalid YAML line: ${trimmed}`);
|
|
3111
|
+
const key = trimmed.slice(0, separator).trim();
|
|
3112
|
+
const rawValue = trimmed.slice(separator + 1).trim();
|
|
3113
|
+
if (!isRecord(parent)) throw new Error(`Invalid YAML parent for key ${key}.`);
|
|
3114
|
+
|
|
3115
|
+
if (rawValue === "") {
|
|
3116
|
+
const next = findNextYamlContentLine(lines, index + 1);
|
|
3117
|
+
const value: Record<string, unknown> | unknown[] =
|
|
3118
|
+
next !== null && next.indent > indent && next.trimmed.startsWith("- ") ? [] : {};
|
|
3119
|
+
parent[key] = value;
|
|
3120
|
+
stack.push({ indent, value });
|
|
3121
|
+
continue;
|
|
3122
|
+
}
|
|
3123
|
+
|
|
3124
|
+
parent[key] = parseYamlScalar(rawValue);
|
|
3125
|
+
}
|
|
3126
|
+
|
|
3127
|
+
return root;
|
|
3128
|
+
}
|
|
3129
|
+
|
|
3130
|
+
function findNextYamlContentLine(
|
|
3131
|
+
lines: string[],
|
|
3132
|
+
start: number,
|
|
3133
|
+
): { indent: number; trimmed: string } | null {
|
|
3134
|
+
for (let index = start; index < lines.length; index += 1) {
|
|
3135
|
+
const line = lines[index] ?? "";
|
|
3136
|
+
if (line.trim() === "" || line.trimStart().startsWith("#")) continue;
|
|
3137
|
+
return {
|
|
3138
|
+
indent: line.match(/^ */)?.[0].length ?? 0,
|
|
3139
|
+
trimmed: line.trim(),
|
|
3140
|
+
};
|
|
3141
|
+
}
|
|
3142
|
+
return null;
|
|
3143
|
+
}
|
|
3144
|
+
|
|
3145
|
+
function parseYamlScalar(value: string): unknown {
|
|
3146
|
+
if (value === "") return "";
|
|
3147
|
+
if (value === "true") return true;
|
|
3148
|
+
if (value === "false") return false;
|
|
3149
|
+
if (value === "null" || value === "~") return null;
|
|
3150
|
+
if (/^-?\d+(\.\d+)?$/.test(value)) return Number(value);
|
|
3151
|
+
if (value.startsWith("[") && value.endsWith("]")) {
|
|
3152
|
+
const inner = value.slice(1, -1).trim();
|
|
3153
|
+
if (inner === "") return [];
|
|
3154
|
+
return inner.split(",").map((item) => parseYamlScalar(item.trim()));
|
|
3155
|
+
}
|
|
3156
|
+
if (value.startsWith('"') && value.endsWith('"')) {
|
|
3157
|
+
try {
|
|
3158
|
+
return JSON.parse(value);
|
|
3159
|
+
} catch {
|
|
3160
|
+
return value.slice(1, -1);
|
|
3161
|
+
}
|
|
3162
|
+
}
|
|
3163
|
+
if (value.startsWith("'") && value.endsWith("'")) {
|
|
3164
|
+
return value.slice(1, -1).replace(/''/g, "'");
|
|
3165
|
+
}
|
|
3166
|
+
return value;
|
|
3167
|
+
}
|
|
3168
|
+
|
|
3169
|
+
function appendPromptBlock(prompt: string, block: string): string {
|
|
3170
|
+
if (block.trim() === "") return prompt;
|
|
3171
|
+
return `${prompt.trimEnd()}\n\n${block.trim()}`;
|
|
3172
|
+
}
|
|
3173
|
+
|
|
3174
|
+
function resolveGlobalTeamMarkdownPath(homeDir?: string): string {
|
|
3175
|
+
return join(resolveEvoDevPaths(homeDir).rootDir, "team", "team.md");
|
|
3176
|
+
}
|
|
3177
|
+
|
|
2617
3178
|
function parseRolePermissions(value: unknown, main: boolean): TeamRolePermissions {
|
|
2618
3179
|
const input = isRecord(value) ? value : {};
|
|
2619
3180
|
return {
|
|
@@ -2742,6 +3303,12 @@ function parseRuntime(value: unknown, fallback: TeamAgentRuntime): TeamAgentRunt
|
|
|
2742
3303
|
throw new Error("Role runtime must be codex or claude.");
|
|
2743
3304
|
}
|
|
2744
3305
|
|
|
3306
|
+
function optionalRuntime(value: unknown): TeamAgentRuntime | null {
|
|
3307
|
+
if (value === undefined || value === null) return null;
|
|
3308
|
+
if (value === "codex" || value === "claude") return value;
|
|
3309
|
+
throw new Error("Role evodev.runtime must be codex or claude.");
|
|
3310
|
+
}
|
|
3311
|
+
|
|
2745
3312
|
function parseWriteMode(value: unknown, fallback: TeamWriteMode): TeamWriteMode {
|
|
2746
3313
|
if (value === undefined || value === null) return fallback;
|
|
2747
3314
|
if (["read-only", "repo-write", "worktree-write", "disabled"].includes(String(value))) {
|
|
@@ -2750,6 +3317,18 @@ function parseWriteMode(value: unknown, fallback: TeamWriteMode): TeamWriteMode
|
|
|
2750
3317
|
throw new Error("Role writeMode is invalid.");
|
|
2751
3318
|
}
|
|
2752
3319
|
|
|
3320
|
+
function optionalWriteMode(value: unknown): TeamWriteMode | null {
|
|
3321
|
+
if (value === undefined || value === null) return null;
|
|
3322
|
+
return parseWriteMode(value, "repo-write");
|
|
3323
|
+
}
|
|
3324
|
+
|
|
3325
|
+
function parseStringList(value: unknown): string[] {
|
|
3326
|
+
if (value === undefined || value === null) return [];
|
|
3327
|
+
if (typeof value === "string" && value.trim() !== "") return [value.trim()];
|
|
3328
|
+
if (!Array.isArray(value)) return [];
|
|
3329
|
+
return value.filter((item): item is string => typeof item === "string" && item.trim() !== "");
|
|
3330
|
+
}
|
|
3331
|
+
|
|
2753
3332
|
export function isActiveTeamAgentStatus(status: TeamAgentStatus): boolean {
|
|
2754
3333
|
return (
|
|
2755
3334
|
status === "starting" ||
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
export function isNotFoundError(error: unknown): boolean {
|
|
2
|
+
return (
|
|
3
|
+
error instanceof Error &&
|
|
4
|
+
(("code" in error && (error as NodeJS.ErrnoException).code === "ENOENT") ||
|
|
5
|
+
error.message.includes("ENOENT"))
|
|
6
|
+
);
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
export function isFileExistsError(error: unknown): boolean {
|
|
10
|
+
return (
|
|
11
|
+
error instanceof Error && "code" in error && (error as NodeJS.ErrnoException).code === "EEXIST"
|
|
12
|
+
);
|
|
13
|
+
}
|