Something went wrong. Try again.
source dump of claude code forked from oppi.li/claude-code
Something went wrong. Try again.
6.4 kB · 179 lines
TypeScript
at main
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180/** * Periodic background summarization for coordinator mode sub-agents. * * Forks the sub-agent's conversation every ~30s using runForkedAgent() * to generate a 1-2 sentence progress summary. The summary is stored * on AgentProgress for UI display. * * Cache sharing: uses the same CacheSafeParams as the parent agent * to share the prompt cache. Tools are kept in the request for cache * key matching but denied via canUseTool callback. */
import type { TaskContext } from '../../Task.js'import { updateAgentSummary } from '../../tasks/LocalAgentTask/LocalAgentTask.js'import { filterIncompleteToolCalls } from '../../tools/AgentTool/runAgent.js'import type { AgentId } from '../../types/ids.js'import { logForDebugging } from '../../utils/debug.js'import { type CacheSafeParams, runForkedAgent,} from '../../utils/forkedAgent.js'import { logError } from '../../utils/log.js'import { createUserMessage } from '../../utils/messages.js'import { getAgentTranscript } from '../../utils/sessionStorage.js'
const SUMMARY_INTERVAL_MS = 30_000
function buildSummaryPrompt(previousSummary: string | null): string { const prevLine = previousSummary ? `\nPrevious: "${previousSummary}" — say something NEW.\n` : ''
return `Describe your most recent action in 3-5 words using present tense (-ing). Name the file or function, not the branch. Do not use tools.${prevLine}Good: "Reading runAgent.ts"Good: "Fixing null check in validate.ts"Good: "Running auth module tests"Good: "Adding retry logic to fetchUser"
Bad (past tense): "Analyzed the branch diff"Bad (too vague): "Investigating the issue"Bad (too long): "Reviewing full branch diff and AgentTool.tsx integration"Bad (branch name): "Analyzed adam/background-summary branch diff"`}
export function startAgentSummarization( taskId: string, agentId: AgentId, cacheSafeParams: CacheSafeParams, setAppState: TaskContext['setAppState'],): { stop: () => void } { // Drop forkContextMessages from the closure — runSummary rebuilds it each // tick from getAgentTranscript(). Without this, the original fork messages // (passed from AgentTool.tsx) are pinned for the lifetime of the timer. const { forkContextMessages: _drop, ...baseParams } = cacheSafeParams let summaryAbortController: AbortController | null = null let timeoutId: ReturnType<typeof setTimeout> | null = null let stopped = false let previousSummary: string | null = null
async function runSummary(): Promise<void> { if (stopped) return
logForDebugging(`[AgentSummary] Timer fired for agent ${agentId}`)
try { // Read current messages from transcript const transcript = await getAgentTranscript(agentId) if (!transcript || transcript.messages.length < 3) { // Not enough context yet — finally block will schedule next attempt logForDebugging( `[AgentSummary] Skipping summary for ${taskId}: not enough messages (${transcript?.messages.length ?? 0})`, ) return }
// Filter to clean message state const cleanMessages = filterIncompleteToolCalls(transcript.messages)
// Build fork params with current messages const forkParams: CacheSafeParams = { ...baseParams, forkContextMessages: cleanMessages, }
logForDebugging( `[AgentSummary] Forking for summary, ${cleanMessages.length} messages in context`, )
// Create abort controller for this summary summaryAbortController = new AbortController()
// Deny tools via callback, NOT by passing tools:[] - that busts cache const canUseTool = async () => ({ behavior: 'deny' as const, message: 'No tools needed for summary', decisionReason: { type: 'other' as const, reason: 'summary only' }, })
// DO NOT set maxOutputTokens here. The fork piggybacks on the main // thread's prompt cache by sending identical cache-key params (system, // tools, model, messages prefix, thinking config). Setting maxOutputTokens // would clamp budget_tokens, creating a thinking config mismatch that // invalidates the cache. // // ContentReplacementState is cloned by default in createSubagentContext // from forkParams.toolUseContext (the subagent's LIVE state captured at // onCacheSafeParams time). No explicit override needed. const result = await runForkedAgent({ promptMessages: [ createUserMessage({ content: buildSummaryPrompt(previousSummary) }), ], cacheSafeParams: forkParams, canUseTool, querySource: 'agent_summary', forkLabel: 'agent_summary', overrides: { abortController: summaryAbortController }, skipTranscript: true, })
if (stopped) return
// Extract summary text from result for (const msg of result.messages) { if (msg.type !== 'assistant') continue // Skip API error messages if (msg.isApiErrorMessage) { logForDebugging( `[AgentSummary] Skipping API error message for ${taskId}`, ) continue } const textBlock = msg.message.content.find(b => b.type === 'text') if (textBlock?.type === 'text' && textBlock.text.trim()) { const summaryText = textBlock.text.trim() logForDebugging( `[AgentSummary] Summary result for ${taskId}: ${summaryText}`, ) previousSummary = summaryText updateAgentSummary(taskId, summaryText, setAppState) break } } } catch (e) { if (!stopped && e instanceof Error) { logError(e) } } finally { summaryAbortController = null // Reset timer on completion (not initiation) to prevent overlapping summaries if (!stopped) { scheduleNext() } } }
function scheduleNext(): void { if (stopped) return timeoutId = setTimeout(runSummary, SUMMARY_INTERVAL_MS) }
function stop(): void { logForDebugging(`[AgentSummary] Stopping summarization for ${taskId}`) stopped = true if (timeoutId) { clearTimeout(timeoutId) timeoutId = null } if (summaryAbortController) { summaryAbortController.abort() summaryAbortController = null } }
// Start the first timer scheduleNext()
return { stop }}