@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
|
@@ -17,6 +17,13 @@ set -euo pipefail
|
|
|
17
17
|
HERE="$(cd "$(dirname "$0")" && pwd)"
|
|
18
18
|
# `.env` 文法(写口 envf_representable + envf_encode / 读口 envf_canonical_line)的唯一定义 —— 写读两半从这一处取判据。
|
|
19
19
|
. "$HERE/lib/envf.sh"
|
|
20
|
+
# 数据根归属(S-851)与「壳怎么接」的渲染(S-850)各一处定义;preflight / kube-up / cluster-up 读同一份。
|
|
21
|
+
. "$HERE/lib/dataroot.sh"
|
|
22
|
+
. "$HERE/lib/shell-access.sh"
|
|
23
|
+
# 取值旗的形(S-621):判据与词表(--profile / --users / --region / --git / --kube)的唯一定义;--help 的词表也从这里渲。
|
|
24
|
+
. "$HERE/lib/flagshape.sh"
|
|
25
|
+
# 日志一律打到 stderr(stdout 留给 --interactive=json 的协议行)。定义在最前面:参数一解析完就要判形,那时下面的函数都还没定义。
|
|
26
|
+
log() { printf '[sema-up] %s\n' "$*" >&2; }
|
|
20
27
|
# 执行车道表的唯一读者(S-599)。定义在**最前面**:`--help` 的词表也从表渲(手抄的词表会在加一条
|
|
21
28
|
# 车道时静默分叉),而 `-h` 在参数循环里就被处理,那时后面的函数还没定义。
|
|
22
29
|
lanes() { python3 "$HERE/lib/lanes.py" "$@"; }
|
|
@@ -32,22 +39,28 @@ PG_URL="${PG_URL:-}" S3_ENDPOINT="${S3_ENDPOINT:-}" S3_ACCESS_KEY="${S3_ACCESS_K
|
|
|
32
39
|
usage() {
|
|
33
40
|
cat <<'EOF'
|
|
34
41
|
用法: sema-up.sh [选项]
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
--
|
|
42
|
+
EOF
|
|
43
|
+
# 闭集词表从 lib/flagshape.sh 渲(单一属主):加 / 删一个词,这几行自己跟着变
|
|
44
|
+
printf ' --profile=P %s\n' "$(printf '%s' "$FLAG_WORDS_PROFILE" | tr ' ' '|')"
|
|
45
|
+
printf ' --users=U %s\n' "$(printf '%s' "$FLAG_WORDS_USERS" | tr ' ' '|')"
|
|
46
|
+
printf ' --region=R %s\n' "$(printf '%s' "$FLAG_WORDS_REGION" | tr ' ' '|')"
|
|
47
|
+
cat <<'EOF'
|
|
38
48
|
--interactive=json 机器可读交互协议(sema cloud deploy 渲染用)
|
|
39
49
|
--yes 全默认无人值守(与 -f 组合)
|
|
40
50
|
-f FILE site 配置(env 形式;machines.yaml=固定形状 mini-parser)
|
|
41
|
-
--project=NAME compose project 名(默认 sema;并行试验用)
|
|
51
|
+
--project=NAME compose project 名(默认 sema;并行试验用)。也是工作目录 ~/.sema-up/<名> 与自动数据根
|
|
52
|
+
<盘>/sema-data/<名> 的一段 ⇒ 只收小写字母 / 数字 / - / _,首字符是字母或数字
|
|
53
|
+
--data-root=DIR 数据卷 bind 到这条目录(缺省:多盘机自动选大盘 <盘>/sema-data/<project>);首装落定不漂移,
|
|
54
|
+
一条数据根只归一个项目(根里的 .sema-up-project 标记,别的项目的根一律拒)
|
|
42
55
|
--dry-run 只打印将执行计划(不写 .env、不起任何容器)
|
|
43
|
-
--port=N 服务端口(默认 8090);--pg-port/--minio-port/--minio-console-port 同理
|
|
56
|
+
--port=N 服务端口(默认 8090);--pg-port/--minio-port/--minio-console-port 同理(端口旗只收 1–65535 的整数)
|
|
44
57
|
--gateway=URL --model=ID --model-key=K 模型面(带 agent 的档必需)
|
|
45
58
|
EOF
|
|
46
59
|
# 词表从 lib/lanes.json 渲(单一属主):加/删一条车道,这行自己跟着变
|
|
47
60
|
printf ' --sandbox=LANE %s(full-single / local-plus;kube 路径不选词,runtimeClass 自动)\n' \
|
|
48
61
|
"$(lanes choices | tr '\n' '|' | sed 's/|$//')"
|
|
49
62
|
cat <<'EOF'
|
|
50
|
-
--registry=
|
|
63
|
+
--registry=true|false 配置中心网站(默认 true;local-plus 默认 false;只收这两个词)
|
|
51
64
|
--registry-image=REF sema-registry 镜像(registry=true 必需,可 -f 配置 SEMA_REGISTRY_IMAGE)
|
|
52
65
|
--git=MODE none(默认)|provision(一键自建 Gitea)|connect(接已有 git)
|
|
53
66
|
--git-url=URL --git-token=T git=connect 的已有服务(Gitea/GitHub/任意三方 git;自动识别,证书不校验)
|
|
@@ -121,6 +134,39 @@ if [ -n "$CONF" ]; then
|
|
|
121
134
|
esac
|
|
122
135
|
fi
|
|
123
136
|
|
|
137
|
+
# ── 取值旗的形(S-621;判据与词表单一属主 lib/flagshape.sh)─────────────────────────────────────────────
|
|
138
|
+
# 一条规则:每只取值旗都过形判,在建任何目录、写任何文件之前(工作目录 ~/.sema-up/<project> 就建在这一段的下一行)。
|
|
139
|
+
# 判的是**生效值**:命令行旗 ∪ -f 配置(env 形的 -f 在命令行之后 source,同名变量盖过旗 ⇒ 称呼里连变量名一起报)。
|
|
140
|
+
# bool / enum 的空 = 没给(走缺省或询问);命令行上写了 `--旗=` 却没给值 ⇒ 不是没给,照判(空串不在任何闭集里)。
|
|
141
|
+
# 交互答案由 ask() 判(必须是那道题的选项之一)。自由文本旗(URL / 镜像引用 / 路径 / 凭据 / 模型 id)不判,盘点见 README。
|
|
142
|
+
_empty_flags=" "; for a in "$@"; do case "$a" in --*=) _empty_flags="$_empty_flags${a%=} " ;; esac; done
|
|
143
|
+
flag_given() { case "$_empty_flags" in *" $1 "*) return 0 ;; esac; [ -n "$2" ]; }
|
|
144
|
+
# 项目名是 compose 项目名,也是工作目录与自动数据根的一段路径(S-851):`..` / `/` / 空串会把两者指到别处。
|
|
145
|
+
flag_project "--project(PROJECT)" "$PROJECT"
|
|
146
|
+
if flag_given --profile "$PROFILE"; then flag_enum "--profile(PROFILE)" "$PROFILE" $FLAG_WORDS_PROFILE; fi
|
|
147
|
+
if flag_given --users "$USERS"; then flag_enum "--users(USERS)" "$USERS" $FLAG_WORDS_USERS; fi
|
|
148
|
+
flag_enum "--region(REGION)" "$REGION" $FLAG_WORDS_REGION
|
|
149
|
+
if flag_given --registry "$REGISTRY_ENABLED"; then flag_bool "--registry(REGISTRY_ENABLED)" "$REGISTRY_ENABLED"; fi
|
|
150
|
+
if flag_given --git "$GIT_MODE"; then flag_enum "--git(GIT_MODE)" "$GIT_MODE" $FLAG_WORDS_GIT; fi
|
|
151
|
+
if flag_given --kube "$KUBE_MODE"; then flag_enum "--kube(KUBE_MODE)" "$KUBE_MODE" $FLAG_WORDS_KUBE; fi
|
|
152
|
+
# --sandbox 的词表属车道表(lib/lanes.json):这里只判「是不是表里的在役词」(退役词照样拒并指路),
|
|
153
|
+
# 与租户形相关的那一半(multi 不许 host 等)留给下面的车道块 `lanes resolve`。
|
|
154
|
+
if flag_given --sandbox "$SANDBOX_LANE"; then lanes check "$SANDBOX_LANE" || exit 1; fi
|
|
155
|
+
flag_port "--port(SERVER_PORT)" "$SERVER_PORT"
|
|
156
|
+
flag_port "--pg-port(PG_PORT)" "$PG_PORT"
|
|
157
|
+
flag_port "--minio-port(MINIO_PORT)" "$MINIO_PORT"
|
|
158
|
+
flag_port "--minio-console-port(MINIO_CONSOLE_PORT)" "$MINIO_CONSOLE_PORT"
|
|
159
|
+
flag_port "--git-http-port(GIT_HTTP_PORT)" "$GIT_HTTP_PORT"
|
|
160
|
+
flag_port "--git-ssh-port(GIT_SSH_PORT)" "$GIT_SSH_PORT"
|
|
161
|
+
# 没有旗、只从 -f / 环境来,但与上面几只同样落 .env、同样进预检的端口面。
|
|
162
|
+
flag_port "REGISTRY_PORT(-f / 环境)" "$REGISTRY_PORT"
|
|
163
|
+
# 无值旗落成的变量同样会被 env 形的 -f 覆盖,而下面四只布尔随后被**当命令执行**(`if $DRY_RUN`、`! $SKIP_PREFLIGHT`):
|
|
164
|
+
# `1` 找不到命令 ⇒ 条件为假(set -e 不管条件)⇒ 显式 --dry-run 静默变真装;`yes` 会跑起 yes 程序(车内 codex r1 [high])。一并判。
|
|
165
|
+
flag_bool "--dry-run(DRY_RUN)" "$DRY_RUN"
|
|
166
|
+
flag_bool "--teardown(TEARDOWN)" "$TEARDOWN"
|
|
167
|
+
flag_bool "--teardown-volumes(TEARDOWN_VOLUMES)" "$TEARDOWN_VOLUMES"
|
|
168
|
+
flag_bool "--skip-preflight(SKIP_PREFLIGHT)" "$SKIP_PREFLIGHT"
|
|
169
|
+
flag_enum "--interactive=json / --yes(MODE_INTERACTIVE)" "$MODE_INTERACTIVE" $FLAG_WORDS_INTERACTIVE
|
|
124
170
|
WORK="$HOME/.sema-up/$PROJECT"; mkdir -p "$WORK"; chmod 700 "$HOME/.sema-up" "$WORK"
|
|
125
171
|
# 凭据/配置文件的坐标(S-599 提到这里:车道决策要在模型面之前读既有 .env)。**读写原语全在 lib/envf.sh**
|
|
126
172
|
# (envf_get / envf_has / envf_load / envf_put / setk / setk_new / resetk,S-618):决策键严格读口、写口序列化、
|
|
@@ -165,7 +211,6 @@ fact_conflict() {
|
|
|
165
211
|
# 协议流(--interactive=json 的 question/summary 行)固定走 fd3=原始 stdout——
|
|
166
212
|
# $(ask …) 的命令替换只捕获答案,协议行不被吞(真机验出的坑)
|
|
167
213
|
exec 3>&1
|
|
168
|
-
log() { printf '[sema-up] %s\n' "$*" >&2; }
|
|
169
214
|
|
|
170
215
|
# compose 入口解析:优先 docker compose(插件),缺了退 docker-compose(独立版)——
|
|
171
216
|
# homebrew 裸 docker CLI 不带插件是真场景;都没有=报错带下一步
|
|
@@ -183,6 +228,15 @@ compose_globals() {
|
|
|
183
228
|
COMPOSE_GLOBALS=(--project-name "$PROJECT" --env-file "$ENVF" -f compose.yaml)
|
|
184
229
|
if [ -n "$DATA_ROOT" ]; then COMPOSE_GLOBALS+=(-f compose.dataroot.yaml); fi
|
|
185
230
|
}
|
|
231
|
+
# compose 路径的「壳接入」(S-850):引擎口在这台机的回环上直连,别的机器 ssh 隧道到它的回环。dry-run 计划与汇总调同一只;
|
|
232
|
+
# 文本与 json 两形的接法同源(lib/shell-access.sh 渲)。<端口> = 这台部署真会用的引擎口。
|
|
233
|
+
compose_shell_access_text() {
|
|
234
|
+
shell_access_text "$ENVF" "引擎这台机上:" "" "http://127.0.0.1:$1" \
|
|
235
|
+
"别的机器上:" "ssh -L <本地口>:127.0.0.1:$1 <用户>@$HOSTIP" "http://127.0.0.1:<本地口>"
|
|
236
|
+
}
|
|
237
|
+
compose_shell_access_json() {
|
|
238
|
+
shell_access_json "$ENVF" "http://127.0.0.1:$1" "ssh -L <local-port>:127.0.0.1:$1 <user>@$HOSTIP" "http://127.0.0.1:<local-port>"
|
|
239
|
+
}
|
|
186
240
|
|
|
187
241
|
# ── 跨平台探针(Linux 目标机为主;macOS 开发机跑 docker 档也是真场景——npx 分发)────────────
|
|
188
242
|
ram_mb() { if [ -r /proc/meminfo ]; then awk '/MemTotal/{printf "%d", $2/1024}' /proc/meminfo
|
|
@@ -236,6 +290,13 @@ ask() {
|
|
|
236
290
|
log "⛔ 选项 $ans 在本机不可行:${infpart%%|*};建议:${infpart#*|}"; exit 1
|
|
237
291
|
fi
|
|
238
292
|
done
|
|
293
|
+
# 有选项的题,答案必须是选项之一(S-621,与取值旗同一条规则:不在闭集里的词一律拒,不会落进「别的那一臂」)。
|
|
294
|
+
# 没有选项的题(网关 / 模型 id / 地址 / 凭据)是自由文本。
|
|
295
|
+
if [ "$#" -gt 0 ]; then
|
|
296
|
+
local hit=false words=""
|
|
297
|
+
for o in "$@"; do [ "${o%%:*}" = "$ans" ] && hit=true; words="${words:+$words / }${o%%:*}"; done
|
|
298
|
+
if ! $hit; then log "⛔ 问题 $id 的回答 $(flag_show "$ans") 不是选项 —— 可选:$words"; exit 1; fi
|
|
299
|
+
fi
|
|
239
300
|
printf '%s' "$ans"
|
|
240
301
|
}
|
|
241
302
|
|
|
@@ -261,8 +322,8 @@ if $TEARDOWN; then
|
|
|
261
322
|
fi
|
|
262
323
|
log "已卸载 project=$PROJECT $($TEARDOWN_VOLUMES && echo '(含数据卷)' || echo '(数据卷保留:pgdata/miniodata/registrydata/gitdata)')"
|
|
263
324
|
if $TEARDOWN_VOLUMES && [ -n "$DATA_ROOT" ]; then
|
|
264
|
-
#
|
|
265
|
-
|
|
325
|
+
# 删不删盘上文件看这条根归谁(S-851:标记是别的项目 / 没有标记 ⇒ 不教 rm -rf)。
|
|
326
|
+
dataroot_teardown_hint "$DATA_ROOT"
|
|
266
327
|
fi
|
|
267
328
|
else log "没有找到 $ENVF,无可卸载"; fi
|
|
268
329
|
exit 0
|
|
@@ -322,7 +383,6 @@ fi
|
|
|
322
383
|
"none:不带(已有别的地方管代码,或暂时用不上)" \
|
|
323
384
|
"provision:一键自建 Gitea(本机起一个 git 服务,含管理员账号)" \
|
|
324
385
|
"connect:接已有的 git 服务(自建 Gitea 或 GitHub,填地址+token)")
|
|
325
|
-
case "$GIT_MODE" in none|provision|connect) : ;; *) log "⛔ --git 只认 none|provision|connect(收到:$GIT_MODE)"; exit 1 ;; esac
|
|
326
386
|
if [ "$GIT_MODE" = "connect" ]; then
|
|
327
387
|
[ -z "$GIT_URL" ] && { [ "$MODE_INTERACTIVE" = "none" ] && { log "⛔ --git=connect 需要 --git-url(如 https://gitea.example.com 或 https://github.com/<org>)"; exit 1; } || GIT_URL=$(ask git_url "git 服务地址?(如 https://gitea.example.com)" "" "要验证连通并登记进配置中心"); }
|
|
328
388
|
[ -z "$GIT_TOKEN" ] && { [ "$MODE_INTERACTIVE" = "none" ] && { log "⛔ --git=connect 需要 --git-token(该服务的 access token)"; exit 1; } || GIT_TOKEN=$(ask git_token "该服务的 access token?" "" "验证连通用;Gitea 还会用它自动建 OAuth 登录应用"); }
|
|
@@ -426,7 +486,9 @@ if [ "$PROFILE" = "full-cluster" ]; then
|
|
|
426
486
|
MODEL_GATEWAY_BASEURL="$GATEWAY" MODEL_ID="$MODEL" MODEL_API_KEY="${MODEL_KEY:-${MODEL_API_KEY:-}}" \
|
|
427
487
|
exec "$HERE/kube-up.sh"
|
|
428
488
|
;;
|
|
429
|
-
|
|
489
|
+
# 到不了:旗在建工作目录之前按 FLAG_WORDS_KUBE 判过,交互答案在 ask() 里按选项判过(选项 == 词表,对拍钉在
|
|
490
|
+
# test/sema-up-flag-shape.test.ts)。真到这里 = 词表加了词而这里没加分支。
|
|
491
|
+
*) log "⛔ 内部不一致:--kube=$KUBE_MODE 过了形判(lib/flagshape.sh 的 FLAG_WORDS_KUBE)却没有对应分支 —— 词表与这里的分支要同批改"; exit 1 ;;
|
|
430
492
|
esac
|
|
431
493
|
fi
|
|
432
494
|
|
|
@@ -462,7 +524,7 @@ if ! $SKIP_PREFLIGHT; then
|
|
|
462
524
|
PF_PORTS="${NEW_PORTS#,}"
|
|
463
525
|
fi
|
|
464
526
|
fi
|
|
465
|
-
PF="$("$HERE/preflight.sh" --json --profile="${PROFILE:-full}" --ports="$PF_PORTS")" || {
|
|
527
|
+
PF="$("$HERE/preflight.sh" --json --profile="${PROFILE:-full}" --ports="$PF_PORTS" --project="$PROJECT")" || {
|
|
466
528
|
emit "{\"type\":\"preflight\",\"deployable\":false,\"findings\":$(printf '%s' "$PF" | sed 's/.*"findings"://;s/,"deployable".*//')}" "预检未过:"
|
|
467
529
|
printf '%s\n' "$PF" | python3 -c 'import json,sys
|
|
468
530
|
d=json.load(sys.stdin)
|
|
@@ -487,23 +549,45 @@ if [ "$REGION" = "auto" ]; then
|
|
|
487
549
|
REGION=$(awk -v a="$t_cn" -v b="$t_gl" 'BEGIN{print (a<b)?"cn":"global"}')
|
|
488
550
|
fi
|
|
489
551
|
HOSTIP=$(host_ip)
|
|
490
|
-
# E(S-618)
|
|
491
|
-
#
|
|
492
|
-
#
|
|
552
|
+
# E(S-618):进度日志只打**已校验的决策词**,不回显未过校验的原始输入(曾把带标记的 --registry 值抢先泄进 stderr,
|
|
553
|
+
# test srv796 实测 NEW-5)。7.104.0 起(S-621)REGISTRY_ENABLED 在建工作目录之前已过形判(只会是 true / false,或空 =
|
|
554
|
+
# 走档位缺省,缺省在上面已落定),registry 仍收成 on/off 决策词。region/profile/users/lane/git 到这里都已定死
|
|
555
|
+
# (形判在参数解析之后、交互答案在 ask() 里、车道在车道块)。
|
|
493
556
|
log "region=$REGION profile=$PROFILE users=$USERS sandbox=${SANDBOX_LANE:-n/a} lane=${LANE_REMOTE_EXEC:-n/a} registry=$([ "$REGISTRY_ENABLED" = "true" ] && echo on || echo off) git=$GIT_MODE"
|
|
494
557
|
|
|
495
|
-
# ──
|
|
496
|
-
#
|
|
497
|
-
|
|
558
|
+
# ── 数据根(S-851:一条规则 —— 数据根归一个项目所有;判据全在 lib/dataroot.sh)───────────────────────────
|
|
559
|
+
# 取哪条根:`.env` 首装值(已装的项目不漂移,不搬数据)∨ --data-root / -f 的 DATA_ROOT ∨ 多盘机自动选大容量盘
|
|
560
|
+
# (<盘>/sema-data/<project>;macOS 不自动 —— bind 要经 VM 挂载,显式给了才用)。换盘 = 先 --teardown-volumes 迁数据,
|
|
561
|
+
# 删掉 .env 的 DATA_ROOT_DIR 行,再带新 --data-root 重装。
|
|
562
|
+
# 能不能用:dataroot_judge —— 在 dry-run 出口与任何写动作**之前**判(dry-run 同判、不写),拒 = 非零退出、目录零改动。
|
|
563
|
+
# 判的必须是 compose 真正插值的那一个值:compose.dataroot.yaml 读 ${DATA_ROOT_DIR},而 compose 插值里 shell / -f 导出**优先于**
|
|
564
|
+
# --env-file ⇒ DATA_ROOT_DIR 是一条部署事实,走 deploy_fact(与 REQUIRE_PRINCIPAL / REMOTE_EXEC 同一条规则:两处不一致 ⇒ 拒)。
|
|
565
|
+
# 只判 --data-root 而 compose 挂导出值 = 所有权检查被绕过(车内 codex r1-F1)。`_stored` 只取 .env 的首装值:认存量归属的唯一证据。
|
|
566
|
+
_stored="$(envf_get DATA_ROOT_DIR)" || exit 1
|
|
567
|
+
deploy_fact DATA_ROOT_DIR; _dr_fact="$FACT_VAL" _dr_fact_src="$FACT_SRC"
|
|
568
|
+
DATA_ROOT_SRC=""
|
|
569
|
+
if [ -n "$_stored" ]; then
|
|
570
|
+
if [ -n "$DATA_ROOT" ] && [ "$DATA_ROOT" != "$_stored" ]; then
|
|
571
|
+
log "⚠ --data-root=$DATA_ROOT 与首装选定的 $_stored 不同——沿用首装值(换盘流程:--teardown-volumes 后迁数据,删掉 $ENVF 的 DATA_ROOT_DIR 行再带新 --data-root 重装)"
|
|
572
|
+
fi
|
|
573
|
+
DATA_ROOT="$_stored"; DATA_ROOT_SRC="$ENVF 的首装值 DATA_ROOT_DIR"
|
|
574
|
+
elif [ -n "$_dr_fact" ]; then
|
|
575
|
+
# 首装、但 shell / -f 导出了 DATA_ROOT_DIR:compose 会挂它 ⇒ 数据根就是它;--data-root 另给了一个不同的 ⇒ 拒(不替运维挑)。
|
|
576
|
+
fact_conflict DATA_ROOT_DIR "$_dr_fact" "$_dr_fact_src" "${DATA_ROOT:-$_dr_fact}" "--data-root=${DATA_ROOT:-$_dr_fact}"
|
|
577
|
+
DATA_ROOT="$_dr_fact"; DATA_ROOT_SRC="$_dr_fact_src 导出的 DATA_ROOT_DIR"
|
|
578
|
+
elif [ -n "$DATA_ROOT" ]; then
|
|
579
|
+
DATA_ROOT_SRC="--data-root / -f 的 DATA_ROOT"
|
|
580
|
+
elif [ "$(uname -s)" = "Linux" ] && [ "$PROFILE" != "local-plus" ]; then
|
|
498
581
|
ROOT_AVAIL=$(df -BG --output=avail / 2>/dev/null | tail -1 | tr -dc '0-9'); ROOT_AVAIL=${ROOT_AVAIL:-0}
|
|
499
582
|
BEST_LINE=$(df -BG --output=source,avail,target 2>/dev/null | awk '$1 ~ /^\/dev\// && $3 != "/" && $3 !~ /^\/(boot|efi)/ {gsub(/G/,"",$2); print $2"\t"$3}' | sort -rn | head -1)
|
|
500
583
|
BEST_GB="${BEST_LINE%%$'\t'*}"; BEST_MNT="${BEST_LINE#*$'\t'}"
|
|
501
584
|
# 逻辑闸:候选盘要「明显更大」(>2× 根盘可用)才自动切,免得在两块差不多的盘之间抖
|
|
502
585
|
if [ -n "$BEST_LINE" ] && [ "${BEST_GB:-0}" -gt $(( ROOT_AVAIL * 2 )) ]; then
|
|
503
|
-
DATA_ROOT="$BEST_MNT
|
|
504
|
-
log "多盘机:自动选大容量盘 $BEST_MNT(${BEST_GB}G avail,根盘 ${ROOT_AVAIL}G)→ 数据根 $DATA_ROOT(
|
|
586
|
+
DATA_ROOT="$(dataroot_for_disk "$BEST_MNT" "$PROJECT")"; DATA_ROOT_SRC="多盘机自动选盘"
|
|
587
|
+
log "多盘机:自动选大容量盘 $BEST_MNT(${BEST_GB}G avail,根盘 ${ROOT_AVAIL}G)→ 数据根 $DATA_ROOT(按项目分;--data-root 可覆盖)"
|
|
505
588
|
fi
|
|
506
589
|
fi
|
|
590
|
+
if [ -n "$DATA_ROOT" ]; then dataroot_judge "$DATA_ROOT" "$_stored" "$DATA_ROOT_SRC"; fi
|
|
507
591
|
|
|
508
592
|
# ── 必需镜像的显式校验(在 dry-run/写 .env 之前,报错带下一步动作)────────────────────────────
|
|
509
593
|
# 数据面细分:外接第三方 PG(--pg-url)/S3(--s3-endpoint)时对应服务不本地部署
|
|
@@ -523,7 +607,9 @@ case "$PROFILE" in full-single|local-plus) COMPOSE_PROFILES="$COMPOSE_PROFILES,a
|
|
|
523
607
|
COMPOSE_PROFILES="${COMPOSE_PROFILES#,}"; COMPOSE_PROFILES="${COMPOSE_PROFILES%,}"
|
|
524
608
|
case ",$COMPOSE_PROFILES," in *,agent,*)
|
|
525
609
|
if [ -z "${SEMA_SERVER_IMAGE:-}" ] && ! envf_has SEMA_SERVER_IMAGE; then
|
|
526
|
-
|
|
610
|
+
# tag 写法取本包版本(脚本随 npm 包发,包根的 package.json 就是它的版本);发布镜像的 tag = git tag = v<版本>(ci.yml 按 ref 名推)。
|
|
611
|
+
_pkgv="$(python3 -c 'import json, sys; print(json.load(open(sys.argv[1]))["version"])' "$HERE/../../package.json" 2>/dev/null || true)"
|
|
612
|
+
log "⛔ $PROFILE 档需要 sema-server 镜像。下一步:-f 配置里加 SEMA_SERVER_IMAGE=ghcr.io/sema-agent/sema-server:v${_pkgv:-<版本>}(tag 带 v —— 发布镜像按 git tag v<版本> 打,不带 v 的 :${_pkgv:-<版本>} 拉不到;公开仓匿名可拉;CN 兜底 = 自建镜像仓里的同名镜像)"
|
|
527
613
|
exit 1
|
|
528
614
|
fi ;;
|
|
529
615
|
esac
|
|
@@ -551,6 +637,12 @@ if $DRY_RUN; then
|
|
|
551
637
|
[ "$GIT_MODE" = "connect" ] && echo " +. 验证 $GIT_URL 连通(Gitea→GitHub→通用 git 探测链,证书不校验;Gitea=自动建 OAuth app,其余=打印待人工项)"
|
|
552
638
|
echo " 5. 装后即验(PG/S3$([ "$PROFILE" != "data-center" ] && printf '/引擎 health')$([ "$REGISTRY_ENABLED" = "true" ] && printf '/registry healthz')$([ "$GIT_MODE" = "provision" ] && printf '/gitea healthz'))+ 一屏 summary"
|
|
553
639
|
echo " 端口: $PF_PORTS(bind:数据面 $([ "$PROFILE" = "data-center" ] && echo 0.0.0.0 || echo 127.0.0.1);服务/registry/git 0.0.0.0)"
|
|
640
|
+
if [ -n "$DATA_ROOT" ]; then dataroot_plan_line "$DATA_ROOT"; fi
|
|
641
|
+
case ",$COMPOSE_PROFILES," in *,agent,*)
|
|
642
|
+
# 端口按这台部署真会用的那一个:重跑时 .env 的首装值赢(setk 不覆盖),只读、不写。
|
|
643
|
+
_dry_port="$(envf_get SERVER_PORT)" || exit 1; _dry_port="${_dry_port:-$SERVER_PORT}"
|
|
644
|
+
compose_shell_access_text "$_dry_port" ;;
|
|
645
|
+
esac
|
|
554
646
|
echo " 卸载: $0 --project=$PROJECT --teardown(留数据)/--teardown-volumes(连卷删)"
|
|
555
647
|
echo "═══════════════════════════════════════════════"
|
|
556
648
|
} >&2
|
|
@@ -672,19 +764,11 @@ elif [ "$GIT_MODE" = "connect" ]; then
|
|
|
672
764
|
resetk GIT_TOKEN "$GIT_TOKEN"
|
|
673
765
|
fi
|
|
674
766
|
|
|
675
|
-
# DATA_ROOT 落定(
|
|
676
|
-
# (S-618
|
|
677
|
-
_stored="$(envf_get DATA_ROOT_DIR)" || exit 1
|
|
678
|
-
if [ -n "$_stored" ]; then
|
|
679
|
-
if [ -n "$DATA_ROOT" ] && [ "$DATA_ROOT" != "$_stored" ]; then
|
|
680
|
-
log "⚠ --data-root=$DATA_ROOT 与首装选定的 $_stored 不同——沿用首装值(换盘流程:--teardown-volumes 后迁数据重装)"
|
|
681
|
-
fi
|
|
682
|
-
DATA_ROOT="$_stored"
|
|
683
|
-
elif [ -n "$DATA_ROOT" ]; then
|
|
684
|
-
setk DATA_ROOT_DIR "$DATA_ROOT"
|
|
685
|
-
fi
|
|
767
|
+
# DATA_ROOT 落定(根在上面「数据根」块里已定、已判归属;首装写 .env 后为准 —— 重跑换值会造出一套空卷,数据看似丢失)。
|
|
768
|
+
# 首装值经严格读口解码回读(S-618 续:路径含空格时写口落整值单引号形)。建根 / 卷目录 / 写项目标记都在 dataroot_settle 里。
|
|
686
769
|
if [ -n "$DATA_ROOT" ]; then
|
|
687
|
-
|
|
770
|
+
[ -n "$_stored" ] || setk DATA_ROOT_DIR "$DATA_ROOT"
|
|
771
|
+
dataroot_settle "$DATA_ROOT"
|
|
688
772
|
fi
|
|
689
773
|
|
|
690
774
|
# A(S-618):`.env` 生成完毕 —— 把 setk 持久化的**生效端口**回读进 shell 变量(替代旧 source 的这半)。
|
|
@@ -942,11 +1026,23 @@ fi
|
|
|
942
1026
|
|
|
943
1027
|
# ── 汇总(凭据打印一次;文件 0600 常驻;一屏读完)────────────────────────────────────────────
|
|
944
1028
|
if [ "$MODE_INTERACTIVE" = "json" ]; then
|
|
945
|
-
printf
|
|
946
|
-
|
|
1029
|
+
# 整行经 json.dumps 铸(S-850 同批):此前 printf 拼 JSON —— `health` 塞的是 /health 响应体的前 60 字节(自带双引号)、
|
|
1030
|
+
# 路径 / URL 里的引号与反斜杠也不转义 ⇒ 带 agent 的档这一行恒不是合法 JSON(本批加 `shellAccess` 时红格读出)。
|
|
1031
|
+
# 键名与取值口径逐字不变;`buckets` 是数字时仍是 JSON 数字(没建桶的档是串 "n/a",此前那一形同样不合法);
|
|
1032
|
+
# `shellAccess` = 壳接入(没有引擎的档为 null)。
|
|
1033
|
+
SHELL_ACCESS_JSON="null"
|
|
1034
|
+
case ",$COMPOSE_PROFILES," in *,agent,*) SHELL_ACCESS_JSON="$(compose_shell_access_json "$SERVER_PORT")" ;; esac
|
|
1035
|
+
python3 -c 'import json, sys
|
|
1036
|
+
a = sys.argv[1:]
|
|
1037
|
+
buckets = a[5]
|
|
1038
|
+
print(json.dumps({"type": "summary", "profile": a[0], "region": a[1], "pg": "postgres://sema:***@%s:%s/sema" % (a[2], a[3]),
|
|
1039
|
+
"s3": "http://%s:%s" % (a[2], a[4]), "buckets": int(buckets) if buckets.isdigit() else buckets,
|
|
1040
|
+
"pgSharedBuffers": a[6], "health": a[7], "registry": a[8], "git": a[9], "credentialsFile": a[10],
|
|
1041
|
+
"shellAccess": json.loads(a[11])}, ensure_ascii=False, separators=(",", ":")))' \
|
|
1042
|
+
"$PROFILE" "$REGION" "$HOSTIP" "$PG_PORT" "$MINIO_PORT" "${V_BK:-0}" "$V_PG" "$V_HEALTH" \
|
|
947
1043
|
"$([ "$REGISTRY_ENABLED" = "true" ] && echo "http://$HOSTIP:$REGISTRY_PORT" || echo n/a)" \
|
|
948
1044
|
"$(case "$GIT_MODE" in provision) echo "http://$HOSTIP:$GIT_HTTP_PORT";; connect) echo "$GIT_URL";; *) echo n/a;; esac)" \
|
|
949
|
-
"$ENVF" >&3
|
|
1045
|
+
"$ENVF" "$SHELL_ACCESS_JSON" >&3
|
|
950
1046
|
else
|
|
951
1047
|
{
|
|
952
1048
|
echo
|
|
@@ -960,7 +1056,8 @@ else
|
|
|
960
1056
|
esac
|
|
961
1057
|
[ -n "$S3_ENDPOINT" ] && echo " S3(外接) $S3_ENDPOINT(连接检测+三桶已确保;密钥在 .env)"
|
|
962
1058
|
case ",$COMPOSE_PROFILES," in *,agent,*)
|
|
963
|
-
echo " 引擎 http://$HOSTIP:$SERVER_PORT(/health: $V_HEALTH;API 凭据=SERVICE_AUTH_TOKEN)"
|
|
1059
|
+
echo " 引擎 http://$HOSTIP:$SERVER_PORT(/health: $V_HEALTH;API 凭据=SERVICE_AUTH_TOKEN)"
|
|
1060
|
+
compose_shell_access_text "$SERVER_PORT" ;;
|
|
964
1061
|
esac
|
|
965
1062
|
if [ "$REGISTRY_ENABLED" = "true" ]; then
|
|
966
1063
|
echo " 配置中心 http://$HOSTIP:$REGISTRY_PORT(healthz $V_REG)"
|
package/deploy/sema-up/smoke.sh
CHANGED
|
@@ -8,6 +8,9 @@ set -u
|
|
|
8
8
|
HERE="$(cd "$(dirname "$0")" && pwd)"
|
|
9
9
|
# `.env` 的文法与读原语只在 lib/envf.sh 定义(S-618 续):本脚本读 `.env` 只经 envf_load,不 source。
|
|
10
10
|
. "$HERE/lib/envf.sh"
|
|
11
|
+
# 取值旗的形(S-621):判据同 sema-up.sh 一处定义。
|
|
12
|
+
. "$HERE/lib/flagshape.sh"
|
|
13
|
+
log() { printf '[smoke] %s\n' "$*" >&2; }
|
|
11
14
|
|
|
12
15
|
PROJECT="" URL="" TOKEN=""
|
|
13
16
|
for a in "$@"; do case "$a" in
|
|
@@ -15,6 +18,8 @@ for a in "$@"; do case "$a" in
|
|
|
15
18
|
--url=*) URL="${a#*=}" ;;
|
|
16
19
|
--token=*) TOKEN="${a#*=}" ;;
|
|
17
20
|
esac; done
|
|
21
|
+
# --project 是 ~/.sema-up/<名> 的一段路径(与 sema-up 同一只形判;空 = 远程 URL 模式)。--url / --token 是自由文本。
|
|
22
|
+
[ -z "$PROJECT" ] || flag_project "--project" "$PROJECT"
|
|
18
23
|
|
|
19
24
|
FAILS=0
|
|
20
25
|
ok() { printf '[OK ] %s\n' "$1"; }
|
package/dist/auto-mode-face.js
CHANGED
|
@@ -2,12 +2,11 @@ import { createRequire } from "node:module";
|
|
|
2
2
|
import { readFileSync } from "node:fs";
|
|
3
3
|
import { dirname, join } from "node:path";
|
|
4
4
|
import { z } from "zod";
|
|
5
|
-
import { classifierSeatModel
|
|
6
|
-
import {
|
|
5
|
+
import { classifierSeatModel } from "@sema-agent/core";
|
|
6
|
+
import { resolveMainModel } from "./model-select.js";
|
|
7
7
|
export function classifierRouteFor(deps) {
|
|
8
8
|
try {
|
|
9
|
-
const
|
|
10
|
-
const main = resolveTaskModel({ model: DEFAULT_MODEL_REF }, { ...deps, ...(expanded !== undefined ? { models: expanded } : {}) }).model;
|
|
9
|
+
const main = resolveMainModel(deps);
|
|
11
10
|
return { id: classifierSeatModel({ deps, main }).model.id };
|
|
12
11
|
}
|
|
13
12
|
catch (err) {
|
|
@@ -31,7 +31,7 @@ export function createBudgetAndTracing(ctx) {
|
|
|
31
31
|
const sideQueryAccounting = createSideQueryAccountant(metrics, costQuota, fleetUsage, fleetLease, (m) => config.modelQuotaWeights[m] ?? 1);
|
|
32
32
|
const toolResultStore = backend?.toolResult ? backend.toolResult() : undefined;
|
|
33
33
|
const sessionPolicyStore = backend ? backend.sessionPolicy() : undefined;
|
|
34
|
-
const { store: fileHistoryStore, erasure: fileHistoryErasure } = assembleFileHistory(backend
|
|
34
|
+
const { store: fileHistoryStore, erasure: fileHistoryErasure } = assembleFileHistory(backend);
|
|
35
35
|
return {
|
|
36
36
|
brain, degradeRouteWatch, pricing, counterDegradeHook, costQuota, modelUsageTracker, promptManifestTracker,
|
|
37
37
|
fleetUsage, fleetLease, tracer, sideQueryAccounting, toolResultStore, sessionPolicyStore, fileHistoryStore, fileHistoryErasure,
|
|
@@ -9,6 +9,7 @@ import { type CenterCredentialHealthView } from "../center-credential-renewer.js
|
|
|
9
9
|
import { type ModelKeyRef } from "../key-resolver.js";
|
|
10
10
|
import type { Logger } from "../observability/logger.js";
|
|
11
11
|
import type { Metrics } from "../observability/metrics.js";
|
|
12
|
+
import { type SealedKeyPublicFace } from "../sealed-key.js";
|
|
12
13
|
import type { UnpricedCeilingVerdict } from "./unpriced-ceiling-verdict.js";
|
|
13
14
|
import type { ConsolidationSeatWatch } from "./memory-consolidation.js";
|
|
14
15
|
import type { DegradeRouteWatch } from "../brain.js";
|
|
@@ -36,10 +37,13 @@ export type SelfReportChange = "roster_landed" | "restart_signal" | "model_refus
|
|
|
36
37
|
export interface ConfigCenterRuntime {
|
|
37
38
|
/** 配置源形态;`undefined` = 纯 env 部署(本运行时的所有分支不存在)。 */
|
|
38
39
|
readonly providerKind: "remote" | "local" | undefined;
|
|
40
|
+
/** S-737:本进程模型钥匙保管店的**公钥面**(只有最新那把公钥;私钥 / 持有 id 列表 / 开箱口结构上够不着,见
|
|
41
|
+
* `sealed-key.ts` `SealedKeyPublicFace`)。present-iff boot 期建店成功 —— 建店与配置源形态无关(纯 env / 本地 / 远程
|
|
42
|
+
* 三形都建),失败 ⇒ `undefined`(`sealed_key_store_unavailable` 那一行已响亮告警)。店只在 boot 建一次、进程内不换钥匙,
|
|
43
|
+
* 所以这是一个值,不是 getter。唯一读者 = `GET /v1/capabilities` 的 `sealedKey` 段(stage-10 经 deps 交过去)。 */
|
|
44
|
+
readonly sealedKeyPublic: SealedKeyPublicFace | undefined;
|
|
39
45
|
/** core `CenterPromptSource`(RunnerDeps / subRunner 必须同实例)。 */
|
|
40
46
|
readonly promptSource: CenterPromptSource;
|
|
41
|
-
/** DUAL-MODE §4/§5:local 形从 `config.d/remote-exec.json` 覆写 `config.remoteExec`(坏文件 = fail-closed 抛)。 */
|
|
42
|
-
applyLocalRemoteExec(): Promise<void>;
|
|
43
47
|
/** center skills overlay → plugins 消费 → boot LKG 落盘/skill-stale 判定 → mcp 解析。返回新的 skills 表。 */
|
|
44
48
|
applyCenterCapabilities(skills: LoadedSkill[]): Promise<LoadedSkill[]>;
|
|
45
49
|
/** center 声明场景 overlay(就地写进传入的两张表)。 */
|
|
@@ -2,7 +2,6 @@ import { createHash } from "node:crypto";
|
|
|
2
2
|
import { readFile as fsReadFile } from "node:fs/promises";
|
|
3
3
|
import { homedir } from "node:os";
|
|
4
4
|
import { join } from "node:path";
|
|
5
|
-
import { loadRemoteExec } from "@sema-agent/settings-schema/node";
|
|
6
5
|
import { CENTER_CREDENTIAL_ENV, skillContentHash } from "@sema-agent/settings-schema";
|
|
7
6
|
import { stableStringify } from "@sema-agent/settings-schema/hash";
|
|
8
7
|
import { buildModelRefusalsSignal } from "../model-entry-refusal.js";
|
|
@@ -17,10 +16,9 @@ import { defaultLkgPath, defaultSkillCacheDir, saveLkg, loadLkg, legacyV1LkgPath
|
|
|
17
16
|
import { answerStillCurrent, centerStateBin, credentialIdentityNow, judgeCenterCredentialAtBoot } from "../center-credential.js";
|
|
18
17
|
import { buildCenterCredentialHealthView, createCenterCredentialRenewer } from "../center-credential-renewer.js";
|
|
19
18
|
import { centerManagedConfigKeys } from "../config-center/apply-effective.js";
|
|
20
|
-
import { createConfigProvider, raceBootFetch, BOOT_FETCH_DEFERRED } from "../config-provider.js";
|
|
21
|
-
import { executionLaneCaps, executionLaneOf } from "../execution-lane-caps.js";
|
|
19
|
+
import { createConfigProvider, raceBootFetch, serviceConfigSourceKind, BOOT_FETCH_DEFERRED } from "../config-provider.js";
|
|
22
20
|
import { createKeyResolver } from "../key-resolver.js";
|
|
23
|
-
import { ensureSealedKeyStore, reportExecutionPublicKey } from "../sealed-key.js";
|
|
21
|
+
import { buildSealedKeyPublicFace, ensureSealedKeyStore, reportExecutionPublicKey } from "../sealed-key.js";
|
|
24
22
|
import { applyEffective, applyCenterReadFace, mutateInPlace, logEffectiveDiff, applyCenterSkills, resolveMcpServers, resolveA2aPeers, restartReasons, modelPlaneChanged, withModelPlaneOf, bootSnapshotOf, fullyLandedForLkg, fetchPromptArtifact } from "../config-center/facade.js";
|
|
25
23
|
import { createMcpRevocationLedger, enabledMcpEntries } from "../config-center/mcp-revocation.js";
|
|
26
24
|
import { SHA256_HEX_RE } from "../digest-form.js";
|
|
@@ -119,11 +117,11 @@ export async function createConfigCenterRuntime(ctx) {
|
|
|
119
117
|
catch (err) {
|
|
120
118
|
logger.warn("sealed_key_store_unavailable", { err: String(err), note: "sealed api keys cannot be unsealed on this host — models carrying sealedApiKey will fail loud per-model" });
|
|
121
119
|
}
|
|
122
|
-
const configProvider =
|
|
120
|
+
const configProvider = serviceConfigSourceKind(config) !== undefined
|
|
123
121
|
? createConfigProvider({
|
|
124
122
|
...(config.configProvider ? { provider: config.configProvider } : {}),
|
|
125
123
|
...(cc ? { configCenter: { baseUrl: cc.baseUrl, credential: cc.credential, worker: cc.worker, dryRun: cc.dryRun } } : {}),
|
|
126
|
-
|
|
124
|
+
localDir: localRoot,
|
|
127
125
|
})
|
|
128
126
|
: undefined;
|
|
129
127
|
const rosterGateArmed = isRosterGateArmed(cc);
|
|
@@ -590,6 +588,7 @@ export async function createConfigCenterRuntime(ctx) {
|
|
|
590
588
|
let ccTimer;
|
|
591
589
|
return {
|
|
592
590
|
providerKind: configProvider?.kind,
|
|
591
|
+
sealedKeyPublic: sealedKeys === undefined ? undefined : buildSealedKeyPublicFace(sealedKeys),
|
|
593
592
|
promptSource,
|
|
594
593
|
takeMcpRevocations: () => {
|
|
595
594
|
mcpRevocationWired = true;
|
|
@@ -612,59 +611,6 @@ export async function createConfigCenterRuntime(ctx) {
|
|
|
612
611
|
return "not-wired";
|
|
613
612
|
return (await manualRefresh()) === "failed" ? "failed" : "triggered";
|
|
614
613
|
},
|
|
615
|
-
async applyLocalRemoteExec() {
|
|
616
|
-
if (configProvider?.kind === "local") {
|
|
617
|
-
try {
|
|
618
|
-
const fileRx = await loadRemoteExec(localRoot);
|
|
619
|
-
if (fileRx) {
|
|
620
|
-
const preemptedLane = executionLaneOf(config.remoteExec?.provider);
|
|
621
|
-
const isolatedLane = (lane) => executionLaneCaps(lane).isolation === "supported";
|
|
622
|
-
const disclosePreemption = (winner) => {
|
|
623
|
-
if (winner === preemptedLane)
|
|
624
|
-
return;
|
|
625
|
-
const downgrade = isolatedLane(preemptedLane) && !isolatedLane(winner);
|
|
626
|
-
logger.warn("remote_exec_lane_preempted_by_file", {
|
|
627
|
-
preemptedLane,
|
|
628
|
-
effectiveLane: winner,
|
|
629
|
-
root: localRoot,
|
|
630
|
-
isolationDowngrade: downgrade,
|
|
631
|
-
note: `config.d/remote-exec.json (CONFIG_PROVIDER=local, file-wins) replaced the env-derived execution lane ` +
|
|
632
|
-
`"${preemptedLane}" with "${winner}" — any REMOTE_EXEC/lane coordinate env for "${preemptedLane}" is now INERT` +
|
|
633
|
-
(downgrade ? `. This is an ISOLATION DOWNGRADE: tasks run on a non-isolated target. Delete/fix the file, or unset the lane env if the file is the intended source.` : `. Delete/fix the file, or unset the lane env if the file is the intended source.`),
|
|
634
|
-
});
|
|
635
|
-
};
|
|
636
|
-
if (fileRx.provider === "host") {
|
|
637
|
-
config.remoteExec = {
|
|
638
|
-
provider: "host",
|
|
639
|
-
...(fileRx.workdir ? { workspaceBase: fileRx.workdir } : {}),
|
|
640
|
-
...(fileRx.commandTimeoutMs != null ? { commandTimeoutMs: fileRx.commandTimeoutMs } : {}),
|
|
641
|
-
};
|
|
642
|
-
disclosePreemption("host");
|
|
643
|
-
logger.info("remote_exec_from_file", { provider: "host", root: localRoot });
|
|
644
|
-
}
|
|
645
|
-
else if (fileRx.provider === "local-docker") {
|
|
646
|
-
config.remoteExec = {
|
|
647
|
-
provider: "local-docker",
|
|
648
|
-
image: fileRx.image,
|
|
649
|
-
...(fileRx.mountPath ? { mountPath: fileRx.mountPath } : fileRx.workdir ? { mountPath: fileRx.workdir } : {}),
|
|
650
|
-
...(fileRx.memory ? { memory: fileRx.memory } : {}),
|
|
651
|
-
...(fileRx.cpus != null ? { cpus: fileRx.cpus } : {}),
|
|
652
|
-
...(fileRx.network ? { network: fileRx.network } : {}),
|
|
653
|
-
};
|
|
654
|
-
disclosePreemption("local-docker");
|
|
655
|
-
logger.info("remote_exec_from_file", { provider: "local-docker", root: localRoot, image: fileRx.image });
|
|
656
|
-
}
|
|
657
|
-
else {
|
|
658
|
-
logger.warn("remote_exec_file_isolated_lane_unwired", { provider: fileRx.provider, effectiveLane: preemptedLane, note: "e2b/k8s/ssh/adb are sourced from REMOTE_EXEC env until file-source NAME→value resolution lands (center field map) — the env-derived lane above stays in force" });
|
|
659
|
-
}
|
|
660
|
-
}
|
|
661
|
-
}
|
|
662
|
-
catch (err) {
|
|
663
|
-
logger.error("remote_exec_file_invalid", { root: localRoot, err: String(err) });
|
|
664
|
-
throw err;
|
|
665
|
-
}
|
|
666
|
-
}
|
|
667
|
-
},
|
|
668
614
|
async applyCenterCapabilities(skills) {
|
|
669
615
|
if (effective?.skills && config.configCenter) {
|
|
670
616
|
skills = await applyCenterSkills(skills, effective.skills, config.configCenter, logger, undefined, configLkgEnabled() ? defaultSkillCacheDir() : undefined);
|
|
@@ -39,6 +39,15 @@ export interface SessionScopedRegistries {
|
|
|
39
39
|
fenceSessionOwner(sid: string, owner: string | null): void;
|
|
40
40
|
/** 会话删除:两张表 + 身份记录一起清(留着身份记录会让同 id 的下一位与一个幽灵身份比对成「同主」)。 */
|
|
41
41
|
dropSession(sid: string): void;
|
|
42
|
+
/**
|
|
43
|
+
* S-506([ref] §5)—— 本副本对「这只会话此刻坐着哪一位住客」的**装配期读数**:resolve-spec 每次提交(fresh 与 resume 腿同经)
|
|
44
|
+
* 异步读一次会话面(`sessionFaceOf`),把化身记在这里;记忆引擎的 `RunnerDeps.memorySessionIncarnation` 座是**同步**签名,
|
|
45
|
+
* 从这里取(`boot/session-incarnation-seat.ts`)。`undefined` = 读不到 ⇒ 清掉这一格(座随之答 undefined + warn,core 退回按 id 绑定)。
|
|
46
|
+
* 与两张内容表同一只 LRU、同一条围栏(换主即清)、同一条删除(`dropSession`)。令牌只在进程内存里,不进任何日志。
|
|
47
|
+
*/
|
|
48
|
+
noteSessionIncarnation(sid: string, incarnation: string | undefined): void;
|
|
49
|
+
/** 同步读装配期记下的化身(记忆座的唯一读者)。 */
|
|
50
|
+
sessionIncarnationOf(sid: string): string | undefined;
|
|
42
51
|
}
|
|
43
52
|
export declare function createSessionScopedRegistries(maxEntries?: number): SessionScopedRegistries;
|
|
44
53
|
/** device lane 的三件活对象(店 / 汇聚端 / 准入链)。缺席 = 本部署没有 device 车道。 */
|
|
@@ -63,6 +72,8 @@ export declare function createExecutionEnv(ctx: ExecutionEnvCtx): {
|
|
|
63
72
|
setSessionShellEnv: (sid: string, env: Record<string, string>) => void;
|
|
64
73
|
fenceSessionOwner: (sid: string, owner: string | null) => void;
|
|
65
74
|
dropSessionScoped: (sid: string) => void;
|
|
75
|
+
noteSessionIncarnation: (sid: string, incarnation: string | undefined) => void;
|
|
76
|
+
sessionIncarnationOf: (sid: string) => string | undefined;
|
|
66
77
|
executionEnvFactory: import("@sema-agent/core").ExecutionEnvFactory | undefined;
|
|
67
78
|
worktreeReap: (() => Promise<void>) | undefined;
|
|
68
79
|
sendUserFileTaskEnvs: TaskEnvRegistry | undefined;
|
|
@@ -51,10 +51,12 @@ const MAX_CWD_SESSIONS = 4096;
|
|
|
51
51
|
export function createSessionScopedRegistries(maxEntries = MAX_CWD_SESSIONS) {
|
|
52
52
|
const perSessionCwd = new Map();
|
|
53
53
|
const perSessionShellEnv = new Map();
|
|
54
|
+
const perSessionIncarnation = new Map();
|
|
54
55
|
const owners = new Map();
|
|
55
56
|
const dropSession = (sid) => {
|
|
56
57
|
perSessionCwd.delete(sid);
|
|
57
58
|
perSessionShellEnv.delete(sid);
|
|
59
|
+
perSessionIncarnation.delete(sid);
|
|
58
60
|
owners.delete(sid);
|
|
59
61
|
};
|
|
60
62
|
const touch = (sid, owner) => {
|
|
@@ -79,10 +81,20 @@ export function createSessionScopedRegistries(maxEntries = MAX_CWD_SESSIONS) {
|
|
|
79
81
|
if (owners.has(sid) && owners.get(sid) !== owner) {
|
|
80
82
|
perSessionCwd.delete(sid);
|
|
81
83
|
perSessionShellEnv.delete(sid);
|
|
84
|
+
perSessionIncarnation.delete(sid);
|
|
82
85
|
}
|
|
83
86
|
touch(sid, owner);
|
|
84
87
|
},
|
|
85
88
|
dropSession,
|
|
89
|
+
noteSessionIncarnation: (sid, incarnation) => {
|
|
90
|
+
if (incarnation === undefined) {
|
|
91
|
+
perSessionIncarnation.delete(sid);
|
|
92
|
+
return;
|
|
93
|
+
}
|
|
94
|
+
perSessionIncarnation.set(sid, incarnation);
|
|
95
|
+
touch(sid, undefined);
|
|
96
|
+
},
|
|
97
|
+
sessionIncarnationOf: (sid) => perSessionIncarnation.get(sid),
|
|
86
98
|
};
|
|
87
99
|
}
|
|
88
100
|
export function createExecutionEnv(ctx) {
|
|
@@ -316,6 +328,8 @@ export function createExecutionEnv(ctx) {
|
|
|
316
328
|
perTaskImage, sessionEnvSelection, perSessionCwd, setSessionCwd, setSessionShellEnv,
|
|
317
329
|
fenceSessionOwner: sessionScoped.fenceSessionOwner,
|
|
318
330
|
dropSessionScoped: sessionScoped.dropSession,
|
|
331
|
+
noteSessionIncarnation: sessionScoped.noteSessionIncarnation,
|
|
332
|
+
sessionIncarnationOf: sessionScoped.sessionIncarnationOf,
|
|
319
333
|
executionEnvFactory, worktreeReap, sendUserFileTaskEnvs, lspManager,
|
|
320
334
|
};
|
|
321
335
|
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { type CrossInvariantView } from "../config-invariants.js";
|
|
2
|
+
import type { ServiceConfig } from "../config-types.js";
|
|
3
|
+
import type { Logger } from "../observability/logger.js";
|
|
4
|
+
/** `config.d/remote-exec.json` 能写进来的车道(文件契约里不带密钥名、可 1:1 映射的两条;e2b/k8s/ssh/adb 要 NAME→值解析,尚未接线)。 */
|
|
5
|
+
export declare const FILE_SOURCED_LANES: readonly ["host", "local-docker"];
|
|
6
|
+
export type FileSourcedLane = (typeof FILE_SOURCED_LANES)[number];
|
|
7
|
+
export interface ExecutionLaneComposeCtx {
|
|
8
|
+
/** 本入口的配置源形态(`configProviderKind` / `serviceConfigSourceKind` 的答案 —— 与它真建的 provider 同一只判据)。
|
|
9
|
+
* 只有 `local` 才读文件;`undefined` = 纯 env 部署。 */
|
|
10
|
+
readonly configSource: "local" | "remote" | undefined;
|
|
11
|
+
/** 配置根(与配置源同一个根,不分裂)。 */
|
|
12
|
+
readonly localRoot: string;
|
|
13
|
+
readonly logger: Logger;
|
|
14
|
+
/** 本入口**驱动得了**的文件车道。HTTP 服务两条都驱动;`run-local` 只驱动 host(它没有容器执行器)—— 文件写了本入口
|
|
15
|
+
* 驱动不了的车道 ⇒ 不写进 config,落「未接线」告警,env 腿的车道照旧(与 e2b/k8s/ssh/adb 的文件表态同一臂)。 */
|
|
16
|
+
readonly drivesFromFile: readonly FileSourcedLane[];
|
|
17
|
+
}
|
|
18
|
+
/** 合成 + 判。覆写就地改写 `config.remoteExec`;坏文件 / 不变量不过 ⇒ 抛(拒启)。 */
|
|
19
|
+
export declare function composeExecutionLane(config: ServiceConfig, ctx: ExecutionLaneComposeCtx): Promise<void>;
|
|
20
|
+
/**
|
|
21
|
+
* 车道合成之后的跨域不变量 —— 表(`config-invariants.ts`)里读 `remoteExec.provider` 的那几条,boot 期 `live === next`。
|
|
22
|
+
* 同步、纯判据:只读 config,不碰文件(测试可以对 `loadConfig()` 的产物直接调它)。
|
|
23
|
+
*/
|
|
24
|
+
export declare function assertExecutionLaneInvariants(config: CrossInvariantView): void;
|
|
25
|
+
//# sourceMappingURL=execution-lane.d.ts.map
|