| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297 |
- import * as vscode from "vscode"
- import delay from "delay"
- import type { CommandId } from "@roo-code/types"
- import { TelemetryService } from "@roo-code/telemetry"
- import { Package } from "../shared/package"
- import { getCommand } from "../utils/commands"
- import { ClineProvider } from "../core/webview/ClineProvider"
- import { ContextProxy } from "../core/config/ContextProxy"
- import { focusPanel } from "../utils/focusPanel"
- import { registerHumanRelayCallback, unregisterHumanRelayCallback, handleHumanRelayResponse } from "./humanRelay"
- import { handleNewTask } from "./handleTask"
- import { CodeIndexManager } from "../services/code-index/manager"
- import { importSettingsWithFeedback } from "../core/config/importExport"
- import { MdmService } from "../services/mdm/MdmService"
- import { t } from "../i18n"
- /**
- * Helper to get the visible ClineProvider instance or log if not found.
- */
- export function getVisibleProviderOrLog(outputChannel: vscode.OutputChannel): ClineProvider | undefined {
- const visibleProvider = ClineProvider.getVisibleInstance()
- if (!visibleProvider) {
- outputChannel.appendLine("Cannot find any visible Roo Code instances.")
- return undefined
- }
- return visibleProvider
- }
- // Store panel references in both modes
- let sidebarPanel: vscode.WebviewView | undefined = undefined
- let tabPanel: vscode.WebviewPanel | undefined = undefined
- /**
- * Get the currently active panel
- * @returns WebviewPanel或WebviewView
- */
- export function getPanel(): vscode.WebviewPanel | vscode.WebviewView | undefined {
- return tabPanel || sidebarPanel
- }
- /**
- * Set panel references
- */
- export function setPanel(
- newPanel: vscode.WebviewPanel | vscode.WebviewView | undefined,
- type: "sidebar" | "tab",
- ): void {
- if (type === "sidebar") {
- sidebarPanel = newPanel as vscode.WebviewView
- tabPanel = undefined
- } else {
- tabPanel = newPanel as vscode.WebviewPanel
- sidebarPanel = undefined
- }
- }
- export type RegisterCommandOptions = {
- context: vscode.ExtensionContext
- outputChannel: vscode.OutputChannel
- provider: ClineProvider
- }
- export const registerCommands = (options: RegisterCommandOptions) => {
- const { context } = options
- for (const [id, callback] of Object.entries(getCommandsMap(options))) {
- const command = getCommand(id as CommandId)
- context.subscriptions.push(vscode.commands.registerCommand(command, callback))
- }
- }
- const getCommandsMap = ({ context, outputChannel, provider }: RegisterCommandOptions): Record<CommandId, any> => ({
- activationCompleted: () => {},
- accountButtonClicked: () => {
- const visibleProvider = getVisibleProviderOrLog(outputChannel)
- if (!visibleProvider) {
- return
- }
- TelemetryService.instance.captureTitleButtonClicked("account")
- visibleProvider.postMessageToWebview({ type: "action", action: "accountButtonClicked" })
- },
- plusButtonClicked: async () => {
- const visibleProvider = getVisibleProviderOrLog(outputChannel)
- if (!visibleProvider) {
- return
- }
- TelemetryService.instance.captureTitleButtonClicked("plus")
- await visibleProvider.removeClineFromStack()
- await visibleProvider.postStateToWebview()
- await visibleProvider.postMessageToWebview({ type: "action", action: "chatButtonClicked" })
- },
- mcpButtonClicked: () => {
- const visibleProvider = getVisibleProviderOrLog(outputChannel)
- if (!visibleProvider) {
- return
- }
- TelemetryService.instance.captureTitleButtonClicked("mcp")
- visibleProvider.postMessageToWebview({ type: "action", action: "mcpButtonClicked" })
- },
- promptsButtonClicked: () => {
- const visibleProvider = getVisibleProviderOrLog(outputChannel)
- if (!visibleProvider) {
- return
- }
- TelemetryService.instance.captureTitleButtonClicked("prompts")
- visibleProvider.postMessageToWebview({ type: "action", action: "promptsButtonClicked" })
- },
- popoutButtonClicked: () => {
- TelemetryService.instance.captureTitleButtonClicked("popout")
- return openClineInNewTab({ context, outputChannel })
- },
- openInNewTab: () => openClineInNewTab({ context, outputChannel }),
- settingsButtonClicked: () => {
- const visibleProvider = getVisibleProviderOrLog(outputChannel)
- if (!visibleProvider) {
- return
- }
- TelemetryService.instance.captureTitleButtonClicked("settings")
- visibleProvider.postMessageToWebview({ type: "action", action: "settingsButtonClicked" })
- // Also explicitly post the visibility message to trigger scroll reliably
- visibleProvider.postMessageToWebview({ type: "action", action: "didBecomeVisible" })
- },
- historyButtonClicked: () => {
- const visibleProvider = getVisibleProviderOrLog(outputChannel)
- if (!visibleProvider) {
- return
- }
- TelemetryService.instance.captureTitleButtonClicked("history")
- visibleProvider.postMessageToWebview({ type: "action", action: "historyButtonClicked" })
- },
- marketplaceButtonClicked: () => {
- const visibleProvider = getVisibleProviderOrLog(outputChannel)
- if (!visibleProvider) return
- visibleProvider.postMessageToWebview({ type: "action", action: "marketplaceButtonClicked" })
- },
- showHumanRelayDialog: (params: { requestId: string; promptText: string }) => {
- const panel = getPanel()
- if (panel) {
- panel?.webview.postMessage({
- type: "showHumanRelayDialog",
- requestId: params.requestId,
- promptText: params.promptText,
- })
- }
- },
- registerHumanRelayCallback: registerHumanRelayCallback,
- unregisterHumanRelayCallback: unregisterHumanRelayCallback,
- handleHumanRelayResponse: handleHumanRelayResponse,
- newTask: handleNewTask,
- setCustomStoragePath: async () => {
- const { promptForCustomStoragePath } = await import("../utils/storage")
- await promptForCustomStoragePath()
- },
- importSettings: async (filePath?: string) => {
- const visibleProvider = getVisibleProviderOrLog(outputChannel)
- if (!visibleProvider) {
- return
- }
- await importSettingsWithFeedback(
- {
- providerSettingsManager: visibleProvider.providerSettingsManager,
- contextProxy: visibleProvider.contextProxy,
- customModesManager: visibleProvider.customModesManager,
- provider: visibleProvider,
- },
- filePath,
- )
- },
- focusInput: async () => {
- try {
- await focusPanel(tabPanel, sidebarPanel)
- // Send focus input message only for sidebar panels
- if (sidebarPanel && getPanel() === sidebarPanel) {
- provider.postMessageToWebview({ type: "action", action: "focusInput" })
- }
- } catch (error) {
- outputChannel.appendLine(`Error focusing input: ${error}`)
- }
- },
- focusPanel: async () => {
- try {
- await focusPanel(tabPanel, sidebarPanel)
- } catch (error) {
- outputChannel.appendLine(`Error focusing panel: ${error}`)
- }
- },
- acceptInput: () => {
- const visibleProvider = getVisibleProviderOrLog(outputChannel)
- if (!visibleProvider) {
- return
- }
- visibleProvider.postMessageToWebview({ type: "acceptInput" })
- },
- })
- export const openClineInNewTab = async ({ context, outputChannel }: Omit<RegisterCommandOptions, "provider">) => {
- // (This example uses webviewProvider activation event which is necessary to
- // deserialize cached webview, but since we use retainContextWhenHidden, we
- // don't need to use that event).
- // https://github.com/microsoft/vscode-extension-samples/blob/main/webview-sample/src/extension.ts
- const contextProxy = await ContextProxy.getInstance(context)
- const codeIndexManager = CodeIndexManager.getInstance(context)
- // Get the existing MDM service instance to ensure consistent policy enforcement
- let mdmService: MdmService | undefined
- try {
- mdmService = MdmService.getInstance()
- } catch (error) {
- // MDM service not initialized, which is fine - extension can work without it
- mdmService = undefined
- }
- const tabProvider = new ClineProvider(context, outputChannel, "editor", contextProxy, codeIndexManager, mdmService)
- const lastCol = Math.max(...vscode.window.visibleTextEditors.map((editor) => editor.viewColumn || 0))
- // Check if there are any visible text editors, otherwise open a new group
- // to the right.
- const hasVisibleEditors = vscode.window.visibleTextEditors.length > 0
- if (!hasVisibleEditors) {
- await vscode.commands.executeCommand("workbench.action.newGroupRight")
- }
- const targetCol = hasVisibleEditors ? Math.max(lastCol + 1, 1) : vscode.ViewColumn.Two
- const newPanel = vscode.window.createWebviewPanel(ClineProvider.tabPanelId, "Roo Code", targetCol, {
- enableScripts: true,
- retainContextWhenHidden: true,
- localResourceRoots: [context.extensionUri],
- })
- // Save as tab type panel.
- setPanel(newPanel, "tab")
- // TODO: Use better svg icon with light and dark variants (see
- // https://stackoverflow.com/questions/58365687/vscode-extension-iconpath).
- newPanel.iconPath = {
- light: vscode.Uri.joinPath(context.extensionUri, "assets", "icons", "panel_light.png"),
- dark: vscode.Uri.joinPath(context.extensionUri, "assets", "icons", "panel_dark.png"),
- }
- await tabProvider.resolveWebviewView(newPanel)
- // Add listener for visibility changes to notify webview
- newPanel.onDidChangeViewState(
- (e) => {
- const panel = e.webviewPanel
- if (panel.visible) {
- panel.webview.postMessage({ type: "action", action: "didBecomeVisible" }) // Use the same message type as in SettingsView.tsx
- }
- },
- null, // First null is for `thisArgs`
- context.subscriptions, // Register listener for disposal
- )
- // Handle panel closing events.
- newPanel.onDidDispose(
- () => {
- setPanel(undefined, "tab")
- },
- null,
- context.subscriptions, // Also register dispose listener
- )
- // Lock the editor group so clicking on files doesn't open them over the panel.
- await delay(100)
- await vscode.commands.executeCommand("workbench.action.lockEditorGroup")
- return tabProvider
- }
|