@yeaft/webchat-agent 0.1.65 → 0.1.67

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.
@@ -26,6 +26,7 @@ export async function addRoleToSession(msg) {
26
26
  }
27
27
 
28
28
  const rolesToAdd = expandRoles([role]);
29
+ const addedRoles = [];
29
30
 
30
31
  for (const r of rolesToAdd) {
31
32
  if (session.roles.has(r.name)) {
@@ -34,6 +35,7 @@ export async function addRoleToSession(msg) {
34
35
  }
35
36
 
36
37
  session.roles.set(r.name, r);
38
+ addedRoles.push(r);
37
39
 
38
40
  if (r.isDecisionMaker) {
39
41
  session.decisionMaker = r.name;
@@ -42,7 +44,7 @@ export async function addRoleToSession(msg) {
42
44
  session.decisionMaker = r.name;
43
45
  }
44
46
 
45
- await initRoleDir(session.sharedDir, r, session.language || 'zh-CN');
47
+ await initRoleDir(session.sharedDir, r, session.language || 'zh-CN', Array.from(session.roles.values()));
46
48
 
47
49
  console.log(`[Crew] Role added: ${r.name} (${r.displayName}) to session ${sessionId}`);
48
50
 
@@ -69,6 +71,28 @@ export async function addRoleToSession(msg) {
69
71
  }
70
72
 
71
73
  await updateSharedClaudeMd(session);
74
+
75
+ // Notify the decision maker about the new role(s) so it can ROUTE to them.
76
+ // We inject a system message into the DM's next turn via dispatchToRole
77
+ // (only if the DM is idle — otherwise the DM will see the updated team list
78
+ // through its CLAUDE.md or recent-routes context on next turn).
79
+ if (session.decisionMaker && addedRoles.length > 0) {
80
+ const dmState = session.roleStates.get(session.decisionMaker);
81
+ const dmIdle = !dmState || !dmState.turnActive;
82
+
83
+ if (dmIdle) {
84
+ const isZh = (session.language || 'zh-CN').startsWith('zh');
85
+ const addedNames = addedRoles.map(r => `${roleLabel(r)}(${r.name})`).join(', ');
86
+ const notice = isZh
87
+ ? `[系统通知] 新角色已加入团队: ${addedNames}。请在后续任务分配中考虑这些新成员。`
88
+ : `[System Notice] New role(s) joined the team: ${addedNames}. Consider them in future task assignments.`;
89
+
90
+ // Lazy import to avoid circular dependency
91
+ const { dispatchToRole } = await import('./routing.js');
92
+ await dispatchToRole(session, session.decisionMaker, notice, 'system');
93
+ }
94
+ }
95
+
72
96
  sendStatusUpdate(session);
73
97
  }
74
98
 
package/crew/session.js CHANGED
@@ -216,7 +216,7 @@ export async function createCrewSession(msg) {
216
216
  for (const role of roles) {
217
217
  if (role.groupIndex > 0 && worktreeMap.has(role.groupIndex)) {
218
218
  role.workDir = worktreeMap.get(role.groupIndex);
219
- await writeRoleClaudeMd(sharedDir, role, language);
219
+ await writeRoleClaudeMd(sharedDir, role, language, roles);
220
220
  }
221
221
  }
222
222
 
@@ -22,7 +22,7 @@ export async function initSharedDir(sharedDir, roles, projectDir, language = 'zh
22
22
 
23
23
  // 初始化每个角色的目录
24
24
  for (const role of roles) {
25
- await initRoleDir(sharedDir, role, language);
25
+ await initRoleDir(sharedDir, role, language, roles);
26
26
  }
27
27
 
28
28
  // 生成 .crew/CLAUDE.md(共享级)
@@ -32,7 +32,7 @@ export async function initSharedDir(sharedDir, roles, projectDir, language = 'zh
32
32
  /**
33
33
  * 初始化角色目录: .crew/roles/{roleName}/CLAUDE.md
34
34
  */
35
- export async function initRoleDir(sharedDir, role, language = 'zh-CN') {
35
+ export async function initRoleDir(sharedDir, role, language = 'zh-CN', allRoles = []) {
36
36
  const roleDir = join(sharedDir, 'roles', role.name);
37
37
  await fs.mkdir(roleDir, { recursive: true });
38
38
 
@@ -42,7 +42,7 @@ export async function initRoleDir(sharedDir, role, language = 'zh-CN') {
42
42
  await fs.access(claudeMdPath);
43
43
  // 已存在,不覆盖(保留角色自己写入的记忆)
44
44
  } catch {
45
- await writeRoleClaudeMd(sharedDir, role, language);
45
+ await writeRoleClaudeMd(sharedDir, role, language, allRoles);
46
46
  }
47
47
  }
48
48
 
@@ -79,15 +79,61 @@ ${m.sharedMemoryDefault}
79
79
  await fs.writeFile(join(sharedDir, 'CLAUDE.md'), claudeMd);
80
80
  }
81
81
 
82
+ /**
83
+ * Replace generic role names in ROUTE examples with actual instance names.
84
+ *
85
+ * Given a role with groupIndex=2 and the full role list containing
86
+ * dev-1, dev-2, rev-1, rev-2, test-1, test-2, the function rewrites:
87
+ * "to: reviewer" → "to: rev-2"
88
+ * "to: developer" → "to: dev-2"
89
+ * "to: tester" → "to: test-2"
90
+ *
91
+ * For roles without a groupIndex (pm, designer, etc.), or when no matching
92
+ * instance exists, the generic name is left untouched.
93
+ *
94
+ * @param {string} text - claudeMd content with generic ROUTE targets
95
+ * @param {object} role - the role being written (must have roleType, groupIndex)
96
+ * @param {Array} allRoles - full expanded role list
97
+ * @returns {string} text with generic names replaced by instance names
98
+ */
99
+ export function resolveRouteTargets(text, role, allRoles) {
100
+ if (!allRoles || allRoles.length === 0 || !role.groupIndex) return text;
101
+
102
+ // Build a lookup: generic roleType → instance name at this groupIndex
103
+ // e.g. { developer: 'dev-2', reviewer: 'rev-2', tester: 'test-2' }
104
+ const instanceMap = {};
105
+ for (const r of allRoles) {
106
+ if (r.groupIndex === role.groupIndex && r.roleType && r.name !== r.roleType) {
107
+ instanceMap[r.roleType] = r.name;
108
+ }
109
+ }
110
+
111
+ if (Object.keys(instanceMap).length === 0) return text;
112
+
113
+ // Replace "to: <genericName>" inside ROUTE blocks
114
+ // Use a careful regex that only touches the `to:` field value
115
+ return text.replace(/(to:\s*)(developer|reviewer|tester)\b/gi, (match, prefix, genericName) => {
116
+ const resolved = instanceMap[genericName.toLowerCase()];
117
+ return resolved ? `${prefix}${resolved}` : match;
118
+ });
119
+ }
120
+
82
121
  /**
83
122
  * 写入 .crew/roles/{roleName}/CLAUDE.md — 角色级
123
+ * @param {string} sharedDir
124
+ * @param {object} role
125
+ * @param {string} language
126
+ * @param {Array} [allRoles] - full expanded role list for ROUTE target resolution
84
127
  */
85
- export async function writeRoleClaudeMd(sharedDir, role, language = 'zh-CN') {
128
+ export async function writeRoleClaudeMd(sharedDir, role, language = 'zh-CN', allRoles = []) {
86
129
  const roleDir = join(sharedDir, 'roles', role.name);
87
130
  const m = getMessages(language);
88
131
 
132
+ // Resolve generic ROUTE targets to actual instance names
133
+ const resolvedClaudeMd = resolveRouteTargets(role.claudeMd || role.description, role, allRoles);
134
+
89
135
  let claudeMd = `${m.roleTitle(roleLabel(role))}
90
- ${role.claudeMd || role.description}
136
+ ${resolvedClaudeMd}
91
137
  `;
92
138
 
93
139
  // 有独立 worktree 的角色,覆盖代码工作目录
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@yeaft/webchat-agent",
3
- "version": "0.1.65",
3
+ "version": "0.1.67",
4
4
  "description": "Remote agent for Yeaft WebChat — connects worker machines to the central server",
5
5
  "main": "index.js",
6
6
  "type": "module",