热重载前置探活+skill跨根找真身:mcpctl对调后先_live_oc探活(WSL未跑/OC未监听则跳过重载按成功返回,不再报WinError10061失败,host复用给推送与reload),ocskill新增_alt_skill_md跨已知根(.workbuddy/.config含trash/WSL对应根,OC_SKILL_ALT_ROOTS可追加)找同名SKILL.md真身,WSL二层空结果补说明行不静默,两套单测全绿(6项+15项)
This commit is contained in:
@@ -139,6 +139,14 @@ python mcpctl.py off --no-reload # 只对调文件,不调用重载
|
||||
热重载打的是 `POST /api/location/reload`,会重建 location 并重读磁盘配置,MCP 立即生效;
|
||||
失败时配置文件已经对调完成,重启 OC 一样生效。
|
||||
|
||||
**前置探活:没有运行中的 OC 就不重载、也不算失败(2026-09-25 用户反馈)**:
|
||||
对调完成后先 `_live_oc()` —— WSL 目标先查 VM 是否在跑(没跑直接跳过、不发网络请求),
|
||||
再端口发现 + 探活找运行中的 OC。都没有 → 打印「无需热重载:… 配置已对调完成,
|
||||
等 OC 下次启动时会自动加载新配置」并按成功返回,**不再报**
|
||||
「热重载失败:… WinError 10061 连接拒绝」(WSL/OC 都没启动时冷启动会自己加载)。
|
||||
探活命中的 host 直接复用给推送兜底与 reload(不重复探测);只有
|
||||
「OC 在跑但 reload 返回 401/404/错误」才照旧算真实失败。
|
||||
|
||||
**开启方向多了「推送兜底」(双保险)**:`on` 在对调+写 flag 之后、reload 之前,
|
||||
先把活配置里的 `mcp` 推进**运行中的 OC**。三端同一套语义——**GET+merge 补 shell
|
||||
→ PATCH 真实路由**,按各自运行方式实现:
|
||||
@@ -188,10 +196,22 @@ UNC 目录扫描(那会触发冷启动卡死),直接报错返回。
|
||||
- 目标读不出来时不再用 realpath 硬解(那只会解出链接自身、误报断链),
|
||||
直接标注「疑似 WSL 内软链接,交由二层诊断」;
|
||||
- 推送失败(缺 SKILL.md)的报错里同样附带这份诊断,回答「真实文件在哪」。
|
||||
- **跨根兜底:空壳目录直接找出「真身在别处」(2026-09-25 用户反馈)**——
|
||||
实测来源目录(`.config/opencode/skills/<name>`,两端)是**真实空目录、不是链接**,
|
||||
但大模型照样能调用:真身在另一个根 `~/.workbuddy/skills/<name>/SKILL.md`
|
||||
(即 `wb` 终端)。结论行不再止步「仍未找到」,而是跨已知根找同名 SKILL.md:
|
||||
本机 `~/.workbuddy/skills`、`~/.config/opencode/skills`(含 `.trash` 回收站)、
|
||||
当前目录 `.workbuddy/skills`、WSL 端对应两根(folder 是 WSL UNC 直查,
|
||||
否则 VM 在跑才查),并可用环境变量 `OC_SKILL_ALT_ROOTS`(`os.pathsep` 分隔)
|
||||
追加自定义根;找到就列出真身路径并提示「改选包含真身的目录(如 wb 终端)作来源」。
|
||||
另外 WSL 二层查完**空结果也会说话**(「ℹ WSL 内二层已查:没有 SKILL.md、也没有链接」),
|
||||
不再静默——静默会被误读成二层没跑。
|
||||
|
||||
**回归单测**:`python _test_diag.py`(空壳 / 正常 skill / junction 链接 / 断链 /
|
||||
大小写 / WSL UNC 真链接共 15 项断言,2 个文件 symlink 场景在拦截 symlink 的
|
||||
机器上自动 SKIP;含 WSL 场景,首次跑会拉起 VM)。
|
||||
大小写 / WSL UNC 真链接 / 跨根兜底等 15+ 项断言,2 个文件 symlink 场景在拦截 symlink 的
|
||||
机器上自动 SKIP;含 WSL 场景,首次跑会拉起 VM);
|
||||
`python _test_mcpctl.py`(热重载前置探活 6 项:未运行 skip 不探测、失败仍报错、
|
||||
host 复用等)。
|
||||
|
||||
---
|
||||
|
||||
|
||||
@@ -9,6 +9,7 @@ from pathlib import Path
|
||||
sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
|
||||
from ocskill import ( # noqa: E402
|
||||
diag_skill_md, skill_brief, probe_push, find_skill_md, _wsl_running,
|
||||
_alt_skill_md,
|
||||
)
|
||||
|
||||
FAIL: list[str] = []
|
||||
@@ -131,6 +132,9 @@ if unc.exists():
|
||||
notes = diag_skill_md(unc)
|
||||
check("WSL UNC 空壳 -> 报空的/仍未找到", any(("目录是空的" in n) or ("仍未找到" in n) for n in notes),
|
||||
str(notes))
|
||||
if _wsl_running("Ubuntu-24.04"):
|
||||
check("WSL UNC 空壳 -> 二层已查有说明行(不静默)",
|
||||
any("WSL 内二层" in n for n in notes), str(notes))
|
||||
else:
|
||||
print("SKIP WSL UNC 空壳(路径不存在):", unc)
|
||||
|
||||
@@ -170,6 +174,39 @@ res = probe_push(str(empty))
|
||||
check("probe_push 空壳 -> ok=False 且带 diag", res.get("ok") is False and res.get("diag"),
|
||||
str(res)[:300])
|
||||
|
||||
# ---------- 11. 空壳但同名 skill 真身在别的根(2026-09-25 用户反馈) ----------
|
||||
# 真实场景:来源目录是空壳,但大模型能调用 —— 真身在 ~/.workbuddy/skills(wb 终端)。
|
||||
# 测试用 OC_SKILL_ALT_ROOTS 注入一个「另一个根」,避免往真实 wb 目录里写文件。
|
||||
altbase = tmp / "alt_roots"
|
||||
(altbase / "alt-skill").mkdir(parents=True)
|
||||
(altbase / "alt-skill" / "SKILL.md").write_text("---\nname: alt-skill\ndescription: d\nt\n---\n",
|
||||
encoding="utf-8")
|
||||
shell2 = tmp / "alt-skill"
|
||||
shell2.mkdir()
|
||||
os.environ["OC_SKILL_ALT_ROOTS"] = str(altbase)
|
||||
try:
|
||||
notes = diag_skill_md(shell2)
|
||||
finally:
|
||||
os.environ.pop("OC_SKILL_ALT_ROOTS", None)
|
||||
check("空壳 + 其它根有真身 -> 结论给出真身路径",
|
||||
any("真身在别处" in n for n in notes)
|
||||
and any(str(altbase / "alt-skill" / "SKILL.md") in n for n in notes),
|
||||
str(notes))
|
||||
b = skill_brief("alt-skill", tmp)
|
||||
check("skill_brief 空壳仍 has_md=False(真身只在诊断里提示)",
|
||||
b.get("has_md") is False and b.get("diag"), str(b)[:200])
|
||||
|
||||
# ---------- 12. 跨端/回收站兜底(环境依赖,不满足就 SKIP) ----------
|
||||
gkd = Path.home() / ".config" / "opencode" / "skills" / "gkd-shizuku-keepalive"
|
||||
gkd_alts = _alt_skill_md(gkd) if gkd.is_dir() and find_skill_md(gkd) is None else []
|
||||
if gkd_alts:
|
||||
notes = diag_skill_md(gkd)
|
||||
check("本机空壳 -> 找到回收站/跨端里的真身",
|
||||
any("真身在别处" in n for n in notes) and any(gkd_alts[0] in n for n in notes),
|
||||
str(notes))
|
||||
else:
|
||||
print("SKIP 跨端回收站兜底(环境不满足:目录不存在/非空壳/无处可找)")
|
||||
|
||||
print()
|
||||
print(("ALL PASS" if not FAIL else "FAILURES: " + ", ".join(FAIL)))
|
||||
sys.exit(1 if FAIL else 0)
|
||||
|
||||
@@ -0,0 +1,84 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
"""mcpctl 热重载前置探活单测(2026-09-25 用户反馈)。
|
||||
|
||||
用户场景:mcp↔wsl 开关对调后,WSL/OC 没在跑 → 报「热重载失败:… WinError 10061
|
||||
连接拒绝」。期望:没有可重载的 OC 就跳过重载、按成功返回(下次冷启动自动加载),
|
||||
只有「OC 在跑但重载出错」才算失败。
|
||||
"""
|
||||
import os
|
||||
import sys
|
||||
|
||||
sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
|
||||
import mcpctl # noqa: E402
|
||||
|
||||
FAIL: list[str] = []
|
||||
|
||||
|
||||
def check(name: str, cond: bool, extra: str = "") -> None:
|
||||
print(("PASS " if cond else "FAIL ") + name + (" | " + extra if extra else ""))
|
||||
if not cond:
|
||||
FAIL.append(name)
|
||||
|
||||
|
||||
class _A: # 最小 args 替身(只用到 _live_oc/do_reload 需要的字段)
|
||||
distro = "Ubuntu-24.04"
|
||||
password = ""
|
||||
port = 4096
|
||||
timeout = 2.0
|
||||
|
||||
|
||||
args = _A()
|
||||
_orig = (mcpctl._wsl_running, mcpctl._find_live_host, mcpctl._post)
|
||||
|
||||
|
||||
def _no_net(*_a, **_k):
|
||||
raise AssertionError("这一步不该做网络探测")
|
||||
|
||||
|
||||
try:
|
||||
# ---------- 1. WSL 目标 + VM 未运行 -> 直接 skip,绝不探测 ----------
|
||||
mcpctl._wsl_running = lambda d: False
|
||||
mcpctl._find_live_host = _no_net
|
||||
h, pwd, skip = mcpctl._live_oc("wsl", args)
|
||||
check("WSL 未运行 -> skip 且不做探测",
|
||||
h == "" and bool(skip) and "WSL 未启动" in skip, str(skip))
|
||||
|
||||
# ---------- 2. WSL 在跑但 OC 没在跑 -> skip 带探测原因(不再是「失败」) ----------
|
||||
mcpctl._wsl_running = lambda d: True
|
||||
mcpctl._find_live_host = (
|
||||
lambda t, a, pw: ("", "/root/.config/opencode", "http://172.29.95.185:4096 连不上")
|
||||
)
|
||||
h, pwd, skip = mcpctl._live_oc("wsl", args)
|
||||
check("OC 未运行 -> skip 带原因", h == "" and bool(skip) and "连不上" in skip, str(skip))
|
||||
|
||||
# ---------- 3. OC 在跑 -> 不 skip,host 直接复用 ----------
|
||||
mcpctl._find_live_host = (
|
||||
lambda t, a, pw: ("http://127.0.0.1:4096", "/root/.config/opencode", "")
|
||||
)
|
||||
h, pwd, skip = mcpctl._live_oc("wsl", args)
|
||||
check("OC 运行中 -> 不 skip 且拿到 host",
|
||||
skip is None and h.startswith("http"), str((h, skip)))
|
||||
|
||||
# ---------- 4. do_reload 复用 host -> 不再探测,直接 reload 成功 ----------
|
||||
mcpctl._find_live_host = _no_net
|
||||
mcpctl._post = lambda url, pw, timeout, data=b"{}", method="POST": (200, "{}")
|
||||
ok, msg = mcpctl.do_reload("wsl", args, host="http://127.0.0.1:4096", password="x")
|
||||
check("do_reload 复用 host -> 热重载成功", ok and "热重载成功" in msg, msg)
|
||||
|
||||
# ---------- 5. OC 在跑但 reload 返回错误 -> 仍算真实失败(不能吞) ----------
|
||||
mcpctl._post = lambda url, pw, timeout, data=b"{}", method="POST": (0, "boom")
|
||||
ok, msg = mcpctl.do_reload("wsl", args, host="http://127.0.0.1:4096", password="x")
|
||||
check("OC 在跑但重载连不上 -> 仍报失败", (not ok) and "连不上" in msg, msg)
|
||||
|
||||
# ---------- 6. 本机目标 OC 没在跑 -> skip(本机目标不看 WSL 状态) ----------
|
||||
mcpctl._find_live_host = (
|
||||
lambda t, a, pw: ("", "C:/cfg", "没有找到运行中的 OC(4096 未监听,端口发现也未命中)")
|
||||
)
|
||||
h, pwd, skip = mcpctl._live_oc("local", args)
|
||||
check("local OC 未运行 -> skip", h == "" and bool(skip) and "没有找到" in skip, str(skip))
|
||||
finally:
|
||||
mcpctl._wsl_running, mcpctl._find_live_host, mcpctl._post = _orig
|
||||
|
||||
print()
|
||||
print(("ALL PASS" if not FAIL else "FAILURES: " + ", ".join(FAIL)))
|
||||
sys.exit(1 if FAIL else 0)
|
||||
@@ -5,8 +5,8 @@ mcpctl —— 命令行版的 MCP 配置对调开关(与 TUI 插件 / server
|
||||
|
||||
做什么:
|
||||
status 查看当前模式与 MCP 开/关状态(只读,不改任何文件)
|
||||
on 把配置对调为「含 MCP」的原始版,并调用 reload 热重载
|
||||
off 把配置对调为「不含 MCP」的安静版,并调用 reload 热重载
|
||||
on 把配置对调为「含 MCP」的原始版,OC 在跑则热重载(没跑就跳过)
|
||||
off 把配置对调为「不含 MCP」的安静版,OC 在跑则热重载(没跑就跳过)
|
||||
mode 查看或修改 mcpcfg.txt 模式键(自动 / 手动 / 关闭)
|
||||
|
||||
模式语义(与插件完全一致):
|
||||
@@ -551,10 +551,13 @@ def _find_live_host(target: str, args, password: str) -> tuple[str, str, str]:
|
||||
return "", directory, last or "没有找到运行中的 OC(4096 未监听,端口发现也未命中)"
|
||||
|
||||
|
||||
def do_reload(target: str, args) -> tuple[bool, str]:
|
||||
def do_reload(target: str, args, host: str | None = None,
|
||||
password: str | None = None) -> tuple[bool, str]:
|
||||
if password is None:
|
||||
password = args.password or _read_password(
|
||||
wsl_config_dir(args.distro) if target == "wsl" else _home_config()
|
||||
)
|
||||
if not host:
|
||||
# 端口发现 + 探活:先找到真正在跑的 OC(4096 是默认值,实测常是随机端口)
|
||||
host, _dir, why = _find_live_host(target, args, password)
|
||||
if not host:
|
||||
@@ -571,7 +574,8 @@ def do_reload(target: str, args) -> tuple[bool, str]:
|
||||
return False, f"{host} 连不上(OC 可能没在运行)"
|
||||
|
||||
|
||||
def push_mcp_config(args) -> tuple[bool, str]:
|
||||
def push_mcp_config(args, host: str | None = None,
|
||||
password: str | None = None) -> tuple[bool, str]:
|
||||
"""reload 之前的兜底:把活配置里的 mcp 直接 PATCH 进运行中的 OC。
|
||||
与 tui.js / ai-mcp.ts 的 pushMcpConfig 同一套链:
|
||||
端口发现 + GET /api/config?directory= 探活并取 shell(GET+merge 三级兜底)
|
||||
@@ -590,10 +594,15 @@ def push_mcp_config(args) -> tuple[bool, str]:
|
||||
if not isinstance(mcp, dict) or not ((mcp.get("servers") or {})):
|
||||
return False, "活配置里没有 mcp.servers,无可推送内容"
|
||||
|
||||
if password is None:
|
||||
password = args.password or _read_password(
|
||||
wsl_config_dir(args.distro) if args.target == "wsl" else _home_config()
|
||||
)
|
||||
# 1) 端口发现 + 探活:GET /api/config 200 = 命中运行中的 OC(4096 常无监听)
|
||||
# 调用方(_switch)已探活时直接复用 host,不重复探测
|
||||
if host:
|
||||
directory = _cfg_directory(args.target, args.distro)
|
||||
else:
|
||||
host, directory, why = _find_live_host(args.target, args, password)
|
||||
if not host:
|
||||
return False, why
|
||||
@@ -678,6 +687,31 @@ def cmd_status(args) -> int:
|
||||
return 0 if not cfg.get("error") else 2
|
||||
|
||||
|
||||
def _live_oc(target: str, args) -> tuple[str, str, str | None]:
|
||||
"""热重载前置探活(2026-09-25 用户反馈新增)。
|
||||
|
||||
返回 (host, password, skip_reason):
|
||||
skip_reason 为 None → host 是运行中的 OC,可直接热重载(复用,不二探);
|
||||
skip_reason 非 None → 没有可重载的 OC,调用方打印原因后按成功返回 ——
|
||||
WSL 没启动 / OC 没在跑时,配置文件已经对调好,下次冷启动 OC 自己就会
|
||||
加载,绝不能报「热重载失败」(如 WinError 10061 连接拒绝)。
|
||||
"""
|
||||
if target == "wsl":
|
||||
try:
|
||||
wsl_on = _wsl_running(args.distro)
|
||||
except Exception:
|
||||
wsl_on = False
|
||||
if not wsl_on:
|
||||
return "", "", "WSL 未启动(OC 自然也没在运行)"
|
||||
password = args.password or _read_password(
|
||||
wsl_config_dir(args.distro) if target == "wsl" else _home_config()
|
||||
)
|
||||
host, _dir, why = _find_live_host(target, args, password)
|
||||
if not host:
|
||||
return "", password, (why or "没有找到运行中的 OC")
|
||||
return host, password, None
|
||||
|
||||
|
||||
def _switch(args, want_on: bool) -> int:
|
||||
# 写操作:WSL 没跑先唤醒(gate 在任何 UNC .exists() 之前)
|
||||
err = wsl_gate(args, need_write=True)
|
||||
@@ -720,22 +754,30 @@ def _switch(args, want_on: bool) -> int:
|
||||
|
||||
print(f"配置已对调,MCP 现在是「{'开启' if after else '关闭'}」。")
|
||||
|
||||
if args.no_reload:
|
||||
print("已按 --no-reload 跳过热重载,需重启 OC 生效。")
|
||||
return 0
|
||||
|
||||
# 热重载前置探活(2026-09-25 用户反馈):WSL 没启动 / OC 没在跑就不重载、
|
||||
# 也不算失败 —— 配置文件已对调好,下次冷启动 OC 自己就会加载
|
||||
host, pwd, skip = _live_oc(args.target, args)
|
||||
if skip:
|
||||
print(f"无需热重载:{skip}。")
|
||||
print(" 配置已对调完成,等 OC 下次启动时会自动加载新配置。")
|
||||
return 0
|
||||
|
||||
# 兜底推送(仅开启方向):reload 之前先把 mcp 配置送进运行中的 OC,
|
||||
# 推送成功 = 双保险;推送失败不阻断,走 reload 主通道。
|
||||
# 推送失败不在这里就报负面消息 —— 它只是额外的双保险通道,
|
||||
# 是否真的生效由 reload 结果决定(避免「MCP 已可用却提示兜底」误导用户)。
|
||||
pushed = None
|
||||
pmsg = ""
|
||||
if want_on and not args.no_reload:
|
||||
pushed, pmsg = push_mcp_config(args)
|
||||
if want_on:
|
||||
pushed, pmsg = push_mcp_config(args, host=host, password=pwd)
|
||||
if pushed:
|
||||
print(f"mcp 配置已先推送进 OC:{pmsg}")
|
||||
|
||||
if args.no_reload:
|
||||
print("已按 --no-reload 跳过热重载,需重启 OC 生效。")
|
||||
return 0
|
||||
|
||||
ok, msg = do_reload(args.target, args)
|
||||
ok, msg = do_reload(args.target, args, host=host, password=pwd)
|
||||
if ok:
|
||||
if pushed:
|
||||
print(f"{msg},mcp 配置已先送入 + 热重载(双保险)。")
|
||||
|
||||
+93
-2
@@ -736,6 +736,79 @@ def _resolve_link_real(path: Path, target: str) -> Path | None:
|
||||
return None
|
||||
|
||||
|
||||
def _alt_skill_md(folder) -> list[str]:
|
||||
"""空壳/断链目录时,跨已知 skill 根目录找同名 skill 的 SKILL.md 真身。
|
||||
|
||||
2026-09-25 用户实测场景:来源目录(如 .config/opencode/skills/<name>)两端
|
||||
都是空壳、也没有任何链接,但大模型照样能调用该 skill —— 因为真身在另一个
|
||||
根(~/.workbuddy/skills/<name>/SKILL.md,即 wb 终端)。诊断必须把「原始文件
|
||||
位置」找出来,而不是只报「仍未找到」。
|
||||
|
||||
搜索根(可用环境变量 OC_SKILL_ALT_ROOTS 以 os.pathsep 追加):
|
||||
本机 ~/.workbuddy/skills、~/.config/opencode/skills(含 .trash 回收站)、
|
||||
当前目录 .workbuddy/skills;Windows 上另查 WSL 端对应两根(folder 是
|
||||
WSL UNC 直接查,否则 VM 在跑才查)。
|
||||
全程只读、任何一步失败跳过;返回找到的 SKILL.md 路径列表(去重,
|
||||
已排除「诊断对象自己」)。
|
||||
"""
|
||||
name = Path(folder).name
|
||||
if not name:
|
||||
return []
|
||||
home = Path.home()
|
||||
roots: list[Path] = [home / ".workbuddy" / "skills",
|
||||
home / ".config" / "opencode" / "skills"]
|
||||
extra = os.environ.get("OC_SKILL_ALT_ROOTS", "")
|
||||
roots += [Path(p) for p in extra.split(os.pathsep) if p.strip()]
|
||||
try:
|
||||
roots.append(Path.cwd() / ".workbuddy" / "skills")
|
||||
except OSError:
|
||||
pass
|
||||
if os.name == "nt":
|
||||
parts = _unc_distro_rest(Path(folder))
|
||||
if parts:
|
||||
wsl_on = True # folder 本身就在 WSL 里,同端根一定可查
|
||||
distro = parts[0]
|
||||
else:
|
||||
distro = DEFAULT_DISTRO
|
||||
try:
|
||||
wsl_on = _wsl_running(distro)
|
||||
except Exception:
|
||||
wsl_on = False
|
||||
if wsl_on:
|
||||
unc = Path("\\\\wsl.localhost\\%s" % distro)
|
||||
roots += [unc / "root" / ".workbuddy" / "skills",
|
||||
unc / "root" / ".config" / "opencode" / "skills"]
|
||||
|
||||
def _norm(p) -> str:
|
||||
return os.path.normcase(os.path.abspath(str(p)))
|
||||
|
||||
self_dir = _norm(folder)
|
||||
out: list[str] = []
|
||||
seen: set[str] = set()
|
||||
for r in roots:
|
||||
# <根>/<同名> + 回收站 <根>/.trash/<时间戳>/<同名>
|
||||
cands = [r / name]
|
||||
try:
|
||||
if (r / ".trash").is_dir():
|
||||
cands += [t / name for t in sorted((r / ".trash").iterdir())
|
||||
if t.is_dir()]
|
||||
except OSError:
|
||||
pass
|
||||
for cand in cands:
|
||||
if _norm(cand) == self_dir:
|
||||
continue # 就是诊断对象自己(已知空壳,别自己找自己)
|
||||
try:
|
||||
md = find_skill_md(cand)
|
||||
except OSError:
|
||||
md = None
|
||||
if md:
|
||||
key = _norm(md)
|
||||
if key not in seen:
|
||||
seen.add(key)
|
||||
out.append(str(md))
|
||||
return out
|
||||
|
||||
|
||||
def diag_skill_md(folder) -> list[str]:
|
||||
"""SKILL.md 缺失时的软链接诊断(只读,2026-09-24 按用户反馈新增)。
|
||||
|
||||
@@ -884,22 +957,40 @@ def diag_skill_md(folder) -> list[str]:
|
||||
timeout=20)
|
||||
except Exception:
|
||||
code, out = 1, b""
|
||||
if code == 0 and out:
|
||||
if code == 0:
|
||||
hits = 0
|
||||
for ln in out.decode("utf-8", "replace").splitlines():
|
||||
ln = ln.strip()
|
||||
if ln.startswith("MD "):
|
||||
wsl_md = ln[3:].strip()
|
||||
found_md.append(wsl_md)
|
||||
hits += 1
|
||||
notes.append(f" ✓ WSL 内找到 SKILL.md:{wsl_md}")
|
||||
elif ln.startswith("LINK "):
|
||||
hits += 1
|
||||
notes.append(f"WSL 内链接:{ln[5:].strip()}")
|
||||
if not hits:
|
||||
# 空结果也要说话(2026-09-25):静默会让人误以为二层没跑
|
||||
notes.append(" ℹ WSL 内二层已查:目录里没有 SKILL.md、也没有链接"
|
||||
"(WSL 内视角与 Windows 一致)")
|
||||
else:
|
||||
notes.append(f" ℹ WSL 内二层查询未完成(rc={code}),只按 Windows 视角给结论")
|
||||
else:
|
||||
# 不静默跳过(2026-09-24 单测暴露):说明二层没查,让用户知道还能再查
|
||||
notes.append(" ℹ WSL 当前未运行,跳过 WSL 内二层诊断"
|
||||
"(唤醒 WSL 后重试,可查出链接真身;VM 冷启动约 5–20 秒,不自动唤醒)")
|
||||
|
||||
# 结论
|
||||
# 结论:先跨已知根找同名真身(空壳目录的常见真相 —— 真身在 wb 等另一个根,
|
||||
# 大模型调用的正是那个文件),找不到才回退「仍未找到」
|
||||
if not found_md:
|
||||
alts = _alt_skill_md(folder)
|
||||
if alts:
|
||||
notes.append("→ 本目录里没有 SKILL.md(空壳/断链),但同名 skill 的真身在别处:")
|
||||
for a in alts:
|
||||
notes.append(f" {a}")
|
||||
notes.append(" 大模型实际调用的就是上面这些文件;推送请改选包含真身的目录"
|
||||
"(如 wb 终端)作为来源。")
|
||||
else:
|
||||
notes.append("→ 仍未找到 SKILL.md 的真实位置:目录可能是空壳 / 链接目标里也没有文件,"
|
||||
"建议换一个完整的 skill 来源再推。")
|
||||
return notes
|
||||
|
||||
Reference in New Issue
Block a user