From ad9af9787689ba01a1898992886c002b1e92ef4b Mon Sep 17 00:00:00 2001 From: Shariq Riaz Date: Sun, 27 Apr 2025 13:33:08 +0500 Subject: [PATCH 01/16] feat: Implement optional context summarization Implements the optional context summarization feature based on the plan in (now deleted) `context-summarization-plan.md`. Key changes: - Adds new settings (`enableContextSummarization`, thresholds, turn counts) to control the feature, accessible in Settings -> Context Management. Settings are persistent and included in import/export. - Creates `ContextSummarizer` service to handle summarization logic, using the main configured LLM with a refined, directive prompt. - Modifies `Cline.ts` to conditionally call the summarizer or fallback to truncation based on settings and token thresholds before API requests. - Adds a system message `[Older conversation turns summarized...]` to the chat UI for transparency when summarization occurs. - Includes necessary updates to schemas, types, message handlers, webview context, and tests. - Fixes UI state bugs related to settings persistence and interaction. --- roo-code-settings.json | 75 +++++++++ src/core/Cline.ts | 151 ++++++++++++++++-- src/core/webview/ClineProvider.ts | 35 ++++ .../webview/__tests__/ClineProvider.test.ts | 67 ++++++++ src/core/webview/webviewMessageHandler.ts | 18 +++ src/exports/roo-code.d.ts | 4 + src/schemas/index.ts | 14 +- .../summarization/ContextSummarizer.ts | 148 +++++++++++++++++ src/shared/ExtensionMessage.ts | 11 ++ src/shared/WebviewMessage.ts | 5 + .../settings/ContextManagementSettings.tsx | 130 ++++++++++++++- .../src/components/settings/SettingsView.tsx | 21 +++ .../ContextManagementSettings.test.tsx | 59 +++++++ .../src/context/ExtensionStateContext.tsx | 34 ++++ .../__tests__/ExtensionStateContext.test.tsx | 5 + webview-ui/src/i18n/locales/en/settings.json | 19 +++ 16 files changed, 782 insertions(+), 14 deletions(-) create mode 100644 roo-code-settings.json create mode 100644 src/services/summarization/ContextSummarizer.ts diff --git a/roo-code-settings.json b/roo-code-settings.json new file mode 100644 index 00000000000..fa2db86adc5 --- /dev/null +++ b/roo-code-settings.json @@ -0,0 +1,75 @@ +{ + "providerProfiles": { + "currentApiConfigName": "default", + "apiConfigs": { + "default": { + "apiProvider": "vertex", + "apiModelId": "gemini-2.5-pro-exp-03-25", + "vertexProjectId": "gen-lang-client-0337025147", + "vertexRegion": "us-central1", + "id": "q0q01aha2ml" + } + }, + "modeApiConfigs": { + "code": "q0q01aha2ml", + "architect": "q0q01aha2ml", + "ask": "q0q01aha2ml", + "debug": "q0q01aha2ml", + "orchestrator": "q0q01aha2ml" + }, + "migrations": { + "rateLimitSecondsMigrated": true, + "diffSettingsMigrated": true + } + }, + "globalSettings": { + "lastShownAnnouncementId": "apr-23-2025-3-14", + "autoApprovalEnabled": true, + "alwaysAllowReadOnly": true, + "alwaysAllowReadOnlyOutsideWorkspace": true, + "alwaysAllowWrite": true, + "alwaysAllowWriteOutsideWorkspace": true, + "writeDelayMs": 1000, + "alwaysAllowBrowser": true, + "alwaysApproveResubmit": true, + "requestDelaySeconds": 5, + "alwaysAllowMcp": true, + "alwaysAllowModeSwitch": true, + "alwaysAllowSubtasks": true, + "alwaysAllowExecute": true, + "allowedCommands": ["*"], + "browserToolEnabled": true, + "browserViewportSize": "900x600", + "screenshotQuality": 100, + "remoteBrowserEnabled": false, + "enableCheckpoints": true, + "ttsEnabled": false, + "ttsSpeed": 1, + "soundEnabled": false, + "soundVolume": 0.5, + "maxOpenTabsContext": 20, + "maxWorkspaceFiles": 200, + "showRooIgnoredFiles": true, + "maxReadFileLine": -1, + "terminalOutputLineLimit": 500, + "terminalShellIntegrationTimeout": 5000, + "terminalCommandDelay": 0, + "terminalPowershellCounter": false, + "terminalZshClearEolMark": true, + "terminalZshOhMy": false, + "terminalZshP10k": false, + "terminalZdotdir": false, + "terminalCompressProgressBar": true, + "enableContextSummarization": false, + "contextSummarizationTriggerThreshold": 80, + "contextSummarizationInitialStaticTurns": 5, + "contextSummarizationRecentTurns": 10, + "experiments": { + "powerSteering": false + }, + "language": "en", + "telemetrySetting": "disabled", + "mcpEnabled": true, + "customModes": [] + } +} diff --git a/src/core/Cline.ts b/src/core/Cline.ts index 304b86d3182..8dd4827d9ae 100644 --- a/src/core/Cline.ts +++ b/src/core/Cline.ts @@ -85,10 +85,11 @@ import { parseMentions } from "./mentions" import { FileContextTracker } from "./context-tracking/FileContextTracker" import { RooIgnoreController } from "./ignore/RooIgnoreController" import { type AssistantMessageContent, parseAssistantMessage } from "./assistant-message" -import { truncateConversationIfNeeded } from "./sliding-window" +import { truncateConversationIfNeeded, estimateTokenCount } from "./sliding-window" // Added estimateTokenCount import { ClineProvider } from "./webview/ClineProvider" import { validateToolUse } from "./mode-validator" import { MultiSearchReplaceDiffStrategy } from "./diff/strategies/multi-search-replace" +import { ContextSummarizer } from "../services/summarization/ContextSummarizer" // Added ContextSummarizer import { readApiMessages, saveApiMessages, readTaskMessages, saveTaskMessages, taskMetadata } from "./task-persistence" type UserContent = Array @@ -124,6 +125,11 @@ export type ClineOptions = { parentTask?: Cline taskNumber?: number onCreated?: (cline: Cline) => void + // Context Summarization Settings + enableContextSummarization?: boolean // Already added + contextSummarizationTriggerThreshold?: number // Already added + contextSummarizationInitialStaticTurns?: number // Already added + contextSummarizationRecentTurns?: number // Already added } export class Cline extends EventEmitter { @@ -153,6 +159,12 @@ export class Cline extends EventEmitter { diffEnabled: boolean = false fuzzyMatchThreshold: number + // Context Summarization Settings (Added) + readonly enableContextSummarization: boolean + readonly contextSummarizationTriggerThreshold: number + readonly contextSummarizationInitialStaticTurns: number + readonly contextSummarizationRecentTurns: number + apiConversationHistory: (Anthropic.MessageParam & { ts?: number })[] = [] clineMessages: ClineMessage[] = [] @@ -213,6 +225,11 @@ export class Cline extends EventEmitter { parentTask, taskNumber = -1, onCreated, + // Context Summarization Settings (Added) + enableContextSummarization = false, + contextSummarizationTriggerThreshold = 80, + contextSummarizationInitialStaticTurns = 5, + contextSummarizationRecentTurns = 10, }: ClineOptions) { super() @@ -246,6 +263,11 @@ export class Cline extends EventEmitter { this.diffViewProvider = new DiffViewProvider(this.cwd) this.enableCheckpoints = enableCheckpoints + this.enableContextSummarization = enableContextSummarization + this.contextSummarizationTriggerThreshold = contextSummarizationTriggerThreshold + this.contextSummarizationInitialStaticTurns = contextSummarizationInitialStaticTurns + this.contextSummarizationRecentTurns = contextSummarizationRecentTurns + this.rootTask = rootTask this.parentTask = parentTask this.taskNumber = taskNumber @@ -993,12 +1015,14 @@ export class Cline extends EventEmitter { ) })() - // If the previous API request's total token usage is close to the context window, truncate the conversation history to free up space for the new request + // If the previous API request's total token usage is close to the context window, + // either truncate or summarize the conversation history based on settings. if (previousApiReqIndex >= 0) { const previousRequest = this.clineMessages[previousApiReqIndex]?.text - if (!previousRequest) return + if (!previousRequest) return // Should not happen, but guard anyway const { + // These tokens are from the *previous* request's response, not the current history size tokensIn = 0, tokensOut = 0, cacheWrites = 0, @@ -1017,17 +1041,102 @@ export class Cline extends EventEmitter { : modelInfo.maxTokens const contextWindow = modelInfo.contextWindow + let historyModifiedBySummarization = false // Flag to track if summarization updated history + + if (this.enableContextSummarization) { + // --- Summarization Logic --- + const currentTokens = await this._estimateTotalTokenCount(this.apiConversationHistory) + const triggerTokenCount = contextWindow * (this.contextSummarizationTriggerThreshold / 100) + + this.providerRef + .deref() + ?.log(`[Summarization] Current tokens: ${currentTokens}, Trigger: ${triggerTokenCount}`) + + if (currentTokens >= triggerTokenCount) { + this.providerRef.deref()?.log(`[Summarization] Threshold met. Attempting summarization.`) + const initialMessagesToKeep = this.contextSummarizationInitialStaticTurns + const recentMessagesToKeep = this.contextSummarizationRecentTurns + + // Ensure slice points are valid and don't overlap negatively + if ( + this.apiConversationHistory.length > initialMessagesToKeep + recentMessagesToKeep && + initialMessagesToKeep >= 0 && + recentMessagesToKeep >= 0 + ) { + const initialSliceEnd = initialMessagesToKeep + const recentSliceStart = this.apiConversationHistory.length - recentMessagesToKeep + + if (initialSliceEnd < recentSliceStart) { + const initialMessages = this.apiConversationHistory.slice(0, initialSliceEnd) + const recentMessages = this.apiConversationHistory.slice(recentSliceStart) + const messagesToSummarize = this.apiConversationHistory.slice( + initialSliceEnd, + recentSliceStart, + ) - const trimmedMessages = await truncateConversationIfNeeded({ - messages: this.apiConversationHistory, - totalTokens, - maxTokens, - contextWindow, - apiHandler: this.api, - }) + this.providerRef + .deref() + ?.log( + `[Summarization] Slicing: Keep Initial ${initialMessages.length}, Summarize ${messagesToSummarize.length}, Keep Recent ${recentMessages.length}`, + ) + + // Instantiate the summarizer (consider using a dedicated API handler/model later) + const summarizer = new ContextSummarizer(this.api) + const summaryMessage = await summarizer.summarize(messagesToSummarize) + + if (summaryMessage) { + const newHistory = [...initialMessages, summaryMessage, ...recentMessages] + this.providerRef + .deref() + ?.log( + `[Summarization] Summarization successful. New history length: ${newHistory.length}`, + ) + // Add a system message to notify the user in the UI + await this.say("text", "[Older conversation turns summarized to preserve context]") + await this.overwriteApiConversationHistory(newHistory) + historyModifiedBySummarization = true // Mark history as modified + } else { + this.providerRef + .deref() + ?.log(`[Summarization] Summarization failed. Falling back to truncation.`) + // Fall through to truncation if summarization fails + } + } else { + this.providerRef + .deref() + ?.log( + `[Summarization] Skipping: initialSliceEnd (${initialSliceEnd}) >= recentSliceStart (${recentSliceStart}). Not enough messages between initial/recent turns.`, + ) + // Fall through to truncation if slicing is not possible + } + } else { + this.providerRef + .deref() + ?.log( + `[Summarization] Skipping: Not enough messages (${this.apiConversationHistory.length}) to satisfy keep counts (${initialMessagesToKeep} + ${recentMessagesToKeep}).`, + ) + // Fall through to truncation if history is too short + } + } + // If summarization is enabled but threshold not met, do nothing and proceed. + } - if (trimmedMessages !== this.apiConversationHistory) { - await this.overwriteApiConversationHistory(trimmedMessages) + // --- Truncation Logic (Only run if summarization didn't modify history) --- + if (!historyModifiedBySummarization) { + // Note: totalTokens here refers to the previous response size, used by truncateConversationIfNeeded + // to estimate if the *next* request might overflow. + const trimmedMessages = await truncateConversationIfNeeded({ + messages: this.apiConversationHistory, // Use potentially already summarized history if summarization failed above + totalTokens, // From previous response metrics + maxTokens, + contextWindow, + apiHandler: this.api, + }) + + if (trimmedMessages !== this.apiConversationHistory) { + this.providerRef.deref()?.log(`[Truncation] Truncation applied.`) + await this.overwriteApiConversationHistory(trimmedMessages) + } } } @@ -2538,4 +2647,22 @@ export class Cline extends EventEmitter { public getToolUsage() { return this.toolUsage } + + /** + * Estimates the total token count for an array of messages. + * @param messages The messages to count tokens for. + * @returns A promise resolving to the estimated total token count. + */ + private async _estimateTotalTokenCount(messages: Anthropic.MessageParam[]): Promise { + let totalTokens = 0 + for (const message of messages) { + const content = message.content + if (Array.isArray(content)) { + totalTokens += await estimateTokenCount(content, this.api) + } else if (typeof content === "string") { + totalTokens += await estimateTokenCount([{ type: "text", text: content }], this.api) + } + } + return totalTokens + } } diff --git a/src/core/webview/ClineProvider.ts b/src/core/webview/ClineProvider.ts index 431f061db74..8b677e11c02 100644 --- a/src/core/webview/ClineProvider.ts +++ b/src/core/webview/ClineProvider.ts @@ -494,6 +494,11 @@ export class ClineProvider extends EventEmitter implements mode, customInstructions: globalInstructions, experiments, + // Context Summarization Settings (Added) + enableContextSummarization, + contextSummarizationTriggerThreshold, + contextSummarizationInitialStaticTurns, + contextSummarizationRecentTurns, } = await this.getState() const modePrompt = customModePrompts?.[mode] as PromptComponent @@ -513,6 +518,11 @@ export class ClineProvider extends EventEmitter implements parentTask, taskNumber: this.clineStack.length + 1, onCreated: (cline) => this.emit("clineCreated", cline), + // Pass summarization settings to Cline (Added) + enableContextSummarization, + contextSummarizationTriggerThreshold, + contextSummarizationInitialStaticTurns, + contextSummarizationRecentTurns, ...options, }) @@ -537,6 +547,11 @@ export class ClineProvider extends EventEmitter implements mode, customInstructions: globalInstructions, experiments, + // Context Summarization Settings (Added) + enableContextSummarization, + contextSummarizationTriggerThreshold, + contextSummarizationInitialStaticTurns, + contextSummarizationRecentTurns, } = await this.getState() const modePrompt = customModePrompts?.[mode] as PromptComponent @@ -555,6 +570,11 @@ export class ClineProvider extends EventEmitter implements parentTask: historyItem.parentTask, taskNumber: historyItem.number, onCreated: (cline) => this.emit("clineCreated", cline), + // Pass summarization settings to Cline (Added) + enableContextSummarization, + contextSummarizationTriggerThreshold, + contextSummarizationInitialStaticTurns, + contextSummarizationRecentTurns, }) await this.addClineToStack(cline) @@ -1214,6 +1234,11 @@ export class ClineProvider extends EventEmitter implements maxReadFileLine, terminalCompressProgressBar, historyPreviewCollapsed, + // Context Summarization Settings (Added) + enableContextSummarization, + contextSummarizationTriggerThreshold, + contextSummarizationInitialStaticTurns, + contextSummarizationRecentTurns, } = await this.getState() const telemetryKey = process.env.POSTHOG_API_KEY @@ -1300,6 +1325,11 @@ export class ClineProvider extends EventEmitter implements terminalCompressProgressBar: terminalCompressProgressBar ?? true, hasSystemPromptOverride, historyPreviewCollapsed: historyPreviewCollapsed ?? false, + // Context Summarization Settings (Added) + enableContextSummarization: enableContextSummarization ?? false, + contextSummarizationTriggerThreshold: contextSummarizationTriggerThreshold ?? 80, + contextSummarizationInitialStaticTurns: contextSummarizationInitialStaticTurns ?? 5, + contextSummarizationRecentTurns: contextSummarizationRecentTurns ?? 10, } } @@ -1389,6 +1419,11 @@ export class ClineProvider extends EventEmitter implements showRooIgnoredFiles: stateValues.showRooIgnoredFiles ?? true, maxReadFileLine: stateValues.maxReadFileLine ?? 500, historyPreviewCollapsed: stateValues.historyPreviewCollapsed ?? false, + // Context Summarization Settings (Added) + enableContextSummarization: stateValues.enableContextSummarization ?? false, + contextSummarizationTriggerThreshold: stateValues.contextSummarizationTriggerThreshold ?? 80, + contextSummarizationInitialStaticTurns: stateValues.contextSummarizationInitialStaticTurns ?? 5, + contextSummarizationRecentTurns: stateValues.contextSummarizationRecentTurns ?? 10, } } diff --git a/src/core/webview/__tests__/ClineProvider.test.ts b/src/core/webview/__tests__/ClineProvider.test.ts index 5bdfd0e23e3..9b63e9a482a 100644 --- a/src/core/webview/__tests__/ClineProvider.test.ts +++ b/src/core/webview/__tests__/ClineProvider.test.ts @@ -418,6 +418,11 @@ describe("ClineProvider", () => { showRooIgnoredFiles: true, renderContext: "sidebar", maxReadFileLine: 500, + // Context Summarization Defaults (Added for test) + enableContextSummarization: false, + contextSummarizationTriggerThreshold: 80, + contextSummarizationInitialStaticTurns: 5, + contextSummarizationRecentTurns: 10, } const message: ExtensionMessage = { @@ -722,6 +727,68 @@ describe("ClineProvider", () => { expect((await provider.getState()).showRooIgnoredFiles).toBe(false) }) + test("handles context summarization settings messages", async () => { + await provider.resolveWebviewView(mockWebviewView) + const messageHandler = (mockWebviewView.webview.onDidReceiveMessage as jest.Mock).mock.calls[0][0] + + // Test enableContextSummarization + await messageHandler({ type: "enableContextSummarization", bool: true }) + expect(updateGlobalStateSpy).toHaveBeenCalledWith("enableContextSummarization", true) + expect(mockContext.globalState.update).toHaveBeenCalledWith("enableContextSummarization", true) + expect(mockPostMessage).toHaveBeenCalled() + expect((await provider.getState()).enableContextSummarization).toBe(true) + + await messageHandler({ type: "enableContextSummarization", bool: false }) + expect(updateGlobalStateSpy).toHaveBeenCalledWith("enableContextSummarization", false) + expect(mockContext.globalState.update).toHaveBeenCalledWith("enableContextSummarization", false) + expect(mockPostMessage).toHaveBeenCalled() + expect((await provider.getState()).enableContextSummarization).toBe(false) + + // Test contextSummarizationTriggerThreshold + await messageHandler({ type: "contextSummarizationTriggerThreshold", value: 90 }) + expect(updateGlobalStateSpy).toHaveBeenCalledWith("contextSummarizationTriggerThreshold", 90) + expect(mockContext.globalState.update).toHaveBeenCalledWith("contextSummarizationTriggerThreshold", 90) + expect(mockPostMessage).toHaveBeenCalled() + expect((await provider.getState()).contextSummarizationTriggerThreshold).toBe(90) + + // Test contextSummarizationInitialStaticTurns + await messageHandler({ type: "contextSummarizationInitialStaticTurns", value: 3 }) + expect(updateGlobalStateSpy).toHaveBeenCalledWith("contextSummarizationInitialStaticTurns", 3) + expect(mockContext.globalState.update).toHaveBeenCalledWith("contextSummarizationInitialStaticTurns", 3) + expect(mockPostMessage).toHaveBeenCalled() + expect((await provider.getState()).contextSummarizationInitialStaticTurns).toBe(3) + + // Test contextSummarizationRecentTurns + await messageHandler({ type: "contextSummarizationRecentTurns", value: 15 }) + expect(updateGlobalStateSpy).toHaveBeenCalledWith("contextSummarizationRecentTurns", 15) + expect(mockContext.globalState.update).toHaveBeenCalledWith("contextSummarizationRecentTurns", 15) + expect(mockPostMessage).toHaveBeenCalled() + expect((await provider.getState()).contextSummarizationRecentTurns).toBe(15) + }) + + test("context summarization settings have correct default values", async () => { + // Mock globalState.get to return undefined for the new settings + ;(mockContext.globalState.get as jest.Mock).mockImplementation((key: string) => { + if ( + [ + "enableContextSummarization", + "contextSummarizationTriggerThreshold", + "contextSummarizationInitialStaticTurns", + "contextSummarizationRecentTurns", + ].includes(key) + ) { + return undefined + } + return null // Return null for other keys to avoid interference + }) + + const state = await provider.getState() + expect(state.enableContextSummarization).toBe(false) + expect(state.contextSummarizationTriggerThreshold).toBe(80) + expect(state.contextSummarizationInitialStaticTurns).toBe(5) + expect(state.contextSummarizationRecentTurns).toBe(10) + }) + test("handles request delay settings messages", async () => { await provider.resolveWebviewView(mockWebviewView) const messageHandler = (mockWebviewView.webview.onDidReceiveMessage as jest.Mock).mock.calls[0][0] diff --git a/src/core/webview/webviewMessageHandler.ts b/src/core/webview/webviewMessageHandler.ts index e5c9299182e..49edd287c86 100644 --- a/src/core/webview/webviewMessageHandler.ts +++ b/src/core/webview/webviewMessageHandler.ts @@ -848,6 +848,24 @@ export const webviewMessageHandler = async (provider: ClineProvider, message: We await updateGlobalState("historyPreviewCollapsed", message.bool ?? false) // No need to call postStateToWebview here as the UI already updated optimistically break + // Context Summarization Settings (Added) + case "enableContextSummarization": + await updateGlobalState("enableContextSummarization", message.bool ?? false) + await provider.postStateToWebview() + break + case "contextSummarizationTriggerThreshold": + await updateGlobalState("contextSummarizationTriggerThreshold", message.value ?? 80) + await provider.postStateToWebview() + break + case "contextSummarizationInitialStaticTurns": + await updateGlobalState("contextSummarizationInitialStaticTurns", message.value ?? 5) + await provider.postStateToWebview() + break + case "contextSummarizationRecentTurns": + await updateGlobalState("contextSummarizationRecentTurns", message.value ?? 10) + await provider.postStateToWebview() + break + // --- End Context Summarization --- case "toggleApiConfigPin": if (message.text) { const currentPinned = getGlobalState("pinnedApiConfigs") ?? {} diff --git a/src/exports/roo-code.d.ts b/src/exports/roo-code.d.ts index a725686fbd1..ba397f1e4bf 100644 --- a/src/exports/roo-code.d.ts +++ b/src/exports/roo-code.d.ts @@ -215,6 +215,10 @@ type GlobalSettings = { terminalZshP10k?: boolean | undefined terminalZdotdir?: boolean | undefined terminalCompressProgressBar?: boolean | undefined + enableContextSummarization?: boolean | undefined + contextSummarizationTriggerThreshold?: number | undefined + contextSummarizationInitialStaticTurns?: number | undefined + contextSummarizationRecentTurns?: number | undefined rateLimitSeconds?: number | undefined diffEnabled?: boolean | undefined fuzzyMatchThreshold?: number | undefined diff --git a/src/schemas/index.ts b/src/schemas/index.ts index 149dc693e17..705155c8319 100644 --- a/src/schemas/index.ts +++ b/src/schemas/index.ts @@ -555,6 +555,12 @@ export const globalSettingsSchema = z.object({ terminalZdotdir: z.boolean().optional(), terminalCompressProgressBar: z.boolean().optional(), + // Context Summarization Settings + enableContextSummarization: z.boolean().optional(), + contextSummarizationTriggerThreshold: z.number().optional(), // Percentage (e.g., 80) + contextSummarizationInitialStaticTurns: z.number().optional(), // Number of initial turns to keep + contextSummarizationRecentTurns: z.number().optional(), // Number of recent turns to keep + rateLimitSeconds: z.number().optional(), diffEnabled: z.boolean().optional(), fuzzyMatchThreshold: z.number().optional(), @@ -632,6 +638,12 @@ const globalSettingsRecord: GlobalSettingsRecord = { terminalZdotdir: undefined, terminalCompressProgressBar: undefined, + // Context Summarization Settings + enableContextSummarization: undefined, + contextSummarizationTriggerThreshold: undefined, + contextSummarizationInitialStaticTurns: undefined, + contextSummarizationRecentTurns: undefined, + rateLimitSeconds: undefined, diffEnabled: undefined, fuzzyMatchThreshold: undefined, @@ -651,7 +663,7 @@ const globalSettingsRecord: GlobalSettingsRecord = { customSupportPrompts: undefined, enhancementApiConfigId: undefined, cachedChromeHostUrl: undefined, - historyPreviewCollapsed: undefined, + historyPreviewCollapsed: undefined, } export const GLOBAL_SETTINGS_KEYS = Object.keys(globalSettingsRecord) as Keys[] diff --git a/src/services/summarization/ContextSummarizer.ts b/src/services/summarization/ContextSummarizer.ts new file mode 100644 index 00000000000..a9e9dd26ece --- /dev/null +++ b/src/services/summarization/ContextSummarizer.ts @@ -0,0 +1,148 @@ +import { Anthropic } from "@anthropic-ai/sdk" + +import { ApiHandler } from "../../api" + +/** + * Service responsible for summarizing conversation history segments. + */ +export class ContextSummarizer { + private apiHandler: ApiHandler + + constructor(apiHandler: ApiHandler) { + this.apiHandler = apiHandler + // TODO: Consider if a specific, potentially faster/cheaper model should be configured for summarization, + // possibly by accepting a separate ApiConfiguration or model ID in the constructor. + } + + /** + * Summarizes a given array of conversation messages using an LLM. + * @param messagesToSummarize The array of messages to be summarized. + * @returns A promise that resolves to a new message object containing the summary, + * or null if summarization fails or is not possible. + */ + async summarize(messagesToSummarize: Anthropic.MessageParam[]): Promise { + if (messagesToSummarize.length === 0) { + return null // Nothing to summarize + } + + // Construct the prompt for the summarization model (User Final Refinement) + const systemPrompt = `You are a specialized context compression system for Roo-Code, a VS Code extension that enables AI coding agents. Your sole purpose is to condense conversation history while preserving maximum technical context with minimum tokens. + +**Context Schema:** +- You are summarizing the MIDDLE portion of a conversation +- The original system prompt and initial interactions remain intact before your summary +- Recent conversation turns remain intact after your summary +- Your summary will be the critical bridge connecting these preserved segments + +**Content Priorities (Highest to Lowest):** +1. **Code Context:** + - Repository structure and file relationships + - Code snippets with their functionality and modifications + - Bugs/errors encountered and their solutions + - API endpoints and data structures + - Implementation decisions and their rationales + +2. **Tool Usage:** + - Tools that were invoked and their outputs + - File operations performed (creation, reading, modification) + - Files examined or referenced + - Terminal commands executed + - External APIs or services utilized + +3. **Task Progress:** + - Original user requirements and specifications + - Current implementation status + - Remaining tasks or issues + - Alternative approaches discussed and decisions made + - User feedback on implementations + +4. **Technical Information:** + - Language/framework specifics + - Environment configuration details + - Performance considerations + - Security requirements + - Testing approaches + +**Output Requirements:** +- Produce ONLY the summary text with no meta-commentary +- Use precise, technical language optimized for information density +- Structure with minimal formatting (use ## for major sections if necessary) +- Omit pleasantries, acknowledgments, and conversational elements +- Format sequences of related facts as compact, semicolon-separated phrases +- Use minimal tokens while maximizing preserved information +- Prioritize factual over instructional content + +This summary must enable seamless conversation continuity with no perceived context loss between the earlier and later preserved segments.` + + // Format the messages for the prompt. Simple stringification might be too verbose or lose structure. + // Let's try a more readable format. + const formattedMessages = messagesToSummarize + .map((msg) => { + let contentText = "" + if (Array.isArray(msg.content)) { + contentText = msg.content + .map((block) => { + if (block.type === "text") return block.text + if (block.type === "image") return "[Image Content]" // Represent images concisely + // Add handling for other potential block types if necessary + return `[Unsupported Content: ${block.type}]` + }) + .join("\n") + } else { + contentText = msg.content + } + return `${msg.role.toUpperCase()}:\n${contentText}` + }) + .join("\n\n---\n\n") + + const userPrompt = `Please summarize the following conversation turns:\n\n${formattedMessages}` + + try { + // Use the configured API handler to make the summarization call + // Note: This uses the main configured model. Consider allowing a specific summarization model. + // Disable prompt caching for summarization calls? - Currently not directly supported per-call. + // It will use the handler's configured caching setting. + const stream = this.apiHandler.createMessage( + systemPrompt, + [{ role: "user", content: userPrompt }], + undefined, // No specific cache key for summarization + // { promptCachingEnabled: false } // Removed incorrect 4th argument + ) + + let summaryText = "" + let finalUsage = null + + // Consume the stream to get the full response + for await (const chunk of stream) { + if (chunk.type === "text") { + summaryText += chunk.text + } else if (chunk.type === "usage") { + // Capture usage details if needed for cost tracking/logging + finalUsage = chunk + } + } + + if (finalUsage) { + // Optional: Log summarization cost/tokens + console.log( + `[Summarization] Usage: In=${finalUsage.inputTokens}, Out=${finalUsage.outputTokens}, Cost=${finalUsage.totalCost?.toFixed(6) ?? "N/A"}`, + ) + } + + if (!summaryText || summaryText.trim() === "") { + console.warn("Context summarization resulted in an empty summary.") + return null + } + + // Return the summary as a user message, representing the summarized history. + return { + role: "user", // Represents the summarized user/assistant interaction leading up to the current point. + content: `[Summarized Conversation History]\n${summaryText.trim()}`, + } + } catch (error) { + console.error("Context summarization API call failed:", error) + // TODO: Add more robust error handling/logging (e.g., telemetry) + return null // Indicate failure + } + } +} diff --git a/src/shared/ExtensionMessage.ts b/src/shared/ExtensionMessage.ts index 48eaa20a45a..f95720d4a7e 100644 --- a/src/shared/ExtensionMessage.ts +++ b/src/shared/ExtensionMessage.ts @@ -163,6 +163,11 @@ export type ExtensionState = Pick< | "customModePrompts" | "customSupportPrompts" | "enhancementApiConfigId" + // Context Summarization Settings (Added) + | "enableContextSummarization" + | "contextSummarizationTriggerThreshold" + | "contextSummarizationInitialStaticTurns" + | "contextSummarizationRecentTurns" > & { version: string clineMessages: ClineMessage[] @@ -199,6 +204,12 @@ export type ExtensionState = Pick< renderContext: "sidebar" | "editor" settingsImportedAt?: number historyPreviewCollapsed?: boolean + + // Context Summarization Settings (Required part) + enableContextSummarization: boolean + contextSummarizationTriggerThreshold: number + contextSummarizationInitialStaticTurns: number + contextSummarizationRecentTurns: number } export type { ClineMessage, ClineAsk, ClineSay } diff --git a/src/shared/WebviewMessage.ts b/src/shared/WebviewMessage.ts index de419ff948e..d4abc548028 100644 --- a/src/shared/WebviewMessage.ts +++ b/src/shared/WebviewMessage.ts @@ -125,6 +125,11 @@ export interface WebviewMessage { | "searchFiles" | "toggleApiConfigPin" | "setHistoryPreviewCollapsed" + // Context Summarization Settings (Added) + | "enableContextSummarization" + | "contextSummarizationTriggerThreshold" + | "contextSummarizationInitialStaticTurns" + | "contextSummarizationRecentTurns" text?: string disabled?: boolean askResponse?: ClineAskResponse diff --git a/webview-ui/src/components/settings/ContextManagementSettings.tsx b/webview-ui/src/components/settings/ContextManagementSettings.tsx index 0f24316a12e..5df93e2214b 100644 --- a/webview-ui/src/components/settings/ContextManagementSettings.tsx +++ b/webview-ui/src/components/settings/ContextManagementSettings.tsx @@ -15,8 +15,22 @@ type ContextManagementSettingsProps = HTMLAttributes & { maxWorkspaceFiles: number showRooIgnoredFiles?: boolean maxReadFileLine?: number + // Context Summarization Props (Added) + enableContextSummarization?: boolean + contextSummarizationTriggerThreshold?: number + contextSummarizationInitialStaticTurns?: number + contextSummarizationRecentTurns?: number setCachedStateField: SetCachedStateField< - "maxOpenTabsContext" | "maxWorkspaceFiles" | "showRooIgnoredFiles" | "maxReadFileLine" + // Update type to include new keys + | "maxOpenTabsContext" + | "maxWorkspaceFiles" + | "showRooIgnoredFiles" + | "maxReadFileLine" + // Context Summarization Keys (Added) + | "enableContextSummarization" + | "contextSummarizationTriggerThreshold" + | "contextSummarizationInitialStaticTurns" + | "contextSummarizationRecentTurns" > } @@ -26,6 +40,11 @@ export const ContextManagementSettings = ({ showRooIgnoredFiles, setCachedStateField, maxReadFileLine, + // Context Summarization Props (Added) + enableContextSummarization, + contextSummarizationTriggerThreshold, + contextSummarizationInitialStaticTurns, + contextSummarizationRecentTurns, className, ...props }: ContextManagementSettingsProps) => { @@ -127,6 +146,115 @@ export const ContextManagementSettings = ({ {t("settings:contextManagement.maxReadFile.description")} + + {/* --- Context Summarization Settings --- */} +
+ +
+ setCachedStateField("enableContextSummarization", e.target.checked)} // Use generic setter + data-testid="enable-context-summarization-checkbox"> + + +
+ {t("settings:contextManagement.summarization.enable.description")} +
+
+ +
+
+ + {t("settings:contextManagement.summarization.triggerThreshold.label")} + +
+ { + const newValue = parseInt(e.target.value, 10) + if (!isNaN(newValue) && newValue >= 1 && newValue <= 100) { + setCachedStateField("contextSummarizationTriggerThreshold", newValue) // Use generic setter + } + }} + onClick={(e) => e.currentTarget.select()} + data-testid="context-summarization-trigger-threshold-input" + disabled={!enableContextSummarization} + /> + % +
+
+
+ {t("settings:contextManagement.summarization.triggerThreshold.description")} +
+
+ +
+
+ + {t("settings:contextManagement.summarization.initialTurns.label")} + +
+ { + const newValue = parseInt(e.target.value, 10) + if (!isNaN(newValue) && newValue >= 0) { + setCachedStateField("contextSummarizationInitialStaticTurns", newValue) // Use generic setter + } + }} + onClick={(e) => e.currentTarget.select()} + data-testid="context-summarization-initial-turns-input" + disabled={!enableContextSummarization} + /> + {t("settings:contextManagement.summarization.turns")} +
+
+
+ {t("settings:contextManagement.summarization.initialTurns.description")} +
+
+ +
+
+ + {t("settings:contextManagement.summarization.recentTurns.label")} + +
+ { + const newValue = parseInt(e.target.value, 10) + if (!isNaN(newValue) && newValue >= 0) { + setCachedStateField("contextSummarizationRecentTurns", newValue) // Use generic setter + } + }} + onClick={(e) => e.currentTarget.select()} + data-testid="context-summarization-recent-turns-input" + disabled={!enableContextSummarization} + /> + {t("settings:contextManagement.summarization.turns")} +
+
+
+ {t("settings:contextManagement.summarization.recentTurns.description")} +
+
+ {/* --- End Context Summarization Settings --- */} ) diff --git a/webview-ui/src/components/settings/SettingsView.tsx b/webview-ui/src/components/settings/SettingsView.tsx index 7205b043707..60974cf2a55 100644 --- a/webview-ui/src/components/settings/SettingsView.tsx +++ b/webview-ui/src/components/settings/SettingsView.tsx @@ -257,6 +257,21 @@ const SettingsView = forwardRef(({ onDone, t vscode.postMessage({ type: "updateExperimental", values: experiments }) vscode.postMessage({ type: "alwaysAllowModeSwitch", bool: alwaysAllowModeSwitch }) vscode.postMessage({ type: "alwaysAllowSubtasks", bool: alwaysAllowSubtasks }) + // Context Summarization Settings (Added - Use cachedState values) + vscode.postMessage({ type: "enableContextSummarization", bool: cachedState.enableContextSummarization }) + vscode.postMessage({ + type: "contextSummarizationTriggerThreshold", + value: cachedState.contextSummarizationTriggerThreshold, + }) + vscode.postMessage({ + type: "contextSummarizationInitialStaticTurns", + value: cachedState.contextSummarizationInitialStaticTurns, + }) + vscode.postMessage({ + type: "contextSummarizationRecentTurns", + value: cachedState.contextSummarizationRecentTurns, + }) + // --- End Context Summarization --- vscode.postMessage({ type: "upsertApiConfiguration", text: currentApiConfigName, apiConfiguration }) vscode.postMessage({ type: "telemetrySetting", text: telemetrySetting }) setChangeDetected(false) @@ -479,6 +494,12 @@ const SettingsView = forwardRef(({ onDone, t showRooIgnoredFiles={showRooIgnoredFiles} maxReadFileLine={maxReadFileLine} setCachedStateField={setCachedStateField} + // Pass summarization state from cachedState (Added) + enableContextSummarization={cachedState.enableContextSummarization} + contextSummarizationTriggerThreshold={cachedState.contextSummarizationTriggerThreshold} + contextSummarizationInitialStaticTurns={cachedState.contextSummarizationInitialStaticTurns} + contextSummarizationRecentTurns={cachedState.contextSummarizationRecentTurns} + // Removed specific setters - ContextManagementSettings uses setCachedStateField now /> diff --git a/webview-ui/src/components/settings/__tests__/ContextManagementSettings.test.tsx b/webview-ui/src/components/settings/__tests__/ContextManagementSettings.test.tsx index 955ce619369..fa97c35daec 100644 --- a/webview-ui/src/components/settings/__tests__/ContextManagementSettings.test.tsx +++ b/webview-ui/src/components/settings/__tests__/ContextManagementSettings.test.tsx @@ -30,6 +30,16 @@ describe("ContextManagementSettings", () => { maxWorkspaceFiles: 200, showRooIgnoredFiles: false, setCachedStateField: jest.fn(), + // Add mock setters for new props (Added) + setEnableContextSummarization: jest.fn(), + setContextSummarizationTriggerThreshold: jest.fn(), + setContextSummarizationInitialStaticTurns: jest.fn(), + setContextSummarizationRecentTurns: jest.fn(), + // Add default values for new state props (Added) + enableContextSummarization: false, + contextSummarizationTriggerThreshold: 80, + contextSummarizationInitialStaticTurns: 5, + contextSummarizationRecentTurns: 10, } beforeEach(() => { @@ -51,6 +61,17 @@ describe("ContextManagementSettings", () => { const showRooIgnoredFilesCheckbox = screen.getByTestId("show-rooignored-files-checkbox") expect(showRooIgnoredFilesCheckbox).toBeInTheDocument() expect(screen.getByTestId("show-rooignored-files-checkbox")).not.toBeChecked() + + // Summarization controls (Added) + expect(screen.getByTestId("enable-context-summarization-checkbox")).toBeInTheDocument() + expect(screen.getByTestId("context-summarization-trigger-threshold-input")).toBeInTheDocument() + expect(screen.getByTestId("context-summarization-initial-turns-input")).toBeInTheDocument() + expect(screen.getByTestId("context-summarization-recent-turns-input")).toBeInTheDocument() + + // Check initial disabled state for sub-settings (Added) + expect(screen.getByTestId("context-summarization-trigger-threshold-input")).toBeDisabled() + expect(screen.getByTestId("context-summarization-initial-turns-input")).toBeDisabled() + expect(screen.getByTestId("context-summarization-recent-turns-input")).toBeDisabled() }) it("updates open tabs context limit", () => { @@ -79,4 +100,42 @@ describe("ContextManagementSettings", () => { expect(defaultProps.setCachedStateField).toHaveBeenCalledWith("showRooIgnoredFiles", true) }) + + // --- Tests for new summarization settings --- (Added) + + it("enables sub-settings when summarization is enabled", () => { + render() + + expect(screen.getByTestId("context-summarization-trigger-threshold-input")).not.toBeDisabled() + expect(screen.getByTestId("context-summarization-initial-turns-input")).not.toBeDisabled() + expect(screen.getByTestId("context-summarization-recent-turns-input")).not.toBeDisabled() + }) + + it("updates enable context summarization setting", () => { + render() + const checkbox = screen.getByTestId("enable-context-summarization-checkbox") + fireEvent.click(checkbox) + expect(defaultProps.setEnableContextSummarization).toHaveBeenCalledWith(true) + }) + + it("updates summarization trigger threshold", () => { + render() // Enable first + const input = screen.getByTestId("context-summarization-trigger-threshold-input") + fireEvent.change(input, { target: { value: "95" } }) + expect(defaultProps.setContextSummarizationTriggerThreshold).toHaveBeenCalledWith(95) + }) + + it("updates initial turns to keep", () => { + render() // Enable first + const input = screen.getByTestId("context-summarization-initial-turns-input") + fireEvent.change(input, { target: { value: "3" } }) + expect(defaultProps.setContextSummarizationInitialStaticTurns).toHaveBeenCalledWith(3) + }) + + it("updates recent turns to keep", () => { + render() // Enable first + const input = screen.getByTestId("context-summarization-recent-turns-input") + fireEvent.change(input, { target: { value: "12" } }) + expect(defaultProps.setContextSummarizationRecentTurns).toHaveBeenCalledWith(12) + }) }) diff --git a/webview-ui/src/context/ExtensionStateContext.tsx b/webview-ui/src/context/ExtensionStateContext.tsx index a7292928fd5..2e210ba1dd4 100644 --- a/webview-ui/src/context/ExtensionStateContext.tsx +++ b/webview-ui/src/context/ExtensionStateContext.tsx @@ -91,6 +91,16 @@ export interface ExtensionStateContextType extends ExtensionState { terminalCompressProgressBar?: boolean setTerminalCompressProgressBar: (value: boolean) => void setHistoryPreviewCollapsed: (value: boolean) => void + + // Context Summarization Setters (Added) + enableContextSummarization: boolean + setEnableContextSummarization: (value: boolean) => void + contextSummarizationTriggerThreshold: number + setContextSummarizationTriggerThreshold: (value: number) => void + contextSummarizationInitialStaticTurns: number + setContextSummarizationInitialStaticTurns: (value: number) => void + contextSummarizationRecentTurns: number + setContextSummarizationRecentTurns: (value: number) => void } export const ExtensionStateContext = createContext(undefined) @@ -169,6 +179,12 @@ export const ExtensionStateContextProvider: React.FC<{ children: React.ReactNode terminalZdotdir: false, // Default ZDOTDIR handling setting terminalCompressProgressBar: true, // Default to compress progress bar output historyPreviewCollapsed: false, // Initialize the new state (default to expanded) + + // Context Summarization Defaults (Added) + enableContextSummarization: false, + contextSummarizationTriggerThreshold: 80, + contextSummarizationInitialStaticTurns: 5, + contextSummarizationRecentTurns: 10, }) const [didHydrateState, setDidHydrateState] = useState(false) @@ -341,6 +357,24 @@ export const ExtensionStateContextProvider: React.FC<{ children: React.ReactNode }), setHistoryPreviewCollapsed: (value) => setState((prevState) => ({ ...prevState, historyPreviewCollapsed: value })), // Implement the setter + + // Context Summarization Setters Implementation (Added) + setEnableContextSummarization: (value) => { + setState((prevState) => ({ ...prevState, enableContextSummarization: value })) + vscode.postMessage({ type: "enableContextSummarization", bool: value }) + }, + setContextSummarizationTriggerThreshold: (value) => { + setState((prevState) => ({ ...prevState, contextSummarizationTriggerThreshold: value })) + vscode.postMessage({ type: "contextSummarizationTriggerThreshold", value }) + }, + setContextSummarizationInitialStaticTurns: (value) => { + setState((prevState) => ({ ...prevState, contextSummarizationInitialStaticTurns: value })) + vscode.postMessage({ type: "contextSummarizationInitialStaticTurns", value }) + }, + setContextSummarizationRecentTurns: (value) => { + setState((prevState) => ({ ...prevState, contextSummarizationRecentTurns: value })) + vscode.postMessage({ type: "contextSummarizationRecentTurns", value }) + }, } return {children} diff --git a/webview-ui/src/context/__tests__/ExtensionStateContext.test.tsx b/webview-ui/src/context/__tests__/ExtensionStateContext.test.tsx index 1ba2d87e9a4..7b180dcd297 100644 --- a/webview-ui/src/context/__tests__/ExtensionStateContext.test.tsx +++ b/webview-ui/src/context/__tests__/ExtensionStateContext.test.tsx @@ -202,6 +202,11 @@ describe("mergeExtensionState", () => { showRooIgnoredFiles: true, renderContext: "sidebar", maxReadFileLine: 500, + // Context Summarization Defaults (Added for test) + enableContextSummarization: false, + contextSummarizationTriggerThreshold: 80, + contextSummarizationInitialStaticTurns: 5, + contextSummarizationRecentTurns: 10, } const prevState: ExtensionState = { diff --git a/webview-ui/src/i18n/locales/en/settings.json b/webview-ui/src/i18n/locales/en/settings.json index 4a81e77ae99..d9869032570 100644 --- a/webview-ui/src/i18n/locales/en/settings.json +++ b/webview-ui/src/i18n/locales/en/settings.json @@ -300,6 +300,25 @@ "description": "Roo reads this number of lines when the model omits start/end values. If this number is less than the file's total, Roo generates a line number index of code definitions. Special cases: -1 instructs Roo to read the entire file (without indexing), and 0 instructs it to read no lines and provides line indexes only for minimal context. Lower values minimize initial context usage, enabling precise subsequent line-range reads. Explicit start/end requests are not limited by this setting.", "lines": "lines", "always_full_read": "Always read entire file" + }, + "summarization": { + "enable": { + "label": "Enable context summarization", + "description": "When enabled, older conversation turns will be summarized instead of truncated when the context limit is approached. This preserves more information but incurs additional token costs for summarization." + }, + "triggerThreshold": { + "label": "Summarization trigger threshold", + "description": "Percentage of the context window size at which summarization should be triggered (e.g., 80%)." + }, + "initialTurns": { + "label": "Initial turns to keep", + "description": "Number of initial conversation messages (system prompt + user/assistant) to always keep in full detail." + }, + "recentTurns": { + "label": "Recent turns to keep", + "description": "Number of the most recent conversation messages to always keep in full detail." + }, + "turns": "messages" } }, "terminal": { From 9419866066a56a8c039590fae5761c9a93442f9d Mon Sep 17 00:00:00 2001 From: Shariq Riaz Date: Sun, 27 Apr 2025 14:15:20 +0500 Subject: [PATCH 02/16] types --- src/exports/types.ts | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/exports/types.ts b/src/exports/types.ts index 3dc88667991..b009dbdc297 100644 --- a/src/exports/types.ts +++ b/src/exports/types.ts @@ -218,6 +218,10 @@ type GlobalSettings = { terminalZshP10k?: boolean | undefined terminalZdotdir?: boolean | undefined terminalCompressProgressBar?: boolean | undefined + enableContextSummarization?: boolean | undefined + contextSummarizationTriggerThreshold?: number | undefined + contextSummarizationInitialStaticTurns?: number | undefined + contextSummarizationRecentTurns?: number | undefined rateLimitSeconds?: number | undefined diffEnabled?: boolean | undefined fuzzyMatchThreshold?: number | undefined From a199005e6523ee1d2469458342683269cc78488e Mon Sep 17 00:00:00 2001 From: Shariq Riaz Date: Mon, 28 Apr 2025 20:50:44 +0500 Subject: [PATCH 03/16] feat(i18n): add missing context summarization translations --- webview-ui/src/i18n/locales/ca/settings.json | 19 +++++++++++++++++++ webview-ui/src/i18n/locales/de/settings.json | 19 +++++++++++++++++++ webview-ui/src/i18n/locales/es/settings.json | 19 +++++++++++++++++++ webview-ui/src/i18n/locales/fr/settings.json | 19 +++++++++++++++++++ webview-ui/src/i18n/locales/hi/settings.json | 19 +++++++++++++++++++ webview-ui/src/i18n/locales/it/settings.json | 19 +++++++++++++++++++ webview-ui/src/i18n/locales/ja/settings.json | 19 +++++++++++++++++++ webview-ui/src/i18n/locales/ko/settings.json | 19 +++++++++++++++++++ webview-ui/src/i18n/locales/pl/settings.json | 19 +++++++++++++++++++ .../src/i18n/locales/pt-BR/settings.json | 19 +++++++++++++++++++ webview-ui/src/i18n/locales/ru/settings.json | 19 +++++++++++++++++++ webview-ui/src/i18n/locales/tr/settings.json | 19 +++++++++++++++++++ webview-ui/src/i18n/locales/vi/settings.json | 19 +++++++++++++++++++ .../src/i18n/locales/zh-CN/settings.json | 19 +++++++++++++++++++ .../src/i18n/locales/zh-TW/settings.json | 19 +++++++++++++++++++ 15 files changed, 285 insertions(+) diff --git a/webview-ui/src/i18n/locales/ca/settings.json b/webview-ui/src/i18n/locales/ca/settings.json index 16f0b833b37..45d7a8d5261 100644 --- a/webview-ui/src/i18n/locales/ca/settings.json +++ b/webview-ui/src/i18n/locales/ca/settings.json @@ -300,6 +300,25 @@ "description": "Roo llegeix aquest nombre de línies quan el model omet els valors d'inici/final. Si aquest nombre és menor que el total del fitxer, Roo genera un índex de números de línia de les definicions de codi. Casos especials: -1 indica a Roo que llegeixi tot el fitxer (sense indexació), i 0 indica que no llegeixi cap línia i proporcioni només índexs de línia per a un context mínim. Valors més baixos minimitzen l'ús inicial de context, permetent lectures posteriors de rangs de línies precisos. Les sol·licituds amb inici/final explícits no estan limitades per aquesta configuració.", "lines": "línies", "always_full_read": "Llegeix sempre el fitxer sencer" + }, + "summarization": { + "enable": { + "label": "Activar resum de context", + "description": "Quan està activat, els torns de conversa més antics es resumiran en lloc de truncar-se quan s'acosti el límit de context. Això conserva més informació però implica costos addicionals de token per al resum." + }, + "triggerThreshold": { + "label": "Llindar d'activació del resum", + "description": "Percentatge de la mida de la finestra de context en què s'ha d'activar el resum (p. ex., 80%)." + }, + "initialTurns": { + "label": "Torns inicials a mantenir", + "description": "Nombre de missatges inicials de conversa (prompt del sistema + usuari/assistent) a mantenir sempre amb detall complet." + }, + "recentTurns": { + "label": "Torns recents a mantenir", + "description": "Nombre dels missatges de conversa més recents a mantenir sempre amb detall complet." + }, + "turns": "missatges" } }, "terminal": { diff --git a/webview-ui/src/i18n/locales/de/settings.json b/webview-ui/src/i18n/locales/de/settings.json index 2307eee89c7..43761866407 100644 --- a/webview-ui/src/i18n/locales/de/settings.json +++ b/webview-ui/src/i18n/locales/de/settings.json @@ -300,6 +300,25 @@ "description": "Roo liest diese Anzahl von Zeilen, wenn das Modell keine Start-/Endwerte angibt. Wenn diese Zahl kleiner als die Gesamtzahl der Zeilen ist, erstellt Roo einen Zeilennummernindex der Codedefinitionen. Spezialfälle: -1 weist Roo an, die gesamte Datei zu lesen (ohne Indexierung), und 0 weist an, keine Zeilen zu lesen und nur Zeilenindizes für minimalen Kontext bereitzustellen. Niedrigere Werte minimieren die anfängliche Kontextnutzung und ermöglichen präzise nachfolgende Zeilenbereich-Lesungen. Explizite Start-/End-Anfragen sind von dieser Einstellung nicht begrenzt.", "lines": "Zeilen", "always_full_read": "Immer die gesamte Datei lesen" + }, + "summarization": { + "enable": { + "label": "Kontextzusammenfassung aktivieren", + "description": "Wenn aktiviert, werden ältere Gesprächsrunden zusammengefasst statt abgeschnitten, wenn das Kontextlimit erreicht wird. Dies bewahrt mehr Informationen, verursacht aber zusätzliche Token-Kosten für die Zusammenfassung." + }, + "triggerThreshold": { + "label": "Auslöseschwelle für Zusammenfassung", + "description": "Prozentsatz der Kontextfenstergröße, bei dem die Zusammenfassung ausgelöst werden soll (z. B. 80%)." + }, + "initialTurns": { + "label": "Anfängliche Runden beibehalten", + "description": "Anzahl der anfänglichen Gesprächsnachrichten (System-Prompt + Benutzer/Assistent), die immer vollständig detailliert beibehalten werden sollen." + }, + "recentTurns": { + "label": "Letzte Runden beibehalten", + "description": "Anzahl der letzten Gesprächsnachrichten, die immer vollständig detailliert beibehalten werden sollen." + }, + "turns": "Nachrichten" } }, "terminal": { diff --git a/webview-ui/src/i18n/locales/es/settings.json b/webview-ui/src/i18n/locales/es/settings.json index 9a481b4beed..c78680c9230 100644 --- a/webview-ui/src/i18n/locales/es/settings.json +++ b/webview-ui/src/i18n/locales/es/settings.json @@ -300,6 +300,25 @@ "description": "Roo lee este número de líneas cuando el modelo omite valores de inicio/fin. Si este número es menor que el total del archivo, Roo genera un índice de números de línea de las definiciones de código. Casos especiales: -1 indica a Roo que lea el archivo completo (sin indexación), y 0 indica que no lea líneas y proporcione solo índices de línea para un contexto mínimo. Valores más bajos minimizan el uso inicial de contexto, permitiendo lecturas posteriores de rangos de líneas precisos. Las solicitudes con inicio/fin explícitos no están limitadas por esta configuración.", "lines": "líneas", "always_full_read": "Siempre leer el archivo completo" + }, + "summarization": { + "enable": { + "label": "Habilitar resumen de contexto", + "description": "Cuando está habilitado, los turnos de conversación más antiguos se resumirán en lugar de truncarse cuando se acerque el límite de contexto. Esto conserva más información pero incurre en costos adicionales de token para el resumen." + }, + "triggerThreshold": { + "label": "Umbral de activación del resumen", + "description": "Porcentaje del tamaño de la ventana de contexto en el que se debe activar el resumen (p. ej., 80%)." + }, + "initialTurns": { + "label": "Turnos iniciales a mantener", + "description": "Número de mensajes iniciales de la conversación (prompt del sistema + usuario/asistente) que siempre se mantendrán con todo detalle." + }, + "recentTurns": { + "label": "Turnos recientes a mantener", + "description": "Número de los mensajes de conversación más recientes que siempre se mantendrán con todo detalle." + }, + "turns": "mensajes" } }, "terminal": { diff --git a/webview-ui/src/i18n/locales/fr/settings.json b/webview-ui/src/i18n/locales/fr/settings.json index b0032980ee8..805e01af4da 100644 --- a/webview-ui/src/i18n/locales/fr/settings.json +++ b/webview-ui/src/i18n/locales/fr/settings.json @@ -300,6 +300,25 @@ "description": "Roo lit ce nombre de lignes lorsque le modèle omet les valeurs de début/fin. Si ce nombre est inférieur au total du fichier, Roo génère un index des numéros de ligne des définitions de code. Cas spéciaux : -1 indique à Roo de lire le fichier entier (sans indexation), et 0 indique de ne lire aucune ligne et de fournir uniquement les index de ligne pour un contexte minimal. Des valeurs plus basses minimisent l'utilisation initiale du contexte, permettant des lectures ultérieures de plages de lignes précises. Les requêtes avec début/fin explicites ne sont pas limitées par ce paramètre.", "lines": "lignes", "always_full_read": "Toujours lire le fichier entier" + }, + "summarization": { + "enable": { + "label": "Activer le résumé du contexte", + "description": "Lorsque cette option est activée, les anciens tours de conversation seront résumés au lieu d'être tronqués à l'approche de la limite de contexte. Cela préserve plus d'informations mais entraîne des coûts de token supplémentaires pour le résumé." + }, + "triggerThreshold": { + "label": "Seuil de déclenchement du résumé", + "description": "Pourcentage de la taille de la fenêtre de contexte auquel le résumé doit être déclenché (par ex., 80%)." + }, + "initialTurns": { + "label": "Tours initiaux à conserver", + "description": "Nombre de messages de conversation initiaux (prompt système + utilisateur/assistant) à toujours conserver en détail." + }, + "recentTurns": { + "label": "Tours récents à conserver", + "description": "Nombre des messages de conversation les plus récents à toujours conserver en détail." + }, + "turns": "messages" } }, "terminal": { diff --git a/webview-ui/src/i18n/locales/hi/settings.json b/webview-ui/src/i18n/locales/hi/settings.json index ba0de0c9d39..36791ef033d 100644 --- a/webview-ui/src/i18n/locales/hi/settings.json +++ b/webview-ui/src/i18n/locales/hi/settings.json @@ -300,6 +300,25 @@ "description": "जब मॉडल प्रारंभ/अंत मान नहीं देता है, तो Roo इतनी पंक्तियाँ पढ़ता है। यदि यह संख्या फ़ाइल की कुल पंक्तियों से कम है, तो Roo कोड परिभाषाओं का पंक्ति क्रमांक इंडेक्स बनाता है। विशेष मामले: -1 Roo को पूरी फ़ाइल पढ़ने का निर्देश देता है (इंडेक्सिंग के बिना), और 0 कोई पंक्ति न पढ़ने और न्यूनतम संदर्भ के लिए केवल पंक्ति इंडेक्स प्रदान करने का निर्देश देता है। कम मान प्रारंभिक संदर्भ उपयोग को कम करते हैं, जो बाद में सटीक पंक्ति श्रेणी पढ़ने की अनुमति देता है। स्पष्ट प्रारंभ/अंत अनुरोध इस सेटिंग से सीमित नहीं हैं।", "lines": "पंक्तियाँ", "always_full_read": "हमेशा पूरी फ़ाइल पढ़ें" + }, + "summarization": { + "enable": { + "label": "संदर्भ सारांश सक्षम करें", + "description": "सक्षम होने पर, संदर्भ सीमा के करीब पहुंचने पर पुरानी बातचीत के दौरों को छोटा करने के बजाय सारांशित किया जाएगा। यह अधिक जानकारी सुरक्षित रखता है लेकिन सारांश के लिए अतिरिक्त टोकन लागत लगती है।" + }, + "triggerThreshold": { + "label": "सारांश ट्रिगर थ्रेशोल्ड", + "description": "संदर्भ विंडो आकार का प्रतिशत जिस पर सारांश ट्रिगर किया जाना चाहिए (उदाहरण के लिए, 80%)।" + }, + "initialTurns": { + "label": "शुरुआती दौर बनाए रखें", + "description": "शुरुआती बातचीत संदेशों की संख्या (सिस्टम प्रॉम्प्ट + उपयोगकर्ता/सहायक) जिन्हें हमेशा पूरे विवरण में रखा जाना चाहिए।" + }, + "recentTurns": { + "label": "हाल के दौर बनाए रखें", + "description": "सबसे हाल के बातचीत संदेशों की संख्या जिन्हें हमेशा पूरे विवरण में रखा जाना चाहिए।" + }, + "turns": "संदेश" } }, "terminal": { diff --git a/webview-ui/src/i18n/locales/it/settings.json b/webview-ui/src/i18n/locales/it/settings.json index 2eb8b38655b..37503ffb5e6 100644 --- a/webview-ui/src/i18n/locales/it/settings.json +++ b/webview-ui/src/i18n/locales/it/settings.json @@ -300,6 +300,25 @@ "description": "Roo legge questo numero di righe quando il modello omette i valori di inizio/fine. Se questo numero è inferiore al totale del file, Roo genera un indice dei numeri di riga delle definizioni di codice. Casi speciali: -1 indica a Roo di leggere l'intero file (senza indicizzazione), e 0 indica di non leggere righe e fornire solo indici di riga per un contesto minimo. Valori più bassi minimizzano l'utilizzo iniziale del contesto, permettendo successive letture precise di intervalli di righe. Le richieste con inizio/fine espliciti non sono limitate da questa impostazione.", "lines": "righe", "always_full_read": "Leggi sempre l'intero file" + }, + "summarization": { + "enable": { + "label": "Abilita riassunto del contesto", + "description": "Se abilitato, i turni di conversazione più vecchi verranno riassunti invece di essere troncati all'avvicinarsi del limite di contesto. Ciò preserva più informazioni ma comporta costi aggiuntivi di token per il riassunto." + }, + "triggerThreshold": { + "label": "Soglia di attivazione del riassunto", + "description": "Percentuale della dimensione della finestra di contesto alla quale attivare il riassunto (es. 80%)." + }, + "initialTurns": { + "label": "Turni iniziali da mantenere", + "description": "Numero di messaggi di conversazione iniziali (prompt di sistema + utente/assistente) da mantenere sempre con tutti i dettagli." + }, + "recentTurns": { + "label": "Turni recenti da mantenere", + "description": "Numero dei messaggi di conversazione più recenti da mantenere sempre con tutti i dettagli." + }, + "turns": "messaggi" } }, "terminal": { diff --git a/webview-ui/src/i18n/locales/ja/settings.json b/webview-ui/src/i18n/locales/ja/settings.json index 2276464bbd0..d6e175ff50e 100644 --- a/webview-ui/src/i18n/locales/ja/settings.json +++ b/webview-ui/src/i18n/locales/ja/settings.json @@ -300,6 +300,25 @@ "description": "モデルが開始/終了の値を指定しない場合、Rooはこの行数を読み込みます。この数がファイルの総行数より少ない場合、Rooはコード定義の行番号インデックスを生成します。特殊なケース:-1はRooにファイル全体を読み込むよう指示し(インデックス作成なし)、0は行を読み込まず最小限のコンテキストのために行インデックスのみを提供するよう指示します。低い値は初期コンテキスト使用量を最小限に抑え、後続の正確な行範囲の読み込みを可能にします。明示的な開始/終了の要求はこの設定による制限を受けません。", "lines": "行", "always_full_read": "常にファイル全体を読み込む" + }, + "summarization": { + "enable": { + "label": "コンテキスト要約を有効にする", + "description": "有効にすると、コンテキスト制限に近づいたときに古い会話のターンが切り捨てられる代わりに要約されます。これにより、より多くの情報が保持されますが、要約に追加のトークンコストが発生します。" + }, + "triggerThreshold": { + "label": "要約トリガーしきい値", + "description": "要約をトリガーするコンテキストウィンドウサイズの割合(例:80%)。" + }, + "initialTurns": { + "label": "保持する初期ターン", + "description": "常に詳細に保持する初期の会話メッセージ(システムプロンプト+ユーザー/アシスタント)の数。" + }, + "recentTurns": { + "label": "保持する最近のターン", + "description": "常に詳細に保持する最新の会話メッセージの数。" + }, + "turns": "メッセージ" } }, "terminal": { diff --git a/webview-ui/src/i18n/locales/ko/settings.json b/webview-ui/src/i18n/locales/ko/settings.json index 414dd93e437..f5c4d0071b7 100644 --- a/webview-ui/src/i18n/locales/ko/settings.json +++ b/webview-ui/src/i18n/locales/ko/settings.json @@ -300,6 +300,25 @@ "description": "모델이 시작/끝 값을 지정하지 않을 때 Roo가 읽는 줄 수입니다. 이 수가 파일의 총 줄 수보다 적으면 Roo는 코드 정의의 줄 번호 인덱스를 생성합니다. 특수한 경우: -1은 Roo에게 전체 파일을 읽도록 지시하고(인덱싱 없이), 0은 줄을 읽지 않고 최소한의 컨텍스트를 위해 줄 인덱스만 제공하도록 지시합니다. 낮은 값은 초기 컨텍스트 사용을 최소화하고, 이후 정확한 줄 범위 읽기를 가능하게 합니다. 명시적 시작/끝 요청은 이 설정의 제한을 받지 않습니다.", "lines": "줄", "always_full_read": "항상 전체 파일 읽기" + }, + "summarization": { + "enable": { + "label": "컨텍스트 요약 활성화", + "description": "활성화하면 컨텍스트 제한에 가까워질 때 이전 대화 턴이 잘리는 대신 요약됩니다. 이렇게 하면 더 많은 정보가 보존되지만 요약에 추가 토큰 비용이 발생합니다." + }, + "triggerThreshold": { + "label": "요약 트리거 임계값", + "description": "요약이 트리거되어야 하는 컨텍스트 창 크기의 백분율(예: 80%)." + }, + "initialTurns": { + "label": "유지할 초기 턴", + "description": "항상 전체 세부 정보로 유지할 초기 대화 메시지 수(시스템 프롬프트 + 사용자/어시스턴트)." + }, + "recentTurns": { + "label": "유지할 최근 턴", + "description": "항상 전체 세부 정보로 유지할 가장 최근 대화 메시지 수." + }, + "turns": "메시지" } }, "terminal": { diff --git a/webview-ui/src/i18n/locales/pl/settings.json b/webview-ui/src/i18n/locales/pl/settings.json index 72054137924..c4ee2aa42e8 100644 --- a/webview-ui/src/i18n/locales/pl/settings.json +++ b/webview-ui/src/i18n/locales/pl/settings.json @@ -300,6 +300,25 @@ "description": "Roo odczytuje tę liczbę linii, gdy model nie określa wartości początkowej/końcowej. Jeśli ta liczba jest mniejsza niż całkowita liczba linii pliku, Roo generuje indeks numerów linii definicji kodu. Przypadki specjalne: -1 nakazuje Roo odczytać cały plik (bez indeksowania), a 0 nakazuje nie czytać żadnych linii i dostarczyć tylko indeksy linii dla minimalnego kontekstu. Niższe wartości minimalizują początkowe użycie kontekstu, umożliwiając późniejsze precyzyjne odczyty zakresów linii. Jawne żądania początku/końca nie są ograniczone tym ustawieniem.", "lines": "linii", "always_full_read": "Zawsze czytaj cały plik" + }, + "summarization": { + "enable": { + "label": "Włącz podsumowanie kontekstu", + "description": "Gdy włączone, starsze tury konwersacji będą podsumowywane zamiast obcinane, gdy zbliża się limit kontekstu. Zachowuje to więcej informacji, ale wiąże się z dodatkowymi kosztami tokenów za podsumowanie." + }, + "triggerThreshold": { + "label": "Próg wyzwalania podsumowania", + "description": "Procent rozmiaru okna kontekstu, przy którym powinno zostać wyzwolone podsumowanie (np. 80%)." + }, + "initialTurns": { + "label": "Początkowe tury do zachowania", + "description": "Liczba początkowych wiadomości konwersacji (prompt systemowy + użytkownik/asystent), które zawsze mają być zachowane w pełnej szczegółowości." + }, + "recentTurns": { + "label": "Ostatnie tury do zachowania", + "description": "Liczba najnowszych wiadomości konwersacji, które zawsze mają być zachowane w pełnej szczegółowości." + }, + "turns": "wiadomości" } }, "terminal": { diff --git a/webview-ui/src/i18n/locales/pt-BR/settings.json b/webview-ui/src/i18n/locales/pt-BR/settings.json index 974f0a7b6b3..c954663dd6c 100644 --- a/webview-ui/src/i18n/locales/pt-BR/settings.json +++ b/webview-ui/src/i18n/locales/pt-BR/settings.json @@ -300,6 +300,25 @@ "description": "O Roo lê este número de linhas quando o modelo omite valores de início/fim. Se este número for menor que o total do arquivo, o Roo gera um índice de números de linha das definições de código. Casos especiais: -1 instrui o Roo a ler o arquivo inteiro (sem indexação), e 0 instrui a não ler linhas e fornecer apenas índices de linha para contexto mínimo. Valores mais baixos minimizam o uso inicial de contexto, permitindo leituras posteriores precisas de intervalos de linhas. Requisições com início/fim explícitos não são limitadas por esta configuração.", "lines": "linhas", "always_full_read": "Sempre ler o arquivo inteiro" + }, + "summarization": { + "enable": { + "label": "Habilitar resumo de contexto", + "description": "Quando habilitado, os turnos de conversa mais antigos serão resumidos em vez de truncados quando o limite de contexto for atingido. Isso preserva mais informações, mas incorre em custos adicionais de token para o resumo." + }, + "triggerThreshold": { + "label": "Limite de gatilho do resumo", + "description": "Percentual do tamanho da janela de contexto em que o resumo deve ser acionado (por exemplo, 80%)." + }, + "initialTurns": { + "label": "Turnos iniciais a manter", + "description": "Número de mensagens iniciais da conversa (prompt do sistema + usuário/assistente) a serem sempre mantidas em detalhes completos." + }, + "recentTurns": { + "label": "Turnos recentes a manter", + "description": "Número das mensagens de conversa mais recentes a serem sempre mantidas em detalhes completos." + }, + "turns": "mensagens" } }, "terminal": { diff --git a/webview-ui/src/i18n/locales/ru/settings.json b/webview-ui/src/i18n/locales/ru/settings.json index 37c080bfe62..7ea6a0a53dd 100644 --- a/webview-ui/src/i18n/locales/ru/settings.json +++ b/webview-ui/src/i18n/locales/ru/settings.json @@ -300,6 +300,25 @@ "description": "Roo читает столько строк, если модель не указала явно начало/конец. Если число меньше общего количества строк в файле, Roo создаёт индекс определений кода по строкам. Особые случаи: -1 — Roo читает весь файл (без индексации), 0 — не читает строки, а создаёт только минимальный индекс. Меньшие значения минимизируют начальный контекст, позволяя точнее читать нужные диапазоны строк. Явные запросы начала/конца не ограничиваются этим параметром.", "lines": "строк", "always_full_read": "Всегда читать весь файл" + }, + "summarization": { + "enable": { + "label": "Включить суммирование контекста", + "description": "Если включено, старые ходы беседы будут суммироваться, а не обрезаться при приближении к лимиту контекста. Это сохраняет больше информации, но влечет за собой дополнительные затраты токенов на суммирование." + }, + "triggerThreshold": { + "label": "Порог срабатывания суммирования", + "description": "Процент размера окна контекста, при котором должно срабатывать суммирование (например, 80%)." + }, + "initialTurns": { + "label": "Начальные ходы для сохранения", + "description": "Количество начальных сообщений беседы (системный промпт + пользователь/ассистент), которые всегда должны сохраняться в полной детализации." + }, + "recentTurns": { + "label": "Последние ходы для сохранения", + "description": "Количество самых последних сообщений беседы, которые всегда должны сохраняться в полной детализации." + }, + "turns": "сообщения" } }, "terminal": { diff --git a/webview-ui/src/i18n/locales/tr/settings.json b/webview-ui/src/i18n/locales/tr/settings.json index c47f4287606..c54d4659a47 100644 --- a/webview-ui/src/i18n/locales/tr/settings.json +++ b/webview-ui/src/i18n/locales/tr/settings.json @@ -300,6 +300,25 @@ "description": "Model başlangıç/bitiş değerlerini belirtmediğinde Roo bu sayıda satırı okur. Bu sayı dosyanın toplam satır sayısından azsa, Roo kod tanımlamalarının satır numarası dizinini oluşturur. Özel durumlar: -1, Roo'ya tüm dosyayı okumasını (dizinleme olmadan), 0 ise hiç satır okumamasını ve minimum bağlam için yalnızca satır dizinleri sağlamasını belirtir. Düşük değerler başlangıç bağlam kullanımını en aza indirir ve sonraki hassas satır aralığı okumalarına olanak tanır. Açık başlangıç/bitiş istekleri bu ayarla sınırlı değildir.", "lines": "satır", "always_full_read": "Her zaman tüm dosyayı oku" + }, + "summarization": { + "enable": { + "label": "Bağlam özetlemeyi etkinleştir", + "description": "Etkinleştirildiğinde, bağlam sınırına yaklaşıldığında eski konuşma turları kesilmek yerine özetlenir. Bu, daha fazla bilgi korur ancak özetleme için ek token maliyetlerine neden olur." + }, + "triggerThreshold": { + "label": "Özetleme tetikleme eşiği", + "description": "Özetlemenin tetiklenmesi gereken bağlam penceresi boyutunun yüzdesi (ör. %80)." + }, + "initialTurns": { + "label": "Saklanacak ilk turlar", + "description": "Her zaman tam ayrıntılarıyla saklanacak ilk konuşma mesajlarının sayısı (sistem istemi + kullanıcı/asistan)." + }, + "recentTurns": { + "label": "Saklanacak son turlar", + "description": "Her zaman tam ayrıntılarıyla saklanacak en son konuşma mesajlarının sayısı." + }, + "turns": "mesajlar" } }, "terminal": { diff --git a/webview-ui/src/i18n/locales/vi/settings.json b/webview-ui/src/i18n/locales/vi/settings.json index 5bcafb666bc..e42d49653e6 100644 --- a/webview-ui/src/i18n/locales/vi/settings.json +++ b/webview-ui/src/i18n/locales/vi/settings.json @@ -300,6 +300,25 @@ "description": "Roo đọc số dòng này khi mô hình không chỉ định giá trị bắt đầu/kết thúc. Nếu số này nhỏ hơn tổng số dòng của tệp, Roo sẽ tạo một chỉ mục số dòng của các định nghĩa mã. Trường hợp đặc biệt: -1 chỉ thị Roo đọc toàn bộ tệp (không tạo chỉ mục), và 0 chỉ thị không đọc dòng nào và chỉ cung cấp chỉ mục dòng cho ngữ cảnh tối thiểu. Giá trị thấp hơn giảm thiểu việc sử dụng ngữ cảnh ban đầu, cho phép đọc chính xác các phạm vi dòng sau này. Các yêu cầu có chỉ định bắt đầu/kết thúc rõ ràng không bị giới hạn bởi cài đặt này.", "lines": "dòng", "always_full_read": "Luôn đọc toàn bộ tệp" + }, + "summarization": { + "enable": { + "label": "Bật tóm tắt ngữ cảnh", + "description": "Khi được bật, các lượt trò chuyện cũ hơn sẽ được tóm tắt thay vì bị cắt bớt khi đạt đến giới hạn ngữ cảnh. Điều này bảo toàn nhiều thông tin hơn nhưng phải trả thêm chi phí token cho việc tóm tắt." + }, + "triggerThreshold": { + "label": "Ngưỡng kích hoạt tóm tắt", + "description": "Phần trăm kích thước cửa sổ ngữ cảnh mà tại đó việc tóm tắt sẽ được kích hoạt (ví dụ: 80%)." + }, + "initialTurns": { + "label": "Lượt ban đầu cần giữ lại", + "description": "Số lượng tin nhắn trò chuyện ban đầu (lời nhắc hệ thống + người dùng/trợ lý) luôn được giữ lại đầy đủ chi tiết." + }, + "recentTurns": { + "label": "Lượt gần đây cần giữ lại", + "description": "Số lượng tin nhắn trò chuyện gần đây nhất luôn được giữ lại đầy đủ chi tiết." + }, + "turns": "tin nhắn" } }, "terminal": { diff --git a/webview-ui/src/i18n/locales/zh-CN/settings.json b/webview-ui/src/i18n/locales/zh-CN/settings.json index 0c40cfdf7e6..efc93e33768 100644 --- a/webview-ui/src/i18n/locales/zh-CN/settings.json +++ b/webview-ui/src/i18n/locales/zh-CN/settings.json @@ -300,6 +300,25 @@ "description": "自动读取文件行数设置:-1=完整读取 0=仅生成行号索引,较小值可节省token,支持后续使用行号进行读取。", "lines": "行", "always_full_read": "始终读取整个文件" + }, + "summarization": { + "enable": { + "label": "启用上下文摘要", + "description": "启用后,当接近上下文限制时,较早的对话轮次将被摘要而不是截断。这可以保留更多信息,但会产生额外的 token 费用用于摘要。" + }, + "triggerThreshold": { + "label": "摘要触发阈值", + "description": "应触发摘要的上下文窗口大小百分比(例如 80%)。" + }, + "initialTurns": { + "label": "保留的初始轮次", + "description": "始终保留完整细节的初始对话消息数量(系统提示 + 用户/助手)。" + }, + "recentTurns": { + "label": "保留的最近轮次", + "description": "始终保留完整细节的最近对话消息数量。" + }, + "turns": "条消息" } }, "terminal": { diff --git a/webview-ui/src/i18n/locales/zh-TW/settings.json b/webview-ui/src/i18n/locales/zh-TW/settings.json index 18a48eb9c57..c4038906b4d 100644 --- a/webview-ui/src/i18n/locales/zh-TW/settings.json +++ b/webview-ui/src/i18n/locales/zh-TW/settings.json @@ -300,6 +300,25 @@ "description": "當模型未指定起始/結束值時,Roo 讀取的行數。如果此數值小於檔案總行數,Roo 將產生程式碼定義的行號索引。特殊情況:-1 指示 Roo 讀取整個檔案(不建立索引),0 指示不讀取任何行並僅提供行索引以取得最小上下文。較低的值可最小化初始上下文使用,允許後續精確的行範圍讀取。明確指定起始/結束的請求不受此設定限制。", "lines": "行", "always_full_read": "始終讀取整個檔案" + }, + "summarization": { + "enable": { + "label": "啟用內容摘要", + "description": "啟用後,當接近內容限制時,較早的對話輪次將被摘要而不是截斷。這可以保留更多資訊,但會產生額外的 token 費用用於摘要。" + }, + "triggerThreshold": { + "label": "摘要觸發閾值", + "description": "應觸發摘要的內容視窗大小百分比(例如 80%)。" + }, + "initialTurns": { + "label": "保留的初始輪次", + "description": "始終保留完整細節的初始對話訊息數量(系統提示 + 使用者/助理)。" + }, + "recentTurns": { + "label": "保留的最近輪次", + "description": "始終保留完整細節的最近對話訊息數量。" + }, + "turns": "則訊息" } }, "terminal": { From 7fc2b770cda84e25f729b7da6432538856012f76 Mon Sep 17 00:00:00 2001 From: Shariq Riaz Date: Mon, 28 Apr 2025 21:04:29 +0500 Subject: [PATCH 04/16] rm --- roo-code-settings.json | 75 ------------------------------------------ 1 file changed, 75 deletions(-) delete mode 100644 roo-code-settings.json diff --git a/roo-code-settings.json b/roo-code-settings.json deleted file mode 100644 index fa2db86adc5..00000000000 --- a/roo-code-settings.json +++ /dev/null @@ -1,75 +0,0 @@ -{ - "providerProfiles": { - "currentApiConfigName": "default", - "apiConfigs": { - "default": { - "apiProvider": "vertex", - "apiModelId": "gemini-2.5-pro-exp-03-25", - "vertexProjectId": "gen-lang-client-0337025147", - "vertexRegion": "us-central1", - "id": "q0q01aha2ml" - } - }, - "modeApiConfigs": { - "code": "q0q01aha2ml", - "architect": "q0q01aha2ml", - "ask": "q0q01aha2ml", - "debug": "q0q01aha2ml", - "orchestrator": "q0q01aha2ml" - }, - "migrations": { - "rateLimitSecondsMigrated": true, - "diffSettingsMigrated": true - } - }, - "globalSettings": { - "lastShownAnnouncementId": "apr-23-2025-3-14", - "autoApprovalEnabled": true, - "alwaysAllowReadOnly": true, - "alwaysAllowReadOnlyOutsideWorkspace": true, - "alwaysAllowWrite": true, - "alwaysAllowWriteOutsideWorkspace": true, - "writeDelayMs": 1000, - "alwaysAllowBrowser": true, - "alwaysApproveResubmit": true, - "requestDelaySeconds": 5, - "alwaysAllowMcp": true, - "alwaysAllowModeSwitch": true, - "alwaysAllowSubtasks": true, - "alwaysAllowExecute": true, - "allowedCommands": ["*"], - "browserToolEnabled": true, - "browserViewportSize": "900x600", - "screenshotQuality": 100, - "remoteBrowserEnabled": false, - "enableCheckpoints": true, - "ttsEnabled": false, - "ttsSpeed": 1, - "soundEnabled": false, - "soundVolume": 0.5, - "maxOpenTabsContext": 20, - "maxWorkspaceFiles": 200, - "showRooIgnoredFiles": true, - "maxReadFileLine": -1, - "terminalOutputLineLimit": 500, - "terminalShellIntegrationTimeout": 5000, - "terminalCommandDelay": 0, - "terminalPowershellCounter": false, - "terminalZshClearEolMark": true, - "terminalZshOhMy": false, - "terminalZshP10k": false, - "terminalZdotdir": false, - "terminalCompressProgressBar": true, - "enableContextSummarization": false, - "contextSummarizationTriggerThreshold": 80, - "contextSummarizationInitialStaticTurns": 5, - "contextSummarizationRecentTurns": 10, - "experiments": { - "powerSteering": false - }, - "language": "en", - "telemetrySetting": "disabled", - "mcpEnabled": true, - "customModes": [] - } -} From 08adf5f42f83be0d752df36e83d55c9d5e50c820 Mon Sep 17 00:00:00 2001 From: Shariq Riaz Date: Mon, 28 Apr 2025 23:09:22 +0500 Subject: [PATCH 05/16] Add context summarization feature with manual trigger option --- src/core/Cline.ts | 79 +++++++++++++++++++ src/core/webview/webviewMessageHandler.ts | 21 +++++ src/i18n/locales/en/common.json | 5 +- src/shared/WebviewMessage.ts | 1 + src/shared/context-mentions.ts | 4 +- .../src/components/chat/ChatTextArea.tsx | 10 +++ .../src/components/chat/ContextMenu.tsx | 4 + webview-ui/src/utils/context-mentions.ts | 9 +++ 8 files changed, 130 insertions(+), 3 deletions(-) diff --git a/src/core/Cline.ts b/src/core/Cline.ts index 707695f5d8c..2afb9fa2f40 100644 --- a/src/core/Cline.ts +++ b/src/core/Cline.ts @@ -2676,4 +2676,83 @@ export class Cline extends EventEmitter { } return totalTokens } + + /** + * Manually triggers summarization of the conversation context. + * @param isManualTrigger Whether this summarization was manually triggered by the user. + * @returns A promise that resolves when summarization is complete. + */ + public async summarizeConversationContext(isManualTrigger: boolean = false): Promise { + // Skip if summarization is disabled + if (!this.enableContextSummarization) { + this.providerRef.deref()?.log("[Summarization] Context summarization is disabled.") + return + } + + const initialMessagesToKeep = this.contextSummarizationInitialStaticTurns + const recentMessagesToKeep = this.contextSummarizationRecentTurns + + // Ensure we have enough messages to summarize + if (this.apiConversationHistory.length <= initialMessagesToKeep + recentMessagesToKeep) { + this.providerRef + .deref() + ?.log( + `[Summarization] Not enough messages to summarize. Need more than ${initialMessagesToKeep + recentMessagesToKeep} messages.`, + ) + return + } + + // Calculate slice points + const initialSliceEnd = initialMessagesToKeep + const recentSliceStart = this.apiConversationHistory.length - recentMessagesToKeep + + // Ensure slice points don't overlap + if (initialSliceEnd >= recentSliceStart) { + this.providerRef + .deref() + ?.log( + `[Summarization] Skipping: initialSliceEnd (${initialSliceEnd}) >= recentSliceStart (${recentSliceStart}). Not enough messages between initial/recent turns.`, + ) + return + } + + // Slice the conversation history + const initialMessages = this.apiConversationHistory.slice(0, initialSliceEnd) + const recentMessages = this.apiConversationHistory.slice(recentSliceStart) + const messagesToSummarize = this.apiConversationHistory.slice(initialSliceEnd, recentSliceStart) + + this.providerRef + .deref() + ?.log( + `[Summarization] Slicing: Keep Initial ${initialMessages.length}, Summarize ${messagesToSummarize.length}, Keep Recent ${recentMessages.length}`, + ) + + // Create summarizer and generate summary + const summarizer = new ContextSummarizer(this.api) + const summaryMessage = await summarizer.summarize(messagesToSummarize) + + if (!summaryMessage) { + this.providerRef.deref()?.log(`[Summarization] Failed to generate summary.`) + return + } + + // Create new history with summary + const newHistory = [...initialMessages, summaryMessage, ...recentMessages] + + // Add a system message to notify the user in the UI + if (isManualTrigger) { + await this.say("text", "[Conversation history has been summarized to preserve context]") + } else { + await this.say("text", "[Older conversation turns summarized to preserve context]") + } + + // Update the conversation history + await this.overwriteApiConversationHistory(newHistory) + + this.providerRef + .deref() + ?.log( + `[Summarization] Successfully summarized ${messagesToSummarize.length} messages. New history length: ${newHistory.length}`, + ) + } } diff --git a/src/core/webview/webviewMessageHandler.ts b/src/core/webview/webviewMessageHandler.ts index 9fafa2045c5..cd6628f8deb 100644 --- a/src/core/webview/webviewMessageHandler.ts +++ b/src/core/webview/webviewMessageHandler.ts @@ -877,6 +877,27 @@ export const webviewMessageHandler = async (provider: ClineProvider, message: We await updateGlobalState("contextSummarizationRecentTurns", message.value ?? 10) await provider.postStateToWebview() break + case "manualSummarize": + // Trigger manual summarization of the conversation context + const currentCline = provider.getCurrentCline() + if (currentCline) { + try { + // Notify user that summarization is in progress + vscode.window.showInformationMessage(t("common:info.summarizing_context")) + + // Trigger the summarization process + await currentCline.summarizeConversationContext(true) // true indicates manual trigger + + // Notify user that summarization is complete + vscode.window.showInformationMessage(t("common:info.summarization_complete")) + } catch (error) { + provider.log( + `Error during manual summarization: ${JSON.stringify(error, Object.getOwnPropertyNames(error), 2)}`, + ) + vscode.window.showErrorMessage(t("common:errors.summarization_failed")) + } + } + break // --- End Context Summarization --- case "toggleApiConfigPin": if (message.text) { diff --git a/src/i18n/locales/en/common.json b/src/i18n/locales/en/common.json index ecd5a4c4133..405bc4b6ba7 100644 --- a/src/i18n/locales/en/common.json +++ b/src/i18n/locales/en/common.json @@ -47,6 +47,7 @@ "create_mcp_json": "Failed to create or open .roo/mcp.json: {{error}}", "hmr_not_running": "Local development server is not running, HMR will not work. Please run 'npm run dev' before launching the extension to enable HMR.", "retrieve_current_mode": "Error: failed to retrieve current mode from state.", + "summarization_failed": "Failed to summarize conversation context.", "failed_delete_repo": "Failed to delete associated shadow repository or branch: {{error}}", "failed_remove_directory": "Failed to remove task directory: {{error}}", "custom_storage_path_unusable": "Custom storage path \"{{path}}\" is unusable, will use default path", @@ -67,7 +68,9 @@ "mcp_server_not_found": "Server \"{{serverName}}\" not found in configuration", "custom_storage_path_set": "Custom storage path set: {{path}}", "default_storage_path": "Reverted to using default storage path", - "settings_imported": "Settings imported successfully." + "settings_imported": "Settings imported successfully.", + "summarizing_context": "Summarizing conversation context...", + "summarization_complete": "Conversation context summarization complete." }, "answers": { "yes": "Yes", diff --git a/src/shared/WebviewMessage.ts b/src/shared/WebviewMessage.ts index d1ee334cbaf..9a218723ea0 100644 --- a/src/shared/WebviewMessage.ts +++ b/src/shared/WebviewMessage.ts @@ -132,6 +132,7 @@ export interface WebviewMessage { | "contextSummarizationTriggerThreshold" | "contextSummarizationInitialStaticTurns" | "contextSummarizationRecentTurns" + | "manualSummarize" text?: string disabled?: boolean askResponse?: ClineAskResponse diff --git a/src/shared/context-mentions.ts b/src/shared/context-mentions.ts index 915114ab932..08c8d36db13 100644 --- a/src/shared/context-mentions.ts +++ b/src/shared/context-mentions.ts @@ -50,11 +50,11 @@ Mention regex: */ export const mentionRegex = - /@((?:\/|\w+:\/\/)[^\s]+?|[a-f0-9]{7,40}\b|problems\b|git-changes\b|terminal\b)(?=[.,;:!?]?(?=[\s\r\n]|$))/ + /@((?:\/|\w+:\/\/)[^\s]+?|[a-f0-9]{7,40}\b|problems\b|git-changes\b|terminal\b|summarize\b)(?=[.,;:!?]?(?=[\s\r\n]|$))/ export const mentionRegexGlobal = new RegExp(mentionRegex.source, "g") export interface MentionSuggestion { - type: "file" | "folder" | "git" | "problems" + type: "file" | "folder" | "git" | "problems" | "summarize" label: string description?: string value: string diff --git a/webview-ui/src/components/chat/ChatTextArea.tsx b/webview-ui/src/components/chat/ChatTextArea.tsx index 2c60d5f1aec..3daf07faa28 100644 --- a/webview-ui/src/components/chat/ChatTextArea.tsx +++ b/webview-ui/src/components/chat/ChatTextArea.tsx @@ -252,6 +252,14 @@ const ChatTextArea = forwardRef( setShowContextMenu(false) setSelectedType(null) + if (type === ContextMenuOptionType.Summarize) { + // Handle summarize action - trigger manual summarization + vscode.postMessage({ type: "manualSummarize" }) + setShowContextMenu(false) + setSelectedType(null) + return + } + if (textAreaRef.current) { let insertValue = value || "" @@ -267,6 +275,8 @@ const ChatTextArea = forwardRef( insertValue = value || "" } + // Note: The Summarize case is handled above before this block + const { newValue, mentionIndex } = insertMention( textAreaRef.current.value, cursorPosition, diff --git a/webview-ui/src/components/chat/ContextMenu.tsx b/webview-ui/src/components/chat/ContextMenu.tsx index 27d53803a85..0735bb9db7b 100644 --- a/webview-ui/src/components/chat/ContextMenu.tsx +++ b/webview-ui/src/components/chat/ContextMenu.tsx @@ -89,6 +89,8 @@ const ContextMenu: React.FC = ({ return Problems case ContextMenuOptionType.Terminal: return Terminal + case ContextMenuOptionType.Summarize: + return {option.label || "Summarize"} case ContextMenuOptionType.URL: return Paste URL to fetch contents case ContextMenuOptionType.NoResults: @@ -175,6 +177,8 @@ const ContextMenu: React.FC = ({ return "link" case ContextMenuOptionType.Git: return "git-commit" + case ContextMenuOptionType.Summarize: + return "archive" case ContextMenuOptionType.NoResults: return "info" default: diff --git a/webview-ui/src/utils/context-mentions.ts b/webview-ui/src/utils/context-mentions.ts index 293aa8f74bb..3430e3e9c15 100644 --- a/webview-ui/src/utils/context-mentions.ts +++ b/webview-ui/src/utils/context-mentions.ts @@ -77,6 +77,7 @@ export enum ContextMenuOptionType { Git = "git", NoResults = "noResults", Mode = "mode", // Add mode type + Summarize = "summarize", // Add summarize type } export interface ContextMenuQueryItem { @@ -170,6 +171,7 @@ export function getContextMenuOptions( { type: ContextMenuOptionType.Folder }, { type: ContextMenuOptionType.File }, { type: ContextMenuOptionType.Git }, + { type: ContextMenuOptionType.Summarize, label: "Summarize", description: "Compress conversation history" }, ] } @@ -193,6 +195,13 @@ export function getContextMenuOptions( if ("terminal".startsWith(lowerQuery)) { suggestions.push({ type: ContextMenuOptionType.Terminal }) } + if ("summarize".startsWith(lowerQuery)) { + suggestions.push({ + type: ContextMenuOptionType.Summarize, + label: "Summarize", + description: "Compress conversation history", + }) + } if (query.startsWith("http")) { suggestions.push({ type: ContextMenuOptionType.URL, value: query }) } From 67b1ac574b362f4a9291d2201452ff7ae69bfcb1 Mon Sep 17 00:00:00 2001 From: Shariq Riaz Date: Mon, 28 Apr 2025 23:29:24 +0500 Subject: [PATCH 06/16] Improve manual summarization UX with visual feedback --- src/core/Cline.ts | 2 +- src/core/webview/webviewMessageHandler.ts | 43 +++++++++++++++++++++++ src/exports/roo-code.d.ts | 2 ++ src/exports/types.ts | 2 ++ src/schemas/index.ts | 1 + src/shared/ExtensionMessage.ts | 2 ++ src/shared/WebviewMessage.ts | 2 ++ 7 files changed, 53 insertions(+), 1 deletion(-) diff --git a/src/core/Cline.ts b/src/core/Cline.ts index 2afb9fa2f40..465afe7632a 100644 --- a/src/core/Cline.ts +++ b/src/core/Cline.ts @@ -370,7 +370,7 @@ export class Cline extends EventEmitter { this.emit("message", { action: "updated", message: partialMessage }) } - private async saveClineMessages() { + public async saveClineMessages() { try { await saveTaskMessages({ messages: this.clineMessages, diff --git a/src/core/webview/webviewMessageHandler.ts b/src/core/webview/webviewMessageHandler.ts index cd6628f8deb..9963be6bde5 100644 --- a/src/core/webview/webviewMessageHandler.ts +++ b/src/core/webview/webviewMessageHandler.ts @@ -882,18 +882,61 @@ export const webviewMessageHandler = async (provider: ClineProvider, message: We const currentCline = provider.getCurrentCline() if (currentCline) { try { + // First send a message to the webview to show a progress indicator + provider.postMessageToWebview({ + type: "summarizationStatus", + status: "started", + text: t("common:info.summarizing_context"), + }) + // Notify user that summarization is in progress vscode.window.showInformationMessage(t("common:info.summarizing_context")) + // Add a "summarizing" message to the chat + await currentCline.say("summarizing", t("common:info.summarizing_context"), undefined, true) + // Trigger the summarization process await currentCline.summarizeConversationContext(true) // true indicates manual trigger + // Update the "summarizing" message to show completion + const lastMessage = currentCline.clineMessages.at(-1) + if (lastMessage && lastMessage.say === "summarizing" && lastMessage.partial) { + lastMessage.text = t("common:info.summarization_complete") + lastMessage.partial = false + await currentCline.saveClineMessages() + await provider.postStateToWebview() + } + + // Send a message to the webview to hide the progress indicator + provider.postMessageToWebview({ + type: "summarizationStatus", + status: "completed", + text: t("common:info.summarization_complete"), + }) + // Notify user that summarization is complete vscode.window.showInformationMessage(t("common:info.summarization_complete")) } catch (error) { provider.log( `Error during manual summarization: ${JSON.stringify(error, Object.getOwnPropertyNames(error), 2)}`, ) + + // Update the UI to show the error + provider.postMessageToWebview({ + type: "summarizationStatus", + status: "failed", + text: t("common:errors.summarization_failed"), + }) + + // Update any partial message + const lastMessage = currentCline.clineMessages.at(-1) + if (lastMessage && lastMessage.say === "summarizing" && lastMessage.partial) { + lastMessage.text = t("common:errors.summarization_failed") + lastMessage.partial = false + await currentCline.saveClineMessages() + await provider.postStateToWebview() + } + vscode.window.showErrorMessage(t("common:errors.summarization_failed")) } } diff --git a/src/exports/roo-code.d.ts b/src/exports/roo-code.d.ts index 318bfdff2b8..29c7b0e2b24 100644 --- a/src/exports/roo-code.d.ts +++ b/src/exports/roo-code.d.ts @@ -342,6 +342,7 @@ type ClineMessage = { | "checkpoint_saved" | "rooignore_error" | "diff_error" + | "summarizing" ) | undefined text?: string | undefined @@ -423,6 +424,7 @@ type RooCodeEvents = { | "checkpoint_saved" | "rooignore_error" | "diff_error" + | "summarizing" ) | undefined text?: string | undefined diff --git a/src/exports/types.ts b/src/exports/types.ts index 9def123603f..51aa5ec3b71 100644 --- a/src/exports/types.ts +++ b/src/exports/types.ts @@ -347,6 +347,7 @@ type ClineMessage = { | "checkpoint_saved" | "rooignore_error" | "diff_error" + | "summarizing" ) | undefined text?: string | undefined @@ -432,6 +433,7 @@ type RooCodeEvents = { | "checkpoint_saved" | "rooignore_error" | "diff_error" + | "summarizing" ) | undefined text?: string | undefined diff --git a/src/schemas/index.ts b/src/schemas/index.ts index 92cc3b182e7..f245eee497a 100644 --- a/src/schemas/index.ts +++ b/src/schemas/index.ts @@ -789,6 +789,7 @@ export const clineSays = [ "checkpoint_saved", "rooignore_error", "diff_error", + "summarizing", ] as const export const clineSaySchema = z.enum(clineSays) diff --git a/src/shared/ExtensionMessage.ts b/src/shared/ExtensionMessage.ts index 3181134491e..11a104a8224 100644 --- a/src/shared/ExtensionMessage.ts +++ b/src/shared/ExtensionMessage.ts @@ -67,6 +67,7 @@ export interface ExtensionMessage { | "toggleApiConfigPin" | "acceptInput" | "setHistoryPreviewCollapsed" + | "summarizationStatus" text?: string action?: | "chatButtonClicked" @@ -103,6 +104,7 @@ export interface ExtensionMessage { promptText?: string results?: { path: string; type: "file" | "folder"; label?: string }[] error?: string + status?: "started" | "completed" | "failed" } export type ExtensionState = Pick< diff --git a/src/shared/WebviewMessage.ts b/src/shared/WebviewMessage.ts index 9a218723ea0..ed12b3c3d53 100644 --- a/src/shared/WebviewMessage.ts +++ b/src/shared/WebviewMessage.ts @@ -133,6 +133,7 @@ export interface WebviewMessage { | "contextSummarizationInitialStaticTurns" | "contextSummarizationRecentTurns" | "manualSummarize" + | "summarizationStatus" text?: string disabled?: boolean askResponse?: ClineAskResponse @@ -161,6 +162,7 @@ export interface WebviewMessage { hasSystemPromptOverride?: boolean terminalOperation?: "continue" | "abort" historyPreviewCollapsed?: boolean + status?: "started" | "completed" | "failed" } export const checkoutDiffPayloadSchema = z.object({ From e7f1cbe73b1ab7b587d53dd1d450adcb157e0781 Mon Sep 17 00:00:00 2001 From: Shariq Riaz Date: Tue, 29 Apr 2025 00:02:10 +0500 Subject: [PATCH 07/16] Move Summarize option to the top of context menu --- webview-ui/src/utils/context-mentions.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/webview-ui/src/utils/context-mentions.ts b/webview-ui/src/utils/context-mentions.ts index 3430e3e9c15..8c843491ed2 100644 --- a/webview-ui/src/utils/context-mentions.ts +++ b/webview-ui/src/utils/context-mentions.ts @@ -165,13 +165,13 @@ export function getContextMenuOptions( } return [ + { type: ContextMenuOptionType.Summarize, label: "Summarize", description: "Compress conversation history" }, { type: ContextMenuOptionType.Problems }, { type: ContextMenuOptionType.Terminal }, { type: ContextMenuOptionType.URL }, { type: ContextMenuOptionType.Folder }, { type: ContextMenuOptionType.File }, { type: ContextMenuOptionType.Git }, - { type: ContextMenuOptionType.Summarize, label: "Summarize", description: "Compress conversation history" }, ] } From 14c85de406a2869ca36f81cf4cf44204c4f97502 Mon Sep 17 00:00:00 2001 From: Shariq Riaz Date: Tue, 29 Apr 2025 01:22:41 +0500 Subject: [PATCH 08/16] @mention-buggy --- src/core/webview/webviewMessageHandler.ts | 93 ++++++++----------- webview-ui/src/components/chat/ChatView.tsx | 6 ++ .../chat/SummarizationIndicator.tsx | 35 +++++++ .../src/context/ExtensionStateContext.tsx | 24 +++++ 4 files changed, 104 insertions(+), 54 deletions(-) create mode 100644 webview-ui/src/components/chat/SummarizationIndicator.tsx diff --git a/src/core/webview/webviewMessageHandler.ts b/src/core/webview/webviewMessageHandler.ts index 9963be6bde5..53b87432c32 100644 --- a/src/core/webview/webviewMessageHandler.ts +++ b/src/core/webview/webviewMessageHandler.ts @@ -881,64 +881,49 @@ export const webviewMessageHandler = async (provider: ClineProvider, message: We // Trigger manual summarization of the conversation context const currentCline = provider.getCurrentCline() if (currentCline) { - try { - // First send a message to the webview to show a progress indicator - provider.postMessageToWebview({ - type: "summarizationStatus", - status: "started", - text: t("common:info.summarizing_context"), - }) - - // Notify user that summarization is in progress - vscode.window.showInformationMessage(t("common:info.summarizing_context")) - - // Add a "summarizing" message to the chat - await currentCline.say("summarizing", t("common:info.summarizing_context"), undefined, true) - - // Trigger the summarization process - await currentCline.summarizeConversationContext(true) // true indicates manual trigger - - // Update the "summarizing" message to show completion - const lastMessage = currentCline.clineMessages.at(-1) - if (lastMessage && lastMessage.say === "summarizing" && lastMessage.partial) { - lastMessage.text = t("common:info.summarization_complete") - lastMessage.partial = false - await currentCline.saveClineMessages() - await provider.postStateToWebview() - } - - // Send a message to the webview to hide the progress indicator - provider.postMessageToWebview({ - type: "summarizationStatus", - status: "completed", - text: t("common:info.summarization_complete"), - }) + // First send a message to the webview to show a progress indicator + void provider.postMessageToWebview({ + type: "summarizationStatus", + status: "started", + text: t("common:info.summarizing_context"), + }) - // Notify user that summarization is complete - vscode.window.showInformationMessage(t("common:info.summarization_complete")) - } catch (error) { - provider.log( - `Error during manual summarization: ${JSON.stringify(error, Object.getOwnPropertyNames(error), 2)}`, - ) + // Use a non-blocking approach with proper error handling + ;(async function summarizeContext() { + try { + // Add a system notification message that doesn't require user interaction + await currentCline.say("text", "[System: Summarizing conversation context...]") + + // Trigger the summarization process + await currentCline.summarizeConversationContext(true) // true indicates manual trigger + + // Add a completion message + await currentCline.say("text", "[System: Conversation context summarization complete]") + + // Send a message to the webview to hide the progress indicator + void provider.postMessageToWebview({ + type: "summarizationStatus", + status: "completed", + text: t("common:info.summarization_complete"), + }) + } catch (error) { + provider.log( + `Error during manual summarization: ${JSON.stringify(error, Object.getOwnPropertyNames(error), 2)}`, + ) - // Update the UI to show the error - provider.postMessageToWebview({ - type: "summarizationStatus", - status: "failed", - text: t("common:errors.summarization_failed"), - }) + // Update the UI to show the error + void provider.postMessageToWebview({ + type: "summarizationStatus", + status: "failed", + text: t("common:errors.summarization_failed"), + }) - // Update any partial message - const lastMessage = currentCline.clineMessages.at(-1) - if (lastMessage && lastMessage.say === "summarizing" && lastMessage.partial) { - lastMessage.text = t("common:errors.summarization_failed") - lastMessage.partial = false - await currentCline.saveClineMessages() - await provider.postStateToWebview() + // Add an error message + await currentCline.say("error", t("common:errors.summarization_failed")) } - - vscode.window.showErrorMessage(t("common:errors.summarization_failed")) - } + })().catch((error) => { + provider.log(`Unhandled error in summarization: ${String(error)}`) + }) } break // --- End Context Summarization --- diff --git a/webview-ui/src/components/chat/ChatView.tsx b/webview-ui/src/components/chat/ChatView.tsx index af002465a4f..f94cad2cda2 100644 --- a/webview-ui/src/components/chat/ChatView.tsx +++ b/webview-ui/src/components/chat/ChatView.tsx @@ -38,6 +38,7 @@ import ChatTextArea from "./ChatTextArea" import TaskHeader from "./TaskHeader" import AutoApproveMenu from "./AutoApproveMenu" import SystemPromptWarning from "./SystemPromptWarning" +import SummarizationIndicator from "./SummarizationIndicator" import { useTaskSearch } from "../history/useTaskSearch" export interface ChatViewProps { @@ -1249,6 +1250,11 @@ const ChatViewComponent: React.ForwardRefRenderFunction )} + + {/* Summarization status indicator */} +
+ +
) : (
diff --git a/webview-ui/src/components/chat/SummarizationIndicator.tsx b/webview-ui/src/components/chat/SummarizationIndicator.tsx new file mode 100644 index 00000000000..a49f5b9723e --- /dev/null +++ b/webview-ui/src/components/chat/SummarizationIndicator.tsx @@ -0,0 +1,35 @@ +import React from "react" +import { cn } from "@/lib/utils" +import { useExtensionState } from "@/context/ExtensionStateContext" + +/** + * A component that displays the current status of context summarization + */ +export const SummarizationIndicator: React.FC = () => { + const { summarizationStatus } = useExtensionState() + + if (!summarizationStatus) { + return null + } + + return ( +
+ {summarizationStatus.status === "started" && ( + + )} + {summarizationStatus.status === "completed" && } + {summarizationStatus.status === "failed" && } + {summarizationStatus.text} +
+ ) +} + +export default SummarizationIndicator diff --git a/webview-ui/src/context/ExtensionStateContext.tsx b/webview-ui/src/context/ExtensionStateContext.tsx index 290f0e88ffe..59a5fc256ec 100644 --- a/webview-ui/src/context/ExtensionStateContext.tsx +++ b/webview-ui/src/context/ExtensionStateContext.tsx @@ -20,6 +20,10 @@ export interface ExtensionStateContextType extends ExtensionState { mcpServers: McpServer[] hasSystemPromptOverride?: boolean currentCheckpoint?: string + summarizationStatus?: { + status: "started" | "completed" | "failed" + text: string + } filePaths: string[] openedTabs: Array<{ label: string; isActive: boolean; path?: string }> setApiConfiguration: (config: ApiConfiguration) => void @@ -248,6 +252,26 @@ export const ExtensionStateContextProvider: React.FC<{ children: React.ReactNode setCurrentCheckpoint(message.text) break } + case "summarizationStatus": { + setState((prevState) => ({ + ...prevState, + summarizationStatus: { + status: message.status as "started" | "completed" | "failed", + text: message.text || "", + }, + })) + + // Auto-clear the status after completion or failure + if (message.status === "completed" || message.status === "failed") { + setTimeout(() => { + setState((prevState) => ({ + ...prevState, + summarizationStatus: undefined, + })) + }, 5000) // Clear after 5 seconds + } + break + } case "listApiConfig": { setListApiConfigMeta(message.listApiConfig ?? []) break From c15680c5ba32c319fdd518d8d08b47b2b37bc741 Mon Sep 17 00:00:00 2001 From: Shariq Riaz Date: Tue, 29 Apr 2025 03:52:16 +0500 Subject: [PATCH 09/16] manual-fixed --- src/core/Cline.ts | 9 +-- src/core/webview/webviewMessageHandler.ts | 55 +++++++------------ .../src/context/ExtensionStateContext.tsx | 37 +++++++++---- 3 files changed, 48 insertions(+), 53 deletions(-) diff --git a/src/core/Cline.ts b/src/core/Cline.ts index 465afe7632a..858a7c05b4d 100644 --- a/src/core/Cline.ts +++ b/src/core/Cline.ts @@ -2682,7 +2682,7 @@ export class Cline extends EventEmitter { * @param isManualTrigger Whether this summarization was manually triggered by the user. * @returns A promise that resolves when summarization is complete. */ - public async summarizeConversationContext(isManualTrigger: boolean = false): Promise { + public async summarizeConversationContext(_isManualTrigger: boolean = false): Promise { // Skip if summarization is disabled if (!this.enableContextSummarization) { this.providerRef.deref()?.log("[Summarization] Context summarization is disabled.") @@ -2739,13 +2739,6 @@ export class Cline extends EventEmitter { // Create new history with summary const newHistory = [...initialMessages, summaryMessage, ...recentMessages] - // Add a system message to notify the user in the UI - if (isManualTrigger) { - await this.say("text", "[Conversation history has been summarized to preserve context]") - } else { - await this.say("text", "[Older conversation turns summarized to preserve context]") - } - // Update the conversation history await this.overwriteApiConversationHistory(newHistory) diff --git a/src/core/webview/webviewMessageHandler.ts b/src/core/webview/webviewMessageHandler.ts index 53b87432c32..db877e898fb 100644 --- a/src/core/webview/webviewMessageHandler.ts +++ b/src/core/webview/webviewMessageHandler.ts @@ -889,41 +889,28 @@ export const webviewMessageHandler = async (provider: ClineProvider, message: We }) // Use a non-blocking approach with proper error handling - ;(async function summarizeContext() { - try { - // Add a system notification message that doesn't require user interaction - await currentCline.say("text", "[System: Summarizing conversation context...]") - - // Trigger the summarization process - await currentCline.summarizeConversationContext(true) // true indicates manual trigger - - // Add a completion message - await currentCline.say("text", "[System: Conversation context summarization complete]") - - // Send a message to the webview to hide the progress indicator - void provider.postMessageToWebview({ - type: "summarizationStatus", - status: "completed", - text: t("common:info.summarization_complete"), - }) - } catch (error) { - provider.log( - `Error during manual summarization: ${JSON.stringify(error, Object.getOwnPropertyNames(error), 2)}`, - ) - - // Update the UI to show the error - void provider.postMessageToWebview({ - type: "summarizationStatus", - status: "failed", - text: t("common:errors.summarization_failed"), - }) + try { + // Trigger the summarization process directly without adding system messages + await currentCline.summarizeConversationContext(true) // true indicates manual trigger + + // Send a message to the webview to hide the progress indicator + void provider.postMessageToWebview({ + type: "summarizationStatus", + status: "completed", + text: t("common:info.summarization_complete"), + }) + } catch (error) { + provider.log( + `Error during manual summarization: ${JSON.stringify(error, Object.getOwnPropertyNames(error), 2)}`, + ) - // Add an error message - await currentCline.say("error", t("common:errors.summarization_failed")) - } - })().catch((error) => { - provider.log(`Unhandled error in summarization: ${String(error)}`) - }) + // Update the UI to show the error + void provider.postMessageToWebview({ + type: "summarizationStatus", + status: "failed", + text: t("common:errors.summarization_failed"), + }) + } } break // --- End Context Summarization --- diff --git a/webview-ui/src/context/ExtensionStateContext.tsx b/webview-ui/src/context/ExtensionStateContext.tsx index 59a5fc256ec..019bfb95e6d 100644 --- a/webview-ui/src/context/ExtensionStateContext.tsx +++ b/webview-ui/src/context/ExtensionStateContext.tsx @@ -253,22 +253,37 @@ export const ExtensionStateContextProvider: React.FC<{ children: React.ReactNode break } case "summarizationStatus": { - setState((prevState) => ({ - ...prevState, - summarizationStatus: { - status: message.status as "started" | "completed" | "failed", - text: message.text || "", - }, - })) + // For "started" status, update the state immediately + if (message.status === "started") { + setState((prevState) => ({ + ...prevState, + summarizationStatus: { + status: message.status as "started" | "completed" | "failed", + text: message.text || "", + }, + })) + } + // For "completed" or "failed" status, update and then clear after delay + else if (message.status === "completed" || message.status === "failed") { + // First update the status + setState((prevState) => ({ + ...prevState, + summarizationStatus: { + status: message.status as "completed" | "failed", + text: message.text || "", + }, + })) - // Auto-clear the status after completion or failure - if (message.status === "completed" || message.status === "failed") { - setTimeout(() => { + // Then clear it after a delay + const timer = setTimeout(() => { setState((prevState) => ({ ...prevState, summarizationStatus: undefined, })) - }, 5000) // Clear after 5 seconds + }, 3000) // Reduced to 3 seconds for better UX + + // Clean up the timer if component unmounts + return () => clearTimeout(timer) } break } From 115efccf537b01898046190be0f2eaebc18ad575 Mon Sep 17 00:00:00 2001 From: Shariq Riaz Date: Tue, 29 Apr 2025 04:21:35 +0500 Subject: [PATCH 10/16] sythesize --- src/core/Cline.ts | 52 +++++++-------- src/core/webview/webviewMessageHandler.ts | 26 ++++---- src/i18n/locales/en/common.json | 6 +- .../ContextSynthesizer.ts} | 66 +++++++++---------- src/shared/ExtensionMessage.ts | 2 +- src/shared/WebviewMessage.ts | 6 +- .../src/components/chat/ChatTextArea.tsx | 6 +- webview-ui/src/components/chat/ChatView.tsx | 6 +- .../src/components/chat/ContextMenu.tsx | 6 +- ...ndicator.tsx => SynthesizingIndicator.tsx} | 22 +++---- .../settings/ContextManagementSettings.tsx | 20 +++--- .../src/context/ExtensionStateContext.tsx | 14 ++-- webview-ui/src/i18n/locales/en/settings.json | 10 +-- webview-ui/src/utils/context-mentions.ts | 22 +++++-- 14 files changed, 136 insertions(+), 128 deletions(-) rename src/services/{summarization/ContextSummarizer.ts => synthesization/ContextSynthesizer.ts} (62%) rename webview-ui/src/components/chat/{SummarizationIndicator.tsx => SynthesizingIndicator.tsx} (50%) diff --git a/src/core/Cline.ts b/src/core/Cline.ts index 858a7c05b4d..99bee874a61 100644 --- a/src/core/Cline.ts +++ b/src/core/Cline.ts @@ -90,7 +90,7 @@ import { truncateConversationIfNeeded, estimateTokenCount } from "./sliding-wind import { ClineProvider } from "./webview/ClineProvider" import { validateToolUse } from "./mode-validator" import { MultiSearchReplaceDiffStrategy } from "./diff/strategies/multi-search-replace" -import { ContextSummarizer } from "../services/summarization/ContextSummarizer" // Added ContextSummarizer +import { ContextSynthesizer } from "../services/synthesization/ContextSynthesizer" // Updated to ContextSynthesizer import { readApiMessages, saveApiMessages, readTaskMessages, saveTaskMessages, taskMetadata } from "./task-persistence" type UserContent = Array @@ -1056,16 +1056,16 @@ export class Cline extends EventEmitter { let historyModifiedBySummarization = false // Flag to track if summarization updated history if (this.enableContextSummarization) { - // --- Summarization Logic --- + // --- Synthesizing Logic --- const currentTokens = await this._estimateTotalTokenCount(this.apiConversationHistory) const triggerTokenCount = contextWindow * (this.contextSummarizationTriggerThreshold / 100) this.providerRef .deref() - ?.log(`[Summarization] Current tokens: ${currentTokens}, Trigger: ${triggerTokenCount}`) + ?.log(`[Synthesizing] Current tokens: ${currentTokens}, Trigger: ${triggerTokenCount}`) if (currentTokens >= triggerTokenCount) { - this.providerRef.deref()?.log(`[Summarization] Threshold met. Attempting summarization.`) + this.providerRef.deref()?.log(`[Synthesizing] Threshold met. Attempting synthesizing.`) const initialMessagesToKeep = this.contextSummarizationInitialStaticTurns const recentMessagesToKeep = this.contextSummarizationRecentTurns @@ -1089,35 +1089,35 @@ export class Cline extends EventEmitter { this.providerRef .deref() ?.log( - `[Summarization] Slicing: Keep Initial ${initialMessages.length}, Summarize ${messagesToSummarize.length}, Keep Recent ${recentMessages.length}`, + `[Synthesizing] Slicing: Keep Initial ${initialMessages.length}, Synthesize ${messagesToSummarize.length}, Keep Recent ${recentMessages.length}`, ) - // Instantiate the summarizer (consider using a dedicated API handler/model later) - const summarizer = new ContextSummarizer(this.api) - const summaryMessage = await summarizer.summarize(messagesToSummarize) + // Instantiate the synthesizer (consider using a dedicated API handler/model later) + const synthesizer = new ContextSynthesizer(this.api) + const summaryMessage = await synthesizer.synthesize(messagesToSummarize) if (summaryMessage) { const newHistory = [...initialMessages, summaryMessage, ...recentMessages] this.providerRef .deref() ?.log( - `[Summarization] Summarization successful. New history length: ${newHistory.length}`, + `[Synthesizing] Synthesizing successful. New history length: ${newHistory.length}`, ) // Add a system message to notify the user in the UI - await this.say("text", "[Older conversation turns summarized to preserve context]") + await this.say("text", "[Older conversation turns synthesized to preserve context]") await this.overwriteApiConversationHistory(newHistory) historyModifiedBySummarization = true // Mark history as modified } else { this.providerRef .deref() - ?.log(`[Summarization] Summarization failed. Falling back to truncation.`) + ?.log(`[Synthesizing] Synthesizing failed. Falling back to truncation.`) // Fall through to truncation if summarization fails } } else { this.providerRef .deref() ?.log( - `[Summarization] Skipping: initialSliceEnd (${initialSliceEnd}) >= recentSliceStart (${recentSliceStart}). Not enough messages between initial/recent turns.`, + `[Synthesizing] Skipping: initialSliceEnd (${initialSliceEnd}) >= recentSliceStart (${recentSliceStart}). Not enough messages between initial/recent turns.`, ) // Fall through to truncation if slicing is not possible } @@ -1125,7 +1125,7 @@ export class Cline extends EventEmitter { this.providerRef .deref() ?.log( - `[Summarization] Skipping: Not enough messages (${this.apiConversationHistory.length}) to satisfy keep counts (${initialMessagesToKeep} + ${recentMessagesToKeep}).`, + `[Synthesizing] Skipping: Not enough messages (${this.apiConversationHistory.length}) to satisfy keep counts (${initialMessagesToKeep} + ${recentMessagesToKeep}).`, ) // Fall through to truncation if history is too short } @@ -1146,7 +1146,7 @@ export class Cline extends EventEmitter { }) if (trimmedMessages !== this.apiConversationHistory) { - this.providerRef.deref()?.log(`[Truncation] Truncation applied.`) + this.providerRef.deref()?.log(`[Synthesizing] Truncation applied as fallback.`) await this.overwriteApiConversationHistory(trimmedMessages) } } @@ -2682,22 +2682,22 @@ export class Cline extends EventEmitter { * @param isManualTrigger Whether this summarization was manually triggered by the user. * @returns A promise that resolves when summarization is complete. */ - public async summarizeConversationContext(_isManualTrigger: boolean = false): Promise { - // Skip if summarization is disabled + public async synthesizeConversationContext(_isManualTrigger: boolean = false): Promise { + // Skip if synthesizing is disabled if (!this.enableContextSummarization) { - this.providerRef.deref()?.log("[Summarization] Context summarization is disabled.") + this.providerRef.deref()?.log("[Synthesizing] Context synthesizing is disabled.") return } const initialMessagesToKeep = this.contextSummarizationInitialStaticTurns const recentMessagesToKeep = this.contextSummarizationRecentTurns - // Ensure we have enough messages to summarize + // Ensure we have enough messages to synthesize if (this.apiConversationHistory.length <= initialMessagesToKeep + recentMessagesToKeep) { this.providerRef .deref() ?.log( - `[Summarization] Not enough messages to summarize. Need more than ${initialMessagesToKeep + recentMessagesToKeep} messages.`, + `[Synthesizing] Not enough messages to synthesize. Need more than ${initialMessagesToKeep + recentMessagesToKeep} messages.`, ) return } @@ -2711,7 +2711,7 @@ export class Cline extends EventEmitter { this.providerRef .deref() ?.log( - `[Summarization] Skipping: initialSliceEnd (${initialSliceEnd}) >= recentSliceStart (${recentSliceStart}). Not enough messages between initial/recent turns.`, + `[Synthesizing] Skipping: initialSliceEnd (${initialSliceEnd}) >= recentSliceStart (${recentSliceStart}). Not enough messages between initial/recent turns.`, ) return } @@ -2724,15 +2724,15 @@ export class Cline extends EventEmitter { this.providerRef .deref() ?.log( - `[Summarization] Slicing: Keep Initial ${initialMessages.length}, Summarize ${messagesToSummarize.length}, Keep Recent ${recentMessages.length}`, + `[Synthesizing] Slicing: Keep Initial ${initialMessages.length}, Synthesize ${messagesToSummarize.length}, Keep Recent ${recentMessages.length}`, ) - // Create summarizer and generate summary - const summarizer = new ContextSummarizer(this.api) - const summaryMessage = await summarizer.summarize(messagesToSummarize) + // Create synthesizer and generate synthesis + const synthesizer = new ContextSynthesizer(this.api) + const summaryMessage = await synthesizer.synthesize(messagesToSummarize) if (!summaryMessage) { - this.providerRef.deref()?.log(`[Summarization] Failed to generate summary.`) + this.providerRef.deref()?.log(`[Synthesizing] Failed to generate synthesis.`) return } @@ -2745,7 +2745,7 @@ export class Cline extends EventEmitter { this.providerRef .deref() ?.log( - `[Summarization] Successfully summarized ${messagesToSummarize.length} messages. New history length: ${newHistory.length}`, + `[Synthesizing] Successfully synthesized ${messagesToSummarize.length} messages. New history length: ${newHistory.length}`, ) } } diff --git a/src/core/webview/webviewMessageHandler.ts b/src/core/webview/webviewMessageHandler.ts index db877e898fb..74956491d24 100644 --- a/src/core/webview/webviewMessageHandler.ts +++ b/src/core/webview/webviewMessageHandler.ts @@ -860,7 +860,7 @@ export const webviewMessageHandler = async (provider: ClineProvider, message: We await updateGlobalState("historyPreviewCollapsed", message.bool ?? false) // No need to call postStateToWebview here as the UI already updated optimistically break - // Context Summarization Settings (Added) + // Context Synthesization Settings case "enableContextSummarization": await updateGlobalState("enableContextSummarization", message.bool ?? false) await provider.postStateToWebview() @@ -877,43 +877,43 @@ export const webviewMessageHandler = async (provider: ClineProvider, message: We await updateGlobalState("contextSummarizationRecentTurns", message.value ?? 10) await provider.postStateToWebview() break - case "manualSummarize": - // Trigger manual summarization of the conversation context + case "manualSynthesize": + // Trigger manual synthesizing of the conversation context const currentCline = provider.getCurrentCline() if (currentCline) { // First send a message to the webview to show a progress indicator void provider.postMessageToWebview({ - type: "summarizationStatus", + type: "synthesizationStatus", status: "started", - text: t("common:info.summarizing_context"), + text: t("common:info.synthesizing_context"), }) // Use a non-blocking approach with proper error handling try { - // Trigger the summarization process directly without adding system messages - await currentCline.summarizeConversationContext(true) // true indicates manual trigger + // Trigger the synthesizing process directly without adding system messages + await currentCline.synthesizeConversationContext(true) // true indicates manual trigger // Send a message to the webview to hide the progress indicator void provider.postMessageToWebview({ - type: "summarizationStatus", + type: "synthesizationStatus", status: "completed", - text: t("common:info.summarization_complete"), + text: t("common:info.synthesization_complete"), }) } catch (error) { provider.log( - `Error during manual summarization: ${JSON.stringify(error, Object.getOwnPropertyNames(error), 2)}`, + `Error during manual synthesizing: ${JSON.stringify(error, Object.getOwnPropertyNames(error), 2)}`, ) // Update the UI to show the error void provider.postMessageToWebview({ - type: "summarizationStatus", + type: "synthesizationStatus", status: "failed", - text: t("common:errors.summarization_failed"), + text: t("common:errors.synthesization_failed"), }) } } break - // --- End Context Summarization --- + // --- End Context Synthesization --- case "toggleApiConfigPin": if (message.text) { const currentPinned = getGlobalState("pinnedApiConfigs") ?? {} diff --git a/src/i18n/locales/en/common.json b/src/i18n/locales/en/common.json index 405bc4b6ba7..a5ec6bccca7 100644 --- a/src/i18n/locales/en/common.json +++ b/src/i18n/locales/en/common.json @@ -47,7 +47,7 @@ "create_mcp_json": "Failed to create or open .roo/mcp.json: {{error}}", "hmr_not_running": "Local development server is not running, HMR will not work. Please run 'npm run dev' before launching the extension to enable HMR.", "retrieve_current_mode": "Error: failed to retrieve current mode from state.", - "summarization_failed": "Failed to summarize conversation context.", + "synthesization_failed": "Failed to synthesize conversation context.", "failed_delete_repo": "Failed to delete associated shadow repository or branch: {{error}}", "failed_remove_directory": "Failed to remove task directory: {{error}}", "custom_storage_path_unusable": "Custom storage path \"{{path}}\" is unusable, will use default path", @@ -69,8 +69,8 @@ "custom_storage_path_set": "Custom storage path set: {{path}}", "default_storage_path": "Reverted to using default storage path", "settings_imported": "Settings imported successfully.", - "summarizing_context": "Summarizing conversation context...", - "summarization_complete": "Conversation context summarization complete." + "synthesizing_context": "Synthesizing conversation context...", + "synthesization_complete": "Conversation context synthesization complete." }, "answers": { "yes": "Yes", diff --git a/src/services/summarization/ContextSummarizer.ts b/src/services/synthesization/ContextSynthesizer.ts similarity index 62% rename from src/services/summarization/ContextSummarizer.ts rename to src/services/synthesization/ContextSynthesizer.ts index a9e9dd26ece..97ba0e80829 100644 --- a/src/services/summarization/ContextSummarizer.ts +++ b/src/services/synthesization/ContextSynthesizer.ts @@ -3,36 +3,36 @@ import { Anthropic } from "@anthropic-ai/sdk" import { ApiHandler } from "../../api" /** - * Service responsible for summarizing conversation history segments. + * Service responsible for synthesizing conversation history segments. */ -export class ContextSummarizer { +export class ContextSynthesizer { private apiHandler: ApiHandler constructor(apiHandler: ApiHandler) { this.apiHandler = apiHandler - // TODO: Consider if a specific, potentially faster/cheaper model should be configured for summarization, + // TODO: Consider if a specific, potentially faster/cheaper model should be configured for synthesizing, // possibly by accepting a separate ApiConfiguration or model ID in the constructor. } /** - * Summarizes a given array of conversation messages using an LLM. - * @param messagesToSummarize The array of messages to be summarized. - * @returns A promise that resolves to a new message object containing the summary, - * or null if summarization fails or is not possible. + * Synthesizes a given array of conversation messages using an LLM. + * @param messagesToSynthesize The array of messages to be synthesized. + * @returns A promise that resolves to a new message object containing the synthesis, + * or null if synthesizing fails or is not possible. */ - async summarize(messagesToSummarize: Anthropic.MessageParam[]): Promise { - if (messagesToSummarize.length === 0) { - return null // Nothing to summarize + async synthesize(messagesToSynthesize: Anthropic.MessageParam[]): Promise { + if (messagesToSynthesize.length === 0) { + return null // Nothing to synthesize } - // Construct the prompt for the summarization model (User Final Refinement) + // Construct the prompt for the synthesizing model (User Final Refinement) const systemPrompt = `You are a specialized context compression system for Roo-Code, a VS Code extension that enables AI coding agents. Your sole purpose is to condense conversation history while preserving maximum technical context with minimum tokens. **Context Schema:** -- You are summarizing the MIDDLE portion of a conversation -- The original system prompt and initial interactions remain intact before your summary -- Recent conversation turns remain intact after your summary -- Your summary will be the critical bridge connecting these preserved segments +- You are synthesizing the MIDDLE portion of a conversation +- The original system prompt and initial interactions remain intact before your synthesis +- Recent conversation turns remain intact after your synthesis +- Your synthesis will be the critical bridge connecting these preserved segments **Content Priorities (Highest to Lowest):** 1. **Code Context:** @@ -64,7 +64,7 @@ export class ContextSummarizer { - Testing approaches **Output Requirements:** -- Produce ONLY the summary text with no meta-commentary +- Produce ONLY the synthesis text with no meta-commentary - Use precise, technical language optimized for information density - Structure with minimal formatting (use ## for major sections if necessary) - Omit pleasantries, acknowledgments, and conversational elements @@ -72,11 +72,11 @@ export class ContextSummarizer { - Use minimal tokens while maximizing preserved information - Prioritize factual over instructional content -This summary must enable seamless conversation continuity with no perceived context loss between the earlier and later preserved segments.` +This synthesis must enable seamless conversation continuity with no perceived context loss between the earlier and later preserved segments.` // Format the messages for the prompt. Simple stringification might be too verbose or lose structure. // Let's try a more readable format. - const formattedMessages = messagesToSummarize + const formattedMessages = messagesToSynthesize .map((msg) => { let contentText = "" if (Array.isArray(msg.content)) { @@ -95,27 +95,27 @@ This summary must enable seamless conversation continuity with no perceived cont }) .join("\n\n---\n\n") - const userPrompt = `Please summarize the following conversation turns:\n\n${formattedMessages}` + const userPrompt = `Please synthesize the following conversation turns:\n\n${formattedMessages}` try { - // Use the configured API handler to make the summarization call - // Note: This uses the main configured model. Consider allowing a specific summarization model. - // Disable prompt caching for summarization calls? - Currently not directly supported per-call. + // Use the configured API handler to make the synthesizing call + // Note: This uses the main configured model. Consider allowing a specific synthesizing model. + // Disable prompt caching for synthesizing calls? - Currently not directly supported per-call. // It will use the handler's configured caching setting. const stream = this.apiHandler.createMessage( systemPrompt, [{ role: "user", content: userPrompt }], - undefined, // No specific cache key for summarization + undefined, // No specific cache key for synthesizing // { promptCachingEnabled: false } // Removed incorrect 4th argument ) - let summaryText = "" + let synthesisText = "" let finalUsage = null // Consume the stream to get the full response for await (const chunk of stream) { if (chunk.type === "text") { - summaryText += chunk.text + synthesisText += chunk.text } else if (chunk.type === "usage") { // Capture usage details if needed for cost tracking/logging finalUsage = chunk @@ -123,24 +123,24 @@ This summary must enable seamless conversation continuity with no perceived cont } if (finalUsage) { - // Optional: Log summarization cost/tokens + // Optional: Log synthesizing cost/tokens console.log( - `[Summarization] Usage: In=${finalUsage.inputTokens}, Out=${finalUsage.outputTokens}, Cost=${finalUsage.totalCost?.toFixed(6) ?? "N/A"}`, + `[Synthesizing] Usage: In=${finalUsage.inputTokens}, Out=${finalUsage.outputTokens}, Cost=${finalUsage.totalCost?.toFixed(6) ?? "N/A"}`, ) } - if (!summaryText || summaryText.trim() === "") { - console.warn("Context summarization resulted in an empty summary.") + if (!synthesisText || synthesisText.trim() === "") { + console.warn("Context synthesizing resulted in an empty synthesis.") return null } - // Return the summary as a user message, representing the summarized history. + // Return the synthesis as a user message, representing the synthesized history. return { - role: "user", // Represents the summarized user/assistant interaction leading up to the current point. - content: `[Summarized Conversation History]\n${summaryText.trim()}`, + role: "user", // Represents the synthesized user/assistant interaction leading up to the current point. + content: `[Synthesized Conversation History]\n${synthesisText.trim()}`, } } catch (error) { - console.error("Context summarization API call failed:", error) + console.error("Context synthesizing API call failed:", error) // TODO: Add more robust error handling/logging (e.g., telemetry) return null // Indicate failure } diff --git a/src/shared/ExtensionMessage.ts b/src/shared/ExtensionMessage.ts index 11a104a8224..ad169c65e5e 100644 --- a/src/shared/ExtensionMessage.ts +++ b/src/shared/ExtensionMessage.ts @@ -67,7 +67,7 @@ export interface ExtensionMessage { | "toggleApiConfigPin" | "acceptInput" | "setHistoryPreviewCollapsed" - | "summarizationStatus" + | "synthesizationStatus" text?: string action?: | "chatButtonClicked" diff --git a/src/shared/WebviewMessage.ts b/src/shared/WebviewMessage.ts index ed12b3c3d53..f2f01d5bb0c 100644 --- a/src/shared/WebviewMessage.ts +++ b/src/shared/WebviewMessage.ts @@ -127,13 +127,13 @@ export interface WebviewMessage { | "searchFiles" | "toggleApiConfigPin" | "setHistoryPreviewCollapsed" - // Context Summarization Settings (Added) + // Context Synthesizing Settings | "enableContextSummarization" | "contextSummarizationTriggerThreshold" | "contextSummarizationInitialStaticTurns" | "contextSummarizationRecentTurns" - | "manualSummarize" - | "summarizationStatus" + | "manualSynthesize" + | "synthesizationStatus" text?: string disabled?: boolean askResponse?: ClineAskResponse diff --git a/webview-ui/src/components/chat/ChatTextArea.tsx b/webview-ui/src/components/chat/ChatTextArea.tsx index 3daf07faa28..fc1304de1c9 100644 --- a/webview-ui/src/components/chat/ChatTextArea.tsx +++ b/webview-ui/src/components/chat/ChatTextArea.tsx @@ -252,9 +252,9 @@ const ChatTextArea = forwardRef( setShowContextMenu(false) setSelectedType(null) - if (type === ContextMenuOptionType.Summarize) { - // Handle summarize action - trigger manual summarization - vscode.postMessage({ type: "manualSummarize" }) + if (type === ContextMenuOptionType.Synthesize) { + // Handle synthesize action - trigger manual synthesizing + vscode.postMessage({ type: "manualSynthesize" }) setShowContextMenu(false) setSelectedType(null) return diff --git a/webview-ui/src/components/chat/ChatView.tsx b/webview-ui/src/components/chat/ChatView.tsx index f94cad2cda2..d7cfa650f8d 100644 --- a/webview-ui/src/components/chat/ChatView.tsx +++ b/webview-ui/src/components/chat/ChatView.tsx @@ -38,7 +38,7 @@ import ChatTextArea from "./ChatTextArea" import TaskHeader from "./TaskHeader" import AutoApproveMenu from "./AutoApproveMenu" import SystemPromptWarning from "./SystemPromptWarning" -import SummarizationIndicator from "./SummarizationIndicator" +import SynthesizingIndicator from "./SynthesizingIndicator" import { useTaskSearch } from "../history/useTaskSearch" export interface ChatViewProps { @@ -1251,9 +1251,9 @@ const ChatViewComponent: React.ForwardRefRenderFunction )} - {/* Summarization status indicator */} + {/* Synthesizing status indicator */}
- +
) : ( diff --git a/webview-ui/src/components/chat/ContextMenu.tsx b/webview-ui/src/components/chat/ContextMenu.tsx index 0735bb9db7b..62b90e91573 100644 --- a/webview-ui/src/components/chat/ContextMenu.tsx +++ b/webview-ui/src/components/chat/ContextMenu.tsx @@ -89,8 +89,8 @@ const ContextMenu: React.FC = ({ return Problems case ContextMenuOptionType.Terminal: return Terminal - case ContextMenuOptionType.Summarize: - return {option.label || "Summarize"} + case ContextMenuOptionType.Synthesize: + return {option.label || "Synthesize"} case ContextMenuOptionType.URL: return Paste URL to fetch contents case ContextMenuOptionType.NoResults: @@ -177,7 +177,7 @@ const ContextMenu: React.FC = ({ return "link" case ContextMenuOptionType.Git: return "git-commit" - case ContextMenuOptionType.Summarize: + case ContextMenuOptionType.Synthesize: return "archive" case ContextMenuOptionType.NoResults: return "info" diff --git a/webview-ui/src/components/chat/SummarizationIndicator.tsx b/webview-ui/src/components/chat/SynthesizingIndicator.tsx similarity index 50% rename from webview-ui/src/components/chat/SummarizationIndicator.tsx rename to webview-ui/src/components/chat/SynthesizingIndicator.tsx index a49f5b9723e..5573ffba7e9 100644 --- a/webview-ui/src/components/chat/SummarizationIndicator.tsx +++ b/webview-ui/src/components/chat/SynthesizingIndicator.tsx @@ -3,12 +3,12 @@ import { cn } from "@/lib/utils" import { useExtensionState } from "@/context/ExtensionStateContext" /** - * A component that displays the current status of context summarization + * A component that displays the current status of context synthesizing */ -export const SummarizationIndicator: React.FC = () => { - const { summarizationStatus } = useExtensionState() +export const SynthesizingIndicator: React.FC = () => { + const { synthesizationStatus } = useExtensionState() - if (!summarizationStatus) { + if (!synthesizationStatus) { return null } @@ -16,20 +16,20 @@ export const SummarizationIndicator: React.FC = () => {
- {summarizationStatus.status === "started" && ( + {synthesizationStatus.status === "started" && ( )} - {summarizationStatus.status === "completed" && } - {summarizationStatus.status === "failed" && } - {summarizationStatus.text} + {synthesizationStatus.status === "completed" && } + {synthesizationStatus.status === "failed" && } + {synthesizationStatus.text}
) } -export default SummarizationIndicator +export default SynthesizingIndicator diff --git a/webview-ui/src/components/settings/ContextManagementSettings.tsx b/webview-ui/src/components/settings/ContextManagementSettings.tsx index 5df93e2214b..78702b6482d 100644 --- a/webview-ui/src/components/settings/ContextManagementSettings.tsx +++ b/webview-ui/src/components/settings/ContextManagementSettings.tsx @@ -156,18 +156,18 @@ export const ContextManagementSettings = ({ onChange={(e: any) => setCachedStateField("enableContextSummarization", e.target.checked)} // Use generic setter data-testid="enable-context-summarization-checkbox">
- {t("settings:contextManagement.summarization.enable.description")} + {t("settings:contextManagement.synthesization.enable.description")}
- {t("settings:contextManagement.summarization.triggerThreshold.label")} + {t("settings:contextManagement.synthesization.triggerThreshold.label")}
- {t("settings:contextManagement.summarization.triggerThreshold.description")} + {t("settings:contextManagement.synthesization.triggerThreshold.description")}
- {t("settings:contextManagement.summarization.initialTurns.label")} + {t("settings:contextManagement.synthesization.initialTurns.label")}
- {t("settings:contextManagement.summarization.turns")} + {t("settings:contextManagement.synthesization.turns")}
- {t("settings:contextManagement.summarization.initialTurns.description")} + {t("settings:contextManagement.synthesization.initialTurns.description")}
- {t("settings:contextManagement.summarization.recentTurns.label")} + {t("settings:contextManagement.synthesization.recentTurns.label")}
- {t("settings:contextManagement.summarization.turns")} + {t("settings:contextManagement.synthesization.turns")}
- {t("settings:contextManagement.summarization.recentTurns.description")} + {t("settings:contextManagement.synthesization.recentTurns.description")}
{/* --- End Context Summarization Settings --- */} diff --git a/webview-ui/src/context/ExtensionStateContext.tsx b/webview-ui/src/context/ExtensionStateContext.tsx index 019bfb95e6d..a8d6898c050 100644 --- a/webview-ui/src/context/ExtensionStateContext.tsx +++ b/webview-ui/src/context/ExtensionStateContext.tsx @@ -20,7 +20,7 @@ export interface ExtensionStateContextType extends ExtensionState { mcpServers: McpServer[] hasSystemPromptOverride?: boolean currentCheckpoint?: string - summarizationStatus?: { + synthesizationStatus?: { status: "started" | "completed" | "failed" text: string } @@ -98,7 +98,7 @@ export interface ExtensionStateContextType extends ExtensionState { setTerminalCompressProgressBar: (value: boolean) => void setHistoryPreviewCollapsed: (value: boolean) => void - // Context Summarization Setters (Added) + // Context Synthesization Setters enableContextSummarization: boolean setEnableContextSummarization: (value: boolean) => void contextSummarizationTriggerThreshold: number @@ -252,12 +252,12 @@ export const ExtensionStateContextProvider: React.FC<{ children: React.ReactNode setCurrentCheckpoint(message.text) break } - case "summarizationStatus": { + case "synthesizationStatus": { // For "started" status, update the state immediately if (message.status === "started") { setState((prevState) => ({ ...prevState, - summarizationStatus: { + synthesizationStatus: { status: message.status as "started" | "completed" | "failed", text: message.text || "", }, @@ -268,7 +268,7 @@ export const ExtensionStateContextProvider: React.FC<{ children: React.ReactNode // First update the status setState((prevState) => ({ ...prevState, - summarizationStatus: { + synthesizationStatus: { status: message.status as "completed" | "failed", text: message.text || "", }, @@ -278,7 +278,7 @@ export const ExtensionStateContextProvider: React.FC<{ children: React.ReactNode const timer = setTimeout(() => { setState((prevState) => ({ ...prevState, - summarizationStatus: undefined, + synthesizationStatus: undefined, })) }, 3000) // Reduced to 3 seconds for better UX @@ -401,7 +401,7 @@ export const ExtensionStateContextProvider: React.FC<{ children: React.ReactNode setHistoryPreviewCollapsed: (value) => setState((prevState) => ({ ...prevState, historyPreviewCollapsed: value })), // Implement the setter - // Context Summarization Setters Implementation (Added) + // Context Synthesization Setters Implementation setEnableContextSummarization: (value) => { setState((prevState) => ({ ...prevState, enableContextSummarization: value })) vscode.postMessage({ type: "enableContextSummarization", bool: value }) diff --git a/webview-ui/src/i18n/locales/en/settings.json b/webview-ui/src/i18n/locales/en/settings.json index 501e7be9003..51c6cd2de7e 100644 --- a/webview-ui/src/i18n/locales/en/settings.json +++ b/webview-ui/src/i18n/locales/en/settings.json @@ -301,14 +301,14 @@ "lines": "lines", "always_full_read": "Always read entire file" }, - "summarization": { + "synthesization": { "enable": { - "label": "Enable context summarization", - "description": "When enabled, older conversation turns will be summarized instead of truncated when the context limit is approached. This preserves more information but incurs additional token costs for summarization." + "label": "Enable automatic context synthesization", + "description": "When enabled, older conversation turns will be synthesized instead of truncated when the context limit is approached. This preserves more information but incurs additional token costs for synthesization." }, "triggerThreshold": { - "label": "Summarization trigger threshold", - "description": "Percentage of the context window size at which summarization should be triggered (e.g., 80%)." + "label": "Synthesization trigger threshold", + "description": "Percentage of the context window size at which synthesization should be triggered (e.g., 80%)." }, "initialTurns": { "label": "Initial turns to keep", diff --git a/webview-ui/src/utils/context-mentions.ts b/webview-ui/src/utils/context-mentions.ts index 8c843491ed2..83ee06cc3e9 100644 --- a/webview-ui/src/utils/context-mentions.ts +++ b/webview-ui/src/utils/context-mentions.ts @@ -1,7 +1,11 @@ import { mentionRegex } from "@roo/shared/context-mentions" import { Fzf } from "fzf" import { ModeConfig } from "@roo/shared/modes" -import * as path from "path" + +// Simple basename function to replace path.basename +function basename(filepath: string): string { + return filepath.split("/").pop() || filepath +} export interface SearchResult { path: string @@ -77,7 +81,7 @@ export enum ContextMenuOptionType { Git = "git", NoResults = "noResults", Mode = "mode", // Add mode type - Summarize = "summarize", // Add summarize type + Synthesize = "synthesize", // Add synthesize type } export interface ContextMenuQueryItem { @@ -165,7 +169,11 @@ export function getContextMenuOptions( } return [ - { type: ContextMenuOptionType.Summarize, label: "Summarize", description: "Compress conversation history" }, + { + type: ContextMenuOptionType.Synthesize, + label: "Synthesize", + description: "Compress conversation history", + }, { type: ContextMenuOptionType.Problems }, { type: ContextMenuOptionType.Terminal }, { type: ContextMenuOptionType.URL }, @@ -195,10 +203,10 @@ export function getContextMenuOptions( if ("terminal".startsWith(lowerQuery)) { suggestions.push({ type: ContextMenuOptionType.Terminal }) } - if ("summarize".startsWith(lowerQuery)) { + if ("synthesize".startsWith(lowerQuery)) { suggestions.push({ - type: ContextMenuOptionType.Summarize, - label: "Summarize", + type: ContextMenuOptionType.Synthesize, + label: "Synthesize", description: "Compress conversation history", }) } @@ -250,7 +258,7 @@ export function getContextMenuOptions( return { type: result.type === "folder" ? ContextMenuOptionType.Folder : ContextMenuOptionType.File, value: formattedPath, - label: result.label || path.basename(result.path), + label: result.label || basename(result.path), description: formattedPath, } }) From d9a1ef6788928ee25ea6cc8b034bce724b73256d Mon Sep 17 00:00:00 2001 From: Shariq Riaz Date: Tue, 29 Apr 2025 04:35:10 +0500 Subject: [PATCH 11/16] fix-cline-conflict --- src/core/Cline.ts | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/core/Cline.ts b/src/core/Cline.ts index 99bee874a61..23b8b782399 100644 --- a/src/core/Cline.ts +++ b/src/core/Cline.ts @@ -1031,7 +1031,9 @@ export class Cline extends EventEmitter { // either truncate or summarize the conversation history based on settings. if (previousApiReqIndex >= 0) { const previousRequest = this.clineMessages[previousApiReqIndex]?.text - if (!previousRequest) return // Should not happen, but guard anyway + if (!previousRequest) { + return // Should not happen, but guard anyway + } const { // These tokens are from the *previous* request's response, not the current history size From 13d239ce49db00ef07136d04584ac100cc24799b Mon Sep 17 00:00:00 2001 From: Shariq Riaz Date: Tue, 29 Apr 2025 14:11:11 +0500 Subject: [PATCH 12/16] fix-inconsistencies --- src/core/Cline.ts | 40 +++++++++---------- src/core/webview/ClineProvider.ts | 14 +++---- .../webview/__tests__/ClineProvider.test.ts | 6 +-- src/schemas/index.ts | 4 +- src/shared/ExtensionMessage.ts | 4 +- .../settings/ContextManagementSettings.tsx | 18 ++++----- .../src/components/settings/SettingsView.tsx | 6 +-- .../ContextManagementSettings.test.tsx | 38 +++++++++--------- .../src/context/ExtensionStateContext.tsx | 2 +- .../__tests__/ExtensionStateContext.test.tsx | 2 +- webview-ui/src/i18n/locales/ca/settings.json | 2 +- webview-ui/src/i18n/locales/de/settings.json | 2 +- webview-ui/src/i18n/locales/es/settings.json | 2 +- webview-ui/src/i18n/locales/fr/settings.json | 2 +- webview-ui/src/i18n/locales/hi/settings.json | 2 +- webview-ui/src/i18n/locales/it/settings.json | 2 +- webview-ui/src/i18n/locales/ja/settings.json | 2 +- webview-ui/src/i18n/locales/ko/settings.json | 2 +- webview-ui/src/i18n/locales/pl/settings.json | 2 +- .../src/i18n/locales/pt-BR/settings.json | 2 +- webview-ui/src/i18n/locales/ru/settings.json | 2 +- webview-ui/src/i18n/locales/tr/settings.json | 2 +- webview-ui/src/i18n/locales/vi/settings.json | 2 +- .../src/i18n/locales/zh-CN/settings.json | 2 +- .../src/i18n/locales/zh-TW/settings.json | 2 +- 25 files changed, 82 insertions(+), 82 deletions(-) diff --git a/src/core/Cline.ts b/src/core/Cline.ts index 9ab93b96728..749a1d5df59 100644 --- a/src/core/Cline.ts +++ b/src/core/Cline.ts @@ -126,7 +126,7 @@ export type ClineOptions = { parentTask?: Cline taskNumber?: number onCreated?: (cline: Cline) => void - // Context Summarization Settings + // Context Synthesization Settings enableContextSummarization?: boolean // Already added contextSummarizationTriggerThreshold?: number // Already added contextSummarizationInitialStaticTurns?: number // Already added @@ -160,7 +160,7 @@ export class Cline extends EventEmitter { diffEnabled: boolean = false fuzzyMatchThreshold: number - // Context Summarization Settings (Added) + // Context Synthesization Settings (Added) readonly enableContextSummarization: boolean readonly contextSummarizationTriggerThreshold: number readonly contextSummarizationInitialStaticTurns: number @@ -229,7 +229,7 @@ export class Cline extends EventEmitter { parentTask, taskNumber = -1, onCreated, - // Context Summarization Settings (Added) + // Context Synthesization Settings (Added) enableContextSummarization = false, contextSummarizationTriggerThreshold = 80, contextSummarizationInitialStaticTurns = 5, @@ -1062,7 +1062,7 @@ export class Cline extends EventEmitter { : modelInfo.maxTokens const contextWindow = modelInfo.contextWindow - let historyModifiedBySummarization = false // Flag to track if summarization updated history + let historyModifiedBySynthesization = false // Flag to track if synthesization updated history if (this.enableContextSummarization) { // --- Synthesizing Logic --- @@ -1090,7 +1090,7 @@ export class Cline extends EventEmitter { if (initialSliceEnd < recentSliceStart) { const initialMessages = this.apiConversationHistory.slice(0, initialSliceEnd) const recentMessages = this.apiConversationHistory.slice(recentSliceStart) - const messagesToSummarize = this.apiConversationHistory.slice( + const messagesToSynthesize = this.apiConversationHistory.slice( initialSliceEnd, recentSliceStart, ) @@ -1098,12 +1098,12 @@ export class Cline extends EventEmitter { this.providerRef .deref() ?.log( - `[Synthesizing] Slicing: Keep Initial ${initialMessages.length}, Synthesize ${messagesToSummarize.length}, Keep Recent ${recentMessages.length}`, + `[Synthesizing] Slicing: Keep Initial ${initialMessages.length}, Synthesize ${messagesToSynthesize.length}, Keep Recent ${recentMessages.length}`, ) // Instantiate the synthesizer (consider using a dedicated API handler/model later) const synthesizer = new ContextSynthesizer(this.api) - const summaryMessage = await synthesizer.synthesize(messagesToSummarize) + const summaryMessage = await synthesizer.synthesize(messagesToSynthesize) if (summaryMessage) { const newHistory = [...initialMessages, summaryMessage, ...recentMessages] @@ -1115,12 +1115,12 @@ export class Cline extends EventEmitter { // Add a system message to notify the user in the UI await this.say("text", "[Older conversation turns synthesized to preserve context]") await this.overwriteApiConversationHistory(newHistory) - historyModifiedBySummarization = true // Mark history as modified + historyModifiedBySynthesization = true // Mark history as modified } else { this.providerRef .deref() ?.log(`[Synthesizing] Synthesizing failed. Falling back to truncation.`) - // Fall through to truncation if summarization fails + // Fall through to truncation if synthesization fails } } else { this.providerRef @@ -1139,15 +1139,15 @@ export class Cline extends EventEmitter { // Fall through to truncation if history is too short } } - // If summarization is enabled but threshold not met, do nothing and proceed. + // If synthesization is enabled but threshold not met, do nothing and proceed. } - // --- Truncation Logic (Only run if summarization didn't modify history) --- - if (!historyModifiedBySummarization) { + // --- Truncation Logic (Only run if synthesization didn't modify history) --- + if (!historyModifiedBySynthesization) { // Note: totalTokens here refers to the previous response size, used by truncateConversationIfNeeded // to estimate if the *next* request might overflow. const trimmedMessages = await truncateConversationIfNeeded({ - messages: this.apiConversationHistory, // Use potentially already summarized history if summarization failed above + messages: this.apiConversationHistory, // Use potentially already summarized history if synthesization failed above totalTokens, // From previous response metrics maxTokens, contextWindow, @@ -2695,9 +2695,9 @@ export class Cline extends EventEmitter { } /** - * Manually triggers summarization of the conversation context. - * @param isManualTrigger Whether this summarization was manually triggered by the user. - * @returns A promise that resolves when summarization is complete. + * Manually triggers synthesization of the conversation context. + * @param isManualTrigger Whether this synthesization was manually triggered by the user. + * @returns A promise that resolves when synthesization is complete. */ public async synthesizeConversationContext(_isManualTrigger: boolean = false): Promise { // Skip if synthesizing is disabled @@ -2736,17 +2736,17 @@ export class Cline extends EventEmitter { // Slice the conversation history const initialMessages = this.apiConversationHistory.slice(0, initialSliceEnd) const recentMessages = this.apiConversationHistory.slice(recentSliceStart) - const messagesToSummarize = this.apiConversationHistory.slice(initialSliceEnd, recentSliceStart) + const messagesToSynthesize = this.apiConversationHistory.slice(initialSliceEnd, recentSliceStart) this.providerRef .deref() ?.log( - `[Synthesizing] Slicing: Keep Initial ${initialMessages.length}, Synthesize ${messagesToSummarize.length}, Keep Recent ${recentMessages.length}`, + `[Synthesizing] Slicing: Keep Initial ${initialMessages.length}, Synthesize ${messagesToSynthesize.length}, Keep Recent ${recentMessages.length}`, ) // Create synthesizer and generate synthesis const synthesizer = new ContextSynthesizer(this.api) - const summaryMessage = await synthesizer.synthesize(messagesToSummarize) + const summaryMessage = await synthesizer.synthesize(messagesToSynthesize) if (!summaryMessage) { this.providerRef.deref()?.log(`[Synthesizing] Failed to generate synthesis.`) @@ -2762,7 +2762,7 @@ export class Cline extends EventEmitter { this.providerRef .deref() ?.log( - `[Synthesizing] Successfully synthesized ${messagesToSummarize.length} messages. New history length: ${newHistory.length}`, + `[Synthesizing] Successfully synthesized ${messagesToSynthesize.length} messages. New history length: ${newHistory.length}`, ) } } diff --git a/src/core/webview/ClineProvider.ts b/src/core/webview/ClineProvider.ts index 448560e45f7..5d9c919be9b 100644 --- a/src/core/webview/ClineProvider.ts +++ b/src/core/webview/ClineProvider.ts @@ -494,7 +494,7 @@ export class ClineProvider extends EventEmitter implements mode, customInstructions: globalInstructions, experiments, - // Context Summarization Settings (Added) + // Context Synthesization Settings (Added) enableContextSummarization, contextSummarizationTriggerThreshold, contextSummarizationInitialStaticTurns, @@ -518,7 +518,7 @@ export class ClineProvider extends EventEmitter implements parentTask, taskNumber: this.clineStack.length + 1, onCreated: (cline) => this.emit("clineCreated", cline), - // Pass summarization settings to Cline (Added) + // Pass synthesization settings to Cline (Added) enableContextSummarization, contextSummarizationTriggerThreshold, contextSummarizationInitialStaticTurns, @@ -547,7 +547,7 @@ export class ClineProvider extends EventEmitter implements mode, customInstructions: globalInstructions, experiments, - // Context Summarization Settings (Added) + // Context Synthesization Settings (Added) enableContextSummarization, contextSummarizationTriggerThreshold, contextSummarizationInitialStaticTurns, @@ -570,7 +570,7 @@ export class ClineProvider extends EventEmitter implements parentTask: historyItem.parentTask, taskNumber: historyItem.number, onCreated: (cline) => this.emit("clineCreated", cline), - // Pass summarization settings to Cline (Added) + // Pass synthesization settings to Cline (Added) enableContextSummarization, contextSummarizationTriggerThreshold, contextSummarizationInitialStaticTurns, @@ -1235,7 +1235,7 @@ export class ClineProvider extends EventEmitter implements maxReadFileLine, terminalCompressProgressBar, historyPreviewCollapsed, - // Context Summarization Settings (Added) + // Context Synthesization Settings (Added) enableContextSummarization, contextSummarizationTriggerThreshold, contextSummarizationInitialStaticTurns, @@ -1327,7 +1327,7 @@ export class ClineProvider extends EventEmitter implements terminalCompressProgressBar: terminalCompressProgressBar ?? true, hasSystemPromptOverride, historyPreviewCollapsed: historyPreviewCollapsed ?? false, - // Context Summarization Settings (Added) + // Context Synthesization Settings (Added) enableContextSummarization: enableContextSummarization ?? false, contextSummarizationTriggerThreshold: contextSummarizationTriggerThreshold ?? 80, contextSummarizationInitialStaticTurns: contextSummarizationInitialStaticTurns ?? 5, @@ -1422,7 +1422,7 @@ export class ClineProvider extends EventEmitter implements showRooIgnoredFiles: stateValues.showRooIgnoredFiles ?? true, maxReadFileLine: stateValues.maxReadFileLine ?? 500, historyPreviewCollapsed: stateValues.historyPreviewCollapsed ?? false, - // Context Summarization Settings (Added) + // Context Synthesization Settings (Added) enableContextSummarization: stateValues.enableContextSummarization ?? false, contextSummarizationTriggerThreshold: stateValues.contextSummarizationTriggerThreshold ?? 80, contextSummarizationInitialStaticTurns: stateValues.contextSummarizationInitialStaticTurns ?? 5, diff --git a/src/core/webview/__tests__/ClineProvider.test.ts b/src/core/webview/__tests__/ClineProvider.test.ts index 3336f0bb7ce..c966104c07c 100644 --- a/src/core/webview/__tests__/ClineProvider.test.ts +++ b/src/core/webview/__tests__/ClineProvider.test.ts @@ -425,7 +425,7 @@ describe("ClineProvider", () => { showRooIgnoredFiles: true, renderContext: "sidebar", maxReadFileLine: 500, - // Context Summarization Defaults (Added for test) + // Context Synthesization Defaults (Added for test) enableContextSummarization: false, contextSummarizationTriggerThreshold: 80, contextSummarizationInitialStaticTurns: 5, @@ -734,7 +734,7 @@ describe("ClineProvider", () => { expect((await provider.getState()).showRooIgnoredFiles).toBe(false) }) - test("handles context summarization settings messages", async () => { + test("handles context synthesization settings messages", async () => { await provider.resolveWebviewView(mockWebviewView) const messageHandler = (mockWebviewView.webview.onDidReceiveMessage as jest.Mock).mock.calls[0][0] @@ -773,7 +773,7 @@ describe("ClineProvider", () => { expect((await provider.getState()).contextSummarizationRecentTurns).toBe(15) }) - test("context summarization settings have correct default values", async () => { + test("context synthesization settings have correct default values", async () => { // Mock globalState.get to return undefined for the new settings ;(mockContext.globalState.get as jest.Mock).mockImplementation((key: string) => { if ( diff --git a/src/schemas/index.ts b/src/schemas/index.ts index d69b61f9c09..2bb084a549c 100644 --- a/src/schemas/index.ts +++ b/src/schemas/index.ts @@ -558,7 +558,7 @@ export const globalSettingsSchema = z.object({ terminalZdotdir: z.boolean().optional(), terminalCompressProgressBar: z.boolean().optional(), - // Context Summarization Settings + // Context Synthesization Settings enableContextSummarization: z.boolean().optional(), contextSummarizationTriggerThreshold: z.number().optional(), // Percentage (e.g., 80) contextSummarizationInitialStaticTurns: z.number().optional(), // Number of initial turns to keep @@ -642,7 +642,7 @@ const globalSettingsRecord: GlobalSettingsRecord = { terminalZdotdir: undefined, terminalCompressProgressBar: undefined, - // Context Summarization Settings + // Context Synthesization Settings enableContextSummarization: undefined, contextSummarizationTriggerThreshold: undefined, contextSummarizationInitialStaticTurns: undefined, diff --git a/src/shared/ExtensionMessage.ts b/src/shared/ExtensionMessage.ts index ad169c65e5e..4f63fc66278 100644 --- a/src/shared/ExtensionMessage.ts +++ b/src/shared/ExtensionMessage.ts @@ -166,7 +166,7 @@ export type ExtensionState = Pick< | "customModePrompts" | "customSupportPrompts" | "enhancementApiConfigId" - // Context Summarization Settings (Added) + // Context Synthesization Settings (Added) | "enableContextSummarization" | "contextSummarizationTriggerThreshold" | "contextSummarizationInitialStaticTurns" @@ -208,7 +208,7 @@ export type ExtensionState = Pick< settingsImportedAt?: number historyPreviewCollapsed?: boolean - // Context Summarization Settings (Required part) + // Context Synthesization Settings (Required part) enableContextSummarization: boolean contextSummarizationTriggerThreshold: number contextSummarizationInitialStaticTurns: number diff --git a/webview-ui/src/components/settings/ContextManagementSettings.tsx b/webview-ui/src/components/settings/ContextManagementSettings.tsx index 78702b6482d..b45fc296604 100644 --- a/webview-ui/src/components/settings/ContextManagementSettings.tsx +++ b/webview-ui/src/components/settings/ContextManagementSettings.tsx @@ -15,7 +15,7 @@ type ContextManagementSettingsProps = HTMLAttributes & { maxWorkspaceFiles: number showRooIgnoredFiles?: boolean maxReadFileLine?: number - // Context Summarization Props (Added) + // Context Synthesization Props (Added) enableContextSummarization?: boolean contextSummarizationTriggerThreshold?: number contextSummarizationInitialStaticTurns?: number @@ -26,7 +26,7 @@ type ContextManagementSettingsProps = HTMLAttributes & { | "maxWorkspaceFiles" | "showRooIgnoredFiles" | "maxReadFileLine" - // Context Summarization Keys (Added) + // Context Synthesization Keys (Added) | "enableContextSummarization" | "contextSummarizationTriggerThreshold" | "contextSummarizationInitialStaticTurns" @@ -40,7 +40,7 @@ export const ContextManagementSettings = ({ showRooIgnoredFiles, setCachedStateField, maxReadFileLine, - // Context Summarization Props (Added) + // Context Synthesization Props (Added) enableContextSummarization, contextSummarizationTriggerThreshold, contextSummarizationInitialStaticTurns, @@ -147,14 +147,14 @@ export const ContextManagementSettings = ({
- {/* --- Context Summarization Settings --- */} + {/* --- Context Synthesization Settings --- */}
setCachedStateField("enableContextSummarization", e.target.checked)} // Use generic setter - data-testid="enable-context-summarization-checkbox"> + data-testid="enable-context-synthesization-checkbox"> @@ -184,7 +184,7 @@ export const ContextManagementSettings = ({ } }} onClick={(e) => e.currentTarget.select()} - data-testid="context-summarization-trigger-threshold-input" + data-testid="context-synthesization-trigger-threshold-input" disabled={!enableContextSummarization} /> % @@ -214,7 +214,7 @@ export const ContextManagementSettings = ({ } }} onClick={(e) => e.currentTarget.select()} - data-testid="context-summarization-initial-turns-input" + data-testid="context-synthesization-initial-turns-input" disabled={!enableContextSummarization} /> {t("settings:contextManagement.synthesization.turns")} @@ -244,7 +244,7 @@ export const ContextManagementSettings = ({ } }} onClick={(e) => e.currentTarget.select()} - data-testid="context-summarization-recent-turns-input" + data-testid="context-synthesization-recent-turns-input" disabled={!enableContextSummarization} /> {t("settings:contextManagement.synthesization.turns")} @@ -254,7 +254,7 @@ export const ContextManagementSettings = ({ {t("settings:contextManagement.synthesization.recentTurns.description")}
- {/* --- End Context Summarization Settings --- */} + {/* --- End Context Synthesization Settings --- */} ) diff --git a/webview-ui/src/components/settings/SettingsView.tsx b/webview-ui/src/components/settings/SettingsView.tsx index 18c9bc8b08b..1925f7483f3 100644 --- a/webview-ui/src/components/settings/SettingsView.tsx +++ b/webview-ui/src/components/settings/SettingsView.tsx @@ -259,7 +259,7 @@ const SettingsView = forwardRef(({ onDone, t vscode.postMessage({ type: "updateExperimental", values: experiments }) vscode.postMessage({ type: "alwaysAllowModeSwitch", bool: alwaysAllowModeSwitch }) vscode.postMessage({ type: "alwaysAllowSubtasks", bool: alwaysAllowSubtasks }) - // Context Summarization Settings (Added - Use cachedState values) + // Context Synthesization Settings (Added - Use cachedState values) vscode.postMessage({ type: "enableContextSummarization", bool: cachedState.enableContextSummarization }) vscode.postMessage({ type: "contextSummarizationTriggerThreshold", @@ -273,7 +273,7 @@ const SettingsView = forwardRef(({ onDone, t type: "contextSummarizationRecentTurns", value: cachedState.contextSummarizationRecentTurns, }) - // --- End Context Summarization --- + // --- End Context Synthesization --- vscode.postMessage({ type: "upsertApiConfiguration", text: currentApiConfigName, apiConfiguration }) vscode.postMessage({ type: "telemetrySetting", text: telemetrySetting }) setChangeDetected(false) @@ -496,7 +496,7 @@ const SettingsView = forwardRef(({ onDone, t showRooIgnoredFiles={showRooIgnoredFiles} maxReadFileLine={maxReadFileLine} setCachedStateField={setCachedStateField} - // Pass summarization state from cachedState (Added) + // Pass synthesization state from cachedState (Added) enableContextSummarization={cachedState.enableContextSummarization} contextSummarizationTriggerThreshold={cachedState.contextSummarizationTriggerThreshold} contextSummarizationInitialStaticTurns={cachedState.contextSummarizationInitialStaticTurns} diff --git a/webview-ui/src/components/settings/__tests__/ContextManagementSettings.test.tsx b/webview-ui/src/components/settings/__tests__/ContextManagementSettings.test.tsx index fa97c35daec..5ef7b5cd6d9 100644 --- a/webview-ui/src/components/settings/__tests__/ContextManagementSettings.test.tsx +++ b/webview-ui/src/components/settings/__tests__/ContextManagementSettings.test.tsx @@ -62,16 +62,16 @@ describe("ContextManagementSettings", () => { expect(showRooIgnoredFilesCheckbox).toBeInTheDocument() expect(screen.getByTestId("show-rooignored-files-checkbox")).not.toBeChecked() - // Summarization controls (Added) - expect(screen.getByTestId("enable-context-summarization-checkbox")).toBeInTheDocument() - expect(screen.getByTestId("context-summarization-trigger-threshold-input")).toBeInTheDocument() - expect(screen.getByTestId("context-summarization-initial-turns-input")).toBeInTheDocument() - expect(screen.getByTestId("context-summarization-recent-turns-input")).toBeInTheDocument() + // Synthesization controls (Added) + expect(screen.getByTestId("enable-context-synthesization-checkbox")).toBeInTheDocument() + expect(screen.getByTestId("context-synthesization-trigger-threshold-input")).toBeInTheDocument() + expect(screen.getByTestId("context-synthesization-initial-turns-input")).toBeInTheDocument() + expect(screen.getByTestId("context-synthesization-recent-turns-input")).toBeInTheDocument() // Check initial disabled state for sub-settings (Added) - expect(screen.getByTestId("context-summarization-trigger-threshold-input")).toBeDisabled() - expect(screen.getByTestId("context-summarization-initial-turns-input")).toBeDisabled() - expect(screen.getByTestId("context-summarization-recent-turns-input")).toBeDisabled() + expect(screen.getByTestId("context-synthesization-trigger-threshold-input")).toBeDisabled() + expect(screen.getByTestId("context-synthesization-initial-turns-input")).toBeDisabled() + expect(screen.getByTestId("context-synthesization-recent-turns-input")).toBeDisabled() }) it("updates open tabs context limit", () => { @@ -101,40 +101,40 @@ describe("ContextManagementSettings", () => { expect(defaultProps.setCachedStateField).toHaveBeenCalledWith("showRooIgnoredFiles", true) }) - // --- Tests for new summarization settings --- (Added) + // --- Tests for new synthesization settings --- (Added) - it("enables sub-settings when summarization is enabled", () => { + it("enables sub-settings when synthesization is enabled", () => { render() - expect(screen.getByTestId("context-summarization-trigger-threshold-input")).not.toBeDisabled() - expect(screen.getByTestId("context-summarization-initial-turns-input")).not.toBeDisabled() - expect(screen.getByTestId("context-summarization-recent-turns-input")).not.toBeDisabled() + expect(screen.getByTestId("context-synthesization-trigger-threshold-input")).not.toBeDisabled() + expect(screen.getByTestId("context-synthesization-initial-turns-input")).not.toBeDisabled() + expect(screen.getByTestId("context-synthesization-recent-turns-input")).not.toBeDisabled() }) - it("updates enable context summarization setting", () => { + it("updates enable context synthesization setting", () => { render() - const checkbox = screen.getByTestId("enable-context-summarization-checkbox") + const checkbox = screen.getByTestId("enable-context-synthesization-checkbox") fireEvent.click(checkbox) expect(defaultProps.setEnableContextSummarization).toHaveBeenCalledWith(true) }) - it("updates summarization trigger threshold", () => { + it("updates synthesization trigger threshold", () => { render() // Enable first - const input = screen.getByTestId("context-summarization-trigger-threshold-input") + const input = screen.getByTestId("context-synthesization-trigger-threshold-input") fireEvent.change(input, { target: { value: "95" } }) expect(defaultProps.setContextSummarizationTriggerThreshold).toHaveBeenCalledWith(95) }) it("updates initial turns to keep", () => { render() // Enable first - const input = screen.getByTestId("context-summarization-initial-turns-input") + const input = screen.getByTestId("context-synthesization-initial-turns-input") fireEvent.change(input, { target: { value: "3" } }) expect(defaultProps.setContextSummarizationInitialStaticTurns).toHaveBeenCalledWith(3) }) it("updates recent turns to keep", () => { render() // Enable first - const input = screen.getByTestId("context-summarization-recent-turns-input") + const input = screen.getByTestId("context-synthesization-recent-turns-input") fireEvent.change(input, { target: { value: "12" } }) expect(defaultProps.setContextSummarizationRecentTurns).toHaveBeenCalledWith(12) }) diff --git a/webview-ui/src/context/ExtensionStateContext.tsx b/webview-ui/src/context/ExtensionStateContext.tsx index a8d6898c050..fd074af2cb9 100644 --- a/webview-ui/src/context/ExtensionStateContext.tsx +++ b/webview-ui/src/context/ExtensionStateContext.tsx @@ -186,7 +186,7 @@ export const ExtensionStateContextProvider: React.FC<{ children: React.ReactNode terminalCompressProgressBar: true, // Default to compress progress bar output historyPreviewCollapsed: false, // Initialize the new state (default to expanded) - // Context Summarization Defaults (Added) + // Context Synthesization Defaults (Added) enableContextSummarization: false, contextSummarizationTriggerThreshold: 80, contextSummarizationInitialStaticTurns: 5, diff --git a/webview-ui/src/context/__tests__/ExtensionStateContext.test.tsx b/webview-ui/src/context/__tests__/ExtensionStateContext.test.tsx index 7b180dcd297..d164a69221c 100644 --- a/webview-ui/src/context/__tests__/ExtensionStateContext.test.tsx +++ b/webview-ui/src/context/__tests__/ExtensionStateContext.test.tsx @@ -202,7 +202,7 @@ describe("mergeExtensionState", () => { showRooIgnoredFiles: true, renderContext: "sidebar", maxReadFileLine: 500, - // Context Summarization Defaults (Added for test) + // Context Synthesization Defaults (Added for test) enableContextSummarization: false, contextSummarizationTriggerThreshold: 80, contextSummarizationInitialStaticTurns: 5, diff --git a/webview-ui/src/i18n/locales/ca/settings.json b/webview-ui/src/i18n/locales/ca/settings.json index 0de1c2e8f09..77318fd5d4d 100644 --- a/webview-ui/src/i18n/locales/ca/settings.json +++ b/webview-ui/src/i18n/locales/ca/settings.json @@ -302,7 +302,7 @@ "lines": "línies", "always_full_read": "Llegeix sempre el fitxer sencer" }, - "summarization": { + "synthesization": { "enable": { "label": "Activar resum de context", "description": "Quan està activat, els torns de conversa més antics es resumiran en lloc de truncar-se quan s'acosti el límit de context. Això conserva més informació però implica costos addicionals de token per al resum." diff --git a/webview-ui/src/i18n/locales/de/settings.json b/webview-ui/src/i18n/locales/de/settings.json index 9850f203a15..e0d17d94252 100644 --- a/webview-ui/src/i18n/locales/de/settings.json +++ b/webview-ui/src/i18n/locales/de/settings.json @@ -302,7 +302,7 @@ "lines": "Zeilen", "always_full_read": "Immer die gesamte Datei lesen" }, - "summarization": { + "synthesization": { "enable": { "label": "Kontextzusammenfassung aktivieren", "description": "Wenn aktiviert, werden ältere Gesprächsrunden zusammengefasst statt abgeschnitten, wenn das Kontextlimit erreicht wird. Dies bewahrt mehr Informationen, verursacht aber zusätzliche Token-Kosten für die Zusammenfassung." diff --git a/webview-ui/src/i18n/locales/es/settings.json b/webview-ui/src/i18n/locales/es/settings.json index 7432f1ecc78..2498445dfed 100644 --- a/webview-ui/src/i18n/locales/es/settings.json +++ b/webview-ui/src/i18n/locales/es/settings.json @@ -302,7 +302,7 @@ "lines": "líneas", "always_full_read": "Siempre leer el archivo completo" }, - "summarization": { + "synthesization": { "enable": { "label": "Habilitar resumen de contexto", "description": "Cuando está habilitado, los turnos de conversación más antiguos se resumirán en lugar de truncarse cuando se acerque el límite de contexto. Esto conserva más información pero incurre en costos adicionales de token para el resumen." diff --git a/webview-ui/src/i18n/locales/fr/settings.json b/webview-ui/src/i18n/locales/fr/settings.json index c7b9adbd584..bdcbce329cf 100644 --- a/webview-ui/src/i18n/locales/fr/settings.json +++ b/webview-ui/src/i18n/locales/fr/settings.json @@ -302,7 +302,7 @@ "lines": "lignes", "always_full_read": "Toujours lire le fichier entier" }, - "summarization": { + "synthesization": { "enable": { "label": "Activer le résumé du contexte", "description": "Lorsque cette option est activée, les anciens tours de conversation seront résumés au lieu d'être tronqués à l'approche de la limite de contexte. Cela préserve plus d'informations mais entraîne des coûts de token supplémentaires pour le résumé." diff --git a/webview-ui/src/i18n/locales/hi/settings.json b/webview-ui/src/i18n/locales/hi/settings.json index 534f8bc8a56..648cd9f0013 100644 --- a/webview-ui/src/i18n/locales/hi/settings.json +++ b/webview-ui/src/i18n/locales/hi/settings.json @@ -302,7 +302,7 @@ "lines": "पंक्तियाँ", "always_full_read": "हमेशा पूरी फ़ाइल पढ़ें" }, - "summarization": { + "synthesization": { "enable": { "label": "संदर्भ सारांश सक्षम करें", "description": "सक्षम होने पर, संदर्भ सीमा के करीब पहुंचने पर पुरानी बातचीत के दौरों को छोटा करने के बजाय सारांशित किया जाएगा। यह अधिक जानकारी सुरक्षित रखता है लेकिन सारांश के लिए अतिरिक्त टोकन लागत लगती है।" diff --git a/webview-ui/src/i18n/locales/it/settings.json b/webview-ui/src/i18n/locales/it/settings.json index 72f6144b4b8..7fd66c24b63 100644 --- a/webview-ui/src/i18n/locales/it/settings.json +++ b/webview-ui/src/i18n/locales/it/settings.json @@ -302,7 +302,7 @@ "lines": "righe", "always_full_read": "Leggi sempre l'intero file" }, - "summarization": { + "synthesization": { "enable": { "label": "Abilita riassunto del contesto", "description": "Se abilitato, i turni di conversazione più vecchi verranno riassunti invece di essere troncati all'avvicinarsi del limite di contesto. Ciò preserva più informazioni ma comporta costi aggiuntivi di token per il riassunto." diff --git a/webview-ui/src/i18n/locales/ja/settings.json b/webview-ui/src/i18n/locales/ja/settings.json index 91f46acde1c..996f634a832 100644 --- a/webview-ui/src/i18n/locales/ja/settings.json +++ b/webview-ui/src/i18n/locales/ja/settings.json @@ -302,7 +302,7 @@ "lines": "行", "always_full_read": "常にファイル全体を読み込む" }, - "summarization": { + "synthesization": { "enable": { "label": "コンテキスト要約を有効にする", "description": "有効にすると、コンテキスト制限に近づいたときに古い会話のターンが切り捨てられる代わりに要約されます。これにより、より多くの情報が保持されますが、要約に追加のトークンコストが発生します。" diff --git a/webview-ui/src/i18n/locales/ko/settings.json b/webview-ui/src/i18n/locales/ko/settings.json index 5ed817103eb..7ef2ec780fb 100644 --- a/webview-ui/src/i18n/locales/ko/settings.json +++ b/webview-ui/src/i18n/locales/ko/settings.json @@ -302,7 +302,7 @@ "lines": "줄", "always_full_read": "항상 전체 파일 읽기" }, - "summarization": { + "synthesization": { "enable": { "label": "컨텍스트 요약 활성화", "description": "활성화하면 컨텍스트 제한에 가까워질 때 이전 대화 턴이 잘리는 대신 요약됩니다. 이렇게 하면 더 많은 정보가 보존되지만 요약에 추가 토큰 비용이 발생합니다." diff --git a/webview-ui/src/i18n/locales/pl/settings.json b/webview-ui/src/i18n/locales/pl/settings.json index abead2e2a9b..adb58274dee 100644 --- a/webview-ui/src/i18n/locales/pl/settings.json +++ b/webview-ui/src/i18n/locales/pl/settings.json @@ -302,7 +302,7 @@ "lines": "linii", "always_full_read": "Zawsze czytaj cały plik" }, - "summarization": { + "synthesization": { "enable": { "label": "Włącz podsumowanie kontekstu", "description": "Gdy włączone, starsze tury konwersacji będą podsumowywane zamiast obcinane, gdy zbliża się limit kontekstu. Zachowuje to więcej informacji, ale wiąże się z dodatkowymi kosztami tokenów za podsumowanie." diff --git a/webview-ui/src/i18n/locales/pt-BR/settings.json b/webview-ui/src/i18n/locales/pt-BR/settings.json index 06eee8d3300..3578d268efe 100644 --- a/webview-ui/src/i18n/locales/pt-BR/settings.json +++ b/webview-ui/src/i18n/locales/pt-BR/settings.json @@ -302,7 +302,7 @@ "lines": "linhas", "always_full_read": "Sempre ler o arquivo inteiro" }, - "summarization": { + "synthesization": { "enable": { "label": "Habilitar resumo de contexto", "description": "Quando habilitado, os turnos de conversa mais antigos serão resumidos em vez de truncados quando o limite de contexto for atingido. Isso preserva mais informações, mas incorre em custos adicionais de token para o resumo." diff --git a/webview-ui/src/i18n/locales/ru/settings.json b/webview-ui/src/i18n/locales/ru/settings.json index 6cadb725fac..b6564b33f0a 100644 --- a/webview-ui/src/i18n/locales/ru/settings.json +++ b/webview-ui/src/i18n/locales/ru/settings.json @@ -302,7 +302,7 @@ "lines": "строк", "always_full_read": "Всегда читать весь файл" }, - "summarization": { + "synthesization": { "enable": { "label": "Включить суммирование контекста", "description": "Если включено, старые ходы беседы будут суммироваться, а не обрезаться при приближении к лимиту контекста. Это сохраняет больше информации, но влечет за собой дополнительные затраты токенов на суммирование." diff --git a/webview-ui/src/i18n/locales/tr/settings.json b/webview-ui/src/i18n/locales/tr/settings.json index d8c8409c973..2f90915a38d 100644 --- a/webview-ui/src/i18n/locales/tr/settings.json +++ b/webview-ui/src/i18n/locales/tr/settings.json @@ -302,7 +302,7 @@ "lines": "satır", "always_full_read": "Her zaman tüm dosyayı oku" }, - "summarization": { + "synthesization": { "enable": { "label": "Bağlam özetlemeyi etkinleştir", "description": "Etkinleştirildiğinde, bağlam sınırına yaklaşıldığında eski konuşma turları kesilmek yerine özetlenir. Bu, daha fazla bilgi korur ancak özetleme için ek token maliyetlerine neden olur." diff --git a/webview-ui/src/i18n/locales/vi/settings.json b/webview-ui/src/i18n/locales/vi/settings.json index 8a193745c53..a3ddeb47ccf 100644 --- a/webview-ui/src/i18n/locales/vi/settings.json +++ b/webview-ui/src/i18n/locales/vi/settings.json @@ -302,7 +302,7 @@ "lines": "dòng", "always_full_read": "Luôn đọc toàn bộ tệp" }, - "summarization": { + "synthesization": { "enable": { "label": "Bật tóm tắt ngữ cảnh", "description": "Khi được bật, các lượt trò chuyện cũ hơn sẽ được tóm tắt thay vì bị cắt bớt khi đạt đến giới hạn ngữ cảnh. Điều này bảo toàn nhiều thông tin hơn nhưng phải trả thêm chi phí token cho việc tóm tắt." diff --git a/webview-ui/src/i18n/locales/zh-CN/settings.json b/webview-ui/src/i18n/locales/zh-CN/settings.json index 89f2c9f96c6..41bc755cab6 100644 --- a/webview-ui/src/i18n/locales/zh-CN/settings.json +++ b/webview-ui/src/i18n/locales/zh-CN/settings.json @@ -302,7 +302,7 @@ "lines": "行", "always_full_read": "始终读取整个文件" }, - "summarization": { + "synthesization": { "enable": { "label": "启用上下文摘要", "description": "启用后,当接近上下文限制时,较早的对话轮次将被摘要而不是截断。这可以保留更多信息,但会产生额外的 token 费用用于摘要。" diff --git a/webview-ui/src/i18n/locales/zh-TW/settings.json b/webview-ui/src/i18n/locales/zh-TW/settings.json index cf0388e0ccf..a3f523f8c04 100644 --- a/webview-ui/src/i18n/locales/zh-TW/settings.json +++ b/webview-ui/src/i18n/locales/zh-TW/settings.json @@ -302,7 +302,7 @@ "lines": "行", "always_full_read": "始終讀取整個檔案" }, - "summarization": { + "synthesization": { "enable": { "label": "啟用內容摘要", "description": "啟用後,當接近內容限制時,較早的對話輪次將被摘要而不是截斷。這可以保留更多資訊,但會產生額外的 token 費用用於摘要。" From 6819e0db6d19179a855859587d3adcddc99376f2 Mon Sep 17 00:00:00 2001 From: Shariq Riaz Date: Tue, 29 Apr 2025 14:24:29 +0500 Subject: [PATCH 13/16] i18n: Add missing backend context synthesization translations --- src/i18n/locales/ca/common.json | 7 +++++-- src/i18n/locales/de/common.json | 7 +++++-- src/i18n/locales/es/common.json | 7 +++++-- src/i18n/locales/fr/common.json | 7 +++++-- src/i18n/locales/hi/common.json | 7 +++++-- src/i18n/locales/it/common.json | 7 +++++-- src/i18n/locales/ja/common.json | 7 +++++-- src/i18n/locales/ko/common.json | 7 +++++-- src/i18n/locales/pl/common.json | 7 +++++-- src/i18n/locales/pt-BR/common.json | 7 +++++-- src/i18n/locales/ru/common.json | 7 +++++-- src/i18n/locales/tr/common.json | 7 +++++-- src/i18n/locales/vi/common.json | 7 +++++-- src/i18n/locales/zh-CN/common.json | 7 +++++-- src/i18n/locales/zh-TW/common.json | 7 +++++-- 15 files changed, 75 insertions(+), 30 deletions(-) diff --git a/src/i18n/locales/ca/common.json b/src/i18n/locales/ca/common.json index 633b90ec3d0..94015ac78c1 100644 --- a/src/i18n/locales/ca/common.json +++ b/src/i18n/locales/ca/common.json @@ -55,7 +55,8 @@ "failed_delete_repo": "Ha fallat l'eliminació del repositori o branca associada: {{error}}", "failed_remove_directory": "Ha fallat l'eliminació del directori de tasques: {{error}}", "custom_storage_path_unusable": "La ruta d'emmagatzematge personalitzada \"{{path}}\" no és utilitzable, s'utilitzarà la ruta predeterminada", - "cannot_access_path": "No es pot accedir a la ruta {{path}}: {{error}}" + "cannot_access_path": "No es pot accedir a la ruta {{path}}: {{error}}", + "synthesization_failed": "Ha fallat la síntesi del context de la conversa." }, "warnings": { "no_terminal_content": "No s'ha seleccionat contingut de terminal", @@ -71,7 +72,9 @@ "mcp_server_not_found": "Servidor \"{{serverName}}\" no trobat a la configuració", "custom_storage_path_set": "Ruta d'emmagatzematge personalitzada establerta: {{path}}", "default_storage_path": "S'ha reprès l'ús de la ruta d'emmagatzematge predeterminada", - "settings_imported": "Configuració importada correctament." + "settings_imported": "Configuració importada correctament.", + "synthesizing_context": "Sintetitzant el context de la conversa...", + "synthesization_complete": "Síntesi del context de la conversa completada." }, "answers": { "yes": "Sí", diff --git a/src/i18n/locales/de/common.json b/src/i18n/locales/de/common.json index ceda64bad71..5f4ec30596a 100644 --- a/src/i18n/locales/de/common.json +++ b/src/i18n/locales/de/common.json @@ -51,7 +51,8 @@ "failed_delete_repo": "Fehler beim Löschen des zugehörigen Shadow-Repositorys oder -Zweigs: {{error}}", "failed_remove_directory": "Fehler beim Entfernen des Aufgabenverzeichnisses: {{error}}", "custom_storage_path_unusable": "Benutzerdefinierter Speicherpfad \"{{path}}\" ist nicht verwendbar, Standardpfad wird verwendet", - "cannot_access_path": "Zugriff auf Pfad {{path}} nicht möglich: {{error}}" + "cannot_access_path": "Zugriff auf Pfad {{path}} nicht möglich: {{error}}", + "synthesization_failed": "Synthese des Gesprächskontexts fehlgeschlagen." }, "warnings": { "no_terminal_content": "Kein Terminal-Inhalt ausgewählt", @@ -67,7 +68,9 @@ "mcp_server_not_found": "Server \"{{serverName}}\" nicht in der Konfiguration gefunden", "custom_storage_path_set": "Benutzerdefinierter Speicherpfad festgelegt: {{path}}", "default_storage_path": "Auf Standardspeicherpfad zurückgesetzt", - "settings_imported": "Einstellungen erfolgreich importiert." + "settings_imported": "Einstellungen erfolgreich importiert.", + "synthesizing_context": "Gesprächskontext wird synthetisiert...", + "synthesization_complete": "Synthese des Gesprächskontexts abgeschlossen." }, "answers": { "yes": "Ja", diff --git a/src/i18n/locales/es/common.json b/src/i18n/locales/es/common.json index 2bfb43055a8..d0f58a748a3 100644 --- a/src/i18n/locales/es/common.json +++ b/src/i18n/locales/es/common.json @@ -51,7 +51,8 @@ "failed_delete_repo": "Error al eliminar el repositorio o rama asociada: {{error}}", "failed_remove_directory": "Error al eliminar el directorio de tareas: {{error}}", "custom_storage_path_unusable": "La ruta de almacenamiento personalizada \"{{path}}\" no es utilizable, se usará la ruta predeterminada", - "cannot_access_path": "No se puede acceder a la ruta {{path}}: {{error}}" + "cannot_access_path": "No se puede acceder a la ruta {{path}}: {{error}}", + "synthesization_failed": "Error al sintetizar el contexto de la conversación." }, "warnings": { "no_terminal_content": "No hay contenido de terminal seleccionado", @@ -67,7 +68,9 @@ "mcp_server_not_found": "Servidor \"{{serverName}}\" no encontrado en la configuración", "custom_storage_path_set": "Ruta de almacenamiento personalizada establecida: {{path}}", "default_storage_path": "Se ha vuelto a usar la ruta de almacenamiento predeterminada", - "settings_imported": "Configuración importada correctamente." + "settings_imported": "Configuración importada correctamente.", + "synthesizing_context": "Sintetizando el contexto de la conversación...", + "synthesization_complete": "Síntesis del contexto de la conversación completada." }, "answers": { "yes": "Sí", diff --git a/src/i18n/locales/fr/common.json b/src/i18n/locales/fr/common.json index 7399432c6a8..00cb6f6ecc2 100644 --- a/src/i18n/locales/fr/common.json +++ b/src/i18n/locales/fr/common.json @@ -51,7 +51,8 @@ "failed_delete_repo": "Échec de la suppression du repo fantôme ou de la branche associée : {{error}}", "failed_remove_directory": "Échec de la suppression du répertoire de tâches : {{error}}", "custom_storage_path_unusable": "Le chemin de stockage personnalisé \"{{path}}\" est inutilisable, le chemin par défaut sera utilisé", - "cannot_access_path": "Impossible d'accéder au chemin {{path}} : {{error}}" + "cannot_access_path": "Impossible d'accéder au chemin {{path}} : {{error}}", + "synthesization_failed": "Échec de la synthèse du contexte de la conversation." }, "warnings": { "no_terminal_content": "Aucun contenu de terminal sélectionné", @@ -67,7 +68,9 @@ "mcp_server_not_found": "Serveur \"{{serverName}}\" introuvable dans la configuration", "custom_storage_path_set": "Chemin de stockage personnalisé défini : {{path}}", "default_storage_path": "Retour au chemin de stockage par défaut", - "settings_imported": "Paramètres importés avec succès." + "settings_imported": "Paramètres importés avec succès.", + "synthesizing_context": "Synthèse du contexte de la conversation en cours...", + "synthesization_complete": "Synthèse du contexte de la conversation terminée." }, "answers": { "yes": "Oui", diff --git a/src/i18n/locales/hi/common.json b/src/i18n/locales/hi/common.json index bf5421eff8f..700ab9ecc4a 100644 --- a/src/i18n/locales/hi/common.json +++ b/src/i18n/locales/hi/common.json @@ -51,7 +51,8 @@ "failed_delete_repo": "संबंधित शैडो रिपॉजिटरी या ब्रांच हटाने में विफल: {{error}}", "failed_remove_directory": "टास्क डायरेक्टरी हटाने में विफल: {{error}}", "custom_storage_path_unusable": "कस्टम स्टोरेज पाथ \"{{path}}\" उपयोग योग्य नहीं है, डिफ़ॉल्ट पाथ का उपयोग किया जाएगा", - "cannot_access_path": "पाथ {{path}} तक पहुंच नहीं पा रहे हैं: {{error}}" + "cannot_access_path": "पाथ {{path}} तक पहुंच नहीं पा रहे हैं: {{error}}", + "synthesization_failed": "बातचीत के संदर्भ को संश्लेषित करने में विफल।" }, "warnings": { "no_terminal_content": "कोई टर्मिनल सामग्री चयनित नहीं", @@ -67,7 +68,9 @@ "mcp_server_not_found": "सर्वर \"{{serverName}}\" कॉन्फ़िगरेशन में नहीं मिला", "custom_storage_path_set": "कस्टम स्टोरेज पाथ सेट किया गया: {{path}}", "default_storage_path": "डिफ़ॉल्ट स्टोरेज पाथ का उपयोग पुनः शुरू किया गया", - "settings_imported": "सेटिंग्स सफलतापूर्वक इम्पोर्ट की गईं।" + "settings_imported": "सेटिंग्स सफलतापूर्वक इम्पोर्ट की गईं।", + "synthesizing_context": "बातचीत के संदर्भ को संश्लेषित किया जा रहा है...", + "synthesization_complete": "बातचीत के संदर्भ का संश्लेषण पूरा हुआ।" }, "answers": { "yes": "हां", diff --git a/src/i18n/locales/it/common.json b/src/i18n/locales/it/common.json index 69e2c2123f1..99eb4ce654f 100644 --- a/src/i18n/locales/it/common.json +++ b/src/i18n/locales/it/common.json @@ -51,7 +51,8 @@ "failed_delete_repo": "Impossibile eliminare il repository o il ramo associato: {{error}}", "failed_remove_directory": "Impossibile rimuovere la directory delle attività: {{error}}", "custom_storage_path_unusable": "Il percorso di archiviazione personalizzato \"{{path}}\" non è utilizzabile, verrà utilizzato il percorso predefinito", - "cannot_access_path": "Impossibile accedere al percorso {{path}}: {{error}}" + "cannot_access_path": "Impossibile accedere al percorso {{path}}: {{error}}", + "synthesization_failed": "Sintesi del contesto della conversazione fallita." }, "warnings": { "no_terminal_content": "Nessun contenuto del terminale selezionato", @@ -67,7 +68,9 @@ "mcp_server_not_found": "Server \"{{serverName}}\" non trovato nella configurazione", "custom_storage_path_set": "Percorso di archiviazione personalizzato impostato: {{path}}", "default_storage_path": "Tornato al percorso di archiviazione predefinito", - "settings_imported": "Impostazioni importate con successo." + "settings_imported": "Impostazioni importate con successo.", + "synthesizing_context": "Sintesi del contesto della conversazione in corso...", + "synthesization_complete": "Sintesi del contesto della conversazione completata." }, "answers": { "yes": "Sì", diff --git a/src/i18n/locales/ja/common.json b/src/i18n/locales/ja/common.json index 6f40c8e03d5..6606f6ac7cb 100644 --- a/src/i18n/locales/ja/common.json +++ b/src/i18n/locales/ja/common.json @@ -51,7 +51,8 @@ "failed_delete_repo": "関連するシャドウリポジトリまたはブランチの削除に失敗しました:{{error}}", "failed_remove_directory": "タスクディレクトリの削除に失敗しました:{{error}}", "custom_storage_path_unusable": "カスタムストレージパス \"{{path}}\" が使用できないため、デフォルトパスを使用します", - "cannot_access_path": "パス {{path}} にアクセスできません:{{error}}" + "cannot_access_path": "パス {{path}} にアクセスできません:{{error}}", + "synthesization_failed": "会話コンテキストの合成に失敗しました。" }, "warnings": { "no_terminal_content": "選択されたターミナルコンテンツがありません", @@ -67,7 +68,9 @@ "mcp_server_not_found": "サーバー\"{{serverName}}\"が設定内に見つかりません", "custom_storage_path_set": "カスタムストレージパスが設定されました:{{path}}", "default_storage_path": "デフォルトのストレージパスに戻りました", - "settings_imported": "設定が正常にインポートされました。" + "settings_imported": "設定が正常にインポートされました。", + "synthesizing_context": "会話コンテキストを合成中...", + "synthesization_complete": "会話コンテキストの合成が完了しました。" }, "answers": { "yes": "はい", diff --git a/src/i18n/locales/ko/common.json b/src/i18n/locales/ko/common.json index 9026315da2e..5ef6a11cd61 100644 --- a/src/i18n/locales/ko/common.json +++ b/src/i18n/locales/ko/common.json @@ -51,7 +51,8 @@ "failed_delete_repo": "관련 shadow 저장소 또는 브랜치 삭제 실패: {{error}}", "failed_remove_directory": "작업 디렉토리 제거 실패: {{error}}", "custom_storage_path_unusable": "사용자 지정 저장 경로 \"{{path}}\"를 사용할 수 없어 기본 경로를 사용합니다", - "cannot_access_path": "경로 {{path}}에 접근할 수 없습니다: {{error}}" + "cannot_access_path": "경로 {{path}}에 접근할 수 없습니다: {{error}}", + "synthesization_failed": "대화 컨텍스트 합성에 실패했습니다." }, "warnings": { "no_terminal_content": "선택된 터미널 내용이 없습니다", @@ -67,7 +68,9 @@ "mcp_server_not_found": "구성에서 서버 \"{{serverName}}\"을(를) 찾을 수 없습니다", "custom_storage_path_set": "사용자 지정 저장 경로 설정됨: {{path}}", "default_storage_path": "기본 저장 경로로 되돌아갔습니다", - "settings_imported": "설정이 성공적으로 가져와졌습니다." + "settings_imported": "설정이 성공적으로 가져와졌습니다.", + "synthesizing_context": "대화 컨텍스트 합성 중...", + "synthesization_complete": "대화 컨텍스트 합성이 완료되었습니다." }, "answers": { "yes": "예", diff --git a/src/i18n/locales/pl/common.json b/src/i18n/locales/pl/common.json index 49f51cefff1..6e483d5a7b4 100644 --- a/src/i18n/locales/pl/common.json +++ b/src/i18n/locales/pl/common.json @@ -51,7 +51,8 @@ "failed_delete_repo": "Nie udało się usunąć powiązanego repozytorium lub gałęzi pomocniczej: {{error}}", "failed_remove_directory": "Nie udało się usunąć katalogu zadania: {{error}}", "custom_storage_path_unusable": "Niestandardowa ścieżka przechowywania \"{{path}}\" nie jest użyteczna, zostanie użyta domyślna ścieżka", - "cannot_access_path": "Nie można uzyskać dostępu do ścieżki {{path}}: {{error}}" + "cannot_access_path": "Nie można uzyskać dostępu do ścieżki {{path}}: {{error}}", + "synthesization_failed": "Nie udało się zsyntetyzować kontekstu rozmowy." }, "warnings": { "no_terminal_content": "Nie wybrano zawartości terminala", @@ -67,7 +68,9 @@ "mcp_server_not_found": "Serwer \"{{serverName}}\" nie znaleziony w konfiguracji", "custom_storage_path_set": "Ustawiono niestandardową ścieżkę przechowywania: {{path}}", "default_storage_path": "Wznowiono używanie domyślnej ścieżki przechowywania", - "settings_imported": "Ustawienia zaimportowane pomyślnie." + "settings_imported": "Ustawienia zaimportowane pomyślnie.", + "synthesizing_context": "Syntetyzowanie kontekstu rozmowy...", + "synthesization_complete": "Synteza kontekstu rozmowy zakończona." }, "answers": { "yes": "Tak", diff --git a/src/i18n/locales/pt-BR/common.json b/src/i18n/locales/pt-BR/common.json index 80112a91ab8..7ee2eb3c8f0 100644 --- a/src/i18n/locales/pt-BR/common.json +++ b/src/i18n/locales/pt-BR/common.json @@ -55,7 +55,8 @@ "failed_delete_repo": "Falha ao excluir o repositório ou ramificação associada: {{error}}", "failed_remove_directory": "Falha ao remover o diretório de tarefas: {{error}}", "custom_storage_path_unusable": "O caminho de armazenamento personalizado \"{{path}}\" não pode ser usado, será usado o caminho padrão", - "cannot_access_path": "Não é possível acessar o caminho {{path}}: {{error}}" + "cannot_access_path": "Não é possível acessar o caminho {{path}}: {{error}}", + "synthesization_failed": "Falha ao sintetizar o contexto da conversa." }, "warnings": { "no_terminal_content": "Nenhum conteúdo do terminal selecionado", @@ -71,7 +72,9 @@ "mcp_server_not_found": "Servidor \"{{serverName}}\" não encontrado na configuração", "custom_storage_path_set": "Caminho de armazenamento personalizado definido: {{path}}", "default_storage_path": "Retornado ao caminho de armazenamento padrão", - "settings_imported": "Configurações importadas com sucesso." + "settings_imported": "Configurações importadas com sucesso.", + "synthesizing_context": "Sintetizando o contexto da conversa...", + "synthesization_complete": "Síntese do contexto da conversa concluída." }, "answers": { "yes": "Sim", diff --git a/src/i18n/locales/ru/common.json b/src/i18n/locales/ru/common.json index 80829e138ce..37bcf56f4da 100644 --- a/src/i18n/locales/ru/common.json +++ b/src/i18n/locales/ru/common.json @@ -51,7 +51,8 @@ "failed_remove_directory": "Не удалось удалить директорию задачи: {{error}}", "custom_storage_path_unusable": "Пользовательский путь хранения \"{{path}}\" непригоден, будет использован путь по умолчанию", "cannot_access_path": "Невозможно получить доступ к пути {{path}}: {{error}}", - "failed_update_project_mcp": "Не удалось обновить серверы проекта MCP" + "failed_update_project_mcp": "Не удалось обновить серверы проекта MCP", + "synthesization_failed": "Не удалось синтезировать контекст разговора." }, "warnings": { "no_terminal_content": "Не выбрано содержимое терминала", @@ -67,7 +68,9 @@ "mcp_server_not_found": "Сервер \"{{serverName}}\" не найден в конфигурации", "custom_storage_path_set": "Установлен пользовательский путь хранения: {{path}}", "default_storage_path": "Возвращено использование пути хранения по умолчанию", - "settings_imported": "Настройки успешно импортированы." + "settings_imported": "Настройки успешно импортированы.", + "synthesizing_context": "Синтез контекста разговора...", + "synthesization_complete": "Синтез контекста разговора завершен." }, "answers": { "yes": "Да", diff --git a/src/i18n/locales/tr/common.json b/src/i18n/locales/tr/common.json index 61b8e12fb5a..6de563bd22f 100644 --- a/src/i18n/locales/tr/common.json +++ b/src/i18n/locales/tr/common.json @@ -51,7 +51,8 @@ "failed_delete_repo": "İlişkili gölge depo veya dal silinemedi: {{error}}", "failed_remove_directory": "Görev dizini kaldırılamadı: {{error}}", "custom_storage_path_unusable": "Özel depolama yolu \"{{path}}\" kullanılamıyor, varsayılan yol kullanılacak", - "cannot_access_path": "{{path}} yoluna erişilemiyor: {{error}}" + "cannot_access_path": "{{path}} yoluna erişilemiyor: {{error}}", + "synthesization_failed": "Konuşma bağlamı sentezlenemedi." }, "warnings": { "no_terminal_content": "Seçili terminal içeriği yok", @@ -67,7 +68,9 @@ "mcp_server_not_found": "Yapılandırmada \"{{serverName}}\" sunucusu bulunamadı", "custom_storage_path_set": "Özel depolama yolu ayarlandı: {{path}}", "default_storage_path": "Varsayılan depolama yoluna geri dönüldü", - "settings_imported": "Ayarlar başarıyla içe aktarıldı." + "settings_imported": "Ayarlar başarıyla içe aktarıldı.", + "synthesizing_context": "Konuşma bağlamı sentezleniyor...", + "synthesization_complete": "Konuşma bağlamı sentezi tamamlandı." }, "answers": { "yes": "Evet", diff --git a/src/i18n/locales/vi/common.json b/src/i18n/locales/vi/common.json index 8945e9e098e..7e8151e96df 100644 --- a/src/i18n/locales/vi/common.json +++ b/src/i18n/locales/vi/common.json @@ -51,7 +51,8 @@ "failed_delete_repo": "Không thể xóa kho lưu trữ hoặc nhánh liên quan: {{error}}", "failed_remove_directory": "Không thể xóa thư mục nhiệm vụ: {{error}}", "custom_storage_path_unusable": "Đường dẫn lưu trữ tùy chỉnh \"{{path}}\" không thể sử dụng được, sẽ sử dụng đường dẫn mặc định", - "cannot_access_path": "Không thể truy cập đường dẫn {{path}}: {{error}}" + "cannot_access_path": "Không thể truy cập đường dẫn {{path}}: {{error}}", + "synthesization_failed": "Không thể tổng hợp ngữ cảnh cuộc trò chuyện." }, "warnings": { "no_terminal_content": "Không có nội dung terminal được chọn", @@ -67,7 +68,9 @@ "mcp_server_not_found": "Không tìm thấy máy chủ \"{{serverName}}\" trong cấu hình", "custom_storage_path_set": "Đã thiết lập đường dẫn lưu trữ tùy chỉnh: {{path}}", "default_storage_path": "Đã quay lại sử dụng đường dẫn lưu trữ mặc định", - "settings_imported": "Cài đặt đã được nhập thành công." + "settings_imported": "Cài đặt đã được nhập thành công.", + "synthesizing_context": "Đang tổng hợp ngữ cảnh cuộc trò chuyện...", + "synthesization_complete": "Đã hoàn tất tổng hợp ngữ cảnh cuộc trò chuyện." }, "answers": { "yes": "Có", diff --git a/src/i18n/locales/zh-CN/common.json b/src/i18n/locales/zh-CN/common.json index 2fc49c9b378..dcd2cdfcea2 100644 --- a/src/i18n/locales/zh-CN/common.json +++ b/src/i18n/locales/zh-CN/common.json @@ -51,7 +51,8 @@ "failed_delete_repo": "删除关联的影子仓库或分支失败:{{error}}", "failed_remove_directory": "删除任务目录失败:{{error}}", "custom_storage_path_unusable": "自定义存储路径 \"{{path}}\" 不可用,将使用默认路径", - "cannot_access_path": "无法访问路径 {{path}}:{{error}}" + "cannot_access_path": "无法访问路径 {{path}}:{{error}}", + "synthesization_failed": "合成对话上下文失败。" }, "warnings": { "no_terminal_content": "没有选择终端内容", @@ -67,7 +68,9 @@ "mcp_server_not_found": "在配置中未找到服务器\"{{serverName}}\"", "custom_storage_path_set": "自定义存储路径已设置:{{path}}", "default_storage_path": "已恢复使用默认存储路径", - "settings_imported": "设置已成功导入。" + "settings_imported": "设置已成功导入。", + "synthesizing_context": "正在合成对话上下文...", + "synthesization_complete": "对话上下文合成完成。" }, "answers": { "yes": "是", diff --git a/src/i18n/locales/zh-TW/common.json b/src/i18n/locales/zh-TW/common.json index a51cfa0e9a4..5104ff5efe9 100644 --- a/src/i18n/locales/zh-TW/common.json +++ b/src/i18n/locales/zh-TW/common.json @@ -51,7 +51,8 @@ "failed_delete_repo": "刪除關聯的影子倉庫或分支失敗:{{error}}", "failed_remove_directory": "刪除工作目錄失敗:{{error}}", "custom_storage_path_unusable": "自訂儲存路徑 \"{{path}}\" 無法使用,將使用預設路徑", - "cannot_access_path": "無法存取路徑 {{path}}:{{error}}" + "cannot_access_path": "無法存取路徑 {{path}}:{{error}}", + "synthesization_failed": "合成對話上下文失敗。" }, "warnings": { "no_terminal_content": "沒有選擇終端機內容", @@ -67,7 +68,9 @@ "mcp_server_not_found": "在設定中沒有找到伺服器\"{{serverName}}\"", "custom_storage_path_set": "自訂儲存路徑已設定:{{path}}", "default_storage_path": "已恢復使用預設儲存路徑", - "settings_imported": "設定已成功匯入。" + "settings_imported": "設定已成功匯入。", + "synthesizing_context": "正在合成對話上下文...", + "synthesization_complete": "對話上下文合成完成。" }, "answers": { "yes": "是", From 7448edbe36552800946a66847d8972735b898327 Mon Sep 17 00:00:00 2001 From: Shariq Riaz Date: Tue, 29 Apr 2025 14:36:14 +0500 Subject: [PATCH 14/16] fix(test): Update context mention and settings tests --- .../settings/__tests__/ContextManagementSettings.test.tsx | 8 ++++---- webview-ui/src/utils/__tests__/context-mentions.test.ts | 3 ++- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/webview-ui/src/components/settings/__tests__/ContextManagementSettings.test.tsx b/webview-ui/src/components/settings/__tests__/ContextManagementSettings.test.tsx index 5ef7b5cd6d9..0f28a94698a 100644 --- a/webview-ui/src/components/settings/__tests__/ContextManagementSettings.test.tsx +++ b/webview-ui/src/components/settings/__tests__/ContextManagementSettings.test.tsx @@ -115,27 +115,27 @@ describe("ContextManagementSettings", () => { render() const checkbox = screen.getByTestId("enable-context-synthesization-checkbox") fireEvent.click(checkbox) - expect(defaultProps.setEnableContextSummarization).toHaveBeenCalledWith(true) + expect(defaultProps.setCachedStateField).toHaveBeenCalledWith("enableContextSummarization", true) }) it("updates synthesization trigger threshold", () => { render() // Enable first const input = screen.getByTestId("context-synthesization-trigger-threshold-input") fireEvent.change(input, { target: { value: "95" } }) - expect(defaultProps.setContextSummarizationTriggerThreshold).toHaveBeenCalledWith(95) + expect(defaultProps.setCachedStateField).toHaveBeenCalledWith("contextSummarizationTriggerThreshold", 95) }) it("updates initial turns to keep", () => { render() // Enable first const input = screen.getByTestId("context-synthesization-initial-turns-input") fireEvent.change(input, { target: { value: "3" } }) - expect(defaultProps.setContextSummarizationInitialStaticTurns).toHaveBeenCalledWith(3) + expect(defaultProps.setCachedStateField).toHaveBeenCalledWith("contextSummarizationInitialStaticTurns", 3) }) it("updates recent turns to keep", () => { render() // Enable first const input = screen.getByTestId("context-synthesization-recent-turns-input") fireEvent.change(input, { target: { value: "12" } }) - expect(defaultProps.setContextSummarizationRecentTurns).toHaveBeenCalledWith(12) + expect(defaultProps.setCachedStateField).toHaveBeenCalledWith("contextSummarizationRecentTurns", 12) }) }) diff --git a/webview-ui/src/utils/__tests__/context-mentions.test.ts b/webview-ui/src/utils/__tests__/context-mentions.test.ts index d28f2de6402..92a6c1e7138 100644 --- a/webview-ui/src/utils/__tests__/context-mentions.test.ts +++ b/webview-ui/src/utils/__tests__/context-mentions.test.ts @@ -91,8 +91,9 @@ describe("getContextMenuOptions", () => { it("should return all option types for empty query", () => { const result = getContextMenuOptions("", "", null, []) - expect(result).toHaveLength(6) + expect(result).toHaveLength(7) expect(result.map((item) => item.type)).toEqual([ + ContextMenuOptionType.Synthesize, ContextMenuOptionType.Problems, ContextMenuOptionType.Terminal, ContextMenuOptionType.URL, From 2a7486788c5a3b3ea71f934f49535e2e474226d4 Mon Sep 17 00:00:00 2001 From: Shariq Riaz Date: Tue, 6 May 2025 03:53:55 +0500 Subject: [PATCH 15/16] fix: Allow manual synthesize trigger independent of auto-synthesize setting --- src/core/Cline.ts | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/src/core/Cline.ts b/src/core/Cline.ts index 749a1d5df59..7a0c5edac47 100644 --- a/src/core/Cline.ts +++ b/src/core/Cline.ts @@ -1039,7 +1039,7 @@ export class Cline extends EventEmitter { const previousRequest = this.clineMessages[previousApiReqIndex]?.text if (!previousRequest) { - return + return } const { @@ -2700,12 +2700,6 @@ export class Cline extends EventEmitter { * @returns A promise that resolves when synthesization is complete. */ public async synthesizeConversationContext(_isManualTrigger: boolean = false): Promise { - // Skip if synthesizing is disabled - if (!this.enableContextSummarization) { - this.providerRef.deref()?.log("[Synthesizing] Context synthesizing is disabled.") - return - } - const initialMessagesToKeep = this.contextSummarizationInitialStaticTurns const recentMessagesToKeep = this.contextSummarizationRecentTurns From 2c3064d5a8052d0b02cc088cc1060fa47da84b51 Mon Sep 17 00:00:00 2001 From: Shariq Riaz Date: Tue, 6 May 2025 04:00:10 +0500 Subject: [PATCH 16/16] feat: Update default synthesizer turns to 3 initial, 3 recent --- src/core/Cline.ts | 4 ++-- src/core/webview/ClineProvider.ts | 8 ++++---- .../src/components/settings/ContextManagementSettings.tsx | 4 ++-- 3 files changed, 8 insertions(+), 8 deletions(-) diff --git a/src/core/Cline.ts b/src/core/Cline.ts index 7a0c5edac47..01b7482903a 100644 --- a/src/core/Cline.ts +++ b/src/core/Cline.ts @@ -232,8 +232,8 @@ export class Cline extends EventEmitter { // Context Synthesization Settings (Added) enableContextSummarization = false, contextSummarizationTriggerThreshold = 80, - contextSummarizationInitialStaticTurns = 5, - contextSummarizationRecentTurns = 10, + contextSummarizationInitialStaticTurns = 3, // Changed default from 5 to 3 + contextSummarizationRecentTurns = 3, // Changed default from 10 to 3 }: ClineOptions) { super() diff --git a/src/core/webview/ClineProvider.ts b/src/core/webview/ClineProvider.ts index 5d9c919be9b..78aba1af15b 100644 --- a/src/core/webview/ClineProvider.ts +++ b/src/core/webview/ClineProvider.ts @@ -1330,8 +1330,8 @@ export class ClineProvider extends EventEmitter implements // Context Synthesization Settings (Added) enableContextSummarization: enableContextSummarization ?? false, contextSummarizationTriggerThreshold: contextSummarizationTriggerThreshold ?? 80, - contextSummarizationInitialStaticTurns: contextSummarizationInitialStaticTurns ?? 5, - contextSummarizationRecentTurns: contextSummarizationRecentTurns ?? 10, + contextSummarizationInitialStaticTurns: contextSummarizationInitialStaticTurns ?? 3, // Changed default from 5 to 3 + contextSummarizationRecentTurns: contextSummarizationRecentTurns ?? 3, // Changed default from 10 to 3 } } @@ -1425,8 +1425,8 @@ export class ClineProvider extends EventEmitter implements // Context Synthesization Settings (Added) enableContextSummarization: stateValues.enableContextSummarization ?? false, contextSummarizationTriggerThreshold: stateValues.contextSummarizationTriggerThreshold ?? 80, - contextSummarizationInitialStaticTurns: stateValues.contextSummarizationInitialStaticTurns ?? 5, - contextSummarizationRecentTurns: stateValues.contextSummarizationRecentTurns ?? 10, + contextSummarizationInitialStaticTurns: stateValues.contextSummarizationInitialStaticTurns ?? 3, // Changed default from 5 to 3 + contextSummarizationRecentTurns: stateValues.contextSummarizationRecentTurns ?? 3, // Changed default from 10 to 3 } } diff --git a/webview-ui/src/components/settings/ContextManagementSettings.tsx b/webview-ui/src/components/settings/ContextManagementSettings.tsx index b45fc296604..fe94e3be750 100644 --- a/webview-ui/src/components/settings/ContextManagementSettings.tsx +++ b/webview-ui/src/components/settings/ContextManagementSettings.tsx @@ -205,7 +205,7 @@ export const ContextManagementSettings = ({ type="number" pattern="[0-9]*" className="w-24 bg-vscode-input-background text-vscode-input-foreground border border-vscode-input-border px-2 py-1 rounded text-right [appearance:textfield] [&::-webkit-outer-spin-button]:appearance-none [&::-webkit-inner-spin-button]:appearance-none disabled:opacity-50" - value={contextSummarizationInitialStaticTurns ?? 5} + value={contextSummarizationInitialStaticTurns ?? 3} // Changed default display from 5 to 3 min={0} onChange={(e) => { const newValue = parseInt(e.target.value, 10) @@ -235,7 +235,7 @@ export const ContextManagementSettings = ({ type="number" pattern="[0-9]*" className="w-24 bg-vscode-input-background text-vscode-input-foreground border border-vscode-input-border px-2 py-1 rounded text-right [appearance:textfield] [&::-webkit-outer-spin-button]:appearance-none [&::-webkit-inner-spin-button]:appearance-none disabled:opacity-50" - value={contextSummarizationRecentTurns ?? 10} + value={contextSummarizationRecentTurns ?? 3} // Changed default display from 10 to 3 min={0} onChange={(e) => { const newValue = parseInt(e.target.value, 10)