diff --git a/.changeset/orange-items-remember.md b/.changeset/orange-items-remember.md new file mode 100644 index 0000000000..180538a200 --- /dev/null +++ b/.changeset/orange-items-remember.md @@ -0,0 +1,5 @@ +--- +"roo-cline": patch +--- + +Consolidate logic that computes the current diff strategy diff --git a/.github/ISSUE_TEMPLATE/bug_report.yml b/.github/ISSUE_TEMPLATE/bug_report.yml index dc66b4f390..91a6d5620b 100644 --- a/.github/ISSUE_TEMPLATE/bug_report.yml +++ b/.github/ISSUE_TEMPLATE/bug_report.yml @@ -1,68 +1,80 @@ name: Bug Report -description: File a bug report +description: Clearly report a bug with detailed repro steps labels: ["bug"] body: - - type: input - id: version - attributes: - label: Which version of the app are you using? - description: Please specify the app version you're using (e.g. v3.3.1) - validations: - required: true - - type: dropdown - id: provider - attributes: - label: Which API Provider are you using? - multiple: false - options: - - OpenRouter - - Anthropic - - Google Gemini - - DeepSeek - - OpenAI - - OpenAI Compatible - - GCP Vertex AI - - AWS Bedrock - - Glama - - VS Code LM API - - LM Studio - - Ollama - validations: - required: true - - type: input - id: model - attributes: - label: Which Model are you using? - description: Please specify the model you're using (e.g. Claude 3.7 Sonnet) - validations: - required: true - - type: textarea - id: what-happened - attributes: - label: What happened? - description: Also tell us, what did you expect to happen? - placeholder: Tell us what you see! - validations: - required: true - - type: textarea - id: steps - attributes: - label: Steps to reproduce - description: How do you trigger this bug? Please walk us through it step by step. - value: | - 1. - 2. - 3. - validations: - required: true - - type: textarea - id: logs - attributes: - label: Relevant API REQUEST output - description: Please copy and paste any relevant output. This will be automatically formatted into code, so no need for backticks. - render: shell - - type: textarea - id: additional-context - attributes: - label: Additional context - description: Add any other context about the problem here, such as screenshots or related issues. + - type: input + id: version + attributes: + label: App Version + description: Specify exactly which version you're using (e.g., v3.3.1) + validations: + required: true + + - type: dropdown + id: provider + attributes: + label: API Provider + description: Choose the API provider involved + multiple: false + options: + - OpenRouter + - Anthropic + - Google Gemini + - DeepSeek + - OpenAI + - OpenAI Compatible + - GCP Vertex AI + - AWS Bedrock + - Requesty + - Glama + - VS Code LM API + - LM Studio + - Ollama + validations: + required: true + + - type: input + id: model + attributes: + label: Model Used + description: Clearly specify the exact model (e.g., Claude 3.7 Sonnet) + validations: + required: true + + - type: textarea + id: what-happened + attributes: + label: Actual vs. Expected Behavior + description: Clearly state what actually happened and what you expected instead. + placeholder: Provide precise details of the issue here. + validations: + required: true + + - type: textarea + id: steps + attributes: + label: Detailed Steps to Reproduce + description: | + List the exact steps someone must follow to reproduce this bug: + 1. Starting conditions (software state, settings, environment) + 2. Precise actions taken (every click, selection, input) + 3. Clearly observe and report outcomes + value: | + 1. + 2. + 3. + validations: + required: true + + - type: textarea + id: logs + attributes: + label: Relevant API Request Output + description: Paste relevant API logs or outputs here (formatted automatically as code) + render: shell + + - type: textarea + id: additional-context + attributes: + label: Additional Context + description: Include extra details, screenshots, or related issues. diff --git a/.gitignore b/.gitignore index 02fdf8f88e..cc6551885f 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,4 @@ +.pnpm-store dist out out-* diff --git a/README.md b/README.md index 69ffef2f5d..47709fab60 100644 --- a/README.md +++ b/README.md @@ -180,24 +180,26 @@ Thanks to all our contributors who have helped make Roo Code better! -| mrubens
mrubens
| saoudrizwan
saoudrizwan
| cte
cte
| samhvw8
samhvw8
| daniel-lxs
daniel-lxs
| a8trejo
a8trejo
| -| :-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------: | :------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------: | :---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------: | :----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------: | :------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------: | :---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------: | -| ColemanRoo
ColemanRoo
| stea9499
stea9499
| joemanley201
joemanley201
| System233
System233
| jquanton
jquanton
| nissa-seru
nissa-seru
| -| NyxJae
NyxJae
| hannesrudolph
hannesrudolph
| MuriloFP
MuriloFP
| punkpeye
punkpeye
| d-oit
d-oit
| monotykamary
monotykamary
| -| vigneshsubbiah16
vigneshsubbiah16
| lloydchang
lloydchang
| cannuri
cannuri
| Szpadel
Szpadel
| psv2522
psv2522
| Premshay
Premshay
| -| Smartsheet-JB-Brown
Smartsheet-JB-Brown
| wkordalski
wkordalski
| qdaxb
qdaxb
| feifei325
feifei325
| lupuletic
lupuletic
| KJ7LNW
KJ7LNW
| -| olweraltuve
olweraltuve
| RaySinner
RaySinner
| pugazhendhi-m
pugazhendhi-m
| pdecat
pdecat
| emshvac
emshvac
| afshawnlotfi
afshawnlotfi
| -| aitoroses
aitoroses
| dtrugman
dtrugman
| diarmidmackenzie
diarmidmackenzie
| sammcj
sammcj
| aheizi
aheizi
| Lunchb0ne
Lunchb0ne
| -| yt3trees
yt3trees
| yongjer
yongjer
| vincentsong
vincentsong
| teddyOOXX
teddyOOXX
| eonghk
eonghk
| arthurauffray
arthurauffray
| -| heyseth
heyseth
| anton-otee
anton-otee
| benzntech
benzntech
| GitlyHallows
GitlyHallows
| jcbdev
jcbdev
| Chenjiayuan195
Chenjiayuan195
| -| mdp
mdp
| napter
napter
| philfung
philfung
| AMHesch
AMHesch
| vladstudio
vladstudio
| Yoshino-Yukitaro
Yoshino-Yukitaro
| -| ashktn
ashktn
| bannzai
bannzai
| dairui1
dairui1
| dqroid
dqroid
| im47cn
im47cn
| jwcraig
jwcraig
| -| kinandan
kinandan
| kohii
kohii
| lightrabbit
lightrabbit
| olup
olup
| moqimoqidea
moqimoqidea
| mosleyit
mosleyit
| -| oprstchn
oprstchn
| philipnext
philipnext
| refactorthis
refactorthis
| samir-nimbly
samir-nimbly
| shaybc
shaybc
| shohei-ihaya
shohei-ihaya
| -| student20880
student20880
| cdlliuy
cdlliuy
| PretzelVector
PretzelVector
| nevermorec
nevermorec
| adamwlarson
adamwlarson
| alarno
alarno
| -| andreastempsch
andreastempsch
| Atlogit
Atlogit
| chadgauth
chadgauth
| dleen
dleen
| dbasclpy
dbasclpy
| linegel
linegel
| -| celestial-vault
celestial-vault
| franekp
franekp
| DeXtroTip
DeXtroTip
| hesara
hesara
| eltociear
eltociear
| Jdo300
Jdo300
| -| libertyteeth
libertyteeth
| mamertofabian
mamertofabian
| | | | | +| mrubens
mrubens
| saoudrizwan
saoudrizwan
| cte
cte
| samhvw8
samhvw8
| daniel-lxs
daniel-lxs
| a8trejo
a8trejo
| +| :---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------: | :------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------: | :---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------: | :-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------: | :---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------: | :------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------: | +| ColemanRoo
ColemanRoo
| stea9499
stea9499
| joemanley201
joemanley201
| System233
System233
| nissa-seru
nissa-seru
| jquanton
jquanton
| +| hannesrudolph
hannesrudolph
| NyxJae
NyxJae
| MuriloFP
MuriloFP
| punkpeye
punkpeye
| d-oit
d-oit
| monotykamary
monotykamary
| +| cannuri
cannuri
| lloydchang
lloydchang
| vigneshsubbiah16
vigneshsubbiah16
| KJ7LNW
KJ7LNW
| Szpadel
Szpadel
| lupuletic
lupuletic
| +| feifei325
feifei325
| qdaxb
qdaxb
| wkordalski
wkordalski
| Smartsheet-JB-Brown
Smartsheet-JB-Brown
| Premshay
Premshay
| psv2522
psv2522
| +| olweraltuve
olweraltuve
| RaySinner
RaySinner
| aheizi
aheizi
| afshawnlotfi
afshawnlotfi
| diarmidmackenzie
diarmidmackenzie
| emshvac
emshvac
| +| pdecat
pdecat
| pugazhendhi-m
pugazhendhi-m
| Lunchb0ne
Lunchb0ne
| sammcj
sammcj
| dtrugman
dtrugman
| aitoroses
aitoroses
| +| yt3trees
yt3trees
| yongjer
yongjer
| vincentsong
vincentsong
| teddyOOXX
teddyOOXX
| eonghk
eonghk
| arthurauffray
arthurauffray
| +| heyseth
heyseth
| philfung
philfung
| napter
napter
| mdp
mdp
| Chenjiayuan195
Chenjiayuan195
| jcbdev
jcbdev
| +| GitlyHallows
GitlyHallows
| benzntech
benzntech
| anton-otee
anton-otee
| lightrabbit
lightrabbit
| kohii
kohii
| kinandan
kinandan
| +| jwcraig
jwcraig
| im47cn
im47cn
| dqroid
dqroid
| dairui1
dairui1
| bannzai
bannzai
| ashktn
ashktn
| +| Yoshino-Yukitaro
Yoshino-Yukitaro
| vladstudio
vladstudio
| tgfjt
tgfjt
| AMHesch
AMHesch
| olup
olup
| moqimoqidea
moqimoqidea
| +| mosleyit
mosleyit
| oprstchn
oprstchn
| philipnext
philipnext
| refactorthis
refactorthis
| samir-nimbly
samir-nimbly
| shaybc
shaybc
| +| shohei-ihaya
shohei-ihaya
| student20880
student20880
| cdlliuy
cdlliuy
| PretzelVector
PretzelVector
| nevermorec
nevermorec
| adamwlarson
adamwlarson
| +| alarno
alarno
| andreastempsch
andreastempsch
| Atlogit
Atlogit
| chadgauth
chadgauth
| dleen
dleen
| dbasclpy
dbasclpy
| +| linegel
linegel
| celestial-vault
celestial-vault
| franekp
franekp
| DeXtroTip
DeXtroTip
| gtaylor
gtaylor
| hesara
hesara
| +| eltociear
eltociear
| Jdo300
Jdo300
| libertyteeth
libertyteeth
| mamertofabian
mamertofabian
| marvijo-code
marvijo-code
| kvokka
kvokka
| +| nbihan-mediware
nbihan-mediware
| PeterDaveHello
PeterDaveHello
| Sarke
Sarke
| 01Rian
01Rian
| samsilveira
samsilveira
| StevenTCramer
StevenTCramer
| +| maekawataiki
maekawataiki
| | | | | | diff --git a/benchmark/README.md b/benchmark/README.md index 9f21627d25..ae69c754dd 100644 --- a/benchmark/README.md +++ b/benchmark/README.md @@ -53,32 +53,3 @@ Run an exercise via the cli: ```sh pnpm cli run [cpp|go|java|javascript|python|rust|all] [|all] ``` - -## Extension Runner - -The extension runner (`@benchmark/runner`) is responsible for spawning VSCode instances and running Roo Code on the benchmark exercises. It leverages the [`@vscode/test-electron`](https://github.com/Microsoft/vscode-test) package to do this. - -VS Code provides two CLI parameters for running extension tests, `--extensionDevelopmentPath` and `--extensionTestsPath`. - -For example: - -```sh -# - Launches VS Code Extension Host -# - Loads the extension at -# - Executes the test runner script at -code \ - --extensionDevelopmentPath= \ - --extensionTestsPath= -``` - -If you make extension code changes then you need to re-build it so that `extensionDevelopmentPath` has the latest version transpiled: - -```sh -cd .. && npm run vscode-test -``` - -If you make changes to `@benchmark/runner` or any of its dependencies then you should rebuild it: - -```sh -pnpm build -``` diff --git a/benchmark/apps/cli/package.json b/benchmark/apps/cli/package.json index eb46b2585c..d9247a90a4 100644 --- a/benchmark/apps/cli/package.json +++ b/benchmark/apps/cli/package.json @@ -12,10 +12,10 @@ "@benchmark/db": "workspace:^", "@benchmark/ipc": "workspace:^", "@benchmark/types": "workspace:^", - "@vscode/test-electron": "^2.4.0", "execa": "^9.5.2", "gluegun": "^5.1.2", - "p-map": "^7.0.3" + "p-map": "^7.0.3", + "p-wait-for": "^5.0.2" }, "devDependencies": { "@benchmark/eslint-config": "workspace:^", diff --git a/benchmark/apps/cli/src/index.ts b/benchmark/apps/cli/src/index.ts index 26e7cacb29..55c220e8bd 100644 --- a/benchmark/apps/cli/src/index.ts +++ b/benchmark/apps/cli/src/index.ts @@ -3,15 +3,33 @@ import * as path from "path" import * as os from "os" import pMap from "p-map" -import { build, filesystem, GluegunPrompt, GluegunToolbox } from "gluegun" -import { runTests } from "@vscode/test-electron" +import pWaitFor from "p-wait-for" import { execa, parseCommandString } from "execa" +import { build, filesystem, GluegunPrompt, GluegunToolbox } from "gluegun" -import { type ExerciseLanguage, exerciseLanguages, IpcOrigin, IpcMessageType, TaskEventName } from "@benchmark/types" -import { type Run, findRun, createRun, finishRun, createTask, Task, getTasks, updateTask } from "@benchmark/db" -import { IpcServer } from "@benchmark/ipc" +import { + type ExerciseLanguage, + exerciseLanguages, + RooCodeEventName, + IpcOrigin, + IpcMessageType, + TaskCommandName, + rooCodeDefaults, +} from "@benchmark/types" +import { + type Run, + findRun, + createRun, + finishRun, + createTask, + Task, + getTasks, + updateTask, + createTaskMetrics, +} from "@benchmark/db" +import { IpcServer, IpcClient } from "@benchmark/ipc" -import { __dirname, extensionDevelopmentPath, extensionTestsPath, exercisesPath } from "./paths.js" +import { __dirname, extensionDevelopmentPath, exercisesPath } from "./paths.js" import { getExercises } from "./exercises.js" const testCommands: Record = { @@ -67,36 +85,28 @@ const run = async (toolbox: GluegunToolbox) => { } const tasks = await getTasks(run.id) - let currentTask = tasks[0] - if (!currentTask) { + if (!tasks[0]) { throw new Error("No tasks found.") } + let currentTask = tasks[0] + const server = new IpcServer(run.socketPath, () => {}) server.listen() - server.on("connect", (clientId) => { + server.on(IpcMessageType.Connect, (clientId) => { server.send(clientId, { type: IpcMessageType.TaskEvent, origin: IpcOrigin.Server, - data: { eventName: TaskEventName.Connect, data: { task: currentTask! } }, - }) - }) - - server.on("taskEvent", (relayClientId, data) => { - server.broadcast({ - type: IpcMessageType.TaskEvent, - origin: IpcOrigin.Server, - relayClientId, - data, + data: { eventName: RooCodeEventName.Connect, taskId: currentTask.id }, }) }) for (const task of tasks) { currentTask = task - await runExercise({ run, task }) + await runExercise({ run, task, server }) const cmd = testCommands[task.language] const exercisePath = path.resolve(exercisesPath, task.language, task.exercise) @@ -112,7 +122,7 @@ const run = async (toolbox: GluegunToolbox) => { try { const result = await execa({ cwd, shell: true, reject: false, cancelSignal })`${command}` - console.log({ ...result, cwd, command }) + // console.log('[cli#run] execa result =', { ...result, cwd, command }) clearTimeout(timeout) @@ -121,7 +131,7 @@ const run = async (toolbox: GluegunToolbox) => { break } } catch (error) { - console.log(error) + console.log("[cli#run] execa error =", error) passed = false break } @@ -131,38 +141,136 @@ const run = async (toolbox: GluegunToolbox) => { } const result = await finishRun(run.id) - console.log(result) + console.log("[cli#run]", result) } -const runExercise = async ({ run, task }: { run: Run; task: Task }) => { +const runExercise = async ({ run, task, server }: { run: Run; task: Task; server: IpcServer }) => { const { language, exercise } = task - const workspacePath = path.resolve(exercisesPath, language, exercise) - const promptPath = path.resolve(exercisesPath, `prompts/${language}.md`) - - if (!fs.existsSync(promptPath)) { - throw new Error(`Prompt file does not exist: ${promptPath}`) - } if (task.finishedAt) { console.log(`Test result exists for ${language} / ${exercise}, skipping`) return false } - console.log(`Running ${language} / ${exercise}`) + const prompt = fs.readFileSync(path.resolve(exercisesPath, `prompts/${language}.md`), "utf-8") - await runTests({ - extensionDevelopmentPath, - extensionTestsPath, - launchArgs: [workspacePath, "--disable-extensions"], - extensionTestsEnv: { - TASK_ID: task.id.toString(), - PROMPT_PATH: promptPath, - WORKSPACE_PATH: workspacePath, - OPENROUTER_MODEL_ID: run.model, + const dirname = path.dirname(run.socketPath) + const basename = path.basename(run.socketPath, ".sock") + const taskSocketPath = path.resolve(dirname, `${dirname}/${basename}-${task.id}.sock`) + await execa({ + env: { ROO_CODE_IPC_SOCKET_PATH: taskSocketPath }, + })`code -n ${path.resolve(exercisesPath, language, exercise)}` + + console.log(`Connecting to ${taskSocketPath}`) + let tries = 0 + let client = new IpcClient(taskSocketPath) + + while (++tries < 5) { + try { + await pWaitFor(() => client.isConnected, { interval: 100, timeout: 2_000 }) + break + } catch (error) { + console.error(error) + client.disconnect() + client = new IpcClient(taskSocketPath) + } + } + + let isTaskFinished = false + + client.on(IpcMessageType.Disconnect, () => { + console.log("disconnect") + isTaskFinished = true + }) + + const ignoreEvents = [ + RooCodeEventName.Message, + RooCodeEventName.TaskTokenUsageUpdated, + RooCodeEventName.TaskAskResponded, + ] + + let taskStartedAt = Date.now() + + client.on(IpcMessageType.TaskEvent, async (taskEvent) => { + const { eventName, payload } = taskEvent + + server.broadcast({ + type: IpcMessageType.TaskEvent, + origin: IpcOrigin.Server, + relayClientId: client.clientId!, + data: { ...taskEvent, taskId: task.id }, + }) + + if (!ignoreEvents.includes(eventName)) { + console.log(`[cli#runExercise] taskEvent -> ${eventName}`) + } + + // if (eventName === RooCodeEventName.Message) { + // const { message: { ts, text, partial } } = payload[0] + + // if (!partial) { + // console.log(`${ts}: ${text}`) + // } + // } + + if (eventName === RooCodeEventName.TaskStarted) { + taskStartedAt = Date.now() + } + + if (eventName === RooCodeEventName.TaskCompleted) { + const duration = Date.now() - taskStartedAt + + const { totalCost, totalTokensIn, totalTokensOut, contextTokens, totalCacheWrites, totalCacheReads } = + payload[1] + + const taskMetrics = await createTaskMetrics({ + cost: totalCost, + tokensIn: totalTokensIn, + tokensOut: totalTokensOut, + tokensContext: contextTokens, + duration, + cacheWrites: totalCacheWrites ?? 0, + cacheReads: totalCacheReads ?? 0, + }) + + await updateTask(task.id, { taskMetricsId: taskMetrics.id }) + isTaskFinished = true + } + + if (eventName === RooCodeEventName.TaskAborted) { + isTaskFinished = true + } + }) + + console.log(`[cli#runExercise] StartNewTask -> ${language} / ${exercise}`) + + client.sendMessage({ + type: IpcMessageType.TaskCommand, + origin: IpcOrigin.Client, + clientId: client.clientId!, + data: { + commandName: TaskCommandName.StartNewTask, + data: { + configuration: { + ...rooCodeDefaults, + openRouterApiKey: process.env.OPENROUTER_API_KEY!, + ...run.settings, + }, + text: prompt, + newTab: true, + }, }, }) - return true + try { + await pWaitFor(() => isTaskFinished, { interval: 1_000, timeout: 300 * 1_000 }) + client.disconnect() + return true + } catch (error) { + console.error(error) + client.disconnect() + return false + } } const askLanguage = async (prompt: GluegunPrompt) => { @@ -215,20 +323,15 @@ const main = async () => { .create() const toolbox = await cli.run(process.argv) - const { print, command } = toolbox + const { command } = toolbox - try { - switch (command?.name) { - case "run": - await run(toolbox) - break - } - - process.exit(0) - } catch (error: unknown) { - print.error(error instanceof Error ? error.message : String(error)) - process.exit(1) + switch (command?.name) { + case "run": + await run(toolbox) + break } + + process.exit(0) } if (!fs.existsSync(extensionDevelopmentPath)) { @@ -236,11 +339,6 @@ if (!fs.existsSync(extensionDevelopmentPath)) { process.exit(1) } -if (!fs.existsSync(extensionTestsPath)) { - console.error(`"extensionTestsPath" does not exist. Please run "pnpm --filter @benchmark/runner build".`) - process.exit(1) -} - if (!fs.existsSync(exercisesPath)) { console.error( `Exercises path does not exist. Please run "git clone https://github.com/cte/Roo-Code-Benchmark.git exercises".`, diff --git a/benchmark/apps/cli/src/paths.ts b/benchmark/apps/cli/src/paths.ts index 5f046510a8..388390ea6f 100644 --- a/benchmark/apps/cli/src/paths.ts +++ b/benchmark/apps/cli/src/paths.ts @@ -4,5 +4,4 @@ import { fileURLToPath } from "url" export const __dirname = path.dirname(fileURLToPath(import.meta.url)) export const extensionDevelopmentPath = path.resolve(__dirname, "..", "..", "..", "..") -export const extensionTestsPath = path.resolve(extensionDevelopmentPath, "benchmark/packages/runner/dist") export const exercisesPath = path.resolve(extensionDevelopmentPath, "..", "exercises") diff --git a/benchmark/apps/web/package.json b/benchmark/apps/web/package.json index 3d6eae311d..bb701b4e28 100644 --- a/benchmark/apps/web/package.json +++ b/benchmark/apps/web/package.json @@ -6,7 +6,7 @@ "check-types": "tsc -b", "dev": "dotenvx run -f ../../.env -- next dev --turbopack", "format": "prettier --write src", - "build": "echo 'NOP' || next build", + "build": "next build", "start": "next start" }, "dependencies": { diff --git a/benchmark/apps/web/src/app/api/runs/[id]/stream/route.ts b/benchmark/apps/web/src/app/api/runs/[id]/stream/route.ts index 4a93428890..a3d3896722 100644 --- a/benchmark/apps/web/src/app/api/runs/[id]/stream/route.ts +++ b/benchmark/apps/web/src/app/api/runs/[id]/stream/route.ts @@ -1,6 +1,7 @@ import type { NextRequest } from "next/server" import { findRun } from "@benchmark/db" +import { IpcMessageType } from "@benchmark/types" import { IpcClient } from "@benchmark/ipc" import { SSEStream } from "@/lib/server/sse-stream" @@ -15,6 +16,7 @@ export async function GET(request: NextRequest, { params }: { params: Promise<{ const client = new IpcClient(run.socketPath, () => {}) const write = async (data: string | object) => { + console.log(`[stream#${requestId}] write`, data) const success = await stream.write(data) if (!success) { @@ -23,9 +25,9 @@ export async function GET(request: NextRequest, { params }: { params: Promise<{ } console.log(`[stream#${requestId}] connect`) - client.on("connect", () => write("connect")) - client.on("disconnect", () => write("disconnect")) - client.on("taskEvent", write) + client.on(IpcMessageType.Connect, () => write("connect")) + client.on(IpcMessageType.Disconnect, () => write("disconnect")) + client.on(IpcMessageType.TaskEvent, write) request.signal.addEventListener("abort", () => { console.log(`[stream#${requestId}] abort`) diff --git a/benchmark/apps/web/src/hooks/use-run-status.ts b/benchmark/apps/web/src/hooks/use-run-status.ts index 4c02b4c7db..4e80b611d0 100644 --- a/benchmark/apps/web/src/hooks/use-run-status.ts +++ b/benchmark/apps/web/src/hooks/use-run-status.ts @@ -1,7 +1,7 @@ import { useState, useCallback, useRef } from "react" import { useQuery, keepPreviousData } from "@tanstack/react-query" -import { TaskEventName, taskEventSchema } from "@benchmark/types" +import { RooCodeEventName, taskEventSchema } from "@benchmark/types" import { Run } from "@benchmark/db" import { getTasks } from "@/lib/server/tasks" @@ -38,19 +38,28 @@ export const useRunStatus = (run: Run) => { return } - const payload = result.data - const taskId = payload.data.task.id + const { eventName, payload, taskId } = result.data - switch (payload.eventName) { - case TaskEventName.Connect: - case TaskEventName.TaskStarted: + if (!taskId) { + console.log(`no taskId: ${messageEvent.data}`) + return + } + + switch (eventName) { + case RooCodeEventName.Connect: + case RooCodeEventName.TaskCreated: + case RooCodeEventName.TaskStarted: setRunningTaskId(taskId) break - case TaskEventName.TaskFinished: + case RooCodeEventName.TaskCompleted: setRunningTaskId(undefined) break - case TaskEventName.Message: { - const text = payload.data.message.message.text + case RooCodeEventName.Message: { + const [ + { + message: { text }, + }, + ] = payload if (text) { outputRef.current.set(taskId, [...(outputRef.current.get(taskId) || []), text]) diff --git a/benchmark/package.json b/benchmark/package.json index faeca34f9b..0fcb978bfa 100644 --- a/benchmark/package.json +++ b/benchmark/package.json @@ -1,7 +1,7 @@ { "name": "@benchmark/monorepo", "private": true, - "packageManager": "pnpm@10.7.0", + "packageManager": "pnpm@10.7.0+sha512.6b865ad4b62a1d9842b61d674a393903b871d9244954f652b8842c2b553c72176b278f64c463e52d40fff8aba385c235c8c9ecf5cc7de4fd78b8bb6d49633ab6", "scripts": { "lint": "turbo lint --log-order grouped --output-logs new-only", "check-types": "turbo check-types --log-order grouped --output-logs new-only", @@ -25,7 +25,6 @@ "eslint": "^9.22.0", "globals": "^16.0.0", "prettier": "^3.5.3", - "tsup": "^8.4.0", "tsx": "^4.19.3", "turbo": "^2.4.4", "typescript": "^5", diff --git a/benchmark/packages/db/package.json b/benchmark/packages/db/package.json index 8a9a25ecae..bb039cba54 100644 --- a/benchmark/packages/db/package.json +++ b/benchmark/packages/db/package.json @@ -2,24 +2,11 @@ "name": "@benchmark/db", "private": true, "type": "module", - "main": "./dist/index.cjs", - "module": "./src/index.ts", - "types": "./src/index.ts", - "exports": { - "import": { - "types": "./src/index.ts", - "import": "./src/index.ts" - }, - "require": { - "types": "./dist/index.d.cts", - "require": "./dist/index.cjs" - } - }, + "exports": "./src/index.ts", "scripts": { "lint": "eslint src --ext ts --max-warnings=0", "check-types": "tsc --noEmit", "format": "prettier --write src", - "build": "tsup", "drizzle-kit": "dotenvx run -f ../../.env -- tsx node_modules/drizzle-kit/bin.cjs", "db:generate": "pnpm drizzle-kit generate", "db:migrate": "pnpm drizzle-kit migrate", diff --git a/benchmark/packages/db/tsconfig.cjs.json b/benchmark/packages/db/tsconfig.cjs.json deleted file mode 100644 index bbb032aed6..0000000000 --- a/benchmark/packages/db/tsconfig.cjs.json +++ /dev/null @@ -1,5 +0,0 @@ -{ - "extends": "@benchmark/typescript-config/cjs.json", - "include": ["src"], - "exclude": ["node_modules"] -} diff --git a/benchmark/packages/db/tsup.config.ts b/benchmark/packages/db/tsup.config.ts deleted file mode 100644 index 07df1d1210..0000000000 --- a/benchmark/packages/db/tsup.config.ts +++ /dev/null @@ -1,13 +0,0 @@ -import { defineConfig } from "tsup" - -export default defineConfig([ - { - entry: ["src/index.ts"], - dts: true, - outDir: "dist", - clean: true, - format: ["cjs"], - tsconfig: "./tsconfig.cjs.json", - outExtension: () => ({ js: ".cjs" }), - }, -]) diff --git a/benchmark/packages/ipc/package.json b/benchmark/packages/ipc/package.json index cf1fcc58e0..ad68425c40 100644 --- a/benchmark/packages/ipc/package.json +++ b/benchmark/packages/ipc/package.json @@ -2,24 +2,11 @@ "name": "@benchmark/ipc", "private": true, "type": "module", - "main": "./dist/index.cjs", - "module": "./src/index.ts", - "types": "./src/index.ts", - "exports": { - "import": { - "types": "./src/index.ts", - "import": "./src/index.ts" - }, - "require": { - "types": "./dist/index.d.cts", - "require": "./dist/index.cjs" - } - }, + "exports": "./src/index.ts", "scripts": { "lint": "eslint src --ext ts --max-warnings=0", "check-types": "tsc --noEmit", - "format": "prettier --write src", - "build": "tsup" + "format": "prettier --write src" }, "dependencies": { "@benchmark/types": "workspace:^", diff --git a/benchmark/packages/ipc/scripts/client.ts b/benchmark/packages/ipc/scripts/client.ts deleted file mode 100644 index f00829a113..0000000000 --- a/benchmark/packages/ipc/scripts/client.ts +++ /dev/null @@ -1,44 +0,0 @@ -import { TaskCommandName } from "@benchmark/types" - -import { IpcMessageType, IpcOrigin } from "@benchmark/types" -import { IpcClient } from "../src/index.js" - -async function main(socketPath: string, prompt: string) { - try { - const client = new IpcClient(socketPath) - const clientStartedAt = Date.now() - - while (!client.isConnected && Date.now() - clientStartedAt < 10_000) { - await new Promise((resolve) => setTimeout(resolve, 250)) - } - - if (!client.isConnected) { - throw new Error("Failed to connect to server.") - } - - client.on("taskEvent", (data) => console.log(data)) - - client.sendMessage({ - type: IpcMessageType.TaskCommand, - origin: IpcOrigin.Client, - clientId: client.clientId!, - data: { commandName: TaskCommandName.StartNewTask, data: { text: prompt } }, - }) - - while (client.isConnected) { - await new Promise((resolve) => setTimeout(resolve, 1000)) - } - - process.exit(0) - } catch (e) { - console.error(e) - process.exit(1) - } -} - -if (!process.argv[2]) { - console.error("Usage: npx tsx scripts/client.ts ") - process.exit(1) -} - -main(process.argv[2], process.argv[3]) diff --git a/benchmark/packages/ipc/src/index.ts b/benchmark/packages/ipc/src/index.ts index 20ba9671a9..7f51ddcaae 100644 --- a/benchmark/packages/ipc/src/index.ts +++ b/benchmark/packages/ipc/src/index.ts @@ -10,12 +10,12 @@ import { IpcOrigin, IpcMessageType, IpcMessage, ipcMessageSchema, TaskCommand, T * IpcClient */ -type IpcClientEvents = { - connect: [] - disconnect: [] - ack: [clientId: string] - taskCommand: [data: TaskCommand] - taskEvent: [data: TaskEvent] +export type IpcClientEvents = { + [IpcMessageType.Connect]: [] + [IpcMessageType.Disconnect]: [] + [IpcMessageType.Ack]: [clientId: string] + [IpcMessageType.TaskCommand]: [data: TaskCommand] + [IpcMessageType.TaskEvent]: [data: TaskEvent] } export class IpcClient extends EventEmitter { @@ -34,30 +34,30 @@ export class IpcClient extends EventEmitter { ipc.config.silent = true ipc.connectTo("benchmarkServer", this.socketPath, () => { - ipc.of.benchmarkServer?.on("connect", (args) => this.onConnect(args)) - ipc.of.benchmarkServer?.on("disconnect", (args) => this.onDisconnect(args)) + ipc.of.benchmarkServer?.on("connect", () => this.onConnect()) + ipc.of.benchmarkServer?.on("disconnect", () => this.onDisconnect()) ipc.of.benchmarkServer?.on("message", (data) => this.onMessage(data)) }) } - private onConnect(args: unknown) { + private onConnect() { if (this._isConnected) { return } - this.log("[client#onConnect]", args) + this.log("[client#onConnect]") this._isConnected = true - this.emit("connect") + this.emit(IpcMessageType.Connect) } - private onDisconnect(args: unknown) { + private onDisconnect() { if (!this._isConnected) { return } - this.log("[client#onDisconnect]", args) + this.log("[client#onDisconnect]") this._isConnected = false - this.emit("disconnect") + this.emit(IpcMessageType.Disconnect) } private onMessage(data: unknown) { @@ -69,7 +69,7 @@ export class IpcClient extends EventEmitter { const result = ipcMessageSchema.safeParse(data) if (!result.success) { - this.log("[client#onMessage] invalid payload", result.error) + this.log("[client#onMessage] invalid payload", data) return } @@ -79,10 +79,10 @@ export class IpcClient extends EventEmitter { switch (payload.type) { case IpcMessageType.Ack: this._clientId = payload.data.clientId - this.emit("ack", payload.data.clientId) + this.emit(IpcMessageType.Ack, payload.data.clientId) break case IpcMessageType.TaskEvent: - this.emit("taskEvent", payload.data) + this.emit(IpcMessageType.TaskEvent, payload.data) break } } @@ -127,10 +127,10 @@ export class IpcClient extends EventEmitter { */ type IpcServerEvents = { - connect: [clientId: string] - disconnect: [clientId: string] - taskCommand: [clientId: string, data: TaskCommand] - taskEvent: [relayClientId: string | undefined, data: TaskEvent] + [IpcMessageType.Connect]: [clientId: string] + [IpcMessageType.Disconnect]: [clientId: string] + [IpcMessageType.TaskCommand]: [clientId: string, data: TaskCommand] + [IpcMessageType.TaskEvent]: [relayClientId: string | undefined, data: TaskEvent] } export class IpcServer extends EventEmitter { @@ -167,7 +167,7 @@ export class IpcServer extends EventEmitter { this._clients.set(clientId, socket) this.log(`[server#onConnect] clientId = ${clientId}, # clients = ${this._clients.size}`) this.send(socket, { type: IpcMessageType.Ack, origin: IpcOrigin.Server, data: { clientId } }) - this.emit("connect", clientId) + this.emit(IpcMessageType.Connect, clientId) } private onDisconnect(destroyedSocket: Socket) { @@ -184,7 +184,7 @@ export class IpcServer extends EventEmitter { this.log(`[server#socket.disconnected] clientId = ${disconnectedClientId}, # clients = ${this._clients.size}`) if (disconnectedClientId) { - this.emit("disconnect", disconnectedClientId) + this.emit(IpcMessageType.Disconnect, disconnectedClientId) } } @@ -206,13 +206,7 @@ export class IpcServer extends EventEmitter { if (payload.origin === IpcOrigin.Client) { switch (payload.type) { case IpcMessageType.TaskCommand: - this.emit("taskCommand", payload.clientId, payload.data) - break - } - } else if (payload.origin === IpcOrigin.Relay) { - switch (payload.type) { - case IpcMessageType.TaskEvent: - this.emit("taskEvent", payload.relayClientId, payload.data) + this.emit(IpcMessageType.TaskCommand, payload.clientId, payload.data) break } } diff --git a/benchmark/packages/ipc/tsconfig.cjs.json b/benchmark/packages/ipc/tsconfig.cjs.json deleted file mode 100644 index bbb032aed6..0000000000 --- a/benchmark/packages/ipc/tsconfig.cjs.json +++ /dev/null @@ -1,5 +0,0 @@ -{ - "extends": "@benchmark/typescript-config/cjs.json", - "include": ["src"], - "exclude": ["node_modules"] -} diff --git a/benchmark/packages/ipc/tsup.config.ts b/benchmark/packages/ipc/tsup.config.ts deleted file mode 100644 index ba7df90447..0000000000 --- a/benchmark/packages/ipc/tsup.config.ts +++ /dev/null @@ -1,14 +0,0 @@ -import { defineConfig } from "tsup" - -export default defineConfig([ - { - entry: ["src/index.ts"], - dts: true, - outDir: "dist", - clean: true, - format: ["cjs"], - tsconfig: "./tsconfig.cjs.json", - outExtension: () => ({ js: ".cjs" }), - noExternal: ["node-ipc"], - }, -]) diff --git a/benchmark/packages/runner/eslint.config.mjs b/benchmark/packages/runner/eslint.config.mjs deleted file mode 100644 index 48af90a4a4..0000000000 --- a/benchmark/packages/runner/eslint.config.mjs +++ /dev/null @@ -1,4 +0,0 @@ -import { config } from "@benchmark/eslint-config/base" - -/** @type {import("eslint").Linter.Config} */ -export default [...config] diff --git a/benchmark/packages/runner/package.json b/benchmark/packages/runner/package.json deleted file mode 100644 index 1a8088bc2a..0000000000 --- a/benchmark/packages/runner/package.json +++ /dev/null @@ -1,20 +0,0 @@ -{ - "name": "@benchmark/runner", - "private": true, - "scripts": { - "lint": "eslint src --ext ts --max-warnings=0", - "check-types": "tsc --noEmit", - "format": "prettier --write src", - "build": "tsup" - }, - "dependencies": { - "@benchmark/db": "workspace:^", - "@benchmark/ipc": "workspace:^", - "@benchmark/types": "workspace:^" - }, - "devDependencies": { - "@benchmark/eslint-config": "workspace:^", - "@benchmark/typescript-config": "workspace:^", - "@types/vscode": "^1.98.0" - } -} diff --git a/benchmark/packages/runner/src/index.ts b/benchmark/packages/runner/src/index.ts deleted file mode 100644 index 4fd60ed782..0000000000 --- a/benchmark/packages/runner/src/index.ts +++ /dev/null @@ -1,158 +0,0 @@ -import * as fs from "fs/promises" -import * as path from "path" - -import * as vscode from "vscode" - -import { RooCodeAPI } from "../../../../src/exports/roo-code.js" - -import { IpcOrigin, IpcMessageType, TaskEventName, rooCodeDefaults } from "@benchmark/types" -import { IpcClient } from "@benchmark/ipc" -import { findTask, findRun, createTaskMetrics, updateTask } from "@benchmark/db" - -import { waitUntilReady, waitUntilCompleted, sleep } from "./utils.js" - -export async function run() { - /** - * Validate environment variables. - */ - - const taskId = process.env.TASK_ID ? parseInt(process.env.TASK_ID) : undefined - const promptPath = process.env.PROMPT_PATH - const workspacePath = process.env.WORKSPACE_PATH - const openRouterApiKey = process.env.OPENROUTER_API_KEY - const openRouterModelId = process.env.OPENROUTER_MODEL_ID - - if (!taskId || !promptPath || !workspacePath || !openRouterApiKey || !openRouterModelId) { - throw new Error( - `ENV not configured. ${JSON.stringify({ taskId, promptPath, workspacePath, openRouterApiKey, openRouterModelId })}`, - ) - } - - const prompt = await fs.readFile(promptPath, "utf-8") - - /** - * Fetch and update the task. - */ - - let task = await findTask(taskId) - task = await updateTask(task.id, { startedAt: new Date() }) - - const run = await findRun(task.runId) - - /** - * Activate the extension. - */ - - const extension = vscode.extensions.getExtension("RooVeterinaryInc.roo-cline") - - if (!extension) { - throw new Error("Extension not found.") - } - - const api = extension.isActive ? extension.exports : await extension.activate() - - /** - * Wait for the Roo Code to be ready to accept tasks. - */ - - await waitUntilReady({ api }) - - /** - * Configure Roo Code as needed. First apply defaults, then apply the - * run-specific settings. - */ - - await api.setConfiguration({ - apiProvider: "openrouter", - openRouterApiKey, - openRouterModelId, - ...rooCodeDefaults, - ...Object.fromEntries(Object.entries(run.settings ?? {}).filter(([, value]) => value !== undefined)), - }) - - await vscode.workspace - .getConfiguration("roo-cline") - .update("allowedCommands", ["*"], vscode.ConfigurationTarget.Global) - - await sleep(1_000) - - /** - * Start the IPC client. - */ - - const client = new IpcClient(run.socketPath, () => {}) - const clientStartedAt = Date.now() - - while (!client.clientId && Date.now() - clientStartedAt < 10_000) { - await sleep(250) - } - - if (!client.clientId) { - throw new Error("No client ID received.") - } - - api.on("taskStarted", () => - client.sendMessage({ - type: IpcMessageType.TaskEvent, - relayClientId: client.clientId, - origin: IpcOrigin.Relay, - data: { eventName: TaskEventName.TaskStarted, data: { task } }, - }), - ) - - api.on("message", (message) => - client.sendMessage({ - type: IpcMessageType.TaskEvent, - relayClientId: client.clientId, - origin: IpcOrigin.Relay, - data: { eventName: TaskEventName.Message, data: { task, message } }, - }), - ) - - api.on("taskTokenUsageUpdated", (_, usage) => - client.sendMessage({ - type: IpcMessageType.TaskEvent, - relayClientId: client.clientId, - origin: IpcOrigin.Relay, - data: { eventName: TaskEventName.TaskTokenUsageUpdated, data: { task, usage } }, - }), - ) - - /** - * Run the task and wait up to 10 minutes for it to complete. - */ - - const startTime = Date.now() - const rooTaskId = await api.startNewTask(prompt) - let usage - - try { - usage = - (await waitUntilCompleted({ api, taskId: rooTaskId, timeout: 5 * 60 * 1_000 })) || - api.getTokenUsage(rooTaskId) - } catch (e: unknown) { - usage = api.getTokenUsage(rooTaskId) - console.error(e) - } - - const taskMetrics = await createTaskMetrics({ - duration: Date.now() - startTime, - tokensIn: usage.totalTokensIn, - tokensOut: usage.totalTokensOut, - tokensContext: usage.contextTokens, - cacheWrites: usage.totalCacheWrites ?? 0, - cacheReads: usage.totalCacheReads ?? 0, - cost: usage.totalCost, - }) - - task = await updateTask(task.id, { taskMetricsId: taskMetrics.id, finishedAt: new Date() }) - - client.sendMessage({ - type: IpcMessageType.TaskEvent, - relayClientId: client.clientId, - origin: IpcOrigin.Relay, - data: { eventName: TaskEventName.TaskFinished, data: { task, taskMetrics } }, - }) - - await fs.writeFile(path.resolve(workspacePath, "usage.json"), JSON.stringify({ ...task, ...taskMetrics }, null, 2)) -} diff --git a/benchmark/packages/runner/src/utils.ts b/benchmark/packages/runner/src/utils.ts deleted file mode 100644 index 0d15733b43..0000000000 --- a/benchmark/packages/runner/src/utils.ts +++ /dev/null @@ -1,111 +0,0 @@ -import * as vscode from "vscode" - -import { RooCodeAPI, TokenUsage } from "../../../../src/exports/roo-code.js" - -type WaitForOptions = { - timeout?: number - interval?: number -} - -export const waitFor = ( - condition: (() => Promise) | (() => boolean), - { timeout = 30_000, interval = 250 }: WaitForOptions = {}, -) => { - let timeoutId: NodeJS.Timeout | undefined = undefined - - return Promise.race([ - new Promise((resolve) => { - const check = async () => { - const result = condition() - const isSatisfied = result instanceof Promise ? await result : result - - if (isSatisfied) { - if (timeoutId) { - clearTimeout(timeoutId) - timeoutId = undefined - } - - resolve() - } else { - setTimeout(check, interval) - } - } - - check() - }), - new Promise((_, reject) => { - timeoutId = setTimeout(() => { - reject(new Error(`Timeout after ${Math.floor(timeout / 1000)}s`)) - }, timeout) - }), - ]) -} - -type WaitUntilReadyOptions = WaitForOptions & { - api: RooCodeAPI -} - -export const waitUntilReady = async ({ api, ...options }: WaitUntilReadyOptions) => { - await vscode.commands.executeCommand("roo-cline.SidebarProvider.focus") - await waitFor(() => api.isReady(), options) -} - -type WaitUntilAbortedOptions = WaitForOptions & { - api: RooCodeAPI - taskId: string -} - -export const waitUntilAborted = async ({ api, taskId, ...options }: WaitUntilAbortedOptions) => { - const set = new Set() - api.on("taskAborted", (taskId) => set.add(taskId)) - await waitFor(() => set.has(taskId), options) -} - -type WaitUntilCompletedOptions = WaitForOptions & { - api: RooCodeAPI - taskId: string -} - -export const waitUntilCompleted = async ({ api, taskId, ...options }: WaitUntilCompletedOptions) => { - const map = new Map() - api.on("taskCompleted", (taskId, usage) => map.set(taskId, usage)) - await waitFor(() => map.has(taskId), options) - return map.get(taskId) -} - -export const waitForCompletion = async ({ - api, - taskId, - ...options -}: WaitUntilReadyOptions & { - taskId: string -}) => waitFor(() => !!getCompletion({ api, taskId }), options) - -export const getCompletion = ({ api, taskId }: { api: RooCodeAPI; taskId: string }) => - api.getMessages(taskId).find(({ say, partial }) => say === "completion_result" && partial === false) - -type WaitForMessageOptions = WaitUntilReadyOptions & { - taskId: string - include: string - exclude?: string -} - -export const waitForMessage = async ({ api, taskId, include, exclude, ...options }: WaitForMessageOptions) => - waitFor(() => !!getMessage({ api, taskId, include, exclude }), options) - -type GetMessageOptions = { - api: RooCodeAPI - taskId: string - include: string - exclude?: string -} - -export const getMessage = ({ api, taskId, include, exclude }: GetMessageOptions) => - api - .getMessages(taskId) - .find( - ({ type, text }) => - type === "say" && text && text.includes(include) && (!exclude || !text.includes(exclude)), - ) - -export const sleep = (ms: number) => new Promise((resolve) => setTimeout(resolve, ms)) diff --git a/benchmark/packages/runner/tsconfig.json b/benchmark/packages/runner/tsconfig.json deleted file mode 100644 index 25c401876b..0000000000 --- a/benchmark/packages/runner/tsconfig.json +++ /dev/null @@ -1,8 +0,0 @@ -{ - "extends": "@benchmark/typescript-config/base.json", - "compilerOptions": { - "outDir": "dist" - }, - "include": ["src"], - "exclude": ["node_modules"] -} diff --git a/benchmark/packages/runner/tsup.config.ts b/benchmark/packages/runner/tsup.config.ts deleted file mode 100644 index 599d3c13cc..0000000000 --- a/benchmark/packages/runner/tsup.config.ts +++ /dev/null @@ -1,11 +0,0 @@ -import { defineConfig } from "tsup" - -export default defineConfig({ - entry: ["src/index.ts"], - format: ["cjs"], - dts: true, - sourcemap: true, - clean: true, - tsconfig: "tsconfig.json", - external: ["vscode"], -}) diff --git a/benchmark/packages/types/package.json b/benchmark/packages/types/package.json index 62a7b58a74..e08ce79bd0 100644 --- a/benchmark/packages/types/package.json +++ b/benchmark/packages/types/package.json @@ -2,24 +2,11 @@ "name": "@benchmark/types", "private": true, "type": "module", - "main": "./dist/index.cjs", - "module": "./src/index.ts", - "types": "./src/index.ts", - "exports": { - "import": { - "types": "./src/index.ts", - "import": "./src/index.ts" - }, - "require": { - "types": "./dist/index.d.cts", - "require": "./dist/index.cjs" - } - }, + "exports": "./src/index.ts", "scripts": { "lint": "eslint src --ext ts --max-warnings=0", "check-types": "tsc --noEmit", - "format": "prettier --write src", - "build": "tsup" + "format": "prettier --write src" }, "dependencies": { "zod": "^3.24.2" diff --git a/benchmark/packages/types/src/ipc.ts b/benchmark/packages/types/src/ipc.ts index a744e7454d..a2737021f8 100644 --- a/benchmark/packages/types/src/ipc.ts +++ b/benchmark/packages/types/src/ipc.ts @@ -1,60 +1,6 @@ import { z } from "zod" -/** - * TaskEvent - */ - -export enum TaskEventName { - Connect = "Connect", - TaskStarted = "TaskStarted", - Message = "Message", - TaskTokenUsageUpdated = "TaskTokenUsageUpdated", - TaskFinished = "TaskFinished", -} - -export const taskEventSchema = z.discriminatedUnion("eventName", [ - z.object({ - eventName: z.literal(TaskEventName.Connect), - data: z.object({ task: z.object({ id: z.number() }) }), - }), - z.object({ - eventName: z.literal(TaskEventName.TaskStarted), - data: z.object({ task: z.object({ id: z.number() }) }), - }), - z.object({ - eventName: z.literal(TaskEventName.Message), - data: z.object({ - task: z.object({ id: z.number() }), - message: z.object({ - taskId: z.string(), - action: z.enum(["created", "updated"]), - message: z.object({ - // See ClineMessage. - ts: z.number(), - type: z.enum(["ask", "say"]), - ask: z.string().optional(), - say: z.string().optional(), - partial: z.boolean().optional(), - text: z.string().optional(), - reasoning: z.string().optional(), - }), - }), - }), - }), - z.object({ - eventName: z.literal(TaskEventName.TaskTokenUsageUpdated), - data: z.object({ - task: z.object({ id: z.number() }), - usage: z.object({}), - }), - }), - z.object({ - eventName: z.literal(TaskEventName.TaskFinished), - data: z.object({ task: z.object({ id: z.number() }), taskMetrics: z.unknown() }), - }), -]) - -export type TaskEvent = z.infer +import { RooCodeEventName, rooCodeEventsSchema, rooCodeSettingsSchema } from "./roo-code.js" /** * TaskCommand @@ -68,19 +14,87 @@ export const taskCommandSchema = z.discriminatedUnion("commandName", [ z.object({ commandName: z.literal(TaskCommandName.StartNewTask), data: z.object({ + configuration: rooCodeSettingsSchema, text: z.string(), images: z.array(z.string()).optional(), + newTab: z.boolean().optional(), }), }), ]) export type TaskCommand = z.infer +/** + * TaskEvent + */ + +export const taskEventSchema = z.discriminatedUnion("eventName", [ + z.object({ + eventName: z.literal(RooCodeEventName.Connect), + payload: z.unknown(), + taskId: z.number(), + }), + 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.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(), + }), +]) + +export type TaskEvent = z.infer + /** * IpcMessage */ export enum IpcMessageType { + Connect = "Connect", + Disconnect = "Disconnect", Ack = "Ack", TaskCommand = "TaskCommand", TaskEvent = "TaskEvent", @@ -89,7 +103,6 @@ export enum IpcMessageType { export enum IpcOrigin { Client = "client", Server = "server", - Relay = "relay", } export const ipcMessageSchema = z.discriminatedUnion("type", [ @@ -106,7 +119,7 @@ export const ipcMessageSchema = z.discriminatedUnion("type", [ }), z.object({ type: z.literal(IpcMessageType.TaskEvent), - origin: z.union([z.literal(IpcOrigin.Server), z.literal(IpcOrigin.Relay)]), + origin: z.literal(IpcOrigin.Server), relayClientId: z.string().optional(), data: taskEventSchema, }), diff --git a/benchmark/packages/types/src/roo-code-defaults.ts b/benchmark/packages/types/src/roo-code-defaults.ts index 9179d4fe55..c22233869f 100644 --- a/benchmark/packages/types/src/roo-code-defaults.ts +++ b/benchmark/packages/types/src/roo-code-defaults.ts @@ -1,6 +1,9 @@ -import { GlobalSettings } from "./roo-code.js" +import { RooCodeSettings } from "./roo-code.js" + +export const rooCodeDefaults: RooCodeSettings = { + apiProvider: "openrouter", + openRouterModelId: "google/gemini-2.0-flash-001", // "anthropic/claude-3.7-sonnet", -export const rooCodeDefaults: GlobalSettings = { pinnedApiConfigs: {}, lastShownAnnouncementId: "mar-20-2025-3-10", diff --git a/benchmark/packages/types/src/roo-code.ts b/benchmark/packages/types/src/roo-code.ts index c58a436061..c25102ecc4 100644 --- a/benchmark/packages/types/src/roo-code.ts +++ b/benchmark/packages/types/src/roo-code.ts @@ -148,7 +148,24 @@ export type HistoryItem = z.infer */ export const groupOptionsSchema = z.object({ - fileRegex: z.string().optional(), + fileRegex: z + .string() + .optional() + .refine( + (pattern) => { + if (!pattern) { + return true // Optional, so empty is valid. + } + + try { + new RegExp(pattern) + return true + } catch { + return false + } + }, + { message: "Invalid regular expression pattern" }, + ), description: z.string().optional(), }) @@ -166,17 +183,62 @@ export type GroupEntry = z.infer * ModeConfig */ +const groupEntryArraySchema = z.array(groupEntrySchema).refine( + (groups) => { + const seen = new Set() + + return groups.every((group) => { + // For tuples, check the group name (first element). + const groupName = Array.isArray(group) ? group[0] : group + + if (seen.has(groupName)) { + return false + } + + seen.add(groupName) + return true + }) + }, + { message: "Duplicate groups are not allowed" }, +) + export const modeConfigSchema = z.object({ - slug: z.string(), - name: z.string(), - roleDefinition: z.string(), + slug: z.string().regex(/^[a-zA-Z0-9-]+$/, "Slug must contain only letters numbers and dashes"), + name: z.string().min(1, "Name is required"), + roleDefinition: z.string().min(1, "Role definition is required"), customInstructions: z.string().optional(), - groups: z.array(groupEntrySchema), + groups: groupEntryArraySchema, source: z.enum(["global", "project"]).optional(), }) export type ModeConfig = z.infer +/** + * CustomModesSettings + */ + +export const customModesSettingsSchema = z.object({ + customModes: z.array(modeConfigSchema).refine( + (modes) => { + const slugs = new Set() + + return modes.every((mode) => { + if (slugs.has(mode.slug)) { + return false + } + + slugs.add(mode.slug) + return true + }) + }, + { + message: "Duplicate mode slugs are not allowed", + }, + ), +}) + +export type CustomModesSettings = z.infer + /** * PromptComponent */ @@ -561,6 +623,8 @@ export const GLOBAL_SETTINGS_KEYS = Object.keys(globalSettingsRecord) as Keys + +/** + * RooCodeEvent + */ + +export enum RooCodeEventName { + Connect = "connect", + Message = "message", + TaskCreated = "taskCreated", + TaskStarted = "taskStarted", + TaskPaused = "taskPaused", + TaskUnpaused = "taskUnpaused", + TaskAskResponded = "taskAskResponded", + TaskAborted = "taskAborted", + TaskSpawned = "taskSpawned", + TaskCompleted = "taskCompleted", + TaskTokenUsageUpdated = "taskTokenUsageUpdated", +} + +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.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]), + [RooCodeEventName.TaskTokenUsageUpdated]: z.tuple([z.string(), tokenUsageSchema]), +}) + +export type RooCodeEvents = z.infer diff --git a/benchmark/packages/types/tsconfig.cjs.json b/benchmark/packages/types/tsconfig.cjs.json deleted file mode 100644 index bbb032aed6..0000000000 --- a/benchmark/packages/types/tsconfig.cjs.json +++ /dev/null @@ -1,5 +0,0 @@ -{ - "extends": "@benchmark/typescript-config/cjs.json", - "include": ["src"], - "exclude": ["node_modules"] -} diff --git a/benchmark/packages/types/tsup.config.ts b/benchmark/packages/types/tsup.config.ts deleted file mode 100644 index 07df1d1210..0000000000 --- a/benchmark/packages/types/tsup.config.ts +++ /dev/null @@ -1,13 +0,0 @@ -import { defineConfig } from "tsup" - -export default defineConfig([ - { - entry: ["src/index.ts"], - dts: true, - outDir: "dist", - clean: true, - format: ["cjs"], - tsconfig: "./tsconfig.cjs.json", - outExtension: () => ({ js: ".cjs" }), - }, -]) diff --git a/benchmark/pnpm-lock.yaml b/benchmark/pnpm-lock.yaml index 45842d49b4..c260071d87 100644 --- a/benchmark/pnpm-lock.yaml +++ b/benchmark/pnpm-lock.yaml @@ -23,9 +23,6 @@ importers: prettier: specifier: ^3.5.3 version: 3.5.3 - tsup: - specifier: ^8.4.0 - version: 8.4.0(@microsoft/api-extractor@7.52.1(@types/node@20.17.24))(jiti@2.4.2)(postcss@8.5.3)(tsx@4.19.3)(typescript@5.8.2) tsx: specifier: ^4.19.3 version: 4.19.3 @@ -50,9 +47,6 @@ importers: '@benchmark/types': specifier: workspace:^ version: link:../../packages/types - '@vscode/test-electron': - specifier: ^2.4.0 - version: 2.4.1 execa: specifier: ^9.5.2 version: 9.5.2 @@ -62,6 +56,9 @@ importers: p-map: specifier: ^7.0.3 version: 7.0.3 + p-wait-for: + specifier: ^5.0.2 + version: 5.0.2 devDependencies: '@benchmark/eslint-config': specifier: workspace:^ @@ -273,28 +270,6 @@ importers: specifier: ^9.2.3 version: 9.2.3 - packages/runner: - dependencies: - '@benchmark/db': - specifier: workspace:^ - version: link:../db - '@benchmark/ipc': - specifier: workspace:^ - version: link:../ipc - '@benchmark/types': - specifier: workspace:^ - version: link:../types - devDependencies: - '@benchmark/eslint-config': - specifier: workspace:^ - version: link:../../config/eslint - '@benchmark/typescript-config': - specifier: workspace:^ - version: link:../../config/typescript - '@types/vscode': - specifier: ^1.98.0 - version: 1.98.0 - packages/types: dependencies: zod: @@ -953,28 +928,9 @@ packages: cpu: [x64] os: [win32] - '@isaacs/cliui@8.0.2': - resolution: {integrity: sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==} - engines: {node: '>=12'} - - '@jridgewell/gen-mapping@0.3.8': - resolution: {integrity: sha512-imAbBGkb+ebQyxKgzv5Hu2nmROxoDOXHh80evxdoXNOrvAnVx7zimzc1Oo5h9RlfV4vPXaE2iM5pOFbvOCClWA==} - engines: {node: '>=6.0.0'} - - '@jridgewell/resolve-uri@3.1.2': - resolution: {integrity: sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==} - engines: {node: '>=6.0.0'} - - '@jridgewell/set-array@1.2.1': - resolution: {integrity: sha512-R8gLRTZeyp03ymzP/6Lil/28tGeGEzhx1q2k703KGWRAI1VdvPIXdG70VJc2pAMw3NA6JKL5hhFu1sJX0Mnn/A==} - engines: {node: '>=6.0.0'} - '@jridgewell/sourcemap-codec@1.5.0': resolution: {integrity: sha512-gv3ZRaISU3fjPAgNsriBRqGWQL6quFx04YMPW/zD8XMLsU32mhCCbfbO6KZFLjvYpCZ8zyDEgqsgf+PwPaM7GQ==} - '@jridgewell/trace-mapping@0.3.25': - resolution: {integrity: sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ==} - '@libsql/client@0.14.0': resolution: {integrity: sha512-/9HEKfn6fwXB5aTEEoMeFh4CtG0ZzbncBb1e++OCdVpgKZ/xyMsIVYXm0w7Pv4RUel803vE6LwniB3PqD72R0Q==} @@ -1026,19 +982,6 @@ packages: cpu: [x64] os: [win32] - '@microsoft/api-extractor-model@7.30.4': - resolution: {integrity: sha512-RobC0gyVYsd2Fao9MTKOfTdBm41P/bCMUmzS5mQ7/MoAKEqy0FOBph3JOYdq4X4BsEnMEiSHc+0NUNmdzxCpjA==} - - '@microsoft/api-extractor@7.52.1': - resolution: {integrity: sha512-m3I5uAwE05orsu3D1AGyisX5KxsgVXB+U4bWOOaX/Z7Ftp/2Cy41qsNhO6LPvSxHBaapyser5dVorF1t5M6tig==} - hasBin: true - - '@microsoft/tsdoc-config@0.17.1': - resolution: {integrity: sha512-UtjIFe0C6oYgTnad4q1QP4qXwLhe6tIpNTRStJ2RZEPIkqQPREAwE5spzVxsdn9UaEMUqhh0AqSx3X4nWAKXWw==} - - '@microsoft/tsdoc@0.15.1': - resolution: {integrity: sha512-4aErSrCR/On/e5G2hDP0wjooqDdauzEbIq8hIkIe5pXV0rtWJZvdCEKL0ykZxex+IxIwBp0eGeV48hQN07dXtw==} - '@neon-rs/load@0.0.4': resolution: {integrity: sha512-kTPhdZyTQxB+2wpiRcFWrDcejc4JI6tkPuS7UZCG4l6Zvc5kU/gGQ/ozvHTh1XR5tS+UlfAfGuPajjzQjCiHCw==} @@ -1123,10 +1066,6 @@ packages: '@petamoriken/float16@3.9.2': resolution: {integrity: sha512-VgffxawQde93xKxT3qap3OH+meZf7VaSB5Sqd4Rqc+FP5alWbpOyan/7tRbOAvynjpG3GpdtAuGU/NdhQpmrog==} - '@pkgjs/parseargs@0.11.0': - resolution: {integrity: sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==} - engines: {node: '>=14'} - '@radix-ui/number@1.1.0': resolution: {integrity: sha512-V3gRzhVNU1ldS5XhAPTom1fOIo4ccrjjJgmE+LI2h/WaFpHmx0MQApT+KZHnx8abG6Avtfcz4WoEciMnpFT3HQ==} @@ -1487,123 +1426,6 @@ packages: '@radix-ui/rect@1.1.0': resolution: {integrity: sha512-A9+lCBZoaMJlVKcRBz2YByCG+Cp2t6nAnMnNba+XiWxnj6r4JUFqfsgwocMBZU9LPtdxC6wB56ySYpc7LQIoJg==} - '@rollup/rollup-android-arm-eabi@4.36.0': - resolution: {integrity: sha512-jgrXjjcEwN6XpZXL0HUeOVGfjXhPyxAbbhD0BlXUB+abTOpbPiN5Wb3kOT7yb+uEtATNYF5x5gIfwutmuBA26w==} - cpu: [arm] - os: [android] - - '@rollup/rollup-android-arm64@4.36.0': - resolution: {integrity: sha512-NyfuLvdPdNUfUNeYKUwPwKsE5SXa2J6bCt2LdB/N+AxShnkpiczi3tcLJrm5mA+eqpy0HmaIY9F6XCa32N5yzg==} - cpu: [arm64] - os: [android] - - '@rollup/rollup-darwin-arm64@4.36.0': - resolution: {integrity: sha512-JQ1Jk5G4bGrD4pWJQzWsD8I1n1mgPXq33+/vP4sk8j/z/C2siRuxZtaUA7yMTf71TCZTZl/4e1bfzwUmFb3+rw==} - cpu: [arm64] - os: [darwin] - - '@rollup/rollup-darwin-x64@4.36.0': - resolution: {integrity: sha512-6c6wMZa1lrtiRsbDziCmjE53YbTkxMYhhnWnSW8R/yqsM7a6mSJ3uAVT0t8Y/DGt7gxUWYuFM4bwWk9XCJrFKA==} - cpu: [x64] - os: [darwin] - - '@rollup/rollup-freebsd-arm64@4.36.0': - resolution: {integrity: sha512-KXVsijKeJXOl8QzXTsA+sHVDsFOmMCdBRgFmBb+mfEb/7geR7+C8ypAml4fquUt14ZyVXaw2o1FWhqAfOvA4sg==} - cpu: [arm64] - os: [freebsd] - - '@rollup/rollup-freebsd-x64@4.36.0': - resolution: {integrity: sha512-dVeWq1ebbvByI+ndz4IJcD4a09RJgRYmLccwlQ8bPd4olz3Y213uf1iwvc7ZaxNn2ab7bjc08PrtBgMu6nb4pQ==} - cpu: [x64] - os: [freebsd] - - '@rollup/rollup-linux-arm-gnueabihf@4.36.0': - resolution: {integrity: sha512-bvXVU42mOVcF4le6XSjscdXjqx8okv4n5vmwgzcmtvFdifQ5U4dXFYaCB87namDRKlUL9ybVtLQ9ztnawaSzvg==} - cpu: [arm] - os: [linux] - - '@rollup/rollup-linux-arm-musleabihf@4.36.0': - resolution: {integrity: sha512-JFIQrDJYrxOnyDQGYkqnNBtjDwTgbasdbUiQvcU8JmGDfValfH1lNpng+4FWlhaVIR4KPkeddYjsVVbmJYvDcg==} - cpu: [arm] - os: [linux] - - '@rollup/rollup-linux-arm64-gnu@4.36.0': - resolution: {integrity: sha512-KqjYVh3oM1bj//5X7k79PSCZ6CvaVzb7Qs7VMWS+SlWB5M8p3FqufLP9VNp4CazJ0CsPDLwVD9r3vX7Ci4J56A==} - cpu: [arm64] - os: [linux] - - '@rollup/rollup-linux-arm64-musl@4.36.0': - resolution: {integrity: sha512-QiGnhScND+mAAtfHqeT+cB1S9yFnNQ/EwCg5yE3MzoaZZnIV0RV9O5alJAoJKX/sBONVKeZdMfO8QSaWEygMhw==} - cpu: [arm64] - os: [linux] - - '@rollup/rollup-linux-loongarch64-gnu@4.36.0': - resolution: {integrity: sha512-1ZPyEDWF8phd4FQtTzMh8FQwqzvIjLsl6/84gzUxnMNFBtExBtpL51H67mV9xipuxl1AEAerRBgBwFNpkw8+Lg==} - cpu: [loong64] - os: [linux] - - '@rollup/rollup-linux-powerpc64le-gnu@4.36.0': - resolution: {integrity: sha512-VMPMEIUpPFKpPI9GZMhJrtu8rxnp6mJR3ZzQPykq4xc2GmdHj3Q4cA+7avMyegXy4n1v+Qynr9fR88BmyO74tg==} - cpu: [ppc64] - os: [linux] - - '@rollup/rollup-linux-riscv64-gnu@4.36.0': - resolution: {integrity: sha512-ttE6ayb/kHwNRJGYLpuAvB7SMtOeQnVXEIpMtAvx3kepFQeowVED0n1K9nAdraHUPJ5hydEMxBpIR7o4nrm8uA==} - cpu: [riscv64] - os: [linux] - - '@rollup/rollup-linux-s390x-gnu@4.36.0': - resolution: {integrity: sha512-4a5gf2jpS0AIe7uBjxDeUMNcFmaRTbNv7NxI5xOCs4lhzsVyGR/0qBXduPnoWf6dGC365saTiwag8hP1imTgag==} - cpu: [s390x] - os: [linux] - - '@rollup/rollup-linux-x64-gnu@4.36.0': - resolution: {integrity: sha512-5KtoW8UWmwFKQ96aQL3LlRXX16IMwyzMq/jSSVIIyAANiE1doaQsx/KRyhAvpHlPjPiSU/AYX/8m+lQ9VToxFQ==} - cpu: [x64] - os: [linux] - - '@rollup/rollup-linux-x64-musl@4.36.0': - resolution: {integrity: sha512-sycrYZPrv2ag4OCvaN5js+f01eoZ2U+RmT5as8vhxiFz+kxwlHrsxOwKPSA8WyS+Wc6Epid9QeI/IkQ9NkgYyQ==} - cpu: [x64] - os: [linux] - - '@rollup/rollup-win32-arm64-msvc@4.36.0': - resolution: {integrity: sha512-qbqt4N7tokFwwSVlWDsjfoHgviS3n/vZ8LK0h1uLG9TYIRuUTJC88E1xb3LM2iqZ/WTqNQjYrtmtGmrmmawB6A==} - cpu: [arm64] - os: [win32] - - '@rollup/rollup-win32-ia32-msvc@4.36.0': - resolution: {integrity: sha512-t+RY0JuRamIocMuQcfwYSOkmdX9dtkr1PbhKW42AMvaDQa+jOdpUYysroTF/nuPpAaQMWp7ye+ndlmmthieJrQ==} - cpu: [ia32] - os: [win32] - - '@rollup/rollup-win32-x64-msvc@4.36.0': - resolution: {integrity: sha512-aRXd7tRZkWLqGbChgcMMDEHjOKudo1kChb1Jt1IfR8cY/KIpgNviLeJy5FUb9IpSuQj8dU2fAYNMPW/hLKOSTw==} - cpu: [x64] - os: [win32] - - '@rushstack/node-core-library@5.12.0': - resolution: {integrity: sha512-QSwwzgzWoil1SCQse+yCHwlhRxNv2dX9siPnAb9zR/UmMhac4mjMrlMZpk64BlCeOFi1kJKgXRkihSwRMbboAQ==} - peerDependencies: - '@types/node': '*' - peerDependenciesMeta: - '@types/node': - optional: true - - '@rushstack/rig-package@0.5.3': - resolution: {integrity: sha512-olzSSjYrvCNxUFZowevC3uz8gvKr3WTpHQ7BkpjtRpA3wK+T0ybep/SRUMfr195gBzJm5gaXw0ZMgjIyHqJUow==} - - '@rushstack/terminal@0.15.1': - resolution: {integrity: sha512-3vgJYwumcjoDOXU3IxZfd616lqOdmr8Ezj4OWgJZfhmiBK4Nh7eWcv8sU8N/HdzXcuHDXCRGn/6O2Q75QvaZMA==} - peerDependencies: - '@types/node': '*' - peerDependenciesMeta: - '@types/node': - optional: true - - '@rushstack/ts-command-line@4.23.6': - resolution: {integrity: sha512-7WepygaF3YPEoToh4MAL/mmHkiIImQq3/uAkQX46kVoKTNOOlCtFGyNnze6OYuWw2o9rxsyrHVfIBKxq/am2RA==} - '@sec-ant/readable-stream@0.4.1': resolution: {integrity: sha512-831qok9r2t8AlxLko40y2ebgSDhenenCatLVeW/uBtnHPyhHOvG0C7TvfgecV+wHzIm5KUICgzmVpWS+IMEAeg==} @@ -1704,9 +1526,6 @@ packages: peerDependencies: react: ^18 || ^19 - '@types/argparse@1.0.38': - resolution: {integrity: sha512-ebDJ9b0e702Yr7pWgB0jzm+CX4Srzz8RcXtLJDJB+BSccqMa36uyH/zUsSYao5+BD1ytv3k3rPYCq4mAE1hsXA==} - '@types/estree@1.0.6': resolution: {integrity: sha512-AYnb1nQyY49te+VRAVgmzfcgjYS91mY5P0TKUDCLEM+gNnA+3T6rWITXRLYCpahpqSQbN5cE+gHpnPyXjHWxcw==} @@ -1733,9 +1552,6 @@ packages: '@types/react@19.0.12': resolution: {integrity: sha512-V6Ar115dBDrjbtXSrS+/Oruobc+qVbbUxDFC1RSbRqLt5SYvxxyIDrSC85RWml54g+jfNeEMZhEj7wW07ONQhA==} - '@types/vscode@1.98.0': - resolution: {integrity: sha512-+KuiWhpbKBaG2egF+51KjbGWatTH5BbmWQjSLMDCssb4xF8FJnW4nGH4nuAdOOfMbpD0QlHtI+C3tPq+DoKElg==} - '@types/ws@8.18.0': resolution: {integrity: sha512-8svvI3hMyvN0kKCJMvTJP/x6Y/EoQbepff882wL+Sn5QsXb3etnamgrJq4isrBxSJj5L2AuXcI0+bgkoAXGUJw==} @@ -1786,10 +1602,6 @@ packages: resolution: {integrity: sha512-AjOC3zfnxd6S4Eiy3jwktJPclqhFHNyd8L6Gycf9WUPoKZpgM5PjkxY1X7uSy61xVpiJDhhk7XT2NVsN3ALTWg==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - '@vscode/test-electron@2.4.1': - resolution: {integrity: sha512-Gc6EdaLANdktQ1t+zozoBVRynfIsMKMc94Svu1QreOBC8y76x4tvaK32TljrLi1LI2+PK58sDVbL7ALdqf3VRQ==} - engines: {node: '>=16'} - '@xobotyi/scrollbar-width@1.9.5': resolution: {integrity: sha512-N8tkAACJx2ww8vFMneJmaAgmjAG1tnVBZJRLRcx061tmsLRZHSEZSLuGWnwPtunsSLvSqXQ2wfp7Mgqg1I+2dQ==} @@ -1803,35 +1615,9 @@ packages: engines: {node: '>=0.4.0'} hasBin: true - agent-base@7.1.3: - resolution: {integrity: sha512-jRR5wdylq8CkOe6hei19GGZnxM6rBGwFl3Bg0YItGDimvjGtAvdZk4Pu6Cl4u4Igsws4a1fd1Vq3ezrhn4KmFw==} - engines: {node: '>= 14'} - - ajv-draft-04@1.0.0: - resolution: {integrity: sha512-mv00Te6nmYbRp5DCwclxtt7yV/joXJPGS7nM+97GdxvuttCOfgI3K4U25zboyeX0O+myI8ERluxQe5wljMmVIw==} - peerDependencies: - ajv: ^8.5.0 - peerDependenciesMeta: - ajv: - optional: true - - ajv-formats@3.0.1: - resolution: {integrity: sha512-8iUql50EUR+uUcdRQ3HDqa6EVyo3docL8g5WJ3FNcWmu62IbkGUue/pEyLBW8VGKKucTPgqeks4fIU1DA4yowQ==} - peerDependencies: - ajv: ^8.0.0 - peerDependenciesMeta: - ajv: - optional: true - ajv@6.12.6: resolution: {integrity: sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==} - ajv@8.12.0: - resolution: {integrity: sha512-sRu1kpcO9yLtYxBKvqfTeh9KzZEwO3STyX1HT+4CaDzC6HpTGYhIhPIzj9XuKU7KYDwnaeh5hcOwjy1QuJzBPA==} - - ajv@8.13.0: - resolution: {integrity: sha512-PRA911Blj99jR5RMeTunVbNXMF6Lp4vZXnk5GQjcnUWUTsrXtekg/pnmFFI2u/I36Y/2bITGS30GZCXei6uNkA==} - ansi-colors@4.1.3: resolution: {integrity: sha512-/6w/C21Pm1A7aZitlI5Ni/2J6FFQN8i1Cvz3kHABAAbw93v/NlvKdVOqz7CCWz/3iv/JplRSEEZ83XION15ovw==} engines: {node: '>=6'} @@ -1844,10 +1630,6 @@ packages: resolution: {integrity: sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==} engines: {node: '>=8'} - ansi-regex@6.1.0: - resolution: {integrity: sha512-7HSX4QQb4CspciLpVFwyRe79O3xsIZDDLER21kERQ71oaPodF8jL725AgJMFAYbooIqolJoRLuM81SpeUkpkvA==} - engines: {node: '>=12'} - ansi-styles@3.2.1: resolution: {integrity: sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==} engines: {node: '>=4'} @@ -1856,22 +1638,12 @@ packages: resolution: {integrity: sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==} engines: {node: '>=8'} - ansi-styles@6.2.1: - resolution: {integrity: sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug==} - engines: {node: '>=12'} - - any-promise@1.3.0: - resolution: {integrity: sha512-7UvmKalWRt1wgjL1RrGxoSJW/0QZFIegpeGvZG9kjp8vrRu55XTHbwnqq2GpXm9uLbcuhxm3IqX9OB4MZR1b2A==} - apisauce@2.1.6: resolution: {integrity: sha512-MdxR391op/FucS2YQRfB/NMRyCnHEPDd4h17LRIuVYi0BpGmMhpxc0shbOpfs5ahABuBEffNCGal5EcsydbBWg==} app-module-path@2.2.0: resolution: {integrity: sha512-gkco+qxENJV+8vFcDiiFhuoSvRXb2a/QPqpSoWhVz829VNJfOTnELbBmPmNKFxf3xdNnw4DWCkzkDaavcX/1YQ==} - argparse@1.0.10: - resolution: {integrity: sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==} - argparse@2.0.1: resolution: {integrity: sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==} @@ -1924,12 +1696,6 @@ packages: balanced-match@1.0.2: resolution: {integrity: sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==} - base64-js@1.5.1: - resolution: {integrity: sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==} - - bl@5.1.0: - resolution: {integrity: sha512-tv1ZJHLfTDnXE6tMHv73YgSJaWR2AFuPwMntBe7XL/GBFHnT0CLnsHMogfk5+GzCDC5ZWarSCYaIGATZt9dNsQ==} - brace-expansion@1.1.11: resolution: {integrity: sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==} @@ -1943,23 +1709,10 @@ packages: buffer-from@1.1.2: resolution: {integrity: sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==} - buffer@6.0.3: - resolution: {integrity: sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA==} - - bundle-require@5.1.0: - resolution: {integrity: sha512-3WrrOuZiyaaZPWiEt4G3+IffISVC9HYlWueJEBWED4ZH4aIAC2PnkdnuRrR94M+w6yGWn4AglWtJtBI8YqvgoA==} - engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} - peerDependencies: - esbuild: '>=0.18' - busboy@1.6.0: resolution: {integrity: sha512-8SFQbg/0hQ9xy3UNTB0YEnsNBbWfhf7RtnzpL7TkBiTBRfrQ9Fxcnz7VJsleJpyp6rVLvXiuORqjlHi5q+PYuA==} engines: {node: '>=10.16.0'} - cac@6.7.14: - resolution: {integrity: sha512-b6Ilus+c3RrdDk+JhLKUAQfzzgLEPy6wcXqS7f/xe1EETvsDP6GORG7SFuOs6cID5YkqchW/LXZbX5bc8j7ZcQ==} - engines: {node: '>=8'} - call-bind-apply-helpers@1.0.2: resolution: {integrity: sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==} engines: {node: '>= 0.4'} @@ -1987,14 +1740,6 @@ packages: resolution: {integrity: sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==} engines: {node: '>=10'} - chalk@5.4.1: - resolution: {integrity: sha512-zgVZuo2WcZgfUEmsn6eO3kINexW8RAE4maiQ8QNs8CtpPCSyMiYsULR3HQYkm3w8FIA3SberyMJMSldGsW+U3w==} - engines: {node: ^12.17.0 || ^14.13 || >=16.0.0} - - chokidar@4.0.3: - resolution: {integrity: sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA==} - engines: {node: '>= 14.16.0'} - class-variance-authority@0.7.1: resolution: {integrity: sha512-Ka+9Trutv7G8M6WT6SeiRWz792K5qEqIGEGzXKhAE6xOWAY6pPH8U+9IY3oCMv6kqTmLsv7Xh/2w2RigkePMsg==} @@ -2002,10 +1747,6 @@ packages: resolution: {integrity: sha512-I/zHAwsKf9FqGoXM4WWRACob9+SNukZTd94DWF57E4toouRulbCxcUh6RKUEOQlYTHJnzkPMySvPNaaSLNfLZw==} engines: {node: '>=8'} - cli-cursor@4.0.0: - resolution: {integrity: sha512-VGtlMu3x/4DOtIUwEkRezxUZ2lBacNJCHash0N0WeZDBS+7Ux1dm3XWAgWYxLJFMMdOeXMHXorshEFhbMSGelg==} - engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} - cli-spinners@2.9.2: resolution: {integrity: sha512-ywqV+5MmyL4E7ybXgKys4DugZbX0FC6LnwrhjuykIjnK9k8OQacQ7axGKnjDXWNhns0xot3bZI5h55H8yo9cJg==} engines: {node: '>=6'} @@ -2062,17 +1803,9 @@ packages: resolution: {integrity: sha512-yPVavfyCcRhmorC7rWlkHn15b4wDVgVmBA7kV4QVBsF7kv/9TKJAbAXVTxvTnwP8HHKjRCJDClKbciiYS7p0DQ==} engines: {node: '>=16'} - commander@4.1.1: - resolution: {integrity: sha512-NOKm8xhkzAjzFx8B2v5OAHT+u5pRQc2UCa2Vq9jYL/31o2wi9mxBA7LIFs3sV5VSC49z6pEhfbMULvShKj26WA==} - engines: {node: '>= 6'} - concat-map@0.0.1: resolution: {integrity: sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==} - consola@3.4.2: - resolution: {integrity: sha512-5IKcdX0nnYavi6G7TtOhwkYzyjfJlatbjMjuLSfE2kYT5pMDOilZ4OvMhi637CcDICTmz3wARPoyhqyX1Y+XvA==} - engines: {node: ^14.18.0 || >=16.10.0} - copy-to-clipboard@3.3.3: resolution: {integrity: sha512-2KV8NhB5JqC3ky0r9PMCAZKbUHSwtEo4CwCs0KXgruG43gX5PMqDEBbVU4OUzw2MuAWUfsuFmWvEKG5QRfSnJA==} @@ -2270,9 +2003,6 @@ packages: resolution: {integrity: sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==} engines: {node: '>= 0.4'} - eastasianwidth@0.2.0: - resolution: {integrity: sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==} - easy-stack@1.0.1: resolution: {integrity: sha512-wK2sCs4feiiJeFXn3zvY0p41mdU5VUgbgs1rNsc/y5ngFUijdWd+iIN8eoyuZHKB8xN6BL4PdWmzqFmxNg6V2w==} engines: {node: '>=6.0.0'} @@ -2286,15 +2016,9 @@ packages: engines: {node: '>=0.10.0'} hasBin: true - emoji-regex@10.4.0: - resolution: {integrity: sha512-EC+0oUMY1Rqm4O6LLrgjtYDvcVYTy7chDnM4Q7030tP4Kwj3u/pR6gP9ygnp2CJMK5Gq+9Q2oqmrFJAz01DXjw==} - emoji-regex@8.0.0: resolution: {integrity: sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==} - emoji-regex@9.2.2: - resolution: {integrity: sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==} - enhanced-resolve@5.18.1: resolution: {integrity: sha512-ZSW3ma5GkcQBIpwZTSRAI8N71Uuwgs93IezB7mf7R60tC8ZbJideoDNKjHn2O9KIlx6rkGTTEk1xUCK2E1Y2Yg==} engines: {node: '>=10.13.0'} @@ -2536,18 +2260,10 @@ packages: resolution: {integrity: sha512-dKx12eRCVIzqCxFGplyFKJMPvLEWgmNtUrpTiJIR5u97zEhRG8ySrtboPHZXx7daLxQVrl643cTzbab2tkQjxg==} engines: {node: '>= 0.4'} - foreground-child@3.3.1: - resolution: {integrity: sha512-gIXjKqtFuWEgzFRJA9WCQeSJLZDjgJUOMCMzxtvFq/37KojM1BFGufqsCy0r4qSQmYLsZYMeyRqzIWOMup03sw==} - engines: {node: '>=14'} - formdata-polyfill@4.0.10: resolution: {integrity: sha512-buewHzMvYL29jdeQTVILecSaZKnt/RJWjoZCF5OW60Z67/GmSLBkOFM7qh1PI3zFNtJbaZL5eQu1vLfazOwj4g==} engines: {node: '>=12.20.0'} - fs-extra@11.3.0: - resolution: {integrity: sha512-Z4XaCL6dUDHfP/jT25jJKMmtxvuwbkrD1vNSMFlo9lNLY2c5FHYSQgHPRZUjAB26TpDEoW9HCOgplrdbaPV/ew==} - engines: {node: '>=14.14'} - fs-jetpack@4.3.1: resolution: {integrity: sha512-dbeOK84F6BiQzk2yqqCVwCPWTxAvVGJ3fMQc6E2wuEohS28mR6yHngbrKuVCK1KHRx/ccByDylqu4H5PCP2urQ==} @@ -2616,10 +2332,6 @@ packages: resolution: {integrity: sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==} engines: {node: '>=10.13.0'} - glob@10.4.5: - resolution: {integrity: sha512-7Bv8RF0k6xjo7d4A/PxYLbUCfb6c+Vpd2/mB2yRDlew7Jb5hEXiCD9ibfO7wpk8i4sevK6DFny9h7EYbM3/sHg==} - hasBin: true - glob@7.2.3: resolution: {integrity: sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==} deprecated: Glob versions prior to v9 are no longer supported @@ -2681,14 +2393,6 @@ packages: resolution: {integrity: sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==} engines: {node: '>= 0.4'} - http-proxy-agent@7.0.2: - resolution: {integrity: sha512-T1gkAiYYDWYx3V5Bmyu7HcfcvL7mUrTWiM6yOfa3PIphViJ/gFPbvidQ+veqSOHci/PxBcDabeUNCzpOODJZig==} - engines: {node: '>= 14'} - - https-proxy-agent@7.0.6: - resolution: {integrity: sha512-vK9P5/iUfdl95AI+JVyUuIcVtd4ofvtrOr3HNtM2yxC9bnMbEdp3x01OhQNnjb8IJYi38VlTE3mBXwcfvywuSw==} - engines: {node: '>= 14'} - human-signals@2.1.0: resolution: {integrity: sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw==} engines: {node: '>=10.17.0'} @@ -2700,24 +2404,14 @@ packages: hyphenate-style-name@1.1.0: resolution: {integrity: sha512-WDC/ui2VVRrz3jOVi+XtjqkDjiVjTtFaAGiW37k6b+ohyQ5wYDOGkvCZa8+H0nx3gyvv0+BST9xuOgIyGQ00gw==} - ieee754@1.2.1: - resolution: {integrity: sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==} - ignore@5.3.2: resolution: {integrity: sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==} engines: {node: '>= 4'} - immediate@3.0.6: - resolution: {integrity: sha512-XXOFtyqDjNDAQxVfYxuF7g9Il/IbWmmlQg2MYKOH8ExIT1qg6xc4zyS3HaEEATgs1btfzxq15ciUiY7gjSXRGQ==} - import-fresh@3.3.1: resolution: {integrity: sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ==} engines: {node: '>=6'} - import-lazy@4.0.0: - resolution: {integrity: sha512-rKtvo6a868b5Hu3heneU+L4yEQ4jYKLtjpnPeUdK7h0yzXGmyBTypknlkCvHFBqfX9YlorEiMM6Dnq/5atfHkw==} - engines: {node: '>=8'} - imurmurhash@0.1.4: resolution: {integrity: sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==} engines: {node: '>=0.8.19'} @@ -2798,10 +2492,6 @@ packages: resolution: {integrity: sha512-2HvIEKRoqS62guEC+qBjpvRubdX910WCMuJTZ+I9yvqKU2/12eSL549HMwtabb4oupdj2sMP50k+XJfB/8JE6w==} engines: {node: '>=8'} - is-interactive@2.0.0: - resolution: {integrity: sha512-qP1vozQRI+BMOPcjFzrjXuQvdak2pHNUMZoeG2eRbiSqyvbEf/wQtEOTOX1guk6E3t36RkaqiSt8A/6YElNxLQ==} - engines: {node: '>=12'} - is-map@2.0.3: resolution: {integrity: sha512-1Qed0/Hr2m+YqxnM09CjA2d/i6YZNfF6R2oRAOj36eUdS6qIV/huPJNSEpKbupewFs+ZsJlxsjjPbc0/afW6Lw==} engines: {node: '>= 0.4'} @@ -2850,10 +2540,6 @@ packages: resolution: {integrity: sha512-p3EcsicXjit7SaskXHs1hA91QxgTw46Fv6EFKKGS5DRFLD8yKnohjF3hxoju94b/OcMZoQukzpPpBE9uLVKzgQ==} engines: {node: '>= 0.4'} - is-unicode-supported@1.3.0: - resolution: {integrity: sha512-43r2mRvz+8JRIKnWJ+3j8JtjRKZ6GmjzfaE/qiBJnikNnYv/6bagRJ1kUhNk8R5EX/GkobD+r+sfxCPJsiKBLQ==} - engines: {node: '>=12'} - is-unicode-supported@2.1.0: resolution: {integrity: sha512-mE00Gnza5EEB3Ds0HfMyllZzbBrmLOX3vfWoj9A9PEnTfratQ/BcaJOuMhnkhjXvb2+FkY3VuHqtAGpTPmglFQ==} engines: {node: '>=18'} @@ -2890,9 +2576,6 @@ packages: resolution: {integrity: sha512-H0dkQoCa3b2VEeKQBOxFph+JAbcrQdE7KC0UkqwpLmv2EC4P41QXP+rqo9wYodACiG5/WM5s9oDApTU8utwj9g==} engines: {node: '>= 0.4'} - jackspeak@3.4.3: - resolution: {integrity: sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw==} - jake@10.9.2: resolution: {integrity: sha512-2P4SQ0HrLQ+fw6llpLnOaGAvN2Zu6778SJMrCUwns4fOoG9ayrTiZk3VV8sCPkVZF8ab0zksVpS8FDY5pRCNBA==} engines: {node: '>=10'} @@ -2902,13 +2585,6 @@ packages: resolution: {integrity: sha512-rg9zJN+G4n2nfJl5MW3BMygZX56zKPNVEYYqq7adpmMh4Jn2QNEwhvQlFy6jPVdcod7txZtKHWnyZiA3a0zP7A==} hasBin: true - jju@1.4.0: - resolution: {integrity: sha512-8wb9Yw966OSxApiCt0K3yNJL8pnNeIv+OEq2YMidz4FKP6nonSRoOXc80iXY4JaN2FC11B9qsNmDsm+ZOfMROA==} - - joycon@3.1.1: - resolution: {integrity: sha512-34wB/Y7MW7bzjKRjUKTa46I2Z7eV62Rkhva+KkopW7Qvv/OSWBqvkSY7vusOPrNuZcUG3tApvdVgNB8POj3SPw==} - engines: {node: '>=10'} - js-base64@3.7.7: resolution: {integrity: sha512-7rCnleh0z2CkXhH67J8K1Ytz0b2Y+yxTPL+/KOJoa20hfnVQ/3/T6W/KflYI4bRHRagNeXeU2bkNGI3v1oS/lw==} @@ -2939,22 +2615,13 @@ packages: json-schema-traverse@0.4.1: resolution: {integrity: sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==} - json-schema-traverse@1.0.0: - resolution: {integrity: sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==} - json-stable-stringify-without-jsonify@1.0.1: resolution: {integrity: sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==} - jsonfile@6.1.0: - resolution: {integrity: sha512-5dgndWOriYSm5cnYaJNhalLNDKOqFwyDB/rr1E9ZsGciGvKPs8R2xYGCacuf3z6K1YKDz182fd+fY3cn3pMqXQ==} - jsx-ast-utils@3.3.5: resolution: {integrity: sha512-ZZow9HBI5O6EPgSJLUb8n2NKgmVWTwCvHGwFuJlMjvLFqlGG6pjirPhtdsseaLZjSibD8eegzmYpUZwoIlj2cQ==} engines: {node: '>=4.0'} - jszip@3.10.1: - resolution: {integrity: sha512-xXDvecyTpGLrqFrvkrUSoxxfJI5AH7U8zxxtVclpsUtMCq4JQ290LY8AW5c7Ggnr/Y/oK+bQMbqK2qmtk3pN4g==} - keyv@4.5.4: resolution: {integrity: sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==} @@ -2967,9 +2634,6 @@ packages: cpu: [x64, arm64, wasm32] os: [darwin, linux, win32] - lie@3.3.0: - resolution: {integrity: sha512-UaiMJzeWRlEujzAuw5LokY1L5ecNQYZKfmyZ9L7wDHb/p5etKaxXhohBcrw0EYby+G/NA52vRSN4N39dxHAIwQ==} - lightningcss-darwin-arm64@1.29.2: resolution: {integrity: sha512-cK/eMabSViKn/PG8U/a7aCorpeKLMlK0bQeNHmdb7qUnBkNPnL+oV5DjJUo0kqWsJUapZsM4jCfYItbqBDvlcA==} engines: {node: '>= 12.0.0'} @@ -3034,17 +2698,9 @@ packages: resolution: {integrity: sha512-6b6gd/RUXKaw5keVdSEtqFVdzWnU5jMxTUjA2bVcMNPLwSQ08Sv/UodBVtETLCn7k4S1Ibxwh7k68IwLZPgKaA==} engines: {node: '>= 12.0.0'} - lilconfig@3.1.3: - resolution: {integrity: sha512-/vlFKAoH5Cgt3Ie+JLhRbwOsCQePABiU3tJ1egGvyQ+33R/vcwM2Zl2QR/LzjsBeItPt3oSVXapn+m4nQDvpzw==} - engines: {node: '>=14'} - lines-and-columns@1.2.4: resolution: {integrity: sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==} - load-tsconfig@0.2.5: - resolution: {integrity: sha512-IXO6OCs9yg8tMKzfPZ1YmheJbZCiEsnBdcB03l0OcfK9prKnJb96siuHCr5Fl37/yo9DnKU+TLpxzTUspw9shg==} - engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} - locate-path@6.0.0: resolution: {integrity: sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==} engines: {node: '>=10'} @@ -3079,9 +2735,6 @@ packages: lodash.snakecase@4.1.1: resolution: {integrity: sha512-QZ1d4xoBHYUeuouhEq3lk3Uq7ldgyFXGBhg04+oRLnIz8o9T65Eh+8YdroUwn846zchkA9yDsDl5CVVaV2nqYw==} - lodash.sortby@4.7.0: - resolution: {integrity: sha512-HDWXG8isMntAyRF5vZ7xKuEvOhT4AhlRt/3czTSjvGUxjYCBVRQY48ViDHyfYz9VIoBkW4TMGQNapx+l3RUwdA==} - lodash.startcase@4.4.0: resolution: {integrity: sha512-+WKqsK294HMSc2jEbNgpHpd0JfIBhp7rEV4aqXWqFr6AlXov+SlcgB1Fv01y2kGe3Gc8nMW7VA0SrGuSkRfIEg==} @@ -3100,24 +2753,14 @@ packages: lodash.upperfirst@4.3.1: resolution: {integrity: sha512-sReKOYJIJf74dhJONhU4e0/shzi1trVbSWDOhKYE5XV2O+H7Sb2Dihwuc7xWxVl+DgFPyTqIN3zMfT9cq5iWDg==} - lodash@4.17.21: - resolution: {integrity: sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==} - log-symbols@3.0.0: resolution: {integrity: sha512-dSkNGuI7iG3mfvDzUuYZyvk5dD9ocYCYzNU6CYDE6+Xqd+gwme6Z00NS3dUh8mq/73HaEtT7m6W+yUPtU6BZnQ==} engines: {node: '>=8'} - log-symbols@5.1.0: - resolution: {integrity: sha512-l0x2DvrW294C9uDCoQe1VSU4gf529FkSZ6leBl4TiqZH/e+0R7hSfHQBNut2mNygDgHwvYHfFLn6Oxb3VWj2rA==} - engines: {node: '>=12'} - loose-envify@1.4.0: resolution: {integrity: sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==} hasBin: true - lru-cache@10.4.3: - resolution: {integrity: sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==} - lru-cache@6.0.0: resolution: {integrity: sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==} engines: {node: '>=10'} @@ -3149,9 +2792,6 @@ packages: resolution: {integrity: sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==} engines: {node: '>=6'} - minimatch@3.0.8: - resolution: {integrity: sha512-6FsRAQsxQ61mw+qP1ZzbL9Bc78x2p5OqNgNpnoAFLTrX8n5Kxph0CsnhmKKNXTWjXqU5L0pGPR7hYk+XWZr60Q==} - minimatch@3.1.2: resolution: {integrity: sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==} @@ -3163,10 +2803,6 @@ packages: resolution: {integrity: sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==} engines: {node: '>=16 || 14 >=14.17'} - minipass@7.1.2: - resolution: {integrity: sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==} - engines: {node: '>=16 || 14 >=14.17'} - mkdirp@1.0.4: resolution: {integrity: sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==} engines: {node: '>=10'} @@ -3175,9 +2811,6 @@ packages: ms@2.1.3: resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==} - mz@2.7.0: - resolution: {integrity: sha512-z81GNO7nnYMEhrGh9LeymoE4+Yr0Wn5McHIZMK5cfQCl+NDX08sCZgUc9/6MHni9IWuFLm1Z3HTCXu2z9fN62Q==} - nano-css@5.6.2: resolution: {integrity: sha512-+6bHaC8dSDGALM1HJjOHVXpuastdu2xFoZlC77Jh4cg+33Zcgm+Gxd+1xsnpZK14eyHObSp82+ll5y3SX75liw==} peerDependencies: @@ -3289,10 +2922,6 @@ packages: resolution: {integrity: sha512-YUOZbamht5mfLxPmk4M35CD/5DuOkAacxlEUbStVXpBAt4fyhBf+vZHI/HRkI++QUp3sNoeA2Gw4C+hi4eGSig==} engines: {node: '>=8'} - ora@7.0.1: - resolution: {integrity: sha512-0TUxTiFJWv+JnjWm4o9yvuskpEJLXTcng8MJuKd+SzAzp2o+OP3HWqNhB4OdJRt1Vsd9/mR0oyaEYlOnL7XIRw==} - engines: {node: '>=16'} - own-keys@1.0.1: resolution: {integrity: sha512-qFOyK5PjiWZd+QQIh+1jhdb9LpxTF0qs7Pm8o5QHYZ0M3vKqSqzsZaEB6oWlxZ+q2sJBMI/Ktgd2N5ZwQoRHfg==} engines: {node: '>= 0.4'} @@ -3309,11 +2938,13 @@ packages: resolution: {integrity: sha512-VkndIv2fIB99swvQoA65bm+fsmt6UNdGeIB0oxBs+WhAhdh08QA04JXpI7rbB9r08/nkbysKoya9rtDERYOYMA==} engines: {node: '>=18'} - package-json-from-dist@1.0.1: - resolution: {integrity: sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw==} + p-timeout@6.1.4: + resolution: {integrity: sha512-MyIV3ZA/PmyBN/ud8vV9XzwTrNtR4jFrObymZYnZqMmW0zA8Z17vnT0rBgFE/TlohB+YCHqXMgZzb3Csp49vqg==} + engines: {node: '>=14.16'} - pako@1.0.11: - resolution: {integrity: sha512-4hLB8Py4zZce5s4yd9XzopqwVv/yGNhV1Bl8NTmCq1763HeK2+EwVTv+leGeL13Dnh2wfbqowVPXCIO0z4taYw==} + p-wait-for@5.0.2: + resolution: {integrity: sha512-lwx6u1CotQYPVju77R+D0vFomni/AqRfqLmqQ8hekklqZ6gAY9rONh7lBQ0uxWMkC2AuX9b2DVAl8To0NyP1JA==} + engines: {node: '>=12'} parent-module@1.0.1: resolution: {integrity: sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==} @@ -3346,10 +2977,6 @@ packages: path-parse@1.0.7: resolution: {integrity: sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==} - path-scurry@1.11.1: - resolution: {integrity: sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA==} - engines: {node: '>=16 || 14 >=14.18'} - path-type@4.0.0: resolution: {integrity: sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==} engines: {node: '>=8'} @@ -3365,10 +2992,6 @@ packages: resolution: {integrity: sha512-M7BAV6Rlcy5u+m6oPhAPFgJTzAioX/6B0DxyvDlo9l8+T3nLKbrczg2WLUyzd45L8RqfUMyGPzekbMvX2Ldkwg==} engines: {node: '>=12'} - pirates@4.0.6: - resolution: {integrity: sha512-saLsH7WeYYPiD25LDuLRRY/i+6HaPYr6G1OUlN39otzkSTxKnubR9RTxS3/Kk50s1g2JTgFwWQDQyplC5/SHZg==} - engines: {node: '>= 6'} - pluralize@8.0.0: resolution: {integrity: sha512-Nc3IT5yHzflTfbjgqWcCPpo7DaKy4FnpB0l/zCAW0Tc7jxAiuqSxHasntB3D7887LSrA93kDJ9IXovxJYxyLCA==} engines: {node: '>=4'} @@ -3377,24 +3000,6 @@ packages: resolution: {integrity: sha512-/+5VFTchJDoVj3bhoqi6UeymcD00DAwb1nJwamzPvHEszJ4FpF6SNNbUbOS8yI56qHzdV8eK0qEfOSiodkTdxg==} engines: {node: '>= 0.4'} - postcss-load-config@6.0.1: - resolution: {integrity: sha512-oPtTM4oerL+UXmx+93ytZVN82RrlY/wPUV8IeDxFrzIjXOLF1pN+EmKPLbubvKHT2HC20xXsCAH2Z+CKV6Oz/g==} - engines: {node: '>= 18'} - peerDependencies: - jiti: '>=1.21.0' - postcss: '>=8.0.9' - tsx: ^4.8.1 - yaml: ^2.4.2 - peerDependenciesMeta: - jiti: - optional: true - postcss: - optional: true - tsx: - optional: true - yaml: - optional: true - postcss@8.4.31: resolution: {integrity: sha512-PS08Iboia9mts/2ygV3eLpY5ghnUcfLV/EXTOW1E2qYxJKGGBUtNjN76FYHnMs36RmARn41bC0AZmn+rR0OVpQ==} engines: {node: ^10 || ^12 || >=14} @@ -3498,14 +3103,6 @@ packages: readable-stream@2.3.8: resolution: {integrity: sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==} - readable-stream@3.6.2: - resolution: {integrity: sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==} - engines: {node: '>= 6'} - - readdirp@4.1.2: - resolution: {integrity: sha512-GDhwkLfywWL2s6vEjyhri+eXmfH6j1L7JE27WhqLeYzoh/A3DBaYGEj2H/HFZCn/kMfim73FXxEJTw06WtxQwg==} - engines: {node: '>= 14.18.0'} - reflect.getprototypeof@1.0.10: resolution: {integrity: sha512-00o4I+DVrefhv+nX0ulyi3biSHCPDe+yLv5o/p6d/UVlirijB8E16FtfwSAi4g3tcqrQ4lRAqQSoFEZJehYEcw==} engines: {node: '>= 0.4'} @@ -3521,10 +3118,6 @@ packages: resolution: {integrity: sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==} engines: {node: '>=0.10.0'} - require-from-string@2.0.2: - resolution: {integrity: sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==} - engines: {node: '>=0.10.0'} - resize-observer-polyfill@1.5.1: resolution: {integrity: sha512-LwZrotdHOo12nQuZlHEmtuXdqGoOD0OhaxopaNFxWzInpEgaLWoVuAMbTzixuosCx2nEG58ngzW3vxdWoxIgdg==} @@ -3532,18 +3125,9 @@ packages: resolution: {integrity: sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==} engines: {node: '>=4'} - resolve-from@5.0.0: - resolution: {integrity: sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==} - engines: {node: '>=8'} - resolve-pkg-maps@1.0.0: resolution: {integrity: sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw==} - resolve@1.22.10: - resolution: {integrity: sha512-NPRy+/ncIMeDlTAsuqwKIiferiawhefFJtkNSW0qZJEqMEb+qBt/77B/jGeeek+F0uOeN05CDa6HXbbIgtVX4w==} - engines: {node: '>= 0.4'} - hasBin: true - resolve@2.0.0-next.5: resolution: {integrity: sha512-U7WjGVG9sH8tvjW5SmGbQuui75FiyjAX72HX15DwBBwF9dNiQZRQAg9nnPhYy+TUnE0+VcrttuvNI8oSxZcocA==} hasBin: true @@ -3552,10 +3136,6 @@ packages: resolution: {integrity: sha512-l+sSefzHpj5qimhFSE5a8nufZYAM3sBSVMAPtYkmC+4EH2anSGaEMXSD0izRQbu9nfyQ9y5JrVmp7E8oZrUjvA==} engines: {node: '>=8'} - restore-cursor@4.0.0: - resolution: {integrity: sha512-I9fPXU9geO9bHOt9pHHOhOkYerIMsmVaWB0rA2AI9ERh/+x/i7MV5HKBNrg+ljO5eoPVgCcnFuRjJ9uH6I/3eg==} - engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} - reusify@1.1.0: resolution: {integrity: sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw==} engines: {iojs: '>=1.0.0', node: '>=0.10.0'} @@ -3565,11 +3145,6 @@ packages: deprecated: Rimraf versions prior to v4 are no longer supported hasBin: true - rollup@4.36.0: - resolution: {integrity: sha512-zwATAXNQxUcd40zgtQG0ZafcRK4g004WtEl7kbuhTWPvf07PsfohXl39jVUvPF7jvNAIkKPQ2XrsDlWuxBd++Q==} - engines: {node: '>=18.0.0', npm: '>=8.0.0'} - hasBin: true - rtl-css-js@1.16.1: resolution: {integrity: sha512-lRQgou1mu19e+Ya0LsTvKrVJ5TYUbqCVPAiImX3UfLTenarvPUl1QFdvu5Z3PYmHT9RCcwIfbjRQBntExyj3Zg==} @@ -3583,9 +3158,6 @@ packages: safe-buffer@5.1.2: resolution: {integrity: sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==} - safe-buffer@5.2.1: - resolution: {integrity: sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==} - safe-push-apply@1.0.0: resolution: {integrity: sha512-iKE9w/Z7xCzUMIZqdBsp6pEQvwuEebH4vdpjcDWnyzaI6yl6O9FHvVpmGelvEHNsoY6wGblkxR6Zty/h00WiSA==} engines: {node: '>= 0.4'} @@ -3610,11 +3182,6 @@ packages: engines: {node: '>=10'} hasBin: true - semver@7.5.4: - resolution: {integrity: sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==} - engines: {node: '>=10'} - hasBin: true - semver@7.7.1: resolution: {integrity: sha512-hlq8tAfn0m/61p4BVRcPzIGr6LKiMwo4VM6dGi6pt4qcRkmNzTcWq6eCEjEh+qXjkMDvPlOFFSGwQjoEa6gyMA==} engines: {node: '>=10'} @@ -3636,9 +3203,6 @@ packages: resolution: {integrity: sha512-RJRdvCo6IAnPdsvP/7m6bsQqNnn1FCBX5ZNtFL98MmFF/4xAIJTIg1YbHW5DC2W5SKZanrC6i4HsJqlajw/dZw==} engines: {node: '>= 0.4'} - setimmediate@1.0.5: - resolution: {integrity: sha512-MATJdZp8sLqDl/68LfQmbP8zKPLQNV6BIZoIgrscFDQ+RsvK/BxeDQOgyxKKoh0y/8h3BqVFnCqQ/gd+reiIXA==} - sharp@0.33.5: resolution: {integrity: sha512-haPVm1EkS9pgvHrQ/F3Xy+hgcuMV0Wm9vfIBSiwZ05k+xgb0PkBQpGsAA/oWdDobNaZTH5ppvHtzCFbnSEwHVw==} engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} @@ -3702,13 +3266,6 @@ packages: resolution: {integrity: sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==} engines: {node: '>=0.10.0'} - source-map@0.8.0-beta.0: - resolution: {integrity: sha512-2ymg6oRBpebeZi9UUNsgQ89bhx01TcTkmNTGnNO88imTmbSgy4nfujrgVEFKWpMTEGA11EDkTt7mqObTPdigIA==} - engines: {node: '>= 8'} - - sprintf-js@1.0.3: - resolution: {integrity: sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==} - stack-generator@2.0.10: resolution: {integrity: sha512-mwnua/hkqM6pF4k8SnmZ2zfETsRUpWXREfA/goT8SLCV4iOFa4bzOX2nDipWAZFPTjLvQB82f5yaodMVhK0yJQ==} @@ -3721,30 +3278,14 @@ packages: stacktrace-js@2.0.2: resolution: {integrity: sha512-Je5vBeY4S1r/RnLydLl0TBTi3F2qdfWmYsGvtfZgEI+SCprPppaIhQf5nGcal4gI4cGpCV/duLcAzT1np6sQqg==} - stdin-discarder@0.1.0: - resolution: {integrity: sha512-xhV7w8S+bUwlPTb4bAOUQhv8/cSS5offJuX8GQGq32ONF0ZtDWKfkdomM3HMRA+LhX6um/FZ0COqlwsjD53LeQ==} - engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} - streamsearch@1.1.0: resolution: {integrity: sha512-Mcc5wHehp9aXz1ax6bZUyY5afg9u2rv5cqQI3mRrYkGC8rW2hM02jWuwjtL++LS5qinSyhj2QfLyNsuc+VsExg==} engines: {node: '>=10.0.0'} - string-argv@0.3.2: - resolution: {integrity: sha512-aqD2Q0144Z+/RqG52NeHEkZauTAUWJO8c6yTftGJKO3Tja5tUgIfmIl6kExvhtxSDP7fXB6DvzkfMpCd/F3G+Q==} - engines: {node: '>=0.6.19'} - string-width@4.2.3: resolution: {integrity: sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==} engines: {node: '>=8'} - string-width@5.1.2: - resolution: {integrity: sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==} - engines: {node: '>=12'} - - string-width@6.1.0: - resolution: {integrity: sha512-k01swCJAgQmuADB0YIc+7TuatfNvTBVOoaUWJjTB9R4VJzR5vNWzf5t42ESVZFPS8xTySF7CAdV4t/aaIm3UnQ==} - engines: {node: '>=16'} - string.prototype.matchall@4.0.12: resolution: {integrity: sha512-6CC9uyBL+/48dYizRf7H7VAYCMCNTBeM78x/VTUe9bFEaxBepPJDa1Ow99LqI/1yF7kuy7Q3cQsYMrcjGUcskA==} engines: {node: '>= 0.4'} @@ -3770,9 +3311,6 @@ packages: string_decoder@1.1.1: resolution: {integrity: sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==} - string_decoder@1.3.0: - resolution: {integrity: sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==} - strip-ansi@5.2.0: resolution: {integrity: sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==} engines: {node: '>=6'} @@ -3781,10 +3319,6 @@ packages: resolution: {integrity: sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==} engines: {node: '>=8'} - strip-ansi@7.1.0: - resolution: {integrity: sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==} - engines: {node: '>=12'} - strip-final-newline@2.0.0: resolution: {integrity: sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA==} engines: {node: '>=6'} @@ -3821,11 +3355,6 @@ packages: stylis@4.3.6: resolution: {integrity: sha512-yQ3rwFWRfwNUY7H5vpU0wfdkNSnvnJinhF9830Swlaxl03zsOjCfmX0ugac+3LtK0lYSgwL/KXc8oYL3mG4YFQ==} - sucrase@3.35.0: - resolution: {integrity: sha512-8EbVDiu9iN/nESwxeSxDKe0dunta1GOlHufmSSXxMD2z2/tMZpDMpvXQGsc+ajGo8y2uYUmixaSRUc/QPoQ0GA==} - engines: {node: '>=16 || 14 >=14.17'} - hasBin: true - supports-color@5.5.0: resolution: {integrity: sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==} engines: {node: '>=4'} @@ -3834,10 +3363,6 @@ packages: resolution: {integrity: sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==} engines: {node: '>=8'} - supports-color@8.1.1: - resolution: {integrity: sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==} - engines: {node: '>=10'} - supports-preserve-symlinks-flag@1.0.0: resolution: {integrity: sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==} engines: {node: '>= 0.4'} @@ -3857,13 +3382,6 @@ packages: resolution: {integrity: sha512-GNzQvQTOIP6RyTfE2Qxb8ZVlNmw0n88vp1szwWRimP02mnTsx3Wtn5qRdqY9w2XduFNUgvOwhNnQsjwCp+kqaQ==} engines: {node: '>=6'} - thenify-all@1.6.0: - resolution: {integrity: sha512-RNxQH/qI8/t3thXJDwcstUO4zeqo64+Uy/+sNVRBx4Xn2OX+OZ9oP+iJnNFqplFra2ZUVeKCSa2oVWi3T4uVmA==} - engines: {node: '>=0.8'} - - thenify@3.3.1: - resolution: {integrity: sha512-RVZSIV5IG10Hk3enotrhvz0T9em6cyHBLkH/YAZuKqd8hRkKhSfCGIcP2KUY0EPxndzANBmNllzWPwak+bheSw==} - throttle-debounce@3.0.1: resolution: {integrity: sha512-dTEWWNu6JmeVXY0ZYoPuH5cRIwc0MeGbJwah9KUNYSJwommQpCzTySTpEe8Gs1J23aeWEuAobe4Ag7EHVt/LOg==} engines: {node: '>=10'} @@ -3871,13 +3389,6 @@ packages: through2@2.0.5: resolution: {integrity: sha512-/mrRod8xqpA+IHSLyGCQ2s8SPHiCDEeQJSep1jqLYeEUClOFG2Qsh+4FU6G9VeqpZnGW/Su8LQGc4YKni5rYSQ==} - tinyexec@0.3.2: - resolution: {integrity: sha512-KQQR9yN7R5+OSwaK0XQoj22pwHoTlgYqmUscPYoknOoWCWfj/5/ABTMRi69FrKU5ffPVh5QcFikpWJI/P1ocHA==} - - tinyglobby@0.2.12: - resolution: {integrity: sha512-qkf4trmKSIiMTs/E63cxH+ojC2unam7rJ0WrauAzpT3ECNTxGRMlaXxVbfxMUC/w0LaYk6jQ4y/nGR9uBO3tww==} - engines: {node: '>=12.0.0'} - to-regex-range@5.0.1: resolution: {integrity: sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==} engines: {node: '>=8.0'} @@ -3885,13 +3396,6 @@ packages: toggle-selection@1.0.6: resolution: {integrity: sha512-BiZS+C1OS8g/q2RRbJmy59xpyghNBqrr6k5L/uKBGRsTfxmu3ffiRnd8mlGPUVayg8pvfi5urfnu8TU7DVOkLQ==} - tr46@1.0.1: - resolution: {integrity: sha512-dTpowEjclQ7Kgx5SdBkqRzVhERQXov8/l9Ft9dVM9fmg0W0KQSVaXX9T4i6twCPNtYiZM53lpSSUAwJbFPOHxA==} - - tree-kill@1.2.2: - resolution: {integrity: sha512-L0Orpi8qGpRG//Nd+H90vFB+3iHnue1zSSGmNOOCh1GLJ7rUKVwV2HvijphGQS2UmhUZewS9VgvxYIdgr+fG1A==} - hasBin: true - ts-api-utils@2.0.1: resolution: {integrity: sha512-dnlgjFSVetynI8nzgJ+qF62efpglpWRk8isUEWZGWlJYySCTD6aKvbUDu+zbPeDakk3bg5H4XpitHukgfL1m9w==} engines: {node: '>=18.12'} @@ -3901,31 +3405,9 @@ packages: ts-easing@0.2.0: resolution: {integrity: sha512-Z86EW+fFFh/IFB1fqQ3/+7Zpf9t2ebOAxNI/V6Wo7r5gqiqtxmgTlQ1qbqQcjLKYeSHPTsEmvlJUDg/EuL0uHQ==} - ts-interface-checker@0.1.13: - resolution: {integrity: sha512-Y/arvbn+rrz3JCKl9C4kVNfTfSm2/mEp5FSz5EsZSANGPSlQrpRI5M4PKF+mJnE52jOO90PnPSc3Ur3bTQw0gA==} - tslib@2.8.1: resolution: {integrity: sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==} - tsup@8.4.0: - resolution: {integrity: sha512-b+eZbPCjz10fRryaAA7C8xlIHnf8VnsaRqydheLIqwG/Mcpfk8Z5zp3HayX7GaTygkigHl5cBUs+IhcySiIexQ==} - engines: {node: '>=18'} - hasBin: true - peerDependencies: - '@microsoft/api-extractor': ^7.36.0 - '@swc/core': ^1 - postcss: ^8.4.12 - typescript: '>=4.5.0' - peerDependenciesMeta: - '@microsoft/api-extractor': - optional: true - '@swc/core': - optional: true - postcss: - optional: true - typescript: - optional: true - tsx@4.19.3: resolution: {integrity: sha512-4H8vUNGNjQ4V2EOoGw005+c+dGuPSnhpPBPHBtsZdGZBk/iJb4kguGlPWaZTZ3q5nMtFOEsY0nRDlh9PJyd6SQ==} engines: {node: '>=18.0.0'} @@ -4008,10 +3490,6 @@ packages: resolution: {integrity: sha512-+QBBXBCvifc56fsbuxZQ6Sic3wqqc3WWaqxs58gvJrcOuN83HGTCwz3oS5phzU9LthRNE9VrJCFCLUgHeeFnfA==} engines: {node: '>=18'} - universalify@2.0.1: - resolution: {integrity: sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==} - engines: {node: '>= 10.0.0'} - untildify@4.0.0: resolution: {integrity: sha512-KK8xQ1mkzZeg9inewmFVDNkg3l5LUhoq9kN6iWYB/CC9YMG8HA+c1Q8HwDe6dEX7kErrEVNVBO3fWsVq5iDgtw==} engines: {node: '>=8'} @@ -4055,12 +3533,6 @@ packages: resolution: {integrity: sha512-d2JWLCivmZYTSIoge9MsgFCZrt571BikcWGYkjC1khllbTeDlGqZ2D8vD8E/lJa8WGWbb7Plm8/XJYV7IJHZZw==} engines: {node: '>= 8'} - webidl-conversions@4.0.2: - resolution: {integrity: sha512-YQ+BmxuTgd6UXZW3+ICGfyqRyHXVlD5GtQr5+qjiNW7bF0cqrzX500HVXPBOvgXb5YnzDd+h0zqyv61KUD7+Sg==} - - whatwg-url@7.1.0: - resolution: {integrity: sha512-WUu7Rg1DroM7oQvGWfOiAK21n74Gg+T4elXEQYkOhtyLeWiJFoOGLXPKI/9gzIie9CtwVLm8wtw6YJdKyxSjeg==} - which-boxed-primitive@1.1.1: resolution: {integrity: sha512-TbX3mj8n0odCBFVlY8AxkqcHASw3L60jIuF8jFP78az3C2YhmGvqbHBpAjTRH2/xqYunrJ9g1jSyjCjpoWzIAA==} engines: {node: '>= 0.4'} @@ -4095,10 +3567,6 @@ packages: resolution: {integrity: sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==} engines: {node: '>=10'} - wrap-ansi@8.1.0: - resolution: {integrity: sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==} - engines: {node: '>=12'} - wrappy@1.0.2: resolution: {integrity: sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==} @@ -4565,32 +4033,8 @@ snapshots: '@img/sharp-win32-x64@0.33.5': optional: true - '@isaacs/cliui@8.0.2': - dependencies: - string-width: 5.1.2 - string-width-cjs: string-width@4.2.3 - strip-ansi: 7.1.0 - strip-ansi-cjs: strip-ansi@6.0.1 - wrap-ansi: 8.1.0 - wrap-ansi-cjs: wrap-ansi@7.0.0 - - '@jridgewell/gen-mapping@0.3.8': - dependencies: - '@jridgewell/set-array': 1.2.1 - '@jridgewell/sourcemap-codec': 1.5.0 - '@jridgewell/trace-mapping': 0.3.25 - - '@jridgewell/resolve-uri@3.1.2': {} - - '@jridgewell/set-array@1.2.1': {} - '@jridgewell/sourcemap-codec@1.5.0': {} - '@jridgewell/trace-mapping@0.3.25': - dependencies: - '@jridgewell/resolve-uri': 3.1.2 - '@jridgewell/sourcemap-codec': 1.5.0 - '@libsql/client@0.14.0': dependencies: '@libsql/core': 0.14.0 @@ -4647,45 +4091,6 @@ snapshots: '@libsql/win32-x64-msvc@0.4.7': optional: true - '@microsoft/api-extractor-model@7.30.4(@types/node@20.17.24)': - dependencies: - '@microsoft/tsdoc': 0.15.1 - '@microsoft/tsdoc-config': 0.17.1 - '@rushstack/node-core-library': 5.12.0(@types/node@20.17.24) - transitivePeerDependencies: - - '@types/node' - optional: true - - '@microsoft/api-extractor@7.52.1(@types/node@20.17.24)': - dependencies: - '@microsoft/api-extractor-model': 7.30.4(@types/node@20.17.24) - '@microsoft/tsdoc': 0.15.1 - '@microsoft/tsdoc-config': 0.17.1 - '@rushstack/node-core-library': 5.12.0(@types/node@20.17.24) - '@rushstack/rig-package': 0.5.3 - '@rushstack/terminal': 0.15.1(@types/node@20.17.24) - '@rushstack/ts-command-line': 4.23.6(@types/node@20.17.24) - lodash: 4.17.21 - minimatch: 3.0.8 - resolve: 1.22.10 - semver: 7.5.4 - source-map: 0.6.1 - typescript: 5.8.2 - transitivePeerDependencies: - - '@types/node' - optional: true - - '@microsoft/tsdoc-config@0.17.1': - dependencies: - '@microsoft/tsdoc': 0.15.1 - ajv: 8.12.0 - jju: 1.4.0 - resolve: 1.22.10 - optional: true - - '@microsoft/tsdoc@0.15.1': - optional: true - '@neon-rs/load@0.0.4': {} '@next/env@15.2.2': {} @@ -4740,9 +4145,6 @@ snapshots: '@petamoriken/float16@3.9.2': {} - '@pkgjs/parseargs@0.11.0': - optional: true - '@radix-ui/number@1.1.0': {} '@radix-ui/primitive@1.1.1': {} @@ -5096,101 +4498,6 @@ snapshots: '@radix-ui/rect@1.1.0': {} - '@rollup/rollup-android-arm-eabi@4.36.0': - optional: true - - '@rollup/rollup-android-arm64@4.36.0': - optional: true - - '@rollup/rollup-darwin-arm64@4.36.0': - optional: true - - '@rollup/rollup-darwin-x64@4.36.0': - optional: true - - '@rollup/rollup-freebsd-arm64@4.36.0': - optional: true - - '@rollup/rollup-freebsd-x64@4.36.0': - optional: true - - '@rollup/rollup-linux-arm-gnueabihf@4.36.0': - optional: true - - '@rollup/rollup-linux-arm-musleabihf@4.36.0': - optional: true - - '@rollup/rollup-linux-arm64-gnu@4.36.0': - optional: true - - '@rollup/rollup-linux-arm64-musl@4.36.0': - optional: true - - '@rollup/rollup-linux-loongarch64-gnu@4.36.0': - optional: true - - '@rollup/rollup-linux-powerpc64le-gnu@4.36.0': - optional: true - - '@rollup/rollup-linux-riscv64-gnu@4.36.0': - optional: true - - '@rollup/rollup-linux-s390x-gnu@4.36.0': - optional: true - - '@rollup/rollup-linux-x64-gnu@4.36.0': - optional: true - - '@rollup/rollup-linux-x64-musl@4.36.0': - optional: true - - '@rollup/rollup-win32-arm64-msvc@4.36.0': - optional: true - - '@rollup/rollup-win32-ia32-msvc@4.36.0': - optional: true - - '@rollup/rollup-win32-x64-msvc@4.36.0': - optional: true - - '@rushstack/node-core-library@5.12.0(@types/node@20.17.24)': - dependencies: - ajv: 8.13.0 - ajv-draft-04: 1.0.0(ajv@8.13.0) - ajv-formats: 3.0.1(ajv@8.13.0) - fs-extra: 11.3.0 - import-lazy: 4.0.0 - jju: 1.4.0 - resolve: 1.22.10 - semver: 7.5.4 - optionalDependencies: - '@types/node': 20.17.24 - optional: true - - '@rushstack/rig-package@0.5.3': - dependencies: - resolve: 1.22.10 - strip-json-comments: 3.1.1 - optional: true - - '@rushstack/terminal@0.15.1(@types/node@20.17.24)': - dependencies: - '@rushstack/node-core-library': 5.12.0(@types/node@20.17.24) - supports-color: 8.1.1 - optionalDependencies: - '@types/node': 20.17.24 - optional: true - - '@rushstack/ts-command-line@4.23.6(@types/node@20.17.24)': - dependencies: - '@rushstack/terminal': 0.15.1(@types/node@20.17.24) - '@types/argparse': 1.0.38 - argparse: 1.0.10 - string-argv: 0.3.2 - transitivePeerDependencies: - - '@types/node' - optional: true - '@sec-ant/readable-stream@0.4.1': {} '@sindresorhus/merge-streams@4.0.0': {} @@ -5272,9 +4579,6 @@ snapshots: '@tanstack/query-core': 5.69.0 react: 19.0.0 - '@types/argparse@1.0.38': - optional: true - '@types/estree@1.0.6': {} '@types/js-cookie@2.2.7': {} @@ -5299,8 +4603,6 @@ snapshots: dependencies: csstype: 3.1.3 - '@types/vscode@1.98.0': {} - '@types/ws@8.18.0': dependencies: '@types/node': 20.17.24 @@ -5382,16 +4684,6 @@ snapshots: '@typescript-eslint/types': 8.26.1 eslint-visitor-keys: 4.2.0 - '@vscode/test-electron@2.4.1': - dependencies: - http-proxy-agent: 7.0.2 - https-proxy-agent: 7.0.6 - jszip: 3.10.1 - ora: 7.0.1 - semver: 7.7.1 - transitivePeerDependencies: - - supports-color - '@xobotyi/scrollbar-width@1.9.5': {} acorn-jsx@5.3.2(acorn@8.14.1): @@ -5400,18 +4692,6 @@ snapshots: acorn@8.14.1: {} - agent-base@7.1.3: {} - - ajv-draft-04@1.0.0(ajv@8.13.0): - optionalDependencies: - ajv: 8.13.0 - optional: true - - ajv-formats@3.0.1(ajv@8.13.0): - optionalDependencies: - ajv: 8.13.0 - optional: true - ajv@6.12.6: dependencies: fast-deep-equal: 3.1.3 @@ -5419,30 +4699,12 @@ snapshots: json-schema-traverse: 0.4.1 uri-js: 4.4.1 - ajv@8.12.0: - dependencies: - fast-deep-equal: 3.1.3 - json-schema-traverse: 1.0.0 - require-from-string: 2.0.2 - uri-js: 4.4.1 - optional: true - - ajv@8.13.0: - dependencies: - fast-deep-equal: 3.1.3 - json-schema-traverse: 1.0.0 - require-from-string: 2.0.2 - uri-js: 4.4.1 - optional: true - ansi-colors@4.1.3: {} ansi-regex@4.1.1: {} ansi-regex@5.0.1: {} - ansi-regex@6.1.0: {} - ansi-styles@3.2.1: dependencies: color-convert: 1.9.3 @@ -5451,10 +4713,6 @@ snapshots: dependencies: color-convert: 2.0.1 - ansi-styles@6.2.1: {} - - any-promise@1.3.0: {} - apisauce@2.1.6: dependencies: axios: 0.21.4 @@ -5463,11 +4721,6 @@ snapshots: app-module-path@2.2.0: {} - argparse@1.0.10: - dependencies: - sprintf-js: 1.0.3 - optional: true - argparse@2.0.1: {} aria-hidden@1.2.4: @@ -5545,14 +4798,6 @@ snapshots: balanced-match@1.0.2: {} - base64-js@1.5.1: {} - - bl@5.1.0: - dependencies: - buffer: 6.0.3 - inherits: 2.0.4 - readable-stream: 3.6.2 - brace-expansion@1.1.11: dependencies: balanced-match: 1.0.2 @@ -5568,22 +4813,10 @@ snapshots: buffer-from@1.1.2: {} - buffer@6.0.3: - dependencies: - base64-js: 1.5.1 - ieee754: 1.2.1 - - bundle-require@5.1.0(esbuild@0.25.1): - dependencies: - esbuild: 0.25.1 - load-tsconfig: 0.2.5 - busboy@1.6.0: dependencies: streamsearch: 1.1.0 - cac@6.7.14: {} - call-bind-apply-helpers@1.0.2: dependencies: es-errors: 1.3.0 @@ -5616,12 +4849,6 @@ snapshots: ansi-styles: 4.3.0 supports-color: 7.2.0 - chalk@5.4.1: {} - - chokidar@4.0.3: - dependencies: - readdirp: 4.1.2 - class-variance-authority@0.7.1: dependencies: clsx: 2.1.1 @@ -5630,10 +4857,6 @@ snapshots: dependencies: restore-cursor: 3.1.0 - cli-cursor@4.0.0: - dependencies: - restore-cursor: 4.0.0 - cli-spinners@2.9.2: {} cli-table3@0.6.0: @@ -5695,12 +4918,8 @@ snapshots: commander@11.1.0: {} - commander@4.1.1: {} - concat-map@0.0.1: {} - consola@3.4.2: {} - copy-to-clipboard@3.3.3: dependencies: toggle-selection: 1.0.6 @@ -5830,8 +5049,6 @@ snapshots: es-errors: 1.3.0 gopd: 1.2.0 - eastasianwidth@0.2.0: {} - easy-stack@1.0.1: {} eciesjs@0.4.14: @@ -5845,12 +5062,8 @@ snapshots: dependencies: jake: 10.9.2 - emoji-regex@10.4.0: {} - emoji-regex@8.0.0: {} - emoji-regex@9.2.2: {} - enhanced-resolve@5.18.1: dependencies: graceful-fs: 4.2.11 @@ -6272,22 +5485,10 @@ snapshots: dependencies: is-callable: 1.2.7 - foreground-child@3.3.1: - dependencies: - cross-spawn: 7.0.6 - signal-exit: 4.1.0 - formdata-polyfill@4.0.10: dependencies: fetch-blob: 3.2.0 - fs-extra@11.3.0: - dependencies: - graceful-fs: 4.2.11 - jsonfile: 6.1.0 - universalify: 2.0.1 - optional: true - fs-jetpack@4.3.1: dependencies: minimatch: 3.1.2 @@ -6371,15 +5572,6 @@ snapshots: dependencies: is-glob: 4.0.3 - glob@10.4.5: - dependencies: - foreground-child: 3.3.1 - jackspeak: 3.4.3 - minimatch: 9.0.5 - minipass: 7.1.2 - package-json-from-dist: 1.0.1 - path-scurry: 1.11.1 - glob@7.2.3: dependencies: fs.realpath: 1.0.0 @@ -6463,40 +5655,19 @@ snapshots: dependencies: function-bind: 1.1.2 - http-proxy-agent@7.0.2: - dependencies: - agent-base: 7.1.3 - debug: 4.4.0 - transitivePeerDependencies: - - supports-color - - https-proxy-agent@7.0.6: - dependencies: - agent-base: 7.1.3 - debug: 4.4.0 - transitivePeerDependencies: - - supports-color - human-signals@2.1.0: {} human-signals@8.0.0: {} hyphenate-style-name@1.1.0: {} - ieee754@1.2.1: {} - ignore@5.3.2: {} - immediate@3.0.6: {} - import-fresh@3.3.1: dependencies: parent-module: 1.0.1 resolve-from: 4.0.0 - import-lazy@4.0.0: - optional: true - imurmurhash@0.1.4: {} inflight@1.0.6: @@ -6582,8 +5753,6 @@ snapshots: is-interactive@1.0.0: {} - is-interactive@2.0.0: {} - is-map@2.0.3: {} is-number-object@1.1.1: @@ -6627,8 +5796,6 @@ snapshots: dependencies: which-typed-array: 1.1.19 - is-unicode-supported@1.3.0: {} - is-unicode-supported@2.1.0: {} is-weakmap@2.0.2: {} @@ -6661,12 +5828,6 @@ snapshots: has-symbols: 1.1.0 set-function-name: 2.0.2 - jackspeak@3.4.3: - dependencies: - '@isaacs/cliui': 8.0.2 - optionalDependencies: - '@pkgjs/parseargs': 0.11.0 - jake@10.9.2: dependencies: async: 3.2.6 @@ -6676,11 +5837,6 @@ snapshots: jiti@2.4.2: {} - jju@1.4.0: - optional: true - - joycon@3.1.1: {} - js-base64@3.7.7: {} js-cookie@2.2.1: {} @@ -6703,18 +5859,8 @@ snapshots: json-schema-traverse@0.4.1: {} - json-schema-traverse@1.0.0: - optional: true - json-stable-stringify-without-jsonify@1.0.1: {} - jsonfile@6.1.0: - dependencies: - universalify: 2.0.1 - optionalDependencies: - graceful-fs: 4.2.11 - optional: true - jsx-ast-utils@3.3.5: dependencies: array-includes: 3.1.8 @@ -6722,13 +5868,6 @@ snapshots: object.assign: 4.1.7 object.values: 1.2.1 - jszip@3.10.1: - dependencies: - lie: 3.3.0 - pako: 1.0.11 - readable-stream: 2.3.8 - setimmediate: 1.0.5 - keyv@4.5.4: dependencies: json-buffer: 3.0.1 @@ -6751,10 +5890,6 @@ snapshots: '@libsql/linux-x64-musl': 0.4.7 '@libsql/win32-x64-msvc': 0.4.7 - lie@3.3.0: - dependencies: - immediate: 3.0.6 - lightningcss-darwin-arm64@1.29.2: optional: true @@ -6800,12 +5935,8 @@ snapshots: lightningcss-win32-arm64-msvc: 1.29.2 lightningcss-win32-x64-msvc: 1.29.2 - lilconfig@3.1.3: {} - lines-and-columns@1.2.4: {} - load-tsconfig@0.2.5: {} - locate-path@6.0.0: dependencies: p-locate: 5.0.0 @@ -6830,8 +5961,6 @@ snapshots: lodash.snakecase@4.1.1: {} - lodash.sortby@4.7.0: {} - lodash.startcase@4.4.0: {} lodash.trim@4.5.1: {} @@ -6844,24 +5973,14 @@ snapshots: lodash.upperfirst@4.3.1: {} - lodash@4.17.21: - optional: true - log-symbols@3.0.0: dependencies: chalk: 2.4.2 - log-symbols@5.1.0: - dependencies: - chalk: 5.4.1 - is-unicode-supported: 1.3.0 - loose-envify@1.4.0: dependencies: js-tokens: 4.0.0 - lru-cache@10.4.3: {} - lru-cache@6.0.0: dependencies: yallist: 4.0.0 @@ -6885,11 +6004,6 @@ snapshots: mimic-fn@2.1.0: {} - minimatch@3.0.8: - dependencies: - brace-expansion: 1.1.11 - optional: true - minimatch@3.1.2: dependencies: brace-expansion: 1.1.11 @@ -6902,18 +6016,10 @@ snapshots: dependencies: brace-expansion: 2.0.1 - minipass@7.1.2: {} - mkdirp@1.0.4: {} ms@2.1.3: {} - mz@2.7.0: - dependencies: - any-promise: 1.3.0 - object-assign: 4.1.1 - thenify-all: 1.6.0 - nano-css@5.6.2(react-dom@19.0.0(react@19.0.0))(react@19.0.0): dependencies: '@jridgewell/sourcemap-codec': 1.5.0 @@ -7055,18 +6161,6 @@ snapshots: strip-ansi: 5.2.0 wcwidth: 1.0.1 - ora@7.0.1: - dependencies: - chalk: 5.4.1 - cli-cursor: 4.0.0 - cli-spinners: 2.9.2 - is-interactive: 2.0.0 - is-unicode-supported: 1.3.0 - log-symbols: 5.1.0 - stdin-discarder: 0.1.0 - string-width: 6.1.0 - strip-ansi: 7.1.0 - own-keys@1.0.1: dependencies: get-intrinsic: 1.3.0 @@ -7083,9 +6177,11 @@ snapshots: p-map@7.0.3: {} - package-json-from-dist@1.0.1: {} + p-timeout@6.1.4: {} - pako@1.0.11: {} + p-wait-for@5.0.2: + dependencies: + p-timeout: 6.1.4 parent-module@1.0.1: dependencies: @@ -7110,11 +6206,6 @@ snapshots: path-parse@1.0.7: {} - path-scurry@1.11.1: - dependencies: - lru-cache: 10.4.3 - minipass: 7.1.2 - path-type@4.0.0: {} picocolors@1.1.1: {} @@ -7123,20 +6214,10 @@ snapshots: picomatch@4.0.2: {} - pirates@4.0.6: {} - pluralize@8.0.0: {} possible-typed-array-names@1.1.0: {} - postcss-load-config@6.0.1(jiti@2.4.2)(postcss@8.5.3)(tsx@4.19.3): - dependencies: - lilconfig: 3.1.3 - optionalDependencies: - jiti: 2.4.2 - postcss: 8.5.3 - tsx: 4.19.3 - postcss@8.4.31: dependencies: nanoid: 3.3.11 @@ -7252,14 +6333,6 @@ snapshots: string_decoder: 1.1.1 util-deprecate: 1.0.2 - readable-stream@3.6.2: - dependencies: - inherits: 2.0.4 - string_decoder: 1.3.0 - util-deprecate: 1.0.2 - - readdirp@4.1.2: {} - reflect.getprototypeof@1.0.10: dependencies: call-bind: 1.0.8 @@ -7284,24 +6357,12 @@ snapshots: require-directory@2.1.1: {} - require-from-string@2.0.2: - optional: true - resize-observer-polyfill@1.5.1: {} resolve-from@4.0.0: {} - resolve-from@5.0.0: {} - resolve-pkg-maps@1.0.0: {} - resolve@1.22.10: - dependencies: - is-core-module: 2.16.1 - path-parse: 1.0.7 - supports-preserve-symlinks-flag: 1.0.0 - optional: true - resolve@2.0.0-next.5: dependencies: is-core-module: 2.16.1 @@ -7313,42 +6374,12 @@ snapshots: onetime: 5.1.2 signal-exit: 3.0.7 - restore-cursor@4.0.0: - dependencies: - onetime: 5.1.2 - signal-exit: 3.0.7 - reusify@1.1.0: {} rimraf@2.7.1: dependencies: glob: 7.2.3 - rollup@4.36.0: - dependencies: - '@types/estree': 1.0.6 - optionalDependencies: - '@rollup/rollup-android-arm-eabi': 4.36.0 - '@rollup/rollup-android-arm64': 4.36.0 - '@rollup/rollup-darwin-arm64': 4.36.0 - '@rollup/rollup-darwin-x64': 4.36.0 - '@rollup/rollup-freebsd-arm64': 4.36.0 - '@rollup/rollup-freebsd-x64': 4.36.0 - '@rollup/rollup-linux-arm-gnueabihf': 4.36.0 - '@rollup/rollup-linux-arm-musleabihf': 4.36.0 - '@rollup/rollup-linux-arm64-gnu': 4.36.0 - '@rollup/rollup-linux-arm64-musl': 4.36.0 - '@rollup/rollup-linux-loongarch64-gnu': 4.36.0 - '@rollup/rollup-linux-powerpc64le-gnu': 4.36.0 - '@rollup/rollup-linux-riscv64-gnu': 4.36.0 - '@rollup/rollup-linux-s390x-gnu': 4.36.0 - '@rollup/rollup-linux-x64-gnu': 4.36.0 - '@rollup/rollup-linux-x64-musl': 4.36.0 - '@rollup/rollup-win32-arm64-msvc': 4.36.0 - '@rollup/rollup-win32-ia32-msvc': 4.36.0 - '@rollup/rollup-win32-x64-msvc': 4.36.0 - fsevents: 2.3.3 - rtl-css-js@1.16.1: dependencies: '@babel/runtime': 7.26.10 @@ -7367,8 +6398,6 @@ snapshots: safe-buffer@5.1.2: {} - safe-buffer@5.2.1: {} - safe-push-apply@1.0.0: dependencies: es-errors: 1.3.0 @@ -7390,11 +6419,6 @@ snapshots: dependencies: lru-cache: 6.0.0 - semver@7.5.4: - dependencies: - lru-cache: 6.0.0 - optional: true - semver@7.7.1: {} set-function-length@1.2.2: @@ -7421,8 +6445,6 @@ snapshots: es-errors: 1.3.0 es-object-atoms: 1.1.1 - setimmediate@1.0.5: {} - sharp@0.33.5: dependencies: color: 4.2.3 @@ -7511,13 +6533,6 @@ snapshots: source-map@0.6.1: {} - source-map@0.8.0-beta.0: - dependencies: - whatwg-url: 7.1.0 - - sprintf-js@1.0.3: - optional: true - stack-generator@2.0.10: dependencies: stackframe: 1.3.4 @@ -7535,33 +6550,14 @@ snapshots: stack-generator: 2.0.10 stacktrace-gps: 3.1.2 - stdin-discarder@0.1.0: - dependencies: - bl: 5.1.0 - streamsearch@1.1.0: {} - string-argv@0.3.2: - optional: true - string-width@4.2.3: dependencies: emoji-regex: 8.0.0 is-fullwidth-code-point: 3.0.0 strip-ansi: 6.0.1 - string-width@5.1.2: - dependencies: - eastasianwidth: 0.2.0 - emoji-regex: 9.2.2 - strip-ansi: 7.1.0 - - string-width@6.1.0: - dependencies: - eastasianwidth: 0.2.0 - emoji-regex: 10.4.0 - strip-ansi: 7.1.0 - string.prototype.matchall@4.0.12: dependencies: call-bind: 1.0.8 @@ -7612,10 +6608,6 @@ snapshots: dependencies: safe-buffer: 5.1.2 - string_decoder@1.3.0: - dependencies: - safe-buffer: 5.2.1 - strip-ansi@5.2.0: dependencies: ansi-regex: 4.1.1 @@ -7624,10 +6616,6 @@ snapshots: dependencies: ansi-regex: 5.0.1 - strip-ansi@7.1.0: - dependencies: - ansi-regex: 6.1.0 - strip-final-newline@2.0.0: {} strip-final-newline@4.0.0: {} @@ -7645,16 +6633,6 @@ snapshots: stylis@4.3.6: {} - sucrase@3.35.0: - dependencies: - '@jridgewell/gen-mapping': 0.3.8 - commander: 4.1.1 - glob: 10.4.5 - lines-and-columns: 1.2.4 - mz: 2.7.0 - pirates: 4.0.6 - ts-interface-checker: 0.1.13 - supports-color@5.5.0: dependencies: has-flag: 3.0.0 @@ -7663,11 +6641,6 @@ snapshots: dependencies: has-flag: 4.0.0 - supports-color@8.1.1: - dependencies: - has-flag: 4.0.0 - optional: true - supports-preserve-symlinks-flag@1.0.0: {} tailwind-merge@3.0.2: {} @@ -7680,14 +6653,6 @@ snapshots: tapable@2.2.1: {} - thenify-all@1.6.0: - dependencies: - thenify: 3.3.1 - - thenify@3.3.1: - dependencies: - any-promise: 1.3.0 - throttle-debounce@3.0.1: {} through2@2.0.5: @@ -7695,63 +6660,20 @@ snapshots: readable-stream: 2.3.8 xtend: 4.0.2 - tinyexec@0.3.2: {} - - tinyglobby@0.2.12: - dependencies: - fdir: 6.4.3(picomatch@4.0.2) - picomatch: 4.0.2 - to-regex-range@5.0.1: dependencies: is-number: 7.0.0 toggle-selection@1.0.6: {} - tr46@1.0.1: - dependencies: - punycode: 2.3.1 - - tree-kill@1.2.2: {} - ts-api-utils@2.0.1(typescript@5.8.2): dependencies: typescript: 5.8.2 ts-easing@0.2.0: {} - ts-interface-checker@0.1.13: {} - tslib@2.8.1: {} - tsup@8.4.0(@microsoft/api-extractor@7.52.1(@types/node@20.17.24))(jiti@2.4.2)(postcss@8.5.3)(tsx@4.19.3)(typescript@5.8.2): - dependencies: - bundle-require: 5.1.0(esbuild@0.25.1) - cac: 6.7.14 - chokidar: 4.0.3 - consola: 3.4.2 - debug: 4.4.0 - esbuild: 0.25.1 - joycon: 3.1.1 - picocolors: 1.1.1 - postcss-load-config: 6.0.1(jiti@2.4.2)(postcss@8.5.3)(tsx@4.19.3) - resolve-from: 5.0.0 - rollup: 4.36.0 - source-map: 0.8.0-beta.0 - sucrase: 3.35.0 - tinyexec: 0.3.2 - tinyglobby: 0.2.12 - tree-kill: 1.2.2 - optionalDependencies: - '@microsoft/api-extractor': 7.52.1(@types/node@20.17.24) - postcss: 8.5.3 - typescript: 5.8.2 - transitivePeerDependencies: - - jiti - - supports-color - - tsx - - yaml - tsx@4.19.3: dependencies: esbuild: 0.25.1 @@ -7846,9 +6768,6 @@ snapshots: unicorn-magic@0.3.0: {} - universalify@2.0.1: - optional: true - untildify@4.0.0: {} uri-js@4.4.1: @@ -7887,14 +6806,6 @@ snapshots: web-streams-polyfill@3.3.3: {} - webidl-conversions@4.0.2: {} - - whatwg-url@7.1.0: - dependencies: - lodash.sortby: 4.7.0 - tr46: 1.0.1 - webidl-conversions: 4.0.2 - which-boxed-primitive@1.1.1: dependencies: is-bigint: 1.1.0 @@ -7952,12 +6863,6 @@ snapshots: string-width: 4.2.3 strip-ansi: 6.0.1 - wrap-ansi@8.1.0: - dependencies: - ansi-styles: 6.2.1 - string-width: 5.1.2 - strip-ansi: 7.1.0 - wrappy@1.0.2: {} ws@8.18.1: {} diff --git a/benchmark/turbo.json b/benchmark/turbo.json index 4f2056691b..6af48fcaac 100644 --- a/benchmark/turbo.json +++ b/benchmark/turbo.json @@ -15,11 +15,11 @@ "format": {}, "lint": {}, "check-types": { - "dependsOn": ["build"] + "dependsOn": [] }, "test": {}, "dev": { - "dependsOn": ["build"], + "dependsOn": [], "cache": false, "persistent": true }, diff --git a/e2e/VSCODE_INTEGRATION_TESTS.md b/e2e/VSCODE_INTEGRATION_TESTS.md index 452c00c226..51e3e0dade 100644 --- a/e2e/VSCODE_INTEGRATION_TESTS.md +++ b/e2e/VSCODE_INTEGRATION_TESTS.md @@ -156,7 +156,7 @@ while (Date.now() - startTime < timeout) { 6. **Grading**: When grading tests, use the `Grade:` format to ensure the test is graded correctly (See modes.test.ts for an example). ```typescript -await globalThis.api.startNewTask( - `Given this prompt: ${testPrompt} grade the response from 1 to 10 in the format of "Grade: (1-10)": ${output} \n Be sure to say 'I AM DONE GRADING' after the task is complete`, -) +await globalThis.api.startNewTask({ + text: `Given this prompt: ${testPrompt} grade the response from 1 to 10 in the format of "Grade: (1-10)": ${output} \n Be sure to say 'I AM DONE GRADING' after the task is complete`, +}) ``` diff --git a/e2e/src/suite/modes.test.ts b/e2e/src/suite/modes.test.ts index 6e130efd54..e2282b27ca 100644 --- a/e2e/src/suite/modes.test.ts +++ b/e2e/src/suite/modes.test.ts @@ -1,9 +1,11 @@ import * as assert from "assert" -import { getCompletion, getMessage, sleep, waitForCompletion, waitUntilAborted } from "./utils" +import { RooCodeEventName, ClineMessage } from "../../../src/exports/roo-code" + +import { waitUntilCompleted } from "./utils" suite("Roo Code Modes", () => { - test("Should handle switching modes correctly", async function () { + test("Should handle switching modes correctly", async () => { const api = globalThis.api /** @@ -14,9 +16,16 @@ suite("Roo Code Modes", () => { "For each mode (Code, Architect, Ask) respond with the mode name and what it specializes in after switching to that mode. " + "Do not start with the current mode." - await api.setConfiguration({ mode: "Code", alwaysAllowModeSwitch: true, autoApprovalEnabled: true }) - const switchModesTaskId = await api.startNewTask(switchModesPrompt) - await waitForCompletion({ api, taskId: switchModesTaskId, timeout: 60_000 }) + let messages: ClineMessage[] = [] + + api.on(RooCodeEventName.Message, ({ message }) => messages.push(message)) + + const switchModesTaskId = await api.startNewTask({ + configuration: { mode: "Code", alwaysAllowModeSwitch: true, autoApprovalEnabled: true }, + text: switchModesPrompt, + }) + + await waitUntilCompleted({ api, taskId: switchModesTaskId, timeout: 60_000 }) /** * Grade the response. @@ -24,17 +33,17 @@ suite("Roo Code Modes", () => { const gradePrompt = `Given this prompt: ${switchModesPrompt} grade the response from 1 to 10 in the format of "Grade: (1-10)": ` + - api - .getMessages(switchModesTaskId) + messages .filter(({ type }) => type === "say") .map(({ text }) => text ?? "") .join("\n") - await api.setConfiguration({ mode: "Ask" }) - const gradeTaskId = await api.startNewTask(gradePrompt) - await waitForCompletion({ api, taskId: gradeTaskId, timeout: 60_000 }) + messages = [] - const completion = getCompletion({ api, taskId: gradeTaskId }) + const gradeTaskId = await api.startNewTask({ configuration: { mode: "Ask" }, text: gradePrompt }) + await waitUntilCompleted({ api, taskId: gradeTaskId, timeout: 60_000 }) + + const completion = messages.find(({ type, say, partial }) => say === "completion_result" && partial === false) const match = completion?.text?.match(/Grade: (\d+)/) const score = parseInt(match?.[1] ?? "0") assert.ok(score >= 7 && score <= 10, `Grade must be between 7 and 10 - ${completion?.text}`) diff --git a/e2e/src/suite/subtasks.test.ts b/e2e/src/suite/subtasks.test.ts index 2a62197908..16ef89f994 100644 --- a/e2e/src/suite/subtasks.test.ts +++ b/e2e/src/suite/subtasks.test.ts @@ -1,11 +1,16 @@ import * as assert from "assert" -import { sleep, waitFor, getMessage, waitForCompletion } from "./utils" +import { RooCodeEventName, ClineMessage } from "../../../src/exports/roo-code" + +import { sleep, waitFor, waitUntilCompleted } from "./utils" suite("Roo Code Subtasks", () => { - test("Should handle subtask cancellation and resumption correctly", async function () { + test("Should handle subtask cancellation and resumption correctly", async () => { const api = globalThis.api + const messages: ClineMessage[] = [] + api.on(RooCodeEventName.Message, ({ message }) => messages.push(message)) + await api.setConfiguration({ mode: "Code", alwaysAllowModeSwitch: true, @@ -17,11 +22,12 @@ suite("Roo Code Subtasks", () => { const childPrompt = "You are a calculator. Respond only with numbers. What is the square root of 9?" // Start a parent task that will create a subtask. - const parentTaskId = await api.startNewTask( - "You are the parent task. " + + const parentTaskId = await api.startNewTask({ + text: + "You are the parent task. " + `Create a subtask by using the new_task tool with the message '${childPrompt}'.` + "After creating the subtask, wait for it to complete and then respond 'Parent task resumed'.", - ) + }) let spawnedTaskId: string | undefined = undefined @@ -36,36 +42,36 @@ suite("Roo Code Subtasks", () => { // The parent task should not have resumed yet, so we shouldn't see // "Parent task resumed". - assert.ok( - getMessage({ - api, - taskId: parentTaskId, - include: "Parent task resumed", - exclude: "You are the parent task", - }) === undefined, - "Parent task should not have resumed after subtask cancellation", - ) + // assert.ok( + // getMessage({ + // api, + // taskId: parentTaskId, + // include: "Parent task resumed", + // exclude: "You are the parent task", + // }) === undefined, + // "Parent task should not have resumed after subtask cancellation", + // ) // Start a new task with the same message as the subtask. - const anotherTaskId = await api.startNewTask(childPrompt) - await waitForCompletion({ api, taskId: anotherTaskId }) + const anotherTaskId = await api.startNewTask({ text: childPrompt }) + await waitUntilCompleted({ api, taskId: anotherTaskId }) // Wait a bit to ensure any task resumption would have happened. await sleep(2_000) // The parent task should still not have resumed. - assert.ok( - getMessage({ - api, - taskId: parentTaskId, - include: "Parent task resumed", - exclude: "You are the parent task", - }) === undefined, - "Parent task should not have resumed after subtask cancellation", - ) + // assert.ok( + // getMessage({ + // api, + // taskId: parentTaskId, + // include: "Parent task resumed", + // exclude: "You are the parent task", + // }) === undefined, + // "Parent task should not have resumed after subtask cancellation", + // ) // Clean up - cancel all tasks. await api.clearCurrentTask() - await waitForCompletion({ api, taskId: parentTaskId }) + await waitUntilCompleted({ api, taskId: parentTaskId }) }) }) diff --git a/e2e/src/suite/task.test.ts b/e2e/src/suite/task.test.ts index 840654a508..7924838414 100644 --- a/e2e/src/suite/task.test.ts +++ b/e2e/src/suite/task.test.ts @@ -1,10 +1,28 @@ -import { waitForMessage } from "./utils" +import * as assert from "assert" + +import { RooCodeEventName, ClineMessage } from "../../../src/exports/roo-code" + +import { waitUntilCompleted } from "./utils" suite("Roo Code Task", () => { - test("Should handle prompt and response correctly", async function () { + test("Should handle prompt and response correctly", async () => { const api = globalThis.api - await api.setConfiguration({ mode: "Ask", alwaysAllowModeSwitch: true, autoApprovalEnabled: true }) - const taskId = await api.startNewTask("Hello world, what is your name? Respond with 'My name is ...'") - await waitForMessage({ api, taskId, include: "My name is Roo" }) + + const messages: ClineMessage[] = [] + api.on(RooCodeEventName.Message, ({ message }) => messages.push(message)) + + const taskId = await api.startNewTask({ + configuration: { mode: "Ask", alwaysAllowModeSwitch: true, autoApprovalEnabled: true }, + text: "Hello world, what is your name? Respond with 'My name is ...'", + }) + + await waitUntilCompleted({ api, taskId }) + + const completion = messages.find(({ type, say, partial }) => say === "completion_result" && partial === false) + + assert.ok( + completion?.text?.includes("My name is Roo"), + `Completion should include "My name is Roo" - ${completion?.text}`, + ) }) }) diff --git a/e2e/src/suite/utils.ts b/e2e/src/suite/utils.ts index e816315633..70d6d59c09 100644 --- a/e2e/src/suite/utils.ts +++ b/e2e/src/suite/utils.ts @@ -61,39 +61,15 @@ export const waitUntilAborted = async ({ api, taskId, ...options }: WaitUntilAbo await waitFor(() => set.has(taskId), options) } -export const waitForCompletion = async ({ - api, - taskId, - ...options -}: WaitUntilReadyOptions & { - taskId: string -}) => waitFor(() => !!getCompletion({ api, taskId }), options) - -export const getCompletion = ({ api, taskId }: { api: RooCodeAPI; taskId: string }) => - api.getMessages(taskId).find(({ say, partial }) => say === "completion_result" && partial === false) - -type WaitForMessageOptions = WaitUntilReadyOptions & { - taskId: string - include: string - exclude?: string -} - -export const waitForMessage = async ({ api, taskId, include, exclude, ...options }: WaitForMessageOptions) => - waitFor(() => !!getMessage({ api, taskId, include, exclude }), options) - -type GetMessageOptions = { +type WaitUntilCompletedOptions = WaitForOptions & { api: RooCodeAPI taskId: string - include: string - exclude?: string } -export const getMessage = ({ api, taskId, include, exclude }: GetMessageOptions) => - api - .getMessages(taskId) - .find( - ({ type, text }) => - type === "say" && text && text.includes(include) && (!exclude || !text.includes(exclude)), - ) +export const waitUntilCompleted = async ({ api, taskId, ...options }: WaitUntilCompletedOptions) => { + const set = new Set() + api.on("taskCompleted", (taskId) => set.add(taskId)) + await waitFor(() => set.has(taskId), options) +} export const sleep = (ms: number) => new Promise((resolve) => setTimeout(resolve, ms)) diff --git a/locales/ca/README.md b/locales/ca/README.md index ff8684b7a8..1ae4abf0c4 100644 --- a/locales/ca/README.md +++ b/locales/ca/README.md @@ -178,26 +178,26 @@ Ens encanten les contribucions de la comunitat! Comenceu llegint el nostre [CONT Gràcies a tots els nostres col·laboradors que han ajudat a millorar Roo Code! - -| mrubens
mrubens
| saoudrizwan
saoudrizwan
| cte
cte
| samhvw8
samhvw8
| daniel-lxs
daniel-lxs
| a8trejo
a8trejo
| -| :-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------: | :------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------: | :---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------: | :----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------: | :------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------: | :---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------: | -| ColemanRoo
ColemanRoo
| stea9499
stea9499
| joemanley201
joemanley201
| System233
System233
| jquanton
jquanton
| nissa-seru
nissa-seru
| -| NyxJae
NyxJae
| hannesrudolph
hannesrudolph
| MuriloFP
MuriloFP
| punkpeye
punkpeye
| d-oit
d-oit
| monotykamary
monotykamary
| -| vigneshsubbiah16
vigneshsubbiah16
| lloydchang
lloydchang
| cannuri
cannuri
| Szpadel
Szpadel
| psv2522
psv2522
| Premshay
Premshay
| -| Smartsheet-JB-Brown
Smartsheet-JB-Brown
| wkordalski
wkordalski
| qdaxb
qdaxb
| feifei325
feifei325
| lupuletic
lupuletic
| KJ7LNW
KJ7LNW
| -| olweraltuve
olweraltuve
| RaySinner
RaySinner
| pugazhendhi-m
pugazhendhi-m
| pdecat
pdecat
| emshvac
emshvac
| afshawnlotfi
afshawnlotfi
| -| aitoroses
aitoroses
| dtrugman
dtrugman
| diarmidmackenzie
diarmidmackenzie
| sammcj
sammcj
| aheizi
aheizi
| Lunchb0ne
Lunchb0ne
| -| yt3trees
yt3trees
| yongjer
yongjer
| vincentsong
vincentsong
| teddyOOXX
teddyOOXX
| eonghk
eonghk
| arthurauffray
arthurauffray
| -| heyseth
heyseth
| anton-otee
anton-otee
| benzntech
benzntech
| GitlyHallows
GitlyHallows
| jcbdev
jcbdev
| Chenjiayuan195
Chenjiayuan195
| -| mdp
mdp
| napter
napter
| philfung
philfung
| AMHesch
AMHesch
| vladstudio
vladstudio
| Yoshino-Yukitaro
Yoshino-Yukitaro
| -| ashktn
ashktn
| bannzai
bannzai
| dairui1
dairui1
| dqroid
dqroid
| im47cn
im47cn
| jwcraig
jwcraig
| -| kinandan
kinandan
| kohii
kohii
| lightrabbit
lightrabbit
| olup
olup
| moqimoqidea
moqimoqidea
| mosleyit
mosleyit
| -| oprstchn
oprstchn
| philipnext
philipnext
| refactorthis
refactorthis
| samir-nimbly
samir-nimbly
| shaybc
shaybc
| shohei-ihaya
shohei-ihaya
| -| student20880
student20880
| cdlliuy
cdlliuy
| PretzelVector
PretzelVector
| nevermorec
nevermorec
| adamwlarson
adamwlarson
| alarno
alarno
| -| andreastempsch
andreastempsch
| Atlogit
Atlogit
| chadgauth
chadgauth
| dleen
dleen
| dbasclpy
dbasclpy
| linegel
linegel
| -| celestial-vault
celestial-vault
| franekp
franekp
| DeXtroTip
DeXtroTip
| hesara
hesara
| eltociear
eltociear
| Jdo300
Jdo300
| -| libertyteeth
libertyteeth
| mamertofabian
mamertofabian
| | | | | - +|mrubens
mrubens
|saoudrizwan
saoudrizwan
|cte
cte
|samhvw8
samhvw8
|daniel-lxs
daniel-lxs
|a8trejo
a8trejo
| +|:---:|:---:|:---:|:---:|:---:|:---:| +|ColemanRoo
ColemanRoo
|stea9499
stea9499
|joemanley201
joemanley201
|System233
System233
|nissa-seru
nissa-seru
|jquanton
jquanton
| +|hannesrudolph
hannesrudolph
|NyxJae
NyxJae
|MuriloFP
MuriloFP
|punkpeye
punkpeye
|d-oit
d-oit
|monotykamary
monotykamary
| +|cannuri
cannuri
|lloydchang
lloydchang
|vigneshsubbiah16
vigneshsubbiah16
|KJ7LNW
KJ7LNW
|Szpadel
Szpadel
|lupuletic
lupuletic
| +|feifei325
feifei325
|qdaxb
qdaxb
|wkordalski
wkordalski
|Smartsheet-JB-Brown
Smartsheet-JB-Brown
|Premshay
Premshay
|psv2522
psv2522
| +|olweraltuve
olweraltuve
|RaySinner
RaySinner
|aheizi
aheizi
|afshawnlotfi
afshawnlotfi
|diarmidmackenzie
diarmidmackenzie
|emshvac
emshvac
| +|pdecat
pdecat
|pugazhendhi-m
pugazhendhi-m
|Lunchb0ne
Lunchb0ne
|sammcj
sammcj
|dtrugman
dtrugman
|aitoroses
aitoroses
| +|yt3trees
yt3trees
|yongjer
yongjer
|vincentsong
vincentsong
|teddyOOXX
teddyOOXX
|eonghk
eonghk
|arthurauffray
arthurauffray
| +|heyseth
heyseth
|philfung
philfung
|napter
napter
|mdp
mdp
|Chenjiayuan195
Chenjiayuan195
|jcbdev
jcbdev
| +|GitlyHallows
GitlyHallows
|benzntech
benzntech
|anton-otee
anton-otee
|lightrabbit
lightrabbit
|kohii
kohii
|kinandan
kinandan
| +|jwcraig
jwcraig
|im47cn
im47cn
|dqroid
dqroid
|dairui1
dairui1
|bannzai
bannzai
|ashktn
ashktn
| +|Yoshino-Yukitaro
Yoshino-Yukitaro
|vladstudio
vladstudio
|tgfjt
tgfjt
|AMHesch
AMHesch
|olup
olup
|moqimoqidea
moqimoqidea
| +|mosleyit
mosleyit
|oprstchn
oprstchn
|philipnext
philipnext
|refactorthis
refactorthis
|samir-nimbly
samir-nimbly
|shaybc
shaybc
| +|shohei-ihaya
shohei-ihaya
|student20880
student20880
|cdlliuy
cdlliuy
|PretzelVector
PretzelVector
|nevermorec
nevermorec
|adamwlarson
adamwlarson
| +|alarno
alarno
|andreastempsch
andreastempsch
|Atlogit
Atlogit
|chadgauth
chadgauth
|dleen
dleen
|dbasclpy
dbasclpy
| +|linegel
linegel
|celestial-vault
celestial-vault
|franekp
franekp
|DeXtroTip
DeXtroTip
|gtaylor
gtaylor
|hesara
hesara
| +|eltociear
eltociear
|Jdo300
Jdo300
|libertyteeth
libertyteeth
|mamertofabian
mamertofabian
|marvijo-code
marvijo-code
|kvokka
kvokka
| +|nbihan-mediware
nbihan-mediware
|PeterDaveHello
PeterDaveHello
|Sarke
Sarke
|01Rian
01Rian
|samsilveira
samsilveira
|StevenTCramer
StevenTCramer
| +|maekawataiki
maekawataiki
| | | | | | ## Llicència diff --git a/locales/de/README.md b/locales/de/README.md index 7306e31469..277c9a7e83 100644 --- a/locales/de/README.md +++ b/locales/de/README.md @@ -178,26 +178,26 @@ Wir lieben Community-Beiträge! Beginnen Sie mit dem Lesen unserer [CONTRIBUTING Danke an alle unsere Mitwirkenden, die geholfen haben, Roo Code zu verbessern! - -| mrubens
mrubens
| saoudrizwan
saoudrizwan
| cte
cte
| samhvw8
samhvw8
| daniel-lxs
daniel-lxs
| a8trejo
a8trejo
| -| :-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------: | :------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------: | :---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------: | :----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------: | :------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------: | :---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------: | -| ColemanRoo
ColemanRoo
| stea9499
stea9499
| joemanley201
joemanley201
| System233
System233
| jquanton
jquanton
| nissa-seru
nissa-seru
| -| NyxJae
NyxJae
| hannesrudolph
hannesrudolph
| MuriloFP
MuriloFP
| punkpeye
punkpeye
| d-oit
d-oit
| monotykamary
monotykamary
| -| vigneshsubbiah16
vigneshsubbiah16
| lloydchang
lloydchang
| cannuri
cannuri
| Szpadel
Szpadel
| psv2522
psv2522
| Premshay
Premshay
| -| Smartsheet-JB-Brown
Smartsheet-JB-Brown
| wkordalski
wkordalski
| qdaxb
qdaxb
| feifei325
feifei325
| lupuletic
lupuletic
| KJ7LNW
KJ7LNW
| -| olweraltuve
olweraltuve
| RaySinner
RaySinner
| pugazhendhi-m
pugazhendhi-m
| pdecat
pdecat
| emshvac
emshvac
| afshawnlotfi
afshawnlotfi
| -| aitoroses
aitoroses
| dtrugman
dtrugman
| diarmidmackenzie
diarmidmackenzie
| sammcj
sammcj
| aheizi
aheizi
| Lunchb0ne
Lunchb0ne
| -| yt3trees
yt3trees
| yongjer
yongjer
| vincentsong
vincentsong
| teddyOOXX
teddyOOXX
| eonghk
eonghk
| arthurauffray
arthurauffray
| -| heyseth
heyseth
| anton-otee
anton-otee
| benzntech
benzntech
| GitlyHallows
GitlyHallows
| jcbdev
jcbdev
| Chenjiayuan195
Chenjiayuan195
| -| mdp
mdp
| napter
napter
| philfung
philfung
| AMHesch
AMHesch
| vladstudio
vladstudio
| Yoshino-Yukitaro
Yoshino-Yukitaro
| -| ashktn
ashktn
| bannzai
bannzai
| dairui1
dairui1
| dqroid
dqroid
| im47cn
im47cn
| jwcraig
jwcraig
| -| kinandan
kinandan
| kohii
kohii
| lightrabbit
lightrabbit
| olup
olup
| moqimoqidea
moqimoqidea
| mosleyit
mosleyit
| -| oprstchn
oprstchn
| philipnext
philipnext
| refactorthis
refactorthis
| samir-nimbly
samir-nimbly
| shaybc
shaybc
| shohei-ihaya
shohei-ihaya
| -| student20880
student20880
| cdlliuy
cdlliuy
| PretzelVector
PretzelVector
| nevermorec
nevermorec
| adamwlarson
adamwlarson
| alarno
alarno
| -| andreastempsch
andreastempsch
| Atlogit
Atlogit
| chadgauth
chadgauth
| dleen
dleen
| dbasclpy
dbasclpy
| linegel
linegel
| -| celestial-vault
celestial-vault
| franekp
franekp
| DeXtroTip
DeXtroTip
| hesara
hesara
| eltociear
eltociear
| Jdo300
Jdo300
| -| libertyteeth
libertyteeth
| mamertofabian
mamertofabian
| | | | | - +|mrubens
mrubens
|saoudrizwan
saoudrizwan
|cte
cte
|samhvw8
samhvw8
|daniel-lxs
daniel-lxs
|a8trejo
a8trejo
| +|:---:|:---:|:---:|:---:|:---:|:---:| +|ColemanRoo
ColemanRoo
|stea9499
stea9499
|joemanley201
joemanley201
|System233
System233
|nissa-seru
nissa-seru
|jquanton
jquanton
| +|hannesrudolph
hannesrudolph
|NyxJae
NyxJae
|MuriloFP
MuriloFP
|punkpeye
punkpeye
|d-oit
d-oit
|monotykamary
monotykamary
| +|cannuri
cannuri
|lloydchang
lloydchang
|vigneshsubbiah16
vigneshsubbiah16
|KJ7LNW
KJ7LNW
|Szpadel
Szpadel
|lupuletic
lupuletic
| +|feifei325
feifei325
|qdaxb
qdaxb
|wkordalski
wkordalski
|Smartsheet-JB-Brown
Smartsheet-JB-Brown
|Premshay
Premshay
|psv2522
psv2522
| +|olweraltuve
olweraltuve
|RaySinner
RaySinner
|aheizi
aheizi
|afshawnlotfi
afshawnlotfi
|diarmidmackenzie
diarmidmackenzie
|emshvac
emshvac
| +|pdecat
pdecat
|pugazhendhi-m
pugazhendhi-m
|Lunchb0ne
Lunchb0ne
|sammcj
sammcj
|dtrugman
dtrugman
|aitoroses
aitoroses
| +|yt3trees
yt3trees
|yongjer
yongjer
|vincentsong
vincentsong
|teddyOOXX
teddyOOXX
|eonghk
eonghk
|arthurauffray
arthurauffray
| +|heyseth
heyseth
|philfung
philfung
|napter
napter
|mdp
mdp
|Chenjiayuan195
Chenjiayuan195
|jcbdev
jcbdev
| +|GitlyHallows
GitlyHallows
|benzntech
benzntech
|anton-otee
anton-otee
|lightrabbit
lightrabbit
|kohii
kohii
|kinandan
kinandan
| +|jwcraig
jwcraig
|im47cn
im47cn
|dqroid
dqroid
|dairui1
dairui1
|bannzai
bannzai
|ashktn
ashktn
| +|Yoshino-Yukitaro
Yoshino-Yukitaro
|vladstudio
vladstudio
|tgfjt
tgfjt
|AMHesch
AMHesch
|olup
olup
|moqimoqidea
moqimoqidea
| +|mosleyit
mosleyit
|oprstchn
oprstchn
|philipnext
philipnext
|refactorthis
refactorthis
|samir-nimbly
samir-nimbly
|shaybc
shaybc
| +|shohei-ihaya
shohei-ihaya
|student20880
student20880
|cdlliuy
cdlliuy
|PretzelVector
PretzelVector
|nevermorec
nevermorec
|adamwlarson
adamwlarson
| +|alarno
alarno
|andreastempsch
andreastempsch
|Atlogit
Atlogit
|chadgauth
chadgauth
|dleen
dleen
|dbasclpy
dbasclpy
| +|linegel
linegel
|celestial-vault
celestial-vault
|franekp
franekp
|DeXtroTip
DeXtroTip
|gtaylor
gtaylor
|hesara
hesara
| +|eltociear
eltociear
|Jdo300
Jdo300
|libertyteeth
libertyteeth
|mamertofabian
mamertofabian
|marvijo-code
marvijo-code
|kvokka
kvokka
| +|nbihan-mediware
nbihan-mediware
|PeterDaveHello
PeterDaveHello
|Sarke
Sarke
|01Rian
01Rian
|samsilveira
samsilveira
|StevenTCramer
StevenTCramer
| +|maekawataiki
maekawataiki
| | | | | | ## Lizenz diff --git a/locales/es/README.md b/locales/es/README.md index a0731b2707..db368f1221 100644 --- a/locales/es/README.md +++ b/locales/es/README.md @@ -178,26 +178,26 @@ Usamos [changesets](https://github.com/changesets/changesets) para versionar y p ¡Gracias a todos nuestros colaboradores que han ayudado a mejorar Roo Code! - -| mrubens
mrubens
| saoudrizwan
saoudrizwan
| cte
cte
| samhvw8
samhvw8
| daniel-lxs
daniel-lxs
| a8trejo
a8trejo
| -| :-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------: | :------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------: | :---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------: | :----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------: | :------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------: | :---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------: | -| ColemanRoo
ColemanRoo
| stea9499
stea9499
| joemanley201
joemanley201
| System233
System233
| jquanton
jquanton
| nissa-seru
nissa-seru
| -| NyxJae
NyxJae
| hannesrudolph
hannesrudolph
| MuriloFP
MuriloFP
| punkpeye
punkpeye
| d-oit
d-oit
| monotykamary
monotykamary
| -| vigneshsubbiah16
vigneshsubbiah16
| lloydchang
lloydchang
| cannuri
cannuri
| Szpadel
Szpadel
| psv2522
psv2522
| Premshay
Premshay
| -| Smartsheet-JB-Brown
Smartsheet-JB-Brown
| wkordalski
wkordalski
| qdaxb
qdaxb
| feifei325
feifei325
| lupuletic
lupuletic
| KJ7LNW
KJ7LNW
| -| olweraltuve
olweraltuve
| RaySinner
RaySinner
| pugazhendhi-m
pugazhendhi-m
| pdecat
pdecat
| emshvac
emshvac
| afshawnlotfi
afshawnlotfi
| -| aitoroses
aitoroses
| dtrugman
dtrugman
| diarmidmackenzie
diarmidmackenzie
| sammcj
sammcj
| aheizi
aheizi
| Lunchb0ne
Lunchb0ne
| -| yt3trees
yt3trees
| yongjer
yongjer
| vincentsong
vincentsong
| teddyOOXX
teddyOOXX
| eonghk
eonghk
| arthurauffray
arthurauffray
| -| heyseth
heyseth
| anton-otee
anton-otee
| benzntech
benzntech
| GitlyHallows
GitlyHallows
| jcbdev
jcbdev
| Chenjiayuan195
Chenjiayuan195
| -| mdp
mdp
| napter
napter
| philfung
philfung
| AMHesch
AMHesch
| vladstudio
vladstudio
| Yoshino-Yukitaro
Yoshino-Yukitaro
| -| ashktn
ashktn
| bannzai
bannzai
| dairui1
dairui1
| dqroid
dqroid
| im47cn
im47cn
| jwcraig
jwcraig
| -| kinandan
kinandan
| kohii
kohii
| lightrabbit
lightrabbit
| olup
olup
| moqimoqidea
moqimoqidea
| mosleyit
mosleyit
| -| oprstchn
oprstchn
| philipnext
philipnext
| refactorthis
refactorthis
| samir-nimbly
samir-nimbly
| shaybc
shaybc
| shohei-ihaya
shohei-ihaya
| -| student20880
student20880
| cdlliuy
cdlliuy
| PretzelVector
PretzelVector
| nevermorec
nevermorec
| adamwlarson
adamwlarson
| alarno
alarno
| -| andreastempsch
andreastempsch
| Atlogit
Atlogit
| chadgauth
chadgauth
| dleen
dleen
| dbasclpy
dbasclpy
| linegel
linegel
| -| celestial-vault
celestial-vault
| franekp
franekp
| DeXtroTip
DeXtroTip
| hesara
hesara
| eltociear
eltociear
| Jdo300
Jdo300
| -| libertyteeth
libertyteeth
| mamertofabian
mamertofabian
| | | | | - +|mrubens
mrubens
|saoudrizwan
saoudrizwan
|cte
cte
|samhvw8
samhvw8
|daniel-lxs
daniel-lxs
|a8trejo
a8trejo
| +|:---:|:---:|:---:|:---:|:---:|:---:| +|ColemanRoo
ColemanRoo
|stea9499
stea9499
|joemanley201
joemanley201
|System233
System233
|nissa-seru
nissa-seru
|jquanton
jquanton
| +|hannesrudolph
hannesrudolph
|NyxJae
NyxJae
|MuriloFP
MuriloFP
|punkpeye
punkpeye
|d-oit
d-oit
|monotykamary
monotykamary
| +|cannuri
cannuri
|lloydchang
lloydchang
|vigneshsubbiah16
vigneshsubbiah16
|KJ7LNW
KJ7LNW
|Szpadel
Szpadel
|lupuletic
lupuletic
| +|feifei325
feifei325
|qdaxb
qdaxb
|wkordalski
wkordalski
|Smartsheet-JB-Brown
Smartsheet-JB-Brown
|Premshay
Premshay
|psv2522
psv2522
| +|olweraltuve
olweraltuve
|RaySinner
RaySinner
|aheizi
aheizi
|afshawnlotfi
afshawnlotfi
|diarmidmackenzie
diarmidmackenzie
|emshvac
emshvac
| +|pdecat
pdecat
|pugazhendhi-m
pugazhendhi-m
|Lunchb0ne
Lunchb0ne
|sammcj
sammcj
|dtrugman
dtrugman
|aitoroses
aitoroses
| +|yt3trees
yt3trees
|yongjer
yongjer
|vincentsong
vincentsong
|teddyOOXX
teddyOOXX
|eonghk
eonghk
|arthurauffray
arthurauffray
| +|heyseth
heyseth
|philfung
philfung
|napter
napter
|mdp
mdp
|Chenjiayuan195
Chenjiayuan195
|jcbdev
jcbdev
| +|GitlyHallows
GitlyHallows
|benzntech
benzntech
|anton-otee
anton-otee
|lightrabbit
lightrabbit
|kohii
kohii
|kinandan
kinandan
| +|jwcraig
jwcraig
|im47cn
im47cn
|dqroid
dqroid
|dairui1
dairui1
|bannzai
bannzai
|ashktn
ashktn
| +|Yoshino-Yukitaro
Yoshino-Yukitaro
|vladstudio
vladstudio
|tgfjt
tgfjt
|AMHesch
AMHesch
|olup
olup
|moqimoqidea
moqimoqidea
| +|mosleyit
mosleyit
|oprstchn
oprstchn
|philipnext
philipnext
|refactorthis
refactorthis
|samir-nimbly
samir-nimbly
|shaybc
shaybc
| +|shohei-ihaya
shohei-ihaya
|student20880
student20880
|cdlliuy
cdlliuy
|PretzelVector
PretzelVector
|nevermorec
nevermorec
|adamwlarson
adamwlarson
| +|alarno
alarno
|andreastempsch
andreastempsch
|Atlogit
Atlogit
|chadgauth
chadgauth
|dleen
dleen
|dbasclpy
dbasclpy
| +|linegel
linegel
|celestial-vault
celestial-vault
|franekp
franekp
|DeXtroTip
DeXtroTip
|gtaylor
gtaylor
|hesara
hesara
| +|eltociear
eltociear
|Jdo300
Jdo300
|libertyteeth
libertyteeth
|mamertofabian
mamertofabian
|marvijo-code
marvijo-code
|kvokka
kvokka
| +|nbihan-mediware
nbihan-mediware
|PeterDaveHello
PeterDaveHello
|Sarke
Sarke
|01Rian
01Rian
|samsilveira
samsilveira
|StevenTCramer
StevenTCramer
| +|maekawataiki
maekawataiki
| | | | | | ## Licencia diff --git a/locales/fr/README.md b/locales/fr/README.md index c304089003..16421964f0 100644 --- a/locales/fr/README.md +++ b/locales/fr/README.md @@ -178,26 +178,26 @@ Nous adorons les contributions de la communauté ! Commencez par lire notre [CON Merci à tous nos contributeurs qui ont aidé à améliorer Roo Code ! - -| mrubens
mrubens
| saoudrizwan
saoudrizwan
| cte
cte
| samhvw8
samhvw8
| daniel-lxs
daniel-lxs
| a8trejo
a8trejo
| -| :-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------: | :------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------: | :---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------: | :----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------: | :------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------: | :---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------: | -| ColemanRoo
ColemanRoo
| stea9499
stea9499
| joemanley201
joemanley201
| System233
System233
| jquanton
jquanton
| nissa-seru
nissa-seru
| -| NyxJae
NyxJae
| hannesrudolph
hannesrudolph
| MuriloFP
MuriloFP
| punkpeye
punkpeye
| d-oit
d-oit
| monotykamary
monotykamary
| -| vigneshsubbiah16
vigneshsubbiah16
| lloydchang
lloydchang
| cannuri
cannuri
| Szpadel
Szpadel
| psv2522
psv2522
| Premshay
Premshay
| -| Smartsheet-JB-Brown
Smartsheet-JB-Brown
| wkordalski
wkordalski
| qdaxb
qdaxb
| feifei325
feifei325
| lupuletic
lupuletic
| KJ7LNW
KJ7LNW
| -| olweraltuve
olweraltuve
| RaySinner
RaySinner
| pugazhendhi-m
pugazhendhi-m
| pdecat
pdecat
| emshvac
emshvac
| afshawnlotfi
afshawnlotfi
| -| aitoroses
aitoroses
| dtrugman
dtrugman
| diarmidmackenzie
diarmidmackenzie
| sammcj
sammcj
| aheizi
aheizi
| Lunchb0ne
Lunchb0ne
| -| yt3trees
yt3trees
| yongjer
yongjer
| vincentsong
vincentsong
| teddyOOXX
teddyOOXX
| eonghk
eonghk
| arthurauffray
arthurauffray
| -| heyseth
heyseth
| anton-otee
anton-otee
| benzntech
benzntech
| GitlyHallows
GitlyHallows
| jcbdev
jcbdev
| Chenjiayuan195
Chenjiayuan195
| -| mdp
mdp
| napter
napter
| philfung
philfung
| AMHesch
AMHesch
| vladstudio
vladstudio
| Yoshino-Yukitaro
Yoshino-Yukitaro
| -| ashktn
ashktn
| bannzai
bannzai
| dairui1
dairui1
| dqroid
dqroid
| im47cn
im47cn
| jwcraig
jwcraig
| -| kinandan
kinandan
| kohii
kohii
| lightrabbit
lightrabbit
| olup
olup
| moqimoqidea
moqimoqidea
| mosleyit
mosleyit
| -| oprstchn
oprstchn
| philipnext
philipnext
| refactorthis
refactorthis
| samir-nimbly
samir-nimbly
| shaybc
shaybc
| shohei-ihaya
shohei-ihaya
| -| student20880
student20880
| cdlliuy
cdlliuy
| PretzelVector
PretzelVector
| nevermorec
nevermorec
| adamwlarson
adamwlarson
| alarno
alarno
| -| andreastempsch
andreastempsch
| Atlogit
Atlogit
| chadgauth
chadgauth
| dleen
dleen
| dbasclpy
dbasclpy
| linegel
linegel
| -| celestial-vault
celestial-vault
| franekp
franekp
| DeXtroTip
DeXtroTip
| hesara
hesara
| eltociear
eltociear
| Jdo300
Jdo300
| -| libertyteeth
libertyteeth
| mamertofabian
mamertofabian
| | | | | - +|mrubens
mrubens
|saoudrizwan
saoudrizwan
|cte
cte
|samhvw8
samhvw8
|daniel-lxs
daniel-lxs
|a8trejo
a8trejo
| +|:---:|:---:|:---:|:---:|:---:|:---:| +|ColemanRoo
ColemanRoo
|stea9499
stea9499
|joemanley201
joemanley201
|System233
System233
|nissa-seru
nissa-seru
|jquanton
jquanton
| +|hannesrudolph
hannesrudolph
|NyxJae
NyxJae
|MuriloFP
MuriloFP
|punkpeye
punkpeye
|d-oit
d-oit
|monotykamary
monotykamary
| +|cannuri
cannuri
|lloydchang
lloydchang
|vigneshsubbiah16
vigneshsubbiah16
|KJ7LNW
KJ7LNW
|Szpadel
Szpadel
|lupuletic
lupuletic
| +|feifei325
feifei325
|qdaxb
qdaxb
|wkordalski
wkordalski
|Smartsheet-JB-Brown
Smartsheet-JB-Brown
|Premshay
Premshay
|psv2522
psv2522
| +|olweraltuve
olweraltuve
|RaySinner
RaySinner
|aheizi
aheizi
|afshawnlotfi
afshawnlotfi
|diarmidmackenzie
diarmidmackenzie
|emshvac
emshvac
| +|pdecat
pdecat
|pugazhendhi-m
pugazhendhi-m
|Lunchb0ne
Lunchb0ne
|sammcj
sammcj
|dtrugman
dtrugman
|aitoroses
aitoroses
| +|yt3trees
yt3trees
|yongjer
yongjer
|vincentsong
vincentsong
|teddyOOXX
teddyOOXX
|eonghk
eonghk
|arthurauffray
arthurauffray
| +|heyseth
heyseth
|philfung
philfung
|napter
napter
|mdp
mdp
|Chenjiayuan195
Chenjiayuan195
|jcbdev
jcbdev
| +|GitlyHallows
GitlyHallows
|benzntech
benzntech
|anton-otee
anton-otee
|lightrabbit
lightrabbit
|kohii
kohii
|kinandan
kinandan
| +|jwcraig
jwcraig
|im47cn
im47cn
|dqroid
dqroid
|dairui1
dairui1
|bannzai
bannzai
|ashktn
ashktn
| +|Yoshino-Yukitaro
Yoshino-Yukitaro
|vladstudio
vladstudio
|tgfjt
tgfjt
|AMHesch
AMHesch
|olup
olup
|moqimoqidea
moqimoqidea
| +|mosleyit
mosleyit
|oprstchn
oprstchn
|philipnext
philipnext
|refactorthis
refactorthis
|samir-nimbly
samir-nimbly
|shaybc
shaybc
| +|shohei-ihaya
shohei-ihaya
|student20880
student20880
|cdlliuy
cdlliuy
|PretzelVector
PretzelVector
|nevermorec
nevermorec
|adamwlarson
adamwlarson
| +|alarno
alarno
|andreastempsch
andreastempsch
|Atlogit
Atlogit
|chadgauth
chadgauth
|dleen
dleen
|dbasclpy
dbasclpy
| +|linegel
linegel
|celestial-vault
celestial-vault
|franekp
franekp
|DeXtroTip
DeXtroTip
|gtaylor
gtaylor
|hesara
hesara
| +|eltociear
eltociear
|Jdo300
Jdo300
|libertyteeth
libertyteeth
|mamertofabian
mamertofabian
|marvijo-code
marvijo-code
|kvokka
kvokka
| +|nbihan-mediware
nbihan-mediware
|PeterDaveHello
PeterDaveHello
|Sarke
Sarke
|01Rian
01Rian
|samsilveira
samsilveira
|StevenTCramer
StevenTCramer
| +|maekawataiki
maekawataiki
| | | | | | ## Licence diff --git a/locales/hi/README.md b/locales/hi/README.md index 6f727c424a..fc2fb0b2c9 100644 --- a/locales/hi/README.md +++ b/locales/hi/README.md @@ -178,26 +178,26 @@ code --install-extension bin/roo-cline-.vsix Roo Code को बेहतर बनाने में मदद करने वाले हमारे सभी योगदानकर्ताओं को धन्यवाद! - -| mrubens
mrubens
| saoudrizwan
saoudrizwan
| cte
cte
| samhvw8
samhvw8
| daniel-lxs
daniel-lxs
| a8trejo
a8trejo
| -| :-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------: | :------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------: | :---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------: | :----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------: | :------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------: | :---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------: | -| ColemanRoo
ColemanRoo
| stea9499
stea9499
| joemanley201
joemanley201
| System233
System233
| jquanton
jquanton
| nissa-seru
nissa-seru
| -| NyxJae
NyxJae
| hannesrudolph
hannesrudolph
| MuriloFP
MuriloFP
| punkpeye
punkpeye
| d-oit
d-oit
| monotykamary
monotykamary
| -| vigneshsubbiah16
vigneshsubbiah16
| lloydchang
lloydchang
| cannuri
cannuri
| Szpadel
Szpadel
| psv2522
psv2522
| Premshay
Premshay
| -| Smartsheet-JB-Brown
Smartsheet-JB-Brown
| wkordalski
wkordalski
| qdaxb
qdaxb
| feifei325
feifei325
| lupuletic
lupuletic
| KJ7LNW
KJ7LNW
| -| olweraltuve
olweraltuve
| RaySinner
RaySinner
| pugazhendhi-m
pugazhendhi-m
| pdecat
pdecat
| emshvac
emshvac
| afshawnlotfi
afshawnlotfi
| -| aitoroses
aitoroses
| dtrugman
dtrugman
| diarmidmackenzie
diarmidmackenzie
| sammcj
sammcj
| aheizi
aheizi
| Lunchb0ne
Lunchb0ne
| -| yt3trees
yt3trees
| yongjer
yongjer
| vincentsong
vincentsong
| teddyOOXX
teddyOOXX
| eonghk
eonghk
| arthurauffray
arthurauffray
| -| heyseth
heyseth
| anton-otee
anton-otee
| benzntech
benzntech
| GitlyHallows
GitlyHallows
| jcbdev
jcbdev
| Chenjiayuan195
Chenjiayuan195
| -| mdp
mdp
| napter
napter
| philfung
philfung
| AMHesch
AMHesch
| vladstudio
vladstudio
| Yoshino-Yukitaro
Yoshino-Yukitaro
| -| ashktn
ashktn
| bannzai
bannzai
| dairui1
dairui1
| dqroid
dqroid
| im47cn
im47cn
| jwcraig
jwcraig
| -| kinandan
kinandan
| kohii
kohii
| lightrabbit
lightrabbit
| olup
olup
| moqimoqidea
moqimoqidea
| mosleyit
mosleyit
| -| oprstchn
oprstchn
| philipnext
philipnext
| refactorthis
refactorthis
| samir-nimbly
samir-nimbly
| shaybc
shaybc
| shohei-ihaya
shohei-ihaya
| -| student20880
student20880
| cdlliuy
cdlliuy
| PretzelVector
PretzelVector
| nevermorec
nevermorec
| adamwlarson
adamwlarson
| alarno
alarno
| -| andreastempsch
andreastempsch
| Atlogit
Atlogit
| chadgauth
chadgauth
| dleen
dleen
| dbasclpy
dbasclpy
| linegel
linegel
| -| celestial-vault
celestial-vault
| franekp
franekp
| DeXtroTip
DeXtroTip
| hesara
hesara
| eltociear
eltociear
| Jdo300
Jdo300
| -| libertyteeth
libertyteeth
| mamertofabian
mamertofabian
| | | | | - +|mrubens
mrubens
|saoudrizwan
saoudrizwan
|cte
cte
|samhvw8
samhvw8
|daniel-lxs
daniel-lxs
|a8trejo
a8trejo
| +|:---:|:---:|:---:|:---:|:---:|:---:| +|ColemanRoo
ColemanRoo
|stea9499
stea9499
|joemanley201
joemanley201
|System233
System233
|nissa-seru
nissa-seru
|jquanton
jquanton
| +|hannesrudolph
hannesrudolph
|NyxJae
NyxJae
|MuriloFP
MuriloFP
|punkpeye
punkpeye
|d-oit
d-oit
|monotykamary
monotykamary
| +|cannuri
cannuri
|lloydchang
lloydchang
|vigneshsubbiah16
vigneshsubbiah16
|KJ7LNW
KJ7LNW
|Szpadel
Szpadel
|lupuletic
lupuletic
| +|feifei325
feifei325
|qdaxb
qdaxb
|wkordalski
wkordalski
|Smartsheet-JB-Brown
Smartsheet-JB-Brown
|Premshay
Premshay
|psv2522
psv2522
| +|olweraltuve
olweraltuve
|RaySinner
RaySinner
|aheizi
aheizi
|afshawnlotfi
afshawnlotfi
|diarmidmackenzie
diarmidmackenzie
|emshvac
emshvac
| +|pdecat
pdecat
|pugazhendhi-m
pugazhendhi-m
|Lunchb0ne
Lunchb0ne
|sammcj
sammcj
|dtrugman
dtrugman
|aitoroses
aitoroses
| +|yt3trees
yt3trees
|yongjer
yongjer
|vincentsong
vincentsong
|teddyOOXX
teddyOOXX
|eonghk
eonghk
|arthurauffray
arthurauffray
| +|heyseth
heyseth
|philfung
philfung
|napter
napter
|mdp
mdp
|Chenjiayuan195
Chenjiayuan195
|jcbdev
jcbdev
| +|GitlyHallows
GitlyHallows
|benzntech
benzntech
|anton-otee
anton-otee
|lightrabbit
lightrabbit
|kohii
kohii
|kinandan
kinandan
| +|jwcraig
jwcraig
|im47cn
im47cn
|dqroid
dqroid
|dairui1
dairui1
|bannzai
bannzai
|ashktn
ashktn
| +|Yoshino-Yukitaro
Yoshino-Yukitaro
|vladstudio
vladstudio
|tgfjt
tgfjt
|AMHesch
AMHesch
|olup
olup
|moqimoqidea
moqimoqidea
| +|mosleyit
mosleyit
|oprstchn
oprstchn
|philipnext
philipnext
|refactorthis
refactorthis
|samir-nimbly
samir-nimbly
|shaybc
shaybc
| +|shohei-ihaya
shohei-ihaya
|student20880
student20880
|cdlliuy
cdlliuy
|PretzelVector
PretzelVector
|nevermorec
nevermorec
|adamwlarson
adamwlarson
| +|alarno
alarno
|andreastempsch
andreastempsch
|Atlogit
Atlogit
|chadgauth
chadgauth
|dleen
dleen
|dbasclpy
dbasclpy
| +|linegel
linegel
|celestial-vault
celestial-vault
|franekp
franekp
|DeXtroTip
DeXtroTip
|gtaylor
gtaylor
|hesara
hesara
| +|eltociear
eltociear
|Jdo300
Jdo300
|libertyteeth
libertyteeth
|mamertofabian
mamertofabian
|marvijo-code
marvijo-code
|kvokka
kvokka
| +|nbihan-mediware
nbihan-mediware
|PeterDaveHello
PeterDaveHello
|Sarke
Sarke
|01Rian
01Rian
|samsilveira
samsilveira
|StevenTCramer
StevenTCramer
| +|maekawataiki
maekawataiki
| | | | | | ## लाइसेंस diff --git a/locales/it/README.md b/locales/it/README.md index 9b2ba3c3c2..6ec0af37b6 100644 --- a/locales/it/README.md +++ b/locales/it/README.md @@ -178,26 +178,26 @@ Amiamo i contributi della community! Inizia leggendo il nostro [CONTRIBUTING.md] Grazie a tutti i nostri contributori che hanno aiutato a migliorare Roo Code! - -| mrubens
mrubens
| saoudrizwan
saoudrizwan
| cte
cte
| samhvw8
samhvw8
| daniel-lxs
daniel-lxs
| a8trejo
a8trejo
| -| :-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------: | :------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------: | :---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------: | :----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------: | :------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------: | :---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------: | -| ColemanRoo
ColemanRoo
| stea9499
stea9499
| joemanley201
joemanley201
| System233
System233
| jquanton
jquanton
| nissa-seru
nissa-seru
| -| NyxJae
NyxJae
| hannesrudolph
hannesrudolph
| MuriloFP
MuriloFP
| punkpeye
punkpeye
| d-oit
d-oit
| monotykamary
monotykamary
| -| vigneshsubbiah16
vigneshsubbiah16
| lloydchang
lloydchang
| cannuri
cannuri
| Szpadel
Szpadel
| psv2522
psv2522
| Premshay
Premshay
| -| Smartsheet-JB-Brown
Smartsheet-JB-Brown
| wkordalski
wkordalski
| qdaxb
qdaxb
| feifei325
feifei325
| lupuletic
lupuletic
| KJ7LNW
KJ7LNW
| -| olweraltuve
olweraltuve
| RaySinner
RaySinner
| pugazhendhi-m
pugazhendhi-m
| pdecat
pdecat
| emshvac
emshvac
| afshawnlotfi
afshawnlotfi
| -| aitoroses
aitoroses
| dtrugman
dtrugman
| diarmidmackenzie
diarmidmackenzie
| sammcj
sammcj
| aheizi
aheizi
| Lunchb0ne
Lunchb0ne
| -| yt3trees
yt3trees
| yongjer
yongjer
| vincentsong
vincentsong
| teddyOOXX
teddyOOXX
| eonghk
eonghk
| arthurauffray
arthurauffray
| -| heyseth
heyseth
| anton-otee
anton-otee
| benzntech
benzntech
| GitlyHallows
GitlyHallows
| jcbdev
jcbdev
| Chenjiayuan195
Chenjiayuan195
| -| mdp
mdp
| napter
napter
| philfung
philfung
| AMHesch
AMHesch
| vladstudio
vladstudio
| Yoshino-Yukitaro
Yoshino-Yukitaro
| -| ashktn
ashktn
| bannzai
bannzai
| dairui1
dairui1
| dqroid
dqroid
| im47cn
im47cn
| jwcraig
jwcraig
| -| kinandan
kinandan
| kohii
kohii
| lightrabbit
lightrabbit
| olup
olup
| moqimoqidea
moqimoqidea
| mosleyit
mosleyit
| -| oprstchn
oprstchn
| philipnext
philipnext
| refactorthis
refactorthis
| samir-nimbly
samir-nimbly
| shaybc
shaybc
| shohei-ihaya
shohei-ihaya
| -| student20880
student20880
| cdlliuy
cdlliuy
| PretzelVector
PretzelVector
| nevermorec
nevermorec
| adamwlarson
adamwlarson
| alarno
alarno
| -| andreastempsch
andreastempsch
| Atlogit
Atlogit
| chadgauth
chadgauth
| dleen
dleen
| dbasclpy
dbasclpy
| linegel
linegel
| -| celestial-vault
celestial-vault
| franekp
franekp
| DeXtroTip
DeXtroTip
| hesara
hesara
| eltociear
eltociear
| Jdo300
Jdo300
| -| libertyteeth
libertyteeth
| mamertofabian
mamertofabian
| | | | | - +|mrubens
mrubens
|saoudrizwan
saoudrizwan
|cte
cte
|samhvw8
samhvw8
|daniel-lxs
daniel-lxs
|a8trejo
a8trejo
| +|:---:|:---:|:---:|:---:|:---:|:---:| +|ColemanRoo
ColemanRoo
|stea9499
stea9499
|joemanley201
joemanley201
|System233
System233
|nissa-seru
nissa-seru
|jquanton
jquanton
| +|hannesrudolph
hannesrudolph
|NyxJae
NyxJae
|MuriloFP
MuriloFP
|punkpeye
punkpeye
|d-oit
d-oit
|monotykamary
monotykamary
| +|cannuri
cannuri
|lloydchang
lloydchang
|vigneshsubbiah16
vigneshsubbiah16
|KJ7LNW
KJ7LNW
|Szpadel
Szpadel
|lupuletic
lupuletic
| +|feifei325
feifei325
|qdaxb
qdaxb
|wkordalski
wkordalski
|Smartsheet-JB-Brown
Smartsheet-JB-Brown
|Premshay
Premshay
|psv2522
psv2522
| +|olweraltuve
olweraltuve
|RaySinner
RaySinner
|aheizi
aheizi
|afshawnlotfi
afshawnlotfi
|diarmidmackenzie
diarmidmackenzie
|emshvac
emshvac
| +|pdecat
pdecat
|pugazhendhi-m
pugazhendhi-m
|Lunchb0ne
Lunchb0ne
|sammcj
sammcj
|dtrugman
dtrugman
|aitoroses
aitoroses
| +|yt3trees
yt3trees
|yongjer
yongjer
|vincentsong
vincentsong
|teddyOOXX
teddyOOXX
|eonghk
eonghk
|arthurauffray
arthurauffray
| +|heyseth
heyseth
|philfung
philfung
|napter
napter
|mdp
mdp
|Chenjiayuan195
Chenjiayuan195
|jcbdev
jcbdev
| +|GitlyHallows
GitlyHallows
|benzntech
benzntech
|anton-otee
anton-otee
|lightrabbit
lightrabbit
|kohii
kohii
|kinandan
kinandan
| +|jwcraig
jwcraig
|im47cn
im47cn
|dqroid
dqroid
|dairui1
dairui1
|bannzai
bannzai
|ashktn
ashktn
| +|Yoshino-Yukitaro
Yoshino-Yukitaro
|vladstudio
vladstudio
|tgfjt
tgfjt
|AMHesch
AMHesch
|olup
olup
|moqimoqidea
moqimoqidea
| +|mosleyit
mosleyit
|oprstchn
oprstchn
|philipnext
philipnext
|refactorthis
refactorthis
|samir-nimbly
samir-nimbly
|shaybc
shaybc
| +|shohei-ihaya
shohei-ihaya
|student20880
student20880
|cdlliuy
cdlliuy
|PretzelVector
PretzelVector
|nevermorec
nevermorec
|adamwlarson
adamwlarson
| +|alarno
alarno
|andreastempsch
andreastempsch
|Atlogit
Atlogit
|chadgauth
chadgauth
|dleen
dleen
|dbasclpy
dbasclpy
| +|linegel
linegel
|celestial-vault
celestial-vault
|franekp
franekp
|DeXtroTip
DeXtroTip
|gtaylor
gtaylor
|hesara
hesara
| +|eltociear
eltociear
|Jdo300
Jdo300
|libertyteeth
libertyteeth
|mamertofabian
mamertofabian
|marvijo-code
marvijo-code
|kvokka
kvokka
| +|nbihan-mediware
nbihan-mediware
|PeterDaveHello
PeterDaveHello
|Sarke
Sarke
|01Rian
01Rian
|samsilveira
samsilveira
|StevenTCramer
StevenTCramer
| +|maekawataiki
maekawataiki
| | | | | | ## Licenza diff --git a/locales/ja/README.md b/locales/ja/README.md index 639e3737db..e8c24ceb9c 100644 --- a/locales/ja/README.md +++ b/locales/ja/README.md @@ -178,26 +178,26 @@ code --install-extension bin/roo-cline-.vsix Roo Codeの改善に貢献してくれたすべての貢献者に感謝します! - -| mrubens
mrubens
| saoudrizwan
saoudrizwan
| cte
cte
| samhvw8
samhvw8
| daniel-lxs
daniel-lxs
| a8trejo
a8trejo
| -| :-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------: | :------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------: | :---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------: | :----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------: | :------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------: | :---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------: | -| ColemanRoo
ColemanRoo
| stea9499
stea9499
| joemanley201
joemanley201
| System233
System233
| jquanton
jquanton
| nissa-seru
nissa-seru
| -| NyxJae
NyxJae
| hannesrudolph
hannesrudolph
| MuriloFP
MuriloFP
| punkpeye
punkpeye
| d-oit
d-oit
| monotykamary
monotykamary
| -| vigneshsubbiah16
vigneshsubbiah16
| lloydchang
lloydchang
| cannuri
cannuri
| Szpadel
Szpadel
| psv2522
psv2522
| Premshay
Premshay
| -| Smartsheet-JB-Brown
Smartsheet-JB-Brown
| wkordalski
wkordalski
| qdaxb
qdaxb
| feifei325
feifei325
| lupuletic
lupuletic
| KJ7LNW
KJ7LNW
| -| olweraltuve
olweraltuve
| RaySinner
RaySinner
| pugazhendhi-m
pugazhendhi-m
| pdecat
pdecat
| emshvac
emshvac
| afshawnlotfi
afshawnlotfi
| -| aitoroses
aitoroses
| dtrugman
dtrugman
| diarmidmackenzie
diarmidmackenzie
| sammcj
sammcj
| aheizi
aheizi
| Lunchb0ne
Lunchb0ne
| -| yt3trees
yt3trees
| yongjer
yongjer
| vincentsong
vincentsong
| teddyOOXX
teddyOOXX
| eonghk
eonghk
| arthurauffray
arthurauffray
| -| heyseth
heyseth
| anton-otee
anton-otee
| benzntech
benzntech
| GitlyHallows
GitlyHallows
| jcbdev
jcbdev
| Chenjiayuan195
Chenjiayuan195
| -| mdp
mdp
| napter
napter
| philfung
philfung
| AMHesch
AMHesch
| vladstudio
vladstudio
| Yoshino-Yukitaro
Yoshino-Yukitaro
| -| ashktn
ashktn
| bannzai
bannzai
| dairui1
dairui1
| dqroid
dqroid
| im47cn
im47cn
| jwcraig
jwcraig
| -| kinandan
kinandan
| kohii
kohii
| lightrabbit
lightrabbit
| olup
olup
| moqimoqidea
moqimoqidea
| mosleyit
mosleyit
| -| oprstchn
oprstchn
| philipnext
philipnext
| refactorthis
refactorthis
| samir-nimbly
samir-nimbly
| shaybc
shaybc
| shohei-ihaya
shohei-ihaya
| -| student20880
student20880
| cdlliuy
cdlliuy
| PretzelVector
PretzelVector
| nevermorec
nevermorec
| adamwlarson
adamwlarson
| alarno
alarno
| -| andreastempsch
andreastempsch
| Atlogit
Atlogit
| chadgauth
chadgauth
| dleen
dleen
| dbasclpy
dbasclpy
| linegel
linegel
| -| celestial-vault
celestial-vault
| franekp
franekp
| DeXtroTip
DeXtroTip
| hesara
hesara
| eltociear
eltociear
| Jdo300
Jdo300
| -| libertyteeth
libertyteeth
| mamertofabian
mamertofabian
| | | | | - +|mrubens
mrubens
|saoudrizwan
saoudrizwan
|cte
cte
|samhvw8
samhvw8
|daniel-lxs
daniel-lxs
|a8trejo
a8trejo
| +|:---:|:---:|:---:|:---:|:---:|:---:| +|ColemanRoo
ColemanRoo
|stea9499
stea9499
|joemanley201
joemanley201
|System233
System233
|nissa-seru
nissa-seru
|jquanton
jquanton
| +|hannesrudolph
hannesrudolph
|NyxJae
NyxJae
|MuriloFP
MuriloFP
|punkpeye
punkpeye
|d-oit
d-oit
|monotykamary
monotykamary
| +|cannuri
cannuri
|lloydchang
lloydchang
|vigneshsubbiah16
vigneshsubbiah16
|KJ7LNW
KJ7LNW
|Szpadel
Szpadel
|lupuletic
lupuletic
| +|feifei325
feifei325
|qdaxb
qdaxb
|wkordalski
wkordalski
|Smartsheet-JB-Brown
Smartsheet-JB-Brown
|Premshay
Premshay
|psv2522
psv2522
| +|olweraltuve
olweraltuve
|RaySinner
RaySinner
|aheizi
aheizi
|afshawnlotfi
afshawnlotfi
|diarmidmackenzie
diarmidmackenzie
|emshvac
emshvac
| +|pdecat
pdecat
|pugazhendhi-m
pugazhendhi-m
|Lunchb0ne
Lunchb0ne
|sammcj
sammcj
|dtrugman
dtrugman
|aitoroses
aitoroses
| +|yt3trees
yt3trees
|yongjer
yongjer
|vincentsong
vincentsong
|teddyOOXX
teddyOOXX
|eonghk
eonghk
|arthurauffray
arthurauffray
| +|heyseth
heyseth
|philfung
philfung
|napter
napter
|mdp
mdp
|Chenjiayuan195
Chenjiayuan195
|jcbdev
jcbdev
| +|GitlyHallows
GitlyHallows
|benzntech
benzntech
|anton-otee
anton-otee
|lightrabbit
lightrabbit
|kohii
kohii
|kinandan
kinandan
| +|jwcraig
jwcraig
|im47cn
im47cn
|dqroid
dqroid
|dairui1
dairui1
|bannzai
bannzai
|ashktn
ashktn
| +|Yoshino-Yukitaro
Yoshino-Yukitaro
|vladstudio
vladstudio
|tgfjt
tgfjt
|AMHesch
AMHesch
|olup
olup
|moqimoqidea
moqimoqidea
| +|mosleyit
mosleyit
|oprstchn
oprstchn
|philipnext
philipnext
|refactorthis
refactorthis
|samir-nimbly
samir-nimbly
|shaybc
shaybc
| +|shohei-ihaya
shohei-ihaya
|student20880
student20880
|cdlliuy
cdlliuy
|PretzelVector
PretzelVector
|nevermorec
nevermorec
|adamwlarson
adamwlarson
| +|alarno
alarno
|andreastempsch
andreastempsch
|Atlogit
Atlogit
|chadgauth
chadgauth
|dleen
dleen
|dbasclpy
dbasclpy
| +|linegel
linegel
|celestial-vault
celestial-vault
|franekp
franekp
|DeXtroTip
DeXtroTip
|gtaylor
gtaylor
|hesara
hesara
| +|eltociear
eltociear
|Jdo300
Jdo300
|libertyteeth
libertyteeth
|mamertofabian
mamertofabian
|marvijo-code
marvijo-code
|kvokka
kvokka
| +|nbihan-mediware
nbihan-mediware
|PeterDaveHello
PeterDaveHello
|Sarke
Sarke
|01Rian
01Rian
|samsilveira
samsilveira
|StevenTCramer
StevenTCramer
| +|maekawataiki
maekawataiki
| | | | | | ## ライセンス diff --git a/locales/ko/README.md b/locales/ko/README.md index b48962977c..19c6c57fe5 100644 --- a/locales/ko/README.md +++ b/locales/ko/README.md @@ -178,26 +178,26 @@ code --install-extension bin/roo-cline-.vsix Roo Code를 더 좋게 만드는 데 도움을 준 모든 기여자에게 감사드립니다! - -| mrubens
mrubens
| saoudrizwan
saoudrizwan
| cte
cte
| samhvw8
samhvw8
| daniel-lxs
daniel-lxs
| a8trejo
a8trejo
| -| :-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------: | :------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------: | :---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------: | :----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------: | :------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------: | :---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------: | -| ColemanRoo
ColemanRoo
| stea9499
stea9499
| joemanley201
joemanley201
| System233
System233
| jquanton
jquanton
| nissa-seru
nissa-seru
| -| NyxJae
NyxJae
| hannesrudolph
hannesrudolph
| MuriloFP
MuriloFP
| punkpeye
punkpeye
| d-oit
d-oit
| monotykamary
monotykamary
| -| vigneshsubbiah16
vigneshsubbiah16
| lloydchang
lloydchang
| cannuri
cannuri
| Szpadel
Szpadel
| psv2522
psv2522
| Premshay
Premshay
| -| Smartsheet-JB-Brown
Smartsheet-JB-Brown
| wkordalski
wkordalski
| qdaxb
qdaxb
| feifei325
feifei325
| lupuletic
lupuletic
| KJ7LNW
KJ7LNW
| -| olweraltuve
olweraltuve
| RaySinner
RaySinner
| pugazhendhi-m
pugazhendhi-m
| pdecat
pdecat
| emshvac
emshvac
| afshawnlotfi
afshawnlotfi
| -| aitoroses
aitoroses
| dtrugman
dtrugman
| diarmidmackenzie
diarmidmackenzie
| sammcj
sammcj
| aheizi
aheizi
| Lunchb0ne
Lunchb0ne
| -| yt3trees
yt3trees
| yongjer
yongjer
| vincentsong
vincentsong
| teddyOOXX
teddyOOXX
| eonghk
eonghk
| arthurauffray
arthurauffray
| -| heyseth
heyseth
| anton-otee
anton-otee
| benzntech
benzntech
| GitlyHallows
GitlyHallows
| jcbdev
jcbdev
| Chenjiayuan195
Chenjiayuan195
| -| mdp
mdp
| napter
napter
| philfung
philfung
| AMHesch
AMHesch
| vladstudio
vladstudio
| Yoshino-Yukitaro
Yoshino-Yukitaro
| -| ashktn
ashktn
| bannzai
bannzai
| dairui1
dairui1
| dqroid
dqroid
| im47cn
im47cn
| jwcraig
jwcraig
| -| kinandan
kinandan
| kohii
kohii
| lightrabbit
lightrabbit
| olup
olup
| moqimoqidea
moqimoqidea
| mosleyit
mosleyit
| -| oprstchn
oprstchn
| philipnext
philipnext
| refactorthis
refactorthis
| samir-nimbly
samir-nimbly
| shaybc
shaybc
| shohei-ihaya
shohei-ihaya
| -| student20880
student20880
| cdlliuy
cdlliuy
| PretzelVector
PretzelVector
| nevermorec
nevermorec
| adamwlarson
adamwlarson
| alarno
alarno
| -| andreastempsch
andreastempsch
| Atlogit
Atlogit
| chadgauth
chadgauth
| dleen
dleen
| dbasclpy
dbasclpy
| linegel
linegel
| -| celestial-vault
celestial-vault
| franekp
franekp
| DeXtroTip
DeXtroTip
| hesara
hesara
| eltociear
eltociear
| Jdo300
Jdo300
| -| libertyteeth
libertyteeth
| mamertofabian
mamertofabian
| | | | | - +|mrubens
mrubens
|saoudrizwan
saoudrizwan
|cte
cte
|samhvw8
samhvw8
|daniel-lxs
daniel-lxs
|a8trejo
a8trejo
| +|:---:|:---:|:---:|:---:|:---:|:---:| +|ColemanRoo
ColemanRoo
|stea9499
stea9499
|joemanley201
joemanley201
|System233
System233
|nissa-seru
nissa-seru
|jquanton
jquanton
| +|hannesrudolph
hannesrudolph
|NyxJae
NyxJae
|MuriloFP
MuriloFP
|punkpeye
punkpeye
|d-oit
d-oit
|monotykamary
monotykamary
| +|cannuri
cannuri
|lloydchang
lloydchang
|vigneshsubbiah16
vigneshsubbiah16
|KJ7LNW
KJ7LNW
|Szpadel
Szpadel
|lupuletic
lupuletic
| +|feifei325
feifei325
|qdaxb
qdaxb
|wkordalski
wkordalski
|Smartsheet-JB-Brown
Smartsheet-JB-Brown
|Premshay
Premshay
|psv2522
psv2522
| +|olweraltuve
olweraltuve
|RaySinner
RaySinner
|aheizi
aheizi
|afshawnlotfi
afshawnlotfi
|diarmidmackenzie
diarmidmackenzie
|emshvac
emshvac
| +|pdecat
pdecat
|pugazhendhi-m
pugazhendhi-m
|Lunchb0ne
Lunchb0ne
|sammcj
sammcj
|dtrugman
dtrugman
|aitoroses
aitoroses
| +|yt3trees
yt3trees
|yongjer
yongjer
|vincentsong
vincentsong
|teddyOOXX
teddyOOXX
|eonghk
eonghk
|arthurauffray
arthurauffray
| +|heyseth
heyseth
|philfung
philfung
|napter
napter
|mdp
mdp
|Chenjiayuan195
Chenjiayuan195
|jcbdev
jcbdev
| +|GitlyHallows
GitlyHallows
|benzntech
benzntech
|anton-otee
anton-otee
|lightrabbit
lightrabbit
|kohii
kohii
|kinandan
kinandan
| +|jwcraig
jwcraig
|im47cn
im47cn
|dqroid
dqroid
|dairui1
dairui1
|bannzai
bannzai
|ashktn
ashktn
| +|Yoshino-Yukitaro
Yoshino-Yukitaro
|vladstudio
vladstudio
|tgfjt
tgfjt
|AMHesch
AMHesch
|olup
olup
|moqimoqidea
moqimoqidea
| +|mosleyit
mosleyit
|oprstchn
oprstchn
|philipnext
philipnext
|refactorthis
refactorthis
|samir-nimbly
samir-nimbly
|shaybc
shaybc
| +|shohei-ihaya
shohei-ihaya
|student20880
student20880
|cdlliuy
cdlliuy
|PretzelVector
PretzelVector
|nevermorec
nevermorec
|adamwlarson
adamwlarson
| +|alarno
alarno
|andreastempsch
andreastempsch
|Atlogit
Atlogit
|chadgauth
chadgauth
|dleen
dleen
|dbasclpy
dbasclpy
| +|linegel
linegel
|celestial-vault
celestial-vault
|franekp
franekp
|DeXtroTip
DeXtroTip
|gtaylor
gtaylor
|hesara
hesara
| +|eltociear
eltociear
|Jdo300
Jdo300
|libertyteeth
libertyteeth
|mamertofabian
mamertofabian
|marvijo-code
marvijo-code
|kvokka
kvokka
| +|nbihan-mediware
nbihan-mediware
|PeterDaveHello
PeterDaveHello
|Sarke
Sarke
|01Rian
01Rian
|samsilveira
samsilveira
|StevenTCramer
StevenTCramer
| +|maekawataiki
maekawataiki
| | | | | | ## 라이선스 diff --git a/locales/pl/README.md b/locales/pl/README.md index ec96e0ad81..5afcf5a401 100644 --- a/locales/pl/README.md +++ b/locales/pl/README.md @@ -178,26 +178,26 @@ Kochamy wkład społeczności! Zacznij od przeczytania naszego [CONTRIBUTING.md] Dziękujemy wszystkim naszym współtwórcom, którzy pomogli ulepszyć Roo Code! - -| mrubens
mrubens
| saoudrizwan
saoudrizwan
| cte
cte
| samhvw8
samhvw8
| daniel-lxs
daniel-lxs
| a8trejo
a8trejo
| -| :-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------: | :------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------: | :---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------: | :----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------: | :------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------: | :---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------: | -| ColemanRoo
ColemanRoo
| stea9499
stea9499
| joemanley201
joemanley201
| System233
System233
| jquanton
jquanton
| nissa-seru
nissa-seru
| -| NyxJae
NyxJae
| hannesrudolph
hannesrudolph
| MuriloFP
MuriloFP
| punkpeye
punkpeye
| d-oit
d-oit
| monotykamary
monotykamary
| -| vigneshsubbiah16
vigneshsubbiah16
| lloydchang
lloydchang
| cannuri
cannuri
| Szpadel
Szpadel
| psv2522
psv2522
| Premshay
Premshay
| -| Smartsheet-JB-Brown
Smartsheet-JB-Brown
| wkordalski
wkordalski
| qdaxb
qdaxb
| feifei325
feifei325
| lupuletic
lupuletic
| KJ7LNW
KJ7LNW
| -| olweraltuve
olweraltuve
| RaySinner
RaySinner
| pugazhendhi-m
pugazhendhi-m
| pdecat
pdecat
| emshvac
emshvac
| afshawnlotfi
afshawnlotfi
| -| aitoroses
aitoroses
| dtrugman
dtrugman
| diarmidmackenzie
diarmidmackenzie
| sammcj
sammcj
| aheizi
aheizi
| Lunchb0ne
Lunchb0ne
| -| yt3trees
yt3trees
| yongjer
yongjer
| vincentsong
vincentsong
| teddyOOXX
teddyOOXX
| eonghk
eonghk
| arthurauffray
arthurauffray
| -| heyseth
heyseth
| anton-otee
anton-otee
| benzntech
benzntech
| GitlyHallows
GitlyHallows
| jcbdev
jcbdev
| Chenjiayuan195
Chenjiayuan195
| -| mdp
mdp
| napter
napter
| philfung
philfung
| AMHesch
AMHesch
| vladstudio
vladstudio
| Yoshino-Yukitaro
Yoshino-Yukitaro
| -| ashktn
ashktn
| bannzai
bannzai
| dairui1
dairui1
| dqroid
dqroid
| im47cn
im47cn
| jwcraig
jwcraig
| -| kinandan
kinandan
| kohii
kohii
| lightrabbit
lightrabbit
| olup
olup
| moqimoqidea
moqimoqidea
| mosleyit
mosleyit
| -| oprstchn
oprstchn
| philipnext
philipnext
| refactorthis
refactorthis
| samir-nimbly
samir-nimbly
| shaybc
shaybc
| shohei-ihaya
shohei-ihaya
| -| student20880
student20880
| cdlliuy
cdlliuy
| PretzelVector
PretzelVector
| nevermorec
nevermorec
| adamwlarson
adamwlarson
| alarno
alarno
| -| andreastempsch
andreastempsch
| Atlogit
Atlogit
| chadgauth
chadgauth
| dleen
dleen
| dbasclpy
dbasclpy
| linegel
linegel
| -| celestial-vault
celestial-vault
| franekp
franekp
| DeXtroTip
DeXtroTip
| hesara
hesara
| eltociear
eltociear
| Jdo300
Jdo300
| -| libertyteeth
libertyteeth
| mamertofabian
mamertofabian
| | | | | - +|mrubens
mrubens
|saoudrizwan
saoudrizwan
|cte
cte
|samhvw8
samhvw8
|daniel-lxs
daniel-lxs
|a8trejo
a8trejo
| +|:---:|:---:|:---:|:---:|:---:|:---:| +|ColemanRoo
ColemanRoo
|stea9499
stea9499
|joemanley201
joemanley201
|System233
System233
|nissa-seru
nissa-seru
|jquanton
jquanton
| +|hannesrudolph
hannesrudolph
|NyxJae
NyxJae
|MuriloFP
MuriloFP
|punkpeye
punkpeye
|d-oit
d-oit
|monotykamary
monotykamary
| +|cannuri
cannuri
|lloydchang
lloydchang
|vigneshsubbiah16
vigneshsubbiah16
|KJ7LNW
KJ7LNW
|Szpadel
Szpadel
|lupuletic
lupuletic
| +|feifei325
feifei325
|qdaxb
qdaxb
|wkordalski
wkordalski
|Smartsheet-JB-Brown
Smartsheet-JB-Brown
|Premshay
Premshay
|psv2522
psv2522
| +|olweraltuve
olweraltuve
|RaySinner
RaySinner
|aheizi
aheizi
|afshawnlotfi
afshawnlotfi
|diarmidmackenzie
diarmidmackenzie
|emshvac
emshvac
| +|pdecat
pdecat
|pugazhendhi-m
pugazhendhi-m
|Lunchb0ne
Lunchb0ne
|sammcj
sammcj
|dtrugman
dtrugman
|aitoroses
aitoroses
| +|yt3trees
yt3trees
|yongjer
yongjer
|vincentsong
vincentsong
|teddyOOXX
teddyOOXX
|eonghk
eonghk
|arthurauffray
arthurauffray
| +|heyseth
heyseth
|philfung
philfung
|napter
napter
|mdp
mdp
|Chenjiayuan195
Chenjiayuan195
|jcbdev
jcbdev
| +|GitlyHallows
GitlyHallows
|benzntech
benzntech
|anton-otee
anton-otee
|lightrabbit
lightrabbit
|kohii
kohii
|kinandan
kinandan
| +|jwcraig
jwcraig
|im47cn
im47cn
|dqroid
dqroid
|dairui1
dairui1
|bannzai
bannzai
|ashktn
ashktn
| +|Yoshino-Yukitaro
Yoshino-Yukitaro
|vladstudio
vladstudio
|tgfjt
tgfjt
|AMHesch
AMHesch
|olup
olup
|moqimoqidea
moqimoqidea
| +|mosleyit
mosleyit
|oprstchn
oprstchn
|philipnext
philipnext
|refactorthis
refactorthis
|samir-nimbly
samir-nimbly
|shaybc
shaybc
| +|shohei-ihaya
shohei-ihaya
|student20880
student20880
|cdlliuy
cdlliuy
|PretzelVector
PretzelVector
|nevermorec
nevermorec
|adamwlarson
adamwlarson
| +|alarno
alarno
|andreastempsch
andreastempsch
|Atlogit
Atlogit
|chadgauth
chadgauth
|dleen
dleen
|dbasclpy
dbasclpy
| +|linegel
linegel
|celestial-vault
celestial-vault
|franekp
franekp
|DeXtroTip
DeXtroTip
|gtaylor
gtaylor
|hesara
hesara
| +|eltociear
eltociear
|Jdo300
Jdo300
|libertyteeth
libertyteeth
|mamertofabian
mamertofabian
|marvijo-code
marvijo-code
|kvokka
kvokka
| +|nbihan-mediware
nbihan-mediware
|PeterDaveHello
PeterDaveHello
|Sarke
Sarke
|01Rian
01Rian
|samsilveira
samsilveira
|StevenTCramer
StevenTCramer
| +|maekawataiki
maekawataiki
| | | | | | ## Licencja diff --git a/locales/pt-BR/README.md b/locales/pt-BR/README.md index aac36ace0b..eb8b164096 100644 --- a/locales/pt-BR/README.md +++ b/locales/pt-BR/README.md @@ -178,26 +178,26 @@ Adoramos contribuições da comunidade! Comece lendo nosso [CONTRIBUTING.md](CON Obrigado a todos os nossos contribuidores que ajudaram a tornar o Roo Code melhor! - -| mrubens
mrubens
| saoudrizwan
saoudrizwan
| cte
cte
| samhvw8
samhvw8
| daniel-lxs
daniel-lxs
| a8trejo
a8trejo
| -| :-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------: | :------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------: | :---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------: | :----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------: | :------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------: | :---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------: | -| ColemanRoo
ColemanRoo
| stea9499
stea9499
| joemanley201
joemanley201
| System233
System233
| jquanton
jquanton
| nissa-seru
nissa-seru
| -| NyxJae
NyxJae
| hannesrudolph
hannesrudolph
| MuriloFP
MuriloFP
| punkpeye
punkpeye
| d-oit
d-oit
| monotykamary
monotykamary
| -| vigneshsubbiah16
vigneshsubbiah16
| lloydchang
lloydchang
| cannuri
cannuri
| Szpadel
Szpadel
| psv2522
psv2522
| Premshay
Premshay
| -| Smartsheet-JB-Brown
Smartsheet-JB-Brown
| wkordalski
wkordalski
| qdaxb
qdaxb
| feifei325
feifei325
| lupuletic
lupuletic
| KJ7LNW
KJ7LNW
| -| olweraltuve
olweraltuve
| RaySinner
RaySinner
| pugazhendhi-m
pugazhendhi-m
| pdecat
pdecat
| emshvac
emshvac
| afshawnlotfi
afshawnlotfi
| -| aitoroses
aitoroses
| dtrugman
dtrugman
| diarmidmackenzie
diarmidmackenzie
| sammcj
sammcj
| aheizi
aheizi
| Lunchb0ne
Lunchb0ne
| -| yt3trees
yt3trees
| yongjer
yongjer
| vincentsong
vincentsong
| teddyOOXX
teddyOOXX
| eonghk
eonghk
| arthurauffray
arthurauffray
| -| heyseth
heyseth
| anton-otee
anton-otee
| benzntech
benzntech
| GitlyHallows
GitlyHallows
| jcbdev
jcbdev
| Chenjiayuan195
Chenjiayuan195
| -| mdp
mdp
| napter
napter
| philfung
philfung
| AMHesch
AMHesch
| vladstudio
vladstudio
| Yoshino-Yukitaro
Yoshino-Yukitaro
| -| ashktn
ashktn
| bannzai
bannzai
| dairui1
dairui1
| dqroid
dqroid
| im47cn
im47cn
| jwcraig
jwcraig
| -| kinandan
kinandan
| kohii
kohii
| lightrabbit
lightrabbit
| olup
olup
| moqimoqidea
moqimoqidea
| mosleyit
mosleyit
| -| oprstchn
oprstchn
| philipnext
philipnext
| refactorthis
refactorthis
| samir-nimbly
samir-nimbly
| shaybc
shaybc
| shohei-ihaya
shohei-ihaya
| -| student20880
student20880
| cdlliuy
cdlliuy
| PretzelVector
PretzelVector
| nevermorec
nevermorec
| adamwlarson
adamwlarson
| alarno
alarno
| -| andreastempsch
andreastempsch
| Atlogit
Atlogit
| chadgauth
chadgauth
| dleen
dleen
| dbasclpy
dbasclpy
| linegel
linegel
| -| celestial-vault
celestial-vault
| franekp
franekp
| DeXtroTip
DeXtroTip
| hesara
hesara
| eltociear
eltociear
| Jdo300
Jdo300
| -| libertyteeth
libertyteeth
| mamertofabian
mamertofabian
| | | | | - +|mrubens
mrubens
|saoudrizwan
saoudrizwan
|cte
cte
|samhvw8
samhvw8
|daniel-lxs
daniel-lxs
|a8trejo
a8trejo
| +|:---:|:---:|:---:|:---:|:---:|:---:| +|ColemanRoo
ColemanRoo
|stea9499
stea9499
|joemanley201
joemanley201
|System233
System233
|nissa-seru
nissa-seru
|jquanton
jquanton
| +|hannesrudolph
hannesrudolph
|NyxJae
NyxJae
|MuriloFP
MuriloFP
|punkpeye
punkpeye
|d-oit
d-oit
|monotykamary
monotykamary
| +|cannuri
cannuri
|lloydchang
lloydchang
|vigneshsubbiah16
vigneshsubbiah16
|KJ7LNW
KJ7LNW
|Szpadel
Szpadel
|lupuletic
lupuletic
| +|feifei325
feifei325
|qdaxb
qdaxb
|wkordalski
wkordalski
|Smartsheet-JB-Brown
Smartsheet-JB-Brown
|Premshay
Premshay
|psv2522
psv2522
| +|olweraltuve
olweraltuve
|RaySinner
RaySinner
|aheizi
aheizi
|afshawnlotfi
afshawnlotfi
|diarmidmackenzie
diarmidmackenzie
|emshvac
emshvac
| +|pdecat
pdecat
|pugazhendhi-m
pugazhendhi-m
|Lunchb0ne
Lunchb0ne
|sammcj
sammcj
|dtrugman
dtrugman
|aitoroses
aitoroses
| +|yt3trees
yt3trees
|yongjer
yongjer
|vincentsong
vincentsong
|teddyOOXX
teddyOOXX
|eonghk
eonghk
|arthurauffray
arthurauffray
| +|heyseth
heyseth
|philfung
philfung
|napter
napter
|mdp
mdp
|Chenjiayuan195
Chenjiayuan195
|jcbdev
jcbdev
| +|GitlyHallows
GitlyHallows
|benzntech
benzntech
|anton-otee
anton-otee
|lightrabbit
lightrabbit
|kohii
kohii
|kinandan
kinandan
| +|jwcraig
jwcraig
|im47cn
im47cn
|dqroid
dqroid
|dairui1
dairui1
|bannzai
bannzai
|ashktn
ashktn
| +|Yoshino-Yukitaro
Yoshino-Yukitaro
|vladstudio
vladstudio
|tgfjt
tgfjt
|AMHesch
AMHesch
|olup
olup
|moqimoqidea
moqimoqidea
| +|mosleyit
mosleyit
|oprstchn
oprstchn
|philipnext
philipnext
|refactorthis
refactorthis
|samir-nimbly
samir-nimbly
|shaybc
shaybc
| +|shohei-ihaya
shohei-ihaya
|student20880
student20880
|cdlliuy
cdlliuy
|PretzelVector
PretzelVector
|nevermorec
nevermorec
|adamwlarson
adamwlarson
| +|alarno
alarno
|andreastempsch
andreastempsch
|Atlogit
Atlogit
|chadgauth
chadgauth
|dleen
dleen
|dbasclpy
dbasclpy
| +|linegel
linegel
|celestial-vault
celestial-vault
|franekp
franekp
|DeXtroTip
DeXtroTip
|gtaylor
gtaylor
|hesara
hesara
| +|eltociear
eltociear
|Jdo300
Jdo300
|libertyteeth
libertyteeth
|mamertofabian
mamertofabian
|marvijo-code
marvijo-code
|kvokka
kvokka
| +|nbihan-mediware
nbihan-mediware
|PeterDaveHello
PeterDaveHello
|Sarke
Sarke
|01Rian
01Rian
|samsilveira
samsilveira
|StevenTCramer
StevenTCramer
| +|maekawataiki
maekawataiki
| | | | | | ## Licença diff --git a/locales/tr/README.md b/locales/tr/README.md index a52a6a2373..24b5dd679e 100644 --- a/locales/tr/README.md +++ b/locales/tr/README.md @@ -178,26 +178,26 @@ Topluluk katkılarını seviyoruz! [CONTRIBUTING.md](CONTRIBUTING.md) dosyasın Roo Code'u daha iyi hale getirmeye yardımcı olan tüm katkıda bulunanlara teşekkür ederiz! - -| mrubens
mrubens
| saoudrizwan
saoudrizwan
| cte
cte
| samhvw8
samhvw8
| daniel-lxs
daniel-lxs
| a8trejo
a8trejo
| -| :-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------: | :------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------: | :---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------: | :----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------: | :------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------: | :---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------: | -| ColemanRoo
ColemanRoo
| stea9499
stea9499
| joemanley201
joemanley201
| System233
System233
| jquanton
jquanton
| nissa-seru
nissa-seru
| -| NyxJae
NyxJae
| hannesrudolph
hannesrudolph
| MuriloFP
MuriloFP
| punkpeye
punkpeye
| d-oit
d-oit
| monotykamary
monotykamary
| -| vigneshsubbiah16
vigneshsubbiah16
| lloydchang
lloydchang
| cannuri
cannuri
| Szpadel
Szpadel
| psv2522
psv2522
| Premshay
Premshay
| -| Smartsheet-JB-Brown
Smartsheet-JB-Brown
| wkordalski
wkordalski
| qdaxb
qdaxb
| feifei325
feifei325
| lupuletic
lupuletic
| KJ7LNW
KJ7LNW
| -| olweraltuve
olweraltuve
| RaySinner
RaySinner
| pugazhendhi-m
pugazhendhi-m
| pdecat
pdecat
| emshvac
emshvac
| afshawnlotfi
afshawnlotfi
| -| aitoroses
aitoroses
| dtrugman
dtrugman
| diarmidmackenzie
diarmidmackenzie
| sammcj
sammcj
| aheizi
aheizi
| Lunchb0ne
Lunchb0ne
| -| yt3trees
yt3trees
| yongjer
yongjer
| vincentsong
vincentsong
| teddyOOXX
teddyOOXX
| eonghk
eonghk
| arthurauffray
arthurauffray
| -| heyseth
heyseth
| anton-otee
anton-otee
| benzntech
benzntech
| GitlyHallows
GitlyHallows
| jcbdev
jcbdev
| Chenjiayuan195
Chenjiayuan195
| -| mdp
mdp
| napter
napter
| philfung
philfung
| AMHesch
AMHesch
| vladstudio
vladstudio
| Yoshino-Yukitaro
Yoshino-Yukitaro
| -| ashktn
ashktn
| bannzai
bannzai
| dairui1
dairui1
| dqroid
dqroid
| im47cn
im47cn
| jwcraig
jwcraig
| -| kinandan
kinandan
| kohii
kohii
| lightrabbit
lightrabbit
| olup
olup
| moqimoqidea
moqimoqidea
| mosleyit
mosleyit
| -| oprstchn
oprstchn
| philipnext
philipnext
| refactorthis
refactorthis
| samir-nimbly
samir-nimbly
| shaybc
shaybc
| shohei-ihaya
shohei-ihaya
| -| student20880
student20880
| cdlliuy
cdlliuy
| PretzelVector
PretzelVector
| nevermorec
nevermorec
| adamwlarson
adamwlarson
| alarno
alarno
| -| andreastempsch
andreastempsch
| Atlogit
Atlogit
| chadgauth
chadgauth
| dleen
dleen
| dbasclpy
dbasclpy
| linegel
linegel
| -| celestial-vault
celestial-vault
| franekp
franekp
| DeXtroTip
DeXtroTip
| hesara
hesara
| eltociear
eltociear
| Jdo300
Jdo300
| -| libertyteeth
libertyteeth
| mamertofabian
mamertofabian
| | | | | - +|mrubens
mrubens
|saoudrizwan
saoudrizwan
|cte
cte
|samhvw8
samhvw8
|daniel-lxs
daniel-lxs
|a8trejo
a8trejo
| +|:---:|:---:|:---:|:---:|:---:|:---:| +|ColemanRoo
ColemanRoo
|stea9499
stea9499
|joemanley201
joemanley201
|System233
System233
|nissa-seru
nissa-seru
|jquanton
jquanton
| +|hannesrudolph
hannesrudolph
|NyxJae
NyxJae
|MuriloFP
MuriloFP
|punkpeye
punkpeye
|d-oit
d-oit
|monotykamary
monotykamary
| +|cannuri
cannuri
|lloydchang
lloydchang
|vigneshsubbiah16
vigneshsubbiah16
|KJ7LNW
KJ7LNW
|Szpadel
Szpadel
|lupuletic
lupuletic
| +|feifei325
feifei325
|qdaxb
qdaxb
|wkordalski
wkordalski
|Smartsheet-JB-Brown
Smartsheet-JB-Brown
|Premshay
Premshay
|psv2522
psv2522
| +|olweraltuve
olweraltuve
|RaySinner
RaySinner
|aheizi
aheizi
|afshawnlotfi
afshawnlotfi
|diarmidmackenzie
diarmidmackenzie
|emshvac
emshvac
| +|pdecat
pdecat
|pugazhendhi-m
pugazhendhi-m
|Lunchb0ne
Lunchb0ne
|sammcj
sammcj
|dtrugman
dtrugman
|aitoroses
aitoroses
| +|yt3trees
yt3trees
|yongjer
yongjer
|vincentsong
vincentsong
|teddyOOXX
teddyOOXX
|eonghk
eonghk
|arthurauffray
arthurauffray
| +|heyseth
heyseth
|philfung
philfung
|napter
napter
|mdp
mdp
|Chenjiayuan195
Chenjiayuan195
|jcbdev
jcbdev
| +|GitlyHallows
GitlyHallows
|benzntech
benzntech
|anton-otee
anton-otee
|lightrabbit
lightrabbit
|kohii
kohii
|kinandan
kinandan
| +|jwcraig
jwcraig
|im47cn
im47cn
|dqroid
dqroid
|dairui1
dairui1
|bannzai
bannzai
|ashktn
ashktn
| +|Yoshino-Yukitaro
Yoshino-Yukitaro
|vladstudio
vladstudio
|tgfjt
tgfjt
|AMHesch
AMHesch
|olup
olup
|moqimoqidea
moqimoqidea
| +|mosleyit
mosleyit
|oprstchn
oprstchn
|philipnext
philipnext
|refactorthis
refactorthis
|samir-nimbly
samir-nimbly
|shaybc
shaybc
| +|shohei-ihaya
shohei-ihaya
|student20880
student20880
|cdlliuy
cdlliuy
|PretzelVector
PretzelVector
|nevermorec
nevermorec
|adamwlarson
adamwlarson
| +|alarno
alarno
|andreastempsch
andreastempsch
|Atlogit
Atlogit
|chadgauth
chadgauth
|dleen
dleen
|dbasclpy
dbasclpy
| +|linegel
linegel
|celestial-vault
celestial-vault
|franekp
franekp
|DeXtroTip
DeXtroTip
|gtaylor
gtaylor
|hesara
hesara
| +|eltociear
eltociear
|Jdo300
Jdo300
|libertyteeth
libertyteeth
|mamertofabian
mamertofabian
|marvijo-code
marvijo-code
|kvokka
kvokka
| +|nbihan-mediware
nbihan-mediware
|PeterDaveHello
PeterDaveHello
|Sarke
Sarke
|01Rian
01Rian
|samsilveira
samsilveira
|StevenTCramer
StevenTCramer
| +|maekawataiki
maekawataiki
| | | | | | ## Lisans diff --git a/locales/vi/README.md b/locales/vi/README.md index 79ac0c3d0d..e15a3063b8 100644 --- a/locales/vi/README.md +++ b/locales/vi/README.md @@ -178,26 +178,26 @@ Chúng tôi rất hoan nghênh đóng góp từ cộng đồng! Bắt đầu b Cảm ơn tất cả những người đóng góp đã giúp cải thiện Roo Code! - -| mrubens
mrubens
| saoudrizwan
saoudrizwan
| cte
cte
| samhvw8
samhvw8
| daniel-lxs
daniel-lxs
| a8trejo
a8trejo
| -| :-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------: | :------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------: | :---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------: | :----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------: | :------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------: | :---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------: | -| ColemanRoo
ColemanRoo
| stea9499
stea9499
| joemanley201
joemanley201
| System233
System233
| jquanton
jquanton
| nissa-seru
nissa-seru
| -| NyxJae
NyxJae
| hannesrudolph
hannesrudolph
| MuriloFP
MuriloFP
| punkpeye
punkpeye
| d-oit
d-oit
| monotykamary
monotykamary
| -| vigneshsubbiah16
vigneshsubbiah16
| lloydchang
lloydchang
| cannuri
cannuri
| Szpadel
Szpadel
| psv2522
psv2522
| Premshay
Premshay
| -| Smartsheet-JB-Brown
Smartsheet-JB-Brown
| wkordalski
wkordalski
| qdaxb
qdaxb
| feifei325
feifei325
| lupuletic
lupuletic
| KJ7LNW
KJ7LNW
| -| olweraltuve
olweraltuve
| RaySinner
RaySinner
| pugazhendhi-m
pugazhendhi-m
| pdecat
pdecat
| emshvac
emshvac
| afshawnlotfi
afshawnlotfi
| -| aitoroses
aitoroses
| dtrugman
dtrugman
| diarmidmackenzie
diarmidmackenzie
| sammcj
sammcj
| aheizi
aheizi
| Lunchb0ne
Lunchb0ne
| -| yt3trees
yt3trees
| yongjer
yongjer
| vincentsong
vincentsong
| teddyOOXX
teddyOOXX
| eonghk
eonghk
| arthurauffray
arthurauffray
| -| heyseth
heyseth
| anton-otee
anton-otee
| benzntech
benzntech
| GitlyHallows
GitlyHallows
| jcbdev
jcbdev
| Chenjiayuan195
Chenjiayuan195
| -| mdp
mdp
| napter
napter
| philfung
philfung
| AMHesch
AMHesch
| vladstudio
vladstudio
| Yoshino-Yukitaro
Yoshino-Yukitaro
| -| ashktn
ashktn
| bannzai
bannzai
| dairui1
dairui1
| dqroid
dqroid
| im47cn
im47cn
| jwcraig
jwcraig
| -| kinandan
kinandan
| kohii
kohii
| lightrabbit
lightrabbit
| olup
olup
| moqimoqidea
moqimoqidea
| mosleyit
mosleyit
| -| oprstchn
oprstchn
| philipnext
philipnext
| refactorthis
refactorthis
| samir-nimbly
samir-nimbly
| shaybc
shaybc
| shohei-ihaya
shohei-ihaya
| -| student20880
student20880
| cdlliuy
cdlliuy
| PretzelVector
PretzelVector
| nevermorec
nevermorec
| adamwlarson
adamwlarson
| alarno
alarno
| -| andreastempsch
andreastempsch
| Atlogit
Atlogit
| chadgauth
chadgauth
| dleen
dleen
| dbasclpy
dbasclpy
| linegel
linegel
| -| celestial-vault
celestial-vault
| franekp
franekp
| DeXtroTip
DeXtroTip
| hesara
hesara
| eltociear
eltociear
| Jdo300
Jdo300
| -| libertyteeth
libertyteeth
| mamertofabian
mamertofabian
| | | | | - +|mrubens
mrubens
|saoudrizwan
saoudrizwan
|cte
cte
|samhvw8
samhvw8
|daniel-lxs
daniel-lxs
|a8trejo
a8trejo
| +|:---:|:---:|:---:|:---:|:---:|:---:| +|ColemanRoo
ColemanRoo
|stea9499
stea9499
|joemanley201
joemanley201
|System233
System233
|nissa-seru
nissa-seru
|jquanton
jquanton
| +|hannesrudolph
hannesrudolph
|NyxJae
NyxJae
|MuriloFP
MuriloFP
|punkpeye
punkpeye
|d-oit
d-oit
|monotykamary
monotykamary
| +|cannuri
cannuri
|lloydchang
lloydchang
|vigneshsubbiah16
vigneshsubbiah16
|KJ7LNW
KJ7LNW
|Szpadel
Szpadel
|lupuletic
lupuletic
| +|feifei325
feifei325
|qdaxb
qdaxb
|wkordalski
wkordalski
|Smartsheet-JB-Brown
Smartsheet-JB-Brown
|Premshay
Premshay
|psv2522
psv2522
| +|olweraltuve
olweraltuve
|RaySinner
RaySinner
|aheizi
aheizi
|afshawnlotfi
afshawnlotfi
|diarmidmackenzie
diarmidmackenzie
|emshvac
emshvac
| +|pdecat
pdecat
|pugazhendhi-m
pugazhendhi-m
|Lunchb0ne
Lunchb0ne
|sammcj
sammcj
|dtrugman
dtrugman
|aitoroses
aitoroses
| +|yt3trees
yt3trees
|yongjer
yongjer
|vincentsong
vincentsong
|teddyOOXX
teddyOOXX
|eonghk
eonghk
|arthurauffray
arthurauffray
| +|heyseth
heyseth
|philfung
philfung
|napter
napter
|mdp
mdp
|Chenjiayuan195
Chenjiayuan195
|jcbdev
jcbdev
| +|GitlyHallows
GitlyHallows
|benzntech
benzntech
|anton-otee
anton-otee
|lightrabbit
lightrabbit
|kohii
kohii
|kinandan
kinandan
| +|jwcraig
jwcraig
|im47cn
im47cn
|dqroid
dqroid
|dairui1
dairui1
|bannzai
bannzai
|ashktn
ashktn
| +|Yoshino-Yukitaro
Yoshino-Yukitaro
|vladstudio
vladstudio
|tgfjt
tgfjt
|AMHesch
AMHesch
|olup
olup
|moqimoqidea
moqimoqidea
| +|mosleyit
mosleyit
|oprstchn
oprstchn
|philipnext
philipnext
|refactorthis
refactorthis
|samir-nimbly
samir-nimbly
|shaybc
shaybc
| +|shohei-ihaya
shohei-ihaya
|student20880
student20880
|cdlliuy
cdlliuy
|PretzelVector
PretzelVector
|nevermorec
nevermorec
|adamwlarson
adamwlarson
| +|alarno
alarno
|andreastempsch
andreastempsch
|Atlogit
Atlogit
|chadgauth
chadgauth
|dleen
dleen
|dbasclpy
dbasclpy
| +|linegel
linegel
|celestial-vault
celestial-vault
|franekp
franekp
|DeXtroTip
DeXtroTip
|gtaylor
gtaylor
|hesara
hesara
| +|eltociear
eltociear
|Jdo300
Jdo300
|libertyteeth
libertyteeth
|mamertofabian
mamertofabian
|marvijo-code
marvijo-code
|kvokka
kvokka
| +|nbihan-mediware
nbihan-mediware
|PeterDaveHello
PeterDaveHello
|Sarke
Sarke
|01Rian
01Rian
|samsilveira
samsilveira
|StevenTCramer
StevenTCramer
| +|maekawataiki
maekawataiki
| | | | | | ## Giấy Phép diff --git a/locales/zh-CN/README.md b/locales/zh-CN/README.md index 73ca52860d..cad76e3ddf 100644 --- a/locales/zh-CN/README.md +++ b/locales/zh-CN/README.md @@ -178,26 +178,26 @@ code --install-extension bin/roo-cline-.vsix 感谢所有帮助改进 Roo Code 的贡献者! - -| mrubens
mrubens
| saoudrizwan
saoudrizwan
| cte
cte
| samhvw8
samhvw8
| daniel-lxs
daniel-lxs
| a8trejo
a8trejo
| -| :-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------: | :------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------: | :---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------: | :----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------: | :------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------: | :---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------: | -| ColemanRoo
ColemanRoo
| stea9499
stea9499
| joemanley201
joemanley201
| System233
System233
| jquanton
jquanton
| nissa-seru
nissa-seru
| -| NyxJae
NyxJae
| hannesrudolph
hannesrudolph
| MuriloFP
MuriloFP
| punkpeye
punkpeye
| d-oit
d-oit
| monotykamary
monotykamary
| -| vigneshsubbiah16
vigneshsubbiah16
| lloydchang
lloydchang
| cannuri
cannuri
| Szpadel
Szpadel
| psv2522
psv2522
| Premshay
Premshay
| -| Smartsheet-JB-Brown
Smartsheet-JB-Brown
| wkordalski
wkordalski
| qdaxb
qdaxb
| feifei325
feifei325
| lupuletic
lupuletic
| KJ7LNW
KJ7LNW
| -| olweraltuve
olweraltuve
| RaySinner
RaySinner
| pugazhendhi-m
pugazhendhi-m
| pdecat
pdecat
| emshvac
emshvac
| afshawnlotfi
afshawnlotfi
| -| aitoroses
aitoroses
| dtrugman
dtrugman
| diarmidmackenzie
diarmidmackenzie
| sammcj
sammcj
| aheizi
aheizi
| Lunchb0ne
Lunchb0ne
| -| yt3trees
yt3trees
| yongjer
yongjer
| vincentsong
vincentsong
| teddyOOXX
teddyOOXX
| eonghk
eonghk
| arthurauffray
arthurauffray
| -| heyseth
heyseth
| anton-otee
anton-otee
| benzntech
benzntech
| GitlyHallows
GitlyHallows
| jcbdev
jcbdev
| Chenjiayuan195
Chenjiayuan195
| -| mdp
mdp
| napter
napter
| philfung
philfung
| AMHesch
AMHesch
| vladstudio
vladstudio
| Yoshino-Yukitaro
Yoshino-Yukitaro
| -| ashktn
ashktn
| bannzai
bannzai
| dairui1
dairui1
| dqroid
dqroid
| im47cn
im47cn
| jwcraig
jwcraig
| -| kinandan
kinandan
| kohii
kohii
| lightrabbit
lightrabbit
| olup
olup
| moqimoqidea
moqimoqidea
| mosleyit
mosleyit
| -| oprstchn
oprstchn
| philipnext
philipnext
| refactorthis
refactorthis
| samir-nimbly
samir-nimbly
| shaybc
shaybc
| shohei-ihaya
shohei-ihaya
| -| student20880
student20880
| cdlliuy
cdlliuy
| PretzelVector
PretzelVector
| nevermorec
nevermorec
| adamwlarson
adamwlarson
| alarno
alarno
| -| andreastempsch
andreastempsch
| Atlogit
Atlogit
| chadgauth
chadgauth
| dleen
dleen
| dbasclpy
dbasclpy
| linegel
linegel
| -| celestial-vault
celestial-vault
| franekp
franekp
| DeXtroTip
DeXtroTip
| hesara
hesara
| eltociear
eltociear
| Jdo300
Jdo300
| -| libertyteeth
libertyteeth
| mamertofabian
mamertofabian
| | | | | - +|mrubens
mrubens
|saoudrizwan
saoudrizwan
|cte
cte
|samhvw8
samhvw8
|daniel-lxs
daniel-lxs
|a8trejo
a8trejo
| +|:---:|:---:|:---:|:---:|:---:|:---:| +|ColemanRoo
ColemanRoo
|stea9499
stea9499
|joemanley201
joemanley201
|System233
System233
|nissa-seru
nissa-seru
|jquanton
jquanton
| +|hannesrudolph
hannesrudolph
|NyxJae
NyxJae
|MuriloFP
MuriloFP
|punkpeye
punkpeye
|d-oit
d-oit
|monotykamary
monotykamary
| +|cannuri
cannuri
|lloydchang
lloydchang
|vigneshsubbiah16
vigneshsubbiah16
|KJ7LNW
KJ7LNW
|Szpadel
Szpadel
|lupuletic
lupuletic
| +|feifei325
feifei325
|qdaxb
qdaxb
|wkordalski
wkordalski
|Smartsheet-JB-Brown
Smartsheet-JB-Brown
|Premshay
Premshay
|psv2522
psv2522
| +|olweraltuve
olweraltuve
|RaySinner
RaySinner
|aheizi
aheizi
|afshawnlotfi
afshawnlotfi
|diarmidmackenzie
diarmidmackenzie
|emshvac
emshvac
| +|pdecat
pdecat
|pugazhendhi-m
pugazhendhi-m
|Lunchb0ne
Lunchb0ne
|sammcj
sammcj
|dtrugman
dtrugman
|aitoroses
aitoroses
| +|yt3trees
yt3trees
|yongjer
yongjer
|vincentsong
vincentsong
|teddyOOXX
teddyOOXX
|eonghk
eonghk
|arthurauffray
arthurauffray
| +|heyseth
heyseth
|philfung
philfung
|napter
napter
|mdp
mdp
|Chenjiayuan195
Chenjiayuan195
|jcbdev
jcbdev
| +|GitlyHallows
GitlyHallows
|benzntech
benzntech
|anton-otee
anton-otee
|lightrabbit
lightrabbit
|kohii
kohii
|kinandan
kinandan
| +|jwcraig
jwcraig
|im47cn
im47cn
|dqroid
dqroid
|dairui1
dairui1
|bannzai
bannzai
|ashktn
ashktn
| +|Yoshino-Yukitaro
Yoshino-Yukitaro
|vladstudio
vladstudio
|tgfjt
tgfjt
|AMHesch
AMHesch
|olup
olup
|moqimoqidea
moqimoqidea
| +|mosleyit
mosleyit
|oprstchn
oprstchn
|philipnext
philipnext
|refactorthis
refactorthis
|samir-nimbly
samir-nimbly
|shaybc
shaybc
| +|shohei-ihaya
shohei-ihaya
|student20880
student20880
|cdlliuy
cdlliuy
|PretzelVector
PretzelVector
|nevermorec
nevermorec
|adamwlarson
adamwlarson
| +|alarno
alarno
|andreastempsch
andreastempsch
|Atlogit
Atlogit
|chadgauth
chadgauth
|dleen
dleen
|dbasclpy
dbasclpy
| +|linegel
linegel
|celestial-vault
celestial-vault
|franekp
franekp
|DeXtroTip
DeXtroTip
|gtaylor
gtaylor
|hesara
hesara
| +|eltociear
eltociear
|Jdo300
Jdo300
|libertyteeth
libertyteeth
|mamertofabian
mamertofabian
|marvijo-code
marvijo-code
|kvokka
kvokka
| +|nbihan-mediware
nbihan-mediware
|PeterDaveHello
PeterDaveHello
|Sarke
Sarke
|01Rian
01Rian
|samsilveira
samsilveira
|StevenTCramer
StevenTCramer
| +|maekawataiki
maekawataiki
| | | | | | ## 许可证 diff --git a/locales/zh-TW/README.md b/locales/zh-TW/README.md index 92de68eabe..bc34f94c8d 100644 --- a/locales/zh-TW/README.md +++ b/locales/zh-TW/README.md @@ -178,26 +178,26 @@ code --install-extension bin/roo-cline-.vsix 感謝所有幫助改進 Roo Code 的貢獻者! - -| mrubens
mrubens
| saoudrizwan
saoudrizwan
| cte
cte
| samhvw8
samhvw8
| daniel-lxs
daniel-lxs
| a8trejo
a8trejo
| -| :-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------: | :------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------: | :---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------: | :----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------: | :------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------: | :---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------: | -| ColemanRoo
ColemanRoo
| stea9499
stea9499
| joemanley201
joemanley201
| System233
System233
| jquanton
jquanton
| nissa-seru
nissa-seru
| -| NyxJae
NyxJae
| hannesrudolph
hannesrudolph
| MuriloFP
MuriloFP
| punkpeye
punkpeye
| d-oit
d-oit
| monotykamary
monotykamary
| -| vigneshsubbiah16
vigneshsubbiah16
| lloydchang
lloydchang
| cannuri
cannuri
| Szpadel
Szpadel
| psv2522
psv2522
| Premshay
Premshay
| -| Smartsheet-JB-Brown
Smartsheet-JB-Brown
| wkordalski
wkordalski
| qdaxb
qdaxb
| feifei325
feifei325
| lupuletic
lupuletic
| KJ7LNW
KJ7LNW
| -| olweraltuve
olweraltuve
| RaySinner
RaySinner
| pugazhendhi-m
pugazhendhi-m
| pdecat
pdecat
| emshvac
emshvac
| afshawnlotfi
afshawnlotfi
| -| aitoroses
aitoroses
| dtrugman
dtrugman
| diarmidmackenzie
diarmidmackenzie
| sammcj
sammcj
| aheizi
aheizi
| Lunchb0ne
Lunchb0ne
| -| yt3trees
yt3trees
| yongjer
yongjer
| vincentsong
vincentsong
| teddyOOXX
teddyOOXX
| eonghk
eonghk
| arthurauffray
arthurauffray
| -| heyseth
heyseth
| anton-otee
anton-otee
| benzntech
benzntech
| GitlyHallows
GitlyHallows
| jcbdev
jcbdev
| Chenjiayuan195
Chenjiayuan195
| -| mdp
mdp
| napter
napter
| philfung
philfung
| AMHesch
AMHesch
| vladstudio
vladstudio
| Yoshino-Yukitaro
Yoshino-Yukitaro
| -| ashktn
ashktn
| bannzai
bannzai
| dairui1
dairui1
| dqroid
dqroid
| im47cn
im47cn
| jwcraig
jwcraig
| -| kinandan
kinandan
| kohii
kohii
| lightrabbit
lightrabbit
| olup
olup
| moqimoqidea
moqimoqidea
| mosleyit
mosleyit
| -| oprstchn
oprstchn
| philipnext
philipnext
| refactorthis
refactorthis
| samir-nimbly
samir-nimbly
| shaybc
shaybc
| shohei-ihaya
shohei-ihaya
| -| student20880
student20880
| cdlliuy
cdlliuy
| PretzelVector
PretzelVector
| nevermorec
nevermorec
| adamwlarson
adamwlarson
| alarno
alarno
| -| andreastempsch
andreastempsch
| Atlogit
Atlogit
| chadgauth
chadgauth
| dleen
dleen
| dbasclpy
dbasclpy
| linegel
linegel
| -| celestial-vault
celestial-vault
| franekp
franekp
| DeXtroTip
DeXtroTip
| hesara
hesara
| eltociear
eltociear
| Jdo300
Jdo300
| -| libertyteeth
libertyteeth
| mamertofabian
mamertofabian
| | | | | - +|mrubens
mrubens
|saoudrizwan
saoudrizwan
|cte
cte
|samhvw8
samhvw8
|daniel-lxs
daniel-lxs
|a8trejo
a8trejo
| +|:---:|:---:|:---:|:---:|:---:|:---:| +|ColemanRoo
ColemanRoo
|stea9499
stea9499
|joemanley201
joemanley201
|System233
System233
|nissa-seru
nissa-seru
|jquanton
jquanton
| +|hannesrudolph
hannesrudolph
|NyxJae
NyxJae
|MuriloFP
MuriloFP
|punkpeye
punkpeye
|d-oit
d-oit
|monotykamary
monotykamary
| +|cannuri
cannuri
|lloydchang
lloydchang
|vigneshsubbiah16
vigneshsubbiah16
|KJ7LNW
KJ7LNW
|Szpadel
Szpadel
|lupuletic
lupuletic
| +|feifei325
feifei325
|qdaxb
qdaxb
|wkordalski
wkordalski
|Smartsheet-JB-Brown
Smartsheet-JB-Brown
|Premshay
Premshay
|psv2522
psv2522
| +|olweraltuve
olweraltuve
|RaySinner
RaySinner
|aheizi
aheizi
|afshawnlotfi
afshawnlotfi
|diarmidmackenzie
diarmidmackenzie
|emshvac
emshvac
| +|pdecat
pdecat
|pugazhendhi-m
pugazhendhi-m
|Lunchb0ne
Lunchb0ne
|sammcj
sammcj
|dtrugman
dtrugman
|aitoroses
aitoroses
| +|yt3trees
yt3trees
|yongjer
yongjer
|vincentsong
vincentsong
|teddyOOXX
teddyOOXX
|eonghk
eonghk
|arthurauffray
arthurauffray
| +|heyseth
heyseth
|philfung
philfung
|napter
napter
|mdp
mdp
|Chenjiayuan195
Chenjiayuan195
|jcbdev
jcbdev
| +|GitlyHallows
GitlyHallows
|benzntech
benzntech
|anton-otee
anton-otee
|lightrabbit
lightrabbit
|kohii
kohii
|kinandan
kinandan
| +|jwcraig
jwcraig
|im47cn
im47cn
|dqroid
dqroid
|dairui1
dairui1
|bannzai
bannzai
|ashktn
ashktn
| +|Yoshino-Yukitaro
Yoshino-Yukitaro
|vladstudio
vladstudio
|tgfjt
tgfjt
|AMHesch
AMHesch
|olup
olup
|moqimoqidea
moqimoqidea
| +|mosleyit
mosleyit
|oprstchn
oprstchn
|philipnext
philipnext
|refactorthis
refactorthis
|samir-nimbly
samir-nimbly
|shaybc
shaybc
| +|shohei-ihaya
shohei-ihaya
|student20880
student20880
|cdlliuy
cdlliuy
|PretzelVector
PretzelVector
|nevermorec
nevermorec
|adamwlarson
adamwlarson
| +|alarno
alarno
|andreastempsch
andreastempsch
|Atlogit
Atlogit
|chadgauth
chadgauth
|dleen
dleen
|dbasclpy
dbasclpy
| +|linegel
linegel
|celestial-vault
celestial-vault
|franekp
franekp
|DeXtroTip
DeXtroTip
|gtaylor
gtaylor
|hesara
hesara
| +|eltociear
eltociear
|Jdo300
Jdo300
|libertyteeth
libertyteeth
|mamertofabian
mamertofabian
|marvijo-code
marvijo-code
|kvokka
kvokka
| +|nbihan-mediware
nbihan-mediware
|PeterDaveHello
PeterDaveHello
|Sarke
Sarke
|01Rian
01Rian
|samsilveira
samsilveira
|StevenTCramer
StevenTCramer
| +|maekawataiki
maekawataiki
| | | | | | ## 許可證 diff --git a/package-lock.json b/package-lock.json index e490d8685f..c956d25f89 100644 --- a/package-lock.json +++ b/package-lock.json @@ -99,7 +99,7 @@ "zod-to-ts": "^1.2.0" }, "engines": { - "node": ">=20.18.1", + "node": "20.18.1", "vscode": "^1.84.0" } }, diff --git a/package.json b/package.json index 1b8ca4db8d..122e08d77f 100644 --- a/package.json +++ b/package.json @@ -11,7 +11,7 @@ }, "engines": { "vscode": "^1.84.0", - "node": ">=20.18.1" + "node": "20.18.1" }, "author": { "name": "Roo Code" diff --git a/scripts/find-missing-i18n-key.js b/scripts/find-missing-i18n-key.js index 3c21dfb410..87d160d490 100644 --- a/scripts/find-missing-i18n-key.js +++ b/scripts/find-missing-i18n-key.js @@ -34,9 +34,17 @@ Output: process.exit(0) } -// Directory to traverse -const TARGET_DIR = path.join(__dirname, "../webview-ui/src/components") -const LOCALES_DIR = path.join(__dirname, "../webview-ui/src/i18n/locales") +// Directories to traverse and their corresponding locales +const DIRS = { + components: { + path: path.join(__dirname, "../webview-ui/src/components"), + localesDir: path.join(__dirname, "../webview-ui/src/i18n/locales"), + }, + src: { + path: path.join(__dirname, "../src"), + localesDir: path.join(__dirname, "../src/i18n/locales"), + }, +} // Regular expressions to match i18n keys const i18nPatterns = [ @@ -45,15 +53,23 @@ const i18nPatterns = [ /t\("([a-zA-Z][a-zA-Z0-9_]*[:.][a-zA-Z0-9_.]+)"\)/g, // Match t("key") format, where key contains a colon or dot ] -// Get all language directories -function getLocaleDirs() { - const allLocales = fs.readdirSync(LOCALES_DIR).filter((file) => { - const stats = fs.statSync(path.join(LOCALES_DIR, file)) - return stats.isDirectory() // Do not exclude any language directories - }) +// Get all language directories for a specific locales directory +function getLocaleDirs(localesDir) { + try { + const allLocales = fs.readdirSync(localesDir).filter((file) => { + const stats = fs.statSync(path.join(localesDir, file)) + return stats.isDirectory() // Do not exclude any language directories + }) - // Filter to a specific language if specified - return args.locale ? allLocales.filter((locale) => locale === args.locale) : allLocales + // Filter to a specific language if specified + return args.locale ? allLocales.filter((locale) => locale === args.locale) : allLocales + } catch (error) { + if (error.code === "ENOENT") { + console.warn(`Warning: Locales directory not found: ${localesDir}`) + return [] + } + throw error + } } // Get the value from JSON by path @@ -72,14 +88,14 @@ function getValueByPath(obj, path) { } // Check if the key exists in all language files, return a list of missing language files -function checkKeyInLocales(key, localeDirs) { +function checkKeyInLocales(key, localeDirs, localesDir) { const [file, ...pathParts] = key.split(":") const jsonPath = pathParts.join(".") const missingLocales = [] localeDirs.forEach((locale) => { - const filePath = path.join(LOCALES_DIR, locale, `${file}.json`) + const filePath = path.join(localesDir, locale, `${file}.json`) if (!fs.existsSync(filePath)) { missingLocales.push(`${locale}/${file}.json`) return @@ -96,21 +112,20 @@ function checkKeyInLocales(key, localeDirs) { // Recursively traverse the directory function findMissingI18nKeys() { - const localeDirs = getLocaleDirs() const results = [] - function walk(dir) { + function walk(dir, baseDir, localeDirs, localesDir) { const files = fs.readdirSync(dir) for (const file of files) { const filePath = path.join(dir, file) const stat = fs.statSync(filePath) - // Exclude test files - if (filePath.includes(".test.")) continue + // Exclude test files and __mocks__ directory + if (filePath.includes(".test.") || filePath.includes("__mocks__")) continue if (stat.isDirectory()) { - walk(filePath) // Recursively traverse subdirectories + walk(filePath, baseDir, localeDirs, localesDir) // Recursively traverse subdirectories } else if (stat.isFile() && [".ts", ".tsx", ".js", ".jsx"].includes(path.extname(filePath))) { const content = fs.readFileSync(filePath, "utf8") @@ -119,12 +134,12 @@ function findMissingI18nKeys() { let match while ((match = pattern.exec(content)) !== null) { const key = match[1] - const missingLocales = checkKeyInLocales(key, localeDirs) + const missingLocales = checkKeyInLocales(key, localeDirs, localesDir) if (missingLocales.length > 0) { results.push({ key, missingLocales, - file: path.relative(TARGET_DIR, filePath), + file: path.relative(baseDir, filePath), }) } } @@ -133,20 +148,33 @@ function findMissingI18nKeys() { } } - walk(TARGET_DIR) + // Walk through all directories + Object.entries(DIRS).forEach(([name, config]) => { + const localeDirs = getLocaleDirs(config.localesDir) + if (localeDirs.length > 0) { + console.log(`\nChecking ${name} directory with ${localeDirs.length} languages: ${localeDirs.join(", ")}`) + walk(config.path, config.path, localeDirs, config.localesDir) + } + }) + return results } // Execute and output the results function main() { try { - const localeDirs = getLocaleDirs() - if (args.locale && localeDirs.length === 0) { - console.error(`Error: Language '${args.locale}' not found in ${LOCALES_DIR}`) - process.exit(1) - } + if (args.locale) { + // Check if the specified locale exists in any of the locales directories + const localeExists = Object.values(DIRS).some((config) => { + const localeDirs = getLocaleDirs(config.localesDir) + return localeDirs.includes(args.locale) + }) - console.log(`Checking ${localeDirs.length} non-English languages: ${localeDirs.join(", ")}`) + if (!localeExists) { + console.error(`Error: Language '${args.locale}' not found in any locales directory`) + process.exit(1) + } + } const missingKeys = findMissingI18nKeys() diff --git a/scripts/generate-types.mts b/scripts/generate-types.mts index c9bed9a003..2ad167b0d8 100644 --- a/scripts/generate-types.mts +++ b/scripts/generate-types.mts @@ -3,7 +3,8 @@ import fs from "fs/promises" import { zodToTs, createTypeAlias, printNode } from "zod-to-ts" import { $ } from "execa" -import { typeDefinitions } from "../src/schemas" +import schemas from "../src/schemas" +const { typeDefinitions } = schemas async function main() { const types: string[] = [ diff --git a/scripts/update-contributors.js b/scripts/update-contributors.js index b737ee5f0d..ab369853cf 100755 --- a/scripts/update-contributors.js +++ b/scripts/update-contributors.js @@ -8,8 +8,13 @@ const https = require("https") const fs = require("fs") +const { promisify } = require("util") const path = require("path") +// Promisify filesystem operations +const readFileAsync = promisify(fs.readFile) +const writeFileAsync = promisify(fs.writeFile) + // GitHub API URL for fetching contributors const GITHUB_API_URL = "https://api.github.com/repos/RooVetGit/Roo-Code/contributors?per_page=100" const README_PATH = path.join(__dirname, "..", "README.md") @@ -33,52 +38,144 @@ if (process.env.GITHUB_TOKEN) { } /** - * Fetches contributors data from GitHub API - * @returns {Promise} Array of contributor objects + * Parses the GitHub API Link header to extract pagination URLs + * Based on RFC 5988 format for the Link header + * @param {string} header The Link header from GitHub API response + * @returns {Object} Object containing URLs for next, prev, first, last pages (if available) */ -function fetchContributors() { +function parseLinkHeader(header) { + // Return empty object if no header is provided + if (!header || header.trim() === "") return {} + + // Initialize links object + const links = {} + + // Split the header into individual link entries + // Example: ; rel="next", ; rel="last" + const entries = header.split(/,\s*/) + + // Process each link entry + for (const entry of entries) { + // Extract the URL (between < and >) and the parameters (after >) + const segments = entry.split(";") + if (segments.length < 2) continue + + // Extract URL from the first segment, removing < and > + const urlMatch = segments[0].match(/<(.+)>/) + if (!urlMatch) continue + const url = urlMatch[1] + + // Find the rel="value" parameter + let rel = null + for (let i = 1; i < segments.length; i++) { + const relMatch = segments[i].match(/\s*rel\s*=\s*"?([^"]+)"?/) + if (relMatch) { + rel = relMatch[1] + break + } + } + + // Only add to links if both URL and rel were found + if (rel) { + links[rel] = url + } + } + + return links +} + +/** + * Performs an HTTP GET request and returns the response + * @param {string} url The URL to fetch + * @param {Object} options Request options + * @returns {Promise} Response object with status, headers and body + */ +function httpGet(url, options) { return new Promise((resolve, reject) => { https - .get(GITHUB_API_URL, options, (res) => { - if (res.statusCode !== 200) { - reject(new Error(`GitHub API request failed with status code: ${res.statusCode}`)) - return - } - + .get(url, options, (res) => { let data = "" res.on("data", (chunk) => { data += chunk }) res.on("end", () => { - try { - const contributors = JSON.parse(data) - resolve(contributors) - } catch (error) { - reject(new Error(`Failed to parse GitHub API response: ${error.message}`)) - } + resolve({ + statusCode: res.statusCode, + headers: res.headers, + body: data, + }) }) }) .on("error", (error) => { - reject(new Error(`GitHub API request failed: ${error.message}`)) + reject(error) }) }) } +/** + * Fetches a single page of contributors from GitHub API + * @param {string} url The API URL to fetch + * @returns {Promise} Object containing contributors and pagination links + */ +async function fetchContributorsPage(url) { + try { + // Make the HTTP request + const response = await httpGet(url, options) + + // Check for successful response + if (response.statusCode !== 200) { + throw new Error(`GitHub API request failed with status code: ${response.statusCode}`) + } + + // Parse the Link header for pagination + const linkHeader = response.headers.link + const links = parseLinkHeader(linkHeader) + + // Parse the JSON response + const contributors = JSON.parse(response.body) + + return { contributors, links } + } catch (error) { + throw new Error(`Failed to fetch contributors page: ${error.message}`) + } +} + +/** + * Fetches all contributors data from GitHub API (handling pagination) + * @returns {Promise} Array of all contributor objects + */ +async function fetchContributors() { + let allContributors = [] + let currentUrl = GITHUB_API_URL + let pageCount = 1 + + // Loop through all pages of contributors + while (currentUrl) { + console.log(`Fetching contributors page ${pageCount}...`) + const { contributors, links } = await fetchContributorsPage(currentUrl) + + allContributors = allContributors.concat(contributors) + + // Move to the next page if it exists + currentUrl = links.next + pageCount++ + } + + console.log(`Fetched ${allContributors.length} contributors from ${pageCount - 1} pages`) + return allContributors +} + /** * Reads the README.md file * @returns {Promise} README content */ -function readReadme() { - return new Promise((resolve, reject) => { - fs.readFile(README_PATH, "utf8", (err, data) => { - if (err) { - reject(new Error(`Failed to read README.md: ${err.message}`)) - return - } - resolve(data) - }) - }) +async function readReadme() { + try { + return await readFileAsync(README_PATH, "utf8") + } catch (err) { + throw new Error(`Failed to read README.md: ${err.message}`) + } } /** @@ -147,7 +244,7 @@ function formatContributorsSection(contributors) { * @param {string} contributorsSection HTML for contributors section * @returns {Promise} */ -function updateReadme(readmeContent, contributorsSection) { +async function updateReadme(readmeContent, contributorsSection) { // Find existing contributors section markers const startPos = readmeContent.indexOf(START_MARKER) const endPos = readmeContent.indexOf(END_MARKER) @@ -164,7 +261,7 @@ function updateReadme(readmeContent, contributorsSection) { // Ensure single newline separators between sections const updatedContent = beforeSection + "\n\n" + contributorsSection.trim() + "\n\n" + afterSection - return writeReadme(updatedContent) + await writeReadme(updatedContent) } /** @@ -172,47 +269,41 @@ function updateReadme(readmeContent, contributorsSection) { * @param {string} content Updated README content * @returns {Promise} */ -function writeReadme(content) { - return new Promise((resolve, reject) => { - fs.writeFile(README_PATH, content, "utf8", (err) => { - if (err) { - reject(new Error(`Failed to write updated README.md: ${err.message}`)) - return - } - resolve() - }) - }) +async function writeReadme(content) { + try { + await writeFileAsync(README_PATH, content, "utf8") + } catch (err) { + throw new Error(`Failed to write updated README.md: ${err.message}`) + } } /** * Finds all localized README files in the locales directory * @returns {Promise} Array of README file paths */ -function findLocalizedReadmes() { - return new Promise((resolve) => { - const readmeFiles = [] +async function findLocalizedReadmes() { + const readmeFiles = [] - // Check if locales directory exists - if (!fs.existsSync(LOCALES_DIR)) { - // No localized READMEs found - return resolve(readmeFiles) + // Check if locales directory exists + if (!fs.existsSync(LOCALES_DIR)) { + // No localized READMEs found + return readmeFiles + } + + // Get all language subdirectories + const languageDirs = fs + .readdirSync(LOCALES_DIR, { withFileTypes: true }) + .filter((dirent) => dirent.isDirectory()) + .map((dirent) => dirent.name) + + // Add all localized READMEs to the list + for (const langDir of languageDirs) { + const readmePath = path.join(LOCALES_DIR, langDir, "README.md") + if (fs.existsSync(readmePath)) { + readmeFiles.push(readmePath) } + } - // Get all language subdirectories - const languageDirs = fs - .readdirSync(LOCALES_DIR, { withFileTypes: true }) - .filter((dirent) => dirent.isDirectory()) - .map((dirent) => dirent.name) - - // Add all localized READMEs to the list - for (const langDir of languageDirs) { - const readmePath = path.join(LOCALES_DIR, langDir, "README.md") - if (fs.existsSync(readmePath)) { - readmeFiles.push(readmePath) - } - } - - resolve(readmeFiles) - }) + return readmeFiles } /** @@ -221,40 +312,33 @@ function findLocalizedReadmes() { * @param {string} contributorsSection HTML for contributors section * @returns {Promise} */ -function updateLocalizedReadme(filePath, contributorsSection) { - return new Promise((resolve, reject) => { - fs.readFile(filePath, "utf8", (err, readmeContent) => { - if (err) { - console.warn(`Warning: Could not read ${filePath}: ${err.message}`) - return resolve() - } +async function updateLocalizedReadme(filePath, contributorsSection) { + try { + // Read the file content + const readmeContent = await readFileAsync(filePath, "utf8") - // Find existing contributors section markers - const startPos = readmeContent.indexOf(START_MARKER) - const endPos = readmeContent.indexOf(END_MARKER) + // Find existing contributors section markers + const startPos = readmeContent.indexOf(START_MARKER) + const endPos = readmeContent.indexOf(END_MARKER) - if (startPos === -1 || endPos === -1) { - console.warn(`Warning: Could not find contributors section markers in ${filePath}`) - console.warn(`Skipping update for ${filePath}`) - return resolve() - } + if (startPos === -1 || endPos === -1) { + console.warn(`Warning: Could not find contributors section markers in ${filePath}`) + console.warn(`Skipping update for ${filePath}`) + return + } - // Replace existing section, trimming whitespace at section boundaries - const beforeSection = readmeContent.substring(0, startPos).trimEnd() - const afterSection = readmeContent.substring(endPos + END_MARKER.length).trimStart() - // Ensure single newline separators between sections - const updatedContent = beforeSection + "\n\n" + contributorsSection.trim() + "\n\n" + afterSection + // Replace existing section, trimming whitespace at section boundaries + const beforeSection = readmeContent.substring(0, startPos).trimEnd() + const afterSection = readmeContent.substring(endPos + END_MARKER.length).trimStart() + // Ensure single newline separators between sections + const updatedContent = beforeSection + "\n\n" + contributorsSection.trim() + "\n\n" + afterSection - fs.writeFile(filePath, updatedContent, "utf8", (writeErr) => { - if (writeErr) { - console.warn(`Warning: Failed to update ${filePath}: ${writeErr.message}`) - return resolve() - } - console.log(`Updated ${filePath}`) - resolve() - }) - }) - }) + // Write the updated content + await writeFileAsync(filePath, updatedContent, "utf8") + console.log(`Updated ${filePath}`) + } catch (err) { + console.warn(`Warning: Could not update ${filePath}: ${err.message}`) + } } /** @@ -262,9 +346,9 @@ function updateLocalizedReadme(filePath, contributorsSection) { */ async function main() { try { - // Fetch contributors from GitHub + // Fetch contributors from GitHub (now handles pagination) const contributors = await fetchContributors() - console.log(`Fetched ${contributors.length} contributors from GitHub`) + console.log(`Total contributors: ${contributors.length}`) // Generate contributors section const contributorsSection = formatContributorsSection(contributors) diff --git a/src/activate/registerCommands.ts b/src/activate/registerCommands.ts index e9491f9371..9559bbc596 100644 --- a/src/activate/registerCommands.ts +++ b/src/activate/registerCommands.ts @@ -50,6 +50,7 @@ export const registerCommands = (options: RegisterCommandOptions) => { const getCommandsMap = ({ context, outputChannel, provider }: RegisterCommandOptions) => { return { + "roo-cline.activationCompleted": () => {}, "roo-cline.plusButtonClicked": async () => { await provider.removeClineFromStack() await provider.postStateToWebview() @@ -94,7 +95,7 @@ const getCommandsMap = ({ context, outputChannel, provider }: RegisterCommandOpt } } -const openClineInNewTab = async ({ context, outputChannel }: Omit) => { +export const openClineInNewTab = async ({ context, outputChannel }: Omit) => { // (This example uses webviewProvider activation event which is necessary to // deserialize cached webview, but since we use retainContextWhenHidden, we // don't need to use that event). @@ -138,4 +139,6 @@ const openClineInNewTab = async ({ context, outputChannel }: Omit type UserContent = Array @@ -126,15 +122,15 @@ export class Cline extends EventEmitter { readonly rootTask: Cline | undefined = undefined readonly parentTask: Cline | undefined = undefined readonly taskNumber: number - private isPaused: boolean = false - private pausedModeSlug: string = defaultModeSlug + isPaused: boolean = false + pausedModeSlug: string = defaultModeSlug private pauseInterval: NodeJS.Timeout | undefined readonly apiConfiguration: ApiConfiguration api: ApiHandler private urlContentFetcher: UrlContentFetcher - private browserSession: BrowserSession - private didEditFile: boolean = false + browserSession: BrowserSession + didEditFile: boolean = false customInstructions?: string diffStrategy?: DiffStrategy diffEnabled: boolean = false @@ -146,16 +142,15 @@ export class Cline extends EventEmitter { private askResponse?: ClineAskResponse private askResponseText?: string private askResponseImages?: string[] - private lastMessageTs?: number // Not private since it needs to be accessible by tools consecutiveMistakeCount: number = 0 - private consecutiveMistakeCountForApplyDiff: Map = new Map() + consecutiveMistakeCountForApplyDiff: Map = new Map() // Not private since it needs to be accessible by tools providerRef: WeakRef private abort: boolean = false didFinishAbortingStream = false abandoned = false - private diffViewProvider: DiffViewProvider + diffViewProvider: DiffViewProvider private lastApiRequestTime?: number isInitialized = false @@ -171,9 +166,9 @@ export class Cline extends EventEmitter { private assistantMessageContent: AssistantMessageContent[] = [] private presentAssistantMessageLocked = false private presentAssistantMessageHasPendingUpdates = false - private userMessageContent: (Anthropic.TextBlockParam | Anthropic.ImageBlockParam)[] = [] + userMessageContent: (Anthropic.TextBlockParam | Anthropic.ImageBlockParam)[] = [] private userMessageContentReady = false - private didRejectTool = false + didRejectTool = false private didAlreadyUseTool = false private didCompleteReadingStream = false @@ -231,11 +226,8 @@ export class Cline extends EventEmitter { telemetryService.captureTaskCreated(this.taskId) } - // Initialize diffStrategy based on current state - this.updateDiffStrategy( - Experiments.isEnabled(experiments ?? {}, EXPERIMENT_IDS.DIFF_STRATEGY), - Experiments.isEnabled(experiments ?? {}, EXPERIMENT_IDS.MULTI_SEARCH_AND_REPLACE), - ) + // Initialize diffStrategy based on current state. + this.updateDiffStrategy(experiments ?? {}) onCreated?.(this) @@ -270,25 +262,13 @@ export class Cline extends EventEmitter { return getWorkspacePath(path.join(os.homedir(), "Desktop")) } - // Add method to update diffStrategy - async updateDiffStrategy(experimentalDiffStrategy?: boolean, multiSearchReplaceDiffStrategy?: boolean) { - // If not provided, get from current state - if (experimentalDiffStrategy === undefined || multiSearchReplaceDiffStrategy === undefined) { - const { experiments: stateExperimental } = (await this.providerRef.deref()?.getState()) ?? {} - if (experimentalDiffStrategy === undefined) { - experimentalDiffStrategy = stateExperimental?.[EXPERIMENT_IDS.DIFF_STRATEGY] ?? false - } - if (multiSearchReplaceDiffStrategy === undefined) { - multiSearchReplaceDiffStrategy = stateExperimental?.[EXPERIMENT_IDS.MULTI_SEARCH_AND_REPLACE] ?? false - } - } - - this.diffStrategy = getDiffStrategy( - this.api.getModel().id, - this.fuzzyMatchThreshold, - experimentalDiffStrategy, - multiSearchReplaceDiffStrategy, - ) + // Add method to update diffStrategy. + async updateDiffStrategy(experiments: Partial>) { + this.diffStrategy = getDiffStrategy({ + model: this.api.getModel().id, + experiments, + fuzzyMatchThreshold: this.fuzzyMatchThreshold, + }) } // Storing task to disk for history @@ -368,7 +348,7 @@ export class Cline extends EventEmitter { this.emit("message", { action: "updated", message: partialMessage }) } - private getTokenUsage() { + getTokenUsage() { const usage = getApiMetrics(combineApiRequests(combineCommandSequences(this.clineMessages.slice(1)))) this.emit("taskTokenUsageUpdated", this.taskId, usage) return usage @@ -460,7 +440,6 @@ export class Cline extends EventEmitter { // This is a new partial message, so add it with partial // state. askTs = Date.now() - this.lastMessageTs = askTs await this.addToClineMessages({ ts: askTs, type: "ask", ask: type, text, partial }) throw new Error("Current ask promise was ignored (#2)") } @@ -479,8 +458,6 @@ export class Cline extends EventEmitter { So in this case we must make sure that the message ts is never altered after first setting it. */ askTs = lastMessage.ts - this.lastMessageTs = askTs - // lastMessage.ts = askTs lastMessage.text = text lastMessage.partial = false lastMessage.progressStatus = progressStatus @@ -492,7 +469,6 @@ export class Cline extends EventEmitter { this.askResponseText = undefined this.askResponseImages = undefined askTs = Date.now() - this.lastMessageTs = askTs await this.addToClineMessages({ ts: askTs, type: "ask", ask: type, text }) } } @@ -502,18 +478,10 @@ export class Cline extends EventEmitter { this.askResponseText = undefined this.askResponseImages = undefined askTs = Date.now() - this.lastMessageTs = askTs await this.addToClineMessages({ ts: askTs, type: "ask", ask: type, text }) } - await pWaitFor(() => this.askResponse !== undefined || this.lastMessageTs !== askTs, { interval: 100 }) - - if (this.lastMessageTs !== askTs) { - // Could happen if we send multiple asks in a row i.e. with - // command_output. It's important that when we know an ask could - // fail, it is handled gracefully. - throw new Error("Current ask promise was ignored") - } + await pWaitFor(() => this.askResponse !== undefined, { interval: 100 }) const result = { response: this.askResponse!, text: this.askResponseText, images: this.askResponseImages } this.askResponse = undefined @@ -556,7 +524,6 @@ export class Cline extends EventEmitter { } else { // this is a new partial message, so add it with partial state const sayTs = Date.now() - this.lastMessageTs = sayTs await this.addToClineMessages({ ts: sayTs, type: "say", say: type, text, images, partial }) } } else { @@ -564,8 +531,6 @@ export class Cline extends EventEmitter { if (isUpdatingPreviousPartial) { // This is the complete version of a previously partial // message, so replace the partial with the complete version. - this.lastMessageTs = lastMessage.ts - // lastMessage.ts = sayTs lastMessage.text = text lastMessage.images = images lastMessage.partial = false @@ -578,14 +543,12 @@ export class Cline extends EventEmitter { } else { // This is a new and complete message, so add it like normal. const sayTs = Date.now() - this.lastMessageTs = sayTs await this.addToClineMessages({ ts: sayTs, type: "say", say: type, text, images }) } } } else { // this is a new non-partial message, so add it like normal const sayTs = Date.now() - this.lastMessageTs = sayTs await this.addToClineMessages({ ts: sayTs, type: "say", say: type, text, images, checkpoint }) } } @@ -1249,7 +1212,21 @@ export class Cline extends EventEmitter { } const baseDelay = requestDelaySeconds || 5 - const exponentialDelay = Math.ceil(baseDelay * Math.pow(2, retryAttempt)) + let exponentialDelay = Math.ceil(baseDelay * Math.pow(2, retryAttempt)) + + // If the error is a 429, and the error details contain a retry delay, use that delay instead of exponential backoff + if (error.status === 429) { + const geminiRetryDetails = error.errorDetails?.find( + (detail: any) => detail["@type"] === "type.googleapis.com/google.rpc.RetryInfo", + ) + if (geminiRetryDetails) { + const match = geminiRetryDetails?.retryDelay?.match(/^(\d+)s$/) + if (match) { + exponentialDelay = Number(match[1]) + 1 + } + } + } + // Wait for the greater of the exponential delay or the rate limit delay const finalDelay = Math.max(exponentialDelay, rateLimitDelay) @@ -1516,7 +1493,7 @@ export class Cline extends EventEmitter { } // If block is partial, remove partial closing tag so its not presented to user - const removeClosingTag = (tag: ToolParamName, text?: string) => { + const removeClosingTag = (tag: ToolParamName, text?: string): string => { if (!block.partial) { return text || "" } @@ -1563,1590 +1540,101 @@ export class Cline extends EventEmitter { } switch (block.name) { - case "write_to_file": { - const relPath: string | undefined = block.params.path - let newContent: string | undefined = block.params.content - let predictedLineCount: number | undefined = parseInt(block.params.line_count ?? "0") - if (!relPath || !newContent) { - // checking for newContent ensure relPath is complete - // wait so we can determine if it's a new file or editing an existing file - break - } - - const accessAllowed = this.rooIgnoreController?.validateAccess(relPath) - if (!accessAllowed) { - await this.say("rooignore_error", relPath) - pushToolResult(formatResponse.toolError(formatResponse.rooIgnoreError(relPath))) - - break - } - - // Check if file exists using cached map or fs.access - let fileExists: boolean - if (this.diffViewProvider.editType !== undefined) { - fileExists = this.diffViewProvider.editType === "modify" - } else { - const absolutePath = path.resolve(this.cwd, relPath) - fileExists = await fileExistsAtPath(absolutePath) - this.diffViewProvider.editType = fileExists ? "modify" : "create" - } - - // pre-processing newContent for cases where weaker models might add artifacts like markdown codeblock markers (deepseek/llama) or extra escape characters (gemini) - if (newContent.startsWith("```")) { - // this handles cases where it includes language specifiers like ```python ```js - newContent = newContent.split("\n").slice(1).join("\n").trim() - } - if (newContent.endsWith("```")) { - newContent = newContent.split("\n").slice(0, -1).join("\n").trim() - } - - if (!this.api.getModel().id.includes("claude")) { - // it seems not just llama models are doing this, but also gemini and potentially others - if ( - newContent.includes(">") || - newContent.includes("<") || - newContent.includes(""") - ) { - newContent = newContent - .replace(/>/g, ">") - .replace(/</g, "<") - .replace(/"/g, '"') - } - } - - // Determine if the path is outside the workspace - const fullPath = relPath ? path.resolve(this.cwd, removeClosingTag("path", relPath)) : "" - const isOutsideWorkspace = isPathOutsideWorkspace(fullPath) - - const sharedMessageProps: ClineSayTool = { - tool: fileExists ? "editedExistingFile" : "newFileCreated", - path: getReadablePath(this.cwd, removeClosingTag("path", relPath)), - isOutsideWorkspace, - } - try { - if (block.partial) { - // update gui message - const partialMessage = JSON.stringify(sharedMessageProps) - await this.ask("tool", partialMessage, block.partial).catch(() => {}) - // update editor - if (!this.diffViewProvider.isEditing) { - // open the editor and prepare to stream content in - await this.diffViewProvider.open(relPath) - } - // editor is open, stream content in - await this.diffViewProvider.update( - everyLineHasLineNumbers(newContent) ? stripLineNumbers(newContent) : newContent, - false, - ) - break - } else { - if (!relPath) { - this.consecutiveMistakeCount++ - pushToolResult(await this.sayAndCreateMissingParamError("write_to_file", "path")) - await this.diffViewProvider.reset() - break - } - if (!newContent) { - this.consecutiveMistakeCount++ - pushToolResult(await this.sayAndCreateMissingParamError("write_to_file", "content")) - await this.diffViewProvider.reset() - break - } - if (!predictedLineCount) { - this.consecutiveMistakeCount++ - pushToolResult( - await this.sayAndCreateMissingParamError("write_to_file", "line_count"), - ) - await this.diffViewProvider.reset() - break - } - this.consecutiveMistakeCount = 0 - - // if isEditingFile false, that means we have the full contents of the file already. - // it's important to note how this function works, you can't make the assumption that the block.partial conditional will always be called since it may immediately get complete, non-partial data. So this part of the logic will always be called. - // in other words, you must always repeat the block.partial logic here - if (!this.diffViewProvider.isEditing) { - // show gui message before showing edit animation - const partialMessage = JSON.stringify(sharedMessageProps) - await this.ask("tool", partialMessage, true).catch(() => {}) // sending true for partial even though it's not a partial, this shows the edit row before the content is streamed into the editor - await this.diffViewProvider.open(relPath) - } - await this.diffViewProvider.update( - everyLineHasLineNumbers(newContent) ? stripLineNumbers(newContent) : newContent, - true, - ) - await delay(300) // wait for diff view to update - this.diffViewProvider.scrollToFirstDiff() - - // Check for code omissions before proceeding - if ( - detectCodeOmission( - this.diffViewProvider.originalContent || "", - newContent, - predictedLineCount, - ) - ) { - if (this.diffStrategy) { - await this.diffViewProvider.revertChanges() - pushToolResult( - formatResponse.toolError( - `Content appears to be truncated (file has ${ - newContent.split("\n").length - } lines but was predicted to have ${predictedLineCount} lines), and found comments indicating omitted code (e.g., '// rest of code unchanged', '/* previous code */'). Please provide the complete file content without any omissions if possible, or otherwise use the 'apply_diff' tool to apply the diff to the original file.`, - ), - ) - break - } else { - vscode.window - .showWarningMessage( - "Potential code truncation detected. This happens when the AI reaches its max output limit.", - "Follow this guide to fix the issue", - ) - .then((selection) => { - if (selection === "Follow this guide to fix the issue") { - vscode.env.openExternal( - vscode.Uri.parse( - "https://github.com/cline/cline/wiki/Troubleshooting-%E2%80%90-Cline-Deleting-Code-with-%22Rest-of-Code-Here%22-Comments", - ), - ) - } - }) - } - } - - const completeMessage = JSON.stringify({ - ...sharedMessageProps, - content: fileExists ? undefined : newContent, - diff: fileExists - ? formatResponse.createPrettyPatch( - relPath, - this.diffViewProvider.originalContent, - newContent, - ) - : undefined, - } satisfies ClineSayTool) - const didApprove = await askApproval("tool", completeMessage) - if (!didApprove) { - await this.diffViewProvider.revertChanges() - break - } - const { newProblemsMessage, userEdits, finalContent } = - await this.diffViewProvider.saveChanges() - this.didEditFile = true // used to determine if we should wait for busy terminal to update before sending api request - if (userEdits) { - await this.say( - "user_feedback_diff", - JSON.stringify({ - tool: fileExists ? "editedExistingFile" : "newFileCreated", - path: getReadablePath(this.cwd, relPath), - diff: userEdits, - } satisfies ClineSayTool), - ) - pushToolResult( - `The user made the following updates to your content:\n\n${userEdits}\n\n` + - `The updated content, which includes both your original modifications and the user's edits, has been successfully saved to ${relPath.toPosix()}. Here is the full, updated content of the file, including line numbers:\n\n` + - `\n${addLineNumbers( - finalContent || "", - )}\n\n\n` + - `Please note:\n` + - `1. You do not need to re-write the file with these changes, as they have already been applied.\n` + - `2. Proceed with the task using this updated file content as the new baseline.\n` + - `3. If the user's edits have addressed part of the task or changed the requirements, adjust your approach accordingly.` + - `${newProblemsMessage}`, - ) - } else { - pushToolResult( - `The content was successfully saved to ${relPath.toPosix()}.${newProblemsMessage}`, - ) - } - await this.diffViewProvider.reset() - break - } - } catch (error) { - await handleError("writing file", error) - await this.diffViewProvider.reset() - break - } - } - case "apply_diff": { - const relPath: string | undefined = block.params.path - const diffContent: string | undefined = block.params.diff - - const sharedMessageProps: ClineSayTool = { - tool: "appliedDiff", - path: getReadablePath(this.cwd, removeClosingTag("path", relPath)), - } - - try { - if (block.partial) { - // update gui message - let toolProgressStatus - if (this.diffStrategy && this.diffStrategy.getProgressStatus) { - toolProgressStatus = this.diffStrategy.getProgressStatus(block) - } - - const partialMessage = JSON.stringify(sharedMessageProps) - - await this.ask("tool", partialMessage, block.partial, toolProgressStatus).catch( - () => {}, - ) - break - } else { - if (!relPath) { - this.consecutiveMistakeCount++ - pushToolResult(await this.sayAndCreateMissingParamError("apply_diff", "path")) - break - } - if (!diffContent) { - this.consecutiveMistakeCount++ - pushToolResult(await this.sayAndCreateMissingParamError("apply_diff", "diff")) - break - } - - const accessAllowed = this.rooIgnoreController?.validateAccess(relPath) - if (!accessAllowed) { - await this.say("rooignore_error", relPath) - pushToolResult(formatResponse.toolError(formatResponse.rooIgnoreError(relPath))) - - break - } - - const absolutePath = path.resolve(this.cwd, relPath) - const fileExists = await fileExistsAtPath(absolutePath) - - if (!fileExists) { - this.consecutiveMistakeCount++ - const formattedError = `File does not exist at path: ${absolutePath}\n\n\nThe specified file could not be found. Please verify the file path and try again.\n` - await this.say("error", formattedError) - pushToolResult(formattedError) - break - } - - const originalContent = await fs.readFile(absolutePath, "utf-8") - - // Apply the diff to the original content - const diffResult = (await this.diffStrategy?.applyDiff( - originalContent, - diffContent, - parseInt(block.params.start_line ?? ""), - parseInt(block.params.end_line ?? ""), - )) ?? { - success: false, - error: "No diff strategy available", - } - let partResults = "" - - if (!diffResult.success) { - this.consecutiveMistakeCount++ - const currentCount = - (this.consecutiveMistakeCountForApplyDiff.get(relPath) || 0) + 1 - this.consecutiveMistakeCountForApplyDiff.set(relPath, currentCount) - let formattedError = "" - if (diffResult.failParts && diffResult.failParts.length > 0) { - for (const failPart of diffResult.failParts) { - if (failPart.success) { - continue - } - const errorDetails = failPart.details - ? JSON.stringify(failPart.details, null, 2) - : "" - formattedError = `\n${ - failPart.error - }${errorDetails ? `\n\nDetails:\n${errorDetails}` : ""}\n` - partResults += formattedError - } - } else { - const errorDetails = diffResult.details - ? JSON.stringify(diffResult.details, null, 2) - : "" - formattedError = `Unable to apply diff to file: ${absolutePath}\n\n\n${ - diffResult.error - }${errorDetails ? `\n\nDetails:\n${errorDetails}` : ""}\n` - } - - if (currentCount >= 2) { - await this.say("error", formattedError) - } - pushToolResult(formattedError) - break - } - - this.consecutiveMistakeCount = 0 - this.consecutiveMistakeCountForApplyDiff.delete(relPath) - // Show diff view before asking for approval - this.diffViewProvider.editType = "modify" - await this.diffViewProvider.open(relPath) - await this.diffViewProvider.update(diffResult.content, true) - await this.diffViewProvider.scrollToFirstDiff() - - const completeMessage = JSON.stringify({ - ...sharedMessageProps, - diff: diffContent, - } satisfies ClineSayTool) - - let toolProgressStatus - if (this.diffStrategy && this.diffStrategy.getProgressStatus) { - toolProgressStatus = this.diffStrategy.getProgressStatus(block, diffResult) - } - - const didApprove = await askApproval("tool", completeMessage, toolProgressStatus) - if (!didApprove) { - await this.diffViewProvider.revertChanges() // This likely handles closing the diff view - break - } - - const { newProblemsMessage, userEdits, finalContent } = - await this.diffViewProvider.saveChanges() - this.didEditFile = true // used to determine if we should wait for busy terminal to update before sending api request - let partFailHint = "" - if (diffResult.failParts && diffResult.failParts.length > 0) { - partFailHint = `But unable to apply all diff parts to file: ${absolutePath}, silently use tool to check newest file version and re-apply diffs\n` - } - if (userEdits) { - await this.say( - "user_feedback_diff", - JSON.stringify({ - tool: fileExists ? "editedExistingFile" : "newFileCreated", - path: getReadablePath(this.cwd, relPath), - diff: userEdits, - } satisfies ClineSayTool), - ) - pushToolResult( - `The user made the following updates to your content:\n\n${userEdits}\n\n` + - partFailHint + - `The updated content, which includes both your original modifications and the user's edits, has been successfully saved to ${relPath.toPosix()}. Here is the full, updated content of the file, including line numbers:\n\n` + - `\n${addLineNumbers( - finalContent || "", - )}\n\n\n` + - `Please note:\n` + - `1. You do not need to re-write the file with these changes, as they have already been applied.\n` + - `2. Proceed with the task using this updated file content as the new baseline.\n` + - `3. If the user's edits have addressed part of the task or changed the requirements, adjust your approach accordingly.` + - `${newProblemsMessage}`, - ) - } else { - pushToolResult( - `Changes successfully applied to ${relPath.toPosix()}:\n\n${newProblemsMessage}\n` + - partFailHint, - ) - } - await this.diffViewProvider.reset() - break - } - } catch (error) { - await handleError("applying diff", error) - await this.diffViewProvider.reset() - break - } - } - - case "insert_content": { - const relPath: string | undefined = block.params.path - const operations: string | undefined = block.params.operations - - const sharedMessageProps: ClineSayTool = { - tool: "appliedDiff", - path: getReadablePath(this.cwd, removeClosingTag("path", relPath)), - } - - try { - if (block.partial) { - const partialMessage = JSON.stringify(sharedMessageProps) - await this.ask("tool", partialMessage, block.partial).catch(() => {}) - break - } - - // Validate required parameters - if (!relPath) { - this.consecutiveMistakeCount++ - pushToolResult(await this.sayAndCreateMissingParamError("insert_content", "path")) - break - } - - if (!operations) { - this.consecutiveMistakeCount++ - pushToolResult(await this.sayAndCreateMissingParamError("insert_content", "operations")) - break - } - - const absolutePath = path.resolve(this.cwd, relPath) - const fileExists = await fileExistsAtPath(absolutePath) - - if (!fileExists) { - this.consecutiveMistakeCount++ - const formattedError = `File does not exist at path: ${absolutePath}\n\n\nThe specified file could not be found. Please verify the file path and try again.\n` - await this.say("error", formattedError) - pushToolResult(formattedError) - break - } - - let parsedOperations: Array<{ - start_line: number - content: string - }> - - try { - parsedOperations = JSON.parse(operations) - if (!Array.isArray(parsedOperations)) { - throw new Error("Operations must be an array") - } - } catch (error) { - this.consecutiveMistakeCount++ - await this.say("error", `Failed to parse operations JSON: ${error.message}`) - pushToolResult(formatResponse.toolError("Invalid operations JSON format")) - break - } - - this.consecutiveMistakeCount = 0 - - // Read the file - const fileContent = await fs.readFile(absolutePath, "utf8") - this.diffViewProvider.editType = "modify" - this.diffViewProvider.originalContent = fileContent - const lines = fileContent.split("\n") - - const updatedContent = insertGroups( - lines, - parsedOperations.map((elem) => { - return { - index: elem.start_line - 1, - elements: elem.content.split("\n"), - } - }), - ).join("\n") - - // Show changes in diff view - if (!this.diffViewProvider.isEditing) { - await this.ask("tool", JSON.stringify(sharedMessageProps), true).catch(() => {}) - // First open with original content - await this.diffViewProvider.open(relPath) - await this.diffViewProvider.update(fileContent, false) - this.diffViewProvider.scrollToFirstDiff() - await delay(200) - } - - const diff = formatResponse.createPrettyPatch(relPath, fileContent, updatedContent) - - if (!diff) { - pushToolResult(`No changes needed for '${relPath}'`) - break - } - - await this.diffViewProvider.update(updatedContent, true) - - const completeMessage = JSON.stringify({ - ...sharedMessageProps, - diff, - } satisfies ClineSayTool) - - const didApprove = await this.ask("tool", completeMessage, false).then( - (response) => response.response === "yesButtonClicked", - ) - - if (!didApprove) { - await this.diffViewProvider.revertChanges() - pushToolResult("Changes were rejected by the user.") - break - } - - const { newProblemsMessage, userEdits, finalContent } = - await this.diffViewProvider.saveChanges() - this.didEditFile = true - - if (!userEdits) { - pushToolResult( - `The content was successfully inserted in ${relPath.toPosix()}.${newProblemsMessage}`, - ) - await this.diffViewProvider.reset() - break - } - - const userFeedbackDiff = JSON.stringify({ - tool: "appliedDiff", - path: getReadablePath(this.cwd, relPath), - diff: userEdits, - } satisfies ClineSayTool) - - console.debug("[DEBUG] User made edits, sending feedback diff:", userFeedbackDiff) - await this.say("user_feedback_diff", userFeedbackDiff) - pushToolResult( - `The user made the following updates to your content:\n\n${userEdits}\n\n` + - `The updated content, which includes both your original modifications and the user's edits, has been successfully saved to ${relPath.toPosix()}. Here is the full, updated content of the file:\n\n` + - `\n${finalContent}\n\n\n` + - `Please note:\n` + - `1. You do not need to re-write the file with these changes, as they have already been applied.\n` + - `2. Proceed with the task using this updated file content as the new baseline.\n` + - `3. If the user's edits have addressed part of the task or changed the requirements, adjust your approach accordingly.` + - `${newProblemsMessage}`, - ) - await this.diffViewProvider.reset() - } catch (error) { - handleError("insert content", error) - await this.diffViewProvider.reset() - } + case "write_to_file": + await writeToFileTool(this, block, askApproval, handleError, pushToolResult, removeClosingTag) break - } - - case "search_and_replace": { - const relPath: string | undefined = block.params.path - const operations: string | undefined = block.params.operations - - const sharedMessageProps: ClineSayTool = { - tool: "appliedDiff", - path: getReadablePath(this.cwd, removeClosingTag("path", relPath)), - } - - try { - if (block.partial) { - const partialMessage = JSON.stringify({ - path: removeClosingTag("path", relPath), - operations: removeClosingTag("operations", operations), - }) - await this.ask("tool", partialMessage, block.partial).catch(() => {}) - break - } else { - if (!relPath) { - this.consecutiveMistakeCount++ - pushToolResult( - await this.sayAndCreateMissingParamError("search_and_replace", "path"), - ) - break - } - if (!operations) { - this.consecutiveMistakeCount++ - pushToolResult( - await this.sayAndCreateMissingParamError("search_and_replace", "operations"), - ) - break - } - - const absolutePath = path.resolve(this.cwd, relPath) - const fileExists = await fileExistsAtPath(absolutePath) - - if (!fileExists) { - this.consecutiveMistakeCount++ - const formattedError = `File does not exist at path: ${absolutePath}\n\n\nThe specified file could not be found. Please verify the file path and try again.\n` - await this.say("error", formattedError) - pushToolResult(formattedError) - break - } - - let parsedOperations: Array<{ - search: string - replace: string - start_line?: number - end_line?: number - use_regex?: boolean - ignore_case?: boolean - regex_flags?: string - }> - - try { - parsedOperations = JSON.parse(operations) - if (!Array.isArray(parsedOperations)) { - throw new Error("Operations must be an array") - } - } catch (error) { - this.consecutiveMistakeCount++ - await this.say("error", `Failed to parse operations JSON: ${error.message}`) - pushToolResult(formatResponse.toolError("Invalid operations JSON format")) - break - } - - // Read the original file content - const fileContent = await fs.readFile(absolutePath, "utf-8") - this.diffViewProvider.editType = "modify" - this.diffViewProvider.originalContent = fileContent - let lines = fileContent.split("\n") - - for (const op of parsedOperations) { - const flags = op.regex_flags ?? (op.ignore_case ? "gi" : "g") - const multilineFlags = flags.includes("m") ? flags : flags + "m" - - const searchPattern = op.use_regex - ? new RegExp(op.search, multilineFlags) - : new RegExp(escapeRegExp(op.search), multilineFlags) - - if (op.start_line || op.end_line) { - const startLine = Math.max((op.start_line ?? 1) - 1, 0) - const endLine = Math.min((op.end_line ?? lines.length) - 1, lines.length - 1) - - // Get the content before and after the target section - const beforeLines = lines.slice(0, startLine) - const afterLines = lines.slice(endLine + 1) - - // Get the target section and perform replacement - const targetContent = lines.slice(startLine, endLine + 1).join("\n") - const modifiedContent = targetContent.replace(searchPattern, op.replace) - const modifiedLines = modifiedContent.split("\n") - - // Reconstruct the full content with the modified section - lines = [...beforeLines, ...modifiedLines, ...afterLines] - } else { - // Global replacement - const fullContent = lines.join("\n") - const modifiedContent = fullContent.replace(searchPattern, op.replace) - lines = modifiedContent.split("\n") - } - } - - const newContent = lines.join("\n") - - this.consecutiveMistakeCount = 0 - - // Show diff preview - const diff = formatResponse.createPrettyPatch(relPath, fileContent, newContent) - - if (!diff) { - pushToolResult(`No changes needed for '${relPath}'`) - break - } - - await this.diffViewProvider.open(relPath) - await this.diffViewProvider.update(newContent, true) - this.diffViewProvider.scrollToFirstDiff() - - const completeMessage = JSON.stringify({ - ...sharedMessageProps, - diff: diff, - } satisfies ClineSayTool) - - const didApprove = await askApproval("tool", completeMessage) - if (!didApprove) { - await this.diffViewProvider.revertChanges() // This likely handles closing the diff view - break - } - - const { newProblemsMessage, userEdits, finalContent } = - await this.diffViewProvider.saveChanges() - this.didEditFile = true // used to determine if we should wait for busy terminal to update before sending api request - if (userEdits) { - await this.say( - "user_feedback_diff", - JSON.stringify({ - tool: fileExists ? "editedExistingFile" : "newFileCreated", - path: getReadablePath(this.cwd, relPath), - diff: userEdits, - } satisfies ClineSayTool), - ) - pushToolResult( - `The user made the following updates to your content:\n\n${userEdits}\n\n` + - `The updated content, which includes both your original modifications and the user's edits, has been successfully saved to ${relPath.toPosix()}. Here is the full, updated content of the file, including line numbers:\n\n` + - `\n${addLineNumbers(finalContent || "")}\n\n\n` + - `Please note:\n` + - `1. You do not need to re-write the file with these changes, as they have already been applied.\n` + - `2. Proceed with the task using this updated file content as the new baseline.\n` + - `3. If the user's edits have addressed part of the task or changed the requirements, adjust your approach accordingly.` + - `${newProblemsMessage}`, - ) - } else { - pushToolResult( - `Changes successfully applied to ${relPath.toPosix()}:\n\n${newProblemsMessage}`, - ) - } - await this.diffViewProvider.reset() - break - } - } catch (error) { - await handleError("applying search and replace", error) - await this.diffViewProvider.reset() - break - } - } - - case "read_file": { - readFileTool(this, block, askApproval, handleError, pushToolResult, removeClosingTag) + case "apply_diff": + await applyDiffTool(this, block, askApproval, handleError, pushToolResult, removeClosingTag) break - } - - case "fetch_instructions": { - fetchInstructionsTool(this, block, askApproval, handleError, pushToolResult) + case "insert_content": + await insertContentTool(this, block, askApproval, handleError, pushToolResult, removeClosingTag) + break + case "search_and_replace": + await searchAndReplaceTool( + this, + block, + askApproval, + handleError, + pushToolResult, + removeClosingTag, + ) + break + case "read_file": + await readFileTool(this, block, askApproval, handleError, pushToolResult, removeClosingTag) + break + case "fetch_instructions": + await fetchInstructionsTool(this, block, askApproval, handleError, pushToolResult) + break + case "list_files": + await listFilesTool(this, block, askApproval, handleError, pushToolResult, removeClosingTag) + break + case "list_code_definition_names": + await listCodeDefinitionNamesTool( + this, + block, + askApproval, + handleError, + pushToolResult, + removeClosingTag, + ) + break + case "search_files": + await searchFilesTool(this, block, askApproval, handleError, pushToolResult, removeClosingTag) + break + case "browser_action": + await browserActionTool(this, block, askApproval, handleError, pushToolResult, removeClosingTag) + break + case "execute_command": + await executeCommandTool( + this, + block, + askApproval, + handleError, + pushToolResult, + removeClosingTag, + ) + break + case "use_mcp_tool": + await useMcpToolTool(this, block, askApproval, handleError, pushToolResult, removeClosingTag) + break + case "access_mcp_resource": + await accessMcpResourceTool( + this, + block, + askApproval, + handleError, + pushToolResult, + removeClosingTag, + ) + break + case "ask_followup_question": + await askFollowupQuestionTool( + this, + block, + askApproval, + handleError, + pushToolResult, + removeClosingTag, + ) + break + case "switch_mode": + await switchModeTool(this, block, askApproval, handleError, pushToolResult, removeClosingTag) + break + case "new_task": + await newTaskTool(this, block, askApproval, handleError, pushToolResult, removeClosingTag) + break + case "attempt_completion": + await attemptCompletionTool( + this, + block, + askApproval, + handleError, + pushToolResult, + removeClosingTag, + toolDescription, + askFinishSubTaskApproval, + ) break - } - - case "list_files": { - const relDirPath: string | undefined = block.params.path - const recursiveRaw: string | undefined = block.params.recursive - const recursive = recursiveRaw?.toLowerCase() === "true" - const sharedMessageProps: ClineSayTool = { - tool: !recursive ? "listFilesTopLevel" : "listFilesRecursive", - path: getReadablePath(this.cwd, removeClosingTag("path", relDirPath)), - } - try { - if (block.partial) { - const partialMessage = JSON.stringify({ - ...sharedMessageProps, - content: "", - } satisfies ClineSayTool) - await this.ask("tool", partialMessage, block.partial).catch(() => {}) - break - } else { - if (!relDirPath) { - this.consecutiveMistakeCount++ - pushToolResult(await this.sayAndCreateMissingParamError("list_files", "path")) - break - } - this.consecutiveMistakeCount = 0 - const absolutePath = path.resolve(this.cwd, relDirPath) - const [files, didHitLimit] = await listFiles(absolutePath, recursive, 200) - const { showRooIgnoredFiles = true } = - (await this.providerRef.deref()?.getState()) ?? {} - const result = formatResponse.formatFilesList( - absolutePath, - files, - didHitLimit, - this.rooIgnoreController, - showRooIgnoredFiles, - ) - const completeMessage = JSON.stringify({ - ...sharedMessageProps, - content: result, - } satisfies ClineSayTool) - const didApprove = await askApproval("tool", completeMessage) - if (!didApprove) { - break - } - pushToolResult(result) - break - } - } catch (error) { - await handleError("listing files", error) - break - } - } - case "list_code_definition_names": { - const relPath: string | undefined = block.params.path - const sharedMessageProps: ClineSayTool = { - tool: "listCodeDefinitionNames", - path: getReadablePath(this.cwd, removeClosingTag("path", relPath)), - } - try { - if (block.partial) { - const partialMessage = JSON.stringify({ - ...sharedMessageProps, - content: "", - } satisfies ClineSayTool) - await this.ask("tool", partialMessage, block.partial).catch(() => {}) - break - } else { - if (!relPath) { - this.consecutiveMistakeCount++ - pushToolResult( - await this.sayAndCreateMissingParamError("list_code_definition_names", "path"), - ) - break - } - this.consecutiveMistakeCount = 0 - const absolutePath = path.resolve(this.cwd, relPath) - let result: string - try { - const stats = await fs.stat(absolutePath) - if (stats.isFile()) { - const fileResult = await parseSourceCodeDefinitionsForFile( - absolutePath, - this.rooIgnoreController, - ) - result = fileResult ?? "No source code definitions found in this file." - } else if (stats.isDirectory()) { - result = await parseSourceCodeForDefinitionsTopLevel( - absolutePath, - this.rooIgnoreController, - ) - } else { - result = "The specified path is neither a file nor a directory." - } - } catch { - result = `${absolutePath}: does not exist or cannot be accessed.` - } - const completeMessage = JSON.stringify({ - ...sharedMessageProps, - content: result, - } satisfies ClineSayTool) - const didApprove = await askApproval("tool", completeMessage) - if (!didApprove) { - break - } - pushToolResult(result) - break - } - } catch (error) { - await handleError("parsing source code definitions", error) - break - } - } - case "search_files": { - const relDirPath: string | undefined = block.params.path - const regex: string | undefined = block.params.regex - const filePattern: string | undefined = block.params.file_pattern - const sharedMessageProps: ClineSayTool = { - tool: "searchFiles", - path: getReadablePath(this.cwd, removeClosingTag("path", relDirPath)), - regex: removeClosingTag("regex", regex), - filePattern: removeClosingTag("file_pattern", filePattern), - } - try { - if (block.partial) { - const partialMessage = JSON.stringify({ - ...sharedMessageProps, - content: "", - } satisfies ClineSayTool) - await this.ask("tool", partialMessage, block.partial).catch(() => {}) - break - } else { - if (!relDirPath) { - this.consecutiveMistakeCount++ - pushToolResult(await this.sayAndCreateMissingParamError("search_files", "path")) - break - } - if (!regex) { - this.consecutiveMistakeCount++ - pushToolResult(await this.sayAndCreateMissingParamError("search_files", "regex")) - break - } - this.consecutiveMistakeCount = 0 - const absolutePath = path.resolve(this.cwd, relDirPath) - const results = await regexSearchFiles( - this.cwd, - absolutePath, - regex, - filePattern, - this.rooIgnoreController, - ) - const completeMessage = JSON.stringify({ - ...sharedMessageProps, - content: results, - } satisfies ClineSayTool) - const didApprove = await askApproval("tool", completeMessage) - if (!didApprove) { - break - } - pushToolResult(results) - break - } - } catch (error) { - await handleError("searching files", error) - break - } - } - case "browser_action": { - const action: BrowserAction | undefined = block.params.action as BrowserAction - const url: string | undefined = block.params.url - const coordinate: string | undefined = block.params.coordinate - const text: string | undefined = block.params.text - if (!action || !browserActions.includes(action)) { - // checking for action to ensure it is complete and valid - if (!block.partial) { - // if the block is complete and we don't have a valid action this is a mistake - this.consecutiveMistakeCount++ - pushToolResult(await this.sayAndCreateMissingParamError("browser_action", "action")) - await this.browserSession.closeBrowser() - } - break - } - - try { - if (block.partial) { - if (action === "launch") { - await this.ask( - "browser_action_launch", - removeClosingTag("url", url), - block.partial, - ).catch(() => {}) - } else { - await this.say( - "browser_action", - JSON.stringify({ - action: action as BrowserAction, - coordinate: removeClosingTag("coordinate", coordinate), - text: removeClosingTag("text", text), - } satisfies ClineSayBrowserAction), - undefined, - block.partial, - ) - } - break - } else { - let browserActionResult: BrowserActionResult - if (action === "launch") { - if (!url) { - this.consecutiveMistakeCount++ - pushToolResult( - await this.sayAndCreateMissingParamError("browser_action", "url"), - ) - await this.browserSession.closeBrowser() - break - } - this.consecutiveMistakeCount = 0 - const didApprove = await askApproval("browser_action_launch", url) - if (!didApprove) { - break - } - - // NOTE: it's okay that we call this message since the partial inspect_site is finished streaming. The only scenario we have to avoid is sending messages WHILE a partial message exists at the end of the messages array. For example the api_req_finished message would interfere with the partial message, so we needed to remove that. - // await this.say("inspect_site_result", "") // no result, starts the loading spinner waiting for result - await this.say("browser_action_result", "") // starts loading spinner - - await this.browserSession.launchBrowser() - browserActionResult = await this.browserSession.navigateToUrl(url) - } else { - if (action === "click") { - if (!coordinate) { - this.consecutiveMistakeCount++ - pushToolResult( - await this.sayAndCreateMissingParamError( - "browser_action", - "coordinate", - ), - ) - await this.browserSession.closeBrowser() - break // can't be within an inner switch - } - } - if (action === "type") { - if (!text) { - this.consecutiveMistakeCount++ - pushToolResult( - await this.sayAndCreateMissingParamError("browser_action", "text"), - ) - await this.browserSession.closeBrowser() - break - } - } - this.consecutiveMistakeCount = 0 - await this.say( - "browser_action", - JSON.stringify({ - action: action as BrowserAction, - coordinate, - text, - } satisfies ClineSayBrowserAction), - undefined, - false, - ) - switch (action) { - case "click": - browserActionResult = await this.browserSession.click(coordinate!) - break - case "type": - browserActionResult = await this.browserSession.type(text!) - break - case "scroll_down": - browserActionResult = await this.browserSession.scrollDown() - break - case "scroll_up": - browserActionResult = await this.browserSession.scrollUp() - break - case "close": - browserActionResult = await this.browserSession.closeBrowser() - break - } - } - - switch (action) { - case "launch": - case "click": - case "type": - case "scroll_down": - case "scroll_up": - await this.say("browser_action_result", JSON.stringify(browserActionResult)) - pushToolResult( - formatResponse.toolResult( - `The browser action has been executed. The console logs and screenshot have been captured for your analysis.\n\nConsole logs:\n${ - browserActionResult.logs || "(No new logs)" - }\n\n(REMEMBER: if you need to proceed to using non-\`browser_action\` tools or launch a new browser, you MUST first close this browser. For example, if after analyzing the logs and screenshot you need to edit a file, you must first close the browser before you can use the write_to_file tool.)`, - browserActionResult.screenshot ? [browserActionResult.screenshot] : [], - ), - ) - break - case "close": - pushToolResult( - formatResponse.toolResult( - `The browser has been closed. You may now proceed to using other tools.`, - ), - ) - break - } - break - } - } catch (error) { - await this.browserSession.closeBrowser() // if any error occurs, the browser session is terminated - await handleError("executing browser action", error) - break - } - } - case "execute_command": { - const command: string | undefined = block.params.command - const customCwd: string | undefined = block.params.cwd - try { - if (block.partial) { - await this.ask("command", removeClosingTag("command", command), block.partial).catch( - () => {}, - ) - break - } else { - if (!command) { - this.consecutiveMistakeCount++ - pushToolResult( - await this.sayAndCreateMissingParamError("execute_command", "command"), - ) - break - } - - const ignoredFileAttemptedToAccess = this.rooIgnoreController?.validateCommand(command) - if (ignoredFileAttemptedToAccess) { - await this.say("rooignore_error", ignoredFileAttemptedToAccess) - pushToolResult( - formatResponse.toolError( - formatResponse.rooIgnoreError(ignoredFileAttemptedToAccess), - ), - ) - - break - } - - this.consecutiveMistakeCount = 0 - - const didApprove = await askApproval("command", command) - if (!didApprove) { - break - } - const [userRejected, result] = await this.executeCommandTool(command, customCwd) - if (userRejected) { - this.didRejectTool = true - } - pushToolResult(result) - break - } - } catch (error) { - await handleError("executing command", error) - break - } - } - case "use_mcp_tool": { - const server_name: string | undefined = block.params.server_name - const tool_name: string | undefined = block.params.tool_name - const mcp_arguments: string | undefined = block.params.arguments - try { - if (block.partial) { - const partialMessage = JSON.stringify({ - type: "use_mcp_tool", - serverName: removeClosingTag("server_name", server_name), - toolName: removeClosingTag("tool_name", tool_name), - arguments: removeClosingTag("arguments", mcp_arguments), - } satisfies ClineAskUseMcpServer) - await this.ask("use_mcp_server", partialMessage, block.partial).catch(() => {}) - break - } else { - if (!server_name) { - this.consecutiveMistakeCount++ - pushToolResult( - await this.sayAndCreateMissingParamError("use_mcp_tool", "server_name"), - ) - break - } - if (!tool_name) { - this.consecutiveMistakeCount++ - pushToolResult( - await this.sayAndCreateMissingParamError("use_mcp_tool", "tool_name"), - ) - break - } - // arguments are optional, but if they are provided they must be valid JSON - // if (!mcp_arguments) { - // this.consecutiveMistakeCount++ - // pushToolResult(await this.sayAndCreateMissingParamError("use_mcp_tool", "arguments")) - // break - // } - let parsedArguments: Record | undefined - if (mcp_arguments) { - try { - parsedArguments = JSON.parse(mcp_arguments) - } catch (error) { - this.consecutiveMistakeCount++ - await this.say( - "error", - `Roo tried to use ${tool_name} with an invalid JSON argument. Retrying...`, - ) - pushToolResult( - formatResponse.toolError( - formatResponse.invalidMcpToolArgumentError(server_name, tool_name), - ), - ) - break - } - } - this.consecutiveMistakeCount = 0 - const completeMessage = JSON.stringify({ - type: "use_mcp_tool", - serverName: server_name, - toolName: tool_name, - arguments: mcp_arguments, - } satisfies ClineAskUseMcpServer) - const didApprove = await askApproval("use_mcp_server", completeMessage) - if (!didApprove) { - break - } - // now execute the tool - await this.say("mcp_server_request_started") // same as browser_action_result - const toolResult = await this.providerRef - .deref() - ?.getMcpHub() - ?.callTool(server_name, tool_name, parsedArguments) - - // TODO: add progress indicator and ability to parse images and non-text responses - const toolResultPretty = - (toolResult?.isError ? "Error:\n" : "") + - toolResult?.content - .map((item) => { - if (item.type === "text") { - return item.text - } - if (item.type === "resource") { - const { blob, ...rest } = item.resource - return JSON.stringify(rest, null, 2) - } - return "" - }) - .filter(Boolean) - .join("\n\n") || "(No response)" - await this.say("mcp_server_response", toolResultPretty) - pushToolResult(formatResponse.toolResult(toolResultPretty)) - break - } - } catch (error) { - await handleError("executing MCP tool", error) - break - } - } - case "access_mcp_resource": { - const server_name: string | undefined = block.params.server_name - const uri: string | undefined = block.params.uri - try { - if (block.partial) { - const partialMessage = JSON.stringify({ - type: "access_mcp_resource", - serverName: removeClosingTag("server_name", server_name), - uri: removeClosingTag("uri", uri), - } satisfies ClineAskUseMcpServer) - await this.ask("use_mcp_server", partialMessage, block.partial).catch(() => {}) - break - } else { - if (!server_name) { - this.consecutiveMistakeCount++ - pushToolResult( - await this.sayAndCreateMissingParamError("access_mcp_resource", "server_name"), - ) - break - } - if (!uri) { - this.consecutiveMistakeCount++ - pushToolResult( - await this.sayAndCreateMissingParamError("access_mcp_resource", "uri"), - ) - break - } - this.consecutiveMistakeCount = 0 - const completeMessage = JSON.stringify({ - type: "access_mcp_resource", - serverName: server_name, - uri, - } satisfies ClineAskUseMcpServer) - const didApprove = await askApproval("use_mcp_server", completeMessage) - if (!didApprove) { - break - } - // now execute the tool - await this.say("mcp_server_request_started") - const resourceResult = await this.providerRef - .deref() - ?.getMcpHub() - ?.readResource(server_name, uri) - const resourceResultPretty = - resourceResult?.contents - .map((item) => { - if (item.text) { - return item.text - } - return "" - }) - .filter(Boolean) - .join("\n\n") || "(Empty response)" - - // handle images (image must contain mimetype and blob) - let images: string[] = [] - resourceResult?.contents.forEach((item) => { - if (item.mimeType?.startsWith("image") && item.blob) { - images.push(item.blob) - } - }) - await this.say("mcp_server_response", resourceResultPretty, images) - pushToolResult(formatResponse.toolResult(resourceResultPretty, images)) - break - } - } catch (error) { - await handleError("accessing MCP resource", error) - break - } - } - case "ask_followup_question": { - const question: string | undefined = block.params.question - const follow_up: string | undefined = block.params.follow_up - try { - if (block.partial) { - await this.ask("followup", removeClosingTag("question", question), block.partial).catch( - () => {}, - ) - break - } else { - if (!question) { - this.consecutiveMistakeCount++ - pushToolResult( - await this.sayAndCreateMissingParamError("ask_followup_question", "question"), - ) - break - } - - type Suggest = { - answer: string - } - - let follow_up_json = { - question, - suggest: [] as Suggest[], - } - - if (follow_up) { - let parsedSuggest: { - suggest: Suggest[] | Suggest - } - - try { - parsedSuggest = parseXml(follow_up, ["suggest"]) as { - suggest: Suggest[] | Suggest - } - } catch (error) { - this.consecutiveMistakeCount++ - await this.say("error", `Failed to parse operations: ${error.message}`) - pushToolResult(formatResponse.toolError("Invalid operations xml format")) - break - } - - const normalizedSuggest = Array.isArray(parsedSuggest?.suggest) - ? parsedSuggest.suggest - : [parsedSuggest?.suggest].filter((sug): sug is Suggest => sug !== undefined) - - follow_up_json.suggest = normalizedSuggest - } - - this.consecutiveMistakeCount = 0 - - const { text, images } = await this.ask( - "followup", - JSON.stringify(follow_up_json), - false, - ) - await this.say("user_feedback", text ?? "", images) - pushToolResult(formatResponse.toolResult(`\n${text}\n`, images)) - break - } - } catch (error) { - await handleError("asking question", error) - break - } - } - case "switch_mode": { - const mode_slug: string | undefined = block.params.mode_slug - const reason: string | undefined = block.params.reason - try { - if (block.partial) { - const partialMessage = JSON.stringify({ - tool: "switchMode", - mode: removeClosingTag("mode_slug", mode_slug), - reason: removeClosingTag("reason", reason), - }) - await this.ask("tool", partialMessage, block.partial).catch(() => {}) - break - } else { - if (!mode_slug) { - this.consecutiveMistakeCount++ - pushToolResult(await this.sayAndCreateMissingParamError("switch_mode", "mode_slug")) - break - } - this.consecutiveMistakeCount = 0 - - // Verify the mode exists - const targetMode = getModeBySlug( - mode_slug, - (await this.providerRef.deref()?.getState())?.customModes, - ) - if (!targetMode) { - pushToolResult(formatResponse.toolError(`Invalid mode: ${mode_slug}`)) - break - } - - // Check if already in requested mode - const currentMode = - (await this.providerRef.deref()?.getState())?.mode ?? defaultModeSlug - if (currentMode === mode_slug) { - pushToolResult(`Already in ${targetMode.name} mode.`) - break - } - - const completeMessage = JSON.stringify({ - tool: "switchMode", - mode: mode_slug, - reason, - }) - - const didApprove = await askApproval("tool", completeMessage) - if (!didApprove) { - break - } - - // Switch the mode using shared handler - await this.providerRef.deref()?.handleModeSwitch(mode_slug) - pushToolResult( - `Successfully switched from ${getModeBySlug(currentMode)?.name ?? currentMode} mode to ${ - targetMode.name - } mode${reason ? ` because: ${reason}` : ""}.`, - ) - await delay(500) // delay to allow mode change to take effect before next tool is executed - break - } - } catch (error) { - await handleError("switching mode", error) - break - } - } - - case "new_task": { - const mode: string | undefined = block.params.mode - const message: string | undefined = block.params.message - try { - if (block.partial) { - const partialMessage = JSON.stringify({ - tool: "newTask", - mode: removeClosingTag("mode", mode), - message: removeClosingTag("message", message), - }) - await this.ask("tool", partialMessage, block.partial).catch(() => {}) - break - } else { - if (!mode) { - this.consecutiveMistakeCount++ - pushToolResult(await this.sayAndCreateMissingParamError("new_task", "mode")) - break - } - if (!message) { - this.consecutiveMistakeCount++ - pushToolResult(await this.sayAndCreateMissingParamError("new_task", "message")) - break - } - this.consecutiveMistakeCount = 0 - - // Verify the mode exists - const targetMode = getModeBySlug( - mode, - (await this.providerRef.deref()?.getState())?.customModes, - ) - if (!targetMode) { - pushToolResult(formatResponse.toolError(`Invalid mode: ${mode}`)) - break - } - - const toolMessage = JSON.stringify({ - tool: "newTask", - mode: targetMode.name, - content: message, - }) - const didApprove = await askApproval("tool", toolMessage) - - if (!didApprove) { - break - } - - const provider = this.providerRef.deref() - - if (!provider) { - break - } - - // Preserve the current mode so we can resume with it later. - this.pausedModeSlug = (await provider.getState()).mode ?? defaultModeSlug - - // Switch mode first, then create new task instance. - await provider.handleModeSwitch(mode) - - // Delay to allow mode change to take effect before next tool is executed. - await delay(500) - - const newCline = await provider.initClineWithTask(message, undefined, this) - this.emit("taskSpawned", newCline.taskId) - - pushToolResult( - `Successfully created new task in ${targetMode.name} mode with message: ${message}`, - ) - - // Set the isPaused flag to true so the parent - // task can wait for the sub-task to finish. - this.isPaused = true - this.emit("taskPaused") - - break - } - } catch (error) { - await handleError("creating new task", error) - break - } - } - - case "attempt_completion": { - const result: string | undefined = block.params.result - const command: string | undefined = block.params.command - try { - const lastMessage = this.clineMessages.at(-1) - if (block.partial) { - if (command) { - // the attempt_completion text is done, now we're getting command - // remove the previous partial attempt_completion ask, replace with say, post state to webview, then stream command - - // const secondLastMessage = this.clineMessages.at(-2) - if (lastMessage && lastMessage.ask === "command") { - // update command - await this.ask( - "command", - removeClosingTag("command", command), - block.partial, - ).catch(() => {}) - } else { - // last message is completion_result - // we have command string, which means we have the result as well, so finish it (doesnt have to exist yet) - await this.say( - "completion_result", - removeClosingTag("result", result), - undefined, - false, - ) - - telemetryService.captureTaskCompleted(this.taskId) - this.emit("taskCompleted", this.taskId, this.getTokenUsage()) - - await this.ask( - "command", - removeClosingTag("command", command), - block.partial, - ).catch(() => {}) - } - } else { - // no command, still outputting partial result - await this.say( - "completion_result", - removeClosingTag("result", result), - undefined, - block.partial, - ) - } - break - } else { - if (!result) { - this.consecutiveMistakeCount++ - pushToolResult( - await this.sayAndCreateMissingParamError("attempt_completion", "result"), - ) - break - } - - this.consecutiveMistakeCount = 0 - - let commandResult: ToolResponse | undefined - - if (command) { - if (lastMessage && lastMessage.ask !== "command") { - // Haven't sent a command message yet so first send completion_result then command. - await this.say("completion_result", result, undefined, false) - telemetryService.captureTaskCompleted(this.taskId) - this.emit("taskCompleted", this.taskId, this.getTokenUsage()) - } - - // Complete command message. - const didApprove = await askApproval("command", command) - - if (!didApprove) { - break - } - - const [userRejected, execCommandResult] = await this.executeCommandTool(command!) - - if (userRejected) { - this.didRejectTool = true - pushToolResult(execCommandResult) - break - } - - // User didn't reject, but the command may have output. - commandResult = execCommandResult - } else { - await this.say("completion_result", result, undefined, false) - telemetryService.captureTaskCompleted(this.taskId) - this.emit("taskCompleted", this.taskId, this.getTokenUsage()) - } - - if (this.parentTask) { - const didApprove = await askFinishSubTaskApproval() - - if (!didApprove) { - break - } - - // tell the provider to remove the current subtask and resume the previous task in the stack - await this.providerRef.deref()?.finishSubTask(`Task complete: ${lastMessage?.text}`) - break - } - - // We already sent completion_result says, an - // empty string asks relinquishes control over - // button and field. - const { response, text, images } = await this.ask("completion_result", "", false) - - // Signals to recursive loop to stop (for now - // this never happens since yesButtonClicked - // will trigger a new task). - if (response === "yesButtonClicked") { - pushToolResult("") - break - } - - await this.say("user_feedback", text ?? "", images) - const toolResults: (Anthropic.TextBlockParam | Anthropic.ImageBlockParam)[] = [] - - if (commandResult) { - if (typeof commandResult === "string") { - toolResults.push({ type: "text", text: commandResult }) - } else if (Array.isArray(commandResult)) { - toolResults.push(...commandResult) - } - } - - toolResults.push({ - type: "text", - text: `The user has provided feedback on the results. Consider their input to continue the task, and then attempt completion again.\n\n${text}\n`, - }) - - toolResults.push(...formatResponse.imageBlocks(images)) - - this.userMessageContent.push({ - type: "text", - text: `${toolDescription()} Result:`, - }) - - this.userMessageContent.push(...toolResults) - break - } - } catch (error) { - await handleError("inspecting site", error) - break - } - } } break @@ -4118,7 +2606,3 @@ export class Cline extends EventEmitter { } } } - -function escapeRegExp(string: string): string { - return string.replace(/[.*+?^${}()|[\]\\]/g, "\\$&") -} diff --git a/src/core/__tests__/Cline.test.ts b/src/core/__tests__/Cline.test.ts index 68c3208876..1fa9453c3c 100644 --- a/src/core/__tests__/Cline.test.ts +++ b/src/core/__tests__/Cline.test.ts @@ -304,7 +304,12 @@ describe("Cline", () => { expect(cline.diffEnabled).toBe(true) expect(cline.diffStrategy).toBeDefined() - expect(getDiffStrategySpy).toHaveBeenCalledWith("claude-3-5-sonnet-20241022", 0.9, false, false) + + expect(getDiffStrategySpy).toHaveBeenCalledWith({ + model: "claude-3-5-sonnet-20241022", + experiments: {}, + fuzzyMatchThreshold: 0.9, + }) }) it("should pass default threshold to diff strategy when not provided", async () => { @@ -321,7 +326,11 @@ describe("Cline", () => { expect(cline.diffEnabled).toBe(true) expect(cline.diffStrategy).toBeDefined() - expect(getDiffStrategySpy).toHaveBeenCalledWith("claude-3-5-sonnet-20241022", 1.0, false, false) + expect(getDiffStrategySpy).toHaveBeenCalledWith({ + model: "claude-3-5-sonnet-20241022", + experiments: {}, + fuzzyMatchThreshold: 1.0, + }) }) it("should require either task or historyItem", () => { diff --git a/src/core/config/ContextProxy.ts b/src/core/config/ContextProxy.ts index 48c9846856..835ca214f6 100644 --- a/src/core/config/ContextProxy.ts +++ b/src/core/config/ContextProxy.ts @@ -2,16 +2,17 @@ import * as vscode from "vscode" import { PROVIDER_SETTINGS_KEYS, - ProviderSettings, - providerSettingsSchema, - GlobalSettings, - globalSettingsSchema, - RooCodeSettings, + GLOBAL_SETTINGS_KEYS, SECRET_STATE_KEYS, - SecretState, - isSecretStateKey, GLOBAL_STATE_KEYS, + ProviderSettings, + GlobalSettings, + SecretState, GlobalState, + RooCodeSettings, + providerSettingsSchema, + globalSettingsSchema, + isSecretStateKey, } from "../../schemas" import { logger } from "../../utils/logging" @@ -151,7 +152,15 @@ export class ContextProxy { */ public getGlobalSettings(): GlobalSettings { - return globalSettingsSchema.parse({ ...this.stateCache }) + const values = this.getValues() + + try { + return globalSettingsSchema.parse(values) + } catch (error) { + // Log to Posthog? + // We'll want to know about bad type assumptions or bad ExtensionState data. + return GLOBAL_SETTINGS_KEYS.reduce((acc, key) => ({ ...acc, [key]: values[key] }), {} as GlobalSettings) + } } /** @@ -159,7 +168,15 @@ export class ContextProxy { */ public getProviderSettings(): ProviderSettings { - return providerSettingsSchema.parse(this.getValues()) + const values = this.getValues() + + try { + return providerSettingsSchema.parse(values) + } catch (error) { + // Log to Posthog? + // We'll want to know about bad type assumptions or bad ExtensionState data. + return PROVIDER_SETTINGS_KEYS.reduce((acc, key) => ({ ...acc, [key]: values[key] }), {} as ProviderSettings) + } } public async setProviderSettings(values: ProviderSettings) { @@ -206,7 +223,6 @@ export class ContextProxy { public async export(): Promise { try { const globalSettings = globalSettingsExportSchema.parse(this.getValues()) - return Object.fromEntries(Object.entries(globalSettings).filter(([_, value]) => value !== undefined)) } catch (error) { console.log(error.message) diff --git a/src/core/config/CustomModesManager.ts b/src/core/config/CustomModesManager.ts index cfbe29dcb9..efa3366aee 100644 --- a/src/core/config/CustomModesManager.ts +++ b/src/core/config/CustomModesManager.ts @@ -19,6 +19,7 @@ export class CustomModesManager { private readonly context: vscode.ExtensionContext, private readonly onUpdate: () => Promise, ) { + // TODO: We really shouldn't have async methods in the constructor. this.watchCustomModesFiles() } diff --git a/src/core/config/ProviderSettingsManager.ts b/src/core/config/ProviderSettingsManager.ts index 471abe7e18..f4df9c0829 100644 --- a/src/core/config/ProviderSettingsManager.ts +++ b/src/core/config/ProviderSettingsManager.ts @@ -2,7 +2,7 @@ import { ExtensionContext } from "vscode" import { z } from "zod" import { providerSettingsSchema, ApiConfigMeta } from "../../schemas" -import { Mode } from "../../shared/modes" +import { Mode, modes } from "../../shared/modes" const providerSettingsWithIdSchema = providerSettingsSchema.extend({ id: z.string().optional() }) @@ -16,24 +16,18 @@ export const providerProfilesSchema = z.object({ export type ProviderProfiles = z.infer -const providerProfilesExportSchema = providerProfilesSchema.extend({ - apiConfigs: z.record( - z.string(), - providerSettingsWithIdSchema.omit({ - glamaModelInfo: true, - openRouterModelInfo: true, - unboundModelInfo: true, - requestyModelInfo: true, - }), - ), -}) - export class ProviderSettingsManager { private static readonly SCOPE_PREFIX = "roo_cline_config_" + private readonly defaultConfigId = this.generateId() + + private readonly defaultModeApiConfigs: Record = Object.fromEntries( + modes.map((mode) => [mode.slug, this.defaultConfigId]), + ) private readonly defaultProviderProfiles: ProviderProfiles = { currentApiConfigName: "default", - apiConfigs: { default: { id: this.generateId() } }, + apiConfigs: { default: { id: this.defaultConfigId } }, + modeApiConfigs: this.defaultModeApiConfigs, } private readonly context: ExtensionContext @@ -246,7 +240,7 @@ export class ProviderSettingsManager { public async export() { try { - return await this.lock(async () => providerProfilesExportSchema.parse(await this.load())) + return await this.lock(async () => providerProfilesSchema.parse(await this.load())) } catch (error) { throw new Error(`Failed to export provider profiles: ${error}`) } diff --git a/src/core/diff/DiffStrategy.ts b/src/core/diff/DiffStrategy.ts index e532aec4b0..fe354196c6 100644 --- a/src/core/diff/DiffStrategy.ts +++ b/src/core/diff/DiffStrategy.ts @@ -1,29 +1,28 @@ import type { DiffStrategy } from "./types" -import { UnifiedDiffStrategy } from "./strategies/unified" import { SearchReplaceDiffStrategy } from "./strategies/search-replace" import { NewUnifiedDiffStrategy } from "./strategies/new-unified" import { MultiSearchReplaceDiffStrategy } from "./strategies/multi-search-replace" +import { EXPERIMENT_IDS, ExperimentId } from "../../shared/experiments" + +export type { DiffStrategy } + /** * Get the appropriate diff strategy for the given model * @param model The name of the model being used (e.g., 'gpt-4', 'claude-3-opus') * @returns The appropriate diff strategy for the model */ -export function getDiffStrategy( - model: string, - fuzzyMatchThreshold?: number, - experimentalDiffStrategy: boolean = false, - multiSearchReplaceDiffStrategy: boolean = false, -): DiffStrategy { - if (experimentalDiffStrategy) { - return new NewUnifiedDiffStrategy(fuzzyMatchThreshold) - } - if (multiSearchReplaceDiffStrategy) { - return new MultiSearchReplaceDiffStrategy(fuzzyMatchThreshold) - } else { - return new SearchReplaceDiffStrategy(fuzzyMatchThreshold) - } +export type DiffStrategyName = "unified" | "multi-search-and-replace" | "search-and-replace" + +type GetDiffStrategyOptions = { + model: string + experiments: Partial> + fuzzyMatchThreshold?: number } -export type { DiffStrategy } -export { UnifiedDiffStrategy, SearchReplaceDiffStrategy } +export const getDiffStrategy = ({ fuzzyMatchThreshold, experiments }: GetDiffStrategyOptions): DiffStrategy => + experiments[EXPERIMENT_IDS.DIFF_STRATEGY_UNIFIED] + ? new NewUnifiedDiffStrategy(fuzzyMatchThreshold) + : experiments[EXPERIMENT_IDS.DIFF_STRATEGY_MULTI_SEARCH_AND_REPLACE] + ? new MultiSearchReplaceDiffStrategy(fuzzyMatchThreshold) + : new SearchReplaceDiffStrategy(fuzzyMatchThreshold) diff --git a/src/core/prompts/__tests__/system.test.ts b/src/core/prompts/__tests__/system.test.ts index 0e9d643923..8fd0046501 100644 --- a/src/core/prompts/__tests__/system.test.ts +++ b/src/core/prompts/__tests__/system.test.ts @@ -171,7 +171,7 @@ describe("SYSTEM_PROMPT", () => { beforeEach(() => { // Reset experiments before each test to ensure they're disabled by default experiments = { - [EXPERIMENT_IDS.SEARCH_AND_REPLACE]: false, + [EXPERIMENT_IDS.DIFF_STRATEGY_SEARCH_AND_REPLACE]: false, [EXPERIMENT_IDS.INSERT_BLOCK]: false, } }) @@ -482,7 +482,7 @@ describe("SYSTEM_PROMPT", () => { it("should disable experimental tools by default", async () => { // Set experiments to explicitly disable experimental tools const experimentsConfig = { - [EXPERIMENT_IDS.SEARCH_AND_REPLACE]: false, + [EXPERIMENT_IDS.DIFF_STRATEGY_SEARCH_AND_REPLACE]: false, [EXPERIMENT_IDS.INSERT_BLOCK]: false, } @@ -516,7 +516,7 @@ describe("SYSTEM_PROMPT", () => { it("should enable experimental tools when explicitly enabled", async () => { // Set experiments for testing experimental features const experimentsEnabled = { - [EXPERIMENT_IDS.SEARCH_AND_REPLACE]: true, + [EXPERIMENT_IDS.DIFF_STRATEGY_SEARCH_AND_REPLACE]: true, [EXPERIMENT_IDS.INSERT_BLOCK]: true, } @@ -552,7 +552,7 @@ describe("SYSTEM_PROMPT", () => { it("should selectively enable experimental tools", async () => { // Set experiments for testing selective enabling const experimentsSelective = { - [EXPERIMENT_IDS.SEARCH_AND_REPLACE]: true, + [EXPERIMENT_IDS.DIFF_STRATEGY_SEARCH_AND_REPLACE]: true, [EXPERIMENT_IDS.INSERT_BLOCK]: false, } @@ -587,7 +587,7 @@ describe("SYSTEM_PROMPT", () => { it("should list all available editing tools in base instruction", async () => { const experiments = { - [EXPERIMENT_IDS.SEARCH_AND_REPLACE]: true, + [EXPERIMENT_IDS.DIFF_STRATEGY_SEARCH_AND_REPLACE]: true, [EXPERIMENT_IDS.INSERT_BLOCK]: true, } @@ -615,7 +615,7 @@ describe("SYSTEM_PROMPT", () => { }) it("should provide detailed instructions for each enabled tool", async () => { const experiments = { - [EXPERIMENT_IDS.SEARCH_AND_REPLACE]: true, + [EXPERIMENT_IDS.DIFF_STRATEGY_SEARCH_AND_REPLACE]: true, [EXPERIMENT_IDS.INSERT_BLOCK]: true, } diff --git a/src/core/tools/__tests__/executeCommandTool.test.ts b/src/core/tools/__tests__/executeCommandTool.test.ts new file mode 100644 index 0000000000..2c975b7809 --- /dev/null +++ b/src/core/tools/__tests__/executeCommandTool.test.ts @@ -0,0 +1,269 @@ +// npx jest src/core/tools/__tests__/executeCommandTool.test.ts + +import { describe, expect, it, jest, beforeEach } from "@jest/globals" +import { executeCommandTool } from "../executeCommandTool" +import { Cline } from "../../Cline" +import { ToolUse } from "../../assistant-message" +import { formatResponse } from "../../prompts/responses" +import { AskApproval, HandleError, PushToolResult, RemoveClosingTag } from "../types" +import { ClineAsk } from "../../../schemas" + +// Mock dependencies +jest.mock("../../Cline") +jest.mock("../../prompts/responses") + +describe("executeCommandTool", () => { + // Setup common test variables + let mockCline: jest.Mocked> & { consecutiveMistakeCount: number; didRejectTool: boolean } + let mockAskApproval: jest.Mock + let mockHandleError: jest.Mock + let mockPushToolResult: jest.Mock + let mockRemoveClosingTag: jest.Mock + let mockToolUse: ToolUse + + beforeEach(() => { + // Reset mocks + jest.clearAllMocks() + + // Create mock implementations with eslint directives to handle the type issues + mockCline = { + // @ts-expect-error - Jest mock function type issues + ask: jest.fn().mockResolvedValue(undefined), + // @ts-expect-error - Jest mock function type issues + say: jest.fn().mockResolvedValue(undefined), + // @ts-expect-error - Jest mock function type issues + sayAndCreateMissingParamError: jest.fn().mockResolvedValue("Missing parameter error"), + // @ts-expect-error - Jest mock function type issues + executeCommandTool: jest.fn().mockResolvedValue([false, "Command executed"]), + consecutiveMistakeCount: 0, + didRejectTool: false, + rooIgnoreController: { + // @ts-expect-error - Jest mock function type issues + validateCommand: jest.fn().mockReturnValue(null), + }, + } + + // @ts-expect-error - Jest mock function type issues + mockAskApproval = jest.fn().mockResolvedValue(true) + // @ts-expect-error - Jest mock function type issues + mockHandleError = jest.fn().mockResolvedValue(undefined) + mockPushToolResult = jest.fn() + mockRemoveClosingTag = jest.fn().mockReturnValue("command") + + // Create a mock tool use object + mockToolUse = { + type: "tool_use", + name: "execute_command", + params: { + command: "echo test", + }, + partial: false, + } + }) + + /** + * Tests for HTML entity unescaping in commands + * This verifies that HTML entities are properly converted to their actual characters + * before the command is executed + */ + describe("HTML entity unescaping", () => { + it("should unescape < to < character in commands", async () => { + // Setup + mockToolUse.params.command = "echo <test>" + + // Execute + await executeCommandTool( + mockCline as unknown as Cline, + mockToolUse, + mockAskApproval as unknown as AskApproval, + mockHandleError as unknown as HandleError, + mockPushToolResult as unknown as PushToolResult, + mockRemoveClosingTag as unknown as RemoveClosingTag, + ) + + // Verify + expect(mockAskApproval).toHaveBeenCalledWith("command", "echo ") + expect(mockCline.executeCommandTool).toHaveBeenCalledWith("echo ", undefined) + }) + + it("should unescape > to > character in commands", async () => { + // Setup + mockToolUse.params.command = "echo test > output.txt" + + // Execute + await executeCommandTool( + mockCline as unknown as Cline, + mockToolUse, + mockAskApproval as unknown as AskApproval, + mockHandleError as unknown as HandleError, + mockPushToolResult as unknown as PushToolResult, + mockRemoveClosingTag as unknown as RemoveClosingTag, + ) + + // Verify + expect(mockAskApproval).toHaveBeenCalledWith("command", "echo test > output.txt") + expect(mockCline.executeCommandTool).toHaveBeenCalledWith("echo test > output.txt", undefined) + }) + + it("should unescape & to & character in commands", async () => { + // Setup + mockToolUse.params.command = "echo foo && echo bar" + + // Execute + await executeCommandTool( + mockCline as unknown as Cline, + mockToolUse, + mockAskApproval as unknown as AskApproval, + mockHandleError as unknown as HandleError, + mockPushToolResult as unknown as PushToolResult, + mockRemoveClosingTag as unknown as RemoveClosingTag, + ) + + // Verify + expect(mockAskApproval).toHaveBeenCalledWith("command", "echo foo && echo bar") + expect(mockCline.executeCommandTool).toHaveBeenCalledWith("echo foo && echo bar", undefined) + }) + + it("should handle multiple mixed HTML entities in commands", async () => { + // Setup + mockToolUse.params.command = "grep -E 'pattern' <file.txt >output.txt 2>&1" + + // Execute + await executeCommandTool( + mockCline as unknown as Cline, + mockToolUse, + mockAskApproval as unknown as AskApproval, + mockHandleError as unknown as HandleError, + mockPushToolResult as unknown as PushToolResult, + mockRemoveClosingTag as unknown as RemoveClosingTag, + ) + + // Verify + const expectedCommand = "grep -E 'pattern' output.txt 2>&1" + expect(mockAskApproval).toHaveBeenCalledWith("command", expectedCommand) + expect(mockCline.executeCommandTool).toHaveBeenCalledWith(expectedCommand, undefined) + }) + }) + + // Other functionality tests + describe("Basic functionality", () => { + it("should execute a command normally without HTML entities", async () => { + // Setup + mockToolUse.params.command = "echo test" + + // Execute + await executeCommandTool( + mockCline as unknown as Cline, + mockToolUse, + mockAskApproval as unknown as AskApproval, + mockHandleError as unknown as HandleError, + mockPushToolResult as unknown as PushToolResult, + mockRemoveClosingTag as unknown as RemoveClosingTag, + ) + + // Verify + expect(mockAskApproval).toHaveBeenCalledWith("command", "echo test") + expect(mockCline.executeCommandTool).toHaveBeenCalledWith("echo test", undefined) + expect(mockPushToolResult).toHaveBeenCalledWith("Command executed") + }) + + it("should pass along custom working directory if provided", async () => { + // Setup + mockToolUse.params.command = "echo test" + mockToolUse.params.cwd = "/custom/path" + + // Execute + await executeCommandTool( + mockCline as unknown as Cline, + mockToolUse, + mockAskApproval as unknown as AskApproval, + mockHandleError as unknown as HandleError, + mockPushToolResult as unknown as PushToolResult, + mockRemoveClosingTag as unknown as RemoveClosingTag, + ) + + // Verify + expect(mockCline.executeCommandTool).toHaveBeenCalledWith("echo test", "/custom/path") + }) + }) + + describe("Error handling", () => { + it("should handle missing command parameter", async () => { + // Setup + mockToolUse.params.command = undefined + + // Execute + await executeCommandTool( + mockCline as unknown as Cline, + mockToolUse, + mockAskApproval as unknown as AskApproval, + mockHandleError as unknown as HandleError, + mockPushToolResult as unknown as PushToolResult, + mockRemoveClosingTag as unknown as RemoveClosingTag, + ) + + // Verify + expect(mockCline.consecutiveMistakeCount).toBe(1) + expect(mockCline.sayAndCreateMissingParamError).toHaveBeenCalledWith("execute_command", "command") + expect(mockPushToolResult).toHaveBeenCalledWith("Missing parameter error") + expect(mockAskApproval).not.toHaveBeenCalled() + expect(mockCline.executeCommandTool).not.toHaveBeenCalled() + }) + + it("should handle command rejection", async () => { + // Setup + mockToolUse.params.command = "echo test" + // @ts-expect-error - Jest mock function type issues + mockAskApproval.mockResolvedValue(false) + + // Execute + await executeCommandTool( + mockCline as unknown as Cline, + mockToolUse, + mockAskApproval as unknown as AskApproval, + mockHandleError as unknown as HandleError, + mockPushToolResult as unknown as PushToolResult, + mockRemoveClosingTag as unknown as RemoveClosingTag, + ) + + // Verify + expect(mockAskApproval).toHaveBeenCalledWith("command", "echo test") + expect(mockCline.executeCommandTool).not.toHaveBeenCalled() + expect(mockPushToolResult).not.toHaveBeenCalled() + }) + + it("should handle rooignore validation failures", async () => { + // Setup + mockToolUse.params.command = "cat .env" + // Override the validateCommand mock to return a filename + const validateCommandMock = jest.fn().mockReturnValue(".env") + mockCline.rooIgnoreController = { + // @ts-expect-error - Jest mock function type issues + validateCommand: validateCommandMock, + } + + const mockRooIgnoreError = "RooIgnore error" + ;(formatResponse.rooIgnoreError as jest.Mock).mockReturnValue(mockRooIgnoreError) + ;(formatResponse.toolError as jest.Mock).mockReturnValue("Tool error") + + // Execute + await executeCommandTool( + mockCline as unknown as Cline, + mockToolUse, + mockAskApproval as unknown as AskApproval, + mockHandleError as unknown as HandleError, + mockPushToolResult as unknown as PushToolResult, + mockRemoveClosingTag as unknown as RemoveClosingTag, + ) + + // Verify + expect(validateCommandMock).toHaveBeenCalledWith("cat .env") + expect(mockCline.say).toHaveBeenCalledWith("rooignore_error", ".env") + expect(formatResponse.rooIgnoreError).toHaveBeenCalledWith(".env") + expect(formatResponse.toolError).toHaveBeenCalledWith(mockRooIgnoreError) + expect(mockPushToolResult).toHaveBeenCalled() + expect(mockAskApproval).not.toHaveBeenCalled() + expect(mockCline.executeCommandTool).not.toHaveBeenCalled() + }) + }) +}) diff --git a/src/core/tools/accessMcpResourceTool.ts b/src/core/tools/accessMcpResourceTool.ts new file mode 100644 index 0000000000..94bea9062c --- /dev/null +++ b/src/core/tools/accessMcpResourceTool.ts @@ -0,0 +1,77 @@ +import { ClineAskUseMcpServer } from "../../shared/ExtensionMessage" +import { RemoveClosingTag } from "./types" +import { ToolUse } from "../assistant-message" +import { AskApproval, HandleError, PushToolResult } from "./types" +import { Cline } from "../Cline" +import { formatResponse } from "../prompts/responses" + +export async function accessMcpResourceTool( + cline: Cline, + block: ToolUse, + askApproval: AskApproval, + handleError: HandleError, + pushToolResult: PushToolResult, + removeClosingTag: RemoveClosingTag, +) { + const server_name: string | undefined = block.params.server_name + const uri: string | undefined = block.params.uri + try { + if (block.partial) { + const partialMessage = JSON.stringify({ + type: "access_mcp_resource", + serverName: removeClosingTag("server_name", server_name), + uri: removeClosingTag("uri", uri), + } satisfies ClineAskUseMcpServer) + await cline.ask("use_mcp_server", partialMessage, block.partial).catch(() => {}) + return + } else { + if (!server_name) { + cline.consecutiveMistakeCount++ + pushToolResult(await cline.sayAndCreateMissingParamError("access_mcp_resource", "server_name")) + return + } + if (!uri) { + cline.consecutiveMistakeCount++ + pushToolResult(await cline.sayAndCreateMissingParamError("access_mcp_resource", "uri")) + return + } + cline.consecutiveMistakeCount = 0 + const completeMessage = JSON.stringify({ + type: "access_mcp_resource", + serverName: server_name, + uri, + } satisfies ClineAskUseMcpServer) + const didApprove = await askApproval("use_mcp_server", completeMessage) + if (!didApprove) { + return + } + // now execute the tool + await cline.say("mcp_server_request_started") + const resourceResult = await cline.providerRef.deref()?.getMcpHub()?.readResource(server_name, uri) + const resourceResultPretty = + resourceResult?.contents + .map((item) => { + if (item.text) { + return item.text + } + return "" + }) + .filter(Boolean) + .join("\n\n") || "(Empty response)" + + // handle images (image must contain mimetype and blob) + let images: string[] = [] + resourceResult?.contents.forEach((item) => { + if (item.mimeType?.startsWith("image") && item.blob) { + images.push(item.blob) + } + }) + await cline.say("mcp_server_response", resourceResultPretty, images) + pushToolResult(formatResponse.toolResult(resourceResultPretty, images)) + return + } + } catch (error) { + await handleError("accessing MCP resource", error) + return + } +} diff --git a/src/core/tools/applyDiffTool.ts b/src/core/tools/applyDiffTool.ts new file mode 100644 index 0000000000..a20bace097 --- /dev/null +++ b/src/core/tools/applyDiffTool.ts @@ -0,0 +1,181 @@ +import { ClineSayTool } from "../../shared/ExtensionMessage" +import { getReadablePath } from "../../utils/path" +import { ToolUse } from "../assistant-message" +import { Cline } from "../Cline" +import { RemoveClosingTag } from "./types" +import { formatResponse } from "../prompts/responses" +import { AskApproval, HandleError, PushToolResult } from "./types" +import { fileExistsAtPath } from "../../utils/fs" +import { addLineNumbers } from "../../integrations/misc/extract-text" +import path from "path" +import fs from "fs/promises" + +export async function applyDiffTool( + cline: Cline, + block: ToolUse, + askApproval: AskApproval, + handleError: HandleError, + pushToolResult: PushToolResult, + removeClosingTag: RemoveClosingTag, +) { + const relPath: string | undefined = block.params.path + const diffContent: string | undefined = block.params.diff + + const sharedMessageProps: ClineSayTool = { + tool: "appliedDiff", + path: getReadablePath(cline.cwd, removeClosingTag("path", relPath)), + } + + try { + if (block.partial) { + // update gui message + let toolProgressStatus + if (cline.diffStrategy && cline.diffStrategy.getProgressStatus) { + toolProgressStatus = cline.diffStrategy.getProgressStatus(block) + } + + const partialMessage = JSON.stringify(sharedMessageProps) + + await cline.ask("tool", partialMessage, block.partial, toolProgressStatus).catch(() => {}) + return + } else { + if (!relPath) { + cline.consecutiveMistakeCount++ + pushToolResult(await cline.sayAndCreateMissingParamError("apply_diff", "path")) + return + } + if (!diffContent) { + cline.consecutiveMistakeCount++ + pushToolResult(await cline.sayAndCreateMissingParamError("apply_diff", "diff")) + return + } + + const accessAllowed = cline.rooIgnoreController?.validateAccess(relPath) + if (!accessAllowed) { + await cline.say("rooignore_error", relPath) + pushToolResult(formatResponse.toolError(formatResponse.rooIgnoreError(relPath))) + + return + } + + const absolutePath = path.resolve(cline.cwd, relPath) + const fileExists = await fileExistsAtPath(absolutePath) + + if (!fileExists) { + cline.consecutiveMistakeCount++ + const formattedError = `File does not exist at path: ${absolutePath}\n\n\nThe specified file could not be found. Please verify the file path and try again.\n` + await cline.say("error", formattedError) + pushToolResult(formattedError) + return + } + + const originalContent = await fs.readFile(absolutePath, "utf-8") + + // Apply the diff to the original content + const diffResult = (await cline.diffStrategy?.applyDiff( + originalContent, + diffContent, + parseInt(block.params.start_line ?? ""), + parseInt(block.params.end_line ?? ""), + )) ?? { + success: false, + error: "No diff strategy available", + } + let partResults = "" + + if (!diffResult.success) { + cline.consecutiveMistakeCount++ + const currentCount = (cline.consecutiveMistakeCountForApplyDiff.get(relPath) || 0) + 1 + cline.consecutiveMistakeCountForApplyDiff.set(relPath, currentCount) + let formattedError = "" + if (diffResult.failParts && diffResult.failParts.length > 0) { + for (const failPart of diffResult.failParts) { + if (failPart.success) { + continue + } + const errorDetails = failPart.details ? JSON.stringify(failPart.details, null, 2) : "" + formattedError = `\n${ + failPart.error + }${errorDetails ? `\n\nDetails:\n${errorDetails}` : ""}\n` + partResults += formattedError + } + } else { + const errorDetails = diffResult.details ? JSON.stringify(diffResult.details, null, 2) : "" + formattedError = `Unable to apply diff to file: ${absolutePath}\n\n\n${ + diffResult.error + }${errorDetails ? `\n\nDetails:\n${errorDetails}` : ""}\n` + } + + if (currentCount >= 2) { + await cline.say("error", formattedError) + } + pushToolResult(formattedError) + return + } + + cline.consecutiveMistakeCount = 0 + cline.consecutiveMistakeCountForApplyDiff.delete(relPath) + // Show diff view before asking for approval + cline.diffViewProvider.editType = "modify" + await cline.diffViewProvider.open(relPath) + await cline.diffViewProvider.update(diffResult.content, true) + await cline.diffViewProvider.scrollToFirstDiff() + + const completeMessage = JSON.stringify({ + ...sharedMessageProps, + diff: diffContent, + } satisfies ClineSayTool) + + let toolProgressStatus + if (cline.diffStrategy && cline.diffStrategy.getProgressStatus) { + toolProgressStatus = cline.diffStrategy.getProgressStatus(block, diffResult) + } + + const didApprove = await askApproval("tool", completeMessage, toolProgressStatus) + if (!didApprove) { + await cline.diffViewProvider.revertChanges() // cline likely handles closing the diff view + return + } + + const { newProblemsMessage, userEdits, finalContent } = await cline.diffViewProvider.saveChanges() + cline.didEditFile = true // used to determine if we should wait for busy terminal to update before sending api request + let partFailHint = "" + if (diffResult.failParts && diffResult.failParts.length > 0) { + partFailHint = `But unable to apply all diff parts to file: ${absolutePath}, silently use tool to check newest file version and re-apply diffs\n` + } + if (userEdits) { + await cline.say( + "user_feedback_diff", + JSON.stringify({ + tool: fileExists ? "editedExistingFile" : "newFileCreated", + path: getReadablePath(cline.cwd, relPath), + diff: userEdits, + } satisfies ClineSayTool), + ) + pushToolResult( + `The user made the following updates to your content:\n\n${userEdits}\n\n` + + partFailHint + + `The updated content, which includes both your original modifications and the user's edits, has been successfully saved to ${relPath.toPosix()}. Here is the full, updated content of the file, including line numbers:\n\n` + + `\n${addLineNumbers( + finalContent || "", + )}\n\n\n` + + `Please note:\n` + + `1. You do not need to re-write the file with these changes, as they have already been applied.\n` + + `2. Proceed with the task using cline updated file content as the new baseline.\n` + + `3. If the user's edits have addressed part of the task or changed the requirements, adjust your approach accordingly.` + + `${newProblemsMessage}`, + ) + } else { + pushToolResult( + `Changes successfully applied to ${relPath.toPosix()}:\n\n${newProblemsMessage}\n` + partFailHint, + ) + } + await cline.diffViewProvider.reset() + return + } + } catch (error) { + await handleError("applying diff", error) + await cline.diffViewProvider.reset() + return + } +} diff --git a/src/core/tools/askFollowupQuestionTool.ts b/src/core/tools/askFollowupQuestionTool.ts new file mode 100644 index 0000000000..5ed06e2403 --- /dev/null +++ b/src/core/tools/askFollowupQuestionTool.ts @@ -0,0 +1,71 @@ +import { Cline } from "../Cline" +import { ToolUse } from "../assistant-message" +import { AskApproval, HandleError, PushToolResult, RemoveClosingTag } from "./types" +import { formatResponse } from "../prompts/responses" +import { parseXml } from "../../utils/xml" + +export async function askFollowupQuestionTool( + cline: Cline, + block: ToolUse, + askApproval: AskApproval, + handleError: HandleError, + pushToolResult: PushToolResult, + removeClosingTag: RemoveClosingTag, +) { + const question: string | undefined = block.params.question + const follow_up: string | undefined = block.params.follow_up + try { + if (block.partial) { + await cline.ask("followup", removeClosingTag("question", question), block.partial).catch(() => {}) + return + } else { + if (!question) { + cline.consecutiveMistakeCount++ + pushToolResult(await cline.sayAndCreateMissingParamError("ask_followup_question", "question")) + return + } + + type Suggest = { + answer: string + } + + let follow_up_json = { + question, + suggest: [] as Suggest[], + } + + if (follow_up) { + let parsedSuggest: { + suggest: Suggest[] | Suggest + } + + try { + parsedSuggest = parseXml(follow_up, ["suggest"]) as { + suggest: Suggest[] | Suggest + } + } catch (error) { + cline.consecutiveMistakeCount++ + await cline.say("error", `Failed to parse operations: ${error.message}`) + pushToolResult(formatResponse.toolError("Invalid operations xml format")) + return + } + + const normalizedSuggest = Array.isArray(parsedSuggest?.suggest) + ? parsedSuggest.suggest + : [parsedSuggest?.suggest].filter((sug): sug is Suggest => sug !== undefined) + + follow_up_json.suggest = normalizedSuggest + } + + cline.consecutiveMistakeCount = 0 + + const { text, images } = await cline.ask("followup", JSON.stringify(follow_up_json), false) + await cline.say("user_feedback", text ?? "", images) + pushToolResult(formatResponse.toolResult(`\n${text}\n`, images)) + return + } + } catch (error) { + await handleError("asking question", error) + return + } +} diff --git a/src/core/tools/attemptCompletionTool.ts b/src/core/tools/attemptCompletionTool.ts new file mode 100644 index 0000000000..6af96e9154 --- /dev/null +++ b/src/core/tools/attemptCompletionTool.ts @@ -0,0 +1,152 @@ +import { ToolResponse } from "../Cline" + +import { ToolUse } from "../assistant-message" +import { Cline } from "../Cline" +import { + AskApproval, + HandleError, + PushToolResult, + RemoveClosingTag, + ToolDescription, + AskFinishSubTaskApproval, +} from "./types" +import { formatResponse } from "../prompts/responses" +import { telemetryService } from "../../services/telemetry/TelemetryService" +import Anthropic from "@anthropic-ai/sdk" + +export async function attemptCompletionTool( + cline: Cline, + block: ToolUse, + askApproval: AskApproval, + handleError: HandleError, + pushToolResult: PushToolResult, + removeClosingTag: RemoveClosingTag, + toolDescription: ToolDescription, + askFinishSubTaskApproval: AskFinishSubTaskApproval, +) { + const result: string | undefined = block.params.result + const command: string | undefined = block.params.command + try { + const lastMessage = cline.clineMessages.at(-1) + if (block.partial) { + if (command) { + // the attempt_completion text is done, now we're getting command + // remove the previous partial attempt_completion ask, replace with say, post state to webview, then stream command + + // const secondLastMessage = cline.clineMessages.at(-2) + if (lastMessage && lastMessage.ask === "command") { + // update command + await cline.ask("command", removeClosingTag("command", command), block.partial).catch(() => {}) + } else { + // last message is completion_result + // we have command string, which means we have the result as well, so finish it (doesnt have to exist yet) + await cline.say("completion_result", removeClosingTag("result", result), undefined, false) + + telemetryService.captureTaskCompleted(cline.taskId) + cline.emit("taskCompleted", cline.taskId, cline.getTokenUsage()) + + await cline.ask("command", removeClosingTag("command", command), block.partial).catch(() => {}) + } + } else { + // no command, still outputting partial result + await cline.say("completion_result", removeClosingTag("result", result), undefined, block.partial) + } + return + } else { + if (!result) { + cline.consecutiveMistakeCount++ + pushToolResult(await cline.sayAndCreateMissingParamError("attempt_completion", "result")) + return + } + + cline.consecutiveMistakeCount = 0 + + let commandResult: ToolResponse | undefined + + if (command) { + if (lastMessage && lastMessage.ask !== "command") { + // Haven't sent a command message yet so first send completion_result then command. + await cline.say("completion_result", result, undefined, false) + telemetryService.captureTaskCompleted(cline.taskId) + cline.emit("taskCompleted", cline.taskId, cline.getTokenUsage()) + } + + // Complete command message. + const didApprove = await askApproval("command", command) + + if (!didApprove) { + return + } + + const [userRejected, execCommandResult] = await cline.executeCommandTool(command!) + + if (userRejected) { + cline.didRejectTool = true + pushToolResult(execCommandResult) + return + } + + // User didn't reject, but the command may have output. + commandResult = execCommandResult + } else { + await cline.say("completion_result", result, undefined, false) + telemetryService.captureTaskCompleted(cline.taskId) + cline.emit("taskCompleted", cline.taskId, cline.getTokenUsage()) + } + + if (cline.parentTask) { + const didApprove = await askFinishSubTaskApproval() + + if (!didApprove) { + return + } + + // tell the provider to remove the current subtask and resume the previous task in the stack + await cline.providerRef.deref()?.finishSubTask(`Task complete: ${lastMessage?.text}`) + return + } + + // We already sent completion_result says, an + // empty string asks relinquishes control over + // button and field. + const { response, text, images } = await cline.ask("completion_result", "", false) + + // Signals to recursive loop to stop (for now + // cline never happens since yesButtonClicked + // will trigger a new task). + if (response === "yesButtonClicked") { + pushToolResult("") + return + } + + await cline.say("user_feedback", text ?? "", images) + const toolResults: (Anthropic.TextBlockParam | Anthropic.ImageBlockParam)[] = [] + + if (commandResult) { + if (typeof commandResult === "string") { + toolResults.push({ type: "text", text: commandResult }) + } else if (Array.isArray(commandResult)) { + toolResults.push(...commandResult) + } + } + + toolResults.push({ + type: "text", + text: `The user has provided feedback on the results. Consider their input to continue the task, and then attempt completion again.\n\n${text}\n`, + }) + + toolResults.push(...formatResponse.imageBlocks(images)) + + cline.userMessageContent.push({ + type: "text", + text: `${toolDescription()} Result:`, + }) + + cline.userMessageContent.push(...toolResults) + return + } + } catch (error) { + await handleError("inspecting site", error) + return + } +} diff --git a/src/core/tools/browserActionTool.ts b/src/core/tools/browserActionTool.ts new file mode 100644 index 0000000000..8a9051070d --- /dev/null +++ b/src/core/tools/browserActionTool.ts @@ -0,0 +1,152 @@ +import { Cline } from "../Cline" +import { ToolUse } from "../assistant-message" +import { AskApproval, HandleError, PushToolResult, RemoveClosingTag } from "./types" +import { + BrowserAction, + BrowserActionResult, + browserActions, + ClineSayBrowserAction, +} from "../../shared/ExtensionMessage" +import { formatResponse } from "../prompts/responses" + +export async function browserActionTool( + cline: Cline, + block: ToolUse, + askApproval: AskApproval, + handleError: HandleError, + pushToolResult: PushToolResult, + removeClosingTag: RemoveClosingTag, +) { + const action: BrowserAction | undefined = block.params.action as BrowserAction + const url: string | undefined = block.params.url + const coordinate: string | undefined = block.params.coordinate + const text: string | undefined = block.params.text + if (!action || !browserActions.includes(action)) { + // checking for action to ensure it is complete and valid + if (!block.partial) { + // if the block is complete and we don't have a valid action cline is a mistake + cline.consecutiveMistakeCount++ + pushToolResult(await cline.sayAndCreateMissingParamError("browser_action", "action")) + await cline.browserSession.closeBrowser() + } + return + } + + try { + if (block.partial) { + if (action === "launch") { + await cline.ask("browser_action_launch", removeClosingTag("url", url), block.partial).catch(() => {}) + } else { + await cline.say( + "browser_action", + JSON.stringify({ + action: action as BrowserAction, + coordinate: removeClosingTag("coordinate", coordinate), + text: removeClosingTag("text", text), + } satisfies ClineSayBrowserAction), + undefined, + block.partial, + ) + } + return + } else { + // Initialize with empty object to avoid "used before assigned" errors + let browserActionResult: BrowserActionResult = {} + if (action === "launch") { + if (!url) { + cline.consecutiveMistakeCount++ + pushToolResult(await cline.sayAndCreateMissingParamError("browser_action", "url")) + await cline.browserSession.closeBrowser() + return + } + cline.consecutiveMistakeCount = 0 + const didApprove = await askApproval("browser_action_launch", url) + if (!didApprove) { + return + } + + // NOTE: it's okay that we call cline message since the partial inspect_site is finished streaming. The only scenario we have to avoid is sending messages WHILE a partial message exists at the end of the messages array. For example the api_req_finished message would interfere with the partial message, so we needed to remove that. + // await cline.say("inspect_site_result", "") // no result, starts the loading spinner waiting for result + await cline.say("browser_action_result", "") // starts loading spinner + + await cline.browserSession.launchBrowser() + browserActionResult = await cline.browserSession.navigateToUrl(url) + } else { + if (action === "click") { + if (!coordinate) { + cline.consecutiveMistakeCount++ + pushToolResult(await cline.sayAndCreateMissingParamError("browser_action", "coordinate")) + await cline.browserSession.closeBrowser() + return // can't be within an inner switch + } + } + if (action === "type") { + if (!text) { + cline.consecutiveMistakeCount++ + pushToolResult(await cline.sayAndCreateMissingParamError("browser_action", "text")) + await cline.browserSession.closeBrowser() + return + } + } + cline.consecutiveMistakeCount = 0 + await cline.say( + "browser_action", + JSON.stringify({ + action: action as BrowserAction, + coordinate, + text, + } satisfies ClineSayBrowserAction), + undefined, + false, + ) + switch (action) { + case "click": + browserActionResult = await cline.browserSession.click(coordinate!) + break + case "type": + browserActionResult = await cline.browserSession.type(text!) + break + case "scroll_down": + browserActionResult = await cline.browserSession.scrollDown() + break + case "scroll_up": + browserActionResult = await cline.browserSession.scrollUp() + break + case "close": + browserActionResult = await cline.browserSession.closeBrowser() + break + } + } + + switch (action) { + case "launch": + case "click": + case "type": + case "scroll_down": + case "scroll_up": + await cline.say("browser_action_result", JSON.stringify(browserActionResult)) + pushToolResult( + formatResponse.toolResult( + `The browser action has been executed. The console logs and screenshot have been captured for your analysis.\n\nConsole logs:\n${ + browserActionResult?.logs || "(No new logs)" + }\n\n(REMEMBER: if you need to proceed to using non-\`browser_action\` tools or launch a new browser, you MUST first close cline browser. For example, if after analyzing the logs and screenshot you need to edit a file, you must first close the browser before you can use the write_to_file tool.)`, + browserActionResult?.screenshot ? [browserActionResult.screenshot] : [], + ), + ) + break + case "close": + pushToolResult( + formatResponse.toolResult( + `The browser has been closed. You may now proceed to using other tools.`, + ), + ) + break + } + return + } + } catch (error) { + await cline.browserSession.closeBrowser() // if any error occurs, the browser session is terminated + await handleError("executing browser action", error) + return + } +} diff --git a/src/core/tools/executeCommandTool.ts b/src/core/tools/executeCommandTool.ts new file mode 100644 index 0000000000..79f89f092d --- /dev/null +++ b/src/core/tools/executeCommandTool.ts @@ -0,0 +1,55 @@ +import { Cline } from "../Cline" +import { ToolUse } from "../assistant-message" +import { AskApproval, HandleError, PushToolResult, RemoveClosingTag } from "./types" +import { formatResponse } from "../prompts/responses" + +export async function executeCommandTool( + cline: Cline, + block: ToolUse, + askApproval: AskApproval, + handleError: HandleError, + pushToolResult: PushToolResult, + removeClosingTag: RemoveClosingTag, +) { + let command: string | undefined = block.params.command + const customCwd: string | undefined = block.params.cwd + try { + if (block.partial) { + await cline.ask("command", removeClosingTag("command", command), block.partial).catch(() => {}) + return + } else { + if (!command) { + cline.consecutiveMistakeCount++ + pushToolResult(await cline.sayAndCreateMissingParamError("execute_command", "command")) + return + } + + const ignoredFileAttemptedToAccess = cline.rooIgnoreController?.validateCommand(command) + if (ignoredFileAttemptedToAccess) { + await cline.say("rooignore_error", ignoredFileAttemptedToAccess) + pushToolResult(formatResponse.toolError(formatResponse.rooIgnoreError(ignoredFileAttemptedToAccess))) + + return + } + + // unescape html entities (e.g. < -> <) + command = command.replace(/</g, "<").replace(/>/g, ">").replace(/&/g, "&") + + cline.consecutiveMistakeCount = 0 + + const didApprove = await askApproval("command", command) + if (!didApprove) { + return + } + const [userRejected, result] = await cline.executeCommandTool(command, customCwd) + if (userRejected) { + cline.didRejectTool = true + } + pushToolResult(result) + return + } + } catch (error) { + await handleError("executing command", error) + return + } +} diff --git a/src/core/tools/fetchInstructionsTool.ts b/src/core/tools/fetchInstructionsTool.ts index 8304c76317..3e185301db 100644 --- a/src/core/tools/fetchInstructionsTool.ts +++ b/src/core/tools/fetchInstructionsTool.ts @@ -12,58 +12,53 @@ export async function fetchInstructionsTool( handleError: HandleError, pushToolResult: PushToolResult, ) { - switch (true) { - default: - const task: string | undefined = block.params.task - const sharedMessageProps: ClineSayTool = { - tool: "fetchInstructions", + const task: string | undefined = block.params.task + const sharedMessageProps: ClineSayTool = { + tool: "fetchInstructions", + content: task, + } + try { + if (block.partial) { + const partialMessage = JSON.stringify({ + ...sharedMessageProps, + content: undefined, + } satisfies ClineSayTool) + await cline.ask("tool", partialMessage, block.partial).catch(() => {}) + return + } else { + if (!task) { + cline.consecutiveMistakeCount++ + pushToolResult(await cline.sayAndCreateMissingParamError("fetch_instructions", "task")) + return + } + + cline.consecutiveMistakeCount = 0 + const completeMessage = JSON.stringify({ + ...sharedMessageProps, content: task, + } satisfies ClineSayTool) + + const didApprove = await askApproval("tool", completeMessage) + if (!didApprove) { + return } - try { - if (block.partial) { - const partialMessage = JSON.stringify({ - ...sharedMessageProps, - content: undefined, - } satisfies ClineSayTool) - await cline.ask("tool", partialMessage, block.partial).catch(() => {}) - break - } else { - if (!task) { - cline.consecutiveMistakeCount++ - pushToolResult(await cline.sayAndCreateMissingParamError("fetch_instructions", "task")) - break - } - cline.consecutiveMistakeCount = 0 - const completeMessage = JSON.stringify({ - ...sharedMessageProps, - content: task, - } satisfies ClineSayTool) - - const didApprove = await askApproval("tool", completeMessage) - if (!didApprove) { - break - } - - // now fetch the content and provide it to the agent. - const provider = cline.providerRef.deref() - const mcpHub = provider?.getMcpHub() - if (!mcpHub) { - throw new Error("MCP hub not available") - } - const diffStrategy = cline.diffStrategy - const context = provider?.context - const content = await fetchInstructions(task, { mcpHub, diffStrategy, context }) - if (!content) { - pushToolResult(formatResponse.toolError(`Invalid instructions request: ${task}`)) - break - } - pushToolResult(content) - break - } - } catch (error) { - await handleError("fetch instructions", error) - break + // now fetch the content and provide it to the agent. + const provider = cline.providerRef.deref() + const mcpHub = provider?.getMcpHub() + if (!mcpHub) { + throw new Error("MCP hub not available") } + const diffStrategy = cline.diffStrategy + const context = provider?.context + const content = await fetchInstructions(task, { mcpHub, diffStrategy, context }) + if (!content) { + pushToolResult(formatResponse.toolError(`Invalid instructions request: ${task}`)) + return + } + pushToolResult(content) + } + } catch (error) { + await handleError("fetch instructions", error) } } diff --git a/src/core/tools/insertContentTool.ts b/src/core/tools/insertContentTool.ts new file mode 100644 index 0000000000..9ff2b28429 --- /dev/null +++ b/src/core/tools/insertContentTool.ts @@ -0,0 +1,161 @@ +import { getReadablePath } from "../../utils/path" +import { Cline } from "../Cline" +import { ToolUse } from "../assistant-message" +import { AskApproval, HandleError, PushToolResult, RemoveClosingTag } from "./types" +import { formatResponse } from "../prompts/responses" +import { ClineSayTool } from "../../shared/ExtensionMessage" +import path from "path" +import { fileExistsAtPath } from "../../utils/fs" +import { insertGroups } from "../diff/insert-groups" +import delay from "delay" +import fs from "fs/promises" + +export async function insertContentTool( + cline: Cline, + block: ToolUse, + askApproval: AskApproval, + handleError: HandleError, + pushToolResult: PushToolResult, + removeClosingTag: RemoveClosingTag, +) { + const relPath: string | undefined = block.params.path + const operations: string | undefined = block.params.operations + + const sharedMessageProps: ClineSayTool = { + tool: "appliedDiff", + path: getReadablePath(cline.cwd, removeClosingTag("path", relPath)), + } + + try { + if (block.partial) { + const partialMessage = JSON.stringify(sharedMessageProps) + await cline.ask("tool", partialMessage, block.partial).catch(() => {}) + return + } + + // Validate required parameters + if (!relPath) { + cline.consecutiveMistakeCount++ + pushToolResult(await cline.sayAndCreateMissingParamError("insert_content", "path")) + return + } + + if (!operations) { + cline.consecutiveMistakeCount++ + pushToolResult(await cline.sayAndCreateMissingParamError("insert_content", "operations")) + return + } + + const absolutePath = path.resolve(cline.cwd, relPath) + const fileExists = await fileExistsAtPath(absolutePath) + + if (!fileExists) { + cline.consecutiveMistakeCount++ + const formattedError = `File does not exist at path: ${absolutePath}\n\n\nThe specified file could not be found. Please verify the file path and try again.\n` + await cline.say("error", formattedError) + pushToolResult(formattedError) + return + } + + let parsedOperations: Array<{ + start_line: number + content: string + }> + + try { + parsedOperations = JSON.parse(operations) + if (!Array.isArray(parsedOperations)) { + throw new Error("Operations must be an array") + } + } catch (error) { + cline.consecutiveMistakeCount++ + await cline.say("error", `Failed to parse operations JSON: ${error.message}`) + pushToolResult(formatResponse.toolError("Invalid operations JSON format")) + return + } + + cline.consecutiveMistakeCount = 0 + + // Read the file + const fileContent = await fs.readFile(absolutePath, "utf8") + cline.diffViewProvider.editType = "modify" + cline.diffViewProvider.originalContent = fileContent + const lines = fileContent.split("\n") + + const updatedContent = insertGroups( + lines, + parsedOperations.map((elem) => { + return { + index: elem.start_line - 1, + elements: elem.content.split("\n"), + } + }), + ).join("\n") + + // Show changes in diff view + if (!cline.diffViewProvider.isEditing) { + await cline.ask("tool", JSON.stringify(sharedMessageProps), true).catch(() => {}) + // First open with original content + await cline.diffViewProvider.open(relPath) + await cline.diffViewProvider.update(fileContent, false) + cline.diffViewProvider.scrollToFirstDiff() + await delay(200) + } + + const diff = formatResponse.createPrettyPatch(relPath, fileContent, updatedContent) + + if (!diff) { + pushToolResult(`No changes needed for '${relPath}'`) + return + } + + await cline.diffViewProvider.update(updatedContent, true) + + const completeMessage = JSON.stringify({ + ...sharedMessageProps, + diff, + } satisfies ClineSayTool) + + const didApprove = await cline + .ask("tool", completeMessage, false) + .then((response) => response.response === "yesButtonClicked") + + if (!didApprove) { + await cline.diffViewProvider.revertChanges() + pushToolResult("Changes were rejected by the user.") + return + } + + const { newProblemsMessage, userEdits, finalContent } = await cline.diffViewProvider.saveChanges() + cline.didEditFile = true + + if (!userEdits) { + pushToolResult(`The content was successfully inserted in ${relPath.toPosix()}.${newProblemsMessage}`) + await cline.diffViewProvider.reset() + return + } + + const userFeedbackDiff = JSON.stringify({ + tool: "appliedDiff", + path: getReadablePath(cline.cwd, relPath), + diff: userEdits, + } satisfies ClineSayTool) + + console.debug("[DEBUG] User made edits, sending feedback diff:", userFeedbackDiff) + await cline.say("user_feedback_diff", userFeedbackDiff) + pushToolResult( + `The user made the following updates to your content:\n\n${userEdits}\n\n` + + `The updated content, which includes both your original modifications and the user's edits, has been successfully saved to ${relPath.toPosix()}. Here is the full, updated content of the file:\n\n` + + `\n${finalContent}\n\n\n` + + `Please note:\n` + + `1. You do not need to re-write the file with these changes, as they have already been applied.\n` + + `2. Proceed with the task using cline updated file content as the new baseline.\n` + + `3. If the user's edits have addressed part of the task or changed the requirements, adjust your approach accordingly.` + + `${newProblemsMessage}`, + ) + await cline.diffViewProvider.reset() + } catch (error) { + handleError("insert content", error) + await cline.diffViewProvider.reset() + } +} diff --git a/src/core/tools/listCodeDefinitionNamesTool.ts b/src/core/tools/listCodeDefinitionNamesTool.ts new file mode 100644 index 0000000000..46b8afae2b --- /dev/null +++ b/src/core/tools/listCodeDefinitionNamesTool.ts @@ -0,0 +1,69 @@ +import { ToolUse } from "../assistant-message" +import { HandleError, PushToolResult, RemoveClosingTag } from "./types" +import { Cline } from "../Cline" +import { AskApproval } from "./types" +import { ClineSayTool } from "../../shared/ExtensionMessage" +import { getReadablePath } from "../../utils/path" +import path from "path" +import fs from "fs/promises" +import { parseSourceCodeForDefinitionsTopLevel, parseSourceCodeDefinitionsForFile } from "../../services/tree-sitter" + +export async function listCodeDefinitionNamesTool( + cline: Cline, + block: ToolUse, + askApproval: AskApproval, + handleError: HandleError, + pushToolResult: PushToolResult, + removeClosingTag: RemoveClosingTag, +) { + const relPath: string | undefined = block.params.path + const sharedMessageProps: ClineSayTool = { + tool: "listCodeDefinitionNames", + path: getReadablePath(cline.cwd, removeClosingTag("path", relPath)), + } + try { + if (block.partial) { + const partialMessage = JSON.stringify({ + ...sharedMessageProps, + content: "", + } satisfies ClineSayTool) + await cline.ask("tool", partialMessage, block.partial).catch(() => {}) + return + } else { + if (!relPath) { + cline.consecutiveMistakeCount++ + pushToolResult(await cline.sayAndCreateMissingParamError("list_code_definition_names", "path")) + return + } + cline.consecutiveMistakeCount = 0 + const absolutePath = path.resolve(cline.cwd, relPath) + let result: string + try { + const stats = await fs.stat(absolutePath) + if (stats.isFile()) { + const fileResult = await parseSourceCodeDefinitionsForFile(absolutePath, cline.rooIgnoreController) + result = fileResult ?? "No source code definitions found in cline file." + } else if (stats.isDirectory()) { + result = await parseSourceCodeForDefinitionsTopLevel(absolutePath, cline.rooIgnoreController) + } else { + result = "The specified path is neither a file nor a directory." + } + } catch { + result = `${absolutePath}: does not exist or cannot be accessed.` + } + const completeMessage = JSON.stringify({ + ...sharedMessageProps, + content: result, + } satisfies ClineSayTool) + const didApprove = await askApproval("tool", completeMessage) + if (!didApprove) { + return + } + pushToolResult(result) + return + } + } catch (error) { + await handleError("parsing source code definitions", error) + return + } +} diff --git a/src/core/tools/listFilesTool.ts b/src/core/tools/listFilesTool.ts new file mode 100644 index 0000000000..efc9226e0a --- /dev/null +++ b/src/core/tools/listFilesTool.ts @@ -0,0 +1,76 @@ +import * as path from "path" +import { Cline } from "../Cline" +import { ClineSayTool } from "../../shared/ExtensionMessage" +import { ToolParamName, ToolUse } from "../assistant-message" +import { formatResponse } from "../prompts/responses" +import { listFiles } from "../../services/glob/list-files" +import { getReadablePath } from "../../utils/path" +import { AskApproval, HandleError, PushToolResult, RemoveClosingTag } from "./types" +/** + * Implements the list_files tool. + * + * @param cline - The instance of Cline that is executing this tool. + * @param block - The block of assistant message content that specifies the + * parameters for this tool. + * @param askApproval - A function that asks the user for approval to show a + * message. + * @param handleError - A function that handles an error that occurred while + * executing this tool. + * @param pushToolResult - A function that pushes the result of this tool to the + * conversation. + * @param removeClosingTag - A function that removes a closing tag from a string. + */ +export async function listFilesTool( + cline: Cline, + block: ToolUse, + askApproval: AskApproval, + handleError: HandleError, + pushToolResult: PushToolResult, + removeClosingTag: RemoveClosingTag, +) { + const relDirPath: string | undefined = block.params.path + const recursiveRaw: string | undefined = block.params.recursive + const recursive = recursiveRaw?.toLowerCase() === "true" + const sharedMessageProps: ClineSayTool = { + tool: !recursive ? "listFilesTopLevel" : "listFilesRecursive", + path: getReadablePath(cline.cwd, removeClosingTag("path", relDirPath)), + } + try { + if (block.partial) { + const partialMessage = JSON.stringify({ + ...sharedMessageProps, + content: "", + } satisfies ClineSayTool) + await cline.ask("tool", partialMessage, block.partial).catch(() => {}) + return + } else { + if (!relDirPath) { + cline.consecutiveMistakeCount++ + pushToolResult(await cline.sayAndCreateMissingParamError("list_files", "path")) + return + } + cline.consecutiveMistakeCount = 0 + const absolutePath = path.resolve(cline.cwd, relDirPath) + const [files, didHitLimit] = await listFiles(absolutePath, recursive, 200) + const { showRooIgnoredFiles = true } = (await cline.providerRef.deref()?.getState()) ?? {} + const result = formatResponse.formatFilesList( + absolutePath, + files, + didHitLimit, + cline.rooIgnoreController, + showRooIgnoredFiles, + ) + const completeMessage = JSON.stringify({ + ...sharedMessageProps, + content: result, + } satisfies ClineSayTool) + const didApprove = await askApproval("tool", completeMessage) + if (!didApprove) { + return + } + pushToolResult(result) + } + } catch (error) { + await handleError("listing files", error) + } +} diff --git a/src/core/tools/newTaskTool.ts b/src/core/tools/newTaskTool.ts new file mode 100644 index 0000000000..57e290c26b --- /dev/null +++ b/src/core/tools/newTaskTool.ts @@ -0,0 +1,90 @@ +import { ToolUse } from "../assistant-message" +import { HandleError, PushToolResult, RemoveClosingTag } from "./types" +import { Cline } from "../Cline" +import { AskApproval } from "./types" +import { defaultModeSlug, getModeBySlug } from "../../shared/modes" +import { formatResponse } from "../prompts/responses" +import delay from "delay" + +export async function newTaskTool( + cline: Cline, + block: ToolUse, + askApproval: AskApproval, + handleError: HandleError, + pushToolResult: PushToolResult, + removeClosingTag: RemoveClosingTag, +) { + const mode: string | undefined = block.params.mode + const message: string | undefined = block.params.message + try { + if (block.partial) { + const partialMessage = JSON.stringify({ + tool: "newTask", + mode: removeClosingTag("mode", mode), + message: removeClosingTag("message", message), + }) + await cline.ask("tool", partialMessage, block.partial).catch(() => {}) + return + } else { + if (!mode) { + cline.consecutiveMistakeCount++ + pushToolResult(await cline.sayAndCreateMissingParamError("new_task", "mode")) + return + } + if (!message) { + cline.consecutiveMistakeCount++ + pushToolResult(await cline.sayAndCreateMissingParamError("new_task", "message")) + return + } + cline.consecutiveMistakeCount = 0 + + // Verify the mode exists + const targetMode = getModeBySlug(mode, (await cline.providerRef.deref()?.getState())?.customModes) + if (!targetMode) { + pushToolResult(formatResponse.toolError(`Invalid mode: ${mode}`)) + return + } + + const toolMessage = JSON.stringify({ + tool: "newTask", + mode: targetMode.name, + content: message, + }) + const didApprove = await askApproval("tool", toolMessage) + + if (!didApprove) { + return + } + + const provider = cline.providerRef.deref() + + if (!provider) { + return + } + + // Preserve the current mode so we can resume with it later. + cline.pausedModeSlug = (await provider.getState()).mode ?? defaultModeSlug + + // Switch mode first, then create new task instance. + await provider.handleModeSwitch(mode) + + // Delay to allow mode change to take effect before next tool is executed. + await delay(500) + + const newCline = await provider.initClineWithTask(message, undefined, cline) + cline.emit("taskSpawned", newCline.taskId) + + pushToolResult(`Successfully created new task in ${targetMode.name} mode with message: ${message}`) + + // Set the isPaused flag to true so the parent + // task can wait for the sub-task to finish. + cline.isPaused = true + cline.emit("taskPaused") + + return + } + } catch (error) { + await handleError("creating new task", error) + return + } +} diff --git a/src/core/tools/readFileTool.ts b/src/core/tools/readFileTool.ts index 6616a5fcd1..8cbe89e570 100644 --- a/src/core/tools/readFileTool.ts +++ b/src/core/tools/readFileTool.ts @@ -3,6 +3,7 @@ import { Cline } from "../Cline" import { ClineSayTool } from "../../shared/ExtensionMessage" import { ToolUse } from "../assistant-message" import { formatResponse } from "../prompts/responses" +import { t } from "../../i18n" import { AskApproval, HandleError, PushToolResult, RemoveClosingTag } from "./types" import { isPathOutsideWorkspace } from "../../utils/pathUtils" import { getReadablePath } from "../../utils/path" @@ -20,149 +21,159 @@ export async function readFileTool( pushToolResult: PushToolResult, removeClosingTag: RemoveClosingTag, ) { - switch (true) { - default: - const relPath: string | undefined = block.params.path - const startLineStr: string | undefined = block.params.start_line - const endLineStr: string | undefined = block.params.end_line + const relPath: string | undefined = block.params.path + const startLineStr: string | undefined = block.params.start_line + const endLineStr: string | undefined = block.params.end_line - // Get the full path and determine if it's outside the workspace - const fullPath = relPath ? path.resolve(cline.cwd, removeClosingTag("path", relPath)) : "" - const isOutsideWorkspace = isPathOutsideWorkspace(fullPath) + // Get the full path and determine if it's outside the workspace + const fullPath = relPath ? path.resolve(cline.cwd, removeClosingTag("path", relPath)) : "" + const isOutsideWorkspace = isPathOutsideWorkspace(fullPath) - const sharedMessageProps: ClineSayTool = { - tool: "readFile", - path: getReadablePath(cline.cwd, removeClosingTag("path", relPath)), - isOutsideWorkspace, + const sharedMessageProps: ClineSayTool = { + tool: "readFile", + path: getReadablePath(cline.cwd, removeClosingTag("path", relPath)), + isOutsideWorkspace, + } + try { + if (block.partial) { + const partialMessage = JSON.stringify({ + ...sharedMessageProps, + content: undefined, + } satisfies ClineSayTool) + await cline.ask("tool", partialMessage, block.partial).catch(() => {}) + return + } else { + if (!relPath) { + cline.consecutiveMistakeCount++ + pushToolResult(await cline.sayAndCreateMissingParamError("read_file", "path")) + return } - try { - if (block.partial) { - const partialMessage = JSON.stringify({ - ...sharedMessageProps, - content: undefined, - } satisfies ClineSayTool) - await cline.ask("tool", partialMessage, block.partial).catch(() => {}) - break - } else { - if (!relPath) { - cline.consecutiveMistakeCount++ - pushToolResult(await cline.sayAndCreateMissingParamError("read_file", "path")) - break - } - // Check if we're doing a line range read - let isRangeRead = false - let startLine: number | undefined = undefined - let endLine: number | undefined = undefined + // Check if we're doing a line range read + let isRangeRead = false + let startLine: number | undefined = undefined + let endLine: number | undefined = undefined - // Check if we have either range parameter - if (startLineStr || endLineStr) { - isRangeRead = true - } + // Check if we have either range parameter + if (startLineStr || endLineStr) { + isRangeRead = true + } - // Parse start_line if provided - if (startLineStr) { - startLine = parseInt(startLineStr) - if (isNaN(startLine)) { - // Invalid start_line - cline.consecutiveMistakeCount++ - await cline.say("error", `Failed to parse start_line: ${startLineStr}`) - pushToolResult(formatResponse.toolError("Invalid start_line value")) - break - } - startLine -= 1 // Convert to 0-based index - } - - // Parse end_line if provided - if (endLineStr) { - endLine = parseInt(endLineStr) - - if (isNaN(endLine)) { - // Invalid end_line - cline.consecutiveMistakeCount++ - await cline.say("error", `Failed to parse end_line: ${endLineStr}`) - pushToolResult(formatResponse.toolError("Invalid end_line value")) - break - } - - // Convert to 0-based index - endLine -= 1 - } - - const accessAllowed = cline.rooIgnoreController?.validateAccess(relPath) - if (!accessAllowed) { - await cline.say("rooignore_error", relPath) - pushToolResult(formatResponse.toolError(formatResponse.rooIgnoreError(relPath))) - - break - } - - cline.consecutiveMistakeCount = 0 - const absolutePath = path.resolve(cline.cwd, relPath) - const completeMessage = JSON.stringify({ - ...sharedMessageProps, - content: absolutePath, - } satisfies ClineSayTool) - - const didApprove = await askApproval("tool", completeMessage) - if (!didApprove) { - break - } - - // Get the maxReadFileLine setting - const { maxReadFileLine = 500 } = (await cline.providerRef.deref()?.getState()) ?? {} - - // Count total lines in the file - let totalLines = 0 - try { - totalLines = await countFileLines(absolutePath) - } catch (error) { - console.error(`Error counting lines in file ${absolutePath}:`, error) - } - - // now execute the tool like normal - let content: string - let isFileTruncated = false - let sourceCodeDef = "" - - const isBinary = await isBinaryFile(absolutePath).catch(() => false) - - if (isRangeRead) { - if (startLine === undefined) { - content = addLineNumbers(await readLines(absolutePath, endLine, startLine)) - } else { - content = addLineNumbers(await readLines(absolutePath, endLine, startLine), startLine + 1) - } - } else if (!isBinary && maxReadFileLine >= 0 && totalLines > maxReadFileLine) { - // If file is too large, only read the first maxReadFileLine lines - isFileTruncated = true - - const res = await Promise.all([ - maxReadFileLine > 0 ? readLines(absolutePath, maxReadFileLine - 1, 0) : "", - parseSourceCodeDefinitionsForFile(absolutePath, cline.rooIgnoreController), - ]) - - content = res[0].length > 0 ? addLineNumbers(res[0]) : "" - const result = res[1] - if (result) { - sourceCodeDef = `\n\n${result}` - } - } else { - // Read entire file - content = await extractTextFromFile(absolutePath) - } - - // Add truncation notice if applicable - if (isFileTruncated) { - content += `\n\n[Showing only ${maxReadFileLine} of ${totalLines} total lines. Use start_line and end_line if you need to read more]${sourceCodeDef}` - } - - pushToolResult(content) - break + // Parse start_line if provided + if (startLineStr) { + startLine = parseInt(startLineStr) + if (isNaN(startLine)) { + // Invalid start_line + cline.consecutiveMistakeCount++ + await cline.say("error", `Failed to parse start_line: ${startLineStr}`) + pushToolResult(formatResponse.toolError("Invalid start_line value")) + return } - } catch (error) { - await handleError("reading file", error) - break + startLine -= 1 // Convert to 0-based index } + + // Parse end_line if provided + if (endLineStr) { + endLine = parseInt(endLineStr) + + if (isNaN(endLine)) { + // Invalid end_line + cline.consecutiveMistakeCount++ + await cline.say("error", `Failed to parse end_line: ${endLineStr}`) + pushToolResult(formatResponse.toolError("Invalid end_line value")) + return + } + + // Convert to 0-based index + endLine -= 1 + } + + const accessAllowed = cline.rooIgnoreController?.validateAccess(relPath) + if (!accessAllowed) { + await cline.say("rooignore_error", relPath) + pushToolResult(formatResponse.toolError(formatResponse.rooIgnoreError(relPath))) + + return + } + + const { maxReadFileLine = 500 } = (await cline.providerRef.deref()?.getState()) ?? {} + + // Create line snippet description for approval message + let lineSnippet = "" + if (startLine !== undefined && endLine !== undefined) { + lineSnippet = t("tools:readFile.linesRange", { start: startLine + 1, end: endLine + 1 }) + } else if (startLine !== undefined) { + lineSnippet = t("tools:readFile.linesFromToEnd", { start: startLine + 1 }) + } else if (endLine !== undefined) { + lineSnippet = t("tools:readFile.linesFromStartTo", { end: endLine + 1 }) + } else if (maxReadFileLine === 0) { + lineSnippet = t("tools:readFile.definitionsOnly") + } else if (maxReadFileLine > 0) { + lineSnippet = t("tools:readFile.maxLines", { max: maxReadFileLine }) + } + + cline.consecutiveMistakeCount = 0 + const absolutePath = path.resolve(cline.cwd, relPath) + + const completeMessage = JSON.stringify({ + ...sharedMessageProps, + content: absolutePath, + reason: lineSnippet, + } satisfies ClineSayTool) + + const didApprove = await askApproval("tool", completeMessage) + if (!didApprove) { + return + } + + // Count total lines in the file + let totalLines = 0 + try { + totalLines = await countFileLines(absolutePath) + } catch (error) { + console.error(`Error counting lines in file ${absolutePath}:`, error) + } + + // now execute the tool like normal + let content: string + let isFileTruncated = false + let sourceCodeDef = "" + + const isBinary = await isBinaryFile(absolutePath).catch(() => false) + + if (isRangeRead) { + if (startLine === undefined) { + content = addLineNumbers(await readLines(absolutePath, endLine, startLine)) + } else { + content = addLineNumbers(await readLines(absolutePath, endLine, startLine), startLine + 1) + } + } else if (!isBinary && maxReadFileLine >= 0 && totalLines > maxReadFileLine) { + // If file is too large, only read the first maxReadFileLine lines + isFileTruncated = true + + const res = await Promise.all([ + maxReadFileLine > 0 ? readLines(absolutePath, maxReadFileLine - 1, 0) : "", + parseSourceCodeDefinitionsForFile(absolutePath, cline.rooIgnoreController), + ]) + + content = res[0].length > 0 ? addLineNumbers(res[0]) : "" + const result = res[1] + if (result) { + sourceCodeDef = `\n\n${result}` + } + } else { + // Read entire file + content = await extractTextFromFile(absolutePath) + } + + // Add truncation notice if applicable + if (isFileTruncated) { + content += `\n\n[Showing only ${maxReadFileLine} of ${totalLines} total lines. Use start_line and end_line if you need to read more]${sourceCodeDef}` + } + + pushToolResult(content) + } + } catch (error) { + await handleError("reading file", error) } } diff --git a/src/core/tools/searchAndReplaceTool.ts b/src/core/tools/searchAndReplaceTool.ts new file mode 100644 index 0000000000..3b204273bb --- /dev/null +++ b/src/core/tools/searchAndReplaceTool.ts @@ -0,0 +1,181 @@ +import { Cline } from "../Cline" +import { ToolUse } from "../assistant-message" +import { AskApproval, HandleError, PushToolResult, RemoveClosingTag } from "./types" +import { formatResponse } from "../prompts/responses" +import { ClineSayTool } from "../../shared/ExtensionMessage" +import { getReadablePath } from "../../utils/path" +import path from "path" +import { fileExistsAtPath } from "../../utils/fs" +import { addLineNumbers } from "../../integrations/misc/extract-text" +import fs from "fs/promises" + +export async function searchAndReplaceTool( + cline: Cline, + block: ToolUse, + askApproval: AskApproval, + handleError: HandleError, + pushToolResult: PushToolResult, + removeClosingTag: RemoveClosingTag, +) { + const relPath: string | undefined = block.params.path + const operations: string | undefined = block.params.operations + + const sharedMessageProps: ClineSayTool = { + tool: "appliedDiff", + path: getReadablePath(cline.cwd, removeClosingTag("path", relPath)), + } + + try { + if (block.partial) { + const partialMessage = JSON.stringify({ + path: removeClosingTag("path", relPath), + operations: removeClosingTag("operations", operations), + }) + await cline.ask("tool", partialMessage, block.partial).catch(() => {}) + return + } else { + if (!relPath) { + cline.consecutiveMistakeCount++ + pushToolResult(await cline.sayAndCreateMissingParamError("search_and_replace", "path")) + return + } + if (!operations) { + cline.consecutiveMistakeCount++ + pushToolResult(await cline.sayAndCreateMissingParamError("search_and_replace", "operations")) + return + } + + const absolutePath = path.resolve(cline.cwd, relPath) + const fileExists = await fileExistsAtPath(absolutePath) + + if (!fileExists) { + cline.consecutiveMistakeCount++ + const formattedError = `File does not exist at path: ${absolutePath}\n\n\nThe specified file could not be found. Please verify the file path and try again.\n` + await cline.say("error", formattedError) + pushToolResult(formattedError) + return + } + + let parsedOperations: Array<{ + search: string + replace: string + start_line?: number + end_line?: number + use_regex?: boolean + ignore_case?: boolean + regex_flags?: string + }> + + try { + parsedOperations = JSON.parse(operations) + if (!Array.isArray(parsedOperations)) { + throw new Error("Operations must be an array") + } + } catch (error) { + cline.consecutiveMistakeCount++ + await cline.say("error", `Failed to parse operations JSON: ${error.message}`) + pushToolResult(formatResponse.toolError("Invalid operations JSON format")) + return + } + + // Read the original file content + const fileContent = await fs.readFile(absolutePath, "utf-8") + cline.diffViewProvider.editType = "modify" + cline.diffViewProvider.originalContent = fileContent + let lines = fileContent.split("\n") + + for (const op of parsedOperations) { + const flags = op.regex_flags ?? (op.ignore_case ? "gi" : "g") + const multilineFlags = flags.includes("m") ? flags : flags + "m" + + const searchPattern = op.use_regex + ? new RegExp(op.search, multilineFlags) + : new RegExp(escapeRegExp(op.search), multilineFlags) + + if (op.start_line || op.end_line) { + const startLine = Math.max((op.start_line ?? 1) - 1, 0) + const endLine = Math.min((op.end_line ?? lines.length) - 1, lines.length - 1) + + // Get the content before and after the target section + const beforeLines = lines.slice(0, startLine) + const afterLines = lines.slice(endLine + 1) + + // Get the target section and perform replacement + const targetContent = lines.slice(startLine, endLine + 1).join("\n") + const modifiedContent = targetContent.replace(searchPattern, op.replace) + const modifiedLines = modifiedContent.split("\n") + + // Reconstruct the full content with the modified section + lines = [...beforeLines, ...modifiedLines, ...afterLines] + } else { + // Global replacement + const fullContent = lines.join("\n") + const modifiedContent = fullContent.replace(searchPattern, op.replace) + lines = modifiedContent.split("\n") + } + } + + const newContent = lines.join("\n") + + cline.consecutiveMistakeCount = 0 + + // Show diff preview + const diff = formatResponse.createPrettyPatch(relPath, fileContent, newContent) + + if (!diff) { + pushToolResult(`No changes needed for '${relPath}'`) + return + } + + await cline.diffViewProvider.open(relPath) + await cline.diffViewProvider.update(newContent, true) + cline.diffViewProvider.scrollToFirstDiff() + + const completeMessage = JSON.stringify({ + ...sharedMessageProps, + diff: diff, + } satisfies ClineSayTool) + + const didApprove = await askApproval("tool", completeMessage) + if (!didApprove) { + await cline.diffViewProvider.revertChanges() // cline likely handles closing the diff view + return + } + + const { newProblemsMessage, userEdits, finalContent } = await cline.diffViewProvider.saveChanges() + cline.didEditFile = true // used to determine if we should wait for busy terminal to update before sending api request + if (userEdits) { + await cline.say( + "user_feedback_diff", + JSON.stringify({ + tool: fileExists ? "editedExistingFile" : "newFileCreated", + path: getReadablePath(cline.cwd, relPath), + diff: userEdits, + } satisfies ClineSayTool), + ) + pushToolResult( + `The user made the following updates to your content:\n\n${userEdits}\n\n` + + `The updated content, which includes both your original modifications and the user's edits, has been successfully saved to ${relPath.toPosix()}. Here is the full, updated content of the file, including line numbers:\n\n` + + `\n${addLineNumbers(finalContent || "")}\n\n\n` + + `Please note:\n` + + `1. You do not need to re-write the file with these changes, as they have already been applied.\n` + + `2. Proceed with the task using cline updated file content as the new baseline.\n` + + `3. If the user's edits have addressed part of the task or changed the requirements, adjust your approach accordingly.` + + `${newProblemsMessage}`, + ) + } else { + pushToolResult(`Changes successfully applied to ${relPath.toPosix()}:\n\n${newProblemsMessage}`) + } + await cline.diffViewProvider.reset() + return + } + } catch (error) { + await handleError("applying search and replace", error) + await cline.diffViewProvider.reset() + return + } +} + +function escapeRegExp(string: string): string { + return string.replace(/[.*+?^${}()|[\]\\]/g, "\\$&") +} diff --git a/src/core/tools/searchFilesTool.ts b/src/core/tools/searchFilesTool.ts new file mode 100644 index 0000000000..e3659da9a1 --- /dev/null +++ b/src/core/tools/searchFilesTool.ts @@ -0,0 +1,69 @@ +import { Cline } from "../Cline" +import { ToolUse } from "../assistant-message" +import { AskApproval, HandleError, PushToolResult, RemoveClosingTag } from "./types" +import { ClineSayTool } from "../../shared/ExtensionMessage" +import { getReadablePath } from "../../utils/path" +import path from "path" +import { regexSearchFiles } from "../../services/ripgrep" + +export async function searchFilesTool( + cline: Cline, + block: ToolUse, + askApproval: AskApproval, + handleError: HandleError, + pushToolResult: PushToolResult, + removeClosingTag: RemoveClosingTag, +) { + const relDirPath: string | undefined = block.params.path + const regex: string | undefined = block.params.regex + const filePattern: string | undefined = block.params.file_pattern + const sharedMessageProps: ClineSayTool = { + tool: "searchFiles", + path: getReadablePath(cline.cwd, removeClosingTag("path", relDirPath)), + regex: removeClosingTag("regex", regex), + filePattern: removeClosingTag("file_pattern", filePattern), + } + try { + if (block.partial) { + const partialMessage = JSON.stringify({ + ...sharedMessageProps, + content: "", + } satisfies ClineSayTool) + await cline.ask("tool", partialMessage, block.partial).catch(() => {}) + return + } else { + if (!relDirPath) { + cline.consecutiveMistakeCount++ + pushToolResult(await cline.sayAndCreateMissingParamError("search_files", "path")) + return + } + if (!regex) { + cline.consecutiveMistakeCount++ + pushToolResult(await cline.sayAndCreateMissingParamError("search_files", "regex")) + return + } + cline.consecutiveMistakeCount = 0 + const absolutePath = path.resolve(cline.cwd, relDirPath) + const results = await regexSearchFiles( + cline.cwd, + absolutePath, + regex, + filePattern, + cline.rooIgnoreController, + ) + const completeMessage = JSON.stringify({ + ...sharedMessageProps, + content: results, + } satisfies ClineSayTool) + const didApprove = await askApproval("tool", completeMessage) + if (!didApprove) { + return + } + pushToolResult(results) + return + } + } catch (error) { + await handleError("searching files", error) + return + } +} diff --git a/src/core/tools/switchModeTool.ts b/src/core/tools/switchModeTool.ts new file mode 100644 index 0000000000..48e6e59fe0 --- /dev/null +++ b/src/core/tools/switchModeTool.ts @@ -0,0 +1,75 @@ +import { Cline } from "../Cline" +import { ToolUse } from "../assistant-message" +import { AskApproval, HandleError, PushToolResult, RemoveClosingTag } from "./types" +import { formatResponse } from "../prompts/responses" +import { defaultModeSlug } from "../../shared/modes" +import { getModeBySlug } from "../../shared/modes" +import delay from "delay" + +export async function switchModeTool( + cline: Cline, + block: ToolUse, + askApproval: AskApproval, + handleError: HandleError, + pushToolResult: PushToolResult, + removeClosingTag: RemoveClosingTag, +) { + const mode_slug: string | undefined = block.params.mode_slug + const reason: string | undefined = block.params.reason + try { + if (block.partial) { + const partialMessage = JSON.stringify({ + tool: "switchMode", + mode: removeClosingTag("mode_slug", mode_slug), + reason: removeClosingTag("reason", reason), + }) + await cline.ask("tool", partialMessage, block.partial).catch(() => {}) + return + } else { + if (!mode_slug) { + cline.consecutiveMistakeCount++ + pushToolResult(await cline.sayAndCreateMissingParamError("switch_mode", "mode_slug")) + return + } + cline.consecutiveMistakeCount = 0 + + // Verify the mode exists + const targetMode = getModeBySlug(mode_slug, (await cline.providerRef.deref()?.getState())?.customModes) + if (!targetMode) { + pushToolResult(formatResponse.toolError(`Invalid mode: ${mode_slug}`)) + return + } + + // Check if already in requested mode + const currentMode = (await cline.providerRef.deref()?.getState())?.mode ?? defaultModeSlug + if (currentMode === mode_slug) { + pushToolResult(`Already in ${targetMode.name} mode.`) + return + } + + const completeMessage = JSON.stringify({ + tool: "switchMode", + mode: mode_slug, + reason, + }) + + const didApprove = await askApproval("tool", completeMessage) + if (!didApprove) { + return + } + + // Switch the mode using shared handler + await cline.providerRef.deref()?.handleModeSwitch(mode_slug) + pushToolResult( + `Successfully switched from ${getModeBySlug(currentMode)?.name ?? currentMode} mode to ${ + targetMode.name + } mode${reason ? ` because: ${reason}` : ""}.`, + ) + await delay(500) // delay to allow mode change to take effect before next tool is executed + return + } + } catch (error) { + await handleError("switching mode", error) + return + } +} diff --git a/src/core/tools/types.ts b/src/core/tools/types.ts index 87d7d1ed36..5b027241f6 100644 --- a/src/core/tools/types.ts +++ b/src/core/tools/types.ts @@ -8,8 +8,12 @@ export type AskApproval = ( progressStatus?: ToolProgressStatus, ) => Promise -export type HandleError = (action: string, error: Error) => void +export type HandleError = (action: string, error: Error) => Promise export type PushToolResult = (content: ToolResponse) => void export type RemoveClosingTag = (tag: ToolParamName, content?: string) => string + +export type AskFinishSubTaskApproval = () => Promise + +export type ToolDescription = () => string diff --git a/src/core/tools/useMcpToolTool.ts b/src/core/tools/useMcpToolTool.ts new file mode 100644 index 0000000000..699f693a13 --- /dev/null +++ b/src/core/tools/useMcpToolTool.ts @@ -0,0 +1,100 @@ +import { Cline } from "../Cline" +import { ToolUse } from "../assistant-message" +import { AskApproval, HandleError, PushToolResult, RemoveClosingTag } from "./types" +import { formatResponse } from "../prompts/responses" +import { ClineAskUseMcpServer } from "../../shared/ExtensionMessage" + +export async function useMcpToolTool( + cline: Cline, + block: ToolUse, + askApproval: AskApproval, + handleError: HandleError, + pushToolResult: PushToolResult, + removeClosingTag: RemoveClosingTag, +) { + const server_name: string | undefined = block.params.server_name + const tool_name: string | undefined = block.params.tool_name + const mcp_arguments: string | undefined = block.params.arguments + try { + if (block.partial) { + const partialMessage = JSON.stringify({ + type: "use_mcp_tool", + serverName: removeClosingTag("server_name", server_name), + toolName: removeClosingTag("tool_name", tool_name), + arguments: removeClosingTag("arguments", mcp_arguments), + } satisfies ClineAskUseMcpServer) + await cline.ask("use_mcp_server", partialMessage, block.partial).catch(() => {}) + return + } else { + if (!server_name) { + cline.consecutiveMistakeCount++ + pushToolResult(await cline.sayAndCreateMissingParamError("use_mcp_tool", "server_name")) + return + } + if (!tool_name) { + cline.consecutiveMistakeCount++ + pushToolResult(await cline.sayAndCreateMissingParamError("use_mcp_tool", "tool_name")) + return + } + // arguments are optional, but if they are provided they must be valid JSON + // if (!mcp_arguments) { + // cline.consecutiveMistakeCount++ + // pushToolResult(await cline.sayAndCreateMissingParamError("use_mcp_tool", "arguments")) + // return + // } + let parsedArguments: Record | undefined + if (mcp_arguments) { + try { + parsedArguments = JSON.parse(mcp_arguments) + } catch (error) { + cline.consecutiveMistakeCount++ + await cline.say("error", `Roo tried to use ${tool_name} with an invalid JSON argument. Retrying...`) + pushToolResult( + formatResponse.toolError(formatResponse.invalidMcpToolArgumentError(server_name, tool_name)), + ) + return + } + } + cline.consecutiveMistakeCount = 0 + const completeMessage = JSON.stringify({ + type: "use_mcp_tool", + serverName: server_name, + toolName: tool_name, + arguments: mcp_arguments, + } satisfies ClineAskUseMcpServer) + const didApprove = await askApproval("use_mcp_server", completeMessage) + if (!didApprove) { + return + } + // now execute the tool + await cline.say("mcp_server_request_started") // same as browser_action_result + const toolResult = await cline.providerRef + .deref() + ?.getMcpHub() + ?.callTool(server_name, tool_name, parsedArguments) + + // TODO: add progress indicator and ability to parse images and non-text responses + const toolResultPretty = + (toolResult?.isError ? "Error:\n" : "") + + toolResult?.content + .map((item) => { + if (item.type === "text") { + return item.text + } + if (item.type === "resource") { + const { blob, ...rest } = item.resource + return JSON.stringify(rest, null, 2) + } + return "" + }) + .filter(Boolean) + .join("\n\n") || "(No response)" + await cline.say("mcp_server_response", toolResultPretty) + pushToolResult(formatResponse.toolResult(toolResultPretty)) + return + } + } catch (error) { + await handleError("executing MCP tool", error) + return + } +} diff --git a/src/core/tools/writeToFileTool.ts b/src/core/tools/writeToFileTool.ts new file mode 100644 index 0000000000..5c24584b91 --- /dev/null +++ b/src/core/tools/writeToFileTool.ts @@ -0,0 +1,209 @@ +import * as vscode from "vscode" + +import { Cline } from "../Cline" +import { ClineSayTool } from "../../shared/ExtensionMessage" +import { ToolUse } from "../assistant-message" +import { formatResponse } from "../prompts/responses" +import { AskApproval, HandleError, PushToolResult, RemoveClosingTag } from "./types" +import path from "path" +import { fileExistsAtPath } from "../../utils/fs" +import { addLineNumbers, stripLineNumbers } from "../../integrations/misc/extract-text" +import { getReadablePath } from "../../utils/path" +import { isPathOutsideWorkspace } from "../../utils/pathUtils" +import { everyLineHasLineNumbers } from "../../integrations/misc/extract-text" +import delay from "delay" +import { detectCodeOmission } from "../../integrations/editor/detect-omission" + +export async function writeToFileTool( + cline: Cline, + block: ToolUse, + askApproval: AskApproval, + handleError: HandleError, + pushToolResult: PushToolResult, + removeClosingTag: RemoveClosingTag, +) { + const relPath: string | undefined = block.params.path + let newContent: string | undefined = block.params.content + let predictedLineCount: number | undefined = parseInt(block.params.line_count ?? "0") + if (!relPath || !newContent) { + // checking for newContent ensure relPath is complete + // wait so we can determine if it's a new file or editing an existing file + return + } + + const accessAllowed = cline.rooIgnoreController?.validateAccess(relPath) + if (!accessAllowed) { + await cline.say("rooignore_error", relPath) + pushToolResult(formatResponse.toolError(formatResponse.rooIgnoreError(relPath))) + + return + } + + // Check if file exists using cached map or fs.access + let fileExists: boolean + if (cline.diffViewProvider.editType !== undefined) { + fileExists = cline.diffViewProvider.editType === "modify" + } else { + const absolutePath = path.resolve(cline.cwd, relPath) + fileExists = await fileExistsAtPath(absolutePath) + cline.diffViewProvider.editType = fileExists ? "modify" : "create" + } + + // pre-processing newContent for cases where weaker models might add artifacts like markdown codeblock markers (deepseek/llama) or extra escape characters (gemini) + if (newContent.startsWith("```")) { + // cline handles cases where it includes language specifiers like ```python ```js + newContent = newContent.split("\n").slice(1).join("\n").trim() + } + if (newContent.endsWith("```")) { + newContent = newContent.split("\n").slice(0, -1).join("\n").trim() + } + + if (!cline.api.getModel().id.includes("claude")) { + // it seems not just llama models are doing cline, but also gemini and potentially others + if (newContent.includes(">") || newContent.includes("<") || newContent.includes(""")) { + newContent = newContent + .replace(/>/g, ">") + .replace(/</g, "<") + .replace(/"/g, '"') + } + } + + // Determine if the path is outside the workspace + const fullPath = relPath ? path.resolve(cline.cwd, removeClosingTag("path", relPath)) : "" + const isOutsideWorkspace = isPathOutsideWorkspace(fullPath) + + const sharedMessageProps: ClineSayTool = { + tool: fileExists ? "editedExistingFile" : "newFileCreated", + path: getReadablePath(cline.cwd, removeClosingTag("path", relPath)), + isOutsideWorkspace, + } + try { + if (block.partial) { + // update gui message + const partialMessage = JSON.stringify(sharedMessageProps) + await cline.ask("tool", partialMessage, block.partial).catch(() => {}) + // update editor + if (!cline.diffViewProvider.isEditing) { + // open the editor and prepare to stream content in + await cline.diffViewProvider.open(relPath) + } + // editor is open, stream content in + await cline.diffViewProvider.update( + everyLineHasLineNumbers(newContent) ? stripLineNumbers(newContent) : newContent, + false, + ) + return + } else { + if (!relPath) { + cline.consecutiveMistakeCount++ + pushToolResult(await cline.sayAndCreateMissingParamError("write_to_file", "path")) + await cline.diffViewProvider.reset() + return + } + if (!newContent) { + cline.consecutiveMistakeCount++ + pushToolResult(await cline.sayAndCreateMissingParamError("write_to_file", "content")) + await cline.diffViewProvider.reset() + return + } + if (!predictedLineCount) { + cline.consecutiveMistakeCount++ + pushToolResult(await cline.sayAndCreateMissingParamError("write_to_file", "line_count")) + await cline.diffViewProvider.reset() + return + } + cline.consecutiveMistakeCount = 0 + + // if isEditingFile false, that means we have the full contents of the file already. + // it's important to note how cline function works, you can't make the assumption that the block.partial conditional will always be called since it may immediately get complete, non-partial data. So cline part of the logic will always be called. + // in other words, you must always repeat the block.partial logic here + if (!cline.diffViewProvider.isEditing) { + // show gui message before showing edit animation + const partialMessage = JSON.stringify(sharedMessageProps) + await cline.ask("tool", partialMessage, true).catch(() => {}) // sending true for partial even though it's not a partial, cline shows the edit row before the content is streamed into the editor + await cline.diffViewProvider.open(relPath) + } + await cline.diffViewProvider.update( + everyLineHasLineNumbers(newContent) ? stripLineNumbers(newContent) : newContent, + true, + ) + await delay(300) // wait for diff view to update + cline.diffViewProvider.scrollToFirstDiff() + + // Check for code omissions before proceeding + if (detectCodeOmission(cline.diffViewProvider.originalContent || "", newContent, predictedLineCount)) { + if (cline.diffStrategy) { + await cline.diffViewProvider.revertChanges() + pushToolResult( + formatResponse.toolError( + `Content appears to be truncated (file has ${ + newContent.split("\n").length + } lines but was predicted to have ${predictedLineCount} lines), and found comments indicating omitted code (e.g., '// rest of code unchanged', '/* previous code */'). Please provide the complete file content without any omissions if possible, or otherwise use the 'apply_diff' tool to apply the diff to the original file.`, + ), + ) + return + } else { + vscode.window + .showWarningMessage( + "Potential code truncation detected. cline happens when the AI reaches its max output limit.", + "Follow cline guide to fix the issue", + ) + .then((selection) => { + if (selection === "Follow cline guide to fix the issue") { + vscode.env.openExternal( + vscode.Uri.parse( + "https://github.com/cline/cline/wiki/Troubleshooting-%E2%80%90-Cline-Deleting-Code-with-%22Rest-of-Code-Here%22-Comments", + ), + ) + } + }) + } + } + + const completeMessage = JSON.stringify({ + ...sharedMessageProps, + content: fileExists ? undefined : newContent, + diff: fileExists + ? formatResponse.createPrettyPatch(relPath, cline.diffViewProvider.originalContent, newContent) + : undefined, + } satisfies ClineSayTool) + const didApprove = await askApproval("tool", completeMessage) + if (!didApprove) { + await cline.diffViewProvider.revertChanges() + return + } + const { newProblemsMessage, userEdits, finalContent } = await cline.diffViewProvider.saveChanges() + cline.didEditFile = true // used to determine if we should wait for busy terminal to update before sending api request + if (userEdits) { + await cline.say( + "user_feedback_diff", + JSON.stringify({ + tool: fileExists ? "editedExistingFile" : "newFileCreated", + path: getReadablePath(cline.cwd, relPath), + diff: userEdits, + } satisfies ClineSayTool), + ) + pushToolResult( + `The user made the following updates to your content:\n\n${userEdits}\n\n` + + `The updated content, which includes both your original modifications and the user's edits, has been successfully saved to ${relPath.toPosix()}. Here is the full, updated content of the file, including line numbers:\n\n` + + `\n${addLineNumbers( + finalContent || "", + )}\n\n\n` + + `Please note:\n` + + `1. You do not need to re-write the file with these changes, as they have already been applied.\n` + + `2. Proceed with the task using cline updated file content as the new baseline.\n` + + `3. If the user's edits have addressed part of the task or changed the requirements, adjust your approach accordingly.` + + `${newProblemsMessage}`, + ) + } else { + pushToolResult(`The content was successfully saved to ${relPath.toPosix()}.${newProblemsMessage}`) + } + await cline.diffViewProvider.reset() + return + } + } catch (error) { + await handleError("writing file", error) + await cline.diffViewProvider.reset() + return + } +} diff --git a/src/core/webview/ClineProvider.ts b/src/core/webview/ClineProvider.ts index 021f95adf5..f0027b322a 100644 --- a/src/core/webview/ClineProvider.ts +++ b/src/core/webview/ClineProvider.ts @@ -50,7 +50,7 @@ import { McpHub } from "../../services/mcp/McpHub" import { McpServerManager } from "../../services/mcp/McpServerManager" import { ShadowCheckpointService } from "../../services/checkpoints/ShadowCheckpointService" import { BrowserSession } from "../../services/browser/BrowserSession" -import { discoverChromeInstances } from "../../services/browser/browserDiscovery" +import { discoverChromeHostUrl, tryChromeHostUrl } from "../../services/browser/browserDiscovery" import { searchWorkspaceFiles } from "../../services/search/file-search" import { fileExistsAtPath } from "../../utils/fs" import { playSound, setSoundEnabled, setSoundVolume } from "../../utils/sound" @@ -102,7 +102,7 @@ export class ClineProvider extends EventEmitter implements protected mcpHub?: McpHub // Change from private to protected private latestAnnouncementId = "mar-20-2025-3-10" // update to some unique identifier when we add a new announcement private settingsImportedAt?: number - private contextProxy: ContextProxy + public readonly contextProxy: ContextProxy public readonly providerSettingsManager: ProviderSettingsManager public readonly customModesManager: CustomModesManager @@ -1250,7 +1250,7 @@ export class ClineProvider extends EventEmitter implements } case "openProjectMcpSettings": { if (!vscode.workspace.workspaceFolders?.length) { - vscode.window.showErrorMessage(t("common:no_workspace")) + vscode.window.showErrorMessage(t("common:errors.no_workspace")) return } @@ -1420,74 +1420,17 @@ export class ClineProvider extends EventEmitter implements await this.postStateToWebview() break case "testBrowserConnection": - try { - const browserSession = new BrowserSession(this.context) - // If no text is provided, try auto-discovery - if (!message.text) { - try { - const discoveredHost = await discoverChromeInstances() - if (discoveredHost) { - // Test the connection to the discovered host - const result = await browserSession.testConnection(discoveredHost) - // Send the result back to the webview - await this.postMessageToWebview({ - type: "browserConnectionResult", - success: result.success, - text: `Auto-discovered and tested connection to Chrome at ${discoveredHost}: ${result.message}`, - values: { endpoint: result.endpoint }, - }) - } else { - await this.postMessageToWebview({ - type: "browserConnectionResult", - success: false, - text: "No Chrome instances found on the network. Make sure Chrome is running with remote debugging enabled (--remote-debugging-port=9222).", - }) - } - } catch (error) { - await this.postMessageToWebview({ - type: "browserConnectionResult", - success: false, - text: `Error during auto-discovery: ${error instanceof Error ? error.message : String(error)}`, - }) - } - } else { - // Test the provided URL - const result = await browserSession.testConnection(message.text) - + // If no text is provided, try auto-discovery + if (!message.text) { + // Use testBrowserConnection for auto-discovery + const chromeHostUrl = await discoverChromeHostUrl() + if (chromeHostUrl) { // Send the result back to the webview await this.postMessageToWebview({ type: "browserConnectionResult", - success: result.success, - text: result.message, - values: { endpoint: result.endpoint }, - }) - } - } catch (error) { - await this.postMessageToWebview({ - type: "browserConnectionResult", - success: false, - text: `Error testing connection: ${error instanceof Error ? error.message : String(error)}`, - }) - } - break - case "discoverBrowser": - try { - const discoveredHost = await discoverChromeInstances() - - if (discoveredHost) { - // Don't update the remoteBrowserHost state when auto-discovering - // This way we don't override the user's preference - - // Test the connection to get the endpoint - const browserSession = new BrowserSession(this.context) - const result = await browserSession.testConnection(discoveredHost) - - // Send the result back to the webview - await this.postMessageToWebview({ - type: "browserConnectionResult", - success: true, - text: `Successfully discovered and connected to Chrome at ${discoveredHost}`, - values: { endpoint: result.endpoint }, + success: !!chromeHostUrl, + text: `Auto-discovered and tested connection to Chrome: ${chromeHostUrl}`, + values: { endpoint: chromeHostUrl }, }) } else { await this.postMessageToWebview({ @@ -1496,11 +1439,17 @@ export class ClineProvider extends EventEmitter implements text: "No Chrome instances found on the network. Make sure Chrome is running with remote debugging enabled (--remote-debugging-port=9222).", }) } - } catch (error) { + } else { + // Test the provided URL + const customHostUrl = message.text + const hostIsValid = await tryChromeHostUrl(message.text) + // Send the result back to the webview await this.postMessageToWebview({ type: "browserConnectionResult", - success: false, - text: `Error discovering browser: ${error instanceof Error ? error.message : String(error)}`, + success: hostIsValid, + text: hostIsValid + ? `Successfully connected to Chrome: ${customHostUrl}` + : "Failed to connect to Chrome", }) } break @@ -1590,6 +1539,7 @@ export class ClineProvider extends EventEmitter implements t("common:confirmation.just_this_message"), t("common:confirmation.this_and_subsequent"), ) + if ( (answer === t("common:confirmation.just_this_message") || answer === t("common:confirmation.this_and_subsequent")) && @@ -1598,9 +1548,11 @@ export class ClineProvider extends EventEmitter implements message.value ) { const timeCutoff = message.value - 1000 // 1 second buffer before the message to delete + const messageIndex = this.getCurrentCline()!.clineMessages.findIndex( (msg) => msg.ts && msg.ts >= timeCutoff, ) + const apiConversationHistoryIndex = this.getCurrentCline()?.apiConversationHistory.findIndex( (msg) => msg.ts && msg.ts >= timeCutoff, @@ -1621,6 +1573,7 @@ export class ClineProvider extends EventEmitter implements const nextUserMessageIndex = this.getCurrentCline()!.clineMessages.findIndex( (msg) => msg === nextUserMessage, ) + // Keep messages before current message and after next user message await this.getCurrentCline()!.overwriteClineMessages([ ...this.getCurrentCline()!.clineMessages.slice(0, messageIndex), @@ -2032,12 +1985,11 @@ export class ClineProvider extends EventEmitter implements await this.updateGlobalState("experiments", updatedExperiments) - // Update diffStrategy in current Cline instance if it exists - if (message.values[EXPERIMENT_IDS.DIFF_STRATEGY] !== undefined && this.getCurrentCline()) { - await this.getCurrentCline()!.updateDiffStrategy( - Experiments.isEnabled(updatedExperiments, EXPERIMENT_IDS.DIFF_STRATEGY), - Experiments.isEnabled(updatedExperiments, EXPERIMENT_IDS.MULTI_SEARCH_AND_REPLACE), - ) + const currentCline = this.getCurrentCline() + + // Update diffStrategy in current Cline instance if it exists. + if (message.values[EXPERIMENT_IDS.DIFF_STRATEGY_UNIFIED] !== undefined && currentCline) { + await currentCline.updateDiffStrategy(updatedExperiments) } await this.postStateToWebview() @@ -2135,13 +2087,13 @@ export class ClineProvider extends EventEmitter implements language, } = await this.getState() - // Create diffStrategy based on current model and settings - const diffStrategy = getDiffStrategy( - apiConfiguration.apiModelId || apiConfiguration.openRouterModelId || "", + // Create diffStrategy based on current model and settings. + const diffStrategy = getDiffStrategy({ + model: apiConfiguration.apiModelId || apiConfiguration.openRouterModelId || "", + experiments, fuzzyMatchThreshold, - Experiments.isEnabled(experiments, EXPERIMENT_IDS.DIFF_STRATEGY), - Experiments.isEnabled(experiments, EXPERIMENT_IDS.MULTI_SEARCH_AND_REPLACE), - ) + }) + const cwd = this.cwd const mode = message.mode ?? defaultModeSlug @@ -2197,6 +2149,7 @@ export class ClineProvider extends EventEmitter implements public async handleModeSwitch(newMode: Mode) { // Capture mode switch telemetry event const currentTaskId = this.getCurrentCline()?.taskId + if (currentTaskId) { telemetryService.captureModeSwitch(currentTaskId, newMode) } @@ -2213,8 +2166,10 @@ export class ClineProvider extends EventEmitter implements // If this mode has a saved config, use it if (savedConfigId) { const config = listApiConfig?.find((c) => c.id === savedConfigId) + if (config?.name) { const apiConfig = await this.providerSettingsManager.loadConfig(config.name) + await Promise.all([ this.updateGlobalState("currentApiConfigName", config.name), this.updateApiConfiguration(apiConfig), @@ -2226,6 +2181,7 @@ export class ClineProvider extends EventEmitter implements if (currentApiConfigName) { const config = listApiConfig?.find((c) => c.name === currentApiConfigName) + if (config?.id) { await this.providerSettingsManager.setModeConfig(newMode, config.id) } @@ -2602,6 +2558,7 @@ export class ClineProvider extends EventEmitter implements screenshotQuality, remoteBrowserHost, remoteBrowserEnabled, + cachedChromeHostUrl, writeDelayMs, terminalOutputLineLimit, terminalShellIntegrationTimeout, @@ -2670,6 +2627,7 @@ export class ClineProvider extends EventEmitter implements screenshotQuality: screenshotQuality ?? 75, remoteBrowserHost, remoteBrowserEnabled: remoteBrowserEnabled ?? false, + cachedChromeHostUrl: cachedChromeHostUrl, writeDelayMs: writeDelayMs ?? 1000, terminalOutputLineLimit: terminalOutputLineLimit ?? 500, terminalShellIntegrationTimeout: terminalShellIntegrationTimeout ?? TERMINAL_SHELL_INTEGRATION_TIMEOUT, @@ -2755,6 +2713,7 @@ export class ClineProvider extends EventEmitter implements screenshotQuality: stateValues.screenshotQuality ?? 75, remoteBrowserHost: stateValues.remoteBrowserHost, remoteBrowserEnabled: stateValues.remoteBrowserEnabled ?? false, + cachedChromeHostUrl: stateValues.cachedChromeHostUrl as string | undefined, fuzzyMatchThreshold: stateValues.fuzzyMatchThreshold ?? 1.0, writeDelayMs: stateValues.writeDelayMs ?? 1000, terminalOutputLineLimit: stateValues.terminalOutputLineLimit ?? 500, diff --git a/src/core/webview/__tests__/ClineProvider.test.ts b/src/core/webview/__tests__/ClineProvider.test.ts index 5c859568eb..059dca72de 100644 --- a/src/core/webview/__tests__/ClineProvider.test.ts +++ b/src/core/webview/__tests__/ClineProvider.test.ts @@ -40,9 +40,12 @@ jest.mock("../../../services/browser/BrowserSession", () => ({ // Mock browserDiscovery jest.mock("../../../services/browser/browserDiscovery", () => ({ - discoverChromeInstances: jest.fn().mockImplementation(async () => { + discoverChromeHostUrl: jest.fn().mockImplementation(async () => { return "http://localhost:9222" }), + tryChromeHostUrl: jest.fn().mockImplementation(async (url) => { + return url === "http://localhost:9222" + }), })) jest.mock( @@ -1916,9 +1919,9 @@ describe("ClineProvider", () => { type: "testBrowserConnection", }) - // Verify discoverChromeInstances was called - const { discoverChromeInstances } = require("../../../services/browser/browserDiscovery") - expect(discoverChromeInstances).toHaveBeenCalled() + // Verify discoverChromeHostUrl was called + const { discoverChromeHostUrl } = require("../../../services/browser/browserDiscovery") + expect(discoverChromeHostUrl).toHaveBeenCalled() // Verify postMessage was called with success result expect(mockPostMessage).toHaveBeenCalledWith( @@ -1929,77 +1932,6 @@ describe("ClineProvider", () => { }), ) }) - - test("handles discoverBrowser message", async () => { - // Get the message handler - const messageHandler = (mockWebviewView.webview.onDidReceiveMessage as jest.Mock).mock.calls[0][0] - - // Test browser discovery - await messageHandler({ - type: "discoverBrowser", - }) - - // Verify discoverChromeInstances was called - const { discoverChromeInstances } = require("../../../services/browser/browserDiscovery") - expect(discoverChromeInstances).toHaveBeenCalled() - - // Verify postMessage was called with success result - expect(mockPostMessage).toHaveBeenCalledWith( - expect.objectContaining({ - type: "browserConnectionResult", - success: true, - text: expect.stringContaining("Successfully discovered and connected to Chrome"), - }), - ) - }) - - test("handles errors during browser discovery", async () => { - // Mock discoverChromeInstances to throw an error - const { discoverChromeInstances } = require("../../../services/browser/browserDiscovery") - discoverChromeInstances.mockImplementationOnce(() => { - throw new Error("Discovery error") - }) - - // Get the message handler - const messageHandler = (mockWebviewView.webview.onDidReceiveMessage as jest.Mock).mock.calls[0][0] - - // Test browser discovery with error - await messageHandler({ - type: "discoverBrowser", - }) - - // Verify postMessage was called with error result - expect(mockPostMessage).toHaveBeenCalledWith( - expect.objectContaining({ - type: "browserConnectionResult", - success: false, - text: expect.stringContaining("Error discovering browser"), - }), - ) - }) - - test("handles case when no browsers are discovered", async () => { - // Mock discoverChromeInstances to return null (no browsers found) - const { discoverChromeInstances } = require("../../../services/browser/browserDiscovery") - discoverChromeInstances.mockImplementationOnce(() => null) - - // Get the message handler - const messageHandler = (mockWebviewView.webview.onDidReceiveMessage as jest.Mock).mock.calls[0][0] - - // Test browser discovery with no browsers found - await messageHandler({ - type: "discoverBrowser", - }) - - // Verify postMessage was called with failure result - expect(mockPostMessage).toHaveBeenCalledWith( - expect.objectContaining({ - type: "browserConnectionResult", - success: false, - text: expect.stringContaining("No Chrome instances found"), - }), - ) - }) }) }) @@ -2099,7 +2031,7 @@ describe("Project MCP Settings", () => { await messageHandler({ type: "openProjectMcpSettings" }) // Verify error message was shown - expect(vscode.window.showErrorMessage).toHaveBeenCalledWith("no_workspace") + expect(vscode.window.showErrorMessage).toHaveBeenCalledWith("errors.no_workspace") }) test.skip("handles openProjectMcpSettings file creation error", async () => { diff --git a/src/exports/api.ts b/src/exports/api.ts index 65ac02c360..e5d67455a7 100644 --- a/src/exports/api.ts +++ b/src/exports/api.ts @@ -2,57 +2,41 @@ import { EventEmitter } from "events" import * as vscode from "vscode" import { ClineProvider } from "../core/webview/ClineProvider" +import { openClineInNewTab } from "../activate/registerCommands" -import { RooCodeAPI, RooCodeEvents, TokenUsage, RooCodeSettings } from "./roo-code" -import { MessageHistory } from "./message-history" -import { IpcClientMessageType, IpcServerMessageType, IpcServer } from "./ipc" +import { RooCodeSettings, RooCodeEvents, RooCodeEventName } from "../schemas" +import { IpcOrigin, IpcMessageType, TaskCommandName, TaskEvent } from "../schemas/ipc" +import { RooCodeAPI } from "./interface" +import { IpcServer } from "./ipc" export class API extends EventEmitter implements RooCodeAPI { private readonly outputChannel: vscode.OutputChannel - private readonly provider: ClineProvider - private readonly history: MessageHistory - private readonly tokenUsage: Record + private readonly sidebarProvider: ClineProvider + private tabProvider?: ClineProvider + private readonly context: vscode.ExtensionContext private readonly ipc?: IpcServer constructor(outputChannel: vscode.OutputChannel, provider: ClineProvider, socketPath?: string) { super() this.outputChannel = outputChannel - this.provider = provider - this.history = new MessageHistory() - this.tokenUsage = {} + this.sidebarProvider = provider + this.context = provider.context - this.provider.on("clineCreated", (cline) => { - cline.on("message", (message) => this.emit("message", { taskId: cline.taskId, ...message })) - cline.on("taskStarted", () => this.emit("taskStarted", cline.taskId)) - cline.on("taskPaused", () => this.emit("taskPaused", cline.taskId)) - cline.on("taskUnpaused", () => this.emit("taskUnpaused", cline.taskId)) - cline.on("taskAskResponded", () => this.emit("taskAskResponded", cline.taskId)) - cline.on("taskAborted", () => this.emit("taskAborted", cline.taskId)) - cline.on("taskSpawned", (childTaskId) => this.emit("taskSpawned", cline.taskId, childTaskId)) - cline.on("taskCompleted", (_, usage) => this.emit("taskCompleted", cline.taskId, usage)) - cline.on("taskTokenUsageUpdated", (_, usage) => this.emit("taskTokenUsageUpdated", cline.taskId, usage)) - this.emit("taskCreated", cline.taskId) - }) - - this.on("message", ({ taskId, action, message }) => { - if (action === "created") { - this.history.add(taskId, message) - } else if (action === "updated") { - this.history.update(taskId, message) - } - }) - - this.on("taskTokenUsageUpdated", (taskId, usage) => (this.tokenUsage[taskId] = usage)) + this.registerListeners(this.sidebarProvider) if (socketPath) { this.ipc = new IpcServer(socketPath) this.ipc.listen() - this.ipc.on("message", (message) => { - switch (message.type) { - case IpcClientMessageType.StartNewTask: - this.startNewTask(message.data.text, message.data.images) + this.outputChannel.appendLine( + `IPC server started: socketPath=${socketPath}, pid=${process.pid}, ppid=${process.ppid}`, + ) + + this.ipc.on(IpcMessageType.TaskCommand, async (_clientId, { commandName, data }) => { + switch (commandName) { + case TaskCommandName.StartNewTask: + this.startNewTask(data) break } }) @@ -63,76 +47,118 @@ export class API extends EventEmitter implements RooCodeAPI { eventName: K, ...args: K extends keyof RooCodeEvents ? RooCodeEvents[K] : never ) { - if (this.ipc) { - this.ipc.broadcast({ type: IpcServerMessageType.TaskEvent, data: { eventName, data: { ...args } } }) - } - + const data = { eventName: eventName as RooCodeEventName, payload: args } as TaskEvent + this.ipc?.broadcast({ type: IpcMessageType.TaskEvent, origin: IpcOrigin.Server, data }) return super.emit(eventName, ...args) } - public async startNewTask(text?: string, images?: string[]) { - await this.provider.removeClineFromStack() - await this.provider.postStateToWebview() - await this.provider.postMessageToWebview({ type: "action", action: "chatButtonClicked" }) - await this.provider.postMessageToWebview({ type: "invoke", invoke: "newChat", text, images }) + public async startNewTask({ + configuration, + text, + images, + newTab, + }: { + configuration: RooCodeSettings + text?: string + images?: string[] + newTab?: boolean + }) { + let provider: ClineProvider - const cline = await this.provider.initClineWithTask(text, images) - return cline.taskId + if (newTab) { + if (!this.tabProvider) { + this.tabProvider = await openClineInNewTab({ context: this.context, outputChannel: this.outputChannel }) + this.registerListeners(this.tabProvider) + } + + provider = this.tabProvider + } else { + provider = this.sidebarProvider + } + + if (configuration) { + await provider.setValues(configuration) + + if (configuration.allowedCommands) { + await vscode.workspace + .getConfiguration("roo-cline") + .update("allowedCommands", configuration.allowedCommands, vscode.ConfigurationTarget.Global) + } + } + + await provider.removeClineFromStack() + await provider.postStateToWebview() + await provider.postMessageToWebview({ type: "action", action: "chatButtonClicked" }) + await provider.postMessageToWebview({ type: "invoke", invoke: "newChat", text, images }) + + const { taskId } = await provider.initClineWithTask(text, images) + + return taskId } public getCurrentTaskStack() { - return this.provider.getCurrentTaskStack() + return this.sidebarProvider.getCurrentTaskStack() } public async clearCurrentTask(lastMessage?: string) { - await this.provider.finishSubTask(lastMessage) + await this.sidebarProvider.finishSubTask(lastMessage) } public async cancelCurrentTask() { - await this.provider.cancelTask() + await this.sidebarProvider.cancelTask() } public async sendMessage(text?: string, images?: string[]) { - await this.provider.postMessageToWebview({ type: "invoke", invoke: "sendMessage", text, images }) + await this.sidebarProvider.postMessageToWebview({ type: "invoke", invoke: "sendMessage", text, images }) } public async pressPrimaryButton() { - await this.provider.postMessageToWebview({ type: "invoke", invoke: "primaryButtonClick" }) + await this.sidebarProvider.postMessageToWebview({ type: "invoke", invoke: "primaryButtonClick" }) } public async pressSecondaryButton() { - await this.provider.postMessageToWebview({ type: "invoke", invoke: "secondaryButtonClick" }) + await this.sidebarProvider.postMessageToWebview({ type: "invoke", invoke: "secondaryButtonClick" }) } public getConfiguration() { - return this.provider.getValues() + return this.sidebarProvider.getValues() } public getConfigurationValue(key: K) { - return this.provider.getValue(key) + return this.sidebarProvider.getValue(key) } public async setConfiguration(values: RooCodeSettings) { - await this.provider.setValues(values) + await this.sidebarProvider.setValues(values) } public async setConfigurationValue(key: K, value: RooCodeSettings[K]) { - await this.provider.setValue(key, value) + await this.sidebarProvider.setValue(key, value) } public isReady() { - return this.provider.viewLaunched - } - - public getMessages(taskId: string) { - return this.history.getMessages(taskId) - } - - public getTokenUsage(taskId: string) { - return this.tokenUsage[taskId] + return this.sidebarProvider.viewLaunched } public log(message: string) { this.outputChannel.appendLine(message) } + + private registerListeners(provider: ClineProvider) { + provider.on("clineCreated", (cline) => { + cline.on("message", (message) => this.emit(RooCodeEventName.Message, { taskId: cline.taskId, ...message })) + cline.on("taskStarted", () => this.emit(RooCodeEventName.TaskStarted, cline.taskId)) + cline.on("taskPaused", () => this.emit(RooCodeEventName.TaskPaused, cline.taskId)) + cline.on("taskUnpaused", () => this.emit(RooCodeEventName.TaskUnpaused, cline.taskId)) + cline.on("taskAskResponded", () => this.emit(RooCodeEventName.TaskAskResponded, cline.taskId)) + cline.on("taskAborted", () => this.emit(RooCodeEventName.TaskAborted, cline.taskId)) + cline.on("taskSpawned", (childTaskId) => this.emit(RooCodeEventName.TaskSpawned, cline.taskId, childTaskId)) + cline.on("taskCompleted", (_, usage) => this.emit(RooCodeEventName.TaskCompleted, cline.taskId, usage)) + cline.on("taskTokenUsageUpdated", (_, usage) => + this.emit(RooCodeEventName.TaskTokenUsageUpdated, cline.taskId, usage), + ) + + this.emit(RooCodeEventName.TaskCreated, cline.taskId) + }) + } } diff --git a/src/exports/interface.ts b/src/exports/interface.ts index de1f7ea15c..af0277f541 100644 --- a/src/exports/interface.ts +++ b/src/exports/interface.ts @@ -1,24 +1,13 @@ import { EventEmitter } from "events" -import type { ProviderSettings, GlobalSettings, ClineMessage, TokenUsage } from "./types" +import type { ProviderSettings, GlobalSettings, ClineMessage, TokenUsage, RooCodeEvents } from "./types" +export type { RooCodeSettings, ProviderSettings, GlobalSettings, ClineMessage, TokenUsage, RooCodeEvents } + +import { RooCodeEventName } from "../schemas" +export type { RooCodeEventName } type RooCodeSettings = GlobalSettings & ProviderSettings -export type { RooCodeSettings, ProviderSettings, GlobalSettings, ClineMessage, TokenUsage } - -export interface RooCodeEvents { - message: [{ taskId: string; action: "created" | "updated"; message: ClineMessage }] - taskCreated: [taskId: string] - taskStarted: [taskId: string] - taskPaused: [taskId: string] - taskUnpaused: [taskId: string] - taskAskResponded: [taskId: string] - taskAborted: [taskId: string] - taskSpawned: [taskId: string, childTaskId: string] - taskCompleted: [taskId: string, usage: TokenUsage] - taskTokenUsageUpdated: [taskId: string, usage: TokenUsage] -} - export interface RooCodeAPI extends EventEmitter { /** * Starts a new task with an optional initial message and images. @@ -26,7 +15,17 @@ export interface RooCodeAPI extends EventEmitter { * @param images Optional array of image data URIs (e.g., "data:image/webp;base64,..."). * @returns The ID of the new task. */ - startNewTask(task?: string, images?: string[]): Promise + startNewTask({ + configuration, + text, + images, + newTab, + }: { + configuration?: RooCodeSettings + text?: string + images?: string[] + newTab?: boolean + }): Promise /** * Returns the current task stack. @@ -92,20 +91,6 @@ export interface RooCodeAPI extends EventEmitter { */ isReady(): boolean - /** - * Returns the messages for a given task. - * @param taskId The ID of the task. - * @returns An array of ClineMessage objects. - */ - getMessages(taskId: string): ClineMessage[] - - /** - * Returns the token usage for a given task. - * @param taskId The ID of the task. - * @returns A TokenUsage object. - */ - getTokenUsage(taskId: string): TokenUsage - /** * Logs a message to the output channel. * @param message The message to log. diff --git a/src/exports/ipc.ts b/src/exports/ipc.ts index 8b0dabcb55..0c6aa2332b 100644 --- a/src/exports/ipc.ts +++ b/src/exports/ipc.ts @@ -3,158 +3,18 @@ import { Socket } from "node:net" import * as crypto from "node:crypto" import ipc from "node-ipc" -import { z } from "zod" -/** - * IpcClient - */ - -export type IpcClientId = string - -export enum IpcClientMessageType { - Message = "Message", - StartNewTask = "StartNewTask", -} - -export const ipcClientMessageSchema = z.discriminatedUnion("type", [ - z.object({ - type: z.literal(IpcClientMessageType.StartNewTask), - data: z.object({ - text: z.string(), - images: z.array(z.string()).optional(), - }), - }), -]) - -export type IpcClientMessage = z.infer - -export interface IpcClientEvents { - connect: [] - disconnect: [] - message: [data: IpcServerMessage] -} - -export class IpcClient extends EventEmitter { - private readonly _socketPath: string - private readonly _log: (...args: unknown[]) => void - - private _isConnected = false - private _clientId?: IpcClientId - - constructor(socketPath: string, log = console.log) { - super() - - this._socketPath = socketPath - this._log = log - - ipc.config.silent = true - - ipc.connectTo("benchmarkServer", this.socketPath, () => { - ipc.of.benchmarkServer?.on("connect", (args) => this.onConnect(args)) - ipc.of.benchmarkServer?.on("disconnect", (args) => this.onDisconnect(args)) - ipc.of.benchmarkServer?.on("message", (data) => this.onMessage(data)) - }) - } - - private onConnect(args: unknown) { - if (this._isConnected) { - return - } - - this.log("[client#onConnect]", args) - this._isConnected = true - this.emit("connect") - } - - private onDisconnect(args: unknown) { - if (!this._isConnected) { - return - } - - this.log("[client#onDisconnect]", args) - this._isConnected = false - this.emit("disconnect") - } - - private onMessage(data: unknown) { - if (typeof data !== "object") { - this._log("[client#onMessage] invalid data", data) - return - } - - const result = ipcServerMessageSchema.safeParse(data) - - if (!result.success) { - this.log("[client#onMessage] invalid payload", result.error) - return - } - - this.emit("message", result.data) - } - - private log(...args: unknown[]) { - this._log(...args) - } - - public sendMessage(message: IpcClientMessage) { - ipc.of.benchmarkServer?.emit("message", message) - } - - public disconnect() { - try { - ipc.disconnect("benchmarkServer") - // @TODO: Should we set _disconnect here? - } catch (error) { - this.log("[client#disconnect] error disconnecting", error) - } - } - - public get socketPath() { - return this._socketPath - } - - public get clientId() { - return this._clientId - } - - public get isConnected() { - return this._isConnected - } - - public get isReady() { - return this._isConnected && this._clientId !== undefined - } -} +import { IpcOrigin, IpcMessageType, IpcMessage, ipcMessageSchema, TaskCommand, TaskEvent } from "../schemas/ipc" /** * IpcServer */ -export enum IpcServerMessageType { - Ack = "Ack", - TaskEvent = "TaskEvent", -} - -export const ipcServerMessageSchema = z.discriminatedUnion("type", [ - z.object({ - type: z.literal(IpcServerMessageType.Ack), - data: z.object({ clientId: z.string() }), - }), - z.object({ - type: z.literal(IpcServerMessageType.TaskEvent), - data: z.object({ - eventName: z.string(), - data: z.unknown(), - }), - }), -]) - -export type IpcServerMessage = z.infer - type IpcServerEvents = { - connect: [id: IpcClientId] - disconnect: [id: IpcClientId] - message: [data: IpcClientMessage] + [IpcMessageType.Connect]: [clientId: string] + [IpcMessageType.Disconnect]: [clientId: string] + [IpcMessageType.TaskCommand]: [clientId: string, data: TaskCommand] + [IpcMessageType.TaskEvent]: [relayClientId: string | undefined, data: TaskEvent] } export class IpcServer extends EventEmitter { @@ -190,12 +50,18 @@ export class IpcServer extends EventEmitter { const clientId = crypto.randomBytes(6).toString("hex") this._clients.set(clientId, socket) this.log(`[server#onConnect] clientId = ${clientId}, # clients = ${this._clients.size}`) - this.send(socket, { type: IpcServerMessageType.Ack, data: { clientId } }) - this.emit("connect", clientId) + + this.send(socket, { + type: IpcMessageType.Ack, + origin: IpcOrigin.Server, + data: { clientId, pid: process.pid, ppid: process.ppid }, + }) + + this.emit(IpcMessageType.Connect, clientId) } private onDisconnect(destroyedSocket: Socket) { - let disconnectedClientId: IpcClientId | undefined + let disconnectedClientId: string | undefined for (const [clientId, socket] of this._clients.entries()) { if (socket === destroyedSocket) { @@ -208,7 +74,7 @@ export class IpcServer extends EventEmitter { this.log(`[server#socket.disconnected] clientId = ${disconnectedClientId}, # clients = ${this._clients.size}`) if (disconnectedClientId) { - this.emit("disconnect", disconnectedClientId) + this.emit(IpcMessageType.Disconnect, disconnectedClientId) } } @@ -218,7 +84,7 @@ export class IpcServer extends EventEmitter { return } - const result = ipcClientMessageSchema.safeParse(data) + const result = ipcMessageSchema.safeParse(data) if (!result.success) { this.log("[server#onMessage] invalid payload", result.error) @@ -226,19 +92,26 @@ export class IpcServer extends EventEmitter { } const payload = result.data - this.emit("message", payload) + + if (payload.origin === IpcOrigin.Client) { + switch (payload.type) { + case IpcMessageType.TaskCommand: + this.emit(IpcMessageType.TaskCommand, payload.clientId, payload.data) + break + } + } } private log(...args: unknown[]) { this._log(...args) } - public broadcast(message: IpcServerMessage) { + public broadcast(message: IpcMessage) { this.log("[server#broadcast] message =", message) ipc.server.broadcast("message", message) } - public send(client: IpcClientId | Socket, message: IpcServerMessage) { + public send(client: string | Socket, message: IpcMessage) { this.log("[server#send] message =", message) if (typeof client === "string") { diff --git a/src/exports/message-history.ts b/src/exports/message-history.ts deleted file mode 100644 index f17e044f8d..0000000000 --- a/src/exports/message-history.ts +++ /dev/null @@ -1,35 +0,0 @@ -import { ClineMessage } from "./roo-code" - -export class MessageHistory { - private readonly messages: Record> - private readonly list: Record - - constructor() { - this.messages = {} - this.list = {} - } - - public add(taskId: string, message: ClineMessage) { - if (!this.messages[taskId]) { - this.messages[taskId] = {} - } - - this.messages[taskId][message.ts] = message - - if (!this.list[taskId]) { - this.list[taskId] = [] - } - - this.list[taskId].push(message.ts) - } - - public update(taskId: string, message: ClineMessage) { - if (this.messages[taskId][message.ts]) { - this.messages[taskId][message.ts] = message - } - } - - public getMessages(taskId: string) { - return (this.list[taskId] ?? []).map((ts) => this.messages[taskId][ts]).filter(Boolean) - } -} diff --git a/src/exports/roo-code.d.ts b/src/exports/roo-code.d.ts index 12e86be8fd..4554813a9c 100644 --- a/src/exports/roo-code.d.ts +++ b/src/exports/roo-code.d.ts @@ -27,7 +27,7 @@ type ProviderSettings = { anthropicBaseUrl?: string | undefined glamaModelId?: string | undefined glamaModelInfo?: - | { + | ({ maxTokens?: number | undefined contextWindow: number supportsImages?: boolean | undefined @@ -40,13 +40,13 @@ type ProviderSettings = { description?: string | undefined reasoningEffort?: ("low" | "medium" | "high") | undefined thinking?: boolean | undefined - } + } | null) | undefined glamaApiKey?: string | undefined openRouterApiKey?: string | undefined openRouterModelId?: string | undefined openRouterModelInfo?: - | { + | ({ maxTokens?: number | undefined contextWindow: number supportsImages?: boolean | undefined @@ -59,7 +59,7 @@ type ProviderSettings = { description?: string | undefined reasoningEffort?: ("low" | "medium" | "high") | undefined thinking?: boolean | undefined - } + } | null) | undefined openRouterBaseUrl?: string | undefined openRouterSpecificProvider?: string | undefined @@ -83,7 +83,7 @@ type ProviderSettings = { openAiR1FormatEnabled?: boolean | undefined openAiModelId?: string | undefined openAiCustomModelInfo?: - | { + | ({ maxTokens?: number | undefined contextWindow: number supportsImages?: boolean | undefined @@ -96,7 +96,7 @@ type ProviderSettings = { description?: string | undefined reasoningEffort?: ("low" | "medium" | "high") | undefined thinking?: boolean | undefined - } + } | null) | undefined openAiUseAzure?: boolean | undefined azureApiVersion?: string | undefined @@ -125,7 +125,7 @@ type ProviderSettings = { unboundApiKey?: string | undefined unboundModelId?: string | undefined unboundModelInfo?: - | { + | ({ maxTokens?: number | undefined contextWindow: number supportsImages?: boolean | undefined @@ -138,12 +138,12 @@ type ProviderSettings = { description?: string | undefined reasoningEffort?: ("low" | "medium" | "high") | undefined thinking?: boolean | undefined - } + } | null) | undefined requestyApiKey?: string | undefined requestyModelId?: string | undefined requestyModelInfo?: - | { + | ({ maxTokens?: number | undefined contextWindow: number supportsImages?: boolean | undefined @@ -156,7 +156,7 @@ type ProviderSettings = { description?: string | undefined reasoningEffort?: ("low" | "medium" | "high") | undefined thinking?: boolean | undefined - } + } | null) | undefined modelTemperature?: (number | null) | undefined modelMaxTokens?: number | undefined @@ -234,6 +234,7 @@ type GlobalSettings = { screenshotQuality?: number | undefined remoteBrowserEnabled?: boolean | undefined remoteBrowserHost?: string | undefined + cachedChromeHostUrl?: string | undefined enableCheckpoints?: boolean | undefined checkpointStorage?: ("task" | "workspace") | undefined ttsEnabled?: boolean | undefined @@ -251,11 +252,11 @@ type GlobalSettings = { fuzzyMatchThreshold?: number | undefined experiments?: | { - experimentalDiffStrategy: boolean search_and_replace: boolean + experimentalDiffStrategy: boolean + multi_search_and_replace: boolean insert_content: boolean powerSteering: boolean - multi_search_and_replace: boolean } | undefined language?: @@ -397,26 +398,125 @@ type TokenUsage = { contextTokens: number } -type RooCodeSettings = GlobalSettings & ProviderSettings - -interface RooCodeEvents { +type RooCodeEvents = { message: [ { taskId: string action: "created" | "updated" - message: ClineMessage + message: { + ts: number + type: "ask" | "say" + ask?: + | ( + | "followup" + | "command" + | "command_output" + | "completion_result" + | "tool" + | "api_req_failed" + | "resume_task" + | "resume_completed_task" + | "mistake_limit_reached" + | "browser_action_launch" + | "use_mcp_server" + | "finishTask" + ) + | undefined + say?: + | ( + | "task" + | "error" + | "api_req_started" + | "api_req_finished" + | "api_req_retried" + | "api_req_retry_delayed" + | "api_req_deleted" + | "text" + | "reasoning" + | "completion_result" + | "user_feedback" + | "user_feedback_diff" + | "command_output" + | "tool" + | "shell_integration_warning" + | "browser_action" + | "browser_action_result" + | "command" + | "mcp_server_request_started" + | "mcp_server_response" + | "new_task_started" + | "new_task" + | "checkpoint_saved" + | "rooignore_error" + ) + | undefined + text?: string | undefined + images?: string[] | undefined + partial?: boolean | undefined + reasoning?: string | undefined + conversationHistoryIndex?: number | undefined + checkpoint?: + | { + [x: string]: unknown + } + | undefined + progressStatus?: + | { + icon?: string | undefined + text?: string | undefined + } + | undefined + } + }, + ] + taskCreated: [string] + taskStarted: [string] + taskPaused: [string] + taskUnpaused: [string] + taskAskResponded: [string] + taskAborted: [string] + taskSpawned: [string, string] + taskCompleted: [ + string, + { + totalTokensIn: number + totalTokensOut: number + totalCacheWrites?: number | undefined + totalCacheReads?: number | undefined + totalCost: number + contextTokens: number + }, + ] + taskTokenUsageUpdated: [ + string, + { + totalTokensIn: number + totalTokensOut: number + totalCacheWrites?: number | undefined + totalCacheReads?: number | undefined + totalCost: number + contextTokens: number }, ] - taskCreated: [taskId: string] - taskStarted: [taskId: string] - taskPaused: [taskId: string] - taskUnpaused: [taskId: string] - taskAskResponded: [taskId: string] - taskAborted: [taskId: string] - taskSpawned: [taskId: string, childTaskId: string] - taskCompleted: [taskId: string, usage: TokenUsage] - taskTokenUsageUpdated: [taskId: string, usage: TokenUsage] } + +/** + * RooCodeEvent + */ +declare enum RooCodeEventName { + Message = "message", + TaskCreated = "taskCreated", + TaskStarted = "taskStarted", + TaskPaused = "taskPaused", + TaskUnpaused = "taskUnpaused", + TaskAskResponded = "taskAskResponded", + TaskAborted = "taskAborted", + TaskSpawned = "taskSpawned", + TaskCompleted = "taskCompleted", + TaskTokenUsageUpdated = "taskTokenUsageUpdated", +} + +type RooCodeSettings = GlobalSettings & ProviderSettings interface RooCodeAPI extends EventEmitter { /** * Starts a new task with an optional initial message and images. @@ -424,7 +524,17 @@ interface RooCodeAPI extends EventEmitter { * @param images Optional array of image data URIs (e.g., "data:image/webp;base64,..."). * @returns The ID of the new task. */ - startNewTask(task?: string, images?: string[]): Promise + startNewTask({ + configuration, + text, + images, + newTab, + }: { + configuration?: RooCodeSettings + text?: string + images?: string[] + newTab?: boolean + }): Promise /** * Returns the current task stack. * @returns An array of task IDs. @@ -478,18 +588,6 @@ interface RooCodeAPI extends EventEmitter { * Returns true if the API is ready to use. */ isReady(): boolean - /** - * Returns the messages for a given task. - * @param taskId The ID of the task. - * @returns An array of ClineMessage objects. - */ - getMessages(taskId: string): ClineMessage[] - /** - * Returns the token usage for a given task. - * @param taskId The ID of the task. - * @returns A TokenUsage object. - */ - getTokenUsage(taskId: string): TokenUsage /** * Logs a message to the output channel. * @param message The message to log. @@ -497,4 +595,13 @@ interface RooCodeAPI extends EventEmitter { log(message: string): void } -export type { ClineMessage, GlobalSettings, ProviderSettings, RooCodeAPI, RooCodeEvents, RooCodeSettings, TokenUsage } +export { + type ClineMessage, + type GlobalSettings, + type ProviderSettings, + type RooCodeAPI, + RooCodeEventName, + type RooCodeEvents, + type RooCodeSettings, + type TokenUsage, +} diff --git a/src/exports/types.ts b/src/exports/types.ts index 9432537718..b12806f4a0 100644 --- a/src/exports/types.ts +++ b/src/exports/types.ts @@ -28,7 +28,7 @@ type ProviderSettings = { anthropicBaseUrl?: string | undefined glamaModelId?: string | undefined glamaModelInfo?: - | { + | ({ maxTokens?: number | undefined contextWindow: number supportsImages?: boolean | undefined @@ -41,13 +41,13 @@ type ProviderSettings = { description?: string | undefined reasoningEffort?: ("low" | "medium" | "high") | undefined thinking?: boolean | undefined - } + } | null) | undefined glamaApiKey?: string | undefined openRouterApiKey?: string | undefined openRouterModelId?: string | undefined openRouterModelInfo?: - | { + | ({ maxTokens?: number | undefined contextWindow: number supportsImages?: boolean | undefined @@ -60,7 +60,7 @@ type ProviderSettings = { description?: string | undefined reasoningEffort?: ("low" | "medium" | "high") | undefined thinking?: boolean | undefined - } + } | null) | undefined openRouterBaseUrl?: string | undefined openRouterSpecificProvider?: string | undefined @@ -84,7 +84,7 @@ type ProviderSettings = { openAiR1FormatEnabled?: boolean | undefined openAiModelId?: string | undefined openAiCustomModelInfo?: - | { + | ({ maxTokens?: number | undefined contextWindow: number supportsImages?: boolean | undefined @@ -97,7 +97,7 @@ type ProviderSettings = { description?: string | undefined reasoningEffort?: ("low" | "medium" | "high") | undefined thinking?: boolean | undefined - } + } | null) | undefined openAiUseAzure?: boolean | undefined azureApiVersion?: string | undefined @@ -126,7 +126,7 @@ type ProviderSettings = { unboundApiKey?: string | undefined unboundModelId?: string | undefined unboundModelInfo?: - | { + | ({ maxTokens?: number | undefined contextWindow: number supportsImages?: boolean | undefined @@ -139,12 +139,12 @@ type ProviderSettings = { description?: string | undefined reasoningEffort?: ("low" | "medium" | "high") | undefined thinking?: boolean | undefined - } + } | null) | undefined requestyApiKey?: string | undefined requestyModelId?: string | undefined requestyModelInfo?: - | { + | ({ maxTokens?: number | undefined contextWindow: number supportsImages?: boolean | undefined @@ -157,7 +157,7 @@ type ProviderSettings = { description?: string | undefined reasoningEffort?: ("low" | "medium" | "high") | undefined thinking?: boolean | undefined - } + } | null) | undefined modelTemperature?: (number | null) | undefined modelMaxTokens?: number | undefined @@ -237,6 +237,7 @@ type GlobalSettings = { screenshotQuality?: number | undefined remoteBrowserEnabled?: boolean | undefined remoteBrowserHost?: string | undefined + cachedChromeHostUrl?: string | undefined enableCheckpoints?: boolean | undefined checkpointStorage?: ("task" | "workspace") | undefined ttsEnabled?: boolean | undefined @@ -254,11 +255,11 @@ type GlobalSettings = { fuzzyMatchThreshold?: number | undefined experiments?: | { - experimentalDiffStrategy: boolean search_and_replace: boolean + experimentalDiffStrategy: boolean + multi_search_and_replace: boolean insert_content: boolean powerSteering: boolean - multi_search_and_replace: boolean } | undefined language?: @@ -405,3 +406,107 @@ type TokenUsage = { } export type { TokenUsage } + +type RooCodeEvents = { + message: [ + { + taskId: string + action: "created" | "updated" + message: { + ts: number + type: "ask" | "say" + ask?: + | ( + | "followup" + | "command" + | "command_output" + | "completion_result" + | "tool" + | "api_req_failed" + | "resume_task" + | "resume_completed_task" + | "mistake_limit_reached" + | "browser_action_launch" + | "use_mcp_server" + | "finishTask" + ) + | undefined + say?: + | ( + | "task" + | "error" + | "api_req_started" + | "api_req_finished" + | "api_req_retried" + | "api_req_retry_delayed" + | "api_req_deleted" + | "text" + | "reasoning" + | "completion_result" + | "user_feedback" + | "user_feedback_diff" + | "command_output" + | "tool" + | "shell_integration_warning" + | "browser_action" + | "browser_action_result" + | "command" + | "mcp_server_request_started" + | "mcp_server_response" + | "new_task_started" + | "new_task" + | "checkpoint_saved" + | "rooignore_error" + ) + | undefined + text?: string | undefined + images?: string[] | undefined + partial?: boolean | undefined + reasoning?: string | undefined + conversationHistoryIndex?: number | undefined + checkpoint?: + | { + [x: string]: unknown + } + | undefined + progressStatus?: + | { + icon?: string | undefined + text?: string | undefined + } + | undefined + } + }, + ] + taskCreated: [string] + taskStarted: [string] + taskPaused: [string] + taskUnpaused: [string] + taskAskResponded: [string] + taskAborted: [string] + taskSpawned: [string, string] + taskCompleted: [ + string, + { + totalTokensIn: number + totalTokensOut: number + totalCacheWrites?: number | undefined + totalCacheReads?: number | undefined + totalCost: number + contextTokens: number + }, + ] + taskTokenUsageUpdated: [ + string, + { + totalTokensIn: number + totalTokensOut: number + totalCacheWrites?: number | undefined + totalCacheReads?: number | undefined + totalCost: number + contextTokens: number + }, + ] +} + +export type { RooCodeEvents } diff --git a/src/extension.ts b/src/extension.ts index 5142f1ef42..a6de6b5ed8 100644 --- a/src/extension.ts +++ b/src/extension.ts @@ -115,9 +115,8 @@ export async function activate(context: vscode.ExtensionContext) { registerCodeActions(context) registerTerminalActions(context) - if (process.env.ROO_CODE_IPC_SOCKET_PATH) { - outputChannel.appendLine(`ROO_CODE_IPC_SOCKET_PATH: ${process.env.ROO_CODE_IPC_SOCKET_PATH}`) - } + // Allows other extensions to activate once Roo is ready. + vscode.commands.executeCommand("roo-cline.activationCompleted") // Implements the `RooCodeAPI` interface. return new API(outputChannel, provider, process.env.ROO_CODE_IPC_SOCKET_PATH) diff --git a/src/i18n/locales/ca/tools.json b/src/i18n/locales/ca/tools.json new file mode 100644 index 0000000000..14e7e43880 --- /dev/null +++ b/src/i18n/locales/ca/tools.json @@ -0,0 +1,9 @@ +{ + "readFile": { + "linesRange": " (línies {{start}}-{{end}})", + "linesFromToEnd": " (línies {{start}}-final)", + "linesFromStartTo": " (línies 1-{{end}})", + "definitionsOnly": " (només definicions)", + "maxLines": " (màxim {{max}} línies)" + } +} diff --git a/src/i18n/locales/de/tools.json b/src/i18n/locales/de/tools.json new file mode 100644 index 0000000000..f1b7d85032 --- /dev/null +++ b/src/i18n/locales/de/tools.json @@ -0,0 +1,9 @@ +{ + "readFile": { + "linesRange": " (Zeilen {{start}}-{{end}})", + "linesFromToEnd": " (Zeilen {{start}}-Ende)", + "linesFromStartTo": " (Zeilen 1-{{end}})", + "definitionsOnly": " (nur Definitionen)", + "maxLines": " (maximal {{max}} Zeilen)" + } +} diff --git a/src/i18n/locales/en/tools.json b/src/i18n/locales/en/tools.json new file mode 100644 index 0000000000..bb258961ba --- /dev/null +++ b/src/i18n/locales/en/tools.json @@ -0,0 +1,9 @@ +{ + "readFile": { + "linesRange": " (lines {{start}}-{{end}})", + "linesFromToEnd": " (lines {{start}}-end)", + "linesFromStartTo": " (lines 1-{{end}})", + "definitionsOnly": " (definitions only)", + "maxLines": " (max {{max}} lines)" + } +} diff --git a/src/i18n/locales/es/tools.json b/src/i18n/locales/es/tools.json new file mode 100644 index 0000000000..f6e4389206 --- /dev/null +++ b/src/i18n/locales/es/tools.json @@ -0,0 +1,9 @@ +{ + "readFile": { + "linesRange": " (líneas {{start}}-{{end}})", + "linesFromToEnd": " (líneas {{start}}-final)", + "linesFromStartTo": " (líneas 1-{{end}})", + "definitionsOnly": " (solo definiciones)", + "maxLines": " (máximo {{max}} líneas)" + } +} diff --git a/src/i18n/locales/fr/tools.json b/src/i18n/locales/fr/tools.json new file mode 100644 index 0000000000..97a640a18f --- /dev/null +++ b/src/i18n/locales/fr/tools.json @@ -0,0 +1,9 @@ +{ + "readFile": { + "linesRange": " (lignes {{start}}-{{end}})", + "linesFromToEnd": " (lignes {{start}}-fin)", + "linesFromStartTo": " (lignes 1-{{end}})", + "definitionsOnly": " (définitions uniquement)", + "maxLines": " (max {{max}} lignes)" + } +} diff --git a/src/i18n/locales/hi/tools.json b/src/i18n/locales/hi/tools.json new file mode 100644 index 0000000000..7f682391f4 --- /dev/null +++ b/src/i18n/locales/hi/tools.json @@ -0,0 +1,9 @@ +{ + "readFile": { + "linesRange": " (पंक्तियाँ {{start}}-{{end}})", + "linesFromToEnd": " (पंक्तियाँ {{start}}-अंत)", + "linesFromStartTo": " (पंक्तियाँ 1-{{end}})", + "definitionsOnly": " (केवल परिभाषाएँ)", + "maxLines": " (अधिकतम {{max}} पंक्तियाँ)" + } +} diff --git a/src/i18n/locales/it/tools.json b/src/i18n/locales/it/tools.json new file mode 100644 index 0000000000..a9ad538e9d --- /dev/null +++ b/src/i18n/locales/it/tools.json @@ -0,0 +1,9 @@ +{ + "readFile": { + "linesRange": " (righe {{start}}-{{end}})", + "linesFromToEnd": " (righe {{start}}-fine)", + "linesFromStartTo": " (righe 1-{{end}})", + "definitionsOnly": " (solo definizioni)", + "maxLines": " (max {{max}} righe)" + } +} diff --git a/src/i18n/locales/ja/tools.json b/src/i18n/locales/ja/tools.json new file mode 100644 index 0000000000..6daed74793 --- /dev/null +++ b/src/i18n/locales/ja/tools.json @@ -0,0 +1,9 @@ +{ + "readFile": { + "linesRange": " ({{start}}-{{end}}行目)", + "linesFromToEnd": " ({{start}}行目-最後まで)", + "linesFromStartTo": " (1-{{end}}行目)", + "definitionsOnly": " (定義のみ)", + "maxLines": " (最大{{max}}行)" + } +} diff --git a/src/i18n/locales/ko/tools.json b/src/i18n/locales/ko/tools.json new file mode 100644 index 0000000000..f4583d2d06 --- /dev/null +++ b/src/i18n/locales/ko/tools.json @@ -0,0 +1,9 @@ +{ + "readFile": { + "linesRange": " ({{start}}-{{end}}행)", + "linesFromToEnd": " ({{start}}행-끝)", + "linesFromStartTo": " (1-{{end}}행)", + "definitionsOnly": " (정의만)", + "maxLines": " (최대 {{max}}행)" + } +} diff --git a/src/i18n/locales/pl/tools.json b/src/i18n/locales/pl/tools.json new file mode 100644 index 0000000000..33edb77cfa --- /dev/null +++ b/src/i18n/locales/pl/tools.json @@ -0,0 +1,9 @@ +{ + "readFile": { + "linesRange": " (linie {{start}}-{{end}})", + "linesFromToEnd": " (linie {{start}}-koniec)", + "linesFromStartTo": " (linie 1-{{end}})", + "definitionsOnly": " (tylko definicje)", + "maxLines": " (maks. {{max}} linii)" + } +} diff --git a/src/i18n/locales/pt-BR/tools.json b/src/i18n/locales/pt-BR/tools.json new file mode 100644 index 0000000000..0992809bdd --- /dev/null +++ b/src/i18n/locales/pt-BR/tools.json @@ -0,0 +1,9 @@ +{ + "readFile": { + "linesRange": " (linhas {{start}}-{{end}})", + "linesFromToEnd": " (linhas {{start}}-fim)", + "linesFromStartTo": " (linhas 1-{{end}})", + "definitionsOnly": " (apenas definições)", + "maxLines": " (máx. {{max}} linhas)" + } +} diff --git a/src/i18n/locales/tr/tools.json b/src/i18n/locales/tr/tools.json new file mode 100644 index 0000000000..19b0158d13 --- /dev/null +++ b/src/i18n/locales/tr/tools.json @@ -0,0 +1,9 @@ +{ + "readFile": { + "linesRange": " (satır {{start}}-{{end}})", + "linesFromToEnd": " (satır {{start}}-son)", + "linesFromStartTo": " (satır 1-{{end}})", + "definitionsOnly": " (sadece tanımlar)", + "maxLines": " (maks. {{max}} satır)" + } +} diff --git a/src/i18n/locales/vi/tools.json b/src/i18n/locales/vi/tools.json new file mode 100644 index 0000000000..76af39abe1 --- /dev/null +++ b/src/i18n/locales/vi/tools.json @@ -0,0 +1,9 @@ +{ + "readFile": { + "linesRange": " (dòng {{start}}-{{end}})", + "linesFromToEnd": " (dòng {{start}}-cuối)", + "linesFromStartTo": " (dòng 1-{{end}})", + "definitionsOnly": " (chỉ định nghĩa)", + "maxLines": " (tối đa {{max}} dòng)" + } +} diff --git a/src/i18n/locales/zh-CN/tools.json b/src/i18n/locales/zh-CN/tools.json new file mode 100644 index 0000000000..5f9b2ddaf7 --- /dev/null +++ b/src/i18n/locales/zh-CN/tools.json @@ -0,0 +1,9 @@ +{ + "readFile": { + "linesRange": " (第 {{start}}-{{end}} 行)", + "linesFromToEnd": " (第 {{start}} 行至末尾)", + "linesFromStartTo": " (第 1-{{end}} 行)", + "definitionsOnly": " (仅定义)", + "maxLines": " (最多 {{max}} 行)" + } +} diff --git a/src/i18n/locales/zh-TW/tools.json b/src/i18n/locales/zh-TW/tools.json new file mode 100644 index 0000000000..a9297e876b --- /dev/null +++ b/src/i18n/locales/zh-TW/tools.json @@ -0,0 +1,9 @@ +{ + "readFile": { + "linesRange": " (第 {{start}}-{{end}} 行)", + "linesFromToEnd": " (第 {{start}} 行至末尾)", + "linesFromStartTo": " (第 1-{{end}} 行)", + "definitionsOnly": " (僅定義)", + "maxLines": " (最多 {{max}} 行)" + } +} diff --git a/src/i18n/setup.ts b/src/i18n/setup.ts index dc5fb7b99d..058f357b46 100644 --- a/src/i18n/setup.ts +++ b/src/i18n/setup.ts @@ -55,7 +55,6 @@ if (!isTestEnv) { // Read and parse the JSON file const content = fs.readFileSync(filePath, "utf8") translations[language][namespace] = JSON.parse(content) - console.log(`Successfully loaded '${language}/${namespace}' translations`) } catch (error) { console.error(`Error loading translation file ${filePath}:`, error) } diff --git a/src/schemas/index.ts b/src/schemas/index.ts index 932fd490c9..89797758a5 100644 --- a/src/schemas/index.ts +++ b/src/schemas/index.ts @@ -275,11 +275,11 @@ export type CustomSupportPrompts = z.infer */ export const experimentIds = [ - "experimentalDiffStrategy", "search_and_replace", + "experimentalDiffStrategy", + "multi_search_and_replace", "insert_content", "powerSteering", - "multi_search_and_replace", ] as const export const experimentIdsSchema = z.enum(experimentIds) @@ -291,11 +291,11 @@ export type ExperimentId = z.infer */ const experimentsSchema = z.object({ - experimentalDiffStrategy: z.boolean(), search_and_replace: z.boolean(), + experimentalDiffStrategy: z.boolean(), + multi_search_and_replace: z.boolean(), insert_content: z.boolean(), powerSteering: z.boolean(), - multi_search_and_replace: z.boolean(), }) export type Experiments = z.infer @@ -314,12 +314,12 @@ export const providerSettingsSchema = z.object({ anthropicBaseUrl: z.string().optional(), // Glama glamaModelId: z.string().optional(), - glamaModelInfo: modelInfoSchema.optional(), + glamaModelInfo: modelInfoSchema.nullish(), glamaApiKey: z.string().optional(), // OpenRouter openRouterApiKey: z.string().optional(), openRouterModelId: z.string().optional(), - openRouterModelInfo: modelInfoSchema.optional(), + openRouterModelInfo: modelInfoSchema.nullish(), openRouterBaseUrl: z.string().optional(), openRouterSpecificProvider: z.string().optional(), openRouterUseMiddleOutTransform: z.boolean().optional(), @@ -344,7 +344,7 @@ export const providerSettingsSchema = z.object({ openAiApiKey: z.string().optional(), openAiR1FormatEnabled: z.boolean().optional(), openAiModelId: z.string().optional(), - openAiCustomModelInfo: modelInfoSchema.optional(), + openAiCustomModelInfo: modelInfoSchema.nullish(), openAiUseAzure: z.boolean().optional(), azureApiVersion: z.string().optional(), openAiStreamingEnabled: z.boolean().optional(), @@ -379,11 +379,11 @@ export const providerSettingsSchema = z.object({ // Unbound unboundApiKey: z.string().optional(), unboundModelId: z.string().optional(), - unboundModelInfo: modelInfoSchema.optional(), + unboundModelInfo: modelInfoSchema.nullish(), // Requesty requestyApiKey: z.string().optional(), requestyModelId: z.string().optional(), - requestyModelInfo: modelInfoSchema.optional(), + requestyModelInfo: modelInfoSchema.nullish(), // Claude 3.7 Sonnet Thinking modelTemperature: z.number().nullish(), modelMaxTokens: z.number().optional(), @@ -513,6 +513,7 @@ export const globalSettingsSchema = z.object({ screenshotQuality: z.number().optional(), remoteBrowserEnabled: z.boolean().optional(), remoteBrowserHost: z.string().optional(), + cachedChromeHostUrl: z.string().optional(), enableCheckpoints: z.boolean().optional(), checkpointStorage: checkpointStoragesSchema.optional(), @@ -618,6 +619,7 @@ const globalSettingsRecord: GlobalSettingsRecord = { customModePrompts: undefined, customSupportPrompts: undefined, enhancementApiConfigId: undefined, + cachedChromeHostUrl: undefined, } export const GLOBAL_SETTINGS_KEYS = Object.keys(globalSettingsRecord) as Keys[] @@ -626,6 +628,8 @@ export const GLOBAL_SETTINGS_KEYS = Object.keys(globalSettingsRecord) as Keys +/** + * RooCodeEvent + */ + +export enum RooCodeEventName { + Message = "message", + TaskCreated = "taskCreated", + TaskStarted = "taskStarted", + TaskPaused = "taskPaused", + TaskUnpaused = "taskUnpaused", + TaskAskResponded = "taskAskResponded", + TaskAborted = "taskAborted", + TaskSpawned = "taskSpawned", + TaskCompleted = "taskCompleted", + TaskTokenUsageUpdated = "taskTokenUsageUpdated", +} + +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.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]), + [RooCodeEventName.TaskTokenUsageUpdated]: z.tuple([z.string(), tokenUsageSchema]), +}) + +export type RooCodeEvents = z.infer + /** * TypeDefinition */ -type TypeDefinition = { +export type TypeDefinition = { schema: z.ZodTypeAny identifier: string } @@ -801,4 +843,8 @@ export const typeDefinitions: TypeDefinition[] = [ { schema: globalSettingsSchema, identifier: "GlobalSettings" }, { schema: clineMessageSchema, identifier: "ClineMessage" }, { schema: tokenUsageSchema, identifier: "TokenUsage" }, + { schema: rooCodeEventsSchema, identifier: "RooCodeEvents" }, ] + +// Also export as default for ESM compatibility +export default { typeDefinitions } diff --git a/src/schemas/ipc.ts b/src/schemas/ipc.ts new file mode 100644 index 0000000000..cae0617968 --- /dev/null +++ b/src/schemas/ipc.ts @@ -0,0 +1,113 @@ +import { z } from "zod" + +import { RooCodeEventName, rooCodeEventsSchema, rooCodeSettingsSchema } from "./index" + +/** + * TaskCommand + */ + +export enum TaskCommandName { + StartNewTask = "StartNewTask", +} + +export const taskCommandSchema = z.discriminatedUnion("commandName", [ + z.object({ + commandName: z.literal(TaskCommandName.StartNewTask), + data: z.object({ + configuration: rooCodeSettingsSchema, + text: z.string(), + images: z.array(z.string()).optional(), + newTab: z.boolean().optional(), + }), + }), +]) + +export type TaskCommand = z.infer + +/** + * TaskEvent + */ + +export const taskEventSchema = z.discriminatedUnion("eventName", [ + z.object({ + eventName: z.literal(RooCodeEventName.Message), + payload: rooCodeEventsSchema.shape[RooCodeEventName.Message], + }), + z.object({ + eventName: z.literal(RooCodeEventName.TaskCreated), + payload: rooCodeEventsSchema.shape[RooCodeEventName.TaskCreated], + }), + z.object({ + eventName: z.literal(RooCodeEventName.TaskStarted), + payload: rooCodeEventsSchema.shape[RooCodeEventName.TaskStarted], + }), + z.object({ + eventName: z.literal(RooCodeEventName.TaskPaused), + payload: rooCodeEventsSchema.shape[RooCodeEventName.TaskPaused], + }), + z.object({ + eventName: z.literal(RooCodeEventName.TaskUnpaused), + payload: rooCodeEventsSchema.shape[RooCodeEventName.TaskUnpaused], + }), + z.object({ + eventName: z.literal(RooCodeEventName.TaskAskResponded), + payload: rooCodeEventsSchema.shape[RooCodeEventName.TaskAskResponded], + }), + z.object({ + eventName: z.literal(RooCodeEventName.TaskAborted), + payload: rooCodeEventsSchema.shape[RooCodeEventName.TaskAborted], + }), + z.object({ + eventName: z.literal(RooCodeEventName.TaskSpawned), + payload: rooCodeEventsSchema.shape[RooCodeEventName.TaskSpawned], + }), + z.object({ + eventName: z.literal(RooCodeEventName.TaskCompleted), + payload: rooCodeEventsSchema.shape[RooCodeEventName.TaskCompleted], + }), + z.object({ + eventName: z.literal(RooCodeEventName.TaskTokenUsageUpdated), + payload: rooCodeEventsSchema.shape[RooCodeEventName.TaskTokenUsageUpdated], + }), +]) + +export type TaskEvent = z.infer + +/** + * 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), + origin: z.literal(IpcOrigin.Server), + data: z.object({ clientId: z.string(), pid: z.number(), ppid: z.number() }), + }), + z.object({ + type: z.literal(IpcMessageType.TaskCommand), + origin: z.literal(IpcOrigin.Client), + clientId: z.string(), + data: taskCommandSchema, + }), + z.object({ + type: z.literal(IpcMessageType.TaskEvent), + origin: z.literal(IpcOrigin.Server), + relayClientId: z.string().optional(), + data: taskEventSchema, + }), +]) + +export type IpcMessage = z.infer diff --git a/src/services/browser/BrowserSession.ts b/src/services/browser/BrowserSession.ts index 5c5f59ffeb..7f8963fe1d 100644 --- a/src/services/browser/BrowserSession.ts +++ b/src/services/browser/BrowserSession.ts @@ -9,7 +9,7 @@ import delay from "delay" import axios from "axios" import { fileExistsAtPath } from "../../utils/fs" import { BrowserActionResult } from "../../shared/ExtensionMessage" -import { discoverChromeInstances, testBrowserConnection } from "./browserDiscovery" +import { discoverChromeHostUrl, tryChromeHostUrl } from "./browserDiscovery" interface PCRStats { puppeteer: { launch: typeof launch } @@ -21,20 +21,12 @@ export class BrowserSession { private browser?: Browser private page?: Page private currentMousePosition?: string - private cachedWebSocketEndpoint?: string - private lastConnectionAttempt: number = 0 + private lastConnectionAttempt?: number constructor(context: vscode.ExtensionContext) { this.context = context } - /** - * Test connection to a remote browser - */ - async testConnection(host: string): Promise<{ success: boolean; message: string; endpoint?: string }> { - return testBrowserConnection(host) - } - private async ensureChromiumExists(): Promise { const globalStoragePath = this.context?.globalStorageUri?.fsPath if (!globalStoragePath) { @@ -56,162 +48,173 @@ export class BrowserSession { return stats } - async launchBrowser(): Promise { - console.log("launch browser called") - if (this.browser) { - // throw new Error("Browser already launched") - await this.closeBrowser() // this may happen when the model launches a browser again after having used it already before - } + /** + * Gets the viewport size from global state or returns default + */ + private getViewport() { + const size = (this.context.globalState.get("browserViewportSize") as string | undefined) || "900x600" + const [width, height] = size.split("x").map(Number) + return { width, height } + } - // Function to get viewport size - const getViewport = () => { - const size = (this.context.globalState.get("browserViewportSize") as string | undefined) || "900x600" - const [width, height] = size.split("x").map(Number) - return { width, height } - } - - // Check if remote browser connection is enabled - const remoteBrowserEnabled = this.context.globalState.get("remoteBrowserEnabled") as boolean | undefined - - // If remote browser connection is not enabled, use local browser - if (!remoteBrowserEnabled) { - console.log("Remote browser connection is disabled, using local browser") - const stats = await this.ensureChromiumExists() - this.browser = await stats.puppeteer.launch({ - args: [ - "--user-agent=Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/128.0.0.0 Safari/537.36", - ], - executablePath: stats.executablePath, - defaultViewport: getViewport(), - // headless: false, - }) - this.page = await this.browser?.newPage() - return - } - // Remote browser connection is enabled - let remoteBrowserHost = this.context.globalState.get("remoteBrowserHost") as string | undefined - let browserWSEndpoint: string | undefined = this.cachedWebSocketEndpoint - let reconnectionAttempted = false - - // Try to connect with cached endpoint first if it exists and is recent (less than 1 hour old) - if (browserWSEndpoint && Date.now() - this.lastConnectionAttempt < 3600000) { - try { - console.log(`Attempting to connect using cached WebSocket endpoint: ${browserWSEndpoint}`) - this.browser = await connect({ - browserWSEndpoint, - defaultViewport: getViewport(), - }) - this.page = await this.browser?.newPage() - return - } catch (error) { - console.log(`Failed to connect using cached endpoint: ${error}`) - // Clear the cached endpoint since it's no longer valid - this.cachedWebSocketEndpoint = undefined - // User wants to give up after one reconnection attempt - if (remoteBrowserHost) { - reconnectionAttempted = true - } - } - } - - // If user provided a remote browser host, try to connect to it - if (remoteBrowserHost && !reconnectionAttempted) { - console.log(`Attempting to connect to remote browser at ${remoteBrowserHost}`) - try { - // Fetch the WebSocket endpoint from the Chrome DevTools Protocol - const versionUrl = `${remoteBrowserHost.replace(/\/$/, "")}/json/version` - console.log(`Fetching WebSocket endpoint from ${versionUrl}`) - - const response = await axios.get(versionUrl) - browserWSEndpoint = response.data.webSocketDebuggerUrl - - if (!browserWSEndpoint) { - throw new Error("Could not find webSocketDebuggerUrl in the response") - } - - console.log(`Found WebSocket endpoint: ${browserWSEndpoint}`) - - // Cache the successful endpoint - this.cachedWebSocketEndpoint = browserWSEndpoint - this.lastConnectionAttempt = Date.now() - - this.browser = await connect({ - browserWSEndpoint, - defaultViewport: getViewport(), - }) - this.page = await this.browser?.newPage() - return - } catch (error) { - console.error(`Failed to connect to remote browser: ${error}`) - // Fall back to auto-discovery if remote connection fails - } - } - - // Always try auto-discovery if no custom URL is specified or if connection failed - try { - console.log("Attempting auto-discovery...") - const discoveredHost = await discoverChromeInstances() - - if (discoveredHost) { - console.log(`Auto-discovered Chrome at ${discoveredHost}`) - - // Don't save the discovered host to global state to avoid overriding user preference - // We'll just use it for this session - - // Try to connect to the discovered host - const testResult = await testBrowserConnection(discoveredHost) - - if (testResult.success && testResult.endpoint) { - // Cache the successful endpoint - this.cachedWebSocketEndpoint = testResult.endpoint - this.lastConnectionAttempt = Date.now() - - this.browser = await connect({ - browserWSEndpoint: testResult.endpoint, - defaultViewport: getViewport(), - }) - this.page = await this.browser?.newPage() - return - } - } - } catch (error) { - console.error(`Auto-discovery failed: ${error}`) - // Fall back to local browser if auto-discovery fails - } - - // If all remote connection attempts fail, fall back to local browser - console.log("Falling back to local browser") + /** + * Launches a local browser instance + */ + private async launchLocalBrowser(): Promise { + console.log("Launching local browser") const stats = await this.ensureChromiumExists() this.browser = await stats.puppeteer.launch({ args: [ "--user-agent=Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/128.0.0.0 Safari/537.36", ], executablePath: stats.executablePath, - defaultViewport: getViewport(), + defaultViewport: this.getViewport(), // headless: false, }) - // (latest version of puppeteer does not add headless to user agent) - this.page = await this.browser?.newPage() } + /** + * Connects to a browser using a WebSocket URL + */ + private async connectWithChromeHostUrl(chromeHostUrl: string): Promise { + try { + this.browser = await connect({ + browserURL: chromeHostUrl, + defaultViewport: this.getViewport(), + }) + + // Cache the successful endpoint + console.log(`Connected to remote browser at ${chromeHostUrl}`) + this.context.globalState.update("cachedChromeHostUrl", chromeHostUrl) + this.lastConnectionAttempt = Date.now() + + return true + } catch (error) { + console.log(`Failed to connect using WebSocket endpoint: ${error}`) + return false + } + } + + /** + * Attempts to connect to a remote browser using various methods + * Returns true if connection was successful, false otherwise + */ + private async connectToRemoteBrowser(): Promise { + let remoteBrowserHost = this.context.globalState.get("remoteBrowserHost") as string | undefined + let reconnectionAttempted = false + + // Try to connect with cached endpoint first if it exists and is recent (less than 1 hour old) + const cachedChromeHostUrl = this.context.globalState.get("cachedChromeHostUrl") as string | undefined + if (cachedChromeHostUrl && this.lastConnectionAttempt && Date.now() - this.lastConnectionAttempt < 3_600_000) { + console.log(`Attempting to connect using cached Chrome Host Url: ${cachedChromeHostUrl}`) + if (await this.connectWithChromeHostUrl(cachedChromeHostUrl)) { + return true + } + + console.log(`Failed to connect using cached Chrome Host Url: ${cachedChromeHostUrl}`) + // Clear the cached endpoint since it's no longer valid + this.context.globalState.update("cachedChromeHostUrl", undefined) + + // User wants to give up after one reconnection attempt + if (remoteBrowserHost) { + reconnectionAttempted = true + } + } + + // If user provided a remote browser host, try to connect to it + else if (remoteBrowserHost && !reconnectionAttempted) { + console.log(`Attempting to connect to remote browser at ${remoteBrowserHost}`) + try { + const hostIsValid = await tryChromeHostUrl(remoteBrowserHost) + + if (!hostIsValid) { + throw new Error("Could not find chromeHostUrl in the response") + } + + console.log(`Found WebSocket endpoint: ${remoteBrowserHost}`) + + if (await this.connectWithChromeHostUrl(remoteBrowserHost)) { + return true + } + } catch (error) { + console.error(`Failed to connect to remote browser: ${error}`) + // Fall back to auto-discovery if remote connection fails + } + } + + try { + console.log("Attempting browser auto-discovery...") + const chromeHostUrl = await discoverChromeHostUrl() + + if (chromeHostUrl && (await this.connectWithChromeHostUrl(chromeHostUrl))) { + return true + } + } catch (error) { + console.error(`Auto-discovery failed: ${error}`) + // Fall back to local browser if auto-discovery fails + } + + return false + } + + async launchBrowser(): Promise { + console.log("launch browser called") + + // Check if remote browser connection is enabled + const remoteBrowserEnabled = this.context.globalState.get("remoteBrowserEnabled") as boolean | undefined + + if (!remoteBrowserEnabled) { + console.log("Launching local browser") + if (this.browser) { + // throw new Error("Browser already launched") + await this.closeBrowser() // this may happen when the model launches a browser again after having used it already before + } else { + // If browser wasn't open, just reset the state + this.resetBrowserState() + } + await this.launchLocalBrowser() + } else { + console.log("Connecting to remote browser") + // Remote browser connection is enabled + const remoteConnected = await this.connectToRemoteBrowser() + + // If all remote connection attempts fail, fall back to local browser + if (!remoteConnected) { + console.log("Falling back to local browser") + await this.launchLocalBrowser() + } + } + } + + /** + * Closes the browser and resets browser state + */ async closeBrowser(): Promise { if (this.browser || this.page) { console.log("closing browser...") - const remoteBrowserEnabled = this.context.globalState.get("remoteBrowserEnabled") as string | undefined + const remoteBrowserEnabled = this.context.globalState.get("remoteBrowserEnabled") as boolean | undefined if (remoteBrowserEnabled && this.browser) { await this.browser.disconnect().catch(() => {}) } else { await this.browser?.close().catch(() => {}) + this.resetBrowserState() } - this.browser = undefined - this.page = undefined - this.currentMousePosition = undefined + // this.resetBrowserState() } return {} } + /** + * Resets all browser state variables + */ + private resetBrowserState(): void { + this.browser = undefined + this.page = undefined + this.currentMousePosition = undefined + } + async doAction(action: (page: Page) => Promise): Promise { if (!this.page) { throw new Error( @@ -297,13 +300,118 @@ export class BrowserSession { } } - async navigateToUrl(url: string): Promise { - return this.doAction(async (page) => { - // networkidle2 isn't good enough since page may take some time to load. we can assume locally running dev sites will reach networkidle0 in a reasonable amount of time - await page.goto(url, { timeout: 7_000, waitUntil: ["domcontentloaded", "networkidle2"] }) - // await page.goto(url, { timeout: 10_000, waitUntil: "load" }) - await this.waitTillHTMLStable(page) // in case the page is loading more resources + /** + * Extract the root domain from a URL + * e.g., http://localhost:3000/path -> localhost:3000 + * e.g., https://example.com/path -> example.com + */ + private getRootDomain(url: string): string { + try { + const urlObj = new URL(url) + // Remove www. prefix if present + return urlObj.host.replace(/^www\./, "") + } catch (error) { + // If URL parsing fails, return the original URL + return url + } + } + + /** + * Navigate to a URL with standard loading options + */ + private async navigatePageToUrl(page: Page, url: string): Promise { + await page.goto(url, { timeout: 7_000, waitUntil: ["domcontentloaded", "networkidle2"] }) + await this.waitTillHTMLStable(page) + } + + /** + * Creates a new tab and navigates to the specified URL + */ + private async createNewTab(url: string): Promise { + if (!this.browser) { + throw new Error("Browser is not launched") + } + + // Create a new page + const newPage = await this.browser.newPage() + + // Set the new page as the active page + this.page = newPage + + // Navigate to the URL + const result = await this.doAction(async (page) => { + await this.navigatePageToUrl(page, url) }) + + return result + } + + async navigateToUrl(url: string): Promise { + if (!this.browser) { + throw new Error("Browser is not launched") + } + // Remove trailing slash for comparison + const normalizedNewUrl = url.replace(/\/$/, "") + + // Extract the root domain from the URL + const rootDomain = this.getRootDomain(normalizedNewUrl) + + // Get all current pages + const pages = await this.browser.pages() + + // Try to find a page with the same root domain + let existingPage: Page | undefined + + for (const page of pages) { + try { + const pageUrl = page.url() + if (pageUrl && this.getRootDomain(pageUrl) === rootDomain) { + existingPage = page + break + } + } catch (error) { + // Skip pages that might have been closed or have errors + console.log(`Error checking page URL: ${error}`) + continue + } + } + + if (existingPage) { + // Tab with the same root domain exists, switch to it + console.log(`Tab with domain ${rootDomain} already exists, switching to it`) + + // Update the active page + this.page = existingPage + existingPage.bringToFront() + + // Navigate to the new URL if it's different] + const currentUrl = existingPage.url().replace(/\/$/, "") // Remove trailing / if present + if (this.getRootDomain(currentUrl) === rootDomain && currentUrl !== normalizedNewUrl) { + console.log(`Navigating to new URL: ${normalizedNewUrl}`) + console.log(`Current URL: ${currentUrl}`) + console.log(`Root domain: ${this.getRootDomain(currentUrl)}`) + console.log(`New URL: ${normalizedNewUrl}`) + // Navigate to the new URL + return this.doAction(async (page) => { + await this.navigatePageToUrl(page, normalizedNewUrl) + }) + } else { + console.log(`Tab with domain ${rootDomain} already exists, and URL is the same: ${normalizedNewUrl}`) + // URL is the same, just reload the page to ensure it's up to date + console.log(`Reloading page: ${normalizedNewUrl}`) + console.log(`Current URL: ${currentUrl}`) + console.log(`Root domain: ${this.getRootDomain(currentUrl)}`) + console.log(`New URL: ${normalizedNewUrl}`) + return this.doAction(async (page) => { + await page.reload({ timeout: 7_000, waitUntil: ["domcontentloaded", "networkidle2"] }) + await this.waitTillHTMLStable(page) + }) + } + } else { + // No tab with this root domain exists, create a new one + console.log(`No tab with domain ${rootDomain} exists, creating a new one`) + return this.createNewTab(normalizedNewUrl) + } } // page.goto { waitUntil: "networkidle0" } may not ever resolve, and not waiting could return page content too early before js has loaded @@ -339,36 +447,50 @@ export class BrowserSession { } } - async click(coordinate: string): Promise { + /** + * Handles mouse interaction with network activity monitoring + */ + private async handleMouseInteraction( + page: Page, + coordinate: string, + action: (x: number, y: number) => Promise, + ): Promise { const [x, y] = coordinate.split(",").map(Number) + + // Set up network request monitoring + let hasNetworkActivity = false + const requestListener = () => { + hasNetworkActivity = true + } + page.on("request", requestListener) + + // Perform the mouse action + await action(x, y) + this.currentMousePosition = coordinate + + // Small delay to check if action triggered any network activity + await delay(100) + + if (hasNetworkActivity) { + // If we detected network activity, wait for navigation/loading + await page + .waitForNavigation({ + waitUntil: ["domcontentloaded", "networkidle2"], + timeout: 7000, + }) + .catch(() => {}) + await this.waitTillHTMLStable(page) + } + + // Clean up listener + page.off("request", requestListener) + } + + async click(coordinate: string): Promise { return this.doAction(async (page) => { - // Set up network request monitoring - let hasNetworkActivity = false - const requestListener = () => { - hasNetworkActivity = true - } - page.on("request", requestListener) - - // Perform the click - await page.mouse.click(x, y) - this.currentMousePosition = coordinate - - // Small delay to check if click triggered any network activity - await delay(100) - - if (hasNetworkActivity) { - // If we detected network activity, wait for navigation/loading - await page - .waitForNavigation({ - waitUntil: ["domcontentloaded", "networkidle2"], - timeout: 7000, - }) - .catch(() => {}) - await this.waitTillHTMLStable(page) - } - - // Clean up listener - page.off("request", requestListener) + await this.handleMouseInteraction(page, coordinate, async (x, y) => { + await page.mouse.click(x, y) + }) }) } @@ -378,31 +500,42 @@ export class BrowserSession { }) } + /** + * Scrolls the page by the specified amount + */ + private async scrollPage(page: Page, direction: "up" | "down"): Promise { + const { height } = this.getViewport() + const scrollAmount = direction === "down" ? height : -height + + await page.evaluate((scrollHeight) => { + window.scrollBy({ + top: scrollHeight, + behavior: "auto", + }) + }, scrollAmount) + + await delay(300) + } + async scrollDown(): Promise { - const size = ((await this.context.globalState.get("browserViewportSize")) as string | undefined) || "900x600" - const height = parseInt(size.split("x")[1]) return this.doAction(async (page) => { - await page.evaluate((scrollHeight) => { - window.scrollBy({ - top: scrollHeight, - behavior: "auto", - }) - }, height) - await delay(300) + await this.scrollPage(page, "down") }) } async scrollUp(): Promise { - const size = ((await this.context.globalState.get("browserViewportSize")) as string | undefined) || "900x600" - const height = parseInt(size.split("x")[1]) return this.doAction(async (page) => { - await page.evaluate((scrollHeight) => { - window.scrollBy({ - top: -scrollHeight, - behavior: "auto", - }) - }, height) - await delay(300) + await this.scrollPage(page, "up") + }) + } + + async hover(coordinate: string): Promise { + return this.doAction(async (page) => { + await this.handleMouseInteraction(page, coordinate, async (x, y) => { + await page.mouse.move(x, y) + // Small delay to allow any hover effects to appear + await delay(300) + }) }) } } diff --git a/src/services/browser/browserDiscovery.ts b/src/services/browser/browserDiscovery.ts index 187f90e299..b17e166a9b 100644 --- a/src/services/browser/browserDiscovery.ts +++ b/src/services/browser/browserDiscovery.ts @@ -1,7 +1,6 @@ -import * as vscode from "vscode" -import * as os from "os" import * as net from "net" import axios from "axios" +import * as dns from "dns" /** * Check if a port is open on a given host @@ -43,46 +42,14 @@ export async function isPortOpen(host: string, port: number, timeout = 1000): Pr /** * Try to connect to Chrome at a specific IP address */ -export async function tryConnect(ipAddress: string): Promise<{ endpoint: string; ip: string } | null> { +export async function tryChromeHostUrl(chromeHostUrl: string): Promise { try { - console.log(`Trying to connect to Chrome at: http://${ipAddress}:9222/json/version`) - const response = await axios.get(`http://${ipAddress}:9222/json/version`, { timeout: 1000 }) + console.log(`Trying to connect to Chrome at: ${chromeHostUrl}/json/version`) + const response = await axios.get(`${chromeHostUrl}/json/version`, { timeout: 1000 }) const data = response.data - return { endpoint: data.webSocketDebuggerUrl, ip: ipAddress } + return true } catch (error) { - return null - } -} - -/** - * Execute a shell command and return stdout and stderr - */ -export async function executeShellCommand(command: string): Promise<{ stdout: string; stderr: string }> { - return new Promise<{ stdout: string; stderr: string }>((resolve) => { - const cp = require("child_process") - cp.exec(command, (err: any, stdout: string, stderr: string) => { - resolve({ stdout, stderr }) - }) - }) -} - -/** - * Get Docker gateway IP without UI feedback - */ -export async function getDockerGatewayIP(): Promise { - try { - if (process.platform === "linux") { - try { - const { stdout } = await executeShellCommand("ip route | grep default | awk '{print $3}'") - return stdout.trim() - } catch (error) { - console.log("Could not determine Docker gateway IP:", error) - } - } - return null - } catch (error) { - console.log("Could not determine Docker gateway IP:", error) - return null + return false } } @@ -93,7 +60,6 @@ export async function getDockerHostIP(): Promise { try { // Try to resolve host.docker.internal (works on Docker Desktop) return new Promise((resolve) => { - const dns = require("dns") dns.lookup("host.docker.internal", (err: any, address: string) => { if (err) { resolve(null) @@ -111,7 +77,7 @@ export async function getDockerHostIP(): Promise { /** * Scan a network range for Chrome debugging port */ -export async function scanNetworkForChrome(baseIP: string): Promise { +export async function scanNetworkForChrome(baseIP: string, port: number): Promise { if (!baseIP || !baseIP.match(/^\d+\.\d+\.\d+\./)) { return null } @@ -130,7 +96,7 @@ export async function scanNetworkForChrome(baseIP: string): Promise { +// Function to discover Chrome instances on the network +const discoverChromeHosts = async (port: number): Promise => { // Get all network interfaces - const networkInterfaces = os.networkInterfaces() const ipAddresses = [] - // Always try localhost first - ipAddresses.push("localhost") - ipAddresses.push("127.0.0.1") - - // Try to get Docker gateway IP (headless mode) - const gatewayIP = await getDockerGatewayIP() - if (gatewayIP) { - console.log("Found Docker gateway IP:", gatewayIP) - ipAddresses.push(gatewayIP) - } - // Try to get Docker host IP const hostIP = await getDockerHostIP() if (hostIP) { @@ -166,44 +118,21 @@ export async function discoverChromeInstances(): Promise { ipAddresses.push(hostIP) } - // Add all local IP addresses from network interfaces - const localIPs: string[] = [] - Object.values(networkInterfaces).forEach((interfaces) => { - if (!interfaces) return - interfaces.forEach((iface) => { - // Only consider IPv4 addresses - if (iface.family === "IPv4" || iface.family === (4 as any)) { - localIPs.push(iface.address) - } - }) - }) - - // Add local IPs to the list - ipAddresses.push(...localIPs) - - // Scan network for Chrome debugging port - for (const ip of localIPs) { - const chromeIP = await scanNetworkForChrome(ip) - if (chromeIP && !ipAddresses.includes(chromeIP)) { - console.log("Found potential Chrome host via network scan:", chromeIP) - ipAddresses.push(chromeIP) - } - } - // Remove duplicates const uniqueIPs = [...new Set(ipAddresses)] console.log("IP Addresses to try:", uniqueIPs) // Try connecting to each IP address for (const ip of uniqueIPs) { - const connection = await tryConnect(ip) - if (connection) { - console.log(`Successfully connected to Chrome at: ${connection.ip}`) + const hostEndpoint = `http://${ip}:${port}` + + const hostIsValid = await tryChromeHostUrl(hostEndpoint) + if (hostIsValid) { // Store the successful IP for future use - console.log(`✅ Found Chrome at ${connection.ip} - You can hardcode this IP if needed`) + console.log(`✅ Found Chrome at ${hostEndpoint}`) // Return the host URL and endpoint - return `http://${connection.ip}:9222` + return hostEndpoint } } @@ -211,36 +140,43 @@ export async function discoverChromeInstances(): Promise { } /** - * Test connection to a remote browser + * Test connection to a remote browser debugging websocket. + * First tries specific hosts, then attempts auto-discovery if needed. + * @param browserHostUrl Optional specific host URL to check first + * @param port Browser debugging port (default: 9222) + * @returns WebSocket debugger URL if connection is successful, null otherwise */ -export async function testBrowserConnection( - host: string, -): Promise<{ success: boolean; message: string; endpoint?: string }> { - try { - // Fetch the WebSocket endpoint from the Chrome DevTools Protocol - const versionUrl = `${host.replace(/\/$/, "")}/json/version` - console.log(`Testing connection to ${versionUrl}`) +export async function discoverChromeHostUrl(port: number = 9222): Promise { + // First try specific hosts + const hostsToTry = [`http://localhost:${port}`, `http://127.0.0.1:${port}`] - const response = await axios.get(versionUrl, { timeout: 3000 }) - const browserWSEndpoint = response.data.webSocketDebuggerUrl - - if (!browserWSEndpoint) { - return { - success: false, - message: "Could not find webSocketDebuggerUrl in the response", - } - } - - return { - success: true, - message: "Successfully connected to Chrome browser", - endpoint: browserWSEndpoint, - } - } catch (error) { - console.error(`Failed to connect to remote browser: ${error}`) - return { - success: false, - message: `Failed to connect: ${error instanceof Error ? error.message : String(error)}`, + // Try each host directly first + for (const hostUrl of hostsToTry) { + console.log(`Trying to connect to: ${hostUrl}`) + try { + const hostIsValid = await tryChromeHostUrl(hostUrl) + if (hostIsValid) return hostUrl + } catch (error) { + console.log(`Failed to connect to ${hostUrl}: ${error instanceof Error ? error.message : error}`) } } + + // If direct connections failed, attempt auto-discovery + console.log("Direct connections failed. Attempting auto-discovery...") + + const discoveredHostUrl = await discoverChromeHosts(port) + if (discoveredHostUrl) { + console.log(`Trying to connect to discovered host: ${discoveredHostUrl}`) + try { + const hostIsValid = await tryChromeHostUrl(discoveredHostUrl) + if (hostIsValid) return discoveredHostUrl + console.log(`Failed to connect to discovered host ${discoveredHostUrl}`) + } catch (error) { + console.log(`Error connecting to discovered host: ${error instanceof Error ? error.message : error}`) + } + } else { + console.log("No browser instances discovered on network") + } + + return null } diff --git a/src/shared/ExtensionMessage.ts b/src/shared/ExtensionMessage.ts index eb5e89a2b4..60c20b6503 100644 --- a/src/shared/ExtensionMessage.ts +++ b/src/shared/ExtensionMessage.ts @@ -233,7 +233,7 @@ export interface ClineSayTool { } // Must keep in sync with system prompt. -export const browserActions = ["launch", "click", "type", "scroll_down", "scroll_up", "close"] as const +export const browserActions = ["launch", "click", "hover", "type", "scroll_down", "scroll_up", "close"] as const export type BrowserAction = (typeof browserActions)[number] diff --git a/src/shared/WebviewMessage.ts b/src/shared/WebviewMessage.ts index 6b23d63b29..2cb1658988 100644 --- a/src/shared/WebviewMessage.ts +++ b/src/shared/WebviewMessage.ts @@ -115,7 +115,6 @@ export interface WebviewMessage { | "telemetrySetting" | "showRooIgnoredFiles" | "testBrowserConnection" - | "discoverBrowser" | "browserConnectionResult" | "remoteBrowserEnabled" | "language" diff --git a/src/shared/__tests__/support-prompts.test.ts b/src/shared/__tests__/support-prompts.test.ts index edee6c255d..3d286d0230 100644 --- a/src/shared/__tests__/support-prompts.test.ts +++ b/src/shared/__tests__/support-prompts.test.ts @@ -10,8 +10,7 @@ describe("Code Action Prompts", () => { filePath: testFilePath, selectedText: testCode, }) - - expect(prompt).toContain(`@/${testFilePath}`) + expect(prompt).toContain(testFilePath) expect(prompt).toContain(testCode) expect(prompt).toContain("purpose and functionality") expect(prompt).toContain("Key components") @@ -25,8 +24,7 @@ describe("Code Action Prompts", () => { filePath: testFilePath, selectedText: testCode, }) - - expect(prompt).toContain(`@/${testFilePath}`) + expect(prompt).toContain(testFilePath) expect(prompt).toContain(testCode) expect(prompt).toContain("Address all detected problems") expect(prompt).not.toContain("Current problems detected") @@ -64,8 +62,7 @@ describe("Code Action Prompts", () => { filePath: testFilePath, selectedText: testCode, }) - - expect(prompt).toContain(`@/${testFilePath}`) + expect(prompt).toContain(testFilePath) expect(prompt).toContain(testCode) expect(prompt).toContain("Code readability") expect(prompt).toContain("Performance optimization") diff --git a/src/shared/api.ts b/src/shared/api.ts index d90dec6ad8..06f8cb82bf 100644 --- a/src/shared/api.ts +++ b/src/shared/api.ts @@ -464,6 +464,14 @@ export const vertexModels = { inputPrice: 0.15, outputPrice: 0.6, }, + "gemini-2.5-pro-exp-03-25": { + maxTokens: 65_535, + contextWindow: 1_048_576, + supportsImages: true, + supportsPromptCache: false, + inputPrice: 0, + outputPrice: 0, + }, "gemini-2.0-pro-exp-02-05": { maxTokens: 8192, contextWindow: 2_097_152, diff --git a/src/shared/experiments.ts b/src/shared/experiments.ts index b731863e0b..9d931d5a07 100644 --- a/src/shared/experiments.ts +++ b/src/shared/experiments.ts @@ -4,11 +4,11 @@ import { AssertEqual, Equals, Keys, Values } from "../utils/type-fu" export type { ExperimentId } export const EXPERIMENT_IDS = { - DIFF_STRATEGY: "experimentalDiffStrategy", - SEARCH_AND_REPLACE: "search_and_replace", + DIFF_STRATEGY_SEARCH_AND_REPLACE: "search_and_replace", + DIFF_STRATEGY_UNIFIED: "experimentalDiffStrategy", + DIFF_STRATEGY_MULTI_SEARCH_AND_REPLACE: "multi_search_and_replace", INSERT_BLOCK: "insert_content", POWER_STEERING: "powerSteering", - MULTI_SEARCH_AND_REPLACE: "multi_search_and_replace", } as const satisfies Record type _AssertExperimentIds = AssertEqual>> @@ -20,11 +20,11 @@ interface ExperimentConfig { } export const experimentConfigsMap: Record = { - DIFF_STRATEGY: { enabled: false }, - SEARCH_AND_REPLACE: { enabled: false }, + DIFF_STRATEGY_SEARCH_AND_REPLACE: { enabled: false }, + DIFF_STRATEGY_UNIFIED: { enabled: false }, + DIFF_STRATEGY_MULTI_SEARCH_AND_REPLACE: { enabled: false }, INSERT_BLOCK: { enabled: false }, POWER_STEERING: { enabled: false }, - MULTI_SEARCH_AND_REPLACE: { enabled: false }, } export const experimentDefault = Object.fromEntries( diff --git a/src/shared/support-prompt.ts b/src/shared/support-prompt.ts index d6391ff380..ee8c4fa3e8 100644 --- a/src/shared/support-prompt.ts +++ b/src/shared/support-prompt.ts @@ -35,7 +35,7 @@ const supportPromptConfigs: Record = { \${userInput}`, }, EXPLAIN: { - template: `Explain the following code from file path @/\${filePath} \${startLine}:\${endLine} + template: `Explain the following code from file path \${filePath}:\${startLine}-\${endLine} \${userInput} \`\`\` @@ -48,7 +48,7 @@ Please provide a clear and concise explanation of what this code does, including 3. Important patterns or techniques used`, }, FIX: { - template: `Fix any issues in the following code from file path @/\${filePath} \${startLine}:\${endLine} + template: `Fix any issues in the following code from file path \${filePath}:\${startLine}-\${endLine} \${diagnosticText} \${userInput} @@ -63,7 +63,7 @@ Please: 4. Explain what was fixed and why`, }, IMPROVE: { - template: `Improve the following code from file path @/\${filePath} \${startLine}:\${endLine} + template: `Improve the following code from file path \${filePath}:\${startLine}-\${endLine} \${userInput} \`\`\` @@ -79,7 +79,7 @@ Please suggest improvements for: Provide the improved code along with explanations for each enhancement.`, }, ADD_TO_CONTEXT: { - template: `\${filePath}:\${startLine}:\${endLine} + template: `\${filePath}:\${startLine}-\${endLine} \`\`\` \${selectedText} \`\`\``, diff --git a/webview-ui/src/components/chat/AutoApproveMenu.tsx b/webview-ui/src/components/chat/AutoApproveMenu.tsx index b3a55c94ec..47565a36d6 100644 --- a/webview-ui/src/components/chat/AutoApproveMenu.tsx +++ b/webview-ui/src/components/chat/AutoApproveMenu.tsx @@ -2,6 +2,8 @@ import { VSCodeCheckbox } from "@vscode/webview-ui-toolkit/react" import { useCallback, useState } from "react" import { useExtensionState } from "../../context/ExtensionStateContext" import { useAppTranslation } from "../../i18n/TranslationContext" +import { Trans } from "react-i18next" +import { VSCodeLink } from "@vscode/webview-ui-toolkit/react" import { vscode } from "../../utils/vscode" interface AutoApproveAction { @@ -158,6 +160,10 @@ const AutoApproveMenu = ({ style }: AutoApproveMenuProps) => { vscode.postMessage({ type: "alwaysApproveResubmit", bool: newValue }) }, [alwaysApproveResubmit, setAlwaysApproveResubmit]) + const handleOpenSettings = useCallback(() => { + window.postMessage({ type: "action", action: "settingsButtonClicked" }) + }, []) + // Map action IDs to their specific handlers const actionHandlers: Record void> = { readFiles: handleReadOnlyChange, @@ -243,7 +249,12 @@ const AutoApproveMenu = ({ style }: AutoApproveMenuProps) => { color: "var(--vscode-descriptionForeground)", fontSize: "12px", }}> - {t("chat:autoApprove.description")} + , + }} + /> {actions.map((action) => (
diff --git a/webview-ui/src/components/chat/ChatRow.tsx b/webview-ui/src/components/chat/ChatRow.tsx index f1a7ed994b..7fe200c3e9 100644 --- a/webview-ui/src/components/chat/ChatRow.tsx +++ b/webview-ui/src/components/chat/ChatRow.tsx @@ -356,6 +356,7 @@ export const ChatRowContent = ({ textAlign: "left", }}> {removeLeadingNonAlphanumeric(tool.path ?? "") + "\u200E"} + {tool.reason}
path.replace(/^[^a-zA-Z0-9]+/, "") +export const removeLeadingNonAlphanumeric = (path: string): string => { + // Only remove specific punctuation characters that might interfere with ellipsis display + // Keep all language characters (including CJK, Cyrillic, etc.) and numbers + return path.replace(/^[/\\:*?"<>|]+/, "") +} const CodeAccordian = ({ code, diff --git a/webview-ui/src/components/settings/AdvancedSettings.tsx b/webview-ui/src/components/settings/AdvancedSettings.tsx index e0a909a373..a54386ad30 100644 --- a/webview-ui/src/components/settings/AdvancedSettings.tsx +++ b/webview-ui/src/components/settings/AdvancedSettings.tsx @@ -68,8 +68,8 @@ export const AdvancedSettings = ({ setCachedStateField("diffEnabled", e.target.checked) if (!e.target.checked) { // Reset both experimental strategies when diffs are disabled. - setExperimentEnabled(EXPERIMENT_IDS.DIFF_STRATEGY, false) - setExperimentEnabled(EXPERIMENT_IDS.MULTI_SEARCH_AND_REPLACE, false) + setExperimentEnabled(EXPERIMENT_IDS.DIFF_STRATEGY_UNIFIED, false) + setExperimentEnabled(EXPERIMENT_IDS.DIFF_STRATEGY_MULTI_SEARCH_AND_REPLACE, false) } }}> {t("settings:advanced.diff.label")} @@ -87,22 +87,31 @@ export const AdvancedSettings = ({
- {!experiments[EXPERIMENT_IDS.DIFF_STRATEGY] && - !experiments[EXPERIMENT_IDS.MULTI_SEARCH_AND_REPLACE] && - t("settings:advanced.diff.strategy.descriptions.standard")} - {experiments[EXPERIMENT_IDS.DIFF_STRATEGY] && - t("settings:advanced.diff.strategy.descriptions.unified")} - {experiments[EXPERIMENT_IDS.MULTI_SEARCH_AND_REPLACE] && - t("settings:advanced.diff.strategy.descriptions.multiBlock")} + {experiments[EXPERIMENT_IDS.DIFF_STRATEGY_UNIFIED] + ? t("settings:advanced.diff.strategy.descriptions.unified") + : experiments[EXPERIMENT_IDS.DIFF_STRATEGY_MULTI_SEARCH_AND_REPLACE] + ? t("settings:advanced.diff.strategy.descriptions.multiBlock") + : t("settings:advanced.diff.strategy.descriptions.standard")}
diff --git a/webview-ui/src/components/settings/ApiOptions.tsx b/webview-ui/src/components/settings/ApiOptions.tsx index 40e329fca3..cf6eec02d6 100644 --- a/webview-ui/src/components/settings/ApiOptions.tsx +++ b/webview-ui/src/components/settings/ApiOptions.tsx @@ -8,8 +8,6 @@ import { Checkbox } from "vscrui" import { VSCodeLink, VSCodeRadio, VSCodeRadioGroup, VSCodeTextField } from "@vscode/webview-ui-toolkit/react" import { ExternalLinkIcon } from "@radix-ui/react-icons" -import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue, SelectSeparator, Button } from "@/components/ui" - import { ApiConfiguration, ModelInfo, @@ -42,56 +40,23 @@ import { import { ExtensionMessage } from "../../../../src/shared/ExtensionMessage" import { vscode } from "@/utils/vscode" +import { validateApiConfiguration, validateModelId, validateBedrockArn } from "@/utils/validate" import { useOpenRouterModelProviders, OPENROUTER_DEFAULT_PROVIDER_NAME, } from "@/components/ui/hooks/useOpenRouterModelProviders" -import { useOpenRouterKeyInfo } from "@/components/ui/hooks/useOpenRouterKeyInfo" -import { useRequestyKeyInfo } from "@/components/ui/hooks/useRequestyKeyInfo" +import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue, SelectSeparator, Button } from "@/components/ui" + import { MODELS_BY_PROVIDER, PROVIDERS, AWS_REGIONS, VERTEX_REGIONS } from "./constants" import { VSCodeButtonLink } from "../common/VSCodeButtonLink" import { ModelInfoView } from "./ModelInfoView" import { ModelPicker } from "./ModelPicker" import { TemperatureControl } from "./TemperatureControl" -import { validateApiConfiguration, validateModelId, validateBedrockArn } from "@/utils/validate" import { ApiErrorMessage } from "./ApiErrorMessage" import { ThinkingBudget } from "./ThinkingBudget" import { R1FormatSetting } from "./R1FormatSetting" - -// Component to display OpenRouter API key balance -const OpenRouterBalanceDisplay = ({ apiKey, baseUrl }: { apiKey: string; baseUrl?: string }) => { - const { data: keyInfo } = useOpenRouterKeyInfo(apiKey, baseUrl) - - if (!keyInfo || !keyInfo.limit) { - return null - } - - const formattedBalance = (keyInfo.limit - keyInfo.usage).toFixed(2) - - return ( - - ${formattedBalance} - - ) -} - -const RequestyBalanceDisplay = ({ apiKey }: { apiKey: string }) => { - const { data: keyInfo } = useRequestyKeyInfo(apiKey) - - if (!keyInfo) { - return null - } - - // Parse the balance to a number and format it to 2 decimal places - const balance = parseFloat(keyInfo.org_balance) - const formattedBalance = balance.toFixed(2) - - return ( - - ${formattedBalance} - - ) -} +import { OpenRouterBalanceDisplay } from "./OpenRouterBalanceDisplay" +import { RequestyBalanceDisplay } from "./RequestyBalanceDisplay" interface ApiOptionsProps { uriScheme: string | undefined @@ -278,10 +243,55 @@ const ApiOptions = ({ [selectedProvider], ) + // Base URL for provider documentation + const DOC_BASE_URL = "https://docs.roocode.com/providers" + + // Custom URL path mappings for providers with different slugs + const providerUrlSlugs: Record = { + "openai-native": "openai", + openai: "openai-compatible", + } + + // Helper function to get provider display name from PROVIDERS constant + const getProviderDisplayName = (providerKey: string): string | undefined => { + const provider = PROVIDERS.find((p) => p.value === providerKey) + return provider?.label + } + + // Helper function to get the documentation URL and name for the currently selected provider + const getSelectedProviderDocUrl = (): { url: string; name: string } | undefined => { + const displayName = getProviderDisplayName(selectedProvider) + if (!displayName) { + return undefined + } + + // Get the URL slug - use custom mapping if available, otherwise use the provider key + const urlSlug = providerUrlSlugs[selectedProvider] || selectedProvider + + return { + url: `${DOC_BASE_URL}/${urlSlug}`, + name: displayName, + } + } + return (
-
- +
+
+ + {getSelectedProviderDocUrl() && ( +
+ + {t("settings:providers.providerDocumentation", { + provider: getSelectedProviderDocUrl()!.name, + })} + +
+ )} +