{"version":3,"file":"agent-management.d.ts","sourceRoot":"","sources":["../../../src/agents/agent-management.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,yBAAyB,CAAC;AAC/D,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,2BAA2B,CAAC;AAYlE,OAAO,KAAK,EAEX,OAAO,EACP,eAAe,EAGf,MAAM,oBAAoB,CAAC;AAI5B,OAAO,EACN,KAAK,WAAW,EAiBhB,MAAM,aAAa,CAAC;AAkBrB,KAAK,iBAAiB,GAAG,IAAI,CAAC,gBAAgB,EAAE,KAAK,GAAG,eAAe,CAAC,GAAG;IAC1E,KAAK,CAAC,EAAE,gBAAgB,CAAC,OAAO,CAAC,CAAC;IAClC,MAAM,CAAC,EAAE,eAAe,CAAC;IACzB,gBAAgB,CAAC,EAAE,MAAM,CAAC;CAC1B,CAAC;AAEF,UAAU,gBAAgB;IACzB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB,MAAM,CAAC,EAAE,OAAO,CAAC;CACjB;AA4ND,wBAAgB,mBAAmB,CAAC,KAAK,EAAE,WAAW,GAAG,WAAW,CAwDnE;AAWD,wBAAgB,+BAA+B,CAAC,KAAK,EAAE,WAAW,EAAE,GAAG,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,GAAG,CAAC,MAAM,CAAC,CAoD7G;AA6gBD,wBAAgB,UAAU,CAAC,MAAM,EAAE,gBAAgB,EAAE,GAAG,EAAE,iBAAiB,GAAG,eAAe,CAAC,OAAO,CAAC,CAgDrG;AAwHD,wBAAgB,YAAY,CAAC,MAAM,EAAE,gBAAgB,EAAE,GAAG,EAAE,iBAAiB,GAAG,eAAe,CAAC,OAAO,CAAC,CAgFvG;AAED,wBAAgB,YAAY,CAAC,MAAM,EAAE,gBAAgB,EAAE,GAAG,EAAE,iBAAiB,GAAG,eAAe,CAAC,OAAO,CAAC,CA+IvG;AA2ND,wBAAgB,sBAAsB,CACrC,MAAM,EAAE,MAAM,EACd,MAAM,EAAE,gBAAgB,EACxB,GAAG,EAAE,iBAAiB,GACpB,eAAe,CAAC,OAAO,CAAC,CAyB1B","sourcesContent":["import * as fs from \"node:fs\";\nimport * as path from \"node:path\";\nimport type { AgentToolResult } from \"@lpb-work/pi-agent-core\";\nimport type { ExtensionContext } from \"@lpb-work/pi-coding-agent\";\nimport { validateAcceptanceInput } from \"../runs/shared/acceptance.ts\";\nimport {\n\tcapabilityCeilingAgentRestrictionSources,\n\tisAgentAllowedByCapabilityCeiling,\n\tresolveCurrentSubagentCapabilityCeiling,\n} from \"../runs/shared/capability-ceiling.ts\";\nimport { type ParentModel, resolveSubagentModelOverride } from \"../runs/shared/model-fallback.ts\";\nimport { validateToolBudgetConfig } from \"../runs/shared/tool-budget.ts\";\nimport { resolveTurnBudgetConfig } from \"../runs/shared/turn-budget.ts\";\nimport { toModelInfo } from \"../shared/model-info.ts\";\nimport { parseRunnerRetryConfig } from \"../shared/runner-config.ts\";\nimport type {\n\tAcceptanceInput,\n\tDetails,\n\tExtensionConfig,\n\tRunnerRetryConfig,\n\tToolBudgetConfig,\n} from \"../shared/types.ts\";\nimport { getProjectConfigDir } from \"../shared/utils.ts\";\nimport { mergeAgentsForScope } from \"./agent-selection.ts\";\nimport { serializeAgent } from \"./agent-serializer.ts\";\nimport {\n\ttype AgentConfig,\n\ttype AgentScope,\n\ttype AgentSource,\n\tBUILTIN_AGENT_NAMES,\n\tbuildRuntimeName,\n\ttype ChainConfig,\n\ttype ChainStepConfig,\n\tdefaultInheritProjectContext,\n\tdefaultInheritSkills,\n\tdefaultSystemPromptMode,\n\tdiscoverAgentsAll,\n\tfrontmatterNameForConfig,\n\tmergeBuiltinAgentOverride,\n\tparsePackageName,\n\tremoveBuiltinAgentOverride,\n\tremoveBuiltinAgentOverrideFields,\n\tresolveAgentName,\n} from \"./agents.ts\";\nimport { serializeChain, serializeJsonChain } from \"./chain-serializer.ts\";\nimport { parseFrontmatter } from \"./frontmatter.ts\";\nimport { buildProactiveSkillSubagentRecommendationLines } from \"./proactive-skills.ts\";\nimport { discoverAvailableSkills, resolveSkills } from \"./skills.ts\";\n\ntype ManagementAction =\n\t| \"list\"\n\t| \"get\"\n\t| \"models\"\n\t| \"create\"\n\t| \"update\"\n\t| \"delete\"\n\t| \"eject\"\n\t| \"disable\"\n\t| \"enable\"\n\t| \"reset\";\ntype ManagementScope = \"user\" | \"project\";\ntype ManagementContext = Pick<ExtensionContext, \"cwd\" | \"modelRegistry\"> & {\n\tmodel?: ExtensionContext[\"model\"];\n\tconfig?: ExtensionConfig;\n\tcurrentSessionId?: string;\n};\n\ninterface ManagementParams {\n\taction?: string;\n\tagent?: string;\n\tchainName?: string;\n\tagentScope?: unknown;\n\tconfig?: unknown;\n}\n\nfunction result(text: string, isError = false): AgentToolResult<Details> {\n\treturn { content: [{ type: \"text\", text }], isError, details: { mode: \"management\", results: [] } };\n}\n\nfunction parseCsv(value: string): string[] {\n\treturn [\n\t\t...new Set(\n\t\t\tvalue\n\t\t\t\t.split(\",\")\n\t\t\t\t.map((v) => v.trim())\n\t\t\t\t.filter(Boolean),\n\t\t),\n\t];\n}\n\nfunction configObject(config: unknown): { value?: Record<string, unknown>; error?: string } {\n\tlet val = config;\n\tif (typeof val === \"string\") {\n\t\ttry {\n\t\t\tval = JSON.parse(val);\n\t\t} catch (error) {\n\t\t\tconst message = error instanceof Error ? error.message : String(error);\n\t\t\treturn { error: `config must be valid JSON: ${message}` };\n\t\t}\n\t}\n\tif (!val || typeof val !== \"object\" || Array.isArray(val)) return {};\n\treturn { value: val as Record<string, unknown> };\n}\n\nfunction hasKey(obj: Record<string, unknown>, key: string): boolean {\n\treturn Object.hasOwn(obj, key);\n}\n\nfunction asDisambiguationScope(scope: unknown): ManagementScope | undefined {\n\tif (scope === \"user\" || scope === \"project\") return scope;\n\treturn undefined;\n}\n\nfunction actionScope(\n\tscope: unknown,\n\taction: ManagementAction,\n): { scope?: ManagementScope; error?: AgentToolResult<Details> } {\n\tif (scope === undefined) return { scope: \"user\" };\n\tconst parsed = asDisambiguationScope(scope);\n\treturn parsed ? { scope: parsed } : { error: result(`agentScope must be 'user' or 'project' for ${action}.`, true) };\n}\n\nfunction normalizeListScope(scope: unknown): AgentScope | undefined {\n\tif (scope === undefined) return \"both\";\n\tif (scope === \"user\" || scope === \"project\" || scope === \"both\") return scope;\n\treturn undefined;\n}\n\nfunction sanitizeName(name: string): string {\n\treturn name\n\t\t.toLowerCase()\n\t\t.trim()\n\t\t.replace(/\\s+/g, \"-\")\n\t\t.replace(/[^a-z0-9-]/g, \"\")\n\t\t.replace(/-+/g, \"-\")\n\t\t.replace(/^-+|-+$/g, \"\");\n}\n\nfunction parsePackageConfig(value: unknown): { packageName?: string; error?: string } {\n\treturn parsePackageName(value, \"config.package\");\n}\n\nfunction allAgents(d: {\n\tbuiltin: AgentConfig[];\n\tpackage: AgentConfig[];\n\tuser: AgentConfig[];\n\tproject: AgentConfig[];\n}): AgentConfig[] {\n\treturn [...d.builtin, ...d.package, ...d.user, ...d.project];\n}\n\nfunction availableNames(cwd: string, kind: \"agent\" | \"chain\"): string[] {\n\tconst d = discoverAgentsAll(cwd);\n\tconst items = kind === \"agent\" ? allAgents(d) : d.chains;\n\treturn [...new Set(items.map((x) => x.name))].sort((a, b) => a.localeCompare(b));\n}\n\nfunction findAgents(name: string, cwd: string, scope: AgentScope = \"both\"): AgentConfig[] {\n\tconst d = discoverAgentsAll(cwd);\n\tconst raw = name.trim();\n\tconst sanitized = sanitizeName(raw);\n\tconst scoped = mergeAgentsForScope(scope, d.user, d.project, d.builtin, d.package);\n\tlet resolved = resolveAgentName(raw, scoped);\n\tif (!resolved.agent && !resolved.error && sanitized !== raw) resolved = resolveAgentName(sanitized, scoped);\n\tif (resolved.agent)\n\t\treturn scoped\n\t\t\t.filter((agent) => agent.name === resolved.agent!.name)\n\t\t\t.sort((a, b) => a.source.localeCompare(b.source));\n\treturn scoped\n\t\t.filter(\n\t\t\t(agent) =>\n\t\t\t\tBoolean(resolveAgentName(raw, [agent]).agent) ||\n\t\t\t\t(sanitized !== raw && Boolean(resolveAgentName(sanitized, [agent]).agent)),\n\t\t)\n\t\t.sort((a, b) => a.source.localeCompare(b.source));\n}\n\nfunction findChains(name: string, cwd: string, scope: AgentScope = \"both\"): ChainConfig[] {\n\tconst raw = name.trim();\n\tconst sanitized = sanitizeName(raw);\n\treturn discoverAgentsAll(cwd)\n\t\t.chains.filter(\n\t\t\t(c) =>\n\t\t\t\t(scope === \"both\" || c.source === \"package\" || c.source === scope) &&\n\t\t\t\t(c.name === raw || c.name === sanitized),\n\t\t)\n\t\t.sort((a, b) => a.source.localeCompare(b.source));\n}\n\nconst AGENT_SOURCE_PRECEDENCE: Record<AgentSource, number> = { builtin: 0, package: 1, user: 2, project: 3 };\n\n// Returns the highest-precedence definition for a resolved canonical name (project > user > package > builtin),\n// matching mergeAgentsForScope for \"both\", including disabled agents so disable/enable can locate hidden targets.\nfunction resolveEffectiveAgent(\n\td: ReturnType<typeof discoverAgentsAll>,\n\tname: string,\n): { agent?: AgentConfig; error?: string } {\n\tconst raw = name.trim();\n\tconst candidates = allAgents(d);\n\tlet resolved = resolveAgentName(raw, candidates);\n\tif (!resolved.agent && !resolved.error) {\n\t\tconst sanitized = sanitizeName(raw);\n\t\tif (sanitized !== raw) resolved = resolveAgentName(sanitized, candidates);\n\t}\n\tif (resolved.error) return { error: resolved.error };\n\tif (!resolved.agent) return {};\n\tconst matches = candidates.filter((agent) => agent.name === resolved.agent!.name);\n\treturn {\n\t\tagent: matches.reduce((best, agent) =>\n\t\t\tAGENT_SOURCE_PRECEDENCE[agent.source] > AGENT_SOURCE_PRECEDENCE[best.source] ? agent : best,\n\t\t),\n\t};\n}\n\nfunction nameExistsInScope(cwd: string, scope: ManagementScope, name: string, excludePath?: string): boolean {\n\tconst d = discoverAgentsAll(cwd);\n\tfor (const a of scope === \"user\" ? d.user : d.project) {\n\t\tif (a.name === name && a.filePath !== excludePath) return true;\n\t}\n\tfor (const c of d.chains) {\n\t\tif (c.source === scope && c.name === name && c.filePath !== excludePath) return true;\n\t}\n\treturn false;\n}\n\nfunction isMutableSource(source: AgentSource): source is ManagementScope {\n\treturn source === \"user\" || source === \"project\";\n}\n\nfunction unknownChainAgents(cwd: string, steps: ChainStepConfig[]): string[] {\n\tconst d = discoverAgentsAll(cwd);\n\tconst agents = allAgents(d);\n\treturn [\n\t\t...new Set(\n\t\t\tsteps\n\t\t\t\t.map((s) => s.agent)\n\t\t\t\t.filter(\n\t\t\t\t\t(agentName): agentName is string =>\n\t\t\t\t\t\ttypeof agentName === \"string\" && !resolveAgentName(agentName, agents).agent,\n\t\t\t\t),\n\t\t),\n\t].sort((a, b) => a.localeCompare(b));\n}\n\nfunction chainStepWarnings(ctx: ManagementContext, steps: ChainStepConfig[]): string[] {\n\tconst warnings: string[] = [];\n\tconst available = new Set(discoverAvailableSkills(ctx.cwd).map((s) => s.name));\n\tfor (let i = 0; i < steps.length; i++) {\n\t\tconst s = steps[i]!;\n\t\tif (s.model) {\n\t\t\tconst found = ctx.modelRegistry\n\t\t\t\t.getAvailable()\n\t\t\t\t.some((m) => `${m.provider}/${m.id}` === s.model || m.id === s.model);\n\t\t\tif (!found)\n\t\t\t\twarnings.push(\n\t\t\t\t\t`Warning: step ${i + 1} (${s.agent}): model '${s.model}' is not in the current model registry.`,\n\t\t\t\t);\n\t\t}\n\t\tif (Array.isArray(s.skills) && s.skills.length > 0) {\n\t\t\tconst missing = s.skills.filter((sk) => !available.has(sk));\n\t\t\tif (missing.length)\n\t\t\t\twarnings.push(`Warning: step ${i + 1} (${s.agent}): skills not found: ${missing.join(\", \")}.`);\n\t\t}\n\t}\n\treturn warnings;\n}\n\nfunction modelWarning(ctx: ManagementContext, model: string | undefined): string | undefined {\n\tif (!model) return undefined;\n\tconst found = ctx.modelRegistry.getAvailable().some((m) => `${m.provider}/${m.id}` === model || m.id === model);\n\treturn found ? undefined : `Warning: model '${model}' is not in the current model registry.`;\n}\n\nfunction fallbackModelsWarning(ctx: ManagementContext, fallbackModels: string[] | undefined): string | undefined {\n\tif (!fallbackModels || fallbackModels.length === 0) return undefined;\n\tconst available = new Set(ctx.modelRegistry.getAvailable().flatMap((m) => [`${m.provider}/${m.id}`, m.id]));\n\tconst missing = fallbackModels.filter((model) => !available.has(model));\n\treturn missing.length\n\t\t? `Warning: fallback models not in the current model registry: ${missing.join(\", \")}.`\n\t\t: undefined;\n}\n\nfunction skillsWarning(cwd: string, agent: Pick<AgentConfig, \"skills\" | \"skillPath\" | \"filePath\">): string | undefined {\n\tif (!agent.skills?.length) return undefined;\n\tconst { missing } = resolveSkills(\n\t\tagent.skills,\n\t\tcwd,\n\t\tagent.skillPath,\n\t\tagent.filePath ? path.dirname(agent.filePath) : cwd,\n\t);\n\treturn missing.length ? `Warning: skills not found: ${missing.join(\", \")}.` : undefined;\n}\n\nexport function editableAgentConfig(agent: AgentConfig): AgentConfig {\n\tconst { extensions: _extensions, ...withoutExtensions } = agent;\n\tconst base = agent.override?.base;\n\tconst {\n\t\toverride: _override,\n\t\tmodel: _model,\n\t\tfallbackModels: _fallbackModels,\n\t\tthinking: _thinking,\n\t\tsystemPromptMode: _systemPromptMode,\n\t\tinheritProjectContext: _inheritProjectContext,\n\t\tinheritSkills: _inheritSkills,\n\t\tdefaultContext: _defaultContext,\n\t\tacceptanceRole: _acceptanceRole,\n\t\tdisabled: _disabled,\n\t\tsystemPrompt: _systemPrompt,\n\t\tskills: _skills,\n\t\tskillPath: _skillPath,\n\t\ttools: _tools,\n\t\tmcpDirectTools: _mcpDirectTools,\n\t\tsubagentOnlyExtensions: _subagentOnlyExtensions,\n\t\tcompletionGuard: _completionGuard,\n\t\t...editable\n\t} = withoutExtensions;\n\tif (!base) {\n\t\treturn {\n\t\t\t...withoutExtensions,\n\t\t\t...(agent.extensionsFromDefault\n\t\t\t\t? {}\n\t\t\t\t: agent.extensions !== undefined\n\t\t\t\t\t? { extensions: [...agent.extensions] }\n\t\t\t\t\t: {}),\n\t\t};\n\t}\n\n\treturn {\n\t\t...editable,\n\t\t...(base.model !== undefined ? { model: base.model } : {}),\n\t\t...(base.fallbackModels !== undefined ? { fallbackModels: [...base.fallbackModels] } : {}),\n\t\t...(base.thinking !== undefined ? { thinking: base.thinking } : {}),\n\t\tsystemPromptMode: base.systemPromptMode,\n\t\tinheritProjectContext: base.inheritProjectContext,\n\t\tinheritSkills: base.inheritSkills,\n\t\t...(base.defaultContext !== undefined ? { defaultContext: base.defaultContext } : {}),\n\t\t...(base.acceptanceRole !== undefined ? { acceptanceRole: base.acceptanceRole } : {}),\n\t\t...(base.disabled !== undefined ? { disabled: base.disabled } : {}),\n\t\tsystemPrompt: base.systemPrompt,\n\t\t...(base.skills !== undefined ? { skills: [...base.skills] } : {}),\n\t\t...(base.skillPath !== undefined ? { skillPath: [...base.skillPath] } : {}),\n\t\t...(base.tools !== undefined ? { tools: [...base.tools] } : {}),\n\t\t...(base.mcpDirectTools !== undefined ? { mcpDirectTools: [...base.mcpDirectTools] } : {}),\n\t\t...(base.extensions !== undefined ? { extensions: [...base.extensions] } : {}),\n\t\t...(base.subagentOnlyExtensions !== undefined\n\t\t\t? { subagentOnlyExtensions: [...base.subagentOnlyExtensions] }\n\t\t\t: {}),\n\t\t...(base.completionGuard !== undefined ? { completionGuard: base.completionGuard } : {}),\n\t};\n}\n\nfunction readAgentFrontmatterFields(filePath: string): Set<string> {\n\ttry {\n\t\tconst { frontmatter } = parseFrontmatter(fs.readFileSync(filePath, \"utf-8\"));\n\t\treturn new Set(Object.keys(frontmatter));\n\t} catch {\n\t\treturn new Set();\n\t}\n}\n\nexport function preservedAgentFrontmatterFields(agent: AgentConfig, cfg: Record<string, unknown>): Set<string> {\n\tconst fields = readAgentFrontmatterFields(agent.filePath);\n\tconst changed = (...names: string[]) => {\n\t\tfor (const name of names) fields.delete(name);\n\t};\n\n\tif (hasKey(cfg, \"name\")) changed(\"name\");\n\tif (hasKey(cfg, \"package\")) changed(\"package\");\n\tif (hasKey(cfg, \"description\")) changed(\"description\");\n\tif (hasKey(cfg, \"aliases\")) changed(\"alias\", \"aliases\");\n\tif (hasKey(cfg, \"systemPrompt\")) changed(\"systemPrompt\");\n\tif (hasKey(cfg, \"runner\")) changed(\"runner\");\n\tif (hasKey(cfg, \"model\")) changed(\"model\");\n\tif (hasKey(cfg, \"fallbackModels\")) changed(\"fallbackModels\");\n\tif (hasKey(cfg, \"tools\")) changed(\"tools\");\n\tif (hasKey(cfg, \"skills\")) changed(\"skill\", \"skills\");\n\tif (hasKey(cfg, \"skillPath\")) changed(\"skillPath\");\n\tif (hasKey(cfg, \"extensions\")) changed(\"extensions\");\n\tif (hasKey(cfg, \"subagentOnlyExtensions\")) changed(\"subagentOnlyExtensions\");\n\tif (hasKey(cfg, \"thinking\")) {\n\t\tchanged(\"thinking\");\n\t\tif (cfg.thinking === \"off\") fields.add(\"thinking\");\n\t}\n\tif (hasKey(cfg, \"systemPromptMode\")) {\n\t\tchanged(\"systemPromptMode\");\n\t\tfields.add(\"systemPromptMode\");\n\t}\n\tif (hasKey(cfg, \"inheritProjectContext\")) {\n\t\tchanged(\"inheritProjectContext\");\n\t\tfields.add(\"inheritProjectContext\");\n\t}\n\tif (hasKey(cfg, \"inheritSkills\")) {\n\t\tchanged(\"inheritSkills\");\n\t\tfields.add(\"inheritSkills\");\n\t}\n\tif (hasKey(cfg, \"defaultContext\")) changed(\"defaultContext\");\n\tif (hasKey(cfg, \"async\")) changed(\"async\");\n\tif (hasKey(cfg, \"timeoutMs\")) changed(\"timeoutMs\");\n\tif (hasKey(cfg, \"turnBudget\")) changed(\"turnBudget\");\n\tif (hasKey(cfg, \"acceptance\")) changed(\"acceptance\");\n\tif (hasKey(cfg, \"acceptanceRole\")) changed(\"acceptanceRole\");\n\tif (hasKey(cfg, \"output\")) changed(\"output\");\n\tif (hasKey(cfg, \"reads\")) changed(\"defaultReads\");\n\tif (hasKey(cfg, \"progress\")) changed(\"defaultProgress\");\n\tif (hasKey(cfg, \"maxSubagentDepth\")) changed(\"maxSubagentDepth\");\n\tif (hasKey(cfg, \"completionGuard\")) {\n\t\tchanged(\"completionGuard\");\n\t\tif (cfg.completionGuard === true) fields.add(\"completionGuard\");\n\t}\n\tif (hasKey(cfg, \"toolBudget\")) changed(\"toolBudget\");\n\n\treturn fields;\n}\n\nfunction parseStepList(raw: unknown): { steps?: ChainStepConfig[]; error?: string } {\n\tif (!Array.isArray(raw)) return { error: \"config.steps must be an array.\" };\n\tif (raw.length === 0) return { error: \"config.steps must include at least one step.\" };\n\tconst steps: ChainStepConfig[] = [];\n\tfor (let i = 0; i < raw.length; i++) {\n\t\tconst item = raw[i];\n\t\tif (!item || typeof item !== \"object\" || Array.isArray(item))\n\t\t\treturn { error: `config.steps[${i}] must be an object.` };\n\t\tconst s = item as Record<string, unknown>;\n\t\tif (typeof s.agent !== \"string\" || !s.agent.trim())\n\t\t\treturn { error: `config.steps[${i}].agent must be a non-empty string.` };\n\t\tconst step: ChainStepConfig = { agent: s.agent.trim(), task: typeof s.task === \"string\" ? s.task : \"\" };\n\t\tif (hasKey(s, \"phase\")) {\n\t\t\tif (typeof s.phase === \"string\") step.phase = s.phase;\n\t\t\telse return { error: `config.steps[${i}].phase must be a string.` };\n\t\t}\n\t\tif (hasKey(s, \"label\")) {\n\t\t\tif (typeof s.label === \"string\") step.label = s.label;\n\t\t\telse return { error: `config.steps[${i}].label must be a string.` };\n\t\t}\n\t\tif (hasKey(s, \"as\")) {\n\t\t\tif (typeof s.as === \"string\") step.as = s.as;\n\t\t\telse return { error: `config.steps[${i}].as must be a string.` };\n\t\t}\n\t\tif (hasKey(s, \"outputSchema\")) {\n\t\t\tif (typeof s.outputSchema === \"string\") step.outputSchema = s.outputSchema;\n\t\t\telse return { error: `config.steps[${i}].outputSchema must be a schema file path string for saved chains.` };\n\t\t}\n\t\tif (hasKey(s, \"output\")) {\n\t\t\tif (s.output === false) step.output = false;\n\t\t\telse if (typeof s.output === \"string\") step.output = s.output;\n\t\t\telse return { error: `config.steps[${i}].output must be a string or false.` };\n\t\t}\n\t\tif (hasKey(s, \"outputMode\")) {\n\t\t\tif (s.outputMode === \"inline\" || s.outputMode === \"file-only\") step.outputMode = s.outputMode;\n\t\t\telse return { error: `config.steps[${i}].outputMode must be 'inline' or 'file-only'.` };\n\t\t}\n\t\tif (hasKey(s, \"reads\")) {\n\t\t\tif (s.reads === false) step.reads = false;\n\t\t\telse if (Array.isArray(s.reads))\n\t\t\t\tstep.reads = s.reads\n\t\t\t\t\t.filter((v): v is string => typeof v === \"string\")\n\t\t\t\t\t.map((v) => v.trim())\n\t\t\t\t\t.filter(Boolean);\n\t\t\telse return { error: `config.steps[${i}].reads must be an array or false.` };\n\t\t}\n\t\tif (hasKey(s, \"model\")) {\n\t\t\tif (typeof s.model === \"string\") step.model = s.model;\n\t\t\telse return { error: `config.steps[${i}].model must be a string.` };\n\t\t}\n\t\tif (hasKey(s, \"skills\")) {\n\t\t\tif (s.skills === false) step.skills = false;\n\t\t\telse if (Array.isArray(s.skills))\n\t\t\t\tstep.skills = s.skills\n\t\t\t\t\t.filter((v): v is string => typeof v === \"string\")\n\t\t\t\t\t.map((v) => v.trim())\n\t\t\t\t\t.filter(Boolean);\n\t\t\telse return { error: `config.steps[${i}].skills must be an array or false.` };\n\t\t}\n\t\tif (hasKey(s, \"progress\")) {\n\t\t\tif (typeof s.progress === \"boolean\") step.progress = s.progress;\n\t\t\telse return { error: `config.steps[${i}].progress must be a boolean.` };\n\t\t}\n\t\tif (hasKey(s, \"toolBudget\")) {\n\t\t\tconst validation = validateToolBudgetConfig(s.toolBudget, `config.steps[${i}].toolBudget`);\n\t\t\tif (validation.error) return { error: validation.error };\n\t\t\tif (s.toolBudget !== undefined) step.toolBudget = s.toolBudget as ToolBudgetConfig;\n\t\t}\n\t\tsteps.push(step);\n\t}\n\treturn { steps };\n}\n\nfunction parseTools(raw: string): { tools?: string[]; mcpDirectTools?: string[] } {\n\tconst tools: string[] = [];\n\tconst mcpDirectTools: string[] = [];\n\tfor (const item of parseCsv(raw)) {\n\t\tif (item.startsWith(\"mcp:\")) {\n\t\t\tconst direct = item.slice(4).trim();\n\t\t\tif (direct) mcpDirectTools.push(direct);\n\t\t} else tools.push(item);\n\t}\n\treturn {\n\t\t...(tools.length ? { tools } : {}),\n\t\t...(mcpDirectTools.length ? { mcpDirectTools } : {}),\n\t};\n}\n\nfunction applyAgentConfig(target: AgentConfig, cfg: Record<string, unknown>): string | undefined {\n\tif (hasKey(cfg, \"aliases\")) {\n\t\tif (cfg.aliases === false || cfg.aliases === \"\") delete target.aliases;\n\t\telse if (typeof cfg.aliases === \"string\") {\n\t\t\tconst aliases = parseCsv(cfg.aliases).filter((alias) => alias !== target.name);\n\t\t\tif (aliases.length) target.aliases = aliases;\n\t\t\telse delete target.aliases;\n\t\t} else if (Array.isArray(cfg.aliases) && cfg.aliases.every((entry) => typeof entry === \"string\")) {\n\t\t\tconst aliases = [\n\t\t\t\t...new Set(\n\t\t\t\t\tcfg.aliases\n\t\t\t\t\t\t.map((entry) => entry.trim())\n\t\t\t\t\t\t.filter(Boolean)\n\t\t\t\t\t\t.filter((alias) => alias !== target.name),\n\t\t\t\t),\n\t\t\t];\n\t\t\tif (aliases.length) target.aliases = aliases;\n\t\t\telse delete target.aliases;\n\t\t} else return \"config.aliases must be a comma-separated string, string array, or false when provided.\";\n\t}\n\tif (hasKey(cfg, \"systemPrompt\")) {\n\t\tif (cfg.systemPrompt === false || cfg.systemPrompt === \"\") target.systemPrompt = \"\";\n\t\telse if (typeof cfg.systemPrompt === \"string\") target.systemPrompt = cfg.systemPrompt;\n\t\telse return \"config.systemPrompt must be a string or false when provided.\";\n\t}\n\tif (hasKey(cfg, \"runner\")) {\n\t\tif (cfg.runner === false || cfg.runner === \"\") delete target.runner;\n\t\telse if (cfg.runner && typeof cfg.runner === \"object\" && !Array.isArray(cfg.runner)) {\n\t\t\tconst runner = cfg.runner as Record<string, unknown>;\n\t\t\tif (runner.type === \"pi\" && Object.keys(runner).every((key) => key === \"type\")) target.runner = { type: \"pi\" };\n\t\t\telse if (\n\t\t\t\trunner.type === \"external-cli\" &&\n\t\t\t\ttypeof runner.command === \"string\" &&\n\t\t\t\trunner.command.trim() &&\n\t\t\t\t(runner.args === undefined ||\n\t\t\t\t\t(Array.isArray(runner.args) && runner.args.every((arg) => typeof arg === \"string\"))) &&\n\t\t\t\t(runner.promptDelivery === undefined || runner.promptDelivery === \"stdin\") &&\n\t\t\t\tObject.keys(runner).every((key) => [\"type\", \"command\", \"args\", \"promptDelivery\", \"retry\"].includes(key))\n\t\t\t) {\n\t\t\t\tlet retry: RunnerRetryConfig | undefined;\n\t\t\t\ttry {\n\t\t\t\t\tretry = parseRunnerRetryConfig(runner.retry, \"config.runner.retry\");\n\t\t\t\t} catch (error) {\n\t\t\t\t\treturn error instanceof Error ? error.message : String(error);\n\t\t\t\t}\n\t\t\t\tconst runnerArgs = Array.isArray(runner.args)\n\t\t\t\t\t? runner.args.filter((arg): arg is string => typeof arg === \"string\")\n\t\t\t\t\t: undefined;\n\t\t\t\ttarget.runner = {\n\t\t\t\t\ttype: \"external-cli\",\n\t\t\t\t\tcommand: runner.command.trim(),\n\t\t\t\t\t...(runnerArgs?.length ? { args: runnerArgs } : {}),\n\t\t\t\t\t...(runner.promptDelivery ? { promptDelivery: \"stdin\" } : {}),\n\t\t\t\t\t...(retry ? { retry } : {}),\n\t\t\t\t};\n\t\t\t} else\n\t\t\t\treturn \"config.runner must be { type: 'pi' } or { type: 'external-cli', command: string, args?: string[], promptDelivery?: 'stdin', retry?: { maxAttempts: number, backoffMs?: number } }.\";\n\t\t} else return \"config.runner must be an object, false, or empty string when provided.\";\n\t}\n\tif (hasKey(cfg, \"model\")) {\n\t\tif (cfg.model === false || cfg.model === \"\") delete target.model;\n\t\telse if (typeof cfg.model === \"string\") {\n\t\t\tconst model = cfg.model.trim();\n\t\t\tif (model) target.model = model;\n\t\t\telse delete target.model;\n\t\t} else return \"config.model must be a string or false when provided.\";\n\t}\n\tif (hasKey(cfg, \"fallbackModels\")) {\n\t\tif (cfg.fallbackModels === false || cfg.fallbackModels === \"\") delete target.fallbackModels;\n\t\telse if (typeof cfg.fallbackModels === \"string\") {\n\t\t\tconst models = parseCsv(cfg.fallbackModels);\n\t\t\tif (models.length) target.fallbackModels = models;\n\t\t\telse delete target.fallbackModels;\n\t\t} else if (Array.isArray(cfg.fallbackModels)) {\n\t\t\tconst models = cfg.fallbackModels\n\t\t\t\t.filter((value): value is string => typeof value === \"string\")\n\t\t\t\t.map((value) => value.trim())\n\t\t\t\t.filter(Boolean);\n\t\t\tif (models.length) target.fallbackModels = [...new Set(models)];\n\t\t\telse delete target.fallbackModels;\n\t\t} else return \"config.fallbackModels must be a comma-separated string, string array, or false when provided.\";\n\t}\n\tif (hasKey(cfg, \"tools\")) {\n\t\tif (cfg.tools === false || cfg.tools === \"\") {\n\t\t\tdelete target.tools;\n\t\t\tdelete target.mcpDirectTools;\n\t\t} else if (typeof cfg.tools === \"string\") {\n\t\t\tconst parsed = parseTools(cfg.tools);\n\t\t\tif (parsed.tools) target.tools = parsed.tools;\n\t\t\telse delete target.tools;\n\t\t\tif (parsed.mcpDirectTools) target.mcpDirectTools = parsed.mcpDirectTools;\n\t\t\telse delete target.mcpDirectTools;\n\t\t} else return \"config.tools must be a comma-separated string or false when provided.\";\n\t}\n\tif (hasKey(cfg, \"skills\")) {\n\t\tif (cfg.skills === false || cfg.skills === \"\") delete target.skills;\n\t\telse if (typeof cfg.skills === \"string\") {\n\t\t\tconst skills = parseCsv(cfg.skills);\n\t\t\tif (skills.length) target.skills = skills;\n\t\t\telse delete target.skills;\n\t\t} else return \"config.skills must be a comma-separated string or false when provided.\";\n\t}\n\tif (hasKey(cfg, \"skillPath\")) {\n\t\tif (cfg.skillPath === false || cfg.skillPath === \"\") delete target.skillPath;\n\t\telse if (typeof cfg.skillPath === \"string\") {\n\t\t\tconst skillPath = parseCsv(cfg.skillPath);\n\t\t\tif (skillPath.length) target.skillPath = skillPath;\n\t\t\telse delete target.skillPath;\n\t\t} else if (Array.isArray(cfg.skillPath) && cfg.skillPath.every((entry) => typeof entry === \"string\")) {\n\t\t\tconst skillPath = [...new Set(cfg.skillPath.map((entry) => entry.trim()).filter(Boolean))];\n\t\t\tif (skillPath.length) target.skillPath = skillPath;\n\t\t\telse delete target.skillPath;\n\t\t} else return \"config.skillPath must be a comma-separated string, string array, or false when provided.\";\n\t}\n\tif (hasKey(cfg, \"extensions\")) {\n\t\tif (cfg.extensions === false) delete target.extensions;\n\t\telse if (cfg.extensions === \"\") target.extensions = [];\n\t\telse if (typeof cfg.extensions === \"string\") target.extensions = parseCsv(cfg.extensions);\n\t\telse return \"config.extensions must be a comma-separated string, empty string, or false when provided.\";\n\t}\n\tif (hasKey(cfg, \"subagentOnlyExtensions\")) {\n\t\tif (cfg.subagentOnlyExtensions === false) delete target.subagentOnlyExtensions;\n\t\telse if (cfg.subagentOnlyExtensions === \"\") target.subagentOnlyExtensions = [];\n\t\telse if (typeof cfg.subagentOnlyExtensions === \"string\")\n\t\t\ttarget.subagentOnlyExtensions = parseCsv(cfg.subagentOnlyExtensions);\n\t\telse\n\t\t\treturn \"config.subagentOnlyExtensions must be a comma-separated string, empty string, or false when provided.\";\n\t}\n\tif (hasKey(cfg, \"thinking\")) {\n\t\tif (cfg.thinking === false || cfg.thinking === \"\") delete target.thinking;\n\t\telse if (typeof cfg.thinking === \"string\") {\n\t\t\tconst thinking = cfg.thinking.trim();\n\t\t\tif (thinking) target.thinking = thinking;\n\t\t\telse delete target.thinking;\n\t\t} else return \"config.thinking must be a string or false when provided.\";\n\t}\n\tif (hasKey(cfg, \"systemPromptMode\")) {\n\t\tif (cfg.systemPromptMode === \"append\" || cfg.systemPromptMode === \"replace\")\n\t\t\ttarget.systemPromptMode = cfg.systemPromptMode;\n\t\telse return \"config.systemPromptMode must be 'append' or 'replace' when provided.\";\n\t}\n\tif (hasKey(cfg, \"inheritProjectContext\")) {\n\t\tif (typeof cfg.inheritProjectContext !== \"boolean\")\n\t\t\treturn \"config.inheritProjectContext must be a boolean when provided.\";\n\t\ttarget.inheritProjectContext = cfg.inheritProjectContext;\n\t}\n\tif (hasKey(cfg, \"inheritSkills\")) {\n\t\tif (typeof cfg.inheritSkills !== \"boolean\") return \"config.inheritSkills must be a boolean when provided.\";\n\t\ttarget.inheritSkills = cfg.inheritSkills;\n\t}\n\tif (hasKey(cfg, \"defaultContext\")) {\n\t\tif (cfg.defaultContext === false || cfg.defaultContext === \"\") delete target.defaultContext;\n\t\telse if (cfg.defaultContext === \"fresh\" || cfg.defaultContext === \"fork\")\n\t\t\ttarget.defaultContext = cfg.defaultContext;\n\t\telse return \"config.defaultContext must be 'fresh', 'fork', or false when provided.\";\n\t}\n\tif (hasKey(cfg, \"async\")) {\n\t\tif (cfg.async === \"\") delete target.defaultAsync;\n\t\telse if (typeof cfg.async === \"boolean\") target.defaultAsync = cfg.async;\n\t\telse return \"config.async must be a boolean or empty string when provided.\";\n\t}\n\tif (hasKey(cfg, \"timeoutMs\")) {\n\t\tif (cfg.timeoutMs === false || cfg.timeoutMs === \"\") delete target.defaultTimeoutMs;\n\t\telse if (typeof cfg.timeoutMs === \"number\" && Number.isInteger(cfg.timeoutMs) && cfg.timeoutMs > 0)\n\t\t\ttarget.defaultTimeoutMs = cfg.timeoutMs;\n\t\telse return \"config.timeoutMs must be a positive integer or false when provided.\";\n\t}\n\tif (hasKey(cfg, \"turnBudget\")) {\n\t\tif (cfg.turnBudget === false || cfg.turnBudget === \"\") delete target.defaultTurnBudget;\n\t\telse {\n\t\t\tconst resolved = resolveTurnBudgetConfig(cfg.turnBudget, \"config.turnBudget\");\n\t\t\tif (resolved.error) return resolved.error;\n\t\t\tif (resolved.turnBudget !== undefined) target.defaultTurnBudget = resolved.turnBudget;\n\t\t\telse delete target.defaultTurnBudget;\n\t\t}\n\t}\n\tif (hasKey(cfg, \"acceptance\")) {\n\t\tif (cfg.acceptance === \"\") delete target.defaultAcceptance;\n\t\telse {\n\t\t\tconst errors = validateAcceptanceInput(cfg.acceptance, \"config.acceptance\");\n\t\t\tif (errors.length > 0) return errors.join(\" \");\n\t\t\ttarget.defaultAcceptance = cfg.acceptance as AcceptanceInput;\n\t\t}\n\t}\n\tif (hasKey(cfg, \"acceptanceRole\")) {\n\t\tif (cfg.acceptanceRole === false || cfg.acceptanceRole === \"\") delete target.acceptanceRole;\n\t\telse if (cfg.acceptanceRole === \"read-only\" || cfg.acceptanceRole === \"writer\")\n\t\t\ttarget.acceptanceRole = cfg.acceptanceRole;\n\t\telse return \"config.acceptanceRole must be 'read-only', 'writer', or false when provided.\";\n\t}\n\tif (hasKey(cfg, \"output\")) {\n\t\tif (cfg.output === false || cfg.output === \"\") delete target.output;\n\t\telse if (typeof cfg.output === \"string\") target.output = cfg.output;\n\t\telse return \"config.output must be a string or false when provided.\";\n\t}\n\tif (hasKey(cfg, \"reads\")) {\n\t\tif (cfg.reads === false || cfg.reads === \"\") delete target.defaultReads;\n\t\telse if (typeof cfg.reads === \"string\") {\n\t\t\tconst reads = parseCsv(cfg.reads);\n\t\t\tif (reads.length) target.defaultReads = reads;\n\t\t\telse delete target.defaultReads;\n\t\t} else return \"config.reads must be a comma-separated string or false when provided.\";\n\t}\n\tif (hasKey(cfg, \"progress\")) {\n\t\tif (typeof cfg.progress !== \"boolean\") return \"config.progress must be a boolean when provided.\";\n\t\ttarget.defaultProgress = cfg.progress;\n\t}\n\tif (hasKey(cfg, \"maxSubagentDepth\")) {\n\t\tif (cfg.maxSubagentDepth === false || cfg.maxSubagentDepth === \"\") delete target.maxSubagentDepth;\n\t\telse if (\n\t\t\ttypeof cfg.maxSubagentDepth === \"number\" &&\n\t\t\tNumber.isInteger(cfg.maxSubagentDepth) &&\n\t\t\tcfg.maxSubagentDepth >= 0\n\t\t) {\n\t\t\ttarget.maxSubagentDepth = cfg.maxSubagentDepth;\n\t\t} else return \"config.maxSubagentDepth must be an integer >= 0 or false when provided.\";\n\t}\n\tif (hasKey(cfg, \"completionGuard\")) {\n\t\tif (typeof cfg.completionGuard !== \"boolean\") return \"config.completionGuard must be a boolean when provided.\";\n\t\ttarget.completionGuard = cfg.completionGuard;\n\t}\n\tif (hasKey(cfg, \"toolBudget\")) {\n\t\tif (cfg.toolBudget === false || cfg.toolBudget === \"\") delete target.toolBudget;\n\t\telse {\n\t\t\tconst validation = validateToolBudgetConfig(cfg.toolBudget, \"config.toolBudget\");\n\t\t\tif (validation.error) return validation.error;\n\t\t\ttarget.toolBudget = cfg.toolBudget as ToolBudgetConfig;\n\t\t}\n\t}\n\tif (target.runner?.type === \"external-cli\") {\n\t\tconst unsupported = [\n\t\t\ttarget.tools?.length || target.mcpDirectTools?.length ? \"tools\" : undefined,\n\t\t\ttarget.model ? \"model\" : undefined,\n\t\t\ttarget.fallbackModels?.length ? \"fallbackModels\" : undefined,\n\t\t\ttarget.thinking ? \"thinking\" : undefined,\n\t\t\ttarget.extensions?.length ? \"extensions\" : undefined,\n\t\t\ttarget.subagentOnlyExtensions?.length ? \"subagentOnlyExtensions\" : undefined,\n\t\t\ttarget.skills?.length || target.skillPath?.length ? \"skills\" : undefined,\n\t\t\ttarget.maxSubagentDepth !== undefined ? \"maxSubagentDepth\" : undefined,\n\t\t\ttarget.completionGuard !== undefined ? \"completionGuard\" : undefined,\n\t\t\ttarget.toolBudget ? \"toolBudget\" : undefined,\n\t\t].filter((field): field is string => Boolean(field));\n\t\tif (unsupported.length > 0)\n\t\t\treturn `config.runner type 'external-cli' does not support Pi-only fields: ${unsupported.join(\", \")}.`;\n\t}\n\treturn undefined;\n}\n\nfunction resolveTarget<T extends { name: string; source: AgentSource; filePath: string }>(\n\tkind: \"agent\" | \"chain\",\n\tname: string,\n\tmatches: T[],\n\tcwd: string,\n\tscopeHint?: string,\n): T | AgentToolResult<Details> {\n\tconst distinctNames = [...new Set(matches.map((m) => m.name))];\n\tif (distinctNames.length > 1) {\n\t\treturn result(\n\t\t\t`Ambiguous ${kind} alias or name '${name}': ${distinctNames.sort((a, b) => a.localeCompare(b)).join(\", \")}`,\n\t\t\ttrue,\n\t\t);\n\t}\n\tconst mutable = matches.filter((m): m is T & { source: ManagementScope } => isMutableSource(m.source));\n\tif (mutable.length === 0) {\n\t\tif (matches.length > 0) {\n\t\t\treturn result(\n\t\t\t\t`${kind === \"agent\" ? \"Agent\" : \"Chain\"} '${name}' is read-only and cannot be modified. Create a same-named ${kind} in user or project scope to override it.`,\n\t\t\t\ttrue,\n\t\t\t);\n\t\t}\n\t\tconst available = availableNames(cwd, kind);\n\t\treturn result(\n\t\t\t`${kind === \"agent\" ? \"Agent\" : \"Chain\"} '${name}' not found. Available: ${available.join(\", \") || \"none\"}.`,\n\t\t\ttrue,\n\t\t);\n\t}\n\tif (mutable.length === 1) return mutable[0]!;\n\tconst scope = asDisambiguationScope(scopeHint);\n\tif (!scope) {\n\t\tconst paths = mutable.map((m) => `${m.source}: ${m.filePath}`).join(\"\\n\");\n\t\treturn result(\n\t\t\t`${kind === \"agent\" ? \"Agent\" : \"Chain\"} '${name}' exists in both scopes. Specify agentScope: 'user' or 'project'.\\n${paths}`,\n\t\t\ttrue,\n\t\t);\n\t}\n\tconst scoped = mutable.filter((m) => m.source === scope);\n\tif (scoped.length === 0)\n\t\treturn result(`${kind === \"agent\" ? \"Agent\" : \"Chain\"} '${name}' not found in scope '${scope}'.`, true);\n\tif (scoped.length > 1)\n\t\treturn result(\n\t\t\t`Multiple ${kind}s named '${name}' found in scope '${scope}': ${scoped.map((m) => m.filePath).join(\", \")}`,\n\t\t\ttrue,\n\t\t);\n\treturn scoped[0]!;\n}\n\nfunction renamePath(\n\tkind: \"agent\" | \"chain\",\n\tcurrentPath: string,\n\tnewName: string,\n\tscope: ManagementScope,\n\tcwd: string,\n): { filePath?: string; error?: string } {\n\tif (nameExistsInScope(cwd, scope, newName, currentPath))\n\t\treturn { error: `Name '${newName}' already exists in ${scope} scope.` };\n\tconst ext = kind === \"agent\" ? \".md\" : currentPath.endsWith(\".chain.json\") ? \".chain.json\" : \".chain.md\";\n\tconst filePath = path.join(path.dirname(currentPath), `${newName}${ext}`);\n\tif (fs.existsSync(filePath) && filePath !== currentPath) {\n\t\treturn {\n\t\t\terror: `File already exists at ${filePath} but is not a valid ${kind} definition. Remove or rename it first.`,\n\t\t};\n\t}\n\tfs.renameSync(currentPath, filePath);\n\treturn { filePath };\n}\n\nfunction formatAgentDetail(agent: AgentConfig): string {\n\tconst tools = [...(agent.tools ?? []), ...(agent.mcpDirectTools ?? []).map((t) => `mcp:${t}`)];\n\tconst lines: string[] = [\n\t\t`Agent: ${agent.name} (${agent.source})`,\n\t\t`Path: ${agent.filePath}`,\n\t\t`Description: ${agent.description}`,\n\t];\n\tif (agent.packageName) {\n\t\tlines.push(`Local name: ${frontmatterNameForConfig(agent)}`);\n\t\tlines.push(`Package: ${agent.packageName}`);\n\t}\n\tif (agent.aliases?.length) lines.push(`Aliases: ${agent.aliases.join(\", \")}`);\n\tif (agent.model) lines.push(`Model: ${agent.model}`);\n\tif (agent.fallbackModels?.length) lines.push(`Fallback models: ${agent.fallbackModels.join(\", \")}`);\n\tif (tools.length) lines.push(`Tools: ${tools.join(\", \")}`);\n\tif (agent.skills?.length) lines.push(`Skills: ${agent.skills.join(\", \")}`);\n\tif (agent.skillPath?.length) lines.push(`Skill paths: ${agent.skillPath.join(\", \")}`);\n\tlines.push(`System prompt mode: ${agent.systemPromptMode}`);\n\tif (agent.runner) lines.push(`Runner: ${JSON.stringify(agent.runner)}`);\n\tlines.push(`Inherit project context: ${agent.inheritProjectContext ? \"true\" : \"false\"}`);\n\tlines.push(`Inherit skills: ${agent.inheritSkills ? \"true\" : \"false\"}`);\n\tif (agent.defaultContext) lines.push(`Default context: ${agent.defaultContext}`);\n\tif (agent.defaultAsync !== undefined) lines.push(`Async: ${agent.defaultAsync ? \"true\" : \"false\"}`);\n\tif (agent.defaultTimeoutMs !== undefined) lines.push(`Timeout: ${agent.defaultTimeoutMs}ms`);\n\tif (agent.defaultTurnBudget) lines.push(`Turn budget: ${JSON.stringify(agent.defaultTurnBudget)}`);\n\tif (agent.defaultAcceptance !== undefined)\n\t\tlines.push(\n\t\t\t`Acceptance: ${typeof agent.defaultAcceptance === \"object\" ? JSON.stringify(agent.defaultAcceptance) : String(agent.defaultAcceptance)}`,\n\t\t);\n\tif (agent.acceptanceRole) lines.push(`Acceptance role: ${agent.acceptanceRole}`);\n\tif (agent.source === \"builtin\") lines.push(`Disabled: ${agent.disabled ? \"true\" : \"false\"}`);\n\tif (agent.extensions !== undefined)\n\t\tlines.push(`Extensions: ${agent.extensions.length ? agent.extensions.join(\", \") : \"(none)\"}`);\n\tif (agent.subagentOnlyExtensions !== undefined)\n\t\tlines.push(\n\t\t\t`Subagent-only extensions: ${agent.subagentOnlyExtensions.length ? agent.subagentOnlyExtensions.join(\", \") : \"(none)\"}`,\n\t\t);\n\tif (agent.thinking) lines.push(`Thinking: ${agent.thinking}`);\n\tif (agent.output) lines.push(`Output: ${agent.output}`);\n\tif (agent.defaultReads?.length) lines.push(`Reads: ${agent.defaultReads.join(\", \")}`);\n\tif (agent.defaultProgress) lines.push(\"Progress: true\");\n\tif (agent.maxSubagentDepth !== undefined) lines.push(`Max subagent depth: ${agent.maxSubagentDepth}`);\n\tif (agent.completionGuard === false) lines.push(\"Completion guard: false\");\n\tif (agent.toolBudget) lines.push(`Tool budget: ${JSON.stringify(agent.toolBudget)}`);\n\tif (agent.memory) lines.push(`Memory: ${agent.memory.scope} scope, path: ${agent.memory.path}`);\n\tif (agent.systemPrompt.trim()) lines.push(\"\", \"System Prompt:\", agent.systemPrompt);\n\treturn lines.join(\"\\n\");\n}\n\nfunction formatChainStepDetail(step: ChainStepConfig, index: number): string[] {\n\tconst lines: string[] = [];\n\tif (step.expand || step.collect) {\n\t\tconst parallel =\n\t\t\tstep.parallel && !Array.isArray(step.parallel) && typeof step.parallel === \"object\"\n\t\t\t\t? (step.parallel as { agent?: unknown; task?: unknown; label?: unknown; outputSchema?: unknown })\n\t\t\t\t: undefined;\n\t\tconst expand =\n\t\t\tstep.expand && typeof step.expand === \"object\"\n\t\t\t\t? (step.expand as {\n\t\t\t\t\t\tfrom?: { output?: unknown; path?: unknown };\n\t\t\t\t\t\titem?: unknown;\n\t\t\t\t\t\tkey?: unknown;\n\t\t\t\t\t\tmaxItems?: unknown;\n\t\t\t\t\t\tonEmpty?: unknown;\n\t\t\t\t\t})\n\t\t\t\t: undefined;\n\t\tconst collect =\n\t\t\tstep.collect && typeof step.collect === \"object\"\n\t\t\t\t? (step.collect as { as?: unknown; outputSchema?: unknown })\n\t\t\t\t: undefined;\n\t\tlines.push(`${index + 1}. Dynamic fanout${typeof collect?.as === \"string\" ? ` -> ${collect.as}` : \"\"}`);\n\t\tif (expand?.from) lines.push(`   Expand: ${String(expand.from.output ?? \"?\")}${String(expand.from.path ?? \"\")}`);\n\t\tif (typeof expand?.item === \"string\") lines.push(`   Item variable: ${expand.item}`);\n\t\tif (typeof expand?.key === \"string\") lines.push(`   Key: ${expand.key}`);\n\t\tif (typeof expand?.maxItems === \"number\") lines.push(`   Max items: ${expand.maxItems}`);\n\t\tif (typeof expand?.onEmpty === \"string\") lines.push(`   On empty: ${expand.onEmpty}`);\n\t\tif (parallel?.agent) lines.push(`   Agent: ${String(parallel.agent)}`);\n\t\tif (typeof parallel?.label === \"string\") lines.push(`   Label: ${parallel.label}`);\n\t\tif (typeof parallel?.task === \"string\" && parallel.task.trim()) lines.push(`   Task: ${parallel.task}`);\n\t\tif (parallel?.outputSchema) lines.push(\"   Structured output: true\");\n\t\tif (parallel && \"toolBudget\" in parallel)\n\t\t\tlines.push(`   Tool budget: ${JSON.stringify((parallel as { toolBudget?: unknown }).toolBudget)}`);\n\t\tif (collect?.outputSchema) lines.push(\"   Collect schema: true\");\n\t\tif (step.concurrency !== undefined) lines.push(`   Concurrency: ${step.concurrency}`);\n\t\tif (step.failFast !== undefined) lines.push(`   Fail fast: ${step.failFast ? \"true\" : \"false\"}`);\n\t\treturn lines;\n\t}\n\tlines.push(`${index + 1}. ${step.agent}`);\n\tif (step.task?.trim()) lines.push(`   Task: ${step.task}`);\n\tif (step.output === false) lines.push(\"   Output: false\");\n\telse if (step.output) lines.push(`   Output: ${step.output}`);\n\tif (step.outputMode) lines.push(`   Output mode: ${step.outputMode}`);\n\tif (step.toolBudget) lines.push(`   Tool budget: ${JSON.stringify(step.toolBudget)}`);\n\tif (step.reads === false) lines.push(\"   Reads: false\");\n\telse if (Array.isArray(step.reads) && step.reads.length > 0) lines.push(`   Reads: ${step.reads.join(\", \")}`);\n\tif (step.model) lines.push(`   Model: ${step.model}`);\n\tif (step.skills === false) lines.push(\"   Skills: false\");\n\telse if (Array.isArray(step.skills) && step.skills.length > 0) lines.push(`   Skills: ${step.skills.join(\", \")}`);\n\tif (step.progress !== undefined) lines.push(`   Progress: ${step.progress ? \"true\" : \"false\"}`);\n\treturn lines;\n}\n\nfunction formatChainDetail(chain: ChainConfig): string {\n\tconst lines: string[] = [\n\t\t`Chain: ${chain.name} (${chain.source})`,\n\t\t`Path: ${chain.filePath}`,\n\t\t`Description: ${chain.description}`,\n\t];\n\tif (chain.packageName) {\n\t\tlines.push(`Local name: ${frontmatterNameForConfig(chain)}`);\n\t\tlines.push(`Package: ${chain.packageName}`);\n\t}\n\tlines.push(\"\", \"Steps:\");\n\tfor (let i = 0; i < chain.steps.length; i++) {\n\t\tlines.push(...formatChainStepDetail(chain.steps[i]!, i));\n\t}\n\treturn lines.join(\"\\n\");\n}\n\nexport function handleList(params: ManagementParams, ctx: ManagementContext): AgentToolResult<Details> {\n\tconst scope = normalizeListScope(params.agentScope) ?? \"both\";\n\tconst d = discoverAgentsAll(ctx.cwd);\n\tconst scopedAgents = mergeAgentsForScope(scope, d.user, d.project, d.builtin, d.package).sort((a, b) =>\n\t\ta.name.localeCompare(b.name),\n\t);\n\tconst capabilityCeiling = resolveCurrentSubagentCapabilityCeiling(ctx.currentSessionId);\n\tconst visibleAgents = scopedAgents.filter((a) => !a.disabled);\n\tconst agents = visibleAgents.filter((a) => isAgentAllowedByCapabilityCeiling(a.name, capabilityCeiling));\n\tconst restrictedAgents = visibleAgents.filter((a) => !isAgentAllowedByCapabilityCeiling(a.name, capabilityCeiling));\n\tconst restrictedSources = capabilityCeilingAgentRestrictionSources(capabilityCeiling);\n\tconst chains = d.chains\n\t\t.filter((c) => scope === \"both\" || c.source === \"package\" || c.source === scope)\n\t\t.sort((a, b) => a.name.localeCompare(b.name));\n\tconst diagnostics = d.chainDiagnostics.filter((entry) => scope === \"both\" || entry.source === scope);\n\tconst proactiveSuggestions = buildProactiveSkillSubagentRecommendationLines({\n\t\tagents,\n\t\tchains,\n\t\t...(ctx.config?.proactiveSkillSubagents !== undefined ? { config: ctx.config.proactiveSkillSubagents } : {}),\n\t\tdiscoverAvailableSkills: () => discoverAvailableSkills(ctx.cwd),\n\t});\n\tconst lines = [\n\t\t\"Executable agents:\",\n\t\t...(agents.length\n\t\t\t? agents.map(\n\t\t\t\t\t(a) =>\n\t\t\t\t\t\t`- ${a.name} (${a.source}${a.defaultContext ? `, context: ${a.defaultContext}` : \"\"}${a.aliases?.length ? `, aliases: ${a.aliases.join(\", \")}` : \"\"}): ${a.description}`,\n\t\t\t\t)\n\t\t\t: [\"- (none)\"]),\n\t\t...(restrictedAgents.length\n\t\t\t? [\n\t\t\t\t\t\"\",\n\t\t\t\t\t`Restricted agents (not executable in this session${restrictedSources?.length ? `; capability ceiling: ${restrictedSources.join(\", \")}` : \"\"}):`,\n\t\t\t\t\t...restrictedAgents.map(\n\t\t\t\t\t\t(a) =>\n\t\t\t\t\t\t\t`- ${a.name} (${a.source}${a.aliases?.length ? `, aliases: ${a.aliases.join(\", \")}` : \"\"}): ${a.description}`,\n\t\t\t\t\t),\n\t\t\t\t]\n\t\t\t: []),\n\t\t\"\",\n\t\t\"Chains:\",\n\t\t...(chains.length ? chains.map((c) => `- ${c.name} (${c.source}): ${c.description}`) : [\"- (none)\"]),\n\t\t...(proactiveSuggestions.length ? [\"\", ...proactiveSuggestions] : []),\n\t\t...(diagnostics.length\n\t\t\t? [\"\", \"Chain diagnostics:\", ...diagnostics.map((entry) => `- ${entry.filePath}: ${entry.error}`)]\n\t\t\t: []),\n\t];\n\treturn result(lines.join(\"\\n\"));\n}\n\nfunction formatModelSource(agent: AgentConfig, currentModel: ParentModel | undefined): string {\n\tif (agent.override && agent.model !== agent.override.base.model) {\n\t\treturn `${agent.override.scope} override`;\n\t}\n\tif (agent.modelSource?.type === \"subagents.defaultModel\" && agent.model === agent.modelSource.model) {\n\t\treturn `${agent.modelSource.scope} defaultModel`;\n\t}\n\tif (agent.model) return \"builtin agent config\";\n\tif (currentModel) return \"inherits current session model\";\n\treturn \"inherit requested, but no current session model is available\";\n}\n\nfunction handleModels(params: ManagementParams, ctx: ManagementContext): AgentToolResult<Details> {\n\tconst requestedAgent = params.agent?.trim();\n\tif (requestedAgent && !(BUILTIN_AGENT_NAMES as readonly string[]).includes(requestedAgent)) {\n\t\treturn result(`Builtin agent '${requestedAgent}' not found. Available: ${BUILTIN_AGENT_NAMES.join(\", \")}.`, true);\n\t}\n\n\tconst discovered = discoverAgentsAll(ctx.cwd);\n\tconst builtinByName = new Map(discovered.builtin.map((agent) => [agent.name, agent]));\n\tconst availableModels = ctx.modelRegistry.getAvailable().map(toModelInfo);\n\tconst currentModel = ctx.model ? { provider: ctx.model.provider, id: ctx.model.id } : undefined;\n\tconst preferredProvider = ctx.model?.provider;\n\tconst names = requestedAgent ? [requestedAgent] : [...BUILTIN_AGENT_NAMES];\n\n\tif (requestedAgent) {\n\t\tconst agent = builtinByName.get(requestedAgent);\n\t\tif (!agent) return result(`Builtin agent '${requestedAgent}' not found.`, true);\n\t\tconst resolvedModel = resolveSubagentModelOverride(agent.model, currentModel, availableModels, preferredProvider);\n\t\tconst lines = [\n\t\t\t\"Builtin subagent model\",\n\t\t\t\"\",\n\t\t\t`Agent: ${requestedAgent}`,\n\t\t\t\"Effective model:\",\n\t\t\t`  ${resolvedModel ?? \"(unresolved)\"}`,\n\t\t\t`Source: ${formatModelSource(agent, currentModel)}`,\n\t\t];\n\t\tif (agent.override) {\n\t\t\tlines.push(\"Override file:\");\n\t\t\tlines.push(`  ${agent.override.path}`);\n\t\t}\n\t\tif (agent.model && resolvedModel && agent.model !== resolvedModel) {\n\t\t\tlines.push(\"Requested model setting:\");\n\t\t\tlines.push(`  ${agent.model}`);\n\t\t}\n\t\tif (agent.disabled) lines.push(\"Disabled: true\");\n\t\tlines.push(\"Current session model:\");\n\t\tlines.push(`  ${currentModel ? `${currentModel.provider}/${currentModel.id}` : \"(unavailable)\"}`);\n\t\treturn result(lines.join(\"\\n\"));\n\t}\n\n\tconst lines = [\n\t\t\"Builtin subagent models\",\n\t\t\"\",\n\t\t\"Current session model:\",\n\t\t`  ${currentModel ? `${currentModel.provider}/${currentModel.id}` : \"(unavailable)\"}`,\n\t\t\"\",\n\t];\n\n\tfor (const name of names) {\n\t\tconst agent = builtinByName.get(name);\n\t\tif (!agent) {\n\t\t\tlines.push(name);\n\t\t\tlines.push(\"  model:\");\n\t\t\tlines.push(\"    (builtin definition not found)\");\n\t\t\tlines.push(\"  source: missing\");\n\t\t\tlines.push(\"\");\n\t\t\tcontinue;\n\t\t}\n\t\tconst resolvedModel = resolveSubagentModelOverride(agent.model, currentModel, availableModels, preferredProvider);\n\t\tconst source = `${formatModelSource(agent, currentModel)}${agent.disabled ? \"; disabled\" : \"\"}`;\n\t\tlines.push(name);\n\t\tlines.push(\"  model:\");\n\t\tlines.push(`    ${resolvedModel ?? \"(unresolved)\"}`);\n\t\tlines.push(`  source: ${source}`);\n\t\tlines.push(\"\");\n\t}\n\n\treturn result(lines.join(\"\\n\"));\n}\n\nfunction handleGet(params: ManagementParams, ctx: ManagementContext): AgentToolResult<Details> {\n\tif (!params.agent && !params.chainName) return result(\"Specify 'agent' or 'chainName' for get.\", true);\n\tconst scope = normalizeListScope(params.agentScope);\n\tif (!scope) return result(\"agentScope must be 'user', 'project', or 'both' for get.\", true);\n\tconst hasBoth = Boolean(params.agent && params.chainName);\n\tconst blocks: string[] = [];\n\tlet anyFound = false;\n\tif (params.agent) {\n\t\tconst matches = findAgents(params.agent, ctx.cwd, scope);\n\t\tconst distinctNames = [...new Set(matches.map((agent) => agent.name))];\n\t\tif (distinctNames.length > 1) {\n\t\t\tconst msg = `Ambiguous agent alias or name '${params.agent}': ${distinctNames.sort((a, b) => a.localeCompare(b)).join(\", \")}`;\n\t\t\tif (!hasBoth) return result(msg, true);\n\t\t\tblocks.push(msg);\n\t\t} else if (!matches.length) {\n\t\t\tconst msg = `Agent '${params.agent}' not found. Available: ${availableNames(ctx.cwd, \"agent\").join(\", \") || \"none\"}.`;\n\t\t\tif (!hasBoth) return result(msg, true);\n\t\t\tblocks.push(msg);\n\t\t} else {\n\t\t\tanyFound = true;\n\t\t\tblocks.push(...matches.map(formatAgentDetail));\n\t\t}\n\t}\n\tif (params.chainName) {\n\t\tconst matches = findChains(params.chainName, ctx.cwd, scope);\n\t\tif (!matches.length) {\n\t\t\tconst msg = `Chain '${params.chainName}' not found. Available: ${availableNames(ctx.cwd, \"chain\").join(\", \") || \"none\"}.`;\n\t\t\tif (!hasBoth) return result(msg, true);\n\t\t\tblocks.push(msg);\n\t\t} else {\n\t\t\tanyFound = true;\n\t\t\tblocks.push(...matches.map(formatChainDetail));\n\t\t}\n\t}\n\treturn result(blocks.join(\"\\n\\n\"), !anyFound);\n}\n\nexport function handleCreate(params: ManagementParams, ctx: ManagementContext): AgentToolResult<Details> {\n\tconst parsedConfig = configObject(params.config);\n\tif (parsedConfig.error) return result(parsedConfig.error, true);\n\tconst cfg = parsedConfig.value;\n\tif (!cfg) return result(\"config required for create.\", true);\n\tif (typeof cfg.name !== \"string\" || !cfg.name.trim())\n\t\treturn result(\"config.name is required and must be a non-empty string.\", true);\n\tif (typeof cfg.description !== \"string\" || !cfg.description.trim())\n\t\treturn result(\"config.description is required and must be a non-empty string.\", true);\n\tconst name = sanitizeName(cfg.name);\n\tif (!name)\n\t\treturn result(\"config.name is invalid after sanitization. Use letters, numbers, spaces, or hyphens.\", true);\n\tconst parsedPackage = parsePackageConfig(cfg.package);\n\tif (parsedPackage.error) return result(parsedPackage.error, true);\n\tconst runtimeName = buildRuntimeName(name, parsedPackage.packageName);\n\tconst scopeRaw = cfg.scope ?? \"user\";\n\tif (scopeRaw !== \"user\" && scopeRaw !== \"project\") return result(\"config.scope must be 'user' or 'project'.\", true);\n\tconst scope = scopeRaw as ManagementScope;\n\tconst isChain = hasKey(cfg, \"steps\");\n\tconst d = discoverAgentsAll(ctx.cwd);\n\tconst projectConfigDir = getProjectConfigDir(ctx.cwd);\n\tconst targetDir = isChain\n\t\t? scope === \"user\"\n\t\t\t? d.userChainDir\n\t\t\t: (d.projectChainDir ?? path.join(projectConfigDir, \"chains\"))\n\t\t: scope === \"user\"\n\t\t\t? d.userDir\n\t\t\t: (d.projectDir ?? path.join(projectConfigDir, \"agents\"));\n\tfs.mkdirSync(targetDir, { recursive: true });\n\tif (nameExistsInScope(ctx.cwd, scope, runtimeName))\n\t\treturn result(`Name '${runtimeName}' already exists in ${scope} scope. Use update instead.`, true);\n\tconst targetPath = path.join(targetDir, isChain ? `${runtimeName}.chain.md` : `${runtimeName}.md`);\n\tif (fs.existsSync(targetPath))\n\t\treturn result(\n\t\t\t`File already exists at ${targetPath} but is not a valid ${isChain ? \"chain\" : \"agent\"} definition. Remove or rename it first.`,\n\t\t\ttrue,\n\t\t);\n\tconst warnings: string[] = [];\n\tif (!isChain && d.builtin.some((a) => a.name === runtimeName))\n\t\twarnings.push(`Note: this shadows the builtin agent '${runtimeName}'.`);\n\tif (isChain) {\n\t\tconst parsed = parseStepList(cfg.steps);\n\t\tif (parsed.error) return result(parsed.error, true);\n\t\tconst chain: ChainConfig = {\n\t\t\tname: runtimeName,\n\t\t\tlocalName: name,\n\t\t\t...(parsedPackage.packageName !== undefined ? { packageName: parsedPackage.packageName } : {}),\n\t\t\tdescription: cfg.description.trim(),\n\t\t\tsource: scope,\n\t\t\tfilePath: targetPath,\n\t\t\tsteps: parsed.steps!,\n\t\t};\n\t\tfs.writeFileSync(targetPath, serializeChain(chain), \"utf-8\");\n\t\tconst missing = unknownChainAgents(ctx.cwd, chain.steps);\n\t\tif (missing.length) warnings.push(`Warning: chain steps reference unknown agents: ${missing.join(\", \")}.`);\n\t\twarnings.push(...chainStepWarnings(ctx, chain.steps));\n\t\treturn result([`Created chain '${runtimeName}' at ${targetPath}.`, ...warnings].join(\"\\n\"));\n\t}\n\tconst agent: AgentConfig = {\n\t\tname: runtimeName,\n\t\tlocalName: name,\n\t\t...(parsedPackage.packageName !== undefined ? { packageName: parsedPackage.packageName } : {}),\n\t\tdescription: cfg.description.trim(),\n\t\tsource: scope,\n\t\tfilePath: targetPath,\n\t\tsystemPrompt: \"\",\n\t\tsystemPromptMode: defaultSystemPromptMode(name),\n\t\tinheritProjectContext: defaultInheritProjectContext(name),\n\t\tinheritSkills: defaultInheritSkills(),\n\t};\n\tconst applyError = applyAgentConfig(agent, cfg);\n\tif (applyError) return result(applyError, true);\n\tconst mw = modelWarning(ctx, agent.model);\n\tif (mw) warnings.push(mw);\n\tconst fmw = fallbackModelsWarning(ctx, agent.fallbackModels);\n\tif (fmw) warnings.push(fmw);\n\tconst sw = skillsWarning(ctx.cwd, agent);\n\tif (sw) warnings.push(sw);\n\tfs.writeFileSync(targetPath, serializeAgent(agent), \"utf-8\");\n\treturn result([`Created agent '${runtimeName}' at ${targetPath}.`, ...warnings].join(\"\\n\"));\n}\n\nexport function handleUpdate(params: ManagementParams, ctx: ManagementContext): AgentToolResult<Details> {\n\tif (!params.agent && !params.chainName) return result(\"Specify 'agent' or 'chainName' for update.\", true);\n\tif (params.agent && params.chainName) return result(\"Specify either 'agent' or 'chainName', not both.\", true);\n\tconst parsedConfig = configObject(params.config);\n\tif (parsedConfig.error) return result(parsedConfig.error, true);\n\tconst cfg = parsedConfig.value;\n\tif (!cfg) return result(\"config required for update.\", true);\n\tconst warnings: string[] = [];\n\tif (params.agent) {\n\t\tconst scopeHint = asDisambiguationScope(params.agentScope);\n\t\tconst targetOrError = resolveTarget(\n\t\t\t\"agent\",\n\t\t\tparams.agent,\n\t\t\tfindAgents(params.agent, ctx.cwd, scopeHint ?? \"both\"),\n\t\t\tctx.cwd,\n\t\t\tscopeHint,\n\t\t);\n\t\tif (\"content\" in targetOrError) return targetOrError;\n\t\tconst target = targetOrError;\n\t\tif (target.source !== \"user\" && target.source !== \"project\")\n\t\t\treturn result(\n\t\t\t\t`Cannot update ${target.source} agent '${target.name}'. Eject it to user or project scope first.`,\n\t\t\t\ttrue,\n\t\t\t);\n\t\tconst updated = editableAgentConfig(target);\n\t\tconst oldName = target.name;\n\t\tif (hasKey(cfg, \"name\") && (typeof cfg.name !== \"string\" || !cfg.name.trim()))\n\t\t\treturn result(\"config.name must be a non-empty string when provided.\", true);\n\t\tif (hasKey(cfg, \"description\") && (typeof cfg.description !== \"string\" || !cfg.description.trim()))\n\t\t\treturn result(\"config.description must be a non-empty string when provided.\", true);\n\t\tlet newLocalName = target.localName ?? frontmatterNameForConfig(target);\n\t\tif (hasKey(cfg, \"name\")) {\n\t\t\tnewLocalName = sanitizeName(cfg.name as string);\n\t\t\tif (!newLocalName) return result(\"config.name is invalid after sanitization.\", true);\n\t\t}\n\t\tlet newPackageName = target.packageName;\n\t\tif (hasKey(cfg, \"package\")) {\n\t\t\tconst parsedPackage = parsePackageConfig(cfg.package);\n\t\t\tif (parsedPackage.error) return result(parsedPackage.error, true);\n\t\t\tnewPackageName = parsedPackage.packageName;\n\t\t}\n\t\tconst applyError = applyAgentConfig(updated, cfg);\n\t\tif (applyError) return result(applyError, true);\n\t\tconst preserveFrontmatterFields = preservedAgentFrontmatterFields(target, cfg);\n\t\tupdated.localName = newLocalName;\n\t\tif (newPackageName !== undefined) updated.packageName = newPackageName;\n\t\telse delete updated.packageName;\n\t\tupdated.name = buildRuntimeName(newLocalName, newPackageName);\n\t\tif (hasKey(cfg, \"description\")) updated.description = (cfg.description as string).trim();\n\t\tif (hasKey(cfg, \"model\")) {\n\t\t\tconst mw = modelWarning(ctx, updated.model);\n\t\t\tif (mw) warnings.push(mw);\n\t\t}\n\t\tif (hasKey(cfg, \"fallbackModels\")) {\n\t\t\tconst fmw = fallbackModelsWarning(ctx, updated.fallbackModels);\n\t\t\tif (fmw) warnings.push(fmw);\n\t\t}\n\t\tif (hasKey(cfg, \"skills\") || hasKey(cfg, \"skillPath\")) {\n\t\t\tconst sw = skillsWarning(ctx.cwd, updated);\n\t\t\tif (sw) warnings.push(sw);\n\t\t}\n\t\tif (updated.name !== oldName) {\n\t\t\tconst renamed = renamePath(\"agent\", target.filePath, updated.name, target.source, ctx.cwd);\n\t\t\tif (renamed.error) return result(renamed.error, true);\n\t\t\tupdated.filePath = renamed.filePath!;\n\t\t}\n\t\tfs.writeFileSync(updated.filePath, serializeAgent(updated, { preserveFrontmatterFields }), \"utf-8\");\n\t\tif (updated.name !== oldName) {\n\t\t\tconst refs = discoverAgentsAll(ctx.cwd)\n\t\t\t\t.chains.filter((c) => c.steps.some((s) => s.agent === oldName))\n\t\t\t\t.map((c) => `${c.name} (${c.source})`);\n\t\t\tif (refs.length) warnings.push(`Warning: chains still reference '${oldName}': ${refs.join(\", \")}.`);\n\t\t}\n\t\tconst headline =\n\t\t\tupdated.name === oldName\n\t\t\t\t? `Updated agent '${updated.name}' at ${updated.filePath}.`\n\t\t\t\t: `Updated agent '${oldName}' to '${updated.name}' at ${updated.filePath}.`;\n\t\treturn result([headline, ...warnings].join(\"\\n\"));\n\t}\n\tconst scopeHint = asDisambiguationScope(params.agentScope);\n\tconst targetOrError = resolveTarget(\n\t\t\"chain\",\n\t\tparams.chainName!,\n\t\tfindChains(params.chainName!, ctx.cwd, scopeHint ?? \"both\"),\n\t\tctx.cwd,\n\t\tscopeHint,\n\t);\n\tif (\"content\" in targetOrError) return targetOrError;\n\tconst target = targetOrError;\n\tif (target.source !== \"user\" && target.source !== \"project\")\n\t\treturn result(\n\t\t\t`Cannot update ${target.source} chain '${target.name}'. Eject it to user or project scope first.`,\n\t\t\ttrue,\n\t\t);\n\tconst updated: ChainConfig = { ...target, steps: [...target.steps] };\n\tconst oldName = target.name;\n\tif (hasKey(cfg, \"name\") && (typeof cfg.name !== \"string\" || !cfg.name.trim()))\n\t\treturn result(\"config.name must be a non-empty string when provided.\", true);\n\tif (hasKey(cfg, \"description\") && (typeof cfg.description !== \"string\" || !cfg.description.trim()))\n\t\treturn result(\"config.description must be a non-empty string when provided.\", true);\n\tlet newLocalName = target.localName ?? frontmatterNameForConfig(target);\n\tif (hasKey(cfg, \"name\")) {\n\t\tnewLocalName = sanitizeName(cfg.name as string);\n\t\tif (!newLocalName) return result(\"config.name is invalid after sanitization.\", true);\n\t}\n\tlet newPackageName = target.packageName;\n\tif (hasKey(cfg, \"package\")) {\n\t\tconst parsedPackage = parsePackageConfig(cfg.package);\n\t\tif (parsedPackage.error) return result(parsedPackage.error, true);\n\t\tnewPackageName = parsedPackage.packageName;\n\t}\n\tlet parsedSteps: ChainStepConfig[] | undefined;\n\tif (hasKey(cfg, \"steps\")) {\n\t\tconst parsed = parseStepList(cfg.steps);\n\t\tif (parsed.error) return result(parsed.error, true);\n\t\tparsedSteps = parsed.steps!;\n\t}\n\tupdated.localName = newLocalName;\n\tif (newPackageName !== undefined) updated.packageName = newPackageName;\n\telse delete updated.packageName;\n\tupdated.name = buildRuntimeName(newLocalName, newPackageName);\n\tif (hasKey(cfg, \"description\")) updated.description = (cfg.description as string).trim();\n\tif (parsedSteps) {\n\t\tupdated.steps = parsedSteps;\n\t\tconst missing = unknownChainAgents(ctx.cwd, updated.steps);\n\t\tif (missing.length) warnings.push(`Warning: chain steps reference unknown agents: ${missing.join(\", \")}.`);\n\t\twarnings.push(...chainStepWarnings(ctx, updated.steps));\n\t}\n\tif (updated.name !== oldName) {\n\t\tconst renamed = renamePath(\"chain\", target.filePath, updated.name, target.source, ctx.cwd);\n\t\tif (renamed.error) return result(renamed.error, true);\n\t\tupdated.filePath = renamed.filePath!;\n\t}\n\tfs.writeFileSync(\n\t\tupdated.filePath,\n\t\tupdated.filePath.endsWith(\".chain.json\") ? serializeJsonChain(updated) : serializeChain(updated),\n\t\t\"utf-8\",\n\t);\n\tconst headline =\n\t\tupdated.name === oldName\n\t\t\t? `Updated chain '${updated.name}' at ${updated.filePath}.`\n\t\t\t: `Updated chain '${oldName}' to '${updated.name}' at ${updated.filePath}.`;\n\treturn result([headline, ...warnings].join(\"\\n\"));\n}\n\nfunction handleDelete(params: ManagementParams, ctx: ManagementContext): AgentToolResult<Details> {\n\tif (!params.agent && !params.chainName) return result(\"Specify 'agent' or 'chainName' for delete.\", true);\n\tif (params.agent && params.chainName) return result(\"Specify either 'agent' or 'chainName', not both.\", true);\n\tconst scopeHint = asDisambiguationScope(params.agentScope);\n\tif (params.agent) {\n\t\tconst targetOrError = resolveTarget(\n\t\t\t\"agent\",\n\t\t\tparams.agent,\n\t\t\tfindAgents(params.agent, ctx.cwd, scopeHint ?? \"both\"),\n\t\t\tctx.cwd,\n\t\t\tscopeHint,\n\t\t);\n\t\tif (\"content\" in targetOrError) return targetOrError;\n\t\tconst target = targetOrError;\n\t\tfs.unlinkSync(target.filePath);\n\t\tconst refs = discoverAgentsAll(ctx.cwd)\n\t\t\t.chains.filter((c) => c.steps.some((s) => s.agent === target.name))\n\t\t\t.map((c) => `${c.name} (${c.source})`);\n\t\tconst lines = [`Deleted agent '${target.name}' at ${target.filePath}.`];\n\t\tif (refs.length) lines.push(`Warning: chains reference deleted agent '${target.name}': ${refs.join(\", \")}.`);\n\t\treturn result(lines.join(\"\\n\"));\n\t}\n\tconst targetOrError = resolveTarget(\n\t\t\"chain\",\n\t\tparams.chainName!,\n\t\tfindChains(params.chainName!, ctx.cwd, scopeHint ?? \"both\"),\n\t\tctx.cwd,\n\t\tscopeHint,\n\t);\n\tif (\"content\" in targetOrError) return targetOrError;\n\tconst target = targetOrError;\n\tfs.unlinkSync(target.filePath);\n\treturn result(`Deleted chain '${target.name}' at ${target.filePath}.`);\n}\n\nfunction handleEject(params: ManagementParams, ctx: ManagementContext): AgentToolResult<Details> {\n\tif (!params.agent) return result(\"Specify 'agent' for eject.\", true);\n\tconst raw = params.agent.trim();\n\tconst sanitized = sanitizeName(raw);\n\tconst parsedScope = actionScope(params.agentScope, \"eject\");\n\tif (parsedScope.error) return parsedScope.error;\n\tconst scope = parsedScope.scope!;\n\tconst d = discoverAgentsAll(ctx.cwd);\n\tconst source = [...d.package, ...d.builtin].find((a) => a.name === raw || a.name === sanitized);\n\tif (!source) {\n\t\treturn result(\n\t\t\t`Agent '${raw}' not found or is not a bundled/package agent. eject copies a builtin or package agent to ${scope} scope so it can be customized. Available: ${availableNames(ctx.cwd, \"agent\").join(\", \") || \"none\"}.`,\n\t\t\ttrue,\n\t\t);\n\t}\n\tconst runtimeName = source.name;\n\tconst existingCustom = (scope === \"user\" ? d.user : d.project).find((a) => a.name === runtimeName);\n\tif (existingCustom) {\n\t\treturn result(\n\t\t\t`Agent '${runtimeName}' is already a custom ${scope} agent at ${existingCustom.filePath}. Edit it with { action: \"update\", agent: \"${runtimeName}\" } or delete it first.`,\n\t\t\ttrue,\n\t\t);\n\t}\n\tif (nameExistsInScope(ctx.cwd, scope, runtimeName)) {\n\t\treturn result(\n\t\t\t`An agent or chain named '${runtimeName}' already exists in ${scope} scope. Remove or rename it first.`,\n\t\t\ttrue,\n\t\t);\n\t}\n\tconst projectConfigDir = getProjectConfigDir(ctx.cwd);\n\tconst targetDir = scope === \"user\" ? d.userDir : (d.projectDir ?? path.join(projectConfigDir, \"agents\"));\n\tfs.mkdirSync(targetDir, { recursive: true });\n\tconst targetPath = path.join(targetDir, `${runtimeName}.md`);\n\tif (fs.existsSync(targetPath)) {\n\t\treturn result(\n\t\t\t`File already exists at ${targetPath} but is not a valid agent definition. Remove or rename it first.`,\n\t\t\ttrue,\n\t\t);\n\t}\n\tlet content: string;\n\ttry {\n\t\tcontent = fs.readFileSync(source.filePath, \"utf-8\");\n\t} catch (error) {\n\t\tconst message = error instanceof Error ? error.message : String(error);\n\t\treturn result(`Failed to read source agent at ${source.filePath}: ${message}`, true);\n\t}\n\tfs.writeFileSync(targetPath, content, \"utf-8\");\n\treturn result(\n\t\t`Ejected agent '${runtimeName}' from ${source.source} to ${scope} scope at ${targetPath}. Edit it there to customize; it shadows the bundled ${source.source} agent of the same name.`,\n\t);\n}\n\nfunction handleDisable(params: ManagementParams, ctx: ManagementContext): AgentToolResult<Details> {\n\tif (!params.agent) return result(\"Specify 'agent' for disable.\", true);\n\tconst raw = params.agent.trim();\n\tconst parsedScope = actionScope(params.agentScope, \"disable\");\n\tif (parsedScope.error) return parsedScope.error;\n\tconst scope = parsedScope.scope!;\n\tconst d = discoverAgentsAll(ctx.cwd);\n\tif (scope === \"project\" && d.projectSettingsPath === null) {\n\t\treturn result(\n\t\t\t\"Project override is not available here: no project config root (.pi or .agents) was found above the cwd. Use agentScope: 'user' or run from inside a project.\",\n\t\t\ttrue,\n\t\t);\n\t}\n\tconst effective = resolveEffectiveAgent(d, raw);\n\tif (effective.error) return result(effective.error, true);\n\tif (!effective.agent) {\n\t\treturn result(\n\t\t\t`Agent '${raw}' not found. Available: ${availableNames(ctx.cwd, \"agent\").join(\", \") || \"none\"}.`,\n\t\t\ttrue,\n\t\t);\n\t}\n\tconst runtimeName = effective.agent.name;\n\tconst settingsPath = mergeBuiltinAgentOverride(ctx.cwd, runtimeName, scope, { disabled: true });\n\tconst after = resolveEffectiveAgent(discoverAgentsAll(ctx.cwd), raw).agent;\n\tif (after?.disabled === true) {\n\t\treturn result(\n\t\t\t`Disabled agent '${runtimeName}' via ${scope} settings override at ${settingsPath}. It is now hidden from runtime discovery and { action: \"list\" }.`,\n\t\t);\n\t}\n\treturn result(\n\t\t`Wrote a disabled override for '${runtimeName}' at ${settingsPath}, but the agent is still enabled. A higher-precedence ${after?.override?.scope ?? \"project\"} override is likely winning. Try agentScope: '${after?.override?.scope ?? \"project\"}'.`,\n\t\ttrue,\n\t);\n}\n\nfunction handleEnable(params: ManagementParams, ctx: ManagementContext): AgentToolResult<Details> {\n\tif (!params.agent) return result(\"Specify 'agent' for enable.\", true);\n\tconst raw = params.agent.trim();\n\tconst parsedScope = actionScope(params.agentScope, \"enable\");\n\tif (parsedScope.error) return parsedScope.error;\n\tconst scope = parsedScope.scope!;\n\tconst d = discoverAgentsAll(ctx.cwd);\n\tif (scope === \"project\" && d.projectSettingsPath === null) {\n\t\treturn result(\n\t\t\t\"Project override is not available here: no project config root (.pi or .agents) was found above the cwd. Use agentScope: 'user' or run from inside a project.\",\n\t\t\ttrue,\n\t\t);\n\t}\n\tconst effective = resolveEffectiveAgent(d, raw);\n\tif (effective.error) return result(effective.error, true);\n\tif (!effective.agent) {\n\t\treturn result(\n\t\t\t`Agent '${raw}' not found. Available: ${availableNames(ctx.cwd, \"agent\").join(\", \") || \"none\"}.`,\n\t\t\ttrue,\n\t\t);\n\t}\n\tconst runtimeName = effective.agent.name;\n\tconst { path: settingsPath, removed } = removeBuiltinAgentOverrideFields(ctx.cwd, runtimeName, scope, [\"disabled\"]);\n\tconst after = resolveEffectiveAgent(discoverAgentsAll(ctx.cwd), raw).agent;\n\tif (after && after.disabled !== true) {\n\t\tif (removed) return result(`Enabled agent '${runtimeName}' (removed disabled override at ${settingsPath}).`);\n\t\treturn result(`Agent '${runtimeName}' is already enabled.`);\n\t}\n\tif (after?.override?.scope && after.override.scope !== scope) {\n\t\treturn result(\n\t\t\t`Agent '${runtimeName}' is still disabled via a ${after.override.scope} scope override at ${after.override.path}. Specify agentScope: '${after.override.scope}' to enable it.`,\n\t\t\ttrue,\n\t\t);\n\t}\n\treturn result(\n\t\t`Agent '${runtimeName}' is still disabled after removing the ${scope} disabled override. It may be hidden via subagents.disableBuiltins in ${after?.override?.scope ?? scope} settings at ${after?.override?.path ?? settingsPath}.`,\n\t\ttrue,\n\t);\n}\n\nfunction handleReset(params: ManagementParams, ctx: ManagementContext): AgentToolResult<Details> {\n\tif (!params.agent) return result(\"Specify 'agent' for reset.\", true);\n\tconst raw = params.agent.trim();\n\tconst sanitized = sanitizeName(raw);\n\tconst parsedScope = actionScope(params.agentScope, \"reset\");\n\tif (parsedScope.error) return parsedScope.error;\n\tconst scope = parsedScope.scope!;\n\tconst d = discoverAgentsAll(ctx.cwd);\n\tif (scope === \"project\" && d.projectSettingsPath === null) {\n\t\treturn result(\n\t\t\t\"Project override is not available here: no project config root (.pi or .agents) was found above the cwd. Use agentScope: 'user' or run from inside a project.\",\n\t\t\ttrue,\n\t\t);\n\t}\n\tconst bundled = [...d.package, ...d.builtin].find((a) => a.name === raw || a.name === sanitized);\n\tif (!bundled) {\n\t\tconst custom = [...d.user, ...d.project].find((a) => a.name === raw || a.name === sanitized);\n\t\tif (custom) {\n\t\t\treturn result(\n\t\t\t\t`Agent '${raw}' has no bundled default to reset to. Use { action: \"delete\", agent: \"${custom.name}\" } to remove the custom ${custom.source} agent.`,\n\t\t\t\ttrue,\n\t\t\t);\n\t\t}\n\t\treturn result(\n\t\t\t`Agent '${raw}' not found. Available: ${availableNames(ctx.cwd, \"agent\").join(\", \") || \"none\"}.`,\n\t\t\ttrue,\n\t\t);\n\t}\n\tconst runtimeName = bundled.name;\n\tconst custom = (scope === \"user\" ? d.user : d.project).find((a) => a.name === raw || a.name === sanitized);\n\tconst lines: string[] = [];\n\tif (custom) {\n\t\tfs.unlinkSync(custom.filePath);\n\t\tlines.push(`Deleted custom ${scope} agent file at ${custom.filePath}.`);\n\t}\n\tconst overrideRemoval = removeBuiltinAgentOverride(ctx.cwd, runtimeName, scope);\n\tif (overrideRemoval.removed) lines.push(`Removed ${scope} settings override at ${overrideRemoval.path}.`);\n\tif (lines.length === 0) {\n\t\tconst otherScope = scope === \"user\" ? \"project\" : \"user\";\n\t\tconst otherCustom = (otherScope === \"user\" ? d.user : d.project).find(\n\t\t\t(a) => a.name === raw || a.name === sanitized,\n\t\t);\n\t\tconst hasOtherOverride = bundled.override?.scope === otherScope;\n\t\tconst note =\n\t\t\totherCustom || hasOtherOverride\n\t\t\t\t? ` Customization exists in ${otherScope} scope; specify agentScope: '${otherScope}' to reset it.`\n\t\t\t\t: \"\";\n\t\treturn result(\n\t\t\t`Agent '${runtimeName}' has no ${scope} customization to reset.${note} It is at its bundled ${bundled.source} default.`,\n\t\t);\n\t}\n\tlines.push(`Reset agent '${runtimeName}' to its bundled ${bundled.source} default.`);\n\treturn result(lines.join(\"\\n\"));\n}\n\nexport function handleManagementAction(\n\taction: string,\n\tparams: ManagementParams,\n\tctx: ManagementContext,\n): AgentToolResult<Details> {\n\tswitch (action as ManagementAction) {\n\t\tcase \"list\":\n\t\t\treturn handleList(params, ctx);\n\t\tcase \"get\":\n\t\t\treturn handleGet(params, ctx);\n\t\tcase \"models\":\n\t\t\treturn handleModels(params, ctx);\n\t\tcase \"create\":\n\t\t\treturn handleCreate(params, ctx);\n\t\tcase \"update\":\n\t\t\treturn handleUpdate(params, ctx);\n\t\tcase \"delete\":\n\t\t\treturn handleDelete(params, ctx);\n\t\tcase \"eject\":\n\t\t\treturn handleEject(params, ctx);\n\t\tcase \"disable\":\n\t\t\treturn handleDisable(params, ctx);\n\t\tcase \"enable\":\n\t\t\treturn handleEnable(params, ctx);\n\t\tcase \"reset\":\n\t\t\treturn handleReset(params, ctx);\n\t\tdefault:\n\t\t\treturn result(`Unknown action: ${action}`, true);\n\t}\n}\n"]}