@yeaft/webchat-agent 0.1.821 → 0.1.822

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.821",
3
+ "version": "0.1.822",
4
4
  "description": "Remote agent for Yeaft WebChat — connects worker machines to the central server",
5
5
  "main": "index.js",
6
6
  "type": "module",
@@ -40,8 +40,8 @@
40
40
  * than swallowing it in a console.warn.
41
41
  */
42
42
 
43
- import { existsSync, mkdirSync, writeFileSync } from 'node:fs';
44
- import { basename, extname, join } from 'node:path';
43
+ import { existsSync, mkdirSync, writeFileSync, readFileSync, statSync } from 'node:fs';
44
+ import { basename, extname, join, resolve, relative, isAbsolute } from 'node:path';
45
45
  import { randomBytes } from 'node:crypto';
46
46
 
47
47
  // Same dir name Chat mode uses, so ".gitignore" rules and tool-side
@@ -54,6 +54,7 @@ const TEMP_UPLOAD_DIR = '.claude-tmp-attachments';
54
54
  // - MAX_TOTAL_BYTES: 50 MiB across all files in one turn.
55
55
  export const MAX_FILES_PER_TURN = 16;
56
56
  export const MAX_TOTAL_BYTES = 50 * 1024 * 1024;
57
+ export const MAX_PREVIEW_BYTES = 10 * 1024 * 1024;
57
58
 
58
59
  /**
59
60
  * Sanitize a user-supplied filename's basename for use as an on-disk
@@ -228,3 +229,49 @@ export function attachmentsForPersistence(promptAttachments) {
228
229
  isImage: !!f.isImage,
229
230
  }));
230
231
  }
232
+
233
+ /**
234
+ * Resolve a persisted Unify attachment path to an on-disk file. The persisted
235
+ * path is intentionally relative (for tool use), so preview hydration must
236
+ * keep it inside the upload root instead of serving arbitrary files.
237
+ *
238
+ * @param {string} attachmentPath
239
+ * @param {{ cwd?: string }} [opts]
240
+ * @returns {string|null}
241
+ */
242
+ export function resolvePersistedAttachmentPath(attachmentPath, opts = {}) {
243
+ if (!attachmentPath || typeof attachmentPath !== 'string') return null;
244
+ if (isAbsolute(attachmentPath)) return null;
245
+ const cwd = resolve(opts.cwd || process.cwd());
246
+ const uploadRoot = resolve(cwd, TEMP_UPLOAD_DIR);
247
+ const absPath = resolve(cwd, attachmentPath);
248
+ const rel = relative(uploadRoot, absPath);
249
+ if (rel === '' || rel.startsWith('..') || isAbsolute(rel)) return null;
250
+ return absPath;
251
+ }
252
+
253
+ /**
254
+ * Read a persisted image attachment into a short-lived preview payload for the
255
+ * web server. Returns null for non-images, bad paths, missing files, or files
256
+ * too large to cache as previews.
257
+ *
258
+ * @param {{name?:string, path?:string, mimeType?:string, isImage?:boolean}} att
259
+ * @param {{ cwd?: string }} [opts]
260
+ * @returns {{data:string,mimeType:string,filename:string}|null}
261
+ */
262
+ export function persistedAttachmentPreviewPayload(att, opts = {}) {
263
+ if (!att || !att.isImage || !att.path) return null;
264
+ const absPath = resolvePersistedAttachmentPath(att.path, opts);
265
+ if (!absPath) return null;
266
+ try {
267
+ const st = statSync(absPath);
268
+ if (!st.isFile() || st.size > MAX_PREVIEW_BYTES) return null;
269
+ return {
270
+ data: readFileSync(absPath).toString('base64'),
271
+ mimeType: att.mimeType || 'application/octet-stream',
272
+ filename: att.name || basename(absPath),
273
+ };
274
+ } catch {
275
+ return null;
276
+ }
277
+ }
@@ -52,7 +52,7 @@ import { seedDefaultGroup } from './groups/seed-default.js';
52
52
  import {
53
53
  trimSnapshotForBudget,
54
54
  } from './history-compact.js';
55
- import { persistUnifyAttachments, attachmentsForPersistence } from './attachments.js';
55
+ import { persistUnifyAttachments, attachmentsForPersistence, persistedAttachmentPreviewPayload } from './attachments.js';
56
56
  import { parseSeqFromId } from './conversation/persist.js';
57
57
  import { sliceLastNTurns } from './turn-utils.js';
58
58
  import { createVpStatusBroker } from './vp-status-broker.js';
@@ -584,6 +584,16 @@ function projectPersistedToVisibleHistoryEntry(m) {
584
584
  return entry && (entry.role === 'user' || entry.role === 'assistant') ? entry : null;
585
585
  }
586
586
 
587
+ function hydrateHistoryAttachmentPreviews(attachments) {
588
+ if (!Array.isArray(attachments) || attachments.length === 0) return [];
589
+ return attachments.map((att) => {
590
+ if (!att || typeof att !== 'object') return att;
591
+ if (!att.isImage || att.preview || att.previewData) return att;
592
+ const payload = persistedAttachmentPreviewPayload(att);
593
+ return payload ? { ...att, previewData: payload } : att;
594
+ });
595
+ }
596
+
587
597
  function loadVisibleGroupHistoryPage(store, groupId, limit, beforeSeq = null) {
588
598
  if (!store || !groupId || !(limit > 0)) return { messages: [], oldestSeq: null, hasMore: false };
589
599
 
@@ -3561,7 +3571,7 @@ export async function handleUnifyLoadHistory(msg) {
3561
3571
  message: {
3562
3572
  content: entry.content,
3563
3573
  id: entry.id || null,
3564
- ...(Array.isArray(entry.attachments) && entry.attachments.length > 0 ? { attachments: entry.attachments } : {}),
3574
+ ...(Array.isArray(entry.attachments) && entry.attachments.length > 0 ? { attachments: hydrateHistoryAttachmentPreviews(entry.attachments) } : {}),
3565
3575
  },
3566
3576
  ts: entry.ts || null,
3567
3577
  }, { groupId: entry.groupId || null });
@@ -3655,7 +3665,7 @@ export async function handleUnifyLoadMoreHistory(msg) {
3655
3665
  role: m.role,
3656
3666
  content: m.content,
3657
3667
  groupId: m.groupId || null,
3658
- ...(Array.isArray(m.attachments) && m.attachments.length > 0 ? { attachments: m.attachments } : {}),
3668
+ ...(Array.isArray(m.attachments) && m.attachments.length > 0 ? { attachments: hydrateHistoryAttachmentPreviews(m.attachments) } : {}),
3659
3669
  ...(m.speakerVpId ? { speakerVpId: m.speakerVpId } : {}),
3660
3670
  }));
3661
3671