New task slash command

This commit is contained in:
cte 2026-01-07 01:34:27 -08:00
parent b6f571cadf
commit e85362fb11
87 changed files with 1142 additions and 861 deletions

View file

@ -433,24 +433,6 @@ describe("ExtensionHost", () => {
expect(handleMsgUpdatedSpy).toHaveBeenCalled()
})
it("should route action messages to handleActionMessage", () => {
const host = createTestHost()
const handleActionSpy = spyOnPrivate(host, "handleActionMessage")
callPrivate(host, "handleExtensionMessage", { type: "action", action: "test" })
expect(handleActionSpy).toHaveBeenCalled()
})
it("should route invoke messages to handleInvokeMessage", () => {
const host = createTestHost()
const handleInvokeSpy = spyOnPrivate(host, "handleInvokeMessage")
callPrivate(host, "handleExtensionMessage", { type: "invoke", invoke: "test" })
expect(handleInvokeSpy).toHaveBeenCalled()
})
})
describe("handleSayMessage", () => {

View file

@ -0,0 +1,103 @@
import { describe, it, expect } from "vitest"
import {
GLOBAL_COMMANDS,
getGlobalCommand,
getGlobalCommandsForAutocomplete,
type GlobalCommand,
type GlobalCommandAction,
} from "../globalCommands.js"
describe("globalCommands", () => {
describe("GLOBAL_COMMANDS", () => {
it("should contain the /new command", () => {
const newCommand = GLOBAL_COMMANDS.find((cmd) => cmd.name === "new")
expect(newCommand).toBeDefined()
expect(newCommand?.action).toBe("clearTask")
expect(newCommand?.description).toBe("Start a new task")
})
it("should have valid structure for all commands", () => {
for (const cmd of GLOBAL_COMMANDS) {
expect(cmd.name).toBeTruthy()
expect(typeof cmd.name).toBe("string")
expect(cmd.description).toBeTruthy()
expect(typeof cmd.description).toBe("string")
expect(cmd.action).toBeTruthy()
expect(typeof cmd.action).toBe("string")
}
})
})
describe("getGlobalCommand", () => {
it("should return the command when found", () => {
const cmd = getGlobalCommand("new")
expect(cmd).toBeDefined()
expect(cmd?.name).toBe("new")
expect(cmd?.action).toBe("clearTask")
})
it("should return undefined for unknown commands", () => {
const cmd = getGlobalCommand("unknown-command")
expect(cmd).toBeUndefined()
})
it("should be case-sensitive", () => {
const cmd = getGlobalCommand("NEW")
expect(cmd).toBeUndefined()
})
})
describe("getGlobalCommandsForAutocomplete", () => {
it("should return commands in autocomplete format", () => {
const commands = getGlobalCommandsForAutocomplete()
expect(commands.length).toBe(GLOBAL_COMMANDS.length)
for (const cmd of commands) {
expect(cmd.name).toBeTruthy()
expect(cmd.source).toBe("global")
expect(cmd.action).toBeTruthy()
}
})
it("should include the /new command with correct format", () => {
const commands = getGlobalCommandsForAutocomplete()
const newCommand = commands.find((cmd) => cmd.name === "new")
expect(newCommand).toBeDefined()
expect(newCommand?.description).toBe("Start a new task")
expect(newCommand?.source).toBe("global")
expect(newCommand?.action).toBe("clearTask")
})
it("should not include argumentHint for action commands", () => {
const commands = getGlobalCommandsForAutocomplete()
// Action commands don't have argument hints
for (const cmd of commands) {
expect(cmd).not.toHaveProperty("argumentHint")
}
})
})
describe("type safety", () => {
it("should have valid GlobalCommandAction types", () => {
// This test ensures the type is properly constrained
const validActions: GlobalCommandAction[] = ["clearTask"]
for (const cmd of GLOBAL_COMMANDS) {
expect(validActions).toContain(cmd.action)
}
})
it("should match GlobalCommand interface", () => {
const testCommand: GlobalCommand = {
name: "test",
description: "Test command",
action: "clearTask",
}
expect(testCommand.name).toBe("test")
expect(testCommand.description).toBe("Test command")
expect(testCommand.action).toBe("clearTask")
})
})
})

View file

@ -13,10 +13,11 @@ import { createRequire } from "module"
import path from "path"
import { fileURLToPath } from "url"
import fs from "fs"
import os from "os"
import readline from "readline"
import { ProviderName, ReasoningEffortExtended, RooCodeSettings, ExtensionMessage } from "@roo-code/types"
import { createVSCodeAPI, setRuntimeConfigValues } from "@roo-code/vscode-shim"
import { ProviderName, ReasoningEffortExtended, RooCodeSettings } from "@roo-code/types"
// Get the CLI package root directory (for finding node_modules/@vscode/ripgrep)
// When bundled, import.meta.url points to dist/index.js, so go up to package root
@ -53,6 +54,8 @@ interface WebviewViewProvider {
resolveWebviewView?(webviewView: unknown, context: unknown, token: unknown): void | Promise<void>
}
const DEBUG_LOG_PATH = path.join(os.homedir(), ".roo", "cli-debug.log")
export class ExtensionHost extends EventEmitter {
private vscode: ReturnType<typeof createVSCodeAPI> | null = null
private extensionModule: ExtensionModule | null = null
@ -61,7 +64,7 @@ export class ExtensionHost extends EventEmitter {
private options: ExtensionHostOptions
private isWebviewReady = false
private pendingMessages: unknown[] = []
private messageListener: ((message: unknown) => void) | null = null
private messageListener: ((message: ExtensionMessage) => void) | null = null
private originalConsole: {
log: typeof console.log
@ -85,9 +88,6 @@ export class ExtensionHost extends EventEmitter {
// Track streamed content by ts for delta computation
private streamedContent: Map<number, { text: string; headerShown: boolean }> = new Map()
// Track message processing for verbose debug output
private processedMessageCount = 0
// Track if we're currently streaming a message (to manage newlines)
private currentlyStreamingTs: number | null = null
@ -97,28 +97,64 @@ export class ExtensionHost extends EventEmitter {
constructor(options: ExtensionHostOptions) {
super()
this.options = options
// Initialize currentMode from options to track mode changes
this.currentMode = options.mode || null
}
private log(...args: unknown[]): void {
if (this.options.verbose) {
// Use original console if available to avoid quiet mode suppression
const logFn = this.originalConsole?.log || console.log
logFn("[ExtensionHost]", ...args)
/**
* Write debug log entry to ~/.roo/cli-debug.log
* This avoids console output which breaks the TUI.
*/
private log(message: string, data?: unknown): void {
try {
const logDir = path.dirname(DEBUG_LOG_PATH)
if (!fs.existsSync(logDir)) {
fs.mkdirSync(logDir, { recursive: true })
}
const timestamp = new Date().toISOString()
const entry = data
? `[${timestamp}] ${message}: ${JSON.stringify(data, null, 2)}\n`
: `[${timestamp}] ${message}\n`
fs.appendFileSync(DEBUG_LOG_PATH, entry)
} catch {
// NO-OP
}
}
/**
* Suppress Node.js warnings (like MaxListenersExceededWarning)
* This is called regardless of quiet mode to prevent warnings from interrupting output
* Get the shape (keys) of an object for logging
*/
private getMessageShape(msg: unknown): Record<string, string> {
if (!msg || typeof msg !== "object") {
return { _type: typeof msg }
}
const shape: Record<string, string> = {}
for (const [key, value] of Object.entries(msg as Record<string, unknown>)) {
if (value === null) {
shape[key] = "null"
} else if (Array.isArray(value)) {
shape[key] = `array[${value.length}]`
} else if (typeof value === "object") {
shape[key] = `object{${Object.keys(value).join(",")}}`
} else {
shape[key] = typeof value
}
}
return shape
}
private suppressNodeWarnings(): void {
// Suppress process warnings (like MaxListenersExceededWarning)
// Suppress process warnings (like MaxListenersExceededWarning).
this.originalProcessEmitWarning = process.emitWarning
process.emitWarning = () => {}
// Also suppress via the warning event handler
// Also suppress via the warning event handler.
process.on("warning", () => {})
}
@ -169,8 +205,6 @@ export class ExtensionHost extends EventEmitter {
}
async activate(): Promise<void> {
this.log("Activating extension...")
// Suppress Node.js warnings (like MaxListenersExceededWarning) before anything else
this.suppressNodeWarnings()
@ -179,14 +213,13 @@ export class ExtensionHost extends EventEmitter {
// Verify extension path exists
const bundlePath = path.join(this.options.extensionPath, "extension.js")
if (!fs.existsSync(bundlePath)) {
this.restoreConsole()
throw new Error(`Extension bundle not found at: ${bundlePath}`)
}
// 1. Create VSCode API mock
this.log("Creating VSCode API mock...")
this.log("Using appRoot:", CLI_PACKAGE_ROOT)
this.vscode = createVSCodeAPI(
this.options.extensionPath,
this.options.workspacePath,
@ -228,8 +261,6 @@ export class ExtensionHost extends EventEmitter {
require: require,
} as unknown as NodeJS.Module
this.log("Loading extension bundle from:", bundlePath)
// 5. Load extension bundle
try {
this.extensionModule = require(bundlePath) as ExtensionModule
@ -244,12 +275,9 @@ export class ExtensionHost extends EventEmitter {
// 6. Restore module resolution
Module._resolveFilename = originalResolve
this.log("Activating extension...")
// 7. Activate extension
try {
this.extensionAPI = await this.extensionModule.activate(this.vscode.context)
this.log("Extension activated successfully")
} catch (error) {
throw new Error(`Failed to activate extension: ${error instanceof Error ? error.message : String(error)}`)
}
@ -260,18 +288,13 @@ export class ExtensionHost extends EventEmitter {
* This is triggered when the extension registers its sidebar webview provider
*/
registerWebviewProvider(viewId: string, provider: WebviewViewProvider): void {
this.log(`Webview provider registered: ${viewId}`)
this.webviewProviders.set(viewId, provider)
// The WindowAPI will call resolveWebviewView automatically
// We don't need to do anything here
}
/**
* Called when a webview provider is disposed
*/
unregisterWebviewProvider(viewId: string): void {
this.log(`Webview provider unregistered: ${viewId}`)
this.webviewProviders.delete(viewId)
}
@ -288,11 +311,8 @@ export class ExtensionHost extends EventEmitter {
* This indicates the webview is ready to receive messages
*/
markWebviewReady(): void {
this.log("Webview marked as ready")
this.isWebviewReady = true
this.emit("webviewReady")
// Flush any pending messages
this.flushPendingMessages()
}
@ -301,10 +321,10 @@ export class ExtensionHost extends EventEmitter {
*/
private flushPendingMessages(): void {
if (this.pendingMessages.length > 0) {
this.log(`Flushing ${this.pendingMessages.length} pending messages`)
for (const message of this.pendingMessages) {
this.emit("webviewMessage", message)
}
this.pendingMessages = []
}
}
@ -314,12 +334,10 @@ export class ExtensionHost extends EventEmitter {
*/
sendToExtension(message: unknown): void {
if (!this.isWebviewReady) {
this.log("Queueing message (webview not ready):", message)
this.pendingMessages.push(message)
return
}
this.log("Sending message to extension:", message)
this.emit("webviewMessage", message)
}
@ -360,6 +378,7 @@ export class ExtensionHost extends EventEmitter {
xai: "XAI_API_KEY",
groq: "GROQ_API_KEY",
}
const envVar = envVarMap[provider.toLowerCase()] || `${provider.toUpperCase().replace(/-/g, "_")}_API_KEY`
return process.env[envVar]
}
@ -528,15 +547,8 @@ export class ExtensionHost extends EventEmitter {
return config
}
/**
* Run a task with the given prompt
*/
async runTask(prompt: string): Promise<void> {
this.log("Running task:", prompt)
// Wait for webview to be ready
if (!this.isWebviewReady) {
this.log("Waiting for webview to be ready...")
await new Promise<void>((resolve) => {
this.once("webviewReady", resolve)
})
@ -549,8 +561,6 @@ export class ExtensionHost extends EventEmitter {
// In non-interactive mode (-y flag), enable auto-approval for everything
// In interactive mode (default), we'll prompt the user for each action
if (this.options.nonInteractive) {
this.log("Non-interactive mode: enabling auto-approval settings...")
const settings: RooCodeSettings = {
autoApprovalEnabled: true,
alwaysAllowReadOnly: true,
@ -572,8 +582,6 @@ export class ExtensionHost extends EventEmitter {
this.sendToExtension({ type: "updateSettings", updatedSettings: settings })
await new Promise<void>((resolve) => setTimeout(resolve, 100))
} else {
this.log("Interactive mode: user will be prompted for approvals...")
const settings: RooCodeSettings = {
autoApprovalEnabled: false,
}
@ -585,7 +593,6 @@ export class ExtensionHost extends EventEmitter {
// Always send API configuration - it may include API key from environment variables
const apiConfig = this.buildApiConfiguration()
this.log("Sending initial API configuration:", JSON.stringify(apiConfig))
this.sendToExtension({ type: "updateSettings", updatedSettings: apiConfig })
await new Promise<void>((resolve) => setTimeout(resolve, 100))
@ -597,52 +604,64 @@ export class ExtensionHost extends EventEmitter {
* Set up listener for messages from the extension
*/
private setupMessageListener(): void {
this.messageListener = (message: unknown) => {
this.handleExtensionMessage(message)
}
this.messageListener = (message: ExtensionMessage) => this.handleExtensionMessage(message)
this.on("extensionWebviewMessage", this.messageListener)
}
/**
* Handle messages from the extension
*/
private handleExtensionMessage(message: unknown): void {
const msg = message as Record<string, unknown>
private handleExtensionMessage(msg: ExtensionMessage): void {
// Log all incoming messages for debugging
this.log(`[MSG] type=${msg.type}`, this.getMessageShape(msg))
if (this.options.verbose) {
this.log("Received message from extension:", JSON.stringify(msg, null, 2))
// For state messages, log additional details about the state
if (msg.type === "state" && msg.state) {
const state = msg.state
// Extract model ID based on provider (different providers use different fields)
const apiConfig = state.apiConfiguration
let modelId = apiConfig?.apiModelId
if (apiConfig?.apiProvider === "openrouter") {
modelId = apiConfig?.openRouterModelId
} else if (apiConfig?.apiProvider === "openai") {
modelId = apiConfig?.openAiModelId
} else if (apiConfig?.apiProvider === "ollama") {
modelId = apiConfig?.ollamaModelId
}
this.log(`[STATE] mode=${state.mode}, clineMessages=${state.clineMessages?.length || 0}`, {
apiProvider: apiConfig?.apiProvider,
modelId: modelId,
mode: state.mode,
cliProvider: this.options.apiProvider,
cliModel: this.options.model,
})
// Log any ask messages in the state that might indicate resume
if (state.clineMessages) {
for (const clineMsg of state.clineMessages) {
if (clineMsg?.ask === "resume_task" || clineMsg?.ask === "resume_completed_task") {
this.log(`[RESUME DETECTED] ask=${clineMsg.ask}, ts=${clineMsg.ts}`)
}
}
}
}
// Handle different message types
switch (msg.type) {
case "state":
this.handleStateMessage(msg)
break
case "messageUpdated":
// This is the streaming update - handle individual message updates
// This is the streaming update - handle individual message updates.
this.handleMessageUpdated(msg)
break
case "action":
this.handleActionMessage(msg)
break
case "invoke":
this.handleInvokeMessage(msg)
break
case "modes":
// Forward modes list to the TUI
// Forward modes list to the TUI.
this.emit("extensionWebviewMessage", msg)
break
default:
// Log unknown message types in verbose mode
if (this.options.verbose) {
this.log("Unknown message type:", msg.type)
}
// NO-OP
}
}
@ -655,6 +674,7 @@ export class ExtensionHost extends EventEmitter {
if (this.options.disableOutput) {
return
}
const text = args.map((arg) => (typeof arg === "string" ? arg : JSON.stringify(arg))).join(" ")
process.stdout.write(text + "\n")
}
@ -668,65 +688,113 @@ export class ExtensionHost extends EventEmitter {
if (this.options.disableOutput) {
return
}
const text = args.map((arg) => (typeof arg === "string" ? arg : JSON.stringify(arg))).join(" ")
process.stderr.write(text + "\n")
}
/**
* Handle state update messages from the extension
* Get the expected model ID from CLI options
*/
private handleStateMessage(msg: Record<string, unknown>): void {
const state = msg.state as Record<string, unknown> | undefined
if (!state) return
private getExpectedModelId(): string | undefined {
return this.options.model
}
// Detect mode changes and re-apply API configuration
// This preserves the CLI-provided provider/model settings across mode switches
const newMode = state.mode as string | undefined
if (this.options.verbose) {
this.log(`State update: mode=${newMode}, currentMode=${this.currentMode}`)
/**
* Get the current model ID from state's apiConfiguration
*/
private getStateModelId(apiConfig: Record<string, unknown> | undefined): string | undefined {
if (!apiConfig) {
return undefined
}
const provider = apiConfig.apiProvider as string | undefined
switch (provider) {
case "openrouter":
return apiConfig.openRouterModelId as string | undefined
case "openai":
return apiConfig.openAiModelId as string | undefined
case "ollama":
return apiConfig.ollamaModelId as string | undefined
case "litellm":
return apiConfig.litellmModelId as string | undefined
case "lmstudio":
return apiConfig.lmStudioModelId as string | undefined
case "huggingface":
return apiConfig.huggingFaceModelId as string | undefined
case "unbound":
return apiConfig.unboundModelId as string | undefined
case "requesty":
return apiConfig.requestyModelId as string | undefined
case "deepinfra":
return apiConfig.deepInfraModelId as string | undefined
case "vercel-ai-gateway":
return apiConfig.vercelAiGatewayModelId as string | undefined
case "io-intelligence":
return apiConfig.ioIntelligenceModelId as string | undefined
default:
return apiConfig.apiModelId as string | undefined
}
}
/**
* Handle state update messages from the extension.
*/
private handleStateMessage(msg: ExtensionMessage): void {
const state = msg.state
if (!state) {
return
}
// Track current mode for mode switch detection (in tool execution).
const newMode = state.mode
if (newMode && this.currentMode !== null && this.currentMode !== newMode) {
const apiConfig = this.buildApiConfiguration()
this.log(`Mode changed from ${this.currentMode} to ${newMode}, re-applying API configuration...`)
if (this.options.verbose) {
this.log(`API config: ${JSON.stringify(apiConfig)}`)
}
this.sendToExtension({ type: "updateSettings", updatedSettings: apiConfig })
this.log(`[MODE CHANGE] from=${this.currentMode} to=${newMode}, re-applying CLI settings`)
const updatedSettings = this.buildApiConfiguration()
this.sendToExtension({ type: "updateSettings", updatedSettings })
}
if (newMode) {
this.currentMode = newMode
}
const clineMessages = state.clineMessages as Array<Record<string, unknown>> | undefined
// Detect when the model in state differs from CLI-specified model.
// This catches task resume scenarios where the extension loads stored apiConfiguration
// which may differ from CLI-provided settings. We re-apply CLI settings proactively.
const apiConfig = state.apiConfiguration as Record<string, unknown> | undefined
const stateModelId = this.getStateModelId(apiConfig)
const expectedModelId = this.getExpectedModelId()
if (expectedModelId && stateModelId && stateModelId !== expectedModelId) {
this.log(`[MODEL MISMATCH] state has ${stateModelId}, CLI expects ${expectedModelId}, re-applying settings`)
const updatedSettings = this.buildApiConfiguration()
this.sendToExtension({ type: "updateSettings", updatedSettings })
}
const clineMessages = state.clineMessages
if (clineMessages && clineMessages.length > 0) {
// Track message processing for verbose debug output
this.processedMessageCount++
// Verbose: log state update summary
if (this.options.verbose) {
this.log(`State update #${this.processedMessageCount}: ${clineMessages.length} messages`)
}
// Process all messages to find new or updated ones
for (const message of clineMessages) {
if (!message) continue
const ts = message.ts as number | undefined
const isPartial = message.partial as boolean | undefined
const text = message.text as string
const type = message.type as string
const say = message.say as string | undefined
const ask = message.ask as string | undefined
if (!ts) continue
// Handle "say" type messages
if (type === "say" && say) {
this.handleSayMessage(ts, say, text, isPartial)
if (!message) {
continue
}
// Handle "ask" type messages
else if (type === "ask" && ask) {
const ts = message.ts
const isPartial = message.partial
const text = message.text
const type = message.type
const say = message.say
const ask = message.ask
if (!ts) {
continue
}
if (type === "say" && say && typeof text === "string") {
this.handleSayMessage(ts, say, text, isPartial)
} else if (type === "ask" && ask && typeof text === "string") {
this.handleAskMessage(ts, ask, text, isPartial)
}
}
@ -737,25 +805,27 @@ export class ExtensionHost extends EventEmitter {
* Handle messageUpdated - individual streaming updates for a single message
* This is where real-time streaming happens!
*/
private handleMessageUpdated(msg: Record<string, unknown>): void {
const clineMessage = msg.clineMessage as Record<string, unknown> | undefined
if (!clineMessage) return
private handleMessageUpdated(msg: ExtensionMessage): void {
const clineMessage = msg.clineMessage
const ts = clineMessage.ts as number | undefined
const isPartial = clineMessage.partial as boolean | undefined
const text = clineMessage.text as string
const type = clineMessage.type as string
const say = clineMessage.say as string | undefined
const ask = clineMessage.ask as string | undefined
if (!ts) return
// Handle "say" type messages
if (type === "say" && say) {
this.handleSayMessage(ts, say, text, isPartial)
if (!clineMessage) {
return
}
// Handle "ask" type messages
else if (type === "ask" && ask) {
const ts = clineMessage.ts
const isPartial = clineMessage.partial
const text = clineMessage.text
const type = clineMessage.type
const say = clineMessage.say
const ask = clineMessage.ask
if (!ts) {
return
}
if (type === "say" && say && typeof text === "string") {
this.handleSayMessage(ts, say, text, isPartial)
} else if (type === "ask" && ask && typeof text === "string") {
this.handleAskMessage(ts, ask, text, isPartial)
}
}
@ -768,6 +838,7 @@ export class ExtensionHost extends EventEmitter {
if (this.options.disableOutput) {
return
}
process.stdout.write(text)
}
@ -823,6 +894,7 @@ export class ExtensionHost extends EventEmitter {
} else if (!isPartial && text && !alreadyDisplayedComplete) {
// Message complete - ensure all content is output
const streamed = this.streamedContent.get(ts)
if (streamed) {
// We were streaming - output any remaining delta and finish
if (text.length > streamed.text.length && text.startsWith(streamed.text)) {
@ -834,6 +906,7 @@ export class ExtensionHost extends EventEmitter {
// Not streamed yet - output complete message
this.output("\n[assistant]", text)
}
this.displayedMessages.set(ts, { text, partial: false })
this.streamedContent.set(ts, { text, headerShown: true })
}
@ -842,13 +915,13 @@ export class ExtensionHost extends EventEmitter {
case "thinking":
case "reasoning":
// Stream reasoning content in real-time.
this.log(`Received ${say} message: partial=${isPartial}, textLength=${text?.length ?? 0}`)
if (isPartial && text) {
this.streamContent(ts, text, "[reasoning]")
this.displayedMessages.set(ts, { text, partial: true })
} else if (!isPartial && text && !alreadyDisplayedComplete) {
// Reasoning complete - finish the stream.
const streamed = this.streamedContent.get(ts)
if (streamed) {
if (text.length > streamed.text.length && text.startsWith(streamed.text)) {
const delta = text.slice(streamed.text.length)
@ -858,6 +931,7 @@ export class ExtensionHost extends EventEmitter {
} else {
this.output("\n[reasoning]", text)
}
this.displayedMessages.set(ts, { text, partial: false })
}
break
@ -870,11 +944,13 @@ export class ExtensionHost extends EventEmitter {
} else if (!isPartial && text && !alreadyDisplayedComplete) {
// Command output complete - finish the stream.
const streamed = this.streamedContent.get(ts)
if (streamed) {
if (text.length > streamed.text.length && text.startsWith(streamed.text)) {
const delta = text.slice(streamed.text.length)
this.writeStream(delta)
}
this.finishStream(ts)
} else {
this.writeStream("\n[command output] ")
@ -907,7 +983,6 @@ export class ExtensionHost extends EventEmitter {
break
case "tool":
// Tool usage - show when complete
if (text && !alreadyDisplayedComplete) {
this.output("\n[tool]", text)
this.displayedMessages.set(ts, { text, partial: false })
@ -915,21 +990,10 @@ export class ExtensionHost extends EventEmitter {
break
case "api_req_started":
// API request started - log in verbose mode
if (this.options.verbose) {
this.log(`API request started: ts=${ts}`)
}
break
default:
// Other say types - show in verbose mode
if (this.options.verbose) {
this.log(`Unknown say type: ${say}, text length: ${text?.length ?? 0}, partial: ${isPartial}`)
if (text && !alreadyDisplayedComplete) {
this.output(`\n[${say}]`, text || "")
this.displayedMessages.set(ts, { text: text || "", partial: false })
}
}
// NO-OP
}
}
@ -986,7 +1050,7 @@ export class ExtensionHost extends EventEmitter {
case "followup":
if (!alreadyDisplayed) {
// In non-interactive mode, still prompt the user but with a 10s timeout
// that auto-selects the first option if no input is received
// that auto-selects the first option if no input is received.
this.pendingAsks.add(ts)
this.handleFollowupQuestionWithTimeout(ts, text)
this.displayedMessages.set(ts, { text, partial: false })
@ -1000,7 +1064,7 @@ export class ExtensionHost extends EventEmitter {
}
break
// Note: command_output is handled separately in handleCommandOutputAsk
// Note: command_output is handled separately in handleCommandOutputAsk.
case "tool":
if (!alreadyDisplayed && text) {
@ -1008,11 +1072,16 @@ export class ExtensionHost extends EventEmitter {
const toolInfo = JSON.parse(text)
const toolName = toolInfo.tool || "unknown"
this.output(`\n[tool] ${toolName}`)
// Display all tool parameters (excluding 'tool' which is the name)
// Display all tool parameters (excluding 'tool' which is the name).
for (const [key, value] of Object.entries(toolInfo)) {
if (key === "tool") continue
if (key === "tool") {
continue
}
// Format the value - truncate long strings
let displayValue: string
if (typeof value === "string") {
displayValue = value.length > 200 ? value.substring(0, 200) + "..." : value
} else if (typeof value === "object" && value !== null) {
@ -1021,21 +1090,13 @@ export class ExtensionHost extends EventEmitter {
} else {
displayValue = String(value)
}
this.output(` ${key}: ${displayValue}`)
}
// Proactively send API config when switchMode tool is detected
// This helps preserve provider/model settings across mode switches
if (toolName === "switchMode") {
this.log("switchMode tool detected, proactively sending API configuration...")
this.sendToExtension({
type: "updateSettings",
updatedSettings: this.buildApiConfiguration(),
})
this.output(` ${key}: ${displayValue}`)
}
} catch {
this.output("\n[tool]", text)
}
this.displayedMessages.set(ts, { text, partial: false })
}
break
@ -1394,15 +1455,20 @@ export class ExtensionHost extends EventEmitter {
toolInfo = JSON.parse(text) as Record<string, unknown>
toolName = (toolInfo.tool as string) || "unknown"
} catch {
// Use raw text if not JSON
// Use raw text if not JSON.
}
this.output(`\n[Tool Request] ${toolName}`)
// Display all tool parameters (excluding 'tool' which is the name)
for (const [key, value] of Object.entries(toolInfo)) {
if (key === "tool") continue
if (key === "tool") {
continue
}
// Format the value - truncate long strings
let displayValue: string
if (typeof value === "string") {
displayValue = value.length > 200 ? value.substring(0, 200) + "..." : value
} else if (typeof value === "object" && value !== null) {
@ -1411,6 +1477,7 @@ export class ExtensionHost extends EventEmitter {
} else {
displayValue = String(value)
}
this.output(` ${key}: ${displayValue}`)
}
@ -1431,7 +1498,10 @@ export class ExtensionHost extends EventEmitter {
*/
private async handleBrowserApproval(ts: number, text: string): Promise<void> {
this.output("\n[browser action request]")
if (text) this.output(` Action: ${text}`)
if (text) {
this.output(` Action: ${text}`)
}
try {
const approved = await this.promptForYesNo("Allow browser action? (y/n): ")
@ -1440,7 +1510,8 @@ export class ExtensionHost extends EventEmitter {
this.output("[Defaulting to: no]")
this.sendApprovalResponse(false)
}
// Note: Don't delete from pendingAsks - see handleFollowupQuestion comment
// Note: Don't delete from pendingAsks - see handleFollowupQuestion comment.
}
/**
@ -1454,19 +1525,26 @@ export class ExtensionHost extends EventEmitter {
try {
const mcpInfo = JSON.parse(text)
serverName = mcpInfo.server_name || "unknown"
if (mcpInfo.type === "use_mcp_tool") {
toolName = mcpInfo.tool_name || ""
} else if (mcpInfo.type === "access_mcp_resource") {
resourceUri = mcpInfo.uri || ""
}
} catch {
// Use raw text if not JSON
// Use raw text if not JSON.
}
this.output("\n[mcp request]")
this.output(` Server: ${serverName}`)
if (toolName) this.output(` Tool: ${toolName}`)
if (resourceUri) this.output(` Resource: ${resourceUri}`)
if (toolName) {
this.output(` Tool: ${toolName}`)
}
if (resourceUri) {
this.output(` Resource: ${resourceUri}`)
}
try {
const approved = await this.promptForYesNo("Allow MCP access? (y/n): ")
@ -1475,7 +1553,8 @@ export class ExtensionHost extends EventEmitter {
this.output("[Defaulting to: no]")
this.sendApprovalResponse(false)
}
// Note: Don't delete from pendingAsks - see handleFollowupQuestion comment
// Note: Don't delete from pendingAsks - see handleFollowupQuestion comment.
}
/**
@ -1501,7 +1580,10 @@ export class ExtensionHost extends EventEmitter {
private async handleResumeTask(ts: number, ask: string, text: string): Promise<void> {
const isCompleted = ask === "resume_completed_task"
this.output(`\n[Resume ${isCompleted ? "Completed " : ""}Task]`)
if (text) this.output(` ${text}`)
if (text) {
this.output(` ${text}`)
}
try {
const resume = await this.promptForYesNo("Continue with this task? (y/n): ")
@ -1510,7 +1592,8 @@ export class ExtensionHost extends EventEmitter {
this.output("[Defaulting to: no]")
this.sendApprovalResponse(false)
}
// Note: Don't delete from pendingAsks - see handleFollowupQuestion comment
// Note: Don't delete from pendingAsks - see handleFollowupQuestion comment.
}
/**
@ -1518,7 +1601,10 @@ export class ExtensionHost extends EventEmitter {
*/
private async handleGenericApproval(ts: number, ask: string, text: string): Promise<void> {
this.output(`\n[${ask}]`)
if (text) this.output(` ${text}`)
if (text) {
this.output(` ${text}`)
}
try {
const approved = await this.promptForYesNo("Approve? (y/n): ")
@ -1527,7 +1613,8 @@ export class ExtensionHost extends EventEmitter {
this.output("[Defaulting to: no]")
this.sendApprovalResponse(false)
}
// Note: Don't delete from pendingAsks - see handleFollowupQuestion comment
// Note: Don't delete from pendingAsks - see handleFollowupQuestion comment.
}
/**
@ -1546,18 +1633,21 @@ export class ExtensionHost extends EventEmitter {
// Message complete - output any remaining content and send approval
if (text && !alreadyDisplayedComplete) {
const streamed = this.streamedContent.get(ts)
if (streamed) {
// We were streaming - output any remaining delta and finish.
if (text.length > streamed.text.length && text.startsWith(streamed.text)) {
const delta = text.slice(streamed.text.length)
this.writeStream(delta)
}
this.finishStream(ts)
} else {
this.writeStream("\n[command output] ")
this.writeStream(text)
this.writeStream("\n")
}
this.displayedMessages.set(ts, { text, partial: false })
this.streamedContent.set(ts, { text, headerShown: true })
}
@ -1629,11 +1719,7 @@ export class ExtensionHost extends EventEmitter {
* Send a followup response (text answer) to the extension
*/
private sendFollowupResponse(text: string): void {
this.sendToExtension({
type: "askResponse",
askResponse: "messageResponse",
text,
})
this.sendToExtension({ type: "askResponse", askResponse: "messageResponse", text })
}
/**
@ -1646,28 +1732,6 @@ export class ExtensionHost extends EventEmitter {
})
}
/**
* Handle action messages
*/
private handleActionMessage(msg: Record<string, unknown>): void {
const action = msg.action as string
if (this.options.verbose) {
this.log("Action:", action)
}
}
/**
* Handle invoke messages
*/
private handleInvokeMessage(msg: Record<string, unknown>): void {
const invoke = msg.invoke as string
if (this.options.verbose) {
this.log("Invoke:", invoke)
}
}
/**
* Wait for the task to complete
*/
@ -1710,8 +1774,6 @@ export class ExtensionHost extends EventEmitter {
* Clean up resources
*/
async dispose(): Promise<void> {
this.log("Disposing extension host...")
// Clear pending asks
this.pendingAsks.clear()
@ -1731,8 +1793,8 @@ export class ExtensionHost extends EventEmitter {
if (this.extensionModule?.deactivate) {
try {
await this.extensionModule.deactivate()
} catch (error) {
this.log("Error deactivating extension:", error)
} catch (_error) {
// NO-OP
}
}
@ -1748,7 +1810,5 @@ export class ExtensionHost extends EventEmitter {
// Restore console if it was suppressed
this.restoreConsole()
this.log("Extension host disposed")
}
}

View file

@ -0,0 +1,62 @@
/**
* CLI-specific global slash commands
*
* These commands are handled entirely within the CLI and trigger actions
* by sending messages to the extension host. They are separate from the
* extension's built-in commands which expand into prompt content.
*/
/**
* Action types that can be triggered by global commands.
* Each action corresponds to a message type sent to the extension host.
*/
export type GlobalCommandAction = "clearTask"
/**
* Definition of a CLI global command
*/
export interface GlobalCommand {
/** Command name (without the leading /) */
name: string
/** Description shown in the autocomplete picker */
description: string
/** Action to trigger when the command is executed */
action: GlobalCommandAction
}
/**
* CLI-specific global slash commands
* These commands trigger actions rather than expanding into prompt content.
*/
export const GLOBAL_COMMANDS: GlobalCommand[] = [
{
name: "new",
description: "Start a new task",
action: "clearTask",
},
]
/**
* Get a global command by name
*/
export function getGlobalCommand(name: string): GlobalCommand | undefined {
return GLOBAL_COMMANDS.find((cmd) => cmd.name === name)
}
/**
* Get global commands formatted for autocomplete
* Returns commands in the SlashCommandResult format expected by the autocomplete trigger
*/
export function getGlobalCommandsForAutocomplete(): Array<{
name: string
description?: string
source: "global" | "project" | "built-in"
action?: string
}> {
return GLOBAL_COMMANDS.map((cmd) => ({
name: cmd.name,
description: cmd.description,
source: "global" as const,
action: cmd.action,
}))
}

View file

@ -44,6 +44,7 @@ import type {
SlashCommandResult,
ModeResult,
} from "./types.js"
import { getGlobalCommand, getGlobalCommandsForAutocomplete } from "../globalCommands.js"
// Layout constants
const PICKER_HEIGHT = 10 // Max height for picker when open
@ -333,7 +334,13 @@ function AppInner({
})
const slashCommandTrigger = createSlashCommandTrigger({
getCommands: () => allSlashCommandsRef.current.map(toSlashCommandResult),
getCommands: () => {
// Merge CLI global commands with extension commands
const extensionCommands = allSlashCommandsRef.current.map(toSlashCommandResult)
const globalCommands = getGlobalCommandsForAutocomplete().map(toSlashCommandResult)
// Global commands appear first, then extension commands
return [...globalCommands, ...extensionCommands]
},
})
const modeTrigger = createModeTrigger({
@ -815,7 +822,6 @@ function AppInner({
}
}, []) // Run once on mount
// Handle user input submission
const handleSubmit = useCallback(
async (text: string) => {
if (!hostRef.current || !text.trim()) {
@ -828,18 +834,34 @@ function AppInner({
return
}
// Check for CLI global action commands (e.g., /new).
if (trimmedText.startsWith("/")) {
const commandMatch = trimmedText.match(/^\/(\w+)(?:\s|$)/)
if (commandMatch && commandMatch[1]) {
const globalCommand = getGlobalCommand(commandMatch[1])
if (globalCommand?.action === "clearTask") {
// Reset CLI state and send clearTask to extension.
useCLIStore.getState().reset()
// Reset component-level refs to avoid stale message tracking.
seenMessageIds.current.clear()
firstTextMessageSkipped.current = false
hostRef.current.sendToExtension({ type: "clearTask" })
return
}
}
}
if (pendingAsk) {
addMessage({
id: randomUUID(),
role: "user",
content: trimmedText,
})
addMessage({ id: randomUUID(), role: "user", content: trimmedText })
hostRef.current.sendToExtension({
type: "askResponse",
askResponse: "messageResponse",
text: trimmedText,
})
setPendingAsk(null)
setShowCustomInput(false)
isTransitioningToCustomInput.current = false
@ -847,12 +869,7 @@ function AppInner({
} else if (!hasStartedTask) {
setHasStartedTask(true)
setLoading(true)
addMessage({
id: randomUUID(),
role: "user",
content: trimmedText,
})
addMessage({ id: randomUUID(), role: "user", content: trimmedText })
try {
await hostRef.current.runTask(trimmedText)
@ -864,13 +881,9 @@ function AppInner({
if (isComplete) {
setComplete(false)
}
setLoading(true)
addMessage({
id: randomUUID(),
role: "user",
content: trimmedText,
})
setLoading(true)
addMessage({ id: randomUUID(), role: "user", content: trimmedText })
hostRef.current.sendToExtension({
type: "askResponse",
@ -894,24 +907,22 @@ function AppInner({
// Handle approval (Y key)
const handleApprove = useCallback(() => {
if (!hostRef.current) return
if (!hostRef.current) {
return
}
hostRef.current.sendToExtension({
type: "askResponse",
askResponse: "yesButtonClicked",
})
hostRef.current.sendToExtension({ type: "askResponse", askResponse: "yesButtonClicked" })
setPendingAsk(null)
setLoading(true)
}, [setPendingAsk, setLoading])
// Handle rejection (N key)
const handleReject = useCallback(() => {
if (!hostRef.current) return
if (!hostRef.current) {
return
}
hostRef.current.sendToExtension({
type: "askResponse",
askResponse: "noButtonClicked",
})
hostRef.current.sendToExtension({ type: "askResponse", askResponse: "noButtonClicked" })
setPendingAsk(null)
setLoading(true)
}, [setPendingAsk, setLoading])
@ -920,6 +931,7 @@ function AppInner({
useInput((input) => {
if (pendingAsk && pendingAsk.type !== "followup") {
const lower = input.toLowerCase()
if (lower === "y") {
handleApprove()
} else if (lower === "n") {
@ -930,9 +942,7 @@ function AppInner({
// Handle picker state changes from AutocompleteInput
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const handlePickerStateChange = useCallback((state: AutocompletePickerState<any>) => {
setPickerState(state)
}, [])
const handlePickerStateChange = useCallback((state: AutocompletePickerState<any>) => setPickerState(state), [])
// Handle item selection from external PickerSelect
const handlePickerSelect = useCallback(
@ -941,13 +951,12 @@ function AppInner({
// Check if this is a mode selection
if (pickerState.activeTrigger?.id === "mode" && item && typeof item === "object" && "slug" in item) {
const modeItem = item as ModeItem
// Send mode change message to extension
if (hostRef.current) {
hostRef.current.sendToExtension({
type: "switchMode",
mode: modeItem.slug,
})
hostRef.current.sendToExtension({ type: "switchMode", mode: modeItem.slug })
}
// Close the picker
autocompleteRef.current?.closePicker()
followupAutocompleteRef.current?.closePicker()
@ -1379,13 +1388,20 @@ function parseMarkdownChecklist(markdown: string): TodoItem[] {
for (let i = 0; i < lines.length; i++) {
const line = lines[i]
if (!line) continue
if (!line) {
continue
}
const trimmedLine = line.trim()
if (!trimmedLine) continue
if (!trimmedLine) {
continue
}
// Match markdown checkbox patterns
const checkboxMatch = trimmedLine.match(/^\[([x\-\s])\]\s*(.+)$/i)
if (checkboxMatch) {
const statusChar = checkboxMatch[1] ?? " "
const content = checkboxMatch[2] ?? ""
@ -1397,11 +1413,7 @@ function parseMarkdownChecklist(markdown: string): TodoItem[] {
status = "in_progress"
}
todos.push({
id: `todo-${i}`,
content: content.trim(),
status,
})
todos.push({ id: `todo-${i}`, content: content.trim(), status })
}
}

View file

@ -16,6 +16,8 @@ export interface SlashCommandResult extends AutocompleteItem {
argumentHint?: string
/** Source of the command */
source: "global" | "project" | "built-in"
/** Action to trigger for CLI global commands (only present for action commands) */
action?: string
}
/**
@ -92,8 +94,15 @@ export function createSlashCommandTrigger(config: SlashCommandTriggerConfig): Au
},
renderItem: (item: SlashCommandResult, isSelected: boolean) => {
// Source indicator icons
const sourceIcon = item.source === "built-in" ? "⚡" : item.source === "project" ? "📁" : "🌐"
// Source indicator icons:
// ⚙️ for action commands (CLI global), ⚡ built-in, 📁 project, 🌐 global (content)
const sourceIcon = item.action
? "⚙️"
: item.source === "built-in"
? "⚡"
: item.source === "project"
? "📁"
: "🌐"
return (
<Box paddingLeft={2}>

View file

@ -1,5 +1,7 @@
import type { ClineAsk, ClineSay, TodoItem } from "@roo-code/types"
import type { GlobalCommandAction } from "../globalCommands.js"
// Re-export TodoItem for convenience
export type { TodoItem }
@ -86,6 +88,8 @@ export interface SlashCommandResult {
description?: string
argumentHint?: string
source: "global" | "project" | "built-in"
/** Action to trigger for CLI global commands (e.g., clearTask for /new) */
action?: GlobalCommandAction
}
export interface ModeResult {

13
packages/types/src/git.ts Normal file
View file

@ -0,0 +1,13 @@
export interface GitRepositoryInfo {
repositoryUrl?: string
repositoryName?: string
defaultBranch?: string
}
export interface GitCommit {
hash: string
shortHash: string
subject: string
author: string
date: string
}

View file

@ -7,6 +7,7 @@ export * from "./custom-tool.js"
export * from "./events.js"
export * from "./experiment.js"
export * from "./followup.js"
export * from "./git.js"
export * from "./global-settings.js"
export * from "./history.js"
export * from "./image-generation.js"
@ -24,6 +25,7 @@ export * from "./terminal.js"
export * from "./tool.js"
export * from "./tool-params.js"
export * from "./type-fu.js"
export * from "./vscode-extension-host.js"
export * from "./vscode.js"
export * from "./providers/index.js"

View file

@ -86,3 +86,8 @@ export const installMarketplaceItemOptionsSchema = z.object({
})
export type InstallMarketplaceItemOptions = z.infer<typeof installMarketplaceItemOptionsSchema>
export interface MarketplaceInstalledMetadata {
project: Record<string, { type: string }>
global: Record<string, { type: string }>
}

View file

@ -1,8 +1,9 @@
import { z } from "zod"
/**
* MCP Server Use Types
* McpServerUse
*/
export interface McpServerUse {
type: string
serverName: string
@ -39,3 +40,91 @@ export const mcpExecutionStatusSchema = z.discriminatedUnion("status", [
])
export type McpExecutionStatus = z.infer<typeof mcpExecutionStatusSchema>
/**
* McpServer
*/
export type McpServer = {
name: string
config: string
status: "connected" | "connecting" | "disconnected"
error?: string
errorHistory?: McpErrorEntry[]
tools?: McpTool[]
resources?: McpResource[]
resourceTemplates?: McpResourceTemplate[]
disabled?: boolean
timeout?: number
source?: "global" | "project"
projectPath?: string
instructions?: string
}
export type McpTool = {
name: string
description?: string
inputSchema?: object
alwaysAllow?: boolean
enabledForPrompt?: boolean
}
export type McpResource = {
uri: string
name: string
mimeType?: string
description?: string
}
export type McpResourceTemplate = {
uriTemplate: string
name: string
description?: string
mimeType?: string
}
export type McpResourceResponse = {
_meta?: Record<string, any> // eslint-disable-line @typescript-eslint/no-explicit-any
contents: Array<{
uri: string
mimeType?: string
text?: string
blob?: string
}>
}
export type McpToolCallResponse = {
_meta?: Record<string, any> // eslint-disable-line @typescript-eslint/no-explicit-any
content: Array<
| {
type: "text"
text: string
}
| {
type: "image"
data: string
mimeType: string
}
| {
type: "audio"
data: string
mimeType: string
}
| {
type: "resource"
resource: {
uri: string
mimeType?: string
text?: string
blob?: string
}
}
>
isError?: boolean
}
export type McpErrorEntry = {
message: string
timestamp: number
level: "error" | "warn" | "info"
}

View file

@ -1,4 +1,5 @@
import { z } from "zod"
import { DynamicProvider, LocalProvider } from "./provider-settings.js"
/**
* ReasoningEffort
@ -140,3 +141,7 @@ export const modelInfoSchema = z.object({
})
export type ModelInfo = z.infer<typeof modelInfoSchema>
export type ModelRecord = Record<string, ModelInfo>
export type RouterModels = Record<DynamicProvider | LocalProvider, ModelRecord>

View file

@ -0,0 +1,327 @@
import type { GlobalSettings } from "./global-settings.js"
import type { ProviderSettings, ProviderSettingsEntry } from "./provider-settings.js"
import type { HistoryItem } from "./history.js"
import type { ModeConfig } from "./mode.js"
import type { TelemetrySetting } from "./telemetry.js"
import type { Experiments } from "./experiment.js"
import type { ClineMessage, QueuedMessage } from "./message.js"
import type { MarketplaceItem, MarketplaceInstalledMetadata } from "./marketplace.js"
import type { TodoItem } from "./todo.js"
import type { CloudUserInfo, CloudOrganizationMembership, OrganizationAllowList, ShareVisibility } from "./cloud.js"
import type { SerializedCustomToolDefinition } from "./custom-tool.js"
import type { GitCommit } from "./git.js"
import type { McpServer } from "./mcp.js"
import type { ModelRecord, RouterModels } from "./model.js"
// Represents JSON data that is sent from extension to the webview or cli.
export interface ExtensionMessage {
type:
| "action"
| "state"
| "selectedImages"
| "theme"
| "workspaceUpdated"
| "invoke"
| "messageUpdated"
| "mcpServers"
| "enhancedPrompt"
| "commitSearchResults"
| "listApiConfig"
| "routerModels"
| "openAiModels"
| "ollamaModels"
| "lmStudioModels"
| "vsCodeLmModels"
| "huggingFaceModels"
| "vsCodeLmApiAvailable"
| "updatePrompt"
| "systemPrompt"
| "autoApprovalEnabled"
| "updateCustomMode"
| "deleteCustomMode"
| "exportModeResult"
| "importModeResult"
| "checkRulesDirectoryResult"
| "deleteCustomModeCheck"
| "currentCheckpointUpdated"
| "checkpointInitWarning"
| "browserToolEnabled"
| "browserConnectionResult"
| "remoteBrowserEnabled"
| "ttsStart"
| "ttsStop"
| "maxReadFileLine"
| "fileSearchResults"
| "toggleApiConfigPin"
| "acceptInput"
| "setHistoryPreviewCollapsed"
| "commandExecutionStatus"
| "mcpExecutionStatus"
| "vsCodeSetting"
| "authenticatedUser"
| "condenseTaskContextStarted"
| "condenseTaskContextResponse"
| "singleRouterModelFetchResponse"
| "rooCreditBalance"
| "indexingStatusUpdate"
| "indexCleared"
| "codebaseIndexConfig"
| "marketplaceInstallResult"
| "marketplaceRemoveResult"
| "marketplaceData"
| "shareTaskSuccess"
| "codeIndexSettingsSaved"
| "codeIndexSecretStatus"
| "showDeleteMessageDialog"
| "showEditMessageDialog"
| "commands"
| "insertTextIntoTextarea"
| "dismissedUpsells"
| "organizationSwitchResult"
| "interactionRequired"
| "browserSessionUpdate"
| "browserSessionNavigate"
| "claudeCodeRateLimits"
| "customToolsResult"
| "modes"
text?: string
payload?: any // eslint-disable-line @typescript-eslint/no-explicit-any
checkpointWarning?: {
type: "WAIT_TIMEOUT" | "INIT_TIMEOUT"
timeout: number
}
action?:
| "chatButtonClicked"
| "settingsButtonClicked"
| "historyButtonClicked"
| "marketplaceButtonClicked"
| "cloudButtonClicked"
| "didBecomeVisible"
| "focusInput"
| "switchTab"
| "toggleAutoApprove"
invoke?: "newChat" | "sendMessage" | "primaryButtonClick" | "secondaryButtonClick" | "setChatBoxMessage"
state?: ExtensionState
images?: string[]
filePaths?: string[]
openedTabs?: Array<{
label: string
isActive: boolean
path?: string
}>
clineMessage?: ClineMessage
routerModels?: RouterModels
openAiModels?: string[]
ollamaModels?: ModelRecord
lmStudioModels?: ModelRecord
vsCodeLmModels?: { vendor?: string; family?: string; version?: string; id?: string }[]
huggingFaceModels?: Array<{
id: string
object: string
created: number
owned_by: string
providers: Array<{
provider: string
status: "live" | "staging" | "error"
supports_tools?: boolean
supports_structured_output?: boolean
context_length?: number
pricing?: {
input: number
output: number
}
}>
}>
mcpServers?: McpServer[]
commits?: GitCommit[]
listApiConfig?: ProviderSettingsEntry[]
mode?: string
customMode?: ModeConfig
slug?: string
success?: boolean
values?: Record<string, any> // eslint-disable-line @typescript-eslint/no-explicit-any
requestId?: string
promptText?: string
results?:
| { path: string; type: "file" | "folder"; label?: string }[]
| { name: string; description?: string; argumentHint?: string; source: "global" | "project" | "built-in" }[]
error?: string
setting?: string
value?: any // eslint-disable-line @typescript-eslint/no-explicit-any
hasContent?: boolean
items?: MarketplaceItem[]
userInfo?: CloudUserInfo
organizationAllowList?: OrganizationAllowList
tab?: string
marketplaceItems?: MarketplaceItem[]
organizationMcps?: MarketplaceItem[]
marketplaceInstalledMetadata?: MarketplaceInstalledMetadata
errors?: string[]
visibility?: ShareVisibility
rulesFolderPath?: string
settings?: any // eslint-disable-line @typescript-eslint/no-explicit-any
messageTs?: number
hasCheckpoint?: boolean
context?: string
commands?: Command[]
queuedMessages?: QueuedMessage[]
list?: string[] // For dismissedUpsells
organizationId?: string | null // For organizationSwitchResult
browserSessionMessages?: ClineMessage[] // For browser session panel updates
isBrowserSessionActive?: boolean // For browser session panel updates
stepIndex?: number // For browserSessionNavigate: the target step index to display
tools?: SerializedCustomToolDefinition[] // For customToolsResult
modes?: { slug: string; name: string }[] // For modes response
}
export type ExtensionState = Pick<
GlobalSettings,
| "currentApiConfigName"
| "listApiConfigMeta"
| "pinnedApiConfigs"
| "customInstructions"
| "dismissedUpsells"
| "autoApprovalEnabled"
| "alwaysAllowReadOnly"
| "alwaysAllowReadOnlyOutsideWorkspace"
| "alwaysAllowWrite"
| "alwaysAllowWriteOutsideWorkspace"
| "alwaysAllowWriteProtected"
| "alwaysAllowBrowser"
| "alwaysAllowMcp"
| "alwaysAllowModeSwitch"
| "alwaysAllowSubtasks"
| "alwaysAllowFollowupQuestions"
| "alwaysAllowExecute"
| "followupAutoApproveTimeoutMs"
| "allowedCommands"
| "deniedCommands"
| "allowedMaxRequests"
| "allowedMaxCost"
| "browserToolEnabled"
| "browserViewportSize"
| "screenshotQuality"
| "remoteBrowserEnabled"
| "cachedChromeHostUrl"
| "remoteBrowserHost"
| "ttsEnabled"
| "ttsSpeed"
| "soundEnabled"
| "soundVolume"
| "maxConcurrentFileReads"
| "terminalOutputLineLimit"
| "terminalOutputCharacterLimit"
| "terminalShellIntegrationTimeout"
| "terminalShellIntegrationDisabled"
| "terminalCommandDelay"
| "terminalPowershellCounter"
| "terminalZshClearEolMark"
| "terminalZshOhMy"
| "terminalZshP10k"
| "terminalZdotdir"
| "terminalCompressProgressBar"
| "diagnosticsEnabled"
| "diffEnabled"
| "fuzzyMatchThreshold"
| "language"
| "modeApiConfigs"
| "customModePrompts"
| "customSupportPrompts"
| "enhancementApiConfigId"
| "condensingApiConfigId"
| "customCondensingPrompt"
| "codebaseIndexConfig"
| "codebaseIndexModels"
| "profileThresholds"
| "includeDiagnosticMessages"
| "maxDiagnosticMessages"
| "imageGenerationProvider"
| "openRouterImageGenerationSelectedModel"
| "includeTaskHistoryInEnhance"
| "reasoningBlockCollapsed"
| "enterBehavior"
| "includeCurrentTime"
| "includeCurrentCost"
| "maxGitStatusFiles"
| "requestDelaySeconds"
> & {
version: string
clineMessages: ClineMessage[]
currentTaskItem?: HistoryItem
currentTaskTodos?: TodoItem[] // Initial todos for the current task
apiConfiguration: ProviderSettings
uriScheme?: string
shouldShowAnnouncement: boolean
taskHistory: HistoryItem[]
writeDelayMs: number
enableCheckpoints: boolean
checkpointTimeout: number // Timeout for checkpoint initialization in seconds (default: 15)
maxOpenTabsContext: number // Maximum number of VSCode open tabs to include in context (0-500)
maxWorkspaceFiles: number // Maximum number of files to include in current working directory details (0-500)
showRooIgnoredFiles: boolean // Whether to show .rooignore'd files in listings
enableSubfolderRules: boolean // Whether to load rules from subdirectories
maxReadFileLine: number // Maximum number of lines to read from a file before truncating
maxImageFileSize: number // Maximum size of image files to process in MB
maxTotalImageSize: number // Maximum total size for all images in a single read operation in MB
experiments: Experiments // Map of experiment IDs to their enabled state
mcpEnabled: boolean
enableMcpServerCreation: boolean
mode: string
customModes: ModeConfig[]
toolRequirements?: Record<string, boolean> // Map of tool names to their requirements (e.g. {"apply_diff": true} if diffEnabled)
cwd?: string // Current working directory
telemetrySetting: TelemetrySetting
telemetryKey?: string
machineId?: string
renderContext: "sidebar" | "editor"
settingsImportedAt?: number
historyPreviewCollapsed?: boolean
cloudUserInfo: CloudUserInfo | null
cloudIsAuthenticated: boolean
cloudAuthSkipModel?: boolean // Flag indicating auth completed without model selection (user should pick 3rd-party provider)
cloudApiUrl?: string
cloudOrganizations?: CloudOrganizationMembership[]
sharingEnabled: boolean
publicSharingEnabled: boolean
organizationAllowList: OrganizationAllowList
organizationSettingsVersion?: number
isBrowserSessionActive: boolean // Actual browser session state
autoCondenseContext: boolean
autoCondenseContextPercent: number
marketplaceItems?: MarketplaceItem[]
// eslint-disable-next-line @typescript-eslint/no-explicit-any
marketplaceInstalledMetadata?: { project: Record<string, any>; global: Record<string, any> }
profileThresholds: Record<string, number>
hasOpenedModeSelector: boolean
openRouterImageApiKey?: string
messageQueue?: QueuedMessage[]
lastShownAnnouncementId?: string
apiModelId?: string
mcpServers?: McpServer[]
hasSystemPromptOverride?: boolean
mdmCompliant?: boolean
remoteControlEnabled: boolean
taskSyncEnabled: boolean
featureRoomoteControlEnabled: boolean
claudeCodeIsAuthenticated?: boolean
debug?: boolean
}
export interface Command {
name: string
source: "global" | "project" | "built-in"
filePath?: string
description?: string
argumentHint?: string
}

View file

@ -3,14 +3,13 @@ import { z } from "zod"
import {
type ModelInfo,
type ModelRecord,
HUGGINGFACE_API_URL,
HUGGINGFACE_CACHE_DURATION,
HUGGINGFACE_DEFAULT_MAX_TOKENS,
HUGGINGFACE_DEFAULT_CONTEXT_WINDOW,
} from "@roo-code/types"
import type { ModelRecord } from "../../../shared/api"
const huggingFaceProviderSchema = z.object({
provider: z.string(),
status: z.enum(["live", "staging", "error"]),

View file

@ -1,9 +1,7 @@
import axios from "axios"
import { z } from "zod"
import { type ModelInfo, IO_INTELLIGENCE_CACHE_DURATION } from "@roo-code/types"
import type { ModelRecord } from "../../../shared/api"
import { type ModelInfo, type ModelRecord, IO_INTELLIGENCE_CACHE_DURATION } from "@roo-code/types"
const ioIntelligenceModelSchema = z.object({
id: z.string(),

View file

@ -1,6 +1,6 @@
import axios from "axios"
import type { ModelRecord } from "../../../shared/api"
import type { ModelRecord } from "@roo-code/types"
import { DEFAULT_HEADERS } from "../constants"
/**

View file

@ -5,7 +5,7 @@ import * as fsSync from "fs"
import NodeCache from "node-cache"
import { z } from "zod"
import type { ProviderName } from "@roo-code/types"
import type { ProviderName, ModelRecord } from "@roo-code/types"
import { modelInfoSchema, TelemetryEventName } from "@roo-code/types"
import { TelemetryService } from "@roo-code/telemetry"
@ -13,7 +13,7 @@ import { safeWriteJson } from "../../../utils/safeWriteJson"
import { ContextProxy } from "../../../core/config/ContextProxy"
import { getCacheDirectoryPath } from "../../../utils/storage"
import type { RouterName, ModelRecord } from "../../../shared/api"
import type { RouterName } from "../../../shared/api"
import { fileExistsAtPath } from "../../../utils/fs"
import { getOpenRouterModels } from "./openrouter"

View file

@ -2,13 +2,15 @@ import * as path from "path"
import fs from "fs/promises"
import NodeCache from "node-cache"
import { safeWriteJson } from "../../../utils/safeWriteJson"
import sanitize from "sanitize-filename"
import type { ModelRecord } from "@roo-code/types"
import { ContextProxy } from "../../../core/config/ContextProxy"
import { RouterName } from "../../../shared/api"
import { getCacheDirectoryPath } from "../../../utils/storage"
import { RouterName, ModelRecord } from "../../../shared/api"
import { fileExistsAtPath } from "../../../utils/fs"
import { safeWriteJson } from "../../../utils/safeWriteJson"
import { getOpenRouterModelEndpoints } from "./openrouter"
import { getModels } from "./modelCache"

View file

@ -1,6 +1,5 @@
import { RooModelsResponseSchema, type ModelInfo } from "@roo-code/types"
import { RooModelsResponseSchema, type ModelInfo, type ModelRecord } from "@roo-code/types"
import type { ModelRecord } from "../../../shared/api"
import { parseApiPrice } from "../../../shared/cost"
import { DEFAULT_HEADERS } from "../constants"

View file

@ -1,7 +1,9 @@
import OpenAI from "openai"
import { Anthropic } from "@anthropic-ai/sdk"
import type { ApiHandlerOptions, ModelRecord } from "../../shared/api"
import type { ModelRecord } from "@roo-code/types"
import type { ApiHandlerOptions } from "../../shared/api"
import { ApiStream } from "../transform/stream"
import { convertToOpenAiMessages } from "../transform/openai-format"
import type { SingleCompletionHandler, ApiHandlerCreateMessageMetadata } from "../index"

View file

@ -3,18 +3,19 @@ import OpenAI from "openai"
import { z } from "zod"
import {
type ModelRecord,
ApiProviderError,
openRouterDefaultModelId,
openRouterDefaultModelInfo,
OPENROUTER_DEFAULT_PROVIDER_NAME,
OPEN_ROUTER_PROMPT_CACHING_MODELS,
DEEP_SEEK_DEFAULT_TEMPERATURE,
ApiProviderError,
} from "@roo-code/types"
import { TelemetryService } from "@roo-code/telemetry"
import { NativeToolCallParser } from "../../core/assistant-message/NativeToolCallParser"
import type { ApiHandlerOptions, ModelRecord } from "../../shared/api"
import type { ApiHandlerOptions } from "../../shared/api"
import { convertToOpenAiMessages } from "../transform/openai-format"
import { normalizeMistralToolCallId } from "../transform/mistral-format"

View file

@ -3,13 +3,14 @@ import OpenAI from "openai"
import {
type ModelInfo,
type ModelRecord,
requestyDefaultModelId,
requestyDefaultModelInfo,
TOOL_PROTOCOL,
NATIVE_TOOL_DEFAULTS,
} from "@roo-code/types"
import type { ApiHandlerOptions, ModelRecord } from "../../shared/api"
import type { ApiHandlerOptions } from "../../shared/api"
import { resolveToolProtocol } from "../../utils/resolveToolProtocol"
import { calculateApiCostOpenAI } from "../../shared/cost"

View file

@ -2,11 +2,12 @@ import { Anthropic } from "@anthropic-ai/sdk"
import OpenAI from "openai"
import { rooDefaultModelId, getApiProtocol, type ImageGenerationApiMethod } from "@roo-code/types"
import { NativeToolCallParser } from "../../core/assistant-message/NativeToolCallParser"
import { CloudService } from "@roo-code/cloud"
import { NativeToolCallParser } from "../../core/assistant-message/NativeToolCallParser"
import { Package } from "../../shared/package"
import type { ApiHandlerOptions, ModelRecord } from "../../shared/api"
import type { ApiHandlerOptions } from "../../shared/api"
import { ApiStream } from "../transform/stream"
import { getModelParams } from "../transform/model-params"
import { convertToOpenAiMessages } from "../transform/openai-format"

View file

@ -1,8 +1,8 @@
import OpenAI from "openai"
import { type ModelInfo, NATIVE_TOOL_DEFAULTS } from "@roo-code/types"
import { type ModelInfo, type ModelRecord, NATIVE_TOOL_DEFAULTS } from "@roo-code/types"
import { ApiHandlerOptions, RouterName, ModelRecord } from "../../shared/api"
import { ApiHandlerOptions, RouterName } from "../../shared/api"
import { BaseProvider } from "./base-provider"
import { getModels, getModelsFromCache } from "./fetchers/modelCache"

View file

@ -1,6 +1,12 @@
import { type ClineAsk, type McpServerUse, type FollowUpData, isNonBlockingAsk } from "@roo-code/types"
import {
type ClineAsk,
type McpServerUse,
type FollowUpData,
type ExtensionState,
isNonBlockingAsk,
} from "@roo-code/types"
import type { ClineSayTool, ExtensionState } from "../../shared/ExtensionMessage"
import type { ClineSayTool } from "../../shared/ExtensionMessage"
import { ClineAskResponse } from "../../shared/WebviewMessage"
import { isWriteToolAction, isReadOnlyToolAction } from "./tools"

View file

@ -1,6 +1,4 @@
import type { McpServerUse } from "@roo-code/types"
import type { McpServer, McpTool } from "../../shared/mcp"
import type { McpServerUse, McpServer, McpTool } from "@roo-code/types"
export function isMcpToolAlwaysAllowed(mcpServerUse: McpServerUse, mcpServers: McpServer[] | undefined): boolean {
if (mcpServerUse.type === "use_mcp_tool" && mcpServerUse.toolName) {

View file

@ -1,7 +1,10 @@
import type OpenAI from "openai"
import { getMcpServerTools } from "../mcp_server"
import type { McpServer, McpTool } from "@roo-code/types"
import type { McpHub } from "../../../../../services/mcp/McpHub"
import type { McpServer, McpTool } from "../../../../../shared/mcp"
import { getMcpServerTools } from "../mcp_server"
// Helper type to access function tools
type FunctionTool = OpenAI.Chat.ChatCompletionTool & { type: "function" }

View file

@ -1688,6 +1688,16 @@ export class Task extends EventEmitter<TaskEvents> implements TaskLike {
}
private async resumeTaskFromHistory() {
// Reset abort and streaming state to ensure clean continuation.
// This matches the behavior in resumeAfterDelegation() and prevents
// corrupted state from a previous cancellation.
this.abort = false
this.abandoned = false
this.abortReason = undefined
this.didFinishAbortingStream = false
this.isStreaming = false
this.isWaitingForFirstChunk = false
if (this.enableBridge) {
try {
await BridgeOrchestrator.subscribeToTask(this)

View file

@ -34,6 +34,9 @@ import {
type CreateTaskOptions,
type TokenUsage,
type ToolUsage,
type ExtensionMessage,
type ExtensionState,
type MarketplaceInstalledMetadata,
RooCodeEventName,
requestyDefaultModelId,
openRouterDefaultModelId,
@ -51,7 +54,6 @@ import { Package } from "../../shared/package"
import { findLast } from "../../shared/array"
import { supportPrompt } from "../../shared/support-prompt"
import { GlobalFileNames } from "../../shared/globalFileNames"
import type { ExtensionMessage, ExtensionState, MarketplaceInstalledMetadata } from "../../shared/ExtensionMessage"
import { Mode, defaultModeSlug, getModeBySlug } from "../../shared/modes"
import { experimentDefault } from "../../shared/experiments"
import { formatLanguage } from "../../shared/language"

View file

@ -7,12 +7,13 @@ import axios from "axios"
import {
type ProviderSettingsEntry,
type ClineMessage,
type ExtensionMessage,
type ExtensionState,
ORGANIZATION_ALLOW_ALL,
DEFAULT_CHECKPOINT_TIMEOUT_SECONDS,
} from "@roo-code/types"
import { TelemetryService } from "@roo-code/telemetry"
import { ExtensionMessage, ExtensionState } from "../../../shared/ExtensionMessage"
import { defaultModeSlug } from "../../../shared/modes"
import { experimentDefault } from "../../../shared/experiments"
import { setTtsEnabled } from "../../../utils/tts"

View file

@ -10,10 +10,11 @@ vi.mock("../diagnosticsHandler", () => ({
generateErrorDiagnostics: vi.fn().mockResolvedValue({ success: true, filePath: "/tmp/diagnostics.json" }),
}))
import type { ModelRecord } from "@roo-code/types"
import { webviewMessageHandler } from "../webviewMessageHandler"
import type { ClineProvider } from "../ClineProvider"
import { getModels } from "../../../api/providers/fetchers/modelCache"
import type { ModelRecord } from "../../../shared/api"
const mockGetModels = getModels as Mock<typeof getModels>

View file

@ -12,6 +12,7 @@ import {
type ClineMessage,
type TelemetrySetting,
type UserSettingsConfig,
type ModelRecord,
TelemetryEventName,
RooCodeSettings,
ExperimentId,
@ -29,7 +30,7 @@ import { handleCheckpointRestoreOperation } from "./checkpointRestoreHandler"
import { generateErrorDiagnostics } from "./diagnosticsHandler"
import { changeLanguage, t } from "../../i18n"
import { Package } from "../../shared/package"
import { type RouterName, type ModelRecord, toRouterName } from "../../shared/api"
import { type RouterName, toRouterName } from "../../shared/api"
import { MessageEnhancer } from "./messageEnhancer"
import {

View file

@ -1,3 +1,7 @@
import * as fs from "fs/promises"
import * as path from "path"
import * as vscode from "vscode"
import { Client } from "@modelcontextprotocol/sdk/client/index.js"
import { StdioClientTransport, getDefaultEnvironment } from "@modelcontextprotocol/sdk/client/stdio.js"
import { SSEClientTransport } from "@modelcontextprotocol/sdk/client/sse.js"
@ -13,22 +17,23 @@ import {
import chokidar, { FSWatcher } from "chokidar"
import delay from "delay"
import deepEqual from "fast-deep-equal"
import * as fs from "fs/promises"
import * as path from "path"
import * as vscode from "vscode"
import { z } from "zod"
import { t } from "../../i18n"
import { ClineProvider } from "../../core/webview/ClineProvider"
import { GlobalFileNames } from "../../shared/globalFileNames"
import {
import type {
McpResource,
McpResourceResponse,
McpResourceTemplate,
McpServer,
McpTool,
McpToolCallResponse,
} from "../../shared/mcp"
} from "@roo-code/types"
import { t } from "../../i18n"
import { ClineProvider } from "../../core/webview/ClineProvider"
import { GlobalFileNames } from "../../shared/globalFileNames"
import { fileExistsAtPath } from "../../utils/fs"
import { arePathsEqual, getWorkspacePath } from "../../utils/path"
import { injectVariables } from "../../utils/config"

View file

@ -1,44 +1,3 @@
import type {
GlobalSettings,
ProviderSettingsEntry,
ProviderSettings,
HistoryItem,
ModeConfig,
TelemetrySetting,
Experiments,
ClineMessage,
MarketplaceItem,
TodoItem,
CloudUserInfo,
CloudOrganizationMembership,
OrganizationAllowList,
ShareVisibility,
QueuedMessage,
SerializedCustomToolDefinition,
} from "@roo-code/types"
import { GitCommit } from "../utils/git"
import { McpServer } from "./mcp"
import { Mode } from "./modes"
import { ModelRecord, RouterModels } from "./api"
// Command interface for frontend/backend communication
export interface Command {
name: string
source: "global" | "project" | "built-in"
filePath?: string
description?: string
argumentHint?: string
}
// Type for marketplace installed metadata
export interface MarketplaceInstalledMetadata {
project: Record<string, { type: string }>
global: Record<string, { type: string }>
}
// Indexing status types
export interface IndexingStatus {
systemStatus: string
message?: string
@ -60,313 +19,6 @@ export interface LanguageModelChatSelector {
id?: string
}
// Represents JSON data that is sent from extension to webview, called
// ExtensionMessage and has 'type' enum which can be 'plusButtonClicked' or
// 'settingsButtonClicked' or 'hello'. Webview will hold state.
export interface ExtensionMessage {
type:
| "action"
| "state"
| "selectedImages"
| "theme"
| "workspaceUpdated"
| "invoke"
| "messageUpdated"
| "mcpServers"
| "enhancedPrompt"
| "commitSearchResults"
| "listApiConfig"
| "routerModels"
| "openAiModels"
| "ollamaModels"
| "lmStudioModels"
| "vsCodeLmModels"
| "huggingFaceModels"
| "vsCodeLmApiAvailable"
| "updatePrompt"
| "systemPrompt"
| "autoApprovalEnabled"
| "updateCustomMode"
| "deleteCustomMode"
| "exportModeResult"
| "importModeResult"
| "checkRulesDirectoryResult"
| "deleteCustomModeCheck"
| "currentCheckpointUpdated"
| "checkpointInitWarning"
| "browserToolEnabled"
| "browserConnectionResult"
| "remoteBrowserEnabled"
| "ttsStart"
| "ttsStop"
| "maxReadFileLine"
| "fileSearchResults"
| "toggleApiConfigPin"
| "acceptInput"
| "setHistoryPreviewCollapsed"
| "commandExecutionStatus"
| "mcpExecutionStatus"
| "vsCodeSetting"
| "authenticatedUser"
| "condenseTaskContextStarted"
| "condenseTaskContextResponse"
| "singleRouterModelFetchResponse"
| "rooCreditBalance"
| "indexingStatusUpdate"
| "indexCleared"
| "codebaseIndexConfig"
| "marketplaceInstallResult"
| "marketplaceRemoveResult"
| "marketplaceData"
| "shareTaskSuccess"
| "codeIndexSettingsSaved"
| "codeIndexSecretStatus"
| "showDeleteMessageDialog"
| "showEditMessageDialog"
| "commands"
| "insertTextIntoTextarea"
| "dismissedUpsells"
| "organizationSwitchResult"
| "interactionRequired"
| "browserSessionUpdate"
| "browserSessionNavigate"
| "claudeCodeRateLimits"
| "customToolsResult"
| "modes"
text?: string
payload?: any // Add a generic payload for now, can refine later
// Checkpoint warning message
checkpointWarning?: {
type: "WAIT_TIMEOUT" | "INIT_TIMEOUT"
timeout: number
}
action?:
| "chatButtonClicked"
| "settingsButtonClicked"
| "historyButtonClicked"
| "marketplaceButtonClicked"
| "cloudButtonClicked"
| "didBecomeVisible"
| "focusInput"
| "switchTab"
| "toggleAutoApprove"
invoke?: "newChat" | "sendMessage" | "primaryButtonClick" | "secondaryButtonClick" | "setChatBoxMessage"
state?: ExtensionState
images?: string[]
filePaths?: string[]
openedTabs?: Array<{
label: string
isActive: boolean
path?: string
}>
clineMessage?: ClineMessage
routerModels?: RouterModels
openAiModels?: string[]
ollamaModels?: ModelRecord
lmStudioModels?: ModelRecord
vsCodeLmModels?: { vendor?: string; family?: string; version?: string; id?: string }[]
huggingFaceModels?: Array<{
id: string
object: string
created: number
owned_by: string
providers: Array<{
provider: string
status: "live" | "staging" | "error"
supports_tools?: boolean
supports_structured_output?: boolean
context_length?: number
pricing?: {
input: number
output: number
}
}>
}>
mcpServers?: McpServer[]
commits?: GitCommit[]
listApiConfig?: ProviderSettingsEntry[]
mode?: Mode
customMode?: ModeConfig
slug?: string
success?: boolean
values?: Record<string, any>
requestId?: string
promptText?: string
results?:
| { path: string; type: "file" | "folder"; label?: string }[]
| { name: string; description?: string; argumentHint?: string; source: "global" | "project" | "built-in" }[]
error?: string
setting?: string
value?: any
hasContent?: boolean // For checkRulesDirectoryResult
items?: MarketplaceItem[]
userInfo?: CloudUserInfo
organizationAllowList?: OrganizationAllowList
tab?: string
marketplaceItems?: MarketplaceItem[]
organizationMcps?: MarketplaceItem[]
marketplaceInstalledMetadata?: MarketplaceInstalledMetadata
errors?: string[]
visibility?: ShareVisibility
rulesFolderPath?: string
settings?: any
messageTs?: number
hasCheckpoint?: boolean
context?: string
commands?: Command[]
queuedMessages?: QueuedMessage[]
list?: string[] // For dismissedUpsells
organizationId?: string | null // For organizationSwitchResult
browserSessionMessages?: ClineMessage[] // For browser session panel updates
isBrowserSessionActive?: boolean // For browser session panel updates
stepIndex?: number // For browserSessionNavigate: the target step index to display
tools?: SerializedCustomToolDefinition[] // For customToolsResult
modes?: { slug: string; name: string }[] // For modes response
}
export type ExtensionState = Pick<
GlobalSettings,
| "currentApiConfigName"
| "listApiConfigMeta"
| "pinnedApiConfigs"
| "customInstructions"
| "dismissedUpsells"
| "autoApprovalEnabled"
| "alwaysAllowReadOnly"
| "alwaysAllowReadOnlyOutsideWorkspace"
| "alwaysAllowWrite"
| "alwaysAllowWriteOutsideWorkspace"
| "alwaysAllowWriteProtected"
| "alwaysAllowBrowser"
| "alwaysAllowMcp"
| "alwaysAllowModeSwitch"
| "alwaysAllowSubtasks"
| "alwaysAllowFollowupQuestions"
| "alwaysAllowExecute"
| "followupAutoApproveTimeoutMs"
| "allowedCommands"
| "deniedCommands"
| "allowedMaxRequests"
| "allowedMaxCost"
| "browserToolEnabled"
| "browserViewportSize"
| "screenshotQuality"
| "remoteBrowserEnabled"
| "cachedChromeHostUrl"
| "remoteBrowserHost"
| "ttsEnabled"
| "ttsSpeed"
| "soundEnabled"
| "soundVolume"
| "maxConcurrentFileReads"
| "terminalOutputLineLimit"
| "terminalOutputCharacterLimit"
| "terminalShellIntegrationTimeout"
| "terminalShellIntegrationDisabled"
| "terminalCommandDelay"
| "terminalPowershellCounter"
| "terminalZshClearEolMark"
| "terminalZshOhMy"
| "terminalZshP10k"
| "terminalZdotdir"
| "terminalCompressProgressBar"
| "diagnosticsEnabled"
| "diffEnabled"
| "fuzzyMatchThreshold"
| "language"
| "modeApiConfigs"
| "customModePrompts"
| "customSupportPrompts"
| "enhancementApiConfigId"
| "condensingApiConfigId"
| "customCondensingPrompt"
| "codebaseIndexConfig"
| "codebaseIndexModels"
| "profileThresholds"
| "includeDiagnosticMessages"
| "maxDiagnosticMessages"
| "imageGenerationProvider"
| "openRouterImageGenerationSelectedModel"
| "includeTaskHistoryInEnhance"
| "reasoningBlockCollapsed"
| "enterBehavior"
| "includeCurrentTime"
| "includeCurrentCost"
| "maxGitStatusFiles"
| "requestDelaySeconds"
> & {
version: string
clineMessages: ClineMessage[]
currentTaskItem?: HistoryItem
currentTaskTodos?: TodoItem[] // Initial todos for the current task
apiConfiguration: ProviderSettings
uriScheme?: string
shouldShowAnnouncement: boolean
taskHistory: HistoryItem[]
writeDelayMs: number
enableCheckpoints: boolean
checkpointTimeout: number // Timeout for checkpoint initialization in seconds (default: 15)
maxOpenTabsContext: number // Maximum number of VSCode open tabs to include in context (0-500)
maxWorkspaceFiles: number // Maximum number of files to include in current working directory details (0-500)
showRooIgnoredFiles: boolean // Whether to show .rooignore'd files in listings
enableSubfolderRules: boolean // Whether to load rules from subdirectories
maxReadFileLine: number // Maximum number of lines to read from a file before truncating
maxImageFileSize: number // Maximum size of image files to process in MB
maxTotalImageSize: number // Maximum total size for all images in a single read operation in MB
experiments: Experiments // Map of experiment IDs to their enabled state
mcpEnabled: boolean
enableMcpServerCreation: boolean
mode: Mode
customModes: ModeConfig[]
toolRequirements?: Record<string, boolean> // Map of tool names to their requirements (e.g. {"apply_diff": true} if diffEnabled)
cwd?: string // Current working directory
telemetrySetting: TelemetrySetting
telemetryKey?: string
machineId?: string
renderContext: "sidebar" | "editor"
settingsImportedAt?: number
historyPreviewCollapsed?: boolean
cloudUserInfo: CloudUserInfo | null
cloudIsAuthenticated: boolean
cloudAuthSkipModel?: boolean // Flag indicating auth completed without model selection (user should pick 3rd-party provider)
cloudApiUrl?: string
cloudOrganizations?: CloudOrganizationMembership[]
sharingEnabled: boolean
publicSharingEnabled: boolean
organizationAllowList: OrganizationAllowList
organizationSettingsVersion?: number
isBrowserSessionActive: boolean // Actual browser session state
autoCondenseContext: boolean
autoCondenseContextPercent: number
marketplaceItems?: MarketplaceItem[]
marketplaceInstalledMetadata?: { project: Record<string, any>; global: Record<string, any> }
profileThresholds: Record<string, number>
hasOpenedModeSelector: boolean
openRouterImageApiKey?: string
messageQueue?: QueuedMessage[]
lastShownAnnouncementId?: string
apiModelId?: string
mcpServers?: McpServer[]
hasSystemPromptOverride?: boolean
mdmCompliant?: boolean
remoteControlEnabled: boolean
taskSyncEnabled: boolean
featureRoomoteControlEnabled: boolean
claudeCodeIsAuthenticated?: boolean
debug?: boolean
}
export interface ClineSayTool {
tool:
| "editedExistingFile"

View file

@ -39,12 +39,6 @@ export function toRouterName(value?: string): RouterName {
throw new Error(`Invalid router name: ${value}`)
}
// RouterModels
export type ModelRecord = Record<string, ModelInfo>
export type RouterModels = Record<RouterName, ModelRecord>
// Reasoning
export const shouldUseReasoningBudget = ({

View file

@ -1,83 +0,0 @@
export type McpErrorEntry = {
message: string
timestamp: number
level: "error" | "warn" | "info"
}
export type McpServer = {
name: string
config: string
status: "connected" | "connecting" | "disconnected"
error?: string
errorHistory?: McpErrorEntry[]
tools?: McpTool[]
resources?: McpResource[]
resourceTemplates?: McpResourceTemplate[]
disabled?: boolean
timeout?: number
source?: "global" | "project"
projectPath?: string
instructions?: string
}
export type McpTool = {
name: string
description?: string
inputSchema?: object
alwaysAllow?: boolean
enabledForPrompt?: boolean
}
export type McpResource = {
uri: string
name: string
mimeType?: string
description?: string
}
export type McpResourceTemplate = {
uriTemplate: string
name: string
description?: string
mimeType?: string
}
export type McpResourceResponse = {
_meta?: Record<string, any>
contents: Array<{
uri: string
mimeType?: string
text?: string
blob?: string
}>
}
export type McpToolCallResponse = {
_meta?: Record<string, any>
content: Array<
| {
type: "text"
text: string
}
| {
type: "image"
data: string
mimeType: string
}
| {
type: "audio"
data: string
mimeType: string
}
| {
type: "resource"
resource: {
uri: string
mimeType?: string
text?: string
blob?: string
}
}
>
isError?: boolean
}

View file

@ -3,25 +3,15 @@ import * as path from "path"
import { promises as fs } from "fs"
import { exec } from "child_process"
import { promisify } from "util"
import type { GitRepositoryInfo, GitCommit } from "@roo-code/types"
import { truncateOutput } from "../integrations/misc/extract-text"
const execAsync = promisify(exec)
const GIT_OUTPUT_LINE_LIMIT = 500
export interface GitRepositoryInfo {
repositoryUrl?: string
repositoryName?: string
defaultBranch?: string
}
export interface GitCommit {
hash: string
shortHash: string
subject: string
author: string
date: string
}
/**
* Extracts git repository information from the workspace's .git directory
* @param workspaceRoot The root path of the workspace

View file

@ -2,13 +2,13 @@ import React, { useCallback, useEffect, useRef, useState, useMemo } from "react"
import { useEvent } from "react-use"
import { QueryClient, QueryClientProvider } from "@tanstack/react-query"
import { ExtensionMessage } from "@roo/ExtensionMessage"
import { type ExtensionMessage, TelemetryEventName } from "@roo-code/types"
import TranslationProvider from "./i18n/TranslationContext"
import { MarketplaceViewStateManager } from "./components/marketplace/MarketplaceViewStateManager"
import { vscode } from "./utils/vscode"
import { telemetryClient } from "./utils/TelemetryClient"
import { TelemetryEventName } from "@roo-code/types"
import { initializeSourceMaps, exposeSourceMapsForDebugging } from "./utils/sourceMapInitializer"
import { ExtensionStateContextProvider, useExtensionState } from "./context/ExtensionStateContext"
import ChatView, { ChatViewRef } from "./components/chat/ChatView"

View file

@ -1,4 +1,4 @@
import type { Command } from "@roo/ExtensionMessage"
import type { Command } from "@roo-code/types"
import { getContextMenuOptions, ContextMenuOptionType } from "../utils/context-mentions"

View file

@ -1,5 +1,6 @@
import React, { createContext, useContext, useState, useEffect, useCallback } from "react"
import { ExtensionMessage } from "@roo/ExtensionMessage"
import { type ExtensionMessage } from "@roo-code/types"
interface BrowserPanelState {
browserViewportSize: string

View file

@ -1,14 +1,18 @@
import React, { useEffect, useState } from "react"
import { type ClineMessage } from "@roo-code/types"
import BrowserSessionRow from "../chat/BrowserSessionRow"
import { type ClineMessage, type ExtensionMessage } from "@roo-code/types"
import { TooltipProvider } from "@src/components/ui/tooltip"
import ErrorBoundary from "../ErrorBoundary"
import TranslationProvider from "@src/i18n/TranslationContext"
import { ExtensionMessage } from "@roo/ExtensionMessage"
import { BrowserPanelStateProvider, useBrowserPanelState } from "./BrowserPanelStateProvider"
import { vscode } from "@src/utils/vscode"
import { ExtensionStateContextProvider } from "@/context/ExtensionStateContext"
import BrowserSessionRow from "../chat/BrowserSessionRow"
import ErrorBoundary from "../ErrorBoundary"
import { BrowserPanelStateProvider, useBrowserPanelState } from "./BrowserPanelStateProvider"
interface BrowserSessionPanelState {
messages: ClineMessage[]
}

View file

@ -3,10 +3,11 @@ import { useEvent } from "react-use"
import DynamicTextArea from "react-textarea-autosize"
import { VolumeX, Image, WandSparkles, SendHorizontal, MessageSquareX } from "lucide-react"
import type { ExtensionMessage } from "@roo-code/types"
import { mentionRegex, mentionRegexGlobal, commandRegexGlobal, unescapeSpaces } from "@roo/context-mentions"
import { WebviewMessage } from "@roo/WebviewMessage"
import { Mode, getAllModes } from "@roo/modes"
import { ExtensionMessage } from "@roo/ExtensionMessage"
import { vscode } from "@src/utils/vscode"
import { useExtensionState } from "@src/context/ExtensionStateContext"

View file

@ -11,9 +11,9 @@ import { Trans } from "react-i18next"
import { useDebounceEffect } from "@src/utils/useDebounceEffect"
import { appendImages } from "@src/utils/imageUtils"
import type { ClineAsk, ClineMessage } from "@roo-code/types"
import type { ClineAsk, ClineMessage, ExtensionMessage } from "@roo-code/types"
import { ClineSayTool, ExtensionMessage } from "@roo/ExtensionMessage"
import { ClineSayTool } from "@roo/ExtensionMessage"
import { findLast } from "@roo/array"
import { SuggestionItem } from "@roo-code/types"
import { combineApiRequests } from "@roo/combineApiRequests"

View file

@ -3,11 +3,9 @@ import { useEvent } from "react-use"
import { t } from "i18next"
import { ChevronDown, OctagonX } from "lucide-react"
import { CommandExecutionStatus, commandExecutionStatusSchema } from "@roo-code/types"
import { type ExtensionMessage, type CommandExecutionStatus, commandExecutionStatusSchema } from "@roo-code/types"
import { ExtensionMessage } from "@roo/ExtensionMessage"
import { safeJsonParse } from "@roo/safeJsonParse"
import { COMMAND_OUTPUT_STRING } from "@roo/combineCommandSequences"
import { parseCommand } from "@roo/parse-command"

View file

@ -1,9 +1,10 @@
import React, { useEffect, useMemo, useRef, useState } from "react"
import { getIconForFilePath, getIconUrlByName, getIconForDirectoryPath } from "vscode-material-icons"
import { Trans } from "react-i18next"
import { t } from "i18next"
import { Settings } from "lucide-react"
import type { ModeConfig } from "@roo-code/types"
import type { Command } from "@roo/ExtensionMessage"
import type { ModeConfig, Command } from "@roo-code/types"
import {
ContextMenuOptionType,
@ -13,9 +14,8 @@ import {
} from "@src/utils/context-mentions"
import { removeLeadingNonAlphanumeric } from "@src/utils/removeLeadingNonAlphanumeric"
import { vscode } from "@src/utils/vscode"
import { buildDocLink } from "@/utils/docLinks"
import { Trans } from "react-i18next"
import { t } from "i18next"
interface ContextMenuProps {
onSelect: (type: ContextMenuOptionType, value?: string) => void

View file

@ -3,13 +3,16 @@ import { Server, ChevronDown } from "lucide-react"
import { useEvent } from "react-use"
import { useTranslation } from "react-i18next"
import { McpExecutionStatus, mcpExecutionStatusSchema } from "@roo-code/types"
import { ExtensionMessage, ClineAskUseMcpServer } from "../../../../src/shared/ExtensionMessage"
import { safeJsonParse } from "../../../../src/shared/safeJsonParse"
import { type ExtensionMessage, type McpExecutionStatus, mcpExecutionStatusSchema } from "@roo-code/types"
import { cn } from "@src/lib/utils"
import { Button } from "@src/components/ui"
import { ClineAskUseMcpServer } from "../../../../src/shared/ExtensionMessage"
import { safeJsonParse } from "../../../../src/shared/safeJsonParse"
import CodeBlock from "../common/CodeBlock"
import McpToolRow from "../mcp/McpToolRow"
import { Markdown } from "./Markdown"
interface McpExecutionProps {

View file

@ -1,7 +1,7 @@
import React from "react"
import { Edit, Trash2 } from "lucide-react"
import type { Command } from "@roo/ExtensionMessage"
import type { Command } from "@roo-code/types"
import { useAppTranslation } from "@/i18n/TranslationContext"
import { Button, StandardTooltip } from "@/components/ui"

View file

@ -1,6 +1,6 @@
import React from "react"
import type { Command } from "@roo/ExtensionMessage"
import type { Command } from "@roo-code/types"
interface SlashCommandItemSimpleProps {
command: Command

View file

@ -1,6 +1,6 @@
import { render, screen, fireEvent } from "@/utils/test-utils"
import type { Command } from "@roo-code/types"
import type { Command } from "@roo/ExtensionMessage"
import { render, screen, fireEvent } from "@/utils/test-utils"
import { SlashCommandItemSimple } from "../SlashCommandItemSimple"

View file

@ -1,10 +1,12 @@
import { useState, useEffect } from "react"
import { Building2, User, Plus } from "lucide-react"
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue, SelectSeparator } from "@/components/ui/select"
import { type CloudUserInfo, type CloudOrganizationMembership } from "@roo-code/types"
import { type CloudUserInfo, type CloudOrganizationMembership, type ExtensionMessage } from "@roo-code/types"
import { useAppTranslation } from "@src/i18n/TranslationContext"
import { vscode } from "@src/utils/vscode"
import { type ExtensionMessage } from "@roo/ExtensionMessage"
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue, SelectSeparator } from "@/components/ui/select"
type OrganizationSwitcherProps = {
userInfo: CloudUserInfo

View file

@ -11,10 +11,10 @@
* 3. Using minimal state updates to avoid resetting scroll position
*/
import { MarketplaceItem } from "@roo-code/types"
import { MarketplaceItem, MarketplaceInstalledMetadata } from "@roo-code/types"
import { vscode } from "../../utils/vscode"
import { WebviewMessage } from "../../../../src/shared/WebviewMessage"
import type { MarketplaceInstalledMetadata } from "../../../../src/shared/ExtensionMessage"
export interface ViewState {
allItems: MarketplaceItem[]

View file

@ -1,5 +1,6 @@
import { VSCodeCheckbox } from "@vscode/webview-ui-toolkit/react"
import { FormEvent } from "react"
import { VSCodeCheckbox } from "@vscode/webview-ui-toolkit/react"
import { useExtensionState } from "@src/context/ExtensionStateContext"
import { useAppTranslation } from "@src/i18n/TranslationContext"
import { vscode } from "@src/utils/vscode"

View file

@ -1,7 +1,7 @@
import { useMemo } from "react"
import { formatRelative } from "date-fns"
import type { McpErrorEntry } from "@roo/mcp"
import type { McpErrorEntry } from "@roo-code/types"
type McpErrorRowProps = {
error: McpErrorEntry

View file

@ -1,4 +1,4 @@
import { McpResource, McpResourceTemplate } from "@roo/mcp"
import type { McpResource, McpResourceTemplate } from "@roo-code/types"
type McpResourceRowProps = {
item: McpResource | McpResourceTemplate

View file

@ -1,6 +1,6 @@
import { VSCodeCheckbox } from "@vscode/webview-ui-toolkit/react"
import { McpTool } from "@roo/mcp"
import type { McpTool } from "@roo-code/types"
import { useAppTranslation } from "@src/i18n/TranslationContext"
import { vscode } from "@src/utils/vscode"

View file

@ -9,7 +9,7 @@ import {
} from "@vscode/webview-ui-toolkit/react"
import { Webhook } from "lucide-react"
import { McpServer } from "@roo/mcp"
import type { McpServer } from "@roo-code/types"
import { vscode } from "@src/utils/vscode"
import { useExtensionState } from "@src/context/ExtensionStateContext"

View file

@ -2,7 +2,7 @@ import React, { useState, useEffect } from "react"
import { Plus, Globe, Folder, Settings, SquareSlash } from "lucide-react"
import { Trans } from "react-i18next"
import type { Command } from "@roo/ExtensionMessage"
import type { Command } from "@roo-code/types"
import { useAppTranslation } from "@/i18n/TranslationContext"
import { useExtensionState } from "@/context/ExtensionStateContext"

View file

@ -7,7 +7,7 @@ import { Trans } from "react-i18next"
import { buildDocLink } from "@src/utils/docLinks"
import { useEvent, useMount } from "react-use"
import { ExtensionMessage } from "@roo/ExtensionMessage"
import { type ExtensionMessage } from "@roo-code/types"
import { cn } from "@/lib/utils"
import { Slider } from "@/components/ui"

View file

@ -1,7 +1,7 @@
import { render, screen, fireEvent, waitFor } from "@/utils/test-utils"
import { QueryClient, QueryClientProvider } from "@tanstack/react-query"
import type { Command } from "@roo/ExtensionMessage"
import type { Command } from "@roo-code/types"
import { ExtensionStateContextProvider } from "@/context/ExtensionStateContext"
import { vscode } from "@/utils/vscode"

View file

@ -1,14 +1,12 @@
import { useCallback } from "react"
import { VSCodeTextField } from "@vscode/webview-ui-toolkit/react"
import type { ProviderSettings, OrganizationAllowList } from "@roo-code/types"
import type { ProviderSettings, OrganizationAllowList, RouterModels } from "@roo-code/types"
import { chutesDefaultModelId } from "@roo-code/types"
import { useAppTranslation } from "@src/i18n/TranslationContext"
import { VSCodeButtonLink } from "@src/components/common/VSCodeButtonLink"
import type { RouterModels } from "@roo/api"
import { ModelPicker } from "../ModelPicker"
import { inputEventTransform } from "../transforms"

View file

@ -1,8 +1,11 @@
import React from "react"
import { type ProviderSettings, claudeCodeDefaultModelId, claudeCodeModels } from "@roo-code/types"
import { useAppTranslation } from "@src/i18n/TranslationContext"
import { Button } from "@src/components/ui"
import { vscode } from "@src/utils/vscode"
import { ModelPicker } from "../ModelPicker"
import { ClaudeCodeRateLimitDashboard } from "./ClaudeCodeRateLimitDashboard"

View file

@ -1,9 +1,12 @@
import { useCallback, useEffect, useState } from "react"
import { VSCodeTextField } from "@vscode/webview-ui-toolkit/react"
import { OrganizationAllowList, type ProviderSettings, deepInfraDefaultModelId } from "@roo-code/types"
import type { RouterModels } from "@roo/api"
import {
type OrganizationAllowList,
type ProviderSettings,
type RouterModels,
deepInfraDefaultModelId,
} from "@roo-code/types"
import { vscode } from "@src/utils/vscode"
import { useAppTranslation } from "@src/i18n/TranslationContext"

View file

@ -2,9 +2,8 @@ import { useCallback, useState, useEffect, useMemo } from "react"
import { useEvent } from "react-use"
import { VSCodeTextField } from "@vscode/webview-ui-toolkit/react"
import type { ProviderSettings } from "@roo-code/types"
import type { ProviderSettings, ExtensionMessage } from "@roo-code/types"
import { ExtensionMessage } from "@roo/ExtensionMessage"
import { vscode } from "@src/utils/vscode"
import { useAppTranslation } from "@src/i18n/TranslationContext"
import { VSCodeButtonLink } from "@src/components/common/VSCodeButtonLink"

View file

@ -4,15 +4,13 @@ import { Trans } from "react-i18next"
import { Checkbox } from "vscrui"
import { VSCodeLink, VSCodeRadio, VSCodeRadioGroup, VSCodeTextField } from "@vscode/webview-ui-toolkit/react"
import type { ProviderSettings } from "@roo-code/types"
import type { ProviderSettings, ExtensionMessage, ModelRecord } from "@roo-code/types"
import { useAppTranslation } from "@src/i18n/TranslationContext"
import { ExtensionMessage } from "@roo/ExtensionMessage"
import { useRouterModels } from "@src/components/ui/hooks/useRouterModels"
import { vscode } from "@src/utils/vscode"
import { inputEventTransform } from "../transforms"
import { ModelRecord } from "@roo/api"
type LMStudioProps = {
apiConfiguration: ProviderSettings

View file

@ -1,10 +1,14 @@
import { useCallback, useState, useEffect, useRef } from "react"
import { VSCodeTextField, VSCodeCheckbox } from "@vscode/webview-ui-toolkit/react"
import { type ProviderSettings, type OrganizationAllowList, litellmDefaultModelId } from "@roo-code/types"
import {
type ProviderSettings,
type OrganizationAllowList,
type ExtensionMessage,
litellmDefaultModelId,
} from "@roo-code/types"
import { RouterName } from "@roo/api"
import { ExtensionMessage } from "@roo/ExtensionMessage"
import { vscode } from "@src/utils/vscode"
import { useExtensionState } from "@src/context/ExtensionStateContext"

View file

@ -1,9 +1,7 @@
import { useCallback } from "react"
import { VSCodeTextField } from "@vscode/webview-ui-toolkit/react"
import { type ProviderSettings, mistralDefaultModelId } from "@roo-code/types"
import type { RouterModels } from "@roo/api"
import { type ProviderSettings, type RouterModels, mistralDefaultModelId } from "@roo-code/types"
import { useAppTranslation } from "@src/i18n/TranslationContext"
import { VSCodeButtonLink } from "@src/components/common/VSCodeButtonLink"

View file

@ -2,16 +2,13 @@ import { useState, useCallback, useMemo, useEffect } from "react"
import { useEvent } from "react-use"
import { VSCodeTextField, VSCodeRadioGroup, VSCodeRadio } from "@vscode/webview-ui-toolkit/react"
import type { ProviderSettings } from "@roo-code/types"
import { ExtensionMessage } from "@roo/ExtensionMessage"
import type { ProviderSettings, ExtensionMessage, ModelRecord } from "@roo-code/types"
import { useAppTranslation } from "@src/i18n/TranslationContext"
import { useRouterModels } from "@src/components/ui/hooks/useRouterModels"
import { vscode } from "@src/utils/vscode"
import { inputEventTransform } from "../transforms"
import { ModelRecord } from "@roo/api"
type OllamaProps = {
apiConfiguration: ProviderSettings

View file

@ -8,12 +8,11 @@ import {
type ModelInfo,
type ReasoningEffort,
type OrganizationAllowList,
type ExtensionMessage,
azureOpenAiDefaultApiVersion,
openAiModelInfoSaneDefaults,
} from "@roo-code/types"
import { ExtensionMessage } from "@roo/ExtensionMessage"
import { useAppTranslation } from "@src/i18n/TranslationContext"
import { Button, StandardTooltip } from "@src/components/ui"

View file

@ -2,9 +2,12 @@ import { useCallback, useState } from "react"
import { Checkbox } from "vscrui"
import { VSCodeTextField } from "@vscode/webview-ui-toolkit/react"
import { type ProviderSettings, type OrganizationAllowList, openRouterDefaultModelId } from "@roo-code/types"
import type { RouterModels } from "@roo/api"
import {
type ProviderSettings,
type OrganizationAllowList,
type RouterModels,
openRouterDefaultModelId,
} from "@roo-code/types"
import { useAppTranslation } from "@src/i18n/TranslationContext"
import { getOpenRouterAuthUrl } from "@src/oauth/urls"

View file

@ -1,5 +1,6 @@
import React from "react"
import { VSCodeTextField, VSCodeLink } from "@vscode/webview-ui-toolkit/react"
import { type ProviderSettings } from "@roo-code/types"
interface QwenCodeProps {

View file

@ -1,9 +1,12 @@
import { useCallback, useEffect, useState } from "react"
import { VSCodeCheckbox, VSCodeTextField } from "@vscode/webview-ui-toolkit/react"
import { type ProviderSettings, type OrganizationAllowList, requestyDefaultModelId } from "@roo-code/types"
import type { RouterModels } from "@roo/api"
import {
type ProviderSettings,
type OrganizationAllowList,
type RouterModels,
requestyDefaultModelId,
} from "@roo-code/types"
import { vscode } from "@src/utils/vscode"
import { useAppTranslation } from "@src/i18n/TranslationContext"

View file

@ -1,6 +1,9 @@
import { type ProviderSettings, type OrganizationAllowList, rooDefaultModelId } from "@roo-code/types"
import type { RouterModels } from "@roo/api"
import {
type ProviderSettings,
type OrganizationAllowList,
type RouterModels,
rooDefaultModelId,
} from "@roo-code/types"
import { useAppTranslation } from "@src/i18n/TranslationContext"
import { vscode } from "@src/utils/vscode"

View file

@ -2,9 +2,12 @@ import { useCallback, useState, useRef } from "react"
import { VSCodeTextField } from "@vscode/webview-ui-toolkit/react"
import { useQueryClient } from "@tanstack/react-query"
import { type ProviderSettings, type OrganizationAllowList, unboundDefaultModelId } from "@roo-code/types"
import type { RouterModels } from "@roo/api"
import {
type ProviderSettings,
type OrganizationAllowList,
type RouterModels,
unboundDefaultModelId,
} from "@roo-code/types"
import { useAppTranslation } from "@src/i18n/TranslationContext"
import { VSCodeButtonLink } from "@src/components/common/VSCodeButtonLink"

View file

@ -2,9 +2,7 @@ import { useState, useCallback } from "react"
import { useEvent } from "react-use"
import { LanguageModelChatSelector } from "vscode"
import type { ProviderSettings } from "@roo-code/types"
import { ExtensionMessage } from "@roo/ExtensionMessage"
import type { ProviderSettings, ExtensionMessage } from "@roo-code/types"
import { useAppTranslation } from "@src/i18n/TranslationContext"
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@src/components/ui"

View file

@ -1,9 +1,12 @@
import { useCallback } from "react"
import { VSCodeTextField } from "@vscode/webview-ui-toolkit/react"
import { type ProviderSettings, type OrganizationAllowList, vercelAiGatewayDefaultModelId } from "@roo-code/types"
import type { RouterModels } from "@roo/api"
import {
type ProviderSettings,
type OrganizationAllowList,
type RouterModels,
vercelAiGatewayDefaultModelId,
} from "@roo-code/types"
import { useAppTranslation } from "@src/i18n/TranslationContext"
import { VSCodeButtonLink } from "@src/components/common/VSCodeButtonLink"

View file

@ -1,7 +1,7 @@
import { useCallback } from "react"
import { VSCodeTextField, VSCodeDropdown, VSCodeOption } from "@vscode/webview-ui-toolkit/react"
import { zaiApiLineConfigs, zaiApiLineSchema, type ProviderSettings } from "@roo-code/types"
import { type ProviderSettings, zaiApiLineConfigs, zaiApiLineSchema } from "@roo-code/types"
import { useAppTranslation } from "@src/i18n/TranslationContext"
import { VSCodeButtonLink } from "@src/components/common/VSCodeButtonLink"

View file

@ -1,7 +1,6 @@
import { useQuery } from "@tanstack/react-query"
import { ModelRecord } from "@roo/api"
import { ExtensionMessage } from "@roo/ExtensionMessage"
import { type ModelRecord, type ExtensionMessage } from "@roo-code/types"
import { vscode } from "@src/utils/vscode"

View file

@ -1,7 +1,6 @@
import { useQuery } from "@tanstack/react-query"
import { ModelRecord } from "@roo/api"
import { ExtensionMessage } from "@roo/ExtensionMessage"
import { type ModelRecord, type ExtensionMessage } from "@roo-code/types"
import { vscode } from "@src/utils/vscode"

View file

@ -1,5 +1,7 @@
import { useEffect, useState } from "react"
import type { ExtensionMessage } from "@roo/ExtensionMessage"
import type { ExtensionMessage } from "@roo-code/types"
import { vscode } from "@src/utils/vscode"
/**

View file

@ -1,7 +1,6 @@
import { useQuery } from "@tanstack/react-query"
import { RouterModels } from "@roo/api"
import { ExtensionMessage } from "@roo/ExtensionMessage"
import { type RouterModels, type ExtensionMessage } from "@roo-code/types"
import { vscode } from "@src/utils/vscode"

View file

@ -2,6 +2,8 @@ import {
type ProviderName,
type ProviderSettings,
type ModelInfo,
type ModelRecord,
type RouterModels,
anthropicModels,
bedrockModels,
cerebrasModels,
@ -36,8 +38,6 @@ import {
NATIVE_TOOL_DEFAULTS,
} from "@roo-code/types"
import type { ModelRecord, RouterModels } from "@roo/api"
import { useRouterModels } from "./useRouterModels"
import { useOpenRouterModelProviders } from "./useOpenRouterModelProviders"
import { useLmStudioModels } from "./useLmStudioModels"

View file

@ -10,18 +10,22 @@ import {
type TelemetrySetting,
type OrganizationAllowList,
type CloudOrganizationMembership,
type ExtensionMessage,
type ExtensionState,
type MarketplaceInstalledMetadata,
type Command,
type McpServer,
RouterModels,
ORGANIZATION_ALLOW_ALL,
DEFAULT_CHECKPOINT_TIMEOUT_SECONDS,
} from "@roo-code/types"
import { ExtensionMessage, ExtensionState, MarketplaceInstalledMetadata, Command } from "@roo/ExtensionMessage"
import { findLastIndex } from "@roo/array"
import { McpServer } from "@roo/mcp"
import { checkExistKey } from "@roo/checkExistApiConfig"
import { Mode, defaultModeSlug, defaultPrompts } from "@roo/modes"
import { CustomSupportPrompts } from "@roo/support-prompt"
import { experimentDefault } from "@roo/experiments"
import { RouterModels } from "@roo/api"
import { vscode } from "@src/utils/vscode"
import { convertTextMateToHljs } from "@src/utils/textMateToHljs"

View file

@ -1,8 +1,11 @@
import { render, screen, act } from "@/utils/test-utils"
import { ProviderSettings, ExperimentId, DEFAULT_CHECKPOINT_TIMEOUT_SECONDS } from "@roo-code/types"
import { ExtensionState } from "@roo/ExtensionMessage"
import {
type ProviderSettings,
type ExperimentId,
type ExtensionState,
DEFAULT_CHECKPOINT_TIMEOUT_SECONDS,
} from "@roo-code/types"
import { ExtensionStateContextProvider, useExtensionState, mergeExtensionState } from "../ExtensionStateContext"

View file

@ -1,6 +1,4 @@
import type { ProviderSettings, OrganizationAllowList } from "@roo-code/types"
import { RouterModels } from "@roo/api"
import type { ProviderSettings, OrganizationAllowList, RouterModels } from "@roo-code/types"
// Mock i18next to return translation keys with interpolated values
vi.mock("i18next", () => ({

View file

@ -1,7 +1,6 @@
import { Fzf } from "fzf"
import type { ModeConfig } from "@roo-code/types"
import type { Command } from "@roo/ExtensionMessage"
import type { ModeConfig, Command } from "@roo-code/types"
import { mentionRegex } from "@roo/context-mentions"

View file

@ -1,4 +1,4 @@
import { McpResource, McpResourceTemplate } from "@roo/mcp"
import type { McpResource, McpResourceTemplate } from "@roo-code/types"
/**
* Matches a URI against an array of URI templates and returns the matching template

View file

@ -4,6 +4,7 @@ import {
type ProviderSettings,
type OrganizationAllowList,
type ProviderName,
type RouterModels,
modelIdKeysByProvider,
isProviderName,
isDynamicProvider,
@ -11,8 +12,6 @@ import {
isCustomProvider,
} from "@roo-code/types"
import type { RouterModels } from "@roo/api"
export function validateApiConfiguration(
apiConfiguration: ProviderSettings,
routerModels?: RouterModels,