Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 18 additions & 1 deletion src/core/task/Task.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2024,9 +2024,26 @@ export class Task extends EventEmitter<TaskEvents> implements TaskLike {

const environmentDetails = await getEnvironmentDetails(this, currentIncludeFileDetails)

// Remove any existing environment_details blocks before adding fresh ones.
// This prevents duplicate environment details when resuming tasks with XML tool calls,
// where the old user message content may already contain environment details from the previous session.
// We check for both opening and closing tags to ensure we're matching complete environment detail blocks,
// not just mentions of the tag in regular content.
const contentWithoutEnvDetails = parsedUserContent.filter((block) => {
if (block.type === "text" && typeof block.text === "string") {
// Check if this text block is a complete environment_details block
// by verifying it starts with the opening tag and ends with the closing tag
const isEnvironmentDetailsBlock =
block.text.trim().startsWith("<environment_details>") &&
block.text.trim().endsWith("</environment_details>")
return !isEnvironmentDetailsBlock
}
return true
})

// Add environment details as its own text block, separate from tool
// results.
const finalUserContent = [...parsedUserContent, { type: "text" as const, text: environmentDetails }]
const finalUserContent = [...contentWithoutEnvDetails, { type: "text" as const, text: environmentDetails }]

// Only add user message to conversation history if:
// 1. This is the first attempt (retryAttempt === 0), OR
Expand Down
121 changes: 121 additions & 0 deletions src/core/task/__tests__/task-tool-history.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -196,5 +196,126 @@ describe("Task Tool History Handling", () => {
content: '{"setting": "value"}',
})
})

describe("environment details deduplication", () => {
it("should filter out existing environment_details blocks before adding new ones", () => {
// Simulate user content that already contains environment details from a previous session
const userContentWithOldEnvDetails = [
{
type: "text" as const,
text: "Some user message",
},
{
type: "text" as const,
text: "<environment_details>\n# Old Environment Details\nCurrent time: 2024-01-01\n</environment_details>",
},
]

// Filter out existing environment_details blocks using the same logic as Task.ts
const contentWithoutEnvDetails = userContentWithOldEnvDetails.filter((block) => {
if (block.type === "text" && typeof block.text === "string") {
// Check if this text block is a complete environment_details block
const isEnvironmentDetailsBlock =
block.text.trim().startsWith("<environment_details>") &&
block.text.trim().endsWith("</environment_details>")
return !isEnvironmentDetailsBlock
}
return true
})

// Verify old environment details were removed
expect(contentWithoutEnvDetails).toHaveLength(1)
expect(contentWithoutEnvDetails[0].text).toBe("Some user message")

// Simulate adding fresh environment details
const newEnvironmentDetails =
"<environment_details>\n# Fresh Environment Details\nCurrent time: 2024-01-02\n</environment_details>"
const finalUserContent = [
...contentWithoutEnvDetails,
{ type: "text" as const, text: newEnvironmentDetails },
]

// Verify we have exactly one environment_details block (the new one)
const envDetailsBlocks = finalUserContent.filter((block) => {
if (block.type === "text" && typeof block.text === "string") {
return (
block.text.trim().startsWith("<environment_details>") &&
block.text.trim().endsWith("</environment_details>")
)
}
return false
})
expect(envDetailsBlocks).toHaveLength(1)
expect(envDetailsBlocks[0].text).toContain("2024-01-02")
expect(envDetailsBlocks[0].text).not.toContain("2024-01-01")
})

it("should not filter out text that mentions environment_details tags in content", () => {
// User content that mentions the tags but isn't an environment_details block
const userContent = [
{
type: "text" as const,
text: "Let me explain how <environment_details> work in this system",
},
{
type: "text" as const,
text: "The closing tag is </environment_details>",
},
{
type: "text" as const,
text: "Regular message",
},
]

// Filter using the same logic as Task.ts
const contentWithoutEnvDetails = userContent.filter((block) => {
if (block.type === "text" && typeof block.text === "string") {
const isEnvironmentDetailsBlock =
block.text.trim().startsWith("<environment_details>") &&
block.text.trim().endsWith("</environment_details>")
return !isEnvironmentDetailsBlock
}
return true
})

// All blocks should be preserved since none are complete environment_details blocks
expect(contentWithoutEnvDetails).toHaveLength(3)
expect(contentWithoutEnvDetails).toEqual(userContent)
})

it("should not filter out regular text blocks", () => {
// User content with various blocks but no environment details
const userContent = [
{
type: "text" as const,
text: "Regular message",
},
{
type: "text" as const,
text: "Another message with <task> tags",
},
{
type: "tool_result" as const,
tool_use_id: "tool_123",
content: "Tool result",
},
]

// Filter using the same logic as Task.ts
const contentWithoutEnvDetails = userContent.filter((block) => {
if (block.type === "text" && typeof block.text === "string") {
const isEnvironmentDetailsBlock =
block.text.trim().startsWith("<environment_details>") &&
block.text.trim().endsWith("</environment_details>")
return !isEnvironmentDetailsBlock
}
return true
})

// All blocks should be preserved
expect(contentWithoutEnvDetails).toHaveLength(3)
expect(contentWithoutEnvDetails).toEqual(userContent)
})
})
})
})
Loading