@yeaft/webchat-agent 0.1.377 → 0.1.379

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/control.js CHANGED
@@ -19,13 +19,23 @@ import { processHumanQueue } from './human-interaction.js';
19
19
  */
20
20
  export async function handleCrewControl(msg) {
21
21
  // Lazy import to avoid circular dependency
22
- const { crewSessions } = await import('./session.js');
22
+ const { crewSessions, resumeCrewSession } = await import('./session.js');
23
23
 
24
24
  const { sessionId, action, targetRole } = msg;
25
- const session = crewSessions.get(sessionId);
25
+ let session = crewSessions.get(sessionId);
26
26
  if (!session) {
27
- console.warn(`[Crew] Session not found: ${sessionId}`);
28
- return;
27
+ // Auto-resume: try to restore from disk before giving up
28
+ console.log(`[Crew] Session ${sessionId} not in memory, attempting auto-resume...`);
29
+ try {
30
+ await resumeCrewSession({ sessionId });
31
+ session = crewSessions.get(sessionId);
32
+ } catch (e) {
33
+ console.warn(`[Crew] Auto-resume failed for ${sessionId}:`, e.message);
34
+ }
35
+ if (!session) {
36
+ console.warn(`[Crew] Session not found: ${sessionId} (even after auto-resume)`);
37
+ return;
38
+ }
29
39
  }
30
40
 
31
41
  switch (action) {
@@ -11,13 +11,23 @@ import { debouncedSaveSessionMeta } from './persistence.js';
11
11
  */
12
12
  export async function handleCrewHumanInput(msg) {
13
13
  // Lazy import to avoid circular dependency
14
- const { crewSessions } = await import('./session.js');
14
+ const { crewSessions, resumeCrewSession } = await import('./session.js');
15
15
 
16
16
  const { sessionId, content, targetRole, files } = msg;
17
- const session = crewSessions.get(sessionId);
17
+ let session = crewSessions.get(sessionId);
18
18
  if (!session) {
19
- console.warn(`[Crew] Session not found: ${sessionId}`);
20
- return;
19
+ // Auto-resume: try to restore from disk before giving up
20
+ console.log(`[Crew] Session ${sessionId} not in memory, attempting auto-resume...`);
21
+ try {
22
+ await resumeCrewSession({ sessionId });
23
+ session = crewSessions.get(sessionId);
24
+ } catch (e) {
25
+ console.warn(`[Crew] Auto-resume failed for ${sessionId}:`, e.message);
26
+ }
27
+ if (!session) {
28
+ console.warn(`[Crew] Session not found: ${sessionId} (even after auto-resume)`);
29
+ return;
30
+ }
21
31
  }
22
32
 
23
33
  // Build dispatch content (supports image attachments)
@@ -17,13 +17,23 @@ function roleLabel(r) {
17
17
  */
18
18
  export async function addRoleToSession(msg) {
19
19
  // Lazy import to avoid circular dependency
20
- const { crewSessions, expandRoles } = await import('./session.js');
20
+ const { crewSessions, expandRoles, resumeCrewSession } = await import('./session.js');
21
21
 
22
22
  const { sessionId, role } = msg;
23
- const session = crewSessions.get(sessionId);
23
+ let session = crewSessions.get(sessionId);
24
24
  if (!session) {
25
- console.warn(`[Crew] Session not found: ${sessionId}`);
26
- return;
25
+ // Auto-resume: try to restore from disk before giving up
26
+ console.log(`[Crew] Session ${sessionId} not in memory, attempting auto-resume...`);
27
+ try {
28
+ await resumeCrewSession({ sessionId });
29
+ session = crewSessions.get(sessionId);
30
+ } catch (e) {
31
+ console.warn(`[Crew] Auto-resume failed for ${sessionId}:`, e.message);
32
+ }
33
+ if (!session) {
34
+ console.warn(`[Crew] Session not found: ${sessionId} (even after auto-resume)`);
35
+ return;
36
+ }
27
37
  }
28
38
 
29
39
  const rolesToAdd = expandRoles([role]);
@@ -104,13 +114,23 @@ export async function addRoleToSession(msg) {
104
114
  * 从 session 移除角色
105
115
  */
106
116
  export async function removeRoleFromSession(msg) {
107
- const { crewSessions } = await import('./session.js');
117
+ const { crewSessions, resumeCrewSession } = await import('./session.js');
108
118
 
109
119
  const { sessionId, roleName } = msg;
110
- const session = crewSessions.get(sessionId);
120
+ let session = crewSessions.get(sessionId);
111
121
  if (!session) {
112
- console.warn(`[Crew] Session not found: ${sessionId}`);
113
- return;
122
+ // Auto-resume: try to restore from disk before giving up
123
+ console.log(`[Crew] Session ${sessionId} not in memory, attempting auto-resume...`);
124
+ try {
125
+ await resumeCrewSession({ sessionId });
126
+ session = crewSessions.get(sessionId);
127
+ } catch (e) {
128
+ console.warn(`[Crew] Auto-resume failed for ${sessionId}:`, e.message);
129
+ }
130
+ if (!session) {
131
+ console.warn(`[Crew] Session not found: ${sessionId} (even after auto-resume)`);
132
+ return;
133
+ }
114
134
  }
115
135
 
116
136
  const role = session.roles.get(roleName);
@@ -149,8 +149,12 @@ async function _createRoleQueryInner(session, roleName) {
149
149
 
150
150
  // 继承全局 MCP disallowedTools,避免不必要的 tool schema token 消耗
151
151
  const globalDisallowed = ctx.CONFIG?.disallowedTools || [];
152
- // Crew 角色禁用 Agent 工具,强制通过 ROUTE 块协作
153
- const crewDisallowed = ['Agent'];
152
+ // Crew 角色禁用 Agent Skill 工具:
153
+ // - Agent: 角色间协作必须通过 ROUTE 块,不能自行启动 sub-agent
154
+ // - Skill: skills 的 trigger 词会和角色职能冲突(如 review-code、commit),
155
+ // 导致 Claude 调用 Skill 而不是输出 ROUTE 块。从 schema 层面移除比
156
+ // canCallTool throw 更可靠(完全不消耗 tool schema token)
157
+ const crewDisallowed = ['Agent', 'Skill'];
154
158
  const effectiveDisallowed = [...globalDisallowed, ...crewDisallowed];
155
159
 
156
160
  const queryOptions = {
@@ -164,14 +168,11 @@ async function _createRoleQueryInner(session, roleName) {
164
168
 
165
169
  // Intercept AskUserQuestion for all roles — forward to Web UI for interactive answering.
166
170
  // Without this, non-DM roles' AskCard buttons stay disabled (no askRequestId).
167
- // Also block yeaft-skills in crew mode they inject personas/workflows that interfere with role behavior.
171
+ // Note: Skill tool is fully disallowed via disallowedTools above, so no canCallTool guard needed.
168
172
  queryOptions.canCallTool = async (toolName, input, toolCtx) => {
169
173
  if (toolName === 'AskUserQuestion') {
170
174
  return await handleAskUserQuestion(session.id, input, toolCtx);
171
175
  }
172
- if (toolName === 'Skill' && input?.skill && input.skill.startsWith('yeaft')) {
173
- throw new Error('yeaft-skills are disabled in crew mode — crew roles use ROUTE protocol instead');
174
- }
175
176
  return input;
176
177
  };
177
178
 
package/crew/routing.js CHANGED
@@ -50,7 +50,8 @@ export function parseRoutes(text) {
50
50
  // ★ Clean `to` value: take only the first word (strip parenthetical notes, extra text)
51
51
  // e.g. "pm (决策者)" → "pm", "dev-1 // main dev" → "dev-1"
52
52
  const toRaw = toMatch[1].trim().toLowerCase();
53
- const toClean = toRaw.split(/[\s(]/)[0];
53
+ // Strip trailing punctuation (commas, semicolons, colons, etc.)
54
+ const toClean = toRaw.split(/[\s(]/)[0].replace(/[,;:!?。,;:!?]+$/, '');
54
55
 
55
56
  // ★ summary: match until next known field (task:/taskTitle:) or end of block
56
57
  const summaryMatch = block.match(/summary:\s*([\s\S]+?)(?=\n\s*(?:task|taskTitle)\s*:|$)/i);
@@ -114,9 +115,28 @@ export function resolveRoleName(to, session, fromRole) {
114
115
  }
115
116
  }
116
117
 
118
+ // 4. displayName match (e.g. "乔布斯" → pm)
119
+ if (candidates.length === 0) {
120
+ for (const [name, config] of session.roles) {
121
+ if (config.displayName && config.displayName.toLowerCase() === to) {
122
+ candidates.push({ name, groupIndex: config.groupIndex || 0 });
123
+ }
124
+ }
125
+ }
126
+
127
+ // 5. name-displayName compound match (e.g. "pm-乔布斯" → pm)
128
+ // Claude sometimes concatenates role name + display name with a hyphen
129
+ if (candidates.length === 0) {
130
+ for (const [name, config] of session.roles) {
131
+ if (to.startsWith(name + '-') && to.length > name.length + 1) {
132
+ candidates.push({ name, groupIndex: config.groupIndex || 0 });
133
+ }
134
+ }
135
+ }
136
+
117
137
  if (candidates.length === 0) return null;
118
138
 
119
- // 4. Prefer same groupIndex as sender
139
+ // 6. Prefer same groupIndex as sender
120
140
  if (fromGroupIndex > 0) {
121
141
  const sameGroup = candidates.find(c => c.groupIndex === fromGroupIndex);
122
142
  if (sameGroup) return sameGroup.name;
@@ -215,8 +235,9 @@ export async function executeRoute(session, fromRole, route, turnImages = []) {
215
235
  await dispatchToRole(session, resolvedTo, taskPrompt, fromRole, taskId, taskTitle);
216
236
  }
217
237
  } else {
218
- console.warn(`[Crew] Unknown route target: ${to}`);
219
- const errorMsg = `路由目标 "${to}" 不存在。来自 ${fromRole} 的消息: ${summary}`;
238
+ const availableRoles = Array.from(session.roles.keys()).join(', ');
239
+ console.warn(`[Crew] Unknown route target: ${to} (available: ${availableRoles})`);
240
+ const errorMsg = `路由目标 "${to}" 不存在。可用角色: ${availableRoles}\n来自 ${fromRole} 的消息: ${summary}`;
220
241
  await dispatchToRole(session, session.decisionMaker, errorMsg, 'system');
221
242
  }
222
243
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@yeaft/webchat-agent",
3
- "version": "0.1.377",
3
+ "version": "0.1.379",
4
4
  "description": "Remote agent for Yeaft WebChat — connects worker machines to the central server",
5
5
  "main": "index.js",
6
6
  "type": "module",