(function (global) { 'use strict'; var WorkbenchAgent = global.PWAgent; if (!WorkbenchAgent) { console.error('[调试工作台] agent-console 需先加载 agent-core'); return; } var CONSOLE_FLUSH_MS = 100; WorkbenchAgent.prototype._hookConsole = function (on) { var self = this; if (on) { if (this._consoleOrig) return; this._consoleOrig = { log: console.log.bind(console), info: console.info.bind(console), warn: console.warn.bind(console), error: console.error.bind(console), debug: console.debug.bind(console), }; var wrap = function (level, fn) { return function () { var args = Array.prototype.slice.call(arguments); self._pushConsole(level, args); return fn.apply(console, args); }; }; console.log = wrap('log', this._consoleOrig.log); console.info = wrap('info', this._consoleOrig.info); console.warn = wrap('warn', this._consoleOrig.warn); console.error = wrap('error', this._consoleOrig.error); console.debug = wrap('debug', this._consoleOrig.debug); this._bindConsoleErrors(true); } else if (this._consoleOrig) { this._bindConsoleErrors(false); console.log = this._consoleOrig.log; console.info = this._consoleOrig.info; console.warn = this._consoleOrig.warn; console.error = this._consoleOrig.error; console.debug = this._consoleOrig.debug; this._consoleOrig = null; this._consolePost = false; if (this._consoleFlushTimer) { clearTimeout(this._consoleFlushTimer); this._consoleFlushTimer = 0; } this._consoleBuf.length = 0; } }; /** 挂载/卸载 window 未捕获异常监听(随 console 通道启停) */ WorkbenchAgent.prototype._bindConsoleErrors = function (on) { if (on) { if (this._consoleErrBound) return; window.addEventListener('error', this._boundConsoleError); window.addEventListener('unhandledrejection', this._boundConsoleRejection); this._consoleErrBound = true; } else if (this._consoleErrBound) { window.removeEventListener('error', this._boundConsoleError); window.removeEventListener('unhandledrejection', this._boundConsoleRejection); this._consoleErrBound = false; } }; /** 脚本运行时错误 → 控制台 error */ WorkbenchAgent.prototype._onConsoleError = function (ev) { if (!ev) return; var args = ev.error instanceof Error ? [ev.error] : [ev.message || 'Script error']; var entry = this._serializeConsoleArgs('error', args); if (ev.filename) { entry.src = this._srcFromUrl(ev.filename, ev.lineno || 0, ev.colno || 0); } this._enqueueConsole(entry); }; /** 未处理 Promise 拒绝 → 对齐浏览器 Uncaught (in promise) */ WorkbenchAgent.prototype._onConsoleRejection = function (ev) { var reason = ev && ev.reason; var args; if (reason instanceof Error) { args = ['Uncaught (in promise)', reason]; } else { args = ['Uncaught (in promise) ' + String(reason)]; } this._pushConsole('error', args); }; WorkbenchAgent.prototype._pushConsole = function (level, args) { this._enqueueConsole(this._serializeConsoleArgs(level, args)); }; WorkbenchAgent.prototype._enqueueConsole = function (entry) { if (!entry) return; if (this._consolePost && !this._hibernating) { this._consoleBuf.push(entry); var self = this; if (!this._consoleFlushTimer) { this._consoleFlushTimer = setTimeout(function () { self._consoleFlushTimer = 0; self._flushConsole(); }, CONSOLE_FLUSH_MS); } return; } // 未订阅时静默环缓冲,打开面板时再推送 this._consoleHist.push(entry); if (this._consoleHist.length > 200) this._consoleHist.splice(0, this._consoleHist.length - 200); }; WorkbenchAgent.prototype._flushConsole = function () { if (!this._consolePost || !this._consoleBuf.length) { this._consoleBuf.length = 0; return; } var batch = this._consoleBuf.splice(0, this._consoleBuf.length); this._post({ type: 'event', name: 'console.batch', data: { items: batch } }); }; /** 订阅时把未推送历史一次性推给壳 */ WorkbenchAgent.prototype._flushConsoleHist = function () { if (!this._consolePost || !this._consoleHist.length) return; var batch = this._consoleHist.splice(0, this._consoleHist.length); this._post({ type: 'event', name: 'console.batch', data: { items: batch } }); this._consoleBuf.length = 0; }; /** * 将 console 参数序列化为可 postMessage 的富结构(保留 %c/%s 等,对象可展开)。 * 原始 console.* 仍会调用,浏览器 DevTools 打印不受影响。 */ WorkbenchAgent.prototype._serializeConsoleArgs = function (level, args) { var parts = []; var plain = ''; var FMT_RE = /%(?:%|[csdifoO])/; var rawStack = ''; try { rawStack = new Error().stack || ''; } catch (e0) { rawStack = ''; } if (args.length && typeof args[0] === 'string' && FMT_RE.test(args[0])) { var formatted = this._formatConsole(args); parts = formatted.parts; plain = formatted.plain; // 对齐 Chrome:%c 只作用于格式串本身;其后附加参数用默认色、不加引号 for (var r = 0; r < formatted.rest.length; r++) { var rv = formatted.rest[r]; if (typeof rv === 'string') { parts.push({ kind: 'text', text: rv, plain: rv }); plain += (plain ? ' ' : '') + rv; } else { var rp = this._valuePart(rv); parts.push(rp); plain += (plain ? ' ' : '') + (rp.plain || ''); } } } else { for (var i = 0; i < args.length; i++) { // 顶层消息字符串不加引号(属性展开时仍用 string 带引号) var p = this._valuePart(args[i], typeof args[i] === 'string'); parts.push(p); plain += (i ? ' ' : '') + (p.plain || ''); } } var src = this._srcFromStack(rawStack); var callStack = ''; for (var j = 0; j < parts.length; j++) { if (parts[j] && parts[j].kind === 'error' && parts[j].stack) { if (!src) src = this._srcFromStack(parts[j].stack); callStack = parts[j].stack; break; } } if (!callStack && (level === 'warn' || level === 'error')) { callStack = this._businessStack(rawStack); } return { id: 'c' + Date.now() + '_' + Math.random().toString(36).slice(2, 7), level: level, t: Date.now(), parts: parts, plain: plain, src: src, stack: callStack || '', }; }; /** 去掉 agent 帧后的业务堆栈文本 */ WorkbenchAgent.prototype._businessStack = function (stack) { if (!stack) return ''; var lines = String(stack).split('\n'); var out = []; for (var i = 0; i < lines.length; i++) { var line = lines[i]; var t = line.trim(); if (!t) continue; if (t.indexOf('Error') === 0 && t.indexOf('at ') < 0) continue; if (this._isAgentStackLine(t)) continue; out.push(line); } return out.join('\n'); }; WorkbenchAgent.prototype._isAgentStackLine = function (line) { return /agent(-\w+)?\.js|WorkbenchAgent|_pushConsole|_enqueueConsole|_serializeConsole|_captureConsoleSrc|_srcFromStack|_businessStack|_hookConsole|_onConsoleError|_onConsoleRejection|_valuePart|_formatConsole|_flushConsole/i.test(line); }; /** 解析 stack:优先可读源文件名,跳过 agent / UUID 哈希帧 */ WorkbenchAgent.prototype._srcFromStack = function (stack) { if (!stack) return null; var lines = String(stack).split('\n'); var best = null; var bestScore = -1; for (var i = 0; i < lines.length; i++) { var line = lines[i].trim(); if (!line || /^error$/i.test(line) || (line.indexOf('Error') === 0 && line.indexOf('at ') < 0)) continue; if (this._isAgentStackLine(line)) continue; var m = line.match(/\((.+?):(\d+):(\d+)\)\s*$/) || line.match(/at\s+(\S+?):(\d+):(\d+)\s*$/); if (!m) continue; var cand = this._srcFromUrl(m[1], parseInt(m[2], 10) || 0, parseInt(m[3], 10) || 0); var score = this._scoreSrcFile(cand.file); if (score > bestScore) { bestScore = score; best = cand; } } return best; }; /** 来源文件可读性评分(越高越优先展示) */ WorkbenchAgent.prototype._scoreSrcFile = function (file) { var f = String(file || ''); if (!f || f === '(anonymous)') return 0; if (/^[a-f0-9]{16,}$/i.test(f) || /^[a-f0-9-]{32,}$/i.test(f)) return 1; if (/\.ts$/i.test(f)) return 50; if (/\.js$/i.test(f) && /[a-zA-Z]/.test(f) && !/^[a-f0-9.-]+$/i.test(f.replace(/\.js$/i, ''))) return 40; if (/\.(js|mjs|cjs)$/i.test(f)) return 20; if (/[a-zA-Z]/.test(f)) return 10; return 2; }; /** URL + 行列 → 简短来源 */ WorkbenchAgent.prototype._srcFromUrl = function (url, line, col) { var u = String(url || ''); var file = u.split('/').pop() || u; var q = file.indexOf('?'); if (q >= 0) file = file.slice(0, q); var h = file.indexOf('#'); if (h >= 0) file = file.slice(0, h); if (!file) file = '(anonymous)'; var ln = line || 0; return { file: file, line: ln, col: col || 0, text: ln ? (file + ':' + ln) : file, }; }; WorkbenchAgent.prototype._formatConsole = function (args) { var fmt = args[0]; var ai = 1; var parts = []; var plain = ''; var style = ''; var i = 0; while (i < fmt.length) { if (fmt.charAt(i) === '%' && i + 1 < fmt.length) { var spec = fmt.charAt(i + 1); if (spec === '%') { parts.push({ kind: 'styled', text: '%', css: style }); plain += '%'; i += 2; continue; } if (spec === 'c') { style = typeof args[ai] === 'string' ? args[ai] : ''; ai++; i += 2; continue; } var val = ai < args.length ? args[ai] : undefined; ai++; var text = ''; if (spec === 's') text = val == null ? 'null' : String(val); else if (spec === 'd' || spec === 'i') text = String(parseInt(val, 10) || 0); else if (spec === 'f') text = String(Number(val)); else if (spec === 'o' || spec === 'O') { var vp = this._valuePart(val); parts.push(vp); plain += vp.plain || ''; i += 2; continue; } else { text = '%' + spec; } parts.push({ kind: 'styled', text: text, css: style }); plain += text; i += 2; continue; } var start = i; while (i < fmt.length && fmt.charAt(i) !== '%') i++; var lit = fmt.slice(start, i); if (lit) { parts.push({ kind: 'styled', text: lit, css: style }); plain += lit; } } return { parts: parts, plain: plain, rest: args.slice(ai), style: style }; }; WorkbenchAgent.prototype._valuePart = function (value, bareString) { if (value === null) return { kind: 'null', plain: 'null' }; var t = typeof value; if (t === 'undefined') return { kind: 'undefined', plain: 'undefined' }; if (t === 'string') { return bareString ? { kind: 'text', text: value, plain: value } : { kind: 'string', text: value, plain: '"' + value + '"' }; } if (t === 'number' || t === 'boolean' || t === 'bigint') { return { kind: t, value: t === 'bigint' ? String(value) : value, plain: String(value) }; } if (t === 'symbol') return { kind: 'symbol', text: String(value), plain: String(value) }; if (t === 'function') { var fname = value.name || 'anonymous'; return { kind: 'function', name: fname, plain: 'ƒ ' + fname + '()' }; } if (value instanceof Error) { return { kind: 'error', name: value.name || 'Error', message: value.message || '', stack: value.stack || '', plain: (value.name || 'Error') + ': ' + (value.message || ''), }; } var oid = 'o' + (this._consoleObjSeq++); this._consoleObjs[oid] = value; var isArray = Array.isArray(value); var ctor = (value && value.constructor && value.constructor.name) || (isArray ? 'Array' : 'Object'); var preview = isArray ? ('Array(' + value.length + ')') : ctor; try { if (isArray) { var bits = []; for (var i = 0; i < Math.min(value.length, 5); i++) bits.push(briefVal(value[i])); if (value.length > 5) bits.push('…'); preview += ' [' + bits.join(', ') + ']'; } else { var keys = Object.keys(value); var bits2 = []; for (var j = 0; j < Math.min(keys.length, 4); j++) { bits2.push(keys[j] + ': ' + briefVal(value[keys[j]])); } if (keys.length > 4) bits2.push('…'); preview += ' {' + bits2.join(', ') + '}'; } } catch (e) { /* */ } return { kind: 'object', oid: oid, ctor: ctor, isArray: isArray, length: isArray ? value.length : undefined, preview: preview, plain: preview, }; }; WorkbenchAgent.prototype._inspectConsoleObj = function (oid, path) { var root = this._consoleObjs[oid]; if (!root) return { props: [] }; var target = root; var segs = String(path || '').split('.').filter(Boolean); for (var i = 0; i < segs.length; i++) { try { target = target[segs[i]]; } catch (e) { return { props: [] }; } } var props = []; var MAX = 40; try { if (Array.isArray(target)) { for (var a = 0; a < Math.min(target.length, MAX); a++) { props.push({ key: String(a), part: this._valuePart(target[a]) }); } if (target.length > MAX) props.push({ key: '…', part: { kind: 'text', text: '…', plain: '…' } }); } else if (target && typeof target === 'object') { var keys = Object.getOwnPropertyNames(target); for (var k = 0; k < Math.min(keys.length, MAX); k++) { var key = keys[k]; var child; try { child = target[key]; } catch (err) { child = '⟨access error⟩'; } props.push({ key: key, part: this._valuePart(child) }); } if (keys.length > MAX) props.push({ key: '…', part: { kind: 'text', text: '…', plain: '…' } }); } } catch (e2) { /* */ } return { props: props }; }; function briefVal(v) { if (v === null) return 'null'; var t = typeof v; if (t === 'string') return '"' + (v.length > 24 ? v.slice(0, 24) + '…' : v) + '"'; if (t === 'number' || t === 'boolean' || t === 'undefined') return String(v); if (t === 'function') return 'ƒ'; if (Array.isArray(v)) return 'Array(' + v.length + ')'; if (t === 'object') return '{…}'; return String(v); } })(window);