web-terminal-agent 1.0.0 → 1.1.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.
@@ -0,0 +1,821 @@
1
+ /**
2
+ * Manus Debug Collector (agent-friendly)
3
+ *
4
+ * Captures:
5
+ * 1) Console logs
6
+ * 2) Network requests (fetch + XHR)
7
+ * 3) User interactions (semantic uiEvents: click/type/submit/nav/scroll/etc.)
8
+ *
9
+ * Data is periodically sent to /__manus__/logs
10
+ * Note: uiEvents are mirrored to sessionEvents for sessionReplay.log
11
+ */
12
+ (function () {
13
+ "use strict";
14
+
15
+ // Prevent double initialization
16
+ if (window.__MANUS_DEBUG_COLLECTOR__) return;
17
+
18
+ // ==========================================================================
19
+ // Configuration
20
+ // ==========================================================================
21
+ const CONFIG = {
22
+ reportEndpoint: "/__manus__/logs",
23
+ bufferSize: {
24
+ console: 500,
25
+ network: 200,
26
+ // semantic, agent-friendly UI events
27
+ ui: 500,
28
+ },
29
+ reportInterval: 2000,
30
+ sensitiveFields: [
31
+ "password",
32
+ "token",
33
+ "secret",
34
+ "key",
35
+ "authorization",
36
+ "cookie",
37
+ "session",
38
+ ],
39
+ maxBodyLength: 10240,
40
+ // UI event logging privacy policy:
41
+ // - inputs matching sensitiveFields or type=password are masked by default
42
+ // - non-sensitive inputs log up to 200 chars
43
+ uiInputMaxLen: 200,
44
+ uiTextMaxLen: 80,
45
+ // Scroll throttling: minimum ms between scroll events
46
+ scrollThrottleMs: 500,
47
+ };
48
+
49
+ // ==========================================================================
50
+ // Storage
51
+ // ==========================================================================
52
+ const store = {
53
+ consoleLogs: [],
54
+ networkRequests: [],
55
+ uiEvents: [],
56
+ lastReportTime: Date.now(),
57
+ lastScrollTime: 0,
58
+ };
59
+
60
+ // ==========================================================================
61
+ // Utility Functions
62
+ // ==========================================================================
63
+
64
+ function sanitizeValue(value, depth) {
65
+ if (depth === void 0) depth = 0;
66
+ if (depth > 5) return "[Max Depth]";
67
+ if (value === null) return null;
68
+ if (value === undefined) return undefined;
69
+
70
+ if (typeof value === "string") {
71
+ return value.length > 1000 ? value.slice(0, 1000) + "...[truncated]" : value;
72
+ }
73
+
74
+ if (typeof value !== "object") return value;
75
+
76
+ if (Array.isArray(value)) {
77
+ return value.slice(0, 100).map(function (v) {
78
+ return sanitizeValue(v, depth + 1);
79
+ });
80
+ }
81
+
82
+ var sanitized = {};
83
+ for (var k in value) {
84
+ if (Object.prototype.hasOwnProperty.call(value, k)) {
85
+ var isSensitive = CONFIG.sensitiveFields.some(function (f) {
86
+ return k.toLowerCase().indexOf(f) !== -1;
87
+ });
88
+ if (isSensitive) {
89
+ sanitized[k] = "[REDACTED]";
90
+ } else {
91
+ sanitized[k] = sanitizeValue(value[k], depth + 1);
92
+ }
93
+ }
94
+ }
95
+ return sanitized;
96
+ }
97
+
98
+ function formatArg(arg) {
99
+ try {
100
+ if (arg instanceof Error) {
101
+ return { type: "Error", message: arg.message, stack: arg.stack };
102
+ }
103
+ if (typeof arg === "object") return sanitizeValue(arg);
104
+ return String(arg);
105
+ } catch (e) {
106
+ return "[Unserializable]";
107
+ }
108
+ }
109
+
110
+ function formatArgs(args) {
111
+ var result = [];
112
+ for (var i = 0; i < args.length; i++) result.push(formatArg(args[i]));
113
+ return result;
114
+ }
115
+
116
+ function pruneBuffer(buffer, maxSize) {
117
+ if (buffer.length > maxSize) buffer.splice(0, buffer.length - maxSize);
118
+ }
119
+
120
+ function tryParseJson(str) {
121
+ if (typeof str !== "string") return str;
122
+ try {
123
+ return JSON.parse(str);
124
+ } catch (e) {
125
+ return str;
126
+ }
127
+ }
128
+
129
+ // ==========================================================================
130
+ // Semantic UI Event Logging (agent-friendly)
131
+ // ==========================================================================
132
+
133
+ function shouldIgnoreTarget(target) {
134
+ try {
135
+ if (!target || !(target instanceof Element)) return false;
136
+ return !!target.closest(".manus-no-record");
137
+ } catch (e) {
138
+ return false;
139
+ }
140
+ }
141
+
142
+ function compactText(s, maxLen) {
143
+ try {
144
+ var t = (s || "").trim().replace(/\s+/g, " ");
145
+ if (!t) return "";
146
+ return t.length > maxLen ? t.slice(0, maxLen) + "…" : t;
147
+ } catch (e) {
148
+ return "";
149
+ }
150
+ }
151
+
152
+ function elText(el) {
153
+ try {
154
+ var t = el.innerText || el.textContent || "";
155
+ return compactText(t, CONFIG.uiTextMaxLen);
156
+ } catch (e) {
157
+ return "";
158
+ }
159
+ }
160
+
161
+ function describeElement(el) {
162
+ if (!el || !(el instanceof Element)) return null;
163
+
164
+ var getAttr = function (name) {
165
+ return el.getAttribute(name);
166
+ };
167
+
168
+ var tag = el.tagName ? el.tagName.toLowerCase() : null;
169
+ var id = el.id || null;
170
+ var name = getAttr("name") || null;
171
+ var role = getAttr("role") || null;
172
+ var ariaLabel = getAttr("aria-label") || null;
173
+
174
+ var dataLoc = getAttr("data-loc") || null;
175
+ var testId =
176
+ getAttr("data-testid") ||
177
+ getAttr("data-test-id") ||
178
+ getAttr("data-test") ||
179
+ null;
180
+
181
+ var type = tag === "input" ? (getAttr("type") || "text") : null;
182
+ var href = tag === "a" ? getAttr("href") || null : null;
183
+
184
+ // a small, stable hint for agents (avoid building full CSS paths)
185
+ var selectorHint = null;
186
+ if (testId) selectorHint = '[data-testid="' + testId + '"]';
187
+ else if (dataLoc) selectorHint = '[data-loc="' + dataLoc + '"]';
188
+ else if (id) selectorHint = "#" + id;
189
+ else selectorHint = tag || "unknown";
190
+
191
+ return {
192
+ tag: tag,
193
+ id: id,
194
+ name: name,
195
+ type: type,
196
+ role: role,
197
+ ariaLabel: ariaLabel,
198
+ testId: testId,
199
+ dataLoc: dataLoc,
200
+ href: href,
201
+ text: elText(el),
202
+ selectorHint: selectorHint,
203
+ };
204
+ }
205
+
206
+ function isSensitiveField(el) {
207
+ if (!el || !(el instanceof Element)) return false;
208
+ var tag = el.tagName ? el.tagName.toLowerCase() : "";
209
+ if (tag !== "input" && tag !== "textarea") return false;
210
+
211
+ var type = (el.getAttribute("type") || "").toLowerCase();
212
+ if (type === "password") return true;
213
+
214
+ var name = (el.getAttribute("name") || "").toLowerCase();
215
+ var id = (el.id || "").toLowerCase();
216
+
217
+ return CONFIG.sensitiveFields.some(function (f) {
218
+ return name.indexOf(f) !== -1 || id.indexOf(f) !== -1;
219
+ });
220
+ }
221
+
222
+ function getInputValueSafe(el) {
223
+ if (!el || !(el instanceof Element)) return null;
224
+ var tag = el.tagName ? el.tagName.toLowerCase() : "";
225
+ if (tag !== "input" && tag !== "textarea" && tag !== "select") return null;
226
+
227
+ var v = "";
228
+ try {
229
+ v = el.value != null ? String(el.value) : "";
230
+ } catch (e) {
231
+ v = "";
232
+ }
233
+
234
+ if (isSensitiveField(el)) return { masked: true, length: v.length };
235
+
236
+ if (v.length > CONFIG.uiInputMaxLen) v = v.slice(0, CONFIG.uiInputMaxLen) + "…";
237
+ return v;
238
+ }
239
+
240
+ function logUiEvent(kind, payload) {
241
+ var entry = {
242
+ timestamp: Date.now(),
243
+ kind: kind,
244
+ url: location.href,
245
+ viewport: { width: window.innerWidth, height: window.innerHeight },
246
+ payload: sanitizeValue(payload),
247
+ };
248
+ store.uiEvents.push(entry);
249
+ pruneBuffer(store.uiEvents, CONFIG.bufferSize.ui);
250
+ }
251
+
252
+ function installUiEventListeners() {
253
+ // Clicks
254
+ document.addEventListener(
255
+ "click",
256
+ function (e) {
257
+ var t = e.target;
258
+ if (shouldIgnoreTarget(t)) return;
259
+ logUiEvent("click", {
260
+ target: describeElement(t),
261
+ x: e.clientX,
262
+ y: e.clientY,
263
+ });
264
+ },
265
+ true
266
+ );
267
+
268
+ // Typing "commit" events
269
+ document.addEventListener(
270
+ "change",
271
+ function (e) {
272
+ var t = e.target;
273
+ if (shouldIgnoreTarget(t)) return;
274
+ logUiEvent("change", {
275
+ target: describeElement(t),
276
+ value: getInputValueSafe(t),
277
+ });
278
+ },
279
+ true
280
+ );
281
+
282
+ document.addEventListener(
283
+ "focusin",
284
+ function (e) {
285
+ var t = e.target;
286
+ if (shouldIgnoreTarget(t)) return;
287
+ logUiEvent("focusin", { target: describeElement(t) });
288
+ },
289
+ true
290
+ );
291
+
292
+ document.addEventListener(
293
+ "focusout",
294
+ function (e) {
295
+ var t = e.target;
296
+ if (shouldIgnoreTarget(t)) return;
297
+ logUiEvent("focusout", {
298
+ target: describeElement(t),
299
+ value: getInputValueSafe(t),
300
+ });
301
+ },
302
+ true
303
+ );
304
+
305
+ // Enter/Escape are useful for form flows & modals
306
+ document.addEventListener(
307
+ "keydown",
308
+ function (e) {
309
+ if (e.key !== "Enter" && e.key !== "Escape") return;
310
+ var t = e.target;
311
+ if (shouldIgnoreTarget(t)) return;
312
+ logUiEvent("keydown", { key: e.key, target: describeElement(t) });
313
+ },
314
+ true
315
+ );
316
+
317
+ // Form submissions
318
+ document.addEventListener(
319
+ "submit",
320
+ function (e) {
321
+ var t = e.target;
322
+ if (shouldIgnoreTarget(t)) return;
323
+ logUiEvent("submit", { target: describeElement(t) });
324
+ },
325
+ true
326
+ );
327
+
328
+ // Throttled scroll events
329
+ window.addEventListener(
330
+ "scroll",
331
+ function () {
332
+ var now = Date.now();
333
+ if (now - store.lastScrollTime < CONFIG.scrollThrottleMs) return;
334
+ store.lastScrollTime = now;
335
+
336
+ logUiEvent("scroll", {
337
+ scrollX: window.scrollX,
338
+ scrollY: window.scrollY,
339
+ documentHeight: document.documentElement.scrollHeight,
340
+ viewportHeight: window.innerHeight,
341
+ });
342
+ },
343
+ { passive: true }
344
+ );
345
+
346
+ // Navigation tracking for SPAs
347
+ function nav(reason) {
348
+ logUiEvent("navigate", { reason: reason });
349
+ }
350
+
351
+ var origPush = history.pushState;
352
+ history.pushState = function () {
353
+ origPush.apply(this, arguments);
354
+ nav("pushState");
355
+ };
356
+
357
+ var origReplace = history.replaceState;
358
+ history.replaceState = function () {
359
+ origReplace.apply(this, arguments);
360
+ nav("replaceState");
361
+ };
362
+
363
+ window.addEventListener("popstate", function () {
364
+ nav("popstate");
365
+ });
366
+ window.addEventListener("hashchange", function () {
367
+ nav("hashchange");
368
+ });
369
+ }
370
+
371
+ // ==========================================================================
372
+ // Console Interception
373
+ // ==========================================================================
374
+
375
+ var originalConsole = {
376
+ log: console.log.bind(console),
377
+ debug: console.debug.bind(console),
378
+ info: console.info.bind(console),
379
+ warn: console.warn.bind(console),
380
+ error: console.error.bind(console),
381
+ };
382
+
383
+ ["log", "debug", "info", "warn", "error"].forEach(function (method) {
384
+ console[method] = function () {
385
+ var args = Array.prototype.slice.call(arguments);
386
+
387
+ var entry = {
388
+ timestamp: Date.now(),
389
+ level: method.toUpperCase(),
390
+ args: formatArgs(args),
391
+ stack: method === "error" ? new Error().stack : null,
392
+ };
393
+
394
+ store.consoleLogs.push(entry);
395
+ pruneBuffer(store.consoleLogs, CONFIG.bufferSize.console);
396
+
397
+ originalConsole[method].apply(console, args);
398
+ };
399
+ });
400
+
401
+ window.addEventListener("error", function (event) {
402
+ store.consoleLogs.push({
403
+ timestamp: Date.now(),
404
+ level: "ERROR",
405
+ args: [
406
+ {
407
+ type: "UncaughtError",
408
+ message: event.message,
409
+ filename: event.filename,
410
+ lineno: event.lineno,
411
+ colno: event.colno,
412
+ stack: event.error ? event.error.stack : null,
413
+ },
414
+ ],
415
+ stack: event.error ? event.error.stack : null,
416
+ });
417
+ pruneBuffer(store.consoleLogs, CONFIG.bufferSize.console);
418
+
419
+ // Mark an error moment in UI event stream for agents
420
+ logUiEvent("error", {
421
+ message: event.message,
422
+ filename: event.filename,
423
+ lineno: event.lineno,
424
+ colno: event.colno,
425
+ });
426
+ });
427
+
428
+ window.addEventListener("unhandledrejection", function (event) {
429
+ var reason = event.reason;
430
+ store.consoleLogs.push({
431
+ timestamp: Date.now(),
432
+ level: "ERROR",
433
+ args: [
434
+ {
435
+ type: "UnhandledRejection",
436
+ reason: reason && reason.message ? reason.message : String(reason),
437
+ stack: reason && reason.stack ? reason.stack : null,
438
+ },
439
+ ],
440
+ stack: reason && reason.stack ? reason.stack : null,
441
+ });
442
+ pruneBuffer(store.consoleLogs, CONFIG.bufferSize.console);
443
+
444
+ logUiEvent("unhandledrejection", {
445
+ reason: reason && reason.message ? reason.message : String(reason),
446
+ });
447
+ });
448
+
449
+ // ==========================================================================
450
+ // Fetch Interception
451
+ // ==========================================================================
452
+
453
+ var originalFetch = window.fetch.bind(window);
454
+
455
+ window.fetch = function (input, init) {
456
+ init = init || {};
457
+ var startTime = Date.now();
458
+ // Handle string, Request object, or URL object
459
+ var url = typeof input === "string"
460
+ ? input
461
+ : (input && (input.url || input.href || String(input))) || "";
462
+ var method = init.method || (input && input.method) || "GET";
463
+
464
+ // Don't intercept internal requests
465
+ if (url.indexOf("/__manus__/") === 0) {
466
+ return originalFetch(input, init);
467
+ }
468
+
469
+ // Safely parse headers (avoid breaking if headers format is invalid)
470
+ var requestHeaders = {};
471
+ try {
472
+ if (init.headers) {
473
+ requestHeaders = Object.fromEntries(new Headers(init.headers).entries());
474
+ }
475
+ } catch (e) {
476
+ requestHeaders = { _parseError: true };
477
+ }
478
+
479
+ var entry = {
480
+ timestamp: startTime,
481
+ type: "fetch",
482
+ method: method.toUpperCase(),
483
+ url: url,
484
+ request: {
485
+ headers: requestHeaders,
486
+ body: init.body ? sanitizeValue(tryParseJson(init.body)) : null,
487
+ },
488
+ response: null,
489
+ duration: null,
490
+ error: null,
491
+ };
492
+
493
+ return originalFetch(input, init)
494
+ .then(function (response) {
495
+ entry.duration = Date.now() - startTime;
496
+
497
+ var contentType = (response.headers.get("content-type") || "").toLowerCase();
498
+ var contentLength = response.headers.get("content-length");
499
+
500
+ entry.response = {
501
+ status: response.status,
502
+ statusText: response.statusText,
503
+ headers: Object.fromEntries(response.headers.entries()),
504
+ body: null,
505
+ };
506
+
507
+ // Semantic network hint for agents on failures (sync, no need to wait for body)
508
+ if (response.status >= 400) {
509
+ logUiEvent("network_error", {
510
+ kind: "fetch",
511
+ method: entry.method,
512
+ url: entry.url,
513
+ status: response.status,
514
+ statusText: response.statusText,
515
+ });
516
+ }
517
+
518
+ // Skip body capture for streaming responses (SSE, etc.) to avoid memory leaks
519
+ var isStreaming = contentType.indexOf("text/event-stream") !== -1 ||
520
+ contentType.indexOf("application/stream") !== -1 ||
521
+ contentType.indexOf("application/x-ndjson") !== -1;
522
+ if (isStreaming) {
523
+ entry.response.body = "[Streaming response - not captured]";
524
+ store.networkRequests.push(entry);
525
+ pruneBuffer(store.networkRequests, CONFIG.bufferSize.network);
526
+ return response;
527
+ }
528
+
529
+ // Skip body capture for large responses to avoid memory issues
530
+ if (contentLength && parseInt(contentLength, 10) > CONFIG.maxBodyLength) {
531
+ entry.response.body = "[Response too large: " + contentLength + " bytes]";
532
+ store.networkRequests.push(entry);
533
+ pruneBuffer(store.networkRequests, CONFIG.bufferSize.network);
534
+ return response;
535
+ }
536
+
537
+ // Skip body capture for binary content types
538
+ var isBinary = contentType.indexOf("image/") !== -1 ||
539
+ contentType.indexOf("video/") !== -1 ||
540
+ contentType.indexOf("audio/") !== -1 ||
541
+ contentType.indexOf("application/octet-stream") !== -1 ||
542
+ contentType.indexOf("application/pdf") !== -1 ||
543
+ contentType.indexOf("application/zip") !== -1;
544
+ if (isBinary) {
545
+ entry.response.body = "[Binary content: " + contentType + "]";
546
+ store.networkRequests.push(entry);
547
+ pruneBuffer(store.networkRequests, CONFIG.bufferSize.network);
548
+ return response;
549
+ }
550
+
551
+ // For text responses, clone and read body in background
552
+ var clonedResponse = response.clone();
553
+
554
+ // Async: read body in background, don't block the response
555
+ clonedResponse
556
+ .text()
557
+ .then(function (text) {
558
+ if (text.length <= CONFIG.maxBodyLength) {
559
+ entry.response.body = sanitizeValue(tryParseJson(text));
560
+ } else {
561
+ entry.response.body = text.slice(0, CONFIG.maxBodyLength) + "...[truncated]";
562
+ }
563
+ })
564
+ .catch(function () {
565
+ entry.response.body = "[Unable to read body]";
566
+ })
567
+ .finally(function () {
568
+ store.networkRequests.push(entry);
569
+ pruneBuffer(store.networkRequests, CONFIG.bufferSize.network);
570
+ });
571
+
572
+ // Return response immediately, don't wait for body reading
573
+ return response;
574
+ })
575
+ .catch(function (error) {
576
+ entry.duration = Date.now() - startTime;
577
+ entry.error = { message: error.message, stack: error.stack };
578
+
579
+ store.networkRequests.push(entry);
580
+ pruneBuffer(store.networkRequests, CONFIG.bufferSize.network);
581
+
582
+ logUiEvent("network_error", {
583
+ kind: "fetch",
584
+ method: entry.method,
585
+ url: entry.url,
586
+ message: error.message,
587
+ });
588
+
589
+ throw error;
590
+ });
591
+ };
592
+
593
+ // ==========================================================================
594
+ // XHR Interception
595
+ // ==========================================================================
596
+
597
+ var originalXHROpen = XMLHttpRequest.prototype.open;
598
+ var originalXHRSend = XMLHttpRequest.prototype.send;
599
+
600
+ XMLHttpRequest.prototype.open = function (method, url) {
601
+ this._manusData = {
602
+ method: (method || "GET").toUpperCase(),
603
+ url: url,
604
+ startTime: null,
605
+ };
606
+ return originalXHROpen.apply(this, arguments);
607
+ };
608
+
609
+ XMLHttpRequest.prototype.send = function (body) {
610
+ var xhr = this;
611
+
612
+ if (
613
+ xhr._manusData &&
614
+ xhr._manusData.url &&
615
+ xhr._manusData.url.indexOf("/__manus__/") !== 0
616
+ ) {
617
+ xhr._manusData.startTime = Date.now();
618
+ xhr._manusData.requestBody = body ? sanitizeValue(tryParseJson(body)) : null;
619
+
620
+ xhr.addEventListener("load", function () {
621
+ var contentType = (xhr.getResponseHeader("content-type") || "").toLowerCase();
622
+ var responseBody = null;
623
+
624
+ // Skip body capture for streaming responses
625
+ var isStreaming = contentType.indexOf("text/event-stream") !== -1 ||
626
+ contentType.indexOf("application/stream") !== -1 ||
627
+ contentType.indexOf("application/x-ndjson") !== -1;
628
+
629
+ // Skip body capture for binary content types
630
+ var isBinary = contentType.indexOf("image/") !== -1 ||
631
+ contentType.indexOf("video/") !== -1 ||
632
+ contentType.indexOf("audio/") !== -1 ||
633
+ contentType.indexOf("application/octet-stream") !== -1 ||
634
+ contentType.indexOf("application/pdf") !== -1 ||
635
+ contentType.indexOf("application/zip") !== -1;
636
+
637
+ if (isStreaming) {
638
+ responseBody = "[Streaming response - not captured]";
639
+ } else if (isBinary) {
640
+ responseBody = "[Binary content: " + contentType + "]";
641
+ } else {
642
+ // Safe to read responseText for text responses
643
+ try {
644
+ var text = xhr.responseText || "";
645
+ if (text.length > CONFIG.maxBodyLength) {
646
+ responseBody = text.slice(0, CONFIG.maxBodyLength) + "...[truncated]";
647
+ } else {
648
+ responseBody = sanitizeValue(tryParseJson(text));
649
+ }
650
+ } catch (e) {
651
+ // responseText may throw for non-text responses
652
+ responseBody = "[Unable to read response: " + e.message + "]";
653
+ }
654
+ }
655
+
656
+ var entry = {
657
+ timestamp: xhr._manusData.startTime,
658
+ type: "xhr",
659
+ method: xhr._manusData.method,
660
+ url: xhr._manusData.url,
661
+ request: { body: xhr._manusData.requestBody },
662
+ response: {
663
+ status: xhr.status,
664
+ statusText: xhr.statusText,
665
+ body: responseBody,
666
+ },
667
+ duration: Date.now() - xhr._manusData.startTime,
668
+ error: null,
669
+ };
670
+
671
+ store.networkRequests.push(entry);
672
+ pruneBuffer(store.networkRequests, CONFIG.bufferSize.network);
673
+
674
+ if (entry.response && entry.response.status >= 400) {
675
+ logUiEvent("network_error", {
676
+ kind: "xhr",
677
+ method: entry.method,
678
+ url: entry.url,
679
+ status: entry.response.status,
680
+ statusText: entry.response.statusText,
681
+ });
682
+ }
683
+ });
684
+
685
+ xhr.addEventListener("error", function () {
686
+ var entry = {
687
+ timestamp: xhr._manusData.startTime,
688
+ type: "xhr",
689
+ method: xhr._manusData.method,
690
+ url: xhr._manusData.url,
691
+ request: { body: xhr._manusData.requestBody },
692
+ response: null,
693
+ duration: Date.now() - xhr._manusData.startTime,
694
+ error: { message: "Network error" },
695
+ };
696
+
697
+ store.networkRequests.push(entry);
698
+ pruneBuffer(store.networkRequests, CONFIG.bufferSize.network);
699
+
700
+ logUiEvent("network_error", {
701
+ kind: "xhr",
702
+ method: entry.method,
703
+ url: entry.url,
704
+ message: "Network error",
705
+ });
706
+ });
707
+ }
708
+
709
+ return originalXHRSend.apply(this, arguments);
710
+ };
711
+
712
+ // ==========================================================================
713
+ // Data Reporting
714
+ // ==========================================================================
715
+
716
+ function reportLogs() {
717
+ var consoleLogs = store.consoleLogs.splice(0);
718
+ var networkRequests = store.networkRequests.splice(0);
719
+ var uiEvents = store.uiEvents.splice(0);
720
+
721
+ // Skip if no new data
722
+ if (
723
+ consoleLogs.length === 0 &&
724
+ networkRequests.length === 0 &&
725
+ uiEvents.length === 0
726
+ ) {
727
+ return Promise.resolve();
728
+ }
729
+
730
+ var payload = {
731
+ timestamp: Date.now(),
732
+ consoleLogs: consoleLogs,
733
+ networkRequests: networkRequests,
734
+ // Mirror uiEvents to sessionEvents for sessionReplay.log
735
+ sessionEvents: uiEvents,
736
+ // agent-friendly semantic events
737
+ uiEvents: uiEvents,
738
+ };
739
+
740
+ return originalFetch(CONFIG.reportEndpoint, {
741
+ method: "POST",
742
+ headers: { "Content-Type": "application/json" },
743
+ body: JSON.stringify(payload),
744
+ }).catch(function () {
745
+ // Put data back on failure (but respect limits)
746
+ store.consoleLogs = consoleLogs.concat(store.consoleLogs);
747
+ store.networkRequests = networkRequests.concat(store.networkRequests);
748
+ store.uiEvents = uiEvents.concat(store.uiEvents);
749
+
750
+ pruneBuffer(store.consoleLogs, CONFIG.bufferSize.console);
751
+ pruneBuffer(store.networkRequests, CONFIG.bufferSize.network);
752
+ pruneBuffer(store.uiEvents, CONFIG.bufferSize.ui);
753
+ });
754
+ }
755
+
756
+ // Periodic reporting
757
+ setInterval(reportLogs, CONFIG.reportInterval);
758
+
759
+ // Report on page unload
760
+ window.addEventListener("beforeunload", function () {
761
+ var consoleLogs = store.consoleLogs;
762
+ var networkRequests = store.networkRequests;
763
+ var uiEvents = store.uiEvents;
764
+
765
+ if (
766
+ consoleLogs.length === 0 &&
767
+ networkRequests.length === 0 &&
768
+ uiEvents.length === 0
769
+ ) {
770
+ return;
771
+ }
772
+
773
+ var payload = {
774
+ timestamp: Date.now(),
775
+ consoleLogs: consoleLogs,
776
+ networkRequests: networkRequests,
777
+ // Mirror uiEvents to sessionEvents for sessionReplay.log
778
+ sessionEvents: uiEvents,
779
+ uiEvents: uiEvents,
780
+ };
781
+
782
+ if (navigator.sendBeacon) {
783
+ var payloadStr = JSON.stringify(payload);
784
+ // sendBeacon has ~64KB limit, truncate if too large
785
+ var MAX_BEACON_SIZE = 60000; // Leave some margin
786
+ if (payloadStr.length > MAX_BEACON_SIZE) {
787
+ // Prioritize: keep recent events, drop older logs
788
+ var truncatedPayload = {
789
+ timestamp: Date.now(),
790
+ consoleLogs: consoleLogs.slice(-50),
791
+ networkRequests: networkRequests.slice(-20),
792
+ sessionEvents: uiEvents.slice(-100),
793
+ uiEvents: uiEvents.slice(-100),
794
+ _truncated: true,
795
+ };
796
+ payloadStr = JSON.stringify(truncatedPayload);
797
+ }
798
+ navigator.sendBeacon(CONFIG.reportEndpoint, payloadStr);
799
+ }
800
+ });
801
+
802
+ // ==========================================================================
803
+ // Initialization
804
+ // ==========================================================================
805
+
806
+ // Install semantic UI listeners ASAP
807
+ try {
808
+ installUiEventListeners();
809
+ } catch (e) {
810
+ console.warn("[Manus] Failed to install UI listeners:", e);
811
+ }
812
+
813
+ // Mark as initialized
814
+ window.__MANUS_DEBUG_COLLECTOR__ = {
815
+ version: "2.0-no-rrweb",
816
+ store: store,
817
+ forceReport: reportLogs,
818
+ };
819
+
820
+ console.debug("[Manus] Debug collector initialized (no rrweb, UI events only)");
821
+ })();