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/dist/cli.js CHANGED
@@ -2611,6 +2611,82 @@ var init_ensure_config = __esm(() => {
2611
2611
  };
2612
2612
  });
2613
2613
 
2614
+ // src/config/agent-templates.ts
2615
+ function getAgentTemplate(name) {
2616
+ const template = TEMPLATES[name];
2617
+ if (!template) {
2618
+ return null;
2619
+ }
2620
+ return {
2621
+ ...template
2622
+ };
2623
+ }
2624
+ function listAgentTemplates() {
2625
+ return Object.keys(TEMPLATES);
2626
+ }
2627
+ function sameAgentConfig(left, right) {
2628
+ return left.driver === right.driver && (left.command ?? "") === (right.command ?? "");
2629
+ }
2630
+ var TEMPLATES;
2631
+ var init_agent_templates = __esm(() => {
2632
+ TEMPLATES = {
2633
+ codex: {
2634
+ driver: "codex"
2635
+ },
2636
+ claude: {
2637
+ driver: "claude"
2638
+ },
2639
+ pi: {
2640
+ driver: "pi"
2641
+ },
2642
+ openclaw: {
2643
+ driver: "openclaw"
2644
+ },
2645
+ gemini: {
2646
+ driver: "gemini"
2647
+ },
2648
+ cursor: {
2649
+ driver: "cursor"
2650
+ },
2651
+ copilot: {
2652
+ driver: "copilot"
2653
+ },
2654
+ droid: {
2655
+ driver: "droid"
2656
+ },
2657
+ "factory-droid": {
2658
+ driver: "factory-droid"
2659
+ },
2660
+ factorydroid: {
2661
+ driver: "factorydroid"
2662
+ },
2663
+ iflow: {
2664
+ driver: "iflow"
2665
+ },
2666
+ kilocode: {
2667
+ driver: "kilocode"
2668
+ },
2669
+ kimi: {
2670
+ driver: "kimi"
2671
+ },
2672
+ kiro: {
2673
+ driver: "kiro"
2674
+ },
2675
+ opencode: {
2676
+ driver: "opencode"
2677
+ },
2678
+ qoder: {
2679
+ driver: "qoder"
2680
+ },
2681
+ qwen: {
2682
+ driver: "qwen"
2683
+ },
2684
+ trae: {
2685
+ driver: "trae"
2686
+ }
2687
+ };
2688
+ });
2689
+
2614
2690
  // src/daemon/daemon-status.ts
2615
2691
  import { mkdir as mkdir2, readFile as readFile3, rm, writeFile as writeFile2 } from "node:fs/promises";
2616
2692
  import { dirname as dirname2 } from "node:path";
@@ -9541,11 +9617,10 @@ function readVersion(moduleUrl = import.meta.url) {
9541
9617
  var PACKAGE_NAME = "weacpx";
9542
9618
  var init_version = () => {};
9543
9619
 
9544
- // src/orchestration/task-wait-timeouts.ts
9545
- var DEFAULT_TASK_WAIT_TIMEOUT_MS, MAX_TASK_WAIT_TIMEOUT_MS, DEFAULT_TASK_WAIT_POLL_INTERVAL_MS = 1000, MAX_TASK_WAIT_POLL_INTERVAL_MS = 1e4, TASK_WAIT_RPC_TIMEOUT_PADDING_MS = 5000;
9546
- var init_task_wait_timeouts = __esm(() => {
9547
- DEFAULT_TASK_WAIT_TIMEOUT_MS = 5 * 60000;
9548
- MAX_TASK_WAIT_TIMEOUT_MS = 20 * 60000;
9620
+ // src/orchestration/task-watch-timeouts.ts
9621
+ var DEFAULT_TASK_WATCH_TIMEOUT_MS = 60000, MAX_TASK_WATCH_TIMEOUT_MS, DEFAULT_TASK_WATCH_POLL_INTERVAL_MS = 1000, MAX_TASK_WATCH_POLL_INTERVAL_MS = 1e4, TASK_WATCH_RPC_TIMEOUT_PADDING_MS = 5000;
9622
+ var init_task_watch_timeouts = __esm(() => {
9623
+ MAX_TASK_WATCH_TIMEOUT_MS = 20 * 60000;
9549
9624
  });
9550
9625
 
9551
9626
  // src/weixin/messaging/quota-errors.ts
@@ -9607,6 +9682,15 @@ function isTaskStatus(value) {
9607
9682
  function isSourceKind(value) {
9608
9683
  return value === "human" || value === "coordinator" || value === "worker";
9609
9684
  }
9685
+ function isOptionalNumber(value) {
9686
+ return value === undefined || typeof value === "number";
9687
+ }
9688
+ function isTaskEventRecord(value) {
9689
+ if (!isRecord2(value)) {
9690
+ return false;
9691
+ }
9692
+ return typeof value.seq === "number" && isString(value.at) && (value.type === "created" || value.type === "progress" || value.type === "status_changed" || value.type === "attention_required" || value.type === "cancel_requested") && (value.status === undefined || isTaskStatus(value.status)) && isOptionalString(value.summary) && isOptionalString(value.message);
9693
+ }
9610
9694
  function isOpenQuestionRecord(value) {
9611
9695
  if (!isRecord2(value)) {
9612
9696
  return false;
@@ -9629,7 +9713,7 @@ function isTaskRecord(value) {
9629
9713
  if (!isRecord2(value)) {
9630
9714
  return false;
9631
9715
  }
9632
- return isString(value.taskId) && isString(value.sourceHandle) && isSourceKind(value.sourceKind) && isString(value.coordinatorSession) && isOptionalString(value.workerSession) && isString(value.workspace) && isOptionalString(value.cwd) && isString(value.targetAgent) && isOptionalString(value.role) && isString(value.task) && isTaskStatus(value.status) && isString(value.summary) && isString(value.resultText) && isString(value.createdAt) && isString(value.updatedAt) && isOptionalString(value.chatKey) && isOptionalString(value.replyContextToken) && isOptionalString(value.accountId) && isOptionalString(value.deliveryAccountId) && isOptionalString(value.coordinatorInjectedAt) && isOptionalString(value.cancelRequestedAt) && isOptionalString(value.cancelCompletedAt) && isOptionalString(value.lastCancelError) && isOptionalBoolean(value.noticePending) && isOptionalString(value.noticeSentAt) && isOptionalString(value.lastNoticeError) && isOptionalBoolean(value.injectionPending) && isOptionalString(value.injectionAppliedAt) && isOptionalString(value.lastInjectionError) && isOptionalString(value.lastProgressAt) && isOptionalString(value.groupId) && (value.openQuestion === undefined || isOpenQuestionRecord(value.openQuestion)) && (value.reviewPending === undefined || isReviewPendingRecord(value.reviewPending)) && (value.correctionPending === undefined || isCorrectionPendingRecord(value.correctionPending));
9716
+ return isString(value.taskId) && isString(value.sourceHandle) && isSourceKind(value.sourceKind) && isString(value.coordinatorSession) && isOptionalString(value.workerSession) && isString(value.workspace) && isOptionalString(value.cwd) && isString(value.targetAgent) && isOptionalString(value.role) && isString(value.task) && isTaskStatus(value.status) && isString(value.summary) && isString(value.resultText) && isString(value.createdAt) && isString(value.updatedAt) && isOptionalString(value.chatKey) && isOptionalString(value.replyContextToken) && isOptionalString(value.accountId) && isOptionalString(value.deliveryAccountId) && isOptionalString(value.coordinatorInjectedAt) && isOptionalString(value.cancelRequestedAt) && isOptionalString(value.cancelCompletedAt) && isOptionalString(value.lastCancelError) && isOptionalBoolean(value.noticePending) && isOptionalString(value.noticeSentAt) && isOptionalString(value.lastNoticeError) && isOptionalBoolean(value.injectionPending) && isOptionalString(value.injectionAppliedAt) && isOptionalString(value.lastInjectionError) && isOptionalString(value.lastProgressAt) && isOptionalString(value.lastProgressSummary) && isOptionalString(value.groupId) && (value.openQuestion === undefined || isOpenQuestionRecord(value.openQuestion)) && (value.reviewPending === undefined || isReviewPendingRecord(value.reviewPending)) && (value.correctionPending === undefined || isCorrectionPendingRecord(value.correctionPending)) && isOptionalNumber(value.eventSeq) && (value.events === undefined || Array.isArray(value.events) && value.events.every(isTaskEventRecord));
9633
9717
  }
9634
9718
  function isExternalCoordinatorRecord(value) {
9635
9719
  if (!isRecord2(value)) {
@@ -9893,37 +9977,6 @@ var init_state_store = __esm(() => {
9893
9977
  init_types();
9894
9978
  });
9895
9979
 
9896
- // src/config/agent-templates.ts
9897
- function getAgentTemplate(name) {
9898
- const template = TEMPLATES[name];
9899
- if (!template) {
9900
- return null;
9901
- }
9902
- return {
9903
- ...template
9904
- };
9905
- }
9906
- function listAgentTemplates() {
9907
- return Object.keys(TEMPLATES);
9908
- }
9909
- var TEMPLATES;
9910
- var init_agent_templates = __esm(() => {
9911
- TEMPLATES = {
9912
- codex: {
9913
- driver: "codex"
9914
- },
9915
- claude: {
9916
- driver: "claude"
9917
- },
9918
- opencode: {
9919
- driver: "opencode"
9920
- },
9921
- gemini: {
9922
- driver: "gemini"
9923
- }
9924
- };
9925
- });
9926
-
9927
9980
  // src/plugins/plugin-home.ts
9928
9981
  import { mkdir as mkdir6, writeFile as writeFile5 } from "node:fs/promises";
9929
9982
  import { homedir as homedir3 } from "node:os";
@@ -13964,7 +14017,7 @@ ${buildFinalHeadsUp({
13964
14017
  sendWeixinErrorNotice({
13965
14018
  to,
13966
14019
  contextToken,
13967
- message: `⚠️ 过程失败:${err instanceof Error ? err.message : JSON.stringify(err)}`,
14020
+ message: `⚠️ 执行出错:${err instanceof Error ? err.message : JSON.stringify(err)}`,
13968
14021
  baseUrl: deps.baseUrl,
13969
14022
  token: deps.token,
13970
14023
  errLog: deps.errLog
@@ -15447,38 +15500,24 @@ function extractPromptFailureMessage(result) {
15447
15500
  function extractPromptOutput(output) {
15448
15501
  const lines = output.split(`
15449
15502
  `).map((line) => line.trim()).filter((line) => line.length > 0);
15450
- const messageSegments = [];
15451
- let currentSegment = "";
15503
+ let text = "";
15452
15504
  let hasAgentMessage = false;
15453
15505
  for (const line of lines) {
15506
+ let event;
15454
15507
  try {
15455
- const event = JSON.parse(line);
15456
- 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";
15457
- if (isMessageChunk) {
15458
- hasAgentMessage = true;
15459
- const chunk = event.params.update.content.text ?? "";
15460
- if (chunk.length > 0) {
15461
- currentSegment += chunk;
15462
- }
15463
- continue;
15464
- }
15465
- if (currentSegment.trim().length > 0) {
15466
- messageSegments.push(currentSegment.trim());
15467
- }
15468
- currentSegment = "";
15508
+ event = JSON.parse(line);
15469
15509
  } catch {
15470
- if (currentSegment.trim().length > 0) {
15471
- messageSegments.push(currentSegment.trim());
15472
- currentSegment = "";
15473
- }
15510
+ continue;
15511
+ }
15512
+ 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";
15513
+ if (isMessageChunk) {
15514
+ hasAgentMessage = true;
15515
+ text += event.params.update.content.text ?? "";
15474
15516
  }
15475
15517
  }
15476
- if (currentSegment.trim().length > 0) {
15477
- messageSegments.push(currentSegment.trim());
15478
- }
15479
- if (messageSegments.length > 0) {
15518
+ if (hasAgentMessage && text.trim().length > 0) {
15480
15519
  return {
15481
- text: messageSegments[messageSegments.length - 1],
15520
+ text: text.trim(),
15482
15521
  hasAgentMessage
15483
15522
  };
15484
15523
  }
@@ -15512,8 +15551,12 @@ function extractJsonRpcErrorMessages(output) {
15512
15551
  `).map((line) => line.trim()).filter((line) => line.length > 0).flatMap((line) => {
15513
15552
  try {
15514
15553
  const payload = JSON.parse(line);
15515
- if (typeof payload.error?.message === "string" && payload.error.message.length > 0) {
15516
- return [payload.error.message];
15554
+ const err = payload.error;
15555
+ const dataMsg = typeof err?.data?.message === "string" && err.data.message.length > 0 ? err.data.message : undefined;
15556
+ const baseMsg = typeof err?.message === "string" && err.message.length > 0 ? err.message : undefined;
15557
+ const chosen = dataMsg && dataMsg !== baseMsg ? dataMsg : baseMsg;
15558
+ if (chosen) {
15559
+ return [chosen];
15517
15560
  }
15518
15561
  } catch {
15519
15562
  return [];
@@ -17202,18 +17245,18 @@ function renderDelegateSuccess2(taskId, workerSession) {
17202
17245
  return [`已创建委派任务「${taskId}」`, `worker 会话:${workerSession}`].join(`
17203
17246
  `);
17204
17247
  }
17205
- function renderGroupCreated2(group) {
17248
+ function renderGroupCreated(group) {
17206
17249
  return [`已创建任务组「${group.groupId}」`, `- 标题:${group.title}`].join(`
17207
17250
  `);
17208
17251
  }
17209
- function renderGroupList2(groups) {
17252
+ function renderGroupList(groups) {
17210
17253
  if (groups.length === 0) {
17211
17254
  return "当前协调会话下还没有任务组。";
17212
17255
  }
17213
- return ["当前协调会话的任务组:", ...groups.map((group) => renderGroupListItem2(group))].join(`
17256
+ return ["当前协调会话的任务组:", ...groups.map((group) => renderGroupListItem(group))].join(`
17214
17257
  `);
17215
17258
  }
17216
- function renderGroupSummary2(summary) {
17259
+ function renderGroupSummary(summary) {
17217
17260
  const { group, tasks } = summary;
17218
17261
  const lines = [
17219
17262
  `任务组「${group.groupId}」`,
@@ -17245,7 +17288,7 @@ function renderGroupSummary2(summary) {
17245
17288
  return lines.join(`
17246
17289
  `);
17247
17290
  }
17248
- function renderGroupCancelSuccess2(input) {
17291
+ function renderGroupCancelSuccess(input) {
17249
17292
  return [
17250
17293
  `任务组「${input.summary.group.groupId}」已发起取消`,
17251
17294
  `- 已请求取消:${input.cancelledTaskIds.length}`,
@@ -17278,6 +17321,8 @@ function renderTaskSummary2(task) {
17278
17321
  header.push(`- 任务:${task.task}`);
17279
17322
  if (task.summary.trim().length > 0)
17280
17323
  header.push(`- 摘要:${task.summary}`);
17324
+ if (task.lastProgressSummary)
17325
+ header.push(`- 最新进展:${task.lastProgressSummary}`);
17281
17326
  if (task.resultText.trim().length > 0)
17282
17327
  header.push(`- 结果:${task.resultText}`);
17283
17328
  const events = [];
@@ -17326,7 +17371,7 @@ function renderTaskApprovalSuccess2(task) {
17326
17371
  return [`已批准任务「${task.taskId}」。`, `- 当前状态:${task.status}`].join(`
17327
17372
  `);
17328
17373
  }
17329
- function renderTaskRejectionSuccess2(task) {
17374
+ function renderTaskRejectSuccess(task) {
17330
17375
  return [`已拒绝任务「${task.taskId}」。`, `- 当前状态:${task.status}`].join(`
17331
17376
  `);
17332
17377
  }
@@ -17360,7 +17405,7 @@ function renderTaskListItem2(task) {
17360
17405
  ].filter(Boolean).map((item) => `;${item}`).join("");
17361
17406
  return `- ${task.taskId} [${task.status}] ${task.targetAgent}${role} -> ${task.workerSession ?? "未分配"}${group}${source}${summary}${reliability}`;
17362
17407
  }
17363
- function renderGroupListItem2(group) {
17408
+ function renderGroupListItem(group) {
17364
17409
  const reliability = [
17365
17410
  group.group.injectionPending ? "注入待重试" : ""
17366
17411
  ].filter(Boolean).map((item) => `;${item}`).join("");
@@ -17421,7 +17466,7 @@ async function handleGroupCreate(context, chatKey, title) {
17421
17466
  coordinatorSession: session.transportSession,
17422
17467
  title
17423
17468
  });
17424
- return { text: renderGroupCreated2(group) };
17469
+ return { text: renderGroupCreated(group) };
17425
17470
  }
17426
17471
  async function handleGroupList(context, chatKey, filter) {
17427
17472
  const session = await getCurrentSession(context, chatKey);
@@ -17436,7 +17481,7 @@ async function handleGroupList(context, chatKey, filter) {
17436
17481
  coordinatorSession: session.transportSession,
17437
17482
  ...filter ?? {}
17438
17483
  });
17439
- return { text: renderGroupList2(groups) };
17484
+ return { text: renderGroupList(groups) };
17440
17485
  }
17441
17486
  async function handleGroupGet(context, chatKey, groupId) {
17442
17487
  const session = await getCurrentSession(context, chatKey);
@@ -17454,7 +17499,7 @@ async function handleGroupGet(context, chatKey, groupId) {
17454
17499
  if (!group) {
17455
17500
  return { text: GROUP_NOT_FOUND_TEXT };
17456
17501
  }
17457
- return { text: renderGroupSummary2(group) };
17502
+ return { text: renderGroupSummary(group) };
17458
17503
  }
17459
17504
  async function handleGroupCancel(context, chatKey, groupId) {
17460
17505
  const session = await getCurrentSession(context, chatKey);
@@ -17476,7 +17521,7 @@ async function handleGroupCancel(context, chatKey, groupId) {
17476
17521
  groupId,
17477
17522
  coordinatorSession: session.transportSession
17478
17523
  });
17479
- return { text: renderGroupCancelSuccess2(cancelled) };
17524
+ return { text: renderGroupCancelSuccess(cancelled) };
17480
17525
  }
17481
17526
  async function handleGroupDelegate(context, chatKey, groupId, targetAgent, task, role, replyContextToken, accountId) {
17482
17527
  const session = await getCurrentSession(context, chatKey);
@@ -17564,11 +17609,11 @@ async function handleTaskReject(context, chatKey, taskId) {
17564
17609
  if (task.status !== "needs_confirmation") {
17565
17610
  return { text: renderTaskConfirmationUnavailable(task) };
17566
17611
  }
17567
- const rejected = await orchestration.rejectTask({
17612
+ const rejected = await orchestration.cancelTask({
17568
17613
  taskId,
17569
17614
  coordinatorSession: session.transportSession
17570
17615
  });
17571
- return { text: renderTaskRejectionSuccess2(rejected) };
17616
+ return { text: renderTaskRejectSuccess(rejected) };
17572
17617
  }
17573
17618
  async function handleTaskCancel(context, chatKey, taskId) {
17574
17619
  const session = await getCurrentSession(context, chatKey);
@@ -17721,6 +17766,13 @@ async function handleAgentAdd(context, templateName) {
17721
17766
  if (!template) {
17722
17767
  return { text: `暂不支持这个 Agent 模板。当前可用:${listAgentTemplates().join("、")}` };
17723
17768
  }
17769
+ const existing = context.config.agents[templateName];
17770
+ if (existing) {
17771
+ if (sameAgentConfig(existing, template)) {
17772
+ return { text: `Agent「${templateName}」已存在` };
17773
+ }
17774
+ return { text: `Agent「${templateName}」已存在且配置不同。请先执行 /agent rm ${templateName}` };
17775
+ }
17724
17776
  const updated = await context.configStore.upsertAgent(templateName, template);
17725
17777
  context.replaceConfig(updated);
17726
17778
  return { text: `Agent「${templateName}」已保存` };
@@ -17745,7 +17797,7 @@ var init_agent_handler = __esm(() => {
17745
17797
  summary: "管理已注册的 Agent。",
17746
17798
  commands: [
17747
17799
  { usage: "/agents", description: "查看当前已注册的 Agent" },
17748
- { usage: "/agent add <codex|claude|opencode|gemini>", description: "添加内置 Agent 模板" },
17800
+ { usage: `/agent add <${listAgentTemplates().join("|")}>`, description: "添加内置 Agent 模板" },
17749
17801
  { usage: "/agent rm <name>", description: "删除一个 Agent" }
17750
17802
  ],
17751
17803
  examples: ["/agent add claude", "/agent rm codex"]
@@ -19259,20 +19311,14 @@ class OrchestrationServer {
19259
19311
  return await this.dispatchTaskGet(params);
19260
19312
  case "task.list":
19261
19313
  return await this.handlers.listTasks(this.parseTaskListFilter(params));
19262
- case "task.wait":
19263
- return await this.handlers.waitTask(this.parseWaitTaskInput(params));
19314
+ case "task.watch":
19315
+ return await this.handlers.watchTask(this.parseWatchTaskInput(params));
19264
19316
  case "task.approve":
19265
19317
  requireOnlyKeys(params, ["taskId", "coordinatorSession"], "params");
19266
19318
  return await this.handlers.approveTask({
19267
19319
  taskId: requireString(params, "taskId"),
19268
19320
  coordinatorSession: requireString(params, "coordinatorSession")
19269
19321
  });
19270
- case "task.reject":
19271
- requireOnlyKeys(params, ["taskId", "coordinatorSession"], "params");
19272
- return await this.handlers.rejectTask({
19273
- taskId: requireString(params, "taskId"),
19274
- coordinatorSession: requireString(params, "coordinatorSession")
19275
- });
19276
19322
  case "task.cancel":
19277
19323
  return await this.handlers.cancelTask(this.parseCancelTaskInput(params));
19278
19324
  case "worker.reply":
@@ -19305,15 +19351,6 @@ class OrchestrationServer {
19305
19351
  ...expectedActivePackageId !== undefined ? { expectedActivePackageId } : {}
19306
19352
  });
19307
19353
  }
19308
- case "coordinator.follow_up_human_package":
19309
- requireOnlyKeys(params, ["coordinatorSession", "packageId", "priorMessageId", "taskQuestions", "promptText"], "params");
19310
- return await this.handlers.coordinatorFollowUpHumanPackage({
19311
- coordinatorSession: requireString(params, "coordinatorSession"),
19312
- packageId: requireString(params, "packageId"),
19313
- priorMessageId: requireString(params, "priorMessageId"),
19314
- taskQuestions: requireTaskQuestions(params, "taskQuestions"),
19315
- promptText: requireString(params, "promptText")
19316
- });
19317
19354
  case "coordinator.review_contested_result":
19318
19355
  requireOnlyKeys(params, ["coordinatorSession", "taskId", "reviewId", "decision"], "params");
19319
19356
  return await this.handlers.coordinatorReviewContestedResult({
@@ -19328,20 +19365,6 @@ class OrchestrationServer {
19328
19365
  coordinatorSession: requireString(params, "coordinatorSession"),
19329
19366
  title: requireString(params, "title")
19330
19367
  });
19331
- case "group.get":
19332
- requireOnlyKeys(params, ["coordinatorSession", "groupId"], "params");
19333
- return await this.handlers.getGroupSummary({
19334
- coordinatorSession: requireString(params, "coordinatorSession"),
19335
- groupId: requireString(params, "groupId")
19336
- });
19337
- case "group.list":
19338
- return await this.handlers.listGroupSummaries(this.parseGroupListFilter(params));
19339
- case "group.cancel":
19340
- requireOnlyKeys(params, ["coordinatorSession", "groupId"], "params");
19341
- return await this.handlers.cancelGroup({
19342
- coordinatorSession: requireString(params, "coordinatorSession"),
19343
- groupId: requireString(params, "groupId")
19344
- });
19345
19368
  default:
19346
19369
  throw new OrchestrationInvalidRequestError(`unsupported orchestration method: ${method}`);
19347
19370
  }
@@ -19436,13 +19459,19 @@ class OrchestrationServer {
19436
19459
  ...resultText !== undefined ? { resultText } : {}
19437
19460
  };
19438
19461
  }
19439
- parseWaitTaskInput(params) {
19440
- requireOnlyKeys(params, ["coordinatorSession", "taskId", "timeoutMs", "pollIntervalMs"], "params");
19441
- const timeoutMs = requireOptionalIntegerInRange(params, "timeoutMs", 0, MAX_TASK_WAIT_TIMEOUT_MS);
19442
- const pollIntervalMs = requireOptionalIntegerInRange(params, "pollIntervalMs", 1, MAX_TASK_WAIT_POLL_INTERVAL_MS);
19462
+ parseWatchTaskInput(params) {
19463
+ requireOnlyKeys(params, ["coordinatorSession", "taskId", "afterSeq", "mode", "includeProgress", "timeoutMs", "pollIntervalMs"], "params");
19464
+ const afterSeq = requireOptionalIntegerInRange(params, "afterSeq", 0, Number.MAX_SAFE_INTEGER);
19465
+ const mode = requireOptionalEnum(params, "mode", ["next_event", "until_attention_or_terminal"]);
19466
+ const includeProgress = requireOptionalBoolean(params, "includeProgress");
19467
+ const timeoutMs = requireOptionalIntegerInRange(params, "timeoutMs", 0, MAX_TASK_WATCH_TIMEOUT_MS);
19468
+ const pollIntervalMs = requireOptionalIntegerInRange(params, "pollIntervalMs", 1, MAX_TASK_WATCH_POLL_INTERVAL_MS);
19443
19469
  return {
19444
19470
  coordinatorSession: requireString(params, "coordinatorSession"),
19445
19471
  taskId: requireString(params, "taskId"),
19472
+ ...afterSeq !== undefined ? { afterSeq } : {},
19473
+ ...mode !== undefined ? { mode } : {},
19474
+ ...includeProgress !== undefined ? { includeProgress } : {},
19446
19475
  ...timeoutMs !== undefined ? { timeoutMs } : {},
19447
19476
  ...pollIntervalMs !== undefined ? { pollIntervalMs } : {}
19448
19477
  };
@@ -19457,20 +19486,6 @@ class OrchestrationServer {
19457
19486
  whatIsNeeded: requireString(params, "whatIsNeeded")
19458
19487
  };
19459
19488
  }
19460
- parseGroupListFilter(params) {
19461
- requireOnlyKeys(params, ["coordinatorSession", "status", "stuck", "sort", "order"], "params");
19462
- const status = requireOptionalEnum(params, "status", ["pending", "running", "terminal"]);
19463
- const stuck = requireOptionalBoolean(params, "stuck");
19464
- const sort = requireOptionalEnum(params, "sort", ["updatedAt", "createdAt"]);
19465
- const order = requireOptionalEnum(params, "order", ["asc", "desc"]);
19466
- return {
19467
- coordinatorSession: requireString(params, "coordinatorSession"),
19468
- ...status !== undefined ? { status } : {},
19469
- ...stuck !== undefined ? { stuck } : {},
19470
- ...sort !== undefined ? { sort } : {},
19471
- ...order !== undefined ? { order } : {}
19472
- };
19473
- }
19474
19489
  async cleanupEndpoint() {
19475
19490
  if (this.endpoint.kind !== "unix") {
19476
19491
  return;
@@ -19681,7 +19696,7 @@ function isServerNotRunningError(error2) {
19681
19696
  var OrchestrationInvalidRequestError, ORCHESTRATION_RPC_METHODS;
19682
19697
  var init_orchestration_server = __esm(() => {
19683
19698
  init_orchestration_ipc();
19684
- init_task_wait_timeouts();
19699
+ init_task_watch_timeouts();
19685
19700
  OrchestrationInvalidRequestError = class OrchestrationInvalidRequestError extends Error {
19686
19701
  };
19687
19702
  ORCHESTRATION_RPC_METHODS = new Set([
@@ -19689,50 +19704,107 @@ var init_orchestration_server = __esm(() => {
19689
19704
  "delegate.request",
19690
19705
  "task.get",
19691
19706
  "task.list",
19692
- "task.wait",
19707
+ "task.watch",
19693
19708
  "task.approve",
19694
- "task.reject",
19695
19709
  "task.cancel",
19696
19710
  "worker.reply",
19697
19711
  "worker.raise_question",
19698
19712
  "coordinator.answer_question",
19699
19713
  "coordinator.retract_answer",
19700
19714
  "coordinator.request_human_input",
19701
- "coordinator.follow_up_human_package",
19702
19715
  "coordinator.review_contested_result",
19703
- "group.new",
19704
- "group.get",
19705
- "group.list",
19706
- "group.cancel"
19716
+ "group.new"
19707
19717
  ]);
19708
19718
  });
19709
19719
 
19710
19720
  // src/orchestration/progress-line-parser.ts
19711
19721
  class ProgressLineBuffer {
19712
- feed(segment) {
19722
+ pending = "";
19723
+ feed(segment, options = {}) {
19724
+ const hadPending = this.pending.length > 0;
19725
+ this.pending += segment;
19713
19726
  const summaries = [];
19714
- for (const line of segment.split(`
19715
- `)) {
19716
- if (line.startsWith(PROGRESS_PREFIX)) {
19717
- const summary = line.slice(PROGRESS_PREFIX.length).trim();
19718
- if (summary.length > 0) {
19719
- summaries.push(summary);
19720
- }
19721
- }
19727
+ let newlineIndex = this.pending.indexOf(`
19728
+ `);
19729
+ while (newlineIndex !== -1) {
19730
+ const line = this.pending.slice(0, newlineIndex).replace(/\r$/, "");
19731
+ this.pending = this.pending.slice(newlineIndex + 1);
19732
+ this.extractLine(line, summaries);
19733
+ newlineIndex = this.pending.indexOf(`
19734
+ `);
19735
+ }
19736
+ if (options.segmentComplete === true && !hadPending && this.pending.startsWith(PROGRESS_PREFIX)) {
19737
+ this.extractLine(this.pending.replace(/\r$/, ""), summaries);
19738
+ this.pending = "";
19739
+ return summaries;
19740
+ }
19741
+ this.trimPendingIfHopeless();
19742
+ return summaries;
19743
+ }
19744
+ flush() {
19745
+ const summaries = [];
19746
+ if (this.pending.length > 0) {
19747
+ this.extractLine(this.pending.replace(/\r$/, ""), summaries);
19748
+ this.pending = "";
19722
19749
  }
19723
19750
  return summaries;
19724
19751
  }
19752
+ extractLine(line, summaries) {
19753
+ if (line.startsWith(PROGRESS_PREFIX)) {
19754
+ const summary = sanitizeProgressSummary(line.slice(PROGRESS_PREFIX.length));
19755
+ if (summary.length > 0) {
19756
+ summaries.push(summary);
19757
+ }
19758
+ }
19759
+ }
19760
+ trimPendingIfHopeless() {
19761
+ if (this.pending.length === 0)
19762
+ return;
19763
+ if (PROGRESS_PREFIX.startsWith(this.pending) || this.pending.startsWith(PROGRESS_PREFIX)) {
19764
+ if (this.pending.length > MAX_PENDING_LINE_LENGTH) {
19765
+ this.pending = "";
19766
+ }
19767
+ return;
19768
+ }
19769
+ this.pending = "";
19770
+ }
19771
+ }
19772
+ function sanitizeProgressSummary(summary) {
19773
+ const cleaned = summary.replace(/[\u0000-\u0008\u000B\u000C\u000E-\u001F\u007F]/g, "").trim();
19774
+ if (cleaned.length <= MAX_PROGRESS_SUMMARY_LENGTH) {
19775
+ return cleaned;
19776
+ }
19777
+ return `${cleaned.slice(0, MAX_PROGRESS_SUMMARY_LENGTH - 3)}...`;
19725
19778
  }
19726
19779
  function stripProgressLines(text) {
19727
- return text.split(`
19728
- `).filter((line) => !line.startsWith(PROGRESS_PREFIX)).join(`
19780
+ return text.split(/\r\n|\n|\r/).filter((line) => {
19781
+ const normalized = normalizeProgressLinePrefix(line);
19782
+ return !normalized.startsWith(PROGRESS_PREFIX) && !(line.length > 0 && normalized.length === 0);
19783
+ }).join(`
19729
19784
  `).trim();
19730
19785
  }
19731
- var PROGRESS_PREFIX = "[PROGRESS]";
19786
+ function normalizeProgressLinePrefix(line) {
19787
+ return line.replace(/^(?:\r+|\u001B\[[0-?]*[ -/]*[@-~])+/, "");
19788
+ }
19789
+ var PROGRESS_PREFIX = "[PROGRESS]", MAX_PROGRESS_SUMMARY_LENGTH = 500, MAX_PENDING_LINE_LENGTH = 4096;
19732
19790
 
19733
19791
  // src/orchestration/orchestration-service.ts
19734
19792
  import { createHash as createHash2 } from "node:crypto";
19735
19793
  import { basename as basename2, isAbsolute as isAbsolute2, normalize } from "node:path";
19794
+ function clampWatchTimeout(value) {
19795
+ if (value === undefined)
19796
+ return DEFAULT_TASK_WATCH_TIMEOUT_MS;
19797
+ if (!Number.isFinite(value) || value < 0)
19798
+ return 0;
19799
+ return Math.min(Math.floor(value), MAX_TASK_WATCH_TIMEOUT_MS);
19800
+ }
19801
+ function clampWatchPollInterval(value) {
19802
+ if (value === undefined)
19803
+ return DEFAULT_TASK_WATCH_POLL_INTERVAL_MS;
19804
+ if (!Number.isFinite(value) || value < 1)
19805
+ return DEFAULT_TASK_WATCH_POLL_INTERVAL_MS;
19806
+ return Math.min(value, MAX_TASK_WATCH_POLL_INTERVAL_MS);
19807
+ }
19736
19808
 
19737
19809
  class OrchestrationService {
19738
19810
  deps;
@@ -19818,15 +19890,6 @@ class OrchestrationService {
19818
19890
  this.logEvent("orchestration.group.created", "group created", this.groupContext(group));
19819
19891
  return group;
19820
19892
  }
19821
- async getGroup(groupId) {
19822
- const state = await this.deps.loadState();
19823
- const group = this.ensureGroups(state)[groupId];
19824
- return group ? { ...group } : null;
19825
- }
19826
- async listGroups(coordinatorSession) {
19827
- const state = await this.deps.loadState();
19828
- return Object.values(this.ensureGroups(state)).filter((group) => coordinatorSession === undefined || group.coordinatorSession === coordinatorSession).sort((left, right) => left.createdAt.localeCompare(right.createdAt)).map((group) => ({ ...group }));
19829
- }
19830
19893
  async getGroupSummary(input) {
19831
19894
  const state = await this.deps.loadState();
19832
19895
  const group = this.ensureGroups(state)[input.groupId];
@@ -19949,6 +20012,8 @@ class OrchestrationService {
19949
20012
  resultText: "",
19950
20013
  createdAt: now,
19951
20014
  updatedAt: now,
20015
+ eventSeq: 1,
20016
+ events: [{ seq: 1, at: now, type: "created", status: "running", message: "Task created" }],
19952
20017
  ...input.chatKey ? { chatKey: input.chatKey } : {},
19953
20018
  ...input.replyContextToken ? { replyContextToken: input.replyContextToken } : {},
19954
20019
  ...input.accountId ? { accountId: input.accountId } : {}
@@ -20072,7 +20137,9 @@ class OrchestrationService {
20072
20137
  summary: "",
20073
20138
  resultText: "",
20074
20139
  createdAt: now,
20075
- updatedAt: now
20140
+ updatedAt: now,
20141
+ eventSeq: 1,
20142
+ events: [{ seq: 1, at: now, type: "created", status, message: "Task created" }]
20076
20143
  };
20077
20144
  if (preflight.normalizedGroupId) {
20078
20145
  const group = this.ensureGroups(state)[preflight.normalizedGroupId];
@@ -20243,6 +20310,11 @@ class OrchestrationService {
20243
20310
  current.summary = message;
20244
20311
  current.resultText = "";
20245
20312
  current.updatedAt = now;
20313
+ this.appendTaskEvent(current, now, "status_changed", {
20314
+ status: "failed",
20315
+ summary: message,
20316
+ message: "Task failed during startup"
20317
+ });
20246
20318
  restoreOrDeleteBinding();
20247
20319
  await this.deps.saveState(state);
20248
20320
  return true;
@@ -20335,6 +20407,11 @@ class OrchestrationService {
20335
20407
  task2.status = input.status ?? "completed";
20336
20408
  task2.summary = input.summary ?? "";
20337
20409
  task2.resultText = stripProgressLines(input.resultText ?? "");
20410
+ this.appendTaskEvent(task2, updatedAt, "status_changed", {
20411
+ status: task2.status,
20412
+ summary: task2.summary,
20413
+ message: task2.status === "completed" ? "Task completed" : task2.status === "failed" ? "Task failed" : "Task cancelled"
20414
+ });
20338
20415
  if (task2.status === "completed" || task2.status === "failed") {
20339
20416
  if (!this.isExternalCoordinatorSession(state, task2.coordinatorSession)) {
20340
20417
  task2.injectionPending = true;
@@ -20363,6 +20440,10 @@ class OrchestrationService {
20363
20440
  resultId: this.deps.createId(),
20364
20441
  resultText: task2.resultText
20365
20442
  };
20443
+ this.appendTaskEvent(task2, updatedAt, "attention_required", {
20444
+ status: task2.status,
20445
+ message: "Task result requires contested review"
20446
+ });
20366
20447
  task2.correctionPending = undefined;
20367
20448
  task2.cancelRequestedAt = undefined;
20368
20449
  task2.cancelCompletedAt = undefined;
@@ -20447,26 +20528,60 @@ class OrchestrationService {
20447
20528
  const task = state.orchestration.tasks[taskId];
20448
20529
  return task ? { ...task } : null;
20449
20530
  }
20450
- async waitTask(input) {
20451
- const timeoutMs = clampWaitTimeout(input.timeoutMs);
20452
- const pollIntervalMs = clampPollInterval(input.pollIntervalMs);
20531
+ async watchTask(input) {
20532
+ const timeoutMs = clampWatchTimeout(input.timeoutMs);
20533
+ const pollIntervalMs = clampWatchPollInterval(input.pollIntervalMs);
20534
+ const afterSeq = Math.max(0, Math.floor(input.afterSeq ?? 0));
20535
+ const mode = input.mode ?? "until_attention_or_terminal";
20536
+ const includeProgress = input.includeProgress ?? true;
20453
20537
  const deadline = Date.now() + timeoutMs;
20454
20538
  while (true) {
20455
20539
  const state = await this.deps.loadState();
20456
20540
  const task = state.orchestration.tasks[input.taskId];
20457
20541
  if (!task || task.coordinatorSession !== input.coordinatorSession) {
20458
- return { status: "not_found", task: null };
20542
+ return { status: "not_found", task: null, events: [], nextAfterSeq: afterSeq };
20459
20543
  }
20460
20544
  const snapshot = { ...task };
20545
+ const allEvents = task.events ?? [];
20546
+ const filteredEvents = allEvents.filter((event) => event.seq > afterSeq).filter((event) => includeProgress || event.type !== "progress");
20547
+ const nextAfterSeq = task.eventSeq ?? allEvents.at(-1)?.seq ?? afterSeq;
20548
+ const historyTruncated = allEvents.length > 0 && afterSeq < allEvents[0].seq - 1;
20461
20549
  if (isTerminalTaskStatus2(task.status) && task.reviewPending === undefined) {
20462
- return { status: "terminal", task: snapshot };
20550
+ return {
20551
+ status: "terminal",
20552
+ task: snapshot,
20553
+ events: filteredEvents.map((event) => ({ ...event })),
20554
+ nextAfterSeq,
20555
+ ...historyTruncated ? { historyTruncated } : {}
20556
+ };
20463
20557
  }
20464
20558
  if (isAttentionRequiredTask(task)) {
20465
- return { status: "attention_required", task: snapshot };
20559
+ return {
20560
+ status: "attention_required",
20561
+ task: snapshot,
20562
+ events: filteredEvents.map((event) => ({ ...event })),
20563
+ nextAfterSeq,
20564
+ ...historyTruncated ? { historyTruncated } : {}
20565
+ };
20566
+ }
20567
+ if (filteredEvents.length > 0 && mode === "next_event") {
20568
+ return {
20569
+ status: "event",
20570
+ task: snapshot,
20571
+ events: filteredEvents.map((event) => ({ ...event })),
20572
+ nextAfterSeq,
20573
+ ...historyTruncated ? { historyTruncated } : {}
20574
+ };
20466
20575
  }
20467
20576
  const remainingMs = deadline - Date.now();
20468
20577
  if (remainingMs <= 0) {
20469
- return { status: "timeout", task: snapshot };
20578
+ return {
20579
+ status: "timeout",
20580
+ task: snapshot,
20581
+ events: filteredEvents.map((event) => ({ ...event })),
20582
+ nextAfterSeq,
20583
+ ...historyTruncated ? { historyTruncated } : {}
20584
+ };
20470
20585
  }
20471
20586
  await sleep2(Math.min(pollIntervalMs, remainingMs));
20472
20587
  }
@@ -20550,6 +20665,10 @@ class OrchestrationService {
20550
20665
  status: "open"
20551
20666
  };
20552
20667
  task.updatedAt = now;
20668
+ this.appendTaskEvent(task, now, "attention_required", {
20669
+ status: "blocked",
20670
+ message: input.question.trim()
20671
+ });
20553
20672
  this.bumpGroupUpdated(state, task.groupId, now);
20554
20673
  await this.deps.saveState(state);
20555
20674
  return {
@@ -20607,6 +20726,10 @@ class OrchestrationService {
20607
20726
  lastResumeError: undefined
20608
20727
  };
20609
20728
  task.updatedAt = now;
20729
+ this.appendTaskEvent(task, now, "status_changed", {
20730
+ status: "running",
20731
+ message: "Blocker question answered"
20732
+ });
20610
20733
  this.bumpGroupUpdated(state, task.groupId, now);
20611
20734
  await this.deps.saveState(state);
20612
20735
  return {
@@ -20661,6 +20784,10 @@ class OrchestrationService {
20661
20784
  };
20662
20785
  task.cancelRequestedAt = task.cancelRequestedAt ?? now;
20663
20786
  task.updatedAt = now;
20787
+ this.appendTaskEvent(task, now, "cancel_requested", {
20788
+ status: task.status,
20789
+ message: "Correction requested for misrouted answer"
20790
+ });
20664
20791
  this.bumpGroupUpdated(state, task.groupId, now);
20665
20792
  await this.deps.saveState(state);
20666
20793
  return {
@@ -20679,6 +20806,10 @@ class OrchestrationService {
20679
20806
  task.noticePending = false;
20680
20807
  task.lastNoticeError = undefined;
20681
20808
  task.updatedAt = now;
20809
+ this.appendTaskEvent(task, now, "attention_required", {
20810
+ status: task.status,
20811
+ message: "Task result requires contested review"
20812
+ });
20682
20813
  this.bumpGroupUpdated(state, task.groupId, now);
20683
20814
  await this.deps.saveState(state);
20684
20815
  return {
@@ -20785,6 +20916,10 @@ class OrchestrationService {
20785
20916
  packageId
20786
20917
  };
20787
20918
  task.updatedAt = now;
20919
+ this.appendTaskEvent(task, now, "attention_required", {
20920
+ status: "waiting_for_human",
20921
+ message: task.openQuestion.question
20922
+ });
20788
20923
  this.bumpGroupUpdated(state, task.groupId, now);
20789
20924
  }
20790
20925
  this.ensureHumanQuestionPackages(state)[packageId] = packageRecord;
@@ -20811,84 +20946,6 @@ class OrchestrationService {
20811
20946
  queuedTaskIds: prepared.queuedTaskIds
20812
20947
  };
20813
20948
  }
20814
- async coordinatorFollowUpHumanPackage(input) {
20815
- const promptText = input.promptText.trim();
20816
- if (promptText.length === 0) {
20817
- throw new Error("promptText must be a non-empty string");
20818
- }
20819
- if (input.taskQuestions.length === 0) {
20820
- throw new Error("taskQuestions must contain at least one question");
20821
- }
20822
- const prepared = await this.mutate(async () => {
20823
- const state = await this.deps.loadState();
20824
- if (this.isExternalCoordinatorSession(state, input.coordinatorSession)) {
20825
- throw new Error("human input routing is not configured for external coordinator");
20826
- }
20827
- const coordinatorState = this.ensureCoordinatorQuestionState(state, input.coordinatorSession);
20828
- if (coordinatorState.activePackageId !== input.packageId) {
20829
- throw new Error(`package "${input.packageId}" is not the active package for coordinator "${input.coordinatorSession}"`);
20830
- }
20831
- const packageRecord = this.ensureHumanQuestionPackages(state)[input.packageId];
20832
- if (!packageRecord || packageRecord.status !== "active") {
20833
- throw new Error(`package "${input.packageId}" is not active`);
20834
- }
20835
- const latestMessage = packageRecord.messages.at(-1);
20836
- if (!latestMessage || latestMessage.messageId !== input.priorMessageId) {
20837
- throw new Error(`package "${input.packageId}" latest message is "${latestMessage?.messageId ?? ""}", not "${input.priorMessageId}"`);
20838
- }
20839
- if (!latestMessage.deliveredAt) {
20840
- throw new Error(`package "${input.packageId}" latest message "${latestMessage.messageId}" is not delivered yet`);
20841
- }
20842
- const tasks = input.taskQuestions.map(({ taskId, questionId }) => {
20843
- const task = state.orchestration.tasks[taskId];
20844
- if (!task) {
20845
- throw new Error(`task "${taskId}" does not exist`);
20846
- }
20847
- this.assertCoordinatorOwnership(task, input.coordinatorSession);
20848
- if (!packageRecord.openTaskIds.includes(taskId)) {
20849
- throw new Error(`task "${taskId}" does not belong to active package "${input.packageId}"`);
20850
- }
20851
- this.assertCoordinatorQuestionMatch(task, questionId);
20852
- return task;
20853
- });
20854
- const now = this.deps.now().toISOString();
20855
- const route = this.resolveFrozenPackageMessageRoute(latestMessage);
20856
- const messageId = this.deps.createId();
20857
- const message = {
20858
- messageId,
20859
- kind: "follow_up",
20860
- promptText,
20861
- createdAt: now,
20862
- taskQuestions: input.taskQuestions.map((entry) => ({ ...entry })),
20863
- ...route ? this.serializeFrozenDeliveryRoute(route) : {}
20864
- };
20865
- packageRecord.messages.push(message);
20866
- packageRecord.awaitingReplyMessageId = undefined;
20867
- packageRecord.updatedAt = now;
20868
- for (const task of tasks) {
20869
- task.status = "waiting_for_human";
20870
- task.openQuestion = {
20871
- ...task.openQuestion,
20872
- packageId: input.packageId
20873
- };
20874
- task.updatedAt = now;
20875
- this.bumpGroupUpdated(state, task.groupId, now);
20876
- }
20877
- await this.deps.saveState(state);
20878
- return {
20879
- coordinatorSession: input.coordinatorSession,
20880
- packageId: input.packageId,
20881
- messageId,
20882
- promptText,
20883
- route
20884
- };
20885
- });
20886
- await this.deliverHumanQuestionPackageMessage(prepared);
20887
- return {
20888
- packageId: prepared.packageId,
20889
- messageId: prepared.messageId
20890
- };
20891
- }
20892
20949
  async retryHumanQuestionPackageDelivery(input) {
20893
20950
  const prepared = await this.mutate(async () => {
20894
20951
  const state = await this.deps.loadState();
@@ -21055,11 +21112,21 @@ class OrchestrationService {
21055
21112
  task.summary = "";
21056
21113
  task.resultText = "";
21057
21114
  task.openQuestion = this.buildReplacementOpenQuestion(task, replacementQuestionId, now, packageId);
21115
+ this.appendTaskEvent(task, now, "attention_required", {
21116
+ status: task.status,
21117
+ message: task.openQuestion.question
21118
+ });
21058
21119
  } else if ((task.status === "completed" || task.status === "failed") && task.chatKey && task.replyContextToken && task.noticeSentAt === undefined) {
21059
21120
  task.noticePending = true;
21060
21121
  task.lastNoticeError = undefined;
21061
21122
  }
21062
21123
  task.updatedAt = now;
21124
+ if (input.decision === "accept") {
21125
+ this.appendTaskEvent(task, now, "status_changed", {
21126
+ status: task.status,
21127
+ message: "Contested result accepted"
21128
+ });
21129
+ }
21063
21130
  this.bumpGroupUpdated(state, task.groupId, now);
21064
21131
  await this.deps.saveState(state);
21065
21132
  return {
@@ -21437,7 +21504,7 @@ class OrchestrationService {
21437
21504
  });
21438
21505
  }
21439
21506
  }
21440
- async recordTaskProgress(taskId) {
21507
+ async recordTaskProgress(taskId, summary) {
21441
21508
  return await this.mutate(async () => {
21442
21509
  const state = await this.deps.loadState();
21443
21510
  const task = state.orchestration.tasks[taskId];
@@ -21445,6 +21512,21 @@ class OrchestrationService {
21445
21512
  throw new Error(`task "${taskId}" does not exist`);
21446
21513
  }
21447
21514
  task.lastProgressAt = this.deps.now().toISOString();
21515
+ if (summary !== undefined) {
21516
+ const cleaned = sanitizeProgressSummary(summary);
21517
+ if (cleaned.length > 0) {
21518
+ task.lastProgressSummary = cleaned;
21519
+ this.appendTaskEvent(task, task.lastProgressAt, "progress", {
21520
+ status: task.status,
21521
+ summary: cleaned
21522
+ });
21523
+ }
21524
+ } else {
21525
+ this.appendTaskEvent(task, task.lastProgressAt, "progress", {
21526
+ status: task.status,
21527
+ message: "heartbeat"
21528
+ });
21529
+ }
21448
21530
  task.updatedAt = task.lastProgressAt;
21449
21531
  await this.deps.saveState(state);
21450
21532
  return { ...task };
@@ -21492,17 +21574,31 @@ class OrchestrationService {
21492
21574
  const shouldPropagate = task.cancelRequestedAt === undefined;
21493
21575
  task.cancelRequestedAt = task.cancelRequestedAt ?? now;
21494
21576
  task.updatedAt = now;
21577
+ if (shouldPropagate) {
21578
+ this.appendTaskEvent(task, now, "cancel_requested", {
21579
+ status: task.status,
21580
+ message: "Cancellation requested"
21581
+ });
21582
+ }
21495
21583
  this.bumpGroupUpdated(state, task.groupId, now);
21496
21584
  await this.deps.saveState(state);
21497
21585
  return { task: { ...task }, shouldPropagate, closedPackageId: undefined };
21498
21586
  }
21499
21587
  const closedPackageId = this.detachTaskFromQuestionFlows(state, task, now);
21588
+ const wasNeedsConfirmation = task.status === "needs_confirmation";
21500
21589
  task.status = "cancelled";
21590
+ if (wasNeedsConfirmation && task.summary.trim().length === 0) {
21591
+ task.summary = "rejected";
21592
+ }
21501
21593
  task.openQuestion = undefined;
21502
21594
  task.cancelRequestedAt = task.cancelRequestedAt ?? now;
21503
21595
  task.cancelCompletedAt = now;
21504
21596
  task.lastCancelError = undefined;
21505
21597
  task.updatedAt = now;
21598
+ this.appendTaskEvent(task, now, "status_changed", {
21599
+ status: "cancelled",
21600
+ message: "Task cancelled"
21601
+ });
21506
21602
  this.bumpGroupUpdated(state, task.groupId, now);
21507
21603
  await this.deps.saveState(state);
21508
21604
  return { task: { ...task }, shouldPropagate: false, closedPackageId };
@@ -21537,10 +21633,18 @@ class OrchestrationService {
21537
21633
  task.cancelRequestedAt = undefined;
21538
21634
  task.cancelCompletedAt = undefined;
21539
21635
  task.lastCancelError = undefined;
21636
+ this.appendTaskEvent(task, now, "attention_required", {
21637
+ status: task.status,
21638
+ message: task.openQuestion.question
21639
+ });
21540
21640
  } else {
21541
21641
  task.status = "cancelled";
21542
21642
  task.cancelCompletedAt = now;
21543
21643
  task.lastCancelError = undefined;
21644
+ this.appendTaskEvent(task, now, "status_changed", {
21645
+ status: "cancelled",
21646
+ message: "Task cancelled"
21647
+ });
21544
21648
  }
21545
21649
  task.updatedAt = now;
21546
21650
  this.bumpGroupUpdated(state, task.groupId, now);
@@ -21582,6 +21686,10 @@ class OrchestrationService {
21582
21686
  }
21583
21687
  task2.lastCancelError = errorMessage;
21584
21688
  task2.updatedAt = this.deps.now().toISOString();
21689
+ this.appendTaskEvent(task2, task2.updatedAt, "progress", {
21690
+ status: task2.status,
21691
+ message: `Cancellation failed: ${errorMessage}`
21692
+ });
21585
21693
  await this.deps.saveState(state);
21586
21694
  return { ...task2 };
21587
21695
  });
@@ -21639,6 +21747,10 @@ class OrchestrationService {
21639
21747
  task.workerSession = ensuredWorkerSession;
21640
21748
  task.status = "running";
21641
21749
  task.updatedAt = this.deps.now().toISOString();
21750
+ this.appendTaskEvent(task, task.updatedAt, "status_changed", {
21751
+ status: "running",
21752
+ message: "Task approved"
21753
+ });
21642
21754
  state.orchestration.workerBindings[ensuredWorkerSession] = {
21643
21755
  sourceHandle: ensuredWorkerSession,
21644
21756
  coordinatorSession: task.coordinatorSession,
@@ -21697,24 +21809,6 @@ class OrchestrationService {
21697
21809
  this.logEvent("orchestration.task.approved", "task approved", this.taskContext(prepared.task));
21698
21810
  return prepared.task;
21699
21811
  }
21700
- async rejectTask(input) {
21701
- const task = await this.mutate(async () => {
21702
- const state = await this.deps.loadState();
21703
- const task2 = state.orchestration.tasks[input.taskId];
21704
- if (!task2) {
21705
- throw new Error(`task "${input.taskId}" does not exist`);
21706
- }
21707
- this.assertCoordinatorOwnership(task2, input.coordinatorSession);
21708
- this.assertNeedsConfirmation(task2);
21709
- task2.status = "cancelled";
21710
- task2.summary = "rejected";
21711
- task2.updatedAt = this.deps.now().toISOString();
21712
- await this.deps.saveState(state);
21713
- return { ...task2 };
21714
- });
21715
- this.logEvent("orchestration.task.rejected", "task rejected", this.taskContext(task));
21716
- return task;
21717
- }
21718
21812
  async resolveWorkerSession(input) {
21719
21813
  const role = this.normalizeRole(input.role);
21720
21814
  const reusable = await this.deps.findReusableWorkerSession?.({
@@ -22637,6 +22731,20 @@ class OrchestrationService {
22637
22731
  }
22638
22732
  })();
22639
22733
  }
22734
+ appendTaskEvent(task, at, type, details = {}) {
22735
+ const nextSeq = (task.eventSeq ?? 0) + 1;
22736
+ task.eventSeq = nextSeq;
22737
+ const events = task.events ?? [];
22738
+ events.push({
22739
+ seq: nextSeq,
22740
+ at,
22741
+ type,
22742
+ ...details.status ? { status: details.status } : {},
22743
+ ...details.summary ? { summary: details.summary } : {},
22744
+ ...details.message ? { message: details.message } : {}
22745
+ });
22746
+ task.events = events.slice(-MAX_TASK_EVENTS_PER_TASK);
22747
+ }
22640
22748
  }
22641
22749
  function isTerminalTaskStatus2(status) {
22642
22750
  return status === "completed" || status === "failed" || status === "cancelled";
@@ -22644,33 +22752,16 @@ function isTerminalTaskStatus2(status) {
22644
22752
  function isAttentionRequiredTask(task) {
22645
22753
  return task.reviewPending !== undefined || task.status === "needs_confirmation" || task.status === "blocked" || task.status === "waiting_for_human";
22646
22754
  }
22647
- function clampWaitTimeout(timeoutMs) {
22648
- if (timeoutMs === undefined) {
22649
- return DEFAULT_TASK_WAIT_TIMEOUT_MS;
22650
- }
22651
- if (!Number.isFinite(timeoutMs) || timeoutMs < 0) {
22652
- return 0;
22653
- }
22654
- return Math.min(Math.floor(timeoutMs), MAX_TASK_WAIT_TIMEOUT_MS);
22655
- }
22656
- function clampPollInterval(pollIntervalMs) {
22657
- if (pollIntervalMs === undefined) {
22658
- return DEFAULT_TASK_WAIT_POLL_INTERVAL_MS;
22659
- }
22660
- if (!Number.isFinite(pollIntervalMs) || pollIntervalMs <= 0) {
22661
- return 1;
22662
- }
22663
- return Math.min(Math.floor(pollIntervalMs), MAX_TASK_WAIT_POLL_INTERVAL_MS);
22664
- }
22665
22755
  async function sleep2(ms) {
22666
22756
  await new Promise((resolve3) => setTimeout(resolve3, ms));
22667
22757
  }
22668
22758
  function isRequestDelegateInput(input) {
22669
22759
  return "sourceKind" in input;
22670
22760
  }
22761
+ var MAX_TASK_EVENTS_PER_TASK = 200;
22671
22762
  var init_orchestration_service = __esm(() => {
22672
22763
  init_quota_errors();
22673
- init_task_wait_timeouts();
22764
+ init_task_watch_timeouts();
22674
22765
  });
22675
22766
 
22676
22767
  // src/orchestration/worker-prompts.ts
@@ -25070,6 +25161,11 @@ async function buildApp(paths, deps = {}) {
25070
25161
  const config2 = await loadConfig(paths.configPath, {
25071
25162
  defaultLoggingLevel: deps.defaultLoggingLevel
25072
25163
  });
25164
+ const reloadRuntimeConfig = async () => {
25165
+ const updated = await configStore.load();
25166
+ replaceRuntimeConfig(config2, updated);
25167
+ return config2;
25168
+ };
25073
25169
  const logger2 = createAppLogger({
25074
25170
  filePath: resolveAppLogPath(paths.configPath),
25075
25171
  level: config2.logging.level,
@@ -25268,32 +25364,39 @@ async function buildApp(paths, deps = {}) {
25268
25364
  };
25269
25365
  };
25270
25366
  const launchWorkerTurn = (input) => {
25271
- const session = resolveWorkerRuntimeSession(input);
25272
- session.mcpCoordinatorSession = input.coordinatorSession;
25273
- session.mcpSourceHandle = input.workerSession;
25274
25367
  const workerDispatch = (async () => {
25275
25368
  let taskRecord;
25276
25369
  try {
25370
+ await reloadRuntimeConfig();
25371
+ const session = resolveWorkerRuntimeSession(input);
25372
+ session.mcpCoordinatorSession = input.coordinatorSession;
25373
+ session.mcpSourceHandle = input.workerSession;
25277
25374
  const progressBuffer = new ProgressLineBuffer;
25375
+ const recordProgress = async (summary) => {
25376
+ try {
25377
+ await orchestration.recordTaskProgress(input.taskId, summary);
25378
+ const taskState = await orchestration.getTask(input.taskId);
25379
+ if (taskState?.chatKey && taskState.replyContextToken && deps.channel) {
25380
+ await deps.channel.notifyTaskProgress(taskState, renderTaskProgress(taskState, summary));
25381
+ }
25382
+ } catch (error2) {
25383
+ await logger2.error("orchestration.progress.send_failed", "failed to send task progress", {
25384
+ taskId: input.taskId,
25385
+ message: error2 instanceof Error ? error2.message : String(error2)
25386
+ });
25387
+ }
25388
+ };
25278
25389
  const result = await transport.prompt(session, input.promptText, undefined, undefined, {
25279
25390
  onSegment: async (chunk) => {
25280
- const summaries = progressBuffer.feed(chunk);
25391
+ const summaries = progressBuffer.feed(chunk, { segmentComplete: true });
25281
25392
  for (const summary of summaries) {
25282
- try {
25283
- await orchestration.recordTaskProgress(input.taskId);
25284
- const taskState = await orchestration.getTask(input.taskId);
25285
- if (taskState?.chatKey && taskState.replyContextToken && deps.channel) {
25286
- await deps.channel.notifyTaskProgress(taskState, renderTaskProgress(taskState, summary));
25287
- }
25288
- } catch (error2) {
25289
- await logger2.error("orchestration.progress.send_failed", "failed to send task progress", {
25290
- taskId: input.taskId,
25291
- message: error2 instanceof Error ? error2.message : String(error2)
25292
- });
25293
- }
25393
+ await recordProgress(summary);
25294
25394
  }
25295
25395
  }
25296
25396
  });
25397
+ for (const summary of progressBuffer.flush()) {
25398
+ await recordProgress(summary);
25399
+ }
25297
25400
  taskRecord = await finalizeWorkerTurn({
25298
25401
  taskId: input.taskId,
25299
25402
  workerSession: input.workerSession,
@@ -25357,6 +25460,7 @@ async function buildApp(paths, deps = {}) {
25357
25460
  },
25358
25461
  stateMutex,
25359
25462
  ensureWorkerSession: async ({ workerSession, targetAgent, workspace, cwd, coordinatorSession }) => {
25463
+ await reloadRuntimeConfig();
25360
25464
  const session = resolveWorkerRuntimeSession({ workerSession, targetAgent, workspace, ...cwd ? { cwd } : {} });
25361
25465
  session.mcpCoordinatorSession = coordinatorSession;
25362
25466
  session.mcpSourceHandle = workerSession;
@@ -25479,6 +25583,9 @@ function replaceRuntimeState(target, source) {
25479
25583
  target.chat_contexts = source.chat_contexts;
25480
25584
  target.orchestration = source.orchestration;
25481
25585
  }
25586
+ function replaceRuntimeConfig(target, source) {
25587
+ Object.assign(target, source);
25588
+ }
25482
25589
  async function main() {
25483
25590
  const paths = resolveRuntimePaths();
25484
25591
  try {
@@ -26628,6 +26735,7 @@ var init_doctor2 = __esm(async () => {
26628
26735
  init_config_store();
26629
26736
  init_load_config();
26630
26737
  init_ensure_config();
26738
+ init_agent_templates();
26631
26739
  init_create_daemon_controller();
26632
26740
  init_daemon_files();
26633
26741
  import { randomUUID as randomUUID4 } from "node:crypto";
@@ -39067,9 +39175,8 @@ function requireHome(env) {
39067
39175
  }
39068
39176
 
39069
39177
  // src/mcp/weacpx-mcp-tools.ts
39070
- init_task_wait_timeouts();
39178
+ init_task_watch_timeouts();
39071
39179
  init_quota_errors();
39072
- var groupStatusSchema = exports_external.enum(["pending", "running", "terminal"]);
39073
39180
  var taskStatusSchema = exports_external.enum([
39074
39181
  "needs_confirmation",
39075
39182
  "running",
@@ -39082,6 +39189,7 @@ var taskStatusSchema = exports_external.enum([
39082
39189
  var sortSchema = exports_external.enum(["updatedAt", "createdAt"]);
39083
39190
  var orderSchema = exports_external.enum(["asc", "desc"]);
39084
39191
  var contestedDecisionSchema = exports_external.enum(["accept", "discard"]);
39192
+ var taskWatchModeSchema = exports_external.enum(["next_event", "until_attention_or_terminal"]);
39085
39193
  var taskQuestionSchema = exports_external.object({
39086
39194
  taskId: exports_external.string().min(1),
39087
39195
  questionId: exports_external.string().min(1)
@@ -39091,7 +39199,8 @@ function buildWeacpxMcpToolRegistry(input) {
39091
39199
  const tools = [
39092
39200
  {
39093
39201
  name: "delegate_request",
39094
- description: `Delegate a subtask to another agent under the current coordinator. Pass an absolute workingDirectory for the worker. After this returns status=running, call task_wait with the returned taskId to wait for completion before reporting back to the user; if status=needs_confirmation, wait for the user to approve (task_approve / task_reject) and do not call task_wait yet.${availableAgents && availableAgents.length > 0 ? ` Available agents: ${availableAgents.join(", ")}.` : ""}`,
39202
+ description: `Delegate a subtask to another agent under the current coordinator. Pass an absolute workingDirectory for the worker. Supports MCP Tasks when the client requests task execution: the tool can return a native task handle immediately, then clients can use tasks/get, tasks/result, tasks/list, and tasks/cancel.${availableAgents && availableAgents.length > 0 ? ` Available agents: ${availableAgents.join(", ")}.` : ""}`,
39203
+ execution: { taskSupport: "optional" },
39095
39204
  inputSchema: exports_external.object({
39096
39205
  targetAgent: exports_external.string().min(1),
39097
39206
  task: exports_external.string().min(1),
@@ -39110,71 +39219,46 @@ function buildWeacpxMcpToolRegistry(input) {
39110
39219
  })
39111
39220
  },
39112
39221
  {
39113
- name: "group_new",
39114
- description: "Create a new task group under the current coordinator. Use to batch multiple delegate_request calls together; pass the resulting groupId on each delegate so they share lifecycle and cancellation.",
39115
- inputSchema: exports_external.object({
39116
- title: exports_external.string().min(1)
39117
- }).strict(),
39118
- handler: async (args) => await asToolResult(async () => {
39119
- const group = await transport.createGroup({
39120
- coordinatorSession,
39121
- title: args.title
39122
- });
39123
- return createSuccessResult(renderGroupCreated(group), group);
39124
- })
39125
- },
39126
- {
39127
- name: "group_get",
39128
- description: "Fetch a single task-group summary under the current coordinator. Use to check aggregate progress when waiting on a batch of delegations.",
39129
- inputSchema: exports_external.object({
39130
- groupId: exports_external.string().min(1)
39131
- }).strict(),
39132
- handler: async (args) => await asToolResult(async () => {
39133
- const summary = await transport.getGroup({
39134
- coordinatorSession,
39135
- groupId: args.groupId
39136
- });
39137
- return createSuccessResult(summary ? renderGroupSummary(summary) : "Group not found.", { group: summary });
39138
- })
39139
- },
39140
- {
39141
- name: "group_list",
39142
- description: "List task groups under the current coordinator. Use to recover groupIds for an earlier batch.",
39143
- inputSchema: exports_external.object({
39144
- status: groupStatusSchema.optional(),
39145
- stuck: exports_external.boolean().optional(),
39146
- sort: sortSchema.optional(),
39147
- order: orderSchema.optional()
39148
- }).strict(),
39149
- handler: async (args) => await asToolResult(async () => {
39150
- const { status, stuck, sort, order } = args;
39151
- const summaries = await transport.listGroups({
39152
- coordinatorSession,
39153
- ...status !== undefined ? { status } : {},
39154
- ...stuck !== undefined ? { stuck } : {},
39155
- ...sort !== undefined ? { sort } : {},
39156
- ...order !== undefined ? { order } : {}
39157
- });
39158
- return createSuccessResult(renderGroupList(summaries), { groups: summaries });
39159
- })
39160
- },
39161
- {
39162
- name: "group_cancel",
39163
- description: "Cancel all unfinished tasks in a task group under the current coordinator. Use to abort a batch started via group_new + delegate_request.",
39222
+ name: "delegate_batch",
39223
+ description: `Delegate several subtasks at once. Pass a "tasks" array; when it holds 2+ tasks they are bound to one auto-created group, so their results are reported back to you together when the whole batch finishes — one handoff instead of one interruption per task. Use this whenever you have multiple parallel delegations. Returns one result per task in input order; a task that fails to start carries an "error" field and does not abort the rest. Legacy-style only: it does not support MCP task execution — use delegate_request for a single native task handle.`,
39164
39224
  inputSchema: exports_external.object({
39165
- groupId: exports_external.string().min(1)
39225
+ title: exports_external.string().min(1).optional(),
39226
+ tasks: exports_external.array(exports_external.object({
39227
+ targetAgent: exports_external.string().min(1),
39228
+ task: exports_external.string().min(1),
39229
+ workingDirectory: exports_external.string().min(1).optional(),
39230
+ role: exports_external.string().min(1).optional()
39231
+ }).strict()).min(1)
39166
39232
  }).strict(),
39167
39233
  handler: async (args) => await asToolResult(async () => {
39168
- const result = await transport.cancelGroup({
39234
+ const { title, tasks } = args;
39235
+ const groupId = tasks.length >= 2 ? (await transport.createGroup({
39169
39236
  coordinatorSession,
39170
- groupId: args.groupId
39171
- });
39172
- return createSuccessResult(renderGroupCancelSuccess(result), result);
39237
+ title: title ?? `Batch delegation (${tasks.length} tasks)`
39238
+ })).groupId : undefined;
39239
+ const results = [];
39240
+ for (const [index, entry] of tasks.entries()) {
39241
+ try {
39242
+ const result = await transport.delegateRequest({
39243
+ coordinatorSession,
39244
+ ...sourceHandle ? { sourceHandle } : {},
39245
+ targetAgent: entry.targetAgent,
39246
+ task: entry.task,
39247
+ ...entry.workingDirectory ? { workingDirectory: entry.workingDirectory } : {},
39248
+ ...entry.role ? { role: entry.role } : {},
39249
+ ...groupId ? { groupId } : {}
39250
+ });
39251
+ results.push({ index, taskId: result.taskId, status: result.status });
39252
+ } catch (error2) {
39253
+ results.push({ index, error: formatToolError(error2) });
39254
+ }
39255
+ }
39256
+ return createSuccessResult(renderDelegateBatchSuccess(groupId, results), { ...groupId ? { groupId } : {}, tasks: results });
39173
39257
  })
39174
39258
  },
39175
39259
  {
39176
39260
  name: "task_get",
39177
- description: "Fetch a single task under the current coordinator, including the worker's final result and any pending question. Use after task_wait returns to read the actual output before summarizing it for the user, or to inspect a task that requires attention.",
39261
+ description: "Fetch a single task under the current coordinator, including the worker's final result and any pending question. Use to inspect a task snapshot non-blockingly, read the actual output after completion, or inspect a task that requires attention.",
39178
39262
  inputSchema: exports_external.object({
39179
39263
  taskId: exports_external.string().min(1)
39180
39264
  }).strict(),
@@ -39209,7 +39293,7 @@ function buildWeacpxMcpToolRegistry(input) {
39209
39293
  },
39210
39294
  {
39211
39295
  name: "task_approve",
39212
- description: "Approve a pending task under the current coordinator. Use when delegate_request returned status=needs_confirmation and the user has authorized it; after approval, call task_wait.",
39296
+ description: "Approve a task that delegate_request returned as needs_confirmation, once the user has authorized it. The task then starts running.",
39213
39297
  inputSchema: exports_external.object({
39214
39298
  taskId: exports_external.string().min(1)
39215
39299
  }).strict(),
@@ -39221,23 +39305,9 @@ function buildWeacpxMcpToolRegistry(input) {
39221
39305
  return createSuccessResult(renderTaskApprovalSuccess(task), task);
39222
39306
  })
39223
39307
  },
39224
- {
39225
- name: "task_reject",
39226
- description: "Reject a pending task under the current coordinator. Use when delegate_request returned status=needs_confirmation and the user declined; no task_wait is needed afterwards.",
39227
- inputSchema: exports_external.object({
39228
- taskId: exports_external.string().min(1)
39229
- }).strict(),
39230
- handler: async (args) => await asToolResult(async () => {
39231
- const task = await transport.rejectTask({
39232
- coordinatorSession,
39233
- taskId: args.taskId
39234
- });
39235
- return createSuccessResult(renderTaskRejectionSuccess(task), task);
39236
- })
39237
- },
39238
39308
  {
39239
39309
  name: "task_cancel",
39240
- description: "Request cancellation for a task under the current coordinator. Use to abort a running delegation; the task transitions to a terminal state shortly after.",
39310
+ description: "Cancel a task under the current coordinator. Works in any non-terminal state: a running delegation is aborted, and a task still waiting for approval (needs_confirmation) is rejected. The task transitions to a terminal state shortly after.",
39241
39311
  inputSchema: exports_external.object({
39242
39312
  taskId: exports_external.string().min(1)
39243
39313
  }).strict(),
@@ -39250,24 +39320,28 @@ function buildWeacpxMcpToolRegistry(input) {
39250
39320
  })
39251
39321
  },
39252
39322
  {
39253
- name: "task_wait",
39254
- description: `Wait for a task to finish or require attention. Call this immediately after delegate_request (when status=running) unless you intend a fire-and-forget. Returns status=terminal (done; call task_get for the result), status=attention_required (call task_get first to read the task's current status, then branch: needs_confirmation -> task_approve or task_reject; blocked or waiting_for_human -> coordinator_answer_question; reviewPending set -> coordinator_review_contested_result; after resolving, call task_wait again), or status=timeout (still running; call task_wait again or task_get for a snapshot). Defaults: timeout ${DEFAULT_TASK_WAIT_TIMEOUT_MS} ms, poll interval ${DEFAULT_TASK_WAIT_POLL_INTERVAL_MS} ms. Maximums: timeout ${MAX_TASK_WAIT_TIMEOUT_MS} ms, poll interval ${MAX_TASK_WAIT_POLL_INTERVAL_MS} ms.`,
39323
+ name: "task_watch",
39324
+ description: `Long-poll a task for the next event, attention-required state, or terminal state. For MCP-task-capable clients, request task execution for this tool to create a background watcher: the call returns a native task handle immediately, and tasks/result returns when the watch condition is met. The native watcher is single-shot: it runs one watch cycle then terminates, so to keep watching start another task_watch with afterSeq set to the returned nextAfterSeq. Defaults: timeout ${DEFAULT_TASK_WATCH_TIMEOUT_MS} ms, poll interval ${DEFAULT_TASK_WATCH_POLL_INTERVAL_MS} ms. Maximums: timeout ${MAX_TASK_WATCH_TIMEOUT_MS} ms, poll interval ${MAX_TASK_WATCH_POLL_INTERVAL_MS} ms.`,
39325
+ execution: { taskSupport: "optional" },
39255
39326
  inputSchema: exports_external.object({
39256
39327
  taskId: exports_external.string().min(1),
39257
- timeoutMs: exports_external.number().int().min(0).max(MAX_TASK_WAIT_TIMEOUT_MS).optional(),
39258
- pollIntervalMs: exports_external.number().int().min(1).max(MAX_TASK_WAIT_POLL_INTERVAL_MS).optional()
39328
+ afterSeq: exports_external.number().int().min(0).optional(),
39329
+ mode: taskWatchModeSchema.optional(),
39330
+ includeProgress: exports_external.boolean().optional(),
39331
+ timeoutMs: exports_external.number().int().min(0).max(MAX_TASK_WATCH_TIMEOUT_MS).optional(),
39332
+ pollIntervalMs: exports_external.number().int().min(1).max(MAX_TASK_WATCH_POLL_INTERVAL_MS).optional()
39259
39333
  }).strict(),
39260
39334
  handler: async (args) => await asToolResult(async () => {
39261
- const result = await transport.waitTask({
39335
+ const result = await transport.watchTask({
39262
39336
  coordinatorSession,
39263
39337
  ...args
39264
39338
  });
39265
- return createSuccessResult(renderTaskWaitResult(result), result);
39339
+ return createSuccessResult(renderTaskWatchResult(result), result);
39266
39340
  })
39267
39341
  },
39268
39342
  {
39269
39343
  name: "worker_raise_question",
39270
- description: "Raise a blocker question for the current bound worker session. Worker-side only: call this from inside a delegated task when you are blocked and need the coordinator's input. Coordinators waiting on a delegation should not call this; use task_wait instead.",
39344
+ description: "Raise a blocker question for the current bound worker session. Worker-side only: call this from inside a delegated task when you are blocked and need the coordinator's input.",
39271
39345
  inputSchema: exports_external.object({
39272
39346
  taskId: exports_external.string().min(1),
39273
39347
  question: exports_external.string().min(1),
@@ -39287,7 +39361,7 @@ function buildWeacpxMcpToolRegistry(input) {
39287
39361
  },
39288
39362
  {
39289
39363
  name: "coordinator_answer_question",
39290
- description: "Answer a blocked worker question under the current coordinator. Use after task_wait returns status=attention_required and task_get shows a pending question; after answering, call task_wait again to keep waiting for the worker to finish.",
39364
+ description: "Answer a blocked worker question under the current coordinator. Use when task_get shows a pending question.",
39291
39365
  inputSchema: exports_external.object({
39292
39366
  taskId: exports_external.string().min(1),
39293
39367
  questionId: exports_external.string().min(1),
@@ -39317,23 +39391,6 @@ function buildWeacpxMcpToolRegistry(input) {
39317
39391
  return createSuccessResult(renderCoordinatorRequestHumanInputSuccess(result), result);
39318
39392
  })
39319
39393
  },
39320
- {
39321
- name: "coordinator_follow_up_human_package",
39322
- description: "Append a follow-up message to the active human question package under the current coordinator. Use to clarify or add context to an in-flight package created via coordinator_request_human_input.",
39323
- inputSchema: exports_external.object({
39324
- packageId: exports_external.string().min(1),
39325
- priorMessageId: exports_external.string().min(1),
39326
- taskQuestions: exports_external.array(taskQuestionSchema).min(1),
39327
- promptText: exports_external.string().min(1)
39328
- }).strict(),
39329
- handler: async (args) => await asToolResult(async () => {
39330
- const result = await transport.coordinatorFollowUpHumanPackage({
39331
- coordinatorSession,
39332
- ...args
39333
- });
39334
- return createSuccessResult(renderCoordinatorFollowUpHumanPackageSuccess(result), result);
39335
- })
39336
- },
39337
39394
  {
39338
39395
  name: "coordinator_review_contested_result",
39339
39396
  description: "Review a contested result under the current coordinator. Use when a worker's result has been challenged and the coordinator must decide accept or discard.",
@@ -39355,8 +39412,7 @@ function buildWeacpxMcpToolRegistry(input) {
39355
39412
  ];
39356
39413
  if (isExternalCoordinator) {
39357
39414
  const externalCoordinatorIncompatibleTools = new Set([
39358
- "coordinator_request_human_input",
39359
- "coordinator_follow_up_human_package"
39415
+ "coordinator_request_human_input"
39360
39416
  ]);
39361
39417
  return tools.filter((tool) => !externalCoordinatorIncompatibleTools.has(tool.name));
39362
39418
  }
@@ -39381,35 +39437,24 @@ async function asToolResult(action) {
39381
39437
  return createErrorResult(formatToolError(error2));
39382
39438
  }
39383
39439
  }
39384
- function renderTaskWaitResult(result) {
39385
- if (result.status === "not_found") {
39440
+ function renderTaskWatchResult(result) {
39441
+ if (result.status === "not_found" || !result.task) {
39386
39442
  return "Task not found.";
39387
39443
  }
39388
- if (!result.task) {
39389
- return `Task wait ${result.status.replace("_", " ")}; current state is unavailable.`;
39390
- }
39391
- if (result.status === "timeout") {
39392
- return [
39393
- `Task ${result.task.taskId} wait timed out; current state is ${result.task.status}.`,
39394
- `Next: call task_wait again with this taskId to keep waiting, or task_get for a snapshot.`
39395
- ].join(`
39396
- `);
39397
- }
39398
- if (result.status === "attention_required") {
39399
- return [
39400
- `Task ${result.task.taskId} requires attention; current state is ${result.task.status}.`,
39401
- `Next: call task_get to read the task's current status and any reviewPending / openQuestion fields, then branch by what you see:`,
39402
- ` - status=needs_confirmation -> task_approve or task_reject`,
39403
- ` - status=blocked or waiting_for_human -> coordinator_answer_question`,
39404
- ` - reviewPending set -> coordinator_review_contested_result`,
39405
- `After resolving, call task_wait again to keep waiting for the worker to finish.`
39406
- ].join(`
39407
- `);
39408
- }
39409
- return [
39410
- `Task ${result.task.taskId} reached terminal state ${result.task.status}.`,
39411
- `Next: call task_get to read the worker's final result before reporting back to the user.`
39412
- ].join(`
39444
+ const header = [
39445
+ `Task ${result.task.taskId} watch ${result.status.replace("_", " ")}; current state is ${result.task.status}.`,
39446
+ `- nextAfterSeq: ${result.nextAfterSeq}`,
39447
+ result.historyTruncated ? "- historyTruncated: true" : ""
39448
+ ].filter((line) => line.length > 0);
39449
+ const events = result.events.length > 0 ? [
39450
+ "- Events:",
39451
+ ...result.events.map((event) => {
39452
+ const detail = event.summary ?? event.message ?? event.status ?? "";
39453
+ return ` - #${event.seq} ${event.type} at ${event.at}${detail ? `: ${detail}` : ""}`;
39454
+ })
39455
+ ] : ["- Events: none"];
39456
+ const next = result.status === "terminal" ? "Next: call task_get to read the final result." : result.status === "attention_required" ? "Next: call task_get to inspect openQuestion / reviewPending, then resolve it with the recommended action tool." : `Next: call task_watch again with afterSeq=${result.nextAfterSeq} to keep watching, preferably as an MCP task if your client supports background task execution.`;
39457
+ return [...header, ...events, next].join(`
39413
39458
  `);
39414
39459
  }
39415
39460
  function createSuccessResult(text, structuredContent) {
@@ -39425,72 +39470,16 @@ function createErrorResult(message) {
39425
39470
  };
39426
39471
  }
39427
39472
  function renderDelegateSuccess(result) {
39428
- const next = result.status === "needs_confirmation" ? `Next: this delegation requires user approval; do not call task_wait yet. Tell the user, then call task_approve or task_reject based on their response.` : `Next: call task_wait with taskId="${result.taskId}" to wait for the worker to finish, then task_get to read the result before reporting back.`;
39473
+ const next = result.status === "needs_confirmation" ? `Next: this delegation requires user approval. Tell the user, then call task_approve or task_cancel based on their response.` : `Next: task "${result.taskId}" is running. Return this taskId to the user, call task_get/task_list for non-blocking progress snapshots, or task_watch to long-poll for the next event or terminal state.`;
39429
39474
  return [`Delegation task "${result.taskId}" created.`, `- Status: ${result.status}`, next].join(`
39430
39475
  `);
39431
39476
  }
39432
- function renderGroupCreated(group) {
39433
- return [`Task group "${group.groupId}" created.`, `- Title: ${group.title}`].join(`
39434
- `);
39435
- }
39436
- function renderGroupSummary(summary) {
39437
- const { group, tasks } = summary;
39438
- const lines = [
39439
- `Task group "${group.groupId}"`,
39440
- `- Title: ${group.title}`,
39441
- `- Coordinator session: ${group.coordinatorSession}`,
39442
- `- Total tasks: ${summary.totalTasks}`,
39443
- `- Pending approval: ${summary.pendingApprovalTasks}`,
39444
- `- Running: ${summary.runningTasks}`,
39445
- `- Completed: ${summary.completedTasks}`,
39446
- `- Failed: ${summary.failedTasks}`,
39447
- `- Cancelled: ${summary.cancelledTasks}`,
39448
- `- Terminal: ${summary.terminal ? "yes" : "no"}`
39449
- ];
39450
- if (group.injectionPending !== undefined) {
39451
- lines.push(`- Injection pending: ${group.injectionPending ? "yes" : "no"}`);
39452
- }
39453
- if (group.injectionAppliedAt) {
39454
- lines.push(`- Injection completed at: ${group.injectionAppliedAt}`);
39455
- }
39456
- if (group.lastInjectionError) {
39457
- lines.push(`- Last injection error: ${group.lastInjectionError}`);
39458
- }
39459
- if (tasks.length > 0) {
39460
- lines.push("- Members:");
39461
- for (const task of tasks) {
39462
- lines.push(` - ${task.taskId} [${task.status}] ${task.targetAgent}`);
39463
- }
39464
- }
39465
- return lines.join(`
39466
- `);
39467
- }
39468
- function renderGroupList(groups) {
39469
- if (groups.length === 0) {
39470
- return "There are no task groups under the current coordinator.";
39471
- }
39472
- return ["Task groups for the current coordinator:", ...groups.map((group) => renderGroupListItem(group))].join(`
39473
- `);
39474
- }
39475
- function renderGroupListItem(group) {
39476
- const reliability = group.group.injectionPending ? " | injection pending" : "";
39477
- return [
39478
- `- ${group.group.groupId}`,
39479
- group.group.title,
39480
- `total ${group.totalTasks}`,
39481
- `pending ${group.pendingApprovalTasks}`,
39482
- `running ${group.runningTasks}`,
39483
- `completed ${group.completedTasks}`,
39484
- `failed ${group.failedTasks}`,
39485
- `cancelled ${group.cancelledTasks}${reliability}`
39486
- ].join(" | ");
39487
- }
39488
- function renderGroupCancelSuccess(input) {
39489
- return [
39490
- `Task group "${input.summary.group.groupId}" cancellation requested.`,
39491
- `- Cancel requested: ${input.cancelledTaskIds.length}`,
39492
- `- Skipped terminal tasks: ${input.skippedTaskIds.length}`
39493
- ].join(`
39477
+ function renderDelegateBatchSuccess(groupId, results) {
39478
+ const lines = results.map((entry) => entry.error ? ` - #${entry.index}: failed to start — ${entry.error}` : ` - #${entry.index}: task "${entry.taskId}" (${entry.status})`);
39479
+ const started = results.filter((entry) => entry.taskId).length;
39480
+ const header = groupId ? `Batch delegation created group "${groupId}" with ${started}/${results.length} tasks started.` : `Delegation: ${started}/${results.length} task started.`;
39481
+ const next = started > 0 ? "Next: track the started tasks with task_get/task_list, or task_watch to long-poll. The group reports all results back together once every task is terminal." : "Next: every task failed to start — fix the errors above and retry.";
39482
+ return [header, "- Tasks:", ...lines, next].join(`
39494
39483
  `);
39495
39484
  }
39496
39485
  function renderTaskList(tasks) {
@@ -39530,6 +39519,8 @@ function renderTaskSummary(task) {
39530
39519
  header.push(`- Task: ${task.task}`);
39531
39520
  if (task.summary.trim().length > 0)
39532
39521
  header.push(`- Summary: ${task.summary}`);
39522
+ if (task.lastProgressSummary)
39523
+ header.push(`- Latest progress: ${task.lastProgressSummary}`);
39533
39524
  if (task.resultText.trim().length > 0)
39534
39525
  header.push(`- Result: ${task.resultText}`);
39535
39526
  const events = [];
@@ -39559,7 +39550,9 @@ function renderTaskSummary(task) {
39559
39550
  events.push({ at: task.updatedAt, event: "injection failed", detail: task.lastInjectionError });
39560
39551
  events.sort((a, b) => a.at.localeCompare(b.at));
39561
39552
  const timeline = events.length > 0 ? ["- Timeline:", ...events.map((e) => ` - [${e.at}] ${e.event}${e.detail ? `: ${e.detail}` : ""}`)] : [];
39562
- return [...header, ...timeline].join(`
39553
+ const isTerminal2 = task.status === "completed" || task.status === "failed" || task.status === "cancelled";
39554
+ const next = isTerminal2 ? ["Next: summarize this result for the user."] : [];
39555
+ return [...header, ...timeline, ...next].join(`
39563
39556
  `);
39564
39557
  }
39565
39558
  function renderTaskCancelRequest(task) {
@@ -39574,14 +39567,10 @@ function renderTaskApprovalSuccess(task) {
39574
39567
  return [
39575
39568
  `Task "${task.taskId}" approved.`,
39576
39569
  `- Current status: ${task.status}`,
39577
- `Next: call task_wait with taskId="${task.taskId}" to wait for the worker to finish, then task_get to read the result before reporting back.`
39570
+ `Next: use task_get/task_list for non-blocking progress snapshots, or task_watch to long-poll until the worker finishes; then task_get to read the final result.`
39578
39571
  ].join(`
39579
39572
  `);
39580
39573
  }
39581
- function renderTaskRejectionSuccess(task) {
39582
- return [`Task "${task.taskId}" rejected.`, `- Current status: ${task.status}`].join(`
39583
- `);
39584
- }
39585
39574
  function renderWorkerRaiseQuestionSuccess(task) {
39586
39575
  return [`Blocker question submitted for task "${task.taskId}".`, `- questionId: ${task.questionId}`].join(`
39587
39576
  `);
@@ -39595,10 +39584,6 @@ function renderCoordinatorRequestHumanInputSuccess(result) {
39595
39584
  `) : [`Queued the question in the current human question queue.`, `- Queued tasks: ${result.queuedTaskIds.length}`].join(`
39596
39585
  `);
39597
39586
  }
39598
- function renderCoordinatorFollowUpHumanPackageSuccess(result) {
39599
- return [`Appended follow-up to human package "${result.packageId}".`, `- messageId: ${result.messageId}`].join(`
39600
- `);
39601
- }
39602
39587
  function renderCoordinatorReviewContestedResultSuccess(task, decision) {
39603
39588
  const actionText = decision === "accept" ? "Accepted" : "Discarded";
39604
39589
  return [`${actionText} contested result for task "${task.taskId}".`, `- Current status: ${task.status}`].join(`
@@ -39616,7 +39601,7 @@ function formatToolError(error2) {
39616
39601
 
39617
39602
  // src/orchestration/orchestration-client.ts
39618
39603
  init_orchestration_ipc();
39619
- init_task_wait_timeouts();
39604
+ init_task_watch_timeouts();
39620
39605
  import { randomUUID } from "node:crypto";
39621
39606
  import { createConnection } from "node:net";
39622
39607
 
@@ -39641,15 +39626,12 @@ class OrchestrationClient {
39641
39626
  async listTasks(filter) {
39642
39627
  return await this.request("task.list", { filter });
39643
39628
  }
39644
- async waitTask(input) {
39645
- return await this.request("task.wait", input, getWaitRequestTimeoutMs(input.timeoutMs, this.timeoutMs));
39629
+ async watchTask(input) {
39630
+ return await this.request("task.watch", input, getWatchRequestTimeoutMs(input.timeoutMs, this.timeoutMs));
39646
39631
  }
39647
39632
  async approveTask(input) {
39648
39633
  return await this.request("task.approve", input);
39649
39634
  }
39650
- async rejectTask(input) {
39651
- return await this.request("task.reject", input);
39652
- }
39653
39635
  async cancelTask(input) {
39654
39636
  return await this.request("task.cancel", input);
39655
39637
  }
@@ -39671,24 +39653,12 @@ class OrchestrationClient {
39671
39653
  async coordinatorRequestHumanInput(input) {
39672
39654
  return await this.request("coordinator.request_human_input", input);
39673
39655
  }
39674
- async coordinatorFollowUpHumanPackage(input) {
39675
- return await this.request("coordinator.follow_up_human_package", input);
39676
- }
39677
39656
  async coordinatorReviewContestedResult(input) {
39678
39657
  return await this.request("coordinator.review_contested_result", input);
39679
39658
  }
39680
39659
  async createGroup(input) {
39681
39660
  return await this.request("group.new", input);
39682
39661
  }
39683
- async getGroup(input) {
39684
- return await this.request("group.get", input);
39685
- }
39686
- async listGroups(input) {
39687
- return await this.request("group.list", input);
39688
- }
39689
- async cancelGroup(input) {
39690
- return await this.request("group.cancel", input);
39691
- }
39692
39662
  async request(method, params, timeoutMs = this.timeoutMs) {
39693
39663
  const id = this.createId();
39694
39664
  return await new Promise((resolve, reject) => {
@@ -39755,10 +39725,10 @@ class OrchestrationClient {
39755
39725
  });
39756
39726
  }
39757
39727
  }
39758
- function getWaitRequestTimeoutMs(waitTimeoutMs, defaultTimeoutMs) {
39759
- const requestedWaitTimeoutMs = waitTimeoutMs === undefined ? undefined : Number.isFinite(waitTimeoutMs) ? waitTimeoutMs : 0;
39760
- const boundedWaitTimeoutMs = Math.min(Math.max(Math.floor(requestedWaitTimeoutMs ?? DEFAULT_TASK_WAIT_TIMEOUT_MS), 0), MAX_TASK_WAIT_TIMEOUT_MS);
39761
- return Math.max(defaultTimeoutMs, boundedWaitTimeoutMs + TASK_WAIT_RPC_TIMEOUT_PADDING_MS);
39728
+ function getWatchRequestTimeoutMs(watchTimeoutMs, defaultTimeoutMs) {
39729
+ const requestedWatchTimeoutMs = watchTimeoutMs === undefined ? undefined : Number.isFinite(watchTimeoutMs) ? watchTimeoutMs : 0;
39730
+ const boundedWatchTimeoutMs = Math.min(Math.max(Math.floor(requestedWatchTimeoutMs ?? DEFAULT_TASK_WATCH_TIMEOUT_MS), 0), MAX_TASK_WATCH_TIMEOUT_MS);
39731
+ return Math.max(defaultTimeoutMs, boundedWatchTimeoutMs + TASK_WATCH_RPC_TIMEOUT_PADDING_MS);
39762
39732
  }
39763
39733
 
39764
39734
  // src/mcp/weacpx-mcp-transport.ts
@@ -39774,9 +39744,6 @@ function createOrchestrationTransport(endpoint, deps = {}) {
39774
39744
  ...input.groupId !== undefined ? { groupId: input.groupId } : {}
39775
39745
  }),
39776
39746
  createGroup: async (input) => await client.createGroup(input),
39777
- getGroup: async (input) => await client.getGroup(input),
39778
- listGroups: async (input) => await client.listGroups(input),
39779
- cancelGroup: async (input) => await client.cancelGroup(input),
39780
39747
  getTask: async (input) => await client.getTaskForCoordinator(input),
39781
39748
  listTasks: async (input) => await client.listTasks({
39782
39749
  coordinatorSession: input.coordinatorSession,
@@ -39786,9 +39753,8 @@ function createOrchestrationTransport(endpoint, deps = {}) {
39786
39753
  ...input.order !== undefined ? { order: input.order } : {}
39787
39754
  }),
39788
39755
  approveTask: async (input) => await client.approveTask(input),
39789
- rejectTask: async (input) => await client.rejectTask(input),
39790
39756
  cancelTask: async (input) => await client.cancelTaskForCoordinator(input),
39791
- waitTask: async (input) => await client.waitTask(input),
39757
+ watchTask: async (input) => await client.watchTask(input),
39792
39758
  workerRaiseQuestion: async (input) => {
39793
39759
  const sourceHandle = input.sourceHandle.trim();
39794
39760
  if (sourceHandle.length === 0) {
@@ -39804,49 +39770,47 @@ function createOrchestrationTransport(endpoint, deps = {}) {
39804
39770
  },
39805
39771
  coordinatorAnswerQuestion: async (input) => await client.coordinatorAnswerQuestion(input),
39806
39772
  coordinatorRequestHumanInput: async (input) => await client.coordinatorRequestHumanInput(input),
39807
- coordinatorFollowUpHumanPackage: async (input) => await client.coordinatorFollowUpHumanPackage(input),
39808
39773
  coordinatorReviewContestedResult: async (input) => await client.coordinatorReviewContestedResult(input)
39809
39774
  };
39810
39775
  }
39811
39776
 
39812
39777
  // src/mcp/weacpx-mcp-server.ts
39778
+ var TASK_OPTIONS_CACHE_LIMIT = 1000;
39779
+ var TASKS_LIST_PAGE_SIZE = 100;
39780
+ var WATCH_TASKS_CACHE_LIMIT = 256;
39813
39781
  var WEACPX_MCP_SERVER_INSTRUCTIONS = [
39814
39782
  "Use these tools to orchestrate work across other agents under your coordinator session.",
39815
39783
  "",
39816
- "Typical lifecycle for a single delegation:",
39817
- "1. delegate_request returns { taskId, status }.",
39818
- " - status=running: the worker has started; go to step 2.",
39819
- " - status=needs_confirmation: tell the user, then call task_approve or task_reject based on their response. After task_approve, return to step 2 to wait for the worker. Do not call task_wait before approval.",
39820
- "2. task_wait(taskId) → blocks until the task is done, needs attention, or times out.",
39821
- " - status=terminal: go to step 3.",
39822
- " - status=attention_required: the task is in needs_confirmation / blocked / waiting_for_human, or has reviewPending set. Call task_get(taskId) to read the actual status and any openQuestion / reviewPending fields, then branch:",
39823
- " * needs_confirmation -> task_approve or task_reject (after approval, go back to step 2)",
39824
- " * blocked or waiting_for_human -> coordinator_answer_question (the answer can come from you or be relayed from a human you consulted)",
39825
- " * reviewPending set -> coordinator_review_contested_result with accept or discard",
39826
- " After resolving, call task_wait again to keep waiting.",
39827
- " - status=timeout: the task is still running. Call task_wait again to keep waiting, or task_get for a snapshot.",
39828
- "3. The task is terminal. Call task_get(taskId) to read the worker's final result, then summarize it for the user. Do not invent results that did not come from task_get.",
39784
+ "Delegate with delegate_request (one task) or delegate_batch (several at once). Each returns a taskId and a status.",
39785
+ "Then follow the task: clients that support MCP Tasks should request task execution on delegate_request / task_watch and poll with tasks/get / tasks/list / tasks/result; other clients use task_get / task_list for snapshots or task_watch to long-poll.",
39829
39786
  "",
39830
- "Batching: use group_new before a wave of delegate_request calls and pass groupId on each, then group_get / group_list / group_cancel to manage the batch.",
39831
- "Cancellation: task_cancel aborts a single running task; group_cancel aborts the whole batch.",
39832
- "Discovery: task_list / group_list recover taskIds and groupIds from earlier in the session.",
39787
+ "Most tool results end with a 'Next:' line telling you the concrete next step follow it when present. In short: status=needs_confirmation needs task_approve or task_cancel; a task that needs attention (blocked / waiting_for_human / a contested review) is resolved with coordinator_answer_question or coordinator_review_contested_result; a terminal task is read with task_get. Never report a result you did not read from task_get.",
39833
39788
  "",
39834
- "worker_raise_question is worker-side only — call it from inside a delegated task when you are blocked, not from the coordinator that is waiting on a delegation."
39789
+ "worker_raise_question is worker-side only — call it from inside a delegated task when you are blocked, not from the coordinator waiting on a delegation."
39835
39790
  ].join(`
39836
39791
  `);
39837
39792
  function createWeacpxMcpServer(options) {
39793
+ let getToolState;
39794
+ const taskOptionsById = new Map;
39795
+ const watchTasksById = new Map;
39838
39796
  const server = new Server({
39839
39797
  name: "weacpx-orchestration",
39840
39798
  version: readVersion()
39841
39799
  }, {
39842
39800
  capabilities: {
39843
- tools: {}
39801
+ tools: {},
39802
+ tasks: {
39803
+ list: {},
39804
+ cancel: {},
39805
+ requests: { tools: { call: {} } }
39806
+ }
39844
39807
  },
39845
- instructions: WEACPX_MCP_SERVER_INSTRUCTIONS
39808
+ instructions: WEACPX_MCP_SERVER_INSTRUCTIONS,
39809
+ taskStore: createWeacpxTaskStore(async () => await getToolState(), taskOptionsById, watchTasksById)
39846
39810
  });
39847
39811
  let toolState = null;
39848
39812
  let toolStatePromise = null;
39849
- async function getToolState() {
39813
+ getToolState = async function getToolState2() {
39850
39814
  if (toolState) {
39851
39815
  return toolState;
39852
39816
  }
@@ -39869,14 +39833,15 @@ function createWeacpxMcpServer(options) {
39869
39833
  toolStatePromise = null;
39870
39834
  });
39871
39835
  return await toolStatePromise;
39872
- }
39836
+ };
39873
39837
  server.setRequestHandler(ListToolsRequestSchema, async () => {
39874
39838
  const tools = (await getToolState()).tools;
39875
39839
  return {
39876
39840
  tools: tools.map((tool) => ({
39877
39841
  name: tool.name,
39878
39842
  description: tool.description,
39879
- inputSchema: normalizeInputSchemaJson(zodToJsonSchema(tool.inputSchema))
39843
+ inputSchema: normalizeInputSchemaJson(zodToJsonSchema(tool.inputSchema)),
39844
+ ...tool.execution ? { execution: tool.execution } : {}
39880
39845
  }))
39881
39846
  };
39882
39847
  });
@@ -39890,15 +39855,399 @@ function createWeacpxMcpServer(options) {
39890
39855
  if (!parsed.success) {
39891
39856
  throw new McpError(ErrorCode.InvalidParams, formatZodError(parsed.error));
39892
39857
  }
39858
+ if (request.params.task) {
39859
+ if (tool.name !== "delegate_request" && tool.name !== "task_watch") {
39860
+ throw new McpError(ErrorCode.InvalidParams, `Tool ${tool.name} does not support MCP task execution`);
39861
+ }
39862
+ if (tool.name === "delegate_request") {
39863
+ return await createDelegationMcpTask({
39864
+ state: await getToolState(),
39865
+ args: parsed.data,
39866
+ taskParams: request.params.task,
39867
+ taskOptionsById
39868
+ });
39869
+ }
39870
+ return await createWatchMcpTask({
39871
+ state: await getToolState(),
39872
+ args: parsed.data,
39873
+ taskParams: request.params.task,
39874
+ taskOptionsById,
39875
+ watchTasksById
39876
+ });
39877
+ }
39893
39878
  return await tool.handler(parsed.data);
39894
39879
  });
39880
+ server.setRequestHandler(GetTaskPayloadRequestSchema, async (request) => {
39881
+ const watchTask = watchTasksById.get(request.params.taskId);
39882
+ if (watchTask) {
39883
+ if (!watchTask.result) {
39884
+ throw new McpError(ErrorCode.InvalidRequest, `Task ${request.params.taskId} is still ${watchTask.task.status}`);
39885
+ }
39886
+ watchTasksById.delete(request.params.taskId);
39887
+ return watchTask.result;
39888
+ }
39889
+ const state = await getToolState();
39890
+ const task = await state.transport.getTask({
39891
+ coordinatorSession: state.coordinatorSession,
39892
+ taskId: request.params.taskId
39893
+ });
39894
+ if (!task) {
39895
+ throw new McpError(ErrorCode.InvalidParams, `Task not found: ${request.params.taskId}`);
39896
+ }
39897
+ return withRelatedTaskMeta(renderNativeTaskPayloadResult(task), task.taskId);
39898
+ });
39895
39899
  return server;
39896
39900
  }
39897
39901
  function buildToolState(options) {
39898
39902
  const tools = buildWeacpxMcpToolRegistry(options);
39899
39903
  return {
39900
39904
  tools,
39901
- toolMap: new Map(tools.map((tool) => [tool.name, tool]))
39905
+ toolMap: new Map(tools.map((tool) => [tool.name, tool])),
39906
+ transport: options.transport,
39907
+ coordinatorSession: options.coordinatorSession,
39908
+ sourceHandle: options.sourceHandle
39909
+ };
39910
+ }
39911
+ async function createDelegationMcpTask(input) {
39912
+ const delegateTool = input.state.toolMap.get("delegate_request");
39913
+ if (!delegateTool) {
39914
+ throw new McpError(ErrorCode.MethodNotFound, "delegate_request is not registered");
39915
+ }
39916
+ const result = await delegateTool.handler(input.args);
39917
+ if (result.isError) {
39918
+ throw new McpError(ErrorCode.InvalidRequest, result.content.map((item) => item.type === "text" ? item.text : "").filter(Boolean).join(`
39919
+ `) || "Delegation failed");
39920
+ }
39921
+ const structured = result.structuredContent;
39922
+ const taskId = typeof structured?.taskId === "string" ? structured.taskId : undefined;
39923
+ if (!taskId) {
39924
+ throw new McpError(ErrorCode.InternalError, "delegate_request did not return a taskId");
39925
+ }
39926
+ rememberTaskOptions(input.taskOptionsById, taskId, input.taskParams);
39927
+ const task = await input.state.transport.getTask({
39928
+ coordinatorSession: input.state.coordinatorSession,
39929
+ taskId
39930
+ });
39931
+ if (!task) {
39932
+ throw new McpError(ErrorCode.InternalError, `delegate_request created task "${taskId}" but it was not readable from orchestration state`);
39933
+ }
39934
+ return {
39935
+ task: toMcpTask(task, input.taskParams)
39936
+ };
39937
+ }
39938
+ async function createWatchMcpTask(input) {
39939
+ const taskId = input.args.taskId;
39940
+ if (typeof taskId !== "string" || taskId.length === 0) {
39941
+ throw new McpError(ErrorCode.InvalidParams, "task_watch requires taskId");
39942
+ }
39943
+ const baseTask = await input.state.transport.getTask({
39944
+ coordinatorSession: input.state.coordinatorSession,
39945
+ taskId
39946
+ });
39947
+ if (!baseTask) {
39948
+ throw new McpError(ErrorCode.InvalidParams, `Task not found: ${taskId}`);
39949
+ }
39950
+ const now = new Date().toISOString();
39951
+ const watchTaskId = `watch:${taskId}:${Date.now()}:${Math.random().toString(36).slice(2, 10)}`;
39952
+ rememberTaskOptions(input.taskOptionsById, watchTaskId, input.taskParams);
39953
+ const watchTask = toMcpTask({
39954
+ taskId: watchTaskId,
39955
+ status: "running",
39956
+ summary: `Watching task ${taskId}`,
39957
+ createdAt: now,
39958
+ updatedAt: now
39959
+ }, input.taskParams);
39960
+ registerWatchTask(input.watchTasksById, watchTaskId, { task: watchTask });
39961
+ runWatchMcpTask({
39962
+ state: input.state,
39963
+ args: input.args,
39964
+ watchTaskId,
39965
+ taskOptions: input.taskParams,
39966
+ watchTasksById: input.watchTasksById
39967
+ });
39968
+ return {
39969
+ task: watchTask
39970
+ };
39971
+ }
39972
+ async function runWatchMcpTask(input) {
39973
+ const args = input.args;
39974
+ try {
39975
+ const result = await input.state.transport.watchTask({
39976
+ coordinatorSession: input.state.coordinatorSession,
39977
+ ...args
39978
+ });
39979
+ if (!input.watchTasksById.has(input.watchTaskId))
39980
+ return;
39981
+ const now = new Date().toISOString();
39982
+ const mcpStatus = result.status === "attention_required" ? "input_required" : result.status === "not_found" ? "failed" : "completed";
39983
+ input.watchTasksById.set(input.watchTaskId, {
39984
+ task: {
39985
+ taskId: input.watchTaskId,
39986
+ status: mcpStatus,
39987
+ ttl: input.taskOptions.ttl ?? null,
39988
+ createdAt: input.watchTasksById.get(input.watchTaskId)?.task.createdAt ?? now,
39989
+ lastUpdatedAt: now,
39990
+ ...input.taskOptions.pollInterval !== undefined ? { pollInterval: input.taskOptions.pollInterval } : {},
39991
+ statusMessage: renderWatchTaskStatusMessage(result)
39992
+ },
39993
+ result: withRelatedTaskMeta(renderWatchMcpTaskResult(result, input.watchTaskId), result.task?.taskId ?? input.watchTaskId)
39994
+ });
39995
+ } catch (error2) {
39996
+ if (!input.watchTasksById.has(input.watchTaskId))
39997
+ return;
39998
+ const now = new Date().toISOString();
39999
+ const message = error2 instanceof Error ? error2.message : String(error2);
40000
+ input.watchTasksById.set(input.watchTaskId, {
40001
+ task: {
40002
+ taskId: input.watchTaskId,
40003
+ status: "failed",
40004
+ ttl: input.taskOptions.ttl ?? null,
40005
+ createdAt: input.watchTasksById.get(input.watchTaskId)?.task.createdAt ?? now,
40006
+ lastUpdatedAt: now,
40007
+ statusMessage: message
40008
+ },
40009
+ result: {
40010
+ content: [{ type: "text", text: `Task watch "${input.watchTaskId}" failed: ${message}` }],
40011
+ structuredContent: { watchTaskId: input.watchTaskId, error: message },
40012
+ isError: true
40013
+ }
40014
+ });
40015
+ }
40016
+ }
40017
+ function renderWatchTaskStatusMessage(result) {
40018
+ if (!result.task)
40019
+ return `Watch finished: ${result.status}`;
40020
+ return `Watch finished for ${result.task.taskId}: ${result.status}; task status ${result.task.status}; events ${result.events?.length ?? 0}`;
40021
+ }
40022
+ function renderWatchMcpTaskResult(result, watchTaskId) {
40023
+ if (result.status === "not_found" || !result.task) {
40024
+ return {
40025
+ content: [{ type: "text", text: `Task watch "${watchTaskId}" finished: watched task not found.` }],
40026
+ structuredContent: { watchTaskId, ...result },
40027
+ isError: true
40028
+ };
40029
+ }
40030
+ const header = [
40031
+ `Task watch "${watchTaskId}" finished with ${result.status.replace("_", " ")}.`,
40032
+ `Watched task ${result.task.taskId} is ${result.task.status}.`,
40033
+ `nextAfterSeq: ${result.nextAfterSeq}`,
40034
+ result.historyTruncated ? "historyTruncated: true" : ""
40035
+ ].filter((line) => line.length > 0);
40036
+ const events = result.events.length > 0 ? [
40037
+ "Events:",
40038
+ ...result.events.map((event) => {
40039
+ const detail = event.summary ?? event.message ?? event.status ?? "";
40040
+ return `- #${event.seq} ${event.type} at ${event.at}${detail ? `: ${detail}` : ""}`;
40041
+ })
40042
+ ] : ["Events: none"];
40043
+ const next = result.status === "terminal" ? "Next: call task_get on the watched task to read the final result." : result.status === "attention_required" ? "Next: call task_get on the watched task, then resolve openQuestion / reviewPending with the recommended action tool." : `Next: call task_watch again with afterSeq=${result.nextAfterSeq} to keep watching.`;
40044
+ return {
40045
+ content: [{ type: "text", text: [...header, ...events, next].join(`
40046
+ `) }],
40047
+ structuredContent: { watchTaskId, ...result }
40048
+ };
40049
+ }
40050
+ function createWeacpxTaskStore(resolveState, taskOptionsById, watchTasksById) {
40051
+ return {
40052
+ createTask: async () => {
40053
+ throw new Error("weacpx native MCP tasks are created by delegate_request");
40054
+ },
40055
+ getTask: async (taskId) => {
40056
+ const watchTask = watchTasksById.get(taskId);
40057
+ if (watchTask)
40058
+ return watchTask.task;
40059
+ const state = await resolveState();
40060
+ const task = await state.transport.getTask({ coordinatorSession: state.coordinatorSession, taskId });
40061
+ return task ? toMcpTask(task, taskOptionsById.get(taskId)) : null;
40062
+ },
40063
+ storeTaskResult: async () => {
40064
+ throw new Error("weacpx native MCP task results are stored by orchestration");
40065
+ },
40066
+ getTaskResult: async (taskId) => {
40067
+ const watchTask = watchTasksById.get(taskId);
40068
+ if (watchTask) {
40069
+ if (!watchTask.result) {
40070
+ throw new Error(`Task ${taskId} is still ${watchTask.task.status}`);
40071
+ }
40072
+ watchTasksById.delete(taskId);
40073
+ return watchTask.result;
40074
+ }
40075
+ const state = await resolveState();
40076
+ const task = await state.transport.getTask({ coordinatorSession: state.coordinatorSession, taskId });
40077
+ if (!task) {
40078
+ throw new Error(`Task not found: ${taskId}`);
40079
+ }
40080
+ return renderNativeTaskPayloadResult(task);
40081
+ },
40082
+ updateTaskStatus: async (taskId, status, statusMessage) => {
40083
+ const state = await resolveState();
40084
+ if (status === "cancelled") {
40085
+ await state.transport.cancelTask({ coordinatorSession: state.coordinatorSession, taskId });
40086
+ return;
40087
+ }
40088
+ throw new Error(`weacpx MCP task status is read-only (${status}${statusMessage ? `: ${statusMessage}` : ""})`);
40089
+ },
40090
+ listTasks: async (cursor) => {
40091
+ const state = await resolveState();
40092
+ const tasks = await state.transport.listTasks({
40093
+ coordinatorSession: state.coordinatorSession,
40094
+ sort: "updatedAt",
40095
+ order: "desc"
40096
+ });
40097
+ const watchTasks = Array.from(watchTasksById.values()).map((record3) => record3.task);
40098
+ pruneTaskOptions(taskOptionsById, new Set([...tasks.map((task) => task.taskId), ...watchTasks.map((task) => task.taskId)]));
40099
+ const offset = parseTaskListCursor(cursor);
40100
+ const allTasks = [
40101
+ ...watchTasks,
40102
+ ...tasks.map((task) => toMcpTask(task, taskOptionsById.get(task.taskId)))
40103
+ ].sort((a, b) => b.lastUpdatedAt.localeCompare(a.lastUpdatedAt));
40104
+ const page = allTasks.slice(offset, offset + TASKS_LIST_PAGE_SIZE);
40105
+ const nextOffset = offset + page.length;
40106
+ return {
40107
+ tasks: page,
40108
+ ...nextOffset < allTasks.length ? { nextCursor: String(nextOffset) } : {}
40109
+ };
40110
+ }
40111
+ };
40112
+ }
40113
+ function rememberTaskOptions(taskOptionsById, taskId, options) {
40114
+ taskOptionsById.set(taskId, normalizeCreateTaskOptions(options));
40115
+ while (taskOptionsById.size > TASK_OPTIONS_CACHE_LIMIT) {
40116
+ const oldestKey = taskOptionsById.keys().next().value;
40117
+ if (oldestKey === undefined)
40118
+ break;
40119
+ taskOptionsById.delete(oldestKey);
40120
+ }
40121
+ }
40122
+ function registerWatchTask(watchTasksById, watchTaskId, record3) {
40123
+ watchTasksById.set(watchTaskId, record3);
40124
+ while (watchTasksById.size > WATCH_TASKS_CACHE_LIMIT) {
40125
+ const oldestKey = watchTasksById.keys().next().value;
40126
+ if (oldestKey === undefined || oldestKey === watchTaskId)
40127
+ break;
40128
+ watchTasksById.delete(oldestKey);
40129
+ }
40130
+ }
40131
+ function pruneTaskOptions(taskOptionsById, taskIds) {
40132
+ for (const taskId of taskOptionsById.keys()) {
40133
+ if (!taskIds.has(taskId)) {
40134
+ taskOptionsById.delete(taskId);
40135
+ }
40136
+ }
40137
+ }
40138
+ function parseTaskListCursor(cursor) {
40139
+ if (!cursor)
40140
+ return 0;
40141
+ const offset = Number(cursor);
40142
+ if (!Number.isInteger(offset) || offset < 0) {
40143
+ throw new McpError(ErrorCode.InvalidParams, `Invalid tasks/list cursor: ${cursor}`);
40144
+ }
40145
+ return offset;
40146
+ }
40147
+ function renderNativeTaskPayloadResult(task) {
40148
+ if (toMcpTaskStatus(task) === "input_required") {
40149
+ return renderInputRequiredTaskResult(task);
40150
+ }
40151
+ if (task.status === "completed" || task.status === "failed" || task.status === "cancelled") {
40152
+ return renderNativeTaskResult(task);
40153
+ }
40154
+ throw new McpError(ErrorCode.InvalidRequest, `Task ${task.taskId} is still ${task.status}; use tasks/get until it is terminal or input_required`);
40155
+ }
40156
+ function withRelatedTaskMeta(result, taskId) {
40157
+ return {
40158
+ ...result,
40159
+ _meta: {
40160
+ ...result._meta,
40161
+ [RELATED_TASK_META_KEY]: { taskId }
40162
+ }
40163
+ };
40164
+ }
40165
+ function renderNativeTaskResult(task) {
40166
+ const isError = task.status === "failed" || task.status === "cancelled";
40167
+ const text = [
40168
+ `Task "${task.taskId}" finished with status ${task.status}.`,
40169
+ task.resultText.trim().length > 0 ? task.resultText : task.summary
40170
+ ].filter((line) => line.trim().length > 0).join(`
40171
+ `);
40172
+ return {
40173
+ content: [{ type: "text", text }],
40174
+ structuredContent: { task },
40175
+ ...isError ? { isError: true } : {}
40176
+ };
40177
+ }
40178
+ function renderInputRequiredTaskResult(task) {
40179
+ const actions = inputRequiredActions(task);
40180
+ const text = [
40181
+ `Task "${task.taskId}" requires input before it can continue.`,
40182
+ task.summary.trim().length > 0 ? task.summary : "",
40183
+ task.openQuestion ? `Open question: ${task.openQuestion.question}` : "",
40184
+ `Next: call task_get("${task.taskId}") to inspect details, then ${actions.join(" or ")}.`
40185
+ ].filter((line) => line.trim().length > 0).join(`
40186
+ `);
40187
+ return {
40188
+ content: [{ type: "text", text }],
40189
+ structuredContent: {
40190
+ task,
40191
+ nextAction: {
40192
+ kind: "input_required",
40193
+ taskId: task.taskId,
40194
+ recommendedTools: actions
40195
+ }
40196
+ }
40197
+ };
40198
+ }
40199
+ function inputRequiredActions(task) {
40200
+ const actions = [];
40201
+ if (task.status === "needs_confirmation") {
40202
+ actions.push("task_approve", "task_cancel");
40203
+ }
40204
+ if (task.status === "blocked" || task.status === "waiting_for_human" || task.openQuestion) {
40205
+ actions.push("coordinator_answer_question");
40206
+ }
40207
+ if (task.reviewPending) {
40208
+ actions.push("coordinator_review_contested_result");
40209
+ }
40210
+ return actions.length > 0 ? actions : ["task_get"];
40211
+ }
40212
+ function toMcpTask(task, options = {}) {
40213
+ const statusMessage = mcpTaskStatusMessage(task);
40214
+ return {
40215
+ taskId: task.taskId,
40216
+ status: toMcpTaskStatus(task),
40217
+ ttl: options.ttl ?? null,
40218
+ createdAt: task.createdAt,
40219
+ lastUpdatedAt: task.updatedAt,
40220
+ ...options.pollInterval !== undefined ? { pollInterval: options.pollInterval } : {},
40221
+ ...statusMessage ? { statusMessage } : {}
40222
+ };
40223
+ }
40224
+ function mcpTaskStatusMessage(task) {
40225
+ const lines = [
40226
+ task.summary.trim().length > 0 ? task.summary : "",
40227
+ task.lastProgressSummary ? `Latest progress: ${task.lastProgressSummary}` : "",
40228
+ task.lastProgressAt ? `Last progress at: ${task.lastProgressAt}` : ""
40229
+ ].filter((line) => line.trim().length > 0);
40230
+ return lines.length > 0 ? lines.join(`
40231
+ `) : undefined;
40232
+ }
40233
+ function toMcpTaskStatus(task) {
40234
+ if (task.reviewPending !== undefined)
40235
+ return "input_required";
40236
+ if (task.status === "completed")
40237
+ return "completed";
40238
+ if (task.status === "failed")
40239
+ return "failed";
40240
+ if (task.status === "cancelled")
40241
+ return "cancelled";
40242
+ if (task.status === "needs_confirmation" || task.status === "blocked" || task.status === "waiting_for_human") {
40243
+ return "input_required";
40244
+ }
40245
+ return "working";
40246
+ }
40247
+ function normalizeCreateTaskOptions(options) {
40248
+ return {
40249
+ ttl: options.ttl ?? null,
40250
+ ...options.pollInterval !== undefined ? { pollInterval: options.pollInterval } : {}
39902
40251
  };
39903
40252
  }
39904
40253
  async function resolveMcpIdentity(server, options) {
@@ -41834,6 +42183,7 @@ var HELP_LINES = [
41834
42183
  "weacpx plugin list|add|update|remove|enable|disable|doctor|known - 管理插件",
41835
42184
  "weacpx doctor - 运行诊断",
41836
42185
  "weacpx version - 查看版本",
42186
+ "weacpx agent|agents list|add|rm|templates - 管理本机 Agent",
41837
42187
  "weacpx workspace list|add|rm - 管理本机工作区(别名:ws)",
41838
42188
  "weacpx mcp-stdio [--coordinator-session <session>] [--source-handle <handle>] [--workspace <name>] - 启动 MCP stdio 服务"
41839
42189
  ];
@@ -41911,6 +42261,17 @@ async function runCli(args, deps = {}) {
41911
42261
  }
41912
42262
  return result;
41913
42263
  }
42264
+ case "agent":
42265
+ case "agents": {
42266
+ const result = await handleAgentCli(args.slice(1), { print });
42267
+ if (result === null) {
42268
+ for (const line of HELP_LINES) {
42269
+ print(line);
42270
+ }
42271
+ return 1;
42272
+ }
42273
+ return result;
42274
+ }
41914
42275
  case "plugin": {
41915
42276
  const result = await handlePluginCli(args.slice(1), await createPluginCliDeps({
41916
42277
  print,
@@ -42152,6 +42513,93 @@ async function workspaceRemove(rawName, print) {
42152
42513
  print(`工作区「${name}」已删除`);
42153
42514
  return 0;
42154
42515
  }
42516
+ async function handleAgentCli(args, deps) {
42517
+ const subcommand = args[0];
42518
+ switch (subcommand) {
42519
+ case "list":
42520
+ if (args.length !== 1)
42521
+ return null;
42522
+ return await agentList(deps.print);
42523
+ case "templates":
42524
+ if (args.length !== 1)
42525
+ return null;
42526
+ return agentTemplates(deps.print);
42527
+ case "add":
42528
+ if (args.length !== 2 || !args[1])
42529
+ return null;
42530
+ return await agentAdd(args[1], deps.print);
42531
+ case "rm":
42532
+ if (args.length !== 2 || !args[1])
42533
+ return null;
42534
+ return await agentRemove(args[1], deps.print);
42535
+ default:
42536
+ return null;
42537
+ }
42538
+ }
42539
+ async function agentList(print) {
42540
+ const store = await createCliConfigStore();
42541
+ const config2 = await store.load();
42542
+ const entries = Object.entries(config2.agents);
42543
+ if (entries.length === 0) {
42544
+ print("还没有 Agent。");
42545
+ return 0;
42546
+ }
42547
+ print("Agent 列表:");
42548
+ for (const [name, agent] of entries) {
42549
+ const command = agent.command ? ` command=${agent.command}` : "";
42550
+ print(`- ${name}: driver=${agent.driver}${command}`);
42551
+ }
42552
+ return 0;
42553
+ }
42554
+ function agentTemplates(print) {
42555
+ print("可用 Agent 模板:");
42556
+ for (const name of listAgentTemplates()) {
42557
+ print(`- ${name}`);
42558
+ }
42559
+ return 0;
42560
+ }
42561
+ async function agentAdd(rawName, print) {
42562
+ const name = rawName.trim();
42563
+ if (name.length === 0) {
42564
+ print("Agent 名称不能为空。");
42565
+ return 1;
42566
+ }
42567
+ const template = getAgentTemplate(name);
42568
+ if (!template) {
42569
+ print(`暂不支持这个 Agent 模板。当前可用:${listAgentTemplates().join("、")}`);
42570
+ return 1;
42571
+ }
42572
+ const store = await createCliConfigStore();
42573
+ const config2 = await store.load();
42574
+ const existing = config2.agents[name];
42575
+ if (existing) {
42576
+ if (sameAgentConfig(existing, template)) {
42577
+ print(`Agent「${name}」已存在`);
42578
+ return 0;
42579
+ }
42580
+ print(`Agent「${name}」已存在且配置不同。请先执行:weacpx agent rm ${name}`);
42581
+ return 1;
42582
+ }
42583
+ await store.upsertAgent(name, template);
42584
+ print(`Agent「${name}」已保存`);
42585
+ return 0;
42586
+ }
42587
+ async function agentRemove(rawName, print) {
42588
+ const name = rawName.trim();
42589
+ if (name.length === 0) {
42590
+ print("Agent 名称不能为空。");
42591
+ return 1;
42592
+ }
42593
+ const store = await createCliConfigStore();
42594
+ const config2 = await store.load();
42595
+ if (!config2.agents[name]) {
42596
+ print(`没有找到 Agent「${name}」。`);
42597
+ return 1;
42598
+ }
42599
+ await store.removeAgent(name);
42600
+ print(`Agent「${name}」已删除`);
42601
+ return 0;
42602
+ }
42155
42603
  async function createCliConfigStore() {
42156
42604
  const configPath = process.env.WEACPX_CONFIG ?? `${requireHome2()}/.weacpx/config.json`;
42157
42605
  await ensureConfigExists(configPath);