mirror of
https://github.com/RooVetGit/Roo-Code.git
synced 2026-08-28 05:27:24 +00:00
fix: make initial prompt preservation conditional on power-steering feature
- Add powerSteeringEnabled parameter to summarizeConversation function - Check power steering experiment flag before preserving first message - Update all callers to pass power steering state - Update tests to handle new parameter This prevents tasks from being restarted unnecessarily after context condensing when power steering is disabled. Fixes #8093
This commit is contained in:
parent
b1c104e20f
commit
334d310c64
6 changed files with 164 additions and 21 deletions
|
|
@ -72,7 +72,17 @@ describe("Condense", () => {
|
|||
{ role: "user", content: "Ninth message" },
|
||||
]
|
||||
|
||||
const result = await summarizeConversation(messages, mockApiHandler, "System prompt", taskId, 5000, false)
|
||||
const result = await summarizeConversation(
|
||||
messages,
|
||||
mockApiHandler,
|
||||
"System prompt",
|
||||
taskId,
|
||||
5000,
|
||||
false,
|
||||
undefined,
|
||||
undefined,
|
||||
true,
|
||||
) // powerSteeringEnabled = true to preserve first message
|
||||
|
||||
// Verify the first message is preserved
|
||||
expect(result.messages[0]).toEqual(messages[0])
|
||||
|
|
@ -106,7 +116,17 @@ describe("Condense", () => {
|
|||
{ role: "user", content: "Thanks!" },
|
||||
]
|
||||
|
||||
const result = await summarizeConversation(messages, mockApiHandler, "System prompt", taskId, 5000, false)
|
||||
const result = await summarizeConversation(
|
||||
messages,
|
||||
mockApiHandler,
|
||||
"System prompt",
|
||||
taskId,
|
||||
5000,
|
||||
false,
|
||||
undefined,
|
||||
undefined,
|
||||
true,
|
||||
) // powerSteeringEnabled = true to preserve first message
|
||||
|
||||
// The first message with slash command should be intact
|
||||
expect(result.messages[0].content).toBe(slashCommandContent)
|
||||
|
|
@ -131,7 +151,17 @@ describe("Condense", () => {
|
|||
{ role: "user", content: "Perfect!" },
|
||||
]
|
||||
|
||||
const result = await summarizeConversation(messages, mockApiHandler, "System prompt", taskId, 5000, false)
|
||||
const result = await summarizeConversation(
|
||||
messages,
|
||||
mockApiHandler,
|
||||
"System prompt",
|
||||
taskId,
|
||||
5000,
|
||||
false,
|
||||
undefined,
|
||||
undefined,
|
||||
true,
|
||||
) // powerSteeringEnabled = true to preserve first message
|
||||
|
||||
// The first message with complex content should be preserved
|
||||
expect(result.messages[0].content).toEqual(complexContent)
|
||||
|
|
@ -146,7 +176,17 @@ describe("Condense", () => {
|
|||
{ role: "assistant", content: "Fourth message" },
|
||||
]
|
||||
|
||||
const result = await summarizeConversation(messages, mockApiHandler, "System prompt", taskId, 5000, false)
|
||||
const result = await summarizeConversation(
|
||||
messages,
|
||||
mockApiHandler,
|
||||
"System prompt",
|
||||
taskId,
|
||||
5000,
|
||||
false,
|
||||
undefined,
|
||||
undefined,
|
||||
false,
|
||||
)
|
||||
|
||||
// Should return an error since we have only 4 messages (first + 3 to keep)
|
||||
expect(result.error).toBeDefined()
|
||||
|
|
@ -165,7 +205,17 @@ describe("Condense", () => {
|
|||
{ role: "user", content: "Final message" },
|
||||
]
|
||||
|
||||
const result = await summarizeConversation(messages, mockApiHandler, "System prompt", taskId, 5000, false)
|
||||
const result = await summarizeConversation(
|
||||
messages,
|
||||
mockApiHandler,
|
||||
"System prompt",
|
||||
taskId,
|
||||
5000,
|
||||
false,
|
||||
undefined,
|
||||
undefined,
|
||||
false,
|
||||
)
|
||||
|
||||
// Should return an error due to recent summary in last N messages
|
||||
expect(result.error).toBeDefined()
|
||||
|
|
@ -198,7 +248,17 @@ describe("Condense", () => {
|
|||
{ role: "user", content: "Seventh" },
|
||||
]
|
||||
|
||||
const result = await summarizeConversation(messages, emptyHandler, "System prompt", taskId, 5000, false)
|
||||
const result = await summarizeConversation(
|
||||
messages,
|
||||
emptyHandler,
|
||||
"System prompt",
|
||||
taskId,
|
||||
5000,
|
||||
false,
|
||||
undefined,
|
||||
undefined,
|
||||
false,
|
||||
)
|
||||
|
||||
expect(result.error).toBeDefined()
|
||||
expect(result.messages).toEqual(messages)
|
||||
|
|
|
|||
|
|
@ -129,6 +129,10 @@ describe("summarizeConversation", () => {
|
|||
defaultSystemPrompt,
|
||||
taskId,
|
||||
DEFAULT_PREV_CONTEXT_TOKENS,
|
||||
false,
|
||||
undefined,
|
||||
undefined,
|
||||
false, // powerSteeringEnabled
|
||||
)
|
||||
expect(result.messages).toEqual(messages)
|
||||
expect(result.cost).toBe(0)
|
||||
|
|
@ -155,6 +159,10 @@ describe("summarizeConversation", () => {
|
|||
defaultSystemPrompt,
|
||||
taskId,
|
||||
DEFAULT_PREV_CONTEXT_TOKENS,
|
||||
false,
|
||||
undefined,
|
||||
undefined,
|
||||
false, // powerSteeringEnabled
|
||||
)
|
||||
expect(result.messages).toEqual(messages)
|
||||
expect(result.cost).toBe(0)
|
||||
|
|
@ -181,6 +189,10 @@ describe("summarizeConversation", () => {
|
|||
defaultSystemPrompt,
|
||||
taskId,
|
||||
DEFAULT_PREV_CONTEXT_TOKENS,
|
||||
false,
|
||||
undefined,
|
||||
undefined,
|
||||
false, // powerSteeringEnabled
|
||||
)
|
||||
|
||||
// Check that the API was called correctly
|
||||
|
|
@ -188,14 +200,11 @@ describe("summarizeConversation", () => {
|
|||
expect(maybeRemoveImageBlocks).toHaveBeenCalled()
|
||||
|
||||
// Verify the structure of the result
|
||||
// The result should be: first message + summary + last N messages
|
||||
expect(result.messages.length).toBe(1 + 1 + N_MESSAGES_TO_KEEP) // First + summary + last N
|
||||
|
||||
// Check that the first message is preserved
|
||||
expect(result.messages[0]).toEqual(messages[0])
|
||||
// When powerSteeringEnabled is false, the result should be: summary + last N messages
|
||||
expect(result.messages.length).toBe(1 + N_MESSAGES_TO_KEEP) // summary + last N
|
||||
|
||||
// Check that the summary message was inserted correctly
|
||||
const summaryMessage = result.messages[1]
|
||||
const summaryMessage = result.messages[0]
|
||||
expect(summaryMessage.role).toBe("assistant")
|
||||
expect(summaryMessage.content).toBe("This is a summary")
|
||||
expect(summaryMessage.isSummary).toBe(true)
|
||||
|
|
@ -244,6 +253,10 @@ describe("summarizeConversation", () => {
|
|||
defaultSystemPrompt,
|
||||
taskId,
|
||||
DEFAULT_PREV_CONTEXT_TOKENS,
|
||||
false,
|
||||
undefined,
|
||||
undefined,
|
||||
false, // powerSteeringEnabled
|
||||
)
|
||||
|
||||
// Should return original messages when summary is empty
|
||||
|
|
@ -265,7 +278,17 @@ describe("summarizeConversation", () => {
|
|||
{ role: "user", content: "Tell me more", ts: 7 },
|
||||
]
|
||||
|
||||
await summarizeConversation(messages, mockApiHandler, defaultSystemPrompt, taskId, DEFAULT_PREV_CONTEXT_TOKENS)
|
||||
await summarizeConversation(
|
||||
messages,
|
||||
mockApiHandler,
|
||||
defaultSystemPrompt,
|
||||
taskId,
|
||||
DEFAULT_PREV_CONTEXT_TOKENS,
|
||||
false,
|
||||
undefined,
|
||||
undefined,
|
||||
false,
|
||||
)
|
||||
|
||||
// Verify the final request message
|
||||
const expectedFinalMessage = {
|
||||
|
|
@ -312,6 +335,10 @@ describe("summarizeConversation", () => {
|
|||
systemPrompt,
|
||||
taskId,
|
||||
DEFAULT_PREV_CONTEXT_TOKENS,
|
||||
false,
|
||||
undefined,
|
||||
undefined,
|
||||
false, // powerSteeringEnabled
|
||||
)
|
||||
|
||||
// Verify that countTokens was called with the correct messages including system prompt
|
||||
|
|
@ -355,6 +382,10 @@ describe("summarizeConversation", () => {
|
|||
defaultSystemPrompt,
|
||||
taskId,
|
||||
prevContextTokens,
|
||||
false,
|
||||
undefined,
|
||||
undefined,
|
||||
false, // powerSteeringEnabled
|
||||
)
|
||||
|
||||
// Should return original messages when context would grow
|
||||
|
|
@ -395,11 +426,15 @@ describe("summarizeConversation", () => {
|
|||
defaultSystemPrompt,
|
||||
taskId,
|
||||
prevContextTokens,
|
||||
false,
|
||||
undefined,
|
||||
undefined,
|
||||
false, // powerSteeringEnabled
|
||||
)
|
||||
|
||||
// Should successfully summarize
|
||||
// Result should be: first message + summary + last N messages
|
||||
expect(result.messages.length).toBe(1 + 1 + N_MESSAGES_TO_KEEP) // First + summary + last N
|
||||
// When powerSteeringEnabled is false, the result should be: summary + last N messages
|
||||
expect(result.messages.length).toBe(1 + N_MESSAGES_TO_KEEP) // summary + last N
|
||||
expect(result.cost).toBe(0.03)
|
||||
expect(result.summary).toBe("Concise summary")
|
||||
expect(result.error).toBeUndefined()
|
||||
|
|
@ -416,6 +451,10 @@ describe("summarizeConversation", () => {
|
|||
defaultSystemPrompt,
|
||||
taskId,
|
||||
DEFAULT_PREV_CONTEXT_TOKENS,
|
||||
false,
|
||||
undefined,
|
||||
undefined,
|
||||
false, // powerSteeringEnabled
|
||||
)
|
||||
|
||||
// Should return original messages when not enough to summarize
|
||||
|
|
@ -444,6 +483,10 @@ describe("summarizeConversation", () => {
|
|||
defaultSystemPrompt,
|
||||
taskId,
|
||||
DEFAULT_PREV_CONTEXT_TOKENS,
|
||||
false,
|
||||
undefined,
|
||||
undefined,
|
||||
false, // powerSteeringEnabled
|
||||
)
|
||||
|
||||
// Should return original messages when recent summary exists
|
||||
|
|
@ -493,6 +536,7 @@ describe("summarizeConversation", () => {
|
|||
false,
|
||||
undefined,
|
||||
invalidCondensingHandler,
|
||||
false, // powerSteeringEnabled
|
||||
)
|
||||
|
||||
// Should return original messages when both handlers are invalid
|
||||
|
|
@ -601,6 +645,8 @@ describe("summarizeConversation with custom settings", () => {
|
|||
DEFAULT_PREV_CONTEXT_TOKENS,
|
||||
false,
|
||||
customPrompt,
|
||||
undefined,
|
||||
false, // powerSteeringEnabled
|
||||
)
|
||||
|
||||
// Verify the custom prompt was used
|
||||
|
|
@ -622,6 +668,8 @@ describe("summarizeConversation with custom settings", () => {
|
|||
DEFAULT_PREV_CONTEXT_TOKENS,
|
||||
false,
|
||||
" ", // Empty custom prompt
|
||||
undefined,
|
||||
false, // powerSteeringEnabled
|
||||
)
|
||||
|
||||
// Verify the default prompt was used
|
||||
|
|
@ -639,6 +687,8 @@ describe("summarizeConversation with custom settings", () => {
|
|||
DEFAULT_PREV_CONTEXT_TOKENS,
|
||||
false,
|
||||
undefined, // No custom prompt
|
||||
undefined,
|
||||
false, // powerSteeringEnabled
|
||||
)
|
||||
|
||||
// Verify the default prompt was used again
|
||||
|
|
@ -660,6 +710,7 @@ describe("summarizeConversation with custom settings", () => {
|
|||
false,
|
||||
undefined,
|
||||
mockCondensingApiHandler,
|
||||
false, // powerSteeringEnabled
|
||||
)
|
||||
|
||||
// Verify the condensing handler was used
|
||||
|
|
@ -680,6 +731,7 @@ describe("summarizeConversation with custom settings", () => {
|
|||
false,
|
||||
undefined,
|
||||
undefined,
|
||||
false, // powerSteeringEnabled
|
||||
)
|
||||
|
||||
// Verify the main handler was used
|
||||
|
|
@ -711,6 +763,7 @@ describe("summarizeConversation with custom settings", () => {
|
|||
false,
|
||||
undefined,
|
||||
invalidHandler,
|
||||
false, // powerSteeringEnabled
|
||||
)
|
||||
|
||||
// Verify the main handler was used as fallback
|
||||
|
|
@ -737,6 +790,8 @@ describe("summarizeConversation with custom settings", () => {
|
|||
DEFAULT_PREV_CONTEXT_TOKENS,
|
||||
false,
|
||||
"Custom prompt",
|
||||
undefined,
|
||||
false, // powerSteeringEnabled
|
||||
)
|
||||
|
||||
// Verify telemetry was called with custom prompt flag
|
||||
|
|
@ -761,6 +816,7 @@ describe("summarizeConversation with custom settings", () => {
|
|||
false,
|
||||
undefined,
|
||||
mockCondensingApiHandler,
|
||||
false, // powerSteeringEnabled
|
||||
)
|
||||
|
||||
// Verify telemetry was called with custom API handler flag
|
||||
|
|
@ -785,6 +841,7 @@ describe("summarizeConversation with custom settings", () => {
|
|||
true, // isAutomaticTrigger
|
||||
"Custom prompt",
|
||||
mockCondensingApiHandler,
|
||||
false, // powerSteeringEnabled
|
||||
)
|
||||
|
||||
// Verify telemetry was called with both flags
|
||||
|
|
|
|||
|
|
@ -80,6 +80,7 @@ export type SummarizeResponse = {
|
|||
* @param {boolean} isAutomaticTrigger - Whether the summarization is triggered automatically
|
||||
* @param {string} customCondensingPrompt - Optional custom prompt to use for condensing
|
||||
* @param {ApiHandler} condensingApiHandler - Optional specific API handler to use for condensing
|
||||
* @param {boolean} powerSteeringEnabled - Whether power steering is enabled (controls initial prompt preservation)
|
||||
* @returns {SummarizeResponse} - The result of the summarization operation (see above)
|
||||
*/
|
||||
export async function summarizeConversation(
|
||||
|
|
@ -91,6 +92,7 @@ export async function summarizeConversation(
|
|||
isAutomaticTrigger?: boolean,
|
||||
customCondensingPrompt?: string,
|
||||
condensingApiHandler?: ApiHandler,
|
||||
powerSteeringEnabled?: boolean,
|
||||
): Promise<SummarizeResponse> {
|
||||
TelemetryService.instance.captureContextCondensed(
|
||||
taskId,
|
||||
|
|
@ -101,10 +103,14 @@ export async function summarizeConversation(
|
|||
|
||||
const response: SummarizeResponse = { messages, cost: 0, summary: "" }
|
||||
|
||||
// Always preserve the first message (which may contain slash command content)
|
||||
const firstMessage = messages[0]
|
||||
// Get messages to summarize, excluding the first message and last N messages
|
||||
const messagesToSummarize = getMessagesSinceLastSummary(messages.slice(1, -N_MESSAGES_TO_KEEP))
|
||||
// Conditionally preserve the first message based on power steering setting
|
||||
// When power steering is enabled, preserve the initial prompt to maintain task context
|
||||
// When disabled, don't preserve it to prevent task restarts after condensing
|
||||
const firstMessage = powerSteeringEnabled ? messages[0] : undefined
|
||||
const messagesToKeepFrom = powerSteeringEnabled ? 1 : 0
|
||||
|
||||
// Get messages to summarize, excluding the conditionally preserved first message and last N messages
|
||||
const messagesToSummarize = getMessagesSinceLastSummary(messages.slice(messagesToKeepFrom, -N_MESSAGES_TO_KEEP))
|
||||
|
||||
if (messagesToSummarize.length <= 1) {
|
||||
const error =
|
||||
|
|
@ -188,8 +194,10 @@ export async function summarizeConversation(
|
|||
isSummary: true,
|
||||
}
|
||||
|
||||
// Reconstruct messages: [first message, summary, last N messages]
|
||||
const newMessages = [firstMessage, summaryMessage, ...keepMessages]
|
||||
// Reconstruct messages: [first message (if power steering enabled), summary, last N messages]
|
||||
const newMessages = firstMessage
|
||||
? [firstMessage, summaryMessage, ...keepMessages]
|
||||
: [summaryMessage, ...keepMessages]
|
||||
|
||||
// Count the tokens in the context for the next API request
|
||||
// We only estimate the tokens in summaryMesage if outputTokens is 0, otherwise we use outputTokens
|
||||
|
|
|
|||
|
|
@ -594,6 +594,7 @@ describe("Sliding Window", () => {
|
|||
true,
|
||||
undefined, // customCondensingPrompt
|
||||
undefined, // condensingApiHandler
|
||||
undefined, // powerSteeringEnabled
|
||||
)
|
||||
|
||||
// Verify the result contains the summary information
|
||||
|
|
@ -765,6 +766,7 @@ describe("Sliding Window", () => {
|
|||
true,
|
||||
undefined, // customCondensingPrompt
|
||||
undefined, // condensingApiHandler
|
||||
undefined, // powerSteeringEnabled
|
||||
)
|
||||
|
||||
// Verify the result contains the summary information
|
||||
|
|
|
|||
|
|
@ -6,6 +6,7 @@ import { ApiHandler } from "../../api"
|
|||
import { MAX_CONDENSE_THRESHOLD, MIN_CONDENSE_THRESHOLD, summarizeConversation, SummarizeResponse } from "../condense"
|
||||
import { ApiMessage } from "../task-persistence/apiMessages"
|
||||
import { ANTHROPIC_DEFAULT_MAX_TOKENS } from "@roo-code/types"
|
||||
import { experiments, EXPERIMENT_IDS } from "../../shared/experiments"
|
||||
|
||||
/**
|
||||
* Default percentage of the context window to use as a buffer when deciding when to truncate
|
||||
|
|
@ -77,6 +78,7 @@ type TruncateOptions = {
|
|||
condensingApiHandler?: ApiHandler
|
||||
profileThresholds: Record<string, number>
|
||||
currentProfileId: string
|
||||
powerSteeringEnabled?: boolean
|
||||
}
|
||||
|
||||
type TruncateResponse = SummarizeResponse & { prevContextTokens: number }
|
||||
|
|
@ -102,6 +104,7 @@ export async function truncateConversationIfNeeded({
|
|||
condensingApiHandler,
|
||||
profileThresholds,
|
||||
currentProfileId,
|
||||
powerSteeringEnabled,
|
||||
}: TruncateOptions): Promise<TruncateResponse> {
|
||||
let error: string | undefined
|
||||
let cost = 0
|
||||
|
|
@ -155,6 +158,7 @@ export async function truncateConversationIfNeeded({
|
|||
true, // automatic trigger
|
||||
customCondensingPrompt,
|
||||
condensingApiHandler,
|
||||
powerSteeringEnabled,
|
||||
)
|
||||
if (result.error) {
|
||||
error = result.error
|
||||
|
|
|
|||
|
|
@ -1006,6 +1006,9 @@ export class Task extends EventEmitter<TaskEvents> implements TaskLike {
|
|||
|
||||
const { contextTokens: prevContextTokens } = this.getTokenUsage()
|
||||
|
||||
// Check if power steering is enabled
|
||||
const powerSteeringEnabled = experiments.isEnabled(state?.experiments ?? {}, EXPERIMENT_IDS.POWER_STEERING)
|
||||
|
||||
const {
|
||||
messages,
|
||||
summary,
|
||||
|
|
@ -1021,6 +1024,7 @@ export class Task extends EventEmitter<TaskEvents> implements TaskLike {
|
|||
false, // manual trigger
|
||||
customCondensingPrompt, // User's custom prompt
|
||||
condensingApiHandler, // Specific handler for condensing
|
||||
powerSteeringEnabled, // Pass power steering state
|
||||
)
|
||||
if (error) {
|
||||
this.say(
|
||||
|
|
@ -2461,6 +2465,9 @@ export class Task extends EventEmitter<TaskEvents> implements TaskLike {
|
|||
`Forcing truncation to ${FORCED_CONTEXT_REDUCTION_PERCENT}% of current context.`,
|
||||
)
|
||||
|
||||
// Check if power steering is enabled
|
||||
const powerSteeringEnabled = experiments.isEnabled(state?.experiments ?? {}, EXPERIMENT_IDS.POWER_STEERING)
|
||||
|
||||
// Force aggressive truncation by keeping only 75% of the conversation history
|
||||
const truncateResult = await truncateConversationIfNeeded({
|
||||
messages: this.apiConversationHistory,
|
||||
|
|
@ -2474,6 +2481,7 @@ export class Task extends EventEmitter<TaskEvents> implements TaskLike {
|
|||
taskId: this.taskId,
|
||||
profileThresholds,
|
||||
currentProfileId,
|
||||
powerSteeringEnabled,
|
||||
})
|
||||
|
||||
if (truncateResult.messages !== this.apiConversationHistory) {
|
||||
|
|
@ -2577,6 +2585,9 @@ export class Task extends EventEmitter<TaskEvents> implements TaskLike {
|
|||
// Get the current profile ID using the helper method
|
||||
const currentProfileId = this.getCurrentProfileId(state)
|
||||
|
||||
// Check if power steering is enabled
|
||||
const powerSteeringEnabled = experiments.isEnabled(state?.experiments ?? {}, EXPERIMENT_IDS.POWER_STEERING)
|
||||
|
||||
const truncateResult = await truncateConversationIfNeeded({
|
||||
messages: this.apiConversationHistory,
|
||||
totalTokens: contextTokens,
|
||||
|
|
@ -2591,6 +2602,7 @@ export class Task extends EventEmitter<TaskEvents> implements TaskLike {
|
|||
condensingApiHandler,
|
||||
profileThresholds,
|
||||
currentProfileId,
|
||||
powerSteeringEnabled,
|
||||
})
|
||||
if (truncateResult.messages !== this.apiConversationHistory) {
|
||||
await this.overwriteApiConversationHistory(truncateResult.messages)
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue