@sema-agent/server 7.22.0-rc.1 → 7.23.0-rc.1
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/README.md +2 -2
- package/README.zh-CN.md +2 -2
- package/USAGE.md +6 -4
- package/deploy/sema-up/chart/values.yaml +1 -1
- package/dist/approval-card.d.ts +53 -0
- package/dist/approval-card.js +104 -1
- package/dist/bench/s1/run-firm.js +1 -1
- package/dist/boot/budget-tracing.d.ts +0 -1
- package/dist/boot/resolve-spec.js +3 -1
- package/dist/budget.d.ts +0 -11
- package/dist/budget.js +10 -22
- package/dist/capabilities/scenarios.js +14 -8
- package/dist/config.d.ts +1 -1
- package/dist/config.js +12 -1
- package/dist/fleet/fleet-bus.d.ts +8 -3
- package/dist/fleet/fleet-bus.js +10 -5
- package/dist/http/route-ctx.d.ts +11 -0
- package/dist/http/route-ctx.js +16 -1
- package/dist/http/routes/runs.d.ts +3 -0
- package/dist/http/routes/runs.js +28 -24
- package/dist/http/routes/side-query.js +4 -13
- package/dist/http/routes/tasks.js +15 -3
- package/dist/http/server.d.ts +4 -3
- package/dist/http/server.js +8 -4
- package/dist/http/wire-types.d.ts +3 -1
- package/dist/lsp/manager.d.ts +9 -0
- package/dist/lsp/manager.js +8 -26
- package/dist/main.js +4 -0
- package/dist/observability/fail-open.d.ts +4 -0
- package/dist/observability/fail-open.js +4 -0
- package/dist/observability/otel-exporter.js +7 -1
- package/dist/observability/secret-env-scrub.d.ts +53 -0
- package/dist/observability/secret-env-scrub.js +135 -0
- package/dist/plugins/image-bake-store-sql.d.ts +8 -1
- package/dist/plugins/image-bake-store-sql.js +8 -1
- package/dist/plugins/image-index-sql.d.ts +13 -3
- package/dist/plugins/image-index-sql.js +13 -3
- package/dist/plugins/permission-rule-store-sql.d.ts +9 -72
- package/dist/plugins/permission-rule-store-sql.js +36 -64
- package/dist/plugins/remote-env-host.js +10 -2
- package/dist/project-memory.js +7 -1
- package/dist/run-local.js +7 -0
- package/dist/runtime-governance.d.ts +1 -1
- package/dist/runtime-governance.js +12 -15
- package/dist/security.d.ts +3 -1
- package/dist/security.js +15 -7
- package/dist/task-settings.js +10 -5
- package/dist/tool-approval.d.ts +20 -1
- package/dist/tool-approval.js +4 -1
- package/dist/trace/core-keyset-guard.d.ts +1 -1
- package/dist/trace/project.d.ts +2 -0
- package/dist/trace/project.js +11 -0
- package/package.json +3 -3
package/README.md
CHANGED
|
@@ -95,13 +95,13 @@ Requirements: Node ≥ 20 (npm path) and an OpenAI-compatible model gateway.
|
|
|
95
95
|
```bash
|
|
96
96
|
# A) npm
|
|
97
97
|
npm install @sema-agent/server
|
|
98
|
-
MODEL_GATEWAY_BASEURL=https://api.deepseek.com MODEL_ID=deepseek-v4-
|
|
98
|
+
MODEL_GATEWAY_BASEURL=https://api.deepseek.com MODEL_ID=deepseek-v4-flash \
|
|
99
99
|
MODEL_API_KEY=<your-key> SERVICE_AUTH_TOKEN=<pick-one> \
|
|
100
100
|
node node_modules/@sema-agent/server/dist/main.js # → :8090
|
|
101
101
|
|
|
102
102
|
# B) container (zero local deps, anonymous pull)
|
|
103
103
|
docker run -p 8090:8090 \
|
|
104
|
-
-e MODEL_GATEWAY_BASEURL=https://api.deepseek.com -e MODEL_ID=deepseek-v4-
|
|
104
|
+
-e MODEL_GATEWAY_BASEURL=https://api.deepseek.com -e MODEL_ID=deepseek-v4-flash \
|
|
105
105
|
-e MODEL_API_KEY=<your-key> -e SERVICE_AUTH_TOKEN=<pick-one> \
|
|
106
106
|
ghcr.io/sema-agent/sema-server:latest # or docker.io/claybobby/sema-server:latest
|
|
107
107
|
|
package/README.zh-CN.md
CHANGED
|
@@ -89,13 +89,13 @@
|
|
|
89
89
|
```bash
|
|
90
90
|
# A) npm
|
|
91
91
|
npm install @sema-agent/server
|
|
92
|
-
MODEL_GATEWAY_BASEURL=https://api.deepseek.com MODEL_ID=deepseek-v4-
|
|
92
|
+
MODEL_GATEWAY_BASEURL=https://api.deepseek.com MODEL_ID=deepseek-v4-flash \
|
|
93
93
|
MODEL_API_KEY=<你的-key> SERVICE_AUTH_TOKEN=<自定> \
|
|
94
94
|
node node_modules/@sema-agent/server/dist/main.js # → :8090
|
|
95
95
|
|
|
96
96
|
# B) 容器(零依赖,匿名可拉)
|
|
97
97
|
docker run -p 8090:8090 \
|
|
98
|
-
-e MODEL_GATEWAY_BASEURL=https://api.deepseek.com -e MODEL_ID=deepseek-v4-
|
|
98
|
+
-e MODEL_GATEWAY_BASEURL=https://api.deepseek.com -e MODEL_ID=deepseek-v4-flash \
|
|
99
99
|
-e MODEL_API_KEY=<你的-key> -e SERVICE_AUTH_TOKEN=<自定> \
|
|
100
100
|
ghcr.io/sema-agent/sema-server:latest # 或 docker.io/claybobby/sema-server:latest
|
|
101
101
|
|
package/USAGE.md
CHANGED
|
@@ -103,7 +103,7 @@ MODEL_DEGRADE_ON=rate_limit,breaker_open # 可选,反应式触发器子集;词
|
|
|
103
103
|
# 不想要这两类触发就显式设回 rate_limit,breaker_open。拼错词 boot 拒启(不静默忽略)。
|
|
104
104
|
```
|
|
105
105
|
- 反应式降级 brain 包在**最外层**(core council #8);fallback brain 用自己的凭据(decorator 清掉主模型的 per-call key 防泄漏给别的 provider)。**坑**:`MODEL_DEGRADE_TO` 最好别和被限流的是同一网关/账号,否则反应式切过去照样撞同一个 rate_limit。
|
|
106
|
-
- **定价怎么设**:`model.cost` 来自 `MODEL_COST_INPUT/OUTPUT/CACHE_READ/CACHE_WRITE`(**USD per 1M tokens**,默认 0 = `costUsd` 读 0)。云模型(如 review-gw 的 deepseek-v4-
|
|
106
|
+
- **定价怎么设**:`model.cost` 来自 `MODEL_COST_INPUT/OUTPUT/CACHE_READ/CACHE_WRITE`(**USD per 1M tokens**,默认 0 = `costUsd` 读 0)。云模型(如 review-gw 的 deepseek-v4-flash)必须设,否则 spend 恒为 $0;本地自托管(qwen)留 0 即对(无 per-token 外部花费)。字段名是 `costUsd`,**非美元计价的网关要先折算**(如 DeepSeek 官方 CNY ÷ 汇率)。配置控制面管的模型走 `CenterModel.cost`(中心存价、不存 secret)。
|
|
107
107
|
- 成本计量**自动开**:从 config 的 `model.cost`(per-1M 绝对 USD)注入 `pricing`,core 算出权威的整数 `costMicroUsd`(避免浮点累计误差)。`/metrics` 新增:`model_cost_micro_usd_total{model}`(覆盖所有 brain 调用=主任务+异步+council 子任务的总花费)、`brain_first_token_ms`(网关 hang 早警)、`brain_call_latency_ms`、`tool_calls_total{name,ok}`、`budget_exceeded_total{code}`、`cost_quota_rejected_total`、`degraded_total{reason}`(1.40 降级)。
|
|
108
108
|
- **近预算降级 vs 硬闸**:降级(`MODEL_DEGRADE_TO`,到 `atCostFraction` 切便宜模型)是**撑长**预算、任务仍完成(出口质量下降、发 `task.degraded` 事件可告警);硬闸(`maxCostUsd` 全额)仍在,切了便宜模型还超全额 → `budget.exceeded` 停。
|
|
109
109
|
- **`METRICS_TOKEN`**(可选,只读):设了它,`GET /metrics`+`/metrics/summary` 接受**它或** `SERVICE_AUTH_TOKEN`。**全 fleet 设同一个值** → 管理端(sema-web,规划名 sema-admin)用**一个** token 拉所有 worker 的指标,**无需持有各 worker 的全权 `SERVICE_AUTH_TOKEN`**(不破坏 secret 边界)。即使泄露也只暴露指标(只读)。
|
|
@@ -131,7 +131,7 @@ OTEL_EXPORTER_OTLP_ENDPOINT=http://otel-collector:4318 # 设了才开;周期
|
|
|
131
131
|
OTEL_EXPORT_INTERVAL_MS=15000 OTEL_SERVICE_NAME=sema-server
|
|
132
132
|
OTEL_EXPORTER_OTLP_HEADERS=authorization=Bearer xxx # 逗号分隔的 k=v(可选,如鉴权头)
|
|
133
133
|
```
|
|
134
|
-
- **零依赖**:不引 OTel SDK(顾及内网镜像 + bun 单文件),直接按 OTLP/JSON 协议把指标注册表 POST 给 collector;Prometheus `/metrics` 不受影响、两者可并存。best-effort——collector
|
|
134
|
+
- **零依赖**:不引 OTel SDK(顾及内网镜像 + bun 单文件),直接按 OTLP/JSON 协议把指标注册表 POST 给 collector;Prometheus `/metrics` 不受影响、两者可并存。best-effort——collector 挂了只记日志、绝不影响服务;⚠️ 这意味着**metrics 轴此前对导出失败零信号**(Prometheus-only 部署看不见 collector 持续宕机)——7.23.0 起每次导出失败计入 `fail_open_total{tag="server.otel.export-failed"}`,告警规则可直接盯这条。计数器→Sum(monotonic)、gauge→Gauge、histogram→Histogram(累积桶转成 OTLP 的 per-bucket + `+Inf` 溢出桶)。
|
|
135
135
|
|
|
136
136
|
**可选 — 成本分层 + `@-model`(core 1.24 role map)**
|
|
137
137
|
```bash
|
|
@@ -148,7 +148,7 @@ MODEL_ID=<your-model-name> MODEL_CHEAP_ID=<your-cheap-model-name>
|
|
|
148
148
|
MODEL_CODE_ROLES=default,subagent # 不设=全中立;仅这些角色在「任务没自带 systemPrompt」时跑编码提示
|
|
149
149
|
```
|
|
150
150
|
- 经 `RoleSpec.systemPrompt` 挂在**角色**上,非开发角色保持中立、全局默认 `DEFAULT_SYSTEM_PROMPT` 不变;任务自带 `systemPrompt`(或客户端注入)时仍优先。
|
|
151
|
-
- **验证门**(core 1.44,opt-in):请求体带 `verify:true`(可选 `verifyRounds`,夹到 [1,5]、默认 2)→ 任务跑完后由**独立只读对抗 verifier**(`verifier` 角色,默认=主模型)证据强制地"试图 break 它",FAIL 则把 findings 注回同 session 续跑修复→重验,循环到 PASS 或轮数上限。结果带 `verification:{verdict,rounds,findings,evidence}`(`verdict` 看质量,`result`/`status` 仍是实现的)。**仅 `/v1/tasks`(同步)与 `/v1/runs`(异步)**——`/v1/tasks/stream` 不支持(多轮非单流,请求 verify 会 400)。verifier 工具默认 = 实现任务工具滤掉 `effect:"write"`(只读边界)。`/metrics` 加 `verifications_total{verdict}
|
|
151
|
+
- **验证门**(core 1.44,opt-in):请求体带 `verify:true`(可选 `verifyRounds`,夹到 [1,5]、默认 2)→ 任务跑完后由**独立只读对抗 verifier**(`verifier` 角色,默认=主模型)证据强制地"试图 break 它",FAIL 则把 findings 注回同 session 续跑修复→重验,循环到 PASS 或轮数上限。结果带 `verification:{verdict,rounds,findings,evidence}`(`verdict` 看质量,`result`/`status` 仍是实现的)。**仅 `/v1/tasks`(同步)与 `/v1/runs`(异步)**——`/v1/tasks/stream` 不支持(多轮非单流,请求 verify 会 400)。verifier 工具默认 = 实现任务工具滤掉 `effect:"write"`(只读边界)。`/metrics` 加 `verifications_total{verdict}`。⚠️ 同步 `/v1/tasks` 形**没有事件通道**(过程帧无处回放,只回终局 JSON)——带 `verify`/`cascade` 的 200 体自 7.23.0 起附可选 `notice` 键指路 `POST /v1/runs`(要过程可见性用异步形;[3979])。
|
|
152
152
|
- **记忆(design/138 文件记忆引擎,2026-07-08 起唯一记忆面)**:core 注入式文件引擎——任务开始时 materialize 记忆目录(`MEMORY_ENGINE_DIR`,默认 `~/.ai-agent`),模型用**普通文件技能**读写记忆(CC `# Memory` 指令 + 派生索引;无 remember/recall 工具),任务边界 harvest 门(secret/cap 扫描)提交。单用户默认开,`MEMORY_ENGINE=off` 显式关;多租户恒关(文件基座无租户隔离,fail-closed)。旧 SQL 记忆面(`MEMORY_BACKEND`/`EMBEDDING_*`/`MEMORY_READ_LIMIT`/去重/向量检索、`GET/DELETE /v1/memory` 与 session memory 写 verb)已退役,数据不迁移——升级后对库跑一次 `scripts/drop-memory-tables.sql`。`body.memoryWrite:false` 仍是每请求只读开关(harvest 不提交)。
|
|
153
153
|
**`MEMORY_PERSISTENCE_CAPABLE`(三态,7.14.0 起)**=**部署自述**「本机能不能把用户的『记住 X』落到持久处」,与请求键 `memoryWrite` 两轴正交(后者收紧本次运行,前者是 operator 声明;任一取否即只读方向)。缺省不设=引擎按 roster 自行推断;`true`=本部署有推断看不见的持久通道(自定义 writer / 记忆 MCP / 远程执行车道与记忆根共享挂载);`false`=强制只读披露并关掉文件工具往记忆根的写通道。不认得的词拒启。
|
|
154
154
|
🔴 **`false` 自 core 5.27.0(7.14.0 提货)起还多一层——但只在 file 记忆引擎上**(`MEMORY_ENGINE_BACKEND` 未设 = 默认单机形):该会话是**受限会话**——materialize/搜索/harvest 一律按**已提交账**供给,记忆根下无事务背书的磁盘分歧既不收编也不供给,而是留盘 + 响亮点名(`restricted_divergence`,本服务把它计进 `memory_harvest_rejections_total{code}` 并 warn 一条),等下一次**非受限**会话走正常门收编。合法流程不受影响:用户手改、`git pull` 落下的删除照旧(延后,不销毁),并发的可写会话提交的变更算有事务背书。
|
|
@@ -445,7 +445,9 @@ SEMA_PARENT_PID=$$ # 壳把自己的 pid 传给它 spawn 出来的引擎
|
|
|
445
445
|
错误文案直接给出该改成什么。所以不存在"两个名字同时设"的状态,也不存在"旧名还在悄悄生效"的状态。
|
|
446
446
|
|
|
447
447
|
**`LSP_ENABLED` 一分为二**:它原来同时驱动两条腿,而且两腿缺省相反——沙箱腿缺省**关**(要烤好的
|
|
448
|
-
`ai-agent-code-lsp`
|
|
448
|
+
`ai-agent-code-lsp` 模板;⚠️ 该模板**只烤 TS/JS**——Python/Go 烤在 `dev-base`,dart/kotlin/java/swift
|
|
449
|
+
烤在 `dev-mobile`,四模板物理独立,`E2B_TEMPLATE` 选错则未烤语言的 LSP **静默退化到 grep/read**;
|
|
450
|
+
对照表见 ARCHITECTURE §7,真源=`src/lsp/e2b-bridge.ts`),host 腿缺省**开**(只要 PATH 上有 language server,没有就优雅退回 grep/read)。
|
|
449
451
|
现在沙箱腿仍是 `LSP_ENABLED`(opt-in),host 腿归 `LSP_HOST_ENABLED`(opt-out)。
|
|
450
452
|
🪦 `LSP_ENABLED=false`(拆分前唯一的 host 腿逃生舱)自 3.0.0 起是墓碑:拒启并指路 `LSP_HOST_ENABLED`。
|
|
451
453
|
`LSP_ENABLED=true` 不受影响——那是沙箱腿自己的 opt-in,语义没变。
|
package/dist/approval-card.d.ts
CHANGED
|
@@ -21,6 +21,7 @@
|
|
|
21
21
|
* 没有方法、没有捕获的行为。
|
|
22
22
|
*/
|
|
23
23
|
import { z } from "zod";
|
|
24
|
+
import { type AskEvidenceAbsence } from "@sema-agent/core";
|
|
24
25
|
import type { AskRow } from "./plugins/approval-ask-store-sql.js";
|
|
25
26
|
/** 模型自由文本(`sourceAgentName` / `delegation.agentName`)的限长(设计稿 §6.2)。设计 §3.1 把
|
|
26
27
|
* 「限长 + 脱敏」写成 **server 新增责任**(引擎无此层):spawning model 挑的名字是自由文本,
|
|
@@ -121,6 +122,36 @@ export type ProbeCauseProjection = z.infer<typeof ProbeCauseSchema>;
|
|
|
121
122
|
* (截出来的是另一个机器码);`shown` 超限 ⇒ **截条目**,`total` 逐字保真。
|
|
122
123
|
*/
|
|
123
124
|
export declare function readProbeCause(req: unknown): ProbeCauseProjection | undefined;
|
|
125
|
+
/**
|
|
126
|
+
* #263 件 G2 —— `AskRequest.ruleEvidence` 的形(帧 / `card_json` / 读面共用;窄读入口是
|
|
127
|
+
* {@link readRuleEvidence})。三对成员各 = 值**或**五词命名缺席(`AskEvidenceAbsence`),core 引擎
|
|
128
|
+
* 盖章的证据恰一在场;server 只判形转录,**永不**自铸缺席词(server 铸词 = 谎报「某一层没报」)。
|
|
129
|
+
*/
|
|
130
|
+
export declare const RuleEvidenceSchema: z.ZodObject<{
|
|
131
|
+
orgRevision: z.ZodOptional<z.ZodNumber>;
|
|
132
|
+
orgRevisionAbsent: z.ZodOptional<z.ZodCustom<AskEvidenceAbsence, AskEvidenceAbsence>>;
|
|
133
|
+
orgRule: z.ZodOptional<z.ZodString>;
|
|
134
|
+
orgRuleAbsent: z.ZodOptional<z.ZodCustom<AskEvidenceAbsence, AskEvidenceAbsence>>;
|
|
135
|
+
personalRuleDots: z.ZodOptional<z.ZodArray<z.ZodObject<{
|
|
136
|
+
actor: z.ZodString;
|
|
137
|
+
counter: z.ZodNumber;
|
|
138
|
+
}, z.core.$strict>>>;
|
|
139
|
+
personalRuleDotsAbsent: z.ZodOptional<z.ZodCustom<AskEvidenceAbsence, AskEvidenceAbsence>>;
|
|
140
|
+
}, z.core.$strict>;
|
|
141
|
+
export type RuleEvidenceProjection = z.infer<typeof RuleEvidenceSchema>;
|
|
142
|
+
/**
|
|
143
|
+
* `AskRequest.ruleEvidence`(core 5.35.0 design/252 G-2)的**边界窄读**——活卡帧(`ToolApprovalFrame.
|
|
144
|
+
* ruleEvidence`)与 `card_json`(`buildApprovalCard`)的**唯一**铸造点,两面结构性同值(与
|
|
145
|
+
* {@link readProbeCause} 同款分工)。姿势逐条:
|
|
146
|
+
* · `safeParse`,形不合 ⇒ 按缺席处置(宪法 [2704]);未知键 strip(core additive 加键不塌卡)。
|
|
147
|
+
* · **恰一不变量**:一对成员(值 / 缺席词)**双在场** ⇒ 整只丢——那是矛盾证据(引擎盖章的恰一,
|
|
148
|
+
* 双在场只可能是第三方 producer 的坏行),半解上卡比缺席更坏。双缺席**容忍**(老 producer 行)。
|
|
149
|
+
* · `orgRule` 内容族(管理员写的规则文本)⇒ `redactSecrets` 后截 `MAX_RULE_TEXT_CHARS`,**先脱敏
|
|
150
|
+
* 再截一次算定**(`persistedRuleShadowed` R2-[medium] 教训:redact 会变长,两面各截会漂);空串丢整只。
|
|
151
|
+
* · `dots.actor` 身份串 ⇒ `redactSecrets`(对正常 id 恒等);超限**整只丢**(见上限顶注,身份不可截)。
|
|
152
|
+
* · `orgRevision` / 缺席词 verbatim(数值 / 闭集词,非内容)。
|
|
153
|
+
*/
|
|
154
|
+
export declare function readRuleEvidence(req: unknown): RuleEvidenceProjection | undefined;
|
|
124
155
|
/**
|
|
125
156
|
* design/172 §3.1 的**中性投影**——呈卡面看到的全部内容,与 wire 面的既有 `ToolApprovalFrame` 解耦。
|
|
126
157
|
*
|
|
@@ -165,6 +196,17 @@ export declare const ApprovalCardSchema: z.ZodObject<{
|
|
|
165
196
|
total: z.ZodNumber;
|
|
166
197
|
}, z.core.$strict>>;
|
|
167
198
|
}, z.core.$strict>>;
|
|
199
|
+
ruleEvidence: z.ZodOptional<z.ZodObject<{
|
|
200
|
+
orgRevision: z.ZodOptional<z.ZodNumber>;
|
|
201
|
+
orgRevisionAbsent: z.ZodOptional<z.ZodCustom<AskEvidenceAbsence, AskEvidenceAbsence>>;
|
|
202
|
+
orgRule: z.ZodOptional<z.ZodString>;
|
|
203
|
+
orgRuleAbsent: z.ZodOptional<z.ZodCustom<AskEvidenceAbsence, AskEvidenceAbsence>>;
|
|
204
|
+
personalRuleDots: z.ZodOptional<z.ZodArray<z.ZodObject<{
|
|
205
|
+
actor: z.ZodString;
|
|
206
|
+
counter: z.ZodNumber;
|
|
207
|
+
}, z.core.$strict>>>;
|
|
208
|
+
personalRuleDotsAbsent: z.ZodOptional<z.ZodCustom<AskEvidenceAbsence, AskEvidenceAbsence>>;
|
|
209
|
+
}, z.core.$strict>>;
|
|
168
210
|
fromSubagent: z.ZodOptional<z.ZodLiteral<true>>;
|
|
169
211
|
sourceTaskId: z.ZodOptional<z.ZodString>;
|
|
170
212
|
sourceAgentName: z.ZodOptional<z.ZodString>;
|
|
@@ -216,6 +258,17 @@ export declare const ApprovalCardEnvelopeSchema: z.ZodObject<{
|
|
|
216
258
|
total: z.ZodNumber;
|
|
217
259
|
}, z.core.$strict>>;
|
|
218
260
|
}, z.core.$strict>>;
|
|
261
|
+
ruleEvidence: z.ZodOptional<z.ZodObject<{
|
|
262
|
+
orgRevision: z.ZodOptional<z.ZodNumber>;
|
|
263
|
+
orgRevisionAbsent: z.ZodOptional<z.ZodCustom<AskEvidenceAbsence, AskEvidenceAbsence>>;
|
|
264
|
+
orgRule: z.ZodOptional<z.ZodString>;
|
|
265
|
+
orgRuleAbsent: z.ZodOptional<z.ZodCustom<AskEvidenceAbsence, AskEvidenceAbsence>>;
|
|
266
|
+
personalRuleDots: z.ZodOptional<z.ZodArray<z.ZodObject<{
|
|
267
|
+
actor: z.ZodString;
|
|
268
|
+
counter: z.ZodNumber;
|
|
269
|
+
}, z.core.$strict>>>;
|
|
270
|
+
personalRuleDotsAbsent: z.ZodOptional<z.ZodCustom<AskEvidenceAbsence, AskEvidenceAbsence>>;
|
|
271
|
+
}, z.core.$strict>>;
|
|
219
272
|
fromSubagent: z.ZodOptional<z.ZodLiteral<true>>;
|
|
220
273
|
sourceTaskId: z.ZodOptional<z.ZodString>;
|
|
221
274
|
sourceAgentName: z.ZodOptional<z.ZodString>;
|
package/dist/approval-card.js
CHANGED
|
@@ -21,7 +21,8 @@
|
|
|
21
21
|
* 没有方法、没有捕获的行为。
|
|
22
22
|
*/
|
|
23
23
|
import { z } from "zod";
|
|
24
|
-
import { MAX_RULE_TEXT_CHARS } from "@sema-agent/core";
|
|
24
|
+
import { ASK_EVIDENCE_ABSENCE_VALUES, MAX_RULE_TEXT_CHARS } from "@sema-agent/core";
|
|
25
|
+
import { redactSecrets } from "./trace/redact.js";
|
|
25
26
|
/** 模型自由文本(`sourceAgentName` / `delegation.agentName`)的限长(设计稿 §6.2)。设计 §3.1 把
|
|
26
27
|
* 「限长 + 脱敏」写成 **server 新增责任**(引擎无此层):spawning model 挑的名字是自由文本,
|
|
27
28
|
* 不能指望壳去截——一条 100KB 的 agentName 在 server 侧就该被拒,而不是变成一张撑爆呈卡面的卡。 */
|
|
@@ -165,6 +166,97 @@ export function readProbeCause(req) {
|
|
|
165
166
|
...(raw.further !== undefined ? { further: family(raw.further) } : {}),
|
|
166
167
|
};
|
|
167
168
|
}
|
|
169
|
+
/**
|
|
170
|
+
* #263 件 G2(core 5.35.0 design/252 G-2)—— `ruleEvidence` 的**防御性上限**两兄弟(信封非政策,
|
|
171
|
+
* 与 {@link MAX_PROBE_CAUSE_CODE} 家族同姿势:core 侧值来自引擎自己的治理层,没有导出上限可抄)。
|
|
172
|
+
* `dot.actor` 是身份串(规则属主 id),256 取 core `APPROVER_ATTRIBUTION_MAX_CHARS` 的同族语义;
|
|
173
|
+
* dots 条数的真实域 = 同一条规则文本的并发批准数(个位数),16 是纯防御臂。
|
|
174
|
+
* 🔴 dots 超限 / actor 超限 ⇒ **整只丢**而不是截:core 契约明写「The set is the identity」——截过的
|
|
175
|
+
* 集合、截过的 actor 名的是**另一个**身份,比缺席更坏(与 probeCause `code` 超限同判据)。
|
|
176
|
+
*/
|
|
177
|
+
const MAX_EVIDENCE_DOT_ACTOR = 256;
|
|
178
|
+
const MAX_EVIDENCE_DOTS = 16;
|
|
179
|
+
/** 五词缺席词表——单一属主 = core 的 `ASK_EVIDENCE_ABSENCE_VALUES`(闭集常量,不在本仓抄词;
|
|
180
|
+
* 上游加词时这里自动跟随)。`z.custom` 而不是 `z.enum`:后者要非空元组字面量型,从 readonly 数组
|
|
181
|
+
* 常量过去只能 `as unknown as`(类型卫生门禁),而运行期判据本来就是「在 core 词表里」这一件事。 */
|
|
182
|
+
const AskEvidenceAbsenceSchema = z.custom((v) => typeof v === "string" && ASK_EVIDENCE_ABSENCE_VALUES.includes(v));
|
|
183
|
+
/** 一颗规则 add dot(core `RuleDot`:add 的持久身份 `{actor, counter}`)。 */
|
|
184
|
+
const RuleDotSchema = z.object({ actor: z.string().min(1).max(MAX_EVIDENCE_DOT_ACTOR), counter: z.number().int().nonnegative() }).strict();
|
|
185
|
+
/**
|
|
186
|
+
* #263 件 G2 —— `AskRequest.ruleEvidence` 的形(帧 / `card_json` / 读面共用;窄读入口是
|
|
187
|
+
* {@link readRuleEvidence})。三对成员各 = 值**或**五词命名缺席(`AskEvidenceAbsence`),core 引擎
|
|
188
|
+
* 盖章的证据恰一在场;server 只判形转录,**永不**自铸缺席词(server 铸词 = 谎报「某一层没报」)。
|
|
189
|
+
*/
|
|
190
|
+
export const RuleEvidenceSchema = z
|
|
191
|
+
.object({
|
|
192
|
+
orgRevision: z.number().finite().optional(),
|
|
193
|
+
orgRevisionAbsent: AskEvidenceAbsenceSchema.optional(),
|
|
194
|
+
orgRule: z.string().max(MAX_RULE_TEXT_CHARS).optional(),
|
|
195
|
+
orgRuleAbsent: AskEvidenceAbsenceSchema.optional(),
|
|
196
|
+
personalRuleDots: z.array(RuleDotSchema).max(MAX_EVIDENCE_DOTS).optional(),
|
|
197
|
+
personalRuleDotsAbsent: AskEvidenceAbsenceSchema.optional(),
|
|
198
|
+
})
|
|
199
|
+
.strict();
|
|
200
|
+
/** 窄读的入参形(不限长孪生,分工同 {@link ProbeCauseEnvelopeSchema}):只判结构,长度/脱敏由
|
|
201
|
+
* {@link readRuleEvidence} 判完之后一次算定。 */
|
|
202
|
+
const RuleEvidenceEnvelopeSchema = z.object({
|
|
203
|
+
ruleEvidence: z
|
|
204
|
+
.object({
|
|
205
|
+
orgRevision: z.number().finite().optional(),
|
|
206
|
+
orgRevisionAbsent: AskEvidenceAbsenceSchema.optional(),
|
|
207
|
+
orgRule: z.string().optional(),
|
|
208
|
+
orgRuleAbsent: AskEvidenceAbsenceSchema.optional(),
|
|
209
|
+
personalRuleDots: z.array(z.object({ actor: z.string().min(1), counter: z.number().int().nonnegative() })).optional(),
|
|
210
|
+
personalRuleDotsAbsent: AskEvidenceAbsenceSchema.optional(),
|
|
211
|
+
})
|
|
212
|
+
.optional(),
|
|
213
|
+
});
|
|
214
|
+
/**
|
|
215
|
+
* `AskRequest.ruleEvidence`(core 5.35.0 design/252 G-2)的**边界窄读**——活卡帧(`ToolApprovalFrame.
|
|
216
|
+
* ruleEvidence`)与 `card_json`(`buildApprovalCard`)的**唯一**铸造点,两面结构性同值(与
|
|
217
|
+
* {@link readProbeCause} 同款分工)。姿势逐条:
|
|
218
|
+
* · `safeParse`,形不合 ⇒ 按缺席处置(宪法 [2704]);未知键 strip(core additive 加键不塌卡)。
|
|
219
|
+
* · **恰一不变量**:一对成员(值 / 缺席词)**双在场** ⇒ 整只丢——那是矛盾证据(引擎盖章的恰一,
|
|
220
|
+
* 双在场只可能是第三方 producer 的坏行),半解上卡比缺席更坏。双缺席**容忍**(老 producer 行)。
|
|
221
|
+
* · `orgRule` 内容族(管理员写的规则文本)⇒ `redactSecrets` 后截 `MAX_RULE_TEXT_CHARS`,**先脱敏
|
|
222
|
+
* 再截一次算定**(`persistedRuleShadowed` R2-[medium] 教训:redact 会变长,两面各截会漂);空串丢整只。
|
|
223
|
+
* · `dots.actor` 身份串 ⇒ `redactSecrets`(对正常 id 恒等);超限**整只丢**(见上限顶注,身份不可截)。
|
|
224
|
+
* · `orgRevision` / 缺席词 verbatim(数值 / 闭集词,非内容)。
|
|
225
|
+
*/
|
|
226
|
+
export function readRuleEvidence(req) {
|
|
227
|
+
const parsed = RuleEvidenceEnvelopeSchema.safeParse(req);
|
|
228
|
+
const raw = parsed.success ? parsed.data.ruleEvidence : undefined;
|
|
229
|
+
if (raw === undefined)
|
|
230
|
+
return undefined;
|
|
231
|
+
if ((raw.orgRevision !== undefined && raw.orgRevisionAbsent !== undefined) ||
|
|
232
|
+
(raw.orgRule !== undefined && raw.orgRuleAbsent !== undefined) ||
|
|
233
|
+
(raw.personalRuleDots !== undefined && raw.personalRuleDotsAbsent !== undefined)) {
|
|
234
|
+
return undefined;
|
|
235
|
+
}
|
|
236
|
+
let dots;
|
|
237
|
+
if (raw.personalRuleDots !== undefined) {
|
|
238
|
+
if (raw.personalRuleDots.length > MAX_EVIDENCE_DOTS)
|
|
239
|
+
return undefined;
|
|
240
|
+
dots = [];
|
|
241
|
+
for (const d of raw.personalRuleDots) {
|
|
242
|
+
const actor = redactSecrets(d.actor);
|
|
243
|
+
if (actor === "" || actor.length > MAX_EVIDENCE_DOT_ACTOR)
|
|
244
|
+
return undefined;
|
|
245
|
+
dots.push({ actor, counter: d.counter });
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
const orgRule = raw.orgRule !== undefined ? redactSecrets(raw.orgRule).slice(0, MAX_RULE_TEXT_CHARS) : undefined;
|
|
249
|
+
if (raw.orgRule !== undefined && (orgRule === undefined || orgRule === ""))
|
|
250
|
+
return undefined;
|
|
251
|
+
return {
|
|
252
|
+
...(raw.orgRevision !== undefined ? { orgRevision: raw.orgRevision } : {}),
|
|
253
|
+
...(raw.orgRevisionAbsent !== undefined ? { orgRevisionAbsent: raw.orgRevisionAbsent } : {}),
|
|
254
|
+
...(orgRule !== undefined ? { orgRule } : {}),
|
|
255
|
+
...(raw.orgRuleAbsent !== undefined ? { orgRuleAbsent: raw.orgRuleAbsent } : {}),
|
|
256
|
+
...(dots !== undefined ? { personalRuleDots: dots } : {}),
|
|
257
|
+
...(raw.personalRuleDotsAbsent !== undefined ? { personalRuleDotsAbsent: raw.personalRuleDotsAbsent } : {}),
|
|
258
|
+
};
|
|
259
|
+
}
|
|
168
260
|
/**
|
|
169
261
|
* design/172 §3.1 的**中性投影**——呈卡面看到的全部内容,与 wire 面的既有 `ToolApprovalFrame` 解耦。
|
|
170
262
|
*
|
|
@@ -273,6 +365,13 @@ export const ApprovalCardSchema = z
|
|
|
273
365
|
* `.strict()` 下旧二进制读不动带本键的新行 ⇒ 重放跳过 + 幂等重入走 park(fail-safe),`schemaVersion` 不动。
|
|
274
366
|
*/
|
|
275
367
|
probeCause: ProbeCauseSchema.optional(),
|
|
368
|
+
/**
|
|
369
|
+
* #263 件 G2(core 5.35.0 design/252 G-2):这只 ask 背后的**规则出处证据**——org 快照 revision /
|
|
370
|
+
* 命中的 org 规则原文 / 被越级的个人规则 add dots,每员 = 值或五词命名缺席。展示/对账元数据,
|
|
371
|
+
* **永不**是裁决输入(core 契约逐字:removing it would leave every decision byte-identical)。
|
|
372
|
+
* 回滚窗代价与 `probeCause` 逐字同族(`.strict()` 下旧二进制读不动新行 ⇒ 重放跳过 + park fail-safe)。
|
|
373
|
+
*/
|
|
374
|
+
ruleEvidence: RuleEvidenceSchema.optional(),
|
|
276
375
|
/** 委派出处(子代 ask 才在场;判别键 = `fromSubagent`,core RB-39②)。 */
|
|
277
376
|
fromSubagent: z.literal(true).optional(),
|
|
278
377
|
sourceTaskId: z.string().max(MAX_IDENT).optional(),
|
|
@@ -351,6 +450,8 @@ export function buildApprovalCard(source, req, requiresRealApproval) {
|
|
|
351
450
|
// #253 件 G1:`probeCause` 与两轴同款,从 `req`(core 的 AskRequest)窄读 —— 发帧点用**同一个**函数
|
|
352
451
|
// 铸帧上那一份,于是「帧与卡同值」是结构事实而不是两处各挑一次键的巧合(§6.5 同款判据)。
|
|
353
452
|
const probeCause = readProbeCause(req);
|
|
453
|
+
// #263 件 G2:`ruleEvidence` 同款(readRuleEvidence 是帧/卡唯一铸造点,redact+截长在函数内一次算定)。
|
|
454
|
+
const ruleEvidence = readRuleEvidence(req);
|
|
354
455
|
const toolCallId = clip(source.toolCallId, MAX_IDENT);
|
|
355
456
|
const sourceTaskId = clip(source.sourceTaskId, MAX_IDENT);
|
|
356
457
|
const sourceAgentName = clip(source.sourceAgentName, MAX_AGENT_NAME);
|
|
@@ -373,6 +474,8 @@ export function buildApprovalCard(source, req, requiresRealApproval) {
|
|
|
373
474
|
// #253 件 G1:结构化探针因由 —— 与活卡帧**同一个**窄读函数(`readProbeCause`),两面结构性同值;
|
|
374
475
|
// 形不合/缺席 ⇒ 不铸键(语义见 `ApprovalCardSchema.probeCause` 顶注)。
|
|
375
476
|
...(probeCause !== undefined ? { probeCause } : {}),
|
|
477
|
+
// #263 件 G2:规则出处证据 —— 同款同源(`readRuleEvidence`);形不合/缺席 ⇒ 不铸键。
|
|
478
|
+
...(ruleEvidence !== undefined ? { ruleEvidence } : {}),
|
|
376
479
|
// #154 车二:候选**逐字**投影(空数组 ⇒ 不投键 —— 「没有候选」与「本部署不供候选」在卡面上都读作
|
|
377
480
|
// 「本卡无此选项」,铸一个空数组只会让消费端多一条无意义的分支)。拷贝一份:卡不与调用方共享可变引用。
|
|
378
481
|
...(source.ruleSuggestions !== undefined && source.ruleSuggestions.length > 0
|
|
@@ -304,7 +304,7 @@ export async function main(argv = process.argv.slice(2)) {
|
|
|
304
304
|
process.exit(2);
|
|
305
305
|
return;
|
|
306
306
|
}
|
|
307
|
-
const modelId = liveRuntime?.modelId ?? "deepseek-v4-
|
|
307
|
+
const modelId = liveRuntime?.modelId ?? "deepseek-v4-flash";
|
|
308
308
|
const log = (msg, meta) => console.error(`[s1 run-firm] ${msg}`, meta ? JSON.stringify(meta) : "");
|
|
309
309
|
const result = await runFirm({
|
|
310
310
|
seeds: args.seeds,
|
|
@@ -30,7 +30,6 @@ export declare function createBudgetAndTracing(ctx: BudgetTracingCtx): {
|
|
|
30
30
|
tracer: import("@sema-agent/core").TracerHook;
|
|
31
31
|
sideQueryAccounting: (principal: string | undefined, r: {
|
|
32
32
|
model: string;
|
|
33
|
-
family?: "input-includes-cached" | "input-excludes-cached";
|
|
34
33
|
usage?: {
|
|
35
34
|
input?: number;
|
|
36
35
|
output?: number;
|
|
@@ -432,7 +432,9 @@ export function createResolveSpec(ctx) {
|
|
|
432
432
|
// 的 auth 从 checkpoint 重建、无此字段 → 回落持久化 body.projectId(原始提交已过形状门,与 memoryWrite
|
|
433
433
|
// 的 rides-the-persisted-body 姿势一致),让种子在 resume 腿等价重放。查表 miss(未登记/已下架)= 无种子,
|
|
434
434
|
// 不 fail(登记簿是加性配置面,不是准入门)。
|
|
435
|
-
|
|
435
|
+
// A-033.1(#256):回落腿同样折小写 —— authorizer 那一格自 core 的 `/i` 词法起收下大写形并归一,
|
|
436
|
+
// 若这条回落腿原样传,同一个 projectId 在首提交与 resume 两腿会查两个不同的 `config.projects` 键。
|
|
437
|
+
const s4ProjectId = auth?.resolvedProjectId ?? (typeof body.projectId === "string" && body.projectId ? body.projectId.toLowerCase() : undefined);
|
|
436
438
|
const s4DefaultScopes = s4ProjectId ? config.projects[s4ProjectId]?.defaultScopes : undefined;
|
|
437
439
|
return { resumeAtEntryId, rewindFilesToEntryId, s4DefaultScopes };
|
|
438
440
|
};
|
package/dist/budget.d.ts
CHANGED
|
@@ -102,19 +102,8 @@ export declare function createTracer(metrics: Pick<Metrics, "inc" | "observe">,
|
|
|
102
102
|
* (保守,绝不少收);根修=core 在 SideQueryResult 上回权威 costMicroUsd(已提货 core)。model 标签/
|
|
103
103
|
* weight 键用**路由身份 r.model**(池配置有界)非 servedModel(provider 上报,无界)。
|
|
104
104
|
*/
|
|
105
|
-
/** core `usage-accounting.cacheFamilyOf` 的私拷镜像(core exports map 只开 barrel,该函数未导出——
|
|
106
|
-
* 已提货 core barrel 导出,到货即换,normalizeAgentName 同款纪律)。逐字对拍源:
|
|
107
|
-
* dist/core/runner/usage-accounting.js(override 别名 anthropic/openai + api 判 anthropic-messages/
|
|
108
|
-
* bedrock-converse-stream)。undefined 入参(catalog 查不到)→ undefined,调用方走保守臂。 */
|
|
109
|
-
export declare function cacheFamilyOfMirror(model: {
|
|
110
|
-
api?: string;
|
|
111
|
-
params?: {
|
|
112
|
-
promptCacheFamily?: string;
|
|
113
|
-
};
|
|
114
|
-
} | undefined): "input-includes-cached" | "input-excludes-cached" | undefined;
|
|
115
105
|
export declare function createSideQueryAccountant(metrics: Pick<Metrics, "inc">, costQuota?: QuotaTracker, fleetUsage?: Pick<FleetUsageAccumulator, "record">, fleetLease?: Pick<FleetLeaseManager, "recordSpend">, quotaWeightFor?: (model: string) => number): (principal: string | undefined, r: {
|
|
116
106
|
model: string;
|
|
117
|
-
family?: "input-includes-cached" | "input-excludes-cached";
|
|
118
107
|
usage?: {
|
|
119
108
|
input?: number;
|
|
120
109
|
output?: number;
|
package/dist/budget.js
CHANGED
|
@@ -341,20 +341,6 @@ export function createTracer(metrics, costQuota, modelUsage, fleetUsage, fleetLe
|
|
|
341
341
|
* (保守,绝不少收);根修=core 在 SideQueryResult 上回权威 costMicroUsd(已提货 core)。model 标签/
|
|
342
342
|
* weight 键用**路由身份 r.model**(池配置有界)非 servedModel(provider 上报,无界)。
|
|
343
343
|
*/
|
|
344
|
-
/** core `usage-accounting.cacheFamilyOf` 的私拷镜像(core exports map 只开 barrel,该函数未导出——
|
|
345
|
-
* 已提货 core barrel 导出,到货即换,normalizeAgentName 同款纪律)。逐字对拍源:
|
|
346
|
-
* dist/core/runner/usage-accounting.js(override 别名 anthropic/openai + api 判 anthropic-messages/
|
|
347
|
-
* bedrock-converse-stream)。undefined 入参(catalog 查不到)→ undefined,调用方走保守臂。 */
|
|
348
|
-
export function cacheFamilyOfMirror(model) {
|
|
349
|
-
if (!model)
|
|
350
|
-
return undefined;
|
|
351
|
-
const o = model.params?.promptCacheFamily;
|
|
352
|
-
if (o === "input-excludes-cached" || o === "anthropic")
|
|
353
|
-
return "input-excludes-cached";
|
|
354
|
-
if (o === "input-includes-cached" || o === "openai")
|
|
355
|
-
return "input-includes-cached";
|
|
356
|
-
return model.api === "anthropic-messages" || model.api === "bedrock-converse-stream" ? "input-excludes-cached" : "input-includes-cached";
|
|
357
|
-
}
|
|
358
344
|
export function createSideQueryAccountant(metrics, costQuota,
|
|
359
345
|
// 依赖面收窄到真消费的方法(§X 同 ModelKeyRef 一式):全形对象照传(结构超集),测试桩零铸型。
|
|
360
346
|
fleetUsage, fleetLease, quotaWeightFor) {
|
|
@@ -363,14 +349,16 @@ fleetUsage, fleetLease, quotaWeightFor) {
|
|
|
363
349
|
const output = typeof r.usage?.output === "number" ? r.usage.output : 0;
|
|
364
350
|
const cacheRead = typeof r.usage?.cacheRead === "number" ? r.usage.cacheRead : 0;
|
|
365
351
|
const cacheWrite = typeof r.usage?.cacheWrite === "number" ? r.usage.cacheWrite : 0;
|
|
366
|
-
// codex
|
|
367
|
-
// (input
|
|
368
|
-
//
|
|
369
|
-
//
|
|
370
|
-
//
|
|
371
|
-
//
|
|
372
|
-
//
|
|
373
|
-
|
|
352
|
+
// #257(codex #256-R3 high 定谳):core 在 side-query 出口已把 `usage.input` **归一为 uncached 量**
|
|
353
|
+
// ——对每个 provider 族(side-query.d.ts:「`input` NORMALIZED to the uncached prompt quantity …
|
|
354
|
+
// for EVERY provider family」,与 run loop 的 usage-accounting 同一道门)。所以配额轴折算恒为
|
|
355
|
+
// input + cacheRead + cacheWrite,**不再按 cache family 分臂**:曾经的
|
|
356
|
+
// `family === "input-includes-cached" ? input : …` 分支建立在「OpenAI 族 input 仍含 cache」这个
|
|
357
|
+
// 已被 core 出口归一推翻的前提上,每次 cache 命中都把 OpenAI 族配额/lease 少收 cacheRead+cacheWrite
|
|
358
|
+
// (红先钉 = budget.test「#257 OpenAI 族 cache 命中不许少计」格)。family 判定链
|
|
359
|
+
// (cacheFamilyOfMirror + catalog 快照)随本修整体退役——LEDGER 的 cacheFamilyOf/CacheFamily
|
|
360
|
+
// 两行同步改 skip(消费点消失,理由=core 出口归一)。
|
|
361
|
+
const promptTokens = input + cacheRead + cacheWrite;
|
|
374
362
|
// RB-368/#59 同判据(createTracer 上方注释同款纪律):`usage.cost.total` 整键缺席 = 未知(unpriced 部署的
|
|
375
363
|
// side query 真形),不是「花了 $0」——折 0 就是把「不知道」编造成「免费」。已知(哪怕显式 0)才带键。
|
|
376
364
|
const costTotal = typeof r.usage?.cost?.total === "number" ? r.usage.cost.total : undefined;
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { CODE_AGENT_PROMPT, CODE_SYSTEM_PROMPT, DEFAULT_SYSTEM_PROMPT, assembleCodeTools, defaultTaskRegistry } from "@sema-agent/core";
|
|
1
|
+
import { CODE_AGENT_PROMPT, CODE_SYSTEM_PROMPT, DEFAULT_SYSTEM_PROMPT, WEB_FETCH_TOOL_NAME, assembleCodeTools, defaultTaskRegistry } from "@sema-agent/core";
|
|
2
2
|
import { HttpError } from "../security.js";
|
|
3
3
|
import { nowTool } from "./builtin-tools.js";
|
|
4
4
|
import { parseRepo, repoToolsFor } from "./repo-tools.js";
|
|
@@ -164,7 +164,10 @@ export function buildScenarios(deps) {
|
|
|
164
164
|
: {}),
|
|
165
165
|
},
|
|
166
166
|
...(webSearch ? { webSearch } : {}),
|
|
167
|
-
|
|
167
|
+
// A-033.1(#256):工具名取 core 的 `WEB_FETCH_TOOL_NAME` 而不是裸串 —— 这是**多租户闸**
|
|
168
|
+
// (allowHosts 未设 = 全公网可达 ⇒ 一个租户能把另一个租户的内网可达面当出口)。core 改名该工具时,
|
|
169
|
+
// 裸串比对恒不相等,闸门整条不生效且零症状:名单里照样挂着一只全公网 WebFetch。
|
|
170
|
+
}).filter((t) => t.name !== WEB_FETCH_TOOL_NAME || deps.requirePrincipal !== true),
|
|
168
171
|
],
|
|
169
172
|
// default/code 是手带并集的**故意消费者**(上方 rank-1 BLOCKER 注:CC agent loop MUST have the
|
|
170
173
|
// full-body roster)——#196 判别位在这里表态 full,收窄只落在声明了 none 的场景上。
|
|
@@ -238,7 +241,10 @@ export function buildScenarios(deps) {
|
|
|
238
241
|
}
|
|
239
242
|
/** Resolve the scenario for a request (falls back to `default`). */
|
|
240
243
|
export function selectScenario(scenarios, name) {
|
|
241
|
-
|
|
244
|
+
// #193 P2-13:own-property 守卫——`Record` 字面量的裸索引对原型链键("constructor" 形状)会把
|
|
245
|
+
// Object.prototype 上的函数当 Scenario 返回。上游 resolve-spec 按 Object.keys 校验 ⇒ 当前不可利用,
|
|
246
|
+
// 本守卫是纵深防御(本函数是导出面,不得依赖单一调用方的入口校验)。
|
|
247
|
+
return (name && Object.hasOwn(scenarios, name) && scenarios[name]) || scenarios.default;
|
|
242
248
|
}
|
|
243
249
|
/** 把属主的判别式摊平成 wire 上的两个键(可用臂整键省略原因,契约=「缺席即没有理由」)。 */
|
|
244
250
|
function availabilityFields(deps, toolset) {
|
|
@@ -251,7 +257,7 @@ function availabilityFields(deps, toolset) {
|
|
|
251
257
|
export function builtinScenarioDetails(scenarios, deps) {
|
|
252
258
|
const probe = (name, req, principal, fallback) => {
|
|
253
259
|
try {
|
|
254
|
-
return (scenarios[name]?.(req, principal)?.tools ?? []).map((t) => t.name);
|
|
260
|
+
return ((Object.hasOwn(scenarios, name) ? scenarios[name] : undefined)?.(req, principal)?.tools ?? []).map((t) => t.name); // P2-13 同族守卫
|
|
255
261
|
}
|
|
256
262
|
catch {
|
|
257
263
|
return fallback; // 工厂 fail-loud(部署未配该场景的后端)→ 静态名单(web 仍能展示工具面选项)
|
|
@@ -283,8 +289,8 @@ export function centerScenarioDetails(specs, builtinNames, deps) {
|
|
|
283
289
|
continue;
|
|
284
290
|
if (typeof spec.name !== "string" || !SCENARIO_NAME_RE.test(spec.name))
|
|
285
291
|
continue;
|
|
286
|
-
if (!TOOLSETS[spec.toolset])
|
|
287
|
-
continue;
|
|
292
|
+
if (!Object.hasOwn(TOOLSETS, spec.toolset) || !TOOLSETS[spec.toolset])
|
|
293
|
+
continue; // P2-13 同族守卫
|
|
288
294
|
out[spec.name] = {
|
|
289
295
|
name: spec.name,
|
|
290
296
|
source: "center",
|
|
@@ -329,7 +335,7 @@ const SCENARIO_AVAILABLE = { available: true };
|
|
|
329
335
|
* 词表外的 toolset(内建的 `full-body`/`team`)不依赖后端 ⇒ 恒可用。
|
|
330
336
|
*/
|
|
331
337
|
export function scenarioAvailability(deps, toolset) {
|
|
332
|
-
if (TOOLSETS[toolset]?.requiresRepo === true && deps.repoClient === undefined) {
|
|
338
|
+
if (Object.hasOwn(TOOLSETS, toolset) && TOOLSETS[toolset]?.requiresRepo === true && deps.repoClient === undefined) { // P2-13 同族守卫
|
|
333
339
|
return { available: false, reason: "git_client_unconfigured" };
|
|
334
340
|
}
|
|
335
341
|
return SCENARIO_AVAILABLE;
|
|
@@ -369,7 +375,7 @@ export function centerScenarios(deps, specs, builtinNames, logger) {
|
|
|
369
375
|
logger?.warn("sema_registry_scenario_invalid", { name: String(spec?.name), reason: "name must match " + String(SCENARIO_NAME_RE) });
|
|
370
376
|
continue;
|
|
371
377
|
}
|
|
372
|
-
const toolset = TOOLSETS[spec.toolset];
|
|
378
|
+
const toolset = Object.hasOwn(TOOLSETS, spec.toolset) ? TOOLSETS[spec.toolset] : undefined; // P2-13 同族守卫
|
|
373
379
|
if (!toolset) {
|
|
374
380
|
logger?.warn("sema_registry_scenario_invalid", { name: spec.name, reason: `unknown toolset "${String(spec.toolset)}" (have: ${Object.keys(TOOLSETS).join(", ")})` });
|
|
375
381
|
continue;
|
package/dist/config.d.ts
CHANGED
|
@@ -64,7 +64,7 @@ export declare function parseNumOrFailNonNegative(name: string, raw: string | un
|
|
|
64
64
|
* `invalid-default` 是**缺省种类**(不写 `kind` 即是它)—— 历史形 `{env,raw}` 的三个存量生产者
|
|
65
65
|
* (`optFinitePositiveEnv` / `boolEnv` / `boolEnvWithLegacyNegated`)因此逐字不动,它们的断言也不破。
|
|
66
66
|
*/
|
|
67
|
-
type ConfigWarningKind = "invalid-default" | "clamped" | "empty-preserved" | "entry-rejected" | "set-rejected";
|
|
67
|
+
type ConfigWarningKind = "invalid-default" | "clamped" | "empty-preserved" | "entry-rejected" | "set-rejected" | "ineffective";
|
|
68
68
|
interface ConfigWarning {
|
|
69
69
|
env: string;
|
|
70
70
|
/** 环境原文。`entry-rejected` 上恒为脱敏占位 —— 那两条 env 装的是凭据,原文永不进日志。 */
|
package/dist/config.js
CHANGED
|
@@ -255,6 +255,9 @@ export function logConfigDiagnostics(logger) {
|
|
|
255
255
|
case "set-rejected":
|
|
256
256
|
logger.warn("config_env_set_rejected", { env: w.env, problem: w.detail });
|
|
257
257
|
break;
|
|
258
|
+
case "ineffective":
|
|
259
|
+
logger.warn("config_env_set_but_ineffective", { env: w.env, raw: w.raw, problem: w.detail });
|
|
260
|
+
break;
|
|
258
261
|
case "invalid-default":
|
|
259
262
|
logger.warn("config_env_invalid_using_default", { env: w.env, raw: w.raw, ...(w.used !== undefined ? { used: w.used } : {}) });
|
|
260
263
|
break;
|
|
@@ -2038,7 +2041,15 @@ function parseLimitsHttpDomain() {
|
|
|
2038
2041
|
// 基建成本恒 0,查不到是自己那个负号被吃掉了。
|
|
2039
2042
|
toolCallMicroUsd: clampEnvWithWarn("INFRA_COST_TOOL_CALL", "0", 0),
|
|
2040
2043
|
sandboxSecMicroUsd: clampEnvWithWarn("INFRA_COST_SANDBOX_SEC", "0", 0),
|
|
2041
|
-
|
|
2044
|
+
// #193 车5(P1-4):per-run 出网计量未建(cost-taxonomy `infraUsageFromEvents` 恒 `egressBytes: 0`),
|
|
2045
|
+
// 该费率设成任何值都恒计 $0。旋钮保留(接真计量是产品件),但设非零必点名——静默会让 operator
|
|
2046
|
+
// 以为出网已计费。接上计量的那批必须同时删掉这条 push(test/config-knob-loudness #193 格看守)。
|
|
2047
|
+
egressGbMicroUsd: (() => {
|
|
2048
|
+
const v = clampEnvWithWarn("INFRA_COST_EGRESS_GB", "0", 0);
|
|
2049
|
+
if (v > 0)
|
|
2050
|
+
CONFIG_WARNINGS.push({ env: "INFRA_COST_EGRESS_GB", raw: env("INFRA_COST_EGRESS_GB", "0"), kind: "ineffective", detail: "egress metering is not wired (per-run egressBytes is a constant 0) — this rate multiplies zero and will never bill anything" });
|
|
2051
|
+
return v;
|
|
2052
|
+
})(),
|
|
2042
2053
|
},
|
|
2043
2054
|
drainGraceMs: clampEnvWithWarn("DRAIN_GRACE_MS", String(10 * 60 * 1000), 10_000), // SIGTERM drain window, default 10min, floor 10s(#210 B 档:把 600 当成"秒"填进来的手滑最常见,夹取后必须点名)
|
|
2044
2055
|
sighupIdleGraceMs: clampEnvWithWarn("SIGHUP_IDLE_GRACE_MS", String(120_000), 5_000), // B3: SIGHUP idle-shutdown window, default 2min, floor 5s
|
|
@@ -4,9 +4,14 @@ import type { RunRecord } from "../plugins/store-contracts.js";
|
|
|
4
4
|
* `onChildTerminal`(fleet「subagent 树」只渲 agent 子代)。`background_bash`/`monitor`/`external`
|
|
5
5
|
* 不属 agent fleet 树——它们此前每条都打,fleet-bus 的「无 tick 无 claim」臂给 b\* 与 m\* handle 合成
|
|
6
6
|
* agent 形瞬态终帧,壳终态留存池把这帧渲 60s = footer 幽灵子行;monitor 的 `status:"event"` 还被
|
|
7
|
-
* 终态映射折成 `failed
|
|
8
|
-
*
|
|
9
|
-
*
|
|
7
|
+
* 终态映射折成 `failed`。
|
|
8
|
+
*
|
|
9
|
+
* 🔴 A-033.1(#256):判别**本体已换 core 的 {@link isDelegatedAgentTerminal}**。此前这里是一份手写语义
|
|
10
|
+
* 孪生(自己枚举 `completed|failed|killed|cancelled`),头注自陈「与 core 语义同源——core 根导出后换装
|
|
11
|
+
* import 删本镜像」。core 现已从包根导出(`index.d.ts:109`),且它的终态集与 pending 店的驱逐偏好共用
|
|
12
|
+
* 同一份 `TERMINAL_STATUSES`(core 原话:「so "terminal" can never mean two things in this module」)——
|
|
13
|
+
* 孪生留在这里等于让 fleet 面板对「终态」的理解可以和引擎分家。本函数保留为**具名的 fleet 语义位**
|
|
14
|
+
* (调用点读的是「这条通知该不该打 fleet 的 onChildTerminal」,不是「core 怎么判终态」)。 */
|
|
10
15
|
export declare function isFleetAgentTerminalNotification(n: Pick<TaskNotificationPayload, "task_type" | "status">): boolean;
|
|
11
16
|
/** The fleet vocabulary the shell renders (MF-Fleet `FleetTask.status`). The service maps its run/workflow status
|
|
12
17
|
* onto this neutral set; `awaiting approval` = a needs-review/plan-approval park, `waiting` = a durable suspend.
|
package/dist/fleet/fleet-bus.js
CHANGED
|
@@ -21,18 +21,23 @@
|
|
|
21
21
|
* replica-local). A cross-replica fleet roll-up is a fleet-token-gated trace-API concern (separate), not this.
|
|
22
22
|
*/
|
|
23
23
|
import { EventEmitter } from "node:events";
|
|
24
|
-
import { deriveAgentDisplayStatus } from "@sema-agent/core";
|
|
24
|
+
import { deriveAgentDisplayStatus, isDelegatedAgentTerminal } from "@sema-agent/core";
|
|
25
25
|
import { recordFailOpen } from "../observability/fail-open.js";
|
|
26
26
|
import { redactSecrets } from "../trace/redact.js";
|
|
27
27
|
/** [2687-cli] 幽灵行案的单源判别:一条 `task_notification` 只有在 **agent 族 × 终态** 时才允许打
|
|
28
28
|
* `onChildTerminal`(fleet「subagent 树」只渲 agent 子代)。`background_bash`/`monitor`/`external`
|
|
29
29
|
* 不属 agent fleet 树——它们此前每条都打,fleet-bus 的「无 tick 无 claim」臂给 b\* 与 m\* handle 合成
|
|
30
30
|
* agent 形瞬态终帧,壳终态留存池把这帧渲 60s = footer 幽灵子行;monitor 的 `status:"event"` 还被
|
|
31
|
-
* 终态映射折成 `failed
|
|
32
|
-
*
|
|
33
|
-
*
|
|
31
|
+
* 终态映射折成 `failed`。
|
|
32
|
+
*
|
|
33
|
+
* 🔴 A-033.1(#256):判别**本体已换 core 的 {@link isDelegatedAgentTerminal}**。此前这里是一份手写语义
|
|
34
|
+
* 孪生(自己枚举 `completed|failed|killed|cancelled`),头注自陈「与 core 语义同源——core 根导出后换装
|
|
35
|
+
* import 删本镜像」。core 现已从包根导出(`index.d.ts:109`),且它的终态集与 pending 店的驱逐偏好共用
|
|
36
|
+
* 同一份 `TERMINAL_STATUSES`(core 原话:「so "terminal" can never mean two things in this module」)——
|
|
37
|
+
* 孪生留在这里等于让 fleet 面板对「终态」的理解可以和引擎分家。本函数保留为**具名的 fleet 语义位**
|
|
38
|
+
* (调用点读的是「这条通知该不该打 fleet 的 onChildTerminal」,不是「core 怎么判终态」)。 */
|
|
34
39
|
export function isFleetAgentTerminalNotification(n) {
|
|
35
|
-
return
|
|
40
|
+
return isDelegatedAgentTerminal(n);
|
|
36
41
|
}
|
|
37
42
|
/**
|
|
38
43
|
* durable {@link WorkflowRun} → 一条 fleet workflow 行的**纯投影**(`build*`=纯数据,无行为、不发布)。
|
package/dist/http/route-ctx.d.ts
CHANGED
|
@@ -173,4 +173,15 @@ export type RouteHandler = (req: IncomingMessage, res: ServerResponse, url: stri
|
|
|
173
173
|
export interface RouteMiss {
|
|
174
174
|
fell: boolean;
|
|
175
175
|
}
|
|
176
|
+
/**
|
|
177
|
+
* #262(A-041 存在性 oracle 封口)——`not_found.run` 的**唯一**文案源,unknown 与 not-yours 两臂共用。
|
|
178
|
+
* 修前:unknown 臂发这条长指路文案、not-yours 臂发短文「run not found」,同 code 下按**文案长度**即可
|
|
179
|
+
* 枚举他人 run id 的存在性(822 行「no existence oracle」注释的意图被长短差打穿)。not-yours 臂只可能
|
|
180
|
+
* 拿到非 a\* id(store 查到行才有 owner 比对;a\* 与 wa\* 形 id 永不在 run store)⇒ 非 a\* 长文串足以让
|
|
181
|
+
* 两臂逐字节同形,指路价值不丢。钉=audit-fixes-http「#262」格(两臂全文比对);文案变更走
|
|
182
|
+
* api-error-text-freeze 基线流程。
|
|
183
|
+
*/
|
|
184
|
+
export declare const RUN_NOT_FOUND_MESSAGE = "run not found \u2014 the id belongs to no run in this deployment's run store (a run from another server process, or an in-memory store that did not survive a restart, is not visible here)";
|
|
185
|
+
/** [1434] 前缀=域判别子既成契约:a\* 与 wa\* 形 id 给 TaskOutput/journal 指路;其余给 {@link RUN_NOT_FOUND_MESSAGE}。 */
|
|
186
|
+
export declare function runNotFoundMessage(id: string): string;
|
|
176
187
|
//# sourceMappingURL=route-ctx.d.ts.map
|
package/dist/http/route-ctx.js
CHANGED
|
@@ -1,2 +1,17 @@
|
|
|
1
|
-
|
|
1
|
+
/**
|
|
2
|
+
* #262(A-041 存在性 oracle 封口)——`not_found.run` 的**唯一**文案源,unknown 与 not-yours 两臂共用。
|
|
3
|
+
* 修前:unknown 臂发这条长指路文案、not-yours 臂发短文「run not found」,同 code 下按**文案长度**即可
|
|
4
|
+
* 枚举他人 run id 的存在性(822 行「no existence oracle」注释的意图被长短差打穿)。not-yours 臂只可能
|
|
5
|
+
* 拿到非 a\* id(store 查到行才有 owner 比对;a\* 与 wa\* 形 id 永不在 run store)⇒ 非 a\* 长文串足以让
|
|
6
|
+
* 两臂逐字节同形,指路价值不丢。钉=audit-fixes-http「#262」格(两臂全文比对);文案变更走
|
|
7
|
+
* api-error-text-freeze 基线流程。
|
|
8
|
+
*/
|
|
9
|
+
export const RUN_NOT_FOUND_MESSAGE = "run not found — the id belongs to no run in this deployment's run store (a run from another server process, or an in-memory store that did not survive a restart, is not visible here)";
|
|
10
|
+
/** [1434] 前缀=域判别子既成契约:a\* 与 wa\* 形 id 给 TaskOutput/journal 指路;其余给 {@link RUN_NOT_FOUND_MESSAGE}。 */
|
|
11
|
+
export function runNotFoundMessage(id) {
|
|
12
|
+
const aStar = /^(?:a|wa)[0-9a-f]{4,}$/i.test(id);
|
|
13
|
+
return aStar
|
|
14
|
+
? `run not found — "${id}" looks like an agent handle (a*/wa* domain), which is never in the run store; use the TaskOutput tool (or the workflow journal / subagents surface) to read it, and the run's durable taskId (X-Task-Id) for this endpoint`
|
|
15
|
+
: RUN_NOT_FOUND_MESSAGE;
|
|
16
|
+
}
|
|
2
17
|
//# sourceMappingURL=route-ctx.js.map
|