weacpx 0.4.4 → 0.4.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/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";
@@ -9542,10 +9618,11 @@ var PACKAGE_NAME = "weacpx";
9542
9618
  var init_version = () => {};
9543
9619
 
9544
9620
  // 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;
9621
+ 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, 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;
9546
9622
  var init_task_wait_timeouts = __esm(() => {
9547
9623
  DEFAULT_TASK_WAIT_TIMEOUT_MS = 5 * 60000;
9548
9624
  MAX_TASK_WAIT_TIMEOUT_MS = 20 * 60000;
9625
+ MAX_TASK_WATCH_TIMEOUT_MS = 20 * 60000;
9549
9626
  });
9550
9627
 
9551
9628
  // src/weixin/messaging/quota-errors.ts
@@ -9607,6 +9684,15 @@ function isTaskStatus(value) {
9607
9684
  function isSourceKind(value) {
9608
9685
  return value === "human" || value === "coordinator" || value === "worker";
9609
9686
  }
9687
+ function isOptionalNumber(value) {
9688
+ return value === undefined || typeof value === "number";
9689
+ }
9690
+ function isTaskEventRecord(value) {
9691
+ if (!isRecord2(value)) {
9692
+ return false;
9693
+ }
9694
+ 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);
9695
+ }
9610
9696
  function isOpenQuestionRecord(value) {
9611
9697
  if (!isRecord2(value)) {
9612
9698
  return false;
@@ -9629,7 +9715,7 @@ function isTaskRecord(value) {
9629
9715
  if (!isRecord2(value)) {
9630
9716
  return false;
9631
9717
  }
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));
9718
+ 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
9719
  }
9634
9720
  function isExternalCoordinatorRecord(value) {
9635
9721
  if (!isRecord2(value)) {
@@ -9893,37 +9979,6 @@ var init_state_store = __esm(() => {
9893
9979
  init_types();
9894
9980
  });
9895
9981
 
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
9982
  // src/plugins/plugin-home.ts
9928
9983
  import { mkdir as mkdir6, writeFile as writeFile5 } from "node:fs/promises";
9929
9984
  import { homedir as homedir3 } from "node:os";
@@ -15447,38 +15502,24 @@ function extractPromptFailureMessage(result) {
15447
15502
  function extractPromptOutput(output) {
15448
15503
  const lines = output.split(`
15449
15504
  `).map((line) => line.trim()).filter((line) => line.length > 0);
15450
- const messageSegments = [];
15451
- let currentSegment = "";
15505
+ let text = "";
15452
15506
  let hasAgentMessage = false;
15453
15507
  for (const line of lines) {
15508
+ let event;
15454
15509
  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 = "";
15510
+ event = JSON.parse(line);
15469
15511
  } catch {
15470
- if (currentSegment.trim().length > 0) {
15471
- messageSegments.push(currentSegment.trim());
15472
- currentSegment = "";
15473
- }
15512
+ continue;
15513
+ }
15514
+ 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";
15515
+ if (isMessageChunk) {
15516
+ hasAgentMessage = true;
15517
+ text += event.params.update.content.text ?? "";
15474
15518
  }
15475
15519
  }
15476
- if (currentSegment.trim().length > 0) {
15477
- messageSegments.push(currentSegment.trim());
15478
- }
15479
- if (messageSegments.length > 0) {
15520
+ if (hasAgentMessage && text.trim().length > 0) {
15480
15521
  return {
15481
- text: messageSegments[messageSegments.length - 1],
15522
+ text: text.trim(),
15482
15523
  hasAgentMessage
15483
15524
  };
15484
15525
  }
@@ -17278,6 +17319,8 @@ function renderTaskSummary2(task) {
17278
17319
  header.push(`- 任务:${task.task}`);
17279
17320
  if (task.summary.trim().length > 0)
17280
17321
  header.push(`- 摘要:${task.summary}`);
17322
+ if (task.lastProgressSummary)
17323
+ header.push(`- 最新进展:${task.lastProgressSummary}`);
17281
17324
  if (task.resultText.trim().length > 0)
17282
17325
  header.push(`- 结果:${task.resultText}`);
17283
17326
  const events = [];
@@ -17721,6 +17764,13 @@ async function handleAgentAdd(context, templateName) {
17721
17764
  if (!template) {
17722
17765
  return { text: `暂不支持这个 Agent 模板。当前可用:${listAgentTemplates().join("、")}` };
17723
17766
  }
17767
+ const existing = context.config.agents[templateName];
17768
+ if (existing) {
17769
+ if (sameAgentConfig(existing, template)) {
17770
+ return { text: `Agent「${templateName}」已存在` };
17771
+ }
17772
+ return { text: `Agent「${templateName}」已存在且配置不同。请先执行 /agent rm ${templateName}` };
17773
+ }
17724
17774
  const updated = await context.configStore.upsertAgent(templateName, template);
17725
17775
  context.replaceConfig(updated);
17726
17776
  return { text: `Agent「${templateName}」已保存` };
@@ -17745,7 +17795,7 @@ var init_agent_handler = __esm(() => {
17745
17795
  summary: "管理已注册的 Agent。",
17746
17796
  commands: [
17747
17797
  { usage: "/agents", description: "查看当前已注册的 Agent" },
17748
- { usage: "/agent add <codex|claude|opencode|gemini>", description: "添加内置 Agent 模板" },
17798
+ { usage: `/agent add <${listAgentTemplates().join("|")}>`, description: "添加内置 Agent 模板" },
17749
17799
  { usage: "/agent rm <name>", description: "删除一个 Agent" }
17750
17800
  ],
17751
17801
  examples: ["/agent add claude", "/agent rm codex"]
@@ -19261,6 +19311,8 @@ class OrchestrationServer {
19261
19311
  return await this.handlers.listTasks(this.parseTaskListFilter(params));
19262
19312
  case "task.wait":
19263
19313
  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({
@@ -19447,6 +19499,23 @@ class OrchestrationServer {
19447
19499
  ...pollIntervalMs !== undefined ? { pollIntervalMs } : {}
19448
19500
  };
19449
19501
  }
19502
+ parseWatchTaskInput(params) {
19503
+ requireOnlyKeys(params, ["coordinatorSession", "taskId", "afterSeq", "mode", "includeProgress", "timeoutMs", "pollIntervalMs"], "params");
19504
+ const afterSeq = requireOptionalIntegerInRange(params, "afterSeq", 0, Number.MAX_SAFE_INTEGER);
19505
+ const mode = requireOptionalEnum(params, "mode", ["next_event", "until_attention_or_terminal"]);
19506
+ const includeProgress = requireOptionalBoolean(params, "includeProgress");
19507
+ const timeoutMs = requireOptionalIntegerInRange(params, "timeoutMs", 0, MAX_TASK_WATCH_TIMEOUT_MS);
19508
+ const pollIntervalMs = requireOptionalIntegerInRange(params, "pollIntervalMs", 1, MAX_TASK_WATCH_POLL_INTERVAL_MS);
19509
+ return {
19510
+ coordinatorSession: requireString(params, "coordinatorSession"),
19511
+ taskId: requireString(params, "taskId"),
19512
+ ...afterSeq !== undefined ? { afterSeq } : {},
19513
+ ...mode !== undefined ? { mode } : {},
19514
+ ...includeProgress !== undefined ? { includeProgress } : {},
19515
+ ...timeoutMs !== undefined ? { timeoutMs } : {},
19516
+ ...pollIntervalMs !== undefined ? { pollIntervalMs } : {}
19517
+ };
19518
+ }
19450
19519
  parseWorkerRaiseQuestionInput(params) {
19451
19520
  requireOnlyKeys(params, ["taskId", "sourceHandle", "question", "whyBlocked", "whatIsNeeded"], "params");
19452
19521
  return {
@@ -19690,6 +19759,7 @@ var init_orchestration_server = __esm(() => {
19690
19759
  "task.get",
19691
19760
  "task.list",
19692
19761
  "task.wait",
19762
+ "task.watch",
19693
19763
  "task.approve",
19694
19764
  "task.reject",
19695
19765
  "task.cancel",
@@ -19709,30 +19779,92 @@ var init_orchestration_server = __esm(() => {
19709
19779
 
19710
19780
  // src/orchestration/progress-line-parser.ts
19711
19781
  class ProgressLineBuffer {
19712
- feed(segment) {
19782
+ pending = "";
19783
+ feed(segment, options = {}) {
19784
+ const hadPending = this.pending.length > 0;
19785
+ this.pending += segment;
19713
19786
  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
- }
19787
+ let newlineIndex = this.pending.indexOf(`
19788
+ `);
19789
+ while (newlineIndex !== -1) {
19790
+ const line = this.pending.slice(0, newlineIndex).replace(/\r$/, "");
19791
+ this.pending = this.pending.slice(newlineIndex + 1);
19792
+ this.extractLine(line, summaries);
19793
+ newlineIndex = this.pending.indexOf(`
19794
+ `);
19795
+ }
19796
+ if (options.segmentComplete === true && !hadPending && this.pending.startsWith(PROGRESS_PREFIX)) {
19797
+ this.extractLine(this.pending.replace(/\r$/, ""), summaries);
19798
+ this.pending = "";
19799
+ return summaries;
19800
+ }
19801
+ this.trimPendingIfHopeless();
19802
+ return summaries;
19803
+ }
19804
+ flush() {
19805
+ const summaries = [];
19806
+ if (this.pending.length > 0) {
19807
+ this.extractLine(this.pending.replace(/\r$/, ""), summaries);
19808
+ this.pending = "";
19722
19809
  }
19723
19810
  return summaries;
19724
19811
  }
19812
+ extractLine(line, summaries) {
19813
+ if (line.startsWith(PROGRESS_PREFIX)) {
19814
+ const summary = sanitizeProgressSummary(line.slice(PROGRESS_PREFIX.length));
19815
+ if (summary.length > 0) {
19816
+ summaries.push(summary);
19817
+ }
19818
+ }
19819
+ }
19820
+ trimPendingIfHopeless() {
19821
+ if (this.pending.length === 0)
19822
+ return;
19823
+ if (PROGRESS_PREFIX.startsWith(this.pending) || this.pending.startsWith(PROGRESS_PREFIX)) {
19824
+ if (this.pending.length > MAX_PENDING_LINE_LENGTH) {
19825
+ this.pending = "";
19826
+ }
19827
+ return;
19828
+ }
19829
+ this.pending = "";
19830
+ }
19831
+ }
19832
+ function sanitizeProgressSummary(summary) {
19833
+ const cleaned = summary.replace(/[\u0000-\u0008\u000B\u000C\u000E-\u001F\u007F]/g, "").trim();
19834
+ if (cleaned.length <= MAX_PROGRESS_SUMMARY_LENGTH) {
19835
+ return cleaned;
19836
+ }
19837
+ return `${cleaned.slice(0, MAX_PROGRESS_SUMMARY_LENGTH - 3)}...`;
19725
19838
  }
19726
19839
  function stripProgressLines(text) {
19727
- return text.split(`
19728
- `).filter((line) => !line.startsWith(PROGRESS_PREFIX)).join(`
19840
+ return text.split(/\r\n|\n|\r/).filter((line) => {
19841
+ const normalized = normalizeProgressLinePrefix(line);
19842
+ return !normalized.startsWith(PROGRESS_PREFIX) && !(line.length > 0 && normalized.length === 0);
19843
+ }).join(`
19729
19844
  `).trim();
19730
19845
  }
19731
- var PROGRESS_PREFIX = "[PROGRESS]";
19846
+ function normalizeProgressLinePrefix(line) {
19847
+ return line.replace(/^(?:\r+|\u001B\[[0-?]*[ -/]*[@-~])+/, "");
19848
+ }
19849
+ var PROGRESS_PREFIX = "[PROGRESS]", MAX_PROGRESS_SUMMARY_LENGTH = 500, MAX_PENDING_LINE_LENGTH = 4096;
19732
19850
 
19733
19851
  // src/orchestration/orchestration-service.ts
19734
19852
  import { createHash as createHash2 } from "node:crypto";
19735
19853
  import { basename as basename2, isAbsolute as isAbsolute2, normalize } from "node:path";
19854
+ function clampWatchTimeout(value) {
19855
+ if (value === undefined)
19856
+ return DEFAULT_TASK_WATCH_TIMEOUT_MS;
19857
+ if (!Number.isFinite(value) || value < 0)
19858
+ return 0;
19859
+ return Math.min(Math.floor(value), MAX_TASK_WATCH_TIMEOUT_MS);
19860
+ }
19861
+ function clampWatchPollInterval(value) {
19862
+ if (value === undefined)
19863
+ return DEFAULT_TASK_WATCH_POLL_INTERVAL_MS;
19864
+ if (!Number.isFinite(value) || value < 1)
19865
+ return DEFAULT_TASK_WATCH_POLL_INTERVAL_MS;
19866
+ return Math.min(value, MAX_TASK_WATCH_POLL_INTERVAL_MS);
19867
+ }
19736
19868
 
19737
19869
  class OrchestrationService {
19738
19870
  deps;
@@ -19949,6 +20081,8 @@ class OrchestrationService {
19949
20081
  resultText: "",
19950
20082
  createdAt: now,
19951
20083
  updatedAt: now,
20084
+ eventSeq: 1,
20085
+ events: [{ seq: 1, at: now, type: "created", status: "running", message: "Task created" }],
19952
20086
  ...input.chatKey ? { chatKey: input.chatKey } : {},
19953
20087
  ...input.replyContextToken ? { replyContextToken: input.replyContextToken } : {},
19954
20088
  ...input.accountId ? { accountId: input.accountId } : {}
@@ -20072,7 +20206,9 @@ class OrchestrationService {
20072
20206
  summary: "",
20073
20207
  resultText: "",
20074
20208
  createdAt: now,
20075
- updatedAt: now
20209
+ updatedAt: now,
20210
+ eventSeq: 1,
20211
+ events: [{ seq: 1, at: now, type: "created", status, message: "Task created" }]
20076
20212
  };
20077
20213
  if (preflight.normalizedGroupId) {
20078
20214
  const group = this.ensureGroups(state)[preflight.normalizedGroupId];
@@ -20243,6 +20379,11 @@ class OrchestrationService {
20243
20379
  current.summary = message;
20244
20380
  current.resultText = "";
20245
20381
  current.updatedAt = now;
20382
+ this.appendTaskEvent(current, now, "status_changed", {
20383
+ status: "failed",
20384
+ summary: message,
20385
+ message: "Task failed during startup"
20386
+ });
20246
20387
  restoreOrDeleteBinding();
20247
20388
  await this.deps.saveState(state);
20248
20389
  return true;
@@ -20335,6 +20476,11 @@ class OrchestrationService {
20335
20476
  task2.status = input.status ?? "completed";
20336
20477
  task2.summary = input.summary ?? "";
20337
20478
  task2.resultText = stripProgressLines(input.resultText ?? "");
20479
+ this.appendTaskEvent(task2, updatedAt, "status_changed", {
20480
+ status: task2.status,
20481
+ summary: task2.summary,
20482
+ message: task2.status === "completed" ? "Task completed" : task2.status === "failed" ? "Task failed" : "Task cancelled"
20483
+ });
20338
20484
  if (task2.status === "completed" || task2.status === "failed") {
20339
20485
  if (!this.isExternalCoordinatorSession(state, task2.coordinatorSession)) {
20340
20486
  task2.injectionPending = true;
@@ -20363,6 +20509,10 @@ class OrchestrationService {
20363
20509
  resultId: this.deps.createId(),
20364
20510
  resultText: task2.resultText
20365
20511
  };
20512
+ this.appendTaskEvent(task2, updatedAt, "attention_required", {
20513
+ status: task2.status,
20514
+ message: "Task result requires contested review"
20515
+ });
20366
20516
  task2.correctionPending = undefined;
20367
20517
  task2.cancelRequestedAt = undefined;
20368
20518
  task2.cancelCompletedAt = undefined;
@@ -20471,6 +20621,64 @@ class OrchestrationService {
20471
20621
  await sleep2(Math.min(pollIntervalMs, remainingMs));
20472
20622
  }
20473
20623
  }
20624
+ async watchTask(input) {
20625
+ const timeoutMs = clampWatchTimeout(input.timeoutMs);
20626
+ const pollIntervalMs = clampWatchPollInterval(input.pollIntervalMs);
20627
+ const afterSeq = Math.max(0, Math.floor(input.afterSeq ?? 0));
20628
+ const mode = input.mode ?? "until_attention_or_terminal";
20629
+ const includeProgress = input.includeProgress ?? true;
20630
+ const deadline = Date.now() + timeoutMs;
20631
+ while (true) {
20632
+ const state = await this.deps.loadState();
20633
+ const task = state.orchestration.tasks[input.taskId];
20634
+ if (!task || task.coordinatorSession !== input.coordinatorSession) {
20635
+ return { status: "not_found", task: null, events: [], nextAfterSeq: afterSeq };
20636
+ }
20637
+ const snapshot = { ...task };
20638
+ const allEvents = task.events ?? [];
20639
+ const filteredEvents = allEvents.filter((event) => event.seq > afterSeq).filter((event) => includeProgress || event.type !== "progress");
20640
+ const nextAfterSeq = task.eventSeq ?? allEvents.at(-1)?.seq ?? afterSeq;
20641
+ const historyTruncated = allEvents.length > 0 && afterSeq < allEvents[0].seq - 1;
20642
+ if (isTerminalTaskStatus2(task.status) && task.reviewPending === undefined) {
20643
+ return {
20644
+ status: "terminal",
20645
+ task: snapshot,
20646
+ events: filteredEvents.map((event) => ({ ...event })),
20647
+ nextAfterSeq,
20648
+ ...historyTruncated ? { historyTruncated } : {}
20649
+ };
20650
+ }
20651
+ if (isAttentionRequiredTask(task)) {
20652
+ return {
20653
+ status: "attention_required",
20654
+ task: snapshot,
20655
+ events: filteredEvents.map((event) => ({ ...event })),
20656
+ nextAfterSeq,
20657
+ ...historyTruncated ? { historyTruncated } : {}
20658
+ };
20659
+ }
20660
+ if (filteredEvents.length > 0 && mode === "next_event") {
20661
+ return {
20662
+ status: "event",
20663
+ task: snapshot,
20664
+ events: filteredEvents.map((event) => ({ ...event })),
20665
+ nextAfterSeq,
20666
+ ...historyTruncated ? { historyTruncated } : {}
20667
+ };
20668
+ }
20669
+ const remainingMs = deadline - Date.now();
20670
+ if (remainingMs <= 0) {
20671
+ return {
20672
+ status: "timeout",
20673
+ task: snapshot,
20674
+ events: filteredEvents.map((event) => ({ ...event })),
20675
+ nextAfterSeq,
20676
+ ...historyTruncated ? { historyTruncated } : {}
20677
+ };
20678
+ }
20679
+ await sleep2(Math.min(pollIntervalMs, remainingMs));
20680
+ }
20681
+ }
20474
20682
  async recordCoordinatorRouteContext(input) {
20475
20683
  if (input.coordinatorSession.trim().length === 0) {
20476
20684
  throw new Error("coordinatorSession must be a non-empty string");
@@ -20550,6 +20758,10 @@ class OrchestrationService {
20550
20758
  status: "open"
20551
20759
  };
20552
20760
  task.updatedAt = now;
20761
+ this.appendTaskEvent(task, now, "attention_required", {
20762
+ status: "blocked",
20763
+ message: input.question.trim()
20764
+ });
20553
20765
  this.bumpGroupUpdated(state, task.groupId, now);
20554
20766
  await this.deps.saveState(state);
20555
20767
  return {
@@ -20607,6 +20819,10 @@ class OrchestrationService {
20607
20819
  lastResumeError: undefined
20608
20820
  };
20609
20821
  task.updatedAt = now;
20822
+ this.appendTaskEvent(task, now, "status_changed", {
20823
+ status: "running",
20824
+ message: "Blocker question answered"
20825
+ });
20610
20826
  this.bumpGroupUpdated(state, task.groupId, now);
20611
20827
  await this.deps.saveState(state);
20612
20828
  return {
@@ -20661,6 +20877,10 @@ class OrchestrationService {
20661
20877
  };
20662
20878
  task.cancelRequestedAt = task.cancelRequestedAt ?? now;
20663
20879
  task.updatedAt = now;
20880
+ this.appendTaskEvent(task, now, "cancel_requested", {
20881
+ status: task.status,
20882
+ message: "Correction requested for misrouted answer"
20883
+ });
20664
20884
  this.bumpGroupUpdated(state, task.groupId, now);
20665
20885
  await this.deps.saveState(state);
20666
20886
  return {
@@ -20679,6 +20899,10 @@ class OrchestrationService {
20679
20899
  task.noticePending = false;
20680
20900
  task.lastNoticeError = undefined;
20681
20901
  task.updatedAt = now;
20902
+ this.appendTaskEvent(task, now, "attention_required", {
20903
+ status: task.status,
20904
+ message: "Task result requires contested review"
20905
+ });
20682
20906
  this.bumpGroupUpdated(state, task.groupId, now);
20683
20907
  await this.deps.saveState(state);
20684
20908
  return {
@@ -20785,6 +21009,10 @@ class OrchestrationService {
20785
21009
  packageId
20786
21010
  };
20787
21011
  task.updatedAt = now;
21012
+ this.appendTaskEvent(task, now, "attention_required", {
21013
+ status: "waiting_for_human",
21014
+ message: task.openQuestion.question
21015
+ });
20788
21016
  this.bumpGroupUpdated(state, task.groupId, now);
20789
21017
  }
20790
21018
  this.ensureHumanQuestionPackages(state)[packageId] = packageRecord;
@@ -20872,6 +21100,10 @@ class OrchestrationService {
20872
21100
  packageId: input.packageId
20873
21101
  };
20874
21102
  task.updatedAt = now;
21103
+ this.appendTaskEvent(task, now, "attention_required", {
21104
+ status: "waiting_for_human",
21105
+ message: task.openQuestion.question
21106
+ });
20875
21107
  this.bumpGroupUpdated(state, task.groupId, now);
20876
21108
  }
20877
21109
  await this.deps.saveState(state);
@@ -21055,11 +21287,21 @@ class OrchestrationService {
21055
21287
  task.summary = "";
21056
21288
  task.resultText = "";
21057
21289
  task.openQuestion = this.buildReplacementOpenQuestion(task, replacementQuestionId, now, packageId);
21290
+ this.appendTaskEvent(task, now, "attention_required", {
21291
+ status: task.status,
21292
+ message: task.openQuestion.question
21293
+ });
21058
21294
  } else if ((task.status === "completed" || task.status === "failed") && task.chatKey && task.replyContextToken && task.noticeSentAt === undefined) {
21059
21295
  task.noticePending = true;
21060
21296
  task.lastNoticeError = undefined;
21061
21297
  }
21062
21298
  task.updatedAt = now;
21299
+ if (input.decision === "accept") {
21300
+ this.appendTaskEvent(task, now, "status_changed", {
21301
+ status: task.status,
21302
+ message: "Contested result accepted"
21303
+ });
21304
+ }
21063
21305
  this.bumpGroupUpdated(state, task.groupId, now);
21064
21306
  await this.deps.saveState(state);
21065
21307
  return {
@@ -21437,7 +21679,7 @@ class OrchestrationService {
21437
21679
  });
21438
21680
  }
21439
21681
  }
21440
- async recordTaskProgress(taskId) {
21682
+ async recordTaskProgress(taskId, summary) {
21441
21683
  return await this.mutate(async () => {
21442
21684
  const state = await this.deps.loadState();
21443
21685
  const task = state.orchestration.tasks[taskId];
@@ -21445,6 +21687,21 @@ class OrchestrationService {
21445
21687
  throw new Error(`task "${taskId}" does not exist`);
21446
21688
  }
21447
21689
  task.lastProgressAt = this.deps.now().toISOString();
21690
+ if (summary !== undefined) {
21691
+ const cleaned = sanitizeProgressSummary(summary);
21692
+ if (cleaned.length > 0) {
21693
+ task.lastProgressSummary = cleaned;
21694
+ this.appendTaskEvent(task, task.lastProgressAt, "progress", {
21695
+ status: task.status,
21696
+ summary: cleaned
21697
+ });
21698
+ }
21699
+ } else {
21700
+ this.appendTaskEvent(task, task.lastProgressAt, "progress", {
21701
+ status: task.status,
21702
+ message: "heartbeat"
21703
+ });
21704
+ }
21448
21705
  task.updatedAt = task.lastProgressAt;
21449
21706
  await this.deps.saveState(state);
21450
21707
  return { ...task };
@@ -21492,6 +21749,12 @@ class OrchestrationService {
21492
21749
  const shouldPropagate = task.cancelRequestedAt === undefined;
21493
21750
  task.cancelRequestedAt = task.cancelRequestedAt ?? now;
21494
21751
  task.updatedAt = now;
21752
+ if (shouldPropagate) {
21753
+ this.appendTaskEvent(task, now, "cancel_requested", {
21754
+ status: task.status,
21755
+ message: "Cancellation requested"
21756
+ });
21757
+ }
21495
21758
  this.bumpGroupUpdated(state, task.groupId, now);
21496
21759
  await this.deps.saveState(state);
21497
21760
  return { task: { ...task }, shouldPropagate, closedPackageId: undefined };
@@ -21503,6 +21766,10 @@ class OrchestrationService {
21503
21766
  task.cancelCompletedAt = now;
21504
21767
  task.lastCancelError = undefined;
21505
21768
  task.updatedAt = now;
21769
+ this.appendTaskEvent(task, now, "status_changed", {
21770
+ status: "cancelled",
21771
+ message: "Task cancelled"
21772
+ });
21506
21773
  this.bumpGroupUpdated(state, task.groupId, now);
21507
21774
  await this.deps.saveState(state);
21508
21775
  return { task: { ...task }, shouldPropagate: false, closedPackageId };
@@ -21537,10 +21804,18 @@ class OrchestrationService {
21537
21804
  task.cancelRequestedAt = undefined;
21538
21805
  task.cancelCompletedAt = undefined;
21539
21806
  task.lastCancelError = undefined;
21807
+ this.appendTaskEvent(task, now, "attention_required", {
21808
+ status: task.status,
21809
+ message: task.openQuestion.question
21810
+ });
21540
21811
  } else {
21541
21812
  task.status = "cancelled";
21542
21813
  task.cancelCompletedAt = now;
21543
21814
  task.lastCancelError = undefined;
21815
+ this.appendTaskEvent(task, now, "status_changed", {
21816
+ status: "cancelled",
21817
+ message: "Task cancelled"
21818
+ });
21544
21819
  }
21545
21820
  task.updatedAt = now;
21546
21821
  this.bumpGroupUpdated(state, task.groupId, now);
@@ -21582,6 +21857,10 @@ class OrchestrationService {
21582
21857
  }
21583
21858
  task2.lastCancelError = errorMessage;
21584
21859
  task2.updatedAt = this.deps.now().toISOString();
21860
+ this.appendTaskEvent(task2, task2.updatedAt, "progress", {
21861
+ status: task2.status,
21862
+ message: `Cancellation failed: ${errorMessage}`
21863
+ });
21585
21864
  await this.deps.saveState(state);
21586
21865
  return { ...task2 };
21587
21866
  });
@@ -21639,6 +21918,10 @@ class OrchestrationService {
21639
21918
  task.workerSession = ensuredWorkerSession;
21640
21919
  task.status = "running";
21641
21920
  task.updatedAt = this.deps.now().toISOString();
21921
+ this.appendTaskEvent(task, task.updatedAt, "status_changed", {
21922
+ status: "running",
21923
+ message: "Task approved"
21924
+ });
21642
21925
  state.orchestration.workerBindings[ensuredWorkerSession] = {
21643
21926
  sourceHandle: ensuredWorkerSession,
21644
21927
  coordinatorSession: task.coordinatorSession,
@@ -21709,6 +21992,11 @@ class OrchestrationService {
21709
21992
  task2.status = "cancelled";
21710
21993
  task2.summary = "rejected";
21711
21994
  task2.updatedAt = this.deps.now().toISOString();
21995
+ this.appendTaskEvent(task2, task2.updatedAt, "status_changed", {
21996
+ status: "cancelled",
21997
+ summary: "rejected",
21998
+ message: "Task rejected"
21999
+ });
21712
22000
  await this.deps.saveState(state);
21713
22001
  return { ...task2 };
21714
22002
  });
@@ -22637,6 +22925,20 @@ class OrchestrationService {
22637
22925
  }
22638
22926
  })();
22639
22927
  }
22928
+ appendTaskEvent(task, at, type, details = {}) {
22929
+ const nextSeq = (task.eventSeq ?? 0) + 1;
22930
+ task.eventSeq = nextSeq;
22931
+ const events = task.events ?? [];
22932
+ events.push({
22933
+ seq: nextSeq,
22934
+ at,
22935
+ type,
22936
+ ...details.status ? { status: details.status } : {},
22937
+ ...details.summary ? { summary: details.summary } : {},
22938
+ ...details.message ? { message: details.message } : {}
22939
+ });
22940
+ task.events = events.slice(-MAX_TASK_EVENTS_PER_TASK);
22941
+ }
22640
22942
  }
22641
22943
  function isTerminalTaskStatus2(status) {
22642
22944
  return status === "completed" || status === "failed" || status === "cancelled";
@@ -22668,6 +22970,7 @@ async function sleep2(ms) {
22668
22970
  function isRequestDelegateInput(input) {
22669
22971
  return "sourceKind" in input;
22670
22972
  }
22973
+ var MAX_TASK_EVENTS_PER_TASK = 200;
22671
22974
  var init_orchestration_service = __esm(() => {
22672
22975
  init_quota_errors();
22673
22976
  init_task_wait_timeouts();
@@ -25070,6 +25373,11 @@ async function buildApp(paths, deps = {}) {
25070
25373
  const config2 = await loadConfig(paths.configPath, {
25071
25374
  defaultLoggingLevel: deps.defaultLoggingLevel
25072
25375
  });
25376
+ const reloadRuntimeConfig = async () => {
25377
+ const updated = await configStore.load();
25378
+ replaceRuntimeConfig(config2, updated);
25379
+ return config2;
25380
+ };
25073
25381
  const logger2 = createAppLogger({
25074
25382
  filePath: resolveAppLogPath(paths.configPath),
25075
25383
  level: config2.logging.level,
@@ -25268,32 +25576,39 @@ async function buildApp(paths, deps = {}) {
25268
25576
  };
25269
25577
  };
25270
25578
  const launchWorkerTurn = (input) => {
25271
- const session = resolveWorkerRuntimeSession(input);
25272
- session.mcpCoordinatorSession = input.coordinatorSession;
25273
- session.mcpSourceHandle = input.workerSession;
25274
25579
  const workerDispatch = (async () => {
25275
25580
  let taskRecord;
25276
25581
  try {
25582
+ await reloadRuntimeConfig();
25583
+ const session = resolveWorkerRuntimeSession(input);
25584
+ session.mcpCoordinatorSession = input.coordinatorSession;
25585
+ session.mcpSourceHandle = input.workerSession;
25277
25586
  const progressBuffer = new ProgressLineBuffer;
25587
+ const recordProgress = async (summary) => {
25588
+ try {
25589
+ await orchestration.recordTaskProgress(input.taskId, summary);
25590
+ const taskState = await orchestration.getTask(input.taskId);
25591
+ if (taskState?.chatKey && taskState.replyContextToken && deps.channel) {
25592
+ await deps.channel.notifyTaskProgress(taskState, renderTaskProgress(taskState, summary));
25593
+ }
25594
+ } catch (error2) {
25595
+ await logger2.error("orchestration.progress.send_failed", "failed to send task progress", {
25596
+ taskId: input.taskId,
25597
+ message: error2 instanceof Error ? error2.message : String(error2)
25598
+ });
25599
+ }
25600
+ };
25278
25601
  const result = await transport.prompt(session, input.promptText, undefined, undefined, {
25279
25602
  onSegment: async (chunk) => {
25280
- const summaries = progressBuffer.feed(chunk);
25603
+ const summaries = progressBuffer.feed(chunk, { segmentComplete: true });
25281
25604
  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
- }
25605
+ await recordProgress(summary);
25294
25606
  }
25295
25607
  }
25296
25608
  });
25609
+ for (const summary of progressBuffer.flush()) {
25610
+ await recordProgress(summary);
25611
+ }
25297
25612
  taskRecord = await finalizeWorkerTurn({
25298
25613
  taskId: input.taskId,
25299
25614
  workerSession: input.workerSession,
@@ -25357,6 +25672,7 @@ async function buildApp(paths, deps = {}) {
25357
25672
  },
25358
25673
  stateMutex,
25359
25674
  ensureWorkerSession: async ({ workerSession, targetAgent, workspace, cwd, coordinatorSession }) => {
25675
+ await reloadRuntimeConfig();
25360
25676
  const session = resolveWorkerRuntimeSession({ workerSession, targetAgent, workspace, ...cwd ? { cwd } : {} });
25361
25677
  session.mcpCoordinatorSession = coordinatorSession;
25362
25678
  session.mcpSourceHandle = workerSession;
@@ -25479,6 +25795,9 @@ function replaceRuntimeState(target, source) {
25479
25795
  target.chat_contexts = source.chat_contexts;
25480
25796
  target.orchestration = source.orchestration;
25481
25797
  }
25798
+ function replaceRuntimeConfig(target, source) {
25799
+ Object.assign(target, source);
25800
+ }
25482
25801
  async function main() {
25483
25802
  const paths = resolveRuntimePaths();
25484
25803
  try {
@@ -26628,6 +26947,7 @@ var init_doctor2 = __esm(async () => {
26628
26947
  init_config_store();
26629
26948
  init_load_config();
26630
26949
  init_ensure_config();
26950
+ init_agent_templates();
26631
26951
  init_create_daemon_controller();
26632
26952
  init_daemon_files();
26633
26953
  import { randomUUID as randomUUID4 } from "node:crypto";
@@ -39082,6 +39402,7 @@ var taskStatusSchema = exports_external.enum([
39082
39402
  var sortSchema = exports_external.enum(["updatedAt", "createdAt"]);
39083
39403
  var orderSchema = exports_external.enum(["asc", "desc"]);
39084
39404
  var contestedDecisionSchema = exports_external.enum(["accept", "discard"]);
39405
+ var taskWatchModeSchema = exports_external.enum(["next_event", "until_attention_or_terminal"]);
39085
39406
  var taskQuestionSchema = exports_external.object({
39086
39407
  taskId: exports_external.string().min(1),
39087
39408
  questionId: exports_external.string().min(1)
@@ -39091,7 +39412,8 @@ function buildWeacpxMcpToolRegistry(input) {
39091
39412
  const tools = [
39092
39413
  {
39093
39414
  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(", ")}.` : ""}`,
39415
+ 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. For legacy clients, after this returns status=running, keep the returned taskId and use task_get/task_list for non-blocking progress snapshots; call task_wait only when the user explicitly wants you to wait/block until completion or attention is required. 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(", ")}.` : ""}`,
39416
+ execution: { taskSupport: "optional" },
39095
39417
  inputSchema: exports_external.object({
39096
39418
  targetAgent: exports_external.string().min(1),
39097
39419
  task: exports_external.string().min(1),
@@ -39174,7 +39496,7 @@ function buildWeacpxMcpToolRegistry(input) {
39174
39496
  },
39175
39497
  {
39176
39498
  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.",
39499
+ 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
39500
  inputSchema: exports_external.object({
39179
39501
  taskId: exports_external.string().min(1)
39180
39502
  }).strict(),
@@ -39209,7 +39531,7 @@ function buildWeacpxMcpToolRegistry(input) {
39209
39531
  },
39210
39532
  {
39211
39533
  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.",
39534
+ 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, use task_get/task_list for snapshots or task_wait only if intentionally blocking.",
39213
39535
  inputSchema: exports_external.object({
39214
39536
  taskId: exports_external.string().min(1)
39215
39537
  }).strict(),
@@ -39251,7 +39573,7 @@ function buildWeacpxMcpToolRegistry(input) {
39251
39573
  },
39252
39574
  {
39253
39575
  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.`,
39576
+ description: `Wait for a task to finish or require attention. This is a blocking legacy compatibility tool; do not call it automatically after delegate_request when the user only asked to start/delegate work. Use task_get/task_list for non-blocking progress snapshots; call task_wait when the user explicitly asks to wait, or when your next step truly depends on completion. 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, use task_get/task_list snapshots or task_wait only if intentionally blocking), or status=timeout (still running; use task_get for a snapshot, or task_wait only if intentionally blocking). 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.`,
39255
39577
  inputSchema: exports_external.object({
39256
39578
  taskId: exports_external.string().min(1),
39257
39579
  timeoutMs: exports_external.number().int().min(0).max(MAX_TASK_WAIT_TIMEOUT_MS).optional(),
@@ -39265,9 +39587,29 @@ function buildWeacpxMcpToolRegistry(input) {
39265
39587
  return createSuccessResult(renderTaskWaitResult(result), result);
39266
39588
  })
39267
39589
  },
39590
+ {
39591
+ name: "task_watch",
39592
+ description: `Long-poll a task for the next event, attention-required state, or terminal state. This is the recommended legacy way to watch a delegated task without repeatedly calling task_wait. 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.`,
39593
+ execution: { taskSupport: "optional" },
39594
+ inputSchema: exports_external.object({
39595
+ taskId: exports_external.string().min(1),
39596
+ afterSeq: exports_external.number().int().min(0).optional(),
39597
+ mode: taskWatchModeSchema.optional(),
39598
+ includeProgress: exports_external.boolean().optional(),
39599
+ timeoutMs: exports_external.number().int().min(0).max(MAX_TASK_WATCH_TIMEOUT_MS).optional(),
39600
+ pollIntervalMs: exports_external.number().int().min(1).max(MAX_TASK_WATCH_POLL_INTERVAL_MS).optional()
39601
+ }).strict(),
39602
+ handler: async (args) => await asToolResult(async () => {
39603
+ const result = await transport.watchTask({
39604
+ coordinatorSession,
39605
+ ...args
39606
+ });
39607
+ return createSuccessResult(renderTaskWatchResult(result), result);
39608
+ })
39609
+ },
39268
39610
  {
39269
39611
  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.",
39612
+ 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_get/task_list for snapshots, or task_wait only if intentionally blocking.",
39271
39613
  inputSchema: exports_external.object({
39272
39614
  taskId: exports_external.string().min(1),
39273
39615
  question: exports_external.string().min(1),
@@ -39287,7 +39629,7 @@ function buildWeacpxMcpToolRegistry(input) {
39287
39629
  },
39288
39630
  {
39289
39631
  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.",
39632
+ description: "Answer a blocked worker question under the current coordinator. Use when task_get shows a pending question; after answering, use task_get/task_list for snapshots or task_wait only if intentionally blocking for the worker to finish.",
39291
39633
  inputSchema: exports_external.object({
39292
39634
  taskId: exports_external.string().min(1),
39293
39635
  questionId: exports_external.string().min(1),
@@ -39391,7 +39733,7 @@ function renderTaskWaitResult(result) {
39391
39733
  if (result.status === "timeout") {
39392
39734
  return [
39393
39735
  `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.`
39736
+ `Next: call task_get for a snapshot, or call task_wait again only if you intentionally want to keep blocking.`
39395
39737
  ].join(`
39396
39738
  `);
39397
39739
  }
@@ -39402,7 +39744,7 @@ function renderTaskWaitResult(result) {
39402
39744
  ` - status=needs_confirmation -> task_approve or task_reject`,
39403
39745
  ` - status=blocked or waiting_for_human -> coordinator_answer_question`,
39404
39746
  ` - reviewPending set -> coordinator_review_contested_result`,
39405
- `After resolving, call task_wait again to keep waiting for the worker to finish.`
39747
+ `After resolving, use task_get/task_list for snapshots, or call task_wait again only if you intentionally want to keep blocking.`
39406
39748
  ].join(`
39407
39749
  `);
39408
39750
  }
@@ -39412,6 +39754,26 @@ function renderTaskWaitResult(result) {
39412
39754
  ].join(`
39413
39755
  `);
39414
39756
  }
39757
+ function renderTaskWatchResult(result) {
39758
+ if (result.status === "not_found" || !result.task) {
39759
+ return "Task not found.";
39760
+ }
39761
+ const header = [
39762
+ `Task ${result.task.taskId} watch ${result.status.replace("_", " ")}; current state is ${result.task.status}.`,
39763
+ `- nextAfterSeq: ${result.nextAfterSeq}`,
39764
+ result.historyTruncated ? "- historyTruncated: true" : ""
39765
+ ].filter((line) => line.length > 0);
39766
+ const events = result.events.length > 0 ? [
39767
+ "- Events:",
39768
+ ...result.events.map((event) => {
39769
+ const detail = event.summary ?? event.message ?? event.status ?? "";
39770
+ return ` - #${event.seq} ${event.type} at ${event.at}${detail ? `: ${detail}` : ""}`;
39771
+ })
39772
+ ] : ["- Events: none"];
39773
+ 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.`;
39774
+ return [...header, ...events, next].join(`
39775
+ `);
39776
+ }
39415
39777
  function createSuccessResult(text, structuredContent) {
39416
39778
  return {
39417
39779
  content: [{ type: "text", text }],
@@ -39425,7 +39787,7 @@ function createErrorResult(message) {
39425
39787
  };
39426
39788
  }
39427
39789
  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.`;
39790
+ 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: task "${result.taskId}" is running. Return this taskId to the user, or call task_get/task_list for non-blocking progress snapshots (or task_watch to long-poll for the next event). Call task_wait only if the user explicitly asks you to wait/block until completion.`;
39429
39791
  return [`Delegation task "${result.taskId}" created.`, `- Status: ${result.status}`, next].join(`
39430
39792
  `);
39431
39793
  }
@@ -39530,6 +39892,8 @@ function renderTaskSummary(task) {
39530
39892
  header.push(`- Task: ${task.task}`);
39531
39893
  if (task.summary.trim().length > 0)
39532
39894
  header.push(`- Summary: ${task.summary}`);
39895
+ if (task.lastProgressSummary)
39896
+ header.push(`- Latest progress: ${task.lastProgressSummary}`);
39533
39897
  if (task.resultText.trim().length > 0)
39534
39898
  header.push(`- Result: ${task.resultText}`);
39535
39899
  const events = [];
@@ -39574,7 +39938,7 @@ function renderTaskApprovalSuccess(task) {
39574
39938
  return [
39575
39939
  `Task "${task.taskId}" approved.`,
39576
39940
  `- 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.`
39941
+ `Next: use task_get/task_list for non-blocking progress snapshots, or call task_wait only if you intentionally want to block until the worker finishes; then task_get to read the final result.`
39578
39942
  ].join(`
39579
39943
  `);
39580
39944
  }
@@ -39644,6 +40008,9 @@ class OrchestrationClient {
39644
40008
  async waitTask(input) {
39645
40009
  return await this.request("task.wait", input, getWaitRequestTimeoutMs(input.timeoutMs, this.timeoutMs));
39646
40010
  }
40011
+ async watchTask(input) {
40012
+ return await this.request("task.watch", input, getWatchRequestTimeoutMs(input.timeoutMs, this.timeoutMs));
40013
+ }
39647
40014
  async approveTask(input) {
39648
40015
  return await this.request("task.approve", input);
39649
40016
  }
@@ -39760,6 +40127,11 @@ function getWaitRequestTimeoutMs(waitTimeoutMs, defaultTimeoutMs) {
39760
40127
  const boundedWaitTimeoutMs = Math.min(Math.max(Math.floor(requestedWaitTimeoutMs ?? DEFAULT_TASK_WAIT_TIMEOUT_MS), 0), MAX_TASK_WAIT_TIMEOUT_MS);
39761
40128
  return Math.max(defaultTimeoutMs, boundedWaitTimeoutMs + TASK_WAIT_RPC_TIMEOUT_PADDING_MS);
39762
40129
  }
40130
+ function getWatchRequestTimeoutMs(watchTimeoutMs, defaultTimeoutMs) {
40131
+ const requestedWatchTimeoutMs = watchTimeoutMs === undefined ? undefined : Number.isFinite(watchTimeoutMs) ? watchTimeoutMs : 0;
40132
+ const boundedWatchTimeoutMs = Math.min(Math.max(Math.floor(requestedWatchTimeoutMs ?? DEFAULT_TASK_WATCH_TIMEOUT_MS), 0), MAX_TASK_WATCH_TIMEOUT_MS);
40133
+ return Math.max(defaultTimeoutMs, boundedWatchTimeoutMs + TASK_WATCH_RPC_TIMEOUT_PADDING_MS);
40134
+ }
39763
40135
 
39764
40136
  // src/mcp/weacpx-mcp-transport.ts
39765
40137
  function createOrchestrationTransport(endpoint, deps = {}) {
@@ -39789,6 +40161,7 @@ function createOrchestrationTransport(endpoint, deps = {}) {
39789
40161
  rejectTask: async (input) => await client.rejectTask(input),
39790
40162
  cancelTask: async (input) => await client.cancelTaskForCoordinator(input),
39791
40163
  waitTask: async (input) => await client.waitTask(input),
40164
+ watchTask: async (input) => await client.watchTask(input),
39792
40165
  workerRaiseQuestion: async (input) => {
39793
40166
  const sourceHandle = input.sourceHandle.trim();
39794
40167
  if (sourceHandle.length === 0) {
@@ -39810,21 +40183,31 @@ function createOrchestrationTransport(endpoint, deps = {}) {
39810
40183
  }
39811
40184
 
39812
40185
  // src/mcp/weacpx-mcp-server.ts
40186
+ var TASK_OPTIONS_CACHE_LIMIT = 1000;
40187
+ var TASKS_LIST_PAGE_SIZE = 100;
40188
+ var WATCH_TASKS_CACHE_LIMIT = 256;
39813
40189
  var WEACPX_MCP_SERVER_INSTRUCTIONS = [
39814
40190
  "Use these tools to orchestrate work across other agents under your coordinator session.",
39815
40191
  "",
39816
40192
  "Typical lifecycle for a single delegation:",
40193
+ "Preferred MCP Tasks lifecycle (for clients that support task-augmented tools/call):",
40194
+ "1. Call delegate_request with task execution requested. It returns a native MCP task handle immediately.",
40195
+ "2. Use task_watch with MCP task execution to start a background watcher, or use tasks/get / tasks/list to poll status. Use tasks/result after terminal status, or on input_required to receive an actionable next-step package; use tasks/cancel to cancel.",
40196
+ " - When tasks/result returns input_required, that result stream is complete. Call the recommended tool, then resume polling with tasks/get / tasks/result.",
40197
+ "3. Status mapping: working = running, input_required = needs_confirmation / blocked / waiting_for_human / contested review, completed / failed / cancelled are terminal.",
40198
+ "",
40199
+ "Legacy tool lifecycle for clients without MCP Tasks support:",
39817
40200
  "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.",
40201
+ " - status=running: the worker has started. Return the taskId to the user, use task_get / task_list for non-blocking snapshots, or task_watch to long-poll for the next event; only go to step 2 when you intentionally want to block waiting.",
40202
+ " - status=needs_confirmation: tell the user, then call task_approve or task_reject based on their response. After task_approve, use task_get/task_list for snapshots or step 2 only if intentionally blocking. Do not call task_wait before approval.",
40203
+ "2. Optional blocking wait: task_wait(taskId) → blocks until the task is done, needs attention, or times out. Do not call it automatically when the user asked to delegate and continue.",
39821
40204
  " - status=terminal: go to step 3.",
39822
40205
  " - 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)",
40206
+ " * needs_confirmation -> task_approve or task_reject (after approval, use snapshots or optional blocking wait only if needed)",
39824
40207
  " * blocked or waiting_for_human -> coordinator_answer_question (the answer can come from you or be relayed from a human you consulted)",
39825
40208
  " * 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.",
40209
+ " After resolving, use task_get / task_list for snapshots, or step 2 only if intentionally blocking.",
40210
+ " - status=timeout: the task is still running. Use task_get for a snapshot, or call task_wait again only if you still intentionally want to block.",
39828
40211
  "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.",
39829
40212
  "",
39830
40213
  "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.",
@@ -39835,18 +40218,27 @@ var WEACPX_MCP_SERVER_INSTRUCTIONS = [
39835
40218
  ].join(`
39836
40219
  `);
39837
40220
  function createWeacpxMcpServer(options) {
40221
+ let getToolState;
40222
+ const taskOptionsById = new Map;
40223
+ const watchTasksById = new Map;
39838
40224
  const server = new Server({
39839
40225
  name: "weacpx-orchestration",
39840
40226
  version: readVersion()
39841
40227
  }, {
39842
40228
  capabilities: {
39843
- tools: {}
40229
+ tools: {},
40230
+ tasks: {
40231
+ list: {},
40232
+ cancel: {},
40233
+ requests: { tools: { call: {} } }
40234
+ }
39844
40235
  },
39845
- instructions: WEACPX_MCP_SERVER_INSTRUCTIONS
40236
+ instructions: WEACPX_MCP_SERVER_INSTRUCTIONS,
40237
+ taskStore: createWeacpxTaskStore(async () => await getToolState(), taskOptionsById, watchTasksById)
39846
40238
  });
39847
40239
  let toolState = null;
39848
40240
  let toolStatePromise = null;
39849
- async function getToolState() {
40241
+ getToolState = async function getToolState2() {
39850
40242
  if (toolState) {
39851
40243
  return toolState;
39852
40244
  }
@@ -39869,14 +40261,15 @@ function createWeacpxMcpServer(options) {
39869
40261
  toolStatePromise = null;
39870
40262
  });
39871
40263
  return await toolStatePromise;
39872
- }
40264
+ };
39873
40265
  server.setRequestHandler(ListToolsRequestSchema, async () => {
39874
40266
  const tools = (await getToolState()).tools;
39875
40267
  return {
39876
40268
  tools: tools.map((tool) => ({
39877
40269
  name: tool.name,
39878
40270
  description: tool.description,
39879
- inputSchema: normalizeInputSchemaJson(zodToJsonSchema(tool.inputSchema))
40271
+ inputSchema: normalizeInputSchemaJson(zodToJsonSchema(tool.inputSchema)),
40272
+ ...tool.execution ? { execution: tool.execution } : {}
39880
40273
  }))
39881
40274
  };
39882
40275
  });
@@ -39890,15 +40283,399 @@ function createWeacpxMcpServer(options) {
39890
40283
  if (!parsed.success) {
39891
40284
  throw new McpError(ErrorCode.InvalidParams, formatZodError(parsed.error));
39892
40285
  }
40286
+ if (request.params.task) {
40287
+ if (tool.name !== "delegate_request" && tool.name !== "task_watch") {
40288
+ throw new McpError(ErrorCode.InvalidParams, `Tool ${tool.name} does not support MCP task execution`);
40289
+ }
40290
+ if (tool.name === "delegate_request") {
40291
+ return await createDelegationMcpTask({
40292
+ state: await getToolState(),
40293
+ args: parsed.data,
40294
+ taskParams: request.params.task,
40295
+ taskOptionsById
40296
+ });
40297
+ }
40298
+ return await createWatchMcpTask({
40299
+ state: await getToolState(),
40300
+ args: parsed.data,
40301
+ taskParams: request.params.task,
40302
+ taskOptionsById,
40303
+ watchTasksById
40304
+ });
40305
+ }
39893
40306
  return await tool.handler(parsed.data);
39894
40307
  });
40308
+ server.setRequestHandler(GetTaskPayloadRequestSchema, async (request) => {
40309
+ const watchTask = watchTasksById.get(request.params.taskId);
40310
+ if (watchTask) {
40311
+ if (!watchTask.result) {
40312
+ throw new McpError(ErrorCode.InvalidRequest, `Task ${request.params.taskId} is still ${watchTask.task.status}`);
40313
+ }
40314
+ watchTasksById.delete(request.params.taskId);
40315
+ return watchTask.result;
40316
+ }
40317
+ const state = await getToolState();
40318
+ const task = await state.transport.getTask({
40319
+ coordinatorSession: state.coordinatorSession,
40320
+ taskId: request.params.taskId
40321
+ });
40322
+ if (!task) {
40323
+ throw new McpError(ErrorCode.InvalidParams, `Task not found: ${request.params.taskId}`);
40324
+ }
40325
+ return withRelatedTaskMeta(renderNativeTaskPayloadResult(task), task.taskId);
40326
+ });
39895
40327
  return server;
39896
40328
  }
39897
40329
  function buildToolState(options) {
39898
40330
  const tools = buildWeacpxMcpToolRegistry(options);
39899
40331
  return {
39900
40332
  tools,
39901
- toolMap: new Map(tools.map((tool) => [tool.name, tool]))
40333
+ toolMap: new Map(tools.map((tool) => [tool.name, tool])),
40334
+ transport: options.transport,
40335
+ coordinatorSession: options.coordinatorSession,
40336
+ sourceHandle: options.sourceHandle
40337
+ };
40338
+ }
40339
+ async function createDelegationMcpTask(input) {
40340
+ const delegateTool = input.state.toolMap.get("delegate_request");
40341
+ if (!delegateTool) {
40342
+ throw new McpError(ErrorCode.MethodNotFound, "delegate_request is not registered");
40343
+ }
40344
+ const result = await delegateTool.handler(input.args);
40345
+ if (result.isError) {
40346
+ throw new McpError(ErrorCode.InvalidRequest, result.content.map((item) => item.type === "text" ? item.text : "").filter(Boolean).join(`
40347
+ `) || "Delegation failed");
40348
+ }
40349
+ const structured = result.structuredContent;
40350
+ const taskId = typeof structured?.taskId === "string" ? structured.taskId : undefined;
40351
+ if (!taskId) {
40352
+ throw new McpError(ErrorCode.InternalError, "delegate_request did not return a taskId");
40353
+ }
40354
+ rememberTaskOptions(input.taskOptionsById, taskId, input.taskParams);
40355
+ const task = await input.state.transport.getTask({
40356
+ coordinatorSession: input.state.coordinatorSession,
40357
+ taskId
40358
+ });
40359
+ if (!task) {
40360
+ throw new McpError(ErrorCode.InternalError, `delegate_request created task "${taskId}" but it was not readable from orchestration state`);
40361
+ }
40362
+ return {
40363
+ task: toMcpTask(task, input.taskParams)
40364
+ };
40365
+ }
40366
+ async function createWatchMcpTask(input) {
40367
+ const taskId = input.args.taskId;
40368
+ if (typeof taskId !== "string" || taskId.length === 0) {
40369
+ throw new McpError(ErrorCode.InvalidParams, "task_watch requires taskId");
40370
+ }
40371
+ const baseTask = await input.state.transport.getTask({
40372
+ coordinatorSession: input.state.coordinatorSession,
40373
+ taskId
40374
+ });
40375
+ if (!baseTask) {
40376
+ throw new McpError(ErrorCode.InvalidParams, `Task not found: ${taskId}`);
40377
+ }
40378
+ const now = new Date().toISOString();
40379
+ const watchTaskId = `watch:${taskId}:${Date.now()}:${Math.random().toString(36).slice(2, 10)}`;
40380
+ rememberTaskOptions(input.taskOptionsById, watchTaskId, input.taskParams);
40381
+ const watchTask = toMcpTask({
40382
+ taskId: watchTaskId,
40383
+ status: "running",
40384
+ summary: `Watching task ${taskId}`,
40385
+ createdAt: now,
40386
+ updatedAt: now
40387
+ }, input.taskParams);
40388
+ registerWatchTask(input.watchTasksById, watchTaskId, { task: watchTask });
40389
+ runWatchMcpTask({
40390
+ state: input.state,
40391
+ args: input.args,
40392
+ watchTaskId,
40393
+ taskOptions: input.taskParams,
40394
+ watchTasksById: input.watchTasksById
40395
+ });
40396
+ return {
40397
+ task: watchTask
40398
+ };
40399
+ }
40400
+ async function runWatchMcpTask(input) {
40401
+ const args = input.args;
40402
+ try {
40403
+ const result = await input.state.transport.watchTask({
40404
+ coordinatorSession: input.state.coordinatorSession,
40405
+ ...args
40406
+ });
40407
+ if (!input.watchTasksById.has(input.watchTaskId))
40408
+ return;
40409
+ const now = new Date().toISOString();
40410
+ const mcpStatus = result.status === "attention_required" ? "input_required" : result.status === "not_found" ? "failed" : "completed";
40411
+ input.watchTasksById.set(input.watchTaskId, {
40412
+ task: {
40413
+ taskId: input.watchTaskId,
40414
+ status: mcpStatus,
40415
+ ttl: input.taskOptions.ttl ?? null,
40416
+ createdAt: input.watchTasksById.get(input.watchTaskId)?.task.createdAt ?? now,
40417
+ lastUpdatedAt: now,
40418
+ ...input.taskOptions.pollInterval !== undefined ? { pollInterval: input.taskOptions.pollInterval } : {},
40419
+ statusMessage: renderWatchTaskStatusMessage(result)
40420
+ },
40421
+ result: withRelatedTaskMeta(renderWatchMcpTaskResult(result, input.watchTaskId), result.task?.taskId ?? input.watchTaskId)
40422
+ });
40423
+ } catch (error2) {
40424
+ if (!input.watchTasksById.has(input.watchTaskId))
40425
+ return;
40426
+ const now = new Date().toISOString();
40427
+ const message = error2 instanceof Error ? error2.message : String(error2);
40428
+ input.watchTasksById.set(input.watchTaskId, {
40429
+ task: {
40430
+ taskId: input.watchTaskId,
40431
+ status: "failed",
40432
+ ttl: input.taskOptions.ttl ?? null,
40433
+ createdAt: input.watchTasksById.get(input.watchTaskId)?.task.createdAt ?? now,
40434
+ lastUpdatedAt: now,
40435
+ statusMessage: message
40436
+ },
40437
+ result: {
40438
+ content: [{ type: "text", text: `Task watch "${input.watchTaskId}" failed: ${message}` }],
40439
+ structuredContent: { watchTaskId: input.watchTaskId, error: message },
40440
+ isError: true
40441
+ }
40442
+ });
40443
+ }
40444
+ }
40445
+ function renderWatchTaskStatusMessage(result) {
40446
+ if (!result.task)
40447
+ return `Watch finished: ${result.status}`;
40448
+ return `Watch finished for ${result.task.taskId}: ${result.status}; task status ${result.task.status}; events ${result.events?.length ?? 0}`;
40449
+ }
40450
+ function renderWatchMcpTaskResult(result, watchTaskId) {
40451
+ if (result.status === "not_found" || !result.task) {
40452
+ return {
40453
+ content: [{ type: "text", text: `Task watch "${watchTaskId}" finished: watched task not found.` }],
40454
+ structuredContent: { watchTaskId, ...result },
40455
+ isError: true
40456
+ };
40457
+ }
40458
+ const header = [
40459
+ `Task watch "${watchTaskId}" finished with ${result.status.replace("_", " ")}.`,
40460
+ `Watched task ${result.task.taskId} is ${result.task.status}.`,
40461
+ `nextAfterSeq: ${result.nextAfterSeq}`,
40462
+ result.historyTruncated ? "historyTruncated: true" : ""
40463
+ ].filter((line) => line.length > 0);
40464
+ const events = result.events.length > 0 ? [
40465
+ "Events:",
40466
+ ...result.events.map((event) => {
40467
+ const detail = event.summary ?? event.message ?? event.status ?? "";
40468
+ return `- #${event.seq} ${event.type} at ${event.at}${detail ? `: ${detail}` : ""}`;
40469
+ })
40470
+ ] : ["Events: none"];
40471
+ 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.`;
40472
+ return {
40473
+ content: [{ type: "text", text: [...header, ...events, next].join(`
40474
+ `) }],
40475
+ structuredContent: { watchTaskId, ...result }
40476
+ };
40477
+ }
40478
+ function createWeacpxTaskStore(resolveState, taskOptionsById, watchTasksById) {
40479
+ return {
40480
+ createTask: async () => {
40481
+ throw new Error("weacpx native MCP tasks are created by delegate_request");
40482
+ },
40483
+ getTask: async (taskId) => {
40484
+ const watchTask = watchTasksById.get(taskId);
40485
+ if (watchTask)
40486
+ return watchTask.task;
40487
+ const state = await resolveState();
40488
+ const task = await state.transport.getTask({ coordinatorSession: state.coordinatorSession, taskId });
40489
+ return task ? toMcpTask(task, taskOptionsById.get(taskId)) : null;
40490
+ },
40491
+ storeTaskResult: async () => {
40492
+ throw new Error("weacpx native MCP task results are stored by orchestration");
40493
+ },
40494
+ getTaskResult: async (taskId) => {
40495
+ const watchTask = watchTasksById.get(taskId);
40496
+ if (watchTask) {
40497
+ if (!watchTask.result) {
40498
+ throw new Error(`Task ${taskId} is still ${watchTask.task.status}`);
40499
+ }
40500
+ watchTasksById.delete(taskId);
40501
+ return watchTask.result;
40502
+ }
40503
+ const state = await resolveState();
40504
+ const task = await state.transport.getTask({ coordinatorSession: state.coordinatorSession, taskId });
40505
+ if (!task) {
40506
+ throw new Error(`Task not found: ${taskId}`);
40507
+ }
40508
+ return renderNativeTaskPayloadResult(task);
40509
+ },
40510
+ updateTaskStatus: async (taskId, status, statusMessage) => {
40511
+ const state = await resolveState();
40512
+ if (status === "cancelled") {
40513
+ await state.transport.cancelTask({ coordinatorSession: state.coordinatorSession, taskId });
40514
+ return;
40515
+ }
40516
+ throw new Error(`weacpx MCP task status is read-only (${status}${statusMessage ? `: ${statusMessage}` : ""})`);
40517
+ },
40518
+ listTasks: async (cursor) => {
40519
+ const state = await resolveState();
40520
+ const tasks = await state.transport.listTasks({
40521
+ coordinatorSession: state.coordinatorSession,
40522
+ sort: "updatedAt",
40523
+ order: "desc"
40524
+ });
40525
+ const watchTasks = Array.from(watchTasksById.values()).map((record3) => record3.task);
40526
+ pruneTaskOptions(taskOptionsById, new Set([...tasks.map((task) => task.taskId), ...watchTasks.map((task) => task.taskId)]));
40527
+ const offset = parseTaskListCursor(cursor);
40528
+ const allTasks = [
40529
+ ...watchTasks,
40530
+ ...tasks.map((task) => toMcpTask(task, taskOptionsById.get(task.taskId)))
40531
+ ].sort((a, b) => b.lastUpdatedAt.localeCompare(a.lastUpdatedAt));
40532
+ const page = allTasks.slice(offset, offset + TASKS_LIST_PAGE_SIZE);
40533
+ const nextOffset = offset + page.length;
40534
+ return {
40535
+ tasks: page,
40536
+ ...nextOffset < allTasks.length ? { nextCursor: String(nextOffset) } : {}
40537
+ };
40538
+ }
40539
+ };
40540
+ }
40541
+ function rememberTaskOptions(taskOptionsById, taskId, options) {
40542
+ taskOptionsById.set(taskId, normalizeCreateTaskOptions(options));
40543
+ while (taskOptionsById.size > TASK_OPTIONS_CACHE_LIMIT) {
40544
+ const oldestKey = taskOptionsById.keys().next().value;
40545
+ if (oldestKey === undefined)
40546
+ break;
40547
+ taskOptionsById.delete(oldestKey);
40548
+ }
40549
+ }
40550
+ function registerWatchTask(watchTasksById, watchTaskId, record3) {
40551
+ watchTasksById.set(watchTaskId, record3);
40552
+ while (watchTasksById.size > WATCH_TASKS_CACHE_LIMIT) {
40553
+ const oldestKey = watchTasksById.keys().next().value;
40554
+ if (oldestKey === undefined || oldestKey === watchTaskId)
40555
+ break;
40556
+ watchTasksById.delete(oldestKey);
40557
+ }
40558
+ }
40559
+ function pruneTaskOptions(taskOptionsById, taskIds) {
40560
+ for (const taskId of taskOptionsById.keys()) {
40561
+ if (!taskIds.has(taskId)) {
40562
+ taskOptionsById.delete(taskId);
40563
+ }
40564
+ }
40565
+ }
40566
+ function parseTaskListCursor(cursor) {
40567
+ if (!cursor)
40568
+ return 0;
40569
+ const offset = Number(cursor);
40570
+ if (!Number.isInteger(offset) || offset < 0) {
40571
+ throw new McpError(ErrorCode.InvalidParams, `Invalid tasks/list cursor: ${cursor}`);
40572
+ }
40573
+ return offset;
40574
+ }
40575
+ function renderNativeTaskPayloadResult(task) {
40576
+ if (toMcpTaskStatus(task) === "input_required") {
40577
+ return renderInputRequiredTaskResult(task);
40578
+ }
40579
+ if (task.status === "completed" || task.status === "failed" || task.status === "cancelled") {
40580
+ return renderNativeTaskResult(task);
40581
+ }
40582
+ throw new McpError(ErrorCode.InvalidRequest, `Task ${task.taskId} is still ${task.status}; use tasks/get until it is terminal or input_required`);
40583
+ }
40584
+ function withRelatedTaskMeta(result, taskId) {
40585
+ return {
40586
+ ...result,
40587
+ _meta: {
40588
+ ...result._meta,
40589
+ [RELATED_TASK_META_KEY]: { taskId }
40590
+ }
40591
+ };
40592
+ }
40593
+ function renderNativeTaskResult(task) {
40594
+ const isError = task.status === "failed" || task.status === "cancelled";
40595
+ const text = [
40596
+ `Task "${task.taskId}" finished with status ${task.status}.`,
40597
+ task.resultText.trim().length > 0 ? task.resultText : task.summary
40598
+ ].filter((line) => line.trim().length > 0).join(`
40599
+ `);
40600
+ return {
40601
+ content: [{ type: "text", text }],
40602
+ structuredContent: { task },
40603
+ ...isError ? { isError: true } : {}
40604
+ };
40605
+ }
40606
+ function renderInputRequiredTaskResult(task) {
40607
+ const actions = inputRequiredActions(task);
40608
+ const text = [
40609
+ `Task "${task.taskId}" requires input before it can continue.`,
40610
+ task.summary.trim().length > 0 ? task.summary : "",
40611
+ task.openQuestion ? `Open question: ${task.openQuestion.question}` : "",
40612
+ `Next: call task_get("${task.taskId}") to inspect details, then ${actions.join(" or ")}.`
40613
+ ].filter((line) => line.trim().length > 0).join(`
40614
+ `);
40615
+ return {
40616
+ content: [{ type: "text", text }],
40617
+ structuredContent: {
40618
+ task,
40619
+ nextAction: {
40620
+ kind: "input_required",
40621
+ taskId: task.taskId,
40622
+ recommendedTools: actions
40623
+ }
40624
+ }
40625
+ };
40626
+ }
40627
+ function inputRequiredActions(task) {
40628
+ const actions = [];
40629
+ if (task.status === "needs_confirmation") {
40630
+ actions.push("task_approve", "task_reject");
40631
+ }
40632
+ if (task.status === "blocked" || task.status === "waiting_for_human" || task.openQuestion) {
40633
+ actions.push("coordinator_answer_question");
40634
+ }
40635
+ if (task.reviewPending) {
40636
+ actions.push("coordinator_review_contested_result");
40637
+ }
40638
+ return actions.length > 0 ? actions : ["task_get"];
40639
+ }
40640
+ function toMcpTask(task, options = {}) {
40641
+ const statusMessage = mcpTaskStatusMessage(task);
40642
+ return {
40643
+ taskId: task.taskId,
40644
+ status: toMcpTaskStatus(task),
40645
+ ttl: options.ttl ?? null,
40646
+ createdAt: task.createdAt,
40647
+ lastUpdatedAt: task.updatedAt,
40648
+ ...options.pollInterval !== undefined ? { pollInterval: options.pollInterval } : {},
40649
+ ...statusMessage ? { statusMessage } : {}
40650
+ };
40651
+ }
40652
+ function mcpTaskStatusMessage(task) {
40653
+ const lines = [
40654
+ task.summary.trim().length > 0 ? task.summary : "",
40655
+ task.lastProgressSummary ? `Latest progress: ${task.lastProgressSummary}` : "",
40656
+ task.lastProgressAt ? `Last progress at: ${task.lastProgressAt}` : ""
40657
+ ].filter((line) => line.trim().length > 0);
40658
+ return lines.length > 0 ? lines.join(`
40659
+ `) : undefined;
40660
+ }
40661
+ function toMcpTaskStatus(task) {
40662
+ if (task.reviewPending !== undefined)
40663
+ return "input_required";
40664
+ if (task.status === "completed")
40665
+ return "completed";
40666
+ if (task.status === "failed")
40667
+ return "failed";
40668
+ if (task.status === "cancelled")
40669
+ return "cancelled";
40670
+ if (task.status === "needs_confirmation" || task.status === "blocked" || task.status === "waiting_for_human") {
40671
+ return "input_required";
40672
+ }
40673
+ return "working";
40674
+ }
40675
+ function normalizeCreateTaskOptions(options) {
40676
+ return {
40677
+ ttl: options.ttl ?? null,
40678
+ ...options.pollInterval !== undefined ? { pollInterval: options.pollInterval } : {}
39902
40679
  };
39903
40680
  }
39904
40681
  async function resolveMcpIdentity(server, options) {
@@ -41834,6 +42611,7 @@ var HELP_LINES = [
41834
42611
  "weacpx plugin list|add|update|remove|enable|disable|doctor|known - 管理插件",
41835
42612
  "weacpx doctor - 运行诊断",
41836
42613
  "weacpx version - 查看版本",
42614
+ "weacpx agent|agents list|add|rm|templates - 管理本机 Agent",
41837
42615
  "weacpx workspace list|add|rm - 管理本机工作区(别名:ws)",
41838
42616
  "weacpx mcp-stdio [--coordinator-session <session>] [--source-handle <handle>] [--workspace <name>] - 启动 MCP stdio 服务"
41839
42617
  ];
@@ -41911,6 +42689,17 @@ async function runCli(args, deps = {}) {
41911
42689
  }
41912
42690
  return result;
41913
42691
  }
42692
+ case "agent":
42693
+ case "agents": {
42694
+ const result = await handleAgentCli(args.slice(1), { print });
42695
+ if (result === null) {
42696
+ for (const line of HELP_LINES) {
42697
+ print(line);
42698
+ }
42699
+ return 1;
42700
+ }
42701
+ return result;
42702
+ }
41914
42703
  case "plugin": {
41915
42704
  const result = await handlePluginCli(args.slice(1), await createPluginCliDeps({
41916
42705
  print,
@@ -42152,6 +42941,93 @@ async function workspaceRemove(rawName, print) {
42152
42941
  print(`工作区「${name}」已删除`);
42153
42942
  return 0;
42154
42943
  }
42944
+ async function handleAgentCli(args, deps) {
42945
+ const subcommand = args[0];
42946
+ switch (subcommand) {
42947
+ case "list":
42948
+ if (args.length !== 1)
42949
+ return null;
42950
+ return await agentList(deps.print);
42951
+ case "templates":
42952
+ if (args.length !== 1)
42953
+ return null;
42954
+ return agentTemplates(deps.print);
42955
+ case "add":
42956
+ if (args.length !== 2 || !args[1])
42957
+ return null;
42958
+ return await agentAdd(args[1], deps.print);
42959
+ case "rm":
42960
+ if (args.length !== 2 || !args[1])
42961
+ return null;
42962
+ return await agentRemove(args[1], deps.print);
42963
+ default:
42964
+ return null;
42965
+ }
42966
+ }
42967
+ async function agentList(print) {
42968
+ const store = await createCliConfigStore();
42969
+ const config2 = await store.load();
42970
+ const entries = Object.entries(config2.agents);
42971
+ if (entries.length === 0) {
42972
+ print("还没有 Agent。");
42973
+ return 0;
42974
+ }
42975
+ print("Agent 列表:");
42976
+ for (const [name, agent] of entries) {
42977
+ const command = agent.command ? ` command=${agent.command}` : "";
42978
+ print(`- ${name}: driver=${agent.driver}${command}`);
42979
+ }
42980
+ return 0;
42981
+ }
42982
+ function agentTemplates(print) {
42983
+ print("可用 Agent 模板:");
42984
+ for (const name of listAgentTemplates()) {
42985
+ print(`- ${name}`);
42986
+ }
42987
+ return 0;
42988
+ }
42989
+ async function agentAdd(rawName, print) {
42990
+ const name = rawName.trim();
42991
+ if (name.length === 0) {
42992
+ print("Agent 名称不能为空。");
42993
+ return 1;
42994
+ }
42995
+ const template = getAgentTemplate(name);
42996
+ if (!template) {
42997
+ print(`暂不支持这个 Agent 模板。当前可用:${listAgentTemplates().join("、")}`);
42998
+ return 1;
42999
+ }
43000
+ const store = await createCliConfigStore();
43001
+ const config2 = await store.load();
43002
+ const existing = config2.agents[name];
43003
+ if (existing) {
43004
+ if (sameAgentConfig(existing, template)) {
43005
+ print(`Agent「${name}」已存在`);
43006
+ return 0;
43007
+ }
43008
+ print(`Agent「${name}」已存在且配置不同。请先执行:weacpx agent rm ${name}`);
43009
+ return 1;
43010
+ }
43011
+ await store.upsertAgent(name, template);
43012
+ print(`Agent「${name}」已保存`);
43013
+ return 0;
43014
+ }
43015
+ async function agentRemove(rawName, print) {
43016
+ const name = rawName.trim();
43017
+ if (name.length === 0) {
43018
+ print("Agent 名称不能为空。");
43019
+ return 1;
43020
+ }
43021
+ const store = await createCliConfigStore();
43022
+ const config2 = await store.load();
43023
+ if (!config2.agents[name]) {
43024
+ print(`没有找到 Agent「${name}」。`);
43025
+ return 1;
43026
+ }
43027
+ await store.removeAgent(name);
43028
+ print(`Agent「${name}」已删除`);
43029
+ return 0;
43030
+ }
42155
43031
  async function createCliConfigStore() {
42156
43032
  const configPath = process.env.WEACPX_CONFIG ?? `${requireHome2()}/.weacpx/config.json`;
42157
43033
  await ensureConfigExists(configPath);