diff --git a/src/vs/platform/agentHost/browser/remoteAgentHostProtocolClient.ts b/src/vs/platform/agentHost/browser/remoteAgentHostProtocolClient.ts index ec27f7f886d..fb3e1c64bb5 100644 --- a/src/vs/platform/agentHost/browser/remoteAgentHostProtocolClient.ts +++ b/src/vs/platform/agentHost/browser/remoteAgentHostProtocolClient.ts @@ -1248,6 +1248,17 @@ export class RemoteAgentHostProtocolClient extends Disposable implements IAgentC return {}; }); } + case 'resourceCopy': { + if (!p.source) { sendError(new Error('Missing source')); return; } + if (!p.destination) { sendError(new Error('Missing destination')); return; } + const sourceUri = URI.parse(p.source as string); + const destinationUri = URI.parse(p.destination as string); + // Gate both source (read) and destination (write) + return void gateAndHandle(sourceUri, AgentHostPermissionMode.Read, { channel: ROOT_STATE_URI, uri: sourceUri.toString(), read: true }, async () => { + await this._fileService.copy(sourceUri, destinationUri, !p.failIfExists); + return {}; + }); + } default: this._logService.warn(`[RemoteAgentHostProtocol] Unhandled reverse request: ${method}`); sendError(new Error(`Unknown method: ${method}`)); diff --git a/src/vs/platform/agentHost/common/agentHostFileSystemProvider.ts b/src/vs/platform/agentHost/common/agentHostFileSystemProvider.ts index 388b9de8718..a830b702e8e 100644 --- a/src/vs/platform/agentHost/common/agentHostFileSystemProvider.ts +++ b/src/vs/platform/agentHost/common/agentHostFileSystemProvider.ts @@ -175,7 +175,7 @@ export abstract class AHPFileSystemProvider extends Disposable implements IFileS /** Decode a provider URI back to the original URI for the remote endpoint. */ protected abstract _decodeUri(resource: URI): URI; - /** Decode a provider URI back to the original URI for the remote endpoint. */ + /** Encode a remote URI back into a provider URI with the given authority. */ protected abstract _encodeUri(resource: URI, authority: string): URI; watch(resource: URI, opts: IWatchOptions): IDisposable { diff --git a/src/vs/platform/agentHost/common/state/protocol/channels-resource-watch/reducer.ts b/src/vs/platform/agentHost/common/state/protocol/channels-resource-watch/reducer.ts index 52683772409..79a656e66ca 100644 --- a/src/vs/platform/agentHost/common/state/protocol/channels-resource-watch/reducer.ts +++ b/src/vs/platform/agentHost/common/state/protocol/channels-resource-watch/reducer.ts @@ -7,6 +7,7 @@ // DO NOT EDIT -- auto-generated by scripts/sync-agent-host-protocol.ts import { ActionType } from '../common/actions.js'; +import { softAssertNever } from '../common/reducer-helpers.js'; import type { ResourceWatchState } from './state.js'; import type { ResourceWatchAction } from '../action-origin.generated.js'; @@ -33,6 +34,6 @@ export function resourceWatchReducer(state: ResourceWatchState, action: Resource return state; } - (log ?? console.warn)(`Unhandled action type: ${JSON.stringify(action)}`); + softAssertNever(action as never, log); return state; } diff --git a/src/vs/platform/agentHost/common/state/protocol/version/registry.ts b/src/vs/platform/agentHost/common/state/protocol/version/registry.ts index 484e2be9b0e..ca579aa499b 100644 --- a/src/vs/platform/agentHost/common/state/protocol/version/registry.ts +++ b/src/vs/platform/agentHost/common/state/protocol/version/registry.ts @@ -35,7 +35,7 @@ export const PROTOCOL_VERSION = '0.2.0'; * `scripts/verify-release-metadata.ts`. */ export const SUPPORTED_PROTOCOL_VERSIONS: readonly string[] = Object.freeze([ - '0.2.0', + PROTOCOL_VERSION, ]); // ─── SemVer Comparison ─────────────────────────────────────────────────────── diff --git a/src/vs/platform/agentHost/node/agentService.ts b/src/vs/platform/agentHost/node/agentService.ts index 98aebd53ea5..7d985a12797 100644 --- a/src/vs/platform/agentHost/node/agentService.ts +++ b/src/vs/platform/agentHost/node/agentService.ts @@ -1781,7 +1781,7 @@ export class AgentService extends Disposable implements IAgentService { descriptor, subscribers: 1, disposables, - pendingGc: new MutableDisposable(), + pendingGc: disposables.add(new MutableDisposable()), dispose: () => disposables.dispose(), }); return descriptor; diff --git a/src/vs/platform/agentHost/test/common/agentHostFileSystemProvider.test.ts b/src/vs/platform/agentHost/test/common/agentHostFileSystemProvider.test.ts index 382ef90a797..f9c8be58d49 100644 --- a/src/vs/platform/agentHost/test/common/agentHostFileSystemProvider.test.ts +++ b/src/vs/platform/agentHost/test/common/agentHostFileSystemProvider.test.ts @@ -588,10 +588,14 @@ suite('AgentHostFileSystemProvider - resolve / mkdir / copy / watch', () => { assert.strictEqual(connection.watchCalls[0].recursive, true); assert.deepStrictEqual(connection.watchCalls[0].excludes, { items: ['**/node_modules/**'] }); - const change: IFileChange = { resource: URI.parse('file:///watched/a.txt'), type: FileChangeType.UPDATED }; - onDidChange.fire([change]); + // When watchResource reports changes from the underlying filesystem, + // they come back with file:// URIs. The provider re-encodes them with + // the agent host authority. + const incomingChange: IFileChange = { resource: URI.parse('file:///watched/a.txt'), type: FileChangeType.UPDATED }; + const expectedChange: IFileChange = { resource: toAgentHostUri(URI.parse('file:///watched/a.txt'), 'remote'), type: FileChangeType.UPDATED }; + onDidChange.fire([incomingChange]); - assert.deepStrictEqual(received, [[change]]); + assert.deepStrictEqual(received, [[expectedChange]]); watchDisposable.dispose(); assert.strictEqual(handleDisposed, true, 'underlying handle should be disposed when wrapper is disposed'); diff --git a/src/vs/platform/agentHost/test/node/protocolServerHandler.test.ts b/src/vs/platform/agentHost/test/node/protocolServerHandler.test.ts index 1cb1e754981..f2128e2a57d 100644 --- a/src/vs/platform/agentHost/test/node/protocolServerHandler.test.ts +++ b/src/vs/platform/agentHost/test/node/protocolServerHandler.test.ts @@ -12,7 +12,7 @@ import { ensureNoDisposablesAreLeakedInTestSuite } from '../../../../base/test/c import { NullLogService } from '../../../log/common/log.js'; import { FileType } from '../../../files/common/files.js'; import { type IAgentCreateSessionConfig, type IAgentResolveSessionConfigParams, type IAgentService, type IAgentSessionConfigCompletionsParams, type IAgentSessionMetadata, type AuthenticateParams, type AuthenticateResult } from '../../common/agentService.js'; -import { CompletionsParams, CompletionsResult, ListSessionsResult, ResourceReadResult, ResolveSessionConfigResult, SessionConfigCompletionsResult } from '../../common/state/protocol/commands.js'; +import { CompletionsParams, CompletionsResult, ListSessionsResult, ResourceReadResult, ResolveSessionConfigResult, SessionConfigCompletionsResult, ResourceMkdirParams, ResourceMkdirResult, ResourceResolveParams, ResourceResolveResult, ResourceCopyParams, ResourceCopyResult } from '../../common/state/protocol/commands.js'; import { ActionType, type IRootConfigChangedAction, type SessionAction, type TerminalAction } from '../../common/state/sessionActions.js'; import { PROTOCOL_VERSION } from '../../common/state/protocol/version/registry.js'; import { isJsonRpcNotification, isJsonRpcRequest, isJsonRpcResponse, JSON_RPC_INTERNAL_ERROR, ProtocolError, AHP_UNSUPPORTED_PROTOCOL_VERSION, type AhpNotification, type InitializeResult, type ProtocolMessage, type ReconnectResult, type ResourceListResult, type ResourceWriteParams, type ResourceWriteResult, type IStateSnapshot } from '../../common/state/sessionProtocol.js'; @@ -145,11 +145,11 @@ class MockAgentService implements IAgentService { async resourceRead(_uri: URI): Promise { throw new Error('Not implemented'); } - async resourceCopy(): Promise<{}> { return {}; } + async resourceCopy(_params: ResourceCopyParams): Promise { return {}; } async resourceDelete(): Promise<{}> { return {}; } async resourceMove(): Promise<{}> { return {}; } - async resourceResolve(): Promise { throw new Error('Not implemented'); } - async resourceMkdir(): Promise<{}> { return {}; } + async resourceResolve(_params: ResourceResolveParams): Promise { throw new Error('Not implemented'); } + async resourceMkdir(_params: ResourceMkdirParams): Promise { return {}; } readonly watchSubscribeCalls: string[] = []; readonly watchUnsubscribeCalls: string[] = []; /** Channels for which `onResourceWatchSubscribed` should return a descriptor. */ diff --git a/src/vs/workbench/contrib/terminal/test/browser/agentHostPty.test.ts b/src/vs/workbench/contrib/terminal/test/browser/agentHostPty.test.ts index 98e4dc8a757..40fff3350cd 100644 --- a/src/vs/workbench/contrib/terminal/test/browser/agentHostPty.test.ts +++ b/src/vs/workbench/contrib/terminal/test/browser/agentHostPty.test.ts @@ -13,11 +13,12 @@ import { ActionType, StateAction } from '../../../../../platform/agentHost/commo import { RootState, TerminalClaimKind, type TerminalState } from '../../../../../platform/agentHost/common/state/protocol/state.js'; import type { CompletionsParams, CompletionsResult, CreateTerminalParams, ResolveSessionConfigResult, SessionConfigCompletionsResult } from '../../../../../platform/agentHost/common/state/protocol/commands.js'; import type { ActionEnvelope, IRootConfigChangedAction, SessionAction, TerminalAction, INotification } from '../../../../../platform/agentHost/common/state/sessionActions.js'; -import type { ResourceCopyParams, ResourceCopyResult, ResourceDeleteParams, ResourceDeleteResult, ResourceListResult, ResourceMoveParams, ResourceMoveResult, ResourceReadResult, ResourceWriteParams, ResourceWriteResult } from '../../../../../platform/agentHost/common/state/sessionProtocol.js'; +import type { ResourceCopyParams, ResourceCopyResult, ResourceDeleteParams, ResourceDeleteResult, ResourceListResult, ResourceMoveParams, ResourceMoveResult, ResourceReadResult, ResourceResolveParams, ResourceResolveResult, ResourceWriteParams, ResourceWriteResult, CreateResourceWatchParams, CreateResourceWatchResult, ResourceMkdirParams, ResourceMkdirResult } from '../../../../../platform/agentHost/common/state/sessionProtocol.js'; import { AgentHostPty } from '../../browser/agentHostPty.js'; import { IAgentSubscription } from '../../../../../platform/agentHost/common/state/agentSubscription.js'; import { StateComponents } from '../../../../../platform/agentHost/common/state/sessionState.js'; +import type { IRemoteWatchHandle } from '../../../../../platform/agentHost/common/agentHostFileSystemProvider.js'; // ---- Mock IAgentConnection -------------------------------------------------- class MockAgentConnection implements IAgentConnection { @@ -78,10 +79,10 @@ class MockAgentConnection implements IAgentConnection { async resourceCopy(_params: ResourceCopyParams): Promise { return {}; } async resourceDelete(_params: ResourceDeleteParams): Promise { return {}; } async resourceMove(_params: ResourceMoveParams): Promise { return {}; } - async resourceResolve(_params: any): Promise { throw new Error('Not implemented'); } - async resourceMkdir(_params: any): Promise { return {}; } - async createResourceWatch(_params: any): Promise { throw new Error('Not implemented'); } - async watchResource(_params: any): Promise { throw new Error('Not implemented'); } + async resourceResolve(_params: ResourceResolveParams): Promise { throw new Error('Not implemented'); } + async resourceMkdir(_params: ResourceMkdirParams): Promise { return {}; } + async createResourceWatch(_params: CreateResourceWatchParams): Promise { throw new Error('Not implemented'); } + async watchResource(_params: CreateResourceWatchParams): Promise { throw new Error('Not implemented'); } // ---- IAgentConnection new API (stubs for tests) ----- readonly rootState: IAgentSubscription = {