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