@yeaft/webchat-agent 0.0.195 → 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.
Files changed (2) hide show
  1. package/crew.js +144 -1
  2. package/package.json +1 -1
package/crew.js CHANGED
@@ -328,7 +328,8 @@ async function saveSessionMeta(session) {
328
328
  costUsd: session.costUsd,
329
329
  totalInputTokens: session.totalInputTokens,
330
330
  totalOutputTokens: session.totalOutputTokens,
331
- features: Array.from(session.features.values())
331
+ features: Array.from(session.features.values()),
332
+ _completedTaskIds: Array.from(session._completedTaskIds || [])
332
333
  };
333
334
  await fs.writeFile(join(session.sharedDir, 'session.json'), JSON.stringify(meta, null, 2));
334
335
  // 保存 UI 消息历史(用于恢复时重放)
@@ -549,6 +550,7 @@ export async function resumeCrewSession(msg) {
549
550
  waitingHumanContext: null,
550
551
  pendingRoutes: [],
551
552
  features: new Map((meta.features || []).map(f => [f.taskId, f])),
553
+ _completedTaskIds: new Set(meta._completedTaskIds || []),
552
554
  userId: userId || meta.userId,
553
555
  username: username || meta.username,
554
556
  agentId: meta.agentId || ctx.CONFIG?.agentName || null,
@@ -684,6 +686,7 @@ export async function createCrewSession(msg) {
684
686
  waitingHumanContext: null, // { fromRole, reason, message }
685
687
  pendingRoutes: [], // [{ fromRole, route }] — 暂停时未完成的路由
686
688
  features: new Map(), // taskId → { taskId, taskTitle, createdAt } — 持久化 feature 列表
689
+ _completedTaskIds: new Set(), // 已完成的 taskId 集合(用于检测新完成的任务)
687
690
  initProgress: null, // 'roles' | 'worktrees' | null — 初始化阶段
688
691
  userId,
689
692
  username,
@@ -1114,6 +1117,9 @@ ${summary}
1114
1117
  }
1115
1118
 
1116
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));
1117
1123
  }
1118
1124
 
1119
1125
  /**
@@ -1151,6 +1157,112 @@ async function readTaskFile(session, taskId) {
1151
1157
  }
1152
1158
  }
1153
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
+
1154
1266
  // =====================================================================
1155
1267
  // Session Persistence
1156
1268
  // =====================================================================
@@ -1458,6 +1570,10 @@ ${isDevTeam ? '3' : '2'}. **任务完成** - 所有任务已完成,给出完
1458
1570
  - PM 分配任务时自动创建文件(包含 task-id、标题、需求描述)
1459
1571
  - 每次 ROUTE 传递时自动追加工作记录(角色名、时间、summary)
1460
1572
  - 你收到的消息中会包含 <task-context> 标签,里面是该任务的完整工作记录
1573
+
1574
+ 系统还维护以下文件(自动更新,无需手动管理):
1575
+ - \`context/features/index.md\`:所有 feature 的索引(进行中/已完成分类),快速查看项目状态
1576
+ - \`context/changelog.md\`:已完成任务的变更记录,每个任务完成时自动追加摘要
1461
1577
  你不需要手动创建或更新这些文件,专注于你的本职工作即可。`;
1462
1578
 
1463
1579
  // 执行者角色的组绑定 prompt(count > 1 时)
@@ -1704,6 +1820,33 @@ async function processRoleOutput(session, roleName, roleQuery, roleState) {
1704
1820
 
1705
1821
  // 解析路由(支持多 ROUTE 块)
1706
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
+
1707
1850
  roleState.accumulatedText = '';
1708
1851
  roleState.turnActive = false;
1709
1852
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@yeaft/webchat-agent",
3
- "version": "0.0.195",
3
+ "version": "0.0.196",
4
4
  "description": "Remote agent for Yeaft WebChat — connects worker machines to the central server",
5
5
  "main": "index.js",
6
6
  "type": "module",