@wrongstack/acp 1.0.27 → 1.0.28
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/server-agent-turn.d.ts +4 -2
- package/dist/agent/stdio-transport.d.ts +12 -1
- package/dist/agent.js +37 -23
- package/dist/client/acp-request-state.d.ts +7 -0
- package/dist/client/websocket-transport.d.ts +5 -1
- package/dist/client.js +81 -18
- package/dist/index.js +88 -23
- package/dist/wrongstack-acp-agent.js +7 -5
- package/package.json +3 -3
|
@@ -162,8 +162,10 @@ declare function isRecord(v: unknown): v is Record<string, unknown>;
|
|
|
162
162
|
* When the prompt is all text we return a plain string (the common,
|
|
163
163
|
* cheapest path). When it carries images we build a multimodal
|
|
164
164
|
* `ContentBlock[]` the provider can pass to a vision-capable model.
|
|
165
|
-
*
|
|
166
|
-
*
|
|
165
|
+
* Images go through core's shared ingest (allowlist, base64, size and count
|
|
166
|
+
* caps, the bytes decide the media type): the client's label used to reach
|
|
167
|
+
* the provider unchecked. Every other block renders as in the text path, so
|
|
168
|
+
* an embedded resource keeps its `[embedded resource: <uri>]` header.
|
|
167
169
|
*/
|
|
168
170
|
declare function promptToAgentInput(blocks: readonly ContentBlock[]): AgentInput;
|
|
169
171
|
/**
|
|
@@ -55,6 +55,13 @@ export interface ACPClientTransport {
|
|
|
55
55
|
* message stream via onMessage, so claim semantics are not needed.
|
|
56
56
|
*/
|
|
57
57
|
onMessageClaim?(handler: (msg: ACPMessage) => boolean): () => void;
|
|
58
|
+
/**
|
|
59
|
+
* Called once when the connection ends for any reason (the agent exited,
|
|
60
|
+
* the socket closed, a frame limit tripped, stop()). Optional for custom
|
|
61
|
+
* transports; without it a client learns of a dead agent only when each
|
|
62
|
+
* in-flight request times out.
|
|
63
|
+
*/
|
|
64
|
+
onClose?(handler: (reason: string) => void): () => void;
|
|
58
65
|
stop(): void;
|
|
59
66
|
}
|
|
60
67
|
export declare class StdioTransport implements AgentServerTransport {
|
|
@@ -134,6 +141,7 @@ export declare class ClientTransport implements ACPClientTransport {
|
|
|
134
141
|
private buffer;
|
|
135
142
|
private readonly handlers;
|
|
136
143
|
private readonly claimHandlers;
|
|
144
|
+
private readonly closeHandlers;
|
|
137
145
|
private closed;
|
|
138
146
|
private resolveRead;
|
|
139
147
|
private messageQueue;
|
|
@@ -158,7 +166,10 @@ export declare class ClientTransport implements ACPClientTransport {
|
|
|
158
166
|
* the gating logic.
|
|
159
167
|
*/
|
|
160
168
|
onMessageClaim(handler: (msg: ACPMessage) => boolean): () => void;
|
|
161
|
-
|
|
169
|
+
onClose(handler: (reason: string) => void): () => void;
|
|
170
|
+
/** Tell close listeners once; the handlers are dropped with the connection. */
|
|
171
|
+
private notifyClosed;
|
|
172
|
+
stop(reason?: string): void;
|
|
162
173
|
private onChildData;
|
|
163
174
|
private onChildError;
|
|
164
175
|
private onChildClose;
|
package/dist/agent.js
CHANGED
|
@@ -534,6 +534,7 @@ async function handleSetModeOp(ctx, id, params) {
|
|
|
534
534
|
modeId
|
|
535
535
|
}
|
|
536
536
|
});
|
|
537
|
+
if (ctx.sessions.get(session.id) === session) await ctx.persist(session);
|
|
537
538
|
await ctx.sendResult(id, {});
|
|
538
539
|
return false;
|
|
539
540
|
}
|
|
@@ -559,7 +560,7 @@ async function handleSetConfigOptionOp(ctx, id, params) {
|
|
|
559
560
|
configOptions: options
|
|
560
561
|
}
|
|
561
562
|
});
|
|
562
|
-
await ctx.persist(session);
|
|
563
|
+
if (ctx.sessions.get(session.id) === session) await ctx.persist(session);
|
|
563
564
|
await ctx.sendResult(id, { configOptions: options });
|
|
564
565
|
return false;
|
|
565
566
|
}
|
|
@@ -976,6 +977,7 @@ var ACPProtocolHandler = class {
|
|
|
976
977
|
};
|
|
977
978
|
|
|
978
979
|
// src/agent/server-agent-turn.ts
|
|
980
|
+
import { parseIncomingImages } from "@wrongstack/core/utils";
|
|
979
981
|
function makeACPServerAgentTurn(opts) {
|
|
980
982
|
const agents = /* @__PURE__ */ new Map();
|
|
981
983
|
const timeouts = /* @__PURE__ */ new Map();
|
|
@@ -987,6 +989,7 @@ function makeACPServerAgentTurn(opts) {
|
|
|
987
989
|
const maxHistoryBytes = finitePositiveLimit(opts.maxHistoryBytes, 8 * 1024 * 1024);
|
|
988
990
|
const deliveryNoticeDebounceMs = finiteNonNegativeLimit(opts.deliveryNoticeDebounceMs, 1500);
|
|
989
991
|
const notifiers = /* @__PURE__ */ new Map();
|
|
992
|
+
const liveTurns = /* @__PURE__ */ new Map();
|
|
990
993
|
const stillPending = (sessionId) => {
|
|
991
994
|
if (!opts.pendingDeliveries) return true;
|
|
992
995
|
try {
|
|
@@ -1056,6 +1059,9 @@ function makeACPServerAgentTurn(opts) {
|
|
|
1056
1059
|
return state;
|
|
1057
1060
|
};
|
|
1058
1061
|
const turn = async (input, emit, api) => {
|
|
1062
|
+
const token = Symbol(input.sessionId);
|
|
1063
|
+
liveTurns.set(input.sessionId, token);
|
|
1064
|
+
const alive = () => liveTurns.get(input.sessionId) === token;
|
|
1059
1065
|
let agent = agents.get(input.sessionId);
|
|
1060
1066
|
if (!agent) {
|
|
1061
1067
|
agent = await opts.agentFor(
|
|
@@ -1064,6 +1070,12 @@ function makeACPServerAgentTurn(opts) {
|
|
|
1064
1070
|
api,
|
|
1065
1071
|
input.mcpServers
|
|
1066
1072
|
);
|
|
1073
|
+
if (!alive()) {
|
|
1074
|
+
const stale = agent;
|
|
1075
|
+
void Promise.resolve().then(() => stale.teardown?.()).catch(() => {
|
|
1076
|
+
});
|
|
1077
|
+
return { stopReason: "cancelled" };
|
|
1078
|
+
}
|
|
1067
1079
|
agents.set(input.sessionId, agent);
|
|
1068
1080
|
if (pendingSeed.has(input.sessionId)) {
|
|
1069
1081
|
pendingSeed.delete(input.sessionId);
|
|
@@ -1129,10 +1141,11 @@ function makeACPServerAgentTurn(opts) {
|
|
|
1129
1141
|
content: { type: "text", text }
|
|
1130
1142
|
});
|
|
1131
1143
|
}
|
|
1144
|
+
const record = alive();
|
|
1132
1145
|
const userText = promptToText(input.prompt);
|
|
1133
1146
|
const hist = history.get(input.sessionId) ?? [];
|
|
1134
1147
|
let retainedHistoryBytes = historyBytes.get(input.sessionId) ?? 0;
|
|
1135
|
-
if (userText) {
|
|
1148
|
+
if (record && userText) {
|
|
1136
1149
|
const update = {
|
|
1137
1150
|
sessionUpdate: "user_message_chunk",
|
|
1138
1151
|
content: { type: "text", text: userText }
|
|
@@ -1140,7 +1153,7 @@ function makeACPServerAgentTurn(opts) {
|
|
|
1140
1153
|
hist.push(update);
|
|
1141
1154
|
retainedHistoryBytes += replayEntryBytes(update);
|
|
1142
1155
|
}
|
|
1143
|
-
if (text) {
|
|
1156
|
+
if (record && text) {
|
|
1144
1157
|
const update = {
|
|
1145
1158
|
sessionUpdate: "agent_message_chunk",
|
|
1146
1159
|
content: { type: "text", text }
|
|
@@ -1188,6 +1201,7 @@ function makeACPServerAgentTurn(opts) {
|
|
|
1188
1201
|
if (usage) result_out.usage = usage;
|
|
1189
1202
|
return result_out;
|
|
1190
1203
|
} finally {
|
|
1204
|
+
if (alive()) liveTurns.delete(input.sessionId);
|
|
1191
1205
|
clearTimeout(timer);
|
|
1192
1206
|
timeouts.delete(input.sessionId);
|
|
1193
1207
|
input.signal.removeEventListener("abort", onParentAbort);
|
|
@@ -1214,6 +1228,7 @@ function makeACPServerAgentTurn(opts) {
|
|
|
1214
1228
|
pendingSeed.add(sessionId);
|
|
1215
1229
|
};
|
|
1216
1230
|
const dispose = (sessionId) => {
|
|
1231
|
+
liveTurns.delete(sessionId);
|
|
1217
1232
|
const timer = timeouts.get(sessionId);
|
|
1218
1233
|
if (timer) clearTimeout(timer);
|
|
1219
1234
|
timeouts.delete(sessionId);
|
|
@@ -1322,26 +1337,24 @@ function isRecord(v) {
|
|
|
1322
1337
|
return typeof v === "object" && v !== null && !Array.isArray(v);
|
|
1323
1338
|
}
|
|
1324
1339
|
function promptToAgentInput(blocks) {
|
|
1325
|
-
const
|
|
1326
|
-
if (
|
|
1340
|
+
const images = blocks.filter((b) => b.type === "image");
|
|
1341
|
+
if (images.length === 0) {
|
|
1327
1342
|
return promptToText(blocks);
|
|
1328
1343
|
}
|
|
1344
|
+
let parsed;
|
|
1345
|
+
try {
|
|
1346
|
+
parsed = parseIncomingImages(images.map((b) => ({ data: b.data, mediaType: b.mimeType })));
|
|
1347
|
+
} catch (err) {
|
|
1348
|
+
throw Object.assign(new Error(err.message), { code: -32602 });
|
|
1349
|
+
}
|
|
1329
1350
|
const out = [];
|
|
1351
|
+
let next = 0;
|
|
1330
1352
|
for (const b of blocks) {
|
|
1331
|
-
if (b.type === "
|
|
1332
|
-
|
|
1333
|
-
|
|
1334
|
-
|
|
1335
|
-
|
|
1336
|
-
source: { type: "base64", media_type: b.mimeType, data: b.data }
|
|
1337
|
-
});
|
|
1338
|
-
} else if (b.type === "audio") {
|
|
1339
|
-
out.push({ type: "text", text: `[audio: ${b.mimeType}]` });
|
|
1340
|
-
} else if (b.type === "resource") {
|
|
1341
|
-
const text = "text" in b.resource && typeof b.resource.text === "string" ? b.resource.text : `[embedded resource: ${b.resource.uri}]`;
|
|
1342
|
-
out.push({ type: "text", text });
|
|
1343
|
-
} else if (b.type === "resource_link") {
|
|
1344
|
-
out.push({ type: "text", text: `[resource link: ${b.uri}]` });
|
|
1353
|
+
if (b.type === "image") out.push(parsed[next++]);
|
|
1354
|
+
else if (b.type === "text") out.push({ type: "text", text: b.text });
|
|
1355
|
+
else {
|
|
1356
|
+
const text = promptToText([b]);
|
|
1357
|
+
if (text) out.push({ type: "text", text });
|
|
1345
1358
|
}
|
|
1346
1359
|
}
|
|
1347
1360
|
return out;
|
|
@@ -2096,16 +2109,17 @@ var WrongStackACPServer = class {
|
|
|
2096
2109
|
return;
|
|
2097
2110
|
}
|
|
2098
2111
|
const MAX_HTTP_BODY = 10 * 1024 * 1024;
|
|
2099
|
-
|
|
2112
|
+
const chunks = [];
|
|
2100
2113
|
let bodyBytes = 0;
|
|
2101
2114
|
let tooLarge = false;
|
|
2102
2115
|
for await (const chunk of req) {
|
|
2103
2116
|
bodyBytes += chunk.length;
|
|
2104
2117
|
if (bodyBytes > MAX_HTTP_BODY) {
|
|
2105
2118
|
tooLarge = true;
|
|
2106
|
-
|
|
2119
|
+
chunks.length = 0;
|
|
2120
|
+
if (bodyBytes > 2 * MAX_HTTP_BODY) break;
|
|
2107
2121
|
}
|
|
2108
|
-
|
|
2122
|
+
if (!tooLarge) chunks.push(chunk);
|
|
2109
2123
|
}
|
|
2110
2124
|
if (tooLarge) {
|
|
2111
2125
|
res.writeHead(413, { "Content-Type": "application/json" });
|
|
@@ -2114,7 +2128,7 @@ var WrongStackACPServer = class {
|
|
|
2114
2128
|
}
|
|
2115
2129
|
let msg;
|
|
2116
2130
|
try {
|
|
2117
|
-
msg = JSON.parse(
|
|
2131
|
+
msg = JSON.parse(Buffer.concat(chunks).toString("utf8"));
|
|
2118
2132
|
} catch {
|
|
2119
2133
|
res.writeHead(400);
|
|
2120
2134
|
res.end(JSON.stringify({ error: { code: -32700, message: "Parse error" } }));
|
|
@@ -6,4 +6,11 @@ export interface PendingRequest {
|
|
|
6
6
|
timeoutHandle: ReturnType<typeof setTimeout>;
|
|
7
7
|
}
|
|
8
8
|
export type State = 'init' | 'ready' | 'authenticated' | 'sessioning' | 'prompting' | 'done' | 'closed';
|
|
9
|
+
/**
|
|
10
|
+
* Fail and forget every in-flight request. Without a `reason` the session was
|
|
11
|
+
* closed locally; with one, the agent connection ended (the process exited,
|
|
12
|
+
* the socket closed), so no reply can arrive and waiting out each request's
|
|
13
|
+
* timeout would only stall the caller.
|
|
14
|
+
*/
|
|
15
|
+
export declare function rejectPendingRequests(pending: Map<string | number, PendingRequest>, reason?: string): void;
|
|
9
16
|
//# sourceMappingURL=acp-request-state.d.ts.map
|
|
@@ -31,6 +31,7 @@ export interface WebSocketClientTransportOptions {
|
|
|
31
31
|
export declare class WebSocketClientTransport implements ACPClientTransport {
|
|
32
32
|
private ws;
|
|
33
33
|
private readonly handlers;
|
|
34
|
+
private readonly closeHandlers;
|
|
34
35
|
private closed;
|
|
35
36
|
private readonly opts;
|
|
36
37
|
private readonly maxBufferedBytes;
|
|
@@ -41,7 +42,10 @@ export declare class WebSocketClientTransport implements ACPClientTransport {
|
|
|
41
42
|
start(): Promise<void>;
|
|
42
43
|
send(msg: ACPMessage): Promise<void>;
|
|
43
44
|
onMessage(handler: (msg: ACPMessage) => void): () => void;
|
|
44
|
-
|
|
45
|
+
onClose(handler: (reason: string) => void): () => void;
|
|
46
|
+
/** Tell close listeners once; the handlers are dropped with the connection. */
|
|
47
|
+
private notifyClosed;
|
|
48
|
+
stop(reason?: string): void;
|
|
45
49
|
private onData;
|
|
46
50
|
private dispatch;
|
|
47
51
|
}
|
package/dist/client.js
CHANGED
|
@@ -29,6 +29,7 @@ var ClientTransport = class {
|
|
|
29
29
|
buffer = "";
|
|
30
30
|
handlers = /* @__PURE__ */ new Set();
|
|
31
31
|
claimHandlers = /* @__PURE__ */ new Set();
|
|
32
|
+
closeHandlers = /* @__PURE__ */ new Set();
|
|
32
33
|
closed = false;
|
|
33
34
|
resolveRead = null;
|
|
34
35
|
messageQueue = [];
|
|
@@ -136,6 +137,7 @@ var ClientTransport = class {
|
|
|
136
137
|
}
|
|
137
138
|
send(msg) {
|
|
138
139
|
if (!this.child) return Promise.reject(new Error("ClientTransport not started"));
|
|
140
|
+
if (this.closed) return Promise.reject(new Error("ClientTransport is closed"));
|
|
139
141
|
return new Promise((resolve3, reject) => {
|
|
140
142
|
const line = JSON.stringify(msg) + "\n";
|
|
141
143
|
this.child?.stdin.write(line, "utf8", (err) => {
|
|
@@ -173,7 +175,22 @@ var ClientTransport = class {
|
|
|
173
175
|
this.claimHandlers.add(handler);
|
|
174
176
|
return () => this.claimHandlers.delete(handler);
|
|
175
177
|
}
|
|
176
|
-
|
|
178
|
+
onClose(handler) {
|
|
179
|
+
this.closeHandlers.add(handler);
|
|
180
|
+
return () => this.closeHandlers.delete(handler);
|
|
181
|
+
}
|
|
182
|
+
/** Tell close listeners once; the handlers are dropped with the connection. */
|
|
183
|
+
notifyClosed(reason) {
|
|
184
|
+
const handlers = [...this.closeHandlers];
|
|
185
|
+
this.closeHandlers.clear();
|
|
186
|
+
for (const handler of handlers) {
|
|
187
|
+
try {
|
|
188
|
+
handler(reason);
|
|
189
|
+
} catch {
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
stop(reason = "transport stopped") {
|
|
177
194
|
this.closed = true;
|
|
178
195
|
this.resolveRead?.(null);
|
|
179
196
|
this.resolveRead = null;
|
|
@@ -182,6 +199,7 @@ var ClientTransport = class {
|
|
|
182
199
|
this.queuedChars = 0;
|
|
183
200
|
this.handlers.clear();
|
|
184
201
|
this.claimHandlers.clear();
|
|
202
|
+
this.notifyClosed(reason);
|
|
185
203
|
const child = this.child;
|
|
186
204
|
if (!child) return;
|
|
187
205
|
treeKill(child);
|
|
@@ -193,7 +211,7 @@ var ClientTransport = class {
|
|
|
193
211
|
if (this.buffer.length > this.maxFrameChars) {
|
|
194
212
|
writeErr(`[acp-child pending frame exceeds ${this.maxFrameChars} characters]
|
|
195
213
|
`);
|
|
196
|
-
this.stop();
|
|
214
|
+
this.stop(`agent frame exceeds ${this.maxFrameChars} characters`);
|
|
197
215
|
return;
|
|
198
216
|
}
|
|
199
217
|
for (const raw of lines) {
|
|
@@ -201,7 +219,7 @@ var ClientTransport = class {
|
|
|
201
219
|
if (raw.length > this.maxFrameChars) {
|
|
202
220
|
writeErr(`[acp-child frame exceeds ${this.maxFrameChars} characters]
|
|
203
221
|
`);
|
|
204
|
-
this.stop();
|
|
222
|
+
this.stop(`agent frame exceeds ${this.maxFrameChars} characters`);
|
|
205
223
|
return;
|
|
206
224
|
}
|
|
207
225
|
try {
|
|
@@ -227,6 +245,7 @@ var ClientTransport = class {
|
|
|
227
245
|
writeErr(`[acp-child exited with code ${code}]
|
|
228
246
|
`);
|
|
229
247
|
}
|
|
248
|
+
this.notifyClosed(`agent process exited with code ${code}`);
|
|
230
249
|
}
|
|
231
250
|
dispatch(msg, chars = JSON.stringify(msg).length) {
|
|
232
251
|
if (this.resolveRead) {
|
|
@@ -373,6 +392,16 @@ function isAuthRequiredError(err) {
|
|
|
373
392
|
return false;
|
|
374
393
|
}
|
|
375
394
|
|
|
395
|
+
// src/client/acp-request-state.ts
|
|
396
|
+
function rejectPendingRequests(pending, reason) {
|
|
397
|
+
for (const p of pending.values()) {
|
|
398
|
+
clearTimeout(p.timeoutHandle);
|
|
399
|
+
const message = reason === void 0 ? "session was closed" : `agent connection closed (${reason}) during ${p.method}`;
|
|
400
|
+
p.reject(new ACPSessionError("closed", message));
|
|
401
|
+
}
|
|
402
|
+
pending.clear();
|
|
403
|
+
}
|
|
404
|
+
|
|
376
405
|
// src/client/acp-session-ops.ts
|
|
377
406
|
function filterMcpServers(agentCapabilities, servers) {
|
|
378
407
|
if (!servers || servers.length === 0) return [];
|
|
@@ -1650,7 +1679,7 @@ function isAllowed(decision) {
|
|
|
1650
1679
|
}
|
|
1651
1680
|
function toTrustBoundaryRequest(request, options) {
|
|
1652
1681
|
const rawSessionId = request.toolCall.rawInput?.sessionId;
|
|
1653
|
-
const sessionId = typeof rawSessionId === "string" && rawSessionId.length > 0 ? rawSessionId :
|
|
1682
|
+
const sessionId = options.actor?.sessionId ?? (typeof rawSessionId === "string" && rawSessionId.length > 0 ? rawSessionId : void 0);
|
|
1654
1683
|
return {
|
|
1655
1684
|
version: 1,
|
|
1656
1685
|
requestId: String(request.toolCall.toolCallId),
|
|
@@ -1686,6 +1715,7 @@ function makeTrustBoundaryPermissionPolicy(options) {
|
|
|
1686
1715
|
var WebSocketClientTransport = class {
|
|
1687
1716
|
ws = null;
|
|
1688
1717
|
handlers = /* @__PURE__ */ new Set();
|
|
1718
|
+
closeHandlers = /* @__PURE__ */ new Set();
|
|
1689
1719
|
closed = false;
|
|
1690
1720
|
opts;
|
|
1691
1721
|
maxBufferedBytes;
|
|
@@ -1712,6 +1742,7 @@ var WebSocketClientTransport = class {
|
|
|
1712
1742
|
const timeoutMs = this.opts.handshakeTimeoutMs ?? 3e4;
|
|
1713
1743
|
return new Promise((resolve3, reject) => {
|
|
1714
1744
|
const ws = new WS(this.opts.url, this.opts.protocols);
|
|
1745
|
+
ws.binaryType = "arraybuffer";
|
|
1715
1746
|
this.ws = ws;
|
|
1716
1747
|
const timer = setTimeout(() => {
|
|
1717
1748
|
const pending = this.pendingStart;
|
|
@@ -1737,7 +1768,7 @@ var WebSocketClientTransport = class {
|
|
|
1737
1768
|
ws.addEventListener("error", (ev) => {
|
|
1738
1769
|
const pending = this.pendingStart;
|
|
1739
1770
|
if (pending === null) {
|
|
1740
|
-
this.stop();
|
|
1771
|
+
this.stop("WebSocket error");
|
|
1741
1772
|
return;
|
|
1742
1773
|
}
|
|
1743
1774
|
this.pendingStart = null;
|
|
@@ -1758,6 +1789,7 @@ var WebSocketClientTransport = class {
|
|
|
1758
1789
|
clearTimeout(pending.timer);
|
|
1759
1790
|
pending.reject(new Error("WebSocket closed before the connection opened"));
|
|
1760
1791
|
}
|
|
1792
|
+
this.notifyClosed("WebSocket closed");
|
|
1761
1793
|
});
|
|
1762
1794
|
ws.addEventListener("message", (ev) => {
|
|
1763
1795
|
this.onData(ev.data);
|
|
@@ -1785,9 +1817,25 @@ var WebSocketClientTransport = class {
|
|
|
1785
1817
|
this.handlers.add(handler);
|
|
1786
1818
|
return () => this.handlers.delete(handler);
|
|
1787
1819
|
}
|
|
1788
|
-
|
|
1820
|
+
onClose(handler) {
|
|
1821
|
+
this.closeHandlers.add(handler);
|
|
1822
|
+
return () => this.closeHandlers.delete(handler);
|
|
1823
|
+
}
|
|
1824
|
+
/** Tell close listeners once; the handlers are dropped with the connection. */
|
|
1825
|
+
notifyClosed(reason) {
|
|
1826
|
+
const handlers = [...this.closeHandlers];
|
|
1827
|
+
this.closeHandlers.clear();
|
|
1828
|
+
for (const handler of handlers) {
|
|
1829
|
+
try {
|
|
1830
|
+
handler(reason);
|
|
1831
|
+
} catch {
|
|
1832
|
+
}
|
|
1833
|
+
}
|
|
1834
|
+
}
|
|
1835
|
+
stop(reason = "transport stopped") {
|
|
1789
1836
|
this.closed = true;
|
|
1790
1837
|
this.handlers.clear();
|
|
1838
|
+
this.notifyClosed(reason);
|
|
1791
1839
|
if (this.pendingStart !== null) {
|
|
1792
1840
|
const pending = this.pendingStart;
|
|
1793
1841
|
this.pendingStart = null;
|
|
@@ -1808,7 +1856,7 @@ var WebSocketClientTransport = class {
|
|
|
1808
1856
|
onData(data) {
|
|
1809
1857
|
const text = typeof data === "string" ? data : data instanceof ArrayBuffer ? Buffer.from(data).toString("utf8") : Buffer.isBuffer(data) ? data.toString("utf8") : String(data);
|
|
1810
1858
|
if (text.length > this.maxMessageChars) {
|
|
1811
|
-
this.stop();
|
|
1859
|
+
this.stop(`agent message exceeds ${this.maxMessageChars} characters`);
|
|
1812
1860
|
return;
|
|
1813
1861
|
}
|
|
1814
1862
|
if (!text.trim()) return;
|
|
@@ -1977,6 +2025,10 @@ var ACPSession = class _ACPSession {
|
|
|
1977
2025
|
}
|
|
1978
2026
|
const session = new _ACPSession(opts, transport);
|
|
1979
2027
|
session.transportOff = transport.onMessage((msg) => session.handleMessage(msg));
|
|
2028
|
+
transport.onClose?.((reason) => {
|
|
2029
|
+
if (!session.closed) rejectPendingRequests(session.pending, reason);
|
|
2030
|
+
void session.close();
|
|
2031
|
+
});
|
|
1980
2032
|
try {
|
|
1981
2033
|
await session.initialize();
|
|
1982
2034
|
} catch (err) {
|
|
@@ -2103,6 +2155,9 @@ var ACPSession = class _ACPSession {
|
|
|
2103
2155
|
if (this.state !== "ready" && this.state !== "authenticated" && this.state !== "done") {
|
|
2104
2156
|
throw new ACPSessionError("protocol_error", `prompt called in state=${this.state}`);
|
|
2105
2157
|
}
|
|
2158
|
+
if (this.promptCallbackAbort) {
|
|
2159
|
+
throw new ACPSessionError("protocol_error", "prompt called while another prompt is running");
|
|
2160
|
+
}
|
|
2106
2161
|
if (signal.aborted) {
|
|
2107
2162
|
return emptyRunResult("cancelled");
|
|
2108
2163
|
}
|
|
@@ -2174,6 +2229,7 @@ var ACPSession = class _ACPSession {
|
|
|
2174
2229
|
this.sessionId = sessionId;
|
|
2175
2230
|
}
|
|
2176
2231
|
if (signal.aborted) {
|
|
2232
|
+
this.promptCallbackAbort = null;
|
|
2177
2233
|
return emptyRunResult("cancelled");
|
|
2178
2234
|
}
|
|
2179
2235
|
this.resetScratch();
|
|
@@ -2281,11 +2337,7 @@ var ACPSession = class _ACPSession {
|
|
|
2281
2337
|
} catch {
|
|
2282
2338
|
}
|
|
2283
2339
|
}
|
|
2284
|
-
|
|
2285
|
-
clearTimeout(p.timeoutHandle);
|
|
2286
|
-
p.reject(new ACPSessionError("closed", "session was closed"));
|
|
2287
|
-
}
|
|
2288
|
-
this.pending.clear();
|
|
2340
|
+
rejectPendingRequests(this.pending);
|
|
2289
2341
|
this.transportOff?.();
|
|
2290
2342
|
this.transportOff = null;
|
|
2291
2343
|
try {
|
|
@@ -2501,6 +2553,7 @@ async function makeACPSubagentRunnerWithStop(options) {
|
|
|
2501
2553
|
const timeoutMs = options.timeoutMs ?? 5 * 6e4;
|
|
2502
2554
|
const persistent = options.persistent === true;
|
|
2503
2555
|
let shared = null;
|
|
2556
|
+
let turns = Promise.resolve();
|
|
2504
2557
|
const startSession = async () => {
|
|
2505
2558
|
return ACPSession.start({
|
|
2506
2559
|
command: options.command,
|
|
@@ -2516,10 +2569,17 @@ async function makeACPSubagentRunnerWithStop(options) {
|
|
|
2516
2569
|
};
|
|
2517
2570
|
const runner = async (task, ctx) => {
|
|
2518
2571
|
let session;
|
|
2519
|
-
const reuse = persistent && shared !== null;
|
|
2520
2572
|
try {
|
|
2521
|
-
|
|
2522
|
-
|
|
2573
|
+
if (persistent) {
|
|
2574
|
+
shared ??= startSession();
|
|
2575
|
+
const starting = shared;
|
|
2576
|
+
session = await starting.catch((err) => {
|
|
2577
|
+
if (shared === starting) shared = null;
|
|
2578
|
+
throw err;
|
|
2579
|
+
});
|
|
2580
|
+
} else {
|
|
2581
|
+
session = await startSession();
|
|
2582
|
+
}
|
|
2523
2583
|
} catch (err) {
|
|
2524
2584
|
throw acpErrorToSubagentError(err, options.role ?? "acp-subagent");
|
|
2525
2585
|
}
|
|
@@ -2536,7 +2596,10 @@ async function makeACPSubagentRunnerWithStop(options) {
|
|
|
2536
2596
|
};
|
|
2537
2597
|
let result;
|
|
2538
2598
|
try {
|
|
2539
|
-
|
|
2599
|
+
const turn = () => session.prompt([textContent(task.description)], ctx.signal, onProgress);
|
|
2600
|
+
const run = persistent ? turns.then(turn, turn) : turn();
|
|
2601
|
+
if (persistent) turns = run;
|
|
2602
|
+
result = await run;
|
|
2540
2603
|
} catch (err) {
|
|
2541
2604
|
throw acpErrorToSubagentError(err, options.role ?? "acp-subagent");
|
|
2542
2605
|
} finally {
|
|
@@ -2557,10 +2620,10 @@ async function makeACPSubagentRunnerWithStop(options) {
|
|
|
2557
2620
|
};
|
|
2558
2621
|
const stop = async () => {
|
|
2559
2622
|
if (shared) {
|
|
2560
|
-
const
|
|
2623
|
+
const starting = shared;
|
|
2561
2624
|
shared = null;
|
|
2562
2625
|
try {
|
|
2563
|
-
await
|
|
2626
|
+
await (await starting).close();
|
|
2564
2627
|
} catch {
|
|
2565
2628
|
}
|
|
2566
2629
|
}
|
package/dist/index.js
CHANGED
|
@@ -537,6 +537,7 @@ async function handleSetModeOp(ctx, id, params) {
|
|
|
537
537
|
modeId
|
|
538
538
|
}
|
|
539
539
|
});
|
|
540
|
+
if (ctx.sessions.get(session.id) === session) await ctx.persist(session);
|
|
540
541
|
await ctx.sendResult(id, {});
|
|
541
542
|
return false;
|
|
542
543
|
}
|
|
@@ -562,7 +563,7 @@ async function handleSetConfigOptionOp(ctx, id, params) {
|
|
|
562
563
|
configOptions: options
|
|
563
564
|
}
|
|
564
565
|
});
|
|
565
|
-
await ctx.persist(session);
|
|
566
|
+
if (ctx.sessions.get(session.id) === session) await ctx.persist(session);
|
|
566
567
|
await ctx.sendResult(id, { configOptions: options });
|
|
567
568
|
return false;
|
|
568
569
|
}
|
|
@@ -1170,6 +1171,7 @@ var ClientTransport = class {
|
|
|
1170
1171
|
buffer = "";
|
|
1171
1172
|
handlers = /* @__PURE__ */ new Set();
|
|
1172
1173
|
claimHandlers = /* @__PURE__ */ new Set();
|
|
1174
|
+
closeHandlers = /* @__PURE__ */ new Set();
|
|
1173
1175
|
closed = false;
|
|
1174
1176
|
resolveRead = null;
|
|
1175
1177
|
messageQueue = [];
|
|
@@ -1277,6 +1279,7 @@ var ClientTransport = class {
|
|
|
1277
1279
|
}
|
|
1278
1280
|
send(msg) {
|
|
1279
1281
|
if (!this.child) return Promise.reject(new Error("ClientTransport not started"));
|
|
1282
|
+
if (this.closed) return Promise.reject(new Error("ClientTransport is closed"));
|
|
1280
1283
|
return new Promise((resolve5, reject) => {
|
|
1281
1284
|
const line = JSON.stringify(msg) + "\n";
|
|
1282
1285
|
this.child?.stdin.write(line, "utf8", (err) => {
|
|
@@ -1314,7 +1317,22 @@ var ClientTransport = class {
|
|
|
1314
1317
|
this.claimHandlers.add(handler);
|
|
1315
1318
|
return () => this.claimHandlers.delete(handler);
|
|
1316
1319
|
}
|
|
1317
|
-
|
|
1320
|
+
onClose(handler) {
|
|
1321
|
+
this.closeHandlers.add(handler);
|
|
1322
|
+
return () => this.closeHandlers.delete(handler);
|
|
1323
|
+
}
|
|
1324
|
+
/** Tell close listeners once; the handlers are dropped with the connection. */
|
|
1325
|
+
notifyClosed(reason) {
|
|
1326
|
+
const handlers = [...this.closeHandlers];
|
|
1327
|
+
this.closeHandlers.clear();
|
|
1328
|
+
for (const handler of handlers) {
|
|
1329
|
+
try {
|
|
1330
|
+
handler(reason);
|
|
1331
|
+
} catch {
|
|
1332
|
+
}
|
|
1333
|
+
}
|
|
1334
|
+
}
|
|
1335
|
+
stop(reason = "transport stopped") {
|
|
1318
1336
|
this.closed = true;
|
|
1319
1337
|
this.resolveRead?.(null);
|
|
1320
1338
|
this.resolveRead = null;
|
|
@@ -1323,6 +1341,7 @@ var ClientTransport = class {
|
|
|
1323
1341
|
this.queuedChars = 0;
|
|
1324
1342
|
this.handlers.clear();
|
|
1325
1343
|
this.claimHandlers.clear();
|
|
1344
|
+
this.notifyClosed(reason);
|
|
1326
1345
|
const child = this.child;
|
|
1327
1346
|
if (!child) return;
|
|
1328
1347
|
treeKill(child);
|
|
@@ -1334,7 +1353,7 @@ var ClientTransport = class {
|
|
|
1334
1353
|
if (this.buffer.length > this.maxFrameChars) {
|
|
1335
1354
|
writeErr(`[acp-child pending frame exceeds ${this.maxFrameChars} characters]
|
|
1336
1355
|
`);
|
|
1337
|
-
this.stop();
|
|
1356
|
+
this.stop(`agent frame exceeds ${this.maxFrameChars} characters`);
|
|
1338
1357
|
return;
|
|
1339
1358
|
}
|
|
1340
1359
|
for (const raw of lines) {
|
|
@@ -1342,7 +1361,7 @@ var ClientTransport = class {
|
|
|
1342
1361
|
if (raw.length > this.maxFrameChars) {
|
|
1343
1362
|
writeErr(`[acp-child frame exceeds ${this.maxFrameChars} characters]
|
|
1344
1363
|
`);
|
|
1345
|
-
this.stop();
|
|
1364
|
+
this.stop(`agent frame exceeds ${this.maxFrameChars} characters`);
|
|
1346
1365
|
return;
|
|
1347
1366
|
}
|
|
1348
1367
|
try {
|
|
@@ -1368,6 +1387,7 @@ var ClientTransport = class {
|
|
|
1368
1387
|
writeErr(`[acp-child exited with code ${code}]
|
|
1369
1388
|
`);
|
|
1370
1389
|
}
|
|
1390
|
+
this.notifyClosed(`agent process exited with code ${code}`);
|
|
1371
1391
|
}
|
|
1372
1392
|
dispatch(msg, chars = JSON.stringify(msg).length) {
|
|
1373
1393
|
if (this.resolveRead) {
|
|
@@ -1643,16 +1663,17 @@ var WrongStackACPServer = class {
|
|
|
1643
1663
|
return;
|
|
1644
1664
|
}
|
|
1645
1665
|
const MAX_HTTP_BODY = 10 * 1024 * 1024;
|
|
1646
|
-
|
|
1666
|
+
const chunks = [];
|
|
1647
1667
|
let bodyBytes = 0;
|
|
1648
1668
|
let tooLarge = false;
|
|
1649
1669
|
for await (const chunk of req) {
|
|
1650
1670
|
bodyBytes += chunk.length;
|
|
1651
1671
|
if (bodyBytes > MAX_HTTP_BODY) {
|
|
1652
1672
|
tooLarge = true;
|
|
1653
|
-
|
|
1673
|
+
chunks.length = 0;
|
|
1674
|
+
if (bodyBytes > 2 * MAX_HTTP_BODY) break;
|
|
1654
1675
|
}
|
|
1655
|
-
|
|
1676
|
+
if (!tooLarge) chunks.push(chunk);
|
|
1656
1677
|
}
|
|
1657
1678
|
if (tooLarge) {
|
|
1658
1679
|
res.writeHead(413, { "Content-Type": "application/json" });
|
|
@@ -1661,7 +1682,7 @@ var WrongStackACPServer = class {
|
|
|
1661
1682
|
}
|
|
1662
1683
|
let msg;
|
|
1663
1684
|
try {
|
|
1664
|
-
msg = JSON.parse(
|
|
1685
|
+
msg = JSON.parse(Buffer.concat(chunks).toString("utf8"));
|
|
1665
1686
|
} catch {
|
|
1666
1687
|
res.writeHead(400);
|
|
1667
1688
|
res.end(JSON.stringify({ error: { code: -32700, message: "Parse error" } }));
|
|
@@ -1895,6 +1916,16 @@ function isAuthRequiredError(err) {
|
|
|
1895
1916
|
return false;
|
|
1896
1917
|
}
|
|
1897
1918
|
|
|
1919
|
+
// src/client/acp-request-state.ts
|
|
1920
|
+
function rejectPendingRequests(pending, reason) {
|
|
1921
|
+
for (const p of pending.values()) {
|
|
1922
|
+
clearTimeout(p.timeoutHandle);
|
|
1923
|
+
const message = reason === void 0 ? "session was closed" : `agent connection closed (${reason}) during ${p.method}`;
|
|
1924
|
+
p.reject(new ACPSessionError("closed", message));
|
|
1925
|
+
}
|
|
1926
|
+
pending.clear();
|
|
1927
|
+
}
|
|
1928
|
+
|
|
1898
1929
|
// src/client/acp-session-ops.ts
|
|
1899
1930
|
function filterMcpServers(agentCapabilities, servers) {
|
|
1900
1931
|
if (!servers || servers.length === 0) return [];
|
|
@@ -3172,7 +3203,7 @@ function isAllowed(decision) {
|
|
|
3172
3203
|
}
|
|
3173
3204
|
function toTrustBoundaryRequest(request, options) {
|
|
3174
3205
|
const rawSessionId = request.toolCall.rawInput?.sessionId;
|
|
3175
|
-
const sessionId = typeof rawSessionId === "string" && rawSessionId.length > 0 ? rawSessionId :
|
|
3206
|
+
const sessionId = options.actor?.sessionId ?? (typeof rawSessionId === "string" && rawSessionId.length > 0 ? rawSessionId : void 0);
|
|
3176
3207
|
return {
|
|
3177
3208
|
version: 1,
|
|
3178
3209
|
requestId: String(request.toolCall.toolCallId),
|
|
@@ -3208,6 +3239,7 @@ function makeTrustBoundaryPermissionPolicy(options) {
|
|
|
3208
3239
|
var WebSocketClientTransport = class {
|
|
3209
3240
|
ws = null;
|
|
3210
3241
|
handlers = /* @__PURE__ */ new Set();
|
|
3242
|
+
closeHandlers = /* @__PURE__ */ new Set();
|
|
3211
3243
|
closed = false;
|
|
3212
3244
|
opts;
|
|
3213
3245
|
maxBufferedBytes;
|
|
@@ -3234,6 +3266,7 @@ var WebSocketClientTransport = class {
|
|
|
3234
3266
|
const timeoutMs = this.opts.handshakeTimeoutMs ?? 3e4;
|
|
3235
3267
|
return new Promise((resolve5, reject) => {
|
|
3236
3268
|
const ws = new WS(this.opts.url, this.opts.protocols);
|
|
3269
|
+
ws.binaryType = "arraybuffer";
|
|
3237
3270
|
this.ws = ws;
|
|
3238
3271
|
const timer = setTimeout(() => {
|
|
3239
3272
|
const pending = this.pendingStart;
|
|
@@ -3259,7 +3292,7 @@ var WebSocketClientTransport = class {
|
|
|
3259
3292
|
ws.addEventListener("error", (ev) => {
|
|
3260
3293
|
const pending = this.pendingStart;
|
|
3261
3294
|
if (pending === null) {
|
|
3262
|
-
this.stop();
|
|
3295
|
+
this.stop("WebSocket error");
|
|
3263
3296
|
return;
|
|
3264
3297
|
}
|
|
3265
3298
|
this.pendingStart = null;
|
|
@@ -3280,6 +3313,7 @@ var WebSocketClientTransport = class {
|
|
|
3280
3313
|
clearTimeout(pending.timer);
|
|
3281
3314
|
pending.reject(new Error("WebSocket closed before the connection opened"));
|
|
3282
3315
|
}
|
|
3316
|
+
this.notifyClosed("WebSocket closed");
|
|
3283
3317
|
});
|
|
3284
3318
|
ws.addEventListener("message", (ev) => {
|
|
3285
3319
|
this.onData(ev.data);
|
|
@@ -3307,9 +3341,25 @@ var WebSocketClientTransport = class {
|
|
|
3307
3341
|
this.handlers.add(handler);
|
|
3308
3342
|
return () => this.handlers.delete(handler);
|
|
3309
3343
|
}
|
|
3310
|
-
|
|
3344
|
+
onClose(handler) {
|
|
3345
|
+
this.closeHandlers.add(handler);
|
|
3346
|
+
return () => this.closeHandlers.delete(handler);
|
|
3347
|
+
}
|
|
3348
|
+
/** Tell close listeners once; the handlers are dropped with the connection. */
|
|
3349
|
+
notifyClosed(reason) {
|
|
3350
|
+
const handlers = [...this.closeHandlers];
|
|
3351
|
+
this.closeHandlers.clear();
|
|
3352
|
+
for (const handler of handlers) {
|
|
3353
|
+
try {
|
|
3354
|
+
handler(reason);
|
|
3355
|
+
} catch {
|
|
3356
|
+
}
|
|
3357
|
+
}
|
|
3358
|
+
}
|
|
3359
|
+
stop(reason = "transport stopped") {
|
|
3311
3360
|
this.closed = true;
|
|
3312
3361
|
this.handlers.clear();
|
|
3362
|
+
this.notifyClosed(reason);
|
|
3313
3363
|
if (this.pendingStart !== null) {
|
|
3314
3364
|
const pending = this.pendingStart;
|
|
3315
3365
|
this.pendingStart = null;
|
|
@@ -3330,7 +3380,7 @@ var WebSocketClientTransport = class {
|
|
|
3330
3380
|
onData(data) {
|
|
3331
3381
|
const text = typeof data === "string" ? data : data instanceof ArrayBuffer ? Buffer.from(data).toString("utf8") : Buffer.isBuffer(data) ? data.toString("utf8") : String(data);
|
|
3332
3382
|
if (text.length > this.maxMessageChars) {
|
|
3333
|
-
this.stop();
|
|
3383
|
+
this.stop(`agent message exceeds ${this.maxMessageChars} characters`);
|
|
3334
3384
|
return;
|
|
3335
3385
|
}
|
|
3336
3386
|
if (!text.trim()) return;
|
|
@@ -3499,6 +3549,10 @@ var ACPSession = class _ACPSession {
|
|
|
3499
3549
|
}
|
|
3500
3550
|
const session = new _ACPSession(opts, transport);
|
|
3501
3551
|
session.transportOff = transport.onMessage((msg) => session.handleMessage(msg));
|
|
3552
|
+
transport.onClose?.((reason) => {
|
|
3553
|
+
if (!session.closed) rejectPendingRequests(session.pending, reason);
|
|
3554
|
+
void session.close();
|
|
3555
|
+
});
|
|
3502
3556
|
try {
|
|
3503
3557
|
await session.initialize();
|
|
3504
3558
|
} catch (err) {
|
|
@@ -3625,6 +3679,9 @@ var ACPSession = class _ACPSession {
|
|
|
3625
3679
|
if (this.state !== "ready" && this.state !== "authenticated" && this.state !== "done") {
|
|
3626
3680
|
throw new ACPSessionError("protocol_error", `prompt called in state=${this.state}`);
|
|
3627
3681
|
}
|
|
3682
|
+
if (this.promptCallbackAbort) {
|
|
3683
|
+
throw new ACPSessionError("protocol_error", "prompt called while another prompt is running");
|
|
3684
|
+
}
|
|
3628
3685
|
if (signal.aborted) {
|
|
3629
3686
|
return emptyRunResult("cancelled");
|
|
3630
3687
|
}
|
|
@@ -3696,6 +3753,7 @@ var ACPSession = class _ACPSession {
|
|
|
3696
3753
|
this.sessionId = sessionId;
|
|
3697
3754
|
}
|
|
3698
3755
|
if (signal.aborted) {
|
|
3756
|
+
this.promptCallbackAbort = null;
|
|
3699
3757
|
return emptyRunResult("cancelled");
|
|
3700
3758
|
}
|
|
3701
3759
|
this.resetScratch();
|
|
@@ -3803,11 +3861,7 @@ var ACPSession = class _ACPSession {
|
|
|
3803
3861
|
} catch {
|
|
3804
3862
|
}
|
|
3805
3863
|
}
|
|
3806
|
-
|
|
3807
|
-
clearTimeout(p.timeoutHandle);
|
|
3808
|
-
p.reject(new ACPSessionError("closed", "session was closed"));
|
|
3809
|
-
}
|
|
3810
|
-
this.pending.clear();
|
|
3864
|
+
rejectPendingRequests(this.pending);
|
|
3811
3865
|
this.transportOff?.();
|
|
3812
3866
|
this.transportOff = null;
|
|
3813
3867
|
try {
|
|
@@ -4565,6 +4619,7 @@ async function makeACPSubagentRunnerWithStop(options) {
|
|
|
4565
4619
|
const timeoutMs = options.timeoutMs ?? 5 * 6e4;
|
|
4566
4620
|
const persistent = options.persistent === true;
|
|
4567
4621
|
let shared = null;
|
|
4622
|
+
let turns = Promise.resolve();
|
|
4568
4623
|
const startSession = async () => {
|
|
4569
4624
|
return ACPSession.start({
|
|
4570
4625
|
command: options.command,
|
|
@@ -4580,10 +4635,17 @@ async function makeACPSubagentRunnerWithStop(options) {
|
|
|
4580
4635
|
};
|
|
4581
4636
|
const runner = async (task, ctx) => {
|
|
4582
4637
|
let session;
|
|
4583
|
-
const reuse = persistent && shared !== null;
|
|
4584
4638
|
try {
|
|
4585
|
-
|
|
4586
|
-
|
|
4639
|
+
if (persistent) {
|
|
4640
|
+
shared ??= startSession();
|
|
4641
|
+
const starting = shared;
|
|
4642
|
+
session = await starting.catch((err) => {
|
|
4643
|
+
if (shared === starting) shared = null;
|
|
4644
|
+
throw err;
|
|
4645
|
+
});
|
|
4646
|
+
} else {
|
|
4647
|
+
session = await startSession();
|
|
4648
|
+
}
|
|
4587
4649
|
} catch (err) {
|
|
4588
4650
|
throw acpErrorToSubagentError(err, options.role ?? "acp-subagent");
|
|
4589
4651
|
}
|
|
@@ -4600,7 +4662,10 @@ async function makeACPSubagentRunnerWithStop(options) {
|
|
|
4600
4662
|
};
|
|
4601
4663
|
let result;
|
|
4602
4664
|
try {
|
|
4603
|
-
|
|
4665
|
+
const turn = () => session.prompt([textContent(task.description)], ctx.signal, onProgress);
|
|
4666
|
+
const run = persistent ? turns.then(turn, turn) : turn();
|
|
4667
|
+
if (persistent) turns = run;
|
|
4668
|
+
result = await run;
|
|
4604
4669
|
} catch (err) {
|
|
4605
4670
|
throw acpErrorToSubagentError(err, options.role ?? "acp-subagent");
|
|
4606
4671
|
} finally {
|
|
@@ -4621,10 +4686,10 @@ async function makeACPSubagentRunnerWithStop(options) {
|
|
|
4621
4686
|
};
|
|
4622
4687
|
const stop = async () => {
|
|
4623
4688
|
if (shared) {
|
|
4624
|
-
const
|
|
4689
|
+
const starting = shared;
|
|
4625
4690
|
shared = null;
|
|
4626
4691
|
try {
|
|
4627
|
-
await
|
|
4692
|
+
await (await starting).close();
|
|
4628
4693
|
} catch {
|
|
4629
4694
|
}
|
|
4630
4695
|
}
|
|
@@ -541,6 +541,7 @@ async function handleSetModeOp(ctx, id, params) {
|
|
|
541
541
|
modeId
|
|
542
542
|
}
|
|
543
543
|
});
|
|
544
|
+
if (ctx.sessions.get(session.id) === session) await ctx.persist(session);
|
|
544
545
|
await ctx.sendResult(id, {});
|
|
545
546
|
return false;
|
|
546
547
|
}
|
|
@@ -566,7 +567,7 @@ async function handleSetConfigOptionOp(ctx, id, params) {
|
|
|
566
567
|
configOptions: options
|
|
567
568
|
}
|
|
568
569
|
});
|
|
569
|
-
await ctx.persist(session);
|
|
570
|
+
if (ctx.sessions.get(session.id) === session) await ctx.persist(session);
|
|
570
571
|
await ctx.sendResult(id, { configOptions: options });
|
|
571
572
|
return false;
|
|
572
573
|
}
|
|
@@ -1276,16 +1277,17 @@ var WrongStackACPServer = class {
|
|
|
1276
1277
|
return;
|
|
1277
1278
|
}
|
|
1278
1279
|
const MAX_HTTP_BODY = 10 * 1024 * 1024;
|
|
1279
|
-
|
|
1280
|
+
const chunks = [];
|
|
1280
1281
|
let bodyBytes = 0;
|
|
1281
1282
|
let tooLarge = false;
|
|
1282
1283
|
for await (const chunk of req) {
|
|
1283
1284
|
bodyBytes += chunk.length;
|
|
1284
1285
|
if (bodyBytes > MAX_HTTP_BODY) {
|
|
1285
1286
|
tooLarge = true;
|
|
1286
|
-
|
|
1287
|
+
chunks.length = 0;
|
|
1288
|
+
if (bodyBytes > 2 * MAX_HTTP_BODY) break;
|
|
1287
1289
|
}
|
|
1288
|
-
|
|
1290
|
+
if (!tooLarge) chunks.push(chunk);
|
|
1289
1291
|
}
|
|
1290
1292
|
if (tooLarge) {
|
|
1291
1293
|
res.writeHead(413, { "Content-Type": "application/json" });
|
|
@@ -1294,7 +1296,7 @@ var WrongStackACPServer = class {
|
|
|
1294
1296
|
}
|
|
1295
1297
|
let msg;
|
|
1296
1298
|
try {
|
|
1297
|
-
msg = JSON.parse(
|
|
1299
|
+
msg = JSON.parse(Buffer.concat(chunks).toString("utf8"));
|
|
1298
1300
|
} catch {
|
|
1299
1301
|
res.writeHead(400);
|
|
1300
1302
|
res.end(JSON.stringify({ error: { code: -32700, message: "Parse error" } }));
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@wrongstack/acp",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.28",
|
|
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.28",
|
|
56
|
+
"@wrongstack/primitives": "1.0.28"
|
|
57
57
|
},
|
|
58
58
|
"devDependencies": {
|
|
59
59
|
"@types/node": "^26.5.1",
|