@sema-agent/server 7.68.0 → 7.69.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 (64) hide show
  1. package/USAGE.md +19 -2
  2. package/dist/adoption/plan.js +11 -0
  3. package/dist/approval-card.d.ts +26 -0
  4. package/dist/approval-card.js +11 -0
  5. package/dist/approval.js +5 -4
  6. package/dist/boot/config-center.d.ts +6 -7
  7. package/dist/boot/config-center.js +31 -8
  8. package/dist/boot/leader.js +1 -1
  9. package/dist/boot/workflow-orchestration.d.ts +2 -0
  10. package/dist/boot/workflow-orchestration.js +10 -2
  11. package/dist/budget.js +1 -1
  12. package/dist/config-catalog.js +20 -1
  13. package/dist/config-center/apply-effective.d.ts +8 -1
  14. package/dist/config-center/apply-effective.js +23 -9
  15. package/dist/config-center/facade.d.ts +1 -1
  16. package/dist/config-center/facade.js +1 -1
  17. package/dist/config-center/read-face.d.ts +30 -1
  18. package/dist/config-center/read-face.js +33 -15
  19. package/dist/config-center/restart-signal.js +2 -1
  20. package/dist/config-center/types.d.ts +20 -5
  21. package/dist/config-provider.js +1 -0
  22. package/dist/config-types.d.ts +17 -2
  23. package/dist/config.d.ts +0 -21
  24. package/dist/config.js +19 -6
  25. package/dist/http/route-ctx.d.ts +39 -1
  26. package/dist/http/routes/approvals-assistant.js +1 -0
  27. package/dist/http/routes/capabilities.js +1 -0
  28. package/dist/http/routes/workflows.js +15 -7
  29. package/dist/http/server.d.ts +10 -2
  30. package/dist/http/server.js +70 -24
  31. package/dist/leader/wire.d.ts +9 -2
  32. package/dist/leader/wire.js +6 -6
  33. package/dist/main.js +6 -5
  34. package/dist/model-compat.d.ts +81 -0
  35. package/dist/model-compat.js +52 -0
  36. package/dist/observability/fail-open.d.ts +4 -0
  37. package/dist/observability/fail-open.js +4 -0
  38. package/dist/operator-ask.d.ts +46 -0
  39. package/dist/operator-ask.js +4 -0
  40. package/dist/orchestration/workflow-agent-session-index.d.ts +96 -0
  41. package/dist/orchestration/workflow-agent-session-index.js +146 -0
  42. package/dist/orchestration/workflow-journal-entry.d.ts +78 -0
  43. package/dist/orchestration/workflow-journal-entry.js +20 -0
  44. package/dist/orchestration/workflow-notify-journal.d.ts +43 -1
  45. package/dist/orchestration/workflow-notify-journal.js +43 -3
  46. package/dist/parked-decide.d.ts +115 -5
  47. package/dist/parked-decide.js +94 -26
  48. package/dist/plugins/pg-pool.js +8 -0
  49. package/dist/plugins/store-backend.d.ts +5 -0
  50. package/dist/plugins/store-backend.js +3 -1
  51. package/dist/plugins/tidb-pool.js +8 -0
  52. package/dist/plugins/workflow-journal-store-sql.js +10 -3
  53. package/dist/plugins/workflow-run-store-sql.d.ts +30 -0
  54. package/dist/plugins/workflow-run-store-sql.js +40 -0
  55. package/dist/router/route-orchestration.js +2 -1
  56. package/dist/run-local.js +1 -1
  57. package/dist/runs.js +2 -0
  58. package/dist/runtime-governance.js +5 -4
  59. package/dist/tool-approval.d.ts +20 -0
  60. package/dist/tool-approval.js +2 -1
  61. package/dist/trace/core-keyset-guard.d.ts +3 -3
  62. package/dist/trace/engine-notice-wire.d.ts +1 -1
  63. package/dist/trace/engine-notice-wire.js +1 -0
  64. package/package.json +3 -3
package/USAGE.md CHANGED
@@ -124,6 +124,19 @@ MODEL_DEGRADE_ON=rate_limit,breaker_open # 可选,反应式触发器子集;词
124
124
  `med,high` 此前静默窄成只剩 `high` 一档,而这根旋钮的全部用途就是宣告高档可用,静默落空 = 高档
125
125
  永远上不去。空串 / 纯分隔(`, `)仍与不设同义(不铸键,行为不变);anthropic 腿刻意不铸 compat
126
126
  (那条腿不吃 `reasoning_effort`),`GET /v1/config/catalog` 对它诚实回 `null`。
127
+ - 🔴 **`MODEL_THINKING_FORMAT`(7.69.0 新,同族第三根)**:这台网关按哪种拼法收「开/关思考」——
128
+ `openai|openrouter|deepseek|together|zai|qwen|qwen-chat-template` 七词闭集(上 core
129
+ `OpenAICompletionsCompat.thinkingFormat`),未知词同样 **boot 拒启并点名闭集**。缺席 ⇒ 不铸这一键
130
+ (core 按 baseUrl / 模型 id 自行推断,行为逐字不变)。**真需求**:vLLM / Qwen 类**缺省开思考**的网关,
131
+ 只有 `qwen-chat-template`(`chat_template_kwargs.enable_thinking=false`)关得掉,而 `MODEL_EXTRA_BODY`
132
+ 穿不过引擎的 OpenAI 保留键表 —— 不设它,这类部署每一轮都白烧一段思考且读数上看不出来。它与
133
+ `MODEL_REASONING_EFFORT_LEVELS` 铸进**同一个** `compat` 对象(两根旋钮同时设两键都在)。
134
+ - 🔴 **逐模型声明(目录腿):`models.models[].compat`**(settings-schema ≥1.10.0;7.69.0 消费)——
135
+ 同一张 `OpenAICompletionsCompat` 五键(`thinkingFormat` / `reasoningEffortLevels` / `maxTokensField` /
136
+ `requiresReasoningContentOnAssistantMessages` / `supportsReasoningEffort`)可以**按模型**写在
137
+ `config.d/models.json` 或配置控制面的目录条目上,逐键盖过上面两根 env 缺省(BL-8:没写的键仍继承 env)。
138
+ **坏形/坏词/未知键 ⇒ 丢掉这条 `compat` 声明、模型本体保留**,并打一条 `model_compat_dropped`
139
+ 点名 warn(引擎回落到自己的推断 = 今天的行为);一条写坏的声明**不会**让这只模型或整套目录消失。
127
140
  - 反应式降级 brain 包在**最外层**;fallback brain 用自己的凭据(decorator 清掉主模型的 per-call key 防外泄给别的 provider)。**坑**:`MODEL_DEGRADE_TO` 最好别和被限流的是同一网关/账号,否则反应式切过去照样撞同一个 rate_limit。
128
141
  - **定价怎么设**:`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)。
129
142
  - 成本计量**自动开**:从 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 降级)。
@@ -638,8 +651,12 @@ READ_DENY_PATTERNS='[{"pattern":"**/secrets/**"},{"pattern":".ssh","caseSensitiv
638
651
  ① **本机 env 显式恒赢**(逐键判)——env 是这台机器的部署主权,下发值不会静默盖掉它,被忽略的键留一条
639
652
  `sema_registry_read_face_env_wins` warn;② **坏值整域拒**——任何一个成员过不了引擎自己的校验器就整域不落、
640
653
  保留 env 前值 + `sema_registry_read_face_invalid` warn(半应用一份 READ 姿态比不应用更危险);
641
- ③ **restart-to-apply**——四键在 boot 期被引擎装配捕获,下发改动经 `/health` 的 `restart.reasons`
642
- 带 `read-face` 通告,由编排器重启兑现。当前生效档可从 `GET /v1/capabilities` 的 `readFace` 位读
654
+ ③ **一席热、三席重启**(server 7.69.0 起,引擎侧交付了 read-face 的活体座席):`face` 是**热换席** ——
655
+ 下发或撤回一个档位,下一拍 refresh(≤60s,或手动 `POST /v1/admin/config/refresh`)当场换进全部 Runner,
656
+ **不重启、也不再签 `read-face` 重启理由**(已在飞的任务保留它准备时的档位,新任务读新档);三个 deny 键
657
+ (`denyPatterns`/`denyBuiltinTiers`/`denyBuiltinExclude`)**仍是 boot-baked**(core 的可换席闭集只收了
658
+ `readFace` 一席),下发改动仍经 `/health` 的 `restart.reasons` 带 `read-face` 通告、由编排器重启兑现。
659
+ 当前生效档可从 `GET /v1/capabilities` 的 `readFace` 位读
643
660
  (`"open"`/`"roots"`/`null`=本部署未钉,引擎默认当家;deny 表内容属策略内容,**不上**能力面);
644
661
  **来源**(env / center / posture / engine-default)在 operator 面 `GET /v1/diagnostics/wiring` 的 `readFace.source`。
645
662
  ⚠️ 本地 `config.d/` 车道今天**接不上**这一域(registry-core 的可移植域表里没有它),只有
@@ -223,6 +223,17 @@ export const REBIND_LEGS = [
223
223
  residualKey: undefined,
224
224
  why: "resume 重放日志的 scope 守卫列 —— 与 run 行**同事务**(错配会让 load 守卫拒掉整条 resume)",
225
225
  },
226
+ {
227
+ leg: "workflow_agent_session#scope",
228
+ table: "workflow_agent_session",
229
+ kind: "bulk-rebind",
230
+ action: "bucket-rebind",
231
+ columns: ["scope"],
232
+ matchColumn: "scope",
233
+ encoding: "verbatim",
234
+ residualKey: undefined,
235
+ why: "S-185 workflow 出身 park 的 join 索引行的租户列(值 = 那条 run 的 `scope`,与 workflow_run/journal 同一个字节)——与它们**同族同事务**迁:不迁 ⇒ 索引行留在旧身份名下,而 run 行已迁,一次收编后人对这条 park 的批准结构上投递不出去",
236
+ },
226
237
  {
227
238
  leg: "workflow_resume_claim#scope",
228
239
  table: "workflow_resume_claim",
@@ -328,6 +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` = error | timeout | breaker_open),本仓**刻意不枚举**它:词表的单一
333
+ * 属主在 core,抄一份的形会在 core 加词那天把新词静默吞成缺席 —— 而丢的正是「这次不可用是新出现的那
334
+ * 一类」这条信息(与 `AskRequest.origin` 顶注同一条纪律)。判据只到「非空串」这一层形门。
335
+ * `.strict()` + 单成员必填:core 将来给这只对象加成员时本读面当场判假、**整键不铸**,新成员必须由人
336
+ * 处置过才上 wire;而一个**没有 cause** 的空壳等于说「分类器坏了但说不出为什么」,比不说更坏。 */
337
+ declare const ClassifierUnavailableSchema: z.ZodObject<{
338
+ cause: z.ZodString;
339
+ }, z.core.$strict>;
340
+ export type ClassifierUnavailable = z.infer<typeof ClassifierUnavailableSchema>;
341
+ /**
342
+ * `AskRequest.classifierUnavailable`(core 7.10.0 [ref])的**边界窄读** —— 活卡帧与 `card_json` 的唯一
343
+ * 铸造点(与 {@link readDenialLimitFallback} 同款分工),两面结构性同值。
344
+ *
345
+ * 🔴 **缺席不是断言**:缺席同时覆盖「分类器答上了」「这只 ask 没资格走分类器」「本部署没接分类器」
346
+ * 三形(core d.ts 逐字:read presence, never absence),消费端禁读成「分类器好着呢」。
347
+ * 🔴 **echo-only**:server 不据它做任何裁决 —— 熔断/超时的处置全在 core 的分类器站,据本键在本仓自铸
348
+ * 第二套回落判据就是同一语义面两个写者(源头修复纪律)。
349
+ */
350
+ export declare function readClassifierUnavailable(req: unknown): ClassifierUnavailable | undefined;
331
351
  /**
332
352
  * `AskRequest.denialLimitFallback`(core 7.4.0 [ref])的**边界窄读** —— 活卡帧与 `card_json` 的唯一铸造点
333
353
  * (与 {@link readRuleOffersAbsence} / {@link readProbeCause} 同款分工),两面结构性同值。
@@ -422,6 +442,9 @@ export declare const ApprovalCardSchema: z.ZodObject<{
422
442
  }>;
423
443
  autoDenyAfterMs: z.ZodNumber;
424
444
  }, z.core.$strict>>;
445
+ classifierUnavailable: z.ZodOptional<z.ZodObject<{
446
+ cause: z.ZodString;
447
+ }, z.core.$strict>>;
425
448
  probeCause: z.ZodOptional<z.ZodObject<{
426
449
  code: z.ZodString;
427
450
  roots: z.ZodObject<{
@@ -532,6 +555,9 @@ export declare const ApprovalCardEnvelopeSchema: z.ZodObject<{
532
555
  }>;
533
556
  autoDenyAfterMs: z.ZodNumber;
534
557
  }, z.core.$strict>>;
558
+ classifierUnavailable: z.ZodOptional<z.ZodObject<{
559
+ cause: z.ZodString;
560
+ }, z.core.$strict>>;
535
561
  probeCause: z.ZodOptional<z.ZodObject<{
536
562
  code: z.ZodString;
537
563
  roots: z.ZodObject<{
@@ -237,6 +237,14 @@ const DenialLimitFallbackSchema = z
237
237
  const _denialLimitFallbackConformance = [true, true];
238
238
  void _denialLimitFallbackConformance;
239
239
  const DenialLimitFallbackEnvelopeSchema = z.object({ denialLimitFallback: DenialLimitFallbackSchema.optional() });
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
+ }
240
248
  export function readDenialLimitFallback(req) {
241
249
  const parsed = DenialLimitFallbackEnvelopeSchema.safeParse(req);
242
250
  return parsed.success ? parsed.data.denialLimitFallback : undefined;
@@ -261,6 +269,7 @@ export const ApprovalCardSchema = z
261
269
  ruleOffers: z.array(RuleOfferSchema).max(MAX_RULE_OFFERS).optional(),
262
270
  ruleOffersAbsence: RuleOffersAbsenceSchema.optional(),
263
271
  denialLimitFallback: DenialLimitFallbackSchema.optional(),
272
+ classifierUnavailable: ClassifierUnavailableSchema.optional(),
264
273
  probeCause: ProbeCauseSchema.optional(),
265
274
  ruleEvidence: RuleEvidenceSchema.optional(),
266
275
  fromSubagent: z.literal(true).optional(),
@@ -303,6 +312,7 @@ export function buildApprovalCard(source, req, requiresRealApproval) {
303
312
  const ruleEvidence = readRuleEvidence(req);
304
313
  const ruleOffersAbsence = readRuleOffersAbsence(req);
305
314
  const denialLimitFallback = readDenialLimitFallback(req);
315
+ const classifierUnavailable = readClassifierUnavailable(req);
306
316
  const toolCallId = clip(source.toolCallId, MAX_IDENT);
307
317
  const sourceTaskId = clip(source.sourceTaskId, MAX_IDENT);
308
318
  const sourceAgentName = clip(source.sourceAgentName, MAX_AGENT_NAME);
@@ -325,6 +335,7 @@ export function buildApprovalCard(source, req, requiresRealApproval) {
325
335
  ...(source.ruleOffers !== undefined && source.ruleOffers.length > 0 ? { ruleOffers: source.ruleOffers.map(copyRuleOffer) } : {}),
326
336
  ...(ruleOffersAbsence !== undefined ? { ruleOffersAbsence } : {}),
327
337
  ...(denialLimitFallback !== undefined ? { denialLimitFallback } : {}),
338
+ ...(classifierUnavailable !== undefined ? { classifierUnavailable } : {}),
328
339
  ...(source.fromSubagent === true ? { fromSubagent: true } : {}),
329
340
  ...(sourceTaskId !== undefined ? { sourceTaskId } : {}),
330
341
  ...(sourceAgentName !== undefined ? { sourceAgentName } : {}),
package/dist/approval.js CHANGED
@@ -1,4 +1,5 @@
1
1
  import { isNamespacedCoveringRuleName, namespacedRuleNameCovers } from "@sema-agent/core";
2
+ import { operatorAsk } from "./operator-ask.js";
2
3
  import { isExplicitPosture } from "./posture-source.js";
3
4
  export function hasOperatorGateIntent(config) {
4
5
  return (config.approvalRequire.length > 0 ||
@@ -51,11 +52,11 @@ export function createDurableAskPolicy(opts) {
51
52
  if (deny(toolName))
52
53
  return { action: "deny", message: `tool "${req.toolName}" is denied by policy` };
53
54
  if (neverAuto(toolName))
54
- return { action: "ask", message: `human approval required to run "${req.toolName}"` };
55
+ return operatorAsk(`APPROVAL_NEVER_AUTO(${toolName})`, `human approval required to run "${req.toolName}"`);
55
56
  if (require(toolName)) {
56
57
  const remEarly = req.budget?.resourceRemainingMicroUsd;
57
58
  if (remEarly !== undefined && remEarly <= 0) {
58
- return { action: "ask", message: `durable resource budget exhausted — human approval required to run "${req.toolName}"` };
59
+ return operatorAsk("RESOURCE_SUSPEND budget exhausted", `durable resource budget exhausted — human approval required to run "${req.toolName}"`);
59
60
  }
60
61
  if (opts.exempt && toolName !== "AskUserQuestion") {
61
62
  return (async () => {
@@ -81,13 +82,13 @@ export function createDurableAskPolicy(opts) {
81
82
  function decideRequired(req, toolName) {
82
83
  const rem = req.budget?.resourceRemainingMicroUsd;
83
84
  if (rem !== undefined && rem <= 0) {
84
- return { action: "ask", message: `durable resource budget exhausted — human approval required to run "${req.toolName}"` };
85
+ return operatorAsk("RESOURCE_SUSPEND budget exhausted", `durable resource budget exhausted — human approval required to run "${req.toolName}"`);
85
86
  }
86
87
  if (toolName !== "AskUserQuestion" && budget > 0 && autoApproved < budget) {
87
88
  autoApproved++;
88
89
  return { action: "allow" };
89
90
  }
90
- return { action: "ask", message: `approval required to run "${req.toolName}"` };
91
+ return operatorAsk(`APPROVAL_REQUIRE(${toolName})`, `approval required to run "${req.toolName}"`);
91
92
  }
92
93
  }
93
94
  //# sourceMappingURL=approval.js.map
@@ -1,4 +1,4 @@
1
- import { CenterPromptSource, type Model } from "@sema-agent/core";
1
+ import { CenterPromptSource, type SwappableDeps } from "@sema-agent/core";
2
2
  import { buildPricing } from "../budget.js";
3
3
  import { type PromptsDomainFaces } from "../prompts-domain-validate.js";
4
4
  import { type Scenario, type ScenarioDeps, type ScenarioDetail } from "../capabilities/scenarios.js";
@@ -42,16 +42,15 @@ export interface ConfigCenterRuntime {
42
42
  /** 60s refresh cadence + boot-deferred 到货续接。
43
43
  * `limitSync`([ref] 批1)= 恒构造的两只限额座的换代口:commit 之后**零 await 内**推一把,与 pricing /
44
44
  * keyResolver 那两件派生重建同族(缺席 = 这条腿本部署没装配,例如 run-local)。
45
- * `swapRunnerModels`(core 5.49,[ref]①)= 模型面永久热的换装口:plane 变更候选在 commit 前喂给
46
- * 全部 boot Runner 原子换代;在场 ⇒ models-tiers defer/restart 臂退役,缺席 ⇒ 旧 defer 行为逐字保留。 */
45
+ * `swapRunnerDeps`(core 7.10.0 `Runner.swapDeps` 一扇门;5.49 起的 `swapModels` 口在 7.10.0 退役、
46
+ * 本参数同语义改名)= **可换席**的换装口:模型面 plane 变更候选在 commit 前喂给全部 boot Runner
47
+ * 原子换代(在场 ⇒ models-tiers defer/restart 臂退役,缺席 ⇒ 旧 defer 行为逐字保留);S-174 起
48
+ * read **face** 席也走同一只函数(见 refresh 拍里的 `applyCenterReadFace` 调用点)。 */
47
49
  startRefreshLoop(a: {
48
50
  runnerTierFrozen: boolean;
49
51
  pricing: ReturnType<typeof buildPricing>;
50
52
  limitSync?: LimitSync;
51
- swapRunnerModels?: (plane: {
52
- models: Record<string, Model>;
53
- tiers: Record<string, string>;
54
- }) => void;
53
+ swapRunnerDeps?: (next: SwappableDeps) => void;
55
54
  }): void;
56
55
  /** [ref]-2:停刷新环(hardShutdown 收尾链;幂等,未起环时 no-op)。 */
57
56
  stopRefreshLoop(): void;
@@ -328,7 +328,8 @@ export async function createConfigCenterRuntime(ctx) {
328
328
  }
329
329
  }
330
330
  }
331
- const readFaceEnvHeld = applyCenterReadFace(config, effective ?? { version: 0, updatedAt: "", models: { models: [], roles: {} } }, effective ? logger : undefined);
331
+ const readFaceBaseline = { ...(config.readFace !== undefined ? { readFace: config.readFace } : {}), readFaceSource: config.readFaceSource };
332
+ const readFaceEnvHeld = applyCenterReadFace(config, effective ?? { version: 0, updatedAt: "", models: { models: [], roles: {} } }, effective ? logger : undefined).envHeld;
332
333
  if (!modelReadyState.ready)
333
334
  logger.warn("model_roster_pending", {
334
335
  note: "CONFIG_REQUIRE_ROSTER=true and no center roster has landed yet — the env MODEL_ID is treated as a PLACEHOLDER, so billable submissions 503 (and /health reports ready:false) until the first effective pull lands ≥1 enabled model. Unset the knob if this worker's env model is authoritative.",
@@ -341,6 +342,26 @@ export async function createConfigCenterRuntime(ctx) {
341
342
  ...(readFaceEnvHeld.length > 0 ? { readFaceEnvHeld } : {}),
342
343
  ...(mcpRevocationWired && mcpRevocations.hasBaseline() ? { mcpRevocationLive: true } : {}),
343
344
  });
345
+ const applyLiveReadFace = (eff, swap, phase) => {
346
+ const before = config.readFace;
347
+ const beforeSource = config.readFaceSource;
348
+ const { faceChanged } = applyCenterReadFace(config, eff, logger, { phase: "live", baseline: readFaceBaseline });
349
+ if (!faceChanged)
350
+ return;
351
+ if (swap === undefined) {
352
+ logger.warn("read_face_swap_unwired", { face: config.readFace ?? null, phase, note: "the center's read FACE changed but no Runner swap door was wired — the value landed in config while the engine keeps the seat it booted with" });
353
+ return;
354
+ }
355
+ try {
356
+ swap({ readFace: config.readFace });
357
+ logger.info("read_face_swapped", { from: before ?? null, to: config.readFace ?? null, phase, note: "hot seat (core swapDeps) — legs that already prepared keep the face they prepared under" });
358
+ }
359
+ catch (err) {
360
+ config.readFace = before;
361
+ config.readFaceSource = beforeSource;
362
+ throw err;
363
+ }
364
+ };
344
365
  let ccTimer;
345
366
  return {
346
367
  providerKind: configProvider?.kind,
@@ -489,7 +510,7 @@ export async function createConfigCenterRuntime(ctx) {
489
510
  }
490
511
  },
491
512
  startRefreshLoop(a) {
492
- const { runnerTierFrozen, pricing, limitSync, swapRunnerModels } = a;
513
+ const { runnerTierFrozen, pricing, limitSync, swapRunnerDeps } = a;
493
514
  if (configProvider) {
494
515
  const ccRef = config.configCenter;
495
516
  let refreshInFlight = false;
@@ -555,7 +576,7 @@ export async function createConfigCenterRuntime(ctx) {
555
576
  }
556
577
  else {
557
578
  const planeChanged = modelPlaneChanged(appliedPlaneEff, r.effective);
558
- const planeDeferred = swapRunnerModels === undefined && (runnerTierFrozen || planeHasActiveTiers(r.effective)) && planeChanged;
579
+ const planeDeferred = swapRunnerDeps === undefined && (runnerTierFrozen || planeHasActiveTiers(r.effective)) && planeChanged;
559
580
  let applyReport;
560
581
  const committed = applyEffective(config, r.effective, logger, {
561
582
  teamsOnly: true,
@@ -566,7 +587,7 @@ export async function createConfigCenterRuntime(ctx) {
566
587
  applyLedger.recordApply(rep);
567
588
  },
568
589
  ...(planeDeferred ? { deferModelPlane: true } : {}),
569
- ...(swapRunnerModels !== undefined && planeChanged ? { swapPlane: swapRunnerModels } : {}),
590
+ ...(swapRunnerDeps !== undefined && planeChanged ? { swapPlane: swapRunnerDeps } : {}),
570
591
  });
571
592
  if (!committed)
572
593
  return "ok";
@@ -574,6 +595,7 @@ export async function createConfigCenterRuntime(ctx) {
574
595
  keyResolver = createKeyResolver(config.modelApiKeyEnv, process.env, config.modelApiKeys);
575
596
  limitSync?.sync();
576
597
  mcpRevocations.applyEnabledEntries(enabledMcpEntries(r.effective.mcp));
598
+ applyLiveReadFace(r.effective, swapRunnerDeps, "refresh");
577
599
  applyLedger.applied(tickGen, r.effective.version);
578
600
  if (planeDeferred) {
579
601
  logger.warn("models_tiers_plane_deferred", { version: r.effective.version, note: "tier-frozen Runner: the changed model plane (models/roles/tiers/default) is NOT hot-applied — admission stays on the Runner's generation; restart applies the new plane (models-tiers restart signal rides /health)" });
@@ -596,7 +618,7 @@ export async function createConfigCenterRuntime(ctx) {
596
618
  }
597
619
  const lkgPersisted = await persistLkgDurable(r.effective, r.etag);
598
620
  const reasons = restartReasons(effective, r.effective, restartCtx());
599
- if (swapRunnerModels !== undefined) {
621
+ if (swapRunnerDeps !== undefined) {
600
622
  const mi = reasons.indexOf("models-tiers");
601
623
  if (mi >= 0)
602
624
  reasons.splice(mi, 1);
@@ -701,7 +723,7 @@ export async function createConfigCenterRuntime(ctx) {
701
723
  return;
702
724
  }
703
725
  const planeChangedLate = modelPlaneChanged(appliedPlaneEff, r.effective);
704
- const planeDeferredLate = swapRunnerModels === undefined && (runnerTierFrozen || planeHasActiveTiers(r.effective)) && planeChangedLate;
726
+ const planeDeferredLate = swapRunnerDeps === undefined && (runnerTierFrozen || planeHasActiveTiers(r.effective)) && planeChangedLate;
705
727
  let applyReportLate;
706
728
  const committedLate = applyEffective(config, r.effective, logger, {
707
729
  teamsOnly: true,
@@ -712,13 +734,14 @@ export async function createConfigCenterRuntime(ctx) {
712
734
  applyLedger.recordApply(rep);
713
735
  },
714
736
  ...(planeDeferredLate ? { deferModelPlane: true } : {}),
715
- ...(swapRunnerModels !== undefined && planeChangedLate ? { swapPlane: swapRunnerModels } : {}),
737
+ ...(swapRunnerDeps !== undefined && planeChangedLate ? { swapPlane: swapRunnerDeps } : {}),
716
738
  });
717
739
  if (!committedLate)
718
740
  return;
719
741
  mutateInPlace(pricing, buildPricing(config.models));
720
742
  keyResolver = createKeyResolver(config.modelApiKeyEnv, process.env, config.modelApiKeys);
721
743
  limitSync?.sync();
744
+ applyLiveReadFace(r.effective, swapRunnerDeps, "boot-deferred");
722
745
  applyLedger.applied(lateGen, r.effective.version);
723
746
  if (planeDeferredLate)
724
747
  logger.warn("models_tiers_plane_deferred", { version: r.effective.version, note: "tier-frozen Runner (env tiers): the late-boot center model plane is NOT hot-applied — restart applies it" });
@@ -743,7 +766,7 @@ export async function createConfigCenterRuntime(ctx) {
743
766
  ccEtag = r.etag;
744
767
  const lkgPersistedLate = await persistLkgDurable(r.effective, r.etag);
745
768
  const reasons = restartReasons(undefined, r.effective, restartCtx());
746
- if (swapRunnerModels !== undefined) {
769
+ if (swapRunnerDeps !== undefined) {
747
770
  const miLate = reasons.indexOf("models-tiers");
748
771
  if (miLate >= 0)
749
772
  reasons.splice(miLate, 1);
@@ -50,7 +50,7 @@ export function createLeaderFace(ctx) {
50
50
  lockedConfig: governanceSeams.lockedConfig,
51
51
  retentionPolicy: governanceSeams.retentionPolicy,
52
52
  },
53
- deploymentPosture: buildDeploymentPostureSeats(config),
53
+ deploymentPosture: () => buildDeploymentPostureSeats(config),
54
54
  ...(ctx.tracer ? { tracer: ctx.tracer } : {}),
55
55
  ...(config.usageWindows && ctx.usageWindowStore
56
56
  ? { usageWindows: config.usageWindows, usageWindowStore: ctx.usageWindowStore }
@@ -14,6 +14,7 @@
14
14
  import { type WorkflowRun, type WorkflowRunStore } from "@sema-agent/core";
15
15
  import type { ServiceConfig } from "../config.js";
16
16
  import { FleetEventBus } from "../fleet/fleet-bus.js";
17
+ import { type WorkflowAgentSessionIndex } from "../orchestration/workflow-agent-session-index.js";
17
18
  import type { Logger } from "../observability/logger.js";
18
19
  import type { Metrics } from "../observability/metrics.js";
19
20
  import { type WorkflowCompletionInbox } from "../orchestration/workflow-completion-inbox.js";
@@ -47,5 +48,6 @@ export declare function createWorkflowOrchestration(ctx: WorkflowOrchestrationCt
47
48
  finalizeStartedBeforeMs?: number;
48
49
  publishTerminalFleetRow?: (id: string, run: WorkflowRun) => void;
49
50
  };
51
+ workflowAgentSessionIndex: WorkflowAgentSessionIndex | undefined;
50
52
  };
51
53
  //# sourceMappingURL=workflow-orchestration.d.ts.map
@@ -1,6 +1,7 @@
1
1
  import { FileWorkflowRunStore, InMemoryWorkflowRunStore } from "@sema-agent/core";
2
2
  import { join } from "node:path";
3
3
  import { FleetEventBus } from "../fleet/fleet-bus.js";
4
+ import { LocalWorkflowAgentSessionIndex } from "../orchestration/workflow-agent-session-index.js";
4
5
  import { FileWorkflowCompletionInbox, InMemoryWorkflowCompletionInbox, resolveCompletionRoute } from "../orchestration/workflow-completion-inbox.js";
5
6
  import { FileWorkflowNotifyJournalStore, JournalingWorkflowRunStore, WorkflowNotifyGate } from "../orchestration/workflow-notify-journal.js";
6
7
  import { SubagentSteerRegistry } from "../orchestration/subagent-steer.js";
@@ -54,8 +55,15 @@ export function createWorkflowOrchestration(ctx) {
54
55
  })
55
56
  : undefined;
56
57
  const fleetBus = new FleetEventBus();
58
+ const workflowAgentSessionIndex = config.selfOrchestrationEnabled
59
+ ? config.workflowRunStoreBackend === "memory"
60
+ ? new LocalWorkflowAgentSessionIndex()
61
+ : (sqlWorkflowRunStore
62
+ ? backend.workflowAgentSessionIndex()
63
+ : new LocalWorkflowAgentSessionIndex(join(config.localDataRoot ?? localRoot, "workflows"), undefined, (msg, meta) => logger.warn(msg, meta)))
64
+ : undefined;
57
65
  const workflowRunStore = baseWorkflowRunStore
58
- ? new JournalingWorkflowRunStore(baseWorkflowRunStore, workflowNotifyGate, fleetBus)
66
+ ? new JournalingWorkflowRunStore(baseWorkflowRunStore, workflowNotifyGate, fleetBus, workflowAgentSessionIndex)
59
67
  : undefined;
60
68
  const replicaLocalNotifyJournal = workflowNotifyJournal instanceof FileWorkflowNotifyJournalStore;
61
69
  const workflowRecoverOpts = {
@@ -70,7 +78,7 @@ export function createWorkflowOrchestration(ctx) {
70
78
  return {
71
79
  sqlWorkflowRunStore, workflowNotifyJournal, workflowCompletionInbox, deliverWorkflowCompletion,
72
80
  workflowNotifyGate, fleetBus, workflowRunStore, workflowJournalStore, outcomeSink,
73
- workflowAgentRegistry, subagentSteerRegistry, workflowRecoverOpts,
81
+ workflowAgentRegistry, subagentSteerRegistry, workflowRecoverOpts, workflowAgentSessionIndex,
74
82
  };
75
83
  }
76
84
  //# sourceMappingURL=workflow-orchestration.js.map
package/dist/budget.js CHANGED
@@ -119,7 +119,7 @@ export function createTracer(metrics, costQuota, modelUsage, fleetUsage, fleetLe
119
119
  else if (e.kind.startsWith("compaction.")) {
120
120
  metrics.inc("compaction_events_total", { outcome: e.kind.slice("compaction.".length), trigger: e.trigger ?? "none" });
121
121
  }
122
- else if (e.kind === "permission.persisted_rule_allowed" || e.kind === "permission.rule_store_unreadable") {
122
+ else if (e.kind === "permission.persisted_rule_allowed" || e.kind === "permission.rule_store_unreadable" || e.kind === "permission.read_only_allowed") {
123
123
  metrics.inc("permission_rule_events_total", { event: e.kind.slice("permission.".length) });
124
124
  }
125
125
  };
@@ -1,4 +1,6 @@
1
1
  import { CROSS_SESSION_INBOUND_SETTINGS, CROSS_SESSION_DIALOG_EXPIRY_VALUES } from "@sema-agent/core";
2
+ import { THINKING_FORMATS } from "./model-compat.js";
3
+ import { CENTER_READ_FACE_LIVE_KEYS } from "./config-center/read-face.js";
2
4
  export const CATALOG_VALUE_TYPES = ["boolean", "number", "string", "enum", "csv", "json", "path", "url", "secret", "opaque"];
3
5
  export const CATALOG_TIMINGS = ["hot", "restart", "deploy", "inert"];
4
6
  export const CATALOG_DANGER_AXES = ["security", "billing", "data"];
@@ -24,7 +26,14 @@ const cLimits = (path, over = {}) => ({
24
26
  });
25
27
  const cGov = (path, over = {}) => ({ domain: "governance", path, centerKey: path, precedence: "center-wins", timing: "hot", ...over });
26
28
  const cModels = (path) => ({ domain: "models", path, centerKey: "models", precedence: "center-wins", timing: "models-plane" });
27
- const cRead = (path, centerKey) => ({ domain: "readFace", path, centerKey, precedence: "env-wins", timing: "restart", restartSlice: "read-face" });
29
+ const cRead = (path, centerKey, over = {}) => ({
30
+ domain: "readFace",
31
+ path,
32
+ centerKey,
33
+ precedence: "env-wins",
34
+ ...(CENTER_READ_FACE_LIVE_KEYS.has(path) ? { timing: "hot" } : { timing: "restart", restartSlice: "read-face" }),
35
+ ...over,
36
+ });
28
37
  const SEC = ["security"];
29
38
  const BILL = ["billing"];
30
39
  const DATA = ["data"];
@@ -284,6 +293,16 @@ export const CONFIG_CATALOG = [
284
293
  },
285
294
  }),
286
295
  r("MODEL_ROUTER_ID", "modelPlane", "string", "leader 路由分类模型覆写", { derivedDefaultNote: "缺席 ⇒ MODEL_CHEAP_ID,再退 default", danger: BILL }),
296
+ r("MODEL_THINKING_FORMAT", "modelPlane", "enum", "本网关收「开/关思考」的拼法(上 core OpenAICompletionsCompat.thinkingFormat;缺省开思考的 vLLM/Qwen 网关只有 qwen-chat-template 关得掉;anthropic 腿不铸此键;未知词拒启)", {
297
+ enumValues: [...THINKING_FORMATS],
298
+ derivedDefaultNote: "缺席 ⇒ 不铸此键,core 按 baseUrl/模型 id 自行推断(默认 openai 的 reasoning_effort)",
299
+ resolve: (c) => {
300
+ const compat = c.model.compat;
301
+ if (compat === undefined || !("thinkingFormat" in compat))
302
+ return null;
303
+ return compat.thinkingFormat ?? null;
304
+ },
305
+ }),
287
306
  r("MODEL_VERIFIER", "modelPlane", "string", "verify 门的异构 verifier(须命中目录;typo 回 default 并 boot warn=自评自批)", { derivedDefaultNote: "缺席/不在目录 ⇒ default(=implementer,去相关性失效警告)" }),
288
307
  r("MODEL_VISION", "modelPlane", "boolean", "主模型 vision 声明(false 让 model_no_vision 预检真拦)", { staticDefault: "true", resolve: (c) => (Array.isArray(c.model.input) ? c.model.input.includes("image") : true), center: cModels("models[].vision") }),
289
308
  r("MYSQL_DATABASE", "store", "string", "MySQL 协议库名(选中该引擎时必填)", { danger: DATA }),
@@ -1,3 +1,10 @@
1
+ /**
2
+ * EffectiveConfig application — mutate the boot `ServiceConfig` OVER with a pulled `EffectiveConfig`
3
+ * (models/roles/tiers/projects `applyEffective`, the boot-only approval gate + runtime limit residue
4
+ * `applyRuntimeGates`, the per-request-hot governance baton `applyRuntimeHot`) plus their shared helpers
5
+ * (`toModel`, `mutateInPlace`, `resolveDefaultModelName`, `logEffectiveDiff`). Split out of `facade.ts`
6
+ * ([ref] A13, internal-lossless — the facade re-exports every symbol below unchanged).
7
+ */
1
8
  import type { Model } from "@sema-agent/core";
2
9
  import type { ServiceConfig } from "../config-types.js";
3
10
  import { type SealedKeyOpener } from "../sealed-key.js";
@@ -23,7 +30,7 @@ export declare function mutateInPlace<V>(target: Record<string, V>, source: Reco
23
30
  * 🔴 只活在内存判定面:不进 wire(`/v1/models` 只发布尔)、不进日志(warn 打的是原始坏值的类型标记)。
24
31
  */
25
32
  export declare const UNPARSABLE_AT_MODEL_ALLOWLIST_ENTRY = "\0<unparsable-at-model-allowlist-entry>";
26
- /** 返回值=是否 COMMIT(false 两臂:[ref]③ 世代序 CAS 拒绝 / 5.49 swapPlane 换装拒绝——两臂都在
33
+ /** 返回值=是否 COMMIT(false 两臂:[ref]③ 世代序 CAS 拒绝 / 7.10.0 swapPlane 换装拒绝——两臂都在
27
34
  * commit 之前,活配置零触碰)。caller 收到 false 必须把**同一世代的旁路消费**(prompts 采用/pricing/
28
35
  * keyResolver/etag 推进/LKG 落盘)一并跳过——否则 applyEffective 拒了主面、旁路却半应用同一个被拒
29
36
  * 世代,混合世代从侧门回来。首次 apply(live 未登记)恒 true。 */
@@ -1,16 +1,29 @@
1
- import { isThinkingLevel } from "@sema-agent/core";
2
1
  import { resolveActiveTiers, SEALED_BOX_ALG } from "@sema-agent/settings-schema";
3
2
  import { sealedKeyPoison } from "../sealed-key.js";
4
3
  import { applyAutoCompactWindow, findUnmatchableToolNames, formatUnmatchableToolNames, parseAutonomy } from "../config.js";
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";
7
7
  import { stageLimits } from "./stage-limits.js";
8
8
  import { BATCH1_LIMIT_KEYS } from "./hot-keys-registry.js";
9
- const asModelThinking = (v) => (v && isThinkingLevel(v) && v !== "off" ? v : undefined);
10
9
  const charsPerTokenOf = (v) => (typeof v === "number" && Number.isFinite(v) && v > 0 ? v : undefined);
11
- function toModel(m, envDefaults = {}) {
12
- const thinkingDefault = asModelThinking(m.defaultThinking) ?? envDefaults.defaultThinking;
13
- const effortLevels = (m.reasoningEffortLevels ? m.reasoningEffortLevels.map(asModelThinking).filter((x) => x !== undefined) : undefined) ?? envDefaults.reasoningEffortLevels ?? [];
10
+ function envDerivedModel(config) {
11
+ const m = config.envModel;
12
+ if (m === undefined) {
13
+ throw new Error("config.envModel is missing — it is the BL-8 inheritance baseline (the boot env-derived model) and is REQUIRED on ServiceConfig. " +
14
+ "A hand-rolled `as ServiceConfig` fixture must set it to the same object as `model`; there is deliberately no fallback to `config.model` (that fallback IS the bug this seat removes).");
15
+ }
16
+ return m;
17
+ }
18
+ function toModel(m, envDefaults = {}, logger) {
19
+ const thinkingDefault = envDefaults.defaultThinking;
20
+ const declared = readModelCompat(m.compat);
21
+ if (!declared.ok)
22
+ logger?.warn("model_compat_dropped", { model: m.name, issue: declared.issue, hint: "the per-model wire-compat declaration was dropped whole — the engine falls back to its own inference (today's behaviour); fix the entry to make it live" });
23
+ else if (declared.compat !== undefined && m.provider === "anthropic") {
24
+ logger?.warn("model_compat_dropped", { model: m.name, issue: "declared on an anthropic model", hint: "`compat` mirrors OpenAICompletionsCompat — the Anthropic brain reads AnthropicMessagesCompat and never sees these keys; the declaration is dropped" });
25
+ }
26
+ const compat = m.provider === "anthropic" ? undefined : buildModelCompat(declared.ok ? declared.compat : undefined, envDefaults.compat);
14
27
  const model = {
15
28
  id: m.id,
16
29
  name: m.name,
@@ -29,7 +42,7 @@ function toModel(m, envDefaults = {}) {
29
42
  : { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 },
30
43
  params: { ...(m.tier ? { tier: m.tier } : {}), promptCacheFamily: m.provider === "anthropic" ? "input-excludes-cached" : "input-includes-cached" },
31
44
  ...(thinkingDefault ? { defaultThinking: thinkingDefault } : {}),
32
- ...(effortLevels.length > 0 && m.provider !== "anthropic" ? { compat: { reasoningEffortLevels: effortLevels } } : {}),
45
+ ...(compat ? { compat } : {}),
33
46
  ...(m.extraBody ? { extraBody: m.extraBody } : {}),
34
47
  ...(Array.isArray(m.promptGuidance) && m.promptGuidance.length > 0 ? { promptGuidance: m.promptGuidance.map(String) } : {}),
35
48
  };
@@ -97,7 +110,7 @@ export function applyEffective(config, eff, logger, opts = {}) {
97
110
  logger?.warn("sema_registry_model_plane_swap_refused", {
98
111
  version: staged.version,
99
112
  error: err instanceof Error ? err.message : String(err),
100
- note: "the Runner refused the candidate model plane (swapModels validates atomically — the current generation stays in force untouched); candidate refused whole, re-judged next poll (/health shows target≠applied while it persists)",
113
+ note: "the Runner refused the candidate model plane (swapDeps screens the whole candidate atomically — the current generation stays in force untouched); candidate refused whole, re-judged next poll (/health shows target≠applied while it persists)",
101
114
  });
102
115
  return false;
103
116
  }
@@ -174,7 +187,8 @@ function stageEffective(config, eff, logger, opts) {
174
187
  const models = {};
175
188
  const modelApiKeyEnv = {};
176
189
  const modelApiKeys = {};
177
- const envDefaults = { maxTokens: config.model.maxTokens, contextWindow: config.model.contextWindow, reasoning: config.model.reasoning, charsPerToken: config.model.charsPerToken, defaultThinking: config.model.defaultThinking, reasoningEffortLevels: config.model.compat?.reasoningEffortLevels };
190
+ const envModel = envDerivedModel(config);
191
+ const envDefaults = { maxTokens: envModel.maxTokens, contextWindow: envModel.contextWindow, reasoning: envModel.reasoning, charsPerToken: envModel.charsPerToken, defaultThinking: envModel.defaultThinking, compat: envModel.compat };
178
192
  for (const m of enabled) {
179
193
  const rawBaseUrl = m.baseUrl;
180
194
  let safeUrl = rawBaseUrl;
@@ -194,7 +208,7 @@ function stageEffective(config, eff, logger, opts) {
194
208
  safeUrl = undefined;
195
209
  }
196
210
  }
197
- models[m.name] = toModel(safeUrl === rawBaseUrl ? m : { ...m, baseUrl: safeUrl }, envDefaults);
211
+ models[m.name] = toModel(safeUrl === rawBaseUrl ? m : { ...m, baseUrl: safeUrl }, envDefaults, logger);
198
212
  if (m.sealedApiKey) {
199
213
  const s = m.sealedApiKey;
200
214
  if (s.alg !== SEALED_BOX_ALG) {
@@ -44,6 +44,6 @@ export { fetchEffective, fetchPrincipalCaps, fetchPrincipalOrgMemory, ConfigCent
44
44
  export { mutateInPlace, applyEffective, type ApplyReport, applyRuntimeGates, applyRuntimeHot, resolveDefaultModelName, logEffectiveDiff, } from "./apply-effective.js";
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
- export { applyCenterReadFace } from "./read-face.js";
47
+ export { applyCenterReadFace, CENTER_READ_FACE_LIVE_KEYS, type CenterReadFaceApply, type CenterReadFaceReport, type ReadFaceBaseline } from "./read-face.js";
48
48
  export type { CenterSkillManifest, CenterMcpServer, CenterA2aPeer, CenterReadFace, EffectiveConfig, ExecutionRuling, SessionMirrorRuling, } from "./types.js";
49
49
  //# sourceMappingURL=facade.d.ts.map
@@ -2,5 +2,5 @@ export { fetchEffective, fetchPrincipalCaps, fetchPrincipalOrgMemory, ConfigCent
2
2
  export { mutateInPlace, applyEffective, applyRuntimeGates, applyRuntimeHot, resolveDefaultModelName, logEffectiveDiff, } from "./apply-effective.js";
3
3
  export { restartReasons, planeHasActiveTiers, modelPlaneChanged, } from "./restart-signal.js";
4
4
  export { applyCenterSkills, resolveMcpServers, mcpForScenario, resolveA2aPeers, a2aForScenario } from "./skills-mcp.js";
5
- export { applyCenterReadFace } from "./read-face.js";
5
+ export { applyCenterReadFace, CENTER_READ_FACE_LIVE_KEYS } from "./read-face.js";
6
6
  //# sourceMappingURL=facade.js.map
@@ -1,6 +1,35 @@
1
1
  import type { ServiceConfig } from "../config-types.js";
2
+ import type { ReadFaceSource } from "../read-face-posture.js";
2
3
  import type { Logger } from "../observability/logger.js";
3
4
  import type { CenterReadFace, EffectiveConfig } from "./types.js";
5
+ /**
6
+ * S-174 —— 哪些 center 键是**热换席**。判据**不是本仓的意见**:`KEY_MAP` 的 `cfgKey` 落在 core 的闭集
7
+ * `SWAPPABLE_DEP_SEATS` 里(经 `isSwappableDepSeat` 亲问)⇒ 这个键 refresh 期可换;否则它是 boot-baked
8
+ * 座,只有重启能换。core 加/删可换席 ⇒ 本集合与 `read-face` restart 片的指纹**同时**跟着走,不留第二份
9
+ * 手抄表(restart-signal.ts 读的就是这一只)。
10
+ */
11
+ export declare const CENTER_READ_FACE_LIVE_KEYS: ReadonlySet<keyof CenterReadFace>;
12
+ /** 本进程的**前 center** READ face 姿态(env / posture / 引擎默认)。`live` 相必带 —— 一次发布**撤回**
13
+ * `face` 键时,活席要还原到它,而不是把上一份发布的档位无限期latch 住(那是这条轴上的 fail-OPEN 方向:
14
+ * 组织把 `open` 撤回之后,worker 还在 open)。boot 相不需要:那一刻 `config` 上的值就是基线。 */
15
+ export interface ReadFaceBaseline {
16
+ readonly readFace?: "open" | "roots";
17
+ readonly readFaceSource: ReadFaceSource;
18
+ }
19
+ /** 本次调用落在哪一相(见文件头注「一席热、三席重启」)。判别联合而不是可选字段:`baseline` 只在
20
+ * `live` 相有意义,写成可选就等于允许一次「没有基线的 live 调用」——那正是撤回臂会静默失效的形。 */
21
+ export type CenterReadFaceApply = {
22
+ readonly phase: "boot";
23
+ } | {
24
+ readonly phase: "live";
25
+ readonly baseline: ReadFaceBaseline;
26
+ };
27
+ /** 调用回执。`envHeld` 同旧返回值(喂 `RestartSliceCtx.readFaceEnvHeld`);`faceChanged` 是 live 相的
28
+ * 换席信号 —— 真 ⇒ 调用方必须 `swapDeps({ readFace: config.readFace })` 一次,否则 config 与 Runner 分家。 */
29
+ export interface CenterReadFaceReport {
30
+ readonly envHeld: readonly (keyof CenterReadFace)[];
31
+ readonly faceChanged: boolean;
32
+ }
4
33
  /**
5
34
  * 应用 center 的 readFace 域(**boot-only**:调用点在 boot 拉取之后、`createRunnerDeps` 之前)。
6
35
  * 缺席 ⇒ 零触碰。规则见文件头注 ①②③。
@@ -10,5 +39,5 @@ import type { CenterReadFace, EffectiveConfig } from "./types.js";
10
39
  * 否则 center 改一个本机永远用不上的键,整片 fleet 会为一次对自己毫无影响的发布滚动重启一遍
11
40
  * (codex 交叉复审 finding 2,验真后采纳的那一半)。
12
41
  */
13
- export declare function applyCenterReadFace(config: ServiceConfig, eff: EffectiveConfig, logger?: Logger): readonly (keyof CenterReadFace)[];
42
+ export declare function applyCenterReadFace(config: ServiceConfig, eff: EffectiveConfig, logger?: Logger, apply?: CenterReadFaceApply): CenterReadFaceReport;
14
43
  //# sourceMappingURL=read-face.d.ts.map