weacpx 0.2.0 → 0.2.1
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 +87 -9
- package/dist/bridge/bridge-main.js +56 -7
- package/dist/cli.js +1433 -366
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -71,7 +71,9 @@ bun add -g weacpx
|
|
|
71
71
|
hello
|
|
72
72
|
```
|
|
73
73
|
|
|
74
|
-
如果任务比较长,`weacpx`
|
|
74
|
+
如果任务比较长,`weacpx` 在支持流式中间回复的 transport(当前主要是 `acpx-cli`)下,会优先把 Agent 的中间回复分段发回微信,而不是一直等到最后一条结果。
|
|
75
|
+
|
|
76
|
+
如果你更想要“一次性只回最终结果”,可以配置全局默认 `wechat.replyMode`,或在当前会话里用 `/replymode final` 临时覆盖。
|
|
75
77
|
|
|
76
78
|
如果你是从源码仓库直接使用:
|
|
77
79
|
|
|
@@ -96,14 +98,55 @@ bun run dev
|
|
|
96
98
|
- `weacpx start`
|
|
97
99
|
- `weacpx status`
|
|
98
100
|
- `weacpx stop`
|
|
101
|
+
- `weacpx doctor`
|
|
102
|
+
- `weacpx version`
|
|
99
103
|
|
|
100
|
-
|
|
104
|
+
其他说明:
|
|
101
105
|
|
|
102
106
|
- `run` 前台运行,适合调试
|
|
103
107
|
- `start` 后台启动
|
|
104
108
|
- `status` 查看后台状态、PID、配置路径和日志路径
|
|
105
109
|
- `stop` 停止后台实例
|
|
106
110
|
- `logout` 清除本机已保存的微信登录凭证;如果当前没有已登录账号,会直接提示
|
|
111
|
+
- `doctor` 运行诊断,默认检查 config / runtime / daemon / wechat / acpx / bridge
|
|
112
|
+
- `version` 输出当前安装的 `weacpx` 版本号,可用于排查环境或确认升级是否生效
|
|
113
|
+
|
|
114
|
+
### doctor 诊断
|
|
115
|
+
|
|
116
|
+
`weacpx doctor` 用来快速检查本机环境是否能正常运行。
|
|
117
|
+
|
|
118
|
+
```bash
|
|
119
|
+
weacpx doctor
|
|
120
|
+
weacpx doctor --verbose
|
|
121
|
+
weacpx doctor --smoke
|
|
122
|
+
weacpx doctor --smoke --agent codex --workspace backend
|
|
123
|
+
```
|
|
124
|
+
|
|
125
|
+
说明:
|
|
126
|
+
|
|
127
|
+
- `--verbose` 会展开每个检查的技术细节,方便定位问题
|
|
128
|
+
- `--smoke` 会额外执行一次真实 transport 级别的最小 prompt 检查
|
|
129
|
+
- `--agent` / `--workspace` 只影响 `--smoke`,不会改变默认诊断检查
|
|
130
|
+
- `--smoke` 可能留下一个临时的底层 `acpx` session;这是为了换取真实链路验证能力
|
|
131
|
+
- 如果不传 `--smoke`,相关 smoke 检查会被标记为 `SKIP`
|
|
132
|
+
|
|
133
|
+
### version 查看版本
|
|
134
|
+
|
|
135
|
+
`weacpx version` 用来输出当前安装的 CLI 版本号。
|
|
136
|
+
|
|
137
|
+
```bash
|
|
138
|
+
weacpx version
|
|
139
|
+
weacpx --version
|
|
140
|
+
weacpx -v
|
|
141
|
+
```
|
|
142
|
+
|
|
143
|
+
说明:
|
|
144
|
+
|
|
145
|
+
- 三种写法都会输出同一个版本号
|
|
146
|
+
- 适合在升级后确认当前生效的是哪个版本
|
|
147
|
+
- 排查用户环境问题时,建议先附上这里输出的版本信息
|
|
148
|
+
|
|
149
|
+
### logout 退出登录
|
|
107
150
|
|
|
108
151
|
说明:
|
|
109
152
|
|
|
@@ -170,14 +213,18 @@ bun run dev
|
|
|
170
213
|
| 命令 | 说明 |
|
|
171
214
|
|------|------|
|
|
172
215
|
| `/sessions` / `/session` / `/ss` | 查看当前已添加的会话 |
|
|
173
|
-
| `/ss <agent> -d <path
|
|
174
|
-
| `/ss new <agent> -d <path
|
|
216
|
+
| `/ss <agent> (-d <path> | --ws <name>)` | 新建会话;传 `-d` 时按目录自动创建或复用 workspace,传 `--ws` 时复用已注册 workspace |
|
|
217
|
+
| `/ss new <agent> (-d <path> | --ws <name>)` | 强制新建会话;`--ws` 只复用已注册 workspace |
|
|
175
218
|
| `/ss new <alias> -a <name> --ws <name>` | 强制新建会话,并指定 agent 和 workspace |
|
|
176
219
|
| `/ss attach <alias> -a <name> --ws <name> --name <transport-session>` | 恢复已存在的会话 |
|
|
177
220
|
| `/use <alias>` | 切换当前会话 |
|
|
178
221
|
| `/status` | 查看当前会话状态 |
|
|
179
222
|
| `/mode` | 查看当前会话已保存的 mode |
|
|
180
223
|
| `/mode <id>` | 设置当前会话 mode,例如 `/mode plan` |
|
|
224
|
+
| `/replymode` | 查看当前会话的回复输出模式(全局默认 / 当前覆盖 / 实际生效) |
|
|
225
|
+
| `/replymode stream` | 当前逻辑会话使用流式回复 |
|
|
226
|
+
| `/replymode final` | 当前逻辑会话只发送最终文本结果 |
|
|
227
|
+
| `/replymode reset` | 清除当前逻辑会话覆盖,回退到全局默认 |
|
|
181
228
|
| `/session reset` | 重置当前会话上下文,保留 alias/agent/workspace,但重新绑定到一个新的后端 session |
|
|
182
229
|
| `/clear` | `/session reset` 的快捷别名 |
|
|
183
230
|
| `/cancel` | 取消当前会话 |
|
|
@@ -186,13 +233,45 @@ bun run dev
|
|
|
186
233
|
说明:
|
|
187
234
|
|
|
188
235
|
- `/ss <agent> -d <path>` 是最常用入口,会自动按目录名推导并创建或复用 workspace,再创建或复用 session
|
|
189
|
-
- `/ss
|
|
236
|
+
- `/ss <agent> --ws <name>` 会直接复用已注册 workspace,再创建或复用 session
|
|
237
|
+
- `/ss new <agent> (-d <path> | --ws <name>)` 表示强制新建 session
|
|
190
238
|
- `/use <alias>` 用来切换当前会话
|
|
191
239
|
- `/mode` 会显示当前逻辑会话里保存的 mode;如果还没设置过,会显示“未设置”
|
|
192
240
|
- `/mode <id>` 会把 mode 透传给底层 `acpx set-mode`,成功后再写回当前逻辑会话
|
|
241
|
+
- `/replymode` 修改的是**当前逻辑会话**的 reply mode override,不是底层 transport session 的全局属性
|
|
242
|
+
- `wechat.replyMode` 是全局默认值;`/replymode reset` 会回退到这个默认值
|
|
243
|
+
- `final` 只影响微信侧是否实时发送文本流式片段,不改变 acpx transport 本身的生成方式
|
|
193
244
|
- `/session reset` 和 `/clear` 会保留当前逻辑会话名,但重新创建一个新的后端 session,从空上下文重新开始
|
|
194
245
|
- 非 `/` 开头的文本会发送到当前 session
|
|
195
246
|
|
|
247
|
+
### 配置命令
|
|
248
|
+
|
|
249
|
+
`/config` 用来查看和修改一小部分**受支持的配置字段**,不是任意 JSON 编辑器。
|
|
250
|
+
|
|
251
|
+
| 命令 | 说明 |
|
|
252
|
+
|------|------|
|
|
253
|
+
| `/config` | 查看当前支持通过微信修改的配置路径 |
|
|
254
|
+
| `/config set <path> <value>` | 修改一个受支持的配置值 |
|
|
255
|
+
|
|
256
|
+
常见示例:
|
|
257
|
+
|
|
258
|
+
```text
|
|
259
|
+
/config
|
|
260
|
+
/config set wechat.replyMode final
|
|
261
|
+
/config set logging.level debug
|
|
262
|
+
/config set transport.permissionMode approve-reads
|
|
263
|
+
/config set workspaces.backend.description backend repo
|
|
264
|
+
```
|
|
265
|
+
|
|
266
|
+
说明:
|
|
267
|
+
|
|
268
|
+
- `/config` 只允许修改白名单字段,不支持任意路径写入
|
|
269
|
+
- `agents.<name>.*` / `workspaces.<name>.*` 这类动态路径要求目标已经存在,不会自动创建
|
|
270
|
+
- `/config set wechat.replyMode final` 修改的是**全局默认回复模式**
|
|
271
|
+
- `/replymode final` 修改的是**当前逻辑会话覆盖**
|
|
272
|
+
- 成功修改后会立即写回 `~/.weacpx/config.json`
|
|
273
|
+
- 更完整的边界和支持字段,请参考 [docs/config-command.md](./docs/config-command.md)
|
|
274
|
+
|
|
196
275
|
### 权限策略
|
|
197
276
|
|
|
198
277
|
`weacpx` 支持直接在微信里查看和切换 `acpx` 的权限策略。
|
|
@@ -204,7 +283,6 @@ bun run dev
|
|
|
204
283
|
| `/pm set read` | 切到 `approve-reads` |
|
|
205
284
|
| `/pm set deny` | 切到 `deny-all` |
|
|
206
285
|
| `/pm auto` | 查看当前非交互策略 |
|
|
207
|
-
| `/pm auto allow` | 切到 `allow` |
|
|
208
286
|
| `/pm auto deny` | 切到 `deny` |
|
|
209
287
|
| `/pm auto fail` | 切到 `fail` |
|
|
210
288
|
|
|
@@ -283,7 +361,7 @@ bun run dev
|
|
|
283
361
|
"type": "acpx-bridge",
|
|
284
362
|
"sessionInitTimeoutMs": 120000,
|
|
285
363
|
"permissionMode": "approve-all",
|
|
286
|
-
"nonInteractivePermissions": "
|
|
364
|
+
"nonInteractivePermissions": "deny"
|
|
287
365
|
}
|
|
288
366
|
}
|
|
289
367
|
```
|
|
@@ -291,8 +369,8 @@ bun run dev
|
|
|
291
369
|
说明:
|
|
292
370
|
|
|
293
371
|
- `permissionMode`: `approve-all`、`approve-reads`、`deny-all`
|
|
294
|
-
- `nonInteractivePermissions`: `
|
|
295
|
-
- 默认值分别是 `approve-all` 和 `
|
|
372
|
+
- `nonInteractivePermissions`: `deny`、`fail`
|
|
373
|
+
- 默认值分别是 `approve-all` 和 `deny`
|
|
296
374
|
- 也可以直接在微信里通过 `/pm` 和 `/pm auto` 修改
|
|
297
375
|
|
|
298
376
|
### 日志配置
|
|
@@ -44,6 +44,7 @@ var __export = (target, all) => {
|
|
|
44
44
|
});
|
|
45
45
|
};
|
|
46
46
|
var __esm = (fn, res) => () => (fn && (res = fn(fn = 0)), res);
|
|
47
|
+
var __promiseAll = (args) => Promise.all(args);
|
|
47
48
|
var __require = /* @__PURE__ */ createRequire(import.meta.url);
|
|
48
49
|
|
|
49
50
|
// src/transport/acpx-bridge/acpx-bridge-protocol.ts
|
|
@@ -461,8 +462,10 @@ function asOptionalString(value) {
|
|
|
461
462
|
// src/bridge/bridge-runtime.ts
|
|
462
463
|
init_spawn_command();
|
|
463
464
|
init_prompt_output();
|
|
465
|
+
import { copyFile, readdir } from "node:fs/promises";
|
|
466
|
+
import { homedir } from "node:os";
|
|
467
|
+
import { join } from "node:path";
|
|
464
468
|
import { spawn } from "node:child_process";
|
|
465
|
-
import { fileURLToPath } from "node:url";
|
|
466
469
|
|
|
467
470
|
class BridgeRuntime {
|
|
468
471
|
command;
|
|
@@ -508,11 +511,18 @@ class BridgeRuntime {
|
|
|
508
511
|
return {};
|
|
509
512
|
}
|
|
510
513
|
const createSpec = resolveSpawnCommand(this.command, this.buildSessionArgs(input, ["sessions", "new", "--name", input.name]));
|
|
511
|
-
const
|
|
512
|
-
if (
|
|
513
|
-
|
|
514
|
+
const created = await this.runSessionCreate(createSpec.command, createSpec.args, input.cwd);
|
|
515
|
+
if (created.code === 0) {
|
|
516
|
+
return {};
|
|
514
517
|
}
|
|
515
|
-
|
|
518
|
+
const output = created.stderr || created.stdout || "";
|
|
519
|
+
if (output.includes("EPERM") && await tryRepairAcpxSessionIndex()) {
|
|
520
|
+
const repaired = await this.run(existingSpec.command, existingSpec.args);
|
|
521
|
+
if (repaired.code === 0) {
|
|
522
|
+
return {};
|
|
523
|
+
}
|
|
524
|
+
}
|
|
525
|
+
throw new Error(output || ensured.stderr || ensured.stdout || "failed to create session");
|
|
516
526
|
}
|
|
517
527
|
async prompt(input, onEvent) {
|
|
518
528
|
const spawnSpec = resolveSpawnCommand(this.command, this.buildPromptArgs(input, [
|
|
@@ -663,9 +673,9 @@ async function defaultPromptRunner(command, args, onEvent) {
|
|
|
663
673
|
return await runStreamingPrompt(command, args, onEvent);
|
|
664
674
|
}
|
|
665
675
|
async function shellSessionCreateRunner(command, args, cwd) {
|
|
666
|
-
const helperPath = fileURLToPath(new URL("../../scripts/acpx-session-new-helper.sh", import.meta.url));
|
|
667
676
|
return await new Promise((resolve, reject) => {
|
|
668
|
-
const child = spawn(
|
|
677
|
+
const child = spawn(command, args, {
|
|
678
|
+
cwd,
|
|
669
679
|
stdio: ["ignore", "pipe", "pipe"]
|
|
670
680
|
});
|
|
671
681
|
let stdout = "";
|
|
@@ -682,6 +692,45 @@ async function shellSessionCreateRunner(command, args, cwd) {
|
|
|
682
692
|
});
|
|
683
693
|
});
|
|
684
694
|
}
|
|
695
|
+
async function tryRepairAcpxSessionIndex() {
|
|
696
|
+
if (process.platform !== "win32") {
|
|
697
|
+
return false;
|
|
698
|
+
}
|
|
699
|
+
const home = process.env.HOME ?? process.env.USERPROFILE ?? homedir();
|
|
700
|
+
if (!home) {
|
|
701
|
+
return false;
|
|
702
|
+
}
|
|
703
|
+
const sessionsDir = join(home, ".acpx", "sessions");
|
|
704
|
+
const indexPath = join(sessionsDir, "index.json");
|
|
705
|
+
let files;
|
|
706
|
+
try {
|
|
707
|
+
files = await readdir(sessionsDir);
|
|
708
|
+
} catch {
|
|
709
|
+
return false;
|
|
710
|
+
}
|
|
711
|
+
const tmpFiles = files.filter((f) => f.startsWith("index.json.") && f.endsWith(".tmp"));
|
|
712
|
+
if (tmpFiles.length === 0) {
|
|
713
|
+
return false;
|
|
714
|
+
}
|
|
715
|
+
let latestTmp = "";
|
|
716
|
+
let latestTime = 0;
|
|
717
|
+
for (const f of tmpFiles) {
|
|
718
|
+
const match = f.match(/^index\.json\.\d+\.(\d+)\.tmp$/);
|
|
719
|
+
if (match && Number(match[1]) > latestTime) {
|
|
720
|
+
latestTime = Number(match[1]);
|
|
721
|
+
latestTmp = f;
|
|
722
|
+
}
|
|
723
|
+
}
|
|
724
|
+
if (!latestTmp) {
|
|
725
|
+
return false;
|
|
726
|
+
}
|
|
727
|
+
try {
|
|
728
|
+
await copyFile(join(sessionsDir, latestTmp), indexPath);
|
|
729
|
+
return true;
|
|
730
|
+
} catch {
|
|
731
|
+
return false;
|
|
732
|
+
}
|
|
733
|
+
}
|
|
685
734
|
|
|
686
735
|
// src/bridge/bridge-main.ts
|
|
687
736
|
var server = new BridgeServer(new BridgeRuntime(process.env.WEACPX_BRIDGE_ACPX_COMMAND ?? "acpx", undefined, undefined, {
|