@sema-agent/server 7.71.0 → 7.72.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 (53) hide show
  1. package/USAGE.md +2 -1
  2. package/dist/approval-card.d.ts +16 -17
  3. package/dist/approval-card.js +0 -9
  4. package/dist/boot/runner-deps.d.ts +32 -16
  5. package/dist/brain.js +2 -9
  6. package/dist/budget.d.ts +12 -1
  7. package/dist/budget.js +1 -1
  8. package/dist/config-catalog.js +12 -12
  9. package/dist/config-center/apply-effective.js +2 -4
  10. package/dist/config-center/facade.d.ts +1 -1
  11. package/dist/config-center/restart-signal.js +2 -2
  12. package/dist/config-center/skills-mcp.js +15 -0
  13. package/dist/config-types.d.ts +68 -16
  14. package/dist/config-types.js +19 -1
  15. package/dist/config.d.ts +42 -0
  16. package/dist/config.js +58 -21
  17. package/dist/http/durable-run-leg.d.ts +64 -0
  18. package/dist/http/durable-run-leg.js +63 -0
  19. package/dist/http/route-ctx.d.ts +14 -0
  20. package/dist/http/route-ctx.js +3 -0
  21. package/dist/http/routes/approvals-assistant.js +3 -3
  22. package/dist/http/routes/capabilities.js +2 -2
  23. package/dist/http/routes/runs.js +10 -58
  24. package/dist/http/routes/workflows.js +1 -0
  25. package/dist/http/server.js +55 -17
  26. package/dist/main.js +4 -1
  27. package/dist/observability/fail-open.d.ts +1 -1
  28. package/dist/observability/fail-open.js +1 -1
  29. package/dist/observability/metrics.js +1 -1
  30. package/dist/observability/tool-trace.d.ts +18 -1
  31. package/dist/observability/tool-trace.js +4 -3
  32. package/dist/parked-decide.js +3 -2
  33. package/dist/plugins/e2b-orphan-reclaim.d.ts +4 -4
  34. package/dist/plugins/e2b-orphan-reclaim.js +18 -20
  35. package/dist/plugins/store-contracts.d.ts +27 -0
  36. package/dist/plugins/store-contracts.js +3 -2
  37. package/dist/run-cancel-context.js +1 -1
  38. package/dist/run-local.js +4 -1
  39. package/dist/runs.d.ts +16 -2
  40. package/dist/runs.js +2 -1
  41. package/dist/runtime-caps-resolver.js +5 -3
  42. package/dist/task-a2a.js +3 -2
  43. package/dist/task-mcp.js +3 -2
  44. package/dist/tool-approval.d.ts +26 -21
  45. package/dist/tool-approval.js +2 -2
  46. package/dist/trace/core-keyset-guard.d.ts +10 -4
  47. package/dist/trace/project.d.ts +12 -0
  48. package/dist/trace/project.js +19 -3
  49. package/dist/usage-analytics.d.ts +9 -1
  50. package/dist/usage-analytics.js +8 -0
  51. package/dist/wire-name-bound.d.ts +14 -0
  52. package/dist/wire-name-bound.js +13 -0
  53. package/package.json +2 -2
package/USAGE.md CHANGED
@@ -161,7 +161,8 @@ MODEL_DEGRADE_ON=rate_limit,breaker_open # 可选,反应式触发器子集;词
161
161
  **坏形/坏词/未知键 ⇒ 丢掉这条 `compat` 声明、模型本体保留**,并打一条 `model_compat_dropped`
162
162
  点名 warn(引擎回落到自己的推断 = 今天的行为);一条写坏的声明**不会**让这只模型或整套目录消失。
163
163
  - 反应式降级 brain 包在**最外层**;fallback brain 用自己的凭据(decorator 清掉主模型的 per-call key 防外泄给别的 provider)。**坑**:`MODEL_DEGRADE_TO` 最好别和被限流的是同一网关/账号,否则反应式切过去照样撞同一个 rate_limit。
164
- - **定价怎么设**:`model.cost` 来自 `MODEL_COST_INPUT/OUTPUT/CACHE_READ/CACHE_WRITE`(**USD per 1M tokens**,默认 0 = `costUsd` 读 0)。云模型(如 review-gw 的 deepseek-v4-flash)必须设,否则 spend 恒为 $0;本地自托管(qwen)留 0 即对(无 per-token 外部花费)。字段名是 `costUsd`,**非美元计价的网关要先折算**(如 DeepSeek 官方 CNY ÷ 汇率)。配置控制面管的模型走 `CenterModel.cost`(中心存价、不存 secret)。
164
+ - **定价怎么设**:`model.cost` 来自 `MODEL_COST_INPUT/OUTPUT/CACHE_READ/CACHE_WRITE`(**USD per 1M tokens**)。**四键一个都不设 = 未定价**:成本读面**缺席**(`result.stats.costMicroUsd` 整键不出、`costBreakdown` 不产、`/v1/capabilities.pricingConfigured=false`),不是 `$0` —— 消费端应渲成「未知」。设其中任一键 ⇒ 四键在场(未设的按 0)。本地自托管(qwen)真的没有 per-token 外部花费时,请**显式**把四键设成 `0`:那是一次「声明免费」,读面照常发 `costMicroUsd:0` 且 `pricingConfigured=true`。云模型(如 review-gw 的 deepseek-v4-flash)必须设真价,否则 spend 无从计算。字段名是 `costUsd`,**非美元计价的网关要先折算**(如 DeepSeek 官方 CNY ÷ 汇率)。配置控制面管的模型走 `CenterModel.cost`(中心存价、不存 secret;名册行不带 `cost` = 该模型未定价,同上)。
165
+ - **成本天花板要有价才咬得住**:`MAX_TASK_COST_USD` / `MAX_PRINCIPAL_COST_USD` 量的是 `costUsd`,未定价的模型算不出成本 ⇒ 这两道闸**永不触发**。目录里一只带价模型都没有却配了天花板时,boot 会打一条 `cost_ceiling_without_pricing` 告警(不拒启);token 轴的 `MAX_TASK_TOKENS` 不受影响。
165
166
  - 成本计量**自动开**:从 config 的 `model.cost`(per-1M 绝对 USD)注入 `pricing`,core 算出权威的整数 `costMicroUsd`(避免浮点累计误差)。`/metrics` 新增:`model_cost_micro_usd_total{model}`(覆盖所有 brain 调用=主任务+异步+council 子任务的总花费)、`brain_first_token_ms`(网关 hang 早警)、`brain_call_latency_ms`、`tool_calls_total{name,ok}`、`budget_exceeded_total{code}`、`cost_quota_rejected_total`、`degraded_total{reason}`(1.40 降级)。
166
167
  - **近预算降级 vs 硬闸**:降级(`MODEL_DEGRADE_TO`,到 `atCostFraction` 切便宜模型)是**撑长**预算、任务仍完成(出口质量下降、发 `task.degraded` 事件可告警);硬闸(`maxCostUsd` 全额)仍在,切了便宜模型还超全额 → `limits.max_cost_exceeded` 停。
167
168
  - **`METRICS_TOKEN`**(可选,只读):设了它,`GET /metrics`+`/metrics/summary` 接受**它或** `SERVICE_AUTH_TOKEN`。**全 fleet 设同一个值** → 管理端(sema-web,规划名 sema-admin)用**一个** token 拉所有 worker 的指标,**无需持有各 worker 的全权 `SERVICE_AUTH_TOKEN`**(不破坏 secret 边界)。即使外泄也只暴露指标(只读)。
@@ -328,27 +328,26 @@ declare const DenialLimitFallbackSchema: z.ZodObject<{
328
328
  autoDenyAfterMs: z.ZodNumber;
329
329
  }, z.core.$strict>;
330
330
  export type DenialLimitFallback = z.infer<typeof DenialLimitFallbackSchema>;
331
- /** S-185(core 7.10.0 [ref])—— **分类器为什么答不了**。`cause` 是 core 的闭集词
332
- * (`AUTO_MODE_UNAVAILABLE_CAUSES`;core 7.12.0 起 = error | timeout,`breaker_open` 随熔断族退役),
333
- * 本仓**刻意不枚举**它:词表的单一
334
- * 属主在 core,抄一份的形会在 core 加词那天把新词静默吞成缺席 —— 而丢的正是「这次不可用是新出现的那
335
- * 一类」这条信息(与 `AskRequest.origin` 顶注同一条纪律)。判据只到「非空串」这一层形门。
336
- * `.strict()` + 单成员必填:core 将来给这只对象加成员时本读面当场判假、**整键不铸**,新成员必须由人
337
- * 处置过才上 wire;而一个**没有 cause** 的空壳等于说「分类器坏了但说不出为什么」,比不说更坏。 */
331
+ /**
332
+ * ⚰️ **退役成员的形(server 7.69.0–7.71.0 铸过,7.72.0 起永不再铸)** —— core 7.14.0 [ref] 把
333
+ * `classifierUnavailable` 从 ask 侧**整族**删掉(事实改骑 `GateDisposition.denied.cause`),所以本仓
334
+ * 的**铸造**腿(`buildApprovalCard` / 活卡帧 / inbox 行)本批全部摘掉;窄读函数
335
+ * `readClassifierUnavailable` 与对 core 型面的合规钉连同它们的素材一起删(素材没了,钉的两端只剩一端)。
336
+ *
337
+ * 🔴 **这只 schema 本身留着,而且是承重的,不是没删干净**:`ApprovalCardSchema` 是 `.strict()`,
338
+ * 而它同时是 `approval_ask.card_json` 这张**耐久列**的读契约(读侧三个 `safeParse`:重放腿、幂等重入、
339
+ * 对账扫描)。把成员从 strict schema 上删掉 ⇒ 7.69.0–7.71.0 铸下、升级时**仍然 pending** 的那些行
340
+ * 当场判假 ⇒ 「跳过 + 一次 warn」:人手上那张卡在重连后变不回来、幂等重入退成 park。
341
+ * ⇒ 一条耐久投影的 schema **必须读得懂它自己写过的每一版**;写侧只写当前引擎供得出的键,读侧接受
342
+ * 全部历史成员。这不是给退役键开特例,这是 strict + durable 这一组合的内生义务(同样的处置将适用于
343
+ * 未来任何一次键退役)。
344
+ *
345
+ * 形保持原样(单成员 `cause` 必填、`.strict()`):它描述的是**已经写在库里的字节**,不许再动。
346
+ */
338
347
  declare const ClassifierUnavailableSchema: z.ZodObject<{
339
348
  cause: z.ZodString;
340
349
  }, z.core.$strict>;
341
350
  export type ClassifierUnavailable = z.infer<typeof ClassifierUnavailableSchema>;
342
- /**
343
- * `AskRequest.classifierUnavailable`(core 7.10.0 [ref])的**边界窄读** —— 活卡帧与 `card_json` 的唯一
344
- * 铸造点(与 {@link readDenialLimitFallback} 同款分工),两面结构性同值。
345
- *
346
- * 🔴 **缺席不是断言**:缺席同时覆盖「分类器答上了」「这只 ask 没资格走分类器」「本部署没接分类器」
347
- * 三形(core d.ts 逐字:read presence, never absence),消费端禁读成「分类器好着呢」。
348
- * 🔴 **echo-only**:server 不据它做任何裁决 —— 熔断/超时的处置全在 core 的分类器站,据本键在本仓自铸
349
- * 第二套回落判据就是同一语义面两个写者(源头修复纪律)。
350
- */
351
- export declare function readClassifierUnavailable(req: unknown): ClassifierUnavailable | undefined;
352
351
  /**
353
352
  * `AskRequest.denialLimitFallback`(core 7.4.0 [ref])的**边界窄读** —— 活卡帧与 `card_json` 的唯一铸造点
354
353
  * (与 {@link readRuleOffersAbsence} / {@link readProbeCause} 同款分工),两面结构性同值。
@@ -238,13 +238,6 @@ const _denialLimitFallbackConformance = [true, true];
238
238
  void _denialLimitFallbackConformance;
239
239
  const DenialLimitFallbackEnvelopeSchema = z.object({ denialLimitFallback: DenialLimitFallbackSchema.optional() });
240
240
  const ClassifierUnavailableSchema = z.object({ cause: z.string().min(1).max(MAX_IDENT) }).strict();
241
- const _classifierUnavailableConformance = [true];
242
- void _classifierUnavailableConformance;
243
- const ClassifierUnavailableEnvelopeSchema = z.object({ classifierUnavailable: ClassifierUnavailableSchema.optional() });
244
- export function readClassifierUnavailable(req) {
245
- const parsed = ClassifierUnavailableEnvelopeSchema.safeParse(req);
246
- return parsed.success ? parsed.data.classifierUnavailable : undefined;
247
- }
248
241
  export function readDenialLimitFallback(req) {
249
242
  const parsed = DenialLimitFallbackEnvelopeSchema.safeParse(req);
250
243
  return parsed.success ? parsed.data.denialLimitFallback : undefined;
@@ -312,7 +305,6 @@ export function buildApprovalCard(source, req, requiresRealApproval) {
312
305
  const ruleEvidence = readRuleEvidence(req);
313
306
  const ruleOffersAbsence = readRuleOffersAbsence(req);
314
307
  const denialLimitFallback = readDenialLimitFallback(req);
315
- const classifierUnavailable = readClassifierUnavailable(req);
316
308
  const toolCallId = clip(source.toolCallId, MAX_IDENT);
317
309
  const sourceTaskId = clip(source.sourceTaskId, MAX_IDENT);
318
310
  const sourceAgentName = clip(source.sourceAgentName, MAX_AGENT_NAME);
@@ -335,7 +327,6 @@ export function buildApprovalCard(source, req, requiresRealApproval) {
335
327
  ...(source.ruleOffers !== undefined && source.ruleOffers.length > 0 ? { ruleOffers: source.ruleOffers.map(copyRuleOffer) } : {}),
336
328
  ...(ruleOffersAbsence !== undefined ? { ruleOffersAbsence } : {}),
337
329
  ...(denialLimitFallback !== undefined ? { denialLimitFallback } : {}),
338
- ...(classifierUnavailable !== undefined ? { classifierUnavailable } : {}),
339
330
  ...(source.fromSubagent === true ? { fromSubagent: true } : {}),
340
331
  ...(sourceTaskId !== undefined ? { sourceTaskId } : {}),
341
332
  ...(sourceAgentName !== undefined ? { sourceAgentName } : {}),
@@ -84,26 +84,42 @@ export interface RunnerDepsCtx {
84
84
  *
85
85
  * 本键从「差异键」升进{@link createSharedRunnerDeps 共享基座}。修前它**只**挂在 subRunner 上,主
86
86
  * runner 靠 per-task `spec.checkpointStore` 拿店 —— 而 core 的 `run_workflow` 是挂在**主** Runner
87
- * (`prepare-caps-and-workflow.js:244` 的 `runner: runnerSelf`;core 7.12.0 亲核行号,B-060 票据引的
88
- * :249 是 7.11.2 的坐标)上的,workflow 子代的 childSpec 不带
89
- * `checkpointStore`(`orchestration/workflow-primitives.js:80-81` 只透传 `"disabled"` 这一种状态),
90
- * 于是子代的 `resolveCheckpointStore(spec, deps)` 落到主 runner 这一格 = `undefined`
91
- * ⇒ `parkLane {capable:false, reasons:["no_checkpoint_store"]}` ⇒ 受门的调用不是 park 而是
92
- * `delegation.ask_unresolvable{parkLaneExisted:false}` 直接拒(过度拒绝,S-185 的 `/decide`
93
- * 第三车道在这条路径上结构性不可达)。
87
+ * (`prepare-caps-and-workflow.js:279` 的 `runner: runnerSelf`;**core 7.14.0 亲核行号**,7.12.0 时是
88
+ * :244、B-060 票据引的 :249 是 7.11.2 的坐标 —— 这一格每版都在漂,引用前必须对着装树重读)上的。
89
+ *
90
+ * ⚠️ **B-060 当初写下的病理有一半已被 core 7.14.0 消解,如实订正**([ref] 的 `CheckpointSeat` 重构):
91
+ * · **7.13.0 及以前**:`orchestration/workflow-primitives.js` 只在父侧
92
+ * `parentCheckpointStoreDisabled === true` 时给子代盖 `"disabled"`,**不把父侧真店透传给子代**
93
+ * ⇒ 子代的 `resolveCheckpointStore(spec, deps)` 落到主 runner 这一格 = `undefined`
94
+ * ⇒ `parkLane {capable:false, reasons:["no_checkpoint_store"]}` ⇒ 受门的调用不是 park 而是
95
+ * `delegation.ask_unresolvable{parkLaneExisted:false}` 直接拒(过度拒绝)。
96
+ * · **7.14.0 起**:core 在 prepare 一次铸出 `checkpointSeat = checkpointSeatOf(spec, resolved)`
97
+ * (`prepare-task.js:262`),两条委派车道**逐字复制**它到子代 —— workflow 腿经
98
+ * `parentCheckpointStore: checkpointSeat`(`prepare-caps-and-workflow.js:335`)落到
99
+ * `workflow-primitives.js:80`(现在的条件是「父侧有座 ∧(座是 disabled ∨ 子 spec 没自带店)」⇒
100
+ * **真店也透传**),Task 腿经 `ToolExecuteContext.checkpointStoreForChildren`。
101
+ * ⇒ 「子代拿不到店」这条病路 core 侧已经堵死。**但本席仍然承重**,理由是那只座**本身**就从
102
+ * `resolveCheckpointStore(spec, deps)` 来:主 runner 这一格空着、而 spec 又没带店时,座就是
103
+ * `undefined`,子代照样什么都拿不到。本席不是 core 单源的第二个写者,它就是 core 读的那个 deps 格。
94
104
  *
95
105
  * 🔴 host 腿的读数判据是**core 侧硬的**,不是本仓的但愿:`resolveCheckpointStore`
96
- * (`checkpoint-store.js:367`)**spec 优先**——`spec.checkpointStore` 在场就用它、`"disabled"` 就是
97
- * 关、只有缺席才落 deps。而本仓 host 腿恒经 `resolveSpec` 的 durableEnabled 分支盖上**同一个实例**,
98
- * 且 `durableEnabled ⇔ checkpointStore !== undefined`(`boot/coordinators.ts:70` 与 main.ts 的构造
99
- * 条件同源)⇒ 每一条经 `resolveSpec` 的腿看到的店逐字同一只。
106
+ * (`checkpoint-store.js:370`,7.14.0 亲核)**spec 优先**——`spec.checkpointStore` 在场就用它、
107
+ * `"disabled"` 就是关、只有缺席才落 deps。而本仓 host 腿恒经 `resolveSpec` 的 durableEnabled 分支盖上
108
+ * **同一个实例**,且 `durableEnabled ⇔ checkpointStore !== undefined`(`boot/coordinators.ts:70` 与
109
+ * main.ts 的构造条件同源)⇒ 每一条经 `resolveSpec` 的腿看到的店逐字同一只。
110
+ *
111
+ * 🔴 **core 7.14.0 新加的响亮拒(装配纪律,本仓已合规但必须知道)**:`agents/subagent.js` 在
112
+ * `wantsBackground` 臂上会检查「`ScenarioDeps.checkpointStore`(→ `bg.checkpointStore`)与本 run 的座
113
+ * **不是同一只**」⇒ `configError("config.invalid_checkpoint_store")` **抛**。本仓 main.ts 的
114
+ * `scenarioDeps.checkpointStore` 与本席是同一个 `const`,run-local 两边都不接 ⇒ 两条腿都不触发;
115
+ * 哪天有人给这两处接了不同的店,失败是响亮的而不是静默半场。
100
116
  *
101
117
  * ⚠️ **如实登记一处不经解析器的直读**(codex 交叉复审提名,亲读 core dist 确认):
102
- * `core/runner/run-terminal-adoption.js:74` 读的是 `runner.deps.checkpointStore` **本身**,不走
103
- * `resolveCheckpointStore`。它是「宿主任务 park 时,把没排空的 steer / follow-up 迁移到 park token 上」
104
- * 那一步 —— 修前主 runner 这一格是空的,于是**整段迁移静默不跑**(那些输入随本腿一起丢,只留
105
- * `task.user_followup_undrained` 一类通告)。接上之后它开始真的跑。方向是修好一条本就该在的腿,
106
- * 但它**不是**「host 腿字节不变」的一部分,所以写在这里而不是混进上面那句。
118
+ * `core/runner/run-terminal-adoption.js:80`(7.14.0 亲核;7.13.0 时是 :74)读的是
119
+ * `runner.deps.checkpointStore` **本身**,不走 `resolveCheckpointStore`,**也不走座** —— 所以 7.14.0
120
+ * 的座重构对它一个字节都没改,本席对它仍是唯一来源。它是「宿主任务 park 时,把没排空的 steer /
121
+ * follow-up 迁移到 park token 上」那一步 —— 修前主 runner 这一格是空的,于是**整段迁移静默不跑**
122
+ * (那些输入随本腿一起丢,只留 `task.user_followup_undrained` 一类通告)。接上之后它开始真的跑。
107
123
  *
108
124
  * 🔴 这一席是**部署腿级**的答案,不是「哪只 Runner」的属性:一条腿要么有赎回口(HTTP 面的
109
125
  * `/v1/approvals/:id/decide` + resume 家族)、于是它的每只 Runner 都该有店;要么没有(`run-local`
package/dist/brain.js CHANGED
@@ -1,6 +1,6 @@
1
1
  import { adjudicateModelRoute, createAnthropicBrain, createCircuitBreakerBrain, createDegradingBrain, createFailoverBrain, createOpenAIBrain, createRoutingBrain, hasAuthCarrier, resolveRouteCredential, routeRefusalText, } from "@sema-agent/core";
2
2
  import { normalizeBaseUrl } from "@sema-agent/core";
3
- import { MODEL_FIRST_TOKEN_TIMEOUT_MS_DEFAULT } from "./config-types.js";
3
+ import { buildDefaultResilience } from "./config-types.js";
4
4
  import { resolveModelApiKey } from "./key-resolver.js";
5
5
  function isOffDeploymentRoot(baseUrl, config) {
6
6
  if (!baseUrl)
@@ -15,14 +15,7 @@ function degradeDeclaredRoot(fallbackBaseUrl, config) {
15
15
  return armed ? config.gatewayBaseUrl : (fallbackBaseUrl || config.gatewayBaseUrl);
16
16
  }
17
17
  export const FAST_FAIL_MAX_RETRIES = 2;
18
- const DEFAULT_RESILIENCE = {
19
- connectTimeoutMs: 30_000,
20
- firstTokenTimeoutMs: MODEL_FIRST_TOKEN_TIMEOUT_MS_DEFAULT,
21
- idleTimeoutMs: 300_000,
22
- circuitBreaker: false,
23
- failureThreshold: 5,
24
- cooldownMs: 30_000,
25
- };
18
+ const DEFAULT_RESILIENCE = buildDefaultResilience();
26
19
  export function createBrain(config, deps = {}) {
27
20
  const { fetchImpl, breakerState } = deps;
28
21
  const r = config.resilience ?? DEFAULT_RESILIENCE;
package/dist/budget.d.ts CHANGED
@@ -11,7 +11,10 @@ import type { QuotaTracker } from "./observability/cost-quota.js";
11
11
  import type { FleetUsageAccumulator } from "./fleet-client.js";
12
12
  import type { FleetLeaseManager } from "./fleet-lease.js";
13
13
  import { type PromptManifestTracker } from "./observability/prompt-manifest.js";
14
- /** ③ Per-model pricing keyed by model id (core falls back to each Model.cost when a model is absent). */
14
+ /** ③ Per-model pricing keyed by model id (core falls back to each Model.cost when a model is absent).
15
+ * B-089:`m.cost` 运行期可缺席(未定价的模型不铸这个键 —— `config.ts` 的 `buildModelCostKey`),于是
16
+ * 未定价模型在本表里**没有行**;core 的 `isModelPriced` 两边都读(`pricing[id]` 或 `model.cost`),
17
+ * 两边同时缺席 = 这只模型未定价 ⇒ 成本读面整键缺席,而不是编造的 0。 */
15
18
  export declare function buildPricing(models: Record<string, Model>): Record<string, ModelPricing>;
16
19
  /**
17
20
  * 缝合审 M3:`capabilities.pricingConfigured` 的判据 —— 提成函数放在 pricing 自己的文件里,因为
@@ -23,6 +26,14 @@ export declare function buildPricing(models: Record<string, Model>): Record<stri
23
26
  *
24
27
  * 同一条论证在同一个字面量里**已经执行过一次**:紧邻的 `restartState: () => configCenter.restartState()`
25
28
  * 带着注释「A live getter (not a snapshot) so /health always reads current」—— 兄弟键漏了。本仓第 7 例同形。
29
+ *
30
+ * 🔴 B-089(判据本身,同一条「0 不是缺席」的病):此前判的是「**有没有一项正价**」
31
+ * (`some(v > 0)`),于是一台把四项单价**显式**配成 0 的部署(= 运营者声明这个模型免费)被报成
32
+ * 「没配价目表」—— 正好是本键要消歧的两态被折成同一个 false,消费端把一个可信的 $0 渲成 unknown。
33
+ * 判据改为**表在不在**:与 core [ref] 的 `isModelPriced`(「price table 是否 EXIST」)同向,也与
34
+ * 本键在 wire 契约里的原话(「价目表是否已配」)一致。
35
+ * 三态读回:未定价 ⇒ 空表 ⇒ false(与改前同值);声明免费 ⇒ 有行(全零)⇒ true(**唯一变的一格**);
36
+ * 配了真价 ⇒ true(与改前同值)。
26
37
  */
27
38
  export declare function isPricingConfigured(pricing: Record<string, ModelPricing>): boolean;
28
39
  /**
package/dist/budget.js CHANGED
@@ -10,7 +10,7 @@ export function buildPricing(models) {
10
10
  return pricing;
11
11
  }
12
12
  export function isPricingConfigured(pricing) {
13
- return Object.values(pricing).some((p) => Object.values(p).some((v) => typeof v === "number" && v > 0));
13
+ return Object.keys(pricing).length > 0;
14
14
  }
15
15
  export function cardinalityGuard(cap, onOverflow) {
16
16
  const seen = new Set();
@@ -1,6 +1,6 @@
1
1
  import { CROSS_SESSION_INBOUND_SETTINGS, CROSS_SESSION_DIALOG_EXPIRY_VALUES } from "@sema-agent/core";
2
2
  import { THINKING_FORMATS } from "./model-compat.js";
3
- import { MODEL_FIRST_TOKEN_TIMEOUT_MS_DEFAULT } from "./config-types.js";
3
+ import { RESILIENCE_DEFAULTS } from "./config-types.js";
4
4
  import { webSearchConfigFromEnv } from "./plugins/web-search.js";
5
5
  import { CENTER_READ_FACE_LIVE_KEYS } from "./config-center/read-face.js";
6
6
  export const CATALOG_VALUE_TYPES = ["boolean", "number", "string", "enum", "csv", "json", "path", "url", "secret", "opaque"];
@@ -246,8 +246,8 @@ export const CONFIG_CATALOG = [
246
246
  r("MODEL_API_KEY", "modelPlane", "secret", "网关腿共享 API key(per-model 覆写在 center 侧)", { danger: [...SEC, ...BILL] }),
247
247
  r("MODEL_AUTO_COMPACT_TOKENS", "modelPlane", "number", "主槽 dual-window 触发阈(1M 窗不再隐式 967000,缺席则用普通触发几何)", { derivedDefaultNote: "缺席 ⇒ 不铸键(core W-33000 几何)" }),
248
248
  r("MODEL_CASCADE_LADDER", "modelPlane", "csv", "质量门级联梯(目录名 cheapest→strongest;opt-in per-task cascade:true)", { danger: BILL, configKey: "cascadeLadder" }),
249
- r("MODEL_CB_COOLDOWN_MS", "modelPlane", "number", "熔断冷却窗(ms)", { staticDefault: "30000" }),
250
- r("MODEL_CB_FAILURE_THRESHOLD", "modelPlane", "number", "熔断失败阈", { staticDefault: "5" }),
249
+ r("MODEL_CB_COOLDOWN_MS", "modelPlane", "number", "熔断冷却窗(ms)", { staticDefault: String(RESILIENCE_DEFAULTS.cooldownMs.value) }),
250
+ r("MODEL_CB_FAILURE_THRESHOLD", "modelPlane", "number", "熔断失败阈", { staticDefault: String(RESILIENCE_DEFAULTS.failureThreshold.value) }),
251
251
  r("MODEL_CHARS_PER_TOKEN", "modelPlane", "number", "结构估 token 系数(CJK 密集模型建议 2-3;坏值回 core 默认)", { derivedDefaultNote: "缺席 ⇒ core 默认 4" }),
252
252
  r("MODEL_CHEAP_AUTO_COMPACT_TOKENS", "modelPlane", "number", "cheap 槽 dual-window 触发阈(主槽值刻意不外溢)", { derivedDefaultNote: "缺席 ⇒ 不铸键" }),
253
253
  r("MODEL_CHEAP_CHARS_PER_TOKEN", "modelPlane", "number", "cheap 槽结构估 token 系数", { derivedDefaultNote: "缺席 ⇒ 继承主模型" }),
@@ -260,14 +260,14 @@ export const CONFIG_CATALOG = [
260
260
  r("MODEL_CHEAP_MAX_TOKENS", "modelPlane", "number", "cheap 槽输出上限", { derivedDefaultNote: "缺席 ⇒ 继承主模型", center: cModels("models[].maxTokens") }),
261
261
  r("MODEL_CHEAP_REASONING", "modelPlane", "boolean", "cheap 槽 reasoning 声明(三态:缺席继承主模型)", { derivedDefaultNote: "缺席 ⇒ 继承主模型" }),
262
262
  r("MODEL_CHEAP_VISION", "modelPlane", "boolean", "cheap 槽 vision 声明(三态:缺席继承主模型;text-only cheap 必设 false 让预检真拦)", { derivedDefaultNote: "缺席 ⇒ 继承主模型" }),
263
- r("MODEL_CIRCUIT_BREAKER", "modelPlane", "boolean", "模型腿熔断开关", { staticDefault: "false" }),
263
+ r("MODEL_CIRCUIT_BREAKER", "modelPlane", "boolean", "模型腿熔断开关", { staticDefault: String(RESILIENCE_DEFAULTS.circuitBreaker.value) }),
264
264
  r("MODEL_CODE_ROLES", "modelPlane", "csv", "给点名 role 附编码 persona(role 表内名字才生效)"),
265
- r("MODEL_CONNECT_TIMEOUT_MS", "modelPlane", "number", "连接超时(ms;0=关)", { staticDefault: "30000" }),
265
+ r("MODEL_CONNECT_TIMEOUT_MS", "modelPlane", "number", "连接超时(ms;0=关)", { staticDefault: String(RESILIENCE_DEFAULTS.connectTimeoutMs.value) }),
266
266
  r("MODEL_CONTEXT_WINDOW", "modelPlane", "number", "主模型上下文窗声明", { staticDefault: "262144", configKey: "model", resolve: (c) => c.model.contextWindow ?? null, center: cModels("models[].contextWindow") }),
267
- r("MODEL_COST_CACHE_READ", "modelPlane", "number", "主模型 cache-read 单价(per-1M USD;0=不计价)", { staticDefault: "0", danger: BILL, resolve: (c) => c.model.cost?.cacheRead ?? null, center: cModels("models[].cost.cacheRead") }),
268
- r("MODEL_COST_CACHE_WRITE", "modelPlane", "number", "主模型 cache-write 单价", { staticDefault: "0", danger: BILL, resolve: (c) => c.model.cost?.cacheWrite ?? null, center: cModels("models[].cost.cacheWrite") }),
269
- r("MODEL_COST_INPUT", "modelPlane", "number", "主模型 input 单价(NaN 会让成本天花板整条消失 ⇒ 坏值拒启)", { staticDefault: "0", danger: BILL, resolve: (c) => c.model.cost?.input ?? null, center: cModels("models[].cost.input") }),
270
- r("MODEL_COST_OUTPUT", "modelPlane", "number", "主模型 output 单价", { staticDefault: "0", danger: BILL, resolve: (c) => c.model.cost?.output ?? null, center: cModels("models[].cost.output") }),
267
+ r("MODEL_COST_CACHE_READ", "modelPlane", "number", "主模型 cache-read 单价(per-1M USD;显式 0=声明免费,不是「没配」)", { derivedDefaultNote: "四键全缺席 ⇒ 未定价(不铸 cost 键,成本读面缺席);任一设 ⇒ 四键在场,未设的按 0 显式", danger: BILL, resolve: (c) => c.model.cost?.cacheRead ?? null, center: cModels("models[].cost.cacheRead") }),
268
+ r("MODEL_COST_CACHE_WRITE", "modelPlane", "number", "主模型 cache-write 单价", { derivedDefaultNote: "四键全缺席 ⇒ 未定价(不铸 cost 键,成本读面缺席);任一设 ⇒ 四键在场,未设的按 0 显式", danger: BILL, resolve: (c) => c.model.cost?.cacheWrite ?? null, center: cModels("models[].cost.cacheWrite") }),
269
+ r("MODEL_COST_INPUT", "modelPlane", "number", "主模型 input 单价(NaN 会让成本天花板整条消失 ⇒ 坏值拒启)", { derivedDefaultNote: "四键全缺席 ⇒ 未定价(不铸 cost 键,成本读面缺席);任一设 ⇒ 四键在场,未设的按 0 显式", danger: BILL, resolve: (c) => c.model.cost?.input ?? null, center: cModels("models[].cost.input") }),
270
+ r("MODEL_COST_OUTPUT", "modelPlane", "number", "主模型 output 单价", { derivedDefaultNote: "四键全缺席 ⇒ 未定价(不铸 cost 键,成本读面缺席);任一设 ⇒ 四键在场,未设的按 0 显式", danger: BILL, resolve: (c) => c.model.cost?.output ?? null, center: cModels("models[].cost.output") }),
271
271
  r("MODEL_DEFAULT_THINKING", "modelPlane", "enum", "主模型默认思考档(llm-core ThinkingLevel;off=不设模型级默认;未知词拒启)", { enumValues: ["minimal", "low", "medium", "high", "xhigh", "max", "off"], derivedDefaultNote: "缺席/off ⇒ 不铸 defaultThinking(逐任务档由 spec>role 决定)", resolve: (c) => c.model.defaultThinking ?? null }),
272
272
  r("MODEL_DEGRADE_AT_COST_FRACTION", "modelPlane", "number", "预算降级触发比(spent/budget;>1=实际别降级)", { staticDefault: "0.7", danger: BILL }),
273
273
  r("MODEL_DEGRADE_ON", "modelPlane", "csv", "反应式降级触发词表(core DegradeReason 全集;拼错拒启)", { center: { domain: "models", path: "degrade", centerKey: "models", precedence: "center-wins", timing: "restart", restartSlice: "degrade-route" } }),
@@ -275,12 +275,12 @@ export const CONFIG_CATALOG = [
275
275
  r("MODEL_DEGRADE_TO", "modelPlane", "string", "降级目标模型(设了=开降级;外部网关名不必在目录)", { danger: BILL }),
276
276
  r("MODEL_DEGRADE_TO_VISION", "modelPlane", "boolean", "降级目标的 vision 声明(false ⇒ 带图任务丢降级不丢图)", { staticDefault: "false" }),
277
277
  r("MODEL_EXTRA_BODY", "modelPlane", "opaque", "生成参数直通 JSON(top_k/logit_bias 等;必须静态 —— 前缀缓存键序)", { danger: SEC }),
278
- r("MODEL_FIRST_TOKEN_TIMEOUT_MS", "modelPlane", "number", "首 token 超时(ms;0=关 —— 开流不吐字的唯一看门狗;自托管长 prefill 后端按此档留足)", { staticDefault: String(MODEL_FIRST_TOKEN_TIMEOUT_MS_DEFAULT) }),
278
+ r("MODEL_FIRST_TOKEN_TIMEOUT_MS", "modelPlane", "number", "首 token 超时(ms;0=关 —— 开流不吐字的唯一看门狗;自托管长 prefill 后端按此档留足)", { staticDefault: String(RESILIENCE_DEFAULTS.firstTokenTimeoutMs.value) }),
279
279
  r("MODEL_FREQUENCY_PENALTY", "modelPlane", "number", "frequency_penalty 直通(压过 EXTRA_BODY 同键)"),
280
280
  r("MODEL_GATEWAY_BASEURL", "modelPlane", "url", "openai 兼容网关 base URL(默认 localhost 占位 —— 公开分发不烤内网坐标)", { staticDefault: "http://127.0.0.1:8000/v1", configKey: "gatewayBaseUrl", center: cModels("models[].baseUrl") }),
281
281
  r("MODEL_GATEWAY_FALLBACK_URLS", "modelPlane", "url", "网关故障转移地址表(CSV)", { configKey: "gatewayFallbackUrls" }),
282
282
  r("MODEL_ID", "modelPlane", "string", "主模型 id(3.0.0 起无出厂缺省,缺 ⇒ 拒启指路)", { configKey: "model", resolve: (c) => c.model.id, center: cModels("models[].id") }),
283
- r("MODEL_IDLE_TIMEOUT_MS", "modelPlane", "number", "流中静默看门狗(ms;0=关)", { staticDefault: "300000" }),
283
+ r("MODEL_IDLE_TIMEOUT_MS", "modelPlane", "number", "流中静默看门狗(ms;0=关)", { staticDefault: String(RESILIENCE_DEFAULTS.idleTimeoutMs.value) }),
284
284
  r("MODEL_MAX_TOKENS", "modelPlane", "number", "主模型输出上限", { derivedDefaultNote: "缺席 ⇒ min(32000, contextWindow)", resolve: (c) => c.model.maxTokens ?? null, center: cModels("models[].maxTokens") }),
285
285
  r("MODEL_PRESENCE_PENALTY", "modelPlane", "number", "presence_penalty 直通"),
286
286
  r("MODEL_PROVIDER", "modelPlane", "string", "主模型 provider(路由选 brain)", { derivedDefaultNote: "缺席:ANTHROPIC base+cred 同在 ⇒ anthropic,否则 gateway", resolve: (c) => c.model.provider, center: cModels("models[].provider") }),
@@ -340,7 +340,7 @@ export const CONFIG_CATALOG = [
340
340
  r("QUESTION_TTL_MS", "approval", "number", "AskUserQuestion 无人应答释放窗(ms)", { derivedDefaultNote: "缺席 ⇒ 出厂窗 300000" }),
341
341
  r("RATE_LIMIT_RPM", "limitsHttp", "number", "计费提交每分钟限流(0=关)", { staticDefault: "0", danger: BILL, configKey: "rateLimitPerMin", center: cLimits("rateLimitPerMin") }),
342
342
  r("READ_DENY_BUILTIN_EXCLUDE", "orchestration", "csv", "按行名删内建 deny 条目(档位之后生效;绝不削任务显式 deny)", { danger: SEC, configKey: "readDenyBuiltinExclude", center: cRead("denyBuiltinExclude", "readDenyBuiltinExclude") }),
343
- r("READ_DENY_BUILTIN_TIERS", "orchestration", "csv", "内建 deny 表档位选择(闭集四档;none=显式空姿态)", { danger: SEC, configKey: "readDenyBuiltinTiers", center: cRead("denyBuiltinTiers", "readDenyBuiltinTiers") }),
343
+ r("READ_DENY_BUILTIN_TIERS", "orchestration", "csv", "内建 deny 表档位选择(闭集五档 credentials/shell-history/browser/wallet/agent-config;none=显式空姿态)", { danger: SEC, configKey: "readDenyBuiltinTiers", center: cRead("denyBuiltinTiers", "readDenyBuiltinTiers") }),
344
344
  r("READ_DENY_PATTERNS", "orchestration", "csv", "READ 面额外 deny pattern 表", { danger: SEC, configKey: "readDenyPatterns", center: cRead("denyPatterns", "readDenyPatterns") }),
345
345
  r("READ_FACE", "orchestration", "enum", "READ 容纳面档位(env 逐键恒赢 center —— 部署主权表达)", { enumValues: ["open", "roots"], danger: SEC, derivedDefaultNote: "缺席 ⇒ 按 posture 派生(S-167):单用户 turnkey(REQUIRE_PRINCIPAL 未设)铸 open;多租户不铸键、core 默认 roots。生效来源见 GET /v1/diagnostics/wiring 的 readFace.source 四词", configKey: "readFace", center: cRead("face", "readFace") }),
346
346
  r("REAP_INTERVAL_SEC", "limitsHttp", "number", "reaper 周期(秒)", { staticDefault: "60", configKey: "reapIntervalSec" }),
@@ -1,6 +1,6 @@
1
1
  import { resolveActiveTiers, SEALED_BOX_ALG } from "@sema-agent/settings-schema";
2
2
  import { sealedKeyPoison } from "../sealed-key.js";
3
- import { applyAutoCompactWindow, findUnmatchableToolNames, formatUnmatchableToolNames, parseAutonomy } from "../config.js";
3
+ import { applyAutoCompactWindow, buildModelCostKey, findUnmatchableToolNames, formatUnmatchableToolNames, parseAutonomy } from "../config.js";
4
4
  import { buildModelCompat, readModelCompat } from "../model-compat.js";
5
5
  import { validateCommandRules } from "../runtime-governance.js";
6
6
  import { projectCollabToWorkflows, registerCollabWorkflows } from "../capabilities/collab-workflows.js";
@@ -37,9 +37,7 @@ function toModel(m, envDefaults = {}, logger) {
37
37
  ...(charsPerTokenOf(m.charsPerToken) !== undefined || envDefaults.charsPerToken !== undefined
38
38
  ? { charsPerToken: charsPerTokenOf(m.charsPerToken) ?? envDefaults.charsPerToken }
39
39
  : {}),
40
- cost: m.cost
41
- ? { input: m.cost.input, output: m.cost.output, cacheRead: m.cost.cacheRead ?? 0, cacheWrite: m.cost.cacheWrite ?? 0 }
42
- : { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 },
40
+ ...buildModelCostKey(m.cost ? { input: m.cost.input, output: m.cost.output, cacheRead: m.cost.cacheRead ?? 0, cacheWrite: m.cost.cacheWrite ?? 0 } : undefined),
43
41
  params: { ...(m.tier ? { tier: m.tier } : {}), promptCacheFamily: m.provider === "anthropic" ? "input-excludes-cached" : "input-includes-cached" },
44
42
  ...(thinkingDefault ? { defaultThinking: thinkingDefault } : {}),
45
43
  ...(compat ? { compat } : {}),
@@ -45,5 +45,5 @@ export { mutateInPlace, applyEffective, type ApplyReport, applyRuntimeGates, app
45
45
  export { restartReasons, planeHasActiveTiers, modelPlaneChanged, type RestartSlice, type RestartSliceCtx, type RestartSignal, } from "./restart-signal.js";
46
46
  export { applyCenterSkills, resolveMcpServers, mcpForScenario, resolveA2aPeers, a2aForScenario } from "./skills-mcp.js";
47
47
  export { applyCenterReadFace, CENTER_READ_FACE_LIVE_KEYS, type CenterReadFaceApply, type CenterReadFaceReport, type ReadFaceBaseline } from "./read-face.js";
48
- export type { CenterSkillManifest, CenterMcpServer, CenterA2aPeer, CenterReadFace, EffectiveConfig, ExecutionRuling, SessionMirrorRuling, } from "./types.js";
48
+ export type { CenterSkillManifest, CenterMcpServer, CenterA2aPeer, CenterModel, CenterReadFace, EffectiveConfig, ExecutionRuling, SessionMirrorRuling, } from "./types.js";
49
49
  //# sourceMappingURL=facade.d.ts.map
@@ -19,8 +19,8 @@ function enabledOnly(rows) {
19
19
  return rows.filter((r) => r.enabled !== false);
20
20
  }
21
21
  function catalogEntryFingerprint(entry) {
22
- const { cost: _cost, quotaWeight: _quotaWeight, enabled: _enabled, ...route } = entry;
23
- return route;
22
+ const { cost, quotaWeight: _quotaWeight, enabled: _enabled, ...route } = entry;
23
+ return { ...route, costDeclared: cost !== undefined };
24
24
  }
25
25
  function planeEntriesFingerprint(rows) {
26
26
  const enabled = enabledOnly(rows);
@@ -4,6 +4,7 @@ import { skillContentHash } from "@sema-agent/settings-schema";
4
4
  import { CONTENT_ORIGIN_WORDS, describeBadContentOrigin, isContentOriginWord } from "../mcp-content-origin.js";
5
5
  import { fetchSkillContent } from "./http-client.js";
6
6
  import { SHA256_HEX_RE } from "../digest-form.js";
7
+ import { describeBadWireName, isUsableWireName } from "../wire-name-bound.js";
7
8
  export async function applyCenterSkills(baseline, manifest, baseUrl, token, logger, fetchImpl = fetch, diskCacheDir) {
8
9
  const { promises: fsp } = await import("node:fs");
9
10
  const { join: joinPath } = await import("node:path");
@@ -84,6 +85,13 @@ export function resolveMcpServers(mcp, logger) {
84
85
  for (const s of mcp.servers ?? []) {
85
86
  if (s.enabled === false)
86
87
  continue;
88
+ if (!isUsableWireName(s.name)) {
89
+ logger?.warn("sema_registry_mcp_name_unusable", {
90
+ server: describeBadWireName(s.name),
91
+ note: "server SKIPPED — an MCP server name is its identity (it becomes the tool namespace prefix) and core REFUSES an out-of-bound name at prepare, which would fail every task in this scenario. Shorten the name and republish; sibling servers are unaffected.",
92
+ });
93
+ continue;
94
+ }
87
95
  const allow = s.allowTools && s.allowTools.length > 0 ? { allowTools: s.allowTools } : {};
88
96
  const elicit = s.elicitation === true ? { elicitation: true } : {};
89
97
  let contentOrigin = {};
@@ -144,6 +152,13 @@ export function resolveA2aPeers(a2a, logger) {
144
152
  for (const p of a2a.peers ?? []) {
145
153
  if (p.enabled === false)
146
154
  continue;
155
+ if (!isUsableWireName(p.name)) {
156
+ logger?.warn("sema_registry_a2a_name_unusable", {
157
+ peer: describeBadWireName(p.name),
158
+ note: "peer SKIPPED — an A2A peer name is its identity (it lands as the roster's `origin.peer`) and core REFUSES an out-of-bound name at prepare, which would fail every task in this scenario. Shorten the name and republish; sibling peers are unaffected.",
159
+ });
160
+ continue;
161
+ }
147
162
  const headers = resolveRefs(p.headerRefs, p.name, "header", logger, "sema_registry_a2a_env_missing", "skipping this A2A peer — referenced env var is unset in this service");
148
163
  if (headers === null)
149
164
  continue;
@@ -56,23 +56,67 @@ export interface RetentionSweepConfig {
56
56
  */
57
57
  export declare const MEMORY_CONSOLIDATION_MIN_INTERVAL_SEC = 300;
58
58
  /**
59
- * S-202 —— **首 token 看门狗的缺省上限(ms)**,全仓单一属主。
59
+ * S-220 —— **`resilience` 块缺省的唯一属主**(S-202 的单源从一根扩到整族)。
60
60
  *
61
- * 「SSE 已开但迟迟不吐第一个 delta」是唯一一条 core 的 idle 计时器够不着的挂死路(它只在首 token
62
- * 之后才武装),所以这条看门狗是那条路上唯一的守卫。600s 而不是原来的 120s:自托管后端在长 prefill
63
- * (大上下文 / 冷缓存 / 排队)下,首字合理地就是要等几分钟——120s 会把一次**正常**的慢启动判成
64
- * `[network]` 失败去重试,而重试再交同一份 prompt 给同一个排队中的后端,只会更慢更贵。
61
+ * 这是一张**表**,不是六个常量:缺省长在三条腿上(解析器腿 `config.ts` 的 `numEnv`/`boolEnv` 缺省、
62
+ * 目录腿 `config-catalog.ts` 的 `staticDefault`、`brain.ts` 的 `DEFAULT_RESILIENCE` —— config 对象
63
+ * 没带 resilience 块时的同姿态兜底),而三条腿此前**各写一份字面量**。S-202 只把 `firstTokenTimeoutMs`
64
+ * 一根收成单源,于是剩下五根还在「改一处漏一处 = 目录对外宣告的缺省与真跑的不是同一代」这条病里 ——
65
+ * 按类修的形是把**整族**收进一张表,让三条腿都只许引用它,而不是再加五个同形常量(六个常量 = 六份
66
+ * 要各自记得引用的纪律;一张表 = 一条)。
65
67
  *
66
- * ⚠️ **`0 = 关`语义不变**,提高缺省不碰它;依赖 120s 快失败的部署把
67
- * `MODEL_FIRST_TOKEN_TIMEOUT_MS=120000` 显式写回即可(env 恒赢)。
68
+ * 每行带自己的 **env 键名** —— 它是这一行的**身份**,给消费门当锚(目录行查名、三腿对账)。
69
+ * ⚠️ 但 `config.ts` / `config-catalog.ts` 的读点仍写**字面** env 名,不写 `RESILIENCE_DEFAULTS.x.env`:
70
+ * D1 双向对账门(`test/config-catalog.test.ts` ⑦)是 AST 扫 `numEnv("NAME", …)` 的字面名来数「src/ 在读
71
+ * 哪些键」,改成间接引用会让这六个键在那道门眼里整体消失(实测:6 个幽灵键 + 动态读棘轮 27→33)——
72
+ * 拿一条真在岗的对账门去换一处写法上的统一是亏的。键名的同源由那道门自己咬:src/ 字面名 ⟺ 目录行名
73
+ * ⟺ 本表的 `env`(S-220 那一格按 `env` 查目录行,查不到当场红),三方互钉,没有静默漂移的空间。
74
+ * `satisfies Readonly<Record<keyof ServiceConfigFlat["resilience"], …>>` 双向咬:块上加字段而表里没行
75
+ * ⇒ tsc 红(加字段的人被迫当场回答「它的出厂缺省是多少、归哪个 env」);表里多一行块上没有的键同样红。
68
76
  *
69
- * 🔴 **常量在此、别处只许引用**:解析器腿(`config.ts` 的 `numEnv` 缺省)、目录腿
70
- * (`config-catalog.ts` 的 `staticDefault`)与 `brain.ts` 的 `DEFAULT_RESILIENCE`(config 对象没带
71
- * resilience 块时的同姿态兜底)此前各写一份 `120000` 字面量,改一处漏一处 = 对外宣告的缺省与真跑的
72
- * 不是同一代。`test/config-catalog.test.ts` 的 S-202 两格咬:一格钉三腿同值,一格扫全 `src/` 判
73
- * 「字面量」这一类写法为红(下一份镜像出生即红)。
77
+ * ⚠️ 语义面**一个数字都没动**:`0 = 关`(三个看门狗)与「熔断默认关」照旧,env 恒赢。
78
+ * 首 token 那一行的 600_000 由来(S-202 clay 裁 A):自托管后端在长 prefill(大上下文 / 冷缓存 /
79
+ * 排队)下首字合理地要等几分钟,120s 会把一次**正常**的慢启动判成 `[network]` 失败去重试,而重试
80
+ * 再交同一份 prompt 给同一个排队中的后端只会更慢更贵;依赖旧姿态的部署显式写回
81
+ * `MODEL_FIRST_TOKEN_TIMEOUT_MS=120000` 即可。
82
+ *
83
+ * 🔴 **常量在此、别处只许引用**:`test/config-catalog.test.ts` 的 S-220 两格咬 —— 一格按表逐行钉
84
+ * 三腿同值(目录 staticDefault / 解析器实算值 / {@link buildDefaultResilience}),一格扫全 `src/`
85
+ * 判「字面量」这一类写法为红(下一份镜像出生即红)。
86
+ */
87
+ export declare const RESILIENCE_DEFAULTS: {
88
+ readonly connectTimeoutMs: {
89
+ readonly env: "MODEL_CONNECT_TIMEOUT_MS";
90
+ readonly value: 30000;
91
+ };
92
+ readonly firstTokenTimeoutMs: {
93
+ readonly env: "MODEL_FIRST_TOKEN_TIMEOUT_MS";
94
+ readonly value: 600000;
95
+ };
96
+ readonly idleTimeoutMs: {
97
+ readonly env: "MODEL_IDLE_TIMEOUT_MS";
98
+ readonly value: 300000;
99
+ };
100
+ readonly circuitBreaker: {
101
+ readonly env: "MODEL_CIRCUIT_BREAKER";
102
+ readonly value: false;
103
+ };
104
+ readonly failureThreshold: {
105
+ readonly env: "MODEL_CB_FAILURE_THRESHOLD";
106
+ readonly value: 5;
107
+ };
108
+ readonly cooldownMs: {
109
+ readonly env: "MODEL_CB_COOLDOWN_MS";
110
+ readonly value: 30000;
111
+ };
112
+ };
113
+ /**
114
+ * S-220 —— 「config 对象没带 resilience 块」时的整块兜底,**逐字段派生自 {@link RESILIENCE_DEFAULTS}**。
115
+ *
116
+ * 返回类型把键集钉死(缺键 / 多键都是 tsc 红),所以这里剩下的唯一手写物是键名本身,值一个字面量都没有。
117
+ * 每次调用铸一只新对象(共享一只导出常量会被消费方就地改掉,那是另一条病)。
74
118
  */
75
- export declare const MODEL_FIRST_TOKEN_TIMEOUT_MS_DEFAULT = 600000;
119
+ export declare function buildDefaultResilience(): ServiceConfigFlat["resilience"];
76
120
  /**
77
121
  * [ref] D1/D2 —— consolidation driver 阀门的三根旋钮(语义与三问见
78
122
  * {@link ServiceConfigFlat.memoryConsolidationDriver})。纯数据。
@@ -1625,9 +1669,17 @@ export interface ServiceConfigFlat {
1625
1669
  pattern: string;
1626
1670
  caseSensitive: boolean;
1627
1671
  }[];
1628
- /** [ref] 件 G5(core 5.33.0 [ref],clay 裁定件):**内建** deny 表的档位选择(闭集四档:credentials /
1629
- * shell-history / browser / wallet)。`READ_DENY_BUILTIN_TIERS`=逗号分隔;`none`=显式空姿态(core 的
1630
- * `[]`:一档内建都不要);坏档名 / 空串**启动期响亮拒**(校验用 core 自己的 `resolveReadDenyBuiltins`)。
1672
+ /** [ref] 件 G5(core 5.33.0 [ref],clay 裁定件):**内建** deny 表的档位选择(闭集**五档**:credentials /
1673
+ * shell-history / browser / wallet / **agent-config**)。`READ_DENY_BUILTIN_TIERS`=逗号分隔;`none`=显式
1674
+ * 空姿态(core 的 `[]`:一档内建都不要);坏档名 / 空串**启动期响亮拒**(校验用 core 自己的
1675
+ * `resolveReadDenyBuiltins` —— 词表**单一属主在 core**,本仓零镜像:加档这天校验自动认,这段散文是
1676
+ * 唯一要跟手的东西)。
1677
+ * 🔴 **`agent-config`(core 7.14.0 [ref] C-e 新加的第五档)**:agent **自己的配置文件** —— 它们的 `env` /
1678
+ * server 段里明文放着 API key(壳的 `settings.json` env 块、项目 `.mcp.json` 的 server env、引擎宿主
1679
+ * data-root 下的 `.env`)。此前对这类文件的一次结构化读会把密钥**逐字**落进工具结果、从而落进会话记录。
1680
+ * 行名的是**文件**不是配置**目录**(`.sema`/`.claude` 底下还住着 agents/skills/hooks 与记忆库 —— 模型
1681
+ * 当作日常材料读写的东西);同族工具的 token **存储**(`.credentials.json`/`.codex/auth.json` …)归
1682
+ * `credentials` 档,不归本档 ⇒ 为排查 agent 配置而关掉本档的部署,凭证行照样拦着。
1631
1683
  * 缺席=不铸 ⇒ core 自默认(除 shell-history 外全开)。**仅 deployment 席**:TaskSpec 上没有同名键,
1632
1684
  * 任务/受治脚本无法据此放宽自己的部署(core 侧的设计,与本仓「部署级旋钮无条件施加」同向)。 */
1633
1685
  readDenyBuiltinTiers?: readonly string[];
@@ -1,4 +1,22 @@
1
1
  export const RETENTION_MODES = ["audit-only", "enforce"];
2
2
  export const MEMORY_CONSOLIDATION_MIN_INTERVAL_SEC = 300;
3
- export const MODEL_FIRST_TOKEN_TIMEOUT_MS_DEFAULT = 600_000;
3
+ export const RESILIENCE_DEFAULTS = {
4
+ connectTimeoutMs: { env: "MODEL_CONNECT_TIMEOUT_MS", value: 30_000 },
5
+ firstTokenTimeoutMs: { env: "MODEL_FIRST_TOKEN_TIMEOUT_MS", value: 600_000 },
6
+ idleTimeoutMs: { env: "MODEL_IDLE_TIMEOUT_MS", value: 300_000 },
7
+ circuitBreaker: { env: "MODEL_CIRCUIT_BREAKER", value: false },
8
+ failureThreshold: { env: "MODEL_CB_FAILURE_THRESHOLD", value: 5 },
9
+ cooldownMs: { env: "MODEL_CB_COOLDOWN_MS", value: 30_000 },
10
+ };
11
+ export function buildDefaultResilience() {
12
+ const d = RESILIENCE_DEFAULTS;
13
+ return {
14
+ connectTimeoutMs: d.connectTimeoutMs.value,
15
+ firstTokenTimeoutMs: d.firstTokenTimeoutMs.value,
16
+ idleTimeoutMs: d.idleTimeoutMs.value,
17
+ circuitBreaker: d.circuitBreaker.value,
18
+ failureThreshold: d.failureThreshold.value,
19
+ cooldownMs: d.cooldownMs.value,
20
+ };
21
+ }
4
22
  //# sourceMappingURL=config-types.js.map
package/dist/config.d.ts CHANGED
@@ -99,6 +99,48 @@ export declare function logConfigDiagnostics(logger: {
99
99
  info?: (event: string, fields?: Record<string, unknown>) => void;
100
100
  debug?: (event: string, fields?: Record<string, unknown>) => void;
101
101
  }): void;
102
+ /** B-089:`Model.cost` 的四项(per-1M USD)。类型从 core 的 `Model` 取,不在本仓重列一份。 */
103
+ type ModelCost = Model["cost"];
104
+ /**
105
+ * 🔴 B-089 —— `Model.cost` 这**一个键**的唯一铸点(可展开片段):有价目表 ⇒ 键在;没配 ⇒ **键不出**。
106
+ * 三个铸点(env 主槽 / env cheap 槽 / config-center 车道)共用它,所以「缺席怎么表达」在本仓只有一处答案。
107
+ *
108
+ * 为什么这里有一处 cast:`Model.cost` 在 core 7.13.0 的**类型面**上仍是必填
109
+ * (`dist/engine/llm/types.d.ts:803`),而它的**运行期**判据读的正是这个键在不在
110
+ * (`dist/core/pricing.js:15`,[ref] 的单一谓词)——「缺席」是 core 明文支持并依赖的运行期状态,
111
+ * 只是类型面还没跟上(core 也未把 `isModelPriced` 从 index 导出,本仓无法直接复用那只谓词)。
112
+ * 把这条唯一的类型让步收在这里,调用点一处不必再 cast。
113
+ *
114
+ * ⚠️ 连带纪律:既然运行期可缺席,**读** `Model.cost` 一律先判在场(`budget.ts` 的
115
+ * `if (m.cost)`、catalog 行的 `c.model.cost?.input`),别写 `model.cost.input` —— 类型面不会拦你。
116
+ * 终态是 core 把该键改成可选,届时本函数的 cast 与本段注一起删。
117
+ */
118
+ export declare function buildModelCostKey(cost: ModelCost | undefined): Pick<Model, "cost">;
119
+ /**
120
+ * 🔴 B-089 —— 「配了 $ 成本天花板,但目录里一只带价模型都没有」的判据 + 文案(纯函数,零副作用)。
121
+ *
122
+ * 两道天花板(`MAX_TASK_COST_USD` / `MAX_PRINCIPAL_COST_USD`)量的是 `costUsd`,而一只**未定价**的模型
123
+ * 算不出成本([ref] 之后它连 `costMicroUsd` 这个键都不发)—— 于是这两根旋钮在这台机器上**永不可能
124
+ * 触发**:运营方以为配了 $ 护栏,实际一道都没有,且没有任何一处会响。不拒启([ref] 只要求 loud ——
125
+ * 这是一台**本来就在服务**的机器的既有形,而且「先配上限、稍后配单价」是正当的上线顺序)。
126
+ *
127
+ * ⚠️ 位置即判据(codex r1 [medium] 真复现):**必须拿生效后的目录与限额来判,不能在 `loadConfig` 里判**。
128
+ * `loadConfig` 只见 env 车道,而 `main.ts` 在第一次 effective 拉取**之前**就把 boot 诊断排空了 ——
129
+ * 在那里判会同时犯两个方向的错:center 名册已配价、env 未配 ⇒ **假警**;env 已配价、center 名册撤了价
130
+ * (或天花板只由 center 的 limits 下发)⇒ **漏警**。故本函数只给判据,席位在两只入口各自的
131
+ * **采纳之后**(`main.ts` 的 `createConfigCenterRuntime` 之后 / `run-local.ts` 的 `applyEffective` 之后)。
132
+ *
133
+ * 判据 = **目录里没有任何一只模型带 `cost`**,与 `budget.buildPricing` 的填表判据同一个(它只为带 cost
134
+ * 的模型建行),而 core 的 `isModelPriced` 读的正是 `pricing[id] ?? model.cost` —— 两边同时缺席才是未定价。
135
+ * 本模块刻意不 import `budget.ts`(那边挂着 metrics/fleet 一串运行期依赖,config 是纯解析层)。
136
+ */
137
+ export declare function buildUnpricedCeilingNotice(models: Record<string, Model>, limits: {
138
+ maxTaskCostUsd: number;
139
+ maxPrincipalCostUsd: number;
140
+ }): {
141
+ event: string;
142
+ fields: Record<string, string>;
143
+ } | undefined;
102
144
  /** [ref] N8/N9 — the BOOLEAN member of this module's env-parser family (`env`/`env2`/`numEnv`/
103
145
  * `numEnvBounded`/`optFinitePositiveEnv`/`enumEnv`/`headerNameEnv`/`firstSetEnv`). Until now booleans were the
104
146
  * one type parsed by a hand-written literal comparison at every single site, and that is exactly why the same