@aiyiran/myclaw 1.1.68 → 1.1.70
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 +88 -50
- package/gateway.js +3 -1
- package/package.json +1 -1
- package/server/sync_workspace.py +59 -35
|
@@ -275,7 +275,7 @@
|
|
|
275
275
|
var _scEnv = detectEnvironment();
|
|
276
276
|
var _scClaw = _scEnv.remote ? _scEnv.clawName : (lastKnownClawName || '');
|
|
277
277
|
var _scWs = getWorkspaceId();
|
|
278
|
-
showcaseLink.href = 'https://www.yiranlaoshi.com/showcase?claw=' + _scClaw + '&workspace=' + _scWs;
|
|
278
|
+
showcaseLink.href = 'https://www.yiranlaoshi.com/showcase?claw=' + _scClaw + '&workspace=' + _scWs + '&clawName=' + _scClaw;
|
|
279
279
|
showcaseLink.target = '_blank';
|
|
280
280
|
showcaseLink.textContent = '我的项目';
|
|
281
281
|
showcaseLink.style.cssText = 'cursor:pointer;padding:2px 10px;border-radius:3px;font-size:12px;background:rgba(255,255,255,0.08);transition:background 0.15s;color:#cdd6f4;text-decoration:none;';
|
|
@@ -2157,54 +2157,21 @@
|
|
|
2157
2157
|
forkForm.appendChild(urlInput);
|
|
2158
2158
|
forkForm.appendChild(errMsg);
|
|
2159
2159
|
|
|
2160
|
-
//
|
|
2161
|
-
|
|
2162
|
-
|
|
2163
|
-
|
|
2164
|
-
|
|
2165
|
-
|
|
2166
|
-
'background: #4a4a7a',
|
|
2167
|
-
'border: none',
|
|
2168
|
-
'border-radius: 4px',
|
|
2169
|
-
'color: #cdd6f4',
|
|
2170
|
-
'font-size: 13px',
|
|
2171
|
-
'font-family: monospace',
|
|
2172
|
-
'cursor: pointer',
|
|
2173
|
-
'transition: background 0.15s',
|
|
2174
|
-
].join(';');
|
|
2175
|
-
forkSubmitBtn.onmouseenter = function () { if (!forkSubmitBtn.disabled) forkSubmitBtn.style.background = '#5a5a9a'; };
|
|
2176
|
-
forkSubmitBtn.onmouseleave = function () { if (!forkSubmitBtn.disabled) forkSubmitBtn.style.background = '#4a4a7a'; };
|
|
2177
|
-
|
|
2178
|
-
forkSubmitBtn.onclick = function () {
|
|
2179
|
-
var url = urlInput.value.trim();
|
|
2180
|
-
|
|
2181
|
-
// 校验 URL 完整性
|
|
2182
|
-
var valid = false;
|
|
2183
|
-
try {
|
|
2184
|
-
var parsed = new URL(url);
|
|
2185
|
-
valid = parsed.protocol === 'http:' || parsed.protocol === 'https:';
|
|
2186
|
-
} catch (e) { valid = false; }
|
|
2187
|
-
|
|
2188
|
-
if (!valid) {
|
|
2189
|
-
urlInput.style.borderColor = '#ff4444';
|
|
2190
|
-
errMsg.style.display = 'block';
|
|
2191
|
-
errVisible = true;
|
|
2192
|
-
return;
|
|
2193
|
-
}
|
|
2194
|
-
urlInput.style.borderColor = '#3d3d5c';
|
|
2195
|
-
errMsg.style.display = 'none';
|
|
2196
|
-
errVisible = false;
|
|
2197
|
-
|
|
2198
|
-
forkSubmitBtn.disabled = true;
|
|
2199
|
-
forkSubmitBtn.textContent = '\u23F3 \u83B7\u53D6\u4E2D...';
|
|
2200
|
-
forkSubmitBtn.style.background = '#3a3a5a';
|
|
2201
|
-
forkSubmitBtn.style.cursor = 'default';
|
|
2160
|
+
// 提交逻辑(供两个按钮共用)
|
|
2161
|
+
function doFork(url, targetAgent, btn) {
|
|
2162
|
+
btn.disabled = true;
|
|
2163
|
+
btn.textContent = '⏳ 获取中...';
|
|
2164
|
+
btn.style.background = '#3a3a5a';
|
|
2165
|
+
btn.style.cursor = 'default';
|
|
2202
2166
|
urlInput.disabled = true;
|
|
2203
2167
|
|
|
2168
|
+
var body = { url: url };
|
|
2169
|
+
if (targetAgent) body.targetAgent = targetAgent;
|
|
2170
|
+
|
|
2204
2171
|
fetch(MYCLAW_API_BASE + '/api/fork', {
|
|
2205
2172
|
method: 'POST',
|
|
2206
2173
|
headers: { 'Content-Type': 'application/json' },
|
|
2207
|
-
body: JSON.stringify(
|
|
2174
|
+
body: JSON.stringify(body),
|
|
2208
2175
|
})
|
|
2209
2176
|
.then(function (res) {
|
|
2210
2177
|
if (!res.ok) throw new Error('HTTP ' + res.status);
|
|
@@ -2212,7 +2179,6 @@
|
|
|
2212
2179
|
})
|
|
2213
2180
|
.then(function (data) {
|
|
2214
2181
|
if (!data.job_id) throw new Error(data.error || 'no job_id');
|
|
2215
|
-
// 轮询 job 状态
|
|
2216
2182
|
var jobId = data.job_id;
|
|
2217
2183
|
var timer = setInterval(function () {
|
|
2218
2184
|
fetch(MYCLAW_API_BASE + '/api/fork/status?job_id=' + encodeURIComponent(jobId))
|
|
@@ -2221,8 +2187,8 @@
|
|
|
2221
2187
|
if (job.status === 'running') return;
|
|
2222
2188
|
clearInterval(timer);
|
|
2223
2189
|
if (job.status === 'done') {
|
|
2224
|
-
|
|
2225
|
-
|
|
2190
|
+
btn.textContent = '✅ 获取成功!';
|
|
2191
|
+
btn.style.background = '#10b981';
|
|
2226
2192
|
setTimeout(function () {
|
|
2227
2193
|
closeForkModal();
|
|
2228
2194
|
showForkDoneToast(job.workspace, job.files);
|
|
@@ -2233,18 +2199,90 @@
|
|
|
2233
2199
|
})
|
|
2234
2200
|
.catch(function (err) {
|
|
2235
2201
|
clearInterval(timer);
|
|
2236
|
-
onForkError(err,
|
|
2202
|
+
onForkError(err, btn, urlInput);
|
|
2237
2203
|
});
|
|
2238
2204
|
}, 1500);
|
|
2239
2205
|
})
|
|
2240
2206
|
.catch(function (err) {
|
|
2241
|
-
onForkError(err,
|
|
2207
|
+
onForkError(err, btn, urlInput);
|
|
2242
2208
|
});
|
|
2209
|
+
}
|
|
2210
|
+
|
|
2211
|
+
function validateAndFork(targetAgent, btn) {
|
|
2212
|
+
var url = urlInput.value.trim();
|
|
2213
|
+
var valid = false;
|
|
2214
|
+
try {
|
|
2215
|
+
var parsed = new URL(url);
|
|
2216
|
+
valid = parsed.protocol === 'http:' || parsed.protocol === 'https:';
|
|
2217
|
+
} catch (e) { valid = false; }
|
|
2218
|
+
|
|
2219
|
+
if (!valid) {
|
|
2220
|
+
urlInput.style.borderColor = '#ff4444';
|
|
2221
|
+
errMsg.style.display = 'block';
|
|
2222
|
+
errVisible = true;
|
|
2223
|
+
return;
|
|
2224
|
+
}
|
|
2225
|
+
urlInput.style.borderColor = '#3d3d5c';
|
|
2226
|
+
errMsg.style.display = 'none';
|
|
2227
|
+
errVisible = false;
|
|
2228
|
+
doFork(url, targetAgent, btn);
|
|
2229
|
+
}
|
|
2230
|
+
|
|
2231
|
+
// 按钮行
|
|
2232
|
+
var btnRow = document.createElement('div');
|
|
2233
|
+
btnRow.style.cssText = 'display:flex;gap:10px;margin:0 20px 20px;';
|
|
2234
|
+
|
|
2235
|
+
// 左侧主按钮:确认获取(新建空间)
|
|
2236
|
+
var forkSubmitBtn = document.createElement('button');
|
|
2237
|
+
forkSubmitBtn.textContent = '确认获取';
|
|
2238
|
+
forkSubmitBtn.style.cssText = [
|
|
2239
|
+
'flex: 2',
|
|
2240
|
+
'padding: 10px',
|
|
2241
|
+
'background: #4a4a7a',
|
|
2242
|
+
'border: none',
|
|
2243
|
+
'border-radius: 4px',
|
|
2244
|
+
'color: #cdd6f4',
|
|
2245
|
+
'font-size: 13px',
|
|
2246
|
+
'font-family: monospace',
|
|
2247
|
+
'cursor: pointer',
|
|
2248
|
+
'transition: background 0.15s',
|
|
2249
|
+
].join(';');
|
|
2250
|
+
forkSubmitBtn.onmouseenter = function () { if (!forkSubmitBtn.disabled) forkSubmitBtn.style.background = '#5a5a9a'; };
|
|
2251
|
+
forkSubmitBtn.onmouseleave = function () { if (!forkSubmitBtn.disabled) forkSubmitBtn.style.background = '#4a4a7a'; };
|
|
2252
|
+
forkSubmitBtn.onclick = function () { validateAndFork(null, forkSubmitBtn); };
|
|
2253
|
+
|
|
2254
|
+
// 右侧次按钮:获取到当前空间
|
|
2255
|
+
var forkCurrentBtn = document.createElement('button');
|
|
2256
|
+
forkCurrentBtn.textContent = '获取到当前空间';
|
|
2257
|
+
forkCurrentBtn.style.cssText = [
|
|
2258
|
+
'flex: 1',
|
|
2259
|
+
'padding: 10px',
|
|
2260
|
+
'background: #2d2d4a',
|
|
2261
|
+
'border: 1px solid #4a4a7a',
|
|
2262
|
+
'border-radius: 4px',
|
|
2263
|
+
'color: #aaa',
|
|
2264
|
+
'font-size: 12px',
|
|
2265
|
+
'font-family: monospace',
|
|
2266
|
+
'cursor: pointer',
|
|
2267
|
+
'transition: background 0.15s',
|
|
2268
|
+
].join(';');
|
|
2269
|
+
forkCurrentBtn.onmouseenter = function () { if (!forkCurrentBtn.disabled) forkCurrentBtn.style.background = '#3a3a5a'; };
|
|
2270
|
+
forkCurrentBtn.onmouseleave = function () { if (!forkCurrentBtn.disabled) forkCurrentBtn.style.background = '#2d2d4a'; };
|
|
2271
|
+
forkCurrentBtn.onclick = function () {
|
|
2272
|
+
var currentAgent = getAgentName();
|
|
2273
|
+
if (!currentAgent) {
|
|
2274
|
+
alert('当前页面未检测到 agent,请确认 URL 中包含 session 或 agent 参数');
|
|
2275
|
+
return;
|
|
2276
|
+
}
|
|
2277
|
+
validateAndFork(currentAgent, forkCurrentBtn);
|
|
2243
2278
|
};
|
|
2244
2279
|
|
|
2280
|
+
btnRow.appendChild(forkSubmitBtn);
|
|
2281
|
+
btnRow.appendChild(forkCurrentBtn);
|
|
2282
|
+
|
|
2245
2283
|
box.appendChild(forkHeader);
|
|
2246
2284
|
box.appendChild(forkForm);
|
|
2247
|
-
box.appendChild(
|
|
2285
|
+
box.appendChild(btnRow);
|
|
2248
2286
|
overlay.appendChild(box);
|
|
2249
2287
|
document.body.appendChild(overlay);
|
|
2250
2288
|
urlInput.focus();
|
package/gateway.js
CHANGED
|
@@ -74,10 +74,12 @@ function stop() {
|
|
|
74
74
|
}
|
|
75
75
|
|
|
76
76
|
// 2b. 杀所有 openclaw 相关进程(防止僵尸进程)
|
|
77
|
+
// 注意:用精确匹配,避免误杀路径中含 "openclaw" 的无关进程(如 Antigravity 等)
|
|
77
78
|
try {
|
|
78
79
|
if (!isWin) {
|
|
79
80
|
const myPid = process.pid;
|
|
80
|
-
|
|
81
|
+
// pgrep -x 只匹配进程名本身;pgrep -f 的精确模式匹配 "openclaw gateway" 子命令
|
|
82
|
+
const procs = execSync('{ pgrep -x openclaw; pgrep -f "openclaw gateway"; } 2>/dev/null | sort -u || true', {
|
|
81
83
|
encoding: 'utf8', stdio: 'pipe', timeout: 5000
|
|
82
84
|
}).trim();
|
|
83
85
|
if (procs) {
|
package/package.json
CHANGED
package/server/sync_workspace.py
CHANGED
|
@@ -624,40 +624,57 @@ def _download_from_cdn(key, local_path):
|
|
|
624
624
|
return False
|
|
625
625
|
|
|
626
626
|
|
|
627
|
-
def _do_fork(job_id, src_clawname, src_workspace, src_version, entry_rel_path=None):
|
|
628
|
-
"""在后台线程中执行 fork
|
|
627
|
+
def _do_fork(job_id, src_clawname, src_workspace, src_version, entry_rel_path=None, target_agent=None, claw_name=None):
|
|
628
|
+
"""在后台线程中执行 fork
|
|
629
|
+
- target_agent 为 None 或对应目录不存在:新建 workspace
|
|
630
|
+
- target_agent 存在:将文件下载到该 workspace 下的子文件夹
|
|
631
|
+
- claw_name 指定目标 claw,由调用方负责路由;未传则使用本机默认路径
|
|
632
|
+
"""
|
|
629
633
|
try:
|
|
630
634
|
now = datetime.now()
|
|
631
635
|
time_id = now.strftime('%d') + 'D' + now.strftime('%M') + 'M'
|
|
632
636
|
|
|
633
|
-
# create_agent.js 会自动加 "workspace-" 前缀,所以这里只传去掉前缀的名字
|
|
634
|
-
# 例:src_workspace="workspace-fangshunhe" → agent_name="fangshunhe-v1-fork-15D30M"
|
|
635
|
-
# mc new 创建目录 → workspace-fangshunhe-v1-fork-15D30M ✓
|
|
636
637
|
base_name = src_workspace
|
|
637
638
|
if base_name.startswith("workspace-"):
|
|
638
639
|
base_name = base_name[len("workspace-"):]
|
|
639
|
-
|
|
640
|
-
new_workspace = f"workspace-{agent_name}"
|
|
640
|
+
folder_name = f"{base_name}-v{src_version}-fork-{time_id}"
|
|
641
641
|
|
|
642
|
-
|
|
643
|
-
if entry_rel_path:
|
|
644
|
-
print(f"[Fork] 入口文件: {entry_rel_path}")
|
|
642
|
+
base_path = get_openclaw_path()
|
|
645
643
|
|
|
646
|
-
#
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
644
|
+
# 判断是否使用已有 workspace
|
|
645
|
+
use_existing = False
|
|
646
|
+
if target_agent:
|
|
647
|
+
target_workspace = f"workspace-{target_agent}" if not target_agent.startswith("workspace-") else target_agent
|
|
648
|
+
target_workspace_dir = os.path.join(base_path, target_workspace)
|
|
649
|
+
if os.path.isdir(target_workspace_dir):
|
|
650
|
+
use_existing = True
|
|
651
|
+
|
|
652
|
+
if use_existing:
|
|
653
|
+
# 下载到已有 workspace 的子文件夹中
|
|
654
|
+
agent_name = target_agent if not target_agent.startswith("workspace-") else target_agent[len("workspace-"):]
|
|
655
|
+
workspace_name = target_workspace
|
|
656
|
+
target_dir = os.path.join(target_workspace_dir, folder_name)
|
|
657
|
+
print(f"[Fork] 使用已有空间: {workspace_name}, 子文件夹: {folder_name}")
|
|
658
|
+
else:
|
|
659
|
+
# 新建 workspace
|
|
660
|
+
agent_name = folder_name
|
|
661
|
+
workspace_name = f"workspace-{agent_name}"
|
|
662
|
+
target_dir = os.path.join(base_path, workspace_name)
|
|
663
|
+
print(f"[Fork] 新建空间: {workspace_name}")
|
|
664
|
+
|
|
665
|
+
mc_result = subprocess.run(
|
|
666
|
+
["mc", "new", agent_name],
|
|
667
|
+
capture_output=True, text=True
|
|
668
|
+
)
|
|
669
|
+
if mc_result.returncode != 0:
|
|
670
|
+
_fork_jobs[job_id] = {
|
|
671
|
+
"status": "failed",
|
|
672
|
+
"error": f"mc new 失败: {mc_result.stderr.strip()}"
|
|
673
|
+
}
|
|
674
|
+
return
|
|
657
675
|
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
target_dir = os.path.join(base_path, new_workspace)
|
|
676
|
+
if entry_rel_path:
|
|
677
|
+
print(f"[Fork] 入口文件: {entry_rel_path}")
|
|
661
678
|
|
|
662
679
|
# 列出源文件
|
|
663
680
|
prefix = f"{FORK_CDN_PREFIX}/{src_clawname}/{src_workspace}/{src_version}/"
|
|
@@ -676,24 +693,26 @@ def _do_fork(job_id, src_clawname, src_workspace, src_version, entry_rel_path=No
|
|
|
676
693
|
rel_path = unquote(key[len(prefix):])
|
|
677
694
|
if not rel_path:
|
|
678
695
|
continue
|
|
696
|
+
# 已有空间模式:rel_path 前加子文件夹前缀
|
|
697
|
+
record_rel = os.path.join(folder_name, rel_path) if use_existing else rel_path
|
|
679
698
|
local_path = os.path.join(target_dir, rel_path)
|
|
680
699
|
if _download_from_cdn(key, local_path):
|
|
681
700
|
success += 1
|
|
682
|
-
downloaded_rels.append(
|
|
683
|
-
print(f" ✓ {
|
|
701
|
+
downloaded_rels.append(record_rel)
|
|
702
|
+
print(f" ✓ {record_rel}")
|
|
684
703
|
|
|
685
704
|
print(f"[Fork] 完成: 成功 {success}/{len(files)} 个文件")
|
|
686
705
|
|
|
687
706
|
# 写入 JSON 记录,入口文件最后写,确保其 updated_at 最新排第一
|
|
688
|
-
entry_decoded = unquote(entry_rel_path) if entry_rel_path else None
|
|
707
|
+
entry_decoded = os.path.join(folder_name, unquote(entry_rel_path)) if (use_existing and entry_rel_path) else (unquote(entry_rel_path) if entry_rel_path else None)
|
|
689
708
|
for rel in downloaded_rels:
|
|
690
709
|
if rel != entry_decoded:
|
|
691
|
-
init_config(
|
|
710
|
+
init_config(workspace_name, rel, method="add")
|
|
692
711
|
if entry_decoded and entry_decoded in downloaded_rels:
|
|
693
|
-
init_config(
|
|
712
|
+
init_config(workspace_name, entry_decoded, method="add")
|
|
694
713
|
print(f"[Fork] 入口文件已置顶: {entry_decoded}")
|
|
695
714
|
|
|
696
|
-
#
|
|
715
|
+
# 文件到位后再发分析消息
|
|
697
716
|
fork_message = (
|
|
698
717
|
"你好!请你仔细阅读我 workspace 下的所有文件,"
|
|
699
718
|
"简单介绍一下这个项目有哪些资源、大概是做什么用的,"
|
|
@@ -711,7 +730,8 @@ def _do_fork(job_id, src_clawname, src_workspace, src_version, entry_rel_path=No
|
|
|
711
730
|
|
|
712
731
|
_fork_jobs[job_id] = {
|
|
713
732
|
"status": "done",
|
|
714
|
-
"workspace":
|
|
733
|
+
"workspace": workspace_name,
|
|
734
|
+
"folder": folder_name if use_existing else None,
|
|
715
735
|
"files": success,
|
|
716
736
|
"total": len(files)
|
|
717
737
|
}
|
|
@@ -721,10 +741,12 @@ def _do_fork(job_id, src_clawname, src_workspace, src_version, entry_rel_path=No
|
|
|
721
741
|
_fork_jobs[job_id] = {"status": "failed", "error": str(e)}
|
|
722
742
|
|
|
723
743
|
|
|
724
|
-
def start_fork(remote_url):
|
|
744
|
+
def start_fork(remote_url, target_agent=None, claw_name=None):
|
|
725
745
|
"""
|
|
726
746
|
解析 remote_url,生成 job_id,在后台线程执行 fork。
|
|
727
747
|
URL 格式: https://cdn.yiranlaoshi.com/myclaw/show/{clawname}/{workspace}/{version}/...
|
|
748
|
+
target_agent: 可选,指定已有 workspace 的 agent 名(不含 workspace- 前缀亦可)
|
|
749
|
+
claw_name: 可选,指定目标 claw,由调用方负责路由到对应机器/目录
|
|
728
750
|
"""
|
|
729
751
|
parsed = urlparse(remote_url)
|
|
730
752
|
# path: /myclaw/show/{clawname}/{workspace}/{version}/...
|
|
@@ -747,7 +769,7 @@ def start_fork(remote_url):
|
|
|
747
769
|
|
|
748
770
|
t = threading.Thread(
|
|
749
771
|
target=_do_fork,
|
|
750
|
-
args=(job_id, src_clawname, src_workspace, src_version, entry_rel_path),
|
|
772
|
+
args=(job_id, src_clawname, src_workspace, src_version, entry_rel_path, target_agent, claw_name),
|
|
751
773
|
daemon=True
|
|
752
774
|
)
|
|
753
775
|
t.start()
|
|
@@ -1030,11 +1052,13 @@ class MyclawAPIHandler(BaseHTTPRequestHandler):
|
|
|
1030
1052
|
self._send_json({"ok": False, "error": str(e)}, 500)
|
|
1031
1053
|
|
|
1032
1054
|
def _handle_fork(self):
|
|
1033
|
-
"""POST /api/fork Body: {"url": "
|
|
1055
|
+
"""POST /api/fork Body: {"url": "...", "targetAgent": "myspace"(可选), "clawName": "alice"(可选)}"""
|
|
1034
1056
|
try:
|
|
1035
1057
|
length = int(self.headers.get('Content-Length', 0))
|
|
1036
1058
|
body = json.loads(self.rfile.read(length).decode('utf-8')) if length else {}
|
|
1037
1059
|
remote_url = body.get('url', '')
|
|
1060
|
+
target_agent = body.get('targetAgent', None) or None
|
|
1061
|
+
claw_name = body.get('clawName', None) or None
|
|
1038
1062
|
except Exception:
|
|
1039
1063
|
self._send_json({"error": "请求体解析失败"}, 400)
|
|
1040
1064
|
return
|
|
@@ -1043,7 +1067,7 @@ class MyclawAPIHandler(BaseHTTPRequestHandler):
|
|
|
1043
1067
|
self._send_json({"error": "缺少 url 参数"}, 400)
|
|
1044
1068
|
return
|
|
1045
1069
|
|
|
1046
|
-
job_id, err = start_fork(remote_url)
|
|
1070
|
+
job_id, err = start_fork(remote_url, target_agent, claw_name)
|
|
1047
1071
|
if err:
|
|
1048
1072
|
self._send_json({"error": err}, 400)
|
|
1049
1073
|
return
|