mirror of
https://github.com/microsoft/vscode.git
synced 2026-05-31 00:10:04 +08:00
agents: add "Debug Local Agent Host Process In Dev Tools" command (#312246)
agents: add 'Debug Local Agent Host Process In Dev Tools' command Adds a Developer command, available in both VS Code and the Agents app, that opens a Chrome DevTools window attached to the local agent host utility analogous to the existing 'Debug Extension Host Inprocess Dev Tools' command. The agent host process enables its inspector live via node:inspector on demand, so no restart is required. The renderer reaches the inspector URL through the existing agent-host IPC channel. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
This commit is contained in:
@@ -1509,6 +1509,7 @@ export default tseslint.config(
|
||||
'fs/promises',
|
||||
'http',
|
||||
'https',
|
||||
'inspector',
|
||||
'minimist',
|
||||
'node:module',
|
||||
'native-keymap',
|
||||
|
||||
@@ -7,7 +7,7 @@ import { Event } from '../../../base/common/event.js';
|
||||
import { IReference } from '../../../base/common/lifecycle.js';
|
||||
import { constObservable, IObservable } from '../../../base/common/observable.js';
|
||||
import { URI } from '../../../base/common/uri.js';
|
||||
import type { IAgentCreateSessionConfig, IAgentHostService, IAgentHostSocketInfo, IAgentResolveSessionConfigParams, IAgentSessionConfigCompletionsParams, IAgentSessionMetadata, AuthenticateParams, AuthenticateResult } from '../common/agentService.js';
|
||||
import type { IAgentCreateSessionConfig, IAgentHostInspectInfo, IAgentHostService, IAgentHostSocketInfo, IAgentResolveSessionConfigParams, IAgentSessionConfigCompletionsParams, IAgentSessionMetadata, AuthenticateParams, AuthenticateResult } from '../common/agentService.js';
|
||||
import type { IAgentSubscription } from '../common/state/agentSubscription.js';
|
||||
import type { CreateTerminalParams, ResolveSessionConfigResult, SessionConfigCompletionsResult } from '../common/state/protocol/commands.js';
|
||||
import type { ActionEnvelope, INotification, SessionAction, TerminalAction } from '../common/state/sessionActions.js';
|
||||
@@ -45,6 +45,7 @@ export class NullAgentHostService implements IAgentHostService {
|
||||
async resolveSessionConfig(_params: IAgentResolveSessionConfigParams): Promise<ResolveSessionConfigResult> { return notSupported(); }
|
||||
async sessionConfigCompletions(_params: IAgentSessionConfigCompletionsParams): Promise<SessionConfigCompletionsResult> { return notSupported(); }
|
||||
async startWebSocketServer(): Promise<IAgentHostSocketInfo> { return notSupported(); }
|
||||
async getInspectInfo(_tryEnable: boolean): Promise<IAgentHostInspectInfo | undefined> { return undefined; }
|
||||
async disposeSession(_session: URI): Promise<void> { }
|
||||
async createTerminal(_params: CreateTerminalParams): Promise<void> { notSupported(); }
|
||||
async disposeTerminal(_terminal: URI): Promise<void> { }
|
||||
|
||||
@@ -41,6 +41,14 @@ export interface IAgentHostSocketInfo {
|
||||
readonly socketPath: string;
|
||||
}
|
||||
|
||||
/** Inspector listener information for the agent host process. */
|
||||
export interface IAgentHostInspectInfo {
|
||||
readonly host: string;
|
||||
readonly port: number;
|
||||
/** A `devtools://` URL that can be opened with `INativeHostService.openDevToolsWindow`. */
|
||||
readonly devtoolsUrl: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* IPC service exposed on the {@link AgentHostIpcChannels.ConnectionTracker}
|
||||
* channel. Used by the server process for lifetime management and by the
|
||||
@@ -55,6 +63,14 @@ export interface IConnectionTrackerService {
|
||||
* If a server is already running, returns the existing info.
|
||||
*/
|
||||
startWebSocketServer(): Promise<IAgentHostSocketInfo>;
|
||||
|
||||
/**
|
||||
* Get inspector listener info for the agent host process. If the inspector
|
||||
* is not currently active and `tryEnable` is true, opens the inspector on
|
||||
* a random local port. Returns `undefined` if the inspector cannot be
|
||||
* enabled (e.g. running in an environment without `node:inspector`).
|
||||
*/
|
||||
getInspectInfo(tryEnable: boolean): Promise<IAgentHostInspectInfo | undefined>;
|
||||
}
|
||||
|
||||
// ---- IPC data types (serializable across MessagePort) -----------------------
|
||||
@@ -708,4 +724,12 @@ export interface IAgentHostService extends IAgentConnection {
|
||||
restartAgentHost(): Promise<void>;
|
||||
|
||||
startWebSocketServer(): Promise<IAgentHostSocketInfo>;
|
||||
|
||||
/**
|
||||
* Get inspector listener info for the agent host process. If the inspector
|
||||
* is not currently active and `tryEnable` is true, opens the inspector on
|
||||
* a random local port. Returns `undefined` if the inspector cannot be
|
||||
* enabled.
|
||||
*/
|
||||
getInspectInfo(tryEnable: boolean): Promise<IAgentHostInspectInfo | undefined>;
|
||||
}
|
||||
|
||||
@@ -14,7 +14,7 @@ import { acquirePort } from '../../../base/parts/ipc/electron-browser/ipc.mp.js'
|
||||
import { InstantiationType, registerSingleton } from '../../instantiation/common/extensions.js';
|
||||
import { IConfigurationService } from '../../configuration/common/configuration.js';
|
||||
import { ILogService } from '../../log/common/log.js';
|
||||
import { AgentHostEnabledSettingId, AgentHostIpcChannels, IAgentCreateSessionConfig, IAgentHostService, IAgentResolveSessionConfigParams, IAgentService, IAgentSessionConfigCompletionsParams, IAgentSessionMetadata, AuthenticateParams, AuthenticateResult, IAgentHostSocketInfo, IConnectionTrackerService } from '../common/agentService.js';
|
||||
import { AgentHostEnabledSettingId, AgentHostIpcChannels, IAgentCreateSessionConfig, IAgentHostInspectInfo, IAgentHostService, IAgentResolveSessionConfigParams, IAgentService, IAgentSessionConfigCompletionsParams, IAgentSessionMetadata, AuthenticateParams, AuthenticateResult, IAgentHostSocketInfo, IConnectionTrackerService } from '../common/agentService.js';
|
||||
import { AgentSubscriptionManager, type IAgentSubscription } from '../common/state/agentSubscription.js';
|
||||
import type { CreateTerminalParams, ResolveSessionConfigResult, SessionConfigCompletionsResult } from '../common/state/protocol/commands.js';
|
||||
import type { ActionEnvelope, INotification, SessionAction, TerminalAction } from '../common/state/sessionActions.js';
|
||||
@@ -210,6 +210,10 @@ class AgentHostServiceClient extends Disposable implements IAgentHostService {
|
||||
startWebSocketServer(): Promise<IAgentHostSocketInfo> {
|
||||
return this._connectionTracker.startWebSocketServer();
|
||||
}
|
||||
|
||||
getInspectInfo(tryEnable: boolean): Promise<IAgentHostInspectInfo | undefined> {
|
||||
return this._connectionTracker.getInspectInfo(tryEnable);
|
||||
}
|
||||
}
|
||||
|
||||
registerSingleton(IAgentHostService, AgentHostServiceClient, InstantiationType.Delayed);
|
||||
|
||||
@@ -13,7 +13,8 @@ import { isWindows } from '../../../base/common/platform.js';
|
||||
import { URI } from '../../../base/common/uri.js';
|
||||
import { generateUuid } from '../../../base/common/uuid.js';
|
||||
import * as os from 'os';
|
||||
import { AgentHostIpcChannels, IAgentHostSocketInfo, IConnectionTrackerService } from '../common/agentService.js';
|
||||
import * as inspector from 'inspector';
|
||||
import { AgentHostIpcChannels, IAgentHostInspectInfo, IAgentHostSocketInfo, IConnectionTrackerService } from '../common/agentService.js';
|
||||
import { AgentService } from './agentService.js';
|
||||
import { IAgentHostTerminalManager } from './agentHostTerminalManager.js';
|
||||
import { CopilotAgent } from './copilot/copilotAgent.js';
|
||||
@@ -148,6 +149,52 @@ function startAgentHost(): void {
|
||||
dynamicSocketInfo = { socketPath };
|
||||
return dynamicSocketInfo;
|
||||
},
|
||||
async getInspectInfo(tryEnable: boolean): Promise<IAgentHostInspectInfo | undefined> {
|
||||
let url = inspector.url();
|
||||
if (!url && tryEnable) {
|
||||
try {
|
||||
inspector.open(0, '127.0.0.1', false);
|
||||
} catch (err) {
|
||||
logService.error('[AgentHost] Failed to open inspector', err);
|
||||
return undefined;
|
||||
}
|
||||
url = inspector.url();
|
||||
}
|
||||
if (!url) {
|
||||
return undefined;
|
||||
}
|
||||
// Inspector URL looks like: ws://host:port/uuid (host may be IPv6 in brackets)
|
||||
try {
|
||||
const parsedUrl = new URL(url);
|
||||
if (parsedUrl.protocol !== 'ws:') {
|
||||
logService.warn(`[AgentHost] Unexpected inspector URL: ${url}`);
|
||||
return undefined;
|
||||
}
|
||||
|
||||
const port = Number(parsedUrl.port);
|
||||
const auth = parsedUrl.pathname.replace(/^\/+/, '');
|
||||
if (!Number.isInteger(port) || !auth) {
|
||||
logService.warn(`[AgentHost] Unexpected inspector URL: ${url}`);
|
||||
return undefined;
|
||||
}
|
||||
|
||||
const host = parsedUrl.hostname === '0.0.0.0'
|
||||
? '127.0.0.1'
|
||||
: parsedUrl.hostname === '::'
|
||||
? '::1'
|
||||
: parsedUrl.hostname;
|
||||
const devtoolsHost = host.includes(':') ? `[${host}]` : host;
|
||||
|
||||
return {
|
||||
host,
|
||||
port,
|
||||
devtoolsUrl: `devtools://devtools/bundled/js_app.html?v8only=true&ws=${devtoolsHost}:${parsedUrl.port}/${auth}`,
|
||||
};
|
||||
} catch {
|
||||
logService.warn(`[AgentHost] Unexpected inspector URL: ${url}`);
|
||||
return undefined;
|
||||
}
|
||||
},
|
||||
};
|
||||
const connectionTrackerChannel = ProxyChannel.fromService(connectionTrackerService, disposables);
|
||||
server.registerChannel(AgentHostIpcChannels.ConnectionTracker, connectionTrackerChannel);
|
||||
|
||||
@@ -23,6 +23,7 @@ import { CopilotCLISessionType } from '../../../services/sessions/common/session
|
||||
import { ISessionsManagementService } from '../../../services/sessions/common/sessionsManagement.js';
|
||||
import { ISessionsProvidersService } from '../../../services/sessions/browser/sessionsProvidersService.js';
|
||||
import { resolveRemoteAuthority } from '../browser/openInVSCodeUtils.js';
|
||||
import { DebugAgentHostInDevToolsAction } from '../../../../workbench/contrib/chat/electron-browser/actions/debugAgentHostAction.js';
|
||||
|
||||
/**
|
||||
* Desktop version of the "Open in VS Code" action.
|
||||
@@ -89,3 +90,5 @@ registerAction2(class OpenSessionWorktreeInVSCodeAction extends Action2 {
|
||||
await nativeHostService.launchSiblingApp(args);
|
||||
}
|
||||
});
|
||||
|
||||
registerAction2(DebugAgentHostInDevToolsAction);
|
||||
|
||||
@@ -10,9 +10,11 @@ import { Action2, registerAction2 } from '../../../../../platform/actions/common
|
||||
import { INativeHostService } from '../../../../../platform/native/common/native.js';
|
||||
import { ChatContextKeys } from '../../common/actions/chatContextKeys.js';
|
||||
import { IChatService } from '../../common/chatService/chatService.js';
|
||||
import { DebugAgentHostInDevToolsAction } from './debugAgentHostAction.js';
|
||||
|
||||
export function registerChatDeveloperActions() {
|
||||
registerAction2(OpenChatStorageFolderAction);
|
||||
registerAction2(DebugAgentHostInDevToolsAction);
|
||||
}
|
||||
|
||||
class OpenChatStorageFolderAction extends Action2 {
|
||||
|
||||
@@ -0,0 +1,43 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { Codicon } from '../../../../../base/common/codicons.js';
|
||||
import { ServicesAccessor } from '../../../../../editor/browser/editorExtensions.js';
|
||||
import { localize, localize2 } from '../../../../../nls.js';
|
||||
import { Categories } from '../../../../../platform/action/common/actionCommonCategories.js';
|
||||
import { Action2 } from '../../../../../platform/actions/common/actions.js';
|
||||
import { IAgentHostService } from '../../../../../platform/agentHost/common/agentService.js';
|
||||
import { INativeHostService } from '../../../../../platform/native/common/native.js';
|
||||
import { INotificationService } from '../../../../../platform/notification/common/notification.js';
|
||||
import { ChatContextKeys } from '../../common/actions/chatContextKeys.js';
|
||||
|
||||
export class DebugAgentHostInDevToolsAction extends Action2 {
|
||||
static readonly ID = 'workbench.action.chat.debugAgentHostInDevTools';
|
||||
|
||||
constructor() {
|
||||
super({
|
||||
id: DebugAgentHostInDevToolsAction.ID,
|
||||
title: localize2('debugAgentHostInDevTools', "Debug Local Agent Host Process In Dev Tools"),
|
||||
category: Categories.Developer,
|
||||
f1: true,
|
||||
icon: Codicon.debugStart,
|
||||
precondition: ChatContextKeys.enabled,
|
||||
});
|
||||
}
|
||||
|
||||
override async run(accessor: ServicesAccessor): Promise<void> {
|
||||
const agentHostService = accessor.get(IAgentHostService);
|
||||
const nativeHostService = accessor.get(INativeHostService);
|
||||
const notificationService = accessor.get(INotificationService);
|
||||
|
||||
const info = await agentHostService.getInspectInfo(true);
|
||||
if (!info) {
|
||||
notificationService.warn(localize('debugAgentHost.noInspectPort', "Could not enable the Node.js inspector for the agent host process."));
|
||||
return;
|
||||
}
|
||||
|
||||
nativeHostService.openDevToolsWindow(info.devtoolsUrl);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user