Skip to content

Commit 9a615e6

Browse files
ulugbeknaCopilot
andcommitted
automations: test: cover aggregate discovery transitions
Exercise provider registration and live discovery precedence through the aggregate observable. Clarify that unsupported hosts are ready only when their legacy storage is readable. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: 7e75f732-f18f-42a7-b1e2-a034c03596eb
1 parent 10c4714 commit 9a615e6

6 files changed

Lines changed: 52 additions & 40 deletions

File tree

‎src/vs/sessions/contrib/automations/test/browser/providerAutomationService.test.ts‎

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1257,16 +1257,16 @@ suite('ProviderAutomationService', () => {
12571257
runs: [],
12581258
});
12591259
const { service, providerStore, storage } = createService(futureLedger);
1260-
const initialDiscoveryState = service.initialDiscoveryState.get();
1260+
const catalogueState = service.catalogueState.get();
12611261

12621262
await assert.rejects(service.waitForMigrationForTesting(), /cannot be migrated safely/);
12631263

12641264
assert.deepStrictEqual({
1265-
initialDiscoveryState,
1265+
catalogueState,
12661266
providerAutomations: providerStore.automations.get(),
12671267
persisted: storage.get(AUTOMATION_STORAGE_KEY, StorageScope.APPLICATION),
12681268
}, {
1269-
initialDiscoveryState: 'unavailable',
1269+
catalogueState: 'error',
12701270
providerAutomations: [],
12711271
persisted: futureLedger,
12721272
});

‎src/vs/sessions/contrib/providers/agentHost/test/browser/localAgentHostSessionsProvider.test.ts‎

Lines changed: 11 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,9 @@ import { runWithFakedTimers } from '../../../../../../base/test/common/timeTrave
1818
import { ensureNoDisposablesAreLeakedInTestSuite } from '../../../../../../base/test/common/utils.js';
1919
import { AgentSession, type IAgentCreateChatRequestOptions, type IAgentCreateSessionConfig, type IAgentSessionMetadata } from '../../../../../../platform/agentHost/common/agent.js';
2020
import { AgentHostCodexAgentEnabledSettingId, IAgentHostService } from '../../../../../../platform/agentHost/common/agentService.js';
21+
import { AGENT_HOST_AUTOMATION_CATALOG_MIGRATED_META_KEY } from '../../../../../../platform/agentHost/common/automationMigration.js';
2122
import type { IAgentSubscription } from '../../../../../../platform/agentHost/common/state/agentSubscription.js';
23+
import type { InitializeResult } from '../../../../../../platform/agentHost/common/state/protocol/common/commands.js';
2224
import type { ResolveSessionConfigResult } from '../../../../../../platform/agentHost/common/state/protocol/commands.js';
2325
import { ChatInteractivity as ProtocolChatInteractivity, ChatOriginKind as ProtocolChatOriginKind, CustomizationEnablementKind, CustomizationLoadStatus, CustomizationType, McpServerStatus, MessageKind, SessionLifecycle, type AgentCustomization, type AgentInfo, type AutomationState, type ChangesSummary, type Customization, type RootState, type SessionActiveClient, type SessionConfigState, type SessionState } from '../../../../../../platform/agentHost/common/state/protocol/state.js';
2426
import { buildChatUri, buildDefaultChatUri, buildSubagentChatUri, ChangesetStatus, isAhpAutomationCatalogChannel, ResponsePartKind, SessionSourceControlOutcome, SessionStatus as ProtocolSessionStatus, StateComponents, ToolCallConfirmationReason, ToolCallStatus, ToolResultContentType, TurnState, withSessionCreationReference, withSessionEhcliAdoptable, withSessionGitHubState, withSessionGitState, withSessionMultiRootMetadata, withSessionSourceControlState, withSessionWorkspaceless, type ChangesetState, type ChatState, type ChatSummary } from '../../../../../../platform/agentHost/common/state/sessionState.js';
@@ -91,7 +93,7 @@ class MockAgentHostService extends mock<IAgentHostService>() {
9193
override readonly onAgentHostStart = this._onAgentHostStart.event;
9294
private readonly _onAgentHostExit = new Emitter<number>();
9395
override readonly onAgentHostExit = this._onAgentHostExit.event;
94-
override readonly initializeResult = constObservable({
96+
override readonly initializeResult = observableValue<InitializeResult>(this, {
9597
protocolVersion: '1',
9698
serverSeq: 0,
9799
snapshots: [],
@@ -100,6 +102,7 @@ class MockAgentHostService extends mock<IAgentHostService>() {
100102

101103
override readonly clientId = 'test-local-client';
102104
private readonly _sessions = new Map<string, IAgentSessionMetadata>();
105+
public automationCatalog: AutomationState = { entries: [] };
103106
public disposedSessions: URI[] = [];
104107
public onDisposeSession: ((session: URI) => void) | undefined;
105108
public failDisposeSessionFor: string | undefined;
@@ -278,7 +281,7 @@ class MockAgentHostService extends mock<IAgentHostService>() {
278281
override getSubscription<T>(_kind: StateComponents, resource: URI): IReference<IAgentSubscription<T>> {
279282
const key = resource.toString();
280283
if (isAhpAutomationCatalogChannel(key) && !this._sessionStateValues.has(key)) {
281-
this._sessionStateValues.set(key, { entries: [] });
284+
this._sessionStateValues.set(key, this.automationCatalog);
282285
}
283286
return this._getSubscription<T>(key);
284287
}
@@ -680,20 +683,22 @@ suite('LocalAgentHostSessionsProvider', () => {
680683

681684
// ---- Provider identity -------
682685

683-
test('Automation discovery follows local Agent Host connection lifetime', () => {
686+
test('Automation catalogue state follows local Agent Host connection lifetime', () => {
687+
agentHost.automationCatalog = { entries: [], _meta: { [AGENT_HOST_AUTOMATION_CATALOG_MIGRATED_META_KEY]: true } };
688+
agentHost.initializeResult.set({ ...agentHost.initializeResult.get(), automations: { create: {} } }, undefined);
684689
const provider = createProvider(disposables, agentHost, undefined, {
685690
configurationService: new TestConfigurationService({ [CHAT_AUTOMATIONS_ENABLED_SETTING]: true }),
686691
});
687-
const initial = provider.automations.initialDiscoveryState?.get();
692+
const initial = provider.automations.catalogueState.get();
688693

689694
agentHost.fireAgentHostExit();
690-
const disconnected = provider.automations.initialDiscoveryState?.get();
695+
const disconnected = provider.automations.catalogueState.get();
691696
agentHost.fireAgentHostStart();
692697

693698
assert.deepStrictEqual({
694699
initial,
695700
disconnected,
696-
reconnected: provider.automations.initialDiscoveryState?.get(),
701+
reconnected: provider.automations.catalogueState.get(),
697702
}, {
698703
initial: 'ready',
699704
disconnected: 'unavailable',

‎src/vs/sessions/contrib/sessions/browser/automationsNewBadge.ts‎

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -96,7 +96,7 @@ export class AutomationsNewBadgeState extends Disposable {
9696
if (this.forcePreview.read(reader) || this.seen.read(reader) || this.startupDecision.read(reader) !== 'eligible') {
9797
return;
9898
}
99-
if (this.automationService.initialDiscoveryState.read(reader) !== 'ready') {
99+
if (this.automationService.catalogueState.read(reader) !== 'ready') {
100100
this.startupDecision.set('suppressed', undefined);
101101
}
102102
}));
@@ -138,7 +138,7 @@ export class AutomationsNewBadgeState extends Disposable {
138138
return;
139139
}
140140

141-
if (this.automationService.initialDiscoveryState.get() !== 'ready') {
141+
if (this.automationService.catalogueState.get() !== 'ready') {
142142
this.startupDecision.set('suppressed', undefined);
143143
return;
144144
}

‎src/vs/sessions/contrib/sessions/test/browser/automationsNewBadge.test.ts‎

Lines changed: 34 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ import { TestConfigurationService } from '../../../../../platform/configuration/
1414
import { NullLogService } from '../../../../../platform/log/common/log.js';
1515
import { InMemoryStorageService, StorageScope, StorageTarget } from '../../../../../platform/storage/common/storage.js';
1616
import type { IAutomationDescriptor, IAutomationRun } from '../../../../../workbench/contrib/chat/common/automations/automation.js';
17-
import { AutomationInitialDiscoveryState, IAutomationService } from '../../../../../workbench/contrib/chat/common/automations/automationService.js';
17+
import { type AutomationCatalogueState, IAutomationService } from '../../../../../workbench/contrib/chat/common/automations/automationService.js';
1818
import { IWorkbenchAssignmentService } from '../../../../../workbench/services/assignment/common/assignmentService.js';
1919
import { ILifecycleService, LifecyclePhase } from '../../../../../workbench/services/lifecycle/common/lifecycle.js';
2020
import type { ICustomViewDescriptor } from '../../../../services/customView/browser/customView.js';
@@ -54,7 +54,7 @@ suite('AutomationsNewBadgeState', () => {
5454
readonly activeView?: ICustomViewDescriptor;
5555
readonly seen?: boolean;
5656
readonly hadPriorWindowOpen?: boolean;
57-
readonly initialDiscoveryState?: AutomationInitialDiscoveryState;
57+
readonly catalogueState?: AutomationCatalogueState;
5858
readonly eventuallyReady?: boolean;
5959
readonly style?: AutomationsNewBadgeStyle;
6060
readonly configuredStyle?: AutomationsNewBadgeStyle;
@@ -67,11 +67,11 @@ suite('AutomationsNewBadgeState', () => {
6767
const automations = observableValue<readonly IAutomationDescriptor[]>(disposables, options.automations ?? []);
6868
const runs = observableValue<readonly IAutomationRun[]>(disposables, options.runs ?? []);
6969
const activeView = observableValue<ICustomViewDescriptor | undefined>(disposables, options.activeView);
70-
const initialDiscoveryState = observableValue<AutomationInitialDiscoveryState>(disposables, options.initialDiscoveryState ?? 'ready');
70+
const catalogueState = observableValue<AutomationCatalogueState>(disposables, options.catalogueState ?? 'ready');
7171
const automationService = new class extends mock<IAutomationService>() {
7272
override readonly automations = automations;
7373
override readonly runs = runs;
74-
override readonly initialDiscoveryState = initialDiscoveryState;
74+
override readonly catalogueState = catalogueState;
7575
};
7676
const customViewService = new class extends mock<ICustomViewService>() {
7777
override readonly activeCustomView = activeView;
@@ -115,7 +115,7 @@ suite('AutomationsNewBadgeState', () => {
115115
assignmentService,
116116
configurationService,
117117
refetchAssignments,
118-
initialDiscoveryState,
118+
catalogueState,
119119
completeEventually: () => eventually.complete(),
120120
};
121121
}
@@ -156,13 +156,13 @@ suite('AutomationsNewBadgeState', () => {
156156
]);
157157
});
158158

159-
test('never reveals after initial provider discovery is suppressed', async () => {
159+
test('never reveals after initial catalogue discovery is suppressed', async () => {
160160
const snapshots = [];
161-
for (const initialState of ['pending', 'unavailable'] as const) {
162-
const fixture = createState({ initialDiscoveryState: initialState, style: 'accent' });
161+
for (const initialState of ['loading', 'unavailable', 'error'] as const) {
162+
const fixture = createState({ catalogueState: initialState, style: 'accent' });
163163

164164
await fixture.state.initialize();
165-
fixture.initialDiscoveryState.set('ready', undefined);
165+
fixture.catalogueState.set('ready', undefined);
166166
await fixture.configurationService.setUserConfiguration(AUTOMATIONS_NEW_BADGE_STYLE_SETTING, 'soft');
167167
fixture.configurationService.onDidChangeConfigurationEmitter.fire(upcastPartial<IConfigurationChangeEvent>({
168168
affectsConfiguration: key => key === AUTOMATIONS_NEW_BADGE_STYLE_SETTING,
@@ -178,8 +178,9 @@ suite('AutomationsNewBadgeState', () => {
178178
}
179179

180180
assert.deepStrictEqual(snapshots, [
181-
{ initialState: 'pending', showNewBadge: false, stored: undefined, treatments: [] },
181+
{ initialState: 'loading', showNewBadge: false, stored: undefined, treatments: [] },
182182
{ initialState: 'unavailable', showNewBadge: false, stored: undefined, treatments: [] },
183+
{ initialState: 'error', showNewBadge: false, stored: undefined, treatments: [] },
183184
]);
184185
});
185186

@@ -220,13 +221,13 @@ suite('AutomationsNewBadgeState', () => {
220221
});
221222
});
222223

223-
test('suppresses for the window when aggregate discovery becomes pending after presentation', async () => {
224+
test('suppresses for the window when the aggregate catalogue starts loading after presentation', async () => {
224225
const fixture = createState();
225226
await fixture.state.initialize();
226227
const beforeDiscoveryChange = fixture.state.presentation.get();
227228

228-
fixture.initialDiscoveryState.set('pending', undefined);
229-
fixture.initialDiscoveryState.set('ready', undefined);
229+
fixture.catalogueState.set('loading', undefined);
230+
fixture.catalogueState.set('ready', undefined);
230231
fixture.refetchAssignments.fire();
231232
await Promise.resolve();
232233

@@ -241,22 +242,28 @@ suite('AutomationsNewBadgeState', () => {
241242
});
242243
});
243244

244-
test('suppresses for the window when aggregate discovery becomes unavailable', async () => {
245-
const fixture = createState();
246-
await fixture.state.initialize();
247-
const beforeDiscoveryChange = fixture.state.presentation.get();
245+
test('suppresses for the window when the aggregate catalogue becomes unavailable or errors', async () => {
246+
const snapshots = [];
247+
for (const catalogueState of ['unavailable', 'error'] as const) {
248+
const fixture = createState();
249+
await fixture.state.initialize();
250+
const beforeDiscoveryChange = fixture.state.presentation.get();
248251

249-
fixture.initialDiscoveryState.set('unavailable', undefined);
252+
fixture.catalogueState.set(catalogueState, undefined);
253+
fixture.catalogueState.set('ready', undefined);
250254

251-
assert.deepStrictEqual({
252-
beforeDiscoveryChange,
253-
afterDiscoveryChange: fixture.state.presentation.get(),
254-
stored: fixture.storageService.get(AUTOMATIONS_NEW_BADGE_SEEN_STORAGE_KEY, StorageScope.APPLICATION),
255-
}, {
256-
beforeDiscoveryChange: 'outline',
257-
afterDiscoveryChange: undefined,
258-
stored: undefined,
259-
});
255+
snapshots.push({
256+
catalogueState,
257+
beforeDiscoveryChange,
258+
afterDiscoveryChange: fixture.state.presentation.get(),
259+
stored: fixture.storageService.get(AUTOMATIONS_NEW_BADGE_SEEN_STORAGE_KEY, StorageScope.APPLICATION),
260+
});
261+
}
262+
263+
assert.deepStrictEqual(snapshots, [
264+
{ catalogueState: 'unavailable', beforeDiscoveryChange: 'outline', afterDiscoveryChange: undefined, stored: undefined },
265+
{ catalogueState: 'error', beforeDiscoveryChange: 'outline', afterDiscoveryChange: undefined, stored: undefined },
266+
]);
260267
});
261268

262269
test('lets the hidden setting override and live-update the treatment', async () => {

‎src/vs/sessions/contrib/sessions/test/browser/sessionsList.test.ts‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -283,7 +283,7 @@ suite('Sessions - SessionsList', () => {
283283
instantiationService.stub(IAutomationService, new class extends mock<IAutomationService>() {
284284
override readonly automations = constObservable([]);
285285
override readonly runs = constObservable([]);
286-
override readonly initialDiscoveryState = constObservable('ready' as const);
286+
override readonly catalogueState = constObservable('ready' as const);
287287
});
288288
instantiationService.stub(ICustomViewService, new class extends mock<ICustomViewService>() {
289289
override readonly activeCustomView = activeCustomView;

‎src/vs/workbench/test/browser/componentFixtures/sessions/sessionsList.fixture.ts‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -368,7 +368,7 @@ async function renderSessionsList(ctx: ComponentFixtureContext, options: IRender
368368
reg.defineInstance(IAutomationService, new class extends mock<IAutomationService>() {
369369
override readonly automations = constObservable([]);
370370
override readonly runs = automationRuns;
371-
override readonly initialDiscoveryState = constObservable('ready' as const);
371+
override readonly catalogueState = constObservable('ready' as const);
372372
}());
373373
reg.defineInstance(IWorkbenchAssignmentService, new class extends mock<IWorkbenchAssignmentService>() {
374374
override readonly onDidRefetchAssignments = Event.None;

0 commit comments

Comments
 (0)