mirror of
https://github.com/RooVetGit/Roo-Code.git
synced 2026-08-28 05:27:24 +00:00
Task and TaskProvider event emitter cleanup + a few new events (#6606)
Co-authored-by: Roo Code <roomote@roocode.com>
This commit is contained in:
parent
8513263a67
commit
b2d2a2c5d2
46 changed files with 1119 additions and 684 deletions
|
|
@ -1,6 +1,6 @@
|
|||
import * as assert from "assert"
|
||||
|
||||
import type { ClineMessage } from "@roo-code/types"
|
||||
import { RooCodeEventName, type ClineMessage } from "@roo-code/types"
|
||||
|
||||
import { waitUntilCompleted } from "./utils"
|
||||
import { setDefaultSuiteTimeout } from "./test-utils"
|
||||
|
|
@ -13,7 +13,7 @@ suite("Markdown List Rendering", function () {
|
|||
|
||||
const messages: ClineMessage[] = []
|
||||
|
||||
api.on("message", ({ message }: { message: ClineMessage }) => {
|
||||
api.on(RooCodeEventName.Message, ({ message }: { message: ClineMessage }) => {
|
||||
if (message.type === "say" && message.partial === false) {
|
||||
messages.push(message)
|
||||
}
|
||||
|
|
@ -50,7 +50,7 @@ suite("Markdown List Rendering", function () {
|
|||
|
||||
const messages: ClineMessage[] = []
|
||||
|
||||
api.on("message", ({ message }: { message: ClineMessage }) => {
|
||||
api.on(RooCodeEventName.Message, ({ message }: { message: ClineMessage }) => {
|
||||
if (message.type === "say" && message.partial === false) {
|
||||
messages.push(message)
|
||||
}
|
||||
|
|
@ -87,7 +87,7 @@ suite("Markdown List Rendering", function () {
|
|||
|
||||
const messages: ClineMessage[] = []
|
||||
|
||||
api.on("message", ({ message }: { message: ClineMessage }) => {
|
||||
api.on(RooCodeEventName.Message, ({ message }: { message: ClineMessage }) => {
|
||||
if (message.type === "say" && message.partial === false) {
|
||||
messages.push(message)
|
||||
}
|
||||
|
|
@ -139,7 +139,7 @@ suite("Markdown List Rendering", function () {
|
|||
|
||||
const messages: ClineMessage[] = []
|
||||
|
||||
api.on("message", ({ message }: { message: ClineMessage }) => {
|
||||
api.on(RooCodeEventName.Message, ({ message }: { message: ClineMessage }) => {
|
||||
if (message.type === "say" && message.partial === false) {
|
||||
messages.push(message)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,5 +1,7 @@
|
|||
import * as assert from "assert"
|
||||
|
||||
import { RooCodeEventName } from "@roo-code/types"
|
||||
|
||||
import { waitUntilCompleted } from "./utils"
|
||||
import { setDefaultSuiteTimeout } from "./test-utils"
|
||||
|
||||
|
|
@ -9,7 +11,7 @@ suite("Roo Code Modes", function () {
|
|||
test("Should handle switching modes correctly", async () => {
|
||||
const modes: string[] = []
|
||||
|
||||
globalThis.api.on("taskModeSwitched", (_taskId, mode) => modes.push(mode))
|
||||
globalThis.api.on(RooCodeEventName.TaskModeSwitched, (_taskId, mode) => modes.push(mode))
|
||||
|
||||
const switchModesTaskId = await globalThis.api.startNewTask({
|
||||
configuration: { mode: "code", alwaysAllowModeSwitch: true, autoApprovalEnabled: true },
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
import * as assert from "assert"
|
||||
|
||||
import type { ClineMessage } from "@roo-code/types"
|
||||
import { RooCodeEventName, type ClineMessage } from "@roo-code/types"
|
||||
|
||||
import { sleep, waitFor, waitUntilCompleted } from "./utils"
|
||||
|
||||
|
|
@ -10,7 +10,7 @@ suite.skip("Roo Code Subtasks", () => {
|
|||
|
||||
const messages: Record<string, ClineMessage[]> = {}
|
||||
|
||||
api.on("message", ({ taskId, message }) => {
|
||||
api.on(RooCodeEventName.Message, ({ taskId, message }) => {
|
||||
if (message.type === "say" && message.partial === false) {
|
||||
messages[taskId] = messages[taskId] || []
|
||||
messages[taskId].push(message)
|
||||
|
|
@ -37,7 +37,7 @@ suite.skip("Roo Code Subtasks", () => {
|
|||
let spawnedTaskId: string | undefined = undefined
|
||||
|
||||
// Wait for the subtask to be spawned and then cancel it.
|
||||
api.on("taskSpawned", (_, childTaskId) => (spawnedTaskId = childTaskId))
|
||||
api.on(RooCodeEventName.TaskSpawned, (_, childTaskId) => (spawnedTaskId = childTaskId))
|
||||
await waitFor(() => !!spawnedTaskId)
|
||||
await sleep(1_000) // Give the task a chance to start and populate the history.
|
||||
await api.cancelCurrentTask()
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
import * as assert from "assert"
|
||||
|
||||
import type { ClineMessage } from "@roo-code/types"
|
||||
import { RooCodeEventName, type ClineMessage } from "@roo-code/types"
|
||||
|
||||
import { waitUntilCompleted } from "./utils"
|
||||
import { setDefaultSuiteTimeout } from "./test-utils"
|
||||
|
|
@ -13,7 +13,7 @@ suite("Roo Code Task", function () {
|
|||
|
||||
const messages: ClineMessage[] = []
|
||||
|
||||
api.on("message", ({ message }) => {
|
||||
api.on(RooCodeEventName.Message, ({ message }) => {
|
||||
if (message.type === "say" && message.partial === false) {
|
||||
messages.push(message)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@ import * as fs from "fs/promises"
|
|||
import * as path from "path"
|
||||
import * as vscode from "vscode"
|
||||
|
||||
import type { ClineMessage } from "@roo-code/types"
|
||||
import { RooCodeEventName, type ClineMessage } from "@roo-code/types"
|
||||
|
||||
import { waitFor, sleep } from "../utils"
|
||||
import { setDefaultSuiteTimeout } from "../test-utils"
|
||||
|
|
@ -192,7 +192,7 @@ function validateInput(input) {
|
|||
}
|
||||
}
|
||||
}
|
||||
api.on("message", messageHandler)
|
||||
api.on(RooCodeEventName.Message, messageHandler)
|
||||
|
||||
// Listen for task events
|
||||
const taskStartedHandler = (id: string) => {
|
||||
|
|
@ -201,7 +201,7 @@ function validateInput(input) {
|
|||
console.log("Task started:", id)
|
||||
}
|
||||
}
|
||||
api.on("taskStarted", taskStartedHandler)
|
||||
api.on(RooCodeEventName.TaskStarted, taskStartedHandler)
|
||||
|
||||
const taskCompletedHandler = (id: string) => {
|
||||
if (id === taskId) {
|
||||
|
|
@ -209,7 +209,7 @@ function validateInput(input) {
|
|||
console.log("Task completed:", id)
|
||||
}
|
||||
}
|
||||
api.on("taskCompleted", taskCompletedHandler)
|
||||
api.on(RooCodeEventName.TaskCompleted, taskCompletedHandler)
|
||||
|
||||
let taskId: string
|
||||
try {
|
||||
|
|
@ -260,9 +260,9 @@ ${testFile.content}\nAssume the file exists and you can modify it directly.`,
|
|||
console.log("Test passed! apply_diff tool executed and file modified successfully")
|
||||
} finally {
|
||||
// Clean up
|
||||
api.off("message", messageHandler)
|
||||
api.off("taskStarted", taskStartedHandler)
|
||||
api.off("taskCompleted", taskCompletedHandler)
|
||||
api.off(RooCodeEventName.Message, messageHandler)
|
||||
api.off(RooCodeEventName.TaskStarted, taskStartedHandler)
|
||||
api.off(RooCodeEventName.TaskCompleted, taskCompletedHandler)
|
||||
}
|
||||
})
|
||||
|
||||
|
|
@ -305,7 +305,7 @@ ${testFile.content}\nAssume the file exists and you can modify it directly.`,
|
|||
}
|
||||
}
|
||||
}
|
||||
api.on("message", messageHandler)
|
||||
api.on(RooCodeEventName.Message, messageHandler)
|
||||
|
||||
// Listen for task events
|
||||
const taskStartedHandler = (id: string) => {
|
||||
|
|
@ -314,7 +314,7 @@ ${testFile.content}\nAssume the file exists and you can modify it directly.`,
|
|||
console.log("Task started:", id)
|
||||
}
|
||||
}
|
||||
api.on("taskStarted", taskStartedHandler)
|
||||
api.on(RooCodeEventName.TaskStarted, taskStartedHandler)
|
||||
|
||||
const taskCompletedHandler = (id: string) => {
|
||||
if (id === taskId) {
|
||||
|
|
@ -322,7 +322,7 @@ ${testFile.content}\nAssume the file exists and you can modify it directly.`,
|
|||
console.log("Task completed:", id)
|
||||
}
|
||||
}
|
||||
api.on("taskCompleted", taskCompletedHandler)
|
||||
api.on(RooCodeEventName.TaskCompleted, taskCompletedHandler)
|
||||
|
||||
let taskId: string
|
||||
try {
|
||||
|
|
@ -375,9 +375,9 @@ ${testFile.content}\nAssume the file exists and you can modify it directly.`,
|
|||
console.log("Test passed! apply_diff tool executed and multiple replacements applied successfully")
|
||||
} finally {
|
||||
// Clean up
|
||||
api.off("message", messageHandler)
|
||||
api.off("taskStarted", taskStartedHandler)
|
||||
api.off("taskCompleted", taskCompletedHandler)
|
||||
api.off(RooCodeEventName.Message, messageHandler)
|
||||
api.off(RooCodeEventName.TaskStarted, taskStartedHandler)
|
||||
api.off(RooCodeEventName.TaskCompleted, taskCompletedHandler)
|
||||
}
|
||||
})
|
||||
|
||||
|
|
@ -424,7 +424,7 @@ function keepThis() {
|
|||
}
|
||||
}
|
||||
}
|
||||
api.on("message", messageHandler)
|
||||
api.on(RooCodeEventName.Message, messageHandler)
|
||||
|
||||
// Listen for task events
|
||||
const taskStartedHandler = (id: string) => {
|
||||
|
|
@ -432,14 +432,14 @@ function keepThis() {
|
|||
taskStarted = true
|
||||
}
|
||||
}
|
||||
api.on("taskStarted", taskStartedHandler)
|
||||
api.on(RooCodeEventName.TaskStarted, taskStartedHandler)
|
||||
|
||||
const taskCompletedHandler = (id: string) => {
|
||||
if (id === taskId) {
|
||||
taskCompleted = true
|
||||
}
|
||||
}
|
||||
api.on("taskCompleted", taskCompletedHandler)
|
||||
api.on(RooCodeEventName.TaskCompleted, taskCompletedHandler)
|
||||
|
||||
let taskId: string
|
||||
try {
|
||||
|
|
@ -487,9 +487,9 @@ ${testFile.content}\nAssume the file exists and you can modify it directly.`,
|
|||
console.log("Test passed! apply_diff tool executed and targeted modification successful")
|
||||
} finally {
|
||||
// Clean up
|
||||
api.off("message", messageHandler)
|
||||
api.off("taskStarted", taskStartedHandler)
|
||||
api.off("taskCompleted", taskCompletedHandler)
|
||||
api.off(RooCodeEventName.Message, messageHandler)
|
||||
api.off(RooCodeEventName.TaskStarted, taskStartedHandler)
|
||||
api.off(RooCodeEventName.TaskCompleted, taskCompletedHandler)
|
||||
}
|
||||
})
|
||||
|
||||
|
|
@ -532,7 +532,7 @@ ${testFile.content}\nAssume the file exists and you can modify it directly.`,
|
|||
}
|
||||
}
|
||||
}
|
||||
api.on("message", messageHandler)
|
||||
api.on(RooCodeEventName.Message, messageHandler)
|
||||
|
||||
// Listen for task events
|
||||
const taskStartedHandler = (id: string) => {
|
||||
|
|
@ -540,14 +540,14 @@ ${testFile.content}\nAssume the file exists and you can modify it directly.`,
|
|||
taskStarted = true
|
||||
}
|
||||
}
|
||||
api.on("taskStarted", taskStartedHandler)
|
||||
api.on(RooCodeEventName.TaskStarted, taskStartedHandler)
|
||||
|
||||
const taskCompletedHandler = (id: string) => {
|
||||
if (id === taskId) {
|
||||
taskCompleted = true
|
||||
}
|
||||
}
|
||||
api.on("taskCompleted", taskCompletedHandler)
|
||||
api.on(RooCodeEventName.TaskCompleted, taskCompletedHandler)
|
||||
|
||||
let taskId: string
|
||||
try {
|
||||
|
|
@ -598,9 +598,9 @@ Assume the file exists and you can modify it directly.`,
|
|||
console.log("Test passed! apply_diff attempted and error handled gracefully")
|
||||
} finally {
|
||||
// Clean up
|
||||
api.off("message", messageHandler)
|
||||
api.off("taskStarted", taskStartedHandler)
|
||||
api.off("taskCompleted", taskCompletedHandler)
|
||||
api.off(RooCodeEventName.Message, messageHandler)
|
||||
api.off(RooCodeEventName.TaskStarted, taskStartedHandler)
|
||||
api.off(RooCodeEventName.TaskCompleted, taskCompletedHandler)
|
||||
}
|
||||
})
|
||||
|
||||
|
|
@ -663,7 +663,7 @@ function checkInput(input) {
|
|||
}
|
||||
}
|
||||
}
|
||||
api.on("message", messageHandler)
|
||||
api.on(RooCodeEventName.Message, messageHandler)
|
||||
|
||||
// Listen for task events
|
||||
const taskStartedHandler = (id: string) => {
|
||||
|
|
@ -672,7 +672,7 @@ function checkInput(input) {
|
|||
console.log("Task started:", id)
|
||||
}
|
||||
}
|
||||
api.on("taskStarted", taskStartedHandler)
|
||||
api.on(RooCodeEventName.TaskStarted, taskStartedHandler)
|
||||
|
||||
const taskCompletedHandler = (id: string) => {
|
||||
if (id === taskId) {
|
||||
|
|
@ -680,7 +680,7 @@ function checkInput(input) {
|
|||
console.log("Task completed:", id)
|
||||
}
|
||||
}
|
||||
api.on("taskCompleted", taskCompletedHandler)
|
||||
api.on(RooCodeEventName.TaskCompleted, taskCompletedHandler)
|
||||
|
||||
let taskId: string
|
||||
try {
|
||||
|
|
@ -742,9 +742,9 @@ Assume the file exists and you can modify it directly.`,
|
|||
console.log("Test passed! apply_diff tool executed and multiple search/replace blocks applied successfully")
|
||||
} finally {
|
||||
// Clean up
|
||||
api.off("message", messageHandler)
|
||||
api.off("taskStarted", taskStartedHandler)
|
||||
api.off("taskCompleted", taskCompletedHandler)
|
||||
api.off(RooCodeEventName.Message, messageHandler)
|
||||
api.off(RooCodeEventName.TaskStarted, taskStartedHandler)
|
||||
api.off(RooCodeEventName.TaskCompleted, taskCompletedHandler)
|
||||
}
|
||||
})
|
||||
})
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@ import * as fs from "fs/promises"
|
|||
import * as path from "path"
|
||||
import * as vscode from "vscode"
|
||||
|
||||
import type { ClineMessage } from "@roo-code/types"
|
||||
import { RooCodeEventName, type ClineMessage } from "@roo-code/types"
|
||||
|
||||
import { waitFor, sleep, waitUntilCompleted } from "../utils"
|
||||
import { setDefaultSuiteTimeout } from "../test-utils"
|
||||
|
|
@ -145,7 +145,7 @@ suite("Roo Code execute_command Tool", function () {
|
|||
}
|
||||
}
|
||||
}
|
||||
api.on("message", messageHandler)
|
||||
api.on(RooCodeEventName.Message, messageHandler)
|
||||
|
||||
// Listen for task events
|
||||
const taskStartedHandler = (id: string) => {
|
||||
|
|
@ -154,7 +154,7 @@ suite("Roo Code execute_command Tool", function () {
|
|||
console.log("Task started:", id)
|
||||
}
|
||||
}
|
||||
api.on("taskStarted", taskStartedHandler)
|
||||
api.on(RooCodeEventName.TaskStarted, taskStartedHandler)
|
||||
|
||||
const taskCompletedHandler = (id: string) => {
|
||||
if (id === taskId) {
|
||||
|
|
@ -162,7 +162,7 @@ suite("Roo Code execute_command Tool", function () {
|
|||
console.log("Task completed:", id)
|
||||
}
|
||||
}
|
||||
api.on("taskCompleted", taskCompletedHandler)
|
||||
api.on(RooCodeEventName.TaskCompleted, taskCompletedHandler)
|
||||
|
||||
let taskId: string
|
||||
try {
|
||||
|
|
@ -208,9 +208,9 @@ Then use the attempt_completion tool to complete the task. Do not suggest any co
|
|||
console.log("Test passed! Command executed successfully")
|
||||
} finally {
|
||||
// Clean up event listeners
|
||||
api.off("message", messageHandler)
|
||||
api.off("taskStarted", taskStartedHandler)
|
||||
api.off("taskCompleted", taskCompletedHandler)
|
||||
api.off(RooCodeEventName.Message, messageHandler)
|
||||
api.off(RooCodeEventName.TaskStarted, taskStartedHandler)
|
||||
api.off(RooCodeEventName.TaskCompleted, taskCompletedHandler)
|
||||
}
|
||||
})
|
||||
|
||||
|
|
@ -251,7 +251,7 @@ Then use the attempt_completion tool to complete the task. Do not suggest any co
|
|||
}
|
||||
}
|
||||
}
|
||||
api.on("message", messageHandler)
|
||||
api.on(RooCodeEventName.Message, messageHandler)
|
||||
|
||||
// Listen for task events
|
||||
const taskStartedHandler = (id: string) => {
|
||||
|
|
@ -260,7 +260,7 @@ Then use the attempt_completion tool to complete the task. Do not suggest any co
|
|||
console.log("Task started:", id)
|
||||
}
|
||||
}
|
||||
api.on("taskStarted", taskStartedHandler)
|
||||
api.on(RooCodeEventName.TaskStarted, taskStartedHandler)
|
||||
|
||||
const taskCompletedHandler = (id: string) => {
|
||||
if (id === taskId) {
|
||||
|
|
@ -268,7 +268,7 @@ Then use the attempt_completion tool to complete the task. Do not suggest any co
|
|||
console.log("Task completed:", id)
|
||||
}
|
||||
}
|
||||
api.on("taskCompleted", taskCompletedHandler)
|
||||
api.on(RooCodeEventName.TaskCompleted, taskCompletedHandler)
|
||||
|
||||
let taskId: string
|
||||
try {
|
||||
|
|
@ -320,9 +320,9 @@ Avoid at all costs suggesting a command when using the attempt_completion tool`,
|
|||
console.log("Test passed! Command executed in custom directory")
|
||||
} finally {
|
||||
// Clean up event listeners
|
||||
api.off("message", messageHandler)
|
||||
api.off("taskStarted", taskStartedHandler)
|
||||
api.off("taskCompleted", taskCompletedHandler)
|
||||
api.off(RooCodeEventName.Message, messageHandler)
|
||||
api.off(RooCodeEventName.TaskStarted, taskStartedHandler)
|
||||
api.off(RooCodeEventName.TaskCompleted, taskCompletedHandler)
|
||||
|
||||
// Clean up subdirectory
|
||||
try {
|
||||
|
|
@ -365,7 +365,7 @@ Avoid at all costs suggesting a command when using the attempt_completion tool`,
|
|||
}
|
||||
}
|
||||
}
|
||||
api.on("message", messageHandler)
|
||||
api.on(RooCodeEventName.Message, messageHandler)
|
||||
|
||||
// Listen for task events
|
||||
const taskStartedHandler = (id: string) => {
|
||||
|
|
@ -374,7 +374,7 @@ Avoid at all costs suggesting a command when using the attempt_completion tool`,
|
|||
console.log("Task started:", id)
|
||||
}
|
||||
}
|
||||
api.on("taskStarted", taskStartedHandler)
|
||||
api.on(RooCodeEventName.TaskStarted, taskStartedHandler)
|
||||
|
||||
const taskCompletedHandler = (id: string) => {
|
||||
if (id === taskId) {
|
||||
|
|
@ -382,7 +382,7 @@ Avoid at all costs suggesting a command when using the attempt_completion tool`,
|
|||
console.log("Task completed:", id)
|
||||
}
|
||||
}
|
||||
api.on("taskCompleted", taskCompletedHandler)
|
||||
api.on(RooCodeEventName.TaskCompleted, taskCompletedHandler)
|
||||
|
||||
let taskId: string
|
||||
try {
|
||||
|
|
@ -440,9 +440,9 @@ After both commands are executed, use the attempt_completion tool to complete th
|
|||
console.log("Test passed! Multiple commands executed successfully")
|
||||
} finally {
|
||||
// Clean up event listeners
|
||||
api.off("message", messageHandler)
|
||||
api.off("taskStarted", taskStartedHandler)
|
||||
api.off("taskCompleted", taskCompletedHandler)
|
||||
api.off(RooCodeEventName.Message, messageHandler)
|
||||
api.off(RooCodeEventName.TaskStarted, taskStartedHandler)
|
||||
api.off(RooCodeEventName.TaskCompleted, taskCompletedHandler)
|
||||
}
|
||||
})
|
||||
|
||||
|
|
@ -484,7 +484,7 @@ After both commands are executed, use the attempt_completion tool to complete th
|
|||
}
|
||||
}
|
||||
}
|
||||
api.on("message", messageHandler)
|
||||
api.on(RooCodeEventName.Message, messageHandler)
|
||||
|
||||
// Listen for task events
|
||||
const taskStartedHandler = (id: string) => {
|
||||
|
|
@ -493,7 +493,7 @@ After both commands are executed, use the attempt_completion tool to complete th
|
|||
console.log("Task started:", id)
|
||||
}
|
||||
}
|
||||
api.on("taskStarted", taskStartedHandler)
|
||||
api.on(RooCodeEventName.TaskStarted, taskStartedHandler)
|
||||
|
||||
const taskCompletedHandler = (id: string) => {
|
||||
if (id === taskId) {
|
||||
|
|
@ -501,7 +501,7 @@ After both commands are executed, use the attempt_completion tool to complete th
|
|||
console.log("Task completed:", id)
|
||||
}
|
||||
}
|
||||
api.on("taskCompleted", taskCompletedHandler)
|
||||
api.on(RooCodeEventName.TaskCompleted, taskCompletedHandler)
|
||||
|
||||
let taskId: string
|
||||
try {
|
||||
|
|
@ -550,9 +550,9 @@ Avoid at all costs suggesting a command when using the attempt_completion tool`,
|
|||
console.log("Test passed! Long-running command handled successfully")
|
||||
} finally {
|
||||
// Clean up event listeners
|
||||
api.off("message", messageHandler)
|
||||
api.off("taskStarted", taskStartedHandler)
|
||||
api.off("taskCompleted", taskCompletedHandler)
|
||||
api.off(RooCodeEventName.Message, messageHandler)
|
||||
api.off(RooCodeEventName.TaskStarted, taskStartedHandler)
|
||||
api.off(RooCodeEventName.TaskCompleted, taskCompletedHandler)
|
||||
}
|
||||
})
|
||||
})
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@ import * as fs from "fs/promises"
|
|||
import * as path from "path"
|
||||
import * as vscode from "vscode"
|
||||
|
||||
import type { ClineMessage } from "@roo-code/types"
|
||||
import { RooCodeEventName, type ClineMessage } from "@roo-code/types"
|
||||
|
||||
import { waitFor, sleep } from "../utils"
|
||||
import { setDefaultSuiteTimeout } from "../test-utils"
|
||||
|
|
@ -145,7 +145,7 @@ ${testFile.content}`
|
|||
}
|
||||
}
|
||||
}
|
||||
api.on("message", messageHandler)
|
||||
api.on(RooCodeEventName.Message, messageHandler)
|
||||
|
||||
// Listen for task events
|
||||
const taskStartedHandler = (id: string) => {
|
||||
|
|
@ -154,7 +154,7 @@ ${testFile.content}`
|
|||
console.log("Task started:", id)
|
||||
}
|
||||
}
|
||||
api.on("taskStarted", taskStartedHandler)
|
||||
api.on(RooCodeEventName.TaskStarted, taskStartedHandler)
|
||||
|
||||
const taskCompletedHandler = (id: string) => {
|
||||
if (id === taskId) {
|
||||
|
|
@ -162,7 +162,7 @@ ${testFile.content}`
|
|||
console.log("Task completed:", id)
|
||||
}
|
||||
}
|
||||
api.on("taskCompleted", taskCompletedHandler)
|
||||
api.on(RooCodeEventName.TaskCompleted, taskCompletedHandler)
|
||||
|
||||
let taskId: string
|
||||
try {
|
||||
|
|
@ -221,9 +221,9 @@ Assume the file exists and you can modify it directly.`,
|
|||
|
||||
console.log("Test passed! insert_content tool executed and content inserted at beginning successfully")
|
||||
} finally {
|
||||
api.off("message", messageHandler)
|
||||
api.off("taskStarted", taskStartedHandler)
|
||||
api.off("taskCompleted", taskCompletedHandler)
|
||||
api.off(RooCodeEventName.Message, messageHandler)
|
||||
api.off(RooCodeEventName.TaskStarted, taskStartedHandler)
|
||||
api.off(RooCodeEventName.TaskCompleted, taskCompletedHandler)
|
||||
}
|
||||
})
|
||||
try {
|
||||
|
|
@ -286,7 +286,7 @@ ${insertContent}`
|
|||
}
|
||||
}
|
||||
}
|
||||
api.on("message", messageHandler)
|
||||
api.on(RooCodeEventName.Message, messageHandler)
|
||||
|
||||
// Listen for task events
|
||||
const taskStartedHandler = (id: string) => {
|
||||
|
|
@ -295,7 +295,7 @@ ${insertContent}`
|
|||
console.log("Task started:", id)
|
||||
}
|
||||
}
|
||||
api.on("taskStarted", taskStartedHandler)
|
||||
api.on(RooCodeEventName.TaskStarted, taskStartedHandler)
|
||||
|
||||
const taskCompletedHandler = (id: string) => {
|
||||
if (id === taskId) {
|
||||
|
|
@ -303,7 +303,7 @@ ${insertContent}`
|
|||
console.log("Task completed:", id)
|
||||
}
|
||||
}
|
||||
api.on("taskCompleted", taskCompletedHandler)
|
||||
api.on(RooCodeEventName.TaskCompleted, taskCompletedHandler)
|
||||
|
||||
let taskId: string
|
||||
try {
|
||||
|
|
@ -388,7 +388,7 @@ ${testFile.content}`
|
|||
}
|
||||
}
|
||||
}
|
||||
api.on("message", messageHandler)
|
||||
api.on(RooCodeEventName.Message, messageHandler)
|
||||
|
||||
// Listen for task events
|
||||
const taskStartedHandler = (id: string) => {
|
||||
|
|
@ -397,7 +397,7 @@ ${testFile.content}`
|
|||
console.log("Task started:", id)
|
||||
}
|
||||
}
|
||||
api.on("taskStarted", taskStartedHandler)
|
||||
api.on(RooCodeEventName.TaskStarted, taskStartedHandler)
|
||||
|
||||
const taskCompletedHandler = (id: string) => {
|
||||
if (id === taskId) {
|
||||
|
|
@ -405,7 +405,7 @@ ${testFile.content}`
|
|||
console.log("Task completed:", id)
|
||||
}
|
||||
}
|
||||
api.on("taskCompleted", taskCompletedHandler)
|
||||
api.on(RooCodeEventName.TaskCompleted, taskCompletedHandler)
|
||||
|
||||
let taskId: string
|
||||
try {
|
||||
|
|
@ -490,7 +490,7 @@ And this is the second line`
|
|||
}
|
||||
}
|
||||
}
|
||||
api.on("message", messageHandler)
|
||||
api.on(RooCodeEventName.Message, messageHandler)
|
||||
|
||||
// Listen for task events
|
||||
const taskStartedHandler = (id: string) => {
|
||||
|
|
@ -499,7 +499,7 @@ And this is the second line`
|
|||
console.log("Task started:", id)
|
||||
}
|
||||
}
|
||||
api.on("taskStarted", taskStartedHandler)
|
||||
api.on(RooCodeEventName.TaskStarted, taskStartedHandler)
|
||||
|
||||
const taskCompletedHandler = (id: string) => {
|
||||
if (id === taskId) {
|
||||
|
|
@ -507,7 +507,7 @@ And this is the second line`
|
|||
console.log("Task completed:", id)
|
||||
}
|
||||
}
|
||||
api.on("taskCompleted", taskCompletedHandler)
|
||||
api.on(RooCodeEventName.TaskCompleted, taskCompletedHandler)
|
||||
|
||||
let taskId: string
|
||||
try {
|
||||
|
|
@ -572,9 +572,9 @@ The file is currently empty. Assume the file exists and you can modify it direct
|
|||
"Test passed! insert_content tool executed and content inserted into empty file successfully",
|
||||
)
|
||||
} finally {
|
||||
api.off("message", messageHandler)
|
||||
api.off("taskStarted", taskStartedHandler)
|
||||
api.off("taskCompleted", taskCompletedHandler)
|
||||
api.off(RooCodeEventName.Message, messageHandler)
|
||||
api.off(RooCodeEventName.TaskStarted, taskStartedHandler)
|
||||
api.off(RooCodeEventName.TaskCompleted, taskCompletedHandler)
|
||||
}
|
||||
})
|
||||
// Check if the file was modified correctly
|
||||
|
|
@ -600,9 +600,9 @@ The file is currently empty. Assume the file exists and you can modify it direct
|
|||
|
||||
console.log("Test passed! insert_content tool executed and multiline content inserted successfully")
|
||||
} finally {
|
||||
api.off("message", messageHandler)
|
||||
api.off("taskStarted", taskStartedHandler)
|
||||
api.off("taskCompleted", taskCompletedHandler)
|
||||
api.off(RooCodeEventName.Message, messageHandler)
|
||||
api.off(RooCodeEventName.TaskStarted, taskStartedHandler)
|
||||
api.off(RooCodeEventName.TaskCompleted, taskCompletedHandler)
|
||||
}
|
||||
})
|
||||
assert.strictEqual(insertContentExecuted, true, "insert_content tool should have been executed")
|
||||
|
|
@ -619,9 +619,9 @@ The file is currently empty. Assume the file exists and you can modify it direct
|
|||
|
||||
console.log("Test passed! insert_content tool executed and content inserted at end successfully")
|
||||
} finally {
|
||||
api.off("message", messageHandler)
|
||||
api.off("taskStarted", taskStartedHandler)
|
||||
api.off("taskCompleted", taskCompletedHandler)
|
||||
api.off(RooCodeEventName.Message, messageHandler)
|
||||
api.off(RooCodeEventName.TaskStarted, taskStartedHandler)
|
||||
api.off(RooCodeEventName.TaskCompleted, taskCompletedHandler)
|
||||
}
|
||||
})
|
||||
// Tests will be added here one by one
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@ import * as fs from "fs/promises"
|
|||
import * as path from "path"
|
||||
import * as vscode from "vscode"
|
||||
|
||||
import type { ClineMessage } from "@roo-code/types"
|
||||
import { RooCodeEventName, type ClineMessage } from "@roo-code/types"
|
||||
|
||||
import { waitFor, sleep } from "../utils"
|
||||
import { setDefaultSuiteTimeout } from "../test-utils"
|
||||
|
|
@ -207,7 +207,7 @@ This directory contains various files and subdirectories for testing the list_fi
|
|||
}
|
||||
}
|
||||
}
|
||||
api.on("message", messageHandler)
|
||||
api.on(RooCodeEventName.Message, messageHandler)
|
||||
|
||||
// Listen for task completion
|
||||
const taskCompletedHandler = (id: string) => {
|
||||
|
|
@ -215,7 +215,7 @@ This directory contains various files and subdirectories for testing the list_fi
|
|||
taskCompleted = true
|
||||
}
|
||||
}
|
||||
api.on("taskCompleted", taskCompletedHandler)
|
||||
api.on(RooCodeEventName.TaskCompleted, taskCompletedHandler)
|
||||
|
||||
let taskId: string
|
||||
try {
|
||||
|
|
@ -271,8 +271,8 @@ This directory contains various files and subdirectories for testing the list_fi
|
|||
console.log("Test passed! Directory listing (non-recursive) executed successfully")
|
||||
} finally {
|
||||
// Clean up
|
||||
api.off("message", messageHandler)
|
||||
api.off("taskCompleted", taskCompletedHandler)
|
||||
api.off(RooCodeEventName.Message, messageHandler)
|
||||
api.off(RooCodeEventName.TaskCompleted, taskCompletedHandler)
|
||||
}
|
||||
})
|
||||
|
||||
|
|
@ -310,7 +310,7 @@ This directory contains various files and subdirectories for testing the list_fi
|
|||
}
|
||||
}
|
||||
}
|
||||
api.on("message", messageHandler)
|
||||
api.on(RooCodeEventName.Message, messageHandler)
|
||||
|
||||
// Listen for task completion
|
||||
const taskCompletedHandler = (id: string) => {
|
||||
|
|
@ -318,7 +318,7 @@ This directory contains various files and subdirectories for testing the list_fi
|
|||
taskCompleted = true
|
||||
}
|
||||
}
|
||||
api.on("taskCompleted", taskCompletedHandler)
|
||||
api.on(RooCodeEventName.TaskCompleted, taskCompletedHandler)
|
||||
|
||||
let taskId: string
|
||||
try {
|
||||
|
|
@ -381,8 +381,8 @@ This directory contains various files and subdirectories for testing the list_fi
|
|||
console.log("Test passed! Directory listing (recursive) executed successfully")
|
||||
} finally {
|
||||
// Clean up
|
||||
api.off("message", messageHandler)
|
||||
api.off("taskCompleted", taskCompletedHandler)
|
||||
api.off(RooCodeEventName.Message, messageHandler)
|
||||
api.off(RooCodeEventName.TaskCompleted, taskCompletedHandler)
|
||||
}
|
||||
})
|
||||
|
||||
|
|
@ -420,7 +420,7 @@ This directory contains various files and subdirectories for testing the list_fi
|
|||
}
|
||||
}
|
||||
}
|
||||
api.on("message", messageHandler)
|
||||
api.on(RooCodeEventName.Message, messageHandler)
|
||||
|
||||
// Listen for task completion
|
||||
const taskCompletedHandler = (id: string) => {
|
||||
|
|
@ -428,7 +428,7 @@ This directory contains various files and subdirectories for testing the list_fi
|
|||
taskCompleted = true
|
||||
}
|
||||
}
|
||||
api.on("taskCompleted", taskCompletedHandler)
|
||||
api.on(RooCodeEventName.TaskCompleted, taskCompletedHandler)
|
||||
|
||||
let taskId: string
|
||||
try {
|
||||
|
|
@ -499,8 +499,8 @@ This directory contains various files and subdirectories for testing the list_fi
|
|||
await fs.rm(testDir, { recursive: true, force: true })
|
||||
} finally {
|
||||
// Clean up
|
||||
api.off("message", messageHandler)
|
||||
api.off("taskCompleted", taskCompletedHandler)
|
||||
api.off(RooCodeEventName.Message, messageHandler)
|
||||
api.off(RooCodeEventName.TaskCompleted, taskCompletedHandler)
|
||||
}
|
||||
})
|
||||
|
||||
|
|
@ -523,7 +523,7 @@ This directory contains various files and subdirectories for testing the list_fi
|
|||
}
|
||||
}
|
||||
}
|
||||
api.on("message", messageHandler)
|
||||
api.on(RooCodeEventName.Message, messageHandler)
|
||||
|
||||
// Listen for task completion
|
||||
const taskCompletedHandler = (id: string) => {
|
||||
|
|
@ -531,7 +531,7 @@ This directory contains various files and subdirectories for testing the list_fi
|
|||
taskCompleted = true
|
||||
}
|
||||
}
|
||||
api.on("taskCompleted", taskCompletedHandler)
|
||||
api.on(RooCodeEventName.TaskCompleted, taskCompletedHandler)
|
||||
|
||||
let taskId: string
|
||||
try {
|
||||
|
|
@ -569,8 +569,8 @@ This directory contains various files and subdirectories for testing the list_fi
|
|||
console.log("Test passed! Workspace root directory listing executed successfully")
|
||||
} finally {
|
||||
// Clean up
|
||||
api.off("message", messageHandler)
|
||||
api.off("taskCompleted", taskCompletedHandler)
|
||||
api.off(RooCodeEventName.Message, messageHandler)
|
||||
api.off(RooCodeEventName.TaskCompleted, taskCompletedHandler)
|
||||
}
|
||||
})
|
||||
})
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@ import * as path from "path"
|
|||
import * as os from "os"
|
||||
import * as vscode from "vscode"
|
||||
|
||||
import type { ClineMessage } from "@roo-code/types"
|
||||
import { RooCodeEventName, type ClineMessage } from "@roo-code/types"
|
||||
|
||||
import { waitFor, sleep } from "../utils"
|
||||
import { setDefaultSuiteTimeout } from "../test-utils"
|
||||
|
|
@ -180,7 +180,7 @@ suite("Roo Code read_file Tool", function () {
|
|||
console.log("AI response:", message.text?.substring(0, 200))
|
||||
}
|
||||
}
|
||||
api.on("message", messageHandler)
|
||||
api.on(RooCodeEventName.Message, messageHandler)
|
||||
|
||||
// Listen for task events
|
||||
const taskStartedHandler = (id: string) => {
|
||||
|
|
@ -189,7 +189,7 @@ suite("Roo Code read_file Tool", function () {
|
|||
console.log("Task started:", id)
|
||||
}
|
||||
}
|
||||
api.on("taskStarted", taskStartedHandler)
|
||||
api.on(RooCodeEventName.TaskStarted, taskStartedHandler)
|
||||
|
||||
const taskCompletedHandler = (id: string) => {
|
||||
if (id === taskId) {
|
||||
|
|
@ -197,7 +197,7 @@ suite("Roo Code read_file Tool", function () {
|
|||
console.log("Task completed:", id)
|
||||
}
|
||||
}
|
||||
api.on("taskCompleted", taskCompletedHandler)
|
||||
api.on(RooCodeEventName.TaskCompleted, taskCompletedHandler)
|
||||
|
||||
let taskId: string
|
||||
try {
|
||||
|
|
@ -259,9 +259,9 @@ suite("Roo Code read_file Tool", function () {
|
|||
console.log("Test passed! File read successfully with correct content")
|
||||
} finally {
|
||||
// Clean up
|
||||
api.off("message", messageHandler)
|
||||
api.off("taskStarted", taskStartedHandler)
|
||||
api.off("taskCompleted", taskCompletedHandler)
|
||||
api.off(RooCodeEventName.Message, messageHandler)
|
||||
api.off(RooCodeEventName.TaskStarted, taskStartedHandler)
|
||||
api.off(RooCodeEventName.TaskCompleted, taskCompletedHandler)
|
||||
}
|
||||
})
|
||||
|
||||
|
|
@ -314,7 +314,7 @@ suite("Roo Code read_file Tool", function () {
|
|||
console.log("AI response:", message.text?.substring(0, 200))
|
||||
}
|
||||
}
|
||||
api.on("message", messageHandler)
|
||||
api.on(RooCodeEventName.Message, messageHandler)
|
||||
|
||||
// Listen for task completion
|
||||
const taskCompletedHandler = (id: string) => {
|
||||
|
|
@ -322,7 +322,7 @@ suite("Roo Code read_file Tool", function () {
|
|||
taskCompleted = true
|
||||
}
|
||||
}
|
||||
api.on("taskCompleted", taskCompletedHandler)
|
||||
api.on(RooCodeEventName.TaskCompleted, taskCompletedHandler)
|
||||
|
||||
let taskId: string
|
||||
try {
|
||||
|
|
@ -371,8 +371,8 @@ suite("Roo Code read_file Tool", function () {
|
|||
console.log("Test passed! Multiline file read successfully with correct content")
|
||||
} finally {
|
||||
// Clean up
|
||||
api.off("message", messageHandler)
|
||||
api.off("taskCompleted", taskCompletedHandler)
|
||||
api.off(RooCodeEventName.Message, messageHandler)
|
||||
api.off(RooCodeEventName.TaskCompleted, taskCompletedHandler)
|
||||
}
|
||||
})
|
||||
|
||||
|
|
@ -425,7 +425,7 @@ suite("Roo Code read_file Tool", function () {
|
|||
console.log("AI response:", message.text?.substring(0, 200))
|
||||
}
|
||||
}
|
||||
api.on("message", messageHandler)
|
||||
api.on(RooCodeEventName.Message, messageHandler)
|
||||
|
||||
// Listen for task completion
|
||||
const taskCompletedHandler = (id: string) => {
|
||||
|
|
@ -433,7 +433,7 @@ suite("Roo Code read_file Tool", function () {
|
|||
taskCompleted = true
|
||||
}
|
||||
}
|
||||
api.on("taskCompleted", taskCompletedHandler)
|
||||
api.on(RooCodeEventName.TaskCompleted, taskCompletedHandler)
|
||||
|
||||
let taskId: string
|
||||
try {
|
||||
|
|
@ -484,8 +484,8 @@ suite("Roo Code read_file Tool", function () {
|
|||
console.log("Test passed! File read with line range successfully")
|
||||
} finally {
|
||||
// Clean up
|
||||
api.off("message", messageHandler)
|
||||
api.off("taskCompleted", taskCompletedHandler)
|
||||
api.off(RooCodeEventName.Message, messageHandler)
|
||||
api.off(RooCodeEventName.TaskCompleted, taskCompletedHandler)
|
||||
}
|
||||
})
|
||||
|
||||
|
|
@ -512,7 +512,7 @@ suite("Roo Code read_file Tool", function () {
|
|||
}
|
||||
}
|
||||
}
|
||||
api.on("message", messageHandler)
|
||||
api.on(RooCodeEventName.Message, messageHandler)
|
||||
|
||||
// Listen for task completion
|
||||
const taskCompletedHandler = (id: string) => {
|
||||
|
|
@ -520,7 +520,7 @@ suite("Roo Code read_file Tool", function () {
|
|||
taskCompleted = true
|
||||
}
|
||||
}
|
||||
api.on("taskCompleted", taskCompletedHandler)
|
||||
api.on(RooCodeEventName.TaskCompleted, taskCompletedHandler)
|
||||
|
||||
let taskId: string
|
||||
try {
|
||||
|
|
@ -556,8 +556,8 @@ suite("Roo Code read_file Tool", function () {
|
|||
console.log("Test passed! Non-existent file handled correctly")
|
||||
} finally {
|
||||
// Clean up
|
||||
api.off("message", messageHandler)
|
||||
api.off("taskCompleted", taskCompletedHandler)
|
||||
api.off(RooCodeEventName.Message, messageHandler)
|
||||
api.off(RooCodeEventName.TaskCompleted, taskCompletedHandler)
|
||||
}
|
||||
})
|
||||
|
||||
|
|
@ -585,7 +585,7 @@ suite("Roo Code read_file Tool", function () {
|
|||
console.log("AI response:", message.text?.substring(0, 200))
|
||||
}
|
||||
}
|
||||
api.on("message", messageHandler)
|
||||
api.on(RooCodeEventName.Message, messageHandler)
|
||||
|
||||
// Listen for task completion
|
||||
const taskCompletedHandler = (id: string) => {
|
||||
|
|
@ -593,7 +593,7 @@ suite("Roo Code read_file Tool", function () {
|
|||
taskCompleted = true
|
||||
}
|
||||
}
|
||||
api.on("taskCompleted", taskCompletedHandler)
|
||||
api.on(RooCodeEventName.TaskCompleted, taskCompletedHandler)
|
||||
|
||||
let taskId: string
|
||||
try {
|
||||
|
|
@ -627,8 +627,8 @@ suite("Roo Code read_file Tool", function () {
|
|||
console.log("Test passed! XML file read successfully")
|
||||
} finally {
|
||||
// Clean up
|
||||
api.off("message", messageHandler)
|
||||
api.off("taskCompleted", taskCompletedHandler)
|
||||
api.off(RooCodeEventName.Message, messageHandler)
|
||||
api.off(RooCodeEventName.TaskCompleted, taskCompletedHandler)
|
||||
}
|
||||
})
|
||||
|
||||
|
|
@ -651,7 +651,7 @@ suite("Roo Code read_file Tool", function () {
|
|||
}
|
||||
}
|
||||
}
|
||||
api.on("message", messageHandler)
|
||||
api.on(RooCodeEventName.Message, messageHandler)
|
||||
|
||||
// Listen for task completion
|
||||
const taskCompletedHandler = (id: string) => {
|
||||
|
|
@ -659,7 +659,7 @@ suite("Roo Code read_file Tool", function () {
|
|||
taskCompleted = true
|
||||
}
|
||||
}
|
||||
api.on("taskCompleted", taskCompletedHandler)
|
||||
api.on(RooCodeEventName.TaskCompleted, taskCompletedHandler)
|
||||
|
||||
let taskId: string
|
||||
try {
|
||||
|
|
@ -700,8 +700,8 @@ Assume both files exist and you can read them directly. Read each file and tell
|
|||
console.log("Test passed! Multiple files read successfully")
|
||||
} finally {
|
||||
// Clean up
|
||||
api.off("message", messageHandler)
|
||||
api.off("taskCompleted", taskCompletedHandler)
|
||||
api.off(RooCodeEventName.Message, messageHandler)
|
||||
api.off(RooCodeEventName.TaskCompleted, taskCompletedHandler)
|
||||
}
|
||||
})
|
||||
|
||||
|
|
@ -729,7 +729,7 @@ Assume both files exist and you can read them directly. Read each file and tell
|
|||
console.log("AI response:", message.text?.substring(0, 200))
|
||||
}
|
||||
}
|
||||
api.on("message", messageHandler)
|
||||
api.on(RooCodeEventName.Message, messageHandler)
|
||||
|
||||
// Listen for task completion
|
||||
const taskCompletedHandler = (id: string) => {
|
||||
|
|
@ -737,7 +737,7 @@ Assume both files exist and you can read them directly. Read each file and tell
|
|||
taskCompleted = true
|
||||
}
|
||||
}
|
||||
api.on("taskCompleted", taskCompletedHandler)
|
||||
api.on(RooCodeEventName.TaskCompleted, taskCompletedHandler)
|
||||
|
||||
let taskId: string
|
||||
try {
|
||||
|
|
@ -771,8 +771,8 @@ Assume both files exist and you can read them directly. Read each file and tell
|
|||
console.log("Test passed! Large file read efficiently")
|
||||
} finally {
|
||||
// Clean up
|
||||
api.off("message", messageHandler)
|
||||
api.off("taskCompleted", taskCompletedHandler)
|
||||
api.off(RooCodeEventName.Message, messageHandler)
|
||||
api.off(RooCodeEventName.TaskCompleted, taskCompletedHandler)
|
||||
}
|
||||
})
|
||||
})
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@ import * as fs from "fs/promises"
|
|||
import * as path from "path"
|
||||
import * as vscode from "vscode"
|
||||
|
||||
import type { ClineMessage } from "@roo-code/types"
|
||||
import { RooCodeEventName, type ClineMessage } from "@roo-code/types"
|
||||
|
||||
import { waitFor, sleep } from "../utils"
|
||||
import { setDefaultSuiteTimeout } from "../test-utils"
|
||||
|
|
@ -175,7 +175,7 @@ Final content`,
|
|||
}
|
||||
}
|
||||
}
|
||||
api.on("message", messageHandler)
|
||||
api.on(RooCodeEventName.Message, messageHandler)
|
||||
|
||||
// Listen for task events
|
||||
const taskStartedHandler = (id: string) => {
|
||||
|
|
@ -184,7 +184,7 @@ Final content`,
|
|||
console.log("Task started:", id)
|
||||
}
|
||||
}
|
||||
api.on("taskStarted", taskStartedHandler)
|
||||
api.on(RooCodeEventName.TaskStarted, taskStartedHandler)
|
||||
|
||||
const taskCompletedHandler = (id: string) => {
|
||||
if (id === taskId) {
|
||||
|
|
@ -192,7 +192,7 @@ Final content`,
|
|||
console.log("Task completed:", id)
|
||||
}
|
||||
}
|
||||
api.on("taskCompleted", taskCompletedHandler)
|
||||
api.on(RooCodeEventName.TaskCompleted, taskCompletedHandler)
|
||||
|
||||
let taskId: string
|
||||
try {
|
||||
|
|
@ -249,9 +249,9 @@ Assume the file exists and you can modify it directly.`,
|
|||
console.log("Test passed! search_and_replace tool executed and file modified successfully")
|
||||
} finally {
|
||||
// Clean up
|
||||
api.off("message", messageHandler)
|
||||
api.off("taskStarted", taskStartedHandler)
|
||||
api.off("taskCompleted", taskCompletedHandler)
|
||||
api.off(RooCodeEventName.Message, messageHandler)
|
||||
api.off(RooCodeEventName.TaskStarted, taskStartedHandler)
|
||||
api.off(RooCodeEventName.TaskCompleted, taskCompletedHandler)
|
||||
}
|
||||
})
|
||||
|
||||
|
|
@ -303,7 +303,7 @@ function anotherNewFunction() {
|
|||
}
|
||||
}
|
||||
}
|
||||
api.on("message", messageHandler)
|
||||
api.on(RooCodeEventName.Message, messageHandler)
|
||||
|
||||
// Listen for task events
|
||||
const taskStartedHandler = (id: string) => {
|
||||
|
|
@ -312,7 +312,7 @@ function anotherNewFunction() {
|
|||
console.log("Task started:", id)
|
||||
}
|
||||
}
|
||||
api.on("taskStarted", taskStartedHandler)
|
||||
api.on(RooCodeEventName.TaskStarted, taskStartedHandler)
|
||||
|
||||
const taskCompletedHandler = (id: string) => {
|
||||
if (id === taskId) {
|
||||
|
|
@ -320,7 +320,7 @@ function anotherNewFunction() {
|
|||
console.log("Task completed:", id)
|
||||
}
|
||||
}
|
||||
api.on("taskCompleted", taskCompletedHandler)
|
||||
api.on(RooCodeEventName.TaskCompleted, taskCompletedHandler)
|
||||
|
||||
let taskId: string
|
||||
try {
|
||||
|
|
@ -378,9 +378,9 @@ Use the search_and_replace tool twice - once for each replacement.`,
|
|||
console.log("Test passed! search_and_replace tool executed with regex successfully")
|
||||
} finally {
|
||||
// Clean up
|
||||
api.off("message", messageHandler)
|
||||
api.off("taskStarted", taskStartedHandler)
|
||||
api.off("taskCompleted", taskCompletedHandler)
|
||||
api.off(RooCodeEventName.Message, messageHandler)
|
||||
api.off(RooCodeEventName.TaskStarted, taskStartedHandler)
|
||||
api.off(RooCodeEventName.TaskCompleted, taskCompletedHandler)
|
||||
}
|
||||
})
|
||||
|
||||
|
|
@ -429,7 +429,7 @@ Final content`
|
|||
}
|
||||
}
|
||||
}
|
||||
api.on("message", messageHandler)
|
||||
api.on(RooCodeEventName.Message, messageHandler)
|
||||
|
||||
// Listen for task events
|
||||
const taskStartedHandler = (id: string) => {
|
||||
|
|
@ -438,7 +438,7 @@ Final content`
|
|||
console.log("Task started:", id)
|
||||
}
|
||||
}
|
||||
api.on("taskStarted", taskStartedHandler)
|
||||
api.on(RooCodeEventName.TaskStarted, taskStartedHandler)
|
||||
|
||||
const taskCompletedHandler = (id: string) => {
|
||||
if (id === taskId) {
|
||||
|
|
@ -446,7 +446,7 @@ Final content`
|
|||
console.log("Task completed:", id)
|
||||
}
|
||||
}
|
||||
api.on("taskCompleted", taskCompletedHandler)
|
||||
api.on(RooCodeEventName.TaskCompleted, taskCompletedHandler)
|
||||
|
||||
let taskId: string
|
||||
try {
|
||||
|
|
@ -503,9 +503,9 @@ Assume the file exists and you can modify it directly.`,
|
|||
console.log("Test passed! search_and_replace tool executed and replaced multiple matches successfully")
|
||||
} finally {
|
||||
// Clean up
|
||||
api.off("message", messageHandler)
|
||||
api.off("taskStarted", taskStartedHandler)
|
||||
api.off("taskCompleted", taskCompletedHandler)
|
||||
api.off(RooCodeEventName.Message, messageHandler)
|
||||
api.off(RooCodeEventName.TaskStarted, taskStartedHandler)
|
||||
api.off(RooCodeEventName.TaskCompleted, taskCompletedHandler)
|
||||
}
|
||||
})
|
||||
|
||||
|
|
@ -549,7 +549,7 @@ Assume the file exists and you can modify it directly.`,
|
|||
}
|
||||
}
|
||||
}
|
||||
api.on("message", messageHandler)
|
||||
api.on(RooCodeEventName.Message, messageHandler)
|
||||
|
||||
// Listen for task events
|
||||
const taskStartedHandler = (id: string) => {
|
||||
|
|
@ -558,7 +558,7 @@ Assume the file exists and you can modify it directly.`,
|
|||
console.log("Task started:", id)
|
||||
}
|
||||
}
|
||||
api.on("taskStarted", taskStartedHandler)
|
||||
api.on(RooCodeEventName.TaskStarted, taskStartedHandler)
|
||||
|
||||
const taskCompletedHandler = (id: string) => {
|
||||
if (id === taskId) {
|
||||
|
|
@ -566,7 +566,7 @@ Assume the file exists and you can modify it directly.`,
|
|||
console.log("Task completed:", id)
|
||||
}
|
||||
}
|
||||
api.on("taskCompleted", taskCompletedHandler)
|
||||
api.on(RooCodeEventName.TaskCompleted, taskCompletedHandler)
|
||||
|
||||
let taskId: string
|
||||
try {
|
||||
|
|
@ -623,9 +623,9 @@ Assume the file exists and you can modify it directly.`,
|
|||
console.log("Test passed! search_and_replace tool executed and handled no matches correctly")
|
||||
} finally {
|
||||
// Clean up
|
||||
api.off("message", messageHandler)
|
||||
api.off("taskStarted", taskStartedHandler)
|
||||
api.off("taskCompleted", taskCompletedHandler)
|
||||
api.off(RooCodeEventName.Message, messageHandler)
|
||||
api.off(RooCodeEventName.TaskStarted, taskStartedHandler)
|
||||
api.off(RooCodeEventName.TaskCompleted, taskCompletedHandler)
|
||||
}
|
||||
})
|
||||
})
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@ import * as fs from "fs/promises"
|
|||
import * as path from "path"
|
||||
import * as vscode from "vscode"
|
||||
|
||||
import type { ClineMessage } from "@roo-code/types"
|
||||
import { RooCodeEventName, type ClineMessage } from "@roo-code/types"
|
||||
|
||||
import { waitFor, sleep } from "../utils"
|
||||
import { setDefaultSuiteTimeout } from "../test-utils"
|
||||
|
|
@ -323,7 +323,7 @@ The search should find matches across different file types and provide context f
|
|||
}
|
||||
}
|
||||
}
|
||||
api.on("message", messageHandler)
|
||||
api.on(RooCodeEventName.Message, messageHandler)
|
||||
|
||||
// Listen for task completion
|
||||
const taskCompletedHandler = (id: string) => {
|
||||
|
|
@ -331,7 +331,7 @@ The search should find matches across different file types and provide context f
|
|||
taskCompleted = true
|
||||
}
|
||||
}
|
||||
api.on("taskCompleted", taskCompletedHandler)
|
||||
api.on(RooCodeEventName.TaskCompleted, taskCompletedHandler)
|
||||
|
||||
let taskId: string
|
||||
try {
|
||||
|
|
@ -397,8 +397,8 @@ The search should find matches across different file types and provide context f
|
|||
console.log("Test passed! Function definitions found successfully with validated results")
|
||||
} finally {
|
||||
// Clean up
|
||||
api.off("message", messageHandler)
|
||||
api.off("taskCompleted", taskCompletedHandler)
|
||||
api.off(RooCodeEventName.Message, messageHandler)
|
||||
api.off(RooCodeEventName.TaskCompleted, taskCompletedHandler)
|
||||
}
|
||||
})
|
||||
|
||||
|
|
@ -421,7 +421,7 @@ The search should find matches across different file types and provide context f
|
|||
}
|
||||
}
|
||||
}
|
||||
api.on("message", messageHandler)
|
||||
api.on(RooCodeEventName.Message, messageHandler)
|
||||
|
||||
// Listen for task completion
|
||||
const taskCompletedHandler = (id: string) => {
|
||||
|
|
@ -429,7 +429,7 @@ The search should find matches across different file types and provide context f
|
|||
taskCompleted = true
|
||||
}
|
||||
}
|
||||
api.on("taskCompleted", taskCompletedHandler)
|
||||
api.on(RooCodeEventName.TaskCompleted, taskCompletedHandler)
|
||||
|
||||
let taskId: string
|
||||
try {
|
||||
|
|
@ -464,8 +464,8 @@ The search should find matches across different file types and provide context f
|
|||
console.log("Test passed! TODO comments found successfully")
|
||||
} finally {
|
||||
// Clean up
|
||||
api.off("message", messageHandler)
|
||||
api.off("taskCompleted", taskCompletedHandler)
|
||||
api.off(RooCodeEventName.Message, messageHandler)
|
||||
api.off(RooCodeEventName.TaskCompleted, taskCompletedHandler)
|
||||
}
|
||||
})
|
||||
|
||||
|
|
@ -488,7 +488,7 @@ The search should find matches across different file types and provide context f
|
|||
}
|
||||
}
|
||||
}
|
||||
api.on("message", messageHandler)
|
||||
api.on(RooCodeEventName.Message, messageHandler)
|
||||
|
||||
// Listen for task completion
|
||||
const taskCompletedHandler = (id: string) => {
|
||||
|
|
@ -496,7 +496,7 @@ The search should find matches across different file types and provide context f
|
|||
taskCompleted = true
|
||||
}
|
||||
}
|
||||
api.on("taskCompleted", taskCompletedHandler)
|
||||
api.on(RooCodeEventName.TaskCompleted, taskCompletedHandler)
|
||||
|
||||
let taskId: string
|
||||
try {
|
||||
|
|
@ -530,8 +530,8 @@ The search should find matches across different file types and provide context f
|
|||
console.log("Test passed! TypeScript interfaces found with file pattern filter")
|
||||
} finally {
|
||||
// Clean up
|
||||
api.off("message", messageHandler)
|
||||
api.off("taskCompleted", taskCompletedHandler)
|
||||
api.off(RooCodeEventName.Message, messageHandler)
|
||||
api.off(RooCodeEventName.TaskCompleted, taskCompletedHandler)
|
||||
}
|
||||
})
|
||||
|
||||
|
|
@ -554,7 +554,7 @@ The search should find matches across different file types and provide context f
|
|||
}
|
||||
}
|
||||
}
|
||||
api.on("message", messageHandler)
|
||||
api.on(RooCodeEventName.Message, messageHandler)
|
||||
|
||||
// Listen for task completion
|
||||
const taskCompletedHandler = (id: string) => {
|
||||
|
|
@ -562,7 +562,7 @@ The search should find matches across different file types and provide context f
|
|||
taskCompleted = true
|
||||
}
|
||||
}
|
||||
api.on("taskCompleted", taskCompletedHandler)
|
||||
api.on(RooCodeEventName.TaskCompleted, taskCompletedHandler)
|
||||
|
||||
let taskId: string
|
||||
try {
|
||||
|
|
@ -598,8 +598,8 @@ The search should find matches across different file types and provide context f
|
|||
console.log("Test passed! JSON configuration keys found successfully")
|
||||
} finally {
|
||||
// Clean up
|
||||
api.off("message", messageHandler)
|
||||
api.off("taskCompleted", taskCompletedHandler)
|
||||
api.off(RooCodeEventName.Message, messageHandler)
|
||||
api.off(RooCodeEventName.TaskCompleted, taskCompletedHandler)
|
||||
}
|
||||
})
|
||||
|
||||
|
|
@ -622,7 +622,7 @@ The search should find matches across different file types and provide context f
|
|||
}
|
||||
}
|
||||
}
|
||||
api.on("message", messageHandler)
|
||||
api.on(RooCodeEventName.Message, messageHandler)
|
||||
|
||||
// Listen for task completion
|
||||
const taskCompletedHandler = (id: string) => {
|
||||
|
|
@ -630,7 +630,7 @@ The search should find matches across different file types and provide context f
|
|||
taskCompleted = true
|
||||
}
|
||||
}
|
||||
api.on("taskCompleted", taskCompletedHandler)
|
||||
api.on(RooCodeEventName.TaskCompleted, taskCompletedHandler)
|
||||
|
||||
let taskId: string
|
||||
try {
|
||||
|
|
@ -663,8 +663,8 @@ The search should find matches across different file types and provide context f
|
|||
console.log("Test passed! Nested directory search completed successfully")
|
||||
} finally {
|
||||
// Clean up
|
||||
api.off("message", messageHandler)
|
||||
api.off("taskCompleted", taskCompletedHandler)
|
||||
api.off(RooCodeEventName.Message, messageHandler)
|
||||
api.off(RooCodeEventName.TaskCompleted, taskCompletedHandler)
|
||||
}
|
||||
})
|
||||
|
||||
|
|
@ -690,7 +690,7 @@ The search should find matches across different file types and provide context f
|
|||
}
|
||||
}
|
||||
}
|
||||
api.on("message", messageHandler)
|
||||
api.on(RooCodeEventName.Message, messageHandler)
|
||||
|
||||
// Listen for task completion
|
||||
const taskCompletedHandler = (id: string) => {
|
||||
|
|
@ -698,7 +698,7 @@ The search should find matches across different file types and provide context f
|
|||
taskCompleted = true
|
||||
}
|
||||
}
|
||||
api.on("taskCompleted", taskCompletedHandler)
|
||||
api.on(RooCodeEventName.TaskCompleted, taskCompletedHandler)
|
||||
|
||||
let taskId: string
|
||||
try {
|
||||
|
|
@ -731,8 +731,8 @@ The search should find matches across different file types and provide context f
|
|||
console.log("Test passed! Complex regex pattern search completed successfully")
|
||||
} finally {
|
||||
// Clean up
|
||||
api.off("message", messageHandler)
|
||||
api.off("taskCompleted", taskCompletedHandler)
|
||||
api.off(RooCodeEventName.Message, messageHandler)
|
||||
api.off(RooCodeEventName.TaskCompleted, taskCompletedHandler)
|
||||
}
|
||||
})
|
||||
|
||||
|
|
@ -775,7 +775,7 @@ The search should find matches across different file types and provide context f
|
|||
console.log("AI completion message:", message.text?.substring(0, 300))
|
||||
}
|
||||
}
|
||||
api.on("message", messageHandler)
|
||||
api.on(RooCodeEventName.Message, messageHandler)
|
||||
|
||||
// Listen for task completion
|
||||
const taskCompletedHandler = (id: string) => {
|
||||
|
|
@ -783,7 +783,7 @@ The search should find matches across different file types and provide context f
|
|||
taskCompleted = true
|
||||
}
|
||||
}
|
||||
api.on("taskCompleted", taskCompletedHandler)
|
||||
api.on(RooCodeEventName.TaskCompleted, taskCompletedHandler)
|
||||
|
||||
let taskId: string
|
||||
try {
|
||||
|
|
@ -859,8 +859,8 @@ The search should find matches across different file types and provide context f
|
|||
console.log("Test passed! No-match scenario handled correctly")
|
||||
} finally {
|
||||
// Clean up
|
||||
api.off("message", messageHandler)
|
||||
api.off("taskCompleted", taskCompletedHandler)
|
||||
api.off(RooCodeEventName.Message, messageHandler)
|
||||
api.off(RooCodeEventName.TaskCompleted, taskCompletedHandler)
|
||||
}
|
||||
})
|
||||
|
||||
|
|
@ -883,7 +883,7 @@ The search should find matches across different file types and provide context f
|
|||
}
|
||||
}
|
||||
}
|
||||
api.on("message", messageHandler)
|
||||
api.on(RooCodeEventName.Message, messageHandler)
|
||||
|
||||
// Listen for task completion
|
||||
const taskCompletedHandler = (id: string) => {
|
||||
|
|
@ -891,7 +891,7 @@ The search should find matches across different file types and provide context f
|
|||
taskCompleted = true
|
||||
}
|
||||
}
|
||||
api.on("taskCompleted", taskCompletedHandler)
|
||||
api.on(RooCodeEventName.TaskCompleted, taskCompletedHandler)
|
||||
|
||||
let taskId: string
|
||||
try {
|
||||
|
|
@ -927,8 +927,8 @@ The search should find matches across different file types and provide context f
|
|||
console.log("Test passed! Class definitions and async methods found successfully")
|
||||
} finally {
|
||||
// Clean up
|
||||
api.off("message", messageHandler)
|
||||
api.off("taskCompleted", taskCompletedHandler)
|
||||
api.off(RooCodeEventName.Message, messageHandler)
|
||||
api.off(RooCodeEventName.TaskCompleted, taskCompletedHandler)
|
||||
}
|
||||
})
|
||||
})
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@ import * as path from "path"
|
|||
import * as os from "os"
|
||||
import * as vscode from "vscode"
|
||||
|
||||
import type { ClineMessage } from "@roo-code/types"
|
||||
import { RooCodeEventName, type ClineMessage } from "@roo-code/types"
|
||||
|
||||
import { waitFor, sleep } from "../utils"
|
||||
import { setDefaultSuiteTimeout } from "../test-utils"
|
||||
|
|
@ -167,7 +167,7 @@ suite.skip("Roo Code use_mcp_tool Tool", function () {
|
|||
console.error("Error:", message.text)
|
||||
}
|
||||
}
|
||||
api.on("message", messageHandler)
|
||||
api.on(RooCodeEventName.Message, messageHandler)
|
||||
|
||||
// Listen for task events
|
||||
const taskStartedHandler = (id: string) => {
|
||||
|
|
@ -176,7 +176,7 @@ suite.skip("Roo Code use_mcp_tool Tool", function () {
|
|||
console.log("Task started:", id)
|
||||
}
|
||||
}
|
||||
api.on("taskStarted", taskStartedHandler)
|
||||
api.on(RooCodeEventName.TaskStarted, taskStartedHandler)
|
||||
|
||||
const taskCompletedHandler = (id: string) => {
|
||||
if (id === taskId) {
|
||||
|
|
@ -184,7 +184,7 @@ suite.skip("Roo Code use_mcp_tool Tool", function () {
|
|||
console.log("Task completed:", id)
|
||||
}
|
||||
}
|
||||
api.on("taskCompleted", taskCompletedHandler)
|
||||
api.on(RooCodeEventName.TaskCompleted, taskCompletedHandler)
|
||||
await sleep(2000) // Wait for Roo Code to fully initialize
|
||||
|
||||
// Trigger MCP server detection by opening and modifying the file
|
||||
|
|
@ -284,9 +284,9 @@ suite.skip("Roo Code use_mcp_tool Tool", function () {
|
|||
console.log("Test passed! MCP read_file tool used successfully and task completed")
|
||||
} finally {
|
||||
// Clean up
|
||||
api.off("message", messageHandler)
|
||||
api.off("taskStarted", taskStartedHandler)
|
||||
api.off("taskCompleted", taskCompletedHandler)
|
||||
api.off(RooCodeEventName.Message, messageHandler)
|
||||
api.off(RooCodeEventName.TaskStarted, taskStartedHandler)
|
||||
api.off(RooCodeEventName.TaskCompleted, taskCompletedHandler)
|
||||
}
|
||||
})
|
||||
|
||||
|
|
@ -344,7 +344,7 @@ suite.skip("Roo Code use_mcp_tool Tool", function () {
|
|||
console.error("Error:", message.text)
|
||||
}
|
||||
}
|
||||
api.on("message", messageHandler)
|
||||
api.on(RooCodeEventName.Message, messageHandler)
|
||||
|
||||
// Listen for task completion
|
||||
const taskCompletedHandler = (id: string) => {
|
||||
|
|
@ -352,7 +352,7 @@ suite.skip("Roo Code use_mcp_tool Tool", function () {
|
|||
_taskCompleted = true
|
||||
}
|
||||
}
|
||||
api.on("taskCompleted", taskCompletedHandler)
|
||||
api.on(RooCodeEventName.TaskCompleted, taskCompletedHandler)
|
||||
|
||||
let taskId: string
|
||||
try {
|
||||
|
|
@ -413,8 +413,8 @@ suite.skip("Roo Code use_mcp_tool Tool", function () {
|
|||
console.log("Test passed! MCP write_file tool used successfully and task completed")
|
||||
} finally {
|
||||
// Clean up
|
||||
api.off("message", messageHandler)
|
||||
api.off("taskCompleted", taskCompletedHandler)
|
||||
api.off(RooCodeEventName.Message, messageHandler)
|
||||
api.off(RooCodeEventName.TaskCompleted, taskCompletedHandler)
|
||||
}
|
||||
})
|
||||
|
||||
|
|
@ -472,7 +472,7 @@ suite.skip("Roo Code use_mcp_tool Tool", function () {
|
|||
console.error("Error:", message.text)
|
||||
}
|
||||
}
|
||||
api.on("message", messageHandler)
|
||||
api.on(RooCodeEventName.Message, messageHandler)
|
||||
|
||||
// Listen for task completion
|
||||
const taskCompletedHandler = (id: string) => {
|
||||
|
|
@ -480,7 +480,7 @@ suite.skip("Roo Code use_mcp_tool Tool", function () {
|
|||
_taskCompleted = true
|
||||
}
|
||||
}
|
||||
api.on("taskCompleted", taskCompletedHandler)
|
||||
api.on(RooCodeEventName.TaskCompleted, taskCompletedHandler)
|
||||
|
||||
let taskId: string
|
||||
try {
|
||||
|
|
@ -552,8 +552,8 @@ suite.skip("Roo Code use_mcp_tool Tool", function () {
|
|||
console.log("Test passed! MCP list_directory tool used successfully and task completed")
|
||||
} finally {
|
||||
// Clean up
|
||||
api.off("message", messageHandler)
|
||||
api.off("taskCompleted", taskCompletedHandler)
|
||||
api.off(RooCodeEventName.Message, messageHandler)
|
||||
api.off(RooCodeEventName.TaskCompleted, taskCompletedHandler)
|
||||
}
|
||||
})
|
||||
|
||||
|
|
@ -611,7 +611,7 @@ suite.skip("Roo Code use_mcp_tool Tool", function () {
|
|||
console.error("Error:", message.text)
|
||||
}
|
||||
}
|
||||
api.on("message", messageHandler)
|
||||
api.on(RooCodeEventName.Message, messageHandler)
|
||||
|
||||
// Listen for task completion
|
||||
const taskCompletedHandler = (id: string) => {
|
||||
|
|
@ -619,7 +619,7 @@ suite.skip("Roo Code use_mcp_tool Tool", function () {
|
|||
_taskCompleted = true
|
||||
}
|
||||
}
|
||||
api.on("taskCompleted", taskCompletedHandler)
|
||||
api.on(RooCodeEventName.TaskCompleted, taskCompletedHandler)
|
||||
|
||||
let taskId: string
|
||||
try {
|
||||
|
|
@ -691,8 +691,8 @@ suite.skip("Roo Code use_mcp_tool Tool", function () {
|
|||
console.log("Test passed! MCP directory_tree tool used successfully and task completed")
|
||||
} finally {
|
||||
// Clean up
|
||||
api.off("message", messageHandler)
|
||||
api.off("taskCompleted", taskCompletedHandler)
|
||||
api.off(RooCodeEventName.Message, messageHandler)
|
||||
api.off(RooCodeEventName.TaskCompleted, taskCompletedHandler)
|
||||
}
|
||||
})
|
||||
|
||||
|
|
@ -730,7 +730,7 @@ suite.skip("Roo Code use_mcp_tool Tool", function () {
|
|||
console.log("Attempt completion called:", message.text?.substring(0, 200))
|
||||
}
|
||||
}
|
||||
api.on("message", messageHandler)
|
||||
api.on(RooCodeEventName.Message, messageHandler)
|
||||
|
||||
// Listen for task completion
|
||||
const taskCompletedHandler = (id: string) => {
|
||||
|
|
@ -738,7 +738,7 @@ suite.skip("Roo Code use_mcp_tool Tool", function () {
|
|||
_taskCompleted = true
|
||||
}
|
||||
}
|
||||
api.on("taskCompleted", taskCompletedHandler)
|
||||
api.on(RooCodeEventName.TaskCompleted, taskCompletedHandler)
|
||||
|
||||
let taskId: string
|
||||
try {
|
||||
|
|
@ -762,8 +762,8 @@ suite.skip("Roo Code use_mcp_tool Tool", function () {
|
|||
console.log("Test passed! MCP error handling verified and task completed")
|
||||
} finally {
|
||||
// Clean up
|
||||
api.off("message", messageHandler)
|
||||
api.off("taskCompleted", taskCompletedHandler)
|
||||
api.off(RooCodeEventName.Message, messageHandler)
|
||||
api.off(RooCodeEventName.TaskCompleted, taskCompletedHandler)
|
||||
}
|
||||
})
|
||||
|
||||
|
|
@ -832,7 +832,7 @@ suite.skip("Roo Code use_mcp_tool Tool", function () {
|
|||
console.error("Error:", message.text)
|
||||
}
|
||||
}
|
||||
api.on("message", messageHandler)
|
||||
api.on(RooCodeEventName.Message, messageHandler)
|
||||
|
||||
// Listen for task completion
|
||||
const taskCompletedHandler = (id: string) => {
|
||||
|
|
@ -840,7 +840,7 @@ suite.skip("Roo Code use_mcp_tool Tool", function () {
|
|||
_taskCompleted = true
|
||||
}
|
||||
}
|
||||
api.on("taskCompleted", taskCompletedHandler)
|
||||
api.on(RooCodeEventName.TaskCompleted, taskCompletedHandler)
|
||||
|
||||
let taskId: string
|
||||
try {
|
||||
|
|
@ -921,8 +921,8 @@ suite.skip("Roo Code use_mcp_tool Tool", function () {
|
|||
console.log("Test passed! MCP message format validation successful and task completed")
|
||||
} finally {
|
||||
// Clean up
|
||||
api.off("message", messageHandler)
|
||||
api.off("taskCompleted", taskCompletedHandler)
|
||||
api.off(RooCodeEventName.Message, messageHandler)
|
||||
api.off(RooCodeEventName.TaskCompleted, taskCompletedHandler)
|
||||
}
|
||||
})
|
||||
})
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@ import * as fs from "fs/promises"
|
|||
import * as path from "path"
|
||||
import * as os from "os"
|
||||
|
||||
import type { ClineMessage } from "@roo-code/types"
|
||||
import { RooCodeEventName, type ClineMessage } from "@roo-code/types"
|
||||
|
||||
import { waitFor, sleep } from "../utils"
|
||||
import { setDefaultSuiteTimeout } from "../test-utils"
|
||||
|
|
@ -110,7 +110,7 @@ suite("Roo Code write_to_file Tool", function () {
|
|||
console.log("AI response:", message.text?.substring(0, 200))
|
||||
}
|
||||
}
|
||||
api.on("message", messageHandler)
|
||||
api.on(RooCodeEventName.Message, messageHandler)
|
||||
|
||||
// Listen for task events
|
||||
const taskStartedHandler = (id: string) => {
|
||||
|
|
@ -119,7 +119,7 @@ suite("Roo Code write_to_file Tool", function () {
|
|||
console.log("Task started:", id)
|
||||
}
|
||||
}
|
||||
api.on("taskStarted", taskStartedHandler)
|
||||
api.on(RooCodeEventName.TaskStarted, taskStartedHandler)
|
||||
|
||||
const taskCompletedHandler = (id: string) => {
|
||||
if (id === taskId) {
|
||||
|
|
@ -127,7 +127,7 @@ suite("Roo Code write_to_file Tool", function () {
|
|||
console.log("Task completed:", id)
|
||||
}
|
||||
}
|
||||
api.on("taskCompleted", taskCompletedHandler)
|
||||
api.on(RooCodeEventName.TaskCompleted, taskCompletedHandler)
|
||||
|
||||
let taskId: string
|
||||
try {
|
||||
|
|
@ -259,9 +259,9 @@ suite("Roo Code write_to_file Tool", function () {
|
|||
console.log("write_to_file tool was properly executed")
|
||||
} finally {
|
||||
// Clean up
|
||||
api.off("message", messageHandler)
|
||||
api.off("taskStarted", taskStartedHandler)
|
||||
api.off("taskCompleted", taskCompletedHandler)
|
||||
api.off(RooCodeEventName.Message, messageHandler)
|
||||
api.off(RooCodeEventName.TaskStarted, taskStartedHandler)
|
||||
api.off(RooCodeEventName.TaskCompleted, taskCompletedHandler)
|
||||
}
|
||||
})
|
||||
|
||||
|
|
@ -302,7 +302,7 @@ suite("Roo Code write_to_file Tool", function () {
|
|||
console.log("Tool request:", message.text?.substring(0, 200))
|
||||
}
|
||||
}
|
||||
api.on("message", messageHandler)
|
||||
api.on(RooCodeEventName.Message, messageHandler)
|
||||
|
||||
// Listen for task events
|
||||
const taskStartedHandler = (id: string) => {
|
||||
|
|
@ -311,7 +311,7 @@ suite("Roo Code write_to_file Tool", function () {
|
|||
console.log("Task started:", id)
|
||||
}
|
||||
}
|
||||
api.on("taskStarted", taskStartedHandler)
|
||||
api.on(RooCodeEventName.TaskStarted, taskStartedHandler)
|
||||
|
||||
const taskCompletedHandler = (id: string) => {
|
||||
if (id === taskId) {
|
||||
|
|
@ -319,7 +319,7 @@ suite("Roo Code write_to_file Tool", function () {
|
|||
console.log("Task completed:", id)
|
||||
}
|
||||
}
|
||||
api.on("taskCompleted", taskCompletedHandler)
|
||||
api.on(RooCodeEventName.TaskCompleted, taskCompletedHandler)
|
||||
|
||||
let taskId: string
|
||||
try {
|
||||
|
|
@ -440,9 +440,9 @@ suite("Roo Code write_to_file Tool", function () {
|
|||
console.log("write_to_file tool was properly executed")
|
||||
} finally {
|
||||
// Clean up
|
||||
api.off("message", messageHandler)
|
||||
api.off("taskStarted", taskStartedHandler)
|
||||
api.off("taskCompleted", taskCompletedHandler)
|
||||
api.off(RooCodeEventName.Message, messageHandler)
|
||||
api.off(RooCodeEventName.TaskStarted, taskStartedHandler)
|
||||
api.off(RooCodeEventName.TaskCompleted, taskCompletedHandler)
|
||||
}
|
||||
})
|
||||
})
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
import type { RooCodeAPI } from "@roo-code/types"
|
||||
import { RooCodeEventName, type RooCodeAPI } from "@roo-code/types"
|
||||
|
||||
type WaitForOptions = {
|
||||
timeout?: number
|
||||
|
|
@ -46,7 +46,7 @@ type WaitUntilAbortedOptions = WaitForOptions & {
|
|||
|
||||
export const waitUntilAborted = async ({ api, taskId, ...options }: WaitUntilAbortedOptions) => {
|
||||
const set = new Set<string>()
|
||||
api.on("taskAborted", (taskId) => set.add(taskId))
|
||||
api.on(RooCodeEventName.TaskAborted, (taskId) => set.add(taskId))
|
||||
await waitFor(() => set.has(taskId), options)
|
||||
}
|
||||
|
||||
|
|
@ -57,7 +57,7 @@ type WaitUntilCompletedOptions = WaitForOptions & {
|
|||
|
||||
export const waitUntilCompleted = async ({ api, taskId, ...options }: WaitUntilCompletedOptions) => {
|
||||
const set = new Set<string>()
|
||||
api.on("taskCompleted", (taskId) => set.add(taskId))
|
||||
api.on(RooCodeEventName.TaskCompleted, (taskId) => set.add(taskId))
|
||||
await waitFor(() => set.has(taskId), options)
|
||||
}
|
||||
|
||||
|
|
|
|||
122
packages/cloud/src/CloudAPI.ts
Normal file
122
packages/cloud/src/CloudAPI.ts
Normal file
|
|
@ -0,0 +1,122 @@
|
|||
import { type ShareVisibility, type ShareResponse, shareResponseSchema } from "@roo-code/types"
|
||||
|
||||
import { getRooCodeApiUrl } from "./config"
|
||||
import type { AuthService } from "./auth"
|
||||
import { getUserAgent } from "./utils"
|
||||
import { AuthenticationError, CloudAPIError, NetworkError, TaskNotFoundError } from "./errors"
|
||||
|
||||
interface CloudAPIRequestOptions extends Omit<RequestInit, "headers"> {
|
||||
timeout?: number
|
||||
headers?: Record<string, string>
|
||||
}
|
||||
|
||||
export class CloudAPI {
|
||||
private authService: AuthService
|
||||
private log: (...args: unknown[]) => void
|
||||
private baseUrl: string
|
||||
|
||||
constructor(authService: AuthService, log?: (...args: unknown[]) => void) {
|
||||
this.authService = authService
|
||||
this.log = log || console.log
|
||||
this.baseUrl = getRooCodeApiUrl()
|
||||
}
|
||||
|
||||
private async request<T>(
|
||||
endpoint: string,
|
||||
options: CloudAPIRequestOptions & {
|
||||
parseResponse?: (data: unknown) => T
|
||||
} = {},
|
||||
): Promise<T> {
|
||||
const { timeout = 10000, parseResponse, headers = {}, ...fetchOptions } = options
|
||||
|
||||
const sessionToken = this.authService.getSessionToken()
|
||||
|
||||
if (!sessionToken) {
|
||||
throw new AuthenticationError()
|
||||
}
|
||||
|
||||
const url = `${this.baseUrl}${endpoint}`
|
||||
|
||||
const requestHeaders = {
|
||||
"Content-Type": "application/json",
|
||||
Authorization: `Bearer ${sessionToken}`,
|
||||
"User-Agent": getUserAgent(),
|
||||
...headers,
|
||||
}
|
||||
|
||||
try {
|
||||
const response = await fetch(url, {
|
||||
...fetchOptions,
|
||||
headers: requestHeaders,
|
||||
signal: AbortSignal.timeout(timeout),
|
||||
})
|
||||
|
||||
if (!response.ok) {
|
||||
await this.handleErrorResponse(response, endpoint)
|
||||
}
|
||||
|
||||
const data = await response.json()
|
||||
|
||||
if (parseResponse) {
|
||||
return parseResponse(data)
|
||||
}
|
||||
|
||||
return data as T
|
||||
} catch (error) {
|
||||
if (error instanceof TypeError && error.message.includes("fetch")) {
|
||||
throw new NetworkError(`Network error while calling ${endpoint}`)
|
||||
}
|
||||
|
||||
if (error instanceof CloudAPIError) {
|
||||
throw error
|
||||
}
|
||||
|
||||
if (error instanceof Error && error.name === "AbortError") {
|
||||
throw new CloudAPIError(`Request to ${endpoint} timed out`, undefined, undefined)
|
||||
}
|
||||
|
||||
throw new CloudAPIError(
|
||||
`Unexpected error while calling ${endpoint}: ${error instanceof Error ? error.message : String(error)}`,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private async handleErrorResponse(response: Response, endpoint: string): Promise<never> {
|
||||
let responseBody: unknown
|
||||
|
||||
try {
|
||||
responseBody = await response.json()
|
||||
} catch {
|
||||
responseBody = await response.text()
|
||||
}
|
||||
|
||||
switch (response.status) {
|
||||
case 401:
|
||||
throw new AuthenticationError()
|
||||
case 404:
|
||||
if (endpoint.includes("/share")) {
|
||||
throw new TaskNotFoundError()
|
||||
}
|
||||
throw new CloudAPIError(`Resource not found: ${endpoint}`, 404, responseBody)
|
||||
default:
|
||||
throw new CloudAPIError(
|
||||
`HTTP ${response.status}: ${response.statusText}`,
|
||||
response.status,
|
||||
responseBody,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
async shareTask(taskId: string, visibility: ShareVisibility = "organization"): Promise<ShareResponse> {
|
||||
this.log(`[CloudAPI] Sharing task ${taskId} with visibility: ${visibility}`)
|
||||
|
||||
const response = await this.request("/api/extension/share", {
|
||||
method: "POST",
|
||||
body: JSON.stringify({ taskId, visibility }),
|
||||
parseResponse: (data) => shareResponseSchema.parse(data),
|
||||
})
|
||||
|
||||
this.log("[CloudAPI] Share response:", response)
|
||||
return response
|
||||
}
|
||||
}
|
||||
|
|
@ -12,13 +12,15 @@ import type {
|
|||
import { TelemetryService } from "@roo-code/telemetry"
|
||||
|
||||
import { CloudServiceEvents } from "./types"
|
||||
import { TaskNotFoundError } from "./errors"
|
||||
import type { AuthService } from "./auth"
|
||||
import { WebAuthService, StaticTokenAuthService } from "./auth"
|
||||
import type { SettingsService } from "./SettingsService"
|
||||
import { CloudSettingsService } from "./CloudSettingsService"
|
||||
import { StaticSettingsService } from "./StaticSettingsService"
|
||||
import { TelemetryClient } from "./TelemetryClient"
|
||||
import { ShareService, TaskNotFoundError } from "./ShareService"
|
||||
import { CloudShareService } from "./CloudShareService"
|
||||
import { CloudAPI } from "./CloudAPI"
|
||||
|
||||
type AuthStateChangedPayload = CloudServiceEvents["auth-state-changed"][0]
|
||||
type AuthUserInfoPayload = CloudServiceEvents["user-info"][0]
|
||||
|
|
@ -34,7 +36,8 @@ export class CloudService extends EventEmitter<CloudServiceEvents> implements vs
|
|||
private settingsListener: (data: SettingsPayload) => void
|
||||
private settingsService: SettingsService | null = null
|
||||
private telemetryClient: TelemetryClient | null = null
|
||||
private shareService: ShareService | null = null
|
||||
private shareService: CloudShareService | null = null
|
||||
private cloudAPI: CloudAPI | null = null
|
||||
private isInitialized = false
|
||||
private log: (...args: unknown[]) => void
|
||||
|
||||
|
|
@ -87,8 +90,9 @@ export class CloudService extends EventEmitter<CloudServiceEvents> implements vs
|
|||
this.settingsService = cloudSettingsService
|
||||
}
|
||||
|
||||
this.cloudAPI = new CloudAPI(this.authService, this.log)
|
||||
this.telemetryClient = new TelemetryClient(this.authService, this.settingsService)
|
||||
this.shareService = new ShareService(this.authService, this.settingsService, this.log)
|
||||
this.shareService = new CloudShareService(this.cloudAPI, this.settingsService, this.log)
|
||||
|
||||
try {
|
||||
TelemetryService.instance.register(this.telemetryClient)
|
||||
|
|
@ -209,7 +213,7 @@ export class CloudService extends EventEmitter<CloudServiceEvents> implements vs
|
|||
return await this.shareService!.shareTask(taskId, visibility)
|
||||
} catch (error) {
|
||||
if (error instanceof TaskNotFoundError && clineMessages) {
|
||||
// Backfill messages and retry
|
||||
// Backfill messages and retry.
|
||||
await this.telemetryClient!.backfillMessages(clineMessages, taskId)
|
||||
return await this.shareService!.shareTask(taskId, visibility)
|
||||
}
|
||||
|
|
@ -229,6 +233,7 @@ export class CloudService extends EventEmitter<CloudServiceEvents> implements vs
|
|||
this.authService.off("auth-state-changed", this.authStateListener)
|
||||
this.authService.off("user-info", this.authUserInfoListener)
|
||||
}
|
||||
|
||||
if (this.settingsService) {
|
||||
if (this.settingsService instanceof CloudSettingsService) {
|
||||
this.settingsService.off("settings-updated", this.settingsListener)
|
||||
|
|
|
|||
|
|
@ -8,7 +8,7 @@ import {
|
|||
organizationSettingsSchema,
|
||||
} from "@roo-code/types"
|
||||
|
||||
import { getRooCodeApiUrl } from "./Config"
|
||||
import { getRooCodeApiUrl } from "./config"
|
||||
import type { AuthService, AuthState } from "./auth"
|
||||
import { RefreshTimer } from "./RefreshTimer"
|
||||
import type { SettingsService } from "./SettingsService"
|
||||
|
|
|
|||
43
packages/cloud/src/CloudShareService.ts
Normal file
43
packages/cloud/src/CloudShareService.ts
Normal file
|
|
@ -0,0 +1,43 @@
|
|||
import * as vscode from "vscode"
|
||||
|
||||
import type { ShareResponse, ShareVisibility } from "@roo-code/types"
|
||||
|
||||
import type { CloudAPI } from "./CloudAPI"
|
||||
import type { SettingsService } from "./SettingsService"
|
||||
|
||||
export class CloudShareService {
|
||||
private cloudAPI: CloudAPI
|
||||
private settingsService: SettingsService
|
||||
private log: (...args: unknown[]) => void
|
||||
|
||||
constructor(cloudAPI: CloudAPI, settingsService: SettingsService, log?: (...args: unknown[]) => void) {
|
||||
this.cloudAPI = cloudAPI
|
||||
this.settingsService = settingsService
|
||||
this.log = log || console.log
|
||||
}
|
||||
|
||||
async shareTask(taskId: string, visibility: ShareVisibility = "organization"): Promise<ShareResponse> {
|
||||
try {
|
||||
const response = await this.cloudAPI.shareTask(taskId, visibility)
|
||||
|
||||
if (response.success && response.shareUrl) {
|
||||
// Copy to clipboard.
|
||||
await vscode.env.clipboard.writeText(response.shareUrl)
|
||||
}
|
||||
|
||||
return response
|
||||
} catch (error) {
|
||||
this.log("[ShareService] Error sharing task:", error)
|
||||
throw error
|
||||
}
|
||||
}
|
||||
|
||||
async canShareTask(): Promise<boolean> {
|
||||
try {
|
||||
return !!this.settingsService.getSettings()?.cloudSettings?.enableTaskSharing
|
||||
} catch (error) {
|
||||
this.log("[ShareService] Error checking if task can be shared:", error)
|
||||
return false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,88 +0,0 @@
|
|||
import * as vscode from "vscode"
|
||||
|
||||
import { shareResponseSchema } from "@roo-code/types"
|
||||
import { getRooCodeApiUrl } from "./Config"
|
||||
import type { AuthService } from "./auth"
|
||||
import type { SettingsService } from "./SettingsService"
|
||||
import { getUserAgent } from "./utils"
|
||||
|
||||
export type ShareVisibility = "organization" | "public"
|
||||
|
||||
export class TaskNotFoundError extends Error {
|
||||
constructor(taskId?: string) {
|
||||
super(taskId ? `Task '${taskId}' not found` : "Task not found")
|
||||
Object.setPrototypeOf(this, TaskNotFoundError.prototype)
|
||||
}
|
||||
}
|
||||
|
||||
export class ShareService {
|
||||
private authService: AuthService
|
||||
private settingsService: SettingsService
|
||||
private log: (...args: unknown[]) => void
|
||||
|
||||
constructor(authService: AuthService, settingsService: SettingsService, log?: (...args: unknown[]) => void) {
|
||||
this.authService = authService
|
||||
this.settingsService = settingsService
|
||||
this.log = log || console.log
|
||||
}
|
||||
|
||||
/**
|
||||
* Share a task with specified visibility
|
||||
* Returns the share response data
|
||||
*/
|
||||
async shareTask(taskId: string, visibility: ShareVisibility = "organization") {
|
||||
try {
|
||||
const sessionToken = this.authService.getSessionToken()
|
||||
if (!sessionToken) {
|
||||
throw new Error("Authentication required")
|
||||
}
|
||||
|
||||
const response = await fetch(`${getRooCodeApiUrl()}/api/extension/share`, {
|
||||
method: "POST",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
Authorization: `Bearer ${sessionToken}`,
|
||||
"User-Agent": getUserAgent(),
|
||||
},
|
||||
body: JSON.stringify({ taskId, visibility }),
|
||||
signal: AbortSignal.timeout(10000),
|
||||
})
|
||||
|
||||
if (!response.ok) {
|
||||
if (response.status === 404) {
|
||||
throw new TaskNotFoundError(taskId)
|
||||
}
|
||||
throw new Error(`HTTP ${response.status}: ${response.statusText}`)
|
||||
}
|
||||
|
||||
const data = shareResponseSchema.parse(await response.json())
|
||||
this.log("[share] Share link created successfully:", data)
|
||||
|
||||
if (data.success && data.shareUrl) {
|
||||
// Copy to clipboard
|
||||
await vscode.env.clipboard.writeText(data.shareUrl)
|
||||
}
|
||||
|
||||
return data
|
||||
} catch (error) {
|
||||
this.log("[share] Error sharing task:", error)
|
||||
throw error
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if sharing is available
|
||||
*/
|
||||
async canShareTask(): Promise<boolean> {
|
||||
try {
|
||||
if (!this.authService.isAuthenticated()) {
|
||||
return false
|
||||
}
|
||||
|
||||
return !!this.settingsService.getSettings()?.cloudSettings?.enableTaskSharing
|
||||
} catch (error) {
|
||||
this.log("[share] Error checking if task can be shared:", error)
|
||||
return false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -36,6 +36,6 @@ export class StaticSettingsService implements SettingsService {
|
|||
}
|
||||
|
||||
public dispose(): void {
|
||||
// No resources to clean up for static settings
|
||||
// No resources to clean up for static settings.
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -6,7 +6,7 @@ import {
|
|||
} from "@roo-code/types"
|
||||
import { BaseTelemetryClient } from "@roo-code/telemetry"
|
||||
|
||||
import { getRooCodeApiUrl } from "./Config"
|
||||
import { getRooCodeApiUrl } from "./config"
|
||||
import type { AuthService } from "./auth"
|
||||
import type { SettingsService } from "./SettingsService"
|
||||
|
||||
|
|
|
|||
|
|
@ -1,14 +1,16 @@
|
|||
// npx vitest run src/__tests__/CloudService.test.ts
|
||||
|
||||
import * as vscode from "vscode"
|
||||
|
||||
import type { ClineMessage } from "@roo-code/types"
|
||||
import { TelemetryService } from "@roo-code/telemetry"
|
||||
|
||||
import { CloudService } from "../CloudService"
|
||||
import { WebAuthService } from "../auth/WebAuthService"
|
||||
import { CloudSettingsService } from "../CloudSettingsService"
|
||||
import { ShareService, TaskNotFoundError } from "../ShareService"
|
||||
import { CloudShareService } from "../CloudShareService"
|
||||
import { TelemetryClient } from "../TelemetryClient"
|
||||
import { TelemetryService } from "@roo-code/telemetry"
|
||||
import { TaskNotFoundError } from "../errors"
|
||||
|
||||
vi.mock("vscode", () => ({
|
||||
ExtensionContext: vi.fn(),
|
||||
|
|
@ -30,7 +32,7 @@ vi.mock("../auth/WebAuthService")
|
|||
|
||||
vi.mock("../CloudSettingsService")
|
||||
|
||||
vi.mock("../ShareService")
|
||||
vi.mock("../CloudShareService")
|
||||
|
||||
vi.mock("../TelemetryClient")
|
||||
|
||||
|
|
@ -154,7 +156,7 @@ describe("CloudService", () => {
|
|||
|
||||
vi.mocked(WebAuthService).mockImplementation(() => mockAuthService as unknown as WebAuthService)
|
||||
vi.mocked(CloudSettingsService).mockImplementation(() => mockSettingsService as unknown as CloudSettingsService)
|
||||
vi.mocked(ShareService).mockImplementation(() => mockShareService as unknown as ShareService)
|
||||
vi.mocked(CloudShareService).mockImplementation(() => mockShareService as unknown as CloudShareService)
|
||||
vi.mocked(TelemetryClient).mockImplementation(() => mockTelemetryClient as unknown as TelemetryClient)
|
||||
|
||||
vi.mocked(TelemetryService.hasInstance).mockReturnValue(true)
|
||||
|
|
|
|||
|
|
@ -6,8 +6,8 @@ import type { OrganizationSettings } from "@roo-code/types"
|
|||
|
||||
// Mock dependencies
|
||||
vi.mock("../RefreshTimer")
|
||||
vi.mock("../Config", () => ({
|
||||
getRooCodeApiUrl: vi.fn().mockReturnValue("https://api.example.com"),
|
||||
vi.mock("../config", () => ({
|
||||
getRooCodeApiUrl: vi.fn().mockReturnValue("https://app.roocode.com"),
|
||||
}))
|
||||
|
||||
// Mock fetch globally
|
||||
|
|
@ -338,7 +338,7 @@ describe("CloudSettingsService", () => {
|
|||
const result = await timerCallback()
|
||||
|
||||
expect(result).toBe(true)
|
||||
expect(fetch).toHaveBeenCalledWith("https://api.example.com/api/organization-settings", {
|
||||
expect(fetch).toHaveBeenCalledWith("https://app.roocode.com/api/organization-settings", {
|
||||
headers: {
|
||||
Authorization: "Bearer valid-token",
|
||||
},
|
||||
|
|
|
|||
|
|
@ -3,9 +3,11 @@
|
|||
import type { MockedFunction } from "vitest"
|
||||
import * as vscode from "vscode"
|
||||
|
||||
import { ShareService, TaskNotFoundError } from "../ShareService"
|
||||
import type { AuthService } from "../auth"
|
||||
import { CloudAPI } from "../CloudAPI"
|
||||
import { CloudShareService } from "../CloudShareService"
|
||||
import type { SettingsService } from "../SettingsService"
|
||||
import type { AuthService } from "../auth"
|
||||
import { CloudAPIError, TaskNotFoundError } from "../errors"
|
||||
|
||||
// Mock fetch
|
||||
const mockFetch = vi.fn()
|
||||
|
|
@ -44,10 +46,11 @@ vi.mock("../utils", () => ({
|
|||
getUserAgent: () => "Roo-Code 1.0.0",
|
||||
}))
|
||||
|
||||
describe("ShareService", () => {
|
||||
let shareService: ShareService
|
||||
describe("CloudShareService", () => {
|
||||
let shareService: CloudShareService
|
||||
let mockAuthService: AuthService
|
||||
let mockSettingsService: SettingsService
|
||||
let mockCloudAPI: CloudAPI
|
||||
let mockLog: MockedFunction<(...args: unknown[]) => void>
|
||||
|
||||
beforeEach(() => {
|
||||
|
|
@ -65,7 +68,8 @@ describe("ShareService", () => {
|
|||
getSettings: vi.fn(),
|
||||
} as any
|
||||
|
||||
shareService = new ShareService(mockAuthService, mockSettingsService, mockLog)
|
||||
mockCloudAPI = new CloudAPI(mockAuthService, mockLog)
|
||||
shareService = new CloudShareService(mockCloudAPI, mockSettingsService, mockLog)
|
||||
})
|
||||
|
||||
describe("shareTask", () => {
|
||||
|
|
@ -189,12 +193,12 @@ describe("ShareService", () => {
|
|||
ok: false,
|
||||
status: 404,
|
||||
statusText: "Not Found",
|
||||
json: vi.fn().mockRejectedValue(new Error("Invalid JSON")),
|
||||
text: vi.fn().mockResolvedValue("Not Found"),
|
||||
})
|
||||
|
||||
await expect(shareService.shareTask("task-123", "organization")).rejects.toThrow(TaskNotFoundError)
|
||||
await expect(shareService.shareTask("task-123", "organization")).rejects.toThrow(
|
||||
"Task 'task-123' not found",
|
||||
)
|
||||
await expect(shareService.shareTask("task-123", "organization")).rejects.toThrow("Task not found")
|
||||
})
|
||||
|
||||
it("should throw generic Error for non-404 HTTP errors", async () => {
|
||||
|
|
@ -203,12 +207,14 @@ describe("ShareService", () => {
|
|||
ok: false,
|
||||
status: 500,
|
||||
statusText: "Internal Server Error",
|
||||
json: vi.fn().mockRejectedValue(new Error("Invalid JSON")),
|
||||
text: vi.fn().mockResolvedValue("Internal Server Error"),
|
||||
})
|
||||
|
||||
await expect(shareService.shareTask("task-123", "organization")).rejects.toThrow(CloudAPIError)
|
||||
await expect(shareService.shareTask("task-123", "organization")).rejects.toThrow(
|
||||
"HTTP 500: Internal Server Error",
|
||||
)
|
||||
await expect(shareService.shareTask("task-123", "organization")).rejects.not.toThrow(TaskNotFoundError)
|
||||
})
|
||||
|
||||
it("should create TaskNotFoundError with correct properties", async () => {
|
||||
|
|
@ -217,6 +223,8 @@ describe("ShareService", () => {
|
|||
ok: false,
|
||||
status: 404,
|
||||
statusText: "Not Found",
|
||||
json: vi.fn().mockRejectedValue(new Error("Invalid JSON")),
|
||||
text: vi.fn().mockResolvedValue("Not Found"),
|
||||
})
|
||||
|
||||
try {
|
||||
|
|
@ -225,7 +233,7 @@ describe("ShareService", () => {
|
|||
} catch (error) {
|
||||
expect(error).toBeInstanceOf(TaskNotFoundError)
|
||||
expect(error).toBeInstanceOf(Error)
|
||||
expect((error as TaskNotFoundError).message).toBe("Task 'task-123' not found")
|
||||
expect((error as TaskNotFoundError).message).toBe("Task not found")
|
||||
}
|
||||
})
|
||||
})
|
||||
|
|
@ -277,8 +285,8 @@ describe("ShareService", () => {
|
|||
expect(result).toBe(false)
|
||||
})
|
||||
|
||||
it("should return false when not authenticated", async () => {
|
||||
;(mockAuthService.isAuthenticated as any).mockReturnValue(false)
|
||||
it("should return false when settings service returns undefined", async () => {
|
||||
;(mockSettingsService.getSettings as any).mockReturnValue(undefined)
|
||||
|
||||
const result = await shareService.canShareTask()
|
||||
|
||||
|
|
@ -286,13 +294,17 @@ describe("ShareService", () => {
|
|||
})
|
||||
|
||||
it("should handle errors gracefully", async () => {
|
||||
;(mockAuthService.isAuthenticated as any).mockImplementation(() => {
|
||||
throw new Error("Auth error")
|
||||
;(mockSettingsService.getSettings as any).mockImplementation(() => {
|
||||
throw new Error("Settings error")
|
||||
})
|
||||
|
||||
const result = await shareService.canShareTask()
|
||||
|
||||
expect(result).toBe(false)
|
||||
expect(mockLog).toHaveBeenCalledWith(
|
||||
"[ShareService] Error checking if task can be shared:",
|
||||
expect.any(Error),
|
||||
)
|
||||
})
|
||||
})
|
||||
})
|
||||
|
|
@ -1,17 +1,17 @@
|
|||
// npx vitest run src/__tests__/AuthService.spec.ts
|
||||
// npx vitest run src/__tests__/auth/WebAuthService.spec.ts
|
||||
|
||||
import { vi, Mock, beforeEach, afterEach, describe, it, expect } from "vitest"
|
||||
import { type Mock } from "vitest"
|
||||
import crypto from "crypto"
|
||||
import * as vscode from "vscode"
|
||||
|
||||
import { WebAuthService } from "../../auth/WebAuthService"
|
||||
import { RefreshTimer } from "../../RefreshTimer"
|
||||
import * as Config from "../../Config"
|
||||
import * as utils from "../../utils"
|
||||
import { getClerkBaseUrl, getRooCodeApiUrl } from "../../config"
|
||||
import { getUserAgent } from "../../utils"
|
||||
|
||||
// Mock external dependencies
|
||||
vi.mock("../../RefreshTimer")
|
||||
vi.mock("../../Config")
|
||||
vi.mock("../../config")
|
||||
vi.mock("../../utils")
|
||||
vi.mock("crypto")
|
||||
|
||||
|
|
@ -101,11 +101,11 @@ describe("WebAuthService", () => {
|
|||
MockedRefreshTimer.mockImplementation(() => mockTimer as unknown as RefreshTimer)
|
||||
|
||||
// Setup config mocks - use production URL by default to maintain existing test behavior
|
||||
vi.mocked(Config.getClerkBaseUrl).mockReturnValue("https://clerk.roocode.com")
|
||||
vi.mocked(Config.getRooCodeApiUrl).mockReturnValue("https://api.test.com")
|
||||
vi.mocked(getClerkBaseUrl).mockReturnValue("https://clerk.roocode.com")
|
||||
vi.mocked(getRooCodeApiUrl).mockReturnValue("https://api.test.com")
|
||||
|
||||
// Setup utils mock
|
||||
vi.mocked(utils.getUserAgent).mockReturnValue("Roo-Code 1.0.0")
|
||||
vi.mocked(getUserAgent).mockReturnValue("Roo-Code 1.0.0")
|
||||
|
||||
// Setup crypto mock
|
||||
vi.mocked(crypto.randomBytes).mockReturnValue(Buffer.from("test-random-bytes") as never)
|
||||
|
|
@ -977,7 +977,7 @@ describe("WebAuthService", () => {
|
|||
describe("auth credentials key scoping", () => {
|
||||
it("should use default key when getClerkBaseUrl returns production URL", async () => {
|
||||
// Mock getClerkBaseUrl to return production URL
|
||||
vi.mocked(Config.getClerkBaseUrl).mockReturnValue("https://clerk.roocode.com")
|
||||
vi.mocked(getClerkBaseUrl).mockReturnValue("https://clerk.roocode.com")
|
||||
|
||||
const service = new WebAuthService(mockContext as unknown as vscode.ExtensionContext, mockLog)
|
||||
const credentials = { clientToken: "test-token", sessionId: "test-session" }
|
||||
|
|
@ -994,7 +994,7 @@ describe("WebAuthService", () => {
|
|||
it("should use scoped key when getClerkBaseUrl returns custom URL", async () => {
|
||||
const customUrl = "https://custom.clerk.com"
|
||||
// Mock getClerkBaseUrl to return custom URL
|
||||
vi.mocked(Config.getClerkBaseUrl).mockReturnValue(customUrl)
|
||||
vi.mocked(getClerkBaseUrl).mockReturnValue(customUrl)
|
||||
|
||||
const service = new WebAuthService(mockContext as unknown as vscode.ExtensionContext, mockLog)
|
||||
const credentials = { clientToken: "test-token", sessionId: "test-session" }
|
||||
|
|
@ -1010,7 +1010,7 @@ describe("WebAuthService", () => {
|
|||
|
||||
it("should load credentials using scoped key", async () => {
|
||||
const customUrl = "https://custom.clerk.com"
|
||||
vi.mocked(Config.getClerkBaseUrl).mockReturnValue(customUrl)
|
||||
vi.mocked(getClerkBaseUrl).mockReturnValue(customUrl)
|
||||
|
||||
const service = new WebAuthService(mockContext as unknown as vscode.ExtensionContext, mockLog)
|
||||
const credentials = { clientToken: "test-token", sessionId: "test-session" }
|
||||
|
|
@ -1025,7 +1025,7 @@ describe("WebAuthService", () => {
|
|||
|
||||
it("should clear credentials using scoped key", async () => {
|
||||
const customUrl = "https://custom.clerk.com"
|
||||
vi.mocked(Config.getClerkBaseUrl).mockReturnValue(customUrl)
|
||||
vi.mocked(getClerkBaseUrl).mockReturnValue(customUrl)
|
||||
|
||||
const service = new WebAuthService(mockContext as unknown as vscode.ExtensionContext, mockLog)
|
||||
|
||||
|
|
@ -1037,7 +1037,7 @@ describe("WebAuthService", () => {
|
|||
|
||||
it("should listen for changes on scoped key", async () => {
|
||||
const customUrl = "https://custom.clerk.com"
|
||||
vi.mocked(Config.getClerkBaseUrl).mockReturnValue(customUrl)
|
||||
vi.mocked(getClerkBaseUrl).mockReturnValue(customUrl)
|
||||
|
||||
let onDidChangeCallback: (e: { key: string }) => void
|
||||
|
||||
|
|
@ -1064,7 +1064,7 @@ describe("WebAuthService", () => {
|
|||
|
||||
it("should not respond to changes on different scoped keys", async () => {
|
||||
const customUrl = "https://custom.clerk.com"
|
||||
vi.mocked(Config.getClerkBaseUrl).mockReturnValue(customUrl)
|
||||
vi.mocked(getClerkBaseUrl).mockReturnValue(customUrl)
|
||||
|
||||
let onDidChangeCallback: (e: { key: string }) => void
|
||||
|
||||
|
|
@ -1088,7 +1088,7 @@ describe("WebAuthService", () => {
|
|||
|
||||
it("should not respond to changes on default key when using scoped key", async () => {
|
||||
const customUrl = "https://custom.clerk.com"
|
||||
vi.mocked(Config.getClerkBaseUrl).mockReturnValue(customUrl)
|
||||
vi.mocked(getClerkBaseUrl).mockReturnValue(customUrl)
|
||||
|
||||
let onDidChangeCallback: (e: { key: string }) => void
|
||||
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
import EventEmitter from "events"
|
||||
|
||||
import type { CloudUserInfo } from "@roo-code/types"
|
||||
|
||||
export interface AuthServiceEvents {
|
||||
|
|
|
|||
|
|
@ -1,6 +1,9 @@
|
|||
import EventEmitter from "events"
|
||||
|
||||
import * as vscode from "vscode"
|
||||
|
||||
import type { CloudUserInfo } from "@roo-code/types"
|
||||
|
||||
import type { AuthService, AuthServiceEvents, AuthState } from "./AuthService"
|
||||
|
||||
export class StaticTokenAuthService extends EventEmitter<AuthServiceEvents> implements AuthService {
|
||||
|
|
|
|||
|
|
@ -6,11 +6,19 @@ import { z } from "zod"
|
|||
|
||||
import type { CloudUserInfo, CloudOrganizationMembership } from "@roo-code/types"
|
||||
|
||||
import { getClerkBaseUrl, getRooCodeApiUrl, PRODUCTION_CLERK_BASE_URL } from "../Config"
|
||||
import { RefreshTimer } from "../RefreshTimer"
|
||||
import { getClerkBaseUrl, getRooCodeApiUrl, PRODUCTION_CLERK_BASE_URL } from "../config"
|
||||
import { getUserAgent } from "../utils"
|
||||
import { InvalidClientTokenError } from "../errors"
|
||||
import { RefreshTimer } from "../RefreshTimer"
|
||||
|
||||
import type { AuthService, AuthServiceEvents, AuthState } from "./AuthService"
|
||||
|
||||
const AUTH_STATE_KEY = "clerk-auth-state"
|
||||
|
||||
/**
|
||||
* AuthCredentials
|
||||
*/
|
||||
|
||||
const authCredentialsSchema = z.object({
|
||||
clientToken: z.string().min(1, "Client token cannot be empty"),
|
||||
sessionId: z.string().min(1, "Session ID cannot be empty"),
|
||||
|
|
@ -19,7 +27,9 @@ const authCredentialsSchema = z.object({
|
|||
|
||||
type AuthCredentials = z.infer<typeof authCredentialsSchema>
|
||||
|
||||
const AUTH_STATE_KEY = "clerk-auth-state"
|
||||
/**
|
||||
* Clerk Schemas
|
||||
*/
|
||||
|
||||
const clerkSignInResponseSchema = z.object({
|
||||
response: z.object({
|
||||
|
|
@ -33,8 +43,9 @@ const clerkCreateSessionTokenResponseSchema = z.object({
|
|||
|
||||
const clerkMeResponseSchema = z.object({
|
||||
response: z.object({
|
||||
first_name: z.string().optional().nullable(),
|
||||
last_name: z.string().optional().nullable(),
|
||||
id: z.string().optional(),
|
||||
first_name: z.string().nullish(),
|
||||
last_name: z.string().nullish(),
|
||||
image_url: z.string().optional(),
|
||||
primary_email_address_id: z.string().optional(),
|
||||
email_addresses: z
|
||||
|
|
@ -69,13 +80,6 @@ const clerkOrganizationMembershipsSchema = z.object({
|
|||
),
|
||||
})
|
||||
|
||||
class InvalidClientTokenError extends Error {
|
||||
constructor() {
|
||||
super("Invalid/Expired client token")
|
||||
Object.setPrototypeOf(this, InvalidClientTokenError.prototype)
|
||||
}
|
||||
}
|
||||
|
||||
export class WebAuthService extends EventEmitter<AuthServiceEvents> implements AuthService {
|
||||
private context: vscode.ExtensionContext
|
||||
private timer: RefreshTimer
|
||||
|
|
@ -94,8 +98,9 @@ export class WebAuthService extends EventEmitter<AuthServiceEvents> implements A
|
|||
this.context = context
|
||||
this.log = log || console.log
|
||||
|
||||
// Calculate auth credentials key based on Clerk base URL
|
||||
// Calculate auth credentials key based on Clerk base URL.
|
||||
const clerkBaseUrl = getClerkBaseUrl()
|
||||
|
||||
if (clerkBaseUrl !== PRODUCTION_CLERK_BASE_URL) {
|
||||
this.authCredentialsKey = `clerk-auth-credentials-${clerkBaseUrl}`
|
||||
} else {
|
||||
|
|
@ -514,9 +519,13 @@ export class WebAuthService extends EventEmitter<AuthServiceEvents> implements A
|
|||
throw new Error(`HTTP ${response.status}: ${response.statusText}`)
|
||||
}
|
||||
|
||||
const { response: userData } = clerkMeResponseSchema.parse(await response.json())
|
||||
const payload = await response.json()
|
||||
const { response: userData } = clerkMeResponseSchema.parse(payload)
|
||||
|
||||
const userInfo: CloudUserInfo = {}
|
||||
const userInfo: CloudUserInfo = {
|
||||
id: userData.id,
|
||||
picture: userData.image_url,
|
||||
}
|
||||
|
||||
const names = [userData.first_name, userData.last_name].filter((name) => !!name)
|
||||
userInfo.name = names.length > 0 ? names.join(" ") : undefined
|
||||
|
|
@ -529,8 +538,6 @@ export class WebAuthService extends EventEmitter<AuthServiceEvents> implements A
|
|||
)?.email_address
|
||||
}
|
||||
|
||||
userInfo.picture = userData.image_url
|
||||
|
||||
// Fetch organization info if user is in organization context
|
||||
try {
|
||||
const storedOrgId = this.getStoredOrganizationId()
|
||||
|
|
@ -544,6 +551,7 @@ export class WebAuthService extends EventEmitter<AuthServiceEvents> implements A
|
|||
|
||||
if (userMembership) {
|
||||
this.setUserOrganizationInfo(userInfo, userMembership)
|
||||
|
||||
this.log("[auth] User in organization context:", {
|
||||
id: userMembership.organization.id,
|
||||
name: userMembership.organization.name,
|
||||
|
|
@ -562,6 +570,7 @@ export class WebAuthService extends EventEmitter<AuthServiceEvents> implements A
|
|||
|
||||
if (primaryOrgMembership) {
|
||||
this.setUserOrganizationInfo(userInfo, primaryOrgMembership)
|
||||
|
||||
this.log("[auth] Legacy credentials: Found organization membership:", {
|
||||
id: primaryOrgMembership.organization.id,
|
||||
name: primaryOrgMembership.organization.name,
|
||||
|
|
|
|||
|
|
@ -1,7 +1,5 @@
|
|||
// Production constants
|
||||
export const PRODUCTION_CLERK_BASE_URL = "https://clerk.roocode.com"
|
||||
export const PRODUCTION_ROO_CODE_API_URL = "https://app.roocode.com"
|
||||
|
||||
// Functions with environment variable fallbacks
|
||||
export const getClerkBaseUrl = () => process.env.CLERK_BASE_URL || PRODUCTION_CLERK_BASE_URL
|
||||
export const getRooCodeApiUrl = () => process.env.ROO_CODE_API_URL || PRODUCTION_ROO_CODE_API_URL
|
||||
42
packages/cloud/src/errors.ts
Normal file
42
packages/cloud/src/errors.ts
Normal file
|
|
@ -0,0 +1,42 @@
|
|||
export class CloudAPIError extends Error {
|
||||
constructor(
|
||||
message: string,
|
||||
public statusCode?: number,
|
||||
public responseBody?: unknown,
|
||||
) {
|
||||
super(message)
|
||||
this.name = "CloudAPIError"
|
||||
Object.setPrototypeOf(this, CloudAPIError.prototype)
|
||||
}
|
||||
}
|
||||
|
||||
export class TaskNotFoundError extends CloudAPIError {
|
||||
constructor(taskId?: string) {
|
||||
super(taskId ? `Task '${taskId}' not found` : "Task not found", 404)
|
||||
this.name = "TaskNotFoundError"
|
||||
Object.setPrototypeOf(this, TaskNotFoundError.prototype)
|
||||
}
|
||||
}
|
||||
|
||||
export class AuthenticationError extends CloudAPIError {
|
||||
constructor(message = "Authentication required") {
|
||||
super(message, 401)
|
||||
this.name = "AuthenticationError"
|
||||
Object.setPrototypeOf(this, AuthenticationError.prototype)
|
||||
}
|
||||
}
|
||||
|
||||
export class NetworkError extends CloudAPIError {
|
||||
constructor(message = "Network error occurred") {
|
||||
super(message)
|
||||
this.name = "NetworkError"
|
||||
Object.setPrototypeOf(this, NetworkError.prototype)
|
||||
}
|
||||
}
|
||||
|
||||
export class InvalidClientTokenError extends Error {
|
||||
constructor() {
|
||||
super("Invalid/Expired client token")
|
||||
Object.setPrototypeOf(this, InvalidClientTokenError.prototype)
|
||||
}
|
||||
}
|
||||
|
|
@ -1,2 +1,4 @@
|
|||
export * from "./config"
|
||||
|
||||
export * from "./CloudAPI"
|
||||
export * from "./CloudService"
|
||||
export * from "./Config"
|
||||
|
|
|
|||
|
|
@ -1,27 +1,12 @@
|
|||
import type { EventEmitter } from "events"
|
||||
import type { Socket } from "net"
|
||||
|
||||
import type { RooCodeEvents } from "./events.js"
|
||||
import type { RooCodeSettings } from "./global-settings.js"
|
||||
import type { ProviderSettingsEntry, ProviderSettings } from "./provider-settings.js"
|
||||
import type { ClineMessage, TokenUsage } from "./message.js"
|
||||
import type { ToolUsage, ToolName } from "./tool.js"
|
||||
import type { IpcMessage, IpcServerEvents, IsSubtask } from "./ipc.js"
|
||||
import type { IpcMessage, IpcServerEvents } from "./ipc.js"
|
||||
|
||||
// TODO: Make sure this matches `RooCodeEvents` from `@roo-code/types`.
|
||||
export interface RooCodeAPIEvents {
|
||||
message: [data: { taskId: string; action: "created" | "updated"; message: ClineMessage }]
|
||||
taskCreated: [taskId: string]
|
||||
taskStarted: [taskId: string]
|
||||
taskModeSwitched: [taskId: string, mode: string]
|
||||
taskPaused: [taskId: string]
|
||||
taskUnpaused: [taskId: string]
|
||||
taskAskResponded: [taskId: string]
|
||||
taskAborted: [taskId: string]
|
||||
taskSpawned: [parentTaskId: string, childTaskId: string]
|
||||
taskCompleted: [taskId: string, tokenUsage: TokenUsage, toolUsage: ToolUsage, isSubtask: IsSubtask]
|
||||
taskTokenUsageUpdated: [taskId: string, tokenUsage: TokenUsage]
|
||||
taskToolFailed: [taskId: string, toolName: ToolName, error: string]
|
||||
}
|
||||
export type RooCodeAPIEvents = RooCodeEvents
|
||||
|
||||
export interface RooCodeAPI extends EventEmitter<RooCodeAPIEvents> {
|
||||
/**
|
||||
|
|
|
|||
|
|
@ -9,6 +9,7 @@ import { discriminatedProviderSettingsWithIdSchema } from "./provider-settings.j
|
|||
*/
|
||||
|
||||
export interface CloudUserInfo {
|
||||
id?: string
|
||||
name?: string
|
||||
email?: string
|
||||
picture?: string
|
||||
|
|
|
|||
192
packages/types/src/events.ts
Normal file
192
packages/types/src/events.ts
Normal file
|
|
@ -0,0 +1,192 @@
|
|||
import { z } from "zod"
|
||||
|
||||
import { clineMessageSchema, tokenUsageSchema } from "./message.js"
|
||||
import { toolNamesSchema, toolUsageSchema } from "./tool.js"
|
||||
|
||||
/**
|
||||
* RooCodeEventName
|
||||
*/
|
||||
|
||||
export enum RooCodeEventName {
|
||||
// Task Provider Lifecycle
|
||||
TaskCreated = "taskCreated",
|
||||
|
||||
// Task Lifecycle
|
||||
TaskStarted = "taskStarted",
|
||||
TaskCompleted = "taskCompleted",
|
||||
TaskAborted = "taskAborted",
|
||||
TaskFocused = "taskFocused",
|
||||
TaskUnfocused = "taskUnfocused",
|
||||
TaskActive = "taskActive",
|
||||
TaskIdle = "taskIdle",
|
||||
|
||||
// Subtask Lifecycle
|
||||
TaskPaused = "taskPaused",
|
||||
TaskUnpaused = "taskUnpaused",
|
||||
TaskSpawned = "taskSpawned",
|
||||
|
||||
// Task Execution
|
||||
Message = "message",
|
||||
TaskModeSwitched = "taskModeSwitched",
|
||||
TaskAskResponded = "taskAskResponded",
|
||||
|
||||
// Task Analytics
|
||||
TaskTokenUsageUpdated = "taskTokenUsageUpdated",
|
||||
TaskToolFailed = "taskToolFailed",
|
||||
|
||||
// Evals
|
||||
EvalPass = "evalPass",
|
||||
EvalFail = "evalFail",
|
||||
}
|
||||
|
||||
/**
|
||||
* RooCodeEvents
|
||||
*/
|
||||
|
||||
export const rooCodeEventsSchema = z.object({
|
||||
[RooCodeEventName.TaskCreated]: z.tuple([z.string()]),
|
||||
|
||||
[RooCodeEventName.TaskStarted]: z.tuple([z.string()]),
|
||||
[RooCodeEventName.TaskCompleted]: z.tuple([
|
||||
z.string(),
|
||||
tokenUsageSchema,
|
||||
toolUsageSchema,
|
||||
z.object({
|
||||
isSubtask: z.boolean(),
|
||||
}),
|
||||
]),
|
||||
[RooCodeEventName.TaskAborted]: z.tuple([z.string()]),
|
||||
[RooCodeEventName.TaskFocused]: z.tuple([z.string()]),
|
||||
[RooCodeEventName.TaskUnfocused]: z.tuple([z.string()]),
|
||||
[RooCodeEventName.TaskActive]: z.tuple([z.string()]),
|
||||
[RooCodeEventName.TaskIdle]: z.tuple([z.string()]),
|
||||
|
||||
[RooCodeEventName.TaskPaused]: z.tuple([z.string()]),
|
||||
[RooCodeEventName.TaskUnpaused]: z.tuple([z.string()]),
|
||||
[RooCodeEventName.TaskSpawned]: z.tuple([z.string(), z.string()]),
|
||||
|
||||
[RooCodeEventName.Message]: z.tuple([
|
||||
z.object({
|
||||
taskId: z.string(),
|
||||
action: z.union([z.literal("created"), z.literal("updated")]),
|
||||
message: clineMessageSchema,
|
||||
}),
|
||||
]),
|
||||
[RooCodeEventName.TaskModeSwitched]: z.tuple([z.string(), z.string()]),
|
||||
[RooCodeEventName.TaskAskResponded]: z.tuple([z.string()]),
|
||||
|
||||
[RooCodeEventName.TaskToolFailed]: z.tuple([z.string(), toolNamesSchema, z.string()]),
|
||||
[RooCodeEventName.TaskTokenUsageUpdated]: z.tuple([z.string(), tokenUsageSchema]),
|
||||
})
|
||||
|
||||
export type RooCodeEvents = z.infer<typeof rooCodeEventsSchema>
|
||||
|
||||
/**
|
||||
* TaskEvent
|
||||
*/
|
||||
|
||||
export const taskEventSchema = z.discriminatedUnion("eventName", [
|
||||
// Task Provider Lifecycle
|
||||
z.object({
|
||||
eventName: z.literal(RooCodeEventName.TaskCreated),
|
||||
payload: rooCodeEventsSchema.shape[RooCodeEventName.TaskCreated],
|
||||
taskId: z.number().optional(),
|
||||
}),
|
||||
|
||||
// Task Lifecycle
|
||||
z.object({
|
||||
eventName: z.literal(RooCodeEventName.TaskStarted),
|
||||
payload: rooCodeEventsSchema.shape[RooCodeEventName.TaskStarted],
|
||||
taskId: z.number().optional(),
|
||||
}),
|
||||
z.object({
|
||||
eventName: z.literal(RooCodeEventName.TaskCompleted),
|
||||
payload: rooCodeEventsSchema.shape[RooCodeEventName.TaskCompleted],
|
||||
taskId: z.number().optional(),
|
||||
}),
|
||||
z.object({
|
||||
eventName: z.literal(RooCodeEventName.TaskAborted),
|
||||
payload: rooCodeEventsSchema.shape[RooCodeEventName.TaskAborted],
|
||||
taskId: z.number().optional(),
|
||||
}),
|
||||
z.object({
|
||||
eventName: z.literal(RooCodeEventName.TaskFocused),
|
||||
payload: rooCodeEventsSchema.shape[RooCodeEventName.TaskFocused],
|
||||
taskId: z.number().optional(),
|
||||
}),
|
||||
z.object({
|
||||
eventName: z.literal(RooCodeEventName.TaskUnfocused),
|
||||
payload: rooCodeEventsSchema.shape[RooCodeEventName.TaskUnfocused],
|
||||
taskId: z.number().optional(),
|
||||
}),
|
||||
z.object({
|
||||
eventName: z.literal(RooCodeEventName.TaskActive),
|
||||
payload: rooCodeEventsSchema.shape[RooCodeEventName.TaskActive],
|
||||
taskId: z.number().optional(),
|
||||
}),
|
||||
z.object({
|
||||
eventName: z.literal(RooCodeEventName.TaskIdle),
|
||||
payload: rooCodeEventsSchema.shape[RooCodeEventName.TaskIdle],
|
||||
taskId: z.number().optional(),
|
||||
}),
|
||||
|
||||
// Subtask Lifecycle
|
||||
z.object({
|
||||
eventName: z.literal(RooCodeEventName.TaskPaused),
|
||||
payload: rooCodeEventsSchema.shape[RooCodeEventName.TaskPaused],
|
||||
taskId: z.number().optional(),
|
||||
}),
|
||||
z.object({
|
||||
eventName: z.literal(RooCodeEventName.TaskUnpaused),
|
||||
payload: rooCodeEventsSchema.shape[RooCodeEventName.TaskUnpaused],
|
||||
taskId: z.number().optional(),
|
||||
}),
|
||||
z.object({
|
||||
eventName: z.literal(RooCodeEventName.TaskSpawned),
|
||||
payload: rooCodeEventsSchema.shape[RooCodeEventName.TaskSpawned],
|
||||
taskId: z.number().optional(),
|
||||
}),
|
||||
|
||||
// Task Execution
|
||||
z.object({
|
||||
eventName: z.literal(RooCodeEventName.Message),
|
||||
payload: rooCodeEventsSchema.shape[RooCodeEventName.Message],
|
||||
taskId: z.number().optional(),
|
||||
}),
|
||||
z.object({
|
||||
eventName: z.literal(RooCodeEventName.TaskModeSwitched),
|
||||
payload: rooCodeEventsSchema.shape[RooCodeEventName.TaskModeSwitched],
|
||||
taskId: z.number().optional(),
|
||||
}),
|
||||
z.object({
|
||||
eventName: z.literal(RooCodeEventName.TaskAskResponded),
|
||||
payload: rooCodeEventsSchema.shape[RooCodeEventName.TaskAskResponded],
|
||||
taskId: z.number().optional(),
|
||||
}),
|
||||
|
||||
// Task Analytics
|
||||
z.object({
|
||||
eventName: z.literal(RooCodeEventName.TaskToolFailed),
|
||||
payload: rooCodeEventsSchema.shape[RooCodeEventName.TaskToolFailed],
|
||||
taskId: z.number().optional(),
|
||||
}),
|
||||
z.object({
|
||||
eventName: z.literal(RooCodeEventName.TaskTokenUsageUpdated),
|
||||
payload: rooCodeEventsSchema.shape[RooCodeEventName.TaskTokenUsageUpdated],
|
||||
taskId: z.number().optional(),
|
||||
}),
|
||||
|
||||
// Evals
|
||||
z.object({
|
||||
eventName: z.literal(RooCodeEventName.EvalPass),
|
||||
payload: z.undefined(),
|
||||
taskId: z.number(),
|
||||
}),
|
||||
z.object({
|
||||
eventName: z.literal(RooCodeEventName.EvalFail),
|
||||
payload: z.undefined(),
|
||||
taskId: z.number(),
|
||||
}),
|
||||
])
|
||||
|
||||
export type TaskEvent = z.infer<typeof taskEventSchema>
|
||||
|
|
@ -1,8 +1,7 @@
|
|||
export * from "./providers/index.js"
|
||||
|
||||
export * from "./api.js"
|
||||
export * from "./codebase-index.js"
|
||||
export * from "./cloud.js"
|
||||
export * from "./codebase-index.js"
|
||||
export * from "./events.js"
|
||||
export * from "./experiment.js"
|
||||
export * from "./followup.js"
|
||||
export * from "./global-settings.js"
|
||||
|
|
@ -15,9 +14,12 @@ export * from "./mode.js"
|
|||
export * from "./model.js"
|
||||
export * from "./provider-settings.js"
|
||||
export * from "./sharing.js"
|
||||
export * from "./task.js"
|
||||
export * from "./todo.js"
|
||||
export * from "./telemetry.js"
|
||||
export * from "./terminal.js"
|
||||
export * from "./tool.js"
|
||||
export * from "./type-fu.js"
|
||||
export * from "./vscode.js"
|
||||
export * from "./todo.js"
|
||||
|
||||
export * from "./providers/index.js"
|
||||
|
|
|
|||
|
|
@ -1,60 +1,28 @@
|
|||
import { z } from "zod"
|
||||
|
||||
import { clineMessageSchema, tokenUsageSchema } from "./message.js"
|
||||
import { toolNamesSchema, toolUsageSchema } from "./tool.js"
|
||||
import { type TaskEvent, taskEventSchema } from "./events.js"
|
||||
import { rooCodeSettingsSchema } from "./global-settings.js"
|
||||
|
||||
/**
|
||||
* isSubtaskSchema
|
||||
*/
|
||||
export const isSubtaskSchema = z.object({
|
||||
isSubtask: z.boolean(),
|
||||
})
|
||||
export type IsSubtask = z.infer<typeof isSubtaskSchema>
|
||||
|
||||
/**
|
||||
* RooCodeEvent
|
||||
* IpcMessageType
|
||||
*/
|
||||
|
||||
export enum RooCodeEventName {
|
||||
Message = "message",
|
||||
TaskCreated = "taskCreated",
|
||||
TaskStarted = "taskStarted",
|
||||
TaskModeSwitched = "taskModeSwitched",
|
||||
TaskPaused = "taskPaused",
|
||||
TaskUnpaused = "taskUnpaused",
|
||||
TaskAskResponded = "taskAskResponded",
|
||||
TaskAborted = "taskAborted",
|
||||
TaskSpawned = "taskSpawned",
|
||||
TaskCompleted = "taskCompleted",
|
||||
TaskTokenUsageUpdated = "taskTokenUsageUpdated",
|
||||
TaskToolFailed = "taskToolFailed",
|
||||
EvalPass = "evalPass",
|
||||
EvalFail = "evalFail",
|
||||
export enum IpcMessageType {
|
||||
Connect = "Connect",
|
||||
Disconnect = "Disconnect",
|
||||
Ack = "Ack",
|
||||
TaskCommand = "TaskCommand",
|
||||
TaskEvent = "TaskEvent",
|
||||
}
|
||||
|
||||
export const rooCodeEventsSchema = z.object({
|
||||
[RooCodeEventName.Message]: z.tuple([
|
||||
z.object({
|
||||
taskId: z.string(),
|
||||
action: z.union([z.literal("created"), z.literal("updated")]),
|
||||
message: clineMessageSchema,
|
||||
}),
|
||||
]),
|
||||
[RooCodeEventName.TaskCreated]: z.tuple([z.string()]),
|
||||
[RooCodeEventName.TaskStarted]: z.tuple([z.string()]),
|
||||
[RooCodeEventName.TaskModeSwitched]: z.tuple([z.string(), z.string()]),
|
||||
[RooCodeEventName.TaskPaused]: z.tuple([z.string()]),
|
||||
[RooCodeEventName.TaskUnpaused]: z.tuple([z.string()]),
|
||||
[RooCodeEventName.TaskAskResponded]: z.tuple([z.string()]),
|
||||
[RooCodeEventName.TaskAborted]: z.tuple([z.string()]),
|
||||
[RooCodeEventName.TaskSpawned]: z.tuple([z.string(), z.string()]),
|
||||
[RooCodeEventName.TaskCompleted]: z.tuple([z.string(), tokenUsageSchema, toolUsageSchema, isSubtaskSchema]),
|
||||
[RooCodeEventName.TaskTokenUsageUpdated]: z.tuple([z.string(), tokenUsageSchema]),
|
||||
[RooCodeEventName.TaskToolFailed]: z.tuple([z.string(), toolNamesSchema, z.string()]),
|
||||
})
|
||||
/**
|
||||
* IpcOrigin
|
||||
*/
|
||||
|
||||
export type RooCodeEvents = z.infer<typeof rooCodeEventsSchema>
|
||||
export enum IpcOrigin {
|
||||
Client = "client",
|
||||
Server = "server",
|
||||
}
|
||||
|
||||
/**
|
||||
* Ack
|
||||
|
|
@ -69,7 +37,7 @@ export const ackSchema = z.object({
|
|||
export type Ack = z.infer<typeof ackSchema>
|
||||
|
||||
/**
|
||||
* TaskCommand
|
||||
* TaskCommandName
|
||||
*/
|
||||
|
||||
export enum TaskCommandName {
|
||||
|
|
@ -78,6 +46,10 @@ export enum TaskCommandName {
|
|||
CloseTask = "CloseTask",
|
||||
}
|
||||
|
||||
/**
|
||||
* TaskCommand
|
||||
*/
|
||||
|
||||
export const taskCommandSchema = z.discriminatedUnion("commandName", [
|
||||
z.object({
|
||||
commandName: z.literal(TaskCommandName.StartNewTask),
|
||||
|
|
@ -100,102 +72,10 @@ export const taskCommandSchema = z.discriminatedUnion("commandName", [
|
|||
|
||||
export type TaskCommand = z.infer<typeof taskCommandSchema>
|
||||
|
||||
/**
|
||||
* TaskEvent
|
||||
*/
|
||||
|
||||
export const taskEventSchema = z.discriminatedUnion("eventName", [
|
||||
z.object({
|
||||
eventName: z.literal(RooCodeEventName.Message),
|
||||
payload: rooCodeEventsSchema.shape[RooCodeEventName.Message],
|
||||
taskId: z.number().optional(),
|
||||
}),
|
||||
z.object({
|
||||
eventName: z.literal(RooCodeEventName.TaskCreated),
|
||||
payload: rooCodeEventsSchema.shape[RooCodeEventName.TaskCreated],
|
||||
taskId: z.number().optional(),
|
||||
}),
|
||||
z.object({
|
||||
eventName: z.literal(RooCodeEventName.TaskStarted),
|
||||
payload: rooCodeEventsSchema.shape[RooCodeEventName.TaskStarted],
|
||||
taskId: z.number().optional(),
|
||||
}),
|
||||
z.object({
|
||||
eventName: z.literal(RooCodeEventName.TaskModeSwitched),
|
||||
payload: rooCodeEventsSchema.shape[RooCodeEventName.TaskModeSwitched],
|
||||
taskId: z.number().optional(),
|
||||
}),
|
||||
z.object({
|
||||
eventName: z.literal(RooCodeEventName.TaskPaused),
|
||||
payload: rooCodeEventsSchema.shape[RooCodeEventName.TaskPaused],
|
||||
taskId: z.number().optional(),
|
||||
}),
|
||||
z.object({
|
||||
eventName: z.literal(RooCodeEventName.TaskUnpaused),
|
||||
payload: rooCodeEventsSchema.shape[RooCodeEventName.TaskUnpaused],
|
||||
taskId: z.number().optional(),
|
||||
}),
|
||||
z.object({
|
||||
eventName: z.literal(RooCodeEventName.TaskAskResponded),
|
||||
payload: rooCodeEventsSchema.shape[RooCodeEventName.TaskAskResponded],
|
||||
taskId: z.number().optional(),
|
||||
}),
|
||||
z.object({
|
||||
eventName: z.literal(RooCodeEventName.TaskAborted),
|
||||
payload: rooCodeEventsSchema.shape[RooCodeEventName.TaskAborted],
|
||||
taskId: z.number().optional(),
|
||||
}),
|
||||
z.object({
|
||||
eventName: z.literal(RooCodeEventName.TaskSpawned),
|
||||
payload: rooCodeEventsSchema.shape[RooCodeEventName.TaskSpawned],
|
||||
taskId: z.number().optional(),
|
||||
}),
|
||||
z.object({
|
||||
eventName: z.literal(RooCodeEventName.TaskCompleted),
|
||||
payload: rooCodeEventsSchema.shape[RooCodeEventName.TaskCompleted],
|
||||
taskId: z.number().optional(),
|
||||
}),
|
||||
z.object({
|
||||
eventName: z.literal(RooCodeEventName.TaskTokenUsageUpdated),
|
||||
payload: rooCodeEventsSchema.shape[RooCodeEventName.TaskTokenUsageUpdated],
|
||||
taskId: z.number().optional(),
|
||||
}),
|
||||
z.object({
|
||||
eventName: z.literal(RooCodeEventName.TaskToolFailed),
|
||||
payload: rooCodeEventsSchema.shape[RooCodeEventName.TaskToolFailed],
|
||||
taskId: z.number().optional(),
|
||||
}),
|
||||
z.object({
|
||||
eventName: z.literal(RooCodeEventName.EvalPass),
|
||||
payload: z.undefined(),
|
||||
taskId: z.number(),
|
||||
}),
|
||||
z.object({
|
||||
eventName: z.literal(RooCodeEventName.EvalFail),
|
||||
payload: z.undefined(),
|
||||
taskId: z.number(),
|
||||
}),
|
||||
])
|
||||
|
||||
export type TaskEvent = z.infer<typeof taskEventSchema>
|
||||
|
||||
/**
|
||||
* IpcMessage
|
||||
*/
|
||||
|
||||
export enum IpcMessageType {
|
||||
Connect = "Connect",
|
||||
Disconnect = "Disconnect",
|
||||
Ack = "Ack",
|
||||
TaskCommand = "TaskCommand",
|
||||
TaskEvent = "TaskEvent",
|
||||
}
|
||||
|
||||
export enum IpcOrigin {
|
||||
Client = "client",
|
||||
Server = "server",
|
||||
}
|
||||
|
||||
export const ipcMessageSchema = z.discriminatedUnion("type", [
|
||||
z.object({
|
||||
type: z.literal(IpcMessageType.Ack),
|
||||
|
|
@ -219,7 +99,7 @@ export const ipcMessageSchema = z.discriminatedUnion("type", [
|
|||
export type IpcMessage = z.infer<typeof ipcMessageSchema>
|
||||
|
||||
/**
|
||||
* Client
|
||||
* IpcClientEvents
|
||||
*/
|
||||
|
||||
export type IpcClientEvents = {
|
||||
|
|
@ -231,7 +111,7 @@ export type IpcClientEvents = {
|
|||
}
|
||||
|
||||
/**
|
||||
* Server
|
||||
* IpcServerEvents
|
||||
*/
|
||||
|
||||
export type IpcServerEvents = {
|
||||
|
|
|
|||
|
|
@ -44,6 +44,26 @@ export const clineAskSchema = z.enum(clineAsks)
|
|||
|
||||
export type ClineAsk = z.infer<typeof clineAskSchema>
|
||||
|
||||
/**
|
||||
* BlockingAsk
|
||||
*/
|
||||
|
||||
export const blockingAsks: ClineAsk[] = [
|
||||
"api_req_failed",
|
||||
"mistake_limit_reached",
|
||||
"completion_result",
|
||||
"resume_task",
|
||||
"resume_completed_task",
|
||||
"command_output",
|
||||
"auto_approval_max_req_reached",
|
||||
] as const
|
||||
|
||||
export type BlockingAsk = (typeof blockingAsks)[number]
|
||||
|
||||
export function isBlockingAsk(ask: ClineAsk): ask is BlockingAsk {
|
||||
return blockingAsks.includes(ask)
|
||||
}
|
||||
|
||||
/**
|
||||
* ClineSay
|
||||
*/
|
||||
|
|
|
|||
98
packages/types/src/task.ts
Normal file
98
packages/types/src/task.ts
Normal file
|
|
@ -0,0 +1,98 @@
|
|||
import { RooCodeEventName } from "./events.js"
|
||||
import { type ClineMessage, type BlockingAsk, type TokenUsage } from "./message.js"
|
||||
import { type ToolUsage, type ToolName } from "./tool.js"
|
||||
|
||||
/**
|
||||
* TaskProviderLike
|
||||
*/
|
||||
|
||||
export interface TaskProviderState {
|
||||
mode?: string
|
||||
}
|
||||
|
||||
export interface TaskProviderLike {
|
||||
readonly cwd: string
|
||||
|
||||
getCurrentCline(): TaskLike | undefined
|
||||
getCurrentTaskStack(): string[]
|
||||
|
||||
initClineWithTask(text?: string, images?: string[], parentTask?: TaskLike): Promise<TaskLike>
|
||||
cancelTask(): Promise<void>
|
||||
clearTask(): Promise<void>
|
||||
postStateToWebview(): Promise<void>
|
||||
|
||||
getState(): Promise<TaskProviderState>
|
||||
|
||||
postMessageToWebview(message: unknown): Promise<void>
|
||||
|
||||
on<K extends keyof TaskProviderEvents>(
|
||||
event: K,
|
||||
listener: (...args: TaskProviderEvents[K]) => void | Promise<void>,
|
||||
): this
|
||||
|
||||
off<K extends keyof TaskProviderEvents>(
|
||||
event: K,
|
||||
listener: (...args: TaskProviderEvents[K]) => void | Promise<void>,
|
||||
): this
|
||||
|
||||
context: {
|
||||
extension?: {
|
||||
packageJSON?: {
|
||||
version?: string
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export type TaskProviderEvents = {
|
||||
[RooCodeEventName.TaskCreated]: [task: TaskLike]
|
||||
|
||||
// Proxied from the Task EventEmitter.
|
||||
[RooCodeEventName.TaskStarted]: [taskId: string]
|
||||
[RooCodeEventName.TaskCompleted]: [taskId: string, tokenUsage: TokenUsage, toolUsage: ToolUsage]
|
||||
[RooCodeEventName.TaskAborted]: [taskId: string]
|
||||
[RooCodeEventName.TaskFocused]: [taskId: string]
|
||||
[RooCodeEventName.TaskUnfocused]: [taskId: string]
|
||||
[RooCodeEventName.TaskActive]: [taskId: string]
|
||||
[RooCodeEventName.TaskIdle]: [taskId: string]
|
||||
}
|
||||
|
||||
/**
|
||||
* TaskLike
|
||||
*/
|
||||
|
||||
export interface TaskLike {
|
||||
readonly taskId: string
|
||||
readonly rootTask?: TaskLike
|
||||
readonly blockingAsk?: BlockingAsk
|
||||
|
||||
on<K extends keyof TaskEvents>(event: K, listener: (...args: TaskEvents[K]) => void | Promise<void>): this
|
||||
off<K extends keyof TaskEvents>(event: K, listener: (...args: TaskEvents[K]) => void | Promise<void>): this
|
||||
|
||||
setMessageResponse(text: string, images?: string[]): void
|
||||
}
|
||||
|
||||
export type TaskEvents = {
|
||||
// Task Lifecycle
|
||||
[RooCodeEventName.TaskStarted]: []
|
||||
[RooCodeEventName.TaskCompleted]: [taskId: string, tokenUsage: TokenUsage, toolUsage: ToolUsage]
|
||||
[RooCodeEventName.TaskAborted]: []
|
||||
[RooCodeEventName.TaskFocused]: []
|
||||
[RooCodeEventName.TaskUnfocused]: []
|
||||
[RooCodeEventName.TaskActive]: [taskId: string]
|
||||
[RooCodeEventName.TaskIdle]: [taskId: string]
|
||||
|
||||
// Subtask Lifecycle
|
||||
[RooCodeEventName.TaskPaused]: []
|
||||
[RooCodeEventName.TaskUnpaused]: []
|
||||
[RooCodeEventName.TaskSpawned]: [taskId: string]
|
||||
|
||||
// Task Execution
|
||||
[RooCodeEventName.Message]: [{ action: "created" | "updated"; message: ClineMessage }]
|
||||
[RooCodeEventName.TaskModeSwitched]: [taskId: string, mode: string]
|
||||
[RooCodeEventName.TaskAskResponded]: []
|
||||
|
||||
// Task Analytics
|
||||
[RooCodeEventName.TaskToolFailed]: [taskId: string, tool: ToolName, error: string]
|
||||
[RooCodeEventName.TaskTokenUsageUpdated]: [taskId: string, tokenUsage: TokenUsage]
|
||||
}
|
||||
|
|
@ -10,21 +10,26 @@ import pWaitFor from "p-wait-for"
|
|||
import { serializeError } from "serialize-error"
|
||||
|
||||
import {
|
||||
type TaskLike,
|
||||
type TaskEvents,
|
||||
type ProviderSettings,
|
||||
type TokenUsage,
|
||||
type ToolUsage,
|
||||
type ToolName,
|
||||
type ContextCondense,
|
||||
type ClineAsk,
|
||||
type ClineMessage,
|
||||
type ClineSay,
|
||||
type ClineAsk,
|
||||
type BlockingAsk,
|
||||
type ToolProgressStatus,
|
||||
DEFAULT_CONSECUTIVE_MISTAKE_LIMIT,
|
||||
type HistoryItem,
|
||||
RooCodeEventName,
|
||||
TelemetryEventName,
|
||||
TodoItem,
|
||||
getApiProtocol,
|
||||
getModelId,
|
||||
DEFAULT_CONSECUTIVE_MISTAKE_LIMIT,
|
||||
isBlockingAsk,
|
||||
} from "@roo-code/types"
|
||||
import { TelemetryService } from "@roo-code/telemetry"
|
||||
import { CloudService } from "@roo-code/cloud"
|
||||
|
|
@ -96,24 +101,6 @@ import { AutoApprovalHandler } from "./AutoApprovalHandler"
|
|||
|
||||
const MAX_EXPONENTIAL_BACKOFF_SECONDS = 600 // 10 minutes
|
||||
|
||||
export type TaskEvents = {
|
||||
message: [{ action: "created" | "updated"; message: ClineMessage }]
|
||||
taskStarted: []
|
||||
taskModeSwitched: [taskId: string, mode: string]
|
||||
taskPaused: []
|
||||
taskUnpaused: []
|
||||
taskAskResponded: []
|
||||
taskAborted: []
|
||||
taskSpawned: [taskId: string]
|
||||
taskCompleted: [taskId: string, tokenUsage: TokenUsage, toolUsage: ToolUsage]
|
||||
taskTokenUsageUpdated: [taskId: string, tokenUsage: TokenUsage]
|
||||
taskToolFailed: [taskId: string, tool: ToolName, error: string]
|
||||
}
|
||||
|
||||
export type TaskEventHandlers = {
|
||||
[K in keyof TaskEvents]: (...args: TaskEvents[K]) => void | Promise<void>
|
||||
}
|
||||
|
||||
export type TaskOptions = {
|
||||
provider: ClineProvider
|
||||
apiConfiguration: ProviderSettings
|
||||
|
|
@ -132,7 +119,7 @@ export type TaskOptions = {
|
|||
onCreated?: (task: Task) => void
|
||||
}
|
||||
|
||||
export class Task extends EventEmitter<TaskEvents> {
|
||||
export class Task extends EventEmitter<TaskEvents> implements TaskLike {
|
||||
todoList?: TodoItem[]
|
||||
readonly taskId: string
|
||||
readonly instanceId: string
|
||||
|
|
@ -189,6 +176,7 @@ export class Task extends EventEmitter<TaskEvents> {
|
|||
providerRef: WeakRef<ClineProvider>
|
||||
private readonly globalStoragePath: string
|
||||
abort: boolean = false
|
||||
blockingAsk?: BlockingAsk
|
||||
didFinishAbortingStream = false
|
||||
abandoned = false
|
||||
isInitialized = false
|
||||
|
|
@ -545,7 +533,7 @@ export class Task extends EventEmitter<TaskEvents> {
|
|||
this.clineMessages.push(message)
|
||||
const provider = this.providerRef.deref()
|
||||
await provider?.postStateToWebview()
|
||||
this.emit("message", { action: "created", message })
|
||||
this.emit(RooCodeEventName.Message, { action: "created", message })
|
||||
await this.saveClineMessages()
|
||||
|
||||
const shouldCaptureMessage = message.partial !== true && CloudService.isEnabled()
|
||||
|
|
@ -567,7 +555,7 @@ export class Task extends EventEmitter<TaskEvents> {
|
|||
private async updateClineMessage(message: ClineMessage) {
|
||||
const provider = this.providerRef.deref()
|
||||
await provider?.postMessageToWebview({ type: "messageUpdated", clineMessage: message })
|
||||
this.emit("message", { action: "updated", message })
|
||||
this.emit(RooCodeEventName.Message, { action: "updated", message })
|
||||
|
||||
const shouldCaptureMessage = message.partial !== true && CloudService.isEnabled()
|
||||
|
||||
|
|
@ -596,7 +584,7 @@ export class Task extends EventEmitter<TaskEvents> {
|
|||
mode: this._taskMode || defaultModeSlug, // Use the task's own mode, not the current provider mode
|
||||
})
|
||||
|
||||
this.emit("taskTokenUsageUpdated", this.taskId, tokenUsage)
|
||||
this.emit(RooCodeEventName.TaskTokenUsageUpdated, this.taskId, tokenUsage)
|
||||
|
||||
await this.providerRef.deref()?.updateTaskHistory(historyItem)
|
||||
} catch (error) {
|
||||
|
|
@ -702,7 +690,17 @@ export class Task extends EventEmitter<TaskEvents> {
|
|||
await this.addToClineMessages({ ts: askTs, type: "ask", ask: type, text, isProtected })
|
||||
}
|
||||
|
||||
// Detect if the task will enter an idle state.
|
||||
const isReady = this.askResponse !== undefined || this.lastMessageTs !== askTs
|
||||
|
||||
if (!partial && !isReady && isBlockingAsk(type)) {
|
||||
this.blockingAsk = type
|
||||
this.emit(RooCodeEventName.TaskIdle, this.taskId)
|
||||
}
|
||||
|
||||
console.log(`[Task#${this.taskId}] pWaitFor askResponse(${type}) -> blocking`)
|
||||
await pWaitFor(() => this.askResponse !== undefined || this.lastMessageTs !== askTs, { interval: 100 })
|
||||
console.log(`[Task#${this.taskId}] pWaitFor askResponse(${type}) -> unblocked (${this.askResponse})`)
|
||||
|
||||
if (this.lastMessageTs !== askTs) {
|
||||
// Could happen if we send multiple asks in a row i.e. with
|
||||
|
|
@ -715,11 +713,22 @@ export class Task extends EventEmitter<TaskEvents> {
|
|||
this.askResponse = undefined
|
||||
this.askResponseText = undefined
|
||||
this.askResponseImages = undefined
|
||||
this.emit("taskAskResponded")
|
||||
|
||||
// Switch back to an active state.
|
||||
if (this.blockingAsk) {
|
||||
this.blockingAsk = undefined
|
||||
this.emit(RooCodeEventName.TaskActive, this.taskId)
|
||||
}
|
||||
|
||||
this.emit(RooCodeEventName.TaskAskResponded)
|
||||
return result
|
||||
}
|
||||
|
||||
async handleWebviewAskResponse(askResponse: ClineAskResponse, text?: string, images?: string[]) {
|
||||
public setMessageResponse(text: string, images?: string[]) {
|
||||
this.handleWebviewAskResponse("messageResponse", text, images)
|
||||
}
|
||||
|
||||
handleWebviewAskResponse(askResponse: ClineAskResponse, text?: string, images?: string[]) {
|
||||
this.askResponse = askResponse
|
||||
this.askResponseText = text
|
||||
this.askResponseImages = images
|
||||
|
|
@ -947,7 +956,7 @@ export class Task extends EventEmitter<TaskEvents> {
|
|||
public async resumePausedTask(lastMessage: string) {
|
||||
// Release this Cline instance from paused state.
|
||||
this.isPaused = false
|
||||
this.emit("taskUnpaused")
|
||||
this.emit(RooCodeEventName.TaskUnpaused)
|
||||
|
||||
// Fake an answer from the subtask that it has completed running and
|
||||
// this is the result of what it has done add the message to the chat
|
||||
|
|
@ -981,7 +990,10 @@ export class Task extends EventEmitter<TaskEvents> {
|
|||
modifiedClineMessages.splice(lastRelevantMessageIndex + 1)
|
||||
}
|
||||
|
||||
// since we don't use api_req_finished anymore, we need to check if the last api_req_started has a cost value, if it doesn't and no cancellation reason to present, then we remove it since it indicates an api request without any partial content streamed
|
||||
// Since we don't use `api_req_finished` anymore, we need to check if the
|
||||
// last `api_req_started` has a cost value, if it doesn't and no
|
||||
// cancellation reason to present, then we remove it since it indicates
|
||||
// an api request without any partial content streamed.
|
||||
const lastApiReqStartedIndex = findLastIndex(
|
||||
modifiedClineMessages,
|
||||
(m) => m.type === "say" && m.say === "api_req_started",
|
||||
|
|
@ -990,6 +1002,7 @@ export class Task extends EventEmitter<TaskEvents> {
|
|||
if (lastApiReqStartedIndex !== -1) {
|
||||
const lastApiReqStarted = modifiedClineMessages[lastApiReqStartedIndex]
|
||||
const { cost, cancelReason }: ClineApiReqInfo = JSON.parse(lastApiReqStarted.text || "{}")
|
||||
|
||||
if (cost === undefined && cancelReason === undefined) {
|
||||
modifiedClineMessages.splice(lastApiReqStartedIndex, 1)
|
||||
}
|
||||
|
|
@ -1009,7 +1022,7 @@ export class Task extends EventEmitter<TaskEvents> {
|
|||
const lastClineMessage = this.clineMessages
|
||||
.slice()
|
||||
.reverse()
|
||||
.find((m) => !(m.ask === "resume_task" || m.ask === "resume_completed_task")) // could be multiple resume tasks
|
||||
.find((m) => !(m.ask === "resume_task" || m.ask === "resume_completed_task")) // Could be multiple resume tasks.
|
||||
|
||||
let askType: ClineAsk
|
||||
if (lastClineMessage?.ask === "completion_result") {
|
||||
|
|
@ -1020,9 +1033,11 @@ export class Task extends EventEmitter<TaskEvents> {
|
|||
|
||||
this.isInitialized = true
|
||||
|
||||
const { response, text, images } = await this.ask(askType) // calls poststatetowebview
|
||||
const { response, text, images } = await this.ask(askType) // Calls `postStateToWebview`.
|
||||
|
||||
let responseText: string | undefined
|
||||
let responseImages: string[] | undefined
|
||||
|
||||
if (response === "messageResponse") {
|
||||
await this.say("user_feedback", text, images)
|
||||
responseText = text
|
||||
|
|
@ -1200,6 +1215,8 @@ export class Task extends EventEmitter<TaskEvents> {
|
|||
}
|
||||
|
||||
public dispose(): void {
|
||||
console.log(`[Task] disposing task ${this.taskId}.${this.instanceId}`)
|
||||
|
||||
// Stop waiting for child task completion.
|
||||
if (this.pauseInterval) {
|
||||
clearInterval(this.pauseInterval)
|
||||
|
|
@ -1261,7 +1278,7 @@ export class Task extends EventEmitter<TaskEvents> {
|
|||
}
|
||||
|
||||
this.abort = true
|
||||
this.emit("taskAborted")
|
||||
this.emit(RooCodeEventName.TaskAborted)
|
||||
|
||||
try {
|
||||
this.dispose() // Call the centralized dispose method
|
||||
|
|
@ -1303,11 +1320,11 @@ export class Task extends EventEmitter<TaskEvents> {
|
|||
let nextUserContent = userContent
|
||||
let includeFileDetails = true
|
||||
|
||||
this.emit("taskStarted")
|
||||
this.emit(RooCodeEventName.TaskStarted)
|
||||
|
||||
while (!this.abort) {
|
||||
const didEndLoop = await this.recursivelyMakeClineRequests(nextUserContent, includeFileDetails)
|
||||
includeFileDetails = false // we only need file details the first time
|
||||
includeFileDetails = false // We only need file details the first time.
|
||||
|
||||
// The way this agentic loop works is that cline will be given a
|
||||
// task that he then calls tools to complete. Unless there's an
|
||||
|
|
@ -1633,13 +1650,13 @@ export class Task extends EventEmitter<TaskEvents> {
|
|||
// If this.abort is already true, it means the user clicked cancel, so we should
|
||||
// treat this as "user_cancelled" rather than "streaming_failed"
|
||||
const cancelReason = this.abort ? "user_cancelled" : "streaming_failed"
|
||||
|
||||
const streamingFailedMessage = this.abort
|
||||
? undefined
|
||||
: (error.message ?? JSON.stringify(serializeError(error), null, 2))
|
||||
|
||||
// Now call abortTask after determining the cancel reason
|
||||
// Now call abortTask after determining the cancel reason.
|
||||
await this.abortTask()
|
||||
|
||||
await abortStream(cancelReason, streamingFailedMessage)
|
||||
|
||||
const history = await provider?.getTaskWithId(this.taskId)
|
||||
|
|
@ -2126,7 +2143,7 @@ export class Task extends EventEmitter<TaskEvents> {
|
|||
this.toolUsage[toolName].failures++
|
||||
|
||||
if (error) {
|
||||
this.emit("taskToolFailed", this.taskId, toolName, error)
|
||||
this.emit(RooCodeEventName.TaskToolFailed, this.taskId, toolName, error)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
import Anthropic from "@anthropic-ai/sdk"
|
||||
import * as vscode from "vscode"
|
||||
|
||||
import { RooCodeEventName } from "@roo-code/types"
|
||||
import { TelemetryService } from "@roo-code/telemetry"
|
||||
|
||||
import { Task } from "../task/Task"
|
||||
|
|
@ -41,11 +42,13 @@ export async function attemptCompletionTool(
|
|||
if (preventCompletionWithOpenTodos && hasIncompleteTodos) {
|
||||
cline.consecutiveMistakeCount++
|
||||
cline.recordToolError("attempt_completion")
|
||||
|
||||
pushToolResult(
|
||||
formatResponse.toolError(
|
||||
"Cannot complete task while there are incomplete todos. Please finish all todos before attempting completion.",
|
||||
),
|
||||
)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
|
|
@ -67,12 +70,12 @@ export async function attemptCompletionTool(
|
|||
await cline.say("completion_result", removeClosingTag("result", result), undefined, false)
|
||||
|
||||
TelemetryService.instance.captureTaskCompleted(cline.taskId)
|
||||
cline.emit("taskCompleted", cline.taskId, cline.getTokenUsage(), cline.toolUsage)
|
||||
cline.emit(RooCodeEventName.TaskCompleted, cline.taskId, cline.getTokenUsage(), cline.toolUsage)
|
||||
|
||||
await cline.ask("command", removeClosingTag("command", command), block.partial).catch(() => {})
|
||||
}
|
||||
} else {
|
||||
// no command, still outputting partial result
|
||||
// No command, still outputting partial result
|
||||
await cline.say("completion_result", removeClosingTag("result", result), undefined, block.partial)
|
||||
}
|
||||
return
|
||||
|
|
@ -90,7 +93,7 @@ export async function attemptCompletionTool(
|
|||
// Users must use execute_command tool separately before attempt_completion
|
||||
await cline.say("completion_result", result, undefined, false)
|
||||
TelemetryService.instance.captureTaskCompleted(cline.taskId)
|
||||
cline.emit("taskCompleted", cline.taskId, cline.getTokenUsage(), cline.toolUsage)
|
||||
cline.emit(RooCodeEventName.TaskCompleted, cline.taskId, cline.getTokenUsage(), cline.toolUsage)
|
||||
|
||||
if (cline.parentTask) {
|
||||
const didApprove = await askFinishSubTaskApproval()
|
||||
|
|
|
|||
|
|
@ -1,5 +1,7 @@
|
|||
import delay from "delay"
|
||||
|
||||
import { RooCodeEventName } from "@roo-code/types"
|
||||
|
||||
import { ToolUse, AskApproval, HandleError, PushToolResult, RemoveClosingTag } from "../../shared/tools"
|
||||
import { Task } from "../task/Task"
|
||||
import { defaultModeSlug, getModeBySlug } from "../../shared/modes"
|
||||
|
|
@ -93,14 +95,14 @@ export async function newTaskTool(
|
|||
// Delay to allow mode change to take effect
|
||||
await delay(500)
|
||||
|
||||
cline.emit("taskSpawned", newCline.taskId)
|
||||
cline.emit(RooCodeEventName.TaskSpawned, newCline.taskId)
|
||||
|
||||
pushToolResult(`Successfully created new task in ${targetMode.name} mode with message: ${unescapedMessage}`)
|
||||
|
||||
// Set the isPaused flag to true so the parent
|
||||
// task can wait for the sub-task to finish.
|
||||
cline.isPaused = true
|
||||
cline.emit("taskPaused")
|
||||
cline.emit(RooCodeEventName.TaskPaused)
|
||||
|
||||
return
|
||||
}
|
||||
|
|
|
|||
|
|
@ -10,6 +10,8 @@ import pWaitFor from "p-wait-for"
|
|||
import * as vscode from "vscode"
|
||||
|
||||
import {
|
||||
type TaskProviderLike,
|
||||
type TaskProviderEvents,
|
||||
type GlobalState,
|
||||
type ProviderName,
|
||||
type ProviderSettings,
|
||||
|
|
@ -24,6 +26,7 @@ import {
|
|||
type TerminalActionPromptType,
|
||||
type HistoryItem,
|
||||
type CloudUserInfo,
|
||||
RooCodeEventName,
|
||||
requestyDefaultModelId,
|
||||
openRouterDefaultModelId,
|
||||
glamaDefaultModelId,
|
||||
|
|
@ -34,8 +37,6 @@ import {
|
|||
import { TelemetryService } from "@roo-code/telemetry"
|
||||
import { CloudService, getRooCodeApiUrl } from "@roo-code/cloud"
|
||||
|
||||
import { t } from "../../i18n"
|
||||
import { setPanel } from "../../activate/registerCommands"
|
||||
import { Package } from "../../shared/package"
|
||||
import { findLast } from "../../shared/array"
|
||||
import { supportPrompt } from "../../shared/support-prompt"
|
||||
|
|
@ -44,10 +45,15 @@ import { ExtensionMessage, MarketplaceInstalledMetadata } from "../../shared/Ext
|
|||
import { Mode, defaultModeSlug, getModeBySlug } from "../../shared/modes"
|
||||
import { experimentDefault } from "../../shared/experiments"
|
||||
import { formatLanguage } from "../../shared/language"
|
||||
import { WebviewMessage } from "../../shared/WebviewMessage"
|
||||
import { EMBEDDING_MODEL_PROFILES } from "../../shared/embeddingModels"
|
||||
import { ProfileValidator } from "../../shared/ProfileValidator"
|
||||
|
||||
import { Terminal } from "../../integrations/terminal/Terminal"
|
||||
import { downloadTask } from "../../integrations/misc/export-markdown"
|
||||
import { getTheme } from "../../integrations/theme/getTheme"
|
||||
import WorkspaceTracker from "../../integrations/workspace/WorkspaceTracker"
|
||||
|
||||
import { McpHub } from "../../services/mcp/McpHub"
|
||||
import { McpServerManager } from "../../services/mcp/McpServerManager"
|
||||
import { MarketplaceManager } from "../../services/marketplace"
|
||||
|
|
@ -55,36 +61,37 @@ import { ShadowCheckpointService } from "../../services/checkpoints/ShadowCheckp
|
|||
import { CodeIndexManager } from "../../services/code-index/manager"
|
||||
import type { IndexProgressUpdate } from "../../services/code-index/interfaces/manager"
|
||||
import { MdmService } from "../../services/mdm/MdmService"
|
||||
|
||||
import { fileExistsAtPath } from "../../utils/fs"
|
||||
import { setTtsEnabled, setTtsSpeed } from "../../utils/tts"
|
||||
import { getWorkspaceGitInfo } from "../../utils/git"
|
||||
import { getWorkspacePath } from "../../utils/path"
|
||||
|
||||
import { setPanel } from "../../activate/registerCommands"
|
||||
|
||||
import { t } from "../../i18n"
|
||||
|
||||
import { buildApiHandler } from "../../api"
|
||||
import { forceFullModelDetailsLoad, hasLoadedFullDetails } from "../../api/providers/fetchers/lmstudio"
|
||||
|
||||
import { ContextProxy } from "../config/ContextProxy"
|
||||
import { ProviderSettingsManager } from "../config/ProviderSettingsManager"
|
||||
import { CustomModesManager } from "../config/CustomModesManager"
|
||||
import { buildApiHandler } from "../../api"
|
||||
import { Task, TaskOptions } from "../task/Task"
|
||||
import { getSystemPromptFilePath } from "../prompts/sections/custom-system-prompt"
|
||||
|
||||
import { webviewMessageHandler } from "./webviewMessageHandler"
|
||||
import { getNonce } from "./getNonce"
|
||||
import { getUri } from "./getUri"
|
||||
import { getSystemPromptFilePath } from "../prompts/sections/custom-system-prompt"
|
||||
import { getWorkspacePath } from "../../utils/path"
|
||||
import { webviewMessageHandler } from "./webviewMessageHandler"
|
||||
import { WebviewMessage } from "../../shared/WebviewMessage"
|
||||
import { EMBEDDING_MODEL_PROFILES } from "../../shared/embeddingModels"
|
||||
import { ProfileValidator } from "../../shared/ProfileValidator"
|
||||
import { getWorkspaceGitInfo } from "../../utils/git"
|
||||
import { forceFullModelDetailsLoad, hasLoadedFullDetails } from "../../api/providers/fetchers/lmstudio"
|
||||
|
||||
/**
|
||||
* https://github.com/microsoft/vscode-webview-ui-toolkit-samples/blob/main/default/weather-webview/src/providers/WeatherViewProvider.ts
|
||||
* https://github.com/KumarVariable/vscode-extension-sidebar-html/blob/master/src/customSidebarViewProvider.ts
|
||||
*/
|
||||
|
||||
export type ClineProviderEvents = {
|
||||
taskCreated: [task: Task]
|
||||
}
|
||||
|
||||
export class ClineProvider
|
||||
extends EventEmitter<ClineProviderEvents>
|
||||
implements vscode.WebviewViewProvider, TelemetryPropertiesProvider
|
||||
extends EventEmitter<TaskProviderEvents>
|
||||
implements vscode.WebviewViewProvider, TelemetryPropertiesProvider, TaskProviderLike
|
||||
{
|
||||
// Used in package.json as the view's id. This value cannot be changed due
|
||||
// to how VSCode caches views based on their id, and updating the id would
|
||||
|
|
@ -155,7 +162,7 @@ export class ClineProvider
|
|||
|
||||
this.marketplaceManager = new MarketplaceManager(this.context, this.customModesManager)
|
||||
|
||||
// Initialize cloud profile sync
|
||||
// Initialize Roo Code Cloud profile sync.
|
||||
this.initializeCloudProfileSync().catch((error) => {
|
||||
this.log(`Failed to initialize cloud profile sync: ${error}`)
|
||||
})
|
||||
|
|
@ -226,17 +233,18 @@ export class ClineProvider
|
|||
}
|
||||
}
|
||||
|
||||
// Adds a new Cline instance to clineStack, marking the start of a new task.
|
||||
// Adds a new Task instance to clineStack, marking the start of a new task.
|
||||
// The instance is pushed to the top of the stack (LIFO order).
|
||||
// When the task is completed, the top instance is removed, reactivating the previous task.
|
||||
async addClineToStack(cline: Task) {
|
||||
console.log(`[subtasks] adding task ${cline.taskId}.${cline.instanceId} to stack`)
|
||||
async addClineToStack(task: Task) {
|
||||
console.log(`[subtasks] adding task ${task.taskId}.${task.instanceId} to stack`)
|
||||
|
||||
// Add this cline instance into the stack that represents the order of all the called tasks.
|
||||
this.clineStack.push(cline)
|
||||
this.clineStack.push(task)
|
||||
task.emit(RooCodeEventName.TaskFocused)
|
||||
|
||||
// Perform special setup provider specific tasks
|
||||
await this.performPreparationTasks(cline)
|
||||
// Perform special setup provider specific tasks.
|
||||
await this.performPreparationTasks(task)
|
||||
|
||||
// Ensure getState() resolves correctly.
|
||||
const state = await this.getState()
|
||||
|
|
@ -247,7 +255,8 @@ export class ClineProvider
|
|||
}
|
||||
|
||||
async performPreparationTasks(cline: Task) {
|
||||
// LMStudio: we need to force model loading in order to read its context size; we do it now since we're starting a task with that model selected
|
||||
// LMStudio: We need to force model loading in order to read its context
|
||||
// size; we do it now since we're starting a task with that model selected.
|
||||
if (cline.apiConfiguration && cline.apiConfiguration.apiProvider === "lmstudio") {
|
||||
try {
|
||||
if (!hasLoadedFullDetails(cline.apiConfiguration.lmStudioModelId!)) {
|
||||
|
|
@ -271,24 +280,26 @@ export class ClineProvider
|
|||
}
|
||||
|
||||
// Pop the top Cline instance from the stack.
|
||||
let cline = this.clineStack.pop()
|
||||
let task = this.clineStack.pop()
|
||||
|
||||
if (cline) {
|
||||
console.log(`[subtasks] removing task ${cline.taskId}.${cline.instanceId} from stack`)
|
||||
if (task) {
|
||||
console.log(`[subtasks] removing task ${task.taskId}.${task.instanceId} from stack`)
|
||||
|
||||
try {
|
||||
// Abort the running task and set isAbandoned to true so
|
||||
// all running promises will exit as well.
|
||||
await cline.abortTask(true)
|
||||
await task.abortTask(true)
|
||||
} catch (e) {
|
||||
this.log(
|
||||
`[subtasks] encountered error while aborting task ${cline.taskId}.${cline.instanceId}: ${e.message}`,
|
||||
`[subtasks] encountered error while aborting task ${task.taskId}.${task.instanceId}: ${e.message}`,
|
||||
)
|
||||
}
|
||||
|
||||
task.emit(RooCodeEventName.TaskUnfocused)
|
||||
|
||||
// Make sure no reference kept, once promises end it will be
|
||||
// garbage collected.
|
||||
cline = undefined
|
||||
task = undefined
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -343,8 +354,13 @@ export class ClineProvider
|
|||
|
||||
async dispose() {
|
||||
this.log("Disposing ClineProvider...")
|
||||
await this.removeClineFromStack()
|
||||
this.log("Cleared task")
|
||||
|
||||
// Clear all tasks from the stack.
|
||||
while (this.clineStack.length > 0) {
|
||||
await this.removeClineFromStack()
|
||||
}
|
||||
|
||||
this.log("Cleared all tasks")
|
||||
|
||||
if (this.view && "dispose" in this.view) {
|
||||
this.view.dispose()
|
||||
|
|
@ -375,6 +391,9 @@ export class ClineProvider
|
|||
this.log("Disposed all disposables")
|
||||
ClineProvider.activeInstances.delete(this)
|
||||
|
||||
// Clean up any event listeners attached to this provider
|
||||
this.removeAllListeners()
|
||||
|
||||
McpServerManager.unregisterProvider(this)
|
||||
}
|
||||
|
||||
|
|
@ -403,6 +422,7 @@ export class ClineProvider
|
|||
|
||||
public static async isActiveTask(): Promise<boolean> {
|
||||
const visibleProvider = await ClineProvider.getInstance()
|
||||
|
||||
if (!visibleProvider) {
|
||||
return false
|
||||
}
|
||||
|
|
@ -653,7 +673,7 @@ export class ClineProvider
|
|||
rootTask: this.clineStack.length > 0 ? this.clineStack[0] : undefined,
|
||||
parentTask,
|
||||
taskNumber: this.clineStack.length + 1,
|
||||
onCreated: (instance) => this.emit("taskCreated", instance),
|
||||
onCreated: (instance) => this.emit(RooCodeEventName.TaskCreated, instance),
|
||||
...options,
|
||||
})
|
||||
|
||||
|
|
@ -732,7 +752,7 @@ export class ClineProvider
|
|||
rootTask: historyItem.rootTask,
|
||||
parentTask: historyItem.parentTask,
|
||||
taskNumber: historyItem.number,
|
||||
onCreated: (instance) => this.emit("taskCreated", instance),
|
||||
onCreated: (instance) => this.emit(RooCodeEventName.TaskCreated, instance),
|
||||
})
|
||||
|
||||
await this.addClineToStack(task)
|
||||
|
|
@ -942,7 +962,7 @@ export class ClineProvider
|
|||
|
||||
if (cline) {
|
||||
TelemetryService.instance.captureModeSwitch(cline.taskId, newMode)
|
||||
cline.emit("taskModeSwitched", cline.taskId, newMode)
|
||||
cline.emit(RooCodeEventName.TaskModeSwitched, cline.taskId, newMode)
|
||||
|
||||
// Store the current mode in case we need to rollback
|
||||
const previousMode = (cline as any)._taskMode
|
||||
|
|
|
|||
|
|
@ -16,7 +16,6 @@ import { Task, TaskOptions } from "../../task/Task"
|
|||
import { safeWriteJson } from "../../../utils/safeWriteJson"
|
||||
|
||||
import { ClineProvider } from "../ClineProvider"
|
||||
import { AsyncInvokeOutputDataConfig } from "@aws-sdk/client-bedrock-runtime"
|
||||
|
||||
// Mock setup must come before imports
|
||||
vi.mock("../../prompts/sections/custom-instructions")
|
||||
|
|
@ -215,6 +214,7 @@ vi.mock("../../task/Task", () => ({
|
|||
setParentTask: vi.fn(),
|
||||
setRootTask: vi.fn(),
|
||||
taskId: taskId || "test-task-id",
|
||||
emit: vi.fn(),
|
||||
}),
|
||||
),
|
||||
}))
|
||||
|
|
|
|||
|
|
@ -900,12 +900,18 @@ describe("ClineProvider - Sticky Mode", () => {
|
|||
it("should handle errors during mode switch gracefully", async () => {
|
||||
await provider.resolveWebviewView(mockWebviewView)
|
||||
|
||||
// Create a mock task that throws on emit
|
||||
// Create a mock task that throws on emit only for specific events
|
||||
let emitCallCount = 0
|
||||
const mockTask = {
|
||||
taskId: "test-task-id",
|
||||
_taskMode: "code",
|
||||
emit: vi.fn().mockImplementation(() => {
|
||||
throw new Error("Emit failed")
|
||||
emit: vi.fn().mockImplementation((event) => {
|
||||
emitCallCount++
|
||||
// Only throw on the second emit call (taskModeSwitched event)
|
||||
// The first call is for TaskFocused in addClineToStack
|
||||
if (emitCallCount === 2 && event === "taskModeSwitched") {
|
||||
throw new Error("Emit failed")
|
||||
}
|
||||
}),
|
||||
saveClineMessages: vi.fn(),
|
||||
clineMessages: [],
|
||||
|
|
@ -915,13 +921,42 @@ describe("ClineProvider - Sticky Mode", () => {
|
|||
// Add task to provider stack
|
||||
await provider.addClineToStack(mockTask as any)
|
||||
|
||||
// Mock getGlobalState to return task history
|
||||
vi.spyOn(provider as any, "getGlobalState").mockReturnValue([
|
||||
{
|
||||
id: mockTask.taskId,
|
||||
ts: Date.now(),
|
||||
task: "Test task",
|
||||
number: 1,
|
||||
tokensIn: 0,
|
||||
tokensOut: 0,
|
||||
cacheWrites: 0,
|
||||
cacheReads: 0,
|
||||
totalCost: 0,
|
||||
},
|
||||
])
|
||||
|
||||
// Mock updateTaskHistory
|
||||
vi.spyOn(provider, "updateTaskHistory").mockImplementation(() => Promise.resolve([]))
|
||||
|
||||
// Mock console.error to suppress error output
|
||||
const consoleErrorSpy = vi.spyOn(console, "error").mockImplementation(() => {})
|
||||
|
||||
// Clear previous mock calls to isolate this test
|
||||
vi.mocked(mockContext.globalState.update).mockClear()
|
||||
|
||||
// The handleModeSwitch method doesn't catch errors from emit, so it will throw
|
||||
// This is the actual behavior based on the test failure
|
||||
// The error is thrown before the task's mode is updated
|
||||
await expect(provider.handleModeSwitch("architect")).rejects.toThrow("Emit failed")
|
||||
|
||||
// Since the error is thrown before updating the task's _taskMode,
|
||||
// neither the task mode nor global state are updated
|
||||
const modeCalls = vi.mocked(mockContext.globalState.update).mock.calls.filter((call) => call[0] === "mode")
|
||||
expect(modeCalls.length).toBe(0)
|
||||
|
||||
// The task's mode should NOT have been updated since the error occurred first
|
||||
expect(mockTask._taskMode).toBe("code")
|
||||
|
||||
consoleErrorSpy.mockRestore()
|
||||
})
|
||||
|
||||
|
|
|
|||
|
|
@ -5,22 +5,21 @@ import * as path from "path"
|
|||
import * as os from "os"
|
||||
|
||||
import {
|
||||
RooCodeAPI,
|
||||
RooCodeSettings,
|
||||
RooCodeEvents,
|
||||
type RooCodeAPI,
|
||||
type RooCodeSettings,
|
||||
type RooCodeEvents,
|
||||
type ProviderSettings,
|
||||
type ProviderSettingsEntry,
|
||||
type TaskEvent,
|
||||
RooCodeEventName,
|
||||
ProviderSettings,
|
||||
ProviderSettingsEntry,
|
||||
TaskCommandName,
|
||||
isSecretStateKey,
|
||||
IpcOrigin,
|
||||
IpcMessageType,
|
||||
TaskCommandName,
|
||||
TaskEvent,
|
||||
} from "@roo-code/types"
|
||||
import { IpcServer } from "@roo-code/ipc"
|
||||
|
||||
import { Package } from "../shared/package"
|
||||
import { getWorkspacePath } from "../utils/path"
|
||||
import { ClineProvider } from "../core/webview/ClineProvider"
|
||||
import { openClineInNewTab } from "../activate/registerCommands"
|
||||
|
||||
|
|
@ -214,58 +213,86 @@ export class API extends EventEmitter<RooCodeEvents> implements RooCodeAPI {
|
|||
}
|
||||
|
||||
private registerListeners(provider: ClineProvider) {
|
||||
provider.on("taskCreated", (cline) => {
|
||||
cline.on("taskStarted", async () => {
|
||||
this.emit(RooCodeEventName.TaskStarted, cline.taskId)
|
||||
this.taskMap.set(cline.taskId, provider)
|
||||
await this.fileLog(`[${new Date().toISOString()}] taskStarted -> ${cline.taskId}\n`)
|
||||
provider.on(RooCodeEventName.TaskCreated, (task) => {
|
||||
// Task Lifecycle
|
||||
|
||||
task.on(RooCodeEventName.TaskStarted, async () => {
|
||||
this.emit(RooCodeEventName.TaskStarted, task.taskId)
|
||||
this.taskMap.set(task.taskId, provider)
|
||||
await this.fileLog(`[${new Date().toISOString()}] taskStarted -> ${task.taskId}\n`)
|
||||
})
|
||||
|
||||
cline.on("message", async (message) => {
|
||||
this.emit(RooCodeEventName.Message, { taskId: cline.taskId, ...message })
|
||||
task.on(RooCodeEventName.TaskCompleted, async (_, tokenUsage, toolUsage) => {
|
||||
let isSubtask = false
|
||||
|
||||
if (typeof task.rootTask !== "undefined") {
|
||||
isSubtask = true
|
||||
}
|
||||
|
||||
this.emit(RooCodeEventName.TaskCompleted, task.taskId, tokenUsage, toolUsage, { isSubtask: isSubtask })
|
||||
this.taskMap.delete(task.taskId)
|
||||
|
||||
await this.fileLog(
|
||||
`[${new Date().toISOString()}] taskCompleted -> ${task.taskId} | ${JSON.stringify(tokenUsage, null, 2)} | ${JSON.stringify(toolUsage, null, 2)}\n`,
|
||||
)
|
||||
})
|
||||
|
||||
task.on(RooCodeEventName.TaskAborted, () => {
|
||||
this.emit(RooCodeEventName.TaskAborted, task.taskId)
|
||||
this.taskMap.delete(task.taskId)
|
||||
})
|
||||
|
||||
// Optional:
|
||||
// RooCodeEventName.TaskFocused
|
||||
// RooCodeEventName.TaskUnfocused
|
||||
// RooCodeEventName.TaskActive
|
||||
// RooCodeEventName.TaskIdle
|
||||
|
||||
// Subtask Lifecycle
|
||||
|
||||
task.on(RooCodeEventName.TaskPaused, () => {
|
||||
this.emit(RooCodeEventName.TaskPaused, task.taskId)
|
||||
})
|
||||
|
||||
task.on(RooCodeEventName.TaskUnpaused, () => {
|
||||
this.emit(RooCodeEventName.TaskUnpaused, task.taskId)
|
||||
})
|
||||
|
||||
task.on(RooCodeEventName.TaskSpawned, (childTaskId) => {
|
||||
this.emit(RooCodeEventName.TaskSpawned, task.taskId, childTaskId)
|
||||
})
|
||||
|
||||
// Task Execution
|
||||
|
||||
task.on(RooCodeEventName.Message, async (message) => {
|
||||
this.emit(RooCodeEventName.Message, { taskId: task.taskId, ...message })
|
||||
|
||||
if (message.message.partial !== true) {
|
||||
await this.fileLog(`[${new Date().toISOString()}] ${JSON.stringify(message.message, null, 2)}\n`)
|
||||
}
|
||||
})
|
||||
|
||||
cline.on("taskModeSwitched", (taskId, mode) => this.emit(RooCodeEventName.TaskModeSwitched, taskId, mode))
|
||||
|
||||
cline.on("taskAskResponded", () => this.emit(RooCodeEventName.TaskAskResponded, cline.taskId))
|
||||
|
||||
cline.on("taskAborted", () => {
|
||||
this.emit(RooCodeEventName.TaskAborted, cline.taskId)
|
||||
this.taskMap.delete(cline.taskId)
|
||||
task.on(RooCodeEventName.TaskModeSwitched, (taskId, mode) => {
|
||||
this.emit(RooCodeEventName.TaskModeSwitched, taskId, mode)
|
||||
})
|
||||
|
||||
cline.on("taskCompleted", async (_, tokenUsage, toolUsage) => {
|
||||
let isSubtask = false
|
||||
|
||||
if (cline.rootTask != undefined) {
|
||||
isSubtask = true
|
||||
}
|
||||
|
||||
this.emit(RooCodeEventName.TaskCompleted, cline.taskId, tokenUsage, toolUsage, { isSubtask: isSubtask })
|
||||
this.taskMap.delete(cline.taskId)
|
||||
|
||||
await this.fileLog(
|
||||
`[${new Date().toISOString()}] taskCompleted -> ${cline.taskId} | ${JSON.stringify(tokenUsage, null, 2)} | ${JSON.stringify(toolUsage, null, 2)}\n`,
|
||||
)
|
||||
task.on(RooCodeEventName.TaskAskResponded, () => {
|
||||
this.emit(RooCodeEventName.TaskAskResponded, task.taskId)
|
||||
})
|
||||
|
||||
cline.on("taskSpawned", (childTaskId) => this.emit(RooCodeEventName.TaskSpawned, cline.taskId, childTaskId))
|
||||
cline.on("taskPaused", () => this.emit(RooCodeEventName.TaskPaused, cline.taskId))
|
||||
cline.on("taskUnpaused", () => this.emit(RooCodeEventName.TaskUnpaused, cline.taskId))
|
||||
// Task Analytics
|
||||
|
||||
cline.on("taskTokenUsageUpdated", (_, usage) =>
|
||||
this.emit(RooCodeEventName.TaskTokenUsageUpdated, cline.taskId, usage),
|
||||
)
|
||||
task.on(RooCodeEventName.TaskToolFailed, (taskId, tool, error) => {
|
||||
this.emit(RooCodeEventName.TaskToolFailed, taskId, tool, error)
|
||||
})
|
||||
|
||||
cline.on("taskToolFailed", (taskId, tool, error) =>
|
||||
this.emit(RooCodeEventName.TaskToolFailed, taskId, tool, error),
|
||||
)
|
||||
task.on(RooCodeEventName.TaskTokenUsageUpdated, (_, usage) => {
|
||||
this.emit(RooCodeEventName.TaskTokenUsageUpdated, task.taskId, usage)
|
||||
})
|
||||
|
||||
this.emit(RooCodeEventName.TaskCreated, cline.taskId)
|
||||
// Let's go!
|
||||
|
||||
this.emit(RooCodeEventName.TaskCreated, task.taskId)
|
||||
})
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -251,7 +251,7 @@ function getSelectedModel({
|
|||
case "cerebras": {
|
||||
const id = apiConfiguration.apiModelId ?? cerebrasDefaultModelId
|
||||
const info = cerebrasModels[id as keyof typeof cerebrasModels]
|
||||
return { id, info }
|
||||
return { id, info }
|
||||
}
|
||||
case "sambanova": {
|
||||
const id = apiConfiguration.apiModelId ?? sambaNovaDefaultModelId
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue