mirror of
https://github.com/RooVetGit/Roo-Code.git
synced 2026-08-28 05:27:24 +00:00
feat: add support for custom instruction file paths
- Add customInstructionPaths field to global settings schema - Implement loadCustomInstructionFiles function with support for: - Single file paths - Directory paths (loads all .md and .txt files) - Glob patterns for flexible file selection - Add security validation to prevent loading files outside workspace - Support parent directory access for monorepo scenarios - Integrate custom instructions into system prompt generation - Pass customInstructionPaths through state management - Add comprehensive unit tests for the new functionality Fixes #6168
This commit is contained in:
parent
ff1f4f0398
commit
04b7823751
6 changed files with 487 additions and 0 deletions
|
|
@ -148,6 +148,12 @@ export const globalSettingsSchema = z.object({
|
|||
hasOpenedModeSelector: z.boolean().optional(),
|
||||
lastModeExportPath: z.string().optional(),
|
||||
lastModeImportPath: z.string().optional(),
|
||||
|
||||
/**
|
||||
* Custom instruction file paths, directories, or glob patterns.
|
||||
* Supports single files, directories, glob patterns, and parent directory paths.
|
||||
*/
|
||||
customInstructionPaths: z.array(z.string()).optional(),
|
||||
})
|
||||
|
||||
export type GlobalSettings = z.infer<typeof globalSettingsSchema>
|
||||
|
|
|
|||
341
src/core/prompts/sections/__tests__/custom-instructions.test.ts
Normal file
341
src/core/prompts/sections/__tests__/custom-instructions.test.ts
Normal file
|
|
@ -0,0 +1,341 @@
|
|||
import { describe, it, expect, vi, beforeEach } from "vitest"
|
||||
import * as path from "path"
|
||||
|
||||
// Mock modules before importing the function to test
|
||||
vi.mock("fs/promises", () => ({
|
||||
default: {},
|
||||
stat: vi.fn(),
|
||||
readFile: vi.fn(),
|
||||
readdir: vi.fn(),
|
||||
}))
|
||||
|
||||
vi.mock("glob", () => ({
|
||||
glob: vi.fn().mockResolvedValue([]),
|
||||
}))
|
||||
|
||||
// Import after mocking
|
||||
import { loadCustomInstructionFiles } from "../custom-instructions"
|
||||
import * as fs from "fs/promises"
|
||||
import { glob } from "glob"
|
||||
|
||||
describe("loadCustomInstructionFiles", () => {
|
||||
const mockCwd = "/workspace/project"
|
||||
|
||||
beforeEach(() => {
|
||||
vi.clearAllMocks()
|
||||
// Reset console.warn mock
|
||||
vi.spyOn(console, "warn").mockImplementation(() => {})
|
||||
})
|
||||
|
||||
describe("basic functionality", () => {
|
||||
it("should return empty string when customPaths is undefined", async () => {
|
||||
const result = await loadCustomInstructionFiles(mockCwd, undefined)
|
||||
expect(result).toBe("")
|
||||
})
|
||||
|
||||
it("should return empty string when customPaths is empty array", async () => {
|
||||
const result = await loadCustomInstructionFiles(mockCwd, [])
|
||||
expect(result).toBe("")
|
||||
})
|
||||
})
|
||||
|
||||
describe("single file loading", () => {
|
||||
it("should load content from a single file", async () => {
|
||||
const filePath = ".github/copilot-instructions.md"
|
||||
const resolvedPath = path.resolve(mockCwd, filePath)
|
||||
const mockContent = "Custom Instructions"
|
||||
|
||||
// Setup mocks
|
||||
vi.mocked(fs.stat).mockResolvedValue({
|
||||
isFile: () => true,
|
||||
isDirectory: () => false,
|
||||
size: 100,
|
||||
} as any)
|
||||
|
||||
vi.mocked(fs.readFile).mockResolvedValue(mockContent)
|
||||
|
||||
const result = await loadCustomInstructionFiles(mockCwd, [filePath])
|
||||
|
||||
expect(fs.stat).toHaveBeenCalledWith(resolvedPath)
|
||||
expect(fs.readFile).toHaveBeenCalledWith(resolvedPath, "utf-8")
|
||||
expect(result).toContain("Custom Instructions")
|
||||
expect(result).toContain(`# Custom instructions from ${filePath}:`)
|
||||
})
|
||||
|
||||
it("should skip files that exceed size limit", async () => {
|
||||
vi.mocked(fs.stat).mockResolvedValue({
|
||||
isFile: () => true,
|
||||
isDirectory: () => false,
|
||||
size: 2 * 1024 * 1024, // 2MB
|
||||
} as any)
|
||||
|
||||
const result = await loadCustomInstructionFiles(mockCwd, ["large-file.md"])
|
||||
|
||||
expect(fs.readFile).not.toHaveBeenCalled()
|
||||
expect(result).toBe("")
|
||||
})
|
||||
|
||||
it("should skip files without allowed extensions", async () => {
|
||||
vi.mocked(fs.stat).mockResolvedValue({
|
||||
isFile: () => true,
|
||||
isDirectory: () => false,
|
||||
size: 100,
|
||||
} as any)
|
||||
|
||||
const result = await loadCustomInstructionFiles(mockCwd, ["file.js"])
|
||||
|
||||
expect(fs.readFile).not.toHaveBeenCalled()
|
||||
expect(result).toBe("")
|
||||
})
|
||||
})
|
||||
|
||||
describe("directory loading", () => {
|
||||
it("should load all .md and .txt files from a directory", async () => {
|
||||
const dirPath = ".roocode"
|
||||
const resolvedPath = path.resolve(mockCwd, dirPath)
|
||||
|
||||
// Mock stat to identify as directory
|
||||
vi.mocked(fs.stat)
|
||||
.mockResolvedValueOnce({
|
||||
isFile: () => false,
|
||||
isDirectory: () => true,
|
||||
} as any)
|
||||
// For individual files
|
||||
.mockResolvedValue({
|
||||
isFile: () => true,
|
||||
isDirectory: () => false,
|
||||
size: 100,
|
||||
} as any)
|
||||
|
||||
// Mock readdir
|
||||
vi.mocked(fs.readdir).mockResolvedValue([
|
||||
{ name: "instructions.md", isFile: () => true, isDirectory: () => false },
|
||||
{ name: "rules.txt", isFile: () => true, isDirectory: () => false },
|
||||
{ name: "image.png", isFile: () => true, isDirectory: () => false },
|
||||
] as any)
|
||||
|
||||
// Mock readFile
|
||||
vi.mocked(fs.readFile).mockResolvedValueOnce("Instructions content").mockResolvedValueOnce("Rules content")
|
||||
|
||||
const result = await loadCustomInstructionFiles(mockCwd, [dirPath])
|
||||
|
||||
expect(fs.readdir).toHaveBeenCalledWith(resolvedPath, { withFileTypes: true })
|
||||
expect(fs.readFile).toHaveBeenCalledTimes(2) // Only .md and .txt files
|
||||
expect(result).toContain("Instructions content")
|
||||
expect(result).toContain("Rules content")
|
||||
})
|
||||
})
|
||||
|
||||
describe("glob patterns", () => {
|
||||
it("should expand glob patterns and load matching files", async () => {
|
||||
const pattern = "docs/**/*.md"
|
||||
const matches = [path.resolve(mockCwd, "docs/api.md"), path.resolve(mockCwd, "docs/guide.md")]
|
||||
|
||||
// Mock stat to fail first (triggering glob)
|
||||
vi.mocked(fs.stat)
|
||||
.mockRejectedValueOnce(new Error("Not found"))
|
||||
.mockResolvedValue({
|
||||
isFile: () => true,
|
||||
isDirectory: () => false,
|
||||
size: 100,
|
||||
} as any)
|
||||
|
||||
// Mock glob
|
||||
vi.mocked(glob).mockResolvedValue(matches)
|
||||
|
||||
// Mock readFile
|
||||
vi.mocked(fs.readFile).mockResolvedValueOnce("API docs").mockResolvedValueOnce("Guide docs")
|
||||
|
||||
const result = await loadCustomInstructionFiles(mockCwd, [pattern])
|
||||
|
||||
expect(glob).toHaveBeenCalledWith(pattern, {
|
||||
cwd: mockCwd,
|
||||
absolute: true,
|
||||
nodir: true,
|
||||
ignore: ["**/node_modules/**", "**/.git/**"],
|
||||
})
|
||||
expect(result).toContain("API docs")
|
||||
expect(result).toContain("Guide docs")
|
||||
})
|
||||
})
|
||||
|
||||
describe("security validation", () => {
|
||||
it("should reject paths outside workspace and parent", async () => {
|
||||
const outsidePath = "/etc/passwd"
|
||||
|
||||
vi.mocked(fs.stat).mockResolvedValue({
|
||||
isFile: () => true,
|
||||
isDirectory: () => false,
|
||||
size: 50,
|
||||
} as any)
|
||||
|
||||
const result = await loadCustomInstructionFiles(mockCwd, [outsidePath])
|
||||
|
||||
expect(fs.readFile).not.toHaveBeenCalled()
|
||||
expect(console.warn).toHaveBeenCalledWith(
|
||||
expect.stringContaining("Skipping instruction path outside allowed directories"),
|
||||
)
|
||||
expect(result).toBe("")
|
||||
})
|
||||
|
||||
it("should allow parent directory access", async () => {
|
||||
const parentPath = "../parent-instructions.md"
|
||||
// This resolves to /workspace/parent-instructions.md which is in the parent dir
|
||||
const resolvedPath = "/workspace/parent-instructions.md"
|
||||
|
||||
vi.mocked(fs.stat).mockResolvedValue({
|
||||
isFile: () => true,
|
||||
isDirectory: () => false,
|
||||
size: 50,
|
||||
} as any)
|
||||
|
||||
vi.mocked(fs.readFile).mockResolvedValue("Parent instructions")
|
||||
|
||||
const result = await loadCustomInstructionFiles(mockCwd, [parentPath])
|
||||
|
||||
expect(result).toContain("Parent instructions")
|
||||
expect(result).toContain("# Custom instructions from ../parent-instructions.md:")
|
||||
})
|
||||
|
||||
it("should allow workspace subdirectories", async () => {
|
||||
const subPath = "src/rules/custom.md"
|
||||
|
||||
vi.mocked(fs.stat).mockResolvedValue({
|
||||
isFile: () => true,
|
||||
isDirectory: () => false,
|
||||
size: 50,
|
||||
} as any)
|
||||
|
||||
vi.mocked(fs.readFile).mockResolvedValue("Subdirectory content")
|
||||
|
||||
const result = await loadCustomInstructionFiles(mockCwd, [subPath])
|
||||
|
||||
expect(result).toContain("Subdirectory content")
|
||||
expect(result).toContain("# Custom instructions from src/rules/custom.md:")
|
||||
})
|
||||
})
|
||||
|
||||
describe("error handling", () => {
|
||||
it("should handle file read errors gracefully", async () => {
|
||||
vi.mocked(fs.stat).mockResolvedValue({
|
||||
isFile: () => true,
|
||||
isDirectory: () => false,
|
||||
size: 100,
|
||||
} as any)
|
||||
|
||||
vi.mocked(fs.readFile).mockRejectedValue(new Error("Permission denied"))
|
||||
|
||||
const result = await loadCustomInstructionFiles(mockCwd, ["protected.md"])
|
||||
|
||||
expect(console.warn).toHaveBeenCalledWith(
|
||||
expect.stringContaining("Error reading instruction file"),
|
||||
expect.any(Error),
|
||||
)
|
||||
expect(result).toBe("")
|
||||
})
|
||||
|
||||
it("should handle glob errors gracefully", async () => {
|
||||
vi.mocked(fs.stat).mockRejectedValue(new Error("Not found"))
|
||||
|
||||
vi.mocked(glob).mockRejectedValue(new Error("Invalid pattern"))
|
||||
|
||||
const result = await loadCustomInstructionFiles(mockCwd, ["[invalid"])
|
||||
|
||||
expect(console.warn).toHaveBeenCalledWith(
|
||||
expect.stringContaining("Error processing custom instruction path"),
|
||||
expect.any(Error),
|
||||
)
|
||||
expect(result).toBe("")
|
||||
})
|
||||
|
||||
it("should handle directory read errors gracefully", async () => {
|
||||
vi.mocked(fs.stat).mockResolvedValue({
|
||||
isFile: () => false,
|
||||
isDirectory: () => true,
|
||||
} as any)
|
||||
|
||||
vi.mocked(fs.readdir).mockRejectedValue(new Error("Permission denied"))
|
||||
|
||||
const result = await loadCustomInstructionFiles(mockCwd, [".roocode"])
|
||||
|
||||
// Should fall through to glob attempt
|
||||
expect(result).toBe("")
|
||||
})
|
||||
})
|
||||
|
||||
describe("content formatting", () => {
|
||||
it("should format content with proper headers", async () => {
|
||||
vi.mocked(fs.stat).mockResolvedValue({
|
||||
isFile: () => true,
|
||||
isDirectory: () => false,
|
||||
size: 50,
|
||||
} as any)
|
||||
vi.mocked(fs.readFile).mockResolvedValue("Test content")
|
||||
|
||||
const result = await loadCustomInstructionFiles(mockCwd, ["test.md"])
|
||||
|
||||
expect(result).toContain("# Custom instructions from test.md:")
|
||||
expect(result).toContain("Test content")
|
||||
})
|
||||
|
||||
it("should combine multiple files with proper formatting", async () => {
|
||||
vi.mocked(fs.stat).mockResolvedValue({
|
||||
isFile: () => true,
|
||||
isDirectory: () => false,
|
||||
size: 50,
|
||||
} as any)
|
||||
vi.mocked(fs.readFile).mockResolvedValueOnce("Content 1").mockResolvedValueOnce("Content 2")
|
||||
|
||||
const result = await loadCustomInstructionFiles(mockCwd, ["file1.md", "file2.md"])
|
||||
|
||||
expect(result).toMatch(
|
||||
/# Custom instructions from file1\.md:[\s\S]*Content 1[\s\S]*# Custom instructions from file2\.md:[\s\S]*Content 2/,
|
||||
)
|
||||
})
|
||||
})
|
||||
|
||||
describe("mixed path types", () => {
|
||||
it("should handle a mix of files, directories, and globs", async () => {
|
||||
// Setup for single file
|
||||
vi.mocked(fs.stat)
|
||||
.mockResolvedValueOnce({
|
||||
isFile: () => true,
|
||||
isDirectory: () => false,
|
||||
size: 50,
|
||||
} as any)
|
||||
// Setup for directory
|
||||
.mockResolvedValueOnce({
|
||||
isFile: () => false,
|
||||
isDirectory: () => true,
|
||||
} as any)
|
||||
// Setup for glob (will fail stat, triggering glob)
|
||||
.mockRejectedValueOnce(new Error("Not found"))
|
||||
// For directory file and glob file
|
||||
.mockResolvedValue({
|
||||
isFile: () => true,
|
||||
isDirectory: () => false,
|
||||
size: 50,
|
||||
} as any)
|
||||
|
||||
// Setup directory listing
|
||||
vi.mocked(fs.readdir).mockResolvedValue([
|
||||
{ name: "dir-file.md", isFile: () => true, isDirectory: () => false },
|
||||
] as any)
|
||||
|
||||
// Setup glob
|
||||
vi.mocked(glob).mockResolvedValue([path.resolve(mockCwd, "docs/glob-file.md")])
|
||||
|
||||
// Setup file reads
|
||||
vi.mocked(fs.readFile)
|
||||
.mockResolvedValueOnce("Single file content")
|
||||
.mockResolvedValueOnce("Directory file content")
|
||||
.mockResolvedValueOnce("Glob file content")
|
||||
|
||||
const result = await loadCustomInstructionFiles(mockCwd, ["single.md", ".roocode", "docs/**/*.md"])
|
||||
|
||||
expect(result).toContain("Single file content")
|
||||
expect(result).toContain("Directory file content")
|
||||
expect(result).toContain("Glob file content")
|
||||
})
|
||||
})
|
||||
})
|
||||
|
|
@ -2,6 +2,7 @@ import fs from "fs/promises"
|
|||
import path from "path"
|
||||
import * as os from "os"
|
||||
import { Dirent } from "fs"
|
||||
import { glob } from "glob"
|
||||
|
||||
import { isLanguage } from "@roo-code/types"
|
||||
|
||||
|
|
@ -261,6 +262,128 @@ async function loadAgentRulesFile(cwd: string): Promise<string> {
|
|||
return ""
|
||||
}
|
||||
|
||||
/**
|
||||
* Load custom instruction files from specified paths, directories, or glob patterns
|
||||
* @param cwd Current working directory for resolving relative paths
|
||||
* @param customPaths Array of paths, directories, or glob patterns
|
||||
* @returns Combined content from all loaded instruction files
|
||||
*/
|
||||
export async function loadCustomInstructionFiles(cwd: string, customPaths?: string[]): Promise<string> {
|
||||
if (!customPaths || customPaths.length === 0) {
|
||||
return ""
|
||||
}
|
||||
|
||||
const loadedFiles: Array<{ path: string; content: string }> = []
|
||||
const MAX_FILE_SIZE = 1024 * 1024 // 1MB limit per file
|
||||
const ALLOWED_EXTENSIONS = [".md", ".markdown", ".txt"]
|
||||
|
||||
for (const pathPattern of customPaths) {
|
||||
try {
|
||||
// Resolve the path relative to the workspace
|
||||
const resolvedPath = path.isAbsolute(pathPattern) ? pathPattern : path.resolve(cwd, pathPattern)
|
||||
|
||||
// Security validation: ensure path is within workspace or parent directories
|
||||
const normalizedPath = path.normalize(resolvedPath)
|
||||
const normalizedCwd = path.normalize(cwd)
|
||||
const parentDir = path.dirname(normalizedCwd)
|
||||
|
||||
// Allow files within workspace or one level up (for monorepo scenarios)
|
||||
if (!normalizedPath.startsWith(normalizedCwd) && !normalizedPath.startsWith(parentDir)) {
|
||||
console.warn(`Skipping instruction path outside allowed directories: ${pathPattern}`)
|
||||
continue
|
||||
}
|
||||
|
||||
// Check if it's a directory
|
||||
try {
|
||||
const stats = await fs.stat(resolvedPath)
|
||||
if (stats.isDirectory()) {
|
||||
// Load all markdown and text files from the directory
|
||||
const dirFiles = await fs.readdir(resolvedPath, { withFileTypes: true })
|
||||
for (const file of dirFiles) {
|
||||
if (file.isFile()) {
|
||||
const ext = path.extname(file.name).toLowerCase()
|
||||
if (ALLOWED_EXTENSIONS.includes(ext)) {
|
||||
const filePath = path.join(resolvedPath, file.name)
|
||||
const content = await loadSingleFile(filePath, MAX_FILE_SIZE)
|
||||
if (content) {
|
||||
loadedFiles.push({ path: filePath, content })
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
continue
|
||||
} else if (stats.isFile()) {
|
||||
// It's a single file
|
||||
const ext = path.extname(resolvedPath).toLowerCase()
|
||||
if (ALLOWED_EXTENSIONS.includes(ext)) {
|
||||
const content = await loadSingleFile(resolvedPath, MAX_FILE_SIZE)
|
||||
if (content) {
|
||||
loadedFiles.push({ path: resolvedPath, content })
|
||||
}
|
||||
}
|
||||
continue
|
||||
}
|
||||
} catch (err) {
|
||||
// File/directory doesn't exist, might be a glob pattern
|
||||
}
|
||||
|
||||
// Try as a glob pattern
|
||||
const matches = await glob(pathPattern, {
|
||||
cwd: cwd,
|
||||
absolute: true,
|
||||
nodir: true,
|
||||
ignore: ["**/node_modules/**", "**/.git/**"],
|
||||
})
|
||||
|
||||
for (const matchedFile of matches) {
|
||||
const ext = path.extname(matchedFile).toLowerCase()
|
||||
if (ALLOWED_EXTENSIONS.includes(ext)) {
|
||||
// Apply same security validation
|
||||
const normalizedMatch = path.normalize(matchedFile)
|
||||
if (!normalizedMatch.startsWith(normalizedCwd) && !normalizedMatch.startsWith(parentDir)) {
|
||||
console.warn(`Skipping matched file outside allowed directories: ${matchedFile}`)
|
||||
continue
|
||||
}
|
||||
|
||||
const content = await loadSingleFile(matchedFile, MAX_FILE_SIZE)
|
||||
if (content) {
|
||||
loadedFiles.push({ path: matchedFile, content })
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
console.warn(`Error processing custom instruction path "${pathPattern}":`, error)
|
||||
}
|
||||
}
|
||||
|
||||
// Combine all loaded content with file headers
|
||||
if (loadedFiles.length === 0) {
|
||||
return ""
|
||||
}
|
||||
|
||||
return loadedFiles
|
||||
.map((file) => `# Custom instructions from ${path.relative(cwd, file.path)}:\n${file.content}`)
|
||||
.join("\n\n")
|
||||
}
|
||||
|
||||
/**
|
||||
* Load a single file with size validation
|
||||
*/
|
||||
async function loadSingleFile(filePath: string, maxSize: number): Promise<string | null> {
|
||||
try {
|
||||
const stats = await fs.stat(filePath)
|
||||
if (stats.size > maxSize) {
|
||||
console.warn(`Skipping large instruction file (>${maxSize} bytes): ${filePath}`)
|
||||
return null
|
||||
}
|
||||
const content = await fs.readFile(filePath, "utf-8")
|
||||
return content.trim()
|
||||
} catch (error) {
|
||||
console.warn(`Error reading instruction file "${filePath}":`, error)
|
||||
return null
|
||||
}
|
||||
}
|
||||
|
||||
export async function addCustomInstructions(
|
||||
modeCustomInstructions: string,
|
||||
globalCustomInstructions: string,
|
||||
|
|
@ -270,6 +393,7 @@ export async function addCustomInstructions(
|
|||
language?: string
|
||||
rooIgnoreInstructions?: string
|
||||
settings?: SystemPromptSettings
|
||||
customInstructionPaths?: string[]
|
||||
} = {},
|
||||
): Promise<string> {
|
||||
const sections = []
|
||||
|
|
@ -366,6 +490,14 @@ export async function addCustomInstructions(
|
|||
sections.push(`Rules:\n\n${rules.join("\n\n")}`)
|
||||
}
|
||||
|
||||
// Load custom instruction files from paths
|
||||
if (options.customInstructionPaths && options.customInstructionPaths.length > 0) {
|
||||
const customFileContent = await loadCustomInstructionFiles(cwd, options.customInstructionPaths)
|
||||
if (customFileContent && customFileContent.trim()) {
|
||||
sections.push(`Custom Instruction Files:\n\n${customFileContent.trim()}`)
|
||||
}
|
||||
}
|
||||
|
||||
const joinedSections = sections.join("\n\n")
|
||||
|
||||
return joinedSections
|
||||
|
|
|
|||
|
|
@ -62,6 +62,7 @@ async function generatePrompt(
|
|||
settings?: SystemPromptSettings,
|
||||
todoList?: TodoItem[],
|
||||
modelId?: string,
|
||||
customInstructionPaths?: string[],
|
||||
): Promise<string> {
|
||||
if (!context) {
|
||||
throw new Error("Extension context is required for generating system prompt")
|
||||
|
|
@ -128,6 +129,7 @@ ${await addCustomInstructions(baseInstructions, globalCustomInstructions || "",
|
|||
language: language ?? formatLanguage(vscode.env.language),
|
||||
rooIgnoreInstructions,
|
||||
settings,
|
||||
customInstructionPaths,
|
||||
})}`
|
||||
|
||||
return basePrompt
|
||||
|
|
@ -153,6 +155,7 @@ export const SYSTEM_PROMPT = async (
|
|||
settings?: SystemPromptSettings,
|
||||
todoList?: TodoItem[],
|
||||
modelId?: string,
|
||||
customInstructionPaths?: string[],
|
||||
): Promise<string> => {
|
||||
if (!context) {
|
||||
throw new Error("Extension context is required for generating system prompt")
|
||||
|
|
@ -191,6 +194,7 @@ export const SYSTEM_PROMPT = async (
|
|||
language: language ?? formatLanguage(vscode.env.language),
|
||||
rooIgnoreInstructions,
|
||||
settings,
|
||||
customInstructionPaths,
|
||||
},
|
||||
)
|
||||
|
||||
|
|
@ -225,5 +229,6 @@ ${customInstructions}`
|
|||
settings,
|
||||
todoList,
|
||||
modelId,
|
||||
customInstructionPaths,
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2196,6 +2196,7 @@ export class Task extends EventEmitter<TaskEvents> implements TaskLike {
|
|||
customModes,
|
||||
customModePrompts,
|
||||
customInstructions,
|
||||
customInstructionPaths,
|
||||
experiments,
|
||||
enableMcpServerCreation,
|
||||
browserToolEnabled,
|
||||
|
|
@ -2239,6 +2240,7 @@ export class Task extends EventEmitter<TaskEvents> implements TaskLike {
|
|||
},
|
||||
undefined, // todoList
|
||||
this.api.getModel().id,
|
||||
customInstructionPaths,
|
||||
)
|
||||
})()
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1974,6 +1974,7 @@ export class ClineProvider
|
|||
apiConfiguration: providerSettings,
|
||||
lastShownAnnouncementId: stateValues.lastShownAnnouncementId,
|
||||
customInstructions: stateValues.customInstructions,
|
||||
customInstructionPaths: stateValues.customInstructionPaths,
|
||||
apiModelId: stateValues.apiModelId,
|
||||
alwaysAllowReadOnly: stateValues.alwaysAllowReadOnly ?? false,
|
||||
alwaysAllowReadOnlyOutsideWorkspace: stateValues.alwaysAllowReadOnlyOutsideWorkspace ?? false,
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue