the claude code sourcemaps leaked march 31
0
fork

Configure Feed

Select the types of activity you want to include in your feed.

at main 265 lines 9.3 kB view raw
1import { promises as fsp } from 'fs' 2import { getSdkAgentProgressSummariesEnabled } from '../../bootstrap/state.js' 3import { getSystemPrompt } from '../../constants/prompts.js' 4import { isCoordinatorMode } from '../../coordinator/coordinatorMode.js' 5import type { CanUseToolFn } from '../../hooks/useCanUseTool.js' 6import type { ToolUseContext } from '../../Tool.js' 7import { registerAsyncAgent } from '../../tasks/LocalAgentTask/LocalAgentTask.js' 8import { assembleToolPool } from '../../tools.js' 9import { asAgentId } from '../../types/ids.js' 10import { runWithAgentContext } from '../../utils/agentContext.js' 11import { runWithCwdOverride } from '../../utils/cwd.js' 12import { logForDebugging } from '../../utils/debug.js' 13import { 14 createUserMessage, 15 filterOrphanedThinkingOnlyMessages, 16 filterUnresolvedToolUses, 17 filterWhitespaceOnlyAssistantMessages, 18} from '../../utils/messages.js' 19import { getAgentModel } from '../../utils/model/agent.js' 20import { getQuerySourceForAgent } from '../../utils/promptCategory.js' 21import { 22 getAgentTranscript, 23 readAgentMetadata, 24} from '../../utils/sessionStorage.js' 25import { buildEffectiveSystemPrompt } from '../../utils/systemPrompt.js' 26import type { SystemPrompt } from '../../utils/systemPromptType.js' 27import { getTaskOutputPath } from '../../utils/task/diskOutput.js' 28import { getParentSessionId } from '../../utils/teammate.js' 29import { reconstructForSubagentResume } from '../../utils/toolResultStorage.js' 30import { runAsyncAgentLifecycle } from './agentToolUtils.js' 31import { GENERAL_PURPOSE_AGENT } from './built-in/generalPurposeAgent.js' 32import { FORK_AGENT, isForkSubagentEnabled } from './forkSubagent.js' 33import type { AgentDefinition } from './loadAgentsDir.js' 34import { isBuiltInAgent } from './loadAgentsDir.js' 35import { runAgent } from './runAgent.js' 36 37export type ResumeAgentResult = { 38 agentId: string 39 description: string 40 outputFile: string 41} 42export async function resumeAgentBackground({ 43 agentId, 44 prompt, 45 toolUseContext, 46 canUseTool, 47 invokingRequestId, 48}: { 49 agentId: string 50 prompt: string 51 toolUseContext: ToolUseContext 52 canUseTool: CanUseToolFn 53 invokingRequestId?: string 54}): Promise<ResumeAgentResult> { 55 const startTime = Date.now() 56 const appState = toolUseContext.getAppState() 57 // In-process teammates get a no-op setAppState; setAppStateForTasks 58 // reaches the root store so task registration/progress/kill stay visible. 59 const rootSetAppState = 60 toolUseContext.setAppStateForTasks ?? toolUseContext.setAppState 61 const permissionMode = appState.toolPermissionContext.mode 62 63 const [transcript, meta] = await Promise.all([ 64 getAgentTranscript(asAgentId(agentId)), 65 readAgentMetadata(asAgentId(agentId)), 66 ]) 67 if (!transcript) { 68 throw new Error(`No transcript found for agent ID: ${agentId}`) 69 } 70 const resumedMessages = filterWhitespaceOnlyAssistantMessages( 71 filterOrphanedThinkingOnlyMessages( 72 filterUnresolvedToolUses(transcript.messages), 73 ), 74 ) 75 const resumedReplacementState = reconstructForSubagentResume( 76 toolUseContext.contentReplacementState, 77 resumedMessages, 78 transcript.contentReplacements, 79 ) 80 // Best-effort: if the original worktree was removed externally, fall back 81 // to parent cwd rather than crashing on chdir later. 82 const resumedWorktreePath = meta?.worktreePath 83 ? await fsp.stat(meta.worktreePath).then( 84 s => (s.isDirectory() ? meta.worktreePath : undefined), 85 () => { 86 logForDebugging( 87 `Resumed worktree ${meta.worktreePath} no longer exists; falling back to parent cwd`, 88 ) 89 return undefined 90 }, 91 ) 92 : undefined 93 if (resumedWorktreePath) { 94 // Bump mtime so stale-worktree cleanup doesn't delete a just-resumed worktree (#22355) 95 const now = new Date() 96 await fsp.utimes(resumedWorktreePath, now, now) 97 } 98 99 // Skip filterDeniedAgents re-gating — original spawn already passed permission checks 100 let selectedAgent: AgentDefinition 101 let isResumedFork = false 102 if (meta?.agentType === FORK_AGENT.agentType) { 103 selectedAgent = FORK_AGENT 104 isResumedFork = true 105 } else if (meta?.agentType) { 106 const found = toolUseContext.options.agentDefinitions.activeAgents.find( 107 a => a.agentType === meta.agentType, 108 ) 109 selectedAgent = found ?? GENERAL_PURPOSE_AGENT 110 } else { 111 selectedAgent = GENERAL_PURPOSE_AGENT 112 } 113 114 const uiDescription = meta?.description ?? '(resumed)' 115 116 let forkParentSystemPrompt: SystemPrompt | undefined 117 if (isResumedFork) { 118 if (toolUseContext.renderedSystemPrompt) { 119 forkParentSystemPrompt = toolUseContext.renderedSystemPrompt 120 } else { 121 const mainThreadAgentDefinition = appState.agent 122 ? appState.agentDefinitions.activeAgents.find( 123 a => a.agentType === appState.agent, 124 ) 125 : undefined 126 const additionalWorkingDirectories = Array.from( 127 appState.toolPermissionContext.additionalWorkingDirectories.keys(), 128 ) 129 const defaultSystemPrompt = await getSystemPrompt( 130 toolUseContext.options.tools, 131 toolUseContext.options.mainLoopModel, 132 additionalWorkingDirectories, 133 toolUseContext.options.mcpClients, 134 ) 135 forkParentSystemPrompt = buildEffectiveSystemPrompt({ 136 mainThreadAgentDefinition, 137 toolUseContext, 138 customSystemPrompt: toolUseContext.options.customSystemPrompt, 139 defaultSystemPrompt, 140 appendSystemPrompt: toolUseContext.options.appendSystemPrompt, 141 }) 142 } 143 if (!forkParentSystemPrompt) { 144 throw new Error( 145 'Cannot resume fork agent: unable to reconstruct parent system prompt', 146 ) 147 } 148 } 149 150 // Resolve model for analytics metadata (runAgent resolves its own internally) 151 const resolvedAgentModel = getAgentModel( 152 selectedAgent.model, 153 toolUseContext.options.mainLoopModel, 154 undefined, 155 permissionMode, 156 ) 157 158 const workerPermissionContext = { 159 ...appState.toolPermissionContext, 160 mode: selectedAgent.permissionMode ?? 'acceptEdits', 161 } 162 const workerTools = isResumedFork 163 ? toolUseContext.options.tools 164 : assembleToolPool(workerPermissionContext, appState.mcp.tools) 165 166 const runAgentParams: Parameters<typeof runAgent>[0] = { 167 agentDefinition: selectedAgent, 168 promptMessages: [ 169 ...resumedMessages, 170 createUserMessage({ content: prompt }), 171 ], 172 toolUseContext, 173 canUseTool, 174 isAsync: true, 175 querySource: getQuerySourceForAgent( 176 selectedAgent.agentType, 177 isBuiltInAgent(selectedAgent), 178 ), 179 model: undefined, 180 // Fork resume: pass parent's system prompt (cache-identical prefix). 181 // Non-fork: undefined → runAgent recomputes under wrapWithCwd so 182 // getCwd() sees resumedWorktreePath. 183 override: isResumedFork 184 ? { systemPrompt: forkParentSystemPrompt } 185 : undefined, 186 availableTools: workerTools, 187 // Transcript already contains the parent context slice from the 188 // original fork. Re-supplying it would cause duplicate tool_use IDs. 189 forkContextMessages: undefined, 190 ...(isResumedFork && { useExactTools: true }), 191 // Re-persist so metadata survives runAgent's writeAgentMetadata overwrite 192 worktreePath: resumedWorktreePath, 193 description: meta?.description, 194 contentReplacementState: resumedReplacementState, 195 } 196 197 // Skip name-registry write — original entry persists from the initial spawn 198 const agentBackgroundTask = registerAsyncAgent({ 199 agentId, 200 description: uiDescription, 201 prompt, 202 selectedAgent, 203 setAppState: rootSetAppState, 204 toolUseId: toolUseContext.toolUseId, 205 }) 206 207 const metadata = { 208 prompt, 209 resolvedAgentModel, 210 isBuiltInAgent: isBuiltInAgent(selectedAgent), 211 startTime, 212 agentType: selectedAgent.agentType, 213 isAsync: true, 214 } 215 216 const asyncAgentContext = { 217 agentId, 218 parentSessionId: getParentSessionId(), 219 agentType: 'subagent' as const, 220 subagentName: selectedAgent.agentType, 221 isBuiltIn: isBuiltInAgent(selectedAgent), 222 invokingRequestId, 223 invocationKind: 'resume' as const, 224 invocationEmitted: false, 225 } 226 227 const wrapWithCwd = <T>(fn: () => T): T => 228 resumedWorktreePath ? runWithCwdOverride(resumedWorktreePath, fn) : fn() 229 230 void runWithAgentContext(asyncAgentContext, () => 231 wrapWithCwd(() => 232 runAsyncAgentLifecycle({ 233 taskId: agentBackgroundTask.agentId, 234 abortController: agentBackgroundTask.abortController!, 235 makeStream: onCacheSafeParams => 236 runAgent({ 237 ...runAgentParams, 238 override: { 239 ...runAgentParams.override, 240 agentId: asAgentId(agentBackgroundTask.agentId), 241 abortController: agentBackgroundTask.abortController!, 242 }, 243 onCacheSafeParams, 244 }), 245 metadata, 246 description: uiDescription, 247 toolUseContext, 248 rootSetAppState, 249 agentIdForCleanup: agentId, 250 enableSummarization: 251 isCoordinatorMode() || 252 isForkSubagentEnabled() || 253 getSdkAgentProgressSummariesEnabled(), 254 getWorktreeResult: async () => 255 resumedWorktreePath ? { worktreePath: resumedWorktreePath } : {}, 256 }), 257 ), 258 ) 259 260 return { 261 agentId, 262 description: uiDescription, 263 outputFile: getTaskOutputPath(agentId), 264 } 265}