@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.
- package/USAGE.md +6 -4
- package/deploy/sema-up/README.md +8 -1
- package/deploy/sema-up/chart/templates/server-v4pro.yaml +3 -0
- package/deploy/sema-up/chart/templates/server.yaml +3 -0
- package/deploy/sema-up/lib/lanes.json +56 -0
- package/deploy/sema-up/lib/lanes.py +179 -0
- package/deploy/sema-up/lib/parse-machines.py +7 -1
- package/deploy/sema-up/machines.example.yaml +5 -2
- package/deploy/sema-up/preflight.sh +7 -4
- package/deploy/sema-up/sema-up.sh +201 -24
- package/deploy/sema-up/stack/compose.yaml +4 -0
- package/deploy/sema-up/stack/sema-stack-cluster.yaml +2 -0
- package/dist/config.d.ts +16 -0
- package/dist/config.js +9 -9
- package/dist/http/resume-ctx-read.d.ts +7 -1
- package/dist/http/resume-ctx-read.js +3 -3
- package/dist/http/routes/capabilities.js +2 -1
- package/dist/http/routes/tasks.js +2 -2
- package/dist/observability/run-terminal.js +1 -0
- package/dist/plugins/approval-ask-store-sql.js +1 -1
- package/dist/plugins/background-agent-store-sql.js +2 -2
- package/dist/plugins/checkpoint-store-sql.js +1 -1
- package/dist/plugins/device-store-sql.js +1 -1
- package/dist/plugins/image-bake-store-sql.js +1 -1
- package/dist/plugins/image-index-sql.js +1 -1
- package/dist/plugins/leader-run-store-sql.js +1 -1
- package/dist/plugins/local-checkpoint-store.js +10 -2
- package/dist/plugins/mailbox-store-sql.js +5 -3
- package/dist/plugins/memory-engine-pg.js +1 -1
- package/dist/plugins/memory-engine-tidb.js +1 -1
- package/dist/plugins/memory-sync-store-pg.js +1 -1
- package/dist/plugins/permission-rule-store-sql.js +2 -2
- package/dist/plugins/run-store-sql.js +1 -1
- package/dist/plugins/session-event-payload.js +1 -1
- package/dist/plugins/session-policy-store-sql.js +4 -2
- package/dist/plugins/sql-json-column.d.ts +52 -7
- package/dist/plugins/sql-json-column.js +14 -3
- package/dist/plugins/task-list-store-sql.js +1 -1
- package/dist/plugins/usage-window-store-sql.js +1 -1
- package/dist/plugins/workflow-journal-store-sql.js +1 -1
- package/dist/plugins/workflow-run-store-sql.js +1 -1
- package/dist/trace/core-keyset-guard.d.ts +1 -1
- package/dist/trace/project.d.ts +11 -0
- package/dist/trace/project.js +1 -0
- package/package.json +4 -3
|
@@ -15,6 +15,9 @@
|
|
|
15
15
|
set -euo pipefail
|
|
16
16
|
|
|
17
17
|
HERE="$(cd "$(dirname "$0")" && pwd)"
|
|
18
|
+
# 执行车道表的唯一读者(S-599)。定义在**最前面**:`--help` 的词表也从表渲(手抄的词表会在加一条
|
|
19
|
+
# 车道时静默分叉),而 `-h` 在参数循环里就被处理,那时后面的函数还没定义。
|
|
20
|
+
lanes() { python3 "$HERE/lib/lanes.py" "$@"; }
|
|
18
21
|
MODE_INTERACTIVE="tty" # tty | json | none(--yes)
|
|
19
22
|
PROFILE="" USERS="" REGION="auto" CONF="" PROJECT="sema" TEARDOWN=false SKIP_PREFLIGHT=false
|
|
20
23
|
DATA_ROOT="" SERVER_PORT=8090 PG_PORT=5432 MINIO_PORT=9000 MINIO_CONSOLE_PORT=9001
|
|
@@ -37,7 +40,11 @@ usage() {
|
|
|
37
40
|
--dry-run 只打印将执行计划(不写 .env、不起任何容器)
|
|
38
41
|
--port=N 服务端口(默认 8090);--pg-port/--minio-port/--minio-console-port 同理
|
|
39
42
|
--gateway=URL --model=ID --model-key=K 模型面(带 agent 的档必需)
|
|
40
|
-
|
|
43
|
+
EOF
|
|
44
|
+
# 词表从 lib/lanes.json 渲(单一属主):加/删一条车道,这行自己跟着变
|
|
45
|
+
printf ' --sandbox=LANE %s(full-single / local-plus;kube 路径不选词,runtimeClass 自动)\n' \
|
|
46
|
+
"$(lanes choices | tr '\n' '|' | sed 's/|$//')"
|
|
47
|
+
cat <<'EOF'
|
|
41
48
|
--registry=BOOL 配置中心网站(默认 true;local-plus 默认 false)
|
|
42
49
|
--registry-image=REF sema-registry 镜像(registry=true 必需,可 -f 配置 SEMA_REGISTRY_IMAGE)
|
|
43
50
|
--git=MODE none(默认)|provision(一键自建 Gitea)|connect(接已有 git)
|
|
@@ -113,6 +120,79 @@ if [ -n "$CONF" ]; then
|
|
|
113
120
|
fi
|
|
114
121
|
|
|
115
122
|
WORK="$HOME/.sema-up/$PROJECT"; mkdir -p "$WORK"; chmod 700 "$HOME/.sema-up" "$WORK"
|
|
123
|
+
# 凭据/配置文件的坐标 —— 与 `has_env` 一起提到这里(S-599):车道决策要在模型面之前读既有 .env
|
|
124
|
+
# (冲突检测 + 「必需键已给过」判定),而生成段在几百行之后。纯搬家,零语义变。
|
|
125
|
+
ENVF="$WORK/.env"
|
|
126
|
+
has_env() { [ -f "$ENVF" ] && grep -q "^$1=..*" "$ENVF"; }
|
|
127
|
+
# ── 决策键的 `.env` 严格读口(7.96.0 合并复审)────────────────────────────────────────────────────
|
|
128
|
+
# 决策(租户形 / 车道)读到的每个键 —— `REQUIRE_PRINCIPAL`、`REMOTE_EXEC`、所选车道的必需键 —— 只经这一只读口。
|
|
129
|
+
# 它**不去猜** compose 会怎么读 `.env`:compose 的 dotenv 方言认 `export ` 前缀、键两侧空格、`K: v`、行内注释、
|
|
130
|
+
# 重复键末次生效、`$VAR` 展开……在 bash 里复刻其中任何一部分都是一份手抄方言,漏一处就是「读口看到
|
|
131
|
+
# false、容器拿到 true」(合并复审实证:`=false` 之后再来一行 `export …=true` ⇒ 本脚本落 host,容器拿到 true)。
|
|
132
|
+
# 所以本读口是一条**闭文法**,fail-closed:
|
|
133
|
+
# ① 凡是在任一方言下**可能**给 <键> 赋值的行(行首空白?+ `export `? + <键> + 空白? + `=` 或 `:`)至多一行;
|
|
134
|
+
# ② 那一行必须是规范形 `<键>=<值>`:值 = 空 | 一个不含空白 / 引号 / `#` / `$` / 反斜杠 / 反引号的词 |
|
|
135
|
+
# 一对同型引号包住的这样一个词(sema-up 自己写出的形全在这里面)。
|
|
136
|
+
# 违反任一条 ⇒ 响亮拒并点名行号(**不回显行内容**:必需键里有凭据)。通过 ⇒ 打印值(剥掉那对引号;空 = 未设)。
|
|
137
|
+
# 调用形恒为 `v="$(envf_get K)" || exit 1`(拒因走 stderr,退出码带回调用方)。
|
|
138
|
+
# ⚠️ **已知未闭环(如实披露,不是本读口的承诺范围)**:本读口按**物理行**判,不认引号跨行 —— 另一个键的跨行
|
|
139
|
+
# 单/双引号值里若有一行恰好长成 `K=<值>`,本读口会把它当成 K 的那一行。生成段的 setk / has_env / resetk
|
|
140
|
+
# 同样按物理行判(早于本读口就是这样),两者对这种输入的误读方向一致:容器拿到的是 compose 的缺省值。
|
|
141
|
+
# 统一形(另立一件):生成 .env 之后用 `docker compose config` 渲染一次,拿容器真正拿到的值与决策对账 ——
|
|
142
|
+
# 那一步以 compose 自己为唯一属主,本读口与 setk 的物理行假设可以一起退役。
|
|
143
|
+
ENVF_WORD='[^[:space:]"'"'"'#$`\\]'
|
|
144
|
+
envf_get() {
|
|
145
|
+
[ -f "$ENVF" ] || return 0
|
|
146
|
+
local k="$1" hits n nums line v
|
|
147
|
+
hits="$(grep -nE "^[[:space:]]*(export[[:space:]]+)?${k}[[:space:]]*[=:]" "$ENVF" || true)"
|
|
148
|
+
[ -n "$hits" ] || return 0
|
|
149
|
+
n="$(printf '%s\n' "$hits" | wc -l | tr -d '[:space:]')"
|
|
150
|
+
nums="$(printf '%s\n' "$hits" | cut -d: -f1 | paste -sd, -)"
|
|
151
|
+
if [ "$n" -gt 1 ]; then
|
|
152
|
+
log "⛔ $ENVF 里可能给 $k 赋值的行有 $n 行(第 $nums 行)—— 决策键只许一行"
|
|
153
|
+
log " ↳ 下一步:只留一行规范形 $k=<值>,其余删掉"
|
|
154
|
+
log " ↳ 为什么不挑一行用:compose 读 .env 的规则(export 前缀、键旁空格、重复键末次生效)与本脚本不同,挑哪一行都可能不是容器真正拿到的那一行"
|
|
155
|
+
return 1
|
|
156
|
+
fi
|
|
157
|
+
line="${hits#*:}"
|
|
158
|
+
if ! printf '%s\n' "$line" | grep -qE "^${k}=(${ENVF_WORD}*|\"${ENVF_WORD}*\"|'${ENVF_WORD}*')\$"; then
|
|
159
|
+
log "⛔ $ENVF 第 $nums 行给 $k 赋值,但不是本脚本认得的规范形"
|
|
160
|
+
log " ↳ 下一步:改成 $k=<值>(行首顶格、不带 export、键旁无空格、值是一个词或一对同型引号包住的一个词、行尾无注释),或删掉这一行"
|
|
161
|
+
log " ↳ 为什么不猜:这一行 compose 怎么读(注释、空白、展开)与本脚本怎么读一旦不一致,容器拿到的就不是决策读到的值"
|
|
162
|
+
return 1
|
|
163
|
+
fi
|
|
164
|
+
v="${line#*=}"
|
|
165
|
+
case "$v" in \"*\") v="${v#\"}"; v="${v%\"}" ;; \'*\') v="${v#\'}"; v="${v%\'}" ;; esac
|
|
166
|
+
printf '%s' "$v"
|
|
167
|
+
}
|
|
168
|
+
# ── 部署事实(租户形与车道两个决策共用)────────────────────────────────────────────────
|
|
169
|
+
# 事实 = `.env`(严格读口)∪ shell / `-f` 环境。compose 插值里 shell **优先于** --env-file(本脚本 teardown 段同一
|
|
170
|
+
# 事实),所以两处都非空而不同 ⇒ 拒并点名两处 —— 不「取第一个非空」:放行的唯一形是两处至多一个值,无论谁赢,
|
|
171
|
+
# 赢的都是它。设 FACT_VAL(可空 = 没有事实)与 FACT_SRC(来源描述)。直接调用,不进命令替换(拒要退整个脚本)。
|
|
172
|
+
deploy_fact() {
|
|
173
|
+
local k="$1" fenv fsh shsrc
|
|
174
|
+
fenv="$(envf_get "$k")" || exit 1
|
|
175
|
+
eval "fsh=\${$k:-}"
|
|
176
|
+
shsrc="${CONF:+-f $CONF / }shell 环境"
|
|
177
|
+
if [ -n "$fenv" ] && [ -n "$fsh" ] && [ "$fenv" != "$fsh" ]; then
|
|
178
|
+
log "⛔ 部署事实冲突:$ENVF 里 $k=$fenv,$shsrc 里 $k=$fsh —— 两处不一致"
|
|
179
|
+
log " ↳ 下一步:两处只留一处(或改成同一个值)"
|
|
180
|
+
log " ↳ 为什么两边都要查:compose 插值里 shell 环境**优先于** --env-file,只对齐 .env 不足以决定容器真正拿到哪个值"
|
|
181
|
+
exit 1
|
|
182
|
+
fi
|
|
183
|
+
FACT_VAL="${fenv:-$fsh}"
|
|
184
|
+
if [ -n "$fenv" ]; then FACT_SRC="$ENVF"; elif [ -n "$fsh" ]; then FACT_SRC="$shsrc"; else FACT_SRC=""; fi
|
|
185
|
+
}
|
|
186
|
+
# 本次决策落盘的值与部署事实不一致 ⇒ 拒并点名两处(不静默覆盖:替运维挑一个是替他做决定)。
|
|
187
|
+
# 只有**显式旗**(或交互里改掉了缺省)走得到这里:不带旗时决策就取自事实,两者恒等。
|
|
188
|
+
# fact_conflict <键> <事实值> <事实来源> <本次要写的值> <本次的旗>
|
|
189
|
+
fact_conflict() {
|
|
190
|
+
[ -n "$2" ] || return 0
|
|
191
|
+
[ "$2" = "$4" ] && return 0
|
|
192
|
+
log "⛔ 部署事实冲突:$3 里已有 $1=$2,本次 $5 要写 $1=$4"
|
|
193
|
+
log " ↳ 下一步(二选一):① 去掉 ${5%%=*} 重跑(不带旗 = 沿用这台部署的事实);② 真要改:先清掉 $3 里的 $1,再带 ${5%%=*} 重跑"
|
|
194
|
+
exit 1
|
|
195
|
+
}
|
|
116
196
|
# 协议流(--interactive=json 的 question/summary 行)固定走 fd3=原始 stdout——
|
|
117
197
|
# $(ask …) 的命令替换只捕获答案,协议行不被吞(真机验出的坑)
|
|
118
198
|
exec 3>&1
|
|
@@ -208,7 +288,8 @@ if $TEARDOWN; then
|
|
|
208
288
|
exit 0
|
|
209
289
|
fi
|
|
210
290
|
|
|
211
|
-
|
|
291
|
+
# (KVM 探测已随 `kata` 词退役删除 —— compose 路径不再有需要 KVM 的车道;kube 路径的 kata/runc
|
|
292
|
+
# 由 kube-up.sh 按 runtimeClass 自动定,preflight 的 kvm 项仍如实报给要走 kube 的人看。)
|
|
212
293
|
|
|
213
294
|
# ── 决策点(三轴+registry+git,§3b;问句都带「为什么问」)─────────────────────────────────────
|
|
214
295
|
[ -z "$PROFILE" ] && PROFILE=$(ask profile "要装成哪种形态?" "full-single" "决定这台机器上起哪些服务" \
|
|
@@ -216,9 +297,39 @@ KVM_OK=$([ -e /dev/kvm ] && echo true || echo false)
|
|
|
216
297
|
"data-center:只装数据面(PG+MinIO+备份)——agent 在你自己机器上,数据放这里" \
|
|
217
298
|
"full-cluster:k8s 准生产——3 机 ssh 装配 / 已有集群 kubeconfig / 本机装 k3s" \
|
|
218
299
|
"local-plus:本机只跑 agent,连一个已经部署好的 data-center 数据面")
|
|
219
|
-
|
|
300
|
+
# ── 两个决策(租户形 / 车道)同一条规则:决策 = 显式旗 ∨ 部署事实 ∨ 表缺省 ─────────────────────
|
|
301
|
+
# 「部署事实」只对 compose 的 agent 档成立(`.env` 就是它们的部署状态;kube 路径的状态在 helm values 里,
|
|
302
|
+
# data-center 不起 agent)。事实 → 决策词只经属主翻译,本脚本不抄第二份:
|
|
303
|
+
# · REQUIRE_PRINCIPAL → --users:只认 sema-up 自己写出的两形 true / false(别的写法 ⇒ 拒;不复刻 server 的布尔词表);
|
|
304
|
+
# · REMOTE_EXEC → --sandbox:`lanes choice-of`(车道表是唯一属主;表里没有的落盘值 ⇒ 拒并列在役词),见车道块。
|
|
305
|
+
# 不带旗重跑 ⇒ 沿用事实(多租部署重跑不必再带 --users=multi);显式旗与事实不一致 ⇒ fact_conflict 拒并点名两处。
|
|
306
|
+
# 🔴 为什么缺省不能先于事实(合并复审两条真案):把表缺省当成意图去和事实比,合法的无旗重跑就被拒;
|
|
307
|
+
# 反过来若只比不拒,保留的多租 `.env` 在缺省 single 下会被车道表判成 host —— 容器拿到 REQUIRE_PRINCIPAL=true ∧ host。
|
|
308
|
+
# 租户形 ⇒ 容器上 REQUIRE_PRINCIPAL 的值 —— 本脚本内**唯一**一处这条翻译。正向(生成段 setk、事实核对)与反向
|
|
309
|
+
# (事实 → --users 缺省)都经它:反向是拿两个租户词逐个过这只函数去比,不另写一张反表(与车道的 `lanes choice-of`
|
|
310
|
+
# 同一姿势)。⚠️ kube 路径另有一处同义翻译(`kube-up.sh` 的 V_REQ_PRINCIPAL,v7.95.1 既有,登记 DEBTS)。
|
|
311
|
+
principal_of_users() { [ "$1" = "multi" ] && echo true || echo false; }
|
|
312
|
+
USERS_DEF="single" RP_FACT="" RP_FACT_SRC=""
|
|
313
|
+
case "$PROFILE" in
|
|
314
|
+
full-single|local-plus)
|
|
315
|
+
deploy_fact REQUIRE_PRINCIPAL; RP_FACT="$FACT_VAL" RP_FACT_SRC="$FACT_SRC"
|
|
316
|
+
if [ -n "$RP_FACT" ]; then
|
|
317
|
+
USERS_DEF=""
|
|
318
|
+
for _u in single multi; do [ "$(principal_of_users "$_u")" = "$RP_FACT" ] && USERS_DEF="$_u"; done
|
|
319
|
+
if [ -z "$USERS_DEF" ]; then
|
|
320
|
+
log "⛔ 部署事实 REQUIRE_PRINCIPAL=$RP_FACT(来自 $RP_FACT_SRC):本脚本只读自己写出的两形 $(principal_of_users multi) / $(principal_of_users single)"
|
|
321
|
+
log " ↳ 下一步:改成 REQUIRE_PRINCIPAL=$(principal_of_users multi)(多人,身份隔离)或 REQUIRE_PRINCIPAL=$(principal_of_users single)(单人),再重跑"
|
|
322
|
+
log " ↳ 为什么不认别的写法:它们在 server 那边也许同样是布尔,但在这里认它们就得抄一份 server 的布尔词表,两份词表迟早分叉"
|
|
323
|
+
exit 1
|
|
324
|
+
fi
|
|
325
|
+
fi
|
|
326
|
+
;;
|
|
327
|
+
esac
|
|
328
|
+
[ -z "$USERS" ] && USERS=$(ask users "几个人用?" "$USERS_DEF" "单人可跳过全部权限配置,多人要开身份隔离" \
|
|
220
329
|
"single:就我一个(超级管理员 turnkey,零权限配置)" \
|
|
221
330
|
"multi:一个团队(身份隔离 ON,能力默认全开,之后可用 policy 收紧)")
|
|
331
|
+
REQUIRE_PRINCIPAL_WANT="$(principal_of_users "$USERS")"
|
|
332
|
+
fact_conflict REQUIRE_PRINCIPAL "$RP_FACT" "$RP_FACT_SRC" "$REQUIRE_PRINCIPAL_WANT" "--users=$USERS"
|
|
222
333
|
|
|
223
334
|
# registry:各档默认带上(clay 2026-07-12 拍;显式 --registry=false 可关)。
|
|
224
335
|
# local-plus 例外默认 false——它的数据面在远端,配置中心应随数据面部署,本机再起一个=两个配置中心。
|
|
@@ -237,16 +348,63 @@ if [ "$GIT_MODE" = "connect" ]; then
|
|
|
237
348
|
[ -z "$GIT_TOKEN" ] && { [ "$MODE_INTERACTIVE" = "none" ] && { log "⛔ --git=connect 需要 --git-token(该服务的 access token)"; exit 1; } || GIT_TOKEN=$(ask git_token "该服务的 access token?" "" "验证连通用;Gitea 还会用它自动建 OAuth 登录应用"); }
|
|
238
349
|
fi
|
|
239
350
|
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
351
|
+
# ── 执行车道(S-599;单一属主 = lib/lanes.json,本脚本里零车道词字面量)──────────────────────
|
|
352
|
+
# 病:compose 路径的 `kata` / `runc` 对**执行运行时零接线**(有 KVM 时 kata 还是默认值)⇒ .env 里
|
|
353
|
+
# 压根不出现 `REMOTE_EXEC` ⇒ core 落 StubExecutionEnv ⇒ 起来的 worker 一只手都没有,而三条部署
|
|
354
|
+
# 路径没有任何一处会红。现在:词表 / 默认 / 退役指路 / 落 .env 的键值**全部**从表渲,拒一律非零退出。
|
|
355
|
+
# 位置刻意在模型面(gateway/model)**之前**:选错车道时失败原因要来自车道门,不是「你还没填网关」。
|
|
356
|
+
LANE_ENV="" LANE_REQUIRED="" LANE_REMOTE_EXEC=""
|
|
357
|
+
case "$PROFILE" in
|
|
358
|
+
full-single|local-plus)
|
|
359
|
+
# 同一条规则的车道半场(见租户形块):决策 = --sandbox ∨ 部署事实(REMOTE_EXEC 反查成车道词)∨ 表缺省。
|
|
360
|
+
# 租户形已在上面定死(事实或显式旗),所以这里 `lanes default "$USERS"` 读到的是容器真正会拿到的租户形 ——
|
|
361
|
+
# 表里「multi 不许 host」那条规则的输入是真的。
|
|
362
|
+
deploy_fact REMOTE_EXEC; RE_FACT="$FACT_VAL" RE_FACT_SRC="$FACT_SRC" RE_FACT_CHOICE=""
|
|
363
|
+
if [ -n "$RE_FACT" ]; then RE_FACT_CHOICE="$(lanes choice-of "$RE_FACT")" || exit 1; fi
|
|
364
|
+
if [ -z "$SANDBOX_LANE" ]; then
|
|
365
|
+
if [ -n "$RE_FACT_CHOICE" ]; then LANE_DEF="$RE_FACT_CHOICE"; else LANE_DEF="$(lanes default "$USERS")" || exit 1; fi
|
|
366
|
+
LANE_OPTS=()
|
|
367
|
+
while IFS= read -r _o; do [ -n "$_o" ] && LANE_OPTS+=("$_o"); done < <(lanes options "$USERS")
|
|
368
|
+
SANDBOX_LANE=$(ask sandbox "agent 跑代码用哪种沙箱?" "$LANE_DEF" \
|
|
369
|
+
"决定 agent 的手落在哪里——不点名车道就是**没有手**(工具面整排关)" \
|
|
370
|
+
${LANE_OPTS[@]+"${LANE_OPTS[@]}"})
|
|
371
|
+
fi
|
|
372
|
+
LANE_ENV="$(lanes resolve "$USERS" "$SANDBOX_LANE")" || exit 1
|
|
373
|
+
LANE_REQUIRED="$(printf '%s\n' "$LANE_ENV" | sed -n 's/^#REQUIRED //p')"
|
|
374
|
+
LANE_ENV="$(printf '%s\n' "$LANE_ENV" | { grep -v '^#REQUIRED ' || true; })"
|
|
375
|
+
LANE_REMOTE_EXEC="$(printf '%s\n' "$LANE_ENV" | sed -n 's/^REMOTE_EXEC=//p')"
|
|
376
|
+
fact_conflict REMOTE_EXEC "$RE_FACT" "$RE_FACT_SRC" "$LANE_REMOTE_EXEC" "--sandbox=$SANDBOX_LANE"
|
|
377
|
+
# 选了车道却没给它的必需键 ⇒ 当场拒。否则 .env 照写、容器起来才在 server 的执行车道拒启门上被拒,
|
|
378
|
+
# 运维要翻容器日志才看得到「缺 X」,而那个键在 sema-up 的对话里从没被问过。
|
|
379
|
+
for _k in $LANE_REQUIRED; do
|
|
380
|
+
eval "_v=\${$_k:-}"
|
|
381
|
+
_ev="$(envf_get "$_k")" || exit 1
|
|
382
|
+
if [ -z "$_v" ] && [ -z "$_ev" ]; then
|
|
383
|
+
log "⛔ --sandbox=$SANDBOX_LANE(车道 $LANE_REMOTE_EXEC)需要 $_k,但 -f 配置 / 环境 / $ENVF 里都没有"
|
|
384
|
+
log " ↳ 下一步:-f 配置里加 $_k=…(或 export $_k=…)后重跑;换车道见 --help 的 --sandbox 词表"
|
|
385
|
+
exit 1
|
|
386
|
+
fi
|
|
387
|
+
done
|
|
388
|
+
;;
|
|
389
|
+
full-cluster)
|
|
390
|
+
# kube 路径**不选词**:runtime 由 kube-up.sh 按 runtimeClass `kata-qemu` 在不在自动定。收到
|
|
391
|
+
# `--sandbox`(或 -f 里的 SANDBOX_LANE)⇒ 响亮拒,不静默忽略 —— 一个「设了却没有任何效果」的
|
|
392
|
+
# 旋钮正是本批要消灭的那类病。
|
|
393
|
+
if [ -n "$SANDBOX_LANE" ]; then
|
|
394
|
+
log "⛔ --profile=full-cluster(kube 路径)不选沙箱词,收到 --sandbox=$SANDBOX_LANE"
|
|
395
|
+
log " ↳ 下一步:删掉 --sandbox / -f 里的 SANDBOX_LANE。kube 路径的 runtime 按 runtimeClass kata-qemu 在不在自动定(在 ⇒ kata VM,不在 ⇒ runc),车道恒 REMOTE_EXEC=k8s"
|
|
396
|
+
exit 1
|
|
397
|
+
fi
|
|
398
|
+
;;
|
|
399
|
+
*)
|
|
400
|
+
# data-center:不启 agent ⇒ 没有执行车道可言;收到 --sandbox 同样响亮拒(同上一条理由)。
|
|
401
|
+
if [ -n "$SANDBOX_LANE" ]; then
|
|
402
|
+
log "⛔ --profile=$PROFILE 不启 agent,没有执行车道;收到 --sandbox=$SANDBOX_LANE"
|
|
403
|
+
log " ↳ 下一步:删掉 --sandbox;要跑 agent 用 --profile=full-single 或 local-plus"
|
|
404
|
+
exit 1
|
|
405
|
+
fi
|
|
406
|
+
;;
|
|
407
|
+
esac
|
|
250
408
|
if [ "$PROFILE" = "full-single" ] || [ "$PROFILE" = "local-plus" ]; then
|
|
251
409
|
[ -z "$GATEWAY" ] && { [ "$MODE_INTERACTIVE" = "none" ] && { log "⛔ $PROFILE 档要跑 agent,需要 --gateway/--model(模型面)。例:--gateway=https://api.deepseek.com/v1 --model=deepseek-v4-flash --model-key=sk-…"; exit 1; } || GATEWAY=$(ask gateway "模型网关 baseURL?(OpenAI 兼容,如 https://api.deepseek.com/v1)" "" "引擎的大脑入口,没有它 agent 无法思考"); }
|
|
252
410
|
[ -z "$MODEL" ] && MODEL=$(ask model "模型 id?" "deepseek-v4-flash" "按成本推荐 flash;要更强推理换 pro")
|
|
@@ -349,7 +507,7 @@ if [ "$REGION" = "auto" ]; then
|
|
|
349
507
|
REGION=$(awk -v a="$t_cn" -v b="$t_gl" 'BEGIN{print (a<b)?"cn":"global"}')
|
|
350
508
|
fi
|
|
351
509
|
HOSTIP=$(host_ip)
|
|
352
|
-
log "region=$REGION profile=$PROFILE users=$USERS sandbox=${SANDBOX_LANE:-n/a} registry=$REGISTRY_ENABLED git=$GIT_MODE"
|
|
510
|
+
log "region=$REGION profile=$PROFILE users=$USERS sandbox=${SANDBOX_LANE:-n/a} lane=${LANE_REMOTE_EXEC:-n/a} registry=$REGISTRY_ENABLED git=$GIT_MODE"
|
|
353
511
|
|
|
354
512
|
# ── 数据盘选择(多盘机自动选大容量盘;--data-root 显式优先;macOS 不自动——bind 要经 VM 挂载,
|
|
355
513
|
# 显式给了才用)。首装落 .env 后不漂移:换盘=先 --teardown-volumes 迁数据再重装。──────────────
|
|
@@ -380,8 +538,6 @@ case "$PROFILE" in full-single|local-plus) COMPOSE_PROFILES="$COMPOSE_PROFILES,a
|
|
|
380
538
|
[ "$REGISTRY_ENABLED" = "true" ] && COMPOSE_PROFILES="$COMPOSE_PROFILES,registry"
|
|
381
539
|
[ "$GIT_MODE" = "provision" ] && COMPOSE_PROFILES="$COMPOSE_PROFILES,git"
|
|
382
540
|
COMPOSE_PROFILES="${COMPOSE_PROFILES#,}"; COMPOSE_PROFILES="${COMPOSE_PROFILES%,}"
|
|
383
|
-
ENVF="$WORK/.env"
|
|
384
|
-
has_env() { [ -f "$ENVF" ] && grep -q "^$1=..*" "$ENVF"; }
|
|
385
541
|
case ",$COMPOSE_PROFILES," in *,agent,*)
|
|
386
542
|
if [ -z "${SEMA_SERVER_IMAGE:-}" ] && ! has_env SEMA_SERVER_IMAGE; then
|
|
387
543
|
log "⛔ $PROFILE 档需要 sema-server 镜像。下一步:-f 配置里加 SEMA_SERVER_IMAGE=<registry>/sema-server:<tag>(默认=ghcr.io/sema-agent/sema-server:<tag> 公开仓匿名可拉;CN 兜底=内网 Gitea 仓;SWR 已弃用);镜像分发=D4"
|
|
@@ -436,11 +592,25 @@ PG_TUNE_FLAGS="-c max_connections=100 -c shared_buffers=${SB}MB -c effective_cac
|
|
|
436
592
|
# ── .env 生成(幂等:已存在则只补缺,凭据绝不轮换;0600) ───────────────────────────────────
|
|
437
593
|
genpw() { openssl rand -hex 24; }
|
|
438
594
|
touch "$ENVF"; chmod 600 "$ENVF"
|
|
439
|
-
|
|
595
|
+
# ── .env 的**唯一**写口:一次写入恰好一个物理行 ────────────────────────────────────────────────
|
|
596
|
+
# 病(7.96.0 修复验证轮 [high]):`--model-key=$'k\nREQUIRE_PRINCIPAL=true'` 经旧 setk 原样写成两行 —— 第二行是
|
|
597
|
+
# 决策检查**之后**才出现在 .env 里的一条 REQUIRE_PRINCIPAL,紧接着的 `setk REQUIRE_PRINCIPAL` 见键在就不写 ⇒
|
|
598
|
+
# 容器拿到多租 + host。严格读口只管「读」;写口与它是同一条约定的两半:.env 一行一条赋值。值里带换行 / 回车
|
|
599
|
+
# ⇒ **写之前**拒(先判后动:resetk 的删旧行也排在判定之后),拒因不回显值(可能是凭据)。
|
|
600
|
+
envf_check() {
|
|
601
|
+
case "$2" in
|
|
602
|
+
*$'\n'*|*$'\r'*)
|
|
603
|
+
log "⛔ 要写进 $ENVF 的 $1 的值里有换行 / 回车 —— .env 一行一条赋值,换行后的内容会被 compose 当成另一条赋值"
|
|
604
|
+
log " ↳ 下一步:去掉值里的换行(查 $1 对应的命令行参数或 -f 配置项)后重跑"
|
|
605
|
+
exit 1 ;;
|
|
606
|
+
esac
|
|
607
|
+
}
|
|
608
|
+
envf_put() { envf_check "$1" "$2"; printf '%s=%s\n' "$1" "$2" >> "$ENVF"; }
|
|
609
|
+
setk() { grep -q "^$1=" "$ENVF" || envf_put "$1" "$2"; }
|
|
440
610
|
# setk_new:同 setk,但新写入时返回 0(summary 用来判断「密码只显示一次」)
|
|
441
|
-
setk_new() { if grep -q "^$1=" "$ENVF"; then return 1; else
|
|
442
|
-
# resetk:非凭据键每次重写(翻转要落 .env,不然 teardown/汇总口径漂)
|
|
443
|
-
resetk() { grep -v "^$1=" "$ENVF" > "$ENVF.tmp" || true; mv "$ENVF.tmp" "$ENVF"; chmod 600 "$ENVF";
|
|
611
|
+
setk_new() { if grep -q "^$1=" "$ENVF"; then return 1; else envf_put "$1" "$2"; return 0; fi; }
|
|
612
|
+
# resetk:非凭据键每次重写(翻转要落 .env,不然 teardown/汇总口径漂)。先判后删:拒了就一行都不动。
|
|
613
|
+
resetk() { envf_check "$1" "$2"; grep -v "^$1=" "$ENVF" > "$ENVF.tmp" || true; mv "$ENVF.tmp" "$ENVF"; chmod 600 "$ENVF"; envf_put "$1" "$2"; }
|
|
444
614
|
setk SEMA_PROJECT "$PROJECT"
|
|
445
615
|
setk PG_PASSWORD "$(genpw)"
|
|
446
616
|
setk MINIO_PASSWORD "$(genpw)"
|
|
@@ -452,16 +622,23 @@ setk SERVER_PORT "$SERVER_PORT"
|
|
|
452
622
|
# data-center 数据面对外服务(agent 在别处连);full-single 数据面仅回环、只有 server 对外
|
|
453
623
|
if [ "$PROFILE" = "data-center" ]; then setk PG_BIND "0.0.0.0"; setk MINIO_BIND "0.0.0.0"; else setk PG_BIND "127.0.0.1"; setk MINIO_BIND "127.0.0.1"; fi
|
|
454
624
|
# PG_TUNE_FLAGS 每次重算(规格可能变;非凭据,轮换无害);引号:source 与 compose env-file 双方兼容
|
|
455
|
-
|
|
456
|
-
printf 'PG_TUNE_FLAGS="%s"\n' "$PG_TUNE_FLAGS" >> "$ENVF"
|
|
625
|
+
resetk PG_TUNE_FLAGS "\"$PG_TUNE_FLAGS\""
|
|
457
626
|
if [ "$PROFILE" = "full-single" ] || [ "$PROFILE" = "local-plus" ]; then
|
|
458
627
|
setk MODEL_GATEWAY_BASEURL "$GATEWAY"
|
|
459
628
|
setk MODEL_ID "$MODEL"
|
|
460
629
|
[ -n "$MODEL_KEY" ] && setk MODEL_API_KEY "$MODEL_KEY"
|
|
461
|
-
setk REQUIRE_PRINCIPAL "$
|
|
630
|
+
setk REQUIRE_PRINCIPAL "$REQUIRE_PRINCIPAL_WANT"
|
|
462
631
|
setk SANDBOX_PKG_SOURCE "$([ "$REGION" = "global" ] && echo global || echo '')"
|
|
463
632
|
[ -n "${SEMA_SERVER_IMAGE:-}" ] && setk SEMA_SERVER_IMAGE "$SEMA_SERVER_IMAGE"
|
|
464
|
-
|
|
633
|
+
# 执行车道落 .env(S-599:从上面 `lanes resolve` 的结果渲,脚本里零车道词)。
|
|
634
|
+
# **resetk 不是 setk**:`setk` 见键在就不改,而升级路径上最常见的既有值恰是空串 `REMOTE_EXEC=`
|
|
635
|
+
# (旧 compose 的 `${REMOTE_EXEC:-}` 渲出来的)——用 setk 就永远修不好那批无手部署。冲突检测在
|
|
636
|
+
# 上面的车道块里已做过(非空且不同 ⇒ 早已拒),所以这里的重写只会落在「空串 / 同值」两种情形上。
|
|
637
|
+
while IFS='=' read -r _k _v; do
|
|
638
|
+
[ -n "$_k" ] && resetk "$_k" "$_v"
|
|
639
|
+
done <<EOF_LANE
|
|
640
|
+
$LANE_ENV
|
|
641
|
+
EOF_LANE
|
|
465
642
|
fi
|
|
466
643
|
if [ "$PROFILE" = "local-plus" ]; then
|
|
467
644
|
setk AGENT_PG_HOST "$AGENT_PG_HOST"
|
|
@@ -94,7 +94,11 @@ services:
|
|
|
94
94
|
MODEL_API_KEY: ${MODEL_API_KEY:-}
|
|
95
95
|
REQUIRE_PRINCIPAL: ${REQUIRE_PRINCIPAL:-false}
|
|
96
96
|
SANDBOX_PKG_SOURCE: ${SANDBOX_PKG_SOURCE:-}
|
|
97
|
+
# 执行车道:值由 sema-up.sh 从 lib/lanes.json 渲进 .env(resetk,每次重写)。
|
|
98
|
+
# `REQUIRE_EXECUTION_LANE` 缺省 true —— 手工起这份 compose 而没点名车道时,server 拒启
|
|
99
|
+
# 并说「没有手」,而不是安静地起成一个什么都干不了的 worker(S-599)。
|
|
97
100
|
REMOTE_EXEC: ${REMOTE_EXEC:-}
|
|
101
|
+
REQUIRE_EXECUTION_LANE: ${REQUIRE_EXECUTION_LANE:-true}
|
|
98
102
|
E2B_API_KEY: ${E2B_API_KEY:-}
|
|
99
103
|
ports:
|
|
100
104
|
- "${SERVER_BIND:-0.0.0.0}:${SERVER_PORT:-8090}:8090"
|
|
@@ -171,6 +171,8 @@ spec:
|
|
|
171
171
|
- { name: MODEL_VISION, value: "false" }
|
|
172
172
|
- { name: SANDBOX_PKG_SOURCE, value: "__PKG_SOURCE__" }
|
|
173
173
|
- { name: REMOTE_EXEC, value: k8s }
|
|
174
|
+
# S-599:显式表态「这台有手」(车道被改没了 ⇒ 拒启,不是安静的无手 worker)
|
|
175
|
+
- { name: REQUIRE_EXECUTION_LANE, value: "true" }
|
|
174
176
|
- { name: K8S_API_URL, value: "https://kubernetes.default.svc" }
|
|
175
177
|
- { name: K8S_INSECURE_TLS, value: "true" }
|
|
176
178
|
- { name: K8S_NAMESPACE, value: sema-sandbox }
|
package/dist/config.d.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { type Model, type PathRuleBases, type RuleBehavior } from "@sema-agent/core";
|
|
2
|
+
import { type RemoteExecProvider } from "./execution-lane-caps.js";
|
|
2
3
|
import { type Autonomy } from "./runtime-governance.js";
|
|
3
4
|
import type { ServiceConfig, ServiceConfigFlat, ServiceConfigGroups } from "./config-types.js";
|
|
4
5
|
/** [ref] A1: the config TYPE face lives in the leaf module config-types.ts; re-exported here so
|
|
@@ -215,6 +216,21 @@ export declare const configLkgEnabled: () => boolean;
|
|
|
215
216
|
export declare const hostBackgroundShellEnabled: () => boolean;
|
|
216
217
|
/** The `host` exec lane's spool-file stdio form; false ⇒ the pre-1.226 pipe fallback (read per exec). */
|
|
217
218
|
export declare const hostExecSpoolEnabled: () => boolean;
|
|
219
|
+
/**
|
|
220
|
+
* 每条执行车道**必需的 env 键名**([ref] 的 fail-closed 拒启门按它点名缺件;见 `parseOrchestrationDomain`
|
|
221
|
+
* 里那处 `REMOTE_EXEC` 校验)。
|
|
222
|
+
*
|
|
223
|
+
* 键集 = `RemoteExecProvider`(判别联合派生,`execution-lane-caps.ts`):配置面加一条 provider 臂而
|
|
224
|
+
* 这里不加行 = **编译红**(codex R1-F2)。此前是 `Record<string, …>`,漏一行只表现为该词在 boot 期被判
|
|
225
|
+
* 「不是执行车道」—— fail-closed 但把「新车道」讲成「拼错了」,运维照着报文永远查不到成因。
|
|
226
|
+
*
|
|
227
|
+
* 🔴 **S-599:提到模块顶层导出**(此前是 `parseOrchestrationDomain` 的函数内局部)。零语义变,动机是
|
|
228
|
+
* 部署面需要**同一张表**:`deploy/sema-up/lib/lanes.json` 的每行必须登记它那条车道的必需键名,根 /
|
|
229
|
+
* review compose 的 environment 白名单必须把**全部**车道的必需键透传出去 —— 那两处若各手抄一份名单,
|
|
230
|
+
* 加一条车道就会静默分叉(选得到、起不来,而 config 的拒启报文里那个键在 compose 里根本不存在)。
|
|
231
|
+
* 门 = `test/deploy-lanes-contract.test.ts`,它读的是本表而不是任何副本。
|
|
232
|
+
*/
|
|
233
|
+
export declare const REQUIRED_ENVS_BY_LANE: Record<RemoteExecProvider, readonly string[]>;
|
|
218
234
|
/** [ref]① 1M dual-window (core 1.289 `Model.autoCompactTokens`): an EXPLICIT positive value wins (env
|
|
219
235
|
* `MODEL_AUTO_COMPACT_TOKENS` / center roster `autoCompactTokens`); else the **model-id table** applies —
|
|
220
236
|
* sonnet-5-family id ∧ contextWindow>=1e6 ⇒ 967000 (the faithful shape of CC's single-entry per-model
|
package/dist/config.js
CHANGED
|
@@ -450,6 +450,15 @@ export function boolEnvWithLegacyNegated(name, legacyNegatedName, def) {
|
|
|
450
450
|
export const configLkgEnabled = () => boolEnvWithLegacyNegated("CONFIG_LKG_ENABLED", "CONFIG_LKG_DISABLED", true);
|
|
451
451
|
export const hostBackgroundShellEnabled = () => boolEnvWithLegacyNegated("HOST_BG_ENABLED", "HOST_BG_DISABLED", true);
|
|
452
452
|
export const hostExecSpoolEnabled = () => boolEnvWithLegacyNegated("HOST_EXEC_SPOOL_ENABLED", "HOST_EXEC_SPOOL_DISABLED", true);
|
|
453
|
+
export const REQUIRED_ENVS_BY_LANE = {
|
|
454
|
+
e2b: ["E2B_API_KEY"],
|
|
455
|
+
k8s: ["K8S_SANDBOX_IMAGE"],
|
|
456
|
+
ssh: ["SSH_HOST", "SSH_USER", "SSH_PRIVATE_KEY"],
|
|
457
|
+
adb: ["ADB_SERIAL"],
|
|
458
|
+
host: [],
|
|
459
|
+
"local-docker": ["DOCKER_IMAGE"],
|
|
460
|
+
device: [],
|
|
461
|
+
};
|
|
453
462
|
function parseReadDenyPatternsEnv(raw) {
|
|
454
463
|
let arr;
|
|
455
464
|
try {
|
|
@@ -1618,15 +1627,6 @@ function parseOrchestrationDomain(ctx) {
|
|
|
1618
1627
|
{
|
|
1619
1628
|
const lane = process.env.REMOTE_EXEC;
|
|
1620
1629
|
if (lane !== undefined && lane !== "") {
|
|
1621
|
-
const REQUIRED_ENVS_BY_LANE = {
|
|
1622
|
-
e2b: ["E2B_API_KEY"],
|
|
1623
|
-
k8s: ["K8S_SANDBOX_IMAGE"],
|
|
1624
|
-
ssh: ["SSH_HOST", "SSH_USER", "SSH_PRIVATE_KEY"],
|
|
1625
|
-
adb: ["ADB_SERIAL"],
|
|
1626
|
-
host: [],
|
|
1627
|
-
"local-docker": ["DOCKER_IMAGE"],
|
|
1628
|
-
device: [],
|
|
1629
|
-
};
|
|
1630
1630
|
const required = Object.entries(REQUIRED_ENVS_BY_LANE).find(([word]) => word === lane)?.[1];
|
|
1631
1631
|
if (required === undefined) {
|
|
1632
1632
|
throw new Error(`REMOTE_EXEC="${lane}" is not an execution lane — valid: ${Object.keys(REQUIRED_ENVS_BY_LANE).join(" | ")}. ` +
|
|
@@ -11,10 +11,16 @@
|
|
|
11
11
|
* · `absent` —— 行不在(`reapCtx` 按设计在 run 终态后删掉它),**或**读腿抛了一个**不是坏体**的错
|
|
12
12
|
* (连接 / 超时 / 权限 / 店抖动):证不出它在就当它不在,**这是刻意保守**([ref] 方向锚:
|
|
13
13
|
* 拒绝可重试,搁浅不可)。
|
|
14
|
-
* · `corrupt` ——
|
|
14
|
+
* · `corrupt` —— 店给了它的**拒绝契约**(`StoreCorruptError`,`kind:"corrupt"` + 坐标)。
|
|
15
15
|
* **不折成缺席**:两者的运维动作完全不同(前者去查那一行,后者什么都不用做)。
|
|
16
16
|
* ⚠️ `not-text`(投影漂 = 我方的码病)**刻意不进这一态**,原样上抛、响亮成 500 —— 不许伪装成
|
|
17
17
|
* 「数据坏了」,更不许折成缺席(那会让一次代码漂看起来像「这条会话没有上下文」)。
|
|
18
|
+
*
|
|
19
|
+
* 🔴 **按「店的拒绝形」分类,不按「列错误类」**(S-608,7.95.1:这一句此前反了,整整一版)。修前本口
|
|
20
|
+
* 认的是属主谓词 `isJsonColumnBodyProblem`(= 一只 `SqlJsonColumnError`),而 SQL 两孪生的 `refusing()`
|
|
21
|
+
* 早把列错误**折**成了一只普通 `Error` ⇒ 折后的错落进下面那条「店抖动 ⇒ 当缺席」臂,坏体与缺席在
|
|
22
|
+
* 用户面上仍然同答 409 —— 正是 S-577 第二半要修的那个病,只是它当时只在 local 车道上修成了(那一侧
|
|
23
|
+
* 恰好没折码,把列错误漏了出来 = 巧合式正确)。两条车道现在同一份拒绝契约,本口只认它。
|
|
18
24
|
*/
|
|
19
25
|
import type { CheckpointCtx } from "./route-ctx.js";
|
|
20
26
|
export type ResumeCtxRead = {
|
|
@@ -1,12 +1,12 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { SqlJsonColumnError, StoreCorruptError } from "../plugins/sql-json-column.js";
|
|
2
2
|
export async function readResumeCtx(load) {
|
|
3
3
|
try {
|
|
4
4
|
const ctx = await load;
|
|
5
5
|
return ctx == null ? { kind: "absent" } : { kind: "ok", ctx };
|
|
6
6
|
}
|
|
7
7
|
catch (e) {
|
|
8
|
-
if (
|
|
9
|
-
return { kind:
|
|
8
|
+
if (e instanceof StoreCorruptError)
|
|
9
|
+
return { kind: e.kind, where: e.where };
|
|
10
10
|
if (e instanceof SqlJsonColumnError)
|
|
11
11
|
throw e;
|
|
12
12
|
return { kind: "absent" };
|
|
@@ -4,6 +4,7 @@ import { resolveModelApiKey } from "../../key-resolver.js";
|
|
|
4
4
|
import { isSealedKeyPoison } from "../../sealed-key.js";
|
|
5
5
|
import { cwdHonored, deviceCwdHonored } from "../../task-cwd.js";
|
|
6
6
|
import { taskApproverPostureHonored, taskWriteFaceRefusal } from "../../hosted-posture.js";
|
|
7
|
+
import { autonomyOverrides } from "../../runtime-governance.js";
|
|
7
8
|
import { DEVICE_PROTOCOL_VERSION, DEVICE_WS_PATH } from "../../device-ws-protocol.js";
|
|
8
9
|
import { a2aInjectionHonored } from "../../task-a2a.js";
|
|
9
10
|
import { mcpInjectionHonored } from "../../task-mcp.js";
|
|
@@ -136,7 +137,7 @@ export async function handleCapabilities(req, res, match, ctx) {
|
|
|
136
137
|
interactiveTools: true,
|
|
137
138
|
projectContext: cwdHonored(deps.config),
|
|
138
139
|
callerCwd: cwdHonored(deps.config) || deviceCwdHonored(deps.config),
|
|
139
|
-
taskWriteFaceOpen: taskWriteFaceRefusal(deps.config) === undefined,
|
|
140
|
+
taskWriteFaceOpen: taskWriteFaceRefusal(deps.config) === undefined && autonomyOverrides(deps.config.autonomy).writeFace !== "roots",
|
|
140
141
|
taskApproverPosture: taskApproverPostureHonored(deps.config),
|
|
141
142
|
executionLane: projectExecutionLaneCapability(deps.config),
|
|
142
143
|
mcpInjection: mcpInjectionHonored(deps.config),
|
|
@@ -13,7 +13,7 @@ import { clearTurnActivity, readTurnActivityMs, recordTurnActivity } from "../..
|
|
|
13
13
|
import { mintCancelledResult } from "../../run-cancel-context.js";
|
|
14
14
|
import { recordResourceWindow } from "../../resource-window.js";
|
|
15
15
|
import { redactSecrets } from "../../trace/redact.js";
|
|
16
|
-
import { contextUsageEventData, toolStartEventData, toolEndEventData, taskProgressEventData, taskNotificationEventData, compactedEventData, diagnosticsEventData, brainStatusEventData, steeringInjectedEventData, compactionOutcomeEventData, workspaceChangedEventData, wiringManifestFaceEventData, toolRosterDeltaEventData, toolProgressEventData, toolDisclosureEventData, humanInputEventData, appendModelUsageDelta, attachModelUsage, textEndEventData, liveIdentityFields } from "../../trace/project.js";
|
|
16
|
+
import { contextUsageEventData, turnEndEventData, toolStartEventData, toolEndEventData, taskProgressEventData, taskNotificationEventData, compactedEventData, diagnosticsEventData, brainStatusEventData, steeringInjectedEventData, compactionOutcomeEventData, workspaceChangedEventData, wiringManifestFaceEventData, toolRosterDeltaEventData, toolProgressEventData, toolDisclosureEventData, humanInputEventData, appendModelUsageDelta, attachModelUsage, textEndEventData, liveIdentityFields } from "../../trace/project.js";
|
|
17
17
|
import { cascadeConfig, runMeta } from "../run-meta.js";
|
|
18
18
|
import { scopedIdempotencyKey } from "../idempotency.js";
|
|
19
19
|
import { sendJson, sendError, sseHeaders } from "../send.js";
|
|
@@ -527,7 +527,7 @@ export async function handleTasks(req, res, match, ctx) {
|
|
|
527
527
|
?
|
|
528
528
|
textEnd === null || textEnd === undefined ? null : { type: "text_end", ...textEnd }
|
|
529
529
|
: ev.type === "turn_end"
|
|
530
|
-
? { type: "turn_end", ...(
|
|
530
|
+
? { type: "turn_end", ...turnEndEventData(ev), ...ident }
|
|
531
531
|
: ev.type === "message_committed"
|
|
532
532
|
? { type: "message_committed", entryId: e.entryId, role: e.role, ...(e.toolCallId !== undefined ? { toolCallId: e.toolCallId } : {}), ...ident }
|
|
533
533
|
: { type: "context_usage", ...contextUsageEventData(ev), ...ident };
|
|
@@ -262,7 +262,7 @@ function mapAskRow(r) {
|
|
|
262
262
|
return mapAskRowBody(r, askId);
|
|
263
263
|
}
|
|
264
264
|
catch (e) {
|
|
265
|
-
throw foldJsonColumn(e, (m) =>
|
|
265
|
+
throw foldJsonColumn(e, (m) => `approval-ask-store: ${m} — refusing rather than serve an ask row nobody can read`);
|
|
266
266
|
}
|
|
267
267
|
}
|
|
268
268
|
function mapAskRowBody(r, askId) {
|
|
@@ -15,7 +15,7 @@ function readRecordColumn(v, handle) {
|
|
|
15
15
|
return decodeJsonColumn(v, readAgentRecordBody, `${BACKGROUND_AGENT_TABLE}.record_json for handle ${handle}`);
|
|
16
16
|
}
|
|
17
17
|
catch (e) {
|
|
18
|
-
throw foldJsonColumn(e, (m) =>
|
|
18
|
+
throw foldJsonColumn(e, (m) => `BackgroundAgentStore: ${m} — refusing rather than serve a hollow agent record`);
|
|
19
19
|
}
|
|
20
20
|
}
|
|
21
21
|
function readUsageColumn(v, handle) {
|
|
@@ -23,7 +23,7 @@ function readUsageColumn(v, handle) {
|
|
|
23
23
|
return decodeNullableJsonColumn(v, readUsageBody, `${BACKGROUND_AGENT_TABLE}.usage_json for handle ${handle}`);
|
|
24
24
|
}
|
|
25
25
|
catch (e) {
|
|
26
|
-
throw foldJsonColumn(e, (m) =>
|
|
26
|
+
throw foldJsonColumn(e, (m) => `BackgroundAgentStore: ${m} — refusing rather than report a usage figure nobody can read`);
|
|
27
27
|
}
|
|
28
28
|
}
|
|
29
29
|
const PROJ_COLS = "scope, owner, session_scoped, session_id, parent_session_id, root_session_id, session_anchor, name, agent_type, status, spawned_at_ms, updated_at_ms, settled_at_ms, usage_json, rev, parked_checkpoint_token, park_claim_id, admitted_org_scopes, admitted_org_write_scope, record_json";
|
|
@@ -137,7 +137,7 @@ function refusing(what, read) {
|
|
|
137
137
|
return read();
|
|
138
138
|
}
|
|
139
139
|
catch (e) {
|
|
140
|
-
throw foldJsonColumn(e, (m) =>
|
|
140
|
+
throw foldJsonColumn(e, (m) => `SqlCheckpointStore: ${m} — refusing rather than serve ${what}`);
|
|
141
141
|
}
|
|
142
142
|
}
|
|
143
143
|
function readRuleOffersCell(cell) {
|
|
@@ -314,7 +314,7 @@ function readAuditDetail(raw, id) {
|
|
|
314
314
|
return decodeJsonColumn(raw, readJsonObjectColumn, `${DEVICE_AUDIT_TABLE}.detail for audit row ${id}`);
|
|
315
315
|
}
|
|
316
316
|
catch (e) {
|
|
317
|
-
throw foldJsonColumn(e, (m) =>
|
|
317
|
+
throw foldJsonColumn(e, (m) => `device-store: ${m} — refusing rather than serve an audit row whose detail nobody can read`);
|
|
318
318
|
}
|
|
319
319
|
}
|
|
320
320
|
function ph(dialect, n) {
|
|
@@ -130,7 +130,7 @@ function refusingBakeRow(read) {
|
|
|
130
130
|
return read();
|
|
131
131
|
}
|
|
132
132
|
catch (e) {
|
|
133
|
-
throw foldJsonColumn(e, (m) =>
|
|
133
|
+
throw foldJsonColumn(e, (m) => `image-bake-store: ${m} — refusing rather than serve a bake row nobody can read`);
|
|
134
134
|
}
|
|
135
135
|
}
|
|
136
136
|
export const IMAGE_BAKE_INDEXES = [
|
|
@@ -89,7 +89,7 @@ function refusingImageRow(read) {
|
|
|
89
89
|
return read();
|
|
90
90
|
}
|
|
91
91
|
catch (e) {
|
|
92
|
-
throw foldJsonColumn(e, (m) =>
|
|
92
|
+
throw foldJsonColumn(e, (m) => `image-index: ${m} — refusing rather than serve an image row nobody can read`);
|
|
93
93
|
}
|
|
94
94
|
}
|
|
95
95
|
export const IMAGE_INDEX_INDEXES = [
|
|
@@ -67,7 +67,7 @@ function readResultColumn(text, id) {
|
|
|
67
67
|
return decodeNullableJsonColumn(text, readNonNullJsonValueColumn, `${LEADER_RUN_TABLE}.result for leader run ${id}`);
|
|
68
68
|
}
|
|
69
69
|
catch (e) {
|
|
70
|
-
throw foldJsonColumn(e, (m) =>
|
|
70
|
+
throw foldJsonColumn(e, (m) => `leader_run: ${m} — refusing rather than serve this run as if it had finished empty-handed`);
|
|
71
71
|
}
|
|
72
72
|
}
|
|
73
73
|
function statusOf(raw, id) {
|
|
@@ -7,7 +7,7 @@ import { isApprovalGateKind } from "../tool-approval.js";
|
|
|
7
7
|
import { FileCheckpointStore, atomicWriteFile, executionVerdict, sanitizePathComponent, } from "@sema-agent/core";
|
|
8
8
|
import { approvalDecisionOfWinner, approvalPayloadFingerprint, boundedRuleOffers, boundedToolInput, pendingActionToolCallId, pendingRowPrecedes, TERMINAL_BACKSTOP_MS, TERMINAL_GRACE_MS } from "./checkpoint-store-sql.js";
|
|
9
9
|
import { quarantineUnreadableSidecar } from "./local-sidecar-quarantine.js";
|
|
10
|
-
import { createZodColumnReader, decodeJsonColumn, readStringArrayColumn } from "./sql-json-column.js";
|
|
10
|
+
import { createZodColumnReader, decodeJsonColumn, foldJsonColumn, readStringArrayColumn } from "./sql-json-column.js";
|
|
11
11
|
import { UNMANAGED_RETENTION } from "./retention-store-sql.js";
|
|
12
12
|
import { assertJsonColumnWritable, createObjectColumnReader } from "./sql-json-column.js";
|
|
13
13
|
function terminalAtOf(cp) {
|
|
@@ -36,6 +36,14 @@ function createCtxRecordReader() {
|
|
|
36
36
|
return { value: { ...envelope.value, ctx: payload.value } };
|
|
37
37
|
};
|
|
38
38
|
}
|
|
39
|
+
function refusingCtx(sessionId, read) {
|
|
40
|
+
try {
|
|
41
|
+
return read();
|
|
42
|
+
}
|
|
43
|
+
catch (e) {
|
|
44
|
+
throw foldJsonColumn(e, (m) => `LocalCheckpointStore: ${m} — refusing rather than serve a resume context nobody can read (session ${JSON.stringify(sessionId)})`);
|
|
45
|
+
}
|
|
46
|
+
}
|
|
39
47
|
export class LocalCheckpointStore {
|
|
40
48
|
retention = UNMANAGED_RETENTION;
|
|
41
49
|
durability = "durable";
|
|
@@ -316,7 +324,7 @@ export class LocalCheckpointStore {
|
|
|
316
324
|
const p = join(this.ctxDir, ctxFileOf(sessionId));
|
|
317
325
|
if (!existsSync(p))
|
|
318
326
|
return null;
|
|
319
|
-
const rec = decodeJsonColumn(readFileSync(p, "utf8"), createCtxRecordReader(), `checkpoint-ctx ${ctxFileOf(sessionId)} for session ${JSON.stringify(sessionId)}`);
|
|
327
|
+
const rec = refusingCtx(sessionId, () => decodeJsonColumn(readFileSync(p, "utf8"), createCtxRecordReader(), `checkpoint-ctx ${ctxFileOf(sessionId)} for session ${JSON.stringify(sessionId)}`));
|
|
320
328
|
return rec.sessionId === sessionId ? rec.ctx : null;
|
|
321
329
|
}
|
|
322
330
|
async reapCtx(cutoff) {
|