Files
oc_session/README.md
T

213 lines
11 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# wsl-round-mcp — 本机 ⇄ WSL OpenCode 互通工具
打通**本机 OpenCode** 与 **WSL 里的 OpenCode**:skill 列表互查、skill 一键推送、MCP 配置对调开关。
全部零第三方依赖,只要装了 Python 3 就能跑。
---
## 一、双击就能用(小白版)
把整个 `wsl-round-mcp` 文件夹拷到任意位置,双击 **`oc.bat`** —— 只有这一个入口,
启动打印数字菜单,**输编号或中文都能进**:
```
============================================
OC 工具箱(输入编号或中文,0 退出)
============================================
0. 退出
1. 技能管理(skill 拉取 / 推送)
2. 连接器管理(MCP 开关 / 模式)
请选择:
```
- `1` / `技能管理` → 列表(1)、推送(2)、拉取(3)、目录(4);每层 `0` 返回
- `2` / `连接器管理` → 状态(1)、开启(2)、关闭(3)、模式(4)、备份(5)、`t` 切换本机/WSL
- **任意位置输入 `/home`(或「首页 / 返回首页 / 回首页 / 回主页 / 主菜单 / 主页」)直接跳回顶层菜单**——
菜单位、参数位(路径/技能名/新模式 的输入框)、回车前都算,不用一层层按 `0`;
在顶层菜单输 `/home` 会提示「已经在首页了」
- 菜单内所有操作会逐项问参数(路径、是否覆盖…),回车即用默认值
- 旧的 6 个分散 bat(skill-list / skill-push / …)已合并删除,功能全部并入本菜单
等价命令行(免菜单直传,写脚本时用):
```bash
python oc.py skill list # = 旧 skill-list.bat
python oc.py mcp status # = 旧 mcp-status.bat
python oc.py list # 自动识别归属
```
> bat 内部会自动 `chcp 65001`,中文输出不会乱码。
> 没装 Python 或没加进 PATH,会提示 `Python not found in PATH`。
---
## 二、命令行用法
### 1. `ocskill.py` — skill 互通
```bash
# 列出 WSL OC 的 skill(API 连不上会自动退回目录扫描)
python ocskill.py list
# 推送:压缩包(自动解压)
python ocskill.py push .\my-skill.zip
# 推送:整个 skill 目录
python ocskill.py push .\my-skill
# 推送:单个 SKILL.md(会自动包成以 name 命名的目录)
python ocskill.py push .\SKILL.md
# 推送:base64 内容
python ocskill.py push <一长串base64> --base64
# 只校验格式、不写入
python ocskill.py push .\my-skill --dry-run
# 覆盖同名 skill(先自动备份成 .bak-时间戳)
python ocskill.py push .\my-skill --force
# 反向:把 WSL 里的 skill 拷回本机
python ocskill.py pull java-expert --out .\skills-backup
# 看两端目录与 API 连接状态
python ocskill.py dirs
```
**push 的完整流程**(对应「先解码 → 确认标准 skill 格式 → 传入 skill 目录」):
1. **解码归一**:zip 就解压(带 zip-slip 目录穿越防护),目录原样用,
单个 `.md` 会包成目录,base64 字符串先解成文件再判断是 zip 还是 md。
2. **定位 `SKILL.md`**:允许压缩包里多套一层父目录(最深 3 层)。
3. **校验标准 skill 格式**,四项全过才算通过:
- 找得到 `SKILL.md`
- 文件以 `---` 开头,有 YAML frontmatter
- frontmatter 里有 `name`,且只含 `字母/数字/._-`
- frontmatter 里有 `description`(大模型靠它决定何时调用)
4. **写入** `\\wsl.localhost\<发行版>\root\.config\opencode\skills\<name>\`,
OC 会 watch 这个目录(约 100ms 去抖后自动 rescan 并推 `skill.reload`),**不用重启**。
常用参数:
| 参数 | 默认 | 说明 |
| --- | --- | --- |
| `--distro` | `Ubuntu-24.04` | WSL 发行版名 |
| `--url` | 自动探测 | 显式指定 OC 地址,如 `http://localhost:4096` |
| `--port` | `4096` | OC 端口(等价于环境变量 `OC_PORT`) |
| `--password` | 自动读 | OC 口令,读取链 `service.json` → 环境变量 `OPENCODE_PASSWORD`/`OPENCODE_SERVER_PASSWORD` → `opencode.json server.password`;鉴权统一 `Basic base64("opencode:<口令>")`(用户名固定 `opencode`)。端口 4096 没监听时会 `tasklist`+`netstat` 自动发现 OC 实际随机端口并探活 |
### 2. `mcpctl.py` — MCP 配置对调开关
```bash
python mcpctl.py status # 看状态(只读)
python mcpctl.py on # 对调成含 MCP + 热重载
python mcpctl.py off # 对调成安静版 + 热重载
python mcpctl.py mode # 看当前模式
python mcpctl.py mode 手动 # 改模式(自动/手动/关闭,或 auto/manual/off)
python mcpctl.py status --target wsl # 操作 WSL 那一份配置
python mcpctl.py off --no-reload # 只对调文件,不调用重载
```
它读的正是 `mcpcfg.txt`(键值格式,小白直接改值即可):
```
# MCP 开关配置:改完保存,重启 OC 生效
模式: 自动 ← 自动 / 手动 / 关闭
含MCP的配置: C:\...\opencode.json
安静配置: C:\...\opencode.json.2
```
兼容旧「裸三行」格式(第1行模式、第2行含MCP、第3行安静),
插件/CLI 读到旧格式会**自动迁移写回**成上面的键值格式;
两行路径指向的文件不存在时会**自动扫同目录 `opencode.json.*` 自愈**并写回,
避免路径写错把 `/mcpon` `/mcpoff` 锁死。
**状态判定只看活文件**(文件名为 `opencode.json` 的那份)里 `mcp.servers` 是否非空,
所以对调之后两行的身份怎么漂移都不影响判断——这与 TUI 插件、server 插件完全一致。
另外 `on`/`off` 成功后会维护 `mcp.on.flag`(开写、关切):
自动模式的「启动归位」见到标记就跳过,不会把刚开启的状态又换回去。
模式语义(三个入口共用同一套规则):
| 模式 | `/mcpon` `/mcpoff` | 大模型(server 插件) | 本工具 `mcpctl.py` |
| --- | --- | --- | --- |
| 自动 | 可用(视为手动切换一次) | 可用 | 可用 |
| 手动 | **仅此可用** | 拒绝 | 可用(等价于用户输入命令) |
| 关闭 | 不注册 | 拒绝 | 拒绝(可用 `--ignore-mode` 强行) |
热重载打的是 `POST /api/location/reload`,会重建 location 并重读磁盘配置,MCP 立即生效;
失败时配置文件已经对调完成,重启 OC 一样生效。
**开启方向多了「推送兜底」(双保险)**:`on` 在对调+写 flag 之后、reload 之前,
先把活配置里的 `mcp` 推进**运行中的 OC**。三端同一套语义——**GET+merge 补 shell
→ PATCH 真实路由**,按各自运行方式实现:
- **TUI 插件(v10,进程内 SDK)**:`client.config.get()` 取 shell →
`config.update({config})` → `({body})` → `experimental.config.update` 三跳;
- **server 插件(v3,raw HTTP)**:`GET /api/config?directory=` 取 shell →
`PATCH /api/experimental/config?directory=`,口令读 `service.json` 链、
`Basic base64("opencode:<口令>")`;
- **本 CLI(mcpctl)**:`tasklist`+`netstat` **端口发现**(OC 每次启动随机端口,
写死 4096 常 10061)→ `GET /api/config` 探活并取 `info.shell`(GET→活文件→null
三级兜底)→ `PATCH /api/experimental/config?directory=`,body 必含 `shell`
(缺则 400 `Missing key at ["shell"]`;裸 `PATCH /config` 是 405)。
推送成功再 reload = 双保险,MCP 百分百加载;推送失败不阻断,自动降级为原 reload 单通道。
### 稳定性设计(WSL 相关)
| 问题 | 修复 |
| --- | --- |
| WSL 路径假错「两键路径不存在 / mcpcfg.txt 不存在」 | mcpcfg 内部是 POSIX 路径(`/root/...`),Windows 上曾被解析成 `C:\root`。现在统一过 `fs_view()` 翻译成 `\\wsl.localhost\<发行版>\...` 再访问;报错与展示仍用**原文**(你在 txt 里看到的样子) |
| 参数顺序被 argparse 拒(`mcpctl status --target wsl` 报 unrecognized) | `_hoist_shared()` 在解析前把共享 flag(`--target/--distro/--port/--timeout/--password/--no-reload/--ignore-mode`)提到最前,**子命令前后都能写** |
| WSL 没跑时读配置卡死/假错 | `wsl_gate` 统一门控:**读写都先唤醒**——「先给机器启动起来,再去读配置」:status / mode 查看 / backup / `list` / `dirs` 等读操作,VM 没跑同样打印「正在唤醒」拉起 VM(超时 45s)再读,绝不秒回假错;唤醒失败给清晰指引 |
| `wsl.exe` 超时后进程永久卡死(必须 ctrl+C) | 所有 `wsl.exe` 调用改走 `_wsl_safe_run`:`stdin=DEVNULL` + stdout/stderr 落**临时文件**(零管道)+ 先查 `-l -q --running` 绝不无提示冷启动。根因是管道句柄被 wslrelay 等后代继承,client 被 kill 后 `communicate()` 等 EOF 永不返回 |
| 菜单被子命令 `raise SystemExit` 杀死 | `oc.py` 的 `run_safe()` 护栏:`KeyboardInterrupt` → 已取消回菜单;`SystemExit` → 报告信息回菜单;异常 → 打印错误回菜单,菜单本体永不退出 |
`list` / `dirs` 连接 OC 前会先打印「正在唤醒以便读配置…」(VM 没跑时)和
「正在探测 OpenCode API 连接…」进度行(flush),
几秒无响应时一眼看出是在唤醒/探测而不是死了;`list` 唤醒失败**绝不**回落到
UNC 目录扫描(那会触发冷启动卡死),只走 API 或明确报错。
---
## 三、连不上 OC 怎么办
**先看这一条(最常见):有 HTTP 代理时会假报 502。**
两个脚本已默认**绕过系统代理**直连(`ProxyHandler({})`)。
如果环境里有 `HTTP_PROXY` / `HTTPS_PROXY`,`urllib` 默认会把 `localhost` 请求也丢给代理,
结果是**代理回 502**,看起来像 OC 挂了、其实是被劫走了。
确实需要走代理时,设环境变量再运行:`MCPCTL_USE_PROXY=1` / `OCSKILL_USE_PROXY=1`。
其余排查:
1. 确认 WSL 里的 OC 起了:`opencode serve --port 4096`
2. **端口不对不用管**:OC 每次启动端口随机(实测如 49374),工具会 `tasklist`
找 `opencode.exe` 的 PID、`netstat -ano` 找它 LISTENING 的端口,
再逐候选 `GET /api/config` 探活(200 = 命中);仍要显式指定就加 `--port`
或环境变量 `OC_PORT`
3. 提示 `401 WWW-Authenticate: Basic realm="Secure Area"` → OC 启用了服务口令。
用 `--password` 传值(默认按 `service.json` → 环境变量
`OPENCODE_PASSWORD`/`OPENCODE_SERVER_PASSWORD` → `opencode.json server.password`
的顺序读,用户名固定 `opencode`、Basic 鉴权)。注意临时
`opencode serve --port X` 实例用**每次启动随机口令且不落盘**,`service.json`
读到的口令对它必 401——要么用 TUI 启动的 OC(走 `service.json`),要么
`--password` 传启动时设的口令
4. `localhost` 不通 → 工具会自动 `wsl hostname -I` 取 WSL 真实 IP 再试
5. 全都失败 → `list` 会自动降级为**直接扫目录**,照样能看到 skill 列表
---
## 四、和另外两个仓库的关系
本目录是独立 git 仓库,**分支名 = 文件夹名** `wsl-round-mcp`,远程与主仓库共用同一个根:
| 文件夹 | 分支 | 内容 |
| --- | --- | --- |
| `oc_session/`(主仓库) | `main` | TUI 插件 `oc-session-all`(`/sessions-all`、`/mcpon`、`/mcpoff`) |
| `oc_session/ai-mcp/` | `ai-mcp` | server 插件 `ai-mcp.ts`,给大模型用的 `mcp_switch` 工具 |
| `oc_session/wsl-round-mcp/` | `wsl-round-mcp` | 本目录:skill 互通 CLI + MCP 命令行开关 |
远程:`https://git.ypshidifu.cn/youpaishidifu/oc_session.git`