weacpx 0.1.7 → 0.2.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 +1 -0
- package/config.example.json +4 -1
- package/dist/bridge/bridge-main.js +179 -10
- package/dist/cli.js +1048 -166
- package/package.json +3 -3
package/README.md
CHANGED
|
@@ -4,6 +4,7 @@
|
|
|
4
4
|
|
|
5
5
|
[](https://www.npmjs.com/package/weacpx)
|
|
6
6
|
[](https://nodejs.org)
|
|
7
|
+
[](https://zread.ai/gadzan/weacpx)
|
|
7
8
|
[](./LICENSE)
|
|
8
9
|
|
|
9
10
|

|
package/config.example.json
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
"type": "acpx-bridge",
|
|
4
4
|
"sessionInitTimeoutMs": 120000,
|
|
5
5
|
"permissionMode": "approve-all",
|
|
6
|
-
"nonInteractivePermissions": "
|
|
6
|
+
"nonInteractivePermissions": "deny"
|
|
7
7
|
},
|
|
8
8
|
"logging": {
|
|
9
9
|
"level": "info",
|
|
@@ -11,6 +11,9 @@
|
|
|
11
11
|
"maxFiles": 5,
|
|
12
12
|
"retentionDays": 7
|
|
13
13
|
},
|
|
14
|
+
"wechat": {
|
|
15
|
+
"replyMode": "stream"
|
|
16
|
+
},
|
|
14
17
|
"agents": {
|
|
15
18
|
"codex": {
|
|
16
19
|
"driver": "codex"
|
|
@@ -46,6 +46,16 @@ var __export = (target, all) => {
|
|
|
46
46
|
var __esm = (fn, res) => () => (fn && (res = fn(fn = 0)), res);
|
|
47
47
|
var __require = /* @__PURE__ */ createRequire(import.meta.url);
|
|
48
48
|
|
|
49
|
+
// src/transport/acpx-bridge/acpx-bridge-protocol.ts
|
|
50
|
+
function encodeBridgeRequest(request) {
|
|
51
|
+
return `${JSON.stringify(request)}
|
|
52
|
+
`;
|
|
53
|
+
}
|
|
54
|
+
function encodeBridgePromptSegmentEvent(event) {
|
|
55
|
+
return `${JSON.stringify(event)}
|
|
56
|
+
`;
|
|
57
|
+
}
|
|
58
|
+
|
|
49
59
|
// src/transport/prompt-output.ts
|
|
50
60
|
function getPromptText(result) {
|
|
51
61
|
const stdoutOutput = extractPromptOutput(result.stdout);
|
|
@@ -193,9 +203,75 @@ var init_spawn_command = __esm(() => {
|
|
|
193
203
|
SCRIPT_FILE_PATTERN = /\.(c|m)?js$/i;
|
|
194
204
|
});
|
|
195
205
|
|
|
206
|
+
// src/transport/streaming-prompt.ts
|
|
207
|
+
function createStreamingPromptState() {
|
|
208
|
+
return {
|
|
209
|
+
buffer: "",
|
|
210
|
+
segments: [],
|
|
211
|
+
hasAgentMessage: false,
|
|
212
|
+
pendingLine: "",
|
|
213
|
+
finalize() {
|
|
214
|
+
if (this.pendingLine.trim().length > 0) {
|
|
215
|
+
parseStreamingChunks(this, this.pendingLine);
|
|
216
|
+
}
|
|
217
|
+
const remaining = this.buffer.trim();
|
|
218
|
+
this.buffer = "";
|
|
219
|
+
this.pendingLine = "";
|
|
220
|
+
return remaining;
|
|
221
|
+
}
|
|
222
|
+
};
|
|
223
|
+
}
|
|
224
|
+
function parseStreamingDataChunk(state, chunk) {
|
|
225
|
+
state.pendingLine += chunk;
|
|
226
|
+
let boundary;
|
|
227
|
+
while ((boundary = state.pendingLine.indexOf(`
|
|
228
|
+
`)) !== -1) {
|
|
229
|
+
const line = state.pendingLine.slice(0, boundary);
|
|
230
|
+
state.pendingLine = state.pendingLine.slice(boundary + 1);
|
|
231
|
+
parseStreamingChunks(state, line);
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
function parseStreamingChunks(state, line) {
|
|
235
|
+
const trimmed = line.trim();
|
|
236
|
+
if (trimmed.length === 0)
|
|
237
|
+
return;
|
|
238
|
+
let event;
|
|
239
|
+
try {
|
|
240
|
+
event = JSON.parse(trimmed);
|
|
241
|
+
} catch {
|
|
242
|
+
return;
|
|
243
|
+
}
|
|
244
|
+
const isMessageChunk = event.method === "session/update" && event.params?.update?.sessionUpdate === "agent_message_chunk" && event.params.update.content?.type === "text" && typeof event.params.update.content.text === "string";
|
|
245
|
+
if (!isMessageChunk)
|
|
246
|
+
return;
|
|
247
|
+
state.hasAgentMessage = true;
|
|
248
|
+
const chunk = event.params.update.content.text ?? "";
|
|
249
|
+
if (chunk.length === 0)
|
|
250
|
+
return;
|
|
251
|
+
state.buffer += chunk;
|
|
252
|
+
let boundary;
|
|
253
|
+
while ((boundary = state.buffer.indexOf(`
|
|
254
|
+
|
|
255
|
+
`)) !== -1) {
|
|
256
|
+
const segment = state.buffer.slice(0, boundary).trim();
|
|
257
|
+
state.buffer = state.buffer.slice(boundary + 2);
|
|
258
|
+
if (segment.length > 0) {
|
|
259
|
+
state.segments.push(segment);
|
|
260
|
+
}
|
|
261
|
+
}
|
|
262
|
+
}
|
|
263
|
+
|
|
196
264
|
// src/bridge/bridge-main.ts
|
|
197
265
|
import { createInterface } from "node:readline";
|
|
198
266
|
|
|
267
|
+
// src/bridge/bridge-env.ts
|
|
268
|
+
function normalizeBridgePermissionMode(value) {
|
|
269
|
+
return value === "approve-reads" || value === "deny-all" || value === "approve-all" ? value : "approve-all";
|
|
270
|
+
}
|
|
271
|
+
function normalizeBridgeNonInteractivePermissions(value) {
|
|
272
|
+
return value === "deny" || value === "fail" ? value : "deny";
|
|
273
|
+
}
|
|
274
|
+
|
|
199
275
|
// src/bridge/bridge-server.ts
|
|
200
276
|
init_prompt_output();
|
|
201
277
|
|
|
@@ -204,6 +280,7 @@ class BridgeInvalidRequestError extends Error {
|
|
|
204
280
|
var BRIDGE_METHODS = new Set([
|
|
205
281
|
"ping",
|
|
206
282
|
"shutdown",
|
|
283
|
+
"updatePermissionPolicy",
|
|
207
284
|
"hasSession",
|
|
208
285
|
"ensureSession",
|
|
209
286
|
"prompt",
|
|
@@ -216,12 +293,12 @@ class BridgeServer {
|
|
|
216
293
|
constructor(runtime) {
|
|
217
294
|
this.runtime = runtime;
|
|
218
295
|
}
|
|
219
|
-
async handleLine(line) {
|
|
296
|
+
async handleLine(line, writeLine) {
|
|
220
297
|
let requestId = extractRequestId(line);
|
|
221
298
|
try {
|
|
222
299
|
const request = parseBridgeRequest(line);
|
|
223
300
|
requestId = request.id;
|
|
224
|
-
const result = await this.dispatch(request.method, request.params);
|
|
301
|
+
const result = await this.dispatch(request.id, request.method, request.params, writeLine);
|
|
225
302
|
return `${JSON.stringify({
|
|
226
303
|
id: request.id,
|
|
227
304
|
ok: true,
|
|
@@ -248,12 +325,17 @@ class BridgeServer {
|
|
|
248
325
|
`;
|
|
249
326
|
}
|
|
250
327
|
}
|
|
251
|
-
async dispatch(method, params) {
|
|
328
|
+
async dispatch(requestId, method, params, writeLine) {
|
|
252
329
|
switch (method) {
|
|
253
330
|
case "ping":
|
|
254
331
|
return {};
|
|
255
332
|
case "shutdown":
|
|
256
333
|
return await this.runtime.shutdown();
|
|
334
|
+
case "updatePermissionPolicy":
|
|
335
|
+
return await this.runtime.updatePermissionPolicy({
|
|
336
|
+
permissionMode: requirePermissionMode(params, "permissionMode"),
|
|
337
|
+
nonInteractivePermissions: requireNonInteractivePermissions(params, "nonInteractivePermissions")
|
|
338
|
+
});
|
|
257
339
|
case "hasSession":
|
|
258
340
|
return await this.runtime.hasSession({
|
|
259
341
|
agent: requireString(params, "agent"),
|
|
@@ -275,6 +357,14 @@ class BridgeServer {
|
|
|
275
357
|
cwd: requireString(params, "cwd"),
|
|
276
358
|
name: requireString(params, "name"),
|
|
277
359
|
text: requireString(params, "text")
|
|
360
|
+
}, (event) => {
|
|
361
|
+
if (event.type === "prompt.segment") {
|
|
362
|
+
writeLine?.(encodeBridgePromptSegmentEvent({
|
|
363
|
+
id: requestId,
|
|
364
|
+
event: "prompt.segment",
|
|
365
|
+
text: event.text
|
|
366
|
+
}));
|
|
367
|
+
}
|
|
278
368
|
});
|
|
279
369
|
case "setMode":
|
|
280
370
|
return await this.runtime.setMode({
|
|
@@ -347,6 +437,20 @@ function requireString(params, key) {
|
|
|
347
437
|
}
|
|
348
438
|
return value;
|
|
349
439
|
}
|
|
440
|
+
function requirePermissionMode(params, key) {
|
|
441
|
+
const value = params[key];
|
|
442
|
+
if (value === "approve-all" || value === "approve-reads" || value === "deny-all") {
|
|
443
|
+
return value;
|
|
444
|
+
}
|
|
445
|
+
throw new BridgeInvalidRequestError(`${key} must be approve-all, approve-reads, or deny-all`);
|
|
446
|
+
}
|
|
447
|
+
function requireNonInteractivePermissions(params, key) {
|
|
448
|
+
const value = params[key];
|
|
449
|
+
if (value === "deny" || value === "fail") {
|
|
450
|
+
return value;
|
|
451
|
+
}
|
|
452
|
+
throw new BridgeInvalidRequestError(`${key} must be deny or fail`);
|
|
453
|
+
}
|
|
350
454
|
function asOptionalString(value) {
|
|
351
455
|
if (typeof value !== "string" || value.length === 0) {
|
|
352
456
|
return;
|
|
@@ -365,11 +469,18 @@ class BridgeRuntime {
|
|
|
365
469
|
run;
|
|
366
470
|
runSessionCreate;
|
|
367
471
|
options;
|
|
368
|
-
|
|
472
|
+
runPromptCommand;
|
|
473
|
+
constructor(command = "acpx", run = defaultRunner, runSessionCreate = shellSessionCreateRunner, options = {}, runPromptCommand = defaultPromptRunner) {
|
|
369
474
|
this.command = command;
|
|
370
475
|
this.run = run;
|
|
371
476
|
this.runSessionCreate = runSessionCreate;
|
|
372
477
|
this.options = options;
|
|
478
|
+
this.runPromptCommand = runPromptCommand;
|
|
479
|
+
}
|
|
480
|
+
async updatePermissionPolicy(policy) {
|
|
481
|
+
this.options.permissionMode = policy.permissionMode;
|
|
482
|
+
this.options.nonInteractivePermissions = policy.nonInteractivePermissions;
|
|
483
|
+
return {};
|
|
373
484
|
}
|
|
374
485
|
async hasSession(input) {
|
|
375
486
|
const spawnSpec = resolveSpawnCommand(this.command, this.buildSessionArgs(input, [
|
|
@@ -403,14 +514,14 @@ class BridgeRuntime {
|
|
|
403
514
|
}
|
|
404
515
|
return {};
|
|
405
516
|
}
|
|
406
|
-
async prompt(input) {
|
|
517
|
+
async prompt(input, onEvent) {
|
|
407
518
|
const spawnSpec = resolveSpawnCommand(this.command, this.buildPromptArgs(input, [
|
|
408
519
|
"prompt",
|
|
409
520
|
"-s",
|
|
410
521
|
input.name,
|
|
411
522
|
input.text
|
|
412
523
|
]));
|
|
413
|
-
const result = await this.run(spawnSpec.command, spawnSpec.args);
|
|
524
|
+
const result = onEvent ? await this.runPromptCommand(spawnSpec.command, spawnSpec.args, onEvent) : await this.run(spawnSpec.command, spawnSpec.args);
|
|
414
525
|
return { text: getPromptText(result) };
|
|
415
526
|
}
|
|
416
527
|
async setMode(input) {
|
|
@@ -473,7 +584,7 @@ class BridgeRuntime {
|
|
|
473
584
|
}
|
|
474
585
|
buildPermissionArgs() {
|
|
475
586
|
const permissionMode = this.options.permissionMode ?? "approve-all";
|
|
476
|
-
const nonInteractivePermissions = this.options.nonInteractivePermissions ?? "
|
|
587
|
+
const nonInteractivePermissions = this.options.nonInteractivePermissions ?? "deny";
|
|
477
588
|
const modeFlag = permissionMode === "approve-reads" ? "--approve-reads" : permissionMode === "deny-all" ? "--deny-all" : "--approve-all";
|
|
478
589
|
return [modeFlag, "--non-interactive-permissions", nonInteractivePermissions];
|
|
479
590
|
}
|
|
@@ -495,6 +606,62 @@ async function defaultRunner(command, args) {
|
|
|
495
606
|
});
|
|
496
607
|
});
|
|
497
608
|
}
|
|
609
|
+
async function runStreamingPrompt(command, args, onEvent, options = {}) {
|
|
610
|
+
const spawnPrompt = options.spawnPrompt ?? ((spawnCommand, spawnArgs) => spawn(spawnCommand, spawnArgs, { stdio: ["ignore", "pipe", "pipe"] }));
|
|
611
|
+
const setIntervalFn = options.setIntervalFn ?? ((fn, delay) => setInterval(fn, delay));
|
|
612
|
+
const clearIntervalFn = options.clearIntervalFn ?? ((timer) => clearInterval(timer));
|
|
613
|
+
const maxSegmentWaitMs = options.maxSegmentWaitMs ?? 30000;
|
|
614
|
+
const flushCheckIntervalMs = options.flushCheckIntervalMs ?? 5000;
|
|
615
|
+
const now = options.now ?? (() => Date.now());
|
|
616
|
+
return await new Promise((resolve, reject) => {
|
|
617
|
+
const child = spawnPrompt(command, args);
|
|
618
|
+
let stdout = "";
|
|
619
|
+
let stderr = "";
|
|
620
|
+
const state = createStreamingPromptState();
|
|
621
|
+
let lastReplyAt = now();
|
|
622
|
+
const flushBuffer = () => {
|
|
623
|
+
const remaining = state.buffer.trim();
|
|
624
|
+
if (remaining.length > 0) {
|
|
625
|
+
state.buffer = "";
|
|
626
|
+
onEvent?.({ type: "prompt.segment", text: remaining });
|
|
627
|
+
lastReplyAt = now();
|
|
628
|
+
}
|
|
629
|
+
};
|
|
630
|
+
const timer = setIntervalFn(() => {
|
|
631
|
+
if (state.buffer.trim().length > 0 && now() - lastReplyAt >= maxSegmentWaitMs) {
|
|
632
|
+
flushBuffer();
|
|
633
|
+
}
|
|
634
|
+
}, flushCheckIntervalMs);
|
|
635
|
+
child.stdout.setEncoding("utf8");
|
|
636
|
+
child.stdout.on("data", (chunk) => {
|
|
637
|
+
const text = String(chunk);
|
|
638
|
+
stdout += text;
|
|
639
|
+
parseStreamingDataChunk(state, text);
|
|
640
|
+
for (const segment of state.segments.splice(0)) {
|
|
641
|
+
onEvent?.({ type: "prompt.segment", text: segment });
|
|
642
|
+
lastReplyAt = now();
|
|
643
|
+
}
|
|
644
|
+
});
|
|
645
|
+
child.stderr.on("data", (chunk) => {
|
|
646
|
+
stderr += String(chunk);
|
|
647
|
+
});
|
|
648
|
+
child.on("error", (error) => {
|
|
649
|
+
clearIntervalFn(timer);
|
|
650
|
+
reject(error);
|
|
651
|
+
});
|
|
652
|
+
child.on("close", (code) => {
|
|
653
|
+
clearIntervalFn(timer);
|
|
654
|
+
const remaining = state.finalize();
|
|
655
|
+
if (remaining.length > 0) {
|
|
656
|
+
onEvent?.({ type: "prompt.segment", text: remaining });
|
|
657
|
+
}
|
|
658
|
+
resolve({ code: code ?? 1, stdout, stderr });
|
|
659
|
+
});
|
|
660
|
+
});
|
|
661
|
+
}
|
|
662
|
+
async function defaultPromptRunner(command, args, onEvent) {
|
|
663
|
+
return await runStreamingPrompt(command, args, onEvent);
|
|
664
|
+
}
|
|
498
665
|
async function shellSessionCreateRunner(command, args, cwd) {
|
|
499
666
|
const helperPath = fileURLToPath(new URL("../../scripts/acpx-session-new-helper.sh", import.meta.url));
|
|
500
667
|
return await new Promise((resolve, reject) => {
|
|
@@ -518,14 +685,16 @@ async function shellSessionCreateRunner(command, args, cwd) {
|
|
|
518
685
|
|
|
519
686
|
// src/bridge/bridge-main.ts
|
|
520
687
|
var server = new BridgeServer(new BridgeRuntime(process.env.WEACPX_BRIDGE_ACPX_COMMAND ?? "acpx", undefined, undefined, {
|
|
521
|
-
permissionMode: process.env.WEACPX_BRIDGE_PERMISSION_MODE
|
|
522
|
-
nonInteractivePermissions: process.env.WEACPX_BRIDGE_NON_INTERACTIVE_PERMISSIONS
|
|
688
|
+
permissionMode: normalizeBridgePermissionMode(process.env.WEACPX_BRIDGE_PERMISSION_MODE),
|
|
689
|
+
nonInteractivePermissions: normalizeBridgeNonInteractivePermissions(process.env.WEACPX_BRIDGE_NON_INTERACTIVE_PERMISSIONS)
|
|
523
690
|
}));
|
|
524
691
|
var input = createInterface({
|
|
525
692
|
input: process.stdin,
|
|
526
693
|
crlfDelay: Infinity
|
|
527
694
|
});
|
|
528
695
|
for await (const line of input) {
|
|
529
|
-
const response = await server.handleLine(line)
|
|
696
|
+
const response = await server.handleLine(line, (chunk) => {
|
|
697
|
+
process.stdout.write(chunk);
|
|
698
|
+
});
|
|
530
699
|
process.stdout.write(response);
|
|
531
700
|
}
|