@wrongstack/acp 0.297.0 → 0.298.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/dist/agent/protocol-handler.d.ts +39 -46
- package/dist/agent/session-store.d.ts +55 -0
- package/dist/agent/stdio-transport.d.ts +74 -1
- package/dist/agent.js +472 -210
- package/dist/client/terminal-server.d.ts +5 -1
- package/dist/client.js +70 -44
- package/dist/index.js +433 -253
- package/dist/win32-cmd.d.ts +9 -6
- package/dist/wrongstack-acp-agent.js +312 -157
- package/package.json +5 -4
- package/dist/agent/index.d.ts.map +0 -1
- package/dist/agent/protocol-contract.d.ts.map +0 -1
- package/dist/agent/protocol-handler.d.ts.map +0 -1
- package/dist/agent/server-agent-turn.d.ts.map +0 -1
- package/dist/agent/session-store.d.ts.map +0 -1
- package/dist/agent/stdio-transport.d.ts.map +0 -1
- package/dist/agent/tools-registry.d.ts.map +0 -1
- package/dist/agent/wrongstack-acp-agent.d.ts.map +0 -1
- package/dist/agent/ws-bridge-transport.d.ts.map +0 -1
- package/dist/agent.js.map +0 -7
- package/dist/client/acp-message-routing.d.ts.map +0 -1
- package/dist/client/acp-session-callbacks.d.ts.map +0 -1
- package/dist/client/acp-session-content.d.ts.map +0 -1
- package/dist/client/acp-session-errors.d.ts.map +0 -1
- package/dist/client/acp-session-types.d.ts.map +0 -1
- package/dist/client/acp-session-updates.d.ts.map +0 -1
- package/dist/client/acp-session.d.ts.map +0 -1
- package/dist/client/file-server.d.ts.map +0 -1
- package/dist/client/index.d.ts.map +0 -1
- package/dist/client/permission.d.ts.map +0 -1
- package/dist/client/terminal-server.d.ts.map +0 -1
- package/dist/client/tool-translator.d.ts.map +0 -1
- package/dist/client/trust-boundary-permission.d.ts.map +0 -1
- package/dist/client/websocket-transport.d.ts.map +0 -1
- package/dist/client.js.map +0 -7
- package/dist/index.d.ts.map +0 -1
- package/dist/index.js.map +0 -7
- package/dist/integration/acp-bench.d.ts.map +0 -1
- package/dist/integration/acp-subagent-runner.d.ts.map +0 -1
- package/dist/integration/ensemble-runner.d.ts.map +0 -1
- package/dist/integration/run-one-acp-task.d.ts.map +0 -1
- package/dist/legacy.d.ts.map +0 -1
- package/dist/legacy.js.map +0 -7
- package/dist/registry/acp-registry-fetch.d.ts.map +0 -1
- package/dist/registry/agents.catalog.d.ts.map +0 -1
- package/dist/registry/ensemble-registry.d.ts.map +0 -1
- package/dist/sdk.d.ts.map +0 -1
- package/dist/sdk.js.map +0 -7
- package/dist/types/acp-messages.d.ts.map +0 -1
- package/dist/types/acp-v1.d.ts.map +0 -1
- package/dist/v1.d.ts.map +0 -1
- package/dist/v1.js.map +0 -7
- package/dist/version.d.ts.map +0 -1
- package/dist/win32-cmd.d.ts.map +0 -1
- package/dist/wrongstack-acp-agent.js.map +0 -7
|
@@ -55,7 +55,11 @@ export declare class TerminalServer {
|
|
|
55
55
|
exitCode: number | null;
|
|
56
56
|
signal: string | null;
|
|
57
57
|
}>;
|
|
58
|
-
/**
|
|
58
|
+
/**
|
|
59
|
+
* Kill the process but keep the terminal record (agent can still read output).
|
|
60
|
+
* On POSIX this signals only the direct child; descendants may survive because
|
|
61
|
+
* terminal processes are not spawned as process-group leaders.
|
|
62
|
+
*/
|
|
59
63
|
kill(terminalId: string): void;
|
|
60
64
|
/** Kill the process if alive and remove the record. */
|
|
61
65
|
release(terminalId: string): void;
|
package/dist/client.js
CHANGED
|
@@ -3,32 +3,14 @@ import { expectDefined, writeErr } from "@wrongstack/core/utils";
|
|
|
3
3
|
import { treeKill } from "@wrongstack/core/utils/tree-kill";
|
|
4
4
|
|
|
5
5
|
// src/win32-cmd.ts
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
const line = ["call", quoteWin32CmdArg(command), ...args.map(quoteWin32CmdArg)].join(" ");
|
|
10
|
-
return {
|
|
11
|
-
command: process.env["COMSPEC"] ?? "cmd.exe",
|
|
12
|
-
args: ["/d", "/c", line],
|
|
13
|
-
windowsVerbatimArguments: true
|
|
14
|
-
};
|
|
15
|
-
}
|
|
16
|
-
function assertSafeWin32CmdArgs(args) {
|
|
17
|
-
for (const arg of args) {
|
|
18
|
-
if (typeof arg === "string" && WIN32_CMD_META.test(arg)) {
|
|
19
|
-
throw new Error(
|
|
20
|
-
'win32 cmd shim spawn: argument contains a shell metacharacter (one of & | < > ", or a newline) that could enable command injection through the .cmd/.bat wrapper - refusing to run. Offending argument: ' + JSON.stringify(arg)
|
|
21
|
-
);
|
|
22
|
-
}
|
|
23
|
-
}
|
|
24
|
-
}
|
|
25
|
-
function quoteWin32CmdArg(arg) {
|
|
26
|
-
return `"${arg}"`;
|
|
27
|
-
}
|
|
6
|
+
import {
|
|
7
|
+
buildWin32CmdShimInvocation
|
|
8
|
+
} from "@wrongstack/core/utils";
|
|
28
9
|
|
|
29
10
|
// src/agent/stdio-transport.ts
|
|
30
11
|
var DEFAULT_MAX_FRAME_CHARS = 20 * 1024 * 1024;
|
|
31
12
|
var DEFAULT_MAX_QUEUED_MESSAGES = 1e3;
|
|
13
|
+
var DEFAULT_MAX_QUEUED_CHARS = 32 * 1024 * 1024;
|
|
32
14
|
function positiveLimit(value, fallback) {
|
|
33
15
|
return value !== void 0 && Number.isFinite(value) && value > 0 ? Math.floor(value) : fallback;
|
|
34
16
|
}
|
|
@@ -36,12 +18,15 @@ var ClientTransport = class {
|
|
|
36
18
|
child = null;
|
|
37
19
|
buffer = "";
|
|
38
20
|
handlers = /* @__PURE__ */ new Set();
|
|
21
|
+
claimHandlers = /* @__PURE__ */ new Set();
|
|
39
22
|
closed = false;
|
|
40
23
|
resolveRead = null;
|
|
41
24
|
messageQueue = [];
|
|
25
|
+
queuedChars = 0;
|
|
42
26
|
opts;
|
|
43
27
|
maxFrameChars;
|
|
44
28
|
maxQueuedMessages;
|
|
29
|
+
maxQueuedChars;
|
|
45
30
|
constructor(options) {
|
|
46
31
|
this.opts = {
|
|
47
32
|
handshakeTimeoutMs: 3e4,
|
|
@@ -49,6 +34,7 @@ var ClientTransport = class {
|
|
|
49
34
|
};
|
|
50
35
|
this.maxFrameChars = positiveLimit(options.maxFrameChars, DEFAULT_MAX_FRAME_CHARS);
|
|
51
36
|
this.maxQueuedMessages = positiveLimit(options.maxQueuedMessages, DEFAULT_MAX_QUEUED_MESSAGES);
|
|
37
|
+
this.maxQueuedChars = positiveLimit(options.maxQueuedChars, DEFAULT_MAX_QUEUED_CHARS);
|
|
52
38
|
}
|
|
53
39
|
async start() {
|
|
54
40
|
if (this.child) return;
|
|
@@ -149,8 +135,11 @@ var ClientTransport = class {
|
|
|
149
135
|
});
|
|
150
136
|
}
|
|
151
137
|
read() {
|
|
152
|
-
if (this.messageQueue.length > 0)
|
|
153
|
-
|
|
138
|
+
if (this.messageQueue.length > 0) {
|
|
139
|
+
const queued = expectDefined(this.messageQueue.shift());
|
|
140
|
+
this.queuedChars = Math.max(0, this.queuedChars - queued.chars);
|
|
141
|
+
return Promise.resolve(queued.message);
|
|
142
|
+
}
|
|
154
143
|
if (this.closed) return Promise.resolve(null);
|
|
155
144
|
return new Promise((resolve3) => {
|
|
156
145
|
this.resolveRead = resolve3;
|
|
@@ -160,13 +149,29 @@ var ClientTransport = class {
|
|
|
160
149
|
this.handlers.add(handler);
|
|
161
150
|
return () => this.handlers.delete(handler);
|
|
162
151
|
}
|
|
152
|
+
/**
|
|
153
|
+
* Register a handler that MAY claim a message by returning `true`.
|
|
154
|
+
* Claimed messages are NOT enqueued for the read() loop — they are
|
|
155
|
+
* considered fully consumed by the handler. This is how the ACP
|
|
156
|
+
* server transport prevents the correlation handler (which always
|
|
157
|
+
* fires on every message but only actually correlates responses)
|
|
158
|
+
* from starving the read() loop of pipelined requests, notifications,
|
|
159
|
+
* and any message the handler chose to ignore. See dispatch() for
|
|
160
|
+
* the gating logic.
|
|
161
|
+
*/
|
|
162
|
+
onMessageClaim(handler) {
|
|
163
|
+
this.claimHandlers.add(handler);
|
|
164
|
+
return () => this.claimHandlers.delete(handler);
|
|
165
|
+
}
|
|
163
166
|
stop() {
|
|
164
167
|
this.closed = true;
|
|
165
168
|
this.resolveRead?.(null);
|
|
166
169
|
this.resolveRead = null;
|
|
167
170
|
this.buffer = "";
|
|
168
171
|
this.messageQueue.length = 0;
|
|
172
|
+
this.queuedChars = 0;
|
|
169
173
|
this.handlers.clear();
|
|
174
|
+
this.claimHandlers.clear();
|
|
170
175
|
const child = this.child;
|
|
171
176
|
if (!child) return;
|
|
172
177
|
treeKill(child);
|
|
@@ -191,7 +196,7 @@ var ClientTransport = class {
|
|
|
191
196
|
return;
|
|
192
197
|
}
|
|
193
198
|
try {
|
|
194
|
-
this.dispatch(JSON.parse(raw));
|
|
199
|
+
this.dispatch(JSON.parse(raw), raw.length);
|
|
195
200
|
} catch {
|
|
196
201
|
}
|
|
197
202
|
}
|
|
@@ -201,29 +206,34 @@ var ClientTransport = class {
|
|
|
201
206
|
}
|
|
202
207
|
onChildClose(code) {
|
|
203
208
|
this.closed = true;
|
|
209
|
+
this.child = null;
|
|
204
210
|
this.resolveRead?.(null);
|
|
205
211
|
this.resolveRead = null;
|
|
206
212
|
this.buffer = "";
|
|
207
213
|
this.messageQueue.length = 0;
|
|
214
|
+
this.queuedChars = 0;
|
|
208
215
|
this.handlers.clear();
|
|
209
216
|
if (code !== 0 && code !== null) {
|
|
210
217
|
writeErr(`[acp-child exited with code ${code}]
|
|
211
218
|
`);
|
|
212
219
|
}
|
|
213
220
|
}
|
|
214
|
-
dispatch(msg) {
|
|
221
|
+
dispatch(msg, chars = JSON.stringify(msg).length) {
|
|
215
222
|
if (this.resolveRead) {
|
|
216
223
|
const resolve3 = this.resolveRead;
|
|
217
224
|
this.resolveRead = null;
|
|
218
225
|
resolve3(msg);
|
|
219
226
|
} else if (this.handlers.size === 0) {
|
|
220
|
-
if (this.messageQueue.length >= this.maxQueuedMessages) {
|
|
221
|
-
writeErr(
|
|
222
|
-
`
|
|
227
|
+
if (this.messageQueue.length >= this.maxQueuedMessages || this.queuedChars + chars > this.maxQueuedChars) {
|
|
228
|
+
writeErr(
|
|
229
|
+
`[acp-child message queue exceeds ${this.maxQueuedMessages} entries or ${this.maxQueuedChars} characters]
|
|
230
|
+
`
|
|
231
|
+
);
|
|
223
232
|
this.stop();
|
|
224
233
|
return;
|
|
225
234
|
}
|
|
226
|
-
this.messageQueue.push(msg);
|
|
235
|
+
this.messageQueue.push({ message: msg, chars });
|
|
236
|
+
this.queuedChars += chars;
|
|
227
237
|
}
|
|
228
238
|
for (const handler of this.handlers) {
|
|
229
239
|
try {
|
|
@@ -508,6 +518,7 @@ import { spawn } from "node:child_process";
|
|
|
508
518
|
import { realpathSync as realpathSync2 } from "node:fs";
|
|
509
519
|
import * as path2 from "node:path";
|
|
510
520
|
import { buildChildEnv } from "@wrongstack/core/utils";
|
|
521
|
+
import { treeKill as treeKill2 } from "@wrongstack/core/utils/tree-kill";
|
|
511
522
|
var EMPTY_BUFFER = Buffer.alloc(0);
|
|
512
523
|
var TerminalServer = class {
|
|
513
524
|
terminals = /* @__PURE__ */ new Map();
|
|
@@ -627,13 +638,11 @@ var TerminalServer = class {
|
|
|
627
638
|
state.outputHead = 0;
|
|
628
639
|
}
|
|
629
640
|
};
|
|
641
|
+
state.onData = onData;
|
|
630
642
|
proc.stdout?.on("data", onData);
|
|
631
643
|
proc.stderr?.on("data", onData);
|
|
632
644
|
state.timeoutHandle = setTimeout(() => {
|
|
633
|
-
|
|
634
|
-
proc.kill("SIGTERM");
|
|
635
|
-
} catch {
|
|
636
|
-
}
|
|
645
|
+
treeKill2(proc);
|
|
637
646
|
}, this.commandTimeoutMs);
|
|
638
647
|
this.terminals.set(id, state);
|
|
639
648
|
return { terminalId: id };
|
|
@@ -657,14 +666,15 @@ var TerminalServer = class {
|
|
|
657
666
|
if (!state) throw new Error(`unknown terminal: ${terminalId}`);
|
|
658
667
|
return state.exitPromise;
|
|
659
668
|
}
|
|
660
|
-
/**
|
|
669
|
+
/**
|
|
670
|
+
* Kill the process but keep the terminal record (agent can still read output).
|
|
671
|
+
* On POSIX this signals only the direct child; descendants may survive because
|
|
672
|
+
* terminal processes are not spawned as process-group leaders.
|
|
673
|
+
*/
|
|
661
674
|
kill(terminalId) {
|
|
662
675
|
const state = this.terminals.get(terminalId);
|
|
663
676
|
if (!state) throw new Error(`unknown terminal: ${terminalId}`);
|
|
664
|
-
|
|
665
|
-
state.proc.kill("SIGTERM");
|
|
666
|
-
} catch {
|
|
667
|
-
}
|
|
677
|
+
treeKill2(state.proc);
|
|
668
678
|
}
|
|
669
679
|
/** Kill the process if alive and remove the record. */
|
|
670
680
|
release(terminalId) {
|
|
@@ -674,10 +684,17 @@ var TerminalServer = class {
|
|
|
674
684
|
clearTimeout(state.timeoutHandle);
|
|
675
685
|
state.timeoutHandle = null;
|
|
676
686
|
}
|
|
677
|
-
|
|
678
|
-
state.proc.
|
|
679
|
-
|
|
680
|
-
|
|
687
|
+
if (state.onData) {
|
|
688
|
+
state.proc.stdout?.off("data", state.onData);
|
|
689
|
+
state.proc.stderr?.off("data", state.onData);
|
|
690
|
+
state.onData = void 0;
|
|
691
|
+
}
|
|
692
|
+
state.proc.stdout?.destroy?.();
|
|
693
|
+
state.proc.stderr?.destroy?.();
|
|
694
|
+
state.outputChunks.length = 0;
|
|
695
|
+
state.outputHead = 0;
|
|
696
|
+
state.retainedBytes = 0;
|
|
697
|
+
treeKill2(state.proc, { force: true });
|
|
681
698
|
this.terminals.delete(terminalId);
|
|
682
699
|
}
|
|
683
700
|
/** Kill all active terminals. Used on session close. */
|
|
@@ -862,6 +879,9 @@ var WebSocketClientTransport = class {
|
|
|
862
879
|
const pending = this.pendingStart;
|
|
863
880
|
if (pending === null) return;
|
|
864
881
|
this.pendingStart = null;
|
|
882
|
+
this.closed = true;
|
|
883
|
+
if (this.ws === ws) this.ws = null;
|
|
884
|
+
this.handlers.clear();
|
|
865
885
|
try {
|
|
866
886
|
ws.close();
|
|
867
887
|
} catch {
|
|
@@ -879,16 +899,21 @@ var WebSocketClientTransport = class {
|
|
|
879
899
|
ws.addEventListener("error", (ev) => {
|
|
880
900
|
const pending = this.pendingStart;
|
|
881
901
|
if (pending === null) {
|
|
882
|
-
this.
|
|
902
|
+
this.stop();
|
|
883
903
|
return;
|
|
884
904
|
}
|
|
885
905
|
this.pendingStart = null;
|
|
906
|
+
this.closed = true;
|
|
907
|
+
if (this.ws === ws) this.ws = null;
|
|
908
|
+
this.handlers.clear();
|
|
886
909
|
clearTimeout(pending.timer);
|
|
887
910
|
const message = ev && typeof ev === "object" && "message" in ev ? String(ev.message) : "WebSocket error";
|
|
888
911
|
pending.reject(new Error(message));
|
|
889
912
|
});
|
|
890
913
|
ws.addEventListener("close", () => {
|
|
891
914
|
this.closed = true;
|
|
915
|
+
if (this.ws === ws) this.ws = null;
|
|
916
|
+
this.handlers.clear();
|
|
892
917
|
const pending = this.pendingStart;
|
|
893
918
|
if (pending !== null) {
|
|
894
919
|
this.pendingStart = null;
|
|
@@ -924,6 +949,7 @@ var WebSocketClientTransport = class {
|
|
|
924
949
|
}
|
|
925
950
|
stop() {
|
|
926
951
|
this.closed = true;
|
|
952
|
+
this.handlers.clear();
|
|
927
953
|
if (this.pendingStart !== null) {
|
|
928
954
|
const pending = this.pendingStart;
|
|
929
955
|
this.pendingStart = null;
|