@hcmai/sdk 0.3.11 → 0.3.13
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.d.ts +176 -166
- package/dist/index.js +185 -160
- package/dist/index.js.map +1 -1
- package/package.json +9 -5
package/dist/index.js
CHANGED
|
@@ -592,6 +592,11 @@ var CliError = class extends Error {
|
|
|
592
592
|
const detailText = formatDetails(details);
|
|
593
593
|
if (detailText) out += `
|
|
594
594
|
\u8BE6\u60C5: ${detailText}`;
|
|
595
|
+
if (!detailText) {
|
|
596
|
+
const causeText = formatCause(this.cause);
|
|
597
|
+
if (causeText) out += `
|
|
598
|
+
\u539F\u56E0: ${causeText}`;
|
|
599
|
+
}
|
|
595
600
|
const nextStep = this.context?.nextStep;
|
|
596
601
|
if (typeof nextStep === "string" && nextStep) out += `
|
|
597
602
|
\u4E0B\u4E00\u6B65: ${nextStep}`;
|
|
@@ -657,6 +662,15 @@ function formatDetails(details) {
|
|
|
657
662
|
if (!text || text === "{}" || text === "[]") return void 0;
|
|
658
663
|
return text.length > 800 ? `${text.slice(0, 800)}\u2026\uFF08\u5DF2\u622A\u65AD\uFF09` : text;
|
|
659
664
|
}
|
|
665
|
+
function formatCause(cause) {
|
|
666
|
+
if (!cause) return void 0;
|
|
667
|
+
if (typeof cause === "string") return cause.trim().slice(0, 300) || void 0;
|
|
668
|
+
if (cause instanceof Error) {
|
|
669
|
+
const text = cause.message?.trim();
|
|
670
|
+
return text ? text.slice(0, 300) : void 0;
|
|
671
|
+
}
|
|
672
|
+
return void 0;
|
|
673
|
+
}
|
|
660
674
|
|
|
661
675
|
// src/config/env.ts
|
|
662
676
|
async function saveEnv(name, cfg) {
|
|
@@ -1129,6 +1143,167 @@ async function describe(http, model, _opts = {}) {
|
|
|
1129
1143
|
};
|
|
1130
1144
|
}
|
|
1131
1145
|
|
|
1146
|
+
// src/fde/fingerprint.ts
|
|
1147
|
+
import { createHash } from "crypto";
|
|
1148
|
+
var GATE_COMPONENTS = [
|
|
1149
|
+
"tenantMetaManifestDigest",
|
|
1150
|
+
"extensionSource",
|
|
1151
|
+
"modelListHash"
|
|
1152
|
+
];
|
|
1153
|
+
var BLIND_SPOTS = "\u4EA7\u54C1\u5347\u7EA7\uFF1Amanifest \u53EA\u8BFB\u79DF\u6237 workspace \u7684 metas/ \u76EE\u5F55\uFF0C\u4EA7\u54C1\u5347\u7EA7\u6539\u7684\u662F\u5B89\u88C5\u5305\u5185 _metas/\uFF0C\u56DB\u5143\u7EC4\u4E0D\u4F1A\u53D8\u2014\u2014\u8BE5\u573A\u666F\u7684\u9632\u7EBF\u53EA\u6709\u884C\u4E3A\u63A2\u9488\u4E00\u6761\u3002 | \u540C\u5B57\u8282\u6570\u5185\u5BB9\u66FF\u6362\uFF1A\u6E05\u5355\u53EA\u7ED9 name/size/lastModified\uFF0C\u628A\u6807\u7B7E\u300C\u90E8\u95E8\u300D\u6539\u6210\u300C\u673A\u6784\u300D\u5728 UTF-8 \u4E0B\u6070\u597D\u90FD\u662F 6 \u5B57\u8282\uFF0C\u6458\u8981\u4E0D\u53D8\u3002\u21D2 \u5B57\u6BB5\u540D\u53EB ManifestDigest \u800C\u975E Fingerprint\uFF0C\u540D\u5B57\u5FC5\u987B\u8BF4\u5B9E\u8BDD\u3002";
|
|
1154
|
+
var MODELS_PATH = "/api/system/models";
|
|
1155
|
+
var MANIFEST_PATH = "/api/tenant-meta/list";
|
|
1156
|
+
var VERSION_PATH = "/api/version/backend";
|
|
1157
|
+
var META_PATH = "/api/models/{model}/meta";
|
|
1158
|
+
var EXTENSION_SOURCE_HEADER = "x-hcm-extension-source";
|
|
1159
|
+
var MAX_SOURCE_PROBES = 50;
|
|
1160
|
+
var MAX_MANIFEST_PAGES = 200;
|
|
1161
|
+
var GATE_SOURCES = [MODELS_PATH, MANIFEST_PATH, META_PATH];
|
|
1162
|
+
function modelEntries(body) {
|
|
1163
|
+
if (!isPlainObject2(body)) return [];
|
|
1164
|
+
const raw = body.models;
|
|
1165
|
+
if (!Array.isArray(raw)) return [];
|
|
1166
|
+
return raw.filter(isPlainObject2);
|
|
1167
|
+
}
|
|
1168
|
+
function modelKeys(body) {
|
|
1169
|
+
return modelEntries(body).map((m) => typeof m.modelKey === "string" ? m.modelKey : "").filter((k) => k !== "").sort();
|
|
1170
|
+
}
|
|
1171
|
+
async function probeExtensionSource(probe, keys) {
|
|
1172
|
+
let attempted = 0;
|
|
1173
|
+
let answered = 0;
|
|
1174
|
+
for (const key of [...keys].sort().slice(0, MAX_SOURCE_PROBES)) {
|
|
1175
|
+
attempted += 1;
|
|
1176
|
+
const out = await probe.get(META_PATH.replace("{model}", key));
|
|
1177
|
+
if (out.error !== "ok") continue;
|
|
1178
|
+
answered += 1;
|
|
1179
|
+
const value = out.headers[EXTENSION_SOURCE_HEADER];
|
|
1180
|
+
if (value) return { sourceKey: value, probeModelKey: key, attempted, answered };
|
|
1181
|
+
}
|
|
1182
|
+
return { sourceKey: "", probeModelKey: "", attempted, answered };
|
|
1183
|
+
}
|
|
1184
|
+
async function fetchManifestRows(probe, failures) {
|
|
1185
|
+
const rows = [];
|
|
1186
|
+
let page = 0;
|
|
1187
|
+
const pageSize = 200;
|
|
1188
|
+
for (; ; ) {
|
|
1189
|
+
const out = await probe.get(MANIFEST_PATH, { page, pageSize });
|
|
1190
|
+
if (out.error !== "ok" || !isPlainObject2(out.body)) {
|
|
1191
|
+
failures.push(MANIFEST_PATH);
|
|
1192
|
+
break;
|
|
1193
|
+
}
|
|
1194
|
+
const body = out.body;
|
|
1195
|
+
const chunk = body.data ?? [];
|
|
1196
|
+
if (!Array.isArray(chunk)) {
|
|
1197
|
+
failures.push(MANIFEST_PATH);
|
|
1198
|
+
break;
|
|
1199
|
+
}
|
|
1200
|
+
rows.push(...chunk.filter(isPlainObject2));
|
|
1201
|
+
const totalPages = typeof body.totalPages === "number" ? body.totalPages : 0;
|
|
1202
|
+
page += 1;
|
|
1203
|
+
if (page >= totalPages || chunk.length === 0) break;
|
|
1204
|
+
if (page >= MAX_MANIFEST_PAGES) {
|
|
1205
|
+
failures.push(`${MANIFEST_PATH}#page-cap`);
|
|
1206
|
+
break;
|
|
1207
|
+
}
|
|
1208
|
+
}
|
|
1209
|
+
return rows;
|
|
1210
|
+
}
|
|
1211
|
+
function manifestDigest(rows) {
|
|
1212
|
+
const lines = rows.map((r) => `${r.name}|${r.size}|${r.lastModified}`).sort();
|
|
1213
|
+
return sha256(lines.join("\n"));
|
|
1214
|
+
}
|
|
1215
|
+
async function computeReconSourceKey(probe) {
|
|
1216
|
+
const out = await probe.get(MODELS_PATH);
|
|
1217
|
+
const p = await probeExtensionSource(probe, modelKeys(out.body));
|
|
1218
|
+
return p.sourceKey || "unknown";
|
|
1219
|
+
}
|
|
1220
|
+
async function computeFingerprint(probe, opts = {}) {
|
|
1221
|
+
const ttlDays = opts.ttlDays ?? 30;
|
|
1222
|
+
const now = opts.now ?? (() => /* @__PURE__ */ new Date());
|
|
1223
|
+
const failures = [];
|
|
1224
|
+
const modelsOut = await probe.get(MODELS_PATH);
|
|
1225
|
+
const modelsOk = modelsOut.error === "ok" && isPlainObject2(modelsOut.body);
|
|
1226
|
+
if (!modelsOk) failures.push(MODELS_PATH);
|
|
1227
|
+
const models = modelsOk ? modelEntries(modelsOut.body) : [];
|
|
1228
|
+
const keys = modelsOk ? modelKeys(modelsOut.body) : [];
|
|
1229
|
+
if (modelsOut.error === "ok" && keys.length === 0) {
|
|
1230
|
+
failures.push(`${MODELS_PATH}#empty-model-list`);
|
|
1231
|
+
}
|
|
1232
|
+
const p = await probeExtensionSource(probe, keys);
|
|
1233
|
+
let sourceKey = p.sourceKey;
|
|
1234
|
+
if (!sourceKey) {
|
|
1235
|
+
failures.push(
|
|
1236
|
+
p.answered > 0 ? `${META_PATH}#X-HCM-Extension-Source` : `${META_PATH}#no-meta-answered`
|
|
1237
|
+
);
|
|
1238
|
+
sourceKey = "unknown";
|
|
1239
|
+
}
|
|
1240
|
+
const hashLines = models.map((m) => `${m.modelKey ?? ""}|${m.type ?? ""}`).sort();
|
|
1241
|
+
const modelHash = sha256(hashLines.join("\n"));
|
|
1242
|
+
const rows = await fetchManifestRows(probe, failures);
|
|
1243
|
+
const versionOut = await probe.get(VERSION_PATH);
|
|
1244
|
+
let backendVersion;
|
|
1245
|
+
if (versionOut.error === "ok" && versionOut.body !== null && versionOut.body !== void 0) {
|
|
1246
|
+
backendVersion = versionOut.body;
|
|
1247
|
+
} else {
|
|
1248
|
+
failures.push(VERSION_PATH);
|
|
1249
|
+
backendVersion = { version: "unavailable" };
|
|
1250
|
+
}
|
|
1251
|
+
return {
|
|
1252
|
+
tenantMetaManifestDigest: manifestDigest(rows),
|
|
1253
|
+
manifestEntryCount: rows.length,
|
|
1254
|
+
extensionSource: sourceKey,
|
|
1255
|
+
// 🔴 记下**是从哪个模型探到的**:让「这个值是谁给的」在产物里留痕。
|
|
1256
|
+
extensionSourceProbe: p.probeModelKey,
|
|
1257
|
+
// 🔴 没有这两个数,`extensionSource: "unknown"` 就是一个**不可追问的结论**。
|
|
1258
|
+
extensionSourceProbeAttempted: p.attempted,
|
|
1259
|
+
extensionSourceProbeAnswered: p.answered,
|
|
1260
|
+
modelListHash: modelHash,
|
|
1261
|
+
modelCount: models.length,
|
|
1262
|
+
backendVersion,
|
|
1263
|
+
capabilities: probe.capabilities,
|
|
1264
|
+
createdAt: `${now().toISOString().slice(0, 19)}Z`,
|
|
1265
|
+
ttlDays,
|
|
1266
|
+
coverageNotes: {
|
|
1267
|
+
gateComponents: [...GATE_COMPONENTS],
|
|
1268
|
+
blindSpots: BLIND_SPOTS,
|
|
1269
|
+
// 空数组 = 三个端点都真取到了;非空 = 这份四元组是**残的**,
|
|
1270
|
+
// 上面那些 0 和空摘要不代表环境真的空。
|
|
1271
|
+
fetchFailures: failures
|
|
1272
|
+
}
|
|
1273
|
+
};
|
|
1274
|
+
}
|
|
1275
|
+
function compareFingerprint(recorded, current) {
|
|
1276
|
+
const diffs = GATE_COMPONENTS.filter((c) => recorded[c] !== current[c]);
|
|
1277
|
+
if (recorded.capabilities !== current.capabilities) diffs.push("capabilities");
|
|
1278
|
+
for (const [fp, tag] of [
|
|
1279
|
+
[recorded, "recorded"],
|
|
1280
|
+
[current, "current"]
|
|
1281
|
+
]) {
|
|
1282
|
+
const notes = fp.coverageNotes;
|
|
1283
|
+
if (!isPlainObject2(notes) || !("fetchFailures" in notes)) {
|
|
1284
|
+
diffs.push(`incomplete:${tag}:coverageNotes-missing`);
|
|
1285
|
+
continue;
|
|
1286
|
+
}
|
|
1287
|
+
const raw = notes.fetchFailures;
|
|
1288
|
+
const list = Array.isArray(raw) ? raw : [];
|
|
1289
|
+
const gateFailures = [...new Set(list.map(String))].filter((x) => GATE_SOURCES.some((s) => x.startsWith(s))).sort();
|
|
1290
|
+
for (const f of gateFailures) diffs.push(`incomplete:${tag}:${f}`);
|
|
1291
|
+
}
|
|
1292
|
+
return diffs;
|
|
1293
|
+
}
|
|
1294
|
+
function sha256(s) {
|
|
1295
|
+
return createHash("sha256").update(s, "utf8").digest("hex");
|
|
1296
|
+
}
|
|
1297
|
+
function isPlainObject2(v) {
|
|
1298
|
+
return typeof v === "object" && v !== null && !Array.isArray(v);
|
|
1299
|
+
}
|
|
1300
|
+
|
|
1301
|
+
// src/meta/models.ts
|
|
1302
|
+
async function listModels(http) {
|
|
1303
|
+
const resp = await http.get(MODELS_PATH);
|
|
1304
|
+
return modelEntries(resp.data?.data ?? resp.data);
|
|
1305
|
+
}
|
|
1306
|
+
|
|
1132
1307
|
// src/models/create.ts
|
|
1133
1308
|
async function create(http, model, data) {
|
|
1134
1309
|
const resp = await http.post(`/api/models/${model}`, data);
|
|
@@ -1378,6 +1553,10 @@ var HcmClient = class _HcmClient {
|
|
|
1378
1553
|
action(model, name, input) {
|
|
1379
1554
|
return action(this.http, model, name, input);
|
|
1380
1555
|
}
|
|
1556
|
+
/** 列出目标环境有哪些 Model —— `describe` 的上一步。 */
|
|
1557
|
+
listModels() {
|
|
1558
|
+
return listModels(this.http);
|
|
1559
|
+
}
|
|
1381
1560
|
describe(model, opts) {
|
|
1382
1561
|
return describe(this.http, model, opts);
|
|
1383
1562
|
}
|
|
@@ -4707,7 +4886,7 @@ function buildPushPlan(context, manifest2, localFiles, remoteFiles) {
|
|
|
4707
4886
|
for (const entry of status) {
|
|
4708
4887
|
const remote = remoteByLocal.get(entry.localPath);
|
|
4709
4888
|
const base = baseByLocal.get(entry.localPath);
|
|
4710
|
-
const remoteSha = remote ?
|
|
4889
|
+
const remoteSha = remote ? sha2562(remote.content.content) : void 0;
|
|
4711
4890
|
if (entry.status === "added") {
|
|
4712
4891
|
if (remote) {
|
|
4713
4892
|
conflicts.push(conflict(entry, "remote file already exists but was not in local snapshot"));
|
|
@@ -4840,7 +5019,7 @@ async function walk(root, relativeDir, files) {
|
|
|
4840
5019
|
localPath,
|
|
4841
5020
|
absolutePath,
|
|
4842
5021
|
content,
|
|
4843
|
-
sha256:
|
|
5022
|
+
sha256: sha2562(content),
|
|
4844
5023
|
size: Buffer.byteLength(content, "utf-8")
|
|
4845
5024
|
});
|
|
4846
5025
|
}
|
|
@@ -4953,7 +5132,7 @@ function localPathFromRemote(remotePrefix, remotePath) {
|
|
|
4953
5132
|
function isDirectoryMarkerPath(relativePath) {
|
|
4954
5133
|
return relativePath.endsWith("/.keep") || relativePath === ".keep";
|
|
4955
5134
|
}
|
|
4956
|
-
function
|
|
5135
|
+
function sha2562(content) {
|
|
4957
5136
|
return crypto.createHash("sha256").update(content, "utf-8").digest("hex");
|
|
4958
5137
|
}
|
|
4959
5138
|
async function writeText(file, content) {
|
|
@@ -5267,7 +5446,7 @@ function parseSettingAssignment(raw) {
|
|
|
5267
5446
|
}
|
|
5268
5447
|
|
|
5269
5448
|
// src/fde/layout.ts
|
|
5270
|
-
import { createHash as
|
|
5449
|
+
import { createHash as createHash3 } from "crypto";
|
|
5271
5450
|
import { mkdirSync, readFileSync as readFileSync2, writeFileSync } from "fs";
|
|
5272
5451
|
import * as path6 from "path";
|
|
5273
5452
|
var UNSAFE = /[^A-Za-z0-9._-]/g;
|
|
@@ -5276,7 +5455,7 @@ function slug(value) {
|
|
|
5276
5455
|
}
|
|
5277
5456
|
function safeSegment(value) {
|
|
5278
5457
|
const raw = value || "unknown";
|
|
5279
|
-
const digest =
|
|
5458
|
+
const digest = createHash3("sha256").update(raw, "utf8").digest("hex").slice(0, 10);
|
|
5280
5459
|
return `${slug(raw)}-${digest}`;
|
|
5281
5460
|
}
|
|
5282
5461
|
function tenantSegment(value) {
|
|
@@ -5391,161 +5570,6 @@ var LayoutContainmentError = class extends Error {
|
|
|
5391
5570
|
}
|
|
5392
5571
|
};
|
|
5393
5572
|
|
|
5394
|
-
// src/fde/fingerprint.ts
|
|
5395
|
-
import { createHash as createHash3 } from "crypto";
|
|
5396
|
-
var GATE_COMPONENTS = [
|
|
5397
|
-
"tenantMetaManifestDigest",
|
|
5398
|
-
"extensionSource",
|
|
5399
|
-
"modelListHash"
|
|
5400
|
-
];
|
|
5401
|
-
var BLIND_SPOTS = "\u4EA7\u54C1\u5347\u7EA7\uFF1Amanifest \u53EA\u8BFB\u79DF\u6237 workspace \u7684 metas/ \u76EE\u5F55\uFF0C\u4EA7\u54C1\u5347\u7EA7\u6539\u7684\u662F\u5B89\u88C5\u5305\u5185 _metas/\uFF0C\u56DB\u5143\u7EC4\u4E0D\u4F1A\u53D8\u2014\u2014\u8BE5\u573A\u666F\u7684\u9632\u7EBF\u53EA\u6709\u884C\u4E3A\u63A2\u9488\u4E00\u6761\u3002 | \u540C\u5B57\u8282\u6570\u5185\u5BB9\u66FF\u6362\uFF1A\u6E05\u5355\u53EA\u7ED9 name/size/lastModified\uFF0C\u628A\u6807\u7B7E\u300C\u90E8\u95E8\u300D\u6539\u6210\u300C\u673A\u6784\u300D\u5728 UTF-8 \u4E0B\u6070\u597D\u90FD\u662F 6 \u5B57\u8282\uFF0C\u6458\u8981\u4E0D\u53D8\u3002\u21D2 \u5B57\u6BB5\u540D\u53EB ManifestDigest \u800C\u975E Fingerprint\uFF0C\u540D\u5B57\u5FC5\u987B\u8BF4\u5B9E\u8BDD\u3002";
|
|
5402
|
-
var MODELS_PATH = "/api/system/models";
|
|
5403
|
-
var MANIFEST_PATH = "/api/tenant-meta/list";
|
|
5404
|
-
var VERSION_PATH = "/api/version/backend";
|
|
5405
|
-
var META_PATH = "/api/models/{model}/meta";
|
|
5406
|
-
var EXTENSION_SOURCE_HEADER = "x-hcm-extension-source";
|
|
5407
|
-
var MAX_SOURCE_PROBES = 50;
|
|
5408
|
-
var MAX_MANIFEST_PAGES = 200;
|
|
5409
|
-
var GATE_SOURCES = [MODELS_PATH, MANIFEST_PATH, META_PATH];
|
|
5410
|
-
function modelEntries(body) {
|
|
5411
|
-
if (!isPlainObject2(body)) return [];
|
|
5412
|
-
const raw = body.models;
|
|
5413
|
-
if (!Array.isArray(raw)) return [];
|
|
5414
|
-
return raw.filter(isPlainObject2);
|
|
5415
|
-
}
|
|
5416
|
-
function modelKeys(body) {
|
|
5417
|
-
return modelEntries(body).map((m) => typeof m.modelKey === "string" ? m.modelKey : "").filter((k) => k !== "").sort();
|
|
5418
|
-
}
|
|
5419
|
-
async function probeExtensionSource(probe, keys) {
|
|
5420
|
-
let attempted = 0;
|
|
5421
|
-
let answered = 0;
|
|
5422
|
-
for (const key of [...keys].sort().slice(0, MAX_SOURCE_PROBES)) {
|
|
5423
|
-
attempted += 1;
|
|
5424
|
-
const out = await probe.get(META_PATH.replace("{model}", key));
|
|
5425
|
-
if (out.error !== "ok") continue;
|
|
5426
|
-
answered += 1;
|
|
5427
|
-
const value = out.headers[EXTENSION_SOURCE_HEADER];
|
|
5428
|
-
if (value) return { sourceKey: value, probeModelKey: key, attempted, answered };
|
|
5429
|
-
}
|
|
5430
|
-
return { sourceKey: "", probeModelKey: "", attempted, answered };
|
|
5431
|
-
}
|
|
5432
|
-
async function fetchManifestRows(probe, failures) {
|
|
5433
|
-
const rows = [];
|
|
5434
|
-
let page = 0;
|
|
5435
|
-
const pageSize = 200;
|
|
5436
|
-
for (; ; ) {
|
|
5437
|
-
const out = await probe.get(MANIFEST_PATH, { page, pageSize });
|
|
5438
|
-
if (out.error !== "ok" || !isPlainObject2(out.body)) {
|
|
5439
|
-
failures.push(MANIFEST_PATH);
|
|
5440
|
-
break;
|
|
5441
|
-
}
|
|
5442
|
-
const body = out.body;
|
|
5443
|
-
const chunk = body.data ?? [];
|
|
5444
|
-
if (!Array.isArray(chunk)) {
|
|
5445
|
-
failures.push(MANIFEST_PATH);
|
|
5446
|
-
break;
|
|
5447
|
-
}
|
|
5448
|
-
rows.push(...chunk.filter(isPlainObject2));
|
|
5449
|
-
const totalPages = typeof body.totalPages === "number" ? body.totalPages : 0;
|
|
5450
|
-
page += 1;
|
|
5451
|
-
if (page >= totalPages || chunk.length === 0) break;
|
|
5452
|
-
if (page >= MAX_MANIFEST_PAGES) {
|
|
5453
|
-
failures.push(`${MANIFEST_PATH}#page-cap`);
|
|
5454
|
-
break;
|
|
5455
|
-
}
|
|
5456
|
-
}
|
|
5457
|
-
return rows;
|
|
5458
|
-
}
|
|
5459
|
-
function manifestDigest(rows) {
|
|
5460
|
-
const lines = rows.map((r) => `${r.name}|${r.size}|${r.lastModified}`).sort();
|
|
5461
|
-
return sha2562(lines.join("\n"));
|
|
5462
|
-
}
|
|
5463
|
-
async function computeReconSourceKey(probe) {
|
|
5464
|
-
const out = await probe.get(MODELS_PATH);
|
|
5465
|
-
const p = await probeExtensionSource(probe, modelKeys(out.body));
|
|
5466
|
-
return p.sourceKey || "unknown";
|
|
5467
|
-
}
|
|
5468
|
-
async function computeFingerprint(probe, opts = {}) {
|
|
5469
|
-
const ttlDays = opts.ttlDays ?? 30;
|
|
5470
|
-
const now = opts.now ?? (() => /* @__PURE__ */ new Date());
|
|
5471
|
-
const failures = [];
|
|
5472
|
-
const modelsOut = await probe.get(MODELS_PATH);
|
|
5473
|
-
const modelsOk = modelsOut.error === "ok" && isPlainObject2(modelsOut.body);
|
|
5474
|
-
if (!modelsOk) failures.push(MODELS_PATH);
|
|
5475
|
-
const models = modelsOk ? modelEntries(modelsOut.body) : [];
|
|
5476
|
-
const keys = modelsOk ? modelKeys(modelsOut.body) : [];
|
|
5477
|
-
if (modelsOut.error === "ok" && keys.length === 0) {
|
|
5478
|
-
failures.push(`${MODELS_PATH}#empty-model-list`);
|
|
5479
|
-
}
|
|
5480
|
-
const p = await probeExtensionSource(probe, keys);
|
|
5481
|
-
let sourceKey = p.sourceKey;
|
|
5482
|
-
if (!sourceKey) {
|
|
5483
|
-
failures.push(
|
|
5484
|
-
p.answered > 0 ? `${META_PATH}#X-HCM-Extension-Source` : `${META_PATH}#no-meta-answered`
|
|
5485
|
-
);
|
|
5486
|
-
sourceKey = "unknown";
|
|
5487
|
-
}
|
|
5488
|
-
const hashLines = models.map((m) => `${m.modelKey ?? ""}|${m.type ?? ""}`).sort();
|
|
5489
|
-
const modelHash = sha2562(hashLines.join("\n"));
|
|
5490
|
-
const rows = await fetchManifestRows(probe, failures);
|
|
5491
|
-
const versionOut = await probe.get(VERSION_PATH);
|
|
5492
|
-
let backendVersion;
|
|
5493
|
-
if (versionOut.error === "ok" && versionOut.body !== null && versionOut.body !== void 0) {
|
|
5494
|
-
backendVersion = versionOut.body;
|
|
5495
|
-
} else {
|
|
5496
|
-
failures.push(VERSION_PATH);
|
|
5497
|
-
backendVersion = { version: "unavailable" };
|
|
5498
|
-
}
|
|
5499
|
-
return {
|
|
5500
|
-
tenantMetaManifestDigest: manifestDigest(rows),
|
|
5501
|
-
manifestEntryCount: rows.length,
|
|
5502
|
-
extensionSource: sourceKey,
|
|
5503
|
-
// 🔴 记下**是从哪个模型探到的**:让「这个值是谁给的」在产物里留痕。
|
|
5504
|
-
extensionSourceProbe: p.probeModelKey,
|
|
5505
|
-
// 🔴 没有这两个数,`extensionSource: "unknown"` 就是一个**不可追问的结论**。
|
|
5506
|
-
extensionSourceProbeAttempted: p.attempted,
|
|
5507
|
-
extensionSourceProbeAnswered: p.answered,
|
|
5508
|
-
modelListHash: modelHash,
|
|
5509
|
-
modelCount: models.length,
|
|
5510
|
-
backendVersion,
|
|
5511
|
-
capabilities: probe.capabilities,
|
|
5512
|
-
createdAt: `${now().toISOString().slice(0, 19)}Z`,
|
|
5513
|
-
ttlDays,
|
|
5514
|
-
coverageNotes: {
|
|
5515
|
-
gateComponents: [...GATE_COMPONENTS],
|
|
5516
|
-
blindSpots: BLIND_SPOTS,
|
|
5517
|
-
// 空数组 = 三个端点都真取到了;非空 = 这份四元组是**残的**,
|
|
5518
|
-
// 上面那些 0 和空摘要不代表环境真的空。
|
|
5519
|
-
fetchFailures: failures
|
|
5520
|
-
}
|
|
5521
|
-
};
|
|
5522
|
-
}
|
|
5523
|
-
function compareFingerprint(recorded, current) {
|
|
5524
|
-
const diffs = GATE_COMPONENTS.filter((c) => recorded[c] !== current[c]);
|
|
5525
|
-
if (recorded.capabilities !== current.capabilities) diffs.push("capabilities");
|
|
5526
|
-
for (const [fp, tag] of [
|
|
5527
|
-
[recorded, "recorded"],
|
|
5528
|
-
[current, "current"]
|
|
5529
|
-
]) {
|
|
5530
|
-
const notes = fp.coverageNotes;
|
|
5531
|
-
if (!isPlainObject2(notes) || !("fetchFailures" in notes)) {
|
|
5532
|
-
diffs.push(`incomplete:${tag}:coverageNotes-missing`);
|
|
5533
|
-
continue;
|
|
5534
|
-
}
|
|
5535
|
-
const raw = notes.fetchFailures;
|
|
5536
|
-
const list = Array.isArray(raw) ? raw : [];
|
|
5537
|
-
const gateFailures = [...new Set(list.map(String))].filter((x) => GATE_SOURCES.some((s) => x.startsWith(s))).sort();
|
|
5538
|
-
for (const f of gateFailures) diffs.push(`incomplete:${tag}:${f}`);
|
|
5539
|
-
}
|
|
5540
|
-
return diffs;
|
|
5541
|
-
}
|
|
5542
|
-
function sha2562(s) {
|
|
5543
|
-
return createHash3("sha256").update(s, "utf8").digest("hex");
|
|
5544
|
-
}
|
|
5545
|
-
function isPlainObject2(v) {
|
|
5546
|
-
return typeof v === "object" && v !== null && !Array.isArray(v);
|
|
5547
|
-
}
|
|
5548
|
-
|
|
5549
5573
|
// src/fde/doctor.ts
|
|
5550
5574
|
var DOCTOR_ANCHORS = [
|
|
5551
5575
|
"connectivity",
|
|
@@ -9275,6 +9299,7 @@ export {
|
|
|
9275
9299
|
isServerSlidingSession,
|
|
9276
9300
|
listEnvs,
|
|
9277
9301
|
listIdentities,
|
|
9302
|
+
listModels,
|
|
9278
9303
|
listProfiles,
|
|
9279
9304
|
listTenantMeta,
|
|
9280
9305
|
listWorkspaceFiles,
|