weacpx 0.1.3 → 0.1.5

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
 
@@ -153,16 +155,42 @@ bun run dev
153
155
  | `/ss attach <alias> -a <name> --ws <name> --name <transport-session>` | 恢复已存在的会话 |
154
156
  | `/use <alias>` | 切换当前会话 |
155
157
  | `/status` | 查看当前会话状态 |
158
+ | `/session reset` | 重置当前会话上下文,保留 alias/agent/workspace,但重新绑定到一个新的后端 session |
159
+ | `/clear` | `/session reset` 的快捷别名 |
156
160
  | `/cancel` | 取消当前会话 |
157
- | `/stop` | 停止当前会话 |
161
+ | `/stop` | `/cancel` 的别名,用于取消当前会话 |
158
162
 
159
163
  说明:
160
164
 
161
165
  - `/ss <agent> -d <path>` 是最常用入口,会自动按目录名推导并创建或复用 workspace,再创建或复用 session
162
166
  - `/ss new <agent> -d <path>` 表示强制新建 session
163
167
  - `/use <alias>` 用来切换当前会话
168
+ - `/session reset` 和 `/clear` 会保留当前逻辑会话名,但重新创建一个新的后端 session,从空上下文重新开始
164
169
  - 非 `/` 开头的文本会发送到当前 session
165
170
 
171
+ ### 权限策略
172
+
173
+ `weacpx` 支持直接在微信里查看和切换 `acpx` 的权限策略。
174
+
175
+ | 命令 | 说明 |
176
+ |------|------|
177
+ | `/pm` / `/permission` | 查看当前权限模式 |
178
+ | `/pm set allow` | 切到 `approve-all` |
179
+ | `/pm set read` | 切到 `approve-reads` |
180
+ | `/pm set deny` | 切到 `deny-all` |
181
+ | `/pm auto` | 查看当前非交互策略 |
182
+ | `/pm auto allow` | 切到 `allow` |
183
+ | `/pm auto deny` | 切到 `deny` |
184
+ | `/pm auto fail` | 切到 `fail` |
185
+
186
+ 说明:
187
+
188
+ - `allow` 对应 `approve-all`
189
+ - `read` 对应 `approve-reads`
190
+ - `deny` 对应 `deny-all`
191
+ - `/pm auto ...` 修改的是 `transport.nonInteractivePermissions`
192
+ - 这些命令会把结果写回 `config.json`
193
+
166
194
  ### 推荐工作流
167
195
 
168
196
  新建一个可聊天的会话:
@@ -190,6 +218,21 @@ bun run dev
190
218
  /workspace rm old-repo
191
219
  ```
192
220
 
221
+ ### 微信内置登录相关指令
222
+
223
+ 除了 `weacpx login` / `weacpx logout` 这类 CLI 命令外,微信通道里还支持少量内置 slash 指令:
224
+
225
+ | 命令 | 说明 |
226
+ |------|------|
227
+ | `/clear` | 重置当前聊天绑定的会话上下文,效果等同于 `/session reset` |
228
+ | `/logout` | 清除当前机器上已保存的所有微信账号凭证 |
229
+
230
+ 说明:
231
+
232
+ - `/logout` 的语义和 CLI 的 `weacpx logout` 一致,都是清凭证
233
+ - 如果当前没有已登录账号,会提示“当前没有已登录的账号”
234
+ - `/logout` 不会停止后台服务,也不会删除 `weacpx` 的工作区、agent、session 状态
235
+
193
236
  ## 配置与运行文件
194
237
 
195
238
  默认文件位置:
@@ -211,6 +254,28 @@ bun run dev
211
254
  - `WEACPX_STATE`
212
255
  - `WEACPX_WEIXIN_SDK`
213
256
 
257
+ ### Transport 权限配置
258
+
259
+ `config.json` 中的 `transport` 支持以下权限字段:
260
+
261
+ ```json
262
+ {
263
+ "transport": {
264
+ "type": "acpx-bridge",
265
+ "sessionInitTimeoutMs": 120000,
266
+ "permissionMode": "approve-all",
267
+ "nonInteractivePermissions": "fail"
268
+ }
269
+ }
270
+ ```
271
+
272
+ 说明:
273
+
274
+ - `permissionMode`: `approve-all`、`approve-reads`、`deny-all`
275
+ - `nonInteractivePermissions`: `allow`、`deny`、`fail`
276
+ - 默认值分别是 `approve-all` 和 `fail`
277
+ - 也可以直接在微信里通过 `/pm` 和 `/pm auto` 修改
278
+
214
279
  ### 日志配置
215
280
 
216
281
  `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