@@ -95,6 +95,8 @@ import { t } from "../../i18n"
9595import { buildApiHandler } from "../../api"
9696import { forceFullModelDetailsLoad, hasLoadedFullDetails } from "../../api/providers/fetchers/lmstudio"
9797
98+ import { logger } from "../../utils/logging"
99+ import { modeSwitchRisksReasoningIncompatibility } from "../../shared/reasoning-mode-compatibility"
98100import { ContextProxy } from "../config/ContextProxy"
99101import { ProviderSettingsManager } from "../config/ProviderSettingsManager"
100102import { CustomModesManager } from "../config/CustomModesManager"
@@ -1481,14 +1483,68 @@ export class ClineProvider
14811483 /**
14821484 * Handle switching to a new mode, including updating the associated API configuration
14831485 * @param newMode The mode to switch to
1486+ * @param via - The origin of the mode switch:
1487+ * "switch_mode" — explicit switch_mode tool call
1488+ * "new_task_delegation" — delegation via new_task
1489+ * "unknown_bypass" — cannot be attributed to explicit tool call (default)
14841490 */
1485- public async handleModeSwitch ( newMode : Mode ) {
1491+ public async handleModeSwitch(newMode: Mode, via: "switch_mode" | "new_task_delegation" | "unknown_bypass" = "unknown_bypass" ) {
14861492 const task = this.getCurrentTask()
1493+ const fromMode = (await this.getState())?.mode ?? "unknown"
14871494
14881495 if (task) {
14891496 TelemetryService.instance.captureModeSwitch(task.taskId, newMode)
14901497 task.emit(RooCodeEventName.TaskModeSwitched, task.taskId, newMode)
14911498
1499+ // Layer 2 — Orchestration safety: check for reasoning-mode incompatibility
1500+ // when switching modes without proper delegation (only relevant for bypass switches).
1501+ if (via !== "new_task_delegation") {
1502+ try {
1503+ const fromProviderName = task.apiConfiguration?.apiProvider
1504+ const toConfigId = await this.providerSettingsManager.getModeConfigId(newMode)
1505+ const listApiConfig = await this.providerSettingsManager.listConfig()
1506+ const toProfile = toConfigId
1507+ ? listApiConfig.find((c) => c.id === toConfigId)
1508+ : undefined
1509+ const toProviderName = toProfile
1510+ ? (await this.providerSettingsManager.getProfile({ name: toProfile.name })).apiProvider
1511+ : fromProviderName
1512+
1513+ if (
1514+ toProviderName &&
1515+ modeSwitchRisksReasoningIncompatibility(fromProviderName, toProviderName)
1516+ ) {
1517+ // Behavior A — always show visible warning in chat
1518+ await task.say(
1519+ "mode_switch_compatibility_warning",
1520+ JSON.stringify({
1521+ fromMode,
1522+ toMode: newMode,
1523+ fromProvider: fromProviderName,
1524+ toProvider: toProviderName,
1525+ via,
1526+ }),
1527+ undefined,
1528+ undefined,
1529+ undefined,
1530+ undefined,
1531+ { isNonInteractive: true },
1532+ )
1533+
1534+ // Behavior B — optional auto-condense when setting is enabled
1535+ const autoCondense = this.context.workspaceState.get("autoCondenseOnRiskyModeSwitch", false)
1536+ if (autoCondense) {
1537+ await task.condenseContext()
1538+ }
1539+ }
1540+ } catch (innerError) {
1541+ // Non-fatal: log but don't block the mode switch if the check fails.
1542+ this.log(
1543+ \`Mode-switch compatibility check failed: ${innerError instanceof Error ? innerError.message : String(innerError)}\`,
1544+ )
1545+ }
1546+ }
1547+
14921548 try {
14931549 // Update the task history with the new mode first.
14941550 const taskHistoryItem =
0 commit comments