@yeaft/webchat-agent 0.0.153 → 0.0.154
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.js +122 -15
- package/package.json +1 -1
package/crew.js
CHANGED
|
@@ -926,6 +926,43 @@ async function loadRoleSessionId(sharedDir, roleName) {
|
|
|
926
926
|
}
|
|
927
927
|
}
|
|
928
928
|
|
|
929
|
+
|
|
930
|
+
/**
|
|
931
|
+
* 清除角色的 savedSessionId(用于强制新建 conversation)
|
|
932
|
+
*/
|
|
933
|
+
async function clearRoleSessionId(sharedDir, roleName) {
|
|
934
|
+
const filePath = join(sharedDir, 'sessions', `${roleName}.json`);
|
|
935
|
+
try {
|
|
936
|
+
await fs.unlink(filePath);
|
|
937
|
+
console.log(`[Crew] Cleared sessionId for ${roleName} (force new conversation)`);
|
|
938
|
+
} catch {
|
|
939
|
+
// 文件不存在也正常
|
|
940
|
+
}
|
|
941
|
+
}
|
|
942
|
+
|
|
943
|
+
/**
|
|
944
|
+
* 判断角色错误是否可恢复
|
|
945
|
+
*/
|
|
946
|
+
function classifyRoleError(error) {
|
|
947
|
+
const msg = error.message || '';
|
|
948
|
+
if (/context.*(window|limit|exceeded)|token.*limit|too.*(long|large)|max.*token/i.test(msg)) {
|
|
949
|
+
return { recoverable: true, reason: 'context_exceeded', skipResume: true };
|
|
950
|
+
}
|
|
951
|
+
if (/compact|compress|context.*reduc/i.test(msg)) {
|
|
952
|
+
return { recoverable: true, reason: 'compact_failed', skipResume: true };
|
|
953
|
+
}
|
|
954
|
+
if (/rate.?limit|429|overloaded|503|502|timeout|ECONNRESET|ETIMEDOUT/i.test(msg)) {
|
|
955
|
+
return { recoverable: true, reason: 'transient_api_error', skipResume: false };
|
|
956
|
+
}
|
|
957
|
+
if (/exited with code [1-9]/i.test(msg) && msg.length < 100) {
|
|
958
|
+
return { recoverable: true, reason: 'process_crashed', skipResume: false };
|
|
959
|
+
}
|
|
960
|
+
if (/spawn|ENOENT|not found/i.test(msg)) {
|
|
961
|
+
return { recoverable: false, reason: 'spawn_failed' };
|
|
962
|
+
}
|
|
963
|
+
return { recoverable: true, reason: 'unknown', skipResume: false };
|
|
964
|
+
}
|
|
965
|
+
|
|
929
966
|
// =====================================================================
|
|
930
967
|
// Role Query Management
|
|
931
968
|
// =====================================================================
|
|
@@ -980,7 +1017,12 @@ async function createRoleQuery(session, roleName) {
|
|
|
980
1017
|
claudeSessionId: savedSessionId,
|
|
981
1018
|
lastCostUsd: 0,
|
|
982
1019
|
lastInputTokens: 0,
|
|
983
|
-
lastOutputTokens: 0
|
|
1020
|
+
lastOutputTokens: 0,
|
|
1021
|
+
consecutiveErrors: 0,
|
|
1022
|
+
lastDispatchContent: null,
|
|
1023
|
+
lastDispatchFrom: null,
|
|
1024
|
+
lastDispatchTaskId: null,
|
|
1025
|
+
lastDispatchTaskTitle: null
|
|
984
1026
|
};
|
|
985
1027
|
|
|
986
1028
|
session.roleStates.set(roleName, roleState);
|
|
@@ -1241,6 +1283,7 @@ async function processRoleOutput(session, roleName, roleQuery, roleState) {
|
|
|
1241
1283
|
} else if (message.type === 'result') {
|
|
1242
1284
|
// ★ Turn 完成!
|
|
1243
1285
|
console.log(`[Crew] ${roleName} turn completed`);
|
|
1286
|
+
roleState.consecutiveErrors = 0;
|
|
1244
1287
|
|
|
1245
1288
|
// ★ 修复2: 反向搜索该角色最后一条 streaming 消息并结束
|
|
1246
1289
|
endRoleStreaming(session, roleName);
|
|
@@ -1325,21 +1368,80 @@ async function processRoleOutput(session, roleName, roleQuery, roleState) {
|
|
|
1325
1368
|
}
|
|
1326
1369
|
} else {
|
|
1327
1370
|
console.error(`[Crew] ${roleName} error:`, error.message);
|
|
1328
|
-
|
|
1329
|
-
|
|
1330
|
-
|
|
1331
|
-
|
|
1371
|
+
|
|
1372
|
+
// Step 1: 清理 roleState(防止后续写入死进程)
|
|
1373
|
+
endRoleStreaming(session, roleName);
|
|
1374
|
+
roleState.query = null;
|
|
1375
|
+
roleState.inputStream = null;
|
|
1376
|
+
roleState.turnActive = false;
|
|
1377
|
+
roleState.accumulatedText = '';
|
|
1378
|
+
|
|
1379
|
+
// Step 2: 错误分类
|
|
1380
|
+
const classification = classifyRoleError(error);
|
|
1381
|
+
roleState.consecutiveErrors++;
|
|
1382
|
+
|
|
1383
|
+
// Step 3: 通知前端
|
|
1384
|
+
sendCrewMessage({
|
|
1385
|
+
type: 'crew_role_error',
|
|
1386
|
+
sessionId: session.id,
|
|
1387
|
+
role: roleName,
|
|
1388
|
+
error: error.message.substring(0, 500),
|
|
1389
|
+
reason: classification.reason,
|
|
1390
|
+
recoverable: classification.recoverable,
|
|
1391
|
+
retryCount: roleState.consecutiveErrors
|
|
1392
|
+
});
|
|
1393
|
+
sendStatusUpdate(session);
|
|
1394
|
+
|
|
1395
|
+
// Step 4: 判断是否重试
|
|
1396
|
+
const MAX_RETRIES = 3;
|
|
1397
|
+
if (!classification.recoverable || roleState.consecutiveErrors > MAX_RETRIES) {
|
|
1398
|
+
const exhausted = roleState.consecutiveErrors > MAX_RETRIES;
|
|
1399
|
+
const errDetail = exhausted
|
|
1400
|
+
? `角色 ${roleName} 连续 ${MAX_RETRIES} 次错误后停止重试。最后错误: ${error.message}`
|
|
1401
|
+
: `角色 ${roleName} 不可恢复错误: ${error.message}`;
|
|
1402
|
+
if (roleName !== session.decisionMaker) {
|
|
1403
|
+
await dispatchToRole(session, session.decisionMaker, errDetail, 'system');
|
|
1404
|
+
} else {
|
|
1405
|
+
session.status = 'waiting_human';
|
|
1406
|
+
sendCrewMessage({
|
|
1407
|
+
type: 'crew_human_needed',
|
|
1408
|
+
sessionId: session.id,
|
|
1409
|
+
fromRole: roleName,
|
|
1410
|
+
reason: 'error',
|
|
1411
|
+
message: errDetail
|
|
1412
|
+
});
|
|
1413
|
+
sendStatusUpdate(session);
|
|
1414
|
+
}
|
|
1415
|
+
return;
|
|
1416
|
+
}
|
|
1417
|
+
|
|
1418
|
+
// Step 5: 可恢复 → 自动重建并重试
|
|
1419
|
+
console.log(`[Crew] ${roleName} attempting recovery (${classification.reason}), retry ${roleState.consecutiveErrors}/${MAX_RETRIES}`);
|
|
1420
|
+
|
|
1421
|
+
sendCrewOutput(session, 'system', 'system', {
|
|
1422
|
+
type: 'assistant',
|
|
1423
|
+
message: { role: 'assistant', content: [{
|
|
1424
|
+
type: 'text',
|
|
1425
|
+
text: `${roleName} 遇到 ${classification.reason},正在自动恢复 (${roleState.consecutiveErrors}/${MAX_RETRIES})...`
|
|
1426
|
+
}] }
|
|
1427
|
+
});
|
|
1428
|
+
|
|
1429
|
+
if (roleState.lastDispatchContent) {
|
|
1430
|
+
if (classification.skipResume) {
|
|
1431
|
+
await clearRoleSessionId(session.sharedDir, roleName);
|
|
1432
|
+
}
|
|
1433
|
+
await dispatchToRole(
|
|
1434
|
+
session, roleName,
|
|
1435
|
+
roleState.lastDispatchContent,
|
|
1436
|
+
roleState.lastDispatchFrom || 'system',
|
|
1437
|
+
roleState.lastDispatchTaskId,
|
|
1438
|
+
roleState.lastDispatchTaskTitle
|
|
1439
|
+
);
|
|
1332
1440
|
} else {
|
|
1333
|
-
|
|
1334
|
-
|
|
1335
|
-
|
|
1336
|
-
|
|
1337
|
-
fromRole: roleName,
|
|
1338
|
-
reason: 'error',
|
|
1339
|
-
message: `决策者 ${roleName} 发生错误: ${error.message}`
|
|
1340
|
-
});
|
|
1341
|
-
session.status = 'waiting_human';
|
|
1342
|
-
sendStatusUpdate(session);
|
|
1441
|
+
const msg = `角色 ${roleName} 已恢复(${classification.reason}),但无待重试消息。`;
|
|
1442
|
+
if (roleName !== session.decisionMaker) {
|
|
1443
|
+
await dispatchToRole(session, session.decisionMaker, msg, 'system');
|
|
1444
|
+
}
|
|
1343
1445
|
}
|
|
1344
1446
|
}
|
|
1345
1447
|
}
|
|
@@ -1491,6 +1593,10 @@ async function dispatchToRole(session, roleName, content, fromSource, taskId, ta
|
|
|
1491
1593
|
});
|
|
1492
1594
|
|
|
1493
1595
|
// 发送
|
|
1596
|
+
roleState.lastDispatchContent = content;
|
|
1597
|
+
roleState.lastDispatchFrom = fromSource;
|
|
1598
|
+
roleState.lastDispatchTaskId = taskId || null;
|
|
1599
|
+
roleState.lastDispatchTaskTitle = taskTitle || null;
|
|
1494
1600
|
roleState.turnActive = true;
|
|
1495
1601
|
roleState.accumulatedText = '';
|
|
1496
1602
|
roleState.inputStream.enqueue({
|
|
@@ -1498,6 +1604,7 @@ async function dispatchToRole(session, roleName, content, fromSource, taskId, ta
|
|
|
1498
1604
|
message: { role: 'user', content }
|
|
1499
1605
|
});
|
|
1500
1606
|
|
|
1607
|
+
sendStatusUpdate(session);
|
|
1501
1608
|
console.log(`[Crew] Dispatched to ${roleName} from ${fromSource}${taskId ? ` (task: ${taskId})` : ''}`);
|
|
1502
1609
|
}
|
|
1503
1610
|
|