修「重载了但模型还在」:热重载/启动时从最近使用与收藏历史(model.json)剔除停用条目,toast同步显示清理条数
This commit is contained in:
@@ -455,6 +455,74 @@ function mmReadPristineBody() {
|
||||
}
|
||||
}
|
||||
|
||||
// ── 「最近使用/收藏」历史清理 ───────────────────────────────────────────
|
||||
// /models 顶部的 Recent/Favorites 不是 catalog,而是 OC TUI 的使用历史:
|
||||
// ~/.local/state/opencode/model.json 的 recent/favorite 数组(providerID/modelID)。
|
||||
// 停用/删除厂商只改 DB catalog,历史不动 → 画面出现「说重载完了,模型还在」
|
||||
// 的前后矛盾(2026-10-20 实锤:xiaomi 已从 catalog 删除,但 model.json 的
|
||||
// recent 里还躺着 {providerID:"xiaomi",modelID:"mimo-v2.6-flash"})。
|
||||
// 每次热重载前/启动时按停用清单把命中条目清出去:
|
||||
// ① cfg「以下是停用列表」(provider 级,支持 * 通配)
|
||||
// ② model-manager.disabled.json(modelID 级镜像)
|
||||
// ③ model-manager.removed.json(已彻底删除的厂商)
|
||||
// 通配语义与 model-manager.ts 的 patMatch 完全一致(大小写不敏感,* 通配)。
|
||||
function mmPatMatch(pattern, text) {
|
||||
const p = String(pattern).toLowerCase()
|
||||
const t = String(text || "").toLowerCase()
|
||||
if (!p) return false
|
||||
if (p === t) return true
|
||||
if (p.includes("*")) {
|
||||
const re = new RegExp("^" + p.split("*").map((s) => s.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")).join(".*") + "$", "i")
|
||||
return re.test(t)
|
||||
}
|
||||
return false
|
||||
}
|
||||
function mmPruneModelState(reason) {
|
||||
try {
|
||||
const stop = mmReadStopList()
|
||||
const removed = mmReadRemovedList()
|
||||
let disabled = {} // modelID 级:provider -> [modelID]
|
||||
try {
|
||||
const dp = join(homedir(), ".config", "opencode", "model-manager.disabled.json")
|
||||
if (existsSync(dp)) {
|
||||
const j = JSON.parse(readFileSync(dp, "utf-8"))
|
||||
for (const k of Object.keys(j || {})) {
|
||||
if (Array.isArray(j[k])) disabled[k] = j[k].filter((x) => typeof x === "string")
|
||||
}
|
||||
}
|
||||
} catch {}
|
||||
if (!stop.length && !removed.length && !Object.keys(disabled).length) return 0
|
||||
const mp = join(homedir(), ".local", "state", "opencode", "model.json")
|
||||
if (!existsSync(mp)) return 0
|
||||
const st = JSON.parse(readFileSync(mp, "utf-8"))
|
||||
const hitProv = (pid) => {
|
||||
if (!pid) return false
|
||||
if (removed.includes(pid)) return true
|
||||
return stop.some((pat) => mmPatMatch(pat, pid))
|
||||
}
|
||||
const hit = (e) => {
|
||||
if (!e || typeof e !== "object") return false
|
||||
if (hitProv(e.providerID)) return true
|
||||
const arr = disabled[e.providerID]
|
||||
return Array.isArray(arr) && arr.includes(e.modelID)
|
||||
}
|
||||
const recent = Array.isArray(st.recent) ? st.recent : []
|
||||
const favorite = Array.isArray(st.favorite) ? st.favorite : []
|
||||
const nextRecent = recent.filter((e) => !hit(e))
|
||||
const nextFav = favorite.filter((e) => !hit(e))
|
||||
const n = recent.length - nextRecent.length + (favorite.length - nextFav.length)
|
||||
if (n <= 0) return 0
|
||||
st.recent = nextRecent
|
||||
st.favorite = nextFav
|
||||
writeFileSync(mp, JSON.stringify(st), "utf-8")
|
||||
log("[oc-session-all] 历史清理(" + reason + "): 从「最近使用/收藏」剔除停用条目 " + n + " 条 → " + mp)
|
||||
return n
|
||||
} catch (e) {
|
||||
log("[oc-session-all] 历史清理失败: " + (e.message || e))
|
||||
return 0
|
||||
}
|
||||
}
|
||||
|
||||
function mmEnqueue(op, payload) {
|
||||
try {
|
||||
const line = JSON.stringify({ op, payload: payload || {}, at: Date.now() })
|
||||
@@ -560,12 +628,19 @@ function mmStartFlushPoller(context) {
|
||||
return
|
||||
}
|
||||
log("[oc-session-all] flush 新标记 tag=" + f.lastOp + " → 执行 TUI 热重载")
|
||||
// 先清「最近使用/收藏」历史中的停用条目,再 reload —— 让画面与 catalog 一致
|
||||
// (修「前面说重载完毕,后面模型还在」:那条躺在 model.json 历史里,不归 catalog 管)
|
||||
const pruned = mmPruneModelState("flush:" + f.lastOp)
|
||||
await delay(500) // DB 写可见性缓冲
|
||||
const ok = await doReload(context)
|
||||
log("[oc-session-all] 模型目录热重载 " + (ok ? "OK" : "ERROR") + " tag=" + f.lastOp)
|
||||
log(
|
||||
"[oc-session-all] 模型目录热重载 " + (ok ? "OK" : "ERROR") + " tag=" + f.lastOp + (pruned ? "(历史清理 " + pruned + " 条)" : ""),
|
||||
)
|
||||
try {
|
||||
context.ui.toast.show({
|
||||
message: ok ? "模型目录已热重载,/models 立即生效" : "热重载失败(详见 oc-session-all.log)",
|
||||
message: ok
|
||||
? "模型目录已热重载" + (pruned ? ",清出停用历史 " + pruned + " 条" : "") + ",/models 立即生效"
|
||||
: "热重载失败(详见 oc-session-all.log)",
|
||||
variant: "info",
|
||||
duration: 4000,
|
||||
})
|
||||
@@ -960,8 +1035,9 @@ async function openModelManager(context) {
|
||||
export default Plugin.define({
|
||||
id: "oc-session-all",
|
||||
async setup(context) {
|
||||
log("[oc-session-all] v11 loaded — 会话选择器 + MCP配置对调(mcpcfg.txt) + 推送兜底(GET+merge补shell) + flush常驻热重载 + 模型停用持久化(cfg) + skill热加载")
|
||||
log("[oc-session-all] v12 loaded — 会话选择器 + MCP配置对调(mcpcfg.txt) + 推送兜底(GET+merge补shell) + flush常驻热重载 + 模型停用持久化(cfg) + skill热加载 + Recent历史清理")
|
||||
mmStartFlushPoller(context) // 常驻轮询 flush 标记:/manage 操作与后台对账回潮都靠它热重载
|
||||
mmPruneModelState("startup") // 启动即清「最近使用/收藏」历史中的停用条目(修「重载了但模型还在」)
|
||||
|
||||
// ── 读取 mcpcfg.txt(键值小白配置)────────────────────────────────
|
||||
const mcfg = readMcpCfg()
|
||||
|
||||
Reference in New Issue
Block a user