@yeaft/webchat-agent 0.0.233 → 0.0.235
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/connection/buffer.js +87 -0
- package/connection/heartbeat.js +47 -0
- package/connection/index.js +89 -0
- package/connection/message-router.js +271 -0
- package/connection/upgrade-worker-template.js +103 -0
- package/connection/upgrade.js +294 -0
- package/connection.js +14 -777
- package/crew/control.js +364 -0
- package/crew/human-interaction.js +115 -0
- package/crew/persistence.js +287 -0
- package/crew/role-management.js +131 -0
- package/crew/role-output.js +315 -0
- package/crew/role-query.js +309 -0
- package/crew/routing.js +194 -0
- package/crew/session.js +474 -0
- package/crew/shared-dir.js +116 -0
- package/crew/task-files.js +370 -0
- package/crew/ui-messages.js +246 -0
- package/crew/worktree.js +130 -0
- package/package.json +6 -2
- package/service/config.js +133 -0
- package/service/index.js +99 -0
- package/service/linux.js +111 -0
- package/service/macos.js +137 -0
- package/service/windows.js +181 -0
- package/service.js +23 -624
- package/workbench/file-ops.js +436 -0
- package/workbench/file-search.js +66 -0
- package/workbench/git-ops.js +313 -0
- package/workbench/transfer.js +99 -0
- package/workbench/utils.js +41 -0
- package/workbench.js +15 -938
package/crew/control.js
ADDED
|
@@ -0,0 +1,364 @@
|
|
|
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 { cleanupWorktrees } from './worktree.js';
|
|
12
|
+
import { upsertCrewIndex } from './persistence.js';
|
|
13
|
+
import { processHumanQueue } from './human-interaction.js';
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* 处理控制命令
|
|
17
|
+
*/
|
|
18
|
+
export async function handleCrewControl(msg) {
|
|
19
|
+
// Lazy import to avoid circular dependency
|
|
20
|
+
const { crewSessions } = await import('./session.js');
|
|
21
|
+
|
|
22
|
+
const { sessionId, action, targetRole } = msg;
|
|
23
|
+
const session = crewSessions.get(sessionId);
|
|
24
|
+
if (!session) {
|
|
25
|
+
console.warn(`[Crew] Session not found: ${sessionId}`);
|
|
26
|
+
return;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
switch (action) {
|
|
30
|
+
case 'pause':
|
|
31
|
+
await pauseAll(session);
|
|
32
|
+
break;
|
|
33
|
+
case 'resume':
|
|
34
|
+
await resumeSession(session);
|
|
35
|
+
break;
|
|
36
|
+
case 'stop_role':
|
|
37
|
+
if (targetRole) await stopRole(session, targetRole);
|
|
38
|
+
break;
|
|
39
|
+
case 'interrupt_role':
|
|
40
|
+
if (targetRole && msg.content) {
|
|
41
|
+
await interruptRole(session, targetRole, msg.content, 'human');
|
|
42
|
+
}
|
|
43
|
+
break;
|
|
44
|
+
case 'abort_role':
|
|
45
|
+
if (targetRole) await abortRole(session, targetRole);
|
|
46
|
+
break;
|
|
47
|
+
case 'clear_role':
|
|
48
|
+
if (targetRole) await clearSingleRole(session, targetRole);
|
|
49
|
+
break;
|
|
50
|
+
case 'stop_all':
|
|
51
|
+
await stopAll(session);
|
|
52
|
+
break;
|
|
53
|
+
case 'clear':
|
|
54
|
+
await clearSession(session);
|
|
55
|
+
break;
|
|
56
|
+
default:
|
|
57
|
+
console.warn(`[Crew] Unknown control action: ${action}`);
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* 清空单个角色的对话
|
|
63
|
+
*/
|
|
64
|
+
async function clearSingleRole(session, roleName) {
|
|
65
|
+
const roleState = session.roleStates.get(roleName);
|
|
66
|
+
|
|
67
|
+
if (roleState) {
|
|
68
|
+
if (roleState.abortController) {
|
|
69
|
+
roleState.abortController.abort();
|
|
70
|
+
}
|
|
71
|
+
roleState.query = null;
|
|
72
|
+
roleState.inputStream = null;
|
|
73
|
+
roleState.turnActive = false;
|
|
74
|
+
roleState.claudeSessionId = null;
|
|
75
|
+
roleState.consecutiveErrors = 0;
|
|
76
|
+
roleState.accumulatedText = '';
|
|
77
|
+
roleState.lastDispatchContent = null;
|
|
78
|
+
roleState.lastDispatchFrom = null;
|
|
79
|
+
roleState.lastDispatchTaskId = null;
|
|
80
|
+
roleState.lastDispatchTaskTitle = null;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
await clearRoleSessionId(session.sharedDir, roleName);
|
|
84
|
+
|
|
85
|
+
sendCrewMessage({
|
|
86
|
+
type: 'crew_role_cleared',
|
|
87
|
+
sessionId: session.id,
|
|
88
|
+
role: roleName,
|
|
89
|
+
reason: 'manual'
|
|
90
|
+
});
|
|
91
|
+
|
|
92
|
+
sendCrewOutput(session, 'system', 'system', {
|
|
93
|
+
type: 'assistant',
|
|
94
|
+
message: { role: 'assistant', content: [{ type: 'text', text: `${roleName} 对话已清空` }] }
|
|
95
|
+
});
|
|
96
|
+
sendStatusUpdate(session);
|
|
97
|
+
console.log(`[Crew] Role ${roleName} cleared`);
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
/**
|
|
101
|
+
* 暂停所有角色
|
|
102
|
+
*/
|
|
103
|
+
async function pauseAll(session) {
|
|
104
|
+
session.status = 'paused';
|
|
105
|
+
|
|
106
|
+
for (const [roleName, roleState] of session.roleStates) {
|
|
107
|
+
if (roleState.claudeSessionId) {
|
|
108
|
+
await saveRoleSessionId(session.sharedDir, roleName, roleState.claudeSessionId)
|
|
109
|
+
.catch(e => console.warn(`[Crew] Failed to save sessionId for ${roleName}:`, e.message));
|
|
110
|
+
}
|
|
111
|
+
if (roleState.abortController) {
|
|
112
|
+
roleState.abortController.abort();
|
|
113
|
+
}
|
|
114
|
+
roleState.wasActive = roleState.turnActive;
|
|
115
|
+
roleState.turnActive = false;
|
|
116
|
+
roleState.query = null;
|
|
117
|
+
roleState.inputStream = null;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
console.log(`[Crew] Session ${session.id} paused, all active queries aborted`);
|
|
121
|
+
|
|
122
|
+
sendCrewOutput(session, 'system', 'system', {
|
|
123
|
+
type: 'assistant',
|
|
124
|
+
message: { role: 'assistant', content: [{ type: 'text', text: 'Session 已暂停' }] }
|
|
125
|
+
});
|
|
126
|
+
sendStatusUpdate(session);
|
|
127
|
+
|
|
128
|
+
await saveSessionMeta(session);
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
/**
|
|
132
|
+
* 恢复 session
|
|
133
|
+
*/
|
|
134
|
+
async function resumeSession(session) {
|
|
135
|
+
if (session.status !== 'paused') return;
|
|
136
|
+
|
|
137
|
+
session.status = 'running';
|
|
138
|
+
console.log(`[Crew] Session ${session.id} resumed`);
|
|
139
|
+
|
|
140
|
+
sendCrewOutput(session, 'system', 'system', {
|
|
141
|
+
type: 'assistant',
|
|
142
|
+
message: { role: 'assistant', content: [{ type: 'text', text: 'Session 已恢复' }] }
|
|
143
|
+
});
|
|
144
|
+
sendStatusUpdate(session);
|
|
145
|
+
|
|
146
|
+
if (session.pendingRoutes.length > 0) {
|
|
147
|
+
const pending = session.pendingRoutes.slice();
|
|
148
|
+
session.pendingRoutes = [];
|
|
149
|
+
console.log(`[Crew] Replaying ${pending.length} pending route(s)`);
|
|
150
|
+
const results = await Promise.allSettled(pending.map(({ fromRole, route }) =>
|
|
151
|
+
executeRoute(session, fromRole, route)
|
|
152
|
+
));
|
|
153
|
+
for (const r of results) {
|
|
154
|
+
if (r.status === 'rejected') {
|
|
155
|
+
console.warn(`[Crew] Pending route replay failed:`, r.reason);
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
return;
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
await processHumanQueue(session);
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
/**
|
|
165
|
+
* 中断角色当前 turn 并发送新消息
|
|
166
|
+
*/
|
|
167
|
+
async function interruptRole(session, roleName, newContent, fromSource = 'human') {
|
|
168
|
+
const roleState = session.roleStates.get(roleName);
|
|
169
|
+
if (!roleState) {
|
|
170
|
+
console.warn(`[Crew] Cannot interrupt ${roleName}: no roleState`);
|
|
171
|
+
return;
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
console.log(`[Crew] Interrupting ${roleName}`);
|
|
175
|
+
|
|
176
|
+
endRoleStreaming(session, roleName);
|
|
177
|
+
|
|
178
|
+
if (roleState.claudeSessionId) {
|
|
179
|
+
await saveRoleSessionId(session.sharedDir, roleName, roleState.claudeSessionId)
|
|
180
|
+
.catch(e => console.warn(`[Crew] Failed to save sessionId for ${roleName}:`, e.message));
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
if (roleState.abortController) {
|
|
184
|
+
roleState.abortController.abort();
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
roleState.query = null;
|
|
188
|
+
roleState.inputStream = null;
|
|
189
|
+
roleState.turnActive = false;
|
|
190
|
+
roleState.accumulatedText = '';
|
|
191
|
+
|
|
192
|
+
sendCrewMessage({
|
|
193
|
+
type: 'crew_turn_completed',
|
|
194
|
+
sessionId: session.id,
|
|
195
|
+
role: roleName,
|
|
196
|
+
interrupted: true
|
|
197
|
+
});
|
|
198
|
+
|
|
199
|
+
sendStatusUpdate(session);
|
|
200
|
+
|
|
201
|
+
sendCrewOutput(session, 'system', 'system', {
|
|
202
|
+
type: 'assistant',
|
|
203
|
+
message: { role: 'assistant', content: [{ type: 'text', text: `${roleName} 被中断` }] }
|
|
204
|
+
});
|
|
205
|
+
|
|
206
|
+
await dispatchToRole(session, roleName, newContent, fromSource);
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
/**
|
|
210
|
+
* 中止角色当前 turn
|
|
211
|
+
*/
|
|
212
|
+
async function abortRole(session, roleName) {
|
|
213
|
+
const roleState = session.roleStates.get(roleName);
|
|
214
|
+
if (!roleState) {
|
|
215
|
+
console.warn(`[Crew] Cannot abort ${roleName}: no roleState`);
|
|
216
|
+
return;
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
if (!roleState.turnActive) {
|
|
220
|
+
console.log(`[Crew] ${roleName} is not active, nothing to abort`);
|
|
221
|
+
return;
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
console.log(`[Crew] Aborting ${roleName}`);
|
|
225
|
+
|
|
226
|
+
endRoleStreaming(session, roleName);
|
|
227
|
+
|
|
228
|
+
if (roleState.claudeSessionId) {
|
|
229
|
+
await saveRoleSessionId(session.sharedDir, roleName, roleState.claudeSessionId)
|
|
230
|
+
.catch(e => console.warn(`[Crew] Failed to save sessionId for ${roleName}:`, e.message));
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
if (roleState.abortController) {
|
|
234
|
+
roleState.abortController.abort();
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
roleState.query = null;
|
|
238
|
+
roleState.inputStream = null;
|
|
239
|
+
roleState.turnActive = false;
|
|
240
|
+
roleState.accumulatedText = '';
|
|
241
|
+
|
|
242
|
+
sendCrewMessage({
|
|
243
|
+
type: 'crew_turn_completed',
|
|
244
|
+
sessionId: session.id,
|
|
245
|
+
role: roleName,
|
|
246
|
+
interrupted: true
|
|
247
|
+
});
|
|
248
|
+
|
|
249
|
+
sendStatusUpdate(session);
|
|
250
|
+
|
|
251
|
+
sendCrewOutput(session, 'system', 'system', {
|
|
252
|
+
type: 'assistant',
|
|
253
|
+
message: { role: 'assistant', content: [{ type: 'text', text: `${roleName} 已中止` }] }
|
|
254
|
+
});
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
async function stopRole(session, roleName) {
|
|
258
|
+
const roleState = session.roleStates.get(roleName);
|
|
259
|
+
if (roleState) {
|
|
260
|
+
if (roleState.claudeSessionId) {
|
|
261
|
+
await saveRoleSessionId(session.sharedDir, roleName, roleState.claudeSessionId)
|
|
262
|
+
.catch(e => console.warn(`[Crew] Failed to save sessionId for ${roleName}:`, e.message));
|
|
263
|
+
}
|
|
264
|
+
if (roleState.abortController) {
|
|
265
|
+
roleState.abortController.abort();
|
|
266
|
+
}
|
|
267
|
+
roleState.query = null;
|
|
268
|
+
roleState.inputStream = null;
|
|
269
|
+
roleState.turnActive = false;
|
|
270
|
+
session.roleStates.delete(roleName);
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
sendCrewOutput(session, 'system', 'system', {
|
|
274
|
+
type: 'assistant',
|
|
275
|
+
message: { role: 'assistant', content: [{ type: 'text', text: `${roleName} 已停止` }] }
|
|
276
|
+
});
|
|
277
|
+
sendStatusUpdate(session);
|
|
278
|
+
console.log(`[Crew] Role ${roleName} stopped`);
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
/**
|
|
282
|
+
* 终止整个 session
|
|
283
|
+
*/
|
|
284
|
+
async function stopAll(session) {
|
|
285
|
+
const { crewSessions } = await import('./session.js');
|
|
286
|
+
|
|
287
|
+
session.status = 'stopped';
|
|
288
|
+
|
|
289
|
+
for (const [roleName, roleState] of session.roleStates) {
|
|
290
|
+
if (roleState.claudeSessionId) {
|
|
291
|
+
await saveRoleSessionId(session.sharedDir, roleName, roleState.claudeSessionId)
|
|
292
|
+
.catch(e => console.warn(`[Crew] Failed to save sessionId for ${roleName}:`, e.message));
|
|
293
|
+
}
|
|
294
|
+
if (roleState.abortController) {
|
|
295
|
+
roleState.abortController.abort();
|
|
296
|
+
}
|
|
297
|
+
console.log(`[Crew] Stopping role: ${roleName}`);
|
|
298
|
+
}
|
|
299
|
+
session.roleStates.clear();
|
|
300
|
+
|
|
301
|
+
sendCrewOutput(session, 'system', 'system', {
|
|
302
|
+
type: 'assistant',
|
|
303
|
+
message: { role: 'assistant', content: [{ type: 'text', text: 'Session 已终止' }] }
|
|
304
|
+
});
|
|
305
|
+
sendStatusUpdate(session);
|
|
306
|
+
|
|
307
|
+
await cleanupWorktrees(session.projectDir);
|
|
308
|
+
|
|
309
|
+
await saveSessionMeta(session);
|
|
310
|
+
await upsertCrewIndex(session);
|
|
311
|
+
|
|
312
|
+
crewSessions.delete(session.id);
|
|
313
|
+
console.log(`[Crew] Session ${session.id} stopped`);
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
/**
|
|
317
|
+
* 清空 session
|
|
318
|
+
*/
|
|
319
|
+
async function clearSession(session) {
|
|
320
|
+
for (const [roleName, roleState] of session.roleStates) {
|
|
321
|
+
if (roleState.abortController) {
|
|
322
|
+
roleState.abortController.abort();
|
|
323
|
+
}
|
|
324
|
+
console.log(`[Crew] Clearing role: ${roleName}`);
|
|
325
|
+
}
|
|
326
|
+
session.roleStates.clear();
|
|
327
|
+
|
|
328
|
+
for (const [roleName] of session.roles) {
|
|
329
|
+
await clearRoleSessionId(session.sharedDir, roleName);
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
session.messageHistory = [];
|
|
333
|
+
session.uiMessages = [];
|
|
334
|
+
session.humanMessageQueue = [];
|
|
335
|
+
session.waitingHumanContext = null;
|
|
336
|
+
session.pendingRoutes = [];
|
|
337
|
+
|
|
338
|
+
// 清除 feature/task 数据,避免 UI 残留空 task 卡片
|
|
339
|
+
session.features.clear();
|
|
340
|
+
session._completedTaskIds = new Set();
|
|
341
|
+
|
|
342
|
+
session.round = 0;
|
|
343
|
+
|
|
344
|
+
const messagesPath = join(session.sharedDir, 'messages.json');
|
|
345
|
+
await fs.writeFile(messagesPath, '[]').catch(() => {});
|
|
346
|
+
await cleanupMessageShards(session.sharedDir);
|
|
347
|
+
|
|
348
|
+
session.status = 'running';
|
|
349
|
+
|
|
350
|
+
sendCrewMessage({
|
|
351
|
+
type: 'crew_session_cleared',
|
|
352
|
+
sessionId: session.id
|
|
353
|
+
});
|
|
354
|
+
|
|
355
|
+
sendCrewOutput(session, 'system', 'system', {
|
|
356
|
+
type: 'assistant',
|
|
357
|
+
message: { role: 'assistant', content: [{ type: 'text', text: '会话已清空,所有角色将使用全新对话' }] }
|
|
358
|
+
});
|
|
359
|
+
sendStatusUpdate(session);
|
|
360
|
+
|
|
361
|
+
await saveSessionMeta(session);
|
|
362
|
+
|
|
363
|
+
console.log(`[Crew] Session ${session.id} cleared`);
|
|
364
|
+
}
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Crew — 人工交互
|
|
3
|
+
* handleCrewHumanInput, processHumanQueue
|
|
4
|
+
*/
|
|
5
|
+
import { dispatchToRole } from './routing.js';
|
|
6
|
+
import { sendStatusUpdate } from './ui-messages.js';
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* 处理人的输入
|
|
10
|
+
*/
|
|
11
|
+
export async function handleCrewHumanInput(msg) {
|
|
12
|
+
// Lazy import to avoid circular dependency
|
|
13
|
+
const { crewSessions } = await import('./session.js');
|
|
14
|
+
|
|
15
|
+
const { sessionId, content, targetRole, files } = msg;
|
|
16
|
+
const session = crewSessions.get(sessionId);
|
|
17
|
+
if (!session) {
|
|
18
|
+
console.warn(`[Crew] Session not found: ${sessionId}`);
|
|
19
|
+
return;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
// Build dispatch content (supports image attachments)
|
|
23
|
+
function buildHumanContent(prefix, text) {
|
|
24
|
+
if (files && files.length > 0) {
|
|
25
|
+
const blocks = [];
|
|
26
|
+
for (const file of files) {
|
|
27
|
+
if (file.isImage || file.mimeType?.startsWith('image/')) {
|
|
28
|
+
blocks.push({
|
|
29
|
+
type: 'image',
|
|
30
|
+
source: { type: 'base64', media_type: file.mimeType, data: file.data }
|
|
31
|
+
});
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
blocks.push({ type: 'text', text: `${prefix}\n${text}` });
|
|
35
|
+
return blocks;
|
|
36
|
+
}
|
|
37
|
+
return `${prefix}\n${text}`;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
// 记录到 uiMessages 用于恢复时重放
|
|
41
|
+
session.uiMessages.push({
|
|
42
|
+
role: 'human', roleIcon: '', roleName: '你',
|
|
43
|
+
type: 'text', content,
|
|
44
|
+
timestamp: Date.now()
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
// 如果在等待人工介入
|
|
48
|
+
if (session.status === 'waiting_human') {
|
|
49
|
+
const waitingContext = session.waitingHumanContext;
|
|
50
|
+
session.status = 'running';
|
|
51
|
+
session.waitingHumanContext = null;
|
|
52
|
+
sendStatusUpdate(session);
|
|
53
|
+
|
|
54
|
+
const target = targetRole || waitingContext?.fromRole || session.decisionMaker;
|
|
55
|
+
await dispatchToRole(session, target, buildHumanContent('人工回复:', content), 'human');
|
|
56
|
+
return;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
// 解析 @role 指令
|
|
60
|
+
const atMatch = content.match(/^@(\S+)\s*([\s\S]*)/);
|
|
61
|
+
if (atMatch) {
|
|
62
|
+
const atTarget = atMatch[1];
|
|
63
|
+
const message = atMatch[2].trim() || content;
|
|
64
|
+
|
|
65
|
+
let target = null;
|
|
66
|
+
for (const [name, role] of session.roles) {
|
|
67
|
+
if (name === atTarget.toLowerCase()) {
|
|
68
|
+
target = name;
|
|
69
|
+
break;
|
|
70
|
+
}
|
|
71
|
+
if (role.displayName === atTarget) {
|
|
72
|
+
target = name;
|
|
73
|
+
break;
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
if (target) {
|
|
78
|
+
await dispatchToRole(session, target, buildHumanContent('人工消息:', message), 'human');
|
|
79
|
+
return;
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
// 默认发给决策者
|
|
84
|
+
const target = targetRole || session.decisionMaker;
|
|
85
|
+
await dispatchToRole(session, target, buildHumanContent('人工消息:', content), 'human');
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
/**
|
|
89
|
+
* 处理排队的人的消息
|
|
90
|
+
*/
|
|
91
|
+
export async function processHumanQueue(session) {
|
|
92
|
+
if (session.humanMessageQueue.length === 0) return;
|
|
93
|
+
if (session._processingHumanQueue) return;
|
|
94
|
+
session._processingHumanQueue = true;
|
|
95
|
+
try {
|
|
96
|
+
const msgs = session.humanMessageQueue.splice(0);
|
|
97
|
+
if (msgs.length === 1) {
|
|
98
|
+
const humanPrompt = `人工消息:\n${msgs[0].content}`;
|
|
99
|
+
await dispatchToRole(session, msgs[0].target, humanPrompt, 'human');
|
|
100
|
+
} else {
|
|
101
|
+
const byTarget = new Map();
|
|
102
|
+
for (const m of msgs) {
|
|
103
|
+
if (!byTarget.has(m.target)) byTarget.set(m.target, []);
|
|
104
|
+
byTarget.get(m.target).push(m.content);
|
|
105
|
+
}
|
|
106
|
+
for (const [target, contents] of byTarget) {
|
|
107
|
+
const combined = contents.join('\n\n---\n\n');
|
|
108
|
+
const humanPrompt = `人工消息:\n你有 ${contents.length} 条待处理消息,请一并分析并用多个 ROUTE 块并行分配:\n\n${combined}`;
|
|
109
|
+
await dispatchToRole(session, target, humanPrompt, 'human');
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
} finally {
|
|
113
|
+
session._processingHumanQueue = false;
|
|
114
|
+
}
|
|
115
|
+
}
|