77
this._size = write;
78
}
80
>
clear(): void {
81
this._buffer.fill(undefined);
82
this._head = 0;
83
this._size = 0;
84
}
86
>
87
>
export class ChatDebugServiceImpl extends Disposable implements IChatDebugService {
88
>
declare readonly _serviceBrand: undefined;
89
>
90
>
static readonly MAX_EVENTS_PER_SESSION = 10_000;
91
>
static readonly MAX_SESSIONS = 5;
92
>
93
>
/** Per-session event buffers. Ordered from oldest to newest session (LRU). */
94
>
private readonly _sessionBuffers = new ResourceMap<SessionEventBuffer>();
95
>
/** Ordered list of session URIs for LRU eviction. */
96
>
private readonly _sessionOrder: URI[] = [];
97
>
/** Per-session tracking of seen event IDs to deduplicate events
98
>
* that share the same ID (e.g. subagentInvocation + userMessage
99
>
* emitted from the same span). Stores id → event kind so we can
100
>
* keep the richer event kind on collision. */
101
>
private readonly _seenEventIds = new ResourceMap<Map<string, IChatDebugEvent['kind']>>();
102
>
103
>
private readonly _onDidAddEvent = this._register(new Emitter<IChatDebugEvent>());
104
>
readonly onDidAddEvent: Event<IChatDebugEvent> = this._onDidAddEvent.event;
105
>
106
>
private readonly _onDidClearProviderEvents = this._register(new Emitter<URI>());
107
>
readonly onDidClearProviderEvents: Event<URI> = this._onDidClearProviderEvents.event;
108
>
109
>
private readonly _onDidEndSession = this._register(new Emitter<URI>());
110
>
readonly onDidEndSession: Event<URI> = this._onDidEndSession.event;
111
>
112
>
private readonly _onDidChangeAvailableSessionResources = this._register(new Emitter<void>());
113
>
readonly onDidChangeAvailableSessionResources: Event<void> = this._onDidChangeAvailableSessionResources.event;
114
>
115
>
private readonly _providers = new Set<IChatDebugLogProvider>();
116
>
private readonly _invocationCts = new ResourceMap<CancellationTokenSource>();
117
>
118
>
/**
119
>
* Sessions whose provider events should be cleared before the next batch of
120
>
* provider events is applied. The clear is deferred until the first new
121
>
* provider event actually arrives so that a provider which transiently
122
>
* returns nothing (e.g. an Agent Host `events.jsonl` mid-rewrite) does not
123
>
* wipe the events currently shown.
124
>
*/
125
>
private readonly _pendingProviderClear = new ResourceMap<boolean>();
126
>
127
>
/** Events that were returned by providers (not internally logged). */
128
>
private readonly _providerEvents = new WeakSet<IChatDebugEvent>();
129
>
130
>
/** Session URIs created via import. */
131
>
private readonly _importedSessions = new ResourceMap<boolean>();
132
>
133
>
/** Session URIs reported by providers as available on disk (historical sessions). */
134
>
private readonly _availableSessionResources: URI[] = [];
135
>
private readonly _availableSessionResourceSet = new Set<string>();
136
>
137
>
/** Titles for historical sessions discovered from disk. */
138
>
private readonly _historicalSessionTitles = new ResourceMap<string>();
139
>
140
>
/** Human-readable titles for imported sessions. */
141
>
private readonly _importedSessionTitles = new ResourceMap<string>();
142
>
143
>
activeSessionResource: URI | undefined;
144
>
145
>
constructor(
146
@IConfigurationService private readonly _configurationService: IConfigurationService,
147
) {
148
super();
149
}
151
>
/** Priority for deduplicating events with the same ID: lower = richer. */
152
>
private static readonly _eventKindPriority: Record<string, number> = {
153
>
subagentInvocation: 0,
154
>
modelTurn: 1,
155
>
toolCall: 2,
156
>
agentResponse: 3,
157
>
userMessage: 4,
158
>
generic: 5,
159
>
};
160
>
161
>
/** Session types eligible for debug logging and provider invocation. */
162
>
private static readonly _debugEligibleSessionTypes = new Set([
163
>
localChatSessionType, // local sessions
164
>
'copilotcli', // Copilot CLI background sessions
165
>
'agent-host-copilotcli', // local Agent Host Copilot CLI sessions
166
>
'claude-code', // Claude Code CLI sessions
167
>
]);
168
169
private _isDebugEligibleSession(sessionResource: URI): boolean {