@yeaft/webchat-agent 0.0.171 → 0.0.173
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 +160 -32
- 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
|
|
|
@@ -1539,6 +1524,10 @@ async function processRoleOutput(session, roleName, roleQuery, roleState) {
|
|
|
1539
1524
|
console.warn(`[Crew] Route execution failed:`, r.reason);
|
|
1540
1525
|
}
|
|
1541
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);
|
|
1542
1531
|
}
|
|
1543
1532
|
return; // abort 后 query 已清空,退出 processRoleOutput
|
|
1544
1533
|
}
|
|
@@ -1558,6 +1547,10 @@ async function processRoleOutput(session, roleName, roleQuery, roleState) {
|
|
|
1558
1547
|
console.warn(`[Crew] Route execution failed:`, r.reason);
|
|
1559
1548
|
}
|
|
1560
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);
|
|
1561
1554
|
}
|
|
1562
1555
|
continue; // 不要重复处理这个 compact result
|
|
1563
1556
|
}
|
|
@@ -1741,23 +1734,46 @@ async function processRoleOutput(session, roleName, roleQuery, roleState) {
|
|
|
1741
1734
|
});
|
|
1742
1735
|
|
|
1743
1736
|
if (roleState.lastDispatchContent) {
|
|
1744
|
-
|
|
1745
|
-
|
|
1746
|
-
// ★ context 超限时精简重派内容
|
|
1747
|
-
if (classification.needContentTrim) {
|
|
1748
|
-
retryContent = trimContentForRetry(retryContent);
|
|
1749
|
-
}
|
|
1750
|
-
|
|
1751
|
-
if (classification.skipResume) {
|
|
1737
|
+
// ★ context_exceeded: clear sessionId → rebuild query → /compact → 重派
|
|
1738
|
+
if (classification.reason === 'context_exceeded') {
|
|
1752
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
|
+
);
|
|
1753
1776
|
}
|
|
1754
|
-
await dispatchToRole(
|
|
1755
|
-
session, roleName,
|
|
1756
|
-
retryContent,
|
|
1757
|
-
roleState.lastDispatchFrom || 'system',
|
|
1758
|
-
roleState.lastDispatchTaskId,
|
|
1759
|
-
roleState.lastDispatchTaskTitle
|
|
1760
|
-
);
|
|
1761
1777
|
} else {
|
|
1762
1778
|
const msg = `角色 ${roleName} 已恢复(${classification.reason}),但无待重试消息。`;
|
|
1763
1779
|
if (roleName !== session.decisionMaker) {
|
|
@@ -2058,6 +2074,112 @@ function findActiveRole(session) {
|
|
|
2058
2074
|
// Control Operations
|
|
2059
2075
|
// =====================================================================
|
|
2060
2076
|
|
|
2077
|
+
/**
|
|
2078
|
+
* 清空单个角色的对话(重置为全新状态)
|
|
2079
|
+
*/
|
|
2080
|
+
async function clearSingleRole(session, roleName) {
|
|
2081
|
+
const roleState = session.roleStates.get(roleName);
|
|
2082
|
+
|
|
2083
|
+
// 如果角色正在 streaming,先 abort
|
|
2084
|
+
if (roleState) {
|
|
2085
|
+
if (roleState.abortController) {
|
|
2086
|
+
roleState.abortController.abort();
|
|
2087
|
+
}
|
|
2088
|
+
roleState.query = null;
|
|
2089
|
+
roleState.inputStream = null;
|
|
2090
|
+
roleState.turnActive = false;
|
|
2091
|
+
roleState._compacting = false;
|
|
2092
|
+
roleState._compactSummaryPending = false;
|
|
2093
|
+
roleState._pendingCompactRoutes = null;
|
|
2094
|
+
roleState._pendingDispatch = null;
|
|
2095
|
+
roleState._fromRole = null;
|
|
2096
|
+
roleState.claudeSessionId = null;
|
|
2097
|
+
roleState.consecutiveErrors = 0;
|
|
2098
|
+
roleState.accumulatedText = '';
|
|
2099
|
+
roleState.lastDispatchContent = null;
|
|
2100
|
+
roleState.lastDispatchFrom = null;
|
|
2101
|
+
roleState.lastDispatchTaskId = null;
|
|
2102
|
+
roleState.lastDispatchTaskTitle = null;
|
|
2103
|
+
}
|
|
2104
|
+
|
|
2105
|
+
// 清除持久化的 sessionId
|
|
2106
|
+
await clearRoleSessionId(session.sharedDir, roleName);
|
|
2107
|
+
|
|
2108
|
+
sendCrewMessage({
|
|
2109
|
+
type: 'crew_role_compact',
|
|
2110
|
+
sessionId: session.id,
|
|
2111
|
+
role: roleName,
|
|
2112
|
+
status: 'cleared'
|
|
2113
|
+
});
|
|
2114
|
+
|
|
2115
|
+
sendCrewOutput(session, 'system', 'system', {
|
|
2116
|
+
type: 'assistant',
|
|
2117
|
+
message: { role: 'assistant', content: [{ type: 'text', text: `${roleName} 对话已清空` }] }
|
|
2118
|
+
});
|
|
2119
|
+
sendStatusUpdate(session);
|
|
2120
|
+
console.log(`[Crew] Role ${roleName} cleared`);
|
|
2121
|
+
}
|
|
2122
|
+
|
|
2123
|
+
/**
|
|
2124
|
+
* 手动压缩指定角色的上下文
|
|
2125
|
+
* - 无活跃 query → 重建 query 后发 /compact
|
|
2126
|
+
* - 有 query 且非 turnActive → 直接发 /compact
|
|
2127
|
+
* - turnActive → 提示用户先停止该角色
|
|
2128
|
+
*/
|
|
2129
|
+
async function compactRole(session, roleName) {
|
|
2130
|
+
const roleState = session.roleStates.get(roleName);
|
|
2131
|
+
|
|
2132
|
+
// Case 1: 角色正在 streaming,不能 compact
|
|
2133
|
+
if (roleState?.turnActive) {
|
|
2134
|
+
sendCrewMessage({
|
|
2135
|
+
type: 'crew_role_compact',
|
|
2136
|
+
sessionId: session.id,
|
|
2137
|
+
role: roleName,
|
|
2138
|
+
status: 'rejected',
|
|
2139
|
+
reason: '角色正在工作中,请先停止该角色再压缩上下文'
|
|
2140
|
+
});
|
|
2141
|
+
return;
|
|
2142
|
+
}
|
|
2143
|
+
|
|
2144
|
+
// Case 2: 已经在 compacting
|
|
2145
|
+
if (roleState?._compacting) {
|
|
2146
|
+
sendCrewMessage({
|
|
2147
|
+
type: 'crew_role_compact',
|
|
2148
|
+
sessionId: session.id,
|
|
2149
|
+
role: roleName,
|
|
2150
|
+
status: 'rejected',
|
|
2151
|
+
reason: '该角色正在压缩中,请等待完成'
|
|
2152
|
+
});
|
|
2153
|
+
return;
|
|
2154
|
+
}
|
|
2155
|
+
|
|
2156
|
+
// Case 3: 无活跃 query → 重建
|
|
2157
|
+
let state = roleState;
|
|
2158
|
+
if (!state || !state.query || !state.inputStream) {
|
|
2159
|
+
console.log(`[Crew] ${roleName} has no active query, rebuilding for compact`);
|
|
2160
|
+
state = await createRoleQuery(session, roleName);
|
|
2161
|
+
}
|
|
2162
|
+
|
|
2163
|
+
// 发送 /compact
|
|
2164
|
+
console.log(`[Crew] Manual compact requested for ${roleName}`);
|
|
2165
|
+
state._compacting = true;
|
|
2166
|
+
state._compactSummaryPending = false;
|
|
2167
|
+
state._pendingCompactRoutes = null;
|
|
2168
|
+
state._fromRole = null;
|
|
2169
|
+
|
|
2170
|
+
state.inputStream.enqueue({
|
|
2171
|
+
type: 'user',
|
|
2172
|
+
message: { role: 'user', content: '/compact' }
|
|
2173
|
+
});
|
|
2174
|
+
|
|
2175
|
+
sendCrewMessage({
|
|
2176
|
+
type: 'crew_role_compact',
|
|
2177
|
+
sessionId: session.id,
|
|
2178
|
+
role: roleName,
|
|
2179
|
+
status: 'compacting'
|
|
2180
|
+
});
|
|
2181
|
+
}
|
|
2182
|
+
|
|
2061
2183
|
/**
|
|
2062
2184
|
* 处理控制命令
|
|
2063
2185
|
*/
|
|
@@ -2084,6 +2206,12 @@ export async function handleCrewControl(msg) {
|
|
|
2084
2206
|
await interruptRole(session, targetRole, msg.content, 'human');
|
|
2085
2207
|
}
|
|
2086
2208
|
break;
|
|
2209
|
+
case 'compact_role':
|
|
2210
|
+
if (targetRole) await compactRole(session, targetRole);
|
|
2211
|
+
break;
|
|
2212
|
+
case 'clear_role':
|
|
2213
|
+
if (targetRole) await clearSingleRole(session, targetRole);
|
|
2214
|
+
break;
|
|
2087
2215
|
case 'stop_all':
|
|
2088
2216
|
await stopAll(session);
|
|
2089
2217
|
break;
|