@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.
Files changed (2) hide show
  1. package/crew.js +160 -32
  2. 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, needContentTrim: 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
- let retryContent = roleState.lastDispatchContent;
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;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@yeaft/webchat-agent",
3
- "version": "0.0.171",
3
+ "version": "0.0.173",
4
4
  "description": "Remote agent for Yeaft WebChat — connects worker machines to the central server",
5
5
  "main": "index.js",
6
6
  "type": "module",