Download src/services/AgentSummary/agentSummary.ts from chenbhao/codev: direct link, hf CLI and curl.
- Browser
- Download file 6.41 kB
-
https://huggingface.co/chenbhao/codev/resolve/main/src/services/AgentSummary/agentSummary.ts
- Command line
-
hf download hf://chenbhao/codev/src/services/AgentSummary/agentSummary.ts
-
curl -L -o agentSummary.ts https://huggingface.co/chenbhao/codev/resolve/main/src/services/AgentSummary/agentSummary.ts
6.41 kB
| /** | |
| * 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 } | |
| } | |