fix: forward task configuration through stdin-prompt-stream (#11778)

fix: forward task configuration through stdin-prompt-stream protocol

The stdin-prompt-stream `start` command only accepted `prompt` — any
`configuration` passed via the cloud worker's StartNewTask was silently
dropped. This meant custom modes (e.g. `ask-artifacts`), disabled tools,
and other task-level settings never reached the extension when running
via the CLI harness.

Changes:
- Add optional `configuration` field to the `start` stdin command
- Parse and forward it in `runStdinStreamMode`
- Thread it through `ExtensionHost.runTask` → `newTask` webview message
  → `ClineProvider.createTask` (which already calls `setValues`)
- Add `taskConfiguration` field to `WebviewMessage` type

Backward-compatible: older CLIs ignore the extra field; older workers
that don't send `configuration` trigger no change in behavior.

Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-authored-by: cte <cestreich@gmail.com>
This commit is contained in:
Daniel 2026-02-27 00:41:14 -05:00 committed by GitHub
parent 81047a65b6
commit a7c8275e85
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
5 changed files with 30 additions and 8 deletions

View file

@ -108,7 +108,7 @@ interface WebviewViewProvider {
export interface ExtensionHostInterface extends IExtensionHost<ExtensionHostEventMap> {
client: ExtensionClient
activate(): Promise<void>
runTask(prompt: string, taskId?: string): Promise<void>
runTask(prompt: string, taskId?: string, configuration?: RooCodeSettings): Promise<void>
resumeTask(taskId: string): Promise<void>
sendToExtension(message: WebviewMessage): void
dispose(): Promise<void>
@ -510,8 +510,8 @@ export class ExtensionHost extends EventEmitter implements ExtensionHostInterfac
})
}
public async runTask(prompt: string, taskId?: string): Promise<void> {
this.sendToExtension({ type: "newTask", text: prompt, taskId })
public async runTask(prompt: string, taskId?: string, configuration?: RooCodeSettings): Promise<void> {
this.sendToExtension({ type: "newTask", text: prompt, taskId, taskConfiguration: configuration })
return this.waitForTaskCompletion()
}

View file

@ -1,6 +1,8 @@
import { createInterface } from "readline"
import { randomUUID } from "crypto"
import type { RooCodeSettings } from "@roo-code/types"
import { isRecord } from "@/lib/utils/guards.js"
import type { ExtensionHost } from "@/agent/index.js"
@ -13,7 +15,7 @@ import type { JsonEventEmitter } from "@/agent/json-event-emitter.js"
export type StdinStreamCommandName = "start" | "message" | "cancel" | "ping" | "shutdown"
export type StdinStreamCommand =
| { command: "start"; requestId: string; prompt: string }
| { command: "start"; requestId: string; prompt: string; configuration?: RooCodeSettings }
| { command: "message"; requestId: string; prompt: string }
| { command: "cancel"; requestId: string }
| { command: "ping"; requestId: string }
@ -64,6 +66,10 @@ export function parseStdinStreamCommand(line: string, lineNumber: number): Stdin
throw new Error(`stdin command line ${lineNumber}: "${command}" requires non-empty string "prompt"`)
}
if (command === "start" && isRecord(parsed.configuration)) {
return { command, requestId, prompt: promptRaw, configuration: parsed.configuration as RooCodeSettings }
}
return { command, requestId, prompt: promptRaw }
}
@ -488,7 +494,7 @@ export async function runStdinStreamMode({ host, jsonEmitter, setStreamRequestId
})
activeTaskPromise = host
.runTask(stdinCommand.prompt, latestTaskId)
.runTask(stdinCommand.prompt, latestTaskId, stdinCommand.configuration)
.catch((error) => {
const message = error instanceof Error ? error.message : String(error)

View file

@ -679,6 +679,8 @@ export interface WebviewMessage {
codebaseIndexOpenRouterApiKey?: string
}
updatedSettings?: RooCodeSettings
/** Task configuration applied via `createTask()` when starting a cloud task. */
taskConfiguration?: RooCodeSettings
// Worktree properties
worktreePath?: string
worktreeBranch?: string

View file

@ -2922,6 +2922,16 @@ export class ClineProvider
if (configuration.currentApiConfigName) {
await this.setProviderProfile(configuration.currentApiConfigName)
}
// Register custom modes so the CustomModesManager knows about them.
// setValues writes to global state, but the manager overwrites that
// when it merges .roomodes + global settings on refresh. Persisting
// via updateCustomMode ensures modes survive the merge cycle.
if (configuration.customModes?.length) {
for (const mode of configuration.customModes) {
await this.customModesManager.updateCustomMode(mode.slug, mode)
}
}
}
const { apiConfiguration, organizationAllowList, enableCheckpoints, checkpointTimeout, experiments } =

View file

@ -554,9 +554,13 @@ export const webviewMessageHandler = async (
// task. This essentially creates a fresh slate for the new task.
try {
const resolved = await resolveIncomingImages({ text: message.text, images: message.images })
await provider.createTask(resolved.text, resolved.images, undefined, {
taskId: message.taskId,
})
await provider.createTask(
resolved.text,
resolved.images,
undefined,
{ taskId: message.taskId },
message.taskConfiguration,
)
// Task created successfully - notify the UI to reset
await provider.postMessageToWebview({ type: "invoke", invoke: "newChat" })
} catch (error) {