Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
1c2fad9
initial fix for issues #1240 and #505
DaubnerF Sep 1, 2026
d52e160
Merge branch 'main' into bugfix_for_1240_505
DaubnerF Sep 2, 2026
41a7698
1st round of fixes
DaubnerF Sep 2, 2026
a607ec9
Merge branch 'main' into bugfix_for_1240_505
DaubnerF Sep 2, 2026
f27387b
fixed comments
DaubnerF Sep 2, 2026
8f2245f
Merge branch 'main' into bugfix_for_1240_505
DaubnerF Sep 3, 2026
bc6f8ff
increase test coverage
DaubnerF Sep 3, 2026
6968a98
revert: remove Windows shell invocation from stryker-diff
DaubnerF Sep 4, 2026
e00a2b1
fix: address CodeRabbit review on tool-policy prompt unification
DaubnerF Sep 4, 2026
943a16e
fix(test): correct apiModelId in generateSystemPrompt state mock
DaubnerF Sep 4, 2026
bd7894c
drop use_mcp_tool from policy when no MCP tool is permitted
DaubnerF Sep 4, 2026
c289453
Merge branch 'main' into bugfix_for_1240_505
DaubnerF Sep 5, 2026
26a3a68
code hardening
DaubnerF Sep 7, 2026
7e42bf5
Merge branch 'Zoo-Code-Org:main' into bugfix_for_1240_505
DaubnerF Sep 7, 2026
528b023
bound model fetch with timeout, typed provider state test doubles
DaubnerF Sep 8, 2026
aa8f108
cover preview model fetch timeout path with tests
DaubnerF Sep 8, 2026
415422b
pin completion-time history save ordering with unit tests
DaubnerF Sep 8, 2026
1db50a1
poll history length in restart e2e to tolerate atomic write window
DaubnerF Sep 8, 2026
227ab61
Merge remote-tracking branch 'upstream/main' into bugfix_for_1240_505
DaubnerF Sep 8, 2026
6a769b5
share one model-info snapshot per request between prompt and tools
DaubnerF Sep 8, 2026
92c6f32
resolve provider state once before the MCP wait
DaubnerF Sep 8, 2026
2ce570a
cover the undefined provider state path in the system prompt tests
DaubnerF Sep 8, 2026
ac35870
reuse one model-info snapshot per request and honor cancellation
DaubnerF Sep 8, 2026
b75e73b
pin the retry count the request seam receives
DaubnerF Sep 8, 2026
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
18 changes: 18 additions & 0 deletions apps/vscode-e2e/src/suite/restart-persistence.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,19 @@ async function quitGracefully(): Promise<void> {
await vscode.commands.executeCommand("workbench.action.quit")
}

// The API history file is committed via a backup-rename swap under an advisory
// lock, so an immediate post-completion read can transiently observe it as
// missing. Poll the same read the assertion uses until it reports a non-empty
// history; the assertion semantics below are unchanged.
async function waitForApiConversationHistoryLength(api: RooCodeAPI, taskId: string): Promise<number> {
let length = 0
await waitFor(async () => {
length = await api.getTaskApiConversationHistoryLength(taskId)
return length > 0
})
return length
}

async function runCreate(api: RooCodeAPI): Promise<void> {
let taskId: string | undefined
let createPhasePassed = false
Expand All @@ -43,6 +56,11 @@ async function runCreate(api: RooCodeAPI): Promise<void> {
})
await waitUntilCompleted({ api, taskId })
assert.strictEqual(sawMarker, true, `Completion should include ${MARKER}`)
const historyItem = await api.getTaskHistoryItem(taskId)
assert.ok(historyItem, "Completed task should have a history item")
assert.ok(historyItem.task.includes("RESTART_PERSISTENCE_SMOKE"), "History title should include the marker")
const conversationLength = await waitForApiConversationHistoryLength(api, taskId)
assert.ok(conversationLength > 0, "Completed task should persist API conversation history")

const result: PhaseResult = {
version: PHASE_RESULT_VERSION,
Expand Down
18 changes: 9 additions & 9 deletions docs/architecture/task-lifecycle-model.md

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,15 @@ vi.mock("@roo-code/core", () => ({
},
}))

// Mock the tool handlers so the tests only exercise validation (toolRequirements)
// and never the real tool execution logic.
vi.mock("../../tools/AttemptCompletionTool", () => ({
attemptCompletionTool: { handle: vi.fn().mockResolvedValue(undefined) },
}))
vi.mock("../../tools/AskFollowupQuestionTool", () => ({
askFollowupQuestionTool: { handle: vi.fn().mockResolvedValue(undefined) },
}))

// presentAssistantMessage records tool usage through TelemetryService.instance.
vi.mock("@roo-code/telemetry", () => ({
TelemetryService: {
Expand Down Expand Up @@ -333,6 +342,86 @@ describe("presentAssistantMessage - Custom Tool Recording", () => {
edit: false,
})
})

it("never marks a protocol tool (attempt_completion) as blocked", async () => {
mockTask.assistantMessageContent = [
{
type: "tool_use",
id: "tool_call_protocol_123",
name: "attempt_completion",
params: {},
nativeArgs: {},
partial: false,
},
]

mockTask.providerRef = {
deref: () => ({
getState: vi.fn().mockResolvedValue({
mode: "code",
customModes: [],
experiments: {
customTools: false,
},
disabledTools: ["attempt_completion"],
}),
}),
}

await presentAssistantMessage(mockTask)

const validateToolUseMock = vi.mocked(validateToolUse)
expect(validateToolUseMock).toHaveBeenCalled()
const toolRequirements = validateToolUseMock.mock.calls[0][3]
// Protocol tools never enter toolRequirements, so the validator cannot
// block them even when disabledTools lists them.
expect(toolRequirements).not.toHaveProperty("attempt_completion")

// With validateToolUse mocked to return normally, the block proceeds
// past validation: no validation-error tool_result is pushed.
const errorToolResults = mockTask.userMessageContent.filter((block: unknown) => {
const b = block as { type?: string; is_error?: boolean }
return b.type === "tool_result" && b.is_error
})
expect(errorToolResults).toEqual([])
})

it("still marks ordinary tools (ask_followup_question) as blocked", async () => {
mockTask.assistantMessageContent = [
{
type: "tool_use",
id: "tool_call_ordinary_123",
name: "ask_followup_question",
params: { question: "Which option?" },
nativeArgs: { question: "Which option?" },
partial: false,
},
]

mockTask.providerRef = {
deref: () => ({
getState: vi.fn().mockResolvedValue({
mode: "code",
customModes: [],
experiments: {
customTools: false,
},
disabledTools: ["ask_followup_question"],
}),
}),
}

await presentAssistantMessage(mockTask)

const validateToolUseMock = vi.mocked(validateToolUse)
expect(validateToolUseMock).toHaveBeenCalled()
const toolRequirements = validateToolUseMock.mock.calls[0][3]
// Control/ordinary tools remain blockable — the inverse of the
// protocol-tool guarantee.
expect(toolRequirements).toMatchObject({
ask_followup_question: false,
})
})
})

describe("Partial blocks", () => {
Expand Down
15 changes: 5 additions & 10 deletions src/core/assistant-message/presentAssistantMessage.ts
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ import { skillTool } from "../tools/SkillTool"
import { generateImageTool } from "../tools/GenerateImageTool"
import { applyDiffTool as applyDiffToolClass } from "../tools/ApplyDiffTool"
import { isValidToolName, validateToolUse } from "../tools/validateToolUse"
import { buildToolRequirements } from "../prompts/tools/effective-tool-policy"
import { codebaseSearchTool } from "../tools/CodebaseSearchTool"

import { formatResponse } from "../prompts/responses"
Expand Down Expand Up @@ -604,16 +605,10 @@ export async function presentAssistantMessage(cline: Task) {
const isCustomTool = Boolean(stateExperiments?.customTools && customToolRegistry.has(block.name))

try {
const toolRequirements =
disabledTools?.reduce(
(acc: Record<string, boolean>, tool: string) => {
acc[tool] = false
const resolvedToolName = resolveToolAlias(tool)
acc[resolvedToolName] = false
return acc
},
{} as Record<string, boolean>,
) ?? {}
// Use the exported resolver so `attempt_completion` (and its aliases)
// never enters `toolRequirements` — the runtime validator never blocks a
// protocol tool. See `buildToolRequirements` in effective-tool-policy.ts.
const toolRequirements = buildToolRequirements(disabledTools)

validateToolUse(
block.name as ToolName,
Expand Down

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading
Loading