@aiyiran/myclaw 1.1.140 → 1.1.141

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/CLAUDE.md CHANGED
@@ -40,6 +40,31 @@ myclaw/
40
40
 
41
41
  ---
42
42
 
43
+ ## 本地开发流程
44
+
45
+ **关键一步**:进仓库目录,跑一次 `npm link`(一次性)
46
+
47
+ ```bash
48
+ cd /Users/yiran/.openclaw/workspace-yiranclaw/myclaw
49
+ npm link
50
+ ```
51
+
52
+ 这会把 npm 包的符号链接指向你的 git 仓库,之后:
53
+
54
+ - 改了 `assets/`、`server/`、`patches/`、`index.js` 等任何文件
55
+ - 直接跑 `myclaw server` 或 `myclaw patch` —— 会用你改的代码,**无需 publish**
56
+
57
+ 开发流程:
58
+
59
+ ```
60
+ 修改代码 → mc server(重启同步) 或 mc patch(注入 control-ui)
61
+ → 验证改动生效 → 改对了才 publish.sh --git
62
+ ```
63
+
64
+ **不 npm link 的后果**:改了代码要 publish + mc update 才能在 npm 包里生效,非常麻烦。
65
+
66
+ ---
67
+
43
68
  ## 两个环境(本地 vs 远程)
44
69
 
45
70
  很多逻辑都在这里分叉,改代码前先确认你在哪个环境:
@@ -179,6 +179,11 @@ async function closePreviewAndSaveDebugRecord() {
179
179
  const result = await saveDebugRecordToServer(recordToSave);
180
180
 
181
181
  latestDebugRecordResult = result;
182
+ window.myclawLastDebugRecord = {
183
+ ...result,
184
+ workspace: currentWorkspaceContext && currentWorkspaceContext.workspace,
185
+ entryFile: currentWorkspaceContext && currentWorkspaceContext.entryFile,
186
+ };
182
187
 
183
188
  setPreviewState("debug-record-ready");
184
189
 
@@ -313,7 +313,340 @@
313
313
  } catch (e) {}
314
314
  }
315
315
 
316
- // ═══ 3. Prompt 小助手按钮(实现见 myclaw-iteration.js) ═══
316
+ // ═══ 3. Debug 按钮 ═══
317
+
318
+ function createDebugButton() {
319
+ var btn = document.createElement("button");
320
+ btn.id = "myclaw-debug-btn";
321
+ btn.className = "agent-chat__input-btn";
322
+ btn.title = "Debug";
323
+ btn.setAttribute("aria-label", "Debug");
324
+ btn.innerHTML = [
325
+ '<svg xmlns="http://www.w3.org/2000/svg" width="18" height="18"',
326
+ ' viewBox="0 0 24 24" fill="none" stroke="currentColor"',
327
+ ' stroke-width="2" stroke-linecap="round" stroke-linejoin="round">',
328
+ ' <path d="m8 2 1.88 1.88"/>',
329
+ ' <path d="M14.12 3.88 16 2"/>',
330
+ ' <path d="M9 7.13v-1a3.003 3.003 0 1 1 6 0v1"/>',
331
+ ' <path d="M12 20c-3.3 0-6-2.7-6-6v-3a4 4 0 0 1 4-4h4a4 4 0 0 1 4 4v3c0 3.3-2.7 6-6 6"/>',
332
+ ' <path d="M12 20v-9"/>',
333
+ ' <path d="M6.53 9C4.6 9.43 3 11.3 3 13.5"/>',
334
+ ' <path d="M20.97 13.5c0-2.2-1.6-4.07-3.53-4.5"/>',
335
+ ' <path d="M4 19.5c0-2.2 1.8-4 4-4"/>',
336
+ ' <path d="M20 19.5c0-2.2-1.8-4-4-4"/>',
337
+ '</svg>',
338
+ ].join("");
339
+
340
+ btn.addEventListener("click", function (e) {
341
+ e.stopPropagation();
342
+ showDebugModal();
343
+ });
344
+
345
+ return btn;
346
+ }
347
+
348
+ var debugVoiceDestroyers = [];
349
+
350
+ function closeDebugModal() {
351
+ debugVoiceDestroyers.forEach(function (destroy) {
352
+ try { destroy(); } catch (e) {}
353
+ });
354
+ debugVoiceDestroyers = [];
355
+ var modal = document.getElementById("myclaw-debug-modal");
356
+ if (modal) modal.remove();
357
+ }
358
+
359
+ function makeDebugLabel(text) {
360
+ var label = document.createElement("div");
361
+ label.textContent = text;
362
+ label.className = "myclaw-debug-label";
363
+ return label;
364
+ }
365
+
366
+ function makeDebugVoiceInputField(placeholder, maxLength, onChange) {
367
+ var wrap = document.createElement("div");
368
+ wrap.style.cssText = [
369
+ "display:flex", "gap:8px", "align-items:center",
370
+ "width:100%", "box-sizing:border-box",
371
+ ].join(";");
372
+
373
+ var input = document.createElement("textarea");
374
+ input.rows = 3;
375
+ input.maxLength = maxLength || 180;
376
+ input.placeholder = placeholder || "";
377
+ input.spellcheck = false;
378
+ input.style.cssText = [
379
+ "flex:1 1 0%", "min-width:0", "padding:9px 12px",
380
+ "background:#252536",
381
+ "border:1.5px solid #3d3d5c",
382
+ "border-radius:8px",
383
+ "color:#cdd6f4",
384
+ "font-size:13px",
385
+ "line-height:1.6",
386
+ "font-family:-apple-system,sans-serif",
387
+ "outline:none",
388
+ "resize:vertical",
389
+ "transition:border-color 0.15s, border-left-width 0.1s",
390
+ ].join(";");
391
+
392
+ var localVoice = null;
393
+ var localRecording = false;
394
+ var localStopping = false;
395
+
396
+ input.onfocus = function () { if (!localRecording) input.style.borderColor = "#6366f1"; };
397
+ input.onblur = function () { if (!localRecording) input.style.borderColor = "#3d3d5c"; };
398
+ input.oninput = function () { if (onChange) onChange(input.value); };
399
+
400
+ var micBtn = document.createElement("button");
401
+ micBtn.type = "button";
402
+ micBtn.title = "语音输入";
403
+ micBtn.style.cssText = [
404
+ "width:34px", "height:34px", "flex-shrink:0",
405
+ "border-radius:50%",
406
+ "border:1.5px solid #3d3d5c",
407
+ "background:#252536",
408
+ "color:#888",
409
+ "cursor:pointer",
410
+ "display:flex", "align-items:center", "justify-content:center",
411
+ "transition:all 0.15s",
412
+ "position:relative",
413
+ ].join(";");
414
+ micBtn.innerHTML = [
415
+ '<svg width="14" height="14" viewBox="0 0 24 24" fill="none"',
416
+ ' stroke="currentColor" stroke-width="2"',
417
+ ' stroke-linecap="round" stroke-linejoin="round">',
418
+ ' <path d="M12 2a3 3 0 0 0-3 3v7a3 3 0 0 0 6 0V5a3 3 0 0 0-3-3Z"/>',
419
+ ' <path d="M19 10v2a7 7 0 0 1-14 0v-2"/>',
420
+ ' <line x1="12" x2="12" y1="19" y2="22"/>',
421
+ '</svg>',
422
+ ].join("");
423
+
424
+ function setMicUI(isRecording) {
425
+ if (isRecording) {
426
+ micBtn.style.background = "#ff4444";
427
+ micBtn.style.borderColor = "#ff4444";
428
+ micBtn.style.color = "#fff";
429
+ micBtn.title = "停止录音";
430
+ input.style.borderColor = "#ff4444";
431
+ input.style.borderLeftWidth = "3px";
432
+ } else {
433
+ micBtn.style.background = "#252536";
434
+ micBtn.style.borderColor = "#3d3d5c";
435
+ micBtn.style.color = "#888";
436
+ micBtn.title = "语音输入";
437
+ input.style.borderColor = "#3d3d5c";
438
+ input.style.borderLeftWidth = "1.5px";
439
+ }
440
+ }
441
+
442
+ function initLocalVoice() {
443
+ if (typeof window.VoiceInput === "undefined") return false;
444
+ localVoice = new window.VoiceInput({
445
+ onResult: function (text) {
446
+ if (!localRecording && !localStopping) return;
447
+ input.value = String(text || "").slice(0, input.maxLength);
448
+ if (onChange) onChange(input.value);
449
+ },
450
+ onStatusChange: function (oldStatus, newStatus) {
451
+ if (newStatus === "idle" && localRecording && !localStopping) {
452
+ setTimeout(function () {
453
+ if (localRecording && localVoice) localVoice.start();
454
+ }, 300);
455
+ }
456
+ },
457
+ onError: function (err) {
458
+ console.error("[myclaw-debug] voice error:", err);
459
+ localRecording = false;
460
+ localStopping = false;
461
+ setMicUI(false);
462
+ },
463
+ });
464
+ return true;
465
+ }
466
+
467
+ function startLocalVoice() {
468
+ if (!localVoice && !initLocalVoice()) {
469
+ console.warn("[myclaw-debug] VoiceInput SDK 未加载");
470
+ return;
471
+ }
472
+ localRecording = true;
473
+ setMicUI(true);
474
+ localVoice.start();
475
+ }
476
+
477
+ function stopLocalVoice() {
478
+ if (!localRecording) return;
479
+ localStopping = true;
480
+ localRecording = false;
481
+ setMicUI(false);
482
+ setTimeout(function () {
483
+ localStopping = false;
484
+ if (localVoice) localVoice.stop();
485
+ }, 1500);
486
+ }
487
+
488
+ micBtn.onclick = function () {
489
+ if (localRecording) {
490
+ stopLocalVoice();
491
+ } else {
492
+ startLocalVoice();
493
+ }
494
+ };
495
+
496
+ wrap.appendChild(input);
497
+ wrap.appendChild(micBtn);
498
+
499
+ return {
500
+ el: wrap,
501
+ getValue: function () { return input.value; },
502
+ focus: function () { input.focus(); },
503
+ destroy: function () { if (localRecording) stopLocalVoice(); },
504
+ };
505
+ }
506
+
507
+ function showDebugModal() {
508
+ if (document.getElementById("myclaw-debug-modal")) return;
509
+
510
+ var state = { description: "" };
511
+
512
+ function buildDebugPrompt() {
513
+ var rec = window.myclawLastDebugRecord;
514
+ var lines = [];
515
+
516
+ if (rec) {
517
+ if (rec.debugRecordPath) {
518
+ lines.push("调试记录文件:" + rec.debugRecordPath);
519
+ } else if (rec.debugRecordUrl) {
520
+ lines.push("调试记录:" + rec.debugRecordUrl);
521
+ } else if (rec.workspace) {
522
+ lines.push("调试记录:" + rec.workspace + "/debug/debug-record.html");
523
+ }
524
+ if (rec.entryFile) {
525
+ lines.push("正在调试的文件:" + rec.entryFile);
526
+ }
527
+ lines.push("");
528
+ }
529
+
530
+ lines.push("学生描述:" + (state.description || "(未填写)"));
531
+ lines.push("");
532
+ lines.push("请从学生描述里提取:我做了什么、发生了什么、我的预期是什么。");
533
+ lines.push("然后先阅读调试记录,再结合源码定位问题,只做最小修复。修复后说明原因和改动位置。");
534
+
535
+ return lines.join("\n");
536
+ }
537
+
538
+ var overlay = document.createElement("div");
539
+ overlay.id = "myclaw-debug-modal";
540
+
541
+ var box = document.createElement("div");
542
+ box.className = "myclaw-debug-box";
543
+
544
+ var header = document.createElement("div");
545
+ header.className = "myclaw-debug-header";
546
+ header.innerHTML = "<span>🐛 告诉 AI,发生了什么?</span>";
547
+ var closeX = document.createElement("span");
548
+ closeX.textContent = "✕";
549
+ closeX.className = "myclaw-debug-close";
550
+ closeX.onclick = function () { closeDebugModal(); };
551
+ header.appendChild(closeX);
552
+
553
+ var scroll = document.createElement("div");
554
+ scroll.className = "myclaw-debug-scroll";
555
+
556
+ var guide = document.createElement("div");
557
+ guide.className = "myclaw-debug-guide";
558
+ ["我做了什么", "发生了什么", "我的预期是什么"].forEach(function (text) {
559
+ var item = document.createElement("span");
560
+ item.textContent = text;
561
+ guide.appendChild(item);
562
+ });
563
+
564
+ var inputSection = document.createElement("div");
565
+ inputSection.appendChild(makeDebugLabel("用一句话说清楚"));
566
+ var descriptionField = makeDebugVoiceInputField("比如:我点了开始按钮,页面没有反应,我希望角色开始移动", 180, function (val) {
567
+ state.description = val;
568
+ refresh();
569
+ });
570
+ inputSection.appendChild(descriptionField.el);
571
+ debugVoiceDestroyers.push(descriptionField.destroy);
572
+
573
+ var previewWrap = document.createElement("div");
574
+ previewWrap.className = "myclaw-debug-preview";
575
+ var previewLabel = document.createElement("div");
576
+ previewLabel.className = "myclaw-debug-preview-label";
577
+ previewLabel.textContent = "将要发给 AI 的内容:";
578
+ var previewEl = document.createElement("div");
579
+ previewEl.className = "myclaw-debug-preview-text";
580
+ previewWrap.appendChild(previewLabel);
581
+ previewWrap.appendChild(previewEl);
582
+
583
+ scroll.appendChild(guide);
584
+ scroll.appendChild(inputSection);
585
+ scroll.appendChild(previewWrap);
586
+
587
+ var footer = document.createElement("div");
588
+ footer.className = "myclaw-debug-footer";
589
+
590
+ var cancelBtn = document.createElement("button");
591
+ cancelBtn.className = "myclaw-debug-cancel";
592
+ cancelBtn.textContent = "取消";
593
+ cancelBtn.addEventListener("click", function () {
594
+ closeDebugModal();
595
+ });
596
+
597
+ var insertBtn = document.createElement("button");
598
+ insertBtn.className = "myclaw-debug-insert";
599
+ insertBtn.textContent = "⬇ 先放到聊天框";
600
+ insertBtn.addEventListener("click", function () {
601
+ if (!hasDebugInput()) return;
602
+ setTextareaValue(buildDebugPrompt());
603
+ closeDebugModal();
604
+ });
605
+
606
+ var submitBtn = document.createElement("button");
607
+ submitBtn.className = "myclaw-debug-submit";
608
+ submitBtn.textContent = "➤ 直接发给 AI";
609
+ submitBtn.addEventListener("click", function () {
610
+ if (!hasDebugInput()) return;
611
+ setTextareaValue(buildDebugPrompt());
612
+ closeDebugModal();
613
+ trySend();
614
+ });
615
+
616
+ function hasDebugInput() {
617
+ return !!state.description.trim();
618
+ }
619
+
620
+ function refresh() {
621
+ var ready = hasDebugInput();
622
+ previewEl.textContent = ready ? buildDebugPrompt() : "先写一项,这里会自动整理成要发给 AI 的内容。";
623
+ previewEl.style.color = ready ? "#cdd6f4" : "#555";
624
+ submitBtn.disabled = !ready;
625
+ insertBtn.disabled = !ready;
626
+ submitBtn.style.opacity = ready ? "1" : "0.4";
627
+ insertBtn.style.opacity = ready ? "1" : "0.4";
628
+ submitBtn.style.cursor = ready ? "pointer" : "not-allowed";
629
+ insertBtn.style.cursor = ready ? "pointer" : "not-allowed";
630
+ }
631
+
632
+ footer.appendChild(cancelBtn);
633
+ footer.appendChild(insertBtn);
634
+ footer.appendChild(submitBtn);
635
+ box.appendChild(header);
636
+ box.appendChild(scroll);
637
+ box.appendChild(footer);
638
+ overlay.appendChild(box);
639
+ document.body.appendChild(overlay);
640
+
641
+ overlay.addEventListener("click", function (e) {
642
+ if (e.target === overlay) closeDebugModal();
643
+ });
644
+
645
+ refresh();
646
+ descriptionField.focus();
647
+ }
648
+
649
+ // ═══ 3.1 Prompt 小助手按钮(实现见 myclaw-iteration.js) ═══
317
650
 
318
651
  function createPromptButton() {
319
652
  return window.myclawIteration.createButton();
@@ -514,26 +847,31 @@ btn.addEventListener("click", function () {
514
847
 
515
848
  function injectButton() {
516
849
  var toolbar = document.querySelector(".agent-chat__toolbar-left");
517
- if (!toolbar || document.querySelector("#myclaw-voice-btn")) return;
850
+ if (!toolbar) return;
851
+ if (
852
+ toolbar.querySelector("#myclaw-debug-btn") &&
853
+ toolbar.querySelector("#myclaw-prompt-btn") &&
854
+ toolbar.querySelector("#myclaw-voice-btn")
855
+ ) {
856
+ injected = true;
857
+ return;
858
+ }
518
859
 
519
860
  if (window.myclawIteration) {
520
861
  window.myclawIteration.init({ trySend: trySend });
521
862
  }
522
863
 
523
- var voiceBtn = createVoiceButton();
524
- var promptBtn = createPromptButton();
864
+ var debugBtn = document.querySelector("#myclaw-debug-btn") || createDebugButton();
865
+ var promptBtn = document.querySelector("#myclaw-prompt-btn") || createPromptButton();
866
+ var voiceBtn = document.querySelector("#myclaw-voice-btn") || createVoiceButton();
525
867
 
526
- // 插入到工具栏最前面:prompt 按钮在最左,语音按钮紧跟其右
527
- if (toolbar.firstChild) {
528
- toolbar.insertBefore(voiceBtn, toolbar.firstChild);
529
- toolbar.insertBefore(promptBtn, voiceBtn);
530
- } else {
531
- toolbar.appendChild(promptBtn);
532
- toolbar.appendChild(voiceBtn);
533
- }
868
+ // 插入到工具栏最前面:debug 按钮在最左,prompt 和语音按钮依次靠右
869
+ toolbar.insertBefore(voiceBtn, toolbar.firstChild);
870
+ toolbar.insertBefore(promptBtn, voiceBtn);
871
+ toolbar.insertBefore(debugBtn, promptBtn);
534
872
 
535
873
  injected = true;
536
- console.log("[myclaw-inject] \u2705 \u8bed\u97f3\u6309\u94ae + prompt\u6309\u94ae\u5df2\u6ce8\u5165");
874
+ console.log("[myclaw-inject] \u2705 debug\u6309\u94ae + \u8bed\u97f3\u6309\u94ae + prompt\u6309\u94ae\u5df2\u6ce8\u5165");
537
875
  }
538
876
 
539
877
  // ═══ 注入录音态样式 ═══
@@ -602,6 +940,45 @@ btn.addEventListener("click", function () {
602
940
  " from { opacity: 0; }",
603
941
  " to { opacity: 1; }",
604
942
  "}",
943
+ /* Debug 提交弹框 */
944
+ "#myclaw-debug-modal {",
945
+ " position: fixed; inset: 0; z-index: 99999;",
946
+ " background: rgba(0,0,0,0.45);",
947
+ " display: flex; align-items: center; justify-content: center;",
948
+ " animation: myclaw-fade-in 0.15s ease;",
949
+ "}",
950
+ ".myclaw-debug-box {",
951
+ " background: #fff; border-radius: 16px;",
952
+ " padding: 24px 28px 20px; width: 420px; max-width: 92vw;",
953
+ " box-shadow: 0 8px 32px rgba(0,0,0,0.18);",
954
+ " display: flex; flex-direction: column; gap: 12px;",
955
+ "}",
956
+ ".myclaw-debug-title {",
957
+ " font-size: 18px; font-weight: 700; color: #222;",
958
+ "}",
959
+ ".myclaw-debug-hint {",
960
+ " font-size: 13px; color: #888;",
961
+ "}",
962
+ ".myclaw-debug-textarea {",
963
+ " width: 100%; box-sizing: border-box;",
964
+ " border: 1.5px solid #e0e0e0; border-radius: 10px;",
965
+ " padding: 12px; font-size: 14px; line-height: 1.7;",
966
+ " resize: vertical; outline: none; font-family: inherit; color: #333;",
967
+ "}",
968
+ ".myclaw-debug-textarea:focus { border-color: #4f6ef7; }",
969
+ ".myclaw-debug-footer {",
970
+ " display: flex; justify-content: flex-end; gap: 10px;",
971
+ "}",
972
+ ".myclaw-debug-cancel {",
973
+ " padding: 8px 18px; border-radius: 8px; border: 1.5px solid #e0e0e0;",
974
+ " background: #fff; color: #666; font-size: 14px; cursor: pointer;",
975
+ "}",
976
+ ".myclaw-debug-submit {",
977
+ " padding: 8px 20px; border-radius: 8px; border: none;",
978
+ " background: #4f6ef7; color: #fff; font-size: 14px;",
979
+ " font-weight: 600; cursor: pointer;",
980
+ "}",
981
+ ".myclaw-debug-submit:hover { background: #3a58d6; }",
605
982
  ].join("\n");
606
983
  document.head.appendChild(style);
607
984
  }
@@ -1795,7 +2172,11 @@ btn.addEventListener("click", function () {
1795
2172
 
1796
2173
  // 持续监听 DOM 变化,确保按钮始终在
1797
2174
  new MutationObserver(function () {
1798
- if (!document.querySelector("#myclaw-voice-btn")) {
2175
+ if (
2176
+ !document.querySelector("#myclaw-debug-btn") ||
2177
+ !document.querySelector("#myclaw-prompt-btn") ||
2178
+ !document.querySelector("#myclaw-voice-btn")
2179
+ ) {
1799
2180
  injected = false;
1800
2181
  }
1801
2182
  if (!injected) {
package/index.js CHANGED
@@ -1986,16 +1986,21 @@ function runSync(workspaceName) {
1986
1986
 
1987
1987
  // 用户目录下的服务目录
1988
1988
  const targetDir = path.join(os.homedir(), '.openclaw', 'myclaw', 'server');
1989
+ const targetAssetsDir = path.join(os.homedir(), '.openclaw', 'myclaw', 'assets');
1989
1990
  const targetPyPath = path.join(targetDir, 'sync_workspace.py');
1990
1991
  const targetConfigPath = path.join(targetDir, 'config.json');
1991
1992
  const sourcePyPath = path.join(__dirname, 'server', 'sync_workspace.py');
1992
1993
  const sourceSchemaPath = path.join(__dirname, 'server', 'artifacts_schema.py');
1993
1994
  const sourceDebugRecordPath = path.join(__dirname, 'server', 'debug_record.py');
1995
+ const sourceProbeJsPath = path.join(__dirname, 'assets', 'debug-probe.js');
1994
1996
 
1995
1997
  // 确保目标目录存在
1996
1998
  if (!fs.existsSync(targetDir)) {
1997
1999
  fs.mkdirSync(targetDir, { recursive: true });
1998
2000
  }
2001
+ if (!fs.existsSync(targetAssetsDir)) {
2002
+ fs.mkdirSync(targetAssetsDir, { recursive: true });
2003
+ }
1999
2004
 
2000
2005
  // 同步 py 文件
2001
2006
  fs.copyFileSync(sourcePyPath, targetPyPath);
@@ -2003,6 +2008,10 @@ function runSync(workspaceName) {
2003
2008
  if (fs.existsSync(sourceDebugRecordPath)) {
2004
2009
  fs.copyFileSync(sourceDebugRecordPath, path.join(targetDir, 'debug_record.py'));
2005
2010
  }
2011
+ // probe 源码:sync_workspace.py 注入 HTML 时需要读取它
2012
+ if (fs.existsSync(sourceProbeJsPath)) {
2013
+ fs.copyFileSync(sourceProbeJsPath, path.join(targetAssetsDir, 'debug-probe.js'));
2014
+ }
2006
2015
 
2007
2016
  // 读取配置(如果不存在则创建)
2008
2017
  let config;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@aiyiran/myclaw",
3
- "version": "1.1.140",
3
+ "version": "1.1.141",
4
4
  "description": "",
5
5
  "main": "index.js",
6
6
  "bin": {