@wrongstack/acp 1.0.17 → 1.0.24

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.
@@ -101,7 +101,7 @@ export interface AuthMethod {
101
101
  * used by some agents but is not a registry-supported setup path.
102
102
  */
103
103
  type?: 'agent' | 'oauth' | 'http' | 'terminal' | 'env_var' | undefined;
104
- /** Extra argv for `type: 'terminal'` setup (replaces the ACP entry args). */
104
+ /** Extra argv for `type: 'terminal'` setup (appended to the configured invocation). */
105
105
  args?: string[] | undefined;
106
106
  env?: Record<string, string> | undefined;
107
107
  }
@@ -390,7 +390,7 @@ export interface AgentMessageChunkUpdate {
390
390
  content: ContentBlock;
391
391
  }
392
392
  export interface ThoughtChunkUpdate {
393
- sessionUpdate: 'thought_chunk';
393
+ sessionUpdate: 'agent_thought_chunk' | 'thought_chunk';
394
394
  messageId?: MessageId | undefined;
395
395
  content: ContentBlock;
396
396
  }
@@ -427,7 +427,8 @@ export interface AvailableCommandsUpdate {
427
427
  }
428
428
  export interface CurrentModeUpdate {
429
429
  sessionUpdate: 'current_mode_update';
430
- modeId: SessionModeId;
430
+ currentModeId: SessionModeId;
431
+ modeId?: SessionModeId;
431
432
  }
432
433
  export interface ConfigOptionUpdate {
433
434
  sessionUpdate: 'config_option_update';
@@ -2,11 +2,12 @@
2
2
  import { createServer } from "node:http";
3
3
  import { isIP } from "node:net";
4
4
  import { fileURLToPath } from "node:url";
5
- import { expandIPv6, writeErr as writeErr2 } from "@wrongstack/core/utils";
5
+ import { expandIPv6, isStandaloneBinary, writeErr as writeErr2 } from "@wrongstack/core/utils";
6
6
  import { timingSafeTokenEqual } from "@wrongstack/primitives";
7
7
 
8
8
  // src/agent/protocol-handler.ts
9
9
  import { randomUUID } from "node:crypto";
10
+ import { isAbsolute as isAbsolute2, resolve as resolve2 } from "node:path";
10
11
 
11
12
  // src/types/acp-v1.ts
12
13
  var ACP_PROTOCOL_VERSION = 1;
@@ -184,7 +185,7 @@ function createRunTurnApi(sessionId, clientCapabilities, request, sendSessionUpd
184
185
  ...sendSessionUpdate ? { sendSessionUpdate } : {}
185
186
  };
186
187
  }
187
- function buildInitializeResult(agentName, modes, configOptions) {
188
+ function buildInitializeResult(agentName, modes, configOptions, clientCapabilities = {}) {
188
189
  return {
189
190
  protocolVersion: ACP_PROTOCOL_VERSION,
190
191
  agentCapabilities: {
@@ -211,9 +212,6 @@ function buildInitializeResult(agentName, modes, configOptions) {
211
212
  delete: {},
212
213
  resume: {},
213
214
  fork: {}
214
- },
215
- auth: {
216
- logout: {}
217
215
  }
218
216
  },
219
217
  agentInfo: {
@@ -221,7 +219,7 @@ function buildInitializeResult(agentName, modes, configOptions) {
221
219
  title: "WrongStack",
222
220
  version: WRONGSTACK_VERSION
223
221
  },
224
- authMethods: WRONGSTACK_AUTH_METHODS,
222
+ authMethods: clientCapabilities.auth?.terminal === true ? WRONGSTACK_AUTH_METHODS : [],
225
223
  modes,
226
224
  configOptions
227
225
  };
@@ -251,7 +249,8 @@ async function handleSessionNewOp(ctx, id, params) {
251
249
  id: sessionId,
252
250
  cwd,
253
251
  abort: new AbortController(),
254
- modeId: DEFAULT_MODE_ID,
252
+ modeId: ctx.modes[0]?.id ?? DEFAULT_MODE_ID,
253
+ configOptions: structuredClone([...ctx.configOptions]),
255
254
  createdAt: now,
256
255
  updatedAt: now,
257
256
  ...mcpServers.length > 0 ? { mcpServers } : {}
@@ -263,6 +262,7 @@ async function handleSessionNewOp(ctx, id, params) {
263
262
  sessionId,
264
263
  update: {
265
264
  sessionUpdate: "current_mode_update",
265
+ currentModeId: ctx.modes[0]?.id ?? DEFAULT_MODE_ID,
266
266
  modeId: ctx.modes[0]?.id ?? DEFAULT_MODE_ID
267
267
  }
268
268
  });
@@ -278,12 +278,12 @@ async function handleSessionNewOp(ctx, id, params) {
278
278
  await reportSkippedMcpServers(ctx, sessionId, skipped);
279
279
  await ctx.sendResult(id, {
280
280
  sessionId,
281
- modes: ctx.modes,
282
- configOptions: ctx.configOptions
281
+ modes: { currentModeId: state.modeId, availableModes: ctx.modes },
282
+ configOptions: state.configOptions
283
283
  });
284
284
  return false;
285
285
  }
286
- async function handleSessionLoadOp(ctx, id, params) {
286
+ async function handleSessionLoadOp(ctx, id, params, replayHistory = true) {
287
287
  const p = params ?? {};
288
288
  const sessionId = typeof p.sessionId === "string" ? p.sessionId : null;
289
289
  const loadCwd = typeof p.cwd === "string" ? p.cwd : void 0;
@@ -308,6 +308,7 @@ async function handleSessionLoadOp(ctx, id, params) {
308
308
  cwd: restoredCwd,
309
309
  abort: new AbortController(),
310
310
  modeId: persisted.modeId ?? DEFAULT_MODE_ID,
311
+ configOptions: structuredClone(persisted.configOptions ?? [...ctx.configOptions]),
311
312
  createdAt: persisted.createdAt ?? (/* @__PURE__ */ new Date()).toISOString(),
312
313
  updatedAt: (/* @__PURE__ */ new Date()).toISOString(),
313
314
  ...persisted.title !== void 0 ? { title: persisted.title } : {},
@@ -315,51 +316,69 @@ async function handleSessionLoadOp(ctx, id, params) {
315
316
  };
316
317
  ctx.sessions.set(sessionId, restored);
317
318
  ctx.seedFor?.(sessionId, persisted.history ?? []);
318
- for (const update of persisted.history ?? []) {
319
+ for (const update of replayHistory ? persisted.history ?? [] : []) {
319
320
  await ctx.sendNotification({ sessionId, update });
320
321
  }
321
- await ctx.sendNotification({
322
- sessionId,
323
- update: { sessionUpdate: "current_mode_update", modeId: restored.modeId }
324
- });
322
+ if (replayHistory)
323
+ await ctx.sendNotification({
324
+ sessionId,
325
+ update: {
326
+ sessionUpdate: "current_mode_update",
327
+ currentModeId: restored.modeId,
328
+ modeId: restored.modeId
329
+ }
330
+ });
325
331
  await reportSkippedMcpServers(ctx, sessionId, loadSkipped);
326
332
  await ctx.sendResult(id, {
327
- initialMode: { currentModeId: restored.modeId, availableModes: ctx.modes }
333
+ modes: { currentModeId: restored.modeId, availableModes: ctx.modes },
334
+ configOptions: restored.configOptions
328
335
  });
329
336
  return false;
330
337
  }
331
338
  }
332
339
  if (existing) {
340
+ if (existing.prompting) {
341
+ await ctx.sendError(id, -32e3, "cannot reload a session while a prompt is running");
342
+ return false;
343
+ }
333
344
  existing.updatedAt = (/* @__PURE__ */ new Date()).toISOString();
334
- if (loadMcpServers.length > 0) {
345
+ const replay = ctx.replayFor?.(sessionId);
346
+ if (Array.isArray(p.mcpServers)) {
347
+ if (JSON.stringify(existing.mcpServers ?? []) !== JSON.stringify(loadMcpServers)) {
348
+ ctx.disposeFor?.(sessionId);
349
+ ctx.seedFor?.(sessionId, replay ?? []);
350
+ }
335
351
  existing.mcpServers = loadMcpServers;
336
352
  }
337
- const replay = ctx.replayFor?.(sessionId);
338
- if (replay) {
353
+ if (replayHistory && replay) {
339
354
  for (const update of replay) {
340
355
  await ctx.sendNotification({ sessionId, update });
341
356
  }
342
357
  }
343
- await ctx.sendNotification({
344
- sessionId,
345
- update: {
346
- sessionUpdate: "session_info_update",
347
- updatedAt: existing.updatedAt
348
- }
349
- });
350
- await ctx.sendNotification({
351
- sessionId,
352
- update: {
353
- sessionUpdate: "current_mode_update",
354
- modeId: existing.modeId
355
- }
356
- });
358
+ if (replayHistory)
359
+ await ctx.sendNotification({
360
+ sessionId,
361
+ update: {
362
+ sessionUpdate: "session_info_update",
363
+ updatedAt: existing.updatedAt
364
+ }
365
+ });
366
+ if (replayHistory)
367
+ await ctx.sendNotification({
368
+ sessionId,
369
+ update: {
370
+ sessionUpdate: "current_mode_update",
371
+ currentModeId: existing.modeId,
372
+ modeId: existing.modeId
373
+ }
374
+ });
357
375
  await reportSkippedMcpServers(ctx, sessionId, loadSkipped);
358
376
  await ctx.sendResult(id, {
359
- initialMode: {
377
+ modes: {
360
378
  currentModeId: existing.modeId,
361
379
  availableModes: ctx.modes
362
- }
380
+ },
381
+ configOptions: existing.configOptions ?? [...ctx.configOptions]
363
382
  });
364
383
  return false;
365
384
  }
@@ -397,6 +416,7 @@ async function handleSessionForkOp(ctx, id, params) {
397
416
  cwd: forkCwd,
398
417
  abort: new AbortController(),
399
418
  modeId: source.modeId,
419
+ configOptions: structuredClone(source.configOptions ?? [...ctx.configOptions]),
400
420
  createdAt: now,
401
421
  updatedAt: now,
402
422
  ...source.title !== void 0 ? { title: source.title } : {},
@@ -412,13 +432,17 @@ async function handleSessionForkOp(ctx, id, params) {
412
432
  await ctx.persist(forked, history);
413
433
  await ctx.sendNotification({
414
434
  sessionId,
415
- update: { sessionUpdate: "current_mode_update", modeId: forked.modeId }
435
+ update: {
436
+ sessionUpdate: "current_mode_update",
437
+ currentModeId: forked.modeId,
438
+ modeId: forked.modeId
439
+ }
416
440
  });
417
441
  await reportSkippedMcpServers(ctx, sessionId, forkSkipped);
418
442
  await ctx.sendResult(id, {
419
443
  sessionId,
420
- modes: ctx.modes,
421
- configOptions: ctx.configOptions
444
+ modes: { currentModeId: forked.modeId, availableModes: ctx.modes },
445
+ configOptions: forked.configOptions
422
446
  });
423
447
  return false;
424
448
  }
@@ -434,6 +458,11 @@ async function handleSessionPromptOp(ctx, id, params) {
434
458
  return false;
435
459
  }
436
460
  const session = ctx.sessions.get(sessionId);
461
+ if (session.prompting) {
462
+ await ctx.sendError(id, -32e3, "a prompt is already running for this session");
463
+ return false;
464
+ }
465
+ session.prompting = true;
437
466
  if (session.abort.signal.aborted) {
438
467
  session.abort = new AbortController();
439
468
  }
@@ -462,6 +491,8 @@ async function handleSessionPromptOp(ctx, id, params) {
462
491
  {
463
492
  sessionId,
464
493
  prompt: p.prompt,
494
+ modeId: session.modeId,
495
+ configOptions: session.configOptions,
465
496
  signal: turnSignal.signal,
466
497
  cwd: session.cwd,
467
498
  ...session.mcpServers ? { mcpServers: session.mcpServers } : {}
@@ -470,16 +501,25 @@ async function handleSessionPromptOp(ctx, id, params) {
470
501
  api
471
502
  );
472
503
  } catch (err) {
504
+ session.prompting = false;
473
505
  session.abort.signal.removeEventListener("abort", onCancel);
506
+ await Promise.all(pendingNotifications);
507
+ if (turnSignal.signal.aborted) {
508
+ await ctx.sendResult(id, { stopReason: "cancelled" });
509
+ return false;
510
+ }
474
511
  const { code, message, data } = errorToJsonRpc(err);
475
512
  await ctx.sendError(id, code, message, data);
476
513
  return false;
477
514
  }
478
515
  await Promise.all(pendingNotifications);
516
+ session.prompting = false;
479
517
  session.abort.signal.removeEventListener("abort", onCancel);
480
518
  session.updatedAt = (/* @__PURE__ */ new Date()).toISOString();
481
- await ctx.persist(session);
482
- await ctx.sendResult(id, { stopReason: result.stopReason });
519
+ if (ctx.sessions.get(sessionId) === session) await ctx.persist(session);
520
+ await ctx.sendResult(id, {
521
+ stopReason: turnSignal.signal.aborted ? "cancelled" : result.stopReason
522
+ });
483
523
  return false;
484
524
  }
485
525
  async function handleSetModeOp(ctx, id, params) {
@@ -495,7 +535,11 @@ async function handleSetModeOp(ctx, id, params) {
495
535
  session.updatedAt = (/* @__PURE__ */ new Date()).toISOString();
496
536
  await ctx.sendNotification({
497
537
  sessionId,
498
- update: { sessionUpdate: "current_mode_update", modeId }
538
+ update: {
539
+ sessionUpdate: "current_mode_update",
540
+ currentModeId: modeId,
541
+ modeId
542
+ }
499
543
  });
500
544
  await ctx.sendResult(id, {});
501
545
  return false;
@@ -506,21 +550,24 @@ async function handleSetConfigOptionOp(ctx, id, params) {
506
550
  const optionId = typeof p.configId === "string" ? p.configId : null;
507
551
  const value = typeof p.value === "string" ? p.value : null;
508
552
  const session = sessionId ? ctx.sessions.get(sessionId) : void 0;
509
- const option = optionId ? ctx.configOptions.find((o) => o.id === optionId) : void 0;
553
+ const options = session?.configOptions ?? structuredClone([...ctx.configOptions]);
554
+ const option = optionId ? options.find((o) => o.id === optionId) : void 0;
510
555
  if (!session || !option || value === null || !option.options.some((o) => o.value === value)) {
511
556
  await ctx.sendError(id, -32602, "invalid sessionId, configId, or value");
512
557
  return false;
513
558
  }
514
559
  option.currentValue = value;
560
+ session.configOptions = options;
515
561
  session.updatedAt = (/* @__PURE__ */ new Date()).toISOString();
516
562
  await ctx.sendNotification({
517
563
  sessionId,
518
564
  update: {
519
565
  sessionUpdate: "config_option_update",
520
- configOptions: [...ctx.configOptions]
566
+ configOptions: options
521
567
  }
522
568
  });
523
- await ctx.sendResult(id, { configOptions: [...ctx.configOptions] });
569
+ await ctx.persist(session);
570
+ await ctx.sendResult(id, { configOptions: options });
524
571
  return false;
525
572
  }
526
573
  async function reportSkippedMcpServers(ctx, sessionId, skipped) {
@@ -562,6 +609,8 @@ var ACPProtocolHandler = class {
562
609
  // session/request_permission). Keyed by our own `srv_N` ids.
563
610
  pendingOut = /* @__PURE__ */ new Map();
564
611
  nextOutId = 1;
612
+ promptRequests = /* @__PURE__ */ new Map();
613
+ persistenceWrites = /* @__PURE__ */ new Map();
565
614
  constructor(opts) {
566
615
  this.transport = opts.transport;
567
616
  this.defaultCwd = opts.defaultCwd;
@@ -587,20 +636,46 @@ var ACPProtocolHandler = class {
587
636
  */
588
637
  request(method, params, timeoutMs = 6e4) {
589
638
  const id = `srv_${this.nextOutId++}`;
590
- return new Promise((resolve2, reject) => {
591
- const timer = setTimeout(() => {
639
+ const sessionId = params?.sessionId;
640
+ const signal = sessionId ? this.sessions.get(sessionId)?.abort.signal : void 0;
641
+ return new Promise((resolve3, reject) => {
642
+ const cleanup = () => {
643
+ clearTimeout(timer);
644
+ signal?.removeEventListener("abort", abort);
592
645
  this.pendingOut.delete(id);
593
- reject(new Error(`${method} timed out after ${timeoutMs}ms`));
646
+ };
647
+ const fail = (error) => {
648
+ cleanup();
649
+ reject(error);
650
+ };
651
+ const abort = () => {
652
+ fail(new Error("client request cancelled"));
653
+ void this.transport.send(toWire({ jsonrpc: "2.0", method: "$/cancel_request", params: { requestId: id } })).catch(() => {
654
+ });
655
+ };
656
+ const timer = setTimeout(() => {
657
+ fail(new Error(`${method} timed out after ${timeoutMs}ms`));
594
658
  }, timeoutMs);
595
- this.pendingOut.set(id, { resolve: resolve2, reject, timer });
659
+ if (signal?.aborted) {
660
+ fail(new Error("client request cancelled"));
661
+ return;
662
+ }
663
+ signal?.addEventListener("abort", abort, { once: true });
664
+ this.pendingOut.set(id, {
665
+ resolve: (value) => {
666
+ cleanup();
667
+ resolve3(value);
668
+ },
669
+ reject: fail,
670
+ timer
671
+ });
596
672
  this.transport.send(toWire({ jsonrpc: "2.0", id, method, params })).catch((e) => {
597
- clearTimeout(timer);
598
- this.pendingOut.delete(id);
599
- reject(e instanceof Error ? e : new Error(String(e)));
673
+ fail(e instanceof Error ? e : new Error(String(e)));
600
674
  });
601
675
  });
602
676
  }
603
677
  maybeResolvePending(m) {
678
+ if (m.result === void 0 && m.error === void 0) return;
604
679
  const id = m.id;
605
680
  if (typeof id !== "string") return;
606
681
  const pending = this.pendingOut.get(id);
@@ -620,6 +695,7 @@ var ACPProtocolHandler = class {
620
695
  if (typeof msg !== "object" || msg === null) return false;
621
696
  const m = msg;
622
697
  if (m.id !== void 0 && (m.result !== void 0 || m.error !== void 0)) {
698
+ this.maybeResolvePending(msg);
623
699
  return false;
624
700
  }
625
701
  if (m.id !== void 0 && typeof m.method === "string") {
@@ -659,6 +735,7 @@ var ACPProtocolHandler = class {
659
735
  store: this.store,
660
736
  replayFor: this.replayFor,
661
737
  seedFor: this.seedFor,
738
+ disposeFor: this.disposeFor,
662
739
  onSessionNew: this.onSessionNew,
663
740
  allocId: () => this.allocId(),
664
741
  persist: (state, history) => this.persist(state, history),
@@ -696,14 +773,23 @@ var ACPProtocolHandler = class {
696
773
  return await this.handleSessionClose(id, params);
697
774
  case "session/delete":
698
775
  return await this.handleSessionDelete(id, params);
699
- case "session/prompt":
700
- return await handleSessionPromptOp(this.sessionContext(), id, params);
776
+ case "session/prompt": {
777
+ const sessionId = params?.sessionId;
778
+ if (typeof sessionId === "string" && !this.sessions.get(sessionId)?.prompting) {
779
+ this.promptRequests.set(id, sessionId);
780
+ }
781
+ try {
782
+ return await handleSessionPromptOp(this.sessionContext(), id, params);
783
+ } finally {
784
+ this.promptRequests.delete(id);
785
+ }
786
+ }
701
787
  case "session/set_mode":
702
788
  return await handleSetModeOp(this.sessionContext(), id, params);
703
789
  case "session/set_config_option":
704
790
  return await handleSetConfigOptionOp(this.sessionContext(), id, params);
705
791
  case "session/list":
706
- return await this.handleSessionList(id);
792
+ return await this.handleSessionList(id, params);
707
793
  case "session/fork":
708
794
  return await handleSessionForkOp(this.sessionContext(), id, params);
709
795
  case "providers/list":
@@ -732,7 +818,12 @@ var ACPProtocolHandler = class {
732
818
  this.initialized = true;
733
819
  await this.sendResult(
734
820
  id,
735
- buildInitializeResult(this.agentName, this.modes, this.configOptions)
821
+ buildInitializeResult(
822
+ this.agentName,
823
+ this.modes,
824
+ this.configOptions,
825
+ this.clientCapabilities
826
+ )
736
827
  );
737
828
  return false;
738
829
  }
@@ -741,29 +832,15 @@ var ACPProtocolHandler = class {
741
832
  return false;
742
833
  }
743
834
  async handleLogout(id, _params) {
744
- await this.sendResult(id, {});
835
+ await this.sendError(
836
+ id,
837
+ -32601,
838
+ "logout is not supported; manage provider credentials with wstack auth"
839
+ );
745
840
  return false;
746
841
  }
747
842
  async handleSessionResume(id, params) {
748
- const p = params ?? {};
749
- const sessionId = typeof p.sessionId === "string" ? p.sessionId : null;
750
- const existing = sessionId ? this.sessions.get(sessionId) : void 0;
751
- if (existing) {
752
- existing.updatedAt = (/* @__PURE__ */ new Date()).toISOString();
753
- const resumeServers = parseMcpServers(p.mcpServers);
754
- if (resumeServers.length > 0) {
755
- existing.mcpServers = resumeServers;
756
- }
757
- await this.sendResult(id, {
758
- initialMode: {
759
- currentModeId: existing.modeId,
760
- availableModes: this.modes
761
- }
762
- });
763
- return false;
764
- }
765
- await this.sendError(id, -32e3, `session not found: ${sessionId}`);
766
- return false;
843
+ return handleSessionLoadOp(this.sessionContext(), id, params, false);
767
844
  }
768
845
  async handleSessionClose(id, params) {
769
846
  const p = params ?? {};
@@ -786,14 +863,12 @@ var ACPProtocolHandler = class {
786
863
  await this.sendError(id, -32e3, `session not found: ${sessionId}`);
787
864
  return false;
788
865
  }
789
- if (!this.sessions.has(sessionId)) {
790
- await this.sendResult(id, { configOptions: [...this.configOptions] });
791
- return false;
792
- }
793
866
  const session = this.sessions.get(sessionId);
794
- session.abort.abort();
867
+ session?.abort.abort();
795
868
  this.sessions.delete(sessionId);
796
869
  this.disposeSession(sessionId);
870
+ await this.persistenceWrites.get(sessionId);
871
+ await this.store?.delete?.(sessionId);
797
872
  await this.sendResult(id, {});
798
873
  return false;
799
874
  }
@@ -820,12 +895,23 @@ var ACPProtocolHandler = class {
820
895
  await this.sendError(id, -32e3, "MCP message routing not available through ACP");
821
896
  return false;
822
897
  }
823
- async handleSessionList(id) {
824
- const sessions = Array.from(this.sessions.values()).map((s) => {
898
+ async handleSessionList(id, params) {
899
+ const p = params ?? {};
900
+ if (p.cwd != null && (typeof p.cwd !== "string" || !isAbsolute2(p.cwd)) || p.cursor != null) {
901
+ await this.sendError(id, -32602, "invalid cwd or cursor");
902
+ return false;
903
+ }
904
+ const known = new Map(this.sessions);
905
+ for (const entry of await this.store?.list?.() ?? []) {
906
+ if (known.has(entry.id)) continue;
907
+ const saved = await this.store?.load(entry.id);
908
+ if (saved?.cwd) known.set(entry.id, { ...saved, id: entry.id });
909
+ }
910
+ const sessions = Array.from(known.values()).filter((s) => typeof p.cwd !== "string" || s.cwd && resolve2(s.cwd) === resolve2(p.cwd)).map((s) => {
825
911
  const out = {
826
912
  sessionId: s.id,
827
913
  cwd: s.cwd,
828
- updatedAt: s.updatedAt
914
+ updatedAt: s.updatedAt ?? ""
829
915
  };
830
916
  if (s.title !== void 0) out.title = s.title;
831
917
  return out;
@@ -848,6 +934,11 @@ var ACPProtocolHandler = class {
848
934
  return false;
849
935
  }
850
936
  case "$/cancel_request": {
937
+ const requestId = params?.requestId;
938
+ if (typeof requestId === "string" || typeof requestId === "number") {
939
+ const sessionId = this.promptRequests.get(requestId);
940
+ if (sessionId) this.sessions.get(sessionId)?.abort.abort();
941
+ }
851
942
  return false;
852
943
  }
853
944
  case "exit":
@@ -868,10 +959,18 @@ var ACPProtocolHandler = class {
868
959
  }
869
960
  async persist(state, history = void 0) {
870
961
  if (!this.store) return;
871
- try {
872
- await this.store.save(state, history ?? this.replayFor?.(state.id));
873
- } catch {
874
- }
962
+ const store = this.store;
963
+ const replay = history ?? this.replayFor?.(state.id);
964
+ const previous = this.persistenceWrites.get(state.id) ?? Promise.resolve();
965
+ const write = previous.then(() => store.save(state, replay)).then(
966
+ () => {
967
+ },
968
+ () => {
969
+ }
970
+ );
971
+ this.persistenceWrites.set(state.id, write);
972
+ await write;
973
+ if (this.persistenceWrites.get(state.id) === write) this.persistenceWrites.delete(state.id);
875
974
  }
876
975
  async sendError(id, code, message, data) {
877
976
  const error = { code, message };
@@ -924,9 +1023,9 @@ var StdioTransport = class {
924
1023
  }
925
1024
  send(msg) {
926
1025
  if (this.closed) return Promise.resolve();
927
- return new Promise((resolve2) => {
1026
+ return new Promise((resolve3) => {
928
1027
  const line = JSON.stringify(msg) + "\n";
929
- this.stdout.write(line, "utf8", () => resolve2());
1028
+ this.stdout.write(line, "utf8", () => resolve3());
930
1029
  });
931
1030
  }
932
1031
  sendRaw(chunk) {
@@ -939,8 +1038,8 @@ var StdioTransport = class {
939
1038
  return Promise.resolve(queued.message);
940
1039
  }
941
1040
  if (this.closed) return Promise.resolve(null);
942
- return new Promise((resolve2) => {
943
- this.resolveRead = resolve2;
1041
+ return new Promise((resolve3) => {
1042
+ this.resolveRead = resolve3;
944
1043
  });
945
1044
  }
946
1045
  onMessage(handler) {
@@ -1009,9 +1108,9 @@ var StdioTransport = class {
1009
1108
  }
1010
1109
  dispatch(msg, chars = JSON.stringify(msg).length) {
1011
1110
  if (this.resolveRead) {
1012
- const resolve2 = this.resolveRead;
1111
+ const resolve3 = this.resolveRead;
1013
1112
  this.resolveRead = null;
1014
- resolve2(msg);
1113
+ resolve3(msg);
1015
1114
  } else {
1016
1115
  let claimed = false;
1017
1116
  for (const handler of this.claimHandlers) {
@@ -1100,15 +1199,30 @@ var WrongStackACPServer = class {
1100
1199
  this.transport.sendStartupMarker();
1101
1200
  }
1102
1201
  this.running = true;
1202
+ const prompts = /* @__PURE__ */ new Set();
1103
1203
  try {
1104
1204
  while (this.running) {
1105
1205
  const msg = await this.transport.read();
1106
1206
  if (!msg) break;
1207
+ if (msg.method === "session/prompt") {
1208
+ const task = this.handler.handleMessage(msg).then(
1209
+ () => {
1210
+ },
1211
+ (err) => {
1212
+ writeErr2(`[wstack-acp] prompt dispatch failed: ${String(err)}
1213
+ `);
1214
+ }
1215
+ );
1216
+ prompts.add(task);
1217
+ void task.finally(() => prompts.delete(task));
1218
+ continue;
1219
+ }
1107
1220
  const terminal = await this.handler.handleMessage(msg);
1108
1221
  if (terminal) break;
1109
1222
  }
1110
1223
  } finally {
1111
1224
  this.handler.close();
1225
+ await Promise.allSettled(prompts);
1112
1226
  this.transport.close();
1113
1227
  }
1114
1228
  }
@@ -1242,10 +1356,10 @@ var WrongStackACPServer = class {
1242
1356
  listenWithRetry(port, host, attempt = 0) {
1243
1357
  const server = this.httpServer;
1244
1358
  if (!server) return Promise.resolve();
1245
- return new Promise((resolve2, reject) => {
1359
+ return new Promise((resolve3, reject) => {
1246
1360
  const onListening = () => {
1247
1361
  server.removeListener("error", onError);
1248
- resolve2();
1362
+ resolve3();
1249
1363
  };
1250
1364
  const onError = (err) => {
1251
1365
  server.removeListener("listening", onListening);
@@ -1253,7 +1367,7 @@ var WrongStackACPServer = class {
1253
1367
  if (port === 0 && transient && attempt < LISTEN_RETRY_LIMIT) {
1254
1368
  const timer = setTimeout(
1255
1369
  () => {
1256
- this.listenWithRetry(port, host, attempt + 1).then(resolve2, reject);
1370
+ this.listenWithRetry(port, host, attempt + 1).then(resolve3, reject);
1257
1371
  },
1258
1372
  LISTEN_RETRY_BASE_MS * (attempt + 1)
1259
1373
  );
@@ -1281,8 +1395,8 @@ var WrongStackACPServer = class {
1281
1395
  if (!server) return;
1282
1396
  for (const socket of this.httpSockets) socket.destroy();
1283
1397
  this.httpSockets.clear();
1284
- await new Promise((resolve2) => {
1285
- server.close(() => resolve2());
1398
+ await new Promise((resolve3) => {
1399
+ server.close(() => resolve3());
1286
1400
  });
1287
1401
  }
1288
1402
  };
@@ -1319,7 +1433,7 @@ async function main() {
1319
1433
  const server = new WrongStackACPServer();
1320
1434
  await server.start();
1321
1435
  }
1322
- var isEntrypoint = process.argv[1] !== void 0 && fileURLToPath(import.meta.url) === process.argv[1];
1436
+ var isEntrypoint = process.argv[1] !== void 0 && !isStandaloneBinary() && fileURLToPath(import.meta.url) === process.argv[1];
1323
1437
  if (isEntrypoint) {
1324
1438
  main().catch((err) => {
1325
1439
  writeErr2(`[wstack-acp fatal] ${err}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@wrongstack/acp",
3
- "version": "1.0.17",
3
+ "version": "1.0.24",
4
4
  "license": "MIT",
5
5
  "description": "ACP (Agent Client Protocol) integration for WrongStack — client + agent support",
6
6
  "keywords": [
@@ -52,8 +52,8 @@
52
52
  ],
53
53
  "dependencies": {
54
54
  "@agentclientprotocol/sdk": "^1.4.0",
55
- "@wrongstack/core": "1.0.17",
56
- "@wrongstack/primitives": "1.0.17"
55
+ "@wrongstack/core": "1.0.24",
56
+ "@wrongstack/primitives": "1.0.24"
57
57
  },
58
58
  "devDependencies": {
59
59
  "@types/node": "^26.5.1",