weacpx 0.4.1 → 0.4.3

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
@@ -2610,18 +2610,22 @@ class DaemonController {
2610
2610
  statusStore;
2611
2611
  startupPollIntervalMs;
2612
2612
  startupTimeoutMs;
2613
+ onboardingStartupTimeoutMs;
2613
2614
  onStartupPoll;
2614
2615
  shutdownPollIntervalMs;
2615
2616
  shutdownTimeoutMs;
2616
2617
  onShutdownPoll;
2618
+ now;
2617
2619
  constructor(paths, deps) {
2618
2620
  this.paths = paths;
2619
2621
  this.deps = deps;
2620
2622
  this.statusStore = new DaemonStatusStore(paths.statusFile);
2621
2623
  this.startupPollIntervalMs = deps.startupPollIntervalMs ?? 50;
2622
2624
  this.startupTimeoutMs = deps.startupTimeoutMs ?? 5000;
2625
+ this.onboardingStartupTimeoutMs = deps.onboardingStartupTimeoutMs ?? 300000;
2623
2626
  this.shutdownPollIntervalMs = deps.shutdownPollIntervalMs ?? 50;
2624
2627
  this.shutdownTimeoutMs = deps.shutdownTimeoutMs ?? 5000;
2628
+ this.now = deps.now ?? (() => Date.now());
2625
2629
  this.onStartupPoll = deps.onStartupPoll ?? (async () => {
2626
2630
  await new Promise((resolve) => setTimeout(resolve, this.startupPollIntervalMs));
2627
2631
  });
@@ -2659,7 +2663,7 @@ class DaemonController {
2659
2663
  await this.statusStore.clear();
2660
2664
  const pid = await this.deps.spawnDetached(options);
2661
2665
  await this.writePid(pid);
2662
- await this.waitForStartupMetadata(pid);
2666
+ await this.waitForStartupMetadata(pid, options.firstRunOnboarding ? this.onboardingStartupTimeoutMs : this.startupTimeoutMs, options.startupWait);
2663
2667
  return { state: "started", pid };
2664
2668
  }
2665
2669
  async stop() {
@@ -2695,9 +2699,10 @@ class DaemonController {
2695
2699
  await rm2(this.paths.pidFile, { force: true });
2696
2700
  await this.statusStore.clear();
2697
2701
  }
2698
- async waitForStartupMetadata(pid) {
2699
- const deadline = Date.now() + this.startupTimeoutMs;
2700
- while (Date.now() < deadline) {
2702
+ async waitForStartupMetadata(pid, timeoutMs, startupWait) {
2703
+ const startedAt = this.now();
2704
+ const deadline = startedAt + timeoutMs;
2705
+ while (this.now() < deadline) {
2701
2706
  const status = await this.statusStore.load();
2702
2707
  if (status?.pid === pid) {
2703
2708
  return;
@@ -2706,9 +2711,17 @@ class DaemonController {
2706
2711
  await this.clearRuntimeFiles();
2707
2712
  throw new Error(`weacpx daemon exited before reporting ready state (pid ${pid})`);
2708
2713
  }
2714
+ if (startupWait?.shouldStopWaiting?.()) {
2715
+ return;
2716
+ }
2717
+ await startupWait?.onPoll?.({
2718
+ elapsedMs: this.now() - startedAt,
2719
+ timeoutMs,
2720
+ pid
2721
+ });
2709
2722
  await this.onStartupPoll();
2710
2723
  }
2711
- throw new Error(`weacpx daemon did not report ready state within ${this.startupTimeoutMs}ms (pid ${pid})`);
2724
+ throw new Error(`weacpx daemon did not report ready state within ${timeoutMs}ms (pid ${pid})`);
2712
2725
  }
2713
2726
  async waitForShutdown(pid) {
2714
2727
  const deadline = Date.now() + this.shutdownTimeoutMs;
@@ -9542,7 +9555,7 @@ function isOptionalBoolean(value) {
9542
9555
  return value === undefined || typeof value === "boolean";
9543
9556
  }
9544
9557
  function isTaskStatus(value) {
9545
- return value === "pending" || value === "needs_confirmation" || value === "running" || value === "blocked" || value === "waiting_for_human" || value === "completed" || value === "failed" || value === "cancelled";
9558
+ return value === "needs_confirmation" || value === "running" || value === "blocked" || value === "waiting_for_human" || value === "completed" || value === "failed" || value === "cancelled";
9546
9559
  }
9547
9560
  function isSourceKind(value) {
9548
9561
  return value === "human" || value === "coordinator" || value === "worker";
@@ -18191,6 +18204,7 @@ class CommandRouter {
18191
18204
  });
18192
18205
  return { text: renderCommandAccessDenied(command) };
18193
18206
  }
18207
+ await this.refreshConfigFromStore();
18194
18208
  return await this.executeCommand(chatKey, command.kind, startedAt, async () => {
18195
18209
  switch (command.kind) {
18196
18210
  case "invalid":
@@ -18396,8 +18410,21 @@ class CommandRouter {
18396
18410
  ...channel,
18397
18411
  ...channel.options ? { options: { ...channel.options } } : {}
18398
18412
  }));
18413
+ this.config.plugins = updated.plugins.map((plugin) => ({ ...plugin }));
18399
18414
  this.config.agents = { ...updated.agents };
18400
18415
  this.config.workspaces = { ...updated.workspaces };
18416
+ this.config.orchestration = {
18417
+ ...updated.orchestration,
18418
+ allowedAgentRequestTargets: [...updated.orchestration.allowedAgentRequestTargets],
18419
+ allowedAgentRequestRoles: [...updated.orchestration.allowedAgentRequestRoles]
18420
+ };
18421
+ }
18422
+ async refreshConfigFromStore() {
18423
+ if (!this.config || !this.configStore) {
18424
+ return;
18425
+ }
18426
+ const updated = await this.configStore.load();
18427
+ this.replaceConfig(updated);
18401
18428
  }
18402
18429
  async executeCommand(chatKey, kind, startedAt, operation) {
18403
18430
  try {
@@ -18971,7 +18998,6 @@ class OrchestrationServer {
18971
18998
  }
18972
18999
  requireOnlyKeys(filter, ["coordinatorSession", "status", "stuck", "sort", "order"], "filter");
18973
19000
  const status = requireOptionalEnum(filter, "status", [
18974
- "pending",
18975
19001
  "needs_confirmation",
18976
19002
  "running",
18977
19003
  "blocked",
@@ -21368,7 +21394,7 @@ class OrchestrationService {
21368
21394
  }
21369
21395
  buildGroupSummary(group, tasks) {
21370
21396
  const sortedTasks = tasks.slice().sort((left, right) => left.createdAt.localeCompare(right.createdAt)).map((task) => ({ ...task }));
21371
- const pendingApprovalTasks = sortedTasks.filter((task) => task.status === "pending" || task.status === "needs_confirmation").length;
21397
+ const pendingApprovalTasks = sortedTasks.filter((task) => task.status === "needs_confirmation").length;
21372
21398
  const runningTasks = sortedTasks.filter((task) => task.status === "running").length;
21373
21399
  const completedTasks = sortedTasks.filter((task) => task.status === "completed").length;
21374
21400
  const failedTasks = sortedTasks.filter((task) => task.status === "failed").length;
@@ -22223,7 +22249,7 @@ function isTerminalTaskStatus2(status) {
22223
22249
  return status === "completed" || status === "failed" || status === "cancelled";
22224
22250
  }
22225
22251
  function isAttentionRequiredTask(task) {
22226
- return task.reviewPending !== undefined || task.status === "pending" || task.status === "needs_confirmation" || task.status === "blocked" || task.status === "waiting_for_human";
22252
+ return task.reviewPending !== undefined || task.status === "needs_confirmation" || task.status === "blocked" || task.status === "waiting_for_human";
22227
22253
  }
22228
22254
  function clampWaitTimeout(timeoutMs) {
22229
22255
  if (timeoutMs === undefined) {
@@ -26172,7 +26198,7 @@ init_create_daemon_controller();
26172
26198
  init_daemon_files();
26173
26199
  import { randomUUID as randomUUID4 } from "node:crypto";
26174
26200
  import { homedir as homedir13 } from "node:os";
26175
- import { sep } from "node:path";
26201
+ import { dirname as dirname13, join as join16, sep } from "node:path";
26176
26202
  import { fileURLToPath as fileURLToPath6 } from "node:url";
26177
26203
 
26178
26204
  // src/daemon/daemon-runtime.ts
@@ -38611,7 +38637,6 @@ init_task_wait_timeouts();
38611
38637
  init_quota_errors();
38612
38638
  var groupStatusSchema = exports_external.enum(["pending", "running", "terminal"]);
38613
38639
  var taskStatusSchema = exports_external.enum([
38614
- "pending",
38615
38640
  "needs_confirmation",
38616
38641
  "running",
38617
38642
  "blocked",
@@ -38628,11 +38653,11 @@ var taskQuestionSchema = exports_external.object({
38628
38653
  questionId: exports_external.string().min(1)
38629
38654
  }).strict();
38630
38655
  function buildWeacpxMcpToolRegistry(input) {
38631
- const { transport, coordinatorSession, sourceHandle, availableAgents } = input;
38632
- return [
38656
+ const { transport, coordinatorSession, sourceHandle, isExternalCoordinator, availableAgents } = input;
38657
+ const tools = [
38633
38658
  {
38634
38659
  name: "delegate_request",
38635
- description: `Delegate a subtask to another agent under the current coordinator. Pass an absolute workingDirectory for the worker.${availableAgents && availableAgents.length > 0 ? ` Available agents: ${availableAgents.join(", ")}.` : ""}`,
38660
+ 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(", ")}.` : ""}`,
38636
38661
  inputSchema: exports_external.object({
38637
38662
  targetAgent: exports_external.string().min(1),
38638
38663
  task: exports_external.string().min(1),
@@ -38652,7 +38677,7 @@ function buildWeacpxMcpToolRegistry(input) {
38652
38677
  },
38653
38678
  {
38654
38679
  name: "group_new",
38655
- description: "Create a new task group under the current coordinator.",
38680
+ 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.",
38656
38681
  inputSchema: exports_external.object({
38657
38682
  title: exports_external.string().min(1)
38658
38683
  }).strict(),
@@ -38666,7 +38691,7 @@ function buildWeacpxMcpToolRegistry(input) {
38666
38691
  },
38667
38692
  {
38668
38693
  name: "group_get",
38669
- description: "Fetch a single task-group summary under the current coordinator.",
38694
+ description: "Fetch a single task-group summary under the current coordinator. Use to check aggregate progress when waiting on a batch of delegations.",
38670
38695
  inputSchema: exports_external.object({
38671
38696
  groupId: exports_external.string().min(1)
38672
38697
  }).strict(),
@@ -38680,7 +38705,7 @@ function buildWeacpxMcpToolRegistry(input) {
38680
38705
  },
38681
38706
  {
38682
38707
  name: "group_list",
38683
- description: "List task groups under the current coordinator.",
38708
+ description: "List task groups under the current coordinator. Use to recover groupIds for an earlier batch.",
38684
38709
  inputSchema: exports_external.object({
38685
38710
  status: groupStatusSchema.optional(),
38686
38711
  stuck: exports_external.boolean().optional(),
@@ -38701,7 +38726,7 @@ function buildWeacpxMcpToolRegistry(input) {
38701
38726
  },
38702
38727
  {
38703
38728
  name: "group_cancel",
38704
- description: "Cancel all unfinished tasks in a task group under the current coordinator.",
38729
+ description: "Cancel all unfinished tasks in a task group under the current coordinator. Use to abort a batch started via group_new + delegate_request.",
38705
38730
  inputSchema: exports_external.object({
38706
38731
  groupId: exports_external.string().min(1)
38707
38732
  }).strict(),
@@ -38715,7 +38740,7 @@ function buildWeacpxMcpToolRegistry(input) {
38715
38740
  },
38716
38741
  {
38717
38742
  name: "task_get",
38718
- description: "Fetch a single task under the current coordinator.",
38743
+ 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.",
38719
38744
  inputSchema: exports_external.object({
38720
38745
  taskId: exports_external.string().min(1)
38721
38746
  }).strict(),
@@ -38729,7 +38754,7 @@ function buildWeacpxMcpToolRegistry(input) {
38729
38754
  },
38730
38755
  {
38731
38756
  name: "task_list",
38732
- description: "List tasks under the current coordinator.",
38757
+ description: "List tasks under the current coordinator. Use to recover taskIds for in-flight delegations or to survey what is still running / blocked.",
38733
38758
  inputSchema: exports_external.object({
38734
38759
  status: taskStatusSchema.optional(),
38735
38760
  stuck: exports_external.boolean().optional(),
@@ -38750,7 +38775,7 @@ function buildWeacpxMcpToolRegistry(input) {
38750
38775
  },
38751
38776
  {
38752
38777
  name: "task_approve",
38753
- description: "Approve a pending task under the current coordinator.",
38778
+ 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.",
38754
38779
  inputSchema: exports_external.object({
38755
38780
  taskId: exports_external.string().min(1)
38756
38781
  }).strict(),
@@ -38764,7 +38789,7 @@ function buildWeacpxMcpToolRegistry(input) {
38764
38789
  },
38765
38790
  {
38766
38791
  name: "task_reject",
38767
- description: "Reject a pending task under the current coordinator.",
38792
+ 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.",
38768
38793
  inputSchema: exports_external.object({
38769
38794
  taskId: exports_external.string().min(1)
38770
38795
  }).strict(),
@@ -38778,7 +38803,7 @@ function buildWeacpxMcpToolRegistry(input) {
38778
38803
  },
38779
38804
  {
38780
38805
  name: "task_cancel",
38781
- description: "Request cancellation for a task under the current coordinator.",
38806
+ 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.",
38782
38807
  inputSchema: exports_external.object({
38783
38808
  taskId: exports_external.string().min(1)
38784
38809
  }).strict(),
@@ -38792,7 +38817,7 @@ function buildWeacpxMcpToolRegistry(input) {
38792
38817
  },
38793
38818
  {
38794
38819
  name: "task_wait",
38795
- description: `Wait for a task to finish or require attention. 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.`,
38820
+ 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.`,
38796
38821
  inputSchema: exports_external.object({
38797
38822
  taskId: exports_external.string().min(1),
38798
38823
  timeoutMs: exports_external.number().int().min(0).max(MAX_TASK_WAIT_TIMEOUT_MS).optional(),
@@ -38808,7 +38833,7 @@ function buildWeacpxMcpToolRegistry(input) {
38808
38833
  },
38809
38834
  {
38810
38835
  name: "worker_raise_question",
38811
- description: "Raise a blocker question for the current bound worker session.",
38836
+ 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.",
38812
38837
  inputSchema: exports_external.object({
38813
38838
  taskId: exports_external.string().min(1),
38814
38839
  question: exports_external.string().min(1),
@@ -38828,7 +38853,7 @@ function buildWeacpxMcpToolRegistry(input) {
38828
38853
  },
38829
38854
  {
38830
38855
  name: "coordinator_answer_question",
38831
- description: "Answer a blocked worker question under the current coordinator.",
38856
+ 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.",
38832
38857
  inputSchema: exports_external.object({
38833
38858
  taskId: exports_external.string().min(1),
38834
38859
  questionId: exports_external.string().min(1),
@@ -38844,7 +38869,7 @@ function buildWeacpxMcpToolRegistry(input) {
38844
38869
  },
38845
38870
  {
38846
38871
  name: "coordinator_request_human_input",
38847
- description: "Create or queue a human question package for blocked tasks under the current coordinator.",
38872
+ description: "Create or queue a human question package for blocked tasks under the current coordinator. Use when answering a worker question requires real human input rather than your own judgement.",
38848
38873
  inputSchema: exports_external.object({
38849
38874
  taskQuestions: exports_external.array(taskQuestionSchema).min(1),
38850
38875
  promptText: exports_external.string().min(1),
@@ -38860,7 +38885,7 @@ function buildWeacpxMcpToolRegistry(input) {
38860
38885
  },
38861
38886
  {
38862
38887
  name: "coordinator_follow_up_human_package",
38863
- description: "Append a follow-up message to the active human question package under the current coordinator.",
38888
+ 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.",
38864
38889
  inputSchema: exports_external.object({
38865
38890
  packageId: exports_external.string().min(1),
38866
38891
  priorMessageId: exports_external.string().min(1),
@@ -38877,7 +38902,7 @@ function buildWeacpxMcpToolRegistry(input) {
38877
38902
  },
38878
38903
  {
38879
38904
  name: "coordinator_review_contested_result",
38880
- description: "Review a contested result under the current coordinator.",
38905
+ 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.",
38881
38906
  inputSchema: exports_external.object({
38882
38907
  taskId: exports_external.string().min(1),
38883
38908
  reviewId: exports_external.string().min(1),
@@ -38894,6 +38919,14 @@ function buildWeacpxMcpToolRegistry(input) {
38894
38919
  })
38895
38920
  }
38896
38921
  ];
38922
+ if (isExternalCoordinator) {
38923
+ const externalCoordinatorIncompatibleTools = new Set([
38924
+ "coordinator_request_human_input",
38925
+ "coordinator_follow_up_human_package"
38926
+ ]);
38927
+ return tools.filter((tool) => !externalCoordinatorIncompatibleTools.has(tool.name));
38928
+ }
38929
+ return tools;
38897
38930
  }
38898
38931
  async function asToolResult(action) {
38899
38932
  try {
@@ -38922,12 +38955,28 @@ function renderTaskWaitResult(result) {
38922
38955
  return `Task wait ${result.status.replace("_", " ")}; current state is unavailable.`;
38923
38956
  }
38924
38957
  if (result.status === "timeout") {
38925
- return `Task ${result.task.taskId} wait timed out; current state is ${result.task.status}.`;
38958
+ return [
38959
+ `Task ${result.task.taskId} wait timed out; current state is ${result.task.status}.`,
38960
+ `Next: call task_wait again with this taskId to keep waiting, or task_get for a snapshot.`
38961
+ ].join(`
38962
+ `);
38926
38963
  }
38927
38964
  if (result.status === "attention_required") {
38928
- return `Task ${result.task.taskId} requires attention; current state is ${result.task.status}.`;
38965
+ return [
38966
+ `Task ${result.task.taskId} requires attention; current state is ${result.task.status}.`,
38967
+ `Next: call task_get to read the task's current status and any reviewPending / openQuestion fields, then branch by what you see:`,
38968
+ ` - status=needs_confirmation -> task_approve or task_reject`,
38969
+ ` - status=blocked or waiting_for_human -> coordinator_answer_question`,
38970
+ ` - reviewPending set -> coordinator_review_contested_result`,
38971
+ `After resolving, call task_wait again to keep waiting for the worker to finish.`
38972
+ ].join(`
38973
+ `);
38929
38974
  }
38930
- return `Task ${result.task.taskId} reached terminal state ${result.task.status}.`;
38975
+ return [
38976
+ `Task ${result.task.taskId} reached terminal state ${result.task.status}.`,
38977
+ `Next: call task_get to read the worker's final result before reporting back to the user.`
38978
+ ].join(`
38979
+ `);
38931
38980
  }
38932
38981
  function createSuccessResult(text, structuredContent) {
38933
38982
  return {
@@ -38942,7 +38991,8 @@ function createErrorResult(message) {
38942
38991
  };
38943
38992
  }
38944
38993
  function renderDelegateSuccess(result) {
38945
- return [`Delegation task "${result.taskId}" created.`, `- Status: ${result.status}`].join(`
38994
+ 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.`;
38995
+ return [`Delegation task "${result.taskId}" created.`, `- Status: ${result.status}`, next].join(`
38946
38996
  `);
38947
38997
  }
38948
38998
  function renderGroupCreated(group) {
@@ -39087,7 +39137,11 @@ function renderTaskCancelRequest(task) {
39087
39137
  `);
39088
39138
  }
39089
39139
  function renderTaskApprovalSuccess(task) {
39090
- return [`Task "${task.taskId}" approved.`, `- Current status: ${task.status}`].join(`
39140
+ return [
39141
+ `Task "${task.taskId}" approved.`,
39142
+ `- Current status: ${task.status}`,
39143
+ `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.`
39144
+ ].join(`
39091
39145
  `);
39092
39146
  }
39093
39147
  function renderTaskRejectionSuccess(task) {
@@ -39322,6 +39376,30 @@ function createOrchestrationTransport(endpoint, deps = {}) {
39322
39376
  }
39323
39377
 
39324
39378
  // src/mcp/weacpx-mcp-server.ts
39379
+ var WEACPX_MCP_SERVER_INSTRUCTIONS = [
39380
+ "Use these tools to orchestrate work across other agents under your coordinator session.",
39381
+ "",
39382
+ "Typical lifecycle for a single delegation:",
39383
+ "1. delegate_request → returns { taskId, status }.",
39384
+ " - status=running: the worker has started; go to step 2.",
39385
+ " - 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.",
39386
+ "2. task_wait(taskId) → blocks until the task is done, needs attention, or times out.",
39387
+ " - status=terminal: go to step 3.",
39388
+ " - 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:",
39389
+ " * needs_confirmation -> task_approve or task_reject (after approval, go back to step 2)",
39390
+ " * blocked or waiting_for_human -> coordinator_answer_question (the answer can come from you or be relayed from a human you consulted)",
39391
+ " * reviewPending set -> coordinator_review_contested_result with accept or discard",
39392
+ " After resolving, call task_wait again to keep waiting.",
39393
+ " - status=timeout: the task is still running. Call task_wait again to keep waiting, or task_get for a snapshot.",
39394
+ "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.",
39395
+ "",
39396
+ "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.",
39397
+ "Cancellation: task_cancel aborts a single running task; group_cancel aborts the whole batch.",
39398
+ "Discovery: task_list / group_list recover taskIds and groupIds from earlier in the session.",
39399
+ "",
39400
+ "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."
39401
+ ].join(`
39402
+ `);
39325
39403
  function createWeacpxMcpServer(options) {
39326
39404
  const server = new Server({
39327
39405
  name: "weacpx-orchestration",
@@ -39329,7 +39407,8 @@ function createWeacpxMcpServer(options) {
39329
39407
  }, {
39330
39408
  capabilities: {
39331
39409
  tools: {}
39332
- }
39410
+ },
39411
+ instructions: WEACPX_MCP_SERVER_INSTRUCTIONS
39333
39412
  });
39334
39413
  let toolState = null;
39335
39414
  let toolStatePromise = null;
@@ -39348,6 +39427,7 @@ function createWeacpxMcpServer(options) {
39348
39427
  transport: options.transport,
39349
39428
  coordinatorSession: identity.coordinatorSession,
39350
39429
  ...identity.sourceHandle ? { sourceHandle: identity.sourceHandle } : {},
39430
+ ...identity.isExternalCoordinator ? { isExternalCoordinator: true } : {},
39351
39431
  ...options.availableAgents ? { availableAgents: options.availableAgents } : {}
39352
39432
  });
39353
39433
  return toolState;
@@ -39397,7 +39477,8 @@ async function resolveMcpIdentity(server, options) {
39397
39477
  if (options.coordinatorSession) {
39398
39478
  return {
39399
39479
  coordinatorSession: options.coordinatorSession,
39400
- ...options.sourceHandle ? { sourceHandle: options.sourceHandle } : {}
39480
+ ...options.sourceHandle ? { sourceHandle: options.sourceHandle } : {},
39481
+ ...options.isExternalCoordinator ? { isExternalCoordinator: true } : {}
39401
39482
  };
39402
39483
  }
39403
39484
  throw new McpError(ErrorCode.InvalidRequest, "weacpx MCP identity is not configured; run through `weacpx mcp-stdio` or provide --coordinator-session");
@@ -41139,6 +41220,79 @@ async function runRestart2(deps) {
41139
41220
  }
41140
41221
  }
41141
41222
 
41223
+ // src/cli/startup-wait-ui.ts
41224
+ var SPINNER_FRAMES = ["⠋", "⠙", "⠹", "⠸", "⠼", "⠴", "⠦", "⠧", "⠇", "⠏"];
41225
+ var DEFAULT_SPINNER_FRAME = "⠋";
41226
+ var ENVIRONMENT_HINT_DELAY_MS = 20000;
41227
+ function renderStartupWaitLine(input) {
41228
+ const elapsedSeconds = Math.floor(input.elapsedMs / 1000);
41229
+ const timeoutSeconds = Math.ceil(input.timeoutMs / 1000);
41230
+ const detail = input.elapsedMs >= ENVIRONMENT_HINT_DELAY_MS ? ",首次启动可能需要准备依赖和运行环境" : "";
41231
+ return `${input.frame} 正在创建初始会话${detail} ${elapsedSeconds}s / ${timeoutSeconds}s,按 Ctrl+B 跳过等待`;
41232
+ }
41233
+ function createStartupWaitUi(input) {
41234
+ const stdin2 = input.stdin ?? process.stdin;
41235
+ const stdout2 = input.stdout ?? process.stdout;
41236
+ if (!input.isInteractive() || !stdin2.isTTY || !stdout2.isTTY) {
41237
+ return { stop: () => {} };
41238
+ }
41239
+ let skipped = false;
41240
+ let interrupted = false;
41241
+ let frameIndex = 0;
41242
+ let rawModeEnabled = false;
41243
+ let stopped = false;
41244
+ const cleanup = () => {
41245
+ if (stopped) {
41246
+ return;
41247
+ }
41248
+ stopped = true;
41249
+ stdin2.off("data", onData);
41250
+ if (rawModeEnabled && typeof stdin2.setRawMode === "function") {
41251
+ stdin2.setRawMode(false);
41252
+ }
41253
+ stdin2.pause();
41254
+ stdout2.write("\r\x1B[2K");
41255
+ };
41256
+ const onData = (chunk) => {
41257
+ const buffer = Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk);
41258
+ if (buffer.includes(2)) {
41259
+ skipped = true;
41260
+ }
41261
+ if (buffer.includes(3)) {
41262
+ interrupted = true;
41263
+ cleanup();
41264
+ (input.onInterrupt ?? (() => process.kill(process.pid, "SIGINT")))();
41265
+ }
41266
+ };
41267
+ if (typeof stdin2.setRawMode === "function") {
41268
+ stdin2.setRawMode(true);
41269
+ rawModeEnabled = true;
41270
+ }
41271
+ stdin2.resume();
41272
+ stdin2.on("data", onData);
41273
+ const render = (poll) => {
41274
+ const frame = SPINNER_FRAMES[frameIndex % SPINNER_FRAMES.length] ?? DEFAULT_SPINNER_FRAME;
41275
+ frameIndex += 1;
41276
+ stdout2.write(`\r\x1B[2K${renderStartupWaitLine({
41277
+ elapsedMs: poll.elapsedMs,
41278
+ timeoutMs: poll.timeoutMs,
41279
+ frame
41280
+ })}`);
41281
+ };
41282
+ return {
41283
+ wait: {
41284
+ onPoll: render,
41285
+ shouldStopWaiting: () => {
41286
+ if (interrupted) {
41287
+ throw new Error("startup wait interrupted");
41288
+ }
41289
+ return skipped;
41290
+ }
41291
+ },
41292
+ stop: cleanup
41293
+ };
41294
+ }
41295
+
41142
41296
  // src/cli.ts
41143
41297
  init_bootstrap();
41144
41298
  async function prepareMcpCoordinatorStartup(input) {
@@ -41193,7 +41347,7 @@ function createMcpStdioIdentityResolver(input) {
41193
41347
  clientName: context.clientName,
41194
41348
  ...resolvedWorkspace ? { workspace: resolvedWorkspace } : { instanceId }
41195
41349
  });
41196
- await prepareMcpCoordinatorStartup({
41350
+ const startup = await prepareMcpCoordinatorStartup({
41197
41351
  coordinatorSession: resolvedCoordinatorSession,
41198
41352
  ...resolvedWorkspace ? { workspace: resolvedWorkspace } : {},
41199
41353
  config: input.config,
@@ -41202,7 +41356,8 @@ function createMcpStdioIdentityResolver(input) {
41202
41356
  });
41203
41357
  return {
41204
41358
  coordinatorSession: resolvedCoordinatorSession,
41205
- ...sourceHandle ? { sourceHandle } : {}
41359
+ ...sourceHandle ? { sourceHandle } : {},
41360
+ ...startup.kind === "external-coordinator" ? { isExternalCoordinator: true } : {}
41206
41361
  };
41207
41362
  };
41208
41363
  }
@@ -41363,6 +41518,7 @@ async function runCli(args, deps = {}) {
41363
41518
  case "start": {
41364
41519
  const controller = deps.controller ?? createDefaultController();
41365
41520
  try {
41521
+ const isInteractive = deps.isInteractive ?? defaultIsInteractive;
41366
41522
  const status = await controller.getStatus();
41367
41523
  if (status.state === "running") {
41368
41524
  print("weacpx 已在后台运行");
@@ -41375,10 +41531,19 @@ async function runCli(args, deps = {}) {
41375
41531
  const onboarding2 = await runOnboardingBeforeStart({
41376
41532
  print,
41377
41533
  cwd: deps.cwd ?? (() => process.cwd()),
41378
- isInteractive: deps.isInteractive,
41534
+ isInteractive,
41379
41535
  promptText: deps.promptText
41380
41536
  });
41381
- const result = await controller.start({ firstRunOnboarding: onboarding2 ?? undefined });
41537
+ const startupWaitUi = onboarding2 ? createStartupWaitUi({ isInteractive }) : null;
41538
+ let result;
41539
+ try {
41540
+ result = await controller.start({
41541
+ firstRunOnboarding: onboarding2 ?? undefined,
41542
+ ...startupWaitUi?.wait ? { startupWait: startupWaitUi.wait } : {}
41543
+ });
41544
+ } finally {
41545
+ startupWaitUi?.stop();
41546
+ }
41382
41547
  if (result.state === "already-running") {
41383
41548
  print("weacpx 已在后台运行");
41384
41549
  print(`PID: ${result.pid}`);
@@ -41389,9 +41554,7 @@ async function runCli(args, deps = {}) {
41389
41554
  return 0;
41390
41555
  } catch (error2) {
41391
41556
  print(`weacpx 启动失败:${describeFriendlyError(error2)}`);
41392
- const stderrPath = safeStderrLogPath();
41393
- if (stderrPath)
41394
- print(`请查看 Stderr: ${stderrPath}`);
41557
+ printDaemonLogHints(print);
41395
41558
  return 1;
41396
41559
  }
41397
41560
  }
@@ -41434,9 +41597,7 @@ async function runCli(args, deps = {}) {
41434
41597
  return await restartDaemonCli(controller, print);
41435
41598
  } catch (error2) {
41436
41599
  print(`weacpx 重启失败:${describeFriendlyError(error2)}`);
41437
- const stderrPath = safeStderrLogPath();
41438
- if (stderrPath)
41439
- print(`请查看 Stderr: ${stderrPath}`);
41600
+ printDaemonLogHints(print);
41440
41601
  return 1;
41441
41602
  }
41442
41603
  }
@@ -41454,7 +41615,7 @@ async function defaultUpdate(args, input) {
41454
41615
  saveConfig: async (config2) => await store.save(config2),
41455
41616
  readCurrentVersion: readVersion,
41456
41617
  print: input.print,
41457
- isInteractive: input.isInteractive ?? (() => Boolean(process.stdin.isTTY && process.stdout.isTTY)),
41618
+ isInteractive: input.isInteractive ?? defaultIsInteractive,
41458
41619
  promptText: input.promptText ?? defaultPromptText,
41459
41620
  ...input.overrides
41460
41621
  };
@@ -41474,7 +41635,7 @@ async function runOnboardingBeforeStart(input) {
41474
41635
  deps: {
41475
41636
  print: input.print,
41476
41637
  cwd: input.cwd,
41477
- isInteractive: input.isInteractive ?? (() => Boolean(process.stdin.isTTY && process.stdout.isTTY)),
41638
+ isInteractive: input.isInteractive ?? defaultIsInteractive,
41478
41639
  promptText: input.promptText ?? defaultPromptText
41479
41640
  }
41480
41641
  });
@@ -41741,7 +41902,7 @@ async function createChannelCliDeps(input) {
41741
41902
  saveConfig: async (config2) => await store.save(config2),
41742
41903
  print: input.print,
41743
41904
  stderr: input.stderr ?? ((text) => process.stderr.write(text)),
41744
- isInteractive: input.isInteractive ?? (() => Boolean(process.stdin.isTTY && process.stdout.isTTY)),
41905
+ isInteractive: input.isInteractive ?? defaultIsInteractive,
41745
41906
  promptText: input.promptText ?? defaultPromptText,
41746
41907
  promptSecret: input.promptSecret ?? defaultPromptSecret,
41747
41908
  getDaemonStatus: async () => {
@@ -41763,7 +41924,7 @@ async function createPluginCliDeps(input) {
41763
41924
  loadConfig: async () => await store.load(),
41764
41925
  saveConfig: async (config2) => await store.save(config2),
41765
41926
  print: input.print,
41766
- isInteractive: input.isInteractive ?? (() => Boolean(process.stdin.isTTY && process.stdout.isTTY)),
41927
+ isInteractive: input.isInteractive ?? defaultIsInteractive,
41767
41928
  promptText: input.promptText ?? defaultPromptText,
41768
41929
  getDaemonStatus: async () => {
41769
41930
  const status = await controller.getStatus();
@@ -41849,7 +42010,8 @@ function createDefaultController() {
41849
42010
  getStatus: () => controller.getStatus(),
41850
42011
  stop: () => controller.stop(),
41851
42012
  start: (options) => controller.start({
41852
- ...options?.firstRunOnboarding ? { firstRunOnboarding: encodeFirstRunOnboarding(options.firstRunOnboarding) } : {}
42013
+ ...options?.firstRunOnboarding ? { firstRunOnboarding: encodeFirstRunOnboarding(options.firstRunOnboarding) } : {},
42014
+ ...options?.startupWait ? { startupWait: options.startupWait } : {}
41853
42015
  })
41854
42016
  };
41855
42017
  }
@@ -41883,12 +42045,27 @@ function requireHome2() {
41883
42045
  }
41884
42046
  return home;
41885
42047
  }
42048
+ function defaultIsInteractive() {
42049
+ return Boolean(process.stdin.isTTY && process.stdout.isTTY);
42050
+ }
41886
42051
  function describeFriendlyError(error2) {
41887
42052
  return error2 instanceof Error ? error2.message : String(error2);
41888
42053
  }
41889
- function safeStderrLogPath() {
42054
+ function printDaemonLogHints(print) {
42055
+ const paths = safeDaemonLogPaths();
42056
+ if (!paths)
42057
+ return;
42058
+ print(`请查看 App Log: ${paths.appLog}`);
42059
+ print(`请查看 Stderr: ${paths.stderrLog}`);
42060
+ }
42061
+ function safeDaemonLogPaths() {
41890
42062
  try {
41891
- return resolveDaemonPaths({ home: requireHome2() }).stderrLog;
42063
+ const configPath = process.env.WEACPX_CONFIG ?? `${requireHome2()}/.weacpx/config.json`;
42064
+ const paths = resolveDaemonPaths({ home: requireHome2() });
42065
+ return {
42066
+ appLog: join16(dirname13(configPath), "runtime", "app.log"),
42067
+ stderrLog: paths.stderrLog
42068
+ };
41892
42069
  } catch {
41893
42070
  return null;
41894
42071
  }
@@ -1,4 +1,4 @@
1
- export type OrchestrationTaskStatus = "pending" | "needs_confirmation" | "running" | "blocked" | "waiting_for_human" | "completed" | "failed" | "cancelled";
1
+ export type OrchestrationTaskStatus = "needs_confirmation" | "running" | "blocked" | "waiting_for_human" | "completed" | "failed" | "cancelled";
2
2
  export type OrchestrationSourceKind = "human" | "coordinator" | "worker";
3
3
  export interface OrchestrationOpenQuestionRecord {
4
4
  questionId: string;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "weacpx",
3
- "version": "0.4.1",
3
+ "version": "0.4.3",
4
4
  "description": "使用微信 ClawBot 随时随地通过 `acpx` 控制 Claude Code、Codex 等 Agents。",
5
5
  "keywords": [
6
6
  "acpx",