Skip to content

Commit f089c04

Browse files
authored
Merge branch 'main' into issue/634
2 parents f0ab3eb + 1ae8b5b commit f089c04

97 files changed

Lines changed: 3785 additions & 710 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

AGENTS.md

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,18 @@ This file provides guidance to agents when working with code in this repository.
55
- Settings View Pattern: When working on `SettingsView`, inputs must bind to the local `cachedState`, NOT the live `useExtensionState()`. The `cachedState` acts as a buffer for user edits, isolating them from the `ContextProxy` source-of-truth until the user explicitly clicks "Save". Wiring inputs directly to the live state causes race conditions.
66
- Changesets: Do NOT create `.changeset` files for each commit or code change. Changesets are managed separately by maintainers and should not be generated by agents during normal development.
77

8+
## ESLint Suppressions
9+
10+
`src/eslint-suppressions.json` tracks per-file counts of suppressed lint rules. Suppression counts must never increase. When touching a file, prefer reducing its count when the fix is local and low-risk; avoid broad unrelated cleanup.
11+
12+
When writing new code:
13+
14+
- Fix lint violations in the new code rather than suppressing them.
15+
- Avoid `as any`; use typed APIs directly (e.g. `RooCodeEventName.X` constants with typed `on()`/`listenerCount()`), or bracket notation (`obj["privateField"]`) to access private members. Prefer precise test doubles or `unknown` with a type guard over double assertions (`as unknown as T`); use double assertions only as a last resort, with a comment explaining why.
16+
- Avoid floating promises; add `void`, `await`, or `.catch()` as appropriate.
17+
- After editing a file, run `pnpm --dir src exec eslint --prune-suppressions --max-warnings=0 <relative-file>` and confirm the count for that file did not increase.
18+
- If a suppression is truly unavoidable (e.g. `vi.spyOn(Cls.prototype as any, "privateMethod")` where no typed alternative exists), document why in a comment next to the cast.
19+
820
## Test Placement Guidance
921

1022
Prefer the narrowest test layer that proves the behavior. This follows standard test-pyramid guidance: keep most coverage in fast, focused tests; add integration tests for cross-module contracts; reserve end-to-end tests for full workflow confidence.

apps/vscode-e2e/src/fixtures/subtasks.ts

Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,15 @@ export const SUBTASK_XPROFILE_SAME_CHILD_RESULT = "Same-profile child completed"
4848
export const SUBTASK_XPROFILE_DIFFERENT_CHILD_RESULT = "Different-profile child completed"
4949
export const SUBTASK_XPROFILE_PARENT_RESULT = "Sequential cross-profile parent resumed"
5050

51+
// Scheduler regression tests — exercises TaskScheduler + run() dispatch post-CodeRabbit fix.
52+
// Separate markers to avoid collisions with the other subtask fixtures.
53+
const SCHED_STANDALONE_MARKER = "SCHED_STANDALONE_INTERRUPT_RESUME"
54+
const SCHED_COMPLETED_MARKER = "SCHED_COMPLETED_REOPEN"
55+
export const SCHED_STANDALONE_PROMPT = `${SCHED_STANDALONE_MARKER}: Ask the user exactly this follow-up question: What is the square root of 64? After the user answers, complete with only the answer.`
56+
export const SCHED_STANDALONE_FOLLOWUP_ANSWER = "8"
57+
export const SCHED_COMPLETED_PROMPT = `${SCHED_COMPLETED_MARKER}: Complete immediately with the exact result "Scheduler completed task".`
58+
export const SCHED_COMPLETED_RESULT = "Scheduler completed task"
59+
5160
const apiHangChildMatch = new RegExp(SUBTASK_API_HANG_CHILD_MARKER)
5261

5362
const requestContains = (req: ChatCompletionRequest, expected: string[]) => {
@@ -396,6 +405,62 @@ export function addSubtaskFixtures(mock: InstanceType<typeof LLMock>) {
396405
},
397406
})
398407

408+
// Scheduler regression fixtures: standalone interrupted task resume and completed task reopen.
409+
mock.addFixture({
410+
match: {
411+
predicate: (req: ChatCompletionRequest) =>
412+
requestContains(req, [SCHED_STANDALONE_MARKER]) &&
413+
!requestContains(req, ["call_sched_standalone_followup_001"]) &&
414+
!requestContains(req, [`<user_message>\\n${SCHED_STANDALONE_FOLLOWUP_ANSWER}\\n</user_message>`]),
415+
},
416+
response: {
417+
toolCalls: [
418+
{
419+
name: "ask_followup_question",
420+
arguments: JSON.stringify({
421+
question: "What is the square root of 64?",
422+
follow_up: [{ text: SCHED_STANDALONE_FOLLOWUP_ANSWER }],
423+
}),
424+
id: "call_sched_standalone_followup_001",
425+
},
426+
],
427+
},
428+
})
429+
430+
mock.addFixture({
431+
match: {
432+
predicate: (req: ChatCompletionRequest) =>
433+
toolResultContains(req, "call_sched_standalone_followup_001", [SCHED_STANDALONE_FOLLOWUP_ANSWER]) ||
434+
requestContains(req, ["call_sched_standalone_followup_001", SCHED_STANDALONE_FOLLOWUP_ANSWER]) ||
435+
requestContains(req, [
436+
SCHED_STANDALONE_MARKER,
437+
`<user_message>\\n${SCHED_STANDALONE_FOLLOWUP_ANSWER}\\n</user_message>`,
438+
]),
439+
},
440+
response: {
441+
toolCalls: [
442+
{
443+
name: "attempt_completion",
444+
arguments: JSON.stringify({ result: SCHED_STANDALONE_FOLLOWUP_ANSWER }),
445+
id: "call_sched_standalone_completion_002",
446+
},
447+
],
448+
},
449+
})
450+
451+
mock.addFixture({
452+
match: { userMessage: new RegExp(SCHED_COMPLETED_MARKER) },
453+
response: {
454+
toolCalls: [
455+
{
456+
name: "attempt_completion",
457+
arguments: JSON.stringify({ result: SCHED_COMPLETED_RESULT }),
458+
id: "call_sched_completed_completion_001",
459+
},
460+
],
461+
},
462+
})
463+
399464
// Interrupted-child-resumes-and-reports-back scenario (#560)
400465
mock.addFixture({
401466
match: {

apps/vscode-e2e/src/suite/subtasks.test.ts

Lines changed: 139 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,10 @@ import { RooCodeEventName, type ClineMessage } from "@roo-code/types"
55
import { setDefaultSuiteTimeout } from "./test-utils"
66
import { sleep, waitFor, waitUntilCompleted } from "./utils"
77
import {
8+
SCHED_COMPLETED_PROMPT,
9+
SCHED_COMPLETED_RESULT,
10+
SCHED_STANDALONE_FOLLOWUP_ANSWER,
11+
SCHED_STANDALONE_PROMPT,
812
SUBTASK_ABANDON_CHILD_FOLLOWUP_ANSWER,
913
SUBTASK_ABANDON_PARENT_PROMPT,
1014
SUBTASK_API_HANG_CHILD_MARKER,
@@ -945,4 +949,139 @@ suite("Roo Code Subtasks", function () {
945949
await waitFor(() => api.getCurrentTaskStack().length === 0).catch(() => {})
946950
}
947951
})
952+
953+
// TaskScheduler regression: resumeTask on a completed task must show resume_completed_task ask.
954+
// Before the CodeRabbit fix, createTaskWithHistoryItem bypassed the scheduler and called
955+
// Task.run() via the constructor's startTask: true default, causing run() to call startTask()
956+
// (clearing history) instead of resumeTaskFromHistory().
957+
test("resumeTask on a completed task presents resume_completed_task ask", async () => {
958+
const api = globalThis.api
959+
const asks: Record<string, ClineMessage[]> = {}
960+
const says: Record<string, ClineMessage[]> = {}
961+
962+
const messageHandler = ({ taskId, message }: { taskId: string; message: ClineMessage }) => {
963+
if (message.type === "ask") {
964+
asks[taskId] = asks[taskId] || []
965+
asks[taskId].push(message)
966+
}
967+
if (message.type === "say" && message.partial === false) {
968+
says[taskId] = says[taskId] || []
969+
says[taskId].push(message)
970+
}
971+
}
972+
973+
api.on(RooCodeEventName.Message, messageHandler)
974+
975+
try {
976+
// Run a task to completion.
977+
const taskId = await waitUntilCompleted({
978+
api,
979+
start: () =>
980+
api.startNewTask({
981+
configuration: {
982+
mode: "ask",
983+
autoApprovalEnabled: true,
984+
enableCheckpoints: false,
985+
},
986+
text: SCHED_COMPLETED_PROMPT,
987+
}),
988+
})
989+
990+
assert.strictEqual(
991+
says[taskId]?.find(({ say }) => say === "completion_result")?.text?.trim(),
992+
SCHED_COMPLETED_RESULT,
993+
"Task should complete with expected result",
994+
)
995+
996+
// Re-open it via resumeTask — should hit resumeTaskFromHistory(), showing resume_completed_task.
997+
await api.resumeTask(taskId)
998+
999+
await waitFor(
1000+
() => asks[taskId]?.some(({ type, ask }) => type === "ask" && ask === "resume_completed_task") ?? false,
1001+
)
1002+
} finally {
1003+
api.off(RooCodeEventName.Message, messageHandler)
1004+
while (api.getCurrentTaskStack().length > 0) {
1005+
await api.clearCurrentTask()
1006+
}
1007+
await sleep(500)
1008+
}
1009+
})
1010+
1011+
// TaskScheduler regression: resumeTask on an interrupted standalone task must show resume_task
1012+
// ask and allow the task to complete normally via the scheduler slot.
1013+
test("resumeTask on an interrupted standalone task presents resume_task ask and completes", async () => {
1014+
const api = globalThis.api
1015+
const asks: Record<string, ClineMessage[]> = {}
1016+
const says: Record<string, ClineMessage[]> = {}
1017+
1018+
const messageHandler = ({ taskId, message }: { taskId: string; message: ClineMessage }) => {
1019+
if (message.type === "ask") {
1020+
asks[taskId] = asks[taskId] || []
1021+
asks[taskId].push(message)
1022+
}
1023+
if (message.type === "say" && message.partial === false) {
1024+
says[taskId] = says[taskId] || []
1025+
says[taskId].push(message)
1026+
}
1027+
}
1028+
1029+
api.on(RooCodeEventName.Message, messageHandler)
1030+
1031+
let taskId: string | undefined
1032+
1033+
try {
1034+
taskId = await api.startNewTask({
1035+
configuration: {
1036+
mode: "ask",
1037+
autoApprovalEnabled: true,
1038+
enableCheckpoints: false,
1039+
},
1040+
text: SCHED_STANDALONE_PROMPT,
1041+
})
1042+
1043+
// Wait until the task pauses at the follow-up question.
1044+
await waitFor(() => asks[taskId!]?.some(({ type, ask }) => type === "ask" && ask === "followup") ?? false)
1045+
1046+
// Cancel it — the task becomes interrupted.
1047+
await api.cancelCurrentTask()
1048+
1049+
await waitFor(
1050+
() => asks[taskId!]?.some(({ type, ask }) => type === "ask" && ask === "resume_task") ?? false,
1051+
)
1052+
1053+
// Resume via scheduler path (createTaskWithHistoryItem).
1054+
const askCountBeforeResume = asks[taskId!]?.length ?? 0
1055+
await api.resumeTask(taskId!)
1056+
1057+
await waitFor(() =>
1058+
(asks[taskId!] ?? [])
1059+
.slice(askCountBeforeResume)
1060+
.some(({ type, ask }) => type === "ask" && ask === "resume_task"),
1061+
)
1062+
1063+
// Sending the answer both acknowledges the resume_task ask and answers the pending
1064+
// follow-up question from the original task, completing the task.
1065+
const completedTaskId = await waitUntilCompleted({
1066+
api,
1067+
start: async () => {
1068+
await api.sendMessage(SCHED_STANDALONE_FOLLOWUP_ANSWER)
1069+
return taskId!
1070+
},
1071+
})
1072+
1073+
assert.strictEqual(completedTaskId, taskId, "The resumed standalone task should complete")
1074+
assert.strictEqual(
1075+
says[taskId!]?.find(({ say }) => say === "completion_result")?.text?.trim(),
1076+
SCHED_STANDALONE_FOLLOWUP_ANSWER,
1077+
"Task should complete with the follow-up answer as result",
1078+
)
1079+
} finally {
1080+
api.off(RooCodeEventName.Message, messageHandler)
1081+
while (api.getCurrentTaskStack().length > 0) {
1082+
await api.clearCurrentTask()
1083+
}
1084+
await sleep(500)
1085+
}
1086+
})
9481087
})

packages/types/src/providers/friendli.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,8 @@ export const friendliModels = {
2020
outputPrice: 4.4,
2121
cacheWritesPrice: 0,
2222
cacheReadsPrice: 0.26,
23+
supportsReasoningEffort: ["minimal", "low", "medium", "high", "xhigh", "max"],
24+
reasoningEffort: "high",
2325
description:
2426
"GLM-5.2 is Zhipu's flagship model with a 1M context window and 128k max output, served via Friendli Model APIs. It delivers top-tier long-context reasoning, coding, and agentic performance for extended engineering sessions.",
2527
},
@@ -33,6 +35,8 @@ export const friendliModels = {
3335
outputPrice: 4.4,
3436
cacheWritesPrice: 0,
3537
cacheReadsPrice: 0.26,
38+
supportsReasoningEffort: ["minimal", "low", "medium", "high", "xhigh", "max"],
39+
reasoningEffort: "high",
3640
description:
3741
"GLM-5.1 is Zhipu's most capable model with a 200k context window and 128k max output, served via Friendli Model APIs. It delivers top-tier reasoning, coding, and agentic performance.",
3842
},

0 commit comments

Comments
 (0)