@yeaft/webchat-agent 0.0.59 → 0.0.61

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 (3) hide show
  1. package/connection.js +6 -1
  2. package/crew.js +74 -9
  3. package/package.json +1 -1
package/connection.js CHANGED
@@ -23,7 +23,7 @@ import {
23
23
  import {
24
24
  createCrewSession, handleCrewHumanInput, handleCrewControl,
25
25
  addRoleToSession, removeRoleFromSession,
26
- handleListCrewSessions, resumeCrewSession
26
+ handleListCrewSessions, resumeCrewSession, removeFromCrewIndex
27
27
  } from './crew.js';
28
28
 
29
29
  // 需要在断连期间缓冲的消息类型(Claude 输出相关的关键消息)
@@ -295,6 +295,11 @@ async function handleMessage(msg) {
295
295
  await resumeCrewSession(msg);
296
296
  break;
297
297
 
298
+ case 'delete_crew_session':
299
+ await removeFromCrewIndex(msg.sessionId);
300
+ (await import('./conversation.js')).sendConversationList();
301
+ break;
302
+
298
303
  // Port proxy
299
304
  case 'proxy_request':
300
305
  handleProxyHttpRequest(msg);
package/crew.js CHANGED
@@ -50,6 +50,9 @@ function sessionToIndexEntry(session) {
50
50
  projectDir: session.projectDir,
51
51
  sharedDir: session.sharedDir,
52
52
  status: session.status,
53
+ goal: session.goal,
54
+ userId: session.userId,
55
+ username: session.username,
53
56
  createdAt: session.createdAt,
54
57
  updatedAt: Date.now()
55
58
  };
@@ -63,6 +66,15 @@ async function upsertCrewIndex(session) {
63
66
  await saveCrewIndex(index);
64
67
  }
65
68
 
69
+ export async function removeFromCrewIndex(sessionId) {
70
+ const index = await loadCrewIndex();
71
+ const filtered = index.filter(e => e.sessionId !== sessionId);
72
+ if (filtered.length !== index.length) {
73
+ await saveCrewIndex(filtered);
74
+ console.log(`[Crew] Removed session ${sessionId} from index`);
75
+ }
76
+ }
77
+
66
78
  // =====================================================================
67
79
  // Session Metadata (.crew/session.json)
68
80
  // =====================================================================
@@ -167,6 +179,7 @@ export async function resumeCrewSession(msg) {
167
179
  messageHistory: [],
168
180
  humanMessageQueue: [],
169
181
  waitingHumanContext: null,
182
+ pendingRoute: null,
170
183
  userId: userId || meta.userId,
171
184
  username: username || meta.username,
172
185
  createdAt: meta.createdAt || Date.now()
@@ -244,6 +257,7 @@ export async function createCrewSession(msg) {
244
257
  messageHistory: [], // 群聊消息历史
245
258
  humanMessageQueue: [], // 人的消息排队
246
259
  waitingHumanContext: null, // { fromRole, reason, message }
260
+ pendingRoute: null, // { fromRole, route } — 暂停时未完成的路由
247
261
  userId,
248
262
  username,
249
263
  createdAt: Date.now()
@@ -684,8 +698,8 @@ ${allRoles.map(r => `- ${r.icon} ${r.name}: ${r.displayName} - ${r.description}`
684
698
  async function processRoleOutput(session, roleName, roleQuery, roleState) {
685
699
  try {
686
700
  for await (const message of roleQuery) {
687
- // 检查 session 是否已停止
688
- if (session.status === 'stopped') break;
701
+ // 检查 session 是否已停止或暂停
702
+ if (session.status === 'stopped' || session.status === 'paused') break;
689
703
 
690
704
  if (message.type === 'system' && message.subtype === 'init') {
691
705
  roleState.claudeSessionId = message.session_id;
@@ -707,14 +721,16 @@ async function processRoleOutput(session, roleName, roleQuery, roleState) {
707
721
  if (block.type === 'text') {
708
722
  roleState.accumulatedText += block.text;
709
723
  } else if (block.type === 'tool_use') {
710
- // 转发 tool use
724
+ // 转发 tool use + 记录当前工具
725
+ roleState.currentTool = block.name;
711
726
  sendCrewOutput(session, roleName, 'tool_use', message);
712
727
  }
713
728
  }
714
729
  }
715
730
  }
716
731
  } else if (message.type === 'user') {
717
- // tool results
732
+ // tool results — clear currentTool
733
+ roleState.currentTool = null;
718
734
  sendCrewOutput(session, roleName, 'tool_result', message);
719
735
  } else if (message.type === 'result') {
720
736
  // ★ Turn 完成!
@@ -759,6 +775,15 @@ async function processRoleOutput(session, roleName, roleQuery, roleState) {
759
775
  } catch (error) {
760
776
  if (error.name === 'AbortError') {
761
777
  console.log(`[Crew] ${roleName} aborted`);
778
+ // 暂停时:检查已累积的文本中是否有 route,保存为 pendingRoute
779
+ if (session.status === 'paused' && roleState.accumulatedText) {
780
+ const route = parseRoute(roleState.accumulatedText);
781
+ if (route && !session.pendingRoute) {
782
+ session.pendingRoute = { fromRole: roleName, route };
783
+ console.log(`[Crew] Saved pending route from aborted ${roleName}: -> ${route.to}`);
784
+ }
785
+ roleState.accumulatedText = '';
786
+ }
762
787
  } else {
763
788
  console.error(`[Crew] ${roleName} error:`, error.message);
764
789
  // 通知决策者
@@ -839,6 +864,13 @@ async function executeRoute(session, fromRole, route) {
839
864
  return;
840
865
  }
841
866
 
867
+ // 如果 session 已暂停或停止,保存 pendingRoute 等恢复时重放
868
+ if (session.status === 'paused' || session.status === 'stopped') {
869
+ session.pendingRoute = { fromRole, route };
870
+ console.log(`[Crew] Session ${session.status}, route saved as pending: ${fromRole} -> ${to}`);
871
+ return;
872
+ }
873
+
842
874
  // 发送路由消息(UI 显示 → @xxx)
843
875
  sendCrewOutput(session, fromRole, 'route', null, { routeTo: to, routeSummary: summary });
844
876
 
@@ -1033,7 +1065,7 @@ export async function handleCrewControl(msg) {
1033
1065
 
1034
1066
  switch (action) {
1035
1067
  case 'pause':
1036
- pauseAll(session);
1068
+ await pauseAll(session);
1037
1069
  break;
1038
1070
  case 'resume':
1039
1071
  await resumeSession(session);
@@ -1051,10 +1083,28 @@ export async function handleCrewControl(msg) {
1051
1083
 
1052
1084
  /**
1053
1085
  * 暂停所有角色
1086
+ * abort 运行中的 query 并保存 sessionId,恢复时 resume
1054
1087
  */
1055
- function pauseAll(session) {
1088
+ async function pauseAll(session) {
1056
1089
  session.status = 'paused';
1057
- console.log(`[Crew] Session ${session.id} paused`);
1090
+
1091
+ // abort 所有运行中的角色,保存 sessionId 以便 resume
1092
+ for (const [roleName, roleState] of session.roleStates) {
1093
+ if (roleState.claudeSessionId) {
1094
+ await saveRoleSessionId(session.sharedDir, roleName, roleState.claudeSessionId)
1095
+ .catch(e => console.warn(`[Crew] Failed to save sessionId for ${roleName}:`, e.message));
1096
+ }
1097
+ if (roleState.abortController) {
1098
+ roleState.abortController.abort();
1099
+ }
1100
+ // 记录哪些角色在暂停时正在工作
1101
+ roleState.wasActive = roleState.turnActive;
1102
+ roleState.turnActive = false;
1103
+ roleState.query = null;
1104
+ roleState.inputStream = null;
1105
+ }
1106
+
1107
+ console.log(`[Crew] Session ${session.id} paused, all active queries aborted`);
1058
1108
 
1059
1109
  sendCrewOutput(session, 'system', 'system', {
1060
1110
  type: 'assistant',
@@ -1065,6 +1115,7 @@ function pauseAll(session) {
1065
1115
 
1066
1116
  /**
1067
1117
  * 恢复 session
1118
+ * 重新执行被暂停时保存的 pendingRoute
1068
1119
  */
1069
1120
  async function resumeSession(session) {
1070
1121
  if (session.status !== 'paused') return;
@@ -1078,7 +1129,16 @@ async function resumeSession(session) {
1078
1129
  });
1079
1130
  sendStatusUpdate(session);
1080
1131
 
1081
- // 恢复后检查是否有排队的人的消息
1132
+ // 恢复被中断的路由
1133
+ if (session.pendingRoute) {
1134
+ const { fromRole, route } = session.pendingRoute;
1135
+ session.pendingRoute = null;
1136
+ console.log(`[Crew] Replaying pending route: ${fromRole} -> ${route.to}`);
1137
+ await executeRoute(session, fromRole, route);
1138
+ return;
1139
+ }
1140
+
1141
+ // 没有 pendingRoute,检查排队的人的消息
1082
1142
  await processHumanQueue(session);
1083
1143
  }
1084
1144
 
@@ -1195,7 +1255,12 @@ function sendStatusUpdate(session) {
1195
1255
  })),
1196
1256
  activeRoles: Array.from(session.roleStates.entries())
1197
1257
  .filter(([, s]) => s.turnActive)
1198
- .map(([name]) => name)
1258
+ .map(([name]) => name),
1259
+ currentToolByRole: Object.fromEntries(
1260
+ Array.from(session.roleStates.entries())
1261
+ .filter(([, s]) => s.turnActive && s.currentTool)
1262
+ .map(([name, s]) => [name, s.currentTool])
1263
+ )
1199
1264
  });
1200
1265
 
1201
1266
  // 异步更新持久化
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@yeaft/webchat-agent",
3
- "version": "0.0.59",
3
+ "version": "0.0.61",
4
4
  "description": "Remote agent for Yeaft WebChat — connects worker machines to the central server",
5
5
  "main": "index.js",
6
6
  "type": "module",