@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/types/acp-v1.d.ts
CHANGED
|
@@ -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 (
|
|
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
|
-
|
|
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:
|
|
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
|
-
|
|
322
|
-
|
|
323
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
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
|
-
|
|
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: {
|
|
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:
|
|
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, {
|
|
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: {
|
|
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
|
|
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:
|
|
566
|
+
configOptions: options
|
|
521
567
|
}
|
|
522
568
|
});
|
|
523
|
-
await ctx.
|
|
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
|
-
|
|
591
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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(
|
|
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.
|
|
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
|
-
|
|
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
|
|
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
|
|
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
|
-
|
|
872
|
-
|
|
873
|
-
|
|
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((
|
|
1026
|
+
return new Promise((resolve3) => {
|
|
928
1027
|
const line = JSON.stringify(msg) + "\n";
|
|
929
|
-
this.stdout.write(line, "utf8", () =>
|
|
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((
|
|
943
|
-
this.resolveRead =
|
|
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
|
|
1111
|
+
const resolve3 = this.resolveRead;
|
|
1013
1112
|
this.resolveRead = null;
|
|
1014
|
-
|
|
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((
|
|
1359
|
+
return new Promise((resolve3, reject) => {
|
|
1246
1360
|
const onListening = () => {
|
|
1247
1361
|
server.removeListener("error", onError);
|
|
1248
|
-
|
|
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(
|
|
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((
|
|
1285
|
-
server.close(() =>
|
|
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.
|
|
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.
|
|
56
|
-
"@wrongstack/primitives": "1.0.
|
|
55
|
+
"@wrongstack/core": "1.0.24",
|
|
56
|
+
"@wrongstack/primitives": "1.0.24"
|
|
57
57
|
},
|
|
58
58
|
"devDependencies": {
|
|
59
59
|
"@types/node": "^26.5.1",
|