@sema-agent/server 7.133.0 → 7.134.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 +87 -0
- package/README.md +2 -2
- package/README.zh-CN.md +2 -2
- package/USAGE.md +129 -11
- package/deploy/sema-up/README.md +16 -0
- package/deploy/sema-up/chart/Chart.yaml +1 -1
- package/deploy/sema-up/chart/templates/NOTES.txt +15 -0
- package/deploy/sema-up/chart/templates/_helpers.tpl +129 -0
- package/deploy/sema-up/chart/templates/sealed-key.yaml +24 -0
- package/deploy/sema-up/chart/templates/server-v4pro.yaml +14 -0
- package/deploy/sema-up/chart/templates/server.yaml +14 -0
- package/deploy/sema-up/chart/values-full-single.yaml +5 -1
- package/deploy/sema-up/chart/values.yaml +34 -0
- package/dist/approval-ask-audit-store.d.ts +1 -1
- package/dist/approval-card.d.ts +4 -4
- package/dist/approval-card.js +1 -2
- package/dist/approval-content-kind.d.ts +5 -2
- package/dist/approval-content-kind.js +16 -3
- package/dist/approval-gate-currency.d.ts +72 -0
- package/dist/approval-gate-currency.js +94 -0
- package/dist/approval.d.ts +6 -0
- package/dist/approval.js +31 -14
- package/dist/boot/coordinators.d.ts +6 -1
- package/dist/boot/coordinators.js +6 -3
- package/dist/boot/leader.d.ts +2 -0
- package/dist/boot/leader.js +2 -1
- package/dist/boot/read-deny-anchor-audit.js +2 -2
- package/dist/boot/runner-deps.d.ts +18 -5
- package/dist/boot/runner-deps.js +9 -0
- package/dist/boot/stage-06-runners.d.ts +4 -3
- package/dist/boot/stage-06-runners.js +15 -2
- package/dist/boot/stage-07-capability-layer.d.ts +2 -1
- package/dist/boot/stage-08-reapers.d.ts +2 -1
- package/dist/boot/stage-09-leader.d.ts +2 -1
- package/dist/boot/stage-09-leader.js +2 -2
- package/dist/boot/stage-10-http-server.d.ts +2 -1
- package/dist/boot/stage-10-http-server.js +2 -1
- package/dist/boot/stores.d.ts +8 -0
- package/dist/boot/stores.js +8 -12
- package/dist/boot/web-admin.d.ts +6 -0
- package/dist/boot/web-admin.js +11 -2
- package/dist/budget.js +5 -1
- package/dist/caller-text-echo.d.ts +2 -0
- package/dist/caller-text-echo.js +2 -1
- package/dist/canonical-json.d.ts +2 -41
- package/dist/canonical-json.js +2 -13
- package/dist/capabilities/composite-members.d.ts +1 -1
- package/dist/capabilities/composite-members.js +1 -1
- package/dist/capabilities/mcp-probe.js +2 -2
- package/dist/capability-facts.d.ts +2 -0
- package/dist/capability-facts.js +4 -4
- package/dist/checkpoint-ctx-seal-m28.d.ts +42 -0
- package/dist/checkpoint-ctx-seal-m28.js +19 -0
- package/dist/field-conflict.d.ts +2 -1
- package/dist/field-conflict.js +1 -0
- package/dist/file-history-disclosure.d.ts +34 -5
- package/dist/file-history-disclosure.js +2 -2
- package/dist/fleet/fleet-bus.d.ts +4 -0
- package/dist/fleet/fleet-bus.js +1 -0
- package/dist/hooks/hook-runner.js +2 -2
- package/dist/http/admission.js +8 -0
- package/dist/http/approval-sweeps.js +3 -1
- package/dist/http/checkpoint-ctx.d.ts +157 -2
- package/dist/http/checkpoint-ctx.js +158 -2
- package/dist/http/dispatch.js +3 -3
- package/dist/http/durable-run-leg.js +3 -2
- package/dist/http/idempotency.d.ts +2 -2
- package/dist/http/idempotency.js +2 -2
- package/dist/http/principal-gate.d.ts +32 -6
- package/dist/http/principal-gate.js +27 -4
- package/dist/http/refusal-body.d.ts +12 -0
- package/dist/http/refusal-body.js +11 -0
- package/dist/http/request-guards.d.ts +0 -9
- package/dist/http/request-guards.js +2 -16
- package/dist/http/resume-ctx-read.d.ts +48 -1
- package/dist/http/resume-ctx-read.js +26 -3
- package/dist/http/resume-legs.d.ts +2 -1
- package/dist/http/resume-legs.js +37 -22
- package/dist/http/route-ctx.d.ts +0 -9
- package/dist/http/routes/agents-roster.js +2 -3
- package/dist/http/routes/approvals-assistant.js +43 -9
- package/dist/http/routes/capabilities.js +1 -1
- package/dist/http/routes/diagnostics.d.ts +3 -3
- package/dist/http/routes/diagnostics.js +1 -1
- package/dist/http/routes/fleet.js +4 -4
- package/dist/http/routes/memory-origin.d.ts +1 -1
- package/dist/http/routes/rules.js +4 -4
- package/dist/http/routes/runs.js +30 -9
- package/dist/http/routes/session-sync.d.ts +3 -2
- package/dist/http/routes/session-sync.js +38 -20
- package/dist/http/routes/sessions-list.d.ts +1 -1
- package/dist/http/routes/sessions-list.js +3 -4
- package/dist/http/routes/sessions.js +4 -4
- package/dist/http/routes/tasks.js +21 -16
- package/dist/http/routes/trace-usage.js +7 -9
- package/dist/http/run-declared-families.d.ts +16 -0
- package/dist/http/run-declared-families.js +12 -0
- package/dist/http/server.d.ts +6 -0
- package/dist/http/server.js +5 -2
- package/dist/http/submit-ctx.d.ts +4 -3
- package/dist/http/submit-ctx.js +19 -3
- package/dist/http/wire-types.d.ts +7 -3
- package/dist/index.d.ts +2 -0
- package/dist/index.js +1 -0
- package/dist/leader/wire.d.ts +4 -0
- package/dist/leader/wire.js +8 -7
- package/dist/main.js +1 -1
- package/dist/memory-origin-fence.js +2 -2
- package/dist/model-compat.d.ts +1 -1
- package/dist/observability/prompt-manifest.d.ts +19 -8
- package/dist/observability/prompt-manifest.js +9 -8
- package/dist/observability/tool-trace.js +8 -9
- package/dist/orchestration/workflow-park-decision.d.ts +1 -1
- package/dist/orchestration/workflow-park-decision.js +1 -0
- package/dist/parked-decide.d.ts +4 -5
- package/dist/parked-decide.js +3 -1
- package/dist/permission-rule-vocab.d.ts +1 -23
- package/dist/permission-rule-vocab.js +0 -13
- package/dist/plugins/approval-payload-fingerprint.d.ts +14 -6
- package/dist/plugins/approval-payload-fingerprint.js +1 -1
- package/dist/plugins/caching-session-store.d.ts +1 -1
- package/dist/plugins/caching-session-store.js +4 -3
- package/dist/plugins/checkpoint-store-sql.d.ts +22 -1
- package/dist/plugins/checkpoint-store-sql.js +45 -1
- package/dist/plugins/local-checkpoint-store.d.ts +13 -2
- package/dist/plugins/local-checkpoint-store.js +26 -0
- package/dist/plugins/local-session-store.d.ts +12 -9
- package/dist/plugins/local-session-store.js +23 -14
- package/dist/plugins/local-session-window.d.ts +12 -6
- package/dist/plugins/local-session-window.js +1 -17
- package/dist/plugins/one-time-migrations.d.ts +11 -0
- package/dist/plugins/one-time-migrations.js +12 -0
- package/dist/plugins/permission-rule-store-file.js +10 -6
- package/dist/plugins/permission-rule-store-sql.js +8 -8
- package/dist/plugins/pg-session-storage.d.ts +1 -1
- package/dist/plugins/pg-session-storage.js +39 -17
- package/dist/plugins/remote-env-device.d.ts +14 -7
- package/dist/plugins/remote-env-device.js +23 -15
- package/dist/plugins/session-ownership.d.ts +11 -0
- package/dist/plugins/session-ownership.js +11 -0
- package/dist/plugins/session-placement.d.ts +12 -14
- package/dist/plugins/session-placement.js +2 -15
- package/dist/plugins/tidb-session-store.d.ts +1 -1
- package/dist/plugins/tidb-session-store.js +39 -17
- package/dist/request-tool-axes.d.ts +12 -0
- package/dist/request-tool-axes.js +20 -0
- package/dist/run-local.js +1 -0
- package/dist/runs.d.ts +2 -1
- package/dist/runs.js +4 -3
- package/dist/sealed-key.d.ts +32 -0
- package/dist/sealed-key.js +55 -1
- package/dist/security.d.ts +9 -7
- package/dist/session-error-wire.d.ts +12 -5
- package/dist/session-error-wire.js +4 -1
- package/dist/session-sync-kernel.d.ts +37 -10
- package/dist/session-sync-kernel.js +10 -0
- package/dist/task-a2a.js +4 -3
- package/dist/task-mcp.d.ts +2 -2
- package/dist/task-mcp.js +20 -8
- package/dist/task-settings.d.ts +5 -1
- package/dist/task-settings.js +1 -1
- package/dist/tool-approval.d.ts +151 -52
- package/dist/tool-approval.js +113 -20
- package/dist/tool-rule-names.d.ts +6 -0
- package/dist/trace/core-keyset-guard.d.ts +4 -3
- package/dist/trace/exit-seat.d.ts +13 -1
- package/dist/trace/exit-seat.js +55 -1
- package/dist/trace/ledger-events.d.ts +3 -1
- package/dist/trace/project.d.ts +18 -1
- package/dist/trace/project.js +16 -4
- package/dist/trace/redact.d.ts +6 -3
- package/dist/trace/redact.js +191 -23
- package/dist/trace/self-minted-wire.d.ts +4 -0
- package/dist/trace/self-minted-wire.js +1 -0
- package/dist/trace/wire-projection-faces.d.ts +6 -0
- package/dist/trace/wire-projection-faces.js +6 -0
- package/dist/web-admin-client/skills-mcp.d.ts +1 -1
- package/dist/web-admin-client/skills-mcp.js +2 -3
- package/dist/web-admin-client/types.d.ts +1 -1
- package/package.json +2 -2
- package/scripts/drain-check-local.mjs +34 -2
- package/scripts/drain-check.mysql.sql +20 -2
- package/scripts/drain-check.pg.sql +20 -2
- package/dist/mcp-tool-faces.d.ts +0 -69
- package/dist/mcp-tool-faces.js +0 -104
package/MIGRATION.md
CHANGED
|
@@ -7,6 +7,93 @@
|
|
|
7
7
|
> ⚠️ 完整清单在仓库根 `CHANGELOG.md`——它**不随 npm tarball 出包**(本文件随包)。看完整迁移窗的
|
|
8
8
|
> 权威姿势是源码 tag diff:`git diff v<旧>..v<新>`(每版都推 `v<版本>` tag);npm 包页也镜像 CHANGELOG。
|
|
9
9
|
|
|
10
|
+
## 7.134.0 —— 停驻任务续跑上下文里的请求凭据只以封签落盘(安全;BREAKING,嵌入方自建 ServiceDeps 带 checkpointStore 必须带 checkpointCtxSealing,否则 createHttpServer 拒启;保管目录建不了的耐久审批部署起服拒;保管目录跨重启持久、多副本共享;先停光 7.133.x 再起 7.134.0,两版不能混跑;一次性迁移 M28) · 引擎 7.53.0 → 7.54.1 提货:先升所有会续跑的 worker(停驻行多两只要求词) · 设备车道发射闸的三条部署前置 · 决定体 `feedback`(人的放行带的话;拒决 / 策略上 400、直连门 400) · `APPROVAL_AUTO_BUDGET` 的放行改记成策略答的放行 · `mcpServers[].toolFaces` / `startupTimeoutMs` 坏形新提交 400 · 请求侧 `toolAxes` 写 `boundaryFlow` 被拒 · 设备车道文件失败码 `target_unavailable` 与换绑后的续跑拒 · placed 会话准入拒句换成引擎的句子 · 带主体头的 service 凭据请求在 fleet 面上就是那位主体(安全;BREAKING:行为面收窄(多租前端带主体头的运维脚本不再看全舰队,升级前核)+ 类型面(嵌入方自实现的会话店暂存导入)) · 审批卡只发给声明认得这一族的客户端(请求面 `settings.approvalFrameFamilies`;没声明的任务遇审批当场停驻 / 拒;BREAKING,wire;客户端先升,见位才发)
|
|
11
|
+
|
|
12
|
+
1. **停驻任务续跑上下文里的请求凭据只以封签落盘**(安全;BREAKING,嵌入方自建 ServiceDeps 带 checkpointStore 必须带 checkpointCtxSealing,否则 createHttpServer 拒启):开了耐久审批的部署(`DURABLE_APPROVAL`;单用户 + 有耐久店时缺省开,含 `DB_BACKEND=local`),任务提交体里 `mcpServers[].transport.headers` / `transport.env` 的值、
|
|
13
|
+
`a2aPeers[].headers` 的值、`settings.env` 的值、`settings.webSearch.apiKey`、http hook 条目的静态头 `settings.hooks.<事件>[].hooks[].headers` 的值在续跑上下文
|
|
14
|
+
(SQL `checkpoint_ctx.ctx` / local `<数据根>/checkpoint-ctx/`)里改存封签(对着这一域自己的保管钥匙 `<数据根>/sealed-key/checkpoint-ctx-keys/` 最新那把公钥封,绑着这一行的会话;
|
|
15
|
+
与模型钥匙 `sealedApiKey` 那对钥匙不共用);续跑时开封,任务照原值重建。开不开 ⇒ `409 conflict.checkpoint_ctx_unsealable`(`field` + `reason`)。**升级前做**:
|
|
16
|
+
- **保管目录可建**:服务要能建并 `chmod 0700` `<数据根>/sealed-key/` 与其下的 `checkpoint-ctx-keys/`(只读挂载 / 权限不够 ⇒ 7.133.x 只是一行 warn `sealed_key_store_unavailable`,
|
|
17
|
+
7.134.0 起耐久审批在场的部署先 warn `checkpoint_ctx_sealing_unavailable` 再起服拒,拒句点名 `checkpointCtxSealing`;确实不要停驻就显式 `DURABLE_APPROVAL=false`)。
|
|
18
|
+
- **保管目录跨重启持久**(SQL 后端也一样:钥匙不进库):`<数据根>/sealed-key/` 在一个跨重启留存的卷上。目录空 ⇒ 起服生成新钥匙 ⇒ 重启之前停驻、带上述凭据的任务续跑恒答 409
|
|
19
|
+
`reason:"key_not_held"`(旧钥匙已不在任何副本上,恢复不了,只能作为新任务重提)。容器部署给数据根(或至少 `sealed-key/`)挂持久卷。
|
|
20
|
+
- **多副本共享钥匙**:SQL 后端的多副本,每个副本的 `<数据根>/sealed-key/` 必须是同一份(整个目录,含子目录 `checkpoint-ctx-keys/`;先起一个副本让它生成,再把那份目录放进每个副本 —— 与 USAGE §9.10 ⑦
|
|
21
|
+
模型钥匙的多副本要求同一件事)。不共享 ⇒ A 副本封的行在 B 副本上续跑答 409 `reason:"key_not_held"`;把钥匙文件补齐、重启那个副本(钥匙只在起服时读)之后,同一个决定原样重发即可。
|
|
22
|
+
- **升级序:7.133.x 与 7.134.0 不能混跑**。**先停掉全部 7.133.x 副本,再起第一个 7.134.0 副本**(一次性迁移 M28 在它起服时跑,把升级前停驻行里的明文原地封好:不删行、不动续跑时钟;
|
|
23
|
+
起服 info 一行 `checkpoint_ctx_credentials_sealed`;M28 的窗 = 发布日 + 30 天,窗外不扫)。多副本:先起一个 7.134.0 副本、等它起好再起其余副本(耐久审批开着时它起好即打出上面那一行)——
|
|
24
|
+
停驻封签的钥匙在新子目录 `sealed-key/checkpoint-ctx-keys/` 里,升级后第一个起服的 7.134.0 副本生成它(`sealed-key/` 早已共享好也一样:那是模型钥匙的那一层);
|
|
25
|
+
同时起多个副本会各建一把,互相答 `key_not_held`,直到各自重启一次(钥匙只在起服时读目录里的全部文件)。混跑时 7.133.x 副本续跑一条已封签的行(M28 封过的,或 7.134.0 副本新写的)会把凭据位当空值 ——
|
|
26
|
+
MCP 的 `headers` / `env` 整键不交(server 照挂)、A2A 对端不挂、`settings.env` 不采纳、WebSearch 不挂、`settings.hooks` 整份不采纳(同一份里的 command hook 一起)—— 任务无凭据照跑、不报错
|
|
27
|
+
(只有运维日志各一行 warn);7.133.x 的续跑读者没有对封签值的拒臂,新版本改不到它。反过来,7.133.x 副本新写的明文行在 7.134.0 副本上续跑答 409 `reason:"plaintext"`。
|
|
28
|
+
- **回滚**:降级到 7.133.x 之前先把带凭据的停驻行结掉(决定或取消);已封签的行在 7.133.x 上同样是无凭据续跑、不报错。随包排空检查的 `sealed-credentials-rollback` 判据列出它们:
|
|
29
|
+
`DB_BACKEND=local` 跑 `node node_modules/@sema-agent/server/scripts/drain-check-local.mjs [<数据根>]`(末行 `SEALED-CREDENTIALS-ROLLBACK rows=0` 才算结完,命中行 `field=` 点名哪一个凭据位;workflow 子代卡续跑读的是发起会话那一份,自己那一份不带封签时脚本按候选列出、点名 `runId=`);
|
|
30
|
+
SQL 后端跑随包 `scripts/drain-check.{mysql,pg}.sql` 里同名那一段(`hit_rows` 为 0;它数的是候选超集)。与第 2 条(`approval_targets` / `cross_boundary` 两类停驻行)同一个动作。
|
|
31
|
+
- **嵌入方**(自建 `ServiceDeps`,带 `checkpointStore`;BREAKING —— tsc 照绿、起服拒就是通知):新可选位 `checkpointCtxSealing`,带店就必须带它,否则 `createHttpServer` 拒启;
|
|
32
|
+
铸法 `await ensureCheckpointCtxSealing(sealedKeyDir)`(在 `sealedKeyDir` 之下建 `checkpoint-ctx-keys/`),工厂与型 `CheckpointCtxSealing` 从包根导出。
|
|
33
|
+
- **读拒体的消费方**:`conflict.checkpoint_ctx_unsealable` 是新码(409,确定性 —— 原样重试同答;`reason` 是 `key_not_held` 时等运维补齐钥匙、重启那个副本(钥匙只在起服时读)再重发,其余作为新任务重提、
|
|
34
|
+
仍停驻的先取消)。
|
|
35
|
+
2. **升级序:先把所有会续跑的 worker 升到 7.134.0,再让新 worker 停行**。7.134.0(引擎 7.54.1)停下的审批行可能带两只新要求词:`approval_targets`(批准对象多一位 `pendingAction.targets`,`rosterDigest` 停铸)
|
|
36
|
+
与 `cross_boundary`(执行在目标设备上的树)。7.133.x 及以前的 worker 在认领前拒这类行(`checkpoint.unsupported_version`,`version_newer`,`requires` 点名该词),行留 pending,
|
|
37
|
+
换到新 worker 上照常赎回。SQL 后端把检查点存成 JSON 文档 —— **零 DDL**。回滚到 7.133.x 之前先把这两类停驻行结掉(决定或取消)。
|
|
38
|
+
3. **设备车道发射闸的三条部署前置**:① 带 `cross_boundary` 的行只在 ≥ 7.134.0 的 worker 上赎回(同第 2 条);
|
|
39
|
+
② 设备会话的新一轮没有版本闸 —— 整个舰队都 ≥ 7.134.0 之前不要开 `multi_tenant` 的设备车道;③ 姿态 `RunnerDeps.crossBoundaryPosture` 每台 worker 显式同值,本服务由 `REQUIRE_PRINCIPAL` 派生
|
|
40
|
+
(`true` ⇒ `multi_tenant`,否则 `single_user`;不是新旋钮)。没声明姿态的 worker(嵌入方自建 Runner、不经本服务的部署姿态座席族)对宿主父腿不启站,并按每条武装腿铸一条 operator 通告
|
|
41
|
+
`config.cross_boundary_posture_undeclared`。`REQUIRE_PRINCIPAL=true` 且跑设备车道的部署:写到设备之外、离目标读之后回到设备的调用从此停下等人(bypass、策略答、无人值守的整批放行都不放)。
|
|
42
|
+
新的事件出现在设备车道的 run 上(宿主车道永不出现):`GET /v1/runs/:id/events` 上是 `cross_boundary_taint`,`GET /v1/tasks/:id/stream` 上是 `cross-boundary-taint`;按事件名精确分派的消费方多认一种。
|
|
43
|
+
4. **耐久决定口的决定体多一只键 `feedback`**(人写在审批卡上给代理的话):只在人的放行上收;拒决或 `decidedBy: "policy"` 上带它 ⇒ `400 request.field_conflict`(`detail.kind: "approval_feedback"`),
|
|
44
|
+
非字符串 ⇒ `400 request.field_invalid`,直连门 worker 上 ⇒ `400 feedback_not_in_proof`;话里带 `<system-reminder>` 标签 ⇒ `409 approval_binding_mismatch`(引擎原句,卡留 pending,改那段话再决)。
|
|
45
|
+
7.133.x 对这只键答 `400 request.body_shape`(`unknownKeys:["feedback"]`)。混合舰队里引擎 < 7.54.0 的 worker 静默忽略它 —— 同第 2 条,先升会续跑的 worker。
|
|
46
|
+
按 `detail.kind` 穷举分支的消费方多一个词 `approval_feedback`。
|
|
47
|
+
5. **`APPROVAL_AUTO_BUDGET` 的放行改记成策略答的放行**:额度内放行的那次调用,结算记录是 `policy_allowed`、审批人 `auto_approve_budget`(此前是名单表态的放行)。
|
|
48
|
+
引擎 7.54.0 起名单表态不再替 MCP / A2A 工具答掉「没人表态」那一问,不改就会每次出卡;现在额度在这两类工具上恢复生效。没有人可问的探针请求、参数绑不住批准的调用不扣额度。
|
|
49
|
+
按结算记录审计的规则跟着改。
|
|
50
|
+
6. **`mcpServers[].toolFaces` 与 `startupTimeoutMs` 的坏形新提交 400**(判官换成引擎包根的 `readToolFaces` / `mcpStartupTimeoutMs`):`toolFaces` 上空工具名、保留审批卡、
|
|
51
|
+
`pathTarget.access` 不在 `read` / `create` / `edit`(`"write"` 拒)、`pathTarget.base: "root"` 而 `access` 不是 `read`、单独的 `patternParam`、未知成员、安全轴写成面成员、非布尔旗 ⇒ 整条 400
|
|
52
|
+
(续跑重放整条 server 不进;中心 / `config.d` 腿跳过该 server 并点名 warn;绕过本服务直接到引擎的部署声明整任务拒 `config.tool_face_invalid`)。`startupTimeoutMs` 坏值 ⇒ `400 request.field_invalid`
|
|
53
|
+
(7.133.x 照常 200、引擎拨号时只拒这一只 server)。改对那一格,见 USAGE 9.5e。
|
|
54
|
+
7. **请求侧 `toolAxes` 写 `boundaryFlow` 被拒**:`mcpServers[i].toolAxes.<tool>.boundaryFlow` ⇒ 新提交 `400 request.field_invalid`;`a2aPeers[i].toolAxes.<skill>.boundaryFlow` ⇒ 那条对端不挂、warn 点名。
|
|
55
|
+
续跑重放同 `toolAxes` 判不过的既有处置:一次性迁移 M19 的窗内按升级前读法重读(`boundaryFlow` 作未知成员剔掉、条目照挂、一行运维 warn),窗关后整条不进。
|
|
56
|
+
声明引擎专用工具只有部署声明(`config.d` / 配置中心)能写:`boundaryFlow: "engine"` 且同条 `egress: false`。
|
|
57
|
+
8. **设备车道:文件失败码 `target_unavailable`;续跑腿的拒带机读终局码**:文件操作的失败码,目标在提交前拒(不在线 / 忙 / 未绑 / 别处挂着 / 认领冲突 / 换绑冲突 / 限流 / 排空)与设备结局 `target_unavailable` 从 `transport_lost` 改为 `target_unavailable`
|
|
58
|
+
(从未开始、零效果、目标回来后重试安全)—— 按 `transport_lost` 匹配设备文件失败的规则跟着改。停驻行记下的设备与 placement 根此刻绑的不是同一台(换绑过)⇒ 续跑失败终局码 `device.identity_mismatch`
|
|
59
|
+
(不连另一台、不改道):先把停驻的任务结掉或取消,再在新绑的设备上重新起。工厂的其余几只拒同样带机读码,续跑失败的终局码随工厂抛的 `.code` 变(修前码只在句子里,终局码不带它):
|
|
60
|
+
placement 根没有设备绑定 ⇒ `device.not_bound`;绑定的设备已不存在或不归该绑定的主体 ⇒ `not_found.device`;设备已吊销 ⇒ `device.revoked`;placement 根为空(装配错误)⇒ `config.placement_root_invalid`。
|
|
61
|
+
按终局码匹配续跑失败的读者(运维看板 / 重试规则)把这四个词与 `device.identity_mismatch` 一并认进去;前三形的出路同换绑(结掉停驻的任务,绑上可用的设备 —— 吊销是终态,重新入籍得到的是新设备 —— 再重新起),`config.placement_root_invalid` 是装配错误、重试无益。
|
|
62
|
+
9. **placed 会话准入的拒句换成引擎的句子**:判据改问引擎(`admitSessionPlacement`),码不变(`placement_refused`);缺元组那一形的句尾从 `written away out of band` 变成 `or the repo dropped the tuple` ——
|
|
63
|
+
按原句匹配告警的运维规则跟着改。
|
|
64
|
+
10. **「service 凭据 + 验过的主体」= 那位主体,不再是全舰队**(安全,BREAKING:行为面收窄(多租前端带主体头的运维脚本不再看全舰队,升级前核)+ 类型面(嵌入方自实现的会话店暂存导入);主体按部署的身份读法 —— 前端设的主体头,直连门上是签名的 principal 令牌,那里自报的头不算):舰队流 `GET /v1/fleet/stream`、会话列表 `GET /v1/sessions`、发件链接列表 `GET /v1/sendfile-links`、
|
|
65
|
+
后台名册 `GET /v1/agents/roster`、trace 读口 `GET /v1/tasks*`、用量 `GET /v1/usage/*`、`GET /v1/tasks/source-summary`、`/metrics*`、`DELETE /v1/sessions/:id`、`POST /v1/sessions/:id/fork`、
|
|
66
|
+
同步族 `/v1/sessions/:id/sync/*` 上,带 `x-agent-principal` 的 service 凭据请求按那位主体钉住(行只列它的;他人会话 404 / `{deleted:false}`;运维聚合两口 `401 auth.unauthorized`)。
|
|
67
|
+
全舰队视图 = **不带主体头**的 service 凭据(读写两档),或只读令牌(只读面 `TRACE_TOKEN`;指标族 `METRICS_TOKEN`)。dev-open 部署、只读 `TRACE_TOKEN`、不带主体头的 service 凭据的读数不变;
|
|
68
|
+
运维名册(`OPERATOR_PRINCIPALS` / `OPERATOR_SINGLE_USER`)不给 fleet 面开全舰队,运维口照旧读它。
|
|
69
|
+
- **升级前核**:多租部署(`REQUIRE_PRINCIPAL=true`)上用「service 凭据 + 某个主体头」拉全舰队视图的运维脚本 / 看板 / 中继 —— 去掉主体头,或改用只读令牌;单用户部署同理(壳不设 `SEMA_LIVE_PRINCIPAL` 时发 `default`,
|
|
70
|
+
它的舰队流 / 会话列表 / trace / 用量按 `default` 收窄,两只运维口对它 401)。
|
|
71
|
+
- **存量**:此前经「service 凭据 + 主体头」同步推上来的新会话是无主行,在同步族上对那位主体是 404,fork 同样 404(单用户部署的 `default` 主除外:存量无主会话互认臂照旧放它 fork / 删除 / 同步;会话视图照旧开放);
|
|
72
|
+
要交回那位主体 ⇒ 不带主体头拉下来,再以那位主体推成一只新 id。存量无主会话的同步族在单用户形下照旧可用(与 fork / 删除同一只互认臂:单用户形 ∧ 行无主且非 placed ∧ 调用方恰为 `default`);多租形、别的主体、无主的 placed 行仍 404。
|
|
73
|
+
`default` 推进一只无主会话时,只有真提交(`fresh` / `fast_forward` / `fork` / `stale`)把这行的属主落成推送方,`identical` 捷径不提交、不换属主(行照旧无主,附随物照常重放)。
|
|
74
|
+
- **暂存句柄**:同步导入第一阶段由 fleet 调用方(不带主体头的 service 凭据)开的暂存,第二阶段也只认 fleet 调用方 —— 两阶段请同一把凭据、同一个主体头。
|
|
75
|
+
- **导入第二阶段与第一阶段同一只属主门**:第二阶段在流读完之后、提交前按会话此刻的面再判同一只门 —— 流期间别的副本删建 / 认领 / 新建了这只会话、这位调用方此刻不过门 ⇒
|
|
76
|
+
`404 not_found.session`(与门同体);提交只落在开暂存时门判过的那一格属主上 —— 属主格变了而调用方仍过门(fleet 调用方,或那一行此刻归它)⇒ `409 conflict`,
|
|
77
|
+
固定句 `session owner changed while the import streamed; nothing was imported — start the sync again`,事务回滚、目标会话一个字节没动。两答的出路相同:从第一阶段重来(重新过门)。
|
|
78
|
+
≤7.133.x 第二阶段只核暂存开启者,照覆盖、并把目标行的属主改成导入方。嵌入方自实现会话店的暂存导入(`OwnerAwareSessionStore.beginImportStaging` 交出的句柄):
|
|
79
|
+
`commit(owner, opts)` 的 `opts` 必传、带 `expectedOwner`(按 `ownerOf` 的读法:`undefined` = 无此会话,`null` = 无主),提交事务里属主格不等就回滚、抛码为 `conflict` 的 `SessionError`(类型面 BREAKING,tsc 红即通知)。
|
|
80
|
+
- 7.133.0 节第 10 条最后一句说的「`/sync/*` 把带凭据的调用方读成 fleet-wide」自本版起不成立:`/sync/*` 与会话视图读同一只判据。
|
|
81
|
+
|
|
82
|
+
11. **审批卡只发给声明认得这一族的客户端(请求面声明 `settings.approvalFrameFamilies`;BREAKING,wire)**:一条任务只在提交体的 `settings.approvalFrameFamilies` 含本服务呈卡用的那一个族
|
|
83
|
+
(`GET /v1/capabilities` 顶层 `approvalFrameFamilies` 的那个词)时收到活卡;缺席 / 不含 ⇒ 这条任务遇审批当场收场,不呈卡、不换族、不双发,也不再挂到窗口过期。结局看这台 worker 有没有耐久店(checkpoint 店;`DURABLE_APPROVAL` 开着):**在场** ⇒ 按 `UNATTENDED_APPROVAL_POLICY`
|
|
84
|
+
(`park` ⇒ 任务停驻、`/v1/approvals/:sessionId/decide` 作答;`deny` ⇒ 当场拒,拒因句点名缺的族);**不在场**(`DURABLE_APPROVAL=false`,含 helm 图表关持久的形)⇒ 没有地方停驻,受门工具当场拒(缺省 `park` 政策下是引擎按 `approver_unavailable` 拒,结算当事方是「无人」,不是 `deny` 政策的结算,拒因句不点名缺的族与声明键;`deny` 政策下是部署政策拒),run 照常收尾,无卡、无 `/decide`。**升级前做**:
|
|
85
|
+
- **会渲染审批卡的客户端(壳 / SDK 审批循环 / 自写客户端)**:见能力位 `taskSettings.approvalFrameFamilies: true` 才带键(7.133.x 及以前对这个键答 `400 request.body_shape`),
|
|
86
|
+
在 `POST /v1/tasks/stream` · `POST /v1/runs` 的提交体里声明你渲染得了的**每一个**族(两族都渲染就写两个;只写最新那一个,到了呈 `tool_approval` 的服务上就没有活卡)。
|
|
87
|
+
- **升级序**:发版时还没有主线客户端发 `settings.approvalFrameFamilies` —— 声明随 cli / client-core 的后续版本到货(以各自的发布说明为准)。到货前:耐久 worker 上 park 等 /decide,非耐久 worker 上(缺省 `park` 政策下)引擎按 `approver_unavailable` 拒(fail-closed;`deny` 政策下是部署政策拒);
|
|
88
|
+
运维要保旧壳体验,只能先不升 server。到货后:客户端先升到会声明的版本,再升服务端;反过来的窗口里,不声明的客户端遇审批当场停驻 / 拒(不再挂住)。
|
|
89
|
+
- **没有耐久店的 worker**(`DURABLE_APPROVAL=false`,含 helm 图表关持久的形):没有地方停驻 —— 未声明的客户端遇受门工具当场拒,run 照常收尾,无卡、无 `/decide`。对 7.133.x 这是有意的行为收窄
|
|
90
|
+
(那类 worker 在 7.133.x 上对这类客户端出活的 `tool_approval` 卡):硬破坏,不发旧族、不双发、不留回退;客户端先声明,或先不升服务端。
|
|
91
|
+
- **关了流内审批的部署**(`STREAM_APPROVAL_ENABLED=false`,本服务呈 `tool_approval`):判据对两个族同一条 —— 为老客户端专门关掉它的部署,升到本版后老客户端同样当场停驻 / 拒,
|
|
92
|
+
要客户端声明 `"tool_approval"` 才有活卡。
|
|
93
|
+
- **升级前停驻的任务**:续跑读持久化的提交体,里面没有声明 ⇒ 续跑那一段遇审批同样当场收场(停驻的卡照旧经 `/decide` 作答)。
|
|
94
|
+
- **坏声明**:非数组 / 词表外 / 重复 ⇒ `400 request.field_invalid`(`field: "settings.approvalFrameFamilies"` · `reason: "bad_entry"` · 坏项的 `index`);`[]` 合法(与缺席同读)。
|
|
95
|
+
- **按运维日志分账的**:`approver_unavailable` 多一个 `arm` 词 `frame_family_undeclared`。
|
|
96
|
+
|
|
10
97
|
## 7.133.0 —— `SANDBOX_PKG_SOURCE=custom` 的包源 / 镜像地址带 userinfo 起服拒启(安全) · 删包根导出 `judgeModelRouteGap`(BREAKING;壳 / 包改读 `GET /v1/models` 的 `modelPlane`) · 审批帧一条腿一个帧族(BREAKING,wire;消费方按 `approvalFrameFamilies` 分派) · 坏 JSON 体统一答 `400 request.invalid_json`、读体超限统一答 `413`(BREAKING,wire 码 / 状态码变更;此前多数口答 `request.rejected`,一批口把超限折成 `400 request.invalid_json`) · device 车道四根数值旋钮越界起服拒启、协议修订号抬到 2(结局码集随修订号) · acceptEdits 的免问写域改为引擎的写根(行为变更;沙箱车道上不再等同 `default`) · 审批列表面同族、`approvalId` = 行的键、已决卡再应答回放首决(BREAKING,wire 值变更) · 委派子代会话的属主随委派树写入、无主的委派子代行不对具名主体放行、SQL 后端存量子代行每次起服回填(安全) · 嵌入方 `ServiceKnobDeps.snapshotBlobSqlCap` 读口退役(BREAKING;零别名;要固定帽设 `SNAPSHOT_BLOB_SQL_MAX_BYTES`) · 子代转录读法 = 同一会话读面 + 终态帧 `transcriptId`、会话视图体顶层加性键 `agent`(消费方读法) · 没声明 `REMOTE_EXEC_HOME_DIR` 的 k8s / local-docker / ssh / adb 车道自己实测执行环境的家目录(行为变更;声明仍胜;起服 warn `execution_lane_home_undeclared` 只留给 device) · SQL 后端 workflow 续跑日志的属主 scope 搬进新表 `workflow_journal_owner`、`workflow_journal` 起服一次性按新主键重建(持久数据;🔴 先停旧副本再起新;收编回执腿名随表改) · workflow 子代的审批决定落盘:宿主会话的每一跑都带着它、同一决定重投回同一张回执(行为变更;SQL 后端起服多建一张表、整队一次切;SQL 后端配 `WORKFLOW_RUN_STORE=file|memory` 且开自编排起服拒) · 审批账本有了保留窗:结了且挂靠的东西都已终局的问安静超过 30 天就整问删掉(行为变更;要永久保留设 `APPROVAL_ASK_RETENTION_MS=0`,且要在升级后的第一次清扫之前) · 多租部署上请求 `settings.permissions.allow` 与按名点用的自带技能 `allowedTools` 按调用方信任档受理(安全,行为变更;低角色 / 不带角色的登录令牌带非空的 ⇒ `400`,能力位 `settings.permissions.callerTrust`) · 本地配置源 `config.d` 读之前判目录信任:别人改得动就拒启(安全;升级前自查权限;新旋钮 `CONFIG_LOCAL_OWNER_UID`) · 单用户记忆项目层换键形 `userrepo:<属主>/<档>.<摘要>`(带主体、跨设备;开记忆同步的部署项目层也同步;先升中心;存量项目层第一次在该项目跑任务时自动折进新键) · SQL 后端断路器键换成路由身份摘要、写新表 `breaker_state`,退役表 `circuit_breaker` 起服删掉(持久数据;无需动手) · `settings.hooks` 的 `source` / `plugin` / `args` 被读(不带且不引用插件变量零动作;托管组独立车道、跑不成即折 ask;exec 形不再被剥参数;引用不在场的插件变量的命令起不来) · 名单为空的单用户部署:审批队列不再把每个调用方当运维,要当自己的运维写一行 `OPERATOR_SINGLE_USER=true`(行为变更) · 隔离型车道(e2b / k8s / local-docker)的读面缺省 `READ_FACE` 缺席时从 `roots` 变 `open`(行为变更;把宿主目录或共享卷挂进沙箱的部署升级前写 `READ_FACE=roots`) · center 的 `allowBypassPermissions` / `allowAutoMode` 两键开始生效:组织关掉的模式由引擎折回 `default`(安全;行为变更) · 模式缺席的请求也问 `Workflow`(行为变更;要清掉这一问用 `settings.permissions.allow:["Workflow"]`) · 中心凭证换人:server 自己降到「无中心配置」档、当场拉取,`/health` 多一键 `centerIdentityNotice`(行为变更;没有 env 模型的部署在换人窗口里没有可派发模型) · 嵌入方:`StoreBackend.orgDeliveryQuota()` / `StoreBackend.checkpointReadView()` 必选(BREAKING,类型面;`orgDeliveryQuota()` 的返回型从包根导出,读口按 `StoreBackend` 成员型读) · SQL 后端收件箱里身份键过不了判官的存量行起服删掉(持久数据;无需动手)
|
|
11
98
|
|
|
12
99
|
1. **`SANDBOX_PKG_SOURCE=custom` 的六只显式地址不得带 userinfo,带的起服拒**(安全):`SEMA_APT_MIRROR` / `SEMA_PIP_INDEX` / `SEMA_UV_INDEX` / `SEMA_UV_PY_MIRROR` /
|
package/README.md
CHANGED
|
@@ -106,7 +106,7 @@ MODEL_GATEWAY_BASEURL=https://api.deepseek.com MODEL_ID=deepseek-v4-flash \
|
|
|
106
106
|
MODEL_API_KEY=<your-key> SERVICE_AUTH_TOKEN=<pick-one> \
|
|
107
107
|
node node_modules/@sema-agent/server/dist/main.js # → :8090
|
|
108
108
|
|
|
109
|
-
# B) container (zero local deps
|
|
109
|
+
# B) container (zero local deps; ⚠ images are no longer published — build your own, see "Image publishing discontinued" below and USAGE 9.13)
|
|
110
110
|
docker run -p 8090:8090 \
|
|
111
111
|
-e MODEL_GATEWAY_BASEURL=https://api.deepseek.com -e MODEL_ID=deepseek-v4-flash \
|
|
112
112
|
-e MODEL_API_KEY=<your-key> -e SERVICE_AUTH_TOKEN=<pick-one> \
|
|
@@ -127,7 +127,7 @@ curl -s localhost:8090/v1/tasks -H "Authorization: Bearer <SERVICE_AUTH_TOKEN>"
|
|
|
127
127
|
- **Distribution coordinates**: npm = [`@sema-agent/server`](https://www.npmjs.com/package/@sema-agent/server)
|
|
128
128
|
(public on npmjs) · images = `ghcr.io/sema-agent/sema-server` + `docker.io/claybobby/sema-server`
|
|
129
129
|
(both public; `:latest` rolling, `:<sha>` pinned).
|
|
130
|
-
- **Image publishing
|
|
130
|
+
- **Image publishing discontinued**: Container images are no longer published since 7.128.0; the npm package is the only release artifact; self-build steps are in USAGE 9.13.
|
|
131
131
|
- **Library embedding** (package root, e.g. `createHttpServer` + your own `StoreBackend`): after
|
|
132
132
|
`loadConfig()`, call `composeExecutionLane(config, ctx)` — the HTTP service and `run-local` both do. Since
|
|
133
133
|
7.104.0 the execution-lane start refusals (`LEADER_ENABLED` on the host lane, a multi-tenant posture on a
|
package/README.zh-CN.md
CHANGED
|
@@ -95,7 +95,7 @@ MODEL_GATEWAY_BASEURL=https://api.deepseek.com MODEL_ID=deepseek-v4-flash \
|
|
|
95
95
|
MODEL_API_KEY=<你的-key> SERVICE_AUTH_TOKEN=<自定> \
|
|
96
96
|
node node_modules/@sema-agent/server/dist/main.js # → :8090
|
|
97
97
|
|
|
98
|
-
# B) 容器(
|
|
98
|
+
# B) 容器(零依赖;⚠️ 镜像不再发布,自建见下方「镜像不再发布」与 USAGE 9.13)
|
|
99
99
|
docker run -p 8090:8090 \
|
|
100
100
|
-e MODEL_GATEWAY_BASEURL=https://api.deepseek.com -e MODEL_ID=deepseek-v4-flash \
|
|
101
101
|
-e MODEL_API_KEY=<你的-key> -e SERVICE_AUTH_TOKEN=<自定> \
|
|
@@ -110,7 +110,7 @@ curl -s localhost:8090/v1/tasks -H "Authorization: Bearer <SERVICE_AUTH_TOKEN>"
|
|
|
110
110
|
- **分发坐标**:npm = [`@sema-agent/server`](https://www.npmjs.com/package/@sema-agent/server)
|
|
111
111
|
(npmjs 公开)· 镜像 = `ghcr.io/sema-agent/sema-server` + `docker.io/claybobby/sema-server`
|
|
112
112
|
(均 public,`:latest` 滚动 / `:<sha>` 钉版)。
|
|
113
|
-
-
|
|
113
|
+
- **镜像不再发布**:容器镜像自 7.128.0 起不再发布,npm 包是唯一发布件;自建镜像步骤见 USAGE 9.13。
|
|
114
114
|
- **作为库嵌入**(包根导出,如 `createHttpServer` + 自建 `StoreBackend`):`loadConfig()` 之后调
|
|
115
115
|
`composeExecutionLane(config, ctx)` —— HTTP 服务与 `run-local` 都这么做。7.104.0 起执行车道的几条拒启(host 车道上开
|
|
116
116
|
`LEADER_ENABLED`、多租户姿态落在共享车道、device 车道缺耐久店)判在这一步,不在 `loadConfig()` 里;
|
package/USAGE.md
CHANGED
|
@@ -11,7 +11,8 @@
|
|
|
11
11
|
|
|
12
12
|
> 部署 = 把本仓跑在一台**能连到模型网关 + TiDB** 的机器上;那台机器的 `IP:8090` 就是你的 `<host>:8090`。
|
|
13
13
|
> 该机器需要 **Node 22 / Bun**(路线 A,无 docker)**或** **Docker**(路线 B)——二选一。
|
|
14
|
-
> 容器镜像自 7.128.0
|
|
14
|
+
> 容器镜像自 7.128.0 起不再发布,npm 包是唯一发布件;自建镜像步骤见 USAGE 9.13(路线 B 用 `docker compose up --build` 就地构建,不受影响;已发布的旧镜像仍可拉,`:latest` 停在 7.128.0 之前的版本)。
|
|
15
|
+
> 要自己构建引擎镜像(推私有 registry / 导入 k3s 节点 / 升到 ≥ 7.128.0):两条路径 —— 有源码仓访问权就在源码 tag 上用仓根 `Dockerfile` 构建,只有 npm 访问权就从 npm 包构建 —— 步骤见 §9.13。
|
|
15
16
|
|
|
16
17
|
**路线 A — 不用 docker(单实例,最简单)**
|
|
17
18
|
```bash
|
|
@@ -878,6 +879,13 @@ QUESTION_TTL_MS=300000
|
|
|
878
879
|
> `streamApproval` 为真)只发 `approval_request` / `approval_complete` / `approval_revoke`,旧族 `tool_approval` / `tool_approval_complete`
|
|
879
880
|
> 不再发;协议不上场的 worker 只发旧族。帧格式、消费端契约、五合取全表在
|
|
880
881
|
> [`docs/ASSISTANT-WIRE-CONTRACT.md` §4a-quint](docs/ASSISTANT-WIRE-CONTRACT.md)(错误码 `feature.approval_ask_disabled` 在附录 A)。
|
|
882
|
+
> 🔴 **7.134.0 起卡只发给声明认得这一族的客户端**:提交体 `settings.approvalFrameFamilies: ("tool_approval" | "approval_request")[]` 声明这条 run 的客户端
|
|
883
|
+
> 渲染得了哪些族;一条 run 只在声明含 `GET /v1/capabilities` 顶层 `approvalFrameFamilies` 那个词时收到活卡。缺席 / 不含 ⇒ 这条 run 不呈卡(不换族、不双发)、
|
|
884
|
+
> 开流不重放挂着的卡,每只审批问(会话里已记住的放行照旧先兑现)当场收场,结局看这台 worker 有没有耐久店(`DURABLE_APPROVAL` 开着):在场 ⇒ 交 `UNATTENDED_APPROVAL_POLICY`:`park`(缺省)⇒ 任务停驻、`/v1/approvals/:sessionId/decide` 作答;`deny` ⇒ 当场拒,拒因句点名缺的族与出路;
|
|
885
|
+
> 不在场(`DURABLE_APPROVAL=false`,含 helm 图表关持久的形)⇒ 受门工具当场拒(缺省 `park` 政策下是引擎按 `approver_unavailable` 拒,结算当事方是「无人」,不是 `deny` 政策的结算,拒因句不点名缺的族与声明键;`deny` 政策下是部署政策拒)、run 跑完、无卡无 `/decide`(对 7.133.x 是有意的行为收窄:那类 worker 原本出活的 `tool_approval` 卡);两形都不出任何一个族的卡。
|
|
886
|
+
> 运维日志 `approver_unavailable` 的 `arm` 是 `frame_family_undeclared`。对两个族同一条(`STREAM_APPROVAL_ENABLED=false` 的 worker 呈 `tool_approval`,同样要声明)。
|
|
887
|
+
> 续跑读持久化的提交体(声明随体重放);耐久事件流 `GET /v1/runs/:id/events` 的开流重放读的也是这条 run 受理时落盘的那份声明(没有就按未声明:零帧);与同步流开流共读同一只判据函数,入参不全对称:关窗臂里「贴 deadline ⇒ `window_zero`」由 sync 腿判,事件流按声明与窗判。坏形(非数组 / 词表外 / 重复)⇒ `400 request.field_invalid`(`field` / `reason: "bad_entry"` / `index`)。
|
|
888
|
+
> 受理位 `taskSettings.approvalFrameFamilies`(恒真;老 server 对这个键 `400 request.body_shape` —— 见位才发)。读法全文在契约 §0.58。
|
|
881
889
|
|
|
882
890
|
```bash
|
|
883
891
|
# 协议总开关。默认 true;显式 =false 是**本协议面**的干净还原键(不发 approval_request / approval_complete、改发旧族帧、不落 ask 行、
|
|
@@ -1663,7 +1671,7 @@ bake-runner 要用 ⇒ `LSP_ENV_ALLOW` / `BAKE_ENV_ALLOW` 点名放回。
|
|
|
1663
1671
|
| 头 | 什么时候必须 | 含义 |
|
|
1664
1672
|
|---|---|---|
|
|
1665
1673
|
| `Authorization: Bearer <SERVICE_AUTH_TOKEN>` | 设了 `SERVICE_AUTH_TOKEN` 时,除 `/health` / `/readyz` 外的所有请求 | **谁有权调本服务**(OA 后端持有,服务到服务) |
|
|
1666
|
-
| `x-agent-principal: user:42` | 设了 `REQUIRE_PRINCIPAL=true` 时 | **代表哪个终端用户**(决定 session 归属 + 记忆隔离;**绝不从 body 取**) |
|
|
1674
|
+
| `x-agent-principal: user:42` | 设了 `REQUIRE_PRINCIPAL=true` 时 | **代表哪个终端用户**(决定 session 归属 + 记忆隔离;**绝不从 body 取**)。7.134.0 起也决定 fleet 面看得见什么:带了它的 service 凭据请求只看得到、动得到这位用户的(舰队流 / 列表 / 名册 / trace / 用量 / 删除 / fork / 同步);全舰队视图不带它(直连门部署上身份是签名的 principal 令牌,不是这个头) |
|
|
1667
1675
|
|
|
1668
1676
|
> **dev-open 部署的主体头是过滤 / 展示用,不是隔离。** 没接任何身份来源(没配 `SERVICE_AUTH_TOKEN(S)`、没装中心鉴权桥、没开直连门 —— 即 `/health.auth.kinds` 为空)、
|
|
1669
1677
|
> 也没配 `TRACE_TOKEN`、没开 `REQUIRE_PRINCIPAL` 的部署上,`x-agent-principal` 谁都能自报:
|
|
@@ -1682,7 +1690,7 @@ bake-runner 要用 ⇒ `LSP_ENV_ALLOW` / `BAKE_ENV_ALLOW` 点名放回。
|
|
|
1682
1690
|
> **`401 auth.unauthorized` 的 `error` 是一句出路(7.104.0 起;此前只有一个词 `unauthorized`)**,按这道门收什么分三句,码都是 `auth.unauthorized`、都**不含任何值**,
|
|
1683
1691
|
> 同一道门上「没带」与「带错」逐字同一句(区分就是探测面):① 全局凭据门 —— 说「带这台部署的 service token 作 Bearer」,并指 sema-up 部署的
|
|
1684
1692
|
> 令牌在 `~/.sema-up/<project>/.env` 的 `SERVICE_AUTH_TOKEN`;② 运维口(`/metrics*`、`GET /v1/tasks/source-summary`)—— 说要运维凭据
|
|
1685
|
-
> (service token
|
|
1693
|
+
> (不带主体头的 service token —— 带主体头的 service token 请求代那位主体、不是运维凭据,7.134.0 起;指标口另认只读的 `METRICS_TOKEN`,`source-summary` 另认只读的 `TRACE_TOKEN`);③ 过了凭据门、但这一口要调用方身份(属主寻址读写、规则、记忆)—— 说要一个验过的
|
|
1686
1694
|
> 调用方身份,**不**叫你再去带 service token。逐字句子见 `docs/ASSISTANT-WIRE-CONTRACT.md` 附录 A.1 码表。壳(sema CLI)接 sema-up 引擎的
|
|
1687
1695
|
> 令牌交法见 `deploy/sema-up/README.md`「壳接入」。
|
|
1688
1696
|
|
|
@@ -1850,11 +1858,12 @@ curl -N http://<host>:8090/v1/tasks/stream -H 'content-type: application/json' \
|
|
|
1850
1858
|
## 7. 其它接口(按需)
|
|
1851
1859
|
| 端点 | 用途 |
|
|
1852
1860
|
|---|---|
|
|
1853
|
-
| `GET /v1/sessions/<id>` | 审计回溯:当前上下文 + 摘要(owner 校验);消息里模型 / 工具产出的文本与 live 帧、账本帧同一只脱敏(密钥形换占位,其余字节不变);Read 图像 / PDF 的媒体卡(工具结果消息的 `details`)的 `file.base64` 是字节编码,逐字。**委派子代的转录**也走这一口:`<id>` = 子代帧 / fleet 子代行上的 `transcriptId`(未压缩过的子代首条用户消息是委派 prompt;子代跑过的工具是 assistant 消息里的 `toolCall` 段 + 同 `toolCallId` 的 `toolResult` 行;整段逐字条目走 `…/sync/entries`)。体顶层加性键 `agent: { id?, parentSessionId?, rootSessionId? }`(**7.133.0+**)说这是哪只子代:只投影会话行上存下来的委派元组,`id` 与 fleet 行的 `id`、`GET /v1/agents/roster` 的 `handle` 同一个串;键缺席 = 普通会话 / 老 server / 会话店不存元组(local 后端),读作「没存」、回落 fleet 行 / 名册上的 id。子代会话的属主是铸行那一刻它所在委派树的根会话的属主(**7.133.0+**,写下那一行时按当时读到的写入;此前 SQL 后端上子代会话无主、谁都读得到)⇒ 用发起子代的那位主体读;别的主体与不存在的 id 同一个 `404 not_found.session`。属主为空的委派子代行(根会话本就无主,或升级前写下、还没回填)只有 fleet 调用方读得到(出示 service 凭据、不带主体头;dev-open 部署不钉人),任何具名主体、没有 service 凭据的无身份调用方同样 404(
|
|
1854
|
-
| `GET /v1/approvals?owner=user:42` | 高危写审批(durable,需 `DURABLE_APPROVAL=true`):**operator** 看待办队列(可按 owner 过滤);非 operator
|
|
1861
|
+
| `GET /v1/sessions/<id>` | 审计回溯:当前上下文 + 摘要(owner 校验);消息里模型 / 工具产出的文本与 live 帧、账本帧同一只脱敏(密钥形换占位,其余字节不变);Read 图像 / PDF 的媒体卡(工具结果消息的 `details`)的 `file.base64` 是字节编码,逐字。**委派子代的转录**也走这一口:`<id>` = 子代帧 / fleet 子代行上的 `transcriptId`(未压缩过的子代首条用户消息是委派 prompt;子代跑过的工具是 assistant 消息里的 `toolCall` 段 + 同 `toolCallId` 的 `toolResult` 行;整段逐字条目走 `…/sync/entries`)。体顶层加性键 `agent: { id?, parentSessionId?, rootSessionId? }`(**7.133.0+**)说这是哪只子代:只投影会话行上存下来的委派元组,`id` 与 fleet 行的 `id`、`GET /v1/agents/roster` 的 `handle` 同一个串;键缺席 = 普通会话 / 老 server / 会话店不存元组(local 后端),读作「没存」、回落 fleet 行 / 名册上的 id。子代会话的属主是铸行那一刻它所在委派树的根会话的属主(**7.133.0+**,写下那一行时按当时读到的写入;此前 SQL 后端上子代会话无主、谁都读得到)⇒ 用发起子代的那位主体读;别的主体与不存在的 id 同一个 `404 not_found.session`。属主为空的委派子代行(根会话本就无主,或升级前写下、还没回填)只有 fleet 调用方读得到(出示 service 凭据、不带主体头;dev-open 部署不钉人),任何具名主体、没有 service 凭据的无身份调用方同样 404(存量无主会话互认臂 —— 单用户形下 `default` 主对无主的普通会话放行,fork / 删除 / `/sync/*`(同步族自 7.134.0 起)同走这一只 —— 对这些 placed 行同样不放行,审批豁免、notify 同样不放行;fork / 删除 / `/sync/*` 的 fleet 调用方与此处同一个判据:出示 service 凭据、不带主体头,带主体头的 service 凭据请求是那位主体,7.134.0 起);SQL 后端每次起服回填这些行(起服读数行 `placedSessionOwnersRemaining` 非零 = 还有没定下的,再起直到为 0,或等它们下一次停驻)。推不出属主的子代写成属主 `_orphan:<id>`,只有 fleet-wide / operator 读得到:SQL 后端的子代行根会话已删时按它记录的父会话,父也删了才是 `_orphan:`;停驻时拷进宿主店的那一份(local 后端)只按根判,根会话已删 ⇒ `_orphan:`;子代被派出 / 批准之后、再次停驻读根之前根会话被删并由别人以同一 id 重新登记 ⇒ 其下的子代再次停驻被否决(读过根之后才换的,写进去的仍是读根那一刻的属主) |
|
|
1862
|
+
| `GET /v1/approvals?owner=user:42` | 高危写审批(durable,需 `DURABLE_APPROVAL=true`):**operator** 看待办队列(可按 owner 过滤);非 operator 只看自己的。队列里某一行的存储体读不出来、重试不会好时,答 `500` + `errorCode:"approvals.read_corrupt"`(**7.134.0+**;与订阅流同码、同一只判官;运维处置那一行后再读,别原样重试) |
|
|
1855
1863
|
| `GET /v1/approvals/stream` | 同一队列的 SSE 推送(范围同上):首拍快照 → `pending` / `resolved` 增量(开了工具审批时另有 `live_pending` / `live_resolved`),空闲时心跳,15 分钟上限(`STREAM_MAX_DURATION` 帧后重连)。**心跳只表示流活着,不表示这一拍读过队列**:队列读不出时每次读失败发一帧 `error`(`approvals.read_unavailable`,流留着、下一拍再读,恢复后同一条流继续);某一行的存储体读不出来、重试不会好时发 `approvals.read_corrupt` 并收流(运维处置那一行后再重连)。15 分钟上限对读失败的拍同样算数 |
|
|
1856
1864
|
| `POST /v1/approvals/<sessionId>/decide` `{"decision":"approve"|"deny","reason":"…"}` | 批/否并恢复挂起任务(CAS,重复决议 409);**仅 operator**(非 operator → 403)。5.0.0 起旧轮询腿 `POST /v1/approvals/<id>` 已退役 |
|
|
1857
|
-
| `GET /v1/
|
|
1865
|
+
| `GET /v1/tool-approvals/<approvalId>` | 按问读口(**7.133.0+**;与 `POST …/respond` 同门同身份,不是你的 ask 与查无同形 404):一只审批问此刻在哪一态,`state` ∈ `pending` / `decided` / `parked` / `void`(闭集)。停驻卡被答过 ⇒ `decided`、停驻 run 被取消 ⇒ `void`(**7.134.0+**;7.133.x 恒答 `parked`);停驻行的门已结算却读不出可用的决议(含坏行:状态词 / 种类 / 当事方不在闭集、结算记录过不了引擎的不变量筛、缺必填成员、时刻 / 方向读不出)⇒ 404 `tool_approval.not_pending`,运维日志一行 error(`defect` 首条缺陷句 · `defectCount`)。**停驻行的 `decided` 可回到 `parked`:续跑腿在被批准的动作开跑前失败(环境恢复失败 / 工具不在 / 配置被拒 / 决议输入被拒)时,引擎重开同一张门(一次已送达的拒不重开),其中工具不在与决议输入被拒要重新决;续跑被取消 ⇒ 服务端把重开的门收掉,读作 `void`;收掉之前的一瞬、以及收不掉(兜底交给 reaper)时仍可能读到 `parked`,以重读为准;重开瞬间两读之间可能读到 `void`,以重读为准。**全文见契约 §4a-bis-read |
|
|
1866
|
+
| `GET /v1/agents/roster` | 后台 agent **名册**(7.29 起,需 durable background-agent store,否则 501 `capability.background_agent_store_required`):调用方自己 scope 的 `a…` 行,新→旧。行只带 `handle`/`name`/`agentType`/`status`/`spawnedAt`/`updatedAt`/`settledAt`(content-free;子代产物正文走 `GET /v1/runs/<id>/subagents/<handle>/output`)。查询:`?limit=`(1..100,默认 20)、`?before=`(上一页的 `nextBefore` 游标)、`?status=`(running\|parked\|completed\|failed\|killed)、`?session=`(按会话树窄化)、`?scope=`(**仅** fleet 调用方:不带主体头的 service 凭据 / 只读 `TRACE_TOKEN`(只在没配 service 凭据的部署上够得着这一口)/ dev-open;带主体头的调用方 —— 含带 service 凭据的 —— 锁定自己)。回体 `{agents,total,nextBefore?,truncated?}`;`truncated:true` = 租户行数超过名册窗(500),`total` 此时是窗内数。非属主的行**不出现也不计数**(无存在性 oracle) |
|
|
1858
1867
|
| `GET /v1/capabilities/scenarios/<name>` | 单个场景的只读详情:工具面、提示词概览、**本部署现在跑不跑得动**(见下) |
|
|
1859
1868
|
| `GET /v1/capabilities/mcp[?scenario=<场景名>]` | **无需先跑 run 的逐台 MCP 状态**(现连、列工具、关掉;按调用方限速 + 30 秒复用)。答的是**某个场景**会挂哪些中心声明的 MCP 服务器:不带参数 = 这位主体 run 时的场景(**7.131.0+**:中心按主体指派的场景胜出,否则部署缺省场景;此前恒读部署缺省场景;指派悬空 ⇒ `503 state.scenario_assignment_unknown`,与提交任务时同一句;`GET /v1/sessions/:id/mcp` 与 `…/a2a` 两张会话面板同读这位主体 run 时的场景:a2a 面板悬空时同样整面 503,而 mcp 面板另有「这个会话上一次真挂上了什么」那一半(`lastLegMcp`),悬空时答 `200`、`lastLegMcp` 照答、`servers` 缺席、拒因在 `scenarioRefusal: { errorCode, error, detail }`(与状态面整面 503 的体是同一份成员,`detail` 是门拒的机读事实 `{ scenario, source }`;`servers` 缺席 = 场景这一半没答,`servers: []` = 该场景没配 MCP));`?scenario=<名>`(**7.130.0+**)= 点名场景,可见集与同场景任务 run 时是同一只解析、过同一只场景门 —— 不在册 `400 scenario_unknown`(附在册词表;空串 `?scenario=` 同此,与提交 `scenario: ""` 同答)、这位 principal 的场景白名单集外 `400 scenario_not_allowed`(附 allowlist),两者的体与提交任务时的拒逐字同形;重复给出参数 `400 request.field_invalid`(提交腿没有这一形)。场景门接缝 `scenarioGate` 是 `createHttpServer` 的必填件:自建 `ServiceDeps` 的嵌入方用包根导出的 `createScenarioGate` 铸它(`"peek"` 腿给读面),没递就是 tsc 红。老 server 忽略这个参数、回缺省场景那一份,按 `GET /v1/capabilities` 的 `version` 判。`REQUIRE_PRINCIPAL` 下无身份 `401`(先于场景门)。每一行(两条口同形)在 §G.7 的行形之外多两个可选键(**7.133.0+**):`toolNames`(这台会挂上的工具名,引擎命名空间形 `mcp__<server>__<tool>`,读数序)+ `toolNamesTruncated: true`(名单超过 200 只时只截表,`toolCount` 仍是真数;读数用 `toolCount`,别用 `toolNames.length`);该台读数里没有名单 ⇒ 两键缺席(不编 `[]`),没连上的台是在场的 `[]`;老 server 没有这两键,按缺席读。自带 `.mcp.json` 的探测口是 `POST /v1/capabilities/mcp/probe` |
|
|
1860
1869
|
| `POST /v1/admin/drain` `{"actionId":"…","worker":"…","deadlineAt":…}` | **排空口**(7.121.0 起体 = settings-schema `DrainRequest`;operator-only —— principal 须在 `OPERATOR_PRINCIPALS` 内,或单用户部署声明了 `OPERATOR_SINGLE_USER=true`;两条出路都没有〔名单为空且没声明〕⇒ 501)。体 `{ actionId, worker, deadlineAt, scope?, expectedGeneration?, instanceId?, term? }`:`actionId` 幂等键(人手形自取,如 `manual:<uuid>`)、`worker` 本引擎的 worker 名(`SEMA_WEB_ADMIN_WORKER`)、`deadlineAt` 到期自停的 epoch ms、`scope` 本引擎的租户 scope(`FLEET_SCOPE`;不带 = 缺省 scope `global`,与没设 `FLEET_SCOPE` 的引擎相符 —— 多租引擎要带它自己的)、`expectedGeneration` fleet 成员的代际(`FLEET_GENERATION`;非成员不带)、`instanceId` 本进程身份(`GET /health` 读;不带则不比)。身份逐字段比(`worker` / `expectedGeneration` 缺席对缺席),不符 ⇒ `409 conflict.fleet_identity`(什么都不变);首次 ⇒ `200 {actionId, since, deadlineAt, state:"draining"}` 并**进入排空**(拒新提交、本副本可抢占的在飞腿当拍让出、活的审批卡改路耐久停驻、在飞归零或到期限即停);再来 ⇒ `200 … state:"already_draining"` 回显第一次的 `since` / `deadlineAt`,不重置。体坏 ⇒ `400 request.field_invalid`;`deadlineAt` 离本引擎此刻超过 `FLEET_LEASE_OUTAGE_MAX_MS`(缺省 24 h,一台实例活着而不服务的最长时长;7.122.0 起)⇒ 同码 400(什么都不变;拒句点名字段与上界的值;只在这一发会进入排空时判 —— 已在排空或硬停已在进行时任何重发都回 `already_draining`),已过的期限照收(进入即到期);嵌入宿主(无收尾链)⇒ `501 capability.drain_state_required`。没有 worker 身份的引擎(没配 `SEMA_WEB_ADMIN_WORKER`)恒 409 —— 用 SIGTERM。经本口进的排空之后,平台随后发的第一发 SIGTERM 被吸收、不缩短期限(第二发判据见「排空期的第二发信号」)。7.120.x 的 `{"reason":"…"}` 声明形与 `/health.drainReason` 已退役。 |
|
|
@@ -1883,6 +1892,11 @@ curl -N http://<host>:8090/v1/tasks/stream -H 'content-type: application/json' \
|
|
|
1883
1892
|
> 没配服务凭据的部署上能连上端口的都是 operator;不带主体的 operator 没有身份可记,记「谁改的」的写面(`PUT /v1/admin/memory-optout[/:principal]`)要带主体(否则 401)。
|
|
1884
1893
|
> 单用户 + 名单空 + 没声明 + 有审批底座 ⇒ 起服 warn `operator_undeclared_single_user`(不拒启)。
|
|
1885
1894
|
> ⚠️ **多租户形拒启**:`DURABLE_APPROVAL=true` + `REQUIRE_PRINCIPAL=true` 而 `OPERATOR_PRINCIPALS` 空 ⇒ 进程启动失败并点名修法(空名单下没人能当这台多租 worker 审批队列的 operator)。设名单,或确属单租户则不设 `REQUIRE_PRINCIPAL`。
|
|
1895
|
+
> 🔒 **停驻任务续跑上下文里的凭据是密封的(7.134.0 起)**:任务停驻时存下的提交体里,请求自带的凭据(`mcpServers[].transport.headers` / `.env` 的值、`a2aPeers[].headers` 的值、`settings.env` 的值、
|
|
1896
|
+
> `settings.webSearch.apiKey`、http hook 条目的静态头 `settings.hooks.<事件>[].hooks[].headers` 的值)只以封签落盘(对着这一域自己的保管钥匙 `<数据根>/sealed-key/checkpoint-ctx-keys/` 最新那把公钥封、
|
|
1897
|
+
> 绑着这一行的会话;与 sealed 模型钥匙那对钥匙不共用),续跑时开封;开不开 ⇒ `409 conflict.checkpoint_ctx_unsealable`(`field` + `reason`)。
|
|
1898
|
+
> 因此耐久审批在场的部署(`DURABLE_APPROVAL`;单用户 + 有耐久店时缺省开)要能建保管目录(建不了 ⇒ 起服拒;不要停驻就显式 `DURABLE_APPROVAL=false`),`<数据根>/sealed-key/` 要跨重启持久
|
|
1899
|
+
> (SQL 后端也一样:钥匙不进库;不持久 ⇒ 每次重启换钥匙,重启前停驻的任务续跑答 `reason:"key_not_held"`),多副本要让每个副本的 `<数据根>/sealed-key/` 是同一份(§9.10 ⑦)。升级与回滚序见 §9.10 ⑦ 与 MIGRATION 7.134.0 第 1 条。
|
|
1886
1900
|
>
|
|
1887
1901
|
> **部署治理三声明(7.21.0 起)**——三根都是**配置期**旋钮,「谁能改它们」≡「谁能改
|
|
1888
1902
|
> 部署配置」,不引入新鉴权面;三根**都不设 = 现行为逐字不变**,坏值一律**拒启**(安全轴上的旋钮不静默失效)。
|
|
@@ -2198,7 +2212,7 @@ curl -N http://<host>:8090/v1/tasks/stream -H 'content-type: application/json' \
|
|
|
2198
2212
|
## 8. 三类调用方怎么接
|
|
2199
2213
|
|
|
2200
2214
|
**A. OA 系统(服务到服务后端)**
|
|
2201
|
-
- OA 后端持有 `SERVICE_AUTH_TOKEN`,每个请求按当前终端用户设 `x-agent-principal: user:<id
|
|
2215
|
+
- OA 后端持有 `SERVICE_AUTH_TOKEN`,每个请求按当前终端用户设 `x-agent-principal: user:<id>`。带了主体头的请求只看得到、动得到那位用户的(7.134.0 起 fleet 面同读);后台自己的全舰队运维视图**不带主体头**(或用只读 `TRACE_TOKEN`)。
|
|
2202
2216
|
- 短问答 → `/v1/tasks`(同步);长任务/要进度条 → `/v1/runs` + `/events`(异步)。
|
|
2203
2217
|
- 续聊:把上次 `sessionId` 存在 OA 会话里,下次带回。
|
|
2204
2218
|
|
|
@@ -2763,12 +2777,18 @@ WebSearch 工具的 `tool_result` 错误文本,回到模型的对话里,**不是
|
|
|
2763
2777
|
- 本版只承载、不发送断言:声明了 `assertionHeader` 的 server 照常挂载,但不带断言头;每台这样的 server 一行 info `mcp_assertion_header_not_sent`(`server` / `note`)。探测口 `POST /v1/capabilities/mcp/probe` 对声明了该键的条目整条拒 `400 request.field_invalid`(探测不拨一份少了断言头的配置)。
|
|
2764
2778
|
**发不发有一位可读**:`GET /v1/capabilities` 的 `mcpAssertionHeaderSent`(布尔,今天恒 `false`)—— 它与剥键、那行 info、探测口的那一拒读同一只开关,**互斥**:位 `false` ⇒ 三者都在(别指望断言头会发出去);位 `true` ⇒ 三者都不再出现、声明的头名随规格交给引擎。按位分支,不要匹配 info 文字;键缺席 = 老 server,按「不发送」读。
|
|
2765
2779
|
|
|
2766
|
-
### 9.5e 请求体 `mcpServers` 的列表与条目键:写坏,整份新提交 400(7.131.0 起;列表级与词表外的键 7.132.0 起;`startupTimeoutMs`
|
|
2780
|
+
### 9.5e 请求体 `mcpServers` 的列表与条目键:写坏,整份新提交 400(7.131.0 起;列表级与词表外的键 7.132.0 起;`startupTimeoutMs` 与 `toolFaces` 的引擎判官 7.134.0 起)
|
|
2767
2781
|
|
|
2768
|
-
条目的受理键 = `name` · `transport` · `allowTools` · `toolAxes` · `toolFaces` · `contentOrigin` · `elicitation` · `source` · `startupTimeoutMs`(7.132.0 起)(`transport` 的子键:`kind` · `command` · `args` · `env`(stdio),`kind` · `url` · `headers` · `principalHeader` · `assertionHeader`(http))
|
|
2769
|
-
- **新提交**(`POST /v1/tasks` · `/v1/tasks/stream` · `/v1/runs`):键在场却判不出形 ⇒ `400 request.field_invalid`,拒句形如 `mcpServers[2].allowTools (server "crm") is not a usable declaration — must be an array of strings (omit the key to leave the server's tools unrestricted) — got a string of 4 characters. …`:点名 `mcpServers[i].<键路径>`、server 名与该键判官的原因句,**整份请求不收** —— 不挂一半、不丢声明、不默认。原因句不回显声明的值,有几处例外,回显只有一只口径(剥控制与双向字符、截到 64 字、超出加 `…`):server 名(含写坏的 `name` 值,另报送来的原长度)、`contentOrigin`
|
|
2782
|
+
条目的受理键 = `name` · `transport` · `allowTools` · `toolAxes` · `toolFaces` · `contentOrigin` · `elicitation` · `source` · `startupTimeoutMs`(7.132.0 起)(`transport` 的子键:`kind` · `command` · `args` · `env`(stdio),`kind` · `url` · `headers` · `principalHeader` · `assertionHeader`(http))。**新提交上每个键只有一只判官,判官在受理口,写坏的答案只有一种**(下面第一条:400)。`startupTimeoutMs`(这一只 server 自己的启动界,毫秒整数,范围与引擎 env 旋钮 `MCP_TIMEOUT` 同一段)7.134.0 起同样在受理口判 —— 判官是引擎包根导出的 `mcpStartupTimeoutMs`(启动界的唯一读者):数字串 / 小数 / 越界 / `null` ⇒ `400 request.field_invalid`,原因句是引擎的那一句(7.132.0–7.133.x:受理口不判、请求照常 200,引擎拨号时只拒这一只 server);耐久 resume 重放存量请求不判(原样交引擎,坏值在拨号时只拒这一只 server —— `config.mcp_startup_timeout_invalid`,兄弟照挂)。
|
|
2783
|
+
- **新提交**(`POST /v1/tasks` · `/v1/tasks/stream` · `/v1/runs`):键在场却判不出形 ⇒ `400 request.field_invalid`,拒句形如 `mcpServers[2].allowTools (server "crm") is not a usable declaration — must be an array of strings (omit the key to leave the server's tools unrestricted) — got a string of 4 characters. …`:点名 `mcpServers[i].<键路径>`、server 名与该键判官的原因句,**整份请求不收** —— 不挂一半、不丢声明、不默认。原因句不回显声明的值,有几处例外,回显只有一只口径(剥控制与双向字符、截到 64 字、超出加 `…`):server 名(含写坏的 `name` 值,另报送来的原长度)、`contentOrigin` 词表外的值;`toolAxes` / `toolFaces` 的原因句是引擎写的(7.134.0 起 `toolFaces` 的判官也是引擎包根导出的 `readToolFaces`):点名工具名(引擎截 80 字)与成员,`toolFaces` 还会点名闭词表拒的那个词,整句详情由引擎截到 240 字、本服务再剥控制与双向字符 —— 不回显显示文本、不回显合规的列表;`transport.url` 的值从不回显:它常带凭据。可选键**缺席**永远合法(真「没声明」),必填的 `name` / `transport` 缺席同样 400;在场的 `null` 不是缺席。与单用户 / 多租户无关。列表本身同一条律(7.132.0 起,先于条目判):`mcpServers` 在场却不是数组、超过 32 条(整份拒,不挂前 32 条)、某一条不是对象 ⇒ `400 request.field_invalid`,拒句点名 `mcpServers` 或 `mcpServers[i]` 与形的种类;条目层或 `transport` 层出现词表外的键 ⇒ `400 request.body_shape`,拒体带 `unknownKeys`(全路径、至多 20 条)。`mcpServers` 缺席或为 `null` = 没声明,照常受理。
|
|
2770
2784
|
- **耐久 resume 重放存量请求**:不拒(拒会把升级前 park 的任务永久卡死)。写坏的 `name` · `transport` 的 `kind`/`command`/`url` · `allowTools` · `toolAxes` · `toolFaces` ⇒ 这一条 server 整条不进,运维 warn `mcp_injection_dropped`(`reason: malformed_entry`,`field` 点名)并投用户面通告 `mcp.injection_dropped`(其中 `toolAxes` 在一次性迁移 M19 的窗开着时先按升级前读法重读一次,过了就挂,见上文 `toolAxes` 一节);写坏的 `contentOrigin` · `elicitation` · `source` · `transport` 的 `args`/`principalHeader` 与词表外的键 ⇒ 丢掉那一个键、server 照挂;`transport` 的 `env`/`headers`(名 → 串记录)对 JSON 存体按 7.131.x 的读法交:有值不是串的成员 ⇒ 整键不交,只有 `__proto__` 名的成员坏 ⇒ 剔掉它、其余照交;server 照挂,一行运维 warn `mcp_transport_members_dropped` 点名(见 9.5c-bis 同段);条目不是对象 ⇒ 这一条不进(`malformed_entry`);超过 32 条 ⇒ 第 33 条起只记 `over_cap`;整只不是数组 ⇒ 整只忽略 + 运维 warn `mcp_injection_ignored`。
|
|
2771
2785
|
- `POST /v1/capabilities/mcp/probe` 问的是新提交那**同一只判官**:上面每一形同码同句(含 `unknownKeys`),拒之前零拨号;判官受理的申报,探测口拨的就是一条任务提交会交给引擎的那一份(判官的合法归一照收)。它多出的两形:本口的体就是这份列表,`mcpServers` 缺席或为 `null` ⇒ `400 request.body_shape`(缺必需字段);声明了 `transport.assertionHeader` 的条目 ⇒ `400 request.field_invalid`(见 9.5d:本版承载、不发送,新提交照挂,探测不拨一份少了断言头的配置 —— 同一只谓词,后果按门分)。7.131.x 上本口另有一只判法(申报原文与读器产物逐字比),列表不是数组 / 条目不是对象答 `request.body_shape`。
|
|
2786
|
+
- **`toolFaces` 的判官是引擎的 `readToolFaces`(7.134.0 起;此前本服务自带一只只判 JSON 形的读器)**:它在门口就判声明本身能说的一切 —— 成员与类型、五张闭词表、上界、跨成员律 ——
|
|
2787
|
+
所以下面几形 7.133.x 受理、7.134.0 起 400(重放腿整条 server 不进、中心 / `config.d` 腿跳过该 server 并点名 warn):空工具名;保留审批卡(`renderHints.approvalCard: "question"` / `"plan"`);
|
|
2788
|
+
🔴 **`pathTarget.access` 是闭集 `read` / `create` / `edit`** —— 写成 `"write"`(或任何别的词)整条拒;**`pathTarget.base: "root"` 要求 `access: "read"`**(写面落根基底整条拒);
|
|
2789
|
+
`patternParam` 只在 `absent: "base"` 旁有意义,单独写是一份读不到的声明(inert)、同样整条拒;未知成员(笔误)、安全轴写成面成员(`effect` 属于 `toolAxes`)、非布尔旗、错型成员。
|
|
2790
|
+
部署腿(中心 / `config.d`)的坏声明若绕过本服务直接到引擎,引擎在准备门**整任务**拒(`config.tool_face_invalid`)—— 一处笔误整任务起不来,写之前对一下这三条。
|
|
2791
|
+
要看物化 schema 才判得了的那一半(`param` 是不是工具的顶层参数、审批卡要的键在不在)与 `resultCards` 落在别的命名空间,仍由引擎在列举处**按工具**判(那一只工具不挂,通告 `config.tool_face_invalid`,兄弟照挂)。
|
|
2772
2792
|
- 7.130.x 及更早:`allowTools` / `toolFaces` / `name` / `transport` 写坏 ⇒ 200 + 这条 server 不挂;`elicitation` / `source` / `transport` 的可选子键写坏 ⇒ 200 + 悄悄丢键。7.131.x 及更早:列表整只不是数组 ⇒ 200、整只忽略;条目不是对象 ⇒ 200 + 点名丢弃;超过 32 条 ⇒ 200、挂前 32 条;词表外的键 ⇒ 悄悄不读。升级后这些请求变 400:改对那个键(或删掉它)、把列表改成数组、删掉第 33 条起的条目。
|
|
2773
2793
|
|
|
2774
2794
|
### 9.5f 中心提示层:一种 wire 形、按场景挂段、品牌行与「无持久记忆」披露(7.132.0 起)
|
|
@@ -3150,7 +3170,9 @@ const ciphertext = Buffer.from(sodium.crypto_box_seal(sodium.from_string(apiKey)
|
|
|
3150
3170
|
|
|
3151
3171
|
**⑥ 保管目录与轮换。** 保管目录 = `<数据根>/sealed-key/`(数据根 = `LOCAL_DATA_ROOT` → `CONFIG_LOCAL_DIR` → `AGENT_DATA_DIR` → `~/.ai-agent`),
|
|
3152
3172
|
每把钥匙一个文件 `<publicKeyId>.json`(文件 `0600`,目录 `0700`)。**私钥只在这里**:丢了,对着它封的密文全部变成 `stale_ciphertext`,
|
|
3153
|
-
只能重封 —— 备份时按私钥对待。
|
|
3173
|
+
只能重封 —— 备份时按私钥对待。7.134.0 起它下面还有一个子目录 `checkpoint-ctx-keys/`:停驻任务续跑上下文里请求凭据的封签钥匙(一个域一对钥匙,与模型钥匙不共用;
|
|
3174
|
+
同样的文件形、同样的「空目录生成一把」;不向中心登记、不出现在 caps 上)。它丢了,封在停驻行里的凭据就开不开(续跑答 `409 conflict.checkpoint_ctx_unsealable` `reason:"key_not_held"`)——
|
|
3175
|
+
所以整个 `sealed-key/` 目录要**跨重启持久**(SQL 后端也一样:钥匙不进库)。下面的轮换步骤对这个子目录同样适用:旧私钥文件留到用它封的停驻行都已结束为止。
|
|
3154
3176
|
|
|
3155
3177
|
- 起服时读目录里的全部钥匙文件;一个可用的都没有 ⇒ 生成一把新的。读不了的文件(坏 JSON / 不认识的格式)跳过、**不删**,warn
|
|
3156
3178
|
`sealed_key_file_unreadable`。文件只在起服时读:运行中放进来的钥匙文件要重启才认。
|
|
@@ -3166,9 +3188,17 @@ const ciphertext = Buffer.from(sodium.crypto_box_seal(sodium.from_string(apiKey)
|
|
|
3166
3188
|
或放好钥匙文件)再起其余副本 —— 空目录上并发首启会各自生成一把,各副本只持有自己那一把,直到下次重启。目录要对每个副本**可写**(每次起服
|
|
3167
3189
|
会把它设成 `0700`;只读挂载 ⇒ 建店失败 ⇒ `no_key_store`)。⚠️ 只共享 `sealed-key/` 这一个子目录:`DB_BACKEND=local` 的整个数据根禁止跨容器 /
|
|
3168
3190
|
跨主机共享(`docs/DEPLOY-PREREQS.md`)。
|
|
3191
|
+
照随包 `deploy/sema-up` 图表部署时这一步图表替你做了:`sealedKey.persistence` 块(缺省开)把整个 `sealed-key/` 目录挂成一只 PVC,访问模式缺省单副本 `ReadWriteOnce`、多副本 `ReadWriteMany`
|
|
3192
|
+
(要存储类支持 RWX;空卷首装后 `rollout restart` 一次,让并发首启各生成的钥匙互相都持有);显式关掉它(`sealedKey.persistence.enabled: false`)图表会同时给 worker 设 `DURABLE_APPROVAL=false`,并拒绝再配 `server.durableApproval: true`。
|
|
3169
3193
|
- **走中心形**:接 web-admin,执行侧公钥登记在中心(起服时带管理令牌自动上报,否则把公钥打印出来供手工登记),web 面对着登记的公钥封、中心存密文。
|
|
3170
3194
|
调用方不必逐台去问 caps;但同一个 worker 的多个副本各有各的保管店这件事中心形不改变 —— 要它们都开得了同一份密文,仍要共享保管目录。
|
|
3171
3195
|
|
|
3196
|
+
**停驻任务的凭据封签(7.134.0 起)同一个目录**:共享的是整个 `<数据根>/sealed-key/`(含子目录 `checkpoint-ctx-keys/` —— 停驻行封签那一域自己的钥匙)。不共享 ⇒ A 副本停驻、带请求凭据的任务
|
|
3197
|
+
在 B 副本上续跑答 `409 conflict.checkpoint_ctx_unsealable` `reason:"key_not_held"`;单副本也要让这个目录跨重启持久。**7.133.x 与 7.134.0 不能混跑**:先停掉全部 7.133.x 副本,再起第一个 7.134.0 副本
|
|
3198
|
+
(一次性迁移 M28 在它起服时把升级前停驻行里的明文封好);混跑时 7.133.x 副本续跑一条已封签的行会把凭据位当空值、任务无凭据照跑、不报错。降级到 7.133.x 之前先把带凭据的停驻行结掉(决定或取消)。
|
|
3199
|
+
多副本升级时先起一个 7.134.0 副本、等它起好再起其余副本(耐久审批开着时它起好即打出 `checkpoint_ctx_credentials_sealed` 一行):停驻封签的钥匙在新子目录 `checkpoint-ctx-keys/` 里,
|
|
3200
|
+
升级后第一个起服的副本生成它(`sealed-key/` 早已共享好也一样);同时起多个副本会各建一把、互相答 `key_not_held`,直到各自重启一次(钥匙只在起服时读目录里的全部文件)。
|
|
3201
|
+
|
|
3172
3202
|
### 9.11 会话规则店:抹除封键与显式恢复(7.106.0 起)
|
|
3173
3203
|
|
|
3174
3204
|
适用于 `DB_BACKEND=mysql|pg`(SQL 会话规则店;`DB_BACKEND=local` 用的是引擎自带的文件店,不在本节)。
|
|
@@ -3245,3 +3275,91 @@ UPDATE session_policy_rev_mark SET rev = 0, gen = gen + 1 WHERE policy_key = '<w
|
|
|
3245
3275
|
而且本版写下的规则条目在旧版上不生效(见 `MIGRATION.md` 7.129.0 节)。`DB_BACKEND=local` 的规则在引擎自带的文件店里,不改列名:带命令桶的规则文件读回 `corrupt`,出路在引擎的拒句里。
|
|
3246
3276
|
文件店也没有上面那一步判读,也不需要:引擎按行自带的文法读,7.128.x 写下的无戳文件里 `toolAllow` 的 `Bash(rm:*)` 这类条目照旧什么都不放行(不放宽),升级后按引擎通告重写。停驻的 run 里引擎写下的
|
|
3247
3277
|
祖先规则快照同理(本服务不改写),升级前决掉或取消待决的停驻 run。
|
|
3278
|
+
|
|
3279
|
+
### 9.13 自建引擎镜像:两条路径(源码 tag 或 npm 包)
|
|
3280
|
+
|
|
3281
|
+
自 7.128.0 起没有新的容器镜像发布(§0 开头的说明):要把引擎升到 ≥ 7.128.0,只能自己构建镜像。两条路径产出同一个 HTTP 服务 —— 入口都是 `main`(路径 1 跑源码 `src/main.ts`,
|
|
3282
|
+
路径 2 跑包里编译好的 `dist/main.js`,装配次序同一张表)—— 按手里有什么访问权选:
|
|
3283
|
+
|
|
3284
|
+
- **路径 1 · 源码 tag**(有源码仓访问权):在 tag `v<版本>` 的检出上用仓根 `Dockerfile` 构建。基镜像 `oven/bun:1-slim`,Bun 直跑 TypeScript,没有构建步骤;入口 `bun run src/main.ts`(工作目录 `/app`)。
|
|
3285
|
+
- **路径 2 · npm 包**(只有 npm 访问权;包是公开的,匿名可装):`npm install @sema-agent/server@<版本>`。基镜像 `node:24-slim`(包的 `engines` 是 `^20.19.0 || >=22.12.0`);
|
|
3286
|
+
入口 `node node_modules/@sema-agent/server/dist/main.js`。包的 `bin` 里没有 HTTP 服务(`run-local` 是单机一次性 runner、`sema-up` 是部署引导脚本),要起服务必须直接跑这个 `dist/main.js`。
|
|
3287
|
+
|
|
3288
|
+
**构建(二选一)**
|
|
3289
|
+
|
|
3290
|
+
路径 1:
|
|
3291
|
+
|
|
3292
|
+
```bash
|
|
3293
|
+
# 1. 取 tag 的检出
|
|
3294
|
+
git clone --depth 1 --branch v<版本> <你有权访问的源码仓地址> sema-server && cd sema-server
|
|
3295
|
+
# 2. 构建 —— 到不了 Docker Hub / npmjs 时用两只 build-arg 换源(下面是国内写法)
|
|
3296
|
+
docker build \
|
|
3297
|
+
--build-arg BUN_IMAGE=docker.m.daocloud.io/oven/bun:1-slim \
|
|
3298
|
+
--build-arg NPM_DEFAULT_REGISTRY=https://registry.npmmirror.com \
|
|
3299
|
+
-t sema-server:<版本> .
|
|
3300
|
+
```
|
|
3301
|
+
|
|
3302
|
+
两只 build-arg 的缺省:`BUN_IMAGE=oven/bun:1-slim`、`NPM_DEFAULT_REGISTRY=https://registry.npmmirror.com`;海外构建把后者换成 `https://registry.npmjs.org`。
|
|
3303
|
+
还有第三只 `PKG_SOURCE`:缺省 `cn` 把最终阶段的 apt 源改写到 aliyun 来装 `git`(leader 把合并结果推回远端要用),官方源直连传 `--build-arg PKG_SOURCE=global`。
|
|
3304
|
+
|
|
3305
|
+
路径 2:先写 `Dockerfile`(构建上下文里不需要别的文件)——
|
|
3306
|
+
|
|
3307
|
+
```dockerfile
|
|
3308
|
+
ARG NODE_IMAGE=node:24-slim
|
|
3309
|
+
FROM ${NODE_IMAGE}
|
|
3310
|
+
ARG SEMA_SERVER_VERSION
|
|
3311
|
+
ARG NPM_DEFAULT_REGISTRY=https://registry.npmmirror.com
|
|
3312
|
+
WORKDIR /app
|
|
3313
|
+
# leader 把合并结果推回远端要用 git;slim 基镜像不带。不用 leader 车道可删这一行。
|
|
3314
|
+
RUN apt-get update && apt-get install -y --no-install-recommends git ca-certificates && rm -rf /var/lib/apt/lists/*
|
|
3315
|
+
RUN test -n "$SEMA_SERVER_VERSION" \
|
|
3316
|
+
&& printf '{"name":"sema-engine-image","private":true}\n' > package.json \
|
|
3317
|
+
&& npm install --omit=dev --save-exact --no-audit --no-fund --registry "$NPM_DEFAULT_REGISTRY" "@sema-agent/server@${SEMA_SERVER_VERSION}"
|
|
3318
|
+
EXPOSE 8090
|
|
3319
|
+
HEALTHCHECK --interval=30s --timeout=3s CMD node -e "fetch('http://127.0.0.1:'+(process.env.PORT||8090)+'/readyz').then(r=>process.exit(r.ok?0:1)).catch(()=>process.exit(1))"
|
|
3320
|
+
CMD ["node", "node_modules/@sema-agent/server/dist/main.js"]
|
|
3321
|
+
```
|
|
3322
|
+
|
|
3323
|
+
```bash
|
|
3324
|
+
# 1–2. 构建 —— 换源同路径 1:`--build-arg NODE_IMAGE=docker.m.daocloud.io/library/node:24-slim`;海外或镜像源没有该版本时 `--build-arg NPM_DEFAULT_REGISTRY=https://registry.npmjs.org`
|
|
3325
|
+
docker build --build-arg SEMA_SERVER_VERSION=<版本> -t sema-server:<版本> .
|
|
3326
|
+
```
|
|
3327
|
+
|
|
3328
|
+
`HEALTHCHECK` 指 `/readyz`:它的 unhealthy 只表示「别往我这儿路由」,不是「重启我」(§0 探针一段)。
|
|
3329
|
+
包本身不带 lockfile,传递依赖按构建那天的 semver 解析;要同一版本两次构建得到同一棵依赖树:在本机用同样的 `npm install` 命令生成 `package.json` 与 `package-lock.json` 一并提交,
|
|
3330
|
+
`Dockerfile` 里改 `COPY package.json package-lock.json ./` 再 `RUN npm ci --omit=dev`。
|
|
3331
|
+
|
|
3332
|
+
**之后(两条路径相同)**
|
|
3333
|
+
|
|
3334
|
+
```bash
|
|
3335
|
+
# 3. 让节点拿到镜像 —— 单机 docker 不用这一步。k3s:在节点上导入(每个会调度它的节点各导一次)
|
|
3336
|
+
docker save sema-server:<版本> | k3s ctr -n k8s.io images import -
|
|
3337
|
+
# 或推到你自己的 registry:docker tag sema-server:<版本> <你的 registry>/sema-server:<版本> && docker push <你的 registry>/sema-server:<版本>
|
|
3338
|
+
# 4. 起服 —— 数据根挂持久卷(AGENT_DATA_DIR 指到卷上)。下面是不带模型目录的最小形:缺 MODEL_ID 或 MODEL_GATEWAY_BASEURL 起服即拒,拒句点名缺的键
|
|
3339
|
+
docker run -d --name sema-server -p 8090:8090 \
|
|
3340
|
+
-e DB_BACKEND=local -e AGENT_DATA_DIR=/data -v sema-data:/data \
|
|
3341
|
+
-e MODEL_GATEWAY_BASEURL=https://<your-gateway>/v1 -e MODEL_ID=<your-model-name> -e MODEL_API_KEY=… \
|
|
3342
|
+
-e SERVICE_AUTH_TOKEN=<随机长串> \
|
|
3343
|
+
sema-server:<版本>
|
|
3344
|
+
# SERVICE_AUTH_TOKEN 不是起服必需,但没有它(且没开 ALLOW_UNAUTHED_WRITES)提交任务一律 503 auth.service_token_required —— 第 5 步只验就绪,交任务前先配它。另一形是显式 ALLOW_UNAUTHED_WRITES=1:那时没有 service 令牌的 worker 只绑 127.0.0.1(自收窄),容器里还要 BIND_HOST=0.0.0.0 才能经 -p 访问,并把第 4 步的映射改成 `-p 127.0.0.1:8090:8090`(否则无鉴权的提交口暴露给整个局域网)—— 两形的取舍见 §0「监听绑址」段。
|
|
3345
|
+
# 5. 验收 —— 就绪 + 版本
|
|
3346
|
+
curl -fsS http://127.0.0.1:8090/readyz # {"ready":true}
|
|
3347
|
+
curl -s http://127.0.0.1:8090/health # "version" 应等于你构建的 <版本>
|
|
3348
|
+
```
|
|
3349
|
+
|
|
3350
|
+
- **随包 `deploy/sema-up`**:图表 `server.image` 必填,填你自建的引用;节点本地导入的(第 3 步 k3s)`--set server.image=sema-server:<版本> --set server.pullPolicy=Never`,推了 registry 的填完整引用并保持缺省 `IfNotPresent`。图表不覆写镜像的启动命令,两条路径的镜像都按它自己的 `CMD` 起。`sema-up` 脚本自己的入口:`full-cluster --kube=machines`(`-f machines.yaml`,≥ 3 台)在环境里设 `SEMA_SERVER_IMAGE_TAR=<docker save 出的 tar 或 .tar.gz>`,脚本逐台灌入并 `k3s ctr images import`,部署的镜像名是固定的 `docker.io/library/sema-server:local`、拉取策略 `Never` —— **所以 tar 里的镜像必须带 `sema-server:local` 这个标签**:`docker tag sema-server:<版本> sema-server:local && docker save sema-server:local -o sema-server.tar`(只存 `sema-server:<版本>` 的 tar 导入成功,但部署找不到 `:local`);或设 `server_image:` / `SEMA_SERVER_IMAGE=<你的 registry 引用>`,二选一;含 server 的 compose 档(`full-single` / `local-plus`)与 `full-cluster --kube=existing|k3s` 要的是 `SEMA_SERVER_IMAGE`。⚠️ 缺镜像时 `sema-up` 打印的提示与 `machines.example.yaml` 的注释仍写着 ghcr 引用 —— 那是曾发布的镜像,7.128.0 之后没有新版;照本节自建,把你的引用填进去。
|
|
3351
|
+
- **数据根**:第 4 步 `DB_BACKEND=local` 是单容器形(数据目录禁跨容器共享)。生产用 `DB_BACKEND=mysql|pg` 与 §0「生产」那组坐标替换它 —— **数据根卷仍要挂**:`<数据根>/sealed-key/` 在 SQL 后端下也不进库,跨重启要持久。不设 `AGENT_DATA_DIR` 时数据根落在容器用户家目录下的 `.ai-agent`,容器一删就没。
|
|
3352
|
+
- **升级 = 换一只更高版本的镜像**:升级序、先升库再升副本、排空与回滚前置,按你跨过的每一版读 `MIGRATION.md` 与源码仓文档 DEPLOY-PREREQS 的同版节(不随包);一次性迁移与新表在起服时自跑,不用手跑。
|
|
3353
|
+
|
|
3354
|
+
**与曾发布镜像的差异**
|
|
3355
|
+
|
|
3356
|
+
- **标签 / 引用**:曾发布的镜像是 `ghcr.io/sema-agent/sema-server:v<版本>`(另有 `:<提交>` 与 `:latest`),停在 7.128.0 之前;自建的引用你自己定,编排里写旧引用的地方(compose、图表 `server.image`、部署清单)要同步换成你的。
|
|
3357
|
+
- **入口**:路径 1 与曾发布镜像同(同一份 `Dockerfile`:`bun run src/main.ts`、端口 8090、`/readyz` 的 `HEALTHCHECK`);路径 2 跑 Node,入口与文件布局不同,编排里若覆写了启动命令成 `bun run src/main.ts`,要改成 node 入口。
|
|
3358
|
+
- **用户**:两条路径的 `Dockerfile` 都没有 `USER` 行,两只基镜像(`oven/bun:1-slim`、`node:24-slim`)的镜像配置里也没设 `User`,容器内以 root 运行(与曾发布镜像相同)。要降权:自己加 `USER`,并让数据根卷对那个 uid 可写。
|
|
3359
|
+
|
|
3360
|
+
**arm64 与可选原生件**
|
|
3361
|
+
|
|
3362
|
+
- 两只基镜像的清单都含 linux/amd64 与 linux/arm64(`oven/bun:1-slim`、`node:24-slim`);构建机是 arm64 就得 arm64 镜像,跨架构构建用 `docker buildx build --platform`。
|
|
3363
|
+
- `sharp`(图片降采样)是可选依赖,按平台装预编译平台包:linux/arm64 glibc 上是 `@img/sharp-linux-arm64`(两条路径的依赖解析在 arm64 目标上都解出了它);Alpine 一类 musl 基镜像要的是 `linuxmusl` 平台包,本节的 Debian 系 `slim` 不涉及。
|
|
3364
|
+
它不是起服条件:引擎用到图片降采样时才动态加载它,装不上就没有降采样。
|
|
3365
|
+
- `slim` 基镜像没有编译器:`ssh2` / `cpu-features` 的可选原生件编译失败不让安装失败,起服也不依赖它们(没有这两件原生件的依赖树能起服到 `/readyz` 200)。
|
package/deploy/sema-up/README.md
CHANGED
|
@@ -154,6 +154,22 @@ data-center:2c/2G/20G)以下 BLOCK——确实无法稳定部署,提前警告不
|
|
|
154
154
|
- **MinIO**:钉死的 tag(`minio/minio:RELEASE.…`,chart / cluster 静态清单 / compose 三份同值;k8s `imagePullPolicy: IfNotPresent`,不再 `:latest` + `Always`)。换源 = 换整条引用:
|
|
155
155
|
compose 与 kube-up 读环境变量 `MINIO_IMAGE`(kube-up 给了才递给 chart),chart 直接 `--set minio.image=<源>/minio/minio:<tag>`(预灌镜像的节点 `--set minio.pullPolicy=Never`)。
|
|
156
156
|
|
|
157
|
+
## 停驻钥匙的持久卷(helm 图表的 `sealedKey` 块)
|
|
158
|
+
|
|
159
|
+
server 的 `<数据根>/sealed-key/` 存着停驻任务续跑上下文里请求凭据的封签钥匙(子目录 `checkpoint-ctx-keys/`)和模型钥匙 —— 私钥只在这个目录里、不进库。
|
|
160
|
+
图表(`kube-up.sh` 的 `--kube=existing|k3s`、`cluster-up.sh` 的 `STACK_ENGINE=helm`)缺省把整个目录挂成一只 PVC(`sealedKey.persistence.enabled: true`),
|
|
161
|
+
容器内路径 `/root/.ai-agent/sealed-key`,主 worker 与启用的 `serverV4pro` 共用:重启与换副本都不丢钥匙,停驻的任务照常续跑。
|
|
162
|
+
|
|
163
|
+
- **访问模式缺省随共享这只卷的 pod 数**:`server.replicas`(加上启用的 `serverV4pro.replicas`)为 1 ⇒ `ReadWriteOnce`,多于 1 ⇒ `ReadWriteMany`,**要集群的存储类支持 RWX**
|
|
164
|
+
(Longhorn 走 NFS 共享管理器、每个节点要装 NFS 客户端;`local-path` 与多数云盘类不支持 ⇒ PVC 一直 Pending、server Pod 起不来)。点名存储类:`--set sealedKey.persistence.storageClass=<支持 RWX 的类>`。
|
|
165
|
+
单节点多副本(pod 都在同一个节点)用 `--set sealedKey.persistence.accessMode=ReadWriteOnce` 显式盖(随包的 `values-full-single.yaml` 就这么写)。
|
|
166
|
+
有效访问模式是 `ReadWriteOnce` 时 Deployment 用 `Recreate`(RWO 卷同时只能被一个节点挂,滚动更新的新 Pod 落在别的节点就永远等不到卷;代价是升级窗口内短暂没有副本)。
|
|
167
|
+
- **没有 RWX 的集群**:`--set server.replicas=1`,或单节点多副本盖 RWO(上一条),或关持久 `--set sealedKey.persistence.enabled=false` —— 关掉时图表**显式**给每只 worker 设 `DURABLE_APPROVAL=false`
|
|
168
|
+
(耐久审批整条关,不产生续不了的停驻行;`helm install` 打印的安装说明里也有这一句),且同时配 `server.durableApproval=true` 渲染即拒。
|
|
169
|
+
- **首装是空卷**:多个 pod 并发首启会各自生成一把钥匙、各持自己那一把;装好后 `kubectl -n <ns> rollout restart deploy/sema-server`(启用 `serverV4pro` 时再加 `deploy/sema-server-v4pro`)一次,之后每个 pod 读到全部钥匙。
|
|
170
|
+
- PVC 的 `accessModes` 建好后不可改:副本数跨过 1 的边界时 `helm upgrade` 会撞「spec is immutable」;卷里是私钥,换卷前先结掉停驻行或备份钥匙文件。
|
|
171
|
+
PVC 带 `helm.sh/resource-policy: keep`:`helm uninstall` 与把 `enabled` 改成 `false` 的 upgrade 都不删它(确认不要了再 `kubectl -n <ns> delete pvc sema-server-sealed-key`)。
|
|
172
|
+
|
|
157
173
|
## 凭据纪律
|
|
158
174
|
|
|
159
175
|
全部凭据生成一次、落 `~/.sema-up/<project>/.env`(0600),重跑**绝不轮换**;
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
{{- $persist := eq (include "sema.sealedKey.enabled" .) "true" }}
|
|
2
|
+
{{- if $persist }}
|
|
3
|
+
停驻封签钥匙:server 的 <数据根>/sealed-key/ 在持久卷 {{ include "sema.sealedKey.claimName" . }}(挂在容器内 {{ include "sema.sealedKey.mountPath" . }},{{ include "sema.sealedKey.accessMode" . }}),重启与换副本都不丢钥匙,停驻的任务照常续跑。PVC 带 helm.sh/resource-policy: keep(helm uninstall 与把 sealedKey.persistence.enabled 改成 false 的 upgrade 都不删它)。
|
|
4
|
+
{{- if eq (include "sema.sealedKey.accessMode" .) "ReadWriteOnce" }}
|
|
5
|
+
有效访问模式是 ReadWriteOnce,server / serverV4pro 的 Deployment 用 Recreate 更新策略(RWO 卷同时只能被一个节点挂,滚动更新的新 Pod 落在别的节点会一直等不到卷;代价是升级窗口内短暂没有副本)。
|
|
6
|
+
{{- end }}
|
|
7
|
+
{{- if eq (include "sema.sealedKey.accessMode" .) "ReadWriteMany" }}
|
|
8
|
+
多个 pod 共用这只卷,存储类必须支持 ReadWriteMany(PVC 一直 Pending 就是它不支持:出路 server.replicas=1,或 sealedKey.persistence.enabled=false 并接受耐久审批随之关)。
|
|
9
|
+
{{- end }}
|
|
10
|
+
{{- if gt (int (include "sema.sealedKey.podCount" .)) 1 }}
|
|
11
|
+
首装是空卷:多个副本并发首启会各自生成一把钥匙、各持自己那一把 —— 装好后执行一次 kubectl -n {{ .Release.Namespace }} rollout restart deploy/sema-server{{ if .Values.serverV4pro.enabled }} deploy/sema-server-v4pro{{ end }},此后每个副本读到目录里的全部钥匙。
|
|
12
|
+
{{- end }}
|
|
13
|
+
{{- else }}
|
|
14
|
+
停驻封签钥匙:sealedKey.persistence.enabled=false —— 不建卷,图表已给每只 worker 显式设上 DURABLE_APPROVAL=false(耐久审批整条关,不产生续不了的停驻行;代价见 values.yaml 的 sealedKey 块;要重新打开,把它改回 true)。关持久 + 提交体没声明 settings.approvalFrameFamilies 的客户端 ⇒ 受门工具当场拒(没有耐久店,无处停驻;7.134.0 起审批卡只发给声明认得这一族的客户端)。
|
|
15
|
+
{{- end }}
|