@aiyiran/myclaw 1.0.242 → 1.0.244

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.
@@ -30,6 +30,10 @@
30
30
  var panelVisible = false;
31
31
  var cachedData = null;
32
32
  var pollTimer = null;
33
+ var cachedConfig = null; // { claw, base_url }
34
+ var envInfo = null; // { remote: bool, clawName: string|null }
35
+ var MYCLAW_API_PORT = 18800;
36
+ var MYCLAW_API_BASE = 'http://127.0.0.1:' + MYCLAW_API_PORT;
33
37
 
34
38
  // ═══ 工具:从 URL 解析 agent 名称 ═══
35
39
  function getAgentName() {
@@ -47,18 +51,82 @@
47
51
  return '';
48
52
  }
49
53
 
50
- // ═══ 构建预览 URL(CDN) ═══
51
- function buildPreviewUrl(data, assetPath) {
52
- var claw = window.location.hostname.split('.')[0];
53
- if (!claw.includes('claw')) {
54
- claw = localStorage.getItem(CLAW_STORAGE_KEY);
54
+ // ═══ 环境检测 ═══
55
+ // 判断当前是远程服务器还是本地环境
56
+ // 远程:https://claw.yiranlaoshi.com → { remote: true, clawName: 'clawdev' }
57
+ // 远程:https://claw3.kekouen.cn → { remote: true, clawName: 'claw3' }
58
+ // 本地:http://127.0.0.1:18789 → { remote: false, clawName: null }
59
+ function detectEnvironment() {
60
+ var hostname = window.location.hostname;
61
+
62
+ // claw.yiranlaoshi.com → 特殊映射:clawdev
63
+ if (hostname === 'claw.yiranlaoshi.com') {
64
+ return { remote: true, clawName: 'clawdev' };
65
+ }
66
+
67
+ // *.kekouen.cn → 子域名即 clawName
68
+ if (hostname.endsWith('.kekouen.cn')) {
69
+ var clawName = hostname.split('.')[0];
70
+ return { remote: true, clawName: clawName };
55
71
  }
56
- if (!claw) {
57
- console.error('[myclaw-artifacts] ❌ 未配置 claw 名称,无法构建预览链接');
72
+
73
+ // 本地或无法判定
74
+ return { remote: false, clawName: null };
75
+ }
76
+
77
+ // ═══ 初始化配置 ═══
78
+ // 远程环境直接用 hostname 中的 clawName
79
+ // 本地环境从 sync_workspace.py 的 HTTP API 获取
80
+ function initConfig() {
81
+ envInfo = detectEnvironment();
82
+
83
+ // 解析 agentName 和 workspaceName(与环境无关,纯粹从 URL 参数获取)
84
+ var agent = getAgentName() || 'main';
85
+ var workspace = agent === 'main' ? 'workspace' : 'workspace-' + agent;
86
+
87
+ if (envInfo.remote) {
88
+ // 远程:直接拿到 clawName,不需要请求
89
+ cachedConfig = {
90
+ claw: envInfo.clawName,
91
+ base_url: 'https://cdn.yiranlaoshi.com/' + envInfo.clawName,
92
+ agentName: agent,
93
+ workspaceName: workspace,
94
+ };
95
+ console.log('[myclaw-artifacts] ✅ 远程环境:', envInfo.clawName, '| agent:', agent, '| workspace:', workspace);
96
+ return Promise.resolve(cachedConfig);
97
+ }
98
+
99
+ // 本地:从 API 获取 claw 配置
100
+ return fetch(MYCLAW_API_BASE + '/api/config')
101
+ .then(function (res) {
102
+ if (!res.ok) throw new Error('HTTP ' + res.status);
103
+ return res.json();
104
+ })
105
+ .then(function (data) {
106
+ data.agentName = agent;
107
+ data.workspaceName = workspace;
108
+ cachedConfig = data;
109
+ console.log('[myclaw-artifacts] ✅ 本地环境 | claw:', data.claw, '| agent:', agent, '| workspace:', workspace);
110
+ return data;
111
+ })
112
+ .catch(function (err) {
113
+ // API 不可用时仍保留 agent/workspace 信息
114
+ cachedConfig = { claw: null, base_url: null, agentName: agent, workspaceName: workspace };
115
+ console.warn('[myclaw-artifacts] ⚠ 本地 API 不可用:', err.message);
116
+ return cachedConfig;
117
+ });
118
+ }
119
+
120
+ // ═══ 构建预览 URL ═══
121
+ // 统一走 CDN,clawName 和 workspaceName 由 cachedConfig 提供
122
+ function buildPreviewUrl(data, assetPath) {
123
+ var clawName = cachedConfig ? cachedConfig.claw : null;
124
+ var wsPrefix = cachedConfig ? cachedConfig.workspaceName : null;
125
+ if (!clawName || !wsPrefix) {
126
+ console.error('[myclaw-artifacts] ❌ 配置未就绪,无法构建预览链接');
58
127
  return null;
59
128
  }
60
- var wsName = data.workspace_id || 'main';
61
- return 'https://cdn.yiranlaoshi.com/' + claw + '/' + wsName + '/' + assetPath;
129
+ return 'https://cdn.yiranlaoshi.com/' + clawName + '/' + wsPrefix + '/' + assetPath;
62
130
  }
63
131
 
64
132
  // ═══ 创建按钮 ═══
@@ -197,20 +265,45 @@
197
265
  }
198
266
 
199
267
  // ═══ 请求数据 ═══
200
- function getArtifactsUrl() {
201
- var agentName = getAgentName() || 'main';
202
- var wsPrefix = agentName === 'main' ? 'workspace' : 'workspace-' + agentName;
203
- return window.location.origin + '/cmd/api/preview?path=' + wsPrefix + '/.myclaw/__MY_ARTIFACTS__.json';
268
+ function getWorkspaceId() {
269
+ return cachedConfig ? cachedConfig.workspaceName : 'workspace';
204
270
  }
205
271
 
206
- function fetchArtifacts(contentEl) {
207
- var url = getArtifactsUrl();
208
- if (!url) return;
209
- fetch(url)
272
+ function fetchArtifactsFromLocalAPI(wsPrefix) {
273
+ return fetch(MYCLAW_API_BASE + '/api/artifacts?workspace=' + encodeURIComponent(wsPrefix))
210
274
  .then(function (res) {
211
275
  if (!res.ok) throw new Error('HTTP ' + res.status);
212
276
  return res.json();
213
- })
277
+ });
278
+ }
279
+
280
+ function fetchArtifactsFromCDN(wsPrefix) {
281
+ var clawName = cachedConfig ? cachedConfig.claw : null;
282
+ if (!clawName) return Promise.reject(new Error('no claw name'));
283
+ var url = 'https://cdn.yiranlaoshi.com/' + clawName + '/' + wsPrefix + '/.myclaw/__MY_ARTIFACTS__.json?t=' + Date.now();
284
+ return fetch(url).then(function (res) {
285
+ if (!res.ok) throw new Error('HTTP ' + res.status);
286
+ return res.text();
287
+ }).then(function (text) {
288
+ if (text.trim().indexOf('<') === 0) throw new Error('Not JSON');
289
+ return JSON.parse(text);
290
+ });
291
+ }
292
+
293
+ function fetchArtifacts(contentEl) {
294
+ var wsPrefix = getWorkspaceId();
295
+ var fetcher;
296
+
297
+ if (envInfo && envInfo.remote) {
298
+ // 远程环境 → 走 CDN
299
+ fetcher = fetchArtifactsFromCDN(wsPrefix);
300
+ } else {
301
+ // 本地环境 → 优先本地 API,失败降级 CDN
302
+ fetcher = fetchArtifactsFromLocalAPI(wsPrefix)
303
+ .catch(function () { return fetchArtifactsFromCDN(wsPrefix); });
304
+ }
305
+
306
+ fetcher
214
307
  .then(function (data) {
215
308
  cachedData = data;
216
309
  if (!contentEl) return;
@@ -220,10 +313,9 @@
220
313
  }
221
314
  renderArtifactsList(contentEl, data);
222
315
  })
223
- .catch(function (err) {
224
- console.error('[myclaw-artifacts] 加载失败:', err);
316
+ .catch(function () {
225
317
  if (contentEl) {
226
- contentEl.innerHTML = '<div style="text-align:center;padding:32px;color:#ff6b6b;">加载失败,请稍后重试</div>';
318
+ contentEl.innerHTML = '<div style="text-align:center;padding:32px;color:#888;">暂无作品</div>';
227
319
  }
228
320
  });
229
321
  }
@@ -618,15 +710,12 @@
618
710
  titleInput.style.borderColor = '#ff4444';
619
711
  return;
620
712
  }
621
- var agentName = getAgentName() || 'main';
622
- var wsField = agentName === 'main' ? 'workspace' : 'workspace-' + agentName;
623
- var clawVal = window.location.hostname.split('.')[0];
624
713
  var payload = {
625
714
  title: titleVal,
626
- workspace: wsField,
715
+ workspace: cachedConfig ? cachedConfig.workspaceName : 'workspace',
627
716
  cover_path: coverSelect.value || '',
628
717
  entry_path: entrySelect.value || '',
629
- claw: clawVal,
718
+ claw: cachedConfig ? cachedConfig.claw : '',
630
719
  };
631
720
  submitBtn.disabled = true;
632
721
  submitBtn.textContent = '\u53D1\u5E03\u4E2D...';
@@ -834,9 +923,9 @@
834
923
  qrSection.style.cssText = 'display:flex;flex-direction:column;align-items:center;gap:6px;padding-top:4px;';
835
924
 
836
925
  var qrCanvas = document.createElement('canvas');
837
- qrCanvas.style.cssText = 'width:140px;height:140px;border-radius:6px;background:#fff;padding:8px;';
926
+ qrCanvas.style.cssText = 'width:140px;height:140px;border-radius:6px;';
838
927
  try {
839
- generateQR(qrCanvas, data.permanent_url, 140);
928
+ generateQR(qrCanvas, data.permanent_url, 280);
840
929
  } catch (e) {
841
930
  console.warn('[myclaw-artifacts] QR generate error:', e);
842
931
  qrCanvas.style.display = 'none';
@@ -858,11 +947,11 @@
858
947
  footer.style.cssText = 'padding: 0 24px 20px;text-align:center;display:flex;gap:10px;justify-content:center;';
859
948
 
860
949
  var showcaseBtn = document.createElement('a');
861
- var agentName = getAgentName() || 'main';
862
- var wsName = agentName === 'main' ? 'workspace' : 'workspace-' + agentName;
863
- showcaseBtn.href = 'https://www.yiranlaoshi.com/showcase?workspace=' + wsName;
950
+ var cfgAgent = cachedConfig ? cachedConfig.agentName : 'main';
951
+ var cfgWs = cachedConfig ? cachedConfig.workspaceName : 'workspace';
952
+ showcaseBtn.href = 'https://www.yiranlaoshi.com/showcase?workspace=' + cfgWs;
864
953
  showcaseBtn.target = '_blank';
865
- showcaseBtn.textContent = '\uD83D\uDC41 \u67E5\u770B' + agentName + '\u9879\u76EE\u96C6';
954
+ showcaseBtn.textContent = '\uD83D\uDC41 \u67E5\u770B' + cfgAgent + '\u9879\u76EE\u96C6';
866
955
  showcaseBtn.style.cssText = [
867
956
  'padding: 10px 20px',
868
957
  'background: #a78bfa',
@@ -1253,17 +1342,27 @@
1253
1342
  if (!qr) throw new Error("QR code too large");
1254
1343
 
1255
1344
  var moduleCount = qr.getModuleCount();
1256
- var cellSize = size / moduleCount;
1345
+ // quiet zone: QR 规范要求至少 4 模块的留白
1346
+ var quietZone = 4;
1347
+ var totalModules = moduleCount + quietZone * 2;
1348
+ var cellSize = size / totalModules;
1257
1349
  canvas.width = size;
1258
1350
  canvas.height = size;
1259
1351
  var ctx = canvas.getContext('2d');
1352
+ // 白色背景(含 quiet zone)
1260
1353
  ctx.fillStyle = '#ffffff';
1261
1354
  ctx.fillRect(0, 0, size, size);
1262
- ctx.fillStyle = '#1e1e2e';
1355
+ // 纯黑前景,确保扫码对比度
1356
+ ctx.fillStyle = '#000000';
1263
1357
  for (var row = 0; row < moduleCount; row++) {
1264
1358
  for (var col = 0; col < moduleCount; col++) {
1265
1359
  if (qr.isDark(row, col)) {
1266
- ctx.fillRect(Math.floor(col * cellSize), Math.floor(row * cellSize), Math.ceil(cellSize), Math.ceil(cellSize));
1360
+ ctx.fillRect(
1361
+ Math.round((col + quietZone) * cellSize),
1362
+ Math.round((row + quietZone) * cellSize),
1363
+ Math.ceil(cellSize),
1364
+ Math.ceil(cellSize)
1365
+ );
1267
1366
  }
1268
1367
  }
1269
1368
  }
@@ -1300,8 +1399,11 @@
1300
1399
  function init() {
1301
1400
  injectStyles();
1302
1401
  createArtifactsButton();
1303
- startPolling();
1304
- console.log('[myclaw-artifacts] 初始化完成');
1402
+ // 检测环境 → 获取配置 → 启动轮询
1403
+ initConfig().then(function () {
1404
+ startPolling();
1405
+ console.log('[myclaw-artifacts] ✅ 初始化完成 (' + (envInfo.remote ? '远程: ' + envInfo.clawName : '本地') + ')');
1406
+ });
1305
1407
  }
1306
1408
 
1307
1409
  if (document.readyState === 'loading') {
@@ -522,6 +522,51 @@
522
522
  }
523
523
  // ═══ 6. 拦截发送按钮 ═══
524
524
 
525
+ var voiceEnterListening = false;
526
+ var voiceEnterTimeout = null;
527
+
528
+ /**
529
+ * 拦截 Enter 键:语音态下按回车 → 显示"等待中...",2秒后自动发送
530
+ */
531
+ function hookVoiceEnter() {
532
+ if (voiceEnterListening) return;
533
+ voiceEnterListening = true;
534
+
535
+ document.addEventListener("keydown", function (e) {
536
+ // 只拦截 textarea 上的 Enter
537
+ if (e.key !== "Enter") return;
538
+ var ta = e.target.closest ? e.target.closest(".agent-chat__input textarea") : null;
539
+ if (!ta) return;
540
+ if (!recording) return;
541
+
542
+ // 阻止默认行为(不在 textarea 里插入换行)
543
+ e.preventDefault();
544
+ e.stopPropagation();
545
+
546
+ // 1) 显示"等待中..."
547
+ setTextareaValue("\u5F85\u6B3A\u4E2D...");
548
+ // 把光标移到末尾
549
+ try {
550
+ ta.setSelectionRange(ta.value.length, ta.value.length);
551
+ } catch (err) {}
552
+
553
+ // 2) 停止录音
554
+ stopVoice();
555
+
556
+ // 3) 2秒后自动点击发送按钮
557
+ if (voiceEnterTimeout) clearTimeout(voiceEnterTimeout);
558
+ voiceEnterTimeout = setTimeout(function () {
559
+ voiceEnterTimeout = null;
560
+ var sendBtn = document.querySelector("button.chat-send-btn, button[title=\"Send\"]");
561
+ if (sendBtn) {
562
+ sendBtn.click();
563
+ }
564
+ console.log("[myclaw-voice] Enter\u89E6\u53D1\u81EA\u52A8\u53D1\u9001");
565
+ }, 2000);
566
+
567
+ }, true); // 捕获阶段,确保早于原生处理
568
+ }
569
+
525
570
  var sendHooked = false;
526
571
 
527
572
  function hookSendButton() {
@@ -853,7 +898,7 @@
853
898
  { label: "\uD83D\uDCAC \u6DFB\u52A0\u5BF9\u8BDD", desc: "\u6253\u5F00\u5DF2\u6709\u4F19\u4F34\u7684\u5BF9\u8BDD\u7A97\u53E3", hasInput: true, inputTitle: "\u6DFB\u52A0\u5BF9\u8BDD", placeholder: "\u8F93\u5165\u4F19\u4F34\u540D\u79F0\uFF0C\u5982 kakaxi", hint: "\u8F93\u5165\u4F60\u7684\u4F19\u4F34\u7684\u540D\u79F0\uFF08\u82F1\u6587\u5B57\u6BCD\u3001\u6570\u5B57\u3001\u8FDE\u5B57\u7B26\uFF09\uFF0C\u70B9\u51FB\u540E\u4F1A\u6253\u5F00\u5BF9\u8BDD\u7A97\u53E3", cmd: "mc tui {name}", color: "#10b981" },
854
899
  { label: "\uD83D\uDE80 \u5347\u7EA7", desc: "\u5347\u7EA7 myclaw \u5230\u6700\u65B0\u7248\u672C", hasInput: false, cmd: "mc up", color: "#8b5cf6" },
855
900
  { label: "\uD83D\uDD04 \u91CD\u542F", desc: "\u91CD\u542F\u670D\u52A1\uFF0C\u4FEE\u590D\u5927\u591A\u6570\u95EE\u9898", hasInput: false, cmd: "mc restart", color: "#ef4444" },
856
- { label: "\uD83E\uDD1D \u65B0\u4F19\u4F34", desc: "\u521B\u5EFA\u4E00\u4E2A\u65B0\u7684 AI \u4F19\u4F34", hasInput: true, inputTitle: "\u65B0\u5EFA\u4F19\u4F34", placeholder: "\u8F93\u5165\u65B0\u4F19\u4F34\u540D\u79F0\uFF0C\u5982 my-cat", hint: "\u7ED9\u4F60\u7684\u65B0 AI \u4F19\u4F34\u8D77\u4E2A\u540D\u5B57\uFF08\u82F1\u6587\u5B57\u6BCD\u3001\u6570\u5B57\u3001\u8FDE\u5B57\u7B26\uFF09\uFF0C\u70B9\u51FB\u540E\u4F1A\u81EA\u52A8\u521B\u5EFA", cmd: "mc tui {name}", color: "#3b82f6" },
901
+ { label: "\uD83E\uDD1D \u65B0\u4F19\u4F34", desc: "\u521B\u5EFA\u4E00\u4E2A\u65B0\u7684 AI \u4F19\u4F34", hasInput: true, inputTitle: "\u65B0\u5EFA\u4F19\u4F34", placeholder: "\u8F93\u5165\u65B0\u4F19\u4F34\u540D\u79F0\uFF0C\u5982 my-cat", hint: "\u7ED9\u4F60\u7684\u65B0 AI \u4F19\u4F34\u8D77\u4E2A\u540D\u5B57\uFF08\u82F1\u6587\u5B57\u6BCD\u3001\u6570\u5B57\u3001\u8FDE\u5B57\u7B26\uFF09\uFF0C\u70B9\u51FB\u540E\u4F1A\u81EA\u52A8\u521B\u5EFA", cmd: "mc new {name}", color: "#3b82f6" },
857
902
  ];
858
903
 
859
904
  btns.forEach(function (item) {
@@ -908,6 +953,175 @@
908
953
  form.appendChild(row);
909
954
  });
910
955
 
956
+ // ── 删除伙伴按钮 ──
957
+ var delRow = document.createElement("div");
958
+ delRow.style.cssText = [
959
+ "padding:10px 14px",
960
+ "background:#252536",
961
+ "border-radius:6px",
962
+ "cursor:pointer",
963
+ "transition:background 0.15s",
964
+ "display:flex",
965
+ "align-items:center",
966
+ "gap:10px",
967
+ ].join(";");
968
+ delRow.onmouseenter = function () { delRow.style.background = "#2f2f4a"; };
969
+ delRow.onmouseleave = function () { delRow.style.background = "#252536"; };
970
+
971
+ var delBar = document.createElement("div");
972
+ delBar.style.cssText = "width:3px;height:28px;border-radius:2px;background:#ef4444;flex-shrink:0;";
973
+ delRow.appendChild(delBar);
974
+
975
+ var delInfo = document.createElement("div");
976
+ delInfo.style.cssText = "flex:1;display:flex;flex-direction:column;gap:2px;";
977
+ var delName = document.createElement("div");
978
+ delName.textContent = "\uD83D\uDDD1 \u5220\u9664\u4F19\u4F34";
979
+ delName.style.cssText = "font-size:13px;font-weight:bold;color:#ef4444;";
980
+ delInfo.appendChild(delName);
981
+ var delDesc = document.createElement("div");
982
+ delDesc.textContent = "\u5220\u9664\u4E00\u4E2A AI \u4F19\u4F34\uFF0C\u6B64\u64CD\u4F5C\u65E0\u6CD5\u6062\u590D";
983
+ delDesc.style.cssText = "font-size:11px;color:#888;";
984
+ delInfo.appendChild(delDesc);
985
+ delRow.appendChild(delInfo);
986
+
987
+ var delArrow = document.createElement("div");
988
+ delArrow.textContent = "\u25B6";
989
+ delArrow.style.cssText = "color:#555;font-size:10px;";
990
+ delRow.appendChild(delArrow);
991
+
992
+ delRow.onclick = function () {
993
+ showDeleteConfirm();
994
+ };
995
+
996
+ form.appendChild(delRow);
997
+
998
+ // 删除伙伴 - 双重确认弹框
999
+ function showDeleteConfirm() {
1000
+ var mask = document.createElement("div");
1001
+ mask.style.cssText = [
1002
+ "position:fixed",
1003
+ "top:0;left:0;width:100vw;height:100vh",
1004
+ "background:rgba(0,0,0,0.3)",
1005
+ "z-index:999999",
1006
+ "display:flex",
1007
+ "align-items:center",
1008
+ "justify-content:center",
1009
+ "animation:myclaw-fade-in 0.15s ease",
1010
+ ].join(";");
1011
+
1012
+ var box = document.createElement("div");
1013
+ box.style.cssText = [
1014
+ "width:360px",
1015
+ "background:#1e1e2e",
1016
+ "border-radius:8px",
1017
+ "overflow:hidden",
1018
+ "box-shadow:0 8px 32px rgba(0,0,0,0.5)",
1019
+ ].join(";");
1020
+
1021
+ // 标题
1022
+ var h = document.createElement("div");
1023
+ h.style.cssText = "padding:10px 14px;background:#ef4444;color:#fff;font-size:13px;display:flex;justify-content:space-between;align-items:center;";
1024
+ h.innerHTML = '<span>\uD83D\uDDD1 \u5220\u9664\u4F19\u4F34</span>';
1025
+ var x = document.createElement("span");
1026
+ x.textContent = "\u2715";
1027
+ x.style.cssText = "cursor:pointer;padding:2px 6px;border-radius:3px;";
1028
+ x.onclick = function () { mask.remove(); };
1029
+ h.appendChild(x);
1030
+ box.appendChild(h);
1031
+
1032
+ // body
1033
+ var body = document.createElement("div");
1034
+ body.style.cssText = "padding:16px;display:flex;flex-direction:column;gap:12px;";
1035
+
1036
+ var hint1 = document.createElement("div");
1037
+ hint1.textContent = "\u8BF7\u8F93\u5165\u8981\u5220\u9664\u7684\u4F19\u4F34 ID\uFF1A";
1038
+ hint1.style.cssText = "font-size:12px;color:#888;";
1039
+ body.appendChild(hint1);
1040
+
1041
+ var input1 = document.createElement("input");
1042
+ input1.type = "text";
1043
+ input1.placeholder = "\u4F19\u4F34 ID";
1044
+ input1.style.cssText = "padding:8px 10px;background:#252536;border:1px solid #3d3d5c;border-radius:4px;color:#cdd6f4;font-size:13px;font-family:monospace;outline:none;";
1045
+ body.appendChild(input1);
1046
+
1047
+ var warn = document.createElement("div");
1048
+ warn.textContent = "\u26A0 \u6B64\u64CD\u4F5C\u65E0\u6CD5\u6062\u590D\uFF0C\u786E\u8BA4\u540E\u5C06\u6C38\u4E45\u5220\u9664\uFF01";
1049
+ warn.style.cssText = "font-size:11px;color:#ef4444;padding:8px;background:rgba(239,68,68,0.1);border-radius:4px;";
1050
+ body.appendChild(warn);
1051
+
1052
+ var confirmHint = document.createElement("div");
1053
+ confirmHint.textContent = '\u8BF7\u8F93\u5165 "YES" \u786E\u8BA4\u5220\u9664\uFF1A';
1054
+ confirmHint.style.cssText = "font-size:12px;color:#888;";
1055
+ confirmHint.style.display = "none";
1056
+ body.appendChild(confirmHint);
1057
+
1058
+ var input2 = document.createElement("input");
1059
+ input2.type = "text";
1060
+ input2.placeholder = "YES";
1061
+ input2.style.cssText = "padding:8px 10px;background:#252536;border:1px solid #3d3d5c;border-radius:4px;color:#cdd6f4;font-size:13px;font-family:monospace;outline:none;";
1062
+ input2.style.display = "none";
1063
+ body.appendChild(input2);
1064
+
1065
+ var submitBtn = document.createElement("button");
1066
+ submitBtn.textContent = "\u7EE7\u7EED";
1067
+ submitBtn.style.cssText = "padding:8px 16px;background:#ef4444;border:none;border-radius:4px;color:#fff;font-size:12px;font-family:monospace;cursor:pointer;";
1068
+ body.appendChild(submitBtn);
1069
+
1070
+ var cancelBtn = document.createElement("button");
1071
+ cancelBtn.textContent = "\u53D6\u6D88";
1072
+ cancelBtn.style.cssText = "padding:8px 16px;background:#3d3d5c;border:none;border-radius:4px;color:#cdd6f4;font-size:12px;font-family:monospace;cursor:pointer;";
1073
+ body.appendChild(cancelBtn);
1074
+
1075
+ box.appendChild(body);
1076
+ mask.appendChild(box);
1077
+ document.body.appendChild(mask);
1078
+
1079
+ input1.focus();
1080
+
1081
+ var agentId = "";
1082
+ submitBtn.onclick = function () {
1083
+ if (!agentId) {
1084
+ // 第一步:输入 agent ID
1085
+ agentId = input1.value.trim();
1086
+ if (!agentId) {
1087
+ input1.style.borderColor = "#ef4444";
1088
+ input1.focus();
1089
+ return;
1090
+ }
1091
+ // 显示第二步确认
1092
+ input1.style.display = "none";
1093
+ hint1.style.display = "none";
1094
+ confirmHint.style.display = "block";
1095
+ input2.style.display = "block";
1096
+ submitBtn.textContent = "\u786E\u8BA4\u5220\u9664";
1097
+ input2.value = "";
1098
+ input2.focus();
1099
+ } else {
1100
+ // 第二步:确认删除
1101
+ var confirmVal = input2.value.trim();
1102
+ if (confirmVal.toUpperCase() !== "YES") {
1103
+ input2.style.borderColor = "#ef4444";
1104
+ input2.focus();
1105
+ return;
1106
+ }
1107
+ submitBtn.disabled = true;
1108
+ submitBtn.textContent = "\u6267\u884C\u4E2D...";
1109
+ runCommand("openclaw agents delete " + agentId + " --force");
1110
+ setTimeout(function () {
1111
+ mask.remove();
1112
+ }, 1000);
1113
+ }
1114
+ };
1115
+
1116
+ cancelBtn.onclick = function () {
1117
+ mask.remove();
1118
+ };
1119
+
1120
+ input2.onkeydown = function (e) {
1121
+ if (e.key === "Enter") submitBtn.click();
1122
+ };
1123
+ }
1124
+
911
1125
  box.appendChild(header);
912
1126
  box.appendChild(form);
913
1127
  overlay.appendChild(box);
@@ -939,6 +1153,9 @@
939
1153
  // 初始化 VoiceInput SDK
940
1154
  initVoice();
941
1155
 
1156
+ // 拦截语音态 Enter 键
1157
+ hookVoiceEnter();
1158
+
942
1159
  // 持续监听 DOM 变化,确保按钮始终在
943
1160
  new MutationObserver(function () {
944
1161
  if (!document.querySelector("#myclaw-voice-btn")) {
package/index.js CHANGED
@@ -1172,7 +1172,53 @@ function padRight(str, len) {
1172
1172
  // 交互式菜单(上下键选择)
1173
1173
  // ============================================================================
1174
1174
 
1175
+ // ============================================================================
1176
+ // 机器选择配置
1177
+ // ============================================================================
1178
+ const MACHINE_CONFIG = [
1179
+ { name: 'kendy', claw: 'claw1', desc: 'kendy 的机器' },
1180
+ { name: 'Ethan', claw: 'claw2', desc: 'Ethan 的机器' },
1181
+ { name: 'HENRY', claw: 'claw3', desc: 'HENRY 的机器' },
1182
+ { name: 'Mo靖宇', claw: 'claw4', desc: 'Mo靖宇 的机器' },
1183
+ { name: '高兴', claw: 'claw5', desc: '高兴 的机器' },
1184
+ { name: '伊伊', claw: 'claw6', desc: '伊伊 的机器' },
1185
+ { name: '雨熙', claw: 'claw7', desc: '雨熙 的机器' },
1186
+ { name: '绍博', claw: 'claw8', desc: '绍博 的机器' },
1187
+ ];
1188
+
1189
+ function showMachineMenu() {
1190
+ console.log('');
1191
+ console.log(' ' + colors.blue + '机器选择' + colors.nc);
1192
+ console.log('----------------------------------------');
1193
+ console.log('');
1194
+ MACHINE_CONFIG.forEach((m, i) => {
1195
+ console.log(' ' + colors.cyan + (i + 1) + '.' + colors.nc + ' ' + m.name + ' (' + colors.dim + m.claw + colors.nc + ')');
1196
+ console.log(' ' + colors.dim + m.desc + colors.nc);
1197
+ console.log('');
1198
+ });
1199
+ console.log(' ' + colors.cyan + '0.' + colors.nc + ' 返回');
1200
+ console.log('');
1201
+
1202
+ const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
1203
+ rl.question('请选择机器 [0-' + MACHINE_CONFIG.length + ']: ', function (answer) {
1204
+ rl.close();
1205
+ const choice = parseInt(answer.trim());
1206
+ if (choice === 0) return;
1207
+ if (choice >= 1 && choice <= MACHINE_CONFIG.length) {
1208
+ const selected = MACHINE_CONFIG[choice - 1];
1209
+ console.log('');
1210
+ console.log(colors.green + ' → 已选择: ' + selected.name + ' (' + selected.claw + ')' + colors.nc);
1211
+ console.log(' ' + colors.dim + '→ 正在用 Chrome 打开...' + colors.nc);
1212
+ const { execSync } = require('child_process');
1213
+ execSync('open -a "Google Chrome" "https://' + selected.claw + '.kekouen.cn?token=aiyiran"', { stdio: 'ignore' });
1214
+ } else {
1215
+ console.log('[' + colors.red + '错误' + colors.nc + '] 无效选择');
1216
+ }
1217
+ });
1218
+ }
1219
+
1175
1220
  const MENU_ITEMS = [
1221
+ { key: 'machine', label: '💻机器', cmd: 'mc machine', desc: '选择不同的机器', action: showMachineMenu },
1176
1222
  { key: 'start', label: '🦞启动🦞', cmd: 'mc start', desc: '把你的 AI 助手叫醒,让它开始工作', action: () => { const start = require('./start'); start.run(); } },
1177
1223
  { key: 'restart', label: '重启', cmd: 'mc restart', desc: 'AI 助手卡住了?让它重新启动一下', action: runRestart },
1178
1224
  { key: 'new', label: '😊新伙伴', cmd: 'mc new', desc: '创建一个新的 AI 助手,给它取个名字', action: runNew },
@@ -1484,7 +1530,7 @@ function runSync(workspaceName) {
1484
1530
  }
1485
1531
 
1486
1532
  async function runServer(name) {
1487
- const { spawn } = require('child_process');
1533
+ const { spawn, execSync: execSyncLocal } = require('child_process');
1488
1534
  const fs = require('fs');
1489
1535
 
1490
1536
  // 用户目录下的服务目录
@@ -1492,6 +1538,19 @@ async function runServer(name) {
1492
1538
  const targetPyPath = path.join(targetDir, 'sync_workspace.py');
1493
1539
  const targetConfigPath = path.join(targetDir, 'config.json');
1494
1540
  const sourcePyPath = path.join(__dirname, 'server', 'sync_workspace.py');
1541
+ const pidFile = path.join(os.homedir(), '.openclaw', '.myclaw-sync.pid');
1542
+
1543
+ // 0. 杀死旧的 sync_workspace 进程(通过 PID 文件)
1544
+ if (fs.existsSync(pidFile)) {
1545
+ try {
1546
+ const oldPid = parseInt(fs.readFileSync(pidFile, 'utf-8').trim(), 10);
1547
+ if (oldPid && !isNaN(oldPid)) {
1548
+ try { process.kill(oldPid, 'SIGTERM'); } catch (e) { /* 不存在则忽略 */ }
1549
+ console.log('[Server] 已终止旧进程 PID=' + oldPid);
1550
+ }
1551
+ } catch (e) { /* 读取失败忽略 */ }
1552
+ try { fs.unlinkSync(pidFile); } catch (e) { }
1553
+ }
1495
1554
 
1496
1555
  // 1. 创建目标目录
1497
1556
  if (!fs.existsSync(targetDir)) {
@@ -1499,7 +1558,7 @@ async function runServer(name) {
1499
1558
  console.log('[Server] 创建目录: ' + targetDir);
1500
1559
  }
1501
1560
 
1502
- // 2. 覆盖 py 文件
1561
+ // 2. 覆盖 py 文件(确保最新)
1503
1562
  fs.copyFileSync(sourcePyPath, targetPyPath);
1504
1563
  console.log('[Server] 同步脚本: ' + targetPyPath);
1505
1564
 
@@ -1550,14 +1609,25 @@ async function runServer(name) {
1550
1609
  console.error('[' + colors.red + '错误' + colors.nc + '] 启动失败: ' + err.message);
1551
1610
  });
1552
1611
 
1553
- child.on('exit', (code) => {
1612
+ child.on('exit', (code, signal) => {
1613
+ // 被 SIGTERM 杀死(下一次 mc server 启动时)→ 不重启
1614
+ if (signal === 'SIGTERM') {
1615
+ console.log('[Server] 服务已被外部终止,不再重启');
1616
+ process.exit(0);
1617
+ return;
1618
+ }
1554
1619
  if (code !== null && code !== 0) {
1555
1620
  console.log('[' + colors.yellow + '警告' + colors.nc + '] 服务异常退出,代码: ' + code + ',3秒后重启...');
1621
+ setTimeout(startProcess, 3000);
1556
1622
  } else {
1557
- console.log('[Server] 服务已停止');
1623
+ console.log('[Server] 服务已正常停止');
1558
1624
  }
1559
- setTimeout(startProcess, 3000);
1560
1625
  });
1626
+
1627
+ // 记录子进程 PID 到文件
1628
+ if (child.pid) {
1629
+ fs.writeFileSync(pidFile, String(child.pid), 'utf-8');
1630
+ }
1561
1631
  }
1562
1632
 
1563
1633
  startProcess();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@aiyiran/myclaw",
3
- "version": "1.0.242",
3
+ "version": "1.0.244",
4
4
  "description": "",
5
5
  "main": "index.js",
6
6
  "bin": {