weacpx 0.2.1 → 0.3.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.
- package/README.md +218 -288
- package/dist/bridge/bridge-main.js +1024 -247
- package/dist/cli.js +33046 -6905
- package/package.json +6 -3
|
@@ -56,6 +56,14 @@ function encodeBridgePromptSegmentEvent(event) {
|
|
|
56
56
|
return `${JSON.stringify(event)}
|
|
57
57
|
`;
|
|
58
58
|
}
|
|
59
|
+
function encodeBridgeSessionProgressEvent(event) {
|
|
60
|
+
return `${JSON.stringify(event)}
|
|
61
|
+
`;
|
|
62
|
+
}
|
|
63
|
+
function encodeBridgeSessionNoteEvent(event) {
|
|
64
|
+
return `${JSON.stringify(event)}
|
|
65
|
+
`;
|
|
66
|
+
}
|
|
59
67
|
|
|
60
68
|
// src/transport/prompt-output.ts
|
|
61
69
|
function getPromptText(result) {
|
|
@@ -205,12 +213,14 @@ var init_spawn_command = __esm(() => {
|
|
|
205
213
|
});
|
|
206
214
|
|
|
207
215
|
// src/transport/streaming-prompt.ts
|
|
208
|
-
function createStreamingPromptState() {
|
|
216
|
+
function createStreamingPromptState(formatToolCalls = false) {
|
|
209
217
|
return {
|
|
210
218
|
buffer: "",
|
|
211
219
|
segments: [],
|
|
212
220
|
hasAgentMessage: false,
|
|
213
221
|
pendingLine: "",
|
|
222
|
+
formatToolCalls,
|
|
223
|
+
emittedToolCallIds: new Set,
|
|
214
224
|
finalize() {
|
|
215
225
|
if (this.pendingLine.trim().length > 0) {
|
|
216
226
|
parseStreamingChunks(this, this.pendingLine);
|
|
@@ -242,11 +252,29 @@ function parseStreamingChunks(state, line) {
|
|
|
242
252
|
} catch {
|
|
243
253
|
return;
|
|
244
254
|
}
|
|
245
|
-
|
|
255
|
+
if (event.method !== "session/update")
|
|
256
|
+
return;
|
|
257
|
+
const update = event.params?.update;
|
|
258
|
+
if (!update)
|
|
259
|
+
return;
|
|
260
|
+
if (state.formatToolCalls && (update.sessionUpdate === "tool_call" || update.sessionUpdate === "tool_call_update")) {
|
|
261
|
+
const formatted = formatToolCallEvent(update, update.sessionUpdate);
|
|
262
|
+
if (formatted) {
|
|
263
|
+
const toolCallId = update.toolCallId;
|
|
264
|
+
if (toolCallId) {
|
|
265
|
+
if (state.emittedToolCallIds.has(toolCallId))
|
|
266
|
+
return;
|
|
267
|
+
state.emittedToolCallIds.add(toolCallId);
|
|
268
|
+
}
|
|
269
|
+
state.segments.push(formatted);
|
|
270
|
+
}
|
|
271
|
+
return;
|
|
272
|
+
}
|
|
273
|
+
const isMessageChunk = update.sessionUpdate === "agent_message_chunk" && update.content?.type === "text" && typeof update.content.text === "string";
|
|
246
274
|
if (!isMessageChunk)
|
|
247
275
|
return;
|
|
248
276
|
state.hasAgentMessage = true;
|
|
249
|
-
const chunk =
|
|
277
|
+
const chunk = update.content.text ?? "";
|
|
250
278
|
if (chunk.length === 0)
|
|
251
279
|
return;
|
|
252
280
|
state.buffer += chunk;
|
|
@@ -261,211 +289,532 @@ function parseStreamingChunks(state, line) {
|
|
|
261
289
|
}
|
|
262
290
|
}
|
|
263
291
|
}
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
292
|
+
function formatToolCallEvent(update, sessionUpdate) {
|
|
293
|
+
if (!update)
|
|
294
|
+
return null;
|
|
295
|
+
const kind = update.kind ?? "";
|
|
296
|
+
const title = update.title ?? "";
|
|
297
|
+
if (title.length === 0)
|
|
298
|
+
return null;
|
|
299
|
+
const emoji = KIND_EMOJI[kind] ?? "\uD83D\uDD27";
|
|
300
|
+
const command = getToolDisplayCommand(update);
|
|
301
|
+
if (command) {
|
|
302
|
+
return `${emoji} ${truncateToolDisplay(command)}`;
|
|
303
|
+
}
|
|
304
|
+
if (sessionUpdate === "tool_call_update" || isGenericToolTitle(kind, title))
|
|
305
|
+
return null;
|
|
306
|
+
return `${emoji} ${title}`;
|
|
271
307
|
}
|
|
272
|
-
function
|
|
273
|
-
|
|
308
|
+
function getToolDisplayCommand(update) {
|
|
309
|
+
if (!update)
|
|
310
|
+
return null;
|
|
311
|
+
const command = update.rawInput?.command;
|
|
312
|
+
if (typeof command === "string" && command.length > 0) {
|
|
313
|
+
return command;
|
|
314
|
+
}
|
|
315
|
+
const parsedCmd = update.rawInput?.parsed_cmd;
|
|
316
|
+
if (parsedCmd && parsedCmd.length > 0) {
|
|
317
|
+
const parts = [];
|
|
318
|
+
for (const entry of parsedCmd) {
|
|
319
|
+
if (entry && typeof entry.cmd === "string" && entry.cmd.length > 0) {
|
|
320
|
+
parts.push(entry.cmd);
|
|
321
|
+
}
|
|
322
|
+
}
|
|
323
|
+
if (parts.length > 0) {
|
|
324
|
+
return parts.join(" ");
|
|
325
|
+
}
|
|
326
|
+
}
|
|
327
|
+
return null;
|
|
274
328
|
}
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
init_prompt_output();
|
|
278
|
-
|
|
279
|
-
class BridgeInvalidRequestError extends Error {
|
|
329
|
+
function truncateToolDisplay(text) {
|
|
330
|
+
return text.length > 60 ? `${text.slice(0, 57)}...` : text;
|
|
280
331
|
}
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
"
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
"
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
"
|
|
290
|
-
|
|
332
|
+
function isGenericToolTitle(kind, title) {
|
|
333
|
+
const normalizedTitle = title.trim().toLowerCase();
|
|
334
|
+
if (kind === "execute" && ["bash", "shell", "sh", "powershell", "cmd"].includes(normalizedTitle)) {
|
|
335
|
+
return true;
|
|
336
|
+
}
|
|
337
|
+
if (kind === "search" && ["grep", "rg", "search"].includes(normalizedTitle)) {
|
|
338
|
+
return true;
|
|
339
|
+
}
|
|
340
|
+
if (kind === "read" && ["read", "cat"].includes(normalizedTitle)) {
|
|
341
|
+
return true;
|
|
342
|
+
}
|
|
343
|
+
return false;
|
|
344
|
+
}
|
|
345
|
+
var KIND_EMOJI;
|
|
346
|
+
var init_streaming_prompt = __esm(() => {
|
|
347
|
+
KIND_EMOJI = {
|
|
348
|
+
read: "\uD83D\uDCD6",
|
|
349
|
+
search: "\uD83D\uDD0D",
|
|
350
|
+
execute: "\uD83D\uDCBB",
|
|
351
|
+
edit: "✏️"
|
|
352
|
+
};
|
|
353
|
+
});
|
|
291
354
|
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
355
|
+
// src/recovery/discover-parent-package-paths.ts
|
|
356
|
+
import { spawn } from "node:child_process";
|
|
357
|
+
import { access } from "node:fs/promises";
|
|
358
|
+
import { homedir } from "node:os";
|
|
359
|
+
import { dirname, join } from "node:path";
|
|
360
|
+
function deriveParentPackageName(platformPackage) {
|
|
361
|
+
return platformPackage.replace(/-(?:linux|darwin|win32|windows|freebsd|openbsd|sunos|aix)(?:-(?:x64|arm64|ia32|arm|ppc64|s390x))?(?:-(?:baseline|musl|gnu|gnueabihf|musleabihf|msvc))?$/, "");
|
|
362
|
+
}
|
|
363
|
+
async function discoverParentPackagePaths(platformPackage, seedPath, deps = {}) {
|
|
364
|
+
const env = deps.env ?? process.env;
|
|
365
|
+
const home = deps.home ?? homedir();
|
|
366
|
+
const cwd = deps.cwd ?? process.cwd();
|
|
367
|
+
const fsExists = deps.fsExists ?? defaultFsExists;
|
|
368
|
+
const resolveFromCwd = deps.resolveFromCwd ?? defaultResolveFromCwd;
|
|
369
|
+
const queryRoot = deps.queryPackageManagerRoot ?? defaultQueryPackageManagerRoot;
|
|
370
|
+
const parentName = deriveParentPackageName(platformPackage);
|
|
371
|
+
const rawCandidates = [];
|
|
372
|
+
const bunGlobalRoot = env.BUN_INSTALL ? join(env.BUN_INSTALL, "install", "global", "node_modules") : join(home, ".bun", "install", "global", "node_modules");
|
|
373
|
+
const [npmRoot, pnpmRoot, yarnRoot] = await Promise.all([
|
|
374
|
+
queryRoot("npm"),
|
|
375
|
+
queryRoot("pnpm"),
|
|
376
|
+
queryRoot("yarn")
|
|
377
|
+
]);
|
|
378
|
+
const classify = (p) => {
|
|
379
|
+
if (isUnder(p, bunGlobalRoot))
|
|
380
|
+
return "bun";
|
|
381
|
+
if (pnpmRoot && isUnder(p, pnpmRoot))
|
|
382
|
+
return "pnpm";
|
|
383
|
+
if (yarnRoot && isUnder(p, yarnRoot))
|
|
384
|
+
return "yarn";
|
|
385
|
+
return "npm";
|
|
386
|
+
};
|
|
387
|
+
if (seedPath) {
|
|
388
|
+
rawCandidates.push({ path: seedPath, manager: classify(seedPath) });
|
|
296
389
|
}
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
requestId = request.id;
|
|
302
|
-
const result = await this.dispatch(request.id, request.method, request.params, writeLine);
|
|
303
|
-
return `${JSON.stringify({
|
|
304
|
-
id: request.id,
|
|
305
|
-
ok: true,
|
|
306
|
-
result
|
|
307
|
-
})}
|
|
308
|
-
`;
|
|
309
|
-
} catch (error) {
|
|
310
|
-
const message = error instanceof Error ? error.message : String(error);
|
|
311
|
-
return `${JSON.stringify({
|
|
312
|
-
id: requestId,
|
|
313
|
-
ok: false,
|
|
314
|
-
error: {
|
|
315
|
-
code: error instanceof BridgeInvalidRequestError ? "BRIDGE_INVALID_REQUEST" : "BRIDGE_INTERNAL_ERROR",
|
|
316
|
-
message,
|
|
317
|
-
...error instanceof PromptCommandError ? {
|
|
318
|
-
details: {
|
|
319
|
-
exitCode: error.exitCode,
|
|
320
|
-
stdout: error.stdout,
|
|
321
|
-
stderr: error.stderr
|
|
322
|
-
}
|
|
323
|
-
} : {}
|
|
324
|
-
}
|
|
325
|
-
})}
|
|
326
|
-
`;
|
|
327
|
-
}
|
|
390
|
+
for (const name of [parentName, platformPackage]) {
|
|
391
|
+
const resolved = resolveFromCwd(name, cwd);
|
|
392
|
+
if (resolved)
|
|
393
|
+
rawCandidates.push({ path: resolved, manager: classify(resolved) });
|
|
328
394
|
}
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
cwd: requireString(params, "cwd"),
|
|
345
|
-
name: requireString(params, "name")
|
|
346
|
-
});
|
|
347
|
-
case "ensureSession":
|
|
348
|
-
return await this.runtime.ensureSession({
|
|
349
|
-
agent: requireString(params, "agent"),
|
|
350
|
-
agentCommand: asOptionalString(params.agentCommand),
|
|
351
|
-
cwd: requireString(params, "cwd"),
|
|
352
|
-
name: requireString(params, "name")
|
|
353
|
-
});
|
|
354
|
-
case "prompt":
|
|
355
|
-
return await this.runtime.prompt({
|
|
356
|
-
agent: requireString(params, "agent"),
|
|
357
|
-
agentCommand: asOptionalString(params.agentCommand),
|
|
358
|
-
cwd: requireString(params, "cwd"),
|
|
359
|
-
name: requireString(params, "name"),
|
|
360
|
-
text: requireString(params, "text")
|
|
361
|
-
}, (event) => {
|
|
362
|
-
if (event.type === "prompt.segment") {
|
|
363
|
-
writeLine?.(encodeBridgePromptSegmentEvent({
|
|
364
|
-
id: requestId,
|
|
365
|
-
event: "prompt.segment",
|
|
366
|
-
text: event.text
|
|
367
|
-
}));
|
|
368
|
-
}
|
|
369
|
-
});
|
|
370
|
-
case "setMode":
|
|
371
|
-
return await this.runtime.setMode({
|
|
372
|
-
agent: requireString(params, "agent"),
|
|
373
|
-
agentCommand: asOptionalString(params.agentCommand),
|
|
374
|
-
cwd: requireString(params, "cwd"),
|
|
375
|
-
name: requireString(params, "name"),
|
|
376
|
-
modeId: requireString(params, "modeId")
|
|
377
|
-
});
|
|
378
|
-
case "cancel":
|
|
379
|
-
return await this.runtime.cancel({
|
|
380
|
-
agent: requireString(params, "agent"),
|
|
381
|
-
agentCommand: asOptionalString(params.agentCommand),
|
|
382
|
-
cwd: requireString(params, "cwd"),
|
|
383
|
-
name: requireString(params, "name")
|
|
384
|
-
});
|
|
385
|
-
default:
|
|
386
|
-
throw new Error(`unsupported bridge method: ${method}`);
|
|
395
|
+
rawCandidates.push({ path: join(bunGlobalRoot, parentName), manager: "bun" });
|
|
396
|
+
if (npmRoot)
|
|
397
|
+
rawCandidates.push({ path: join(npmRoot, parentName), manager: "npm" });
|
|
398
|
+
if (pnpmRoot)
|
|
399
|
+
rawCandidates.push({ path: join(pnpmRoot, parentName), manager: "pnpm" });
|
|
400
|
+
if (yarnRoot)
|
|
401
|
+
rawCandidates.push({ path: join(yarnRoot, parentName), manager: "yarn" });
|
|
402
|
+
const seen = new Set;
|
|
403
|
+
const verified = [];
|
|
404
|
+
for (const candidate of rawCandidates) {
|
|
405
|
+
if (seen.has(candidate.path))
|
|
406
|
+
continue;
|
|
407
|
+
seen.add(candidate.path);
|
|
408
|
+
if (await fsExists(join(candidate.path, "package.json"))) {
|
|
409
|
+
verified.push(candidate);
|
|
387
410
|
}
|
|
388
411
|
}
|
|
412
|
+
return verified;
|
|
389
413
|
}
|
|
390
|
-
function
|
|
414
|
+
function isUnder(child, parent) {
|
|
415
|
+
const c = child.replace(/[\\/]+$/, "");
|
|
416
|
+
const p = parent.replace(/[\\/]+$/, "");
|
|
417
|
+
return c === p || c.startsWith(p + "/") || c.startsWith(p + "\\");
|
|
418
|
+
}
|
|
419
|
+
async function defaultFsExists(path) {
|
|
391
420
|
try {
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
return "unknown";
|
|
395
|
-
}
|
|
396
|
-
const id = raw.id;
|
|
397
|
-
return typeof id === "string" && id.length > 0 ? id : "unknown";
|
|
421
|
+
await access(path);
|
|
422
|
+
return true;
|
|
398
423
|
} catch {
|
|
399
|
-
return
|
|
424
|
+
return false;
|
|
400
425
|
}
|
|
401
426
|
}
|
|
402
|
-
function
|
|
403
|
-
let raw;
|
|
427
|
+
function defaultResolveFromCwd(name, cwd) {
|
|
404
428
|
try {
|
|
405
|
-
|
|
429
|
+
const pkgJson = __require.resolve(`${name}/package.json`, {
|
|
430
|
+
paths: [cwd, ...__require.resolve.paths(name) ?? []]
|
|
431
|
+
});
|
|
432
|
+
return dirname(pkgJson);
|
|
406
433
|
} catch {
|
|
407
|
-
|
|
434
|
+
return null;
|
|
408
435
|
}
|
|
409
|
-
|
|
410
|
-
|
|
436
|
+
}
|
|
437
|
+
async function defaultQueryPackageManagerRoot(tool) {
|
|
438
|
+
const spec = tool === "yarn" ? { cmd: "yarn", args: ["global", "dir"], postfix: "node_modules" } : { cmd: tool, args: ["root", "-g"], postfix: null };
|
|
439
|
+
return await new Promise((resolve) => {
|
|
440
|
+
let settled = false;
|
|
441
|
+
const done = (value) => {
|
|
442
|
+
if (settled)
|
|
443
|
+
return;
|
|
444
|
+
settled = true;
|
|
445
|
+
resolve(value);
|
|
446
|
+
};
|
|
447
|
+
let child;
|
|
448
|
+
try {
|
|
449
|
+
child = spawn(spec.cmd, spec.args, {
|
|
450
|
+
stdio: ["ignore", "pipe", "pipe"],
|
|
451
|
+
shell: process.platform === "win32"
|
|
452
|
+
});
|
|
453
|
+
} catch {
|
|
454
|
+
done(null);
|
|
455
|
+
return;
|
|
456
|
+
}
|
|
457
|
+
let stdout = "";
|
|
458
|
+
const timer = setTimeout(() => {
|
|
459
|
+
try {
|
|
460
|
+
child.kill();
|
|
461
|
+
} catch {}
|
|
462
|
+
done(null);
|
|
463
|
+
}, 2000);
|
|
464
|
+
timer.unref?.();
|
|
465
|
+
child.stdout.on("data", (chunk) => {
|
|
466
|
+
stdout += String(chunk);
|
|
467
|
+
});
|
|
468
|
+
child.on("error", () => {
|
|
469
|
+
clearTimeout(timer);
|
|
470
|
+
done(null);
|
|
471
|
+
});
|
|
472
|
+
child.on("close", (code) => {
|
|
473
|
+
clearTimeout(timer);
|
|
474
|
+
if (code !== 0)
|
|
475
|
+
return done(null);
|
|
476
|
+
const trimmed = stdout.trim().split(/\r?\n/).pop()?.trim() ?? "";
|
|
477
|
+
if (!trimmed)
|
|
478
|
+
return done(null);
|
|
479
|
+
done(spec.postfix ? join(trimmed, spec.postfix) : trimmed);
|
|
480
|
+
});
|
|
481
|
+
});
|
|
482
|
+
}
|
|
483
|
+
var init_discover_parent_package_paths = () => {};
|
|
484
|
+
|
|
485
|
+
// src/process/terminate-process-tree.ts
|
|
486
|
+
import { spawn as spawn2 } from "node:child_process";
|
|
487
|
+
async function terminateProcessTree(pid, platform = process.platform, runCommand = defaultRunProcessCommand, killProcess = (targetPid, signal) => {
|
|
488
|
+
process.kill(targetPid, signal);
|
|
489
|
+
}, isProcessRunning = defaultIsProcessRunning) {
|
|
490
|
+
if (pid <= 0) {
|
|
491
|
+
return;
|
|
411
492
|
}
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
throw new BridgeInvalidRequestError("id must be a non-empty string");
|
|
493
|
+
if (platform === "win32") {
|
|
494
|
+
try {
|
|
495
|
+
await runCommand("taskkill", ["/PID", String(pid), "/T", "/F"]);
|
|
496
|
+
} catch {}
|
|
497
|
+
return;
|
|
418
498
|
}
|
|
419
|
-
|
|
420
|
-
|
|
499
|
+
const targetPid = pid > 0 ? -pid : pid;
|
|
500
|
+
try {
|
|
501
|
+
killProcess(targetPid, "SIGTERM");
|
|
502
|
+
} catch {
|
|
503
|
+
return;
|
|
421
504
|
}
|
|
422
|
-
|
|
423
|
-
|
|
505
|
+
const deadline = Date.now() + 5000;
|
|
506
|
+
while (Date.now() < deadline) {
|
|
507
|
+
if (!isProcessRunning(targetPid)) {
|
|
508
|
+
return;
|
|
509
|
+
}
|
|
510
|
+
await new Promise((resolve) => setTimeout(resolve, 100));
|
|
424
511
|
}
|
|
425
|
-
|
|
426
|
-
|
|
512
|
+
try {
|
|
513
|
+
killProcess(targetPid, "SIGKILL");
|
|
514
|
+
} catch {}
|
|
515
|
+
}
|
|
516
|
+
function defaultIsProcessRunning(pid) {
|
|
517
|
+
try {
|
|
518
|
+
process.kill(pid, 0);
|
|
519
|
+
return true;
|
|
520
|
+
} catch {
|
|
521
|
+
return false;
|
|
427
522
|
}
|
|
523
|
+
}
|
|
524
|
+
async function defaultRunProcessCommand(command, args) {
|
|
525
|
+
return await new Promise((resolve, reject) => {
|
|
526
|
+
const child = spawn2(command, args, { stdio: "ignore" });
|
|
527
|
+
child.on("error", reject);
|
|
528
|
+
child.on("close", (code) => resolve(code ?? 1));
|
|
529
|
+
});
|
|
530
|
+
}
|
|
531
|
+
var init_terminate_process_tree = () => {};
|
|
532
|
+
|
|
533
|
+
// src/transport/acpx-queue-owner-launcher.ts
|
|
534
|
+
import { createHash } from "node:crypto";
|
|
535
|
+
import { spawn as spawn3 } from "node:child_process";
|
|
536
|
+
import { readFile, unlink } from "node:fs/promises";
|
|
537
|
+
import { homedir as homedir2 } from "node:os";
|
|
538
|
+
import { join as join2 } from "node:path";
|
|
539
|
+
function buildWeacpxMcpServerSpec(input) {
|
|
540
|
+
const { command, args } = splitCommandLine(input.weacpxCommand);
|
|
428
541
|
return {
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
542
|
+
name: "weacpx-orchestration",
|
|
543
|
+
type: "stdio",
|
|
544
|
+
command,
|
|
545
|
+
args: [
|
|
546
|
+
...args,
|
|
547
|
+
"mcp-stdio",
|
|
548
|
+
"--coordinator-session",
|
|
549
|
+
input.coordinatorSession,
|
|
550
|
+
...input.sourceHandle ? ["--source-handle", input.sourceHandle] : []
|
|
551
|
+
]
|
|
432
552
|
};
|
|
433
553
|
}
|
|
434
|
-
function
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
554
|
+
function buildQueueOwnerPayload(input) {
|
|
555
|
+
return {
|
|
556
|
+
sessionId: input.sessionId,
|
|
557
|
+
permissionMode: input.permissionMode,
|
|
558
|
+
nonInteractivePermissions: input.nonInteractivePermissions,
|
|
559
|
+
ttlMs: input.ttlMs ?? 300000,
|
|
560
|
+
maxQueueDepth: input.maxQueueDepth ?? 16,
|
|
561
|
+
mcpServers: input.mcpServers
|
|
562
|
+
};
|
|
440
563
|
}
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
564
|
+
|
|
565
|
+
class AcpxQueueOwnerLauncher {
|
|
566
|
+
acpxCommand;
|
|
567
|
+
weacpxCommand;
|
|
568
|
+
spawnOwner;
|
|
569
|
+
terminateOwner;
|
|
570
|
+
baseEnv;
|
|
571
|
+
ttlMs;
|
|
572
|
+
maxQueueDepth;
|
|
573
|
+
launchLocks = new Map;
|
|
574
|
+
constructor(options) {
|
|
575
|
+
this.acpxCommand = options.acpxCommand;
|
|
576
|
+
this.weacpxCommand = options.weacpxCommand ?? resolveDefaultWeacpxCommand(options.baseEnv ?? process.env);
|
|
577
|
+
this.spawnOwner = options.spawnOwner ?? defaultQueueOwnerSpawner;
|
|
578
|
+
this.terminateOwner = options.terminateOwner ?? createDefaultQueueOwnerTerminator(options.acpxCommand);
|
|
579
|
+
this.baseEnv = options.baseEnv ?? process.env;
|
|
580
|
+
this.ttlMs = options.ttlMs;
|
|
581
|
+
this.maxQueueDepth = options.maxQueueDepth;
|
|
582
|
+
}
|
|
583
|
+
async launch(input) {
|
|
584
|
+
const key = input.acpxRecordId;
|
|
585
|
+
const previous = this.launchLocks.get(key) ?? Promise.resolve();
|
|
586
|
+
const next = previous.then(() => this.doLaunch(input), () => this.doLaunch(input));
|
|
587
|
+
this.launchLocks.set(key, next.catch(() => {}));
|
|
588
|
+
return next;
|
|
589
|
+
}
|
|
590
|
+
async doLaunch(input) {
|
|
591
|
+
await this.terminateOwner(input.acpxRecordId);
|
|
592
|
+
const payload = buildQueueOwnerPayload({
|
|
593
|
+
sessionId: input.acpxRecordId,
|
|
594
|
+
permissionMode: input.permissionMode,
|
|
595
|
+
nonInteractivePermissions: input.nonInteractivePermissions,
|
|
596
|
+
ttlMs: this.ttlMs,
|
|
597
|
+
maxQueueDepth: this.maxQueueDepth,
|
|
598
|
+
mcpServers: [buildWeacpxMcpServerSpec({
|
|
599
|
+
weacpxCommand: this.weacpxCommand,
|
|
600
|
+
coordinatorSession: input.coordinatorSession,
|
|
601
|
+
...input.sourceHandle ? { sourceHandle: input.sourceHandle } : {}
|
|
602
|
+
})]
|
|
603
|
+
});
|
|
604
|
+
const spawnSpec = resolveSpawnCommand(this.acpxCommand, ["__queue-owner"]);
|
|
605
|
+
await this.spawnOwner(spawnSpec.command, spawnSpec.args, {
|
|
606
|
+
env: {
|
|
607
|
+
...stringEnv(this.baseEnv),
|
|
608
|
+
ACPX_QUEUE_OWNER_PAYLOAD: JSON.stringify(payload)
|
|
609
|
+
}
|
|
610
|
+
});
|
|
445
611
|
}
|
|
446
|
-
throw new BridgeInvalidRequestError(`${key} must be approve-all, approve-reads, or deny-all`);
|
|
447
612
|
}
|
|
448
|
-
function
|
|
449
|
-
const
|
|
450
|
-
|
|
451
|
-
|
|
613
|
+
function splitCommandLine(value) {
|
|
614
|
+
const parts = [];
|
|
615
|
+
let current = "";
|
|
616
|
+
let quote = null;
|
|
617
|
+
let escaping = false;
|
|
618
|
+
for (const char of value) {
|
|
619
|
+
if (escaping) {
|
|
620
|
+
current += char;
|
|
621
|
+
escaping = false;
|
|
622
|
+
continue;
|
|
623
|
+
}
|
|
624
|
+
if (char === "\\" && quote !== "'") {
|
|
625
|
+
escaping = true;
|
|
626
|
+
continue;
|
|
627
|
+
}
|
|
628
|
+
if (quote) {
|
|
629
|
+
if (char === quote) {
|
|
630
|
+
quote = null;
|
|
631
|
+
} else {
|
|
632
|
+
current += char;
|
|
633
|
+
}
|
|
634
|
+
continue;
|
|
635
|
+
}
|
|
636
|
+
if (char === "'" || char === '"') {
|
|
637
|
+
quote = char;
|
|
638
|
+
continue;
|
|
639
|
+
}
|
|
640
|
+
if (/\s/.test(char)) {
|
|
641
|
+
if (current.length > 0) {
|
|
642
|
+
parts.push(current);
|
|
643
|
+
current = "";
|
|
644
|
+
}
|
|
645
|
+
continue;
|
|
646
|
+
}
|
|
647
|
+
current += char;
|
|
452
648
|
}
|
|
453
|
-
|
|
649
|
+
if (escaping) {
|
|
650
|
+
current += "\\";
|
|
651
|
+
}
|
|
652
|
+
if (quote) {
|
|
653
|
+
throw new Error("weacpx MCP command has an unterminated quote");
|
|
654
|
+
}
|
|
655
|
+
if (current.length > 0) {
|
|
656
|
+
parts.push(current);
|
|
657
|
+
}
|
|
658
|
+
if (parts.length === 0) {
|
|
659
|
+
throw new Error("weacpx MCP command must not be empty");
|
|
660
|
+
}
|
|
661
|
+
return { command: parts[0], args: parts.slice(1) };
|
|
454
662
|
}
|
|
455
|
-
function
|
|
456
|
-
|
|
663
|
+
function stringEnv(env) {
|
|
664
|
+
return Object.fromEntries(Object.entries(env).filter((entry) => typeof entry[1] === "string"));
|
|
665
|
+
}
|
|
666
|
+
async function defaultQueueOwnerSpawner(command, args, options) {
|
|
667
|
+
await new Promise((resolve, reject) => {
|
|
668
|
+
const child = spawn3(command, args, {
|
|
669
|
+
detached: true,
|
|
670
|
+
stdio: "ignore",
|
|
671
|
+
env: options.env,
|
|
672
|
+
windowsHide: true
|
|
673
|
+
});
|
|
674
|
+
child.once("error", reject);
|
|
675
|
+
child.once("spawn", () => {
|
|
676
|
+
child.unref();
|
|
677
|
+
resolve();
|
|
678
|
+
});
|
|
679
|
+
});
|
|
680
|
+
}
|
|
681
|
+
function createDefaultQueueOwnerTerminator(_acpxCommand) {
|
|
682
|
+
return async (sessionId) => {
|
|
683
|
+
await terminateAcpxQueueOwner(sessionId);
|
|
684
|
+
};
|
|
685
|
+
}
|
|
686
|
+
async function terminateAcpxQueueOwner(sessionId) {
|
|
687
|
+
const lockPath = queueLockFilePath(sessionId);
|
|
688
|
+
let owner;
|
|
689
|
+
try {
|
|
690
|
+
owner = JSON.parse(await readFile(lockPath, "utf8"));
|
|
691
|
+
} catch {
|
|
457
692
|
return;
|
|
458
693
|
}
|
|
459
|
-
|
|
694
|
+
if (typeof owner.pid === "number" && Number.isInteger(owner.pid) && owner.pid > 0) {
|
|
695
|
+
await terminateProcessTree(owner.pid);
|
|
696
|
+
}
|
|
697
|
+
await unlink(lockPath).catch(() => {});
|
|
460
698
|
}
|
|
461
|
-
|
|
699
|
+
function queueLockFilePath(sessionId) {
|
|
700
|
+
return join2(homedir2(), ".acpx", "queues", `${shortHash(sessionId, 24)}.lock`);
|
|
701
|
+
}
|
|
702
|
+
function shortHash(value, length) {
|
|
703
|
+
return createHash("sha256").update(value).digest("hex").slice(0, length);
|
|
704
|
+
}
|
|
705
|
+
function resolveDefaultWeacpxCommand(env) {
|
|
706
|
+
if (env.WEACPX_CLI_COMMAND?.trim()) {
|
|
707
|
+
return env.WEACPX_CLI_COMMAND.trim();
|
|
708
|
+
}
|
|
709
|
+
if (env.WEACPX_DAEMON_ARG0?.trim()) {
|
|
710
|
+
return `${quoteCommandPart(process.execPath)} ${quoteCommandPart(env.WEACPX_DAEMON_ARG0.trim())}`;
|
|
711
|
+
}
|
|
712
|
+
if (process.argv[1]) {
|
|
713
|
+
return `${quoteCommandPart(process.execPath)} ${quoteCommandPart(process.argv[1])}`;
|
|
714
|
+
}
|
|
715
|
+
return "weacpx";
|
|
716
|
+
}
|
|
717
|
+
function quoteCommandPart(value) {
|
|
718
|
+
return `"${value.replace(/\\/g, "\\\\").replace(/"/g, "\\\"")}"`;
|
|
719
|
+
}
|
|
720
|
+
var init_acpx_queue_owner_launcher = __esm(() => {
|
|
721
|
+
init_spawn_command();
|
|
722
|
+
init_terminate_process_tree();
|
|
723
|
+
});
|
|
724
|
+
|
|
725
|
+
// src/transport/permission-mode-flag.ts
|
|
726
|
+
function permissionModeToFlag(permissionMode) {
|
|
727
|
+
switch (permissionMode) {
|
|
728
|
+
case "approve-reads":
|
|
729
|
+
return "--approve-reads";
|
|
730
|
+
case "deny-all":
|
|
731
|
+
return "--deny-all";
|
|
732
|
+
case "approve-all":
|
|
733
|
+
return "--approve-all";
|
|
734
|
+
}
|
|
735
|
+
}
|
|
736
|
+
|
|
737
|
+
// src/bridge/bridge-main.ts
|
|
738
|
+
import { createInterface } from "node:readline";
|
|
739
|
+
|
|
740
|
+
// src/bridge/bridge-env.ts
|
|
741
|
+
function normalizeBridgePermissionMode(value) {
|
|
742
|
+
return value === "approve-reads" || value === "deny-all" || value === "approve-all" ? value : "approve-all";
|
|
743
|
+
}
|
|
744
|
+
function normalizeBridgeNonInteractivePermissions(value) {
|
|
745
|
+
return value === "deny" || value === "fail" ? value : "deny";
|
|
746
|
+
}
|
|
747
|
+
|
|
748
|
+
// src/bridge/bridge-server.ts
|
|
749
|
+
init_prompt_output();
|
|
750
|
+
|
|
751
|
+
// src/bridge/bridge-request-scheduler.ts
|
|
752
|
+
class BridgeRequestScheduler {
|
|
753
|
+
sessions = new Map;
|
|
754
|
+
run(sessionName, lane, task) {
|
|
755
|
+
if (lane === "control") {
|
|
756
|
+
return Promise.resolve().then(task);
|
|
757
|
+
}
|
|
758
|
+
const state = this.sessions.get(sessionName) ?? this.createSessionState(sessionName);
|
|
759
|
+
state.pendingNormals += 1;
|
|
760
|
+
const result = state.tail.then(() => task());
|
|
761
|
+
state.tail = result.then(() => {
|
|
762
|
+
return;
|
|
763
|
+
}, () => {
|
|
764
|
+
return;
|
|
765
|
+
});
|
|
766
|
+
return result.finally(() => {
|
|
767
|
+
state.pendingNormals -= 1;
|
|
768
|
+
if (state.pendingNormals === 0 && this.sessions.get(sessionName) === state) {
|
|
769
|
+
this.sessions.delete(sessionName);
|
|
770
|
+
}
|
|
771
|
+
});
|
|
772
|
+
}
|
|
773
|
+
createSessionState(sessionName) {
|
|
774
|
+
const state = {
|
|
775
|
+
pendingNormals: 0,
|
|
776
|
+
tail: Promise.resolve()
|
|
777
|
+
};
|
|
778
|
+
this.sessions.set(sessionName, state);
|
|
779
|
+
return state;
|
|
780
|
+
}
|
|
781
|
+
}
|
|
782
|
+
|
|
462
783
|
// src/bridge/bridge-runtime.ts
|
|
463
784
|
init_spawn_command();
|
|
464
785
|
init_prompt_output();
|
|
786
|
+
init_streaming_prompt();
|
|
465
787
|
import { copyFile, readdir } from "node:fs/promises";
|
|
466
|
-
import { homedir } from "node:os";
|
|
467
|
-
import { join } from "node:path";
|
|
468
|
-
import { spawn } from "node:child_process";
|
|
788
|
+
import { homedir as homedir3 } from "node:os";
|
|
789
|
+
import { dirname as dirname2, join as join3, win32 } from "node:path";
|
|
790
|
+
import { spawn as spawn4 } from "node:child_process";
|
|
791
|
+
|
|
792
|
+
// src/bridge/parse-missing-optional-dep.ts
|
|
793
|
+
var PATTERN = /You can try manually installing ["']([^"']+)["']/;
|
|
794
|
+
var VALID_NAME = /^(@[a-z0-9][a-z0-9._-]*\/)?[a-z0-9][a-z0-9._-]*$/;
|
|
795
|
+
function parseMissingOptionalDep(text) {
|
|
796
|
+
const match = PATTERN.exec(text);
|
|
797
|
+
if (!match || !match[1])
|
|
798
|
+
return null;
|
|
799
|
+
const pkg = match[1];
|
|
800
|
+
if (!VALID_NAME.test(pkg))
|
|
801
|
+
return null;
|
|
802
|
+
return { package: pkg };
|
|
803
|
+
}
|
|
804
|
+
|
|
805
|
+
// src/bridge/bridge-runtime.ts
|
|
806
|
+
init_discover_parent_package_paths();
|
|
807
|
+
init_acpx_queue_owner_launcher();
|
|
808
|
+
class EnsureSessionFailedError extends Error {
|
|
809
|
+
kind;
|
|
810
|
+
data;
|
|
811
|
+
constructor(message, kind, data) {
|
|
812
|
+
super(message);
|
|
813
|
+
this.name = "EnsureSessionFailedError";
|
|
814
|
+
this.kind = kind;
|
|
815
|
+
this.data = data;
|
|
816
|
+
}
|
|
817
|
+
}
|
|
469
818
|
|
|
470
819
|
class BridgeRuntime {
|
|
471
820
|
command;
|
|
@@ -473,12 +822,19 @@ class BridgeRuntime {
|
|
|
473
822
|
runSessionCreate;
|
|
474
823
|
options;
|
|
475
824
|
runPromptCommand;
|
|
476
|
-
|
|
825
|
+
repairSessionIndex;
|
|
826
|
+
queueOwnerLauncher;
|
|
827
|
+
acpxVerboseSupported = undefined;
|
|
828
|
+
constructor(command = "acpx", run = defaultRunner, runSessionCreate = shellSessionCreateRunner, options = {}, runPromptCommand = defaultPromptRunner, repairSessionIndex = tryRepairAcpxSessionIndex, queueOwnerLauncher = new AcpxQueueOwnerLauncher({
|
|
829
|
+
acpxCommand: command
|
|
830
|
+
})) {
|
|
477
831
|
this.command = command;
|
|
478
832
|
this.run = run;
|
|
479
833
|
this.runSessionCreate = runSessionCreate;
|
|
480
834
|
this.options = options;
|
|
481
835
|
this.runPromptCommand = runPromptCommand;
|
|
836
|
+
this.repairSessionIndex = repairSessionIndex;
|
|
837
|
+
this.queueOwnerLauncher = queueOwnerLauncher;
|
|
482
838
|
}
|
|
483
839
|
async updatePermissionPolicy(policy) {
|
|
484
840
|
this.options.permissionMode = policy.permissionMode;
|
|
@@ -494,46 +850,137 @@ class BridgeRuntime {
|
|
|
494
850
|
const result = await this.run(spawnSpec.command, spawnSpec.args);
|
|
495
851
|
return { exists: result.code === 0 };
|
|
496
852
|
}
|
|
497
|
-
async ensureSession(input) {
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
"
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
853
|
+
async ensureSession(input, onProgress) {
|
|
854
|
+
onProgress?.("spawn");
|
|
855
|
+
const onStderrLine = onProgress ? (line) => {
|
|
856
|
+
const trimmed = line.replace(/\r$/, "").trimEnd();
|
|
857
|
+
if (trimmed.length === 0)
|
|
858
|
+
return;
|
|
859
|
+
onProgress({ kind: "note", text: trimmed });
|
|
860
|
+
} : undefined;
|
|
861
|
+
const runWithVerboseFallback = async (tailArgs, runner) => {
|
|
862
|
+
const useVerbose = this.acpxVerboseSupported !== false;
|
|
863
|
+
const spec = resolveSpawnCommand(this.command, this.buildSessionArgs(input, tailArgs, { verbose: useVerbose }));
|
|
864
|
+
const result = await runner(spec.command, spec.args);
|
|
865
|
+
if (result.code === 0) {
|
|
866
|
+
if (useVerbose)
|
|
867
|
+
this.acpxVerboseSupported = true;
|
|
868
|
+
return result;
|
|
869
|
+
}
|
|
870
|
+
if (useVerbose && isUnknownVerboseOption(result.stderr, result.stdout)) {
|
|
871
|
+
this.acpxVerboseSupported = false;
|
|
872
|
+
const retrySpec = resolveSpawnCommand(this.command, this.buildSessionArgs(input, tailArgs, { verbose: false }));
|
|
873
|
+
return await runner(retrySpec.command, retrySpec.args);
|
|
874
|
+
}
|
|
875
|
+
return result;
|
|
876
|
+
};
|
|
877
|
+
const ensured = await runWithVerboseFallback(["sessions", "ensure", "--name", input.name], (command, args) => this.run(command, args, { onStderrLine }));
|
|
505
878
|
if (ensured.code === 0) {
|
|
879
|
+
onProgress?.("ready");
|
|
506
880
|
return {};
|
|
507
881
|
}
|
|
508
882
|
const existingSpec = resolveSpawnCommand(this.command, this.buildSessionArgs(input, ["sessions", "show", input.name]));
|
|
509
883
|
const existing = await this.run(existingSpec.command, existingSpec.args);
|
|
510
884
|
if (existing.code === 0) {
|
|
885
|
+
onProgress?.("ready");
|
|
511
886
|
return {};
|
|
512
887
|
}
|
|
513
|
-
|
|
514
|
-
const created = await this.runSessionCreate(
|
|
888
|
+
onProgress?.("initializing");
|
|
889
|
+
const created = await runWithVerboseFallback(["sessions", "new", "--name", input.name], (command, args) => this.runSessionCreate(command, args, input.cwd, { onStderrLine }));
|
|
515
890
|
if (created.code === 0) {
|
|
891
|
+
onProgress?.("ready");
|
|
516
892
|
return {};
|
|
517
893
|
}
|
|
518
894
|
const output = created.stderr || created.stdout || "";
|
|
519
|
-
if (output.includes("EPERM") && await
|
|
895
|
+
if (output.includes("EPERM") && await this.repairSessionIndex()) {
|
|
520
896
|
const repaired = await this.run(existingSpec.command, existingSpec.args);
|
|
521
897
|
if (repaired.code === 0) {
|
|
898
|
+
onProgress?.("ready");
|
|
522
899
|
return {};
|
|
523
900
|
}
|
|
524
901
|
}
|
|
525
|
-
|
|
902
|
+
const rawMessage = output || ensured.stderr || ensured.stdout || "failed to create session";
|
|
903
|
+
const parseInput = rawMessage.split(/\r\n|\r|\n/).filter((line) => !/^\s*\[acpx\]/.test(line)).join(`
|
|
904
|
+
`);
|
|
905
|
+
const parsed = parseMissingOptionalDep(parseInput);
|
|
906
|
+
if (parsed) {
|
|
907
|
+
const parentPackagePath = this.resolveParentPackagePath(input, parsed.package);
|
|
908
|
+
throw new EnsureSessionFailedError(rawMessage, "missing_optional_dep", {
|
|
909
|
+
package: parsed.package,
|
|
910
|
+
parentPackagePath
|
|
911
|
+
});
|
|
912
|
+
}
|
|
913
|
+
throw new EnsureSessionFailedError(rawMessage, "generic");
|
|
914
|
+
}
|
|
915
|
+
resolveParentPackagePath(input, platformPackage) {
|
|
916
|
+
const guess = deriveParentPackageName(platformPackage);
|
|
917
|
+
const candidates = [input.agentCommand, input.agent, guess].filter((c) => Boolean(c));
|
|
918
|
+
for (const candidate of candidates) {
|
|
919
|
+
try {
|
|
920
|
+
const resolved = __require.resolve(`${candidate}/package.json`, {
|
|
921
|
+
paths: [process.cwd(), ...__require.resolve.paths(candidate) ?? []]
|
|
922
|
+
});
|
|
923
|
+
return dirname2(resolved);
|
|
924
|
+
} catch {
|
|
925
|
+
continue;
|
|
926
|
+
}
|
|
927
|
+
}
|
|
928
|
+
return null;
|
|
526
929
|
}
|
|
527
930
|
async prompt(input, onEvent) {
|
|
931
|
+
await this.launchMcpQueueOwnerIfNeeded(input);
|
|
528
932
|
const spawnSpec = resolveSpawnCommand(this.command, this.buildPromptArgs(input, [
|
|
529
933
|
"prompt",
|
|
530
934
|
"-s",
|
|
531
935
|
input.name,
|
|
532
936
|
input.text
|
|
533
937
|
]));
|
|
534
|
-
const
|
|
938
|
+
const formatToolCalls = input.replyMode === "verbose";
|
|
939
|
+
const result = onEvent ? await this.runPromptCommand(spawnSpec.command, spawnSpec.args, onEvent, { formatToolCalls }) : await this.run(spawnSpec.command, spawnSpec.args);
|
|
535
940
|
return { text: getPromptText(result) };
|
|
536
941
|
}
|
|
942
|
+
async launchMcpQueueOwnerIfNeeded(input) {
|
|
943
|
+
if (!input.mcpCoordinatorSession) {
|
|
944
|
+
return;
|
|
945
|
+
}
|
|
946
|
+
const record = await this.readSessionRecord(input);
|
|
947
|
+
await this.queueOwnerLauncher.launch({
|
|
948
|
+
acpxRecordId: record.acpxRecordId,
|
|
949
|
+
coordinatorSession: input.mcpCoordinatorSession,
|
|
950
|
+
...input.mcpSourceHandle ? { sourceHandle: input.mcpSourceHandle } : {},
|
|
951
|
+
permissionMode: this.options.permissionMode ?? "approve-all",
|
|
952
|
+
nonInteractivePermissions: this.options.nonInteractivePermissions ?? "deny"
|
|
953
|
+
});
|
|
954
|
+
}
|
|
955
|
+
async readSessionRecord(input) {
|
|
956
|
+
const spawnSpec = resolveSpawnCommand(this.command, this.buildSessionArgs(input, [
|
|
957
|
+
"sessions",
|
|
958
|
+
"show",
|
|
959
|
+
input.name
|
|
960
|
+
]));
|
|
961
|
+
const result = await this.run(spawnSpec.command, spawnSpec.args);
|
|
962
|
+
if (result.code !== 0) {
|
|
963
|
+
throw new Error(result.stderr || result.stdout || "sessions show failed");
|
|
964
|
+
}
|
|
965
|
+
try {
|
|
966
|
+
const parsed = JSON.parse(result.stdout);
|
|
967
|
+
let acpxRecordId;
|
|
968
|
+
if (typeof parsed.acpxRecordId === "string") {
|
|
969
|
+
acpxRecordId = parsed.acpxRecordId;
|
|
970
|
+
} else if (typeof parsed.id === "string") {
|
|
971
|
+
acpxRecordId = parsed.id;
|
|
972
|
+
}
|
|
973
|
+
if (acpxRecordId) {
|
|
974
|
+
return { acpxRecordId };
|
|
975
|
+
}
|
|
976
|
+
} catch {
|
|
977
|
+
const firstLine = result.stdout.trim().split(/\r?\n/, 1)[0];
|
|
978
|
+
if (firstLine && /^[\w.:-]+$/.test(firstLine) && firstLine.length >= 8) {
|
|
979
|
+
return { acpxRecordId: firstLine };
|
|
980
|
+
}
|
|
981
|
+
}
|
|
982
|
+
throw new Error("failed to resolve acpx session record id");
|
|
983
|
+
}
|
|
537
984
|
async setMode(input) {
|
|
538
985
|
const spawnSpec = resolveSpawnCommand(this.command, this.buildSessionArgs(input, [
|
|
539
986
|
"set-mode",
|
|
@@ -562,10 +1009,25 @@ class BridgeRuntime {
|
|
|
562
1009
|
message: result.stdout.trim()
|
|
563
1010
|
};
|
|
564
1011
|
}
|
|
1012
|
+
async removeSession(input) {
|
|
1013
|
+
const spawnSpec = resolveSpawnCommand(this.command, this.buildSessionArgs(input, [
|
|
1014
|
+
"sessions",
|
|
1015
|
+
"close",
|
|
1016
|
+
input.name
|
|
1017
|
+
]));
|
|
1018
|
+
const result = await this.run(spawnSpec.command, spawnSpec.args);
|
|
1019
|
+
if (result.code === 0) {
|
|
1020
|
+
return {};
|
|
1021
|
+
}
|
|
1022
|
+
if (isMissingBridgeSessionError(result.stderr, result.stdout)) {
|
|
1023
|
+
return {};
|
|
1024
|
+
}
|
|
1025
|
+
throw new Error(result.stderr || result.stdout || "sessions close failed");
|
|
1026
|
+
}
|
|
565
1027
|
async shutdown() {
|
|
566
1028
|
return {};
|
|
567
1029
|
}
|
|
568
|
-
buildSessionArgs(input, tail) {
|
|
1030
|
+
buildSessionArgs(input, tail, options = {}) {
|
|
569
1031
|
const prefix = [
|
|
570
1032
|
"--format",
|
|
571
1033
|
"quiet",
|
|
@@ -573,6 +1035,9 @@ class BridgeRuntime {
|
|
|
573
1035
|
input.cwd,
|
|
574
1036
|
...this.buildPermissionArgs()
|
|
575
1037
|
];
|
|
1038
|
+
if (options.verbose) {
|
|
1039
|
+
prefix.push("--verbose");
|
|
1040
|
+
}
|
|
576
1041
|
if (input.agentCommand) {
|
|
577
1042
|
return [...prefix, "--agent", input.agentCommand, ...tail];
|
|
578
1043
|
}
|
|
@@ -595,29 +1060,47 @@ class BridgeRuntime {
|
|
|
595
1060
|
buildPermissionArgs() {
|
|
596
1061
|
const permissionMode = this.options.permissionMode ?? "approve-all";
|
|
597
1062
|
const nonInteractivePermissions = this.options.nonInteractivePermissions ?? "deny";
|
|
598
|
-
const modeFlag = permissionMode
|
|
1063
|
+
const modeFlag = permissionModeToFlag(permissionMode);
|
|
599
1064
|
return [modeFlag, "--non-interactive-permissions", nonInteractivePermissions];
|
|
600
1065
|
}
|
|
601
1066
|
}
|
|
602
|
-
|
|
603
|
-
return
|
|
604
|
-
const child =
|
|
1067
|
+
function spawnCapture(command, args, options) {
|
|
1068
|
+
return new Promise((resolve, reject) => {
|
|
1069
|
+
const child = spawn4(command, args, { cwd: options?.cwd, stdio: ["ignore", "pipe", "pipe"] });
|
|
1070
|
+
child.stdout.setEncoding("utf8");
|
|
1071
|
+
child.stderr.setEncoding("utf8");
|
|
605
1072
|
let stdout = "";
|
|
606
1073
|
let stderr = "";
|
|
1074
|
+
let stderrTail = "";
|
|
607
1075
|
child.stdout.on("data", (chunk) => {
|
|
608
1076
|
stdout += String(chunk);
|
|
609
1077
|
});
|
|
610
1078
|
child.stderr.on("data", (chunk) => {
|
|
611
|
-
|
|
1079
|
+
const text = String(chunk);
|
|
1080
|
+
stderr += text;
|
|
1081
|
+
if (!options?.onStderrLine)
|
|
1082
|
+
return;
|
|
1083
|
+
stderrTail += text;
|
|
1084
|
+
const matches = stderrTail.split(/\r\n|\r|\n/);
|
|
1085
|
+
stderrTail = matches.pop() ?? "";
|
|
1086
|
+
for (const line of matches) {
|
|
1087
|
+
options.onStderrLine(line);
|
|
1088
|
+
}
|
|
612
1089
|
});
|
|
613
1090
|
child.on("error", reject);
|
|
614
1091
|
child.on("close", (code) => {
|
|
1092
|
+
if (options?.onStderrLine && stderrTail.length > 0) {
|
|
1093
|
+
options.onStderrLine(stderrTail);
|
|
1094
|
+
}
|
|
615
1095
|
resolve({ code: code ?? 1, stdout, stderr });
|
|
616
1096
|
});
|
|
617
1097
|
});
|
|
618
1098
|
}
|
|
1099
|
+
async function defaultRunner(command, args, options) {
|
|
1100
|
+
return await spawnCapture(command, args, options);
|
|
1101
|
+
}
|
|
619
1102
|
async function runStreamingPrompt(command, args, onEvent, options = {}) {
|
|
620
|
-
const spawnPrompt = options.spawnPrompt ?? ((spawnCommand, spawnArgs) =>
|
|
1103
|
+
const spawnPrompt = options.spawnPrompt ?? ((spawnCommand, spawnArgs) => spawn4(spawnCommand, spawnArgs, { stdio: ["ignore", "pipe", "pipe"] }));
|
|
621
1104
|
const setIntervalFn = options.setIntervalFn ?? ((fn, delay) => setInterval(fn, delay));
|
|
622
1105
|
const clearIntervalFn = options.clearIntervalFn ?? ((timer) => clearInterval(timer));
|
|
623
1106
|
const maxSegmentWaitMs = options.maxSegmentWaitMs ?? 30000;
|
|
@@ -627,7 +1110,7 @@ async function runStreamingPrompt(command, args, onEvent, options = {}) {
|
|
|
627
1110
|
const child = spawnPrompt(command, args);
|
|
628
1111
|
let stdout = "";
|
|
629
1112
|
let stderr = "";
|
|
630
|
-
const state = createStreamingPromptState();
|
|
1113
|
+
const state = createStreamingPromptState(options.formatToolCalls ?? false);
|
|
631
1114
|
let lastReplyAt = now();
|
|
632
1115
|
const flushBuffer = () => {
|
|
633
1116
|
const remaining = state.buffer.trim();
|
|
@@ -669,81 +1152,375 @@ async function runStreamingPrompt(command, args, onEvent, options = {}) {
|
|
|
669
1152
|
});
|
|
670
1153
|
});
|
|
671
1154
|
}
|
|
672
|
-
async function defaultPromptRunner(command, args, onEvent) {
|
|
673
|
-
return await runStreamingPrompt(command, args, onEvent);
|
|
1155
|
+
async function defaultPromptRunner(command, args, onEvent, options) {
|
|
1156
|
+
return await runStreamingPrompt(command, args, onEvent, options);
|
|
674
1157
|
}
|
|
675
|
-
async function shellSessionCreateRunner(command, args, cwd) {
|
|
676
|
-
return await
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
}
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
});
|
|
1158
|
+
async function shellSessionCreateRunner(command, args, cwd, options) {
|
|
1159
|
+
return await spawnCapture(command, args, { cwd, onStderrLine: options?.onStderrLine });
|
|
1160
|
+
}
|
|
1161
|
+
function selectLatestAcpxSessionIndexTmp(files) {
|
|
1162
|
+
let latestTmp = null;
|
|
1163
|
+
let latestTime = 0;
|
|
1164
|
+
for (const file of files) {
|
|
1165
|
+
const match = file.match(/^index\.json\.\d+\.(\d+)\.tmp$/);
|
|
1166
|
+
if (!match) {
|
|
1167
|
+
continue;
|
|
1168
|
+
}
|
|
1169
|
+
const timestamp = Number(match[1]);
|
|
1170
|
+
if (timestamp > latestTime) {
|
|
1171
|
+
latestTime = timestamp;
|
|
1172
|
+
latestTmp = file;
|
|
1173
|
+
}
|
|
1174
|
+
}
|
|
1175
|
+
return latestTmp;
|
|
694
1176
|
}
|
|
695
|
-
async function tryRepairAcpxSessionIndex() {
|
|
696
|
-
|
|
1177
|
+
async function tryRepairAcpxSessionIndex(deps = {}) {
|
|
1178
|
+
const platform = deps.platform ?? process.platform;
|
|
1179
|
+
if (platform !== "win32") {
|
|
697
1180
|
return false;
|
|
698
1181
|
}
|
|
699
|
-
const home = process.env.HOME ?? process.env.USERPROFILE ??
|
|
1182
|
+
const home = deps.home ?? process.env.HOME ?? process.env.USERPROFILE ?? homedir3();
|
|
700
1183
|
if (!home) {
|
|
701
1184
|
return false;
|
|
702
1185
|
}
|
|
703
|
-
const
|
|
704
|
-
const
|
|
1186
|
+
const pathJoin = platform === "win32" ? win32.join : join3;
|
|
1187
|
+
const sessionsDir = pathJoin(home, ".acpx", "sessions");
|
|
1188
|
+
const indexPath = pathJoin(sessionsDir, "index.json");
|
|
1189
|
+
const readdirFn = deps.readdirFn ?? readdir;
|
|
1190
|
+
const copyFileFn = deps.copyFileFn ?? copyFile;
|
|
705
1191
|
let files;
|
|
706
1192
|
try {
|
|
707
|
-
files = await
|
|
1193
|
+
files = await readdirFn(sessionsDir);
|
|
708
1194
|
} catch {
|
|
709
1195
|
return false;
|
|
710
1196
|
}
|
|
711
|
-
const
|
|
712
|
-
if (tmpFiles.length === 0) {
|
|
713
|
-
return false;
|
|
714
|
-
}
|
|
715
|
-
let latestTmp = "";
|
|
716
|
-
let latestTime = 0;
|
|
717
|
-
for (const f of tmpFiles) {
|
|
718
|
-
const match = f.match(/^index\.json\.\d+\.(\d+)\.tmp$/);
|
|
719
|
-
if (match && Number(match[1]) > latestTime) {
|
|
720
|
-
latestTime = Number(match[1]);
|
|
721
|
-
latestTmp = f;
|
|
722
|
-
}
|
|
723
|
-
}
|
|
1197
|
+
const latestTmp = selectLatestAcpxSessionIndexTmp(files);
|
|
724
1198
|
if (!latestTmp) {
|
|
725
1199
|
return false;
|
|
726
1200
|
}
|
|
727
1201
|
try {
|
|
728
|
-
await
|
|
1202
|
+
await copyFileFn(pathJoin(sessionsDir, latestTmp), indexPath);
|
|
729
1203
|
return true;
|
|
730
1204
|
} catch {
|
|
731
1205
|
return false;
|
|
732
1206
|
}
|
|
733
1207
|
}
|
|
1208
|
+
function isUnknownVerboseOption(stderr, stdout) {
|
|
1209
|
+
const combined = `${stderr}
|
|
1210
|
+
${stdout}`;
|
|
1211
|
+
return /(unknown|unrecognized)\b[^\n]*--verbose/i.test(combined);
|
|
1212
|
+
}
|
|
1213
|
+
function isMissingBridgeSessionError(stderr, stdout) {
|
|
1214
|
+
const combined = `${stderr}
|
|
1215
|
+
${stdout}`.toLowerCase();
|
|
1216
|
+
return combined.includes("no named session") || combined.includes("no cwd session") || combined.includes("session not found") || combined.includes("unknown session") || combined.includes("no acpx session found");
|
|
1217
|
+
}
|
|
1218
|
+
|
|
1219
|
+
// src/bridge/bridge-server.ts
|
|
1220
|
+
class BridgeInvalidRequestError extends Error {
|
|
1221
|
+
}
|
|
1222
|
+
var BRIDGE_METHODS = new Set([
|
|
1223
|
+
"ping",
|
|
1224
|
+
"shutdown",
|
|
1225
|
+
"updatePermissionPolicy",
|
|
1226
|
+
"hasSession",
|
|
1227
|
+
"ensureSession",
|
|
1228
|
+
"prompt",
|
|
1229
|
+
"setMode",
|
|
1230
|
+
"cancel",
|
|
1231
|
+
"removeSession"
|
|
1232
|
+
]);
|
|
1233
|
+
var SESSION_SCOPED_METHODS = new Set([
|
|
1234
|
+
"hasSession",
|
|
1235
|
+
"ensureSession",
|
|
1236
|
+
"prompt",
|
|
1237
|
+
"setMode",
|
|
1238
|
+
"cancel",
|
|
1239
|
+
"removeSession"
|
|
1240
|
+
]);
|
|
1241
|
+
|
|
1242
|
+
class BridgeServer {
|
|
1243
|
+
runtime;
|
|
1244
|
+
scheduler = new BridgeRequestScheduler;
|
|
1245
|
+
constructor(runtime) {
|
|
1246
|
+
this.runtime = runtime;
|
|
1247
|
+
}
|
|
1248
|
+
async handleLine(line, writeLine) {
|
|
1249
|
+
let requestId = extractRequestId(line);
|
|
1250
|
+
try {
|
|
1251
|
+
const request = parseBridgeRequest(line);
|
|
1252
|
+
requestId = request.id;
|
|
1253
|
+
const result = await this.dispatchRequest(request.id, request.method, request.params, writeLine);
|
|
1254
|
+
return `${JSON.stringify({
|
|
1255
|
+
id: request.id,
|
|
1256
|
+
ok: true,
|
|
1257
|
+
result
|
|
1258
|
+
})}
|
|
1259
|
+
`;
|
|
1260
|
+
} catch (error) {
|
|
1261
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
1262
|
+
const ensureSessionFields = error instanceof EnsureSessionFailedError ? { kind: error.kind, ...error.data ? { data: error.data } : {} } : {};
|
|
1263
|
+
const promptDetails = error instanceof PromptCommandError ? { details: { exitCode: error.exitCode, stdout: error.stdout, stderr: error.stderr } } : {};
|
|
1264
|
+
return `${JSON.stringify({
|
|
1265
|
+
id: requestId,
|
|
1266
|
+
ok: false,
|
|
1267
|
+
error: {
|
|
1268
|
+
code: error instanceof BridgeInvalidRequestError ? "BRIDGE_INVALID_REQUEST" : "BRIDGE_INTERNAL_ERROR",
|
|
1269
|
+
message,
|
|
1270
|
+
...ensureSessionFields,
|
|
1271
|
+
...promptDetails
|
|
1272
|
+
}
|
|
1273
|
+
})}
|
|
1274
|
+
`;
|
|
1275
|
+
}
|
|
1276
|
+
}
|
|
1277
|
+
async dispatchRequest(requestId, method, params, writeLine) {
|
|
1278
|
+
if (!SESSION_SCOPED_METHODS.has(method)) {
|
|
1279
|
+
return await this.dispatch(requestId, method, params, writeLine);
|
|
1280
|
+
}
|
|
1281
|
+
const sessionName = getSessionName(params);
|
|
1282
|
+
if (!sessionName) {
|
|
1283
|
+
return await this.dispatch(requestId, method, params, writeLine);
|
|
1284
|
+
}
|
|
1285
|
+
const sessionKey = getSessionScheduleKey(params);
|
|
1286
|
+
if (!sessionKey) {
|
|
1287
|
+
return await this.dispatch(requestId, method, params, writeLine);
|
|
1288
|
+
}
|
|
1289
|
+
const lane = method === "cancel" ? "control" : "normal";
|
|
1290
|
+
return await this.scheduler.run(sessionKey, lane, () => this.dispatch(requestId, method, params, writeLine));
|
|
1291
|
+
}
|
|
1292
|
+
async dispatch(requestId, method, params, writeLine) {
|
|
1293
|
+
switch (method) {
|
|
1294
|
+
case "ping":
|
|
1295
|
+
return {};
|
|
1296
|
+
case "shutdown":
|
|
1297
|
+
return await this.runtime.shutdown();
|
|
1298
|
+
case "updatePermissionPolicy":
|
|
1299
|
+
return await this.runtime.updatePermissionPolicy({
|
|
1300
|
+
permissionMode: requirePermissionMode(params, "permissionMode"),
|
|
1301
|
+
nonInteractivePermissions: requireNonInteractivePermissions(params, "nonInteractivePermissions")
|
|
1302
|
+
});
|
|
1303
|
+
case "hasSession":
|
|
1304
|
+
return await this.runtime.hasSession({
|
|
1305
|
+
agent: requireString(params, "agent"),
|
|
1306
|
+
agentCommand: asOptionalString(params.agentCommand),
|
|
1307
|
+
cwd: requireString(params, "cwd"),
|
|
1308
|
+
name: requireString(params, "name")
|
|
1309
|
+
});
|
|
1310
|
+
case "ensureSession":
|
|
1311
|
+
return await this.runtime.ensureSession({
|
|
1312
|
+
agent: requireString(params, "agent"),
|
|
1313
|
+
agentCommand: asOptionalString(params.agentCommand),
|
|
1314
|
+
cwd: requireString(params, "cwd"),
|
|
1315
|
+
name: requireString(params, "name"),
|
|
1316
|
+
mcpCoordinatorSession: asOptionalString(params.mcpCoordinatorSession),
|
|
1317
|
+
mcpSourceHandle: asOptionalString(params.mcpSourceHandle)
|
|
1318
|
+
}, (progress) => {
|
|
1319
|
+
if (typeof progress === "string") {
|
|
1320
|
+
writeLine?.(encodeBridgeSessionProgressEvent({
|
|
1321
|
+
id: requestId,
|
|
1322
|
+
event: "session.progress",
|
|
1323
|
+
stage: progress
|
|
1324
|
+
}));
|
|
1325
|
+
} else if (progress.kind === "note") {
|
|
1326
|
+
writeLine?.(encodeBridgeSessionNoteEvent({
|
|
1327
|
+
id: requestId,
|
|
1328
|
+
event: "session.note",
|
|
1329
|
+
text: progress.text
|
|
1330
|
+
}));
|
|
1331
|
+
}
|
|
1332
|
+
});
|
|
1333
|
+
case "prompt":
|
|
1334
|
+
return await this.runtime.prompt({
|
|
1335
|
+
agent: requireString(params, "agent"),
|
|
1336
|
+
agentCommand: asOptionalString(params.agentCommand),
|
|
1337
|
+
cwd: requireString(params, "cwd"),
|
|
1338
|
+
name: requireString(params, "name"),
|
|
1339
|
+
mcpCoordinatorSession: asOptionalString(params.mcpCoordinatorSession),
|
|
1340
|
+
mcpSourceHandle: asOptionalString(params.mcpSourceHandle),
|
|
1341
|
+
text: requireString(params, "text"),
|
|
1342
|
+
replyMode: asOptionalReplyMode(params.replyMode)
|
|
1343
|
+
}, (event) => {
|
|
1344
|
+
if (event.type === "prompt.segment") {
|
|
1345
|
+
writeLine?.(encodeBridgePromptSegmentEvent({
|
|
1346
|
+
id: requestId,
|
|
1347
|
+
event: "prompt.segment",
|
|
1348
|
+
text: event.text
|
|
1349
|
+
}));
|
|
1350
|
+
}
|
|
1351
|
+
});
|
|
1352
|
+
case "setMode":
|
|
1353
|
+
return await this.runtime.setMode({
|
|
1354
|
+
agent: requireString(params, "agent"),
|
|
1355
|
+
agentCommand: asOptionalString(params.agentCommand),
|
|
1356
|
+
cwd: requireString(params, "cwd"),
|
|
1357
|
+
name: requireString(params, "name"),
|
|
1358
|
+
modeId: requireString(params, "modeId")
|
|
1359
|
+
});
|
|
1360
|
+
case "cancel":
|
|
1361
|
+
return await this.runtime.cancel({
|
|
1362
|
+
agent: requireString(params, "agent"),
|
|
1363
|
+
agentCommand: asOptionalString(params.agentCommand),
|
|
1364
|
+
cwd: requireString(params, "cwd"),
|
|
1365
|
+
name: requireString(params, "name")
|
|
1366
|
+
});
|
|
1367
|
+
case "removeSession":
|
|
1368
|
+
return await this.runtime.removeSession({
|
|
1369
|
+
agent: requireString(params, "agent"),
|
|
1370
|
+
agentCommand: asOptionalString(params.agentCommand),
|
|
1371
|
+
cwd: requireString(params, "cwd"),
|
|
1372
|
+
name: requireString(params, "name")
|
|
1373
|
+
});
|
|
1374
|
+
default:
|
|
1375
|
+
throw new Error(`unsupported bridge method: ${method}`);
|
|
1376
|
+
}
|
|
1377
|
+
}
|
|
1378
|
+
}
|
|
1379
|
+
function extractRequestId(line) {
|
|
1380
|
+
try {
|
|
1381
|
+
const raw = JSON.parse(line);
|
|
1382
|
+
if (!raw || typeof raw !== "object" || Array.isArray(raw)) {
|
|
1383
|
+
return "unknown";
|
|
1384
|
+
}
|
|
1385
|
+
const id = raw.id;
|
|
1386
|
+
return typeof id === "string" && id.length > 0 ? id : "unknown";
|
|
1387
|
+
} catch {
|
|
1388
|
+
return "unknown";
|
|
1389
|
+
}
|
|
1390
|
+
}
|
|
1391
|
+
function parseBridgeRequest(line) {
|
|
1392
|
+
let raw;
|
|
1393
|
+
try {
|
|
1394
|
+
raw = JSON.parse(line);
|
|
1395
|
+
} catch {
|
|
1396
|
+
throw new BridgeInvalidRequestError("request must be valid JSON");
|
|
1397
|
+
}
|
|
1398
|
+
if (!raw || typeof raw !== "object" || Array.isArray(raw)) {
|
|
1399
|
+
throw new BridgeInvalidRequestError("request must be a JSON object");
|
|
1400
|
+
}
|
|
1401
|
+
const request = raw;
|
|
1402
|
+
const id = request.id;
|
|
1403
|
+
const method = request.method;
|
|
1404
|
+
const params = request.params;
|
|
1405
|
+
if (typeof id !== "string" || id.length === 0) {
|
|
1406
|
+
throw new BridgeInvalidRequestError("id must be a non-empty string");
|
|
1407
|
+
}
|
|
1408
|
+
if (typeof method !== "string" || method.length === 0) {
|
|
1409
|
+
throw new BridgeInvalidRequestError("method must be a non-empty string");
|
|
1410
|
+
}
|
|
1411
|
+
if (!BRIDGE_METHODS.has(method)) {
|
|
1412
|
+
throw new BridgeInvalidRequestError(`unsupported bridge method: ${method}`);
|
|
1413
|
+
}
|
|
1414
|
+
if (!params || typeof params !== "object" || Array.isArray(params)) {
|
|
1415
|
+
throw new BridgeInvalidRequestError("params must be an object");
|
|
1416
|
+
}
|
|
1417
|
+
return {
|
|
1418
|
+
id,
|
|
1419
|
+
method,
|
|
1420
|
+
params
|
|
1421
|
+
};
|
|
1422
|
+
}
|
|
1423
|
+
function getSessionName(params) {
|
|
1424
|
+
return asNonEmptyString(params.name);
|
|
1425
|
+
}
|
|
1426
|
+
function getSessionScheduleKey(params) {
|
|
1427
|
+
const name = asNonEmptyString(params.name);
|
|
1428
|
+
const cwd = asNonEmptyString(params.cwd);
|
|
1429
|
+
const agentIdentity = asNonEmptyString(params.agentCommand) ?? asNonEmptyString(params.agent);
|
|
1430
|
+
if (!name || !cwd || !agentIdentity) {
|
|
1431
|
+
return;
|
|
1432
|
+
}
|
|
1433
|
+
return JSON.stringify([agentIdentity, cwd, name]);
|
|
1434
|
+
}
|
|
1435
|
+
function asNonEmptyString(value) {
|
|
1436
|
+
if (typeof value !== "string" || value.length === 0) {
|
|
1437
|
+
return;
|
|
1438
|
+
}
|
|
1439
|
+
return value;
|
|
1440
|
+
}
|
|
1441
|
+
function requireString(params, key) {
|
|
1442
|
+
const value = params[key];
|
|
1443
|
+
if (typeof value !== "string" || value.length === 0) {
|
|
1444
|
+
throw new BridgeInvalidRequestError(`${key} must be a non-empty string`);
|
|
1445
|
+
}
|
|
1446
|
+
return value;
|
|
1447
|
+
}
|
|
1448
|
+
function requirePermissionMode(params, key) {
|
|
1449
|
+
const value = params[key];
|
|
1450
|
+
if (value === "approve-all" || value === "approve-reads" || value === "deny-all") {
|
|
1451
|
+
return value;
|
|
1452
|
+
}
|
|
1453
|
+
throw new BridgeInvalidRequestError(`${key} must be approve-all, approve-reads, or deny-all`);
|
|
1454
|
+
}
|
|
1455
|
+
function requireNonInteractivePermissions(params, key) {
|
|
1456
|
+
const value = params[key];
|
|
1457
|
+
if (value === "deny" || value === "fail") {
|
|
1458
|
+
return value;
|
|
1459
|
+
}
|
|
1460
|
+
throw new BridgeInvalidRequestError(`${key} must be deny or fail`);
|
|
1461
|
+
}
|
|
1462
|
+
function asOptionalString(value) {
|
|
1463
|
+
if (typeof value !== "string" || value.length === 0) {
|
|
1464
|
+
return;
|
|
1465
|
+
}
|
|
1466
|
+
return value;
|
|
1467
|
+
}
|
|
1468
|
+
var VALID_REPLY_MODES = new Set(["stream", "final", "verbose"]);
|
|
1469
|
+
function asOptionalReplyMode(value) {
|
|
1470
|
+
if (typeof value !== "string" || !VALID_REPLY_MODES.has(value)) {
|
|
1471
|
+
return;
|
|
1472
|
+
}
|
|
1473
|
+
return value;
|
|
1474
|
+
}
|
|
734
1475
|
|
|
735
1476
|
// src/bridge/bridge-main.ts
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
});
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
|
|
1477
|
+
async function processBridgeInput(options) {
|
|
1478
|
+
const pendingWrites = new Set;
|
|
1479
|
+
let firstError;
|
|
1480
|
+
for await (const line of options.input) {
|
|
1481
|
+
const pendingWrite = (async () => {
|
|
1482
|
+
const response = await options.server.handleLine(line, (chunk) => {
|
|
1483
|
+
options.write(chunk);
|
|
1484
|
+
});
|
|
1485
|
+
options.write(response);
|
|
1486
|
+
})();
|
|
1487
|
+
const observedPendingWrite = pendingWrite.catch((error) => {
|
|
1488
|
+
if (firstError === undefined) {
|
|
1489
|
+
firstError = error;
|
|
1490
|
+
options.input.close();
|
|
1491
|
+
}
|
|
1492
|
+
});
|
|
1493
|
+
pendingWrites.add(pendingWrite);
|
|
1494
|
+
observedPendingWrite.finally(() => {
|
|
1495
|
+
pendingWrites.delete(pendingWrite);
|
|
1496
|
+
});
|
|
1497
|
+
}
|
|
1498
|
+
await Promise.allSettled(pendingWrites);
|
|
1499
|
+
if (firstError !== undefined) {
|
|
1500
|
+
throw firstError;
|
|
1501
|
+
}
|
|
1502
|
+
}
|
|
1503
|
+
async function runBridgeMain() {
|
|
1504
|
+
const server = new BridgeServer(new BridgeRuntime(process.env.WEACPX_BRIDGE_ACPX_COMMAND ?? "acpx", undefined, undefined, {
|
|
1505
|
+
permissionMode: normalizeBridgePermissionMode(process.env.WEACPX_BRIDGE_PERMISSION_MODE),
|
|
1506
|
+
nonInteractivePermissions: normalizeBridgeNonInteractivePermissions(process.env.WEACPX_BRIDGE_NON_INTERACTIVE_PERMISSIONS)
|
|
1507
|
+
}));
|
|
1508
|
+
const input = createInterface({
|
|
1509
|
+
input: process.stdin,
|
|
1510
|
+
crlfDelay: Infinity
|
|
1511
|
+
});
|
|
1512
|
+
await processBridgeInput({
|
|
1513
|
+
input,
|
|
1514
|
+
server,
|
|
1515
|
+
write: (chunk) => {
|
|
1516
|
+
process.stdout.write(chunk);
|
|
1517
|
+
}
|
|
747
1518
|
});
|
|
748
|
-
process.stdout.write(response);
|
|
749
1519
|
}
|
|
1520
|
+
if (__require.main == __require.module) {
|
|
1521
|
+
await runBridgeMain();
|
|
1522
|
+
}
|
|
1523
|
+
export {
|
|
1524
|
+
runBridgeMain,
|
|
1525
|
+
processBridgeInput
|
|
1526
|
+
};
|