@sema-agent/server 7.95.0 → 7.96.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (45) hide show
  1. package/USAGE.md +6 -4
  2. package/deploy/sema-up/README.md +8 -1
  3. package/deploy/sema-up/chart/templates/server-v4pro.yaml +3 -0
  4. package/deploy/sema-up/chart/templates/server.yaml +3 -0
  5. package/deploy/sema-up/lib/lanes.json +56 -0
  6. package/deploy/sema-up/lib/lanes.py +179 -0
  7. package/deploy/sema-up/lib/parse-machines.py +7 -1
  8. package/deploy/sema-up/machines.example.yaml +5 -2
  9. package/deploy/sema-up/preflight.sh +7 -4
  10. package/deploy/sema-up/sema-up.sh +201 -24
  11. package/deploy/sema-up/stack/compose.yaml +4 -0
  12. package/deploy/sema-up/stack/sema-stack-cluster.yaml +2 -0
  13. package/dist/config.d.ts +16 -0
  14. package/dist/config.js +9 -9
  15. package/dist/http/resume-ctx-read.d.ts +7 -1
  16. package/dist/http/resume-ctx-read.js +3 -3
  17. package/dist/http/routes/capabilities.js +2 -1
  18. package/dist/http/routes/tasks.js +2 -2
  19. package/dist/observability/run-terminal.js +1 -0
  20. package/dist/plugins/approval-ask-store-sql.js +1 -1
  21. package/dist/plugins/background-agent-store-sql.js +2 -2
  22. package/dist/plugins/checkpoint-store-sql.js +1 -1
  23. package/dist/plugins/device-store-sql.js +1 -1
  24. package/dist/plugins/image-bake-store-sql.js +1 -1
  25. package/dist/plugins/image-index-sql.js +1 -1
  26. package/dist/plugins/leader-run-store-sql.js +1 -1
  27. package/dist/plugins/local-checkpoint-store.js +10 -2
  28. package/dist/plugins/mailbox-store-sql.js +5 -3
  29. package/dist/plugins/memory-engine-pg.js +1 -1
  30. package/dist/plugins/memory-engine-tidb.js +1 -1
  31. package/dist/plugins/memory-sync-store-pg.js +1 -1
  32. package/dist/plugins/permission-rule-store-sql.js +2 -2
  33. package/dist/plugins/run-store-sql.js +1 -1
  34. package/dist/plugins/session-event-payload.js +1 -1
  35. package/dist/plugins/session-policy-store-sql.js +4 -2
  36. package/dist/plugins/sql-json-column.d.ts +52 -7
  37. package/dist/plugins/sql-json-column.js +14 -3
  38. package/dist/plugins/task-list-store-sql.js +1 -1
  39. package/dist/plugins/usage-window-store-sql.js +1 -1
  40. package/dist/plugins/workflow-journal-store-sql.js +1 -1
  41. package/dist/plugins/workflow-run-store-sql.js +1 -1
  42. package/dist/trace/core-keyset-guard.d.ts +1 -1
  43. package/dist/trace/project.d.ts +11 -0
  44. package/dist/trace/project.js +1 -0
  45. package/package.json +4 -3
package/USAGE.md CHANGED
@@ -24,7 +24,7 @@ npm run dev # 或 bun run src/main.ts → http://<
24
24
  ```bash
25
25
  cd sema-server
26
26
  MODEL_GATEWAY_BASEURL=https://<your-gateway>/v1 MODEL_ID=<your-model-name> \
27
- SESSION_BACKEND=memory \
27
+ SESSION_BACKEND=memory REMOTE_EXEC=host \
28
28
  docker compose up --build
29
29
  # 起来后 OA 只认 http://<host>:8090(LB)
30
30
  ```
@@ -40,10 +40,12 @@ docker compose up --build
40
40
  ```bash
41
41
  IMAGE_REGISTRY=<registry-namespace> \
42
42
  DB_BACKEND=mysql MYSQL_HOST=… MYSQL_USER=… MYSQL_PASSWORD=… MYSQL_DATABASE=sema_server \
43
- MODEL_GATEWAY_BASEURL=… MODEL_ID=<your-model-name> \
43
+ MODEL_GATEWAY_BASEURL=… MODEL_ID=<your-model-name> REMOTE_EXEC=e2b E2B_API_KEY=… \
44
44
  docker compose up --build --scale service=4 # 4 个无状态 worker,nginx LB 扇发,TiDB 协调
45
45
  ```
46
46
 
47
+ > 🔴 7.96.0 起根 `docker-compose.yml` 的 `REMOTE_EXEC`(执行车道)是**必填**:不设 ⇒ `docker compose config` 当场失败(此前静默起成一个没有手的 worker)。单人自用 `REMOTE_EXEC=host`(手落在 worker 容器里);多人 / 生产选隔离车道 `e2b`(配 `E2B_API_KEY`)或 `k8s`(配 `K8S_SANDBOX_IMAGE`)。
48
+
47
49
  > 拓扑:LB(入口)→ N 个一样的 worker → 共享 SQL 库(数据中心;MySQL 协议或 PG)+ 模型网关。无中央调度器,跨 worker 靠 DB 协调。
48
50
 
49
51
  **可选 — 多网关 failover + Anthropic 路由(消除单网关 SPOF)**
@@ -793,8 +795,8 @@ TASK_WRITE_FACE_ESCALATION=allow # 词表 allow|refuse;坏词启动期响亮
793
795
  - **续跑腿**:一条在托管形机器上带着 `bypassPermissions` 的 parked 任务,赎回时被同一道门挡住,
794
796
  上层折成 `409 resume_blocked_by_policy`(`blockedBy: request.host_posture_refused`)。
795
797
  运维的解法是 `TASK_WRITE_FACE_ESCALATION=allow`。
796
- - **能力面披露位**:`GET /v1/capabilities` 的 `taskWriteFaceOpen`(`false` ⇒ 发那个键会拿 422)——
797
- 壳不必靠踩一次 422 才学会。
798
+ - **能力面披露位**:`GET /v1/capabilities` 的 `taskWriteFaceOpen`(`false` ⇒ 发那个键会拿 422;或 —— 在
799
+ `AUTONOMY=read-only|plan` 的部署上(7.96.0 起)—— 受理但写面被钳回 roots)——壳不必靠踩一次才学会。
798
800
  - **`AUTONOMY=read-only|plan` 一行未动**。⚠️ 两者叠加时**本门先说话**:托管形 + `AUTONOMY=read-only`
799
801
  + `bypassPermissions` 的请求现在是 422,而 7.94 及以前它会跑起来(写被 autonomy 禁掉)。
800
802
  要保留旧结局就 `TASK_WRITE_FACE_ESCALATION=allow`(写面仍被 autonomy 压回 roots)。
@@ -62,7 +62,7 @@ summary 会打印手动接法。
62
62
  | `--gateway=URL` | — | 模型网关 baseURL(OpenAI 兼容);带 agent 的档必需 |
63
63
  | `--model=ID` | `deepseek-v4-flash` | 模型 id |
64
64
  | `--model-key=K` | — | 模型 API key(也可 -f 里 MODEL_API_KEY) |
65
- | `--sandbox=LANE` | kvm 有→`kata` | `kata`/`e2b-cloud`/`runc`(full-single) |
65
+ | `--sandbox=LANE` | single→`host`;multi→`e2b-cloud` | `host`/`e2b-cloud`(full-single / local-plus)。**执行车道**=agent 的手落在哪里,不点名就是没有手;词表与必需键的单一属主=`lib/lanes.json`。`kata`/`runc`/`e2b-self` 已退役(compose 路径对它们零接线),收到即非零退出并指路。kube 路径(full-cluster)**不选词**:runtime 按 runtimeClass `kata-qemu` 在不在自动定,车道恒 `REMOTE_EXEC=k8s` |
66
66
  | `--registry=BOOL` | `true`(local-plus `false`) | 配置中心网站开关 |
67
67
  | `--registry-image=REF` | — | sema-registry 镜像(registry=true 必需) |
68
68
  | `--git=MODE` | `none` | `none`/`provision`/`connect` |
@@ -105,6 +105,13 @@ data-center:2c/2G/20G)以下 BLOCK——确实无法稳定部署,提前警告不
105
105
  全部凭据生成一次、落 `~/.sema-up/<project>/.env`(0600),重跑**绝不轮换**;
106
106
  summary 里管理员密码只显示一次(新生成那次),不进日志不进 git。
107
107
 
108
+ 重跑时租户形与车道同一条规则:**决策 = 显式旗 ∨ 部署事实 ∨ 表缺省**。部署事实 = `.env`(与 shell / `-f` 环境)里
109
+ 上一次落下的 `REQUIRE_PRINCIPAL` / `REMOTE_EXEC` —— **不带旗重跑即沿用它**(多租部署、e2b 部署都不必再带旗);显式
110
+ `--users` / `--sandbox` 与事实不一致 ⇒ 非零退出并点名两处(不静默覆盖;要换就先清掉旧值)。`.env` 与 shell / `-f`
111
+ 两处都给了且不同 ⇒ 同样拒。读 `.env` 的这几个键(连同所选车道的必需键,如 `E2B_API_KEY`)只认 sema-up 自己写出的
112
+ **规范形**:一行、行首顶格 `K=<值>`,值是一个词或一对同型引号包住的一个词;`export ` 前缀、键旁空格、`K: v`、
113
+ 行内注释、重复行 ⇒ 非零退出并点名行号(不去猜 compose 会怎么读那一行)。`REQUIRE_PRINCIPAL` 只认 `true` / `false`。
114
+
108
115
  ## 验收
109
116
 
110
117
  ```bash
@@ -57,6 +57,9 @@ spec:
57
57
  - { name: MODEL_VISION, value: {{ .Values.server.modelVision | quote }} }
58
58
  - { name: SANDBOX_PKG_SOURCE, value: {{ .Values.server.sandboxPkgSource | quote }} }
59
59
  - { name: REMOTE_EXEC, value: k8s }
60
+ {{/* S-599:车道点名了就显式表态「这台有手」——REQUIRE_EXECUTION_LANE 让「车道被谁改没了」
61
+ 变成拒启而不是安静的无手 worker。server 侧默认仍 off,sema-up 产物一律写 true。 */}}
62
+ - { name: REQUIRE_EXECUTION_LANE, value: "true" }
60
63
  - { name: K8S_API_URL, value: "https://kubernetes.default.svc" }
61
64
  {{- if .Values.server.k8sInsecureTls }}
62
65
  - { name: K8S_INSECURE_TLS, value: "true" }
@@ -70,6 +70,9 @@ spec:
70
70
  - { name: REQUIRE_PRINCIPAL, value: {{ .Values.server.requirePrincipal | default "false" | quote }} }
71
71
  - { name: SANDBOX_PKG_SOURCE, value: {{ .Values.server.sandboxPkgSource | quote }} }
72
72
  - { name: REMOTE_EXEC, value: k8s }
73
+ {{/* S-599:车道点名了就显式表态「这台有手」——REQUIRE_EXECUTION_LANE 让「车道被谁改没了」
74
+ 变成拒启而不是安静的无手 worker。server 侧默认仍 off,sema-up 产物一律写 true。 */}}
75
+ - { name: REQUIRE_EXECUTION_LANE, value: "true" }
73
76
  - { name: K8S_API_URL, value: "https://kubernetes.default.svc" }
74
77
  {{- if .Values.server.k8sInsecureTls }}
75
78
  - { name: K8S_INSECURE_TLS, value: "true" }
@@ -0,0 +1,56 @@
1
+ {
2
+ "lanes": [
3
+ {
4
+ "path": "compose",
5
+ "choice": "host",
6
+ "users": ["single"],
7
+ "env": {
8
+ "REMOTE_EXEC": "host",
9
+ "REQUIRE_EXECUTION_LANE": "true"
10
+ },
11
+ "isolation": "unsupported",
12
+ "note": "server 容器本身就是沙箱:agent 的手落在这只容器里(worktree 隔离在、OS 级隔离不在)。只给单用户 —— 多租户下一个租户的输入会把手指向另一个租户的文件与进程。"
13
+ },
14
+ {
15
+ "path": "compose",
16
+ "choice": "e2b-cloud",
17
+ "users": ["single", "multi"],
18
+ "env": {
19
+ "REMOTE_EXEC": "e2b",
20
+ "REQUIRE_EXECUTION_LANE": "true",
21
+ "E2B_API_KEY": null
22
+ },
23
+ "isolation": "supported",
24
+ "note": "E2B 云沙箱:每任务一只 microVM。E2B_API_KEY 由运维自备(sema-up 只登记键名,绝不写值)。"
25
+ },
26
+ {
27
+ "path": "kube",
28
+ "choice": null,
29
+ "users": ["single", "multi"],
30
+ "env": {
31
+ "REMOTE_EXEC": "k8s",
32
+ "REQUIRE_EXECUTION_LANE": "true",
33
+ "K8S_SANDBOX_IMAGE": null
34
+ },
35
+ "isolation": "supported",
36
+ "note": "kube 路径**不选词**:runtime 由 kube-up.sh 按 runtimeClass `kata-qemu` 在不在自动定(在 ⇒ kata VM,不在 ⇒ runc 容器),沙箱镜像由 chart/清单给。本行只为对表(能力表 / 必需键 / REQUIRE_EXECUTION_LANE 三面),不进 --sandbox 词表。"
37
+ }
38
+ ],
39
+ "retired": [
40
+ {
41
+ "choice": "kata",
42
+ "why": "compose 路径对 kata 零接线:选了它 .env 里不会出现任何 REMOTE_EXEC,core 落 StubExecutionEnv ⇒ 起来的 worker 一只手都没有。有 KVM 的机器上它还曾是默认值,所以这是最贵的一个死词。",
43
+ "goto": "要 VM 级隔离走 kube 路径:sema-up.sh --profile=full-cluster(runtimeClass kata-qemu 在场即自动用 kata,不在场退 runc);留在单机 compose 上就选 host 或 e2b-cloud。"
44
+ },
45
+ {
46
+ "choice": "runc",
47
+ "why": "同 kata:compose 路径对它零接线,选了等于没选车道(无手)。",
48
+ "goto": "单机 compose 上「容器隔离」的真形就是 host(server 容器自己);要独立沙箱容器走 kube 路径 --profile=full-cluster。"
49
+ },
50
+ {
51
+ "choice": "e2b-self",
52
+ "why": "自托管 E2B 端点从未接线:它只在 preflight 文案与 machines.yaml 模板里出现过,没有任何一处把它翻成 REMOTE_EXEC。",
53
+ "goto": "先用 e2b-cloud(填 E2B_API_KEY);自托管端点的接线尚未排期。"
54
+ }
55
+ ]
56
+ }
@@ -0,0 +1,179 @@
1
+ #!/usr/bin/env python3
2
+ """lanes.py — `lanes.json`(执行车道表)的唯一读者。
3
+
4
+ ## 为什么是一个文件而不是 sema-up.sh 里的几行 bash
5
+
6
+ 车道这件事此前被五份产物各写一遍(`sema-up.sh` 一行 `setk REMOTE_EXEC e2b`、三份 kube 清单的
7
+ 硬编码 `k8s`、根/review compose 的零车道白名单),于是「有 KVM 的机器默认选 kata ⇒ 零接线 ⇒
8
+ 起来的 worker 没有手」这件事没有任何一处会红。收成一张表之后,**表是唯一属主**:脚本里不许再
9
+ 出现任何一个车道词字面量,选项串、默认值、退役词指路、落 .env 的键值**全部**从这里渲。
10
+
11
+ ## 子命令(全部以非零退出表示「拒」,理由写 stderr)
12
+
13
+ choices 打印 compose 路径的在役词(每行一个;--help / README 的词表渲它)
14
+ default <users> 打印该租户形下的缺省 choice
15
+ options <users> 每行一个 `ask` 选项串 `value:label[:infeasible=原因|建议]`
16
+ resolve <users> <choice> 校验并打印该车道要落 .env 的 `KEY=VALUE` 行(仅有值的键);
17
+ 必需但由运维自备的键名打印成 `#REQUIRED <NAME>` 注释行(「选了却没给」的早拒读它)
18
+ choice-of <REMOTE_EXEC 值> 部署事实 → `--sandbox` 词的反查(决策 = 显式旗 ∨ 部署事实 ∨ 表缺省):上一次落下的 `REMOTE_EXEC=<值>`
19
+ 是哪条 compose 车道;表里没有这个落盘值 / 反查不唯一 ⇒ 拒
20
+
21
+ ## 拒的三种形(全部 fail-closed,没有一条静默降级)
22
+
23
+ ① 退役词(`retired`):非零 + why + 指路;
24
+ ② 不在表里的词:非零 + 列出在役词表;
25
+ ③ 该租户形不许选的词(`users` 不含它):非零 + 列出该租户形可选的词。
26
+ """
27
+ import json
28
+ import os
29
+ import sys
30
+
31
+ HERE = os.path.dirname(os.path.abspath(__file__))
32
+ TABLE_PATH = os.path.join(HERE, "lanes.json")
33
+
34
+
35
+ def die(msg):
36
+ sys.stderr.write("[sema-up] ⛔ %s\n" % msg)
37
+ raise SystemExit(1)
38
+
39
+
40
+ def table():
41
+ with open(TABLE_PATH, encoding="utf-8") as fh:
42
+ return json.load(fh)
43
+
44
+
45
+ def compose_rows(t):
46
+ return [r for r in t["lanes"] if r["path"] == "compose"]
47
+
48
+
49
+ def offered(t, users):
50
+ """该租户形下**可选**的 compose 行(顺序 = 表的声明序)。"""
51
+ return [r for r in compose_rows(t) if users in r["users"]]
52
+
53
+
54
+ def find(t, choice):
55
+ for r in compose_rows(t):
56
+ if r["choice"] == choice:
57
+ return r
58
+ return None
59
+
60
+
61
+ def retired_of(t, choice):
62
+ for r in t["retired"]:
63
+ if r["choice"] == choice:
64
+ return r
65
+ return None
66
+
67
+
68
+ def cmd_choices():
69
+ return "\n".join(r["choice"] for r in compose_rows(table()))
70
+
71
+
72
+ def cmd_default(users):
73
+ t = table()
74
+ rows = offered(t, users)
75
+ if not rows:
76
+ die("users=%s 在车道表里没有任何可选车道 —— 表坏了" % users)
77
+ return rows[0]["choice"]
78
+
79
+
80
+ def cmd_options(users):
81
+ """`ask` 的选项串。该租户形不许选的车道**仍然列出**,但标 infeasible ——
82
+ 运维要看见「这条路存在、但对你这个形不行、该怎么办」,而不是一个凭空少了一项的菜单
83
+ (`ask` 自己会在选中 infeasible 项时 fail-loud 退出,见 sema-up.sh 的 ask())。"""
84
+ t = table()
85
+ lines = []
86
+ for r in compose_rows(t):
87
+ label = r["note"].split("。")[0]
88
+ if users in r["users"]:
89
+ lines.append("%s:%s" % (r["choice"], label))
90
+ else:
91
+ lines.append(
92
+ "%s:%s:infeasible=users=%s 不许选它(隔离性 %s)|%s"
93
+ % (
94
+ r["choice"],
95
+ label,
96
+ users,
97
+ r["isolation"],
98
+ "改选 %s,或走 kube 路径 --profile=full-cluster"
99
+ % " / ".join(x["choice"] for x in offered(t, users)),
100
+ )
101
+ )
102
+ return "\n".join(lines)
103
+
104
+
105
+ def cmd_choice_of(remote_exec):
106
+ """部署事实 → `--sandbox` 词的**反查**(sema-up 的决策规则:决策 = 显式旗 ∨ 部署事实 ∨ 表缺省)。
107
+
108
+ `.env` / shell 里已有的 `REMOTE_EXEC=<值>` 是上一次落下的哪条 compose 车道 —— 表是唯一属主,脚本不抄词。
109
+ 表里没有这个落盘值 ⇒ 拒并列在役词(不猜、不把它讲成「冲突」);两行落同一个值 ⇒ 反查不唯一,表坏了
110
+ (`test/deploy-lanes-contract.test.ts` ⑥ 同时咬住「compose 行的 REMOTE_EXEC 两两不同」)。"""
111
+ t = table()
112
+ rows = [r for r in compose_rows(t) if r["env"].get("REMOTE_EXEC") == remote_exec]
113
+ if len(rows) == 1:
114
+ return rows[0]["choice"]
115
+ if len(rows) > 1:
116
+ die("车道表里有 %d 条 compose 车道都落 REMOTE_EXEC=%s —— 反查不唯一,表坏了" % (len(rows), remote_exec))
117
+ die(
118
+ "部署事实 REMOTE_EXEC=%s 不是这条部署路径上任何一条车道的落盘值 —— 在役:%s\n"
119
+ " ↳ 下一步:清掉 .env / shell 里的 REMOTE_EXEC 后重跑(不带 --sandbox 即取表缺省),或带 --sandbox 选一条在役车道;"
120
+ "k8s 车道走 kube 路径 --profile=full-cluster"
121
+ % (
122
+ remote_exec,
123
+ " / ".join("%s(REMOTE_EXEC=%s)" % (r["choice"], r["env"]["REMOTE_EXEC"]) for r in compose_rows(t)),
124
+ )
125
+ )
126
+
127
+
128
+ def cmd_resolve(users, choice):
129
+ t = table()
130
+ gone = retired_of(t, choice)
131
+ if gone is not None:
132
+ die(
133
+ "--sandbox=%s 已退役:%s\n ↳ 下一步:%s"
134
+ % (choice, gone["why"], gone["goto"])
135
+ )
136
+ row = find(t, choice)
137
+ if row is None:
138
+ die(
139
+ "--sandbox=%s 不是这条部署路径上的车道 —— 可选:%s\n"
140
+ " ↳ kube 路径(--profile=full-cluster)**不选词**:runtime 按 runtimeClass 自动定"
141
+ % (choice, " / ".join(r["choice"] for r in compose_rows(t)))
142
+ )
143
+ if users not in row["users"]:
144
+ die(
145
+ "--sandbox=%s 不能用在 --users=%s 上(该车道隔离性 %s)—— 改选:%s,或走 kube 路径 "
146
+ "--profile=full-cluster(k8s 车道,每任务一只 pod)"
147
+ % (
148
+ choice,
149
+ users,
150
+ row["isolation"],
151
+ " / ".join(r["choice"] for r in offered(t, users)),
152
+ )
153
+ )
154
+ out = []
155
+ for k, v in row["env"].items():
156
+ out.append("%s=%s" % (k, v) if v is not None else "#REQUIRED %s" % k)
157
+ return "\n".join(out)
158
+
159
+
160
+ def main(argv):
161
+ if len(argv) < 2:
162
+ die("用法: lanes.py choices|default|options|resolve|choice-of …")
163
+ verb = argv[1]
164
+ if verb == "choices" and len(argv) == 2:
165
+ print(cmd_choices())
166
+ elif verb == "default" and len(argv) == 3:
167
+ print(cmd_default(argv[2]))
168
+ elif verb == "options" and len(argv) == 3:
169
+ print(cmd_options(argv[2]))
170
+ elif verb == "choice-of" and len(argv) == 3:
171
+ print(cmd_choice_of(argv[2]))
172
+ elif verb == "resolve" and len(argv) == 4:
173
+ print(cmd_resolve(argv[2], argv[3]))
174
+ else:
175
+ die("用法: lanes.py choices | default <users> | options <users> | resolve <users> <choice> | choice-of <REMOTE_EXEC 值>")
176
+
177
+
178
+ if __name__ == "__main__":
179
+ main(sys.argv)
@@ -101,7 +101,13 @@ def main(path):
101
101
  emit("DP_S3", dp.get("s3", "deploy"))
102
102
  emit("DP_BACKUP", dp.get("backup", "true"))
103
103
  sb = sections.get("sandbox", {})
104
- emit("SANDBOX_LANE", sb.get("lane"))
104
+ # S-599:`sandbox.lane` 是**死词**,已删。machines.yaml 只走 kube 路径,而 kube-up.sh 的 runtime
105
+ # 是按 runtimeClass `kata-qemu` 在不在自动定的(kube-up.sh:94-96),从来不读 SANDBOX_LANE ——
106
+ # 发射它只会让运维以为自己选到了什么。**响亮拒**而不是静默忽略:一个设了却没有任何效果的旋钮,
107
+ # 正是本批要消灭的那类病(留着它,下一个人还会照模板填)。
108
+ if sb.get("lane") is not None:
109
+ die("machines.yaml 的 sandbox.lane 已退役(kube 路径的 runtime 按 runtimeClass kata-qemu "
110
+ "在不在自动定,不读该键)——删掉那一行即可;compose 路径的车道见 sema-up.sh --help 的 --sandbox")
105
111
  emit("E2B_ENDPOINT", sb.get("e2b_endpoint"))
106
112
  emit("E2B_API_KEY", sb.get("e2b_api_key"))
107
113
  rg = sections.get("registry", {})
@@ -34,8 +34,11 @@ data_plane:
34
34
  backup: true # PG 定时备份到 S3(data-center/full 默认 true)
35
35
 
36
36
  sandbox:
37
- lane: kata # kata(默认,需 KVM,预检不过不给)| e2b-cloud | e2b-self | runc
38
- # e2b_endpoint: … # lane=e2b-self 时必填(校验连通+模板可用)
37
+ # ⚠️ 这里**不选沙箱词**:machines.yaml 走的是 kube 路径,runtime 由 kube-up.sh 按 runtimeClass
38
+ # `kata-qemu` 在不在自动定(在 ⇒ kata VM,不在 ⇒ runc 容器),执行车道恒 REMOTE_EXEC=k8s。
39
+ # 旧模板里的 `lane:` 是死词(从来没有任何一处读它),已退役——还写着它的 yaml 会被 fail-loud 拒。
40
+ # 单机 compose 路径的车道选择见 sema-up.sh --help 的 --sandbox。
41
+ # e2b_endpoint: … # 自托管 E2B 端点(尚未接线,登记为 v2 候选)
39
42
  # e2b_api_key: …
40
43
 
41
44
  registry:
@@ -97,12 +97,15 @@ if [ -n "$BEST_MNT" ] && [ "$BEST_MNT" != "/" ] && [ "$BEST_GB" -gt "${DISK_GB:-
97
97
  add WARN disk-alt "$BEST_MNT ${BEST_GB}G avail" "存在比 / 更大的数据盘(多盘机)" "--data-root=$BEST_MNT/sema-data(sema-up 未显式指定时会自动选它)"
98
98
  fi
99
99
 
100
- # ── KVM(kata lane 前提;无=不给选 kata,建议替代,不算 BLOCK——lane 可换) ───────────────────
100
+ # ── KVM(**kube 路径**的 kata runtimeClass 前提;compose 路径已无需要 KVM 的车道,S-599)────────
101
+ # 有 KVM ⇒ 走 full-cluster 时 kata-deploy 能装上,沙箱得 VM 级隔离;没有 ⇒ kube 路径退 runc 容器
102
+ # 隔离(仍是真隔离,只是弱于 VM)。两种都不是 BLOCK:单机 compose 路径的车道(host / e2b-cloud)
103
+ # 与 KVM 无关。
101
104
  if [ -e /dev/kvm ] && grep -qE 'vmx|svm' /proc/cpuinfo 2>/dev/null; then
102
- add OK kvm "available" "kata lane 可用(默认建议)" ""
105
+ add OK kvm "available" "kube 路径可用 kata runtimeClass(VM 级沙箱隔离)" ""
103
106
  else
104
- add WARN kvm "unavailable" "无虚拟化支持,kata lane 不可部署" \
105
- "①云上开嵌套虚拟化/换 bare metal ②e2b-cloud(填 key 即用)或 e2b-self ③runc(容器隔离,弱于 VM,不建议生产)"
107
+ add WARN kvm "unavailable" "无虚拟化支持:走 kube 路径时沙箱退 runc 容器隔离(弱于 VM)" \
108
+ "①云上开嵌套虚拟化/换 bare metal ②单机 compose 路径选 --sandbox=e2b-cloud(E2B 云沙箱,每任务一只 microVM)"
106
109
  fi
107
110
 
108
111
  # ── 端口(按 --ports/profile 决定检查面) ──────────────────────────────────────────────────────