weacpx 0.4.4 → 0.4.6

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
@@ -11,7 +11,7 @@
11
11
 
12
12
  ## 这是什么
13
13
 
14
- `weacpx` 是一个可以通过微信、飞书或元宝直接控制 Codex / Claude Code / Gemini / OpenCode 的工具。它把聊天消息通过 `acpx` 连接到 Agent CLI 会话上,让你直接在手机里:
14
+ `weacpx` 是一个可以通过微信、飞书或元宝直接控制 Codex / Claude Code / Gemini / OpenCode 等 ACP Agent 的工具。它把聊天消息通过 `acpx` 连接到 Agent CLI 会话上,让你直接在手机里:
15
15
 
16
16
  - 新建和切换会话
17
17
  - 让 Agent 继续在指定项目目录里工作
@@ -34,7 +34,7 @@
34
34
  开始前,你至少需要:
35
35
 
36
36
  - Node.js 22+ 或 Bun
37
- - 已可用的 Codex / Claude Code / Gemini / OpenCode
37
+ - 已可用的 Codex / Claude Code / Gemini / OpenCode 等你要使用的 Agent CLI
38
38
  - 一台装了微信、飞书或元宝的手机
39
39
 
40
40
  > 微信频道基于 `weixin-agent-sdk` 工作,飞书频道使用飞书自建应用凭据,元宝频道使用 `appKey` / `appSecret`;底层 Agent 会话由 `acpx` 驱动。正常情况下,你不需要额外全局安装 `acpx`。
@@ -169,13 +169,16 @@ weacpx restart
169
169
  | `weacpx status` | 查看后台状态、PID、配置路径、日志路径 |
170
170
  | `weacpx stop` | 停止后台实例 |
171
171
  | `weacpx restart` | 重启后台实例,让频道配置变更生效 |
172
- | `weacpx update [--all|<name>]` | 检查并更新 weacpx 与已安装插件;安装了插件时会交互式选择更新项 |
172
+ | `weacpx update [--all\|<name>]` | 检查并更新 weacpx 与已安装插件;安装了插件时会交互式选择更新项 |
173
173
  | `weacpx channel list` | 查看已配置的消息频道 |
174
174
  | `weacpx plugin known` | 查看官方插件清单(飞书/元宝包名) |
175
175
  | `weacpx plugin add @ganglion/weacpx-channel-feishu && weacpx channel add feishu` | 安装并添加飞书频道,会提示输入飞书应用凭据 |
176
176
  | `weacpx plugin add @ganglion/weacpx-channel-yuanbao && weacpx channel add yuanbao` | 安装并添加元宝频道,会提示输入元宝 appKey/appSecret |
177
177
  | `weacpx doctor` | 运行环境诊断 |
178
178
  | `weacpx version` | 查看当前版本 |
179
+ | `weacpx agent list` | 查看本机已注册的 agent |
180
+ | `weacpx agent add <name>` | 从内置模板添加 agent;已存在且配置不同的同名 agent 不会被覆盖 |
181
+ | `weacpx agent rm <name>` | 删除 agent |
179
182
  | `weacpx workspace list` | 查看本机已注册的 workspace |
180
183
  | `weacpx workspace add [name]` | 把当前目录注册成 workspace;不传 `name` 时使用当前目录名 |
181
184
  | `weacpx workspace rm <name>` | 删除 workspace |
@@ -223,6 +226,26 @@ weacpx ws rm frontend
223
226
 
224
227
  注意:`workspace add` 总是注册**当前终端所在目录**;如果不传名称,会用当前目录名作为 workspace 名称。
225
228
 
229
+ ### `agent` CLI 怎么用
230
+
231
+ `weacpx agent` 用来在电脑本机维护 `~/.weacpx/config.json` 里的 `agents` 配置;`agents` 是同等别名。
232
+
233
+ | 命令 | 说明 |
234
+ |------|------|
235
+ | `weacpx agent list` | 列出已注册的 agent |
236
+ | `weacpx agent templates` | 列出可添加的内置模板 |
237
+ | `weacpx agent add <name>` | 从内置模板添加 agent,例如 `kimi`、`opencode` |
238
+ | `weacpx agent rm <name>` | 删除指定 agent |
239
+
240
+ 常见用法:
241
+
242
+ ```bash
243
+ weacpx agent templates
244
+ weacpx agent add kimi
245
+ weacpx agents list
246
+ weacpx agent rm kimi
247
+ ```
248
+
226
249
  ### `doctor` 怎么用
227
250
 
228
251
  ```bash
@@ -245,9 +268,7 @@ weacpx doctor --smoke --agent codex --workspace backend
245
268
 
246
269
  ### Agent 管理
247
270
 
248
- 已内置常用的 CodexClaude Code;
249
-
250
- 可以使用 `/agent add opencode` 添加你所需要的 agents。
271
+ 默认配置通常已包含 `codex``claude`。如果你要使用其它 acpx 支持的 agent,可以先用 `/agent add <name>` 从内置模板添加。
251
272
 
252
273
  | 命令 | 说明 |
253
274
  |------|------|
@@ -256,6 +277,16 @@ weacpx doctor --smoke --agent codex --workspace backend
256
277
  | `/agent add opencode` | 添加 `OpenCode` Agent |
257
278
  | `/agent rm <name>` | 删除 agent |
258
279
 
280
+ 当前内置模板与 acpx 的 built-in agents 对齐:
281
+
282
+ ```text
283
+ codex, claude, pi, openclaw, gemini, cursor, copilot, droid,
284
+ factory-droid, factorydroid, iflow, kilocode, kimi, kiro,
285
+ opencode, qoder, qwen, trae
286
+ ```
287
+
288
+ 这些模板只写入 `driver`,实际启动命令交给 acpx 解析;例如 `/agent add kimi` 会保存 `{ "driver": "kimi" }`。完整命令说明见 [docs/commands.md](./docs/commands.md),配置字段见 [docs/config-reference.md](./docs/config-reference.md)。
289
+
259
290
  ### Workspace 管理
260
291
 
261
292
  | 命令 | 说明 |
@@ -320,22 +351,15 @@ README 里只保留用户视角的最常用命令。
320
351
  | 命令 | 说明 |
321
352
  |------|------|
322
353
  | `/dg <agent> <task>` | 快速委派一个子任务 |
323
- | `/group new <title>` | 创建任务组 |
324
- | `/group add <groupId> <agent> <task>` | 往任务组里加子任务 |
325
- | `/groups` | 查看任务组列表 |
326
- | `/group <id>` | 查看单个任务组 |
327
- | `/group cancel <groupId>` | 取消组内未结束任务 |
328
354
  | `/tasks` | 查看当前主线下的任务 |
329
355
  | `/task <id>` | 查看单个任务详情 |
330
356
  | `/task approve <id>` | 批准 `needs_confirmation` 任务 |
331
- | `/task cancel <id>` | 取消任务 |
357
+ | `/task cancel <id>` | 取消任务;取消一个尚未批准的任务等同于拒绝 |
332
358
 
333
359
  最常见例子:
334
360
 
335
361
  ```text
336
362
  /dg claude 审查当前方案的 3 个高风险点
337
- /group new review
338
- /group add review claude 审查接口设计
339
363
  /tasks
340
364
  /task approve task_123
341
365
  ```
@@ -345,9 +369,9 @@ README 里只保留用户视角的最常用命令。
345
369
  - 当前会话就是主控会话
346
370
  - 被委派出去的是独立子任务会话
347
371
  - agent 发起的委派请求默认需要人工确认
348
- - group 适合把 2~3 个独立子任务并行派出去,再把结果汇总回主线
372
+ - 如果你在用外部 MCP host(Codex / Claude Code),用 `delegate_batch` 一次派发多个并行子任务:传一个 `tasks` 数组,底层自动建组,全部结果一次性回注,无需手动维护 groupId
349
373
 
350
- 如果你想先理解什么时候该用 delegate、什么时候该开 group,请看:
374
+ 如果你想先理解什么时候该用 delegate、什么时候应该并行派出多个子任务,请看:
351
375
 
352
376
  - [docs/weacpx-group-usage-guide.md](./docs/weacpx-group-usage-guide.md)
353
377
 
@@ -356,6 +380,8 @@ README 里只保留用户视角的最常用命令。
356
380
 
357
381
  如果你想让 Codex、Claude Code 等外部 MCP host 直接使用 weacpx 的多 Agent 编排能力,可以把 `weacpx mcp-stdio` 配成一个 stdio MCP server。
358
382
 
383
+ `delegate_request` 支持 MCP Tasks:支持该能力的 host 可以让委派请求立即返回原生 task handle,之后通过 `tasks/get` / `tasks/result` / `tasks/cancel` 获取状态、结果或取消任务;worker 输出的 `[PROGRESS] ...` 会显示在 `tasks/get` / `tasks/list` 的 `statusMessage` 里;`input_required` 状态下的 `tasks/result` 会返回下一步操作提示并结束本次 result stream,而不是长时间阻塞;client 按提示调用 `task_get` / `task_approve` / `coordinator_answer_question` 等工具后,再继续 `tasks/get` / `tasks/result` 轮询。不支持 MCP Tasks 的 host 仍可使用兼容工具 `task_get` / `task_list` / `task_watch` / `task_cancel`。
384
+
359
385
  先启动 daemon:
360
386
 
361
387
  ```bash
@@ -111,38 +111,24 @@ function extractPromptFailureMessage(result) {
111
111
  function extractPromptOutput(output) {
112
112
  const lines = output.split(`
113
113
  `).map((line) => line.trim()).filter((line) => line.length > 0);
114
- const messageSegments = [];
115
- let currentSegment = "";
114
+ let text = "";
116
115
  let hasAgentMessage = false;
117
116
  for (const line of lines) {
117
+ let event;
118
118
  try {
119
- const event = JSON.parse(line);
120
- 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";
121
- if (isMessageChunk) {
122
- hasAgentMessage = true;
123
- const chunk = event.params.update.content.text ?? "";
124
- if (chunk.length > 0) {
125
- currentSegment += chunk;
126
- }
127
- continue;
128
- }
129
- if (currentSegment.trim().length > 0) {
130
- messageSegments.push(currentSegment.trim());
131
- }
132
- currentSegment = "";
119
+ event = JSON.parse(line);
133
120
  } catch {
134
- if (currentSegment.trim().length > 0) {
135
- messageSegments.push(currentSegment.trim());
136
- currentSegment = "";
137
- }
121
+ continue;
122
+ }
123
+ 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";
124
+ if (isMessageChunk) {
125
+ hasAgentMessage = true;
126
+ text += event.params.update.content.text ?? "";
138
127
  }
139
128
  }
140
- if (currentSegment.trim().length > 0) {
141
- messageSegments.push(currentSegment.trim());
142
- }
143
- if (messageSegments.length > 0) {
129
+ if (hasAgentMessage && text.trim().length > 0) {
144
130
  return {
145
- text: messageSegments[messageSegments.length - 1],
131
+ text: text.trim(),
146
132
  hasAgentMessage
147
133
  };
148
134
  }
@@ -176,8 +162,12 @@ function extractJsonRpcErrorMessages(output) {
176
162
  `).map((line) => line.trim()).filter((line) => line.length > 0).flatMap((line) => {
177
163
  try {
178
164
  const payload = JSON.parse(line);
179
- if (typeof payload.error?.message === "string" && payload.error.message.length > 0) {
180
- return [payload.error.message];
165
+ const err = payload.error;
166
+ const dataMsg = typeof err?.data?.message === "string" && err.data.message.length > 0 ? err.data.message : undefined;
167
+ const baseMsg = typeof err?.message === "string" && err.message.length > 0 ? err.message : undefined;
168
+ const chosen = dataMsg && dataMsg !== baseMsg ? dataMsg : baseMsg;
169
+ if (chosen) {
170
+ return [chosen];
181
171
  }
182
172
  } catch {
183
173
  return [];