@yeaft/webchat-agent 0.1.825 → 0.1.827

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@yeaft/webchat-agent",
3
- "version": "0.1.825",
3
+ "version": "0.1.827",
4
4
  "description": "Remote agent for Yeaft WebChat — connects worker machines to the central server",
5
5
  "main": "index.js",
6
6
  "type": "module",
@@ -31,8 +31,10 @@ import { ToolRegistry } from '../tools/registry.js';
31
31
  import { buildSpawnedPreamble } from './spawned-prompt.js';
32
32
 
33
33
  const RESTRICTED_TOOLS = new Set([
34
- 'Agent',
35
- 'SendMessage',
34
+ 'SpawnAgent',
35
+ 'Agent', // legacy alias
36
+ 'PromptAgent',
37
+ 'SendMessage', // legacy alias
36
38
  'WaitAgent',
37
39
  'CloseAgent',
38
40
  'ListAgents',
@@ -38,7 +38,7 @@ export function buildSpawnedPreamble({ parentName, parentVpId, agentName, missio
38
38
  m || '(无具体任务说明)',
39
39
  '',
40
40
  '## 行为约束',
41
- '- 不要再 spawn sub-agent(你已经没有 Agent / SendMessage / WaitAgent / CloseAgent 工具)。',
41
+ '- 不要再 spawn sub-agent(你已经没有 SpawnAgent / PromptAgent / WaitAgent / CloseAgent 工具)。',
42
42
  '- 不要 route_forward 给别的 VP,不要 ask_user。',
43
43
  '- 完成时直接以 markdown 自由文本回复(end_turn)。建议结构:"## 结果" / "## 关键发现" / "## 遗留问题"。',
44
44
  '- 失败/不可行也要明确说出来,不要假装完成。父 VP 会读你的最终消息。',
@@ -55,7 +55,7 @@ export function buildSpawnedPreamble({ parentName, parentVpId, agentName, missio
55
55
  m || '(no mission body provided)',
56
56
  '',
57
57
  '## Constraints',
58
- '- Do NOT spawn further sub-agents (Agent / SendMessage / WaitAgent / CloseAgent are not in your toolset).',
58
+ '- Do NOT spawn further sub-agents (SpawnAgent / PromptAgent / WaitAgent / CloseAgent are not in your toolset).',
59
59
  '- Do NOT use route_forward to other VPs. Do NOT ask_user.',
60
60
  '- When done, reply in free markdown (end_turn). Suggested structure: "## Result" / "## Key findings" / "## Open questions".',
61
61
  '- If the mission is infeasible or you fail, say so plainly. The parent will read your final message.',
@@ -174,7 +174,8 @@ export function tickAgent(agentId, delta = {}, now = Date.now()) {
174
174
  }
175
175
 
176
176
  export default defineTool({
177
- name: 'Agent',
177
+ name: 'SpawnAgent',
178
+ aliases: ['Agent'],
178
179
  description: `Create a sub-agent to work on an independent task in parallel.
179
180
 
180
181
  Sub-agents run in their own context and can be given a concrete mission
@@ -189,7 +190,7 @@ Guidelines:
189
190
  - Give a clear, focused mission — what "done" looks like
190
191
  - Use expected_output to pin the structure you want back
191
192
  - Always set a budget for unbounded missions
192
- - Use SendMessage to communicate, WaitAgent to collect results, CloseAgent to finalize`,
193
+ - Use PromptAgent to communicate, WaitAgent to collect results, CloseAgent to finalize`,
193
194
  parameters: {
194
195
  type: 'object',
195
196
  properties: {
@@ -302,7 +303,7 @@ Guidelines:
302
303
  persona: spec.persona || null,
303
304
  budget: spec.budget || null,
304
305
  status: agent.status,
305
- message: `Sub-agent "${name}" spawned (${agentId}). Use WaitAgent to collect its first turn output, SendMessage to give it more work, CloseAgent to finish.`,
306
+ message: `Sub-agent "${name}" spawned (${agentId}). Use WaitAgent to collect its first turn output, PromptAgent to give it more work, CloseAgent to finish.`,
306
307
  });
307
308
  },
308
309
  });
@@ -18,7 +18,6 @@ import exitWorktree from './exit-worktree.js';
18
18
 
19
19
  // --- P0 Core tools ---
20
20
  import askUser from './ask-user.js';
21
- import openSourceMessage from './open-source-message.js';
22
21
  import webSearch from './web-search.js';
23
22
  import webFetch from './web-fetch.js';
24
23
  import historySearch from './history-search.js';
@@ -66,7 +65,6 @@ import { jsRepl, jsReplReset } from './js-repl.js';
66
65
  import notebookEdit from './notebook-edit.js';
67
66
  import imageGeneration from './image-generation.js';
68
67
  import viewImage from './view-image.js';
69
- import requestPermissions from './request-permissions.js';
70
68
 
71
69
  /**
72
70
  * All built-in tools, flattened into a single array.
@@ -82,7 +80,6 @@ export const allTools = [
82
80
 
83
81
  // P0 Core
84
82
  askUser,
85
- openSourceMessage,
86
83
  webSearch,
87
84
  webFetch,
88
85
  historySearch,
@@ -117,7 +114,6 @@ export const allTools = [
117
114
  notebookEdit,
118
115
  imageGeneration,
119
116
  viewImage,
120
- requestPermissions,
121
117
  ];
122
118
 
123
119
  /**
@@ -226,6 +226,17 @@ export class ToolRegistry {
226
226
  register(tool) {
227
227
  if (!tool || !tool.name) throw new Error('Tool must have a name');
228
228
  this.#tools.set(tool.name, tool);
229
+ // Legacy-name aliases: keep historical jsonl tool_calls resolvable
230
+ // after a rename. The alias entry shares the same tool object so
231
+ // execute/has lookups succeed, but `getToolDefs()` dedupes by tool
232
+ // identity so the LLM only sees the canonical name.
233
+ if (Array.isArray(tool.aliases)) {
234
+ for (const alias of tool.aliases) {
235
+ if (typeof alias === 'string' && alias && alias !== tool.name) {
236
+ this.#tools.set(alias, tool);
237
+ }
238
+ }
239
+ }
229
240
  return this;
230
241
  }
231
242
 
@@ -269,10 +280,21 @@ export class ToolRegistry {
269
280
 
270
281
  /**
271
282
  * Get all registered tools (unfiltered).
283
+ *
284
+ * Dedupes alias entries — when a tool is registered with `aliases`,
285
+ * the same ToolDef object lives at multiple Map keys. We return each
286
+ * tool object at most once (under its canonical `tool.name`).
272
287
  * @returns {import('./types.js').ToolDef[]}
273
288
  */
274
289
  getAllTools() {
275
- return Array.from(this.#tools.values());
290
+ const seen = new Set();
291
+ const out = [];
292
+ for (const tool of this.#tools.values()) {
293
+ if (seen.has(tool)) continue;
294
+ seen.add(tool);
295
+ out.push(tool);
296
+ }
297
+ return out;
276
298
  }
277
299
 
278
300
  /**
@@ -291,11 +313,12 @@ export class ToolRegistry {
291
313
  }
292
314
 
293
315
  /**
294
- * Get all registered tool names.
316
+ * Get all registered tool names (canonical only — aliases are excluded
317
+ * so debug surfaces like the tool-stats panel show one row per tool).
295
318
  * @returns {string[]}
296
319
  */
297
320
  getToolNames() {
298
- return Array.from(this.#tools.keys());
321
+ return this.getAllTools().map(t => t.name);
299
322
  }
300
323
 
301
324
  /**
@@ -337,12 +360,12 @@ export class ToolRegistry {
337
360
 
338
361
  /** Number of registered tools. */
339
362
  get size() {
340
- return this.#tools.size;
363
+ return this.getAllTools().length;
341
364
  }
342
365
 
343
- /** All registered tool names. */
366
+ /** All registered tool names (canonical only; aliases excluded). */
344
367
  get names() {
345
- return Array.from(this.#tools.keys());
368
+ return this.getAllTools().map(t => t.name);
346
369
  }
347
370
  }
348
371
 
@@ -1,16 +1,20 @@
1
1
  /**
2
- * send-message.js — Send a message to a sub-agent.
2
+ * send-message.js — Send a follow-up prompt to a sub-agent.
3
+ *
4
+ * Tool name: PromptAgent (canonical) / SendMessage (legacy alias for
5
+ * historical jsonl replay — see registry alias map).
3
6
  */
4
7
 
5
8
  import { defineTool } from './types.js';
6
9
  import { getAgentRegistry } from './agent.js';
7
10
 
8
11
  export default defineTool({
9
- name: 'SendMessage',
10
- description: `Send a message to a sub-agent.
12
+ name: 'PromptAgent',
13
+ aliases: ['SendMessage'],
14
+ description: `Send a follow-up prompt to a sub-agent you previously spawned.
11
15
 
12
- Use this to give tasks, provide instructions, or relay information to a sub-agent.
13
- The message is queued for the agent to process.`,
16
+ Use this to give the sub-agent more work, additional instructions, or relay
17
+ information. The prompt is queued for the agent to process on its next turn.`,
14
18
  parameters: {
15
19
  type: 'object',
16
20
  properties: {
@@ -74,6 +74,7 @@
74
74
  */
75
75
  export function defineTool({
76
76
  name,
77
+ aliases,
77
78
  description,
78
79
  parameters,
79
80
  execute,
@@ -94,6 +95,12 @@ export function defineTool({
94
95
  isReadOnly,
95
96
  isDestructive,
96
97
  };
98
+ // Legacy tool-name aliases. Registered as extra lookup keys so old
99
+ // jsonl tool_calls (e.g. `SendMessage` → `PromptAgent`) keep resolving,
100
+ // but excluded from the LLM-visible catalogue.
101
+ if (Array.isArray(aliases) && aliases.length > 0) {
102
+ def.aliases = aliases.slice();
103
+ }
97
104
  // Only attach `timeoutMs` when the tool author opts in. Leaving it
98
105
  // unset means ToolRegistry.execute uses DEFAULT_TOOL_TIMEOUT_MS — set
99
106
  // to <= 0 to disable the per-tool timeout entirely.
@@ -10,7 +10,7 @@ export default defineTool({
10
10
  description: `Wait for a sub-agent to complete its task and retrieve the result.
11
11
 
12
12
  Returns the agent's final result or current status if still running.
13
- Use after sending a task to an agent via SendMessage.`,
13
+ Use after sending a task to an agent via PromptAgent.`,
14
14
  parameters: {
15
15
  type: 'object',
16
16
  properties: {
@@ -562,6 +562,8 @@ function projectPersistedToHistoryEntry(m) {
562
562
  if (isPersistedInternalMessage(m)) return null;
563
563
  const entry = { role: m.role, content: m.role === 'tool' ? m.content : __testNormalizePersistedVisibleContent(m.content) };
564
564
  if (m.id) entry.id = m.id;
565
+ entry.threadId = m.threadId || m.turnId || 'main';
566
+ entry.turnId = m.turnId || entry.threadId;
565
567
  if (m.groupId) entry.groupId = m.groupId;
566
568
  if (m.speakerVpId) entry.speakerVpId = m.speakerVpId;
567
569
  if (m.toolCallId) entry.toolCallId = m.toolCallId;
@@ -2268,6 +2270,7 @@ export async function handleUnifyGroupChat(msg) {
2268
2270
  let report;
2269
2271
  try {
2270
2272
  report = coord.ingest({
2273
+ id: typeof msg.id === 'string' && msg.id ? msg.id : undefined,
2271
2274
  from: 'user',
2272
2275
  role: 'user',
2273
2276
  text,
@@ -3585,7 +3588,7 @@ export async function handleUnifyLoadHistory(msg) {
3585
3588
  ...(Array.isArray(entry.attachments) && entry.attachments.length > 0 ? { attachments: hydrateHistoryAttachmentPreviews(entry.attachments) } : {}),
3586
3589
  },
3587
3590
  ts: entry.ts || null,
3588
- }, { groupId: entry.groupId || null });
3591
+ }, { groupId: entry.groupId || null, threadId: entry.threadId || 'main', turnId: entry.turnId || entry.threadId || 'main' });
3589
3592
  } else if (entry.role === 'assistant') {
3590
3593
  // speakerVpId rides on the envelope so the frontend can route this
3591
3594
  // replayed assistant text to the correct VP track. Without it, the
@@ -3593,6 +3596,8 @@ export async function handleUnifyLoadHistory(msg) {
3593
3596
  // anonymous assistant turn.
3594
3597
  const envelopeOpts = {
3595
3598
  groupId: entry.groupId || null,
3599
+ threadId: entry.threadId || 'main',
3600
+ turnId: entry.turnId || entry.threadId || 'main',
3596
3601
  };
3597
3602
  if (entry.speakerVpId) envelopeOpts.vpId = entry.speakerVpId;
3598
3603
  sendUnifyOutput({
@@ -3676,6 +3681,8 @@ export async function handleUnifyLoadMoreHistory(msg) {
3676
3681
  role: m.role,
3677
3682
  content: m.content,
3678
3683
  groupId: m.groupId || null,
3684
+ threadId: m.threadId || m.turnId || 'main',
3685
+ turnId: m.turnId || m.threadId || 'main',
3679
3686
  ...(Array.isArray(m.attachments) && m.attachments.length > 0 ? { attachments: hydrateHistoryAttachmentPreviews(m.attachments) } : {}),
3680
3687
  ...(m.speakerVpId ? { speakerVpId: m.speakerVpId } : {}),
3681
3688
  }));
@@ -1,49 +0,0 @@
1
- /**
2
- * open-source-message.js — task-334f R6 §Δ24.4.
3
- *
4
- * Low-level random access: given a (groupId, msgId), fetch the raw message
5
- * from the group's jsonl log. Used when a VP has an exact pointer but does
6
- * not want to run the memory_trace wrapper (5% case: audit / debug).
7
- */
8
-
9
- import { defineTool } from './types.js';
10
-
11
- export default defineTool({
12
- name: 'open_source_message',
13
- description: `Open a single source message by (groupId, msgId).
14
-
15
- This is the low-level random-access primitive. Prefer memory_trace if you are
16
- starting from a memory entry. Returns JSON: { message } or { error }.`,
17
- parameters: {
18
- type: 'object',
19
- properties: {
20
- groupId: { type: 'string', description: 'Group id' },
21
- msgId: { type: 'string', description: 'Message id' },
22
- },
23
- required: ['groupId', 'msgId'],
24
- },
25
- isConcurrencySafe: () => true,
26
- isReadOnly: () => true,
27
- async execute(input, ctx) {
28
- const { groupId, msgId } = input || {};
29
- if (!groupId || !msgId) {
30
- return JSON.stringify({ error: 'groupId and msgId required' });
31
- }
32
- const coordinator = ctx?.coordinator;
33
- if (!coordinator || typeof coordinator.openGroup !== 'function') {
34
- return JSON.stringify({ error: 'group coordinator not available' });
35
- }
36
- const group = coordinator.openGroup(groupId);
37
- if (!group) return JSON.stringify({ error: `group not found: ${groupId}` });
38
-
39
- const iter = typeof group.readMessageRange === 'function'
40
- ? group.readMessageRange(msgId, msgId)
41
- : group.streamMessages();
42
- for (const msg of iter) {
43
- if (msg.id === msgId) {
44
- return JSON.stringify({ message: msg });
45
- }
46
- }
47
- return JSON.stringify({ error: `message not found: ${msgId} in ${groupId}` });
48
- },
49
- });
@@ -1,59 +0,0 @@
1
- /**
2
- * request-permissions.js — Request permission for dangerous operations.
3
- *
4
- * When an operation is flagged as destructive, this tool requests
5
- * explicit user permission before proceeding.
6
- */
7
-
8
- import { defineTool } from './types.js';
9
-
10
- export default defineTool({
11
- name: 'RequestPermissions',
12
- description: `Request permission from the user for a potentially dangerous operation.
13
-
14
- Use this before executing destructive operations like:
15
- - Deleting files or directories
16
- - Running commands that modify system state
17
- - Force-pushing to git
18
- - Resetting databases
19
-
20
- The user must explicitly approve before you proceed.`,
21
- parameters: {
22
- type: 'object',
23
- properties: {
24
- operation: {
25
- type: 'string',
26
- description: 'Description of the operation that needs permission',
27
- },
28
- reason: {
29
- type: 'string',
30
- description: 'Why this operation is necessary',
31
- },
32
- risk_level: {
33
- type: 'string',
34
- enum: ['low', 'medium', 'high', 'critical'],
35
- description: 'Risk level of the operation',
36
- },
37
- },
38
- required: ['operation'],
39
- },
40
- isConcurrencySafe: () => false,
41
- isReadOnly: () => true,
42
- async execute(input, ctx) {
43
- const { operation, reason, risk_level = 'medium' } = input;
44
- if (!operation) return JSON.stringify({ error: 'operation is required' });
45
-
46
- // In a full integration, this would use the ask_user mechanism
47
- // to get explicit permission. For now, return a structured request.
48
- return JSON.stringify({
49
- type: 'permission_request',
50
- operation,
51
- reason: reason || 'Operation requires explicit permission',
52
- riskLevel: risk_level,
53
- message: `⚠️ Permission required for: ${operation}` +
54
- (reason ? `\nReason: ${reason}` : '') +
55
- `\nRisk level: ${risk_level}`,
56
- hint: 'User must explicitly approve this operation before proceeding.',
57
- });
58
- },
59
- });