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 +230 -53
- package/dist/orchestration/orchestration-types.d.ts +1 -1
- package/package.json +1 -1
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
|
|
2700
|
-
|
|
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 ${
|
|
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 === "
|
|
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 === "
|
|
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 === "
|
|
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
|
-
|
|
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
|
|
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
|
|
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
|
|
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
|
-
|
|
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 [
|
|
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
|
|
41534
|
+
isInteractive,
|
|
41379
41535
|
promptText: deps.promptText
|
|
41380
41536
|
});
|
|
41381
|
-
const
|
|
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
|
-
|
|
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
|
-
|
|
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 ??
|
|
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 ??
|
|
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 ??
|
|
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 ??
|
|
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
|
|
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
|
-
|
|
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 = "
|
|
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;
|