Something went wrong. Try again.
source dump of claude code forked from oppi.li/claude-code
Something went wrong. Try again.
26 kB · 755 lines
TypeScript
at main
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756import { feature } from 'bun:bundle'import memoize from 'lodash-es/memoize.js'import { basename } from 'path'import type { SettingSource } from 'src/utils/settings/constants.js'import { z } from 'zod/v4'import { isAutoMemoryEnabled } from '../../memdir/paths.js'import { type AnalyticsMetadata_I_VERIFIED_THIS_IS_NOT_CODE_OR_FILEPATHS, logEvent,} from '../../services/analytics/index.js'import { type McpServerConfig, McpServerConfigSchema,} from '../../services/mcp/types.js'import type { ToolUseContext } from '../../Tool.js'import { logForDebugging } from '../../utils/debug.js'import { EFFORT_LEVELS, type EffortValue, parseEffortValue,} from '../../utils/effort.js'import { isEnvTruthy } from '../../utils/envUtils.js'import { parsePositiveIntFromFrontmatter } from '../../utils/frontmatterParser.js'import { lazySchema } from '../../utils/lazySchema.js'import { logError } from '../../utils/log.js'import { loadMarkdownFilesForSubdir, parseAgentToolsFromFrontmatter, parseSlashCommandToolsFromFrontmatter,} from '../../utils/markdownConfigLoader.js'import { PERMISSION_MODES, type PermissionMode,} from '../../utils/permissions/PermissionMode.js'import { clearPluginAgentCache, loadPluginAgents,} from '../../utils/plugins/loadPluginAgents.js'import { HooksSchema, type HooksSettings } from '../../utils/settings/types.js'import { jsonStringify } from '../../utils/slowOperations.js'import { FILE_EDIT_TOOL_NAME } from '../FileEditTool/constants.js'import { FILE_READ_TOOL_NAME } from '../FileReadTool/prompt.js'import { FILE_WRITE_TOOL_NAME } from '../FileWriteTool/prompt.js'import { AGENT_COLORS, type AgentColorName, setAgentColor,} from './agentColorManager.js'import { type AgentMemoryScope, loadAgentMemoryPrompt } from './agentMemory.js'import { checkAgentMemorySnapshot, initializeFromSnapshot,} from './agentMemorySnapshot.js'import { getBuiltInAgents } from './builtInAgents.js'
// Type for MCP server specification in agent definitions// Can be either a reference to an existing server by name, or an inline definition as { [name]: config }export type AgentMcpServerSpec = | string // Reference to existing server by name (e.g., "slack") | { [name: string]: McpServerConfig } // Inline definition as { name: config }
// Zod schema for agent MCP server specsconst AgentMcpServerSpecSchema = lazySchema(() => z.union([ z.string(), // Reference by name z.record(z.string(), McpServerConfigSchema()), // Inline as { name: config } ]),)
// Zod schemas for JSON agent validation// Note: HooksSchema is lazy so the circular chain AppState -> loadAgentsDir -> settings/types// is broken at module load timeconst AgentJsonSchema = lazySchema(() => z.object({ description: z.string().min(1, 'Description cannot be empty'), tools: z.array(z.string()).optional(), disallowedTools: z.array(z.string()).optional(), prompt: z.string().min(1, 'Prompt cannot be empty'), model: z .string() .trim() .min(1, 'Model cannot be empty') .transform(m => (m.toLowerCase() === 'inherit' ? 'inherit' : m)) .optional(), effort: z.union([z.enum(EFFORT_LEVELS), z.number().int()]).optional(), permissionMode: z.enum(PERMISSION_MODES).optional(), mcpServers: z.array(AgentMcpServerSpecSchema()).optional(), hooks: HooksSchema().optional(), maxTurns: z.number().int().positive().optional(), skills: z.array(z.string()).optional(), initialPrompt: z.string().optional(), memory: z.enum(['user', 'project', 'local']).optional(), background: z.boolean().optional(), isolation: (process.env.USER_TYPE === 'ant' ? z.enum(['worktree', 'remote']) : z.enum(['worktree']) ).optional(), }),)
const AgentsJsonSchema = lazySchema(() => z.record(z.string(), AgentJsonSchema()),)
// Base type with common fields for all agentsexport type BaseAgentDefinition = { agentType: string whenToUse: string tools?: string[] disallowedTools?: string[] skills?: string[] // Skill names to preload (parsed from comma-separated frontmatter) mcpServers?: AgentMcpServerSpec[] // MCP servers specific to this agent hooks?: HooksSettings // Session-scoped hooks registered when agent starts color?: AgentColorName model?: string effort?: EffortValue permissionMode?: PermissionMode maxTurns?: number // Maximum number of agentic turns before stopping filename?: string // Original filename without .md extension (for user/project/managed agents) baseDir?: string criticalSystemReminder_EXPERIMENTAL?: string // Short message re-injected at every user turn requiredMcpServers?: string[] // MCP server name patterns that must be configured for agent to be available background?: boolean // Always run as background task when spawned initialPrompt?: string // Prepended to the first user turn (slash commands work) memory?: AgentMemoryScope // Persistent memory scope isolation?: 'worktree' | 'remote' // Run in an isolated git worktree, or remotely in CCR (ant-only) pendingSnapshotUpdate?: { snapshotTimestamp: string } /** Omit CLAUDE.md hierarchy from the agent's userContext. Read-only agents * (Explore, Plan) don't need commit/PR/lint guidelines — the main agent has * full CLAUDE.md and interprets their output. Saves ~5-15 Gtok/week across * 34M+ Explore spawns. Kill-switch: tengu_slim_subagent_claudemd. */ omitClaudeMd?: boolean}
// Built-in agents - dynamic prompts only, no static systemPrompt fieldexport type BuiltInAgentDefinition = BaseAgentDefinition & { source: 'built-in' baseDir: 'built-in' callback?: () => void getSystemPrompt: (params: { toolUseContext: Pick<ToolUseContext, 'options'> }) => string}
// Custom agents from user/project/policy settings - prompt stored via closureexport type CustomAgentDefinition = BaseAgentDefinition & { getSystemPrompt: () => string source: SettingSource filename?: string baseDir?: string}
// Plugin agents - similar to custom but with plugin metadata, prompt stored via closureexport type PluginAgentDefinition = BaseAgentDefinition & { getSystemPrompt: () => string source: 'plugin' filename?: string plugin: string}
// Union type for all agent typesexport type AgentDefinition = | BuiltInAgentDefinition | CustomAgentDefinition | PluginAgentDefinition
// Type guards for runtime type checkingexport function isBuiltInAgent( agent: AgentDefinition,): agent is BuiltInAgentDefinition { return agent.source === 'built-in'}
export function isCustomAgent( agent: AgentDefinition,): agent is CustomAgentDefinition { return agent.source !== 'built-in' && agent.source !== 'plugin'}
export function isPluginAgent( agent: AgentDefinition,): agent is PluginAgentDefinition { return agent.source === 'plugin'}
export type AgentDefinitionsResult = { activeAgents: AgentDefinition[] allAgents: AgentDefinition[] failedFiles?: Array<{ path: string; error: string }> allowedAgentTypes?: string[]}
export function getActiveAgentsFromList( allAgents: AgentDefinition[],): AgentDefinition[] { const builtInAgents = allAgents.filter(a => a.source === 'built-in') const pluginAgents = allAgents.filter(a => a.source === 'plugin') const userAgents = allAgents.filter(a => a.source === 'userSettings') const projectAgents = allAgents.filter(a => a.source === 'projectSettings') const managedAgents = allAgents.filter(a => a.source === 'policySettings') const flagAgents = allAgents.filter(a => a.source === 'flagSettings')
const agentGroups = [ builtInAgents, pluginAgents, userAgents, projectAgents, flagAgents, managedAgents, ]
const agentMap = new Map<string, AgentDefinition>()
for (const agents of agentGroups) { for (const agent of agents) { agentMap.set(agent.agentType, agent) } }
return Array.from(agentMap.values())}
/** * Checks if an agent's required MCP servers are available. * Returns true if no requirements or all requirements are met. * @param agent The agent to check * @param availableServers List of available MCP server names (e.g., from mcp.clients) */export function hasRequiredMcpServers( agent: AgentDefinition, availableServers: string[],): boolean { if (!agent.requiredMcpServers || agent.requiredMcpServers.length === 0) { return true } // Each required pattern must match at least one available server (case-insensitive) return agent.requiredMcpServers.every(pattern => availableServers.some(server => server.toLowerCase().includes(pattern.toLowerCase()), ), )}
/** * Filters agents based on MCP server requirements. * Only returns agents whose required MCP servers are available. * @param agents List of agents to filter * @param availableServers List of available MCP server names */export function filterAgentsByMcpRequirements( agents: AgentDefinition[], availableServers: string[],): AgentDefinition[] { return agents.filter(agent => hasRequiredMcpServers(agent, availableServers))}
/** * Check for and initialize agent memory from project snapshots. * For agents with memory enabled, copies snapshot to local if no local memory exists. * For agents with newer snapshots, logs a debug message (user prompt TODO). */async function initializeAgentMemorySnapshots( agents: CustomAgentDefinition[],): Promise<void> { await Promise.all( agents.map(async agent => { if (agent.memory !== 'user') return const result = await checkAgentMemorySnapshot( agent.agentType, agent.memory, ) switch (result.action) { case 'initialize': logForDebugging( `Initializing ${agent.agentType} memory from project snapshot`, ) await initializeFromSnapshot( agent.agentType, agent.memory, result.snapshotTimestamp!, ) break case 'prompt-update': agent.pendingSnapshotUpdate = { snapshotTimestamp: result.snapshotTimestamp!, } logForDebugging( `Newer snapshot available for ${agent.agentType} memory (snapshot: ${result.snapshotTimestamp})`, ) break } }), )}
export const getAgentDefinitionsWithOverrides = memoize( async (cwd: string): Promise<AgentDefinitionsResult> => { // Simple mode: skip custom agents, only return built-ins if (isEnvTruthy(process.env.CLAUDE_CODE_SIMPLE)) { const builtInAgents = getBuiltInAgents() return { activeAgents: builtInAgents, allAgents: builtInAgents, } }
try { const markdownFiles = await loadMarkdownFilesForSubdir('agents', cwd)
const failedFiles: Array<{ path: string; error: string }> = [] const customAgents = markdownFiles .map(({ filePath, baseDir, frontmatter, content, source }) => { const agent = parseAgentFromMarkdown( filePath, baseDir, frontmatter, content, source, ) if (!agent) { // Skip non-agent markdown files silently (e.g., reference docs // co-located with agent definitions). Only report errors for files // that look like agent attempts (have a 'name' field in frontmatter). if (!frontmatter['name']) { return null } const errorMsg = getParseError(frontmatter) failedFiles.push({ path: filePath, error: errorMsg }) logForDebugging( `Failed to parse agent from ${filePath}: ${errorMsg}`, ) logEvent('tengu_agent_parse_error', { error: errorMsg as AnalyticsMetadata_I_VERIFIED_THIS_IS_NOT_CODE_OR_FILEPATHS, location: source as AnalyticsMetadata_I_VERIFIED_THIS_IS_NOT_CODE_OR_FILEPATHS, }) return null } return agent }) .filter(agent => agent !== null)
// Kick off plugin agent loading concurrently with memory snapshot init — // loadPluginAgents is memoized and takes no args, so it's independent. // Join both so neither becomes a floating promise if the other throws. let pluginAgentsPromise = loadPluginAgents() if (feature('AGENT_MEMORY_SNAPSHOT') && isAutoMemoryEnabled()) { const [pluginAgents_] = await Promise.all([ pluginAgentsPromise, initializeAgentMemorySnapshots(customAgents), ]) pluginAgentsPromise = Promise.resolve(pluginAgents_) } const pluginAgents = await pluginAgentsPromise
const builtInAgents = getBuiltInAgents()
const allAgentsList: AgentDefinition[] = [ ...builtInAgents, ...pluginAgents, ...customAgents, ]
const activeAgents = getActiveAgentsFromList(allAgentsList)
// Initialize colors for all active agents for (const agent of activeAgents) { if (agent.color) { setAgentColor(agent.agentType, agent.color) } }
return { activeAgents, allAgents: allAgentsList, failedFiles: failedFiles.length > 0 ? failedFiles : undefined, } } catch (error) { const errorMessage = error instanceof Error ? error.message : String(error) logForDebugging(`Error loading agent definitions: ${errorMessage}`) logError(error) // Even on error, return the built-in agents const builtInAgents = getBuiltInAgents() return { activeAgents: builtInAgents, allAgents: builtInAgents, failedFiles: [{ path: 'unknown', error: errorMessage }], } } },)
export function clearAgentDefinitionsCache(): void { getAgentDefinitionsWithOverrides.cache.clear?.() clearPluginAgentCache()}
/** * Helper to determine the specific parsing error for an agent file */function getParseError(frontmatter: Record<string, unknown>): string { const agentType = frontmatter['name'] const description = frontmatter['description']
if (!agentType || typeof agentType !== 'string') { return 'Missing required "name" field in frontmatter' }
if (!description || typeof description !== 'string') { return 'Missing required "description" field in frontmatter' }
return 'Unknown parsing error'}
/** * Parse hooks from frontmatter using the HooksSchema * @param frontmatter The frontmatter object containing potential hooks * @param agentType The agent type for logging purposes * @returns Parsed hooks settings or undefined if invalid/missing */function parseHooksFromFrontmatter( frontmatter: Record<string, unknown>, agentType: string,): HooksSettings | undefined { if (!frontmatter.hooks) { return undefined }
const result = HooksSchema().safeParse(frontmatter.hooks) if (!result.success) { logForDebugging( `Invalid hooks in agent '${agentType}': ${result.error.message}`, ) return undefined } return result.data}
/** * Parses agent definition from JSON data */export function parseAgentFromJson( name: string, definition: unknown, source: SettingSource = 'flagSettings',): CustomAgentDefinition | null { try { const parsed = AgentJsonSchema().parse(definition)
let tools = parseAgentToolsFromFrontmatter(parsed.tools)
// If memory is enabled, inject Write/Edit/Read tools for memory access if (isAutoMemoryEnabled() && parsed.memory && tools !== undefined) { const toolSet = new Set(tools) for (const tool of [ FILE_WRITE_TOOL_NAME, FILE_EDIT_TOOL_NAME, FILE_READ_TOOL_NAME, ]) { if (!toolSet.has(tool)) { tools = [...tools, tool] } } }
const disallowedTools = parsed.disallowedTools !== undefined ? parseAgentToolsFromFrontmatter(parsed.disallowedTools) : undefined
const systemPrompt = parsed.prompt
const agent: CustomAgentDefinition = { agentType: name, whenToUse: parsed.description, ...(tools !== undefined ? { tools } : {}), ...(disallowedTools !== undefined ? { disallowedTools } : {}), getSystemPrompt: () => { if (isAutoMemoryEnabled() && parsed.memory) { return ( systemPrompt + '\n\n' + loadAgentMemoryPrompt(name, parsed.memory) ) } return systemPrompt }, source, ...(parsed.model ? { model: parsed.model } : {}), ...(parsed.effort !== undefined ? { effort: parsed.effort } : {}), ...(parsed.permissionMode ? { permissionMode: parsed.permissionMode } : {}), ...(parsed.mcpServers && parsed.mcpServers.length > 0 ? { mcpServers: parsed.mcpServers } : {}), ...(parsed.hooks ? { hooks: parsed.hooks } : {}), ...(parsed.maxTurns !== undefined ? { maxTurns: parsed.maxTurns } : {}), ...(parsed.skills && parsed.skills.length > 0 ? { skills: parsed.skills } : {}), ...(parsed.initialPrompt ? { initialPrompt: parsed.initialPrompt } : {}), ...(parsed.background ? { background: parsed.background } : {}), ...(parsed.memory ? { memory: parsed.memory } : {}), ...(parsed.isolation ? { isolation: parsed.isolation } : {}), }
return agent } catch (error) { const errorMessage = error instanceof Error ? error.message : String(error) logForDebugging(`Error parsing agent '${name}' from JSON: ${errorMessage}`) logError(error) return null }}
/** * Parses multiple agents from a JSON object */export function parseAgentsFromJson( agentsJson: unknown, source: SettingSource = 'flagSettings',): AgentDefinition[] { try { const parsed = AgentsJsonSchema().parse(agentsJson) return Object.entries(parsed) .map(([name, def]) => parseAgentFromJson(name, def, source)) .filter((agent): agent is CustomAgentDefinition => agent !== null) } catch (error) { const errorMessage = error instanceof Error ? error.message : String(error) logForDebugging(`Error parsing agents from JSON: ${errorMessage}`) logError(error) return [] }}
/** * Parses agent definition from markdown file data */export function parseAgentFromMarkdown( filePath: string, baseDir: string, frontmatter: Record<string, unknown>, content: string, source: SettingSource,): CustomAgentDefinition | null { try { const agentType = frontmatter['name'] let whenToUse = frontmatter['description'] as string
// Validate required fields — silently skip files without any agent // frontmatter (they're likely co-located reference documentation) if (!agentType || typeof agentType !== 'string') { return null } if (!whenToUse || typeof whenToUse !== 'string') { logForDebugging( `Agent file ${filePath} is missing required 'description' in frontmatter`, ) return null }
// Unescape newlines in whenToUse that were escaped for YAML parsing whenToUse = whenToUse.replace(/\\n/g, '\n')
const color = frontmatter['color'] as AgentColorName | undefined const modelRaw = frontmatter['model'] let model: string | undefined if (typeof modelRaw === 'string' && modelRaw.trim().length > 0) { const trimmed = modelRaw.trim() model = trimmed.toLowerCase() === 'inherit' ? 'inherit' : trimmed }
// Parse background flag const backgroundRaw = frontmatter['background']
if ( backgroundRaw !== undefined && backgroundRaw !== 'true' && backgroundRaw !== 'false' && backgroundRaw !== true && backgroundRaw !== false ) { logForDebugging( `Agent file ${filePath} has invalid background value '${backgroundRaw}'. Must be 'true', 'false', or omitted.`, ) }
const background = backgroundRaw === 'true' || backgroundRaw === true ? true : undefined
// Parse memory scope const VALID_MEMORY_SCOPES: AgentMemoryScope[] = ['user', 'project', 'local'] const memoryRaw = frontmatter['memory'] as string | undefined let memory: AgentMemoryScope | undefined if (memoryRaw !== undefined) { if (VALID_MEMORY_SCOPES.includes(memoryRaw as AgentMemoryScope)) { memory = memoryRaw as AgentMemoryScope } else { logForDebugging( `Agent file ${filePath} has invalid memory value '${memoryRaw}'. Valid options: ${VALID_MEMORY_SCOPES.join(', ')}`, ) } }
// Parse isolation mode. 'remote' is ant-only; external builds reject it at parse time. type IsolationMode = 'worktree' | 'remote' const VALID_ISOLATION_MODES: readonly IsolationMode[] = process.env.USER_TYPE === 'ant' ? ['worktree', 'remote'] : ['worktree'] const isolationRaw = frontmatter['isolation'] as string | undefined let isolation: IsolationMode | undefined if (isolationRaw !== undefined) { if (VALID_ISOLATION_MODES.includes(isolationRaw as IsolationMode)) { isolation = isolationRaw as IsolationMode } else { logForDebugging( `Agent file ${filePath} has invalid isolation value '${isolationRaw}'. Valid options: ${VALID_ISOLATION_MODES.join(', ')}`, ) } }
// Parse effort from frontmatter (supports string levels and integers) const effortRaw = frontmatter['effort'] const parsedEffort = effortRaw !== undefined ? parseEffortValue(effortRaw) : undefined
if (effortRaw !== undefined && parsedEffort === undefined) { logForDebugging( `Agent file ${filePath} has invalid effort '${effortRaw}'. Valid options: ${EFFORT_LEVELS.join(', ')} or an integer`, ) }
// Parse permissionMode from frontmatter const permissionModeRaw = frontmatter['permissionMode'] as | string | undefined const isValidPermissionMode = permissionModeRaw && (PERMISSION_MODES as readonly string[]).includes(permissionModeRaw)
if (permissionModeRaw && !isValidPermissionMode) { const errorMsg = `Agent file ${filePath} has invalid permissionMode '${permissionModeRaw}'. Valid options: ${PERMISSION_MODES.join(', ')}` logForDebugging(errorMsg) }
// Parse maxTurns from frontmatter const maxTurnsRaw = frontmatter['maxTurns'] const maxTurns = parsePositiveIntFromFrontmatter(maxTurnsRaw) if (maxTurnsRaw !== undefined && maxTurns === undefined) { logForDebugging( `Agent file ${filePath} has invalid maxTurns '${maxTurnsRaw}'. Must be a positive integer.`, ) }
// Extract filename without extension const filename = basename(filePath, '.md')
// Parse tools from frontmatter let tools = parseAgentToolsFromFrontmatter(frontmatter['tools'])
// If memory is enabled, inject Write/Edit/Read tools for memory access if (isAutoMemoryEnabled() && memory && tools !== undefined) { const toolSet = new Set(tools) for (const tool of [ FILE_WRITE_TOOL_NAME, FILE_EDIT_TOOL_NAME, FILE_READ_TOOL_NAME, ]) { if (!toolSet.has(tool)) { tools = [...tools, tool] } } }
// Parse disallowedTools from frontmatter const disallowedToolsRaw = frontmatter['disallowedTools'] const disallowedTools = disallowedToolsRaw !== undefined ? parseAgentToolsFromFrontmatter(disallowedToolsRaw) : undefined
// Parse skills from frontmatter const skills = parseSlashCommandToolsFromFrontmatter(frontmatter['skills'])
const initialPromptRaw = frontmatter['initialPrompt'] const initialPrompt = typeof initialPromptRaw === 'string' && initialPromptRaw.trim() ? initialPromptRaw : undefined
// Parse mcpServers from frontmatter using same Zod validation as JSON agents const mcpServersRaw = frontmatter['mcpServers'] let mcpServers: AgentMcpServerSpec[] | undefined if (Array.isArray(mcpServersRaw)) { mcpServers = mcpServersRaw .map(item => { const result = AgentMcpServerSpecSchema().safeParse(item) if (result.success) { return result.data } logForDebugging( `Agent file ${filePath} has invalid mcpServers item: ${jsonStringify(item)}. Error: ${result.error.message}`, ) return null }) .filter((item): item is AgentMcpServerSpec => item !== null) }
// Parse hooks from frontmatter const hooks = parseHooksFromFrontmatter(frontmatter, agentType)
const systemPrompt = content.trim() const agentDef: CustomAgentDefinition = { baseDir, agentType: agentType, whenToUse: whenToUse, ...(tools !== undefined ? { tools } : {}), ...(disallowedTools !== undefined ? { disallowedTools } : {}), ...(skills !== undefined ? { skills } : {}), ...(initialPrompt !== undefined ? { initialPrompt } : {}), ...(mcpServers !== undefined && mcpServers.length > 0 ? { mcpServers } : {}), ...(hooks !== undefined ? { hooks } : {}), getSystemPrompt: () => { if (isAutoMemoryEnabled() && memory) { const memoryPrompt = loadAgentMemoryPrompt(agentType, memory) return systemPrompt + '\n\n' + memoryPrompt } return systemPrompt }, source, filename, ...(color && typeof color === 'string' && AGENT_COLORS.includes(color) ? { color } : {}), ...(model !== undefined ? { model } : {}), ...(parsedEffort !== undefined ? { effort: parsedEffort } : {}), ...(isValidPermissionMode ? { permissionMode: permissionModeRaw as PermissionMode } : {}), ...(maxTurns !== undefined ? { maxTurns } : {}), ...(background ? { background } : {}), ...(memory ? { memory } : {}), ...(isolation ? { isolation } : {}), } return agentDef } catch (error) { const errorMessage = error instanceof Error ? error.message : String(error) logForDebugging(`Error parsing agent from ${filePath}: ${errorMessage}`) logError(error) return null }}