mirror of
https://github.com/microsoft/vscode.git
synced 2026-08-17 03:58:49 +01:00
agentHost: Respect telemetry disablement from process launch (#330929)
* agentHost: Respect telemetry disablement during initialization Send each client's effective telemetry level with initialize and reconnect so the host applies consent before connection telemetry or queued actions. Keep the host disabled until a client level arrives, and propagate process-level restrictions to generic remote telemetry.\n\n(Written by Copilot)\n\nCo-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * agentHost: Preserve telemetry wrapper defaults for direct callers Keep fail-closed startup explicit to the production factory while preserving the established constructor behavior used by isolated Agent Host components and tests.\n\n(Written by Copilot)\n\nCo-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * agentHost: Gate telemetry on client consent Keep seeded root configuration from enabling telemetry before initialize or reconnect provides a client telemetry level. Preserve existing direct-construction defaults for isolated callers and tests.\n\n(Written by Copilot)\n\nCo-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * agentHost: Propagate telemetry level at process launch Start controlled Agent Host processes with the launcher's effective telemetry level so opted-in clients retain early diagnostics while opted-out clients disable telemetry before startup. Keep initialize and reconnect updates as a monotonic multi-client clamp across local, remote-server, SSH, WSL, and CLI-supervised hosts.\n\n(Written by Copilot)\n\nCo-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * Update generated extension point cache Include the link presentation provider extension point generated by hygiene after merging origin/main.\n\n(Written by Copilot)\n\nCo-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * agentHost: Derive SSH telemetry in shared process Use the shared process telemetry service when launching SSH and WSL Agent Hosts instead of threading the telemetry level through renderer IPC contracts.\n\n(Written by Copilot)\n\nCo-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
This commit is contained in:
@@ -15,6 +15,7 @@ use tokio::io::{AsyncBufReadExt, BufReader};
|
||||
use crate::auth::Auth;
|
||||
use crate::constants::{self, AGENT_HOST_PORT};
|
||||
use crate::log;
|
||||
use crate::options::TelemetryLevel;
|
||||
use crate::state::LauncherPaths;
|
||||
use crate::tunnels::agent_host::{
|
||||
classify_agent_host, serve_agent_host_tunnel_connection, AgentHostConfig, AgentHostManager,
|
||||
@@ -288,6 +289,11 @@ async fn run_supervisor(mut ctx: CommandContext, mut args: AgentHostArgs) -> Res
|
||||
Arc::new(ReqwestSimpleHttp::with_client(ctx.http.clone())),
|
||||
AgentHostConfig {
|
||||
server_data_dir: args.server_data_dir.clone(),
|
||||
telemetry_level: if ctx.args.global_options.disable_telemetry {
|
||||
Some(TelemetryLevel::Off)
|
||||
} else {
|
||||
ctx.args.global_options.telemetry_level
|
||||
},
|
||||
// The AH backend runs on an internal-only unix socket / named
|
||||
// pipe between this supervisor and its child, so we
|
||||
// deliberately disable the backend's token check; this
|
||||
|
||||
@@ -31,7 +31,7 @@ use crate::async_pipe::{
|
||||
use crate::constants::VSCODE_CLI_QUALITY;
|
||||
use crate::download_cache::DownloadCache;
|
||||
use crate::log;
|
||||
use crate::options::Quality;
|
||||
use crate::options::{Quality, TelemetryLevel};
|
||||
use crate::state::LauncherPaths;
|
||||
use crate::update_service::{
|
||||
unzip_downloaded_release, Platform, Release, TargetKind, UpdateService,
|
||||
@@ -98,6 +98,7 @@ const UPGRADE_KILL_DELAY: Duration = Duration::from_secs(3);
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct AgentHostConfig {
|
||||
pub server_data_dir: Option<String>,
|
||||
pub telemetry_level: Option<TelemetryLevel>,
|
||||
pub without_connection_token: bool,
|
||||
pub connection_token: Option<String>,
|
||||
pub connection_token_file: Option<String>,
|
||||
@@ -262,6 +263,10 @@ impl AgentHostManager {
|
||||
cmd.arg("--server-data-dir");
|
||||
cmd.arg(a);
|
||||
}
|
||||
if let Some(level) = self.config.telemetry_level {
|
||||
cmd.arg("--telemetry-level");
|
||||
cmd.arg(level.to_string());
|
||||
}
|
||||
if self.config.without_connection_token {
|
||||
cmd.arg("--without-connection-token");
|
||||
}
|
||||
@@ -2255,6 +2260,7 @@ mod tests {
|
||||
Arc::new(ReqwestSimpleHttp::new()),
|
||||
AgentHostConfig {
|
||||
server_data_dir: None,
|
||||
telemetry_level: None,
|
||||
without_connection_token: true,
|
||||
connection_token: None,
|
||||
connection_token_file: None,
|
||||
|
||||
@@ -37,12 +37,12 @@ import { ChatSourceKind, ContentEncoding, ResourceRequestParams, type Completion
|
||||
import type { InvokeChangesetOperationParams, InvokeChangesetOperationResult } from '../common/state/protocol/channels-changeset/commands.js';
|
||||
import { encodeBase64 } from '../../../base/common/buffer.js';
|
||||
import { ILoadEstimator, LoadEstimator } from '../../../base/parts/ipc/common/ipc.net.js';
|
||||
import { ITelemetryService, TELEMETRY_CRASH_REPORTER_SETTING_ID, TELEMETRY_OLD_SETTING_ID, TELEMETRY_SETTING_ID, TelemetryLevel, telemetryLevelEnabled } from '../../telemetry/common/telemetry.js';
|
||||
import { ITelemetryService, TelemetryLevel, TELEMETRY_CRASH_REPORTER_SETTING_ID, TELEMETRY_OLD_SETTING_ID, TELEMETRY_SETTING_ID } from '../../telemetry/common/telemetry.js';
|
||||
import { getTelemetryLevel } from '../../telemetry/common/telemetryUtils.js';
|
||||
import { AgentHostTelemetryLevelConfigKey, AgentHostTerminalAutoApproveEnabledConfigKey, AgentHostTerminalAutoApproveRulesConfigKey, AgentHostDisableRepoInfoTelemetryConfigKey, getAgentHostTerminalAutoApproveRulesConfig, TERMINAL_AUTO_APPROVE_ENABLED_SETTING_ID, TERMINAL_AUTO_APPROVE_SETTING_ID, TERMINAL_IGNORE_DEFAULT_AUTO_APPROVE_RULES_SETTING_ID, DISABLE_REPO_INFO_TELEMETRY_SETTING_ID, telemetryLevelToAgentHostConfigValue } from '../common/agentHostSchema.js';
|
||||
import { getAgentHostConfigurationSyncEntries, resolveAgentHostConfigurationSyncPatch, resolveAgentHostConfigurationSyncValue } from '../common/agentHostConfigurationSync.js';
|
||||
import { managedPermissionsConfigurationIds, resolveManagedSettingsPermissions, type IAgentHostManagedSettingsPermissions } from '../common/agentHostManagedSettings.js';
|
||||
import { AgentHostClientConnectionKind, toClientTelemetryMeta } from '../common/agentHostTelemetry.js';
|
||||
import { AgentHostClientConnectionKind, toAgentHostClientMeta } from '../common/agentHostTelemetry.js';
|
||||
import type { OtlpExportLogsParams } from '../common/state/protocol/channels-otlp/notifications.js';
|
||||
import type { TelemetryCapabilities } from '../common/state/protocol/channels-otlp/state.js';
|
||||
import type { Implementation, InitializeResult } from '../common/state/protocol/common/commands.js';
|
||||
@@ -455,7 +455,7 @@ export class RemoteAgentHostProtocolClient extends Disposable implements IAgentC
|
||||
protocolVersions: [...SUPPORTED_PROTOCOL_VERSIONS],
|
||||
clientId: this._clientId,
|
||||
clientInfo: this._clientInfo,
|
||||
...this._clientConnectionTelemetryMeta(),
|
||||
_meta: this._clientMeta(),
|
||||
initialSubscriptions: [ROOT_STATE_URI],
|
||||
}, { bypassInitializeQueue: true });
|
||||
this._applyInitializeResult(result);
|
||||
@@ -705,7 +705,7 @@ export class RemoteAgentHostProtocolClient extends Disposable implements IAgentC
|
||||
clientId: this._clientId,
|
||||
lastSeenServerSeq,
|
||||
subscriptions,
|
||||
...this._clientConnectionTelemetryMeta(),
|
||||
_meta: this._clientMeta(),
|
||||
}, { bypassReconnectGate: true });
|
||||
return { result, freshInitialize: false };
|
||||
} catch (error) {
|
||||
@@ -720,7 +720,7 @@ export class RemoteAgentHostProtocolClient extends Disposable implements IAgentC
|
||||
protocolVersions: [...SUPPORTED_PROTOCOL_VERSIONS],
|
||||
clientId: this._clientId,
|
||||
clientInfo: this._clientInfo,
|
||||
...this._clientConnectionTelemetryMeta(),
|
||||
_meta: this._clientMeta(),
|
||||
initialSubscriptions: subscriptions,
|
||||
}, { bypassReconnectGate: true });
|
||||
this._applyInitializeResult(initializeResult, false);
|
||||
@@ -774,12 +774,15 @@ export class RemoteAgentHostProtocolClient extends Disposable implements IAgentC
|
||||
}, { bypassReconnectGate: true })));
|
||||
}
|
||||
|
||||
private _clientConnectionTelemetryMeta(): { _meta: Record<string, unknown> } | Record<string, never> {
|
||||
const sendIdentity = telemetryLevelEnabled(this._telemetryService, TelemetryLevel.USAGE);
|
||||
const machineId = sendIdentity ? this._telemetryService.machineId : undefined;
|
||||
const devDeviceId = sendIdentity ? this._telemetryService.devDeviceId : undefined;
|
||||
const meta = toClientTelemetryMeta(this._transport.clientConnectionKind, machineId, devDeviceId);
|
||||
return meta ? { _meta: meta } : {};
|
||||
private _clientMeta(): Record<string, unknown> {
|
||||
const telemetryLevel = this._effectiveTelemetryLevel();
|
||||
const sendIdentity = telemetryLevel >= TelemetryLevel.USAGE;
|
||||
return toAgentHostClientMeta(
|
||||
this._transport.clientConnectionKind,
|
||||
telemetryLevel,
|
||||
sendIdentity ? this._telemetryService.machineId : undefined,
|
||||
sendIdentity ? this._telemetryService.devDeviceId : undefined,
|
||||
);
|
||||
}
|
||||
|
||||
private _applyInitializeResult(result: CommandMap['initialize']['result'], forwardClientConfig = true): void {
|
||||
@@ -1616,7 +1619,11 @@ export class RemoteAgentHostProtocolClient extends Disposable implements IAgentC
|
||||
}
|
||||
|
||||
private _updateTelemetryLevel(): void {
|
||||
this._dispatchRootConfig({ [AgentHostTelemetryLevelConfigKey]: telemetryLevelToAgentHostConfigValue(getTelemetryLevel(this._configurationService)) });
|
||||
this._dispatchRootConfig({ [AgentHostTelemetryLevelConfigKey]: telemetryLevelToAgentHostConfigValue(this._effectiveTelemetryLevel()) });
|
||||
}
|
||||
|
||||
private _effectiveTelemetryLevel(): TelemetryLevel {
|
||||
return Math.min(getTelemetryLevel(this._configurationService), this._telemetryService.telemetryLevel);
|
||||
}
|
||||
|
||||
/** Merge a patch into the agent host's root configuration. */
|
||||
|
||||
@@ -9,6 +9,7 @@ import { ConfigurationTarget, type IConfigurationService, type IConfigurationVal
|
||||
import { DEFAULT_EDIT_AUTO_APPROVE_PATTERNS, type ChatEditAutoApprovePatterns } from '../../chat/common/chatSettings.js';
|
||||
import type { IMcpServerConfiguration } from '../../mcp/common/mcpPlatformTypes.js';
|
||||
import { TelemetryConfiguration, TelemetryLevel } from '../../telemetry/common/telemetry.js';
|
||||
import { telemetryLevelToAgentHostValue } from './agentHostTelemetry.js';
|
||||
import { SessionConfigKey } from './sessionConfigKeys.js';
|
||||
import type { SessionConfigPropertySchema, SessionConfigSchema } from './state/protocol/commands.js';
|
||||
import { JsonRpcErrorCodes, ProtocolError } from './state/sessionProtocol.js';
|
||||
@@ -593,16 +594,7 @@ export const AgentHostMcpServersConfigKey = 'mcpServers';
|
||||
export type AgentHostMcpServers = Record<string, IMcpServerConfiguration>;
|
||||
|
||||
export function telemetryLevelToAgentHostConfigValue(telemetryLevel: TelemetryLevel): TelemetryConfiguration {
|
||||
switch (telemetryLevel) {
|
||||
case TelemetryLevel.NONE:
|
||||
return TelemetryConfiguration.OFF;
|
||||
case TelemetryLevel.CRASH:
|
||||
return TelemetryConfiguration.CRASH;
|
||||
case TelemetryLevel.ERROR:
|
||||
return TelemetryConfiguration.ERROR;
|
||||
case TelemetryLevel.USAGE:
|
||||
return TelemetryConfiguration.ON;
|
||||
}
|
||||
return telemetryLevelToAgentHostValue(telemetryLevel);
|
||||
}
|
||||
|
||||
export function agentHostConfigValueToTelemetryLevel(value: unknown): TelemetryLevel | undefined {
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { TelemetryConfiguration, TelemetryLevel } from '../../telemetry/common/telemetry.js';
|
||||
import type { AgentHostClientType } from './agentHostClientInfo.js';
|
||||
|
||||
export const enum AgentHostLaunchKind {
|
||||
@@ -49,11 +50,14 @@ export function createUnknownAgentHostClientTelemetryContext(clientType: AgentHo
|
||||
}
|
||||
|
||||
const CLIENT_CONNECTION_KIND_META_KEY = 'vscode.clientConnectionKind';
|
||||
const CLIENT_TELEMETRY_LEVEL_META_KEY = 'vscode.telemetryLevel';
|
||||
const CLIENT_MACHINE_ID_META_KEY = 'vscode.clientMachineId';
|
||||
const CLIENT_DEV_DEVICE_ID_META_KEY = 'vscode.clientDevDeviceId';
|
||||
|
||||
export function toClientTelemetryMeta(connectionKind: AgentHostClientConnectionKind | undefined, machineId: string | undefined, devDeviceId: string | undefined): Record<string, unknown> | undefined {
|
||||
const meta: Record<string, unknown> = {};
|
||||
export function toAgentHostClientMeta(connectionKind: AgentHostClientConnectionKind | undefined, telemetryLevel: TelemetryLevel, machineId: string | undefined, devDeviceId: string | undefined): Record<string, unknown> {
|
||||
const meta: Record<string, unknown> = {
|
||||
[CLIENT_TELEMETRY_LEVEL_META_KEY]: telemetryLevelToAgentHostValue(telemetryLevel),
|
||||
};
|
||||
if (connectionKind !== undefined && connectionKind !== AgentHostClientConnectionKind.Unknown) {
|
||||
meta[CLIENT_CONNECTION_KIND_META_KEY] = connectionKind;
|
||||
}
|
||||
@@ -63,7 +67,7 @@ export function toClientTelemetryMeta(connectionKind: AgentHostClientConnectionK
|
||||
if (devDeviceId) {
|
||||
meta[CLIENT_DEV_DEVICE_ID_META_KEY] = devDeviceId;
|
||||
}
|
||||
return Object.keys(meta).length > 0 ? meta : undefined;
|
||||
return meta;
|
||||
}
|
||||
|
||||
export function readClientConnectionKind(meta: Record<string, unknown> | undefined): AgentHostClientConnectionKind {
|
||||
@@ -82,6 +86,35 @@ export function readClientConnectionKind(meta: Record<string, unknown> | undefin
|
||||
}
|
||||
}
|
||||
|
||||
export function readClientTelemetryLevel(meta: Record<string, unknown> | undefined): TelemetryLevel | undefined {
|
||||
const value = meta?.[CLIENT_TELEMETRY_LEVEL_META_KEY];
|
||||
switch (value) {
|
||||
case TelemetryConfiguration.OFF:
|
||||
return TelemetryLevel.NONE;
|
||||
case TelemetryConfiguration.CRASH:
|
||||
return TelemetryLevel.CRASH;
|
||||
case TelemetryConfiguration.ERROR:
|
||||
return TelemetryLevel.ERROR;
|
||||
case TelemetryConfiguration.ON:
|
||||
return TelemetryLevel.USAGE;
|
||||
default:
|
||||
return value === undefined ? undefined : TelemetryLevel.NONE;
|
||||
}
|
||||
}
|
||||
|
||||
export function telemetryLevelToAgentHostValue(telemetryLevel: TelemetryLevel): TelemetryConfiguration {
|
||||
switch (telemetryLevel) {
|
||||
case TelemetryLevel.NONE:
|
||||
return TelemetryConfiguration.OFF;
|
||||
case TelemetryLevel.CRASH:
|
||||
return TelemetryConfiguration.CRASH;
|
||||
case TelemetryLevel.ERROR:
|
||||
return TelemetryConfiguration.ERROR;
|
||||
case TelemetryLevel.USAGE:
|
||||
return TelemetryConfiguration.ON;
|
||||
}
|
||||
}
|
||||
|
||||
export function readClientMachineId(meta: Record<string, unknown> | undefined): string | undefined {
|
||||
return readClientTelemetryIdentity(meta, CLIENT_MACHINE_ID_META_KEY);
|
||||
}
|
||||
|
||||
@@ -21,6 +21,7 @@
|
||||
export const AgentHostMachineIdEnvKey = 'VSCODE_AGENT_HOST_MACHINE_ID';
|
||||
export const AgentHostSqmIdEnvKey = 'VSCODE_AGENT_HOST_SQM_ID';
|
||||
export const AgentHostDevDeviceIdEnvKey = 'VSCODE_AGENT_HOST_DEV_DEVICE_ID';
|
||||
export const AgentHostTelemetryLevelEnvKey = 'VSCODE_AGENT_HOST_TELEMETRY_LEVEL';
|
||||
|
||||
export interface IAgentHostForwardedTelemetryIds {
|
||||
readonly machineId: string;
|
||||
|
||||
@@ -17,11 +17,12 @@ import { ILifecycleMainService } from '../../lifecycle/electron-main/lifecycleMa
|
||||
import { ILogService } from '../../log/common/log.js';
|
||||
import { Schemas } from '../../../base/common/network.js';
|
||||
import { getResolvedShellEnv } from '../../shell/node/shellEnv.js';
|
||||
import { ITelemetryService } from '../../telemetry/common/telemetry.js';
|
||||
import { NullTelemetryService } from '../../telemetry/common/telemetryUtils.js';
|
||||
import { UtilityProcess } from '../../utilityProcess/electron-main/utilityProcess.js';
|
||||
import { AgentHostStartError, IAgentHostConnection, IAgentHostShutdownRequest, IAgentHostStarter, IAgentHostStartRequest } from '../common/agent.js';
|
||||
import { buildAgentHostTelemetryIdEnv, IAgentHostForwardedTelemetryIds } from '../common/agentHostTelemetryEnv.js';
|
||||
import { AgentHostLaunchKind, AgentHostLaunchKindEnvVar } from '../common/agentHostTelemetry.js';
|
||||
import { AgentHostLaunchKind, AgentHostLaunchKindEnvVar, telemetryLevelToAgentHostValue } from '../common/agentHostTelemetry.js';
|
||||
import { AgentHostByokModelsEnabledSettingId, AgentHostClaudeAgentEnabledSettingId, AgentHostCodexAgentBinaryArgsSettingId, AgentHostCodexAgentEnabledSettingId, AgentHostCodexAgentSdkRootSettingId, AgentHostCodexAgentCodexHomeSettingId, AgentHostIpcChannels, AgentHostOTelCaptureContentSettingId, AgentHostOTelDbSpanExporterEnabledSettingId, AgentHostOTelEnabledSettingId, AgentHostOTelExporterTypeSettingId, AgentHostOTelOtlpEndpointSettingId, AgentHostOTelOtlpProtocolSettingId, AgentHostOTelOutfileSettingId, AgentHostOTelResourceAttributesSettingId, AgentHostOTelServiceNameSettingId, AgentHostOTelPolicyIpcChannel, AgentHostRestartIpcChannel, AgentHostWillRestartIpcChannel, buildAgentHostOTelEnv, buildAgentSdkEnv, IAgentHostManagementService, IAgentHostOTelSettings, sanitizeAgentHostOTelPolicySettings } from '../common/agentService.js';
|
||||
import { deepClone } from '../../../base/common/objects.js';
|
||||
import '../common/agentHostStarter.config.contribution.js';
|
||||
@@ -54,6 +55,7 @@ export class ElectronAgentHostStarter extends Disposable implements IAgentHostSt
|
||||
@IEnvironmentMainService private readonly _environmentMainService: IEnvironmentMainService,
|
||||
@ILifecycleMainService private readonly _lifecycleMainService: ILifecycleMainService,
|
||||
@ILogService private readonly _logService: ILogService,
|
||||
@ITelemetryService private readonly _telemetryService: ITelemetryService,
|
||||
) {
|
||||
super();
|
||||
|
||||
@@ -158,10 +160,8 @@ export class ElectronAgentHostStarter extends Disposable implements IAgentHostSt
|
||||
const args = [
|
||||
'--logsPath', this._environmentMainService.logsHome.with({ scheme: Schemas.file }).fsPath,
|
||||
'--user-data-dir', this._environmentMainService.userDataPath,
|
||||
'--telemetry-level', telemetryLevelToAgentHostValue(this._telemetryService.telemetryLevel),
|
||||
];
|
||||
if (this._environmentMainService.disableTelemetry) {
|
||||
args.push('--disable-telemetry');
|
||||
}
|
||||
|
||||
// Forward the host's resolved telemetry identifiers so the agent host
|
||||
// reuses the same persisted machineId/sqmId/devDeviceId instead of
|
||||
|
||||
@@ -23,7 +23,7 @@ import { TelemetryLogAppender } from '../../telemetry/common/telemetryLogAppende
|
||||
import { TelemetryService } from '../../telemetry/common/telemetryService.js';
|
||||
import { getPiiPathsFromEnvironment, isInternalTelemetry, isLoggingOnly, NullTelemetryService, supportsTelemetry, type ITelemetryAppender } from '../../telemetry/common/telemetryUtils.js';
|
||||
import { AgentHostTelemetryLevelConfigKey, agentHostConfigValueToTelemetryLevel } from '../common/agentHostSchema.js';
|
||||
import { AgentHostDevDeviceIdEnvKey, AgentHostMachineIdEnvKey, AgentHostSqmIdEnvKey } from '../common/agentHostTelemetryEnv.js';
|
||||
import { AgentHostDevDeviceIdEnvKey, AgentHostMachineIdEnvKey, AgentHostSqmIdEnvKey, AgentHostTelemetryLevelEnvKey } from '../common/agentHostTelemetryEnv.js';
|
||||
import { AgentHostRestrictedTelemetrySender, IAgentHostRestrictedTelemetry, IAgentHostInternalTelemetryContext, IAgentHostRestrictedTelemetryContext, TelemetryMeasurements, TelemetryProps } from './agentHostRestrictedTelemetry.js';
|
||||
import { AgentHostInternalTelemetrySender } from './agentHostMicrosoftTelemetry.js';
|
||||
|
||||
@@ -37,6 +37,7 @@ export interface IAgentHostTelemetryServiceOptions {
|
||||
readonly disableTelemetry?: boolean;
|
||||
readonly fetchFn?: typeof globalThis.fetch;
|
||||
readonly requestService?: IRequestService;
|
||||
readonly readTelemetryLevelEnvironment?: () => string | undefined;
|
||||
}
|
||||
|
||||
export interface IAgentHostTelemetryService extends ITelemetryService, IAgentHostRestrictedTelemetry {
|
||||
@@ -46,7 +47,7 @@ export interface IAgentHostTelemetryService extends ITelemetryService, IAgentHos
|
||||
export class AgentHostTelemetryService extends Disposable implements IAgentHostTelemetryService {
|
||||
declare readonly _serviceBrand: undefined;
|
||||
|
||||
private _telemetryLevel = TelemetryLevel.USAGE;
|
||||
private _telemetryLevel: TelemetryLevel;
|
||||
|
||||
/**
|
||||
* Whether the current Copilot token opts into enhanced/restricted telemetry (`rt=1`). Defaults
|
||||
@@ -61,8 +62,10 @@ export class AgentHostTelemetryService extends Disposable implements IAgentHostT
|
||||
private readonly _restricted?: IAgentHostRestrictedTelemetry,
|
||||
copilotSdkVersion?: string,
|
||||
copilotRuntimeVersion?: string,
|
||||
initialTelemetryLevel: TelemetryLevel = TelemetryLevel.USAGE,
|
||||
) {
|
||||
super();
|
||||
this._telemetryLevel = initialTelemetryLevel;
|
||||
if (isDisposable(_delegate)) {
|
||||
this._register(_delegate);
|
||||
}
|
||||
@@ -274,5 +277,15 @@ export async function createAgentHostTelemetryService(options: IAgentHostTelemet
|
||||
const internalSender = loggingOnly ? undefined : disposables.add(new AgentHostInternalTelemetrySender({ requestService: options.requestService, commonProperties, extensionVersion }));
|
||||
const restricted = loggingOnly ? undefined : new AgentHostRestrictedTelemetrySender(commonProperties, logService, undefined, internalSender, options.fetchFn);
|
||||
|
||||
return disposables.add(new AgentHostTelemetryService(telemetryService, restricted, productService.copilotVersions?.sdk, productService.copilotVersions?.runtime));
|
||||
const initialTelemetryLevel = Math.min(
|
||||
parseLaunchTelemetryLevel(environmentService.args?.['telemetry-level']),
|
||||
parseLaunchTelemetryLevel((options.readTelemetryLevelEnvironment ?? (() => process.env[AgentHostTelemetryLevelEnvKey]))()),
|
||||
);
|
||||
return disposables.add(new AgentHostTelemetryService(telemetryService, restricted, productService.copilotVersions?.sdk, productService.copilotVersions?.runtime, initialTelemetryLevel));
|
||||
}
|
||||
|
||||
function parseLaunchTelemetryLevel(value: string | undefined): TelemetryLevel {
|
||||
return value === undefined
|
||||
? TelemetryLevel.USAGE
|
||||
: agentHostConfigValueToTelemetryLevel(value) ?? TelemetryLevel.NONE;
|
||||
}
|
||||
|
||||
@@ -14,8 +14,9 @@ import { IEnvironmentService, INativeEnvironmentService } from '../../environmen
|
||||
import { parseAgentHostDebugPort } from '../../environment/node/environmentService.js';
|
||||
import { ILogService } from '../../log/common/log.js';
|
||||
import { getResolvedShellEnv } from '../../shell/node/shellEnv.js';
|
||||
import { ITelemetryService } from '../../telemetry/common/telemetry.js';
|
||||
import { IAgentHostConnection, IAgentHostStarter } from '../common/agent.js';
|
||||
import { AgentHostLaunchKind, AgentHostLaunchKindEnvVar } from '../common/agentHostTelemetry.js';
|
||||
import { AgentHostLaunchKind, AgentHostLaunchKindEnvVar, telemetryLevelToAgentHostValue } from '../common/agentHostTelemetry.js';
|
||||
import { AgentHostByokModelsEnabledSettingId, AgentHostClaudeAgentEnabledSettingId, AgentHostCodexAgentBinaryArgsSettingId, AgentHostCodexAgentEnabledSettingId, AgentHostCodexAgentSdkRootSettingId, AgentHostCodexAgentCodexHomeSettingId, AgentHostIpcChannels, AgentHostOTelCaptureContentSettingId, AgentHostOTelDbSpanExporterEnabledSettingId, AgentHostOTelEnabledSettingId, AgentHostOTelExporterTypeSettingId, AgentHostOTelOtlpEndpointSettingId, AgentHostOTelOtlpProtocolSettingId, AgentHostOTelOutfileSettingId, AgentHostOTelResourceAttributesSettingId, AgentHostOTelServiceNameSettingId, buildAgentHostOTelEnv, buildAgentSdkEnv, IAgentHostManagementService } from '../common/agentService.js';
|
||||
import '../common/agentHostStarter.config.contribution.js';
|
||||
|
||||
@@ -46,6 +47,7 @@ export class NodeAgentHostStarter extends Disposable implements IAgentHostStarte
|
||||
@IConfigurationService private readonly _configurationService: IConfigurationService,
|
||||
@IEnvironmentService private readonly _environmentService: INativeEnvironmentService,
|
||||
@ILogService private readonly _logService: ILogService,
|
||||
@ITelemetryService private readonly _telemetryService: ITelemetryService,
|
||||
) {
|
||||
super();
|
||||
}
|
||||
@@ -132,10 +134,8 @@ export class NodeAgentHostStarter extends Disposable implements IAgentHostStarte
|
||||
'--type=agentHost',
|
||||
'--logsPath', this._environmentService.logsHome.with({ scheme: Schemas.file }).fsPath,
|
||||
'--user-data-dir', this._environmentService.userDataPath,
|
||||
'--telemetry-level', telemetryLevelToAgentHostValue(this._telemetryService.telemetryLevel),
|
||||
];
|
||||
if (this._environmentService.disableTelemetry) {
|
||||
args.push('--disable-telemetry');
|
||||
}
|
||||
|
||||
const opts: IIPCOptions = {
|
||||
serverName: 'Agent Host',
|
||||
|
||||
@@ -15,7 +15,7 @@ import { ILogService } from '../../log/common/log.js';
|
||||
import { ITelemetryService } from '../../telemetry/common/telemetry.js';
|
||||
import { AHPFileSystemProvider } from '../common/agentHostFileSystemProvider.js';
|
||||
import { getAgentHostClientType } from '../common/agentHostClientInfo.js';
|
||||
import { AgentHostClientConnectionKind, AgentHostLaunchKind, AgentHostTransportKind, readClientConnectionKind, readClientDevDeviceId, readClientMachineId, type IAgentHostClientTelemetryContext } from '../common/agentHostTelemetry.js';
|
||||
import { AgentHostClientConnectionKind, AgentHostLaunchKind, AgentHostTransportKind, readClientConnectionKind, readClientDevDeviceId, readClientMachineId, readClientTelemetryLevel, type IAgentHostClientTelemetryContext } from '../common/agentHostTelemetry.js';
|
||||
import { AgentSession, type IAgentCreateChatOptions, type IMcpNotification } from '../common/agent.js';
|
||||
import { isManagedSettingsPermissions } from '../common/agentHostManagedSettings.js';
|
||||
import { type IAgentService } from '../common/agentService.js';
|
||||
@@ -66,6 +66,7 @@ import { isFileResourceRead } from '../common/resourceReadLogging.js';
|
||||
import type { Implementation } from '../common/state/protocol/common/commands.js';
|
||||
import { AGENT_HOST_CLIENT_CONNECTION_HISTORY_RETENTION, AgentHostClientConnectionTelemetryTracker } from './agentHostClientConnectionTelemetry.js';
|
||||
import { AgentHostTelemetryReporter } from './agentHostTelemetryReporter.js';
|
||||
import { isAgentHostTelemetryService } from './agentHostTelemetryService.js';
|
||||
|
||||
/** Default capacity of the server-side action replay buffer. */
|
||||
const REPLAY_BUFFER_CAPACITY = 1000;
|
||||
@@ -371,11 +372,11 @@ export class ProtocolServerHandler extends Disposable {
|
||||
private readonly _config: IProtocolServerConfig,
|
||||
private readonly _clientFileSystemProvider: AHPFileSystemProvider,
|
||||
@ILogService private readonly _logService: ILogService,
|
||||
@ITelemetryService telemetryService: ITelemetryService,
|
||||
@ITelemetryService private readonly _telemetryService: ITelemetryService,
|
||||
@IAgentHostManagedSettingsService private readonly _managedSettingsService: IAgentHostManagedSettingsService,
|
||||
) {
|
||||
super();
|
||||
this._telemetryReporter = new AgentHostTelemetryReporter(telemetryService);
|
||||
this._telemetryReporter = new AgentHostTelemetryReporter(this._telemetryService);
|
||||
this._connectionTelemetryTracker = this._config.connectionTelemetryTracker ?? this._register(new AgentHostClientConnectionTelemetryTracker());
|
||||
|
||||
this._register(this._server.onConnection(transport => {
|
||||
@@ -596,6 +597,7 @@ export class ProtocolServerHandler extends Disposable {
|
||||
}
|
||||
|
||||
const previousRecord = this._clients.get(params.clientId);
|
||||
this._applyClientTelemetryLevel(params._meta);
|
||||
const telemetryTransportToken = {};
|
||||
const initializationDisposables = disposables.add(new DisposableStore());
|
||||
const telemetryContext = this._createClientTelemetryContext(params.clientInfo, params._meta, transport);
|
||||
@@ -736,6 +738,7 @@ export class ProtocolServerHandler extends Disposable {
|
||||
if (!existingRecord) {
|
||||
throw new ProtocolError(AhpErrorCodes.NotFound, `Reconnect client not found: ${params.clientId}`);
|
||||
}
|
||||
this._applyClientTelemetryLevel(params._meta);
|
||||
|
||||
// Synchronously install the client so messages arriving on this transport
|
||||
// while we restore subscriptions can find a valid client object. The
|
||||
@@ -1194,6 +1197,13 @@ export class ProtocolServerHandler extends Disposable {
|
||||
};
|
||||
}
|
||||
|
||||
private _applyClientTelemetryLevel(meta: Record<string, unknown> | undefined): void {
|
||||
const telemetryLevel = readClientTelemetryLevel(meta);
|
||||
if (telemetryLevel !== undefined && isAgentHostTelemetryService(this._telemetryService)) {
|
||||
this._telemetryService.updateTelemetryLevel(telemetryLevel);
|
||||
}
|
||||
}
|
||||
|
||||
private _reportClientDisconnected(client: IConnectedClient, subscriptionCount: number): void {
|
||||
if (!client.telemetryConnectionActive) {
|
||||
return;
|
||||
|
||||
@@ -6,6 +6,7 @@
|
||||
import { timeout } from '../../../base/common/async.js';
|
||||
import { CancellationToken } from '../../../base/common/cancellation.js';
|
||||
import { vArray, vObj, vString, vUnknown } from '../../../base/common/validation.js';
|
||||
import { TelemetryConfiguration } from '../../telemetry/common/telemetry.js';
|
||||
import { getAgentHostEndpointIdentityKey, IAgentHostEndpointMetadata, parseAgentHostEndpointRegistry } from '../common/agentHostEndpointRegistry.js';
|
||||
|
||||
/**
|
||||
@@ -21,6 +22,18 @@ export function validateShellToken(value: string, label: string): string {
|
||||
return value;
|
||||
}
|
||||
|
||||
export function validateAgentHostTelemetryLevel(value: unknown): TelemetryConfiguration {
|
||||
switch (value) {
|
||||
case TelemetryConfiguration.OFF:
|
||||
case TelemetryConfiguration.CRASH:
|
||||
case TelemetryConfiguration.ERROR:
|
||||
case TelemetryConfiguration.ON:
|
||||
return value;
|
||||
default:
|
||||
throw new Error(`Unsafe telemetry level for shell interpolation: ${JSON.stringify(value)}`);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Validate and normalize a commit SHA. Returns the lowercase form.
|
||||
*
|
||||
@@ -127,8 +140,8 @@ export function shellEscape(s: string): string {
|
||||
* build them via {@link getRemoteCLIBin} / {@link getRemoteCLIDataDir}
|
||||
* which validate their components.
|
||||
*/
|
||||
export function buildAgentHostBaseCommand(cliBin: string, cliDataDir: string): string {
|
||||
return `${cliBin} --cli-data-dir ${cliDataDir} agent host --port 0`;
|
||||
export function buildAgentHostBaseCommand(cliBin: string, cliDataDir: string, telemetryLevel: TelemetryConfiguration): string {
|
||||
return `${cliBin} --cli-data-dir ${cliDataDir} --telemetry-level ${validateAgentHostTelemetryLevel(telemetryLevel)} agent host --port 0`;
|
||||
}
|
||||
|
||||
export function resolveRemotePlatform(unameS: string, unameM: string): { os: string; arch: string } | undefined {
|
||||
@@ -345,11 +358,11 @@ export function buildAgentEndpointsCommand(cliBin: string, cliDataDir: string, u
|
||||
* genuinely new process/registry entry every time this command runs,
|
||||
* leaving all existing standalone/editor entries untouched.
|
||||
*/
|
||||
export function buildAgentHostSpawnCommand(cliBin: string, cliDataDir: string, userDataPath: string, idleTimeoutSec = 300): string {
|
||||
export function buildAgentHostSpawnCommand(cliBin: string, cliDataDir: string, userDataPath: string, telemetryLevel: TelemetryConfiguration, idleTimeoutSec = 300): string {
|
||||
if (!Number.isSafeInteger(idleTimeoutSec) || idleTimeoutSec <= 0) {
|
||||
throw new Error(`Unsafe idle timeout value for shell interpolation: ${JSON.stringify(idleTimeoutSec)}`);
|
||||
}
|
||||
return `${buildAgentHostBaseCommand(cliBin, cliDataDir)} --new-instance --user-data-dir ${shellEscape(userDataPath)} --idle-timeout ${idleTimeoutSec}`;
|
||||
return `${buildAgentHostBaseCommand(cliBin, cliDataDir, telemetryLevel)} --new-instance --user-data-dir ${shellEscape(userDataPath)} --idle-timeout ${idleTimeoutSec}`;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -17,6 +17,7 @@ import { URI } from '../../../base/common/uri.js';
|
||||
import { localize } from '../../../nls.js';
|
||||
import { ILogService } from '../../log/common/log.js';
|
||||
import { IProductService } from '../../product/common/productService.js';
|
||||
import { ITelemetryService, TelemetryConfiguration } from '../../telemetry/common/telemetry.js';
|
||||
import {
|
||||
ISSHRemoteAgentHostMainService,
|
||||
SSHAuthMethod,
|
||||
@@ -46,6 +47,8 @@ import {
|
||||
} from './sshKnownHosts.js';
|
||||
import type { RemoteAgentHostLocationPreference } from '../common/remoteAgentHostLocationPreference.js';
|
||||
import type { IRelayMessage } from '../common/relayTransport.js';
|
||||
import { AgentHostTelemetryLevelEnvKey } from '../common/agentHostTelemetryEnv.js';
|
||||
import { telemetryLevelToAgentHostValue } from '../common/agentHostTelemetry.js';
|
||||
import {
|
||||
type AgentHostEndpointAddress,
|
||||
type AgentHostServerType,
|
||||
@@ -69,6 +72,7 @@ import {
|
||||
resolveRemotePlatform,
|
||||
runAgentEndpoints,
|
||||
shellEscape,
|
||||
validateAgentHostTelemetryLevel,
|
||||
waitForNewStandaloneEndpoint,
|
||||
} from './sshRemoteAgentHostHelpers.js';
|
||||
import { parseSSHConfigHostEntries, parseSSHGOutput, stripSSHComment } from '../common/sshConfigParsing.js';
|
||||
@@ -372,18 +376,20 @@ function startRemoteAgentHost(
|
||||
cliBin: string | undefined,
|
||||
cliDataDir: string | undefined,
|
||||
commandOverride?: string,
|
||||
telemetryLevel = TelemetryConfiguration.OFF,
|
||||
): Promise<{ port: number; connectionToken: string | undefined; pid: number | undefined; stream: SSHChannel }> {
|
||||
return new Promise((resolve, reject) => {
|
||||
if (!commandOverride && (!cliBin || !cliDataDir)) {
|
||||
reject(new Error(`${LOG_PREFIX} startRemoteAgentHost requires either a cliBin+cliDataDir pair or a commandOverride`));
|
||||
return;
|
||||
}
|
||||
const baseCmd = commandOverride ?? buildAgentHostBaseCommand(cliBin!, cliDataDir!);
|
||||
const validatedTelemetryLevel = validateAgentHostTelemetryLevel(telemetryLevel);
|
||||
const baseCmd = commandOverride ?? buildAgentHostBaseCommand(cliBin!, cliDataDir!, validatedTelemetryLevel);
|
||||
// Wrap in a login shell so the agent host process inherits the
|
||||
// user's PATH and environment from ~/.bash_profile / ~/.bashrc
|
||||
// (ssh2 exec runs a non-interactive non-login shell by default).
|
||||
// Echo the PID so we can record it for process reuse detection.
|
||||
const cmd = `bash -l -c ${shellEscape(`echo VSCODE_PID=$$ && exec ${baseCmd}`)}`;
|
||||
const cmd = `bash -l -c ${shellEscape(`echo VSCODE_PID=$$ && export ${AgentHostTelemetryLevelEnvKey}=${validatedTelemetryLevel} && exec ${baseCmd}`)}`;
|
||||
logService.info(`${LOG_PREFIX} Starting remote agent host: ${cmd}`);
|
||||
|
||||
client.exec(cmd, (err: Error | undefined, stream: SSHChannel) => {
|
||||
@@ -768,6 +774,7 @@ export class SSHRemoteAgentHostMainService extends Disposable implements ISSHRem
|
||||
constructor(
|
||||
@ILogService private readonly _logService: ILogService,
|
||||
@IProductService private readonly _productService: IProductService,
|
||||
@ITelemetryService private readonly _telemetryService: ITelemetryService,
|
||||
) {
|
||||
super();
|
||||
}
|
||||
@@ -917,7 +924,7 @@ export class SSHRemoteAgentHostMainService extends Disposable implements ISSHRem
|
||||
// picker over. Always start a fresh process (requirement 6).
|
||||
this._logService.info(`${LOG_PREFIX} Using custom agent host command: ${config.remoteAgentHostCommand}; skipping endpoint discovery/selection`);
|
||||
reportProgress(localize('sshProgressStartingAgent', "Starting remote agent host..."));
|
||||
const result = await this._startRemoteAgentHost(sshClient, undefined, undefined, config.remoteAgentHostCommand);
|
||||
const result = await this._startRemoteAgentHost(sshClient, undefined, undefined, config.remoteAgentHostCommand, this._effectiveTelemetryLevel);
|
||||
endpoint = { type: 'tcp', host: '127.0.0.1', port: result.port };
|
||||
connectionToken = result.connectionToken;
|
||||
agentStream = result.stream;
|
||||
@@ -948,7 +955,7 @@ export class SSHRemoteAgentHostMainService extends Disposable implements ISSHRem
|
||||
const standalones = live.filter(e => e.type === 'standalone');
|
||||
|
||||
const spawnDedicated = async (): Promise<IAgentHostEndpointMetadata> => {
|
||||
const spawnCommand = buildAgentHostSpawnCommand(cliBin, cliDataDir, userDataPath);
|
||||
const spawnCommand = buildAgentHostSpawnCommand(cliBin, cliDataDir, userDataPath, this._effectiveTelemetryLevel);
|
||||
reportProgress(localize('sshProgressStartingAgent', "Starting remote agent host..."));
|
||||
this._logService.info(`${LOG_PREFIX} Spawning dedicated standalone agent host: ${spawnCommand}`);
|
||||
// Fire-and-forget: the spawned process is self-managed via
|
||||
@@ -2032,10 +2039,14 @@ export class SSHRemoteAgentHostMainService extends Disposable implements ISSHRem
|
||||
return this._productService.commit;
|
||||
}
|
||||
|
||||
private get _effectiveTelemetryLevel(): TelemetryConfiguration {
|
||||
return telemetryLevelToAgentHostValue(this._telemetryService.telemetryLevel);
|
||||
}
|
||||
|
||||
protected _startRemoteAgentHost(
|
||||
client: SSHClient, cliBin: string | undefined, cliDataDir: string | undefined, commandOverride?: string,
|
||||
client: SSHClient, cliBin: string | undefined, cliDataDir: string | undefined, commandOverride?: string, telemetryLevel?: TelemetryConfiguration,
|
||||
): Promise<{ port: number; connectionToken: string | undefined; pid: number | undefined; stream: SSHChannel }> {
|
||||
return startRemoteAgentHost(client, this._logService, cliBin, cliDataDir, commandOverride);
|
||||
return startRemoteAgentHost(client, this._logService, cliBin, cliDataDir, commandOverride, telemetryLevel);
|
||||
}
|
||||
|
||||
protected async _createWebSocketRelay(
|
||||
|
||||
@@ -5,6 +5,8 @@
|
||||
|
||||
import * as cp from 'child_process';
|
||||
import { join } from '../../../base/common/path.js';
|
||||
import { TelemetryConfiguration } from '../../telemetry/common/telemetry.js';
|
||||
import { AgentHostTelemetryLevelEnvKey } from '../common/agentHostTelemetryEnv.js';
|
||||
import type { IWSLDistro } from '../common/wslRemoteAgentHost.js';
|
||||
import {
|
||||
buildAgentHostBaseCommand,
|
||||
@@ -15,6 +17,7 @@ import {
|
||||
getRemoteCLIDataDir,
|
||||
getRemoteCLIInstallRoot,
|
||||
shellEscape,
|
||||
validateAgentHostTelemetryLevel,
|
||||
validateShellToken,
|
||||
} from './sshRemoteAgentHostHelpers.js';
|
||||
|
||||
@@ -250,6 +253,7 @@ export interface IComposeAgentHostBootstrapScriptArgs {
|
||||
readonly commit: string | undefined;
|
||||
readonly os: string;
|
||||
readonly arch: string;
|
||||
readonly telemetryLevel?: TelemetryConfiguration;
|
||||
/** Dev override; when set, returned verbatim and all CLI bootstrap is skipped. */
|
||||
readonly remoteAgentHostCommand?: string;
|
||||
}
|
||||
@@ -270,14 +274,15 @@ export interface IComposeAgentHostBootstrapScriptArgs {
|
||||
* lives in the helper functions above, not in the composition itself.
|
||||
*/
|
||||
export function composeAgentHostBootstrapScript(args: IComposeAgentHostBootstrapScriptArgs): string {
|
||||
const telemetryLevel = validateAgentHostTelemetryLevel(args.telemetryLevel ?? TelemetryConfiguration.OFF);
|
||||
if (args.remoteAgentHostCommand) {
|
||||
return args.remoteAgentHostCommand;
|
||||
return `export ${AgentHostTelemetryLevelEnvKey}=${telemetryLevel} && ${args.remoteAgentHostCommand}`;
|
||||
}
|
||||
const installRoot = getRemoteCLIInstallRoot(args.serverDataFolderName);
|
||||
const cliBin = getRemoteCLIBin(args.serverDataFolderName, args.quality, args.commit);
|
||||
const cliDataDir = getRemoteCLIDataDir(args.serverDataFolderName);
|
||||
const url = buildCLIDownloadUrl(args.os, args.arch, args.quality, args.commit);
|
||||
const launch = `exec ${buildAgentHostBaseCommand(cliBin, cliDataDir)}`;
|
||||
const launch = `exec ${buildAgentHostBaseCommand(cliBin, cliDataDir, telemetryLevel)}`;
|
||||
|
||||
if (args.commit) {
|
||||
// Pinned-install path. Mirrors SSH's _ensureCLIInstalledPinned: stage
|
||||
|
||||
@@ -12,6 +12,8 @@ import { generateUuid } from '../../../base/common/uuid.js';
|
||||
import { localize } from '../../../nls.js';
|
||||
import { ILogService } from '../../log/common/log.js';
|
||||
import { IProductService } from '../../product/common/productService.js';
|
||||
import { ITelemetryService } from '../../telemetry/common/telemetry.js';
|
||||
import { telemetryLevelToAgentHostValue } from '../common/agentHostTelemetry.js';
|
||||
import type { IRelayMessage } from '../common/relayTransport.js';
|
||||
import {
|
||||
IWSLRemoteAgentHostMainService,
|
||||
@@ -80,6 +82,7 @@ export class WSLRemoteAgentHostMainService extends Disposable implements IWSLRem
|
||||
constructor(
|
||||
@ILogService private readonly _logService: ILogService,
|
||||
@IProductService private readonly _productService: IProductService,
|
||||
@ITelemetryService private readonly _telemetryService: ITelemetryService,
|
||||
) {
|
||||
super();
|
||||
this._register(toDisposable(() => {
|
||||
@@ -193,6 +196,7 @@ export class WSLRemoteAgentHostMainService extends Disposable implements IWSLRem
|
||||
commit: this._commit,
|
||||
os: targetOs,
|
||||
arch: targetArch,
|
||||
telemetryLevel: telemetryLevelToAgentHostValue(this._telemetryService.telemetryLevel),
|
||||
remoteAgentHostCommand: config.remoteAgentHostCommand,
|
||||
});
|
||||
|
||||
|
||||
+48
-4
@@ -63,7 +63,7 @@ const syncTestConfigurationNode = {
|
||||
},
|
||||
};
|
||||
import type { Implementation } from '../../common/state/protocol/common/commands.js';
|
||||
import { agentsWindowAgentHostClientInfo } from '../../common/agentHostClientInfo.js';
|
||||
import { agentsWindowAgentHostClientInfo, editorWindowAgentHostClientInfo } from '../../common/agentHostClientInfo.js';
|
||||
import { AgentHostClientConnectionKind } from '../../common/agentHostTelemetry.js';
|
||||
|
||||
type ProtocolTransportMessage = ProtocolMessage | AhpServerNotification | JsonRpcNotification | JsonRpcResponse | JsonRpcRequest;
|
||||
@@ -318,6 +318,7 @@ suite('RemoteAgentHostProtocolClient', () => {
|
||||
|
||||
assert.deepStrictEqual((initialize.params as { _meta?: Record<string, unknown> })._meta, {
|
||||
'vscode.clientConnectionKind': AgentHostClientConnectionKind.RemoteExtensionHost,
|
||||
'vscode.telemetryLevel': 'all',
|
||||
'vscode.clientMachineId': 'client-machine-id',
|
||||
'vscode.clientDevDeviceId': 'client-dev-device-id',
|
||||
});
|
||||
@@ -333,7 +334,9 @@ suite('RemoteAgentHostProtocolClient', () => {
|
||||
const noTelemetryClient = createClient(noTelemetryTransport).client;
|
||||
const noTelemetryConnectPromise = noTelemetryClient.connect();
|
||||
const noTelemetryInitialize = noTelemetryTransport.sentMessages[0] as JsonRpcRequest;
|
||||
assert.strictEqual((noTelemetryInitialize.params as { _meta?: Record<string, unknown> })._meta, undefined);
|
||||
assert.deepStrictEqual((noTelemetryInitialize.params as { _meta?: Record<string, unknown> })._meta, {
|
||||
'vscode.telemetryLevel': 'off',
|
||||
});
|
||||
noTelemetryTransport.fireMessage({
|
||||
jsonrpc: '2.0',
|
||||
id: noTelemetryInitialize.id,
|
||||
@@ -966,7 +969,7 @@ suite('RemoteAgentHostProtocolClient', () => {
|
||||
test('initialize handshake includes protocol version and client info', async () => {
|
||||
const transport = disposables.add(new TestClientProtocolTransport(AgentHostClientConnectionKind.DevTunnel));
|
||||
const clientInfo = agentsWindowAgentHostClientInfo;
|
||||
const { client } = createClient(transport, undefined, undefined, undefined, undefined, 'renderer-client-id', clientInfo);
|
||||
const { client } = createClientForIdentity('test.example:1234', transport, createPermissionService(), undefined, new NullLogService(), new TestConfigurationService(), 'renderer-client-id', clientInfo, new TestClientIdentityTelemetryService());
|
||||
const connectPromise = client.connect();
|
||||
|
||||
transport.connectDeferred.complete();
|
||||
@@ -990,7 +993,12 @@ suite('RemoteAgentHostProtocolClient', () => {
|
||||
protocolVersions: [...SUPPORTED_PROTOCOL_VERSIONS],
|
||||
clientId: 'renderer-client-id',
|
||||
clientInfo,
|
||||
_meta: { 'vscode.clientConnectionKind': 'dev_tunnel' },
|
||||
_meta: {
|
||||
'vscode.clientConnectionKind': 'dev_tunnel',
|
||||
'vscode.telemetryLevel': 'all',
|
||||
'vscode.clientMachineId': 'client-machine-id',
|
||||
'vscode.clientDevDeviceId': 'client-dev-device-id',
|
||||
},
|
||||
});
|
||||
assert.strictEqual(params.protocolVersions[0], PROTOCOL_VERSION);
|
||||
|
||||
@@ -1030,6 +1038,40 @@ suite('RemoteAgentHostProtocolClient', () => {
|
||||
});
|
||||
});
|
||||
|
||||
test('forwards the actual telemetry service restriction during initialization and config sync', async () => {
|
||||
const transport = disposables.add(new TestProtocolTransport(AgentHostClientConnectionKind.RemoteExtensionHost));
|
||||
const configurationService = new TestConfigurationService();
|
||||
const client = disposables.add(new RemoteAgentHostProtocolClient(
|
||||
'test.example:1234',
|
||||
transport,
|
||||
undefined,
|
||||
'telemetry-disabled-client',
|
||||
editorWindowAgentHostClientInfo,
|
||||
new NullLogService(),
|
||||
createPermissionService(),
|
||||
configurationService,
|
||||
NullTelemetryService,
|
||||
));
|
||||
|
||||
const connectPromise = client.connect();
|
||||
const initialize = transport.sentMessages[0] as JsonRpcRequest;
|
||||
assert.deepStrictEqual((initialize.params as { _meta?: Record<string, unknown> })._meta, {
|
||||
'vscode.clientConnectionKind': AgentHostClientConnectionKind.RemoteExtensionHost,
|
||||
'vscode.telemetryLevel': 'off',
|
||||
});
|
||||
transport.fireMessage({
|
||||
jsonrpc: '2.0',
|
||||
id: initialize.id,
|
||||
result: { protocolVersion: PROTOCOL_VERSION, serverSeq: 0, snapshots: [] },
|
||||
});
|
||||
await connectPromise;
|
||||
|
||||
assert.strictEqual(
|
||||
findRootConfigValue(transport.sentMessages, AgentHostTelemetryLevelConfigKey),
|
||||
'off',
|
||||
);
|
||||
});
|
||||
|
||||
test('forwards every setting declaring `agentHost` on connect and when one changes', async () => {
|
||||
const configurationService = new TestConfigurationService({
|
||||
[SYNC_SETTING_A]: true,
|
||||
@@ -2004,6 +2046,7 @@ suite('RemoteAgentHostProtocolClient', () => {
|
||||
reconnectTransport.connectDeferred.complete();
|
||||
const reconnect = await waitForRequest(reconnectTransport, 'reconnect');
|
||||
assert.deepStrictEqual((reconnect.params as { _meta?: Record<string, unknown> })._meta, {
|
||||
'vscode.telemetryLevel': 'all',
|
||||
'vscode.clientMachineId': 'client-machine-id',
|
||||
'vscode.clientDevDeviceId': 'client-dev-device-id',
|
||||
});
|
||||
@@ -2020,6 +2063,7 @@ suite('RemoteAgentHostProtocolClient', () => {
|
||||
}, {
|
||||
clientInfo: agentsWindowAgentHostClientInfo,
|
||||
meta: {
|
||||
'vscode.telemetryLevel': 'all',
|
||||
'vscode.clientMachineId': 'client-machine-id',
|
||||
'vscode.clientDevDeviceId': 'client-dev-device-id',
|
||||
},
|
||||
|
||||
@@ -17,7 +17,7 @@ import type { IProductService } from '../../../product/common/productService.js'
|
||||
import { ITelemetryData, ITelemetryService, TelemetryLevel } from '../../../telemetry/common/telemetry.js';
|
||||
import { AgentHostTelemetryLevelConfigKey, telemetryLevelToAgentHostConfigValue } from '../../common/agentHostSchema.js';
|
||||
import { AgentHostRestrictedTelemetrySender, IAgentHostRestrictedTelemetry, IAgentHostInternalTelemetryContext, IAgentHostRestrictedTelemetryContext, TelemetryProps } from '../../node/agentHostRestrictedTelemetry.js';
|
||||
import { AgentHostTelemetryService, createAgentHostTelemetryService, updateAgentHostTelemetryLevelFromConfig } from '../../node/agentHostTelemetryService.js';
|
||||
import { AgentHostTelemetryService, createAgentHostTelemetryService, type IAgentHostTelemetryService, updateAgentHostTelemetryLevelFromConfig } from '../../node/agentHostTelemetryService.js';
|
||||
import { AgentHostInternalTelemetrySender } from '../../node/agentHostMicrosoftTelemetry.js';
|
||||
|
||||
class TestTelemetryService implements ITelemetryService {
|
||||
@@ -101,6 +101,54 @@ class TestRestrictedSink implements IAgentHostRestrictedTelemetry {
|
||||
suite('AgentHostTelemetryService', () => {
|
||||
const disposables = ensureNoDisposablesAreLeakedInTestSuite();
|
||||
|
||||
async function createFactoryService(telemetryLevelArg: string | undefined, telemetryLevelEnvironment: string | undefined): Promise<IAgentHostTelemetryService> {
|
||||
const localDisposables = disposables.add(new DisposableStore());
|
||||
const logService = new NullLogService();
|
||||
const fileService = localDisposables.add(new FileService(logService));
|
||||
localDisposables.add(fileService.registerProvider(Schemas.file, localDisposables.add(new InMemoryFileSystemProvider())));
|
||||
return createAgentHostTelemetryService({
|
||||
environmentService: {
|
||||
args: telemetryLevelArg === undefined ? {} : { 'telemetry-level': telemetryLevelArg },
|
||||
isBuilt: true,
|
||||
disableTelemetry: false,
|
||||
appRoot: '/app',
|
||||
extensionsPath: '/extensions',
|
||||
userHome: URI.file('/home'),
|
||||
tmpDir: URI.file('/tmp'),
|
||||
userDataPath: '/user-data',
|
||||
appSettingsHome: URI.file('/User'),
|
||||
} as INativeEnvironmentService,
|
||||
productService: {
|
||||
_serviceBrand: undefined,
|
||||
version: '1.130.0',
|
||||
enableTelemetry: true,
|
||||
} as IProductService,
|
||||
fileService,
|
||||
loggerService: localDisposables.add(new NullLoggerService()),
|
||||
logService,
|
||||
disposables: localDisposables,
|
||||
readTelemetryLevelEnvironment: () => telemetryLevelEnvironment,
|
||||
});
|
||||
}
|
||||
|
||||
test('uses the most restrictive valid launch source and fails closed for malformed sources', async () => {
|
||||
const services = await Promise.all([
|
||||
createFactoryService('all', 'off'),
|
||||
createFactoryService('off', 'all'),
|
||||
createFactoryService('invalid', 'all'),
|
||||
createFactoryService('all', 'invalid'),
|
||||
createFactoryService(undefined, undefined),
|
||||
]);
|
||||
|
||||
assert.deepStrictEqual(services.map(service => service.telemetryLevel), [
|
||||
TelemetryLevel.NONE,
|
||||
TelemetryLevel.NONE,
|
||||
TelemetryLevel.NONE,
|
||||
TelemetryLevel.NONE,
|
||||
TelemetryLevel.USAGE,
|
||||
]);
|
||||
});
|
||||
|
||||
test('logging-only builds do not create restricted network senders', async () => {
|
||||
const localDisposables = disposables.add(new DisposableStore());
|
||||
const logService = new NullLogService();
|
||||
@@ -165,17 +213,18 @@ suite('AgentHostTelemetryService', () => {
|
||||
assert.strictEqual((internalSender as unknown as { _options: { extensionVersion: string | undefined } })._options.extensionVersion, '0.58.0');
|
||||
});
|
||||
|
||||
test('permanently disables usage and error telemetry after TelemetryLevel.NONE', async () => {
|
||||
test('uses the launch telemetry level before a client connects and only becomes more restrictive', () => {
|
||||
const delegate = new TestTelemetryService();
|
||||
const service = disposables.add(new AgentHostTelemetryService(delegate));
|
||||
const service = disposables.add(new AgentHostTelemetryService(delegate, undefined, undefined, undefined, TelemetryLevel.USAGE));
|
||||
|
||||
service.publicLog('beforeDisable', { count: 1 });
|
||||
service.publicLog('beforeClientLevel', { count: 1 });
|
||||
service.updateTelemetryLevel(TelemetryLevel.ERROR);
|
||||
service.publicLog('afterClientLevel', { count: 2 });
|
||||
service.publicLogError('afterClientLevelError', { count: 3 });
|
||||
service.updateTelemetryLevel(TelemetryLevel.NONE);
|
||||
service.updateTelemetryLevel(TelemetryLevel.USAGE);
|
||||
service.publicLog2('afterDisable');
|
||||
service.publicLogError2('afterDisableError');
|
||||
service.publicLog('afterDisableAsync', { count: 4 });
|
||||
service.publicLogError('afterDisableErrorAsync', { count: 5 });
|
||||
|
||||
assert.deepStrictEqual({
|
||||
telemetryLevel: service.telemetryLevel,
|
||||
@@ -185,8 +234,8 @@ suite('AgentHostTelemetryService', () => {
|
||||
}, {
|
||||
telemetryLevel: TelemetryLevel.NONE,
|
||||
sendErrorTelemetry: false,
|
||||
events: [{ eventName: 'beforeDisable', data: { count: 1 } }],
|
||||
errorEvents: [],
|
||||
events: [{ eventName: 'beforeClientLevel', data: { count: 1 } }],
|
||||
errorEvents: [{ eventName: 'afterClientLevelError', data: { count: 3 } }],
|
||||
});
|
||||
});
|
||||
|
||||
@@ -235,6 +284,7 @@ suite('AgentHostTelemetryService', () => {
|
||||
test('enhanced GH telemetry is gated on the restricted (rt) opt-in; standard GH telemetry is not', () => {
|
||||
const restricted = new TestRestrictedSink();
|
||||
const service = disposables.add(new AgentHostTelemetryService(new TestTelemetryService(), restricted));
|
||||
service.updateTelemetryLevel(TelemetryLevel.USAGE);
|
||||
|
||||
service.sendEnhancedGHTelemetryEvent('request.options.tools'); // dropped: rt disabled by default
|
||||
service.sendGHTelemetryEvent('completion'); // sent: standard GH telemetry is not rt-gated
|
||||
@@ -264,6 +314,7 @@ suite('AgentHostTelemetryService', () => {
|
||||
delegate.telemetryLevel = TelemetryLevel.ERROR; // user opted below USAGE
|
||||
const restricted = new TestRestrictedSink();
|
||||
const service = disposables.add(new AgentHostTelemetryService(delegate, restricted));
|
||||
service.updateTelemetryLevel(TelemetryLevel.USAGE);
|
||||
|
||||
service.setRestrictedTelemetryEnabled(true); // rt=1
|
||||
service.sendEnhancedGHTelemetryEvent('request.options.tools');
|
||||
@@ -276,6 +327,7 @@ suite('AgentHostTelemetryService', () => {
|
||||
test('internal telemetry is independently gated and identity is cleared on account changes', () => {
|
||||
const restricted = new TestRestrictedSink();
|
||||
const service = disposables.add(new AgentHostTelemetryService(new TestTelemetryService(), restricted));
|
||||
service.updateTelemetryLevel(TelemetryLevel.USAGE);
|
||||
const internalContext = { isInternal: true, trackingId: 'tid-1', userName: 'octocat', isVscodeTeamMember: true };
|
||||
|
||||
service.sendInternalMSFTTelemetryEvent('beforeIdentity');
|
||||
|
||||
@@ -7,12 +7,14 @@ import assert from 'assert';
|
||||
import { DeferredPromise } from '../../../../base/common/async.js';
|
||||
import { Emitter, Event } from '../../../../base/common/event.js';
|
||||
import { DisposableStore } from '../../../../base/common/lifecycle.js';
|
||||
import { hasKey } from '../../../../base/common/types.js';
|
||||
import { URI } from '../../../../base/common/uri.js';
|
||||
import { runWithFakedTimers } from '../../../../base/test/common/timeTravelScheduler.js';
|
||||
import { ensureNoDisposablesAreLeakedInTestSuite } from '../../../../base/test/common/utils.js';
|
||||
import { NullLogService } from '../../../log/common/log.js';
|
||||
import { FileType } from '../../../files/common/files.js';
|
||||
import { NullTelemetryService, NullTelemetryServiceShape } from '../../../telemetry/common/telemetryUtils.js';
|
||||
import { NullTelemetryService } from '../../../telemetry/common/telemetryUtils.js';
|
||||
import { ITelemetryService, TelemetryLevel } from '../../../telemetry/common/telemetry.js';
|
||||
import { type IAgentCreateChatOptions, type IAgentCreateSessionConfig, type IAgentResolveSessionConfigParams, type IAgentSessionConfigCompletionsParams, type IAgentSessionMetadata, type AuthenticateParams, type AuthenticateResult } from '../../common/agent.js';
|
||||
import { type IAgentHostManagedSettingsDiagnostics, type IAgentHostNetworkDiagnosticsInfo, type IAgentHostNetworkFetchResult, type IAgentService } from '../../common/agentService.js';
|
||||
import { ChatSourceKind, CompletionsParams, CompletionsResult, ContentEncoding, ListSessionsResult, ResourceReadResult, ResolveSessionConfigResult, SessionConfigCompletionsResult, ResourceMkdirParams, ResourceMkdirResult, ResourceResolveParams, ResourceResolveResult, ResourceCopyParams, ResourceCopyResult } from '../../common/state/protocol/commands.js';
|
||||
@@ -33,6 +35,7 @@ import { iterateOtlpLogRecords, OtlpLogEmitter } from '../../common/otlp/otlpLog
|
||||
import { MessagePortProtocolServer } from '../../node/messagePortProtocolServer.js';
|
||||
import { AgentHostClientConnectionTelemetryTracker } from '../../node/agentHostClientConnectionTelemetry.js';
|
||||
import { AgentHostManagedSettingsService } from '../../node/agentHostManagedSettingsService.js';
|
||||
import { AgentHostTelemetryService } from '../../node/agentHostTelemetryService.js';
|
||||
|
||||
// ---- Mock helpers -----------------------------------------------------------
|
||||
|
||||
@@ -108,14 +111,27 @@ class FailingReconnectAgentHostFileSystemProvider extends AgentHostFileSystemPro
|
||||
}
|
||||
}
|
||||
|
||||
class TestTelemetryService extends NullTelemetryServiceShape {
|
||||
class TestTelemetryService implements ITelemetryService {
|
||||
declare readonly _serviceBrand: undefined;
|
||||
readonly telemetryLevel = TelemetryLevel.USAGE;
|
||||
readonly sendErrorTelemetry = true;
|
||||
readonly sessionId = 'session';
|
||||
readonly machineId = 'machine';
|
||||
readonly sqmId = 'sqm';
|
||||
readonly devDeviceId = 'device';
|
||||
readonly firstSessionDate = 'first-session';
|
||||
readonly events: { eventName: string; data: unknown }[] = [];
|
||||
|
||||
override publicLog2(eventName?: string, data?: unknown): void {
|
||||
publicLog(): void { }
|
||||
publicLog2(eventName?: string, data?: unknown): void {
|
||||
if (eventName) {
|
||||
this.events.push({ eventName, data });
|
||||
}
|
||||
}
|
||||
publicLogError(): void { }
|
||||
publicLogError2(): void { }
|
||||
setExperimentProperty(): void { }
|
||||
setCommonProperty(): void { }
|
||||
}
|
||||
|
||||
class MockAgentService implements IAgentService {
|
||||
@@ -291,6 +307,7 @@ suite('ProtocolServerHandler', () => {
|
||||
let fileSystemProvider: AgentHostFileSystemProvider;
|
||||
let logService: CountingLogService;
|
||||
let telemetryService: TestTelemetryService;
|
||||
let agentHostTelemetryService: AgentHostTelemetryService;
|
||||
|
||||
const sessionUri = URI.from({ scheme: 'copilot', path: '/test-session' }).toString();
|
||||
const defaultChatUri = buildDefaultChatUri(sessionUri);
|
||||
@@ -314,7 +331,10 @@ suite('ProtocolServerHandler', () => {
|
||||
protocolVersions: [PROTOCOL_VERSION],
|
||||
clientId,
|
||||
clientInfo,
|
||||
_meta: meta,
|
||||
_meta: {
|
||||
'vscode.telemetryLevel': 'all',
|
||||
...meta,
|
||||
},
|
||||
initialSubscriptions,
|
||||
}));
|
||||
return transport;
|
||||
@@ -329,6 +349,7 @@ suite('ProtocolServerHandler', () => {
|
||||
managedSettingsService = disposables.add(new AgentHostManagedSettingsService());
|
||||
logService = new CountingLogService();
|
||||
telemetryService = new TestTelemetryService();
|
||||
agentHostTelemetryService = disposables.add(new AgentHostTelemetryService(telemetryService));
|
||||
disposables.add(agentService);
|
||||
disposables.add(handler = new ProtocolServerHandler(
|
||||
agentService,
|
||||
@@ -337,7 +358,7 @@ suite('ProtocolServerHandler', () => {
|
||||
{ hostLaunchKind: AgentHostLaunchKind.VSCodeMainProcess, defaultDirectory: URI.file('/home/testuser').toString() },
|
||||
disposables.add(fileSystemProvider = new AgentHostFileSystemProvider()),
|
||||
logService,
|
||||
telemetryService,
|
||||
agentHostTelemetryService,
|
||||
managedSettingsService,
|
||||
));
|
||||
});
|
||||
@@ -352,12 +373,79 @@ suite('ProtocolServerHandler', () => {
|
||||
const transport = connectClient('client-1');
|
||||
|
||||
const resp = findResponse(transport.sent, 1);
|
||||
assert.ok(resp, 'should have sent initialize response');
|
||||
const result = (resp as { result: InitializeResult }).result;
|
||||
if (!resp || !hasKey(resp, { result: true })) {
|
||||
assert.fail('should have sent initialize response');
|
||||
}
|
||||
const result = resp.result as InitializeResult;
|
||||
assert.strictEqual(result.protocolVersion, PROTOCOL_VERSION);
|
||||
assert.strictEqual(result.serverSeq, stateManager.serverSeq);
|
||||
});
|
||||
|
||||
test('applies telemetry disablement before reporting the client connection', () => {
|
||||
const transport = new MockProtocolTransport(AgentHostTransportKind.WebSocket);
|
||||
server.simulateConnection(transport);
|
||||
transport.simulateMessage(request(1, 'initialize', {
|
||||
protocolVersions: [PROTOCOL_VERSION],
|
||||
clientId: 'telemetry-disabled-client',
|
||||
clientInfo: editorWindowAgentHostClientInfo,
|
||||
_meta: {
|
||||
'vscode.clientConnectionKind': AgentHostClientConnectionKind.RemoteExtensionHost,
|
||||
'vscode.telemetryLevel': 'off',
|
||||
},
|
||||
}));
|
||||
|
||||
assert.deepStrictEqual({
|
||||
telemetryLevel: agentHostTelemetryService.telemetryLevel,
|
||||
events: telemetryService.events,
|
||||
}, {
|
||||
telemetryLevel: TelemetryLevel.NONE,
|
||||
events: [],
|
||||
});
|
||||
transport.simulateClose();
|
||||
transport.dispose();
|
||||
});
|
||||
|
||||
test('uses the launch telemetry level when a legacy client omits telemetry metadata', () => {
|
||||
const transport = new MockProtocolTransport(AgentHostTransportKind.WebSocket);
|
||||
server.simulateConnection(transport);
|
||||
transport.simulateMessage(request(1, 'initialize', {
|
||||
protocolVersions: [PROTOCOL_VERSION],
|
||||
clientId: 'legacy-client',
|
||||
clientInfo: editorWindowAgentHostClientInfo,
|
||||
}));
|
||||
|
||||
assert.deepStrictEqual({
|
||||
telemetryLevel: agentHostTelemetryService.telemetryLevel,
|
||||
eventNames: telemetryService.events.map(event => event.eventName),
|
||||
}, {
|
||||
telemetryLevel: TelemetryLevel.USAGE,
|
||||
eventNames: ['agentHost.clientConnection'],
|
||||
});
|
||||
transport.simulateClose();
|
||||
transport.dispose();
|
||||
});
|
||||
|
||||
test('fails closed before reporting the client connection for malformed telemetry metadata', () => {
|
||||
const transport = new MockProtocolTransport(AgentHostTransportKind.WebSocket);
|
||||
server.simulateConnection(transport);
|
||||
transport.simulateMessage(request(1, 'initialize', {
|
||||
protocolVersions: [PROTOCOL_VERSION],
|
||||
clientId: 'malformed-telemetry-client',
|
||||
clientInfo: editorWindowAgentHostClientInfo,
|
||||
_meta: { 'vscode.telemetryLevel': 'invalid' },
|
||||
}));
|
||||
|
||||
assert.deepStrictEqual({
|
||||
telemetryLevel: agentHostTelemetryService.telemetryLevel,
|
||||
events: telemetryService.events,
|
||||
}, {
|
||||
telemetryLevel: TelemetryLevel.NONE,
|
||||
events: [],
|
||||
});
|
||||
transport.simulateClose();
|
||||
transport.dispose();
|
||||
});
|
||||
|
||||
test('handshake rejects unsupported protocol versions', () => {
|
||||
const transport = new MockProtocolTransport();
|
||||
server.simulateConnection(transport);
|
||||
@@ -1292,6 +1380,86 @@ suite('ProtocolServerHandler', () => {
|
||||
});
|
||||
});
|
||||
|
||||
test('applies telemetry disablement before reporting a reconnected client', async () => {
|
||||
const transport1 = connectClient('telemetry-reconnect-client');
|
||||
transport1.simulateClose();
|
||||
const eventsBeforeReconnect = [...telemetryService.events];
|
||||
|
||||
const transport2 = new MockProtocolTransport();
|
||||
server.simulateConnection(transport2);
|
||||
const reconnectResponse = waitForResponse(transport2, 2);
|
||||
transport2.simulateMessage(request(2, 'reconnect', {
|
||||
clientId: 'telemetry-reconnect-client',
|
||||
lastSeenServerSeq: stateManager.serverSeq,
|
||||
subscriptions: [],
|
||||
_meta: { 'vscode.telemetryLevel': 'off' },
|
||||
}));
|
||||
await reconnectResponse;
|
||||
|
||||
assert.deepStrictEqual({
|
||||
telemetryLevel: agentHostTelemetryService.telemetryLevel,
|
||||
events: telemetryService.events,
|
||||
}, {
|
||||
telemetryLevel: TelemetryLevel.NONE,
|
||||
events: eventsBeforeReconnect,
|
||||
});
|
||||
transport2.simulateClose();
|
||||
transport2.dispose();
|
||||
});
|
||||
|
||||
test('fails closed before reporting a reconnected client for malformed telemetry metadata', async () => {
|
||||
const transport1 = connectClient('malformed-telemetry-reconnect-client');
|
||||
transport1.simulateClose();
|
||||
const eventsBeforeReconnect = [...telemetryService.events];
|
||||
|
||||
const transport2 = new MockProtocolTransport();
|
||||
server.simulateConnection(transport2);
|
||||
const reconnectResponse = waitForResponse(transport2, 2);
|
||||
transport2.simulateMessage(request(2, 'reconnect', {
|
||||
clientId: 'malformed-telemetry-reconnect-client',
|
||||
lastSeenServerSeq: stateManager.serverSeq,
|
||||
subscriptions: [],
|
||||
_meta: { 'vscode.telemetryLevel': 'invalid' },
|
||||
}));
|
||||
await reconnectResponse;
|
||||
|
||||
assert.deepStrictEqual({
|
||||
telemetryLevel: agentHostTelemetryService.telemetryLevel,
|
||||
events: telemetryService.events,
|
||||
}, {
|
||||
telemetryLevel: TelemetryLevel.NONE,
|
||||
events: eventsBeforeReconnect,
|
||||
});
|
||||
transport2.simulateClose();
|
||||
transport2.dispose();
|
||||
});
|
||||
|
||||
test('uses the launch telemetry level when a legacy reconnect omits telemetry metadata', async () => {
|
||||
const transport1 = connectClient('legacy-reconnect-client');
|
||||
transport1.simulateClose();
|
||||
const eventCountBeforeReconnect = telemetryService.events.length;
|
||||
|
||||
const transport2 = new MockProtocolTransport();
|
||||
server.simulateConnection(transport2);
|
||||
const reconnectResponse = waitForResponse(transport2, 2);
|
||||
transport2.simulateMessage(request(2, 'reconnect', {
|
||||
clientId: 'legacy-reconnect-client',
|
||||
lastSeenServerSeq: stateManager.serverSeq,
|
||||
subscriptions: [],
|
||||
}));
|
||||
await reconnectResponse;
|
||||
|
||||
assert.deepStrictEqual({
|
||||
telemetryLevel: agentHostTelemetryService.telemetryLevel,
|
||||
newEventNames: telemetryService.events.slice(eventCountBeforeReconnect).map(event => event.eventName),
|
||||
}, {
|
||||
telemetryLevel: TelemetryLevel.USAGE,
|
||||
newEventNames: ['agentHost.clientConnection'],
|
||||
});
|
||||
transport2.simulateClose();
|
||||
transport2.dispose();
|
||||
});
|
||||
|
||||
test('does not retain client telemetry identity when reconnect omits it', async () => {
|
||||
const transport1 = connectClient('client-consent', undefined, agentsWindowAgentHostClientInfo, {
|
||||
'vscode.clientMachineId': 'client-machine-id',
|
||||
@@ -1366,6 +1534,7 @@ suite('ProtocolServerHandler', () => {
|
||||
clientInfo: { name: 'vscode-agents-window', version: '1.2.3', title: 'VS Code Agents Window' },
|
||||
_meta: {
|
||||
'vscode.clientConnectionKind': AgentHostClientConnectionKind.DevTunnel,
|
||||
'vscode.telemetryLevel': 'all',
|
||||
'vscode.clientMachineId': 'client-machine-id',
|
||||
'vscode.clientDevDeviceId': 'client-dev-device-id',
|
||||
},
|
||||
|
||||
@@ -8,6 +8,7 @@ import type { ConnectConfig } from 'ssh2';
|
||||
import { DisposableStore } from '../../../../base/common/lifecycle.js';
|
||||
import { NullLogService } from '../../../log/common/log.js';
|
||||
import { IProductService } from '../../../product/common/productService.js';
|
||||
import { NullTelemetryService } from '../../../telemetry/common/telemetryUtils.js';
|
||||
import { ensureNoDisposablesAreLeakedInTestSuite } from '../../../../base/test/common/utils.js';
|
||||
import { isSSHHostKeyDeniedError, SSHAuthMethod, type ISSHAgentHostConfig, type ISSHHostKeyVerificationRequest } from '../../common/sshRemoteAgentHost.js';
|
||||
import { SSHRemoteAgentHostMainService, type SSHAuthAttempt } from '../../node/sshRemoteAgentHostService.js';
|
||||
@@ -184,7 +185,7 @@ suite('SSHRemoteAgentHostMainService - host key verification', () => {
|
||||
quality: 'stable',
|
||||
dataFolderName: '.vscode-oss',
|
||||
};
|
||||
return disposables.add(new HostKeyTestService(new NullLogService(), productService as IProductService));
|
||||
return disposables.add(new HostKeyTestService(new NullLogService(), productService as IProductService, NullTelemetryService));
|
||||
}
|
||||
|
||||
/** Run a connect attempt, answering the verification request with `trusted`. */
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
|
||||
import assert from 'assert';
|
||||
import { ensureNoDisposablesAreLeakedInTestSuite } from '../../../../base/test/common/utils.js';
|
||||
import { TelemetryConfiguration } from '../../../telemetry/common/telemetry.js';
|
||||
import { AGENT_HOST_ENDPOINT_REGISTRY_SCHEMA_VERSION, type IAgentHostEndpointMetadata } from '../../common/agentHostEndpointRegistry.js';
|
||||
import {
|
||||
buildAgentEndpointsCommand,
|
||||
@@ -26,6 +27,7 @@ import {
|
||||
resolveRemotePlatform,
|
||||
runAgentEndpoints,
|
||||
shellEscape,
|
||||
validateAgentHostTelemetryLevel,
|
||||
validateCommit,
|
||||
validateShellToken,
|
||||
waitForNewStandaloneEndpoint,
|
||||
@@ -157,9 +159,18 @@ suite('SSH Remote Agent Host Helpers', () => {
|
||||
});
|
||||
|
||||
suite('buildAgentHostBaseCommand', () => {
|
||||
test('includes --cli-data-dir before the agent host subcommand', () => {
|
||||
const cmd = buildAgentHostBaseCommand('~/.vscode-server/code-insiders-abc', '~/.vscode-server/cli');
|
||||
assert.strictEqual(cmd, '~/.vscode-server/code-insiders-abc --cli-data-dir ~/.vscode-server/cli agent host --port 0');
|
||||
test('includes --cli-data-dir and the default telemetry level before the agent host subcommand', () => {
|
||||
const cmd = buildAgentHostBaseCommand('~/.vscode-server/code-insiders-abc', '~/.vscode-server/cli', TelemetryConfiguration.ON);
|
||||
assert.strictEqual(cmd, '~/.vscode-server/code-insiders-abc --cli-data-dir ~/.vscode-server/cli --telemetry-level all agent host --port 0');
|
||||
});
|
||||
|
||||
test('includes telemetry disablement before the agent host subcommand', () => {
|
||||
const cmd = buildAgentHostBaseCommand('~/.vscode-server/code-insiders-abc', '~/.vscode-server/cli', TelemetryConfiguration.OFF);
|
||||
assert.strictEqual(cmd, '~/.vscode-server/code-insiders-abc --cli-data-dir ~/.vscode-server/cli --telemetry-level off agent host --port 0');
|
||||
});
|
||||
|
||||
test('rejects unsafe telemetry levels', () => {
|
||||
assert.throws(() => validateAgentHostTelemetryLevel('off; touch /tmp/unsafe'), /Unsafe telemetry level/);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -456,26 +467,33 @@ suite('SSH Remote Agent Host Helpers', () => {
|
||||
suite('buildAgentHostSpawnCommand', () => {
|
||||
test('includes --new-instance, --user-data-dir and default --idle-timeout', () => {
|
||||
assert.strictEqual(
|
||||
buildAgentHostSpawnCommand('~/.vscode-server/code', '~/.vscode-server/cli', '/home/user/.vscode-remote'),
|
||||
'~/.vscode-server/code --cli-data-dir ~/.vscode-server/cli agent host --port 0 --new-instance --user-data-dir \'/home/user/.vscode-remote\' --idle-timeout 300',
|
||||
buildAgentHostSpawnCommand('~/.vscode-server/code', '~/.vscode-server/cli', '/home/user/.vscode-remote', TelemetryConfiguration.ON),
|
||||
'~/.vscode-server/code --cli-data-dir ~/.vscode-server/cli --telemetry-level all agent host --port 0 --new-instance --user-data-dir \'/home/user/.vscode-remote\' --idle-timeout 300',
|
||||
);
|
||||
});
|
||||
|
||||
test('honors a custom idle timeout', () => {
|
||||
assert.strictEqual(
|
||||
buildAgentHostSpawnCommand('~/.vscode-server/code', '~/.vscode-server/cli', '/home/user/.vscode-remote', 60),
|
||||
'~/.vscode-server/code --cli-data-dir ~/.vscode-server/cli agent host --port 0 --new-instance --user-data-dir \'/home/user/.vscode-remote\' --idle-timeout 60',
|
||||
buildAgentHostSpawnCommand('~/.vscode-server/code', '~/.vscode-server/cli', '/home/user/.vscode-remote', TelemetryConfiguration.ON, 60),
|
||||
'~/.vscode-server/code --cli-data-dir ~/.vscode-server/cli --telemetry-level all agent host --port 0 --new-instance --user-data-dir \'/home/user/.vscode-remote\' --idle-timeout 60',
|
||||
);
|
||||
});
|
||||
|
||||
test('propagates telemetry disablement to a new dedicated agent host', () => {
|
||||
assert.strictEqual(
|
||||
buildAgentHostSpawnCommand('~/.vscode-server/code', '~/.vscode-server/cli', '/home/user/.vscode-remote', TelemetryConfiguration.OFF),
|
||||
'~/.vscode-server/code --cli-data-dir ~/.vscode-server/cli --telemetry-level off agent host --port 0 --new-instance --user-data-dir \'/home/user/.vscode-remote\' --idle-timeout 300',
|
||||
);
|
||||
});
|
||||
|
||||
test('rejects unsafe idle timeout values', () => {
|
||||
assert.throws(() => buildAgentHostSpawnCommand('~/.vscode-server/code', '~/.vscode-server/cli', '/x', 0), /Unsafe idle timeout/);
|
||||
assert.throws(() => buildAgentHostSpawnCommand('~/.vscode-server/code', '~/.vscode-server/cli', '/x', -1), /Unsafe idle timeout/);
|
||||
assert.throws(() => buildAgentHostSpawnCommand('~/.vscode-server/code', '~/.vscode-server/cli', '/x', 1.5), /Unsafe idle timeout/);
|
||||
assert.throws(() => buildAgentHostSpawnCommand('~/.vscode-server/code', '~/.vscode-server/cli', '/x', TelemetryConfiguration.ON, 0), /Unsafe idle timeout/);
|
||||
assert.throws(() => buildAgentHostSpawnCommand('~/.vscode-server/code', '~/.vscode-server/cli', '/x', TelemetryConfiguration.ON, -1), /Unsafe idle timeout/);
|
||||
assert.throws(() => buildAgentHostSpawnCommand('~/.vscode-server/code', '~/.vscode-server/cli', '/x', TelemetryConfiguration.ON, 1.5), /Unsafe idle timeout/);
|
||||
});
|
||||
|
||||
test('always includes --new-instance so an existing standalone is never silently reused', () => {
|
||||
const cmd = buildAgentHostSpawnCommand('~/.vscode-server/code', '~/.vscode-server/cli', '/x');
|
||||
const cmd = buildAgentHostSpawnCommand('~/.vscode-server/code', '~/.vscode-server/cli', '/x', TelemetryConfiguration.ON);
|
||||
assert.ok(cmd.includes(' --new-instance '), 'spawn command must request a genuinely new instance, not reuse an existing standalone');
|
||||
});
|
||||
});
|
||||
|
||||
@@ -12,6 +12,8 @@ import { DisposableStore } from '../../../../base/common/lifecycle.js';
|
||||
import { ensureNoDisposablesAreLeakedInTestSuite } from '../../../../base/test/common/utils.js';
|
||||
import { NullLogService } from '../../../log/common/log.js';
|
||||
import { IProductService } from '../../../product/common/productService.js';
|
||||
import { TelemetryConfiguration } from '../../../telemetry/common/telemetry.js';
|
||||
import { NullTelemetryService } from '../../../telemetry/common/telemetryUtils.js';
|
||||
import { AGENT_HOST_ENDPOINT_REGISTRY_SCHEMA_VERSION, type AgentHostEndpointAddress, type IAgentHostEndpointMetadata } from '../../common/agentHostEndpointRegistry.js';
|
||||
import { SSHAuthMethod, type ISSHAgentHostConfig, type ISSHConnectProgress, type ISSHEndpointSelection, type ISSHEndpointSelectionRequest, type ISSHKeyboardInteractivePrompt, type ISSHKeyboardInteractiveRequest } from '../../common/sshRemoteAgentHost.js';
|
||||
import { SSHRemoteAgentHostMainService, makeAuthHandler, type SSHAuthAttempt } from '../../node/sshRemoteAgentHostService.js';
|
||||
@@ -301,7 +303,7 @@ class TestableSSHRemoteAgentHostMainService extends SSHRemoteAgentHostMainServic
|
||||
}
|
||||
|
||||
protected override async _startRemoteAgentHost(
|
||||
_client: unknown, _cliBin: string | undefined, _cliDataDir: string | undefined, _commandOverride?: string,
|
||||
_client: unknown, _cliBin: string | undefined, _cliDataDir: string | undefined, _commandOverride?: string, _telemetryLevel?: TelemetryConfiguration,
|
||||
) {
|
||||
this.startCalled++;
|
||||
return { ...this.startResult, stream: new MockSSHChannel() as never };
|
||||
@@ -463,6 +465,7 @@ suite('SSHRemoteAgentHostMainService - connect flow', () => {
|
||||
service = new TestableSSHRemoteAgentHostMainService(
|
||||
logService,
|
||||
productService as IProductService,
|
||||
NullTelemetryService,
|
||||
);
|
||||
disposables.add(service);
|
||||
});
|
||||
@@ -588,6 +591,7 @@ suite('SSHRemoteAgentHostMainService - connect flow', () => {
|
||||
const execCalls = service.mockClients[0].execCalls;
|
||||
assert.ok(execCalls.some(c => c.includes('--idle-timeout 300')), `should spawn with idle timeout; saw: ${JSON.stringify(execCalls)}`);
|
||||
assert.ok(execCalls.some(c => c.includes('--new-instance')), `spawn must request a genuinely new instance; saw: ${JSON.stringify(execCalls)}`);
|
||||
assert.ok(execCalls.some(c => c.includes('--telemetry-level off')), `spawn must apply telemetry disablement; saw: ${JSON.stringify(execCalls)}`);
|
||||
});
|
||||
|
||||
test('reuses the single live standalone deterministically without a picker', async () => {
|
||||
@@ -1188,6 +1192,7 @@ suite('SSHRemoteAgentHostMainService - connect flow', () => {
|
||||
quality,
|
||||
dataFolderName,
|
||||
} as IProductService,
|
||||
NullTelemetryService,
|
||||
));
|
||||
const request = new DeferredPromise<ISSHKeyboardInteractiveRequest>();
|
||||
disposables.add(kbiService.onDidRequestKeyboardInteractive(kbiRequest => request.complete(kbiRequest)));
|
||||
@@ -1282,6 +1287,7 @@ suite('SSHRemoteAgentHostMainService - connect flow', () => {
|
||||
const loggingService = disposables.add(new TestableSSHRemoteAgentHostMainService(
|
||||
logService,
|
||||
productService as IProductService,
|
||||
NullTelemetryService,
|
||||
));
|
||||
loggingService.execResponses = [
|
||||
{ stdout: 'Linux\n', code: 0 },
|
||||
@@ -1309,6 +1315,7 @@ suite('SSHRemoteAgentHostMainService - connect flow', () => {
|
||||
const loggingService = disposables.add(new TestableSSHRemoteAgentHostMainService(
|
||||
logService,
|
||||
productService as IProductService,
|
||||
NullTelemetryService,
|
||||
));
|
||||
loggingService.execResponses = [
|
||||
{ stdout: 'Linux\n', code: 0 },
|
||||
@@ -1343,6 +1350,7 @@ suite('SSHRemoteAgentHostMainService - connect flow', () => {
|
||||
pinnedService = new TestableSSHRemoteAgentHostMainService(
|
||||
logService,
|
||||
productService as IProductService,
|
||||
NullTelemetryService,
|
||||
);
|
||||
disposables.add(pinnedService);
|
||||
});
|
||||
@@ -1632,6 +1640,7 @@ suite('SSHRemoteAgentHostMainService - _buildAuthAttempts', () => {
|
||||
service = new AuthAttemptsTestService(
|
||||
logService,
|
||||
productService as IProductService,
|
||||
NullTelemetryService,
|
||||
);
|
||||
disposables.add(service);
|
||||
});
|
||||
|
||||
@@ -5,7 +5,9 @@
|
||||
|
||||
import assert from 'assert';
|
||||
import { ensureNoDisposablesAreLeakedInTestSuite } from '../../../../base/test/common/utils.js';
|
||||
import { TelemetryConfiguration } from '../../../telemetry/common/telemetry.js';
|
||||
import {
|
||||
composeAgentHostBootstrapScript,
|
||||
decodeWslOutput,
|
||||
parseRunningDistros,
|
||||
parseWslListVerbose,
|
||||
@@ -99,4 +101,34 @@ suite('WSL Remote Agent Host Helpers', () => {
|
||||
assert.strictEqual(decodeWslOutput(Buffer.alloc(0)), '');
|
||||
});
|
||||
});
|
||||
|
||||
suite('composeAgentHostBootstrapScript', () => {
|
||||
test('propagates telemetry disablement to the WSL agent host', () => {
|
||||
const commit = 'a'.repeat(40);
|
||||
const script = composeAgentHostBootstrapScript({
|
||||
serverDataFolderName: '.vscode-server',
|
||||
quality: 'stable',
|
||||
commit,
|
||||
os: 'linux',
|
||||
arch: 'x64',
|
||||
telemetryLevel: TelemetryConfiguration.OFF,
|
||||
});
|
||||
|
||||
assert.ok(script.endsWith(`exec ~/.vscode-server/code-${commit} --cli-data-dir ~/.vscode-server/cli --telemetry-level off agent host --port 0`));
|
||||
});
|
||||
|
||||
test('exports telemetry disablement for a custom command', () => {
|
||||
const script = composeAgentHostBootstrapScript({
|
||||
serverDataFolderName: '.vscode-server',
|
||||
quality: 'stable',
|
||||
commit: undefined,
|
||||
os: 'linux',
|
||||
arch: 'x64',
|
||||
telemetryLevel: TelemetryConfiguration.OFF,
|
||||
remoteAgentHostCommand: './start-agent-host',
|
||||
});
|
||||
|
||||
assert.strictEqual(script, 'export VSCODE_AGENT_HOST_TELEMETRY_LEVEL=off && ./start-agent-host');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -110,6 +110,7 @@ export interface NativeParsedArgs {
|
||||
'skip-release-notes'?: boolean;
|
||||
'skip-welcome'?: boolean;
|
||||
'disable-telemetry'?: boolean;
|
||||
'telemetry-level'?: string;
|
||||
'export-default-configuration'?: string;
|
||||
'export-policy-data'?: string;
|
||||
'export-default-keybindings'?: string;
|
||||
|
||||
@@ -185,6 +185,7 @@ export const OPTIONS: OptionDescriptions<Required<NativeParsedArgs>> = {
|
||||
'skip-release-notes': { type: 'boolean' },
|
||||
'skip-welcome': { type: 'boolean' },
|
||||
'disable-telemetry': { type: 'boolean' },
|
||||
'telemetry-level': { type: 'string' },
|
||||
'disable-updates': { type: 'boolean' },
|
||||
'share-secrets-with-agents-app': { type: 'boolean' },
|
||||
'transient': { type: 'boolean', cat: 't', description: localize('transient', "Run with temporary data and extension directories, as if launched for the first time.") },
|
||||
|
||||
@@ -30,6 +30,10 @@ export class ServerTelemetryService extends TelemetryService implements IServerT
|
||||
this._injectedTelemetryLevel = injectedTelemetryLevel;
|
||||
}
|
||||
|
||||
override get telemetryLevel(): TelemetryLevel {
|
||||
return Math.min(super.telemetryLevel, this._injectedTelemetryLevel);
|
||||
}
|
||||
|
||||
override publicLog(eventName: string, data?: ITelemetryData) {
|
||||
if (this._injectedTelemetryLevel < TelemetryLevel.USAGE) {
|
||||
return;
|
||||
@@ -58,7 +62,7 @@ export class ServerTelemetryService extends TelemetryService implements IServerT
|
||||
throw new Error('Telemetry level cannot be undefined. This will cause infinite looping!');
|
||||
}
|
||||
// We always take the most restrictive level because we don't want multiple clients to connect and send data when one client does not consent
|
||||
this._injectedTelemetryLevel = this._injectedTelemetryLevel ? Math.min(this._injectedTelemetryLevel, telemetryLevel) : telemetryLevel;
|
||||
this._injectedTelemetryLevel = Math.min(this._injectedTelemetryLevel, telemetryLevel);
|
||||
if (this._injectedTelemetryLevel === TelemetryLevel.NONE) {
|
||||
this.dispose();
|
||||
}
|
||||
|
||||
@@ -0,0 +1,39 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import assert from 'assert';
|
||||
import { ensureNoDisposablesAreLeakedInTestSuite } from '../../../../base/test/common/utils.js';
|
||||
import { TestConfigurationService } from '../../../configuration/test/common/testConfigurationService.js';
|
||||
import product from '../../../product/common/product.js';
|
||||
import { IProductService } from '../../../product/common/productService.js';
|
||||
import { TelemetryLevel } from '../../common/telemetry.js';
|
||||
import { ServerTelemetryService } from '../../common/serverTelemetryService.js';
|
||||
import { NullAppender } from '../../common/telemetryUtils.js';
|
||||
|
||||
suite('ServerTelemetryService', () => {
|
||||
const disposables = ensureNoDisposablesAreLeakedInTestSuite();
|
||||
const productService: IProductService = { _serviceBrand: undefined, ...product };
|
||||
|
||||
test('exposes and preserves the most restrictive injected telemetry level', async () => {
|
||||
const service = disposables.add(new ServerTelemetryService(
|
||||
{ appenders: [NullAppender] },
|
||||
TelemetryLevel.ERROR,
|
||||
new TestConfigurationService(),
|
||||
productService,
|
||||
));
|
||||
|
||||
const initialLevel = service.telemetryLevel;
|
||||
await service.updateInjectedTelemetryLevel(TelemetryLevel.NONE);
|
||||
await service.updateInjectedTelemetryLevel(TelemetryLevel.USAGE);
|
||||
|
||||
assert.deepStrictEqual({
|
||||
initialLevel,
|
||||
finalLevel: service.telemetryLevel,
|
||||
}, {
|
||||
initialLevel: TelemetryLevel.ERROR,
|
||||
finalLevel: TelemetryLevel.NONE,
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -25,7 +25,7 @@ import { IConfigurationRegistry, Extensions as ConfigurationExtensions } from '.
|
||||
import { IRemoteAuthorityResolverService } from '../../../../platform/remote/common/remoteAuthorityResolver.js';
|
||||
import { OpenLocalFileFolderCommand, OpenLocalFileCommand, OpenLocalFolderCommand, SaveLocalFileCommand, RemoteFileDialogContext } from '../../../services/dialogs/browser/simpleFileDialog.js';
|
||||
import { IWorkspaceContextService, WorkbenchState } from '../../../../platform/workspace/common/workspace.js';
|
||||
import { TELEMETRY_SETTING_ID } from '../../../../platform/telemetry/common/telemetry.js';
|
||||
import { ITelemetryService, TELEMETRY_CRASH_REPORTER_SETTING_ID, TELEMETRY_OLD_SETTING_ID, TELEMETRY_SETTING_ID } from '../../../../platform/telemetry/common/telemetry.js';
|
||||
import { getTelemetryLevel } from '../../../../platform/telemetry/common/telemetryUtils.js';
|
||||
import { IContextKeyService, RawContextKey, ContextKeyExpr } from '../../../../platform/contextkey/common/contextkey.js';
|
||||
import { INativeHostService } from '../../../../platform/native/common/native.js';
|
||||
@@ -100,21 +100,22 @@ class RemoteTelemetryEnablementUpdater extends Disposable implements IWorkbenchC
|
||||
|
||||
constructor(
|
||||
@IRemoteAgentService private readonly remoteAgentService: IRemoteAgentService,
|
||||
@IConfigurationService private readonly configurationService: IConfigurationService
|
||||
@IConfigurationService private readonly configurationService: IConfigurationService,
|
||||
@ITelemetryService private readonly telemetryService: ITelemetryService,
|
||||
) {
|
||||
super();
|
||||
|
||||
this.updateRemoteTelemetryEnablement();
|
||||
|
||||
this._register(configurationService.onDidChangeConfiguration(e => {
|
||||
if (e.affectsConfiguration(TELEMETRY_SETTING_ID)) {
|
||||
if (e.affectsConfiguration(TELEMETRY_SETTING_ID) || e.affectsConfiguration(TELEMETRY_OLD_SETTING_ID) || e.affectsConfiguration(TELEMETRY_CRASH_REPORTER_SETTING_ID)) {
|
||||
this.updateRemoteTelemetryEnablement();
|
||||
}
|
||||
}));
|
||||
}
|
||||
|
||||
private updateRemoteTelemetryEnablement(): Promise<void> {
|
||||
return this.remoteAgentService.updateTelemetryLevel(getTelemetryLevel(this.configurationService));
|
||||
return this.remoteAgentService.updateTelemetryLevel(Math.min(getTelemetryLevel(this.configurationService), this.telemetryService.telemetryLevel));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user