lightclawbot 1.2.6-beta.0 → 1.2.7
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/src/gateway.js +50 -6
- package/dist/src/group/constants/index.js +20 -0
- package/dist/src/group/inbound/index.js +254 -0
- package/dist/src/group/index.js +15 -0
- package/dist/src/group/orchestrator/execution/agent-runner.js +299 -0
- package/dist/src/group/orchestrator/execution/index.js +7 -0
- package/dist/src/group/orchestrator/execution/prompt-builder.js +288 -0
- package/dist/src/group/orchestrator/execution/soul-resolver.js +38 -0
- package/dist/src/group/orchestrator/execution/types.js +7 -0
- package/dist/src/group/orchestrator/index.js +14 -0
- package/dist/src/group/orchestrator/lifecycle/conversation-state.js +162 -0
- package/dist/src/group/orchestrator/lifecycle/index.js +7 -0
- package/dist/src/group/orchestrator/lifecycle/ledger-writer.js +96 -0
- package/dist/src/group/orchestrator/lifecycle/run-registry.js +174 -0
- package/dist/src/group/orchestrator/orchestrator.js +265 -0
- package/dist/src/group/orchestrator/planning/index.js +13 -0
- package/dist/src/group/orchestrator/planning/plan-validator.js +233 -0
- package/dist/src/group/orchestrator/planning/planning-parser.js +207 -0
- package/dist/src/group/orchestrator/planning/subtask-executor.js +345 -0
- package/dist/src/group/orchestrator/planning/summarizer-runner.js +224 -0
- package/dist/src/group/orchestrator/routes/index.js +9 -0
- package/dist/src/group/orchestrator/routes/leader-dispatch.js +229 -0
- package/dist/src/group/orchestrator/routes/leader-orchestration-route.js +179 -0
- package/dist/src/group/orchestrator/routes/leader-planning.js +92 -0
- package/dist/src/group/orchestrator/routes/leader-self-answer.js +223 -0
- package/dist/src/group/orchestrator/routes/mention-concurrent-route.js +226 -0
- package/dist/src/group/orchestrator/routes/route-helpers.js +186 -0
- package/dist/src/group/orchestrator/routes/types.js +8 -0
- package/dist/src/group/services/group-cleanup-service.js +183 -0
- package/dist/src/group/services/group-creation-service.js +122 -0
- package/dist/src/group/services/group-deletion-service.js +111 -0
- package/dist/src/group/services/group-history-service.js +73 -0
- package/dist/src/group/services/group-member-service.js +169 -0
- package/dist/src/group/services/group-query-service.js +133 -0
- package/dist/src/group/services/group-update-service.js +144 -0
- package/dist/src/group/services/index.js +20 -0
- package/dist/src/group/storage/concurrency-manager.js +119 -0
- package/dist/src/group/storage/group-storage-core.js +227 -0
- package/dist/src/group/storage/index.js +12 -0
- package/dist/src/group/storage/message-reader.js +213 -0
- package/dist/src/group/storage/message-writer.js +229 -0
- package/dist/src/group/storage/slice-manager.js +165 -0
- package/dist/src/group/types/common.js +5 -0
- package/dist/src/group/types/index.js +5 -0
- package/dist/src/group/types/message.js +5 -0
- package/dist/src/group/types/orchestrator.js +5 -0
- package/dist/src/group/types/storage.js +5 -0
- package/dist/src/group/utils/id-generator.js +15 -0
- package/dist/src/group/utils/index.js +12 -0
- package/dist/src/group/utils/mime.js +36 -0
- package/dist/src/group/utils/normalize.js +32 -0
- package/dist/src/group/utils/run-helpers.js +36 -0
- package/dist/src/outbound.js +12 -19
- package/dist/src/shared.js +4 -3
- package/dist/src/socket/events/agents-request.js +147 -0
- package/dist/src/socket/events/chat-request.js +67 -0
- package/dist/src/socket/events/file-download.js +121 -0
- package/dist/src/socket/events/group-abort.js +59 -0
- package/dist/src/socket/events/group-history.js +59 -0
- package/dist/src/socket/events/group-member.js +83 -0
- package/dist/src/socket/events/group-request.js +91 -0
- package/dist/src/socket/events/history-request.js +95 -0
- package/dist/src/socket/events/index.js +39 -0
- package/dist/src/socket/events/message-private.js +82 -0
- package/dist/src/socket/handlers.js +53 -517
- package/dist/src/socket/native-socket.js +21 -20
- package/dist/src/socket/registry.js +6 -3
- package/dist/src/socket/reliable-emitter.js +16 -13
- package/dist/src/socket/service/chat-common.js +36 -0
- package/dist/src/socket/service/chat-create.js +75 -0
- package/dist/src/socket/service/chat-delete.js +94 -0
- package/dist/src/socket/service/chat-list.js +82 -0
- package/dist/src/socket/service/chat-update.js +83 -0
- package/dist/src/socket/service/group-abort.js +104 -0
- package/dist/src/socket/service/group-history.js +140 -0
- package/dist/src/socket/service/group-member.js +209 -0
- package/dist/src/socket/service/group.js +233 -0
- package/dist/src/socket/service/history.js +102 -0
- package/dist/src/socket/service/index.js +14 -0
- package/dist/src/socket/types/index.js +7 -0
- package/dist/src/socket/types/request.js +8 -0
- package/dist/src/socket/types/service.js +8 -0
- package/dist/src/socket/utils/agent-soul.js +95 -0
- package/dist/src/socket/utils/index.js +8 -0
- package/dist/src/socket/utils/message.js +83 -0
- package/dist/src/socket/utils/validate.js +42 -0
- package/dist/src/streaming/index.js +1 -0
- package/dist/src/streaming/stream-reply-sink.js +270 -14
- package/dist/src/streaming/types.js +20 -1
- package/dist/src/{download-tool.js → tools/download-tool.js} +41 -35
- package/dist/src/tools/group-history-tool.js +172 -0
- package/dist/src/{upload-tool.js → tools/upload-tool.js} +2 -2
- package/dist/src/tools.js +4 -3
- package/dist/src/utils/index.js +1 -0
- package/dist/src/utils/logger.js +38 -0
- package/openclaw.plugin.json +2 -1
- package/package.json +1 -1
- package/dist/src/socket/chat.js +0 -257
|
@@ -0,0 +1,224 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* SummarizerRunner —— 场景 C 拆活路径的"汇总阶段"
|
|
3
|
+
*
|
|
4
|
+
* 职责:
|
|
5
|
+
* 1. 在所有 child 完成(含 done / failed / aborted)后,
|
|
6
|
+
* 用 Leader Agent 自身再跑一次 run(role=summarizer),不挂派活工具;
|
|
7
|
+
* 2. 通过 PromptBuilder.buildSummarizer 产出汇总指令;
|
|
8
|
+
* 3. 流式输出到独立的 SocketStreamSink(runId 与 leaderRunId 不同,
|
|
9
|
+
* parentRunId=leaderRunId,前端按此聚合为汇总气泡);
|
|
10
|
+
* 4. 写 assistant 账本(parentRunId=leaderRunId),便于后续历史回放。
|
|
11
|
+
*
|
|
12
|
+
* 失败 / 中止:
|
|
13
|
+
* - aborted → 部分内容仍持久化,由上层路由统一写 user_aborted 事件;
|
|
14
|
+
* - error/timeout → 写 system run_failed;
|
|
15
|
+
* - 路由层据此决定 finalStatus = ABORTED / FAILED。
|
|
16
|
+
*
|
|
17
|
+
* 设计要点:
|
|
18
|
+
* - 不转移状态机:SUMMARIZING / DONE 由调用方负责;
|
|
19
|
+
* - 子任务全部失败时仍然跑汇总,由 prompt 决定如何"诚实告知失败"。
|
|
20
|
+
*/
|
|
21
|
+
import { createStreamReplyConfig } from '../../../streaming/stream-reply-sink.js';
|
|
22
|
+
import { generatePreRunId } from '../../utils/id-generator.js';
|
|
23
|
+
import { errorToString, normalizeRunStatus } from '../../utils/run-helpers.js';
|
|
24
|
+
import { getModuleLogger } from '../../../utils/logger.js';
|
|
25
|
+
import { generateMsgId } from '../../../dedup.js';
|
|
26
|
+
/**
|
|
27
|
+
* 汇总阶段禁用的工具列表
|
|
28
|
+
*
|
|
29
|
+
* 防止 LLM 在汇总时再次尝试派活或调用 session 相关工具
|
|
30
|
+
*/
|
|
31
|
+
const SUMMARIZER_DENY_TOOLS = [
|
|
32
|
+
'sessions_spawn', 'sessions_send', 'sessions_list',
|
|
33
|
+
'sessions_history', 'sessions_yield', 'session_status',
|
|
34
|
+
'sessions.spawn', 'subagents', 'agents_list',
|
|
35
|
+
];
|
|
36
|
+
/**
|
|
37
|
+
* SummarizerRunner —— 汇总阶段执行器(无状态)
|
|
38
|
+
*
|
|
39
|
+
* 负责拉起 Leader Agent 的汇总 run,将子任务结果综合为最终回答。
|
|
40
|
+
*/
|
|
41
|
+
export class SummarizerRunner {
|
|
42
|
+
deps;
|
|
43
|
+
log = getModuleLogger('group.summarizer');
|
|
44
|
+
constructor(deps) {
|
|
45
|
+
this.deps = deps;
|
|
46
|
+
}
|
|
47
|
+
/** 执行一次汇总 run */
|
|
48
|
+
async run(input) {
|
|
49
|
+
const { ctx, leaderAgentId, leaderRunId, originalMsgId, subtaskResults } = input;
|
|
50
|
+
const { runRegistry } = this.deps;
|
|
51
|
+
const tag = `[Summarizer conv=${ctx.conversationId} agent=${leaderAgentId}]`;
|
|
52
|
+
// 注册 run(获取 abort 信号)
|
|
53
|
+
const summarizerRunId = generatePreRunId(ctx.conversationId, 'summarizer');
|
|
54
|
+
const abortSignal = runRegistry.registerRun(ctx.conversationId, summarizerRunId, leaderAgentId);
|
|
55
|
+
try {
|
|
56
|
+
// 准备流式输出 sink
|
|
57
|
+
const sinkContext = this.createSinkContext(input, summarizerRunId);
|
|
58
|
+
sinkContext.emitStart();
|
|
59
|
+
// 执行 runner
|
|
60
|
+
const runOutput = await this.executeRun(input, summarizerRunId, abortSignal, sinkContext);
|
|
61
|
+
// 处理结果并落账本
|
|
62
|
+
return await this.handleRunOutput(runOutput, input, summarizerRunId, tag);
|
|
63
|
+
}
|
|
64
|
+
catch (err) {
|
|
65
|
+
// runner 抛异常:记录错误并返回 failed
|
|
66
|
+
return await this.handleRunException(err, input, summarizerRunId, tag);
|
|
67
|
+
}
|
|
68
|
+
finally {
|
|
69
|
+
runRegistry.unregisterRun(ctx.conversationId, summarizerRunId);
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
/**
|
|
73
|
+
* 创建流式输出的 SinkContext
|
|
74
|
+
*
|
|
75
|
+
* 汇总气泡独立于 leader 气泡,通过 parentRunId 关联
|
|
76
|
+
*/
|
|
77
|
+
createSinkContext(input, summarizerRunId) {
|
|
78
|
+
const { ctx, leaderAgentId, leaderRunId, originalMsgId } = input;
|
|
79
|
+
const { emitter } = this.deps;
|
|
80
|
+
const replyMsgId = generateMsgId();
|
|
81
|
+
const signalCtx = {
|
|
82
|
+
emitter,
|
|
83
|
+
targetId: ctx.userId,
|
|
84
|
+
replyMsgId,
|
|
85
|
+
originalMsgId,
|
|
86
|
+
agentId: leaderAgentId,
|
|
87
|
+
};
|
|
88
|
+
return createStreamReplyConfig({
|
|
89
|
+
emitter,
|
|
90
|
+
targetId: ctx.userId,
|
|
91
|
+
replyMsgId,
|
|
92
|
+
originalMsgId,
|
|
93
|
+
effectiveApiKey: ctx.effectiveApiKey ?? '',
|
|
94
|
+
agentId: leaderAgentId,
|
|
95
|
+
groupId: ctx.groupId,
|
|
96
|
+
runId: summarizerRunId,
|
|
97
|
+
parentRunId: leaderRunId,
|
|
98
|
+
conversationId: ctx.conversationId,
|
|
99
|
+
}, { suppressTyping: true, runId: summarizerRunId }, signalCtx);
|
|
100
|
+
}
|
|
101
|
+
/**
|
|
102
|
+
* 执行 summarizer run
|
|
103
|
+
*
|
|
104
|
+
* 构造汇总 prompt 并调用 runner,禁用派活相关工具
|
|
105
|
+
*/
|
|
106
|
+
async executeRun(input, summarizerRunId, abortSignal, sinkContext) {
|
|
107
|
+
const { ctx, leaderAgentId, leaderRunId, subtaskResults } = input;
|
|
108
|
+
const { runner, promptBuilder } = this.deps;
|
|
109
|
+
const systemPrompt = promptBuilder.buildSummarizer({
|
|
110
|
+
leaderAgentId,
|
|
111
|
+
userMessage: ctx.userMessage,
|
|
112
|
+
subtaskResults,
|
|
113
|
+
});
|
|
114
|
+
// 明确告知 LLM 当前是汇总阶段,避免受历史 Planning JSON 影响
|
|
115
|
+
const summarizerMessage = [
|
|
116
|
+
'[汇总阶段] 子任务已全部完成,请用自然语言综合回答用户。不要输出JSON。',
|
|
117
|
+
`用户原始问题:${ctx.userMessage}`,
|
|
118
|
+
].join('\n');
|
|
119
|
+
return runner.run({
|
|
120
|
+
groupId: ctx.groupId,
|
|
121
|
+
agentId: leaderAgentId,
|
|
122
|
+
userId: ctx.userId,
|
|
123
|
+
role: 'summarizer',
|
|
124
|
+
parentRunId: leaderRunId,
|
|
125
|
+
message: summarizerMessage,
|
|
126
|
+
systemPrompt,
|
|
127
|
+
abortSignal,
|
|
128
|
+
sinkContext,
|
|
129
|
+
preassignedRunId: summarizerRunId,
|
|
130
|
+
denyTools: [...SUMMARIZER_DENY_TOOLS],
|
|
131
|
+
});
|
|
132
|
+
}
|
|
133
|
+
/**
|
|
134
|
+
* 处理 runner 正常返回的结果,根据状态落账本
|
|
135
|
+
*/
|
|
136
|
+
async handleRunOutput(runOutput, input, summarizerRunId, tag) {
|
|
137
|
+
const { ctx, leaderAgentId, leaderRunId } = input;
|
|
138
|
+
const realRunId = runOutput.runId || summarizerRunId;
|
|
139
|
+
const normalized = normalizeRunStatus(runOutput.status);
|
|
140
|
+
// normalizeRunStatus 实际只返回 done/aborted/failed,但类型签名为 RunStatus,
|
|
141
|
+
// 此处做 fallback 保证类型安全
|
|
142
|
+
const status = normalized === 'done' || normalized === 'aborted' || normalized === 'failed'
|
|
143
|
+
? normalized
|
|
144
|
+
: 'failed';
|
|
145
|
+
const out = {
|
|
146
|
+
runId: realRunId,
|
|
147
|
+
agentId: leaderAgentId,
|
|
148
|
+
status,
|
|
149
|
+
finalText: runOutput.finalText,
|
|
150
|
+
};
|
|
151
|
+
const assistantParams = {
|
|
152
|
+
groupId: ctx.groupId,
|
|
153
|
+
agentId: leaderAgentId,
|
|
154
|
+
content: runOutput.finalText,
|
|
155
|
+
runId: realRunId,
|
|
156
|
+
parentRunId: leaderRunId,
|
|
157
|
+
};
|
|
158
|
+
switch (status) {
|
|
159
|
+
case 'done':
|
|
160
|
+
await this.persistAssistant(assistantParams, tag);
|
|
161
|
+
this.log.info(`${tag} done bytes=${runOutput.finalText.length}`);
|
|
162
|
+
break;
|
|
163
|
+
case 'aborted':
|
|
164
|
+
out.errorMessage = 'aborted';
|
|
165
|
+
// 被中止但有部分内容时仍持久化,刷新页面后用户可见中断前内容
|
|
166
|
+
if (runOutput.finalText) {
|
|
167
|
+
await this.persistAssistant(assistantParams, tag, 'aborted partial');
|
|
168
|
+
this.log.info(`${tag} aborted,已持久化部分内容 bytes=${runOutput.finalText.length}`);
|
|
169
|
+
}
|
|
170
|
+
break;
|
|
171
|
+
case 'failed':
|
|
172
|
+
out.errorMessage = errorToString(runOutput.error);
|
|
173
|
+
await this.persistSystemError(ctx.groupId, leaderAgentId, realRunId, out.errorMessage, tag);
|
|
174
|
+
this.log.warn(`${tag} failed reason=${out.errorMessage}`);
|
|
175
|
+
break;
|
|
176
|
+
}
|
|
177
|
+
return out;
|
|
178
|
+
}
|
|
179
|
+
/**
|
|
180
|
+
* 处理 runner 抛出异常的情况
|
|
181
|
+
*/
|
|
182
|
+
async handleRunException(err, input, summarizerRunId, tag) {
|
|
183
|
+
const { ctx, leaderAgentId } = input;
|
|
184
|
+
const e = err instanceof Error ? err : new Error(String(err));
|
|
185
|
+
this.log.error(`${tag} runner 异常: ${e.message}`);
|
|
186
|
+
await this.persistSystemError(ctx.groupId, leaderAgentId, summarizerRunId, e.message, tag);
|
|
187
|
+
return {
|
|
188
|
+
runId: summarizerRunId,
|
|
189
|
+
agentId: leaderAgentId,
|
|
190
|
+
status: 'failed',
|
|
191
|
+
finalText: '',
|
|
192
|
+
errorMessage: errorToString(e),
|
|
193
|
+
};
|
|
194
|
+
}
|
|
195
|
+
/**
|
|
196
|
+
* 持久化 assistant 账本条目(带容错)
|
|
197
|
+
*/
|
|
198
|
+
async persistAssistant(params, tag, label) {
|
|
199
|
+
try {
|
|
200
|
+
await this.deps.ledger.appendAssistant(params);
|
|
201
|
+
}
|
|
202
|
+
catch (err) {
|
|
203
|
+
const suffix = label ? `(${label})` : '';
|
|
204
|
+
this.log.warn(`${tag} appendAssistant${suffix} failed: ${err}`);
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
/**
|
|
208
|
+
* 持久化 run_failed 系统事件(带容错)
|
|
209
|
+
*/
|
|
210
|
+
async persistSystemError(groupId, agentId, runId, content, tag) {
|
|
211
|
+
try {
|
|
212
|
+
await this.deps.ledger.appendSystem({
|
|
213
|
+
groupId,
|
|
214
|
+
event: 'run_failed',
|
|
215
|
+
agentId,
|
|
216
|
+
runId,
|
|
217
|
+
content,
|
|
218
|
+
});
|
|
219
|
+
}
|
|
220
|
+
catch (err) {
|
|
221
|
+
this.log.warn(`${tag} appendSystem failed: ${err}`);
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @file routes 子模块出口
|
|
3
|
+
* @description 统一导出路径层的路由实现和共享类型
|
|
4
|
+
*/
|
|
5
|
+
export { LeaderOrchestrationRoute } from './leader-orchestration-route.js';
|
|
6
|
+
export { MentionConcurrentRoute } from './mention-concurrent-route.js';
|
|
7
|
+
// 工具函数(从统一工具层转发)
|
|
8
|
+
export { generatePreRunId } from '../../utils/id-generator.js';
|
|
9
|
+
export { normalizeRunStatus, errorToString } from '../../utils/run-helpers.js';
|
|
@@ -0,0 +1,229 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview Leader 派活路径执行器
|
|
3
|
+
*
|
|
4
|
+
* @description
|
|
5
|
+
* 当 Planning 阶段决策为 dispatch(需要将任务分配给群成员执行)时走此路径。
|
|
6
|
+
*
|
|
7
|
+
* 执行流程:
|
|
8
|
+
* 1. 校验 Plan 合法性(成员存在性、任务数上限等)
|
|
9
|
+
* 2. 推送过渡话给前端(如「正在为您处理...」)
|
|
10
|
+
* 3. 子任务 DAG 并行/串行执行
|
|
11
|
+
* 4. 汇总器收集所有子任务结果,生成最终回复
|
|
12
|
+
* 5. 根据各阶段结果确定终态
|
|
13
|
+
*
|
|
14
|
+
* 与自答路径的区别:
|
|
15
|
+
* - 派活路径涉及多个 Agent 协作,产生多个 run
|
|
16
|
+
* - 派活路径有明确的 Plan 结构(tasks DAG)
|
|
17
|
+
* - 派活路径包含汇总阶段(Summarizer)
|
|
18
|
+
*
|
|
19
|
+
* @module leader-dispatch
|
|
20
|
+
*/
|
|
21
|
+
// ─── 工具函数 ────────────────────────────────────────────────────────────────
|
|
22
|
+
import { generatePreRunId } from '../../utils/id-generator.js';
|
|
23
|
+
import { getModuleLogger } from '../../../utils/logger.js';
|
|
24
|
+
import { generateMsgId } from '../../../dedup.js';
|
|
25
|
+
// ─── Planning 模块 ───────────────────────────────────────────────────────────
|
|
26
|
+
import { extractPlanFromDecision, validatePlan } from '../planning/index.js';
|
|
27
|
+
// ─── 路由层公共工具 ──────────────────────────────────────────────────────────
|
|
28
|
+
import { createSinkForAgent, safeAppendAssistant } from './route-helpers.js';
|
|
29
|
+
/** 模块级日志记录器 */
|
|
30
|
+
const log = getModuleLogger('group.leader-route');
|
|
31
|
+
/**
|
|
32
|
+
* 执行派活路径:校验 Plan → 推送过渡话 → 子任务 DAG → 汇总
|
|
33
|
+
*
|
|
34
|
+
* @remarks
|
|
35
|
+
* 整个流程中状态机转移顺序:
|
|
36
|
+
* RUNNING → PLANNING → RUNNING_CHILDREN → SUMMARIZING → DONE/FAILED/ABORTED
|
|
37
|
+
*
|
|
38
|
+
* 任何阶段失败都会通过 forceTerminal 直接终结状态机,
|
|
39
|
+
* 确保不会卡在中间态。
|
|
40
|
+
*
|
|
41
|
+
* @param deps - 路由依赖
|
|
42
|
+
* @param input - 路由运行输入(ctx + state)
|
|
43
|
+
* @param agentId - Leader Agent ID
|
|
44
|
+
* @param decision - Planning 阶段产出的派活决策
|
|
45
|
+
* @param memberCards - 群成员能力卡(当前用于日志,未来可用于动态路由)
|
|
46
|
+
* @param tag - 日志标签
|
|
47
|
+
* @param routeName - 路由名称(透传到返回结果中)
|
|
48
|
+
* @param options - 执行器与降级回调配置
|
|
49
|
+
*/
|
|
50
|
+
export async function executeDispatch(deps, input, agentId, decision, memberCards, tag, routeName, options) {
|
|
51
|
+
const { ctx, state } = input;
|
|
52
|
+
const { subtaskExecutor, summarizerRunner, fallbackSelfAnswer } = options;
|
|
53
|
+
// ── 1. 校验 Plan 合法性 ──
|
|
54
|
+
// 从决策中提取原始 Plan 结构,并校验成员存在性、任务数上限等约束
|
|
55
|
+
const rawPlan = extractPlanFromDecision(decision);
|
|
56
|
+
const validationResult = validatePlan(rawPlan, {
|
|
57
|
+
leaderAgentId: agentId,
|
|
58
|
+
members: ctx.meta.members.map((m) => m.agentId),
|
|
59
|
+
maxTasks: 5, // 单次派活最多 5 个子任务,防止资源耗尽
|
|
60
|
+
});
|
|
61
|
+
if (!validationResult.ok) {
|
|
62
|
+
// Plan 不合法时降级为自答,不中断用户体验
|
|
63
|
+
log.warn(`${tag} Plan 校验失败: ${validationResult.errors.join('; ')},降级为自答`);
|
|
64
|
+
return fallbackSelfAnswer();
|
|
65
|
+
}
|
|
66
|
+
const plan = validationResult.plan;
|
|
67
|
+
const originalMsgId = ctx.msgId ?? generateMsgId();
|
|
68
|
+
// ── 2. 推送过渡话给前端 ──
|
|
69
|
+
// 过渡话是 Leader 在子任务执行前给用户的即时反馈(如「正在为您处理...」)
|
|
70
|
+
const transitionRunId = generatePreRunId(ctx.conversationId, 'transition');
|
|
71
|
+
if (decision.transition?.trim()) {
|
|
72
|
+
await emitTransition(deps, ctx, agentId, decision.transition, transitionRunId, originalMsgId, tag);
|
|
73
|
+
}
|
|
74
|
+
// Leader 自身的 run 摘要(过渡话视为 Leader 的一次 run)
|
|
75
|
+
const leaderSummary = {
|
|
76
|
+
runId: transitionRunId,
|
|
77
|
+
agentId,
|
|
78
|
+
status: 'done',
|
|
79
|
+
finalText: decision.transition || '',
|
|
80
|
+
};
|
|
81
|
+
// ── 3. 子任务 DAG 执行 ──
|
|
82
|
+
log.info(`${tag} 进入子任务流程,共 ${plan.tasks.length} 个任务`);
|
|
83
|
+
state.transitionTo('PLANNING');
|
|
84
|
+
state.transitionTo('RUNNING_CHILDREN');
|
|
85
|
+
let subtaskResults;
|
|
86
|
+
try {
|
|
87
|
+
subtaskResults = await subtaskExecutor.run({
|
|
88
|
+
ctx,
|
|
89
|
+
plan,
|
|
90
|
+
leaderAgentId: agentId,
|
|
91
|
+
leaderRunId: transitionRunId,
|
|
92
|
+
originalMsgId,
|
|
93
|
+
});
|
|
94
|
+
}
|
|
95
|
+
catch (err) {
|
|
96
|
+
// 子任务执行器整体崩溃(非单个子任务失败),直接终结
|
|
97
|
+
const e = err instanceof Error ? err : new Error(String(err));
|
|
98
|
+
log.error(`${tag} 子任务执行器崩溃: ${e.message}`);
|
|
99
|
+
state.forceTerminal('FAILED');
|
|
100
|
+
return { route: routeName, finalStatus: 'FAILED', runs: [leaderSummary], error: e };
|
|
101
|
+
}
|
|
102
|
+
// 将子任务结果映射为统一的 RouteRunSummary 格式
|
|
103
|
+
const childSummaries = mapSubtaskResults(subtaskResults);
|
|
104
|
+
// ── 4. 汇总阶段 ──
|
|
105
|
+
state.transitionTo('SUMMARIZING');
|
|
106
|
+
let summarizerSummary;
|
|
107
|
+
try {
|
|
108
|
+
const summarizerOut = await summarizerRunner.run({
|
|
109
|
+
ctx,
|
|
110
|
+
leaderAgentId: agentId,
|
|
111
|
+
leaderRunId: transitionRunId,
|
|
112
|
+
originalMsgId,
|
|
113
|
+
subtaskResults,
|
|
114
|
+
});
|
|
115
|
+
summarizerSummary = {
|
|
116
|
+
runId: summarizerOut.runId,
|
|
117
|
+
agentId: summarizerOut.agentId,
|
|
118
|
+
status: summarizerOut.status,
|
|
119
|
+
finalText: summarizerOut.finalText,
|
|
120
|
+
errorMessage: summarizerOut.errorMessage,
|
|
121
|
+
};
|
|
122
|
+
}
|
|
123
|
+
catch (err) {
|
|
124
|
+
// 汇总器崩溃兜底:子任务已完成但无法汇总,标记为失败
|
|
125
|
+
const e = err instanceof Error ? err : new Error(String(err));
|
|
126
|
+
log.error(`${tag} 汇总器执行异常: ${e.message}`);
|
|
127
|
+
state.forceTerminal('FAILED');
|
|
128
|
+
return {
|
|
129
|
+
route: routeName,
|
|
130
|
+
finalStatus: 'FAILED',
|
|
131
|
+
runs: [leaderSummary, ...childSummaries],
|
|
132
|
+
error: e,
|
|
133
|
+
};
|
|
134
|
+
}
|
|
135
|
+
// ── 5. 终态决策 ──
|
|
136
|
+
const allRuns = [leaderSummary, ...childSummaries, summarizerSummary];
|
|
137
|
+
const finalStatus = determineFinalStatus(allRuns, summarizerSummary.status, state);
|
|
138
|
+
// ── 6. ABORTED 时写入 user_aborted 系统事件 ──
|
|
139
|
+
// 在所有 run 结束(部分内容已 appendAssistant)之后再写,
|
|
140
|
+
// 保证前端按 ts 排序后终止提示在 agent 部分回复内容之后。
|
|
141
|
+
if (finalStatus === 'ABORTED') {
|
|
142
|
+
try {
|
|
143
|
+
await deps.ledger.appendSystem({
|
|
144
|
+
groupId: ctx.groupId,
|
|
145
|
+
event: 'user_aborted',
|
|
146
|
+
userId: ctx.userId,
|
|
147
|
+
});
|
|
148
|
+
}
|
|
149
|
+
catch (err) {
|
|
150
|
+
log.warn(`${tag} appendSystem(user_aborted) 失败: ${err}`);
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
log.info(`${tag} 派活流程完成 状态=${finalStatus} ` +
|
|
154
|
+
`子任务数=${childSummaries.length} ` +
|
|
155
|
+
`成功数=${childSummaries.filter((r) => r.status === 'done').length} ` +
|
|
156
|
+
`汇总器=${summarizerSummary.status}`);
|
|
157
|
+
return {
|
|
158
|
+
route: routeName,
|
|
159
|
+
finalStatus,
|
|
160
|
+
runs: allRuns,
|
|
161
|
+
error: finalStatus === 'FAILED' ? new Error(summarizerSummary.errorMessage ?? 'summarizer failed') : undefined,
|
|
162
|
+
};
|
|
163
|
+
}
|
|
164
|
+
// ─── 私有辅助函数 ────────────────────────────────────────────────────────────
|
|
165
|
+
/**
|
|
166
|
+
* 将子任务结果数组映射为统一的 RouteRunSummary 格式
|
|
167
|
+
*
|
|
168
|
+
* @remarks
|
|
169
|
+
* SubtaskResult 使用 `output` 字段存储文本,而 RouteRunSummary 使用 `finalText`,
|
|
170
|
+
* 此函数负责字段名适配。
|
|
171
|
+
*/
|
|
172
|
+
function mapSubtaskResults(results) {
|
|
173
|
+
return results.map((r) => ({
|
|
174
|
+
runId: r.runId,
|
|
175
|
+
agentId: r.agentId,
|
|
176
|
+
status: r.status,
|
|
177
|
+
finalText: r.output,
|
|
178
|
+
errorMessage: r.errorMessage,
|
|
179
|
+
}));
|
|
180
|
+
}
|
|
181
|
+
/**
|
|
182
|
+
* 推送过渡话并写入账本
|
|
183
|
+
*
|
|
184
|
+
* @remarks
|
|
185
|
+
* 过渡话是 Leader 在子任务执行前给用户的即时反馈,
|
|
186
|
+
* 通过 SocketStreamSink 的 onFinal 直接推送完整文本(非流式),
|
|
187
|
+
* 同时持久化到群账本供历史消息回放。
|
|
188
|
+
*/
|
|
189
|
+
async function emitTransition(deps, ctx, agentId, text, runId, originalMsgId, tag) {
|
|
190
|
+
const sinkContext = createSinkForAgent(deps, ctx, agentId, runId, originalMsgId);
|
|
191
|
+
sinkContext.emitStart();
|
|
192
|
+
try {
|
|
193
|
+
// 过渡话不走流式,直接通过 dispatcher 推送完整文本并触发终态帧
|
|
194
|
+
sinkContext.dispatcher.sendFinalReply({ text });
|
|
195
|
+
sinkContext.dispatcher.markComplete();
|
|
196
|
+
}
|
|
197
|
+
catch (err) {
|
|
198
|
+
log.warn(`${tag} 过渡话推送异常: ${err}`);
|
|
199
|
+
}
|
|
200
|
+
await safeAppendAssistant(deps, ctx.groupId, agentId, text, runId, tag);
|
|
201
|
+
}
|
|
202
|
+
/**
|
|
203
|
+
* 根据所有 run 结果确定最终终态
|
|
204
|
+
*
|
|
205
|
+
* @remarks
|
|
206
|
+
* 终态决策优先级:
|
|
207
|
+
* 1. 任何 run 被 abort → 整体 ABORTED
|
|
208
|
+
* 2. 汇总器成功 → 整体 DONE(即使部分子任务失败,汇总器仍可能产出有效回复)
|
|
209
|
+
* 3. 其他情况 → FAILED
|
|
210
|
+
*
|
|
211
|
+
* @param allRuns - 所有 run 的摘要(Leader + 子任务 + 汇总器)
|
|
212
|
+
* @param summarizerStatus - 汇总器的终态
|
|
213
|
+
* @param state - 会话状态机
|
|
214
|
+
*/
|
|
215
|
+
function determineFinalStatus(allRuns, summarizerStatus, state) {
|
|
216
|
+
// 优先检查是否有 abort(用户主动取消)
|
|
217
|
+
if (allRuns.some((r) => r.status === 'aborted')) {
|
|
218
|
+
state.forceTerminal('ABORTED');
|
|
219
|
+
return 'ABORTED';
|
|
220
|
+
}
|
|
221
|
+
// 汇总器成功即视为整体成功
|
|
222
|
+
if (summarizerStatus === 'done') {
|
|
223
|
+
state.transitionTo('DONE');
|
|
224
|
+
return 'DONE';
|
|
225
|
+
}
|
|
226
|
+
// 兜底:汇总器未成功则整体失败
|
|
227
|
+
state.forceTerminal('FAILED');
|
|
228
|
+
return 'FAILED';
|
|
229
|
+
}
|
|
@@ -0,0 +1,179 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview 路径 C —— Leader 决策编排路由(主入口)
|
|
3
|
+
*
|
|
4
|
+
* @description
|
|
5
|
+
* 实现两阶段决策流程:
|
|
6
|
+
* 1. Planning 阶段:Leader Agent 以 JSON 模式输出决策(self_answer / dispatch)
|
|
7
|
+
* 2. Execution 阶段:根据决策走自答或派活(子任务 DAG + 汇总)
|
|
8
|
+
*
|
|
9
|
+
* 核心设计:
|
|
10
|
+
* - Planning 支持重试(JSON 解析失败时自动重试)
|
|
11
|
+
* - 所有阶段失败均可优雅降级为自答
|
|
12
|
+
* - 子任务执行后由 Summarizer 汇总最终回复
|
|
13
|
+
*
|
|
14
|
+
* @module leader-orchestration-route
|
|
15
|
+
*/
|
|
16
|
+
// ─── 工具函数 ────────────────────────────────────────────────────────────────
|
|
17
|
+
import { generatePreRunId } from '../../utils/id-generator.js';
|
|
18
|
+
import { getModuleLogger } from '../../../utils/logger.js';
|
|
19
|
+
// ─── Planning 模块 ───────────────────────────────────────────────────────────
|
|
20
|
+
import { SubtaskExecutor, SummarizerRunner } from '../planning/index.js';
|
|
21
|
+
// ─── 拆分后的子模块 ──────────────────────────────────────────────────────────
|
|
22
|
+
import { buildTag, resolveMemberCards } from './route-helpers.js';
|
|
23
|
+
import { executePlanning } from './leader-planning.js';
|
|
24
|
+
import { executeSelfAnswer } from './leader-self-answer.js';
|
|
25
|
+
import { executeDispatch } from './leader-dispatch.js';
|
|
26
|
+
/**
|
|
27
|
+
* 路径 C —— Leader 决策层(提示词驱动方案)
|
|
28
|
+
*
|
|
29
|
+
* @remarks
|
|
30
|
+
* 两阶段决策:Planning(JSON 决策)→ Execution(自答 / 派活+汇总)
|
|
31
|
+
*/
|
|
32
|
+
export class LeaderOrchestrationRoute {
|
|
33
|
+
deps;
|
|
34
|
+
/** 路由名称标识,用于日志和结果追踪 */
|
|
35
|
+
name = 'leader_orchestration';
|
|
36
|
+
/** 子任务 DAG 执行器,负责并行/串行调度多个子 Agent */
|
|
37
|
+
subtaskExecutor;
|
|
38
|
+
/** 汇总阶段执行器,负责将子任务结果合并为最终回复 */
|
|
39
|
+
summarizerRunner;
|
|
40
|
+
/** Planning 阶段 JSON 解析失败时的最大重试次数 */
|
|
41
|
+
planningMaxRetries;
|
|
42
|
+
/** 日志记录器,命名空间 'group.leader-route' */
|
|
43
|
+
log = getModuleLogger('group.leader-route');
|
|
44
|
+
/**
|
|
45
|
+
* 构造 LeaderOrchestrationRoute 实例
|
|
46
|
+
*
|
|
47
|
+
* @param deps - 路由公共依赖(runner、emitter、ledger 等)
|
|
48
|
+
* @param options - 可选配置项,支持注入自定义执行器和重试策略
|
|
49
|
+
*/
|
|
50
|
+
constructor(deps, options = {}) {
|
|
51
|
+
this.deps = deps;
|
|
52
|
+
this.subtaskExecutor = options.subtaskExecutor ?? new SubtaskExecutor(deps);
|
|
53
|
+
this.summarizerRunner = options.summarizerRunner ?? new SummarizerRunner(deps);
|
|
54
|
+
this.planningMaxRetries = options.planningMaxRetries ?? 1;
|
|
55
|
+
}
|
|
56
|
+
/**
|
|
57
|
+
* 执行路径 C —— 两阶段决策主流程
|
|
58
|
+
*
|
|
59
|
+
* @remarks
|
|
60
|
+
* 执行流程:
|
|
61
|
+
* 1. 状态机转移至 RUNNING_LEADER
|
|
62
|
+
* 2. 解析群成员能力卡(用于 Planning 提示词)
|
|
63
|
+
* 3. 调用 Planning 阶段获取 JSON 决策
|
|
64
|
+
* 4. 根据决策分支:self_answer → 自答 / dispatch → 派活+汇总
|
|
65
|
+
* 5. Planning 失败时优雅降级为自答
|
|
66
|
+
*
|
|
67
|
+
* @param input - 路由运行输入,包含上下文(ctx)和状态机(state)
|
|
68
|
+
* @returns 路由执行结果,包含终态、所有 run 摘要及可能的错误
|
|
69
|
+
*/
|
|
70
|
+
async run(input) {
|
|
71
|
+
const { ctx, state } = input;
|
|
72
|
+
const tag = buildTag(ctx);
|
|
73
|
+
const agentId = ctx.meta.defaultAgentId;
|
|
74
|
+
// ── 1. 状态转移 ──
|
|
75
|
+
if (state.getStatus() === 'PENDING') {
|
|
76
|
+
state.transitionTo('ROUTING');
|
|
77
|
+
}
|
|
78
|
+
state.transitionTo('RUNNING_LEADER');
|
|
79
|
+
// ── 2. 解析群成员能力卡 ──
|
|
80
|
+
const memberCards = resolveMemberCards(this.deps, ctx, tag);
|
|
81
|
+
// ── 2.5 欢迎消息模式:跳过 Planning,直接走自答路径 ──
|
|
82
|
+
if (ctx.welcomeMode) {
|
|
83
|
+
this.log.info(`${tag} 欢迎消息模式,跳过 Planning 直接自答`);
|
|
84
|
+
return await this.doWelcomeAnswer(input, agentId, memberCards, tag);
|
|
85
|
+
}
|
|
86
|
+
// ── 3. Planning 阶段 ──
|
|
87
|
+
const planningResult = await executePlanning(this.deps, ctx, agentId, memberCards, tag, this.planningMaxRetries);
|
|
88
|
+
// ── 4. 处理 Planning 结果 ──
|
|
89
|
+
if (planningResult.aborted) {
|
|
90
|
+
const planningRunId = generatePreRunId(ctx.conversationId, 'planning');
|
|
91
|
+
state.forceTerminal('ABORTED');
|
|
92
|
+
// Planning 阶段被中止:写入 user_aborted 系统事件
|
|
93
|
+
try {
|
|
94
|
+
await this.deps.ledger.appendSystem({
|
|
95
|
+
groupId: ctx.groupId,
|
|
96
|
+
event: 'user_aborted',
|
|
97
|
+
userId: ctx.userId,
|
|
98
|
+
});
|
|
99
|
+
}
|
|
100
|
+
catch (err) {
|
|
101
|
+
this.log.warn(`${tag} appendSystem(user_aborted) 失败: ${err}`);
|
|
102
|
+
}
|
|
103
|
+
return {
|
|
104
|
+
route: this.name,
|
|
105
|
+
finalStatus: 'ABORTED',
|
|
106
|
+
runs: [{ runId: planningRunId, agentId, status: 'aborted' }],
|
|
107
|
+
};
|
|
108
|
+
}
|
|
109
|
+
if (!planningResult.decision) {
|
|
110
|
+
this.log.warn(`${tag} Planning 失败,降级为自答: ${planningResult.lastError}`);
|
|
111
|
+
// 短暂等待 SDK 内部 session 写锁释放,避免 "Previous run is still shutting down" 冲突
|
|
112
|
+
await new Promise((r) => setTimeout(r, 200));
|
|
113
|
+
return await this.doSelfAnswer(input, agentId, memberCards, tag);
|
|
114
|
+
}
|
|
115
|
+
const decision = planningResult.decision;
|
|
116
|
+
// ── 5. 分支执行 ──
|
|
117
|
+
if (decision.action === 'self_answer') {
|
|
118
|
+
this.log.info(`${tag} 决策=自答`);
|
|
119
|
+
// 短暂等待 SDK 内部 session 写锁释放
|
|
120
|
+
await new Promise((r) => setTimeout(r, 200));
|
|
121
|
+
return await this.doSelfAnswer(input, agentId, memberCards, tag);
|
|
122
|
+
}
|
|
123
|
+
const dispatchDecision = decision;
|
|
124
|
+
return await this.doDispatch(input, agentId, dispatchDecision, memberCards, tag);
|
|
125
|
+
}
|
|
126
|
+
/**
|
|
127
|
+
* 委托自答路径
|
|
128
|
+
*
|
|
129
|
+
* @remarks
|
|
130
|
+
* 将执行委托给 {@link executeSelfAnswer},Leader 直接流式回答用户。
|
|
131
|
+
* 用于 Planning 决策为 self_answer 或 Planning 失败降级的场景。
|
|
132
|
+
*
|
|
133
|
+
* @param input - 路由运行输入
|
|
134
|
+
* @param agentId - Leader Agent ID
|
|
135
|
+
* @param memberCards - 群成员能力卡(用于构建系统提示词)
|
|
136
|
+
* @param tag - 日志标签(含 conversationId、groupId)
|
|
137
|
+
*/
|
|
138
|
+
doSelfAnswer(input, agentId, memberCards, tag) {
|
|
139
|
+
return executeSelfAnswer(this.deps, input, agentId, memberCards, tag, this.name);
|
|
140
|
+
}
|
|
141
|
+
/**
|
|
142
|
+
* 委托派活路径
|
|
143
|
+
*
|
|
144
|
+
* @remarks
|
|
145
|
+
* 将执行委托给 {@link executeDispatch},流程为:
|
|
146
|
+
* 校验 Plan → 推送过渡话 → 子任务 DAG 执行 → Summarizer 汇总。
|
|
147
|
+
* 若 Plan 校验失败,内部会通过 fallbackSelfAnswer 降级为自答。
|
|
148
|
+
*
|
|
149
|
+
* @param input - 路由运行输入
|
|
150
|
+
* @param agentId - Leader Agent ID
|
|
151
|
+
* @param decision - Planning 阶段输出的派活决策(含 tasks 列表)
|
|
152
|
+
* @param memberCards - 群成员能力卡
|
|
153
|
+
* @param tag - 日志标签
|
|
154
|
+
*/
|
|
155
|
+
doDispatch(input, agentId, decision, memberCards, tag) {
|
|
156
|
+
return executeDispatch(this.deps, input, agentId, decision, memberCards, tag, this.name, {
|
|
157
|
+
subtaskExecutor: this.subtaskExecutor,
|
|
158
|
+
summarizerRunner: this.summarizerRunner,
|
|
159
|
+
fallbackSelfAnswer: () => this.doSelfAnswer(input, agentId, memberCards, tag),
|
|
160
|
+
});
|
|
161
|
+
}
|
|
162
|
+
/**
|
|
163
|
+
* 欢迎消息自答路径
|
|
164
|
+
*
|
|
165
|
+
* @remarks
|
|
166
|
+
* 创建群后触发,使用 buildWelcome prompt 让主Agent生成欢迎消息。
|
|
167
|
+
* 与普通自答路径的区别:使用专门的欢迎消息 prompt 而非通用 Leader prompt。
|
|
168
|
+
*
|
|
169
|
+
* @param input - 路由运行输入
|
|
170
|
+
* @param agentId - Leader Agent ID
|
|
171
|
+
* @param memberCards - 群成员能力卡
|
|
172
|
+
* @param tag - 日志标签
|
|
173
|
+
*/
|
|
174
|
+
doWelcomeAnswer(input, agentId, memberCards, tag) {
|
|
175
|
+
return executeSelfAnswer(this.deps, input, agentId, memberCards, tag, this.name, {
|
|
176
|
+
welcomeMode: true,
|
|
177
|
+
});
|
|
178
|
+
}
|
|
179
|
+
}
|