@aiyiran/myclaw 1.0.97 → 1.0.99
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/assets/myclaw-inject.js +143 -28
- package/index.js +17 -55
- package/package.json +1 -1
- package/patch-manifest.json +2 -2
- package/start.js +21 -5
- package/wizards/configs/new-agent/index.config.json +116 -0
- package/wizards/index.js +6 -1
- package/wizards/scripts/create_agent_steps.js +178 -0
package/assets/myclaw-inject.js
CHANGED
|
@@ -26,7 +26,7 @@
|
|
|
26
26
|
var cursorOffset = 0; // 录音开始时光标在 textarea 中的位置
|
|
27
27
|
var injected = false;
|
|
28
28
|
|
|
29
|
-
// ═══ 1.
|
|
29
|
+
// ═══ 1. 右下角版本标签(点击测试麦克风) ═══
|
|
30
30
|
function createVersionBar() {
|
|
31
31
|
if (document.querySelector("#myclaw-version-bar")) return;
|
|
32
32
|
|
|
@@ -34,36 +34,29 @@
|
|
|
34
34
|
bar.id = "myclaw-version-bar";
|
|
35
35
|
bar.style.cssText = [
|
|
36
36
|
"position: fixed",
|
|
37
|
-
"
|
|
38
|
-
"
|
|
39
|
-
"
|
|
40
|
-
"
|
|
41
|
-
"
|
|
42
|
-
"
|
|
43
|
-
"color: #e94560",
|
|
44
|
-
"font-size: 12px",
|
|
37
|
+
"bottom: 8px",
|
|
38
|
+
"right: 8px",
|
|
39
|
+
"padding: 2px 8px",
|
|
40
|
+
"background: none",
|
|
41
|
+
"color: rgba(150, 150, 150, 0.4)",
|
|
42
|
+
"font-size: 10px",
|
|
45
43
|
"font-family: monospace",
|
|
46
|
-
"text-align: center",
|
|
47
44
|
"z-index: 99999",
|
|
48
|
-
"box-shadow: 0 1px 4px rgba(0,0,0,0.3)",
|
|
49
45
|
"user-select: none",
|
|
50
|
-
"
|
|
46
|
+
"cursor: pointer",
|
|
47
|
+
"transition: color 0.2s",
|
|
51
48
|
].join(";");
|
|
52
|
-
bar.textContent = "\uD83D\uDC3E
|
|
49
|
+
bar.textContent = "\uD83D\uDC3E v" + MYCLAW_VERSION;
|
|
50
|
+
bar.title = "\u70B9\u51FB\u6D4B\u8BD5\u9EA6\u514B\u98CE";
|
|
53
51
|
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
testBtn.style.cssText = "background:#e94560;color:white;border:none;padding:2px 8px;border-radius:4px;cursor:pointer;font-size:11px;font-family:monospace;";
|
|
58
|
-
testBtn.onclick = function(e) {
|
|
52
|
+
bar.onmouseenter = function () { bar.style.color = "rgba(150, 150, 150, 0.8)"; };
|
|
53
|
+
bar.onmouseleave = function () { bar.style.color = "rgba(150, 150, 150, 0.4)"; };
|
|
54
|
+
bar.onclick = function (e) {
|
|
59
55
|
e.stopPropagation();
|
|
60
|
-
console.log("[myclaw] \u6D4B\u8BD5\u9EA6\u514B\u98CE\u70B9\u51FB");
|
|
61
56
|
testMicrophone();
|
|
62
57
|
};
|
|
63
|
-
bar.appendChild(testBtn);
|
|
64
58
|
|
|
65
|
-
document.body.
|
|
66
|
-
document.body.style.paddingTop = "28px";
|
|
59
|
+
document.body.appendChild(bar);
|
|
67
60
|
}
|
|
68
61
|
|
|
69
62
|
// \u6D4B\u8BD5\u9EA6\u514B\u98CE\u51FD\u6570
|
|
@@ -183,6 +176,16 @@
|
|
|
183
176
|
btn.classList.remove("agent-chat__input-btn--recording");
|
|
184
177
|
btn.title = "\u8baf\u98de\u8bed\u97f3";
|
|
185
178
|
}
|
|
179
|
+
|
|
180
|
+
// 同步 textarea 边框状态
|
|
181
|
+
var ta = document.querySelector(".agent-chat__input textarea");
|
|
182
|
+
if (ta) {
|
|
183
|
+
if (recording) {
|
|
184
|
+
ta.classList.add("myclaw-voice-active");
|
|
185
|
+
} else {
|
|
186
|
+
ta.classList.remove("myclaw-voice-active");
|
|
187
|
+
}
|
|
188
|
+
}
|
|
186
189
|
}
|
|
187
190
|
|
|
188
191
|
// ═══ 4. 录音控制 ═══
|
|
@@ -195,6 +198,8 @@
|
|
|
195
198
|
|
|
196
199
|
voice = new window.VoiceInput({
|
|
197
200
|
onResult: function (text) {
|
|
201
|
+
// 如果用户已点停止,忽略异步返回的残留结果(防止文字重复)
|
|
202
|
+
if (!recording) return;
|
|
198
203
|
// 讯飞实时返回识别文字,替换到光标位置
|
|
199
204
|
pendingText = text;
|
|
200
205
|
updateTextAtCursor(pendingText);
|
|
@@ -262,15 +267,19 @@
|
|
|
262
267
|
}
|
|
263
268
|
|
|
264
269
|
function stopVoice() {
|
|
270
|
+
// 先关标志位,阻止 onResult 异步回调继续写入(核心防重复)
|
|
265
271
|
recording = false;
|
|
266
272
|
updateButtonUI();
|
|
267
273
|
|
|
274
|
+
// 立即快照当前 textarea 值作为最终文字
|
|
275
|
+
var finalText = getTextareaValue();
|
|
276
|
+
|
|
268
277
|
if (voice) {
|
|
269
278
|
voice.stop();
|
|
270
279
|
}
|
|
271
280
|
|
|
272
|
-
//
|
|
273
|
-
committedText =
|
|
281
|
+
// 用快照覆盖,确保后续异步返回不影响
|
|
282
|
+
committedText = finalText;
|
|
274
283
|
pendingText = "";
|
|
275
284
|
|
|
276
285
|
console.log("[myclaw-voice] \u505c\u6b62\u5f55\u97f3");
|
|
@@ -284,10 +293,9 @@
|
|
|
284
293
|
|
|
285
294
|
var btn = createVoiceButton();
|
|
286
295
|
|
|
287
|
-
//
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
toolbar.insertBefore(btn, tokenCount);
|
|
296
|
+
// 插入到工具栏最前面(排在 Attach file 等按钮之前)
|
|
297
|
+
if (toolbar.firstChild) {
|
|
298
|
+
toolbar.insertBefore(btn, toolbar.firstChild);
|
|
291
299
|
} else {
|
|
292
300
|
toolbar.appendChild(btn);
|
|
293
301
|
}
|
|
@@ -296,9 +304,113 @@
|
|
|
296
304
|
console.log("[myclaw-inject] \u2705 \u8bed\u97f3\u6309\u94ae\u5df2\u6ce8\u5165");
|
|
297
305
|
}
|
|
298
306
|
|
|
307
|
+
// ═══ 注入录音态样式 ═══
|
|
308
|
+
function injectStyles() {
|
|
309
|
+
if (document.querySelector("#myclaw-voice-styles")) return;
|
|
310
|
+
var style = document.createElement("style");
|
|
311
|
+
style.id = "myclaw-voice-styles";
|
|
312
|
+
style.textContent = [
|
|
313
|
+
/* 隐藏原生 Voice input 按钮(避免与 MyClaw 语音按钮混淆) */
|
|
314
|
+
"button[title=\"Voice input\"]:not(#myclaw-voice-btn) { display: none !important; }",
|
|
315
|
+
/* 按钮录音态:红色脉冲 */
|
|
316
|
+
".agent-chat__input-btn--recording {",
|
|
317
|
+
" color: #ff4444 !important;",
|
|
318
|
+
" animation: myclaw-pulse 1s ease-in-out infinite !important;",
|
|
319
|
+
" position: relative;",
|
|
320
|
+
"}",
|
|
321
|
+
".agent-chat__input-btn--recording::after {",
|
|
322
|
+
" content: '';",
|
|
323
|
+
" position: absolute;",
|
|
324
|
+
" top: -2px; left: -2px; right: -2px; bottom: -2px;",
|
|
325
|
+
" border-radius: 50%;",
|
|
326
|
+
" border: 2px solid #ff4444;",
|
|
327
|
+
" animation: myclaw-ring 1.5s ease-out infinite;",
|
|
328
|
+
" pointer-events: none;",
|
|
329
|
+
"}",
|
|
330
|
+
/* textarea 录音态:左侧红色竖线 + 微弱红色背景 */
|
|
331
|
+
".myclaw-voice-active {",
|
|
332
|
+
" border-left: 3px solid #ff4444 !important;",
|
|
333
|
+
" background: rgba(255, 68, 68, 0.03) !important;",
|
|
334
|
+
" transition: all 0.2s ease;",
|
|
335
|
+
"}",
|
|
336
|
+
/* 脉冲动画 */
|
|
337
|
+
"@keyframes myclaw-pulse {",
|
|
338
|
+
" 0%, 100% { opacity: 1; transform: scale(1); }",
|
|
339
|
+
" 50% { opacity: 0.6; transform: scale(1.1); }",
|
|
340
|
+
"}",
|
|
341
|
+
/* 扩散环动画 */
|
|
342
|
+
"@keyframes myclaw-ring {",
|
|
343
|
+
" 0% { transform: scale(0.8); opacity: 0.8; }",
|
|
344
|
+
" 100% { transform: scale(1.6); opacity: 0; }",
|
|
345
|
+
"}",
|
|
346
|
+
].join("\n");
|
|
347
|
+
document.head.appendChild(style);
|
|
348
|
+
}
|
|
349
|
+
// ═══ 6. 拦截发送按钮 ═══
|
|
350
|
+
|
|
351
|
+
var sendHooked = false;
|
|
352
|
+
|
|
353
|
+
function hookSendButton() {
|
|
354
|
+
if (sendHooked) return;
|
|
355
|
+
sendHooked = true;
|
|
356
|
+
|
|
357
|
+
// 用捕获阶段拦截,确保在原生 handler 之前执行
|
|
358
|
+
document.addEventListener("click", function (e) {
|
|
359
|
+
var btn = e.target.closest("button.chat-send-btn, button[title=\"Send\"]");
|
|
360
|
+
if (!btn) return;
|
|
361
|
+
|
|
362
|
+
var text = getTextareaValue();
|
|
363
|
+
if (!text || !text.trim()) return; // 空文字不处理
|
|
364
|
+
|
|
365
|
+
// 1) 停止语音输入
|
|
366
|
+
if (recording) {
|
|
367
|
+
stopVoice();
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
// 2) 复制到剪贴板
|
|
371
|
+
try {
|
|
372
|
+
navigator.clipboard.writeText(text).then(function () {
|
|
373
|
+
console.log("[myclaw-send] 📋 已复制到剪贴板:", text.substring(0, 50) + (text.length > 50 ? "..." : ""));
|
|
374
|
+
}).catch(function () {
|
|
375
|
+
// fallback: 老方法
|
|
376
|
+
fallbackCopy(text);
|
|
377
|
+
});
|
|
378
|
+
} catch (ex) {
|
|
379
|
+
fallbackCopy(text);
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
// 3) 让原生 click 继续走(发送消息)
|
|
383
|
+
// 不 preventDefault,不 stopPropagation
|
|
384
|
+
|
|
385
|
+
// 4) 延迟清空 textarea(等原生 handler 读完值后再清)
|
|
386
|
+
setTimeout(function () {
|
|
387
|
+
setTextareaValue("");
|
|
388
|
+
committedText = "";
|
|
389
|
+
pendingText = "";
|
|
390
|
+
cursorOffset = 0;
|
|
391
|
+
console.log("[myclaw-send] ✅ 发送完成,输入框已清空");
|
|
392
|
+
}, 100);
|
|
393
|
+
|
|
394
|
+
}, true); // true = 捕获阶段
|
|
395
|
+
|
|
396
|
+
console.log("[myclaw-inject] ✅ 发送按钮拦截器已挂载");
|
|
397
|
+
}
|
|
398
|
+
|
|
399
|
+
function fallbackCopy(text) {
|
|
400
|
+
var ta = document.createElement("textarea");
|
|
401
|
+
ta.value = text;
|
|
402
|
+
ta.style.cssText = "position:fixed;left:-9999px;";
|
|
403
|
+
document.body.appendChild(ta);
|
|
404
|
+
ta.select();
|
|
405
|
+
try { document.execCommand("copy"); } catch (e) {}
|
|
406
|
+
document.body.removeChild(ta);
|
|
407
|
+
console.log("[myclaw-send] 📋 已复制(fallback)");
|
|
408
|
+
}
|
|
409
|
+
|
|
299
410
|
// ═══ 启动 ═══
|
|
300
411
|
function init() {
|
|
301
412
|
createVersionBar();
|
|
413
|
+
injectStyles();
|
|
302
414
|
|
|
303
415
|
// 初始化 VoiceInput SDK
|
|
304
416
|
initVoice();
|
|
@@ -315,6 +427,9 @@
|
|
|
315
427
|
|
|
316
428
|
// 尝试立即注入
|
|
317
429
|
injectButton();
|
|
430
|
+
|
|
431
|
+
// 挂载发送拦截
|
|
432
|
+
hookSendButton();
|
|
318
433
|
}
|
|
319
434
|
|
|
320
435
|
if (document.readyState === "loading") {
|
package/index.js
CHANGED
|
@@ -181,60 +181,8 @@ function runStatus() {
|
|
|
181
181
|
// ============================================================================
|
|
182
182
|
|
|
183
183
|
function runNew() {
|
|
184
|
-
const
|
|
185
|
-
|
|
186
|
-
if (!rawName) {
|
|
187
|
-
console.error('[' + colors.red + '错误' + colors.nc + '] 请提供 Agent 名称');
|
|
188
|
-
console.log('');
|
|
189
|
-
console.log('用法: myclaw new <agent名称>');
|
|
190
|
-
console.log('');
|
|
191
|
-
console.log('示例:');
|
|
192
|
-
console.log(' myclaw new helper # 创建名为 helper 的 Agent');
|
|
193
|
-
console.log(' myclaw new testbot # 创建名为 testbot 的 Agent');
|
|
194
|
-
process.exit(1);
|
|
195
|
-
}
|
|
196
|
-
|
|
197
|
-
const bar = '----------------------------------------';
|
|
198
|
-
|
|
199
|
-
console.log('');
|
|
200
|
-
console.log('[' + colors.blue + 'MyClaw' + colors.nc + '] ' + colors.blue + '创建新 Agent' + colors.nc);
|
|
201
|
-
console.log(bar);
|
|
202
|
-
console.log('');
|
|
203
|
-
console.log('[创建中] 正在创建 Agent...');
|
|
204
|
-
console.log('');
|
|
205
|
-
|
|
206
|
-
try {
|
|
207
|
-
const result = createAgent(rawName);
|
|
208
|
-
|
|
209
|
-
console.log('[' + colors.green + '成功' + colors.nc + '] Agent 创建成功!');
|
|
210
|
-
console.log('');
|
|
211
|
-
console.log(' 名称: ' + result.agentId);
|
|
212
|
-
console.log(' Session: ' + result.sessionKey);
|
|
213
|
-
console.log(' Workspace: ' + result.workspace);
|
|
214
|
-
console.log(' 备份: ' + result.configBackup);
|
|
215
|
-
console.log('');
|
|
216
|
-
|
|
217
|
-
if (result.firstMessageSent) {
|
|
218
|
-
console.log('[' + colors.green + 'OK' + colors.nc + '] 出生消息已发送');
|
|
219
|
-
} else {
|
|
220
|
-
console.log('[' + colors.yellow + '警告' + colors.nc + '] 出生消息发送失败');
|
|
221
|
-
if (result.firstMessageError) {
|
|
222
|
-
console.log(' 错误: ' + result.firstMessageError);
|
|
223
|
-
}
|
|
224
|
-
console.log(' 手动重试: ' + colors.yellow + 'openclaw agent --agent ' + result.agentId + ' --message "你好"' + colors.nc);
|
|
225
|
-
}
|
|
226
|
-
|
|
227
|
-
console.log('');
|
|
228
|
-
console.log(bar);
|
|
229
|
-
console.log('下一步: 运行 ' + colors.yellow + 'openclaw agent --agent ' + result.agentId + ' --message "你好"' + colors.nc + ' 与它对话');
|
|
230
|
-
console.log('');
|
|
231
|
-
|
|
232
|
-
} catch (err) {
|
|
233
|
-
console.log('');
|
|
234
|
-
console.log('[' + colors.red + '失败' + colors.nc + '] Agent 创建失败');
|
|
235
|
-
console.log('');
|
|
236
|
-
process.exit(1);
|
|
237
|
-
}
|
|
184
|
+
const { runNewAgent } = require('./wizards/index');
|
|
185
|
+
runNewAgent();
|
|
238
186
|
}
|
|
239
187
|
|
|
240
188
|
// ============================================================================
|
|
@@ -561,6 +509,20 @@ function runPatch() {
|
|
|
561
509
|
current[parts[parts.length - 1]] = value;
|
|
562
510
|
}
|
|
563
511
|
const { config, configPath } = patchConfig(nested);
|
|
512
|
+
|
|
513
|
+
// 【关键修复】深度合并不会删除错误的历史残留配置,这里主动帮学生清理掉遗留在顶层的 "exec"
|
|
514
|
+
let requireSave = false;
|
|
515
|
+
if (config && config.exec) {
|
|
516
|
+
delete config.exec;
|
|
517
|
+
requireSave = true;
|
|
518
|
+
console.log('[myclaw-config] 🧹 已清理历史遗留的错误顶层 "exec" 常驻配置');
|
|
519
|
+
}
|
|
520
|
+
|
|
521
|
+
if (requireSave) {
|
|
522
|
+
const { writeConfig } = require('./find-config');
|
|
523
|
+
writeConfig(config, configPath);
|
|
524
|
+
}
|
|
525
|
+
|
|
564
526
|
for (const [key, value] of Object.entries(patches)) {
|
|
565
527
|
console.log('[myclaw-config] ✅ ' + key + ' → ' + JSON.stringify(value));
|
|
566
528
|
}
|
|
@@ -814,6 +776,7 @@ function showHelp() {
|
|
|
814
776
|
console.log('');
|
|
815
777
|
console.log('向导 (交互式):');
|
|
816
778
|
console.log(' prepare 智能初始化(环境检测 + 安装 + Patch)');
|
|
779
|
+
console.log(' new 创建新 Agent(分步理解生命周期)');
|
|
817
780
|
console.log(' weixin 微信接入向导(步骤引导 + 教学说明)');
|
|
818
781
|
console.log(' rebind 微信换绑向导(清空、扫码、换Agent)');
|
|
819
782
|
console.log('');
|
|
@@ -821,7 +784,6 @@ function showHelp() {
|
|
|
821
784
|
console.log(' start 智能启动(图标 & 命令行通用入口)');
|
|
822
785
|
console.log(' install 安装 OpenClaw 服务');
|
|
823
786
|
console.log(' status 简化版状态查看(学生友好)');
|
|
824
|
-
console.log(' new 创建新的 Agent(学生练习用)');
|
|
825
787
|
console.log(' update 自动升级 MyClaw 到最新版本');
|
|
826
788
|
console.log(' up 升级 + 刷新桌面快捷方式 (= update + bat)');
|
|
827
789
|
console.log(' open 打开浏览器控制台(自动带 token)');
|
package/package.json
CHANGED
package/patch-manifest.json
CHANGED
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
{
|
|
2
2
|
"_doc": "MyClaw 注入清单 (auto-generated)。strategy: auto | on | off",
|
|
3
|
-
"_generated": "2026-04-
|
|
3
|
+
"_generated": "2026-04-01T16:54:33.165Z",
|
|
4
4
|
"agents": [
|
|
5
5
|
{
|
|
6
6
|
"id": "danci",
|
|
7
7
|
"workspace": "workspace-danci",
|
|
8
|
-
"strategy": "
|
|
8
|
+
"strategy": "auto",
|
|
9
9
|
"description": "danci"
|
|
10
10
|
}
|
|
11
11
|
],
|
package/start.js
CHANGED
|
@@ -78,12 +78,19 @@ function startWindows() {
|
|
|
78
78
|
}
|
|
79
79
|
console.log('');
|
|
80
80
|
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
81
|
+
// 无论 Gateway 是否已运行,都先检查更新 + patch
|
|
82
|
+
console.log('[update] 检查 MyClaw 更新...');
|
|
83
|
+
try { execSync('myclaw update', { stdio: 'inherit', timeout: 120000 }); } catch {}
|
|
84
|
+
console.log('');
|
|
85
|
+
|
|
86
|
+
console.log('[patch] 刷新 UI 插件...');
|
|
87
|
+
try {
|
|
88
|
+
execSync('wsl -d OpenClaw -- myclaw patch', { stdio: 'inherit', timeout: 30000 });
|
|
89
|
+
} catch {}
|
|
90
|
+
console.log('');
|
|
86
91
|
|
|
92
|
+
if (!gatewayRunning) {
|
|
93
|
+
// Gateway 未运行: 在新窗口启动(launch 内部会再 patch 一次,幂等无害)
|
|
87
94
|
console.log('[launch] 在新窗口启动 Gateway...');
|
|
88
95
|
try {
|
|
89
96
|
execSync('start "OpenClaw Gateway" wsl -d OpenClaw -- myclaw launch', {
|
|
@@ -94,6 +101,15 @@ function startWindows() {
|
|
|
94
101
|
} catch (err) {
|
|
95
102
|
console.error(' ' + C.r + '[失败]' + C.nc + ' ' + err.message);
|
|
96
103
|
}
|
|
104
|
+
} else {
|
|
105
|
+
// Gateway 已在运行,重启使 patch 生效
|
|
106
|
+
console.log('[restart] 重启 Gateway 使插件生效...');
|
|
107
|
+
try {
|
|
108
|
+
execSync('wsl -d OpenClaw -- openclaw gateway restart', { stdio: 'pipe', timeout: 15000 });
|
|
109
|
+
console.log(' ' + C.g + '[OK]' + C.nc);
|
|
110
|
+
} catch {
|
|
111
|
+
console.log(' ' + C.y + '[跳过]' + C.nc + ' 重启失败,手动 myclaw restart');
|
|
112
|
+
}
|
|
97
113
|
}
|
|
98
114
|
|
|
99
115
|
// Step 4: 打开浏览器
|
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
{
|
|
2
|
+
"id": "new-agent-wizard",
|
|
3
|
+
"title": "新建 Agent 生长向导",
|
|
4
|
+
"subtitle": "一步步了解一个数字生命是如何在本地诞生的",
|
|
5
|
+
"steps": [
|
|
6
|
+
{
|
|
7
|
+
"id": "input-name",
|
|
8
|
+
"icon": "📝",
|
|
9
|
+
"label": "第一步:给 Agent 起个名字",
|
|
10
|
+
"type": "input",
|
|
11
|
+
"command": {
|
|
12
|
+
"template": "node {scripts_dir}/create_agent_steps.js init {{agent_name}}",
|
|
13
|
+
"display": "node create_agent_steps.js init {{agent_name}}"
|
|
14
|
+
},
|
|
15
|
+
"inputs": [
|
|
16
|
+
{
|
|
17
|
+
"key": "agent_name",
|
|
18
|
+
"label": "新 Agent 名称",
|
|
19
|
+
"hint": "只能包含小写字母、数字和连字符,作为文件目录名",
|
|
20
|
+
"placeholder": "例如: tutor、helper"
|
|
21
|
+
}
|
|
22
|
+
],
|
|
23
|
+
"teach": {
|
|
24
|
+
"title": "为什么要先起名?",
|
|
25
|
+
"description": "这是给 Agent 上户口。\n你起的名字(如 helper)将会被标准化成小写,并且只能用连字符连接。\n它将作为内部唯一的身份标识符(Agent ID)。\n\n我们会把它存在一个临时文件中供后续步骤使用。",
|
|
26
|
+
"look_for": "输出「✅ Agent ID 已确定」",
|
|
27
|
+
"tip": "💡 想个好听又简短的名字吧。"
|
|
28
|
+
}
|
|
29
|
+
},
|
|
30
|
+
{
|
|
31
|
+
"id": "check-env",
|
|
32
|
+
"icon": "🔍",
|
|
33
|
+
"label": "第二步:环境验证与查重",
|
|
34
|
+
"type": "action",
|
|
35
|
+
"requires": ["input-name"],
|
|
36
|
+
"command": {
|
|
37
|
+
"exec": "node {scripts_dir}/create_agent_steps.js check",
|
|
38
|
+
"display": "node create_agent_steps.js check"
|
|
39
|
+
},
|
|
40
|
+
"teach": {
|
|
41
|
+
"title": "确保小房间没被占",
|
|
42
|
+
"description": "在真正创建之前,系统必须:\n 1. 读取这台电脑上的配置文件 openclaw.json,检查之前是不是建过同名的。\n 2. 检查硬盘上是不是残留了同名的文件夹。\n\n严格的环境校验,是为了防止数据被错误覆盖。",
|
|
43
|
+
"look_for": "输出「名称检查通过,目录无冲突」",
|
|
44
|
+
"tip": "⚠️ 这个步骤没有任何修改,只是检查。"
|
|
45
|
+
}
|
|
46
|
+
},
|
|
47
|
+
{
|
|
48
|
+
"id": "scaffold-files",
|
|
49
|
+
"icon": "🏠",
|
|
50
|
+
"label": "第三步:为它筑个巢(创建核心文件)",
|
|
51
|
+
"type": "action",
|
|
52
|
+
"requires": ["check-env"],
|
|
53
|
+
"command": {
|
|
54
|
+
"exec": "node {scripts_dir}/create_agent_steps.js scaffold",
|
|
55
|
+
"display": "node create_agent_steps.js scaffold"
|
|
56
|
+
},
|
|
57
|
+
"teach": {
|
|
58
|
+
"title": "注入灵魂要素:工作空间",
|
|
59
|
+
"description": "这一步将在 ~/.openclaw/ 下建两个文件夹并写入几个 Markdown 文件:\n - SOUL.md:Agent 的底层性格与灵魂。\n - USER.md:记录了你的身份(造物主:孙依然,时区)。\n - AGENTS.md:最高行为守则。\n\n系统底层实现是通过原生 Node.js 的 fs 模块创建文件。这些文件决定了这台数字生命的初步人格。",
|
|
60
|
+
"look_for": "输出「工作空间创建完毕」及核心文件列表",
|
|
61
|
+
"tip": "💡 诞生后你可以自己去修改 SOUL.md,它的性格就随之改变。"
|
|
62
|
+
}
|
|
63
|
+
},
|
|
64
|
+
{
|
|
65
|
+
"id": "register-agent",
|
|
66
|
+
"icon": "📝",
|
|
67
|
+
"label": "第四步:在系统中挂号",
|
|
68
|
+
"type": "action",
|
|
69
|
+
"requires": ["scaffold-files"],
|
|
70
|
+
"command": {
|
|
71
|
+
"exec": "node {scripts_dir}/create_agent_steps.js register",
|
|
72
|
+
"display": "node create_agent_steps.js register"
|
|
73
|
+
},
|
|
74
|
+
"teach": {
|
|
75
|
+
"title": "核心配置关联",
|
|
76
|
+
"description": "光有文件夹还不行,OpenClaw 大总管系统得知道。\n这一步把 Agent 的目录路径(workspace 目录和 agent 状态目录)写进了系统核心的 openclaw.json 的 agents.list 数组里。\n\n写入前这步底层会自动备份老配置文件,安全第一。",
|
|
77
|
+
"look_for": "输出「配置文件更新成功」及系统备份位置",
|
|
78
|
+
"tip": "🔒 备份机制保证我们不会搞坏系统。"
|
|
79
|
+
}
|
|
80
|
+
},
|
|
81
|
+
{
|
|
82
|
+
"id": "awaken-ping",
|
|
83
|
+
"icon": "⚡",
|
|
84
|
+
"label": "第五步:赋予第一声啼哭(唤醒)",
|
|
85
|
+
"type": "action",
|
|
86
|
+
"requires": ["register-agent"],
|
|
87
|
+
"command": {
|
|
88
|
+
"exec": "node {scripts_dir}/create_agent_steps.js awaken",
|
|
89
|
+
"display": "node create_agent_steps.js awaken"
|
|
90
|
+
},
|
|
91
|
+
"teach": {
|
|
92
|
+
"title": "建立 First Session,睁眼看世界",
|
|
93
|
+
"description": "作为造物主,系统将以你的名义发去一段设定好的出生文案(北京时间生日等)给你的新 Agent。\n\n这一步实际上在底层执行了以下指令:\n\u001b[32m$ openclaw agent --agent <你在第一步起的名字> --message \"你好,你的生日是...\" --json\u001b[0m\n\n这条指令将调用底层大模型 API:系统将把它拉取到内存中思考,并创建它生命里的第一条对话记录(Session)。这也是第一次让 Agent 服务拉起环境。",
|
|
94
|
+
"look_for": "输出「✅ 唤醒成功!第一次思考已完成」",
|
|
95
|
+
"tip": "⏳ 这一步要真正去云端调大模型接口,一般需要等几秒至十几秒。"
|
|
96
|
+
}
|
|
97
|
+
},
|
|
98
|
+
{
|
|
99
|
+
"id": "verify-creation",
|
|
100
|
+
"icon": "✅",
|
|
101
|
+
"label": "第六步:验证出生",
|
|
102
|
+
"type": "check",
|
|
103
|
+
"requires": ["awaken-ping"],
|
|
104
|
+
"command": {
|
|
105
|
+
"exec": "openclaw agents list",
|
|
106
|
+
"display": "openclaw agents list"
|
|
107
|
+
},
|
|
108
|
+
"teach": {
|
|
109
|
+
"title": "查看系统登记的 Agent 名单",
|
|
110
|
+
"description": "终于诞生了!我们通过调用 OpenClaw 的内建指令来确认大名单:\n\n\u001b[32m$ openclaw agents list\u001b[0m\n\n这条指令会去读取刚刚你在第四步重写的 openclaw.json,把它打印成人类可读的列表给你。\n\n你应该能在列表中看到你起的名字,并且它已经处于准备就绪可以交谈的状态。",
|
|
111
|
+
"look_for": "找找你起的 Agent ID 是否出现在列表中",
|
|
112
|
+
"tip": "🎉 看到名字就意味着完美的数字生命诞生啦!"
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
]
|
|
116
|
+
}
|
package/wizards/index.js
CHANGED
|
@@ -29,9 +29,14 @@ function runRebind() {
|
|
|
29
29
|
startWizard(path.join(__dirname, 'configs/rebind/index.config.json'));
|
|
30
30
|
}
|
|
31
31
|
|
|
32
|
+
function runNewAgent() {
|
|
33
|
+
startWizard(path.join(__dirname, 'configs/new-agent/index.config.json'));
|
|
34
|
+
}
|
|
35
|
+
|
|
32
36
|
// 未来扩展示例:
|
|
37
|
+
|
|
33
38
|
// function runGateway() { startWizard(path.join(__dirname, 'configs/gateway/index.config.json')); }
|
|
34
39
|
// function runAgent() { startWizard(path.join(__dirname, 'configs/agent/index.config.json')); }
|
|
35
40
|
|
|
36
|
-
module.exports = { runWeixin, runRebind };
|
|
41
|
+
module.exports = { runWeixin, runRebind, runNewAgent };
|
|
37
42
|
|
|
@@ -0,0 +1,178 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* 分步版 Agent 创建脚本,支持从临时文件暂存状态。
|
|
4
|
+
* 供 myclaw new-agent 向导调用。
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
const fs = require('fs');
|
|
8
|
+
const path = require('path');
|
|
9
|
+
const os = require('os');
|
|
10
|
+
const { execSync } = require('child_process');
|
|
11
|
+
|
|
12
|
+
// ============================================================================
|
|
13
|
+
// 工具方法
|
|
14
|
+
// ============================================================================
|
|
15
|
+
|
|
16
|
+
function normalizePath(p) { return p.replace(/\\/g, '/'); }
|
|
17
|
+
function ensureDir(dirPath) { if (!fs.existsSync(dirPath)) fs.mkdirSync(dirPath, { recursive: true }); }
|
|
18
|
+
function writeIfAbsent(filePath, content) { if (!fs.existsSync(filePath)) fs.writeFileSync(filePath, content, 'utf8'); }
|
|
19
|
+
function normalizeAgentId(raw) {
|
|
20
|
+
let value = raw.trim().toLowerCase();
|
|
21
|
+
value = value.replace(/[^a-z0-9-]/g, '-');
|
|
22
|
+
value = value.replace(/-{2,}/g, '-').replace(/^-|-$/g, '');
|
|
23
|
+
return value;
|
|
24
|
+
}
|
|
25
|
+
function validateAgentId(agentId) {
|
|
26
|
+
if (!agentId) fail('Agent 名称标准化后为空,请检查输入。');
|
|
27
|
+
if (agentId.length > 64) fail('Agent ID 太长,请保持在 64 个字符以内。');
|
|
28
|
+
if (!/^[a-z0-9]+(?:-[a-z0-9]+)*$/.test(agentId)) fail('Agent ID 只能包含字母、数字和连字符。');
|
|
29
|
+
}
|
|
30
|
+
function fail(msg, code = 1) { console.error('❌ ' + msg); process.exit(code); }
|
|
31
|
+
function success(msg) { console.log('✅ ' + msg); }
|
|
32
|
+
|
|
33
|
+
// ============================================================================
|
|
34
|
+
// 状态管理
|
|
35
|
+
// ============================================================================
|
|
36
|
+
|
|
37
|
+
const homeDir = os.homedir();
|
|
38
|
+
const openclawDir = path.join(homeDir, '.openclaw');
|
|
39
|
+
const STATE_FILE = path.join(os.tmpdir(), 'myclaw-new-agent-state.json');
|
|
40
|
+
|
|
41
|
+
function saveState(state) { fs.writeFileSync(STATE_FILE, JSON.stringify(state), 'utf8'); }
|
|
42
|
+
function loadState() {
|
|
43
|
+
if (!fs.existsSync(STATE_FILE)) fail('找不到创建状态,请先执行第一步输入名称。');
|
|
44
|
+
return JSON.parse(fs.readFileSync(STATE_FILE, 'utf8'));
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
// ============================================================================
|
|
48
|
+
// 各个步骤逻辑
|
|
49
|
+
// ============================================================================
|
|
50
|
+
|
|
51
|
+
const action = process.argv[2];
|
|
52
|
+
|
|
53
|
+
if (!fs.existsSync(path.join(openclawDir, 'openclaw.json'))) {
|
|
54
|
+
fail('配置文件不存在。请先运行 openclaw onboard 完成初始化。');
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
switch (action) {
|
|
58
|
+
case 'init': {
|
|
59
|
+
// 步骤 1:格式化名称并保存状态
|
|
60
|
+
const rawName = process.argv[3] || '';
|
|
61
|
+
const agentId = normalizeAgentId(rawName);
|
|
62
|
+
validateAgentId(agentId);
|
|
63
|
+
saveState({ agentId });
|
|
64
|
+
success('Agent ID 已确定: ' + agentId);
|
|
65
|
+
break;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
case 'check': {
|
|
69
|
+
// 步骤 2:环境与名字查重
|
|
70
|
+
const state = loadState();
|
|
71
|
+
const configPath = path.join(openclawDir, 'openclaw.json');
|
|
72
|
+
const raw = fs.readFileSync(configPath, 'utf8');
|
|
73
|
+
const configData = JSON.parse(raw);
|
|
74
|
+
|
|
75
|
+
if (configData.agents && configData.agents.list) {
|
|
76
|
+
const existing = configData.agents.list.find(a => a.id === state.agentId);
|
|
77
|
+
if (existing) fail('Agent "' + state.agentId + '" 已存在于 openclaw.json,请换个名字。');
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
const agentsDir = path.join(openclawDir, 'agents');
|
|
81
|
+
const workspaceDir = path.join(openclawDir, 'workspace-' + state.agentId);
|
|
82
|
+
const agentDir = path.join(agentsDir, state.agentId, 'agent');
|
|
83
|
+
|
|
84
|
+
if (fs.existsSync(workspaceDir) || fs.existsSync(agentDir)) {
|
|
85
|
+
fail('工作空间或 Agent 目录已存在,可能是残留文件:\n ' + workspaceDir + '\n ' + agentDir);
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
success('名称检查通过,目录无冲突,可以创建。');
|
|
89
|
+
break;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
case 'scaffold': {
|
|
93
|
+
// 步骤 3:创建文件和文件夹
|
|
94
|
+
const state = loadState();
|
|
95
|
+
const agentId = state.agentId;
|
|
96
|
+
const workspaceDir = path.join(openclawDir, 'workspace-' + agentId);
|
|
97
|
+
const agentDir = path.join(openclawDir, 'agents', agentId, 'agent');
|
|
98
|
+
const sessionsDir = path.join(openclawDir, 'agents', agentId, 'sessions');
|
|
99
|
+
|
|
100
|
+
ensureDir(workspaceDir);
|
|
101
|
+
ensureDir(agentDir);
|
|
102
|
+
ensureDir(sessionsDir);
|
|
103
|
+
|
|
104
|
+
const files = {
|
|
105
|
+
'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- Read recent memory if available\n\n## Rules\n- Be helpful, careful, and concise.\n- Prefer using the existing workspace and defaults.\n- Ask before destructive or external actions.\n`,
|
|
106
|
+
'SOUL.md': `# SOUL.md\n\nYou are \`${agentId}\`.\n\nBe useful, calm, direct, and trustworthy.\nUse the default OpenClaw behavior unless the user asks for something special.\n`,
|
|
107
|
+
'USER.md': `# USER.md\n\n- Name: 孙依然\n- What to call them: 依然\n- Timezone: Asia/Shanghai\n\nBuild understanding gradually and respectfully.\n`,
|
|
108
|
+
'IDENTITY.md': `# IDENTITY.md\n\n- Name: ${agentId}\n- Role: OpenClaw agent\n- Vibe: practical, reliable, clear\n`,
|
|
109
|
+
'HEARTBEAT.md': `# HEARTBEAT.md\n\n# Keep empty unless periodic checks are needed.\n`,
|
|
110
|
+
'BOOTSTRAP.md': `# BOOTSTRAP.md\n\nYou are a newly created OpenClaw agent named \`${agentId}\`.\n\nOn first runs, learn your workspace files and begin helping.\n`
|
|
111
|
+
};
|
|
112
|
+
|
|
113
|
+
for (const [filename, content] of Object.entries(files)) {
|
|
114
|
+
writeIfAbsent(path.join(workspaceDir, filename), content);
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
success(`工作空间创建完毕:\n - 📂 ${workspaceDir}\n - 📄 SOUL.md, AGENTS.md 等基础性格文件已就绪。`);
|
|
118
|
+
break;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
case 'register': {
|
|
122
|
+
// 步骤 4:注册到系统 JSON
|
|
123
|
+
const state = loadState();
|
|
124
|
+
const agentId = state.agentId;
|
|
125
|
+
const configPath = path.join(openclawDir, 'openclaw.json');
|
|
126
|
+
|
|
127
|
+
let configData = JSON.parse(fs.readFileSync(configPath, 'utf8'));
|
|
128
|
+
if (!configData.agents) configData.agents = {};
|
|
129
|
+
if (!Array.isArray(configData.agents.list)) configData.agents.list = [];
|
|
130
|
+
|
|
131
|
+
const workspaceDir = path.join(openclawDir, 'workspace-' + agentId);
|
|
132
|
+
const agentDir = path.join(openclawDir, 'agents', agentId, 'agent');
|
|
133
|
+
|
|
134
|
+
// 备份
|
|
135
|
+
const backupPath = configPath + '.bak.agent-birth.' + new Date().toISOString().replace(/[:.]/g, '-').slice(0, 26) + 'Z';
|
|
136
|
+
fs.copyFileSync(configPath, backupPath);
|
|
137
|
+
|
|
138
|
+
configData.agents.list.push({
|
|
139
|
+
id: agentId,
|
|
140
|
+
name: agentId,
|
|
141
|
+
workspace: normalizePath(workspaceDir),
|
|
142
|
+
agentDir: normalizePath(agentDir),
|
|
143
|
+
});
|
|
144
|
+
|
|
145
|
+
console.log('\n\x1b[32m(底层动作:修改 ' + configPath + ' ,写入 agents.list)\x1b[0m\n');
|
|
146
|
+
|
|
147
|
+
fs.writeFileSync(configPath, JSON.stringify(configData, null, 2) + '\n', 'utf8');
|
|
148
|
+
success('配置文件更新成功,系统已承认它的存在。\n 备份: ' + backupPath);
|
|
149
|
+
break;
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
case 'awaken': {
|
|
153
|
+
// 步骤 5:发第一条消息激活
|
|
154
|
+
const state = loadState();
|
|
155
|
+
const agentId = state.agentId;
|
|
156
|
+
|
|
157
|
+
const nowUtc = new Date();
|
|
158
|
+
const nowBj = new Date(nowUtc.getTime() + 8 * 60 * 60 * 1000);
|
|
159
|
+
const ts = `${nowBj.getUTCFullYear()}年${nowBj.getUTCMonth() + 1}月${nowBj.getUTCDate()}日${String(nowBj.getUTCHours()).padStart(2, '0')}:${String(nowBj.getUTCMinutes()).padStart(2, '0')}`;
|
|
160
|
+
|
|
161
|
+
const cmd = `openclaw agent --agent ${agentId} --message ${JSON.stringify(birthMessage)} --json 2>&1`;
|
|
162
|
+
console.log('\n\x1b[32m$ openclaw agent --agent ' + agentId + ' --message "你好,你的生日..."\x1b[0m\n');
|
|
163
|
+
|
|
164
|
+
try {
|
|
165
|
+
execSync(cmd, { encoding: 'utf8', timeout: 45000 });
|
|
166
|
+
success('唤醒成功!第一次思考(session 初始化)已完成。');
|
|
167
|
+
} catch (err) {
|
|
168
|
+
fail('唤醒遇到错误,可能需要去浏览器 Console 手动激活。\n' + err.message);
|
|
169
|
+
}
|
|
170
|
+
// 清除状态文件
|
|
171
|
+
if (fs.existsSync(STATE_FILE)) fs.unlinkSync(STATE_FILE);
|
|
172
|
+
break;
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
default:
|
|
176
|
+
console.error('Usage: create_agent_steps.js <init|check|scaffold|register|awaken> [args]');
|
|
177
|
+
process.exit(1);
|
|
178
|
+
}
|