@wrongstack/acp 0.274.0 → 0.275.1
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/README.md +376 -0
- package/dist/acp-subagent-runner-BAlo23L-.d.ts +644 -0
- package/dist/acp-v1-BxskPsdo.d.ts +520 -0
- package/dist/agent.d.ts +34 -61
- package/dist/agent.js +796 -32
- package/dist/agent.js.map +1 -1
- package/dist/client.d.ts +3 -2
- package/dist/client.js +779 -112
- package/dist/client.js.map +1 -1
- package/dist/index-DEEYyEpu.d.ts +54 -0
- package/dist/index.d.ts +186 -227
- package/dist/index.js +1881 -286
- package/dist/index.js.map +1 -1
- package/dist/sdk.d.ts +12 -0
- package/dist/sdk.js +3350 -0
- package/dist/sdk.js.map +1 -0
- package/dist/server-agent-turn-C3U0lhA-.d.ts +163 -0
- package/dist/terminal-server-P9KpMZTT.d.ts +99 -0
- package/dist/{tools-registry-BCf8evEG.d.ts → tools-registry-D2xdbzN7.d.ts} +1 -1
- package/dist/wrongstack-acp-agent-nzrqmJnc.d.ts +341 -0
- package/dist/wrongstack-acp-agent.d.ts +2 -2
- package/dist/wrongstack-acp-agent.js +426 -26
- package/dist/wrongstack-acp-agent.js.map +1 -1
- package/package.json +7 -2
- package/dist/index-BvPqJHhm.d.ts +0 -119
- package/dist/stdio-transport-CsFr8JzC.d.ts +0 -205
- package/dist/wrongstack-acp-agent-Dv-A0bEm.d.ts +0 -310
package/dist/agent.js
CHANGED
|
@@ -1,5 +1,8 @@
|
|
|
1
1
|
import { writeErr, expectDefined } from '@wrongstack/core';
|
|
2
|
+
import * as fsp from 'fs/promises';
|
|
3
|
+
import * as path from 'path';
|
|
2
4
|
import { fileURLToPath } from 'url';
|
|
5
|
+
import { createServer } from 'http';
|
|
3
6
|
|
|
4
7
|
// src/agent/stdio-transport.ts
|
|
5
8
|
var StdioTransport = class {
|
|
@@ -212,7 +215,16 @@ var ACP_PROTOCOL_VERSION = 1;
|
|
|
212
215
|
function toWire(msg) {
|
|
213
216
|
return msg;
|
|
214
217
|
}
|
|
215
|
-
var WRONGSTACK_VERSION = "0.
|
|
218
|
+
var WRONGSTACK_VERSION = "0.274.1";
|
|
219
|
+
var WRONGSTACK_AUTH_METHODS = [
|
|
220
|
+
{
|
|
221
|
+
id: "wrongstack-auth",
|
|
222
|
+
name: "Run wstack auth",
|
|
223
|
+
description: "Configure a WrongStack model provider in an interactive terminal.",
|
|
224
|
+
type: "terminal",
|
|
225
|
+
args: ["auth"]
|
|
226
|
+
}
|
|
227
|
+
];
|
|
216
228
|
var DEFAULT_MODE_ID = "code";
|
|
217
229
|
var DEFAULT_MODES = [
|
|
218
230
|
{
|
|
@@ -229,9 +241,17 @@ var ACPProtocolHandler = class {
|
|
|
229
241
|
modes;
|
|
230
242
|
configOptions;
|
|
231
243
|
agentName;
|
|
244
|
+
replayFor;
|
|
245
|
+
seedFor;
|
|
246
|
+
store;
|
|
232
247
|
initialized = false;
|
|
248
|
+
clientCapabilities = {};
|
|
233
249
|
sessions = /* @__PURE__ */ new Map();
|
|
234
250
|
nextId = 1;
|
|
251
|
+
// Outbound request correlation (server → client requests, e.g.
|
|
252
|
+
// session/request_permission). Keyed by our own `srv_N` ids.
|
|
253
|
+
pendingOut = /* @__PURE__ */ new Map();
|
|
254
|
+
nextOutId = 1;
|
|
235
255
|
constructor(opts) {
|
|
236
256
|
this.transport = opts.transport;
|
|
237
257
|
this.defaultCwd = opts.defaultCwd;
|
|
@@ -241,6 +261,43 @@ var ACPProtocolHandler = class {
|
|
|
241
261
|
this.modes = opts.modes ?? DEFAULT_MODES;
|
|
242
262
|
this.configOptions = opts.configOptions ?? [];
|
|
243
263
|
this.agentName = opts.agentName ?? "wrongstack";
|
|
264
|
+
this.replayFor = opts.replayFor;
|
|
265
|
+
this.seedFor = opts.seedFor;
|
|
266
|
+
this.store = opts.store;
|
|
267
|
+
if (typeof this.transport.onMessage === "function") {
|
|
268
|
+
this.transport.onMessage((m) => this.maybeResolvePending(m));
|
|
269
|
+
}
|
|
270
|
+
}
|
|
271
|
+
/**
|
|
272
|
+
* Send a request to the client and await its response. Used for
|
|
273
|
+
* server-initiated calls like `session/request_permission`. Rejects on
|
|
274
|
+
* timeout or transport error so the caller can pick a safe fallback.
|
|
275
|
+
*/
|
|
276
|
+
request(method, params, timeoutMs = 6e4) {
|
|
277
|
+
const id = `srv_${this.nextOutId++}`;
|
|
278
|
+
return new Promise((resolve, reject) => {
|
|
279
|
+
const timer = setTimeout(() => {
|
|
280
|
+
this.pendingOut.delete(id);
|
|
281
|
+
reject(new Error(`${method} timed out after ${timeoutMs}ms`));
|
|
282
|
+
}, timeoutMs);
|
|
283
|
+
this.pendingOut.set(id, { resolve, reject, timer });
|
|
284
|
+
this.transport.send(toWire({ jsonrpc: "2.0", id, method, params })).catch((e) => {
|
|
285
|
+
clearTimeout(timer);
|
|
286
|
+
this.pendingOut.delete(id);
|
|
287
|
+
reject(e instanceof Error ? e : new Error(String(e)));
|
|
288
|
+
});
|
|
289
|
+
});
|
|
290
|
+
}
|
|
291
|
+
maybeResolvePending(m) {
|
|
292
|
+
const id = m.id;
|
|
293
|
+
if (typeof id !== "string") return;
|
|
294
|
+
const pending = this.pendingOut.get(id);
|
|
295
|
+
if (!pending) return;
|
|
296
|
+
this.pendingOut.delete(id);
|
|
297
|
+
clearTimeout(pending.timer);
|
|
298
|
+
const err = m.error;
|
|
299
|
+
if (err) pending.reject(new Error(err.message ?? "client request failed"));
|
|
300
|
+
else pending.resolve(m.result);
|
|
244
301
|
}
|
|
245
302
|
/**
|
|
246
303
|
* Process one inbound message. Returns true if this was a terminal
|
|
@@ -267,6 +324,11 @@ var ACPProtocolHandler = class {
|
|
|
267
324
|
session.abort.abort();
|
|
268
325
|
}
|
|
269
326
|
this.sessions.clear();
|
|
327
|
+
for (const [, p] of this.pendingOut) {
|
|
328
|
+
clearTimeout(p.timer);
|
|
329
|
+
p.reject(new Error("protocol handler closed"));
|
|
330
|
+
}
|
|
331
|
+
this.pendingOut.clear();
|
|
270
332
|
}
|
|
271
333
|
// ────────────────────────────────────────────────────────────────────
|
|
272
334
|
// Requests
|
|
@@ -282,10 +344,18 @@ var ACPProtocolHandler = class {
|
|
|
282
344
|
return await this.handleInitialize(id, params);
|
|
283
345
|
case "authenticate":
|
|
284
346
|
return await this.handleAuthenticate(id, params);
|
|
347
|
+
case "logout":
|
|
348
|
+
return await this.handleLogout(id, params);
|
|
285
349
|
case "session/new":
|
|
286
350
|
return await this.handleSessionNew(id, params);
|
|
287
351
|
case "session/load":
|
|
288
352
|
return await this.handleSessionLoad(id, params);
|
|
353
|
+
case "session/resume":
|
|
354
|
+
return await this.handleSessionResume(id, params);
|
|
355
|
+
case "session/close":
|
|
356
|
+
return await this.handleSessionClose(id, params);
|
|
357
|
+
case "session/delete":
|
|
358
|
+
return await this.handleSessionDelete(id, params);
|
|
289
359
|
case "session/prompt":
|
|
290
360
|
return await this.handleSessionPrompt(id, params);
|
|
291
361
|
case "session/set_mode":
|
|
@@ -294,6 +364,16 @@ var ACPProtocolHandler = class {
|
|
|
294
364
|
return await this.handleSetConfigOption(id, params);
|
|
295
365
|
case "session/list":
|
|
296
366
|
return await this.handleSessionList(id);
|
|
367
|
+
case "session/fork":
|
|
368
|
+
return await this.handleSessionFork(id, params);
|
|
369
|
+
case "providers/list":
|
|
370
|
+
return await this.handleProvidersList(id, params);
|
|
371
|
+
case "providers/set":
|
|
372
|
+
return await this.handleProvidersSet(id, params);
|
|
373
|
+
case "providers/disable":
|
|
374
|
+
return await this.handleProvidersDisable(id, params);
|
|
375
|
+
case "mcp/message":
|
|
376
|
+
return await this.handleMcpMessage(id, params);
|
|
297
377
|
default:
|
|
298
378
|
await this.sendError(id, -32601, `Unknown method: ${method}`);
|
|
299
379
|
return false;
|
|
@@ -306,14 +386,8 @@ var ACPProtocolHandler = class {
|
|
|
306
386
|
}
|
|
307
387
|
async handleInitialize(id, params) {
|
|
308
388
|
const p = params ?? {};
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
await this.sendError(
|
|
312
|
-
id,
|
|
313
|
-
-32e3,
|
|
314
|
-
`server speaks protocolVersion=${ACP_PROTOCOL_VERSION}, client requested ${requested}`
|
|
315
|
-
);
|
|
316
|
-
return false;
|
|
389
|
+
if (p.clientCapabilities && typeof p.clientCapabilities === "object") {
|
|
390
|
+
this.clientCapabilities = p.clientCapabilities;
|
|
317
391
|
}
|
|
318
392
|
this.initialized = true;
|
|
319
393
|
await this.transport.send(toWire({
|
|
@@ -324,9 +398,25 @@ var ACPProtocolHandler = class {
|
|
|
324
398
|
agentCapabilities: {
|
|
325
399
|
loadSession: true,
|
|
326
400
|
promptCapabilities: {
|
|
327
|
-
image
|
|
401
|
+
// We route ACP image blocks into the core agent's multimodal
|
|
402
|
+
// input (server-agent-turn.promptToAgentInput); whether the
|
|
403
|
+
// model can see them is the configured provider's concern.
|
|
404
|
+
image: true,
|
|
328
405
|
audio: false,
|
|
329
406
|
embeddedContext: true
|
|
407
|
+
},
|
|
408
|
+
mcpCapabilities: {
|
|
409
|
+
http: false,
|
|
410
|
+
sse: false
|
|
411
|
+
},
|
|
412
|
+
sessionCapabilities: {
|
|
413
|
+
close: {},
|
|
414
|
+
list: {},
|
|
415
|
+
delete: {},
|
|
416
|
+
resume: {}
|
|
417
|
+
},
|
|
418
|
+
auth: {
|
|
419
|
+
logout: {}
|
|
330
420
|
}
|
|
331
421
|
},
|
|
332
422
|
agentInfo: {
|
|
@@ -334,10 +424,7 @@ var ACPProtocolHandler = class {
|
|
|
334
424
|
title: "WrongStack",
|
|
335
425
|
version: WRONGSTACK_VERSION
|
|
336
426
|
},
|
|
337
|
-
|
|
338
|
-
// re-sent on every `current_mode_update` / `config_option_update`
|
|
339
|
-
// notification so late-joining clients see them.
|
|
340
|
-
authMethods: [],
|
|
427
|
+
authMethods: WRONGSTACK_AUTH_METHODS,
|
|
341
428
|
modes: this.modes,
|
|
342
429
|
configOptions: this.configOptions
|
|
343
430
|
}
|
|
@@ -352,6 +439,14 @@ var ACPProtocolHandler = class {
|
|
|
352
439
|
}));
|
|
353
440
|
return false;
|
|
354
441
|
}
|
|
442
|
+
async handleLogout(id, _params) {
|
|
443
|
+
await this.transport.send(toWire({
|
|
444
|
+
jsonrpc: "2.0",
|
|
445
|
+
id,
|
|
446
|
+
result: {}
|
|
447
|
+
}));
|
|
448
|
+
return false;
|
|
449
|
+
}
|
|
355
450
|
async handleSessionNew(id, params) {
|
|
356
451
|
const p = params ?? {};
|
|
357
452
|
const cwd = typeof p.cwd === "string" ? p.cwd : this.defaultCwd;
|
|
@@ -367,6 +462,7 @@ var ACPProtocolHandler = class {
|
|
|
367
462
|
};
|
|
368
463
|
this.sessions.set(sessionId, state);
|
|
369
464
|
this.onSessionNew(state);
|
|
465
|
+
await this.persist(state);
|
|
370
466
|
await this.sendNotification({
|
|
371
467
|
sessionId,
|
|
372
468
|
update: {
|
|
@@ -395,7 +491,173 @@ var ACPProtocolHandler = class {
|
|
|
395
491
|
return false;
|
|
396
492
|
}
|
|
397
493
|
async handleSessionLoad(id, params) {
|
|
398
|
-
|
|
494
|
+
const p = params ?? {};
|
|
495
|
+
const sessionId = typeof p.sessionId === "string" ? p.sessionId : null;
|
|
496
|
+
const loadCwd = typeof p.cwd === "string" ? p.cwd : void 0;
|
|
497
|
+
let existing = sessionId ? this.sessions.get(sessionId) : void 0;
|
|
498
|
+
if (!existing && sessionId && this.store) {
|
|
499
|
+
const persisted = await this.store.load(sessionId);
|
|
500
|
+
if (persisted) {
|
|
501
|
+
const restored = {
|
|
502
|
+
id: sessionId,
|
|
503
|
+
cwd: persisted.cwd ?? loadCwd ?? this.defaultCwd,
|
|
504
|
+
abort: new AbortController(),
|
|
505
|
+
modeId: persisted.modeId ?? DEFAULT_MODE_ID,
|
|
506
|
+
createdAt: persisted.createdAt ?? (/* @__PURE__ */ new Date()).toISOString(),
|
|
507
|
+
updatedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
508
|
+
...persisted.title !== void 0 ? { title: persisted.title } : {}
|
|
509
|
+
};
|
|
510
|
+
this.sessions.set(sessionId, restored);
|
|
511
|
+
this.seedFor?.(sessionId, persisted.history ?? []);
|
|
512
|
+
for (const update of persisted.history ?? []) {
|
|
513
|
+
await this.sendNotification({ sessionId, update });
|
|
514
|
+
}
|
|
515
|
+
await this.sendNotification({
|
|
516
|
+
sessionId,
|
|
517
|
+
update: { sessionUpdate: "current_mode_update", modeId: restored.modeId }
|
|
518
|
+
});
|
|
519
|
+
await this.transport.send(toWire({
|
|
520
|
+
jsonrpc: "2.0",
|
|
521
|
+
id,
|
|
522
|
+
result: {
|
|
523
|
+
initialMode: { currentModeId: restored.modeId, availableModes: this.modes }
|
|
524
|
+
}
|
|
525
|
+
}));
|
|
526
|
+
return false;
|
|
527
|
+
}
|
|
528
|
+
}
|
|
529
|
+
if (existing) {
|
|
530
|
+
existing.updatedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
531
|
+
const replay = sessionId ? this.replayFor?.(sessionId) : void 0;
|
|
532
|
+
if (replay) {
|
|
533
|
+
for (const update of replay) {
|
|
534
|
+
await this.sendNotification({ sessionId, update });
|
|
535
|
+
}
|
|
536
|
+
}
|
|
537
|
+
await this.sendNotification({
|
|
538
|
+
sessionId,
|
|
539
|
+
update: {
|
|
540
|
+
sessionUpdate: "session_info_update",
|
|
541
|
+
updatedAt: existing.updatedAt
|
|
542
|
+
}
|
|
543
|
+
});
|
|
544
|
+
await this.sendNotification({
|
|
545
|
+
sessionId,
|
|
546
|
+
update: {
|
|
547
|
+
sessionUpdate: "current_mode_update",
|
|
548
|
+
modeId: existing.modeId
|
|
549
|
+
}
|
|
550
|
+
});
|
|
551
|
+
await this.transport.send(toWire({
|
|
552
|
+
jsonrpc: "2.0",
|
|
553
|
+
id,
|
|
554
|
+
result: {
|
|
555
|
+
initialMode: {
|
|
556
|
+
currentModeId: existing.modeId,
|
|
557
|
+
availableModes: this.modes
|
|
558
|
+
}
|
|
559
|
+
}
|
|
560
|
+
}));
|
|
561
|
+
return false;
|
|
562
|
+
}
|
|
563
|
+
await this.sendError(id, -32e3, `session not found: ${sessionId}`);
|
|
564
|
+
return false;
|
|
565
|
+
}
|
|
566
|
+
async handleSessionResume(id, params) {
|
|
567
|
+
const p = params ?? {};
|
|
568
|
+
const sessionId = typeof p.sessionId === "string" ? p.sessionId : null;
|
|
569
|
+
const existing = sessionId ? this.sessions.get(sessionId) : void 0;
|
|
570
|
+
if (existing) {
|
|
571
|
+
existing.updatedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
572
|
+
await this.transport.send(toWire({
|
|
573
|
+
jsonrpc: "2.0",
|
|
574
|
+
id,
|
|
575
|
+
result: {
|
|
576
|
+
initialMode: {
|
|
577
|
+
currentModeId: existing.modeId,
|
|
578
|
+
availableModes: this.modes
|
|
579
|
+
}
|
|
580
|
+
}
|
|
581
|
+
}));
|
|
582
|
+
return false;
|
|
583
|
+
}
|
|
584
|
+
await this.sendError(id, -32e3, `session not found: ${sessionId}`);
|
|
585
|
+
return false;
|
|
586
|
+
}
|
|
587
|
+
async handleSessionClose(id, params) {
|
|
588
|
+
const p = params ?? {};
|
|
589
|
+
const sessionId = typeof p.sessionId === "string" ? p.sessionId : null;
|
|
590
|
+
const session = sessionId ? this.sessions.get(sessionId) : void 0;
|
|
591
|
+
if (!session) {
|
|
592
|
+
await this.sendError(id, -32e3, `session not found: ${sessionId}`);
|
|
593
|
+
return false;
|
|
594
|
+
}
|
|
595
|
+
session.abort.abort();
|
|
596
|
+
if (sessionId) this.sessions.delete(sessionId);
|
|
597
|
+
await this.transport.send(toWire({
|
|
598
|
+
jsonrpc: "2.0",
|
|
599
|
+
id,
|
|
600
|
+
result: {}
|
|
601
|
+
}));
|
|
602
|
+
return false;
|
|
603
|
+
}
|
|
604
|
+
async handleSessionDelete(id, params) {
|
|
605
|
+
const p = params ?? {};
|
|
606
|
+
const sessionId = typeof p.sessionId === "string" ? p.sessionId : null;
|
|
607
|
+
if (!sessionId) {
|
|
608
|
+
await this.sendError(id, -32e3, `session not found: ${sessionId}`);
|
|
609
|
+
return false;
|
|
610
|
+
}
|
|
611
|
+
if (!this.sessions.has(sessionId)) {
|
|
612
|
+
await this.transport.send(toWire({ jsonrpc: "2.0", id, result: { configOptions: [...this.configOptions] } }));
|
|
613
|
+
return false;
|
|
614
|
+
}
|
|
615
|
+
const session = this.sessions.get(sessionId);
|
|
616
|
+
session.abort.abort();
|
|
617
|
+
this.sessions.delete(sessionId);
|
|
618
|
+
await this.transport.send(toWire({
|
|
619
|
+
jsonrpc: "2.0",
|
|
620
|
+
id,
|
|
621
|
+
result: {}
|
|
622
|
+
}));
|
|
623
|
+
return false;
|
|
624
|
+
}
|
|
625
|
+
async handleSessionFork(id, params) {
|
|
626
|
+
const p = params ?? {};
|
|
627
|
+
const sourceId = typeof p.sessionId === "string" ? p.sessionId : null;
|
|
628
|
+
if (!sourceId || !this.sessions.has(sourceId)) {
|
|
629
|
+
await this.sendError(id, -32e3, `session not found: ${sourceId}`);
|
|
630
|
+
return false;
|
|
631
|
+
}
|
|
632
|
+
const forkParams = params;
|
|
633
|
+
return this.handleSessionNew(id, { ...forkParams, cwd: p.cwd ?? this.defaultCwd });
|
|
634
|
+
}
|
|
635
|
+
async handleProvidersList(id, _params) {
|
|
636
|
+
await this.transport.send(toWire({
|
|
637
|
+
jsonrpc: "2.0",
|
|
638
|
+
id,
|
|
639
|
+
result: {
|
|
640
|
+
providers: [],
|
|
641
|
+
currentProviderId: null
|
|
642
|
+
}
|
|
643
|
+
}));
|
|
644
|
+
return false;
|
|
645
|
+
}
|
|
646
|
+
async handleProvidersSet(id, _params) {
|
|
647
|
+
await this.sendError(id, -32e3, "provider configuration not available through ACP; use wstack auth");
|
|
648
|
+
return false;
|
|
649
|
+
}
|
|
650
|
+
async handleProvidersDisable(id, _params) {
|
|
651
|
+
await this.transport.send(toWire({
|
|
652
|
+
jsonrpc: "2.0",
|
|
653
|
+
id,
|
|
654
|
+
result: {}
|
|
655
|
+
}));
|
|
656
|
+
return false;
|
|
657
|
+
}
|
|
658
|
+
async handleMcpMessage(id, _params) {
|
|
659
|
+
await this.sendError(id, -32e3, "MCP message routing not available through ACP");
|
|
660
|
+
return false;
|
|
399
661
|
}
|
|
400
662
|
async handleSessionPrompt(id, params) {
|
|
401
663
|
const p = params ?? {};
|
|
@@ -415,11 +677,54 @@ var ACPProtocolHandler = class {
|
|
|
415
677
|
const turnSignal = new AbortController();
|
|
416
678
|
const onCancel = () => turnSignal.abort();
|
|
417
679
|
session.abort.signal.addEventListener("abort", onCancel, { once: true });
|
|
680
|
+
const api = {
|
|
681
|
+
clientCapabilities: this.clientCapabilities,
|
|
682
|
+
requestPermission: async (req) => {
|
|
683
|
+
const res = await this.request("session/request_permission", {
|
|
684
|
+
sessionId,
|
|
685
|
+
toolCall: req.toolCall,
|
|
686
|
+
options: req.options
|
|
687
|
+
});
|
|
688
|
+
const outcome = res?.outcome;
|
|
689
|
+
return outcome ?? { outcome: "cancelled" };
|
|
690
|
+
},
|
|
691
|
+
readTextFile: async (params2) => {
|
|
692
|
+
const res = await this.request("fs/read_text_file", { sessionId, ...params2 });
|
|
693
|
+
return String(res?.content ?? "");
|
|
694
|
+
},
|
|
695
|
+
writeTextFile: async (params2) => {
|
|
696
|
+
await this.request("fs/write_text_file", { sessionId, ...params2 });
|
|
697
|
+
},
|
|
698
|
+
runTerminal: async ({ command, args, cwd }) => {
|
|
699
|
+
const created = await this.request("terminal/create", {
|
|
700
|
+
sessionId,
|
|
701
|
+
command,
|
|
702
|
+
...args ? { args } : {},
|
|
703
|
+
...cwd ? { cwd } : {}
|
|
704
|
+
});
|
|
705
|
+
const terminalId = created?.terminalId;
|
|
706
|
+
if (!terminalId) return { output: "", exitCode: null };
|
|
707
|
+
try {
|
|
708
|
+
const exit = await this.request("terminal/wait_for_exit", { sessionId, terminalId });
|
|
709
|
+
const out = await this.request("terminal/output", { sessionId, terminalId });
|
|
710
|
+
return {
|
|
711
|
+
output: String(out?.output ?? ""),
|
|
712
|
+
exitCode: typeof exit?.exitCode === "number" ? exit.exitCode : null
|
|
713
|
+
};
|
|
714
|
+
} finally {
|
|
715
|
+
try {
|
|
716
|
+
await this.request("terminal/release", { sessionId, terminalId });
|
|
717
|
+
} catch {
|
|
718
|
+
}
|
|
719
|
+
}
|
|
720
|
+
}
|
|
721
|
+
};
|
|
418
722
|
let result;
|
|
419
723
|
try {
|
|
420
724
|
result = await this.runTurn(
|
|
421
725
|
{ sessionId, prompt: p.prompt, signal: turnSignal.signal },
|
|
422
|
-
(update) => this.sendNotification({ sessionId, update })
|
|
726
|
+
(update) => this.sendNotification({ sessionId, update }),
|
|
727
|
+
api
|
|
423
728
|
);
|
|
424
729
|
} catch (err) {
|
|
425
730
|
session.abort.signal.removeEventListener("abort", onCancel);
|
|
@@ -429,6 +734,7 @@ var ACPProtocolHandler = class {
|
|
|
429
734
|
}
|
|
430
735
|
session.abort.signal.removeEventListener("abort", onCancel);
|
|
431
736
|
session.updatedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
737
|
+
await this.persist(session);
|
|
432
738
|
await this.transport.send(toWire({
|
|
433
739
|
jsonrpc: "2.0",
|
|
434
740
|
id,
|
|
@@ -457,12 +763,12 @@ var ACPProtocolHandler = class {
|
|
|
457
763
|
async handleSetConfigOption(id, params) {
|
|
458
764
|
const p = params ?? {};
|
|
459
765
|
const sessionId = typeof p.sessionId === "string" ? p.sessionId : null;
|
|
460
|
-
const optionId = typeof p.
|
|
766
|
+
const optionId = typeof p.configId === "string" ? p.configId : null;
|
|
461
767
|
const value = typeof p.value === "string" ? p.value : null;
|
|
462
768
|
const session = sessionId ? this.sessions.get(sessionId) : void 0;
|
|
463
769
|
const option = optionId ? this.configOptions.find((o) => o.id === optionId) : void 0;
|
|
464
770
|
if (!session || !option || value === null || !option.options.some((o) => o.value === value)) {
|
|
465
|
-
await this.sendError(id, -32602, "invalid sessionId,
|
|
771
|
+
await this.sendError(id, -32602, "invalid sessionId, configId, or value");
|
|
466
772
|
return false;
|
|
467
773
|
}
|
|
468
774
|
option.currentValue = value;
|
|
@@ -474,7 +780,7 @@ var ACPProtocolHandler = class {
|
|
|
474
780
|
configOptions: [...this.configOptions]
|
|
475
781
|
}
|
|
476
782
|
});
|
|
477
|
-
await this.transport.send(toWire({ jsonrpc: "2.0", id, result: {} }));
|
|
783
|
+
await this.transport.send(toWire({ jsonrpc: "2.0", id, result: { configOptions: [...this.configOptions] } }));
|
|
478
784
|
return false;
|
|
479
785
|
}
|
|
480
786
|
async handleSessionList(id) {
|
|
@@ -508,6 +814,9 @@ var ACPProtocolHandler = class {
|
|
|
508
814
|
}
|
|
509
815
|
return false;
|
|
510
816
|
}
|
|
817
|
+
case "$/cancel_request": {
|
|
818
|
+
return false;
|
|
819
|
+
}
|
|
511
820
|
case "exit":
|
|
512
821
|
this.close();
|
|
513
822
|
return true;
|
|
@@ -521,6 +830,14 @@ var ACPProtocolHandler = class {
|
|
|
521
830
|
async sendNotification(params) {
|
|
522
831
|
await this.transport.send(toWire({ jsonrpc: "2.0", method: "session/update", params }));
|
|
523
832
|
}
|
|
833
|
+
/** Best-effort durable persistence of a session + its recorded history. */
|
|
834
|
+
async persist(state) {
|
|
835
|
+
if (!this.store) return;
|
|
836
|
+
try {
|
|
837
|
+
await this.store.save(state, this.replayFor?.(state.id));
|
|
838
|
+
} catch {
|
|
839
|
+
}
|
|
840
|
+
}
|
|
524
841
|
async sendError(id, code, message, data) {
|
|
525
842
|
const error = { code, message };
|
|
526
843
|
if (data !== void 0) error.data = data;
|
|
@@ -545,29 +862,240 @@ function errorToJsonRpc(err) {
|
|
|
545
862
|
const message = err instanceof Error ? err.message : String(err);
|
|
546
863
|
return { code: -32603, message };
|
|
547
864
|
}
|
|
865
|
+
var ACPSessionStore = class {
|
|
866
|
+
dir;
|
|
867
|
+
/**
|
|
868
|
+
* Memoized result of the first successful `init()`. Saved sessions
|
|
869
|
+
* are the hot path — calling `mkdir(..., {recursive:true})` on every
|
|
870
|
+
* turn adds an avoidable syscall to the per-prompt persistence flow.
|
|
871
|
+
* Cleared automatically if the directory disappears between calls.
|
|
872
|
+
*/
|
|
873
|
+
initialized = false;
|
|
874
|
+
constructor(opts = {}) {
|
|
875
|
+
this.dir = opts.dir ?? path.join(process.cwd(), ".acp-sessions");
|
|
876
|
+
}
|
|
877
|
+
/** Ensure the store directory exists. Memoized — only mkdirs once. */
|
|
878
|
+
async init() {
|
|
879
|
+
if (this.initialized) return;
|
|
880
|
+
await fsp.mkdir(this.dir, { recursive: true });
|
|
881
|
+
this.initialized = true;
|
|
882
|
+
}
|
|
883
|
+
/**
|
|
884
|
+
* Persist a session state (and optionally its conversation history) to
|
|
885
|
+
* disk. Returns the session id. `history` enables cross-restart
|
|
886
|
+
* `session/load` replay.
|
|
887
|
+
*/
|
|
888
|
+
async save(state, history) {
|
|
889
|
+
await this.init();
|
|
890
|
+
await fsp.writeFile(
|
|
891
|
+
path.join(this.dir, `${state.id}.json`),
|
|
892
|
+
JSON.stringify({
|
|
893
|
+
id: state.id,
|
|
894
|
+
cwd: state.cwd,
|
|
895
|
+
modeId: state.modeId,
|
|
896
|
+
createdAt: state.createdAt,
|
|
897
|
+
updatedAt: state.updatedAt,
|
|
898
|
+
title: state.title,
|
|
899
|
+
...history && history.length > 0 ? { history } : {}
|
|
900
|
+
}),
|
|
901
|
+
"utf8"
|
|
902
|
+
);
|
|
903
|
+
await this.updateIndex(state.id, state.updatedAt);
|
|
904
|
+
return state.id;
|
|
905
|
+
}
|
|
906
|
+
/** Load a persisted session (metadata + history) from disk, or null. */
|
|
907
|
+
async load(sessionId) {
|
|
908
|
+
try {
|
|
909
|
+
const data = await fsp.readFile(path.join(this.dir, `${sessionId}.json`), "utf8");
|
|
910
|
+
return JSON.parse(data);
|
|
911
|
+
} catch {
|
|
912
|
+
return null;
|
|
913
|
+
}
|
|
914
|
+
}
|
|
915
|
+
/** List all persisted sessions. */
|
|
916
|
+
async list() {
|
|
917
|
+
const indexEntries = await this.readIndex();
|
|
918
|
+
if (indexEntries !== null) {
|
|
919
|
+
return indexEntries.sort((a, b) => b.updatedAt.localeCompare(a.updatedAt));
|
|
920
|
+
}
|
|
921
|
+
const files = [];
|
|
922
|
+
try {
|
|
923
|
+
const entries = await fsp.readdir(this.dir);
|
|
924
|
+
for (const entry of entries) {
|
|
925
|
+
if (entry.endsWith(".json") && entry !== "index.json") {
|
|
926
|
+
files.push(entry);
|
|
927
|
+
}
|
|
928
|
+
}
|
|
929
|
+
} catch {
|
|
930
|
+
return [];
|
|
931
|
+
}
|
|
932
|
+
const sessions = [];
|
|
933
|
+
for (const file of files) {
|
|
934
|
+
try {
|
|
935
|
+
const data = await fsp.readFile(path.join(this.dir, file), "utf8");
|
|
936
|
+
const parsed = JSON.parse(data);
|
|
937
|
+
if (parsed.id) {
|
|
938
|
+
sessions.push({ id: parsed.id, updatedAt: parsed.updatedAt ?? "" });
|
|
939
|
+
}
|
|
940
|
+
} catch {
|
|
941
|
+
}
|
|
942
|
+
}
|
|
943
|
+
sessions.sort((a, b) => b.updatedAt.localeCompare(a.updatedAt));
|
|
944
|
+
void this.writeIndex(sessions).catch(() => void 0);
|
|
945
|
+
return sessions;
|
|
946
|
+
}
|
|
947
|
+
/** Sidecar path that stores `{id, updatedAt}` for every saved session. */
|
|
948
|
+
indexPath() {
|
|
949
|
+
return path.join(this.dir, "index.json");
|
|
950
|
+
}
|
|
951
|
+
/** Read the sidecar index. Returns `null` when missing or unreadable. */
|
|
952
|
+
async readIndex() {
|
|
953
|
+
try {
|
|
954
|
+
const data = await fsp.readFile(this.indexPath(), "utf8");
|
|
955
|
+
const parsed = JSON.parse(data);
|
|
956
|
+
if (!Array.isArray(parsed)) return null;
|
|
957
|
+
const out = [];
|
|
958
|
+
for (const e of parsed) {
|
|
959
|
+
if (e && typeof e.id === "string" && typeof e.updatedAt === "string") {
|
|
960
|
+
out.push({
|
|
961
|
+
id: e.id,
|
|
962
|
+
updatedAt: e.updatedAt
|
|
963
|
+
});
|
|
964
|
+
}
|
|
965
|
+
}
|
|
966
|
+
return out;
|
|
967
|
+
} catch {
|
|
968
|
+
return null;
|
|
969
|
+
}
|
|
970
|
+
}
|
|
971
|
+
/** Atomically replace the sidecar index with the supplied entries. */
|
|
972
|
+
async writeIndex(entries) {
|
|
973
|
+
const target = this.indexPath();
|
|
974
|
+
const tmp = `${target}.${process.pid}.${Date.now()}.tmp`;
|
|
975
|
+
await fsp.writeFile(tmp, JSON.stringify(entries), "utf8");
|
|
976
|
+
await fsp.rename(tmp, target);
|
|
977
|
+
}
|
|
978
|
+
/** Update one entry in the index, adding it if missing. Best-effort. */
|
|
979
|
+
async updateIndex(id, updatedAt) {
|
|
980
|
+
let entries = await this.readIndex();
|
|
981
|
+
if (entries === null) {
|
|
982
|
+
await this.list();
|
|
983
|
+
return;
|
|
984
|
+
}
|
|
985
|
+
const i = entries.findIndex((e) => e.id === id);
|
|
986
|
+
if (i >= 0) entries[i] = { id, updatedAt };
|
|
987
|
+
else entries.push({ id, updatedAt });
|
|
988
|
+
try {
|
|
989
|
+
await this.writeIndex(entries);
|
|
990
|
+
} catch {
|
|
991
|
+
}
|
|
992
|
+
}
|
|
993
|
+
/** Delete a session file. */
|
|
994
|
+
async delete(sessionId) {
|
|
995
|
+
try {
|
|
996
|
+
await fsp.unlink(path.join(this.dir, `${sessionId}.json`));
|
|
997
|
+
} catch {
|
|
998
|
+
}
|
|
999
|
+
const entries = await this.readIndex();
|
|
1000
|
+
if (entries === null) return;
|
|
1001
|
+
const next = entries.filter((e) => e.id !== sessionId);
|
|
1002
|
+
if (next.length !== entries.length) {
|
|
1003
|
+
try {
|
|
1004
|
+
await this.writeIndex(next);
|
|
1005
|
+
} catch {
|
|
1006
|
+
}
|
|
1007
|
+
}
|
|
1008
|
+
}
|
|
1009
|
+
/** Get the store directory path. */
|
|
1010
|
+
getDirectory() {
|
|
1011
|
+
return this.dir;
|
|
1012
|
+
}
|
|
1013
|
+
};
|
|
1014
|
+
|
|
1015
|
+
// src/agent/ws-bridge-transport.ts
|
|
1016
|
+
var WsBridgeTransport = class {
|
|
1017
|
+
/** @param sink Called with each outbound message to write to the socket. */
|
|
1018
|
+
constructor(sink) {
|
|
1019
|
+
this.sink = sink;
|
|
1020
|
+
}
|
|
1021
|
+
sink;
|
|
1022
|
+
handlers = /* @__PURE__ */ new Set();
|
|
1023
|
+
closed = false;
|
|
1024
|
+
send(msg) {
|
|
1025
|
+
if (this.closed) return Promise.resolve();
|
|
1026
|
+
try {
|
|
1027
|
+
this.sink(msg);
|
|
1028
|
+
} catch {
|
|
1029
|
+
}
|
|
1030
|
+
return Promise.resolve();
|
|
1031
|
+
}
|
|
1032
|
+
sendRaw() {
|
|
1033
|
+
}
|
|
1034
|
+
read() {
|
|
1035
|
+
return Promise.resolve(null);
|
|
1036
|
+
}
|
|
1037
|
+
onMessage(handler) {
|
|
1038
|
+
this.handlers.add(handler);
|
|
1039
|
+
return () => this.handlers.delete(handler);
|
|
1040
|
+
}
|
|
1041
|
+
close() {
|
|
1042
|
+
this.closed = true;
|
|
1043
|
+
this.handlers.clear();
|
|
1044
|
+
}
|
|
1045
|
+
/**
|
|
1046
|
+
* Feed one inbound message from the socket. Fires the registered
|
|
1047
|
+
* `onMessage` handlers (which route JSON-RPC responses to pending
|
|
1048
|
+
* outbound requests inside the handler). Inbound *requests* are processed
|
|
1049
|
+
* by the caller via `handler.handleMessage(msg)` — call both per message.
|
|
1050
|
+
*/
|
|
1051
|
+
receive(msg) {
|
|
1052
|
+
if (this.closed) return;
|
|
1053
|
+
for (const handler of [...this.handlers]) {
|
|
1054
|
+
try {
|
|
1055
|
+
handler(msg);
|
|
1056
|
+
} catch {
|
|
1057
|
+
}
|
|
1058
|
+
}
|
|
1059
|
+
}
|
|
1060
|
+
};
|
|
548
1061
|
var WrongStackACPServer = class {
|
|
549
1062
|
transport;
|
|
550
1063
|
handler;
|
|
1064
|
+
options;
|
|
1065
|
+
/** HTTP server when transport mode is HTTP. */
|
|
1066
|
+
httpServer = null;
|
|
551
1067
|
running = false;
|
|
552
1068
|
constructor(opts = {}) {
|
|
1069
|
+
this.options = opts;
|
|
553
1070
|
this.transport = new StdioTransport();
|
|
554
1071
|
const runTurn = opts.runTurn ?? defaultEchoRunTurn;
|
|
555
1072
|
this.handler = new ACPProtocolHandler({
|
|
556
1073
|
transport: this.transport,
|
|
557
1074
|
defaultCwd: opts.defaultCwd ?? process.cwd(),
|
|
558
1075
|
runTurn,
|
|
559
|
-
agentName: opts.agentName
|
|
1076
|
+
agentName: opts.agentName,
|
|
1077
|
+
...opts.replayFor ? { replayFor: opts.replayFor } : {},
|
|
1078
|
+
...opts.seedFor ? { seedFor: opts.seedFor } : {},
|
|
1079
|
+
...opts.store ? { store: opts.store } : {}
|
|
560
1080
|
});
|
|
561
1081
|
}
|
|
562
1082
|
/**
|
|
563
|
-
* Start the server.
|
|
564
|
-
*
|
|
565
|
-
*
|
|
566
|
-
* process is the ACP server (the old `StdioTransport` handshake).
|
|
567
|
-
* 2. Loop: read messages, dispatch to the handler, until EOF / error.
|
|
1083
|
+
* Start the server. Mode depends on `options.transport`:
|
|
1084
|
+
* - 'stdio' (default): reads JSON-RPC from stdin, writes to stdout.
|
|
1085
|
+
* - number: listens as HTTP on the given port.
|
|
568
1086
|
*/
|
|
569
1087
|
async start() {
|
|
570
|
-
this.transport
|
|
1088
|
+
const transportMode = this.options.transport;
|
|
1089
|
+
if (typeof transportMode === "number") {
|
|
1090
|
+
await this.startHttp(transportMode);
|
|
1091
|
+
} else {
|
|
1092
|
+
await this.startStdio();
|
|
1093
|
+
}
|
|
1094
|
+
}
|
|
1095
|
+
async startStdio() {
|
|
1096
|
+
if (this.options.legacyStartupMarker) {
|
|
1097
|
+
this.transport.sendStartupMarker();
|
|
1098
|
+
}
|
|
571
1099
|
this.running = true;
|
|
572
1100
|
while (this.running) {
|
|
573
1101
|
const msg = await this.transport.read();
|
|
@@ -577,10 +1105,80 @@ var WrongStackACPServer = class {
|
|
|
577
1105
|
}
|
|
578
1106
|
this.transport.close();
|
|
579
1107
|
}
|
|
1108
|
+
async startHttp(port) {
|
|
1109
|
+
const host = this.options.host ?? "127.0.0.1";
|
|
1110
|
+
const handler = this.handler;
|
|
1111
|
+
this.httpServer = createServer(async (req, res) => {
|
|
1112
|
+
const selfOrigin = `http://${host}:${port}`;
|
|
1113
|
+
const reqOrigin = Array.isArray(req.headers.origin) ? req.headers.origin[0] : req.headers.origin;
|
|
1114
|
+
if (reqOrigin && reqOrigin !== selfOrigin) {
|
|
1115
|
+
res.writeHead(403);
|
|
1116
|
+
res.end(JSON.stringify({ error: "cross-origin request forbidden" }));
|
|
1117
|
+
return;
|
|
1118
|
+
}
|
|
1119
|
+
if (reqOrigin) res.setHeader("Access-Control-Allow-Origin", reqOrigin);
|
|
1120
|
+
res.setHeader("Access-Control-Allow-Methods", "POST, OPTIONS");
|
|
1121
|
+
res.setHeader("Access-Control-Allow-Headers", "Content-Type, Mcp-Session-Id");
|
|
1122
|
+
if (req.method === "OPTIONS") {
|
|
1123
|
+
res.writeHead(204);
|
|
1124
|
+
res.end();
|
|
1125
|
+
return;
|
|
1126
|
+
}
|
|
1127
|
+
if (req.method !== "POST") {
|
|
1128
|
+
res.writeHead(405);
|
|
1129
|
+
res.end(JSON.stringify({ error: "method not allowed" }));
|
|
1130
|
+
return;
|
|
1131
|
+
}
|
|
1132
|
+
let body = "";
|
|
1133
|
+
for await (const chunk of req) {
|
|
1134
|
+
body += chunk;
|
|
1135
|
+
}
|
|
1136
|
+
let msg;
|
|
1137
|
+
try {
|
|
1138
|
+
msg = JSON.parse(body);
|
|
1139
|
+
} catch {
|
|
1140
|
+
res.writeHead(400);
|
|
1141
|
+
res.end(JSON.stringify({ error: { code: -32700, message: "Parse error" } }));
|
|
1142
|
+
return;
|
|
1143
|
+
}
|
|
1144
|
+
const notifications = [];
|
|
1145
|
+
let response = null;
|
|
1146
|
+
const originalSend = this.transport.send.bind(this.transport);
|
|
1147
|
+
this.transport.send = async (m) => {
|
|
1148
|
+
if (m.id !== void 0 && (m.result !== void 0 || m.error !== void 0)) {
|
|
1149
|
+
response = m;
|
|
1150
|
+
} else if (m.method === "session/update") {
|
|
1151
|
+
notifications.push(m.params);
|
|
1152
|
+
} else {
|
|
1153
|
+
notifications.push(m);
|
|
1154
|
+
}
|
|
1155
|
+
};
|
|
1156
|
+
try {
|
|
1157
|
+
await handler.handleMessage(msg);
|
|
1158
|
+
} finally {
|
|
1159
|
+
this.transport.send = originalSend;
|
|
1160
|
+
}
|
|
1161
|
+
res.writeHead(200, { "Content-Type": "application/json" });
|
|
1162
|
+
const responseBody = response !== null ? { ...response, notifications } : { notifications };
|
|
1163
|
+
res.end(JSON.stringify(responseBody));
|
|
1164
|
+
});
|
|
1165
|
+
return new Promise((resolve) => {
|
|
1166
|
+
this.httpServer.listen(port, host, () => {
|
|
1167
|
+
writeErr(`[wstack-acp] HTTP server listening on http://${host}:${port}
|
|
1168
|
+
`);
|
|
1169
|
+
this.running = true;
|
|
1170
|
+
resolve();
|
|
1171
|
+
});
|
|
1172
|
+
});
|
|
1173
|
+
}
|
|
580
1174
|
/** Stop the server. */
|
|
581
1175
|
stop() {
|
|
582
1176
|
this.running = false;
|
|
583
1177
|
this.transport.close();
|
|
1178
|
+
if (this.httpServer) {
|
|
1179
|
+
this.httpServer.close();
|
|
1180
|
+
this.httpServer = null;
|
|
1181
|
+
}
|
|
584
1182
|
}
|
|
585
1183
|
};
|
|
586
1184
|
var defaultEchoRunTurn = async (_input, _emit) => {
|
|
@@ -603,20 +1201,63 @@ if (isEntrypoint) {
|
|
|
603
1201
|
function makeACPServerAgentTurn(opts) {
|
|
604
1202
|
const agents = /* @__PURE__ */ new Map();
|
|
605
1203
|
const timeouts = /* @__PURE__ */ new Map();
|
|
1204
|
+
const history = /* @__PURE__ */ new Map();
|
|
1205
|
+
const pendingSeed = /* @__PURE__ */ new Set();
|
|
606
1206
|
const timeoutMs = opts.timeoutMs ?? 5 * 6e4;
|
|
607
|
-
|
|
1207
|
+
const turn = async (input, emit, api) => {
|
|
608
1208
|
let agent = agents.get(input.sessionId);
|
|
609
1209
|
if (!agent) {
|
|
610
|
-
agent = await opts.agentFor(input.sessionId, process.cwd());
|
|
1210
|
+
agent = await opts.agentFor(input.sessionId, process.cwd(), api);
|
|
611
1211
|
agents.set(input.sessionId, agent);
|
|
1212
|
+
if (pendingSeed.has(input.sessionId)) {
|
|
1213
|
+
pendingSeed.delete(input.sessionId);
|
|
1214
|
+
seedAgentContext(agent, history.get(input.sessionId) ?? []);
|
|
1215
|
+
}
|
|
1216
|
+
}
|
|
1217
|
+
const turnAbort = new AbortController();
|
|
1218
|
+
const abortForTimeout = () => turnAbort.abort();
|
|
1219
|
+
const onParentAbort = () => turnAbort.abort();
|
|
1220
|
+
if (input.signal.aborted) {
|
|
1221
|
+
turnAbort.abort();
|
|
1222
|
+
} else {
|
|
1223
|
+
input.signal.addEventListener("abort", onParentAbort, { once: true });
|
|
612
1224
|
}
|
|
613
1225
|
const timer = setTimeout(() => {
|
|
614
1226
|
timeouts.delete(input.sessionId);
|
|
1227
|
+
abortForTimeout();
|
|
615
1228
|
}, timeoutMs);
|
|
616
1229
|
timeouts.set(input.sessionId, timer);
|
|
1230
|
+
const unsub = [];
|
|
1231
|
+
const bus = agent.events;
|
|
1232
|
+
if (bus?.on) {
|
|
1233
|
+
unsub.push(
|
|
1234
|
+
bus.on("tool.started", (e) => {
|
|
1235
|
+
emit({
|
|
1236
|
+
sessionUpdate: "tool_call",
|
|
1237
|
+
toolCallId: e.id,
|
|
1238
|
+
title: toolTitle(e.name, e.input),
|
|
1239
|
+
kind: toolNameToKind(e.name),
|
|
1240
|
+
status: "in_progress",
|
|
1241
|
+
...isRecord(e.input) ? { rawInput: e.input } : {}
|
|
1242
|
+
});
|
|
1243
|
+
}),
|
|
1244
|
+
bus.on("tool.executed", (e) => {
|
|
1245
|
+
emit({
|
|
1246
|
+
sessionUpdate: "tool_call_update",
|
|
1247
|
+
toolCallId: e.id ?? e.name,
|
|
1248
|
+
status: e.ok ? "completed" : "failed",
|
|
1249
|
+
...e.output !== void 0 ? {
|
|
1250
|
+
content: [
|
|
1251
|
+
{ type: "content", content: { type: "text", text: e.output } }
|
|
1252
|
+
]
|
|
1253
|
+
} : {}
|
|
1254
|
+
});
|
|
1255
|
+
})
|
|
1256
|
+
);
|
|
1257
|
+
}
|
|
617
1258
|
try {
|
|
618
|
-
const
|
|
619
|
-
const result = await agent.run(
|
|
1259
|
+
const userInput = promptToAgentInput(input.prompt);
|
|
1260
|
+
const result = await agent.run(userInput, { signal: turnAbort.signal });
|
|
620
1261
|
const text = extractText(result);
|
|
621
1262
|
if (text) {
|
|
622
1263
|
emit({
|
|
@@ -624,16 +1265,114 @@ function makeACPServerAgentTurn(opts) {
|
|
|
624
1265
|
content: { type: "text", text }
|
|
625
1266
|
});
|
|
626
1267
|
}
|
|
1268
|
+
const userText = promptToText(input.prompt);
|
|
1269
|
+
const hist = history.get(input.sessionId) ?? [];
|
|
1270
|
+
if (userText) {
|
|
1271
|
+
hist.push({ sessionUpdate: "user_message_chunk", content: { type: "text", text: userText } });
|
|
1272
|
+
}
|
|
1273
|
+
if (text) {
|
|
1274
|
+
hist.push({ sessionUpdate: "agent_message_chunk", content: { type: "text", text } });
|
|
1275
|
+
}
|
|
1276
|
+
if (hist.length > 0) history.set(input.sessionId, hist);
|
|
1277
|
+
const plan = extractPlan(result);
|
|
1278
|
+
if (plan.length > 0) {
|
|
1279
|
+
emit({
|
|
1280
|
+
sessionUpdate: "plan",
|
|
1281
|
+
entries: plan
|
|
1282
|
+
});
|
|
1283
|
+
}
|
|
1284
|
+
const usage = extractUsage(result);
|
|
1285
|
+
if (usage) {
|
|
1286
|
+
emit({
|
|
1287
|
+
sessionUpdate: "usage_update",
|
|
1288
|
+
used: usage.used,
|
|
1289
|
+
size: usage.size,
|
|
1290
|
+
...usage.cost ? { cost: usage.cost } : {}
|
|
1291
|
+
});
|
|
1292
|
+
}
|
|
627
1293
|
const result_out = {
|
|
628
|
-
|
|
1294
|
+
// `turnAbort.signal` covers both client cancellation and the
|
|
1295
|
+
// wall-clock timeout, so either maps to stopReason 'cancelled'.
|
|
1296
|
+
stopReason: pickStopReason(result, turnAbort.signal)
|
|
629
1297
|
};
|
|
630
1298
|
if (text) result_out.text = text;
|
|
1299
|
+
const runTurnPlan = extractPlan(result);
|
|
1300
|
+
if (runTurnPlan.length > 0) result_out.plan = runTurnPlan;
|
|
1301
|
+
if (usage) result_out.usage = usage;
|
|
631
1302
|
return result_out;
|
|
632
1303
|
} finally {
|
|
633
1304
|
clearTimeout(timer);
|
|
634
1305
|
timeouts.delete(input.sessionId);
|
|
1306
|
+
input.signal.removeEventListener("abort", onParentAbort);
|
|
1307
|
+
for (const u of unsub) u();
|
|
635
1308
|
}
|
|
636
1309
|
};
|
|
1310
|
+
const replay = (sessionId) => history.get(sessionId) ?? [];
|
|
1311
|
+
const seed = (sessionId, incoming) => {
|
|
1312
|
+
if (incoming.length === 0) return;
|
|
1313
|
+
history.set(sessionId, [...incoming]);
|
|
1314
|
+
pendingSeed.add(sessionId);
|
|
1315
|
+
};
|
|
1316
|
+
return Object.assign(turn, { replay, seed });
|
|
1317
|
+
}
|
|
1318
|
+
function seedAgentContext(agent, history) {
|
|
1319
|
+
const state = agent.ctx?.state;
|
|
1320
|
+
if (!state?.appendMessage) return;
|
|
1321
|
+
for (const u of history) {
|
|
1322
|
+
const text = u.content?.text;
|
|
1323
|
+
if (typeof text !== "string" || text.length === 0) continue;
|
|
1324
|
+
const role = u.sessionUpdate === "user_message_chunk" ? "user" : "assistant";
|
|
1325
|
+
state.appendMessage({ role, content: text });
|
|
1326
|
+
}
|
|
1327
|
+
}
|
|
1328
|
+
function toolNameToKind(name) {
|
|
1329
|
+
const n = name.toLowerCase();
|
|
1330
|
+
if (n.includes("read") || n.includes("cat")) return "read";
|
|
1331
|
+
if (n.includes("write") || n.includes("edit") || n.includes("apply") || n.includes("patch")) return "edit";
|
|
1332
|
+
if (n.includes("delete") || n.includes("rm")) return "delete";
|
|
1333
|
+
if (n.includes("move") || n.includes("rename") || n.includes("mv")) return "move";
|
|
1334
|
+
if (n.includes("grep") || n.includes("glob") || n.includes("search") || n.includes("find")) return "search";
|
|
1335
|
+
if (n.includes("bash") || n.includes("shell") || n.includes("exec") || n.includes("run") || n.includes("terminal")) return "execute";
|
|
1336
|
+
if (n.includes("fetch") || n.includes("http") || n.includes("web") || n.includes("url")) return "fetch";
|
|
1337
|
+
if (n.includes("think") || n.includes("plan")) return "think";
|
|
1338
|
+
return "other";
|
|
1339
|
+
}
|
|
1340
|
+
function toolTitle(name, input) {
|
|
1341
|
+
if (isRecord(input)) {
|
|
1342
|
+
const path2 = input.path ?? input.file ?? input.filePath ?? input.pattern ?? input.command;
|
|
1343
|
+
if (typeof path2 === "string" && path2.length > 0) {
|
|
1344
|
+
return `${name}: ${path2.length > 80 ? `${path2.slice(0, 77)}\u2026` : path2}`;
|
|
1345
|
+
}
|
|
1346
|
+
}
|
|
1347
|
+
return name;
|
|
1348
|
+
}
|
|
1349
|
+
function isRecord(v) {
|
|
1350
|
+
return typeof v === "object" && v !== null && !Array.isArray(v);
|
|
1351
|
+
}
|
|
1352
|
+
function promptToAgentInput(blocks) {
|
|
1353
|
+
const hasImage = blocks.some((b) => b.type === "image");
|
|
1354
|
+
if (!hasImage) {
|
|
1355
|
+
return promptToText(blocks);
|
|
1356
|
+
}
|
|
1357
|
+
const out = [];
|
|
1358
|
+
for (const b of blocks) {
|
|
1359
|
+
if (b.type === "text") {
|
|
1360
|
+
out.push({ type: "text", text: b.text });
|
|
1361
|
+
} else if (b.type === "image") {
|
|
1362
|
+
out.push({
|
|
1363
|
+
type: "image",
|
|
1364
|
+
source: { type: "base64", media_type: b.mimeType, data: b.data }
|
|
1365
|
+
});
|
|
1366
|
+
} else if (b.type === "audio") {
|
|
1367
|
+
out.push({ type: "text", text: `[audio: ${b.mimeType}]` });
|
|
1368
|
+
} else if (b.type === "resource") {
|
|
1369
|
+
const text = "text" in b.resource && typeof b.resource.text === "string" ? b.resource.text : `[embedded resource: ${b.resource.uri}]`;
|
|
1370
|
+
out.push({ type: "text", text });
|
|
1371
|
+
} else if (b.type === "resource_link") {
|
|
1372
|
+
out.push({ type: "text", text: `[resource link: ${b.uri}]` });
|
|
1373
|
+
}
|
|
1374
|
+
}
|
|
1375
|
+
return out;
|
|
637
1376
|
}
|
|
638
1377
|
function promptToText(blocks) {
|
|
639
1378
|
const parts = [];
|
|
@@ -680,7 +1419,32 @@ function pickStopReason(result, signal) {
|
|
|
680
1419
|
}
|
|
681
1420
|
return "end_turn";
|
|
682
1421
|
}
|
|
1422
|
+
function extractPlan(result) {
|
|
1423
|
+
if (typeof result !== "object" || result === null) return [];
|
|
1424
|
+
const r = result;
|
|
1425
|
+
if (Array.isArray(r.plan)) {
|
|
1426
|
+
return r.plan.filter(
|
|
1427
|
+
(e) => typeof e === "object" && e !== null && typeof e.content === "string"
|
|
1428
|
+
);
|
|
1429
|
+
}
|
|
1430
|
+
return [];
|
|
1431
|
+
}
|
|
1432
|
+
function extractUsage(result) {
|
|
1433
|
+
if (typeof result !== "object" || result === null) return null;
|
|
1434
|
+
const r = result;
|
|
1435
|
+
if (typeof r.usage === "object" && r.usage !== null) {
|
|
1436
|
+
const u = r.usage;
|
|
1437
|
+
if (typeof u.used === "number" && typeof u.size === "number") {
|
|
1438
|
+
return {
|
|
1439
|
+
used: u.used,
|
|
1440
|
+
size: u.size,
|
|
1441
|
+
...typeof u.cost === "object" && u.cost !== null ? { cost: u.cost } : {}
|
|
1442
|
+
};
|
|
1443
|
+
}
|
|
1444
|
+
}
|
|
1445
|
+
return null;
|
|
1446
|
+
}
|
|
683
1447
|
|
|
684
|
-
export { ACPProtocolHandler, ACPToolsRegistry, StdioTransport, WrongStackACPServer, makeACPServerAgentTurn };
|
|
1448
|
+
export { ACPProtocolHandler, ACPSessionStore, ACPToolsRegistry, StdioTransport, WrongStackACPServer, WsBridgeTransport, makeACPServerAgentTurn };
|
|
685
1449
|
//# sourceMappingURL=agent.js.map
|
|
686
1450
|
//# sourceMappingURL=agent.js.map
|