@aiyiran/myclaw 1.1.67 → 1.1.69
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/package.json +1 -1
- package/server/sync_workspace.py +61 -36
|
@@ -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/package.json
CHANGED
package/server/sync_workspace.py
CHANGED
|
@@ -624,39 +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()
|
|
635
|
+
time_id = now.strftime('%d') + 'D' + now.strftime('%M') + 'M'
|
|
631
636
|
|
|
632
|
-
# create_agent.js 会自动加 "workspace-" 前缀,所以这里只传去掉前缀的名字
|
|
633
|
-
# 例:src_workspace="workspace-fangshunhe" → agent_name="fangshunhe-v1-fork-475"
|
|
634
|
-
# mc new 创建目录 → workspace-fangshunhe-v1-fork-475 ✓
|
|
635
637
|
base_name = src_workspace
|
|
636
638
|
if base_name.startswith("workspace-"):
|
|
637
639
|
base_name = base_name[len("workspace-"):]
|
|
638
|
-
|
|
639
|
-
new_workspace = f"workspace-{agent_name}"
|
|
640
|
+
folder_name = f"{base_name}-v{src_version}-fork-{time_id}"
|
|
640
641
|
|
|
641
|
-
|
|
642
|
-
if entry_rel_path:
|
|
643
|
-
print(f"[Fork] 入口文件: {entry_rel_path}")
|
|
642
|
+
base_path = get_openclaw_path()
|
|
644
643
|
|
|
645
|
-
#
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
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
|
|
656
675
|
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
target_dir = os.path.join(base_path, new_workspace)
|
|
676
|
+
if entry_rel_path:
|
|
677
|
+
print(f"[Fork] 入口文件: {entry_rel_path}")
|
|
660
678
|
|
|
661
679
|
# 列出源文件
|
|
662
680
|
prefix = f"{FORK_CDN_PREFIX}/{src_clawname}/{src_workspace}/{src_version}/"
|
|
@@ -675,24 +693,26 @@ def _do_fork(job_id, src_clawname, src_workspace, src_version, entry_rel_path=No
|
|
|
675
693
|
rel_path = unquote(key[len(prefix):])
|
|
676
694
|
if not rel_path:
|
|
677
695
|
continue
|
|
696
|
+
# 已有空间模式:rel_path 前加子文件夹前缀
|
|
697
|
+
record_rel = os.path.join(folder_name, rel_path) if use_existing else rel_path
|
|
678
698
|
local_path = os.path.join(target_dir, rel_path)
|
|
679
699
|
if _download_from_cdn(key, local_path):
|
|
680
700
|
success += 1
|
|
681
|
-
downloaded_rels.append(
|
|
682
|
-
print(f" ✓ {
|
|
701
|
+
downloaded_rels.append(record_rel)
|
|
702
|
+
print(f" ✓ {record_rel}")
|
|
683
703
|
|
|
684
704
|
print(f"[Fork] 完成: 成功 {success}/{len(files)} 个文件")
|
|
685
705
|
|
|
686
706
|
# 写入 JSON 记录,入口文件最后写,确保其 updated_at 最新排第一
|
|
687
|
-
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)
|
|
688
708
|
for rel in downloaded_rels:
|
|
689
709
|
if rel != entry_decoded:
|
|
690
|
-
init_config(
|
|
710
|
+
init_config(workspace_name, rel, method="add")
|
|
691
711
|
if entry_decoded and entry_decoded in downloaded_rels:
|
|
692
|
-
init_config(
|
|
712
|
+
init_config(workspace_name, entry_decoded, method="add")
|
|
693
713
|
print(f"[Fork] 入口文件已置顶: {entry_decoded}")
|
|
694
714
|
|
|
695
|
-
#
|
|
715
|
+
# 文件到位后再发分析消息
|
|
696
716
|
fork_message = (
|
|
697
717
|
"你好!请你仔细阅读我 workspace 下的所有文件,"
|
|
698
718
|
"简单介绍一下这个项目有哪些资源、大概是做什么用的,"
|
|
@@ -710,7 +730,8 @@ def _do_fork(job_id, src_clawname, src_workspace, src_version, entry_rel_path=No
|
|
|
710
730
|
|
|
711
731
|
_fork_jobs[job_id] = {
|
|
712
732
|
"status": "done",
|
|
713
|
-
"workspace":
|
|
733
|
+
"workspace": workspace_name,
|
|
734
|
+
"folder": folder_name if use_existing else None,
|
|
714
735
|
"files": success,
|
|
715
736
|
"total": len(files)
|
|
716
737
|
}
|
|
@@ -720,10 +741,12 @@ def _do_fork(job_id, src_clawname, src_workspace, src_version, entry_rel_path=No
|
|
|
720
741
|
_fork_jobs[job_id] = {"status": "failed", "error": str(e)}
|
|
721
742
|
|
|
722
743
|
|
|
723
|
-
def start_fork(remote_url):
|
|
744
|
+
def start_fork(remote_url, target_agent=None, claw_name=None):
|
|
724
745
|
"""
|
|
725
746
|
解析 remote_url,生成 job_id,在后台线程执行 fork。
|
|
726
747
|
URL 格式: https://cdn.yiranlaoshi.com/myclaw/show/{clawname}/{workspace}/{version}/...
|
|
748
|
+
target_agent: 可选,指定已有 workspace 的 agent 名(不含 workspace- 前缀亦可)
|
|
749
|
+
claw_name: 可选,指定目标 claw,由调用方负责路由到对应机器/目录
|
|
727
750
|
"""
|
|
728
751
|
parsed = urlparse(remote_url)
|
|
729
752
|
# path: /myclaw/show/{clawname}/{workspace}/{version}/...
|
|
@@ -746,7 +769,7 @@ def start_fork(remote_url):
|
|
|
746
769
|
|
|
747
770
|
t = threading.Thread(
|
|
748
771
|
target=_do_fork,
|
|
749
|
-
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),
|
|
750
773
|
daemon=True
|
|
751
774
|
)
|
|
752
775
|
t.start()
|
|
@@ -1029,11 +1052,13 @@ class MyclawAPIHandler(BaseHTTPRequestHandler):
|
|
|
1029
1052
|
self._send_json({"ok": False, "error": str(e)}, 500)
|
|
1030
1053
|
|
|
1031
1054
|
def _handle_fork(self):
|
|
1032
|
-
"""POST /api/fork Body: {"url": "
|
|
1055
|
+
"""POST /api/fork Body: {"url": "...", "targetAgent": "myspace"(可选), "clawName": "alice"(可选)}"""
|
|
1033
1056
|
try:
|
|
1034
1057
|
length = int(self.headers.get('Content-Length', 0))
|
|
1035
1058
|
body = json.loads(self.rfile.read(length).decode('utf-8')) if length else {}
|
|
1036
1059
|
remote_url = body.get('url', '')
|
|
1060
|
+
target_agent = body.get('targetAgent', None) or None
|
|
1061
|
+
claw_name = body.get('clawName', None) or None
|
|
1037
1062
|
except Exception:
|
|
1038
1063
|
self._send_json({"error": "请求体解析失败"}, 400)
|
|
1039
1064
|
return
|
|
@@ -1042,7 +1067,7 @@ class MyclawAPIHandler(BaseHTTPRequestHandler):
|
|
|
1042
1067
|
self._send_json({"error": "缺少 url 参数"}, 400)
|
|
1043
1068
|
return
|
|
1044
1069
|
|
|
1045
|
-
job_id, err = start_fork(remote_url)
|
|
1070
|
+
job_id, err = start_fork(remote_url, target_agent, claw_name)
|
|
1046
1071
|
if err:
|
|
1047
1072
|
self._send_json({"error": err}, 400)
|
|
1048
1073
|
return
|