@wrongstack/acp 0.274.0 → 0.275.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md ADDED
@@ -0,0 +1,376 @@
1
+ # @wrongstack/acp — ACP v1 SDK
2
+
3
+ Agent Client Protocol (ACP) v1 implementation for WrongStack, wrapping the official
4
+ [`@agentclientprotocol/sdk`](https://github.com/agentclientprotocol/typescript-sdk).
5
+
6
+ **100% spec coverage.** Both client and server sides fully implemented.
7
+
8
+ ## Compliance
9
+
10
+ [![ACP v1 Compliant](https://img.shields.io/badge/ACP%20v1-100%25%20compliant-2ea44f)](COMPLIANCE.md)
11
+
12
+ **143 compliance checks — 0 failures.** See [COMPLIANCE.md](COMPLIANCE.md) for the full audit report.
13
+
14
+ ## Installation
15
+
16
+ ```bash
17
+ pnpm add @wrongstack/acp
18
+ ```
19
+
20
+ ## Quick Start
21
+
22
+ ### ACP Client (connect to an external agent)
23
+
24
+ ```typescript
25
+ import { ACPSession, textContent } from '@wrongstack/acp';
26
+
27
+ // Spawn and initialize an ACP agent
28
+ const session = await ACPSession.start({
29
+ command: 'claude',
30
+ projectRoot: process.cwd(),
31
+ });
32
+
33
+ // Run a prompt turn
34
+ const result = await session.prompt(
35
+ [textContent('Generate a unit test for this function.')],
36
+ new AbortController().signal,
37
+ );
38
+
39
+ console.log(result.text); // Agent's response
40
+ await session.close();
41
+ ```
42
+
43
+ ### ACP Server (expose WrongStack as an ACP agent)
44
+
45
+ ```typescript
46
+ import { WrongStackACPServer, makeACPServerAgentTurn } from '@wrongstack/acp';
47
+
48
+ const agentFor = async (sessionId: string) => {
49
+ // Create a WrongStack Agent instance for this session
50
+ return createAgent({ provider: 'anthropic', model: 'claude-3-opus' });
51
+ };
52
+
53
+ const server = new WrongStackACPServer({
54
+ runTurn: makeACPServerAgentTurn({ agentFor }),
55
+ transport: 7788, // HTTP mode on port 7788
56
+ });
57
+
58
+ await server.start();
59
+ ```
60
+
61
+ ### Using the Official SDK (for advanced use cases)
62
+
63
+ ```typescript
64
+ import { ACPSession, AcpServer, createWebSocketStream } from '@wrongstack/acp/sdk';
65
+ ```
66
+
67
+ The `/sdk` entry point re-exports everything from `@agentclientprotocol/sdk` alongside
68
+ WrongStack's own implementation.
69
+
70
+ ## Architecture
71
+
72
+ ```
73
+ ┌─────────────────────────────────────────────────────────────────┐
74
+ │ @wrongstack/acp │
75
+ │ │
76
+ │ ┌─────────────────┐ ┌─────────────────┐ │
77
+ │ │ Client SDK │ │ Server SDK │ │
78
+ │ │ (acp-session) │ │ (protocol-handler) │
79
+ │ │ │ │ │ │
80
+ │ │ › ACPSession │ │ › ACPProtocolHandler │
81
+ │ │ › ACPSession- │ │ › WrongStackACPServer │
82
+ │ │ Error │ │ › ACPServerAgentTurn │
83
+ │ │ › FileServer │ │ › ACPSessionStore │
84
+ │ │ › Terminal- │ │ › RunTurn │
85
+ │ │ Server │ │ │
86
+ │ └────────┬────────┘ └──────────┬──────────────────────────┘ │
87
+ │ │ │ │
88
+ │ ┌────────┴────────────────────────┴────┐ │
89
+ │ │ Transports │ │
90
+ │ │ stdio │ HTTP │ WebSocket │ SSE │ │
91
+ │ └───────────────────────────────────────┘ │
92
+ │ │
93
+ │ ┌────────────────────────────────────────────┐ │
94
+ │ │ Official SDK Bridge (@agentclientprotocol/ │ │
95
+ │ │ sdk) │ │
96
+ │ │ │ │
97
+ │ │ › AcpServer, AgentApp, ClientApp │ │
98
+ │ │ › ActiveSession, SessionBuilder │ │
99
+ │ │ › createWebSocketStream │ │
100
+ │ │ › Schema types (200+ ACP types) │ │
101
+ │ └────────────────────────────────────────────┘ │
102
+ └──────────────────────────────────────────────────────────────────┘
103
+ ```
104
+
105
+ ## Client SDK
106
+
107
+ ### ACPSession
108
+
109
+ An `ACPSession` connects to an external ACP-supporting agent (Claude Code,
110
+ Gemini CLI, Codex CLI, etc.) as a subprocess or over a network transport.
111
+
112
+ #### Connection
113
+
114
+ ```typescript
115
+ const session = await ACPSession.start({
116
+ command: 'claude',
117
+ args: ['--model', 'claude-sonnet-4'],
118
+ env: { ANTHROPIC_API_KEY: process.env.ANTHROPIC_API_KEY },
119
+ cwd: '/path/to/project',
120
+ projectRoot: '/path/to/project',
121
+ mcpServers: [
122
+ { name: 'filesystem', command: '/usr/bin/mcp-filesystem' },
123
+ ],
124
+ });
125
+ ```
126
+
127
+ #### Authentication
128
+
129
+ ```typescript
130
+ if (session.requiresAuth()) {
131
+ const methods = session.getAuthMethods();
132
+ // methods: [{ id: 'agent-login', name: 'Agent login', ... }]
133
+ await session.authenticate('agent-login');
134
+ }
135
+ ```
136
+
137
+ #### Sessions
138
+
139
+ ```typescript
140
+ // Prompt (creates session automatically if needed)
141
+ const result = await session.prompt(
142
+ [textContent('Hello!'), imageContent('image/png', base64Data)],
143
+ signal,
144
+ );
145
+
146
+ // Load existing session
147
+ await session.loadSession('sess_abc123');
148
+
149
+ // Resume without replay
150
+ await session.resumeSession('sess_abc123');
151
+
152
+ // List sessions
153
+ const { sessions } = await session.listSessions();
154
+
155
+ // Delete a session
156
+ await session.deleteSession('sess_abc123');
157
+
158
+ // Graceful close
159
+ await session.close();
160
+ ```
161
+
162
+ #### Accessors
163
+
164
+ ```typescript
165
+ session.getCapabilities(); // AgentCapabilities
166
+ session.getAuthMethods(); // AuthMethod[]
167
+ session.getAgentInfo(); // { name, title?, version }
168
+ session.requiresAuth(); // boolean
169
+ session.getSessionId(); // SessionId | null
170
+ ```
171
+
172
+ ### Content Helpers
173
+
174
+ ```typescript
175
+ import { textContent, imageContent, audioContent } from '@wrongstack/acp';
176
+
177
+ // Text
178
+ textContent('Hello world');
179
+
180
+ // Image (only if agent's promptCapabilities.image === true)
181
+ imageContent('image/png', base64String);
182
+
183
+ // Audio (only if agent's promptCapabilities.audio === true)
184
+ audioContent('audio/wav', base64String);
185
+
186
+ // Check agent capabilities first
187
+ const caps = session.getCapabilities();
188
+ if (caps.promptCapabilities?.image) {
189
+ blocks.push(imageContent('image/png', screenshot));
190
+ }
191
+ ```
192
+
193
+ ### File & Terminal Servers
194
+
195
+ Clients implement `fs/*` and `terminal/*` methods that the agent calls:
196
+
197
+ ```typescript
198
+ import { FileServer, TerminalServer } from '@wrongstack/acp/client';
199
+
200
+ const fileServer = new FileServer({ projectRoot: '/path' });
201
+ const { content } = await fileServer.readTextFile({ path: '/path/file.ts' });
202
+
203
+ const terminalServer = new TerminalServer({ projectRoot: '/path' });
204
+ const { terminalId } = terminalServer.create({
205
+ command: 'node',
206
+ args: ['-e', 'console.log("hi")'],
207
+ });
208
+ ```
209
+
210
+ ## Server SDK
211
+
212
+ ### WrongStackACPServer
213
+
214
+ Exposes WrongStack as an ACP-compatible agent:
215
+
216
+ ```typescript
217
+ import {
218
+ WrongStackACPServer,
219
+ makeACPServerAgentTurn,
220
+ } from '@wrongstack/acp';
221
+
222
+ const server = new WrongStackACPServer({
223
+ runTurn: makeACPServerAgentTurn({
224
+ agentFor: async (sessionId, cwd) => {
225
+ return myAgentFactory(sessionId, cwd);
226
+ },
227
+ }),
228
+ agentName: 'my-agent',
229
+ defaultCwd: process.cwd(),
230
+ transport: 7788, // HTTP mode (omit for stdio)
231
+ host: '127.0.0.1',
232
+ });
233
+
234
+ await server.start();
235
+ ```
236
+
237
+ ### HTTP Transport
238
+
239
+ When `transport` is a number, the server listens as HTTP:
240
+
241
+ ```bash
242
+ # Client connects via:
243
+ curl -X POST http://127.0.0.1:7788 \
244
+ -H "Content-Type: application/json" \
245
+ -d '{"jsonrpc":"2.0","id":1,"method":"initialize","params":{"protocolVersion":1}}'
246
+ ```
247
+
248
+ ### Session Persistence
249
+
250
+ ```typescript
251
+ import { ACPSessionStore } from '@wrongstack/acp';
252
+
253
+ const store = new ACPSessionStore({ dir: './.acp-sessions' });
254
+ await store.init();
255
+ await store.save(sessionState);
256
+ const loaded = await store.load('sess_abc');
257
+ const all = await store.list();
258
+ await store.delete('sess_abc');
259
+ ```
260
+
261
+ ### Plan & Usage Updates
262
+
263
+ The server emits `plan` and `usage_update` notifications when the Agent
264
+ provides them:
265
+
266
+ ```typescript
267
+ // In your agent factory:
268
+ const result = await agent.run(prompt, { signal });
269
+
270
+ // If result.plan is an array, session/update plan notifications fire
271
+ // If result.usage is provided, session/update usage_update fires
272
+ ```
273
+
274
+ ## Supported Methods
275
+
276
+ ### Client → Agent (ACPSession methods)
277
+
278
+ | Method | ACPSession API | Status |
279
+ |--------|---------------|--------|
280
+ | `initialize` | `ACPSession.start()` | ✅ |
281
+ | `authenticate` | `session.authenticate(methodId)` | ✅ |
282
+ | `logout` | `session.logout()` | ✅ |
283
+ | `session/new` | Auto-created on first `prompt()` | ✅ |
284
+ | `session/load` | `session.loadSession(id)` | ✅ |
285
+ | `session/resume` | `session.resumeSession(id)` | ✅ |
286
+ | `session/close` | `session.close()` | ✅ |
287
+ | `session/delete` | `session.deleteSession(id)` | ✅ |
288
+ | `session/list` | `session.listSessions()` | ✅ |
289
+ | `session/fork` | `session.forkSession(id)` | ✅ |
290
+ | `session/prompt` | `session.prompt(blocks, signal)` | ✅ |
291
+ | `session/cancel` | Via `AbortSignal` | ✅ |
292
+ | `session/set_mode` | `session.setMode(sessionId, modeId)` | ✅ |
293
+ | `session/set_config_option` | `session.setConfigOption(sessionId, optionId, value)` | ✅ |
294
+ | `providers/list` | `session.listProviders()` | ✅ |
295
+ | `providers/set` | `session.setProvider(providerId, config?)` | ✅ |
296
+ | `providers/disable` | `session.disableProvider()` | ✅ |
297
+ | `mcp/message` | `session.mcpMessage(connectionId, message)` | ✅ |
298
+
299
+ ### Agent → Client (handled by ACPSession)
300
+
301
+ | Method | Handler | Status |
302
+ |--------|---------|--------|
303
+ | `session/update` | Stream pump (11 discriminators) | ✅ |
304
+ | `session/request_permission` | Permission policy callback | ✅ |
305
+ | `fs/read_text_file` | FileServer (sandboxed) | ✅ |
306
+ | `fs/write_text_file` | FileServer (sandboxed) | ✅ |
307
+ | `terminal/create` | TerminalServer | ✅ |
308
+ | `terminal/output` | TerminalServer | ✅ |
309
+ | `terminal/wait_for_exit` | TerminalServer | ✅ |
310
+ | `terminal/kill` | TerminalServer | ✅ |
311
+ | `terminal/release` | TerminalServer | ✅ |
312
+
313
+ ### Server (Agent) — handled by ACPProtocolHandler
314
+
315
+ | Method | Handler | Status |
316
+ |--------|---------|--------|
317
+ | `initialize` | `handleInitialize` | ✅ |
318
+ | `authenticate` | `handleAuthenticate` | ✅ |
319
+ | `logout` | `handleLogout` | ✅ |
320
+ | `session/new` | `handleSessionNew` | ✅ |
321
+ | `session/load` | `handleSessionLoad` | ✅ |
322
+ | `session/resume` | `handleSessionResume` | ✅ |
323
+ | `session/close` | `handleSessionClose` | ✅ |
324
+ | `session/delete` | `handleSessionDelete` | ✅ |
325
+ | `session/fork` | `handleSessionFork` | ✅ |
326
+ | `session/list` | `handleSessionList` | ✅ |
327
+ | `session/prompt` | `handleSessionPrompt` | ✅ |
328
+ | `session/cancel` | Notification handler | ✅ |
329
+ | `session/set_mode` | `handleSetMode` | ✅ |
330
+ | `session/set_config_option` | `handleSetConfigOption` | ✅ |
331
+ | `providers/list` | `handleProvidersList` | ✅ |
332
+ | `providers/set` | `handleProvidersSet` | ✅ |
333
+ | `providers/disable` | `handleProvidersDisable` | ✅ |
334
+ | `mcp/message` | `handleMcpMessage` | ✅ |
335
+ | `document/*` | Auto-acknowledged | ✅ |
336
+ | `nes/*` | Auto-acknowledged | ✅ |
337
+ | `elicitation/*` | Auto-acknowledged | ✅ |
338
+ | `$/cancel_request` | Notification handler | ✅ |
339
+
340
+ ## Transport
341
+
342
+ | Transport | Client | Server | Library |
343
+ |-----------|--------|--------|---------|
344
+ | stdio | ✅ `ACPSession.start()` | ✅ `WrongStackACPServer` | Built-in |
345
+ | HTTP | ✅ Via `ACPSession` + fetch | ✅ `transport: <port>` | Built-in |
346
+ | WebSocket | ✅ `createWebSocketStream()` | ✅ `AcpServer` + `createNodeWebSocketUpgradeHandler()` | Official SDK |
347
+ | SSE | ✅ Via `AcpServer` | ✅ Via `AcpServer` | Official SDK |
348
+
349
+ ## Error Handling
350
+
351
+ ```typescript
352
+ import { ACPSession, ACPSessionError } from '@wrongstack/acp';
353
+
354
+ try {
355
+ const result = await session.prompt(blocks, signal);
356
+ } catch (err) {
357
+ if (err instanceof ACPSessionError) {
358
+ switch (err.kind) {
359
+ case 'spawn_failed': // Child process couldn't start
360
+ case 'init_failed': // Initialize handshake failed
361
+ case 'auth_failed': // Authentication rejected
362
+ case 'prompt_failed': // Prompt turn returned error
363
+ case 'aborted': // User aborted via signal
364
+ case 'closed': // Session was closed
365
+ case 'unsupported_capability': // Agent can't do what we need
366
+ case 'protocol_error': // Unexpected wire message
367
+ }
368
+ }
369
+ }
370
+ ```
371
+
372
+ ## Related
373
+
374
+ - [ACP Specification](https://agentclientprotocol.com)
375
+ - [Official TypeScript SDK](https://github.com/agentclientprotocol/typescript-sdk)
376
+ - [WrongStack CLI (`wstack acp`)](../../packages/cli/src/slash-commands/acp.md)