@wrongstack/acp 0.273.1 → 0.275.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.
@@ -1,4 +1,5 @@
1
1
  import { fileURLToPath } from 'url';
2
+ import { createServer } from 'http';
2
3
  import { writeErr, expectDefined } from '@wrongstack/core';
3
4
 
4
5
  // src/agent/wrongstack-acp-agent.ts
@@ -10,7 +11,16 @@ var ACP_PROTOCOL_VERSION = 1;
10
11
  function toWire(msg) {
11
12
  return msg;
12
13
  }
13
- var WRONGSTACK_VERSION = "0.263.0";
14
+ var WRONGSTACK_VERSION = "0.274.1";
15
+ var WRONGSTACK_AUTH_METHODS = [
16
+ {
17
+ id: "wrongstack-auth",
18
+ name: "Run wstack auth",
19
+ description: "Configure a WrongStack model provider in an interactive terminal.",
20
+ type: "terminal",
21
+ args: ["auth"]
22
+ }
23
+ ];
14
24
  var DEFAULT_MODE_ID = "code";
15
25
  var DEFAULT_MODES = [
16
26
  {
@@ -27,9 +37,17 @@ var ACPProtocolHandler = class {
27
37
  modes;
28
38
  configOptions;
29
39
  agentName;
40
+ replayFor;
41
+ seedFor;
42
+ store;
30
43
  initialized = false;
44
+ clientCapabilities = {};
31
45
  sessions = /* @__PURE__ */ new Map();
32
46
  nextId = 1;
47
+ // Outbound request correlation (server → client requests, e.g.
48
+ // session/request_permission). Keyed by our own `srv_N` ids.
49
+ pendingOut = /* @__PURE__ */ new Map();
50
+ nextOutId = 1;
33
51
  constructor(opts) {
34
52
  this.transport = opts.transport;
35
53
  this.defaultCwd = opts.defaultCwd;
@@ -39,6 +57,43 @@ var ACPProtocolHandler = class {
39
57
  this.modes = opts.modes ?? DEFAULT_MODES;
40
58
  this.configOptions = opts.configOptions ?? [];
41
59
  this.agentName = opts.agentName ?? "wrongstack";
60
+ this.replayFor = opts.replayFor;
61
+ this.seedFor = opts.seedFor;
62
+ this.store = opts.store;
63
+ if (typeof this.transport.onMessage === "function") {
64
+ this.transport.onMessage((m) => this.maybeResolvePending(m));
65
+ }
66
+ }
67
+ /**
68
+ * Send a request to the client and await its response. Used for
69
+ * server-initiated calls like `session/request_permission`. Rejects on
70
+ * timeout or transport error so the caller can pick a safe fallback.
71
+ */
72
+ request(method, params, timeoutMs = 6e4) {
73
+ const id = `srv_${this.nextOutId++}`;
74
+ return new Promise((resolve, reject) => {
75
+ const timer = setTimeout(() => {
76
+ this.pendingOut.delete(id);
77
+ reject(new Error(`${method} timed out after ${timeoutMs}ms`));
78
+ }, timeoutMs);
79
+ this.pendingOut.set(id, { resolve, reject, timer });
80
+ this.transport.send(toWire({ jsonrpc: "2.0", id, method, params })).catch((e) => {
81
+ clearTimeout(timer);
82
+ this.pendingOut.delete(id);
83
+ reject(e instanceof Error ? e : new Error(String(e)));
84
+ });
85
+ });
86
+ }
87
+ maybeResolvePending(m) {
88
+ const id = m.id;
89
+ if (typeof id !== "string") return;
90
+ const pending = this.pendingOut.get(id);
91
+ if (!pending) return;
92
+ this.pendingOut.delete(id);
93
+ clearTimeout(pending.timer);
94
+ const err = m.error;
95
+ if (err) pending.reject(new Error(err.message ?? "client request failed"));
96
+ else pending.resolve(m.result);
42
97
  }
43
98
  /**
44
99
  * Process one inbound message. Returns true if this was a terminal
@@ -65,6 +120,11 @@ var ACPProtocolHandler = class {
65
120
  session.abort.abort();
66
121
  }
67
122
  this.sessions.clear();
123
+ for (const [, p] of this.pendingOut) {
124
+ clearTimeout(p.timer);
125
+ p.reject(new Error("protocol handler closed"));
126
+ }
127
+ this.pendingOut.clear();
68
128
  }
69
129
  // ────────────────────────────────────────────────────────────────────
70
130
  // Requests
@@ -80,10 +140,18 @@ var ACPProtocolHandler = class {
80
140
  return await this.handleInitialize(id, params);
81
141
  case "authenticate":
82
142
  return await this.handleAuthenticate(id, params);
143
+ case "logout":
144
+ return await this.handleLogout(id, params);
83
145
  case "session/new":
84
146
  return await this.handleSessionNew(id, params);
85
147
  case "session/load":
86
148
  return await this.handleSessionLoad(id, params);
149
+ case "session/resume":
150
+ return await this.handleSessionResume(id, params);
151
+ case "session/close":
152
+ return await this.handleSessionClose(id, params);
153
+ case "session/delete":
154
+ return await this.handleSessionDelete(id, params);
87
155
  case "session/prompt":
88
156
  return await this.handleSessionPrompt(id, params);
89
157
  case "session/set_mode":
@@ -92,6 +160,16 @@ var ACPProtocolHandler = class {
92
160
  return await this.handleSetConfigOption(id, params);
93
161
  case "session/list":
94
162
  return await this.handleSessionList(id);
163
+ case "session/fork":
164
+ return await this.handleSessionFork(id, params);
165
+ case "providers/list":
166
+ return await this.handleProvidersList(id, params);
167
+ case "providers/set":
168
+ return await this.handleProvidersSet(id, params);
169
+ case "providers/disable":
170
+ return await this.handleProvidersDisable(id, params);
171
+ case "mcp/message":
172
+ return await this.handleMcpMessage(id, params);
95
173
  default:
96
174
  await this.sendError(id, -32601, `Unknown method: ${method}`);
97
175
  return false;
@@ -104,14 +182,8 @@ var ACPProtocolHandler = class {
104
182
  }
105
183
  async handleInitialize(id, params) {
106
184
  const p = params ?? {};
107
- const requested = typeof p.protocolVersion === "number" ? p.protocolVersion : 1;
108
- if (requested !== ACP_PROTOCOL_VERSION) {
109
- await this.sendError(
110
- id,
111
- -32e3,
112
- `server speaks protocolVersion=${ACP_PROTOCOL_VERSION}, client requested ${requested}`
113
- );
114
- return false;
185
+ if (p.clientCapabilities && typeof p.clientCapabilities === "object") {
186
+ this.clientCapabilities = p.clientCapabilities;
115
187
  }
116
188
  this.initialized = true;
117
189
  await this.transport.send(toWire({
@@ -122,9 +194,25 @@ var ACPProtocolHandler = class {
122
194
  agentCapabilities: {
123
195
  loadSession: true,
124
196
  promptCapabilities: {
125
- image: false,
197
+ // We route ACP image blocks into the core agent's multimodal
198
+ // input (server-agent-turn.promptToAgentInput); whether the
199
+ // model can see them is the configured provider's concern.
200
+ image: true,
126
201
  audio: false,
127
202
  embeddedContext: true
203
+ },
204
+ mcpCapabilities: {
205
+ http: false,
206
+ sse: false
207
+ },
208
+ sessionCapabilities: {
209
+ close: {},
210
+ list: {},
211
+ delete: {},
212
+ resume: {}
213
+ },
214
+ auth: {
215
+ logout: {}
128
216
  }
129
217
  },
130
218
  agentInfo: {
@@ -132,10 +220,7 @@ var ACPProtocolHandler = class {
132
220
  title: "WrongStack",
133
221
  version: WRONGSTACK_VERSION
134
222
  },
135
- // Static options advertised at handshake. They are also
136
- // re-sent on every `current_mode_update` / `config_option_update`
137
- // notification so late-joining clients see them.
138
- authMethods: [],
223
+ authMethods: WRONGSTACK_AUTH_METHODS,
139
224
  modes: this.modes,
140
225
  configOptions: this.configOptions
141
226
  }
@@ -150,6 +235,14 @@ var ACPProtocolHandler = class {
150
235
  }));
151
236
  return false;
152
237
  }
238
+ async handleLogout(id, _params) {
239
+ await this.transport.send(toWire({
240
+ jsonrpc: "2.0",
241
+ id,
242
+ result: {}
243
+ }));
244
+ return false;
245
+ }
153
246
  async handleSessionNew(id, params) {
154
247
  const p = params ?? {};
155
248
  const cwd = typeof p.cwd === "string" ? p.cwd : this.defaultCwd;
@@ -165,6 +258,7 @@ var ACPProtocolHandler = class {
165
258
  };
166
259
  this.sessions.set(sessionId, state);
167
260
  this.onSessionNew(state);
261
+ await this.persist(state);
168
262
  await this.sendNotification({
169
263
  sessionId,
170
264
  update: {
@@ -193,7 +287,173 @@ var ACPProtocolHandler = class {
193
287
  return false;
194
288
  }
195
289
  async handleSessionLoad(id, params) {
196
- return this.handleSessionNew(id, params);
290
+ const p = params ?? {};
291
+ const sessionId = typeof p.sessionId === "string" ? p.sessionId : null;
292
+ const loadCwd = typeof p.cwd === "string" ? p.cwd : void 0;
293
+ let existing = sessionId ? this.sessions.get(sessionId) : void 0;
294
+ if (!existing && sessionId && this.store) {
295
+ const persisted = await this.store.load(sessionId);
296
+ if (persisted) {
297
+ const restored = {
298
+ id: sessionId,
299
+ cwd: persisted.cwd ?? loadCwd ?? this.defaultCwd,
300
+ abort: new AbortController(),
301
+ modeId: persisted.modeId ?? DEFAULT_MODE_ID,
302
+ createdAt: persisted.createdAt ?? (/* @__PURE__ */ new Date()).toISOString(),
303
+ updatedAt: (/* @__PURE__ */ new Date()).toISOString(),
304
+ ...persisted.title !== void 0 ? { title: persisted.title } : {}
305
+ };
306
+ this.sessions.set(sessionId, restored);
307
+ this.seedFor?.(sessionId, persisted.history ?? []);
308
+ for (const update of persisted.history ?? []) {
309
+ await this.sendNotification({ sessionId, update });
310
+ }
311
+ await this.sendNotification({
312
+ sessionId,
313
+ update: { sessionUpdate: "current_mode_update", modeId: restored.modeId }
314
+ });
315
+ await this.transport.send(toWire({
316
+ jsonrpc: "2.0",
317
+ id,
318
+ result: {
319
+ initialMode: { currentModeId: restored.modeId, availableModes: this.modes }
320
+ }
321
+ }));
322
+ return false;
323
+ }
324
+ }
325
+ if (existing) {
326
+ existing.updatedAt = (/* @__PURE__ */ new Date()).toISOString();
327
+ const replay = sessionId ? this.replayFor?.(sessionId) : void 0;
328
+ if (replay) {
329
+ for (const update of replay) {
330
+ await this.sendNotification({ sessionId, update });
331
+ }
332
+ }
333
+ await this.sendNotification({
334
+ sessionId,
335
+ update: {
336
+ sessionUpdate: "session_info_update",
337
+ updatedAt: existing.updatedAt
338
+ }
339
+ });
340
+ await this.sendNotification({
341
+ sessionId,
342
+ update: {
343
+ sessionUpdate: "current_mode_update",
344
+ modeId: existing.modeId
345
+ }
346
+ });
347
+ await this.transport.send(toWire({
348
+ jsonrpc: "2.0",
349
+ id,
350
+ result: {
351
+ initialMode: {
352
+ currentModeId: existing.modeId,
353
+ availableModes: this.modes
354
+ }
355
+ }
356
+ }));
357
+ return false;
358
+ }
359
+ await this.sendError(id, -32e3, `session not found: ${sessionId}`);
360
+ return false;
361
+ }
362
+ async handleSessionResume(id, params) {
363
+ const p = params ?? {};
364
+ const sessionId = typeof p.sessionId === "string" ? p.sessionId : null;
365
+ const existing = sessionId ? this.sessions.get(sessionId) : void 0;
366
+ if (existing) {
367
+ existing.updatedAt = (/* @__PURE__ */ new Date()).toISOString();
368
+ await this.transport.send(toWire({
369
+ jsonrpc: "2.0",
370
+ id,
371
+ result: {
372
+ initialMode: {
373
+ currentModeId: existing.modeId,
374
+ availableModes: this.modes
375
+ }
376
+ }
377
+ }));
378
+ return false;
379
+ }
380
+ await this.sendError(id, -32e3, `session not found: ${sessionId}`);
381
+ return false;
382
+ }
383
+ async handleSessionClose(id, params) {
384
+ const p = params ?? {};
385
+ const sessionId = typeof p.sessionId === "string" ? p.sessionId : null;
386
+ const session = sessionId ? this.sessions.get(sessionId) : void 0;
387
+ if (!session) {
388
+ await this.sendError(id, -32e3, `session not found: ${sessionId}`);
389
+ return false;
390
+ }
391
+ session.abort.abort();
392
+ if (sessionId) this.sessions.delete(sessionId);
393
+ await this.transport.send(toWire({
394
+ jsonrpc: "2.0",
395
+ id,
396
+ result: {}
397
+ }));
398
+ return false;
399
+ }
400
+ async handleSessionDelete(id, params) {
401
+ const p = params ?? {};
402
+ const sessionId = typeof p.sessionId === "string" ? p.sessionId : null;
403
+ if (!sessionId) {
404
+ await this.sendError(id, -32e3, `session not found: ${sessionId}`);
405
+ return false;
406
+ }
407
+ if (!this.sessions.has(sessionId)) {
408
+ await this.transport.send(toWire({ jsonrpc: "2.0", id, result: { configOptions: [...this.configOptions] } }));
409
+ return false;
410
+ }
411
+ const session = this.sessions.get(sessionId);
412
+ session.abort.abort();
413
+ this.sessions.delete(sessionId);
414
+ await this.transport.send(toWire({
415
+ jsonrpc: "2.0",
416
+ id,
417
+ result: {}
418
+ }));
419
+ return false;
420
+ }
421
+ async handleSessionFork(id, params) {
422
+ const p = params ?? {};
423
+ const sourceId = typeof p.sessionId === "string" ? p.sessionId : null;
424
+ if (!sourceId || !this.sessions.has(sourceId)) {
425
+ await this.sendError(id, -32e3, `session not found: ${sourceId}`);
426
+ return false;
427
+ }
428
+ const forkParams = params;
429
+ return this.handleSessionNew(id, { ...forkParams, cwd: p.cwd ?? this.defaultCwd });
430
+ }
431
+ async handleProvidersList(id, _params) {
432
+ await this.transport.send(toWire({
433
+ jsonrpc: "2.0",
434
+ id,
435
+ result: {
436
+ providers: [],
437
+ currentProviderId: null
438
+ }
439
+ }));
440
+ return false;
441
+ }
442
+ async handleProvidersSet(id, _params) {
443
+ await this.sendError(id, -32e3, "provider configuration not available through ACP; use wstack auth");
444
+ return false;
445
+ }
446
+ async handleProvidersDisable(id, _params) {
447
+ await this.transport.send(toWire({
448
+ jsonrpc: "2.0",
449
+ id,
450
+ result: {}
451
+ }));
452
+ return false;
453
+ }
454
+ async handleMcpMessage(id, _params) {
455
+ await this.sendError(id, -32e3, "MCP message routing not available through ACP");
456
+ return false;
197
457
  }
198
458
  async handleSessionPrompt(id, params) {
199
459
  const p = params ?? {};
@@ -213,11 +473,54 @@ var ACPProtocolHandler = class {
213
473
  const turnSignal = new AbortController();
214
474
  const onCancel = () => turnSignal.abort();
215
475
  session.abort.signal.addEventListener("abort", onCancel, { once: true });
476
+ const api = {
477
+ clientCapabilities: this.clientCapabilities,
478
+ requestPermission: async (req) => {
479
+ const res = await this.request("session/request_permission", {
480
+ sessionId,
481
+ toolCall: req.toolCall,
482
+ options: req.options
483
+ });
484
+ const outcome = res?.outcome;
485
+ return outcome ?? { outcome: "cancelled" };
486
+ },
487
+ readTextFile: async (params2) => {
488
+ const res = await this.request("fs/read_text_file", { sessionId, ...params2 });
489
+ return String(res?.content ?? "");
490
+ },
491
+ writeTextFile: async (params2) => {
492
+ await this.request("fs/write_text_file", { sessionId, ...params2 });
493
+ },
494
+ runTerminal: async ({ command, args, cwd }) => {
495
+ const created = await this.request("terminal/create", {
496
+ sessionId,
497
+ command,
498
+ ...args ? { args } : {},
499
+ ...cwd ? { cwd } : {}
500
+ });
501
+ const terminalId = created?.terminalId;
502
+ if (!terminalId) return { output: "", exitCode: null };
503
+ try {
504
+ const exit = await this.request("terminal/wait_for_exit", { sessionId, terminalId });
505
+ const out = await this.request("terminal/output", { sessionId, terminalId });
506
+ return {
507
+ output: String(out?.output ?? ""),
508
+ exitCode: typeof exit?.exitCode === "number" ? exit.exitCode : null
509
+ };
510
+ } finally {
511
+ try {
512
+ await this.request("terminal/release", { sessionId, terminalId });
513
+ } catch {
514
+ }
515
+ }
516
+ }
517
+ };
216
518
  let result;
217
519
  try {
218
520
  result = await this.runTurn(
219
521
  { sessionId, prompt: p.prompt, signal: turnSignal.signal },
220
- (update) => this.sendNotification({ sessionId, update })
522
+ (update) => this.sendNotification({ sessionId, update }),
523
+ api
221
524
  );
222
525
  } catch (err) {
223
526
  session.abort.signal.removeEventListener("abort", onCancel);
@@ -227,6 +530,7 @@ var ACPProtocolHandler = class {
227
530
  }
228
531
  session.abort.signal.removeEventListener("abort", onCancel);
229
532
  session.updatedAt = (/* @__PURE__ */ new Date()).toISOString();
533
+ await this.persist(session);
230
534
  await this.transport.send(toWire({
231
535
  jsonrpc: "2.0",
232
536
  id,
@@ -255,12 +559,12 @@ var ACPProtocolHandler = class {
255
559
  async handleSetConfigOption(id, params) {
256
560
  const p = params ?? {};
257
561
  const sessionId = typeof p.sessionId === "string" ? p.sessionId : null;
258
- const optionId = typeof p.configOptionId === "string" ? p.configOptionId : null;
562
+ const optionId = typeof p.configId === "string" ? p.configId : null;
259
563
  const value = typeof p.value === "string" ? p.value : null;
260
564
  const session = sessionId ? this.sessions.get(sessionId) : void 0;
261
565
  const option = optionId ? this.configOptions.find((o) => o.id === optionId) : void 0;
262
566
  if (!session || !option || value === null || !option.options.some((o) => o.value === value)) {
263
- await this.sendError(id, -32602, "invalid sessionId, configOptionId, or value");
567
+ await this.sendError(id, -32602, "invalid sessionId, configId, or value");
264
568
  return false;
265
569
  }
266
570
  option.currentValue = value;
@@ -272,7 +576,7 @@ var ACPProtocolHandler = class {
272
576
  configOptions: [...this.configOptions]
273
577
  }
274
578
  });
275
- await this.transport.send(toWire({ jsonrpc: "2.0", id, result: {} }));
579
+ await this.transport.send(toWire({ jsonrpc: "2.0", id, result: { configOptions: [...this.configOptions] } }));
276
580
  return false;
277
581
  }
278
582
  async handleSessionList(id) {
@@ -306,6 +610,9 @@ var ACPProtocolHandler = class {
306
610
  }
307
611
  return false;
308
612
  }
613
+ case "$/cancel_request": {
614
+ return false;
615
+ }
309
616
  case "exit":
310
617
  this.close();
311
618
  return true;
@@ -319,6 +626,14 @@ var ACPProtocolHandler = class {
319
626
  async sendNotification(params) {
320
627
  await this.transport.send(toWire({ jsonrpc: "2.0", method: "session/update", params }));
321
628
  }
629
+ /** Best-effort durable persistence of a session + its recorded history. */
630
+ async persist(state) {
631
+ if (!this.store) return;
632
+ try {
633
+ await this.store.save(state, this.replayFor?.(state.id));
634
+ } catch {
635
+ }
636
+ }
322
637
  async sendError(id, code, message, data) {
323
638
  const error = { code, message };
324
639
  if (data !== void 0) error.data = data;
@@ -436,26 +751,41 @@ var StdioTransport = class {
436
751
  var WrongStackACPServer = class {
437
752
  transport;
438
753
  handler;
754
+ options;
755
+ /** HTTP server when transport mode is HTTP. */
756
+ httpServer = null;
439
757
  running = false;
440
758
  constructor(opts = {}) {
759
+ this.options = opts;
441
760
  this.transport = new StdioTransport();
442
761
  const runTurn = opts.runTurn ?? defaultEchoRunTurn;
443
762
  this.handler = new ACPProtocolHandler({
444
763
  transport: this.transport,
445
764
  defaultCwd: opts.defaultCwd ?? process.cwd(),
446
765
  runTurn,
447
- agentName: opts.agentName
766
+ agentName: opts.agentName,
767
+ ...opts.replayFor ? { replayFor: opts.replayFor } : {},
768
+ ...opts.seedFor ? { seedFor: opts.seedFor } : {},
769
+ ...opts.store ? { store: opts.store } : {}
448
770
  });
449
771
  }
450
772
  /**
451
- * Start the server. Blocks until the client disconnects.
452
- *
453
- * 1. Print the legacy `[wstack-acp]\n` marker so the client knows the
454
- * process is the ACP server (the old `StdioTransport` handshake).
455
- * 2. Loop: read messages, dispatch to the handler, until EOF / error.
773
+ * Start the server. Mode depends on `options.transport`:
774
+ * - 'stdio' (default): reads JSON-RPC from stdin, writes to stdout.
775
+ * - number: listens as HTTP on the given port.
456
776
  */
457
777
  async start() {
458
- this.transport.sendStartupMarker();
778
+ const transportMode = this.options.transport;
779
+ if (typeof transportMode === "number") {
780
+ await this.startHttp(transportMode);
781
+ } else {
782
+ await this.startStdio();
783
+ }
784
+ }
785
+ async startStdio() {
786
+ if (this.options.legacyStartupMarker) {
787
+ this.transport.sendStartupMarker();
788
+ }
459
789
  this.running = true;
460
790
  while (this.running) {
461
791
  const msg = await this.transport.read();
@@ -465,10 +795,80 @@ var WrongStackACPServer = class {
465
795
  }
466
796
  this.transport.close();
467
797
  }
798
+ async startHttp(port) {
799
+ const host = this.options.host ?? "127.0.0.1";
800
+ const handler = this.handler;
801
+ this.httpServer = createServer(async (req, res) => {
802
+ const selfOrigin = `http://${host}:${port}`;
803
+ const reqOrigin = Array.isArray(req.headers.origin) ? req.headers.origin[0] : req.headers.origin;
804
+ if (reqOrigin && reqOrigin !== selfOrigin) {
805
+ res.writeHead(403);
806
+ res.end(JSON.stringify({ error: "cross-origin request forbidden" }));
807
+ return;
808
+ }
809
+ if (reqOrigin) res.setHeader("Access-Control-Allow-Origin", reqOrigin);
810
+ res.setHeader("Access-Control-Allow-Methods", "POST, OPTIONS");
811
+ res.setHeader("Access-Control-Allow-Headers", "Content-Type, Mcp-Session-Id");
812
+ if (req.method === "OPTIONS") {
813
+ res.writeHead(204);
814
+ res.end();
815
+ return;
816
+ }
817
+ if (req.method !== "POST") {
818
+ res.writeHead(405);
819
+ res.end(JSON.stringify({ error: "method not allowed" }));
820
+ return;
821
+ }
822
+ let body = "";
823
+ for await (const chunk of req) {
824
+ body += chunk;
825
+ }
826
+ let msg;
827
+ try {
828
+ msg = JSON.parse(body);
829
+ } catch {
830
+ res.writeHead(400);
831
+ res.end(JSON.stringify({ error: { code: -32700, message: "Parse error" } }));
832
+ return;
833
+ }
834
+ const notifications = [];
835
+ let response = null;
836
+ const originalSend = this.transport.send.bind(this.transport);
837
+ this.transport.send = async (m) => {
838
+ if (m.id !== void 0 && (m.result !== void 0 || m.error !== void 0)) {
839
+ response = m;
840
+ } else if (m.method === "session/update") {
841
+ notifications.push(m.params);
842
+ } else {
843
+ notifications.push(m);
844
+ }
845
+ };
846
+ try {
847
+ await handler.handleMessage(msg);
848
+ } finally {
849
+ this.transport.send = originalSend;
850
+ }
851
+ res.writeHead(200, { "Content-Type": "application/json" });
852
+ const responseBody = response !== null ? { ...response, notifications } : { notifications };
853
+ res.end(JSON.stringify(responseBody));
854
+ });
855
+ return new Promise((resolve) => {
856
+ this.httpServer.listen(port, host, () => {
857
+ writeErr(`[wstack-acp] HTTP server listening on http://${host}:${port}
858
+ `);
859
+ this.running = true;
860
+ resolve();
861
+ });
862
+ });
863
+ }
468
864
  /** Stop the server. */
469
865
  stop() {
470
866
  this.running = false;
471
867
  this.transport.close();
868
+ if (this.httpServer) {
869
+ this.httpServer.close();
870
+ this.httpServer = null;
871
+ }
472
872
  }
473
873
  };
474
874
  var defaultEchoRunTurn = async (_input, _emit) => {