From 5478cfd9bf2be4668ae5bbeec01593226e954b1b Mon Sep 17 00:00:00 2001 From: Copilot <198982749+Copilot@users.noreply.github.com> Date: Thu, 29 Jan 2026 15:37:24 +0000 Subject: [PATCH 1/2] Remove plan availability note from allow-specific-actions-intro reusable (#59390) Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> Co-authored-by: angel-jiakou <115738347+angel-jiakou@users.noreply.github.com> --- data/reusables/actions/allow-specific-actions-intro.md | 7 ------- package-lock.json | 1 + 2 files changed, 1 insertion(+), 7 deletions(-) diff --git a/data/reusables/actions/allow-specific-actions-intro.md b/data/reusables/actions/allow-specific-actions-intro.md index 422954be0c6d..d3848577fa6a 100644 --- a/data/reusables/actions/allow-specific-actions-intro.md +++ b/data/reusables/actions/allow-specific-actions-intro.md @@ -26,11 +26,4 @@ When you choose {% data reusables.actions.policy-label-for-select-actions-workfl Use the `!` prefix to block patterns. For example, to allow all actions{% ifversion actions-workflow-policy %} and reusable workflows{% endif %} from the `space-org` organization, but block a specific action like `space-org/action`, you can specify `space-org/*, !space-org/action@*`. By default, only actions{% ifversion actions-workflow-policy %} and reusable workflows{% endif %} specified in the list will be allowed. To allow all actions{% ifversion actions-workflow-policy %} and reusable workflows{% endif %} while also blocking specific actions, you can specify `*, !space-org/action@*`. {% endif %} - {% ifversion fpt or ghec %} - - > [!NOTE] - > For {% data variables.product.prodname_free_user %}, {% data variables.product.prodname_pro %}, {% data variables.product.prodname_free_team %} for organizations, or {% data variables.product.prodname_team %} plans, the **Allow specified actions{% ifversion actions-workflow-policy %} and reusable workflows{% endif %}** option is only available in public repositories. - - {% endif %} - This procedure demonstrates how to add specific actions{% ifversion actions-workflow-policy %} and reusable workflows{% endif %} to the list. diff --git a/package-lock.json b/package-lock.json index adde603e9480..3dff5c246fb6 100644 --- a/package-lock.json +++ b/package-lock.json @@ -17159,6 +17159,7 @@ "name": "eslint-plugin-custom-rules", "version": "1.0.0", "dev": true, + "license": "MIT", "peerDependencies": { "eslint": "^8.0.0 || ^9.0.0" } From 2482284f8e182541c1fa9b7455a2f3ea70ab25ec Mon Sep 17 00:00:00 2001 From: William Martin Date: Thu, 29 Jan 2026 17:53:14 +0100 Subject: [PATCH 2/2] Rewrite copilot CLI ACP docs (#59400) Co-authored-by: hubwriter Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- content/copilot/reference/acp-server.md | 432 +++++------------------- 1 file changed, 80 insertions(+), 352 deletions(-) diff --git a/content/copilot/reference/acp-server.md b/content/copilot/reference/acp-server.md index 8b5657007fb0..f1089fb517b2 100644 --- a/content/copilot/reference/acp-server.md +++ b/content/copilot/reference/acp-server.md @@ -11,388 +11,116 @@ category: contentType: reference --- +> [!NOTE] +> ACP support in {% data variables.copilot.copilot_cli %} is in {% data variables.release-phases.public_preview %} and subject to change. ## Overview -Agent Client Protocol (ACP) is a standardized protocol that enables external clients, such as IDEs, editors, and other tools, to communicate with the {% data variables.product.prodname_copilot_short %} agent runtime. It provides a structured way to create and manage agent sessions, send prompts, receive streaming responses, and handle permission requests. +The Agent Client Protocol (ACP) is a protocol that standardizes communication between clients (such as code editors and IDEs) and coding agents (such as {% data variables.copilot.copilot_cli_short %}). For more details about this protocol, see the [official introduction](https://agentclientprotocol.com/get-started/introduction). -## Architecture - -```text -┌─────────────────┐ ┌─────────────────────┐ -│ IDE / Editor │ ACP │ Copilot Agent │ -│ (ACP Client) │◄───────►│ Runtime │ -│ │ stdio/ │ (ACP Server) │ -│ │ TCP │ │ -└─────────────────┘ └─────────────────────┘ -``` +## Starting the ACP server -The ACP server runs as part of {% data variables.copilot.copilot_cli %} and exposes the agent's capabilities through a well-defined protocol. +{% data variables.copilot.copilot_cli %} can be started as an ACP server using the `--acp` flag. The server supports two modes, `stdio` and `TCP`. -## Starting the ACP server +### stdio mode (recommended for IDE integration) -### Stdio Mode (Recommended for IDE Integration) +By default, when providing the `--acp` flag, `stdio` mode will be inferred. The `--stdio` flag can also be provided for disambiguation. ```bash copilot --acp --stdio ``` -Communication happens over stdin/stdout using newline-delimited JSON (NDJSON). +### TCP mode -### TCP Mode +If the `--port` flag is provided in combination with the `--acp` flag, the server is started in TCP mode. ```bash copilot --acp --port 3000 ``` -Communication happens over a TCP socket on the specified port. - -## Protocol flow - -### 1. Initialize connection - -```typescript -// Client sends initialize request -const response = await connection.initialize({ - clientInfo: { - name: "MyIDE", - version: "1.0" - } -}); - -// Server responds with capabilities -// { -// serverInfo: { name: "copilot-agent-runtime", version: "x.x.x" }, -// capabilities: { ... } -// } -``` - -### 2. Create a session - -```typescript -const session = await connection.newSession({ - cwd: "/path/to/project" -}); - -// Returns: { sessionId: "uuid-..." } -``` +## Integrating with the ACP server -### 3. Send prompts +There is a growing ecosystem of libraries to programmatically interact with ACP servers. Given {% data variables.copilot.copilot_cli %} is correctly installed and authenticated, the following example demonstrates using the [typescript](https://agentclientprotocol.com/libraries/typescript) client to send a single prompt and print the AI response. ```typescript -await connection.prompt({ - sessionId: session.sessionId, - prompt: [ - { type: "text", text: "Fix the authentication bug in login.ts" } - ] -}); -``` - -### 4. Receive session updates +import * as acp from "@agentclientprotocol/sdk"; +import { spawn } from "node:child_process"; +import { Readable, Writable } from "node:stream"; -The client receives real-time updates via the `sessionUpdate` callback: +async function main() { + const executable = process.env.COPILOT_CLI_PATH ?? "copilot"; -```typescript -const client: acp.Client = { - async sessionUpdate(params) { - // params.state: "idle" | "working" - // params.lastUpdateId: number - // params.updates: Array of content updates - - for (const update of params.updates) { - switch (update.type) { - case "text": - console.log("Agent says:", update.text); - break; - case "tool": - console.log("Tool execution:", update.name, update.status); - break; - } - } - } -}; -``` + // ACP uses standard input/output (stdin/stdout) for transport; we pipe these for the NDJSON stream. + const copilotProcess = spawn(executable, ["--acp", "--stdio"], { + stdio: ["pipe", "pipe", "inherit"], + }); -### 5. Handle permission requests + if (!copilotProcess.stdin || !copilotProcess.stdout) { + throw new Error("Failed to start Copilot ACP process with piped stdio."); + } -The agent requests permission for sensitive operations: + // Create ACP streams (NDJSON over stdio) + const output = Writable.toWeb(copilotProcess.stdin) as WritableStream; + const input = Readable.toWeb(copilotProcess.stdout) as ReadableStream; + const stream = acp.ndJsonStream(output, input); -```typescript -const client: acp.Client = { + const client: acp.Client = { async requestPermission(params) { - // params.request contains details about what permission is needed - // e.g., tool execution, file access, URL fetch - - // Display dialog to user and return their choice - return { - outcome: { - outcome: "selected", - optionId: "allow_once" // or "allow_always" or "reject_once" - } - }; - } -}; -``` - -## Content types - -### Text content - -```typescript -{ - type: "text", - text: "Your message here" -} -``` - -### Image content - -```typescript -{ - type: "image", - data: "", - mimeType: "image/png" // or "image/jpeg", "image/gif", "image/webp" -} -``` - -### Embedded resources - -```typescript -{ - type: "resource", - resource: { - uri: "file:///path/to/file.txt", - text: "file contents", - mimeType: "text/plain" - } -} -``` - -### Blob data - -```typescript -{ - type: "blob", - data: "", - mimeType: "application/octet-stream" -} -``` - -## Tool execution updates - -When the agent executes tools, clients receive detailed updates: - -```typescript -{ - type: "tool", - id: "tool-execution-id", - name: "edit", // Tool name - status: "running", // "running" | "completed" - kind: "edit", // Tool category - input: { ... }, // Tool parameters - output: { ... }, // Tool result (when completed) - diff: "..." // Diff content for edit operations -} -``` - -### Tool kinds - -| Kind | Description | -|------|-------------| -| `execute` | Command execution (bash) | -| `read` | File reading operations | -| `edit` | File modifications | -| `delete` | File deletions | -| `create` | File creation | -| `search` | Code search (grep, glob) | -| `fetch` | Web fetching | -| `agent` | Sub-agent invocation | -| `github` | GitHub API operations | -| `other` | Other tool types | - -## Permission types - -### Tool permissions - -Requested when the agent wants to execute a tool: - -```typescript -{ - type: "tool", - toolName: "bash", - input: { command: "npm install" } -} -``` - -### Path permissions - -Requested for file system access: - -```typescript -{ - type: "path", - path: "/path/to/sensitive/file", - operation: "write" // or "read" -} -``` - -### URL permissions - -Requested for network access: - -```typescript -{ - type: "url", - url: "https://api.example.com/data", - operation: "fetch" -} -``` - -## Session lifecycle - -```text -┌─────────────┐ -│ Created │ -└──────┬──────┘ - │ prompt() - ▼ -┌─────────────┐ -│ Working │◄───┐ -└──────┬──────┘ │ - │ │ prompt() - ▼ │ -┌─────────────┐ │ -│ Idle │────┘ -└──────┬──────┘ - │ destroy() - ▼ -┌─────────────┐ -│ Destroyed │ -└─────────────┘ -``` - -## Error handling + // This example should not trigger tool calls; if it does, refuse. + return { outcome: { outcome: "cancelled" } }; + }, -Errors are communicated through the protocol's standard error response format: - -```typescript -{ - error: { - code: -32600, - message: "Invalid request", - data: { ... } - } -} -``` - -## Example: Full Client Implementation - -```typescript -import * as acp from '@anthropic/acp-sdk'; -import { spawn } from 'child_process'; - -async function main() { - // Start the ACP server - const process = spawn('copilot', ['--acp', '--stdio'], { - stdio: ['pipe', 'pipe', 'inherit'] - }); - - // Create transport stream - const stream = acp.ndJsonStream(process.stdin, process.stdout); - - // Define client handlers - const client: acp.Client = { - async requestPermission(params) { - console.log('Permission requested:', params.request); - // Auto-approve for this example - return { - outcome: { outcome: "selected", optionId: "allow_once" } - }; - }, - - async sessionUpdate(params) { - console.log(`Session ${params.sessionId} state: ${params.state}`); - for (const update of params.updates) { - if (update.type === 'text') { - process.stdout.write(update.text); - } else if (update.type === 'tool') { - console.log(`Tool: ${update.name} - ${update.status}`); - } - } - } - }; - - // Establish connection - const connection = new acp.ClientSideConnection(client, stream); - - // Initialize - await connection.initialize({ - clientInfo: { name: "example-client", version: "1.0.0" } - }); - - // Create session - const session = await connection.newSession({ - cwd: process.cwd() - }); - - // Send prompt - await connection.prompt({ - sessionId: session.sessionId, - prompt: [ - { type: "text", text: "List the files in the current directory" } - ] - }); - - // Wait for completion (in real app, handle via sessionUpdate) - await new Promise(resolve => setTimeout(resolve, 10000)); - - // Cleanup - process.kill(); + async sessionUpdate(params) { + const update = params.update; + + if (update.sessionUpdate === "agent_message_chunk" && update.content.type === "text") { + process.stdout.write(update.content.text); + } + }, + }; + + const connection = new acp.ClientSideConnection((_agent) => client, stream); + + await connection.initialize({ + protocolVersion: acp.PROTOCOL_VERSION, + clientCapabilities: {}, + }); + + const sessionResult = await connection.newSession({ + cwd: process.cwd(), + mcpServers: [], + }); + + process.stdout.write("Session started!\n"); + const promptText = "Hello ACP Server!"; + process.stdout.write(`Sending prompt: '${promptText}'\n`); + + const promptResult = await connection.prompt({ + sessionId: sessionResult.sessionId, + prompt: [{ type: "text", text: promptText }], + }); + + process.stdout.write("\n"); + + if (promptResult.stopReason !== "end_turn") { + process.stderr.write(`Prompt finished with stopReason=${promptResult.stopReason}\n`); + } + + // Best-effort cleanup + copilotProcess.stdin.end(); + copilotProcess.kill("SIGTERM"); + await new Promise((resolve) => { + copilotProcess.once("exit", () => resolve()); + setTimeout(() => resolve(), 2000); + }); } -main().catch(console.error); +main().catch((error) => { + console.error(error); + process.exitCode = 1; +}); ``` -## Configuration - -### Runtime settings - -The ACP server inherits settings from the {% data variables.product.prodname_copilot_short %} runtime: - -* **Model selection**: Configured via environment or runtime settings -* **Tool permissions**: Managed through the permission service -* **Logging**: Configurable log level and directory - -### Environment variables - -| Variable | Description | -|----------|-------------| -| `COPILOT_LOG_LEVEL` | Log verbosity (debug, info, warn, error) | -| `COPILOT_LOG_DIR` | Directory for log files | - -## Best practices - -1. **Use Stdio Mode for IDEs**: More reliable than TCP for local integrations -1. **Handle Permissions Gracefully**: Always implement the `requestPermission` callback -1. **Process Updates Incrementally**: Don't wait for completion to show progress -1. **Clean Up Sessions**: Call destroy when done to free resources -1. **Handle Reconnection**: Implement retry logic for network transports - -## Source files - -| File | Description | -|------|-------------| -| `src/cli/acp/server.ts` | Main ACP server implementation | -| `src/cli/acp/mapping.ts` | Event transformation layer | -| `src/cli/acp/permissionHandlers.ts` | Permission request handlers | -| `src/cli/acp/permissionMapping.ts` | Permission mapping utilities | - -## Testing - -ACP has end-to-end tests that can be run with: - -```bash -CI=true npm run test:cli-e2e -- test/cli/e2e/acp.test.ts -``` +## Further reading -Test captures are available in `test/cli/e2e/captures/acp/` showing various content type handling scenarios. +* [Official ACP documentation](https://agentclientprotocol.com/protocol/overview)