@sema-agent/server 7.103.2 → 7.105.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/MIGRATION.md +378 -2
- package/README.md +6 -1
- package/README.zh-CN.md +5 -1
- package/USAGE.md +320 -16
- package/deploy/sema-up/README.md +57 -7
- package/deploy/sema-up/cluster-up.sh +18 -4
- package/deploy/sema-up/kube-up.sh +16 -0
- package/deploy/sema-up/lib/dataroot.sh +142 -0
- package/deploy/sema-up/lib/flagshape.sh +69 -0
- package/deploy/sema-up/lib/lanes.py +14 -4
- package/deploy/sema-up/lib/shell-access.sh +41 -0
- package/deploy/sema-up/preflight.sh +14 -1
- package/deploy/sema-up/sema-up.sh +134 -37
- package/deploy/sema-up/smoke.sh +5 -0
- package/dist/auto-mode-face.js +3 -4
- package/dist/boot/budget-tracing.js +1 -1
- package/dist/boot/config-center.d.ts +6 -2
- package/dist/boot/config-center.js +5 -59
- package/dist/boot/execution-env.d.ts +11 -0
- package/dist/boot/execution-env.js +14 -0
- package/dist/boot/execution-lane.d.ts +25 -0
- package/dist/boot/execution-lane.js +95 -0
- package/dist/boot/memory-consolidation.d.ts +185 -9
- package/dist/boot/memory-consolidation.js +192 -32
- package/dist/boot/resolve-spec.d.ts +2 -0
- package/dist/boot/resolve-spec.js +28 -24
- package/dist/boot/runner-deps.d.ts +6 -2
- package/dist/boot/runner-deps.js +6 -1
- package/dist/boot/session-faces.d.ts +5 -0
- package/dist/boot/session-faces.js +18 -2
- package/dist/boot/session-incarnation-seat.d.ts +41 -0
- package/dist/boot/session-incarnation-seat.js +69 -0
- package/dist/boot/stage-01-config.js +2 -2
- package/dist/boot/stage-02-registry-pull.js +3 -1
- package/dist/boot/stage-04-stores.d.ts +2 -0
- package/dist/boot/stage-04-stores.js +3 -1
- package/dist/boot/stage-05-execution-env.d.ts +4 -0
- package/dist/boot/stage-05-execution-env.js +2 -2
- package/dist/boot/stage-06-runners.d.ts +4 -1
- package/dist/boot/stage-06-runners.js +7 -14
- package/dist/boot/stage-07-capability-layer.d.ts +4 -1
- package/dist/boot/stage-08-reapers.d.ts +4 -1
- package/dist/boot/stage-09-leader.d.ts +6 -1
- package/dist/boot/stage-09-leader.js +2 -1
- package/dist/boot/stage-10-http-server.d.ts +6 -1
- package/dist/boot/stage-10-http-server.js +4 -1
- package/dist/brain.d.ts +1 -1
- package/dist/config-catalog.d.ts +16 -1
- package/dist/config-catalog.js +9 -9
- package/dist/config-center/restart-signal.d.ts +4 -4
- package/dist/config-invariants.d.ts +7 -1
- package/dist/config-invariants.js +37 -6
- package/dist/config-provider.d.ts +42 -10
- package/dist/config-provider.js +18 -16
- package/dist/config-types.d.ts +37 -16
- package/dist/config-types.js +1 -0
- package/dist/config.d.ts +28 -30
- package/dist/config.js +95 -77
- package/dist/env-facts.d.ts +13 -3
- package/dist/env-facts.js +15 -3
- package/dist/execution-lane-caps.d.ts +15 -0
- package/dist/execution-lane-caps.js +8 -0
- package/dist/file-history-capture.d.ts +42 -35
- package/dist/file-history-capture.js +15 -10
- package/dist/file-history-disclosure.d.ts +7 -31
- package/dist/file-history-disclosure.js +12 -0
- package/dist/fleet/fleet-bus.d.ts +5 -1
- package/dist/http/admission.d.ts +12 -5
- package/dist/http/admission.js +5 -1
- package/dist/http/dispatch.d.ts +18 -0
- package/dist/http/dispatch.js +19 -6
- package/dist/http/principal-gate.d.ts +22 -0
- package/dist/http/principal-gate.js +20 -0
- package/dist/http/resume-legs.d.ts +1 -1
- package/dist/http/resume-legs.js +9 -5
- package/dist/http/route-table.d.ts +15 -4
- package/dist/http/route-table.js +6 -5
- package/dist/http/routes/agents-roster.d.ts +7 -0
- package/dist/http/routes/agents-roster.js +6 -2
- package/dist/http/routes/approvals-assistant.d.ts +48 -19
- package/dist/http/routes/approvals-assistant.js +117 -50
- package/dist/http/routes/capabilities.js +3 -2
- package/dist/http/routes/config-catalog.js +1 -0
- package/dist/http/routes/fleet.d.ts +3 -2
- package/dist/http/routes/images.js +0 -1
- package/dist/http/routes/memory-consolidation.d.ts +3 -2
- package/dist/http/routes/memory-policy.js +3 -3
- package/dist/http/routes/rules.js +2 -2
- package/dist/http/routes/runs.js +29 -10
- package/dist/http/routes/session-sync.js +14 -15
- package/dist/http/routes/sessions-list.js +3 -2
- package/dist/http/routes/sessions.js +49 -25
- package/dist/http/routes/shared-memory.js +2 -2
- package/dist/http/routes/side-query.js +8 -1
- package/dist/http/routes/tasks.js +22 -19
- package/dist/http/routes/trace-usage.d.ts +2 -0
- package/dist/http/routes/trace-usage.js +8 -13
- package/dist/http/send.d.ts +12 -0
- package/dist/http/send.js +12 -1
- package/dist/http/server.d.ts +30 -8
- package/dist/http/server.js +11 -5
- package/dist/http/sse-lifecycle.d.ts +1 -1
- package/dist/http/sse-log.d.ts +12 -9
- package/dist/http/sse-log.js +1 -8
- package/dist/http/sync-submit-body.d.ts +43 -0
- package/dist/http/sync-submit-body.js +4 -0
- package/dist/http/wire-types.d.ts +23 -5
- package/dist/index.d.ts +1 -0
- package/dist/index.js +1 -0
- package/dist/leader/merge.d.ts +8 -3
- package/dist/memory-incident-sink.js +2 -1
- package/dist/model-select.d.ts +9 -0
- package/dist/model-select.js +5 -1
- package/dist/observability/corrupt-read-seat.d.ts +151 -16
- package/dist/observability/corrupt-read-seat.js +158 -15
- package/dist/observability/fail-open.d.ts +17 -1
- package/dist/observability/fail-open.js +17 -1
- package/dist/observability/metrics.js +1 -1
- package/dist/observability/run-terminal.d.ts +60 -10
- package/dist/observability/run-terminal.js +119 -9
- package/dist/parked-decide.d.ts +8 -3
- package/dist/parked-decide.js +56 -20
- package/dist/parked-revive-surface.d.ts +14 -7
- package/dist/parked-revive-surface.js +5 -1
- package/dist/plugins/approval-ask-store-file.d.ts +4 -1
- package/dist/plugins/approval-ask-store-file.js +9 -12
- package/dist/plugins/approval-ask-store-sql.d.ts +4 -0
- package/dist/plugins/approval-ask-store-sql.js +4 -1
- package/dist/plugins/approval-exemption-store.d.ts +21 -1
- package/dist/plugins/approval-exemption-store.js +36 -7
- package/dist/plugins/approval-nonce-store.d.ts +16 -2
- package/dist/plugins/approval-nonce-store.js +17 -7
- package/dist/plugins/caching-session-store.d.ts +5 -0
- package/dist/plugins/caching-session-store.js +3 -0
- package/dist/plugins/file-resume-anchor-store.d.ts +7 -2
- package/dist/plugins/file-resume-anchor-store.js +19 -5
- package/dist/plugins/file-run-store.d.ts +21 -8
- package/dist/plugins/file-run-store.js +60 -38
- package/dist/plugins/image-bake-store-sql.d.ts +0 -2
- package/dist/plugins/image-bake-store-sql.js +0 -5
- package/dist/plugins/local-session-store.d.ts +22 -0
- package/dist/plugins/local-session-store.js +74 -11
- package/dist/plugins/memory-run-store.d.ts +7 -5
- package/dist/plugins/memory-run-store.js +11 -4
- package/dist/plugins/one-time-migrations.d.ts +38 -1
- package/dist/plugins/one-time-migrations.js +143 -15
- package/dist/plugins/permission-rule-store-file.d.ts +9 -2
- package/dist/plugins/permission-rule-store-file.js +37 -51
- package/dist/plugins/pg-session-storage.d.ts +3 -0
- package/dist/plugins/pg-session-storage.js +19 -10
- package/dist/plugins/retention-store-sql.js +1 -0
- package/dist/plugins/run-store-sql.d.ts +15 -6
- package/dist/plugins/run-store-sql.js +15 -13
- package/dist/plugins/schema-indexes.js +1 -0
- package/dist/plugins/send-file-ledger.d.ts +5 -1
- package/dist/plugins/send-file-ledger.js +11 -6
- package/dist/plugins/session-event-payload.d.ts +14 -4
- package/dist/plugins/session-event-payload.js +5 -2
- package/dist/plugins/session-policy-store-sql.js +18 -5
- package/dist/plugins/store-backend.d.ts +13 -7
- package/dist/plugins/store-backend.js +10 -6
- package/dist/plugins/store-contracts.d.ts +7 -2
- package/dist/plugins/tidb-pool.js +12 -5
- package/dist/plugins/tidb-session-store.d.ts +6 -1
- package/dist/plugins/tidb-session-store.js +17 -10
- package/dist/project-identity.d.ts +4 -6
- package/dist/read-face-posture.d.ts +21 -0
- package/dist/read-face-posture.js +9 -0
- package/dist/request-key-closure.js +1 -1
- package/dist/request-value-closure.d.ts +72 -3
- package/dist/request-value-closure.js +12 -4
- package/dist/roster-bound-http-status.d.ts +6 -0
- package/dist/roster-bound-http-status.js +6 -0
- package/dist/rules-consent.js +0 -11
- package/dist/run-local.js +22 -27
- package/dist/runs.d.ts +22 -2
- package/dist/runs.js +29 -7
- package/dist/sealed-key.d.ts +31 -4
- package/dist/sealed-key.js +9 -0
- package/dist/security.d.ts +5 -1
- package/dist/session-children-retire.d.ts +92 -0
- package/dist/session-children-retire.js +85 -0
- package/dist/session-error-wire.d.ts +58 -0
- package/dist/session-error-wire.js +38 -0
- package/dist/session-incarnation.d.ts +61 -0
- package/dist/session-incarnation.js +43 -0
- package/dist/session-policy-wire.d.ts +98 -12
- package/dist/session-policy-wire.js +71 -8
- package/dist/session-records-skipped-notice.d.ts +20 -0
- package/dist/session-records-skipped-notice.js +27 -0
- package/dist/tool-approval.d.ts +65 -0
- package/dist/tool-approval.js +36 -9
- package/dist/trace/core-keyset-guard.d.ts +8 -7
- package/dist/trace/engine-notice-wire.d.ts +3 -1
- package/dist/trace/engine-notice-wire.js +2 -0
- package/dist/trace/project.d.ts +11 -6
- package/dist/trace/project.js +12 -3
- package/dist/trace/redact.d.ts +7 -0
- package/dist/trace/redact.js +153 -61
- package/dist/trace/wire-projection-faces.d.ts +10 -3
- package/dist/trace/wire-projection-faces.js +8 -2
- package/package.json +2 -2
- package/dist/plugins/memory-session-policy-store.d.ts +0 -46
- package/dist/plugins/memory-session-policy-store.js +0 -52
package/dist/config-types.d.ts
CHANGED
|
@@ -47,6 +47,13 @@ export declare const TASK_WRITE_FACE_ESCALATIONS: readonly ["allow", "refuse"];
|
|
|
47
47
|
export type TaskWriteFaceEscalation = (typeof TASK_WRITE_FACE_ESCALATIONS)[number];
|
|
48
48
|
export declare const RETENTION_MODES: readonly ["audit-only", "enforce"];
|
|
49
49
|
export type RetentionMode = (typeof RETENTION_MODES)[number];
|
|
50
|
+
/**
|
|
51
|
+
* S-841 —— `CONFIG_PROVIDER` 的词表闭集(**属主在此**:`loadConfig` 的唯一读点、config-center 工厂、执行车道合成步、
|
|
52
|
+
* 配置目录的 enum 行共读这一份)。缺席 / 空串 = 未表态(配了 `SEMA_REGISTRY_URL` ⇒ 中心,否则纯 env);两词之外 ⇒ 拒启。
|
|
53
|
+
* 修前是开放词:认不出的词被配置源判官当 `local`、被隐式 host 臂当「不是 local」,一个旋钮在一个进程里有两种读法。
|
|
54
|
+
*/
|
|
55
|
+
export declare const CONFIG_PROVIDER_WORDS: readonly ["local", "remote"];
|
|
56
|
+
export type ConfigProviderWord = (typeof CONFIG_PROVIDER_WORDS)[number];
|
|
50
57
|
/** [ref] §3/§4 —— sweep lane 的节律与灰度档(语义与三问见 {@link ServiceConfigFlat.retentionSweep})。 */
|
|
51
58
|
export interface RetentionSweepConfig {
|
|
52
59
|
/** `audit-only` = 照跑照判、只记 `audit_only` 行,**不调任何破坏性方法**;`enforce` = 真删。默认前者。 */
|
|
@@ -193,9 +200,17 @@ export declare function buildDefaultResilience(): ServiceConfigFlat["resilience"
|
|
|
193
200
|
* {@link ServiceConfigFlat.memoryConsolidationDriver})。纯数据。
|
|
194
201
|
*/
|
|
195
202
|
export interface MemoryConsolidationDriverConfig {
|
|
196
|
-
/**
|
|
197
|
-
*
|
|
198
|
-
|
|
203
|
+
/**
|
|
204
|
+
* 阀门的**部署表态**(env `MEMORY_CONSOLIDATION_DRIVER=on|off`,原词)。**缺席 = 没表态,不是 off**。
|
|
205
|
+
*
|
|
206
|
+
* 🔴 S-824 / clay **[ref]**(取代 [ref] ① 的「出厂 off」实况):出厂缺省 = **开,但只在整理座不落主模型时生效**
|
|
207
|
+
* (`MODEL_CHEAP_ID` 在场,或目录给 consolidate / summarize / flash 档位绑了别的模型);否则不开并在起服时提示一行。
|
|
208
|
+
* 这件事要等**目录应用之后**、记忆引擎接线之后才判得出来,所以本字段只记表态,开不开由 boot 第 6 段的
|
|
209
|
+
* `resolveConsolidationValve`(`boot/memory-consolidation.ts`)一次判定 —— 那是阀门开关**唯一**的属主。
|
|
210
|
+
* 显式 `on` / `off` 恒赢;阀门关 = 零装配零探针:座不解析、引擎不构造、两个 admin 口整个不挂(404 族,
|
|
211
|
+
* **不是** 501)、能力位翻假。
|
|
212
|
+
*/
|
|
213
|
+
valve?: "on" | "off";
|
|
199
214
|
/**
|
|
200
215
|
* 周期腿节律(秒;env `MEMORY_CONSOLIDATION_INTERVAL_SEC`,缺省 `0` = 不周期跑)。
|
|
201
216
|
* 🔴 **本 build 里任何正值都拒启**:结构上没有周期腿(没有可用的真互斥基建 —— 全文与偏离登记见
|
|
@@ -225,7 +240,8 @@ export interface MemoryConsolidationDriverConfig {
|
|
|
225
240
|
* · `true`(`=on`)= 武装。**武装是一条出口决定,不是性能旋钮**:每个建议任务之后会有一次整库整理跑,
|
|
226
241
|
* 记忆内容自动外流到配置的整理模型 —— 设它之前请读 `boot/memory-consolidation.ts` 的 EGRESS 段。
|
|
227
242
|
*
|
|
228
|
-
* ⚠️ **前置 = `MEMORY_CONSOLIDATION_DRIVER=on
|
|
243
|
+
* ⚠️ **前置 = 整理阀门开着**(`MEMORY_CONSOLIDATION_DRIVER=on`,或未设而 [ref] 出厂缺省派生成开 —— 见 {@link valve};
|
|
244
|
+
* 且记忆引擎接了线)。阀门开着 ⇒ 引擎那半场的整理两席
|
|
229
245
|
* (`RunnerDeps.memoryConsolidation` + `memoryConsolidationDriver`)由**同一只已解析座**接上
|
|
230
246
|
* (S-399,`boot/runner-deps.ts` 的 `ctx.consolidationSeat` 那一段)⇒ 本旋钮真的生效;阀门关着 ⇒ 两席
|
|
231
247
|
* 一个键都不写,而 core 对「武装但没接整理席」的处置是**每一次 prepare 都抛** `config.auto_consolidation`
|
|
@@ -761,12 +777,12 @@ export interface ServiceConfigFlat {
|
|
|
761
777
|
/**
|
|
762
778
|
* [ref](core 5.58 [ref] 片②)—— 记忆 **consolidation driver 阀门**的三根旋钮。
|
|
763
779
|
* **恒在场**(不是可选块,与 {@link retentionSweep} 同姿态):三根都有文档化的缺省,而缺省本身
|
|
764
|
-
* 是一条要能被读出来的姿态 —— `{
|
|
780
|
+
* 是一条要能被读出来的姿态 —— `{intervalSec:0, scopes:[]}`(`valve` 键缺席)= 「阀门没表态,出厂缺省在 boot 期派生」。
|
|
765
781
|
*
|
|
766
782
|
* 三问(设计稿 §消长账):谁需要 = 记忆库只涨不折的 TOB 部署(检索质量随库龄衰减);谁被伤 =
|
|
767
783
|
* 误开 + 错 role 的部署(一轮 consolidation 读全库 ~1e5 prompt tokens,静默升级到最贵 default
|
|
768
|
-
* 就是把一次维护跑成一笔意外账单);补偿 =
|
|
769
|
-
*
|
|
784
|
+
* 就是把一次维护跑成一笔意外账单);补偿 = 出厂缺省只在整理座**不落主模型**时开([ref];落主模型 ⇒ 不开 +
|
|
785
|
+
* 起服提示)+ 座解析失败拒启 + D3 投影把每轮花了什么摆出来 + v1 只有**手动**阀门(不信任周期腿之前可人工控节奏)。
|
|
770
786
|
*
|
|
771
787
|
* 语义与解析见 {@link MemoryConsolidationDriverConfig} 与 config.ts 的 `parseMemoryDomain`。
|
|
772
788
|
*/
|
|
@@ -1051,11 +1067,12 @@ export interface ServiceConfigFlat {
|
|
|
1051
1067
|
fileHistory?: {
|
|
1052
1068
|
retention?: FileHistoryRetentionPolicy;
|
|
1053
1069
|
};
|
|
1054
|
-
/** S-589:文件历史**捕获**总开关(`FILE_HISTORY_CAPTURE`,布尔词表,缺省 true,未知词拒启)。三条车道
|
|
1055
|
-
* (local / mysql / pg)同一语义:`false` ⇒
|
|
1056
|
-
*
|
|
1057
|
-
*
|
|
1058
|
-
*
|
|
1070
|
+
/** S-589 / S-724:文件历史**捕获**总开关(`FILE_HISTORY_CAPTURE`,布尔词表,缺省 true,未知词拒启)。三条车道
|
|
1071
|
+
* (local / mysql / pg)同一语义:`false` ⇒ 装配点照接历史店,但给 core 的部署捕获位 `RunnerDeps.fileHistoryCapture: "off"`
|
|
1072
|
+
* (首次触碰备份 / 回合边界 / fork 携带结构上不发生;能力词 `fileHistoryCapture:"off"` 同一只判定派生)。**只关捕获**:
|
|
1073
|
+
* 已存历史照常服务 restore / rewind / 2c session-sync(S-589 时旋钮关 = 摘店,连已存历史也读不了;S-724 改落 core 的捕获位)。
|
|
1074
|
+
* **不管擦除**:已捕获的历史照旧随会话删除 / 留存规则清理,翻旋钮本身不做批量删除。没有店的部署形不下发捕获位
|
|
1075
|
+
* (core 对「off 但没店」拒 prepare,而没店本来就不捕获)。判定的唯一属主是 `file-history-capture.ts`。
|
|
1059
1076
|
* 刻意**不**并进上面的 `fileHistory` 键:那一格是 core 店构造选项的同域键形,原样透传给 core。 */
|
|
1060
1077
|
fileHistoryCapture: boolean;
|
|
1061
1078
|
/** plugins 域 clone host 白名单(`PLUGINS_ALLOW_HOSTS`,缺省 github.com;契约 [ref]① 双保险面,
|
|
@@ -1526,8 +1543,11 @@ export interface ServiceConfigFlat {
|
|
|
1526
1543
|
selectEnvironmentTool: boolean;
|
|
1527
1544
|
/** RFC A1: inject structured sandbox envFacts into the task (`TaskSpec.envFacts` — core 1.240.0 whitelist
|
|
1528
1545
|
* field; rendered as the `# Environment` block, sanitized+bounded core-side). Default ON —
|
|
1529
|
-
* facts only ride when the deployment actually knows them (image binding/pkgSource/egress), absent =
|
|
1530
|
-
* unchanged. `SANDBOX_ENV_FACTS=false` opts out.
|
|
1546
|
+
* facts only ride when the deployment actually knows them (image binding/pkgSource/egress/resume facts), absent =
|
|
1547
|
+
* block unchanged. `SANDBOX_ENV_FACTS=false` opts out of the SANDBOX facts only.
|
|
1548
|
+
* 🔴 S-842:the session scratchpad declaration (`envFacts.scratchpadDir`) is NOT under this knob — it is the write
|
|
1549
|
+
* surface's declaration (core opens the root-fence hole for it and teaches it by the same verdict), not a display
|
|
1550
|
+
* fact; gating it would close the scratchpad together with the display. */
|
|
1531
1551
|
envFactsEnabled: boolean;
|
|
1532
1552
|
/** ③ sensitive-path write DENY set (core 1.295 `createSensitivePathPolicy`). The PATTERN SET is core's call
|
|
1533
1553
|
* (RECOMMENDED_SENSITIVE_PATTERNS — .env/.ssh/keys/.git hooks+config/cloud creds/histories, rationale
|
|
@@ -2054,8 +2074,9 @@ export interface ServiceConfigFlat {
|
|
|
2054
2074
|
centerCredentialRenewLeadMs: number;
|
|
2055
2075
|
/** Dual-mode config SOURCE (DUAL-MODE-DESIGN §4): "remote" (sema-registry HTTP) | "local" (.env + config.d
|
|
2056
2076
|
* via @sema-agent/settings-schema FileConfigStore) | unset → remote if SEMA_REGISTRY_URL (legacy CONFIG_CENTER_URL now boot-rejects, no dual-read) is set, else pure-env.
|
|
2057
|
-
* Env: CONFIG_PROVIDER
|
|
2058
|
-
|
|
2077
|
+
* Env: CONFIG_PROVIDER —— S-841 起闭集 {@link CONFIG_PROVIDER_WORDS}(认不出的词拒启;空串 = 未设)。
|
|
2078
|
+
* The same resolver/EffectiveConfig either way (can't drift). */
|
|
2079
|
+
configProvider?: ConfigProviderWord;
|
|
2059
2080
|
/** Root dir holding `config.d/<domain>.json` for the local config source. Env: CONFIG_LOCAL_DIR. */
|
|
2060
2081
|
configLocalDir?: string;
|
|
2061
2082
|
}
|
package/dist/config-types.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { DRAIN_GRACE_FLOOR_MS } from "@sema-agent/settings-schema/fleet";
|
|
2
2
|
export const TASK_WRITE_FACE_ESCALATIONS = ["allow", "refuse"];
|
|
3
3
|
export const RETENTION_MODES = ["audit-only", "enforce"];
|
|
4
|
+
export const CONFIG_PROVIDER_WORDS = ["local", "remote"];
|
|
4
5
|
export const MEMORY_CONSOLIDATION_MIN_INTERVAL_SEC = 300;
|
|
5
6
|
export const MAX_TIMER_DELAY_MS = 2_147_483_647;
|
|
6
7
|
export const AUTO_MODE_CLASSIFIER_TIMEOUT_BOUNDS = { minMs: 1_000, maxMs: MAX_TIMER_DELAY_MS };
|
package/dist/config.d.ts
CHANGED
|
@@ -75,8 +75,9 @@ export declare function parseNumOrFailNonNegative(name: string, raw: string | un
|
|
|
75
75
|
* 采用值也被报成夹取、而钥集条目**被丢弃**(没有任何采用值、也不该印 raw)却被报成「坏值回默认」。
|
|
76
76
|
* 告警规则是按事件名分流的,报错的种类就是报错的事实本身;可选字段的在场性是实现细节,不该被当契约用。
|
|
77
77
|
*
|
|
78
|
-
* `invalid-default` 是**缺省种类**(不写 `kind` 即是它)—— 历史形 `{env,raw}`
|
|
79
|
-
* (`
|
|
78
|
+
* `invalid-default` 是**缺省种类**(不写 `kind` 即是它)—— 历史形 `{env,raw}` 的存量生产者(`optFinitePositiveEnv`)
|
|
79
|
+
* 因此逐字不动,它的断言也不破。(布尔族 `boolEnv` 自 [ref]-rc2 起未知词在 `parseBoolWord` 里拒启,不再往这里 push;
|
|
80
|
+
* S-588 起旧负名走墓碑表 —— 两者都不是本种类的生产者。)
|
|
80
81
|
*/
|
|
81
82
|
type ConfigWarningKind = "invalid-default" | "clamped" | "empty-preserved" | "entry-rejected" | "set-rejected" | "ineffective";
|
|
82
83
|
interface ConfigWarning {
|
|
@@ -90,10 +91,23 @@ interface ConfigWarning {
|
|
|
90
91
|
detail?: string;
|
|
91
92
|
}
|
|
92
93
|
export declare function drainConfigWarnings(): ConfigWarning[];
|
|
93
|
-
|
|
94
|
+
/** Boot NOTICES ([ref]④): non-error decisions loadConfig makes that the operator should see once (e.g. the
|
|
95
|
+
* MODEL_PROVIDER inference below). Same pre-logger collection pattern as CONFIG_WARNINGS, but each entry
|
|
96
|
+
* carries its own event name + fields (the warnings drain has one fixed "invalid, using default" message
|
|
97
|
+
* which would misdescribe an inference). main.ts drains + logs right after createLogger.
|
|
98
|
+
*
|
|
99
|
+
* S-176 —— 每条带一个**级别**(闭集两词,排空处穷举 switch)。此前这一格固定按 warn 排空,于是「每次 boot 都成立的
|
|
100
|
+
* 常态事实」(posture 派生的档位)在这里没有位置:进 warn 会让一台一个字节都没配错的单机每次启动报警,另开一个
|
|
101
|
+
* 收集器又是第五条日志族。级别写在**铸点**(不是按事件名在排空处猜):
|
|
102
|
+
* · `warn` —— 运维要看一眼的决定(推断出的 provider、被丢掉的缺省行、no-op 旋钮……):既有的每一条;
|
|
103
|
+
* · `info` —— 常态事实,每次这样配就每次成立,不是异常(与执行车道落点那一行同级)。 */
|
|
104
|
+
type ConfigNoticeLevel = "warn" | "info";
|
|
105
|
+
export interface ConfigNotice {
|
|
106
|
+
level: ConfigNoticeLevel;
|
|
94
107
|
event: string;
|
|
95
108
|
fields: Record<string, string>;
|
|
96
|
-
}
|
|
109
|
+
}
|
|
110
|
+
export declare function drainConfigNotices(): ConfigNotice[];
|
|
97
111
|
/** F10 (codex audit): drain + log BOTH pre-logger diagnostic collectors (S20 soft-knob warnings + [ref]④ boot
|
|
98
112
|
* notices) in one place. EVERY entrypoint that calls loadConfig() then builds a logger must call this right
|
|
99
113
|
* after (main.ts, run-local.ts) — a new entry that forgets it would silently drop operator-facing diagnostics
|
|
@@ -169,10 +183,10 @@ export declare function buildUnpricedCeilingNotice(models: Record<string, Model>
|
|
|
169
183
|
* stated, so it was re-invented per line and the suffix stopped predicting anything.
|
|
170
184
|
*
|
|
171
185
|
* Semantics are the two dialects' semantics VERBATIM — an opt-in knob is `boolEnv(x, false)`, an opt-out knob is
|
|
172
|
-
* `boolEnv(x, true)` —
|
|
173
|
-
*
|
|
174
|
-
*
|
|
175
|
-
*
|
|
186
|
+
* `boolEnv(x, true)` — for the two literals and for unset. Anything else goes through `parseBoolWord` ([ref]-rc2,
|
|
187
|
+
* [ref]): `1`/`0`/`yes`/`no`/`on`/`off` in any case are read as the operator meant them (`LSP_ENABLED=1` reads as
|
|
188
|
+
* "on" to a human, and now to the parser too), and an unknown word REFUSES TO BOOT — it no longer lands on the
|
|
189
|
+
* default with a warning (that was this helper's first form, retired with [ref]-rc2; S-588 corrected this note).
|
|
176
190
|
*
|
|
177
191
|
* Note the deliberate non-migrations: the posture family (`postureOn`) keeps its tri-state — its unset case is
|
|
178
192
|
* neither `true` nor `false` but "single-user ∧ infra-ready" — and the sentinel-valued knobs (`MEMORY_ENGINE`,
|
|
@@ -183,7 +197,7 @@ export declare function buildUnpricedCeilingNotice(models: Record<string, Model>
|
|
|
183
197
|
* 用",而实际设了就拒启——诊断面撒的谎比没有诊断更贵(它会让人先去试那个必然拒启的开关)。 */
|
|
184
198
|
type ConfigKnobSource = "env" | "posture" | "default";
|
|
185
199
|
export interface ConfigKnobRecord {
|
|
186
|
-
/** The env name. 3.0.0 起只有这一个名字 —— 负名别名已是墓碑(
|
|
200
|
+
/** The env name. 3.0.0 起只有这一个名字 —— 负名别名已是墓碑(`RETIRED_ENVS` 的 replaced 行,S-588 起在场即拒)。 */
|
|
187
201
|
name: string;
|
|
188
202
|
/** `opt-in` = default OFF, `opt-out` = default ON, `posture` = default derived from REQUIRE_PRINCIPAL ∧ infra. */
|
|
189
203
|
polarity: "opt-in" | "opt-out" | "posture";
|
|
@@ -192,28 +206,12 @@ export interface ConfigKnobRecord {
|
|
|
192
206
|
}
|
|
193
207
|
/** The registered boolean knobs, sorted by name — the data behind `logConfigDiagnostics`'s polarity table. */
|
|
194
208
|
export declare function configKnobTable(): ConfigKnobRecord[];
|
|
195
|
-
/** [ref]
|
|
196
|
-
*
|
|
197
|
-
*
|
|
198
|
-
*
|
|
199
|
-
* used it (`PROJECT_MEMORY`, `CONFIG_LKG`, `HOST_BG`, `HOST_EXEC_SPOOL`) could not be read for their default
|
|
200
|
-
* without inverting twice in your head, and `projectMemoryDisabled` was the ONE negative boolean on a
|
|
201
|
-
* `ServiceConfig` that otherwise carries 16 positive `*Enabled`/`*Allowed` fields.
|
|
202
|
-
*
|
|
203
|
-
* 🔴 3.0.0([ref],clay 令「不做兼容」):旧名不再有兼容窗——**设了就拒启**。这个函数保留 legacy 形参
|
|
204
|
-
* 不是为了继续读它,而正是为了**认出它在场并拒绝启动**(把墓碑放在唯一知道这对名字关系的地方)。
|
|
205
|
-
*
|
|
206
|
-
* Resolution order:
|
|
207
|
-
* 1. `X_ENABLED` set to a literal ⇒ that.
|
|
208
|
-
* 2. `X_ENABLED` set to a non-literal ⇒ default + warning (the tombstone is NOT consulted — a typo in the
|
|
209
|
-
* new name must not turn into a boot refusal about an env the operator never set).
|
|
210
|
-
* 3. `X_ENABLED` unset ∧ `X_DISABLED` set(**任何值**,含 `"false"`)⇒ throw,文案指路新名。选拒启不选
|
|
211
|
-
* 静默忽略:静默会让升级部署的旧开关名义在、实际归默认——比拒启危险,且下游冒烟测试测不出来。
|
|
212
|
-
* 4. neither ⇒ default. */
|
|
213
|
-
export declare function boolEnvWithLegacyNegated(name: string, legacyNegatedName: string, def: boolean): boolean;
|
|
214
|
-
/** LKG (last-known-good) persistence of the remote effective-config fetch. Read LAZILY at both main.ts sites
|
|
209
|
+
/** [ref] B4 ②:D 族四枚(`PROJECT_MEMORY` / `CONFIG_LKG` / `HOST_BG` / `HOST_EXEC_SPOOL`)的正名 —— 缺省开,读点只读正名。
|
|
210
|
+
* 3.0.0 之前它们叫 `X_DISABLED`(读点上的双重否定);那四个旧负名是墓碑,在 `RETIRED_ENVS` 里(S-588 起在场即拒启,不论正名在不在)。
|
|
211
|
+
*
|
|
212
|
+
* LKG (last-known-good) persistence of the remote effective-config fetch. Read LAZILY at both main.ts sites
|
|
215
213
|
* rather than lifted onto `ServiceConfig`, so this stays a pure env knob; `loadConfig` pre-registers it (below)
|
|
216
|
-
* so its polarity row
|
|
214
|
+
* so its polarity row appears at boot with everything else. */
|
|
217
215
|
export declare const configLkgEnabled: () => boolean;
|
|
218
216
|
/** The `host` exec lane's background-shell capability kill switch (read per capability probe in remote-env-host.ts). */
|
|
219
217
|
export declare const hostBackgroundShellEnabled: () => boolean;
|
package/dist/config.js
CHANGED
|
@@ -10,10 +10,10 @@ import { DEFAULT_ELICITATION_THROTTLE } from "./elicitation.js";
|
|
|
10
10
|
import { isV2ScopeKey, memoryReplicationCoverage } from "./memory-scope.js";
|
|
11
11
|
import { DEFAULT_QUESTION_THROTTLE } from "./question.js";
|
|
12
12
|
import { GIT_API_KINDS, isGitApiKind } from "./git-api-kind.js";
|
|
13
|
-
import { RETENTION_MODES, MEMORY_CONSOLIDATION_MIN_INTERVAL_SEC, RESILIENCE_DEFAULTS, TASK_WRITE_FACE_ESCALATIONS, MAX_TIMER_DELAY_MS, AUTO_MODE_CLASSIFIER_TIMEOUT_BOUNDS, DRAIN_GRACE_FLOOR_MS, DRAIN_SECOND_SIGNAL_MIN_GAP_BOUNDS, DRAIN_SECOND_SIGNAL_MIN_GAP_DEFAULT_MS, FIRST_BYTE_TIMEOUT_BOUNDS } from "./config-types.js";
|
|
13
|
+
import { CONFIG_PROVIDER_WORDS, RETENTION_MODES, MEMORY_CONSOLIDATION_MIN_INTERVAL_SEC, RESILIENCE_DEFAULTS, TASK_WRITE_FACE_ESCALATIONS, MAX_TIMER_DELAY_MS, AUTO_MODE_CLASSIFIER_TIMEOUT_BOUNDS, DRAIN_GRACE_FLOOR_MS, DRAIN_SECOND_SIGNAL_MIN_GAP_BOUNDS, DRAIN_SECOND_SIGNAL_MIN_GAP_DEFAULT_MS, FIRST_BYTE_TIMEOUT_BOUNDS } from "./config-types.js";
|
|
14
14
|
import { buildWriteProtectionPosture } from "./write-protection.js";
|
|
15
15
|
import { normalizeSshFingerprint } from "./ssh-host-key.js";
|
|
16
|
-
import { readFaceForPosture } from "./read-face-posture.js";
|
|
16
|
+
import { buildReadFacePostureNotice, readFaceForPosture } from "./read-face-posture.js";
|
|
17
17
|
import { THINKING_LEVELS, THINKING_FORMATS, buildModelCompat, isThinkingFormat, isThinkingTier } from "./model-compat.js";
|
|
18
18
|
import {} from "./posture-source.js";
|
|
19
19
|
import { CONTROL_AND_BIDI_STRIP_RE } from "./text-bidi.js";
|
|
@@ -130,6 +130,10 @@ const RETIRED_ENVS = [
|
|
|
130
130
|
"widened retry; 0 = no engine clock on that segment and the transport's header / body clocks off too — the TCP + TLS connect " +
|
|
131
131
|
"keeps the transport's own clock)",
|
|
132
132
|
],
|
|
133
|
+
["PROJECT_MEMORY_DISABLED", "PROJECT_MEMORY_ENABLED", "replaced", "negative-name alias retired in server 3.0.0 — unset it; the polarity is inverted: PROJECT_MEMORY_DISABLED=true is PROJECT_MEMORY_ENABLED=false (the positive name defaults on)"],
|
|
134
|
+
["CONFIG_LKG_DISABLED", "CONFIG_LKG_ENABLED", "replaced", "negative-name alias retired in server 3.0.0 — unset it; the polarity is inverted: CONFIG_LKG_DISABLED=true is CONFIG_LKG_ENABLED=false (the positive name defaults on)"],
|
|
135
|
+
["HOST_BG_DISABLED", "HOST_BG_ENABLED", "replaced", "negative-name alias retired in server 3.0.0 — unset it; the polarity is inverted: HOST_BG_DISABLED=true is HOST_BG_ENABLED=false (the positive name defaults on)"],
|
|
136
|
+
["HOST_EXEC_SPOOL_DISABLED", "HOST_EXEC_SPOOL_ENABLED", "replaced", "negative-name alias retired in server 3.0.0 — unset it; the polarity is inverted: HOST_EXEC_SPOOL_DISABLED=true is HOST_EXEC_SPOOL_ENABLED=false (the positive name defaults on)"],
|
|
133
137
|
];
|
|
134
138
|
const RETIRED_ENV_SENTENCE = {
|
|
135
139
|
renamed: (hits) => `${hits.map(([old, to]) => `${old} → use ${to}`).join("; ")} — rename the env key(s); the value semantics are unchanged`,
|
|
@@ -147,7 +151,7 @@ function rejectRetiredEnvs() {
|
|
|
147
151
|
if (hit.length === 0)
|
|
148
152
|
return;
|
|
149
153
|
const parts = Object.keys(RETIRED_ENV_SENTENCE).flatMap((kind) => retiredEnvSentence(kind, hit));
|
|
150
|
-
throw new Error(`retired env name(s) set [${RETIRED_ENV_REFUSAL_CODE}]
|
|
154
|
+
throw new Error(`retired env name(s) set [${RETIRED_ENV_REFUSAL_CODE}] — refused while present, whether or not the replacement is also set: ${parts.join(" | ")}.`);
|
|
151
155
|
}
|
|
152
156
|
export function numEnv(name, fallback) {
|
|
153
157
|
return parseNumOrFail(name, env(name, fallback));
|
|
@@ -201,13 +205,24 @@ export function drainConfigNotices() {
|
|
|
201
205
|
return CONFIG_NOTICES.splice(0, CONFIG_NOTICES.length);
|
|
202
206
|
}
|
|
203
207
|
let EXEC_LANE_NOTICE;
|
|
204
|
-
function buildExecLaneNotice(remoteExec) {
|
|
208
|
+
function buildExecLaneNotice(remoteExec, configProvider) {
|
|
205
209
|
const explicit = process.env.REMOTE_EXEC !== undefined && process.env.REMOTE_EXEC !== "";
|
|
206
210
|
return {
|
|
207
211
|
lane: executionLaneOf(remoteExec?.provider),
|
|
208
|
-
source: explicit ? "explicit-env" :
|
|
212
|
+
source: explicit ? "explicit-env" : configProvider === "local" ? "default-local-provider" : "default-unset",
|
|
209
213
|
};
|
|
210
214
|
}
|
|
215
|
+
function parseConfigProvider() {
|
|
216
|
+
const raw = process.env.CONFIG_PROVIDER;
|
|
217
|
+
if (raw === undefined || raw === "")
|
|
218
|
+
return undefined;
|
|
219
|
+
const word = CONFIG_PROVIDER_WORDS.find((w) => w === raw);
|
|
220
|
+
if (word === undefined) {
|
|
221
|
+
throw new Error(`env CONFIG_PROVIDER="${raw.slice(0, 120)}" must be one of: ${CONFIG_PROVIDER_WORDS.join(", ")} — or leave it unset (then SEMA_REGISTRY_URL, if set, selects the center as the config source; otherwise this worker is env-only). ` +
|
|
222
|
+
`The word is matched exactly (no trimming, no case folding). Refusing to boot rather than guessing: the config source, the execution-lane default and the local config root all key on this one word.`);
|
|
223
|
+
}
|
|
224
|
+
return word;
|
|
225
|
+
}
|
|
211
226
|
export function logConfigDiagnostics(logger) {
|
|
212
227
|
for (const w of drainConfigWarnings()) {
|
|
213
228
|
const kind = w.kind ?? "invalid-default";
|
|
@@ -236,8 +251,20 @@ export function logConfigDiagnostics(logger) {
|
|
|
236
251
|
}
|
|
237
252
|
}
|
|
238
253
|
}
|
|
239
|
-
for (const n of drainConfigNotices())
|
|
240
|
-
|
|
254
|
+
for (const n of drainConfigNotices()) {
|
|
255
|
+
switch (n.level) {
|
|
256
|
+
case "warn":
|
|
257
|
+
logger.warn(n.event, n.fields);
|
|
258
|
+
break;
|
|
259
|
+
case "info":
|
|
260
|
+
logger.info?.(n.event, n.fields);
|
|
261
|
+
break;
|
|
262
|
+
default: {
|
|
263
|
+
const unreachable = n.level;
|
|
264
|
+
throw new Error(`unhandled config notice level: ${String(unreachable)}`);
|
|
265
|
+
}
|
|
266
|
+
}
|
|
267
|
+
}
|
|
241
268
|
const laneNotice = EXEC_LANE_NOTICE;
|
|
242
269
|
EXEC_LANE_NOTICE = undefined;
|
|
243
270
|
if (laneNotice && logger.info)
|
|
@@ -433,30 +460,9 @@ function boolEnv(name, def) {
|
|
|
433
460
|
registerKnob({ name, polarity: def ? "opt-out" : "opt-in", value, source });
|
|
434
461
|
return value;
|
|
435
462
|
}
|
|
436
|
-
export
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
let value;
|
|
440
|
-
let source;
|
|
441
|
-
const parsedCanonical = parseBoolWord(name, raw);
|
|
442
|
-
if (parsedCanonical !== undefined) {
|
|
443
|
-
value = parsedCanonical;
|
|
444
|
-
source = "env";
|
|
445
|
-
}
|
|
446
|
-
else if (legacy !== undefined && legacy !== "") {
|
|
447
|
-
throw new Error(`${legacyNegatedName} was removed in server 3.0.0 — set ${name}=${legacy !== "true"} instead ` +
|
|
448
|
-
`(positive-name knob, default ${def ? "on" : "off"}; migration table in USAGE.md)`);
|
|
449
|
-
}
|
|
450
|
-
else {
|
|
451
|
-
value = def;
|
|
452
|
-
source = "default";
|
|
453
|
-
}
|
|
454
|
-
registerKnob({ name, polarity: def ? "opt-out" : "opt-in", value, source });
|
|
455
|
-
return value;
|
|
456
|
-
}
|
|
457
|
-
export const configLkgEnabled = () => boolEnvWithLegacyNegated("CONFIG_LKG_ENABLED", "CONFIG_LKG_DISABLED", true);
|
|
458
|
-
export const hostBackgroundShellEnabled = () => boolEnvWithLegacyNegated("HOST_BG_ENABLED", "HOST_BG_DISABLED", true);
|
|
459
|
-
export const hostExecSpoolEnabled = () => boolEnvWithLegacyNegated("HOST_EXEC_SPOOL_ENABLED", "HOST_EXEC_SPOOL_DISABLED", true);
|
|
463
|
+
export const configLkgEnabled = () => boolEnv("CONFIG_LKG_ENABLED", true);
|
|
464
|
+
export const hostBackgroundShellEnabled = () => boolEnv("HOST_BG_ENABLED", true);
|
|
465
|
+
export const hostExecSpoolEnabled = () => boolEnv("HOST_EXEC_SPOOL_ENABLED", true);
|
|
460
466
|
export const REQUIRED_ENVS_BY_LANE = {
|
|
461
467
|
e2b: ["E2B_API_KEY"],
|
|
462
468
|
k8s: ["K8S_SANDBOX_IMAGE"],
|
|
@@ -537,6 +543,7 @@ export function parseEnvNameAllowlist(knob, singleUserTurnkey) {
|
|
|
537
543
|
return [];
|
|
538
544
|
case "honored":
|
|
539
545
|
CONFIG_NOTICES.push({
|
|
546
|
+
level: "warn",
|
|
540
547
|
event: "config_env_forwarded_multi_tenant",
|
|
541
548
|
fields: {
|
|
542
549
|
env: knob,
|
|
@@ -640,6 +647,7 @@ function parseWriteProtectedTableEnv(extraRaw, replaceRaw) {
|
|
|
640
647
|
const dropped = posture.droppedDefaultRows ?? [];
|
|
641
648
|
CONFIG_NOTICES.push(posture.source === "off"
|
|
642
649
|
? {
|
|
650
|
+
level: "warn",
|
|
643
651
|
event: "write_protection_table_disabled",
|
|
644
652
|
fields: {
|
|
645
653
|
env: "WRITE_PROTECTED_TABLE_REPLACE",
|
|
@@ -650,6 +658,7 @@ function parseWriteProtectedTableEnv(extraRaw, replaceRaw) {
|
|
|
650
658
|
},
|
|
651
659
|
}
|
|
652
660
|
: {
|
|
661
|
+
level: "warn",
|
|
653
662
|
event: "write_protection_table_replaced",
|
|
654
663
|
fields: {
|
|
655
664
|
env: "WRITE_PROTECTED_TABLE_REPLACE",
|
|
@@ -801,6 +810,7 @@ function noticeAutoCompactAboveWindow(slot, envName, m) {
|
|
|
801
810
|
if (act === undefined || window <= 0 || act <= window)
|
|
802
811
|
return;
|
|
803
812
|
CONFIG_NOTICES.push({
|
|
813
|
+
level: "warn",
|
|
804
814
|
event: "auto_compact_tokens_above_context_window",
|
|
805
815
|
fields: {
|
|
806
816
|
slot,
|
|
@@ -822,6 +832,7 @@ function parseStoreDomain(ctx) {
|
|
|
822
832
|
const dbBackend = enumEnv("DB_BACKEND", bareBoot ? (multiTenantBareBoot ? "memory" : "local") : "mysql", ["mysql", "pg", "local", "memory"]);
|
|
823
833
|
if (multiTenantBareBoot) {
|
|
824
834
|
CONFIG_NOTICES.push({
|
|
835
|
+
level: "warn",
|
|
825
836
|
event: "db_backend_default_memory_multi_tenant",
|
|
826
837
|
fields: {
|
|
827
838
|
note: "REQUIRE_PRINCIPAL=true with no DB_BACKEND — the single-user default (local file store) is incompatible with multi-tenant (no tenant isolation in the file owner map), so this deployment runs IN-MEMORY (runs/sessions lost on restart, durable runs 501). Configure DB_BACKEND=mysql|pg for a durable multi-tenant deployment.",
|
|
@@ -928,6 +939,7 @@ function parseModelDomain() {
|
|
|
928
939
|
return explicitProvider;
|
|
929
940
|
if (inferredAnthropicBaseUrl && inferredAnthropicCred) {
|
|
930
941
|
CONFIG_NOTICES.push({
|
|
942
|
+
level: "warn",
|
|
931
943
|
event: "model_provider_inferred",
|
|
932
944
|
fields: {
|
|
933
945
|
provider: "anthropic",
|
|
@@ -973,11 +985,28 @@ function parseModelDomain() {
|
|
|
973
985
|
const cheapId = process.env.MODEL_CHEAP_ID;
|
|
974
986
|
if (cheapId && modelId === "") {
|
|
975
987
|
CONFIG_NOTICES.push({
|
|
988
|
+
level: "warn",
|
|
976
989
|
event: "model_cheap_slot_without_main",
|
|
977
990
|
fields: { hint: "MODEL_CHEAP_ID is set but MODEL_ID is not — the env cheap slot derives from the env main model, so it is not minted; the catalog (config.d/models.json or the config center) serves the roles. Set MODEL_ID to use the env slots, or unset MODEL_CHEAP_ID to silence this line." },
|
|
978
991
|
});
|
|
979
992
|
}
|
|
980
|
-
const
|
|
993
|
+
const cheapIsMain = cheapId !== undefined && cheapId !== "" && modelId !== "" && cheapId === modelId;
|
|
994
|
+
if (cheapIsMain) {
|
|
995
|
+
CONFIG_NOTICES.push({
|
|
996
|
+
level: "warn",
|
|
997
|
+
event: "config_env_entry_cheap_same_as_main",
|
|
998
|
+
fields: {
|
|
999
|
+
env: "MODEL_CHEAP_ID",
|
|
1000
|
+
model: modelId,
|
|
1001
|
+
hint: `MODEL_CHEAP_ID names the same model as MODEL_ID ("${modelId}") — the env cheap slot is the main gateway under a different id, ` +
|
|
1002
|
+
`so the same id IS the main model: no cheap slot is minted and subagent / summarize stay on the main model (roles -> default); ` +
|
|
1003
|
+
`any other MODEL_CHEAP_* key set here describes that slot and is not applied. Set MODEL_CHEAP_ID to a different (cheaper) model ` +
|
|
1004
|
+
`id to run those roles cheap, or declare two differently-named entries in config.d/models.json to give one upstream id a second ` +
|
|
1005
|
+
`shape (price / reasoning / window); unset MODEL_CHEAP_ID to silence this line.`,
|
|
1006
|
+
},
|
|
1007
|
+
});
|
|
1008
|
+
}
|
|
1009
|
+
const cheapModel = cheapId && modelId !== "" && !cheapIsMain
|
|
981
1010
|
? {
|
|
982
1011
|
...model,
|
|
983
1012
|
id: cheapId,
|
|
@@ -998,6 +1027,7 @@ function parseModelDomain() {
|
|
|
998
1027
|
const mainAct = optFinitePositiveEnv("MODEL_AUTO_COMPACT_TOKENS");
|
|
999
1028
|
if (applyAutoCompactWindow(model, mainAct) === "removed-1m" && modelId !== "") {
|
|
1000
1029
|
CONFIG_NOTICES.push({
|
|
1030
|
+
level: "warn",
|
|
1001
1031
|
event: "auto_compact_window_not_derived_1m",
|
|
1002
1032
|
fields: {
|
|
1003
1033
|
slot: "main",
|
|
@@ -1013,6 +1043,7 @@ function parseModelDomain() {
|
|
|
1013
1043
|
if (applyAutoCompactWindow(cheapModel, cheapAct) === "removed-1m") {
|
|
1014
1044
|
const inherited = process.env.MODEL_CHEAP_CONTEXT_WINDOW === undefined || process.env.MODEL_CHEAP_CONTEXT_WINDOW === "";
|
|
1015
1045
|
CONFIG_NOTICES.push({
|
|
1046
|
+
level: "warn",
|
|
1016
1047
|
event: "auto_compact_window_not_derived_1m",
|
|
1017
1048
|
fields: {
|
|
1018
1049
|
slot: "cheap",
|
|
@@ -1029,6 +1060,7 @@ function parseModelDomain() {
|
|
|
1029
1060
|
const gatewayApiKey = process.env.MODEL_API_KEY;
|
|
1030
1061
|
if (gatewayBaseUrl === undefined && gatewayApiKey !== undefined && gatewayApiKey !== "") {
|
|
1031
1062
|
CONFIG_NOTICES.push({
|
|
1063
|
+
level: "warn",
|
|
1032
1064
|
event: "model_gateway_key_unpaired",
|
|
1033
1065
|
fields: {
|
|
1034
1066
|
hint: "MODEL_API_KEY is set but MODEL_GATEWAY_BASEURL is not — MODEL_API_KEY is the gateway's credential and pairs only with the gateway's own root, so with no gateway it is NOT loaded and is sent to no route (a model with its own baseUrl authenticates with its own per-model key). Set MODEL_GATEWAY_BASEURL to use it, or unset MODEL_API_KEY to silence this line.",
|
|
@@ -1308,6 +1340,7 @@ function parseApprovalDomain(ctx) {
|
|
|
1308
1340
|
if (trimmed === "" || trimmed === "off") {
|
|
1309
1341
|
if (trimmed === "off") {
|
|
1310
1342
|
CONFIG_NOTICES.push({
|
|
1343
|
+
level: "warn",
|
|
1311
1344
|
event: "manual_mode_shell_gate_off_is_noop",
|
|
1312
1345
|
fields: {
|
|
1313
1346
|
env: "MANUAL_MODE_SHELL_GATE",
|
|
@@ -1526,7 +1559,7 @@ function parseMemoryConsolidationDriver() {
|
|
|
1526
1559
|
`who believes the valve is armed gets a 404 rather than a diagnosis). This knob is new in server 7.46 and has no legacy ` +
|
|
1527
1560
|
`spellings to preserve, unlike MEMORY_ENGINE's two-family word table.`);
|
|
1528
1561
|
}
|
|
1529
|
-
const
|
|
1562
|
+
const valve = word === "on" || word === "off" ? word : undefined;
|
|
1530
1563
|
const autoWord = requireNonBlank("MEMORY_AUTO_CONSOLIDATION", process.env.MEMORY_AUTO_CONSOLIDATION);
|
|
1531
1564
|
if (autoWord !== undefined && autoWord !== "on" && autoWord !== "off") {
|
|
1532
1565
|
throw new Error(`MEMORY_AUTO_CONSOLIDATION must be exactly "on" or "off", got ${JSON.stringify(autoWord)} — this knob decides whether memory content ` +
|
|
@@ -1558,12 +1591,12 @@ function parseMemoryConsolidationDriver() {
|
|
|
1558
1591
|
}
|
|
1559
1592
|
const scopesRaw = requireNonBlank("MEMORY_CONSOLIDATION_SCOPES", process.env.MEMORY_CONSOLIDATION_SCOPES);
|
|
1560
1593
|
const scopes = scopesRaw !== undefined ? csv("MEMORY_CONSOLIDATION_SCOPES") : [];
|
|
1561
|
-
|
|
1562
|
-
|
|
1563
|
-
|
|
1564
|
-
|
|
1565
|
-
|
|
1566
|
-
|
|
1594
|
+
return {
|
|
1595
|
+
...(valve !== undefined ? { valve } : {}),
|
|
1596
|
+
intervalSec,
|
|
1597
|
+
scopes,
|
|
1598
|
+
...(autoRunOnRecommendation !== undefined ? { autoRunOnRecommendation } : {}),
|
|
1599
|
+
};
|
|
1567
1600
|
}
|
|
1568
1601
|
function parseMemoryDomain(ctx) {
|
|
1569
1602
|
const { requirePrincipal } = ctx;
|
|
@@ -1605,6 +1638,7 @@ function parseMemoryDomain(ctx) {
|
|
|
1605
1638
|
const replication = memoryReplicationCoverage(memorySync !== undefined ? { memorySync } : {});
|
|
1606
1639
|
if (replication.kind === "scopes" && memoryEngineEnabled && !requirePrincipal && memoryScope !== undefined) {
|
|
1607
1640
|
CONFIG_NOTICES.push({
|
|
1641
|
+
level: "warn",
|
|
1608
1642
|
event: "memory_project_scope_yields_to_sync",
|
|
1609
1643
|
fields: {
|
|
1610
1644
|
replicatedScopes: replication.scopes.join(","),
|
|
@@ -1615,7 +1649,7 @@ function parseMemoryDomain(ctx) {
|
|
|
1615
1649
|
},
|
|
1616
1650
|
});
|
|
1617
1651
|
}
|
|
1618
|
-
const projectMemoryEnabled =
|
|
1652
|
+
const projectMemoryEnabled = boolEnv("PROJECT_MEMORY_ENABLED", true);
|
|
1619
1653
|
const memoryOrgAdmissionMode = enumEnv("MEMORY_ORG_ADMISSION_MODE", "enforce", ["audit", "enforce"]);
|
|
1620
1654
|
const memoryOrgDirectoryJson = process.env.MEMORY_ORG_DIRECTORY_JSON || undefined;
|
|
1621
1655
|
const memoryOrgGrantTtlMs = numEnvBounded("MEMORY_ORG_GRANT_TTL_MS", "60000", 1000, 3_600_000);
|
|
@@ -1787,11 +1821,15 @@ function parseOrchestrationDomain(ctx) {
|
|
|
1787
1821
|
}
|
|
1788
1822
|
}
|
|
1789
1823
|
}
|
|
1824
|
+
const readFaceFacts = process.env.READ_FACE !== undefined
|
|
1825
|
+
? { readFace: enumEnv("READ_FACE", "roots", ["open", "roots"]), readFaceSource: "env" }
|
|
1826
|
+
: readFaceForPosture(singleUserTurnkey);
|
|
1827
|
+
const readFaceNotice = buildReadFacePostureNotice(readFaceFacts);
|
|
1828
|
+
if (readFaceNotice !== undefined)
|
|
1829
|
+
CONFIG_NOTICES.push({ level: "info", ...readFaceNotice });
|
|
1790
1830
|
return {
|
|
1791
1831
|
requireExecutionLane: boolEnv("REQUIRE_EXECUTION_LANE", false),
|
|
1792
|
-
...
|
|
1793
|
-
? { readFace: enumEnv("READ_FACE", "roots", ["open", "roots"]), readFaceSource: "env" }
|
|
1794
|
-
: readFaceForPosture(singleUserTurnkey)),
|
|
1832
|
+
...readFaceFacts,
|
|
1795
1833
|
...optionalEnumKey("taskWriteFaceEscalation", "TASK_WRITE_FACE_ESCALATION", process.env.TASK_WRITE_FACE_ESCALATION, TASK_WRITE_FACE_ESCALATIONS),
|
|
1796
1834
|
...(process.env.READ_DENY_PATTERNS !== undefined ? { readDenyPatterns: parseReadDenyPatternsEnv(process.env.READ_DENY_PATTERNS) } : {}),
|
|
1797
1835
|
...(() => {
|
|
@@ -1910,7 +1948,7 @@ function parseOrchestrationDomain(ctx) {
|
|
|
1910
1948
|
...(process.env.DEVICE_SUPERSEDE ? { supersede: enumEnv("DEVICE_SUPERSEDE", "stale_only", ["stale_only", "deny"]) } : {}),
|
|
1911
1949
|
}
|
|
1912
1950
|
:
|
|
1913
|
-
|
|
1951
|
+
ctx.configProvider === "local"
|
|
1914
1952
|
? { provider: "host" }
|
|
1915
1953
|
: undefined,
|
|
1916
1954
|
worktreeIsolation: boolEnv("WORKTREE_ISOLATION_ENABLED", false) && process.env.WORKTREE_REPO_ROOT
|
|
@@ -2028,7 +2066,13 @@ function parseLimitsHttpDomain() {
|
|
|
2028
2066
|
costQuotaWindowSec: numEnv("COST_QUOTA_WINDOW_SEC", "86400"),
|
|
2029
2067
|
reapIntervalSec: numEnv("REAP_INTERVAL_SEC", "60"),
|
|
2030
2068
|
runStaleSec: numEnv("REAP_RUN_STALE_SEC", "120"),
|
|
2031
|
-
toolResultTtlSec:
|
|
2069
|
+
toolResultTtlSec: (() => {
|
|
2070
|
+
const ttl = numEnv("TOOL_RESULT_TTL_SEC", "86400");
|
|
2071
|
+
if (ttl <= 0) {
|
|
2072
|
+
throw new Error(`env TOOL_RESULT_TTL_SEC=${ttl} must be > 0 — it is the age (seconds) after which an offloaded tool result is reaped, so 0 or less puts the cutoff at or after now and every reaper tick would delete ALL of them; there is no "off": set a larger value to keep results longer (the config center refuses the same value)`);
|
|
2073
|
+
}
|
|
2074
|
+
return ttl;
|
|
2075
|
+
})(),
|
|
2032
2076
|
};
|
|
2033
2077
|
}
|
|
2034
2078
|
function parseObservabilityDomain() {
|
|
@@ -2081,7 +2125,7 @@ function parseGitApiKind(raw, baseUrl) {
|
|
|
2081
2125
|
return kind;
|
|
2082
2126
|
}
|
|
2083
2127
|
function parseIntegrationsDomain(ctx) {
|
|
2084
|
-
const { requirePrincipal } = ctx;
|
|
2128
|
+
const { requirePrincipal, configProvider } = ctx;
|
|
2085
2129
|
return {
|
|
2086
2130
|
pluginsAllowHosts: (process.env.PLUGINS_ALLOW_HOSTS ?? "github.com").split(",").map((h) => h.trim()).filter(Boolean),
|
|
2087
2131
|
configBootFetchBudgetMs: clampEnvWithWarn("CONFIG_BOOT_FETCH_BUDGET_MS", "1500", 0),
|
|
@@ -2092,7 +2136,7 @@ function parseIntegrationsDomain(ctx) {
|
|
|
2092
2136
|
skillsDir: env("SKILLS_DIR", defaultSkillsDir()),
|
|
2093
2137
|
centerCredentialRenewLeadMs: parseCenterCredentialRenewLeadMs(process.env.CENTER_CREDENTIAL_RENEW_LEAD_MS),
|
|
2094
2138
|
configCenter: process.env.SEMA_REGISTRY_URL ? parseConfigCenter(env("SEMA_REGISTRY_URL"), requirePrincipal) : undefined,
|
|
2095
|
-
configProvider
|
|
2139
|
+
...(configProvider !== undefined ? { configProvider } : {}),
|
|
2096
2140
|
configLocalDir: process.env.CONFIG_LOCAL_DIR || undefined,
|
|
2097
2141
|
a2aServe: parseA2aServe(),
|
|
2098
2142
|
};
|
|
@@ -2281,6 +2325,7 @@ export function loadConfig() {
|
|
|
2281
2325
|
CONFIG_KNOBS.clear();
|
|
2282
2326
|
const requirePrincipal = boolEnv("REQUIRE_PRINCIPAL", false);
|
|
2283
2327
|
const singleUserTurnkey = !requirePrincipal;
|
|
2328
|
+
const configProvider = parseConfigProvider();
|
|
2284
2329
|
const postureOn = (name, infraReady = true) => {
|
|
2285
2330
|
const explicit = parseBoolWord(name, process.env[name]);
|
|
2286
2331
|
const value = explicit ?? (singleUserTurnkey && infraReady);
|
|
@@ -2293,10 +2338,10 @@ export function loadConfig() {
|
|
|
2293
2338
|
const approval = parseApprovalDomain({ postureOn, singleUserTurnkey, infraReady: durableStoreReady });
|
|
2294
2339
|
const memory = parseMemoryDomain({ requirePrincipal });
|
|
2295
2340
|
const auth = parseAuthDomain({ requirePrincipal });
|
|
2296
|
-
const orchestration = parseOrchestrationDomain({ postureOn, singleUserTurnkey });
|
|
2341
|
+
const orchestration = parseOrchestrationDomain({ postureOn, singleUserTurnkey, configProvider });
|
|
2297
2342
|
const limitsHttp = parseLimitsHttpDomain();
|
|
2298
2343
|
const observability = parseObservabilityDomain();
|
|
2299
|
-
const integrations = parseIntegrationsDomain({ requirePrincipal });
|
|
2344
|
+
const integrations = parseIntegrationsDomain({ requirePrincipal, configProvider });
|
|
2300
2345
|
const { directApprovalDoor, durableApproval: dgDurable, approvalHmacKeys: dgHmacKeys } = approval;
|
|
2301
2346
|
const { principalJwtPubkeys, principalJwtIss, principalJwtAud, operatorPrincipals: dgOperators, authTokens } = auth;
|
|
2302
2347
|
const { enabled: imageBakesEnabled, runnerPrincipal: bakeRunnerPrincipal } = orchestration.imageBakes;
|
|
@@ -2330,14 +2375,6 @@ export function loadConfig() {
|
|
|
2330
2375
|
if (imageBakesEnabled && !Object.values(authTokens).includes(bakeRunnerPrincipal)) {
|
|
2331
2376
|
throw new Error(`IMAGE_BAKES_ENABLED=true but no SERVICE_AUTH_TOKENS entry maps to the runner principal '${bakeRunnerPrincipal}' — the bake-runner authenticates by its credential (token-derived source), so a '<BAKE_RUNNER_TOKEN>=${bakeRunnerPrincipal}' SERVICE_AUTH_TOKENS entry is required or claim/ingest/heartbeat all 403 (IMAGE-API-DESIGN.md §P2.12 boot invariant)`);
|
|
2332
2377
|
}
|
|
2333
|
-
if (orchestration.leaderEnabled && orchestration.remoteExec?.provider === "host") {
|
|
2334
|
-
throw new Error("LEADER_ENABLED=true + REMOTE_EXEC=host is refused: the leader wire pins ONE absolute workspace path " +
|
|
2335
|
-
"(LEADER_WORKSPACE / the /home/user default) for every sub-worker, while the host adapter gives each env a " +
|
|
2336
|
-
"DIFFERENT random directory — so N concurrent host workers would share one physical git worktree and " +
|
|
2337
|
-
"corrupt each other's state SILENTLY (per-worker workspace is not wired on this lane yet). Run the leader " +
|
|
2338
|
-
"on an isolated lane (REMOTE_EXEC=e2b or k8s), or turn the leader off (unset LEADER_ENABLED) " +
|
|
2339
|
-
"(#193 车4 件2 / staleness ledger P1-10 boot invariant)");
|
|
2340
|
-
}
|
|
2341
2378
|
if (orchestration.leaderEnabled && store.dbBackend !== "mysql" && store.dbBackend !== "pg") {
|
|
2342
2379
|
throw new Error(`LEADER_ENABLED=true but DB_BACKEND=${store.dbBackend} (no external SQL store) is refused: a leader run is an ` +
|
|
2343
2380
|
"N-worker fan-out lasting up to 24h that ends in a REAL git push, and without a durable store its registry is " +
|
|
@@ -2345,25 +2382,6 @@ export function loadConfig() {
|
|
|
2345
2382
|
"push. Point the service at a durable store (DB_BACKEND=mysql|pg), or turn the leader off (unset LEADER_ENABLED) " +
|
|
2346
2383
|
"(#193 车4 件2 / staleness ledger P1-9+P1-10 boot invariant)");
|
|
2347
2384
|
}
|
|
2348
|
-
{
|
|
2349
|
-
const deviceLane = orchestration.remoteExec?.provider === "device";
|
|
2350
|
-
const deviceSqlStore = (store.dbBackend === "mysql" && store.tidb !== undefined) || (store.dbBackend === "pg" && store.pg !== undefined);
|
|
2351
|
-
if (deviceLane && !deviceSqlStore) {
|
|
2352
|
-
throw new Error(`REMOTE_EXEC=device with DB_BACKEND=${store.dbBackend} and no SQL connection coordinates built is refused ` +
|
|
2353
|
-
"[device-durable-store]: the device registry, the session↔device binding and the device audit trail are all " +
|
|
2354
|
-
"SQL-backed — without a real SQL store the binding lives in this process only, so a restart forgets WHICH device " +
|
|
2355
|
-
"a parked run must resume onto and a second replica cannot see it at all. Note DB_BACKEND alone is not enough: " +
|
|
2356
|
-
"SESSION_BACKEND=memory (or auto with no MYSQL_HOST/PG_HOST) builds no coordinates at all. Configure a durable " +
|
|
2357
|
-
"store (DB_BACKEND=mysql|pg + its *_HOST/*_USER/*_DATABASE), or unset REMOTE_EXEC " +
|
|
2358
|
-
"(device-executor-lane-v2 §4.7 boot invariant)");
|
|
2359
|
-
}
|
|
2360
|
-
if (deviceLane && !dgDurable) {
|
|
2361
|
-
throw new Error("REMOTE_EXEC=device without DURABLE_APPROVAL=true is refused [device-durable-approval]: a personal device goes " +
|
|
2362
|
-
"offline (lid closed, network drop) at arbitrary points, so an in-flight approval that outlives the window MUST " +
|
|
2363
|
-
"have a park exit — without the durable approval store there is none and the run is stuck with nowhere to persist. " +
|
|
2364
|
-
"Set DURABLE_APPROVAL=true, or unset REMOTE_EXEC (device-executor-lane-v2 §4.7 boot invariant)");
|
|
2365
|
-
}
|
|
2366
|
-
}
|
|
2367
2385
|
if (orchestration.requireExecutionLane && orchestration.remoteExec === undefined) {
|
|
2368
2386
|
throw new Error(`REQUIRE_EXECUTION_LANE=true but NO execution lane is named on this deployment — refusing to start. ` +
|
|
2369
2387
|
`With no lane the engine falls back to its in-process stub environment, where at least the Bash tool is ` +
|
|
@@ -2387,7 +2405,7 @@ export function loadConfig() {
|
|
|
2387
2405
|
throw new Error(refusal);
|
|
2388
2406
|
}
|
|
2389
2407
|
}
|
|
2390
|
-
EXEC_LANE_NOTICE = buildExecLaneNotice(orchestration.remoteExec);
|
|
2408
|
+
EXEC_LANE_NOTICE = buildExecLaneNotice(orchestration.remoteExec, configProvider);
|
|
2391
2409
|
return attachConfigGroups({
|
|
2392
2410
|
...store,
|
|
2393
2411
|
...modelPlane,
|