@sema-agent/settings-schema 14.7.0 → 14.8.0

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.
Files changed (51) hide show
  1. package/CHANGELOG.md +209 -0
  2. package/README.md +58 -2
  3. package/conformance/VERSION +1 -1
  4. package/dist/atomic-write.d.ts +15 -0
  5. package/dist/atomic-write.js +109 -5
  6. package/dist/catalog-manages.d.ts +15 -0
  7. package/dist/catalog-manages.js +60 -0
  8. package/dist/center-refs.d.ts +68 -0
  9. package/dist/center-refs.js +70 -0
  10. package/dist/config-dir-migration.js +1 -0
  11. package/dist/config-fns.js +5 -3
  12. package/dist/config-root-documents.d.ts +106 -0
  13. package/dist/config-root-documents.js +93 -0
  14. package/dist/config-root-node.d.ts +117 -0
  15. package/dist/config-root-node.js +260 -0
  16. package/dist/conformance/run.d.ts +3 -0
  17. package/dist/conformance/run.js +10 -6
  18. package/dist/conformance/temp-roots.d.ts +31 -0
  19. package/dist/conformance/temp-roots.js +184 -0
  20. package/dist/default-model.d.ts +1 -1
  21. package/dist/default-model.js +5 -4
  22. package/dist/env-model-table.d.ts +163 -0
  23. package/dist/env-model-table.js +257 -0
  24. package/dist/fleet.d.ts +25 -7
  25. package/dist/fleet.js +24 -6
  26. package/dist/index.d.ts +5 -0
  27. package/dist/index.js +5 -0
  28. package/dist/mcp-request-entry.d.ts +58 -6
  29. package/dist/mcp-request-entry.js +45 -13
  30. package/dist/mcp-server-name.d.ts +17 -0
  31. package/dist/mcp-server-name.js +19 -0
  32. package/dist/migration-receipt.d.ts +10 -6
  33. package/dist/migration-receipt.js +5 -3
  34. package/dist/node.d.ts +1 -0
  35. package/dist/node.js +2 -0
  36. package/dist/one-time-migrations.d.ts +4 -2
  37. package/dist/one-time-migrations.js +29 -1
  38. package/dist/own-key-read.d.ts +1 -1
  39. package/dist/retired-form.d.ts +19 -0
  40. package/dist/retired-form.js +24 -1
  41. package/dist/safety-merge-spec.d.ts +6 -6
  42. package/dist/tier-words.d.ts +33 -0
  43. package/dist/tier-words.js +43 -0
  44. package/dist/types.d.ts +6 -0
  45. package/dist/types.js +24 -0
  46. package/dist/worker-row-verdict.js +7 -1
  47. package/docs/API-SURFACE.md +68 -4
  48. package/docs/CONSUMER-INTEGRATION.md +72 -2
  49. package/llms-full.txt +4 -1
  50. package/llms.txt +9 -3
  51. package/package.json +2 -2
@@ -0,0 +1,163 @@
1
+ import { type EnvModelsDecision } from "./config-root-documents.js";
2
+ import { SUMMARIZE_MODEL_MISSING_CODE } from "./fleet.js";
3
+ import type { ModelRole } from "./types.js";
4
+ /** 一份 env 快照(`process.env` 也满足这个形)。 */
5
+ export type EnvSnapshot = Readonly<Record<string, string | undefined>>;
6
+ /** env 模型表的键名(闭集)。 */
7
+ export declare const ENV_MODEL_TABLE_KEYS: {
8
+ readonly mainId: "MODEL_ID";
9
+ readonly cheapId: "MODEL_CHEAP_ID";
10
+ readonly verifier: "MODEL_VERIFIER";
11
+ readonly codeRoles: "MODEL_CODE_ROLES";
12
+ };
13
+ /** env 模型表的一把键名。 */
14
+ export type EnvModelTableKey = (typeof ENV_MODEL_TABLE_KEYS)[keyof typeof ENV_MODEL_TABLE_KEYS];
15
+ /** env 表里主模型的别名键(角色底的 `default` 绑它;引擎 env 表的条目键 = 这个别名 + 主 id + cheap id)。 */
16
+ export declare const DEFAULT_MODEL_KEY: "default";
17
+ /**
18
+ * env 表的 **cheap 槽**铸不铸(闭集四态,消费方穷举 `switch`;{@link mintedCheapId}):
19
+ * · `absent` —— 没设(或空串);
20
+ * · `without_main` —— 设了而主槽没设:cheap 槽是主模型「同网关换个 id」的形,没有主槽就没有它 ⇒ 不铸;
21
+ * · `same_as_main` —— 与主 id 相同:说的就是主模型 ⇒ 不铸;
22
+ * · `minted` —— 铸(`id` = `MODEL_CHEAP_ID`)。
23
+ */
24
+ export type EnvCheapSlot = {
25
+ readonly kind: "absent";
26
+ } | {
27
+ readonly kind: "without_main" | "same_as_main" | "minted";
28
+ readonly id: string;
29
+ };
30
+ /** cheap 槽铸出来的 id(不铸 ⇒ `undefined`)。 */
31
+ export declare function mintedCheapId(slot: EnvCheapSlot): string | undefined;
32
+ /**
33
+ * 包内(不进包根):把 env 模型表的键(缺省四把;调用方只要两槽时只拍两把)**读一次**进一份快照(下标读,同「读到即算」)。计划助手与一次性迁移在**要用的那一刻**拍、
34
+ * 此后的每个读法都答自同一份 —— 一只 getter 两次读答两个值时,判「env 表已声明」与列 env 模型不会各读到一个(那样结局与回执自相矛盾);用不到的键不读。
35
+ */
36
+ export declare function snapshotEnvModelTable(env: EnvSnapshot, keys?: readonly EnvModelTableKey[]): EnvSnapshot;
37
+ /** env 两槽的**唯一读口**(见文件头)。`mainId` = `MODEL_ID`(空串 = 没有主槽)。 */
38
+ export declare function readEnvModelSlots(env: EnvSnapshot): {
39
+ readonly mainId: string;
40
+ readonly cheapSlot: EnvCheapSlot;
41
+ };
42
+ /** **env 模型表已声明吗**(= `MODEL_ID` 在场且非空串;纯空白算在场)—— 引擎一侧的顶替闸(今天还不强制)与计划助手共用的那一只谓词。 */
43
+ export declare function envModelTableDeclared(env: EnvSnapshot): boolean;
44
+ /** env 表里的一条模型(槽序 main → cheap)。 */
45
+ export interface EnvModelTableEntry {
46
+ readonly id: string;
47
+ readonly slot: "main" | "cheap";
48
+ readonly env: typeof ENV_MODEL_TABLE_KEYS.mainId | typeof ENV_MODEL_TABLE_KEYS.cheapId;
49
+ }
50
+ /** env 底的一个角色绑定:env 表键(别名 `default`、主 id 或 cheap id),或带开发模式提示词标记的同一个键(`MODEL_CODE_ROLES`)。 */
51
+ export type EnvRoleBinding = string | {
52
+ readonly model: string;
53
+ readonly promptRole: "code";
54
+ };
55
+ /** env 模型表铸出来的全部事实(纯数据)。 */
56
+ export interface EnvModelBaseline {
57
+ readonly entries: readonly EnvModelTableEntry[];
58
+ /** 角色 → env 表键(键序 verifier → default → subagent → summarize)。 */
59
+ readonly roles: Readonly<Partial<Record<ModelRole, EnvRoleBinding>>>;
60
+ /** `MODEL_VERIFIER` 的读数:未设 / 指 env 表里的一个键 / 指表外(⇒ verifier 落 `default`,由消费方告警)。 */
61
+ readonly verifier: {
62
+ readonly kind: "unset";
63
+ } | {
64
+ readonly kind: "bound";
65
+ readonly ref: string;
66
+ } | {
67
+ readonly kind: "rejected";
68
+ readonly raw: string;
69
+ };
70
+ }
71
+ /**
72
+ * env 模型表的**角色底**(纯数据):条目(主槽在 ⇒ 主;cheap 槽铸了 ⇒ cheap)、角色(verifier ⇒ `MODEL_VERIFIER` 指 env 表里的键时是它、否则 `default`;
73
+ * 主角色族(`ROSTER_PRIMARY_ROLES`)⇒ `default`;cheap 角色族(`ROSTER_CHEAP_ROLES`)⇒ cheap id,cheap 没铸 ⇒ `default`;`MODEL_CODE_ROLES` 点名的、角色底自己有的键
74
+ * 加开发模式提示词标记)。env 表键 = 别名 `default` + 主 id + cheap id(主槽不在 ⇒ 表空)。
75
+ */
76
+ export declare function buildEnvModelBaseline(env: EnvSnapshot): EnvModelBaseline;
77
+ /** 目标引擎读哪条配置腿:`local` = 读配置根的 `config.d`;`remote` = 读中心;`none` = 纯 env 部署,不读 `config.d`。 */
78
+ export type EnvModelTableTarget = "local" | "remote" | "none";
79
+ /** 一个落点:目录条目的名与它的上游 id。 */
80
+ export interface EnvModelTableLanding {
81
+ readonly name: string;
82
+ readonly id: string;
83
+ }
84
+ /** 回执的一个角色行。`before.id = null` ⇒ env 表空,这个键在写前不解到任何 env 模型。 */
85
+ export interface EnvModelTableRoleRow {
86
+ readonly role: string;
87
+ readonly before: {
88
+ readonly ref: string;
89
+ readonly id: string | null;
90
+ };
91
+ readonly promptRole?: "code";
92
+ /** `candidate_binds` = 候选自己的 `roles` 绑了这个角色({select} ⇒ `landing: null`);`catalog_default` = 落到目录缺省(env 底绑别名 `default`,或 `default` 角色本身
93
+ * —— 它恒落在缺省解析器的答案上;候选把它绑到停用条目时解析器落到下一级;{select} 缺省 ⇒ `null`);
94
+ * `candidate_entry` = env 底绑的名在候选目录里解得到(一条启用条目的名,或经活跃档位组展开的档位词 / 别名);`dangles` = 落不到任何启用条目。 */
95
+ readonly after: {
96
+ readonly kind: "candidate_binds" | "catalog_default" | "candidate_entry" | "dangles";
97
+ readonly landing: EnvModelTableLanding | null;
98
+ };
99
+ }
100
+ /** 回执(纯数据;确认键 = 它的定序 JSON)。 */
101
+ export interface EnvModelTableReceipt {
102
+ /** 配置根的决定读数(`readEnvModelsDecision` 的 `kind`):读不懂的会在确认后被整份覆盖。 */
103
+ readonly decision: EnvModelsDecision["kind"];
104
+ /** 退场的 env 条目(槽序);`servedAs` = 候选里同 id 的启用条目名。 */
105
+ readonly envModels: readonly (EnvModelTableEntry & {
106
+ readonly servedAs: readonly string[];
107
+ })[];
108
+ readonly roles: readonly EnvModelTableRoleRow[];
109
+ }
110
+ /** 调用方带回的确认:上一次 `confirm` 给的键。 */
111
+ export interface EnvModelTableWriteIntent {
112
+ readonly kind: "replace";
113
+ readonly confirmKey: string;
114
+ }
115
+ export interface EnvModelTableWriteInput {
116
+ /** 目标引擎起服看到的 env(只用于预测与回执;引擎用它自己的 env 复判)。 */
117
+ readonly env: EnvSnapshot;
118
+ readonly target: EnvModelTableTarget;
119
+ /** 配置根上的决定读数(`readEnvModelsDecision` 的答案)。 */
120
+ readonly decision: EnvModelsDecision;
121
+ /** 将要原子写入的完整 models 文档。 */
122
+ readonly candidate: unknown;
123
+ readonly intent?: EnvModelTableWriteIntent;
124
+ }
125
+ /** 计划(闭集;写者穷举 `switch`)。 */
126
+ export type EnvModelTableWritePlan = {
127
+ readonly kind: "write";
128
+ readonly why: "not_read" | "not_managing" | "env_table_empty";
129
+ } | {
130
+ readonly kind: "write";
131
+ readonly why: "acknowledged";
132
+ readonly receipt: EnvModelTableReceipt;
133
+ }
134
+ /** 先写 `config.env-models.json = replaced`(`writeEnvModelsDecision(root, "replaced", { write: "overwrite" })`),再写 models.json。 */
135
+ | {
136
+ readonly kind: "write_with_decision";
137
+ readonly receipt: EnvModelTableReceipt;
138
+ }
139
+ /** 把回执给用户看;用户确认 ⇒ 带 `{ kind: "replace", confirmKey }` 再调一次。`intent`:没带过键 `absent` / 带的键已过期 `stale`。 */
140
+ | {
141
+ readonly kind: "confirm";
142
+ readonly receipt: EnvModelTableReceipt;
143
+ readonly confirmKey: string;
144
+ readonly intent: "absent" | "stale";
145
+ }
146
+ /** 不可确认:先改文档(summarize 要落到一条启用条目)。 */
147
+ | {
148
+ readonly kind: "fix_first";
149
+ readonly code: typeof SUMMARIZE_MODEL_MISSING_CODE;
150
+ readonly receipt: EnvModelTableReceipt;
151
+ };
152
+ /**
153
+ * 写者在写 `config.d/models.json` 之前调的**计划**(判序见文件头)。纯函数:不读环境、不读盘、不改入参。
154
+ * 目标腿 / 决定读数不在闭集 ⇒ `TypeError`;候选读不出(撤销的 Proxy 等)或整份读不进 models 域(重名、悬空的角色名 …)⇒ `TypeError`(先修文档)。
155
+ */
156
+ export declare function planEnvModelTableWrite(input: EnvModelTableWriteInput): EnvModelTableWritePlan;
157
+ /**
158
+ * 计划停下来的那两种结局(`confirm` / `fix_first`)的**一句话**(三端同一句;只用回执里的 env 键名、目录名与模型 id —— 可披露的原样,秘密形的打成记号;凭据与别的 env 值从不进句子)。
159
+ * 写类结局不是拒 ⇒ `TypeError`。
160
+ */
161
+ export declare function envModelTableWriteRefusal(plan: Extract<EnvModelTableWritePlan, {
162
+ readonly kind: "confirm" | "fix_first";
163
+ }>): string;
@@ -0,0 +1,257 @@
1
+ /**
2
+ * env 模型表 —— `MODEL_ID` / `MODEL_CHEAP_ID` 两槽与它们铸出的角色底 —— 的**一份读法**,与写者在写 `config.d/models.json` 之前调的**计划助手**
3
+ * (纯、零 I/O、浏览器安全;读配置根上的决定文档的函数在 `./node`)。
4
+ *
5
+ * ## 律
6
+ * · **env 两槽只在这里读一次**({@link readEnvModelSlots}):`MODEL_ID` 缺席或空串 = 没有主槽(纯空白算在场 —— 与引擎的 env 路由读口同尺);
7
+ * cheap 槽四态({@link EnvCheapSlot})。「env 模型表已声明」({@link envModelTableDeclared})与角色底({@link buildEnvModelBaseline})都由它派生 ——
8
+ * 这道闸的执行方(引擎一侧;今天引擎还不强制)与写者的计划助手调同一份(一次性迁移 SS-M11 只用两槽点名被顶替的 env 模型,铸不铸不看它),不各写一份。env 快照按「读到即算」(下标读;原型分层的快照里继承来的键算在场)。
9
+ * 引擎另有「名册占位 ⇒ env 表算空」一格,那是引擎自己的部署事实,不在本包。
10
+ * · **计划助手判序**({@link planEnvModelTableWrite}):目标腿不读 `config.d`(remote / none)⇒ 照写 → 候选不管模型面 ⇒ 照写 → summarize 会悬空 ⇒
11
+ * `fix_first`(不可确认:每个任务都要解 summarize,采纳后任务全拒)→ env 表未声明 ⇒ 照写、**不写确认** → 配置根的决定是 `replaced` ⇒ 照写 →
12
+ * 调用方带回的确认键等于这一次的 ⇒ `write_with_decision`(先经 `writeEnvModelsDecision` 写 `replaced`,再写目录;两次写在写者对 models.json 的同一把锁里)→
13
+ * 否则 `confirm`(把回执给用户看)。执法在引擎一侧(本地腿的顶替闸;今天引擎不拒这个组),本助手只是让写者在写之前知道这道闸强制起来会不会拒、会顶掉什么。
14
+ * · **回执按 id**:每个退场的 env 条目带 `servedAs`(候选里同 id 的启用条目名 —— 上游模型仍被服务,形可能不同);每个 env 底角色带写前绑谁
15
+ * (env 表键 + 它解到的 id)与采纳后落到哪个条目(`{name, id}`)。缺省落点经 `resolveCatalogDefaultModel`(与引擎的 stage 同一只解析器);
16
+ * 绑到停用条目或目录外的名 = `dangles`。确认键 = 回执的定序 JSON —— 后果(落点名 / id、env 槽、决定读数)一变,旧键就过期。
17
+ * · 可用性(引擎的 brain 腿、端点、读腿逐条丢)计划判不了;决定性的 summarize 那一格由引擎的不变量兜。
18
+ */
19
+ import { z } from "zod";
20
+ import { entryEnabledFlag, catalogManagesModelPlane } from "./catalog-manages.js";
21
+ import { parseDomainSalvaged, isDisclosableIdentity, resolveActiveTiers, withheldNameToken } from "./config-fns.js";
22
+ import { ENV_MODELS_DOCUMENT } from "./config-root-documents.js";
23
+ import { resolveCatalogDefaultModel } from "./default-model.js";
24
+ import { SUMMARIZE_MODEL_MISSING_CODE } from "./fleet.js";
25
+ import { landCatalogWord } from "./tier-words.js";
26
+ import { stableStringify } from "./hash.js";
27
+ import { captureJson, dataDescriptor, isolatedObject } from "./own-key-read.js";
28
+ import { ROSTER_CHEAP_ROLES, ROSTER_PRIMARY_ROLES } from "./resolve-roster.js";
29
+ /** env 模型表的键名(闭集)。 */
30
+ export const ENV_MODEL_TABLE_KEYS = { mainId: "MODEL_ID", cheapId: "MODEL_CHEAP_ID", verifier: "MODEL_VERIFIER", codeRoles: "MODEL_CODE_ROLES" };
31
+ /** env 表里主模型的别名键(角色底的 `default` 绑它;引擎 env 表的条目键 = 这个别名 + 主 id + cheap id)。 */
32
+ export const DEFAULT_MODEL_KEY = "default";
33
+ /** cheap 槽铸出来的 id(不铸 ⇒ `undefined`)。 */
34
+ export function mintedCheapId(slot) {
35
+ switch (slot.kind) {
36
+ case "minted":
37
+ return slot.id;
38
+ case "absent":
39
+ case "without_main":
40
+ case "same_as_main":
41
+ return undefined;
42
+ default: {
43
+ const unhandled = slot;
44
+ throw new Error(`mintedCheapId: unhandled slot ${JSON.stringify(unhandled)}`);
45
+ }
46
+ }
47
+ }
48
+ /**
49
+ * 包内(不进包根):把 env 模型表的键(缺省四把;调用方只要两槽时只拍两把)**读一次**进一份快照(下标读,同「读到即算」)。计划助手与一次性迁移在**要用的那一刻**拍、
50
+ * 此后的每个读法都答自同一份 —— 一只 getter 两次读答两个值时,判「env 表已声明」与列 env 模型不会各读到一个(那样结局与回执自相矛盾);用不到的键不读。
51
+ */
52
+ export function snapshotEnvModelTable(env, keys = Object.values(ENV_MODEL_TABLE_KEYS)) {
53
+ const snapshot = isolatedObject(); // the package's copy container (no inherited members to read)
54
+ for (const key of keys)
55
+ Object.defineProperty(snapshot, key, dataDescriptor(env[key]));
56
+ return snapshot;
57
+ }
58
+ /** env 两槽的**唯一读口**(见文件头)。`mainId` = `MODEL_ID`(空串 = 没有主槽)。 */
59
+ export function readEnvModelSlots(env) {
60
+ const mainId = env[ENV_MODEL_TABLE_KEYS.mainId] ?? "";
61
+ const rawCheapId = env[ENV_MODEL_TABLE_KEYS.cheapId];
62
+ const cheapSlot = !rawCheapId
63
+ ? { kind: "absent" }
64
+ : mainId === ""
65
+ ? { kind: "without_main", id: rawCheapId }
66
+ : rawCheapId === mainId
67
+ ? { kind: "same_as_main", id: rawCheapId }
68
+ : { kind: "minted", id: rawCheapId };
69
+ return { mainId, cheapSlot };
70
+ }
71
+ /** **env 模型表已声明吗**(= `MODEL_ID` 在场且非空串;纯空白算在场)—— 引擎一侧的顶替闸(今天还不强制)与计划助手共用的那一只谓词。 */
72
+ export function envModelTableDeclared(env) {
73
+ return readEnvModelSlots(env).mainId !== "";
74
+ }
75
+ /** 逗号串:逐项 trim、丢空项。 */
76
+ const csv = (raw) => (raw ?? "")
77
+ .split(",")
78
+ .map((s) => s.trim())
79
+ .filter(Boolean);
80
+ /**
81
+ * env 模型表的**角色底**(纯数据):条目(主槽在 ⇒ 主;cheap 槽铸了 ⇒ cheap)、角色(verifier ⇒ `MODEL_VERIFIER` 指 env 表里的键时是它、否则 `default`;
82
+ * 主角色族(`ROSTER_PRIMARY_ROLES`)⇒ `default`;cheap 角色族(`ROSTER_CHEAP_ROLES`)⇒ cheap id,cheap 没铸 ⇒ `default`;`MODEL_CODE_ROLES` 点名的、角色底自己有的键
83
+ * 加开发模式提示词标记)。env 表键 = 别名 `default` + 主 id + cheap id(主槽不在 ⇒ 表空)。
84
+ */
85
+ export function buildEnvModelBaseline(env) {
86
+ const { mainId, cheapSlot } = readEnvModelSlots(env);
87
+ const cheapId = mintedCheapId(cheapSlot);
88
+ const entries = mainId === "" ? [] : [{ id: mainId, slot: "main", env: ENV_MODEL_TABLE_KEYS.mainId }, ...(cheapId === undefined ? [] : [{ id: cheapId, slot: "cheap", env: ENV_MODEL_TABLE_KEYS.cheapId }])];
89
+ const tableKeys = new Set(mainId === "" ? [] : [DEFAULT_MODEL_KEY, mainId, ...(cheapId === undefined ? [] : [cheapId])]);
90
+ const rawVerifier = env[ENV_MODEL_TABLE_KEYS.verifier];
91
+ const verifier = !rawVerifier ? { kind: "unset" } : tableKeys.has(rawVerifier) ? { kind: "bound", ref: rawVerifier } : { kind: "rejected", raw: rawVerifier };
92
+ const roles = new Map([["verifier", verifier.kind === "bound" ? verifier.ref : DEFAULT_MODEL_KEY]]);
93
+ for (const r of ROSTER_PRIMARY_ROLES)
94
+ roles.set(r, DEFAULT_MODEL_KEY);
95
+ for (const r of ROSTER_CHEAP_ROLES)
96
+ roles.set(r, cheapId ?? DEFAULT_MODEL_KEY);
97
+ for (const name of csv(env[ENV_MODEL_TABLE_KEYS.codeRoles])) {
98
+ const role = name;
99
+ const cur = roles.get(role);
100
+ if (cur === undefined)
101
+ continue; // 只装饰角色底自己有的键(Map 没有原型上的名字)
102
+ roles.set(role, { model: typeof cur === "string" ? cur : cur.model, promptRole: "code" });
103
+ }
104
+ return { entries, roles: Object.fromEntries(roles), verifier };
105
+ }
106
+ const ENV_MODEL_TABLE_TARGETS = ["local", "remote", "none"];
107
+ const decisionKindOf = (decision) => {
108
+ const kind = decision?.kind;
109
+ switch (kind) {
110
+ case "absent":
111
+ case "replaced":
112
+ case "kept":
113
+ case "unreadable":
114
+ return kind;
115
+ default:
116
+ throw new TypeError(`planEnvModelTableWrite: decision must be a readEnvModelsDecision answer (got kind ${JSON.stringify(kind)})`);
117
+ }
118
+ };
119
+ /** 候选按引擎本地读腿的视图读(逐条抢救;整份读不进 ⇒ 抛 TypeError —— 引擎读不进的文档,本助手不替它算回执,先修文档)。 */
120
+ function candidateView(candidate) {
121
+ try {
122
+ return parseDomainSalvaged("models", candidate);
123
+ }
124
+ catch (e) {
125
+ const first = e instanceof z.ZodError ? e.issues[0] : undefined;
126
+ const why = first === undefined ? (e instanceof Error ? e.message : String(e)) : `${first.path.join(".") || "(document)"}: ${first.message}`;
127
+ throw new TypeError(`planEnvModelTableWrite: the candidate does not read as a models document — fix it first (${why})`);
128
+ }
129
+ }
130
+ function buildReceipt(baseline, view, decision) {
131
+ const enabled = view.models.filter((m) => entryEnabledFlag(m) !== false).map((m) => ({ name: m.name, id: m.id }));
132
+ const byName = new Map(enabled.map((e) => [e.name, e]));
133
+ const tiers = resolveActiveTiers(view);
134
+ /** 一个名采纳后落到哪条:经与引擎同一张档位展开(目录键先赢;env 底继承来的 `flash` 一类档位词按活跃组解)。 */
135
+ const land = (word) => {
136
+ const name = landCatalogWord(word, (n) => byName.has(n), tiers);
137
+ const e = name === undefined ? undefined : byName.get(name);
138
+ return e === undefined ? undefined : { name: e.name, id: e.id };
139
+ };
140
+ const main = baseline.entries.find((e) => e.slot === "main");
141
+ const idOf = (ref) => (ref === DEFAULT_MODEL_KEY ? (main?.id ?? null) : (baseline.entries.find((e) => e.id === ref)?.id ?? null));
142
+ /** 目录缺省的落点 —— 恒是 `resolveCatalogDefaultModel` 的答案(与引擎的 stage 同一只解析器;它的第一级就是候选自己的 `roles.default`,点名停用条目时它落到下一级)。
143
+ * `bound` = 候选自己绑了 `default`:解析器用的正是那一级 ⇒ `candidate_binds`,否则 `catalog_default`。 */
144
+ const viaCatalogDefault = (bound) => {
145
+ const fallback = enabled[0];
146
+ if (fallback === undefined)
147
+ return { kind: "dangles", landing: null };
148
+ const d = resolveCatalogDefaultModel(view, (n) => byName.has(n), fallback.name);
149
+ if (d.kind === "select")
150
+ return { kind: bound ? "candidate_binds" : "catalog_default", landing: null };
151
+ const l = land(d.name);
152
+ if (l === undefined)
153
+ return { kind: "dangles", landing: null };
154
+ return { kind: bound && d.source === "roles.default" ? "candidate_binds" : "catalog_default", landing: l };
155
+ };
156
+ const afterOf = (role, ref) => {
157
+ const bound = Object.hasOwn(view.roles, role) ? view.roles[role] : undefined;
158
+ if (role === DEFAULT_MODEL_KEY)
159
+ return viaCatalogDefault(bound !== undefined); // the default role lands where the catalog default resolves, bound or not
160
+ if (bound !== undefined) {
161
+ if (!("model" in bound))
162
+ return { kind: "candidate_binds", landing: null };
163
+ const l = land(bound.model);
164
+ return l === undefined ? { kind: "dangles", landing: null } : { kind: "candidate_binds", landing: l };
165
+ }
166
+ if (ref === DEFAULT_MODEL_KEY)
167
+ return viaCatalogDefault(false);
168
+ const l = land(ref);
169
+ return l === undefined ? { kind: "dangles", landing: null } : { kind: "candidate_entry", landing: l };
170
+ };
171
+ return {
172
+ decision,
173
+ envModels: baseline.entries.map((e) => ({ id: e.id, slot: e.slot, env: e.env, servedAs: enabled.filter((c) => c.id === e.id).map((c) => c.name) })),
174
+ roles: Object.entries(baseline.roles).map(([role, spec]) => {
175
+ const ref = typeof spec === "string" ? spec : spec.model;
176
+ const promptRole = typeof spec === "string" ? undefined : spec.promptRole;
177
+ return { role, before: { ref, id: idOf(ref) }, ...(promptRole === undefined ? {} : { promptRole }), after: afterOf(role, ref) };
178
+ }),
179
+ };
180
+ }
181
+ /**
182
+ * 写者在写 `config.d/models.json` 之前调的**计划**(判序见文件头)。纯函数:不读环境、不读盘、不改入参。
183
+ * 目标腿 / 决定读数不在闭集 ⇒ `TypeError`;候选读不出(撤销的 Proxy 等)或整份读不进 models 域(重名、悬空的角色名 …)⇒ `TypeError`(先修文档)。
184
+ */
185
+ export function planEnvModelTableWrite(input) {
186
+ const decision = decisionKindOf(input.decision);
187
+ const target = input.target;
188
+ if (!ENV_MODEL_TABLE_TARGETS.includes(target))
189
+ throw new TypeError(`planEnvModelTableWrite: target must be one of ${ENV_MODEL_TABLE_TARGETS.join(" / ")} (got ${JSON.stringify(target)})`);
190
+ switch (target) {
191
+ case "remote":
192
+ case "none":
193
+ return { kind: "write", why: "not_read" };
194
+ case "local":
195
+ break;
196
+ default: {
197
+ const unhandled = target;
198
+ throw new TypeError(`planEnvModelTableWrite: unhandled target ${JSON.stringify(unhandled)}`);
199
+ }
200
+ }
201
+ const copy = captureJson(input.candidate);
202
+ if (!copy.ok)
203
+ throw new TypeError(`planEnvModelTableWrite: the candidate could not be read — ${copy.sentence}`);
204
+ if (!catalogManagesModelPlane(copy.value))
205
+ return { kind: "write", why: "not_managing" };
206
+ const env = snapshotEnvModelTable(input.env); // read once: the receipt and the env-table rule answer from the same reading
207
+ const receipt = buildReceipt(buildEnvModelBaseline(env), candidateView(copy.value), decision);
208
+ if (receipt.roles.find((r) => r.role === "summarize")?.after.kind === "dangles")
209
+ return { kind: "fix_first", code: SUMMARIZE_MODEL_MISSING_CODE, receipt };
210
+ if (!envModelTableDeclared(env))
211
+ return { kind: "write", why: "env_table_empty" };
212
+ if (decision === "replaced")
213
+ return { kind: "write", why: "acknowledged", receipt };
214
+ const confirmKey = stableStringify(receipt);
215
+ const intent = input.intent;
216
+ if (intent !== undefined && intent.kind === "replace" && intent.confirmKey === confirmKey)
217
+ return { kind: "write_with_decision", receipt };
218
+ return { kind: "confirm", receipt, confirmKey, intent: intent === undefined ? "absent" : "stale" };
219
+ }
220
+ // ── 拒句投影 ─────────────────────────────────────────────────────────────────────────────────────────────
221
+ /** 句子里的一个名 / id:可披露 ⇒ 原样(加引号);否则 ⇒ 不可逆记号(秘密形不出门)。 */
222
+ const shown = (v) => (isDisclosableIdentity(v) ? JSON.stringify(v) : withheldNameToken(v));
223
+ const landingText = (after) => {
224
+ if (after.landing !== null)
225
+ return `${shown(after.landing.name)} (id ${shown(after.landing.id)})`;
226
+ return after.kind === "dangles" ? "nothing (no enabled catalog entry)" : "an entry the engine selects ({select})";
227
+ };
228
+ /**
229
+ * 计划停下来的那两种结局(`confirm` / `fix_first`)的**一句话**(三端同一句;只用回执里的 env 键名、目录名与模型 id —— 可披露的原样,秘密形的打成记号;凭据与别的 env 值从不进句子)。
230
+ * 写类结局不是拒 ⇒ `TypeError`。
231
+ */
232
+ export function envModelTableWriteRefusal(plan) {
233
+ const file = `${ENV_MODELS_DOCUMENT}.json`;
234
+ const kind = plan.kind;
235
+ switch (kind) {
236
+ case "confirm": {
237
+ const r = plan.receipt;
238
+ const models = r.envModels.map((e) => `${e.env} ${shown(e.id)} (${e.slot} slot) ${e.servedAs.length > 0 ? `stays served as ${e.servedAs.map(shown).join(", ")}` : "stops being served"}`).join("; ");
239
+ const roles = r.roles.map((row) => `${row.role} → ${landingText(row.after)}`).join(", ");
240
+ const decision = r.decision === "unreadable"
241
+ ? ` The config root's ${file} is unreadable; confirming overwrites it as a whole.`
242
+ : r.decision === "kept"
243
+ ? ` The config root's ${file} says "kept"; confirming overwrites it.`
244
+ : "";
245
+ return (`writing this models document replaces the env model table on the local config leg: ${models}; after adoption ${roles}.${decision} ` +
246
+ `To let the catalog replace them, confirm — the writer records ${file} = {"envModels":"replaced"} in the config root first, then writes the models document; to keep the env models, do not write it.`);
247
+ }
248
+ case "fix_first": {
249
+ const row = plan.receipt.roles.find((x) => x.role === "summarize");
250
+ const ref = row === undefined ? "" : ` (env binding ${shown(row.before.ref)})`;
251
+ return (`this models document leaves the summarize role unresolved${ref}: it lands on no enabled catalog entry, so every task would be refused after adoption (${SUMMARIZE_MODEL_MISSING_CODE}). ` +
252
+ "Bind roles.summarize to an enabled entry of the document, or add an enabled entry under the name it points at, then plan the write again.");
253
+ }
254
+ default:
255
+ throw new TypeError(`envModelTableWriteRefusal: only a "confirm" or "fix_first" plan is a refusal (got ${JSON.stringify(kind)})`);
256
+ }
257
+ }
package/dist/fleet.d.ts CHANGED
@@ -163,9 +163,15 @@ export type RestartSignal = z.infer<typeof RestartSignal>;
163
163
  * `config.model_refused_name_minted` — a refused entry's name would be re-minted onto another entry by the
164
164
  * tier / alias expansion (or the dropped-entry carrier cannot prove it would not be);
165
165
  * `config.model_route_endpoint_missing` — an openai-compatible route of the candidate resolves to no endpoint;
166
- * `config.gateway_fallback_without_primary` — fallback gateways are declared without a primary.
167
- * Source (engine): the first two in `src/model-entry-refusal.ts`, the last two in `src/model-route-endpoint.ts:40,42`
168
- * (invariant `model-route-endpoint`, judged by the model-plane stage in `src/config-center/apply-effective.ts:664-665`).
166
+ * `config.gateway_fallback_without_primary` — fallback gateways are declared without a primary;
167
+ * `config.catalog_replaces_env_models` ({@link CATALOG_REPLACES_ENV_MODELS_CODE}) — on the LOCAL config leg, a candidate that
168
+ * manages the model plane would replace a declared env model table (`MODEL_ID` …) while the config root's decision document
169
+ * (`config.env-models.json`, read by `readEnvModelsDecision`) does not say `replaced` — the env models keep serving;
170
+ * `config.summarize_model_missing` ({@link SUMMARIZE_MODEL_MISSING_CODE}) — the summarize role, which every task resolves,
171
+ * would land on no servable entry (every task would be refused after adoption).
172
+ * Source (engine): the first two in `src/model-entry-refusal.ts`, the next two in `src/model-route-endpoint.ts:40,42`
173
+ * (invariant `model-route-endpoint`, judged by the model-plane stage in `src/config-center/apply-effective.ts:664-665`); the
174
+ * last two are named HERE first (the code is a contract word: this package names it and exports the two constants for the engine to read; the engine does not mint them today).
169
175
  */
170
176
  export declare const MODEL_ENTRY_REFUSAL_CODES: readonly ["config.model_no_brain", "config.model_entry_invalid"];
171
177
  export declare const ModelEntryRefusalCode: z.ZodEnum<{
@@ -173,8 +179,14 @@ export declare const ModelEntryRefusalCode: z.ZodEnum<{
173
179
  "config.model_entry_invalid": "config.model_entry_invalid";
174
180
  }>;
175
181
  export type ModelEntryRefusalCode = z.infer<typeof ModelEntryRefusalCode>;
176
- export declare const MODEL_PLANE_REFUSAL_CODES: readonly ["config.default_model_missing", "config.model_refused_name_minted", "config.model_route_endpoint_missing", "config.gateway_fallback_without_primary"];
182
+ /** The env-model-table confirmation gate's group-refusal code (see {@link MODEL_PLANE_REFUSAL_CODES}). */
183
+ export declare const CATALOG_REPLACES_ENV_MODELS_CODE: "config.catalog_replaces_env_models";
184
+ /** The summarize invariant's group-refusal code (see {@link MODEL_PLANE_REFUSAL_CODES}); a writer's plan answers `fix_first` with it. */
185
+ export declare const SUMMARIZE_MODEL_MISSING_CODE: "config.summarize_model_missing";
186
+ export declare const MODEL_PLANE_REFUSAL_CODES: readonly ["config.default_model_missing", "config.model_refused_name_minted", "config.model_route_endpoint_missing", "config.gateway_fallback_without_primary", "config.catalog_replaces_env_models", "config.summarize_model_missing"];
177
187
  export declare const ModelPlaneRefusalCode: z.ZodEnum<{
188
+ "config.catalog_replaces_env_models": "config.catalog_replaces_env_models";
189
+ "config.summarize_model_missing": "config.summarize_model_missing";
178
190
  "config.default_model_missing": "config.default_model_missing";
179
191
  "config.model_refused_name_minted": "config.model_refused_name_minted";
180
192
  "config.model_route_endpoint_missing": "config.model_route_endpoint_missing";
@@ -566,9 +578,15 @@ export declare const FLEET_ADVERTISE_ADDRESS_ENV = "FLEET_ADVERTISE_ADDRESS";
566
578
  * - {@link SERVING_LEASE_TTL_DEFAULT_MS} — the lease length when the operator sets none (10 min).
567
579
  * - {@link SERVING_LEASE_TTL_FLOOR_MS} — the shortest lease there is: a configured TTL below it is treated as it (1 min),
568
580
  * the same way {@link DRAIN_GRACE_FLOOR_MS} protects a fat-fingered grace.
569
- * - {@link SERVING_LEASE_DRIFT_MARGIN_MS} — web-admin's clock-skew allowance: it adds this to a lease's recorded expiry
570
- * before it counts that lease as over. It is DERIVED from {@link DEFAULT_HEARTBEAT_MS} (one cadence, one number), never a
571
- * second constant that could drift from it.
581
+ * - {@link SERVING_LEASE_DRIFT_MARGIN_MS} — the clock-skew allowance for reading a lease deadline an engine REPORTS. The engine
582
+ * says `EngineSignal.leaseUntil` (epoch ms: its own computation from the granted `ttlMs`, on its own clock); a reader that asks
583
+ * "does this member's lease still hold?" compares that instant with ITS OWN now — two clocks that need not agree — so it counts
584
+ * the lease as held only while `leaseUntil >= now + SERVING_LEASE_DRIFT_MARGIN_MS`. That is the conservative direction: a skew
585
+ * up to the margin can make a lease that still holds read as short, never one that is over read as held. It is an allowance for
586
+ * reading a reported deadline, not part of the lease clock: the engine enforces the lease on its own monotonic clock and
587
+ * web-admin keeps its own expiry from its store time (see {@link ServingLeaseResponse}) — the two sides never compare clocks
588
+ * to enforce. It is DERIVED from {@link DEFAULT_HEARTBEAT_MS} (one cadence, one number), never a second constant that could
589
+ * drift from it.
572
590
  */
573
591
  export declare const SERVING_LEASE_TTL_DEFAULT_MS = 600000;
574
592
  export declare const SERVING_LEASE_TTL_FLOOR_MS = 60000;
package/dist/fleet.js CHANGED
@@ -231,17 +231,29 @@ function readSignalBody(body, pick, schema) {
231
231
  * `config.model_refused_name_minted` — a refused entry's name would be re-minted onto another entry by the
232
232
  * tier / alias expansion (or the dropped-entry carrier cannot prove it would not be);
233
233
  * `config.model_route_endpoint_missing` — an openai-compatible route of the candidate resolves to no endpoint;
234
- * `config.gateway_fallback_without_primary` — fallback gateways are declared without a primary.
235
- * Source (engine): the first two in `src/model-entry-refusal.ts`, the last two in `src/model-route-endpoint.ts:40,42`
236
- * (invariant `model-route-endpoint`, judged by the model-plane stage in `src/config-center/apply-effective.ts:664-665`).
234
+ * `config.gateway_fallback_without_primary` — fallback gateways are declared without a primary;
235
+ * `config.catalog_replaces_env_models` ({@link CATALOG_REPLACES_ENV_MODELS_CODE}) — on the LOCAL config leg, a candidate that
236
+ * manages the model plane would replace a declared env model table (`MODEL_ID` …) while the config root's decision document
237
+ * (`config.env-models.json`, read by `readEnvModelsDecision`) does not say `replaced` — the env models keep serving;
238
+ * `config.summarize_model_missing` ({@link SUMMARIZE_MODEL_MISSING_CODE}) — the summarize role, which every task resolves,
239
+ * would land on no servable entry (every task would be refused after adoption).
240
+ * Source (engine): the first two in `src/model-entry-refusal.ts`, the next two in `src/model-route-endpoint.ts:40,42`
241
+ * (invariant `model-route-endpoint`, judged by the model-plane stage in `src/config-center/apply-effective.ts:664-665`); the
242
+ * last two are named HERE first (the code is a contract word: this package names it and exports the two constants for the engine to read; the engine does not mint them today).
237
243
  */
238
244
  export const MODEL_ENTRY_REFUSAL_CODES = ["config.model_no_brain", "config.model_entry_invalid"];
239
245
  export const ModelEntryRefusalCode = z.enum(MODEL_ENTRY_REFUSAL_CODES);
246
+ /** The env-model-table confirmation gate's group-refusal code (see {@link MODEL_PLANE_REFUSAL_CODES}). */
247
+ export const CATALOG_REPLACES_ENV_MODELS_CODE = "config.catalog_replaces_env_models";
248
+ /** The summarize invariant's group-refusal code (see {@link MODEL_PLANE_REFUSAL_CODES}); a writer's plan answers `fix_first` with it. */
249
+ export const SUMMARIZE_MODEL_MISSING_CODE = "config.summarize_model_missing";
240
250
  export const MODEL_PLANE_REFUSAL_CODES = [
241
251
  "config.default_model_missing",
242
252
  "config.model_refused_name_minted",
243
253
  "config.model_route_endpoint_missing",
244
254
  "config.gateway_fallback_without_primary",
255
+ CATALOG_REPLACES_ENV_MODELS_CODE,
256
+ SUMMARIZE_MODEL_MISSING_CODE,
245
257
  ];
246
258
  export const ModelPlaneRefusalCode = z.enum(MODEL_PLANE_REFUSAL_CODES);
247
259
  /**
@@ -594,9 +606,15 @@ export const FLEET_ADVERTISE_ADDRESS_ENV = "FLEET_ADVERTISE_ADDRESS";
594
606
  * - {@link SERVING_LEASE_TTL_DEFAULT_MS} — the lease length when the operator sets none (10 min).
595
607
  * - {@link SERVING_LEASE_TTL_FLOOR_MS} — the shortest lease there is: a configured TTL below it is treated as it (1 min),
596
608
  * the same way {@link DRAIN_GRACE_FLOOR_MS} protects a fat-fingered grace.
597
- * - {@link SERVING_LEASE_DRIFT_MARGIN_MS} — web-admin's clock-skew allowance: it adds this to a lease's recorded expiry
598
- * before it counts that lease as over. It is DERIVED from {@link DEFAULT_HEARTBEAT_MS} (one cadence, one number), never a
599
- * second constant that could drift from it.
609
+ * - {@link SERVING_LEASE_DRIFT_MARGIN_MS} — the clock-skew allowance for reading a lease deadline an engine REPORTS. The engine
610
+ * says `EngineSignal.leaseUntil` (epoch ms: its own computation from the granted `ttlMs`, on its own clock); a reader that asks
611
+ * "does this member's lease still hold?" compares that instant with ITS OWN now — two clocks that need not agree — so it counts
612
+ * the lease as held only while `leaseUntil >= now + SERVING_LEASE_DRIFT_MARGIN_MS`. That is the conservative direction: a skew
613
+ * up to the margin can make a lease that still holds read as short, never one that is over read as held. It is an allowance for
614
+ * reading a reported deadline, not part of the lease clock: the engine enforces the lease on its own monotonic clock and
615
+ * web-admin keeps its own expiry from its store time (see {@link ServingLeaseResponse}) — the two sides never compare clocks
616
+ * to enforce. It is DERIVED from {@link DEFAULT_HEARTBEAT_MS} (one cadence, one number), never a second constant that could
617
+ * drift from it.
600
618
  */
601
619
  export const SERVING_LEASE_TTL_DEFAULT_MS = 600_000;
602
620
  export const SERVING_LEASE_TTL_FLOOR_MS = 60_000;
package/dist/index.d.ts CHANGED
@@ -7,6 +7,9 @@ export * from "./execution-lanes.js";
7
7
  export * from "./url-shape.js";
8
8
  export * from "./url-credential.js";
9
9
  export * from "./default-model.js";
10
+ export { catalogManagesModelPlane, enabledEntryCount } from "./catalog-manages.js";
11
+ export { DEFAULT_MODEL_KEY, ENV_MODEL_TABLE_KEYS, buildEnvModelBaseline, envModelTableDeclared, envModelTableWriteRefusal, mintedCheapId, planEnvModelTableWrite, readEnvModelSlots, type EnvCheapSlot, type EnvModelBaseline, type EnvModelTableEntry, type EnvModelTableKey, type EnvModelTableLanding, type EnvModelTableReceipt, type EnvModelTableRoleRow, type EnvModelTableTarget, type EnvModelTableWriteInput, type EnvModelTableWriteIntent, type EnvModelTableWritePlan, type EnvRoleBinding, type EnvSnapshot, } from "./env-model-table.js";
12
+ export { CONFIG_ROOT_DOCUMENTS, ENV_MODELS_DECISIONS, ENV_MODELS_DOCUMENT, ROOT_DOCUMENT_UNREADABLE_REASONS, envModelsDecisionTag, type ConfigRootDocument, type EnvModelsDecision, type EnvModelsDecisionWord, type MarkedMigrationId, type MigrationMarker, type RootDocumentUnreadableReason } from "./config-root-documents.js";
10
13
  export * from "./loopback.js";
11
14
  export * from "./hash.js";
12
15
  export * from "./sha256.js";
@@ -42,3 +45,5 @@ export * from "./mcp-server-policy.js";
42
45
  export * from "./ui-language.js";
43
46
  export * from "./control-and-bidi.js";
44
47
  export * from "./mcp-request-entry.js";
48
+ export { REMOTE_MCP_SERVER_NAME_PREFIX } from "./mcp-server-name.js";
49
+ export * from "./center-refs.js";
package/dist/index.js CHANGED
@@ -7,6 +7,9 @@ export * from "./execution-lanes.js"; // 7.0.0 M-R2: the ONE lane / store vocabu
7
7
  export * from "./url-shape.js"; // 11.0.0: THE URL shape judge (httpUrlFlaws / httpUrlRule / isHierarchicalUrl), pure/browser-safe
8
8
  export * from "./url-credential.js"; // 14.5.0: THE "does this URL carry a credential" judge (findUrlCredential / findUrlUserinfo / URL_CREDENTIAL_REASONS / CREDENTIAL_PARAM_NAMES …), pure/browser-safe
9
9
  export * from "./default-model.js"; // 11.0.0: THE catalog-default resolver (roles.default → active tier ladder → fallback), pure/browser-safe
10
+ export { catalogManagesModelPlane, enabledEntryCount } from "./catalog-manages.js"; // THE "does this models document manage the model plane" judge (raw half: at least one entry not explicitly disabled) + the log count it is derived from, pure/browser-safe
11
+ export { DEFAULT_MODEL_KEY, ENV_MODEL_TABLE_KEYS, buildEnvModelBaseline, envModelTableDeclared, envModelTableWriteRefusal, mintedCheapId, planEnvModelTableWrite, readEnvModelSlots, } from "./env-model-table.js"; // the env model table (MODEL_ID / MODEL_CHEAP_ID) read once: readEnvModelSlots / envModelTableDeclared / buildEnvModelBaseline + the writer's plan planEnvModelTableWrite (receipt by id, confirmKey) + envModelTableWriteRefusal, pure/browser-safe (named: the env snapshot helper stays package-private)
12
+ export { CONFIG_ROOT_DOCUMENTS, ENV_MODELS_DECISIONS, ENV_MODELS_DOCUMENT, ROOT_DOCUMENT_UNREADABLE_REASONS, envModelsDecisionTag } from "./config-root-documents.js"; // the config-root documents (config.env-models.json + the per-id migration marker config.migration.<id>.json): names + closed answer tables + the one tagged identity of a decision reading (envModelsDecisionTag); the readers / writer are in ./node
10
13
  export * from "./loopback.js"; // 11.0.0: the two「本机」predicates (auth relaxation — narrow / connection routing — wide), pure/browser-safe
11
14
  export * from "./hash.js";
12
15
  export * from "./sha256.js";
@@ -42,6 +45,8 @@ export * from "./mcp-server-policy.js"; // 13.0.0 (U6): mcpServerPolicyVerdict
42
45
  export * from "./ui-language.js"; // 13.0.0 (S-1592): the UI-language settings fragment (semaUiLanguage, en | zh, category ui, user settings) + readUiLanguage, pure/browser-safe
43
46
  export * from "./control-and-bidi.js"; // the one-line echo strip set (C0 + DEL + the 12 Bidi_Control code points) — CONTROL_AND_BIDI_CHARS (the class, as regex source text) + stripControlAndBidi / hasControlOrBidi, pure/browser-safe
44
47
  export * from "./mcp-request-entry.js"; // 14.0.0: the request-plane MCP entry judges (isUsableWireName / isContentOriginWord / readMcpIdentityHeaders / readRequestMcpTransport + the value-shape describers) — one reading for the service legs and the shell pre-check, pure/browser-safe
48
+ export { REMOTE_MCP_SERVER_NAME_PREFIX } from "./mcp-server-name.js"; // the MCP server-name prefix reserved for the remote MCP servers a device executor offers (mount name = prefix + the executor's server name); the predicate and its sentence stay package-private — the request plane's MCP name judges (isUsableMcpServerName / describeBadMcpServerName) read them; the config plane's McpServerSpec.name accepts the prefix (refusing it would be a validation tightening — a major), pure/browser-safe
49
+ export * from "./center-refs.js"; // resolveCenterRefs(refs, lookup) — THE center-roster reference resolver: one verdict word per member (missing_name / not_a_string / read_threw, CENTER_REF_FAULTS), one whole verdict (resolved / unresolved / refs_unreadable, CENTER_REFS_VERDICTS); the refs record is read by the public readPlainStringRecord, the lookup is the caller's, it reads no process.env; pure/browser-safe
45
50
  // NOTE: the Node-fs-backed helpers (FileConfigStore + the file editor) are NOT in this barrel — they pull
46
51
  // `node:fs`/`node:path` and would break a browser/client bundle that only wants the contract (e.g.
47
52
  // web-admin's client components import the zod types through here). Import them from the Node-only