| import { feature } from 'bun:bundle' |
| import type { UUID } from 'crypto' |
| import { randomUUID } from 'crypto' |
| import uniqBy from 'lodash-es/uniqBy.js' |
| import { logForDebugging } from 'src/utils/debug.js' |
| import { getProjectRoot, getSessionId } from '../../bootstrap/state.js' |
| import { getCommand, getSkillToolCommands, hasCommand } from '../../commands.js' |
| import { |
| DEFAULT_AGENT_PROMPT, |
| enhanceSystemPromptWithEnvDetails, |
| } from '../../constants/prompts.js' |
| import type { QuerySource } from '../../constants/querySource.js' |
| import { getSystemContext, getUserContext } from '../../context.js' |
| import type { CanUseToolFn } from '../../hooks/useCanUseTool.js' |
| import { query } from '../../query.js' |
| import { getFeatureValue_CACHED_MAY_BE_STALE } from '../../services/analytics/growthbook.js' |
| import { getDumpPromptsPath } from '../../services/api/dumpPrompts.js' |
| import { cleanupAgentTracking } from '../../services/api/promptCacheBreakDetection.js' |
| import { |
| connectToServer, |
| fetchToolsForClient, |
| } from '../../services/mcp/client.js' |
| import { getMcpConfigByName } from '../../services/mcp/config.js' |
| import type { |
| MCPServerConnection, |
| ScopedMcpServerConfig, |
| } from '../../services/mcp/types.js' |
| import type { Tool, Tools, ToolUseContext } from '../../Tool.js' |
| import { killShellTasksForAgent } from '../../tasks/LocalShellTask/killShellTasks.js' |
| import type { Command } from '../../types/command.js' |
| import type { AgentId } from '../../types/ids.js' |
| import type { |
| AssistantMessage, |
| Message, |
| ProgressMessage, |
| RequestStartEvent, |
| StreamEvent, |
| SystemCompactBoundaryMessage, |
| TombstoneMessage, |
| ToolUseSummaryMessage, |
| UserMessage, |
| } from '../../types/message.js' |
| import { createAttachmentMessage } from '../../utils/attachments.js' |
| import { AbortError } from '../../utils/errors.js' |
| import { getDisplayPath } from '../../utils/file.js' |
| import { |
| cloneFileStateCache, |
| createFileStateCacheWithSizeLimit, |
| READ_FILE_STATE_CACHE_SIZE, |
| } from '../../utils/fileStateCache.js' |
| import { |
| type CacheSafeParams, |
| createSubagentContext, |
| } from '../../utils/forkedAgent.js' |
| import { registerFrontmatterHooks } from '../../utils/hooks/registerFrontmatterHooks.js' |
| import { clearSessionHooks } from '../../utils/hooks/sessionHooks.js' |
| import { executeSubagentStartHooks } from '../../utils/hooks.js' |
| import { createUserMessage } from '../../utils/messages.js' |
| import { getAgentModel } from '../../utils/model/agent.js' |
| import type { ModelAlias } from '../../utils/model/aliases.js' |
| import { |
| clearAgentTranscriptSubdir, |
| recordSidechainTranscript, |
| setAgentTranscriptSubdir, |
| writeAgentMetadata, |
| } from '../../utils/sessionStorage.js' |
| import { |
| isRestrictedToPluginOnly, |
| isSourceAdminTrusted, |
| } from '../../utils/settings/pluginOnlyPolicy.js' |
| import { |
| asSystemPrompt, |
| type SystemPrompt, |
| } from '../../utils/systemPromptType.js' |
| import { |
| isPerfettoTracingEnabled, |
| registerAgent as registerPerfettoAgent, |
| unregisterAgent as unregisterPerfettoAgent, |
| } from '../../utils/telemetry/perfettoTracing.js' |
| import type { ContentReplacementState } from '../../utils/toolResultStorage.js' |
| import { createAgentId } from '../../utils/uuid.js' |
| import { resolveAgentTools } from './agentToolUtils.js' |
| import { type AgentDefinition, isBuiltInAgent } from './loadAgentsDir.js' |
|
|
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| async function initializeAgentMcpServers( |
| agentDefinition: AgentDefinition, |
| parentClients: MCPServerConnection[], |
| ): Promise<{ |
| clients: MCPServerConnection[] |
| tools: Tools |
| cleanup: () => Promise<void> |
| }> { |
| |
| if (!agentDefinition.mcpServers?.length) { |
| return { |
| clients: parentClients, |
| tools: [], |
| cleanup: async () => {}, |
| } |
| } |
|
|
| |
| |
| |
| |
| |
| const agentIsAdminTrusted = isSourceAdminTrusted(agentDefinition.source) |
| if (isRestrictedToPluginOnly('mcp') && !agentIsAdminTrusted) { |
| logForDebugging( |
| `[Agent: ${agentDefinition.agentType}] Skipping MCP servers: strictPluginOnlyCustomization locks MCP to plugin-only (agent source: ${agentDefinition.source})`, |
| ) |
| return { |
| clients: parentClients, |
| tools: [], |
| cleanup: async () => {}, |
| } |
| } |
|
|
| const agentClients: MCPServerConnection[] = [] |
| |
| |
| const newlyCreatedClients: MCPServerConnection[] = [] |
| const agentTools: Tool[] = [] |
|
|
| for (const spec of agentDefinition.mcpServers) { |
| let config: ScopedMcpServerConfig | null = null |
| let name: string |
| let isNewlyCreated = false |
|
|
| if (typeof spec === 'string') { |
| |
| |
| name = spec |
| config = getMcpConfigByName(spec) |
| if (!config) { |
| logForDebugging( |
| `[Agent: ${agentDefinition.agentType}] MCP server not found: ${spec}`, |
| { level: 'warn' }, |
| ) |
| continue |
| } |
| } else { |
| |
| |
| const entries = Object.entries(spec) |
| if (entries.length !== 1) { |
| logForDebugging( |
| `[Agent: ${agentDefinition.agentType}] Invalid MCP server spec: expected exactly one key`, |
| { level: 'warn' }, |
| ) |
| continue |
| } |
| const [serverName, serverConfig] = entries[0]! |
| name = serverName |
| config = { |
| ...serverConfig, |
| scope: 'dynamic' as const, |
| } as ScopedMcpServerConfig |
| isNewlyCreated = true |
| } |
|
|
| |
| const client = await connectToServer(name, config) |
| agentClients.push(client) |
| if (isNewlyCreated) { |
| newlyCreatedClients.push(client) |
| } |
|
|
| |
| if (client.type === 'connected') { |
| const tools = await fetchToolsForClient(client) |
| agentTools.push(...tools) |
| logForDebugging( |
| `[Agent: ${agentDefinition.agentType}] Connected to MCP server '${name}' with ${tools.length} tools`, |
| ) |
| } else { |
| logForDebugging( |
| `[Agent: ${agentDefinition.agentType}] Failed to connect to MCP server '${name}': ${client.type}`, |
| { level: 'warn' }, |
| ) |
| } |
| } |
|
|
| |
| |
| |
| const cleanup = async () => { |
| for (const client of newlyCreatedClients) { |
| if (client.type === 'connected') { |
| try { |
| await client.cleanup() |
| } catch (error) { |
| logForDebugging( |
| `[Agent: ${agentDefinition.agentType}] Error cleaning up MCP server '${client.name}': ${error}`, |
| { level: 'warn' }, |
| ) |
| } |
| } |
| } |
| } |
|
|
| |
| return { |
| clients: [...parentClients, ...agentClients], |
| tools: agentTools, |
| cleanup, |
| } |
| } |
|
|
| type QueryMessage = |
| | StreamEvent |
| | RequestStartEvent |
| | Message |
| | ToolUseSummaryMessage |
| | TombstoneMessage |
|
|
| |
| |
| |
| |
| function isRecordableMessage( |
| msg: QueryMessage, |
| ): msg is |
| | AssistantMessage |
| | UserMessage |
| | ProgressMessage |
| | SystemCompactBoundaryMessage { |
| return ( |
| msg.type === 'assistant' || |
| msg.type === 'user' || |
| msg.type === 'progress' || |
| (msg.type === 'system' && |
| 'subtype' in msg && |
| msg.subtype === 'compact_boundary') |
| ) |
| } |
|
|
| export async function* runAgent({ |
| agentDefinition, |
| promptMessages, |
| toolUseContext, |
| canUseTool, |
| isAsync, |
| canShowPermissionPrompts, |
| forkContextMessages, |
| querySource, |
| override, |
| model, |
| maxTurns, |
| preserveToolUseResults, |
| availableTools, |
| allowedTools, |
| onCacheSafeParams, |
| contentReplacementState, |
| useExactTools, |
| worktreePath, |
| description, |
| transcriptSubdir, |
| onQueryProgress, |
| }: { |
| agentDefinition: AgentDefinition |
| promptMessages: Message[] |
| toolUseContext: ToolUseContext |
| canUseTool: CanUseToolFn |
| isAsync: boolean |
| |
| |
| canShowPermissionPrompts?: boolean |
| forkContextMessages?: Message[] |
| querySource: QuerySource |
| override?: { |
| userContext?: { [k: string]: string } |
| systemContext?: { [k: string]: string } |
| systemPrompt?: SystemPrompt |
| abortController?: AbortController |
| agentId?: AgentId |
| } |
| model?: ModelAlias |
| maxTurns?: number |
| |
| preserveToolUseResults?: boolean |
| |
| |
| |
| |
| availableTools: Tools |
| |
| |
| |
| allowedTools?: string[] |
| |
| |
| |
| onCacheSafeParams?: (params: CacheSafeParams) => void |
| |
| |
| |
| contentReplacementState?: ContentReplacementState |
| |
| |
| |
| |
| |
| useExactTools?: boolean |
| |
| |
| worktreePath?: string |
| |
| |
| description?: string |
| |
| |
| transcriptSubdir?: string |
| |
| |
| |
| |
| onQueryProgress?: () => void |
| }): AsyncGenerator<Message, void> { |
| |
|
|
| const appState = toolUseContext.getAppState() |
| const permissionMode = appState.toolPermissionContext.mode |
| |
| |
| |
| const rootSetAppState = |
| toolUseContext.setAppStateForTasks ?? toolUseContext.setAppState |
|
|
| const resolvedAgentModel = getAgentModel( |
| agentDefinition.model, |
| toolUseContext.options.mainLoopModel, |
| model, |
| permissionMode, |
| ) |
|
|
| const agentId = override?.agentId ? override.agentId : createAgentId() |
|
|
| |
| |
| if (transcriptSubdir) { |
| setAgentTranscriptSubdir(agentId, transcriptSubdir) |
| } |
|
|
| |
| if (isPerfettoTracingEnabled()) { |
| const parentId = toolUseContext.agentId ?? getSessionId() |
| registerPerfettoAgent(agentId, agentDefinition.agentType, parentId) |
| } |
|
|
| |
| if (process.env.USER_TYPE === 'ant') { |
| logForDebugging( |
| `[Subagent ${agentDefinition.agentType}] API calls: ${getDisplayPath(getDumpPromptsPath(agentId))}`, |
| ) |
| } |
|
|
| |
| |
| const contextMessages: Message[] = forkContextMessages |
| ? filterIncompleteToolCalls(forkContextMessages) |
| : [] |
| const initialMessages: Message[] = [...contextMessages, ...promptMessages] |
|
|
| const agentReadFileState = |
| forkContextMessages !== undefined |
| ? cloneFileStateCache(toolUseContext.readFileState) |
| : createFileStateCacheWithSizeLimit(READ_FILE_STATE_CACHE_SIZE) |
|
|
| const [baseUserContext, baseSystemContext] = await Promise.all([ |
| override?.userContext ?? getUserContext(), |
| override?.systemContext ?? getSystemContext(), |
| ]) |
|
|
| |
| |
| |
| |
| |
| const shouldOmitClaudeMd = |
| agentDefinition.omitClaudeMd && |
| !override?.userContext && |
| getFeatureValue_CACHED_MAY_BE_STALE('tengu_slim_subagent_claudemd', true) |
| const { claudeMd: _omittedClaudeMd, ...userContextNoClaudeMd } = |
| baseUserContext |
| const resolvedUserContext = shouldOmitClaudeMd |
| ? userContextNoClaudeMd |
| : baseUserContext |
|
|
| |
| |
| |
| |
| const { gitStatus: _omittedGitStatus, ...systemContextNoGit } = |
| baseSystemContext |
| const resolvedSystemContext = |
| agentDefinition.agentType === 'Explore' || |
| agentDefinition.agentType === 'Plan' |
| ? systemContextNoGit |
| : baseSystemContext |
|
|
| |
| |
| |
| const agentPermissionMode = agentDefinition.permissionMode |
| const agentGetAppState = () => { |
| const state = toolUseContext.getAppState() |
| let toolPermissionContext = state.toolPermissionContext |
|
|
| |
| if ( |
| agentPermissionMode && |
| state.toolPermissionContext.mode !== 'bypassPermissions' && |
| state.toolPermissionContext.mode !== 'acceptEdits' && |
| !( |
| feature('TRANSCRIPT_CLASSIFIER') && |
| state.toolPermissionContext.mode === 'auto' |
| ) |
| ) { |
| toolPermissionContext = { |
| ...toolPermissionContext, |
| mode: agentPermissionMode, |
| } |
| } |
|
|
| |
| |
| |
| |
| const shouldAvoidPrompts = |
| canShowPermissionPrompts !== undefined |
| ? !canShowPermissionPrompts |
| : agentPermissionMode === 'bubble' |
| ? false |
| : isAsync |
| if (shouldAvoidPrompts) { |
| toolPermissionContext = { |
| ...toolPermissionContext, |
| shouldAvoidPermissionPrompts: true, |
| } |
| } |
|
|
| |
| |
| |
| |
| |
| if (isAsync && !shouldAvoidPrompts) { |
| toolPermissionContext = { |
| ...toolPermissionContext, |
| awaitAutomatedChecksBeforeDialog: true, |
| } |
| } |
|
|
| |
| |
| |
| |
| if (allowedTools !== undefined) { |
| toolPermissionContext = { |
| ...toolPermissionContext, |
| alwaysAllowRules: { |
| |
| cliArg: state.toolPermissionContext.alwaysAllowRules.cliArg, |
| |
| session: [...allowedTools], |
| }, |
| } |
| } |
|
|
| |
| const effortValue = |
| agentDefinition.effort !== undefined |
| ? agentDefinition.effort |
| : state.effortValue |
|
|
| if ( |
| toolPermissionContext === state.toolPermissionContext && |
| effortValue === state.effortValue |
| ) { |
| return state |
| } |
| return { |
| ...state, |
| toolPermissionContext, |
| effortValue, |
| } |
| } |
|
|
| const resolvedTools = useExactTools |
| ? availableTools |
| : resolveAgentTools(agentDefinition, availableTools, isAsync).resolvedTools |
|
|
| const additionalWorkingDirectories = Array.from( |
| appState.toolPermissionContext.additionalWorkingDirectories.keys(), |
| ) |
|
|
| const agentSystemPrompt = override?.systemPrompt |
| ? override.systemPrompt |
| : asSystemPrompt( |
| await getAgentSystemPrompt( |
| agentDefinition, |
| toolUseContext, |
| resolvedAgentModel, |
| additionalWorkingDirectories, |
| resolvedTools, |
| ), |
| ) |
|
|
| |
| |
| |
| |
| const agentAbortController = override?.abortController |
| ? override.abortController |
| : isAsync |
| ? new AbortController() |
| : toolUseContext.abortController |
|
|
| |
| const additionalContexts: string[] = [] |
| for await (const hookResult of executeSubagentStartHooks( |
| agentId, |
| agentDefinition.agentType, |
| agentAbortController.signal, |
| )) { |
| if ( |
| hookResult.additionalContexts && |
| hookResult.additionalContexts.length > 0 |
| ) { |
| additionalContexts.push(...hookResult.additionalContexts) |
| } |
| } |
|
|
| |
| if (additionalContexts.length > 0) { |
| const contextMessage = createAttachmentMessage({ |
| type: 'hook_additional_context', |
| content: additionalContexts, |
| hookName: 'SubagentStart', |
| toolUseID: randomUUID(), |
| hookEvent: 'SubagentStart', |
| }) |
| initialMessages.push(contextMessage) |
| } |
|
|
| |
| |
| |
| |
| |
| |
| |
| const hooksAllowedForThisAgent = |
| !isRestrictedToPluginOnly('hooks') || |
| isSourceAdminTrusted(agentDefinition.source) |
| if (agentDefinition.hooks && hooksAllowedForThisAgent) { |
| registerFrontmatterHooks( |
| rootSetAppState, |
| agentId, |
| agentDefinition.hooks, |
| `agent '${agentDefinition.agentType}'`, |
| true, |
| ) |
| } |
|
|
| |
| const skillsToPreload = agentDefinition.skills ?? [] |
| if (skillsToPreload.length > 0) { |
| const allSkills = await getSkillToolCommands(getProjectRoot()) |
|
|
| |
| const validSkills: Array<{ |
| skillName: string |
| skill: (typeof allSkills)[0] & { type: 'prompt' } |
| }> = [] |
|
|
| for (const skillName of skillsToPreload) { |
| |
| |
| |
| |
| const resolvedName = resolveSkillName( |
| skillName, |
| allSkills, |
| agentDefinition, |
| ) |
| if (!resolvedName) { |
| logForDebugging( |
| `[Agent: ${agentDefinition.agentType}] Warning: Skill '${skillName}' specified in frontmatter was not found`, |
| { level: 'warn' }, |
| ) |
| continue |
| } |
|
|
| const skill = getCommand(resolvedName, allSkills) |
| if (skill.type !== 'prompt') { |
| logForDebugging( |
| `[Agent: ${agentDefinition.agentType}] Warning: Skill '${skillName}' is not a prompt-based skill`, |
| { level: 'warn' }, |
| ) |
| continue |
| } |
| validSkills.push({ skillName, skill }) |
| } |
|
|
| |
| const { formatSkillLoadingMetadata } = await import( |
| '../../utils/processUserInput/processSlashCommand.js' |
| ) |
| const loaded = await Promise.all( |
| validSkills.map(async ({ skillName, skill }) => ({ |
| skillName, |
| skill, |
| content: await skill.getPromptForCommand('', toolUseContext), |
| })), |
| ) |
| for (const { skillName, skill, content } of loaded) { |
| logForDebugging( |
| `[Agent: ${agentDefinition.agentType}] Preloaded skill '${skillName}'`, |
| ) |
|
|
| |
| const metadata = formatSkillLoadingMetadata( |
| skillName, |
| skill.progressMessage, |
| ) |
|
|
| initialMessages.push( |
| createUserMessage({ |
| content: [{ type: 'text', text: metadata }, ...content], |
| isMeta: true, |
| }), |
| ) |
| } |
| } |
|
|
| |
| const { |
| clients: mergedMcpClients, |
| tools: agentMcpTools, |
| cleanup: mcpCleanup, |
| } = await initializeAgentMcpServers( |
| agentDefinition, |
| toolUseContext.options.mcpClients, |
| ) |
|
|
| |
| |
| |
| const allTools = |
| agentMcpTools.length > 0 |
| ? uniqBy([...resolvedTools, ...agentMcpTools], 'name') |
| : resolvedTools |
|
|
| |
| const agentOptions: ToolUseContext['options'] = { |
| isNonInteractiveSession: useExactTools |
| ? toolUseContext.options.isNonInteractiveSession |
| : isAsync |
| ? true |
| : (toolUseContext.options.isNonInteractiveSession ?? false), |
| appendSystemPrompt: toolUseContext.options.appendSystemPrompt, |
| tools: allTools, |
| commands: [], |
| debug: toolUseContext.options.debug, |
| verbose: toolUseContext.options.verbose, |
| mainLoopModel: resolvedAgentModel, |
| |
| |
| |
| thinkingConfig: useExactTools |
| ? toolUseContext.options.thinkingConfig |
| : { type: 'disabled' as const }, |
| mcpClients: mergedMcpClients, |
| mcpResources: toolUseContext.options.mcpResources, |
| agentDefinitions: toolUseContext.options.agentDefinitions, |
| |
| |
| |
| |
| |
| |
| ...(useExactTools && { querySource }), |
| } |
|
|
| |
| |
| |
| const agentToolUseContext = createSubagentContext(toolUseContext, { |
| options: agentOptions, |
| agentId, |
| agentType: agentDefinition.agentType, |
| messages: initialMessages, |
| readFileState: agentReadFileState, |
| abortController: agentAbortController, |
| getAppState: agentGetAppState, |
| |
| shareSetAppState: !isAsync, |
| shareSetResponseLength: true, |
| criticalSystemReminder_EXPERIMENTAL: |
| agentDefinition.criticalSystemReminder_EXPERIMENTAL, |
| contentReplacementState, |
| }) |
|
|
| |
| if (preserveToolUseResults) { |
| agentToolUseContext.preserveToolUseResults = true |
| } |
|
|
| |
| if (onCacheSafeParams) { |
| onCacheSafeParams({ |
| systemPrompt: agentSystemPrompt, |
| userContext: resolvedUserContext, |
| systemContext: resolvedSystemContext, |
| toolUseContext: agentToolUseContext, |
| forkContextMessages: initialMessages, |
| }) |
| } |
|
|
| |
| |
| |
| void recordSidechainTranscript(initialMessages, agentId).catch(_err => |
| logForDebugging(`Failed to record sidechain transcript: ${_err}`), |
| ) |
| void writeAgentMetadata(agentId, { |
| agentType: agentDefinition.agentType, |
| ...(worktreePath && { worktreePath }), |
| ...(description && { description }), |
| }).catch(_err => logForDebugging(`Failed to write agent metadata: ${_err}`)) |
|
|
| |
| let lastRecordedUuid: UUID | null = initialMessages.at(-1)?.uuid ?? null |
|
|
| try { |
| for await (const message of query({ |
| messages: initialMessages, |
| systemPrompt: agentSystemPrompt, |
| userContext: resolvedUserContext, |
| systemContext: resolvedSystemContext, |
| canUseTool, |
| toolUseContext: agentToolUseContext, |
| querySource, |
| maxTurns: maxTurns ?? agentDefinition.maxTurns, |
| })) { |
| onQueryProgress?.() |
| |
| |
| if ( |
| message.type === 'stream_event' && |
| message.event.type === 'message_start' && |
| message.ttftMs != null |
| ) { |
| toolUseContext.pushApiMetricsEntry?.(message.ttftMs) |
| continue |
| } |
|
|
| |
| if (message.type === 'attachment') { |
| |
| if (message.attachment.type === 'max_turns_reached') { |
| logForDebugging( |
| `[Agent |
| : $ |
| { |
| agentDefinition.agentType |
| } |
| ] Reached max turns limit ($ |
| { |
| message.attachment.maxTurns |
| } |
| )`, |
| ) |
| break |
| } |
| yield message |
| continue |
| } |
|
|
| if (isRecordableMessage(message)) { |
| |
| await recordSidechainTranscript( |
| [message], |
| agentId, |
| lastRecordedUuid, |
| ).catch(err => |
| logForDebugging(`Failed to record sidechain transcript: ${err}`), |
| ) |
| if (message.type !== 'progress') { |
| lastRecordedUuid = message.uuid |
| } |
| yield message |
| } |
| } |
|
|
| if (agentAbortController.signal.aborted) { |
| throw new AbortError() |
| } |
|
|
| |
| if (isBuiltInAgent(agentDefinition) && agentDefinition.callback) { |
| agentDefinition.callback() |
| } |
| } finally { |
| |
| await mcpCleanup() |
| |
| if (agentDefinition.hooks) { |
| clearSessionHooks(rootSetAppState, agentId) |
| } |
| |
| if (feature('PROMPT_CACHE_BREAK_DETECTION')) { |
| cleanupAgentTracking(agentId) |
| } |
| |
| agentToolUseContext.readFileState.clear() |
| |
| initialMessages.length = 0 |
| |
| unregisterPerfettoAgent(agentId) |
| |
| clearAgentTranscriptSubdir(agentId) |
| |
| |
| |
| |
| rootSetAppState(prev => { |
| if (!(agentId in prev.todos)) return prev |
| const { [agentId]: _removed, ...todos } = prev.todos |
| return { ...prev, todos } |
| }) |
| |
| |
| |
| killShellTasksForAgent(agentId, toolUseContext.getAppState, rootSetAppState) |
| |
| if (feature('MONITOR_TOOL')) { |
| const mcpMod = |
| require('../../tasks/MonitorMcpTask/MonitorMcpTask.js') as typeof import('../../tasks/MonitorMcpTask/MonitorMcpTask.js') |
| mcpMod.killMonitorMcpTasksForAgent( |
| agentId, |
| toolUseContext.getAppState, |
| rootSetAppState, |
| ) |
| } |
| |
| } |
| } |
|
|
| |
| |
| |
| |
| export function filterIncompleteToolCalls(messages: Message[]): Message[] { |
| |
| const toolUseIdsWithResults = new Set<string>() |
|
|
| for (const message of messages) { |
| if (message?.type === 'user') { |
| const userMessage = message as UserMessage |
| const content = userMessage.message.content |
| if (Array.isArray(content)) { |
| for (const block of content) { |
| if (block.type === 'tool_result' && block.tool_use_id) { |
| toolUseIdsWithResults.add(block.tool_use_id) |
| } |
| } |
| } |
| } |
| } |
|
|
| |
| return messages.filter(message => { |
| if (message?.type === 'assistant') { |
| const assistantMessage = message as AssistantMessage |
| const content = assistantMessage.message.content |
| if (Array.isArray(content)) { |
| |
| const hasIncompleteToolCall = content.some( |
| block => |
| block.type === 'tool_use' && |
| block.id && |
| !toolUseIdsWithResults.has(block.id), |
| ) |
| |
| return !hasIncompleteToolCall |
| } |
| } |
| |
| return true |
| }) |
| } |
|
|
| async function getAgentSystemPrompt( |
| agentDefinition: AgentDefinition, |
| toolUseContext: Pick<ToolUseContext, 'options'>, |
| resolvedAgentModel: string, |
| additionalWorkingDirectories: string[], |
| resolvedTools: readonly Tool[], |
| ): Promise<string[]> { |
| const enabledToolNames = new Set(resolvedTools.map(t => t.name)) |
| try { |
| const agentPrompt = agentDefinition.getSystemPrompt({ toolUseContext }) |
| const prompts = [agentPrompt] |
|
|
| return await enhanceSystemPromptWithEnvDetails( |
| prompts, |
| resolvedAgentModel, |
| additionalWorkingDirectories, |
| enabledToolNames, |
| ) |
| } catch (_error) { |
| return enhanceSystemPromptWithEnvDetails( |
| [DEFAULT_AGENT_PROMPT], |
| resolvedAgentModel, |
| additionalWorkingDirectories, |
| enabledToolNames, |
| ) |
| } |
| } |
|
|
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| function resolveSkillName( |
| skillName: string, |
| allSkills: Command[], |
| agentDefinition: AgentDefinition, |
| ): string | null { |
| |
| if (hasCommand(skillName, allSkills)) { |
| return skillName |
| } |
|
|
| |
| |
| const pluginPrefix = agentDefinition.agentType.split(':')[0] |
| if (pluginPrefix) { |
| const qualifiedName = `${pluginPrefix}:${skillName}` |
| if (hasCommand(qualifiedName, allSkills)) { |
| return qualifiedName |
| } |
| } |
|
|
| |
| const suffix = `:${skillName}` |
| const match = allSkills.find(cmd => cmd.name.endsWith(suffix)) |
| if (match) { |
| return match.name |
| } |
|
|
| return null |
| } |
|
|