@sema-agent/server 7.101.1 → 7.103.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.
- package/MIGRATION.md +84 -0
- package/README.md +4 -4
- package/README.zh-CN.md +4 -4
- package/USAGE.md +156 -9
- package/dist/approval-card.d.ts +35 -2
- package/dist/approval-card.js +19 -1
- package/dist/background-session-reap.d.ts +58 -0
- package/dist/background-session-reap.js +90 -0
- package/dist/bake-runner/main.d.ts +21 -3
- package/dist/bake-runner/main.js +18 -11
- package/dist/boot/config-center.d.ts +21 -4
- package/dist/boot/config-center.js +62 -19
- package/dist/boot/exit-boundary.js +17 -0
- package/dist/boot/runner-deps.js +1 -0
- package/dist/boot/runtime-caps.js +3 -2
- package/dist/boot/session-faces.js +3 -4
- package/dist/boot/shutdown.d.ts +2 -1
- package/dist/boot/shutdown.js +33 -12
- package/dist/boot/stage-01-config.js +2 -0
- package/dist/boot/stage-07-capability-layer.d.ts +1 -1
- package/dist/boot/stage-07-capability-layer.js +6 -4
- package/dist/boot/stage-08-reapers.d.ts +1 -1
- package/dist/boot/stage-09-leader.d.ts +1 -1
- package/dist/boot/stage-10-http-server.d.ts +1 -1
- package/dist/boot/stage-10-http-server.js +13 -4
- package/dist/brain.d.ts +28 -8
- package/dist/brain.js +20 -4
- package/dist/capabilities/builtin-tools.d.ts +14 -1
- package/dist/capabilities/builtin-tools.js +3 -0
- package/dist/capabilities/center-plugins.d.ts +9 -3
- package/dist/capabilities/center-plugins.js +5 -10
- package/dist/capabilities/scenarios.d.ts +1 -1
- package/dist/capabilities/scenarios.js +3 -3
- package/dist/center-credential-renewer.d.ts +76 -0
- package/dist/center-credential-renewer.js +359 -0
- package/dist/center-credential.d.ts +76 -22
- package/dist/center-credential.js +119 -43
- package/dist/config-catalog.js +17 -8
- package/dist/config-center/apply-effective.d.ts +16 -0
- package/dist/config-center/apply-effective.js +26 -21
- package/dist/config-center/center-request.d.ts +13 -0
- package/dist/config-center/center-request.js +23 -3
- package/dist/config-center/facade.d.ts +1 -1
- package/dist/config-center/http-client.js +2 -5
- package/dist/config-center/mcp-revocation.d.ts +2 -2
- package/dist/config-center/mcp-revocation.js +1 -1
- package/dist/config-center/read-warnings.d.ts +19 -48
- package/dist/config-center/read-warnings.js +7 -49
- package/dist/config-center/restart-signal.d.ts +8 -21
- package/dist/config-center/restart-signal.js +2 -12
- package/dist/config-center/types.d.ts +4 -2
- package/dist/config-provider.js +1 -48
- package/dist/config-types.d.ts +107 -17
- package/dist/config-types.js +7 -2
- package/dist/config.d.ts +7 -0
- package/dist/config.js +91 -29
- package/dist/device-store.d.ts +1 -1
- package/dist/env-name-allowlist-knobs.d.ts +21 -7
- package/dist/env-name-allowlist-knobs.js +7 -2
- package/dist/fleet-client.d.ts +27 -7
- package/dist/fleet-client.js +43 -12
- package/dist/fleet-lease.d.ts +1 -1
- package/dist/host-lsp-manager.d.ts +4 -3
- package/dist/host-lsp-manager.js +2 -6
- package/dist/http/approval-sweeps.js +5 -20
- package/dist/http/dispatch.js +24 -14
- package/dist/http/readiness.d.ts +87 -0
- package/dist/http/readiness.js +54 -0
- package/dist/http/resume-legs.js +5 -4
- package/dist/http/route-table.d.ts +7 -0
- package/dist/http/route-table.js +6 -0
- package/dist/http/routes/a2a-serve.js +6 -14
- package/dist/http/routes/approvals-assistant.js +1 -0
- package/dist/http/routes/background.d.ts +80 -0
- package/dist/http/routes/background.js +94 -0
- package/dist/http/routes/capabilities.js +2 -0
- package/dist/http/server.d.ts +25 -8
- package/dist/http/server.js +2 -2
- package/dist/leader/wire.d.ts +8 -1
- package/dist/leader/wire.js +4 -3
- package/dist/model-entry-refusal.d.ts +42 -19
- package/dist/model-entry-refusal.js +34 -19
- package/dist/model-route-endpoint.d.ts +10 -0
- package/dist/model-route-endpoint.js +6 -2
- package/dist/observability/fail-open.d.ts +2 -2
- package/dist/observability/fail-open.js +2 -2
- package/dist/observability/secret-env-scrub.d.ts +12 -0
- package/dist/observability/secret-env-scrub.js +12 -0
- package/dist/outbound-dispatcher.d.ts +125 -0
- package/dist/outbound-dispatcher.js +211 -0
- package/dist/parked-decide.d.ts +11 -5
- package/dist/parked-decide.js +4 -1
- package/dist/parked-revive-surface.d.ts +47 -0
- package/dist/parked-revive-surface.js +20 -0
- package/dist/plugins/background-shell-support.d.ts +20 -1
- package/dist/plugins/background-shell-support.js +26 -0
- package/dist/plugins/host-platform.d.ts +16 -1
- package/dist/plugins/host-platform.js +11 -0
- package/dist/plugins/remote-env-host.d.ts +14 -1
- package/dist/plugins/remote-env-host.js +15 -10
- package/dist/project-memory.js +3 -6
- package/dist/run-local.js +2 -0
- package/dist/server-secret-env.d.ts +97 -24
- package/dist/server-secret-env.js +37 -1
- package/dist/tool-approval.d.ts +30 -1
- package/dist/tool-approval.js +31 -8
- package/dist/trace/core-keyset-guard.d.ts +9 -4
- package/dist/trace/engine-notice-wire.d.ts +1 -1
- package/dist/trace/engine-notice-wire.js +2 -0
- package/dist/trace/project.js +13 -0
- package/dist/trace/redact.d.ts +11 -0
- package/dist/trace/redact.js +1 -0
- package/package.json +4 -3
|
@@ -46,8 +46,8 @@ export declare function createMcpRevocationLedger(onChange?: (ev: {
|
|
|
46
46
|
}) => void): McpRevocationLedger;
|
|
47
47
|
/** 一拍 EffectiveConfig 的 ENABLED MCP 服务器条目表(`resolveMcpServers` 同判据:`enabled !== false`;
|
|
48
48
|
* 值=整条目的 key 排序 JSON 指纹,Map 插入序=发布序);mcp 域缺席 ⇒ `null`(台账不动)。
|
|
49
|
-
* 指纹序列化器 =
|
|
50
|
-
*
|
|
49
|
+
* 指纹序列化器 = `@sema-agent/settings-schema/hash` 的 `stableStringify`(S-778 起;[ref] 2026-08-31 起与 restart-signal 同源:此前本文件
|
|
50
|
+
* 持一份**逐字相同**的私有复刻 `fingerprintOf`;JSON 形输入上字节恒等,golden 钉 test/canonical-json-golden.test.ts)。
|
|
51
51
|
* 两处指纹仍只在各自域内自比、不跨域比对——同源只是去掉一份多余的复刻,不引入跨域约束;也**不**改用
|
|
52
52
|
* `src/canonical-json.ts`(undefined 值键语义不同,见 restart-signal.ts 该函数顶注)。 */
|
|
53
53
|
export declare function enabledMcpEntries(mcp: {
|
|
@@ -25,7 +25,7 @@
|
|
|
25
25
|
* 候选门整拒,而它在 1.10.0 上零告警零 domainErrors(亲验读数)。兼容臂的关窗观测归属主(settings-schema 的
|
|
26
26
|
* `onWarning` 流),不借候选门这条带副作用的通道传信息。
|
|
27
27
|
*/
|
|
28
|
-
import { type
|
|
28
|
+
import { type EffectiveReadWarning } from "@sema-agent/settings-schema";
|
|
29
29
|
/** 折叠产物:`scope` 决定消费方做什么 —— `"domain"` 进候选门(`domainErrors`),`"entry"` 进逐条丢弃面(`droppedEntries`)。 */
|
|
30
30
|
export type FoldedReadWarning = {
|
|
31
31
|
readonly scope: "domain";
|
|
@@ -58,72 +58,43 @@ export declare function describeSchemaIssueFields(error: unknown): string[];
|
|
|
58
58
|
/** null = 不入诊断面(`hosts-grandfathered` / `legacy-governance-lifted`:值已被收编接受,与本地店同口径)。 */
|
|
59
59
|
export declare function foldEffectiveReadWarning(w: EffectiveReadWarning): FoldedReadWarning | null;
|
|
60
60
|
/**
|
|
61
|
-
* 🔴 S-712 —— 一条被 schema
|
|
61
|
+
* 🔴 S-712 —— 一条被 schema 逐条丢掉的**启用**、**有名**目录条目的身份(模型面墓碑的来源;载体 = `EffectiveConfig.models.droppedIdentities`,
|
|
62
62
|
* 随 effective 走 —— 盘上 LKG 从同一个对象落盘,重启从 LKG 起服墓碑不丢)。
|
|
63
63
|
*
|
|
64
64
|
* 为什么要交出身份:条目被丢之后它的名字从目录里消失,而档位增广(core `expandTiers`)会把一个**空出来的**档位词 / CC 别名
|
|
65
65
|
* (条目恰好叫 `sonnet` / `pro` …)重新铸到另一条条目上 —— 显式点它的请求与点名它的角色绑定就这样被静默换了目的地
|
|
66
66
|
* (gpt-6 腿 F2 / F3,真复现)。身份进 `modelRefusals` 之后,不变量 `refused-names-not-minted` 才判得到它。
|
|
67
67
|
*
|
|
68
|
-
* 🔐 纯身份,不含值:`name` / `id`
|
|
69
|
-
*
|
|
70
|
-
* 绝不进载体、日志、LKG 与 400 拒因句。
|
|
68
|
+
* 🔐 纯身份,不含值:`name` / `id` 取自告警的 `identity`(settings-schema 6.1.0:只取被丢条目原文里的字符串形,脱敏器会改动的串
|
|
69
|
+
* 整键扣下),`fields` 是坏字段的**键路径**,`issues` 是 {@link describeSchemaIssues} 的一行(键名 + 类型词)—— 被丢条目的原值
|
|
70
|
+
* (可能是一段贴错位置的凭据)绝不进载体、日志、LKG 与 400 拒因句。
|
|
71
71
|
*/
|
|
72
72
|
export interface DroppedModelIdentity {
|
|
73
73
|
/** 出门形坐标(`models.1`),与同一次读的 `droppedEntries[].entry` 同一串。 */
|
|
74
74
|
readonly entry: string;
|
|
75
|
-
/** 条目写的 `name
|
|
76
|
-
*
|
|
77
|
-
|
|
78
|
-
readonly name?: string;
|
|
75
|
+
/** 条目写的 `name`(读腿只为有名的行铸载体)。载体上缺了它 = 载体形不对(LKG 载体被改 / 手写 effective)⇒ 模型面整组拒,
|
|
76
|
+
* 判在 `model-entry-refusal.ts` `judgeCatalogEntries`。 */
|
|
77
|
+
readonly name: string;
|
|
79
78
|
readonly id?: string;
|
|
80
|
-
readonly provider?: string;
|
|
81
79
|
readonly fields: readonly string[];
|
|
82
80
|
readonly issues: string;
|
|
83
81
|
}
|
|
84
|
-
/** 本次读的模型域**原文**与**解析产物**(两腿各自手里都有这两件:远端 = 响应体与 `readEffectiveWire` 输出;本地 = 盘上
|
|
85
|
-
* `config.d/models.json` 与 `FileConfigStore` 输出)。 */
|
|
86
|
-
export interface ModelsDomainSource {
|
|
87
|
-
/** 模型域的原始文档(`{ models: [...], roles, … }`);读不到 ⇒ `undefined`(身份一律证明不了)。 */
|
|
88
|
-
readonly raw: unknown;
|
|
89
|
-
/** 同一次读解析后幸存的条目(schema 逐条丢弃之后的 `models.models`)。 */
|
|
90
|
-
readonly parsed: readonly unknown[] | undefined;
|
|
91
|
-
}
|
|
92
|
-
/**
|
|
93
|
-
* 一条被丢行在原文档里那一格的读数 —— **三态**(7.101.0 验修轮 V-2,规则变小:「读不出」只剩一种含义):
|
|
94
|
-
* · `named`:格读到了、`name` 是字符串 ⇒ 身份已定(启用的铸墓碑,停用的不是);
|
|
95
|
-
* · `nameless`:格读到了、没有字符串 `name`(`null` / 数 / 整格不是对象)⇒ 身份**也**已定:**没有可保留的名字** —— 请求、
|
|
96
|
-
* `@`-mention、角色绑定都点不到一个非字符串名,档位增广也铸不出它 ⇒ 与 `enabled:false` 同处置(不是墓碑、不拒组);
|
|
97
|
-
* · `undefined`(由 {@link droppedEntryIdentity} 答):格读不到 —— 坐标在原文档里不存在;调用方在原文档与本次解析对不上
|
|
98
|
-
* (`sameRead` 假)时根本不来读 ⇒ 两者同是「身份证明不了」,交一条不带 `name` 的载体(模型面据此整组拒)。
|
|
99
|
-
* 此前 `nameless` 被折进「读不出」⇒ 一行 `{ name: null }` 的坏行(哪怕是停用的)就让整个模型面被拒(codex 验修腿 V-2,boot 腿对
|
|
100
|
-
* v7.100.0 的窄回归:那时 boot 对同一文档宽容采纳幸存条目)。
|
|
101
|
-
*/
|
|
102
|
-
export type DroppedEntryCell = {
|
|
103
|
-
readonly kind: "named";
|
|
104
|
-
readonly name: string;
|
|
105
|
-
readonly id?: string;
|
|
106
|
-
readonly provider?: string;
|
|
107
|
-
readonly enabled: boolean;
|
|
108
|
-
} | {
|
|
109
|
-
readonly kind: "nameless";
|
|
110
|
-
};
|
|
111
|
-
/**
|
|
112
|
-
* 🔴 S-712 **唯一**的身份读法:按告警坐标(`path`,模型域文档内的段坐标 —— settings-schema 已把它回写成原文档坐标)回读
|
|
113
|
-
* 原文档那一格,答 {@link DroppedEntryCell} 三态之一;坐标在原文档里走不通 ⇒ `undefined`(格读不到)。字符串才算
|
|
114
|
-
* (`name` / `id` / `provider`),`enabled` 只认字面 `false` 为停用。
|
|
115
|
-
* settings-schema 6.1.0 的 `entry-dropped` 自带 `name` / `id` 到货后,本函数改读告警(同一只函数,换数据源),原文档入参随之删除。
|
|
116
|
-
*/
|
|
117
|
-
export declare function droppedEntryIdentity(rawDoc: unknown, path: ConfigPath): DroppedEntryCell | undefined;
|
|
118
82
|
/**
|
|
119
83
|
* 两条读腿(远端 `http-client.ts` / 本地 `config-provider.ts`)共用的**分面单点**:折叠产物按 `scope` 分进候选门
|
|
120
84
|
* (`domainErrors`)与逐条丢弃面(`droppedEntries`)。`switch` 穷举 —— 加一种 scope 不补臂 = 编译红,两腿不会各分各的。
|
|
121
85
|
* 文案**未脱敏**:进日志流之前各腿在自己的产出边界上过 `redactConfigError`(与 `domainErrors` 同一道)。
|
|
122
|
-
*
|
|
123
|
-
*
|
|
124
|
-
*
|
|
86
|
+
*
|
|
87
|
+
* 🔴 S-712 身份规则(一条,数据源 = 告警自带的 `identity`,settings-schema 6.1.0 在**同一次解析**里从被丢条目原文取 —— 本仓不再回读原文档):
|
|
88
|
+
* **`identity.name` 是字符串 ⇒ 有名;有名且 `identity.enabled !== false` ⇒ 墓碑(进载体);其余一律不进载体。**
|
|
89
|
+
* · 有名而停用 ⇒ 停用 = 不在目录(与 7.100.0 同义);
|
|
90
|
+
* · 没有字符串名 —— `identity` 在场而无 `name`,或 `identity` 整个缺席 ⇒ **无名可留**,与停用同处置。按 settings-schema 6.1.0 的铸点
|
|
91
|
+
* (`config-fns.ts` `readEntryIdentity`),「没有名」有两个成因、刻意不分:原文 `name` 不是字符串(`null` / 数 / 整行不是对象),
|
|
92
|
+
* 或是字符串但脱敏器会改动它(秘密形 —— 扣下不出门)。后者对本判据同样安全:档位增广只铸闭集词(`ultra` / `max` / `pro` /
|
|
93
|
+
* `flash` / `lite`、CC 别名、`best`),没有一个是秘密形,一个被扣下的名字无从被铸出;它的代价只是显式点它的请求拿到「未知模型」
|
|
94
|
+
* 而不是 `request.model_unavailable`(那个名字本来也不许进日志与拒因句)。
|
|
95
|
+
* 坏行本身照样走 `droppedEntries`(坐标 + 字段,不带值)。
|
|
125
96
|
*/
|
|
126
|
-
export declare function splitFoldedReadWarnings(warnings: readonly EffectiveReadWarning[]
|
|
97
|
+
export declare function splitFoldedReadWarnings(warnings: readonly EffectiveReadWarning[]): {
|
|
127
98
|
domainErrors: Array<{
|
|
128
99
|
domain: string;
|
|
129
100
|
error: string;
|
|
@@ -68,38 +68,10 @@ export function foldEffectiveReadWarning(w) {
|
|
|
68
68
|
}
|
|
69
69
|
}
|
|
70
70
|
}
|
|
71
|
-
const isRecord = (v) => typeof v === "object" && v !== null && !Array.isArray(v);
|
|
72
|
-
export function droppedEntryIdentity(rawDoc, path) {
|
|
73
|
-
let at = rawDoc;
|
|
74
|
-
for (const seg of path) {
|
|
75
|
-
if (typeof seg === "number") {
|
|
76
|
-
if (!Array.isArray(at) || !Number.isInteger(seg) || seg < 0 || seg >= at.length)
|
|
77
|
-
return undefined;
|
|
78
|
-
at = at[seg];
|
|
79
|
-
}
|
|
80
|
-
else {
|
|
81
|
-
if (!isRecord(at) || !Object.hasOwn(at, seg))
|
|
82
|
-
return undefined;
|
|
83
|
-
at = at[seg];
|
|
84
|
-
}
|
|
85
|
-
}
|
|
86
|
-
if (!isRecord(at) || typeof at.name !== "string")
|
|
87
|
-
return { kind: "nameless" };
|
|
88
|
-
return { kind: "named", name: at.name, ...(typeof at.id === "string" ? { id: at.id } : {}), ...(typeof at.provider === "string" ? { provider: at.provider } : {}), enabled: at.enabled !== false };
|
|
89
|
-
}
|
|
90
71
|
function isModelEntryDrop(w) {
|
|
91
72
|
return w.kind === "entry-dropped" && w.domain === "models" && w.path.length === 2 && w.path[0] === "models" && typeof w.path[1] === "number";
|
|
92
73
|
}
|
|
93
|
-
function
|
|
94
|
-
const rawModels = isRecord(source.raw) ? source.raw.models : undefined;
|
|
95
|
-
if (!Array.isArray(rawModels) || source.parsed === undefined)
|
|
96
|
-
return false;
|
|
97
|
-
if ([...dropped].some((i) => i >= rawModels.length))
|
|
98
|
-
return false;
|
|
99
|
-
const survivors = rawModels.filter((_, i) => !dropped.has(i));
|
|
100
|
-
return survivors.length === source.parsed.length && survivors.every((s, k) => isRecord(s) && isRecord(source.parsed[k]) && s.name === source.parsed[k].name);
|
|
101
|
-
}
|
|
102
|
-
export function splitFoldedReadWarnings(warnings, models) {
|
|
74
|
+
export function splitFoldedReadWarnings(warnings) {
|
|
103
75
|
const domainErrors = [];
|
|
104
76
|
const droppedEntries = [];
|
|
105
77
|
for (const w of warnings) {
|
|
@@ -119,28 +91,14 @@ export function splitFoldedReadWarnings(warnings, models) {
|
|
|
119
91
|
}
|
|
120
92
|
}
|
|
121
93
|
}
|
|
122
|
-
const modelDrops = warnings.filter(isModelEntryDrop);
|
|
123
|
-
const readable = sameRead(models, new Set(modelDrops.map((w) => w.path[1])));
|
|
124
94
|
const droppedIdentities = [];
|
|
125
|
-
for (const w of
|
|
126
|
-
|
|
127
|
-
const cell = readable ? droppedEntryIdentity(models.raw, w.path) : undefined;
|
|
128
|
-
if (cell === undefined) {
|
|
129
|
-
droppedIdentities.push(unproven);
|
|
95
|
+
for (const w of warnings) {
|
|
96
|
+
if (!isModelEntryDrop(w))
|
|
130
97
|
continue;
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
case "named":
|
|
136
|
-
if (cell.enabled)
|
|
137
|
-
droppedIdentities.push({ ...unproven, name: cell.name, ...(cell.id !== undefined ? { id: cell.id } : {}), ...(cell.provider !== undefined ? { provider: cell.provider } : {}) });
|
|
138
|
-
break;
|
|
139
|
-
default: {
|
|
140
|
-
const never = cell;
|
|
141
|
-
throw new Error(`unhandled dropped-entry cell: ${JSON.stringify(never)}`);
|
|
142
|
-
}
|
|
143
|
-
}
|
|
98
|
+
const identity = w.identity;
|
|
99
|
+
if (identity?.name === undefined || identity.enabled === false)
|
|
100
|
+
continue;
|
|
101
|
+
droppedIdentities.push({ entry: formatConfigPath(w.path), name: identity.name, ...(identity.id !== undefined ? { id: identity.id } : {}), fields: describeSchemaIssueFields(w.error), issues: describeSchemaIssues(w.error) ?? "(schema issues unreadable)" });
|
|
144
102
|
}
|
|
145
103
|
return { domainErrors, droppedEntries, droppedIdentities };
|
|
146
104
|
}
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import type { RestartSignal } from "@sema-agent/settings-schema/fleet";
|
|
1
2
|
import type { EffectiveConfig } from "./types.js";
|
|
2
3
|
export type RestartSlice = "skills" | "mcp" | "a2a" | "scenarios" | "degrade-route" | "read-face" | "memory-consolidation";
|
|
3
4
|
/** What the CALLER must contribute about boot-baked catalog consumers that live OUTSIDE the Runner — the
|
|
@@ -31,28 +32,14 @@ export interface RestartSliceCtx {
|
|
|
31
32
|
* = 装载器的出厂名单 `DEFAULT_PLUGIN_ALLOW_HOSTS`。 */
|
|
32
33
|
pluginsAllowHosts?: readonly string[];
|
|
33
34
|
}
|
|
34
|
-
/**
|
|
35
|
-
*
|
|
36
|
-
*
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
35
|
+
/** S-741(车OG):本引擎发布的重启信号。形的唯一定义是 settings-schema 6.1.0 `RestartSignal`(`{ reasons, version, since }`,在场即信号,
|
|
36
|
+
* 不夹 `restartRequired` 布尔;本地手写的那份接口已删),这里只把 `reasons` 收窄到本文件的闭集 {@link RestartSlice} —— 写口
|
|
37
|
+
* (`boot/config-center.ts` `publishRestart`)按它收,拼错 / 已退役的词编译红(opus 对抗腿 #2:直接用上游型时 `reasons` 是
|
|
38
|
+
* `string[]`,闭集的编译期判据丢了)。`/health.restart` 与 fleet 心跳 `restart` 两个出口交 config-center 同一只 `restartState()`
|
|
39
|
+
* 的同一个对象,按契约形出门。 */
|
|
40
|
+
export type EngineRestartSignal = RestartSignal & {
|
|
40
41
|
reasons: RestartSlice[];
|
|
41
|
-
|
|
42
|
-
version: number;
|
|
43
|
-
/** Epoch ms when this restart first became required (stable across refreshes while `reasons` is unchanged). */
|
|
44
|
-
since: number;
|
|
45
|
-
}
|
|
46
|
-
/** Canonical, key-sorted JSON (array order preserved) so two semantically-equal effective slices fingerprint
|
|
47
|
-
* identically regardless of object key order from the center serializer.
|
|
48
|
-
*
|
|
49
|
-
* ⚠️ [ref](2026-08-31)如实登记:这**不是** `src/canonical-json.ts#canonicalJson`(core 逐字港,全仓单源)——
|
|
50
|
-
* 两者在 JSON 形输入上逐字节相同(golden 交叉读数已证),但本函数把 `undefined` 值键**保留为 `"k":null`**,
|
|
51
|
-
* 共享件按 `JSON.stringify` 语义**省略**。restart 指纹的输入是进程内构造的切片对象(zod 解析产物 + rest 展开/
|
|
52
|
-
* 挑字段),`{k: undefined}` 形原则上可达,而它与 `{}` 是否同指纹 = 本轴上的**行为**(多签/少签一次 fleet 滚动
|
|
53
|
-
* 重启),不是序列化器的私事 ⇒ 语义不恒等不合并;字节钉 test/canonical-json-golden.test.ts,K→O 统一另立件
|
|
54
|
-
* 走三问。导出只为 mcp-revocation 同源改调(两处此前各持一份逐字相同的复刻)与 golden 直钉,不是公共 API。 */
|
|
55
|
-
export declare function stableStringify(v: unknown): string;
|
|
42
|
+
};
|
|
56
43
|
/** Restart-to-apply slices that DIFFER between this process's BOOT config and a freshly-pulled one. Empty =
|
|
57
44
|
* nothing baked-at-boot changed (only hot-apply slices moved, or nothing) → no restart needed. An orchestrator
|
|
58
45
|
* can auto rolling-restart on a non-empty result WITHOUT a restart loop, because the comparison is always
|
|
@@ -1,19 +1,9 @@
|
|
|
1
1
|
import { resolveActiveTiers } from "@sema-agent/settings-schema";
|
|
2
|
+
import { stableStringify } from "@sema-agent/settings-schema/hash";
|
|
2
3
|
import { DEFAULT_PLUGIN_ALLOW_HOSTS, materializablePlugins } from "../capabilities/center-plugins.js";
|
|
3
4
|
import { resolveDefaultModelName } from "./apply-effective.js";
|
|
4
5
|
import { CENTER_READ_FACE_LIVE_KEYS } from "./read-face.js";
|
|
5
6
|
const RESTART_SLICES = ["skills", "mcp", "a2a", "scenarios", "degrade-route", "read-face", "memory-consolidation"];
|
|
6
|
-
export function stableStringify(v) {
|
|
7
|
-
if (v === null || typeof v !== "object")
|
|
8
|
-
return JSON.stringify(v) ?? "null";
|
|
9
|
-
if (Array.isArray(v))
|
|
10
|
-
return `[${v.map(stableStringify).join(",")}]`;
|
|
11
|
-
const obj = v;
|
|
12
|
-
return `{${Object.keys(obj)
|
|
13
|
-
.sort()
|
|
14
|
-
.map((k) => `${JSON.stringify(k)}:${stableStringify(obj[k])}`)
|
|
15
|
-
.join(",")}}`;
|
|
16
|
-
}
|
|
17
7
|
function enabledOnly(rows) {
|
|
18
8
|
if (!rows)
|
|
19
9
|
return null;
|
|
@@ -73,7 +63,7 @@ export function restartReasons(boot, current, ctx) {
|
|
|
73
63
|
if (stableStringify(b) === stableStringify(c))
|
|
74
64
|
return false;
|
|
75
65
|
if (s === "mcp" && ctx?.mcpRevocationLive === true && Array.isArray(b) && Array.isArray(c) && c.length < b.length) {
|
|
76
|
-
const bootFps = b.map(stableStringify);
|
|
66
|
+
const bootFps = b.map((row) => stableStringify(row));
|
|
77
67
|
let bi = 0;
|
|
78
68
|
const pureRemoval = c.every((row) => {
|
|
79
69
|
const fp = stableStringify(row);
|
|
@@ -265,9 +265,11 @@ export interface EffectiveConfig {
|
|
|
265
265
|
tierGroups?: unknown;
|
|
266
266
|
activeTierGroup?: string;
|
|
267
267
|
/** 🔴 S-712 —— **本仓读腿铸的**载体(不是 wire 键:中心在模型域里写同名键会被 schema 当未知键剥掉并进候选门):这次读
|
|
268
|
-
* 被 schema
|
|
268
|
+
* 被 schema 逐条丢掉的**启用**、**有名**目录条目的身份(取 settings-schema 6.1.0 告警自带的 `identity`,见
|
|
269
|
+
* `read-warnings.ts` `splitFoldedReadWarnings`)。模型面 stage 把它们铸成 `config.model_entry_invalid` 墓碑进 `modelRefusals`,
|
|
269
270
|
* 不变量 `refused-names-not-minted` 据此判增广有没有把它们的名字铸到别的条目上。挂在 effective 上(而不是读结果旁边)是为了
|
|
270
|
-
* **随 effective 落 LKG**:重启从 LKG 起服时墓碑还在。形见 {@link DroppedModelIdentity}(纯身份,不含值)
|
|
271
|
+
* **随 effective 落 LKG**:重启从 LKG 起服时墓碑还在。形见 {@link DroppedModelIdentity}(纯身份,不含值);盘上形不对
|
|
272
|
+
* (不是列表 / 行缺字符串 `name`)⇒ 模型面整组拒。 */
|
|
271
273
|
droppedIdentities?: DroppedModelIdentity[];
|
|
272
274
|
};
|
|
273
275
|
/** registry-core 0.7 collab domain (teams' successor) — consumed by the slice-1.5 projection
|
package/dist/config-provider.js
CHANGED
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
import { readMcpSourceLabel } from "./mcp-source-label.js";
|
|
2
2
|
import { createHmac, randomBytes } from "node:crypto";
|
|
3
3
|
import { splitFoldedReadWarnings, withDroppedIdentities } from "./config-center/read-warnings.js";
|
|
4
|
-
import { readFile, stat } from "node:fs/promises";
|
|
5
4
|
import { FileConfigStore } from "@sema-agent/settings-schema/node";
|
|
6
5
|
import { findSkillContent, skillContentHash, refIntegrityIssues, createSiblingResolver, formatConfigPath, REF_CHECKED_DOMAINS, } from "@sema-agent/settings-schema";
|
|
7
6
|
import { redactSecrets } from "./trace/redact.js";
|
|
@@ -12,24 +11,6 @@ const OPERAND_FINGERPRINT_KEY = randomBytes(16);
|
|
|
12
11
|
function redactConfigError(text) {
|
|
13
12
|
return redactSecrets(text).replace(/"[^"]*"|'[^']*'/g, (m) => `"#${createHmac("sha256", OPERAND_FINGERPRINT_KEY).update(m.slice(1, -1)).digest("hex").slice(0, 8)}"`);
|
|
14
13
|
}
|
|
15
|
-
async function readRawDomainDoc(path) {
|
|
16
|
-
try {
|
|
17
|
-
const doc = JSON.parse(await readFile(path, "utf8"));
|
|
18
|
-
return { ok: true, doc };
|
|
19
|
-
}
|
|
20
|
-
catch (e) {
|
|
21
|
-
return { ok: false, why: e instanceof Error ? e.message : String(e) };
|
|
22
|
-
}
|
|
23
|
-
}
|
|
24
|
-
async function statSignature(path) {
|
|
25
|
-
try {
|
|
26
|
-
const st = await stat(path, { bigint: true });
|
|
27
|
-
return { ok: true, sig: `${st.ino}:${st.size}:${st.mtimeNs}:${st.ctimeNs}` };
|
|
28
|
-
}
|
|
29
|
-
catch (e) {
|
|
30
|
-
return { ok: false, why: e instanceof Error ? e.message : String(e) };
|
|
31
|
-
}
|
|
32
|
-
}
|
|
33
14
|
export const BOOT_FETCH_DEFERRED = Symbol("boot-fetch-deferred");
|
|
34
15
|
export function raceBootFetch(fetch, budgetMs) {
|
|
35
16
|
if (budgetMs <= 0)
|
|
@@ -101,9 +82,7 @@ export class LocalConfigProvider {
|
|
|
101
82
|
if (issues.length > 0)
|
|
102
83
|
refErrors.push({ domain: d, error: issues.map((i) => `${formatConfigPath(i.path)}: ${redactConfigError(i.message)}`).join("; ") });
|
|
103
84
|
}
|
|
104
|
-
const
|
|
105
|
-
const rawModels = modelDropped ? await this.#provenRawModelsDoc(store, eff.version) : undefined;
|
|
106
|
-
const folded = splitFoldedReadWarnings(readWarnings, { raw: rawModels, parsed: eff.models.models ?? [] });
|
|
85
|
+
const folded = splitFoldedReadWarnings(readWarnings);
|
|
107
86
|
const parseErrors = folded.domainErrors.map((w) => ({ domain: w.domain, error: redactConfigError(w.error) }));
|
|
108
87
|
const droppedEntries = folded.droppedEntries.map((d) => ({ ...d, error: redactConfigError(d.error) }));
|
|
109
88
|
const errors = [...domainErrors.map((de) => ({ domain: de.domain, error: redactConfigError(de.error) })), ...parseErrors, ...refErrors];
|
|
@@ -115,32 +94,6 @@ export class LocalConfigProvider {
|
|
|
115
94
|
...(droppedEntries.length > 0 ? { droppedEntries } : {}),
|
|
116
95
|
};
|
|
117
96
|
}
|
|
118
|
-
async #provenRawModelsDoc(store, version) {
|
|
119
|
-
const path = store.domainPath("models");
|
|
120
|
-
const unproven = (why) => {
|
|
121
|
-
logger.warn("config_models_raw_unproven", { dir: this.root, why: redactConfigError(why), note: "the dropped model entries of this read cannot be identified from the exact snapshot that was parsed — the candidate's model plane is refused as a group (no tombstone is taken from another version of the file); the next poll re-reads" });
|
|
122
|
-
return undefined;
|
|
123
|
-
};
|
|
124
|
-
const s1 = await statSignature(path);
|
|
125
|
-
const raw = await readRawDomainDoc(path);
|
|
126
|
-
let again;
|
|
127
|
-
try {
|
|
128
|
-
again = (await (this.#injectedStore ?? new FileConfigStore(this.root)).getEffective({ tolerant: true })).effective.version;
|
|
129
|
-
}
|
|
130
|
-
catch (e) {
|
|
131
|
-
again = `re-parse failed: ${e instanceof Error ? e.message : String(e)}`;
|
|
132
|
-
}
|
|
133
|
-
const s2 = await statSignature(path);
|
|
134
|
-
if (!raw.ok)
|
|
135
|
-
return unproven(`config.d/models.json unreadable on the identity re-read: ${raw.why}`);
|
|
136
|
-
if (typeof again !== "number")
|
|
137
|
-
return unproven(again);
|
|
138
|
-
if (!s1.ok || !s2.ok || s1.sig !== s2.sig)
|
|
139
|
-
return unproven("config.d/models.json changed (or could not be stat-ed) while its identity re-read was being proven");
|
|
140
|
-
if (again !== version)
|
|
141
|
-
return unproven("config.d changed between the parse and the identity re-read (content version differs)");
|
|
142
|
-
return raw.doc;
|
|
143
|
-
}
|
|
144
97
|
async fetchSkillContent(contentHash) {
|
|
145
98
|
const skills = await this.store.getDomain("skills");
|
|
146
99
|
if (!skills)
|
package/dist/config-types.d.ts
CHANGED
|
@@ -18,6 +18,7 @@ import type { InfraCostRates } from "./observability/cost-taxonomy.js";
|
|
|
18
18
|
import type { Autonomy, CommandRule } from "./runtime-governance.js";
|
|
19
19
|
import type { CenterCredentialSource } from "./center-credential.js";
|
|
20
20
|
import type { GitApiKind } from "./git-api-kind.js";
|
|
21
|
+
import { DRAIN_GRACE_FLOOR_MS } from "@sema-agent/settings-schema/fleet";
|
|
21
22
|
/** [ref]:记忆向量面的 embedder 坐标(env `MEMORY_EMBEDDER_*` 解析结果;单一属主 = 本类型,
|
|
22
23
|
* `plugins/memory-embedder.ts` 的工厂直接消费它,不复制形状)。语义与三问见
|
|
23
24
|
* {@link ServiceConfigFlat.memoryEmbedder}。纯数据 —— 端点原文照存(拼 `/embeddings` 是 transport
|
|
@@ -62,6 +63,71 @@ export interface RetentionSweepConfig {
|
|
|
62
63
|
* 那道拒之后它即刻生效 —— 留着是为了「那天不必有人再猜一个数」。
|
|
63
64
|
*/
|
|
64
65
|
export declare const MEMORY_CONSOLIDATION_MIN_INTERVAL_SEC = 300;
|
|
66
|
+
/**
|
|
67
|
+
* Node 的 `setTimeout` delay 上界(2^31−1 ms ≈ 24.8 天)。**超过它的 delay 被静默折成 1ms** —— 任何「一个时长
|
|
68
|
+
* 旋钮的值最终会变成一次 `setTimeout` 的 delay」的键都必须在上界处拒启,不能夹取([ref] + codex r1-[high]:夹取
|
|
69
|
+
* 只是把一句谎换成另一句)。S-757 起从 `config.ts` 的模块私有常量提到本纯叶:解析器与配置目录的合法域句读
|
|
70
|
+
* 同一个数([ref]:边界不手抄)。
|
|
71
|
+
*/
|
|
72
|
+
export declare const MAX_TIMER_DELAY_MS = 2147483647;
|
|
73
|
+
/**
|
|
74
|
+
* S-757(test [ref] / core #1125)—— `AUTO_MODE_CLASSIFIER_TIMEOUT_MS`(→ core `RunnerDeps.autoMode.timeoutMs`)
|
|
75
|
+
* 的**合法域**,闭区间、整数 ms。解析器(`config.ts`)与目录行(`config-catalog.ts`)共读这一处。
|
|
76
|
+
*
|
|
77
|
+
* · **下界 1000**:亚秒值是「秒写成毫秒」的手滑形(`180` 想说三分钟,落地 180 ms ⇒ 每一级立即判「没裁」⇒ 门控
|
|
78
|
+
* 调用整条拒)。`0` 同拒:core 对这一座**没有**「关」的语义(d.ts 未写 0 的含义;分类器不判就走下一级 / 拒),
|
|
79
|
+
* 写 0 表达不出任何可执行的意图。
|
|
80
|
+
* · **上界 = {@link MAX_TIMER_DELAY_MS}**:core 拿这个数直接武装 `setTimeout`(7.32.1 `dist/core/auto-mode.js`
|
|
81
|
+
* `withDeadline`,座上不做值校验),更大的值被 Node 折成 1 ms —— 「抬上限」变成「立即超时」,方向正好反了。
|
|
82
|
+
* **不另设更紧的政策上界**:这是**一级**梯子的等待上限(不是整次分类;`classifierFallback=main` 时最坏 =
|
|
83
|
+
* 两级 × 上限 + 一次装配),而本级调用同时收到 run 的中止信号(`withDeadline` 把外层 signal 接进本级),
|
|
84
|
+
* run 被取消 / 硬 abort 时不靠这个数收尾。
|
|
85
|
+
*/
|
|
86
|
+
export declare const AUTO_MODE_CLASSIFIER_TIMEOUT_BOUNDS: {
|
|
87
|
+
readonly minMs: 1000;
|
|
88
|
+
readonly maxMs: 2147483647;
|
|
89
|
+
};
|
|
90
|
+
/**
|
|
91
|
+
* `DRAIN_GRACE_MS` 的**地板**(10s)—— 夹取(env,[ref] B 档)与中心 stage 校验(`stage-limits.ts` 的 `atLeast`)两条写入腿
|
|
92
|
+
* 都以它为下界,于是「排空窗」在任何写入通道上都不会短于它。**属主在 settings-schema**(`@sema-agent/settings-schema/fleet` 的
|
|
93
|
+
* `DRAIN_GRACE_FLOOR_MS`,drain 协议合同值;S-778 起本叶只转出口,不再本地铸同一个数)。S-752 起它从 `config.ts` 的
|
|
94
|
+
* `LIMITS_HTTP_FLOORS` 字面提到本叶:{@link DRAIN_SECOND_SIGNAL_MIN_GAP_BOUNDS} 的上界与配置目录的合法域句共读这一个数([ref]:
|
|
95
|
+
* 边界不手抄;目录模块不能值引 `config.ts` —— `config → server-secret-env → config-catalog` 已成链)。
|
|
96
|
+
*/
|
|
97
|
+
export { DRAIN_GRACE_FLOOR_MS };
|
|
98
|
+
/**
|
|
99
|
+
* S-752(test [ref])—— `DRAIN_SECOND_SIGNAL_MIN_GAP_MS` 的**合法域**(闭区间、整数 ms)与缺省。解析器(`config.ts`)
|
|
100
|
+
* 与目录行(`config-catalog.ts`)共读这一处。
|
|
101
|
+
*
|
|
102
|
+
* · **语义**:排空期里一发停止信号(SIGTERM / SIGINT)只有在与**排空起点**相隔 ≥ 本值时才算硬停意图;更近的是同一拍的
|
|
103
|
+
* 重复投递(壳心跳误判 / 编排器重试),记一行就忽略。
|
|
104
|
+
* · **缺省 250**:人手连按两次的最短间隔量级 —— 同一毫秒的重复投递(事故形)远在其内,有意的第二发远在其外。
|
|
105
|
+
* · **下界 0 = 关**:恒按「第二发 = 硬停」,即 7.101.x 行为逐字。0 在这里有真含义(不同于 `AUTO_MODE_CLASSIFIER_TIMEOUT_MS`
|
|
106
|
+
* 的 0),所以放行。
|
|
107
|
+
* · **上界 = {@link DRAIN_GRACE_FLOOR_MS}**:本闸不能比**最短**可能的排空窗还宽 —— 更宽的值会让「排空期的第二发」在整个窗
|
|
108
|
+
* 里都不算数(信号面的硬梯子被静默摘掉,只剩 SIGKILL)。取地板而不是「≤ 本进程的 `DRAIN_GRACE_MS`」:后者是中心热改键,
|
|
109
|
+
* boot 期跨键校验会被一次热改在运行期推翻;地板则在所有写入腿上都成立,无需第二处校验。
|
|
110
|
+
*/
|
|
111
|
+
export declare const DRAIN_SECOND_SIGNAL_MIN_GAP_BOUNDS: {
|
|
112
|
+
readonly minMs: 0;
|
|
113
|
+
readonly maxMs: 10000;
|
|
114
|
+
};
|
|
115
|
+
export declare const DRAIN_SECOND_SIGNAL_MIN_GAP_DEFAULT_MS = 250;
|
|
116
|
+
/**
|
|
117
|
+
* S-753(core 7.33.0 #1121)—— `MODEL_FIRST_BYTE_TIMEOUT_MS`(→ core `BrainTimeoutConfig.firstByteTimeoutMs`,`fetch()` → 响应头
|
|
118
|
+
* 的等待)的**合法域**,闭区间、整数 ms。解析器(`config.ts`)与目录行(`config-catalog.ts`)共读这一处。
|
|
119
|
+
*
|
|
120
|
+
* · **下界 0**:`0` 在这一座有真含义 —— 该段**不设引擎钟,传输层的响应头 / 响应体钟也关**(TCP + TLS 建连仍归传输层自己的钟;core 7.33.0 起每请求带引擎 dispatcher,把传输层的
|
|
121
|
+
* headers / body 钟置 0;在此之前 `0` 只关引擎的钟,传输层 300 s 仍在)。亚秒正值不拒:它是合法的「很快就切」表态
|
|
122
|
+
* (failover 拓扑真会这么配;窗上还有 core 按 body 大小加的上传宽限)。
|
|
123
|
+
* · **上界 = {@link MAX_TIMER_DELAY_MS}**:core 对超过它的值构造期拒(`config.stall_timeout_invalid`);本仓在 boot 就拒,
|
|
124
|
+
* 拒句点名 env 键,而不是等到 brain 装配时报一个运维没写过的 config 键名。
|
|
125
|
+
* · **没有缺省常量**:缺席 = 不铸键,core 自缺省当家([ref];本仓不复制上游数字 —— S-753 的病根正是本仓自带的 30 000)。
|
|
126
|
+
*/
|
|
127
|
+
export declare const FIRST_BYTE_TIMEOUT_BOUNDS: {
|
|
128
|
+
readonly minMs: 0;
|
|
129
|
+
readonly maxMs: 2147483647;
|
|
130
|
+
};
|
|
65
131
|
/**
|
|
66
132
|
* S-220 —— **`resilience` 块缺省的唯一属主**(S-202 的单源从一根扩到整族)。
|
|
67
133
|
*
|
|
@@ -81,7 +147,9 @@ export declare const MEMORY_CONSOLIDATION_MIN_INTERVAL_SEC = 300;
|
|
|
81
147
|
* `satisfies Readonly<Record<keyof ServiceConfigFlat["resilience"], …>>` 双向咬:块上加字段而表里没行
|
|
82
148
|
* ⇒ tsc 红(加字段的人被迫当场回答「它的出厂缺省是多少、归哪个 env」);表里多一行块上没有的键同样红。
|
|
83
149
|
*
|
|
84
|
-
* ⚠️
|
|
150
|
+
* ⚠️ 语义面**一个数字都没动**:「熔断默认关」照旧,env 恒赢。看门狗的 `0` 自 core 7.33.0 起 = 该段**不设引擎钟,传输层的响应头 / 响应体钟
|
|
151
|
+
* 也关**(TCP + TLS 建连仍归传输层自己的钟;此前 = 只关引擎的钟)。S-753 起本表**不再有首字节那一行**:`MODEL_CONNECT_TIMEOUT_MS`(自带 30 000)退役,替代键
|
|
152
|
+
* `MODEL_FIRST_BYTE_TIMEOUT_MS` 缺席 = 不铸键(见 {@link ServerDefaultedResilienceKey})。
|
|
85
153
|
* 首 token 那一行的 600_000 由来(S-202 clay 裁 A):自托管后端在长 prefill(大上下文 / 冷缓存 /
|
|
86
154
|
* 排队)下首字合理地要等几分钟,120s 会把一次**正常**的慢启动判成 `[network]` 失败去重试,而重试
|
|
87
155
|
* 再交同一份 prompt 给同一个排队中的后端只会更慢更贵;依赖旧姿态的部署显式写回
|
|
@@ -92,10 +160,6 @@ export declare const MEMORY_CONSOLIDATION_MIN_INTERVAL_SEC = 300;
|
|
|
92
160
|
* 判「字面量」这一类写法为红(下一份镜像出生即红)。
|
|
93
161
|
*/
|
|
94
162
|
export declare const RESILIENCE_DEFAULTS: {
|
|
95
|
-
readonly connectTimeoutMs: {
|
|
96
|
-
readonly env: "MODEL_CONNECT_TIMEOUT_MS";
|
|
97
|
-
readonly value: 30000;
|
|
98
|
-
};
|
|
99
163
|
readonly firstTokenTimeoutMs: {
|
|
100
164
|
readonly env: "MODEL_FIRST_TOKEN_TIMEOUT_MS";
|
|
101
165
|
readonly value: 600000;
|
|
@@ -417,16 +481,20 @@ export interface ServiceConfigFlat {
|
|
|
417
481
|
* immediately instead of waiting out timeouts). BreakerState is core's in-process Map for now —
|
|
418
482
|
* cross-replica shared state (TiDB adapter) is a documented follow-up. */
|
|
419
483
|
resilience: {
|
|
420
|
-
/**
|
|
421
|
-
*
|
|
422
|
-
|
|
484
|
+
/** S-753(core 7.33.0 #1121)—— `fetch()` → 响应头的等待上限(ms;env `MODEL_FIRST_BYTE_TIMEOUT_MS`,合法域
|
|
485
|
+
* {@link FIRST_BYTE_TIMEOUT_BOUNDS})。**缺席 = 不铸键** ⇒ core 自缺省当家(另加 core 按 body 大小给的上传宽限,
|
|
486
|
+
* 到点后 core 自有一次加宽重试,不吃 `maxRetries`)—— 本仓不复制上游数字([ref])。`0` = 该段不设引擎钟,传输层的响应头 / 响应体钟
|
|
487
|
+
* 也关(TCP + TLS 建连仍归传输层自己的钟)。取代退役的 `connectTimeoutMs`(名义建连、实际就是计到响应头;本仓自带的 30 000 缺省正是 S-753 的病根)。 */
|
|
488
|
+
firstByteTimeoutMs?: number;
|
|
423
489
|
/** Cancel an open SSE stream if no content/thinking delta arrives within this many ms.
|
|
424
|
-
* Default
|
|
425
|
-
* so this is the only guard for a stream that opens and never emits). 0 =
|
|
490
|
+
* Default 600s (S-202; core's idle timer only arms after the first token,
|
|
491
|
+
* so this is the only guard for a stream that opens and never emits). 0 = no engine clock on this
|
|
492
|
+
* segment and the transport's body clock off too (core ≥7.33.0). */
|
|
426
493
|
firstTokenTimeoutMs: number;
|
|
427
494
|
/** Mid-stream stall watchdog (1.40.1): after output starts, re-armed on every delta; silence for
|
|
428
|
-
* this many ms → cancel + retryable
|
|
429
|
-
* (firstTokenTimeoutMs only covers the first). 0 =
|
|
495
|
+
* this many ms → cancel + retryable. Catches a gateway that hangs AFTER the first token
|
|
496
|
+
* (firstTokenTimeoutMs only covers the first). 0 = no engine clock on this segment and the transport's
|
|
497
|
+
* body clock off too (core ≥7.33.0). */
|
|
430
498
|
idleTimeoutMs: number;
|
|
431
499
|
/** Wrap the primary gateway route(s) in a circuit breaker (in-process state, per model+provider). */
|
|
432
500
|
circuitBreaker: boolean;
|
|
@@ -1556,6 +1624,15 @@ export interface ServiceConfigFlat {
|
|
|
1556
1624
|
* 真源。所以缺席时这台机器跟随 core 的 `main`,运维面由 DEPLOY-PREREQS 的升级段点名(「默认变了」
|
|
1557
1625
|
* 是升级须知,不是一个可以静默过去的实现细节)。 */
|
|
1558
1626
|
autoModeClassifierFallback?: "main" | "none";
|
|
1627
|
+
/** 7.102.0 S-757(test [ref] / core #1125)—— **分类器单级判决上限**(`AUTO_MODE_CLASSIFIER_TIMEOUT_MS`,
|
|
1628
|
+
* 部署级数值旋钮,整数 ms,合法域 {@link AUTO_MODE_CLASSIFIER_TIMEOUT_BOUNDS})→ core `RunnerDeps.autoMode.timeoutMs`。
|
|
1629
|
+
*
|
|
1630
|
+
* 为什么要一根旋钮:这一级到点没裁 = 梯子往下走,走完仍没裁 ⇒ 这次门控调用**整条拒**、不出卡。自建慢网关
|
|
1631
|
+
* (排队 + prefill 以分钟计)在 core 缺省上限内答不出 ⇒ auto 模式在这类部署上等于「工具一律拒」。
|
|
1632
|
+
*
|
|
1633
|
+
* 🔴 **缺席 = 不铸键**(与 {@link autoModeClassifierFallback} 同律):本仓不复制上游缺省数字,core 改缺省之日
|
|
1634
|
+
* 本仓不会成为第二份真源。旋钮**无条件**:不挂任何客户端 / 请求面派生腿。坏值(非十进制整数 / 越界)boot 拒启。 */
|
|
1635
|
+
autoModeClassifierTimeoutMs?: number;
|
|
1559
1636
|
/** S-111([ref] §4.4)—— CC `settings.crossSessionInbound` 的**部署/组织层**(引擎层形的 `managed` 层,
|
|
1560
1637
|
* CC `policySettings` 对位):本部署对入站跨会话消息的治理三态。`accept` 投递 / `hold` 停在收件会话的待审
|
|
1561
1638
|
* 队列 / `refuse` 整体退出车道。**未设 = 本层不设**(不是 accept):引擎读作「managed 层未表态」,由其余层
|
|
@@ -1607,8 +1684,14 @@ export interface ServiceConfigFlat {
|
|
|
1607
1684
|
* instance) to finish before the hard shutdown. During drain, new billable submissions get 503+Retry-After and
|
|
1608
1685
|
* /health carries `draining:true`. Default 10min (an interactive turn is minutes, not seconds — a
|
|
1609
1686
|
* 10s hard-cut would kill long turns); floor 10s. Env: DRAIN_GRACE_MS. SIGINT (Ctrl-C dev
|
|
1610
|
-
* ergonomics)
|
|
1687
|
+
* ergonomics) hard-stops immediately; DURING a drain, a second SIGTERM / SIGINT hard-stops only when it arrives
|
|
1688
|
+
* at least {@link drainSecondSignalMinGapMs} after the drain began (S-752). */
|
|
1611
1689
|
drainGraceMs: number;
|
|
1690
|
+
/** S-752(test [ref]):排空期「第二发信号 = 硬停」的最小间隔(ms,锚 = 排空起点)。不足 ⇒ 同一拍的重复投递,一行
|
|
1691
|
+
* info `drain_duplicate_signal_ignored` 后忽略、排空继续;`0` = 关(7.101.x 行为)。合法域
|
|
1692
|
+
* {@link DRAIN_SECOND_SIGNAL_MIN_GAP_BOUNDS},缺省 {@link DRAIN_SECOND_SIGNAL_MIN_GAP_DEFAULT_MS};坏值拒启([ref] A 档)。
|
|
1693
|
+
* 部署级旋钮,**无条件**解析。 Env: `DRAIN_SECOND_SIGNAL_MIN_GAP_MS`。 */
|
|
1694
|
+
drainSecondSignalMinGapMs: number;
|
|
1612
1695
|
/** B3 (shell [ref]②): SIGHUP idle window. SIGHUP means "the process-group owner's terminal went away" — but
|
|
1613
1696
|
* under the shared-engine shape (several shell sessions on one config, engine rides the FIRST shell's process
|
|
1614
1697
|
* group per the T11 anti-orphan call) other live sessions may still be using this engine, so an unconditional
|
|
@@ -1955,13 +2038,20 @@ export interface ServiceConfigFlat {
|
|
|
1955
2038
|
* that worker's roster via `/effective?worker=<name>`. Unset → the global roster (backward compatible).
|
|
1956
2039
|
* `credential`(S-651,BREAKING:取代 7.98.0 及以前的 `token: string` 启动快照):凭证来源活对象
|
|
1957
2040
|
* (`center-credential.ts`),每条出站请求经它取一次值 —— `SEMA_REGISTRY_TOKEN`(值)或
|
|
1958
|
-
* `SEMA_REGISTRY_TOKEN_FILE`(每次读文件,轮换 / 换账号不重启)
|
|
2041
|
+
* `SEMA_REGISTRY_TOKEN_FILE`(每次读文件,轮换 / 换账号不重启)二选一(两键名与判官的属主 = settings-schema
|
|
2042
|
+
* `CENTER_CREDENTIAL_ENV` / `judgeCenterCredentialDeclaration`,S-742)。 */
|
|
1959
2043
|
configCenter?: {
|
|
1960
2044
|
baseUrl: string;
|
|
1961
2045
|
credential: CenterCredentialSource;
|
|
1962
2046
|
dryRun: boolean;
|
|
1963
2047
|
worker?: string;
|
|
1964
2048
|
};
|
|
2049
|
+
/** S-698(design-325 §2):中心凭证**续签提前量**(ms)—— `SEMA_REGISTRY_TOKEN_FILE` 指向人的登录会话信封
|
|
2050
|
+
* (`kind:"user-session"`)时,访问令牌离到期 ≤ 本值即由引擎续签并原子改写同一文件(`center-credential-renewer.ts`)。
|
|
2051
|
+
* 缺省 5 min;`0` = 关续签、只读信封(壳负责续);其余必须是整数毫秒且落在 `CENTER_CREDENTIAL_RENEW_LEAD_MIN_MS`(一次续签请求的
|
|
2052
|
+
* 传输上界)与 `CENTER_CREDENTIAL_RENEW_LEAD_MAX_MS`(访问令牌寿命之半)之间,坏值拒启(解析口 `parseCenterCredentialRenewLeadMs`)。
|
|
2053
|
+
* 裸行文件 / `SEMA_REGISTRY_TOKEN` 值形不受它影响(不续签)。`CENTER_CREDENTIAL_RENEW_LEAD_MS`。 */
|
|
2054
|
+
centerCredentialRenewLeadMs: number;
|
|
1965
2055
|
/** Dual-mode config SOURCE (DUAL-MODE-DESIGN §4): "remote" (sema-registry HTTP) | "local" (.env + config.d
|
|
1966
2056
|
* via @sema-agent/settings-schema FileConfigStore) | unset → remote if SEMA_REGISTRY_URL (legacy CONFIG_CENTER_URL now boot-rejects, no dual-read) is set, else pure-env.
|
|
1967
2057
|
* Env: CONFIG_PROVIDER. The same resolver/EffectiveConfig either way (can't drift). */
|
|
@@ -1975,7 +2065,7 @@ export type ServiceStoreConfig = Pick<ServiceConfigFlat, "sessionBackend" | "ses
|
|
|
1975
2065
|
export type ServiceModelPlaneConfig = Pick<ServiceConfigFlat, "gatewayBaseUrl" | "gatewayApiKey" | "gatewayFallbackUrls" | "gatewayMaxRetries" | "anthropic" | "resilience" | "model" | "envModel" | "models" | "modelApiKeyEnv" | "modelApiKeys" | "modelRefusals" | "modelQuotaWeights" | "tiers" | "projects" | "roles" | "atModelAllowlist" | "cascadeLadder" | "degrade">;
|
|
1976
2066
|
/** 组:approval(审批 / HITL 门)。`directDoorActive` 无 env 解析腿(装配层三域合取的产物),但语义上
|
|
1977
2067
|
* 就是本组的门状态,故进组;`parseApprovalDomain` 的返回类型相应是 `Omit<…, "directDoorActive">`。 */
|
|
1978
|
-
export type ServiceApprovalConfig = Pick<ServiceConfigFlat, "approvalRequire" | "approvalDeny" | "approvalTimeoutSec" | "approvalAutoBudget" | "approvalNeverAuto" | "approvalHmacKeys" | "durableApproval" | "durableApprovalSource" | "streamAskWindowMsSource" | "directApprovalDoor" | "directDoorActive" | "resourceSuspend" | "resourceSuspendTtlSec" | "resourceSuspendTotalTokens" | "resourceSuspendTotalBudgetUsd" | "resourceSuspendMaxSlices" | "askQuestionEnabled" | "questionThrottle" | "toolApprovalEnabled" | "permissionRulesEnabled" | "permissionRulesEnabledExplicit" | "streamAskWindowMarginMs" | "streamApproval" | "unattendedApprovalPolicy" | "mcpElicitation" | "sensitiveWritePatterns" | "writeProtectedPaths" | "manualModeShellGate" | "permissionsDisableAutoMode" | "autoModeClassifierFallback" | "crossSessionInbound" | "crossSessionDialogExpiry">;
|
|
2068
|
+
export type ServiceApprovalConfig = Pick<ServiceConfigFlat, "approvalRequire" | "approvalDeny" | "approvalTimeoutSec" | "approvalAutoBudget" | "approvalNeverAuto" | "approvalHmacKeys" | "durableApproval" | "durableApprovalSource" | "streamAskWindowMsSource" | "directApprovalDoor" | "directDoorActive" | "resourceSuspend" | "resourceSuspendTtlSec" | "resourceSuspendTotalTokens" | "resourceSuspendTotalBudgetUsd" | "resourceSuspendMaxSlices" | "askQuestionEnabled" | "questionThrottle" | "toolApprovalEnabled" | "permissionRulesEnabled" | "permissionRulesEnabledExplicit" | "streamAskWindowMarginMs" | "streamApproval" | "unattendedApprovalPolicy" | "mcpElicitation" | "sensitiveWritePatterns" | "writeProtectedPaths" | "manualModeShellGate" | "permissionsDisableAutoMode" | "autoModeClassifierFallback" | "autoModeClassifierTimeoutMs" | "crossSessionInbound" | "crossSessionDialogExpiry">;
|
|
1979
2069
|
/** 组:memory(记忆面 + TOC 同步腿)。 */
|
|
1980
2070
|
export type ServiceMemoryConfig = Pick<ServiceConfigFlat, "memoryEngineEnabled" | "memoryEngineDir" | "memoryEngineRemoteLaneAllowed" | "memoryEngineBackend" | "memoryScope" | "memoryPersistenceCapable" | "memorySync" | "memoryEmbedder" | "memoryOrgAdmissionMode" | "memoryDelegationEvidence" | "memoryProvenance" | "memoryCapturePolicy" | "memoryConsolidationDriver" | "memoryOrgDirectoryJson" | "memoryOrgGrantTtlMs" | "memoryOrgUnavailableBackoffMs" | "projectMemoryEnabled" | "syncImportLeaseStaleSec">;
|
|
1981
2071
|
/** 组:auth(鉴权 / 身份 / 治理棒)。`commandPolicy` 只有 sema-registry 腿(无 env 标量形),故 env 解析
|
|
@@ -1988,11 +2078,11 @@ export type ServiceAuthConfig = Pick<ServiceConfigFlat, "authToken" | "authToken
|
|
|
1988
2078
|
/** 组:orchestration(编排 + 执行车道 + 沙箱面 + workflow/后台 agent 存留)。 */
|
|
1989
2079
|
export type ServiceOrchestrationConfig = Pick<ServiceConfigFlat, "remoteExec" | "remoteExecHomeDir" | "requireExecutionLane" | "worktreeIsolation" | "leaderEnabled" | "leaderFanoutEnabled" | "routerEnabled" | "selfOrchestrationEnabled" | "selfOrchestrationModels" | "selfOrchestrationWorkerIsolation" | "forkEnabled" | "experimentalObserverAgents" | "schedulerEnabled" | "schedulerSessionWakeup" | "schedulerSessionLifetime" | "schedulerStorePath" | "planModeEnabled" | "hooksTimeoutMs" | "workflowRunStoreBackend" | "workflowOrphanGraceMs" | "workflowJournalRetentionMs" | "workflowRunRetentionMs" | "usageWindows" | "workflowAgentsReadOnly" | "workflowSizeGuideline" | "backgroundAgentRetentionMs" | "backgroundAgentStaleRunningMs" | "backgroundAgentParkClaimStaleMs" | "delegationEntryCaps" | "rosterRetentionMs" | "leaderRunStaleMs" | "scratchpadSweepTtlMs" | "sandboxPkgSource" | "sessionAutoTitle" | "sessionAutoTitleSource" | "selectEnvironmentTool" | "envFactsEnabled" | "toolDeferLongtail" | "lspEnabled" | "lspHostEnabled" | "lspEnvAllow" | "imageBakes" | "readFace" | "readFaceSource" | "taskWriteFaceEscalation" | "readDenyPatterns" | "readDenyBuiltinTiers" | "readDenyBuiltinExclude">;
|
|
1990
2080
|
/** 组:limitsHttp(HTTP 面 + 各类上限/配额/回收窗)。 */
|
|
1991
|
-
export type ServiceLimitsHttpConfig = Pick<ServiceConfigFlat, "port" | "attachmentOrphanGraceMs" | "workspaceFileMaxBytes" | "attachmentMaxBytes" | "attachmentMimeAllowlist" | "attachmentUnboundTtlMs" | "infraCostRates" | "drainGraceMs" | "sighupIdleGraceMs" | "parentPid" | "engineAutoExit" | "engineLingerMs" | "engineOrphanLingerMs" | "rateLimitPerMin" | "maxTaskCostUsd" | "maxTaskTokens" | "maxPrincipalCostUsd" | "costQuotaWindowSec" | "reapIntervalSec" | "runStaleSec" | "toolResultTtlSec">;
|
|
2081
|
+
export type ServiceLimitsHttpConfig = Pick<ServiceConfigFlat, "port" | "attachmentOrphanGraceMs" | "workspaceFileMaxBytes" | "attachmentMaxBytes" | "attachmentMimeAllowlist" | "attachmentUnboundTtlMs" | "infraCostRates" | "drainGraceMs" | "drainSecondSignalMinGapMs" | "sighupIdleGraceMs" | "parentPid" | "engineAutoExit" | "engineLingerMs" | "engineOrphanLingerMs" | "rateLimitPerMin" | "maxTaskCostUsd" | "maxTaskTokens" | "maxPrincipalCostUsd" | "costQuotaWindowSec" | "reapIntervalSec" | "runStaleSec" | "toolResultTtlSec">;
|
|
1992
2082
|
/** 组:observability(可观测)。 */
|
|
1993
2083
|
export type ServiceObservabilityConfig = Pick<ServiceConfigFlat, "metricsToken" | "traceToken" | "toolTrace" | "traceThinking" | "logLevel" | "otel">;
|
|
1994
2084
|
/** 组:integrations(外部集成)。`mcpServers` / `a2aPeers` 由 sema-registry 适配器填(无 env 腿),归本组。 */
|
|
1995
|
-
export type ServiceIntegrationsConfig = Pick<ServiceConfigFlat, "pluginsAllowHosts" | "mcpServers" | "a2aPeers" | "a2aServe" | "configBootFetchBudgetMs" | "gitApiBaseUrl" | "gitApiToken" | "gitApiKind" | "defaultScenario" | "skillsDir" | "configCenter" | "configProvider" | "configLocalDir">;
|
|
2085
|
+
export type ServiceIntegrationsConfig = Pick<ServiceConfigFlat, "pluginsAllowHosts" | "mcpServers" | "a2aPeers" | "a2aServe" | "configBootFetchBudgetMs" | "gitApiBaseUrl" | "gitApiToken" | "gitApiKind" | "defaultScenario" | "skillsDir" | "centerCredentialRenewLeadMs" | "configCenter" | "configProvider" | "configLocalDir">;
|
|
1996
2086
|
/** 九个组槽。每组恒在场(`loadConfig` / `attachConfigGroups` 装好才交出配置),故不可选——新代码写
|
|
1997
2087
|
* `config.modelPlane.model` 不需要 `?.`(可选组会把 `Model` 污染成 `Model | undefined`)。 */
|
|
1998
2088
|
export interface ServiceConfigGroups {
|
package/dist/config-types.js
CHANGED
|
@@ -1,8 +1,14 @@
|
|
|
1
|
+
import { DRAIN_GRACE_FLOOR_MS } from "@sema-agent/settings-schema/fleet";
|
|
1
2
|
export const TASK_WRITE_FACE_ESCALATIONS = ["allow", "refuse"];
|
|
2
3
|
export const RETENTION_MODES = ["audit-only", "enforce"];
|
|
3
4
|
export const MEMORY_CONSOLIDATION_MIN_INTERVAL_SEC = 300;
|
|
5
|
+
export const MAX_TIMER_DELAY_MS = 2_147_483_647;
|
|
6
|
+
export const AUTO_MODE_CLASSIFIER_TIMEOUT_BOUNDS = { minMs: 1_000, maxMs: MAX_TIMER_DELAY_MS };
|
|
7
|
+
export { DRAIN_GRACE_FLOOR_MS };
|
|
8
|
+
export const DRAIN_SECOND_SIGNAL_MIN_GAP_BOUNDS = { minMs: 0, maxMs: DRAIN_GRACE_FLOOR_MS };
|
|
9
|
+
export const DRAIN_SECOND_SIGNAL_MIN_GAP_DEFAULT_MS = 250;
|
|
10
|
+
export const FIRST_BYTE_TIMEOUT_BOUNDS = { minMs: 0, maxMs: MAX_TIMER_DELAY_MS };
|
|
4
11
|
export const RESILIENCE_DEFAULTS = {
|
|
5
|
-
connectTimeoutMs: { env: "MODEL_CONNECT_TIMEOUT_MS", value: 30_000 },
|
|
6
12
|
firstTokenTimeoutMs: { env: "MODEL_FIRST_TOKEN_TIMEOUT_MS", value: 600_000 },
|
|
7
13
|
idleTimeoutMs: { env: "MODEL_IDLE_TIMEOUT_MS", value: 300_000 },
|
|
8
14
|
circuitBreaker: { env: "MODEL_CIRCUIT_BREAKER", value: false },
|
|
@@ -12,7 +18,6 @@ export const RESILIENCE_DEFAULTS = {
|
|
|
12
18
|
export function buildDefaultResilience() {
|
|
13
19
|
const d = RESILIENCE_DEFAULTS;
|
|
14
20
|
return {
|
|
15
|
-
connectTimeoutMs: d.connectTimeoutMs.value,
|
|
16
21
|
firstTokenTimeoutMs: d.firstTokenTimeoutMs.value,
|
|
17
22
|
idleTimeoutMs: d.idleTimeoutMs.value,
|
|
18
23
|
circuitBreaker: d.circuitBreaker.value,
|