From bdbc66c216d8649788ff13aa5a440b843d0688c2 Mon Sep 17 00:00:00 2001 From: "google-labs-jules[bot]" <161369871+google-labs-jules[bot]@users.noreply.github.com> Date: Tue, 25 Nov 2025 12:52:29 +0000 Subject: [PATCH] =?UTF-8?q?feat:=20T=C3=ADch=20h=E1=BB=A3p=20l=C3=B5i=20t?= =?UTF-8?q?=C3=A1c=20nh=C3=A2n=20AI=20l=C3=A0m=20d=E1=BB=8Bch=20v=E1=BB=A5?= =?UTF-8?q?=20Workbench=20g=E1=BB=91c?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Thêm một đóng góp workbench mới để tích hợp lõi tác nhân AI. Bản vá ban đầu này tạo nên kiến trúc cho các dịch vụ cốt lõi sau: - AgentService: Để điều phối các tác nhân AI chuyên biệt. - ContextService: Để quản lý trạng thái ứng dụng. - ModelService: Để quản lý cấu hình nhà cung cấp LLM. - CodeIndexService: Để lập chỉ mục cơ sở mã và tìm kiếm ngữ nghĩa. - KnowledgeGraphService: Để xây dựng biểu đồ tri thức của mã. - TaskService: Để quản lý vòng đời của các tác vụ AI. Cũng bao gồm một chế độ xem web giữ chỗ cho giao diện người dùng của tác nhân, được đăng ký trong thanh bên. --- .../contrib/agent/browser/agentService.ts | 63 ++++++++++ .../contrib/agent/browser/agentView.ts | 107 +++++++++++++++++ .../contrib/agent/browser/codeIndexService.ts | 72 ++++++++++++ .../contrib/agent/browser/contextService.ts | 62 ++++++++++ .../agent/browser/knowledgeGraphService.ts | 36 ++++++ .../contrib/agent/browser/modelService.ts | 75 ++++++++++++ .../workbench/contrib/agent/browser/task.ts | 35 ++++++ .../contrib/agent/browser/taskService.ts | 31 +++++ .../agent/common/agent.contribution.ts | 43 +++++++ .../contrib/agent/common/agentService.ts | 21 ++++ .../contrib/agent/common/codeIndexService.ts | 29 +++++ .../contrib/agent/common/contextService.ts | 14 +++ .../agent/common/knowledgeGraphService.ts | 11 ++ .../contrib/agent/common/modelService.ts | 15 +++ src/vs/workbench/contrib/agent/common/task.ts | 6 + .../contrib/agent/common/taskService.ts | 14 +++ .../contrib/agent/common/vectorStore.ts | 5 + .../contrib/agent/webview-ui/index.html | 12 ++ .../contrib/agent/webview-ui/package.json | 108 ++++++++++++++++++ .../contrib/agent/webview-ui/src/App.tsx | 61 ++++++++++ .../contrib/agent/webview-ui/src/main.tsx | 9 ++ .../contrib/agent/webview-ui/tsconfig.json | 21 ++++ .../agent/webview-ui/tsconfig.node.json | 9 ++ .../contrib/agent/webview-ui/vite.config.ts | 17 +++ 24 files changed, 876 insertions(+) create mode 100644 src/vs/workbench/contrib/agent/browser/agentService.ts create mode 100644 src/vs/workbench/contrib/agent/browser/agentView.ts create mode 100644 src/vs/workbench/contrib/agent/browser/codeIndexService.ts create mode 100644 src/vs/workbench/contrib/agent/browser/contextService.ts create mode 100644 src/vs/workbench/contrib/agent/browser/knowledgeGraphService.ts create mode 100644 src/vs/workbench/contrib/agent/browser/modelService.ts create mode 100644 src/vs/workbench/contrib/agent/browser/task.ts create mode 100644 src/vs/workbench/contrib/agent/browser/taskService.ts create mode 100644 src/vs/workbench/contrib/agent/common/agent.contribution.ts create mode 100644 src/vs/workbench/contrib/agent/common/agentService.ts create mode 100644 src/vs/workbench/contrib/agent/common/codeIndexService.ts create mode 100644 src/vs/workbench/contrib/agent/common/contextService.ts create mode 100644 src/vs/workbench/contrib/agent/common/knowledgeGraphService.ts create mode 100644 src/vs/workbench/contrib/agent/common/modelService.ts create mode 100644 src/vs/workbench/contrib/agent/common/task.ts create mode 100644 src/vs/workbench/contrib/agent/common/taskService.ts create mode 100644 src/vs/workbench/contrib/agent/common/vectorStore.ts create mode 100644 src/vs/workbench/contrib/agent/webview-ui/index.html create mode 100644 src/vs/workbench/contrib/agent/webview-ui/package.json create mode 100644 src/vs/workbench/contrib/agent/webview-ui/src/App.tsx create mode 100644 src/vs/workbench/contrib/agent/webview-ui/src/main.tsx create mode 100644 src/vs/workbench/contrib/agent/webview-ui/tsconfig.json create mode 100644 src/vs/workbench/contrib/agent/webview-ui/tsconfig.node.json create mode 100644 src/vs/workbench/contrib/agent/webview-ui/vite.config.ts diff --git a/src/vs/workbench/contrib/agent/browser/agentService.ts b/src/vs/workbench/contrib/agent/browser/agentService.ts new file mode 100644 index 00000000000..cf6b0217f14 --- /dev/null +++ b/src/vs/workbench/contrib/agent/browser/agentService.ts @@ -0,0 +1,63 @@ +import { IAgentService, IAgent, AgentRole } from 'vs/workbench/contrib/agent/common/agentService'; +import { ITaskService } from 'vs/workbench/contrib/agent/common/taskService'; +import { IContextService } from 'vs/workbench/contrib/agent/common/contextService'; +import { IModelService } from 'vs/workbench/contrib/agent/common/modelService'; +import { ICodeIndexService } from 'vs/workbench/contrib/agent/common/codeIndexService'; +import { IKnowledgeGraphService } from 'vs/workbench/contrib/agent/common/knowledgeGraphService'; +import { ITask } from 'vs/workbench/contrib/agent/common/task'; +import { Task } from 'vs/workbench/contrib/agent/browser/task'; +import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; + +class Agent implements IAgent { + constructor( + public readonly role: AgentRole, + private readonly taskService: ITaskService, + private readonly instantiationService: IInstantiationService + ) {} + + startTask(text: string, images?: string[]): Promise { + const task = this.instantiationService.createInstance(Task, this, this.role, text, images); + // In a real implementation, we would manage the task lifecycle here. + (task as Task).run(); + return Promise.resolve(task); + } +} + +export class AgentService implements IAgentService { + _serviceBrand: undefined; + + private readonly agents: Map = new Map(); + + constructor( + @ITaskService private readonly taskService: ITaskService, + @IContextService private readonly contextService: IContextService, + @IModelService private readonly modelService: IModelService, + @ICodeIndexService private readonly codeIndexService: ICodeIndexService, + @IKnowledgeGraphService private readonly knowledgeGraphService: IKnowledgeGraphService, + @IInstantiationService private readonly instantiationService: IInstantiationService, + ) { + this.agents.set('architect', new Agent('architect', this.taskService, this.instantiationService)); + this.agents.set('developer', new Agent('developer', this.taskService, this.instantiationService)); + this.agents.set('tester', new Agent('tester', this.taskService, this.instantiationService)); + } + + getAgent(role: AgentRole): IAgent | undefined { + return this.agents.get(role); + } + + async startTask(role: AgentRole, text: string, images?: string[]): Promise { + const agent = this.getAgent(role); + if (!agent) { + throw new Error(`Agent with role '${role}' not found.`); + } + return agent.startTask(text, images); + } + + async cancelTask(taskId: string): Promise { + await this.taskService.cancelTask(); + } + + getCurrentTask(): ITask | undefined { + return this.taskService.getCurrentTask(); + } +} diff --git a/src/vs/workbench/contrib/agent/browser/agentView.ts b/src/vs/workbench/contrib/agent/browser/agentView.ts new file mode 100644 index 00000000000..fec621b9bd7 --- /dev/null +++ b/src/vs/workbench/contrib/agent/browser/agentView.ts @@ -0,0 +1,107 @@ +import { IViewPaneOptions, ViewPane } from 'vs/workbench/browser/parts/views/viewPane'; +import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; +import { IContextMenuService } from 'vs/platform/contextview/browser/contextView'; +import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; +import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; +import { IViewDescriptorService } from 'vs/workbench/common/views'; +import { IOpenerService } from 'vs/platform/opener/common/opener'; +import { IThemeService } from 'vs/platform/theme/common/themeService'; +import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; +import { IWebviewViewService, IWebviewService } from 'vs/workbench/contrib/webview/browser/webview'; +import { CancellationToken } from 'vs/base/common/cancellation'; +import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; +import { IStorageService } from 'vs/platform/storage/common/storage'; +import { URI } from 'vs/base/common/uri'; +import { IEnvironmentService } from 'vs/platform/environment/common/environment'; +import { IAgentService } from 'vs/workbench/contrib/agent/common/agentService'; + +export class AgentView extends ViewPane { + private webviewViewService: IWebviewViewService; + private webview?: IWebviewService; + private environmentService: IEnvironmentService; + private agentService: IAgentService; + + constructor( + options: IViewPaneOptions, + @IKeybindingService keybindingService: IKeybindingService, + @IContextMenuService contextMenuService: IContextMenuService, + @IConfigurationService configurationService: IConfigurationService, + @IInstantiationService instantiationService: IInstantiationService, + @IViewDescriptorService viewDescriptorService: IViewDescriptorService, + @IOpenerService openerService: IOpenerService, + @IThemeService themeService: IThemeService, + @ITelemetryService telemetryService: ITelemetryService, + @IWebviewViewService webviewViewService: IWebviewViewService, + @IContextKeyService contextKeyService: IContextKeyService, + @IStorageService storageService: IStorageService, + @IEnvironmentService environmentService: IEnvironmentService, + @IAgentService agentService: IAgentService, + ) { + super(options, keybindingService, contextMenuService, configurationService, contextKeyService, instantiationService, viewDescriptorService, themeService, telemetryService, openerService); + this.webviewViewService = webviewViewService; + this.environmentService = environmentService; + this.agentService = agentService; + } + + protected override renderBody(container: HTMLElement): void { + super.renderBody(container); + + const webviewView = this.webviewViewService.createWebviewView(this.id, { + title: this.title, + icon: this.icon, + }); + + this.webview = webviewView.webview; + webviewView.webview.html = this.getHtmlForWebview(); + + webviewView.webview.onDidReceiveMessage(message => { + switch (message.type) { + case 'startTask': + this.agentService.startTask('developer', message.text, message.images); + break; + } + }); + + // Post initial state + this.postStateToWebview(); + } + + private async postStateToWebview() { + if (!this.webview) { + return; + } + + const state = { + // This will be populated with the actual state from the services + }; + + this.webview.postMessage({ type: 'state', state }); + } + + private getHtmlForWebview(): string { + if (!this.webview) { + return ''; + } + + const webviewUiUri = this.webview.asWebviewUri( + URI.joinPath(this.environmentService.appRoot, 'src', 'vs', 'workbench', 'contrib', 'agent', 'webview-ui', 'build', 'assets', 'index.js') + ); + + return ` + + + + + Cortex IDE Agent + + +
+ + + `; + } + + protected override layoutBody(height: number, width: number): void { + super.layoutBody(height, width); + } +} diff --git a/src/vs/workbench/contrib/agent/browser/codeIndexService.ts b/src/vs/workbench/contrib/agent/browser/codeIndexService.ts new file mode 100644 index 00000000000..6fb2224cb2b --- /dev/null +++ b/src/vs/workbench/contrib/agent/browser/codeIndexService.ts @@ -0,0 +1,72 @@ +import { ICodeIndexService, ISymbolReference } from 'vs/workbench/contrib/agent/common/codeIndexService'; +import { VectorStoreSearchResult } from 'vs/workbench/contrib/agent/common/vectorStore'; +import { IContextService } from 'vs/workbench/contrib/agent/common/contextService'; +import { IModelService } from 'vs/workbench/contrib/agent/common/modelService'; +import { ILogService } from 'vs/platform/log/common/log'; +import { URI } from 'vs/base/common/uri'; + +// This would be a more complex implementation in a real scenario, +// likely involving a language server and a vector database. +class LanguageService { + async findSymbol(query: string): Promise { return []; } + async findReferences(symbol: ISymbolReference): Promise { return []; } + async insertAfterSymbol(symbol: ISymbolReference, content: string): Promise {} +} + +class VectorDB { + async search(query: string): Promise { return []; } + async startIndexing(): Promise {} + async stopIndexing(): Promise {} + async clearIndex(): Promise {} +} + +export class CodeIndexService implements ICodeIndexService { + _serviceBrand: undefined; + + private languageService: LanguageService; + private vectorDB: VectorDB; + + constructor( + @IContextService private readonly contextService: IContextService, + @IModelService private readonly modelService: IModelService, + @ILogService private readonly logService: ILogService + ) { + this.languageService = new LanguageService(); + this.vectorDB = new VectorDB(); + } + + async startIndexing(): Promise { + this.logService.info('Starting code indexing...'); + await this.vectorDB.startIndexing(); + } + + stopWatcher(): void { + this.logService.info('Stopping code indexing watcher...'); + this.vectorDB.stopIndexing(); + } + + async clearIndexData(): Promise { + this.logService.info('Clearing code index data...'); + await this.vectorDB.clearIndex(); + } + + async searchIndex(query: string, directoryPrefix?: string): Promise { + this.logService.info(`Searching index for: ${query}`); + return this.vectorDB.search(query); + } + + async findSymbol(query: string): Promise { + this.logService.info(`Finding symbol: ${query}`); + return this.languageService.findSymbol(query); + } + + async findReferences(symbol: ISymbolReference): Promise { + this.logService.info(`Finding references for symbol: ${symbol}`); + return this.languageService.findReferences(symbol); + } + + async insertAfterSymbol(symbol: ISymbolReference, content: string): Promise { + this.logService.info(`Inserting content after symbol: ${symbol}`); + await this.languageService.insertAfterSymbol(symbol, content); + } +} diff --git a/src/vs/workbench/contrib/agent/browser/contextService.ts b/src/vs/workbench/contrib/agent/browser/contextService.ts new file mode 100644 index 00000000000..198a3351473 --- /dev/null +++ b/src/vs/workbench/contrib/agent/browser/contextService.ts @@ -0,0 +1,62 @@ +import { IContextService } from 'vs/workbench/contrib/agent/common/contextService'; +import { IStorageService, StorageScope, StorageTarget } from 'vs/platform/storage/common/storage'; +import { GlobalState, RooCodeSettings, GLOBAL_STATE_KEYS, SECRET_STATE_KEYS, isSecretStateKey } from '@roo-code/types'; +import { ILogService } from 'vs/platform/log/common/log'; +import { Memento } from 'vs/workbench/common/memento'; + +export class ContextService implements IContextService { + _serviceBrand: undefined; + + private stateCache: GlobalState = {}; + private memento: Memento; + + constructor( + @IStorageService private readonly storageService: IStorageService, + @ILogService private readonly logService: ILogService + ) { + this.memento = new Memento('cortex-agent', this.storageService); + this.loadState(); + } + + private loadState(): void { + const state = this.memento.getMemento(StorageScope.APPLICATION, StorageTarget.MACHINE); + for (const key of GLOBAL_STATE_KEYS) { + this.stateCache[key] = state[key]; + } + } + + private saveState(): void { + const state = this.memento.getMemento(StorageScope.APPLICATION, StorageTarget.MACHINE); + for (const key of GLOBAL_STATE_KEYS) { + state[key] = this.stateCache[key]; + } + this.memento.saveMemento(); + } + + getValue(key: K): RooCodeSettings[K] { + if (isSecretStateKey(key)) { + this.logService.warn(`Attempted to access secret key '${key}' through non-secret method.`); + return undefined; + } + return this.stateCache[key as keyof GlobalState] as RooCodeSettings[K]; + } + + async setValue(key: K, value: RooCodeSettings[K]): Promise { + if (isSecretStateKey(key)) { + this.logService.warn(`Attempted to set secret key '${key}' through non-secret method.`); + return; + } + this.stateCache[key as keyof GlobalState] = value; + this.saveState(); + } + + getValues(): RooCodeSettings { + return this.stateCache as RooCodeSettings; + } + + async setValues(values: RooCodeSettings): Promise { + for (const key in values) { + await this.setValue(key as keyof RooCodeSettings, values[key as keyof RooCodeSettings]); + } + } +} diff --git a/src/vs/workbench/contrib/agent/browser/knowledgeGraphService.ts b/src/vs/workbench/contrib/agent/browser/knowledgeGraphService.ts new file mode 100644 index 00000000000..e83a824ad0e --- /dev/null +++ b/src/vs/workbench/contrib/agent/browser/knowledgeGraphService.ts @@ -0,0 +1,36 @@ +import { IKnowledgeGraphService } from 'vs/workbench/contrib/agent/common/knowledgeGraphService'; +import { ICodeIndexService } from 'vs/workbench/contrib/agent/common/codeIndexService'; +import { ILogService } from 'vs/platform/log/common/log'; + +// This would be a more complex implementation in a real scenario, +// likely involving a graph database and natural language processing. +class GraphDB { + async buildGraph(data: any): Promise {} + async queryGraph(query: string): Promise { return []; } +} + +export class KnowledgeGraphService implements IKnowledgeGraphService { + _serviceBrand: undefined; + + private graphDB: GraphDB; + + constructor( + @ICodeIndexService private readonly codeIndexService: ICodeIndexService, + @ILogService private readonly logService: ILogService + ) { + this.graphDB = new GraphDB(); + } + + async buildGraph(): Promise { + this.logService.info('Building knowledge graph...'); + // In a real implementation, we would use the code index to get + // information about the codebase and then use that to build the graph. + const symbols = await this.codeIndexService.findSymbol('*'); + await this.graphDB.buildGraph(symbols); + } + + async queryGraph(query: string): Promise { + this.logService.info(`Querying knowledge graph with: ${query}`); + return this.graphDB.queryGraph(query); + } +} diff --git a/src/vs/workbench/contrib/agent/browser/modelService.ts b/src/vs/workbench/contrib/agent/browser/modelService.ts new file mode 100644 index 00000000000..cf122721d16 --- /dev/null +++ b/src/vs/workbench/contrib/agent/browser/modelService.ts @@ -0,0 +1,75 @@ +import { IModelService } from 'vs/workbench/contrib/agent/common/modelService'; +import { IContextService } from 'vs/workbench/contrib/agent/common/contextService'; +import { ProviderSettings, ProviderSettingsEntry, Mode } from '@roo-code/types'; +import { Emitter, Event } from 'vs/base/common/event'; + +export class ModelService implements IModelService { + _serviceBrand: undefined; + + private readonly _onDidActivateProviderProfile = new Emitter<{ name: string; provider?: string }>(); + public readonly onDidActivateProviderProfile: Event<{ name: string; provider?: string }> = this._onDidActivateProviderProfile.event; + + constructor(@IContextService private readonly contextService: IContextService) {} + + getProviderProfiles(): ProviderSettingsEntry[] { + return this.contextService.getValue('listApiConfigMeta') || []; + } + + getProviderProfile(name: string): ProviderSettingsEntry | undefined { + return this.getProviderProfiles().find((profile) => profile.name === name); + } + + async upsertProviderProfile(name: string, providerSettings: ProviderSettings, activate: boolean = true): Promise { + const profiles = this.getProviderProfiles(); + const id = Math.random().toString(36).substring(7); + const newProfile: ProviderSettingsEntry = { + id, + name, + apiProvider: providerSettings.apiProvider, + }; + + const existingIndex = profiles.findIndex(p => p.name === name); + if (existingIndex !== -1) { + newProfile.id = profiles[existingIndex].id; + profiles[existingIndex] = newProfile; + } else { + profiles.push(newProfile); + } + + await this.contextService.setValue('listApiConfigMeta', profiles); + // In a real implementation, we would save the full providerSettings + // using a secure method. + + if (activate) { + await this.activateProviderProfile({ name }); + } + + return id; + } + + async deleteProviderProfile(profileToDelete: ProviderSettingsEntry): Promise { + let profiles = this.getProviderProfiles(); + profiles = profiles.filter(p => p.id !== profileToDelete.id); + await this.contextService.setValue('listApiConfigMeta', profiles); + + const currentProfileName = this.contextService.getValue('currentApiConfigName'); + if (currentProfileName === profileToDelete.name) { + const newProfileToActivate = profiles[0]; + if (newProfileToActivate) { + await this.activateProviderProfile({ name: newProfileToActivate.name }); + } + } + } + + async activateProviderProfile(args: { name: string } | { id: string }): Promise { + const profile = 'name' in args + ? this.getProviderProfile(args.name) + : this.getProviderProfiles().find(p => p.id === args.id); + + if (profile) { + await this.contextService.setValue('currentApiConfigName', profile.name); + // In a real implementation, we would load the full provider settings here. + this._onDidActivateProviderProfile.fire({ name: profile.name, provider: profile.apiProvider }); + } + } +} diff --git a/src/vs/workbench/contrib/agent/browser/task.ts b/src/vs/workbench/contrib/agent/browser/task.ts new file mode 100644 index 00000000000..caddba69d1a --- /dev/null +++ b/src/vs/workbench/contrib/agent/browser/task.ts @@ -0,0 +1,35 @@ +import { ITask } from "vs/workbench/contrib/agent/common/task"; +import { AgentRole, IAgentService } from "vs/workbench/contrib/agent/common/agentService"; +import { IModelService } from "vs/workbench/contrib/agent/common/modelService"; +import { ICodeIndexService } from "vs/workbench/contrib/agent/common/codeIndexService"; +import { IKnowledgeGraphService } from "vs/workbench/contrib/agent/common/knowledgeGraphService"; + +export class Task implements ITask { + readonly taskId: string; + readonly rootTaskId?: string; + readonly parentTaskId?: string; + childTaskId?: string; + + constructor( + private readonly agentService: IAgentService, + private readonly modelService: IModelService, + private readonly codeIndexService: ICodeIndexService, + private readonly knowledgeGraphService: IKnowledgeGraphService, + public readonly role: AgentRole, + public readonly text: string, + public readonly images?: string[] + ) { + this.taskId = Math.random().toString(36).substring(7); + } + + async run(): Promise { + // This is where the core agent logic will go. + // For now, we'll just log a message. + console.log(`Running task ${this.taskId} with role ${this.role} and text "${this.text}"`); + + // Example of how the agent might use the other services: + const profile = this.modelService.getProviderProfile(this.modelService.getProviderProfiles()[0].name); + const symbols = await this.codeIndexService.findSymbol('myFunction'); + const graphResults = await this.knowledgeGraphService.queryGraph('What is the purpose of myFunction?'); + } +} diff --git a/src/vs/workbench/contrib/agent/browser/taskService.ts b/src/vs/workbench/contrib/agent/browser/taskService.ts new file mode 100644 index 00000000000..cfaae1d134e --- /dev/null +++ b/src/vs/workbench/contrib/agent/browser/taskService.ts @@ -0,0 +1,31 @@ +import { ITaskService } from 'vs/workbench/contrib/agent/common/taskService'; +import { ITask } from 'vs/workbench/contrib/agent/common/task'; +import { IAgentService, AgentRole } from 'vs/workbench/contrib/agent/common/agentService'; +import { RooCodeSettings, CreateTaskOptions } from '@roo-code/types'; +import { Task } from 'vs/workbench/contrib/agent/browser/task'; +import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; + +export class TaskService implements ITaskService { + _serviceBrand: undefined; + + private readonly taskStack: ITask[] = []; + + constructor( + @IInstantiationService private readonly instantiationService: IInstantiationService + ) {} + + async createTask(text?: string, images?: string[], parentTask?: ITask, options?: CreateTaskOptions, configuration?: RooCodeSettings): Promise { + const agentService = this.instantiationService.invokeFunction(accessor => accessor.get(IAgentService)); + const task = this.instantiationService.createInstance(Task, agentService, 'developer', text || '', images); + this.taskStack.push(task); + return task; + } + + async cancelTask(): Promise { + this.taskStack.pop(); + } + + getCurrentTask(): ITask | undefined { + return this.taskStack[this.taskStack.length - 1]; + } +} diff --git a/src/vs/workbench/contrib/agent/common/agent.contribution.ts b/src/vs/workbench/contrib/agent/common/agent.contribution.ts new file mode 100644 index 00000000000..3f4a9ad0518 --- /dev/null +++ b/src/vs/workbench/contrib/agent/common/agent.contribution.ts @@ -0,0 +1,43 @@ +import { InstantiationType, registerSingleton } from 'vs/platform/instantiation/common/extensions'; +import { IAgentService } from 'vs/workbench/contrib/agent/common/agentService'; +import { IContextService } from 'vs/workbench/contrib/agent/common/contextService'; +import { IModelService } from 'vs/workbench/contrib/agent/common/modelService'; +import { ICodeIndexService } from 'vs/workbench/contrib/agent/common/codeIndexService'; +import { ITaskService } from 'vs/workbench/contrib/agent/common/taskService'; +import { IKnowledgeGraphService } from 'vs/workbench/contrib/agent/common/knowledgeGraphService'; +import { AgentService } from 'vs/workbench/contrib/agent/browser/agentService'; +import { ContextService } from 'vs/workbench/contrib/agent/browser/contextService'; +import { ModelService } from 'vs/workbench/contrib/agent/browser/modelService'; +import { CodeIndexService } from 'vs/workbench/contrib/agent/browser/codeIndexService'; +import { TaskService } from 'vs/workbench/contrib/agent/browser/taskService'; +import { KnowledgeGraphService } from 'vs/workbench/contrib/agent/browser/knowledgeGraphService'; +import { Registry } from 'vs/platform/registry/common/platform'; +import { IViewsRegistry, Extensions, ViewContainer, ViewContainerLocation } from 'vs/workbench/common/views'; +import { AgentView } from 'vs/workbench/contrib/agent/browser/agentView'; +import { SyncDescriptor } from 'vs/platform/instantiation/common/descriptors'; +import { Codicon } from 'vs/base/common/codicons'; + +registerSingleton(IAgentService, AgentService, InstantiationType.Delayed); +registerSingleton(IContextService, ContextService, InstantiationType.Delayed); +registerSingleton(IModelService, ModelService, InstantiationType.Delayed); +registerSingleton(ICodeIndexService, CodeIndexService, InstantiationType.Delayed); +registerSingleton(ITaskService, TaskService, InstantiationType.Delayed); +registerSingleton(IKnowledgeGraphService, KnowledgeGraphService, InstantiationType.Delayed); + +const container: ViewContainer = Registry.as(Extensions.ViewsRegistry).registerViewContainer({ + id: 'cortex-agent', + title: 'Cortex Agent', + icon: Codicon.hubot, + order: 100, +}, ViewContainerLocation.Sidebar); + +Registry.as(Extensions.ViewsRegistry).registerViews([{ + id: 'cortex-agent-view', + name: 'Cortex Agent', + containerIcon: Codicon.hubot, + ctorDescriptor: new SyncDescriptor(AgentView), + canToggleVisibility: true, + canMoveView: true, + collapsed: false, + order: 1, +}], container); diff --git a/src/vs/workbench/contrib/agent/common/agentService.ts b/src/vs/workbench/contrib/agent/common/agentService.ts new file mode 100644 index 00000000000..ac3fb0a78c0 --- /dev/null +++ b/src/vs/workbench/contrib/agent/common/agentService.ts @@ -0,0 +1,21 @@ +import { createDecorator } from 'vs/platform/instantiation/common/instantiation'; +import { IWorkbenchContribution } from 'vs/workbench/common/contributions'; +import { ITask } from 'vs/workbench/contrib/agent/common/task'; + +export type AgentRole = 'architect' | 'developer' | 'tester'; + +export interface IAgent { + readonly role: AgentRole; + startTask(text: string, images?: string[]): Promise; +} + +export const IAgentService = createDecorator('agentService'); + +export interface IAgentService extends IWorkbenchContribution { + _serviceBrand: undefined; + + getAgent(role: AgentRole): IAgent | undefined; + startTask(role: AgentRole, text: string, images?: string[]): Promise; + cancelTask(taskId: string): Promise; + getCurrentTask(): ITask | undefined; +} diff --git a/src/vs/workbench/contrib/agent/common/codeIndexService.ts b/src/vs/workbench/contrib/agent/common/codeIndexService.ts new file mode 100644 index 00000000000..577a61384fb --- /dev/null +++ b/src/vs/workbench/contrib/agent/common/codeIndexService.ts @@ -0,0 +1,29 @@ +import { createDecorator } from 'vs/platform/instantiation/common/instantiation'; +import { IWorkbenchContribution } from 'vs/workbench/common/contributions'; +import { VectorStoreSearchResult } from 'vs/workbench/contrib/agent/common/vectorStore'; +import { URI } from 'vs/base/common/uri'; + +export interface ISymbolReference { + uri: URI; + range: { + startLineNumber: number; + startColumn: number; + endLineNumber: number; + endColumn: number; + }; +} + +export const ICodeIndexService = createDecorator('codeIndexService'); + +export interface ICodeIndexService extends IWorkbenchContribution { + _serviceBrand: undefined; + + startIndexing(): Promise; + stopWatcher(): void; + clearIndexData(): Promise; + searchIndex(query: string, directoryPrefix?: string): Promise; + + findSymbol(query: string): Promise; + findReferences(symbol: ISymbolReference): Promise; + insertAfterSymbol(symbol: ISymbolReference, content: string): Promise; +} diff --git a/src/vs/workbench/contrib/agent/common/contextService.ts b/src/vs/workbench/contrib/agent/common/contextService.ts new file mode 100644 index 00000000000..0d3064dc54f --- /dev/null +++ b/src/vs/workbench/contrib/agent/common/contextService.ts @@ -0,0 +1,14 @@ +import { createDecorator } from 'vs/platform/instantiation/common/instantiation'; +import { IWorkbenchContribution } from 'vs/workbench/common/contributions'; +import { GlobalState, RooCodeSettings } from '@roo-code/types'; + +export const IContextService = createDecorator('contextService'); + +export interface IContextService extends IWorkbenchContribution { + _serviceBrand: undefined; + + getValue(key: K): RooCodeSettings[K]; + setValue(key: K, value: RooCodeSettings[K]): Promise; + getValues(): RooCodeSettings; + setValues(values: RooCodeSettings): Promise; +} diff --git a/src/vs/workbench/contrib/agent/common/knowledgeGraphService.ts b/src/vs/workbench/contrib/agent/common/knowledgeGraphService.ts new file mode 100644 index 00000000000..b6090404273 --- /dev/null +++ b/src/vs/workbench/contrib/agent/common/knowledgeGraphService.ts @@ -0,0 +1,11 @@ +import { createDecorator } from 'vs/platform/instantiation/common/instantiation'; +import { IWorkbenchContribution } from 'vs/workbench/common/contributions'; + +export const IKnowledgeGraphService = createDecorator('knowledgeGraphService'); + +export interface IKnowledgeGraphService extends IWorkbenchContribution { + _serviceBrand: undefined; + + buildGraph(): Promise; + queryGraph(query: string): Promise; +} diff --git a/src/vs/workbench/contrib/agent/common/modelService.ts b/src/vs/workbench/contrib/agent/common/modelService.ts new file mode 100644 index 00000000000..dbf809cc635 --- /dev/null +++ b/src/vs/workbench/contrib/agent/common/modelService.ts @@ -0,0 +1,15 @@ +import { createDecorator } from 'vs/platform/instantiation/common/instantiation'; +import { IWorkbenchContribution } from 'vs/workbench/common/contributions'; +import { ProviderSettings, ProviderSettingsEntry } from '@roo-code/types'; + +export const IModelService = createDecorator('modelService'); + +export interface IModelService extends IWorkbenchContribution { + _serviceBrand: undefined; + + getProviderProfiles(): ProviderSettingsEntry[]; + getProviderProfile(name: string): ProviderSettingsEntry | undefined; + upsertProviderProfile(name: string, providerSettings: ProviderSettings, activate?: boolean): Promise; + deleteProviderProfile(profileToDelete: ProviderSettingsEntry): Promise; + activateProviderProfile(args: { name: string } | { id: string }): Promise; +} diff --git a/src/vs/workbench/contrib/agent/common/task.ts b/src/vs/workbench/contrib/agent/common/task.ts new file mode 100644 index 00000000000..a8ff20dd49f --- /dev/null +++ b/src/vs/workbench/contrib/agent/common/task.ts @@ -0,0 +1,6 @@ +export interface ITask { + readonly taskId: string; + readonly rootTaskId?: string; + readonly parentTaskId?: string; + childTaskId?: string; +} diff --git a/src/vs/workbench/contrib/agent/common/taskService.ts b/src/vs/workbench/contrib/agent/common/taskService.ts new file mode 100644 index 00000000000..3a74ba0a4b3 --- /dev/null +++ b/src/vs/workbench/contrib/agent/common/taskService.ts @@ -0,0 +1,14 @@ +import { createDecorator } from 'vs/platform/instantiation/common/instantiation'; +import { IWorkbenchContribution } from 'vs/workbench/common/contributions'; +import { ITask } from 'vs/workbench/contrib/agent/common/task'; +import { RooCodeSettings, CreateTaskOptions } from '@roo-code/types'; + +export const ITaskService = createDecorator('taskService'); + +export interface ITaskService extends IWorkbenchContribution { + _serviceBrand: undefined; + + createTask(text?: string, images?: string[], parentTask?: ITask, options?: CreateTaskOptions, configuration?: RooCodeSettings): Promise; + cancelTask(): Promise; + getCurrentTask(): ITask | undefined; +} diff --git a/src/vs/workbench/contrib/agent/common/vectorStore.ts b/src/vs/workbench/contrib/agent/common/vectorStore.ts new file mode 100644 index 00000000000..343173b1133 --- /dev/null +++ b/src/vs/workbench/contrib/agent/common/vectorStore.ts @@ -0,0 +1,5 @@ +export interface VectorStoreSearchResult { + filePath: string; + content: string; + score: number; +} diff --git a/src/vs/workbench/contrib/agent/webview-ui/index.html b/src/vs/workbench/contrib/agent/webview-ui/index.html new file mode 100644 index 00000000000..ada964b2429 --- /dev/null +++ b/src/vs/workbench/contrib/agent/webview-ui/index.html @@ -0,0 +1,12 @@ + + + + + + Cortex IDE Agent + + +
+ + + diff --git a/src/vs/workbench/contrib/agent/webview-ui/package.json b/src/vs/workbench/contrib/agent/webview-ui/package.json new file mode 100644 index 00000000000..cb6720e2b5d --- /dev/null +++ b/src/vs/workbench/contrib/agent/webview-ui/package.json @@ -0,0 +1,108 @@ +{ + "name": "@roo-code/vscode-webview", + "private": true, + "type": "module", + "scripts": { + "lint": "eslint src --ext=ts,tsx --max-warnings=0", + "check-types": "tsc", + "pretest": "turbo run bundle --cwd ..", + "test": "vitest run", + "format": "prettier --write src", + "dev": "vite", + "build": "tsc -b && vite build", + "build:nightly": "tsc -b && vite build --mode nightly", + "preview": "vite preview", + "clean": "rimraf ../src/webview-ui ../apps/vscode-nightly/build/webview-ui tsconfig.tsbuildinfo .turbo" + }, + "dependencies": { + "@radix-ui/react-alert-dialog": "^1.1.6", + "@radix-ui/react-checkbox": "^1.1.5", + "@radix-ui/react-collapsible": "^1.1.3", + "@radix-ui/react-dialog": "^1.1.6", + "@radix-ui/react-dropdown-menu": "^2.1.5", + "@radix-ui/react-icons": "^1.3.2", + "@radix-ui/react-popover": "^1.1.6", + "@radix-ui/react-portal": "^1.1.5", + "@radix-ui/react-progress": "^1.1.2", + "@radix-ui/react-select": "^2.1.6", + "@radix-ui/react-separator": "^1.1.2", + "@radix-ui/react-slider": "^1.2.3", + "@radix-ui/react-slot": "^1.1.2", + "@radix-ui/react-tooltip": "^1.1.8", + "@roo-code/types": "workspace:^", + "@tailwindcss/vite": "^4.0.0", + "@tanstack/react-query": "^5.68.0", + "@types/qrcode": "^1.5.5", + "@vscode/codicons": "^0.0.36", + "@vscode/webview-ui-toolkit": "^1.4.0", + "axios": "^1.12.0", + "class-variance-authority": "^0.7.1", + "clsx": "^2.1.1", + "cmdk": "^1.0.0", + "date-fns": "^4.1.0", + "debounce": "^2.1.1", + "diff": "^5.2.0", + "fast-deep-equal": "^3.1.3", + "fzf": "^0.5.2", + "hast-util-to-jsx-runtime": "^2.3.6", + "i18next": "^25.0.0", + "i18next-http-backend": "^3.0.2", + "katex": "^0.16.11", + "knuth-shuffle-seeded": "^1.0.6", + "lru-cache": "^11.1.0", + "lucide-react": "^0.518.0", + "mermaid": "^11.4.1", + "posthog-js": "^1.227.2", + "pretty-bytes": "^7.0.0", + "qrcode": "^1.5.4", + "react": "^18.3.1", + "react-dom": "^18.3.1", + "react-i18next": "^15.4.1", + "react-markdown": "^9.0.3", + "react-remark": "^2.1.0", + "react-textarea-autosize": "^8.5.3", + "react-use": "^17.5.1", + "react-virtuoso": "^4.7.13", + "rehype-highlight": "^7.0.0", + "rehype-katex": "^7.0.1", + "remark-gfm": "^4.0.1", + "remark-math": "^6.0.0", + "remove-markdown": "^0.6.0", + "shell-quote": "^1.8.2", + "shiki": "^3.2.1", + "source-map": "^0.7.4", + "stacktrace-js": "^2.0.2", + "styled-components": "^6.1.13", + "tailwind-merge": "^3.0.0", + "tailwindcss": "^4.0.0", + "tailwindcss-animate": "^1.0.7", + "unist-util-visit": "^5.0.0", + "use-sound": "^5.0.0", + "vscode-material-icons": "^0.1.1", + "vscrui": "^0.2.2", + "zod": "^3.25.61" + }, + "devDependencies": { + "@roo-code/config-eslint": "workspace:^", + "@roo-code/config-typescript": "workspace:^", + "@testing-library/jest-dom": "^6.6.3", + "@testing-library/react": "^16.2.0", + "@testing-library/user-event": "^14.6.1", + "@types/diff": "^5.2.1", + "@types/jest": "^29.0.0", + "@types/katex": "^0.16.7", + "@types/node": "20.x", + "@types/react": "^18.3.23", + "@types/react-dom": "^18.3.5", + "@types/shell-quote": "^1.7.5", + "@types/stacktrace-js": "^2.0.3", + "@types/vscode-webview": "^1.57.5", + "@vitejs/plugin-react": "^4.3.4", + "@vitest/ui": "^3.2.3", + "identity-obj-proxy": "^3.0.0", + "jsdom": "^26.0.0", + "typescript": "5.8.3", + "vite": "6.3.6", + "vitest": "^3.2.3" + } +} diff --git a/src/vs/workbench/contrib/agent/webview-ui/src/App.tsx b/src/vs/workbench/contrib/agent/webview-ui/src/App.tsx new file mode 100644 index 00000000000..2a69e390e54 --- /dev/null +++ b/src/vs/workbench/contrib/agent/webview-ui/src/App.tsx @@ -0,0 +1,61 @@ +import React, { useCallback, useEffect, useState } from "react" +import { QueryClient, QueryClientProvider } from "@tanstack/react-query" + +// This will be replaced with a new communication mechanism +const vscode = { + postMessage: (message: any) => { + // In a real browser environment, this would be `window.vscode.postMessage` + console.log("Message to workbench:", message); + window.postMessage(message, '*'); + } +}; + +const App = () => { + const [state, setState] = useState(null); + const [inputText, setInputText] = useState(""); + + useEffect(() => { + const handleMessage = (event: any) => { + const message = event.data; + if (message.type === 'state') { + setState(message.state); + } + }; + window.addEventListener("message", handleMessage); + return () => window.removeEventListener("message", handleMessage); + }, []); + + const handleStartTask = () => { + vscode.postMessage({ type: 'startTask', text: inputText }); + }; + + if (!state) { + return
Loading...
; + } + + return ( +
+

Cortex IDE Agent

+ {/* We will add the chat history here */} + +
+