diff --git a/src/openrouter/reviewer.test.ts b/src/openrouter/reviewer.test.ts index febc38ebd..7f9976a88 100644 --- a/src/openrouter/reviewer.test.ts +++ b/src/openrouter/reviewer.test.ts @@ -194,7 +194,7 @@ describe('summarizeToolUsage', () => { // ─── extractUserQuestion ──────────────────────────────────────────────────── describe('extractUserQuestion', () => { - it('extracts the first real user message', () => { + it('extracts the most recent user message', () => { const messages: ChatMessage[] = [ { role: 'system', content: 'You are an assistant' }, { role: 'user', content: 'What is the weather in Milan?' }, @@ -229,6 +229,26 @@ describe('extractUserQuestion', () => { it('returns fallback for empty messages', () => { expect(extractUserQuestion([])).toBe('(Unknown question)'); }); + + it('picks the latest user question in multi-turn conversations', () => { + const messages: ChatMessage[] = [ + { role: 'system', content: 'You are an assistant' }, + { role: 'user', content: 'What is the capital of France?' }, + { role: 'assistant', content: 'Paris.' }, + { role: 'user', content: 'Now read the README.md from my repo and summarize it' }, + { role: 'assistant', content: null, tool_calls: [{ id: 'tc1', type: 'function' as const, function: { name: 'github_read_file', arguments: '{}' } }] }, + { role: 'tool', content: '# README content...', tool_call_id: 'tc1' }, + ]; + expect(extractUserQuestion(messages)).toBe('Now read the README.md from my repo and summarize it'); + }); + + it('skips file injection blocks from Phase 7B.4', () => { + const messages: ChatMessage[] = [ + { role: 'user', content: 'Read and summarize the project' }, + { role: 'user', content: '[FILE: owner/repo/README.md]\n# Contents here...' }, + ]; + expect(extractUserQuestion(messages)).toBe('Read and summarize the project'); + }); }); // ─── buildReviewMessages ──────────────────────────────────────────────────── diff --git a/src/openrouter/reviewer.ts b/src/openrouter/reviewer.ts index f1792ab12..8b124638a 100644 --- a/src/openrouter/reviewer.ts +++ b/src/openrouter/reviewer.ts @@ -170,16 +170,19 @@ export function summarizeToolUsage(messages: readonly ChatMessage[]): string { } /** - * Extract the original user question from the conversation messages. - * Skips system messages and planning prompts. + * Extract the most recent user question from the conversation messages. + * Iterates backwards to find the latest user message, skipping injected phase prompts. */ export function extractUserQuestion(messages: readonly ChatMessage[]): string { - for (const msg of messages) { + for (let i = messages.length - 1; i >= 0; i--) { + const msg = messages[i]; if (msg.role !== 'user') continue; const text = typeof msg.content === 'string' ? msg.content : ''; // Skip injected phase prompts if (text.includes('[PLANNING PHASE]') || text.includes('[REVIEW PHASE]')) continue; if (text.includes('STRUCTURED_PLAN_PROMPT') || text.startsWith('Before starting,')) continue; + // Skip file injection blocks (Phase 7B.4) + if (text.startsWith('[FILE:') || text.startsWith('Pre-loaded file contents')) continue; if (text.length > 10) return text; } return '(Unknown question)'; diff --git a/src/telegram/handler.ts b/src/telegram/handler.ts index fe2033307..4e838eef8 100644 --- a/src/telegram/handler.ts +++ b/src/telegram/handler.ts @@ -2222,8 +2222,11 @@ export class TelegramHandler { const complexity = classifyTaskComplexity(messageText, fullHistory.length); // Route simple queries to fast models when user is on default 'auto' (Phase 7B.2) + // Use message-only complexity (ignoring conversation length) so that simple messages + // in long conversations still get routed to fast models. const autoRouteEnabled = await this.storage.getUserAutoRoute(userId); - const routing = routeByComplexity(modelAlias, complexity, autoRouteEnabled); + const routingComplexity = classifyTaskComplexity(messageText, 0); + const routing = routeByComplexity(modelAlias, routingComplexity, autoRouteEnabled); if (routing.wasRouted) { console.log(`[ModelRouter] ${routing.reason} (user=${userId})`); modelAlias = routing.modelAlias;