2
>
* Copyright (c) Microsoft Corporation. All rights reserved.
3
>
* Licensed under the MIT License. See License.txt in the project root for license information.
4
>
*--------------------------------------------------------------------------------------------*/
5
>
6
>
import * as fs from 'fs';
7
>
import { Emitter, Event } from '../../../base/common/event.js';
8
>
import { Disposable } from '../../../base/common/lifecycle.js';
9
>
import { dirname } from '../../../base/common/path.js';
10
>
import { hasKey } from '../../../base/common/types.js';
11
>
import { URI } from '../../../base/common/uri.js';
12
>
import { createDecorator } from '../../instantiation/common/instantiation.js';
13
>
import { ILogService } from '../../log/common/log.js';
14
>
import { AgentHostConfigKey, agentHostCustomizationConfigSchema, defaultAgentHostCustomizationConfigValues } from '../common/agentHostCustomizationConfig.js';
15
>
import { getAgentCustomizationSettingsEntries, getProviderBackedRootConfigKeys, withAgentCustomizationSettings, type IAgentCustomizationSettingsRegistration } from '../common/agentCustomizationSettings.js';
16
>
import { copilotCliConfigSchema } from '../common/copilotCliConfig.js';
17
>
import { sandboxConfigSchema } from '../common/sandboxConfigSchema.js';
18
>
import type { ISchema, SchemaDefinition, SchemaValue } from '../common/agentHostSchema.js';
19
>
import { ProtocolError } from '../common/state/sessionProtocol.js';
20
>
import { ActionType } from '../common/state/sessionActions.js';
21
>
import { parseSubagentSessionUri, ROOT_STATE_URI, type URI as ProtocolURI } from '../common/state/sessionState.js';
22
>
import { AgentSession } from '../common/agentService.js';
23
>
import { AgentHostStateManager } from './agentHostStateManager.js';
24
>
import type { WorktreeIsolation } from './shared/worktreeIsolation.js';
25
>
26
>
export const IAgentConfigurationService = createDecorator<IAgentConfigurationService>('agentConfigurationService');
27
>
28
>
export interface IAgentSessionConfigurationChangeEvent {
29
>
readonly session: ProtocolURI;
30
>
readonly config: Record<string, unknown>;
31
>
}
32
>
33
>
/**
34
>
* Cohesive read/write surface for agent-host configuration.
35
>
*
36
>
* All platform-layer consumers (tool auto-approval, side effects, future
37
>
* host-config editors) should read and mutate config values through this
38
>
* service rather than reaching into raw session state. The service owns
39
>
* the `session → parent session → host` inheritance chain so that
40
>
* host-level defaults, subagent inheritance, and per-session overrides
41
>
* compose the same way everywhere.
42
>
*
43
>
* Reads go through a caller-supplied {@link ISchema}: each raw value is
44
>
* validated against the property's schema before being returned, so a
45
>
* malformed value in one layer transparently falls back to the next.
46
>
*/
47
>
export interface IAgentConfigurationService {
48
>
readonly _serviceBrand: undefined;
49
>
50
>
/**
51
>
* Fires whenever a {@link ActionType.RootConfigChanged} action is
52
>
* processed by the state manager, signalling that callers should
53
>
* re-read any root config values they depend on.
54
>
*/
55
>
readonly onDidRootConfigChange: Event<void>;
56
>
57
>
/** Fires whenever a session configuration change is processed. */
58
>
readonly onDidSessionConfigChange: Event<IAgentSessionConfigurationChangeEvent>;
59
>
60
>
/**
61
>
* Returns the effective value of `key` for `session`, walking the
62
>
* `session → parent session → host` chain and returning the first
63
>
* layer that provides a value which validates against
64
>
* `schema.definition[key]`. Layers that provide a malformed value
65
>
* are logged and skipped. Returns `undefined` when no layer provides
66
>
* a valid value.
67
>
*/
68
>
getEffectiveValue<D extends SchemaDefinition, K extends keyof D & string>(
69
>
session: ProtocolURI,
70
>
schema: ISchema<D>,
71
>
key: K,
72
>
): SchemaValue<D[K]> | undefined;
73
>
74
>
/**
75
>
* Returns the effective working directory for a session, falling back
76
>
* to the parent (subagent) session's working directory when the
77
>
* session itself does not have one set. The host layer does not carry
78
>
* a working directory.
79
>
*/
80
>
getEffectiveWorkingDirectory(session: ProtocolURI): string | undefined;
81
>
82
>
/**
83
>
* Whether a fresh worktree-isolation session's worktree has not yet been
84
>
* created. Agents consult this to defer prewarming (and any other eager
85
>
* materialization) until the host resolves the worktree on the first send.
86
>
*/
87
>
isWorkingDirectoryPending(session: ProtocolURI): boolean;
88
>
89
>
/** Resolves a persisted working directory, repairing a removed worktree when possible. */
90
>
resolveWorkingDirectoryForResume(session: ProtocolURI, workingDirectory: URI): Promise<URI>;
91
>
92
>
/**
93
>
* Merges a partial config patch into a session's values via a
94
>
* {@link ActionType.SessionConfigChanged} action. Keys not present in
95
>
* `patch` are left untouched. The patch is applied atomically through
96
>
* the state manager's reducer.
97
>
*/
98
>
updateSessionConfig(session: ProtocolURI, patch: Record<string, unknown>): void;
99
>
100
>
/**
101
>
* Returns the merged config values currently stored on `session`.
102
>
*
103
>
* Reflects the live state managed by the reducer: every
104
>
* {@link ActionType.SessionConfigChanged} action mutates these values
105
>
* before this method returns. Callers materializing a provisional session
106
>
* use this to read the user's latest selections without subscribing to
107
>
* the action stream themselves.
108
>
*/
109
>
getSessionConfigValues(session: ProtocolURI): Record<string, unknown> | undefined;
110
>
111
>
/**
112
>
* Returns the host-level value for `key`, validating it against
113
>
* `schema.definition[key]`. Invalid persisted values are logged and treated
114
>
* as missing.
115
>
*/
116
>
getRootValue<D extends SchemaDefinition, K extends keyof D & string>(
117
>
schema: ISchema<D>,
118
>
key: K,
119
>
): SchemaValue<D[K]> | undefined;
120
>
121
>
/**
122
>
* Merges a partial config patch into the host-level value bag and persists
123
>
* the updated values for future agent-host lifetimes.
124
>
*/
125
>
updateRootConfig(patch: Record<string, unknown>, replace?: boolean): void;
126
>
127
>
/**
128
>
* Persists the current host-level value bag without mutating it.
129
>
*/
130
>
persistRootConfig(): void;
131
>
132
>
/**
133
>
* Resolves once any in-flight root-config write has settled.
134
>
*/
135
>
whenIdle(): Promise<void>;
136
>
137
>
registerProviderConfiguration?(registration: IAgentCustomizationSettingsRegistration): void;
138
>
getRootConfigValues?(): Readonly<Record<string, unknown>>;
139
>
}
140
>
141
>
export class AgentConfigurationService extends Disposable implements IAgentConfigurationService {
142
>
declare readonly _serviceBrand: undefined;
143
>
private _rootConfigWrite = Promise.resolve();
144
>
145
>
private readonly _onDidRootConfigChange = this._register(new Emitter<void>());
146
>
readonly onDidRootConfigChange: Event<void> = this._onDidRootConfigChange.event;
147
>
private readonly _onDidSessionConfigChange = this._register(new Emitter<IAgentSessionConfigurationChangeEvent>());
148
>
readonly onDidSessionConfigChange: Event<IAgentSessionConfigurationChangeEvent> = this._onDidSessionConfigChange.event;
149
>
150
>
/**
151
>
* Host-owned worktree isolation controller. Injected after construction (via
152
>
* {@link setWorktreeIsolation}) because it only becomes available once the
153
>
* branch-name generator has been wired, which happens after this service is
154
>
* built. Consulted by {@link isWorkingDirectoryPending}, which degrades to
155
>
* folder behavior while it is unset (tests, early startup).
156
>
*/
157
>
private _worktree: WorktreeIsolation | undefined;
158
>
159
>
setWorktreeIsolation(worktree: WorktreeIsolation): void {
160
>
this._worktree = worktree;
161
>
}
162
>
163
>
constructor(
164
private readonly _stateManager: AgentHostStateManager,
165
@ILogService private readonly _logService: ILogService,