@yeaft/webchat-agent 1.0.555 → 1.0.557

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.
@@ -170,19 +170,33 @@ function hydrateInlinePreviewData(data) {
170
170
  return message === data.message ? data : { ...data, message };
171
171
  }
172
172
 
173
- function projectConfirmedAssetImages(messages, { ownerId, agentId, sessionId }) {
174
- const turnIds = messages
175
- .filter(message => message?.role === 'assistant' && message.imageAssetAnchor === true)
173
+ export function projectConfirmedAssetImages(messages, { ownerId, agentId, sessionId }, assetStore = yeaftAssetStore) {
174
+ const turnIds = [...new Set(messages
175
+ .filter(message => message?.role === 'assistant')
176
176
  .map(message => message.turnId || message.threadId || null)
177
- .filter(Boolean);
178
- const imagesByTurn = yeaftAssetStore.describeTurns({ ownerId, agentId, sessionId, turnIds });
177
+ .filter(Boolean))];
178
+ const imagesByTurn = assetStore.describeTurns({ ownerId, agentId, sessionId, turnIds });
179
179
  return messages.map((message) => {
180
180
  if (!message || message.role !== 'assistant') return message;
181
181
  const { images: _pendingImages, ...rest } = message;
182
- if (message.imageAssetAnchor !== true) return rest;
183
182
  const turnId = message.turnId || message.threadId || null;
184
183
  const images = turnId ? imagesByTurn.get(turnId) || [] : [];
185
- return images.length > 0 ? { ...rest, images } : rest;
184
+ const toolCallIds = new Set((Array.isArray(message.toolCalls) ? message.toolCalls : [])
185
+ .map(toolCall => toolCall?.id)
186
+ .filter(Boolean));
187
+ const vpId = message.speakerVpId || message.vpId || null;
188
+ const projectedImages = images.filter(image => {
189
+ if (image.vpId && image.vpId !== vpId) return false;
190
+ return image.sourceToolCallId
191
+ ? toolCallIds.has(image.sourceToolCallId)
192
+ : message.imageAssetAnchor === true;
193
+ });
194
+ const toolOrder = [...toolCallIds];
195
+ projectedImages.sort((a, b) => (
196
+ toolOrder.indexOf(a.sourceToolCallId) - toolOrder.indexOf(b.sourceToolCallId)
197
+ || (a.sourceImageIndex ?? 0) - (b.sourceImageIndex ?? 0)
198
+ ));
199
+ return projectedImages.length > 0 ? { ...rest, images: projectedImages } : rest;
186
200
  });
187
201
  }
188
202
 
@@ -550,6 +564,8 @@ export async function handleAgentOutput(agentId, agent, msg) {
550
564
  width: msg.metadata?.width ?? msg.image.width,
551
565
  height: msg.metadata?.height ?? msg.image.height,
552
566
  turnId: msg.turnId || null,
567
+ sourceToolCallId: msg.sourceToolCallId || null,
568
+ sourceImageIndex: msg.sourceImageIndex,
553
569
  vpId: msg.vpId || null,
554
570
  threadId: msg.threadId || null,
555
571
  });
@@ -560,7 +576,11 @@ export async function handleAgentOutput(agentId, agent, msg) {
560
576
  ...(msg.vpId ? { vpId: msg.vpId } : {}),
561
577
  ...(msg.turnId ? { turnId: msg.turnId } : {}),
562
578
  ...(msg.threadId ? { threadId: msg.threadId } : {}),
563
- image, _requestUserId: agent.ownerId,
579
+ image: msg.sourceToolCallId
580
+ ? { ...image, sourceToolCallId: msg.sourceToolCallId,
581
+ ...(Number.isInteger(msg.sourceImageIndex) && msg.sourceImageIndex >= 0 ? { sourceImageIndex: msg.sourceImageIndex } : {}) }
582
+ : image,
583
+ _requestUserId: agent.ownerId,
564
584
  });
565
585
  ok = true;
566
586
  } catch (err) {
@@ -196,7 +196,7 @@ export function createYeaftAssetStore({
196
196
  collectGarbage();
197
197
 
198
198
  return {
199
- put({ ownerId, agentId, sessionId, assetId, data, mimeType, filename, width = null, height = null, turnId = null, vpId = null, threadId = null }) {
199
+ put({ ownerId, agentId, sessionId, assetId, data, mimeType, filename, width = null, height = null, turnId = null, vpId = null, threadId = null, sourceToolCallId = null, sourceImageIndex = null }) {
200
200
  if (!ownerId || !agentId || !sessionId) throw new Error('Asset owner, agent, and Session are required');
201
201
  const buffer = Buffer.isBuffer(data) ? data : Buffer.from(String(data || ''), 'base64');
202
202
  if (!buffer.length || buffer.length > MAX_ASSET_BYTES) throw new Error(`Image asset must be between 1 byte and ${MAX_ASSET_BYTES} bytes`);
@@ -222,6 +222,25 @@ export function createYeaftAssetStore({
222
222
  if (duplicate) {
223
223
  try { existing = JSON.parse(readFileSync(paths.meta, 'utf8')); } catch { /* rewrite below */ }
224
224
  }
225
+ // An asset is content-addressed, but each use owns its own turn/tool/VP
226
+ // anchor. Preserve old unanchored uses when the same pixels are uploaded again.
227
+ const turnAssociations = Array.isArray(existing?.turnAssociations)
228
+ ? existing.turnAssociations.filter(association => association
229
+ && typeof association.turnId === 'string' && association.turnId)
230
+ : [...new Set([existing?.turnId, ...(existing?.turnIds || [])].filter(Boolean))]
231
+ .map(legacyTurnId => ({ turnId: legacyTurnId }));
232
+ if (turnId) {
233
+ const association = {
234
+ turnId,
235
+ ...(typeof sourceToolCallId === 'string' && sourceToolCallId ? { sourceToolCallId } : {}),
236
+ ...(Number.isInteger(sourceImageIndex) && sourceImageIndex >= 0 ? { sourceImageIndex } : {}),
237
+ ...(vpId ? { vpId } : {}),
238
+ };
239
+ if (!turnAssociations.some(item => item.turnId === association.turnId
240
+ && item.sourceToolCallId === association.sourceToolCallId
241
+ && item.sourceImageIndex === association.sourceImageIndex
242
+ && item.vpId === association.vpId)) turnAssociations.push(association);
243
+ }
225
244
  const metadata = {
226
245
  assetId: computedAssetId,
227
246
  scopeId,
@@ -238,6 +257,7 @@ export function createYeaftAssetStore({
238
257
  ...(existing?.turnId ? [existing.turnId] : []),
239
258
  ...(turnId ? [turnId] : []),
240
259
  ])],
260
+ turnAssociations,
241
261
  vpId: vpId || existing?.vpId || null,
242
262
  threadId: threadId || existing?.threadId || null,
243
263
  createdAt: Number(existing?.createdAt) || now(),
@@ -305,12 +325,31 @@ export function createYeaftAssetStore({
305
325
  const associatedTurns = new Set([
306
326
  ...(row.metadata.turnId ? [row.metadata.turnId] : []),
307
327
  ...(Array.isArray(row.metadata.turnIds) ? row.metadata.turnIds : []),
328
+ ...(Array.isArray(row.metadata.turnAssociations)
329
+ ? row.metadata.turnAssociations.map(association => association?.turnId)
330
+ : []),
308
331
  ]);
309
332
  const matches = Array.from(associatedTurns).filter(turnId => requested.has(turnId));
310
333
  if (matches.length === 0) continue;
311
334
  const image = this.describe({ ownerId, agentId, sessionId, assetId: row.metadata.assetId });
312
335
  if (!image) continue;
313
- for (const turnId of matches) result.get(turnId).push(image);
336
+ for (const turnId of matches) {
337
+ const associations = Array.isArray(row.metadata.turnAssociations)
338
+ ? row.metadata.turnAssociations.filter(association => association?.turnId === turnId)
339
+ : [];
340
+ if (associations.length > 0) {
341
+ for (const association of associations) {
342
+ result.get(turnId).push({
343
+ ...image,
344
+ ...(association.sourceToolCallId ? { sourceToolCallId: association.sourceToolCallId } : {}),
345
+ ...(Number.isInteger(association.sourceImageIndex) ? { sourceImageIndex: association.sourceImageIndex } : {}),
346
+ ...(association.vpId ? { vpId: association.vpId } : {}),
347
+ });
348
+ }
349
+ } else {
350
+ result.get(turnId).push(image);
351
+ }
352
+ }
314
353
  }
315
354
  return result;
316
355
  },
@@ -1 +1 @@
1
- {"version":"1.0.555"}
1
+ {"version":"1.0.557"}