@yeaft/webchat-agent 0.0.163 → 0.0.165
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 +5 -1
- package/crew.js +96 -15
- package/history.js +11 -0
- 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, 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. 连续尝试
|
|
890
|
-
上报时请说明:你在做什么任务、卡在哪里、你认为需要谁来协助。
|
|
965
|
+
5. 连续尝试 2 次相同操作仍然失败
|
|
966
|
+
上报时请说明:你在做什么任务、卡在哪里、你认为需要谁来协助。PM 会统筹全局,判断是分配给合适的人还是调整任务顺序。
|
|
891
967
|
|
|
892
968
|
# Worktree 隔离规则
|
|
893
|
-
-
|
|
894
|
-
-
|
|
895
|
-
-
|
|
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
|
-
|
|
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
|
|