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