@luckydraw/cumulus 0.30.28 → 0.30.30
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/CHANGELOG.md +16 -1
- package/README.md +40 -43
- package/dist/gateway/daemon.js +0 -0
- package/dist/gateway/server.d.ts.map +1 -1
- package/dist/gateway/server.js +4 -2
- package/dist/gateway/server.js.map +1 -1
- package/dist/gateway/static/widget.js +285 -32
- package/dist/lib/intent-reviewer.d.ts +103 -0
- package/dist/lib/intent-reviewer.d.ts.map +1 -0
- package/dist/lib/intent-reviewer.js +363 -0
- package/dist/lib/intent-reviewer.js.map +1 -0
- package/package.json +1 -1
- package/dist/lib/projects.d.ts.map +0 -1
|
@@ -563,6 +563,31 @@
|
|
|
563
563
|
' font-size: 0.85em; line-height: 1; cursor: pointer; padding: 0 0.1em;',
|
|
564
564
|
'}',
|
|
565
565
|
'.cumulus-attach-chip-remove:hover { color: #ff6666; }',
|
|
566
|
+
/* Upload progress bar — thin overlay at bottom of chip */
|
|
567
|
+
'.cumulus-attach-chip-progress {',
|
|
568
|
+
' position: absolute; left: 0; right: 0; bottom: 0;',
|
|
569
|
+
' height: 3px; background: rgba(255,255,255,0.08);',
|
|
570
|
+
' overflow: hidden;',
|
|
571
|
+
'}',
|
|
572
|
+
'.cumulus-attach-chip-progress-fill {',
|
|
573
|
+
' height: 100%; width: 0%;',
|
|
574
|
+
' background: linear-gradient(90deg, #7c3aed, #a78bfa);',
|
|
575
|
+
' transition: width 0.15s ease;',
|
|
576
|
+
'}',
|
|
577
|
+
/* Status badge (✓ or ✗) — small corner indicator */
|
|
578
|
+
'.cumulus-attach-chip-status {',
|
|
579
|
+
' position: absolute; bottom: 0.15em; left: 0.2em;',
|
|
580
|
+
' font-size: 0.75em; line-height: 1; font-weight: bold;',
|
|
581
|
+
' pointer-events: none;',
|
|
582
|
+
'}',
|
|
583
|
+
'.cumulus-attach-chip[data-status="done"] .cumulus-attach-chip-status { color: #4ade80; }',
|
|
584
|
+
'.cumulus-attach-chip[data-status="error"] {',
|
|
585
|
+
' border-color: #b91c1c;',
|
|
586
|
+
'}',
|
|
587
|
+
'.cumulus-attach-chip[data-status="error"] .cumulus-attach-chip-status { color: #ef4444; }',
|
|
588
|
+
'.cumulus-attach-chip[data-status="uploading"] {',
|
|
589
|
+
' opacity: 0.85;',
|
|
590
|
+
'}',
|
|
566
591
|
|
|
567
592
|
/* Input row (attach btn + textarea + send btn) */
|
|
568
593
|
'.cumulus-input-row {',
|
|
@@ -2612,6 +2637,77 @@
|
|
|
2612
2637
|
});
|
|
2613
2638
|
}
|
|
2614
2639
|
|
|
2640
|
+
function formatFileSize(bytes) {
|
|
2641
|
+
if (bytes < 1024) return bytes + ' B';
|
|
2642
|
+
if (bytes < 1024 * 1024) return (bytes / 1024).toFixed(1) + ' KB';
|
|
2643
|
+
if (bytes < 1024 * 1024 * 1024) return (bytes / (1024 * 1024)).toFixed(1) + ' MB';
|
|
2644
|
+
return (bytes / (1024 * 1024 * 1024)).toFixed(2) + ' GB';
|
|
2645
|
+
}
|
|
2646
|
+
|
|
2647
|
+
// Upload a non-image file to /api/media/upload via XHR with progress.
|
|
2648
|
+
// Mutates the attachment object in place: progress, status, url, path, error, xhr.
|
|
2649
|
+
// Calls onChange() whenever state advances so the UI can re-render.
|
|
2650
|
+
function uploadAttachment(att, file, apiKey, onChange) {
|
|
2651
|
+
var xhr = new XMLHttpRequest();
|
|
2652
|
+
att.xhr = xhr;
|
|
2653
|
+
|
|
2654
|
+
var url = '/api/media/upload?filename=' + encodeURIComponent(file.name);
|
|
2655
|
+
xhr.open('POST', url, true);
|
|
2656
|
+
xhr.setRequestHeader('X-API-Key', apiKey || '');
|
|
2657
|
+
xhr.setRequestHeader('Content-Type', file.type || 'application/octet-stream');
|
|
2658
|
+
|
|
2659
|
+
xhr.upload.addEventListener('progress', function (e) {
|
|
2660
|
+
if (e.lengthComputable && att.status === 'uploading') {
|
|
2661
|
+
att.progress = e.loaded / e.total;
|
|
2662
|
+
if (onChange) onChange();
|
|
2663
|
+
}
|
|
2664
|
+
});
|
|
2665
|
+
|
|
2666
|
+
xhr.addEventListener('load', function () {
|
|
2667
|
+
if (att.status !== 'uploading') return; // aborted/removed
|
|
2668
|
+
if (xhr.status >= 200 && xhr.status < 300) {
|
|
2669
|
+
try {
|
|
2670
|
+
var resp = JSON.parse(xhr.responseText);
|
|
2671
|
+
// /api/media/upload returns {files: [...]} for multipart, or the single result object
|
|
2672
|
+
var result = resp.files && resp.files[0] ? resp.files[0] : resp;
|
|
2673
|
+
att.status = 'done';
|
|
2674
|
+
att.progress = 1;
|
|
2675
|
+
att.url = result.url;
|
|
2676
|
+
att.path = result.path;
|
|
2677
|
+
att.serverSize = result.size;
|
|
2678
|
+
} catch (e) {
|
|
2679
|
+
att.status = 'error';
|
|
2680
|
+
att.error = 'Invalid server response';
|
|
2681
|
+
}
|
|
2682
|
+
} else {
|
|
2683
|
+
att.status = 'error';
|
|
2684
|
+
try {
|
|
2685
|
+
var errResp = JSON.parse(xhr.responseText);
|
|
2686
|
+
att.error = errResp.error || 'HTTP ' + xhr.status;
|
|
2687
|
+
} catch (e) {
|
|
2688
|
+
att.error = 'HTTP ' + xhr.status;
|
|
2689
|
+
}
|
|
2690
|
+
}
|
|
2691
|
+
att.xhr = null;
|
|
2692
|
+
if (onChange) onChange();
|
|
2693
|
+
});
|
|
2694
|
+
|
|
2695
|
+
xhr.addEventListener('error', function () {
|
|
2696
|
+
if (att.status !== 'uploading') return;
|
|
2697
|
+
att.status = 'error';
|
|
2698
|
+
att.error = 'Network error';
|
|
2699
|
+
att.xhr = null;
|
|
2700
|
+
if (onChange) onChange();
|
|
2701
|
+
});
|
|
2702
|
+
|
|
2703
|
+
xhr.addEventListener('abort', function () {
|
|
2704
|
+
// Caller is responsible for removing the chip when it aborts
|
|
2705
|
+
att.xhr = null;
|
|
2706
|
+
});
|
|
2707
|
+
|
|
2708
|
+
xhr.send(file);
|
|
2709
|
+
}
|
|
2710
|
+
|
|
2615
2711
|
// ─── WebSocket Connection ────────────────────────────────────────────────────
|
|
2616
2712
|
function createConnection(opts) {
|
|
2617
2713
|
var wsUrl = opts.wsUrl;
|
|
@@ -2852,7 +2948,7 @@
|
|
|
2852
2948
|
var fileInput = document.createElement('input');
|
|
2853
2949
|
fileInput.type = 'file';
|
|
2854
2950
|
fileInput.multiple = true;
|
|
2855
|
-
|
|
2951
|
+
// Accept any file type — non-image files upload to media server with progress
|
|
2856
2952
|
fileInput.style.display = 'none';
|
|
2857
2953
|
fileInput.setAttribute('data-testid', 'webchat-file-input');
|
|
2858
2954
|
|
|
@@ -3180,11 +3276,22 @@
|
|
|
3180
3276
|
panel.appendChild(dialog);
|
|
3181
3277
|
}
|
|
3182
3278
|
|
|
3279
|
+
function hasUploadingAttachments() {
|
|
3280
|
+
for (var i = 0; i < pendingAttachments.length; i++) {
|
|
3281
|
+
if (pendingAttachments[i].status === 'uploading') return true;
|
|
3282
|
+
}
|
|
3283
|
+
return false;
|
|
3284
|
+
}
|
|
3285
|
+
|
|
3183
3286
|
function updateSendBtn() {
|
|
3184
3287
|
if (streaming) {
|
|
3185
3288
|
sendBtn.textContent = 'Stop';
|
|
3186
3289
|
sendBtn.classList.add('stop');
|
|
3187
3290
|
sendBtn.disabled = false;
|
|
3291
|
+
} else if (hasUploadingAttachments()) {
|
|
3292
|
+
sendBtn.textContent = 'Uploading…';
|
|
3293
|
+
sendBtn.classList.remove('stop');
|
|
3294
|
+
sendBtn.disabled = true;
|
|
3188
3295
|
} else {
|
|
3189
3296
|
sendBtn.textContent = 'Send';
|
|
3190
3297
|
sendBtn.classList.remove('stop');
|
|
@@ -3222,6 +3329,7 @@
|
|
|
3222
3329
|
attachStrip.innerHTML = '';
|
|
3223
3330
|
if (pendingAttachments.length === 0) {
|
|
3224
3331
|
attachStrip.style.display = 'none';
|
|
3332
|
+
updateSendBtn();
|
|
3225
3333
|
return;
|
|
3226
3334
|
}
|
|
3227
3335
|
attachStrip.style.display = 'flex';
|
|
@@ -3229,6 +3337,8 @@
|
|
|
3229
3337
|
var chip = document.createElement('div');
|
|
3230
3338
|
chip.className = 'cumulus-attach-chip';
|
|
3231
3339
|
chip.setAttribute('data-testid', 'webchat-attach-chip');
|
|
3340
|
+
chip.setAttribute('data-status', att.status || 'done');
|
|
3341
|
+
if (att.error) chip.setAttribute('title', att.error);
|
|
3232
3342
|
if (att.isImage) {
|
|
3233
3343
|
var thumb = document.createElement('img');
|
|
3234
3344
|
thumb.className = 'cumulus-attach-chip-thumb';
|
|
@@ -3245,6 +3355,25 @@
|
|
|
3245
3355
|
nameEl.className = 'cumulus-attach-chip-name';
|
|
3246
3356
|
nameEl.textContent = att.name;
|
|
3247
3357
|
chip.appendChild(nameEl);
|
|
3358
|
+
if (att.status === 'uploading') {
|
|
3359
|
+
var bar = document.createElement('div');
|
|
3360
|
+
bar.className = 'cumulus-attach-chip-progress';
|
|
3361
|
+
var fill = document.createElement('div');
|
|
3362
|
+
fill.className = 'cumulus-attach-chip-progress-fill';
|
|
3363
|
+
fill.style.width = Math.round((att.progress || 0) * 100) + '%';
|
|
3364
|
+
bar.appendChild(fill);
|
|
3365
|
+
chip.appendChild(bar);
|
|
3366
|
+
} else if (att.status === 'done' && !att.isImage) {
|
|
3367
|
+
var ok = document.createElement('div');
|
|
3368
|
+
ok.className = 'cumulus-attach-chip-status';
|
|
3369
|
+
ok.textContent = '✓';
|
|
3370
|
+
chip.appendChild(ok);
|
|
3371
|
+
} else if (att.status === 'error') {
|
|
3372
|
+
var bad = document.createElement('div');
|
|
3373
|
+
bad.className = 'cumulus-attach-chip-status';
|
|
3374
|
+
bad.textContent = '✗';
|
|
3375
|
+
chip.appendChild(bad);
|
|
3376
|
+
}
|
|
3248
3377
|
var removeBtn = document.createElement('button');
|
|
3249
3378
|
removeBtn.className = 'cumulus-attach-chip-remove';
|
|
3250
3379
|
removeBtn.setAttribute('data-testid', 'webchat-attach-remove');
|
|
@@ -3252,6 +3381,14 @@
|
|
|
3252
3381
|
removeBtn.setAttribute('title', 'Remove attachment');
|
|
3253
3382
|
(function (index) {
|
|
3254
3383
|
removeBtn.addEventListener('click', function () {
|
|
3384
|
+
var removed = pendingAttachments[index];
|
|
3385
|
+
if (removed && removed.xhr) {
|
|
3386
|
+
try {
|
|
3387
|
+
removed.xhr.abort();
|
|
3388
|
+
} catch (e) {
|
|
3389
|
+
/* ignore */
|
|
3390
|
+
}
|
|
3391
|
+
}
|
|
3255
3392
|
pendingAttachments.splice(index, 1);
|
|
3256
3393
|
renderAttachStrip();
|
|
3257
3394
|
});
|
|
@@ -3259,23 +3396,44 @@
|
|
|
3259
3396
|
chip.appendChild(removeBtn);
|
|
3260
3397
|
attachStrip.appendChild(chip);
|
|
3261
3398
|
});
|
|
3399
|
+
updateSendBtn();
|
|
3262
3400
|
}
|
|
3263
3401
|
|
|
3264
3402
|
async function addFilesToPending(files) {
|
|
3265
3403
|
for (var i = 0; i < files.length; i++) {
|
|
3266
3404
|
var file = files[i];
|
|
3267
|
-
|
|
3268
|
-
|
|
3269
|
-
|
|
3270
|
-
|
|
3271
|
-
|
|
3272
|
-
|
|
3405
|
+
var isImg = file.type && file.type.startsWith('image/');
|
|
3406
|
+
if (isImg) {
|
|
3407
|
+
try {
|
|
3408
|
+
var info = await readFileAsBase64(file);
|
|
3409
|
+
var dataUrl = await readFileAsDataUrl(file);
|
|
3410
|
+
pendingAttachments.push({
|
|
3411
|
+
base64: info.base64,
|
|
3412
|
+
mimeType: info.mimeType,
|
|
3413
|
+
name: file.name,
|
|
3414
|
+
size: file.size,
|
|
3415
|
+
dataUrl: dataUrl,
|
|
3416
|
+
isImage: true,
|
|
3417
|
+
status: 'done',
|
|
3418
|
+
});
|
|
3419
|
+
} catch (e) {
|
|
3420
|
+
console.error('[Cumulus] Failed to read image:', file.name, e);
|
|
3421
|
+
}
|
|
3422
|
+
} else {
|
|
3423
|
+
var att = {
|
|
3273
3424
|
name: file.name,
|
|
3274
|
-
|
|
3275
|
-
|
|
3276
|
-
|
|
3277
|
-
|
|
3278
|
-
|
|
3425
|
+
mimeType: file.type || 'application/octet-stream',
|
|
3426
|
+
size: file.size,
|
|
3427
|
+
isImage: false,
|
|
3428
|
+
status: 'uploading',
|
|
3429
|
+
progress: 0,
|
|
3430
|
+
url: null,
|
|
3431
|
+
path: null,
|
|
3432
|
+
error: null,
|
|
3433
|
+
xhr: null,
|
|
3434
|
+
};
|
|
3435
|
+
pendingAttachments.push(att);
|
|
3436
|
+
uploadAttachment(att, file, activeApiKey, renderAttachStrip);
|
|
3279
3437
|
}
|
|
3280
3438
|
}
|
|
3281
3439
|
renderAttachStrip();
|
|
@@ -3456,7 +3614,13 @@
|
|
|
3456
3614
|
|
|
3457
3615
|
if (!text && pendingAttachments.length === 0) return;
|
|
3458
3616
|
if (!connection) return;
|
|
3459
|
-
|
|
3617
|
+
if (hasUploadingAttachments()) return; // shouldn't reach here — button disabled
|
|
3618
|
+
|
|
3619
|
+
// Drop attachments that failed to upload — keep only images + done non-images
|
|
3620
|
+
var sendable = pendingAttachments.filter(function (a) {
|
|
3621
|
+
return a.isImage || a.status === 'done';
|
|
3622
|
+
});
|
|
3623
|
+
var attachSnapshot = sendable.slice();
|
|
3460
3624
|
var displayText = text || '(attachment)';
|
|
3461
3625
|
messages.push({ role: 'user', content: displayText, attachments: attachSnapshot });
|
|
3462
3626
|
input.value = '';
|
|
@@ -3475,10 +3639,22 @@
|
|
|
3475
3639
|
.map(function (a) {
|
|
3476
3640
|
return { mimeType: a.mimeType, base64: a.base64 };
|
|
3477
3641
|
});
|
|
3642
|
+
// Build "Attached files:" prefix for non-image uploads so the agent can read_file them
|
|
3643
|
+
var fileAttachments = attachSnapshot.filter(function (a) {
|
|
3644
|
+
return !a.isImage && a.status === 'done' && a.path;
|
|
3645
|
+
});
|
|
3646
|
+
var messageBody = text || '';
|
|
3647
|
+
if (fileAttachments.length > 0) {
|
|
3648
|
+
var lines = fileAttachments.map(function (a) {
|
|
3649
|
+
return '- ' + a.name + ' (' + formatFileSize(a.size || 0) + ') → ' + a.path;
|
|
3650
|
+
});
|
|
3651
|
+
messageBody =
|
|
3652
|
+
'Attached files:\n' + lines.join('\n') + (messageBody ? '\n\n' + messageBody : '');
|
|
3653
|
+
}
|
|
3478
3654
|
var payload = {
|
|
3479
3655
|
type: 'message',
|
|
3480
3656
|
threadName: sessionId,
|
|
3481
|
-
message:
|
|
3657
|
+
message: messageBody || ' ',
|
|
3482
3658
|
};
|
|
3483
3659
|
if (imagePayload.length > 0) payload.images = imagePayload;
|
|
3484
3660
|
connection.send(payload);
|
|
@@ -5234,7 +5410,7 @@
|
|
|
5234
5410
|
var fileInput = document.createElement('input');
|
|
5235
5411
|
fileInput.type = 'file';
|
|
5236
5412
|
fileInput.multiple = true;
|
|
5237
|
-
|
|
5413
|
+
// Accept any file type — non-image files upload to media server with progress
|
|
5238
5414
|
fileInput.style.display = 'none';
|
|
5239
5415
|
fileInput.setAttribute('data-testid', 'webchat-file-input');
|
|
5240
5416
|
|
|
@@ -5593,8 +5769,16 @@
|
|
|
5593
5769
|
panelEl.appendChild(dialog);
|
|
5594
5770
|
}
|
|
5595
5771
|
|
|
5772
|
+
function hasPanelUploading() {
|
|
5773
|
+
for (var i = 0; i < state.pendingAttachments.length; i++) {
|
|
5774
|
+
if (state.pendingAttachments[i].status === 'uploading') return true;
|
|
5775
|
+
}
|
|
5776
|
+
return false;
|
|
5777
|
+
}
|
|
5778
|
+
|
|
5596
5779
|
function updatePanelSendBtn() {
|
|
5597
5780
|
var hasInput = inputEl.value.trim().length > 0 || state.pendingAttachments.length > 0;
|
|
5781
|
+
var uploading = hasPanelUploading();
|
|
5598
5782
|
if (state.streaming && !hasInput) {
|
|
5599
5783
|
// Streaming with no input — show Stop button
|
|
5600
5784
|
sendBtn.textContent = 'Stop';
|
|
@@ -5602,15 +5786,15 @@
|
|
|
5602
5786
|
sendBtn.disabled = false;
|
|
5603
5787
|
} else if (state.streaming && hasInput) {
|
|
5604
5788
|
// Streaming with input — show Send (interjection mode)
|
|
5605
|
-
sendBtn.textContent = 'Send';
|
|
5789
|
+
sendBtn.textContent = uploading ? 'Uploading…' : 'Send';
|
|
5606
5790
|
sendBtn.classList.remove('stop');
|
|
5607
5791
|
sendBtn.classList.add('interject');
|
|
5608
|
-
sendBtn.disabled =
|
|
5792
|
+
sendBtn.disabled = uploading;
|
|
5609
5793
|
} else {
|
|
5610
|
-
sendBtn.textContent = 'Send';
|
|
5794
|
+
sendBtn.textContent = uploading ? 'Uploading…' : 'Send';
|
|
5611
5795
|
sendBtn.classList.remove('stop');
|
|
5612
5796
|
sendBtn.classList.remove('interject');
|
|
5613
|
-
sendBtn.disabled =
|
|
5797
|
+
sendBtn.disabled = uploading;
|
|
5614
5798
|
}
|
|
5615
5799
|
}
|
|
5616
5800
|
|
|
@@ -5618,6 +5802,7 @@
|
|
|
5618
5802
|
attachStrip.innerHTML = '';
|
|
5619
5803
|
if (state.pendingAttachments.length === 0) {
|
|
5620
5804
|
attachStrip.style.display = 'none';
|
|
5805
|
+
updatePanelSendBtn();
|
|
5621
5806
|
return;
|
|
5622
5807
|
}
|
|
5623
5808
|
attachStrip.style.display = 'flex';
|
|
@@ -5625,6 +5810,8 @@
|
|
|
5625
5810
|
var chip = document.createElement('div');
|
|
5626
5811
|
chip.className = 'cumulus-attach-chip';
|
|
5627
5812
|
chip.setAttribute('data-testid', 'webchat-attach-chip');
|
|
5813
|
+
chip.setAttribute('data-status', att.status || 'done');
|
|
5814
|
+
if (att.error) chip.setAttribute('title', att.error);
|
|
5628
5815
|
if (att.isImage) {
|
|
5629
5816
|
var thumb = document.createElement('img');
|
|
5630
5817
|
thumb.className = 'cumulus-attach-chip-thumb';
|
|
@@ -5641,6 +5828,25 @@
|
|
|
5641
5828
|
nameEl.className = 'cumulus-attach-chip-name';
|
|
5642
5829
|
nameEl.textContent = att.name;
|
|
5643
5830
|
chip.appendChild(nameEl);
|
|
5831
|
+
if (att.status === 'uploading') {
|
|
5832
|
+
var bar = document.createElement('div');
|
|
5833
|
+
bar.className = 'cumulus-attach-chip-progress';
|
|
5834
|
+
var fill = document.createElement('div');
|
|
5835
|
+
fill.className = 'cumulus-attach-chip-progress-fill';
|
|
5836
|
+
fill.style.width = Math.round((att.progress || 0) * 100) + '%';
|
|
5837
|
+
bar.appendChild(fill);
|
|
5838
|
+
chip.appendChild(bar);
|
|
5839
|
+
} else if (att.status === 'done' && !att.isImage) {
|
|
5840
|
+
var ok = document.createElement('div');
|
|
5841
|
+
ok.className = 'cumulus-attach-chip-status';
|
|
5842
|
+
ok.textContent = '✓';
|
|
5843
|
+
chip.appendChild(ok);
|
|
5844
|
+
} else if (att.status === 'error') {
|
|
5845
|
+
var bad = document.createElement('div');
|
|
5846
|
+
bad.className = 'cumulus-attach-chip-status';
|
|
5847
|
+
bad.textContent = '✗';
|
|
5848
|
+
chip.appendChild(bad);
|
|
5849
|
+
}
|
|
5644
5850
|
var removeBtn = document.createElement('button');
|
|
5645
5851
|
removeBtn.className = 'cumulus-attach-chip-remove';
|
|
5646
5852
|
removeBtn.setAttribute('data-testid', 'webchat-attach-remove');
|
|
@@ -5648,6 +5854,14 @@
|
|
|
5648
5854
|
removeBtn.setAttribute('title', 'Remove attachment');
|
|
5649
5855
|
(function (index) {
|
|
5650
5856
|
removeBtn.addEventListener('click', function () {
|
|
5857
|
+
var removed = state.pendingAttachments[index];
|
|
5858
|
+
if (removed && removed.xhr) {
|
|
5859
|
+
try {
|
|
5860
|
+
removed.xhr.abort();
|
|
5861
|
+
} catch (e) {
|
|
5862
|
+
/* ignore */
|
|
5863
|
+
}
|
|
5864
|
+
}
|
|
5651
5865
|
state.pendingAttachments.splice(index, 1);
|
|
5652
5866
|
renderPanelAttachStrip();
|
|
5653
5867
|
});
|
|
@@ -5655,23 +5869,44 @@
|
|
|
5655
5869
|
chip.appendChild(removeBtn);
|
|
5656
5870
|
attachStrip.appendChild(chip);
|
|
5657
5871
|
});
|
|
5872
|
+
updatePanelSendBtn();
|
|
5658
5873
|
}
|
|
5659
5874
|
|
|
5660
5875
|
async function addFilesToPending(files) {
|
|
5661
5876
|
for (var i = 0; i < files.length; i++) {
|
|
5662
5877
|
var file = files[i];
|
|
5663
|
-
|
|
5664
|
-
|
|
5665
|
-
|
|
5666
|
-
|
|
5667
|
-
|
|
5668
|
-
|
|
5878
|
+
var isImg = file.type && file.type.startsWith('image/');
|
|
5879
|
+
if (isImg) {
|
|
5880
|
+
try {
|
|
5881
|
+
var info = await readFileAsBase64(file);
|
|
5882
|
+
var dataUrl = await readFileAsDataUrl(file);
|
|
5883
|
+
state.pendingAttachments.push({
|
|
5884
|
+
base64: info.base64,
|
|
5885
|
+
mimeType: info.mimeType,
|
|
5886
|
+
name: file.name,
|
|
5887
|
+
size: file.size,
|
|
5888
|
+
dataUrl: dataUrl,
|
|
5889
|
+
isImage: true,
|
|
5890
|
+
status: 'done',
|
|
5891
|
+
});
|
|
5892
|
+
} catch (e) {
|
|
5893
|
+
console.error('[Cumulus] Failed to read image:', file.name, e);
|
|
5894
|
+
}
|
|
5895
|
+
} else {
|
|
5896
|
+
var att = {
|
|
5669
5897
|
name: file.name,
|
|
5670
|
-
|
|
5671
|
-
|
|
5672
|
-
|
|
5673
|
-
|
|
5674
|
-
|
|
5898
|
+
mimeType: file.type || 'application/octet-stream',
|
|
5899
|
+
size: file.size,
|
|
5900
|
+
isImage: false,
|
|
5901
|
+
status: 'uploading',
|
|
5902
|
+
progress: 0,
|
|
5903
|
+
url: null,
|
|
5904
|
+
path: null,
|
|
5905
|
+
error: null,
|
|
5906
|
+
xhr: null,
|
|
5907
|
+
};
|
|
5908
|
+
state.pendingAttachments.push(att);
|
|
5909
|
+
uploadAttachment(att, file, activeApiKey, renderPanelAttachStrip);
|
|
5675
5910
|
}
|
|
5676
5911
|
}
|
|
5677
5912
|
renderPanelAttachStrip();
|
|
@@ -5751,8 +5986,13 @@
|
|
|
5751
5986
|
|
|
5752
5987
|
if (!text && state.pendingAttachments.length === 0) return;
|
|
5753
5988
|
if (!connection) return;
|
|
5989
|
+
if (hasPanelUploading()) return; // button is disabled while uploading
|
|
5754
5990
|
|
|
5755
|
-
|
|
5991
|
+
// Drop attachments that failed to upload
|
|
5992
|
+
var sendable = state.pendingAttachments.filter(function (a) {
|
|
5993
|
+
return a.isImage || a.status === 'done';
|
|
5994
|
+
});
|
|
5995
|
+
var attachSnapshot = sendable.slice();
|
|
5756
5996
|
var displayText = text || '(attachment)';
|
|
5757
5997
|
state.messages.push({ role: 'user', content: displayText, attachments: attachSnapshot });
|
|
5758
5998
|
inputEl.value = '';
|
|
@@ -5776,10 +6016,23 @@
|
|
|
5776
6016
|
return { mimeType: a.mimeType, base64: a.base64 };
|
|
5777
6017
|
});
|
|
5778
6018
|
|
|
6019
|
+
// Build "Attached files:" prefix for non-image uploads
|
|
6020
|
+
var fileAttachments = attachSnapshot.filter(function (a) {
|
|
6021
|
+
return !a.isImage && a.status === 'done' && a.path;
|
|
6022
|
+
});
|
|
6023
|
+
var messageBody = text || '';
|
|
6024
|
+
if (fileAttachments.length > 0) {
|
|
6025
|
+
var lines = fileAttachments.map(function (a) {
|
|
6026
|
+
return '- ' + a.name + ' (' + formatFileSize(a.size || 0) + ') → ' + a.path;
|
|
6027
|
+
});
|
|
6028
|
+
messageBody =
|
|
6029
|
+
'Attached files:\n' + lines.join('\n') + (messageBody ? '\n\n' + messageBody : '');
|
|
6030
|
+
}
|
|
6031
|
+
|
|
5779
6032
|
var payload = {
|
|
5780
6033
|
type: 'message',
|
|
5781
6034
|
threadName: threadName,
|
|
5782
|
-
message:
|
|
6035
|
+
message: messageBody || ' ',
|
|
5783
6036
|
};
|
|
5784
6037
|
if (imagePayload.length > 0) payload.images = imagePayload;
|
|
5785
6038
|
connection.send(payload);
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Intent Reviewer — adversarial quality gate for Write/Edit tool calls.
|
|
3
|
+
*
|
|
4
|
+
* Intercepts Write/Edit results, compares stated intent vs actual execution
|
|
5
|
+
* using GLM-5 via HuggingFace, and either passes or rejects with actionable feedback.
|
|
6
|
+
* After 3 failures on the same task+file, escalates to the user.
|
|
7
|
+
*/
|
|
8
|
+
export interface ReviewVerdict {
|
|
9
|
+
verdict: 'pass' | 'provisional_pass' | 'fail';
|
|
10
|
+
score: number;
|
|
11
|
+
claims: Array<{
|
|
12
|
+
claim: string;
|
|
13
|
+
finding: string;
|
|
14
|
+
result: 'true' | 'false' | 'partially_false';
|
|
15
|
+
}>;
|
|
16
|
+
limitations: string;
|
|
17
|
+
feedback: string;
|
|
18
|
+
}
|
|
19
|
+
export interface ReviewInput {
|
|
20
|
+
intent: string;
|
|
21
|
+
execution: {
|
|
22
|
+
type: 'edit' | 'write';
|
|
23
|
+
file: string;
|
|
24
|
+
old?: string;
|
|
25
|
+
new_content: string;
|
|
26
|
+
};
|
|
27
|
+
taskContext?: string;
|
|
28
|
+
}
|
|
29
|
+
export interface ReviewLog {
|
|
30
|
+
threadId: string;
|
|
31
|
+
model: string;
|
|
32
|
+
toolName: string;
|
|
33
|
+
filePath: string;
|
|
34
|
+
verdict: 'pass' | 'provisional_pass' | 'fail' | 'skipped' | 'timeout' | 'error';
|
|
35
|
+
score: number;
|
|
36
|
+
attempt: number;
|
|
37
|
+
latencyMs: number;
|
|
38
|
+
timestamp: number;
|
|
39
|
+
}
|
|
40
|
+
export declare function shouldReview(toolName: string, params: Record<string, unknown>): boolean;
|
|
41
|
+
interface MessageLike {
|
|
42
|
+
role: string;
|
|
43
|
+
content: string | Array<{
|
|
44
|
+
type: string;
|
|
45
|
+
text?: string;
|
|
46
|
+
}>;
|
|
47
|
+
}
|
|
48
|
+
/**
|
|
49
|
+
* Extract intent from the last assistant message.
|
|
50
|
+
* Priority: <thinking> > active <todo> item > plain text.
|
|
51
|
+
*/
|
|
52
|
+
export declare function extractIntent(messages: MessageLike[]): string | null;
|
|
53
|
+
/**
|
|
54
|
+
* Get the currently-active todo text (first [ ] item in last <todo> block).
|
|
55
|
+
* Used for failure counter keys and reset detection.
|
|
56
|
+
*/
|
|
57
|
+
export declare function getActiveTodoText(messages: MessageLike[]): string | null;
|
|
58
|
+
export declare class IntentReviewer {
|
|
59
|
+
private hfApiKey;
|
|
60
|
+
private states;
|
|
61
|
+
private reviewLogs;
|
|
62
|
+
constructor(hfApiKey: string);
|
|
63
|
+
/**
|
|
64
|
+
* Get or create per-thread review state.
|
|
65
|
+
*/
|
|
66
|
+
private getState;
|
|
67
|
+
/**
|
|
68
|
+
* Reset failure counters when user sends a new message.
|
|
69
|
+
*/
|
|
70
|
+
resetOnNewUserMessage(threadId: string): void;
|
|
71
|
+
/**
|
|
72
|
+
* Check if active todo changed (reset counters for affected files).
|
|
73
|
+
*/
|
|
74
|
+
private checkTodoReset;
|
|
75
|
+
/**
|
|
76
|
+
* Evaluate a Write/Edit execution against stated intent.
|
|
77
|
+
* Returns the modified tool result (pass-through, annotated, or error).
|
|
78
|
+
*/
|
|
79
|
+
evaluate(threadId: string, model: string, toolName: string, params: Record<string, unknown>, toolResult: string, messages: MessageLike[]): Promise<{
|
|
80
|
+
output: string;
|
|
81
|
+
isError: boolean;
|
|
82
|
+
}>;
|
|
83
|
+
/**
|
|
84
|
+
* Call GLM-5 via HuggingFace to evaluate intent vs execution.
|
|
85
|
+
*/
|
|
86
|
+
private callReviewModel;
|
|
87
|
+
/**
|
|
88
|
+
* Log a review for analytics.
|
|
89
|
+
*/
|
|
90
|
+
private logReview;
|
|
91
|
+
/**
|
|
92
|
+
* Get review statistics for analytics.
|
|
93
|
+
*/
|
|
94
|
+
getStats(): {
|
|
95
|
+
total: number;
|
|
96
|
+
byVerdict: Record<string, number>;
|
|
97
|
+
avgLatencyMs: number;
|
|
98
|
+
p95LatencyMs: number;
|
|
99
|
+
p99LatencyMs: number;
|
|
100
|
+
};
|
|
101
|
+
}
|
|
102
|
+
export {};
|
|
103
|
+
//# sourceMappingURL=intent-reviewer.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"intent-reviewer.d.ts","sourceRoot":"","sources":["../../src/lib/intent-reviewer.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAIH,MAAM,WAAW,aAAa;IAC5B,OAAO,EAAE,MAAM,GAAG,kBAAkB,GAAG,MAAM,CAAC;IAC9C,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,EAAE,KAAK,CAAC;QAAE,KAAK,EAAE,MAAM,CAAC;QAAC,OAAO,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,MAAM,GAAG,OAAO,GAAG,iBAAiB,CAAA;KAAE,CAAC,CAAC;IAChG,WAAW,EAAE,MAAM,CAAC;IACpB,QAAQ,EAAE,MAAM,CAAC;CAClB;AAED,MAAM,WAAW,WAAW;IAC1B,MAAM,EAAE,MAAM,CAAC;IACf,SAAS,EAAE;QACT,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC;QACvB,IAAI,EAAE,MAAM,CAAC;QACb,GAAG,CAAC,EAAE,MAAM,CAAC;QACb,WAAW,EAAE,MAAM,CAAC;KACrB,CAAC;IACF,WAAW,CAAC,EAAE,MAAM,CAAC;CACtB;AAED,MAAM,WAAW,SAAS;IACxB,QAAQ,EAAE,MAAM,CAAC;IACjB,KAAK,EAAE,MAAM,CAAC;IACd,QAAQ,EAAE,MAAM,CAAC;IACjB,QAAQ,EAAE,MAAM,CAAC;IACjB,OAAO,EAAE,MAAM,GAAG,kBAAkB,GAAG,MAAM,GAAG,SAAS,GAAG,SAAS,GAAG,OAAO,CAAC;IAChF,KAAK,EAAE,MAAM,CAAC;IACd,OAAO,EAAE,MAAM,CAAC;IAChB,SAAS,EAAE,MAAM,CAAC;IAClB,SAAS,EAAE,MAAM,CAAC;CACnB;AA2CD,wBAAgB,YAAY,CAC1B,QAAQ,EAAE,MAAM,EAChB,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAC9B,OAAO,CAmBT;AAID,UAAU,WAAW;IACnB,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,MAAM,GAAG,KAAK,CAAC;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,IAAI,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;CAC1D;AAED;;;GAGG;AACH,wBAAgB,aAAa,CAAC,QAAQ,EAAE,WAAW,EAAE,GAAG,MAAM,GAAG,IAAI,CAmDpE;AAED;;;GAGG;AACH,wBAAgB,iBAAiB,CAAC,QAAQ,EAAE,WAAW,EAAE,GAAG,MAAM,GAAG,IAAI,CAyBxE;AAID,qBAAa,cAAc;IACzB,OAAO,CAAC,QAAQ,CAAS;IACzB,OAAO,CAAC,MAAM,CAAuC;IACrD,OAAO,CAAC,UAAU,CAAmB;gBAEzB,QAAQ,EAAE,MAAM;IAI5B;;OAEG;IACH,OAAO,CAAC,QAAQ;IAWhB;;OAEG;IACH,qBAAqB,CAAC,QAAQ,EAAE,MAAM,GAAG,IAAI;IAQ7C;;OAEG;IACH,OAAO,CAAC,cAAc;IAUtB;;;OAGG;IACG,QAAQ,CACZ,QAAQ,EAAE,MAAM,EAChB,KAAK,EAAE,MAAM,EACb,QAAQ,EAAE,MAAM,EAChB,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAC/B,UAAU,EAAE,MAAM,EAClB,QAAQ,EAAE,WAAW,EAAE,GACtB,OAAO,CAAC;QAAE,MAAM,EAAE,MAAM,CAAC;QAAC,OAAO,EAAE,OAAO,CAAA;KAAE,CAAC;IAiHhD;;OAEG;YACW,eAAe;IA8D7B;;OAEG;IACH,OAAO,CAAC,SAAS;IAmCjB;;OAEG;IACH,QAAQ,IAAI;QACV,KAAK,EAAE,MAAM,CAAC;QACd,SAAS,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;QAClC,YAAY,EAAE,MAAM,CAAC;QACrB,YAAY,EAAE,MAAM,CAAC;QACrB,YAAY,EAAE,MAAM,CAAC;KACtB;CAiBF"}
|