mirror of
https://github.com/microsoft/vscode.git
synced 2026-04-29 13:03:42 +01:00
Merge remote-tracking branch 'origin/main' into connor4312/edit-metadata
This commit is contained in:
@@ -723,4 +723,67 @@ suite('AgentSideEffects', () => {
|
||||
assert.strictEqual(state?.steeringMessage, undefined);
|
||||
});
|
||||
});
|
||||
|
||||
// ---- Edit auto-approve patterns -----------------------------------------
|
||||
|
||||
suite('edit auto-approve patterns', () => {
|
||||
|
||||
test('auto-approves regular files with default patterns', () => {
|
||||
setupSession();
|
||||
disposables.add(sideEffects.registerProgressListener(agent));
|
||||
startTurn('turn-1');
|
||||
|
||||
// Fire tool_start so the tool call exists in the state
|
||||
agent.fireProgress({ session: sessionUri, type: 'tool_start' as const, toolCallId: 'tc-write-1', toolName: 'bash', displayName: 'Write File', invocationMessage: 'Write' });
|
||||
|
||||
// Fire tool_ready with write permission for a regular .ts file
|
||||
agent.fireProgress({ session: sessionUri, type: 'tool_ready' as const, toolCallId: 'tc-write-1', invocationMessage: 'Write src/app.ts', permissionKind: 'write', permissionPath: '/workspace/src/app.ts' });
|
||||
|
||||
// The agent should have been auto-responded to with approved=true
|
||||
const permCall = agent.respondToPermissionCalls.find(c => c.requestId === 'tc-write-1');
|
||||
assert.ok(permCall, 'should auto-approve regular files with default patterns');
|
||||
assert.strictEqual(permCall!.approved, true);
|
||||
|
||||
// The tool call should NOT be in PendingConfirmation state (it was auto-approved)
|
||||
const state = stateManager.getSessionState(sessionUri.toString());
|
||||
const tcPart = state?.activeTurn?.responseParts.find(
|
||||
p => p.kind === ResponsePartKind.ToolCall && p.toolCall.toolCallId === 'tc-write-1'
|
||||
) as IToolCallResponsePart | undefined;
|
||||
assert.ok(tcPart);
|
||||
assert.strictEqual(tcPart!.toolCall.status, ToolCallStatus.Running);
|
||||
});
|
||||
|
||||
test('default patterns block .env files', () => {
|
||||
setupSession();
|
||||
disposables.add(sideEffects.registerProgressListener(agent));
|
||||
startTurn('turn-1');
|
||||
|
||||
agent.fireProgress({ session: sessionUri, type: 'tool_start' as const, toolCallId: 'tc-write-2', toolName: 'bash', displayName: 'Write File', invocationMessage: 'Write' });
|
||||
agent.fireProgress({ session: sessionUri, type: 'tool_ready' as const, toolCallId: 'tc-write-2', invocationMessage: 'Write .env', permissionKind: 'write', permissionPath: '/workspace/.env' });
|
||||
|
||||
// Should NOT have auto-responded
|
||||
const permCall = agent.respondToPermissionCalls.find(c => c.requestId === 'tc-write-2');
|
||||
assert.ok(!permCall, 'should not auto-approve .env files with default patterns');
|
||||
|
||||
// The tool call should be in PendingConfirmation state
|
||||
const state = stateManager.getSessionState(sessionUri.toString());
|
||||
const tcPart = state?.activeTurn?.responseParts.find(
|
||||
p => p.kind === ResponsePartKind.ToolCall && p.toolCall.toolCallId === 'tc-write-2'
|
||||
) as IToolCallResponsePart | undefined;
|
||||
assert.ok(tcPart);
|
||||
assert.strictEqual(tcPart!.toolCall.status, ToolCallStatus.PendingConfirmation);
|
||||
});
|
||||
|
||||
test('default patterns block .git files', () => {
|
||||
setupSession();
|
||||
disposables.add(sideEffects.registerProgressListener(agent));
|
||||
startTurn('turn-1');
|
||||
|
||||
agent.fireProgress({ session: sessionUri, type: 'tool_start' as const, toolCallId: 'tc-write-3', toolName: 'bash', displayName: 'Write File', invocationMessage: 'Write' });
|
||||
agent.fireProgress({ session: sessionUri, type: 'tool_ready' as const, toolCallId: 'tc-write-3', invocationMessage: 'Write .git/config', permissionKind: 'write', permissionPath: '/workspace/.git/config' });
|
||||
|
||||
const permCall = agent.respondToPermissionCalls.find(c => c.requestId === 'tc-write-3');
|
||||
assert.ok(!permCall, 'should not auto-approve .git files with default patterns');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { timeout } from '../../../../base/common/async.js';
|
||||
import { Emitter } from '../../../../base/common/event.js';
|
||||
import { URI } from '../../../../base/common/uri.js';
|
||||
import type { IAuthorizationProtectedResourceMetadata } from '../../../../base/common/oauth.js';
|
||||
@@ -212,10 +213,12 @@ export class ScriptedMockAgent implements IAgent {
|
||||
toolInput: 'echo test',
|
||||
confirmationTitle: 'Run a test command',
|
||||
};
|
||||
setTimeout(() => {
|
||||
(async () => {
|
||||
await timeout(10);
|
||||
this._onDidSessionProgress.fire(toolStartEvent);
|
||||
setTimeout(() => this._onDidSessionProgress.fire(toolReadyEvent), 5);
|
||||
}, 10);
|
||||
await timeout(5);
|
||||
this._onDidSessionProgress.fire(toolReadyEvent);
|
||||
})();
|
||||
this._pendingPermissions.set('tc-perm-1', (approved) => {
|
||||
if (approved) {
|
||||
this._fireSequence(session, [
|
||||
@@ -227,6 +230,42 @@ export class ScriptedMockAgent implements IAgent {
|
||||
break;
|
||||
}
|
||||
|
||||
case 'write-file': {
|
||||
// Fire tool_start + tool_ready with write permission for a regular file (should be auto-approved)
|
||||
(async () => {
|
||||
await timeout(10);
|
||||
this._onDidSessionProgress.fire({ type: 'tool_start', session, toolCallId: 'tc-write-1', toolName: 'write', displayName: 'Write File', invocationMessage: 'Write file' });
|
||||
await timeout(5);
|
||||
this._onDidSessionProgress.fire({ type: 'tool_ready', session, toolCallId: 'tc-write-1', invocationMessage: 'Write src/app.ts', permissionKind: 'write', permissionPath: '/workspace/src/app.ts' });
|
||||
// Auto-approved writes resolve immediately — complete the tool and turn
|
||||
await timeout(10);
|
||||
this._fireSequence(session, [
|
||||
{ type: 'tool_complete', session, toolCallId: 'tc-write-1', result: { pastTenseMessage: 'Wrote file', content: [{ type: ToolResultContentType.Text, text: 'ok' }], success: true } },
|
||||
{ type: 'idle', session },
|
||||
]);
|
||||
})();
|
||||
break;
|
||||
}
|
||||
|
||||
case 'write-env': {
|
||||
// Fire tool_start + tool_ready with write permission for .env (should be blocked)
|
||||
(async () => {
|
||||
await timeout(10);
|
||||
this._onDidSessionProgress.fire({ type: 'tool_start', session, toolCallId: 'tc-write-env-1', toolName: 'write', displayName: 'Write File', invocationMessage: 'Write file' });
|
||||
await timeout(5);
|
||||
this._onDidSessionProgress.fire({ type: 'tool_ready', session, toolCallId: 'tc-write-env-1', invocationMessage: 'Write .env', permissionKind: 'write', permissionPath: '/workspace/.env', confirmationTitle: 'Write .env' });
|
||||
})();
|
||||
this._pendingPermissions.set('tc-write-env-1', (approved) => {
|
||||
if (approved) {
|
||||
this._fireSequence(session, [
|
||||
{ type: 'tool_complete', session, toolCallId: 'tc-write-env-1', result: { pastTenseMessage: 'Wrote .env', content: [{ type: ToolResultContentType.Text, text: 'ok' }], success: true } },
|
||||
{ type: 'idle', session },
|
||||
]);
|
||||
}
|
||||
});
|
||||
break;
|
||||
}
|
||||
|
||||
case 'with-usage':
|
||||
this._fireSequence(session, [
|
||||
{ type: 'delta', session, messageId: 'msg-1', content: 'Usage response.' },
|
||||
|
||||
@@ -920,4 +920,61 @@ suite('Protocol WebSocket E2E', function () {
|
||||
|
||||
raw.close();
|
||||
});
|
||||
|
||||
// ---- Edit auto-approve patterns -----------------------------------------
|
||||
|
||||
test('auto-approves write to regular file (no pending confirmation)', async function () {
|
||||
this.timeout(10_000);
|
||||
|
||||
const sessionUri = await createAndSubscribeSession(client, 'test-autoapprove');
|
||||
client.clearReceived();
|
||||
|
||||
// Start a turn that triggers a write permission request for a regular .ts file
|
||||
dispatchTurnStarted(client, sessionUri, 'turn-autoapprove', 'write-file', 1);
|
||||
|
||||
// The write should be auto-approved — we should see tool_start, tool_complete, and turn_complete
|
||||
// but NOT a pending-confirmation toolCallReady (one without `confirmed`).
|
||||
await client.waitForNotification(n => isActionNotification(n, 'session/toolCallStart'));
|
||||
await client.waitForNotification(n => isActionNotification(n, 'session/toolCallComplete'));
|
||||
await client.waitForNotification(n => isActionNotification(n, 'session/turnComplete'));
|
||||
|
||||
// Verify no pending-confirmation toolCallReady was received
|
||||
const pendingConfirmNotifs = client.receivedNotifications(n => {
|
||||
if (!isActionNotification(n, 'session/toolCallReady')) {
|
||||
return false;
|
||||
}
|
||||
const action = getActionEnvelope(n).action as { confirmed?: string };
|
||||
return !action.confirmed;
|
||||
});
|
||||
assert.strictEqual(pendingConfirmNotifs.length, 0, 'should not have received pending-confirmation toolCallReady for auto-approved write');
|
||||
});
|
||||
|
||||
test('blocks write to .env file (requires manual confirmation)', async function () {
|
||||
this.timeout(10_000);
|
||||
|
||||
const sessionUri = await createAndSubscribeSession(client, 'test-autoapprove-deny');
|
||||
client.clearReceived();
|
||||
|
||||
// Start a turn that tries to write .env (blocked by default patterns)
|
||||
dispatchTurnStarted(client, sessionUri, 'turn-deny', 'write-env', 1);
|
||||
|
||||
// The .env write should NOT be auto-approved — we should see toolCallReady (pending confirmation)
|
||||
await client.waitForNotification(n => isActionNotification(n, 'session/toolCallStart'));
|
||||
await client.waitForNotification(n => isActionNotification(n, 'session/toolCallReady'));
|
||||
|
||||
// Confirm it manually to let the turn complete
|
||||
client.notify('dispatchAction', {
|
||||
clientSeq: 2,
|
||||
action: {
|
||||
type: 'session/toolCallConfirmed',
|
||||
session: sessionUri,
|
||||
turnId: 'turn-deny',
|
||||
toolCallId: 'tc-write-env-1',
|
||||
approved: true,
|
||||
confirmed: 'user-action',
|
||||
},
|
||||
});
|
||||
|
||||
await client.waitForNotification(n => isActionNotification(n, 'session/turnComplete'));
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user