diff --git a/web/admin/src/lib/layers/analyzeOrg.ts b/web/admin/src/lib/layers/analyzeOrg.ts index d9b5680001..00dad2dc3a 100644 --- a/web/admin/src/lib/layers/analyzeOrg.ts +++ b/web/admin/src/lib/layers/analyzeOrg.ts @@ -10,7 +10,7 @@ import { analyzeWorkflowsLayer } from "./workflows"; export type AnalyzeOrgLayersInput = { org: string; gh: LayerGithub; - /** Agent roles from org config (drives secret/variable names). */ + /** Roles from org config (drives secret/variable names). */ agents: { role: string }[]; /** Repos with `enabled: true` in config (drives enrollment checks). */ enabledRepos: string[]; diff --git a/web/admin/src/lib/layers/orgConfigParse.test.ts b/web/admin/src/lib/layers/orgConfigParse.test.ts index 85b7fe1f4c..63c24e63c1 100644 --- a/web/admin/src/lib/layers/orgConfigParse.test.ts +++ b/web/admin/src/lib/layers/orgConfigParse.test.ts @@ -1,6 +1,6 @@ import { describe, expect, it } from "vitest"; import { - agentsFromConfig, + rolesFromConfig, enabledReposFromConfig, MAX_ORG_CONFIG_YAML_DEPTH, MAX_ORG_CONFIG_YAML_UTF8_BYTES, @@ -65,28 +65,73 @@ repos: {} expect(validateOrgConfig(cfg)).toMatch(/non-negative integer/); }); - it("rejects invalid agent role", () => { + it.each(["fullsend", "triage", "coder", "review", "fix", "retro", "prioritize", "e2e"])( + "accepts defaults role %s", + (role) => { + const cfg = parseOrgConfigYaml(`version: "1" +dispatch: + platform: github-actions +defaults: + roles: [${role}] +repos: {} +`); + expect(validateOrgConfig(cfg)).toBeNull(); + }, + ); + + it("rejects invalid defaults role", () => { + const cfg = parseOrgConfigYaml(`version: "1" +dispatch: + platform: github-actions +defaults: + roles: [bogus] +repos: {} +`); + expect(validateOrgConfig(cfg)).toMatch(/invalid role/); + }); + + it("parses agents with source-based entries without error", () => { const cfg = parseOrgConfigYaml(`version: "1" dispatch: platform: github-actions defaults: roles: [fullsend] agents: - - role: not-a-valid-role + - source: harness/triage.yaml + - source: harness/coder.yaml + name: my-coder + - harness/review.yaml repos: {} `); - expect(validateOrgConfig(cfg)).toMatch(/invalid agent role/); + expect(validateOrgConfig(cfg)).toBeNull(); }); - it("lists agents and enabled repos from config", () => { + it("rolesFromConfig returns defaults.roles as role objects", () => { + const cfg = parseOrgConfigYaml(`version: "1" +dispatch: + platform: github-actions +defaults: + roles: [triage, coder] +repos: {} +`); + expect(rolesFromConfig(cfg)).toEqual([{ role: "triage" }, { role: "coder" }]); + }); + + it("rolesFromConfig returns empty array when no defaults.roles", () => { + const cfg = parseOrgConfigYaml(`version: "1" +dispatch: + platform: github-actions +repos: {} +`); + expect(rolesFromConfig(cfg)).toEqual([]); + }); + + it("lists enabled repos from config", () => { const cfg = parseOrgConfigYaml(`version: "1" dispatch: platform: github-actions defaults: roles: [fullsend] -agents: - - role: triage - slug: t repos: zed: enabled: false @@ -96,7 +141,6 @@ repos: enabled: true `); expect(validateOrgConfig(cfg)).toBeNull(); - expect(agentsFromConfig(cfg)).toEqual([{ role: "triage" }]); expect(enabledReposFromConfig(cfg)).toEqual(["alpha", "beta"]); }); diff --git a/web/admin/src/lib/layers/orgConfigParse.ts b/web/admin/src/lib/layers/orgConfigParse.ts index 229fed8fff..db3e66b5d6 100644 --- a/web/admin/src/lib/layers/orgConfigParse.ts +++ b/web/admin/src/lib/layers/orgConfigParse.ts @@ -9,11 +9,11 @@ export type OrgConfigYaml = { max_implementation_retries?: number; auto_merge?: boolean; }; - agents?: { role: string; name?: string; slug?: string }[]; + agents?: (string | { source?: string; name?: string; enabled?: boolean })[]; repos?: Record; }; -const VALID_ROLES = new Set(["fullsend", "triage", "coder", "review"]); +const VALID_ROLES = new Set(["fullsend", "triage", "coder", "review", "fix", "retro", "prioritize", "e2e"]); /** 512 KiB — more than sufficient for any realistic org `config.yaml`. */ export const MAX_ORG_CONFIG_YAML_UTF8_BYTES = 512 * 1024; @@ -111,12 +111,9 @@ function assertOrgConfigShape(doc: Record): void { } for (let i = 0; i < doc.agents.length; i++) { const el = doc.agents[i]; + if (typeof el === "string") continue; if (el === null || typeof el !== "object" || Array.isArray(el)) { - throw new Error(`parsing org config: agents[${i}] must be a mapping with a string role`); - } - const role = (el as Record).role; - if (typeof role !== "string") { - throw new Error(`parsing org config: agents[${i}].role must be a string`); + throw new Error(`parsing org config: agents[${i}] must be a string or mapping`); } } } @@ -153,20 +150,15 @@ export function validateOrgConfig(cfg: OrgConfigYaml): string | null { } for (const role of cfg.defaults?.roles ?? []) { if (!VALID_ROLES.has(role)) { - return `invalid role ${JSON.stringify(role)}: must be one of fullsend, triage, coder, review`; - } - } - for (const agent of cfg.agents ?? []) { - if (!VALID_ROLES.has(agent.role)) { - return `invalid agent role ${JSON.stringify(agent.role)}: must be one of fullsend, triage, coder, review`; + return `invalid role ${JSON.stringify(role)}: must be one of fullsend, triage, coder, review, fix, retro, prioritize, e2e`; } } return null; } -/** Agent rows for secrets-layer analyze (mirrors `config.OrgConfig.Agents`). */ -export function agentsFromConfig(cfg: OrgConfigYaml): { role: string }[] { - return (cfg.agents ?? []).map((a) => ({ role: a.role })); +/** Roles for secrets-layer analyze — Go keys secrets by role, not agent name. */ +export function rolesFromConfig(cfg: OrgConfigYaml): { role: string }[] { + return (cfg.defaults?.roles ?? []).map((r) => ({ role: r })); } /** Enabled repo names for enrollment-layer analyze (sorted). */ diff --git a/web/admin/src/lib/orgs/orgListRow.ts b/web/admin/src/lib/orgs/orgListRow.ts index b0372fcb46..d7d7bb5c97 100644 --- a/web/admin/src/lib/orgs/orgListRow.ts +++ b/web/admin/src/lib/orgs/orgListRow.ts @@ -9,7 +9,7 @@ import { import { CONFIG_FILE_PATH, CONFIG_REPO_NAME } from "../layers/constants"; import { createLayerGithub } from "../layers/githubClient"; import { - agentsFromConfig, + rolesFromConfig, enabledReposFromConfig, OrgConfigYamlLimitError, parseOrgConfigYaml, @@ -86,7 +86,7 @@ export async function analyzeOrgForOrgList( try { const cfg = parseOrgConfigYaml(raw); if (validateOrgConfig(cfg) === null) { - agents = agentsFromConfig(cfg); + agents = rolesFromConfig(cfg); enabledRepos = enabledReposFromConfig(cfg); } } catch (e) {