Something went wrong. Try again.
source dump of claude code forked from oppi.li/claude-code
Something went wrong. Try again.
9.3 kB · 265 lines
TypeScript
at main
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266import { promises as fsp } from 'fs'import { getSdkAgentProgressSummariesEnabled } from '../../bootstrap/state.js'import { getSystemPrompt } from '../../constants/prompts.js'import { isCoordinatorMode } from '../../coordinator/coordinatorMode.js'import type { CanUseToolFn } from '../../hooks/useCanUseTool.js'import type { ToolUseContext } from '../../Tool.js'import { registerAsyncAgent } from '../../tasks/LocalAgentTask/LocalAgentTask.js'import { assembleToolPool } from '../../tools.js'import { asAgentId } from '../../types/ids.js'import { runWithAgentContext } from '../../utils/agentContext.js'import { runWithCwdOverride } from '../../utils/cwd.js'import { logForDebugging } from '../../utils/debug.js'import { createUserMessage, filterOrphanedThinkingOnlyMessages, filterUnresolvedToolUses, filterWhitespaceOnlyAssistantMessages,} from '../../utils/messages.js'import { getAgentModel } from '../../utils/model/agent.js'import { getQuerySourceForAgent } from '../../utils/promptCategory.js'import { getAgentTranscript, readAgentMetadata,} from '../../utils/sessionStorage.js'import { buildEffectiveSystemPrompt } from '../../utils/systemPrompt.js'import type { SystemPrompt } from '../../utils/systemPromptType.js'import { getTaskOutputPath } from '../../utils/task/diskOutput.js'import { getParentSessionId } from '../../utils/teammate.js'import { reconstructForSubagentResume } from '../../utils/toolResultStorage.js'import { runAsyncAgentLifecycle } from './agentToolUtils.js'import { GENERAL_PURPOSE_AGENT } from './built-in/generalPurposeAgent.js'import { FORK_AGENT, isForkSubagentEnabled } from './forkSubagent.js'import type { AgentDefinition } from './loadAgentsDir.js'import { isBuiltInAgent } from './loadAgentsDir.js'import { runAgent } from './runAgent.js'
export type ResumeAgentResult = { agentId: string description: string outputFile: string}export async function resumeAgentBackground({ agentId, prompt, toolUseContext, canUseTool, invokingRequestId,}: { agentId: string prompt: string toolUseContext: ToolUseContext canUseTool: CanUseToolFn invokingRequestId?: string}): Promise<ResumeAgentResult> { const startTime = Date.now() const appState = toolUseContext.getAppState() // In-process teammates get a no-op setAppState; setAppStateForTasks // reaches the root store so task registration/progress/kill stay visible. const rootSetAppState = toolUseContext.setAppStateForTasks ?? toolUseContext.setAppState const permissionMode = appState.toolPermissionContext.mode
const [transcript, meta] = await Promise.all([ getAgentTranscript(asAgentId(agentId)), readAgentMetadata(asAgentId(agentId)), ]) if (!transcript) { throw new Error(`No transcript found for agent ID: ${agentId}`) } const resumedMessages = filterWhitespaceOnlyAssistantMessages( filterOrphanedThinkingOnlyMessages( filterUnresolvedToolUses(transcript.messages), ), ) const resumedReplacementState = reconstructForSubagentResume( toolUseContext.contentReplacementState, resumedMessages, transcript.contentReplacements, ) // Best-effort: if the original worktree was removed externally, fall back // to parent cwd rather than crashing on chdir later. const resumedWorktreePath = meta?.worktreePath ? await fsp.stat(meta.worktreePath).then( s => (s.isDirectory() ? meta.worktreePath : undefined), () => { logForDebugging( `Resumed worktree ${meta.worktreePath} no longer exists; falling back to parent cwd`, ) return undefined }, ) : undefined if (resumedWorktreePath) { // Bump mtime so stale-worktree cleanup doesn't delete a just-resumed worktree (#22355) const now = new Date() await fsp.utimes(resumedWorktreePath, now, now) }
// Skip filterDeniedAgents re-gating — original spawn already passed permission checks let selectedAgent: AgentDefinition let isResumedFork = false if (meta?.agentType === FORK_AGENT.agentType) { selectedAgent = FORK_AGENT isResumedFork = true } else if (meta?.agentType) { const found = toolUseContext.options.agentDefinitions.activeAgents.find( a => a.agentType === meta.agentType, ) selectedAgent = found ?? GENERAL_PURPOSE_AGENT } else { selectedAgent = GENERAL_PURPOSE_AGENT }
const uiDescription = meta?.description ?? '(resumed)'
let forkParentSystemPrompt: SystemPrompt | undefined if (isResumedFork) { if (toolUseContext.renderedSystemPrompt) { forkParentSystemPrompt = toolUseContext.renderedSystemPrompt } else { const mainThreadAgentDefinition = appState.agent ? appState.agentDefinitions.activeAgents.find( a => a.agentType === appState.agent, ) : undefined const additionalWorkingDirectories = Array.from( appState.toolPermissionContext.additionalWorkingDirectories.keys(), ) const defaultSystemPrompt = await getSystemPrompt( toolUseContext.options.tools, toolUseContext.options.mainLoopModel, additionalWorkingDirectories, toolUseContext.options.mcpClients, ) forkParentSystemPrompt = buildEffectiveSystemPrompt({ mainThreadAgentDefinition, toolUseContext, customSystemPrompt: toolUseContext.options.customSystemPrompt, defaultSystemPrompt, appendSystemPrompt: toolUseContext.options.appendSystemPrompt, }) } if (!forkParentSystemPrompt) { throw new Error( 'Cannot resume fork agent: unable to reconstruct parent system prompt', ) } }
// Resolve model for analytics metadata (runAgent resolves its own internally) const resolvedAgentModel = getAgentModel( selectedAgent.model, toolUseContext.options.mainLoopModel, undefined, permissionMode, )
const workerPermissionContext = { ...appState.toolPermissionContext, mode: selectedAgent.permissionMode ?? 'acceptEdits', } const workerTools = isResumedFork ? toolUseContext.options.tools : assembleToolPool(workerPermissionContext, appState.mcp.tools)
const runAgentParams: Parameters<typeof runAgent>[0] = { agentDefinition: selectedAgent, promptMessages: [ ...resumedMessages, createUserMessage({ content: prompt }), ], toolUseContext, canUseTool, isAsync: true, querySource: getQuerySourceForAgent( selectedAgent.agentType, isBuiltInAgent(selectedAgent), ), model: undefined, // Fork resume: pass parent's system prompt (cache-identical prefix). // Non-fork: undefined → runAgent recomputes under wrapWithCwd so // getCwd() sees resumedWorktreePath. override: isResumedFork ? { systemPrompt: forkParentSystemPrompt } : undefined, availableTools: workerTools, // Transcript already contains the parent context slice from the // original fork. Re-supplying it would cause duplicate tool_use IDs. forkContextMessages: undefined, ...(isResumedFork && { useExactTools: true }), // Re-persist so metadata survives runAgent's writeAgentMetadata overwrite worktreePath: resumedWorktreePath, description: meta?.description, contentReplacementState: resumedReplacementState, }
// Skip name-registry write — original entry persists from the initial spawn const agentBackgroundTask = registerAsyncAgent({ agentId, description: uiDescription, prompt, selectedAgent, setAppState: rootSetAppState, toolUseId: toolUseContext.toolUseId, })
const metadata = { prompt, resolvedAgentModel, isBuiltInAgent: isBuiltInAgent(selectedAgent), startTime, agentType: selectedAgent.agentType, isAsync: true, }
const asyncAgentContext = { agentId, parentSessionId: getParentSessionId(), agentType: 'subagent' as const, subagentName: selectedAgent.agentType, isBuiltIn: isBuiltInAgent(selectedAgent), invokingRequestId, invocationKind: 'resume' as const, invocationEmitted: false, }
const wrapWithCwd = <T>(fn: () => T): T => resumedWorktreePath ? runWithCwdOverride(resumedWorktreePath, fn) : fn()
void runWithAgentContext(asyncAgentContext, () => wrapWithCwd(() => runAsyncAgentLifecycle({ taskId: agentBackgroundTask.agentId, abortController: agentBackgroundTask.abortController!, makeStream: onCacheSafeParams => runAgent({ ...runAgentParams, override: { ...runAgentParams.override, agentId: asAgentId(agentBackgroundTask.agentId), abortController: agentBackgroundTask.abortController!, }, onCacheSafeParams, }), metadata, description: uiDescription, toolUseContext, rootSetAppState, agentIdForCleanup: agentId, enableSummarization: isCoordinatorMode() || isForkSubagentEnabled() || getSdkAgentProgressSummariesEnabled(), getWorktreeResult: async () => resumedWorktreePath ? { worktreePath: resumedWorktreePath } : {}, }), ), )
return { agentId, description: uiDescription, outputFile: getTaskOutputPath(agentId), }}