@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.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 ? sha256(remote.content.content) : void 0;
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: sha256(content),
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 sha256(content) {
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 createHash2 } from "crypto";
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 = createHash2("sha256").update(raw, "utf8").digest("hex").slice(0, 10);
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,