@hcmai/sdk 0.3.3 → 0.3.5
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 +276 -2
- package/dist/index.js +549 -28
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -516,6 +516,9 @@ var CliErrorCode = /* @__PURE__ */ ((CliErrorCode2) => {
|
|
|
516
516
|
CliErrorCode2["IDENTITY_NOT_IN_ENV"] = "IDENTITY_NOT_IN_ENV";
|
|
517
517
|
CliErrorCode2["TOKEN_EXPIRED"] = "TOKEN_EXPIRED";
|
|
518
518
|
CliErrorCode2["NO_ACTIVE_ENV"] = "NO_ACTIVE_ENV";
|
|
519
|
+
CliErrorCode2["SKILL_ID_CONFLICT"] = "SKILL_ID_CONFLICT";
|
|
520
|
+
CliErrorCode2["SKILL_SOURCE_UNREACHABLE"] = "SKILL_SOURCE_UNREACHABLE";
|
|
521
|
+
CliErrorCode2["SYNC_SOURCE_UNSUPPORTED"] = "SYNC_SOURCE_UNSUPPORTED";
|
|
519
522
|
return CliErrorCode2;
|
|
520
523
|
})(CliErrorCode || {});
|
|
521
524
|
var CODE_TO_EXIT = {
|
|
@@ -546,10 +549,14 @@ var CODE_TO_EXIT = {
|
|
|
546
549
|
["IDENTITY_ALREADY_EXISTS" /* IDENTITY_ALREADY_EXISTS */]: 5 /* CONFIG_ERROR */,
|
|
547
550
|
["IDENTITY_NOT_IN_ENV" /* IDENTITY_NOT_IN_ENV */]: 5 /* CONFIG_ERROR */,
|
|
548
551
|
["TOKEN_EXPIRED" /* TOKEN_EXPIRED */]: 2 /* AUTH_ERROR */,
|
|
549
|
-
["NO_ACTIVE_ENV" /* NO_ACTIVE_ENV */]: 5 /* CONFIG_ERROR
|
|
552
|
+
["NO_ACTIVE_ENV" /* NO_ACTIVE_ENV */]: 5 /* CONFIG_ERROR */,
|
|
553
|
+
["SKILL_ID_CONFLICT" /* SKILL_ID_CONFLICT */]: 3 /* BUSINESS_ERROR */,
|
|
554
|
+
// 🔴 装坏了不是网络问题:报 network 会让顾问先去查 VPN。
|
|
555
|
+
["SKILL_SOURCE_UNREACHABLE" /* SKILL_SOURCE_UNREACHABLE */]: 5 /* CONFIG_ERROR */,
|
|
556
|
+
["SYNC_SOURCE_UNSUPPORTED" /* SYNC_SOURCE_UNSUPPORTED */]: 1 /* USAGE_ERROR */
|
|
550
557
|
};
|
|
551
558
|
function exitCodeFor(code) {
|
|
552
|
-
return CODE_TO_EXIT[code]
|
|
559
|
+
return CODE_TO_EXIT[code] ?? 1 /* USAGE_ERROR */;
|
|
553
560
|
}
|
|
554
561
|
|
|
555
562
|
// src/errors/cli-error.ts
|
|
@@ -581,6 +588,10 @@ var CliError = class extends Error {
|
|
|
581
588
|
if (this.traceId) parts.push(`[traceId: ${this.traceId}]`);
|
|
582
589
|
if (this.profile) parts.push(`[profile: ${this.profile}]`);
|
|
583
590
|
let out = parts.join(" ");
|
|
591
|
+
const details = this.context?.details;
|
|
592
|
+
const detailText = formatDetails(details);
|
|
593
|
+
if (detailText) out += `
|
|
594
|
+
\u8BE6\u60C5: ${detailText}`;
|
|
584
595
|
const nextStep = this.context?.nextStep;
|
|
585
596
|
if (typeof nextStep === "string" && nextStep) out += `
|
|
586
597
|
\u4E0B\u4E00\u6B65: ${nextStep}`;
|
|
@@ -633,6 +644,19 @@ function fromAxiosError(err, profile) {
|
|
|
633
644
|
cause: err
|
|
634
645
|
});
|
|
635
646
|
}
|
|
647
|
+
function formatDetails(details) {
|
|
648
|
+
if (details === void 0 || details === null) return void 0;
|
|
649
|
+
if (typeof details === "string") return details.trim() || void 0;
|
|
650
|
+
if (typeof details === "number" || typeof details === "boolean") return String(details);
|
|
651
|
+
let text;
|
|
652
|
+
try {
|
|
653
|
+
text = JSON.stringify(details);
|
|
654
|
+
} catch {
|
|
655
|
+
return void 0;
|
|
656
|
+
}
|
|
657
|
+
if (!text || text === "{}" || text === "[]") return void 0;
|
|
658
|
+
return text.length > 800 ? `${text.slice(0, 800)}\u2026\uFF08\u5DF2\u622A\u65AD\uFF09` : text;
|
|
659
|
+
}
|
|
636
660
|
|
|
637
661
|
// src/config/env.ts
|
|
638
662
|
async function saveEnv(name, cfg) {
|
|
@@ -1044,18 +1068,18 @@ async function action(http, model, actionName, input) {
|
|
|
1044
1068
|
if (scope === "object" && !input.id) {
|
|
1045
1069
|
throw new Error(`object action ${model}.${actionName} requires id`);
|
|
1046
1070
|
}
|
|
1047
|
-
const
|
|
1071
|
+
const path7 = scope === "object" ? `/api/models/${model}/${input.id}/action/${actionName}` : `/api/models/${model}/action/${actionName}`;
|
|
1048
1072
|
const resp = await (async () => {
|
|
1049
1073
|
if (method === "GET") {
|
|
1050
|
-
return http.get(
|
|
1074
|
+
return http.get(path7, { params: queryParams(input) });
|
|
1051
1075
|
}
|
|
1052
1076
|
if (method === "DELETE") {
|
|
1053
|
-
return http.delete(
|
|
1077
|
+
return http.delete(path7, { params: queryParams(input) });
|
|
1054
1078
|
}
|
|
1055
1079
|
if (method === "PUT") {
|
|
1056
|
-
return http.put(
|
|
1080
|
+
return http.put(path7, scope === "object" ? objectBody(input) : classBody(input));
|
|
1057
1081
|
}
|
|
1058
|
-
return http.post(
|
|
1082
|
+
return http.post(path7, scope === "object" ? objectBody(input) : classBody(input));
|
|
1059
1083
|
})();
|
|
1060
1084
|
return resp.data;
|
|
1061
1085
|
}
|
|
@@ -1788,7 +1812,7 @@ var WsClient = class {
|
|
|
1788
1812
|
});
|
|
1789
1813
|
}
|
|
1790
1814
|
this.ws = new WebSocket(this.opts.endpoint);
|
|
1791
|
-
return new Promise((
|
|
1815
|
+
return new Promise((resolve4, reject) => {
|
|
1792
1816
|
const settle = (fn) => {
|
|
1793
1817
|
off();
|
|
1794
1818
|
fn();
|
|
@@ -1827,7 +1851,7 @@ var WsClient = class {
|
|
|
1827
1851
|
this.intentionalClose = false;
|
|
1828
1852
|
this.sessionId = msg.sessionId;
|
|
1829
1853
|
this.startHeartbeat();
|
|
1830
|
-
settle(
|
|
1854
|
+
settle(resolve4);
|
|
1831
1855
|
this.ws.on("message", this.persistentOnMessage);
|
|
1832
1856
|
this.ws.on("close", this.persistentOnClose);
|
|
1833
1857
|
this.ws.on("error", this.persistentOnClose);
|
|
@@ -2097,13 +2121,13 @@ async function sendMessageAndStream(ws, prompt, opts = {}) {
|
|
|
2097
2121
|
ws.send(cmd);
|
|
2098
2122
|
const events = {
|
|
2099
2123
|
[Symbol.asyncIterator]: () => ({
|
|
2100
|
-
next: () => new Promise((
|
|
2124
|
+
next: () => new Promise((resolve4) => {
|
|
2101
2125
|
if (queue.length) {
|
|
2102
|
-
|
|
2126
|
+
resolve4({ value: queue.shift(), done: false });
|
|
2103
2127
|
} else if (done) {
|
|
2104
|
-
|
|
2128
|
+
resolve4({ value: void 0, done: true });
|
|
2105
2129
|
} else {
|
|
2106
|
-
waiters.push(
|
|
2130
|
+
waiters.push(resolve4);
|
|
2107
2131
|
}
|
|
2108
2132
|
})
|
|
2109
2133
|
})
|
|
@@ -5011,18 +5035,18 @@ async function listTenantMeta(http, opts = {}) {
|
|
|
5011
5035
|
const items = payload?.items ?? payload?.list ?? payload?.records ?? [];
|
|
5012
5036
|
return { items, total: payload?.total, raw: payload };
|
|
5013
5037
|
}
|
|
5014
|
-
async function getTenantMeta(http,
|
|
5015
|
-
const resp = await http.get("/api/tenant-meta/meta", { params: { path:
|
|
5038
|
+
async function getTenantMeta(http, path7) {
|
|
5039
|
+
const resp = await http.get("/api/tenant-meta/meta", { params: { path: path7 } });
|
|
5016
5040
|
const payload = resp.data?.data ?? resp.data;
|
|
5017
5041
|
if (typeof payload === "string") return payload;
|
|
5018
5042
|
return String(payload?.content ?? "");
|
|
5019
5043
|
}
|
|
5020
|
-
async function saveTenantMeta(http,
|
|
5021
|
-
const resp = await http.put("/api/tenant-meta/meta", { content }, { params: { path:
|
|
5044
|
+
async function saveTenantMeta(http, path7, content) {
|
|
5045
|
+
const resp = await http.put("/api/tenant-meta/meta", { content }, { params: { path: path7 } });
|
|
5022
5046
|
return resp.data?.data ?? resp.data;
|
|
5023
5047
|
}
|
|
5024
|
-
async function deleteTenantMeta(http,
|
|
5025
|
-
const resp = await http.delete("/api/tenant-meta/meta", { params: { path:
|
|
5048
|
+
async function deleteTenantMeta(http, path7) {
|
|
5049
|
+
const resp = await http.delete("/api/tenant-meta/meta", { params: { path: path7 } });
|
|
5026
5050
|
return resp.data?.data ?? resp.data;
|
|
5027
5051
|
}
|
|
5028
5052
|
|
|
@@ -5124,14 +5148,14 @@ function resolveSkillInstallOrder(manifest2, targetId) {
|
|
|
5124
5148
|
throw invalid(`\u6280\u80FD\u76EE\u5F55\u4E2D\u4E0D\u5B58\u5728 ${targetId}`);
|
|
5125
5149
|
}
|
|
5126
5150
|
const state = /* @__PURE__ */ new Map();
|
|
5127
|
-
const
|
|
5151
|
+
const path7 = [];
|
|
5128
5152
|
const order = [];
|
|
5129
5153
|
const visit = (id, requiredBy) => {
|
|
5130
5154
|
const current = state.get(id);
|
|
5131
5155
|
if (current === "done") return;
|
|
5132
5156
|
if (current === "visiting") {
|
|
5133
|
-
const cycleStart =
|
|
5134
|
-
const cycle = [...
|
|
5157
|
+
const cycleStart = path7.indexOf(id);
|
|
5158
|
+
const cycle = [...path7.slice(Math.max(0, cycleStart)), id];
|
|
5135
5159
|
throw invalid(`\u6280\u80FD\u4F9D\u8D56\u5B58\u5728\u73AF: ${cycle.join(" -> ")}`);
|
|
5136
5160
|
}
|
|
5137
5161
|
const skill = byId.get(id);
|
|
@@ -5140,11 +5164,11 @@ function resolveSkillInstallOrder(manifest2, targetId) {
|
|
|
5140
5164
|
}
|
|
5141
5165
|
assertSafeSkillId(skill.id);
|
|
5142
5166
|
state.set(id, "visiting");
|
|
5143
|
-
|
|
5167
|
+
path7.push(id);
|
|
5144
5168
|
for (const required of normalizeRequirements(skill.requires, skill.id)) {
|
|
5145
5169
|
visit(required, skill.id);
|
|
5146
5170
|
}
|
|
5147
|
-
|
|
5171
|
+
path7.pop();
|
|
5148
5172
|
state.set(id, "done");
|
|
5149
5173
|
order.push(skill);
|
|
5150
5174
|
};
|
|
@@ -5235,30 +5259,517 @@ async function resetSettingItem(http, domain, namespace, settingKey, revision) {
|
|
|
5235
5259
|
function parseSettingAssignment(raw) {
|
|
5236
5260
|
const eq = raw.indexOf("=");
|
|
5237
5261
|
if (eq <= 0) throw new Error(`--set \u9700\u8981 namespace.key=value \u5F62\u6001\uFF0C\u6536\u5230 "${raw}"`);
|
|
5238
|
-
const
|
|
5262
|
+
const path7 = raw.slice(0, eq).trim();
|
|
5239
5263
|
const value = raw.slice(eq + 1);
|
|
5240
|
-
const dot =
|
|
5241
|
-
if (dot <= 0) throw new Error(`--set \u7684\u952E\u9700\u542B namespace\uFF0C\u5982 security.password.passwordPolicy=complex\uFF0C\u6536\u5230 "${
|
|
5242
|
-
return { namespace:
|
|
5264
|
+
const dot = path7.lastIndexOf(".");
|
|
5265
|
+
if (dot <= 0) throw new Error(`--set \u7684\u952E\u9700\u542B namespace\uFF0C\u5982 security.password.passwordPolicy=complex\uFF0C\u6536\u5230 "${path7}"`);
|
|
5266
|
+
return { namespace: path7.slice(0, dot), settingKey: path7.slice(dot + 1), value };
|
|
5267
|
+
}
|
|
5268
|
+
|
|
5269
|
+
// src/fde/layout.ts
|
|
5270
|
+
import { createHash as createHash2 } from "crypto";
|
|
5271
|
+
import { mkdirSync, readFileSync as readFileSync2, writeFileSync } from "fs";
|
|
5272
|
+
import * as path6 from "path";
|
|
5273
|
+
var UNSAFE = /[^A-Za-z0-9._-]/g;
|
|
5274
|
+
function slug(value) {
|
|
5275
|
+
return (value || "unknown").replace(UNSAFE, "_");
|
|
5276
|
+
}
|
|
5277
|
+
function safeSegment(value) {
|
|
5278
|
+
const raw = value || "unknown";
|
|
5279
|
+
const digest = createHash2("sha256").update(raw, "utf8").digest("hex").slice(0, 10);
|
|
5280
|
+
return `${slug(raw)}-${digest}`;
|
|
5281
|
+
}
|
|
5282
|
+
function tenantSegment(value) {
|
|
5283
|
+
return safeSegment(value);
|
|
5284
|
+
}
|
|
5285
|
+
function isInside(p, container) {
|
|
5286
|
+
const rel = path6.relative(container, p);
|
|
5287
|
+
if (rel === "") return true;
|
|
5288
|
+
if (path6.isAbsolute(rel)) return false;
|
|
5289
|
+
return !rel.split(path6.sep).includes("..");
|
|
5290
|
+
}
|
|
5291
|
+
function hasDotDotComponent(rel) {
|
|
5292
|
+
return rel.split(/[/\\]/).includes("..");
|
|
5293
|
+
}
|
|
5294
|
+
var ReconLayout = class {
|
|
5295
|
+
root;
|
|
5296
|
+
tenantId;
|
|
5297
|
+
sourceKey;
|
|
5298
|
+
dir;
|
|
5299
|
+
constructor(root, tenantId, sourceKey) {
|
|
5300
|
+
this.root = root;
|
|
5301
|
+
this.tenantId = String(tenantId);
|
|
5302
|
+
this.sourceKey = sourceKey;
|
|
5303
|
+
this.dir = path6.join(
|
|
5304
|
+
this.root,
|
|
5305
|
+
"recon",
|
|
5306
|
+
tenantSegment(this.tenantId),
|
|
5307
|
+
safeSegment(sourceKey)
|
|
5308
|
+
);
|
|
5309
|
+
const container = path6.resolve(this.root, "recon");
|
|
5310
|
+
if (!isInside(path6.resolve(this.dir), container)) {
|
|
5311
|
+
throw new LayoutContainmentError(
|
|
5312
|
+
`\u79DF\u6237\u6BB5\u8D8A\u754C\uFF0C\u62D2\u7EDD\u6784\u9020\u4EA7\u7269\u76EE\u5F55\uFF1AtenantId=${JSON.stringify(tenantId)}\uFF08\u4EA7\u7269\u6839 ${container}\uFF09`
|
|
5313
|
+
);
|
|
5314
|
+
}
|
|
5315
|
+
}
|
|
5316
|
+
/**
|
|
5317
|
+
* 把相对路径解析成绝对路径,并过两道**互相印证**的遏制闸。
|
|
5318
|
+
*
|
|
5319
|
+
* 🔴 `rel` 里会嵌入**现爬的 modelKey**:`meta/<k>.model.json`、`valuedomain/<k>.json`、
|
|
5320
|
+
* `mapping/<k>.json`。服务端说什么,这里就拼什么。
|
|
5321
|
+
*
|
|
5322
|
+
* 实测分档(沿用 Python 侧 2026-08-16 的实跑结论):
|
|
5323
|
+
* `../evil` → 规范化后**仍在产物根内**,写成 `<产物根>/evil.json`。第二道闸**拦不住**。
|
|
5324
|
+
* `../models` → 同上,**顶掉 recon 自己的模型名册**,写入方拿到「成功」,产物里没有留痕。
|
|
5325
|
+
* `../../evil` → 真的逃出产物根 ⇒ 第二道闸拦得住。
|
|
5326
|
+
* `/etc/passwd` → 前导 `/` 被路径规范化吃掉,落在 `valuedomain/etc/passwd.json`。
|
|
5327
|
+
* ⇒ 真正的危害不是「逃出产物根」(那一档本来就拦得住),是「**逃出预期子目录、
|
|
5328
|
+
* 静默顶掉同级产物**」——`models.json` / `fingerprint.json` / `gates.json` 全在射程内。
|
|
5329
|
+
*
|
|
5330
|
+
* ⚠️ 拒绝而不是消毒:消毒会让 `meta/<modelKey>.json` 不再能按 modelKey grep 回去。
|
|
5331
|
+
*/
|
|
5332
|
+
resolvePath(rel, createParents = true) {
|
|
5333
|
+
const p = path6.join(this.dir, rel);
|
|
5334
|
+
if (hasDotDotComponent(rel)) {
|
|
5335
|
+
throw new LayoutContainmentError(
|
|
5336
|
+
`\u4EA7\u7269\u76F8\u5BF9\u8DEF\u5F84\u542B \`..\` \u5206\u91CF\uFF0C\u62D2\u7EDD\u5199\u5165\uFF1A${JSON.stringify(rel)}\uFF08\u4F1A\u8D8A\u51FA\u9884\u671F\u5B50\u76EE\u5F55\u3001\u9759\u9ED8\u9876\u6389\u540C\u7EA7\u4EA7\u7269\uFF1B\u4EA7\u7269\u6839 ${this.dir}\uFF09`
|
|
5337
|
+
);
|
|
5338
|
+
}
|
|
5339
|
+
if (!isInside(path6.resolve(p), path6.resolve(this.dir))) {
|
|
5340
|
+
throw new LayoutContainmentError(
|
|
5341
|
+
`\u4EA7\u7269\u76F8\u5BF9\u8DEF\u5F84\u8D8A\u754C\uFF0C\u62D2\u7EDD\u5199\u5165\uFF1A${JSON.stringify(rel)}\uFF08\u4EA7\u7269\u6839 ${this.dir}\uFF09`
|
|
5342
|
+
);
|
|
5343
|
+
}
|
|
5344
|
+
if (createParents) {
|
|
5345
|
+
mkdirSync(path6.dirname(p), { recursive: true });
|
|
5346
|
+
}
|
|
5347
|
+
return p;
|
|
5348
|
+
}
|
|
5349
|
+
writeJson(rel, obj) {
|
|
5350
|
+
const p = this.resolvePath(rel);
|
|
5351
|
+
writeFileSync(p, JSON.stringify(obj, null, 2), "utf8");
|
|
5352
|
+
return p;
|
|
5353
|
+
}
|
|
5354
|
+
writeJsonl(rel, rows) {
|
|
5355
|
+
const p = this.resolvePath(rel);
|
|
5356
|
+
let body = "";
|
|
5357
|
+
for (const row of rows) body += `${JSON.stringify(row)}
|
|
5358
|
+
`;
|
|
5359
|
+
writeFileSync(p, body, "utf8");
|
|
5360
|
+
return p;
|
|
5361
|
+
}
|
|
5362
|
+
writeText(rel, text) {
|
|
5363
|
+
const p = this.resolvePath(rel);
|
|
5364
|
+
writeFileSync(p, text, "utf8");
|
|
5365
|
+
return p;
|
|
5366
|
+
}
|
|
5367
|
+
/**
|
|
5368
|
+
* 逐行读一份 JSONL 产物。
|
|
5369
|
+
*
|
|
5370
|
+
* 🔴 与 `readJson` 同走那两道越界闸——同一个收口点不能一半有闸一半没有。
|
|
5371
|
+
* 🔴 **跳过空行**(末尾换行是 `writeJsonl` 必然产生的),但**不跳过坏行**:
|
|
5372
|
+
* 坏行原样抛,因为「这一行读不出来」和「这个产物没有这一行」是两件事。
|
|
5373
|
+
*/
|
|
5374
|
+
readJsonlRows(rel) {
|
|
5375
|
+
const body = readFileSync2(this.resolvePath(rel, false), "utf8");
|
|
5376
|
+
const rows = [];
|
|
5377
|
+
for (const line of body.split("\n")) {
|
|
5378
|
+
const trimmed = line.trim();
|
|
5379
|
+
if (trimmed) rows.push(JSON.parse(trimmed));
|
|
5380
|
+
}
|
|
5381
|
+
return rows;
|
|
5382
|
+
}
|
|
5383
|
+
readJson(rel) {
|
|
5384
|
+
return JSON.parse(readFileSync2(this.resolvePath(rel, false), "utf8"));
|
|
5385
|
+
}
|
|
5386
|
+
};
|
|
5387
|
+
var LayoutContainmentError = class extends Error {
|
|
5388
|
+
constructor(message) {
|
|
5389
|
+
super(message);
|
|
5390
|
+
this.name = "LayoutContainmentError";
|
|
5391
|
+
}
|
|
5392
|
+
};
|
|
5393
|
+
|
|
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 computeFingerprint(probe, opts = {}) {
|
|
5464
|
+
const ttlDays = opts.ttlDays ?? 30;
|
|
5465
|
+
const now = opts.now ?? (() => /* @__PURE__ */ new Date());
|
|
5466
|
+
const failures = [];
|
|
5467
|
+
const modelsOut = await probe.get(MODELS_PATH);
|
|
5468
|
+
const modelsOk = modelsOut.error === "ok" && isPlainObject2(modelsOut.body);
|
|
5469
|
+
if (!modelsOk) failures.push(MODELS_PATH);
|
|
5470
|
+
const models = modelsOk ? modelEntries(modelsOut.body) : [];
|
|
5471
|
+
const keys = modelsOk ? modelKeys(modelsOut.body) : [];
|
|
5472
|
+
if (modelsOut.error === "ok" && keys.length === 0) {
|
|
5473
|
+
failures.push(`${MODELS_PATH}#empty-model-list`);
|
|
5474
|
+
}
|
|
5475
|
+
const p = await probeExtensionSource(probe, keys);
|
|
5476
|
+
let sourceKey = p.sourceKey;
|
|
5477
|
+
if (!sourceKey) {
|
|
5478
|
+
failures.push(
|
|
5479
|
+
p.answered > 0 ? `${META_PATH}#X-HCM-Extension-Source` : `${META_PATH}#no-meta-answered`
|
|
5480
|
+
);
|
|
5481
|
+
sourceKey = "unknown";
|
|
5482
|
+
}
|
|
5483
|
+
const hashLines = models.map((m) => `${m.modelKey ?? ""}|${m.type ?? ""}`).sort();
|
|
5484
|
+
const modelHash = sha2562(hashLines.join("\n"));
|
|
5485
|
+
const rows = await fetchManifestRows(probe, failures);
|
|
5486
|
+
const versionOut = await probe.get(VERSION_PATH);
|
|
5487
|
+
let backendVersion;
|
|
5488
|
+
if (versionOut.error === "ok" && versionOut.body !== null && versionOut.body !== void 0) {
|
|
5489
|
+
backendVersion = versionOut.body;
|
|
5490
|
+
} else {
|
|
5491
|
+
failures.push(VERSION_PATH);
|
|
5492
|
+
backendVersion = { version: "unavailable" };
|
|
5493
|
+
}
|
|
5494
|
+
return {
|
|
5495
|
+
tenantMetaManifestDigest: manifestDigest(rows),
|
|
5496
|
+
manifestEntryCount: rows.length,
|
|
5497
|
+
extensionSource: sourceKey,
|
|
5498
|
+
// 🔴 记下**是从哪个模型探到的**:让「这个值是谁给的」在产物里留痕。
|
|
5499
|
+
extensionSourceProbe: p.probeModelKey,
|
|
5500
|
+
// 🔴 没有这两个数,`extensionSource: "unknown"` 就是一个**不可追问的结论**。
|
|
5501
|
+
extensionSourceProbeAttempted: p.attempted,
|
|
5502
|
+
extensionSourceProbeAnswered: p.answered,
|
|
5503
|
+
modelListHash: modelHash,
|
|
5504
|
+
modelCount: models.length,
|
|
5505
|
+
backendVersion,
|
|
5506
|
+
capabilities: probe.capabilities,
|
|
5507
|
+
createdAt: `${now().toISOString().slice(0, 19)}Z`,
|
|
5508
|
+
ttlDays,
|
|
5509
|
+
coverageNotes: {
|
|
5510
|
+
gateComponents: [...GATE_COMPONENTS],
|
|
5511
|
+
blindSpots: BLIND_SPOTS,
|
|
5512
|
+
// 空数组 = 三个端点都真取到了;非空 = 这份四元组是**残的**,
|
|
5513
|
+
// 上面那些 0 和空摘要不代表环境真的空。
|
|
5514
|
+
fetchFailures: failures
|
|
5515
|
+
}
|
|
5516
|
+
};
|
|
5517
|
+
}
|
|
5518
|
+
function compareFingerprint(recorded, current) {
|
|
5519
|
+
const diffs = GATE_COMPONENTS.filter((c) => recorded[c] !== current[c]);
|
|
5520
|
+
if (recorded.capabilities !== current.capabilities) diffs.push("capabilities");
|
|
5521
|
+
for (const [fp, tag] of [
|
|
5522
|
+
[recorded, "recorded"],
|
|
5523
|
+
[current, "current"]
|
|
5524
|
+
]) {
|
|
5525
|
+
const notes = fp.coverageNotes;
|
|
5526
|
+
if (!isPlainObject2(notes) || !("fetchFailures" in notes)) {
|
|
5527
|
+
diffs.push(`incomplete:${tag}:coverageNotes-missing`);
|
|
5528
|
+
continue;
|
|
5529
|
+
}
|
|
5530
|
+
const raw = notes.fetchFailures;
|
|
5531
|
+
const list = Array.isArray(raw) ? raw : [];
|
|
5532
|
+
const gateFailures = [...new Set(list.map(String))].filter((x) => GATE_SOURCES.some((s) => x.startsWith(s))).sort();
|
|
5533
|
+
for (const f of gateFailures) diffs.push(`incomplete:${tag}:${f}`);
|
|
5534
|
+
}
|
|
5535
|
+
return diffs;
|
|
5536
|
+
}
|
|
5537
|
+
function sha2562(s) {
|
|
5538
|
+
return createHash3("sha256").update(s, "utf8").digest("hex");
|
|
5539
|
+
}
|
|
5540
|
+
function isPlainObject2(v) {
|
|
5541
|
+
return typeof v === "object" && v !== null && !Array.isArray(v);
|
|
5542
|
+
}
|
|
5543
|
+
|
|
5544
|
+
// src/fde/doctor.ts
|
|
5545
|
+
var DOCTOR_ANCHORS = [
|
|
5546
|
+
"connectivity",
|
|
5547
|
+
"identity",
|
|
5548
|
+
"config-admin",
|
|
5549
|
+
"layer"
|
|
5550
|
+
];
|
|
5551
|
+
async function runDoctor(probe, identity) {
|
|
5552
|
+
const checks = [];
|
|
5553
|
+
const modelsOut = await probe.get(MODELS_PATH);
|
|
5554
|
+
const connected = modelsOut.error === "ok";
|
|
5555
|
+
const modelsBody = isObj(modelsOut.body) ? modelsOut.body : {};
|
|
5556
|
+
checks.push({
|
|
5557
|
+
anchor: "connectivity",
|
|
5558
|
+
ok: connected,
|
|
5559
|
+
detail: connected ? `\u6A21\u578B\u6E05\u5355\u53EF\u8FBE\uFF0C\u5171 ${modelsBody.totalCount ?? 0} \u4E2A\u6A21\u578B` : `\u4E0D\u53EF\u8FBE\uFF1A${modelsOut.error}\uFF08status=${modelsOut.status ?? "?"}\uFF09`
|
|
5560
|
+
});
|
|
5561
|
+
const keys = modelKeys(modelsOut.body);
|
|
5562
|
+
const p = await probeExtensionSource(probe, keys);
|
|
5563
|
+
checks.push({ anchor: "identity", ...judgeIdentity(identity) });
|
|
5564
|
+
const manifestOut = await probe.get(MANIFEST_PATH, { page: 0, pageSize: 1 });
|
|
5565
|
+
const caOk = manifestOut.error === "ok";
|
|
5566
|
+
const manifestBody = isObj(manifestOut.body) ? manifestOut.body : {};
|
|
5567
|
+
const total = caOk ? manifestBody.total ?? "?" : "?";
|
|
5568
|
+
let caDetail;
|
|
5569
|
+
if (caOk) {
|
|
5570
|
+
caDetail = `\u79DF\u6237 meta \u6E05\u5355\u53EF\u8BFB\uFF0C\u5171 ${total} \u6761\uFF080 \u6761\u662F\u5408\u6CD5\u72B6\u6001\uFF1A\u514B\u9686\u5E93\u5E26\u5F97\u8D70 workspace \u7ED1\u5B9A\uFF0C\u5E26\u4E0D\u8D70 workspace \u6587\u4EF6\uFF09`;
|
|
5571
|
+
} else if (manifestOut.status === 401 || manifestOut.status === 403) {
|
|
5572
|
+
caDetail = `HTTP ${manifestOut.status}\uFF1A\u5F53\u524D\u8D26\u53F7**\u6CA1\u6709 configAdmin \u6743\u9650**\u3002\u627E\u5BA2\u6237\u7BA1\u7406\u5458\u52A0\u89D2\u8272\uFF1B\u8FD9\u4E0D\u662F\u7F51\u7EDC\u95EE\u9898`;
|
|
5573
|
+
} else {
|
|
5574
|
+
caDetail = `\u4E0D\u53EF\u8BFB\uFF1A${manifestOut.error}\uFF08status=${manifestOut.status ?? "?"}\uFF09\u3002\u5148\u6392\u8FDE\u901A/\u7F51\u5173\uFF0C\u518D\u8C08\u6743\u9650\u3002\u7F3A configAdmin \u65F6\u4E3B\u951A\u70B9\u7B97\u4E0D\u51FA\u6765`;
|
|
5575
|
+
}
|
|
5576
|
+
checks.push({ anchor: "config-admin", ok: caOk, detail: caDetail });
|
|
5577
|
+
checks.push({
|
|
5578
|
+
anchor: "layer",
|
|
5579
|
+
ok: Boolean(p.sourceKey),
|
|
5580
|
+
detail: layerDetail(p, connected)
|
|
5581
|
+
});
|
|
5582
|
+
return { ok: checks.every((c) => c.ok), checks };
|
|
5583
|
+
}
|
|
5584
|
+
function judgeIdentity(ident) {
|
|
5585
|
+
if (ident === null) {
|
|
5586
|
+
return { ok: false, detail: "\u672A\u7ECF\u672C\u5DE5\u5177\u767B\u5F55\uFF0C\u65E0\u6CD5\u6838\u5BF9 token \u5F52\u5C5E\u7684\u79DF\u6237" };
|
|
5587
|
+
}
|
|
5588
|
+
const want = String(ident.requestedTenantId ?? "").trim();
|
|
5589
|
+
const got = String(ident.tenantId ?? "").trim();
|
|
5590
|
+
if (!want) {
|
|
5591
|
+
return {
|
|
5592
|
+
ok: false,
|
|
5593
|
+
detail: `\u672C\u5DE5\u5177\u6CA1\u6709\u8BB0\u4E0B\u300C\u4F60\u8981\u7684\u79DF\u6237\u300D\uFF08\u547D\u4EE4\u5C42\u672A\u586B requestedTenantId\uFF09\uFF0C\u65E0\u6CD5\u4E0E token \u5F52\u5C5E\u7684\u79DF\u6237 ${got || "(\u672A\u77E5)"} \u6838\u5BF9 \u2014\u2014 \u8FD9\u662F\u5DE5\u5177\u63A5\u7EBF\u95EE\u9898\uFF0C\u4E0D\u662F\u73AF\u5883\u95EE\u9898`
|
|
5594
|
+
};
|
|
5595
|
+
}
|
|
5596
|
+
if (!got) {
|
|
5597
|
+
return {
|
|
5598
|
+
ok: false,
|
|
5599
|
+
detail: `\u670D\u52A1\u7AEF\u767B\u5F55\u54CD\u5E94\u91CC\u6CA1\u6709 user.tenantId\uFF0C\u65E0\u6CD5\u6838\u5BF9\uFF08\u4F60\u8981\u7684\u662F ${want}\uFF09`
|
|
5600
|
+
};
|
|
5601
|
+
}
|
|
5602
|
+
if (want !== got) {
|
|
5603
|
+
return {
|
|
5604
|
+
ok: false,
|
|
5605
|
+
detail: `\u{1F534} **token \u5F52\u5C5E\u79DF\u6237 ${got}\uFF0C\u800C\u4F60\u8981\u7684\u662F ${want}** \u2014\u2014 \u4F60\u6B63\u5728\u722C\u53E6\u4E00\u4E2A\u79DF\u6237\uFF0C\u5148\u505C\u4E0B\u6765\u3002\u8FD9\u4E00\u5BF9\u8EAB\u4EFD\u4F1A\u968F\u8EAB\u4EFD\u6BB5\u843D\u8FDB\u4EA7\u7269\u7D22\u5F15\uFF0C\u6240\u4EE5\u722C\u5B8C\u4E4B\u540E\u5728\u4EA7\u7269\u91CC\u4E5F\u5BF9\u5F97\u51FA\u6765\uFF1B\u4F46**\u843D\u76D8\u8DEF\u5F84**\u4E0A\u90A3\u4E2A\u79DF\u6237\u6BB5\u53D6\u81EA\u4F60\u7ED9\u7684 \`--tenant\` \u2014\u2014 \u76EE\u5F55\u540D\u7167\u65E7\u662F\u4F60\u8981\u7684\u90A3\u4E2A\u53F7\u3002`
|
|
5606
|
+
};
|
|
5607
|
+
}
|
|
5608
|
+
return {
|
|
5609
|
+
ok: true,
|
|
5610
|
+
detail: `\u79DF\u6237 ${got} \u6838\u5BF9\u4E00\u81F4\uFF1B\u767B\u5F55\u8EAB\u4EFD ${ident.username || "(\u672A\u77E5)"}\uFF0C\u89D2\u8272 ${JSON.stringify(ident.roles ?? [])}`
|
|
5611
|
+
};
|
|
5612
|
+
}
|
|
5613
|
+
function layerDetail(p, connected) {
|
|
5614
|
+
if (p.sourceKey) {
|
|
5615
|
+
return `\u4F60\u7AD9\u5728 \`${p.sourceKey}\` \u8FD9\u4E00\u5C42\uFF08\u63A2\u81EA \`${p.probeModelKey}\` \u7684 meta\uFF09\u3002\u4E0D\u540C sourceKey \u7684 recon \u4EA7\u7269\u4E0D\u53EF\u4E92\u6362 \u2014\u2014 \u4E24\u4E2A FDE \u540C\u4E00\u6761\u547D\u4EE4\u4F1A\u5F97\u5230\u4E24\u4EFD\u4E0D\u540C\u7684\u4E8B\u5B9E\uFF0C\u90A3\u4E0D\u662F\u9648\u65E7\uFF0C\u662F\u7AD9\u5728\u4E0D\u540C\u7684\u5C42\u4E0A`;
|
|
5616
|
+
}
|
|
5617
|
+
if (p.attempted === 0) {
|
|
5618
|
+
return connected ? "\u6A21\u578B\u6E05\u5355\u53D6\u5230\u4E86\uFF0C\u4F46\u91CC\u9762**\u4E00\u4E2A\u53EF\u7528 modelKey \u90FD\u6CA1\u6709**\uFF0C**\u4E00\u4E2A\u6A21\u578B\u90FD\u6CA1\u5F97\u8BD5** \u21D2 \u8FD9\u4E0D\u662F\u5C42\u7684\u95EE\u9898\uFF1A\u67E5\u6E05\u5355\u7684 modelKey \u952E\u540D\u662F\u4E0D\u662F\u88AB\u7F51\u5173\u6539\u5199\u3001\u6216\u5F53\u524D\u8D26\u53F7\u770B\u4E0D\u5230\u4EFB\u4F55\u6A21\u578B" : "\u6A21\u578B\u6E05\u5355\u6CA1\u53D6\u5230\uFF0C**\u4E00\u4E2A\u6A21\u578B\u90FD\u6CA1\u5F97\u8BD5** \u21D2 \u5148\u770B\u4E0A\u9762 connectivity \u90A3\u6761";
|
|
5619
|
+
}
|
|
5620
|
+
if (p.answered > 0) {
|
|
5621
|
+
return `\u8BD5\u4E86 ${p.attempted} \u4E2A\u6A21\u578B\u7684 meta\uFF0C${p.answered} \u4E2A\u7B54\u5E94\u4E86\uFF0C\u4F46\u90FD\u6CA1\u56DE\u663E X-HCM-Extension-Source \u21D2 \u5148\u67E5\u53CD\u4EE3\u662F\u4E0D\u662F\u628A X-HCM-* \u54CD\u5E94\u5934\u5265\u4E86`;
|
|
5622
|
+
}
|
|
5623
|
+
return `\u8BD5\u4E86 ${p.attempted} \u4E2A\u6A21\u578B\u7684 meta\uFF0C**\u4E00\u4E2A\u90FD\u6CA1\u7B54\u5E94** \u21D2 \u5148\u67E5\u8FDE\u901A\u4E0E\u6743\u9650\uFF0C\u518D\u770B\u6A21\u578B\u6E05\u5355\u662F\u4E0D\u662F\u7A7A\u7684\u3002\u26A0\uFE0F \u672C\u5DE5\u5177\u53EA\u77E5\u9053\u300C\u4E00\u4E2A\u90FD\u6CA1\u7B54\u5E94\u300D\uFF0C**\u5206\u4E0D\u51FA**\u662F\u8FDE\u901A\u3001\u6743\u9650\u8FD8\u662F\u4E2D\u95F4\u6709\u4EBA\u6321\u4E86 \u2014\u2014 \u4E0A\u4E00\u6863\uFF08\u7B54\u5E94\u4E86\u4F46\u6CA1\u56DE\u663E X-HCM-* \u5934\uFF09\u624D\u6307\u5F97\u52A8\u53CD\u4EE3\uFF0C\u8FD9\u4E00\u6863\u6307\u4E0D\u52A8\u4EFB\u4F55\u4E00\u65B9`;
|
|
5624
|
+
}
|
|
5625
|
+
function isObj(v) {
|
|
5626
|
+
return typeof v === "object" && v !== null && !Array.isArray(v);
|
|
5627
|
+
}
|
|
5628
|
+
var AUTH_INFO_PATH = "/api/auth/info";
|
|
5629
|
+
async function fetchReconIdentity(probe, requestedTenantId) {
|
|
5630
|
+
const out = await probe.get(AUTH_INFO_PATH);
|
|
5631
|
+
if (out.error !== "ok") return null;
|
|
5632
|
+
const principal = toPrincipal(out.body);
|
|
5633
|
+
if (!principal) return { requestedTenantId };
|
|
5634
|
+
return {
|
|
5635
|
+
requestedTenantId,
|
|
5636
|
+
tenantId: principal.tenantId,
|
|
5637
|
+
username: principal.username,
|
|
5638
|
+
roles: principal.roles
|
|
5639
|
+
};
|
|
5640
|
+
}
|
|
5641
|
+
|
|
5642
|
+
// src/fde/probe.ts
|
|
5643
|
+
var STATUS_CLASS = {
|
|
5644
|
+
401: "unauthorized",
|
|
5645
|
+
403: "forbidden",
|
|
5646
|
+
404: "not-found"
|
|
5647
|
+
};
|
|
5648
|
+
function classifyStatus(status) {
|
|
5649
|
+
if (status >= 200 && status < 300) return "ok";
|
|
5650
|
+
if (STATUS_CLASS[status]) return STATUS_CLASS[status];
|
|
5651
|
+
if (status >= 500) return "server-error";
|
|
5652
|
+
return "bad-response";
|
|
5653
|
+
}
|
|
5654
|
+
var CAPABILITIES_HEADER = "x-hcm-capabilities";
|
|
5655
|
+
function buildUrl(path7, params) {
|
|
5656
|
+
const entries = Object.entries(params ?? {}).filter(([, v]) => v !== void 0 && v !== null);
|
|
5657
|
+
if (entries.length === 0) return path7;
|
|
5658
|
+
const qs = entries.map(([k, v]) => `${encodeURIComponent(k)}=${encodeURIComponent(String(v))}`).join("&");
|
|
5659
|
+
return `${path7}${path7.includes("?") ? "&" : "?"}${qs}`;
|
|
5660
|
+
}
|
|
5661
|
+
function lowerHeaders(raw) {
|
|
5662
|
+
const out = {};
|
|
5663
|
+
if (raw && typeof raw === "object") {
|
|
5664
|
+
for (const [k, v] of Object.entries(raw)) {
|
|
5665
|
+
out[k.toLowerCase()] = String(v);
|
|
5666
|
+
}
|
|
5667
|
+
}
|
|
5668
|
+
return out;
|
|
5669
|
+
}
|
|
5670
|
+
function toOutcome(status, data, headers) {
|
|
5671
|
+
const cls = classifyStatus(status);
|
|
5672
|
+
if (status === 204) return { status, body: null, error: "ok", headers };
|
|
5673
|
+
const empty = data === void 0 || data === null || typeof data === "string" && data.trim() === "";
|
|
5674
|
+
if (empty) {
|
|
5675
|
+
return { status, body: null, error: cls === "ok" ? "bad-response" : cls, headers };
|
|
5676
|
+
}
|
|
5677
|
+
if (typeof data === "string") {
|
|
5678
|
+
try {
|
|
5679
|
+
return { status, body: JSON.parse(data), error: cls, headers };
|
|
5680
|
+
} catch {
|
|
5681
|
+
return { status, body: null, error: cls === "ok" ? "bad-response" : cls, headers };
|
|
5682
|
+
}
|
|
5683
|
+
}
|
|
5684
|
+
return { status, body: data, error: cls, headers };
|
|
5685
|
+
}
|
|
5686
|
+
function httpProbe(http, capabilities) {
|
|
5687
|
+
async function once(fn) {
|
|
5688
|
+
try {
|
|
5689
|
+
const res = await fn();
|
|
5690
|
+
return toOutcome(res.status, res.data, lowerHeaders(res.headers));
|
|
5691
|
+
} catch (e) {
|
|
5692
|
+
const msg = e instanceof Error ? e.message : String(e);
|
|
5693
|
+
const local = /Invalid URL|ERR_INVALID_URL|ERR_UNESCAPED_CHARACTERS/i.test(msg);
|
|
5694
|
+
return { status: -1, body: null, error: local ? "bad-response" : "network", headers: {} };
|
|
5695
|
+
}
|
|
5696
|
+
}
|
|
5697
|
+
const headers = { [CAPABILITIES_HEADER]: capabilities };
|
|
5698
|
+
const probe = {
|
|
5699
|
+
capabilities,
|
|
5700
|
+
get(path7, params) {
|
|
5701
|
+
return once(
|
|
5702
|
+
() => http.get(buildUrl(path7, params), { headers, validateStatus: () => true })
|
|
5703
|
+
);
|
|
5704
|
+
},
|
|
5705
|
+
post(path7, body) {
|
|
5706
|
+
return once(
|
|
5707
|
+
() => http.post(path7, body, {
|
|
5708
|
+
headers: { ...headers, "content-type": "application/json" },
|
|
5709
|
+
validateStatus: () => true
|
|
5710
|
+
})
|
|
5711
|
+
);
|
|
5712
|
+
},
|
|
5713
|
+
/**
|
|
5714
|
+
* 🔴 **按原始路径回填、输入全覆盖**:调用方拿 `out[path]` 取结果,
|
|
5715
|
+
* 少一个键就会读成 `undefined` 然后当成「没这个东西」。
|
|
5716
|
+
* 🔴 一路炸不许拖垮整批——逐路径独立归类。
|
|
5717
|
+
*/
|
|
5718
|
+
async getMany(paths) {
|
|
5719
|
+
const pairs = await Promise.all(
|
|
5720
|
+
paths.map(async (p) => [
|
|
5721
|
+
p,
|
|
5722
|
+
await probe.get(p).catch(() => networkOutcome())
|
|
5723
|
+
])
|
|
5724
|
+
);
|
|
5725
|
+
return Object.fromEntries(pairs);
|
|
5726
|
+
},
|
|
5727
|
+
async postMany(jobs) {
|
|
5728
|
+
const pairs = await Promise.all(
|
|
5729
|
+
jobs.map(async (j) => [
|
|
5730
|
+
j.path,
|
|
5731
|
+
await probe.post(j.path, j.body).catch(() => networkOutcome())
|
|
5732
|
+
])
|
|
5733
|
+
);
|
|
5734
|
+
return Object.fromEntries(pairs);
|
|
5735
|
+
}
|
|
5736
|
+
};
|
|
5737
|
+
return probe;
|
|
5738
|
+
}
|
|
5739
|
+
function networkOutcome() {
|
|
5740
|
+
return { status: -1, body: null, error: "network", headers: {} };
|
|
5243
5741
|
}
|
|
5244
5742
|
export {
|
|
5743
|
+
AUTH_INFO_PATH,
|
|
5744
|
+
BLIND_SPOTS,
|
|
5245
5745
|
CliError,
|
|
5246
5746
|
CliErrorCode,
|
|
5247
5747
|
DEFAULT_AGENT_ID,
|
|
5248
5748
|
DEFAULT_SESSION_TTL_SECONDS,
|
|
5749
|
+
DOCTOR_ANCHORS,
|
|
5750
|
+
EXTENSION_SOURCE_HEADER,
|
|
5249
5751
|
ExitCode,
|
|
5752
|
+
GATE_COMPONENTS,
|
|
5250
5753
|
HcmClient,
|
|
5754
|
+
LayoutContainmentError,
|
|
5755
|
+
MANIFEST_PATH,
|
|
5756
|
+
MAX_MANIFEST_PAGES,
|
|
5757
|
+
MAX_SOURCE_PROBES,
|
|
5758
|
+
META_PATH,
|
|
5251
5759
|
MINI_CONTEXT_FILE,
|
|
5252
5760
|
MINI_CONTEXT_KIND,
|
|
5253
5761
|
MINI_CONTEXT_VERSION,
|
|
5254
5762
|
MINI_PULL_MANIFEST_FILE,
|
|
5255
5763
|
MINI_REMOTE_ROOT,
|
|
5256
5764
|
MINI_STATE_DIR,
|
|
5765
|
+
MODELS_PATH,
|
|
5257
5766
|
PASSWORD_CHANGE_REQUIRED,
|
|
5258
5767
|
PROACTIVE_REFRESH_MARGIN_SECONDS,
|
|
5768
|
+
ReconLayout,
|
|
5259
5769
|
RefStore,
|
|
5260
5770
|
SDK_VERSION,
|
|
5261
5771
|
TokenStore,
|
|
5772
|
+
VERSION_PATH,
|
|
5262
5773
|
WsClient,
|
|
5263
5774
|
absoluteDownloadUrl,
|
|
5264
5775
|
adminResetPassword,
|
|
@@ -5281,6 +5792,8 @@ export {
|
|
|
5281
5792
|
classifyMessage,
|
|
5282
5793
|
clearMetaCache,
|
|
5283
5794
|
clearModelCache,
|
|
5795
|
+
compareFingerprint,
|
|
5796
|
+
computeFingerprint,
|
|
5284
5797
|
conversationStateFile,
|
|
5285
5798
|
create,
|
|
5286
5799
|
createConversation,
|
|
@@ -5310,6 +5823,7 @@ export {
|
|
|
5310
5823
|
fetchConversationTimeline,
|
|
5311
5824
|
fetchConversationTimelineStrict,
|
|
5312
5825
|
fetchRecentConversations,
|
|
5826
|
+
fetchReconIdentity,
|
|
5313
5827
|
fetchSkillCatalog,
|
|
5314
5828
|
fetchSkillFile,
|
|
5315
5829
|
fetchSkillMarkdown,
|
|
@@ -5328,6 +5842,7 @@ export {
|
|
|
5328
5842
|
globalConfigFile,
|
|
5329
5843
|
guessMimeType,
|
|
5330
5844
|
hcmConfigDir,
|
|
5845
|
+
httpProbe,
|
|
5331
5846
|
identitiesDir,
|
|
5332
5847
|
identityDir,
|
|
5333
5848
|
identityMetaFile,
|
|
@@ -5353,6 +5868,8 @@ export {
|
|
|
5353
5868
|
loginPat,
|
|
5354
5869
|
matchSkills,
|
|
5355
5870
|
migrateLegacyProfiles,
|
|
5871
|
+
modelEntries,
|
|
5872
|
+
modelKeys,
|
|
5356
5873
|
needsRefresh,
|
|
5357
5874
|
normalizeReferences,
|
|
5358
5875
|
oneShot,
|
|
@@ -5365,6 +5882,7 @@ export {
|
|
|
5365
5882
|
parseSkillFrontmatter,
|
|
5366
5883
|
parseSkillRequirements,
|
|
5367
5884
|
patchSettingDomain,
|
|
5885
|
+
probeExtensionSource,
|
|
5368
5886
|
profileDir,
|
|
5369
5887
|
profileFile,
|
|
5370
5888
|
pullMiniAppProject,
|
|
@@ -5382,8 +5900,10 @@ export {
|
|
|
5382
5900
|
resolveChironBase,
|
|
5383
5901
|
resolveRefs,
|
|
5384
5902
|
resolveSkillInstallOrder,
|
|
5903
|
+
runDoctor,
|
|
5385
5904
|
runImport,
|
|
5386
5905
|
runMiniSmokeChecks,
|
|
5906
|
+
safeSegment,
|
|
5387
5907
|
saveConversationState,
|
|
5388
5908
|
saveEnv,
|
|
5389
5909
|
saveGlobalConfig,
|
|
@@ -5393,6 +5913,7 @@ export {
|
|
|
5393
5913
|
saveWorkspaceFileContent,
|
|
5394
5914
|
sendMessageAndStream,
|
|
5395
5915
|
snakeToCamel,
|
|
5916
|
+
tenantSegment,
|
|
5396
5917
|
toJson,
|
|
5397
5918
|
toOrigin,
|
|
5398
5919
|
toPrincipal,
|