Skip to content

Commit 35500b2

Browse files
committed
fix: include initial ask in condense summarization
- Changed slice in summarizeConversation from (1, -N) to (0, -N) to include first message - Updated SUMMARY_PROMPT to explicitly capture initial request - Added tests to verify initial ask is included in summarization input - Fixes #8293 where summaries lost context of original task
1 parent d3d0967 commit 35500b2

File tree

4 files changed

+105
-16
lines changed

4 files changed

+105
-16
lines changed

.review/pr-8274

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Subproject commit e46929b8d8add0cd3c412d69f8ac882c405a4ba9

src/core/condense/__tests__/condense.spec.ts

Lines changed: 84 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,14 +3,15 @@
33
import { Anthropic } from "@anthropic-ai/sdk"
44
import type { ModelInfo } from "@roo-code/types"
55
import { TelemetryService } from "@roo-code/telemetry"
6+
import { vi } from "vitest"
67

78
import { BaseProvider } from "../../../api/providers/base-provider"
89
import { ApiMessage } from "../../task-persistence/apiMessages"
910
import { summarizeConversation, getMessagesSinceLastSummary, N_MESSAGES_TO_KEEP } from "../index"
1011

1112
// Create a mock ApiHandler for testing
1213
class MockApiHandler extends BaseProvider {
13-
createMessage(): any {
14+
createMessage(systemPrompt?: string, messages?: any[]): any {
1415
// Mock implementation for testing - returns an async iterable stream
1516
const mockStream = {
1617
async *[Symbol.asyncIterator]() {
@@ -176,7 +177,7 @@ describe("Condense", () => {
176177
it("should handle empty summary from API gracefully", async () => {
177178
// Mock handler that returns empty summary
178179
class EmptyMockApiHandler extends MockApiHandler {
179-
override createMessage(): any {
180+
override createMessage(systemPrompt?: string, messages?: any[]): any {
180181
const mockStream = {
181182
async *[Symbol.asyncIterator]() {
182183
yield { type: "text", text: "" }
@@ -204,6 +205,87 @@ describe("Condense", () => {
204205
expect(result.messages).toEqual(messages)
205206
expect(result.cost).toBeGreaterThan(0)
206207
})
208+
209+
it("should include the initial ask in the summarization input", async () => {
210+
const initialAsk = "Please help me implement a new authentication system"
211+
const messages: ApiMessage[] = [
212+
{ role: "user", content: initialAsk },
213+
{ role: "assistant", content: "I'll help you implement an authentication system" },
214+
{ role: "user", content: "Let's start with JWT tokens" },
215+
{ role: "assistant", content: "Setting up JWT authentication" },
216+
{ role: "user", content: "Add refresh token support" },
217+
{ role: "assistant", content: "Adding refresh token logic" },
218+
{ role: "user", content: "Include rate limiting" },
219+
{ role: "assistant", content: "Implementing rate limiting" },
220+
{ role: "user", content: "Add tests" },
221+
]
222+
223+
// Create a spy to capture what's sent to createMessage
224+
let capturedMessages: any[] = []
225+
class SpyApiHandler extends MockApiHandler {
226+
override createMessage(systemPrompt?: string, messages?: any[]): any {
227+
capturedMessages = messages || []
228+
return super.createMessage(systemPrompt, messages)
229+
}
230+
}
231+
232+
const spyHandler = new SpyApiHandler()
233+
await summarizeConversation(messages, spyHandler, "System prompt", taskId, 5000, false)
234+
235+
// Verify the initial ask is included in the messages sent for summarization
236+
expect(capturedMessages.length).toBeGreaterThan(0)
237+
238+
// The first user message in the captured messages should be the initial ask
239+
const firstUserMessage = capturedMessages.find((msg) => msg.role === "user")
240+
expect(firstUserMessage).toBeDefined()
241+
expect(firstUserMessage.content).toBe(initialAsk)
242+
243+
// Verify all messages except the last N are included
244+
const expectedMessagesToSummarize = messages.slice(0, -N_MESSAGES_TO_KEEP)
245+
// The last message in capturedMessages is the summarization request, so we exclude it
246+
const actualSummarizedMessages = capturedMessages.slice(0, -1)
247+
248+
// Check that we have the right number of messages
249+
expect(actualSummarizedMessages.length).toBe(expectedMessagesToSummarize.length)
250+
251+
// Verify the content matches
252+
for (let i = 0; i < expectedMessagesToSummarize.length; i++) {
253+
expect(actualSummarizedMessages[i].role).toBe(expectedMessagesToSummarize[i].role)
254+
expect(actualSummarizedMessages[i].content).toBe(expectedMessagesToSummarize[i].content)
255+
}
256+
})
257+
258+
it("should include initial ask with slash command in summarization", async () => {
259+
const slashCommand = "/prr #456 - Implement feature X"
260+
const messages: ApiMessage[] = [
261+
{ role: "user", content: slashCommand },
262+
{ role: "assistant", content: "Working on PR #456" },
263+
{ role: "user", content: "Add error handling" },
264+
{ role: "assistant", content: "Adding error handling" },
265+
{ role: "user", content: "Include logging" },
266+
{ role: "assistant", content: "Adding logging" },
267+
{ role: "user", content: "Write documentation" },
268+
{ role: "assistant", content: "Writing docs" },
269+
{ role: "user", content: "Final review" },
270+
]
271+
272+
// Spy on the API handler to verify what's being sent
273+
let capturedMessages: any[] = []
274+
class SpyApiHandler extends MockApiHandler {
275+
override createMessage(systemPrompt?: string, messages?: any[]): any {
276+
capturedMessages = messages || []
277+
return super.createMessage(systemPrompt, messages)
278+
}
279+
}
280+
281+
const spyHandler = new SpyApiHandler()
282+
await summarizeConversation(messages, spyHandler, "System prompt", taskId, 5000, false)
283+
284+
// Verify the slash command is in the summarization input
285+
const firstMessage = capturedMessages[0]
286+
expect(firstMessage.role).toBe("user")
287+
expect(firstMessage.content).toBe(slashCommand)
288+
})
207289
})
208290

209291
describe("getMessagesSinceLastSummary", () => {

src/core/condense/index.ts

Lines changed: 19 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -15,35 +15,40 @@ const SUMMARY_PROMPT = `\
1515
Your task is to create a detailed summary of the conversation so far, paying close attention to the user's explicit requests and your previous actions.
1616
This summary should be thorough in capturing technical details, code patterns, and architectural decisions that would be essential for continuing with the conversation and supporting any continuing tasks.
1717
18+
IMPORTANT: Always capture and preserve the initial user request or task that started this conversation. This is critical for maintaining context when resuming work after condensation.
19+
1820
Your summary should be structured as follows:
1921
Context: The context to continue the conversation with. If applicable based on the current task, this should include:
20-
1. Previous Conversation: High level details about what was discussed throughout the entire conversation with the user. This should be written to allow someone to be able to follow the general overarching conversation flow.
21-
2. Current Work: Describe in detail what was being worked on prior to this request to summarize the conversation. Pay special attention to the more recent messages in the conversation.
22-
3. Key Technical Concepts: List all important technical concepts, technologies, coding conventions, and frameworks discussed, which might be relevant for continuing with this work.
23-
4. Relevant Files and Code: If applicable, enumerate specific files and code sections examined, modified, or created for the task continuation. Pay special attention to the most recent messages and changes.
24-
5. Problem Solving: Document problems solved thus far and any ongoing troubleshooting efforts.
25-
6. Pending Tasks and Next Steps: Outline all pending tasks that you have explicitly been asked to work on, as well as list the next steps you will take for all outstanding work, if applicable. Include code snippets where they add clarity. For any next steps, include direct quotes from the most recent conversation showing exactly what task you were working on and where you left off. This should be verbatim to ensure there's no information loss in context between tasks.
22+
1. Initial Request: The original user request or task that started this conversation. This should be captured verbatim or as close to the original as possible to preserve the initial intent.
23+
2. Previous Conversation: High level details about what was discussed throughout the entire conversation with the user. This should be written to allow someone to be able to follow the general overarching conversation flow.
24+
3. Current Work: Describe in detail what was being worked on prior to this request to summarize the conversation. Pay special attention to the more recent messages in the conversation.
25+
4. Key Technical Concepts: List all important technical concepts, technologies, coding conventions, and frameworks discussed, which might be relevant for continuing with this work.
26+
5. Relevant Files and Code: If applicable, enumerate specific files and code sections examined, modified, or created for the task continuation. Pay special attention to the most recent messages and changes.
27+
6. Problem Solving: Document problems solved thus far and any ongoing troubleshooting efforts.
28+
7. Pending Tasks and Next Steps: Outline all pending tasks that you have explicitly been asked to work on, as well as list the next steps you will take for all outstanding work, if applicable. Include code snippets where they add clarity. For any next steps, include direct quotes from the most recent conversation showing exactly what task you were working on and where you left off. This should be verbatim to ensure there's no information loss in context between tasks.
2629
2730
Example summary structure:
28-
1. Previous Conversation:
31+
1. Initial Request:
32+
[The original user request that started this conversation]
33+
2. Previous Conversation:
2934
[Detailed description]
30-
2. Current Work:
35+
3. Current Work:
3136
[Detailed description]
32-
3. Key Technical Concepts:
37+
4. Key Technical Concepts:
3338
- [Concept 1]
3439
- [Concept 2]
3540
- [...]
36-
4. Relevant Files and Code:
41+
5. Relevant Files and Code:
3742
- [File Name 1]
3843
- [Summary of why this file is important]
3944
- [Summary of the changes made to this file, if any]
4045
- [Important Code Snippet]
4146
- [File Name 2]
4247
- [Important Code Snippet]
4348
- [...]
44-
5. Problem Solving:
49+
6. Problem Solving:
4550
[Detailed description]
46-
6. Pending Tasks and Next Steps:
51+
7. Pending Tasks and Next Steps:
4752
- [Task 1 details & next steps]
4853
- [Task 2 details & next steps]
4954
- [...]
@@ -103,8 +108,8 @@ export async function summarizeConversation(
103108

104109
// Always preserve the first message (which may contain slash command content)
105110
const firstMessage = messages[0]
106-
// Get messages to summarize, excluding the first message and last N messages
107-
const messagesToSummarize = getMessagesSinceLastSummary(messages.slice(1, -N_MESSAGES_TO_KEEP))
111+
// Get messages to summarize, INCLUDING the first message to preserve initial context
112+
const messagesToSummarize = getMessagesSinceLastSummary(messages.slice(0, -N_MESSAGES_TO_KEEP))
108113

109114
if (messagesToSummarize.length <= 1) {
110115
const error =

tmp/pr-8287-Roo-Code

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Subproject commit 88a473b017af37091c85ce3056e444e856f80d6e

0 commit comments

Comments
 (0)