@yeaft/webchat-agent 0.1.66 → 0.1.68
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/role-management.js +25 -1
- package/crew/session.js +8 -3
- package/crew/shared-dir.js +51 -5
- package/package.json +1 -1
package/crew/role-management.js
CHANGED
|
@@ -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
|
|
|
@@ -335,14 +335,19 @@ export async function handleCheckCrewExists(msg) {
|
|
|
335
335
|
}
|
|
336
336
|
|
|
337
337
|
/**
|
|
338
|
-
* 删除工作目录下的 .crew
|
|
338
|
+
* 删除工作目录下的 .crew 目录(保留 context/ 历史记录)
|
|
339
339
|
*/
|
|
340
340
|
export async function handleDeleteCrewDir(msg) {
|
|
341
341
|
const { projectDir } = msg;
|
|
342
342
|
if (!isValidProjectDir(projectDir)) return;
|
|
343
343
|
const crewDir = join(projectDir, '.crew');
|
|
344
344
|
try {
|
|
345
|
-
await fs.
|
|
345
|
+
const entries = await fs.readdir(crewDir);
|
|
346
|
+
await Promise.all(
|
|
347
|
+
entries
|
|
348
|
+
.filter(name => name !== 'context')
|
|
349
|
+
.map(name => fs.rm(join(crewDir, name), { recursive: true, force: true }))
|
|
350
|
+
);
|
|
346
351
|
} catch {}
|
|
347
352
|
}
|
|
348
353
|
|
package/crew/shared-dir.js
CHANGED
|
@@ -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
|
-
${
|
|
136
|
+
${resolvedClaudeMd}
|
|
91
137
|
`;
|
|
92
138
|
|
|
93
139
|
// 有独立 worktree 的角色,覆盖代码工作目录
|