@aiyiran/myclaw 1.1.154 → 1.1.156

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.
@@ -121,25 +121,40 @@ function appendDebugEvent(probeEvent) {
121
121
 
122
122
  /**
123
123
  * 控制日志长度,避免无限膨胀。
124
+ *
125
+ * 关键约束:作品可能在循环里疯狂打 console.log(每帧几十条),若简单地"保留
126
+ * 最近 N 条",会让 console 噪音把 page-enter / 点击 / 按键 / 报错等关键事件
127
+ * 全部挤掉。所以这里分两档:
128
+ * - 关键事件(导航 / 交互 / 各类报错)优先全量保留;
129
+ * - console 只保留最近一批,给它单独的配额。
124
130
  */
125
131
  function trimDebugEvents(events) {
126
132
  if (events.length <= MAX_EVENTS) {
127
133
  return;
128
134
  }
129
135
 
130
- const importantTypes = new Set([
136
+ const KEY_TYPES = new Set([
137
+ "probe-ready",
138
+ "page-enter",
139
+ "click",
140
+ "keydown",
131
141
  "runtime-error",
132
142
  "unhandledrejection",
133
- "resource-error"
143
+ "resource-error",
144
+ "console-truncated"
134
145
  ]);
135
146
 
136
- const importantEvents = events.filter((event) =>
137
- importantTypes.has(event.type)
138
- );
147
+ const keyEvents = events.filter((event) => KEY_TYPES.has(event.type));
148
+ const consoleEvents = events.filter((event) => !KEY_TYPES.has(event.type));
139
149
 
140
- const latestEvents = events.slice(-MAX_EVENTS);
150
+ // console 配额 = 总上限减去关键事件占用,至少留一部分给 console。
151
+ const consoleQuota = Math.max(0, MAX_EVENTS - keyEvents.length);
152
+ const keptConsole = consoleEvents.slice(-consoleQuota);
141
153
 
142
- const merged = [...importantEvents, ...latestEvents];
154
+ // 合并后按 seq 还原时间顺序,再做去重。
155
+ const merged = [...keyEvents, ...keptConsole].sort(
156
+ (a, b) => (a.seq || 0) - (b.seq || 0)
157
+ );
143
158
 
144
159
  const seen = new Set();
145
160
  const deduped = [];
@@ -317,6 +332,16 @@ function renderDebugRecordPreview(record) {
317
332
  function formatEventForStudent(event) {
318
333
  const time = event.time ? event.time.slice(11, 19) : "";
319
334
 
335
+ if (event.type === "probe-ready") {
336
+ const version = event.payload && event.payload.probeVersion;
337
+ return `${time} 调试探针启动${version ? "(" + version + ")" : ""}`;
338
+ }
339
+
340
+ if (event.type === "console-truncated") {
341
+ const limit = event.payload && event.payload.limit;
342
+ return `${time} 程序输出过多,已截断${limit ? "(上限 " + limit + " 条)" : ""}`;
343
+ }
344
+
320
345
  if (event.type === "page-enter") {
321
346
  return `${time} 进入页面:${event.pageTitle || event.pagePath || event.pageUrl}`;
322
347
  }
@@ -19,11 +19,18 @@
19
19
 
20
20
  window.__OPENCLAW_DEBUG_PROBE_INSTALLED__ = true;
21
21
 
22
- const PROBE_VERSION = "probe_003";
22
+ const PROBE_VERSION = "probe_004";
23
23
  const MAX_TEXT_LENGTH = 1000;
24
24
  const MAX_STACK_LENGTH = 3000;
25
25
  const MAX_ELEMENT_TEXT_LENGTH = 80;
26
26
 
27
+ // console 上报限流:作品可能在循环里疯狂打 log,无节制上报会刷爆父页面
28
+ // 的事件缓冲、把 page-enter / error 等关键事件挤掉。超过上限后停止上报
29
+ // console(其它类型不受影响),并补一条截断提示。
30
+ const MAX_CONSOLE_EVENTS = 100;
31
+ let _consoleEventCount = 0;
32
+ let _consoleTruncatedNotified = false;
33
+
27
34
  function now() {
28
35
  return new Date().toISOString();
29
36
  }
@@ -222,11 +229,22 @@
222
229
  original.apply(console, args);
223
230
  } catch (error) {}
224
231
 
232
+ // error 级别始终上报(数量少且关键);log/warn 受限流约束。
233
+ if (level !== "error" && _consoleEventCount >= MAX_CONSOLE_EVENTS) {
234
+ if (!_consoleTruncatedNotified) {
235
+ _consoleTruncatedNotified = true;
236
+ sendEvent("console-truncated", { limit: MAX_CONSOLE_EVENTS });
237
+ }
238
+ return;
239
+ }
240
+
225
241
  try {
226
242
  const stringArgs = args.map(function (arg) {
227
243
  return truncate(safeStringify(arg), MAX_TEXT_LENGTH);
228
244
  });
229
245
 
246
+ if (level !== "error") _consoleEventCount++;
247
+
230
248
  sendEvent("console", {
231
249
  level,
232
250
  message: truncate(stringArgs.join(" "), MAX_TEXT_LENGTH),
@@ -237,11 +255,21 @@
237
255
  });
238
256
  }
239
257
 
240
- // 去重:避免 setTimeout 拦截 和 window.error 事件 重复上报同一个错误
258
+ // 去重:避免 setTimeout 拦截 和 window.error 事件 重复上报同一个错误。
259
+ // 两条路径拿到的 message 不一致:setTimeout 拦截器拿到原始 error.message
260
+ // (如 "Cannot read..."),window error 事件则带 "Uncaught TypeError: " 前缀。
261
+ // 归一化时剥掉这个前缀,确保同一个错误只上报一次。
241
262
  const _reportedErrorKeys = new Set();
242
263
 
264
+ function normalizeErrorKey(message) {
265
+ return truncate(
266
+ String(message || "").replace(/^Uncaught\s+(\w*Error)?:?\s*/i, ""),
267
+ 200
268
+ );
269
+ }
270
+
243
271
  function reportRuntimeError(message, filename, lineno, colno, stack) {
244
- const key = truncate(message, 200);
272
+ const key = normalizeErrorKey(message);
245
273
  if (_reportedErrorKeys.has(key)) return;
246
274
  _reportedErrorKeys.add(key);
247
275
  sendEvent("runtime-error", {
@@ -361,6 +389,13 @@
361
389
  setupClickListener();
362
390
  setupKeydownListener();
363
391
 
392
+ // 启动标记:版本号 + 时间。
393
+ // - 独立页面:直接打到控制台,方便人工确认 probe 已就位、是哪个版本。
394
+ // - iframe 模式:作为本轮记录的明确起点事件,便于排查"是否新一轮"。
395
+ const startedAt = now();
396
+ _nativeLog("[PROBE] " + PROBE_VERSION + " 已启动 @ " + startedAt);
397
+ sendEvent("probe-ready", { probeVersion: PROBE_VERSION, startedAt });
398
+
364
399
  if (document.readyState === "loading") {
365
400
  document.addEventListener("DOMContentLoaded", sendPageEnter);
366
401
  } else {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@aiyiran/myclaw",
3
- "version": "1.1.154",
3
+ "version": "1.1.156",
4
4
  "description": "",
5
5
  "main": "index.js",
6
6
  "bin": {