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/README.md +38 -5
- package/dist/bridge/bridge-main.js +11 -25
- package/dist/cli.js +987 -111
- package/dist/orchestration/orchestration-types.d.ts +12 -0
- package/package.json +2 -2
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
15471
|
-
|
|
15472
|
-
|
|
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 (
|
|
15477
|
-
messageSegments.push(currentSegment.trim());
|
|
15478
|
-
}
|
|
15479
|
-
if (messageSegments.length > 0) {
|
|
15520
|
+
if (hasAgentMessage && text.trim().length > 0) {
|
|
15480
15521
|
return {
|
|
15481
|
-
text:
|
|
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:
|
|
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
|
-
|
|
19782
|
+
pending = "";
|
|
19783
|
+
feed(segment, options = {}) {
|
|
19784
|
+
const hadPending = this.pending.length > 0;
|
|
19785
|
+
this.pending += segment;
|
|
19713
19786
|
const summaries = [];
|
|
19714
|
-
|
|
19715
|
-
`)
|
|
19716
|
-
|
|
19717
|
-
|
|
19718
|
-
|
|
19719
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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.
|
|
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
|
|
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,
|
|
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.
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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:
|
|
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
|
|
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,
|
|
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,
|
|
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,
|
|
39827
|
-
" - status=timeout: the task is still running.
|
|
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
|
|
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);
|