mirror of
https://github.com/microsoft/vscode.git
synced 2026-04-02 00:09:30 +01:00
comments and tests
This commit is contained in:
@@ -24,6 +24,7 @@ import { isJsonRpcNotification, isJsonRpcRequest, isJsonRpcResponse, type IJsonR
|
||||
import { ContentEncoding } from '../common/state/protocol/commands.js';
|
||||
import type { ISessionSummary } from '../common/state/sessionState.js';
|
||||
import { WebSocketClientTransport } from './webSocketClientTransport.js';
|
||||
import { encodeBase64 } from '../../../base/common/buffer.js';
|
||||
|
||||
/**
|
||||
* A protocol-level client for a single remote agent host connection.
|
||||
@@ -288,9 +289,8 @@ export class RemoteAgentHostProtocolClient extends Disposable implements IAgentC
|
||||
if (!p.uri) { sendError('Missing uri'); return; }
|
||||
this._fileService.readFile(URI.parse(p.uri)).then(content => {
|
||||
sendResult({
|
||||
data: content.value.toString(),
|
||||
encoding: ContentEncoding.Utf8,
|
||||
contentType: 'text/plain',
|
||||
data: encodeBase64(content.value),
|
||||
encoding: ContentEncoding.Base64,
|
||||
});
|
||||
}).catch(err => sendError(err instanceof Error ? err.message : String(err)));
|
||||
return;
|
||||
|
||||
@@ -5,7 +5,6 @@
|
||||
|
||||
import { VSBuffer } from '../../../base/common/buffer.js';
|
||||
import { SequencerByKey } from '../../../base/common/async.js';
|
||||
import { ResourceMap } from '../../../base/common/map.js';
|
||||
import { URI } from '../../../base/common/uri.js';
|
||||
import { IFileService } from '../../files/common/files.js';
|
||||
import { ILogService } from '../../log/common/log.js';
|
||||
@@ -42,11 +41,11 @@ export class AgentPluginManager implements IAgentPluginManager {
|
||||
/** Serializes concurrent sync operations per plugin URI. */
|
||||
private readonly _sequencer = new SequencerByKey<string>();
|
||||
|
||||
/** Nonces for plugins on disk, keyed by plugin URI. */
|
||||
private readonly _cachedNonces = new ResourceMap<string>();
|
||||
/** Nonces for plugins on disk, keyed by original customization URI string. */
|
||||
private readonly _cachedNonces = new Map<string, string>();
|
||||
|
||||
/** LRU order: most recently used plugin URIs at the end. */
|
||||
private readonly _lruOrder: URI[] = [];
|
||||
/** LRU order: most recently used original customization URI strings at the end. */
|
||||
private readonly _lruOrder: string[] = [];
|
||||
|
||||
/** Whether the on-disk cache has been loaded. */
|
||||
private _cacheLoaded = false;
|
||||
@@ -110,8 +109,8 @@ export class AgentPluginManager implements IAgentPluginManager {
|
||||
const destDir = URI.joinPath(this._basePath, key);
|
||||
|
||||
// Nonce cache hit — skip copy
|
||||
if (ref.nonce && this._cachedNonces.get(pluginUri) === ref.nonce) {
|
||||
this._touchLru(pluginUri);
|
||||
if (ref.nonce && this._cachedNonces.get(ref.uri) === ref.nonce) {
|
||||
this._touchLru(ref.uri);
|
||||
this._logService.trace(`[AgentPluginManager] Nonce match for ${ref.uri}, skipping copy`);
|
||||
return destDir;
|
||||
}
|
||||
@@ -121,9 +120,9 @@ export class AgentPluginManager implements IAgentPluginManager {
|
||||
await this._fileService.copy(pluginUri, destDir, true);
|
||||
|
||||
if (ref.nonce) {
|
||||
this._cachedNonces.set(pluginUri, ref.nonce);
|
||||
this._cachedNonces.set(ref.uri, ref.nonce);
|
||||
}
|
||||
this._touchLru(pluginUri);
|
||||
this._touchLru(ref.uri);
|
||||
await this._evictIfNeeded();
|
||||
await this._persistCache();
|
||||
|
||||
@@ -134,8 +133,8 @@ export class AgentPluginManager implements IAgentPluginManager {
|
||||
return uri.replace(/[^a-zA-Z0-9]/g, '-').replace(/-+/g, '-').replace(/^-|-$/g, '').substring(0, 128);
|
||||
}
|
||||
|
||||
private _touchLru(uri: URI): void {
|
||||
const idx = this._lruOrder.findIndex(u => u.toString() === uri.toString());
|
||||
private _touchLru(uri: string): void {
|
||||
const idx = this._lruOrder.indexOf(uri);
|
||||
if (idx !== -1) {
|
||||
this._lruOrder.splice(idx, 1);
|
||||
}
|
||||
@@ -149,13 +148,13 @@ export class AgentPluginManager implements IAgentPluginManager {
|
||||
break;
|
||||
}
|
||||
this._cachedNonces.delete(evictUri);
|
||||
const evictKey = this._keyForUri(evictUri.toString());
|
||||
const evictKey = this._keyForUri(evictUri);
|
||||
const evictDir = URI.joinPath(this._basePath, evictKey);
|
||||
this._logService.info(`[AgentPluginManager] Evicting plugin: ${evictUri.toString()}`);
|
||||
this._logService.info(`[AgentPluginManager] Evicting plugin: ${evictUri}`);
|
||||
try {
|
||||
await this._fileService.del(evictDir, { recursive: true });
|
||||
} catch (err) {
|
||||
this._logService.warn(`[AgentPluginManager] Failed to evict plugin: ${evictUri.toString()}`, err);
|
||||
this._logService.warn(`[AgentPluginManager] Failed to evict plugin: ${evictUri}`, err);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -181,9 +180,8 @@ export class AgentPluginManager implements IAgentPluginManager {
|
||||
// Entries are stored in LRU order (oldest first)
|
||||
for (const entry of entries) {
|
||||
if (typeof entry.uri === 'string' && typeof entry.nonce === 'string') {
|
||||
const uri = URI.parse(entry.uri);
|
||||
this._cachedNonces.set(uri, entry.nonce);
|
||||
this._lruOrder.push(uri);
|
||||
this._cachedNonces.set(entry.uri, entry.nonce);
|
||||
this._lruOrder.push(entry.uri);
|
||||
}
|
||||
}
|
||||
this._logService.trace(`[AgentPluginManager] Loaded ${entries.length} cache entries from disk`);
|
||||
@@ -199,7 +197,7 @@ export class AgentPluginManager implements IAgentPluginManager {
|
||||
for (const uri of this._lruOrder) {
|
||||
const nonce = this._cachedNonces.get(uri);
|
||||
if (nonce) {
|
||||
entries.push({ uri: uri.toString(), nonce });
|
||||
entries.push({ uri, nonce });
|
||||
}
|
||||
}
|
||||
await this._fileService.createFolder(this._basePath);
|
||||
|
||||
@@ -201,6 +201,7 @@ export class ProtocolServerHandler extends Disposable {
|
||||
if (client && this._clients.get(client.clientId) === client) {
|
||||
this._logService.info(`[ProtocolServer] Client disconnected: ${client.clientId}`);
|
||||
this._clients.delete(client.clientId);
|
||||
this._rejectPendingReverseRequests(client.clientId);
|
||||
this._onDidChangeConnectionCount.fire(this._clients.size);
|
||||
}
|
||||
disposables.dispose();
|
||||
@@ -403,11 +404,12 @@ export class ProtocolServerHandler extends Disposable {
|
||||
// ---- Reverse RPC (server → client requests) ----------------------------
|
||||
|
||||
private _reverseRequestId = 0;
|
||||
private readonly _pendingReverseRequests = new Map<number, { resolve: (value: unknown) => void; reject: (reason: unknown) => void }>();
|
||||
private readonly _pendingReverseRequests = new Map<number, { clientId: string; resolve: (value: unknown) => void; reject: (reason: unknown) => void }>();
|
||||
|
||||
/**
|
||||
* Sends a JSON-RPC request to a connected client and waits for the response.
|
||||
* Used for reverse-RPC operations like reading client-side files.
|
||||
* Rejects if the client disconnects or the server is disposed.
|
||||
*/
|
||||
private _sendReverseRequest<T>(clientId: string, method: string, params: unknown): Promise<T> {
|
||||
const client = this._clients.get(clientId);
|
||||
@@ -416,12 +418,24 @@ export class ProtocolServerHandler extends Disposable {
|
||||
}
|
||||
const id = ++this._reverseRequestId;
|
||||
return new Promise<T>((resolve, reject) => {
|
||||
this._pendingReverseRequests.set(id, { resolve: resolve as (value: unknown) => void, reject });
|
||||
this._pendingReverseRequests.set(id, { clientId, resolve: resolve as (value: unknown) => void, reject });
|
||||
const request: IJsonRpcRequest = { jsonrpc: '2.0', id, method, params };
|
||||
client.transport.send(request);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Rejects and clears all pending reverse-RPC requests for a given client.
|
||||
*/
|
||||
private _rejectPendingReverseRequests(clientId: string): void {
|
||||
for (const [id, pending] of this._pendingReverseRequests) {
|
||||
if (pending.clientId === clientId) {
|
||||
this._pendingReverseRequests.delete(id);
|
||||
pending.reject(new Error(`Client ${clientId} disconnected`));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private _handleRequest(client: IConnectedClient, method: string, params: unknown, id: number): void {
|
||||
const handler = this._requestHandlers.hasOwnProperty(method) ? this._requestHandlers[method as RequestMethod] : undefined;
|
||||
if (handler) {
|
||||
@@ -512,6 +526,10 @@ export class ProtocolServerHandler extends Disposable {
|
||||
client.disposables.dispose();
|
||||
}
|
||||
this._clients.clear();
|
||||
for (const [, pending] of this._pendingReverseRequests) {
|
||||
pending.reject(new Error('ProtocolServerHandler disposed'));
|
||||
}
|
||||
this._pendingReverseRequests.clear();
|
||||
this._replayBuffer.length = 0;
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
@@ -12,6 +12,7 @@ import { ensureNoDisposablesAreLeakedInTestSuite } from '../../../../base/test/c
|
||||
import { FileService } from '../../../files/common/fileService.js';
|
||||
import { InMemoryFileSystemProvider } from '../../../files/common/inMemoryFilesystemProvider.js';
|
||||
import { NullLogService } from '../../../log/common/log.js';
|
||||
import { AGENT_CLIENT_SCHEME, toAgentClientUri } from '../../common/agentClientUri.js';
|
||||
import { CustomizationStatus, type ICustomizationRef, type ISessionCustomization } from '../../common/state/sessionState.js';
|
||||
import { AgentPluginManager } from '../../node/agentPluginManager.js';
|
||||
|
||||
@@ -25,6 +26,7 @@ suite('AgentPluginManager', () => {
|
||||
setup(() => {
|
||||
fileService = disposables.add(new FileService(new NullLogService()));
|
||||
disposables.add(fileService.registerProvider(Schemas.inMemory, disposables.add(new InMemoryFileSystemProvider())));
|
||||
disposables.add(fileService.registerProvider(AGENT_CLIENT_SCHEME, disposables.add(new InMemoryFileSystemProvider())));
|
||||
manager = new AgentPluginManager(basePath, fileService, new NullLogService());
|
||||
});
|
||||
|
||||
@@ -40,10 +42,11 @@ suite('AgentPluginManager', () => {
|
||||
}
|
||||
|
||||
async function seedPluginDir(name: string, files: Record<string, string>): Promise<void> {
|
||||
const dir = URI.from({ scheme: Schemas.inMemory, path: `/plugins/${name}` });
|
||||
await fileService.createFolder(dir);
|
||||
const originalUri = URI.from({ scheme: Schemas.inMemory, path: `/plugins/${name}` });
|
||||
const agentClientDir = toAgentClientUri(originalUri, 'test-client');
|
||||
await fileService.createFolder(agentClientDir);
|
||||
for (const [fileName, content] of Object.entries(files)) {
|
||||
await fileService.writeFile(URI.joinPath(dir, fileName), VSBuffer.fromString(content));
|
||||
await fileService.writeFile(URI.joinPath(agentClientDir, fileName), VSBuffer.fromString(content));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -79,8 +79,8 @@ export class RemoteAgentCustomizationItemProvider extends Disposable implements
|
||||
// When a session is active, prefer session-level data (includes status)
|
||||
if (this._sessionCustomizations) {
|
||||
return this._sessionCustomizations.map(sc => ({
|
||||
uri: URI.isUri(sc.customization.uri) ? sc.customization.uri : URI.parse(sc.customization.uri as unknown as string),
|
||||
type: 'customization',
|
||||
uri: URI.isUri(sc.customization.uri) ? sc.customization.uri : URI.parse(sc.customization.uri),
|
||||
type: 'plugin',
|
||||
name: sc.customization.displayName,
|
||||
description: sc.customization.description,
|
||||
status: toStatusString(sc.status),
|
||||
@@ -92,7 +92,7 @@ export class RemoteAgentCustomizationItemProvider extends Disposable implements
|
||||
// Baseline: agent-level customizations (no status info)
|
||||
return this._agentCustomizations.map(ref => ({
|
||||
uri: URI.isUri(ref.uri) ? ref.uri : URI.parse(ref.uri as unknown as string),
|
||||
type: 'customization',
|
||||
type: 'plugin',
|
||||
name: ref.displayName,
|
||||
description: ref.description,
|
||||
}));
|
||||
|
||||
@@ -46,7 +46,13 @@ export class AgentCustomizationSyncProvider extends Disposable implements ICusto
|
||||
const stored = this._storageService.get(this._storageKey, StorageScope.PROFILE);
|
||||
this._entries = new Map();
|
||||
if (stored) {
|
||||
const parsed = JSON.parse(stored) as (string | ISyncEntry)[];
|
||||
let parsed: (string | ISyncEntry)[] | undefined;
|
||||
try {
|
||||
parsed = JSON.parse(stored) as (string | ISyncEntry)[];
|
||||
} catch {
|
||||
// ignored
|
||||
}
|
||||
|
||||
if (Array.isArray(parsed)) {
|
||||
for (const item of parsed) {
|
||||
if (typeof item === 'string') {
|
||||
|
||||
@@ -407,13 +407,14 @@ export function getEffectiveCommandSource(hook: IHookCommand, os: OperatingSyste
|
||||
* Returns the actual field name from the JSON (e.g., 'bash' instead of 'osx' if bash was used).
|
||||
* This is used for editor focus to highlight the correct field.
|
||||
*/
|
||||
export function getEffectiveCommandFieldKey(hook: IHookCommand, os: OperatingSystem): string {
|
||||
export function getEffectiveCommandFieldKey(hook: IHookCommand | IParsedHookCommand, os: OperatingSystem): string {
|
||||
const h = hook as Partial<IHookCommand>;
|
||||
if (os === OperatingSystem.Windows && hook.windows) {
|
||||
return hook.windowsSource ?? 'windows';
|
||||
return h.windowsSource ?? 'windows';
|
||||
} else if (os === OperatingSystem.Macintosh && hook.osx) {
|
||||
return hook.osxSource ?? 'osx';
|
||||
return h.osxSource ?? 'osx';
|
||||
} else if (os === OperatingSystem.Linux && hook.linux) {
|
||||
return hook.linuxSource ?? 'linux';
|
||||
return h.linuxSource ?? 'linux';
|
||||
}
|
||||
return 'command';
|
||||
}
|
||||
|
||||
@@ -40,6 +40,9 @@ import { TestFileService } from '../../../../../test/common/workbenchTestService
|
||||
import { ILabelService } from '../../../../../../platform/label/common/label.js';
|
||||
import { MockLabelService } from '../../../../../services/label/test/common/mockLabelService.js';
|
||||
import { IAgentHostFileSystemService } from '../../../../../services/agentHost/common/agentHostFileSystemService.js';
|
||||
import { ICustomizationHarnessService } from '../../../common/customizationHarnessService.js';
|
||||
import { IAgentPluginService } from '../../../common/plugins/agentPluginService.js';
|
||||
import { IStorageService, InMemoryStorageService } from '../../../../../../platform/storage/common/storage.js';
|
||||
|
||||
// ---- Mock agent host service ------------------------------------------------
|
||||
|
||||
@@ -86,6 +89,12 @@ class MockAgentHostService extends mock<IAgentHostService>() {
|
||||
// Protocol methods
|
||||
public override readonly clientId = 'test-window-1';
|
||||
public dispatchedActions: { action: ISessionAction; clientId: string; clientSeq: number }[] = [];
|
||||
|
||||
/** Returns dispatched actions filtered to turn-related types only
|
||||
* (excludes lifecycle actions like activeClientChanged). */
|
||||
get turnActions() {
|
||||
return this.dispatchedActions.filter(d => d.action.type === 'session/turnStarted');
|
||||
}
|
||||
public sessionStates = new Map<string, ISessionState>();
|
||||
override async subscribe(resource: URI): Promise<IStateSnapshot> {
|
||||
const resourceStr = resource.toString();
|
||||
@@ -192,6 +201,13 @@ function createTestServices(disposables: DisposableStore) {
|
||||
registerAuthority: () => toDisposable(() => { }),
|
||||
ensureSyncedCustomizationProvider: () => { },
|
||||
});
|
||||
instantiationService.stub(IStorageService, disposables.add(new InMemoryStorageService()));
|
||||
instantiationService.stub(ICustomizationHarnessService, {
|
||||
registerExternalHarness: () => toDisposable(() => { }),
|
||||
});
|
||||
instantiationService.stub(IAgentPluginService, {
|
||||
plugins: observableValue('plugins', []),
|
||||
});
|
||||
|
||||
return { instantiationService, agentHostService, chatAgentService };
|
||||
}
|
||||
@@ -255,6 +271,10 @@ async function startTurn(
|
||||
const chatSession = await sessionHandler.provideChatSessionContent(sessionResource, CancellationToken.None);
|
||||
ds.add(toDisposable(() => chatSession.dispose()));
|
||||
|
||||
// Clear any lifecycle actions (e.g. activeClientChanged from customization setup)
|
||||
// so tests only see turn-related dispatches.
|
||||
agentHostService.dispatchedActions.length = 0;
|
||||
|
||||
const collected: IChatProgress[][] = [];
|
||||
const seq = { v: 1 };
|
||||
|
||||
@@ -272,7 +292,9 @@ async function startTurn(
|
||||
|
||||
await timeout(10);
|
||||
|
||||
const lastDispatch = agentHostService.dispatchedActions[agentHostService.dispatchedActions.length - 1];
|
||||
// Filter for turn-related dispatches only (skip activeClientChanged etc.)
|
||||
const turnDispatches = agentHostService.dispatchedActions.filter(d => d.action.type === 'session/turnStarted');
|
||||
const lastDispatch = turnDispatches[turnDispatches.length - 1] ?? agentHostService.dispatchedActions[agentHostService.dispatchedActions.length - 1];
|
||||
const session = (lastDispatch?.action as ITurnStartedAction)?.session;
|
||||
const turnId = (lastDispatch?.action as ITurnStartedAction)?.turnId;
|
||||
|
||||
@@ -365,9 +387,9 @@ suite('AgentHostChatContribution', () => {
|
||||
fire({ type: 'session/turnComplete', session, turnId } as ISessionAction);
|
||||
await turnPromise;
|
||||
|
||||
assert.strictEqual(agentHostService.dispatchedActions.length, 1);
|
||||
assert.strictEqual(agentHostService.dispatchedActions[0].action.type, 'session/turnStarted');
|
||||
assert.strictEqual((agentHostService.dispatchedActions[0].action as ITurnStartedAction).userMessage.text, 'Hello');
|
||||
assert.strictEqual(agentHostService.turnActions.length, 1);
|
||||
assert.strictEqual(agentHostService.turnActions[0].action.type, 'session/turnStarted');
|
||||
assert.strictEqual((agentHostService.turnActions[0].action as ITurnStartedAction).userMessage.text, 'Hello');
|
||||
assert.ok(AgentSession.id(URI.parse(session)).startsWith('sdk-session-'));
|
||||
}));
|
||||
|
||||
@@ -378,13 +400,16 @@ suite('AgentHostChatContribution', () => {
|
||||
const chatSession = await sessionHandler.provideChatSessionContent(resource, CancellationToken.None);
|
||||
disposables.add(toDisposable(() => chatSession.dispose()));
|
||||
|
||||
// Clear lifecycle actions so only turn dispatches are counted
|
||||
agentHostService.dispatchedActions.length = 0;
|
||||
|
||||
// First turn
|
||||
const turn1Promise = chatSession.requestHandler!(
|
||||
makeRequest({ message: 'First', sessionResource: resource }),
|
||||
() => { }, [], CancellationToken.None,
|
||||
);
|
||||
await timeout(10);
|
||||
const dispatch1 = agentHostService.dispatchedActions[0];
|
||||
const dispatch1 = agentHostService.turnActions[0];
|
||||
const action1 = dispatch1.action as ITurnStartedAction;
|
||||
// Echo the turnStarted to clear pending write-ahead
|
||||
agentHostService.fireAction({ action: dispatch1.action, serverSeq: 1, origin: { clientId: agentHostService.clientId, clientSeq: dispatch1.clientSeq } });
|
||||
@@ -397,16 +422,16 @@ suite('AgentHostChatContribution', () => {
|
||||
() => { }, [], CancellationToken.None,
|
||||
);
|
||||
await timeout(10);
|
||||
const dispatch2 = agentHostService.dispatchedActions[1];
|
||||
const dispatch2 = agentHostService.turnActions[1];
|
||||
const action2 = dispatch2.action as ITurnStartedAction;
|
||||
agentHostService.fireAction({ action: dispatch2.action, serverSeq: 3, origin: { clientId: agentHostService.clientId, clientSeq: dispatch2.clientSeq } });
|
||||
agentHostService.fireAction({ action: { type: 'session/turnComplete', session: action2.session, turnId: action2.turnId } as ISessionAction, serverSeq: 4, origin: undefined });
|
||||
await turn2Promise;
|
||||
|
||||
assert.strictEqual(agentHostService.dispatchedActions.length, 2);
|
||||
assert.strictEqual(agentHostService.turnActions.length, 2);
|
||||
assert.strictEqual(
|
||||
(agentHostService.dispatchedActions[0].action as ITurnStartedAction).session.toString(),
|
||||
(agentHostService.dispatchedActions[1].action as ITurnStartedAction).session.toString(),
|
||||
(agentHostService.turnActions[0].action as ITurnStartedAction).session.toString(),
|
||||
(agentHostService.turnActions[1].action as ITurnStartedAction).session.toString(),
|
||||
);
|
||||
}));
|
||||
|
||||
@@ -1265,8 +1290,8 @@ suite('AgentHostChatContribution', () => {
|
||||
fire({ type: 'session/turnComplete', session, turnId } as ISessionAction);
|
||||
await turnPromise;
|
||||
|
||||
assert.strictEqual(agentHostService.dispatchedActions.length, 1);
|
||||
const turnAction = agentHostService.dispatchedActions[0].action as ITurnStartedAction;
|
||||
assert.strictEqual(agentHostService.turnActions.length, 1);
|
||||
const turnAction = agentHostService.turnActions[0].action as ITurnStartedAction;
|
||||
assert.deepStrictEqual(turnAction.userMessage.attachments, [
|
||||
{ type: 'file', path: URI.file('/workspace/test.ts').fsPath, displayName: 'test.ts' },
|
||||
]);
|
||||
@@ -1286,8 +1311,8 @@ suite('AgentHostChatContribution', () => {
|
||||
fire({ type: 'session/turnComplete', session, turnId } as ISessionAction);
|
||||
await turnPromise;
|
||||
|
||||
assert.strictEqual(agentHostService.dispatchedActions.length, 1);
|
||||
const turnAction = agentHostService.dispatchedActions[0].action as ITurnStartedAction;
|
||||
assert.strictEqual(agentHostService.turnActions.length, 1);
|
||||
const turnAction = agentHostService.turnActions[0].action as ITurnStartedAction;
|
||||
assert.deepStrictEqual(turnAction.userMessage.attachments, [
|
||||
{ type: 'directory', path: URI.file('/workspace/src').fsPath, displayName: 'src' },
|
||||
]);
|
||||
@@ -1307,8 +1332,8 @@ suite('AgentHostChatContribution', () => {
|
||||
fire({ type: 'session/turnComplete', session, turnId } as ISessionAction);
|
||||
await turnPromise;
|
||||
|
||||
assert.strictEqual(agentHostService.dispatchedActions.length, 1);
|
||||
const turnAction = agentHostService.dispatchedActions[0].action as ITurnStartedAction;
|
||||
assert.strictEqual(agentHostService.turnActions.length, 1);
|
||||
const turnAction = agentHostService.turnActions[0].action as ITurnStartedAction;
|
||||
assert.deepStrictEqual(turnAction.userMessage.attachments, [
|
||||
{ type: 'selection', path: URI.file('/workspace/foo.ts').fsPath, displayName: 'selection' },
|
||||
]);
|
||||
@@ -1328,8 +1353,8 @@ suite('AgentHostChatContribution', () => {
|
||||
fire({ type: 'session/turnComplete', session, turnId } as ISessionAction);
|
||||
await turnPromise;
|
||||
|
||||
assert.strictEqual(agentHostService.dispatchedActions.length, 1);
|
||||
const turnAction = agentHostService.dispatchedActions[0].action as ITurnStartedAction;
|
||||
assert.strictEqual(agentHostService.turnActions.length, 1);
|
||||
const turnAction = agentHostService.turnActions[0].action as ITurnStartedAction;
|
||||
// No attachments because it's not a file:// URI
|
||||
assert.strictEqual(turnAction.userMessage.attachments, undefined);
|
||||
}));
|
||||
@@ -1348,8 +1373,8 @@ suite('AgentHostChatContribution', () => {
|
||||
fire({ type: 'session/turnComplete', session, turnId } as ISessionAction);
|
||||
await turnPromise;
|
||||
|
||||
assert.strictEqual(agentHostService.dispatchedActions.length, 1);
|
||||
const turnAction = agentHostService.dispatchedActions[0].action as ITurnStartedAction;
|
||||
assert.strictEqual(agentHostService.turnActions.length, 1);
|
||||
const turnAction = agentHostService.turnActions[0].action as ITurnStartedAction;
|
||||
assert.strictEqual(turnAction.userMessage.attachments, undefined);
|
||||
}));
|
||||
|
||||
@@ -1370,8 +1395,8 @@ suite('AgentHostChatContribution', () => {
|
||||
fire({ type: 'session/turnComplete', session, turnId } as ISessionAction);
|
||||
await turnPromise;
|
||||
|
||||
assert.strictEqual(agentHostService.dispatchedActions.length, 1);
|
||||
const turnAction = agentHostService.dispatchedActions[0].action as ITurnStartedAction;
|
||||
assert.strictEqual(agentHostService.turnActions.length, 1);
|
||||
const turnAction = agentHostService.turnActions[0].action as ITurnStartedAction;
|
||||
assert.deepStrictEqual(turnAction.userMessage.attachments, [
|
||||
{ type: 'file', path: URI.file('/workspace/a.ts').fsPath, displayName: 'a.ts' },
|
||||
{ type: 'directory', path: URI.file('/workspace/lib').fsPath, displayName: 'lib' },
|
||||
@@ -1387,8 +1412,8 @@ suite('AgentHostChatContribution', () => {
|
||||
fire({ type: 'session/turnComplete', session, turnId } as ISessionAction);
|
||||
await turnPromise;
|
||||
|
||||
assert.strictEqual(agentHostService.dispatchedActions.length, 1);
|
||||
const turnAction = agentHostService.dispatchedActions[0].action as ITurnStartedAction;
|
||||
assert.strictEqual(agentHostService.turnActions.length, 1);
|
||||
const turnAction = agentHostService.turnActions[0].action as ITurnStartedAction;
|
||||
assert.strictEqual(turnAction.userMessage.attachments, undefined);
|
||||
}));
|
||||
});
|
||||
@@ -1560,8 +1585,8 @@ suite('AgentHostChatContribution', () => {
|
||||
await turnPromise;
|
||||
|
||||
// Turn dispatched via connection.dispatchAction
|
||||
assert.strictEqual(agentHostService.dispatchedActions.length, 1);
|
||||
assert.strictEqual((agentHostService.dispatchedActions[0].action as ITurnStartedAction).userMessage.text, 'Test message');
|
||||
assert.strictEqual(agentHostService.turnActions.length, 1);
|
||||
assert.strictEqual((agentHostService.turnActions[0].action as ITurnStartedAction).userMessage.text, 'Test message');
|
||||
}));
|
||||
});
|
||||
|
||||
@@ -1857,13 +1882,16 @@ suite('AgentHostChatContribution', () => {
|
||||
const chatSession = await sessionHandler.provideChatSessionContent(sessionResource, CancellationToken.None);
|
||||
disposables.add(toDisposable(() => chatSession.dispose()));
|
||||
|
||||
// Clear lifecycle actions so only turn dispatches are counted
|
||||
agentHostService.dispatchedActions.length = 0;
|
||||
|
||||
// First, do a normal turn so the backend session is created
|
||||
const turn1Promise = chatSession.requestHandler!(
|
||||
makeRequest({ message: 'Hello', sessionResource }),
|
||||
() => { }, [], CancellationToken.None,
|
||||
);
|
||||
await timeout(10);
|
||||
const dispatch1 = agentHostService.dispatchedActions[0];
|
||||
const dispatch1 = agentHostService.turnActions[0];
|
||||
const action1 = dispatch1.action as ITurnStartedAction;
|
||||
const session = action1.session;
|
||||
// Echo + complete the first turn
|
||||
@@ -1904,13 +1932,16 @@ suite('AgentHostChatContribution', () => {
|
||||
const chatSession = await sessionHandler.provideChatSessionContent(sessionResource, CancellationToken.None);
|
||||
disposables.add(toDisposable(() => chatSession.dispose()));
|
||||
|
||||
// Clear lifecycle actions so only turn dispatches are counted
|
||||
agentHostService.dispatchedActions.length = 0;
|
||||
|
||||
// Normal turn to create backend session
|
||||
const turn1Promise = chatSession.requestHandler!(
|
||||
makeRequest({ message: 'Init', sessionResource }),
|
||||
() => { }, [], CancellationToken.None,
|
||||
);
|
||||
await timeout(10);
|
||||
const dispatch1 = agentHostService.dispatchedActions[0];
|
||||
const dispatch1 = agentHostService.turnActions[0];
|
||||
const action1 = dispatch1.action as ITurnStartedAction;
|
||||
const session = action1.session;
|
||||
agentHostService.fireAction({ action: dispatch1.action, serverSeq: 1, origin: { clientId: agentHostService.clientId, clientSeq: dispatch1.clientSeq } });
|
||||
@@ -1976,13 +2007,16 @@ suite('AgentHostChatContribution', () => {
|
||||
const serverRequestEvents: { prompt: string }[] = [];
|
||||
disposables.add(chatSession.onDidStartServerRequest!(e => serverRequestEvents.push(e)));
|
||||
|
||||
// Clear lifecycle actions so only turn dispatches are counted
|
||||
agentHostService.dispatchedActions.length = 0;
|
||||
|
||||
// Normal client turn — should NOT fire onDidStartServerRequest
|
||||
const turnPromise = chatSession.requestHandler!(
|
||||
makeRequest({ message: 'Client turn', sessionResource }),
|
||||
() => { }, [], CancellationToken.None,
|
||||
);
|
||||
await timeout(10);
|
||||
const dispatch = agentHostService.dispatchedActions[0];
|
||||
const dispatch = agentHostService.turnActions[0];
|
||||
const action = dispatch.action as ITurnStartedAction;
|
||||
agentHostService.fireAction({ action: dispatch.action, serverSeq: 1, origin: { clientId: agentHostService.clientId, clientSeq: dispatch.clientSeq } });
|
||||
agentHostService.fireAction({ action: { type: 'session/turnComplete', session: action.session, turnId: action.turnId } as ISessionAction, serverSeq: 2, origin: undefined });
|
||||
@@ -1998,13 +2032,16 @@ suite('AgentHostChatContribution', () => {
|
||||
const chatSession = await sessionHandler.provideChatSessionContent(sessionResource, CancellationToken.None);
|
||||
disposables.add(toDisposable(() => chatSession.dispose()));
|
||||
|
||||
// Clear lifecycle actions so only turn dispatches are counted
|
||||
agentHostService.dispatchedActions.length = 0;
|
||||
|
||||
// First, do a normal turn so the backend session is created
|
||||
const turn1Promise = chatSession.requestHandler!(
|
||||
makeRequest({ message: 'Init', sessionResource }),
|
||||
() => { }, [], CancellationToken.None,
|
||||
);
|
||||
await timeout(10);
|
||||
const dispatch1 = agentHostService.dispatchedActions[0];
|
||||
const dispatch1 = agentHostService.turnActions[0];
|
||||
const action1 = dispatch1.action as ITurnStartedAction;
|
||||
const session = action1.session;
|
||||
agentHostService.fireAction({ action: dispatch1.action, serverSeq: 1, origin: { clientId: agentHostService.clientId, clientSeq: dispatch1.clientSeq } });
|
||||
@@ -2061,13 +2098,16 @@ suite('AgentHostChatContribution', () => {
|
||||
const chatSession = await sessionHandler.provideChatSessionContent(sessionResource, CancellationToken.None);
|
||||
disposables.add(toDisposable(() => chatSession.dispose()));
|
||||
|
||||
// Clear lifecycle actions so only turn dispatches are counted
|
||||
agentHostService.dispatchedActions.length = 0;
|
||||
|
||||
// First, do a normal turn so the backend session is created
|
||||
const turn1Promise = chatSession.requestHandler!(
|
||||
makeRequest({ message: 'Init', sessionResource }),
|
||||
() => { }, [], CancellationToken.None,
|
||||
);
|
||||
await timeout(10);
|
||||
const dispatch1 = agentHostService.dispatchedActions[0];
|
||||
const dispatch1 = agentHostService.turnActions[0];
|
||||
const action1 = dispatch1.action as ITurnStartedAction;
|
||||
const session = action1.session;
|
||||
agentHostService.fireAction({ action: dispatch1.action, serverSeq: 1, origin: { clientId: agentHostService.clientId, clientSeq: dispatch1.clientSeq } });
|
||||
|
||||
Reference in New Issue
Block a user