Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,16 @@ All notable changes to the "vscode-acp" extension will be documented in this fil

Check [Keep a Changelog](http://keepachangelog.com/) for recommendations on how to structure this file.

## [0.1.4]

### Fixed
- Queued concurrent permission requests so multiple prompts no longer clobber each other’s QuickPick dialogs, which was silently cancelling approvals and causing repeated trust prompts

## [0.1.3] - 2026-03-01

### Added
- **OpenClaw**: Added OpenClaw as a pre-configured agent (`npx openclaw acp`)

## [0.1.2] - 2026-02-12

### Added
Expand Down
9 changes: 5 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ A [Visual Studio Code extension](https://marketplace.visualstudio.com/items?item

## Features

- **Multi-Agent Support**: Connect to 8 pre-configured ACP agents or add your own
- **Multi-Agent Support**: Connect to 9 pre-configured ACP agents or add your own
- **Single-Agent Focus**: One agent active at a time — seamlessly switch between agents
- **Interactive Chat**: Built-in chat panel with Markdown rendering, inline tool call display, and collapsible tool sections
- **Thinking Display**: See agent reasoning in a collapsible block with streaming animation and elapsed time
Expand Down Expand Up @@ -46,14 +46,15 @@ The extension comes with default configurations for:
| Qoder CLI | `npx @qoder-ai/qodercli@latest --acp` |
| Codex CLI | `npx @zed-industries/codex-acp@latest` |
| OpenCode | `npx opencode-ai@latest acp` |
| OpenClaw | `npx openclaw acp` |

You can add custom agent configurations in settings.

## Extension Settings

| Setting | Default | Description |
|---------|---------|-------------|
| `acp.agents` | *(8 agents)* | Agent configurations. Each key is the agent name, value has `command`, `args`, and `env`. |
| `acp.agents` | *(9 agents)* | Agent configurations. Each key is the agent name, value has `command`, `args`, and `env`. |
| `acp.autoApprovePermissions` | `ask` | How agent permission requests are handled: `ask` or `allowAll`. |
| `acp.defaultWorkingDirectory` | `""` | Default working directory for agent sessions. Empty uses current workspace. |
| `acp.logTraffic` | `true` | Log all ACP protocol traffic to the ACP Traffic output channel. |
Expand Down Expand Up @@ -96,7 +97,7 @@ All commands are accessible via the Command Palette (`Ctrl+Shift+P`):
### Setup

```bash
git clone https://github.com/your-username/vscode-acp.git
git clone https://github.com/formulahendry/vscode-acp.git
cd vscode-acp
npm install
```
Expand Down Expand Up @@ -146,7 +147,7 @@ Communication with agents uses the ACP protocol (JSON-RPC 2.0 over stdio).

- [ACP Client on Visual Studio Code Marketplace](https://marketplace.visualstudio.com/items?itemName=formulahendry.acp-client)
- [Agent Client Protocol](https://agentclientprotocol.com/)
- [GitHub Repository](https://github.com/nicepkg/vscode-acp)
- [GitHub Repository](https://github.com/formulahendry/vscode-acp)

## License

Expand Down
16 changes: 3 additions & 13 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

12 changes: 10 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
{
"name": "acp-client",
"displayName": "ACP Client",
"description": "Agent Client Protocol client for VS Code — connect to GitHub Copilot, Claude Code, Gemini CLI, Qwen Code, Codex CLI, OpenCode, and any ACP-compatible AI coding agent",
"version": "0.1.2",
"description": "Agent Client Protocol client for VS Code — connect to GitHub Copilot, Claude Code, Gemini CLI, Qwen Code, Codex CLI, OpenCode, OpenClaw, and any ACP-compatible AI coding agent",
"version": "0.1.4",
"publisher": "formulahendry",
"license": "MIT",
"icon": "resources/icon.png",
Expand Down Expand Up @@ -292,6 +292,14 @@
"acp"
],
"env": {}
},
"OpenClaw": {
"command": "npx",
"args": [
"openclaw",
"acp"
],
"env": {}
}
}
},
Expand Down
3 changes: 2 additions & 1 deletion src/core/AgentManager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -97,7 +97,8 @@ export class AgentManager extends EventEmitter {
const shellArgs = useLoginFlag ? ['-l', '-c', commandStr] : ['-c', commandStr];

log(`Using shell: ${shell} ${shellArgs.join(' ')}`);
sendEvent('agent/spawn/shell', { shell, useLoginFlag: String(useLoginFlag) });
const shellName = shell.split('/').pop() || shell;
sendEvent('agent/spawn/shell', { shell: shellName, useLoginFlag: String(useLoginFlag) });
return spawn(shell, shellArgs, {
stdio: ['pipe', 'pipe', 'pipe'],
env: { ...process.env, ...(config.env || {}) },
Expand Down
8 changes: 2 additions & 6 deletions src/extension.ts
Original file line number Diff line number Diff line change
Expand Up @@ -222,18 +222,15 @@ export function activate(context: vscode.ExtensionContext): void {
}
});

// Show Logs
const showLogsCmd = vscode.commands.registerCommand('acp.showLogs', () => {
getOutputChannel().show();
});

// Show Log
const showLogCmd = vscode.commands.registerCommand('acp.showLog', () => {
sendEvent('command/showLog');
getOutputChannel().show();
});

// Show Traffic
const showTrafficCmd = vscode.commands.registerCommand('acp.showTraffic', () => {
sendEvent('command/showTraffic');
getTrafficChannel().show();
});

Expand Down Expand Up @@ -394,7 +391,6 @@ export function activate(context: vscode.ExtensionContext): void {
sendPromptCmd,
cancelTurnCmd,
restartAgentCmd,
showLogsCmd,
showLogCmd,
showTrafficCmd,
setModeCmd,
Expand Down
161 changes: 87 additions & 74 deletions src/handlers/PermissionHandler.ts
Original file line number Diff line number Diff line change
@@ -1,74 +1,87 @@
import * as vscode from 'vscode';
import { log } from '../utils/Logger';
import { sendEvent } from '../utils/TelemetryManager';

import type { RequestPermissionRequest, RequestPermissionResponse } from '@agentclientprotocol/sdk';

/**
* Handles ACP permission requests from agents.
* Shows VS Code QuickPick for user to select from agent-provided options.
*/
export class PermissionHandler {
async requestPermission(params: RequestPermissionRequest): Promise<RequestPermissionResponse> {
const config = vscode.workspace.getConfiguration('acp');
const autoApprove = config.get<string>('autoApprovePermissions', 'none');

const title = params.toolCall?.title || 'Permission Request';
log(`requestPermission: ${title} (autoApprove=${autoApprove})`);

// Auto-approve: pick first allow-type option
if (autoApprove === 'allowAll') {
const allowOption = params.options.find(o =>
o.kind === 'allow_once' || o.kind === 'allow_always'
);
if (allowOption) {
sendEvent('permission/requested', { permissionType: title, autoApproved: 'true' });
return {
outcome: {
outcome: 'selected',
optionId: allowOption.optionId,
},
};
}
}

// Build QuickPick items from agent-provided options
const items: (vscode.QuickPickItem & { optionId: string })[] = params.options.map(option => {
const icon = option.kind.startsWith('allow') ? '$(check)' : '$(x)';
return {
label: `${icon} ${option.name}`,
description: option.kind,
optionId: option.optionId,
};
});

sendEvent('permission/requested', { permissionType: title, autoApproved: 'false' });

const selection = await vscode.window.showQuickPick(items, {
placeHolder: title,
title: 'ACP Agent Permission Request',
ignoreFocusOut: true,
});

if (!selection) {
log('Permission cancelled by user');
sendEvent('permission/responded', { permissionType: title, outcome: 'cancelled' });
return {
outcome: { outcome: 'cancelled' },
};
}

log(`Permission selected: ${selection.optionId}`);
sendEvent('permission/responded', {
permissionType: title,
action: selection.optionId,
outcome: 'selected',
});
return {
outcome: {
outcome: 'selected',
optionId: selection.optionId,
},
};
}
}
import * as vscode from 'vscode';
import { log } from '../utils/Logger';
import { sendEvent } from '../utils/TelemetryManager';

import type { RequestPermissionRequest, RequestPermissionResponse } from '@agentclientprotocol/sdk';

/**
* Handles ACP permission requests from agents.
* Shows VS Code QuickPick for user to select from agent-provided options.
* Requests are queued so concurrent permission prompts don't clobber each other.
*/
export class PermissionHandler {
private queue: Promise<void> = Promise.resolve();

async requestPermission(params: RequestPermissionRequest): Promise<RequestPermissionResponse> {
const result = this.queue.then(() => this.handlePermission(params));
this.queue = result.then(() => {}, () => {});
return result.catch((err) => {
log(`Permission request failed: ${err}`);
sendEvent('permission/responded', { permissionType: params.toolCall?.title || 'Permission Request', outcome: 'error' });
return { outcome: { outcome: 'cancelled' as const } };
});
}

private async handlePermission(params: RequestPermissionRequest): Promise<RequestPermissionResponse> {
const config = vscode.workspace.getConfiguration('acp');
const autoApprove = config.get<string>('autoApprovePermissions', 'none');

const title = params.toolCall?.title || 'Permission Request';
log(`requestPermission: ${title} (autoApprove=${autoApprove})`);

// Auto-approve: pick first allow-type option
if (autoApprove === 'allowAll') {
const allowOption = params.options.find(o =>
o.kind === 'allow_once' || o.kind === 'allow_always'
);
if (allowOption) {
sendEvent('permission/requested', { permissionType: title, autoApproved: 'true' });
return {
outcome: {
outcome: 'selected',
optionId: allowOption.optionId,
},
};
}
}

// Build QuickPick items from agent-provided options
const items: (vscode.QuickPickItem & { optionId: string })[] = params.options.map(option => {
const icon = option.kind.startsWith('allow') ? '$(check)' : '$(x)';
return {
label: `${icon} ${option.name}`,
description: option.kind,
optionId: option.optionId,
};
});

sendEvent('permission/requested', { permissionType: title, autoApproved: 'false' });

const selection = await vscode.window.showQuickPick(items, {
placeHolder: title,
title: 'ACP Agent Permission Request',
ignoreFocusOut: true,
});

if (!selection) {
log('Permission cancelled by user');
sendEvent('permission/responded', { permissionType: title, outcome: 'cancelled' });
return {
outcome: { outcome: 'cancelled' },
};
}

log(`Permission selected: ${selection.optionId}`);
sendEvent('permission/responded', {
permissionType: title,
action: selection.optionId,
outcome: 'selected',
});
return {
outcome: {
outcome: 'selected',
optionId: selection.optionId,
},
};
}
}