{"version":3,"file":"config.d.ts","sourceRoot":"","sources":["../../src/evolve/config.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,aAAa,CAAC;AAG5C,OAAO,KAAK,EAAE,gBAAgB,EAAsB,MAAM,aAAa,CAAC;AAqIxE,wBAAsB,4BAA4B,CAAC,KAAK,EAAE,QAAQ,GAAG,OAAO,CAAC,IAAI,CAAC,CAoBjF;AAED,wBAAsB,oBAAoB,CAAC,KAAK,EAAE,QAAQ,GAAG,OAAO,CAAC,gBAAgB,CAAC,CAGrF;AAYD;;;;GAIG;AACH,wBAAsB,2BAA2B,CAChD,KAAK,EAAE,QAAQ,EACf,GAAG,EAAE,MAAM,EACX,QAAQ,EAAE,MAAM,GACd,OAAO,CAAC,gBAAgB,CAAC,CAkB3B;AAED,wBAAsB,qBAAqB,CAAC,KAAK,EAAE,QAAQ,GAAG,OAAO,CAAC,MAAM,CAAC,CAK5E;AAED,wBAAsB,sBAAsB,CAAC,KAAK,EAAE,QAAQ,GAAG,OAAO,CAAC,MAAM,CAAC,CAK7E","sourcesContent":["import { copyFile, readFile } from \"node:fs/promises\";\nimport type { ThinkingLevel } from \"@ch1nyzzz/pi-agent-core\";\nimport type { EvoPaths } from \"../paths.ts\";\nimport { ensureEvoLayout } from \"../paths.ts\";\nimport { atomicWriteFile, atomicWriteJson, readJsonIfExists } from \"../storage.ts\";\nimport type { EvoControlConfig, EvoRoleModelConfig } from \"../types.ts\";\n\nconst DEFAULT_WORKFLOW_URL = new URL(\"../prompts/evolution-workflow.md\", import.meta.url);\nconst THINKING_LEVELS = new Set<ThinkingLevel>([\"off\", \"minimal\", \"low\", \"medium\", \"high\", \"xhigh\", \"max\"]);\n\nconst DEFAULT_EVO_CONTROL_CONFIG: Readonly<EvoControlConfig> = {\n\tschemaVersion: 1,\n\tmodels: {\n\t\tresearchPlanner: { model: \"openai-codex/gpt-5.6-sol\", thinkingLevel: \"max\" },\n\t\tbuilder: { model: \"openai-codex/gpt-5.6-terra\", thinkingLevel: \"max\" },\n\t\tevaluator: { model: \"openai-codex/gpt-5.6-terra\", thinkingLevel: \"xhigh\" },\n\t\ttriage: { model: \"openai-codex/gpt-5.6-luna\", thinkingLevel: \"low\" },\n\t},\n\trelease: {\n\t\tautoApplyT0: true,\n\t\tautoStartDataTrial: true,\n\t\tautoStartComponentTrial: true,\n\t\tautoKeepSuccessfulTrial: true,\n\t},\n\tgrants: {\n\t\tapproval: \"auto\",\n\t},\n\tverification: {\n\t\tapproval: \"ask\",\n\t},\n\ttriage: {\n\t\teveryNSessions: 5,\n\t},\n};\n\nfunction asRecord(value: unknown, label: string): Record<string, unknown> {\n\tif (typeof value !== \"object\" || value === null || Array.isArray(value)) {\n\t\tthrow new Error(`${label} must be an object`);\n\t}\n\treturn value as Record<string, unknown>;\n}\n\nfunction exactKeys(record: Record<string, unknown>, allowed: readonly string[], label: string): void {\n\tconst keys = new Set(allowed);\n\tfor (const key of Object.keys(record)) if (!keys.has(key)) throw new Error(`${label} has unknown key: ${key}`);\n}\n\nfunction parseRole(value: unknown, label: string): EvoRoleModelConfig {\n\tconst role = asRecord(value, label);\n\texactKeys(role, [\"model\", \"thinkingLevel\"], label);\n\tif (typeof role.model !== \"string\" || !role.model || role.model.length > 200 || /\\s/.test(role.model)) {\n\t\tthrow new Error(`${label}.model must be a provider/model route or unique model id`);\n\t}\n\tif (role.thinkingLevel !== undefined && !THINKING_LEVELS.has(role.thinkingLevel as ThinkingLevel)) {\n\t\tthrow new Error(`${label}.thinkingLevel is invalid`);\n\t}\n\treturn {\n\t\tmodel: role.model,\n\t\t...(role.thinkingLevel ? { thinkingLevel: role.thinkingLevel as ThinkingLevel } : {}),\n\t};\n}\n\nfunction parseEvoControlConfig(value: unknown): EvoControlConfig {\n\tconst config = asRecord(value, \"Evo control config\");\n\texactKeys(config, [\"schemaVersion\", \"models\", \"release\", \"grants\", \"verification\", \"triage\"], \"Evo control config\");\n\tif (config.schemaVersion !== 1) throw new Error(\"Evo control config schemaVersion must be 1\");\n\tconst models = asRecord(config.models, \"Evo control config.models\");\n\texactKeys(models, [\"researchPlanner\", \"builder\", \"evaluator\", \"triage\"], \"Evo control config.models\");\n\tconst release = asRecord(config.release, \"Evo control config.release\");\n\texactKeys(\n\t\trelease,\n\t\t[\"autoApplyT0\", \"autoStartDataTrial\", \"autoStartComponentTrial\", \"autoKeepSuccessfulTrial\"],\n\t\t\"Evo control config.release\",\n\t);\n\tfor (const key of [\n\t\t\"autoApplyT0\",\n\t\t\"autoStartDataTrial\",\n\t\t\"autoStartComponentTrial\",\n\t\t\"autoKeepSuccessfulTrial\",\n\t] as const) {\n\t\tif (typeof release[key] !== \"boolean\") throw new Error(`Evo control config.release.${key} must be boolean`);\n\t}\n\tlet grantApproval: \"auto\" | \"prompt\" = \"auto\";\n\tif (config.grants !== undefined) {\n\t\tconst grants = asRecord(config.grants, \"Evo control config.grants\");\n\t\texactKeys(grants, [\"approval\"], \"Evo control config.grants\");\n\t\tif (grants.approval !== \"auto\" && grants.approval !== \"prompt\") {\n\t\t\tthrow new Error(\"Evo control config.grants.approval must be 'auto' or 'prompt'\");\n\t\t}\n\t\tgrantApproval = grants.approval;\n\t}\n\tlet verificationApproval: \"ask\" | \"auto\" = \"ask\";\n\tif (config.verification !== undefined) {\n\t\tconst verification = asRecord(config.verification, \"Evo control config.verification\");\n\t\texactKeys(verification, [\"approval\"], \"Evo control config.verification\");\n\t\tif (verification.approval !== \"ask\" && verification.approval !== \"auto\") {\n\t\t\tthrow new Error(\"Evo control config.verification.approval must be 'ask' or 'auto'\");\n\t\t}\n\t\tverificationApproval = verification.approval;\n\t}\n\tlet triageEveryNSessions = DEFAULT_EVO_CONTROL_CONFIG.triage.everyNSessions;\n\tif (config.triage !== undefined) {\n\t\tconst triage = asRecord(config.triage, \"Evo control config.triage\");\n\t\texactKeys(triage, [\"everyNSessions\"], \"Evo control config.triage\");\n\t\tif (!Number.isSafeInteger(triage.everyNSessions) || (triage.everyNSessions as number) < 1) {\n\t\t\tthrow new Error(\"Evo control config.triage.everyNSessions must be a positive integer\");\n\t\t}\n\t\ttriageEveryNSessions = triage.everyNSessions as number;\n\t}\n\treturn {\n\t\tschemaVersion: 1,\n\t\tmodels: {\n\t\t\tresearchPlanner: parseRole(models.researchPlanner, \"Evo control config.models.researchPlanner\"),\n\t\t\tbuilder: parseRole(models.builder, \"Evo control config.models.builder\"),\n\t\t\tevaluator: parseRole(models.evaluator, \"Evo control config.models.evaluator\"),\n\t\t\ttriage:\n\t\t\t\tmodels.triage === undefined\n\t\t\t\t\t? DEFAULT_EVO_CONTROL_CONFIG.models.triage\n\t\t\t\t\t: parseRole(models.triage, \"Evo control config.models.triage\"),\n\t\t},\n\t\trelease: {\n\t\t\tautoApplyT0: release.autoApplyT0 as boolean,\n\t\t\tautoStartDataTrial: release.autoStartDataTrial as boolean,\n\t\t\tautoStartComponentTrial: release.autoStartComponentTrial as boolean,\n\t\t\tautoKeepSuccessfulTrial: release.autoKeepSuccessfulTrial as boolean,\n\t\t},\n\t\tgrants: {\n\t\t\tapproval: grantApproval,\n\t\t},\n\t\tverification: {\n\t\t\tapproval: verificationApproval,\n\t\t},\n\t\ttriage: {\n\t\t\teveryNSessions: triageEveryNSessions,\n\t\t},\n\t};\n}\n\nexport async function ensureEvolutionConfiguration(paths: EvoPaths): Promise<void> {\n\tawait ensureEvoLayout(paths);\n\tif (!(await readJsonIfExists(paths.config))) await atomicWriteJson(paths.config, DEFAULT_EVO_CONTROL_CONFIG);\n\ttry {\n\t\tawait readFile(paths.workflow, \"utf8\");\n\t} catch (error) {\n\t\tif (typeof error !== \"object\" || error === null || !(\"code\" in error) || error.code !== \"ENOENT\") throw error;\n\t\ttry {\n\t\t\tawait copyFile(DEFAULT_WORKFLOW_URL, paths.workflow, 1);\n\t\t} catch (copyError) {\n\t\t\tif (\n\t\t\t\ttypeof copyError !== \"object\" ||\n\t\t\t\tcopyError === null ||\n\t\t\t\t!(\"code\" in copyError) ||\n\t\t\t\tcopyError.code !== \"EEXIST\"\n\t\t\t) {\n\t\t\t\tthrow copyError;\n\t\t\t}\n\t\t}\n\t}\n}\n\nexport async function readEvoControlConfig(paths: EvoPaths): Promise<EvoControlConfig> {\n\tawait ensureEvolutionConfiguration(paths);\n\treturn parseEvoControlConfig(JSON.parse(await readFile(paths.config, \"utf8\")));\n}\n\nconst SETTABLE_CONFIG_KEY_PATTERN =\n\t/^(grants\\.approval|verification\\.approval|triage\\.everyNSessions|models\\.(researchPlanner|builder|evaluator|triage)\\.(model|thinkingLevel)|release\\.(autoApplyT0|autoStartDataTrial|autoStartComponentTrial|autoKeepSuccessfulTrial))$/;\n\nfunction coerceConfigValue(raw: string): unknown {\n\tif (raw === \"true\") return true;\n\tif (raw === \"false\") return false;\n\tif (/^[0-9]+$/.test(raw)) return Number(raw);\n\treturn raw;\n}\n\n/**\n * Set one whitelisted dotted config key and persist the result. The updated\n * object is re-validated through the same fail-closed parser as reads, so an\n * invalid value can never reach disk.\n */\nexport async function updateEvoControlConfigValue(\n\tpaths: EvoPaths,\n\tkey: string,\n\trawValue: string,\n): Promise<EvoControlConfig> {\n\tif (!SETTABLE_CONFIG_KEY_PATTERN.test(key)) {\n\t\tthrow new Error(\n\t\t\t`Unsupported config key: ${key}. Settable keys: grants.approval, verification.approval, triage.everyNSessions, models.<role>.model, models.<role>.thinkingLevel, release.<flag>`,\n\t\t);\n\t}\n\tconst current = await readEvoControlConfig(paths);\n\tconst next = JSON.parse(JSON.stringify(current)) as Record<string, unknown>;\n\tconst segments = key.split(\".\");\n\tlet cursor: Record<string, unknown> = next;\n\tfor (const segment of segments.slice(0, -1)) {\n\t\tcursor[segment] ??= {};\n\t\tcursor = cursor[segment] as Record<string, unknown>;\n\t}\n\tcursor[segments[segments.length - 1] as string] = coerceConfigValue(rawValue);\n\tconst validated = parseEvoControlConfig(next);\n\tawait atomicWriteJson(paths.config, validated);\n\treturn validated;\n}\n\nexport async function readEvolutionWorkflow(paths: EvoPaths): Promise<string> {\n\tawait ensureEvolutionConfiguration(paths);\n\tconst workflow = await readFile(paths.workflow, \"utf8\");\n\tif (!workflow.trim()) throw new Error(\"Evo workflow.md must not be empty\");\n\treturn workflow;\n}\n\nexport async function resetEvolutionWorkflow(paths: EvoPaths): Promise<string> {\n\tawait ensureEvoLayout(paths);\n\tconst workflow = await readFile(DEFAULT_WORKFLOW_URL, \"utf8\");\n\tawait atomicWriteFile(paths.workflow, workflow);\n\treturn workflow;\n}\n"]}