@yeaft/webchat-agent 0.0.164 → 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
@@ -383,6 +383,63 @@ export async function handleListCrewSessions(msg) {
383
383
  });
384
384
  }
385
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
+
386
443
  /**
387
444
  * 恢复已停止的 crew session
388
445
  */
@@ -585,6 +642,20 @@ export async function createCrewSession(msg) {
585
642
  }
586
643
  }
587
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
+
588
659
  // 找到决策者
589
660
  const decisionMaker = roles.find(r => r.isDecisionMaker)?.name || roles[0]?.name || null;
590
661
 
@@ -610,6 +681,7 @@ export async function createCrewSession(msg) {
610
681
  waitingHumanContext: null, // { fromRole, reason, message }
611
682
  pendingRoutes: [], // [{ fromRole, route }] — 暂停时未完成的路由
612
683
  features: new Map(), // taskId → { taskId, taskTitle, createdAt } — 持久化 feature 列表
684
+ groupNames, // groupIndex → 组名
613
685
  userId,
614
686
  username,
615
687
  agentId: ctx.CONFIG?.agentName || null,
@@ -639,6 +711,7 @@ export async function createCrewSession(msg) {
639
711
  })),
640
712
  decisionMaker,
641
713
  maxRounds,
714
+ groupNames,
642
715
  userId,
643
716
  username
644
717
  });
@@ -889,18 +962,17 @@ ${roles.length > 0 ? roles.map(r => `- ${roleLabel(r)}(${r.name}): ${r.descripti
889
962
  2. 等待其他角色的产出但迟迟没有收到
890
963
  3. 任务描述不清楚或有歧义,无法判断正确做法
891
964
  4. 遇到超出自己职责范围的问题
892
- 5. 连续尝试5次相同操作仍然失败
893
- 上报时请说明:你在做什么任务、卡在哪里、你认为需要谁来协助。
965
+ 5. 连续尝试 2 次相同操作仍然失败
966
+ 上报时请说明:你在做什么任务、卡在哪里、你认为需要谁来协助。PM 会统筹全局,判断是分配给合适的人还是调整任务顺序。
894
967
 
895
968
  # Worktree 隔离规则
896
- - 多实例模式下,每个开发组(dev-N/rev-N/test-N)在独立的 git worktree 中工作
897
- - 每个角色必须在自己的 worktree 路径下操作代码,绝对不要操作项目主目录
898
- - 绝对禁止在其他开发组的 branch worktree 中操作代码
969
+ - dev/reviewer/tester 角色必须在各自分配的 worktree 中工作,绝对禁止在项目主目录或 main 分支上修改代码
970
+ - 每个角色的 CLAUDE.md 会标明「代码工作目录」,该路径就是你的 worktree,所有文件操作必须使用该路径
971
+ - PM designer 不使用 worktree,他们在项目主目录下以只读方式工作
972
+ - 绝对禁止在其他开发组的 worktree 中操作代码
899
973
  - 代码完成并通过 review 后,dev 自己提 PR 合并到 main 分支
900
974
  - PM 不做 cherry-pick,只负责打 tag
901
- - 合并完成后清理旧的 worktree
902
975
  - 每次新任务/新 feature 必须基于最新的 main 分支创建新的 worktree,确保在最新代码上开发
903
- - 禁止复用旧的 worktree 开发新任务,因为旧 worktree 的代码基线可能已过时
904
976
 
905
977
  ${sharedMemoryContent}`;
906
978
 
@@ -921,12 +993,9 @@ ${role.claudeMd || role.description}
921
993
  // 有独立 worktree 的角色,覆盖代码工作目录
922
994
  if (role.workDir) {
923
995
  claudeMd += `
924
- # 代码工作目录(重要!)
996
+ # 代码工作目录
925
997
  ${role.workDir}
926
- 所有代码操作必须在此 worktree 路径下进行。
927
- 绝对禁止直接操作项目主目录或其他组的 worktree,否则会覆盖其他开发组的修改。
928
- 代码完成并通过 review 后,自己提 PR 合并到 main。
929
- 此 worktree 仅用于当前任务,合并后会被清理,新任务会创建新的 worktree。
998
+ 所有代码操作请使用此路径。不要使用项目主目录。
930
999
  `;
931
1000
  }
932
1001
 
@@ -2265,7 +2334,9 @@ function sendStatusUpdate(session) {
2265
2334
  icon: r.icon,
2266
2335
  description: r.description,
2267
2336
  isDecisionMaker: r.isDecisionMaker || false,
2268
- model: r.model
2337
+ model: r.model,
2338
+ roleType: r.roleType,
2339
+ groupIndex: r.groupIndex
2269
2340
  })),
2270
2341
  activeRoles: Array.from(session.roleStates.entries())
2271
2342
  .filter(([, s]) => s.turnActive)
@@ -2275,7 +2346,8 @@ function sendStatusUpdate(session) {
2275
2346
  .filter(([, s]) => s.turnActive && s.currentTool)
2276
2347
  .map(([name, s]) => [name, s.currentTool])
2277
2348
  ),
2278
- features: Array.from(session.features.values())
2349
+ features: Array.from(session.features.values()),
2350
+ groupNames: session.groupNames || {}
2279
2351
  });
2280
2352
 
2281
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.164",
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",