@sema-agent/server 7.70.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 (110) hide show
  1. package/README.md +1 -1
  2. package/README.zh-CN.md +1 -1
  3. package/USAGE.md +8 -4
  4. package/dist/approval-card.d.ts +16 -17
  5. package/dist/approval-card.js +0 -9
  6. package/dist/approval-content-kind.d.ts +13 -7
  7. package/dist/approval-content-kind.js +3 -1
  8. package/dist/approval.js +3 -2
  9. package/dist/bench/s1/live-deps.js +2 -0
  10. package/dist/boot/execution-env.js +12 -2
  11. package/dist/boot/memory-consolidation.d.ts +2 -1
  12. package/dist/boot/parked-revive-gate.d.ts +2 -0
  13. package/dist/boot/parked-revive-gate.js +5 -1
  14. package/dist/boot/resolve-spec.js +8 -3
  15. package/dist/boot/runner-deps.d.ts +48 -59
  16. package/dist/boot/runner-deps.js +10 -4
  17. package/dist/brain.d.ts +3 -1
  18. package/dist/brain.js +2 -8
  19. package/dist/budget.d.ts +12 -1
  20. package/dist/budget.js +1 -1
  21. package/dist/config-catalog.js +13 -11
  22. package/dist/config-center/apply-effective.js +2 -4
  23. package/dist/config-center/facade.d.ts +1 -1
  24. package/dist/config-center/restart-signal.js +2 -2
  25. package/dist/config-center/skills-mcp.js +15 -0
  26. package/dist/config-types.d.ts +86 -4
  27. package/dist/config-types.js +19 -0
  28. package/dist/config.d.ts +42 -0
  29. package/dist/config.js +71 -22
  30. package/dist/deployment-governance.d.ts +6 -3
  31. package/dist/deployment-governance.js +2 -1
  32. package/dist/execution-lane-caps.d.ts +51 -37
  33. package/dist/execution-lane-caps.js +23 -0
  34. package/dist/fleet/fleet-bus.d.ts +22 -1
  35. package/dist/fleet/fleet-bus.js +2 -1
  36. package/dist/http/durable-run-leg.d.ts +64 -0
  37. package/dist/http/durable-run-leg.js +63 -0
  38. package/dist/http/route-ctx.d.ts +14 -0
  39. package/dist/http/route-ctx.js +3 -0
  40. package/dist/http/routes/approvals-assistant.js +3 -3
  41. package/dist/http/routes/capabilities.js +2 -2
  42. package/dist/http/routes/runs.js +10 -58
  43. package/dist/http/routes/workflows.js +1 -0
  44. package/dist/http/server.js +72 -22
  45. package/dist/index.d.ts +1 -1
  46. package/dist/leader/wire.js +4 -3
  47. package/dist/main.js +8 -3
  48. package/dist/memory-operator-faces.d.ts +1 -1
  49. package/dist/memory-scope.d.ts +24 -0
  50. package/dist/memory-scope.js +12 -3
  51. package/dist/observability/fail-open.d.ts +1 -1
  52. package/dist/observability/fail-open.js +1 -1
  53. package/dist/observability/metrics.d.ts +4 -0
  54. package/dist/observability/metrics.js +3 -1
  55. package/dist/observability/run-terminal-log.d.ts +4 -3
  56. package/dist/observability/run-terminal-log.js +4 -15
  57. package/dist/observability/tool-trace.d.ts +18 -1
  58. package/dist/observability/tool-trace.js +4 -3
  59. package/dist/orchestration/workflow-agent-session-index.d.ts +85 -2
  60. package/dist/orchestration/workflow-agent-session-index.js +38 -5
  61. package/dist/parked-decide.d.ts +40 -1
  62. package/dist/parked-decide.js +22 -4
  63. package/dist/plugins/checkpoint-store-sql.js +5 -5
  64. package/dist/plugins/e2b-orphan-reclaim.d.ts +4 -4
  65. package/dist/plugins/e2b-orphan-reclaim.js +18 -20
  66. package/dist/plugins/local-checkpoint-store.js +3 -3
  67. package/dist/plugins/remote-env-adb.d.ts +11 -0
  68. package/dist/plugins/remote-env-adb.js +2 -0
  69. package/dist/plugins/remote-env-device.d.ts +9 -0
  70. package/dist/plugins/remote-env-device.js +3 -0
  71. package/dist/plugins/remote-env-e2b.d.ts +11 -0
  72. package/dist/plugins/remote-env-e2b.js +2 -0
  73. package/dist/plugins/remote-env-host.d.ts +11 -0
  74. package/dist/plugins/remote-env-host.js +2 -0
  75. package/dist/plugins/remote-env-k8s.d.ts +11 -0
  76. package/dist/plugins/remote-env-k8s.js +2 -0
  77. package/dist/plugins/remote-env-local-docker.d.ts +11 -0
  78. package/dist/plugins/remote-env-local-docker.js +2 -0
  79. package/dist/plugins/remote-env-ssh.d.ts +11 -0
  80. package/dist/plugins/remote-env-ssh.js +2 -0
  81. package/dist/plugins/store-backend.d.ts +34 -12
  82. package/dist/plugins/store-backend.js +12 -3
  83. package/dist/plugins/store-contracts.d.ts +81 -18
  84. package/dist/plugins/store-contracts.js +18 -30
  85. package/dist/plugins/workflow-run-store-sql.d.ts +31 -9
  86. package/dist/plugins/workflow-run-store-sql.js +28 -1
  87. package/dist/run-cancel-context.js +1 -1
  88. package/dist/run-local.js +7 -1
  89. package/dist/runs.d.ts +16 -2
  90. package/dist/runs.js +2 -1
  91. package/dist/runtime-caps-resolver.js +5 -3
  92. package/dist/task-a2a.js +2 -1
  93. package/dist/task-mcp.js +2 -1
  94. package/dist/task-settings.d.ts +21 -8
  95. package/dist/task-settings.js +2 -2
  96. package/dist/tool-approval.d.ts +26 -21
  97. package/dist/tool-approval.js +2 -2
  98. package/dist/trace/core-keyset-guard.d.ts +136 -10
  99. package/dist/trace/engine-notice-wire.d.ts +1 -1
  100. package/dist/trace/engine-notice-wire.js +1 -0
  101. package/dist/trace/ledger-events.d.ts +5 -1
  102. package/dist/trace/ledger-sink.js +8 -1
  103. package/dist/trace/project.d.ts +33 -4
  104. package/dist/trace/project.js +24 -6
  105. package/dist/trace/redact.js +10 -5
  106. package/dist/usage-analytics.d.ts +9 -1
  107. package/dist/usage-analytics.js +8 -0
  108. package/dist/wire-name-bound.d.ts +14 -0
  109. package/dist/wire-name-bound.js +13 -0
  110. package/package.json +3 -3
@@ -23,9 +23,15 @@ export const CONFIG_ADVISORY_CLASSIFICATIONS = new Set([
23
23
  "shared-memory-not-mounted",
24
24
  "shell-gate-off",
25
25
  ]);
26
- export function createEngineNoticeForwarder(logger, router = defaultEngineNoticeRouter) {
26
+ const CEILING_ORIGINS = new Set(["walltime", "turns"]);
27
+ export function createEngineNoticeForwarder(logger, router = defaultEngineNoticeRouter, metrics) {
27
28
  return (notice) => {
28
29
  logger.warn("engine_notice", { code: notice.code, message: notice.message, ...(notice.detail !== undefined ? { detail: notice.detail } : {}) });
30
+ if (metrics !== undefined && notice.code === "task.interrupt_unconsumed") {
31
+ const origin = notice.detail?.origin;
32
+ if (typeof origin === "string" && CEILING_ORIGINS.has(origin))
33
+ metrics.inc("limit_ceiling_after_answer_total", { origin });
34
+ }
29
35
  router.route(notice);
30
36
  };
31
37
  }
@@ -53,7 +59,7 @@ export function buildDeploymentPostureSeats(config) {
53
59
  export function createSharedRunnerDeps(ctx) {
54
60
  const shared = {
55
61
  brain: ctx.brain,
56
- onNotice: createEngineNoticeForwarder(ctx.logger),
62
+ onNotice: createEngineNoticeForwarder(ctx.logger, defaultEngineNoticeRouter, ctx.metrics),
57
63
  tiers: ctx.config.tiers,
58
64
  models: ctx.config.models,
59
65
  roles: ctx.config.roles,
@@ -84,7 +90,7 @@ export function createSharedRunnerDeps(ctx) {
84
90
  return shared;
85
91
  }
86
92
  export function createRunnerDeps(ctx) {
87
- const { config, logger, metrics, localRoot, outcomeSink, elicitation, question, toolApproval, sessionStore, memoryEngine, sessionCaptureRecordStore, memorySyncRunner, runtimeCapsResolver, fileHistoryStore, legacyRewindBoundaryProbe, permissionRuleStore, fleetBus, workflowRunStore, workflowJournalStore, workflowAgentRegistry, workflowNotifyGate, workflowCompletionInbox, deliverWorkflowCompletion, getRunStore, sessionShellGateExplicitlyOff, } = ctx;
93
+ const { config, logger, metrics, localRoot, outcomeSink, elicitation, question, toolApproval, sessionStore, memoryEngine, sessionCaptureRecords, memorySyncRunner, runtimeCapsResolver, fileHistoryStore, legacyRewindBoundaryProbe, permissionRuleStore, fleetBus, workflowRunStore, workflowJournalStore, workflowAgentRegistry, workflowNotifyGate, workflowCompletionInbox, deliverWorkflowCompletion, getRunStore, sessionShellGateExplicitlyOff, } = ctx;
88
94
  const sharedRunnerDeps = createSharedRunnerDeps(ctx);
89
95
  const workflowModelAllowlist = workflowModelAllowlistFor(config);
90
96
  const selfOrchestrationDeps = config.selfOrchestrationEnabled
@@ -152,7 +158,7 @@ export function createRunnerDeps(ctx) {
152
158
  sessionStore,
153
159
  memoryBackend: memoryEngine ? memoryEngine.backend : undefined,
154
160
  memoryEngineDir: memoryEngine ? memoryEngine.root : undefined,
155
- memoryCaptureRecordStore: sessionCaptureRecordStore !== undefined ? () => sessionCaptureRecordStore : undefined,
161
+ memoryCaptureRecordStore: sessionCaptureRecords !== undefined ? (plane) => sessionCaptureRecords(plane) : undefined,
156
162
  onMemoryHarvestReport: memoryEngine
157
163
  ? (report, info) => {
158
164
  metrics.inc("memory_harvest_total", { ok: String(report.ok), phase: info.phase, incident: report.incident?.kind ?? "none" });
package/dist/brain.d.ts CHANGED
@@ -15,7 +15,9 @@ export declare const FAST_FAIL_MAX_RETRIES = 2;
15
15
  * - **Tiered timeouts** (1.38, `connectTimeoutMs`/`firstTokenTimeoutMs`) are passed into each
16
16
  * openai-compatible brain at creation — they bound "gateway never accepts the connection" and
17
17
  * "stream opened but hangs without a token" as retryable `[network]` failures. Default ON
18
- * (30s/120s, [ref] fleet audit; env =0 disables).
18
+ * (connect 30s per the fleet-audit recommendation; first-token 600s since S-202 — a self-hosted backend's long
19
+ * prefill legitimately takes minutes to the first byte, and 120s turned that into a retry handing the
20
+ * same prompt back to the same queue. env =0 disables; env =120000 restores the old posture).
19
21
  * - **Circuit breaker** (`createCircuitBreakerBrain`) wraps the *primary* gateway route(s) when
20
22
  * `MODEL_CIRCUIT_BREAKER=true`: after N transient failures it opens and fast-fails, so failover
21
23
  * switches to the bare backup immediately instead of waiting out each timeout. The last gateway
package/dist/brain.js CHANGED
@@ -1,5 +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 { buildDefaultResilience } from "./config-types.js";
3
4
  import { resolveModelApiKey } from "./key-resolver.js";
4
5
  function isOffDeploymentRoot(baseUrl, config) {
5
6
  if (!baseUrl)
@@ -14,14 +15,7 @@ function degradeDeclaredRoot(fallbackBaseUrl, config) {
14
15
  return armed ? config.gatewayBaseUrl : (fallbackBaseUrl || config.gatewayBaseUrl);
15
16
  }
16
17
  export const FAST_FAIL_MAX_RETRIES = 2;
17
- const DEFAULT_RESILIENCE = {
18
- connectTimeoutMs: 30_000,
19
- firstTokenTimeoutMs: 120_000,
20
- idleTimeoutMs: 300_000,
21
- circuitBreaker: false,
22
- failureThreshold: 5,
23
- cooldownMs: 30_000,
24
- };
18
+ const DEFAULT_RESILIENCE = buildDefaultResilience();
25
19
  export function createBrain(config, deps = {}) {
26
20
  const { fetchImpl, breakerState } = deps;
27
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,5 +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 { RESILIENCE_DEFAULTS } from "./config-types.js";
3
4
  import { webSearchConfigFromEnv } from "./plugins/web-search.js";
4
5
  import { CENTER_READ_FACE_LIVE_KEYS } from "./config-center/read-face.js";
5
6
  export const CATALOG_VALUE_TYPES = ["boolean", "number", "string", "enum", "csv", "json", "path", "url", "secret", "opaque"];
@@ -245,8 +246,8 @@ export const CONFIG_CATALOG = [
245
246
  r("MODEL_API_KEY", "modelPlane", "secret", "网关腿共享 API key(per-model 覆写在 center 侧)", { danger: [...SEC, ...BILL] }),
246
247
  r("MODEL_AUTO_COMPACT_TOKENS", "modelPlane", "number", "主槽 dual-window 触发阈(1M 窗不再隐式 967000,缺席则用普通触发几何)", { derivedDefaultNote: "缺席 ⇒ 不铸键(core W-33000 几何)" }),
247
248
  r("MODEL_CASCADE_LADDER", "modelPlane", "csv", "质量门级联梯(目录名 cheapest→strongest;opt-in per-task cascade:true)", { danger: BILL, configKey: "cascadeLadder" }),
248
- r("MODEL_CB_COOLDOWN_MS", "modelPlane", "number", "熔断冷却窗(ms)", { staticDefault: "30000" }),
249
- 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) }),
250
251
  r("MODEL_CHARS_PER_TOKEN", "modelPlane", "number", "结构估 token 系数(CJK 密集模型建议 2-3;坏值回 core 默认)", { derivedDefaultNote: "缺席 ⇒ core 默认 4" }),
251
252
  r("MODEL_CHEAP_AUTO_COMPACT_TOKENS", "modelPlane", "number", "cheap 槽 dual-window 触发阈(主槽值刻意不外溢)", { derivedDefaultNote: "缺席 ⇒ 不铸键" }),
252
253
  r("MODEL_CHEAP_CHARS_PER_TOKEN", "modelPlane", "number", "cheap 槽结构估 token 系数", { derivedDefaultNote: "缺席 ⇒ 继承主模型" }),
@@ -259,14 +260,14 @@ export const CONFIG_CATALOG = [
259
260
  r("MODEL_CHEAP_MAX_TOKENS", "modelPlane", "number", "cheap 槽输出上限", { derivedDefaultNote: "缺席 ⇒ 继承主模型", center: cModels("models[].maxTokens") }),
260
261
  r("MODEL_CHEAP_REASONING", "modelPlane", "boolean", "cheap 槽 reasoning 声明(三态:缺席继承主模型)", { derivedDefaultNote: "缺席 ⇒ 继承主模型" }),
261
262
  r("MODEL_CHEAP_VISION", "modelPlane", "boolean", "cheap 槽 vision 声明(三态:缺席继承主模型;text-only cheap 必设 false 让预检真拦)", { derivedDefaultNote: "缺席 ⇒ 继承主模型" }),
262
- r("MODEL_CIRCUIT_BREAKER", "modelPlane", "boolean", "模型腿熔断开关", { staticDefault: "false" }),
263
+ r("MODEL_CIRCUIT_BREAKER", "modelPlane", "boolean", "模型腿熔断开关", { staticDefault: String(RESILIENCE_DEFAULTS.circuitBreaker.value) }),
263
264
  r("MODEL_CODE_ROLES", "modelPlane", "csv", "给点名 role 附编码 persona(role 表内名字才生效)"),
264
- 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) }),
265
266
  r("MODEL_CONTEXT_WINDOW", "modelPlane", "number", "主模型上下文窗声明", { staticDefault: "262144", configKey: "model", resolve: (c) => c.model.contextWindow ?? null, center: cModels("models[].contextWindow") }),
266
- 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") }),
267
- 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") }),
268
- r("MODEL_COST_INPUT", "modelPlane", "number", "主模型 input 单价(NaN 会让成本天花板整条消失 ⇒ 坏值拒启)", { staticDefault: "0", danger: BILL, resolve: (c) => c.model.cost?.input ?? null, center: cModels("models[].cost.input") }),
269
- 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") }),
270
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 }),
271
272
  r("MODEL_DEGRADE_AT_COST_FRACTION", "modelPlane", "number", "预算降级触发比(spent/budget;>1=实际别降级)", { staticDefault: "0.7", danger: BILL }),
272
273
  r("MODEL_DEGRADE_ON", "modelPlane", "csv", "反应式降级触发词表(core DegradeReason 全集;拼错拒启)", { center: { domain: "models", path: "degrade", centerKey: "models", precedence: "center-wins", timing: "restart", restartSlice: "degrade-route" } }),
@@ -274,12 +275,12 @@ export const CONFIG_CATALOG = [
274
275
  r("MODEL_DEGRADE_TO", "modelPlane", "string", "降级目标模型(设了=开降级;外部网关名不必在目录)", { danger: BILL }),
275
276
  r("MODEL_DEGRADE_TO_VISION", "modelPlane", "boolean", "降级目标的 vision 声明(false ⇒ 带图任务丢降级不丢图)", { staticDefault: "false" }),
276
277
  r("MODEL_EXTRA_BODY", "modelPlane", "opaque", "生成参数直通 JSON(top_k/logit_bias 等;必须静态 —— 前缀缓存键序)", { danger: SEC }),
277
- r("MODEL_FIRST_TOKEN_TIMEOUT_MS", "modelPlane", "number", "首 token 超时(ms;0=关 —— 开流不吐字的唯一看门狗)", { staticDefault: "120000" }),
278
+ r("MODEL_FIRST_TOKEN_TIMEOUT_MS", "modelPlane", "number", "首 token 超时(ms;0=关 —— 开流不吐字的唯一看门狗;自托管长 prefill 后端按此档留足)", { staticDefault: String(RESILIENCE_DEFAULTS.firstTokenTimeoutMs.value) }),
278
279
  r("MODEL_FREQUENCY_PENALTY", "modelPlane", "number", "frequency_penalty 直通(压过 EXTRA_BODY 同键)"),
279
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") }),
280
281
  r("MODEL_GATEWAY_FALLBACK_URLS", "modelPlane", "url", "网关故障转移地址表(CSV)", { configKey: "gatewayFallbackUrls" }),
281
282
  r("MODEL_ID", "modelPlane", "string", "主模型 id(3.0.0 起无出厂缺省,缺 ⇒ 拒启指路)", { configKey: "model", resolve: (c) => c.model.id, center: cModels("models[].id") }),
282
- 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) }),
283
284
  r("MODEL_MAX_TOKENS", "modelPlane", "number", "主模型输出上限", { derivedDefaultNote: "缺席 ⇒ min(32000, contextWindow)", resolve: (c) => c.model.maxTokens ?? null, center: cModels("models[].maxTokens") }),
284
285
  r("MODEL_PRESENCE_PENALTY", "modelPlane", "number", "presence_penalty 直通"),
285
286
  r("MODEL_PROVIDER", "modelPlane", "string", "主模型 provider(路由选 brain)", { derivedDefaultNote: "缺席:ANTHROPIC base+cred 同在 ⇒ anthropic,否则 gateway", resolve: (c) => c.model.provider, center: cModels("models[].provider") }),
@@ -339,12 +340,13 @@ export const CONFIG_CATALOG = [
339
340
  r("QUESTION_TTL_MS", "approval", "number", "AskUserQuestion 无人应答释放窗(ms)", { derivedDefaultNote: "缺席 ⇒ 出厂窗 300000" }),
340
341
  r("RATE_LIMIT_RPM", "limitsHttp", "number", "计费提交每分钟限流(0=关)", { staticDefault: "0", danger: BILL, configKey: "rateLimitPerMin", center: cLimits("rateLimitPerMin") }),
341
342
  r("READ_DENY_BUILTIN_EXCLUDE", "orchestration", "csv", "按行名删内建 deny 条目(档位之后生效;绝不削任务显式 deny)", { danger: SEC, configKey: "readDenyBuiltinExclude", center: cRead("denyBuiltinExclude", "readDenyBuiltinExclude") }),
342
- 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") }),
343
344
  r("READ_DENY_PATTERNS", "orchestration", "csv", "READ 面额外 deny pattern 表", { danger: SEC, configKey: "readDenyPatterns", center: cRead("denyPatterns", "readDenyPatterns") }),
344
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") }),
345
346
  r("REAP_INTERVAL_SEC", "limitsHttp", "number", "reaper 周期(秒)", { staticDefault: "60", configKey: "reapIntervalSec" }),
346
347
  r("REAP_RUN_STALE_SEC", "limitsHttp", "number", "run 活性判陈旧阈(秒)", { staticDefault: "120", configKey: "runStaleSec", center: cLimits("runStaleSec") }),
347
348
  r("REMOTE_EXEC", "orchestration", "enum", "执行车道选择(点名而配不齐/认不出 ⇒ 拒启,绝不静默降级到 host/stub)", { enumValues: ["e2b", "k8s", "ssh", "adb", "host", "local-docker", "device"], danger: SEC, derivedDefaultNote: "缺席:CONFIG_PROVIDER=local ⇒ host;其余 ⇒ 进程内 stub", configKey: "remoteExec", resolve: (c) => c.remoteExec?.provider ?? null }),
349
+ r("REMOTE_EXEC_HOME_DIR", "orchestration", "path", "执行环境的 home 目录(`~/` 形权限规则的基;core ExecutionEnv.homeDir 座)", { danger: SEC, derivedDefaultNote: "缺席:host/in-process ⇒ os.homedir();e2b ⇒ /home/user;k8s/local-docker/ssh/adb/device ⇒ 无(`~/` 规则 422 响亮拒)", configKey: "remoteExecHomeDir", resolve: (c) => c.remoteExecHomeDir ?? null }),
348
350
  r("REQUIRE_PRINCIPAL", "auth", "boolean", "多租户开关(posture 判别位:true ⇒ CC 能力族默认收紧)", { staticDefault: "false", danger: SEC, configKey: "requirePrincipal" }),
349
351
  r("RESOURCE_SUSPEND", "approval", "boolean", "durable 路径 opt-in core resourceSuspend(预算/preempt ⇒ 可恢复挂起)", { staticDefault: "false", configKey: "resourceSuspend" }),
350
352
  r("RESOURCE_SUSPEND_MAX_SLICES", "approval", "number", "resume 链片数上限(0=不设;零进展循环兜底)", { staticDefault: "0", configKey: "resourceSuspendMaxSlices" }),
@@ -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;
@@ -55,6 +55,68 @@ export interface RetentionSweepConfig {
55
55
  * 那道拒之后它即刻生效 —— 留着是为了「那天不必有人再猜一个数」。
56
56
  */
57
57
  export declare const MEMORY_CONSOLIDATION_MIN_INTERVAL_SEC = 300;
58
+ /**
59
+ * S-220 —— **`resilience` 块缺省的唯一属主**(S-202 的单源从一根扩到整族)。
60
+ *
61
+ * 这是一张**表**,不是六个常量:缺省长在三条腿上(解析器腿 `config.ts` 的 `numEnv`/`boolEnv` 缺省、
62
+ * 目录腿 `config-catalog.ts` 的 `staticDefault`、`brain.ts` 的 `DEFAULT_RESILIENCE` —— config 对象
63
+ * 没带 resilience 块时的同姿态兜底),而三条腿此前**各写一份字面量**。S-202 只把 `firstTokenTimeoutMs`
64
+ * 一根收成单源,于是剩下五根还在「改一处漏一处 = 目录对外宣告的缺省与真跑的不是同一代」这条病里 ——
65
+ * 按类修的形是把**整族**收进一张表,让三条腿都只许引用它,而不是再加五个同形常量(六个常量 = 六份
66
+ * 要各自记得引用的纪律;一张表 = 一条)。
67
+ *
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」);表里多一行块上没有的键同样红。
76
+ *
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
+ * 每次调用铸一只新对象(共享一只导出常量会被消费方就地改掉,那是另一条病)。
118
+ */
119
+ export declare function buildDefaultResilience(): ServiceConfigFlat["resilience"];
58
120
  /**
59
121
  * [ref] D1/D2 —— consolidation driver 阀门的三根旋钮(语义与三问见
60
122
  * {@link ServiceConfigFlat.memoryConsolidationDriver})。纯数据。
@@ -608,6 +670,18 @@ export interface ServiceConfigFlat {
608
670
  * snapshot). Self-hosted sandbox lane; auth = explicit K8S_* env or in-cluster service account.
609
671
  * - `ssh` → a real host over SSH (NOT isolated/suspendable; [ref] §5). AI-orchestrated batch deploy.
610
672
  * - `adb` → a real device over ADB (NOT isolated/suspendable). On-device APP testing. */
673
+ /**
674
+ * S-213④ —— `REMOTE_EXEC_HOME_DIR`:**执行环境的 home 目录**(core `ExecutionEnv.homeDir`),
675
+ * `~/…` 形权限规则(`Edit(~/.ssh/**)`)唯一的基,绝对路径。
676
+ *
677
+ * 部署级旋钮,**跨车道同一根**(不按 provider 分七根 —— 它回答的是同一个问题:「这条部署的手,
678
+ * 跑在哪个用户的 home 下」)。显式声明**恒赢**;缺席时只有 home 属于车道本身的那几条车道有答案
679
+ * (host/in-process = `os.homedir()`、e2b = 平台固定用户),k8s/local-docker(镜像属性)、
680
+ * ssh(目标机登录用户)、adb/device 一律缺席 ⇒ `~/` 规则在下单时被 422 响亮拒。
681
+ * 派生与逐车道理由的**单一属主** = `execution-lane-caps.ts` 的 `executionLaneHomeDir`。
682
+ * 非绝对路径 ⇒ **拒启**([ref] A 档;core 侧同判为 `config.execution_env_home_dir_invalid`)。
683
+ */
684
+ remoteExecHomeDir?: string;
611
685
  remoteExec?: {
612
686
  provider: "e2b";
613
687
  apiKey: string;
@@ -1595,9 +1669,17 @@ export interface ServiceConfigFlat {
1595
1669
  pattern: string;
1596
1670
  caseSensitive: boolean;
1597
1671
  }[];
1598
- /** [ref] 件 G5(core 5.33.0 [ref],clay 裁定件):**内建** deny 表的档位选择(闭集四档:credentials /
1599
- * shell-history / browser / wallet)。`READ_DENY_BUILTIN_TIERS`=逗号分隔;`none`=显式空姿态(core 的
1600
- * `[]`:一档内建都不要);坏档名 / 空串**启动期响亮拒**(校验用 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 配置而关掉本档的部署,凭证行照样拦着。
1601
1683
  * 缺席=不铸 ⇒ core 自默认(除 shell-history 外全开)。**仅 deployment 席**:TaskSpec 上没有同名键,
1602
1684
  * 任务/受治脚本无法据此放宽自己的部署(core 侧的设计,与本仓「部署级旋钮无条件施加」同向)。 */
1603
1685
  readDenyBuiltinTiers?: readonly string[];
@@ -1721,7 +1803,7 @@ export type ServiceMemoryConfig = Pick<ServiceConfigFlat, "memoryEngineEnabled"
1721
1803
  * config 这一层它只是一句声明。 */
1722
1804
  export type ServiceAuthConfig = Pick<ServiceConfigFlat, "authToken" | "authTokens" | "allowUnauthedWrites" | "corsOrigins" | "principalHeader" | "requirePrincipal" | "autonomy" | "commandPolicy" | "compliancePosture" | "lockedConfigKeys" | "configCatalogEnabled" | "retentionPolicy" | "retentionSweep" | "operatorPrincipals" | "principalJwtPubkeys" | "principalJwtIss" | "principalJwtAud" | "principalJwtMaxTtlSec" | "bindHost" | "bindHostSource">;
1723
1805
  /** 组:orchestration(编排 + 执行车道 + 沙箱面 + workflow/后台 agent 存留)。 */
1724
- export type ServiceOrchestrationConfig = Pick<ServiceConfigFlat, "remoteExec" | "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" | "imageBakes" | "readFace" | "readFaceSource" | "readDenyPatterns" | "readDenyBuiltinTiers" | "readDenyBuiltinExclude">;
1806
+ export type ServiceOrchestrationConfig = Pick<ServiceConfigFlat, "remoteExec" | "remoteExecHomeDir" | "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" | "imageBakes" | "readFace" | "readFaceSource" | "readDenyPatterns" | "readDenyBuiltinTiers" | "readDenyBuiltinExclude">;
1725
1807
  /** 组:limitsHttp(HTTP 面 + 各类上限/配额/回收窗)。 */
1726
1808
  export type ServiceLimitsHttpConfig = Pick<ServiceConfigFlat, "port" | "attachmentOrphanGraceMs" | "workspaceFileMaxBytes" | "attachmentMaxBytes" | "attachmentMimeAllowlist" | "attachmentUnboundTtlMs" | "infraCostRates" | "drainGraceMs" | "sighupIdleGraceMs" | "parentPid" | "engineAutoExit" | "engineLingerMs" | "rateLimitPerMin" | "maxTaskCostUsd" | "maxTaskTokens" | "maxPrincipalCostUsd" | "costQuotaWindowSec" | "reapIntervalSec" | "runStaleSec" | "toolResultTtlSec">;
1727
1809
  /** 组:observability(可观测)。 */
@@ -1,3 +1,22 @@
1
1
  export const RETENTION_MODES = ["audit-only", "enforce"];
2
2
  export const MEMORY_CONSOLIDATION_MIN_INTERVAL_SEC = 300;
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
+ }
3
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