import { APIUserAbortError } from '@anthropic-ai/sdk' import { getEmptyToolPermissionContext } from '../Tool.js' import type { Message } from '../types/message.js' import { logForDebugging } from '../utils/debug.js' import { createUserMessage, getAssistantMessageText, } from '../utils/messages.js' import { getSmallFastModel } from '../utils/model/model.js' import { asSystemPrompt } from '../utils/systemPromptType.js' import { queryModelWithoutStreaming } from './api/claude.js' import { getSessionMemoryContent } from './SessionMemory/sessionMemoryUtils.js' // Recap only needs recent context — truncate to avoid "prompt too long" on // large sessions. 30 messages ≈ ~15 exchanges, plenty for "where we left off." const RECENT_MESSAGE_WINDOW = 30 function buildAwaySummaryPrompt(memory: string | null): string { const memoryBlock = memory ? `Session memory (broader context):\n${memory}\n\n` : '' return `${memoryBlock}The user stepped away and is coming back. Write exactly 1-3 short sentences. Start by stating the high-level task — what they are building or debugging, not implementation details. Next: the concrete next step. Skip status reports and commit recaps.` } /** * Generates a short session recap for the "while you were away" card. * Returns null on abort, empty transcript, or error. */ export async function generateAwaySummary( messages: readonly Message[], signal: AbortSignal, ): Promise { if (messages.length === 0) { return null } try { const memory = await getSessionMemoryContent() const recent = messages.slice(-RECENT_MESSAGE_WINDOW) recent.push(createUserMessage({ content: buildAwaySummaryPrompt(memory) })) const response = await queryModelWithoutStreaming({ messages: recent, systemPrompt: asSystemPrompt([]), thinkingConfig: { type: 'disabled' }, tools: [], signal, options: { getToolPermissionContext: async () => getEmptyToolPermissionContext(), model: getSmallFastModel(), toolChoice: undefined, isNonInteractiveSession: false, hasAppendSystemPrompt: false, agents: [], querySource: 'away_summary', mcpTools: [], skipCacheWrite: true, }, }) if (response.isApiErrorMessage) { logForDebugging( `[awaySummary] API error: ${getAssistantMessageText(response)}`, ) return null } return getAssistantMessageText(response) } catch (err) { if (err instanceof APIUserAbortError || signal.aborted) { return null } logForDebugging(`[awaySummary] generation failed: ${err}`) return null } }