@aiyiran/myclaw 1.0.242 → 1.0.243
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-artifacts.js +139 -37
- package/index.js +75 -5
- package/package.json +1 -1
- package/patches/patch.js +20 -22
- package/server/sync_workspace.py +138 -0
|
@@ -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
|
-
// ═══
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
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
|
-
|
|
57
|
-
|
|
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
|
-
|
|
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
|
|
201
|
-
|
|
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
|
|
207
|
-
|
|
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 (
|
|
224
|
-
console.error('[myclaw-artifacts] 加载失败:', err);
|
|
316
|
+
.catch(function () {
|
|
225
317
|
if (contentEl) {
|
|
226
|
-
contentEl.innerHTML = '<div style="text-align:center;padding:32px;color:#
|
|
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:
|
|
715
|
+
workspace: cachedConfig ? cachedConfig.workspaceName : 'workspace',
|
|
627
716
|
cover_path: coverSelect.value || '',
|
|
628
717
|
entry_path: entrySelect.value || '',
|
|
629
|
-
claw:
|
|
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;
|
|
926
|
+
qrCanvas.style.cssText = 'width:140px;height:140px;border-radius:6px;';
|
|
838
927
|
try {
|
|
839
|
-
generateQR(qrCanvas, data.permanent_url,
|
|
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
|
|
862
|
-
var
|
|
863
|
-
showcaseBtn.href = 'https://www.yiranlaoshi.com/showcase?workspace=' +
|
|
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' +
|
|
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
|
-
|
|
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
|
-
|
|
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(
|
|
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
|
-
|
|
1304
|
-
|
|
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') {
|
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
package/patches/patch.js
CHANGED
|
@@ -232,9 +232,9 @@ function patch() {
|
|
|
232
232
|
|
|
233
233
|
// 检查是否需要 patch(分别检测每项,避免部分已 patch 导致整体跳过)
|
|
234
234
|
const needsMicrophonePatch = content.includes('microphone=()');
|
|
235
|
-
const needsFrameSrc = !content.includes('"frame-src');
|
|
236
|
-
const needsConnectSrc = content.includes(
|
|
237
|
-
const needsFrameAncestors = content.includes(
|
|
235
|
+
const needsFrameSrc = !content.includes('"frame-src *');
|
|
236
|
+
const needsConnectSrc = !content.includes("connect-src *");
|
|
237
|
+
const needsFrameAncestors = !content.includes("frame-ancestors *");
|
|
238
238
|
const needsAnyCspPatch = needsFrameSrc || needsConnectSrc || needsFrameAncestors;
|
|
239
239
|
|
|
240
240
|
if (needsMicrophonePatch || needsAnyCspPatch) {
|
|
@@ -253,31 +253,29 @@ function patch() {
|
|
|
253
253
|
console.log('[myclaw-patch] ✅ 已修复 Permissions-Policy (microphone): ' + f);
|
|
254
254
|
}
|
|
255
255
|
|
|
256
|
-
// Patch 2: CSP -
|
|
256
|
+
// Patch 2: CSP - 全部放宽到最宽松模式
|
|
257
257
|
const cspPatches = [];
|
|
258
|
-
//
|
|
258
|
+
// frame-src → 允许所有
|
|
259
259
|
if (needsFrameSrc) {
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
260
|
+
if (content.includes('"frame-src')) {
|
|
261
|
+
content = content.replace(/\"frame-src[^"]*\"/g, '"frame-src *"');
|
|
262
|
+
} else {
|
|
263
|
+
content = content.replace(
|
|
264
|
+
'"default-src \'self\'"',
|
|
265
|
+
'"default-src \'self\'",\n\t\t"frame-src *"'
|
|
266
|
+
);
|
|
267
|
+
}
|
|
268
|
+
cspPatches.push('frame-src *');
|
|
265
269
|
}
|
|
266
|
-
//
|
|
270
|
+
// connect-src → 允许所有(含 http://127.0.0.1 本地 API)
|
|
267
271
|
if (needsConnectSrc) {
|
|
268
|
-
content = content.replace(
|
|
269
|
-
|
|
270
|
-
'"connect-src \'self\' https: ws: wss: https://cdn.yiranlaoshi.com"'
|
|
271
|
-
);
|
|
272
|
-
cspPatches.push('connect-src');
|
|
272
|
+
content = content.replace(/\"connect-src[^"]*\"/g, '"connect-src *"');
|
|
273
|
+
cspPatches.push('connect-src *');
|
|
273
274
|
}
|
|
274
|
-
//
|
|
275
|
+
// frame-ancestors → 允许所有
|
|
275
276
|
if (needsFrameAncestors) {
|
|
276
|
-
content = content.replace(
|
|
277
|
-
|
|
278
|
-
'"frame-ancestors \'self\' https:"'
|
|
279
|
-
);
|
|
280
|
-
cspPatches.push('frame-ancestors');
|
|
277
|
+
content = content.replace(/\"frame-ancestors[^"]*\"/g, '"frame-ancestors *"');
|
|
278
|
+
cspPatches.push('frame-ancestors *');
|
|
281
279
|
}
|
|
282
280
|
if (cspPatches.length > 0) {
|
|
283
281
|
console.log('[myclaw-patch] ✅ 已修复 CSP (' + cspPatches.join(', ') + '): ' + f);
|
package/server/sync_workspace.py
CHANGED
|
@@ -5,6 +5,9 @@ import platform
|
|
|
5
5
|
from datetime import datetime, timezone, timedelta
|
|
6
6
|
import json
|
|
7
7
|
import time
|
|
8
|
+
import threading
|
|
9
|
+
from http.server import HTTPServer, BaseHTTPRequestHandler
|
|
10
|
+
from urllib.parse import urlparse, parse_qs
|
|
8
11
|
from watchdog.observers import Observer
|
|
9
12
|
from watchdog.events import FileSystemEventHandler
|
|
10
13
|
from qiniu import Auth, put_file_v2, CdnManager
|
|
@@ -33,9 +36,22 @@ cdn_manager = CdnManager(q)
|
|
|
33
36
|
|
|
34
37
|
|
|
35
38
|
class MyHandler(FileSystemEventHandler):
|
|
39
|
+
# 需要忽略的文件模式
|
|
40
|
+
IGNORE_PATTERNS = {'.myclaw-sync.pid', '.DS_Store'}
|
|
41
|
+
|
|
36
42
|
def _is_file_event(self, event):
|
|
37
43
|
return not getattr(event, "is_directory", False)
|
|
38
44
|
|
|
45
|
+
def _should_ignore(self, filepath):
|
|
46
|
+
"""忽略 PID 文件、隐藏文件等非业务文件"""
|
|
47
|
+
basename = os.path.basename(filepath)
|
|
48
|
+
if basename in self.IGNORE_PATTERNS:
|
|
49
|
+
return True
|
|
50
|
+
# 忽略 .openclaw 根目录下的隐藏文件(非 workspace 目录内的)
|
|
51
|
+
if basename.startswith('.') and 'workspace' not in filepath:
|
|
52
|
+
return True
|
|
53
|
+
return False
|
|
54
|
+
|
|
39
55
|
# def on_created(self, event):
|
|
40
56
|
# if not self._is_file_event(event):
|
|
41
57
|
# return
|
|
@@ -45,18 +61,24 @@ class MyHandler(FileSystemEventHandler):
|
|
|
45
61
|
def on_modified(self, event):
|
|
46
62
|
if not self._is_file_event(event):
|
|
47
63
|
return
|
|
64
|
+
if self._should_ignore(event.src_path):
|
|
65
|
+
return
|
|
48
66
|
print(f"🟡 修改: {event.src_path}")
|
|
49
67
|
file_gen(event.src_path, "add")
|
|
50
68
|
|
|
51
69
|
def on_deleted(self, event):
|
|
52
70
|
if not self._is_file_event(event):
|
|
53
71
|
return
|
|
72
|
+
if self._should_ignore(event.src_path):
|
|
73
|
+
return
|
|
54
74
|
print(f"🔴 删除: {event.src_path}")
|
|
55
75
|
file_gen(event.src_path, "delete")
|
|
56
76
|
|
|
57
77
|
def on_moved(self, event):
|
|
58
78
|
if not self._is_file_event(event):
|
|
59
79
|
return
|
|
80
|
+
if self._should_ignore(event.src_path):
|
|
81
|
+
return
|
|
60
82
|
print(f"🔵 移动: {event.src_path} -> {getattr(event, 'dest_path', '')}")
|
|
61
83
|
file_gen(event.src_path, "delete")
|
|
62
84
|
if getattr(event, 'dest_path', ''):
|
|
@@ -268,11 +290,124 @@ def sync_all(workspace_id):
|
|
|
268
290
|
return True
|
|
269
291
|
|
|
270
292
|
|
|
293
|
+
# ═══════════════════════════════════════════════════════════════
|
|
294
|
+
# 内嵌 HTTP API 服务
|
|
295
|
+
# ═══════════════════════════════════════════════════════════════
|
|
296
|
+
|
|
297
|
+
API_PORT = int(os.environ.get("MYCLAW_API_PORT", "18800"))
|
|
298
|
+
|
|
299
|
+
|
|
300
|
+
class MyclawAPIHandler(BaseHTTPRequestHandler):
|
|
301
|
+
"""轻量 HTTP API,供前端获取作品列表和配置"""
|
|
302
|
+
|
|
303
|
+
def _cors_headers(self):
|
|
304
|
+
self.send_header('Access-Control-Allow-Origin', '*')
|
|
305
|
+
self.send_header('Access-Control-Allow-Methods', 'GET, OPTIONS')
|
|
306
|
+
self.send_header('Access-Control-Allow-Headers', 'Content-Type')
|
|
307
|
+
self.send_header('Cache-Control', 'no-cache, no-store, must-revalidate')
|
|
308
|
+
|
|
309
|
+
def do_OPTIONS(self):
|
|
310
|
+
self.send_response(200)
|
|
311
|
+
self._cors_headers()
|
|
312
|
+
self.end_headers()
|
|
313
|
+
|
|
314
|
+
def do_GET(self):
|
|
315
|
+
parsed = urlparse(self.path)
|
|
316
|
+
path = parsed.path.rstrip('/')
|
|
317
|
+
params = parse_qs(parsed.query)
|
|
318
|
+
|
|
319
|
+
if path == '/api/config':
|
|
320
|
+
return self._handle_config()
|
|
321
|
+
elif path == '/api/artifacts':
|
|
322
|
+
return self._handle_artifacts(params)
|
|
323
|
+
else:
|
|
324
|
+
self.send_response(404)
|
|
325
|
+
self._cors_headers()
|
|
326
|
+
self.send_header('Content-Type', 'application/json; charset=utf-8')
|
|
327
|
+
self.end_headers()
|
|
328
|
+
self.wfile.write(json.dumps({"error": "not found"}).encode('utf-8'))
|
|
329
|
+
|
|
330
|
+
def _handle_config(self):
|
|
331
|
+
"""GET /api/config → 返回 claw 名称和 CDN base_url"""
|
|
332
|
+
data = {
|
|
333
|
+
"claw": claw,
|
|
334
|
+
"base_url": BASE_URL,
|
|
335
|
+
}
|
|
336
|
+
self.send_response(200)
|
|
337
|
+
self._cors_headers()
|
|
338
|
+
self.send_header('Content-Type', 'application/json; charset=utf-8')
|
|
339
|
+
self.end_headers()
|
|
340
|
+
self.wfile.write(json.dumps(data, ensure_ascii=False).encode('utf-8'))
|
|
341
|
+
|
|
342
|
+
def _handle_artifacts(self, params):
|
|
343
|
+
"""GET /api/artifacts?workspace=xxx → 直接从磁盘读取 __MY_ARTIFACTS__.json"""
|
|
344
|
+
workspace = params.get('workspace', [''])[0]
|
|
345
|
+
if not workspace:
|
|
346
|
+
self.send_response(400)
|
|
347
|
+
self._cors_headers()
|
|
348
|
+
self.send_header('Content-Type', 'application/json; charset=utf-8')
|
|
349
|
+
self.end_headers()
|
|
350
|
+
self.wfile.write(json.dumps({"error": "missing workspace param"}).encode('utf-8'))
|
|
351
|
+
return
|
|
352
|
+
|
|
353
|
+
base_path = get_openclaw_path()
|
|
354
|
+
json_path = os.path.join(base_path, workspace, '.myclaw', '__MY_ARTIFACTS__.json')
|
|
355
|
+
|
|
356
|
+
if not os.path.exists(json_path):
|
|
357
|
+
# 文件不存在,返回空结构
|
|
358
|
+
data = {
|
|
359
|
+
"workspace_id": workspace,
|
|
360
|
+
"base_url": BASE_URL,
|
|
361
|
+
"assets": [],
|
|
362
|
+
}
|
|
363
|
+
self.send_response(200)
|
|
364
|
+
self._cors_headers()
|
|
365
|
+
self.send_header('Content-Type', 'application/json; charset=utf-8')
|
|
366
|
+
self.end_headers()
|
|
367
|
+
self.wfile.write(json.dumps(data, ensure_ascii=False).encode('utf-8'))
|
|
368
|
+
return
|
|
369
|
+
|
|
370
|
+
try:
|
|
371
|
+
with open(json_path, 'r', encoding='utf-8') as f:
|
|
372
|
+
content = f.read()
|
|
373
|
+
self.send_response(200)
|
|
374
|
+
self._cors_headers()
|
|
375
|
+
self.send_header('Content-Type', 'application/json; charset=utf-8')
|
|
376
|
+
self.end_headers()
|
|
377
|
+
self.wfile.write(content.encode('utf-8'))
|
|
378
|
+
except Exception as e:
|
|
379
|
+
self.send_response(500)
|
|
380
|
+
self._cors_headers()
|
|
381
|
+
self.send_header('Content-Type', 'application/json; charset=utf-8')
|
|
382
|
+
self.end_headers()
|
|
383
|
+
self.wfile.write(json.dumps({"error": str(e)}).encode('utf-8'))
|
|
384
|
+
|
|
385
|
+
def log_message(self, format, *args):
|
|
386
|
+
# 静默日志,避免轮询刷屏
|
|
387
|
+
pass
|
|
388
|
+
|
|
389
|
+
|
|
390
|
+
def start_api_server():
|
|
391
|
+
"""在后台线程启动 HTTP API 服务"""
|
|
392
|
+
class ReusableHTTPServer(HTTPServer):
|
|
393
|
+
allow_reuse_address = True
|
|
394
|
+
|
|
395
|
+
server = ReusableHTTPServer(('0.0.0.0', API_PORT), MyclawAPIHandler)
|
|
396
|
+
thread = threading.Thread(target=server.serve_forever, daemon=True)
|
|
397
|
+
thread.start()
|
|
398
|
+
print(f"[myclaw-api] ✅ HTTP API 服务已启动: http://127.0.0.1:{API_PORT}")
|
|
399
|
+
print(f"[myclaw-api] GET /api/config → claw 配置")
|
|
400
|
+
print(f"[myclaw-api] GET /api/artifacts → 作品列表")
|
|
401
|
+
|
|
402
|
+
|
|
271
403
|
if __name__ == "__main__":
|
|
272
404
|
parser = argparse.ArgumentParser(description="文件同步服务")
|
|
273
405
|
parser.add_argument("--agent", help="启动前先全量同步指定 workspace")
|
|
406
|
+
parser.add_argument("--port", type=int, default=API_PORT, help="API 服务端口 (默认 18800)")
|
|
274
407
|
args = parser.parse_args()
|
|
275
408
|
|
|
409
|
+
API_PORT = args.port
|
|
410
|
+
|
|
276
411
|
base_path = get_openclaw_path()
|
|
277
412
|
path = base_path
|
|
278
413
|
|
|
@@ -282,6 +417,9 @@ if __name__ == "__main__":
|
|
|
282
417
|
sys.exit(1)
|
|
283
418
|
print("")
|
|
284
419
|
|
|
420
|
+
# 启动 HTTP API 服务
|
|
421
|
+
start_api_server()
|
|
422
|
+
|
|
285
423
|
event_handler = MyHandler()
|
|
286
424
|
observer = Observer()
|
|
287
425
|
observer.schedule(event_handler, path, recursive=True)
|