Get package publisher and name from package.json + command type safety (#3766)

This commit is contained in:
Chris Estreich 2025-05-20 16:41:03 -07:00 committed by GitHub
parent ff837d5cbf
commit f274a150c4
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
39 changed files with 361 additions and 235 deletions

2
.gitignore vendored
View file

@ -13,7 +13,7 @@ mock/
# Builds
bin/
roo-cline-*.vsix
*.vsix
# Local prompts and rules
/local-prompts

21
e2e/package-lock.json generated
View file

@ -8,6 +8,7 @@
"name": "e2e",
"version": "0.1.0",
"devDependencies": {
"@roo-code/types": "^1.12.0",
"@types/mocha": "^10.0.10",
"@vscode/test-cli": "^0.0.10",
"@vscode/test-electron": "^2.4.0",
@ -89,6 +90,16 @@
"node": ">=14"
}
},
"node_modules/@roo-code/types": {
"version": "1.12.0",
"resolved": "https://registry.npmjs.org/@roo-code/types/-/types-1.12.0.tgz",
"integrity": "sha512-djdZ4lzsiOc+umX357JvcSwRlAMm05P+8DU58IFyZERmEh8wkm4TglDuaaRVGtQSHw9YGFikqfruLtZSEb7zJQ==",
"dev": true,
"license": "MIT",
"dependencies": {
"zod": "^3.24.4"
}
},
"node_modules/@types/istanbul-lib-coverage": {
"version": "2.0.6",
"resolved": "https://registry.npmjs.org/@types/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.6.tgz",
@ -2344,6 +2355,16 @@
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/zod": {
"version": "3.25.8",
"resolved": "https://registry.npmjs.org/zod/-/zod-3.25.8.tgz",
"integrity": "sha512-iJPWX8HoZ2VE21VrhHGU9jVo/kVDUQyqM9vF0MxDhW/fp2sAl1eVwGJgiYZdHGiMwQJImXIW80lKk0MnfDxqiQ==",
"dev": true,
"license": "MIT",
"funding": {
"url": "https://github.com/sponsors/colinhacks"
}
}
}
}

View file

@ -10,8 +10,8 @@
"build": "rimraf out && tsc -p tsconfig.json",
"vscode-test": "cd .. && npm run vscode-test"
},
"dependencies": {},
"devDependencies": {
"@roo-code/types": "^1.12.0",
"@types/mocha": "^10.0.10",
"@vscode/test-cli": "^0.0.10",
"@vscode/test-electron": "^2.4.0",

View file

@ -1,24 +1,47 @@
import * as assert from "assert"
import * as vscode from "vscode"
import { Package } from "@roo-code/types"
suite("Roo Code Extension", () => {
test("Commands should be registered", async () => {
const expectedCommands = [
"roo-cline.plusButtonClicked",
"roo-cline.mcpButtonClicked",
"roo-cline.historyButtonClicked",
"roo-cline.popoutButtonClicked",
"roo-cline.settingsButtonClicked",
"roo-cline.openInNewTab",
"roo-cline.explainCode",
"roo-cline.fixCode",
"roo-cline.improveCode",
"SidebarProvider.open",
"SidebarProvider.focus",
"SidebarProvider.resetViewLocation",
"SidebarProvider.toggleVisibility",
"SidebarProvider.removeView",
"activationCompleted",
"plusButtonClicked",
"mcpButtonClicked",
"promptsButtonClicked",
"popoutButtonClicked",
"openInNewTab",
"settingsButtonClicked",
"historyButtonClicked",
"showHumanRelayDialog",
"registerHumanRelayCallback",
"unregisterHumanRelayCallback",
"handleHumanRelayResponse",
"newTask",
"setCustomStoragePath",
"focusInput",
"acceptInput",
"explainCode",
"fixCode",
"improveCode",
"addToContext",
"terminalAddToContext",
"terminalFixCommand",
"terminalExplainCommand",
]
const commands = await vscode.commands.getCommands(true)
const commands = new Set(
(await vscode.commands.getCommands(true)).filter((cmd) => cmd.startsWith(Package.name)),
)
for (const cmd of expectedCommands) {
assert.ok(commands.includes(cmd), `Command ${cmd} should be registered`)
for (const command of expectedCommands) {
assert.ok(commands.has(`${Package.name}.${command}`), `Command ${command} should be registered`)
}
})
})

View file

@ -3,7 +3,7 @@ import Mocha from "mocha"
import { glob } from "glob"
import * as vscode from "vscode"
import type { RooCodeAPI } from "../../../src/exports/roo-code"
import { type RooCodeAPI, Package } from "@roo-code/types"
import { waitFor } from "./utils"
@ -12,7 +12,7 @@ declare global {
}
export async function run() {
const extension = vscode.extensions.getExtension<RooCodeAPI>("RooVeterinaryInc.roo-cline")
const extension = vscode.extensions.getExtension<RooCodeAPI>(`${Package.publisher}.${Package.name}`)
if (!extension) {
throw new Error("Extension not found")
@ -26,7 +26,7 @@ export async function run() {
openRouterModelId: "google/gemini-2.0-flash-001",
})
await vscode.commands.executeCommand("roo-cline.SidebarProvider.focus")
await vscode.commands.executeCommand(`${Package.name}.SidebarProvider.focus`)
await waitFor(() => api.isReady())
// Expose the API to the tests.

View file

@ -1,6 +1,6 @@
import * as assert from "assert"
import type { ClineMessage } from "../../../src/exports/roo-code"
import type { ClineMessage } from "@roo-code/types"
import { waitUntilCompleted } from "./utils"

View file

@ -1,6 +1,6 @@
import * as assert from "assert"
import type { ClineMessage } from "../../../src/exports/roo-code"
import type { ClineMessage } from "@roo-code/types"
import { sleep, waitFor, waitUntilCompleted } from "./utils"

View file

@ -1,6 +1,6 @@
import * as assert from "assert"
import type { ClineMessage } from "../../../src/exports/roo-code"
import type { ClineMessage } from "@roo-code/types"
import { waitUntilCompleted } from "./utils"

View file

@ -1,4 +1,4 @@
import type { RooCodeAPI } from "../../../src/exports/roo-code"
import type { RooCodeAPI } from "@roo-code/types"
type WaitForOptions = {
timeout?: number

View file

@ -1,17 +1,10 @@
import * as vscode from "vscode"
import { CodeActionName, CodeActionId } from "../schemas"
import { getCodeActionCommand } from "../utils/commands"
import { EditorUtils } from "../integrations/editor/EditorUtils"
export type CodeActionName = "EXPLAIN" | "FIX" | "IMPROVE" | "ADD_TO_CONTEXT" | "NEW_TASK"
export type CodeActionId =
| "roo-cline.explainCode"
| "roo-cline.fixCode"
| "roo-cline.improveCode"
| "roo-cline.addToContext"
| "roo-cline.newTask"
export const ACTION_TITLES: Record<CodeActionName, string> = {
export const TITLES: Record<CodeActionName, string> = {
EXPLAIN: "Explain with Roo Code",
FIX: "Fix with Roo Code",
IMPROVE: "Improve with Roo Code",
@ -19,14 +12,6 @@ export const ACTION_TITLES: Record<CodeActionName, string> = {
NEW_TASK: "New Roo Code Task",
} as const
export const COMMAND_IDS: Record<CodeActionName, CodeActionId> = {
EXPLAIN: "roo-cline.explainCode",
FIX: "roo-cline.fixCode",
IMPROVE: "roo-cline.improveCode",
ADD_TO_CONTEXT: "roo-cline.addToContext",
NEW_TASK: "roo-cline.newTask",
} as const
export class CodeActionProvider implements vscode.CodeActionProvider {
public static readonly providedCodeActionKinds = [
vscode.CodeActionKind.QuickFix,
@ -40,7 +25,7 @@ export class CodeActionProvider implements vscode.CodeActionProvider {
args: any[],
): vscode.CodeAction {
const action = new vscode.CodeAction(title, kind)
action.command = { command, title, arguments: args }
action.command = { command: getCodeActionCommand(command), title, arguments: args }
return action
}
@ -60,17 +45,12 @@ export class CodeActionProvider implements vscode.CodeActionProvider {
const actions: vscode.CodeAction[] = []
actions.push(
this.createAction(
ACTION_TITLES.ADD_TO_CONTEXT,
vscode.CodeActionKind.QuickFix,
COMMAND_IDS.ADD_TO_CONTEXT,
[
filePath,
effectiveRange.text,
effectiveRange.range.start.line + 1,
effectiveRange.range.end.line + 1,
],
),
this.createAction(TITLES.ADD_TO_CONTEXT, vscode.CodeActionKind.QuickFix, "addToContext", [
filePath,
effectiveRange.text,
effectiveRange.range.start.line + 1,
effectiveRange.range.end.line + 1,
]),
)
if (context.diagnostics.length > 0) {
@ -80,7 +60,7 @@ export class CodeActionProvider implements vscode.CodeActionProvider {
if (relevantDiagnostics.length > 0) {
actions.push(
this.createAction(ACTION_TITLES.FIX, vscode.CodeActionKind.QuickFix, COMMAND_IDS.FIX, [
this.createAction(TITLES.FIX, vscode.CodeActionKind.QuickFix, "fixCode", [
filePath,
effectiveRange.text,
effectiveRange.range.start.line + 1,
@ -91,7 +71,7 @@ export class CodeActionProvider implements vscode.CodeActionProvider {
}
} else {
actions.push(
this.createAction(ACTION_TITLES.EXPLAIN, vscode.CodeActionKind.QuickFix, COMMAND_IDS.EXPLAIN, [
this.createAction(TITLES.EXPLAIN, vscode.CodeActionKind.QuickFix, "explainCode", [
filePath,
effectiveRange.text,
effectiveRange.range.start.line + 1,
@ -100,7 +80,7 @@ export class CodeActionProvider implements vscode.CodeActionProvider {
)
actions.push(
this.createAction(ACTION_TITLES.IMPROVE, vscode.CodeActionKind.QuickFix, COMMAND_IDS.IMPROVE, [
this.createAction(TITLES.IMPROVE, vscode.CodeActionKind.QuickFix, "improveCode", [
filePath,
effectiveRange.text,
effectiveRange.range.start.line + 1,

View file

@ -4,7 +4,7 @@ import * as vscode from "vscode"
import { EditorUtils } from "../../integrations/editor/EditorUtils"
import { CodeActionProvider, ACTION_TITLES } from "../CodeActionProvider"
import { CodeActionProvider, TITLES } from "../CodeActionProvider"
jest.mock("vscode", () => ({
CodeAction: jest.fn().mockImplementation((title, kind) => ({
@ -70,9 +70,9 @@ describe("CodeActionProvider", () => {
const actions = provider.provideCodeActions(mockDocument, mockRange, mockContext)
expect(actions).toHaveLength(3)
expect((actions as any)[0].title).toBe(ACTION_TITLES.ADD_TO_CONTEXT)
expect((actions as any)[1].title).toBe(ACTION_TITLES.EXPLAIN)
expect((actions as any)[2].title).toBe(ACTION_TITLES.IMPROVE)
expect((actions as any)[0].title).toBe(TITLES.ADD_TO_CONTEXT)
expect((actions as any)[1].title).toBe(TITLES.EXPLAIN)
expect((actions as any)[2].title).toBe(TITLES.IMPROVE)
})
it("should provide fix action instead of fix logic when diagnostics exist", () => {
@ -83,8 +83,8 @@ describe("CodeActionProvider", () => {
const actions = provider.provideCodeActions(mockDocument, mockRange, mockContext)
expect(actions).toHaveLength(2)
expect((actions as any).some((a: any) => a.title === `${ACTION_TITLES.FIX}`)).toBe(true)
expect((actions as any).some((a: any) => a.title === `${ACTION_TITLES.ADD_TO_CONTEXT}`)).toBe(true)
expect((actions as any).some((a: any) => a.title === `${TITLES.FIX}`)).toBe(true)
expect((actions as any).some((a: any) => a.title === `${TITLES.ADD_TO_CONTEXT}`)).toBe(true)
})
it("should return empty array when no effective range", () => {

View file

@ -1,11 +1,9 @@
import * as vscode from "vscode"
import { Package } from "../schemas"
import { ClineProvider } from "../core/webview/ClineProvider"
import { t } from "../i18n"
import { COMMAND_IDS } from "./CodeActionProvider"
export const handleNewTask = async (params: { prompt?: string } | null | undefined) => {
let prompt = params?.prompt
@ -17,9 +15,9 @@ export const handleNewTask = async (params: { prompt?: string } | null | undefin
}
if (!prompt) {
await vscode.commands.executeCommand("roo-cline.SidebarProvider.focus")
await vscode.commands.executeCommand(`${Package.name}.SidebarProvider.focus`)
return
}
await ClineProvider.handleCodeAction(COMMAND_IDS.NEW_TASK, "NEW_TASK", { userInput: prompt })
await ClineProvider.handleCodeAction("newTask", "NEW_TASK", { userInput: prompt })
}

View file

@ -1,22 +1,22 @@
import * as vscode from "vscode"
import { CodeActionId, CodeActionName } from "../schemas"
import { getCodeActionCommand } from "../utils/commands"
import { EditorUtils } from "../integrations/editor/EditorUtils"
import { ClineProvider } from "../core/webview/ClineProvider"
import { type CodeActionName, type CodeActionId, COMMAND_IDS } from "./CodeActionProvider"
export const registerCodeActions = (context: vscode.ExtensionContext) => {
registerCodeAction(context, COMMAND_IDS.EXPLAIN, "EXPLAIN")
registerCodeAction(context, COMMAND_IDS.FIX, "FIX")
registerCodeAction(context, COMMAND_IDS.IMPROVE, "IMPROVE")
registerCodeAction(context, COMMAND_IDS.ADD_TO_CONTEXT, "ADD_TO_CONTEXT")
registerCodeAction(context, "explainCode", "EXPLAIN")
registerCodeAction(context, "fixCode", "FIX")
registerCodeAction(context, "improveCode", "IMPROVE")
registerCodeAction(context, "addToContext", "ADD_TO_CONTEXT")
}
const registerCodeAction = (context: vscode.ExtensionContext, command: CodeActionId, promptType: CodeActionName) => {
let userInput: string | undefined
context.subscriptions.push(
vscode.commands.registerCommand(command, async (...args: any[]) => {
vscode.commands.registerCommand(getCodeActionCommand(command), async (...args: any[]) => {
// Handle both code action and direct command cases.
let filePath: string
let selectedText: string

View file

@ -1,6 +1,8 @@
import * as vscode from "vscode"
import delay from "delay"
import { CommandId, Package } from "../schemas"
import { getCommand } from "../utils/commands"
import { ClineProvider } from "../core/webview/ClineProvider"
import { ContextProxy } from "../core/config/ContextProxy"
import { telemetryService } from "../services/telemetry/TelemetryService"
@ -57,125 +59,124 @@ export type RegisterCommandOptions = {
export const registerCommands = (options: RegisterCommandOptions) => {
const { context } = options
for (const [command, callback] of Object.entries(getCommandsMap(options))) {
for (const [id, callback] of Object.entries(getCommandsMap(options))) {
const command = getCommand(id as CommandId)
context.subscriptions.push(vscode.commands.registerCommand(command, callback))
}
}
const getCommandsMap = ({ context, outputChannel, provider }: RegisterCommandOptions) => {
return {
"roo-cline.activationCompleted": () => {},
"roo-cline.plusButtonClicked": async () => {
const visibleProvider = getVisibleProviderOrLog(outputChannel)
const getCommandsMap = ({ context, outputChannel, provider }: RegisterCommandOptions): Record<CommandId, any> => ({
activationCompleted: () => {},
plusButtonClicked: async () => {
const visibleProvider = getVisibleProviderOrLog(outputChannel)
if (!visibleProvider) {
return
}
if (!visibleProvider) {
return
}
telemetryService.captureTitleButtonClicked("plus")
telemetryService.captureTitleButtonClicked("plus")
await visibleProvider.removeClineFromStack()
await visibleProvider.postStateToWebview()
await visibleProvider.postMessageToWebview({ type: "action", action: "chatButtonClicked" })
},
"roo-cline.mcpButtonClicked": () => {
const visibleProvider = getVisibleProviderOrLog(outputChannel)
await visibleProvider.removeClineFromStack()
await visibleProvider.postStateToWebview()
await visibleProvider.postMessageToWebview({ type: "action", action: "chatButtonClicked" })
},
mcpButtonClicked: () => {
const visibleProvider = getVisibleProviderOrLog(outputChannel)
if (!visibleProvider) {
return
}
if (!visibleProvider) {
return
}
telemetryService.captureTitleButtonClicked("mcp")
telemetryService.captureTitleButtonClicked("mcp")
visibleProvider.postMessageToWebview({ type: "action", action: "mcpButtonClicked" })
},
"roo-cline.promptsButtonClicked": () => {
const visibleProvider = getVisibleProviderOrLog(outputChannel)
visibleProvider.postMessageToWebview({ type: "action", action: "mcpButtonClicked" })
},
promptsButtonClicked: () => {
const visibleProvider = getVisibleProviderOrLog(outputChannel)
if (!visibleProvider) {
return
}
if (!visibleProvider) {
return
}
telemetryService.captureTitleButtonClicked("prompts")
telemetryService.captureTitleButtonClicked("prompts")
visibleProvider.postMessageToWebview({ type: "action", action: "promptsButtonClicked" })
},
"roo-cline.popoutButtonClicked": () => {
telemetryService.captureTitleButtonClicked("popout")
visibleProvider.postMessageToWebview({ type: "action", action: "promptsButtonClicked" })
},
popoutButtonClicked: () => {
telemetryService.captureTitleButtonClicked("popout")
return openClineInNewTab({ context, outputChannel })
},
"roo-cline.openInNewTab": () => openClineInNewTab({ context, outputChannel }),
"roo-cline.settingsButtonClicked": () => {
const visibleProvider = getVisibleProviderOrLog(outputChannel)
return openClineInNewTab({ context, outputChannel })
},
openInNewTab: () => openClineInNewTab({ context, outputChannel }),
settingsButtonClicked: () => {
const visibleProvider = getVisibleProviderOrLog(outputChannel)
if (!visibleProvider) {
return
}
if (!visibleProvider) {
return
}
telemetryService.captureTitleButtonClicked("settings")
telemetryService.captureTitleButtonClicked("settings")
visibleProvider.postMessageToWebview({ type: "action", action: "settingsButtonClicked" })
// Also explicitly post the visibility message to trigger scroll reliably
visibleProvider.postMessageToWebview({ type: "action", action: "didBecomeVisible" })
},
"roo-cline.historyButtonClicked": () => {
const visibleProvider = getVisibleProviderOrLog(outputChannel)
visibleProvider.postMessageToWebview({ type: "action", action: "settingsButtonClicked" })
// Also explicitly post the visibility message to trigger scroll reliably
visibleProvider.postMessageToWebview({ type: "action", action: "didBecomeVisible" })
},
historyButtonClicked: () => {
const visibleProvider = getVisibleProviderOrLog(outputChannel)
if (!visibleProvider) {
return
}
if (!visibleProvider) {
return
}
telemetryService.captureTitleButtonClicked("history")
telemetryService.captureTitleButtonClicked("history")
visibleProvider.postMessageToWebview({ type: "action", action: "historyButtonClicked" })
},
"roo-cline.showHumanRelayDialog": (params: { requestId: string; promptText: string }) => {
visibleProvider.postMessageToWebview({ type: "action", action: "historyButtonClicked" })
},
showHumanRelayDialog: (params: { requestId: string; promptText: string }) => {
const panel = getPanel()
if (panel) {
panel?.webview.postMessage({
type: "showHumanRelayDialog",
requestId: params.requestId,
promptText: params.promptText,
})
}
},
registerHumanRelayCallback: registerHumanRelayCallback,
unregisterHumanRelayCallback: unregisterHumanRelayCallback,
handleHumanRelayResponse: handleHumanRelayResponse,
newTask: handleNewTask,
setCustomStoragePath: async () => {
const { promptForCustomStoragePath } = await import("../utils/storage")
await promptForCustomStoragePath()
},
focusInput: async () => {
try {
const panel = getPanel()
if (panel) {
panel?.webview.postMessage({
type: "showHumanRelayDialog",
requestId: params.requestId,
promptText: params.promptText,
})
if (!panel) {
await vscode.commands.executeCommand(`workbench.view.extension.${Package.name}-ActivityBar`)
} else if (panel === tabPanel) {
panel.reveal(vscode.ViewColumn.Active, false)
} else if (panel === sidebarPanel) {
await vscode.commands.executeCommand(`${ClineProvider.sideBarId}.focus`)
provider.postMessageToWebview({ type: "action", action: "focusInput" })
}
},
"roo-cline.registerHumanRelayCallback": registerHumanRelayCallback,
"roo-cline.unregisterHumanRelayCallback": unregisterHumanRelayCallback,
"roo-cline.handleHumanRelayResponse": handleHumanRelayResponse,
"roo-cline.newTask": handleNewTask,
"roo-cline.setCustomStoragePath": async () => {
const { promptForCustomStoragePath } = await import("../shared/storagePathManager")
await promptForCustomStoragePath()
},
"roo-cline.focusInput": async () => {
try {
const panel = getPanel()
} catch (error) {
outputChannel.appendLine(`Error focusing input: ${error}`)
}
},
acceptInput: () => {
const visibleProvider = getVisibleProviderOrLog(outputChannel)
if (!panel) {
await vscode.commands.executeCommand("workbench.view.extension.roo-cline-ActivityBar")
} else if (panel === tabPanel) {
panel.reveal(vscode.ViewColumn.Active, false)
} else if (panel === sidebarPanel) {
await vscode.commands.executeCommand(`${ClineProvider.sideBarId}.focus`)
provider.postMessageToWebview({ type: "action", action: "focusInput" })
}
} catch (error) {
outputChannel.appendLine(`Error focusing input: ${error}`)
}
},
"roo.acceptInput": () => {
const visibleProvider = getVisibleProviderOrLog(outputChannel)
if (!visibleProvider) {
return
}
if (!visibleProvider) {
return
}
visibleProvider.postMessageToWebview({ type: "acceptInput" })
},
}
}
visibleProvider.postMessageToWebview({ type: "acceptInput" })
},
})
export const openClineInNewTab = async ({ context, outputChannel }: Omit<RegisterCommandOptions, "provider">) => {
// (This example uses webviewProvider activation event which is necessary to

View file

@ -1,27 +1,24 @@
import * as vscode from "vscode"
import { TerminalActionId, TerminalActionPromptType } from "../schemas"
import { getTerminalCommand } from "../utils/commands"
import { ClineProvider } from "../core/webview/ClineProvider"
import { Terminal } from "../integrations/terminal/Terminal"
import { t } from "../i18n"
const TERMINAL_COMMAND_IDS = {
ADD_TO_CONTEXT: "roo-cline.terminalAddToContext",
FIX: "roo-cline.terminalFixCommand",
EXPLAIN: "roo-cline.terminalExplainCommand",
} as const
export const registerTerminalActions = (context: vscode.ExtensionContext) => {
registerTerminalAction(context, TERMINAL_COMMAND_IDS.ADD_TO_CONTEXT, "TERMINAL_ADD_TO_CONTEXT")
registerTerminalAction(context, TERMINAL_COMMAND_IDS.FIX, "TERMINAL_FIX")
registerTerminalAction(context, TERMINAL_COMMAND_IDS.EXPLAIN, "TERMINAL_EXPLAIN")
registerTerminalAction(context, "terminalAddToContext", "TERMINAL_ADD_TO_CONTEXT")
registerTerminalAction(context, "terminalFixCommand", "TERMINAL_FIX")
registerTerminalAction(context, "terminalExplainCommand", "TERMINAL_EXPLAIN")
}
const registerTerminalAction = (
context: vscode.ExtensionContext,
command: string,
promptType: "TERMINAL_ADD_TO_CONTEXT" | "TERMINAL_FIX" | "TERMINAL_EXPLAIN",
command: TerminalActionId,
promptType: TerminalActionPromptType,
) => {
context.subscriptions.push(
vscode.commands.registerCommand(command, async (args: any) => {
vscode.commands.registerCommand(getTerminalCommand(command), async (args: any) => {
let content = args.selection
if (!content || content === "") {

View file

@ -4,7 +4,7 @@ import fs from "fs/promises"
import NodeCache from "node-cache"
import { ContextProxy } from "../../../core/config/ContextProxy"
import { getCacheDirectoryPath } from "../../../shared/storagePathManager"
import { getCacheDirectoryPath } from "../../../utils/storage"
import { RouterName, ModelRecord } from "../../../shared/api"
import { fileExistsAtPath } from "../../../utils/fs"

View file

@ -5,7 +5,7 @@ import NodeCache from "node-cache"
import sanitize from "sanitize-filename"
import { ContextProxy } from "../../../core/config/ContextProxy"
import { getCacheDirectoryPath } from "../../../shared/storagePathManager"
import { getCacheDirectoryPath } from "../../../utils/storage"
import { RouterName, ModelRecord } from "../../../shared/api"
import { fileExistsAtPath } from "../../../utils/fs"

View file

@ -2,6 +2,7 @@ import { Anthropic } from "@anthropic-ai/sdk"
import axios from "axios"
import OpenAI from "openai"
import { Package } from "../../schemas"
import { ApiHandlerOptions, glamaDefaultModelId, glamaDefaultModelInfo } from "../../shared/api"
import { ApiStream } from "../transform/stream"
@ -14,7 +15,9 @@ import { RouterProvider } from "./router-provider"
const GLAMA_DEFAULT_TEMPERATURE = 0
const DEFAULT_HEADERS = {
"X-Glama-Metadata": JSON.stringify({ labels: [{ key: "app", value: "vscode.rooveterinaryinc.roo-cline" }] }),
"X-Glama-Metadata": JSON.stringify({
labels: [{ key: "app", value: `vscode.${Package.publisher}.${Package.name}` }],
}),
}
export class GlamaHandler extends RouterProvider implements SingleCompletionHandler {

View file

@ -2,9 +2,9 @@ import { Anthropic } from "@anthropic-ai/sdk"
import * as vscode from "vscode"
import { ModelInfo } from "../../shared/api"
import { ApiHandler, SingleCompletionHandler } from "../index"
import { getCommand } from "../../utils/commands"
import { ApiStream } from "../transform/stream"
import { ApiHandler, SingleCompletionHandler } from "../index"
/**
* Human Relay API processor
* This processor does not directly call the API, but interacts with the model through human operations copy and paste.
@ -111,17 +111,17 @@ function getMessageContent(message: Anthropic.Messages.MessageParam): string {
*/
async function showHumanRelayDialog(promptText: string): Promise<string | undefined> {
return new Promise<string | undefined>((resolve) => {
// Create a unique request ID
// Create a unique request ID.
const requestId = Date.now().toString()
// Register a global callback function
// Register a global callback function.
vscode.commands.executeCommand(
"roo-cline.registerHumanRelayCallback",
getCommand("registerHumanRelayCallback"),
requestId,
(response: string | undefined) => resolve(response),
)
// Open the dialog box directly using the current panel
vscode.commands.executeCommand("roo-cline.showHumanRelayDialog", { requestId, promptText })
// Open the dialog box directly using the current panel.
vscode.commands.executeCommand(getCommand("showHumanRelayDialog"), { requestId, promptText })
})
}

View file

@ -1,6 +1,6 @@
import * as path from "path"
import * as vscode from "vscode"
import { getTaskDirectoryPath } from "../../shared/storagePathManager"
import { getTaskDirectoryPath } from "../../utils/storage"
import { GlobalFileNames } from "../../shared/globalFileNames"
import { fileExistsAtPath } from "../../utils/fs"
import fs from "fs/promises"

View file

@ -6,7 +6,7 @@ import { Anthropic } from "@anthropic-ai/sdk"
import { fileExistsAtPath } from "../../utils/fs"
import { GlobalFileNames } from "../../shared/globalFileNames"
import { getTaskDirectoryPath } from "../../shared/storagePathManager"
import { getTaskDirectoryPath } from "../../utils/storage"
export type ApiMessage = Anthropic.MessageParam & { ts?: number; isSummary?: boolean }

View file

@ -5,7 +5,7 @@ import { fileExistsAtPath } from "../../utils/fs"
import { GlobalFileNames } from "../../shared/globalFileNames"
import { ClineMessage } from "../../shared/ExtensionMessage"
import { getTaskDirectoryPath } from "../../shared/storagePathManager"
import { getTaskDirectoryPath } from "../../utils/storage"
export type ReadTaskMessagesOptions = {
taskId: string

View file

@ -7,7 +7,7 @@ import { combineCommandSequences } from "../../shared/combineCommandSequences"
import { getApiMetrics } from "../../shared/getApiMetrics"
import { findLastIndex } from "../../shared/array"
import { HistoryItem } from "../../shared/HistoryItem"
import { getTaskDirectoryPath } from "../../shared/storagePathManager"
import { getTaskDirectoryPath } from "../../utils/storage"
const taskSizeCache = new NodeCache({ stdTTL: 30, checkperiod: 5 * 60 })

View file

@ -130,7 +130,7 @@ jest.mock("../../environment/getEnvironmentDetails", () => ({
jest.mock("../../ignore/RooIgnoreController")
// Mock storagePathManager to prevent dynamic import issues
jest.mock("../../../shared/storagePathManager", () => ({
jest.mock("../../../utils/storage", () => ({
getTaskDirectoryPath: jest
.fn()
.mockImplementation((globalStoragePath, taskId) => Promise.resolve(`${globalStoragePath}/tasks/${taskId}`)),

View file

@ -9,7 +9,18 @@ import axios from "axios"
import pWaitFor from "p-wait-for"
import * as vscode from "vscode"
import type { GlobalState, ProviderName, ProviderSettings, RooCodeSettings, ProviderSettingsEntry } from "../../schemas"
import {
GlobalState,
ProviderName,
ProviderSettings,
RooCodeSettings,
ProviderSettingsEntry,
Package,
CodeActionId,
CodeActionName,
TerminalActionId,
TerminalActionPromptType,
} from "../../schemas"
import { t } from "../../i18n"
import { setPanel } from "../../activate/registerCommands"
import { requestyDefaultModelId, openRouterDefaultModelId, glamaDefaultModelId } from "../../shared/api"
@ -34,7 +45,6 @@ import { ContextProxy } from "../config/ContextProxy"
import { ProviderSettingsManager } from "../config/ProviderSettingsManager"
import { CustomModesManager } from "../config/CustomModesManager"
import { buildApiHandler } from "../../api"
import { CodeActionName } from "../../activate/CodeActionProvider"
import { Task, TaskOptions } from "../task/Task"
import { getNonce } from "./getNonce"
import { getUri } from "./getUri"
@ -54,8 +64,11 @@ export type ClineProviderEvents = {
}
export class ClineProvider extends EventEmitter<ClineProviderEvents> implements vscode.WebviewViewProvider {
public static readonly sideBarId = "roo-cline.SidebarProvider" // used in package.json as the view's id. This value cannot be changed due to how vscode caches views based on their id, and updating the id would break existing instances of the extension.
public static readonly tabPanelId = "roo-cline.TabPanelProvider"
// Used in package.json as the view's id. This value cannot be changed due
// to how VSCode caches views based on their id, and updating the id would
// break existing instances of the extension.
public static readonly sideBarId = `${Package.name}.SidebarProvider`
public static readonly tabPanelId = `${Package.name}.TabPanelProvider`
private static activeInstances: Set<ClineProvider> = new Set()
private disposables: vscode.Disposable[] = []
private view?: vscode.WebviewView | vscode.WebviewPanel
@ -225,7 +238,7 @@ export class ClineProvider extends EventEmitter<ClineProviderEvents> implements
// If no visible provider, try to show the sidebar view
if (!visibleProvider) {
await vscode.commands.executeCommand("roo-cline.SidebarProvider.focus")
await vscode.commands.executeCommand(`${Package.name}.SidebarProvider.focus`)
// Wait briefly for the view to become visible
await delay(100)
visibleProvider = ClineProvider.getVisibleInstance()
@ -254,7 +267,7 @@ export class ClineProvider extends EventEmitter<ClineProviderEvents> implements
}
public static async handleCodeAction(
command: string,
command: CodeActionId,
promptType: CodeActionName,
params: Record<string, string | any[]>,
): Promise<void> {
@ -272,7 +285,7 @@ export class ClineProvider extends EventEmitter<ClineProviderEvents> implements
// TODO: Improve type safety for promptType.
const prompt = supportPrompt.create(promptType, params, customSupportPrompts)
if (command.endsWith("addToContext")) {
if (command === "addToContext") {
await visibleProvider.postMessageToWebview({ type: "invoke", invoke: "setChatBoxMessage", text: prompt })
return
}
@ -281,12 +294,12 @@ export class ClineProvider extends EventEmitter<ClineProviderEvents> implements
}
public static async handleTerminalAction(
command: string,
promptType: "TERMINAL_ADD_TO_CONTEXT" | "TERMINAL_FIX" | "TERMINAL_EXPLAIN",
command: TerminalActionId,
promptType: TerminalActionPromptType,
params: Record<string, string | any[]>,
): Promise<void> {
// Capture telemetry for terminal action usage
telemetryService.captureCodeActionUsed(promptType)
const visibleProvider = await ClineProvider.getInstance()
if (!visibleProvider) {
@ -294,10 +307,9 @@ export class ClineProvider extends EventEmitter<ClineProviderEvents> implements
}
const { customSupportPrompts } = await visibleProvider.getState()
const prompt = supportPrompt.create(promptType, params, customSupportPrompts)
if (command.endsWith("AddToContext")) {
if (command === "terminalAddToContext") {
await visibleProvider.postMessageToWebview({ type: "invoke", invoke: "setChatBoxMessage", text: prompt })
return
}
@ -974,7 +986,7 @@ export class ClineProvider extends EventEmitter<ClineProviderEvents> implements
}
async ensureSettingsDirectoryExists(): Promise<string> {
const { getSettingsDirectoryPath } = await import("../../shared/storagePathManager")
const { getSettingsDirectoryPath } = await import("../../utils/storage")
const globalStoragePath = this.contextProxy.globalStorageUri.fsPath
return getSettingsDirectoryPath(globalStoragePath)
}
@ -1070,7 +1082,7 @@ export class ClineProvider extends EventEmitter<ClineProviderEvents> implements
const historyItem = history.find((item) => item.id === id)
if (historyItem) {
const { getTaskDirectoryPath } = await import("../../shared/storagePathManager")
const { getTaskDirectoryPath } = await import("../../utils/storage")
const globalStoragePath = this.contextProxy.globalStorageUri.fsPath
const taskDirPath = await getTaskDirectoryPath(globalStoragePath, id)
const apiConversationHistoryFilePath = path.join(taskDirPath, GlobalFileNames.apiConversationHistory)
@ -1260,7 +1272,7 @@ export class ClineProvider extends EventEmitter<ClineProviderEvents> implements
const telemetryKey = process.env.POSTHOG_API_KEY
const machineId = vscode.env.machineId
const allowedCommands = vscode.workspace.getConfiguration("roo-cline").get<string[]>("allowedCommands") || []
const allowedCommands = vscode.workspace.getConfiguration(Package.name).get<string[]>("allowedCommands") || []
const cwd = this.cwd
// Check if there's a system prompt override for the current mode

View file

@ -4,11 +4,10 @@ import pWaitFor from "p-wait-for"
import * as vscode from "vscode"
import { ClineProvider } from "./ClineProvider"
import { Language, ProviderSettings, GlobalState } from "../../schemas"
import { Language, ProviderSettings, GlobalState, Package } from "../../schemas"
import { changeLanguage, t } from "../../i18n"
import { RouterName, toRouterName } from "../../shared/api"
import { supportPrompt } from "../../shared/support-prompt"
import { checkoutDiffPayloadSchema, checkoutRestorePayloadSchema, WebviewMessage } from "../../shared/WebviewMessage"
import { checkExistKey } from "../../shared/checkExistApiConfig"
import { experimentDefault } from "../../shared/experiments"
@ -34,6 +33,7 @@ import { getWorkspacePath } from "../../utils/path"
import { Mode, defaultModeSlug } from "../../shared/modes"
import { getModels, flushModels } from "../../api/providers/fetchers/modelCache"
import { generateSystemPrompt } from "./generateSystemPrompt"
import { getCommand } from "../../utils/commands"
const ALLOWED_VSCODE_SETTINGS = new Set(["terminal.integrated.inheritEnv"])
@ -376,7 +376,7 @@ export const webviewMessageHandler = async (provider: ClineProvider, message: We
// Also update workspace settings.
await vscode.workspace
.getConfiguration("roo-cline")
.getConfiguration(Package.name)
.update("allowedCommands", message.commands, vscode.ConfigurationTarget.Global)
break
@ -1230,7 +1230,7 @@ export const webviewMessageHandler = async (provider: ClineProvider, message: We
break
case "humanRelayResponse":
if (message.requestId && message.text) {
vscode.commands.executeCommand("roo-cline.handleHumanRelayResponse", {
vscode.commands.executeCommand(getCommand("handleHumanRelayResponse"), {
requestId: message.requestId,
text: message.text,
cancelled: false,
@ -1240,7 +1240,7 @@ export const webviewMessageHandler = async (provider: ClineProvider, message: We
case "humanRelayCancel":
if (message.requestId) {
vscode.commands.executeCommand("roo-cline.handleHumanRelayResponse", {
vscode.commands.executeCommand(getCommand("handleHumanRelayResponse"), {
requestId: message.requestId,
cancelled: true,
})

View file

@ -7,7 +7,9 @@ The Roo Code extension exposes an API that can be used by other extensions. To u
3. Get access to the API with the following code:
```typescript
const extension = vscode.extensions.getExtension<RooCodeAPI>("rooveterinaryinc.roo-cline")
import { RooCodeAPI, Package } from "path/to/roo-code"
const extension = vscode.extensions.getExtension<RooCodeAPI>(`${Package.publisher}.${Package.name}`)
if (!extension?.isActive) {
throw new Error("Extension is not activated")
@ -35,10 +37,10 @@ await api.pressPrimaryButton()
await api.pressSecondaryButton()
```
**NOTE:** To ensure that the `rooveterinaryinc.roo-cline` extension is activated before your extension, add it to the `extensionDependencies` in your `package.json`:
**NOTE:** To ensure that the `RooVeterinaryInc.roo-cline` extension is activated before your extension, add it to the `extensionDependencies` in your `package.json`:
```json
"extensionDependencies": ["rooveterinaryinc.roo-cline"]
"extensionDependencies": ["RooVeterinaryInc.roo-cline"]
```
For detailed information on the available methods and their usage, refer to the `roo-code.d.ts` file.

View file

@ -3,6 +3,7 @@ import * as vscode from "vscode"
import fs from "fs/promises"
import * as path from "path"
import { Package } from "../schemas"
import { getWorkspacePath } from "../utils/path"
import { ClineProvider } from "../core/webview/ClineProvider"
import { openClineInNewTab } from "../activate/registerCommands"
@ -112,7 +113,7 @@ export class API extends EventEmitter<RooCodeEvents> implements RooCodeAPI {
provider = await openClineInNewTab({ context: this.context, outputChannel: this.outputChannel })
this.registerListeners(provider)
} else {
await vscode.commands.executeCommand("roo-cline.SidebarProvider.focus")
await vscode.commands.executeCommand(`${Package.name}.SidebarProvider.focus`)
provider = this.sidebarProvider
}
@ -122,7 +123,7 @@ export class API extends EventEmitter<RooCodeEvents> implements RooCodeAPI {
if (configuration.allowedCommands) {
await vscode.workspace
.getConfiguration("roo-cline")
.getConfiguration(Package.name)
.update("allowedCommands", configuration.allowedCommands, vscode.ConfigurationTarget.Global)
}
}

View file

@ -43,9 +43,9 @@ export { RooCodeEventName, IpcOrigin, IpcMessageType }
* Constants
*/
import { providerNames } from "../schemas"
import { providerNames, Package } from "../schemas"
export { providerNames }
export { providerNames, Package }
/**
* RooCodeAPI

View file

@ -1540,6 +1540,11 @@ type TaskEvent =
]
}
declare const Package: {
readonly publisher: string
readonly name: string
readonly version: string
}
/**
* ProviderName
*/
@ -1752,6 +1757,7 @@ export {
IpcMessageType,
IpcOrigin,
type IpcServerEvents,
Package,
type ProviderName,
type ProviderSettings,
type ProviderSettingsEntry,

View file

@ -14,6 +14,7 @@ try {
import "./utils/path" // Necessary to have access to String.prototype.toPosix.
import { Package } from "./schemas"
import { ContextProxy } from "./core/config/ContextProxy"
import { ClineProvider } from "./core/webview/ClineProvider"
import { DIFF_VIEW_URI_SCHEME } from "./integrations/editor/DiffViewProvider"
@ -65,7 +66,7 @@ export async function activate(context: vscode.ExtensionContext) {
TerminalRegistry.initialize()
// Get default commands from configuration.
const defaultCommands = vscode.workspace.getConfiguration("roo-cline").get<string[]>("allowedCommands") || []
const defaultCommands = vscode.workspace.getConfiguration(Package.name).get<string[]>("allowedCommands") || []
// Initialize global state if not already set.
if (!context.globalState.get("allowedCommands")) {
@ -123,7 +124,7 @@ export async function activate(context: vscode.ExtensionContext) {
registerTerminalActions(context)
// Allows other extensions to activate once Roo is ready.
vscode.commands.executeCommand("roo-cline.activationCompleted")
vscode.commands.executeCommand(`${Package.name}.activationCompleted`)
// Implements the `RooCodeAPI` interface.
const socketPath = process.env.ROO_CODE_IPC_SOCKET_PATH

View file

@ -3,6 +3,8 @@ import * as path from "path"
import * as fs from "fs/promises"
import { convertTheme } from "monaco-vscode-textmate-theme-converter/lib/cjs"
import { Package } from "../../schemas"
const defaultThemes: Record<string, string> = {
"Default Dark Modern": "dark_modern",
"Dark+": "dark_plus",
@ -141,5 +143,5 @@ export function mergeJson(
}
function getExtensionUri(): vscode.Uri {
return vscode.extensions.getExtension("rooveterinaryinc.roo-cline")!.extensionUri
return vscode.extensions.getExtension(`${Package.publisher}.${Package.name}`)!.extensionUri
}

View file

@ -6,6 +6,61 @@ import { z } from "zod"
import { Equals, Keys, AssertEqual } from "../utils/type-fu"
/**
* Extension
*/
import { publisher, name, version } from "../../package.json"
export const Package = {
publisher,
name,
version,
} as const
/**
* CodeAction
*/
export type CodeActionName = "EXPLAIN" | "FIX" | "IMPROVE" | "ADD_TO_CONTEXT" | "NEW_TASK"
export type CodeActionId = "explainCode" | "fixCode" | "improveCode" | "addToContext" | "newTask"
/**
* TerminalAction
*/
export type TerminalActionName = "ADD_TO_CONTEXT" | "FIX" | "EXPLAIN"
export type TerminalActionPromptType = `TERMINAL_${TerminalActionName}`
export type TerminalActionId = "terminalAddToContext" | "terminalFixCommand" | "terminalExplainCommand"
/**
* Command
*/
const commandIds = [
"activationCompleted",
"plusButtonClicked",
"mcpButtonClicked",
"promptsButtonClicked",
"popoutButtonClicked",
"openInNewTab",
"settingsButtonClicked",
"historyButtonClicked",
"showHumanRelayDialog",
"registerHumanRelayCallback",
"unregisterHumanRelayCallback",
"handleHumanRelayResponse",
"newTask",
"setCustomStoragePath",
"focusInput",
"acceptInput",
] as const
export type CommandId = (typeof commandIds)[number]
/**
* ProviderName
*/

View file

@ -32,7 +32,18 @@ interface SupportPromptConfig {
template: string
}
const supportPromptConfigs: Record<string, SupportPromptConfig> = {
type SupportPromptType =
| "ENHANCE"
| "EXPLAIN"
| "FIX"
| "IMPROVE"
| "ADD_TO_CONTEXT"
| "TERMINAL_ADD_TO_CONTEXT"
| "TERMINAL_FIX"
| "TERMINAL_EXPLAIN"
| "NEW_TASK"
const supportPromptConfigs: Record<SupportPromptType, SupportPromptConfig> = {
ENHANCE: {
template: `Generate an enhanced version of this prompt (reply with only the enhanced prompt - no conversation, explanations, lead-in, bullet points, placeholders, or surrounding quotes):
@ -124,8 +135,6 @@ Please provide:
},
} as const
type SupportPromptType = keyof typeof supportPromptConfigs
export const supportPrompt = {
default: Object.fromEntries(Object.entries(supportPromptConfigs).map(([key, config]) => [key, config.template])),
get: (customSupportPrompts: Record<string, any> | undefined, type: SupportPromptType): string => {

7
src/utils/commands.ts Normal file
View file

@ -0,0 +1,7 @@
import { Package, CommandId, CodeActionId, TerminalActionId } from "../schemas"
export const getCommand = (id: CommandId) => `${Package.name}.${id}`
export const getCodeActionCommand = (id: CodeActionId) => `${Package.name}.${id}`
export const getTerminalCommand = (id: TerminalActionId) => `${Package.name}.${id}`

View file

@ -1,6 +1,8 @@
import * as vscode from "vscode"
import * as path from "path"
import * as fs from "fs/promises"
import { Package } from "../schemas"
import { t } from "../i18n"
/**
@ -14,7 +16,7 @@ export async function getStorageBasePath(defaultPath: string): Promise<string> {
try {
// This is the line causing the error in tests
const config = vscode.workspace.getConfiguration("roo-cline")
const config = vscode.workspace.getConfiguration(Package.name)
customStoragePath = config.get<string>("customStoragePath", "")
} catch (error) {
console.warn("Could not access VSCode configuration - using default path")
@ -88,7 +90,7 @@ export async function promptForCustomStoragePath(): Promise<void> {
let currentPath = ""
try {
const currentConfig = vscode.workspace.getConfiguration("roo-cline")
const currentConfig = vscode.workspace.getConfiguration(Package.name)
currentPath = currentConfig.get<string>("customStoragePath", "")
} catch (error) {
console.error("Could not access configuration")
@ -123,7 +125,7 @@ export async function promptForCustomStoragePath(): Promise<void> {
// If user canceled the operation, result will be undefined
if (result !== undefined) {
try {
const currentConfig = vscode.workspace.getConfiguration("roo-cline")
const currentConfig = vscode.workspace.getConfiguration(Package.name)
await currentConfig.update("customStoragePath", result, vscode.ConfigurationTarget.Global)
if (result) {

View file

@ -2,9 +2,10 @@ import { useState, memo } from "react"
import { Trans } from "react-i18next"
import { VSCodeLink } from "@vscode/webview-ui-toolkit/react"
import { Package } from "@roo/schemas"
import { useAppTranslation } from "@src/i18n/TranslationContext"
import { Dialog, DialogContent, DialogDescription, DialogHeader, DialogTitle } from "@src/components/ui"
import { version } from "../../../../package.json"
interface AnnouncementProps {
hideAnnouncement: () => void
@ -35,8 +36,10 @@ const Announcement = ({ hideAnnouncement }: AnnouncementProps) => {
}}>
<DialogContent className="max-w-96">
<DialogHeader>
<DialogTitle>{t("chat:announcement.title", { version })}</DialogTitle>
<DialogDescription>{t("chat:announcement.description", { version })}</DialogDescription>
<DialogTitle>{t("chat:announcement.title", { version: Package.version })}</DialogTitle>
<DialogDescription>
{t("chat:announcement.description", { version: Package.version })}
</DialogDescription>
</DialogHeader>
<div>
<h3>{t("chat:announcement.whatsNew")}</h3>

View file

@ -1,6 +1,7 @@
import { render, screen } from "@testing-library/react"
import { jest } from "@jest/globals" // Or 'jest' if using Jest
import { version } from "../../../../../package.json"
import { Package } from "@roo/schemas"
import Announcement from "../Announcement"
@ -31,7 +32,7 @@ jest.mock("@src/i18n/TranslationContext", () => ({
describe("Announcement", () => {
const mockHideAnnouncement = jest.fn()
const expectedVersion = version
const expectedVersion = Package.version
it("renders the announcement with the version number from package.json", () => {
render(<Announcement hideAnnouncement={mockHideAnnouncement} />)

View file

@ -1,5 +1,7 @@
import { Package } from "@roo/schemas"
export function getCallbackUrl(provider: string, uriScheme?: string) {
return encodeURIComponent(`${uriScheme || "vscode"}://rooveterinaryinc.roo-cline/${provider}`)
return encodeURIComponent(`${uriScheme || "vscode"}://${Package.publisher}.${Package.name}/${provider}`)
}
export function getGlamaAuthUrl(uriScheme?: string) {