@yeaft/webchat-agent 1.0.79 → 1.0.80

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/crew/control.js DELETED
@@ -1,444 +0,0 @@
1
- /**
2
- * Crew — 控制操作
3
- * pause, resume, stop, clear, abort, interrupt 等
4
- */
5
- import { join } from 'path';
6
- import { promises as fs } from 'fs';
7
- import { sendCrewMessage, sendCrewOutput, sendStatusUpdate, endRoleStreaming } from './ui-messages.js';
8
- import { saveRoleSessionId, clearRoleSessionId, createRoleQuery } from './role-query.js';
9
- import { saveSessionMeta, cleanupMessageShards } from './persistence.js';
10
- import { executeRoute, dispatchToRole } from './routing.js';
11
- import { saveRoleWorkSummary } from './task-files.js';
12
- import { getMessages } from '../crew-i18n.js';
13
- import { cleanupWorktrees } from './worktree.js';
14
- import { upsertCrewIndex } from './persistence.js';
15
- import { processHumanQueue } from './human-interaction.js';
16
-
17
- /**
18
- * 处理控制命令
19
- */
20
- export async function handleCrewControl(msg) {
21
- // Lazy import to avoid circular dependency
22
- const { crewSessions, resumeCrewSession } = await import('./session.js');
23
-
24
- const { sessionId, action, targetRole } = msg;
25
- let session = crewSessions.get(sessionId);
26
- if (!session) {
27
- // Auto-resume: try to restore from disk before giving up
28
- console.log(`[Crew] Session ${sessionId} not in memory, attempting auto-resume...`);
29
- try {
30
- await resumeCrewSession({ sessionId });
31
- session = crewSessions.get(sessionId);
32
- } catch (e) {
33
- console.warn(`[Crew] Auto-resume failed for ${sessionId}:`, e.message);
34
- }
35
- if (!session) {
36
- console.warn(`[Crew] Session not found: ${sessionId} (even after auto-resume)`);
37
- return;
38
- }
39
- }
40
-
41
- switch (action) {
42
- case 'pause':
43
- await pauseAll(session);
44
- break;
45
- case 'resume':
46
- await resumeSession(session);
47
- break;
48
- case 'stop_role':
49
- if (targetRole) await stopRole(session, targetRole);
50
- break;
51
- case 'interrupt_role':
52
- if (targetRole && msg.content) {
53
- await interruptRole(session, targetRole, msg.content, 'human');
54
- }
55
- break;
56
- case 'abort_role':
57
- if (targetRole) await abortRole(session, targetRole);
58
- break;
59
- case 'clear_role':
60
- if (targetRole) await clearSingleRole(session, targetRole);
61
- break;
62
- case 'stop_all':
63
- await stopAll(session);
64
- break;
65
- case 'clear':
66
- await clearSession(session);
67
- break;
68
- default:
69
- console.warn(`[Crew] Unknown control action: ${action}`);
70
- }
71
- }
72
-
73
- /**
74
- * 清空单个角色的对话
75
- */
76
- async function clearSingleRole(session, roleName) {
77
- const roleState = session.roleStates.get(roleName);
78
-
79
- // P0-1: 清除该角色在 humanMessageQueue 中的待处理消息,防止幽灵 ROUTE 执行
80
- if (session.humanMessageQueue.length > 0) {
81
- session.humanMessageQueue = session.humanMessageQueue.filter(m => m.target !== roleName);
82
- }
83
-
84
- // 保存 task 信息,clear 后恢复时传给 dispatchToRole(必须在 if 块外声明,恢复 dispatch 在块外引用)
85
- const lastTask = roleState?.currentTask ? { ...roleState.currentTask } : null;
86
-
87
- if (roleState) {
88
- // 保存工作摘要到 task file(与 context_exceeded clear 一致)
89
- if (roleState.accumulatedText) {
90
- await saveRoleWorkSummary(session, roleName, roleState.accumulatedText).catch(e =>
91
- console.warn(`[Crew] Failed to save work summary for ${roleName}:`, e.message));
92
- }
93
-
94
- // P1-3: abort 并等待 query iterator 退出,避免 abort+dispatch 竞态
95
- if (roleState.abortController) {
96
- roleState.abortController.abort();
97
- }
98
- // 等待 query 完成(iterator 会因 AbortError 退出)
99
- if (roleState.query) {
100
- try {
101
- // Drain the iterator — it will throw AbortError and exit
102
- // eslint-disable-next-line no-empty
103
- for await (const _ of roleState.query) {}
104
- } catch {
105
- // Expected: AbortError or other cleanup errors
106
- }
107
- }
108
-
109
- roleState.query = null;
110
- roleState.inputStream = null;
111
- roleState.turnActive = false;
112
- roleState.claudeSessionId = null;
113
- roleState.consecutiveErrors = 0;
114
- roleState.accumulatedText = '';
115
- roleState.lastDispatchContent = null;
116
- roleState.lastDispatchFrom = null;
117
- roleState.lastDispatchTaskId = null;
118
- roleState.lastDispatchTaskTitle = null;
119
- // P0-3: 重置 UI 相关状态,防止显示过时信息
120
- roleState.currentTask = null;
121
- roleState.currentTool = null;
122
- roleState.lastTurnText = '';
123
- }
124
-
125
- await clearRoleSessionId(session.sharedDir, roleName);
126
-
127
- sendCrewMessage({
128
- type: 'crew_role_cleared',
129
- sessionId: session.id,
130
- role: roleName,
131
- reason: 'manual'
132
- });
133
-
134
- sendCrewOutput(session, 'system', 'system', {
135
- type: 'assistant',
136
- message: { role: 'assistant', content: [{ type: 'text', text: `${roleName} 对话已清空` }] }
137
- });
138
- sendStatusUpdate(session);
139
- console.log(`[Crew] Role ${roleName} cleared`);
140
-
141
- // 从 messageHistory 提取该角色最近 5 条相关消息,构造记忆恢复 prompt
142
- const roleMessages = session.messageHistory
143
- .filter(m => m.from === roleName || m.to === roleName)
144
- .slice(-5);
145
- if (roleMessages.length > 0) {
146
- const m = getMessages(session.language || 'zh-CN');
147
- const summary = roleMessages
148
- .map(msg => `[${msg.from} → ${msg.to}${msg.taskId ? ` (${msg.taskId})` : ''}] ${msg.content}`)
149
- .join('\n');
150
- const restorePrompt = `${m.memoryRestorePrompt}\n\n${summary}`;
151
- await dispatchToRole(session, roleName, restorePrompt, 'system', lastTask?.taskId, lastTask?.taskTitle);
152
- }
153
- }
154
-
155
- /**
156
- * 暂停所有角色
157
- */
158
- async function pauseAll(session) {
159
- session.status = 'paused';
160
-
161
- for (const [roleName, roleState] of session.roleStates) {
162
- if (roleState.claudeSessionId) {
163
- await saveRoleSessionId(session.sharedDir, roleName, roleState.claudeSessionId)
164
- .catch(e => console.warn(`[Crew] Failed to save sessionId for ${roleName}:`, e.message));
165
- }
166
- if (roleState.abortController) {
167
- roleState.abortController.abort();
168
- }
169
- roleState.wasActive = roleState.turnActive;
170
- roleState.turnActive = false;
171
- roleState.query = null;
172
- roleState.inputStream = null;
173
- }
174
-
175
- console.log(`[Crew] Session ${session.id} paused, all active queries aborted`);
176
-
177
- sendCrewOutput(session, 'system', 'system', {
178
- type: 'assistant',
179
- message: { role: 'assistant', content: [{ type: 'text', text: 'Session 已暂停' }] }
180
- });
181
- sendStatusUpdate(session);
182
-
183
- await saveSessionMeta(session);
184
- }
185
-
186
- /**
187
- * 恢复 session
188
- */
189
- async function resumeSession(session) {
190
- if (session.status !== 'paused') return;
191
-
192
- session.status = 'running';
193
- console.log(`[Crew] Session ${session.id} resumed`);
194
-
195
- sendCrewOutput(session, 'system', 'system', {
196
- type: 'assistant',
197
- message: { role: 'assistant', content: [{ type: 'text', text: 'Session 已恢复' }] }
198
- });
199
- sendStatusUpdate(session);
200
- // Status changed to running — persist
201
- saveSessionMeta(session).catch(e => console.warn('[Crew] Failed to save after resume:', e.message));
202
-
203
- if (session.pendingRoutes.length > 0) {
204
- const pending = session.pendingRoutes.slice();
205
- session.pendingRoutes = [];
206
- console.log(`[Crew] Replaying ${pending.length} pending route(s)`);
207
- // P1-5: 串行执行 pending routes,防止多个 route 指向同一角色时并发 dispatch
208
- for (const { fromRole, route } of pending) {
209
- try {
210
- await executeRoute(session, fromRole, route);
211
- } catch (err) {
212
- console.warn(`[Crew] Pending route replay failed:`, err);
213
- }
214
- }
215
- return;
216
- }
217
-
218
- await processHumanQueue(session);
219
- }
220
-
221
- /**
222
- * 中断角色当前 turn 并发送新消息
223
- */
224
- async function interruptRole(session, roleName, newContent, fromSource = 'human') {
225
- const roleState = session.roleStates.get(roleName);
226
- if (!roleState) {
227
- console.warn(`[Crew] Cannot interrupt ${roleName}: no roleState`);
228
- return;
229
- }
230
-
231
- console.log(`[Crew] Interrupting ${roleName}`);
232
-
233
- endRoleStreaming(session, roleName);
234
-
235
- if (roleState.claudeSessionId) {
236
- await saveRoleSessionId(session.sharedDir, roleName, roleState.claudeSessionId)
237
- .catch(e => console.warn(`[Crew] Failed to save sessionId for ${roleName}:`, e.message));
238
- }
239
-
240
- if (roleState.abortController) {
241
- roleState.abortController.abort();
242
- }
243
-
244
- roleState.query = null;
245
- roleState.inputStream = null;
246
- roleState.turnActive = false;
247
- roleState.accumulatedText = '';
248
-
249
- // Mark pending tool messages as completed before notifying frontend
250
- for (const m of session.uiMessages) {
251
- if (m.role === roleName && m.type === 'tool' && !m.hasResult) {
252
- m.hasResult = true;
253
- }
254
- }
255
-
256
- sendCrewMessage({
257
- type: 'crew_turn_completed',
258
- sessionId: session.id,
259
- role: roleName,
260
- interrupted: true
261
- });
262
-
263
- sendStatusUpdate(session);
264
-
265
- sendCrewOutput(session, 'system', 'system', {
266
- type: 'assistant',
267
- message: { role: 'assistant', content: [{ type: 'text', text: `${roleName} 被中断` }] }
268
- });
269
-
270
- await dispatchToRole(session, roleName, newContent, fromSource);
271
- }
272
-
273
- /**
274
- * 中止角色当前 turn
275
- */
276
- async function abortRole(session, roleName) {
277
- const roleState = session.roleStates.get(roleName);
278
- if (!roleState) {
279
- console.warn(`[Crew] Cannot abort ${roleName}: no roleState`);
280
- return;
281
- }
282
-
283
- if (!roleState.turnActive) {
284
- console.log(`[Crew] ${roleName} is not active, nothing to abort`);
285
- return;
286
- }
287
-
288
- console.log(`[Crew] Aborting ${roleName}`);
289
-
290
- endRoleStreaming(session, roleName);
291
-
292
- if (roleState.claudeSessionId) {
293
- await saveRoleSessionId(session.sharedDir, roleName, roleState.claudeSessionId)
294
- .catch(e => console.warn(`[Crew] Failed to save sessionId for ${roleName}:`, e.message));
295
- }
296
-
297
- if (roleState.abortController) {
298
- roleState.abortController.abort();
299
- }
300
-
301
- roleState.query = null;
302
- roleState.inputStream = null;
303
- roleState.turnActive = false;
304
- roleState.accumulatedText = '';
305
-
306
- // Mark pending tool messages as completed before notifying frontend
307
- for (const m of session.uiMessages) {
308
- if (m.role === roleName && m.type === 'tool' && !m.hasResult) {
309
- m.hasResult = true;
310
- }
311
- }
312
-
313
- sendCrewMessage({
314
- type: 'crew_turn_completed',
315
- sessionId: session.id,
316
- role: roleName,
317
- interrupted: true
318
- });
319
-
320
- sendStatusUpdate(session);
321
-
322
- sendCrewOutput(session, 'system', 'system', {
323
- type: 'assistant',
324
- message: { role: 'assistant', content: [{ type: 'text', text: `${roleName} 已中止` }] }
325
- });
326
- }
327
-
328
- async function stopRole(session, roleName) {
329
- const roleState = session.roleStates.get(roleName);
330
- if (roleState) {
331
- if (roleState.claudeSessionId) {
332
- await saveRoleSessionId(session.sharedDir, roleName, roleState.claudeSessionId)
333
- .catch(e => console.warn(`[Crew] Failed to save sessionId for ${roleName}:`, e.message));
334
- }
335
- if (roleState.abortController) {
336
- roleState.abortController.abort();
337
- }
338
- roleState.query = null;
339
- roleState.inputStream = null;
340
- roleState.turnActive = false;
341
- session.roleStates.delete(roleName);
342
- }
343
-
344
- sendCrewOutput(session, 'system', 'system', {
345
- type: 'assistant',
346
- message: { role: 'assistant', content: [{ type: 'text', text: `${roleName} 已停止` }] }
347
- });
348
- sendStatusUpdate(session);
349
- console.log(`[Crew] Role ${roleName} stopped`);
350
- }
351
-
352
- /**
353
- * 终止整个 session
354
- */
355
- async function stopAll(session) {
356
- const { crewSessions } = await import('./session.js');
357
-
358
- session.status = 'stopped';
359
-
360
- for (const [roleName, roleState] of session.roleStates) {
361
- if (roleState.claudeSessionId) {
362
- await saveRoleSessionId(session.sharedDir, roleName, roleState.claudeSessionId)
363
- .catch(e => console.warn(`[Crew] Failed to save sessionId for ${roleName}:`, e.message));
364
- }
365
- if (roleState.abortController) {
366
- roleState.abortController.abort();
367
- }
368
- console.log(`[Crew] Stopping role: ${roleName}`);
369
- }
370
- session.roleStates.clear();
371
-
372
- sendCrewOutput(session, 'system', 'system', {
373
- type: 'assistant',
374
- message: { role: 'assistant', content: [{ type: 'text', text: 'Session 已终止' }] }
375
- });
376
- sendStatusUpdate(session);
377
-
378
- await cleanupWorktrees(session.projectDir);
379
-
380
- await saveSessionMeta(session);
381
- await upsertCrewIndex(session);
382
-
383
- crewSessions.delete(session.id);
384
- console.log(`[Crew] Session ${session.id} stopped`);
385
- }
386
-
387
- /**
388
- * 清空 session
389
- */
390
- async function clearSession(session) {
391
- // P1-2: 先重置 _processingHumanQueue,防止后续消息处理被阻塞
392
- session._processingHumanQueue = false;
393
-
394
- for (const [roleName, roleState] of session.roleStates) {
395
- if (roleState.abortController) {
396
- roleState.abortController.abort();
397
- }
398
- console.log(`[Crew] Clearing role: ${roleName}`);
399
- }
400
- session.roleStates.clear();
401
-
402
- for (const [roleName] of session.roles) {
403
- await clearRoleSessionId(session.sharedDir, roleName);
404
- }
405
-
406
- // P1-1: humanMessageQueue 在这里清空,确保不会有幽灵消息在 clear 后被处理
407
- session.messageHistory = [];
408
- session.uiMessages = [];
409
- session.humanMessageQueue = [];
410
- session.waitingHumanContext = null;
411
- session.pendingRoutes = [];
412
-
413
- // 清除 feature/task 数据,避免 UI 残留空 task 卡片
414
- session.features.clear();
415
- session._completedTaskIds = new Set();
416
-
417
- session.round = 0;
418
-
419
- // 重置计费统计(clearSession 清除了所有 claudeSessionId,后续 query 全新,费用从零开始)
420
- session.costUsd = 0;
421
- session.totalInputTokens = 0;
422
- session.totalOutputTokens = 0;
423
-
424
- const messagesPath = join(session.sharedDir, 'messages.json');
425
- await fs.writeFile(messagesPath, '[]').catch(() => {});
426
- await cleanupMessageShards(session.sharedDir);
427
-
428
- session.status = 'running';
429
-
430
- sendCrewMessage({
431
- type: 'crew_session_cleared',
432
- sessionId: session.id
433
- });
434
-
435
- sendCrewOutput(session, 'system', 'system', {
436
- type: 'assistant',
437
- message: { role: 'assistant', content: [{ type: 'text', text: '会话已清空,所有角色将使用全新对话' }] }
438
- });
439
- sendStatusUpdate(session);
440
-
441
- await saveSessionMeta(session);
442
-
443
- console.log(`[Crew] Session ${session.id} cleared`);
444
- }
@@ -1,195 +0,0 @@
1
- /**
2
- * Crew — 人工交互
3
- * handleCrewHumanInput, processHumanQueue
4
- */
5
- import { dispatchToRole } from './routing.js';
6
- import { sendStatusUpdate } from './ui-messages.js';
7
- import { debouncedSaveSessionMeta } from './persistence.js';
8
-
9
- /**
10
- * Resolve @role mention from message content.
11
- * Returns { target, message } if a valid role is found, null otherwise.
12
- */
13
- export function resolveAtMention(content, session) {
14
- const atMatch = content.match(/^@(\S+)\s*([\s\S]*)/);
15
- if (!atMatch) return null;
16
-
17
- const atTarget = atMatch[1];
18
- const message = atMatch[2].trim() || content;
19
-
20
- for (const [name, role] of session.roles) {
21
- if (name === atTarget.toLowerCase()) {
22
- return { target: name, message };
23
- }
24
- if (role.displayName === atTarget) {
25
- return { target: name, message };
26
- }
27
- // Fuzzy: compound "name-displayName" pattern (e.g. "dev-1-托瓦兹" or partial displayName)
28
- const compound = `${name}-${role.displayName}`;
29
- if (compound.toLowerCase() === atTarget.toLowerCase()) {
30
- return { target: name, message };
31
- }
32
- }
33
- return null;
34
- }
35
-
36
- /**
37
- * 处理人的输入
38
- */
39
- export async function handleCrewHumanInput(msg) {
40
- // Lazy import to avoid circular dependency
41
- const { crewSessions, resumeCrewSession } = await import('./session.js');
42
-
43
- const { sessionId, content, targetRole, files } = msg;
44
- let session = crewSessions.get(sessionId);
45
- if (!session) {
46
- // Auto-resume: try to restore from disk before giving up
47
- console.log(`[Crew] Session ${sessionId} not in memory, attempting auto-resume...`);
48
- try {
49
- await resumeCrewSession({ sessionId });
50
- session = crewSessions.get(sessionId);
51
- } catch (e) {
52
- console.warn(`[Crew] Auto-resume failed for ${sessionId}:`, e.message);
53
- }
54
- if (!session) {
55
- console.warn(`[Crew] Session not found: ${sessionId} (even after auto-resume)`);
56
- return;
57
- }
58
- }
59
-
60
- // Auto-resume: user sending a message = intent to continue → restore to running
61
- if (session.status === 'paused' || session.status === 'stopped' || session.status === 'completed') {
62
- console.log(`[Crew] Auto-resuming session from ${session.status} to running (user sent message)`);
63
- session.status = 'running';
64
- sendStatusUpdate(session);
65
- debouncedSaveSessionMeta(session);
66
- }
67
-
68
- // Resolve @role mention from content (works regardless of targetRole from frontend)
69
- const atMention = resolveAtMention(content, session);
70
- // Effective target: explicit targetRole from frontend > @mention from content > null
71
- const effectiveTargetRole = targetRole || (atMention ? atMention.target : null);
72
- // If @mention found, use the stripped message (without @prefix); otherwise use original content
73
- const effectiveContent = atMention ? atMention.message : content;
74
-
75
- // Build dispatch content (supports image attachments)
76
- function buildHumanContent(prefix, text) {
77
- if (files && files.length > 0) {
78
- const blocks = [];
79
- for (const file of files) {
80
- if (file.isImage || file.mimeType?.startsWith('image/')) {
81
- blocks.push({
82
- type: 'image',
83
- source: { type: 'base64', media_type: file.mimeType, data: file.data }
84
- });
85
- }
86
- }
87
- blocks.push({ type: 'text', text: `${prefix}\n${text}` });
88
- return blocks;
89
- }
90
- return `${prefix}\n${text}`;
91
- }
92
-
93
- // 记录到 uiMessages 用于恢复时重放
94
- session.uiMessages.push({
95
- role: 'human', roleIcon: '', roleName: '你',
96
- type: 'text', content,
97
- timestamp: Date.now()
98
- });
99
-
100
- // 如果在等待人工介入
101
- if (session.status === 'waiting_human') {
102
- const waitingContext = session.waitingHumanContext;
103
- session.status = 'running';
104
- session.waitingHumanContext = null;
105
- sendStatusUpdate(session);
106
- // Status changed + new human message — persist (debounced, dispatch will follow)
107
- debouncedSaveSessionMeta(session);
108
-
109
- const target = effectiveTargetRole || waitingContext?.fromRole || session.decisionMaker;
110
- if (effectiveTargetRole) {
111
- console.log(`[Crew] @mention override in waiting_human: routing to ${target} instead of ${waitingContext?.fromRole || session.decisionMaker}`);
112
- }
113
- await dispatchToRole(session, target, buildHumanContent('人工回复:', effectiveContent), 'human');
114
- return;
115
- }
116
-
117
- // @role 指令 — effectiveTargetRole already resolved above
118
- if (atMention && effectiveTargetRole) {
119
- const target = effectiveTargetRole;
120
- const message = effectiveContent;
121
-
122
- // 检测纯 skill 命令(如 /context, /simplify),直接发送不加前缀
123
- if (/^\/[a-zA-Z0-9_-]+(?:\s+.*)?$/s.test(message)) {
124
- let roleState = session.roleStates.get(target);
125
- if (!roleState || !roleState.query || !roleState.inputStream) {
126
- const { createRoleQuery } = await import('./role-query.js');
127
- roleState = await createRoleQuery(session, target);
128
- }
129
- // P1-4: 守卫 stream.enqueue
130
- try {
131
- if (roleState.inputStream && !roleState.inputStream.isDone) {
132
- roleState.inputStream.enqueue({
133
- type: 'user',
134
- message: { role: 'user', content: message }
135
- });
136
- } else {
137
- console.warn(`[Crew] Skill dispatch: stream closed for ${target}, recreating`);
138
- const { createRoleQuery } = await import('./role-query.js');
139
- roleState = await createRoleQuery(session, target);
140
- roleState.inputStream.enqueue({
141
- type: 'user',
142
- message: { role: 'user', content: message }
143
- });
144
- }
145
- } catch (enqueueErr) {
146
- console.error(`[Crew] Skill dispatch enqueue failed for ${target}:`, enqueueErr.message);
147
- const { createRoleQuery } = await import('./role-query.js');
148
- roleState = await createRoleQuery(session, target);
149
- roleState.inputStream.enqueue({
150
- type: 'user',
151
- message: { role: 'user', content: message }
152
- });
153
- }
154
- sendStatusUpdate(session);
155
- console.log(`[Crew] Skill command dispatched to ${target}: ${message}`);
156
- return;
157
- }
158
- console.log(`[Crew] @mention routing to ${target}: ${message.substring(0, 50)}...`);
159
- await dispatchToRole(session, target, buildHumanContent('人工消息:', message), 'human');
160
- return;
161
- }
162
-
163
- // 默认发给决策者
164
- const target = effectiveTargetRole || session.decisionMaker;
165
- await dispatchToRole(session, target, buildHumanContent('人工消息:', effectiveContent), 'human');
166
- }
167
-
168
- /**
169
- * 处理排队的人的消息
170
- */
171
- export async function processHumanQueue(session) {
172
- if (session.humanMessageQueue.length === 0) return;
173
- if (session._processingHumanQueue) return;
174
- session._processingHumanQueue = true;
175
- try {
176
- const msgs = session.humanMessageQueue.splice(0);
177
- if (msgs.length === 1) {
178
- const humanPrompt = `人工消息:\n${msgs[0].content}`;
179
- await dispatchToRole(session, msgs[0].target, humanPrompt, 'human');
180
- } else {
181
- const byTarget = new Map();
182
- for (const m of msgs) {
183
- if (!byTarget.has(m.target)) byTarget.set(m.target, []);
184
- byTarget.get(m.target).push(m.content);
185
- }
186
- for (const [target, contents] of byTarget) {
187
- const combined = contents.join('\n\n---\n\n');
188
- const humanPrompt = `人工消息:\n你有 ${contents.length} 条待处理消息,请一并分析并用多个 ROUTE 块并行分配:\n\n${combined}`;
189
- await dispatchToRole(session, target, humanPrompt, 'human');
190
- }
191
- }
192
- } finally {
193
- session._processingHumanQueue = false;
194
- }
195
- }