@yeaft/webchat-agent 0.1.352 → 0.1.354

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.
@@ -185,6 +185,10 @@ export async function processRoleOutput(session, roleName, roleQuery, roleState)
185
185
  if (routes.length > 0) {
186
186
  session.round++;
187
187
 
188
+ // ★ Collect turn images for auto-attach (last 3, then clear)
189
+ const turnImages = roleState.turnImages || [];
190
+ roleState.turnImages = [];
191
+
188
192
  const currentTask = roleState.currentTask;
189
193
  for (const route of routes) {
190
194
  if (!route.taskId && currentTask) {
@@ -203,7 +207,7 @@ export async function processRoleOutput(session, roleName, roleQuery, roleState)
203
207
  });
204
208
 
205
209
  const results = await Promise.allSettled(routes.map(route =>
206
- executeRoute(session, roleName, route)
210
+ executeRoute(session, roleName, route, turnImages)
207
211
  ));
208
212
  for (const r of results) {
209
213
  if (r.status === 'rejected') {
package/crew/routing.js CHANGED
@@ -14,6 +14,24 @@ function roleLabel(r) {
14
14
  return r.icon ? `${r.icon} ${r.displayName}` : r.displayName;
15
15
  }
16
16
 
17
+ /**
18
+ * Append text to content — works for both string and multimodal array content.
19
+ * For arrays, appends to the last text block (or adds a new one).
20
+ */
21
+ function _appendTextToContent(content, text) {
22
+ if (typeof content === 'string') return content + text;
23
+ // Multimodal array: find last text block and append
24
+ for (let i = content.length - 1; i >= 0; i--) {
25
+ if (content[i].type === 'text') {
26
+ content[i].text += text;
27
+ return content;
28
+ }
29
+ }
30
+ // No text block found — add one
31
+ content.push({ type: 'text', text });
32
+ return content;
33
+ }
34
+
17
35
  /**
18
36
  * 从累积文本中解析所有 ROUTE 块(支持多 ROUTE + task 字段)
19
37
  * @returns {Array<{ to, summary, taskId, taskTitle }>}
@@ -110,8 +128,9 @@ export function resolveRoleName(to, session, fromRole) {
110
128
 
111
129
  /**
112
130
  * 执行路由
131
+ * @param {Array<{mimeType, data}>} [turnImages] - auto-attached images from the turn (max 3)
113
132
  */
114
- export async function executeRoute(session, fromRole, route) {
133
+ export async function executeRoute(session, fromRole, route, turnImages = []) {
115
134
  const { to, summary, taskId, taskTitle } = route;
116
135
 
117
136
  // 如果 session 已暂停或停止,保存为 pendingRoutes
@@ -156,7 +175,12 @@ export async function executeRoute(session, fromRole, route) {
156
175
  sendCrewOutput(session, fromRole, 'route', null, {
157
176
  routeTo: to, routeSummary: summary,
158
177
  taskId: taskId || undefined,
159
- taskTitle: taskTitle || undefined
178
+ taskTitle: taskTitle || undefined,
179
+ // ★ Auto-attach turn images (base64) — server will cache and convert to fileId/previewToken
180
+ routeImages: turnImages.length > 0 ? turnImages.map(img => ({
181
+ mimeType: img.mimeType,
182
+ data: img.data
183
+ })) : undefined
160
184
  });
161
185
 
162
186
  // 路由到 human
@@ -187,7 +211,7 @@ export async function executeRoute(session, fromRole, route) {
187
211
  const { processHumanQueue } = await import('./human-interaction.js');
188
212
  await processHumanQueue(session);
189
213
  } else {
190
- const taskPrompt = buildRoutePrompt(fromRole, summary, session);
214
+ const taskPrompt = buildRoutePrompt(fromRole, summary, session, turnImages);
191
215
  await dispatchToRole(session, resolvedTo, taskPrompt, fromRole, taskId, taskTitle);
192
216
  }
193
217
  } else {
@@ -198,12 +222,27 @@ export async function executeRoute(session, fromRole, route) {
198
222
  }
199
223
 
200
224
  /**
201
- * 构建路由转发的 prompt
225
+ * 构建路由转发的 prompt(支持多模态 — 自动附加 turn 截图)
226
+ * @param {Array<{mimeType, data}>} [turnImages] - auto-attached images
227
+ * @returns {string|Array} text string, or multimodal content array when images present
202
228
  */
203
- export function buildRoutePrompt(fromRole, summary, session) {
229
+ export function buildRoutePrompt(fromRole, summary, session, turnImages = []) {
204
230
  const fromRoleConfig = session.roles.get(fromRole);
205
231
  const fromName = fromRoleConfig ? roleLabel(fromRoleConfig) : fromRole;
206
- return `来自 ${fromName} 的消息:\n${summary}\n\n请开始你的工作。完成后通过 ROUTE 块传递给下一个角色。`;
232
+ const text = `来自 ${fromName} 的消息:\n${summary}\n\n请开始你的工作。完成后通过 ROUTE 块传递给下一个角色。`;
233
+
234
+ if (turnImages.length === 0) return text;
235
+
236
+ // Build multimodal content: images first, then text
237
+ const blocks = [];
238
+ for (const img of turnImages) {
239
+ blocks.push({
240
+ type: 'image',
241
+ source: { type: 'base64', media_type: img.mimeType, data: img.data }
242
+ });
243
+ }
244
+ blocks.push({ type: 'text', text });
245
+ return blocks;
207
246
  }
208
247
 
209
248
  /**
@@ -229,38 +268,44 @@ export async function dispatchToRole(session, roleName, content, fromSource, tas
229
268
 
230
269
  // Task 上下文注入
231
270
  const effectiveTaskId = taskId || roleState.currentTask?.taskId;
232
- if (effectiveTaskId && typeof content === 'string') {
271
+ if (effectiveTaskId) {
233
272
  const taskContent = await readTaskFile(session, effectiveTaskId);
234
273
  if (taskContent) {
235
- content = `${content}\n\n---\n<task-context file=".crew/context/features/${effectiveTaskId}.md">\n${taskContent}\n</task-context>`;
274
+ const ctx = `\n\n---\n<task-context file=".crew/context/features/${effectiveTaskId}.md">\n${taskContent}\n</task-context>`;
275
+ content = _appendTextToContent(content, ctx);
236
276
  }
237
277
  }
238
278
 
239
279
  // 看板上下文注入(角色重启后知道全局状态)
240
- if (typeof content === 'string') {
280
+ {
241
281
  const kanbanContent = await readKanban(session);
242
282
  if (kanbanContent) {
243
- content = `${content}\n\n---\n<kanban file=".crew/context/kanban.md">\n${kanbanContent}\n</kanban>`;
283
+ const ctx = `\n\n---\n<kanban file=".crew/context/kanban.md">\n${kanbanContent}\n</kanban>`;
284
+ content = _appendTextToContent(content, ctx);
244
285
  }
245
286
  }
246
287
 
247
288
  // 最近路由消息注入(帮助 clear 后的角色恢复上下文)
248
- if (typeof content === 'string' && session.messageHistory.length > 0) {
289
+ if (session.messageHistory.length > 0) {
249
290
  const recentRoutes = session.messageHistory
250
291
  .filter(m => m.from !== 'system')
251
292
  .slice(-5)
252
293
  .map(m => `[${m.from} → ${m.to}${m.taskId ? ` (${m.taskId})` : ''}] ${m.content}`)
253
294
  .join('\n');
254
295
  if (recentRoutes) {
255
- content = `${content}\n\n---\n<recent-routes>\n${recentRoutes}\n</recent-routes>`;
296
+ const ctx = `\n\n---\n<recent-routes>\n${recentRoutes}\n</recent-routes>`;
297
+ content = _appendTextToContent(content, ctx);
256
298
  }
257
299
  }
258
300
 
259
301
  // 记录消息历史
302
+ const historyContent = typeof content === 'string'
303
+ ? content.substring(0, 200)
304
+ : (Array.isArray(content) ? content.filter(b => b.type === 'text').map(b => b.text).join('').substring(0, 200) + (content.some(b => b.type === 'image') ? ' [+images]' : '') : '...');
260
305
  session.messageHistory.push({
261
306
  from: fromSource,
262
307
  to: roleName,
263
- content: typeof content === 'string' ? content.substring(0, 200) : '...',
308
+ content: historyContent,
264
309
  taskId: taskId || roleState.currentTask?.taskId || null,
265
310
  timestamp: Date.now()
266
311
  });
@@ -200,6 +200,19 @@ export function sendCrewOutput(session, roleName, outputType, rawMessage, extra
200
200
  taskId, taskTitle, isDecisionMaker,
201
201
  timestamp: Date.now()
202
202
  });
203
+ // ★ Collect turn images for auto-attach on ROUTE (last 3 per turn)
204
+ const roleState = session.roleStates.get(roleName);
205
+ if (roleState) {
206
+ if (!roleState.turnImages) roleState.turnImages = [];
207
+ roleState.turnImages.push({
208
+ mimeType: item.source.media_type,
209
+ data: item.source.data
210
+ });
211
+ // Cap at last 3 images per turn
212
+ if (roleState.turnImages.length > 3) {
213
+ roleState.turnImages = roleState.turnImages.slice(-3);
214
+ }
215
+ }
203
216
  }
204
217
  }
205
218
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@yeaft/webchat-agent",
3
- "version": "0.1.352",
3
+ "version": "0.1.354",
4
4
  "description": "Remote agent for Yeaft WebChat — connects worker machines to the central server",
5
5
  "main": "index.js",
6
6
  "type": "module",