MCP 配置对调降级方案:热开关 + reload 热更新 + 命令全面中文化
- 新增 mcpcfg.txt 三行配置(模式/含MCP路径/安静路径),小白只改 txt - 模式语义:自动=启动归位+命令可用+server插件可切换;手动=仅 /mcpon /mcpoff;关闭=全部禁用 - /mcpon /mcpoff:对调两份配置后调用 location.reload 热重载,MCP 立即生效 - auto 模式启动自动把含 MCP 的活配置归位为安静版(6 秒后重载),保持零 toast 零报错 - enableMcp 加门控:活配置不含 mcp 时跳过 90s 重试 connect,避免 404 空转 - 全部命令标题/描述/dialog/toast 改为简体中文 - .gitignore 排除 ai-mcp/ 与 wsl-round-mcp/ 两个独立子仓库
This commit is contained in:
+12
-1
@@ -2,4 +2,15 @@ node_modules/
|
||||
!tui-plugins/oc-session-all-debug.js
|
||||
!tui-plugins/oc-session-all.js
|
||||
src/
|
||||
node_modules/
|
||||
|
||||
# 独立 git 仓库(分支名 = 文件夹名),各自独立推送到共用远程根目录,不纳入主仓库
|
||||
ai-mcp/
|
||||
wsl-round-mcp/
|
||||
|
||||
# Python 运行产物
|
||||
__pycache__/
|
||||
*.py[cod]
|
||||
|
||||
# 备份与本地回滚产物
|
||||
*.bak-*
|
||||
skills-backup/
|
||||
|
||||
+236
-56
@@ -8,7 +8,7 @@ import {
|
||||
readdirSync,
|
||||
statSync,
|
||||
} from "fs"
|
||||
import { join } from "path"
|
||||
import { join, basename } from "path"
|
||||
import { homedir } from "os"
|
||||
|
||||
// ── 日志(跨平台:Windows C:\Users\... / WSL /root)────────────────────
|
||||
@@ -27,7 +27,7 @@ function delay(ms) {
|
||||
return new Promise((resolve) => setTimeout(resolve, ms))
|
||||
}
|
||||
|
||||
// ── Helpers(移植自 v1)────────────────────────────────────────────────
|
||||
// ── Helpers ────────────────────────────────────────────────────────────
|
||||
function formatDateTime(ms) {
|
||||
const d = new Date(ms)
|
||||
const pad = (v) => String(v).padStart(2, "0")
|
||||
@@ -46,18 +46,161 @@ function projectDisplayName(session, projectMap) {
|
||||
return session.projectID ? session.projectID.slice(0, 8) : "unknown"
|
||||
}
|
||||
|
||||
// MCP server 名单:opencode.json 里 disabled:true 的项(启动无自动连接、无 toast),
|
||||
// 由本插件运行时 connect 启用(官方语义:overriding disabled config until restart)。
|
||||
// 注意:enabled 字段会被 OC 的 server schema 剥掉,真正生效的开关是 disabled。
|
||||
// 新增被禁用的 MCP server 时把名字加进来即可。
|
||||
const MCP_NAMES = ["baota-mcp"]
|
||||
// ── MCP 配置对调(mcpcfg.txt 三行驱动,小白只改这个 txt)─────────────────
|
||||
// 第1行:模式 auto/manual/off(中英别名),空或未知=自动
|
||||
// 自动 = 启动时把活配置归位为安静版(无 mcp,零 toast);
|
||||
// /mcpon /mcpoff 与大模型 server 插件都可切换(命令=手动切换一次状态)
|
||||
// 手动 = 启动不动文件,仅 /mcpon /mcpoff 触发对调(大模型插件会拒绝)
|
||||
// 关闭 = 本功能整体禁用(不注册命令、不 connect、不动文件)
|
||||
// 第2行:含 MCP 的原始配置路径
|
||||
// 第3行:删了 MCP 的备份(安静)配置路径
|
||||
// 对调 = 交换第2/3行两文件的内容,活文件永远是名为 opencode.json 的那份
|
||||
const CFG_TXT = join(LOG_DIR, "mcpcfg.txt")
|
||||
|
||||
function readMcpCfg() {
|
||||
try {
|
||||
if (!existsSync(CFG_TXT)) {
|
||||
return { mode: "off", missing: true, error: "缺少 mcpcfg.txt(配置缺失,功能视为关闭)" }
|
||||
}
|
||||
const lines = readFileSync(CFG_TXT, "utf8").split(/\r?\n/)
|
||||
const raw = (lines[0] || "").trim()
|
||||
const key = raw.toLowerCase()
|
||||
let mode
|
||||
if (["auto", "audo", "自动"].includes(key) || key === "") mode = "auto"
|
||||
else if (["manual", "hand", "on", "手动"].includes(key)) mode = "manual"
|
||||
else if (["off", "disable", "disabled", "关闭"].includes(key)) mode = "off"
|
||||
else mode = "auto"
|
||||
const onPath = (lines[1] || "").trim()
|
||||
const quietPath = (lines[2] || "").trim()
|
||||
// 活文件 = 两行中文件名为 opencode.json 的那份(OC 只读这个名字)
|
||||
let activePath = null
|
||||
let shadowPath = null
|
||||
if (basename(onPath) === "opencode.json") {
|
||||
activePath = onPath
|
||||
shadowPath = quietPath
|
||||
} else if (basename(quietPath) === "opencode.json") {
|
||||
activePath = quietPath
|
||||
shadowPath = onPath
|
||||
}
|
||||
const result = { mode, onPath, quietPath, activePath, shadowPath, missing: false }
|
||||
if (mode !== "off" && !activePath) {
|
||||
result.error = "mcpcfg.txt 第2/3行必须有一个是 opencode.json 的完整路径"
|
||||
} else if (mode !== "off" && (!existsSync(onPath) || !existsSync(quietPath))) {
|
||||
result.error = "mcpcfg.txt 第2/3行指向的文件不存在(请检查路径)"
|
||||
}
|
||||
return result
|
||||
} catch (e) {
|
||||
return { mode: "off", error: "读取 mcpcfg.txt 失败: " + e.message }
|
||||
}
|
||||
}
|
||||
|
||||
function activeHasMcp(activePath) {
|
||||
try {
|
||||
const j = JSON.parse(readFileSync(activePath, "utf8"))
|
||||
return !!(j.mcp && j.mcp.servers && Object.keys(j.mcp.servers).length > 0)
|
||||
} catch (e) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
// 交换两文件内容(对调)。状态判定只看活文件是否含 mcp,与行语义无关
|
||||
function swapFiles(a, b) {
|
||||
const ca = readFileSync(a, "utf8")
|
||||
const cb = readFileSync(b, "utf8")
|
||||
writeFileSync(a, cb)
|
||||
writeFileSync(b, ca)
|
||||
}
|
||||
|
||||
// reload 热重载:POST /api/location/reload(重建 location,重读磁盘配置 → MCP 生效)
|
||||
async function doReload(context) {
|
||||
const attempts = [
|
||||
() => context.client.location.reload({ location: context.location }),
|
||||
() => context.client.location.reload({}),
|
||||
() => context.client.location.reload(),
|
||||
]
|
||||
let lastErr = null
|
||||
for (const fn of attempts) {
|
||||
try {
|
||||
await fn()
|
||||
log("[oc-session-all] location.reload OK")
|
||||
return true
|
||||
} catch (e) {
|
||||
lastErr = e
|
||||
}
|
||||
}
|
||||
log("[oc-session-all] location.reload ERROR: " + (lastErr && lastErr.message))
|
||||
return false
|
||||
}
|
||||
|
||||
// 切换 MCP 开/关(对调 + reload)。返回 { ok, unchanged?, reloaded?, why }
|
||||
async function setMcp(context, wantOn) {
|
||||
const cfg = readMcpCfg()
|
||||
if (cfg.error) return { ok: false, why: cfg.error }
|
||||
if (cfg.mode === "off") return { ok: false, why: "MCP 开关功能已被关闭(mcpcfg.txt 第1行=关闭)" }
|
||||
const cur = activeHasMcp(cfg.activePath)
|
||||
if (cur === wantOn) {
|
||||
return { ok: true, unchanged: true, why: wantOn ? "MCP 已是开启状态" : "MCP 已是关闭(安静)状态" }
|
||||
}
|
||||
swapFiles(cfg.onPath, cfg.quietPath)
|
||||
const now = activeHasMcp(cfg.activePath)
|
||||
if (now !== wantOn) {
|
||||
log("[oc-session-all] swap verify FAILED want=" + wantOn + " got=" + now)
|
||||
return { ok: false, why: "配置对调后状态校验失败(请检查 mcpcfg.txt 两行路径)" }
|
||||
}
|
||||
log("[oc-session-all] config swapped -> " + (wantOn ? "WITH mcp" : "QUIET (no mcp)"))
|
||||
const reloaded = await doReload(context)
|
||||
return { ok: true, reloaded, why: reloaded ? null : "配置已对调,但热重载调用失败(重启 OC 生效)" }
|
||||
}
|
||||
|
||||
function toastResult(context, res, actionLabel) {
|
||||
let message
|
||||
if (!res.ok) message = actionLabel + "失败:" + res.why
|
||||
else if (res.unchanged) message = res.why
|
||||
else if (res.reloaded) message = actionLabel + "成功(配置已对调并热重载)"
|
||||
else message = actionLabel + "成功,但热重载未完成:" + res.why
|
||||
try {
|
||||
context.ui.toast.show({ message, variant: res.ok ? "info" : "error", duration: 4000 })
|
||||
} catch (e) {}
|
||||
log("[oc-session-all] " + message)
|
||||
}
|
||||
|
||||
export default Plugin.define({
|
||||
id: "oc-session-all",
|
||||
async setup(context) {
|
||||
log("[oc-session-all] v6 loaded — sessions picker + MCP runtime connect + skill hot-reload")
|
||||
log("[oc-session-all] v8 loaded — 会话选择器 + MCP配置对调(mcpcfg.txt) + 热重载 + skill热加载")
|
||||
|
||||
// ── MCP error toast 拦截(monkey-patch 兜底;主方案已改为 config 禁用)──
|
||||
// ── 读取 mcpcfg.txt(三行小白配置)────────────────────────────────
|
||||
const mcfg = readMcpCfg()
|
||||
const hasMcpNow = mcfg.activePath ? activeHasMcp(mcfg.activePath) : false
|
||||
log(
|
||||
"[oc-session-all] mcpcfg: mode=" +
|
||||
mcfg.mode +
|
||||
" active=" +
|
||||
(mcfg.activePath || "(none)") +
|
||||
" hasMcp=" +
|
||||
hasMcpNow +
|
||||
(mcfg.error ? " ERROR=" + mcfg.error : ""),
|
||||
)
|
||||
const featureOn = !mcfg.error && mcfg.mode !== "off"
|
||||
|
||||
// ── 自动模式:启动归位(把活配置换回安静版,保持平时零 toast)────────
|
||||
let bootSwapReload = false
|
||||
if (featureOn && mcfg.mode === "auto" && hasMcpNow) {
|
||||
try {
|
||||
swapFiles(mcfg.onPath, mcfg.quietPath)
|
||||
const still = activeHasMcp(mcfg.activePath)
|
||||
if (!still) {
|
||||
bootSwapReload = true
|
||||
log("[oc-session-all] 自动模式启动归位:活配置已对调为安静版(无 mcp)")
|
||||
} else {
|
||||
log("[oc-session-all] 启动归位校验失败(活配置仍含 mcp)")
|
||||
}
|
||||
} catch (e) {
|
||||
log("[oc-session-all] 启动归位 ERROR: " + e.message)
|
||||
}
|
||||
}
|
||||
|
||||
// ── MCP error toast 拦截(monkey-patch 兜底层)────────────────────
|
||||
try {
|
||||
const toast = context.ui.toast
|
||||
const orig = toast.show.bind(toast)
|
||||
@@ -73,7 +216,7 @@ export default Plugin.define({
|
||||
return orig({
|
||||
...opts,
|
||||
variant: "info",
|
||||
message: "MCP server offline (auto-reconnect when available)",
|
||||
message: "MCP 服务暂时不可用(恢复后自动重连)",
|
||||
duration: 2000,
|
||||
})
|
||||
}
|
||||
@@ -82,19 +225,38 @@ export default Plugin.define({
|
||||
}
|
||||
return orig(opts)
|
||||
}
|
||||
log("[oc-session-all] MCP toast interceptor installed (backup layer)")
|
||||
log("[oc-session-all] MCP toast 拦截已安装(兜底层)")
|
||||
} catch (e) {
|
||||
log("[oc-session-all] toast interceptor install ERROR: " + e.message)
|
||||
log("[oc-session-all] toast 拦截安装 ERROR: " + e.message)
|
||||
}
|
||||
|
||||
// ── MCP 运行时启用(config disabled:true + 插件 connect 覆盖)─────────
|
||||
// OC 启动后 location/config 异步就绪(实测约 4~5s),过早调用会 list 为空
|
||||
// → connect 返回 404 "MCP server not found"。这里每 2s 重试直到成功(上限 90s)。
|
||||
// ── 启动归位后热重载(让本次会话立即回到安静态)─────────────────────
|
||||
if (bootSwapReload) {
|
||||
void (async () => {
|
||||
await delay(6000) // 等 location/config 就绪(约4~5s)
|
||||
try {
|
||||
await doReload(context)
|
||||
} catch (e) {
|
||||
log("[oc-session-all] 启动归位 reload ERROR: " + e.message)
|
||||
}
|
||||
})()
|
||||
}
|
||||
|
||||
// ── MCP 运行时 connect(仅当活配置含 mcp 时才跑,避免 404 空转)──────
|
||||
const enableMcp = async () => {
|
||||
await delay(1500)
|
||||
const cfg = readMcpCfg()
|
||||
if (cfg.error || cfg.mode === "off") {
|
||||
log("[oc-session-all] enableMcp skipped: 功能关闭或配置缺失")
|
||||
return
|
||||
}
|
||||
if (!activeHasMcp(cfg.activePath)) {
|
||||
log("[oc-session-all] enableMcp skipped: 当前为安静状态(活配置无 mcp)")
|
||||
return
|
||||
}
|
||||
const deadline = Date.now() + 90000
|
||||
let attempt = 0
|
||||
let pending = MCP_NAMES.slice()
|
||||
let pending = ["baota-mcp"]
|
||||
while (pending.length && Date.now() < deadline) {
|
||||
attempt++
|
||||
try {
|
||||
@@ -123,8 +285,7 @@ export default Plugin.define({
|
||||
}
|
||||
void enableMcp()
|
||||
|
||||
// ── Skill 目录热加载(原生 watch 不覆盖"新增 skill 目录",此处轮询兜底)──
|
||||
// 标准目录(逆向 ConfigSkillPlugin):每个 config entry 下的 skill/ 与 skills/
|
||||
// ── Skill 目录热加载(原生 watch 不覆盖“新增 skill 目录”,此处轮询兜底)──
|
||||
const projDir = (context.location && context.location.directory) || process.cwd()
|
||||
const skillDirs = [
|
||||
join(LOG_DIR, "skill"),
|
||||
@@ -147,7 +308,6 @@ export default Plugin.define({
|
||||
parts.push(p + ":" + statSync(p).mtimeMs)
|
||||
} catch (e) {}
|
||||
if (ent.isDirectory()) {
|
||||
// 第二层:skills/<name>/SKILL.md 或 skills/<name>.md 的兄弟文件
|
||||
try {
|
||||
for (const f of readdirSync(p)) {
|
||||
const fp = join(p, f)
|
||||
@@ -176,8 +336,6 @@ export default Plugin.define({
|
||||
log("[oc-session-all] skill change detected (" + reason + ") -> forcing reload")
|
||||
let triggered = "none"
|
||||
try {
|
||||
// a) 优先尝试 PATCH /api/experimental/config(触发 config.updated →
|
||||
// ConfigSkillPlugin 全量 rescan → skill.reload() 推送客户端 invalidate)
|
||||
try {
|
||||
const maybePatch =
|
||||
(context.client.experimental &&
|
||||
@@ -192,7 +350,6 @@ export default Plugin.define({
|
||||
} catch (e) {
|
||||
log("[oc-session-all] config patch attempt ERROR: " + e.message)
|
||||
}
|
||||
// b) 回退:同内容重写 opencode.json(write 事件触发 config watcher)
|
||||
if (triggered === "none") {
|
||||
const cfg = join(LOG_DIR, "opencode.json")
|
||||
if (existsSync(cfg)) {
|
||||
@@ -201,7 +358,6 @@ export default Plugin.define({
|
||||
triggered = "rewrite"
|
||||
}
|
||||
}
|
||||
// c) 等服务端 rescan 完成后拉新列表
|
||||
await delay(600)
|
||||
const r = await context.client.skill.list({ location: context.location })
|
||||
const arr = (r && r.data) || r
|
||||
@@ -209,7 +365,7 @@ export default Plugin.define({
|
||||
log("[oc-session-all] skill reload via " + triggered + " -> skill.list = " + n + " skills")
|
||||
try {
|
||||
context.ui.toast.show({
|
||||
message: "Skills reloaded (" + n + ") — " + triggered,
|
||||
message: "技能已重载(" + n + " 个,方式:" + triggered + ")",
|
||||
variant: "info",
|
||||
duration: 3000,
|
||||
})
|
||||
@@ -230,14 +386,13 @@ export default Plugin.define({
|
||||
}
|
||||
}, 5000)
|
||||
|
||||
// ── Session 选择器(移植自 v1,改用 v2 dialog.select/router API)──
|
||||
// ── Session 选择器(v2 dialog.select/router API)───────────────────
|
||||
const openPicker = async () => {
|
||||
log("[oc-session-all] openPicker called")
|
||||
try {
|
||||
let sessions = []
|
||||
let projects = []
|
||||
|
||||
// 数据源 1:client SDK
|
||||
try {
|
||||
const r = await context.client.session.list({ limit: 1000, order: "desc" })
|
||||
sessions = (r && r.data) || r || []
|
||||
@@ -246,7 +401,6 @@ export default Plugin.define({
|
||||
log("[oc-session-all] client.session.list ERROR: " + e.message)
|
||||
}
|
||||
|
||||
// 数据源 2:data store 回退(内置 /sessions 也用它)
|
||||
if (!Array.isArray(sessions) || sessions.length === 0) {
|
||||
try {
|
||||
sessions = context.data.session.list() || []
|
||||
@@ -264,21 +418,19 @@ export default Plugin.define({
|
||||
}
|
||||
|
||||
if (!Array.isArray(sessions) || sessions.length === 0) {
|
||||
context.ui.toast.show({ message: "No sessions found", variant: "warning" })
|
||||
context.ui.toast.show({ message: "未找到任何会话", variant: "warning" })
|
||||
return
|
||||
}
|
||||
|
||||
const projectMap = new Map(
|
||||
(Array.isArray(projects) ? projects : []).map((p) => [p.id, p]),
|
||||
)
|
||||
// 过滤归档与子代理会话
|
||||
const visible = sessions.filter((s) => !s.time?.archived && !s.parentID)
|
||||
if (visible.length === 0) {
|
||||
context.ui.toast.show({ message: "No sessions found", variant: "warning" })
|
||||
context.ui.toast.show({ message: "未找到任何会话", variant: "warning" })
|
||||
return
|
||||
}
|
||||
|
||||
// 按项目分组
|
||||
const grouped = new Map()
|
||||
for (const session of visible) {
|
||||
const name = projectDisplayName(session, projectMap)
|
||||
@@ -286,28 +438,25 @@ export default Plugin.define({
|
||||
list.push(session)
|
||||
grouped.set(name, list)
|
||||
}
|
||||
// 组内按更新时间倒序
|
||||
for (const list of grouped.values()) {
|
||||
list.sort((a, b) => (b.time?.updated ?? 0) - (a.time?.updated ?? 0))
|
||||
}
|
||||
// 项目名排序
|
||||
const orderedProjects = [...grouped.keys()].sort((a, b) => a.localeCompare(b))
|
||||
|
||||
const options = orderedProjects.flatMap((project) =>
|
||||
grouped.get(project).map((session) => ({
|
||||
title: `${project} ${
|
||||
session.time?.updated ? formatDateTime(session.time.updated) : "unknown"
|
||||
} │ ${session.title || "Untitled"}`,
|
||||
session.time?.updated ? formatDateTime(session.time.updated) : "未知时间"
|
||||
} │ ${session.title || "无标题"}`,
|
||||
value: session.id,
|
||||
category: project,
|
||||
})),
|
||||
)
|
||||
log("[oc-session-all] picker options built: " + options.length)
|
||||
|
||||
// v2 dialog.select 返回 Promise<value|undefined>
|
||||
const selected = await context.ui.dialog.select({
|
||||
title: "All Sessions",
|
||||
placeholder: "Search sessions across all projects…",
|
||||
title: "全部会话",
|
||||
placeholder: "跨项目搜索并切换任意会话…",
|
||||
options,
|
||||
})
|
||||
log("[oc-session-all] dialog.select resolved: " + String(selected))
|
||||
@@ -318,39 +467,67 @@ export default Plugin.define({
|
||||
log("[oc-session-all] openPicker ERROR: " + (e.stack || e.message))
|
||||
try {
|
||||
context.ui.toast.show({
|
||||
message: "Failed to load sessions: " + e.message,
|
||||
message: "会话列表加载失败:" + e.message,
|
||||
variant: "error",
|
||||
})
|
||||
} catch (e2) {}
|
||||
}
|
||||
}
|
||||
|
||||
// ── 命令注册:官方 opencode.btw 插件同款写法 ──────────────────────
|
||||
// ── 命令注册(全部中文描述;模式=关闭时不注册 MCP 开关命令)───────────
|
||||
try {
|
||||
context.ui.slot({
|
||||
append: "app",
|
||||
render() {
|
||||
log("[oc-session-all] slot render called — registering keymap layer")
|
||||
try {
|
||||
return context.keymap.layer(() => ({
|
||||
mode: "global",
|
||||
commands: [
|
||||
const commands = [
|
||||
{
|
||||
id: "sessions.all",
|
||||
title: "浏览全部会话",
|
||||
description: "跨项目浏览并切换到任意历史会话(快捷键 Ctrl+O)",
|
||||
group: "会话",
|
||||
palette: true,
|
||||
bind: "ctrl+o",
|
||||
slash: {
|
||||
name: "sessions-all",
|
||||
aliases: ["resume-all", "continue-all"],
|
||||
},
|
||||
run() {
|
||||
void openPicker()
|
||||
},
|
||||
},
|
||||
]
|
||||
if (featureOn) {
|
||||
commands.push(
|
||||
{
|
||||
id: "sessions.all",
|
||||
title: "All Sessions",
|
||||
description: "Browse and switch to any session across all projects",
|
||||
group: "Session",
|
||||
id: "mcp.on",
|
||||
title: "开启 MCP",
|
||||
description:
|
||||
"把配置对调为含 MCP 的原始版并热重载(大模型可用 /mcpon 按需开启)",
|
||||
group: "MCP",
|
||||
palette: true,
|
||||
bind: "ctrl+o",
|
||||
slash: {
|
||||
name: "sessions-all",
|
||||
aliases: ["resume-all", "continue-all"],
|
||||
},
|
||||
slash: { name: "mcpon", aliases: ["mcp-on"] },
|
||||
run() {
|
||||
void openPicker()
|
||||
void setMcp(context, true).then((r) => toastResult(context, r, "开启 MCP"))
|
||||
},
|
||||
},
|
||||
],
|
||||
{
|
||||
id: "mcp.off",
|
||||
title: "关闭 MCP",
|
||||
description: "把配置对调回无 MCP 的安静版并热重载(恢复零提示)",
|
||||
group: "MCP",
|
||||
palette: true,
|
||||
slash: { name: "mcpoff", aliases: ["mcp-off"] },
|
||||
run() {
|
||||
void setMcp(context, false).then((r) => toastResult(context, r, "关闭 MCP"))
|
||||
},
|
||||
},
|
||||
)
|
||||
}
|
||||
return context.keymap.layer(() => ({
|
||||
mode: "global",
|
||||
commands,
|
||||
}))
|
||||
} catch (e) {
|
||||
log("[oc-session-all] layer inside render ERROR: " + e.message)
|
||||
@@ -358,7 +535,7 @@ export default Plugin.define({
|
||||
}
|
||||
},
|
||||
})
|
||||
log("[oc-session-all] ui.slot({append:'app'}) registered OK")
|
||||
log("[oc-session-all] ui.slot({append:'app'}) registered OK (mode=" + mcfg.mode + ")")
|
||||
} catch (e) {
|
||||
log("[oc-session-all] ui.slot ERROR: " + e.message)
|
||||
}
|
||||
@@ -370,6 +547,7 @@ export default Plugin.define({
|
||||
const s = JSON.stringify(cmds)
|
||||
const ok = s.includes("sessions.all") || s.includes("sessions-all")
|
||||
log("[oc-session-all] VERIFY commands() contains sessions-all: " + ok)
|
||||
if (featureOn) log("[oc-session-all] VERIFY mcpon present: " + s.includes("mcp.on"))
|
||||
if (!ok) log("[oc-session-all] VERIFY commands() dump: " + s.substring(0, 600))
|
||||
} catch (e) {
|
||||
log("[oc-session-all] VERIFY commands() ERROR: " + e.message)
|
||||
@@ -379,7 +557,9 @@ export default Plugin.define({
|
||||
await delay(3000)
|
||||
try {
|
||||
context.ui.toast.show({
|
||||
message: "oc-session-all v6 loaded — /sessions-all or Ctrl+O",
|
||||
message:
|
||||
"oc-session-all v8 已加载 — /sessions-all 浏览全部会话(Ctrl+O)" +
|
||||
(featureOn ? ",/mcpon 开启 MCP、/mcpoff 关闭 MCP" : ""),
|
||||
variant: "info",
|
||||
duration: 20000,
|
||||
})
|
||||
|
||||
Reference in New Issue
Block a user