mirror of
https://github.com/RooVetGit/Roo-Code.git
synced 2026-08-28 05:27:24 +00:00
Mode and provider profile selector (#7545)
This commit is contained in:
parent
0126507e9b
commit
47855ed69f
10 changed files with 442 additions and 363 deletions
|
|
@ -83,7 +83,7 @@ export abstract class BaseChannel<TCommand = unknown, TEventName extends string
|
|||
/**
|
||||
* Handle incoming commands - must be implemented by subclasses.
|
||||
*/
|
||||
public abstract handleCommand(command: TCommand): void
|
||||
public abstract handleCommand(command: TCommand): Promise<void>
|
||||
|
||||
/**
|
||||
* Handle connection-specific logic.
|
||||
|
|
|
|||
|
|
@ -53,10 +53,7 @@ export class ExtensionChannel extends BaseChannel<
|
|||
this.setupListeners()
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle extension-specific commands from the web app
|
||||
*/
|
||||
public handleCommand(command: ExtensionBridgeCommand): void {
|
||||
public async handleCommand(command: ExtensionBridgeCommand): Promise<void> {
|
||||
if (command.instanceId !== this.instanceId) {
|
||||
console.log(`[ExtensionChannel] command -> instance id mismatch | ${this.instanceId}`, {
|
||||
messageInstanceId: command.instanceId,
|
||||
|
|
@ -69,13 +66,22 @@ export class ExtensionChannel extends BaseChannel<
|
|||
console.log(`[ExtensionChannel] command -> createTask() | ${command.instanceId}`, {
|
||||
text: command.payload.text?.substring(0, 100) + "...",
|
||||
hasImages: !!command.payload.images,
|
||||
mode: command.payload.mode,
|
||||
providerProfile: command.payload.providerProfile,
|
||||
})
|
||||
|
||||
this.provider.createTask(command.payload.text, command.payload.images)
|
||||
this.provider.createTask(
|
||||
command.payload.text,
|
||||
command.payload.images,
|
||||
undefined, // parentTask
|
||||
undefined, // options
|
||||
{ mode: command.payload.mode, currentApiConfigName: command.payload.providerProfile },
|
||||
)
|
||||
|
||||
break
|
||||
}
|
||||
case ExtensionBridgeCommandName.StopTask: {
|
||||
const instance = this.updateInstance()
|
||||
const instance = await this.updateInstance()
|
||||
|
||||
if (instance.task.taskStatus === TaskStatus.Running) {
|
||||
console.log(`[ExtensionChannel] command -> cancelTask() | ${command.instanceId}`)
|
||||
|
|
@ -86,6 +92,7 @@ export class ExtensionChannel extends BaseChannel<
|
|||
this.provider.clearTask()
|
||||
this.provider.postStateToWebview()
|
||||
}
|
||||
|
||||
break
|
||||
}
|
||||
case ExtensionBridgeCommandName.ResumeTask: {
|
||||
|
|
@ -93,7 +100,6 @@ export class ExtensionChannel extends BaseChannel<
|
|||
taskId: command.payload.taskId,
|
||||
})
|
||||
|
||||
// Resume the task from history by taskId
|
||||
this.provider.resumeTask(command.payload.taskId)
|
||||
this.provider.postStateToWebview()
|
||||
break
|
||||
|
|
@ -122,12 +128,12 @@ export class ExtensionChannel extends BaseChannel<
|
|||
}
|
||||
|
||||
private async registerInstance(_socket: Socket): Promise<void> {
|
||||
const instance = this.updateInstance()
|
||||
const instance = await this.updateInstance()
|
||||
await this.publish(ExtensionSocketEvents.REGISTER, instance)
|
||||
}
|
||||
|
||||
private async unregisterInstance(_socket: Socket): Promise<void> {
|
||||
const instance = this.updateInstance()
|
||||
const instance = await this.updateInstance()
|
||||
await this.publish(ExtensionSocketEvents.UNREGISTER, instance)
|
||||
}
|
||||
|
||||
|
|
@ -135,7 +141,7 @@ export class ExtensionChannel extends BaseChannel<
|
|||
this.stopHeartbeat()
|
||||
|
||||
this.heartbeatInterval = setInterval(async () => {
|
||||
const instance = this.updateInstance()
|
||||
const instance = await this.updateInstance()
|
||||
|
||||
try {
|
||||
socket.emit(ExtensionSocketEvents.HEARTBEAT, instance)
|
||||
|
|
@ -172,11 +178,11 @@ export class ExtensionChannel extends BaseChannel<
|
|||
] as const
|
||||
|
||||
eventMapping.forEach(({ from, to }) => {
|
||||
// Create and store the listener function for cleanup/
|
||||
const listener = (..._args: unknown[]) => {
|
||||
// Create and store the listener function for cleanup.
|
||||
const listener = async (..._args: unknown[]) => {
|
||||
this.publish(ExtensionSocketEvents.EVENT, {
|
||||
type: to,
|
||||
instance: this.updateInstance(),
|
||||
instance: await this.updateInstance(),
|
||||
timestamp: Date.now(),
|
||||
})
|
||||
}
|
||||
|
|
@ -195,10 +201,16 @@ export class ExtensionChannel extends BaseChannel<
|
|||
this.eventListeners.clear()
|
||||
}
|
||||
|
||||
private updateInstance(): ExtensionInstance {
|
||||
private async updateInstance(): Promise<ExtensionInstance> {
|
||||
const task = this.provider?.getCurrentTask()
|
||||
const taskHistory = this.provider?.getRecentTasks() ?? []
|
||||
|
||||
const mode = await this.provider?.getMode()
|
||||
const modes = (await this.provider?.getModes()) ?? []
|
||||
|
||||
const providerProfile = await this.provider?.getProviderProfile()
|
||||
const providerProfiles = (await this.provider?.getProviderProfiles()) ?? []
|
||||
|
||||
this.extensionInstance = {
|
||||
...this.extensionInstance,
|
||||
appProperties: this.extensionInstance.appProperties ?? this.provider.appProperties,
|
||||
|
|
@ -213,6 +225,10 @@ export class ExtensionChannel extends BaseChannel<
|
|||
: { taskId: "", taskStatus: TaskStatus.None },
|
||||
taskAsk: task?.taskAsk,
|
||||
taskHistory,
|
||||
mode,
|
||||
providerProfile,
|
||||
modes,
|
||||
providerProfiles,
|
||||
}
|
||||
|
||||
return this.extensionInstance
|
||||
|
|
|
|||
|
|
@ -73,7 +73,7 @@ export class TaskChannel extends BaseChannel<
|
|||
super(instanceId)
|
||||
}
|
||||
|
||||
public handleCommand(command: TaskBridgeCommand): void {
|
||||
public async handleCommand(command: TaskBridgeCommand): Promise<void> {
|
||||
const task = this.subscribedTasks.get(command.taskId)
|
||||
|
||||
if (!task) {
|
||||
|
|
@ -87,7 +87,14 @@ export class TaskChannel extends BaseChannel<
|
|||
`[TaskChannel] ${TaskBridgeCommandName.Message} ${command.taskId} -> submitUserMessage()`,
|
||||
command,
|
||||
)
|
||||
task.submitUserMessage(command.payload.text, command.payload.images)
|
||||
|
||||
await task.submitUserMessage(
|
||||
command.payload.text,
|
||||
command.payload.images,
|
||||
command.payload.mode,
|
||||
command.payload.providerProfile,
|
||||
)
|
||||
|
||||
break
|
||||
|
||||
case TaskBridgeCommandName.ApproveAsk:
|
||||
|
|
@ -95,6 +102,7 @@ export class TaskChannel extends BaseChannel<
|
|||
`[TaskChannel] ${TaskBridgeCommandName.ApproveAsk} ${command.taskId} -> approveAsk()`,
|
||||
command,
|
||||
)
|
||||
|
||||
task.approveAsk(command.payload)
|
||||
break
|
||||
|
||||
|
|
|
|||
|
|
@ -53,6 +53,13 @@ describe("ExtensionChannel", () => {
|
|||
postStateToWebview: vi.fn(),
|
||||
postMessageToWebview: vi.fn(),
|
||||
getTelemetryProperties: vi.fn(),
|
||||
getMode: vi.fn().mockResolvedValue("code"),
|
||||
getModes: vi.fn().mockResolvedValue([
|
||||
{ slug: "code", name: "Code", description: "Code mode" },
|
||||
{ slug: "architect", name: "Architect", description: "Architect mode" },
|
||||
]),
|
||||
getProviderProfile: vi.fn().mockResolvedValue("default"),
|
||||
getProviderProfiles: vi.fn().mockResolvedValue([{ name: "default", description: "Default profile" }]),
|
||||
on: vi.fn((event: keyof TaskProviderEvents, listener: (...args: unknown[]) => unknown) => {
|
||||
if (!eventListeners.has(event)) {
|
||||
eventListeners.set(event, new Set())
|
||||
|
|
@ -184,6 +191,9 @@ describe("ExtensionChannel", () => {
|
|||
// Connect the socket to enable publishing
|
||||
await extensionChannel.onConnect(mockSocket)
|
||||
|
||||
// Clear the mock calls from the connection (which emits a register event)
|
||||
;(mockSocket.emit as any).mockClear()
|
||||
|
||||
// Get a listener that was registered for TaskStarted
|
||||
const taskStartedListeners = eventListeners.get(RooCodeEventName.TaskStarted)
|
||||
expect(taskStartedListeners).toBeDefined()
|
||||
|
|
@ -192,7 +202,7 @@ describe("ExtensionChannel", () => {
|
|||
// Trigger the listener
|
||||
const listener = Array.from(taskStartedListeners!)[0]
|
||||
if (listener) {
|
||||
listener("test-task-id")
|
||||
await listener("test-task-id")
|
||||
}
|
||||
|
||||
// Verify the event was published to the socket
|
||||
|
|
|
|||
|
|
@ -333,7 +333,12 @@ describe("TaskChannel", () => {
|
|||
|
||||
taskChannel.handleCommand(command)
|
||||
|
||||
expect(mockTask.submitUserMessage).toHaveBeenCalledWith(command.payload.text, command.payload.images)
|
||||
expect(mockTask.submitUserMessage).toHaveBeenCalledWith(
|
||||
command.payload.text,
|
||||
command.payload.images,
|
||||
undefined,
|
||||
undefined,
|
||||
)
|
||||
})
|
||||
|
||||
it("should handle ApproveAsk command", () => {
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "@roo-code/types",
|
||||
"version": "1.65.0",
|
||||
"version": "1.66.0",
|
||||
"description": "TypeScript type definitions for Roo Code.",
|
||||
"publishConfig": {
|
||||
"access": "public",
|
||||
|
|
|
|||
|
|
@ -378,6 +378,10 @@ export const extensionInstanceSchema = z.object({
|
|||
task: extensionTaskSchema,
|
||||
taskAsk: clineMessageSchema.optional(),
|
||||
taskHistory: z.array(z.string()),
|
||||
mode: z.string().optional(),
|
||||
modes: z.array(z.object({ slug: z.string(), name: z.string() })).optional(),
|
||||
providerProfile: z.string().optional(),
|
||||
providerProfiles: z.array(z.object({ name: z.string(), provider: z.string().optional() })).optional(),
|
||||
})
|
||||
|
||||
export type ExtensionInstance = z.infer<typeof extensionInstanceSchema>
|
||||
|
|
@ -398,6 +402,9 @@ export enum ExtensionBridgeEventName {
|
|||
TaskResumable = RooCodeEventName.TaskResumable,
|
||||
TaskIdle = RooCodeEventName.TaskIdle,
|
||||
|
||||
ModeChanged = RooCodeEventName.ModeChanged,
|
||||
ProviderProfileChanged = RooCodeEventName.ProviderProfileChanged,
|
||||
|
||||
InstanceRegistered = "instance_registered",
|
||||
InstanceUnregistered = "instance_unregistered",
|
||||
HeartbeatUpdated = "heartbeat_updated",
|
||||
|
|
@ -469,6 +476,18 @@ export const extensionBridgeEventSchema = z.discriminatedUnion("type", [
|
|||
instance: extensionInstanceSchema,
|
||||
timestamp: z.number(),
|
||||
}),
|
||||
z.object({
|
||||
type: z.literal(ExtensionBridgeEventName.ModeChanged),
|
||||
instance: extensionInstanceSchema,
|
||||
mode: z.string(),
|
||||
timestamp: z.number(),
|
||||
}),
|
||||
z.object({
|
||||
type: z.literal(ExtensionBridgeEventName.ProviderProfileChanged),
|
||||
instance: extensionInstanceSchema,
|
||||
providerProfile: z.object({ name: z.string(), provider: z.string().optional() }),
|
||||
timestamp: z.number(),
|
||||
}),
|
||||
])
|
||||
|
||||
export type ExtensionBridgeEvent = z.infer<typeof extensionBridgeEventSchema>
|
||||
|
|
@ -490,6 +509,8 @@ export const extensionBridgeCommandSchema = z.discriminatedUnion("type", [
|
|||
payload: z.object({
|
||||
text: z.string(),
|
||||
images: z.array(z.string()).optional(),
|
||||
mode: z.string().optional(),
|
||||
providerProfile: z.string().optional(),
|
||||
}),
|
||||
timestamp: z.number(),
|
||||
}),
|
||||
|
|
@ -502,9 +523,7 @@ export const extensionBridgeCommandSchema = z.discriminatedUnion("type", [
|
|||
z.object({
|
||||
type: z.literal(ExtensionBridgeCommandName.ResumeTask),
|
||||
instanceId: z.string(),
|
||||
payload: z.object({
|
||||
taskId: z.string(),
|
||||
}),
|
||||
payload: z.object({ taskId: z.string() }),
|
||||
timestamp: z.number(),
|
||||
}),
|
||||
])
|
||||
|
|
@ -558,6 +577,8 @@ export const taskBridgeCommandSchema = z.discriminatedUnion("type", [
|
|||
payload: z.object({
|
||||
text: z.string(),
|
||||
images: z.array(z.string()).optional(),
|
||||
mode: z.string().optional(),
|
||||
providerProfile: z.string().optional(),
|
||||
}),
|
||||
timestamp: z.number(),
|
||||
}),
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@ import { z } from "zod"
|
|||
|
||||
import { RooCodeEventName } from "./events.js"
|
||||
import type { RooCodeSettings } from "./global-settings.js"
|
||||
import type { ClineMessage, QueuedMessage, TokenUsage } from "./message.js"
|
||||
import type { ClineMessage, TokenUsage } from "./message.js"
|
||||
import type { ToolUsage, ToolName } from "./tool.js"
|
||||
import type { StaticAppProperties, GitProperties, TelemetryProperties } from "./telemetry.js"
|
||||
import type { TodoItem } from "./todo.js"
|
||||
|
|
@ -59,6 +59,8 @@ export interface TaskProviderLike {
|
|||
|
||||
export type TaskProviderEvents = {
|
||||
[RooCodeEventName.TaskCreated]: [task: TaskLike]
|
||||
|
||||
// Proxied from the Task EventEmitter.
|
||||
[RooCodeEventName.TaskStarted]: [taskId: string]
|
||||
[RooCodeEventName.TaskCompleted]: [taskId: string, tokenUsage: TokenUsage, toolUsage: ToolUsage]
|
||||
[RooCodeEventName.TaskAborted]: [taskId: string]
|
||||
|
|
@ -68,21 +70,14 @@ export type TaskProviderEvents = {
|
|||
[RooCodeEventName.TaskInteractive]: [taskId: string]
|
||||
[RooCodeEventName.TaskResumable]: [taskId: string]
|
||||
[RooCodeEventName.TaskIdle]: [taskId: string]
|
||||
[RooCodeEventName.TaskPaused]: [taskId: string]
|
||||
[RooCodeEventName.TaskUnpaused]: [taskId: string]
|
||||
[RooCodeEventName.TaskSpawned]: [taskId: string]
|
||||
|
||||
[RooCodeEventName.TaskUserMessage]: [taskId: string]
|
||||
|
||||
[RooCodeEventName.TaskTokenUsageUpdated]: [taskId: string, tokenUsage: TokenUsage]
|
||||
|
||||
[RooCodeEventName.ModeChanged]: [mode: string]
|
||||
[RooCodeEventName.ProviderProfileChanged]: [config: { name: string; provider?: string }]
|
||||
}
|
||||
|
||||
/**
|
||||
* TaskLike
|
||||
*/
|
||||
* TaskLike
|
||||
*/
|
||||
|
||||
export interface CreateTaskOptions {
|
||||
enableDiff?: boolean
|
||||
|
|
@ -110,14 +105,11 @@ export type TaskMetadata = z.infer<typeof taskMetadataSchema>
|
|||
|
||||
export interface TaskLike {
|
||||
readonly taskId: string
|
||||
readonly rootTaskId?: string
|
||||
readonly parentTaskId?: string
|
||||
readonly childTaskId?: string
|
||||
readonly metadata: TaskMetadata
|
||||
readonly taskStatus: TaskStatus
|
||||
readonly taskAsk: ClineMessage | undefined
|
||||
readonly queuedMessages: QueuedMessage[]
|
||||
readonly tokenUsage: TokenUsage | undefined
|
||||
readonly metadata: TaskMetadata
|
||||
|
||||
readonly rootTask?: TaskLike
|
||||
|
||||
on<K extends keyof TaskEvents>(event: K, listener: (...args: TaskEvents[K]) => void | Promise<void>): this
|
||||
off<K extends keyof TaskEvents>(event: K, listener: (...args: TaskEvents[K]) => void | Promise<void>): this
|
||||
|
|
@ -141,15 +133,14 @@ export type TaskEvents = {
|
|||
[RooCodeEventName.TaskIdle]: [taskId: string]
|
||||
|
||||
// Subtask Lifecycle
|
||||
[RooCodeEventName.TaskPaused]: [taskId: string]
|
||||
[RooCodeEventName.TaskUnpaused]: [taskId: string]
|
||||
[RooCodeEventName.TaskPaused]: []
|
||||
[RooCodeEventName.TaskUnpaused]: []
|
||||
[RooCodeEventName.TaskSpawned]: [taskId: string]
|
||||
|
||||
// Task Execution
|
||||
[RooCodeEventName.Message]: [{ action: "created" | "updated"; message: ClineMessage }]
|
||||
[RooCodeEventName.TaskModeSwitched]: [taskId: string, mode: string]
|
||||
[RooCodeEventName.TaskAskResponded]: []
|
||||
[RooCodeEventName.TaskUserMessage]: [taskId: string]
|
||||
|
||||
// Task Analytics
|
||||
[RooCodeEventName.TaskToolFailed]: [taskId: string, tool: ToolName, error: string]
|
||||
|
|
|
|||
|
|
@ -10,6 +10,7 @@ import pWaitFor from "p-wait-for"
|
|||
import { serializeError } from "serialize-error"
|
||||
|
||||
import {
|
||||
type RooCodeSettings,
|
||||
type TaskLike,
|
||||
type TaskMetadata,
|
||||
type TaskEvents,
|
||||
|
|
@ -23,6 +24,7 @@ import {
|
|||
type ClineAsk,
|
||||
type ToolProgressStatus,
|
||||
type HistoryItem,
|
||||
type CreateTaskOptions,
|
||||
RooCodeEventName,
|
||||
TelemetryEventName,
|
||||
TaskStatus,
|
||||
|
|
@ -111,7 +113,7 @@ const DEFAULT_USAGE_COLLECTION_TIMEOUT_MS = 5000 // 5 seconds
|
|||
const FORCED_CONTEXT_REDUCTION_PERCENT = 75 // Keep 75% of context (remove 25%) on context window errors
|
||||
const MAX_CONTEXT_WINDOW_RETRIES = 3 // Maximum retries for context window errors
|
||||
|
||||
export type TaskOptions = {
|
||||
export interface TaskOptions extends CreateTaskOptions {
|
||||
provider: ClineProvider
|
||||
apiConfiguration: ProviderSettings
|
||||
enableDiff?: boolean
|
||||
|
|
@ -847,7 +849,12 @@ export class Task extends EventEmitter<TaskEvents> implements TaskLike {
|
|||
this.handleWebviewAskResponse("noButtonClicked", text, images)
|
||||
}
|
||||
|
||||
public submitUserMessage(text: string, images?: string[]): void {
|
||||
public async submitUserMessage(
|
||||
text: string,
|
||||
images?: string[],
|
||||
mode?: string,
|
||||
providerProfile?: string,
|
||||
): Promise<void> {
|
||||
try {
|
||||
text = (text ?? "").trim()
|
||||
images = images ?? []
|
||||
|
|
@ -859,6 +866,14 @@ export class Task extends EventEmitter<TaskEvents> implements TaskLike {
|
|||
const provider = this.providerRef.deref()
|
||||
|
||||
if (provider) {
|
||||
if (mode) {
|
||||
await provider.setMode(mode)
|
||||
}
|
||||
|
||||
if (providerProfile) {
|
||||
await provider.setProviderProfile(providerProfile)
|
||||
}
|
||||
|
||||
provider.postMessageToWebview({ type: "invoke", invoke: "sendMessage", text, images })
|
||||
} else {
|
||||
console.error("[Task#submitUserMessage] Provider reference lost")
|
||||
|
|
|
|||
|
|
@ -30,6 +30,7 @@ import {
|
|||
type TerminalActionPromptType,
|
||||
type HistoryItem,
|
||||
type CloudUserInfo,
|
||||
type CreateTaskOptions,
|
||||
RooCodeEventName,
|
||||
requestyDefaultModelId,
|
||||
openRouterDefaultModelId,
|
||||
|
|
@ -37,9 +38,10 @@ import {
|
|||
DEFAULT_TERMINAL_OUTPUT_CHARACTER_LIMIT,
|
||||
DEFAULT_WRITE_DELAY_MS,
|
||||
ORGANIZATION_ALLOW_ALL,
|
||||
DEFAULT_MODES,
|
||||
} from "@roo-code/types"
|
||||
import { TelemetryService } from "@roo-code/telemetry"
|
||||
import { CloudService, BridgeOrchestrator, getRooCodeApiUrl } from "@roo-code/cloud"
|
||||
import { CloudService, getRooCodeApiUrl } from "@roo-code/cloud"
|
||||
|
||||
import { Package } from "../../shared/package"
|
||||
import { findLast } from "../../shared/array"
|
||||
|
|
@ -70,6 +72,7 @@ import { fileExistsAtPath } from "../../utils/fs"
|
|||
import { setTtsEnabled, setTtsSpeed } from "../../utils/tts"
|
||||
import { getWorkspaceGitInfo } from "../../utils/git"
|
||||
import { getWorkspacePath } from "../../utils/path"
|
||||
import { OrganizationAllowListViolationError } from "../../utils/errors"
|
||||
|
||||
import { setPanel } from "../../activate/registerCommands"
|
||||
|
||||
|
|
@ -81,7 +84,7 @@ import { forceFullModelDetailsLoad, hasLoadedFullDetails } from "../../api/provi
|
|||
import { ContextProxy } from "../config/ContextProxy"
|
||||
import { ProviderSettingsManager } from "../config/ProviderSettingsManager"
|
||||
import { CustomModesManager } from "../config/CustomModesManager"
|
||||
import { Task, TaskOptions } from "../task/Task"
|
||||
import { Task } from "../task/Task"
|
||||
import { getSystemPromptFilePath } from "../prompts/sections/custom-system-prompt"
|
||||
|
||||
import { webviewMessageHandler } from "./webviewMessageHandler"
|
||||
|
|
@ -266,27 +269,29 @@ export class ClineProvider
|
|||
}
|
||||
|
||||
/**
|
||||
* Synchronize cloud profiles with local profiles
|
||||
* Synchronize cloud profiles with local profiles.
|
||||
*/
|
||||
private async syncCloudProfiles() {
|
||||
try {
|
||||
const settings = CloudService.instance.getOrganizationSettings()
|
||||
|
||||
if (!settings?.providerProfiles) {
|
||||
return
|
||||
}
|
||||
|
||||
const currentApiConfigName = this.getGlobalState("currentApiConfigName")
|
||||
|
||||
const result = await this.providerSettingsManager.syncCloudProfiles(
|
||||
settings.providerProfiles,
|
||||
currentApiConfigName,
|
||||
)
|
||||
|
||||
if (result.hasChanges) {
|
||||
// Update list
|
||||
// Update list.
|
||||
await this.updateGlobalState("listApiConfigMeta", await this.providerSettingsManager.listConfig())
|
||||
|
||||
if (result.activeProfileChanged && result.activeProfileId) {
|
||||
// Reload full settings for new active profile
|
||||
// Reload full settings for new active profile.
|
||||
const profile = await this.providerSettingsManager.getProfile({
|
||||
id: result.activeProfileId,
|
||||
})
|
||||
|
|
@ -376,17 +381,6 @@ export class ClineProvider
|
|||
}
|
||||
}
|
||||
|
||||
// returns the current cline object in the stack (the top one)
|
||||
// if the stack is empty, returns undefined
|
||||
getCurrentTask(): Task | undefined {
|
||||
if (this.clineStack.length === 0) {
|
||||
return undefined
|
||||
}
|
||||
|
||||
return this.clineStack[this.clineStack.length - 1]
|
||||
}
|
||||
|
||||
// returns the current clineStack length (how many cline objects are in the stack)
|
||||
getTaskStackSize(): number {
|
||||
return this.clineStack.length
|
||||
}
|
||||
|
|
@ -409,58 +403,6 @@ export class ClineProvider
|
|||
await this.getCurrentTask()?.resumePausedTask(lastMessage)
|
||||
}
|
||||
|
||||
resumeTask(taskId: string): void {
|
||||
// Use the existing showTaskWithId method which handles both current and historical tasks
|
||||
this.showTaskWithId(taskId).catch((error) => {
|
||||
this.log(`Failed to resume task ${taskId}: ${error.message}`)
|
||||
})
|
||||
}
|
||||
|
||||
getRecentTasks(): string[] {
|
||||
if (this.recentTasksCache) {
|
||||
return this.recentTasksCache
|
||||
}
|
||||
|
||||
const history = this.getGlobalState("taskHistory") ?? []
|
||||
const workspaceTasks: HistoryItem[] = []
|
||||
|
||||
for (const item of history) {
|
||||
if (!item.ts || !item.task || item.workspace !== this.cwd) {
|
||||
continue
|
||||
}
|
||||
|
||||
workspaceTasks.push(item)
|
||||
}
|
||||
|
||||
if (workspaceTasks.length === 0) {
|
||||
this.recentTasksCache = []
|
||||
return this.recentTasksCache
|
||||
}
|
||||
|
||||
workspaceTasks.sort((a, b) => b.ts - a.ts)
|
||||
let recentTaskIds: string[] = []
|
||||
|
||||
if (workspaceTasks.length >= 100) {
|
||||
// If we have at least 100 tasks, return tasks from the last 7 days.
|
||||
const sevenDaysAgo = Date.now() - 7 * 24 * 60 * 60 * 1000
|
||||
|
||||
for (const item of workspaceTasks) {
|
||||
// Stop when we hit tasks older than 7 days.
|
||||
if (item.ts < sevenDaysAgo) {
|
||||
break
|
||||
}
|
||||
|
||||
recentTaskIds.push(item.id)
|
||||
}
|
||||
} else {
|
||||
// Otherwise, return the most recent 100 tasks (or all if less than 100).
|
||||
recentTaskIds = workspaceTasks.slice(0, Math.min(100, workspaceTasks.length)).map((item) => item.id)
|
||||
}
|
||||
|
||||
this.recentTasksCache = recentTaskIds
|
||||
return this.recentTasksCache
|
||||
}
|
||||
|
||||
/*
|
||||
VSCode extensions use the disposable pattern to clean up resources when the sidebar/editor tab is closed by the user or system. This applies to event listening, commands, interacting with the UI, etc.
|
||||
- https://vscode-docs.readthedocs.io/en/stable/extensions/patterns-and-principles/
|
||||
|
|
@ -741,84 +683,19 @@ export class ClineProvider
|
|||
await this.removeClineFromStack()
|
||||
}
|
||||
|
||||
// When initializing a new task, (not from history but from a tool command
|
||||
// new_task) there is no need to remove the previous task since the new
|
||||
// task is a subtask of the previous one, and when it finishes it is removed
|
||||
// from the stack and the caller is resumed in this way we can have a chain
|
||||
// of tasks, each one being a sub task of the previous one until the main
|
||||
// task is finished.
|
||||
public async createTask(
|
||||
text?: string,
|
||||
images?: string[],
|
||||
parentTask?: Task,
|
||||
options: Partial<
|
||||
Pick<
|
||||
TaskOptions,
|
||||
| "enableDiff"
|
||||
| "enableCheckpoints"
|
||||
| "fuzzyMatchThreshold"
|
||||
| "consecutiveMistakeLimit"
|
||||
| "experiments"
|
||||
| "initialTodos"
|
||||
>
|
||||
> = {},
|
||||
) {
|
||||
const {
|
||||
apiConfiguration,
|
||||
organizationAllowList,
|
||||
diffEnabled: enableDiff,
|
||||
enableCheckpoints,
|
||||
fuzzyMatchThreshold,
|
||||
experiments,
|
||||
cloudUserInfo,
|
||||
remoteControlEnabled,
|
||||
} = await this.getState()
|
||||
|
||||
if (!ProfileValidator.isProfileAllowed(apiConfiguration, organizationAllowList)) {
|
||||
throw new OrganizationAllowListViolationError(t("common:errors.violated_organization_allowlist"))
|
||||
}
|
||||
|
||||
const task = new Task({
|
||||
provider: this,
|
||||
apiConfiguration,
|
||||
enableDiff,
|
||||
enableCheckpoints,
|
||||
fuzzyMatchThreshold,
|
||||
consecutiveMistakeLimit: apiConfiguration.consecutiveMistakeLimit,
|
||||
task: text,
|
||||
images,
|
||||
experiments,
|
||||
rootTask: this.clineStack.length > 0 ? this.clineStack[0] : undefined,
|
||||
parentTask,
|
||||
taskNumber: this.clineStack.length + 1,
|
||||
onCreated: this.taskCreationCallback,
|
||||
enableBridge: BridgeOrchestrator.isEnabled(cloudUserInfo, remoteControlEnabled),
|
||||
initialTodos: options.initialTodos,
|
||||
...options,
|
||||
})
|
||||
|
||||
await this.addClineToStack(task)
|
||||
|
||||
this.log(
|
||||
`[createTask] ${task.parentTask ? "child" : "parent"} task ${task.taskId}.${task.instanceId} instantiated`,
|
||||
)
|
||||
|
||||
return task
|
||||
}
|
||||
|
||||
public async createTaskWithHistoryItem(
|
||||
public async createTaskWithHistoryItem(
|
||||
historyItem: HistoryItem & { rootTask?: Task; parentTask?: Task; preservedFCOState?: any },
|
||||
) {
|
||||
await this.removeClineFromStack()
|
||||
|
||||
// If the history item has a saved mode, restore it and its associated API configuration
|
||||
// If the history item has a saved mode, restore it and its associated API configuration.
|
||||
if (historyItem.mode) {
|
||||
// Validate that the mode still exists
|
||||
const customModes = await this.customModesManager.getCustomModes()
|
||||
const modeExists = getModeBySlug(historyItem.mode, customModes) !== undefined
|
||||
|
||||
if (!modeExists) {
|
||||
// Mode no longer exists, fall back to default mode
|
||||
// Mode no longer exists, fall back to default mode.
|
||||
this.log(
|
||||
`Mode '${historyItem.mode}' from history no longer exists. Falling back to default mode '${defaultModeSlug}'.`,
|
||||
)
|
||||
|
|
@ -827,14 +704,14 @@ export class ClineProvider
|
|||
|
||||
await this.updateGlobalState("mode", historyItem.mode)
|
||||
|
||||
// Load the saved API config for the restored mode if it exists
|
||||
// Load the saved API config for the restored mode if it exists.
|
||||
const savedConfigId = await this.providerSettingsManager.getModeConfigId(historyItem.mode)
|
||||
const listApiConfig = await this.providerSettingsManager.listConfig()
|
||||
|
||||
// Update listApiConfigMeta first to ensure UI has latest data
|
||||
// Update listApiConfigMeta first to ensure UI has latest data.
|
||||
await this.updateGlobalState("listApiConfigMeta", listApiConfig)
|
||||
|
||||
// If this mode has a saved config, use it
|
||||
// If this mode has a saved config, use it.
|
||||
if (savedConfigId) {
|
||||
const profile = listApiConfig.find(({ id }) => id === savedConfigId)
|
||||
|
||||
|
|
@ -842,13 +719,13 @@ export class ClineProvider
|
|||
try {
|
||||
await this.activateProviderProfile({ name: profile.name })
|
||||
} catch (error) {
|
||||
// Log the error but continue with task restoration
|
||||
// Log the error but continue with task restoration.
|
||||
this.log(
|
||||
`Failed to restore API configuration for mode '${historyItem.mode}': ${
|
||||
error instanceof Error ? error.message : String(error)
|
||||
}. Continuing with default configuration.`,
|
||||
)
|
||||
// The task will continue with the current/default configuration
|
||||
// The task will continue with the current/default configuration.
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -877,7 +754,7 @@ export class ClineProvider
|
|||
parentTask: historyItem.parentTask,
|
||||
taskNumber: historyItem.number,
|
||||
onCreated: this.taskCreationCallback,
|
||||
enableBridge: BridgeOrchestrator.isEnabled(cloudUserInfo, remoteControlEnabled),
|
||||
enableBridge: false, // BridgeOrchestrator removed in main
|
||||
})
|
||||
|
||||
await this.addClineToStack(task)
|
||||
|
|
@ -1127,39 +1004,39 @@ export class ClineProvider
|
|||
TelemetryService.instance.captureModeSwitch(cline.taskId, newMode)
|
||||
cline.emit(RooCodeEventName.TaskModeSwitched, cline.taskId, newMode)
|
||||
|
||||
// Store the current mode in case we need to rollback
|
||||
const previousMode = (cline as any)._taskMode
|
||||
|
||||
try {
|
||||
// Update the task history with the new mode first
|
||||
// Update the task history with the new mode first.
|
||||
const history = this.getGlobalState("taskHistory") ?? []
|
||||
const taskHistoryItem = history.find((item) => item.id === cline.taskId)
|
||||
|
||||
if (taskHistoryItem) {
|
||||
taskHistoryItem.mode = newMode
|
||||
await this.updateTaskHistory(taskHistoryItem)
|
||||
}
|
||||
|
||||
// Only update the task's mode after successful persistence
|
||||
// Only update the task's mode after successful persistence.
|
||||
;(cline as any)._taskMode = newMode
|
||||
} catch (error) {
|
||||
// If persistence fails, log the error but don't update the in-memory state
|
||||
// If persistence fails, log the error but don't update the in-memory state.
|
||||
this.log(
|
||||
`Failed to persist mode switch for task ${cline.taskId}: ${error instanceof Error ? error.message : String(error)}`,
|
||||
)
|
||||
|
||||
// Optionally, we could emit an event to notify about the failure
|
||||
// This ensures the in-memory state remains consistent with persisted state
|
||||
// Optionally, we could emit an event to notify about the failure.
|
||||
// This ensures the in-memory state remains consistent with persisted state.
|
||||
throw error
|
||||
}
|
||||
}
|
||||
|
||||
await this.updateGlobalState("mode", newMode)
|
||||
|
||||
// Load the saved API config for the new mode if it exists
|
||||
this.emit(RooCodeEventName.ModeChanged, newMode)
|
||||
|
||||
// Load the saved API config for the new mode if it exists.
|
||||
const savedConfigId = await this.providerSettingsManager.getModeConfigId(newMode)
|
||||
const listApiConfig = await this.providerSettingsManager.listConfig()
|
||||
|
||||
// Update listApiConfigMeta first to ensure UI has latest data
|
||||
// Update listApiConfigMeta first to ensure UI has latest data.
|
||||
await this.updateGlobalState("listApiConfigMeta", listApiConfig)
|
||||
|
||||
// If this mode has a saved config, use it.
|
||||
|
|
@ -1302,73 +1179,75 @@ export class ClineProvider
|
|||
}
|
||||
|
||||
await this.postStateToWebview()
|
||||
|
||||
if (providerSettings.apiProvider) {
|
||||
this.emit(RooCodeEventName.ProviderProfileChanged, { name, provider: providerSettings.apiProvider })
|
||||
}
|
||||
}
|
||||
|
||||
public async cancelTask(): Promise<void> {
|
||||
const cline = this.getCurrentTask()
|
||||
|
||||
if (!cline) {
|
||||
return
|
||||
}
|
||||
|
||||
// Task Management
|
||||
console.log(`[cancelTask] cancelling task ${cline.taskId}.${cline.instanceId}`)
|
||||
|
||||
async cancelTask() {
|
||||
const cline = this.getCurrentTask()
|
||||
const { historyItem } = await this.getTaskWithId(cline.taskId)
|
||||
// Preserve parent and root task information for history item.
|
||||
const rootTask = cline.rootTask
|
||||
const parentTask = cline.parentTask
|
||||
|
||||
if (!cline) {
|
||||
return
|
||||
// Preserve FCO state before aborting task to prevent FCO from disappearing
|
||||
let preservedFCOState: any = undefined
|
||||
try {
|
||||
const fileChangeManager = this.getFileChangeManager()
|
||||
if (fileChangeManager) {
|
||||
preservedFCOState = fileChangeManager.getChanges()
|
||||
this.log(`[cancelTask] Preserved FCO state with ${preservedFCOState.files.length} files`)
|
||||
}
|
||||
|
||||
console.log(`[cancelTask] cancelling task ${cline.taskId}.${cline.instanceId}`)
|
||||
|
||||
const { historyItem } = await this.getTaskWithId(cline.taskId)
|
||||
// Preserve parent and root task information for history item.
|
||||
const rootTask = cline.rootTask
|
||||
const parentTask = cline.parentTask
|
||||
|
||||
// Preserve FCO state before aborting task to prevent FCO from disappearing
|
||||
let preservedFCOState: any = undefined
|
||||
try {
|
||||
const fileChangeManager = this.getFileChangeManager()
|
||||
if (fileChangeManager) {
|
||||
preservedFCOState = fileChangeManager.getChanges()
|
||||
this.log(`[cancelTask] Preserved FCO state with ${preservedFCOState.files.length} files`)
|
||||
}
|
||||
} catch (error) {
|
||||
this.log(`[cancelTask] Failed to preserve FCO state: ${error}`)
|
||||
}
|
||||
|
||||
cline.abortTask()
|
||||
|
||||
await pWaitFor(
|
||||
() =>
|
||||
this.getCurrentTask()! === undefined ||
|
||||
this.getCurrentTask()!.isStreaming === false ||
|
||||
this.getCurrentTask()!.didFinishAbortingStream ||
|
||||
// If only the first chunk is processed, then there's no
|
||||
// need to wait for graceful abort (closes edits, browser,
|
||||
// etc).
|
||||
this.getCurrentTask()!.isWaitingForFirstChunk,
|
||||
{
|
||||
timeout: 3_000,
|
||||
},
|
||||
).catch(() => {
|
||||
console.error("Failed to abort task")
|
||||
})
|
||||
|
||||
if (this.getCurrentTask()) {
|
||||
// 'abandoned' will prevent this Cline instance from affecting
|
||||
// future Cline instances. This may happen if its hanging on a
|
||||
// streaming request.
|
||||
this.getCurrentTask()!.abandoned = true
|
||||
}
|
||||
|
||||
// Clears task again, so we need to abortTask manually above.
|
||||
await this.createTaskWithHistoryItem({ ...historyItem, rootTask, parentTask, preservedFCOState })
|
||||
} catch (error) {
|
||||
this.log(`[cancelTask] Failed to preserve FCO state: ${error}`)
|
||||
}
|
||||
|
||||
// Clear the current task without treating it as a subtask.
|
||||
// This is used when the user cancels a task that is not a subtask.
|
||||
async clearTask() {
|
||||
if (this.clineStack.length > 0) {
|
||||
const task = this.clineStack[this.clineStack.length - 1]
|
||||
console.log(`[clearTask] clearing task ${task.taskId}.${task.instanceId}`)
|
||||
await this.removeClineFromStack()
|
||||
}
|
||||
cline.abortTask()
|
||||
|
||||
await pWaitFor(
|
||||
() =>
|
||||
this.getCurrentTask()! === undefined ||
|
||||
this.getCurrentTask()!.isStreaming === false ||
|
||||
this.getCurrentTask()!.didFinishAbortingStream ||
|
||||
// If only the first chunk is processed, then there's no
|
||||
// need to wait for graceful abort (closes edits, browser,
|
||||
// etc).
|
||||
this.getCurrentTask()!.isWaitingForFirstChunk,
|
||||
{
|
||||
timeout: 3_000,
|
||||
},
|
||||
).catch(() => {
|
||||
console.error("Failed to abort task")
|
||||
})
|
||||
|
||||
if (this.getCurrentTask()) {
|
||||
// 'abandoned' will prevent this Cline instance from affecting
|
||||
// future Cline instances. This may happen if its hanging on a
|
||||
// streaming request.
|
||||
this.getCurrentTask()!.abandoned = true
|
||||
}
|
||||
|
||||
// Clears task again, so we need to abortTask manually above.
|
||||
await this.createTaskWithHistoryItem({ ...historyItem, rootTask, parentTask, preservedFCOState })
|
||||
}
|
||||
|
||||
// Clear the current task without treating it as a subtask.
|
||||
// This is used when the user cancels a task that is not a subtask.
|
||||
public async clearTask(): Promise<void> {
|
||||
if (this.clineStack.length > 0) {
|
||||
const task = this.clineStack[this.clineStack.length - 1]
|
||||
console.log(`[clearTask] clearing task ${task.taskId}.${task.instanceId}`)
|
||||
await this.removeClineFromStack()
|
||||
}
|
||||
}
|
||||
|
||||
async updateCustomInstructions(instructions?: string) {
|
||||
|
|
@ -2143,18 +2022,8 @@ export class ClineProvider
|
|||
maxDiagnosticMessages: stateValues.maxDiagnosticMessages ?? 50,
|
||||
// Add includeTaskHistoryInEnhance setting
|
||||
includeTaskHistoryInEnhance: stateValues.includeTaskHistoryInEnhance ?? true,
|
||||
// Add remoteControlEnabled setting - get from cloud settings
|
||||
remoteControlEnabled: (() => {
|
||||
try {
|
||||
const cloudSettings = CloudService.instance.getUserSettings()
|
||||
return cloudSettings?.settings?.extensionBridgeEnabled ?? false
|
||||
} catch (error) {
|
||||
console.error(
|
||||
`[getState] failed to get remote control setting from cloud: ${error instanceof Error ? error.message : String(error)}`,
|
||||
)
|
||||
return false
|
||||
}
|
||||
})(),
|
||||
// Remote control functionality removed in main branch
|
||||
remoteControlEnabled: false,
|
||||
// Add image generation settings
|
||||
openRouterImageApiKey: stateValues.openRouterImageApiKey,
|
||||
openRouterImageGenerationSelectedModel: stateValues.openRouterImageGenerationSelectedModel,
|
||||
|
|
@ -2205,12 +2074,6 @@ export class ClineProvider
|
|||
await this.contextProxy.setValues(values)
|
||||
}
|
||||
|
||||
// cwd
|
||||
|
||||
get cwd() {
|
||||
return getWorkspacePath()
|
||||
}
|
||||
|
||||
// dev
|
||||
|
||||
async resetState() {
|
||||
|
|
@ -2285,43 +2148,252 @@ export class ClineProvider
|
|||
return
|
||||
}
|
||||
|
||||
await BridgeOrchestrator.connectOrDisconnect(userInfo, enabled, {
|
||||
...config,
|
||||
provider: this,
|
||||
sessionId: vscode.env.sessionId,
|
||||
})
|
||||
// BridgeOrchestrator functionality removed in main branch
|
||||
this.log(`[ClineProvider#remoteControlEnabled] Remote control ${enabled ? 'enabled' : 'disabled'}`)
|
||||
}
|
||||
|
||||
const bridge = BridgeOrchestrator.getInstance()
|
||||
/**
|
||||
* Gets the CodeIndexManager for the current active workspace
|
||||
* @returns CodeIndexManager instance for the current workspace or the default one
|
||||
*/
|
||||
public getCurrentWorkspaceCodeIndexManager(): CodeIndexManager | undefined {
|
||||
return CodeIndexManager.getInstance(this.context)
|
||||
}
|
||||
|
||||
if (bridge) {
|
||||
const currentTask = this.getCurrentTask()
|
||||
/**
|
||||
* Updates the code index status subscription to listen to the current workspace manager
|
||||
*/
|
||||
private updateCodeIndexStatusSubscription(): void {
|
||||
// Get the current workspace manager
|
||||
const currentManager = this.getCurrentWorkspaceCodeIndexManager()
|
||||
|
||||
if (currentTask && !currentTask.enableBridge) {
|
||||
try {
|
||||
currentTask.enableBridge = true
|
||||
await BridgeOrchestrator.subscribeToTask(currentTask)
|
||||
} catch (error) {
|
||||
const message = `[ClineProvider#remoteControlEnabled] BridgeOrchestrator.subscribeToTask() failed: ${error instanceof Error ? error.message : String(error)}`
|
||||
this.log(message)
|
||||
console.error(message)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
for (const task of this.clineStack) {
|
||||
if (task.enableBridge) {
|
||||
try {
|
||||
await BridgeOrchestrator.getInstance()?.unsubscribeFromTask(task.taskId)
|
||||
} catch (error) {
|
||||
const message = `[ClineProvider#remoteControlEnabled] BridgeOrchestrator#unsubscribeFromTask() failed: ${error instanceof Error ? error.message : String(error)}`
|
||||
this.log(message)
|
||||
console.error(message)
|
||||
}
|
||||
// If the manager hasn't changed, no need to update subscription
|
||||
if (currentManager === this.currentWorkspaceManager) {
|
||||
return
|
||||
}
|
||||
|
||||
// Dispose the old subscription if it exists
|
||||
if (this.codeIndexStatusSubscription) {
|
||||
this.codeIndexStatusSubscription.dispose()
|
||||
this.codeIndexStatusSubscription = undefined
|
||||
}
|
||||
|
||||
// Update the current workspace manager reference
|
||||
this.currentWorkspaceManager = currentManager
|
||||
|
||||
// Subscribe to the new manager's progress updates if it exists
|
||||
if (currentManager) {
|
||||
this.codeIndexStatusSubscription = currentManager.onProgressUpdate((update: IndexProgressUpdate) => {
|
||||
// Only send updates if this manager is still the current one
|
||||
if (currentManager === this.getCurrentWorkspaceCodeIndexManager()) {
|
||||
// Get the full status from the manager to ensure we have all fields correctly formatted
|
||||
const fullStatus = currentManager.getCurrentStatus()
|
||||
this.postMessageToWebview({
|
||||
type: "indexingStatusUpdate",
|
||||
values: fullStatus,
|
||||
})
|
||||
}
|
||||
})
|
||||
|
||||
if (this.view) {
|
||||
this.webviewDisposables.push(this.codeIndexStatusSubscription)
|
||||
}
|
||||
|
||||
// Send initial status for the current workspace
|
||||
this.postMessageToWebview({
|
||||
type: "indexingStatusUpdate",
|
||||
values: currentManager.getCurrentStatus(),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* TaskProviderLike, TelemetryPropertiesProvider
|
||||
*/
|
||||
|
||||
public getCurrentTask(): Task | undefined {
|
||||
if (this.clineStack.length === 0) {
|
||||
return undefined
|
||||
}
|
||||
|
||||
return this.clineStack[this.clineStack.length - 1]
|
||||
}
|
||||
|
||||
public getRecentTasks(): string[] {
|
||||
if (this.recentTasksCache) {
|
||||
return this.recentTasksCache
|
||||
}
|
||||
|
||||
const history = this.getGlobalState("taskHistory") ?? []
|
||||
const workspaceTasks: HistoryItem[] = []
|
||||
|
||||
for (const item of history) {
|
||||
if (!item.ts || !item.task || item.workspace !== this.cwd) {
|
||||
continue
|
||||
}
|
||||
|
||||
workspaceTasks.push(item)
|
||||
}
|
||||
|
||||
if (workspaceTasks.length === 0) {
|
||||
this.recentTasksCache = []
|
||||
return this.recentTasksCache
|
||||
}
|
||||
|
||||
workspaceTasks.sort((a, b) => b.ts - a.ts)
|
||||
let recentTaskIds: string[] = []
|
||||
|
||||
if (workspaceTasks.length >= 100) {
|
||||
// If we have at least 100 tasks, return tasks from the last 7 days.
|
||||
const sevenDaysAgo = Date.now() - 7 * 24 * 60 * 60 * 1000
|
||||
|
||||
for (const item of workspaceTasks) {
|
||||
// Stop when we hit tasks older than 7 days.
|
||||
if (item.ts < sevenDaysAgo) {
|
||||
break
|
||||
}
|
||||
|
||||
recentTaskIds.push(item.id)
|
||||
}
|
||||
} else {
|
||||
// Otherwise, return the most recent 100 tasks (or all if less than 100).
|
||||
recentTaskIds = workspaceTasks.slice(0, Math.min(100, workspaceTasks.length)).map((item) => item.id)
|
||||
}
|
||||
|
||||
this.recentTasksCache = recentTaskIds
|
||||
return this.recentTasksCache
|
||||
}
|
||||
|
||||
// When initializing a new task, (not from history but from a tool command
|
||||
// new_task) there is no need to remove the previous task since the new
|
||||
// task is a subtask of the previous one, and when it finishes it is removed
|
||||
// from the stack and the caller is resumed in this way we can have a chain
|
||||
// of tasks, each one being a sub task of the previous one until the main
|
||||
// task is finished.
|
||||
public async createTask(
|
||||
text?: string,
|
||||
images?: string[],
|
||||
parentTask?: Task,
|
||||
options: CreateTaskOptions = {},
|
||||
configuration: RooCodeSettings = {},
|
||||
): Promise<Task> {
|
||||
if (configuration) {
|
||||
await this.setValues(configuration)
|
||||
|
||||
if (configuration.allowedCommands) {
|
||||
await vscode.workspace
|
||||
.getConfiguration(Package.name)
|
||||
.update("allowedCommands", configuration.allowedCommands, vscode.ConfigurationTarget.Global)
|
||||
}
|
||||
|
||||
if (configuration.deniedCommands) {
|
||||
await vscode.workspace
|
||||
.getConfiguration(Package.name)
|
||||
.update("deniedCommands", configuration.deniedCommands, vscode.ConfigurationTarget.Global)
|
||||
}
|
||||
|
||||
if (configuration.commandExecutionTimeout !== undefined) {
|
||||
await vscode.workspace
|
||||
.getConfiguration(Package.name)
|
||||
.update(
|
||||
"commandExecutionTimeout",
|
||||
configuration.commandExecutionTimeout,
|
||||
vscode.ConfigurationTarget.Global,
|
||||
)
|
||||
}
|
||||
|
||||
if (configuration.currentApiConfigName) {
|
||||
await this.setProviderProfile(configuration.currentApiConfigName)
|
||||
}
|
||||
}
|
||||
|
||||
const {
|
||||
apiConfiguration,
|
||||
organizationAllowList,
|
||||
diffEnabled: enableDiff,
|
||||
enableCheckpoints,
|
||||
fuzzyMatchThreshold,
|
||||
experiments,
|
||||
cloudUserInfo,
|
||||
remoteControlEnabled,
|
||||
} = await this.getState()
|
||||
|
||||
if (!ProfileValidator.isProfileAllowed(apiConfiguration, organizationAllowList)) {
|
||||
throw new OrganizationAllowListViolationError(t("common:errors.violated_organization_allowlist"))
|
||||
}
|
||||
|
||||
const task = new Task({
|
||||
provider: this,
|
||||
apiConfiguration,
|
||||
enableDiff,
|
||||
enableCheckpoints,
|
||||
fuzzyMatchThreshold,
|
||||
consecutiveMistakeLimit: apiConfiguration.consecutiveMistakeLimit,
|
||||
task: text,
|
||||
images,
|
||||
experiments,
|
||||
rootTask: this.clineStack.length > 0 ? this.clineStack[0] : undefined,
|
||||
parentTask,
|
||||
taskNumber: this.clineStack.length + 1,
|
||||
onCreated: this.taskCreationCallback,
|
||||
enableBridge: false, // BridgeOrchestrator removed in main
|
||||
initialTodos: options.initialTodos,
|
||||
...options,
|
||||
})
|
||||
|
||||
await this.addClineToStack(task)
|
||||
|
||||
this.log(
|
||||
`[createTask] ${task.parentTask ? "child" : "parent"} task ${task.taskId}.${task.instanceId} instantiated`,
|
||||
)
|
||||
|
||||
return task
|
||||
}
|
||||
|
||||
|
||||
public resumeTask(taskId: string): void {
|
||||
// Use the existing showTaskWithId method which handles both current and
|
||||
// historical tasks.
|
||||
this.showTaskWithId(taskId).catch((error) => {
|
||||
this.log(`Failed to resume task ${taskId}: ${error.message}`)
|
||||
})
|
||||
}
|
||||
|
||||
// Modes
|
||||
|
||||
public async getModes(): Promise<{ slug: string; name: string }[]> {
|
||||
return DEFAULT_MODES.map((mode) => ({ slug: mode.slug, name: mode.name }))
|
||||
}
|
||||
|
||||
public async getMode(): Promise<string> {
|
||||
const { mode } = await this.getState()
|
||||
return mode
|
||||
}
|
||||
|
||||
public async setMode(mode: string): Promise<void> {
|
||||
await this.setValues({ mode })
|
||||
}
|
||||
|
||||
// Provider Profiles
|
||||
|
||||
public async getProviderProfiles(): Promise<{ name: string; provider?: string }[]> {
|
||||
const { listApiConfigMeta } = await this.getState()
|
||||
return listApiConfigMeta.map((profile) => ({ name: profile.name, provider: profile.apiProvider }))
|
||||
}
|
||||
|
||||
public async getProviderProfile(): Promise<string> {
|
||||
const { currentApiConfigName } = await this.getState()
|
||||
return currentApiConfigName
|
||||
}
|
||||
|
||||
public async setProviderProfile(name: string): Promise<void> {
|
||||
await this.activateProviderProfile({ name })
|
||||
}
|
||||
|
||||
// Telemetry
|
||||
|
||||
private _appProperties?: StaticAppProperties
|
||||
private _gitProperties?: GitProperties
|
||||
|
||||
private getAppProperties(): StaticAppProperties {
|
||||
if (!this._appProperties) {
|
||||
|
|
@ -2388,8 +2460,6 @@ export class ClineProvider
|
|||
}
|
||||
}
|
||||
|
||||
private _gitProperties?: GitProperties
|
||||
|
||||
private async getGitProperties(): Promise<GitProperties> {
|
||||
if (!this._gitProperties) {
|
||||
this._gitProperties = await getWorkspaceGitInfo()
|
||||
|
|
@ -2411,61 +2481,6 @@ export class ClineProvider
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the CodeIndexManager for the current active workspace
|
||||
* @returns CodeIndexManager instance for the current workspace or the default one
|
||||
*/
|
||||
public getCurrentWorkspaceCodeIndexManager(): CodeIndexManager | undefined {
|
||||
return CodeIndexManager.getInstance(this.context)
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates the code index status subscription to listen to the current workspace manager
|
||||
*/
|
||||
private updateCodeIndexStatusSubscription(): void {
|
||||
// Get the current workspace manager
|
||||
const currentManager = this.getCurrentWorkspaceCodeIndexManager()
|
||||
|
||||
// If the manager hasn't changed, no need to update subscription
|
||||
if (currentManager === this.currentWorkspaceManager) {
|
||||
return
|
||||
}
|
||||
|
||||
// Dispose the old subscription if it exists
|
||||
if (this.codeIndexStatusSubscription) {
|
||||
this.codeIndexStatusSubscription.dispose()
|
||||
this.codeIndexStatusSubscription = undefined
|
||||
}
|
||||
|
||||
// Update the current workspace manager reference
|
||||
this.currentWorkspaceManager = currentManager
|
||||
|
||||
// Subscribe to the new manager's progress updates if it exists
|
||||
if (currentManager) {
|
||||
this.codeIndexStatusSubscription = currentManager.onProgressUpdate((update: IndexProgressUpdate) => {
|
||||
// Only send updates if this manager is still the current one
|
||||
if (currentManager === this.getCurrentWorkspaceCodeIndexManager()) {
|
||||
// Get the full status from the manager to ensure we have all fields correctly formatted
|
||||
const fullStatus = currentManager.getCurrentStatus()
|
||||
this.postMessageToWebview({
|
||||
type: "indexingStatusUpdate",
|
||||
values: fullStatus,
|
||||
})
|
||||
}
|
||||
})
|
||||
|
||||
if (this.view) {
|
||||
this.webviewDisposables.push(this.codeIndexStatusSubscription)
|
||||
}
|
||||
|
||||
// Send initial status for the current workspace
|
||||
this.postMessageToWebview({
|
||||
type: "indexingStatusUpdate",
|
||||
values: currentManager.getCurrentStatus(),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
public getFileChangeManager():
|
||||
| import("../../services/file-changes/FileChangeManager").FileChangeManager
|
||||
| undefined {
|
||||
|
|
@ -2481,10 +2496,8 @@ export class ClineProvider
|
|||
}
|
||||
return this.globalFileChangeManager
|
||||
}
|
||||
}
|
||||
|
||||
class OrganizationAllowListViolationError extends Error {
|
||||
constructor(message: string) {
|
||||
super(message)
|
||||
public get cwd() {
|
||||
return getWorkspacePath()
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue