@yeaft/webchat-agent 0.0.187 → 0.0.196
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/crew.js +233 -36
- package/package.json +1 -1
package/crew.js
CHANGED
|
@@ -324,10 +324,12 @@ async function saveSessionMeta(session) {
|
|
|
324
324
|
userId: session.userId,
|
|
325
325
|
username: session.username,
|
|
326
326
|
agentId: session.agentId || null,
|
|
327
|
+
teamType: session.teamType || 'dev',
|
|
327
328
|
costUsd: session.costUsd,
|
|
328
329
|
totalInputTokens: session.totalInputTokens,
|
|
329
330
|
totalOutputTokens: session.totalOutputTokens,
|
|
330
|
-
features: Array.from(session.features.values())
|
|
331
|
+
features: Array.from(session.features.values()),
|
|
332
|
+
_completedTaskIds: Array.from(session._completedTaskIds || [])
|
|
331
333
|
};
|
|
332
334
|
await fs.writeFile(join(session.sharedDir, 'session.json'), JSON.stringify(meta, null, 2));
|
|
333
335
|
// 保存 UI 消息历史(用于恢复时重放)
|
|
@@ -548,9 +550,11 @@ export async function resumeCrewSession(msg) {
|
|
|
548
550
|
waitingHumanContext: null,
|
|
549
551
|
pendingRoutes: [],
|
|
550
552
|
features: new Map((meta.features || []).map(f => [f.taskId, f])),
|
|
553
|
+
_completedTaskIds: new Set(meta._completedTaskIds || []),
|
|
551
554
|
userId: userId || meta.userId,
|
|
552
555
|
username: username || meta.username,
|
|
553
556
|
agentId: meta.agentId || ctx.CONFIG?.agentName || null,
|
|
557
|
+
teamType: meta.teamType || 'dev',
|
|
554
558
|
createdAt: meta.createdAt || Date.now()
|
|
555
559
|
};
|
|
556
560
|
crewSessions.set(sessionId, session);
|
|
@@ -635,6 +639,7 @@ export async function createCrewSession(msg) {
|
|
|
635
639
|
sharedKnowledge,
|
|
636
640
|
roles: rawRoles = [], // [{ name, displayName, icon, description, claudeMd, model, budget, isDecisionMaker, count }]
|
|
637
641
|
maxRounds = 20,
|
|
642
|
+
teamType = 'dev',
|
|
638
643
|
userId,
|
|
639
644
|
username
|
|
640
645
|
} = msg;
|
|
@@ -655,23 +660,10 @@ export async function createCrewSession(msg) {
|
|
|
655
660
|
? sharedDirRel
|
|
656
661
|
: join(projectDir, sharedDirRel || '.crew');
|
|
657
662
|
|
|
658
|
-
// 初始化共享区
|
|
659
|
-
await initSharedDir(sharedDir, goal, roles, projectDir, sharedKnowledge);
|
|
660
|
-
|
|
661
|
-
// 初始化 git worktrees(所有 EXPANDABLE_ROLES 都会获得独立 worktree)
|
|
662
|
-
const worktreeMap = await initWorktrees(projectDir, roles);
|
|
663
|
-
// 回填 workDir:同组的 dev/rev/test 共享同一个 worktree
|
|
664
|
-
for (const role of roles) {
|
|
665
|
-
if (role.groupIndex > 0 && worktreeMap.has(role.groupIndex)) {
|
|
666
|
-
role.workDir = worktreeMap.get(role.groupIndex);
|
|
667
|
-
// 重新写入 CLAUDE.md(加入工作目录信息)
|
|
668
|
-
await writeRoleClaudeMd(sharedDir, role);
|
|
669
|
-
}
|
|
670
|
-
}
|
|
671
|
-
|
|
672
663
|
// 找到决策者
|
|
673
664
|
const decisionMaker = roles.find(r => r.isDecisionMaker)?.name || roles[0]?.name || null;
|
|
674
665
|
|
|
666
|
+
// ★ 阶段1:立即构建 session 并通知前端,让 UI 先显示
|
|
675
667
|
const session = {
|
|
676
668
|
id: sessionId,
|
|
677
669
|
projectDir,
|
|
@@ -682,7 +674,7 @@ export async function createCrewSession(msg) {
|
|
|
682
674
|
roles: new Map(roles.map(r => [r.name, r])),
|
|
683
675
|
roleStates: new Map(),
|
|
684
676
|
decisionMaker,
|
|
685
|
-
status: '
|
|
677
|
+
status: 'initializing', // ← 新增初始化状态
|
|
686
678
|
round: 0,
|
|
687
679
|
maxRounds,
|
|
688
680
|
costUsd: 0,
|
|
@@ -694,15 +686,18 @@ export async function createCrewSession(msg) {
|
|
|
694
686
|
waitingHumanContext: null, // { fromRole, reason, message }
|
|
695
687
|
pendingRoutes: [], // [{ fromRole, route }] — 暂停时未完成的路由
|
|
696
688
|
features: new Map(), // taskId → { taskId, taskTitle, createdAt } — 持久化 feature 列表
|
|
689
|
+
_completedTaskIds: new Set(), // 已完成的 taskId 集合(用于检测新完成的任务)
|
|
690
|
+
initProgress: null, // 'roles' | 'worktrees' | null — 初始化阶段
|
|
697
691
|
userId,
|
|
698
692
|
username,
|
|
699
693
|
agentId: ctx.CONFIG?.agentName || null,
|
|
694
|
+
teamType,
|
|
700
695
|
createdAt: Date.now()
|
|
701
696
|
};
|
|
702
697
|
|
|
703
698
|
crewSessions.set(sessionId, session);
|
|
704
699
|
|
|
705
|
-
//
|
|
700
|
+
// 立即通知前端:session 已创建,可以显示 UI
|
|
706
701
|
sendCrewMessage({
|
|
707
702
|
type: 'crew_session_created',
|
|
708
703
|
sessionId,
|
|
@@ -727,20 +722,66 @@ export async function createCrewSession(msg) {
|
|
|
727
722
|
username
|
|
728
723
|
});
|
|
729
724
|
|
|
730
|
-
// 发送状态
|
|
731
725
|
sendStatusUpdate(session);
|
|
732
726
|
|
|
733
|
-
//
|
|
734
|
-
|
|
735
|
-
|
|
727
|
+
// ★ 阶段2:异步完成文件系统和 worktree 初始化
|
|
728
|
+
try {
|
|
729
|
+
// 初始化共享区(角色目录 + CLAUDE.md)
|
|
730
|
+
session.initProgress = 'roles';
|
|
731
|
+
sendStatusUpdate(session);
|
|
732
|
+
await initSharedDir(sharedDir, goal, roles, projectDir, sharedKnowledge);
|
|
733
|
+
|
|
734
|
+
// 初始化 git worktrees
|
|
735
|
+
const groupIndices = [...new Set(roles.filter(r => r.groupIndex > 0).map(r => r.groupIndex))];
|
|
736
|
+
if (groupIndices.length > 0) {
|
|
737
|
+
session.initProgress = 'worktrees';
|
|
738
|
+
sendStatusUpdate(session);
|
|
739
|
+
}
|
|
740
|
+
const worktreeMap = await initWorktrees(projectDir, roles);
|
|
741
|
+
|
|
742
|
+
// 回填 workDir
|
|
743
|
+
for (const role of roles) {
|
|
744
|
+
if (role.groupIndex > 0 && worktreeMap.has(role.groupIndex)) {
|
|
745
|
+
role.workDir = worktreeMap.get(role.groupIndex);
|
|
746
|
+
await writeRoleClaudeMd(sharedDir, role);
|
|
747
|
+
}
|
|
748
|
+
}
|
|
749
|
+
|
|
750
|
+
// 持久化
|
|
751
|
+
await upsertCrewIndex(session);
|
|
752
|
+
await saveSessionMeta(session);
|
|
753
|
+
|
|
754
|
+
// 初始化完成,仅在 initializing 状态下切换到 running(避免覆盖用户手动暂停/停止)
|
|
755
|
+
if (session.status === 'initializing') {
|
|
756
|
+
session.status = 'running';
|
|
757
|
+
}
|
|
758
|
+
session.initProgress = null;
|
|
759
|
+
sendStatusUpdate(session);
|
|
736
760
|
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
|
|
761
|
+
// 如果有目标且状态为 running,自动启动第一个角色
|
|
762
|
+
if (goal && roles.length > 0 && session.status === 'running') {
|
|
763
|
+
const firstRole = roles.find(r => r.name === 'pm') || roles[0];
|
|
764
|
+
if (firstRole) {
|
|
765
|
+
const initialPrompt = buildInitialTask(goal, firstRole, roles);
|
|
766
|
+
await dispatchToRole(session, firstRole.name, initialPrompt, 'system');
|
|
767
|
+
}
|
|
768
|
+
}
|
|
769
|
+
} catch (e) {
|
|
770
|
+
console.error('[Crew] Session initialization failed:', e);
|
|
771
|
+
if (session.status === 'initializing') {
|
|
772
|
+
session.status = 'running';
|
|
743
773
|
}
|
|
774
|
+
session.initProgress = null;
|
|
775
|
+
sendStatusUpdate(session);
|
|
776
|
+
sendCrewMessage({
|
|
777
|
+
type: 'crew_output',
|
|
778
|
+
sessionId,
|
|
779
|
+
roleName: 'system',
|
|
780
|
+
roleIcon: 'S',
|
|
781
|
+
roleDisplayName: '系统',
|
|
782
|
+
content: `工作环境初始化失败: ${e.message}`,
|
|
783
|
+
isTurnEnd: true
|
|
784
|
+
});
|
|
744
785
|
}
|
|
745
786
|
|
|
746
787
|
return session;
|
|
@@ -1076,6 +1117,9 @@ ${summary}
|
|
|
1076
1117
|
}
|
|
1077
1118
|
|
|
1078
1119
|
console.log(`[Crew] Task file created: ${taskId} (${taskTitle})`);
|
|
1120
|
+
|
|
1121
|
+
// 更新 feature 索引
|
|
1122
|
+
updateFeatureIndex(session).catch(e => console.warn('[Crew] Failed to update feature index:', e.message));
|
|
1079
1123
|
}
|
|
1080
1124
|
|
|
1081
1125
|
/**
|
|
@@ -1113,6 +1157,112 @@ async function readTaskFile(session, taskId) {
|
|
|
1113
1157
|
}
|
|
1114
1158
|
}
|
|
1115
1159
|
|
|
1160
|
+
/**
|
|
1161
|
+
* 从 TASKS block 文本中提取已完成任务的 taskId 集合
|
|
1162
|
+
*/
|
|
1163
|
+
function parseCompletedTasks(text) {
|
|
1164
|
+
const ids = new Set();
|
|
1165
|
+
const match = text.match(/---TASKS---([\s\S]*?)---END_TASKS---/);
|
|
1166
|
+
if (!match) return ids;
|
|
1167
|
+
for (const line of match[1].split('\n')) {
|
|
1168
|
+
const m = line.match(/^-\s*\[[xX]\]\s*.+#(\S+)/);
|
|
1169
|
+
if (m) ids.add(m[1]);
|
|
1170
|
+
}
|
|
1171
|
+
return ids;
|
|
1172
|
+
}
|
|
1173
|
+
|
|
1174
|
+
/**
|
|
1175
|
+
* 更新 feature 索引文件 context/features/index.md
|
|
1176
|
+
* 全量重建:根据 session.features 和 session._completedTaskIds 生成分类表格
|
|
1177
|
+
*/
|
|
1178
|
+
async function updateFeatureIndex(session) {
|
|
1179
|
+
const featuresDir = join(session.sharedDir, 'context', 'features');
|
|
1180
|
+
await fs.mkdir(featuresDir, { recursive: true });
|
|
1181
|
+
|
|
1182
|
+
const completed = session._completedTaskIds || new Set();
|
|
1183
|
+
const allFeatures = Array.from(session.features.values());
|
|
1184
|
+
|
|
1185
|
+
// 按创建时间排序
|
|
1186
|
+
allFeatures.sort((a, b) => (a.createdAt || 0) - (b.createdAt || 0));
|
|
1187
|
+
|
|
1188
|
+
const inProgress = allFeatures.filter(f => !completed.has(f.taskId));
|
|
1189
|
+
const done = allFeatures.filter(f => completed.has(f.taskId));
|
|
1190
|
+
|
|
1191
|
+
const now = new Date().toLocaleString('zh-CN', { timeZone: 'Asia/Shanghai' });
|
|
1192
|
+
let content = `# Feature Index\n> 最后更新: ${now}\n`;
|
|
1193
|
+
|
|
1194
|
+
content += `\n## 进行中 (${inProgress.length})\n`;
|
|
1195
|
+
if (inProgress.length > 0) {
|
|
1196
|
+
content += '| task-id | 标题 | 创建时间 |\n|---------|------|----------|\n';
|
|
1197
|
+
for (const f of inProgress) {
|
|
1198
|
+
const date = f.createdAt ? new Date(f.createdAt).toLocaleDateString('zh-CN') : '-';
|
|
1199
|
+
content += `| ${f.taskId} | ${f.taskTitle} | ${date} |\n`;
|
|
1200
|
+
}
|
|
1201
|
+
}
|
|
1202
|
+
|
|
1203
|
+
content += `\n## 已完成 (${done.length})\n`;
|
|
1204
|
+
if (done.length > 0) {
|
|
1205
|
+
content += '| task-id | 标题 | 创建时间 |\n|---------|------|----------|\n';
|
|
1206
|
+
for (const f of done) {
|
|
1207
|
+
const date = f.createdAt ? new Date(f.createdAt).toLocaleDateString('zh-CN') : '-';
|
|
1208
|
+
content += `| ${f.taskId} | ${f.taskTitle} | ${date} |\n`;
|
|
1209
|
+
}
|
|
1210
|
+
}
|
|
1211
|
+
|
|
1212
|
+
await fs.writeFile(join(featuresDir, 'index.md'), content);
|
|
1213
|
+
console.log(`[Crew] Feature index updated: ${inProgress.length} in progress, ${done.length} completed`);
|
|
1214
|
+
}
|
|
1215
|
+
|
|
1216
|
+
/**
|
|
1217
|
+
* 追加完成汇总到 context/changelog.md
|
|
1218
|
+
* 从 feature 文件的工作记录中提取最后一条记录作为摘要
|
|
1219
|
+
*/
|
|
1220
|
+
async function appendChangelog(session, taskId, taskTitle) {
|
|
1221
|
+
const contextDir = join(session.sharedDir, 'context');
|
|
1222
|
+
await fs.mkdir(contextDir, { recursive: true });
|
|
1223
|
+
const changelogPath = join(contextDir, 'changelog.md');
|
|
1224
|
+
|
|
1225
|
+
// 读取 feature 文件提取最后一条工作记录作为摘要
|
|
1226
|
+
const taskContent = await readTaskFile(session, taskId);
|
|
1227
|
+
let summaryText = '';
|
|
1228
|
+
if (taskContent) {
|
|
1229
|
+
// 提取最后一个 ### 块作为摘要
|
|
1230
|
+
const records = taskContent.split(/\n### /);
|
|
1231
|
+
if (records.length > 1) {
|
|
1232
|
+
const lastRecord = records[records.length - 1];
|
|
1233
|
+
// 取第一行之后的内容作为摘要(第一行是角色名和时间)
|
|
1234
|
+
const lines = lastRecord.split('\n');
|
|
1235
|
+
summaryText = lines.slice(1).join('\n').trim();
|
|
1236
|
+
}
|
|
1237
|
+
}
|
|
1238
|
+
if (!summaryText) {
|
|
1239
|
+
summaryText = '(无详细摘要)';
|
|
1240
|
+
}
|
|
1241
|
+
|
|
1242
|
+
// 限制摘要长度
|
|
1243
|
+
if (summaryText.length > 500) {
|
|
1244
|
+
summaryText = summaryText.substring(0, 497) + '...';
|
|
1245
|
+
}
|
|
1246
|
+
|
|
1247
|
+
const now = new Date().toLocaleString('zh-CN', { timeZone: 'Asia/Shanghai' });
|
|
1248
|
+
const entry = `\n## ${taskId}: ${taskTitle}\n- 完成时间: ${now}\n- 摘要: ${summaryText}\n`;
|
|
1249
|
+
|
|
1250
|
+
// 如果文件不存在,先写 header
|
|
1251
|
+
let exists = false;
|
|
1252
|
+
try {
|
|
1253
|
+
await fs.access(changelogPath);
|
|
1254
|
+
exists = true;
|
|
1255
|
+
} catch {}
|
|
1256
|
+
|
|
1257
|
+
if (!exists) {
|
|
1258
|
+
await fs.writeFile(changelogPath, `# Changelog\n${entry}`);
|
|
1259
|
+
} else {
|
|
1260
|
+
await fs.appendFile(changelogPath, entry);
|
|
1261
|
+
}
|
|
1262
|
+
|
|
1263
|
+
console.log(`[Crew] Changelog appended: ${taskId} (${taskTitle})`);
|
|
1264
|
+
}
|
|
1265
|
+
|
|
1116
1266
|
// =====================================================================
|
|
1117
1267
|
// Session Persistence
|
|
1118
1268
|
// =====================================================================
|
|
@@ -1311,21 +1461,37 @@ ${routeTargets.map(r => `- ${r.name}: ${roleLabel(r)} — ${r.description}`).joi
|
|
|
1311
1461
|
|
|
1312
1462
|
// 决策者额外 prompt
|
|
1313
1463
|
if (role.isDecisionMaker) {
|
|
1464
|
+
const isDevTeam = session.teamType === 'dev';
|
|
1314
1465
|
|
|
1315
1466
|
prompt += `\n\n# 工具使用
|
|
1316
|
-
PM 可以使用所有工具,包括 Read、Grep、Glob、Bash、Edit、Write
|
|
1467
|
+
PM 可以使用所有工具,包括 Read、Grep、Glob、Bash、Edit、Write。${isDevTeam ? '代码文件的改动仍建议 ROUTE 给 developer 执行,但不做硬性限制。' : ''}`;
|
|
1317
1468
|
|
|
1318
1469
|
prompt += `\n\n# 决策者职责
|
|
1319
1470
|
你是团队的决策者。其他角色遇到不确定的情况会请求你的决策。
|
|
1320
1471
|
- 如果你有足够的信息做出决策,直接决定并 @相关角色执行
|
|
1321
1472
|
- 如果你需要更多信息,@具体角色请求补充
|
|
1322
1473
|
- 如果问题超出你的能力范围或需要业务判断,@human 请人类决定
|
|
1323
|
-
-
|
|
1474
|
+
- 你可以随时审查其他角色的工作并给出反馈`;
|
|
1475
|
+
|
|
1476
|
+
if (isDevTeam) {
|
|
1477
|
+
prompt += `
|
|
1324
1478
|
- PM 不做代码分析。收到需求后直接将原始需求 ROUTE 给空闲 dev 做技术分析,dev 分析完返回 PM,PM 再拆分任务并直接分配执行。
|
|
1325
1479
|
- PM 拥有 commit + push + tag 的自主权。只要修改没有大的 regression 影响(测试全通过),PM 可以自行决定 commit、push 和 tag,无需等待人工确认。只有当改动会直接影响对话交互逻辑时,才需要人工介入审核。`;
|
|
1480
|
+
}
|
|
1481
|
+
|
|
1482
|
+
// 非开发团队:注入讨论模式 prompt
|
|
1483
|
+
if (!isDevTeam) {
|
|
1484
|
+
prompt += `\n\n# 协作模式
|
|
1485
|
+
这是一个协作讨论团队,不走严格的 PM→执行→审查→测试 工作流。
|
|
1486
|
+
- 角色之间可以自由讨论、相互请教、提出不同意见
|
|
1487
|
+
- 不需要严格的"分配→执行→审查"流程,鼓励角色之间直接对话
|
|
1488
|
+
- 当一个角色需要另一个角色的输入时,直接 ROUTE 给对方并说明需要什么
|
|
1489
|
+
- 决策者负责把控整体方向和最终决策,但日常讨论不需要经过决策者中转
|
|
1490
|
+
- 每次 ROUTE 仍建议包含 task 和 taskTitle 字段,用于消息按 feature 分组显示`;
|
|
1491
|
+
}
|
|
1326
1492
|
|
|
1327
|
-
//
|
|
1328
|
-
if (hasMultiInstance) {
|
|
1493
|
+
// 多实例模式(仅开发团队使用):注入开发组状态和调度规则
|
|
1494
|
+
if (isDevTeam && hasMultiInstance) {
|
|
1329
1495
|
// 构建开发组实时状态
|
|
1330
1496
|
const maxGroup = Math.max(...allRoles.map(r => r.groupIndex));
|
|
1331
1497
|
const groupLines = [];
|
|
@@ -1375,9 +1541,8 @@ summary: 请实现注册页面,包括邮箱验证
|
|
|
1375
1541
|
prompt += `\n
|
|
1376
1542
|
# 工作流终结点
|
|
1377
1543
|
团队的工作流有明确的结束条件。当以下任一条件满足时,你应该给出总结并结束当前工作流:
|
|
1378
|
-
1. **代码已提交** - 所有代码修改已经 commit(如需要,可让 developer 执行 git commit
|
|
1379
|
-
2.
|
|
1380
|
-
3. **任务完成** - 所有任务已完成,给出完成总结(列出完成了什么、变更了哪些文件、还有什么后续建议)
|
|
1544
|
+
${isDevTeam ? '1. **代码已提交** - 所有代码修改已经 commit(如需要,可让 developer 执行 git commit)\n' : ''}${isDevTeam ? '2' : '1'}. **需要用户输入** - 遇到需要用户决定的问题时,@human 提出具体问题,等待用户回复
|
|
1545
|
+
${isDevTeam ? '3' : '2'}. **任务完成** - 所有任务已完成,给出完成总结(列出完成了什么${isDevTeam ? '、变更了哪些文件' : ''}、还有什么后续建议)
|
|
1381
1546
|
|
|
1382
1547
|
重要:不要无限循环地在角色之间传递。当工作实质性完成时,主动给出总结并结束。
|
|
1383
1548
|
|
|
@@ -1405,6 +1570,10 @@ summary: 请实现注册页面,包括邮箱验证
|
|
|
1405
1570
|
- PM 分配任务时自动创建文件(包含 task-id、标题、需求描述)
|
|
1406
1571
|
- 每次 ROUTE 传递时自动追加工作记录(角色名、时间、summary)
|
|
1407
1572
|
- 你收到的消息中会包含 <task-context> 标签,里面是该任务的完整工作记录
|
|
1573
|
+
|
|
1574
|
+
系统还维护以下文件(自动更新,无需手动管理):
|
|
1575
|
+
- \`context/features/index.md\`:所有 feature 的索引(进行中/已完成分类),快速查看项目状态
|
|
1576
|
+
- \`context/changelog.md\`:已完成任务的变更记录,每个任务完成时自动追加摘要
|
|
1408
1577
|
你不需要手动创建或更新这些文件,专注于你的本职工作即可。`;
|
|
1409
1578
|
|
|
1410
1579
|
// 执行者角色的组绑定 prompt(count > 1 时)
|
|
@@ -1651,6 +1820,33 @@ async function processRoleOutput(session, roleName, roleQuery, roleState) {
|
|
|
1651
1820
|
|
|
1652
1821
|
// 解析路由(支持多 ROUTE 块)
|
|
1653
1822
|
const routes = parseRoutes(roleState.accumulatedText);
|
|
1823
|
+
|
|
1824
|
+
// ★ 决策者 turn 完成:检测 TASKS block 中新完成的任务
|
|
1825
|
+
const roleConfig = session.roles.get(roleName);
|
|
1826
|
+
if (roleConfig?.isDecisionMaker) {
|
|
1827
|
+
const nowCompleted = parseCompletedTasks(roleState.accumulatedText);
|
|
1828
|
+
if (nowCompleted.size > 0) {
|
|
1829
|
+
const prev = session._completedTaskIds || new Set();
|
|
1830
|
+
const newlyDone = [];
|
|
1831
|
+
for (const tid of nowCompleted) {
|
|
1832
|
+
if (!prev.has(tid)) {
|
|
1833
|
+
prev.add(tid);
|
|
1834
|
+
newlyDone.push(tid);
|
|
1835
|
+
}
|
|
1836
|
+
}
|
|
1837
|
+
session._completedTaskIds = prev;
|
|
1838
|
+
if (newlyDone.length > 0) {
|
|
1839
|
+
// 更新索引 + 追加 changelog(fire-and-forget)
|
|
1840
|
+
updateFeatureIndex(session).catch(e => console.warn('[Crew] Failed to update feature index:', e.message));
|
|
1841
|
+
for (const tid of newlyDone) {
|
|
1842
|
+
const feature = session.features.get(tid);
|
|
1843
|
+
const title = feature?.taskTitle || tid;
|
|
1844
|
+
appendChangelog(session, tid, title).catch(e => console.warn(`[Crew] Failed to append changelog for ${tid}:`, e.message));
|
|
1845
|
+
}
|
|
1846
|
+
}
|
|
1847
|
+
}
|
|
1848
|
+
}
|
|
1849
|
+
|
|
1654
1850
|
roleState.accumulatedText = '';
|
|
1655
1851
|
roleState.turnActive = false;
|
|
1656
1852
|
|
|
@@ -1988,7 +2184,7 @@ function buildRoutePrompt(fromRole, summary, session) {
|
|
|
1988
2184
|
* 向角色发送消息
|
|
1989
2185
|
*/
|
|
1990
2186
|
async function dispatchToRole(session, roleName, content, fromSource, taskId, taskTitle) {
|
|
1991
|
-
if (session.status === 'paused' || session.status === 'stopped') {
|
|
2187
|
+
if (session.status === 'paused' || session.status === 'stopped' || session.status === 'initializing') {
|
|
1992
2188
|
console.log(`[Crew] Session ${session.status}, skipping dispatch to ${roleName}`);
|
|
1993
2189
|
return;
|
|
1994
2190
|
}
|
|
@@ -2769,7 +2965,8 @@ function sendStatusUpdate(session) {
|
|
|
2769
2965
|
.filter(([, s]) => s.turnActive && s.currentTool)
|
|
2770
2966
|
.map(([name, s]) => [name, s.currentTool])
|
|
2771
2967
|
),
|
|
2772
|
-
features: Array.from(session.features.values())
|
|
2968
|
+
features: Array.from(session.features.values()),
|
|
2969
|
+
initProgress: session.initProgress || null
|
|
2773
2970
|
});
|
|
2774
2971
|
|
|
2775
2972
|
// 异步更新持久化
|