agent-nuvira 1.95.0 → 1.97.0

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.
@@ -1 +1 @@
1
- {"version":3,"file":"server.d.ts","sourceRoot":"","sources":["../../src/web-dashboard/server.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"server.d.ts","sourceRoot":"","sources":["../../src/web-dashboard/server.ts"],"names":[],"mappings":"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"}
@@ -320,6 +320,105 @@ export function broadcastTyping(event) {
320
320
  catch { /* client disconnected */ }
321
321
  }
322
322
  }
323
+ // ─── ZIP Generation (no external dependencies) ─────────────────────────────
324
+ /** CRC-32 lookup table (precomputed). */
325
+ const CRC_TABLE = new Uint32Array(256);
326
+ for (let i = 0; i < 256; i++) {
327
+ let c = i;
328
+ for (let j = 0; j < 8; j++) {
329
+ c = (c & 1) ? (0xEDB88320 ^ (c >>> 1)) : (c >>> 1);
330
+ }
331
+ CRC_TABLE[i] = c;
332
+ }
333
+ function crc32(buf) {
334
+ let crc = 0xFFFFFFFF;
335
+ for (let i = 0; i < buf.length; i++) {
336
+ crc = CRC_TABLE[(crc ^ buf[i]) & 0xFF] ^ (crc >>> 8);
337
+ }
338
+ return (crc ^ 0xFFFFFFFF) >>> 0;
339
+ }
340
+ /** DOS date/time format for ZIP headers. */
341
+ function dosDateTime(date) {
342
+ const time = ((date.getHours() & 0x1F) << 11) | ((date.getMinutes() & 0x3F) << 5) | ((date.getSeconds() >> 1) & 0x1F);
343
+ const d = ((date.getFullYear() - 1980) & 0x7F) << 9 | ((date.getMonth() + 1) & 0xF) << 5 | (date.getDate() & 0x1F);
344
+ return { time, date: d };
345
+ }
346
+ /**
347
+ * Generate a ZIP file from an array of { name, content } entries.
348
+ * Supports text files only (no compression — stored method).
349
+ * Returns a Buffer suitable for HTTP response.
350
+ */
351
+ function generateZip(files) {
352
+ const now = new Date();
353
+ const { time, date } = dosDateTime(now);
354
+ const entries = [];
355
+ const parts = [];
356
+ let offset = 0;
357
+ // Local file headers + file data.
358
+ for (const file of files) {
359
+ const nameBytes = new TextEncoder().encode(file.name);
360
+ const dataBytes = new TextEncoder().encode(file.content);
361
+ const crc = crc32(dataBytes);
362
+ // Local file header (30 + nameLen).
363
+ const header = new ArrayBuffer(30 + nameBytes.length);
364
+ const hv = new DataView(header);
365
+ hv.setUint32(0, 0x04034b50, true); // signature
366
+ hv.setUint16(4, 20, true); // version needed
367
+ hv.setUint16(6, 0, true); // flags
368
+ hv.setUint16(8, 0, true); // compression method (stored)
369
+ hv.setUint16(10, time, true); // mod time
370
+ hv.setUint16(12, date, true); // mod date
371
+ hv.setUint32(14, crc, true); // crc-32
372
+ hv.setUint32(18, dataBytes.length, true); // compressed size
373
+ hv.setUint32(22, dataBytes.length, true); // uncompressed size
374
+ hv.setUint16(26, nameBytes.length, true); // name length
375
+ hv.setUint16(28, 0, true); // extra field length
376
+ new Uint8Array(header).set(nameBytes, 30);
377
+ entries.push({ name: nameBytes, data: dataBytes, crc, offset });
378
+ parts.push(new Uint8Array(header), dataBytes);
379
+ offset += header.byteLength + dataBytes.length;
380
+ }
381
+ // Central directory.
382
+ const centralStart = offset;
383
+ for (const entry of entries) {
384
+ const ch = new ArrayBuffer(46 + entry.name.length);
385
+ const cv = new DataView(ch);
386
+ cv.setUint32(0, 0x02014b50, true); // signature
387
+ cv.setUint16(4, 20, true); // version made by
388
+ cv.setUint16(6, 20, true); // version needed
389
+ cv.setUint16(8, 0, true); // flags
390
+ cv.setUint16(10, 0, true); // compression (stored)
391
+ cv.setUint16(12, time, true); // mod time
392
+ cv.setUint16(14, date, true); // mod date
393
+ cv.setUint32(16, entry.crc, true); // crc-32
394
+ cv.setUint32(20, entry.data.length, true); // compressed size
395
+ cv.setUint32(24, entry.data.length, true); // uncompressed size
396
+ cv.setUint16(28, entry.name.length, true); // name length
397
+ cv.setUint16(30, 0, true); // extra field length
398
+ cv.setUint16(32, 0, true); // file comment length
399
+ cv.setUint16(34, 0, true); // disk number start
400
+ cv.setUint16(36, 0, true); // internal file attributes
401
+ cv.setUint32(38, 0, true); // external file attributes
402
+ cv.setUint32(42, entry.offset, true); // local header offset
403
+ new Uint8Array(ch).set(entry.name, 46);
404
+ parts.push(new Uint8Array(ch));
405
+ offset += ch.byteLength;
406
+ }
407
+ // End of central directory.
408
+ const centralSize = offset - centralStart;
409
+ const eocd = new ArrayBuffer(22);
410
+ const ev = new DataView(eocd);
411
+ ev.setUint32(0, 0x06054b50, true); // signature
412
+ ev.setUint16(4, 0, true); // disk number
413
+ ev.setUint16(6, 0, true); // central dir disk
414
+ ev.setUint16(8, entries.length, true); // entries on this disk
415
+ ev.setUint16(10, entries.length, true); // total entries
416
+ ev.setUint32(12, centralSize, true); // central dir size
417
+ ev.setUint32(16, centralStart, true); // central dir offset
418
+ ev.setUint16(20, 0, true); // comment length
419
+ parts.push(new Uint8Array(eocd));
420
+ return Buffer.concat(parts.map((p) => Buffer.from(p.buffer, p.byteOffset, p.byteLength)));
421
+ }
323
422
  let activePipeline = null; // goal/description of current pipeline
324
423
  let activeNodes = [];
325
424
  let activeEdges = [];
@@ -2775,6 +2874,88 @@ function handleRequest(req, res) {
2775
2874
  // dashboard passes the FULL map it read; the running gateway re-reads
2776
2875
  // config per inbound, so changes apply without a restart). Named
2777
2876
  // WhatsApp contacts are also synced into the bridge contacts file
2877
+ // POST /api/admin/gateway/conversations/export — bulk export as ZIP.
2878
+ // Rides on gateway.manage (admin + operator).
2879
+ if (pathname === '/api/admin/gateway/conversations/export' && req.method === 'POST') {
2880
+ void (async () => {
2881
+ const session = adminSessions.validate(bearerToken(req));
2882
+ if (!session) {
2883
+ writeJson(res, 401, { ok: false, error: 'Not authenticated — log in first.' });
2884
+ return;
2885
+ }
2886
+ if (!roleCan(session.role, 'gateway.manage')) {
2887
+ writeJson(res, 403, { ok: false, error: `Access denied — role '${session.role}' cannot export conversations.` });
2888
+ return;
2889
+ }
2890
+ const body = await readJsonBody(req);
2891
+ const keys = body?.keys;
2892
+ if (!Array.isArray(keys) || keys.length === 0) {
2893
+ writeJson(res, 400, { ok: false, error: 'Missing or invalid "keys" array.' });
2894
+ return;
2895
+ }
2896
+ try {
2897
+ const { GatewayChatStore, CHAT_HISTORY_TTL_MS } = await import('../gateway/chat-store.js');
2898
+ const store = new GatewayChatStore();
2899
+ const allConvs = store.getAllConversations();
2900
+ // Resolve contact names.
2901
+ let contactLookup = {};
2902
+ try {
2903
+ const { readContactsFile } = await import('../gateway/whatsapp/contacts.js');
2904
+ const { whatsappSessionDir } = await import('../gateway/whatsapp/session.js');
2905
+ const contacts = readContactsFile(whatsappSessionDir());
2906
+ for (const [name, digits] of Object.entries(contacts)) {
2907
+ if (name && digits)
2908
+ contactLookup[digits] = name;
2909
+ }
2910
+ }
2911
+ catch { /* best-effort */ }
2912
+ const keySet = new Set(keys);
2913
+ const convs = allConvs.filter((c) => keySet.has(c.key));
2914
+ if (convs.length === 0) {
2915
+ writeJson(res, 404, { ok: false, error: 'No matching conversations found.' });
2916
+ return;
2917
+ }
2918
+ // Build ZIP entries (manual ZIP64-compatible for text files).
2919
+ const files = [];
2920
+ const now = new Date();
2921
+ const dateStr = `${now.getFullYear()}-${String(now.getMonth() + 1).padStart(2, '0')}-${String(now.getDate()).padStart(2, '0')}`;
2922
+ for (const conv of convs) {
2923
+ const [platform, ...rest] = conv.key.split(':');
2924
+ const channelId = rest.join(':');
2925
+ const cleanId = channelId.replace(/[^\d]/g, '');
2926
+ const contactName = contactLookup[cleanId] || contactLookup[channelId] || channelId;
2927
+ const lines = [
2928
+ `Conversation with ${contactName} (${platform || 'unknown'})`,
2929
+ `Exported: ${now.toLocaleString()}`,
2930
+ `Messages: ${conv.messages.length}`,
2931
+ '─'.repeat(50),
2932
+ '',
2933
+ ];
2934
+ for (const m of conv.messages) {
2935
+ const role = m.role === 'user' ? contactName : 'Agent';
2936
+ const ts = new Date(m.ts).toLocaleString();
2937
+ lines.push(`[${ts}] ${role}:`);
2938
+ lines.push(m.content);
2939
+ lines.push('');
2940
+ }
2941
+ const safeName = `${platform || 'unknown'}-${contactName.replace(/[^\w]/g, '_')}-${channelId.replace(/[^\w]/g, '_')}.txt`;
2942
+ files.push({ name: safeName, content: lines.join('\n') });
2943
+ }
2944
+ // Generate ZIP manually (no external deps).
2945
+ const zipBuf = generateZip(files);
2946
+ res.writeHead(200, {
2947
+ 'Content-Type': 'application/zip',
2948
+ 'Content-Disposition': `attachment; filename="conversations-${dateStr}.zip"`,
2949
+ 'Content-Length': zipBuf.length,
2950
+ });
2951
+ res.end(zipBuf);
2952
+ }
2953
+ catch (err) {
2954
+ writeJson(res, 500, { ok: false, error: `Export failed: ${err instanceof Error ? err.message : String(err)}` });
2955
+ }
2956
+ })();
2957
+ return;
2958
+ }
2778
2959
  // GET /api/admin/gateway/conversations — paginated conversation list.
2779
2960
  // Rides on gateway.manage (admin + operator).
2780
2961
  if (pathname === '/api/admin/gateway/conversations' && req.method === 'GET') {