@sema-agent/server 7.79.0 → 7.79.2
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/dist/file-history-disclosure.d.ts +53 -0
- package/dist/file-history-disclosure.js +89 -0
- package/dist/http/admission.d.ts +56 -0
- package/dist/http/admission.js +560 -0
- package/dist/http/approval-sweeps.d.ts +21 -0
- package/dist/http/approval-sweeps.js +138 -0
- package/dist/http/dispatch.d.ts +32 -0
- package/dist/http/dispatch.js +230 -0
- package/dist/http/request-guards.d.ts +51 -0
- package/dist/http/request-guards.js +205 -0
- package/dist/http/resume-legs.d.ts +80 -0
- package/dist/http/resume-legs.js +1230 -0
- package/dist/http/route-table.d.ts +113 -0
- package/dist/http/route-table.js +235 -0
- package/dist/http/routes/admin-memory-optout.d.ts +1 -1
- package/dist/http/routes/memory-compliance.d.ts +1 -1
- package/dist/http/routes/memory-origin.d.ts +1 -1
- package/dist/http/routes/rules.js +4 -4
- package/dist/http/server.d.ts +20 -104
- package/dist/http/server.js +24 -2589
- package/dist/main.js +2 -0
- package/dist/observability/metrics.js +1 -1
- package/dist/plugins/file-history-store-sql.js +12 -11
- package/dist/trace/redact.d.ts +9 -0
- package/dist/trace/redact.js +210 -78
- package/package.json +1 -1
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* S-157④ —— SQL file-history 店的 **typed 永久失败**披露座:散文一个铸点 + 机器面一只 sink。
|
|
3
|
+
*
|
|
4
|
+
* ## 病(基线 v7.79.0 逐行亲核 + 亲跑)
|
|
5
|
+
* `plugins/blob-backend.ts:55` 的 `BlobTooLargeError` 带 typed 判别码 `blob_too_large_for_sql`,头注
|
|
6
|
+
* 原话「绝不能与 transient 写失败混在一类(那类是可重试的 502;这类重试永远不会成功)」。可它一旦被
|
|
7
|
+
* `plugins/file-history-store-sql.ts` 的任何一条 `catch` 折进 `FileHistoryResult.error.message`,判别码
|
|
8
|
+
* 就只剩散文,**机器一位都读不到**。两条腿各自的后果:
|
|
9
|
+
*
|
|
10
|
+
* · **snapshot(边界捕获)腿**:引擎把失败包成一句散文交给 `RunnerDeps.onError({phase:"rewind"})`
|
|
11
|
+
* (core 7.20.0 `dist/core/runner/prepare-file-history.js:269`/`:275`),服务那条臂从文本里抠**第一个**
|
|
12
|
+
* 括号词当 metric label —— 而第一个括号词恒是引擎自己的闭集码。实测:「这个文件永远存不进这口店」
|
|
13
|
+
* 与「连接被重置」在 `rewind_snapshot_failed_total` 上**同形** `code="snapshot_failed"`。
|
|
14
|
+
* · **trackEdit(首触备份)腿**:本服务用引擎默认的 `onTrackFailure:"refuse"`(`boot/runner-deps.ts` 亲读),
|
|
15
|
+
* 那条路 **完全不走 onError** —— 引擎把 `(track_failed: <本店 message>)` 念给模型,末句是
|
|
16
|
+
* 「Retry when the file-history store is healthy.」(`prepare-file-history.js:154`)。对这枚码那是永远
|
|
17
|
+
* 不会到来的一天,而**运维面一个计数都没有**:这条腿今天在遥测上完全不可见。
|
|
18
|
+
*
|
|
19
|
+
* ## 修法(源头留痕,不走文本通道)
|
|
20
|
+
* · **散文面**({@link discloseCaughtFailure} 的返回值):把 caught error 的 typed `code` 写成一个括号词
|
|
21
|
+
* 前置到它自己的 message 前 —— **不 `instanceof` 单挑任何一个类**(身上有字符串 `code` 就折),该店
|
|
22
|
+
* 11 处折点全部改走这一只函数。人与模型因此看得到「这是哪一类失败」。
|
|
23
|
+
* · **机器面**(同一只函数的副作用):码取自 typed 错**对象**、对闭集词表判定,**绝不从散文里抠**。
|
|
24
|
+
* 🔴 这一条是 codex R1-[high] 验真后的定形:文本通道**天然可伪造** —— 一个名叫
|
|
25
|
+
* `(blob_too_large_for_sql).txt` 的文件读不出来时,它的路径就在 `snapshot_failed` 的散文里,任何
|
|
26
|
+
* 「从散文里取在册括号词」的读法都会把它当成一枚永久码(继而抑制掉真故障的告警)。闭集只约束基数,
|
|
27
|
+
* 不能证明来源。所以机读判定搬回**唯一知道真相的那一层**=铸错的地方,`rewind` 臂的读法一字不动。
|
|
28
|
+
* · 留痕三件套与 `observability/fail-open.ts` / `observability/secret-env-scrub.ts` 同律:**计数逐次**
|
|
29
|
+
* (`file_history_permanent_failure_total{code}`)、**每进程每码一条 warn**(第二条起零新增内容,而
|
|
30
|
+
* 20G 树的用户每 turn 撞一次)、装配前累计的次数在 install 时补进 metrics。
|
|
31
|
+
*/
|
|
32
|
+
import type { Logger } from "./observability/logger.js";
|
|
33
|
+
import type { Metrics } from "./observability/metrics.js";
|
|
34
|
+
interface Sink {
|
|
35
|
+
logger?: Logger;
|
|
36
|
+
metrics?: Metrics;
|
|
37
|
+
}
|
|
38
|
+
/**
|
|
39
|
+
* 把一个 caught error 折进店侧散文,**并**在它是一枚 typed 永久失败时留痕。
|
|
40
|
+
*
|
|
41
|
+
* 返回值 = 散文形:带 typed `code` 的前缀一个括号词(`(blob_too_large_for_sql) blob of … exceeds …`),
|
|
42
|
+
* 不带的**逐字**是原 message(transient 失败的文案因此与本批之前一字不差)。
|
|
43
|
+
* 副作用 = 机器形:码取自错误对象,逐次计数 + 每进程每码一条 warn。观测绝不抛。
|
|
44
|
+
*/
|
|
45
|
+
export declare function discloseCaughtFailure(err: unknown): string;
|
|
46
|
+
/** 装配层把 logger/metrics 接上(main.ts,与 `installFailOpenRecorder` 同一处)。 */
|
|
47
|
+
export declare function installFileHistoryFailureRecorder(deps: Sink): void;
|
|
48
|
+
/** 本进程逐码累计(测试/诊断读面)。 */
|
|
49
|
+
export declare function fileHistoryPermanentFailureCounts(): ReadonlyMap<string, number>;
|
|
50
|
+
/** 测试用重置(进程级状态在同一个 vitest worker 里跨用例存活)。 */
|
|
51
|
+
export declare function resetFileHistoryFailureRecorderForTest(deps?: Sink): void;
|
|
52
|
+
export {};
|
|
53
|
+
//# sourceMappingURL=file-history-disclosure.d.ts.map
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
const PERMANENT_STORE_FAILURE_CODES = {
|
|
2
|
+
blob_too_large_for_sql: {
|
|
3
|
+
note: "a blob is larger than this deployment's SQL snapshot-blob cap — the same file will fail every turn until the deployment changes",
|
|
4
|
+
knobs: "MINIO_ENDPOINT/MINIO_ACCESS_KEY/MINIO_SECRET_KEY (object storage) or SNAPSHOT_BLOB_SQL_MAX_BYTES (raise the cap)",
|
|
5
|
+
},
|
|
6
|
+
};
|
|
7
|
+
const PERMANENT_FAILURE_COUNTER = "file_history_permanent_failure_total";
|
|
8
|
+
const PERMANENT_FAILURE_HELP = "file-history store failures whose typed code means retrying can NEVER succeed (the deployment must change), by code. " +
|
|
9
|
+
"Covers BOTH legs — the rewind boundary capture AND the first-touch trackEdit refusal, which the engine answers to the model " +
|
|
10
|
+
"without ever calling onError, so it appears in no other metric. Non-zero = go read the paired one-shot warn for the knobs.";
|
|
11
|
+
let sink = {};
|
|
12
|
+
let counts = new Map();
|
|
13
|
+
let pushed = new Map();
|
|
14
|
+
let warned = new Set();
|
|
15
|
+
let pendingWarn = new Map();
|
|
16
|
+
function isPermanentCode(code) {
|
|
17
|
+
return Object.prototype.hasOwnProperty.call(PERMANENT_STORE_FAILURE_CODES, code);
|
|
18
|
+
}
|
|
19
|
+
function drainToMetrics() {
|
|
20
|
+
const m = sink.metrics;
|
|
21
|
+
if (m === undefined)
|
|
22
|
+
return;
|
|
23
|
+
for (const [code, total] of counts) {
|
|
24
|
+
const already = pushed.get(code) ?? 0;
|
|
25
|
+
if (total > already) {
|
|
26
|
+
m.inc(PERMANENT_FAILURE_COUNTER, { code }, total - already);
|
|
27
|
+
pushed.set(code, total);
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
export function discloseCaughtFailure(err) {
|
|
32
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
33
|
+
const code = err?.code;
|
|
34
|
+
if (typeof code !== "string" || code === "")
|
|
35
|
+
return message;
|
|
36
|
+
if (isPermanentCode(code))
|
|
37
|
+
recordPermanent(code, message);
|
|
38
|
+
return `(${code}) ${message}`;
|
|
39
|
+
}
|
|
40
|
+
function safely(record) {
|
|
41
|
+
try {
|
|
42
|
+
record();
|
|
43
|
+
}
|
|
44
|
+
catch {
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
function recordPermanent(code, message) {
|
|
48
|
+
safely(() => {
|
|
49
|
+
counts.set(code, (counts.get(code) ?? 0) + 1);
|
|
50
|
+
drainToMetrics();
|
|
51
|
+
if (!warned.has(code))
|
|
52
|
+
emitFirstWarn(code, message);
|
|
53
|
+
});
|
|
54
|
+
}
|
|
55
|
+
function emitFirstWarn(code, message) {
|
|
56
|
+
if (!pendingWarn.has(code))
|
|
57
|
+
pendingWarn.set(code, message);
|
|
58
|
+
const logger = sink.logger;
|
|
59
|
+
if (logger === undefined)
|
|
60
|
+
return;
|
|
61
|
+
logger.warn("file_history_permanent_failure", {
|
|
62
|
+
code,
|
|
63
|
+
note: PERMANENT_STORE_FAILURE_CODES[code].note,
|
|
64
|
+
knobs: PERMANENT_STORE_FAILURE_CODES[code].knobs,
|
|
65
|
+
info: pendingWarn.get(code) ?? message,
|
|
66
|
+
once: "每进程每码一条;计数逐次,读 " + PERMANENT_FAILURE_COUNTER,
|
|
67
|
+
});
|
|
68
|
+
warned.add(code);
|
|
69
|
+
pendingWarn.delete(code);
|
|
70
|
+
}
|
|
71
|
+
export function installFileHistoryFailureRecorder(deps) {
|
|
72
|
+
sink = { ...deps };
|
|
73
|
+
deps.metrics?.counter(PERMANENT_FAILURE_COUNTER, PERMANENT_FAILURE_HELP);
|
|
74
|
+
drainToMetrics();
|
|
75
|
+
for (const [code, message] of [...pendingWarn])
|
|
76
|
+
safely(() => emitFirstWarn(code, message));
|
|
77
|
+
}
|
|
78
|
+
export function fileHistoryPermanentFailureCounts() {
|
|
79
|
+
return new Map(counts);
|
|
80
|
+
}
|
|
81
|
+
export function resetFileHistoryFailureRecorderForTest(deps = {}) {
|
|
82
|
+
counts = new Map();
|
|
83
|
+
pushed = new Map();
|
|
84
|
+
warned = new Set();
|
|
85
|
+
pendingWarn = new Map();
|
|
86
|
+
sink = { ...deps };
|
|
87
|
+
deps.metrics?.counter(PERMANENT_FAILURE_COUNTER, PERMANENT_FAILURE_HELP);
|
|
88
|
+
}
|
|
89
|
+
//# sourceMappingURL=file-history-disclosure.js.map
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 提交准入(S-147 切片:`http/server.ts` 的 `prepareSpec` 与它的模块级校验词表逐字搬家,正文一字未改)。
|
|
3
|
+
* 读体 → 验型 → 授权 → 建 spec 的**唯一**一条链;tasks / runs / A2A 三个提交面共用它(抄一份 = 抄一份会漂的 400)。
|
|
4
|
+
* ctx 字段 3 只(`deps` / `readJson` / `runnerFor`),机器算(closure-free-vars)与手抄一致。
|
|
5
|
+
*/
|
|
6
|
+
import type { IncomingMessage, ServerResponse } from "node:http";
|
|
7
|
+
import type { TaskRequestBody } from "./wire-types.js";
|
|
8
|
+
import { HttpError } from "../security.js";
|
|
9
|
+
import type { PreparedTaskSubmission, RouteHelpers } from "./route-ctx.js";
|
|
10
|
+
import type { FlatServiceDeps } from "./server.js";
|
|
11
|
+
/** = core `SKILL_CONTENT_MAX_CHARS`(1MB 加载门,core 1.293 起 invoke 全文零截断、超 1MB 整拒;常量在
|
|
12
|
+
* dist/core/runner/synthetic-tools.js:84,未从 core 包根导出,故钉本地孪生+本出处注记)。修7 曾对齐旧
|
|
13
|
+
* 20k 截断门([ref] 飞轮考据:20k 是压缩保留区口径,invoke 时刻用错),core 1.293 撤门后本门跟随回收
|
|
14
|
+
* ([ref]① 收口)——HTTP 面与引擎加载门同值,超限 400 fail-loud 而非引擎侧整拒后难诊断。
|
|
15
|
+
*
|
|
16
|
+
* 🔴 **这是技能面**唯一**剩下的尺寸门,而且它不是超集帽**:引擎在装配期对超 1 MiB 的技能本就整拒
|
|
17
|
+
* (config 阶段错误),门上先答只是把同一个判决搬到调用方看得懂的地方。[ref] 退役的三条帽
|
|
18
|
+
* (条数 / description / name 长度)都没有这个性质——它们是本仓自造的、CC 与引擎都没有的门。 */
|
|
19
|
+
export declare const MAX_SKILL_CONTENT_CHARS = 1048576;
|
|
20
|
+
export declare const MAX_SYSTEM_PROMPT_CHARS = 16384;
|
|
21
|
+
/** Cap on a request's `outputSchema` (JSON-serialized) — an uncapped schema bloats the prompt + the per-turn
|
|
22
|
+
* validation cost. 32KB comfortably holds a rich nested schema while bounding the abuse surface. */
|
|
23
|
+
export declare const MAX_OUTPUT_SCHEMA_CHARS = 32768;
|
|
24
|
+
/**
|
|
25
|
+
* Validate `body.skills` —— **形**校验 + 一条引擎孪生门。返回错误串(→ 400)或 null。
|
|
26
|
+
*
|
|
27
|
+
* 🔴 **[ref](7.77.0,硬 BREAKING;零别名零双读)**:此前这里有三条本仓自造的拒绝臂,全部退役 ——
|
|
28
|
+
* · `at most 10 skills per request`(条数帽);
|
|
29
|
+
* · `skill.description must be a string of at most 1024 characters`(描述长度帽);
|
|
30
|
+
* · `skill.name must be a non-empty string of at most 64 characters` 里的 **长度**那一半。
|
|
31
|
+
*
|
|
32
|
+
* 退役的判据是**上游两侧都没有同形门**:CC 2.1.259 的技能清单渲染(`cli259.js:23195` 的预算算法)
|
|
33
|
+
* 在超预算时把**描述裁短**、再不够就退成 name-only,**从不**因为条数多或描述长而拒掉一条技能;
|
|
34
|
+
* 它的 `Ir()` 名字校验也只判非空 / 无配对代理 / 无首尾空白 / 无括号逗号控制字符,没有长度帽。
|
|
35
|
+
* 引擎侧同理:唯一整拒的是 1 MiB 的单技能加载门。于是那三条帽是「装了 11 个技能就 400」这种
|
|
36
|
+
* 纯本仓面的伤——收益是零(预算由渲染侧按字节兜),代价是用户装不进自己的技能库。
|
|
37
|
+
*
|
|
38
|
+
* **补偿**(三问的第三问):`settings.skillListingBudgetFraction` / `settings.skillListingMaxDescChars`
|
|
39
|
+
* 两个旋钮直达引擎的渲染预算(per-turn 成本的真属主)。
|
|
40
|
+
* ⚠️ **fraction 是**单向**的**:引擎的预算 = `min(窗口 × fraction, 8 000 字节)`,8 000 是**投递车道的结构
|
|
41
|
+
* 上限**(清单帧与同 turn 的其它附件共用一份完整送达额度)⇒ **调低生效、调高被夹**;调高唯一的用处是让
|
|
42
|
+
* 一个声明窗口小于 200 000 token 的模型把清单预算拿回大窗口的水平。别把这句读成「调用方可以随便加大」。
|
|
43
|
+
* 体积面照旧由**非技能专属**的 8 MiB 请求体上限兜住(`MAX_BODY`),那是一条一直都在的门。
|
|
44
|
+
*
|
|
45
|
+
* 🔴 **server 侧禁裁列表 / 禁截描述**:渲染归引擎(单一属主)。本函数只判「这一条技能的形能不能用」。
|
|
46
|
+
*/
|
|
47
|
+
export declare function validateUserSkills(skills: unknown): string | null;
|
|
48
|
+
export declare function createAdmission(ctx: {
|
|
49
|
+
deps: FlatServiceDeps;
|
|
50
|
+
runnerFor: NonNullable<FlatServiceDeps["runnerFor"]>;
|
|
51
|
+
} & Pick<RouteHelpers, "readJson">): {
|
|
52
|
+
prepareSpec: (req: IncomingMessage, res: ServerResponse, bodyIn?: TaskRequestBody, onTypedFailure?: (err: HttpError) => void, legFacts?: {
|
|
53
|
+
liveStream?: boolean;
|
|
54
|
+
}) => Promise<PreparedTaskSubmission | null>;
|
|
55
|
+
};
|
|
56
|
+
//# sourceMappingURL=admission.d.ts.map
|