feat: zcode thinking-mode 0.3.0(state历史+rollback+分写+校验收紧+34用例全绿+文档)
This commit is contained in:
@@ -0,0 +1,131 @@
|
||||
#!/usr/bin/env node
|
||||
/**
|
||||
* /sync-status:对比本机与远端最新快照差异(只读,不改本机)。
|
||||
* 用法: node status.mjs
|
||||
*/
|
||||
import fs from "node:fs";
|
||||
import path from "node:path";
|
||||
import {
|
||||
CLI_CONFIG, readJson, officialPlugins, enabledPlugins,
|
||||
customPlugins, hashDir, secretTargets, secretsManifest,
|
||||
ensureRepo, latestSnapshotDir, latestSnapshotName, readSnapshotManifest,
|
||||
extractModelSecrets, modelKeyManifest, listSnapshots, humanSize,
|
||||
} from "./sync-core.mjs";
|
||||
|
||||
function fail(e) { console.error(`状态检查失败:${e?.message ?? e}`); process.exitCode = 1; }
|
||||
|
||||
/**
|
||||
* --list:列出远端全部快照。
|
||||
* sync-import 的文档让用户"先 /sync-status 看远端有哪些(快照名)",
|
||||
* 但这里原本只有"本机 vs 最新一个"的对比,列不出来 —— 补上这条。
|
||||
*/
|
||||
function listAll() {
|
||||
ensureRepo();
|
||||
const all = listSnapshots();
|
||||
if (!all.length) { console.log("# 远端尚无快照\n- 先 /sync-export 推第一版"); return; }
|
||||
const latest = latestSnapshotName();
|
||||
const out = [`# 远端快照(${all.length} 个,合计 ${humanSize(all.reduce((a, s) => a + s.bytes, 0))})`];
|
||||
for (const s of all) {
|
||||
const mark = s.name === latest ? "*" : " ";
|
||||
out.push(`${mark} ${s.name}`);
|
||||
out.push(` ${s.host} @ ${s.createdAt ?? "?"} ${humanSize(s.bytes)}`
|
||||
+ (s.valid ? "" : " ⚠ manifest 损坏"));
|
||||
if (s.valid) {
|
||||
out.push(` 官方启用 ${s.enabledCount} / 自研 ${s.custom.length}${s.custom.length ? `(${s.custom.join(", ")})` : ""}`
|
||||
+ ` / MCP ${s.mcpCount} / 密钥 ${s.secretCount}`
|
||||
+ (s.modelProviderCount ? ` / 模型 provider ${s.modelProviderCount}` : ""));
|
||||
}
|
||||
}
|
||||
out.push(`- 标 * 的是 latest.json 指向的快照(/sync-import 不带参数时用的就是它)`);
|
||||
out.push(`- 导入指定版本:/sync-import <上面任意快照名>`);
|
||||
console.log(out.join("\n"));
|
||||
}
|
||||
|
||||
try {
|
||||
if (process.argv.includes("--list")) { listAll(); process.exit(0); }
|
||||
ensureRepo();
|
||||
const snapDir = latestSnapshotDir();
|
||||
if (!snapDir) {
|
||||
console.log("# 远端尚无快照\n- 先 /sync-export 推第一版");
|
||||
process.exit(0);
|
||||
}
|
||||
const m = readSnapshotManifest(snapDir);
|
||||
const out = [`# 本机 vs ${path.basename(snapDir)}`, `- 快照来源:${m.host} @ ${m.createdAt}`];
|
||||
|
||||
// 官方启用差异
|
||||
const localEn = new Set(enabledPlugins());
|
||||
const snapEn = new Set(m.enabled ?? []);
|
||||
const miss = [...snapEn].filter((e) => !localEn.has(e));
|
||||
const extra = [...localEn].filter((e) => !snapEn.has(e));
|
||||
out.push(`- 官方插件:快照启用 ${(m.enabled ?? []).length} 项,本机启用 ${localEn.size} 项,缺 ${miss.length}(${miss.join(", ") || "—"}),多 ${extra.length}(${extra.join(", ") || "—"})`);
|
||||
|
||||
// 自研插件差异(按 hash)
|
||||
const localCp = new Map(customPlugins().map((c) => [c.name, hashDir(c.dir)]));
|
||||
const snapCp = new Map((m.custom ?? []).map((c) => [c.name, c.hash]));
|
||||
const diffCp = [];
|
||||
for (const [name, h] of snapCp) {
|
||||
if (!localCp.has(name)) diffCp.push(`${name}(快照有/本机无)`);
|
||||
else if (localCp.get(name) !== h) diffCp.push(`${name}(版本不同)`);
|
||||
}
|
||||
for (const name of localCp.keys()) if (!snapCp.has(name)) diffCp.push(`${name}(本机有/快照无)`);
|
||||
out.push(`- 自研插件:${diffCp.length ? `差异 ${diffCp.length}:` + diffCp.join("; ") : "一致"}`);
|
||||
|
||||
// MCP server 名差异
|
||||
const cfg = readJson(CLI_CONFIG, {});
|
||||
const localMcp = new Set(Object.keys(cfg?.mcp?.servers ?? {}));
|
||||
const snapMcp = new Set(m.mcpServerNames ?? []);
|
||||
const mMiss = [...snapMcp].filter((s) => !localMcp.has(s));
|
||||
const mExtra = [...localMcp].filter((s) => !snapMcp.has(s));
|
||||
out.push(`- MCP:${mMiss.length || mExtra.length ? `缺 ${mMiss.join(", ") || "—"};多 ${mExtra.join(", ") || "—"}` : "server 名一致"}`);
|
||||
|
||||
// 密钥文件差异(只比清单 hash,不读值)
|
||||
const localSec = new Map(secretsManifest(secretTargets()).map((f) => [f.rel, f.sha256]));
|
||||
const snapSec = new Map((m.secretFiles ?? []).map((f) => [f.rel, f.sha256]));
|
||||
const sDiff = [];
|
||||
for (const [rel, h] of snapSec) {
|
||||
if (!localSec.has(rel)) sDiff.push(`${rel}(快照有/本机无)`);
|
||||
else if (localSec.get(rel) !== h) sDiff.push(`${rel}(内容不同)`);
|
||||
}
|
||||
for (const rel of localSec.keys()) if (!snapSec.has(rel)) sDiff.push(`${rel}(本机有/快照无)`);
|
||||
out.push(`- 密钥束:${sDiff.length ? `差异 ${sDiff.length}:` + sDiff.join("; ") : "清单一致"}`);
|
||||
|
||||
// 模型 provider 差异(按 id 比对,只比结构和模型数,不读任何密钥值)
|
||||
if ((m.modelProviders ?? []).length) {
|
||||
let localProvs = [];
|
||||
try {
|
||||
const { summarizeProviders, V2_CONFIG } = await import("./sync-core.mjs");
|
||||
localProvs = summarizeProviders(readJson(V2_CONFIG, {}));
|
||||
} catch { /* 读不到就当本机没有 */ }
|
||||
const snapP = new Map(m.modelProviders.map((p) => [p.id, p]));
|
||||
const locP = new Map(localProvs.map((p) => [p.id, p]));
|
||||
const pd = [];
|
||||
for (const [id, sp] of snapP) {
|
||||
const lp = locP.get(id);
|
||||
if (!lp) pd.push(`${id}(快照有/本机无)`);
|
||||
else if (JSON.stringify(lp.models) !== JSON.stringify(sp.models)) pd.push(`${id}(模型不同)`);
|
||||
}
|
||||
for (const id of locP.keys()) if (!snapP.has(id)) pd.push(`${id}(本机有/快照无)`);
|
||||
const needKey = m.modelProvidersWithSecrets ?? [];
|
||||
out.push(`- 模型 provider:${pd.length ? `差异 ${pd.length}:` + pd.join("; ") : `${snapP.size} 个一致`}`
|
||||
+ (needKey.length ? `;带密钥 ${needKey.length} 个` : ""));
|
||||
// 模型密钥清单对比(只比哈希,不读不打值)
|
||||
if ((m.modelKeys ?? []).length) {
|
||||
let localKeys = [];
|
||||
try {
|
||||
const { V2_CONFIG } = await import("./sync-core.mjs");
|
||||
localKeys = modelKeyManifest(extractModelSecrets(readJson(V2_CONFIG, {})));
|
||||
} catch { /* 读不到就当本机没有 */ }
|
||||
const snapK = new Map((m.modelKeys ?? []).map((k) => [k.rel, k.sha256]));
|
||||
const locK = new Map(localKeys.map((k) => [k.rel, k.sha256]));
|
||||
const kd = [];
|
||||
for (const [rel, h] of snapK) {
|
||||
if (!locK.has(rel)) kd.push(`${rel}(快照有/本机无)`);
|
||||
else if (locK.get(rel) !== h) kd.push(`${rel}(值不同)`);
|
||||
}
|
||||
for (const rel of locK.keys()) if (!snapK.has(rel)) kd.push(`${rel}(本机有/快照无)`);
|
||||
out.push(`- 模型密钥:${kd.length ? `差异 ${kd.length}:` + kd.join("; ") : `${locK.size} 个一致`}`);
|
||||
}
|
||||
}
|
||||
|
||||
console.log(out.join("\n"));
|
||||
} catch (e) { fail(e); }
|
||||
Reference in New Issue
Block a user