Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
28 changes: 15 additions & 13 deletions pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

29 changes: 28 additions & 1 deletion src/core/task/Task.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ import {
isBlockingAsk,
} from "@roo-code/types"
import { TelemetryService } from "@roo-code/telemetry"
import { CloudService } from "@roo-code/cloud"
import { CloudService, TaskBridgeService } from "@roo-code/cloud"

// api
import { ApiHandler, ApiHandlerCreateMessageMetadata, buildApiHandler } from "../../api"
Expand Down Expand Up @@ -118,6 +118,7 @@ export type TaskOptions = {
parentTask?: Task
taskNumber?: number
onCreated?: (task: Task) => void
enableTaskBridge?: boolean
}

export class Task extends EventEmitter<TaskEvents> implements TaskLike {
Expand Down Expand Up @@ -237,6 +238,9 @@ export class Task extends EventEmitter<TaskEvents> implements TaskLike {
checkpointService?: RepoPerTaskCheckpointService
checkpointServiceInitializing = false

// Task Bridge
taskBridgeService?: TaskBridgeService

// Streaming
isWaitingForFirstChunk = false
isStreaming = false
Expand Down Expand Up @@ -268,6 +272,7 @@ export class Task extends EventEmitter<TaskEvents> implements TaskLike {
parentTask,
taskNumber = -1,
onCreated,
enableTaskBridge = false,
}: TaskOptions) {
super()

Expand Down Expand Up @@ -345,6 +350,11 @@ export class Task extends EventEmitter<TaskEvents> implements TaskLike {

this.toolRepetitionDetector = new ToolRepetitionDetector(this.consecutiveMistakeLimit)

// Initialize TaskBridgeService only if enabled
if (enableTaskBridge) {
this.taskBridgeService = TaskBridgeService.getInstance()
}

onCreated?.(this)

if (startTask) {
Expand Down Expand Up @@ -931,6 +941,11 @@ export class Task extends EventEmitter<TaskEvents> implements TaskLike {
// Start / Abort / Resume

private async startTask(task?: string, images?: string[]): Promise<void> {
if (this.taskBridgeService) {
await this.taskBridgeService.initialize()
await this.taskBridgeService.subscribeToTask(this)
}

// `conversationHistory` (for API) and `clineMessages` (for webview)
// need to be in sync.
// If the extension process were killed, then on restart the
Expand Down Expand Up @@ -982,6 +997,11 @@ export class Task extends EventEmitter<TaskEvents> implements TaskLike {
}

private async resumeTaskFromHistory() {
if (this.taskBridgeService) {
await this.taskBridgeService.initialize()
await this.taskBridgeService.subscribeToTask(this)
}

const modifiedClineMessages = await this.getSavedClineMessages()

// Remove any resume messages that may have been added before
Expand Down Expand Up @@ -1227,6 +1247,13 @@ export class Task extends EventEmitter<TaskEvents> implements TaskLike {
this.pauseInterval = undefined
}

// Unsubscribe from TaskBridge service.
if (this.taskBridgeService) {
this.taskBridgeService
.unsubscribeFromTask(this.taskId)
.catch((error) => console.error("Error unsubscribing from task bridge:", error))
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Consider also logging this error to the provider's output channel for better visibility:

Suggested change
.catch((error) => console.error("Error unsubscribing from task bridge:", error))
const message = `Error unsubscribing from task bridge: ${error instanceof Error ? error.message : String(error)}`
this.providerRef.deref()?.log(`[Task#dispose] ${message}`)
console.error(message)

This would make debugging easier by ensuring errors appear in the extension's output channel.

}

// Release any terminals associated with this task.
try {
// Release any terminals associated with this task.
Expand Down
115 changes: 112 additions & 3 deletions src/core/webview/ClineProvider.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,6 @@ import {
type ProviderSettings,
type RooCodeSettings,
type ProviderSettingsEntry,
type ProviderSettingsWithId,
type TelemetryProperties,
type TelemetryPropertiesProvider,
type CodeActionId,
Expand Down Expand Up @@ -66,6 +65,7 @@ import { fileExistsAtPath } from "../../utils/fs"
import { setTtsEnabled, setTtsSpeed } from "../../utils/tts"
import { getWorkspaceGitInfo } from "../../utils/git"
import { getWorkspacePath } from "../../utils/path"
import { isRemoteControlEnabled } from "../../utils/remoteControl"

import { setPanel } from "../../activate/registerCommands"

Expand Down Expand Up @@ -111,6 +111,8 @@ export class ClineProvider
protected mcpHub?: McpHub // Change from private to protected
private marketplaceManager: MarketplaceManager
private mdmService?: MdmService
private taskCreationCallback: (task: Task) => void
private taskEventListeners: WeakMap<Task, Array<() => void>> = new WeakMap()

public isViewLaunched = false
public settingsImportedAt?: number
Expand Down Expand Up @@ -162,6 +164,40 @@ export class ClineProvider

this.marketplaceManager = new MarketplaceManager(this.context, this.customModesManager)

this.taskCreationCallback = (instance: Task) => {
this.emit(RooCodeEventName.TaskCreated, instance)

// Create named listener functions so we can remove them later.
const onTaskStarted = () => this.emit(RooCodeEventName.TaskStarted, instance.taskId)
const onTaskCompleted = (taskId: string, tokenUsage: any, toolUsage: any) =>
this.emit(RooCodeEventName.TaskCompleted, taskId, tokenUsage, toolUsage)
const onTaskAborted = () => this.emit(RooCodeEventName.TaskAborted, instance.taskId)
const onTaskFocused = () => this.emit(RooCodeEventName.TaskFocused, instance.taskId)
const onTaskUnfocused = () => this.emit(RooCodeEventName.TaskUnfocused, instance.taskId)
const onTaskActive = (taskId: string) => this.emit(RooCodeEventName.TaskActive, taskId)
const onTaskIdle = (taskId: string) => this.emit(RooCodeEventName.TaskIdle, taskId)

// Attach the listeners.
instance.on(RooCodeEventName.TaskStarted, onTaskStarted)
instance.on(RooCodeEventName.TaskCompleted, onTaskCompleted)
instance.on(RooCodeEventName.TaskAborted, onTaskAborted)
instance.on(RooCodeEventName.TaskFocused, onTaskFocused)
instance.on(RooCodeEventName.TaskUnfocused, onTaskUnfocused)
instance.on(RooCodeEventName.TaskActive, onTaskActive)
instance.on(RooCodeEventName.TaskIdle, onTaskIdle)

// Store the cleanup functions for later removal.
this.taskEventListeners.set(instance, [
() => instance.off(RooCodeEventName.TaskStarted, onTaskStarted),
() => instance.off(RooCodeEventName.TaskCompleted, onTaskCompleted),
() => instance.off(RooCodeEventName.TaskAborted, onTaskAborted),
() => instance.off(RooCodeEventName.TaskFocused, onTaskFocused),
() => instance.off(RooCodeEventName.TaskUnfocused, onTaskUnfocused),
() => instance.off(RooCodeEventName.TaskActive, onTaskActive),
() => instance.off(RooCodeEventName.TaskIdle, onTaskIdle),
])
}

// Initialize Roo Code Cloud profile sync.
this.initializeCloudProfileSync().catch((error) => {
this.log(`Failed to initialize cloud profile sync: ${error}`)
Expand Down Expand Up @@ -297,6 +333,14 @@ export class ClineProvider

task.emit(RooCodeEventName.TaskUnfocused)

// Remove event listeners before clearing the reference.
const cleanupFunctions = this.taskEventListeners.get(task)

if (cleanupFunctions) {
cleanupFunctions.forEach((cleanup) => cleanup())
this.taskEventListeners.delete(task)
}

// Make sure no reference kept, once promises end it will be
// garbage collected.
task = undefined
Expand Down Expand Up @@ -654,12 +698,17 @@ export class ClineProvider
enableCheckpoints,
fuzzyMatchThreshold,
experiments,
cloudUserInfo,
remoteControlEnabled,
} = await this.getState()

if (!ProfileValidator.isProfileAllowed(apiConfiguration, organizationAllowList)) {
throw new OrganizationAllowListViolationError(t("common:errors.violated_organization_allowlist"))
}

// Determine if TaskBridge should be enabled
const enableTaskBridge = isRemoteControlEnabled(cloudUserInfo, remoteControlEnabled)

const task = new Task({
provider: this,
apiConfiguration,
Expand All @@ -673,7 +722,8 @@ export class ClineProvider
rootTask: this.clineStack.length > 0 ? this.clineStack[0] : undefined,
parentTask,
taskNumber: this.clineStack.length + 1,
onCreated: (instance) => this.emit(RooCodeEventName.TaskCreated, instance),
onCreated: this.taskCreationCallback,
enableTaskBridge,
...options,
})

Expand Down Expand Up @@ -738,8 +788,13 @@ export class ClineProvider
enableCheckpoints,
fuzzyMatchThreshold,
experiments,
cloudUserInfo,
remoteControlEnabled,
} = await this.getState()

// Determine if TaskBridge should be enabled
const enableTaskBridge = isRemoteControlEnabled(cloudUserInfo, remoteControlEnabled)

const task = new Task({
provider: this,
apiConfiguration,
Expand All @@ -752,7 +807,8 @@ export class ClineProvider
rootTask: historyItem.rootTask,
parentTask: historyItem.parentTask,
taskNumber: historyItem.number,
onCreated: (instance) => this.emit(RooCodeEventName.TaskCreated, instance),
onCreated: this.taskCreationCallback,
enableTaskBridge,
})

await this.addClineToStack(task)
Expand Down Expand Up @@ -1631,6 +1687,7 @@ export class ClineProvider
includeDiagnosticMessages,
maxDiagnosticMessages,
includeTaskHistoryInEnhance,
remoteControlEnabled,
} = await this.getState()

const telemetryKey = process.env.POSTHOG_API_KEY
Expand Down Expand Up @@ -1758,6 +1815,7 @@ export class ClineProvider
includeDiagnosticMessages: includeDiagnosticMessages ?? true,
maxDiagnosticMessages: maxDiagnosticMessages ?? 50,
includeTaskHistoryInEnhance: includeTaskHistoryInEnhance ?? false,
remoteControlEnabled: remoteControlEnabled ?? false,
}
}

Expand Down Expand Up @@ -1945,6 +2003,8 @@ export class ClineProvider
maxDiagnosticMessages: stateValues.maxDiagnosticMessages ?? 50,
// Add includeTaskHistoryInEnhance setting
includeTaskHistoryInEnhance: stateValues.includeTaskHistoryInEnhance ?? false,
// Add remoteControlEnabled setting
remoteControlEnabled: stateValues.remoteControlEnabled ?? false,
}
}

Expand Down Expand Up @@ -2057,6 +2117,55 @@ export class ClineProvider
return true
}

/**
* Handle remote control enabled/disabled state changes
* Manages ExtensionBridgeService and TaskBridgeService lifecycle
*/
public async handleRemoteControlToggle(enabled: boolean): Promise<void> {
const {
CloudService: CloudServiceImport,
ExtensionBridgeService,
TaskBridgeService,
} = await import("@roo-code/cloud")
const userInfo = CloudServiceImport.instance.getUserInfo()

// Handle ExtensionBridgeService using static method
await ExtensionBridgeService.handleRemoteControlState(userInfo, enabled, this, (message: string) =>
this.log(message),
)

if (isRemoteControlEnabled(userInfo, enabled)) {
// Set up TaskBridgeService for the currently active task if one exists
const currentTask = this.getCurrentCline()
if (currentTask && !currentTask.taskBridgeService) {
try {
currentTask.taskBridgeService = TaskBridgeService.getInstance()
await currentTask.taskBridgeService.subscribeToTask(currentTask)
this.log(`[TaskBridgeService] Subscribed current task ${currentTask.taskId} to TaskBridge`)
} catch (error) {
const message = `[TaskBridgeService#subscribeToTask] ${error instanceof Error ? error.message : String(error)}`
this.log(message)
console.error(message)
}
}
} else {
// Disconnect TaskBridgeService for all tasks in the stack
for (const task of this.clineStack) {
if (task.taskBridgeService) {
try {
await task.taskBridgeService.unsubscribeFromTask(task.taskId)
task.taskBridgeService = undefined
this.log(`[TaskBridgeService] Unsubscribed task ${task.taskId} from TaskBridge`)
} catch (error) {
const message = `[TaskBridgeService#unsubscribeFromTask] for task ${task.taskId}: ${error instanceof Error ? error.message : String(error)}`
this.log(message)
console.error(message)
}
}
}
}
}

/**
* Returns properties to be included in every telemetry event
* This method is called by the telemetry service to get context information
Expand Down
5 changes: 5 additions & 0 deletions src/core/webview/webviewMessageHandler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -906,6 +906,11 @@ export const webviewMessageHandler = async (
await updateGlobalState("enableMcpServerCreation", message.bool ?? true)
await provider.postStateToWebview()
break
case "remoteControlEnabled":
await updateGlobalState("remoteControlEnabled", message.bool ?? false)
await provider.handleRemoteControlToggle(message.bool ?? false)
await provider.postStateToWebview()
break
case "refreshAllMcpServers": {
const mcpHub = provider.getMcpHub()
if (mcpHub) {
Expand Down
Loading
Loading