@yeaft/webchat-agent 0.0.163 → 0.0.166

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/connection.js CHANGED
@@ -23,7 +23,7 @@ import {
23
23
  import {
24
24
  createCrewSession, handleCrewHumanInput, handleCrewControl,
25
25
  addRoleToSession, removeRoleFromSession,
26
- handleListCrewSessions, resumeCrewSession, removeFromCrewIndex
26
+ handleListCrewSessions, handleCheckCrewExists, resumeCrewSession, removeFromCrewIndex
27
27
  } from './crew.js';
28
28
 
29
29
  // 需要在断连期间缓冲的消息类型(Claude 输出相关的关键消息)
@@ -299,6 +299,10 @@ async function handleMessage(msg) {
299
299
  await handleListCrewSessions(msg);
300
300
  break;
301
301
 
302
+ case 'check_crew_exists':
303
+ await handleCheckCrewExists(msg);
304
+ break;
305
+
302
306
  case 'resume_crew_session':
303
307
  await resumeCrewSession(msg);
304
308
  break;
package/crew.js CHANGED
@@ -325,7 +325,8 @@ async function saveSessionMeta(session) {
325
325
  agentId: session.agentId || null,
326
326
  costUsd: session.costUsd,
327
327
  totalInputTokens: session.totalInputTokens,
328
- totalOutputTokens: session.totalOutputTokens
328
+ totalOutputTokens: session.totalOutputTokens,
329
+ features: Array.from(session.features.values())
329
330
  };
330
331
  await fs.writeFile(join(session.sharedDir, 'session.json'), JSON.stringify(meta, null, 2));
331
332
  // 保存 UI 消息历史(用于恢复时重放)
@@ -382,6 +383,63 @@ export async function handleListCrewSessions(msg) {
382
383
  });
383
384
  }
384
385
 
386
+ /**
387
+ * 检查工作目录下是否存在 .crew 目录
388
+ */
389
+ export async function handleCheckCrewExists(msg) {
390
+ const { projectDir, requestId, _requestClientId } = msg;
391
+ if (!projectDir) {
392
+ ctx.sendToServer({
393
+ type: 'crew_exists_result',
394
+ requestId,
395
+ _requestClientId,
396
+ exists: false,
397
+ error: 'projectDir is required'
398
+ });
399
+ return;
400
+ }
401
+
402
+ const crewDir = join(projectDir, '.crew');
403
+ try {
404
+ const stat = await fs.stat(crewDir);
405
+ if (stat.isDirectory()) {
406
+ // 尝试读取 crew-session.json 获取 session 信息
407
+ let sessionInfo = null;
408
+ try {
409
+ const sessionPath = join(crewDir, 'crew-session.json');
410
+ const data = await fs.readFile(sessionPath, 'utf-8');
411
+ sessionInfo = JSON.parse(data);
412
+ } catch {
413
+ // crew-session.json 可能不存在,不影响
414
+ }
415
+ ctx.sendToServer({
416
+ type: 'crew_exists_result',
417
+ requestId,
418
+ _requestClientId,
419
+ exists: true,
420
+ projectDir,
421
+ sessionInfo
422
+ });
423
+ } else {
424
+ ctx.sendToServer({
425
+ type: 'crew_exists_result',
426
+ requestId,
427
+ _requestClientId,
428
+ exists: false,
429
+ projectDir
430
+ });
431
+ }
432
+ } catch {
433
+ ctx.sendToServer({
434
+ type: 'crew_exists_result',
435
+ requestId,
436
+ _requestClientId,
437
+ exists: false,
438
+ projectDir
439
+ });
440
+ }
441
+ }
442
+
385
443
  /**
386
444
  * 恢复已停止的 crew session
387
445
  */
@@ -463,6 +521,7 @@ export async function resumeCrewSession(msg) {
463
521
  humanMessageQueue: [],
464
522
  waitingHumanContext: null,
465
523
  pendingRoutes: [],
524
+ features: new Map((meta.features || []).map(f => [f.taskId, f])),
466
525
  userId: userId || meta.userId,
467
526
  username: username || meta.username,
468
527
  agentId: meta.agentId || ctx.CONFIG?.agentName || null,
@@ -583,6 +642,20 @@ export async function createCrewSession(msg) {
583
642
  }
584
643
  }
585
644
 
645
+ // 生成组名
646
+ const groupNames = {};
647
+ const maxGroup = Math.max(0, ...roles.map(r => r.groupIndex || 0));
648
+ for (let g = 1; g <= maxGroup; g++) {
649
+ const members = roles.filter(r => r.groupIndex === g);
650
+ const hasRev = members.some(r => r.roleType === 'reviewer');
651
+ const hasTest = members.some(r => r.roleType === 'tester');
652
+ if (hasRev && hasTest) {
653
+ groupNames[g] = maxGroup > 1 ? `全栈开发组 ${g}` : '全栈开发组';
654
+ } else {
655
+ groupNames[g] = maxGroup > 1 ? `开发组 ${g}` : '开发组';
656
+ }
657
+ }
658
+
586
659
  // 找到决策者
587
660
  const decisionMaker = roles.find(r => r.isDecisionMaker)?.name || roles[0]?.name || null;
588
661
 
@@ -607,6 +680,8 @@ export async function createCrewSession(msg) {
607
680
  humanMessageQueue: [], // 人的消息排队
608
681
  waitingHumanContext: null, // { fromRole, reason, message }
609
682
  pendingRoutes: [], // [{ fromRole, route }] — 暂停时未完成的路由
683
+ features: new Map(), // taskId → { taskId, taskTitle, createdAt } — 持久化 feature 列表
684
+ groupNames, // groupIndex → 组名
610
685
  userId,
611
686
  username,
612
687
  agentId: ctx.CONFIG?.agentName || null,
@@ -636,6 +711,7 @@ export async function createCrewSession(msg) {
636
711
  })),
637
712
  decisionMaker,
638
713
  maxRounds,
714
+ groupNames,
639
715
  userId,
640
716
  username
641
717
  });
@@ -886,18 +962,17 @@ ${roles.length > 0 ? roles.map(r => `- ${roleLabel(r)}(${r.name}): ${r.descripti
886
962
  2. 等待其他角色的产出但迟迟没有收到
887
963
  3. 任务描述不清楚或有歧义,无法判断正确做法
888
964
  4. 遇到超出自己职责范围的问题
889
- 5. 连续尝试5次相同操作仍然失败
890
- 上报时请说明:你在做什么任务、卡在哪里、你认为需要谁来协助。
965
+ 5. 连续尝试 2 次相同操作仍然失败
966
+ 上报时请说明:你在做什么任务、卡在哪里、你认为需要谁来协助。PM 会统筹全局,判断是分配给合适的人还是调整任务顺序。
891
967
 
892
968
  # Worktree 隔离规则
893
- - 多实例模式下,每个开发组(dev-N/rev-N/test-N)在独立的 git worktree 中工作
894
- - 每个角色必须在自己的 worktree 路径下操作代码,绝对不要操作项目主目录
895
- - 绝对禁止在其他开发组的 branch worktree 中操作代码
969
+ - dev/reviewer/tester 角色必须在各自分配的 worktree 中工作,绝对禁止在项目主目录或 main 分支上修改代码
970
+ - 每个角色的 CLAUDE.md 会标明「代码工作目录」,该路径就是你的 worktree,所有文件操作必须使用该路径
971
+ - PM designer 不使用 worktree,他们在项目主目录下以只读方式工作
972
+ - 绝对禁止在其他开发组的 worktree 中操作代码
896
973
  - 代码完成并通过 review 后,dev 自己提 PR 合并到 main 分支
897
974
  - PM 不做 cherry-pick,只负责打 tag
898
- - 合并完成后清理旧的 worktree
899
975
  - 每次新任务/新 feature 必须基于最新的 main 分支创建新的 worktree,确保在最新代码上开发
900
- - 禁止复用旧的 worktree 开发新任务,因为旧 worktree 的代码基线可能已过时
901
976
 
902
977
  ${sharedMemoryContent}`;
903
978
 
@@ -918,12 +993,9 @@ ${role.claudeMd || role.description}
918
993
  // 有独立 worktree 的角色,覆盖代码工作目录
919
994
  if (role.workDir) {
920
995
  claudeMd += `
921
- # 代码工作目录(重要!)
996
+ # 代码工作目录
922
997
  ${role.workDir}
923
- 所有代码操作必须在此 worktree 路径下进行。
924
- 绝对禁止直接操作项目主目录或其他组的 worktree,否则会覆盖其他开发组的修改。
925
- 代码完成并通过 review 后,自己提 PR 合并到 main。
926
- 此 worktree 仅用于当前任务,合并后会被清理,新任务会创建新的 worktree。
998
+ 所有代码操作请使用此路径。不要使用项目主目录。
927
999
  `;
928
1000
  }
929
1001
 
@@ -2103,6 +2175,11 @@ function sendCrewOutput(session, roleName, outputType, rawMessage, extra = {}) {
2103
2175
  ...extra
2104
2176
  });
2105
2177
 
2178
+ // ★ 累积 feature 到持久化列表
2179
+ if (taskId && taskTitle && !session.features.has(taskId)) {
2180
+ session.features.set(taskId, { taskId, taskTitle, createdAt: Date.now() });
2181
+ }
2182
+
2106
2183
  // ★ 记录精简 UI 消息用于恢复(跳过 tool_use/tool_result,只记录可见内容)
2107
2184
  if (outputType === 'text') {
2108
2185
  const content = rawMessage?.message?.content;
@@ -2257,7 +2334,9 @@ function sendStatusUpdate(session) {
2257
2334
  icon: r.icon,
2258
2335
  description: r.description,
2259
2336
  isDecisionMaker: r.isDecisionMaker || false,
2260
- model: r.model
2337
+ model: r.model,
2338
+ roleType: r.roleType,
2339
+ groupIndex: r.groupIndex
2261
2340
  })),
2262
2341
  activeRoles: Array.from(session.roleStates.entries())
2263
2342
  .filter(([, s]) => s.turnActive)
@@ -2266,7 +2345,9 @@ function sendStatusUpdate(session) {
2266
2345
  Array.from(session.roleStates.entries())
2267
2346
  .filter(([, s]) => s.turnActive && s.currentTool)
2268
2347
  .map(([name, s]) => [name, s.currentTool])
2269
- )
2348
+ ),
2349
+ features: Array.from(session.features.values()),
2350
+ groupNames: session.groupNames || {}
2270
2351
  });
2271
2352
 
2272
2353
  // 异步更新持久化
package/history.js CHANGED
@@ -63,6 +63,11 @@ export function getWorkDirFromProjectFolder(projectFolderPath, folderName) {
63
63
 
64
64
  // 获取指定目录的历史会话列表
65
65
  export async function getHistorySessions(workDir) {
66
+ // 过滤掉 crew 角色目录的 sessions
67
+ if (workDir && /[/\\]\.crew[/\\]roles[/\\]/.test(workDir)) {
68
+ return [];
69
+ }
70
+
66
71
  const projectsDir = getClaudeProjectsDir();
67
72
  const projectFolder = pathToProjectFolder(workDir);
68
73
  const projectPath = join(projectsDir, projectFolder);
@@ -224,6 +229,12 @@ export async function handleListFolders(msg) {
224
229
  const stats = statSync(entryPath);
225
230
 
226
231
  if (stats.isDirectory()) {
232
+ // 过滤掉 crew 角色的 session 文件夹
233
+ // crew 角色的 cwd 在 .crew/roles/{roleName} 下,对应的文件夹名包含 --crew-roles-
234
+ if (entry.includes('--crew-roles-')) {
235
+ continue;
236
+ }
237
+
227
238
  // 从 session 文件读取真实的工作目录路径
228
239
  const originalPath = getWorkDirFromProjectFolder(entryPath, entry);
229
240
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@yeaft/webchat-agent",
3
- "version": "0.0.163",
3
+ "version": "0.0.166",
4
4
  "description": "Remote agent for Yeaft WebChat — connects worker machines to the central server",
5
5
  "main": "index.js",
6
6
  "type": "module",