weacpx 0.4.5 → 0.4.8

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
@@ -169,7 +169,7 @@ 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` | 安装并添加飞书频道,会提示输入飞书应用凭据 |
@@ -351,22 +351,15 @@ README 里只保留用户视角的最常用命令。
351
351
  | 命令 | 说明 |
352
352
  |------|------|
353
353
  | `/dg <agent> <task>` | 快速委派一个子任务 |
354
- | `/group new <title>` | 创建任务组 |
355
- | `/group add <groupId> <agent> <task>` | 往任务组里加子任务 |
356
- | `/groups` | 查看任务组列表 |
357
- | `/group <id>` | 查看单个任务组 |
358
- | `/group cancel <groupId>` | 取消组内未结束任务 |
359
354
  | `/tasks` | 查看当前主线下的任务 |
360
355
  | `/task <id>` | 查看单个任务详情 |
361
356
  | `/task approve <id>` | 批准 `needs_confirmation` 任务 |
362
- | `/task cancel <id>` | 取消任务 |
357
+ | `/task cancel <id>` | 取消任务;取消一个尚未批准的任务等同于拒绝 |
363
358
 
364
359
  最常见例子:
365
360
 
366
361
  ```text
367
362
  /dg claude 审查当前方案的 3 个高风险点
368
- /group new review
369
- /group add review claude 审查接口设计
370
363
  /tasks
371
364
  /task approve task_123
372
365
  ```
@@ -376,9 +369,9 @@ README 里只保留用户视角的最常用命令。
376
369
  - 当前会话就是主控会话
377
370
  - 被委派出去的是独立子任务会话
378
371
  - agent 发起的委派请求默认需要人工确认
379
- - group 适合把 2~3 个独立子任务并行派出去,再把结果汇总回主线
372
+ - 如果你在用外部 MCP host(Codex / Claude Code),用 `delegate_batch` 一次派发多个并行子任务:传一个 `tasks` 数组,底层自动建组,全部结果一次性回注,无需手动维护 groupId
380
373
 
381
- 如果你想先理解什么时候该用 delegate、什么时候该开 group,请看:
374
+ 如果你想先理解什么时候该用 delegate、什么时候应该并行派出多个子任务,请看:
382
375
 
383
376
  - [docs/weacpx-group-usage-guide.md](./docs/weacpx-group-usage-guide.md)
384
377
 
@@ -387,7 +380,7 @@ README 里只保留用户视角的最常用命令。
387
380
 
388
381
  如果你想让 Codex、Claude Code 等外部 MCP host 直接使用 weacpx 的多 Agent 编排能力,可以把 `weacpx mcp-stdio` 配成一个 stdio MCP server。
389
382
 
390
- `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_wait` / `task_cancel`。
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`。
391
384
 
392
385
  先启动 daemon:
393
386
 
@@ -60,6 +60,10 @@ function encodeBridgePromptToolEvent(event) {
60
60
  return `${JSON.stringify(event)}
61
61
  `;
62
62
  }
63
+ function encodeBridgePromptThoughtEvent(event) {
64
+ return `${JSON.stringify(event)}
65
+ `;
66
+ }
63
67
  function encodeBridgeSessionProgressEvent(event) {
64
68
  return `${JSON.stringify(event)}
65
69
  `;
@@ -162,8 +166,12 @@ function extractJsonRpcErrorMessages(output) {
162
166
  `).map((line) => line.trim()).filter((line) => line.length > 0).flatMap((line) => {
163
167
  try {
164
168
  const payload = JSON.parse(line);
165
- if (typeof payload.error?.message === "string" && payload.error.message.length > 0) {
166
- return [payload.error.message];
169
+ const err = payload.error;
170
+ const dataMsg = typeof err?.data?.message === "string" && err.data.message.length > 0 ? err.data.message : undefined;
171
+ const baseMsg = typeof err?.message === "string" && err.message.length > 0 ? err.message : undefined;
172
+ const chosen = dataMsg && dataMsg !== baseMsg ? dataMsg : baseMsg;
173
+ if (chosen) {
174
+ return [chosen];
167
175
  }
168
176
  } catch {
169
177
  return [];
@@ -362,6 +370,7 @@ var init_tool_kind_emoji = __esm(() => {
362
370
  function createStreamingPromptState(formatToolCalls = false, options) {
363
371
  let toolEventMode;
364
372
  let onToolEvent;
373
+ let onThought;
365
374
  if (options === undefined) {
366
375
  toolEventMode = "text";
367
376
  onToolEvent = undefined;
@@ -370,6 +379,7 @@ function createStreamingPromptState(formatToolCalls = false, options) {
370
379
  toolEventMode = "structured";
371
380
  } else {
372
381
  onToolEvent = options.onToolEvent;
382
+ onThought = options.onThought;
373
383
  toolEventMode = resolveToolEventMode({
374
384
  toolEventMode: options.mode,
375
385
  onToolEvent
@@ -384,6 +394,7 @@ function createStreamingPromptState(formatToolCalls = false, options) {
384
394
  emittedToolCallIds: new Set,
385
395
  toolEventMode,
386
396
  onToolEvent,
397
+ onThought,
387
398
  finalize() {
388
399
  if (this.pendingLine.trim().length > 0) {
389
400
  parseStreamingChunks(this, this.pendingLine);
@@ -442,6 +453,14 @@ function parseStreamingChunks(state, line) {
442
453
  }
443
454
  return;
444
455
  }
456
+ const isThoughtChunk = update.sessionUpdate === "agent_thought_chunk" && update.content?.type === "text" && typeof update.content.text === "string";
457
+ if (isThoughtChunk) {
458
+ const chunk2 = update.content.text;
459
+ if (chunk2.length > 0) {
460
+ state.onThought?.(chunk2);
461
+ }
462
+ return;
463
+ }
445
464
  const isMessageChunk = update.sessionUpdate === "agent_message_chunk" && update.content?.type === "text" && typeof update.content.text === "string";
446
465
  if (!isMessageChunk)
447
466
  return;
@@ -469,7 +488,7 @@ function formatToolCallEvent(update, sessionUpdate) {
469
488
  if (title.length === 0)
470
489
  return null;
471
490
  const emoji = TOOL_KIND_EMOJI[kind] ?? DEFAULT_TOOL_EMOJI;
472
- const inputSummary = summarizeToolInput(update.rawInput, title);
491
+ const inputSummary = summarizeToolInput(update.rawInput, title) || summarizeToolInput(update.rawOutput, title);
473
492
  const status = readString(update, "status");
474
493
  if (!inputSummary && status === "pending")
475
494
  return null;
@@ -500,15 +519,23 @@ function buildToolUseEvent(update) {
500
519
  })();
501
520
  const title = (update.title ?? "").trim();
502
521
  const toolName = title || "Tool";
503
- const summaryRaw = summarizeToolInput(update.rawInput, title);
522
+ const summaryRaw = summarizeToolInput(update.rawInput, title) || summarizeToolInput(update.rawOutput, title);
504
523
  const summary = summaryRaw && summaryRaw !== title ? summaryRaw : undefined;
505
524
  const statusRaw = readString(update, "status");
506
525
  const status = statusRaw === "completed" || statusRaw === "success" ? "success" : statusRaw === "failed" || statusRaw === "error" ? "error" : "running";
526
+ const rawInput = update.rawInput;
527
+ const content = update.content;
528
+ const rawOutput = update.rawOutput;
529
+ const locations = update.locations;
507
530
  return {
508
531
  toolCallId,
509
532
  toolName,
510
533
  kind,
511
534
  ...summary ? { summary } : {},
535
+ ...rawInput !== undefined ? { rawInput } : {},
536
+ ...content !== undefined ? { content } : {},
537
+ ...rawOutput !== undefined ? { rawOutput } : {},
538
+ ...locations !== undefined ? { locations } : {},
512
539
  status
513
540
  };
514
541
  }
@@ -827,6 +854,8 @@ function buildQueueOwnerPayload(input) {
827
854
  nonInteractivePermissions: input.nonInteractivePermissions,
828
855
  ttlMs: input.ttlMs ?? 300000,
829
856
  maxQueueDepth: input.maxQueueDepth ?? 16,
857
+ ...Number.isFinite(input.promptRetries) ? { promptRetries: input.promptRetries } : {},
858
+ ...input.sessionOptions ? { sessionOptions: input.sessionOptions } : {},
830
859
  mcpServers: input.mcpServers
831
860
  };
832
861
  }
@@ -1109,6 +1138,7 @@ class BridgeRuntime {
1109
1138
  async updatePermissionPolicy(policy) {
1110
1139
  this.options.permissionMode = policy.permissionMode;
1111
1140
  this.options.nonInteractivePermissions = policy.nonInteractivePermissions;
1141
+ this.options.permissionPolicy = policy.permissionPolicy;
1112
1142
  return {};
1113
1143
  }
1114
1144
  async hasSession(input) {
@@ -1120,6 +1150,26 @@ class BridgeRuntime {
1120
1150
  const result = await this.run(spawnSpec.command, spawnSpec.args);
1121
1151
  return { exists: result.code === 0 };
1122
1152
  }
1153
+ async tailSessionHistory(input) {
1154
+ const candidates = [
1155
+ ["sessions", "history", "quiet", "-s", input.name, String(input.lines)],
1156
+ ["sessions", "history", "quiet", input.name, String(input.lines)],
1157
+ ["sessions", "history", "-s", input.name, "--tail", String(input.lines)],
1158
+ ["sessions", "history", input.name, "--tail", String(input.lines)],
1159
+ ["sessions", "history", "--name", input.name, "--tail", String(input.lines)]
1160
+ ];
1161
+ let lastResult;
1162
+ for (const tailArgs of candidates) {
1163
+ const spawnSpec = resolveSpawnCommand(this.command, this.buildSessionArgs(input, tailArgs));
1164
+ const result = await this.run(spawnSpec.command, spawnSpec.args);
1165
+ if (result.code === 0) {
1166
+ return { text: result.stdout.trimEnd() };
1167
+ }
1168
+ lastResult = result;
1169
+ }
1170
+ const message = lastResult?.stderr || lastResult?.stdout || "sessions history failed";
1171
+ throw new Error(message);
1172
+ }
1123
1173
  async ensureSession(input, onProgress) {
1124
1174
  onProgress?.("spawn");
1125
1175
  const onStderrLine = onProgress ? (line) => {
@@ -1342,7 +1392,11 @@ class BridgeRuntime {
1342
1392
  const permissionMode = this.options.permissionMode ?? "approve-all";
1343
1393
  const nonInteractivePermissions = this.options.nonInteractivePermissions ?? "deny";
1344
1394
  const modeFlag = permissionModeToFlag(permissionMode);
1345
- return [modeFlag, "--non-interactive-permissions", nonInteractivePermissions];
1395
+ const args = [modeFlag, "--non-interactive-permissions", nonInteractivePermissions];
1396
+ if (typeof this.options.permissionPolicy === "string" && this.options.permissionPolicy.trim().length > 0) {
1397
+ args.push("--permission-policy", this.options.permissionPolicy);
1398
+ }
1399
+ return args;
1346
1400
  }
1347
1401
  }
1348
1402
  function spawnCapture(command, args, options) {
@@ -1394,7 +1448,8 @@ async function runStreamingPrompt(command, args, onEvent, options = {}) {
1394
1448
  const toolEventMode = options.toolEventMode ?? "text";
1395
1449
  const state = createStreamingPromptState(options.formatToolCalls ?? false, {
1396
1450
  mode: toolEventMode,
1397
- ...onEvent && (toolEventMode === "structured" || toolEventMode === "both") ? { onToolEvent: (toolEvent) => onEvent({ type: "prompt.tool_event", event: toolEvent }) } : {}
1451
+ ...onEvent && (toolEventMode === "structured" || toolEventMode === "both") ? { onToolEvent: (toolEvent) => onEvent({ type: "prompt.tool_event", event: toolEvent }) } : {},
1452
+ ...onEvent ? { onThought: (chunk) => onEvent({ type: "prompt.thought", text: chunk }) } : {}
1398
1453
  });
1399
1454
  let lastReplyAt = now();
1400
1455
  const flushBuffer = () => {
@@ -1510,6 +1565,7 @@ var BRIDGE_METHODS = new Set([
1510
1565
  "updatePermissionPolicy",
1511
1566
  "hasSession",
1512
1567
  "ensureSession",
1568
+ "tailSessionHistory",
1513
1569
  "prompt",
1514
1570
  "setMode",
1515
1571
  "cancel",
@@ -1518,6 +1574,7 @@ var BRIDGE_METHODS = new Set([
1518
1574
  var SESSION_SCOPED_METHODS = new Set([
1519
1575
  "hasSession",
1520
1576
  "ensureSession",
1577
+ "tailSessionHistory",
1521
1578
  "prompt",
1522
1579
  "setMode",
1523
1580
  "cancel",
@@ -1592,6 +1649,14 @@ class BridgeServer {
1592
1649
  cwd: requireString(params, "cwd"),
1593
1650
  name: requireString(params, "name")
1594
1651
  });
1652
+ case "tailSessionHistory":
1653
+ return await this.runtime.tailSessionHistory({
1654
+ agent: requireString(params, "agent"),
1655
+ agentCommand: asOptionalString(params.agentCommand),
1656
+ cwd: requireString(params, "cwd"),
1657
+ name: requireString(params, "name"),
1658
+ lines: requirePositiveInt(params, "lines")
1659
+ });
1595
1660
  case "ensureSession":
1596
1661
  return await this.runtime.ensureSession({
1597
1662
  agent: requireString(params, "agent"),
@@ -1643,6 +1708,12 @@ class BridgeServer {
1643
1708
  event: "prompt.tool_event",
1644
1709
  toolEvent: event.event
1645
1710
  }));
1711
+ } else if (event.type === "prompt.thought") {
1712
+ writeLine?.(encodeBridgePromptThoughtEvent({
1713
+ id: requestId,
1714
+ event: "prompt.thought",
1715
+ text: event.text
1716
+ }));
1646
1717
  }
1647
1718
  });
1648
1719
  case "setMode":
@@ -1741,6 +1812,13 @@ function requireString(params, key) {
1741
1812
  }
1742
1813
  return value;
1743
1814
  }
1815
+ function requirePositiveInt(params, key) {
1816
+ const value = params[key];
1817
+ if (typeof value !== "number" || !Number.isFinite(value) || !Number.isInteger(value) || value <= 0) {
1818
+ throw new BridgeInvalidRequestError(`${key} must be a positive integer`);
1819
+ }
1820
+ return value;
1821
+ }
1744
1822
  function requirePromptText(params, media) {
1745
1823
  const value = params.text;
1746
1824
  if (typeof value !== "string") {
@@ -71,6 +71,10 @@ export interface ToolUseEvent {
71
71
  kind: ToolUseKind;
72
72
  /** Best-effort one-line summary derived from `rawInput`. */
73
73
  summary?: string;
74
+ rawInput?: unknown;
75
+ content?: unknown;
76
+ rawOutput?: unknown;
77
+ locations?: unknown;
74
78
  status: ToolUseStatus;
75
79
  /** Set when status transitions out of "running". */
76
80
  durationMs?: number;