@wrongstack/acp 0.286.0 → 0.289.0

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 CHANGED
@@ -1,15 +1,19 @@
1
1
  # @wrongstack/acp — ACP v1 SDK
2
2
 
3
- Agent Client Protocol (ACP) v1 implementation for WrongStack, wrapping the official
4
- [`@agentclientprotocol/sdk`](https://github.com/agentclientprotocol/typescript-sdk).
3
+ Agent Client Protocol (ACP) v1 implementation for WrongStack. The live client and
4
+ server use WrongStack's JSON-RPC implementation; the `/sdk` export provides the official
5
+ [`@agentclientprotocol/sdk`](https://github.com/agentclientprotocol/typescript-sdk) APIs.
5
6
 
6
- **100% spec coverage.** Both client and server sides fully implemented.
7
+ WrongStack implements the ACP v1 client and server session flows, with stdio and
8
+ WebSocket support plus an HTTP server transport. Some optional protocol surfaces
9
+ are intentionally unavailable; see [COMPLIANCE.md](COMPLIANCE.md) for the tested
10
+ surface and current limitations.
7
11
 
8
12
  ## Compliance
9
13
 
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.
14
+ The package test suite includes client/server protocol tests and an in-process
15
+ JSON round-trip loopback test. See [COMPLIANCE.md](COMPLIANCE.md) for the method
16
+ matrix and the distinction between wire handling and functional behavior.
13
17
 
14
18
  ## Installation
15
19
 
@@ -43,7 +47,7 @@ await session.close();
43
47
  ### ACP Server (expose WrongStack as an ACP agent)
44
48
 
45
49
  ```typescript
46
- import { WrongStackACPServer, makeACPServerAgentTurn } from '@wrongstack/acp';
50
+ import { WrongStackACPServer, makeACPServerAgentTurn } from '@wrongstack/acp/agent';
47
51
 
48
52
  const agentFor = async (sessionId: string) => {
49
53
  // Create a WrongStack Agent instance for this session
@@ -64,8 +68,8 @@ await server.start();
64
68
  import { ACPSession, AcpServer, createWebSocketStream } from '@wrongstack/acp/sdk';
65
69
  ```
66
70
 
67
- The `/sdk` entry point re-exports everything from `@agentclientprotocol/sdk` alongside
68
- WrongStack's own implementation.
71
+ The `/sdk` entry point re-exports the official SDK's public high-level APIs, method
72
+ constants, and experimental Node/WebSocket helpers alongside WrongStack's implementation.
69
73
 
70
74
  ## Architecture
71
75
 
@@ -217,7 +221,7 @@ Exposes WrongStack as an ACP-compatible agent:
217
221
  import {
218
222
  WrongStackACPServer,
219
223
  makeACPServerAgentTurn,
220
- } from '@wrongstack/acp';
224
+ } from '@wrongstack/acp/agent';
221
225
 
222
226
  const server = new WrongStackACPServer({
223
227
  runTurn: makeACPServerAgentTurn({
@@ -248,7 +252,7 @@ curl -X POST http://127.0.0.1:7788 \
248
252
  ### Session Persistence
249
253
 
250
254
  ```typescript
251
- import { ACPSessionStore } from '@wrongstack/acp';
255
+ import { ACPSessionStore } from '@wrongstack/acp/agent';
252
256
 
253
257
  const store = new ACPSessionStore({ dir: './.acp-sessions' });
254
258
  await store.init();
@@ -300,7 +304,7 @@ const result = await agent.run(prompt, { signal });
300
304
 
301
305
  | Method | Handler | Status |
302
306
  |--------|---------|--------|
303
- | `session/update` | Stream pump (11 discriminators) | ✅ |
307
+ | `session/update` | Stream pump (11 stable discriminators; unknown/unstable updates are tolerated) | ✅ |
304
308
  | `session/request_permission` | Permission policy callback | ✅ |
305
309
  | `fs/read_text_file` | FileServer (sandboxed) | ✅ |
306
310
  | `fs/write_text_file` | FileServer (sandboxed) | ✅ |
@@ -328,13 +332,13 @@ const result = await agent.run(prompt, { signal });
328
332
  | `session/cancel` | Notification handler | ✅ |
329
333
  | `session/set_mode` | `handleSetMode` | ✅ |
330
334
  | `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 | |
335
+ | `providers/list` | Returns an empty provider list; provider configuration stays in `wstack auth` | Limited |
336
+ | `providers/set` | Returns an error directing callers to `wstack auth` | Unsupported |
337
+ | `providers/disable` | Acknowledges without changing CLI provider configuration | Limited |
338
+ | `mcp/message` | Returns an unavailable error | Unsupported |
339
+ | `document/*` | Standard method-not-found response | Unsupported |
340
+ | `nes/*` | Standard method-not-found response | Unsupported |
341
+ | `elicitation/*` | Standard method-not-found response | Unsupported |
338
342
  | `$/cancel_request` | Notification handler | ✅ |
339
343
 
340
344
  ## Transport
@@ -342,9 +346,9 @@ const result = await agent.run(prompt, { signal });
342
346
  | Transport | Client | Server | Library |
343
347
  |-----------|--------|--------|---------|
344
348
  | 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 |
349
+ | HTTP | No `ACPSession` HTTP client | ✅ `transport: <port>` | Built-in server |
350
+ | WebSocket | ✅ `ACPSession.connectWebSocket()` | ✅ via the CLI `--ws` bridge | Built-in |
351
+ | SSE | SDK exports only; no WrongStack command wires it | SDK exports only; no WrongStack command wires it | Official SDK re-export |
348
352
 
349
353
  ## Error Handling
350
354
 
@@ -373,4 +377,4 @@ try {
373
377
 
374
378
  - [ACP Specification](https://agentclientprotocol.com)
375
379
  - [Official TypeScript SDK](https://github.com/agentclientprotocol/typescript-sdk)
376
- - [WrongStack CLI (`wstack acp`)](../../packages/cli/src/slash-commands/acp.md)
380
+ - [WrongStack CLI (`wstack acp`)](../../docs/subcommands/acp.md)
@@ -11,6 +11,13 @@ export interface WrongStackACPServerOptions {
11
11
  transport?: 'stdio' | number | undefined;
12
12
  /** Host for HTTP transport. Defaults to '127.0.0.1'. */
13
13
  host?: string | undefined;
14
+ /**
15
+ * Bearer token required for HTTP transport authentication. When set,
16
+ * every HTTP request must include `Authorization: Bearer <token>` or
17
+ * `?token=<token>` in the query string. When unset, the server is
18
+ * unauthenticated (acceptable for loopback-only development).
19
+ */
20
+ authToken?: string | undefined;
14
21
  /** Emit the pre-v1 startup marker on stdio. Defaults to false. */
15
22
  legacyStartupMarker?: boolean | undefined;
16
23
  /**
@@ -1 +1 @@
1
- {"version":3,"file":"wrongstack-acp-agent.d.ts","sourceRoot":"","sources":["../../src/agent/wrongstack-acp-agent.ts"],"names":[],"mappings":"AA8BA,OAAO,EAEL,KAAK,OAAO,EAEZ,KAAK,kBAAkB,EACxB,MAAM,uBAAuB,CAAC;AAG/B,MAAM,WAAW,0BAA0B;IACzC,OAAO,CAAC,EAAE,OAAO,GAAG,SAAS,CAAC;IAC9B,UAAU,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IAChC,SAAS,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IAC/B;;;;OAIG;IACH,SAAS,CAAC,EAAE,OAAO,GAAG,MAAM,GAAG,SAAS,CAAC;IACzC,wDAAwD;IACxD,IAAI,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IAC1B,kEAAkE;IAClE,mBAAmB,CAAC,EAAE,OAAO,GAAG,SAAS,CAAC;IAC1C;;;;OAIG;IACH,SAAS,CAAC,EAAE,CAAC,CAAC,SAAS,EAAE,MAAM,KAAK,KAAK,CAAC;QAAE,aAAa,EAAE,MAAM,CAAC;QAAC,OAAO,EAAE,OAAO,CAAA;KAAE,CAAC,CAAC,GAAG,SAAS,CAAC;IACpG;;;OAGG;IACH,OAAO,CAAC,EAAE,CAAC,CAAC,SAAS,EAAE,MAAM,EAAE,OAAO,EAAE,KAAK,CAAC;QAAE,aAAa,EAAE,MAAM,CAAC;QAAC,OAAO,EAAE,OAAO,CAAA;KAAE,CAAC,KAAK,IAAI,CAAC,GAAG,SAAS,CAAC;IACjH;;;OAGG;IACH,KAAK,CAAC,EAAE,kBAAkB,GAAG,SAAS,CAAC;CACxC;AAED,qBAAa,mBAAmB;IAC9B,OAAO,CAAC,QAAQ,CAAC,SAAS,CAAiB;IAC3C,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAqB;IAC7C,OAAO,CAAC,QAAQ,CAAC,OAAO,CAA6B;IACrD,+CAA+C;IAC/C,OAAO,CAAC,UAAU,CAAuB;IACzC,OAAO,CAAC,OAAO,CAAS;IAExB,YAAY,IAAI,GAAE,0BAA+B,EAahD;IAED;;;;OAIG;IACG,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC,CAO3B;YAEa,UAAU;YAcV,SAAS;IAkGvB,uBAAuB;IACvB,IAAI,IAAI,IAAI,CAOX;CACF"}
1
+ {"version":3,"file":"wrongstack-acp-agent.d.ts","sourceRoot":"","sources":["../../src/agent/wrongstack-acp-agent.ts"],"names":[],"mappings":"AA8BA,OAAO,EAEL,KAAK,OAAO,EAEZ,KAAK,kBAAkB,EACxB,MAAM,uBAAuB,CAAC;AAG/B,MAAM,WAAW,0BAA0B;IACzC,OAAO,CAAC,EAAE,OAAO,GAAG,SAAS,CAAC;IAC9B,UAAU,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IAChC,SAAS,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IAC/B;;;;OAIG;IACH,SAAS,CAAC,EAAE,OAAO,GAAG,MAAM,GAAG,SAAS,CAAC;IACzC,wDAAwD;IACxD,IAAI,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IAC1B;;;;;OAKG;IACH,SAAS,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IAC/B,kEAAkE;IAClE,mBAAmB,CAAC,EAAE,OAAO,GAAG,SAAS,CAAC;IAC1C;;;;OAIG;IACH,SAAS,CAAC,EAAE,CAAC,CAAC,SAAS,EAAE,MAAM,KAAK,KAAK,CAAC;QAAE,aAAa,EAAE,MAAM,CAAC;QAAC,OAAO,EAAE,OAAO,CAAA;KAAE,CAAC,CAAC,GAAG,SAAS,CAAC;IACpG;;;OAGG;IACH,OAAO,CAAC,EAAE,CAAC,CAAC,SAAS,EAAE,MAAM,EAAE,OAAO,EAAE,KAAK,CAAC;QAAE,aAAa,EAAE,MAAM,CAAC;QAAC,OAAO,EAAE,OAAO,CAAA;KAAE,CAAC,KAAK,IAAI,CAAC,GAAG,SAAS,CAAC;IACjH;;;OAGG;IACH,KAAK,CAAC,EAAE,kBAAkB,GAAG,SAAS,CAAC;CACxC;AAED,qBAAa,mBAAmB;IAC9B,OAAO,CAAC,QAAQ,CAAC,SAAS,CAAiB;IAC3C,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAqB;IAC7C,OAAO,CAAC,QAAQ,CAAC,OAAO,CAA6B;IACrD,+CAA+C;IAC/C,OAAO,CAAC,UAAU,CAAuB;IACzC,OAAO,CAAC,OAAO,CAAS;IAExB,YAAY,IAAI,GAAE,0BAA+B,EAahD;IAED;;;;OAIG;IACG,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC,CAO3B;YAEa,UAAU;YAcV,SAAS;IAmKvB,uBAAuB;IACvB,IAAI,IAAI,IAAI,CAOX;CACF"}
package/dist/agent.js CHANGED
@@ -1145,7 +1145,21 @@ var WrongStackACPServer = class {
1145
1145
  async startHttp(port) {
1146
1146
  const host = this.options.host ?? "127.0.0.1";
1147
1147
  const handler = this.handler;
1148
+ const authToken = this.options.authToken;
1149
+ let httpChain = Promise.resolve();
1148
1150
  this.httpServer = createServer(async (req, res) => {
1151
+ if (authToken) {
1152
+ const url = new URL(req.url ?? "/", `http://${host}:${port}`);
1153
+ const queryToken = url.searchParams.get("token");
1154
+ const authHeader = req.headers["authorization"];
1155
+ const bearerToken = Array.isArray(authHeader) ? authHeader[0]?.replace(/^Bearer\s+/i, "") : authHeader?.replace(/^Bearer\s+/i, "");
1156
+ const supplied = queryToken ?? bearerToken ?? "";
1157
+ if (supplied !== authToken) {
1158
+ res.writeHead(401, { "Content-Type": "application/json" });
1159
+ res.end(JSON.stringify({ error: { code: -32001, message: "Unauthorized" } }));
1160
+ return;
1161
+ }
1162
+ }
1149
1163
  const selfOrigin = `http://${host}:${port}`;
1150
1164
  const reqOrigin = Array.isArray(req.headers.origin) ? req.headers.origin[0] : req.headers.origin;
1151
1165
  if (reqOrigin && reqOrigin !== selfOrigin) {
@@ -1155,7 +1169,7 @@ var WrongStackACPServer = class {
1155
1169
  }
1156
1170
  if (reqOrigin) res.setHeader("Access-Control-Allow-Origin", reqOrigin);
1157
1171
  res.setHeader("Access-Control-Allow-Methods", "POST, OPTIONS");
1158
- res.setHeader("Access-Control-Allow-Headers", "Content-Type, Mcp-Session-Id");
1172
+ res.setHeader("Access-Control-Allow-Headers", "Content-Type, Mcp-Session-Id, Authorization");
1159
1173
  if (req.method === "OPTIONS") {
1160
1174
  res.writeHead(204);
1161
1175
  res.end();
@@ -1166,10 +1180,23 @@ var WrongStackACPServer = class {
1166
1180
  res.end(JSON.stringify({ error: "method not allowed" }));
1167
1181
  return;
1168
1182
  }
1183
+ const MAX_HTTP_BODY = 10 * 1024 * 1024;
1169
1184
  let body = "";
1185
+ let bodyBytes = 0;
1186
+ let tooLarge = false;
1170
1187
  for await (const chunk of req) {
1188
+ bodyBytes += chunk.length;
1189
+ if (bodyBytes > MAX_HTTP_BODY) {
1190
+ tooLarge = true;
1191
+ break;
1192
+ }
1171
1193
  body += chunk;
1172
1194
  }
1195
+ if (tooLarge) {
1196
+ res.writeHead(413, { "Content-Type": "application/json" });
1197
+ res.end(JSON.stringify({ error: { code: -32700, message: "Request body too large" } }));
1198
+ return;
1199
+ }
1173
1200
  let msg;
1174
1201
  try {
1175
1202
  msg = JSON.parse(body);
@@ -1178,26 +1205,43 @@ var WrongStackACPServer = class {
1178
1205
  res.end(JSON.stringify({ error: { code: -32700, message: "Parse error" } }));
1179
1206
  return;
1180
1207
  }
1181
- const notifications = [];
1182
- let response = null;
1183
- const originalSend = this.transport.send.bind(this.transport);
1184
- this.transport.send = async (m) => {
1185
- if (m.id !== void 0 && (m.result !== void 0 || m.error !== void 0)) {
1186
- response = m;
1187
- } else if (m.method === "session/update") {
1188
- notifications.push(m.params);
1189
- } else {
1190
- notifications.push(m);
1208
+ const isNotification = typeof msg === "object" && msg !== null && msg.id === void 0 && typeof msg.method === "string";
1209
+ if (isNotification) {
1210
+ try {
1211
+ await handler.handleMessage(msg);
1212
+ } catch {
1191
1213
  }
1192
- };
1214
+ res.writeHead(200, { "Content-Type": "application/json" });
1215
+ res.end(JSON.stringify({ notifications: [] }));
1216
+ return;
1217
+ }
1218
+ const requestPromise = httpChain.then(async () => {
1219
+ const notifications = [];
1220
+ let response = null;
1221
+ const originalSend = this.transport.send.bind(this.transport);
1222
+ this.transport.send = async (m) => {
1223
+ if (m.id !== void 0 && (m.result !== void 0 || m.error !== void 0)) {
1224
+ response = m;
1225
+ } else if (m.method === "session/update") {
1226
+ notifications.push(m.params);
1227
+ } else {
1228
+ notifications.push(m);
1229
+ }
1230
+ };
1231
+ try {
1232
+ await handler.handleMessage(msg);
1233
+ } finally {
1234
+ this.transport.send = originalSend;
1235
+ }
1236
+ res.writeHead(200, { "Content-Type": "application/json" });
1237
+ const responseBody = response !== null ? { ...response, notifications } : { notifications };
1238
+ res.end(JSON.stringify(responseBody));
1239
+ });
1240
+ httpChain = requestPromise.catch(() => void 0);
1193
1241
  try {
1194
- await handler.handleMessage(msg);
1195
- } finally {
1196
- this.transport.send = originalSend;
1242
+ await requestPromise;
1243
+ } catch {
1197
1244
  }
1198
- res.writeHead(200, { "Content-Type": "application/json" });
1199
- const responseBody = response !== null ? { ...response, notifications } : { notifications };
1200
- res.end(JSON.stringify(responseBody));
1201
1245
  });
1202
1246
  return new Promise((resolve) => {
1203
1247
  this.httpServer.listen(port, host, () => {