@aiyiran/myclaw 1.1.138 → 1.1.140

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,306 @@
1
+ /**
2
+ * OpenClaw Debug Probe V1
3
+ *
4
+ * 职责:
5
+ * 1. 记录网页运行现场
6
+ * 2. 通过 postMessage 发给父页面
7
+ *
8
+ * 不负责:
9
+ * 1. traceId
10
+ * 2. workspaceId
11
+ * 3. 上传服务端
12
+ * 4. 判断 bug 原因
13
+ */
14
+
15
+ (function () {
16
+ if (window.__OPENCLAW_DEBUG_PROBE_INSTALLED__) {
17
+ return;
18
+ }
19
+
20
+ window.__OPENCLAW_DEBUG_PROBE_INSTALLED__ = true;
21
+
22
+ const PROBE_VERSION = "probe_001";
23
+ const MAX_TEXT_LENGTH = 1000;
24
+ const MAX_STACK_LENGTH = 3000;
25
+ const MAX_ELEMENT_TEXT_LENGTH = 80;
26
+
27
+ function now() {
28
+ return new Date().toISOString();
29
+ }
30
+
31
+ function truncate(text, maxLength) {
32
+ if (text == null) return "";
33
+ const str = String(text);
34
+ if (str.length <= maxLength) return str;
35
+ return str.slice(0, maxLength) + "...[truncated]";
36
+ }
37
+
38
+ function getPageInfo() {
39
+ return {
40
+ pageUrl: location.href,
41
+ pagePath: location.pathname,
42
+ pageTitle: document.title || ""
43
+ };
44
+ }
45
+
46
+ function safeStringify(value) {
47
+ try {
48
+ if (typeof value === "string") {
49
+ return value;
50
+ }
51
+
52
+ if (value instanceof Error) {
53
+ return value.stack || value.message || String(value);
54
+ }
55
+
56
+ if (typeof value === "function") {
57
+ return "[Function " + (value.name || "anonymous") + "]";
58
+ }
59
+
60
+ if (value instanceof Element) {
61
+ return getElementSummary(value);
62
+ }
63
+
64
+ return JSON.stringify(value);
65
+ } catch (error) {
66
+ try {
67
+ return String(value);
68
+ } catch (e) {
69
+ return "[Unserializable Value]";
70
+ }
71
+ }
72
+ }
73
+
74
+ function getElementText(element) {
75
+ if (!element) return "";
76
+
77
+ const text =
78
+ element.innerText ||
79
+ element.textContent ||
80
+ element.value ||
81
+ element.getAttribute("aria-label") ||
82
+ element.getAttribute("title") ||
83
+ "";
84
+
85
+ return truncate(String(text).replace(/\s+/g, " ").trim(), MAX_ELEMENT_TEXT_LENGTH);
86
+ }
87
+
88
+ function getElementUrl(element) {
89
+ if (!element) return "";
90
+
91
+ return (
92
+ element.currentSrc ||
93
+ element.src ||
94
+ element.href ||
95
+ element.getAttribute("src") ||
96
+ element.getAttribute("href") ||
97
+ ""
98
+ );
99
+ }
100
+
101
+ function getSimpleSelector(element) {
102
+ if (!element || !element.tagName) return "";
103
+
104
+ const tag = element.tagName.toLowerCase();
105
+ const id = element.id ? "#" + element.id : "";
106
+
107
+ let classPart = "";
108
+ try {
109
+ if (element.classList && element.classList.length > 0) {
110
+ classPart =
111
+ "." +
112
+ Array.from(element.classList)
113
+ .slice(0, 3)
114
+ .join(".");
115
+ }
116
+ } catch (error) {}
117
+
118
+ return tag + id + classPart;
119
+ }
120
+
121
+ function getElementSummary(element) {
122
+ if (!element || !element.tagName) {
123
+ return "";
124
+ }
125
+
126
+ return {
127
+ tag: element.tagName,
128
+ id: element.id || "",
129
+ className:
130
+ typeof element.className === "string"
131
+ ? element.className
132
+ : "",
133
+ text: getElementText(element),
134
+ selector: getSimpleSelector(element)
135
+ };
136
+ }
137
+
138
+ function sendEvent(type, payload) {
139
+ const event = {
140
+ type,
141
+ time: now(),
142
+ probeVersion: PROBE_VERSION,
143
+ ...getPageInfo(),
144
+ payload: payload || {}
145
+ };
146
+
147
+ try {
148
+ if (window.parent && window.parent !== window) {
149
+ window.parent.postMessage(
150
+ {
151
+ source: "OPENCLAW_DEBUG_PROBE",
152
+ type: "DEBUG_EVENT",
153
+ event
154
+ },
155
+ "*"
156
+ );
157
+ }
158
+ } catch (error) {
159
+ // 不让 probe 影响学生作品运行
160
+ }
161
+ }
162
+
163
+ function sendPageEnter() {
164
+ sendEvent("page-enter", {
165
+ referrer: document.referrer || ""
166
+ });
167
+ }
168
+
169
+ function setupClickListener() {
170
+ document.addEventListener(
171
+ "click",
172
+ function (event) {
173
+ const target = event.target;
174
+
175
+ sendEvent("click", {
176
+ ...getElementSummary(target),
177
+ x: event.clientX,
178
+ y: event.clientY
179
+ });
180
+ },
181
+ true
182
+ );
183
+ }
184
+
185
+ function setupKeydownListener() {
186
+ document.addEventListener(
187
+ "keydown",
188
+ function (event) {
189
+ sendEvent("keydown", {
190
+ key: event.key,
191
+ code: event.code,
192
+ altKey: event.altKey,
193
+ ctrlKey: event.ctrlKey,
194
+ shiftKey: event.shiftKey,
195
+ metaKey: event.metaKey
196
+ });
197
+ },
198
+ true
199
+ );
200
+ }
201
+
202
+ function setupConsoleListener() {
203
+ ["log", "warn", "error"].forEach(function (level) {
204
+ const original = console[level];
205
+
206
+ if (typeof original !== "function") {
207
+ return;
208
+ }
209
+
210
+ console[level] = function (...args) {
211
+ try {
212
+ original.apply(console, args);
213
+ } catch (error) {}
214
+
215
+ try {
216
+ const stringArgs = args.map(function (arg) {
217
+ return truncate(safeStringify(arg), MAX_TEXT_LENGTH);
218
+ });
219
+
220
+ sendEvent("console", {
221
+ level,
222
+ message: truncate(stringArgs.join(" "), MAX_TEXT_LENGTH),
223
+ args: stringArgs
224
+ });
225
+ } catch (error) {}
226
+ };
227
+ });
228
+ }
229
+
230
+ function setupRuntimeErrorListener() {
231
+ window.addEventListener(
232
+ "error",
233
+ function (event) {
234
+ const target = event.target;
235
+
236
+ // 资源加载失败:img/script/link/audio/video 等
237
+ if (target && target !== window && target.tagName) {
238
+ sendEvent("resource-error", {
239
+ tag: target.tagName,
240
+ id: target.id || "",
241
+ className:
242
+ typeof target.className === "string"
243
+ ? target.className
244
+ : "",
245
+ url: getElementUrl(target),
246
+ selector: getSimpleSelector(target)
247
+ });
248
+
249
+ return;
250
+ }
251
+
252
+ // JS 运行时报错
253
+ sendEvent("runtime-error", {
254
+ message: truncate(event.message || "Unknown runtime error", MAX_TEXT_LENGTH),
255
+ filename: event.filename || "",
256
+ lineno: event.lineno || null,
257
+ colno: event.colno || null,
258
+ stack: truncate(
259
+ event.error && event.error.stack
260
+ ? event.error.stack
261
+ : "",
262
+ MAX_STACK_LENGTH
263
+ )
264
+ });
265
+ },
266
+ true
267
+ );
268
+ }
269
+
270
+ function setupUnhandledRejectionListener() {
271
+ window.addEventListener("unhandledrejection", function (event) {
272
+ const reason = event.reason;
273
+
274
+ sendEvent("unhandledrejection", {
275
+ message: truncate(
276
+ reason && reason.message
277
+ ? reason.message
278
+ : safeStringify(reason),
279
+ MAX_TEXT_LENGTH
280
+ ),
281
+ stack: truncate(
282
+ reason && reason.stack
283
+ ? reason.stack
284
+ : "",
285
+ MAX_STACK_LENGTH
286
+ )
287
+ });
288
+ });
289
+ }
290
+
291
+ function init() {
292
+ setupConsoleListener();
293
+ setupRuntimeErrorListener();
294
+ setupUnhandledRejectionListener();
295
+ setupClickListener();
296
+ setupKeydownListener();
297
+
298
+ if (document.readyState === "loading") {
299
+ document.addEventListener("DOMContentLoaded", sendPageEnter);
300
+ } else {
301
+ sendPageEnter();
302
+ }
303
+ }
304
+
305
+ init();
306
+ })();
@@ -1187,8 +1187,10 @@
1187
1187
  video.focus();
1188
1188
  } else {
1189
1189
  // HTML 走 CDN;其余类型走本地 API
1190
+ var htmlPreviewSrc = '';
1190
1191
  if (isHtml) {
1191
- iframe.src = previewUrl + '?t=' + Date.now();
1192
+ htmlPreviewSrc = previewUrl + '?t=' + Date.now();
1193
+ iframe.src = htmlPreviewSrc;
1192
1194
  } else {
1193
1195
  iframe.src = MYCLAW_API_BASE + '/api/file?path=' + encodeURIComponent(getWorkspaceId() + '/' + asset.path) + '&t=' + Date.now();
1194
1196
  }
@@ -1196,6 +1198,18 @@
1196
1198
  overlay.appendChild(box);
1197
1199
  document.body.appendChild(overlay);
1198
1200
  iframe.focus();
1201
+ // HTML 作品已由服务端注入 debug probe,挂上调试记录收集器开始录制。
1202
+ // 其余类型(图片/视频/PDF…)不录制。collector 不存在时静默跳过。
1203
+ if (isHtml && typeof window.startDebugRecord === 'function') {
1204
+ var _dbgEnv = detectEnvironment();
1205
+ var debugContext = {
1206
+ claw: _dbgEnv.remote ? _dbgEnv.clawName : lastKnownClawName,
1207
+ workspace: getWorkspaceId(),
1208
+ entryFile: asset.path,
1209
+ apiBase: MYCLAW_API_BASE
1210
+ };
1211
+ try { window.startDebugRecord(iframe, htmlPreviewSrc, debugContext); } catch (e) {}
1212
+ }
1199
1213
  }
1200
1214
  return;
1201
1215
  }
@@ -1204,6 +1218,11 @@
1204
1218
  }
1205
1219
 
1206
1220
  function closePreviewModal() {
1221
+ // 通知调试记录收集器收尾并提交。仅在录制中(HTML 预览)时有实际动作,
1222
+ // 其余情况内部自带兜底直接返回;异步保存失败不阻塞弹框关闭。
1223
+ if (typeof window.closePreviewAndSaveDebugRecord === 'function') {
1224
+ try { window.closePreviewAndSaveDebugRecord(); } catch (e) {}
1225
+ }
1207
1226
  var modal = document.querySelector('#myclaw-artifacts-preview');
1208
1227
  if (modal) modal.remove();
1209
1228
  }
@@ -881,39 +881,52 @@ btn.addEventListener("click", function () {
881
881
  });
882
882
  }
883
883
 
884
+ function hideNativeNewSessionBtn(nativeBtn) {
885
+ nativeBtn.style.setProperty("display", "none", "important");
886
+ }
887
+
884
888
  function createNewSessionButton() {
885
- if (document.querySelector("#myclaw-newsession-btn")) return;
889
+ var nativeBtn = findNativeNewSessionBtn();
890
+ if (!nativeBtn) {
891
+ setTimeout(createNewSessionButton, 500);
892
+ return;
893
+ }
886
894
 
887
- var btn = document.createElement("div");
895
+ // 已经替换过,不重复插入
896
+ if (nativeBtn.parentNode && nativeBtn.parentNode.querySelector("#myclaw-newsession-btn")) return;
897
+
898
+ // 隐藏原生按钮
899
+ hideNativeNewSessionBtn(nativeBtn);
900
+
901
+ // 复用原生按钮的 class + innerHTML,外观完全一致
902
+ var btn = document.createElement("button");
888
903
  btn.id = "myclaw-newsession-btn";
889
- btn.style.cssText = [
890
- "position: fixed",
891
- "bottom: 8px",
892
- "right: 335px",
893
- "padding: 3px 10px",
894
- "background: rgba(139, 92, 246, 0.85)",
895
- "color: #fff",
896
- "font-size: 11px",
897
- "font-weight: bold",
898
- "font-family: monospace",
899
- "border-radius: 4px",
900
- "z-index: 99999",
901
- "user-select: none",
902
- "cursor: pointer",
903
- "transition: all 0.2s",
904
- "letter-spacing: 0.5px",
905
- ].join(";");
906
- btn.textContent = "🆕 新会话";
904
+ btn.className = nativeBtn.className;
905
+ btn.innerHTML = nativeBtn.innerHTML;
907
906
  btn.title = "创建新会话(可中文命名)";
908
907
 
909
- btn.onmouseenter = function () { btn.style.background = "rgba(124, 58, 237, 1)"; btn.style.transform = "scale(1.05)"; };
910
- btn.onmouseleave = function () { btn.style.background = "rgba(139, 92, 246, 0.85)"; btn.style.transform = "scale(1)"; };
911
908
  btn.onclick = function (e) {
912
909
  e.stopPropagation();
910
+ e.preventDefault();
913
911
  if (newSessionOpen) { closeNewSessionModal(); } else { openNewSessionModal(); }
914
912
  };
915
913
 
916
- document.body.appendChild(btn);
914
+ nativeBtn.parentNode.insertBefore(btn, nativeBtn);
915
+
916
+ // 监听 shadow DOM 变化:防止 Lit 重渲染后原生按钮恢复显示或我们的按钮被移除
917
+ var shadowRoot = nativeBtn.getRootNode();
918
+ if (shadowRoot && shadowRoot !== document) {
919
+ new MutationObserver(function () {
920
+ var ourBtn = shadowRoot.querySelector("#myclaw-newsession-btn");
921
+ if (!ourBtn) {
922
+ // 被重渲染移除了,重新插入
923
+ createNewSessionButton();
924
+ return;
925
+ }
926
+ var native = shadowRoot.querySelector(".sidebar-new-session:not(#myclaw-newsession-btn)");
927
+ if (native) hideNativeNewSessionBtn(native);
928
+ }).observe(shadowRoot, { childList: true, subtree: true });
929
+ }
917
930
  }
918
931
 
919
932
  function closeNewSessionModal() {
package/index.js CHANGED
@@ -1990,6 +1990,7 @@ function runSync(workspaceName) {
1990
1990
  const targetConfigPath = path.join(targetDir, 'config.json');
1991
1991
  const sourcePyPath = path.join(__dirname, 'server', 'sync_workspace.py');
1992
1992
  const sourceSchemaPath = path.join(__dirname, 'server', 'artifacts_schema.py');
1993
+ const sourceDebugRecordPath = path.join(__dirname, 'server', 'debug_record.py');
1993
1994
 
1994
1995
  // 确保目标目录存在
1995
1996
  if (!fs.existsSync(targetDir)) {
@@ -1999,6 +2000,9 @@ function runSync(workspaceName) {
1999
2000
  // 同步 py 文件
2000
2001
  fs.copyFileSync(sourcePyPath, targetPyPath);
2001
2002
  fs.copyFileSync(sourceSchemaPath, path.join(targetDir, 'artifacts_schema.py'));
2003
+ if (fs.existsSync(sourceDebugRecordPath)) {
2004
+ fs.copyFileSync(sourceDebugRecordPath, path.join(targetDir, 'debug_record.py'));
2005
+ }
2002
2006
 
2003
2007
  // 读取配置(如果不存在则创建)
2004
2008
  let config;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@aiyiran/myclaw",
3
- "version": "1.1.138",
3
+ "version": "1.1.140",
4
4
  "description": "",
5
5
  "main": "index.js",
6
6
  "bin": {
package/patches/patch.js CHANGED
@@ -28,6 +28,7 @@ const VOICE_OUTPUT_SDK_FILENAME = 'voice-output.js';
28
28
  const TTS_INJECT_FILENAME = 'myclaw-tts.js';
29
29
  const ARTIFACTS_INJECT_FILENAME = 'myclaw-artifacts.js';
30
30
  const ITERATION_INJECT_FILENAME = 'myclaw-iteration.js';
31
+ const DEBUG_COLLECTOR_FILENAME = 'debug-collector.js';
31
32
  const BACKUP_SUFFIX = '.myclaw-backup';
32
33
 
33
34
  /**
@@ -151,6 +152,8 @@ function patch() {
151
152
  const artifactsInjectDest = path.join(uiDir, ARTIFACTS_INJECT_FILENAME);
152
153
  const iterationInjectSrc = path.join(__dirname, '..', 'assets', ITERATION_INJECT_FILENAME);
153
154
  const iterationInjectDest = path.join(uiDir, ITERATION_INJECT_FILENAME);
155
+ const debugCollectorSrc = path.join(__dirname, '..', 'assets', DEBUG_COLLECTOR_FILENAME);
156
+ const debugCollectorDest = path.join(uiDir, DEBUG_COLLECTOR_FILENAME);
154
157
  const version = getMyclawVersion();
155
158
 
156
159
  console.log('[myclaw-patch] → control-ui: ' + uiDir);
@@ -218,6 +221,14 @@ function patch() {
218
221
  console.error('[myclaw-patch] ⚠ Iteration 注入脚本复制失败 (非致命): ' + err.message);
219
222
  }
220
223
 
224
+ // 5.7 复制 Debug Collector 注入脚本(父页面侧,接收 iframe 内 probe 的事件)
225
+ try {
226
+ fs.copyFileSync(debugCollectorSrc, debugCollectorDest);
227
+ console.log('[myclaw-patch] ✅ Debug Collector 注入脚本已复制');
228
+ } catch (err) {
229
+ console.error('[myclaw-patch] ⚠ Debug Collector 复制失败 (非致命): ' + err.message);
230
+ }
231
+
221
232
  // 6. Patch index.html(幂等)
222
233
  try {
223
234
  let html = fs.readFileSync(indexPath, 'utf8');
@@ -235,6 +246,7 @@ function patch() {
235
246
  '<script src="./' + VOICE_OUTPUT_SDK_FILENAME + '"></script>',
236
247
  '<script src="./' + VOICE_SDK_FILENAME + '"></script>',
237
248
  '<script src="./' + TTS_INJECT_FILENAME + '"></script>',
249
+ '<script src="./' + DEBUG_COLLECTOR_FILENAME + '"></script>',
238
250
  '<script src="./' + ARTIFACTS_INJECT_FILENAME + '"></script>',
239
251
  '<script src="./' + ITERATION_INJECT_FILENAME + '"></script>',
240
252
  '<script src="./' + INJECT_FILENAME + '"></script>',