Skip to content
7 changes: 7 additions & 0 deletions packages/types/src/vscode-extension-host.ts
Original file line number Diff line number Diff line change
Expand Up @@ -106,11 +106,14 @@ export interface ExtensionMessage {
| "skills"
| "rules"
| "fileContent"
| "originalContent"
| "rooHistoryImportProgress"
| "themeFixtureProbeRequest"
text?: string
/** For fileContent: { path, content, error? } */
fileContent?: { path: string; content: string | null; error?: string }
/** For originalContent: the pre-edit file content of the requested tool message (null when unavailable) */
originalContentInfo?: { ts: number; messageId?: string; taskId?: string; content: string | null }
payload?: any // eslint-disable-line @typescript-eslint/no-explicit-any
checkpointWarning?: {
type: "WAIT_TIMEOUT" | "INIT_TIMEOUT"
Expand Down Expand Up @@ -499,6 +502,7 @@ export interface WebviewMessage {
| "saveImage"
| "openFile"
| "readFileContent"
| "readOriginalContent"
| "openMention"
| "cancelTask"
| "cancelAutoApproval"
Expand Down Expand Up @@ -697,6 +701,7 @@ export interface WebviewMessage {
ids?: string[]
terminalOperation?: "continue" | "abort"
messageTs?: number
messageId?: string
restoreCheckpoint?: boolean
historyPreviewCollapsed?: boolean
filters?: { type?: string; search?: string; tags?: string[] }
Expand Down Expand Up @@ -859,6 +864,8 @@ export interface ClineSayTool {
content?: string
// Original file content before first edit (for merged diff display in FileChangesPanel)
originalContent?: string
// Length of the originalContent the extension left out of the webview state (request it with readOriginalContent)
originalContentLength?: number
// Unified diff statistics computed by the extension
diffStats?: { added: number; removed: number }
regex?: string
Expand Down
3 changes: 2 additions & 1 deletion src/core/webview/ClineProvider.ts
Original file line number Diff line number Diff line change
Expand Up @@ -131,6 +131,7 @@ import {
import { readTaskMessages } from "../task-persistence/taskMessages"
import { getNonce } from "./getNonce"
import { getUri } from "./getUri"
import { omitOriginalContentFromExtensionMessage } from "./stripOriginalContent"
import { REQUESTY_BASE_URL } from "../../shared/utils/requesty"
import { validateAndFixToolResultIds } from "../task/validateToolResultIds"
import { PendingEditOperationStore, type PendingEditOperationInput } from "./PendingEditOperationStore"
Expand Down Expand Up @@ -1466,7 +1467,7 @@ export class ClineProvider
}

try {
await this.view?.webview.postMessage(message)
await this.view?.webview.postMessage(omitOriginalContentFromExtensionMessage(message))
} catch {
// View disposed, drop message silently
}
Expand Down
54 changes: 54 additions & 0 deletions src/core/webview/__tests__/ClineProvider.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -841,6 +841,60 @@ describe("ClineProvider", () => {
await expect(provider.postMessageToWebview(message)).resolves.toBeUndefined()
})

test("postMessageToWebview leaves originalContent of file-edit tool messages out of the state it posts", async () => {
await provider.resolveWebviewView(mockWebviewView)

const originalFile = "line of the original file\n".repeat(500)
const toolText = JSON.stringify({
tool: "appliedDiff",
path: "a.ts",
diff: "@@ d",
originalContent: originalFile,
})
// Only the field under test is populated; the rest of ExtensionState is irrelevant here.
const message = {
type: "state",
state: { clineMessages: [{ ts: 1, type: "ask", ask: "tool", text: toolText }] },
} as unknown as ExtensionMessage

await provider.postMessageToWebview(message)

const posted = mockPostMessage.mock.calls.at(-1)![0] as ExtensionMessage
const postedText = posted.state!.clineMessages![0]!.text!

expect(JSON.parse(postedText)).toEqual({
tool: "appliedDiff",
path: "a.ts",
diff: "@@ d",
originalContentLength: originalFile.length,
})
// the extension's own message (and so the persisted task) keeps the full content
expect(message.state!.clineMessages![0]!.text).toBe(toolText)
})

test("postMessageToWebview leaves originalContent out of messageUpdated", async () => {
await provider.resolveWebviewView(mockWebviewView)

const toolText = JSON.stringify({
tool: "appliedDiff",
path: "a.ts",
originalContent: "original file\n".repeat(100),
})

await provider.postMessageToWebview({
type: "messageUpdated",
clineMessage: { ts: 2, type: "ask", ask: "tool", text: toolText },
})

const posted = mockPostMessage.mock.calls.at(-1)![0] as ExtensionMessage

expect(JSON.parse(posted.clineMessage!.text!)).toEqual({
tool: "appliedDiff",
path: "a.ts",
originalContentLength: "original file\n".length * 100,
})
})

describe("theme fixture probes", () => {
const fixture = {
themeId: "Default Dark Modern",
Expand Down
Loading
Loading