diff --git a/src/core/assistant-message/__tests__/presentAssistantMessage-environment-filtering.spec.ts b/src/core/assistant-message/__tests__/presentAssistantMessage-environment-filtering.spec.ts
new file mode 100644
index 0000000000..a80a0e3211
--- /dev/null
+++ b/src/core/assistant-message/__tests__/presentAssistantMessage-environment-filtering.spec.ts
@@ -0,0 +1,313 @@
+// npx vitest src/core/assistant-message/__tests__/presentAssistantMessage-environment-filtering.spec.ts
+
+import { describe, it, expect, beforeEach, vi } from "vitest"
+import { presentAssistantMessage } from "../presentAssistantMessage"
+
+// Mock dependencies
+vi.mock("../../task/Task")
+vi.mock("../../tools/validateToolUse", () => ({
+ validateToolUse: vi.fn(),
+}))
+vi.mock("@roo-code/telemetry", () => ({
+ TelemetryService: {
+ instance: {
+ captureToolUsage: vi.fn(),
+ captureConsecutiveMistakeError: vi.fn(),
+ },
+ },
+}))
+
+describe("presentAssistantMessage - Environment Details Filtering", () => {
+ let mockTask: any
+
+ beforeEach(() => {
+ // Create a mock Task with minimal properties needed for testing
+ mockTask = {
+ taskId: "test-task-id",
+ instanceId: "test-instance",
+ abort: false,
+ presentAssistantMessageLocked: false,
+ presentAssistantMessageHasPendingUpdates: false,
+ currentStreamingContentIndex: 0,
+ assistantMessageContent: [],
+ userMessageContent: [],
+ didCompleteReadingStream: false,
+ didRejectTool: false,
+ didAlreadyUseTool: false,
+ diffEnabled: false,
+ consecutiveMistakeCount: 0,
+ api: {
+ getModel: () => ({ id: "xai/grok-code-fast-1", info: {} }),
+ },
+ browserSession: {
+ closeBrowser: vi.fn().mockResolvedValue(undefined),
+ },
+ recordToolUsage: vi.fn(),
+ toolRepetitionDetector: {
+ check: vi.fn().mockReturnValue({ allowExecution: true }),
+ },
+ providerRef: {
+ deref: () => ({
+ getState: vi.fn().mockResolvedValue({
+ mode: "code",
+ customModes: [],
+ }),
+ }),
+ },
+ say: vi.fn().mockResolvedValue(undefined),
+ ask: vi.fn().mockResolvedValue({ response: "yesButtonClicked" }),
+ }
+ })
+
+ it("should filter out complete tags", async () => {
+ const contentWithEnvironmentDetails = `Here is my response.
+
+# VSCode Visible Files
+src/test.ts
+# Current Time
+2025-11-20T03:00:00Z
+
+This is the actual content the user should see.`
+
+ mockTask.assistantMessageContent = [
+ {
+ type: "text",
+ content: contentWithEnvironmentDetails,
+ partial: false,
+ },
+ ]
+
+ await presentAssistantMessage(mockTask)
+
+ // Check that say was called with filtered content
+ expect(mockTask.say).toHaveBeenCalledWith(
+ "text",
+ expect.stringContaining("Here is my response."),
+ undefined,
+ false,
+ )
+ expect(mockTask.say).toHaveBeenCalledWith(
+ "text",
+ expect.stringContaining("This is the actual content the user should see."),
+ undefined,
+ false,
+ )
+ expect(mockTask.say).toHaveBeenCalledWith(
+ "text",
+ expect.not.stringContaining("environment_details"),
+ undefined,
+ false,
+ )
+ expect(mockTask.say).toHaveBeenCalledWith(
+ "text",
+ expect.not.stringContaining("VSCode Visible Files"),
+ undefined,
+ false,
+ )
+ })
+
+ it("should filter out partial tag at the end", async () => {
+ const contentWithPartialTag = `Here is my response to your query.
+The task has been completed successfully.
+ tag", async () => {
+ const contentWithClosingTag = `Some content here.
+
+The actual response continues here.`
+
+ mockTask.assistantMessageContent = [
+ {
+ type: "text",
+ content: contentWithClosingTag,
+ partial: false,
+ },
+ ]
+
+ await presentAssistantMessage(mockTask)
+
+ // Check that say was called without the closing tag
+ expect(mockTask.say).toHaveBeenCalledWith(
+ "text",
+ expect.not.stringContaining(""),
+ undefined,
+ false,
+ )
+ expect(mockTask.say).toHaveBeenCalledWith(
+ "text",
+ expect.stringContaining("The actual response continues here."),
+ undefined,
+ false,
+ )
+ })
+
+ it("should filter out multiple environment_details blocks", async () => {
+ const contentWithMultipleBlocks = `First part of response.
+
+First block of system info
+
+Middle content.
+
+Second block of system info
+
+Final part of response.`
+
+ mockTask.assistantMessageContent = [
+ {
+ type: "text",
+ content: contentWithMultipleBlocks,
+ partial: false,
+ },
+ ]
+
+ await presentAssistantMessage(mockTask)
+
+ // Check that all environment_details blocks are removed
+ expect(mockTask.say).toHaveBeenCalledWith(
+ "text",
+ expect.stringContaining("First part of response."),
+ undefined,
+ false,
+ )
+ expect(mockTask.say).toHaveBeenCalledWith("text", expect.stringContaining("Middle content."), undefined, false)
+ expect(mockTask.say).toHaveBeenCalledWith(
+ "text",
+ expect.stringContaining("Final part of response."),
+ undefined,
+ false,
+ )
+ expect(mockTask.say).toHaveBeenCalledWith(
+ "text",
+ expect.not.stringContaining("environment_details"),
+ undefined,
+ false,
+ )
+ expect(mockTask.say).toHaveBeenCalledWith("text", expect.not.stringContaining("system info"), undefined, false)
+ })
+
+ it("should handle partial closing tag {
+ const contentWithPartialClosingTag = `Response text here.
+Some more content.
+ {
+ const mixedContent = `Assistant: I'll help you with that task.
+
+
+# System information
+Current directory: /test
+
+
+Let me analyze your code:
+- First point
+- Second point
+
+
+More system info here
+
+
+The solution is straightforward.`
+
+ mockTask.assistantMessageContent = [
+ {
+ type: "text",
+ content: mixedContent,
+ partial: false,
+ },
+ ]
+
+ await presentAssistantMessage(mockTask)
+
+ const calledContent = mockTask.say.mock.calls[0][1]
+
+ // Check that normal content is preserved
+ expect(calledContent).toContain("I'll help you with that task")
+ expect(calledContent).toContain("Let me analyze your code:")
+ expect(calledContent).toContain("- First point")
+ expect(calledContent).toContain("- Second point")
+ expect(calledContent).toContain("The solution is straightforward")
+
+ // Check that environment_details content is removed
+ expect(calledContent).not.toContain("environment_details")
+ expect(calledContent).not.toContain("System information")
+ expect(calledContent).not.toContain("Current directory")
+ expect(calledContent).not.toContain("More system info")
+ })
+
+ it("should handle environment_details tags with whitespace", async () => {
+ const contentWithWhitespace = `Response start.
+
+Content to be removed
+
+Response end.`
+
+ mockTask.assistantMessageContent = [
+ {
+ type: "text",
+ content: contentWithWhitespace,
+ partial: false,
+ },
+ ]
+
+ await presentAssistantMessage(mockTask)
+
+ const calledContent = mockTask.say.mock.calls[0][1]
+
+ // Check that tags with whitespace are properly removed
+ expect(calledContent).toContain("Response start")
+ expect(calledContent).toContain("Response end")
+ expect(calledContent).not.toContain("environment_details")
+ expect(calledContent).not.toContain("Content to be removed")
+ })
+})
diff --git a/src/core/assistant-message/presentAssistantMessage.ts b/src/core/assistant-message/presentAssistantMessage.ts
index fcf7d25cc9..4fac6ebe40 100644
--- a/src/core/assistant-message/presentAssistantMessage.ts
+++ b/src/core/assistant-message/presentAssistantMessage.ts
@@ -123,8 +123,15 @@ export async function presentAssistantMessage(cline: Task) {
content = content.replace(/\s?/g, "")
content = content.replace(/\s?<\/thinking>/g, "")
+ // Remove all instances of ... blocks
+ // This prevents internal system messages from being exposed in model responses
+ // Use regex to match the entire block including content between tags
+ content = content.replace(/[\s\S]*?<\/environment_details>/g, "")
+ // Also remove orphaned closing tags that might appear without opening tags
+ content = content.replace(/\s?<\/environment_details>/g, "")
+
// Remove partial XML tag at the very end of the content (for
- // tool use and thinking tags), Prevents scrollview from
+ // tool use, thinking tags, and environment_details tags), Prevents scrollview from
// jumping when tags are automatically removed.
const lastOpenBracketIndex = content.lastIndexOf("<")
@@ -132,7 +139,7 @@ export async function presentAssistantMessage(cline: Task) {
const possibleTag = content.slice(lastOpenBracketIndex)
// Check if there's a '>' after the last '<' (i.e., if the
- // tag is complete) (complete thinking and tool tags will
+ // tag is complete) (complete thinking, environment_details, and tool tags will
// have been removed by now.)
const hasCloseBracket = possibleTag.includes(">")
@@ -150,14 +157,20 @@ export async function presentAssistantMessage(cline: Task) {
// (letters and underscores only).
const isLikelyTagName = /^[a-zA-Z_]+$/.test(tagContent)
+ // Check if it's a partial environment_details tag
+ const isPartialEnvironmentDetails =
+ "environment_details".startsWith(tagContent.toLowerCase()) ||
+ (tagContent.toLowerCase().startsWith("e") &&
+ "environment_details".includes(tagContent.toLowerCase()))
+
// Preemptively remove < or to keep from these
// artifacts showing up in chat (also handles closing
- // thinking tags).
+ // thinking and environment_details tags).
const isOpeningOrClosing = possibleTag === "<" || possibleTag === ""
// If the tag is incomplete and at the end, remove it
// from the content.
- if (isOpeningOrClosing || isLikelyTagName) {
+ if (isOpeningOrClosing || isLikelyTagName || isPartialEnvironmentDetails) {
content = content.slice(0, lastOpenBracketIndex).trim()
}
}