@wrongstack/acp 0.293.0 → 0.295.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.
Files changed (52) hide show
  1. package/README.md +22 -2
  2. package/dist/agent/protocol-contract.d.ts +210 -0
  3. package/dist/agent/protocol-contract.d.ts.map +1 -0
  4. package/dist/agent/protocol-handler.d.ts +6 -189
  5. package/dist/agent/protocol-handler.d.ts.map +1 -1
  6. package/dist/agent/server-agent-turn.d.ts +7 -1
  7. package/dist/agent/server-agent-turn.d.ts.map +1 -1
  8. package/dist/agent/stdio-transport.d.ts +12 -1
  9. package/dist/agent/stdio-transport.d.ts.map +1 -1
  10. package/dist/agent/tools-registry.d.ts +1 -1
  11. package/dist/agent/tools-registry.d.ts.map +1 -1
  12. package/dist/agent/wrongstack-acp-agent.d.ts +2 -0
  13. package/dist/agent/wrongstack-acp-agent.d.ts.map +1 -1
  14. package/dist/agent.js +172 -22
  15. package/dist/agent.js.map +4 -4
  16. package/dist/client/acp-session.d.ts +13 -1
  17. package/dist/client/acp-session.d.ts.map +1 -1
  18. package/dist/client/index.d.ts +11 -9
  19. package/dist/client/index.d.ts.map +1 -1
  20. package/dist/client/terminal-server.d.ts +5 -0
  21. package/dist/client/terminal-server.d.ts.map +1 -1
  22. package/dist/client/tool-translator.d.ts +1 -1
  23. package/dist/client/tool-translator.d.ts.map +1 -1
  24. package/dist/client/trust-boundary-permission.d.ts +16 -0
  25. package/dist/client/trust-boundary-permission.d.ts.map +1 -0
  26. package/dist/client/websocket-transport.d.ts +6 -0
  27. package/dist/client/websocket-transport.d.ts.map +1 -1
  28. package/dist/client.js +429 -218
  29. package/dist/client.js.map +4 -4
  30. package/dist/index.d.ts +29 -29
  31. package/dist/index.d.ts.map +1 -1
  32. package/dist/index.js +2555 -2250
  33. package/dist/index.js.map +4 -4
  34. package/dist/integration/acp-subagent-runner.d.ts +3 -3
  35. package/dist/integration/acp-subagent-runner.d.ts.map +1 -1
  36. package/dist/legacy.d.ts +8 -0
  37. package/dist/legacy.d.ts.map +1 -0
  38. package/dist/legacy.js +6 -0
  39. package/dist/legacy.js.map +7 -0
  40. package/dist/sdk.d.ts +10 -8
  41. package/dist/sdk.d.ts.map +1 -1
  42. package/dist/sdk.js +22 -0
  43. package/dist/sdk.js.map +3 -3
  44. package/dist/v1.d.ts +3 -0
  45. package/dist/v1.d.ts.map +1 -0
  46. package/dist/v1.js +12 -0
  47. package/dist/v1.js.map +7 -0
  48. package/dist/version.d.ts +2 -0
  49. package/dist/version.d.ts.map +1 -0
  50. package/dist/wrongstack-acp-agent.js +107 -16
  51. package/dist/wrongstack-acp-agent.js.map +4 -4
  52. package/package.json +10 -2
package/dist/agent.js CHANGED
@@ -1,5 +1,10 @@
1
1
  // src/agent/stdio-transport.ts
2
- import { expectDefined, writeErr } from "@wrongstack/core";
2
+ import { expectDefined, writeErr } from "@wrongstack/core/utils";
3
+ var DEFAULT_MAX_FRAME_CHARS = 20 * 1024 * 1024;
4
+ var DEFAULT_MAX_QUEUED_MESSAGES = 1e3;
5
+ function positiveLimit(value, fallback) {
6
+ return Number.isFinite(value) && (value ?? 0) > 0 ? Math.floor(value) : fallback;
7
+ }
3
8
  var StdioTransport = class {
4
9
  stdin = process.stdin;
5
10
  stdout = process.stdout;
@@ -9,7 +14,14 @@ var StdioTransport = class {
9
14
  closed = false;
10
15
  resolveRead = null;
11
16
  messageQueue = [];
12
- constructor() {
17
+ maxFrameChars;
18
+ maxQueuedMessages;
19
+ constructor(opts = {}) {
20
+ this.maxFrameChars = positiveLimit(opts.maxFrameChars, DEFAULT_MAX_FRAME_CHARS);
21
+ this.maxQueuedMessages = positiveLimit(
22
+ opts.maxQueuedMessages,
23
+ DEFAULT_MAX_QUEUED_MESSAGES
24
+ );
13
25
  this.stdin.resume();
14
26
  this.stdin.setEncoding("utf8");
15
27
  this.stdin.on("data", (chunk) => this.onData(chunk));
@@ -45,13 +57,34 @@ var StdioTransport = class {
45
57
  this.stdin.pause();
46
58
  this.resolveRead?.(null);
47
59
  this.resolveRead = null;
60
+ this.buffer = "";
61
+ this.messageQueue.length = 0;
62
+ this.handlers.clear();
48
63
  }
49
64
  onData(chunk) {
50
65
  this.buffer += chunk;
51
66
  const lines = this.buffer.split("\n");
52
67
  this.buffer = lines.pop() ?? "";
68
+ if (this.buffer.length > this.maxFrameChars) {
69
+ this.stderr.write(
70
+ `[wstack-acp frame error] pending frame exceeds ${this.maxFrameChars} characters
71
+ `,
72
+ "utf8"
73
+ );
74
+ this.close();
75
+ return;
76
+ }
53
77
  for (const raw of lines) {
54
78
  if (!raw.trim()) continue;
79
+ if (raw.length > this.maxFrameChars) {
80
+ this.stderr.write(
81
+ `[wstack-acp frame error] frame exceeds ${this.maxFrameChars} characters
82
+ `,
83
+ "utf8"
84
+ );
85
+ this.close();
86
+ return;
87
+ }
55
88
  try {
56
89
  this.dispatch(JSON.parse(raw));
57
90
  } catch (err) {
@@ -66,6 +99,15 @@ var StdioTransport = class {
66
99
  this.resolveRead = null;
67
100
  resolve(msg);
68
101
  } else {
102
+ if (this.messageQueue.length >= this.maxQueuedMessages) {
103
+ this.stderr.write(
104
+ `[wstack-acp queue error] pending message queue exceeds ${this.maxQueuedMessages} entries
105
+ `,
106
+ "utf8"
107
+ );
108
+ this.close();
109
+ return;
110
+ }
69
111
  this.messageQueue.push(msg);
70
112
  }
71
113
  for (const handler of this.handlers) {
@@ -78,9 +120,7 @@ var StdioTransport = class {
78
120
  }
79
121
  }
80
122
  handleClose() {
81
- this.closed = true;
82
- this.resolveRead?.(null);
83
- this.resolveRead = null;
123
+ this.close();
84
124
  }
85
125
  failAll(err) {
86
126
  this.stderr.write(`[wstack-acp stdin error] ${err.message}
@@ -206,11 +246,28 @@ function toolToPriority(tool) {
206
246
  // src/types/acp-v1.ts
207
247
  var ACP_PROTOCOL_VERSION = 1;
208
248
 
209
- // src/agent/protocol-handler.ts
249
+ // src/version.ts
250
+ import { createRequire } from "node:module";
251
+ var require2 = createRequire(import.meta.url);
252
+ function readPackageVersion() {
253
+ try {
254
+ const packageJson = require2("../package.json");
255
+ if (typeof packageJson.version === "string" && packageJson.version.length > 0) {
256
+ return packageJson.version;
257
+ }
258
+ } catch {
259
+ }
260
+ return "dev";
261
+ }
262
+ var ACP_PACKAGE_VERSION = readPackageVersion();
263
+
264
+ // src/agent/protocol-contract.ts
210
265
  function toWire(msg) {
211
266
  return msg;
212
267
  }
213
- var WRONGSTACK_VERSION = "0.274.1";
268
+ var WRONGSTACK_VERSION = ACP_PACKAGE_VERSION;
269
+
270
+ // src/agent/protocol-handler.ts
214
271
  var WRONGSTACK_AUTH_METHODS = [
215
272
  {
216
273
  id: "wrongstack-auth",
@@ -221,6 +278,7 @@ var WRONGSTACK_AUTH_METHODS = [
221
278
  }
222
279
  ];
223
280
  var DEFAULT_MODE_ID = "code";
281
+ var DEFAULT_MAX_SESSIONS = 64;
224
282
  var DEFAULT_MODES = [
225
283
  {
226
284
  id: DEFAULT_MODE_ID,
@@ -238,6 +296,8 @@ var ACPProtocolHandler = class {
238
296
  agentName;
239
297
  replayFor;
240
298
  seedFor;
299
+ disposeFor;
300
+ maxSessions;
241
301
  store;
242
302
  initialized = false;
243
303
  clientCapabilities = {};
@@ -258,6 +318,8 @@ var ACPProtocolHandler = class {
258
318
  this.agentName = opts.agentName ?? "wrongstack";
259
319
  this.replayFor = opts.replayFor;
260
320
  this.seedFor = opts.seedFor;
321
+ this.disposeFor = opts.disposeFor;
322
+ this.maxSessions = Number.isFinite(opts.maxSessions) && (opts.maxSessions ?? 0) > 0 ? Math.floor(opts.maxSessions) : DEFAULT_MAX_SESSIONS;
261
323
  this.store = opts.store;
262
324
  if (typeof this.transport.onMessage === "function") {
263
325
  this.transport.onMessage((m) => this.maybeResolvePending(m));
@@ -315,8 +377,9 @@ var ACPProtocolHandler = class {
315
377
  }
316
378
  /** Abort all active turns and drop session state. */
317
379
  close() {
318
- for (const [, session] of this.sessions) {
380
+ for (const [sessionId, session] of this.sessions) {
319
381
  session.abort.abort();
382
+ this.disposeSession(sessionId);
320
383
  }
321
384
  this.sessions.clear();
322
385
  for (const [, p] of this.pendingOut) {
@@ -325,6 +388,12 @@ var ACPProtocolHandler = class {
325
388
  }
326
389
  this.pendingOut.clear();
327
390
  }
391
+ disposeSession(sessionId) {
392
+ try {
393
+ this.disposeFor?.(sessionId);
394
+ } catch {
395
+ }
396
+ }
328
397
  // ────────────────────────────────────────────────────────────────────
329
398
  // Requests
330
399
  // ────────────────────────────────────────────────────────────────────
@@ -444,6 +513,10 @@ var ACPProtocolHandler = class {
444
513
  return false;
445
514
  }
446
515
  async handleSessionNew(id, params) {
516
+ if (this.sessions.size >= this.maxSessions) {
517
+ await this.sendError(id, -32e3, `active session limit reached (${this.maxSessions})`);
518
+ return false;
519
+ }
447
520
  const p = params ?? {};
448
521
  const cwd = typeof p.cwd === "string" ? p.cwd : this.defaultCwd;
449
522
  const sessionId = `sess_${this.allocId()}`;
@@ -494,6 +567,10 @@ var ACPProtocolHandler = class {
494
567
  if (!existing && sessionId && this.store) {
495
568
  const persisted = await this.store.load(sessionId);
496
569
  if (persisted) {
570
+ if (this.sessions.size >= this.maxSessions) {
571
+ await this.sendError(id, -32e3, `active session limit reached (${this.maxSessions})`);
572
+ return false;
573
+ }
497
574
  const restored = {
498
575
  id: sessionId,
499
576
  cwd: persisted.cwd ?? loadCwd ?? this.defaultCwd,
@@ -589,7 +666,10 @@ var ACPProtocolHandler = class {
589
666
  return false;
590
667
  }
591
668
  session.abort.abort();
592
- if (sessionId) this.sessions.delete(sessionId);
669
+ if (sessionId) {
670
+ this.sessions.delete(sessionId);
671
+ this.disposeSession(sessionId);
672
+ }
593
673
  await this.transport.send(toWire({
594
674
  jsonrpc: "2.0",
595
675
  id,
@@ -611,6 +691,7 @@ var ACPProtocolHandler = class {
611
691
  const session = this.sessions.get(sessionId);
612
692
  session.abort.abort();
613
693
  this.sessions.delete(sessionId);
694
+ this.disposeSession(sessionId);
614
695
  await this.transport.send(toWire({
615
696
  jsonrpc: "2.0",
616
697
  id,
@@ -626,6 +707,10 @@ var ACPProtocolHandler = class {
626
707
  await this.sendError(id, -32e3, `session not found: ${sourceId}`);
627
708
  return false;
628
709
  }
710
+ if (this.sessions.size >= this.maxSessions) {
711
+ await this.sendError(id, -32e3, `active session limit reached (${this.maxSessions})`);
712
+ return false;
713
+ }
629
714
  const now = (/* @__PURE__ */ new Date()).toISOString();
630
715
  const sessionId = `sess_${this.allocId()}`;
631
716
  const forked = {
@@ -1094,7 +1179,7 @@ var WsBridgeTransport = class {
1094
1179
  // src/agent/wrongstack-acp-agent.ts
1095
1180
  import { fileURLToPath } from "node:url";
1096
1181
  import { createServer } from "node:http";
1097
- import { writeErr as writeErr2 } from "@wrongstack/core";
1182
+ import { writeErr as writeErr2 } from "@wrongstack/core/utils";
1098
1183
  var WrongStackACPServer = class {
1099
1184
  transport;
1100
1185
  handler;
@@ -1113,6 +1198,7 @@ var WrongStackACPServer = class {
1113
1198
  agentName: opts.agentName,
1114
1199
  ...opts.replayFor ? { replayFor: opts.replayFor } : {},
1115
1200
  ...opts.seedFor ? { seedFor: opts.seedFor } : {},
1201
+ ...opts.disposeFor ? { disposeFor: opts.disposeFor } : {},
1116
1202
  ...opts.store ? { store: opts.store } : {}
1117
1203
  });
1118
1204
  }
@@ -1134,13 +1220,17 @@ var WrongStackACPServer = class {
1134
1220
  this.transport.sendStartupMarker();
1135
1221
  }
1136
1222
  this.running = true;
1137
- while (this.running) {
1138
- const msg = await this.transport.read();
1139
- if (!msg) break;
1140
- const terminal = await this.handler.handleMessage(msg);
1141
- if (terminal) break;
1223
+ try {
1224
+ while (this.running) {
1225
+ const msg = await this.transport.read();
1226
+ if (!msg) break;
1227
+ const terminal = await this.handler.handleMessage(msg);
1228
+ if (terminal) break;
1229
+ }
1230
+ } finally {
1231
+ this.handler.close();
1232
+ this.transport.close();
1142
1233
  }
1143
- this.transport.close();
1144
1234
  }
1145
1235
  async startHttp(port) {
1146
1236
  const host = this.options.host ?? "127.0.0.1";
@@ -1255,6 +1345,7 @@ var WrongStackACPServer = class {
1255
1345
  /** Stop the server. */
1256
1346
  stop() {
1257
1347
  this.running = false;
1348
+ this.handler.close();
1258
1349
  this.transport.close();
1259
1350
  if (this.httpServer) {
1260
1351
  this.httpServer.close();
@@ -1283,8 +1374,11 @@ function makeACPServerAgentTurn(opts) {
1283
1374
  const agents = /* @__PURE__ */ new Map();
1284
1375
  const timeouts = /* @__PURE__ */ new Map();
1285
1376
  const history = /* @__PURE__ */ new Map();
1377
+ const historyBytes = /* @__PURE__ */ new Map();
1286
1378
  const pendingSeed = /* @__PURE__ */ new Set();
1287
1379
  const timeoutMs = opts.timeoutMs ?? 5 * 6e4;
1380
+ const maxHistoryEntries = finitePositiveLimit(opts.maxHistoryEntries, 1e3);
1381
+ const maxHistoryBytes = finitePositiveLimit(opts.maxHistoryBytes, 8 * 1024 * 1024);
1288
1382
  const turn = async (input, emit, api) => {
1289
1383
  let agent = agents.get(input.sessionId);
1290
1384
  if (!agent) {
@@ -1348,13 +1442,36 @@ function makeACPServerAgentTurn(opts) {
1348
1442
  }
1349
1443
  const userText = promptToText(input.prompt);
1350
1444
  const hist = history.get(input.sessionId) ?? [];
1445
+ let retainedHistoryBytes = historyBytes.get(input.sessionId) ?? 0;
1351
1446
  if (userText) {
1352
- hist.push({ sessionUpdate: "user_message_chunk", content: { type: "text", text: userText } });
1447
+ const update = {
1448
+ sessionUpdate: "user_message_chunk",
1449
+ content: { type: "text", text: userText }
1450
+ };
1451
+ hist.push(update);
1452
+ retainedHistoryBytes += replayEntryBytes(update);
1353
1453
  }
1354
1454
  if (text) {
1355
- hist.push({ sessionUpdate: "agent_message_chunk", content: { type: "text", text } });
1455
+ const update = {
1456
+ sessionUpdate: "agent_message_chunk",
1457
+ content: { type: "text", text }
1458
+ };
1459
+ hist.push(update);
1460
+ retainedHistoryBytes += replayEntryBytes(update);
1461
+ }
1462
+ retainedHistoryBytes = trimHistory(
1463
+ hist,
1464
+ retainedHistoryBytes,
1465
+ maxHistoryEntries,
1466
+ maxHistoryBytes
1467
+ );
1468
+ if (hist.length > 0) {
1469
+ history.set(input.sessionId, hist);
1470
+ historyBytes.set(input.sessionId, retainedHistoryBytes);
1471
+ } else {
1472
+ history.delete(input.sessionId);
1473
+ historyBytes.delete(input.sessionId);
1356
1474
  }
1357
- if (hist.length > 0) history.set(input.sessionId, hist);
1358
1475
  const plan = extractPlan(result);
1359
1476
  if (plan.length > 0) {
1360
1477
  emit({
@@ -1388,13 +1505,46 @@ function makeACPServerAgentTurn(opts) {
1388
1505
  for (const u of unsub) u();
1389
1506
  }
1390
1507
  };
1391
- const replay = (sessionId) => history.get(sessionId) ?? [];
1508
+ const replay = (sessionId) => [
1509
+ ...history.get(sessionId) ?? []
1510
+ ];
1392
1511
  const seed = (sessionId, incoming) => {
1393
1512
  if (incoming.length === 0) return;
1394
- history.set(sessionId, [...incoming]);
1513
+ const seeded = [...incoming];
1514
+ const retainedBytes = trimHistory(
1515
+ seeded,
1516
+ seeded.reduce((total, entry) => total + replayEntryBytes(entry), 0),
1517
+ maxHistoryEntries,
1518
+ maxHistoryBytes
1519
+ );
1520
+ history.set(sessionId, seeded);
1521
+ historyBytes.set(sessionId, retainedBytes);
1395
1522
  pendingSeed.add(sessionId);
1396
1523
  };
1397
- return Object.assign(turn, { replay, seed });
1524
+ const dispose = (sessionId) => {
1525
+ const timer = timeouts.get(sessionId);
1526
+ if (timer) clearTimeout(timer);
1527
+ timeouts.delete(sessionId);
1528
+ agents.delete(sessionId);
1529
+ history.delete(sessionId);
1530
+ historyBytes.delete(sessionId);
1531
+ pendingSeed.delete(sessionId);
1532
+ };
1533
+ return Object.assign(turn, { replay, seed, dispose });
1534
+ }
1535
+ function finitePositiveLimit(value, fallback) {
1536
+ return Number.isFinite(value) && (value ?? 0) > 0 ? Math.floor(value) : fallback;
1537
+ }
1538
+ function trimHistory(entries, retainedBytes, maxEntries, maxBytes) {
1539
+ while (entries.length > maxEntries || retainedBytes > maxBytes) {
1540
+ const removed = entries.shift();
1541
+ if (!removed) break;
1542
+ retainedBytes -= replayEntryBytes(removed);
1543
+ }
1544
+ return Math.max(0, retainedBytes);
1545
+ }
1546
+ function replayEntryBytes(entry) {
1547
+ return Buffer.byteLength(JSON.stringify(entry), "utf8");
1398
1548
  }
1399
1549
  function seedAgentContext(agent, history) {
1400
1550
  const state = agent.ctx?.state;