@wrongstack/acp 1.0.19 → 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.
- package/dist/agent/protocol-contract.d.ts +13 -0
- package/dist/agent/protocol-handler.d.ts +2 -0
- package/dist/agent/protocol-session-management.d.ts +2 -1
- package/dist/agent/protocol-session-ops.d.ts +1 -4
- package/dist/agent.js +223 -101
- package/dist/client/acp-late-session.d.ts +4 -0
- package/dist/client/acp-request-state.d.ts +9 -0
- package/dist/client/file-server.d.ts +2 -0
- package/dist/client.js +130 -65
- package/dist/index.js +363 -184
- package/dist/types/acp-v1.d.ts +4 -3
- package/dist/wrongstack-acp-agent.js +213 -99
- package/package.json +3 -3
package/dist/agent.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
// src/agent/protocol-handler.ts
|
|
2
2
|
import { randomUUID } from "node:crypto";
|
|
3
|
+
import { isAbsolute as isAbsolute2, resolve as resolve2 } from "node:path";
|
|
3
4
|
|
|
4
5
|
// src/types/acp-v1.ts
|
|
5
6
|
var ACP_PROTOCOL_VERSION = 1;
|
|
@@ -177,7 +178,7 @@ function createRunTurnApi(sessionId, clientCapabilities, request, sendSessionUpd
|
|
|
177
178
|
...sendSessionUpdate ? { sendSessionUpdate } : {}
|
|
178
179
|
};
|
|
179
180
|
}
|
|
180
|
-
function buildInitializeResult(agentName, modes, configOptions) {
|
|
181
|
+
function buildInitializeResult(agentName, modes, configOptions, clientCapabilities = {}) {
|
|
181
182
|
return {
|
|
182
183
|
protocolVersion: ACP_PROTOCOL_VERSION,
|
|
183
184
|
agentCapabilities: {
|
|
@@ -204,9 +205,6 @@ function buildInitializeResult(agentName, modes, configOptions) {
|
|
|
204
205
|
delete: {},
|
|
205
206
|
resume: {},
|
|
206
207
|
fork: {}
|
|
207
|
-
},
|
|
208
|
-
auth: {
|
|
209
|
-
logout: {}
|
|
210
208
|
}
|
|
211
209
|
},
|
|
212
210
|
agentInfo: {
|
|
@@ -214,7 +212,7 @@ function buildInitializeResult(agentName, modes, configOptions) {
|
|
|
214
212
|
title: "WrongStack",
|
|
215
213
|
version: WRONGSTACK_VERSION
|
|
216
214
|
},
|
|
217
|
-
authMethods: WRONGSTACK_AUTH_METHODS,
|
|
215
|
+
authMethods: clientCapabilities.auth?.terminal === true ? WRONGSTACK_AUTH_METHODS : [],
|
|
218
216
|
modes,
|
|
219
217
|
configOptions
|
|
220
218
|
};
|
|
@@ -244,7 +242,8 @@ async function handleSessionNewOp(ctx, id, params) {
|
|
|
244
242
|
id: sessionId,
|
|
245
243
|
cwd,
|
|
246
244
|
abort: new AbortController(),
|
|
247
|
-
modeId: DEFAULT_MODE_ID,
|
|
245
|
+
modeId: ctx.modes[0]?.id ?? DEFAULT_MODE_ID,
|
|
246
|
+
configOptions: structuredClone([...ctx.configOptions]),
|
|
248
247
|
createdAt: now,
|
|
249
248
|
updatedAt: now,
|
|
250
249
|
...mcpServers.length > 0 ? { mcpServers } : {}
|
|
@@ -256,6 +255,7 @@ async function handleSessionNewOp(ctx, id, params) {
|
|
|
256
255
|
sessionId,
|
|
257
256
|
update: {
|
|
258
257
|
sessionUpdate: "current_mode_update",
|
|
258
|
+
currentModeId: ctx.modes[0]?.id ?? DEFAULT_MODE_ID,
|
|
259
259
|
modeId: ctx.modes[0]?.id ?? DEFAULT_MODE_ID
|
|
260
260
|
}
|
|
261
261
|
});
|
|
@@ -271,12 +271,12 @@ async function handleSessionNewOp(ctx, id, params) {
|
|
|
271
271
|
await reportSkippedMcpServers(ctx, sessionId, skipped);
|
|
272
272
|
await ctx.sendResult(id, {
|
|
273
273
|
sessionId,
|
|
274
|
-
modes: ctx.modes,
|
|
275
|
-
configOptions:
|
|
274
|
+
modes: { currentModeId: state.modeId, availableModes: ctx.modes },
|
|
275
|
+
configOptions: state.configOptions
|
|
276
276
|
});
|
|
277
277
|
return false;
|
|
278
278
|
}
|
|
279
|
-
async function handleSessionLoadOp(ctx, id, params) {
|
|
279
|
+
async function handleSessionLoadOp(ctx, id, params, replayHistory = true) {
|
|
280
280
|
const p = params ?? {};
|
|
281
281
|
const sessionId = typeof p.sessionId === "string" ? p.sessionId : null;
|
|
282
282
|
const loadCwd = typeof p.cwd === "string" ? p.cwd : void 0;
|
|
@@ -301,6 +301,7 @@ async function handleSessionLoadOp(ctx, id, params) {
|
|
|
301
301
|
cwd: restoredCwd,
|
|
302
302
|
abort: new AbortController(),
|
|
303
303
|
modeId: persisted.modeId ?? DEFAULT_MODE_ID,
|
|
304
|
+
configOptions: structuredClone(persisted.configOptions ?? [...ctx.configOptions]),
|
|
304
305
|
createdAt: persisted.createdAt ?? (/* @__PURE__ */ new Date()).toISOString(),
|
|
305
306
|
updatedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
306
307
|
...persisted.title !== void 0 ? { title: persisted.title } : {},
|
|
@@ -308,51 +309,69 @@ async function handleSessionLoadOp(ctx, id, params) {
|
|
|
308
309
|
};
|
|
309
310
|
ctx.sessions.set(sessionId, restored);
|
|
310
311
|
ctx.seedFor?.(sessionId, persisted.history ?? []);
|
|
311
|
-
for (const update of persisted.history ?? []) {
|
|
312
|
+
for (const update of replayHistory ? persisted.history ?? [] : []) {
|
|
312
313
|
await ctx.sendNotification({ sessionId, update });
|
|
313
314
|
}
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
315
|
+
if (replayHistory)
|
|
316
|
+
await ctx.sendNotification({
|
|
317
|
+
sessionId,
|
|
318
|
+
update: {
|
|
319
|
+
sessionUpdate: "current_mode_update",
|
|
320
|
+
currentModeId: restored.modeId,
|
|
321
|
+
modeId: restored.modeId
|
|
322
|
+
}
|
|
323
|
+
});
|
|
318
324
|
await reportSkippedMcpServers(ctx, sessionId, loadSkipped);
|
|
319
325
|
await ctx.sendResult(id, {
|
|
320
|
-
|
|
326
|
+
modes: { currentModeId: restored.modeId, availableModes: ctx.modes },
|
|
327
|
+
configOptions: restored.configOptions
|
|
321
328
|
});
|
|
322
329
|
return false;
|
|
323
330
|
}
|
|
324
331
|
}
|
|
325
332
|
if (existing) {
|
|
333
|
+
if (existing.prompting) {
|
|
334
|
+
await ctx.sendError(id, -32e3, "cannot reload a session while a prompt is running");
|
|
335
|
+
return false;
|
|
336
|
+
}
|
|
326
337
|
existing.updatedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
327
|
-
|
|
338
|
+
const replay = ctx.replayFor?.(sessionId);
|
|
339
|
+
if (Array.isArray(p.mcpServers)) {
|
|
340
|
+
if (JSON.stringify(existing.mcpServers ?? []) !== JSON.stringify(loadMcpServers)) {
|
|
341
|
+
ctx.disposeFor?.(sessionId);
|
|
342
|
+
ctx.seedFor?.(sessionId, replay ?? []);
|
|
343
|
+
}
|
|
328
344
|
existing.mcpServers = loadMcpServers;
|
|
329
345
|
}
|
|
330
|
-
|
|
331
|
-
if (replay) {
|
|
346
|
+
if (replayHistory && replay) {
|
|
332
347
|
for (const update of replay) {
|
|
333
348
|
await ctx.sendNotification({ sessionId, update });
|
|
334
349
|
}
|
|
335
350
|
}
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
351
|
+
if (replayHistory)
|
|
352
|
+
await ctx.sendNotification({
|
|
353
|
+
sessionId,
|
|
354
|
+
update: {
|
|
355
|
+
sessionUpdate: "session_info_update",
|
|
356
|
+
updatedAt: existing.updatedAt
|
|
357
|
+
}
|
|
358
|
+
});
|
|
359
|
+
if (replayHistory)
|
|
360
|
+
await ctx.sendNotification({
|
|
361
|
+
sessionId,
|
|
362
|
+
update: {
|
|
363
|
+
sessionUpdate: "current_mode_update",
|
|
364
|
+
currentModeId: existing.modeId,
|
|
365
|
+
modeId: existing.modeId
|
|
366
|
+
}
|
|
367
|
+
});
|
|
350
368
|
await reportSkippedMcpServers(ctx, sessionId, loadSkipped);
|
|
351
369
|
await ctx.sendResult(id, {
|
|
352
|
-
|
|
370
|
+
modes: {
|
|
353
371
|
currentModeId: existing.modeId,
|
|
354
372
|
availableModes: ctx.modes
|
|
355
|
-
}
|
|
373
|
+
},
|
|
374
|
+
configOptions: existing.configOptions ?? [...ctx.configOptions]
|
|
356
375
|
});
|
|
357
376
|
return false;
|
|
358
377
|
}
|
|
@@ -390,6 +409,7 @@ async function handleSessionForkOp(ctx, id, params) {
|
|
|
390
409
|
cwd: forkCwd,
|
|
391
410
|
abort: new AbortController(),
|
|
392
411
|
modeId: source.modeId,
|
|
412
|
+
configOptions: structuredClone(source.configOptions ?? [...ctx.configOptions]),
|
|
393
413
|
createdAt: now,
|
|
394
414
|
updatedAt: now,
|
|
395
415
|
...source.title !== void 0 ? { title: source.title } : {},
|
|
@@ -405,13 +425,17 @@ async function handleSessionForkOp(ctx, id, params) {
|
|
|
405
425
|
await ctx.persist(forked, history);
|
|
406
426
|
await ctx.sendNotification({
|
|
407
427
|
sessionId,
|
|
408
|
-
update: {
|
|
428
|
+
update: {
|
|
429
|
+
sessionUpdate: "current_mode_update",
|
|
430
|
+
currentModeId: forked.modeId,
|
|
431
|
+
modeId: forked.modeId
|
|
432
|
+
}
|
|
409
433
|
});
|
|
410
434
|
await reportSkippedMcpServers(ctx, sessionId, forkSkipped);
|
|
411
435
|
await ctx.sendResult(id, {
|
|
412
436
|
sessionId,
|
|
413
|
-
modes: ctx.modes,
|
|
414
|
-
configOptions:
|
|
437
|
+
modes: { currentModeId: forked.modeId, availableModes: ctx.modes },
|
|
438
|
+
configOptions: forked.configOptions
|
|
415
439
|
});
|
|
416
440
|
return false;
|
|
417
441
|
}
|
|
@@ -427,6 +451,11 @@ async function handleSessionPromptOp(ctx, id, params) {
|
|
|
427
451
|
return false;
|
|
428
452
|
}
|
|
429
453
|
const session = ctx.sessions.get(sessionId);
|
|
454
|
+
if (session.prompting) {
|
|
455
|
+
await ctx.sendError(id, -32e3, "a prompt is already running for this session");
|
|
456
|
+
return false;
|
|
457
|
+
}
|
|
458
|
+
session.prompting = true;
|
|
430
459
|
if (session.abort.signal.aborted) {
|
|
431
460
|
session.abort = new AbortController();
|
|
432
461
|
}
|
|
@@ -455,6 +484,8 @@ async function handleSessionPromptOp(ctx, id, params) {
|
|
|
455
484
|
{
|
|
456
485
|
sessionId,
|
|
457
486
|
prompt: p.prompt,
|
|
487
|
+
modeId: session.modeId,
|
|
488
|
+
configOptions: session.configOptions,
|
|
458
489
|
signal: turnSignal.signal,
|
|
459
490
|
cwd: session.cwd,
|
|
460
491
|
...session.mcpServers ? { mcpServers: session.mcpServers } : {}
|
|
@@ -463,16 +494,25 @@ async function handleSessionPromptOp(ctx, id, params) {
|
|
|
463
494
|
api
|
|
464
495
|
);
|
|
465
496
|
} catch (err) {
|
|
497
|
+
session.prompting = false;
|
|
466
498
|
session.abort.signal.removeEventListener("abort", onCancel);
|
|
499
|
+
await Promise.all(pendingNotifications);
|
|
500
|
+
if (turnSignal.signal.aborted) {
|
|
501
|
+
await ctx.sendResult(id, { stopReason: "cancelled" });
|
|
502
|
+
return false;
|
|
503
|
+
}
|
|
467
504
|
const { code, message, data } = errorToJsonRpc(err);
|
|
468
505
|
await ctx.sendError(id, code, message, data);
|
|
469
506
|
return false;
|
|
470
507
|
}
|
|
471
508
|
await Promise.all(pendingNotifications);
|
|
509
|
+
session.prompting = false;
|
|
472
510
|
session.abort.signal.removeEventListener("abort", onCancel);
|
|
473
511
|
session.updatedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
474
|
-
await ctx.persist(session);
|
|
475
|
-
await ctx.sendResult(id, {
|
|
512
|
+
if (ctx.sessions.get(sessionId) === session) await ctx.persist(session);
|
|
513
|
+
await ctx.sendResult(id, {
|
|
514
|
+
stopReason: turnSignal.signal.aborted ? "cancelled" : result.stopReason
|
|
515
|
+
});
|
|
476
516
|
return false;
|
|
477
517
|
}
|
|
478
518
|
async function handleSetModeOp(ctx, id, params) {
|
|
@@ -488,7 +528,11 @@ async function handleSetModeOp(ctx, id, params) {
|
|
|
488
528
|
session.updatedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
489
529
|
await ctx.sendNotification({
|
|
490
530
|
sessionId,
|
|
491
|
-
update: {
|
|
531
|
+
update: {
|
|
532
|
+
sessionUpdate: "current_mode_update",
|
|
533
|
+
currentModeId: modeId,
|
|
534
|
+
modeId
|
|
535
|
+
}
|
|
492
536
|
});
|
|
493
537
|
await ctx.sendResult(id, {});
|
|
494
538
|
return false;
|
|
@@ -499,21 +543,24 @@ async function handleSetConfigOptionOp(ctx, id, params) {
|
|
|
499
543
|
const optionId = typeof p.configId === "string" ? p.configId : null;
|
|
500
544
|
const value = typeof p.value === "string" ? p.value : null;
|
|
501
545
|
const session = sessionId ? ctx.sessions.get(sessionId) : void 0;
|
|
502
|
-
const
|
|
546
|
+
const options = session?.configOptions ?? structuredClone([...ctx.configOptions]);
|
|
547
|
+
const option = optionId ? options.find((o) => o.id === optionId) : void 0;
|
|
503
548
|
if (!session || !option || value === null || !option.options.some((o) => o.value === value)) {
|
|
504
549
|
await ctx.sendError(id, -32602, "invalid sessionId, configId, or value");
|
|
505
550
|
return false;
|
|
506
551
|
}
|
|
507
552
|
option.currentValue = value;
|
|
553
|
+
session.configOptions = options;
|
|
508
554
|
session.updatedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
509
555
|
await ctx.sendNotification({
|
|
510
556
|
sessionId,
|
|
511
557
|
update: {
|
|
512
558
|
sessionUpdate: "config_option_update",
|
|
513
|
-
configOptions:
|
|
559
|
+
configOptions: options
|
|
514
560
|
}
|
|
515
561
|
});
|
|
516
|
-
await ctx.
|
|
562
|
+
await ctx.persist(session);
|
|
563
|
+
await ctx.sendResult(id, { configOptions: options });
|
|
517
564
|
return false;
|
|
518
565
|
}
|
|
519
566
|
async function reportSkippedMcpServers(ctx, sessionId, skipped) {
|
|
@@ -555,6 +602,8 @@ var ACPProtocolHandler = class {
|
|
|
555
602
|
// session/request_permission). Keyed by our own `srv_N` ids.
|
|
556
603
|
pendingOut = /* @__PURE__ */ new Map();
|
|
557
604
|
nextOutId = 1;
|
|
605
|
+
promptRequests = /* @__PURE__ */ new Map();
|
|
606
|
+
persistenceWrites = /* @__PURE__ */ new Map();
|
|
558
607
|
constructor(opts) {
|
|
559
608
|
this.transport = opts.transport;
|
|
560
609
|
this.defaultCwd = opts.defaultCwd;
|
|
@@ -580,20 +629,46 @@ var ACPProtocolHandler = class {
|
|
|
580
629
|
*/
|
|
581
630
|
request(method, params, timeoutMs = 6e4) {
|
|
582
631
|
const id = `srv_${this.nextOutId++}`;
|
|
583
|
-
|
|
584
|
-
|
|
632
|
+
const sessionId = params?.sessionId;
|
|
633
|
+
const signal = sessionId ? this.sessions.get(sessionId)?.abort.signal : void 0;
|
|
634
|
+
return new Promise((resolve3, reject) => {
|
|
635
|
+
const cleanup = () => {
|
|
636
|
+
clearTimeout(timer);
|
|
637
|
+
signal?.removeEventListener("abort", abort);
|
|
585
638
|
this.pendingOut.delete(id);
|
|
586
|
-
|
|
639
|
+
};
|
|
640
|
+
const fail = (error) => {
|
|
641
|
+
cleanup();
|
|
642
|
+
reject(error);
|
|
643
|
+
};
|
|
644
|
+
const abort = () => {
|
|
645
|
+
fail(new Error("client request cancelled"));
|
|
646
|
+
void this.transport.send(toWire({ jsonrpc: "2.0", method: "$/cancel_request", params: { requestId: id } })).catch(() => {
|
|
647
|
+
});
|
|
648
|
+
};
|
|
649
|
+
const timer = setTimeout(() => {
|
|
650
|
+
fail(new Error(`${method} timed out after ${timeoutMs}ms`));
|
|
587
651
|
}, timeoutMs);
|
|
588
|
-
|
|
652
|
+
if (signal?.aborted) {
|
|
653
|
+
fail(new Error("client request cancelled"));
|
|
654
|
+
return;
|
|
655
|
+
}
|
|
656
|
+
signal?.addEventListener("abort", abort, { once: true });
|
|
657
|
+
this.pendingOut.set(id, {
|
|
658
|
+
resolve: (value) => {
|
|
659
|
+
cleanup();
|
|
660
|
+
resolve3(value);
|
|
661
|
+
},
|
|
662
|
+
reject: fail,
|
|
663
|
+
timer
|
|
664
|
+
});
|
|
589
665
|
this.transport.send(toWire({ jsonrpc: "2.0", id, method, params })).catch((e) => {
|
|
590
|
-
|
|
591
|
-
this.pendingOut.delete(id);
|
|
592
|
-
reject(e instanceof Error ? e : new Error(String(e)));
|
|
666
|
+
fail(e instanceof Error ? e : new Error(String(e)));
|
|
593
667
|
});
|
|
594
668
|
});
|
|
595
669
|
}
|
|
596
670
|
maybeResolvePending(m) {
|
|
671
|
+
if (m.result === void 0 && m.error === void 0) return;
|
|
597
672
|
const id = m.id;
|
|
598
673
|
if (typeof id !== "string") return;
|
|
599
674
|
const pending = this.pendingOut.get(id);
|
|
@@ -613,6 +688,7 @@ var ACPProtocolHandler = class {
|
|
|
613
688
|
if (typeof msg !== "object" || msg === null) return false;
|
|
614
689
|
const m = msg;
|
|
615
690
|
if (m.id !== void 0 && (m.result !== void 0 || m.error !== void 0)) {
|
|
691
|
+
this.maybeResolvePending(msg);
|
|
616
692
|
return false;
|
|
617
693
|
}
|
|
618
694
|
if (m.id !== void 0 && typeof m.method === "string") {
|
|
@@ -652,6 +728,7 @@ var ACPProtocolHandler = class {
|
|
|
652
728
|
store: this.store,
|
|
653
729
|
replayFor: this.replayFor,
|
|
654
730
|
seedFor: this.seedFor,
|
|
731
|
+
disposeFor: this.disposeFor,
|
|
655
732
|
onSessionNew: this.onSessionNew,
|
|
656
733
|
allocId: () => this.allocId(),
|
|
657
734
|
persist: (state, history) => this.persist(state, history),
|
|
@@ -689,14 +766,23 @@ var ACPProtocolHandler = class {
|
|
|
689
766
|
return await this.handleSessionClose(id, params);
|
|
690
767
|
case "session/delete":
|
|
691
768
|
return await this.handleSessionDelete(id, params);
|
|
692
|
-
case "session/prompt":
|
|
693
|
-
|
|
769
|
+
case "session/prompt": {
|
|
770
|
+
const sessionId = params?.sessionId;
|
|
771
|
+
if (typeof sessionId === "string" && !this.sessions.get(sessionId)?.prompting) {
|
|
772
|
+
this.promptRequests.set(id, sessionId);
|
|
773
|
+
}
|
|
774
|
+
try {
|
|
775
|
+
return await handleSessionPromptOp(this.sessionContext(), id, params);
|
|
776
|
+
} finally {
|
|
777
|
+
this.promptRequests.delete(id);
|
|
778
|
+
}
|
|
779
|
+
}
|
|
694
780
|
case "session/set_mode":
|
|
695
781
|
return await handleSetModeOp(this.sessionContext(), id, params);
|
|
696
782
|
case "session/set_config_option":
|
|
697
783
|
return await handleSetConfigOptionOp(this.sessionContext(), id, params);
|
|
698
784
|
case "session/list":
|
|
699
|
-
return await this.handleSessionList(id);
|
|
785
|
+
return await this.handleSessionList(id, params);
|
|
700
786
|
case "session/fork":
|
|
701
787
|
return await handleSessionForkOp(this.sessionContext(), id, params);
|
|
702
788
|
case "providers/list":
|
|
@@ -725,7 +811,12 @@ var ACPProtocolHandler = class {
|
|
|
725
811
|
this.initialized = true;
|
|
726
812
|
await this.sendResult(
|
|
727
813
|
id,
|
|
728
|
-
buildInitializeResult(
|
|
814
|
+
buildInitializeResult(
|
|
815
|
+
this.agentName,
|
|
816
|
+
this.modes,
|
|
817
|
+
this.configOptions,
|
|
818
|
+
this.clientCapabilities
|
|
819
|
+
)
|
|
729
820
|
);
|
|
730
821
|
return false;
|
|
731
822
|
}
|
|
@@ -734,29 +825,15 @@ var ACPProtocolHandler = class {
|
|
|
734
825
|
return false;
|
|
735
826
|
}
|
|
736
827
|
async handleLogout(id, _params) {
|
|
737
|
-
await this.
|
|
828
|
+
await this.sendError(
|
|
829
|
+
id,
|
|
830
|
+
-32601,
|
|
831
|
+
"logout is not supported; manage provider credentials with wstack auth"
|
|
832
|
+
);
|
|
738
833
|
return false;
|
|
739
834
|
}
|
|
740
835
|
async handleSessionResume(id, params) {
|
|
741
|
-
|
|
742
|
-
const sessionId = typeof p.sessionId === "string" ? p.sessionId : null;
|
|
743
|
-
const existing = sessionId ? this.sessions.get(sessionId) : void 0;
|
|
744
|
-
if (existing) {
|
|
745
|
-
existing.updatedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
746
|
-
const resumeServers = parseMcpServers(p.mcpServers);
|
|
747
|
-
if (resumeServers.length > 0) {
|
|
748
|
-
existing.mcpServers = resumeServers;
|
|
749
|
-
}
|
|
750
|
-
await this.sendResult(id, {
|
|
751
|
-
initialMode: {
|
|
752
|
-
currentModeId: existing.modeId,
|
|
753
|
-
availableModes: this.modes
|
|
754
|
-
}
|
|
755
|
-
});
|
|
756
|
-
return false;
|
|
757
|
-
}
|
|
758
|
-
await this.sendError(id, -32e3, `session not found: ${sessionId}`);
|
|
759
|
-
return false;
|
|
836
|
+
return handleSessionLoadOp(this.sessionContext(), id, params, false);
|
|
760
837
|
}
|
|
761
838
|
async handleSessionClose(id, params) {
|
|
762
839
|
const p = params ?? {};
|
|
@@ -779,14 +856,12 @@ var ACPProtocolHandler = class {
|
|
|
779
856
|
await this.sendError(id, -32e3, `session not found: ${sessionId}`);
|
|
780
857
|
return false;
|
|
781
858
|
}
|
|
782
|
-
if (!this.sessions.has(sessionId)) {
|
|
783
|
-
await this.sendResult(id, { configOptions: [...this.configOptions] });
|
|
784
|
-
return false;
|
|
785
|
-
}
|
|
786
859
|
const session = this.sessions.get(sessionId);
|
|
787
|
-
session
|
|
860
|
+
session?.abort.abort();
|
|
788
861
|
this.sessions.delete(sessionId);
|
|
789
862
|
this.disposeSession(sessionId);
|
|
863
|
+
await this.persistenceWrites.get(sessionId);
|
|
864
|
+
await this.store?.delete?.(sessionId);
|
|
790
865
|
await this.sendResult(id, {});
|
|
791
866
|
return false;
|
|
792
867
|
}
|
|
@@ -813,12 +888,23 @@ var ACPProtocolHandler = class {
|
|
|
813
888
|
await this.sendError(id, -32e3, "MCP message routing not available through ACP");
|
|
814
889
|
return false;
|
|
815
890
|
}
|
|
816
|
-
async handleSessionList(id) {
|
|
817
|
-
const
|
|
891
|
+
async handleSessionList(id, params) {
|
|
892
|
+
const p = params ?? {};
|
|
893
|
+
if (p.cwd != null && (typeof p.cwd !== "string" || !isAbsolute2(p.cwd)) || p.cursor != null) {
|
|
894
|
+
await this.sendError(id, -32602, "invalid cwd or cursor");
|
|
895
|
+
return false;
|
|
896
|
+
}
|
|
897
|
+
const known = new Map(this.sessions);
|
|
898
|
+
for (const entry of await this.store?.list?.() ?? []) {
|
|
899
|
+
if (known.has(entry.id)) continue;
|
|
900
|
+
const saved = await this.store?.load(entry.id);
|
|
901
|
+
if (saved?.cwd) known.set(entry.id, { ...saved, id: entry.id });
|
|
902
|
+
}
|
|
903
|
+
const sessions = Array.from(known.values()).filter((s) => typeof p.cwd !== "string" || s.cwd && resolve2(s.cwd) === resolve2(p.cwd)).map((s) => {
|
|
818
904
|
const out = {
|
|
819
905
|
sessionId: s.id,
|
|
820
906
|
cwd: s.cwd,
|
|
821
|
-
updatedAt: s.updatedAt
|
|
907
|
+
updatedAt: s.updatedAt ?? ""
|
|
822
908
|
};
|
|
823
909
|
if (s.title !== void 0) out.title = s.title;
|
|
824
910
|
return out;
|
|
@@ -841,6 +927,11 @@ var ACPProtocolHandler = class {
|
|
|
841
927
|
return false;
|
|
842
928
|
}
|
|
843
929
|
case "$/cancel_request": {
|
|
930
|
+
const requestId = params?.requestId;
|
|
931
|
+
if (typeof requestId === "string" || typeof requestId === "number") {
|
|
932
|
+
const sessionId = this.promptRequests.get(requestId);
|
|
933
|
+
if (sessionId) this.sessions.get(sessionId)?.abort.abort();
|
|
934
|
+
}
|
|
844
935
|
return false;
|
|
845
936
|
}
|
|
846
937
|
case "exit":
|
|
@@ -861,10 +952,18 @@ var ACPProtocolHandler = class {
|
|
|
861
952
|
}
|
|
862
953
|
async persist(state, history = void 0) {
|
|
863
954
|
if (!this.store) return;
|
|
864
|
-
|
|
865
|
-
|
|
866
|
-
|
|
867
|
-
|
|
955
|
+
const store = this.store;
|
|
956
|
+
const replay = history ?? this.replayFor?.(state.id);
|
|
957
|
+
const previous = this.persistenceWrites.get(state.id) ?? Promise.resolve();
|
|
958
|
+
const write = previous.then(() => store.save(state, replay)).then(
|
|
959
|
+
() => {
|
|
960
|
+
},
|
|
961
|
+
() => {
|
|
962
|
+
}
|
|
963
|
+
);
|
|
964
|
+
this.persistenceWrites.set(state.id, write);
|
|
965
|
+
await write;
|
|
966
|
+
if (this.persistenceWrites.get(state.id) === write) this.persistenceWrites.delete(state.id);
|
|
868
967
|
}
|
|
869
968
|
async sendError(id, code, message, data) {
|
|
870
969
|
const error = { code, message };
|
|
@@ -1131,7 +1230,10 @@ function makeACPServerAgentTurn(opts) {
|
|
|
1131
1230
|
}
|
|
1132
1231
|
}
|
|
1133
1232
|
}
|
|
1233
|
+
const agent = agents.get(sessionId);
|
|
1134
1234
|
agents.delete(sessionId);
|
|
1235
|
+
void Promise.resolve().then(() => agent?.teardown?.()).catch(() => {
|
|
1236
|
+
});
|
|
1135
1237
|
history.delete(sessionId);
|
|
1136
1238
|
historyBytes.delete(sessionId);
|
|
1137
1239
|
pendingSeed.delete(sessionId);
|
|
@@ -1254,7 +1356,10 @@ function promptToText(blocks) {
|
|
|
1254
1356
|
} else if (b.type === "audio") {
|
|
1255
1357
|
parts.push(`[audio: ${b.mimeType}]`);
|
|
1256
1358
|
} else if (b.type === "resource") {
|
|
1257
|
-
parts.push(
|
|
1359
|
+
parts.push(
|
|
1360
|
+
"text" in b.resource && typeof b.resource.text === "string" ? `[embedded resource: ${b.resource.uri}]
|
|
1361
|
+
${b.resource.text}` : `[embedded resource: ${b.resource.uri}]`
|
|
1362
|
+
);
|
|
1258
1363
|
} else if (b.type === "resource_link") {
|
|
1259
1364
|
parts.push(`[resource link: ${b.uri}]`);
|
|
1260
1365
|
}
|
|
@@ -1398,6 +1503,7 @@ var ACPSessionStore = class {
|
|
|
1398
1503
|
id: state.id,
|
|
1399
1504
|
cwd: state.cwd,
|
|
1400
1505
|
modeId: state.modeId,
|
|
1506
|
+
configOptions: state.configOptions,
|
|
1401
1507
|
createdAt: state.createdAt,
|
|
1402
1508
|
updatedAt: state.updatedAt,
|
|
1403
1509
|
title: state.title,
|
|
@@ -1558,7 +1664,8 @@ var ACPSessionStore = class {
|
|
|
1558
1664
|
if (target === null) return;
|
|
1559
1665
|
try {
|
|
1560
1666
|
await fsp2.unlink(target);
|
|
1561
|
-
} catch {
|
|
1667
|
+
} catch (err) {
|
|
1668
|
+
if (err.code !== "ENOENT") throw err;
|
|
1562
1669
|
}
|
|
1563
1670
|
await this.withIndexLock(async () => {
|
|
1564
1671
|
const entries = await this.readIndex();
|
|
@@ -1619,9 +1726,9 @@ var StdioTransport = class {
|
|
|
1619
1726
|
}
|
|
1620
1727
|
send(msg) {
|
|
1621
1728
|
if (this.closed) return Promise.resolve();
|
|
1622
|
-
return new Promise((
|
|
1729
|
+
return new Promise((resolve3) => {
|
|
1623
1730
|
const line = JSON.stringify(msg) + "\n";
|
|
1624
|
-
this.stdout.write(line, "utf8", () =>
|
|
1731
|
+
this.stdout.write(line, "utf8", () => resolve3());
|
|
1625
1732
|
});
|
|
1626
1733
|
}
|
|
1627
1734
|
sendRaw(chunk) {
|
|
@@ -1634,8 +1741,8 @@ var StdioTransport = class {
|
|
|
1634
1741
|
return Promise.resolve(queued.message);
|
|
1635
1742
|
}
|
|
1636
1743
|
if (this.closed) return Promise.resolve(null);
|
|
1637
|
-
return new Promise((
|
|
1638
|
-
this.resolveRead =
|
|
1744
|
+
return new Promise((resolve3) => {
|
|
1745
|
+
this.resolveRead = resolve3;
|
|
1639
1746
|
});
|
|
1640
1747
|
}
|
|
1641
1748
|
onMessage(handler) {
|
|
@@ -1704,9 +1811,9 @@ var StdioTransport = class {
|
|
|
1704
1811
|
}
|
|
1705
1812
|
dispatch(msg, chars = JSON.stringify(msg).length) {
|
|
1706
1813
|
if (this.resolveRead) {
|
|
1707
|
-
const
|
|
1814
|
+
const resolve3 = this.resolveRead;
|
|
1708
1815
|
this.resolveRead = null;
|
|
1709
|
-
|
|
1816
|
+
resolve3(msg);
|
|
1710
1817
|
} else {
|
|
1711
1818
|
let claimed = false;
|
|
1712
1819
|
for (const handler of this.claimHandlers) {
|
|
@@ -1866,7 +1973,7 @@ function toolToPriority(tool) {
|
|
|
1866
1973
|
import { createServer } from "node:http";
|
|
1867
1974
|
import { isIP } from "node:net";
|
|
1868
1975
|
import { fileURLToPath } from "node:url";
|
|
1869
|
-
import { expandIPv6, writeErr as writeErr2 } from "@wrongstack/core/utils";
|
|
1976
|
+
import { expandIPv6, isStandaloneBinary, writeErr as writeErr2 } from "@wrongstack/core/utils";
|
|
1870
1977
|
import { timingSafeTokenEqual } from "@wrongstack/primitives";
|
|
1871
1978
|
var LISTEN_RETRY_LIMIT = 5;
|
|
1872
1979
|
var LISTEN_RETRY_BASE_MS = 25;
|
|
@@ -1912,15 +2019,30 @@ var WrongStackACPServer = class {
|
|
|
1912
2019
|
this.transport.sendStartupMarker();
|
|
1913
2020
|
}
|
|
1914
2021
|
this.running = true;
|
|
2022
|
+
const prompts = /* @__PURE__ */ new Set();
|
|
1915
2023
|
try {
|
|
1916
2024
|
while (this.running) {
|
|
1917
2025
|
const msg = await this.transport.read();
|
|
1918
2026
|
if (!msg) break;
|
|
2027
|
+
if (msg.method === "session/prompt") {
|
|
2028
|
+
const task = this.handler.handleMessage(msg).then(
|
|
2029
|
+
() => {
|
|
2030
|
+
},
|
|
2031
|
+
(err) => {
|
|
2032
|
+
writeErr2(`[wstack-acp] prompt dispatch failed: ${String(err)}
|
|
2033
|
+
`);
|
|
2034
|
+
}
|
|
2035
|
+
);
|
|
2036
|
+
prompts.add(task);
|
|
2037
|
+
void task.finally(() => prompts.delete(task));
|
|
2038
|
+
continue;
|
|
2039
|
+
}
|
|
1919
2040
|
const terminal = await this.handler.handleMessage(msg);
|
|
1920
2041
|
if (terminal) break;
|
|
1921
2042
|
}
|
|
1922
2043
|
} finally {
|
|
1923
2044
|
this.handler.close();
|
|
2045
|
+
await Promise.allSettled(prompts);
|
|
1924
2046
|
this.transport.close();
|
|
1925
2047
|
}
|
|
1926
2048
|
}
|
|
@@ -2054,10 +2176,10 @@ var WrongStackACPServer = class {
|
|
|
2054
2176
|
listenWithRetry(port, host, attempt = 0) {
|
|
2055
2177
|
const server = this.httpServer;
|
|
2056
2178
|
if (!server) return Promise.resolve();
|
|
2057
|
-
return new Promise((
|
|
2179
|
+
return new Promise((resolve3, reject) => {
|
|
2058
2180
|
const onListening = () => {
|
|
2059
2181
|
server.removeListener("error", onError);
|
|
2060
|
-
|
|
2182
|
+
resolve3();
|
|
2061
2183
|
};
|
|
2062
2184
|
const onError = (err) => {
|
|
2063
2185
|
server.removeListener("listening", onListening);
|
|
@@ -2065,7 +2187,7 @@ var WrongStackACPServer = class {
|
|
|
2065
2187
|
if (port === 0 && transient && attempt < LISTEN_RETRY_LIMIT) {
|
|
2066
2188
|
const timer = setTimeout(
|
|
2067
2189
|
() => {
|
|
2068
|
-
this.listenWithRetry(port, host, attempt + 1).then(
|
|
2190
|
+
this.listenWithRetry(port, host, attempt + 1).then(resolve3, reject);
|
|
2069
2191
|
},
|
|
2070
2192
|
LISTEN_RETRY_BASE_MS * (attempt + 1)
|
|
2071
2193
|
);
|
|
@@ -2093,8 +2215,8 @@ var WrongStackACPServer = class {
|
|
|
2093
2215
|
if (!server) return;
|
|
2094
2216
|
for (const socket of this.httpSockets) socket.destroy();
|
|
2095
2217
|
this.httpSockets.clear();
|
|
2096
|
-
await new Promise((
|
|
2097
|
-
server.close(() =>
|
|
2218
|
+
await new Promise((resolve3) => {
|
|
2219
|
+
server.close(() => resolve3());
|
|
2098
2220
|
});
|
|
2099
2221
|
}
|
|
2100
2222
|
};
|
|
@@ -2125,7 +2247,7 @@ async function main() {
|
|
|
2125
2247
|
const server = new WrongStackACPServer();
|
|
2126
2248
|
await server.start();
|
|
2127
2249
|
}
|
|
2128
|
-
var isEntrypoint = process.argv[1] !== void 0 && fileURLToPath(import.meta.url) === process.argv[1];
|
|
2250
|
+
var isEntrypoint = process.argv[1] !== void 0 && !isStandaloneBinary() && fileURLToPath(import.meta.url) === process.argv[1];
|
|
2129
2251
|
if (isEntrypoint) {
|
|
2130
2252
|
main().catch((err) => {
|
|
2131
2253
|
writeErr2(`[wstack-acp fatal] ${err}
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
import type { ACPClientTransport } from '../agent/stdio-transport.js';
|
|
2
|
+
import type { SessionId } from '../types/acp-v1.js';
|
|
3
|
+
export declare function cancelLateAcpSession(transport: ACPClientTransport, createPromise: Promise<SessionId>): void;
|
|
4
|
+
//# sourceMappingURL=acp-late-session.d.ts.map
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
export interface PendingRequest {
|
|
2
|
+
method: string;
|
|
3
|
+
resolve: (v: unknown) => void;
|
|
4
|
+
reject: (e: Error) => void;
|
|
5
|
+
timeoutMs: number;
|
|
6
|
+
timeoutHandle: ReturnType<typeof setTimeout>;
|
|
7
|
+
}
|
|
8
|
+
export type State = 'init' | 'ready' | 'authenticated' | 'sessioning' | 'prompting' | 'done' | 'closed';
|
|
9
|
+
//# sourceMappingURL=acp-request-state.d.ts.map
|