feat: implement enhanced logging feature for troubleshooting

- Add enhancedLoggingEnabled toggle in Settings → Info (About) section
- Create logEnhancedError and logEnhanced utility functions
- Add sanitization for sensitive data in logs
- Integrate enhanced logging into API error handling
- Add yellow warning banner when enhanced logging is enabled
- Add comprehensive test coverage for logging functionality
- Export disposeEnhancedLogging for proper cleanup

Implements #8098
This commit is contained in:
Roo Code 2025-09-17 23:45:23 +00:00
parent a255c95bd0
commit 27558b87d2
11 changed files with 682 additions and 0 deletions

View file

@ -136,6 +136,8 @@ export const globalSettingsSchema = z.object({
telemetrySetting: telemetrySettingsSchema.optional(),
enhancedLoggingEnabled: z.boolean().optional(),
mcpEnabled: z.boolean().optional(),
enableMcpServerCreation: z.boolean().optional(),

View file

@ -73,6 +73,7 @@ import { TerminalRegistry } from "../../integrations/terminal/TerminalRegistry"
// utils
import { calculateApiCostAnthropic } from "../../shared/cost"
import { getWorkspacePath } from "../../utils/path"
import { logEnhancedError } from "../../utils/enhancedLogging"
// prompts
import { formatResponse } from "../prompts/responses"
@ -2182,6 +2183,19 @@ export class Task extends EventEmitter<TaskEvents> implements TaskLike {
// Cline instance to finish aborting (error is thrown here when
// any function in the for loop throws due to this.abort).
if (!this.abandoned) {
// Log enhanced error details if enabled
const provider = this.providerRef.deref()
if (provider && !this.abort) {
logEnhancedError(provider.context, error, {
operation: "API Stream Processing",
provider: this.apiConfiguration.apiProvider,
model: this.api.getModel().id,
taskId: this.taskId,
streamPosition: "during streaming",
assistantMessageLength: assistantMessage.length,
})
}
// If the stream failed, there's various states the task
// could be in (i.e. could have streamed some tools the user
// may have executed), so we just resort to replicating a
@ -2688,6 +2702,20 @@ export class Task extends EventEmitter<TaskEvents> implements TaskLike {
this.isWaitingForFirstChunk = false
const isContextWindowExceededError = checkContextWindowExceededError(error)
// Log enhanced error details if enabled
const provider = this.providerRef.deref()
if (provider) {
logEnhancedError(provider.context, error, {
operation: "API Request (First Chunk)",
provider: this.apiConfiguration.apiProvider,
model: this.api.getModel().id,
taskId: this.taskId,
retryAttempt,
isContextWindowError: isContextWindowExceededError,
contextTokens: this.getTokenUsage().contextTokens,
})
}
// If it's a context window error and we haven't exceeded max retries for this error type
if (isContextWindowExceededError && retryAttempt < MAX_CONTEXT_WINDOW_RETRIES) {
console.warn(

View file

@ -2286,6 +2286,12 @@ export const webviewMessageHandler = async (
await provider.postStateToWebview()
break
}
case "enhancedLoggingEnabled": {
const enabled = message.bool ?? false
await updateGlobalState("enhancedLoggingEnabled", enabled)
await provider.postStateToWebview()
break
}
case "cloudButtonClicked": {
// Navigate to the cloud tab.
provider.postMessageToWebview({ type: "action", action: "cloudButtonClicked" })

View file

@ -86,6 +86,7 @@ export interface WebviewMessage {
| "allowedMaxCost"
| "alwaysAllowSubtasks"
| "alwaysAllowUpdateTodoList"
| "enhancedLoggingEnabled"
| "autoCondenseContext"
| "autoCondenseContextPercent"
| "condensingApiConfigId"

View file

@ -0,0 +1,306 @@
import { describe, it, expect, vi, beforeEach, afterEach } from "vitest"
import * as vscode from "vscode"
import { logEnhancedError, logEnhanced, disposeEnhancedLogging } from "../enhancedLogging"
// Mock the Package module
vi.mock("../shared/package", () => ({
Package: {
outputChannel: "Roo-Code",
},
}))
// Mock vscode module
vi.mock("vscode", () => ({
window: {
createOutputChannel: vi.fn(),
},
OutputChannel: vi.fn(),
}))
describe("enhancedLogging", () => {
let mockContext: vscode.ExtensionContext
let mockOutputChannel: any
let mockGlobalState: any
beforeEach(() => {
// Reset all mocks
vi.clearAllMocks()
// Create mock output channel
mockOutputChannel = {
appendLine: vi.fn(),
append: vi.fn(),
clear: vi.fn(),
show: vi.fn(),
hide: vi.fn(),
dispose: vi.fn(),
replace: vi.fn(),
name: "Roo-Code",
}
// Mock window.createOutputChannel to return our mock
vi.mocked(vscode.window.createOutputChannel).mockReturnValue(mockOutputChannel as any)
// Create mock global state
mockGlobalState = {
get: vi.fn(),
update: vi.fn(),
keys: vi.fn().mockReturnValue([]),
setKeysForSync: vi.fn(),
}
// Create mock context
mockContext = {
globalState: mockGlobalState as any,
subscriptions: [],
workspaceState: {} as any,
extensionUri: {} as any,
extensionPath: "",
asAbsolutePath: vi.fn(),
storagePath: "",
globalStoragePath: "",
logPath: "",
extensionMode: 3,
extension: {} as any,
globalStorageUri: {} as any,
logUri: {} as any,
storageUri: {} as any,
secrets: {} as any,
environmentVariableCollection: {} as any,
languageModelAccessInformation: {} as any,
}
})
afterEach(() => {
// Dispose the output channel to reset module state
disposeEnhancedLogging()
vi.restoreAllMocks()
})
describe("logEnhancedError", () => {
it("should not log when enhanced logging is disabled", () => {
// Arrange
vi.mocked(mockGlobalState.get).mockImplementation((key: string, defaultValue?: any) => {
if (key === "enhancedLoggingEnabled") return false
return defaultValue
})
const error = new Error("Test error")
// Act
logEnhancedError(mockContext, error)
// Assert
expect(vscode.window.createOutputChannel).not.toHaveBeenCalled()
expect(mockOutputChannel.appendLine).not.toHaveBeenCalled()
})
it("should log error details when enhanced logging is enabled", () => {
// Arrange
vi.mocked(mockGlobalState.get).mockImplementation((key: string, defaultValue?: any) => {
if (key === "enhancedLoggingEnabled") return true
return defaultValue
})
const error = new Error("Test error")
error.stack = "Error: Test error\n at test.js:1:1"
// Act
logEnhancedError(mockContext, error, {
operation: "Test Operation",
provider: "test-provider",
})
// Assert
expect(vscode.window.createOutputChannel).toHaveBeenCalledWith("Roo-Code")
expect(mockOutputChannel.appendLine).toHaveBeenCalledWith(
expect.stringContaining("[ENHANCED LOGGING] Error occurred at"),
)
expect(mockOutputChannel.appendLine).toHaveBeenCalledWith("Operation: Test Operation")
expect(mockOutputChannel.appendLine).toHaveBeenCalledWith("Provider: test-provider")
expect(mockOutputChannel.appendLine).toHaveBeenCalledWith("Error Type: Error")
expect(mockOutputChannel.appendLine).toHaveBeenCalledWith("Message: Test error")
expect(mockOutputChannel.show).toHaveBeenCalledWith(true)
})
it("should sanitize sensitive information", () => {
// Arrange
vi.mocked(mockGlobalState.get).mockImplementation((key: string, defaultValue?: any) => {
if (key === "enhancedLoggingEnabled") return true
return defaultValue
})
const error = new Error("API key failed")
// Act
logEnhancedError(mockContext, error, {
request: {
headers: {
authorization: "Bearer sk-abcdef123456",
apikey: "key-123456789",
},
},
})
// Assert
// Check that sensitive data is redacted in the output
const calls = mockOutputChannel.appendLine.mock.calls
// The function should have been called multiple times
expect(calls.length).toBeGreaterThan(0)
const allOutput = calls.map((call: any) => call[0]).join("\n")
expect(allOutput).toContain("[REDACTED")
expect(allOutput).not.toContain("sk-abcdef123456")
expect(allOutput).not.toContain("key-123456789")
})
it("should handle non-Error objects", () => {
// Arrange
vi.mocked(mockGlobalState.get).mockImplementation((key: string, defaultValue?: any) => {
if (key === "enhancedLoggingEnabled") return true
return defaultValue
})
const error = { code: "NETWORK_ERROR", details: "Connection failed" }
// Act
logEnhancedError(mockContext, error)
// Assert
const calls = mockOutputChannel.appendLine.mock.calls
const allOutput = calls.map((call: any) => call[0]).join("\n")
expect(allOutput).toContain("NETWORK_ERROR")
expect(allOutput).toContain("Connection failed")
})
it("should handle string errors", () => {
// Arrange
vi.mocked(mockGlobalState.get).mockImplementation((key: string, defaultValue?: any) => {
if (key === "enhancedLoggingEnabled") return true
return defaultValue
})
const error = "Simple error string"
// Act
logEnhancedError(mockContext, error)
// Assert
expect(mockOutputChannel.appendLine).toHaveBeenCalledWith(
expect.stringContaining("Error Message: Simple error string"),
)
})
it("should include timestamp in logs", () => {
// Arrange
vi.mocked(mockGlobalState.get).mockImplementation((key: string, defaultValue?: any) => {
if (key === "enhancedLoggingEnabled") return true
return defaultValue
})
const error = new Error("Test error")
const dateSpy = vi.spyOn(Date.prototype, "toISOString").mockReturnValue("2024-01-01T12:00:00.000Z")
// Act
logEnhancedError(mockContext, error)
// Assert
expect(mockOutputChannel.appendLine).toHaveBeenCalledWith(
expect.stringContaining("2024-01-01T12:00:00.000Z"),
)
dateSpy.mockRestore()
})
})
describe("logEnhanced", () => {
it("should not log when enhanced logging is disabled", () => {
// Arrange
vi.mocked(mockGlobalState.get).mockImplementation((key: string, defaultValue?: any) => {
if (key === "enhancedLoggingEnabled") return false
return defaultValue
})
// Act
logEnhanced(mockContext, "Test message")
// Assert
expect(vscode.window.createOutputChannel).not.toHaveBeenCalled()
expect(mockOutputChannel.appendLine).not.toHaveBeenCalled()
})
it("should log message with INFO level by default", () => {
// Arrange
vi.mocked(mockGlobalState.get).mockImplementation((key: string, defaultValue?: any) => {
if (key === "enhancedLoggingEnabled") return true
return defaultValue
})
const dateSpy = vi.spyOn(Date.prototype, "toISOString").mockReturnValue("2024-01-01T12:00:00.000Z")
// Act
logEnhanced(mockContext, "Test message")
// Assert
expect(vscode.window.createOutputChannel).toHaveBeenCalledWith("Roo-Code")
expect(mockOutputChannel.appendLine).toHaveBeenCalledWith("[2024-01-01T12:00:00.000Z] [INFO] Test message")
dateSpy.mockRestore()
})
it("should log message with specified level", () => {
// Arrange
vi.mocked(mockGlobalState.get).mockImplementation((key: string, defaultValue?: any) => {
if (key === "enhancedLoggingEnabled") return true
return defaultValue
})
const dateSpy = vi.spyOn(Date.prototype, "toISOString").mockReturnValue("2024-01-01T12:00:00.000Z")
// Act
logEnhanced(mockContext, "Error occurred", "ERROR")
// Assert
expect(mockOutputChannel.appendLine).toHaveBeenCalledWith(
"[2024-01-01T12:00:00.000Z] [ERROR] Error occurred",
)
dateSpy.mockRestore()
})
it("should support different log levels", () => {
// Arrange
vi.mocked(mockGlobalState.get).mockImplementation((key: string, defaultValue?: any) => {
if (key === "enhancedLoggingEnabled") return true
return defaultValue
})
const dateSpy = vi.spyOn(Date.prototype, "toISOString").mockReturnValue("2024-01-01T12:00:00.000Z")
// Act
logEnhanced(mockContext, "Debug info", "DEBUG")
logEnhanced(mockContext, "Warning message", "WARNING")
// Assert
expect(mockOutputChannel.appendLine).toHaveBeenCalledWith("[2024-01-01T12:00:00.000Z] [DEBUG] Debug info")
expect(mockOutputChannel.appendLine).toHaveBeenCalledWith(
"[2024-01-01T12:00:00.000Z] [WARNING] Warning message",
)
dateSpy.mockRestore()
})
})
describe("Output channel management", () => {
it("should reuse the same output channel for multiple logs", () => {
// Arrange
vi.mocked(mockGlobalState.get).mockImplementation((key: string, defaultValue?: any) => {
if (key === "enhancedLoggingEnabled") return true
return defaultValue
})
// Act
logEnhanced(mockContext, "First message")
logEnhanced(mockContext, "Second message")
logEnhancedError(mockContext, new Error("Test error"))
// Assert
expect(vscode.window.createOutputChannel).toHaveBeenCalledTimes(1)
expect(mockOutputChannel.appendLine).toHaveBeenCalled()
// logEnhanced doesn't call show(), only logEnhancedError does
expect(mockOutputChannel.show).toHaveBeenCalledTimes(1)
})
})
})

View file

@ -0,0 +1,250 @@
import * as vscode from "vscode"
import { Package } from "../shared/package"
let outputChannel: vscode.OutputChannel | undefined
/**
* Gets or creates the output channel for enhanced logging
*/
function getOutputChannel(): vscode.OutputChannel {
if (!outputChannel) {
outputChannel = vscode.window.createOutputChannel(Package.outputChannel)
}
return outputChannel
}
/**
* Logs detailed error information to the VS Code Output channel when enhanced logging is enabled
*
* @param context - The vscode.ExtensionContext to access global state
* @param error - The error object or error information to log
* @param additionalContext - Optional additional context information
*/
export function logEnhancedError(
context: vscode.ExtensionContext,
error: unknown,
additionalContext?: {
operation?: string
provider?: string
model?: string
request?: any
response?: any
[key: string]: any
},
): void {
// Check if enhanced logging is enabled
const enhancedLoggingEnabled = context.globalState.get<boolean>("enhancedLoggingEnabled", false)
if (!enhancedLoggingEnabled) {
return
}
const channel = getOutputChannel()
const timestamp = new Date().toISOString()
// Start logging the error
channel.appendLine("")
channel.appendLine("=".repeat(80))
channel.appendLine(`[ENHANCED LOGGING] Error occurred at ${timestamp}`)
channel.appendLine("=".repeat(80))
// Log the operation context if provided
if (additionalContext?.operation) {
channel.appendLine(`Operation: ${additionalContext.operation}`)
}
if (additionalContext?.provider) {
channel.appendLine(`Provider: ${additionalContext.provider}`)
}
if (additionalContext?.model) {
channel.appendLine(`Model: ${additionalContext.model}`)
}
// Log the error details
channel.appendLine("")
channel.appendLine("Error Details:")
channel.appendLine("-".repeat(40))
if (error instanceof Error) {
channel.appendLine(`Error Type: ${error.constructor.name}`)
channel.appendLine(`Message: ${error.message}`)
if (error.stack) {
channel.appendLine("")
channel.appendLine("Stack Trace:")
channel.appendLine(error.stack)
}
// Log any additional error properties
const errorObj = error as any
const standardProps = ["name", "message", "stack"]
const additionalProps = Object.keys(errorObj).filter((key) => !standardProps.includes(key))
if (additionalProps.length > 0) {
channel.appendLine("")
channel.appendLine("Additional Error Properties:")
for (const prop of additionalProps) {
try {
const value = errorObj[prop]
if (value !== undefined && value !== null) {
const displayValue = typeof value === "object" ? JSON.stringify(value, null, 2) : String(value)
channel.appendLine(` ${prop}: ${displayValue}`)
}
} catch (e) {
channel.appendLine(` ${prop}: [Unable to serialize]`)
}
}
}
} else if (typeof error === "string") {
channel.appendLine(`Error Message: ${error}`)
} else if (error !== null && error !== undefined) {
try {
channel.appendLine(`Error Object: ${JSON.stringify(error, null, 2)}`)
} catch (e) {
channel.appendLine(`Error: ${String(error)}`)
}
} else {
channel.appendLine("Unknown error occurred (null or undefined)")
}
// Log request details if provided
if (additionalContext?.request) {
channel.appendLine("")
channel.appendLine("Request Details:")
channel.appendLine("-".repeat(40))
try {
// Sanitize sensitive information from request
const sanitizedRequest = sanitizeForLogging(additionalContext.request)
channel.appendLine(JSON.stringify(sanitizedRequest, null, 2))
} catch (e) {
channel.appendLine("[Unable to serialize request]")
}
}
// Log response details if provided
if (additionalContext?.response) {
channel.appendLine("")
channel.appendLine("Response Details:")
channel.appendLine("-".repeat(40))
try {
// Sanitize sensitive information from response
const sanitizedResponse = sanitizeForLogging(additionalContext.response)
channel.appendLine(JSON.stringify(sanitizedResponse, null, 2))
} catch (e) {
channel.appendLine("[Unable to serialize response]")
}
}
// Log any other additional context
const excludedKeys = ["operation", "provider", "model", "request", "response"]
const otherContext = Object.keys(additionalContext || {})
.filter((key) => !excludedKeys.includes(key))
.reduce((acc, key) => {
acc[key] = additionalContext![key]
return acc
}, {} as any)
if (Object.keys(otherContext).length > 0) {
channel.appendLine("")
channel.appendLine("Additional Context:")
channel.appendLine("-".repeat(40))
try {
channel.appendLine(JSON.stringify(otherContext, null, 2))
} catch (e) {
channel.appendLine("[Unable to serialize additional context]")
}
}
channel.appendLine("")
channel.appendLine("=".repeat(80))
channel.appendLine("")
// Show the output channel to the user
channel.show(true)
}
/**
* Sanitizes an object for logging by removing or masking sensitive information
*/
function sanitizeForLogging(obj: any): any {
if (obj === null || obj === undefined) {
return obj
}
if (typeof obj !== "object") {
return obj
}
if (Array.isArray(obj)) {
return obj.map((item) => sanitizeForLogging(item))
}
const sanitized: any = {}
const sensitiveKeys = [
"apikey",
"api_key",
"apiKey",
"password",
"passwd",
"pwd",
"secret",
"token",
"auth",
"authorization",
"bearer",
"credential",
"credentials",
]
for (const [key, value] of Object.entries(obj)) {
const lowerKey = key.toLowerCase()
// Check if this key contains sensitive information
if (sensitiveKeys.some((sensitive) => lowerKey.includes(sensitive))) {
// Mask the value but show its type and partial info
if (typeof value === "string" && value.length > 0) {
sanitized[key] = `[REDACTED: ${value.slice(0, 4)}...${value.slice(-4)}]`
} else {
sanitized[key] = "[REDACTED]"
}
} else if (typeof value === "object" && value !== null) {
// Recursively sanitize nested objects
sanitized[key] = sanitizeForLogging(value)
} else {
sanitized[key] = value
}
}
return sanitized
}
/**
* Logs a simple enhanced message (not necessarily an error)
*/
export function logEnhanced(
context: vscode.ExtensionContext,
message: string,
level: "INFO" | "WARNING" | "ERROR" | "DEBUG" = "INFO",
): void {
const enhancedLoggingEnabled = context.globalState.get<boolean>("enhancedLoggingEnabled", false)
if (!enhancedLoggingEnabled) {
return
}
const channel = getOutputChannel()
const timestamp = new Date().toISOString()
channel.appendLine(`[${timestamp}] [${level}] ${message}`)
}
/**
* Disposes the output channel if it exists
*/
export function disposeEnhancedLogging(): void {
if (outputChannel) {
outputChannel.dispose()
outputChannel = undefined
}
}

View file

@ -43,6 +43,7 @@ import { useAutoApprovalToggles } from "@src/hooks/useAutoApprovalToggles"
import { CloudUpsellDialog } from "@src/components/cloud/CloudUpsellDialog"
import TelemetryBanner from "../common/TelemetryBanner"
import EnhancedLoggingBanner from "../common/EnhancedLoggingBanner"
import VersionIndicator from "../common/VersionIndicator"
import { useTaskSearch } from "../history/useTaskSearch"
import HistoryPreview from "../history/HistoryPreview"
@ -117,6 +118,7 @@ const ChatViewComponent: React.ForwardRefRenderFunction<ChatViewRef, ChatViewPro
alwaysAllowUpdateTodoList,
customModes,
telemetrySetting,
enhancedLoggingEnabled,
hasSystemPromptOverride,
historyPreviewCollapsed, // Added historyPreviewCollapsed
soundEnabled,
@ -1777,6 +1779,7 @@ const ChatViewComponent: React.ForwardRefRenderFunction<ChatViewRef, ChatViewPro
data-testid="chat-view"
className={isHidden ? "hidden" : "fixed top-0 left-0 right-0 bottom-0 flex flex-col overflow-hidden"}>
{telemetrySetting === "unset" && <TelemetryBanner />}
{enhancedLoggingEnabled && <EnhancedLoggingBanner />}
{(showAnnouncement || showAnnouncementModal) && (
<Announcement
hideAnnouncement={() => {

View file

@ -0,0 +1,53 @@
import { memo } from "react"
import { VSCodeLink } from "@vscode/webview-ui-toolkit/react"
import { useExtensionState } from "@src/context/ExtensionStateContext"
import { vscode } from "@src/utils/vscode"
import { useAppTranslation } from "@src/i18n/TranslationContext"
const EnhancedLoggingBanner = () => {
const { t } = useAppTranslation()
const { enhancedLoggingEnabled, setEnhancedLoggingEnabled } = useExtensionState()
const handleDisable = () => {
setEnhancedLoggingEnabled(false)
vscode.postMessage({ type: "enhancedLoggingEnabled", bool: false })
}
const handleOpenSettings = () => {
window.postMessage({
type: "action",
action: "settingsButtonClicked",
values: { section: "about" },
})
}
if (!enhancedLoggingEnabled) {
return null
}
return (
<div className="relative px-4 py-2.5 pr-10 bg-yellow-500/20 border-b border-yellow-500/30 text-sm leading-normal text-vscode-foreground">
{/* Close button (X) */}
<button
onClick={handleDisable}
className="absolute top-1.5 right-2 bg-transparent border-none text-vscode-foreground cursor-pointer text-2xl p-1 opacity-70 hover:opacity-100 transition-opacity duration-200 leading-none"
aria-label="Disable enhanced logging">
×
</button>
<div className="mb-0.5 font-bold flex items-center gap-2">
<span className="text-yellow-600"></span>
{t("settings:about.enhancedLogging.bannerTitle")}
</div>
<div>
{t("settings:about.enhancedLogging.bannerMessage")}{" "}
<VSCodeLink href="#" onClick={handleOpenSettings}>
{t("settings:about.enhancedLogging.bannerSettings")}
</VSCodeLink>
</div>
</div>
)
}
export default memo(EnhancedLoggingBanner)

View file

@ -11,6 +11,7 @@ import { Package } from "@roo/package"
import { vscode } from "@/utils/vscode"
import { cn } from "@/lib/utils"
import { Button } from "@/components/ui"
import { useExtensionState } from "@/context/ExtensionStateContext"
import { SectionHeader } from "./SectionHeader"
import { Section } from "./Section"
@ -22,6 +23,7 @@ type AboutProps = HTMLAttributes<HTMLDivElement> & {
export const About = ({ telemetrySetting, setTelemetrySetting, className, ...props }: AboutProps) => {
const { t } = useAppTranslation()
const { enhancedLoggingEnabled, setEnhancedLoggingEnabled } = useExtensionState()
return (
<div className={cn("flex flex-col gap-2", className)} {...props}>
@ -57,6 +59,21 @@ export const About = ({ telemetrySetting, setTelemetrySetting, className, ...pro
</p>
</div>
<div>
<VSCodeCheckbox
checked={enhancedLoggingEnabled || false}
onChange={(e: any) => {
const checked = e.target.checked === true
setEnhancedLoggingEnabled(checked)
vscode.postMessage({ type: "enhancedLoggingEnabled", bool: checked })
}}>
{t("settings:footer.enhancedLogging.label")}
</VSCodeCheckbox>
<p className="text-vscode-descriptionForeground text-sm mt-0">
{t("settings:footer.enhancedLogging.description")}
</p>
</div>
<div>
<Trans
i18nKey="settings:footer.feedback"

View file

@ -155,6 +155,8 @@ export interface ExtensionStateContextType extends ExtensionState {
setMaxDiagnosticMessages: (value: number) => void
includeTaskHistoryInEnhance?: boolean
setIncludeTaskHistoryInEnhance: (value: boolean) => void
enhancedLoggingEnabled?: boolean
setEnhancedLoggingEnabled: (value: boolean) => void
}
export const ExtensionStateContext = createContext<ExtensionStateContextType | undefined>(undefined)
@ -280,6 +282,7 @@ export const ExtensionStateContextProvider: React.FC<{ children: React.ReactNode
global: {},
})
const [includeTaskHistoryInEnhance, setIncludeTaskHistoryInEnhance] = useState(true)
const [enhancedLoggingEnabled, setEnhancedLoggingEnabled] = useState(false)
const setListApiConfigMeta = useCallback(
(value: ProviderSettingsEntry[]) => setState((prevState) => ({ ...prevState, listApiConfigMeta: value })),
@ -317,6 +320,10 @@ export const ExtensionStateContextProvider: React.FC<{ children: React.ReactNode
if ((newState as any).includeTaskHistoryInEnhance !== undefined) {
setIncludeTaskHistoryInEnhance((newState as any).includeTaskHistoryInEnhance)
}
// Update enhancedLoggingEnabled if present in state message
if ((newState as any).enhancedLoggingEnabled !== undefined) {
setEnhancedLoggingEnabled((newState as any).enhancedLoggingEnabled)
}
// Handle marketplace data if present in state message
if (newState.marketplaceItems !== undefined) {
setMarketplaceItems(newState.marketplaceItems)
@ -538,6 +545,8 @@ export const ExtensionStateContextProvider: React.FC<{ children: React.ReactNode
},
includeTaskHistoryInEnhance,
setIncludeTaskHistoryInEnhance,
enhancedLoggingEnabled,
setEnhancedLoggingEnabled,
}
return <ExtensionStateContext.Provider value={contextValue}>{children}</ExtensionStateContext.Provider>

View file

@ -801,6 +801,13 @@
"import": "Import",
"export": "Export",
"reset": "Reset"
},
"enhancedLogging": {
"label": "Enhanced logging",
"description": "When enabled, writes detailed failure logs to the VS Code Output panel for troubleshooting. Logs remain local and are not uploaded.",
"bannerTitle": "Enhanced Logging Enabled",
"bannerMessage": "Detailed failure logs are being written to the VS Code Output panel. This may include sensitive information.",
"bannerSettings": "Disable in settings"
}
},
"thinkingBudget": {