Compare commits

...

1 Commits

Author SHA1 Message Date
celestial-vault 4e30c68f9c migrate and remove custom instructions 2025-06-10 23:08:14 -07:00
17 changed files with 61 additions and 176 deletions
+8 -9
View File
@@ -70,15 +70,14 @@ message AutoApprovalSettingsRequest {
message UpdateSettingsRequest {
Metadata metadata = 1;
optional ApiConfiguration api_configuration = 2;
optional string custom_instructions_setting = 3;
optional string telemetry_setting = 4;
optional bool plan_act_separate_models_setting = 5;
optional bool enable_checkpoints_setting = 6;
optional bool mcp_marketplace_enabled = 7;
optional ChatSettings chat_settings = 8;
optional int64 shell_integration_timeout = 9;
optional bool terminal_reuse_enabled = 10;
optional bool mcp_responses_collapsed = 11;
optional string telemetry_setting = 3;
optional bool plan_act_separate_models_setting = 4;
optional bool enable_checkpoints_setting = 5;
optional bool mcp_marketplace_enabled = 6;
optional ChatSettings chat_settings = 7;
optional int64 shell_integration_timeout = 8;
optional bool terminal_reuse_enabled = 9;
optional bool mcp_responses_collapsed = 10;
}
// Complete API Configuration message
-12
View File
@@ -130,7 +130,6 @@ export class Controller {
await this.clearTask() // ensures that an existing task doesn't exist before starting a new one, although this shouldn't be possible since user must clear task before starting a new one
const {
apiConfiguration,
customInstructions,
autoApprovalSettings,
browserSettings,
chatSettings,
@@ -172,7 +171,6 @@ export class Controller {
shellIntegrationTimeout,
terminalReuseEnabled ?? true,
enableCheckpointsSetting ?? true,
customInstructions,
task,
images,
files,
@@ -468,14 +466,6 @@ export class Controller {
}
}
async updateCustomInstructions(instructions?: string) {
// User may be clearing the field
await updateGlobalState(this.context, "customInstructions", instructions || undefined)
if (this.task) {
this.task.customInstructions = instructions || undefined
}
}
// Account
async fetchUserCreditsData() {
@@ -957,7 +947,6 @@ export class Controller {
const {
apiConfiguration,
lastShownAnnouncementId,
customInstructions,
taskHistory,
autoApprovalSettings,
browserSettings,
@@ -989,7 +978,6 @@ export class Controller {
return {
version: this.context.extension?.packageJSON?.version ?? "",
apiConfiguration,
customInstructions,
uriScheme: vscode.env.uriScheme,
currentTaskItem: this.task?.taskId ? (taskHistory || []).find((item) => item.id === this.task?.taskId) : undefined,
checkpointTrackerErrorMessage: this.task?.checkpointTrackerErrorMessage,
@@ -25,11 +25,6 @@ export async function updateSettings(controller: Controller, request: UpdateSett
}
}
// Update custom instructions
if (request.customInstructionsSetting !== undefined) {
await controller.updateCustomInstructions(request.customInstructionsSetting)
}
// Update telemetry setting
if (request.telemetrySetting) {
await controller.updateTelemetrySetting(request.telemetrySetting as TelemetrySetting)
@@ -663,7 +663,6 @@ You accomplish a given task iteratively, breaking it down into clear steps and w
}
export function addUserInstructions(
settingsCustomInstructions?: string,
globalClineRulesFileInstructions?: string,
localClineRulesFileInstructions?: string,
localCursorRulesFileInstructions?: string,
@@ -676,9 +675,6 @@ export function addUserInstructions(
if (preferredLanguageInstructions) {
customInstructions += preferredLanguageInstructions + "\n\n"
}
if (settingsCustomInstructions) {
customInstructions += settingsCustomInstructions + "\n\n"
}
if (globalClineRulesFileInstructions) {
customInstructions += globalClineRulesFileInstructions + "\n\n"
}
-4
View File
@@ -651,7 +651,6 @@ You accomplish a given task iteratively, breaking it down into clear steps and w
export function addUserInstructions(
settingsCustomInstructions?: string,
globalClineRulesFileInstructions?: string,
localClineRulesFileInstructions?: string,
localCursorRulesFileInstructions?: string,
@@ -664,9 +663,6 @@ export function addUserInstructions(
if (preferredLanguageInstructions) {
customInstructions += preferredLanguageInstructions + "\n\n"
}
if (settingsCustomInstructions) {
customInstructions += settingsCustomInstructions + "\n\n"
}
if (globalClineRulesFileInstructions) {
customInstructions += globalClineRulesFileInstructions + "\n\n"
}
-1
View File
@@ -33,7 +33,6 @@ export type GlobalStateKey =
| "vertexProjectId"
| "vertexRegion"
| "lastShownAnnouncementId"
| "customInstructions"
| "taskHistory"
| "openAiBaseUrl"
| "openAiModelId"
+48 -3
View File
@@ -11,6 +11,9 @@ import { ChatSettings } from "@shared/ChatSettings"
import { TelemetrySetting } from "@shared/TelemetrySetting"
import { UserInfo } from "@shared/UserInfo"
import { ClineRulesToggles } from "@shared/cline-rules"
import { ensureRulesDirectoryExists } from "./disk"
import fs from "fs/promises"
import path from "path"
/*
Storage
https://dev.to/kompotkot/how-to-use-secretstorage-in-your-vscode-extensions-2hco
@@ -125,6 +128,51 @@ async function migrateEnableCheckpointsSetting(enableCheckpointsSettingRaw: bool
return enableCheckpointsSettingRaw ?? true
}
export async function migrateCustomInstructionsToGlobalRules(context: vscode.ExtensionContext) {
try {
const customInstructions = (await context.globalState.get("customInstructions")) as string | undefined
if (customInstructions?.trim()) {
console.log("Migrating custom instructions to global Cline rules...")
// Create global .clinerules directory if it doesn't exist
const globalRulesDir = await ensureRulesDirectoryExists()
// Use a fixed filename for custom instructions
const migrationFileName = "custom_instructions.md"
const migrationFilePath = path.join(globalRulesDir, migrationFileName)
try {
// Check if file already exists to determine if we should append
let existingContent = ""
try {
existingContent = await fs.readFile(migrationFilePath, "utf8")
} catch (readError) {
// File doesn't exist, which is fine
}
// Append or create the file with custom instructions
const contentToWrite = existingContent
? `${existingContent}\n\n---\n\n${customInstructions.trim()}`
: customInstructions.trim()
await fs.writeFile(migrationFilePath, contentToWrite)
console.log(`Successfully ${existingContent ? "appended to" : "created"} migration file: ${migrationFilePath}`)
} catch (fileError) {
console.error("Failed to write migration file:", fileError)
return
}
// Remove customInstructions from global state only after successful file creation
await context.globalState.update("customInstructions", undefined)
console.log("Successfully migrated custom instructions to global Cline rules")
}
} catch (error) {
console.error("Failed to migrate custom instructions to global rules:", error)
// Continue execution - migration failure shouldn't break extension startup
}
}
export async function getAllExtensionState(context: vscode.ExtensionContext) {
const [
isNewUser,
@@ -161,7 +209,6 @@ export async function getAllExtensionState(context: vscode.ExtensionContext) {
azureApiVersion,
openRouterProviderSorting,
lastShownAnnouncementId,
customInstructions,
taskHistory,
autoApprovalSettings,
browserSettings,
@@ -225,7 +272,6 @@ export async function getAllExtensionState(context: vscode.ExtensionContext) {
getGlobalState(context, "azureApiVersion") as Promise<string | undefined>,
getGlobalState(context, "openRouterProviderSorting") as Promise<string | undefined>,
getGlobalState(context, "lastShownAnnouncementId") as Promise<string | undefined>,
getGlobalState(context, "customInstructions") as Promise<string | undefined>,
getGlobalState(context, "taskHistory") as Promise<HistoryItem[] | undefined>,
getGlobalState(context, "autoApprovalSettings") as Promise<AutoApprovalSettings | undefined>,
getGlobalState(context, "browserSettings") as Promise<BrowserSettings | undefined>,
@@ -425,7 +471,6 @@ export async function getAllExtensionState(context: vscode.ExtensionContext) {
},
isNewUser: isNewUser ?? true,
lastShownAnnouncementId,
customInstructions,
taskHistory,
autoApprovalSettings: autoApprovalSettings || DEFAULT_AUTO_APPROVAL_SETTINGS, // default value can be 0 or empty string
globalClineRulesToggles: globalClineRulesToggles || {},
+1 -6
View File
@@ -144,7 +144,6 @@ export class Task {
browserSession: BrowserSession
contextManager: ContextManager
private didEditFile: boolean = false
customInstructions?: string
autoApprovalSettings: AutoApprovalSettings
browserSettings: BrowserSettings
chatSettings: ChatSettings
@@ -204,7 +203,6 @@ export class Task {
shellIntegrationTimeout: number,
terminalReuseEnabled: boolean,
enableCheckpointsSetting: boolean,
customInstructions?: string,
task?: string,
images?: string[],
files?: string[],
@@ -227,7 +225,6 @@ export class Task {
this.browserSession = new BrowserSession(context, browserSettings)
this.contextManager = new ContextManager()
this.diffViewProvider = new DiffViewProvider(cwd)
this.customInstructions = customInstructions
this.autoApprovalSettings = autoApprovalSettings
this.browserSettings = browserSettings
this.chatSettings = chatSettings
@@ -1642,7 +1639,6 @@ export class Task {
const isClaude4ModelFamily = await this.isClaude4ModelFamily()
let systemPrompt = await SYSTEM_PROMPT(cwd, supportsBrowserUse, this.mcpHub, this.browserSettings, isClaude4ModelFamily)
let settingsCustomInstructions = this.customInstructions?.trim()
await this.migratePreferredLanguageToolSetting()
const preferredLanguage = getLanguageKey(this.chatSettings.preferredLanguage as LanguageDisplay)
const preferredLanguageInstructions =
@@ -1670,7 +1666,6 @@ export class Task {
}
if (
settingsCustomInstructions ||
globalClineRulesFileInstructions ||
localClineRulesFileInstructions ||
localCursorRulesFileInstructions ||
@@ -1681,7 +1676,6 @@ export class Task {
) {
// altering the system prompt mid-task will break the prompt cache, but in the grand scheme this will not change often so it's better to not pollute user messages with it the way we have to with <potentially relevant details>
const userInstructions = addUserInstructions(
settingsCustomInstructions,
globalClineRulesFileInstructions,
localClineRulesFileInstructions,
localCursorRulesFileInstructions,
@@ -1690,6 +1684,7 @@ export class Task {
clineIgnoreInstructions,
preferredLanguageInstructions,
)
console.log("[INSTRUCTIONS] User instructions:", userInstructions)
systemPrompt += userInstructions
}
const contextManagementMetadata = await this.contextManager.getNewContextMessagesAndMetadata(
-7
View File
@@ -18,13 +18,6 @@ The Cline extension exposes an API that can be used by other extensions. To use
if (cline) {
// Now you can use the API
// Set custom instructions
await cline.setCustomInstructions("Talk like a pirate")
// Get custom instructions
const instructions = await cline.getCustomInstructions()
console.log("Current custom instructions:", instructions)
// Start a new task with an initial message
await cline.startNewTask("Hello, Cline! Let's make a new project...")
-64
View File
@@ -65,59 +65,6 @@ describe("ClineAPI Core Functionality", () => {
sandbox.restore()
})
describe("setCustomInstructions", () => {
it("should update custom instructions in controller", async () => {
const testInstructions = "Test custom instructions"
await api.setCustomInstructions(testInstructions)
// Verify controller method was called
sinon.assert.calledOnce(mockController.updateCustomInstructions)
sinon.assert.calledWith(mockController.updateCustomInstructions, testInstructions)
// Verify output channel was updated
sinon.assert.calledWith(mockOutputChannel.appendLine, "Custom instructions set")
})
it("should handle empty instructions", async () => {
await api.setCustomInstructions("")
sinon.assert.calledWith(mockController.updateCustomInstructions, "")
sinon.assert.calledWith(mockOutputChannel.appendLine, "Custom instructions set")
})
it("should handle very long instructions", async () => {
const longInstructions = "a".repeat(10000)
await api.setCustomInstructions(longInstructions)
sinon.assert.calledWith(mockController.updateCustomInstructions, longInstructions)
})
})
describe("getCustomInstructions", () => {
it("should retrieve custom instructions from state", async () => {
const testInstructions = "Retrieved instructions"
// The real implementation uses getGlobalState from the state module
getGlobalStateStub.resolves(testInstructions)
const result = await api.getCustomInstructions()
result!.should.equal(testInstructions)
sinon.assert.calledWith(getGlobalStateStub, mockController.context, "customInstructions")
})
it("should return undefined when no instructions set", async () => {
// The real implementation uses getGlobalState from the state module
getGlobalStateStub.resolves(undefined)
const result = await api.getCustomInstructions()
should.not.exist(result)
sinon.assert.calledWith(getGlobalStateStub, mockController.context, "customInstructions")
})
})
describe("startNewTask", () => {
it("should clear existing task and start new one with description", async () => {
const taskDescription = "Create a test function"
@@ -260,17 +207,6 @@ describe("ClineAPI Core Functionality", () => {
})
describe("Error Handling", () => {
it("should handle errors in setCustomInstructions", async () => {
mockController.updateCustomInstructions.rejects(new Error("Update failed"))
try {
await api.setCustomInstructions("test")
should.fail("", "", "Should have thrown an error", "")
} catch (error: any) {
error.message.should.equal("Update failed")
}
})
it("should handle errors in task initialization", async () => {
mockController.initTask.rejects(new Error("Init failed"))
-12
View File
@@ -1,16 +1,4 @@
export interface ClineAPI {
/**
* Sets the custom instructions in the global storage.
* @param value The custom instructions to be saved.
*/
setCustomInstructions(value: string): Promise<void>
/**
* Retrieves the custom instructions from the global storage.
* @returns The saved custom instructions, or undefined if not set.
*/
getCustomInstructions(): Promise<string | undefined>
/**
* Starts a new task with an optional initial message and images.
* @param task Optional initial task message.
-9
View File
@@ -7,15 +7,6 @@ import { WebviewProviderType as WebviewProviderTypeEnum } from "@shared/proto/ui
export function createClineAPI(outputChannel: vscode.OutputChannel, sidebarController: Controller): ClineAPI {
const api: ClineAPI = {
setCustomInstructions: async (value: string) => {
await sidebarController.updateCustomInstructions(value)
outputChannel.appendLine("Custom instructions set")
},
getCustomInstructions: async () => {
return (await getGlobalState(sidebarController.context, "customInstructions")) as string | undefined
},
startNewTask: async (task?: string, images?: string[]) => {
outputChannel.appendLine("Starting new task")
await sidebarController.clearTask()
+4 -1
View File
@@ -22,7 +22,7 @@ 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 } from "./core/storage/state"
import { migratePlanActGlobalToWorkspaceStorage, migrateCustomInstructionsToGlobalRules } from "./core/storage/state"
import { sendFocusChatInputEvent } from "./core/controller/ui/subscribeToFocusChatInput"
/*
@@ -49,6 +49,9 @@ export async function activate(context: vscode.ExtensionContext) {
// Migrate global storage values to workspace storage (one-time cleanup)
await migratePlanActGlobalToWorkspaceStorage(context)
// Migrate custom instructions to global Cline rules (one-time cleanup)
await migrateCustomInstructionsToGlobalRules(context)
// Version checking for autoupdate notification
const currentVersion = context.extension.packageJSON.version
const previousVersion = context.globalState.get<string>("clineVersion")
-1
View File
@@ -81,7 +81,6 @@ export interface ExtensionState {
checkpointTrackerErrorMessage?: string
clineMessages: ClineMessage[]
currentTaskItem?: HistoryItem
customInstructions?: string
mcpMarketplaceEnabled?: boolean
planActSeparateModelsSetting: boolean
enableCheckpointsSetting?: boolean
-1
View File
@@ -46,7 +46,6 @@ export interface WebviewMessage {
mcpMarketplaceEnabled?: boolean
mcpResponsesCollapsed?: boolean
telemetrySetting?: TelemetrySetting
customInstructionsSetting?: string
mentionsRequestId?: string
query?: string
// For toggleFavoriteModel
@@ -115,8 +115,6 @@ const SettingsView = ({ onDone, targetSection }: SettingsViewProps) => {
const {
apiConfiguration,
version,
customInstructions,
setCustomInstructions,
openRouterModels,
telemetrySetting,
setTelemetrySetting,
@@ -140,7 +138,6 @@ const SettingsView = ({ onDone, targetSection }: SettingsViewProps) => {
// Store the original state to detect changes
const originalState = useRef({
apiConfiguration,
customInstructions,
telemetrySetting,
planActSeparateModelsSetting,
enableCheckpointsSetting,
@@ -163,10 +160,6 @@ const SettingsView = ({ onDone, targetSection }: SettingsViewProps) => {
if (!apiValidationResult && !modelIdValidationResult) {
// vscode.postMessage({ type: "apiConfiguration", apiConfiguration })
// vscode.postMessage({
// type: "customInstructions",
// text: customInstructions,
// })
// vscode.postMessage({
// type: "telemetrySetting",
// text: telemetrySetting,
// })
@@ -184,7 +177,6 @@ const SettingsView = ({ onDone, targetSection }: SettingsViewProps) => {
await StateServiceClient.updateSettings(
UpdateSettingsRequest.create({
planActSeparateModelsSetting,
customInstructionsSetting: customInstructions,
telemetrySetting,
enableCheckpointsSetting,
mcpMarketplaceEnabled,
@@ -215,7 +207,6 @@ const SettingsView = ({ onDone, targetSection }: SettingsViewProps) => {
useEffect(() => {
const hasChanges =
JSON.stringify(apiConfiguration) !== JSON.stringify(originalState.current.apiConfiguration) ||
customInstructions !== originalState.current.customInstructions ||
telemetrySetting !== originalState.current.telemetrySetting ||
planActSeparateModelsSetting !== originalState.current.planActSeparateModelsSetting ||
enableCheckpointsSetting !== originalState.current.enableCheckpointsSetting ||
@@ -228,7 +219,6 @@ const SettingsView = ({ onDone, targetSection }: SettingsViewProps) => {
setHasUnsavedChanges(hasChanges)
}, [
apiConfiguration,
customInstructions,
telemetrySetting,
planActSeparateModelsSetting,
enableCheckpointsSetting,
@@ -246,7 +236,6 @@ const SettingsView = ({ onDone, targetSection }: SettingsViewProps) => {
setIsUnsavedChangesDialogOpen(true)
pendingAction.current = () => {
// Reset all tracked state to original values
setCustomInstructions(originalState.current.customInstructions)
setTelemetrySetting(originalState.current.telemetrySetting)
setPlanActSeparateModelsSetting(originalState.current.planActSeparateModelsSetting)
setChatSettings(originalState.current.chatSettings)
@@ -287,7 +276,6 @@ const SettingsView = ({ onDone, targetSection }: SettingsViewProps) => {
}, [
hasUnsavedChanges,
onDone,
setCustomInstructions,
setTelemetrySetting,
setPlanActSeparateModelsSetting,
setChatSettings,
@@ -585,24 +573,6 @@ const SettingsView = ({ onDone, targetSection }: SettingsViewProps) => {
architect a plan for a cheaper coding model to act on.
</p>
</div>
<div className="mb-[5px]">
<VSCodeTextArea
value={customInstructions ?? ""}
className="w-full"
resize="vertical"
rows={4}
placeholder={
'e.g. "Run unit tests at the end", "Use TypeScript with async/await", "Speak in Spanish"'
}
onInput={(e: any) => setCustomInstructions(e.target?.value ?? "")}>
<span className="font-medium">Custom Instructions</span>
</VSCodeTextArea>
<p className="text-xs mt-[5px] text-[var(--vscode-descriptionForeground)]">
These instructions are added to the end of the system prompt sent with every
request.
</p>
</div>
</Section>
</div>
)}
@@ -52,7 +52,6 @@ interface ExtensionStateContextType extends ExtensionState {
// Setters
setApiConfiguration: (config: ApiConfiguration) => void
setCustomInstructions: (value?: string) => void
setTelemetrySetting: (value: TelemetrySetting) => void
setShowAnnouncement: (value: boolean) => void
setShouldShowAnnouncement: (value: boolean) => void
@@ -688,11 +687,6 @@ export const ExtensionStateContextProvider: React.FC<{
...prevState,
apiConfiguration: value,
})),
setCustomInstructions: (value) =>
setState((prevState) => ({
...prevState,
customInstructions: value,
})),
setTelemetrySetting: (value) =>
setState((prevState) => ({
...prevState,
@@ -760,7 +754,6 @@ export const ExtensionStateContextProvider: React.FC<{
apiConfiguration: state.apiConfiguration
? convertApiConfigurationToProtoApiConfiguration(state.apiConfiguration)
: undefined,
customInstructionsSetting: state.customInstructions,
telemetrySetting: state.telemetrySetting,
planActSeparateModelsSetting: state.planActSeparateModelsSetting,
enableCheckpointsSetting: state.enableCheckpointsSetting,