weacpx 0.1.3 → 0.1.4

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
@@ -28,17 +28,10 @@ bun add -g weacpx
28
28
 
29
29
  第一次使用建议按这个顺序:
30
30
 
31
- 1. 登录微信
32
- 2. 启动服务
31
+ 1. 登录微信 `weacpx login`
32
+ 2. 启动服务 `weacpx start`
33
33
  3. 在微信里创建会话并开始对话
34
34
 
35
- 如果你是全局安装版本:
36
-
37
- ```bash
38
- weacpx login
39
- weacpx start
40
- ```
41
-
42
35
  `weacpx login` 会在终端里显示二维码,使用微信扫描登录。`weacpx start` 启动后,在微信里发:
43
36
 
44
37
  ```text
@@ -57,6 +50,8 @@ weacpx start
57
50
  hello
58
51
  ```
59
52
 
53
+ 如果任务比较长,`weacpx` 会优先把 Agent 的中间回复分段发回微信,而不是一直等到最后一条结果。
54
+
60
55
  如果你是从源码仓库直接使用:
61
56
 
62
57
  ```bash
@@ -75,6 +70,7 @@ bun run dev
75
70
  常用命令:
76
71
 
77
72
  - `weacpx login`
73
+ - `weacpx logout`
78
74
  - `weacpx run`
79
75
  - `weacpx start`
80
76
  - `weacpx status`
@@ -86,6 +82,12 @@ bun run dev
86
82
  - `start` 后台启动
87
83
  - `status` 查看后台状态、PID、配置路径和日志路径
88
84
  - `stop` 停止后台实例
85
+ - `logout` 清除本机已保存的微信登录凭证;如果当前没有已登录账号,会直接提示
86
+
87
+ 说明:
88
+
89
+ - `weacpx logout` 只清理已保存的微信账号凭证
90
+ - 它不会停止当前 daemon,也不会删除 `weacpx` 的 session/state 配置
89
91
 
90
92
  ## 微信中使用说明
91
93
 
@@ -163,6 +165,29 @@ bun run dev
163
165
  - `/use <alias>` 用来切换当前会话
164
166
  - 非 `/` 开头的文本会发送到当前 session
165
167
 
168
+ ### 权限策略
169
+
170
+ `weacpx` 支持直接在微信里查看和切换 `acpx` 的权限策略。
171
+
172
+ | 命令 | 说明 |
173
+ |------|------|
174
+ | `/pm` / `/permission` | 查看当前权限模式 |
175
+ | `/pm set allow` | 切到 `approve-all` |
176
+ | `/pm set read` | 切到 `approve-reads` |
177
+ | `/pm set deny` | 切到 `deny-all` |
178
+ | `/pm auto` | 查看当前非交互策略 |
179
+ | `/pm auto allow` | 切到 `allow` |
180
+ | `/pm auto deny` | 切到 `deny` |
181
+ | `/pm auto fail` | 切到 `fail` |
182
+
183
+ 说明:
184
+
185
+ - `allow` 对应 `approve-all`
186
+ - `read` 对应 `approve-reads`
187
+ - `deny` 对应 `deny-all`
188
+ - `/pm auto ...` 修改的是 `transport.nonInteractivePermissions`
189
+ - 这些命令会把结果写回 `config.json`
190
+
166
191
  ### 推荐工作流
167
192
 
168
193
  新建一个可聊天的会话:
@@ -190,6 +215,21 @@ bun run dev
190
215
  /workspace rm old-repo
191
216
  ```
192
217
 
218
+ ### 微信内置登录相关指令
219
+
220
+ 除了 `weacpx login` / `weacpx logout` 这类 CLI 命令外,微信通道里还支持少量内置 slash 指令:
221
+
222
+ | 命令 | 说明 |
223
+ |------|------|
224
+ | `/clear` | 清除当前聊天会话,上下文重新开始 |
225
+ | `/logout` | 清除当前机器上已保存的所有微信账号凭证 |
226
+
227
+ 说明:
228
+
229
+ - `/logout` 的语义和 CLI 的 `weacpx logout` 一致,都是清凭证
230
+ - 如果当前没有已登录账号,会提示“当前没有已登录的账号”
231
+ - `/logout` 不会停止后台服务,也不会删除 `weacpx` 的工作区、agent、session 状态
232
+
193
233
  ## 配置与运行文件
194
234
 
195
235
  默认文件位置:
@@ -211,6 +251,28 @@ bun run dev
211
251
  - `WEACPX_STATE`
212
252
  - `WEACPX_WEIXIN_SDK`
213
253
 
254
+ ### Transport 权限配置
255
+
256
+ `config.json` 中的 `transport` 支持以下权限字段:
257
+
258
+ ```json
259
+ {
260
+ "transport": {
261
+ "type": "acpx-bridge",
262
+ "sessionInitTimeoutMs": 120000,
263
+ "permissionMode": "approve-all",
264
+ "nonInteractivePermissions": "fail"
265
+ }
266
+ }
267
+ ```
268
+
269
+ 说明:
270
+
271
+ - `permissionMode`: `approve-all`、`approve-reads`、`deny-all`
272
+ - `nonInteractivePermissions`: `allow`、`deny`、`fail`
273
+ - 默认值分别是 `approve-all` 和 `fail`
274
+ - 也可以直接在微信里通过 `/pm` 和 `/pm auto` 修改
275
+
214
276
  ### 日志配置
215
277
 
216
278
  `config.json` 支持可选的 `logging` 配置:
@@ -28,21 +28,6 @@ var __export = (target, all) => {
28
28
  var __esm = (fn, res) => () => (fn && (res = fn(fn = 0)), res);
29
29
  var __require = /* @__PURE__ */ createRequire(import.meta.url);
30
30
 
31
- // src/process/spawn-command.ts
32
- function resolveSpawnCommand(command, args) {
33
- if (SCRIPT_FILE_PATTERN.test(command)) {
34
- return {
35
- command: process.execPath,
36
- args: [command, ...args]
37
- };
38
- }
39
- return { command, args };
40
- }
41
- var SCRIPT_FILE_PATTERN;
42
- var init_spawn_command = __esm(() => {
43
- SCRIPT_FILE_PATTERN = /\.(c|m)?js$/i;
44
- });
45
-
46
31
  // src/transport/prompt-output.ts
47
32
  function getPromptText(result) {
48
33
  const stdoutOutput = extractPromptOutput(result.stdout);
@@ -51,14 +36,14 @@ function getPromptText(result) {
51
36
  }
52
37
  const preferredError = extractPromptFailureMessage(result);
53
38
  if (preferredError) {
54
- throw new Error(preferredError);
39
+ throw new PromptCommandError(preferredError, result);
55
40
  }
56
41
  const stderrOutput = extractPromptOutput(result.stderr);
57
42
  const partialReply = [stdoutOutput, stderrOutput].filter((output) => output.hasAgentMessage && output.text.length > 0).map((output) => sanitizePromptText(output.text)).find((text) => text.length > 0);
58
43
  if (partialReply) {
59
44
  return partialReply;
60
45
  }
61
- throw new Error(`command failed with exit code ${result.code}`);
46
+ throw new PromptCommandError(`command failed with exit code ${result.code}`, result);
62
47
  }
63
48
  function normalizeCommandError(result) {
64
49
  const preferredError = extractPromptFailureMessage(result);
@@ -159,11 +144,43 @@ function extractJsonRpcErrorMessages(output) {
159
144
  return [];
160
145
  });
161
146
  }
147
+ var PromptCommandError;
148
+ var init_prompt_output = __esm(() => {
149
+ PromptCommandError = class PromptCommandError extends Error {
150
+ exitCode;
151
+ stdout;
152
+ stderr;
153
+ constructor(message, result) {
154
+ super(message);
155
+ this.name = "PromptCommandError";
156
+ this.exitCode = result.code;
157
+ this.stdout = result.stdout;
158
+ this.stderr = result.stderr;
159
+ }
160
+ };
161
+ });
162
+
163
+ // src/process/spawn-command.ts
164
+ function resolveSpawnCommand(command, args) {
165
+ if (SCRIPT_FILE_PATTERN.test(command)) {
166
+ return {
167
+ command: process.execPath,
168
+ args: [command, ...args]
169
+ };
170
+ }
171
+ return { command, args };
172
+ }
173
+ var SCRIPT_FILE_PATTERN;
174
+ var init_spawn_command = __esm(() => {
175
+ SCRIPT_FILE_PATTERN = /\.(c|m)?js$/i;
176
+ });
162
177
 
163
178
  // src/bridge/bridge-main.ts
164
179
  import { createInterface } from "node:readline";
165
180
 
166
181
  // src/bridge/bridge-server.ts
182
+ init_prompt_output();
183
+
167
184
  class BridgeServer {
168
185
  runtime;
169
186
  constructor(runtime) {
@@ -186,7 +203,14 @@ class BridgeServer {
186
203
  ok: false,
187
204
  error: {
188
205
  code: "BRIDGE_INTERNAL_ERROR",
189
- message
206
+ message,
207
+ ...error instanceof PromptCommandError ? {
208
+ details: {
209
+ exitCode: error.exitCode,
210
+ stdout: error.stdout,
211
+ stderr: error.stderr
212
+ }
213
+ } : {}
190
214
  }
191
215
  })}
192
216
  `;
@@ -241,6 +265,7 @@ function asOptionalString(value) {
241
265
 
242
266
  // src/bridge/bridge-runtime.ts
243
267
  init_spawn_command();
268
+ init_prompt_output();
244
269
  import { spawn } from "node:child_process";
245
270
  import { fileURLToPath } from "node:url";
246
271
 
@@ -248,10 +273,12 @@ class BridgeRuntime {
248
273
  command;
249
274
  run;
250
275
  runSessionCreate;
251
- constructor(command = "acpx", run = defaultRunner, runSessionCreate = shellSessionCreateRunner) {
276
+ options;
277
+ constructor(command = "acpx", run = defaultRunner, runSessionCreate = shellSessionCreateRunner, options = {}) {
252
278
  this.command = command;
253
279
  this.run = run;
254
280
  this.runSessionCreate = runSessionCreate;
281
+ this.options = options;
255
282
  }
256
283
  async hasSession(input) {
257
284
  const spawnSpec = resolveSpawnCommand(this.command, this.buildSessionArgs(input, [
@@ -314,16 +341,37 @@ class BridgeRuntime {
314
341
  return {};
315
342
  }
316
343
  buildSessionArgs(input, tail) {
344
+ const prefix = [
345
+ "--format",
346
+ "quiet",
347
+ "--cwd",
348
+ input.cwd,
349
+ ...this.buildPermissionArgs()
350
+ ];
317
351
  if (input.agentCommand) {
318
- return ["--format", "quiet", "--cwd", input.cwd, "--agent", input.agentCommand, ...tail];
352
+ return [...prefix, "--agent", input.agentCommand, ...tail];
319
353
  }
320
- return ["--format", "quiet", "--cwd", input.cwd, input.agent, ...tail];
354
+ return [...prefix, input.agent, ...tail];
321
355
  }
322
356
  buildPromptArgs(input, tail) {
357
+ const prefix = [
358
+ "--format",
359
+ "json",
360
+ "--json-strict",
361
+ "--cwd",
362
+ input.cwd,
363
+ ...this.buildPermissionArgs()
364
+ ];
323
365
  if (input.agentCommand) {
324
- return ["--format", "json", "--json-strict", "--cwd", input.cwd, "--agent", input.agentCommand, ...tail];
366
+ return [...prefix, "--agent", input.agentCommand, ...tail];
325
367
  }
326
- return ["--format", "json", "--json-strict", "--cwd", input.cwd, input.agent, ...tail];
368
+ return [...prefix, input.agent, ...tail];
369
+ }
370
+ buildPermissionArgs() {
371
+ const permissionMode = this.options.permissionMode ?? "approve-all";
372
+ const nonInteractivePermissions = this.options.nonInteractivePermissions ?? "fail";
373
+ const modeFlag = permissionMode === "approve-reads" ? "--approve-reads" : permissionMode === "deny-all" ? "--deny-all" : "--approve-all";
374
+ return [modeFlag, "--non-interactive-permissions", nonInteractivePermissions];
327
375
  }
328
376
  }
329
377
  async function defaultRunner(command, args) {
@@ -365,7 +413,10 @@ async function shellSessionCreateRunner(command, args, cwd) {
365
413
  }
366
414
 
367
415
  // src/bridge/bridge-main.ts
368
- var server = new BridgeServer(new BridgeRuntime(process.env.WEACPX_BRIDGE_ACPX_COMMAND ?? "acpx"));
416
+ var server = new BridgeServer(new BridgeRuntime(process.env.WEACPX_BRIDGE_ACPX_COMMAND ?? "acpx", undefined, undefined, {
417
+ 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",
418
+ nonInteractivePermissions: process.env.WEACPX_BRIDGE_NON_INTERACTIVE_PERMISSIONS === "deny" ? "deny" : "fail"
419
+ }));
369
420
  var input = createInterface({
370
421
  input: process.stdin,
371
422
  crlfDelay: Infinity