@ganglion/xacpx 0.22.0 → 0.23.0-beta.0

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.
Files changed (30) hide show
  1. package/dist/adapters/acp-output-guard-main.js +563 -0
  2. package/dist/adapters/acp-output-guard.d.ts +43 -0
  3. package/dist/bridge/bridge-main.js +733 -101
  4. package/dist/channels/types.d.ts +22 -0
  5. package/dist/cli.js +3152 -385
  6. package/dist/commands/router-types.d.ts +4 -4
  7. package/dist/config/local-agent-bin.d.ts +7 -0
  8. package/dist/config/resolve-agent-command.d.ts +2 -0
  9. package/dist/control/control-event-bus.d.ts +6 -0
  10. package/dist/control/control-service.d.ts +147 -3
  11. package/dist/control/session-turn-runner.d.ts +25 -1
  12. package/dist/control/turn-queue.d.ts +22 -6
  13. package/dist/control/turn-support.d.ts +14 -1
  14. package/dist/orchestration/agent-messaging-error.d.ts +15 -0
  15. package/dist/orchestration/agent-messaging-types.d.ts +112 -0
  16. package/dist/orchestration/orchestration-service.d.ts +4 -0
  17. package/dist/orchestration/orchestration-types.d.ts +6 -0
  18. package/dist/orchestration/service/coordinator-registry-service.d.ts +1 -1
  19. package/dist/orchestration/service/human-delegation-service.d.ts +1 -1
  20. package/dist/orchestration/service/rpc-delegation-service.d.ts +1 -1
  21. package/dist/orchestration/service/task-approval-service.d.ts +1 -1
  22. package/dist/orchestration/service/worker-session-manager.d.ts +1 -1
  23. package/dist/orchestration/worker-launch.d.ts +18 -0
  24. package/dist/sessions/session-service.d.ts +5 -1
  25. package/dist/state/state-store.d.ts +3 -3
  26. package/dist/transport/command-timeouts.d.ts +1 -1
  27. package/dist/transport/message-injection.d.ts +18 -0
  28. package/dist/transport/types.d.ts +5 -0
  29. package/dist/weixin/agent/interface.d.ts +5 -0
  30. package/package.json +2 -2
@@ -0,0 +1,563 @@
1
+ import { createRequire } from "node:module";
2
+ var __create = Object.create;
3
+ var __getProtoOf = Object.getPrototypeOf;
4
+ var __defProp = Object.defineProperty;
5
+ var __getOwnPropNames = Object.getOwnPropertyNames;
6
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
7
+ function __accessProp(key) {
8
+ return this[key];
9
+ }
10
+ var __toESMCache_node;
11
+ var __toESMCache_esm;
12
+ var __toESM = (mod, isNodeMode, target) => {
13
+ var canCache = mod != null && typeof mod === "object";
14
+ if (canCache) {
15
+ var cache = isNodeMode ? __toESMCache_node ??= new WeakMap : __toESMCache_esm ??= new WeakMap;
16
+ var cached = cache.get(mod);
17
+ if (cached)
18
+ return cached;
19
+ }
20
+ target = mod != null ? __create(__getProtoOf(mod)) : {};
21
+ const to = isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target;
22
+ for (let key of __getOwnPropNames(mod))
23
+ if (!__hasOwnProp.call(to, key))
24
+ __defProp(to, key, {
25
+ get: __accessProp.bind(mod, key),
26
+ enumerable: true
27
+ });
28
+ if (canCache)
29
+ cache.set(mod, to);
30
+ return to;
31
+ };
32
+ var __commonJS = (cb, mod) => () => (mod || cb((mod = { exports: {} }).exports, mod), mod.exports);
33
+ var __returnValue = (v) => v;
34
+ function __exportSetter(name, newValue) {
35
+ this[name] = __returnValue.bind(null, newValue);
36
+ }
37
+ var __export = (target, all) => {
38
+ for (var name in all)
39
+ __defProp(target, name, {
40
+ get: all[name],
41
+ enumerable: true,
42
+ configurable: true,
43
+ set: __exportSetter.bind(all, name)
44
+ });
45
+ };
46
+ var __esm = (fn, res) => () => (fn && (res = fn(fn = 0)), res);
47
+ var __require = /* @__PURE__ */ createRequire(import.meta.url);
48
+
49
+ // src/config/local-agent-bin.ts
50
+ import { statSync } from "node:fs";
51
+ import { delimiter, join, win32 as win32Path } from "node:path";
52
+ function executableExtensions(platform, env) {
53
+ return platform === "win32" ? (env.PATHEXT ?? ".EXE;.CMD;.BAT;.COM").split(";").filter((e) => e.length > 0) : [""];
54
+ }
55
+ function defaultIsExecutableFile(p, platform = process.platform) {
56
+ try {
57
+ const st = statSync(p);
58
+ if (!st.isFile())
59
+ return false;
60
+ return platform === "win32" || (st.mode & 73) !== 0;
61
+ } catch {
62
+ return false;
63
+ }
64
+ }
65
+ function resolveExecutableOnPath(name, platform = process.platform, env = process.env, isExecutableFile) {
66
+ const pathValue = env.PATH ?? env.Path ?? "";
67
+ if (!pathValue)
68
+ return;
69
+ const pathJoin = platform === "win32" ? win32Path.join : join;
70
+ const pathEntries = pathValue.split(platform === "win32" ? ";" : delimiter).filter(Boolean);
71
+ const extensions = executableExtensions(platform, env);
72
+ const basename = name.slice(Math.max(name.lastIndexOf("/"), name.lastIndexOf("\\")) + 1);
73
+ const hasExtension = platform === "win32" && /\.[^\\/]+$/u.test(basename);
74
+ const hasPath = /[\\/]/u.test(name) || platform === "win32" && /^[A-Za-z]:/u.test(name);
75
+ const fileCheck = isExecutableFile ?? ((path) => defaultIsExecutableFile(path, platform));
76
+ for (const directory of hasPath ? [""] : pathEntries) {
77
+ const base = hasPath ? name : pathJoin(directory, name);
78
+ const candidates = hasExtension ? [base] : extensions.map((extension) => `${base}${extension}`);
79
+ for (const candidate of candidates) {
80
+ if (fileCheck(candidate))
81
+ return candidate;
82
+ }
83
+ }
84
+ return;
85
+ }
86
+ function isExecutableOnPath(name, env = process.env, isExecutableFile = defaultIsExecutableFile) {
87
+ return resolveExecutableOnPath(name, process.platform, env, isExecutableFile) !== undefined;
88
+ }
89
+ function resolveLocalAgentArgv(driver, onPath = (name) => isExecutableOnPath(name)) {
90
+ const spec = LOCAL_AGENT_BINS[driver];
91
+ if (!spec)
92
+ return;
93
+ if (!onPath(spec.bin))
94
+ return;
95
+ return [spec.bin, ...spec.args];
96
+ }
97
+ var LOCAL_AGENT_BINS;
98
+ var init_local_agent_bin = __esm(() => {
99
+ LOCAL_AGENT_BINS = {
100
+ opencode: { bin: "opencode", args: ["acp"] },
101
+ kilocode: { bin: "kilocode", args: ["acp"] },
102
+ reasonix: { bin: "reasonix", args: ["acp"] },
103
+ omp: { bin: "omp", args: ["acp"] }
104
+ };
105
+ });
106
+
107
+ // src/adapters/acp-output-guard.ts
108
+ import { fileURLToPath } from "node:url";
109
+ async function pumpAcpStdout(source, onLine, maxRawLineBytes = MAX_RAW_ACP_LINE_BYTES) {
110
+ let pendingParts = [];
111
+ let pendingBytes = 0;
112
+ const append = (part) => {
113
+ if (part.length === 0)
114
+ return;
115
+ pendingParts.push(part);
116
+ pendingBytes += part.length;
117
+ if (pendingBytes > maxRawLineBytes) {
118
+ throw new AcpOutputGuardError(`raw ACP stdout line exceeded ${maxRawLineBytes} bytes`);
119
+ }
120
+ };
121
+ const reset = () => {
122
+ pendingParts = [];
123
+ pendingBytes = 0;
124
+ };
125
+ for await (const chunk of source) {
126
+ const buffer = Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk);
127
+ let offset = 0;
128
+ while (offset <= buffer.length) {
129
+ const newline = buffer.indexOf(10, offset);
130
+ const end = newline === -1 ? buffer.length : newline;
131
+ append(buffer.subarray(offset, end));
132
+ if (newline === -1)
133
+ break;
134
+ await onLine(Buffer.concat(pendingParts, pendingBytes));
135
+ reset();
136
+ offset = newline + 1;
137
+ }
138
+ }
139
+ if (pendingBytes > 0) {
140
+ await onLine(Buffer.concat(pendingParts, pendingBytes));
141
+ }
142
+ }
143
+ function buildAcpAgentSpawnSpec(commandArgv, platform = process.platform, comspec = process.env.ComSpec ?? process.env.COMSPEC ?? "cmd.exe", env = process.env, isExecutableFile) {
144
+ const command = commandArgv[0];
145
+ if (!command) {
146
+ throw new AcpOutputGuardError("missing agent command after --");
147
+ }
148
+ const args = [...commandArgv.slice(1)];
149
+ const resolvedCommand = platform === "win32" ? resolveExecutableOnPath(command, platform, env, isExecutableFile) : undefined;
150
+ const launcherCommand = resolvedCommand && isWindowsScriptLauncher(resolvedCommand) ? resolvedCommand : command;
151
+ if (platform !== "win32" || !isWindowsScriptLauncher(launcherCommand)) {
152
+ return { command, args, shell: false };
153
+ }
154
+ const commandLine = [
155
+ escapeWindowsCmdCommand(launcherCommand),
156
+ ...args.map(quoteWindowsCmdArg)
157
+ ].join(" ");
158
+ return {
159
+ command: comspec,
160
+ args: ["/d", "/v:off", "/s", "/c", `"${commandLine}"`],
161
+ shell: false,
162
+ windowsVerbatimArguments: true
163
+ };
164
+ }
165
+ function isWindowsScriptLauncher(command) {
166
+ const basename = command.slice(Math.max(command.lastIndexOf("/"), command.lastIndexOf("\\")) + 1).toLowerCase();
167
+ return basename.endsWith(".cmd") || basename.endsWith(".bat");
168
+ }
169
+ function escapeWindowsCmdCommand(value) {
170
+ return value.replace(WINDOWS_CMD_META_CHARACTERS, "^$1");
171
+ }
172
+ function quoteWindowsCmdArg(value) {
173
+ let escaped = value.replace(/(?=(\\+?)?)\1"/gu, "$1$1\\\"").replace(/(?=(\\+?)?)\1$/gu, "$1$1");
174
+ escaped = `"${escaped}"`;
175
+ return escaped.replace(WINDOWS_CMD_META_CHARACTERS, "^$1");
176
+ }
177
+ function guardAcpStdoutLine(line, options = {}) {
178
+ const safeLimit = positiveOption(options.safeSerializedLimit, SAFE_ACP_LINE_CHARS);
179
+ if (line.length <= safeLimit) {
180
+ return [line];
181
+ }
182
+ let message;
183
+ try {
184
+ message = JSON.parse(line);
185
+ } catch {
186
+ throw new AcpOutputGuardError(`non-JSON ACP stdout line exceeded ${safeLimit} characters`);
187
+ }
188
+ if (!isRecord(message)) {
189
+ throw new AcpOutputGuardError(`oversized ACP stdout payload is not a JSON-RPC object (${line.length} characters)`);
190
+ }
191
+ const textUpdate = textUpdateParts(message);
192
+ if (textUpdate) {
193
+ const split = splitTextUpdate(message, textUpdate, options, safeLimit);
194
+ if (split) {
195
+ return split;
196
+ }
197
+ }
198
+ const bounded = markQueueTruncation(boundJsonValue(message, createBoundContext(options, safeLimit), 0, []), message, line.length);
199
+ const boundedLine = safeJsonStringify(bounded);
200
+ if (boundedLine !== undefined && boundedLine.length <= safeLimit) {
201
+ return [boundedLine];
202
+ }
203
+ const minimal = minimalAcpMessage(message, line.length);
204
+ const minimalLine = safeJsonStringify(minimal);
205
+ if (minimalLine !== undefined && minimalLine.length <= safeLimit) {
206
+ return [minimalLine];
207
+ }
208
+ throw new AcpOutputGuardError(`unable to bound ACP stdout payload below ${safeLimit} characters (${line.length} characters)`);
209
+ }
210
+ function resolveAcpOutputGuardEntry(moduleUrl = import.meta.url) {
211
+ if (moduleUrl.endsWith(".ts")) {
212
+ return fileURLToPath(new URL("./acp-output-guard-main.ts", moduleUrl));
213
+ }
214
+ const index = moduleUrl.lastIndexOf(DIST_MARKER);
215
+ if (index !== -1) {
216
+ return fileURLToPath(new URL(`${moduleUrl.slice(0, index + DIST_MARKER.length)}adapters/acp-output-guard-main.js`, moduleUrl));
217
+ }
218
+ return fileURLToPath(new URL("./acp-output-guard-main.js", moduleUrl));
219
+ }
220
+ function wrapAcpOutputGuardArgv(agentArgv, execPath = process.execPath, guardEntry = resolveAcpOutputGuardEntry()) {
221
+ if (isAcpOutputGuardArgv(agentArgv)) {
222
+ return [...agentArgv];
223
+ }
224
+ return [execPath, guardEntry, "--", ...agentArgv];
225
+ }
226
+ function isAcpOutputGuardArgv(argv) {
227
+ return argv.length >= 3 && argv[2] === "--" && (argv[1]?.includes("/acp-output-guard-main.") === true || argv[1]?.includes("\\acp-output-guard-main.") === true);
228
+ }
229
+ function textUpdateParts(message) {
230
+ if (message.method !== "session/update" || !isRecord(message.params))
231
+ return;
232
+ const update = message.params.update;
233
+ if (!isRecord(update))
234
+ return;
235
+ if (update.sessionUpdate !== "agent_message_chunk" && update.sessionUpdate !== "agent_thought_chunk") {
236
+ return;
237
+ }
238
+ if (!isRecord(update.content) || update.content.type !== "text" || typeof update.content.text !== "string") {
239
+ return;
240
+ }
241
+ return { params: message.params, update, content: update.content, text: update.content.text };
242
+ }
243
+ function splitTextUpdate(message, parts, options, safeLimit) {
244
+ const skeleton = boundJsonValue({
245
+ ...message,
246
+ params: {
247
+ ...parts.params,
248
+ update: {
249
+ ...parts.update,
250
+ content: { ...parts.content, text: "" }
251
+ }
252
+ }
253
+ }, createBoundContext(options, safeLimit), 0, []);
254
+ if (!isRecord(skeleton) || !isRecord(skeleton.params) || !isRecord(skeleton.params.update)) {
255
+ return;
256
+ }
257
+ const skeletonUpdate = skeleton.params.update;
258
+ const skeletonContent = isRecord(skeletonUpdate.content) ? skeletonUpdate.content : { type: "text" };
259
+ const textChunkChars = positiveOption(options.textChunkChars, TEXT_CHUNK_CHARS);
260
+ const output = [];
261
+ let offset = 0;
262
+ while (offset < parts.text.length) {
263
+ let end = safeTextBoundary(parts.text, Math.min(parts.text.length, offset + textChunkChars));
264
+ let accepted;
265
+ while (end > offset) {
266
+ const piece = parts.text.slice(offset, end);
267
+ const candidate = {
268
+ ...skeleton,
269
+ params: {
270
+ ...skeleton.params,
271
+ update: {
272
+ ...skeletonUpdate,
273
+ content: { ...skeletonContent, text: piece }
274
+ }
275
+ }
276
+ };
277
+ const encoded = safeJsonStringify(candidate);
278
+ if (encoded !== undefined && encoded.length <= safeLimit) {
279
+ accepted = encoded;
280
+ break;
281
+ }
282
+ end = safeTextBoundary(parts.text, offset + Math.floor((end - offset) / 2));
283
+ }
284
+ if (accepted === undefined) {
285
+ return;
286
+ }
287
+ output.push(accepted);
288
+ offset = end;
289
+ }
290
+ if (output.length === 0)
291
+ return;
292
+ return output;
293
+ }
294
+ function safeTextBoundary(text, proposed) {
295
+ if (proposed <= 0 || proposed >= text.length)
296
+ return Math.min(text.length, Math.max(0, proposed));
297
+ const code = text.charCodeAt(proposed - 1);
298
+ return code >= 55296 && code <= 56319 ? proposed - 1 : proposed;
299
+ }
300
+ function createBoundContext(options, safeLimit) {
301
+ return {
302
+ remaining: Math.min(512 * 1024, Math.max(1024, safeLimit - 1024)),
303
+ nodes: 0,
304
+ options: {
305
+ maxDepth: positiveOption(options.maxDepth, DEFAULT_MAX_DEPTH),
306
+ maxObjectKeys: positiveOption(options.maxObjectKeys, DEFAULT_MAX_OBJECT_KEYS),
307
+ maxArrayItems: positiveOption(options.maxArrayItems, DEFAULT_MAX_ARRAY_ITEMS),
308
+ maxStringChars: positiveOption(options.maxStringChars, DEFAULT_MAX_STRING_CHARS)
309
+ }
310
+ };
311
+ }
312
+ function boundJsonValue(value, context, depth, path) {
313
+ context.nodes += 1;
314
+ if (context.nodes > MAX_BOUND_NODES || context.remaining <= 0) {
315
+ return ACP_OUTPUT_GUARD_TRUNCATION_MARKER;
316
+ }
317
+ if (value === null || typeof value === "number" || typeof value === "boolean") {
318
+ context.remaining -= 16;
319
+ return value;
320
+ }
321
+ if (typeof value === "string") {
322
+ return boundString(value, context, path.at(-1));
323
+ }
324
+ if (depth >= context.options.maxDepth) {
325
+ context.remaining -= ACP_OUTPUT_GUARD_TRUNCATION_MARKER.length;
326
+ return ACP_OUTPUT_GUARD_TRUNCATION_MARKER;
327
+ }
328
+ if (Array.isArray(value)) {
329
+ const result2 = [];
330
+ for (const item of value.slice(0, context.options.maxArrayItems)) {
331
+ result2.push(boundJsonValue(item, context, depth + 1, [...path, "[]"]));
332
+ if (context.remaining <= 0)
333
+ break;
334
+ }
335
+ if (value.length > result2.length && context.remaining > 0) {
336
+ result2.push(ACP_OUTPUT_GUARD_TRUNCATION_MARKER);
337
+ }
338
+ context.remaining -= 2;
339
+ return result2;
340
+ }
341
+ if (!isRecord(value)) {
342
+ return ACP_OUTPUT_GUARD_TRUNCATION_MARKER;
343
+ }
344
+ const result = {};
345
+ const keys = prioritizedKeys(Object.keys(value), path.at(-1));
346
+ for (const key of keys.slice(0, context.options.maxObjectKeys)) {
347
+ if (context.remaining <= 0)
348
+ break;
349
+ result[key] = boundJsonValue(value[key], context, depth + 1, [...path, key]);
350
+ }
351
+ if (keys.length > Object.keys(result).length && context.remaining > 0) {
352
+ result.__xacpxTruncated = true;
353
+ }
354
+ context.remaining -= 2;
355
+ return result;
356
+ }
357
+ function boundString(value, context, key) {
358
+ if (value.length <= context.options.maxStringChars) {
359
+ context.remaining -= value.length;
360
+ return value;
361
+ }
362
+ if (key?.toLowerCase() === "data" && looksBinaryLike(value)) {
363
+ context.remaining -= ACP_OUTPUT_GUARD_BINARY_MARKER.length;
364
+ return ACP_OUTPUT_GUARD_BINARY_MARKER;
365
+ }
366
+ const cap = Math.max(32, Math.min(context.options.maxStringChars, Math.max(32, context.remaining - 64)));
367
+ const marker = ACP_OUTPUT_GUARD_TRUNCATION_MARKER;
368
+ const lowerKey = key?.toLowerCase();
369
+ let result;
370
+ if (lowerKey === "stdout" || lowerKey === "stderr" || lowerKey?.includes("diff")) {
371
+ const available = Math.max(0, cap - marker.length);
372
+ const head = Math.min(16 * 1024, Math.floor(available / 2));
373
+ result = `${value.slice(0, head)}${marker}${value.slice(-Math.max(0, available - head))}`;
374
+ } else {
375
+ result = `${value.slice(0, Math.max(0, cap - marker.length))}${marker}`;
376
+ }
377
+ context.remaining -= result.length;
378
+ return result;
379
+ }
380
+ function prioritizedKeys(keys, parentKey) {
381
+ if (parentKey !== "_meta")
382
+ return keys;
383
+ const preferred = ["qoder", "codex", "claudeCode", "routing", "toolName", "parentToolUseId"];
384
+ return [...keys].sort((left, right) => {
385
+ const leftIndex = preferred.indexOf(left);
386
+ const rightIndex = preferred.indexOf(right);
387
+ return (leftIndex === -1 ? preferred.length : leftIndex) - (rightIndex === -1 ? preferred.length : rightIndex);
388
+ });
389
+ }
390
+ function markQueueTruncation(bounded, original, originalSerializedLength) {
391
+ if (!isRecord(bounded) || original.method !== "session/update" || !isRecord(bounded.params)) {
392
+ return bounded;
393
+ }
394
+ const boundedUpdate = isRecord(bounded.params.update) ? bounded.params.update : {};
395
+ const originalUpdate = isRecord(original.params) && isRecord(original.params.update) ? original.params.update : {};
396
+ return {
397
+ ...bounded,
398
+ params: {
399
+ ...bounded.params,
400
+ update: {
401
+ ...boundedUpdate,
402
+ _meta: {
403
+ ...isRecord(boundedUpdate._meta) ? boundedUpdate._meta : {},
404
+ acpx: {
405
+ ...isRecord(boundedUpdate._meta) && isRecord(boundedUpdate._meta.acpx) ? boundedUpdate._meta.acpx : {},
406
+ queueTruncated: true,
407
+ originalSerializedLength
408
+ }
409
+ },
410
+ ...originalUpdate.sessionUpdate ? { sessionUpdate: boundedUpdate.sessionUpdate ?? originalUpdate.sessionUpdate } : {}
411
+ }
412
+ }
413
+ };
414
+ }
415
+ function minimalAcpMessage(message, originalSerializedLength) {
416
+ const minimal = {};
417
+ for (const key of ["jsonrpc", "id", "method"]) {
418
+ if (key in message)
419
+ minimal[key] = message[key];
420
+ }
421
+ if (message.method === "session/update" && isRecord(message.params)) {
422
+ const update = isRecord(message.params.update) ? message.params.update : {};
423
+ const minimalUpdate = {
424
+ sessionUpdate: typeof update.sessionUpdate === "string" ? update.sessionUpdate : "unknown"
425
+ };
426
+ for (const key of ["toolCallId", "parentToolCallId", "status", "kind", "title", "locations", "messageId"]) {
427
+ if (key in update)
428
+ minimalUpdate[key] = key === "locations" ? boundJsonValue(update[key], createBoundContext({}, SAFE_ACP_LINE_CHARS), 0, [key]) : update[key];
429
+ }
430
+ minimalUpdate.content = { type: "text", text: ACP_OUTPUT_GUARD_TRUNCATION_MARKER };
431
+ minimalUpdate._meta = { acpx: { queueTruncated: true, originalSerializedLength } };
432
+ minimal.params = {
433
+ ...typeof message.params.sessionId === "string" ? { sessionId: message.params.sessionId } : {},
434
+ update: minimalUpdate
435
+ };
436
+ } else if ("id" in message) {
437
+ minimal.result = {};
438
+ } else if ("params" in message) {
439
+ minimal.params = { _meta: { acpx: { queueTruncated: true, originalSerializedLength } } };
440
+ }
441
+ return minimal;
442
+ }
443
+ function safeJsonStringify(value) {
444
+ try {
445
+ return JSON.stringify(value);
446
+ } catch {
447
+ return;
448
+ }
449
+ }
450
+ function looksBinaryLike(value) {
451
+ return value.length > 4096 && /^[A-Za-z0-9+/=\s]+$/u.test(value);
452
+ }
453
+ function isRecord(value) {
454
+ return typeof value === "object" && value !== null && !Array.isArray(value);
455
+ }
456
+ function positiveOption(value, fallback) {
457
+ return typeof value === "number" && Number.isFinite(value) && value > 0 ? Math.floor(value) : fallback;
458
+ }
459
+ var SAFE_ACP_LINE_CHARS, TEXT_CHUNK_CHARS, MAX_RAW_ACP_LINE_BYTES, ACP_OUTPUT_GUARD_TRUNCATION_MARKER = "…(truncated by xacpx ACP output guard)", ACP_OUTPUT_GUARD_BINARY_MARKER = "[large binary payload omitted by xacpx ACP output guard]", ACP_OUTPUT_GUARD_ERROR_CODE = "ACPX_OUTPUT_GUARD_FRAME_TOO_LARGE", DEFAULT_MAX_DEPTH = 12, DEFAULT_MAX_OBJECT_KEYS = 128, DEFAULT_MAX_ARRAY_ITEMS = 64, DEFAULT_MAX_STRING_CHARS, MAX_BOUND_NODES = 1e5, DIST_MARKER = "/dist/", WINDOWS_CMD_META_CHARACTERS, AcpOutputGuardError;
460
+ var init_acp_output_guard = __esm(() => {
461
+ init_local_agent_bin();
462
+ SAFE_ACP_LINE_CHARS = 2 * 1024 * 1024;
463
+ TEXT_CHUNK_CHARS = 256 * 1024;
464
+ MAX_RAW_ACP_LINE_BYTES = 64 * 1024 * 1024;
465
+ DEFAULT_MAX_STRING_CHARS = 64 * 1024;
466
+ WINDOWS_CMD_META_CHARACTERS = /([()\][%!^"`<>&|;, *?])/gu;
467
+ AcpOutputGuardError = class AcpOutputGuardError extends Error {
468
+ code = ACP_OUTPUT_GUARD_ERROR_CODE;
469
+ constructor(message) {
470
+ super(message);
471
+ this.name = "AcpOutputGuardError";
472
+ }
473
+ };
474
+ });
475
+
476
+ // src/adapters/acp-output-guard-main.ts
477
+ init_acp_output_guard();
478
+ import { spawn } from "node:child_process";
479
+ import { constants as osConstants } from "node:os";
480
+ var separator = process.argv.indexOf("--");
481
+ var commandArgv = separator === -1 ? process.argv.slice(2) : process.argv.slice(separator + 1);
482
+ if (commandArgv.length === 0) {
483
+ process.stderr.write(`[xacpx-acp-output-guard] missing agent command after --
484
+ `);
485
+ process.exit(1);
486
+ }
487
+ var spawnSpec = buildAcpAgentSpawnSpec(commandArgv);
488
+ var child = spawn(spawnSpec.command, spawnSpec.args, {
489
+ stdio: ["pipe", "pipe", "pipe"],
490
+ shell: spawnSpec.shell,
491
+ windowsVerbatimArguments: spawnSpec.windowsVerbatimArguments
492
+ });
493
+ var stopping = false;
494
+ var finalized = false;
495
+ function writeStderr(chunk) {
496
+ process.stderr.write(chunk);
497
+ }
498
+ function failClosed(error) {
499
+ if (stopping)
500
+ return;
501
+ stopping = true;
502
+ const message = error instanceof AcpOutputGuardError ? `${error.code}: ${error.message}` : error instanceof Error ? error.message : String(error);
503
+ writeStderr(`[xacpx-acp-output-guard] ${message}
504
+ `);
505
+ try {
506
+ child.stdin?.destroy();
507
+ child.kill("SIGTERM");
508
+ } catch {}
509
+ const forceKill = setTimeout(() => {
510
+ try {
511
+ child.kill("SIGKILL");
512
+ } catch {}
513
+ }, 5000);
514
+ forceKill.unref?.();
515
+ }
516
+ async function writeLine(line) {
517
+ if (process.stdout.write(`${line}
518
+ `))
519
+ return;
520
+ await new Promise((resolve) => process.stdout.once("drain", resolve));
521
+ }
522
+ async function forwardLine(line) {
523
+ if (stopping)
524
+ return;
525
+ const safeLines = guardAcpStdoutLine(line.toString("utf8"));
526
+ for (const safeLine of safeLines) {
527
+ await writeLine(safeLine);
528
+ }
529
+ }
530
+ var stdoutPump = pumpAcpStdout(child.stdout, forwardLine, MAX_RAW_ACP_LINE_BYTES).catch((error) => {
531
+ failClosed(error);
532
+ });
533
+ async function finalize(code, signal) {
534
+ if (finalized)
535
+ return;
536
+ finalized = true;
537
+ try {
538
+ await stdoutPump;
539
+ } catch (error) {
540
+ failClosed(error);
541
+ }
542
+ if (stopping) {
543
+ process.exitCode = 1;
544
+ } else {
545
+ process.exitCode = signal ? 128 + (osConstants.signals[signal] ?? 0) : code ?? 0;
546
+ }
547
+ }
548
+ child.on("error", (error) => {
549
+ failClosed(new Error(`failed to spawn agent: ${error.message}`));
550
+ });
551
+ child.on("close", (code, signal) => {
552
+ finalize(code, signal);
553
+ });
554
+ for (const signal of ["SIGINT", "SIGTERM", "SIGHUP"]) {
555
+ process.on(signal, () => {
556
+ try {
557
+ child.kill(signal);
558
+ } catch {}
559
+ });
560
+ }
561
+ child.stdin?.on("error", () => {});
562
+ process.stdin.pipe(child.stdin);
563
+ child.stderr?.pipe(process.stderr);
@@ -0,0 +1,43 @@
1
+ export declare const SAFE_ACP_LINE_CHARS: number;
2
+ export declare const TEXT_CHUNK_CHARS: number;
3
+ export declare const MAX_RAW_ACP_LINE_BYTES: number;
4
+ export declare const ACP_OUTPUT_GUARD_TRUNCATION_MARKER = "\u2026(truncated by xacpx ACP output guard)";
5
+ export declare const ACP_OUTPUT_GUARD_BINARY_MARKER = "[large binary payload omitted by xacpx ACP output guard]";
6
+ export declare const ACP_OUTPUT_GUARD_ERROR_CODE: "ACPX_OUTPUT_GUARD_FRAME_TOO_LARGE";
7
+ export interface GuardOptions {
8
+ safeSerializedLimit?: number;
9
+ textChunkChars?: number;
10
+ maxDepth?: number;
11
+ maxObjectKeys?: number;
12
+ maxArrayItems?: number;
13
+ maxStringChars?: number;
14
+ }
15
+ /**
16
+ * Read ACP stdout as a backpressure-aware async stream. The caller's line
17
+ * handler is awaited before the iterator is advanced, so a slow acpx stdout
18
+ * consumer pauses the child stdout source instead of accumulating a Promise
19
+ * chain in this process.
20
+ */
21
+ export declare function pumpAcpStdout(source: AsyncIterable<Buffer | string>, onLine: (line: Buffer) => Promise<void>, maxRawLineBytes?: number): Promise<void>;
22
+ export interface AcpAgentSpawnSpec {
23
+ command: string;
24
+ args: string[];
25
+ shell: false;
26
+ windowsVerbatimArguments?: boolean;
27
+ }
28
+ /** Keep real Windows executables on the exact argv path. cmd/bat launchers are
29
+ * the one case that needs an explicit cmd.exe boundary. */
30
+ export declare function buildAcpAgentSpawnSpec(commandArgv: readonly string[], platform?: NodeJS.Platform, comspec?: string, env?: NodeJS.ProcessEnv, isExecutableFile?: (path: string) => boolean): AcpAgentSpawnSpec;
31
+ export declare class AcpOutputGuardError extends Error {
32
+ readonly code: "ACPX_OUTPUT_GUARD_FRAME_TOO_LARGE";
33
+ constructor(message: string);
34
+ }
35
+ /**
36
+ * Bounds one complete ACP stdout line before it reaches acpx. Small lines take
37
+ * the exact passthrough path; only oversized lines are parsed and rewritten.
38
+ */
39
+ export declare function guardAcpStdoutLine(line: string, options?: GuardOptions): string[];
40
+ /** Resolve the built guard entry from either a source module or a dist bundle. */
41
+ export declare function resolveAcpOutputGuardEntry(moduleUrl?: string): string;
42
+ export declare function wrapAcpOutputGuardArgv(agentArgv: readonly string[], execPath?: string, guardEntry?: string): string[];
43
+ export declare function isAcpOutputGuardArgv(argv: readonly string[]): boolean;