Skip to content

Commit b008353

Browse files
committed
Reapply "Extension bridge (#6677)" (#6729)
This reverts commit c632b22.
1 parent 7ea1ae5 commit b008353

29 files changed

+388
-74
lines changed

pnpm-lock.yaml

Lines changed: 4 additions & 4 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/core/task/Task.ts

Lines changed: 28 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@ import {
3232
isBlockingAsk,
3333
} from "@roo-code/types"
3434
import { TelemetryService } from "@roo-code/telemetry"
35-
import { CloudService } from "@roo-code/cloud"
35+
import { CloudService, TaskBridgeService } from "@roo-code/cloud"
3636

3737
// api
3838
import { ApiHandler, ApiHandlerCreateMessageMetadata, buildApiHandler } from "../../api"
@@ -118,6 +118,7 @@ export type TaskOptions = {
118118
parentTask?: Task
119119
taskNumber?: number
120120
onCreated?: (task: Task) => void
121+
enableTaskBridge?: boolean
121122
}
122123

123124
export class Task extends EventEmitter<TaskEvents> implements TaskLike {
@@ -237,6 +238,9 @@ export class Task extends EventEmitter<TaskEvents> implements TaskLike {
237238
checkpointService?: RepoPerTaskCheckpointService
238239
checkpointServiceInitializing = false
239240

241+
// Task Bridge
242+
taskBridgeService?: TaskBridgeService
243+
240244
// Streaming
241245
isWaitingForFirstChunk = false
242246
isStreaming = false
@@ -268,6 +272,7 @@ export class Task extends EventEmitter<TaskEvents> implements TaskLike {
268272
parentTask,
269273
taskNumber = -1,
270274
onCreated,
275+
enableTaskBridge = false,
271276
}: TaskOptions) {
272277
super()
273278

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

346351
this.toolRepetitionDetector = new ToolRepetitionDetector(this.consecutiveMistakeLimit)
347352

353+
// Initialize TaskBridgeService only if enabled
354+
if (enableTaskBridge) {
355+
this.taskBridgeService = TaskBridgeService.getInstance()
356+
}
357+
348358
onCreated?.(this)
349359

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

933943
private async startTask(task?: string, images?: string[]): Promise<void> {
944+
if (this.taskBridgeService) {
945+
await this.taskBridgeService.initialize()
946+
await this.taskBridgeService.subscribeToTask(this)
947+
}
948+
934949
// `conversationHistory` (for API) and `clineMessages` (for webview)
935950
// need to be in sync.
936951
// If the extension process were killed, then on restart the
@@ -982,6 +997,11 @@ export class Task extends EventEmitter<TaskEvents> implements TaskLike {
982997
}
983998

984999
private async resumeTaskFromHistory() {
1000+
if (this.taskBridgeService) {
1001+
await this.taskBridgeService.initialize()
1002+
await this.taskBridgeService.subscribeToTask(this)
1003+
}
1004+
9851005
const modifiedClineMessages = await this.getSavedClineMessages()
9861006

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

1250+
// Unsubscribe from TaskBridge service.
1251+
if (this.taskBridgeService) {
1252+
this.taskBridgeService
1253+
.unsubscribeFromTask(this.taskId)
1254+
.catch((error) => console.error("Error unsubscribing from task bridge:", error))
1255+
}
1256+
12301257
// Release any terminals associated with this task.
12311258
try {
12321259
// Release any terminals associated with this task.

src/core/webview/ClineProvider.ts

Lines changed: 112 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,6 @@ import {
1717
type ProviderSettings,
1818
type RooCodeSettings,
1919
type ProviderSettingsEntry,
20-
type ProviderSettingsWithId,
2120
type TelemetryProperties,
2221
type TelemetryPropertiesProvider,
2322
type CodeActionId,
@@ -66,6 +65,7 @@ import { fileExistsAtPath } from "../../utils/fs"
6665
import { setTtsEnabled, setTtsSpeed } from "../../utils/tts"
6766
import { getWorkspaceGitInfo } from "../../utils/git"
6867
import { getWorkspacePath } from "../../utils/path"
68+
import { isRemoteControlEnabled } from "../../utils/remoteControl"
6969

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

@@ -112,6 +112,8 @@ export class ClineProvider
112112
protected mcpHub?: McpHub // Change from private to protected
113113
private marketplaceManager: MarketplaceManager
114114
private mdmService?: MdmService
115+
private taskCreationCallback: (task: Task) => void
116+
private taskEventListeners: WeakMap<Task, Array<() => void>> = new WeakMap()
115117

116118
public isViewLaunched = false
117119
public settingsImportedAt?: number
@@ -161,6 +163,40 @@ export class ClineProvider
161163

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

166+
this.taskCreationCallback = (instance: Task) => {
167+
this.emit(RooCodeEventName.TaskCreated, instance)
168+
169+
// Create named listener functions so we can remove them later.
170+
const onTaskStarted = () => this.emit(RooCodeEventName.TaskStarted, instance.taskId)
171+
const onTaskCompleted = (taskId: string, tokenUsage: any, toolUsage: any) =>
172+
this.emit(RooCodeEventName.TaskCompleted, taskId, tokenUsage, toolUsage)
173+
const onTaskAborted = () => this.emit(RooCodeEventName.TaskAborted, instance.taskId)
174+
const onTaskFocused = () => this.emit(RooCodeEventName.TaskFocused, instance.taskId)
175+
const onTaskUnfocused = () => this.emit(RooCodeEventName.TaskUnfocused, instance.taskId)
176+
const onTaskActive = (taskId: string) => this.emit(RooCodeEventName.TaskActive, taskId)
177+
const onTaskIdle = (taskId: string) => this.emit(RooCodeEventName.TaskIdle, taskId)
178+
179+
// Attach the listeners.
180+
instance.on(RooCodeEventName.TaskStarted, onTaskStarted)
181+
instance.on(RooCodeEventName.TaskCompleted, onTaskCompleted)
182+
instance.on(RooCodeEventName.TaskAborted, onTaskAborted)
183+
instance.on(RooCodeEventName.TaskFocused, onTaskFocused)
184+
instance.on(RooCodeEventName.TaskUnfocused, onTaskUnfocused)
185+
instance.on(RooCodeEventName.TaskActive, onTaskActive)
186+
instance.on(RooCodeEventName.TaskIdle, onTaskIdle)
187+
188+
// Store the cleanup functions for later removal.
189+
this.taskEventListeners.set(instance, [
190+
() => instance.off(RooCodeEventName.TaskStarted, onTaskStarted),
191+
() => instance.off(RooCodeEventName.TaskCompleted, onTaskCompleted),
192+
() => instance.off(RooCodeEventName.TaskAborted, onTaskAborted),
193+
() => instance.off(RooCodeEventName.TaskFocused, onTaskFocused),
194+
() => instance.off(RooCodeEventName.TaskUnfocused, onTaskUnfocused),
195+
() => instance.off(RooCodeEventName.TaskActive, onTaskActive),
196+
() => instance.off(RooCodeEventName.TaskIdle, onTaskIdle),
197+
])
198+
}
199+
164200
// Initialize Roo Code Cloud profile sync.
165201
this.initializeCloudProfileSync().catch((error) => {
166202
this.log(`Failed to initialize cloud profile sync: ${error}`)
@@ -296,6 +332,14 @@ export class ClineProvider
296332

297333
task.emit(RooCodeEventName.TaskUnfocused)
298334

335+
// Remove event listeners before clearing the reference.
336+
const cleanupFunctions = this.taskEventListeners.get(task)
337+
338+
if (cleanupFunctions) {
339+
cleanupFunctions.forEach((cleanup) => cleanup())
340+
this.taskEventListeners.delete(task)
341+
}
342+
299343
// Make sure no reference kept, once promises end it will be
300344
// garbage collected.
301345
task = undefined
@@ -652,12 +696,17 @@ export class ClineProvider
652696
enableCheckpoints,
653697
fuzzyMatchThreshold,
654698
experiments,
699+
cloudUserInfo,
700+
remoteControlEnabled,
655701
} = await this.getState()
656702

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

707+
// Determine if TaskBridge should be enabled
708+
const enableTaskBridge = isRemoteControlEnabled(cloudUserInfo, remoteControlEnabled)
709+
661710
const task = new Task({
662711
provider: this,
663712
apiConfiguration,
@@ -671,7 +720,8 @@ export class ClineProvider
671720
rootTask: this.clineStack.length > 0 ? this.clineStack[0] : undefined,
672721
parentTask,
673722
taskNumber: this.clineStack.length + 1,
674-
onCreated: (instance) => this.emit(RooCodeEventName.TaskCreated, instance),
723+
onCreated: this.taskCreationCallback,
724+
enableTaskBridge,
675725
...options,
676726
})
677727

@@ -736,8 +786,13 @@ export class ClineProvider
736786
enableCheckpoints,
737787
fuzzyMatchThreshold,
738788
experiments,
789+
cloudUserInfo,
790+
remoteControlEnabled,
739791
} = await this.getState()
740792

793+
// Determine if TaskBridge should be enabled
794+
const enableTaskBridge = isRemoteControlEnabled(cloudUserInfo, remoteControlEnabled)
795+
741796
const task = new Task({
742797
provider: this,
743798
apiConfiguration,
@@ -750,7 +805,8 @@ export class ClineProvider
750805
rootTask: historyItem.rootTask,
751806
parentTask: historyItem.parentTask,
752807
taskNumber: historyItem.number,
753-
onCreated: (instance) => this.emit(RooCodeEventName.TaskCreated, instance),
808+
onCreated: this.taskCreationCallback,
809+
enableTaskBridge,
754810
})
755811

756812
await this.addClineToStack(task)
@@ -1629,6 +1685,7 @@ export class ClineProvider
16291685
includeDiagnosticMessages,
16301686
maxDiagnosticMessages,
16311687
includeTaskHistoryInEnhance,
1688+
remoteControlEnabled,
16321689
} = await this.getState()
16331690

16341691
const telemetryKey = process.env.POSTHOG_API_KEY
@@ -1756,6 +1813,7 @@ export class ClineProvider
17561813
includeDiagnosticMessages: includeDiagnosticMessages ?? true,
17571814
maxDiagnosticMessages: maxDiagnosticMessages ?? 50,
17581815
includeTaskHistoryInEnhance: includeTaskHistoryInEnhance ?? false,
1816+
remoteControlEnabled: remoteControlEnabled ?? false,
17591817
}
17601818
}
17611819

@@ -1943,6 +2001,8 @@ export class ClineProvider
19432001
maxDiagnosticMessages: stateValues.maxDiagnosticMessages ?? 50,
19442002
// Add includeTaskHistoryInEnhance setting
19452003
includeTaskHistoryInEnhance: stateValues.includeTaskHistoryInEnhance ?? false,
2004+
// Add remoteControlEnabled setting
2005+
remoteControlEnabled: stateValues.remoteControlEnabled ?? false,
19462006
}
19472007
}
19482008

@@ -2055,6 +2115,55 @@ export class ClineProvider
20552115
return true
20562116
}
20572117

2118+
/**
2119+
* Handle remote control enabled/disabled state changes
2120+
* Manages ExtensionBridgeService and TaskBridgeService lifecycle
2121+
*/
2122+
public async handleRemoteControlToggle(enabled: boolean): Promise<void> {
2123+
const {
2124+
CloudService: CloudServiceImport,
2125+
ExtensionBridgeService,
2126+
TaskBridgeService,
2127+
} = await import("@roo-code/cloud")
2128+
const userInfo = CloudServiceImport.instance.getUserInfo()
2129+
2130+
// Handle ExtensionBridgeService using static method
2131+
await ExtensionBridgeService.handleRemoteControlState(userInfo, enabled, this, (message: string) =>
2132+
this.log(message),
2133+
)
2134+
2135+
if (isRemoteControlEnabled(userInfo, enabled)) {
2136+
// Set up TaskBridgeService for the currently active task if one exists
2137+
const currentTask = this.getCurrentCline()
2138+
if (currentTask && !currentTask.taskBridgeService) {
2139+
try {
2140+
currentTask.taskBridgeService = TaskBridgeService.getInstance()
2141+
await currentTask.taskBridgeService.subscribeToTask(currentTask)
2142+
this.log(`[TaskBridgeService] Subscribed current task ${currentTask.taskId} to TaskBridge`)
2143+
} catch (error) {
2144+
const message = `[TaskBridgeService#subscribeToTask] ${error instanceof Error ? error.message : String(error)}`
2145+
this.log(message)
2146+
console.error(message)
2147+
}
2148+
}
2149+
} else {
2150+
// Disconnect TaskBridgeService for all tasks in the stack
2151+
for (const task of this.clineStack) {
2152+
if (task.taskBridgeService) {
2153+
try {
2154+
await task.taskBridgeService.unsubscribeFromTask(task.taskId)
2155+
task.taskBridgeService = undefined
2156+
this.log(`[TaskBridgeService] Unsubscribed task ${task.taskId} from TaskBridge`)
2157+
} catch (error) {
2158+
const message = `[TaskBridgeService#unsubscribeFromTask] for task ${task.taskId}: ${error instanceof Error ? error.message : String(error)}`
2159+
this.log(message)
2160+
console.error(message)
2161+
}
2162+
}
2163+
}
2164+
}
2165+
}
2166+
20582167
/**
20592168
* Returns properties to be included in every telemetry event
20602169
* This method is called by the telemetry service to get context information

src/core/webview/webviewMessageHandler.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -921,6 +921,11 @@ export const webviewMessageHandler = async (
921921
await updateGlobalState("enableMcpServerCreation", message.bool ?? true)
922922
await provider.postStateToWebview()
923923
break
924+
case "remoteControlEnabled":
925+
await updateGlobalState("remoteControlEnabled", message.bool ?? false)
926+
await provider.handleRemoteControlToggle(message.bool ?? false)
927+
await provider.postStateToWebview()
928+
break
924929
case "refreshAllMcpServers": {
925930
const mcpHub = provider.getMcpHub()
926931
if (mcpHub) {

0 commit comments

Comments
 (0)