@aiyiran/myclaw 1.0.103 → 1.0.105

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.
@@ -9,11 +9,10 @@
9
9
  * - 光标处插入文字
10
10
  * - 持续录入,手动停止
11
11
  * - 讯飞 60 秒断开后自动重连
12
- * 3. 拦截 chat-tts-btn 按钮,点击后使用讯飞 TTS 播放消息
13
12
  *
14
13
  * 依赖:
15
14
  * - voice-input.js(讯飞 VoiceInput SDK)
16
- * - voice-output.js(讯飞 VoiceOutput SDK)
15
+ * - myclaw-tts.js(TTS 按钮绑定,由 myclaw-tts.js 提供)
17
16
  * ============================================================================
18
17
  */
19
18
  (function () {
@@ -23,13 +22,11 @@
23
22
 
24
23
  // ═══ 状态 ═══
25
24
  var voice = null; // VoiceInput 实例
26
- var tts = null; // VoiceOutput 实例
27
25
  var recording = false; // 用户层面的录音状态(独立于 SDK 的 status)
28
26
  var pendingText = ""; // 当前这轮识别的文字(实时更新)
29
27
  var committedText = ""; // 已经提交到 textarea 的文字(上一轮累积)
30
28
  var cursorOffset = 0; // 录音开始时光标在 textarea 中的位置
31
29
  var injected = false;
32
- var ttsBtnHooked = false; // TTS 按钮是否已绑定
33
30
 
34
31
  // ═══ 1. 右下角版本标签(点击测试麦克风) ═══
35
32
  function createVersionBar() {
@@ -401,92 +398,6 @@
401
398
  console.log("[myclaw-inject] ✅ 发送按钮拦截器已挂载");
402
399
  }
403
400
 
404
- // ═══ 7. TTS 按钮绑定(讯飞语音合成) ═══
405
-
406
- function initTts() {
407
- if (typeof window.VoiceOutput === "undefined") {
408
- console.error("[myclaw-tts] VoiceOutput SDK 未加载");
409
- return;
410
- }
411
- if (tts) return;
412
-
413
- tts = new window.VoiceOutput({
414
- APPID: 'de6ec59e',
415
- APISecret: 'ZTA3ZDU3YjAyNDUyZGVkNjQwMGM5MWNi',
416
- APIKey: '37104a0463a5460d7571869324b667d5',
417
- vcn: 'aisbabyxu',
418
- onAudio: function (audioBuffer) {
419
- console.log('[myclaw-tts] audio chunk:', audioBuffer.byteLength, 'bytes');
420
- },
421
- onStatusChange: function (oldStatus, newStatus) {
422
- console.log('[myclaw-tts] status:', oldStatus, '->', newStatus);
423
- },
424
- onError: function (err) {
425
- console.error('[myclaw-tts] error:', err);
426
- }
427
- });
428
-
429
- console.log("[myclaw-tts] ✅ 讯飞 TTS SDK 已初始化");
430
- }
431
-
432
- /**
433
- * 从 .chat-tts-btn 按钮获取对应的消息文字
434
- * DOM 结构:
435
- * .chat-group-messages
436
- * .chat-bubble
437
- * .chat-text > p (消息文字)
438
- * .chat-group-footer
439
- * .chat-tts-btn
440
- */
441
- function extractMessageText(ttsBtn) {
442
- // 向上找到 .chat-group-messages
443
- var group = ttsBtn.closest('.chat-group-messages');
444
- if (!group) return '';
445
-
446
- // 在 group 内找 .chat-bubble
447
- var bubble = group.querySelector('.chat-bubble');
448
- if (!bubble) return '';
449
-
450
- // 在气泡内找 .chat-text 的文字
451
- var chatText = bubble.querySelector('.chat-text');
452
- if (!chatText) return '';
453
-
454
- // 获取文字,移除多余空白
455
- var text = chatText.textContent || '';
456
- return text.replace(/\s+/g, ' ').trim();
457
- }
458
-
459
- function hookTtsButton() {
460
- if (ttsBtnHooked) return;
461
- ttsBtnHooked = true;
462
-
463
- // 使用捕获阶段拦截点击
464
- document.addEventListener('click', function (e) {
465
- var btn = e.target.closest('.chat-tts-btn');
466
- if (!btn) return;
467
-
468
- e.preventDefault();
469
- e.stopPropagation();
470
-
471
- var text = extractMessageText(btn);
472
- if (!text) {
473
- console.warn('[myclaw-tts] 未找到消息文字');
474
- return;
475
- }
476
-
477
- console.log('[myclaw-tts] 播放:', text.substring(0, 50) + (text.length > 50 ? '...' : ''));
478
-
479
- if (!tts) {
480
- initTts();
481
- }
482
- if (tts) {
483
- tts.speak(text);
484
- }
485
- }, true);
486
-
487
- console.log("[myclaw-inject] ✅ TTS 按钮拦截器已挂载");
488
- }
489
-
490
401
  function fallbackCopy(text) {
491
402
  var ta = document.createElement("textarea");
492
403
  ta.value = text;
@@ -506,9 +417,6 @@
506
417
  // 初始化 VoiceInput SDK
507
418
  initVoice();
508
419
 
509
- // 初始化 TTS SDK
510
- initTts();
511
-
512
420
  // 持续监听 DOM 变化,确保按钮始终在
513
421
  new MutationObserver(function () {
514
422
  if (!document.querySelector("#myclaw-voice-btn")) {
@@ -524,9 +432,7 @@
524
432
 
525
433
  // 挂载发送拦截
526
434
  hookSendButton();
527
-
528
- // 挂载 TTS 按钮拦截
529
- hookTtsButton();
435
+ // TTS 按钮绑定由 myclaw-tts.js 独立处理
530
436
  }
531
437
 
532
438
  if (document.readyState === "loading") {
@@ -0,0 +1,270 @@
1
+ /**
2
+ * ============================================================================
3
+ * MyClaw TTS — 讯飞语音合成
4
+ * ============================================================================
5
+ *
6
+ * 功能:拦截 chat-tts-btn 按钮,点击后使用讯飞 TTS 播放消息
7
+ *
8
+ * 状态:
9
+ * idle → 默认状态,显示小嘴巴 emoji
10
+ * generating → 合成中,显示加载动画
11
+ * playing → 播放中,显示喇叭 emoji,点击可停止
12
+ *
13
+ * 依赖:voice-output.js(讯飞 VoiceOutput SDK,需先于本脚本加载)
14
+ * ============================================================================
15
+ */
16
+ (function () {
17
+ 'use strict';
18
+
19
+ // ═══ 状态 ═══
20
+ var tts = null; // VoiceOutput 实例
21
+ var ttsBtnHooked = false;
22
+ var currentBtn = null; // 当前正在播放的按钮
23
+ var playerState = 'idle'; // idle | generating | playing
24
+
25
+ // ═══ 讯飞配置 ═══
26
+ var IFLYTEK_CONFIG = {
27
+ APPID: 'de6ec59e',
28
+ APISecret: 'ZTA3ZDU3YjAyNDUyZGVkNjQwMGM5MWNi',
29
+ APIKey: '37104a0463a5460d7571869324b667d5',
30
+ vcn: 'x4_yezi', // 默认音色:小露(普通话女声)
31
+ };
32
+
33
+ // ═══ 公开配置接口 ═══
34
+ window.MyClawTts = {
35
+ setVcn: function (vcn) {
36
+ IFLYTEK_CONFIG.vcn = vcn;
37
+ if (tts) tts.vcn = vcn;
38
+ },
39
+ getVcn: function () {
40
+ return IFLYTEK_CONFIG.vcn;
41
+ },
42
+ speak: function (text) {
43
+ if (!tts) initTts();
44
+ if (tts) tts.speak(text);
45
+ },
46
+ };
47
+
48
+ // ═══ 按钮图标 ═══
49
+ var ICONS = {
50
+ idle: '👁', // 👁 小嘴巴
51
+ generating: '🔄', // 🔄 加载中
52
+ playing: '🔊', // 🔊 喇叭
53
+ };
54
+
55
+ // ═══ 状态更新 ═══
56
+ function setPlayerState(btn, state) {
57
+ if (!btn) return;
58
+ playerState = state;
59
+
60
+ // 恢复原生 SVG
61
+ var svg = btn.querySelector('svg');
62
+ if (svg) {
63
+ svg.style.display = state === 'idle' ? '' : 'none';
64
+ }
65
+
66
+ // 移除所有状态 class
67
+ btn.classList.remove('myclaw-tts--generating', 'myclaw-tts--playing');
68
+
69
+ if (state === 'generating') {
70
+ btn.classList.add('myclaw-tts--generating');
71
+ btn.title = '生成中...';
72
+ } else if (state === 'playing') {
73
+ btn.classList.add('myclaw-tts--playing');
74
+ btn.title = '点击停止';
75
+ } else {
76
+ btn.title = '朗读';
77
+ }
78
+ }
79
+
80
+ function updateBtnIcon(btn, state) {
81
+ if (!btn) return;
82
+
83
+ // 移除旧的 emoji 容器
84
+ var oldEmoji = btn.querySelector('.myclaw-tts-icon');
85
+ if (oldEmoji) oldEmoji.remove();
86
+
87
+ // 创建 emoji 容器
88
+ var emoji = document.createElement('span');
89
+ emoji.className = 'myclaw-tts-icon';
90
+ emoji.innerHTML = ICONS[state];
91
+ emoji.style.cssText = 'display:inline-block;width:18px;height:18px;line-height:18px;text-align:center;';
92
+
93
+ // 插入到按钮最前面
94
+ btn.insertBefore(emoji, btn.firstChild);
95
+ }
96
+
97
+ // ═══ 初始化 TTS ═══
98
+ function initTts() {
99
+ if (typeof window.VoiceOutput === 'undefined') {
100
+ console.error('[myclaw-tts] VoiceOutput SDK 未加载');
101
+ return;
102
+ }
103
+ if (tts) return;
104
+
105
+ tts = new window.VoiceOutput({
106
+ APPID: IFLYTEK_CONFIG.APPID,
107
+ APISecret: IFLYTEK_CONFIG.APISecret,
108
+ APIKey: IFLYTEK_CONFIG.APIKey,
109
+ vcn: IFLYTEK_CONFIG.vcn,
110
+ onAudio: function (audioBuffer) {
111
+ // console.log('[myclaw-tts] audio chunk:', audioBuffer.byteLength, 'bytes');
112
+ },
113
+ onStatusChange: function (oldStatus, newStatus) {
114
+ console.log('[myclaw-tts] status:', oldStatus, '->', newStatus);
115
+
116
+ if (!currentBtn) return;
117
+
118
+ if (newStatus === 'connecting' || newStatus === 'synthesizing') {
119
+ updateBtnIcon(currentBtn, 'generating');
120
+ setPlayerState(currentBtn, 'generating');
121
+ } else if (newStatus === 'idle') {
122
+ if (playerState === 'generating') {
123
+ // 合成完成但未开始播放(可能是空文字或其他错误)
124
+ setPlayerState(currentBtn, 'idle');
125
+ }
126
+ // playing 状态会在 audio.onended 时处理
127
+ }
128
+ },
129
+ onError: function (err) {
130
+ console.error('[myclaw-tts] error:', err);
131
+ if (currentBtn) {
132
+ setPlayerState(currentBtn, 'idle');
133
+ updateBtnIcon(currentBtn, 'idle');
134
+ }
135
+ currentBtn = null;
136
+ playerState = 'idle';
137
+ },
138
+ });
139
+
140
+ console.log('[myclaw-tts] ✅ 讯飞 TTS SDK 已初始化');
141
+ }
142
+
143
+ // ═══ 提取消息文字 ═══
144
+ function extractMessageText(ttsBtn) {
145
+ var group = ttsBtn.closest('.chat-group-messages');
146
+ if (!group) return '';
147
+
148
+ var bubble = group.querySelector('.chat-bubble');
149
+ if (!bubble) return '';
150
+
151
+ var chatText = bubble.querySelector('.chat-text');
152
+ if (!chatText) return '';
153
+
154
+ var text = chatText.textContent || '';
155
+ return text.replace(/\s+/g, ' ').trim();
156
+ }
157
+
158
+ // ═══ 注入样式 ═══
159
+ function injectStyles() {
160
+ if (document.querySelector('#myclaw-tts-styles')) return;
161
+ var style = document.createElement('style');
162
+ style.id = 'myclaw-tts-styles';
163
+ style.textContent = [
164
+ /* 生成中:旋转动画 */
165
+ '.myclaw-tts--generating {',
166
+ ' animation: myclaw-tts-spin 1s linear infinite;',
167
+ ' opacity: 0.7;',
168
+ '}',
169
+ '@keyframes myclaw-tts-spin {',
170
+ ' from { transform: rotate(0deg); }',
171
+ ' to { transform: rotate(360deg); }',
172
+ '}',
173
+ /* 播放中:脉冲动画 */
174
+ '.myclaw-tts--playing {',
175
+ ' animation: myclaw-tts-pulse 0.8s ease-in-out infinite;',
176
+ '}',
177
+ '@keyframes myclaw-tts-pulse {',
178
+ ' 0%, 100% { opacity: 1; }',
179
+ ' 50% { opacity: 0.5; }',
180
+ '}',
181
+ ].join('\n');
182
+ document.head.appendChild(style);
183
+ }
184
+
185
+ // ═══ 挂载按钮拦截 ═══
186
+ function hookTtsButton() {
187
+ if (ttsBtnHooked) return;
188
+ ttsBtnHooked = true;
189
+
190
+ document.addEventListener('click', function (e) {
191
+ var btn = e.target.closest('.chat-tts-btn');
192
+ if (!btn) return;
193
+
194
+ e.preventDefault();
195
+ e.stopPropagation();
196
+
197
+ // 如果正在播放,点击停止
198
+ if (playerState === 'playing' && currentBtn === btn) {
199
+ console.log('[myclaw-tts] 停止播放');
200
+ if (tts) tts.stop();
201
+ setPlayerState(btn, 'idle');
202
+ updateBtnIcon(btn, 'idle');
203
+ currentBtn = null;
204
+ playerState = 'idle';
205
+ return;
206
+ }
207
+
208
+ // 如果当前有其他按钮在播放,先停掉
209
+ if (playerState === 'playing' && currentBtn && currentBtn !== btn) {
210
+ if (tts) tts.stop();
211
+ setPlayerState(currentBtn, 'idle');
212
+ updateBtnIcon(currentBtn, 'idle');
213
+ }
214
+
215
+ var text = extractMessageText(btn);
216
+ if (!text) {
217
+ console.warn('[myclaw-tts] 未找到消息文字');
218
+ return;
219
+ }
220
+
221
+ console.log('[myclaw-tts] 播放:', text.substring(0, 50) + (text.length > 50 ? '...' : ''));
222
+
223
+ // 设置当前按钮和状态
224
+ currentBtn = btn;
225
+ setPlayerState(btn, 'generating');
226
+ updateBtnIcon(btn, 'generating');
227
+
228
+ // 拦截 onAudioEnd 来设置 playing 状态
229
+ if (!tts) initTts();
230
+ if (tts) {
231
+ tts.onAudioEnd = function () {
232
+ console.log('[myclaw-tts] 播放结束');
233
+ if (currentBtn) {
234
+ setPlayerState(currentBtn, 'idle');
235
+ updateBtnIcon(currentBtn, 'idle');
236
+ currentBtn = null;
237
+ }
238
+ playerState = 'idle';
239
+ };
240
+ tts.vcn = IFLYTEK_CONFIG.vcn;
241
+ tts.speak(text);
242
+
243
+ // 状态会在 onStatusChange 里变成 playing
244
+ // 手动设置一次,因为 Web Audio API 可能比 status 更快
245
+ setTimeout(function () {
246
+ if (playerState === 'generating') {
247
+ setPlayerState(btn, 'playing');
248
+ updateBtnIcon(btn, 'playing');
249
+ playerState = 'playing';
250
+ }
251
+ }, 100);
252
+ }
253
+ }, true);
254
+
255
+ console.log('[myclaw-tts] ✅ TTS 按钮拦截器已挂载');
256
+ }
257
+
258
+ // ═══ 启动 ═══
259
+ function init() {
260
+ injectStyles();
261
+ initTts();
262
+ hookTtsButton();
263
+ }
264
+
265
+ if (document.readyState === 'loading') {
266
+ document.addEventListener('DOMContentLoaded', init);
267
+ } else {
268
+ init();
269
+ }
270
+ })();
package/create_agent.js CHANGED
@@ -154,7 +154,8 @@ function defaultWorkspaceFiles(agentId) {
154
154
  * @param {string} rawName - 原始名称
155
155
  * @returns {object} 创建结果
156
156
  */
157
- function createAgent(rawName) {
157
+ function createAgent(rawName, opts) {
158
+ opts = opts || {};
158
159
  // 标准化 & 验证名称
159
160
  const agentId = normalizeAgentId(rawName);
160
161
  validateAgentId(agentId);
@@ -239,8 +240,8 @@ function createAgent(rawName) {
239
240
  fail('配置文件写入失败: ' + err.message + '\n备份文件: ' + normalizePath(backupPath));
240
241
  }
241
242
 
242
- // 发送出生消息
243
- const birthMessage = buildBirthMessage();
243
+ // 发送首条消息:优先用自定义内容,否则走默认出生文案
244
+ const birthMessage = opts.firstMessage || buildBirthMessage();
244
245
  let firstMessageSent = false;
245
246
  let firstMessageError = null;
246
247
 
package/index.js CHANGED
@@ -181,8 +181,175 @@ function runStatus() {
181
181
  // ============================================================================
182
182
 
183
183
  function runNew() {
184
- const { runNewAgent } = require('./wizards/index');
185
- runNewAgent();
184
+ const rawName = args[1];
185
+
186
+ // 无参数 → 向导模式
187
+ if (!rawName) {
188
+ const { runNewAgent } = require('./wizards/index');
189
+ runNewAgent();
190
+ return;
191
+ }
192
+
193
+ // 有参数 → 一键创建模式(带分步日志)
194
+ // myclaw new haha → 默认出生文案
195
+ // myclaw new haha 你好见到你很高兴 → 自定义首条消息
196
+ const firstMessage = args.slice(2).join(' ').trim() || null;
197
+
198
+ const bar = '────────────────────────────────────────';
199
+ const G = colors.green;
200
+ const Y = colors.yellow;
201
+ const R = colors.red;
202
+ const B = colors.blue;
203
+ const NC = colors.nc;
204
+
205
+ console.log('');
206
+ console.log('[' + B + 'MyClaw' + NC + '] ' + B + '创建 Agent: ' + rawName + NC);
207
+ console.log(bar);
208
+ console.log('');
209
+
210
+ const { normalizeAgentId, validateAgentId } = require('./create_agent');
211
+ const fs = require('fs');
212
+ const path = require('path');
213
+ const os = require('os');
214
+ const { execSync } = require('child_process');
215
+
216
+ const homeDir = os.homedir();
217
+ const openclawDir = path.join(homeDir, '.openclaw');
218
+ const configPath = path.join(openclawDir, 'openclaw.json');
219
+
220
+ // ── Step 1: 名称标准化 ──
221
+ console.log(B + '[1/6]' + NC + ' 标准化名称...');
222
+ console.log(' ' + G + '▶ 发起:' + NC + ' normalizeAgentId("' + rawName + '")');
223
+ let agentId;
224
+ try {
225
+ agentId = normalizeAgentId(rawName);
226
+ validateAgentId(agentId);
227
+ console.log(' ' + G + '◀ 返回:' + NC + ' agentId = "' + agentId + '" ✅');
228
+ } catch (err) {
229
+ console.log(' ' + R + '◀ 返回:' + NC + ' ❌ ' + err.message);
230
+ process.exit(1);
231
+ }
232
+ console.log('');
233
+
234
+ // ── Step 2: 环境检查 ──
235
+ console.log(B + '[2/6]' + NC + ' 环境检查(查重 + 目录冲突)...');
236
+ console.log(' ' + G + '▶ 发起:' + NC + ' 读取 ' + configPath);
237
+ let configData;
238
+ try {
239
+ configData = JSON.parse(fs.readFileSync(configPath, 'utf8'));
240
+ if (!configData.agents) configData.agents = {};
241
+ if (!Array.isArray(configData.agents.list)) configData.agents.list = [];
242
+
243
+ const exists = configData.agents.list.find(a => a && a.id === agentId);
244
+ if (exists) {
245
+ console.log(' ' + R + '◀ 返回:' + NC + ' ❌ Agent "' + agentId + '" 已存在');
246
+ process.exit(1);
247
+ }
248
+ const workspaceDir = path.join(openclawDir, 'workspace-' + agentId);
249
+ const agentDir = path.join(openclawDir, 'agents', agentId, 'agent');
250
+ if (fs.existsSync(workspaceDir) || fs.existsSync(agentDir)) {
251
+ console.log(' ' + R + '◀ 返回:' + NC + ' ❌ 目录已存在');
252
+ process.exit(1);
253
+ }
254
+ console.log(' ' + G + '◀ 返回:' + NC + ' 名称无冲突,目录可用 ✅');
255
+ } catch (err) {
256
+ console.log(' ' + R + '◀ 返回:' + NC + ' ❌ ' + err.message);
257
+ process.exit(1);
258
+ }
259
+ console.log('');
260
+
261
+ // ── Step 3: 创建工作空间 ──
262
+ console.log(B + '[3/6]' + NC + ' 创建工作空间(SOUL.md / AGENTS.md / USER.md ...)...');
263
+ const workspaceDir = path.join(openclawDir, 'workspace-' + agentId);
264
+ const agentDir = path.join(openclawDir, 'agents', agentId, 'agent');
265
+ const sessionsDir = path.join(openclawDir, 'agents', agentId, 'sessions');
266
+ console.log(' ' + G + '▶ 发起:' + NC + ' mkdir + writeFile → ' + workspaceDir);
267
+ try {
268
+ fs.mkdirSync(workspaceDir, { recursive: true });
269
+ fs.mkdirSync(agentDir, { recursive: true });
270
+ fs.mkdirSync(sessionsDir, { recursive: true });
271
+ // 写入默认文件
272
+ const defaultFiles = {
273
+ 'AGENTS.md': '# AGENTS.md - ' + agentId + '\n\nThis workspace belongs to the `' + agentId + '` agent.\n\n## Startup\n- Read `SOUL.md`\n- Read `USER.md`\n\n## Rules\n- Be helpful, careful, and concise.\n',
274
+ 'SOUL.md': '# SOUL.md\n\nYou are `' + agentId + '`.\n\nBe useful, calm, direct, and trustworthy.\n',
275
+ 'USER.md': '# USER.md\n\n- Name: 孙依然\n- What to call them: 依然\n- Timezone: Asia/Shanghai\n',
276
+ 'IDENTITY.md': '# IDENTITY.md\n\n- Name: ' + agentId + '\n- Role: OpenClaw agent\n',
277
+ 'BOOTSTRAP.md': '# BOOTSTRAP.md\n\nYou are a newly created agent named `' + agentId + '`.\nOn first runs, learn your workspace files and begin helping.\n',
278
+ };
279
+ for (const [fn, content] of Object.entries(defaultFiles)) {
280
+ const fp = path.join(workspaceDir, fn);
281
+ if (!fs.existsSync(fp)) fs.writeFileSync(fp, content, 'utf8');
282
+ }
283
+ console.log(' ' + G + '◀ 返回:' + NC + ' 已创建 ' + Object.keys(defaultFiles).length + ' 个基础文件 ✅');
284
+ } catch (err) {
285
+ console.log(' ' + R + '◀ 返回:' + NC + ' ❌ ' + err.message);
286
+ process.exit(1);
287
+ }
288
+ console.log('');
289
+
290
+ // ── Step 4: 注册到配置 ──
291
+ console.log(B + '[4/6]' + NC + ' 注册到 openclaw.json(agents.list)...');
292
+ const backupPath = configPath + '.bak.agent-birth.' + new Date().toISOString().replace(/[:.]/g, '-').slice(0, 26) + 'Z';
293
+ console.log(' ' + G + '▶ 发起:' + NC + ' 备份 → ' + path.basename(backupPath));
294
+ try {
295
+ fs.copyFileSync(configPath, backupPath);
296
+ configData.agents.list.push({
297
+ id: agentId,
298
+ name: agentId,
299
+ workspace: workspaceDir.replace(/\\/g, '/'),
300
+ agentDir: agentDir.replace(/\\/g, '/'),
301
+ });
302
+ fs.writeFileSync(configPath, JSON.stringify(configData, null, 2) + '\n', 'utf8');
303
+ console.log(' ' + G + '◀ 返回:' + NC + ' 配置已写入,备份已保存 ✅');
304
+ } catch (err) {
305
+ console.log(' ' + R + '◀ 返回:' + NC + ' ❌ ' + err.message);
306
+ process.exit(1);
307
+ }
308
+ console.log('');
309
+
310
+ // ── Step 5: 发送首条消息 ──
311
+ console.log(B + '[5/6]' + NC + ' 发送首条消息(唤醒 Agent)...');
312
+ const nowUtc = new Date();
313
+ const nowBj = new Date(nowUtc.getTime() + 8 * 60 * 60 * 1000);
314
+ const ts = nowBj.getUTCFullYear() + '年' + (nowBj.getUTCMonth() + 1) + '月' + nowBj.getUTCDate() + '日' + String(nowBj.getUTCHours()).padStart(2, '0') + ':' + String(nowBj.getUTCMinutes()).padStart(2, '0');
315
+ const birthMessage = firstMessage || ('你好,你的生日是北京时间' + ts + ',请记录这个信息。\n欢迎你来到这个世界,我是你的造物主,我们是伙伴,我叫做孙依然,你可以叫我依然,请多多指教,共同成长。');
316
+
317
+ const cmd = 'openclaw agent --agent ' + agentId + ' --message ' + JSON.stringify(birthMessage) + ' --json';
318
+ console.log(' ' + G + '▶ 发起:' + NC + ' $ ' + G + cmd.substring(0, 80) + (cmd.length > 80 ? '...' : '') + NC);
319
+ console.log(' ' + Y + ' ⏳ 等待大模型响应(可能需要 5~15 秒)...' + NC);
320
+
321
+ let firstMessageSent = false;
322
+ try {
323
+ execSync(cmd + ' 2>&1', { encoding: 'utf8', timeout: 45000 });
324
+ firstMessageSent = true;
325
+ console.log(' ' + G + '◀ 返回:' + NC + ' 唤醒成功,Session 已建立 ✅');
326
+ } catch (err) {
327
+ console.log(' ' + Y + '◀ 返回:' + NC + ' ⚠️ 唤醒超时或失败(非致命)');
328
+ console.log(' ' + '手动重试: ' + Y + 'openclaw agent --agent ' + agentId + ' --message "你好"' + NC);
329
+ }
330
+ console.log('');
331
+
332
+ // ── Step 6: 验证 ──
333
+ console.log(B + '[6/6]' + NC + ' 验证创建结果...');
334
+ console.log(' ' + G + '▶ 发起:' + NC + ' 检查 agents.list 是否包含 "' + agentId + '"');
335
+ try {
336
+ const verify = JSON.parse(fs.readFileSync(configPath, 'utf8'));
337
+ const found = verify.agents && verify.agents.list && verify.agents.list.find(a => a.id === agentId);
338
+ if (found) {
339
+ console.log(' ' + G + '◀ 返回:' + NC + ' Agent "' + agentId + '" 已成功登记 ✅');
340
+ } else {
341
+ console.log(' ' + R + '◀ 返回:' + NC + ' ⚠️ 未在配置中找到,请检查');
342
+ }
343
+ } catch (err) {
344
+ console.log(' ' + Y + '◀ 返回:' + NC + ' ⚠️ 验证读取失败(非致命)');
345
+ }
346
+
347
+ console.log('');
348
+ console.log(bar);
349
+ console.log(G + '🎉 Agent "' + agentId + '" 创建完成!' + NC);
350
+ console.log('');
351
+ console.log('下一步: ' + Y + 'openclaw agent --agent ' + agentId + ' --message "你好"' + NC);
352
+ console.log('');
186
353
  }
187
354
 
188
355
  // ============================================================================
@@ -776,7 +943,7 @@ function showHelp() {
776
943
  console.log('');
777
944
  console.log('向导 (交互式):');
778
945
  console.log(' prepare 智能初始化(环境检测 + 安装 + Patch)');
779
- console.log(' new 创建新 Agent(分步理解生命周期)');
946
+ console.log(' new 创建新 Agent(无参数=向导,有参数=一键)');
780
947
  console.log(' weixin 微信接入向导(步骤引导 + 教学说明)');
781
948
  console.log(' rebind 微信换绑向导(清空、扫码、换Agent)');
782
949
  console.log('');
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@aiyiran/myclaw",
3
- "version": "1.0.103",
3
+ "version": "1.0.105",
4
4
  "description": "",
5
5
  "main": "index.js",
6
6
  "bin": {
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "_doc": "MyClaw 注入清单 (auto-generated)。strategy: auto | on | off",
3
- "_generated": "2026-04-01T18:08:46.905Z",
3
+ "_generated": "2026-04-02T12:35:21.624Z",
4
4
  "agents": [
5
5
  {
6
6
  "id": "danci",
package/patch.js CHANGED
@@ -25,6 +25,7 @@ const INJECT_MARKER = '<!-- myclaw-inject -->';
25
25
  const INJECT_FILENAME = 'myclaw-inject.js';
26
26
  const VOICE_SDK_FILENAME = 'voice-input.js';
27
27
  const VOICE_OUTPUT_SDK_FILENAME = 'voice-output.js';
28
+ const TTS_INJECT_FILENAME = 'myclaw-tts.js';
28
29
  const BACKUP_SUFFIX = '.myclaw-backup';
29
30
 
30
31
  /**
@@ -112,6 +113,8 @@ function patch() {
112
113
  const voiceSdkDest = path.join(uiDir, VOICE_SDK_FILENAME);
113
114
  const voiceOutputSdkSrc = path.join(__dirname, 'voice-output', VOICE_OUTPUT_SDK_FILENAME);
114
115
  const voiceOutputSdkDest = path.join(uiDir, VOICE_OUTPUT_SDK_FILENAME);
116
+ const ttsInjectSrc = path.join(__dirname, 'assets', TTS_INJECT_FILENAME);
117
+ const ttsInjectDest = path.join(uiDir, TTS_INJECT_FILENAME);
115
118
  const version = getMyclawVersion();
116
119
 
117
120
  console.log('[myclaw-patch] 📍 找到 control-ui: ' + uiDir);
@@ -156,7 +159,15 @@ function patch() {
156
159
  console.error('[myclaw-patch] ⚠️ TTS SDK 复制失败 (非致命): ' + err.message);
157
160
  }
158
161
 
159
- // 4. Patch index.html(幂等)
162
+ // 5. 复制 TTS 注入脚本
163
+ try {
164
+ fs.copyFileSync(ttsInjectSrc, ttsInjectDest);
165
+ console.log('[myclaw-patch] 📄 TTS 注入脚本已复制: ' + ttsInjectDest);
166
+ } catch (err) {
167
+ console.error('[myclaw-patch] ⚠️ TTS 注入脚本复制失败 (非致命): ' + err.message);
168
+ }
169
+
170
+ // 6. Patch index.html(幂等)
160
171
  try {
161
172
  let html = fs.readFileSync(indexPath, 'utf8');
162
173
 
@@ -172,6 +183,7 @@ function patch() {
172
183
  INJECT_MARKER,
173
184
  '<script src="./' + VOICE_OUTPUT_SDK_FILENAME + '"></script>',
174
185
  '<script src="./' + VOICE_SDK_FILENAME + '"></script>',
186
+ '<script src="./' + TTS_INJECT_FILENAME + '"></script>',
175
187
  '<script src="./' + INJECT_FILENAME + '"></script>',
176
188
  '',
177
189
  ].join('\n');
@@ -249,6 +261,7 @@ function unpatch() {
249
261
  const injectDest = path.join(uiDir, INJECT_FILENAME);
250
262
  const voiceSdkDest = path.join(uiDir, VOICE_SDK_FILENAME);
251
263
  const voiceOutputSdkDest = path.join(uiDir, VOICE_OUTPUT_SDK_FILENAME);
264
+ const ttsInjectDest = path.join(uiDir, TTS_INJECT_FILENAME);
252
265
 
253
266
  // 恢复备份
254
267
  if (fs.existsSync(backupPath)) {
@@ -275,6 +288,12 @@ function unpatch() {
275
288
  console.log('[myclaw-patch] ✅ TTS SDK 已删除');
276
289
  }
277
290
 
291
+ // 删除 TTS 注入脚本
292
+ if (fs.existsSync(ttsInjectDest)) {
293
+ fs.unlinkSync(ttsInjectDest);
294
+ console.log('[myclaw-patch] ✅ TTS 注入脚本已删除');
295
+ }
296
+
278
297
  // 恢复 gateway-cli-*.js 的 Permissions-Policy
279
298
  try {
280
299
  const distParent = path.resolve(uiDir, '..');
@@ -107,9 +107,33 @@
107
107
  VoiceOutput.prototype._playAudio = function (audioBuffer) {
108
108
  var self = this;
109
109
 
110
+ // 尝试 Web Audio API(可以解码 MP3)
111
+ try {
112
+ this._initAudioContext();
113
+ this.audioContext.decodeAudioData(audioBuffer.slice(0), function (decodedBuffer) {
114
+ console.log('[tts] decoded audio:', decodedBuffer.duration, 's,', decodedBuffer.numberOfChannels, 'ch');
115
+ self._playBuffer(decodedBuffer);
116
+ }, function (err) {
117
+ console.error('[tts] decode error, trying blob URL:', err);
118
+ // fallback: 用 Blob URL 方式
119
+ self._playAudioBlob(audioBuffer);
120
+ });
121
+ return;
122
+ } catch (e) {
123
+ console.error('[tts] Web Audio API error:', e);
124
+ // fallback: 用 Blob URL 方式
125
+ }
126
+
127
+ this._playAudioBlob(audioBuffer);
128
+ };
129
+
130
+ VoiceOutput.prototype._playAudioBlob = function (audioBuffer) {
131
+ var self = this;
132
+
110
133
  // 创建 Blob URL 用于 <audio> 元素播放
111
134
  var mimeType = this.aue === 'lame' ? 'audio/mpeg' : 'audio/wav';
112
135
  var blob = new Blob([audioBuffer], { type: mimeType });
136
+ console.log('[tts] blob size:', blob.size, 'type:', mimeType);
113
137
  var blobUrl = URL.createObjectURL(blob);
114
138
 
115
139
  // 停止之前的播放
@@ -158,6 +158,8 @@ switch (action) {
158
158
  const nowBj = new Date(nowUtc.getTime() + 8 * 60 * 60 * 1000);
159
159
  const ts = `${nowBj.getUTCFullYear()}年${nowBj.getUTCMonth() + 1}月${nowBj.getUTCDate()}日${String(nowBj.getUTCHours()).padStart(2, '0')}:${String(nowBj.getUTCMinutes()).padStart(2, '0')}`;
160
160
 
161
+ const birthMessage = `你好,你的生日是北京时间${ts},请记录这个信息。\n欢迎你来到这个世界,我是你的造物主,我们是伙伴,我叫做孙依然,你可以叫我依然,请多多指教,共同成长。`;
162
+
161
163
  const cmd = `openclaw agent --agent ${agentId} --message ${JSON.stringify(birthMessage)} --json 2>&1`;
162
164
  console.log('\n\x1b[32m$ openclaw agent --agent ' + agentId + ' --message "你好,你的生日..."\x1b[0m\n');
163
165