mirror of
https://github.com/cline/cline.git
synced 2026-10-29 17:42:34 +08:00
Move mode to controller (#4343)
* you know what im talking about * store mode in controller and target sendStateUpdate by controller ID
This commit is contained in:
@@ -16,7 +16,7 @@ import { McpHub } from "@services/mcp/McpHub"
|
||||
import { telemetryService } from "@/services/posthog/telemetry/TelemetryService"
|
||||
import { ApiProvider, ModelInfo } from "@shared/api"
|
||||
import { ChatContent } from "@shared/ChatContent"
|
||||
import { ChatSettings } from "@shared/ChatSettings"
|
||||
import { ChatSettings, StoredChatSettings } from "@shared/ChatSettings"
|
||||
import { ExtensionMessage, ExtensionState, Platform } from "@shared/ExtensionMessage"
|
||||
import { HistoryItem } from "@shared/HistoryItem"
|
||||
import { McpMarketplaceCatalog } from "@shared/mcp"
|
||||
@@ -56,6 +56,7 @@ export class Controller {
|
||||
private postMessage: (message: ExtensionMessage) => Thenable<boolean> | undefined
|
||||
|
||||
private disposables: vscode.Disposable[] = []
|
||||
private mode: "plan" | "act" = "plan" // In-memory plan/act mode state
|
||||
task?: Task
|
||||
workspaceTracker: WorkspaceTracker
|
||||
mcpHub: McpHub
|
||||
@@ -135,7 +136,7 @@ export class Controller {
|
||||
apiConfiguration,
|
||||
autoApprovalSettings,
|
||||
browserSettings,
|
||||
chatSettings,
|
||||
chatSettings: storedChatSettings,
|
||||
shellIntegrationTimeout,
|
||||
terminalReuseEnabled,
|
||||
terminalOutputLineLimit,
|
||||
@@ -145,6 +146,12 @@ export class Controller {
|
||||
taskHistory,
|
||||
} = await getAllExtensionState(this.context)
|
||||
|
||||
// Reconstruct ChatSettings with in-memory mode and stored preferences
|
||||
const chatSettings: ChatSettings = {
|
||||
...storedChatSettings, // Spread stored preferences (preferredLanguage, openAIReasoningEffort)
|
||||
mode: this.mode, // Use in-memory mode (override any stored mode)
|
||||
}
|
||||
|
||||
const NEW_USER_TASK_COUNT_THRESHOLD = 10
|
||||
|
||||
// Check if the user has completed enough tasks to no longer be considered a "new user"
|
||||
@@ -265,6 +272,9 @@ export class Controller {
|
||||
async togglePlanActModeWithChatSettings(chatSettings: ChatSettings, chatContent?: ChatContent): Promise<boolean> {
|
||||
const didSwitchToActMode = chatSettings.mode === "act"
|
||||
|
||||
// Store mode in-memory only
|
||||
this.mode = chatSettings.mode
|
||||
|
||||
// Capture mode switch telemetry | Capture regardless of if we know the taskId
|
||||
telemetryService.captureModeSwitch(this.task?.taskId ?? "0", chatSettings.mode)
|
||||
|
||||
@@ -434,7 +444,9 @@ export class Controller {
|
||||
}
|
||||
}
|
||||
|
||||
await updateWorkspaceState(this.context, "chatSettings", chatSettings)
|
||||
// Save only non-mode properties to workspace storage
|
||||
const { mode, ...persistentChatSettings }: { mode: string } & StoredChatSettings = chatSettings
|
||||
await updateWorkspaceState(this.context, "chatSettings", persistentChatSettings)
|
||||
await this.postStateToWebview()
|
||||
|
||||
if (this.task) {
|
||||
@@ -948,7 +960,7 @@ export class Controller {
|
||||
|
||||
async postStateToWebview() {
|
||||
const state = await this.getStateToPostToWebview()
|
||||
await sendStateUpdate(state)
|
||||
await sendStateUpdate(this.id, state)
|
||||
}
|
||||
|
||||
async getStateToPostToWebview(): Promise<ExtensionState> {
|
||||
@@ -958,7 +970,7 @@ export class Controller {
|
||||
taskHistory,
|
||||
autoApprovalSettings,
|
||||
browserSettings,
|
||||
chatSettings,
|
||||
chatSettings: storedChatSettings,
|
||||
userInfo,
|
||||
mcpMarketplaceEnabled,
|
||||
mcpRichDisplayEnabled,
|
||||
@@ -975,6 +987,12 @@ export class Controller {
|
||||
terminalOutputLineLimit,
|
||||
} = await getAllExtensionState(this.context)
|
||||
|
||||
// Reconstruct ChatSettings with in-memory mode and stored preferences
|
||||
const chatSettings: ChatSettings = {
|
||||
...storedChatSettings, // Spread stored preferences (preferredLanguage, openAIReasoningEffort)
|
||||
mode: this.mode, // Use in-memory mode (override any stored mode)
|
||||
}
|
||||
|
||||
const localClineRulesToggles =
|
||||
((await getWorkspaceState(this.context, "localClineRulesToggles")) as ClineRulesToggles) || {}
|
||||
|
||||
|
||||
@@ -3,8 +3,8 @@ import { Controller } from "../index"
|
||||
import { EmptyRequest } from "../../../shared/proto/common"
|
||||
import { StreamingResponseHandler, getRequestRegistry } from "../grpc-handler"
|
||||
|
||||
// Keep track of active state subscriptions
|
||||
const activeStateSubscriptions = new Set<StreamingResponseHandler>()
|
||||
// Keep track of active state subscriptions by controller ID
|
||||
const activeStateSubscriptions = new Map<string, StreamingResponseHandler>()
|
||||
|
||||
/**
|
||||
* Subscribe to state updates
|
||||
@@ -19,23 +19,25 @@ export async function subscribeToState(
|
||||
responseStream: StreamingResponseHandler,
|
||||
requestId?: string,
|
||||
): Promise<void> {
|
||||
const controllerId = controller.id
|
||||
|
||||
// Send the initial state
|
||||
const initialState = await controller.getStateToPostToWebview()
|
||||
const initialStateJson = JSON.stringify(initialState)
|
||||
|
||||
console.log("[DEBUG] set up state subscription")
|
||||
console.log(`[DEBUG] set up state subscription for controller ${controllerId}`)
|
||||
|
||||
await responseStream({
|
||||
stateJson: initialStateJson,
|
||||
})
|
||||
|
||||
// Add this subscription to the active subscriptions
|
||||
activeStateSubscriptions.add(responseStream)
|
||||
// Add this subscription to the active subscriptions with the controller ID
|
||||
activeStateSubscriptions.set(controllerId, responseStream)
|
||||
|
||||
// Register cleanup when the connection is closed
|
||||
const cleanup = () => {
|
||||
activeStateSubscriptions.delete(responseStream)
|
||||
console.log("[DEBUG] Cleaned up state subscription")
|
||||
activeStateSubscriptions.delete(controllerId)
|
||||
console.log(`[DEBUG] Cleaned up state subscription for controller ${controllerId}`)
|
||||
}
|
||||
|
||||
// Register the cleanup function with the request registry if we have a requestId
|
||||
@@ -45,30 +47,31 @@ export async function subscribeToState(
|
||||
}
|
||||
|
||||
/**
|
||||
* Send a state update to all active subscribers
|
||||
* Send a state update to a specific controller's subscription
|
||||
* @param controllerId The ID of the controller to send the state to
|
||||
* @param state The state to send
|
||||
*/
|
||||
export async function sendStateUpdate(state: any): Promise<void> {
|
||||
const stateJson = JSON.stringify(state)
|
||||
export async function sendStateUpdate(controllerId: string, state: any): Promise<void> {
|
||||
// Get the subscription for this specific controller
|
||||
const responseStream = activeStateSubscriptions.get(controllerId)
|
||||
|
||||
// Send the update to all active subscribers
|
||||
const promises = Array.from(activeStateSubscriptions).map(async (responseStream) => {
|
||||
try {
|
||||
// The issue might be that we're not properly formatting the response
|
||||
// Let's ensure we're sending a properly formatted State message
|
||||
await responseStream(
|
||||
{
|
||||
stateJson,
|
||||
},
|
||||
false, // Not the last message
|
||||
)
|
||||
console.log("[DEBUG] sending followup state", stateJson.length, "chars")
|
||||
} catch (error) {
|
||||
console.error("Error sending state update:", error)
|
||||
// Remove the subscription if there was an error
|
||||
activeStateSubscriptions.delete(responseStream)
|
||||
}
|
||||
})
|
||||
if (!responseStream) {
|
||||
console.log(`[DEBUG] No active state subscription for controller ${controllerId}`)
|
||||
return
|
||||
}
|
||||
|
||||
await Promise.all(promises)
|
||||
try {
|
||||
const stateJson = JSON.stringify(state)
|
||||
await responseStream(
|
||||
{
|
||||
stateJson,
|
||||
},
|
||||
false, // Not the last message
|
||||
)
|
||||
console.log(`[DEBUG] sending followup state to controller ${controllerId}`, stateJson.length, "chars")
|
||||
} catch (error) {
|
||||
console.error(`Error sending state update to controller ${controllerId}:`, error)
|
||||
// Remove the subscription if there was an error
|
||||
activeStateSubscriptions.delete(controllerId)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,7 +7,7 @@ import { ApiConfiguration, ApiProvider, BedrockModelId, ModelInfo } from "@share
|
||||
import { HistoryItem } from "@shared/HistoryItem"
|
||||
import { AutoApprovalSettings } from "@shared/AutoApprovalSettings"
|
||||
import { BrowserSettings } from "@shared/BrowserSettings"
|
||||
import { ChatSettings } from "@shared/ChatSettings"
|
||||
import { ChatSettings, StoredChatSettings } from "@shared/ChatSettings"
|
||||
import { TelemetrySetting } from "@shared/TelemetrySetting"
|
||||
import { UserInfo } from "@shared/UserInfo"
|
||||
import { ClineRulesToggles } from "@shared/cline-rules"
|
||||
@@ -173,6 +173,28 @@ export async function migrateCustomInstructionsToGlobalRules(context: vscode.Ext
|
||||
}
|
||||
}
|
||||
|
||||
export async function cleanupModeFromWorkspaceStorage(context: vscode.ExtensionContext) {
|
||||
try {
|
||||
// Get current chatSettings from workspace storage
|
||||
const chatSettings = (await getWorkspaceState(context, "chatSettings")) as any
|
||||
|
||||
if (chatSettings && typeof chatSettings === "object" && "mode" in chatSettings) {
|
||||
console.log("Cleaning up mode from workspace storage...")
|
||||
|
||||
// Remove mode property from chatSettings
|
||||
const { mode, ...cleanedChatSettings } = chatSettings
|
||||
|
||||
// Save cleaned chatSettings back to workspace storage
|
||||
await updateWorkspaceState(context, "chatSettings", cleanedChatSettings)
|
||||
|
||||
console.log("Successfully removed mode from workspace storage chatSettings")
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Failed to cleanup mode from workspace storage:", error)
|
||||
// Continue execution - migration failure shouldn't break extension startup
|
||||
}
|
||||
}
|
||||
|
||||
export async function getAllExtensionState(context: vscode.ExtensionContext) {
|
||||
const [
|
||||
isNewUser,
|
||||
@@ -360,7 +382,7 @@ export async function getAllExtensionState(context: vscode.ExtensionContext) {
|
||||
previousModeSapAiCoreResourceGroup,
|
||||
previousModeSapAiCoreModelId,
|
||||
] = await Promise.all([
|
||||
getWorkspaceState(context, "chatSettings") as Promise<ChatSettings | undefined>,
|
||||
getWorkspaceState(context, "chatSettings") as Promise<StoredChatSettings | undefined>,
|
||||
getWorkspaceState(context, "apiProvider") as Promise<ApiProvider | undefined>,
|
||||
getWorkspaceState(context, "apiModelId") as Promise<string | undefined>,
|
||||
getWorkspaceState(context, "thinkingBudgetTokens") as Promise<number | undefined>,
|
||||
|
||||
+8
-1
@@ -22,7 +22,11 @@ import { WebviewProviderType as WebviewProviderTypeEnum } from "@shared/proto/ui
|
||||
import { WebviewProviderType } from "./shared/webview/types"
|
||||
import { sendHistoryButtonClickedEvent } from "./core/controller/ui/subscribeToHistoryButtonClicked"
|
||||
import { sendAccountButtonClickedEvent } from "./core/controller/ui/subscribeToAccountButtonClicked"
|
||||
import { migratePlanActGlobalToWorkspaceStorage, migrateCustomInstructionsToGlobalRules } from "./core/storage/state"
|
||||
import {
|
||||
migratePlanActGlobalToWorkspaceStorage,
|
||||
migrateCustomInstructionsToGlobalRules,
|
||||
cleanupModeFromWorkspaceStorage,
|
||||
} from "./core/storage/state"
|
||||
|
||||
import { sendFocusChatInputEvent } from "./core/controller/ui/subscribeToFocusChatInput"
|
||||
import { FileContextTracker } from "./core/context/context-tracking/FileContextTracker"
|
||||
@@ -60,6 +64,9 @@ export async function activate(context: vscode.ExtensionContext) {
|
||||
// Migrate custom instructions to global Cline rules (one-time cleanup)
|
||||
await migrateCustomInstructionsToGlobalRules(context)
|
||||
|
||||
// Clean up mode from workspace storage (one-time cleanup)
|
||||
await cleanupModeFromWorkspaceStorage(context)
|
||||
|
||||
// Clean up orphaned file context warnings (startup cleanup)
|
||||
await FileContextTracker.cleanupOrphanedWarnings(context)
|
||||
|
||||
|
||||
@@ -8,6 +8,9 @@ export interface ChatSettings {
|
||||
|
||||
export type PartialChatSettings = Partial<ChatSettings>
|
||||
|
||||
// Type for chat settings stored in workspace (excludes in-memory mode)
|
||||
export type StoredChatSettings = Omit<ChatSettings, "mode">
|
||||
|
||||
export const DEFAULT_CHAT_SETTINGS: ChatSettings = {
|
||||
mode: "act",
|
||||
preferredLanguage: "English",
|
||||
|
||||
Reference in New Issue
Block a user