@yeaft/webchat-agent 0.0.170 → 0.0.172
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 +114 -34
- package/package.json +1 -1
package/crew.js
CHANGED
|
@@ -1090,7 +1090,7 @@ async function clearRoleSessionId(sharedDir, roleName) {
|
|
|
1090
1090
|
function classifyRoleError(error) {
|
|
1091
1091
|
const msg = error.message || '';
|
|
1092
1092
|
if (/context.*(window|limit|exceeded)|token.*limit|too.*(long|large)|max.*token/i.test(msg)) {
|
|
1093
|
-
return { recoverable: true, reason: 'context_exceeded', skipResume: true
|
|
1093
|
+
return { recoverable: true, reason: 'context_exceeded', skipResume: true };
|
|
1094
1094
|
}
|
|
1095
1095
|
if (/compact|compress|context.*reduc/i.test(msg)) {
|
|
1096
1096
|
return { recoverable: true, reason: 'compact_failed', skipResume: true };
|
|
@@ -1107,22 +1107,6 @@ function classifyRoleError(error) {
|
|
|
1107
1107
|
return { recoverable: true, reason: 'unknown', skipResume: false };
|
|
1108
1108
|
}
|
|
1109
1109
|
|
|
1110
|
-
/**
|
|
1111
|
-
* context 超限时精简重派内容
|
|
1112
|
-
*/
|
|
1113
|
-
function trimContentForRetry(content) {
|
|
1114
|
-
if (typeof content === 'string' && content.length > 2000) {
|
|
1115
|
-
return `[注意: 上一轮因 context 超限被截断重发]\n\n${content.substring(0, 2000)}\n\n[内容已截断,请基于已知信息继续工作]`;
|
|
1116
|
-
}
|
|
1117
|
-
if (Array.isArray(content)) {
|
|
1118
|
-
return content.filter(b => b.type === 'text').map(b => ({
|
|
1119
|
-
...b,
|
|
1120
|
-
text: b.text.length > 2000 ? b.text.substring(0, 2000) + '\n[已截断]' : b.text
|
|
1121
|
-
}));
|
|
1122
|
-
}
|
|
1123
|
-
return content;
|
|
1124
|
-
}
|
|
1125
|
-
|
|
1126
1110
|
// =====================================================================
|
|
1127
1111
|
// Role Query Management
|
|
1128
1112
|
// =====================================================================
|
|
@@ -1187,6 +1171,7 @@ async function createRoleQuery(session, roleName) {
|
|
|
1187
1171
|
_compacting: false, // 是否正在 compact
|
|
1188
1172
|
_compactSummaryPending: false, // 是否等待过滤 compact summary
|
|
1189
1173
|
_pendingCompactRoutes: null, // compact 期间缓存的待执行路由 Array|null
|
|
1174
|
+
_pendingDispatch: null, // compact 完成后待重派的内容 { content, from, taskId, taskTitle }
|
|
1190
1175
|
_fromRole: null // 缓存路由的来源角色
|
|
1191
1176
|
};
|
|
1192
1177
|
|
|
@@ -1337,13 +1322,14 @@ summary: 请实现注册页面,包括邮箱验证
|
|
|
1337
1322
|
|
|
1338
1323
|
\`\`\`
|
|
1339
1324
|
---TASKS---
|
|
1340
|
-
- [ ] 任务描述 @角色name
|
|
1341
|
-
- [x] 已完成的任务 @角色name
|
|
1325
|
+
- [ ] 任务描述 #task-1 @角色name
|
|
1326
|
+
- [x] 已完成的任务 #task-2 @角色name
|
|
1342
1327
|
---END_TASKS---
|
|
1343
1328
|
\`\`\`
|
|
1344
1329
|
|
|
1345
1330
|
注意:
|
|
1346
1331
|
- 每行一个任务,[ ] 表示待办,[x] 表示已完成
|
|
1332
|
+
- #taskId 标注对应的 feature ID(如 #task-1),用于精确关联任务完成状态
|
|
1347
1333
|
- @角色name 标注负责人(可选)
|
|
1348
1334
|
- 后续回复中可更新 TASKS 块(标记完成的任务)
|
|
1349
1335
|
- TASKS 块不需要在回复最末尾,可以放在任意位置`;
|
|
@@ -1538,6 +1524,10 @@ async function processRoleOutput(session, roleName, roleQuery, roleState) {
|
|
|
1538
1524
|
console.warn(`[Crew] Route execution failed:`, r.reason);
|
|
1539
1525
|
}
|
|
1540
1526
|
}
|
|
1527
|
+
} else if (roleState._pendingDispatch) {
|
|
1528
|
+
const pd = roleState._pendingDispatch;
|
|
1529
|
+
roleState._pendingDispatch = null;
|
|
1530
|
+
await dispatchToRole(session, roleName, pd.content, pd.from, pd.taskId, pd.taskTitle);
|
|
1541
1531
|
}
|
|
1542
1532
|
return; // abort 后 query 已清空,退出 processRoleOutput
|
|
1543
1533
|
}
|
|
@@ -1557,6 +1547,10 @@ async function processRoleOutput(session, roleName, roleQuery, roleState) {
|
|
|
1557
1547
|
console.warn(`[Crew] Route execution failed:`, r.reason);
|
|
1558
1548
|
}
|
|
1559
1549
|
}
|
|
1550
|
+
} else if (roleState._pendingDispatch) {
|
|
1551
|
+
const pd = roleState._pendingDispatch;
|
|
1552
|
+
roleState._pendingDispatch = null;
|
|
1553
|
+
await dispatchToRole(session, roleName, pd.content, pd.from, pd.taskId, pd.taskTitle);
|
|
1560
1554
|
}
|
|
1561
1555
|
continue; // 不要重复处理这个 compact result
|
|
1562
1556
|
}
|
|
@@ -1740,23 +1734,46 @@ async function processRoleOutput(session, roleName, roleQuery, roleState) {
|
|
|
1740
1734
|
});
|
|
1741
1735
|
|
|
1742
1736
|
if (roleState.lastDispatchContent) {
|
|
1743
|
-
|
|
1744
|
-
|
|
1745
|
-
// ★ context 超限时精简重派内容
|
|
1746
|
-
if (classification.needContentTrim) {
|
|
1747
|
-
retryContent = trimContentForRetry(retryContent);
|
|
1748
|
-
}
|
|
1749
|
-
|
|
1750
|
-
if (classification.skipResume) {
|
|
1737
|
+
// ★ context_exceeded: clear sessionId → rebuild query → /compact → 重派
|
|
1738
|
+
if (classification.reason === 'context_exceeded') {
|
|
1751
1739
|
await clearRoleSessionId(session.sharedDir, roleName);
|
|
1740
|
+
const newState = await createRoleQuery(session, roleName);
|
|
1741
|
+
|
|
1742
|
+
// 缓存待重派内容,compact 完成后自动发送
|
|
1743
|
+
newState._pendingDispatch = {
|
|
1744
|
+
content: roleState.lastDispatchContent,
|
|
1745
|
+
from: roleState.lastDispatchFrom || 'system',
|
|
1746
|
+
taskId: roleState.lastDispatchTaskId,
|
|
1747
|
+
taskTitle: roleState.lastDispatchTaskTitle
|
|
1748
|
+
};
|
|
1749
|
+
newState._compacting = true;
|
|
1750
|
+
newState._compactSummaryPending = false;
|
|
1751
|
+
newState.consecutiveErrors = roleState.consecutiveErrors;
|
|
1752
|
+
|
|
1753
|
+
newState.inputStream.enqueue({
|
|
1754
|
+
type: 'user',
|
|
1755
|
+
message: { role: 'user', content: '/compact' }
|
|
1756
|
+
});
|
|
1757
|
+
|
|
1758
|
+
sendCrewMessage({
|
|
1759
|
+
type: 'crew_role_compact',
|
|
1760
|
+
sessionId: session.id,
|
|
1761
|
+
role: roleName,
|
|
1762
|
+
status: 'compacting'
|
|
1763
|
+
});
|
|
1764
|
+
} else {
|
|
1765
|
+
// 其他可恢复错误:原有重派逻辑
|
|
1766
|
+
if (classification.skipResume) {
|
|
1767
|
+
await clearRoleSessionId(session.sharedDir, roleName);
|
|
1768
|
+
}
|
|
1769
|
+
await dispatchToRole(
|
|
1770
|
+
session, roleName,
|
|
1771
|
+
roleState.lastDispatchContent,
|
|
1772
|
+
roleState.lastDispatchFrom || 'system',
|
|
1773
|
+
roleState.lastDispatchTaskId,
|
|
1774
|
+
roleState.lastDispatchTaskTitle
|
|
1775
|
+
);
|
|
1752
1776
|
}
|
|
1753
|
-
await dispatchToRole(
|
|
1754
|
-
session, roleName,
|
|
1755
|
-
retryContent,
|
|
1756
|
-
roleState.lastDispatchFrom || 'system',
|
|
1757
|
-
roleState.lastDispatchTaskId,
|
|
1758
|
-
roleState.lastDispatchTaskTitle
|
|
1759
|
-
);
|
|
1760
1777
|
} else {
|
|
1761
1778
|
const msg = `角色 ${roleName} 已恢复(${classification.reason}),但无待重试消息。`;
|
|
1762
1779
|
if (roleName !== session.decisionMaker) {
|
|
@@ -2057,6 +2074,66 @@ function findActiveRole(session) {
|
|
|
2057
2074
|
// Control Operations
|
|
2058
2075
|
// =====================================================================
|
|
2059
2076
|
|
|
2077
|
+
/**
|
|
2078
|
+
* 手动压缩指定角色的上下文
|
|
2079
|
+
* - 无活跃 query → 重建 query 后发 /compact
|
|
2080
|
+
* - 有 query 且非 turnActive → 直接发 /compact
|
|
2081
|
+
* - turnActive → 提示用户先停止该角色
|
|
2082
|
+
*/
|
|
2083
|
+
async function compactRole(session, roleName) {
|
|
2084
|
+
const roleState = session.roleStates.get(roleName);
|
|
2085
|
+
|
|
2086
|
+
// Case 1: 角色正在 streaming,不能 compact
|
|
2087
|
+
if (roleState?.turnActive) {
|
|
2088
|
+
sendCrewMessage({
|
|
2089
|
+
type: 'crew_role_compact',
|
|
2090
|
+
sessionId: session.id,
|
|
2091
|
+
role: roleName,
|
|
2092
|
+
status: 'rejected',
|
|
2093
|
+
reason: '角色正在工作中,请先停止该角色再压缩上下文'
|
|
2094
|
+
});
|
|
2095
|
+
return;
|
|
2096
|
+
}
|
|
2097
|
+
|
|
2098
|
+
// Case 2: 已经在 compacting
|
|
2099
|
+
if (roleState?._compacting) {
|
|
2100
|
+
sendCrewMessage({
|
|
2101
|
+
type: 'crew_role_compact',
|
|
2102
|
+
sessionId: session.id,
|
|
2103
|
+
role: roleName,
|
|
2104
|
+
status: 'rejected',
|
|
2105
|
+
reason: '该角色正在压缩中,请等待完成'
|
|
2106
|
+
});
|
|
2107
|
+
return;
|
|
2108
|
+
}
|
|
2109
|
+
|
|
2110
|
+
// Case 3: 无活跃 query → 重建
|
|
2111
|
+
let state = roleState;
|
|
2112
|
+
if (!state || !state.query || !state.inputStream) {
|
|
2113
|
+
console.log(`[Crew] ${roleName} has no active query, rebuilding for compact`);
|
|
2114
|
+
state = await createRoleQuery(session, roleName);
|
|
2115
|
+
}
|
|
2116
|
+
|
|
2117
|
+
// 发送 /compact
|
|
2118
|
+
console.log(`[Crew] Manual compact requested for ${roleName}`);
|
|
2119
|
+
state._compacting = true;
|
|
2120
|
+
state._compactSummaryPending = false;
|
|
2121
|
+
state._pendingCompactRoutes = null;
|
|
2122
|
+
state._fromRole = null;
|
|
2123
|
+
|
|
2124
|
+
state.inputStream.enqueue({
|
|
2125
|
+
type: 'user',
|
|
2126
|
+
message: { role: 'user', content: '/compact' }
|
|
2127
|
+
});
|
|
2128
|
+
|
|
2129
|
+
sendCrewMessage({
|
|
2130
|
+
type: 'crew_role_compact',
|
|
2131
|
+
sessionId: session.id,
|
|
2132
|
+
role: roleName,
|
|
2133
|
+
status: 'compacting'
|
|
2134
|
+
});
|
|
2135
|
+
}
|
|
2136
|
+
|
|
2060
2137
|
/**
|
|
2061
2138
|
* 处理控制命令
|
|
2062
2139
|
*/
|
|
@@ -2083,6 +2160,9 @@ export async function handleCrewControl(msg) {
|
|
|
2083
2160
|
await interruptRole(session, targetRole, msg.content, 'human');
|
|
2084
2161
|
}
|
|
2085
2162
|
break;
|
|
2163
|
+
case 'compact_role':
|
|
2164
|
+
if (targetRole) await compactRole(session, targetRole);
|
|
2165
|
+
break;
|
|
2086
2166
|
case 'stop_all':
|
|
2087
2167
|
await stopAll(session);
|
|
2088
2168
|
break;
|