TUI插件v10:MCP推送兜底改GET+merge补shell三跳,README同步安装路径与快捷键

This commit is contained in:
ypshidifu
2026-09-23 17:50:26 +08:00
parent 6e0ed1743a
commit 0f099c88d0
2 changed files with 57 additions and 15 deletions
+15 -4
View File
@@ -9,21 +9,25 @@ OpenCode TUI 插件:弹出全项目会话选择器,跨所有项目浏览和
- 过滤掉已归档和子代理会话
- 对调2份`opencode.json`实现拦截mcp报错的toast
- 热更新skill,新的skill丢入目录,插件自动发现并更新
- **MCP 推送兜底(v10)**:`config.get` 取 shell(GET→活文件→null 三级兜底)→
`config.update({config})` → `({body})` → `experimental.config.update` 三跳推进
运行中的 OC,配合 reload 双保险;`/mcpon` `/mcpoff` 可用
## 安装
在 `~/.config/opencode/tui.json` 中添加:
在 `~/.config/opencode/tui.json` 中添加(现役源码是 `.js`,部署副本放 `tui-plugins/` 下):
```json
{
"$schema": "https://opencode.ai/tui.json",
"plugin": [
"D:/桌面/源码/oc_session/tui-plugins/oc-session-all.ts"
"file:///C:/Users/<你>/.config/opencode/tui-plugins/oc-session-all.js"
]
}
```
重启 OpenCode 即可生效。
WSL 侧同理:`file:///root/.config/opencode/tui-plugins/oc-session-all.js`。
源码改动后需同步部署副本(本机 + WSL 两处,MD5 应与源码一致),重启 OpenCode 生效。
## 快捷键
@@ -31,13 +35,20 @@ OpenCode TUI 插件:弹出全项目会话选择器,跨所有项目浏览和
|--------|------|------|
| `Ctrl+o` | — | 打开会话选择器 |
| `/sessions-all` | `resume-all`, `continue-all` | 斜杠命令 |
| — | `/mcpon` `/mcpoff` | MCP 开关(对调 + 推送兜底 + 热重载) |
| — | 任意位置 `/home` 等 | CLI 菜单(`wsl-round-mcp/oc.py`)跳回顶层,见其 README |
## 项目结构
```
oc_session/
├── tui-plugins/
│ └── oc-session-all.ts ← 插件源码(OC 加载入口)
│ ├── oc-session-all.js ← 现役插件源码(OC 加载入口,v10)
│ ├── oc-session-all.ts ← 旧版留档
│ ├── oc-session-all-debug.js
│ └── readmcpcfg.test.mjs ← mcpcfg 解析 16 项单测(node readmcpcfg.test.mjs)
├── ai-mcp/ ← 独立 git 仓库(分支 ai-mcp),server 插件
├── wsl-round-mcp/ ← 独立 git 仓库(分支 wsl-round-mcp),Python CLI
├── package.json
├── node_modules/
├── .gitignore
+42 -11
View File
@@ -265,9 +265,11 @@ async function doReload(context) {
return false
}
// P2 兜底:reload 之前先把 mcp 配置 PATCH 进运行中的 OC(PATCH /config)。
// 这样「配置就位」不再依赖 reload 的读盘时序 —— 推送 + reload 双保险,
// 保证 MCP 百分百加载;推送失败也只降级为原有 reload 单通道,不影响对调。
// P2 兜底:reload 之前先把 mcp 配置 PATCH 进运行中的 OC。
// v10 改为 GET+merge:PATCH 的 zod schema 里 shell 是必填键(string|null),
// 只送 mcp 会 400「Missing key at ["shell"]」—— 就是「配置推送未生效」的根因。
// 链路:GET 当前 config 取 shell(容错 document 数组 / 扁平对象两形态)→
// 补 shell(GET 没带则用活文件值,再缺则 null)→ PATCH { shell, mcp }。
async function pushMcpConfig(context, activePath) {
try {
const j = JSON.parse(readFileSync(activePath, "utf8"))
@@ -275,17 +277,46 @@ async function pushMcpConfig(context, activePath) {
if (!mcp || !mcp.servers || !Object.keys(mcp.servers).length) return false
const errs = []
const cfgApi = context.client && context.client.config
if (cfgApi && typeof cfgApi.update === "function") {
// SDK 签名:parameters.config -> body(PATCH /config)
// 1) GET 当前 config(进程内 handler)→ 提取 document 的 info 作 base
let base = null
if (cfgApi && typeof cfgApi.get === "function") {
try {
await cfgApi.update({ config: mcp })
log("[oc-session-all] mcp 配置已推送进 OC(client.config.update → PATCH /config)")
const got = await cfgApi.get()
if (Array.isArray(got)) {
const doc =
got.find((d) => d && d.type === "document" && d.info) ||
got.find((d) => d && d.info)
base = (doc && doc.info) || null
} else if (got && typeof got === "object") {
base = got.info && typeof got.info === "object" ? got.info : got
}
} catch (e) {
errs.push("config.get: " + e.message)
}
} else {
errs.push("client.config.get 不存在")
}
// 2) 补 shell(必填键):GET 结果 → 活文件 → null,三级兜底
let shell = base && typeof base === "object" && "shell" in base ? base.shell : undefined
if (shell === undefined) shell = j && "shell" in j ? j.shell : null
if (typeof shell !== "string" && shell !== null) shell = null
const payload = { shell, mcp }
// 3) PATCH(shell 已补齐)
if (cfgApi && typeof cfgApi.update === "function") {
// SDK 签名:parameters.config -> body(进程内直调同一套 zod 校验)
try {
await cfgApi.update({ config: payload })
log(
"[oc-session-all] mcp 配置已推送进 OC(GET+merge → PATCH /config,shell=" +
JSON.stringify(shell) +
")",
)
return true
} catch (e) {
errs.push("config.update: " + e.message)
}
try {
await cfgApi.update({ body: { mcp } })
await cfgApi.update({ body: payload })
log("[oc-session-all] mcp 配置已推送进 OC(body 变体)")
return true
} catch (e) {
@@ -297,7 +328,7 @@ async function pushMcpConfig(context, activePath) {
const exp = context.client && context.client.experimental && context.client.experimental.config
if (exp && typeof exp.update === "function") {
try {
await exp.update({ mcp })
await exp.update(payload)
log("[oc-session-all] mcp 配置已推送进 OC(experimental.config.update)")
return true
} catch (e) {
@@ -370,7 +401,7 @@ function toastResult(context, res, actionLabel) {
export default Plugin.define({
id: "oc-session-all",
async setup(context) {
log("[oc-session-all] v9 loaded — 会话选择器 + MCP配置对调(mcpcfg.txt) + 推送兜底 + 热重载 + skill热加载")
log("[oc-session-all] v10 loaded — 会话选择器 + MCP配置对调(mcpcfg.txt) + 推送兜底(GET+merge补shell) + 热重载 + skill热加载")
// ── 读取 mcpcfg.txt(键值小白配置)────────────────────────────────
const mcfg = readMcpCfg()
@@ -784,7 +815,7 @@ export default Plugin.define({
try {
context.ui.toast.show({
message:
"oc-session-all v9 已加载 — /sessions-all 浏览全部会话(Ctrl+O)" +
"oc-session-all v10 已加载 — /sessions-all 浏览全部会话(Ctrl+O)" +
(featureOn ? ",/mcpon 开启 MCP、/mcpoff 关闭 MCP" : ""),
variant: "info",
duration: 20000,