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 CHANGED
@@ -4,6 +4,7 @@
4
4
 
5
5
  [![npm](https://img.shields.io/npm/v/weacpx?style=flat-square)](https://www.npmjs.com/package/weacpx)
6
6
  [![Node.js Version](https://img.shields.io/node/v/weacpx?style=flat-square)](https://nodejs.org)
7
+ [![zread](https://img.shields.io/badge/Ask_Zread-_.svg?style=flat-square&color=00b0aa&labelColor=000000&logo=data%3Aimage%2Fsvg%2Bxml%3Bbase64%2CPHN2ZyB3aWR0aD0iMTYiIGhlaWdodD0iMTYiIHZpZXdCb3g9IjAgMCAxNiAxNiIgZmlsbD0ibm9uZSIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj4KPHBhdGggZD0iTTQuOTYxNTYgMS42MDAxSDIuMjQxNTZDMS44ODgxIDEuNjAwMSAxLjYwMTU2IDEuODg2NjQgMS42MDE1NiAyLjI0MDFWNC45NjAxQzEuNjAxNTYgNS4zMTM1NiAxLjg4ODEgNS42MDAxIDIuMjQxNTYgNS42MDAxSDQuOTYxNTZDNS4zMTUwMiA1LjYwMDEgNS42MDE1NiA1LjMxMzU2IDUuNjAxNTYgNC45NjAxVjIuMjQwMUM1LjYwMTU2IDEuODg2NjQgNS4zMTUwMiAxLjYwMDEgNC45NjE1NiAxLjYwMDFaIiBmaWxsPSIjZmZmIi8%2BCjxwYXRoIGQ9Ik00Ljk2MTU2IDEwLjM5OTlIMi4yNDE1NkMxLjg4ODEgMTAuMzk5OSAxLjYwMTU2IDEwLjY4NjQgMS42MDE1NiAxMS4wMzk5VjEzLjc1OTlDMS42MDE1NiAxNC4xMTM0IDEuODg4MSAxNC4zOTk5IDIuMjQxNTYgMTQuMzk5OUg0Ljk2MTU2QzUuMzE1MDIgMTQuMzk5OSA1LjYwMTU2IDE0LjExMzQgNS42MDE1NiAxMy43NTk5VjExLjAzOTlDNS42MDE1NiAxMC42ODY0IDUuMzE1MDIgMTAuMzk5OSA0Ljk2MTU2IDEwLjM5OTlaIiBmaWxsPSIjZmZmIi8%2BCjxwYXRoIGQ9Ik0xMy43NTg0IDEuNjAwMUgxMS4wMzg0QzEwLjY4NSAxLjYwMDEgMTAuMzk4NCAxLjg4NjY0IDEwLjM5ODQgMi4yNDAxVjQuOTYwMUMxMC4zOTg0IDUuMzEzNTYgMTAuNjg1IDUuNjAwMSAxMS4wMzg0IDUuNjAwMUgxMy43NTg0QzE0LjExMTkgNS42MDAxIDE0LjM5ODQgNS4zMTM1NiAxNC4zOTg0IDQuOTYwMVYyLjI0MDFDMTQuMzk4NCAxLjg4NjY0IDE0LjExMTkgMS42MDAxIDEzLjc1ODQgMS42MDAxWiIgZmlsbD0iI2ZmZiIvPgo8cGF0aCBkPSJNNCAxMkwxMiA0TDQgMTJaIiBmaWxsPSIjZmZmIi8%2BCjxwYXRoIGQ9Ik00IDEyTDEyIDQiIHN0cm9rZT0iI2ZmZiIgc3Ryb2tlLXdpZHRoPSIxLjUiIHN0cm9rZS1saW5lY2FwPSJyb3VuZCIvPgo8L3N2Zz4K&logoColor=ffffff)](https://zread.ai/gadzan/weacpx)
7
8
  [![License](https://img.shields.io/npm/l/weacpx?style=flat-square)](./LICENSE)
8
9
 
9
10
  ![weacpx logo](assets/weacpx.jpg)
@@ -3,7 +3,7 @@
3
3
  "type": "acpx-bridge",
4
4
  "sessionInitTimeoutMs": 120000,
5
5
  "permissionMode": "approve-all",
6
- "nonInteractivePermissions": "fail"
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
- constructor(command = "acpx", run = defaultRunner, runSessionCreate = shellSessionCreateRunner, options = {}) {
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 ?? "fail";
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 === "approve-reads" || process.env.WEACPX_BRIDGE_PERMISSION_MODE === "deny-all" ? process.env.WEACPX_BRIDGE_PERMISSION_MODE : "approve-all",
522
- nonInteractivePermissions: process.env.WEACPX_BRIDGE_NON_INTERACTIVE_PERMISSIONS === "deny" ? "deny" : "fail"
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
  }