weacpx 0.1.6 → 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
@@ -1,14 +1,35 @@
1
- # weacpx
1
+ # Weacpx
2
2
 
3
- 使用微信 ClawBot 随时随地通过 `acpx` 控制 Claude CodeCodex Agents。
3
+ 连接微信与 acpx 协议,让 Claude Code / Codex 成为你口袋里的 24/7 伙伴。
4
+
5
+ [![npm](https://img.shields.io/npm/v/weacpx?style=flat-square)](https://www.npmjs.com/package/weacpx)
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)
8
+ [![License](https://img.shields.io/npm/l/weacpx?style=flat-square)](./LICENSE)
9
+
10
+ ![weacpx logo](assets/weacpx.jpg)
11
+
12
+ ## Why Weacpx?
13
+
14
+ 在 Agent-First 的开发模式下,编码任务必须依托顶级 Agents,🙅‍♀️不要通过 openclaw 去开发,现在有一个更好的方案。Weacpx 通过微信提供一个轻量化的远程入口,随时随地通过手机驱动你的顶级 Agents。
15
+
16
+ Weacpx 的核心价值主张很简单:
17
+
18
+ **随时随地访问** — 只要你有微信,就能控制你的 Agent。无需 VPN、Web 界面或复杂的云服务配置。
19
+
20
+ **统一的会话管理** — 通过 acpx 协议,weacpx 让你在微信里管理多个 Agent 会话(Codex、Claude Code 等),就像在本地终端一样。创建、切换、查询状态,全部通过简单的斜杠命令完成,这是其它简单基于 ACP 实现的远控 agent 所不具备的。
21
+
22
+ **轻量守护进程** — weacpx 作为后台守护进程运行,资源占用极低。不用启动一个臃肿 openclaw,不用担心在工作机器上使用会占用资源。启动、停止、查看状态都通过简单的 CLI 命令完成。
23
+
24
+ **权限可控** — 可以即时通过微信修改 agent 的权限,无论是 YOLO 还是只读。
4
25
 
5
26
  ## 安装前准备
6
27
 
7
28
  开始前,至少需要:
8
29
 
9
30
  - Node.js 22+ 或 Bun
10
- - 一个可用的微信登录环境
11
31
  - Claude Code 或 Codex
32
+ - 装了微信的手机
12
33
 
13
34
  > `weacpx` 基于 `weixin-agent-sdk` 与 `acpx` 实现。
14
35
  > 正常情况下,不需要再额外全局安装 `acpx`。
@@ -252,12 +273,6 @@ bun run dev
252
273
  - `~/.weacpx/runtime/stdout.log`
253
274
  - `~/.weacpx/runtime/stderr.log`
254
275
 
255
- 常用环境变量:
256
-
257
- - `WEACPX_CONFIG`
258
- - `WEACPX_STATE`
259
- - `WEACPX_WEIXIN_SDK`
260
-
261
276
  ### Transport 权限配置
262
277
 
263
278
  `config.json` 中的 `transport` 支持以下权限字段:
@@ -304,36 +319,6 @@ bun run dev
304
319
 
305
320
  ## 注意事项
306
321
 
307
- ### `dry-run`
308
-
309
- `dry-run` 会复用同一套 router、session service、transport,只是把微信消息换成终端输入,适合本地排查。
310
-
311
- 示例:
312
-
313
- ```bash
314
- bun run dry-run --chat-key wx:test -- \
315
- "/agent add codex" \
316
- "/ws new backend -d /absolute/path/to/backend" \
317
- "/ss new demo -a codex --ws backend" \
318
- "/status"
319
- ```
320
-
321
- ### 如果 `/ss new` 失败
322
-
323
- 当前最常见的问题仍然是底层 `acpx` named session 的运行时恢复,不一定是 `weacpx` 本身的逻辑问题。
324
-
325
- 可以先在本地创建一个 named session,再在微信里 attach:
326
-
327
- ```bash
328
- ./node_modules/.bin/acpx --verbose --cwd /absolute/workspace/path codex sessions new --name existing-demo
329
- ```
330
-
331
- 然后在微信里:
332
-
333
- ```text
334
- /ss attach demo -a codex --ws backend --name existing-demo
335
- ```
336
-
337
322
  ### Adapter mode 参考
338
323
 
339
324
  `acpx set-mode` / 计划中的 `/mode <id>` 本质上都是给底层 ACP session 发送 `session/set_mode`。
@@ -354,6 +339,22 @@ bun run dry-run --chat-key wx:test -- \
354
339
  - 对其他 adapter,不要在 `weacpx` 里写死候选值;最好把 `/mode <id>` 设计成透传,由 adapter 自己决定是否接受。
355
340
  - 如果某个 adapter 后续补充了官方 mode 文档,再把它们补进这里。
356
341
 
342
+ ### 如果 `/ss new` 失败
343
+
344
+ 当前最常见的问题仍然是底层 `acpx` named session 的运行时恢复,不一定是 `weacpx` 本身的逻辑问题。
345
+
346
+ 可以先在本地创建一个 named session,再在微信里 attach:
347
+
348
+ ```bash
349
+ ./node_modules/.bin/acpx --verbose --cwd /absolute/workspace/path codex sessions new --name existing-demo
350
+ ```
351
+
352
+ 然后在微信里:
353
+
354
+ ```text
355
+ /ss attach demo -a codex --ws backend --name existing-demo
356
+ ```
357
+
357
358
  ## 更多文档
358
359
 
359
360
  - 配置参考:[docs/config-reference.md](./docs/config-reference.md)
@@ -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,21 +203,102 @@ 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
 
278
+ class BridgeInvalidRequestError extends Error {
279
+ }
280
+ var BRIDGE_METHODS = new Set([
281
+ "ping",
282
+ "shutdown",
283
+ "updatePermissionPolicy",
284
+ "hasSession",
285
+ "ensureSession",
286
+ "prompt",
287
+ "setMode",
288
+ "cancel"
289
+ ]);
290
+
202
291
  class BridgeServer {
203
292
  runtime;
204
293
  constructor(runtime) {
205
294
  this.runtime = runtime;
206
295
  }
207
- async handleLine(line) {
208
- const request = JSON.parse(line);
296
+ async handleLine(line, writeLine) {
297
+ let requestId = extractRequestId(line);
209
298
  try {
210
- const result = await this.dispatch(request.method, request.params);
299
+ const request = parseBridgeRequest(line);
300
+ requestId = request.id;
301
+ const result = await this.dispatch(request.id, request.method, request.params, writeLine);
211
302
  return `${JSON.stringify({
212
303
  id: request.id,
213
304
  ok: true,
@@ -217,10 +308,10 @@ class BridgeServer {
217
308
  } catch (error) {
218
309
  const message = error instanceof Error ? error.message : String(error);
219
310
  return `${JSON.stringify({
220
- id: request.id,
311
+ id: requestId,
221
312
  ok: false,
222
313
  error: {
223
- code: "BRIDGE_INTERNAL_ERROR",
314
+ code: error instanceof BridgeInvalidRequestError ? "BRIDGE_INVALID_REQUEST" : "BRIDGE_INTERNAL_ERROR",
224
315
  message,
225
316
  ...error instanceof PromptCommandError ? {
226
317
  details: {
@@ -234,54 +325,132 @@ class BridgeServer {
234
325
  `;
235
326
  }
236
327
  }
237
- async dispatch(method, params) {
328
+ async dispatch(requestId, method, params, writeLine) {
238
329
  switch (method) {
239
330
  case "ping":
240
331
  return {};
241
332
  case "shutdown":
242
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
+ });
243
339
  case "hasSession":
244
340
  return await this.runtime.hasSession({
245
- agent: String(params.agent),
341
+ agent: requireString(params, "agent"),
246
342
  agentCommand: asOptionalString(params.agentCommand),
247
- cwd: String(params.cwd),
248
- name: String(params.name)
343
+ cwd: requireString(params, "cwd"),
344
+ name: requireString(params, "name")
249
345
  });
250
346
  case "ensureSession":
251
347
  return await this.runtime.ensureSession({
252
- agent: String(params.agent),
348
+ agent: requireString(params, "agent"),
253
349
  agentCommand: asOptionalString(params.agentCommand),
254
- cwd: String(params.cwd),
255
- name: String(params.name)
350
+ cwd: requireString(params, "cwd"),
351
+ name: requireString(params, "name")
256
352
  });
257
353
  case "prompt":
258
354
  return await this.runtime.prompt({
259
- agent: String(params.agent),
355
+ agent: requireString(params, "agent"),
260
356
  agentCommand: asOptionalString(params.agentCommand),
261
- cwd: String(params.cwd),
262
- name: String(params.name),
263
- text: String(params.text)
357
+ cwd: requireString(params, "cwd"),
358
+ name: requireString(params, "name"),
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
+ }
264
368
  });
265
369
  case "setMode":
266
370
  return await this.runtime.setMode({
267
- agent: String(params.agent),
371
+ agent: requireString(params, "agent"),
268
372
  agentCommand: asOptionalString(params.agentCommand),
269
- cwd: String(params.cwd),
270
- name: String(params.name),
271
- modeId: String(params.modeId)
373
+ cwd: requireString(params, "cwd"),
374
+ name: requireString(params, "name"),
375
+ modeId: requireString(params, "modeId")
272
376
  });
273
377
  case "cancel":
274
378
  return await this.runtime.cancel({
275
- agent: String(params.agent),
379
+ agent: requireString(params, "agent"),
276
380
  agentCommand: asOptionalString(params.agentCommand),
277
- cwd: String(params.cwd),
278
- name: String(params.name)
381
+ cwd: requireString(params, "cwd"),
382
+ name: requireString(params, "name")
279
383
  });
280
384
  default:
281
385
  throw new Error(`unsupported bridge method: ${method}`);
282
386
  }
283
387
  }
284
388
  }
389
+ function extractRequestId(line) {
390
+ try {
391
+ const raw = JSON.parse(line);
392
+ if (!raw || typeof raw !== "object" || Array.isArray(raw)) {
393
+ return "unknown";
394
+ }
395
+ const id = raw.id;
396
+ return typeof id === "string" && id.length > 0 ? id : "unknown";
397
+ } catch {
398
+ return "unknown";
399
+ }
400
+ }
401
+ function parseBridgeRequest(line) {
402
+ let raw;
403
+ try {
404
+ raw = JSON.parse(line);
405
+ } catch {
406
+ throw new BridgeInvalidRequestError("request must be valid JSON");
407
+ }
408
+ if (!raw || typeof raw !== "object" || Array.isArray(raw)) {
409
+ throw new BridgeInvalidRequestError("request must be a JSON object");
410
+ }
411
+ const request = raw;
412
+ const id = request.id;
413
+ const method = request.method;
414
+ const params = request.params;
415
+ if (typeof id !== "string" || id.length === 0) {
416
+ throw new BridgeInvalidRequestError("id must be a non-empty string");
417
+ }
418
+ if (typeof method !== "string" || method.length === 0) {
419
+ throw new BridgeInvalidRequestError("method must be a non-empty string");
420
+ }
421
+ if (!BRIDGE_METHODS.has(method)) {
422
+ throw new BridgeInvalidRequestError(`unsupported bridge method: ${method}`);
423
+ }
424
+ if (!params || typeof params !== "object" || Array.isArray(params)) {
425
+ throw new BridgeInvalidRequestError("params must be an object");
426
+ }
427
+ return {
428
+ id,
429
+ method,
430
+ params
431
+ };
432
+ }
433
+ function requireString(params, key) {
434
+ const value = params[key];
435
+ if (typeof value !== "string" || value.length === 0) {
436
+ throw new BridgeInvalidRequestError(`${key} must be a non-empty string`);
437
+ }
438
+ return value;
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
+ }
285
454
  function asOptionalString(value) {
286
455
  if (typeof value !== "string" || value.length === 0) {
287
456
  return;
@@ -300,11 +469,18 @@ class BridgeRuntime {
300
469
  run;
301
470
  runSessionCreate;
302
471
  options;
303
- constructor(command = "acpx", run = defaultRunner, runSessionCreate = shellSessionCreateRunner, options = {}) {
472
+ runPromptCommand;
473
+ constructor(command = "acpx", run = defaultRunner, runSessionCreate = shellSessionCreateRunner, options = {}, runPromptCommand = defaultPromptRunner) {
304
474
  this.command = command;
305
475
  this.run = run;
306
476
  this.runSessionCreate = runSessionCreate;
307
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 {};
308
484
  }
309
485
  async hasSession(input) {
310
486
  const spawnSpec = resolveSpawnCommand(this.command, this.buildSessionArgs(input, [
@@ -338,14 +514,14 @@ class BridgeRuntime {
338
514
  }
339
515
  return {};
340
516
  }
341
- async prompt(input) {
517
+ async prompt(input, onEvent) {
342
518
  const spawnSpec = resolveSpawnCommand(this.command, this.buildPromptArgs(input, [
343
519
  "prompt",
344
520
  "-s",
345
521
  input.name,
346
522
  input.text
347
523
  ]));
348
- 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);
349
525
  return { text: getPromptText(result) };
350
526
  }
351
527
  async setMode(input) {
@@ -408,7 +584,7 @@ class BridgeRuntime {
408
584
  }
409
585
  buildPermissionArgs() {
410
586
  const permissionMode = this.options.permissionMode ?? "approve-all";
411
- const nonInteractivePermissions = this.options.nonInteractivePermissions ?? "fail";
587
+ const nonInteractivePermissions = this.options.nonInteractivePermissions ?? "deny";
412
588
  const modeFlag = permissionMode === "approve-reads" ? "--approve-reads" : permissionMode === "deny-all" ? "--deny-all" : "--approve-all";
413
589
  return [modeFlag, "--non-interactive-permissions", nonInteractivePermissions];
414
590
  }
@@ -430,6 +606,62 @@ async function defaultRunner(command, args) {
430
606
  });
431
607
  });
432
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
+ }
433
665
  async function shellSessionCreateRunner(command, args, cwd) {
434
666
  const helperPath = fileURLToPath(new URL("../../scripts/acpx-session-new-helper.sh", import.meta.url));
435
667
  return await new Promise((resolve, reject) => {
@@ -453,14 +685,16 @@ async function shellSessionCreateRunner(command, args, cwd) {
453
685
 
454
686
  // src/bridge/bridge-main.ts
455
687
  var server = new BridgeServer(new BridgeRuntime(process.env.WEACPX_BRIDGE_ACPX_COMMAND ?? "acpx", undefined, undefined, {
456
- 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",
457
- 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)
458
690
  }));
459
691
  var input = createInterface({
460
692
  input: process.stdin,
461
693
  crlfDelay: Infinity
462
694
  });
463
695
  for await (const line of input) {
464
- const response = await server.handleLine(line);
696
+ const response = await server.handleLine(line, (chunk) => {
697
+ process.stdout.write(chunk);
698
+ });
465
699
  process.stdout.write(response);
466
700
  }