@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.
- 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 +133 -66
- package/dist/index.js +366 -185
- package/dist/types/acp-v1.d.ts +4 -3
- package/dist/wrongstack-acp-agent.js +213 -99
- package/package.json +3 -3
package/dist/index.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;
|
|
@@ -180,7 +181,7 @@ function createRunTurnApi(sessionId, clientCapabilities, request, sendSessionUpd
|
|
|
180
181
|
...sendSessionUpdate ? { sendSessionUpdate } : {}
|
|
181
182
|
};
|
|
182
183
|
}
|
|
183
|
-
function buildInitializeResult(agentName, modes, configOptions) {
|
|
184
|
+
function buildInitializeResult(agentName, modes, configOptions, clientCapabilities = {}) {
|
|
184
185
|
return {
|
|
185
186
|
protocolVersion: ACP_PROTOCOL_VERSION,
|
|
186
187
|
agentCapabilities: {
|
|
@@ -207,9 +208,6 @@ function buildInitializeResult(agentName, modes, configOptions) {
|
|
|
207
208
|
delete: {},
|
|
208
209
|
resume: {},
|
|
209
210
|
fork: {}
|
|
210
|
-
},
|
|
211
|
-
auth: {
|
|
212
|
-
logout: {}
|
|
213
211
|
}
|
|
214
212
|
},
|
|
215
213
|
agentInfo: {
|
|
@@ -217,7 +215,7 @@ function buildInitializeResult(agentName, modes, configOptions) {
|
|
|
217
215
|
title: "WrongStack",
|
|
218
216
|
version: WRONGSTACK_VERSION
|
|
219
217
|
},
|
|
220
|
-
authMethods: WRONGSTACK_AUTH_METHODS,
|
|
218
|
+
authMethods: clientCapabilities.auth?.terminal === true ? WRONGSTACK_AUTH_METHODS : [],
|
|
221
219
|
modes,
|
|
222
220
|
configOptions
|
|
223
221
|
};
|
|
@@ -247,7 +245,8 @@ async function handleSessionNewOp(ctx, id, params) {
|
|
|
247
245
|
id: sessionId,
|
|
248
246
|
cwd,
|
|
249
247
|
abort: new AbortController(),
|
|
250
|
-
modeId: DEFAULT_MODE_ID,
|
|
248
|
+
modeId: ctx.modes[0]?.id ?? DEFAULT_MODE_ID,
|
|
249
|
+
configOptions: structuredClone([...ctx.configOptions]),
|
|
251
250
|
createdAt: now,
|
|
252
251
|
updatedAt: now,
|
|
253
252
|
...mcpServers.length > 0 ? { mcpServers } : {}
|
|
@@ -259,6 +258,7 @@ async function handleSessionNewOp(ctx, id, params) {
|
|
|
259
258
|
sessionId,
|
|
260
259
|
update: {
|
|
261
260
|
sessionUpdate: "current_mode_update",
|
|
261
|
+
currentModeId: ctx.modes[0]?.id ?? DEFAULT_MODE_ID,
|
|
262
262
|
modeId: ctx.modes[0]?.id ?? DEFAULT_MODE_ID
|
|
263
263
|
}
|
|
264
264
|
});
|
|
@@ -274,12 +274,12 @@ async function handleSessionNewOp(ctx, id, params) {
|
|
|
274
274
|
await reportSkippedMcpServers(ctx, sessionId, skipped);
|
|
275
275
|
await ctx.sendResult(id, {
|
|
276
276
|
sessionId,
|
|
277
|
-
modes: ctx.modes,
|
|
278
|
-
configOptions:
|
|
277
|
+
modes: { currentModeId: state.modeId, availableModes: ctx.modes },
|
|
278
|
+
configOptions: state.configOptions
|
|
279
279
|
});
|
|
280
280
|
return false;
|
|
281
281
|
}
|
|
282
|
-
async function handleSessionLoadOp(ctx, id, params) {
|
|
282
|
+
async function handleSessionLoadOp(ctx, id, params, replayHistory = true) {
|
|
283
283
|
const p = params ?? {};
|
|
284
284
|
const sessionId = typeof p.sessionId === "string" ? p.sessionId : null;
|
|
285
285
|
const loadCwd = typeof p.cwd === "string" ? p.cwd : void 0;
|
|
@@ -304,6 +304,7 @@ async function handleSessionLoadOp(ctx, id, params) {
|
|
|
304
304
|
cwd: restoredCwd,
|
|
305
305
|
abort: new AbortController(),
|
|
306
306
|
modeId: persisted.modeId ?? DEFAULT_MODE_ID,
|
|
307
|
+
configOptions: structuredClone(persisted.configOptions ?? [...ctx.configOptions]),
|
|
307
308
|
createdAt: persisted.createdAt ?? (/* @__PURE__ */ new Date()).toISOString(),
|
|
308
309
|
updatedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
309
310
|
...persisted.title !== void 0 ? { title: persisted.title } : {},
|
|
@@ -311,51 +312,69 @@ async function handleSessionLoadOp(ctx, id, params) {
|
|
|
311
312
|
};
|
|
312
313
|
ctx.sessions.set(sessionId, restored);
|
|
313
314
|
ctx.seedFor?.(sessionId, persisted.history ?? []);
|
|
314
|
-
for (const update of persisted.history ?? []) {
|
|
315
|
+
for (const update of replayHistory ? persisted.history ?? [] : []) {
|
|
315
316
|
await ctx.sendNotification({ sessionId, update });
|
|
316
317
|
}
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
318
|
+
if (replayHistory)
|
|
319
|
+
await ctx.sendNotification({
|
|
320
|
+
sessionId,
|
|
321
|
+
update: {
|
|
322
|
+
sessionUpdate: "current_mode_update",
|
|
323
|
+
currentModeId: restored.modeId,
|
|
324
|
+
modeId: restored.modeId
|
|
325
|
+
}
|
|
326
|
+
});
|
|
321
327
|
await reportSkippedMcpServers(ctx, sessionId, loadSkipped);
|
|
322
328
|
await ctx.sendResult(id, {
|
|
323
|
-
|
|
329
|
+
modes: { currentModeId: restored.modeId, availableModes: ctx.modes },
|
|
330
|
+
configOptions: restored.configOptions
|
|
324
331
|
});
|
|
325
332
|
return false;
|
|
326
333
|
}
|
|
327
334
|
}
|
|
328
335
|
if (existing) {
|
|
336
|
+
if (existing.prompting) {
|
|
337
|
+
await ctx.sendError(id, -32e3, "cannot reload a session while a prompt is running");
|
|
338
|
+
return false;
|
|
339
|
+
}
|
|
329
340
|
existing.updatedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
330
|
-
|
|
341
|
+
const replay = ctx.replayFor?.(sessionId);
|
|
342
|
+
if (Array.isArray(p.mcpServers)) {
|
|
343
|
+
if (JSON.stringify(existing.mcpServers ?? []) !== JSON.stringify(loadMcpServers)) {
|
|
344
|
+
ctx.disposeFor?.(sessionId);
|
|
345
|
+
ctx.seedFor?.(sessionId, replay ?? []);
|
|
346
|
+
}
|
|
331
347
|
existing.mcpServers = loadMcpServers;
|
|
332
348
|
}
|
|
333
|
-
|
|
334
|
-
if (replay) {
|
|
349
|
+
if (replayHistory && replay) {
|
|
335
350
|
for (const update of replay) {
|
|
336
351
|
await ctx.sendNotification({ sessionId, update });
|
|
337
352
|
}
|
|
338
353
|
}
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
354
|
+
if (replayHistory)
|
|
355
|
+
await ctx.sendNotification({
|
|
356
|
+
sessionId,
|
|
357
|
+
update: {
|
|
358
|
+
sessionUpdate: "session_info_update",
|
|
359
|
+
updatedAt: existing.updatedAt
|
|
360
|
+
}
|
|
361
|
+
});
|
|
362
|
+
if (replayHistory)
|
|
363
|
+
await ctx.sendNotification({
|
|
364
|
+
sessionId,
|
|
365
|
+
update: {
|
|
366
|
+
sessionUpdate: "current_mode_update",
|
|
367
|
+
currentModeId: existing.modeId,
|
|
368
|
+
modeId: existing.modeId
|
|
369
|
+
}
|
|
370
|
+
});
|
|
353
371
|
await reportSkippedMcpServers(ctx, sessionId, loadSkipped);
|
|
354
372
|
await ctx.sendResult(id, {
|
|
355
|
-
|
|
373
|
+
modes: {
|
|
356
374
|
currentModeId: existing.modeId,
|
|
357
375
|
availableModes: ctx.modes
|
|
358
|
-
}
|
|
376
|
+
},
|
|
377
|
+
configOptions: existing.configOptions ?? [...ctx.configOptions]
|
|
359
378
|
});
|
|
360
379
|
return false;
|
|
361
380
|
}
|
|
@@ -393,6 +412,7 @@ async function handleSessionForkOp(ctx, id, params) {
|
|
|
393
412
|
cwd: forkCwd,
|
|
394
413
|
abort: new AbortController(),
|
|
395
414
|
modeId: source.modeId,
|
|
415
|
+
configOptions: structuredClone(source.configOptions ?? [...ctx.configOptions]),
|
|
396
416
|
createdAt: now,
|
|
397
417
|
updatedAt: now,
|
|
398
418
|
...source.title !== void 0 ? { title: source.title } : {},
|
|
@@ -408,13 +428,17 @@ async function handleSessionForkOp(ctx, id, params) {
|
|
|
408
428
|
await ctx.persist(forked, history);
|
|
409
429
|
await ctx.sendNotification({
|
|
410
430
|
sessionId,
|
|
411
|
-
update: {
|
|
431
|
+
update: {
|
|
432
|
+
sessionUpdate: "current_mode_update",
|
|
433
|
+
currentModeId: forked.modeId,
|
|
434
|
+
modeId: forked.modeId
|
|
435
|
+
}
|
|
412
436
|
});
|
|
413
437
|
await reportSkippedMcpServers(ctx, sessionId, forkSkipped);
|
|
414
438
|
await ctx.sendResult(id, {
|
|
415
439
|
sessionId,
|
|
416
|
-
modes: ctx.modes,
|
|
417
|
-
configOptions:
|
|
440
|
+
modes: { currentModeId: forked.modeId, availableModes: ctx.modes },
|
|
441
|
+
configOptions: forked.configOptions
|
|
418
442
|
});
|
|
419
443
|
return false;
|
|
420
444
|
}
|
|
@@ -430,6 +454,11 @@ async function handleSessionPromptOp(ctx, id, params) {
|
|
|
430
454
|
return false;
|
|
431
455
|
}
|
|
432
456
|
const session = ctx.sessions.get(sessionId);
|
|
457
|
+
if (session.prompting) {
|
|
458
|
+
await ctx.sendError(id, -32e3, "a prompt is already running for this session");
|
|
459
|
+
return false;
|
|
460
|
+
}
|
|
461
|
+
session.prompting = true;
|
|
433
462
|
if (session.abort.signal.aborted) {
|
|
434
463
|
session.abort = new AbortController();
|
|
435
464
|
}
|
|
@@ -458,6 +487,8 @@ async function handleSessionPromptOp(ctx, id, params) {
|
|
|
458
487
|
{
|
|
459
488
|
sessionId,
|
|
460
489
|
prompt: p.prompt,
|
|
490
|
+
modeId: session.modeId,
|
|
491
|
+
configOptions: session.configOptions,
|
|
461
492
|
signal: turnSignal.signal,
|
|
462
493
|
cwd: session.cwd,
|
|
463
494
|
...session.mcpServers ? { mcpServers: session.mcpServers } : {}
|
|
@@ -466,16 +497,25 @@ async function handleSessionPromptOp(ctx, id, params) {
|
|
|
466
497
|
api
|
|
467
498
|
);
|
|
468
499
|
} catch (err) {
|
|
500
|
+
session.prompting = false;
|
|
469
501
|
session.abort.signal.removeEventListener("abort", onCancel);
|
|
502
|
+
await Promise.all(pendingNotifications);
|
|
503
|
+
if (turnSignal.signal.aborted) {
|
|
504
|
+
await ctx.sendResult(id, { stopReason: "cancelled" });
|
|
505
|
+
return false;
|
|
506
|
+
}
|
|
470
507
|
const { code, message, data } = errorToJsonRpc(err);
|
|
471
508
|
await ctx.sendError(id, code, message, data);
|
|
472
509
|
return false;
|
|
473
510
|
}
|
|
474
511
|
await Promise.all(pendingNotifications);
|
|
512
|
+
session.prompting = false;
|
|
475
513
|
session.abort.signal.removeEventListener("abort", onCancel);
|
|
476
514
|
session.updatedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
477
|
-
await ctx.persist(session);
|
|
478
|
-
await ctx.sendResult(id, {
|
|
515
|
+
if (ctx.sessions.get(sessionId) === session) await ctx.persist(session);
|
|
516
|
+
await ctx.sendResult(id, {
|
|
517
|
+
stopReason: turnSignal.signal.aborted ? "cancelled" : result.stopReason
|
|
518
|
+
});
|
|
479
519
|
return false;
|
|
480
520
|
}
|
|
481
521
|
async function handleSetModeOp(ctx, id, params) {
|
|
@@ -491,7 +531,11 @@ async function handleSetModeOp(ctx, id, params) {
|
|
|
491
531
|
session.updatedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
492
532
|
await ctx.sendNotification({
|
|
493
533
|
sessionId,
|
|
494
|
-
update: {
|
|
534
|
+
update: {
|
|
535
|
+
sessionUpdate: "current_mode_update",
|
|
536
|
+
currentModeId: modeId,
|
|
537
|
+
modeId
|
|
538
|
+
}
|
|
495
539
|
});
|
|
496
540
|
await ctx.sendResult(id, {});
|
|
497
541
|
return false;
|
|
@@ -502,21 +546,24 @@ async function handleSetConfigOptionOp(ctx, id, params) {
|
|
|
502
546
|
const optionId = typeof p.configId === "string" ? p.configId : null;
|
|
503
547
|
const value = typeof p.value === "string" ? p.value : null;
|
|
504
548
|
const session = sessionId ? ctx.sessions.get(sessionId) : void 0;
|
|
505
|
-
const
|
|
549
|
+
const options = session?.configOptions ?? structuredClone([...ctx.configOptions]);
|
|
550
|
+
const option = optionId ? options.find((o) => o.id === optionId) : void 0;
|
|
506
551
|
if (!session || !option || value === null || !option.options.some((o) => o.value === value)) {
|
|
507
552
|
await ctx.sendError(id, -32602, "invalid sessionId, configId, or value");
|
|
508
553
|
return false;
|
|
509
554
|
}
|
|
510
555
|
option.currentValue = value;
|
|
556
|
+
session.configOptions = options;
|
|
511
557
|
session.updatedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
512
558
|
await ctx.sendNotification({
|
|
513
559
|
sessionId,
|
|
514
560
|
update: {
|
|
515
561
|
sessionUpdate: "config_option_update",
|
|
516
|
-
configOptions:
|
|
562
|
+
configOptions: options
|
|
517
563
|
}
|
|
518
564
|
});
|
|
519
|
-
await ctx.
|
|
565
|
+
await ctx.persist(session);
|
|
566
|
+
await ctx.sendResult(id, { configOptions: options });
|
|
520
567
|
return false;
|
|
521
568
|
}
|
|
522
569
|
async function reportSkippedMcpServers(ctx, sessionId, skipped) {
|
|
@@ -558,6 +605,8 @@ var ACPProtocolHandler = class {
|
|
|
558
605
|
// session/request_permission). Keyed by our own `srv_N` ids.
|
|
559
606
|
pendingOut = /* @__PURE__ */ new Map();
|
|
560
607
|
nextOutId = 1;
|
|
608
|
+
promptRequests = /* @__PURE__ */ new Map();
|
|
609
|
+
persistenceWrites = /* @__PURE__ */ new Map();
|
|
561
610
|
constructor(opts) {
|
|
562
611
|
this.transport = opts.transport;
|
|
563
612
|
this.defaultCwd = opts.defaultCwd;
|
|
@@ -583,20 +632,46 @@ var ACPProtocolHandler = class {
|
|
|
583
632
|
*/
|
|
584
633
|
request(method, params, timeoutMs = 6e4) {
|
|
585
634
|
const id = `srv_${this.nextOutId++}`;
|
|
586
|
-
|
|
587
|
-
|
|
635
|
+
const sessionId = params?.sessionId;
|
|
636
|
+
const signal = sessionId ? this.sessions.get(sessionId)?.abort.signal : void 0;
|
|
637
|
+
return new Promise((resolve5, reject) => {
|
|
638
|
+
const cleanup = () => {
|
|
639
|
+
clearTimeout(timer);
|
|
640
|
+
signal?.removeEventListener("abort", abort);
|
|
588
641
|
this.pendingOut.delete(id);
|
|
589
|
-
|
|
642
|
+
};
|
|
643
|
+
const fail = (error) => {
|
|
644
|
+
cleanup();
|
|
645
|
+
reject(error);
|
|
646
|
+
};
|
|
647
|
+
const abort = () => {
|
|
648
|
+
fail(new Error("client request cancelled"));
|
|
649
|
+
void this.transport.send(toWire({ jsonrpc: "2.0", method: "$/cancel_request", params: { requestId: id } })).catch(() => {
|
|
650
|
+
});
|
|
651
|
+
};
|
|
652
|
+
const timer = setTimeout(() => {
|
|
653
|
+
fail(new Error(`${method} timed out after ${timeoutMs}ms`));
|
|
590
654
|
}, timeoutMs);
|
|
591
|
-
|
|
655
|
+
if (signal?.aborted) {
|
|
656
|
+
fail(new Error("client request cancelled"));
|
|
657
|
+
return;
|
|
658
|
+
}
|
|
659
|
+
signal?.addEventListener("abort", abort, { once: true });
|
|
660
|
+
this.pendingOut.set(id, {
|
|
661
|
+
resolve: (value) => {
|
|
662
|
+
cleanup();
|
|
663
|
+
resolve5(value);
|
|
664
|
+
},
|
|
665
|
+
reject: fail,
|
|
666
|
+
timer
|
|
667
|
+
});
|
|
592
668
|
this.transport.send(toWire({ jsonrpc: "2.0", id, method, params })).catch((e) => {
|
|
593
|
-
|
|
594
|
-
this.pendingOut.delete(id);
|
|
595
|
-
reject(e instanceof Error ? e : new Error(String(e)));
|
|
669
|
+
fail(e instanceof Error ? e : new Error(String(e)));
|
|
596
670
|
});
|
|
597
671
|
});
|
|
598
672
|
}
|
|
599
673
|
maybeResolvePending(m) {
|
|
674
|
+
if (m.result === void 0 && m.error === void 0) return;
|
|
600
675
|
const id = m.id;
|
|
601
676
|
if (typeof id !== "string") return;
|
|
602
677
|
const pending = this.pendingOut.get(id);
|
|
@@ -616,6 +691,7 @@ var ACPProtocolHandler = class {
|
|
|
616
691
|
if (typeof msg !== "object" || msg === null) return false;
|
|
617
692
|
const m = msg;
|
|
618
693
|
if (m.id !== void 0 && (m.result !== void 0 || m.error !== void 0)) {
|
|
694
|
+
this.maybeResolvePending(msg);
|
|
619
695
|
return false;
|
|
620
696
|
}
|
|
621
697
|
if (m.id !== void 0 && typeof m.method === "string") {
|
|
@@ -655,6 +731,7 @@ var ACPProtocolHandler = class {
|
|
|
655
731
|
store: this.store,
|
|
656
732
|
replayFor: this.replayFor,
|
|
657
733
|
seedFor: this.seedFor,
|
|
734
|
+
disposeFor: this.disposeFor,
|
|
658
735
|
onSessionNew: this.onSessionNew,
|
|
659
736
|
allocId: () => this.allocId(),
|
|
660
737
|
persist: (state, history) => this.persist(state, history),
|
|
@@ -692,14 +769,23 @@ var ACPProtocolHandler = class {
|
|
|
692
769
|
return await this.handleSessionClose(id, params);
|
|
693
770
|
case "session/delete":
|
|
694
771
|
return await this.handleSessionDelete(id, params);
|
|
695
|
-
case "session/prompt":
|
|
696
|
-
|
|
772
|
+
case "session/prompt": {
|
|
773
|
+
const sessionId = params?.sessionId;
|
|
774
|
+
if (typeof sessionId === "string" && !this.sessions.get(sessionId)?.prompting) {
|
|
775
|
+
this.promptRequests.set(id, sessionId);
|
|
776
|
+
}
|
|
777
|
+
try {
|
|
778
|
+
return await handleSessionPromptOp(this.sessionContext(), id, params);
|
|
779
|
+
} finally {
|
|
780
|
+
this.promptRequests.delete(id);
|
|
781
|
+
}
|
|
782
|
+
}
|
|
697
783
|
case "session/set_mode":
|
|
698
784
|
return await handleSetModeOp(this.sessionContext(), id, params);
|
|
699
785
|
case "session/set_config_option":
|
|
700
786
|
return await handleSetConfigOptionOp(this.sessionContext(), id, params);
|
|
701
787
|
case "session/list":
|
|
702
|
-
return await this.handleSessionList(id);
|
|
788
|
+
return await this.handleSessionList(id, params);
|
|
703
789
|
case "session/fork":
|
|
704
790
|
return await handleSessionForkOp(this.sessionContext(), id, params);
|
|
705
791
|
case "providers/list":
|
|
@@ -728,7 +814,12 @@ var ACPProtocolHandler = class {
|
|
|
728
814
|
this.initialized = true;
|
|
729
815
|
await this.sendResult(
|
|
730
816
|
id,
|
|
731
|
-
buildInitializeResult(
|
|
817
|
+
buildInitializeResult(
|
|
818
|
+
this.agentName,
|
|
819
|
+
this.modes,
|
|
820
|
+
this.configOptions,
|
|
821
|
+
this.clientCapabilities
|
|
822
|
+
)
|
|
732
823
|
);
|
|
733
824
|
return false;
|
|
734
825
|
}
|
|
@@ -737,29 +828,15 @@ var ACPProtocolHandler = class {
|
|
|
737
828
|
return false;
|
|
738
829
|
}
|
|
739
830
|
async handleLogout(id, _params) {
|
|
740
|
-
await this.
|
|
831
|
+
await this.sendError(
|
|
832
|
+
id,
|
|
833
|
+
-32601,
|
|
834
|
+
"logout is not supported; manage provider credentials with wstack auth"
|
|
835
|
+
);
|
|
741
836
|
return false;
|
|
742
837
|
}
|
|
743
838
|
async handleSessionResume(id, params) {
|
|
744
|
-
|
|
745
|
-
const sessionId = typeof p.sessionId === "string" ? p.sessionId : null;
|
|
746
|
-
const existing = sessionId ? this.sessions.get(sessionId) : void 0;
|
|
747
|
-
if (existing) {
|
|
748
|
-
existing.updatedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
749
|
-
const resumeServers = parseMcpServers(p.mcpServers);
|
|
750
|
-
if (resumeServers.length > 0) {
|
|
751
|
-
existing.mcpServers = resumeServers;
|
|
752
|
-
}
|
|
753
|
-
await this.sendResult(id, {
|
|
754
|
-
initialMode: {
|
|
755
|
-
currentModeId: existing.modeId,
|
|
756
|
-
availableModes: this.modes
|
|
757
|
-
}
|
|
758
|
-
});
|
|
759
|
-
return false;
|
|
760
|
-
}
|
|
761
|
-
await this.sendError(id, -32e3, `session not found: ${sessionId}`);
|
|
762
|
-
return false;
|
|
839
|
+
return handleSessionLoadOp(this.sessionContext(), id, params, false);
|
|
763
840
|
}
|
|
764
841
|
async handleSessionClose(id, params) {
|
|
765
842
|
const p = params ?? {};
|
|
@@ -782,14 +859,12 @@ var ACPProtocolHandler = class {
|
|
|
782
859
|
await this.sendError(id, -32e3, `session not found: ${sessionId}`);
|
|
783
860
|
return false;
|
|
784
861
|
}
|
|
785
|
-
if (!this.sessions.has(sessionId)) {
|
|
786
|
-
await this.sendResult(id, { configOptions: [...this.configOptions] });
|
|
787
|
-
return false;
|
|
788
|
-
}
|
|
789
862
|
const session = this.sessions.get(sessionId);
|
|
790
|
-
session
|
|
863
|
+
session?.abort.abort();
|
|
791
864
|
this.sessions.delete(sessionId);
|
|
792
865
|
this.disposeSession(sessionId);
|
|
866
|
+
await this.persistenceWrites.get(sessionId);
|
|
867
|
+
await this.store?.delete?.(sessionId);
|
|
793
868
|
await this.sendResult(id, {});
|
|
794
869
|
return false;
|
|
795
870
|
}
|
|
@@ -816,12 +891,23 @@ var ACPProtocolHandler = class {
|
|
|
816
891
|
await this.sendError(id, -32e3, "MCP message routing not available through ACP");
|
|
817
892
|
return false;
|
|
818
893
|
}
|
|
819
|
-
async handleSessionList(id) {
|
|
820
|
-
const
|
|
894
|
+
async handleSessionList(id, params) {
|
|
895
|
+
const p = params ?? {};
|
|
896
|
+
if (p.cwd != null && (typeof p.cwd !== "string" || !isAbsolute2(p.cwd)) || p.cursor != null) {
|
|
897
|
+
await this.sendError(id, -32602, "invalid cwd or cursor");
|
|
898
|
+
return false;
|
|
899
|
+
}
|
|
900
|
+
const known = new Map(this.sessions);
|
|
901
|
+
for (const entry of await this.store?.list?.() ?? []) {
|
|
902
|
+
if (known.has(entry.id)) continue;
|
|
903
|
+
const saved = await this.store?.load(entry.id);
|
|
904
|
+
if (saved?.cwd) known.set(entry.id, { ...saved, id: entry.id });
|
|
905
|
+
}
|
|
906
|
+
const sessions = Array.from(known.values()).filter((s) => typeof p.cwd !== "string" || s.cwd && resolve2(s.cwd) === resolve2(p.cwd)).map((s) => {
|
|
821
907
|
const out = {
|
|
822
908
|
sessionId: s.id,
|
|
823
909
|
cwd: s.cwd,
|
|
824
|
-
updatedAt: s.updatedAt
|
|
910
|
+
updatedAt: s.updatedAt ?? ""
|
|
825
911
|
};
|
|
826
912
|
if (s.title !== void 0) out.title = s.title;
|
|
827
913
|
return out;
|
|
@@ -844,6 +930,11 @@ var ACPProtocolHandler = class {
|
|
|
844
930
|
return false;
|
|
845
931
|
}
|
|
846
932
|
case "$/cancel_request": {
|
|
933
|
+
const requestId = params?.requestId;
|
|
934
|
+
if (typeof requestId === "string" || typeof requestId === "number") {
|
|
935
|
+
const sessionId = this.promptRequests.get(requestId);
|
|
936
|
+
if (sessionId) this.sessions.get(sessionId)?.abort.abort();
|
|
937
|
+
}
|
|
847
938
|
return false;
|
|
848
939
|
}
|
|
849
940
|
case "exit":
|
|
@@ -864,10 +955,18 @@ var ACPProtocolHandler = class {
|
|
|
864
955
|
}
|
|
865
956
|
async persist(state, history = void 0) {
|
|
866
957
|
if (!this.store) return;
|
|
867
|
-
|
|
868
|
-
|
|
869
|
-
|
|
870
|
-
|
|
958
|
+
const store = this.store;
|
|
959
|
+
const replay = history ?? this.replayFor?.(state.id);
|
|
960
|
+
const previous = this.persistenceWrites.get(state.id) ?? Promise.resolve();
|
|
961
|
+
const write = previous.then(() => store.save(state, replay)).then(
|
|
962
|
+
() => {
|
|
963
|
+
},
|
|
964
|
+
() => {
|
|
965
|
+
}
|
|
966
|
+
);
|
|
967
|
+
this.persistenceWrites.set(state.id, write);
|
|
968
|
+
await write;
|
|
969
|
+
if (this.persistenceWrites.get(state.id) === write) this.persistenceWrites.delete(state.id);
|
|
871
970
|
}
|
|
872
971
|
async sendError(id, code, message, data) {
|
|
873
972
|
const error = { code, message };
|
|
@@ -927,9 +1026,9 @@ var StdioTransport = class {
|
|
|
927
1026
|
}
|
|
928
1027
|
send(msg) {
|
|
929
1028
|
if (this.closed) return Promise.resolve();
|
|
930
|
-
return new Promise((
|
|
1029
|
+
return new Promise((resolve5) => {
|
|
931
1030
|
const line = JSON.stringify(msg) + "\n";
|
|
932
|
-
this.stdout.write(line, "utf8", () =>
|
|
1031
|
+
this.stdout.write(line, "utf8", () => resolve5());
|
|
933
1032
|
});
|
|
934
1033
|
}
|
|
935
1034
|
sendRaw(chunk) {
|
|
@@ -942,8 +1041,8 @@ var StdioTransport = class {
|
|
|
942
1041
|
return Promise.resolve(queued.message);
|
|
943
1042
|
}
|
|
944
1043
|
if (this.closed) return Promise.resolve(null);
|
|
945
|
-
return new Promise((
|
|
946
|
-
this.resolveRead =
|
|
1044
|
+
return new Promise((resolve5) => {
|
|
1045
|
+
this.resolveRead = resolve5;
|
|
947
1046
|
});
|
|
948
1047
|
}
|
|
949
1048
|
onMessage(handler) {
|
|
@@ -1012,9 +1111,9 @@ var StdioTransport = class {
|
|
|
1012
1111
|
}
|
|
1013
1112
|
dispatch(msg, chars = JSON.stringify(msg).length) {
|
|
1014
1113
|
if (this.resolveRead) {
|
|
1015
|
-
const
|
|
1114
|
+
const resolve5 = this.resolveRead;
|
|
1016
1115
|
this.resolveRead = null;
|
|
1017
|
-
|
|
1116
|
+
resolve5(msg);
|
|
1018
1117
|
} else {
|
|
1019
1118
|
let claimed = false;
|
|
1020
1119
|
for (const handler of this.claimHandlers) {
|
|
@@ -1086,7 +1185,7 @@ var ClientTransport = class {
|
|
|
1086
1185
|
import("@wrongstack/core/utils"),
|
|
1087
1186
|
import("node:os")
|
|
1088
1187
|
]);
|
|
1089
|
-
return new Promise((
|
|
1188
|
+
return new Promise((resolve5, reject) => {
|
|
1090
1189
|
const timeout = setTimeout(() => {
|
|
1091
1190
|
const child2 = this.child;
|
|
1092
1191
|
this.child = null;
|
|
@@ -1139,7 +1238,7 @@ var ClientTransport = class {
|
|
|
1139
1238
|
if (settled) return;
|
|
1140
1239
|
settled = true;
|
|
1141
1240
|
clearTimeout(timeout);
|
|
1142
|
-
|
|
1241
|
+
resolve5();
|
|
1143
1242
|
});
|
|
1144
1243
|
return;
|
|
1145
1244
|
}
|
|
@@ -1150,7 +1249,7 @@ var ClientTransport = class {
|
|
|
1150
1249
|
child.stderr.on("data", (c) => this.onChildError(c));
|
|
1151
1250
|
child.on("close", (code) => this.onChildClose(code));
|
|
1152
1251
|
clearTimeout(timeout);
|
|
1153
|
-
|
|
1252
|
+
resolve5();
|
|
1154
1253
|
};
|
|
1155
1254
|
const waitForMarker = (chunk) => {
|
|
1156
1255
|
this.buffer += chunk;
|
|
@@ -1169,11 +1268,11 @@ var ClientTransport = class {
|
|
|
1169
1268
|
}
|
|
1170
1269
|
send(msg) {
|
|
1171
1270
|
if (!this.child) return Promise.reject(new Error("ClientTransport not started"));
|
|
1172
|
-
return new Promise((
|
|
1271
|
+
return new Promise((resolve5, reject) => {
|
|
1173
1272
|
const line = JSON.stringify(msg) + "\n";
|
|
1174
1273
|
this.child?.stdin.write(line, "utf8", (err) => {
|
|
1175
1274
|
if (err) reject(err);
|
|
1176
|
-
else
|
|
1275
|
+
else resolve5();
|
|
1177
1276
|
});
|
|
1178
1277
|
});
|
|
1179
1278
|
}
|
|
@@ -1184,8 +1283,8 @@ var ClientTransport = class {
|
|
|
1184
1283
|
return Promise.resolve(queued.message);
|
|
1185
1284
|
}
|
|
1186
1285
|
if (this.closed) return Promise.resolve(null);
|
|
1187
|
-
return new Promise((
|
|
1188
|
-
this.resolveRead =
|
|
1286
|
+
return new Promise((resolve5) => {
|
|
1287
|
+
this.resolveRead = resolve5;
|
|
1189
1288
|
});
|
|
1190
1289
|
}
|
|
1191
1290
|
onMessage(handler) {
|
|
@@ -1256,6 +1355,7 @@ var ClientTransport = class {
|
|
|
1256
1355
|
this.messageQueue.length = 0;
|
|
1257
1356
|
this.queuedChars = 0;
|
|
1258
1357
|
this.handlers.clear();
|
|
1358
|
+
this.claimHandlers.clear();
|
|
1259
1359
|
if (code !== 0 && code !== null) {
|
|
1260
1360
|
writeErr(`[acp-child exited with code ${code}]
|
|
1261
1361
|
`);
|
|
@@ -1263,20 +1363,31 @@ var ClientTransport = class {
|
|
|
1263
1363
|
}
|
|
1264
1364
|
dispatch(msg, chars = JSON.stringify(msg).length) {
|
|
1265
1365
|
if (this.resolveRead) {
|
|
1266
|
-
const
|
|
1366
|
+
const resolve5 = this.resolveRead;
|
|
1267
1367
|
this.resolveRead = null;
|
|
1268
|
-
|
|
1269
|
-
} else
|
|
1270
|
-
|
|
1271
|
-
|
|
1272
|
-
|
|
1368
|
+
resolve5(msg);
|
|
1369
|
+
} else {
|
|
1370
|
+
let claimed = false;
|
|
1371
|
+
for (const handler of this.claimHandlers) {
|
|
1372
|
+
try {
|
|
1373
|
+
if (handler(msg)) claimed = true;
|
|
1374
|
+
} catch (err) {
|
|
1375
|
+
writeErr(`[acp-child handler error] ${err}
|
|
1376
|
+
`);
|
|
1377
|
+
}
|
|
1378
|
+
}
|
|
1379
|
+
if (!claimed && this.handlers.size === 0) {
|
|
1380
|
+
if (this.messageQueue.length >= this.maxQueuedMessages || this.queuedChars + chars > this.maxQueuedChars) {
|
|
1381
|
+
writeErr(
|
|
1382
|
+
`[acp-child message queue exceeds ${this.maxQueuedMessages} entries or ${this.maxQueuedChars} characters]
|
|
1273
1383
|
`
|
|
1274
|
-
|
|
1275
|
-
|
|
1276
|
-
|
|
1384
|
+
);
|
|
1385
|
+
this.stop();
|
|
1386
|
+
return;
|
|
1387
|
+
}
|
|
1388
|
+
this.messageQueue.push({ message: msg, chars });
|
|
1389
|
+
this.queuedChars += chars;
|
|
1277
1390
|
}
|
|
1278
|
-
this.messageQueue.push({ message: msg, chars });
|
|
1279
|
-
this.queuedChars += chars;
|
|
1280
1391
|
}
|
|
1281
1392
|
for (const handler of this.handlers) {
|
|
1282
1393
|
try {
|
|
@@ -1410,7 +1521,7 @@ function toolToPriority(tool) {
|
|
|
1410
1521
|
import { createServer } from "node:http";
|
|
1411
1522
|
import { isIP } from "node:net";
|
|
1412
1523
|
import { fileURLToPath } from "node:url";
|
|
1413
|
-
import { expandIPv6, writeErr as writeErr2 } from "@wrongstack/core/utils";
|
|
1524
|
+
import { expandIPv6, isStandaloneBinary, writeErr as writeErr2 } from "@wrongstack/core/utils";
|
|
1414
1525
|
import { timingSafeTokenEqual } from "@wrongstack/primitives";
|
|
1415
1526
|
var LISTEN_RETRY_LIMIT = 5;
|
|
1416
1527
|
var LISTEN_RETRY_BASE_MS = 25;
|
|
@@ -1456,15 +1567,30 @@ var WrongStackACPServer = class {
|
|
|
1456
1567
|
this.transport.sendStartupMarker();
|
|
1457
1568
|
}
|
|
1458
1569
|
this.running = true;
|
|
1570
|
+
const prompts = /* @__PURE__ */ new Set();
|
|
1459
1571
|
try {
|
|
1460
1572
|
while (this.running) {
|
|
1461
1573
|
const msg = await this.transport.read();
|
|
1462
1574
|
if (!msg) break;
|
|
1575
|
+
if (msg.method === "session/prompt") {
|
|
1576
|
+
const task = this.handler.handleMessage(msg).then(
|
|
1577
|
+
() => {
|
|
1578
|
+
},
|
|
1579
|
+
(err) => {
|
|
1580
|
+
writeErr2(`[wstack-acp] prompt dispatch failed: ${String(err)}
|
|
1581
|
+
`);
|
|
1582
|
+
}
|
|
1583
|
+
);
|
|
1584
|
+
prompts.add(task);
|
|
1585
|
+
void task.finally(() => prompts.delete(task));
|
|
1586
|
+
continue;
|
|
1587
|
+
}
|
|
1463
1588
|
const terminal = await this.handler.handleMessage(msg);
|
|
1464
1589
|
if (terminal) break;
|
|
1465
1590
|
}
|
|
1466
1591
|
} finally {
|
|
1467
1592
|
this.handler.close();
|
|
1593
|
+
await Promise.allSettled(prompts);
|
|
1468
1594
|
this.transport.close();
|
|
1469
1595
|
}
|
|
1470
1596
|
}
|
|
@@ -1598,10 +1724,10 @@ var WrongStackACPServer = class {
|
|
|
1598
1724
|
listenWithRetry(port, host, attempt = 0) {
|
|
1599
1725
|
const server = this.httpServer;
|
|
1600
1726
|
if (!server) return Promise.resolve();
|
|
1601
|
-
return new Promise((
|
|
1727
|
+
return new Promise((resolve5, reject) => {
|
|
1602
1728
|
const onListening = () => {
|
|
1603
1729
|
server.removeListener("error", onError);
|
|
1604
|
-
|
|
1730
|
+
resolve5();
|
|
1605
1731
|
};
|
|
1606
1732
|
const onError = (err) => {
|
|
1607
1733
|
server.removeListener("listening", onListening);
|
|
@@ -1609,7 +1735,7 @@ var WrongStackACPServer = class {
|
|
|
1609
1735
|
if (port === 0 && transient && attempt < LISTEN_RETRY_LIMIT) {
|
|
1610
1736
|
const timer = setTimeout(
|
|
1611
1737
|
() => {
|
|
1612
|
-
this.listenWithRetry(port, host, attempt + 1).then(
|
|
1738
|
+
this.listenWithRetry(port, host, attempt + 1).then(resolve5, reject);
|
|
1613
1739
|
},
|
|
1614
1740
|
LISTEN_RETRY_BASE_MS * (attempt + 1)
|
|
1615
1741
|
);
|
|
@@ -1637,8 +1763,8 @@ var WrongStackACPServer = class {
|
|
|
1637
1763
|
if (!server) return;
|
|
1638
1764
|
for (const socket of this.httpSockets) socket.destroy();
|
|
1639
1765
|
this.httpSockets.clear();
|
|
1640
|
-
await new Promise((
|
|
1641
|
-
server.close(() =>
|
|
1766
|
+
await new Promise((resolve5) => {
|
|
1767
|
+
server.close(() => resolve5());
|
|
1642
1768
|
});
|
|
1643
1769
|
}
|
|
1644
1770
|
};
|
|
@@ -1669,7 +1795,7 @@ async function main() {
|
|
|
1669
1795
|
const server = new WrongStackACPServer();
|
|
1670
1796
|
await server.start();
|
|
1671
1797
|
}
|
|
1672
|
-
var isEntrypoint = process.argv[1] !== void 0 && fileURLToPath(import.meta.url) === process.argv[1];
|
|
1798
|
+
var isEntrypoint = process.argv[1] !== void 0 && !isStandaloneBinary() && fileURLToPath(import.meta.url) === process.argv[1];
|
|
1673
1799
|
if (isEntrypoint) {
|
|
1674
1800
|
main().catch((err) => {
|
|
1675
1801
|
writeErr2(`[wstack-acp fatal] ${err}
|
|
@@ -1678,6 +1804,55 @@ if (isEntrypoint) {
|
|
|
1678
1804
|
});
|
|
1679
1805
|
}
|
|
1680
1806
|
|
|
1807
|
+
// src/client/acp-late-session.ts
|
|
1808
|
+
var LATE_CANCEL_SEND_TIMEOUT_MS = 1e4;
|
|
1809
|
+
function cancelLateAcpSession(transport, createPromise) {
|
|
1810
|
+
createPromise.then(async (lateId) => {
|
|
1811
|
+
try {
|
|
1812
|
+
await new Promise((resolve5, reject) => {
|
|
1813
|
+
const timer = setTimeout(() => {
|
|
1814
|
+
reject(new Error("late session/cancel send timed out"));
|
|
1815
|
+
}, LATE_CANCEL_SEND_TIMEOUT_MS);
|
|
1816
|
+
Promise.resolve(
|
|
1817
|
+
transport.send({
|
|
1818
|
+
jsonrpc: "2.0",
|
|
1819
|
+
method: "session/cancel",
|
|
1820
|
+
params: { sessionId: lateId }
|
|
1821
|
+
})
|
|
1822
|
+
).then(
|
|
1823
|
+
() => {
|
|
1824
|
+
clearTimeout(timer);
|
|
1825
|
+
resolve5();
|
|
1826
|
+
},
|
|
1827
|
+
(sendErr) => {
|
|
1828
|
+
clearTimeout(timer);
|
|
1829
|
+
reject(sendErr instanceof Error ? sendErr : new Error(String(sendErr)));
|
|
1830
|
+
}
|
|
1831
|
+
);
|
|
1832
|
+
});
|
|
1833
|
+
} catch (err) {
|
|
1834
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
1835
|
+
console.warn(
|
|
1836
|
+
JSON.stringify({
|
|
1837
|
+
level: "warn",
|
|
1838
|
+
event: "acp_session.late_cancel_failed",
|
|
1839
|
+
sessionId: lateId,
|
|
1840
|
+
message
|
|
1841
|
+
})
|
|
1842
|
+
);
|
|
1843
|
+
}
|
|
1844
|
+
}).catch((reason) => {
|
|
1845
|
+
const message = reason instanceof Error ? reason.message : String(reason);
|
|
1846
|
+
console.warn(
|
|
1847
|
+
JSON.stringify({
|
|
1848
|
+
level: "warn",
|
|
1849
|
+
event: "acp_session.late_cancel_failed",
|
|
1850
|
+
reason: message
|
|
1851
|
+
})
|
|
1852
|
+
);
|
|
1853
|
+
});
|
|
1854
|
+
}
|
|
1855
|
+
|
|
1681
1856
|
// src/client/acp-message-routing.ts
|
|
1682
1857
|
function isBestEffortAckMethod(method) {
|
|
1683
1858
|
return method === "mcp/connect" || method === "mcp/message" || method === "mcp/disconnect" || method === "elicitation/create" || method === "elicitation/complete";
|
|
@@ -1725,6 +1900,13 @@ var FileServer = class {
|
|
|
1725
1900
|
}
|
|
1726
1901
|
/** Read a text file. Returns the content as a string. */
|
|
1727
1902
|
async readTextFile(params) {
|
|
1903
|
+
if (params.line != null && (!Number.isSafeInteger(params.line) || params.line < 1) || params.limit != null && (!Number.isSafeInteger(params.limit) || params.limit < 0)) {
|
|
1904
|
+
throw new FsError(
|
|
1905
|
+
"INVALID_PATH",
|
|
1906
|
+
params.path,
|
|
1907
|
+
"line must be a positive integer and limit a non-negative integer"
|
|
1908
|
+
);
|
|
1909
|
+
}
|
|
1728
1910
|
const safe = await this.resolveInside(params.path);
|
|
1729
1911
|
const controller = new AbortController();
|
|
1730
1912
|
const timer = setTimeout(() => controller.abort(), this.timeoutMs);
|
|
@@ -1743,7 +1925,12 @@ var FileServer = class {
|
|
|
1743
1925
|
encoding: "utf8",
|
|
1744
1926
|
signal: controller.signal
|
|
1745
1927
|
});
|
|
1746
|
-
return { content };
|
|
1928
|
+
if (params.line == null && params.limit == null) return { content };
|
|
1929
|
+
const lines = content.match(/[^\n]*\n|[^\n]+$/g) ?? [];
|
|
1930
|
+
const start = (params.line ?? 1) - 1;
|
|
1931
|
+
return {
|
|
1932
|
+
content: lines.slice(start, params.limit == null ? void 0 : start + params.limit).join("")
|
|
1933
|
+
};
|
|
1747
1934
|
} catch (err) {
|
|
1748
1935
|
if (err instanceof FsError) throw err;
|
|
1749
1936
|
if (controller.signal.aborted) {
|
|
@@ -1848,7 +2035,8 @@ var FileServer = class {
|
|
|
1848
2035
|
throw mapFsError(err, resolvedPath);
|
|
1849
2036
|
}
|
|
1850
2037
|
const normReal = isWindows ? real.toLowerCase() : real;
|
|
1851
|
-
|
|
2038
|
+
const realRootWithSep = normRealRoot.endsWith(path2.sep) ? normRealRoot : normRealRoot + path2.sep;
|
|
2039
|
+
if (normReal === normRealRoot || normReal.startsWith(realRootWithSep)) return;
|
|
1852
2040
|
throw new FsError(
|
|
1853
2041
|
"OUTSIDE_ROOT",
|
|
1854
2042
|
resolvedPath,
|
|
@@ -1897,9 +2085,12 @@ async function handleAcpPermissionRequest(msg, permissionPolicy, sender, callbac
|
|
|
1897
2085
|
);
|
|
1898
2086
|
await sender.sendResult(id, { outcome });
|
|
1899
2087
|
} catch (err) {
|
|
2088
|
+
if (isAbortLikeError(err)) {
|
|
2089
|
+
await sender.sendResult(id, { outcome: { outcome: "cancelled" } });
|
|
2090
|
+
return;
|
|
2091
|
+
}
|
|
1900
2092
|
const message = err instanceof Error ? err.message : String(err);
|
|
1901
|
-
|
|
1902
|
-
await sender.sendErrorResponse(id, code, `permission policy failed: ${message}`);
|
|
2093
|
+
await sender.sendErrorResponse(id, -32603, `permission policy failed: ${message}`);
|
|
1903
2094
|
}
|
|
1904
2095
|
}
|
|
1905
2096
|
async function handleAcpFsRequest(msg, fileServer, permissionPolicy, sender, callbackOptions = {}) {
|
|
@@ -1935,7 +2126,9 @@ async function handleAcpFsRequest(msg, fileServer, permissionPolicy, sender, cal
|
|
|
1935
2126
|
if (msg.method === "fs/read_text_file") {
|
|
1936
2127
|
const result = await fileServer.readTextFile({
|
|
1937
2128
|
sessionId: params.sessionId ?? "",
|
|
1938
|
-
path: params.path
|
|
2129
|
+
path: params.path,
|
|
2130
|
+
line: params.line,
|
|
2131
|
+
limit: params.limit
|
|
1939
2132
|
});
|
|
1940
2133
|
await sender.sendResult(id, result);
|
|
1941
2134
|
} else {
|
|
@@ -2185,7 +2378,7 @@ async function executeLoadSession(ctx, sessionId, mcpServers, cwd) {
|
|
|
2185
2378
|
"agent does not support session/load (loadSession capability not advertised)"
|
|
2186
2379
|
);
|
|
2187
2380
|
}
|
|
2188
|
-
if (ctx.sessionId) {
|
|
2381
|
+
if (ctx.sessionId && ctx.sessionId !== sessionId) {
|
|
2189
2382
|
await ctx.closeSession();
|
|
2190
2383
|
}
|
|
2191
2384
|
ctx.resetScratch();
|
|
@@ -2209,7 +2402,7 @@ async function executeResumeSession(ctx, sessionId, mcpServers, cwd) {
|
|
|
2209
2402
|
"agent does not support session/resume (sessionCapabilities.resume not advertised)"
|
|
2210
2403
|
);
|
|
2211
2404
|
}
|
|
2212
|
-
if (ctx.sessionId) {
|
|
2405
|
+
if (ctx.sessionId && ctx.sessionId !== sessionId) {
|
|
2213
2406
|
await ctx.closeSession();
|
|
2214
2407
|
}
|
|
2215
2408
|
const servers = filterMcpServers(ctx.agentCapabilities, mcpServers ?? ctx.opts.mcpServers);
|
|
@@ -2387,6 +2580,7 @@ function handleAcpSessionUpdate(msg, scratch, emitProgress) {
|
|
|
2387
2580
|
}
|
|
2388
2581
|
return;
|
|
2389
2582
|
}
|
|
2583
|
+
case "agent_thought_chunk":
|
|
2390
2584
|
case "thought_chunk": {
|
|
2391
2585
|
const text = extractText(u.content);
|
|
2392
2586
|
if (text) {
|
|
@@ -2536,7 +2730,9 @@ var TerminalServer = class {
|
|
|
2536
2730
|
this.instanceId = `term_srv_${randomBytes2(4).toString("hex")}`;
|
|
2537
2731
|
this.maxTerminals = this.clampFiniteInt(opts.maxTerminals, 32);
|
|
2538
2732
|
this.abortSignal = opts.signal;
|
|
2539
|
-
if (opts.signal) {
|
|
2733
|
+
if (opts.signal?.aborted) {
|
|
2734
|
+
this.dispose();
|
|
2735
|
+
} else if (opts.signal) {
|
|
2540
2736
|
opts.signal.addEventListener("abort", this.abortHandler, { once: true });
|
|
2541
2737
|
}
|
|
2542
2738
|
}
|
|
@@ -2582,7 +2778,7 @@ var TerminalServer = class {
|
|
|
2582
2778
|
truncated: false,
|
|
2583
2779
|
exitStatus: void 0,
|
|
2584
2780
|
timeoutHandle: null,
|
|
2585
|
-
exitPromise: new Promise((
|
|
2781
|
+
exitPromise: new Promise((resolve5) => {
|
|
2586
2782
|
proc.on("close", (code, signalName) => {
|
|
2587
2783
|
if (state.timeoutHandle) {
|
|
2588
2784
|
clearTimeout(state.timeoutHandle);
|
|
@@ -2593,7 +2789,7 @@ var TerminalServer = class {
|
|
|
2593
2789
|
signal: typeof signalName === "string" ? signalName : null
|
|
2594
2790
|
};
|
|
2595
2791
|
state.exitStatus = exitStatus;
|
|
2596
|
-
|
|
2792
|
+
resolve5(exitStatus);
|
|
2597
2793
|
});
|
|
2598
2794
|
proc.on("error", (err) => {
|
|
2599
2795
|
if (state.timeoutHandle) {
|
|
@@ -2612,7 +2808,7 @@ var TerminalServer = class {
|
|
|
2612
2808
|
}
|
|
2613
2809
|
state.outputChunks.push(errorOutput);
|
|
2614
2810
|
state.retainedBytes = errorOutput.length;
|
|
2615
|
-
|
|
2811
|
+
resolve5(exitStatus);
|
|
2616
2812
|
});
|
|
2617
2813
|
})
|
|
2618
2814
|
};
|
|
@@ -2919,7 +3115,7 @@ var WebSocketClientTransport = class {
|
|
|
2919
3115
|
);
|
|
2920
3116
|
}
|
|
2921
3117
|
const timeoutMs = this.opts.handshakeTimeoutMs ?? 3e4;
|
|
2922
|
-
return new Promise((
|
|
3118
|
+
return new Promise((resolve5, reject) => {
|
|
2923
3119
|
const ws = new WS(this.opts.url, this.opts.protocols);
|
|
2924
3120
|
this.ws = ws;
|
|
2925
3121
|
const timer = setTimeout(() => {
|
|
@@ -2935,7 +3131,7 @@ var WebSocketClientTransport = class {
|
|
|
2935
3131
|
}
|
|
2936
3132
|
pending.reject(new Error(`WebSocket failed to open within ${timeoutMs}ms`));
|
|
2937
3133
|
}, timeoutMs);
|
|
2938
|
-
this.pendingStart = { resolve:
|
|
3134
|
+
this.pendingStart = { resolve: resolve5, reject, timer };
|
|
2939
3135
|
ws.addEventListener("open", () => {
|
|
2940
3136
|
const pending = this.pendingStart;
|
|
2941
3137
|
if (pending === null) return;
|
|
@@ -3050,7 +3246,6 @@ function finitePositiveLimit(value, fallback) {
|
|
|
3050
3246
|
}
|
|
3051
3247
|
|
|
3052
3248
|
// src/client/acp-session.ts
|
|
3053
|
-
var LATE_CANCEL_SEND_TIMEOUT_MS = 1e4;
|
|
3054
3249
|
var ACPSession = class _ACPSession {
|
|
3055
3250
|
transport;
|
|
3056
3251
|
fileServer;
|
|
@@ -3220,7 +3415,7 @@ var ACPSession = class _ACPSession {
|
|
|
3220
3415
|
throw new ACPSessionError("protocol_error", "initialize returned no protocolVersion");
|
|
3221
3416
|
}
|
|
3222
3417
|
const r = result;
|
|
3223
|
-
if (r.protocolVersion
|
|
3418
|
+
if (r.protocolVersion !== ACP_PROTOCOL_VERSION) {
|
|
3224
3419
|
throw new ACPSessionError(
|
|
3225
3420
|
"unsupported_capability",
|
|
3226
3421
|
`agent requires protocolVersion=${r.protocolVersion}, client supports up to ${ACP_PROTOCOL_VERSION}`
|
|
@@ -3256,6 +3451,12 @@ var ACPSession = class _ACPSession {
|
|
|
3256
3451
|
`auth method "${methodId}" not in advertised methods: ${this.authMethods.map((m) => m.id).join(", ")}`
|
|
3257
3452
|
);
|
|
3258
3453
|
}
|
|
3454
|
+
if (this.authMethods.find((m) => m.id === methodId)?.type === "terminal") {
|
|
3455
|
+
throw new ACPSessionError(
|
|
3456
|
+
"auth_failed",
|
|
3457
|
+
"Terminal authentication requires interactive login followed by reconnect; it cannot use authenticate"
|
|
3458
|
+
);
|
|
3459
|
+
}
|
|
3259
3460
|
const id = this.allocId();
|
|
3260
3461
|
const result = await this.sendRequest(id, "authenticate", { methodId });
|
|
3261
3462
|
if (isJsonRpcError(result)) {
|
|
@@ -3351,6 +3552,16 @@ var ACPSession = class _ACPSession {
|
|
|
3351
3552
|
if (signal.aborted) {
|
|
3352
3553
|
return emptyRunResult("cancelled");
|
|
3353
3554
|
}
|
|
3555
|
+
const caps = this.agentCapabilities.promptCapabilities;
|
|
3556
|
+
for (const block of blocks) {
|
|
3557
|
+
const supported = block.type === "image" ? caps?.image : block.type === "audio" ? caps?.audio : block.type === "resource" ? caps?.embeddedContext : true;
|
|
3558
|
+
if (supported !== true) {
|
|
3559
|
+
throw new ACPSessionError(
|
|
3560
|
+
"unsupported_capability",
|
|
3561
|
+
`agent does not support ${block.type} prompt content`
|
|
3562
|
+
);
|
|
3563
|
+
}
|
|
3564
|
+
}
|
|
3354
3565
|
let cancelled = false;
|
|
3355
3566
|
this.promptCallbackAbort = new AbortController();
|
|
3356
3567
|
const onAbort = () => {
|
|
@@ -3485,50 +3696,7 @@ var ACPSession = class _ACPSession {
|
|
|
3485
3696
|
* being silently swallowed.
|
|
3486
3697
|
*/
|
|
3487
3698
|
cancelLateSession(createPromise) {
|
|
3488
|
-
|
|
3489
|
-
try {
|
|
3490
|
-
await new Promise((resolve4, reject) => {
|
|
3491
|
-
const timer = setTimeout(() => {
|
|
3492
|
-
reject(new Error("late session/cancel send timed out"));
|
|
3493
|
-
}, LATE_CANCEL_SEND_TIMEOUT_MS);
|
|
3494
|
-
Promise.resolve(
|
|
3495
|
-
this.transport.send({
|
|
3496
|
-
jsonrpc: "2.0",
|
|
3497
|
-
method: "session/cancel",
|
|
3498
|
-
params: { sessionId: lateId }
|
|
3499
|
-
})
|
|
3500
|
-
).then(
|
|
3501
|
-
() => {
|
|
3502
|
-
clearTimeout(timer);
|
|
3503
|
-
resolve4();
|
|
3504
|
-
},
|
|
3505
|
-
(sendErr) => {
|
|
3506
|
-
clearTimeout(timer);
|
|
3507
|
-
reject(sendErr instanceof Error ? sendErr : new Error(String(sendErr)));
|
|
3508
|
-
}
|
|
3509
|
-
);
|
|
3510
|
-
});
|
|
3511
|
-
} catch (err) {
|
|
3512
|
-
const message = err instanceof Error ? err.message : String(err);
|
|
3513
|
-
console.warn(
|
|
3514
|
-
JSON.stringify({
|
|
3515
|
-
level: "warn",
|
|
3516
|
-
event: "acp_session.late_cancel_failed",
|
|
3517
|
-
sessionId: lateId,
|
|
3518
|
-
message
|
|
3519
|
-
})
|
|
3520
|
-
);
|
|
3521
|
-
}
|
|
3522
|
-
}).catch((reason) => {
|
|
3523
|
-
const message = reason instanceof Error ? reason.message : String(reason);
|
|
3524
|
-
console.warn(
|
|
3525
|
-
JSON.stringify({
|
|
3526
|
-
level: "warn",
|
|
3527
|
-
event: "acp_session.late_cancel_failed",
|
|
3528
|
-
reason: message
|
|
3529
|
-
})
|
|
3530
|
-
);
|
|
3531
|
-
});
|
|
3699
|
+
cancelLateAcpSession(this.transport, createPromise);
|
|
3532
3700
|
}
|
|
3533
3701
|
async closeSession() {
|
|
3534
3702
|
if (!this.sessionId) return;
|
|
@@ -3575,10 +3743,16 @@ var ACPSession = class _ACPSession {
|
|
|
3575
3743
|
// Wire layer
|
|
3576
3744
|
// ────────────────────────────────────────────────────────────────────
|
|
3577
3745
|
allocId() {
|
|
3578
|
-
|
|
3746
|
+
let candidate = Number.isSafeInteger(this.nextId) && this.nextId >= 1 ? this.nextId : 1;
|
|
3747
|
+
for (let probe = 0; probe <= this.pending.size; probe++) {
|
|
3748
|
+
this.nextId = candidate >= Number.MAX_SAFE_INTEGER ? 1 : candidate + 1;
|
|
3749
|
+
if (!this.pending.has(candidate)) return candidate;
|
|
3750
|
+
candidate = this.nextId;
|
|
3751
|
+
}
|
|
3752
|
+
throw new ACPSessionError("protocol_error", "no JSON-RPC request id is available");
|
|
3579
3753
|
}
|
|
3580
3754
|
async sendRequest(id, method, params, timeoutMs) {
|
|
3581
|
-
return new Promise((
|
|
3755
|
+
return new Promise((resolve5, reject) => {
|
|
3582
3756
|
const effectiveTimeout = timeoutMs ?? this.timeoutMs;
|
|
3583
3757
|
const handle = setTimeout(() => {
|
|
3584
3758
|
this.pending.delete(id);
|
|
@@ -3588,7 +3762,7 @@ var ACPSession = class _ACPSession {
|
|
|
3588
3762
|
}, effectiveTimeout);
|
|
3589
3763
|
this.pending.set(id, {
|
|
3590
3764
|
method,
|
|
3591
|
-
resolve:
|
|
3765
|
+
resolve: resolve5,
|
|
3592
3766
|
reject,
|
|
3593
3767
|
timeoutMs: effectiveTimeout,
|
|
3594
3768
|
timeoutHandle: handle
|
|
@@ -3643,7 +3817,7 @@ var ACPSession = class _ACPSession {
|
|
|
3643
3817
|
}
|
|
3644
3818
|
const terminal = this.authMethods.find((m) => m.type === "terminal");
|
|
3645
3819
|
const setupArgs = terminal?.args?.length ? terminal.args.join(" ") : void 0;
|
|
3646
|
-
const setup = setupArgs !== void 0 ? `${this.opts.command} ${setupArgs}` : `${this.opts.command}${this.opts.args?.length ? ` ${this.opts.args.join(" ")}` : ""}`;
|
|
3820
|
+
const setup = setupArgs !== void 0 ? `${this.opts.command}${this.opts.args?.length ? ` ${this.opts.args.join(" ")}` : ""} ${setupArgs}` : `${this.opts.command}${this.opts.args?.length ? ` ${this.opts.args.join(" ")}` : ""}`;
|
|
3647
3821
|
throw new ACPSessionError(
|
|
3648
3822
|
"auth_failed",
|
|
3649
3823
|
`This agent requires a terminal login before ACP can start. Run \`${setup}\` (or the CLI's /login), then retry.`
|
|
@@ -3725,7 +3899,7 @@ var ACPSession = class _ACPSession {
|
|
|
3725
3899
|
}
|
|
3726
3900
|
if (isBestEffortAckMethod(msg.method)) {
|
|
3727
3901
|
if (msg.id !== void 0) {
|
|
3728
|
-
this.
|
|
3902
|
+
this.sendErrorResponse(msg.id, -32601, `Unsupported method: ${msg.method}`).catch(() => {
|
|
3729
3903
|
});
|
|
3730
3904
|
}
|
|
3731
3905
|
return;
|
|
@@ -3734,6 +3908,13 @@ var ACPSession = class _ACPSession {
|
|
|
3734
3908
|
return;
|
|
3735
3909
|
}
|
|
3736
3910
|
if (msg.method) {
|
|
3911
|
+
if (msg.id !== void 0) {
|
|
3912
|
+
void this.sendErrorResponse(msg.id, -32601, `Unsupported method: ${msg.method}`).catch(
|
|
3913
|
+
() => {
|
|
3914
|
+
}
|
|
3915
|
+
);
|
|
3916
|
+
return;
|
|
3917
|
+
}
|
|
3737
3918
|
console.warn(
|
|
3738
3919
|
JSON.stringify({
|
|
3739
3920
|
level: "warn",
|
|
@@ -3807,12 +3988,12 @@ var ToolTranslator = class {
|
|
|
3807
3988
|
id: callId,
|
|
3808
3989
|
params: { name, arguments: args }
|
|
3809
3990
|
});
|
|
3810
|
-
return new Promise((
|
|
3991
|
+
return new Promise((resolve5, reject) => {
|
|
3811
3992
|
const timeout = setTimeout(() => {
|
|
3812
3993
|
this.pending.delete(callId);
|
|
3813
3994
|
reject(new Error(`Tool call ${name} timed out after ${this.opts.totalTimeoutMs}ms`));
|
|
3814
3995
|
}, this.opts.totalTimeoutMs);
|
|
3815
|
-
this.pending.set(callId, { resolve:
|
|
3996
|
+
this.pending.set(callId, { resolve: resolve5, reject, timeout });
|
|
3816
3997
|
});
|
|
3817
3998
|
}
|
|
3818
3999
|
cancelAll() {
|
|
@@ -4635,7 +4816,7 @@ async function probeWithBound(items, worker, limit) {
|
|
|
4635
4816
|
}
|
|
4636
4817
|
async function defaultProbe(desc, timeoutMs, spawnProcess = spawn2, platform = process.platform) {
|
|
4637
4818
|
const start = Date.now();
|
|
4638
|
-
return new Promise((
|
|
4819
|
+
return new Promise((resolve5) => {
|
|
4639
4820
|
let settled = false;
|
|
4640
4821
|
let stdout = "";
|
|
4641
4822
|
let stderr = "";
|
|
@@ -4648,7 +4829,7 @@ async function defaultProbe(desc, timeoutMs, spawnProcess = spawn2, platform = p
|
|
|
4648
4829
|
}
|
|
4649
4830
|
} catch {
|
|
4650
4831
|
}
|
|
4651
|
-
|
|
4832
|
+
resolve5(result);
|
|
4652
4833
|
};
|
|
4653
4834
|
let child;
|
|
4654
4835
|
try {
|