Skip to content

Commit aad6807

Browse files
committed
feat: add debug buttons to view API and UI history
- Add roo-cline.debug package.json setting (default: false) - Add debug property to ExtensionState for webview - Add Open API History and Open UI History buttons in TaskActions - Buttons appear when debug=true and task has ID - Opens prettified JSON in temporary file for debugging - Add tests for new debug button functionality
1 parent 127ecf6 commit aad6807

File tree

8 files changed

+190
-2
lines changed

8 files changed

+190
-2
lines changed

src/core/webview/ClineProvider.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2101,6 +2101,7 @@ export class ClineProvider
21012101
openRouterImageGenerationSelectedModel,
21022102
openRouterUseMiddleOutTransform,
21032103
featureRoomoteControlEnabled,
2104+
debug: vscode.workspace.getConfiguration(Package.name).get<boolean>("debug", false),
21042105
}
21052106
}
21062107

src/core/webview/webviewMessageHandler.ts

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3043,6 +3043,63 @@ export const webviewMessageHandler = async (
30433043
})
30443044
break
30453045
}
3046+
3047+
case "openDebugApiHistory":
3048+
case "openDebugUiHistory": {
3049+
const currentTask = provider.getCurrentTask()
3050+
if (!currentTask) {
3051+
vscode.window.showErrorMessage("No active task to view history for")
3052+
break
3053+
}
3054+
3055+
try {
3056+
const { getTaskDirectoryPath } = await import("../../utils/storage")
3057+
const globalStoragePath = provider.contextProxy.globalStorageUri.fsPath
3058+
const taskDirPath = await getTaskDirectoryPath(globalStoragePath, currentTask.taskId)
3059+
3060+
const fileName =
3061+
message.type === "openDebugApiHistory" ? "api_conversation_history.json" : "ui_messages.json"
3062+
const sourceFilePath = path.join(taskDirPath, fileName)
3063+
3064+
// Check if file exists
3065+
if (!(await fileExistsAtPath(sourceFilePath))) {
3066+
vscode.window.showErrorMessage(`File not found: ${fileName}`)
3067+
break
3068+
}
3069+
3070+
// Read the source file
3071+
const content = await fs.readFile(sourceFilePath, "utf8")
3072+
let jsonContent: unknown
3073+
3074+
try {
3075+
jsonContent = JSON.parse(content)
3076+
} catch {
3077+
vscode.window.showErrorMessage(`Failed to parse ${fileName}`)
3078+
break
3079+
}
3080+
3081+
// Prettify the JSON
3082+
const prettifiedContent = JSON.stringify(jsonContent, null, 2)
3083+
3084+
// Create a temporary file
3085+
const tmpDir = os.tmpdir()
3086+
const timestamp = Date.now()
3087+
const tempFileName = `roo-debug-${message.type === "openDebugApiHistory" ? "api" : "ui"}-${currentTask.taskId.slice(0, 8)}-${timestamp}.json`
3088+
const tempFilePath = path.join(tmpDir, tempFileName)
3089+
3090+
await fs.writeFile(tempFilePath, prettifiedContent, "utf8")
3091+
3092+
// Open the temp file in VS Code
3093+
const doc = await vscode.workspace.openTextDocument(tempFilePath)
3094+
await vscode.window.showTextDocument(doc, { preview: true })
3095+
} catch (error) {
3096+
const errorMessage = error instanceof Error ? error.message : String(error)
3097+
provider.log(`Error opening debug history: ${errorMessage}`)
3098+
vscode.window.showErrorMessage(`Failed to open debug history: ${errorMessage}`)
3099+
}
3100+
break
3101+
}
3102+
30463103
default: {
30473104
// console.log(`Unhandled message type: ${message.type}`)
30483105
//

src/package.json

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -436,6 +436,11 @@
436436
"minimum": 1,
437437
"maximum": 200,
438438
"description": "%settings.codeIndex.embeddingBatchSize.description%"
439+
},
440+
"roo-cline.debug": {
441+
"type": "boolean",
442+
"default": false,
443+
"description": "%settings.debug.description%"
439444
}
440445
}
441446
}

src/package.nls.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -43,5 +43,6 @@
4343
"settings.useAgentRules.description": "Enable loading of AGENTS.md files for agent-specific rules (see https://agent-rules.org/)",
4444
"settings.apiRequestTimeout.description": "Maximum time in seconds to wait for API responses (0 = no timeout, 1-3600s, default: 600s). Higher values are recommended for local providers like LM Studio and Ollama that may need more processing time.",
4545
"settings.newTaskRequireTodos.description": "Require todos parameter when creating new tasks with the new_task tool",
46-
"settings.codeIndex.embeddingBatchSize.description": "The batch size for embedding operations during code indexing. Adjust this based on your API provider's limits. Default is 60."
46+
"settings.codeIndex.embeddingBatchSize.description": "The batch size for embedding operations during code indexing. Adjust this based on your API provider's limits. Default is 60.",
47+
"settings.debug.description": "Enable debug mode to show additional buttons for viewing API conversation history and UI messages as prettified JSON in temporary files."
4748
}

src/shared/ExtensionMessage.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -358,6 +358,7 @@ export type ExtensionState = Pick<
358358
remoteControlEnabled: boolean
359359
taskSyncEnabled: boolean
360360
featureRoomoteControlEnabled: boolean
361+
debug?: boolean
361362
}
362363

363364
export interface ClineSayTool {

src/shared/WebviewMessage.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -173,6 +173,8 @@ export interface WebviewMessage {
173173
| "showBrowserSessionPanelAtStep"
174174
| "refreshBrowserSessionPanel"
175175
| "browserPanelDidLaunch"
176+
| "openDebugApiHistory"
177+
| "openDebugUiHistory"
176178
text?: string
177179
editedMessageContent?: string
178180
tab?: "settings" | "history" | "mcp" | "modes" | "chat" | "marketplace" | "cloud"

webview-ui/src/components/chat/TaskActions.tsx

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,11 +5,12 @@ import type { HistoryItem } from "@roo-code/types"
55

66
import { vscode } from "@/utils/vscode"
77
import { useCopyToClipboard } from "@/utils/clipboard"
8+
import { useExtensionState } from "@/context/ExtensionStateContext"
89

910
import { DeleteTaskDialog } from "../history/DeleteTaskDialog"
1011
import { ShareButton } from "./ShareButton"
1112
import { CloudTaskButton } from "./CloudTaskButton"
12-
import { CopyIcon, DownloadIcon, Trash2Icon } from "lucide-react"
13+
import { CopyIcon, DownloadIcon, Trash2Icon, FileJsonIcon, MessageSquareCodeIcon } from "lucide-react"
1314
import { LucideIconButton } from "./LucideIconButton"
1415

1516
interface TaskActionsProps {
@@ -21,6 +22,7 @@ export const TaskActions = ({ item, buttonsDisabled }: TaskActionsProps) => {
2122
const [deleteTaskId, setDeleteTaskId] = useState<string | null>(null)
2223
const { t } = useTranslation()
2324
const { copyWithFeedback } = useCopyToClipboard()
25+
const { debug } = useExtensionState()
2426

2527
return (
2628
<div className="flex flex-row items-center -ml-0.5 mt-1 gap-1">
@@ -63,6 +65,20 @@ export const TaskActions = ({ item, buttonsDisabled }: TaskActionsProps) => {
6365
)}
6466
<ShareButton item={item} disabled={false} />
6567
<CloudTaskButton item={item} disabled={buttonsDisabled} />
68+
{debug && item?.id && (
69+
<>
70+
<LucideIconButton
71+
icon={FileJsonIcon}
72+
title="Open API History"
73+
onClick={() => vscode.postMessage({ type: "openDebugApiHistory" })}
74+
/>
75+
<LucideIconButton
76+
icon={MessageSquareCodeIcon}
77+
title="Open UI History"
78+
onClick={() => vscode.postMessage({ type: "openDebugUiHistory" })}
79+
/>
80+
</>
81+
)}
6682
</div>
6783
)
6884
}

webview-ui/src/components/chat/__tests__/TaskActions.spec.tsx

Lines changed: 105 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -380,4 +380,109 @@ describe("TaskActions", () => {
380380
expect(deleteButton).toBeDisabled()
381381
})
382382
})
383+
384+
describe("Debug Buttons", () => {
385+
it("does not render debug buttons when debug is false", () => {
386+
mockUseExtensionState.mockReturnValue({
387+
sharingEnabled: true,
388+
cloudIsAuthenticated: true,
389+
cloudUserInfo: { organizationName: "Test Organization" },
390+
debug: false,
391+
} as any)
392+
393+
render(<TaskActions item={mockItem} buttonsDisabled={false} />)
394+
395+
const apiHistoryButton = screen.queryByLabelText("Open API History")
396+
const uiHistoryButton = screen.queryByLabelText("Open UI History")
397+
398+
expect(apiHistoryButton).not.toBeInTheDocument()
399+
expect(uiHistoryButton).not.toBeInTheDocument()
400+
})
401+
402+
it("does not render debug buttons when debug is undefined", () => {
403+
mockUseExtensionState.mockReturnValue({
404+
sharingEnabled: true,
405+
cloudIsAuthenticated: true,
406+
cloudUserInfo: { organizationName: "Test Organization" },
407+
} as any)
408+
409+
render(<TaskActions item={mockItem} buttonsDisabled={false} />)
410+
411+
const apiHistoryButton = screen.queryByLabelText("Open API History")
412+
const uiHistoryButton = screen.queryByLabelText("Open UI History")
413+
414+
expect(apiHistoryButton).not.toBeInTheDocument()
415+
expect(uiHistoryButton).not.toBeInTheDocument()
416+
})
417+
418+
it("renders debug buttons when debug is true and item has id", () => {
419+
mockUseExtensionState.mockReturnValue({
420+
sharingEnabled: true,
421+
cloudIsAuthenticated: true,
422+
cloudUserInfo: { organizationName: "Test Organization" },
423+
debug: true,
424+
} as any)
425+
426+
render(<TaskActions item={mockItem} buttonsDisabled={false} />)
427+
428+
const apiHistoryButton = screen.getByLabelText("Open API History")
429+
const uiHistoryButton = screen.getByLabelText("Open UI History")
430+
431+
expect(apiHistoryButton).toBeInTheDocument()
432+
expect(uiHistoryButton).toBeInTheDocument()
433+
})
434+
435+
it("does not render debug buttons when debug is true but item has no id", () => {
436+
mockUseExtensionState.mockReturnValue({
437+
sharingEnabled: true,
438+
cloudIsAuthenticated: true,
439+
cloudUserInfo: { organizationName: "Test Organization" },
440+
debug: true,
441+
} as any)
442+
443+
render(<TaskActions item={undefined} buttonsDisabled={false} />)
444+
445+
const apiHistoryButton = screen.queryByLabelText("Open API History")
446+
const uiHistoryButton = screen.queryByLabelText("Open UI History")
447+
448+
expect(apiHistoryButton).not.toBeInTheDocument()
449+
expect(uiHistoryButton).not.toBeInTheDocument()
450+
})
451+
452+
it("sends openDebugApiHistory message when Open API History button is clicked", () => {
453+
mockUseExtensionState.mockReturnValue({
454+
sharingEnabled: true,
455+
cloudIsAuthenticated: true,
456+
cloudUserInfo: { organizationName: "Test Organization" },
457+
debug: true,
458+
} as any)
459+
460+
render(<TaskActions item={mockItem} buttonsDisabled={false} />)
461+
462+
const apiHistoryButton = screen.getByLabelText("Open API History")
463+
fireEvent.click(apiHistoryButton)
464+
465+
expect(mockPostMessage).toHaveBeenCalledWith({
466+
type: "openDebugApiHistory",
467+
})
468+
})
469+
470+
it("sends openDebugUiHistory message when Open UI History button is clicked", () => {
471+
mockUseExtensionState.mockReturnValue({
472+
sharingEnabled: true,
473+
cloudIsAuthenticated: true,
474+
cloudUserInfo: { organizationName: "Test Organization" },
475+
debug: true,
476+
} as any)
477+
478+
render(<TaskActions item={mockItem} buttonsDisabled={false} />)
479+
480+
const uiHistoryButton = screen.getByLabelText("Open UI History")
481+
fireEvent.click(uiHistoryButton)
482+
483+
expect(mockPostMessage).toHaveBeenCalledWith({
484+
type: "openDebugUiHistory",
485+
})
486+
})
487+
})
383488
})

0 commit comments

Comments
 (0)