@sema-agent/server 7.103.2 → 7.104.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 +346 -2
- package/README.md +6 -1
- package/README.zh-CN.md +5 -1
- package/USAGE.md +271 -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 +4 -0
- 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 +13 -28
- package/dist/config.js +53 -70
- 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/http/admission.d.ts +11 -4
- package/dist/http/admission.js +5 -1
- package/dist/http/dispatch.js +3 -3
- package/dist/http/principal-gate.d.ts +22 -0
- package/dist/http/principal-gate.js +20 -0
- package/dist/http/resume-legs.js +9 -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/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 +27 -2
- package/dist/http/routes/session-sync.js +8 -14
- package/dist/http/routes/sessions-list.js +3 -2
- package/dist/http/routes/sessions.js +44 -23
- package/dist/http/routes/shared-memory.js +2 -2
- package/dist/http/routes/tasks.js +22 -19
- package/dist/http/routes/trace-usage.js +4 -4
- package/dist/http/send.d.ts +12 -0
- package/dist/http/send.js +12 -1
- package/dist/http/server.d.ts +21 -4
- package/dist/http/server.js +7 -4
- 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 +12 -0
- package/dist/observability/fail-open.js +12 -0
- package/dist/observability/run-terminal.d.ts +15 -1
- package/dist/observability/run-terminal.js +4 -0
- 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/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/run-store-sql.d.ts +12 -4
- package/dist/plugins/run-store-sql.js +15 -13
- 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/store-backend.d.ts +7 -3
- package/dist/plugins/store-backend.js +10 -6
- package/dist/plugins/store-contracts.d.ts +7 -2
- package/dist/plugins/tidb-pool.js +8 -2
- 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/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/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-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 +1 -1
- package/dist/trace/project.js +9 -0
- 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/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 {
|
|
@@ -169,10 +170,10 @@ export declare function buildUnpricedCeilingNotice(models: Record<string, Model>
|
|
|
169
170
|
* stated, so it was re-invented per line and the suffix stopped predicting anything.
|
|
170
171
|
*
|
|
171
172
|
* 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
|
-
*
|
|
173
|
+
* `boolEnv(x, true)` — for the two literals and for unset. Anything else goes through `parseBoolWord` ([ref]-rc2,
|
|
174
|
+
* [ref]): `1`/`0`/`yes`/`no`/`on`/`off` in any case are read as the operator meant them (`LSP_ENABLED=1` reads as
|
|
175
|
+
* "on" to a human, and now to the parser too), and an unknown word REFUSES TO BOOT — it no longer lands on the
|
|
176
|
+
* default with a warning (that was this helper's first form, retired with [ref]-rc2; S-588 corrected this note).
|
|
176
177
|
*
|
|
177
178
|
* Note the deliberate non-migrations: the posture family (`postureOn`) keeps its tri-state — its unset case is
|
|
178
179
|
* neither `true` nor `false` but "single-user ∧ infra-ready" — and the sentinel-valued knobs (`MEMORY_ENGINE`,
|
|
@@ -183,7 +184,7 @@ export declare function buildUnpricedCeilingNotice(models: Record<string, Model>
|
|
|
183
184
|
* 用",而实际设了就拒启——诊断面撒的谎比没有诊断更贵(它会让人先去试那个必然拒启的开关)。 */
|
|
184
185
|
type ConfigKnobSource = "env" | "posture" | "default";
|
|
185
186
|
export interface ConfigKnobRecord {
|
|
186
|
-
/** The env name. 3.0.0 起只有这一个名字 —— 负名别名已是墓碑(
|
|
187
|
+
/** The env name. 3.0.0 起只有这一个名字 —— 负名别名已是墓碑(`RETIRED_ENVS` 的 replaced 行,S-588 起在场即拒)。 */
|
|
187
188
|
name: string;
|
|
188
189
|
/** `opt-in` = default OFF, `opt-out` = default ON, `posture` = default derived from REQUIRE_PRINCIPAL ∧ infra. */
|
|
189
190
|
polarity: "opt-in" | "opt-out" | "posture";
|
|
@@ -192,28 +193,12 @@ export interface ConfigKnobRecord {
|
|
|
192
193
|
}
|
|
193
194
|
/** The registered boolean knobs, sorted by name — the data behind `logConfigDiagnostics`'s polarity table. */
|
|
194
195
|
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
|
|
196
|
+
/** [ref] B4 ②:D 族四枚(`PROJECT_MEMORY` / `CONFIG_LKG` / `HOST_BG` / `HOST_EXEC_SPOOL`)的正名 —— 缺省开,读点只读正名。
|
|
197
|
+
* 3.0.0 之前它们叫 `X_DISABLED`(读点上的双重否定);那四个旧负名是墓碑,在 `RETIRED_ENVS` 里(S-588 起在场即拒启,不论正名在不在)。
|
|
198
|
+
*
|
|
199
|
+
* LKG (last-known-good) persistence of the remote effective-config fetch. Read LAZILY at both main.ts sites
|
|
215
200
|
* rather than lifted onto `ServiceConfig`, so this stays a pure env knob; `loadConfig` pre-registers it (below)
|
|
216
|
-
* so its polarity row
|
|
201
|
+
* so its polarity row appears at boot with everything else. */
|
|
217
202
|
export declare const configLkgEnabled: () => boolean;
|
|
218
203
|
/** The `host` exec lane's background-shell capability kill switch (read per capability probe in remote-env-host.ts). */
|
|
219
204
|
export declare const hostBackgroundShellEnabled: () => boolean;
|
package/dist/config.js
CHANGED
|
@@ -10,7 +10,7 @@ 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
16
|
import { readFaceForPosture } from "./read-face-posture.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";
|
|
@@ -433,30 +448,9 @@ function boolEnv(name, def) {
|
|
|
433
448
|
registerKnob({ name, polarity: def ? "opt-out" : "opt-in", value, source });
|
|
434
449
|
return value;
|
|
435
450
|
}
|
|
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);
|
|
451
|
+
export const configLkgEnabled = () => boolEnv("CONFIG_LKG_ENABLED", true);
|
|
452
|
+
export const hostBackgroundShellEnabled = () => boolEnv("HOST_BG_ENABLED", true);
|
|
453
|
+
export const hostExecSpoolEnabled = () => boolEnv("HOST_EXEC_SPOOL_ENABLED", true);
|
|
460
454
|
export const REQUIRED_ENVS_BY_LANE = {
|
|
461
455
|
e2b: ["E2B_API_KEY"],
|
|
462
456
|
k8s: ["K8S_SANDBOX_IMAGE"],
|
|
@@ -977,7 +971,22 @@ function parseModelDomain() {
|
|
|
977
971
|
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
972
|
});
|
|
979
973
|
}
|
|
980
|
-
const
|
|
974
|
+
const cheapIsMain = cheapId !== undefined && cheapId !== "" && modelId !== "" && cheapId === modelId;
|
|
975
|
+
if (cheapIsMain) {
|
|
976
|
+
CONFIG_NOTICES.push({
|
|
977
|
+
event: "config_env_entry_cheap_same_as_main",
|
|
978
|
+
fields: {
|
|
979
|
+
env: "MODEL_CHEAP_ID",
|
|
980
|
+
model: modelId,
|
|
981
|
+
hint: `MODEL_CHEAP_ID names the same model as MODEL_ID ("${modelId}") — the env cheap slot is the main gateway under a different id, ` +
|
|
982
|
+
`so the same id IS the main model: no cheap slot is minted and subagent / summarize stay on the main model (roles -> default); ` +
|
|
983
|
+
`any other MODEL_CHEAP_* key set here describes that slot and is not applied. Set MODEL_CHEAP_ID to a different (cheaper) model ` +
|
|
984
|
+
`id to run those roles cheap, or declare two differently-named entries in config.d/models.json to give one upstream id a second ` +
|
|
985
|
+
`shape (price / reasoning / window); unset MODEL_CHEAP_ID to silence this line.`,
|
|
986
|
+
},
|
|
987
|
+
});
|
|
988
|
+
}
|
|
989
|
+
const cheapModel = cheapId && modelId !== "" && !cheapIsMain
|
|
981
990
|
? {
|
|
982
991
|
...model,
|
|
983
992
|
id: cheapId,
|
|
@@ -1526,7 +1535,7 @@ function parseMemoryConsolidationDriver() {
|
|
|
1526
1535
|
`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
1536
|
`spellings to preserve, unlike MEMORY_ENGINE's two-family word table.`);
|
|
1528
1537
|
}
|
|
1529
|
-
const
|
|
1538
|
+
const valve = word === "on" || word === "off" ? word : undefined;
|
|
1530
1539
|
const autoWord = requireNonBlank("MEMORY_AUTO_CONSOLIDATION", process.env.MEMORY_AUTO_CONSOLIDATION);
|
|
1531
1540
|
if (autoWord !== undefined && autoWord !== "on" && autoWord !== "off") {
|
|
1532
1541
|
throw new Error(`MEMORY_AUTO_CONSOLIDATION must be exactly "on" or "off", got ${JSON.stringify(autoWord)} — this knob decides whether memory content ` +
|
|
@@ -1558,12 +1567,12 @@ function parseMemoryConsolidationDriver() {
|
|
|
1558
1567
|
}
|
|
1559
1568
|
const scopesRaw = requireNonBlank("MEMORY_CONSOLIDATION_SCOPES", process.env.MEMORY_CONSOLIDATION_SCOPES);
|
|
1560
1569
|
const scopes = scopesRaw !== undefined ? csv("MEMORY_CONSOLIDATION_SCOPES") : [];
|
|
1561
|
-
|
|
1562
|
-
|
|
1563
|
-
|
|
1564
|
-
|
|
1565
|
-
|
|
1566
|
-
|
|
1570
|
+
return {
|
|
1571
|
+
...(valve !== undefined ? { valve } : {}),
|
|
1572
|
+
intervalSec,
|
|
1573
|
+
scopes,
|
|
1574
|
+
...(autoRunOnRecommendation !== undefined ? { autoRunOnRecommendation } : {}),
|
|
1575
|
+
};
|
|
1567
1576
|
}
|
|
1568
1577
|
function parseMemoryDomain(ctx) {
|
|
1569
1578
|
const { requirePrincipal } = ctx;
|
|
@@ -1615,7 +1624,7 @@ function parseMemoryDomain(ctx) {
|
|
|
1615
1624
|
},
|
|
1616
1625
|
});
|
|
1617
1626
|
}
|
|
1618
|
-
const projectMemoryEnabled =
|
|
1627
|
+
const projectMemoryEnabled = boolEnv("PROJECT_MEMORY_ENABLED", true);
|
|
1619
1628
|
const memoryOrgAdmissionMode = enumEnv("MEMORY_ORG_ADMISSION_MODE", "enforce", ["audit", "enforce"]);
|
|
1620
1629
|
const memoryOrgDirectoryJson = process.env.MEMORY_ORG_DIRECTORY_JSON || undefined;
|
|
1621
1630
|
const memoryOrgGrantTtlMs = numEnvBounded("MEMORY_ORG_GRANT_TTL_MS", "60000", 1000, 3_600_000);
|
|
@@ -1910,7 +1919,7 @@ function parseOrchestrationDomain(ctx) {
|
|
|
1910
1919
|
...(process.env.DEVICE_SUPERSEDE ? { supersede: enumEnv("DEVICE_SUPERSEDE", "stale_only", ["stale_only", "deny"]) } : {}),
|
|
1911
1920
|
}
|
|
1912
1921
|
:
|
|
1913
|
-
|
|
1922
|
+
ctx.configProvider === "local"
|
|
1914
1923
|
? { provider: "host" }
|
|
1915
1924
|
: undefined,
|
|
1916
1925
|
worktreeIsolation: boolEnv("WORKTREE_ISOLATION_ENABLED", false) && process.env.WORKTREE_REPO_ROOT
|
|
@@ -2081,7 +2090,7 @@ function parseGitApiKind(raw, baseUrl) {
|
|
|
2081
2090
|
return kind;
|
|
2082
2091
|
}
|
|
2083
2092
|
function parseIntegrationsDomain(ctx) {
|
|
2084
|
-
const { requirePrincipal } = ctx;
|
|
2093
|
+
const { requirePrincipal, configProvider } = ctx;
|
|
2085
2094
|
return {
|
|
2086
2095
|
pluginsAllowHosts: (process.env.PLUGINS_ALLOW_HOSTS ?? "github.com").split(",").map((h) => h.trim()).filter(Boolean),
|
|
2087
2096
|
configBootFetchBudgetMs: clampEnvWithWarn("CONFIG_BOOT_FETCH_BUDGET_MS", "1500", 0),
|
|
@@ -2092,7 +2101,7 @@ function parseIntegrationsDomain(ctx) {
|
|
|
2092
2101
|
skillsDir: env("SKILLS_DIR", defaultSkillsDir()),
|
|
2093
2102
|
centerCredentialRenewLeadMs: parseCenterCredentialRenewLeadMs(process.env.CENTER_CREDENTIAL_RENEW_LEAD_MS),
|
|
2094
2103
|
configCenter: process.env.SEMA_REGISTRY_URL ? parseConfigCenter(env("SEMA_REGISTRY_URL"), requirePrincipal) : undefined,
|
|
2095
|
-
configProvider
|
|
2104
|
+
...(configProvider !== undefined ? { configProvider } : {}),
|
|
2096
2105
|
configLocalDir: process.env.CONFIG_LOCAL_DIR || undefined,
|
|
2097
2106
|
a2aServe: parseA2aServe(),
|
|
2098
2107
|
};
|
|
@@ -2281,6 +2290,7 @@ export function loadConfig() {
|
|
|
2281
2290
|
CONFIG_KNOBS.clear();
|
|
2282
2291
|
const requirePrincipal = boolEnv("REQUIRE_PRINCIPAL", false);
|
|
2283
2292
|
const singleUserTurnkey = !requirePrincipal;
|
|
2293
|
+
const configProvider = parseConfigProvider();
|
|
2284
2294
|
const postureOn = (name, infraReady = true) => {
|
|
2285
2295
|
const explicit = parseBoolWord(name, process.env[name]);
|
|
2286
2296
|
const value = explicit ?? (singleUserTurnkey && infraReady);
|
|
@@ -2293,10 +2303,10 @@ export function loadConfig() {
|
|
|
2293
2303
|
const approval = parseApprovalDomain({ postureOn, singleUserTurnkey, infraReady: durableStoreReady });
|
|
2294
2304
|
const memory = parseMemoryDomain({ requirePrincipal });
|
|
2295
2305
|
const auth = parseAuthDomain({ requirePrincipal });
|
|
2296
|
-
const orchestration = parseOrchestrationDomain({ postureOn, singleUserTurnkey });
|
|
2306
|
+
const orchestration = parseOrchestrationDomain({ postureOn, singleUserTurnkey, configProvider });
|
|
2297
2307
|
const limitsHttp = parseLimitsHttpDomain();
|
|
2298
2308
|
const observability = parseObservabilityDomain();
|
|
2299
|
-
const integrations = parseIntegrationsDomain({ requirePrincipal });
|
|
2309
|
+
const integrations = parseIntegrationsDomain({ requirePrincipal, configProvider });
|
|
2300
2310
|
const { directApprovalDoor, durableApproval: dgDurable, approvalHmacKeys: dgHmacKeys } = approval;
|
|
2301
2311
|
const { principalJwtPubkeys, principalJwtIss, principalJwtAud, operatorPrincipals: dgOperators, authTokens } = auth;
|
|
2302
2312
|
const { enabled: imageBakesEnabled, runnerPrincipal: bakeRunnerPrincipal } = orchestration.imageBakes;
|
|
@@ -2330,14 +2340,6 @@ export function loadConfig() {
|
|
|
2330
2340
|
if (imageBakesEnabled && !Object.values(authTokens).includes(bakeRunnerPrincipal)) {
|
|
2331
2341
|
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
2342
|
}
|
|
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
2343
|
if (orchestration.leaderEnabled && store.dbBackend !== "mysql" && store.dbBackend !== "pg") {
|
|
2342
2344
|
throw new Error(`LEADER_ENABLED=true but DB_BACKEND=${store.dbBackend} (no external SQL store) is refused: a leader run is an ` +
|
|
2343
2345
|
"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 +2347,6 @@ export function loadConfig() {
|
|
|
2345
2347
|
"push. Point the service at a durable store (DB_BACKEND=mysql|pg), or turn the leader off (unset LEADER_ENABLED) " +
|
|
2346
2348
|
"(#193 车4 件2 / staleness ledger P1-9+P1-10 boot invariant)");
|
|
2347
2349
|
}
|
|
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
2350
|
if (orchestration.requireExecutionLane && orchestration.remoteExec === undefined) {
|
|
2368
2351
|
throw new Error(`REQUIRE_EXECUTION_LANE=true but NO execution lane is named on this deployment — refusing to start. ` +
|
|
2369
2352
|
`With no lane the engine falls back to its in-process stub environment, where at least the Bash tool is ` +
|
|
@@ -2387,7 +2370,7 @@ export function loadConfig() {
|
|
|
2387
2370
|
throw new Error(refusal);
|
|
2388
2371
|
}
|
|
2389
2372
|
}
|
|
2390
|
-
EXEC_LANE_NOTICE = buildExecLaneNotice(orchestration.remoteExec);
|
|
2373
|
+
EXEC_LANE_NOTICE = buildExecLaneNotice(orchestration.remoteExec, configProvider);
|
|
2391
2374
|
return attachConfigGroups({
|
|
2392
2375
|
...store,
|
|
2393
2376
|
...modelPlane,
|
package/dist/env-facts.d.ts
CHANGED
|
@@ -30,6 +30,14 @@ export interface SandboxEnvFacts {
|
|
|
30
30
|
note?: string;
|
|
31
31
|
};
|
|
32
32
|
}
|
|
33
|
+
/**
|
|
34
|
+
* 一条 scratchpad 路径能不能被**原样**宣告(core 的渲染上限 {@link MAX_PATH} 之内)—— scratchpad 的**铸造条件**之一。
|
|
35
|
+
* 两个铸点(server 自算 {@link ensureScratchpadDir}、壳供 {@link screenShellScratchpadDir})读这同一只:渲染不下的路径
|
|
36
|
+
* **根本不铸**(不建目录、不进写门豁免、不宣告 —— 宣告是根围栏那个洞的唯一写者,S-842,于是也不开洞)。
|
|
37
|
+
* 🔴 S-842 codex r1 [medium]:删 [ref] 折叠之后,只在宣告那一步丢长路径会留下「建了、豁免了、却不宣告也不开洞」的目录;
|
|
38
|
+
* 规则挪到铸点,四件同有同无。(远程车道的 `/tmp/scratchpad/<sessionId>` 恒在上限内:sessionId ≤ 64,见 prepareSpec。)
|
|
39
|
+
*/
|
|
40
|
+
export declare function scratchpadPathRenderable(path: string): boolean;
|
|
33
41
|
/** Compose envFacts from what the deployment knows at spec-prep. Returns undefined when nothing is known
|
|
34
42
|
* (no fact beats a vacuous one — an empty block would still spend prompt tokens). */
|
|
35
43
|
export declare function buildEnvFacts(opts: {
|
|
@@ -68,12 +76,14 @@ export declare function scratchpadDirFor(localDataRoot: string, sessionId: strin
|
|
|
68
76
|
* egress/pkgSource "don't declare what you don't know"). Lifecycle: the reaper primitives live below —
|
|
69
77
|
* {@link purgeScratchpadDir}(session delete)+ {@link sweepStaleScratchpads}(mtime 周期扫),keyed by the
|
|
70
78
|
* same scratchpadDirFor convention;接线(E21 DELETE purge + 周期 sweep)在 main.ts 侧(D 组)。 */
|
|
71
|
-
export declare function ensureScratchpadDir(localDataRoot: string, sessionId: string
|
|
79
|
+
export declare function ensureScratchpadDir(localDataRoot: string, sessionId: string,
|
|
80
|
+
/** 渲染不下(铸造条件不成立)时的那一行 —— 调用方交日志口(S-842 codex r1:不铸必须说出来,否则「本会话没有 scratchpad」无从排查)。 */
|
|
81
|
+
warn: (msg: string, meta?: object) => void): Promise<string | undefined>;
|
|
72
82
|
/** G3([ref] 壳侧承诺③/[ref]③,[ref]§四接单):**壳供** scratchpad 路径的验收门。
|
|
73
83
|
*
|
|
74
84
|
* 壳(cli)vendored getScratchpadDir 算出 per-session scratchpad 路径随 `TaskRequest.scratchpadDir` 发来;
|
|
75
|
-
* 接受后它**优先于** server 自算(ensureScratchpadDir),喂
|
|
76
|
-
* fs-write gate exemptDirs
|
|
85
|
+
* 接受后它**优先于** server 自算(ensureScratchpadDir),喂 resolve-spec 的同一单点(envFacts 宣告 ——
|
|
86
|
+
* core 据它开根围栏的洞,S-842 起是那个洞的唯一写者 —— + fs-write gate exemptDirs)。「没到货引擎忽略 = 无害 additive」。
|
|
77
87
|
*
|
|
78
88
|
* 信任边界:这是 exemptDirs **放宽**输入,验收 fail-closed(任何拒收形回落自算,不 fault 任务):
|
|
79
89
|
* - 多租户(requirePrincipal)一律拒——共享 worker 盘上租户自选豁免目录=超范围面;
|
package/dist/env-facts.js
CHANGED
|
@@ -5,6 +5,9 @@ import { dirCoversPath, isValidCwd, normalizeAbsolutePathLexically } from "./tas
|
|
|
5
5
|
const MAX_STR = 64;
|
|
6
6
|
const MAX_CAPS = 16;
|
|
7
7
|
const MAX_PATH = 160;
|
|
8
|
+
export function scratchpadPathRenderable(path) {
|
|
9
|
+
return path.length <= MAX_PATH;
|
|
10
|
+
}
|
|
8
11
|
const bound = (s) => (s.length > MAX_STR ? s.slice(0, MAX_STR) : s);
|
|
9
12
|
const bound2 = (s) => (s.length > MAX_PATH ? s.slice(0, MAX_PATH) : s);
|
|
10
13
|
export function buildEnvFacts(opts) {
|
|
@@ -17,7 +20,7 @@ export function buildEnvFacts(opts) {
|
|
|
17
20
|
...(caps.length > 0 ? { capabilities: caps } : {}),
|
|
18
21
|
...(opts.pkgSource ? { pkgSource: bound(opts.pkgSource) } : {}),
|
|
19
22
|
...(opts.egress ? { egress: opts.egress } : {}),
|
|
20
|
-
...(opts.scratchpadDir && opts.scratchpadDir
|
|
23
|
+
...(opts.scratchpadDir && scratchpadPathRenderable(opts.scratchpadDir) ? { scratchpadDir: opts.scratchpadDir } : {}),
|
|
21
24
|
...(opts.resumeFacts && Object.keys(opts.resumeFacts).length > 0
|
|
22
25
|
? { resumeFacts: { ...opts.resumeFacts, ...(opts.resumeFacts.note ? { note: bound2(opts.resumeFacts.note) } : {}) } }
|
|
23
26
|
: {}),
|
|
@@ -51,8 +54,17 @@ export function scratchpadSessionSegment(sessionId) {
|
|
|
51
54
|
export function scratchpadDirFor(localDataRoot, sessionId) {
|
|
52
55
|
return join(localDataRoot, "scratchpad", scratchpadSessionSegment(sessionId));
|
|
53
56
|
}
|
|
54
|
-
export async function ensureScratchpadDir(localDataRoot, sessionId) {
|
|
57
|
+
export async function ensureScratchpadDir(localDataRoot, sessionId, warn) {
|
|
55
58
|
const dir = scratchpadDirFor(localDataRoot, sessionId);
|
|
59
|
+
if (!scratchpadPathRenderable(dir)) {
|
|
60
|
+
warn("scratchpad_not_minted", {
|
|
61
|
+
reason: "too_long",
|
|
62
|
+
length: dir.length,
|
|
63
|
+
max: MAX_PATH,
|
|
64
|
+
note: "the session scratchpad path is longer than the engine renders verbatim, so it is not created, not exempted and not declared (a truncated path would be a fake one) — shorten LOCAL_DATA_ROOT to get a scratchpad back",
|
|
65
|
+
});
|
|
66
|
+
return undefined;
|
|
67
|
+
}
|
|
56
68
|
try {
|
|
57
69
|
await mkdir(dir, { recursive: true });
|
|
58
70
|
return dir;
|
|
@@ -89,7 +101,7 @@ export function screenShellScratchpadDir(raw, opts) {
|
|
|
89
101
|
opts.warn("shell_scratchpad_ignored", { reason: "not_absolute" });
|
|
90
102
|
return undefined;
|
|
91
103
|
}
|
|
92
|
-
if (raw
|
|
104
|
+
if (!scratchpadPathRenderable(raw)) {
|
|
93
105
|
opts.warn("shell_scratchpad_ignored", { reason: "too_long", length: raw.length, max: MAX_PATH });
|
|
94
106
|
return undefined;
|
|
95
107
|
}
|
|
@@ -70,6 +70,21 @@ export interface ExecutionLaneCaps {
|
|
|
70
70
|
* 静默继承一个没人表过态的缺席。
|
|
71
71
|
*/
|
|
72
72
|
readonly canonicalPathAuthority: LaneCapability;
|
|
73
|
+
/**
|
|
74
|
+
* 车PI 修复轮 ②(S-775 / clay C-R91A ②,主会话裁「按表不按词」;裁定语义 = **租户不共宿主**)—— 这条车道**分不分租户**。
|
|
75
|
+
* · `supported` = 每个租户的命令落在各自的目标上(每任务一只沙箱:e2b / k8s / local-docker;发起者自己的设备:device,
|
|
76
|
+
* 归属由 deviceId↔principal 绑定验过),或根本没有手(in-process:没有可共享的执行目标);
|
|
77
|
+
* · `single-user-only` = 所有任务落在**同一个共享目标**上(worker 宿主:host;一台常驻 SSH 对端:ssh;一台 ADB 设备:adb)
|
|
78
|
+
* ⇒ 只许单用户部署用。
|
|
79
|
+
* 本位**没有** `unsupported` 这一档(那会读成「谁都不许用」,不是车道事实)—— 型上收窄掉,每条车道必须在两档里表态。
|
|
80
|
+
* 🔴 **本位即执法源**(车PI 验修轮 F8,如实登记):唯一消费点是 `config-invariants.ts` 的 `multi-tenant-shared-lane`(经
|
|
81
|
+
* {@link laneCapabilityHolds} 落成布尔),能力表对账格里本位的「判别式」读的就是这张表 —— 自指,只证明消费点读的是本位,不证明本位
|
|
82
|
+
* 的值对。本位值的**独立作者** = 部署面 `deploy/sema-up/lib/lanes.json` 的 users 列(只有 host / e2b / k8s 三行)+ 行为格
|
|
83
|
+
* (`test/multi-tenant-shared-lane.test.ts`:ssh / adb 的工厂给两个租户的任务铸出的 env 报同一个 `workspaceHandle().sandboxId`)。
|
|
84
|
+
* local-docker / device / in-process 的 `supported` 今天没有独立预言机(e2b / local-docker 的句柄要连上才有,k8s 连前恒 `(unconnected)`,
|
|
85
|
+
* 都不是零成本格)—— 改这几行时按适配器事实自查。
|
|
86
|
+
*/
|
|
87
|
+
readonly tenantSeparation: Exclude<LaneCapability, "unsupported">;
|
|
73
88
|
}
|
|
74
89
|
/**
|
|
75
90
|
* per-lane 能力表。**穷举 switch,禁 default 臂**(hands-lane.ts:39-46 先例)。
|
|
@@ -20,6 +20,7 @@ export function executionLaneCaps(lane) {
|
|
|
20
20
|
hostFilePlane: YES,
|
|
21
21
|
coreRemoteExecutionEnv: NO,
|
|
22
22
|
canonicalPathAuthority: NO,
|
|
23
|
+
tenantSeparation: YES,
|
|
23
24
|
};
|
|
24
25
|
case "host":
|
|
25
26
|
return {
|
|
@@ -37,6 +38,7 @@ export function executionLaneCaps(lane) {
|
|
|
37
38
|
hostFilePlane: YES,
|
|
38
39
|
coreRemoteExecutionEnv: YES,
|
|
39
40
|
canonicalPathAuthority: YES,
|
|
41
|
+
tenantSeparation: SU,
|
|
40
42
|
};
|
|
41
43
|
case "e2b":
|
|
42
44
|
return {
|
|
@@ -54,6 +56,7 @@ export function executionLaneCaps(lane) {
|
|
|
54
56
|
hostFilePlane: NO,
|
|
55
57
|
coreRemoteExecutionEnv: YES,
|
|
56
58
|
canonicalPathAuthority: NO,
|
|
59
|
+
tenantSeparation: YES,
|
|
57
60
|
};
|
|
58
61
|
case "k8s":
|
|
59
62
|
return {
|
|
@@ -71,6 +74,7 @@ export function executionLaneCaps(lane) {
|
|
|
71
74
|
hostFilePlane: NO,
|
|
72
75
|
coreRemoteExecutionEnv: YES,
|
|
73
76
|
canonicalPathAuthority: YES,
|
|
77
|
+
tenantSeparation: YES,
|
|
74
78
|
};
|
|
75
79
|
case "ssh":
|
|
76
80
|
return {
|
|
@@ -88,6 +92,7 @@ export function executionLaneCaps(lane) {
|
|
|
88
92
|
hostFilePlane: NO,
|
|
89
93
|
coreRemoteExecutionEnv: YES,
|
|
90
94
|
canonicalPathAuthority: YES,
|
|
95
|
+
tenantSeparation: SU,
|
|
91
96
|
};
|
|
92
97
|
case "adb":
|
|
93
98
|
return {
|
|
@@ -105,6 +110,7 @@ export function executionLaneCaps(lane) {
|
|
|
105
110
|
hostFilePlane: NO,
|
|
106
111
|
coreRemoteExecutionEnv: YES,
|
|
107
112
|
canonicalPathAuthority: YES,
|
|
113
|
+
tenantSeparation: SU,
|
|
108
114
|
};
|
|
109
115
|
case "local-docker":
|
|
110
116
|
return {
|
|
@@ -122,6 +128,7 @@ export function executionLaneCaps(lane) {
|
|
|
122
128
|
hostFilePlane: NO,
|
|
123
129
|
coreRemoteExecutionEnv: YES,
|
|
124
130
|
canonicalPathAuthority: NO,
|
|
131
|
+
tenantSeparation: YES,
|
|
125
132
|
};
|
|
126
133
|
case "device":
|
|
127
134
|
return {
|
|
@@ -139,6 +146,7 @@ export function executionLaneCaps(lane) {
|
|
|
139
146
|
hostFilePlane: NO,
|
|
140
147
|
coreRemoteExecutionEnv: YES,
|
|
141
148
|
canonicalPathAuthority: YES,
|
|
149
|
+
tenantSeparation: YES,
|
|
142
150
|
};
|
|
143
151
|
}
|
|
144
152
|
}
|