@yeaft/webchat-agent 0.1.429 → 0.1.431

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.
@@ -6,6 +6,33 @@ import { dispatchToRole } from './routing.js';
6
6
  import { sendStatusUpdate } from './ui-messages.js';
7
7
  import { debouncedSaveSessionMeta } from './persistence.js';
8
8
 
9
+ /**
10
+ * Resolve @role mention from message content.
11
+ * Returns { target, message } if a valid role is found, null otherwise.
12
+ */
13
+ export function resolveAtMention(content, session) {
14
+ const atMatch = content.match(/^@(\S+)\s*([\s\S]*)/);
15
+ if (!atMatch) return null;
16
+
17
+ const atTarget = atMatch[1];
18
+ const message = atMatch[2].trim() || content;
19
+
20
+ for (const [name, role] of session.roles) {
21
+ if (name === atTarget.toLowerCase()) {
22
+ return { target: name, message };
23
+ }
24
+ if (role.displayName === atTarget) {
25
+ return { target: name, message };
26
+ }
27
+ // Fuzzy: compound "name-displayName" pattern (e.g. "dev-1-托瓦兹" or partial displayName)
28
+ const compound = `${name}-${role.displayName}`;
29
+ if (compound.toLowerCase() === atTarget.toLowerCase()) {
30
+ return { target: name, message };
31
+ }
32
+ }
33
+ return null;
34
+ }
35
+
9
36
  /**
10
37
  * 处理人的输入
11
38
  */
@@ -38,6 +65,13 @@ export async function handleCrewHumanInput(msg) {
38
65
  debouncedSaveSessionMeta(session);
39
66
  }
40
67
 
68
+ // Resolve @role mention from content (works regardless of targetRole from frontend)
69
+ const atMention = resolveAtMention(content, session);
70
+ // Effective target: explicit targetRole from frontend > @mention from content > null
71
+ const effectiveTargetRole = targetRole || (atMention ? atMention.target : null);
72
+ // If @mention found, use the stripped message (without @prefix); otherwise use original content
73
+ const effectiveContent = atMention ? atMention.message : content;
74
+
41
75
  // Build dispatch content (supports image attachments)
42
76
  function buildHumanContent(prefix, text) {
43
77
  if (files && files.length > 0) {
@@ -72,55 +106,35 @@ export async function handleCrewHumanInput(msg) {
72
106
  // Status changed + new human message — persist (debounced, dispatch will follow)
73
107
  debouncedSaveSessionMeta(session);
74
108
 
75
- const target = targetRole || waitingContext?.fromRole || session.decisionMaker;
76
- await dispatchToRole(session, target, buildHumanContent('人工回复:', content), 'human');
109
+ const target = effectiveTargetRole || waitingContext?.fromRole || session.decisionMaker;
110
+ if (effectiveTargetRole) {
111
+ console.log(`[Crew] @mention override in waiting_human: routing to ${target} instead of ${waitingContext?.fromRole || session.decisionMaker}`);
112
+ }
113
+ await dispatchToRole(session, target, buildHumanContent('人工回复:', effectiveContent), 'human');
77
114
  return;
78
115
  }
79
116
 
80
- // 解析 @role 指令
81
- const atMatch = content.match(/^@(\S+)\s*([\s\S]*)/);
82
- if (atMatch) {
83
- const atTarget = atMatch[1];
84
- const message = atMatch[2].trim() || content;
117
+ // @role 指令 — effectiveTargetRole already resolved above
118
+ if (atMention && effectiveTargetRole) {
119
+ const target = effectiveTargetRole;
120
+ const message = effectiveContent;
85
121
 
86
- let target = null;
87
- for (const [name, role] of session.roles) {
88
- if (name === atTarget.toLowerCase()) {
89
- target = name;
90
- break;
91
- }
92
- if (role.displayName === atTarget) {
93
- target = name;
94
- break;
122
+ // 检测纯 skill 命令(如 /context, /simplify),直接发送不加前缀
123
+ if (/^\/[a-zA-Z0-9_-]+(?:\s+.*)?$/s.test(message)) {
124
+ let roleState = session.roleStates.get(target);
125
+ if (!roleState || !roleState.query || !roleState.inputStream) {
126
+ const { createRoleQuery } = await import('./role-query.js');
127
+ roleState = await createRoleQuery(session, target);
95
128
  }
96
- }
97
-
98
- if (target) {
99
- // 检测纯 skill 命令(如 /context, /simplify),直接发送不加前缀
100
- if (/^\/[a-zA-Z0-9_-]+(?:\s+.*)?$/s.test(message)) {
101
- let roleState = session.roleStates.get(target);
102
- if (!roleState || !roleState.query || !roleState.inputStream) {
103
- const { createRoleQuery } = await import('./role-query.js');
104
- roleState = await createRoleQuery(session, target);
105
- }
106
- // P1-4: 守卫 stream.enqueue
107
- try {
108
- if (roleState.inputStream && !roleState.inputStream.isDone) {
109
- roleState.inputStream.enqueue({
110
- type: 'user',
111
- message: { role: 'user', content: message }
112
- });
113
- } else {
114
- console.warn(`[Crew] Skill dispatch: stream closed for ${target}, recreating`);
115
- const { createRoleQuery } = await import('./role-query.js');
116
- roleState = await createRoleQuery(session, target);
117
- roleState.inputStream.enqueue({
118
- type: 'user',
119
- message: { role: 'user', content: message }
120
- });
121
- }
122
- } catch (enqueueErr) {
123
- console.error(`[Crew] Skill dispatch enqueue failed for ${target}:`, enqueueErr.message);
129
+ // P1-4: 守卫 stream.enqueue
130
+ try {
131
+ if (roleState.inputStream && !roleState.inputStream.isDone) {
132
+ roleState.inputStream.enqueue({
133
+ type: 'user',
134
+ message: { role: 'user', content: message }
135
+ });
136
+ } else {
137
+ console.warn(`[Crew] Skill dispatch: stream closed for ${target}, recreating`);
124
138
  const { createRoleQuery } = await import('./role-query.js');
125
139
  roleState = await createRoleQuery(session, target);
126
140
  roleState.inputStream.enqueue({
@@ -128,18 +142,27 @@ export async function handleCrewHumanInput(msg) {
128
142
  message: { role: 'user', content: message }
129
143
  });
130
144
  }
131
- sendStatusUpdate(session);
132
- console.log(`[Crew] Skill command dispatched to ${target}: ${message}`);
133
- return;
145
+ } catch (enqueueErr) {
146
+ console.error(`[Crew] Skill dispatch enqueue failed for ${target}:`, enqueueErr.message);
147
+ const { createRoleQuery } = await import('./role-query.js');
148
+ roleState = await createRoleQuery(session, target);
149
+ roleState.inputStream.enqueue({
150
+ type: 'user',
151
+ message: { role: 'user', content: message }
152
+ });
134
153
  }
135
- await dispatchToRole(session, target, buildHumanContent('人工消息:', message), 'human');
154
+ sendStatusUpdate(session);
155
+ console.log(`[Crew] Skill command dispatched to ${target}: ${message}`);
136
156
  return;
137
157
  }
158
+ console.log(`[Crew] @mention routing to ${target}: ${message.substring(0, 50)}...`);
159
+ await dispatchToRole(session, target, buildHumanContent('人工消息:', message), 'human');
160
+ return;
138
161
  }
139
162
 
140
163
  // 默认发给决策者
141
- const target = targetRole || session.decisionMaker;
142
- await dispatchToRole(session, target, buildHumanContent('人工消息:', content), 'human');
164
+ const target = effectiveTargetRole || session.decisionMaker;
165
+ await dispatchToRole(session, target, buildHumanContent('人工消息:', effectiveContent), 'human');
143
166
  }
144
167
 
145
168
  /**
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@yeaft/webchat-agent",
3
- "version": "0.1.429",
3
+ "version": "0.1.431",
4
4
  "description": "Remote agent for Yeaft WebChat — connects worker machines to the central server",
5
5
  "main": "index.js",
6
6
  "type": "module",
package/unify/index.js CHANGED
@@ -37,5 +37,6 @@ export { MCPManager, createMCPManager } from './mcp.js';
37
37
  export { SkillManager, createSkillManager, parseSkill, serializeSkill } from './skills.js';
38
38
  export { defineTool } from './tools/types.js';
39
39
  export { ToolRegistry, createEmptyRegistry } from './tools/registry.js';
40
+ export { createFullRegistry, allTools } from './tools/index.js';
40
41
  export { loadSession } from './session.js';
41
42
 
package/unify/session.js CHANGED
@@ -21,16 +21,10 @@ import { ConversationStore } from './conversation/persist.js';
21
21
  import { MemoryStore } from './memory/store.js';
22
22
  import { SkillManager, createSkillManager } from './skills.js';
23
23
  import { MCPManager } from './mcp.js';
24
- import { createEmptyRegistry } from './tools/registry.js';
24
+ import { createFullRegistry } from './tools/index.js';
25
25
  import { Engine } from './engine.js';
26
26
  import { join } from 'path';
27
27
 
28
- // Built-in tools
29
- import mcpTools from './tools/mcp-tools.js';
30
- import skillTool from './tools/skill.js';
31
- import enterWorktree from './tools/enter-worktree.js';
32
- import exitWorktree from './tools/exit-worktree.js';
33
-
34
28
  /**
35
29
  * @typedef {Object} SessionOptions
36
30
  * @property {string} [dir] — Yeaft data directory override (default: ~/.yeaft)
@@ -129,15 +123,7 @@ export async function loadSession(options = {}) {
129
123
  }
130
124
 
131
125
  // ─── 8. Build tool registry ────────────────────────────
132
- const toolRegistry = createEmptyRegistry();
133
-
134
- // Register built-in tools
135
- for (const tool of mcpTools) {
136
- toolRegistry.register(tool);
137
- }
138
- toolRegistry.register(skillTool);
139
- toolRegistry.register(enterWorktree);
140
- toolRegistry.register(exitWorktree);
126
+ const toolRegistry = createFullRegistry();
141
127
 
142
128
  // Register any extra tools from caller
143
129
  for (const tool of extraTools) {
@@ -0,0 +1,37 @@
1
+ /**
2
+ * tools/index.js — All built-in tools + createFullRegistry()
3
+ *
4
+ * Central barrel file that imports every built-in tool and exposes a
5
+ * factory function to create a ToolRegistry pre-loaded with all of them.
6
+ *
7
+ * session.js should use createFullRegistry() instead of createEmptyRegistry()
8
+ * to ensure all built-in tools are available to the LLM.
9
+ */
10
+
11
+ import { ToolRegistry } from './registry.js';
12
+ import mcpTools from './mcp-tools.js';
13
+ import skillTool from './skill.js';
14
+ import enterWorktree from './enter-worktree.js';
15
+ import exitWorktree from './exit-worktree.js';
16
+
17
+ /**
18
+ * All built-in tools, flattened into a single array.
19
+ * mcpTools is already an array; the rest are single ToolDef objects.
20
+ * @type {import('./types.js').ToolDef[]}
21
+ */
22
+ export const allTools = [
23
+ ...mcpTools,
24
+ skillTool,
25
+ enterWorktree,
26
+ exitWorktree,
27
+ ];
28
+
29
+ /**
30
+ * Create a ToolRegistry pre-loaded with all built-in tools.
31
+ * @returns {ToolRegistry}
32
+ */
33
+ export function createFullRegistry() {
34
+ const registry = new ToolRegistry();
35
+ registry.registerAll(allTools);
36
+ return registry;
37
+ }