Skip to content

Commit 6ac68a7

Browse files
committed
first attempt
1 parent f9e85a5 commit 6ac68a7

File tree

5 files changed

+138
-19
lines changed

5 files changed

+138
-19
lines changed

packages/types/src/task.ts

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,13 +10,26 @@ export interface TaskProviderState {
1010
mode?: string
1111
}
1212

13+
export interface InitTaskOptions {
14+
mode_slug?: string
15+
enableDiff?: boolean
16+
enableCheckpoints?: boolean
17+
fuzzyMatchThreshold?: number
18+
consecutiveMistakeLimit?: number
19+
experiments?: any
20+
}
1321
export interface TaskProviderLike {
1422
readonly cwd: string
1523

1624
getCurrentCline(): TaskLike | undefined
1725
getCurrentTaskStack(): string[]
1826

19-
initClineWithTask(text?: string, images?: string[], parentTask?: TaskLike): Promise<TaskLike>
27+
initClineWithTask(
28+
text?: string,
29+
images?: string[],
30+
parentTask?: TaskLike,
31+
options?: InitTaskOptions,
32+
): Promise<TaskLike>
2033
cancelTask(): Promise<void>
2134
clearTask(): Promise<void>
2235
postStateToWebview(): Promise<void>

src/core/task/Task.ts

Lines changed: 15 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -285,15 +285,20 @@ export class Task extends EventEmitter<TaskEvents> implements TaskLike {
285285
}
286286

287287
this.taskId = historyItem ? historyItem.id : crypto.randomUUID()
288-
289-
// Normal use-case is usually retry similar history task with new workspace.
290-
this.workspacePath = parentTask
291-
? parentTask.workspacePath
292-
: getWorkspacePath(path.join(os.homedir(), "Desktop"))
293-
294288
this.instanceId = crypto.randomUUID().slice(0, 8)
295289
this.taskNumber = -1
296290

291+
// Initialize workspacePath FIRST before any code that uses this.cwd
292+
// This MUST happen before creating RooIgnoreController or RooProtectedController
293+
const defaultPath = path.join(os.homedir(), "Desktop")
294+
const workspaceFromVSCode = getWorkspacePath(defaultPath)
295+
296+
// Ensure workspacePath is never undefined or empty - use the VSCode workspace or fallback
297+
// Check for both undefined and empty string from parentTask
298+
const parentWorkspace = parentTask?.workspacePath
299+
this.workspacePath = parentWorkspace && parentWorkspace.trim() !== "" ? parentWorkspace : workspaceFromVSCode
300+
301+
// Now create controllers with properly initialized workspacePath
297302
this.rooIgnoreController = new RooIgnoreController(this.cwd)
298303
this.rooProtectedController = new RooProtectedController(this.cwd)
299304
this.fileContextTracker = new FileContextTracker(provider, this.taskId)
@@ -2468,6 +2473,10 @@ export class Task extends EventEmitter<TaskEvents> implements TaskLike {
24682473
// Getters
24692474

24702475
public get cwd() {
2476+
if (!this.workspacePath) {
2477+
// Return a fallback to prevent crashes
2478+
return path.join(os.homedir(), "Desktop")
2479+
}
24712480
return this.workspacePath
24722481
}
24732482
}

src/core/webview/ClineProvider.ts

Lines changed: 28 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ import {
2525
type TerminalActionPromptType,
2626
type HistoryItem,
2727
type CloudUserInfo,
28+
type InitTaskOptions,
2829
RooCodeEventName,
2930
requestyDefaultModelId,
3031
openRouterDefaultModelId,
@@ -77,7 +78,7 @@ import { forceFullModelDetailsLoad, hasLoadedFullDetails } from "../../api/provi
7778
import { ContextProxy } from "../config/ContextProxy"
7879
import { ProviderSettingsManager } from "../config/ProviderSettingsManager"
7980
import { CustomModesManager } from "../config/CustomModesManager"
80-
import { Task, TaskOptions } from "../task/Task"
81+
import { Task } from "../task/Task"
8182
import { getSystemPromptFilePath } from "../prompts/sections/custom-system-prompt"
8283

8384
import { webviewMessageHandler } from "./webviewMessageHandler"
@@ -698,17 +699,7 @@ export class ClineProvider
698699
// from the stack and the caller is resumed in this way we can have a chain
699700
// of tasks, each one being a sub task of the previous one until the main
700701
// task is finished.
701-
public async initClineWithTask(
702-
text?: string,
703-
images?: string[],
704-
parentTask?: Task,
705-
options: Partial<
706-
Pick<
707-
TaskOptions,
708-
"enableDiff" | "enableCheckpoints" | "fuzzyMatchThreshold" | "consecutiveMistakeLimit" | "experiments"
709-
>
710-
> = {},
711-
) {
702+
public async initClineWithTask(text?: string, images?: string[], parentTask?: Task, options: InitTaskOptions = {}) {
712703
const {
713704
apiConfiguration,
714705
organizationAllowList,
@@ -724,6 +715,31 @@ export class ClineProvider
724715
throw new OrganizationAllowListViolationError(t("common:errors.violated_organization_allowlist"))
725716
}
726717

718+
// Bridge: If a mode_slug is provided by the cloud extension bridge, honor it before creating the task
719+
// This ensures the task initializes with the correct mode and associated provider profile
720+
try {
721+
const modeSlugFromBridge: string | undefined = (options as any)?.mode_slug
722+
if (typeof modeSlugFromBridge === "string" && modeSlugFromBridge.trim().length > 0) {
723+
const customModes = await this.customModesManager.getCustomModes()
724+
const targetMode = getModeBySlug(modeSlugFromBridge, customModes)
725+
if (targetMode) {
726+
// Switch provider/global mode first so Task reads it during initialization
727+
await this.handleModeSwitch(targetMode.slug)
728+
this.log(`[initClineWithTask] Applied mode from bridge: '${targetMode.slug}'`)
729+
} else {
730+
this.log(
731+
`[initClineWithTask] Ignoring invalid mode_slug from bridge: '${modeSlugFromBridge}'. Falling back to current mode.`,
732+
)
733+
}
734+
}
735+
} catch (err) {
736+
this.log(
737+
`[initClineWithTask] Failed to apply mode_slug from bridge: ${
738+
err instanceof Error ? err.message : String(err)
739+
}`,
740+
)
741+
}
742+
727743
const task = new Task({
728744
provider: this,
729745
apiConfiguration,

src/core/webview/__tests__/ClineProvider.spec.ts

Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2206,6 +2206,84 @@ describe("ClineProvider", () => {
22062206
})
22072207
})
22082208
})
2209+
describe("Bridge mode_slug handling", () => {
2210+
let provider: ClineProvider
2211+
let mockContext: vscode.ExtensionContext
2212+
let mockOutputChannel: vscode.OutputChannel
2213+
let mockWebviewView: vscode.WebviewView
2214+
2215+
beforeEach(() => {
2216+
vi.clearAllMocks()
2217+
2218+
mockContext = {
2219+
extensionPath: "/test/path",
2220+
extensionUri: {} as vscode.Uri,
2221+
globalState: {
2222+
get: vi.fn(),
2223+
update: vi.fn(),
2224+
keys: vi.fn().mockReturnValue([]),
2225+
},
2226+
secrets: {
2227+
get: vi.fn(),
2228+
store: vi.fn(),
2229+
delete: vi.fn(),
2230+
},
2231+
subscriptions: [],
2232+
extension: {
2233+
packageJSON: { version: "1.0.0" },
2234+
},
2235+
globalStorageUri: {
2236+
fsPath: "/test/storage/path",
2237+
},
2238+
} as unknown as vscode.ExtensionContext
2239+
2240+
mockOutputChannel = {
2241+
appendLine: vi.fn(),
2242+
clear: vi.fn(),
2243+
dispose: vi.fn(),
2244+
} as unknown as vscode.OutputChannel
2245+
2246+
mockWebviewView = {
2247+
webview: {
2248+
postMessage: vi.fn(),
2249+
html: "",
2250+
options: {},
2251+
onDidReceiveMessage: vi.fn(),
2252+
asWebviewUri: vi.fn(),
2253+
cspSource: "vscode-webview://test-csp-source",
2254+
},
2255+
visible: true,
2256+
onDidDispose: vi.fn(),
2257+
onDidChangeVisibility: vi.fn(),
2258+
} as unknown as vscode.WebviewView
2259+
2260+
provider = new ClineProvider(mockContext, mockOutputChannel, "sidebar", new ContextProxy(mockContext))
2261+
})
2262+
2263+
it("applies mode_slug from bridge options when starting task", async () => {
2264+
await provider.resolveWebviewView(mockWebviewView)
2265+
2266+
// Spy on handleModeSwitch to ensure it's invoked with the bridge-provided mode
2267+
const handleModeSwitchSpy = vi.spyOn(provider, "handleModeSwitch").mockResolvedValue(undefined as any)
2268+
2269+
// Ensure getModeBySlug returns a valid mode for the provided slug
2270+
const { getModeBySlug } = await import("../../../shared/modes")
2271+
vi.mocked(getModeBySlug).mockReturnValueOnce({
2272+
slug: "architect",
2273+
name: "Architect Mode",
2274+
roleDefinition: "You are an architect",
2275+
groups: ["read", "edit"] as any,
2276+
} as any)
2277+
2278+
// Pass mode_slug through the options object (as provided by the bridge package)
2279+
await provider.initClineWithTask("Started from bridge", undefined, undefined, {
2280+
experiments: {},
2281+
mode_slug: "architect",
2282+
} as any)
2283+
2284+
expect(handleModeSwitchSpy).toHaveBeenCalledWith("architect")
2285+
})
2286+
})
22092287

22102288
describe("Project MCP Settings", () => {
22112289
let provider: ClineProvider

src/extension/api.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -97,11 +97,13 @@ export class API extends EventEmitter<RooCodeEvents> implements RooCodeAPI {
9797
text,
9898
images,
9999
newTab,
100+
mode_slug,
100101
}: {
101102
configuration: RooCodeSettings
102103
text?: string
103104
images?: string[]
104105
newTab?: boolean
106+
mode_slug?: string
105107
}) {
106108
let provider: ClineProvider
107109

@@ -150,6 +152,7 @@ export class API extends EventEmitter<RooCodeEvents> implements RooCodeAPI {
150152

151153
const cline = await provider.initClineWithTask(text, images, undefined, {
152154
consecutiveMistakeLimit: Number.MAX_SAFE_INTEGER,
155+
mode_slug,
153156
})
154157

155158
if (!cline) {

0 commit comments

Comments
 (0)