@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
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { fileURLToPath } from 'url';
|
|
2
|
+
import { createServer } from 'http';
|
|
2
3
|
import { writeErr, expectDefined } from '@wrongstack/core';
|
|
3
4
|
|
|
4
5
|
// src/agent/wrongstack-acp-agent.ts
|
|
@@ -10,7 +11,16 @@ var ACP_PROTOCOL_VERSION = 1;
|
|
|
10
11
|
function toWire(msg) {
|
|
11
12
|
return msg;
|
|
12
13
|
}
|
|
13
|
-
var WRONGSTACK_VERSION = "0.
|
|
14
|
+
var WRONGSTACK_VERSION = "0.274.1";
|
|
15
|
+
var WRONGSTACK_AUTH_METHODS = [
|
|
16
|
+
{
|
|
17
|
+
id: "wrongstack-auth",
|
|
18
|
+
name: "Run wstack auth",
|
|
19
|
+
description: "Configure a WrongStack model provider in an interactive terminal.",
|
|
20
|
+
type: "terminal",
|
|
21
|
+
args: ["auth"]
|
|
22
|
+
}
|
|
23
|
+
];
|
|
14
24
|
var DEFAULT_MODE_ID = "code";
|
|
15
25
|
var DEFAULT_MODES = [
|
|
16
26
|
{
|
|
@@ -27,9 +37,17 @@ var ACPProtocolHandler = class {
|
|
|
27
37
|
modes;
|
|
28
38
|
configOptions;
|
|
29
39
|
agentName;
|
|
40
|
+
replayFor;
|
|
41
|
+
seedFor;
|
|
42
|
+
store;
|
|
30
43
|
initialized = false;
|
|
44
|
+
clientCapabilities = {};
|
|
31
45
|
sessions = /* @__PURE__ */ new Map();
|
|
32
46
|
nextId = 1;
|
|
47
|
+
// Outbound request correlation (server → client requests, e.g.
|
|
48
|
+
// session/request_permission). Keyed by our own `srv_N` ids.
|
|
49
|
+
pendingOut = /* @__PURE__ */ new Map();
|
|
50
|
+
nextOutId = 1;
|
|
33
51
|
constructor(opts) {
|
|
34
52
|
this.transport = opts.transport;
|
|
35
53
|
this.defaultCwd = opts.defaultCwd;
|
|
@@ -39,6 +57,43 @@ var ACPProtocolHandler = class {
|
|
|
39
57
|
this.modes = opts.modes ?? DEFAULT_MODES;
|
|
40
58
|
this.configOptions = opts.configOptions ?? [];
|
|
41
59
|
this.agentName = opts.agentName ?? "wrongstack";
|
|
60
|
+
this.replayFor = opts.replayFor;
|
|
61
|
+
this.seedFor = opts.seedFor;
|
|
62
|
+
this.store = opts.store;
|
|
63
|
+
if (typeof this.transport.onMessage === "function") {
|
|
64
|
+
this.transport.onMessage((m) => this.maybeResolvePending(m));
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
/**
|
|
68
|
+
* Send a request to the client and await its response. Used for
|
|
69
|
+
* server-initiated calls like `session/request_permission`. Rejects on
|
|
70
|
+
* timeout or transport error so the caller can pick a safe fallback.
|
|
71
|
+
*/
|
|
72
|
+
request(method, params, timeoutMs = 6e4) {
|
|
73
|
+
const id = `srv_${this.nextOutId++}`;
|
|
74
|
+
return new Promise((resolve, reject) => {
|
|
75
|
+
const timer = setTimeout(() => {
|
|
76
|
+
this.pendingOut.delete(id);
|
|
77
|
+
reject(new Error(`${method} timed out after ${timeoutMs}ms`));
|
|
78
|
+
}, timeoutMs);
|
|
79
|
+
this.pendingOut.set(id, { resolve, reject, timer });
|
|
80
|
+
this.transport.send(toWire({ jsonrpc: "2.0", id, method, params })).catch((e) => {
|
|
81
|
+
clearTimeout(timer);
|
|
82
|
+
this.pendingOut.delete(id);
|
|
83
|
+
reject(e instanceof Error ? e : new Error(String(e)));
|
|
84
|
+
});
|
|
85
|
+
});
|
|
86
|
+
}
|
|
87
|
+
maybeResolvePending(m) {
|
|
88
|
+
const id = m.id;
|
|
89
|
+
if (typeof id !== "string") return;
|
|
90
|
+
const pending = this.pendingOut.get(id);
|
|
91
|
+
if (!pending) return;
|
|
92
|
+
this.pendingOut.delete(id);
|
|
93
|
+
clearTimeout(pending.timer);
|
|
94
|
+
const err = m.error;
|
|
95
|
+
if (err) pending.reject(new Error(err.message ?? "client request failed"));
|
|
96
|
+
else pending.resolve(m.result);
|
|
42
97
|
}
|
|
43
98
|
/**
|
|
44
99
|
* Process one inbound message. Returns true if this was a terminal
|
|
@@ -65,6 +120,11 @@ var ACPProtocolHandler = class {
|
|
|
65
120
|
session.abort.abort();
|
|
66
121
|
}
|
|
67
122
|
this.sessions.clear();
|
|
123
|
+
for (const [, p] of this.pendingOut) {
|
|
124
|
+
clearTimeout(p.timer);
|
|
125
|
+
p.reject(new Error("protocol handler closed"));
|
|
126
|
+
}
|
|
127
|
+
this.pendingOut.clear();
|
|
68
128
|
}
|
|
69
129
|
// ────────────────────────────────────────────────────────────────────
|
|
70
130
|
// Requests
|
|
@@ -80,10 +140,18 @@ var ACPProtocolHandler = class {
|
|
|
80
140
|
return await this.handleInitialize(id, params);
|
|
81
141
|
case "authenticate":
|
|
82
142
|
return await this.handleAuthenticate(id, params);
|
|
143
|
+
case "logout":
|
|
144
|
+
return await this.handleLogout(id, params);
|
|
83
145
|
case "session/new":
|
|
84
146
|
return await this.handleSessionNew(id, params);
|
|
85
147
|
case "session/load":
|
|
86
148
|
return await this.handleSessionLoad(id, params);
|
|
149
|
+
case "session/resume":
|
|
150
|
+
return await this.handleSessionResume(id, params);
|
|
151
|
+
case "session/close":
|
|
152
|
+
return await this.handleSessionClose(id, params);
|
|
153
|
+
case "session/delete":
|
|
154
|
+
return await this.handleSessionDelete(id, params);
|
|
87
155
|
case "session/prompt":
|
|
88
156
|
return await this.handleSessionPrompt(id, params);
|
|
89
157
|
case "session/set_mode":
|
|
@@ -92,6 +160,16 @@ var ACPProtocolHandler = class {
|
|
|
92
160
|
return await this.handleSetConfigOption(id, params);
|
|
93
161
|
case "session/list":
|
|
94
162
|
return await this.handleSessionList(id);
|
|
163
|
+
case "session/fork":
|
|
164
|
+
return await this.handleSessionFork(id, params);
|
|
165
|
+
case "providers/list":
|
|
166
|
+
return await this.handleProvidersList(id, params);
|
|
167
|
+
case "providers/set":
|
|
168
|
+
return await this.handleProvidersSet(id, params);
|
|
169
|
+
case "providers/disable":
|
|
170
|
+
return await this.handleProvidersDisable(id, params);
|
|
171
|
+
case "mcp/message":
|
|
172
|
+
return await this.handleMcpMessage(id, params);
|
|
95
173
|
default:
|
|
96
174
|
await this.sendError(id, -32601, `Unknown method: ${method}`);
|
|
97
175
|
return false;
|
|
@@ -104,14 +182,8 @@ var ACPProtocolHandler = class {
|
|
|
104
182
|
}
|
|
105
183
|
async handleInitialize(id, params) {
|
|
106
184
|
const p = params ?? {};
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
await this.sendError(
|
|
110
|
-
id,
|
|
111
|
-
-32e3,
|
|
112
|
-
`server speaks protocolVersion=${ACP_PROTOCOL_VERSION}, client requested ${requested}`
|
|
113
|
-
);
|
|
114
|
-
return false;
|
|
185
|
+
if (p.clientCapabilities && typeof p.clientCapabilities === "object") {
|
|
186
|
+
this.clientCapabilities = p.clientCapabilities;
|
|
115
187
|
}
|
|
116
188
|
this.initialized = true;
|
|
117
189
|
await this.transport.send(toWire({
|
|
@@ -122,9 +194,25 @@ var ACPProtocolHandler = class {
|
|
|
122
194
|
agentCapabilities: {
|
|
123
195
|
loadSession: true,
|
|
124
196
|
promptCapabilities: {
|
|
125
|
-
image
|
|
197
|
+
// We route ACP image blocks into the core agent's multimodal
|
|
198
|
+
// input (server-agent-turn.promptToAgentInput); whether the
|
|
199
|
+
// model can see them is the configured provider's concern.
|
|
200
|
+
image: true,
|
|
126
201
|
audio: false,
|
|
127
202
|
embeddedContext: true
|
|
203
|
+
},
|
|
204
|
+
mcpCapabilities: {
|
|
205
|
+
http: false,
|
|
206
|
+
sse: false
|
|
207
|
+
},
|
|
208
|
+
sessionCapabilities: {
|
|
209
|
+
close: {},
|
|
210
|
+
list: {},
|
|
211
|
+
delete: {},
|
|
212
|
+
resume: {}
|
|
213
|
+
},
|
|
214
|
+
auth: {
|
|
215
|
+
logout: {}
|
|
128
216
|
}
|
|
129
217
|
},
|
|
130
218
|
agentInfo: {
|
|
@@ -132,10 +220,7 @@ var ACPProtocolHandler = class {
|
|
|
132
220
|
title: "WrongStack",
|
|
133
221
|
version: WRONGSTACK_VERSION
|
|
134
222
|
},
|
|
135
|
-
|
|
136
|
-
// re-sent on every `current_mode_update` / `config_option_update`
|
|
137
|
-
// notification so late-joining clients see them.
|
|
138
|
-
authMethods: [],
|
|
223
|
+
authMethods: WRONGSTACK_AUTH_METHODS,
|
|
139
224
|
modes: this.modes,
|
|
140
225
|
configOptions: this.configOptions
|
|
141
226
|
}
|
|
@@ -150,6 +235,14 @@ var ACPProtocolHandler = class {
|
|
|
150
235
|
}));
|
|
151
236
|
return false;
|
|
152
237
|
}
|
|
238
|
+
async handleLogout(id, _params) {
|
|
239
|
+
await this.transport.send(toWire({
|
|
240
|
+
jsonrpc: "2.0",
|
|
241
|
+
id,
|
|
242
|
+
result: {}
|
|
243
|
+
}));
|
|
244
|
+
return false;
|
|
245
|
+
}
|
|
153
246
|
async handleSessionNew(id, params) {
|
|
154
247
|
const p = params ?? {};
|
|
155
248
|
const cwd = typeof p.cwd === "string" ? p.cwd : this.defaultCwd;
|
|
@@ -165,6 +258,7 @@ var ACPProtocolHandler = class {
|
|
|
165
258
|
};
|
|
166
259
|
this.sessions.set(sessionId, state);
|
|
167
260
|
this.onSessionNew(state);
|
|
261
|
+
await this.persist(state);
|
|
168
262
|
await this.sendNotification({
|
|
169
263
|
sessionId,
|
|
170
264
|
update: {
|
|
@@ -193,7 +287,173 @@ var ACPProtocolHandler = class {
|
|
|
193
287
|
return false;
|
|
194
288
|
}
|
|
195
289
|
async handleSessionLoad(id, params) {
|
|
196
|
-
|
|
290
|
+
const p = params ?? {};
|
|
291
|
+
const sessionId = typeof p.sessionId === "string" ? p.sessionId : null;
|
|
292
|
+
const loadCwd = typeof p.cwd === "string" ? p.cwd : void 0;
|
|
293
|
+
let existing = sessionId ? this.sessions.get(sessionId) : void 0;
|
|
294
|
+
if (!existing && sessionId && this.store) {
|
|
295
|
+
const persisted = await this.store.load(sessionId);
|
|
296
|
+
if (persisted) {
|
|
297
|
+
const restored = {
|
|
298
|
+
id: sessionId,
|
|
299
|
+
cwd: persisted.cwd ?? loadCwd ?? this.defaultCwd,
|
|
300
|
+
abort: new AbortController(),
|
|
301
|
+
modeId: persisted.modeId ?? DEFAULT_MODE_ID,
|
|
302
|
+
createdAt: persisted.createdAt ?? (/* @__PURE__ */ new Date()).toISOString(),
|
|
303
|
+
updatedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
304
|
+
...persisted.title !== void 0 ? { title: persisted.title } : {}
|
|
305
|
+
};
|
|
306
|
+
this.sessions.set(sessionId, restored);
|
|
307
|
+
this.seedFor?.(sessionId, persisted.history ?? []);
|
|
308
|
+
for (const update of persisted.history ?? []) {
|
|
309
|
+
await this.sendNotification({ sessionId, update });
|
|
310
|
+
}
|
|
311
|
+
await this.sendNotification({
|
|
312
|
+
sessionId,
|
|
313
|
+
update: { sessionUpdate: "current_mode_update", modeId: restored.modeId }
|
|
314
|
+
});
|
|
315
|
+
await this.transport.send(toWire({
|
|
316
|
+
jsonrpc: "2.0",
|
|
317
|
+
id,
|
|
318
|
+
result: {
|
|
319
|
+
initialMode: { currentModeId: restored.modeId, availableModes: this.modes }
|
|
320
|
+
}
|
|
321
|
+
}));
|
|
322
|
+
return false;
|
|
323
|
+
}
|
|
324
|
+
}
|
|
325
|
+
if (existing) {
|
|
326
|
+
existing.updatedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
327
|
+
const replay = sessionId ? this.replayFor?.(sessionId) : void 0;
|
|
328
|
+
if (replay) {
|
|
329
|
+
for (const update of replay) {
|
|
330
|
+
await this.sendNotification({ sessionId, update });
|
|
331
|
+
}
|
|
332
|
+
}
|
|
333
|
+
await this.sendNotification({
|
|
334
|
+
sessionId,
|
|
335
|
+
update: {
|
|
336
|
+
sessionUpdate: "session_info_update",
|
|
337
|
+
updatedAt: existing.updatedAt
|
|
338
|
+
}
|
|
339
|
+
});
|
|
340
|
+
await this.sendNotification({
|
|
341
|
+
sessionId,
|
|
342
|
+
update: {
|
|
343
|
+
sessionUpdate: "current_mode_update",
|
|
344
|
+
modeId: existing.modeId
|
|
345
|
+
}
|
|
346
|
+
});
|
|
347
|
+
await this.transport.send(toWire({
|
|
348
|
+
jsonrpc: "2.0",
|
|
349
|
+
id,
|
|
350
|
+
result: {
|
|
351
|
+
initialMode: {
|
|
352
|
+
currentModeId: existing.modeId,
|
|
353
|
+
availableModes: this.modes
|
|
354
|
+
}
|
|
355
|
+
}
|
|
356
|
+
}));
|
|
357
|
+
return false;
|
|
358
|
+
}
|
|
359
|
+
await this.sendError(id, -32e3, `session not found: ${sessionId}`);
|
|
360
|
+
return false;
|
|
361
|
+
}
|
|
362
|
+
async handleSessionResume(id, params) {
|
|
363
|
+
const p = params ?? {};
|
|
364
|
+
const sessionId = typeof p.sessionId === "string" ? p.sessionId : null;
|
|
365
|
+
const existing = sessionId ? this.sessions.get(sessionId) : void 0;
|
|
366
|
+
if (existing) {
|
|
367
|
+
existing.updatedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
368
|
+
await this.transport.send(toWire({
|
|
369
|
+
jsonrpc: "2.0",
|
|
370
|
+
id,
|
|
371
|
+
result: {
|
|
372
|
+
initialMode: {
|
|
373
|
+
currentModeId: existing.modeId,
|
|
374
|
+
availableModes: this.modes
|
|
375
|
+
}
|
|
376
|
+
}
|
|
377
|
+
}));
|
|
378
|
+
return false;
|
|
379
|
+
}
|
|
380
|
+
await this.sendError(id, -32e3, `session not found: ${sessionId}`);
|
|
381
|
+
return false;
|
|
382
|
+
}
|
|
383
|
+
async handleSessionClose(id, params) {
|
|
384
|
+
const p = params ?? {};
|
|
385
|
+
const sessionId = typeof p.sessionId === "string" ? p.sessionId : null;
|
|
386
|
+
const session = sessionId ? this.sessions.get(sessionId) : void 0;
|
|
387
|
+
if (!session) {
|
|
388
|
+
await this.sendError(id, -32e3, `session not found: ${sessionId}`);
|
|
389
|
+
return false;
|
|
390
|
+
}
|
|
391
|
+
session.abort.abort();
|
|
392
|
+
if (sessionId) this.sessions.delete(sessionId);
|
|
393
|
+
await this.transport.send(toWire({
|
|
394
|
+
jsonrpc: "2.0",
|
|
395
|
+
id,
|
|
396
|
+
result: {}
|
|
397
|
+
}));
|
|
398
|
+
return false;
|
|
399
|
+
}
|
|
400
|
+
async handleSessionDelete(id, params) {
|
|
401
|
+
const p = params ?? {};
|
|
402
|
+
const sessionId = typeof p.sessionId === "string" ? p.sessionId : null;
|
|
403
|
+
if (!sessionId) {
|
|
404
|
+
await this.sendError(id, -32e3, `session not found: ${sessionId}`);
|
|
405
|
+
return false;
|
|
406
|
+
}
|
|
407
|
+
if (!this.sessions.has(sessionId)) {
|
|
408
|
+
await this.transport.send(toWire({ jsonrpc: "2.0", id, result: { configOptions: [...this.configOptions] } }));
|
|
409
|
+
return false;
|
|
410
|
+
}
|
|
411
|
+
const session = this.sessions.get(sessionId);
|
|
412
|
+
session.abort.abort();
|
|
413
|
+
this.sessions.delete(sessionId);
|
|
414
|
+
await this.transport.send(toWire({
|
|
415
|
+
jsonrpc: "2.0",
|
|
416
|
+
id,
|
|
417
|
+
result: {}
|
|
418
|
+
}));
|
|
419
|
+
return false;
|
|
420
|
+
}
|
|
421
|
+
async handleSessionFork(id, params) {
|
|
422
|
+
const p = params ?? {};
|
|
423
|
+
const sourceId = typeof p.sessionId === "string" ? p.sessionId : null;
|
|
424
|
+
if (!sourceId || !this.sessions.has(sourceId)) {
|
|
425
|
+
await this.sendError(id, -32e3, `session not found: ${sourceId}`);
|
|
426
|
+
return false;
|
|
427
|
+
}
|
|
428
|
+
const forkParams = params;
|
|
429
|
+
return this.handleSessionNew(id, { ...forkParams, cwd: p.cwd ?? this.defaultCwd });
|
|
430
|
+
}
|
|
431
|
+
async handleProvidersList(id, _params) {
|
|
432
|
+
await this.transport.send(toWire({
|
|
433
|
+
jsonrpc: "2.0",
|
|
434
|
+
id,
|
|
435
|
+
result: {
|
|
436
|
+
providers: [],
|
|
437
|
+
currentProviderId: null
|
|
438
|
+
}
|
|
439
|
+
}));
|
|
440
|
+
return false;
|
|
441
|
+
}
|
|
442
|
+
async handleProvidersSet(id, _params) {
|
|
443
|
+
await this.sendError(id, -32e3, "provider configuration not available through ACP; use wstack auth");
|
|
444
|
+
return false;
|
|
445
|
+
}
|
|
446
|
+
async handleProvidersDisable(id, _params) {
|
|
447
|
+
await this.transport.send(toWire({
|
|
448
|
+
jsonrpc: "2.0",
|
|
449
|
+
id,
|
|
450
|
+
result: {}
|
|
451
|
+
}));
|
|
452
|
+
return false;
|
|
453
|
+
}
|
|
454
|
+
async handleMcpMessage(id, _params) {
|
|
455
|
+
await this.sendError(id, -32e3, "MCP message routing not available through ACP");
|
|
456
|
+
return false;
|
|
197
457
|
}
|
|
198
458
|
async handleSessionPrompt(id, params) {
|
|
199
459
|
const p = params ?? {};
|
|
@@ -213,11 +473,54 @@ var ACPProtocolHandler = class {
|
|
|
213
473
|
const turnSignal = new AbortController();
|
|
214
474
|
const onCancel = () => turnSignal.abort();
|
|
215
475
|
session.abort.signal.addEventListener("abort", onCancel, { once: true });
|
|
476
|
+
const api = {
|
|
477
|
+
clientCapabilities: this.clientCapabilities,
|
|
478
|
+
requestPermission: async (req) => {
|
|
479
|
+
const res = await this.request("session/request_permission", {
|
|
480
|
+
sessionId,
|
|
481
|
+
toolCall: req.toolCall,
|
|
482
|
+
options: req.options
|
|
483
|
+
});
|
|
484
|
+
const outcome = res?.outcome;
|
|
485
|
+
return outcome ?? { outcome: "cancelled" };
|
|
486
|
+
},
|
|
487
|
+
readTextFile: async (params2) => {
|
|
488
|
+
const res = await this.request("fs/read_text_file", { sessionId, ...params2 });
|
|
489
|
+
return String(res?.content ?? "");
|
|
490
|
+
},
|
|
491
|
+
writeTextFile: async (params2) => {
|
|
492
|
+
await this.request("fs/write_text_file", { sessionId, ...params2 });
|
|
493
|
+
},
|
|
494
|
+
runTerminal: async ({ command, args, cwd }) => {
|
|
495
|
+
const created = await this.request("terminal/create", {
|
|
496
|
+
sessionId,
|
|
497
|
+
command,
|
|
498
|
+
...args ? { args } : {},
|
|
499
|
+
...cwd ? { cwd } : {}
|
|
500
|
+
});
|
|
501
|
+
const terminalId = created?.terminalId;
|
|
502
|
+
if (!terminalId) return { output: "", exitCode: null };
|
|
503
|
+
try {
|
|
504
|
+
const exit = await this.request("terminal/wait_for_exit", { sessionId, terminalId });
|
|
505
|
+
const out = await this.request("terminal/output", { sessionId, terminalId });
|
|
506
|
+
return {
|
|
507
|
+
output: String(out?.output ?? ""),
|
|
508
|
+
exitCode: typeof exit?.exitCode === "number" ? exit.exitCode : null
|
|
509
|
+
};
|
|
510
|
+
} finally {
|
|
511
|
+
try {
|
|
512
|
+
await this.request("terminal/release", { sessionId, terminalId });
|
|
513
|
+
} catch {
|
|
514
|
+
}
|
|
515
|
+
}
|
|
516
|
+
}
|
|
517
|
+
};
|
|
216
518
|
let result;
|
|
217
519
|
try {
|
|
218
520
|
result = await this.runTurn(
|
|
219
521
|
{ sessionId, prompt: p.prompt, signal: turnSignal.signal },
|
|
220
|
-
(update) => this.sendNotification({ sessionId, update })
|
|
522
|
+
(update) => this.sendNotification({ sessionId, update }),
|
|
523
|
+
api
|
|
221
524
|
);
|
|
222
525
|
} catch (err) {
|
|
223
526
|
session.abort.signal.removeEventListener("abort", onCancel);
|
|
@@ -227,6 +530,7 @@ var ACPProtocolHandler = class {
|
|
|
227
530
|
}
|
|
228
531
|
session.abort.signal.removeEventListener("abort", onCancel);
|
|
229
532
|
session.updatedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
533
|
+
await this.persist(session);
|
|
230
534
|
await this.transport.send(toWire({
|
|
231
535
|
jsonrpc: "2.0",
|
|
232
536
|
id,
|
|
@@ -255,12 +559,12 @@ var ACPProtocolHandler = class {
|
|
|
255
559
|
async handleSetConfigOption(id, params) {
|
|
256
560
|
const p = params ?? {};
|
|
257
561
|
const sessionId = typeof p.sessionId === "string" ? p.sessionId : null;
|
|
258
|
-
const optionId = typeof p.
|
|
562
|
+
const optionId = typeof p.configId === "string" ? p.configId : null;
|
|
259
563
|
const value = typeof p.value === "string" ? p.value : null;
|
|
260
564
|
const session = sessionId ? this.sessions.get(sessionId) : void 0;
|
|
261
565
|
const option = optionId ? this.configOptions.find((o) => o.id === optionId) : void 0;
|
|
262
566
|
if (!session || !option || value === null || !option.options.some((o) => o.value === value)) {
|
|
263
|
-
await this.sendError(id, -32602, "invalid sessionId,
|
|
567
|
+
await this.sendError(id, -32602, "invalid sessionId, configId, or value");
|
|
264
568
|
return false;
|
|
265
569
|
}
|
|
266
570
|
option.currentValue = value;
|
|
@@ -272,7 +576,7 @@ var ACPProtocolHandler = class {
|
|
|
272
576
|
configOptions: [...this.configOptions]
|
|
273
577
|
}
|
|
274
578
|
});
|
|
275
|
-
await this.transport.send(toWire({ jsonrpc: "2.0", id, result: {} }));
|
|
579
|
+
await this.transport.send(toWire({ jsonrpc: "2.0", id, result: { configOptions: [...this.configOptions] } }));
|
|
276
580
|
return false;
|
|
277
581
|
}
|
|
278
582
|
async handleSessionList(id) {
|
|
@@ -306,6 +610,9 @@ var ACPProtocolHandler = class {
|
|
|
306
610
|
}
|
|
307
611
|
return false;
|
|
308
612
|
}
|
|
613
|
+
case "$/cancel_request": {
|
|
614
|
+
return false;
|
|
615
|
+
}
|
|
309
616
|
case "exit":
|
|
310
617
|
this.close();
|
|
311
618
|
return true;
|
|
@@ -319,6 +626,14 @@ var ACPProtocolHandler = class {
|
|
|
319
626
|
async sendNotification(params) {
|
|
320
627
|
await this.transport.send(toWire({ jsonrpc: "2.0", method: "session/update", params }));
|
|
321
628
|
}
|
|
629
|
+
/** Best-effort durable persistence of a session + its recorded history. */
|
|
630
|
+
async persist(state) {
|
|
631
|
+
if (!this.store) return;
|
|
632
|
+
try {
|
|
633
|
+
await this.store.save(state, this.replayFor?.(state.id));
|
|
634
|
+
} catch {
|
|
635
|
+
}
|
|
636
|
+
}
|
|
322
637
|
async sendError(id, code, message, data) {
|
|
323
638
|
const error = { code, message };
|
|
324
639
|
if (data !== void 0) error.data = data;
|
|
@@ -436,26 +751,41 @@ var StdioTransport = class {
|
|
|
436
751
|
var WrongStackACPServer = class {
|
|
437
752
|
transport;
|
|
438
753
|
handler;
|
|
754
|
+
options;
|
|
755
|
+
/** HTTP server when transport mode is HTTP. */
|
|
756
|
+
httpServer = null;
|
|
439
757
|
running = false;
|
|
440
758
|
constructor(opts = {}) {
|
|
759
|
+
this.options = opts;
|
|
441
760
|
this.transport = new StdioTransport();
|
|
442
761
|
const runTurn = opts.runTurn ?? defaultEchoRunTurn;
|
|
443
762
|
this.handler = new ACPProtocolHandler({
|
|
444
763
|
transport: this.transport,
|
|
445
764
|
defaultCwd: opts.defaultCwd ?? process.cwd(),
|
|
446
765
|
runTurn,
|
|
447
|
-
agentName: opts.agentName
|
|
766
|
+
agentName: opts.agentName,
|
|
767
|
+
...opts.replayFor ? { replayFor: opts.replayFor } : {},
|
|
768
|
+
...opts.seedFor ? { seedFor: opts.seedFor } : {},
|
|
769
|
+
...opts.store ? { store: opts.store } : {}
|
|
448
770
|
});
|
|
449
771
|
}
|
|
450
772
|
/**
|
|
451
|
-
* Start the server.
|
|
452
|
-
*
|
|
453
|
-
*
|
|
454
|
-
* process is the ACP server (the old `StdioTransport` handshake).
|
|
455
|
-
* 2. Loop: read messages, dispatch to the handler, until EOF / error.
|
|
773
|
+
* Start the server. Mode depends on `options.transport`:
|
|
774
|
+
* - 'stdio' (default): reads JSON-RPC from stdin, writes to stdout.
|
|
775
|
+
* - number: listens as HTTP on the given port.
|
|
456
776
|
*/
|
|
457
777
|
async start() {
|
|
458
|
-
this.transport
|
|
778
|
+
const transportMode = this.options.transport;
|
|
779
|
+
if (typeof transportMode === "number") {
|
|
780
|
+
await this.startHttp(transportMode);
|
|
781
|
+
} else {
|
|
782
|
+
await this.startStdio();
|
|
783
|
+
}
|
|
784
|
+
}
|
|
785
|
+
async startStdio() {
|
|
786
|
+
if (this.options.legacyStartupMarker) {
|
|
787
|
+
this.transport.sendStartupMarker();
|
|
788
|
+
}
|
|
459
789
|
this.running = true;
|
|
460
790
|
while (this.running) {
|
|
461
791
|
const msg = await this.transport.read();
|
|
@@ -465,10 +795,80 @@ var WrongStackACPServer = class {
|
|
|
465
795
|
}
|
|
466
796
|
this.transport.close();
|
|
467
797
|
}
|
|
798
|
+
async startHttp(port) {
|
|
799
|
+
const host = this.options.host ?? "127.0.0.1";
|
|
800
|
+
const handler = this.handler;
|
|
801
|
+
this.httpServer = createServer(async (req, res) => {
|
|
802
|
+
const selfOrigin = `http://${host}:${port}`;
|
|
803
|
+
const reqOrigin = Array.isArray(req.headers.origin) ? req.headers.origin[0] : req.headers.origin;
|
|
804
|
+
if (reqOrigin && reqOrigin !== selfOrigin) {
|
|
805
|
+
res.writeHead(403);
|
|
806
|
+
res.end(JSON.stringify({ error: "cross-origin request forbidden" }));
|
|
807
|
+
return;
|
|
808
|
+
}
|
|
809
|
+
if (reqOrigin) res.setHeader("Access-Control-Allow-Origin", reqOrigin);
|
|
810
|
+
res.setHeader("Access-Control-Allow-Methods", "POST, OPTIONS");
|
|
811
|
+
res.setHeader("Access-Control-Allow-Headers", "Content-Type, Mcp-Session-Id");
|
|
812
|
+
if (req.method === "OPTIONS") {
|
|
813
|
+
res.writeHead(204);
|
|
814
|
+
res.end();
|
|
815
|
+
return;
|
|
816
|
+
}
|
|
817
|
+
if (req.method !== "POST") {
|
|
818
|
+
res.writeHead(405);
|
|
819
|
+
res.end(JSON.stringify({ error: "method not allowed" }));
|
|
820
|
+
return;
|
|
821
|
+
}
|
|
822
|
+
let body = "";
|
|
823
|
+
for await (const chunk of req) {
|
|
824
|
+
body += chunk;
|
|
825
|
+
}
|
|
826
|
+
let msg;
|
|
827
|
+
try {
|
|
828
|
+
msg = JSON.parse(body);
|
|
829
|
+
} catch {
|
|
830
|
+
res.writeHead(400);
|
|
831
|
+
res.end(JSON.stringify({ error: { code: -32700, message: "Parse error" } }));
|
|
832
|
+
return;
|
|
833
|
+
}
|
|
834
|
+
const notifications = [];
|
|
835
|
+
let response = null;
|
|
836
|
+
const originalSend = this.transport.send.bind(this.transport);
|
|
837
|
+
this.transport.send = async (m) => {
|
|
838
|
+
if (m.id !== void 0 && (m.result !== void 0 || m.error !== void 0)) {
|
|
839
|
+
response = m;
|
|
840
|
+
} else if (m.method === "session/update") {
|
|
841
|
+
notifications.push(m.params);
|
|
842
|
+
} else {
|
|
843
|
+
notifications.push(m);
|
|
844
|
+
}
|
|
845
|
+
};
|
|
846
|
+
try {
|
|
847
|
+
await handler.handleMessage(msg);
|
|
848
|
+
} finally {
|
|
849
|
+
this.transport.send = originalSend;
|
|
850
|
+
}
|
|
851
|
+
res.writeHead(200, { "Content-Type": "application/json" });
|
|
852
|
+
const responseBody = response !== null ? { ...response, notifications } : { notifications };
|
|
853
|
+
res.end(JSON.stringify(responseBody));
|
|
854
|
+
});
|
|
855
|
+
return new Promise((resolve) => {
|
|
856
|
+
this.httpServer.listen(port, host, () => {
|
|
857
|
+
writeErr(`[wstack-acp] HTTP server listening on http://${host}:${port}
|
|
858
|
+
`);
|
|
859
|
+
this.running = true;
|
|
860
|
+
resolve();
|
|
861
|
+
});
|
|
862
|
+
});
|
|
863
|
+
}
|
|
468
864
|
/** Stop the server. */
|
|
469
865
|
stop() {
|
|
470
866
|
this.running = false;
|
|
471
867
|
this.transport.close();
|
|
868
|
+
if (this.httpServer) {
|
|
869
|
+
this.httpServer.close();
|
|
870
|
+
this.httpServer = null;
|
|
871
|
+
}
|
|
472
872
|
}
|
|
473
873
|
};
|
|
474
874
|
var defaultEchoRunTurn = async (_input, _emit) => {
|