@sema-agent/server 7.75.0 → 7.75.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 +13 -0
- package/README.zh-CN.md +10 -0
- package/USAGE.md +24 -0
- package/dist/http/routes/runs.js +4 -1
- package/dist/http/server.js +3 -3
- package/dist/observability/run-terminal-log.d.ts +42 -4
- package/dist/observability/run-terminal-log.js +51 -5
- package/dist/trace/ledger-events.d.ts +12 -0
- package/dist/trace/ledger-events.js +6 -0
- package/dist/trace/ledger-sink.js +3 -4
- package/dist/trace/project.d.ts +22 -0
- package/dist/trace/project.js +3 -0
- package/dist/trace/redact.d.ts +20 -0
- package/package.json +3 -3
package/README.md
CHANGED
|
@@ -194,6 +194,19 @@ boundary) — the side-by-side table is in `USAGE.md`, worth reading before pick
|
|
|
194
194
|
Web search (deployment `WEB_SEARCH_*` env, per-request `settings.webSearch`, the two-lane precedence
|
|
195
195
|
and the tool-level error forms) is documented in `USAGE.md` §9.5.
|
|
196
196
|
|
|
197
|
+
## Security & privacy note
|
|
198
|
+
|
|
199
|
+
> 🔴 **Trace redaction ≠ egress control** (anchor: 脱敏 ≠ 出站控制). Trace redaction (the `«redacted»` markers you see in the
|
|
200
|
+
> ledger / SSE / turns / approval previews) only rewrites the **persistence and display** surfaces —
|
|
201
|
+
> **the request body carrying a tool result to the model's actual route is the original, unredacted
|
|
202
|
+
> text.** That route is not just `MODEL_GATEWAY_BASEURL`: it can also be the fallback gateway list
|
|
203
|
+
> (`MODEL_GATEWAY_FALLBACK_URLS`), an independent Anthropic route for `provider:"anthropic"` models
|
|
204
|
+
> (defaults to `https://api.anthropic.com` unless `ANTHROPIC_BASEURL` is set), or a per-model `baseUrl`
|
|
205
|
+
> override the registry can hand a single catalog model (see `docs/ARCHITECTURE.md` §10). To keep
|
|
206
|
+
> content from leaving the machine, rely on the **read-side sensitive-path gate** and confirm **every
|
|
207
|
+
> route this deployment can select** points at infrastructure you control — pointing only the default
|
|
208
|
+
> gateway env at a private endpoint is not sufficient. Verification method: `USAGE.md` §9.6.
|
|
209
|
+
|
|
197
210
|
## HTTP API overview
|
|
198
211
|
|
|
199
212
|
One row per endpoint family (not exhaustive):
|
package/README.zh-CN.md
CHANGED
|
@@ -173,6 +173,16 @@ curl -s localhost:8090/v1/tasks -H "Authorization: Bearer <SERVICE_AUTH_TOKEN>"
|
|
|
173
173
|
Web search(部署 env `WEB_SEARCH_*` 七键、per-request `settings.webSearch`、两车道优先级、工具层错误形)
|
|
174
174
|
见 `USAGE.md` §9.5。
|
|
175
175
|
|
|
176
|
+
## 安全与隐私提示
|
|
177
|
+
|
|
178
|
+
> 🔴 **trace 脱敏 ≠ 出站控制。** trace 脱敏(你在账本 / SSE / turns / 审批预览里看到的 `«redacted»`
|
|
179
|
+
> 标记)只作用于**持久化与展示面**——**发往「该 run 实际选中的模型路由」的请求体是工具结果原文**。
|
|
180
|
+
> 这条路由不止 `MODEL_GATEWAY_BASEURL` 一根:还可能是备用网关列表(`MODEL_GATEWAY_FALLBACK_URLS`)、
|
|
181
|
+
> `provider:"anthropic"` 模型的独立直连路由(缺省根 `https://api.anthropic.com`,除非显式配
|
|
182
|
+
> `ANTHROPIC_BASEURL`)、或配置中心可对单个目录模型下发的 per-model `baseUrl` 覆盖(见
|
|
183
|
+
> `docs/ARCHITECTURE.md` §10)。要让内容不出机器,靠**读侧敏感路径门**,并核对**本部署会被选中的每一条
|
|
184
|
+
> 路由**都指向自己控制的基础设施——只把默认网关 env 指到私有端点是不够的。验法见 `USAGE.md` §9.6。
|
|
185
|
+
|
|
176
186
|
## HTTP API 概览
|
|
177
187
|
|
|
178
188
|
一行一个端点族(非全量):
|
package/USAGE.md
CHANGED
|
@@ -1512,3 +1512,27 @@ TOC 本地形)时被读取;`REQUIRE_PRINCIPAL=true`(多租户)上这个键**结
|
|
|
1512
1512
|
`SEMA_WEBSEARCH_KEY_<PROVIDER>` 这样的命名空间存放每个 provider 的 key,由壳在本地读出后把明文塞进请求体;
|
|
1513
1513
|
这纯粹是**客户端约定**,本仓不读取、不校验、也不感知任何这类客户端侧 env 命名(这条命名事实来自任务书,
|
|
1514
1514
|
本车未读 cli 源码核实,列入收车档「未闭环」)。
|
|
1515
|
+
|
|
1516
|
+
### 9.6 trace 脱敏的射程:与出站请求体无关
|
|
1517
|
+
|
|
1518
|
+
🔴 **脱敏 ≠ 出站控制**:trace 脱敏(账本 / SSE / turns / 审批预览里的 `«redacted»` 标记)只作用于
|
|
1519
|
+
**持久化与展示面**——**工具结果发往「该 run 实际选中的模型路由」的请求体是原文**。⚠️ 「实际选中的路由」
|
|
1520
|
+
不等于 `MODEL_GATEWAY_BASEURL` 一根:本部署的模型出口至少四条——默认网关(`MODEL_GATEWAY_BASEURL`)、
|
|
1521
|
+
备用网关(`MODEL_GATEWAY_FALLBACK_URLS`,failover 拓扑参与)、`provider:"anthropic"` 模型的**独立直连
|
|
1522
|
+
路由**(缺省根 `https://api.anthropic.com`,除非显式配 `ANTHROPIC_BASEURL`)、以及配置中心可对**单个
|
|
1523
|
+
目录模型**下发的 per-model `baseUrl` 覆盖(见 `docs/ARCHITECTURE.md` §10「Trace/redaction」)。要让内容
|
|
1524
|
+
不出机器,靠**读侧敏感路径门**(`READ_DENY_PATTERNS` 等,见 §「执行 lane 与租户门」附近)并核对**上述
|
|
1525
|
+
四条路由每一条**都指向自己控制的基础设施——只把 `MODEL_GATEWAY_BASEURL` 指到私有网关、放着
|
|
1526
|
+
`provider:"anthropic"` 或某个目录模型的 `baseUrl` 覆盖不管,那部分流量仍走公网原厂,不靠
|
|
1527
|
+
`src/trace/redact.ts` 这个脱敏器——它自述「不是穷举的」,即便是穷举的也只管持久化/展示这一面(且它同时
|
|
1528
|
+
被项目记忆注入、hook asyncRewake 唤醒文本这两条**反方向**调用复用——那两条是"先脱敏、脱敏结果才给模型
|
|
1529
|
+
看",与工具结果这一条方向相反,验法时别混)。
|
|
1530
|
+
|
|
1531
|
+
**怎么验(运维口令)**:用自控转发代理截获**每一条生效路由**的出站体核对——不能只把 `MODEL_GATEWAY_BASEURL`
|
|
1532
|
+
指向自己起的转发代理,还要按部署实际配置的路由数量逐条起代理(默认网关一个 + 每条备用网关一个 +
|
|
1533
|
+
`ANTHROPIC_BASEURL` 一个,如果 `provider:"anthropic"` 在用);代理记录经过的每个请求体后原样转发给真
|
|
1534
|
+
网关,跑一条会产出敏感格式(如一个明文 token)的工具结果,比对代理记下的出站 `messages[]` 里
|
|
1535
|
+
`role:"tool"` 的内容与该 run 的 trace/SSE 拷贝:出站体应为**原文**,trace 拷贝应为 `«redacted…»`。两者
|
|
1536
|
+
不同,即验证「脱敏只发生在持久化/展示面,不发生在出站面」这句话为真——**若代理本身继续把请求转发到外部
|
|
1537
|
+
模型服务,即便截获成功,也不满足「内容不出机器」这个更高的目标,截获只证明了脱敏的射程,不证明数据没有
|
|
1538
|
+
离开你控制的边界**。
|
package/dist/http/routes/runs.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { persistedPlaneOf } from "../../terminal.js";
|
|
2
2
|
import { memoRedactedRead, memoRedactTerminalResult, redactErrorMessage, redactHead, redactLedgerEventData, redactTerminalResult } from "../../observability/run-terminal-log.js";
|
|
3
|
+
import { isModelTextLedgerEventType } from "../../trace/ledger-events.js";
|
|
3
4
|
import { z } from "zod";
|
|
4
5
|
import { uuidv7, mintCheckpointToken, CheckpointError, validatePendingSteer } from "@sema-agent/core";
|
|
5
6
|
import { isTerminalRunStatus, isParkedRunStatus } from "../../plugins/store-contracts.js";
|
|
@@ -139,7 +140,9 @@ async function streamRunEvents(req, res, deps, runStore, taskId, staleMs) {
|
|
|
139
140
|
const type = normalizeRunEventType(ev.type);
|
|
140
141
|
const redacted = type === "done" && ev.data && typeof ev.data === "object" && ev.data.result && typeof ev.data.result === "object"
|
|
141
142
|
? { ...ev.data, result: memoRedactTerminalResult("ev.done.result", ev.data.result) }
|
|
142
|
-
: type === "failed" ? memoRedactedRead("ev.failed", ev.data, (d) => redactLedgerEventData(type, d))
|
|
143
|
+
: type === "failed" ? memoRedactedRead("ev.failed", ev.data, (d) => redactLedgerEventData(type, d))
|
|
144
|
+
: isModelTextLedgerEventType(type) ? memoRedactedRead("ev.model-text", ev.data, (d) => redactLedgerEventData(type, d))
|
|
145
|
+
: redactLedgerEventData(type, ev.data);
|
|
143
146
|
const data = screenedStoredEventData(type, redacted);
|
|
144
147
|
return { id: ev.seq, event: type, data: { type, ...(data ?? {}) } };
|
|
145
148
|
},
|
package/dist/http/server.js
CHANGED
|
@@ -33,7 +33,7 @@ import { TERMINAL_PARK_GATE_KINDS } from "../plugins/checkpoint-store-sql.js";
|
|
|
33
33
|
import { createWallClockJumpGuard } from "../wall-clock-jump-guard.js";
|
|
34
34
|
import {} from "../observability/rate-limit.js";
|
|
35
35
|
import { withPrincipal } from "../observability/principal-context.js";
|
|
36
|
-
import { turnEndEventData, contextUsageEventData, toolStartEventData, toolEndEventData, taskProgressEventData, taskNotificationEventData, compactedEventData, diagnosticsEventData, brainStatusEventData, steeringInjectedEventData, compactionOutcomeEventData, workspaceChangedEventData, wiringManifestEventData, toolRosterDeltaEventData, humanInputEventData, appendModelUsageDelta, appendPromptManifest, attachModelUsage } from "../trace/project.js";
|
|
36
|
+
import { modelTextEventData, turnEndEventData, contextUsageEventData, toolStartEventData, toolEndEventData, taskProgressEventData, taskNotificationEventData, compactedEventData, diagnosticsEventData, brainStatusEventData, steeringInjectedEventData, compactionOutcomeEventData, workspaceChangedEventData, wiringManifestEventData, toolRosterDeltaEventData, humanInputEventData, appendModelUsageDelta, appendPromptManifest, attachModelUsage } from "../trace/project.js";
|
|
37
37
|
import { redactSecrets } from "../trace/redact.js";
|
|
38
38
|
import { createLedgerSink, createSerialLedgerAppend } from "../trace/ledger-sink.js";
|
|
39
39
|
import { registerEngineNoticeLeg } from "../trace/engine-notice-wire.js";
|
|
@@ -1561,11 +1561,11 @@ export function createHttpServer(rawDeps) {
|
|
|
1561
1561
|
const anchor = new TurnAnchorCapture(captureTurnAnchor, () => deps.metrics?.inc("resume_anchor_capture_failed"), captureUserMessageAnchor);
|
|
1562
1562
|
const flush = async () => {
|
|
1563
1563
|
if (reasoning) {
|
|
1564
|
-
await append("reasoning", { text:
|
|
1564
|
+
await append("reasoning", modelTextEventData({ text: reasoning }));
|
|
1565
1565
|
reasoning = "";
|
|
1566
1566
|
}
|
|
1567
1567
|
if (text) {
|
|
1568
|
-
await append("text", { text,
|
|
1568
|
+
await append("text", modelTextEventData({ text, eventId: anchor.firstTextEventId }));
|
|
1569
1569
|
text = "";
|
|
1570
1570
|
}
|
|
1571
1571
|
};
|
|
@@ -54,6 +54,34 @@ export declare const REDACT_INPUT_CAP = 65536;
|
|
|
54
54
|
* 脱敏**之后**按字节封顶(占位已替换完,切在占位中间不是泄漏)并带标记。 */
|
|
55
55
|
export declare const REDACT_OUTPUT_MAX_BYTES = 65000;
|
|
56
56
|
export declare function redactErrorMessage(raw: string | undefined): string | undefined;
|
|
57
|
+
/**
|
|
58
|
+
* 🔴 **模型自由文本的唯一装载口**(B-106 / S-268,7.75.1)——`TaskResult.result` / `TaskResult.salvagedOutput`
|
|
59
|
+
* 与账本的 `text` / `reasoning` 两行,四座同调本函数。
|
|
60
|
+
*
|
|
61
|
+
* 它**不是第二只脱敏器**:零自有规则表、零自有词表,判「什么算凭据」的仍然只有 {@link redactSecrets} 一只。
|
|
62
|
+
* 本函数只定两件**写回**的事,而这两件事在三条既有纪律面前各只有一个正确答案:
|
|
63
|
+
*
|
|
64
|
+
* ① **认出凭据形才改写字节;没认出就回原串**(codex 对抗复审 r1 [medium],亲核实测后修)。
|
|
65
|
+
* `redactSecrets` 自 S-103 起**无条件先剥** `\p{Cf}`,所以 `redactSecrets(x) !== x` 对任何含 Cf 的文本都为真 ——
|
|
66
|
+
* 实测:`👨👩👧`(ZWJ 家庭 emoji)被拆成三个独立 emoji、波斯语 `میخواهم`(ZWNJ)被粘成 `میخواهم`。
|
|
67
|
+
* 诊断文本上这是成文代价(那一族本就要「先扫描后渲染」);**用户最终答复**上它是每一条含 ZWJ/ZWNJ 的
|
|
68
|
+
* 正常答案被永久改坏,而本件对外的承诺逐字是「不含凭据形的文本逐字节不变」。判据用的是本模块**自己成文的
|
|
69
|
+
* 那一条**(`redact.ts` 的 `normalizeForRedaction` 顶注:「正确的谓词是 `redactSecrets(x) !== normalizeForRedaction(x)`」),
|
|
70
|
+
* 既有消费方 `tool-approval.ts` 的规则车道闸用的也是它 —— 一条谓词两处用,不是新规矩。
|
|
71
|
+
* ⚠️ **命中那一次仍然给剥后文本**(S-103 的「先扫描后渲染」不动摇):把零宽字符塞进密钥中间的那条文本,
|
|
72
|
+
* 渲染的必须是剥后那份,否则消费端把残片拼回原钥。
|
|
73
|
+
* ② **扫描器抛 ⇒ 登记过的占位(fail-closed),不把整条 run 拖下水**(codex r1 [high],亲核实测后修)。
|
|
74
|
+
* `redactSecrets` 的 JSON 秘密字段臂仍含多字符循环体(S-106 既有未闭:`(?:\\.[^"\\]*)*`),实测
|
|
75
|
+
* `{"password":"<400 万个 \\n 转义对>"}`(8MB)抛 `RangeError`。`result` 接进来之前这条路不存在;接进来之后,
|
|
76
|
+
* 裸调用会让异常穿过 `redactTerminalResult` → 三孪生 `setTerminal`,**一条跑成功的 run 落成 failed + result:null**
|
|
77
|
+
* (实测读数)。⇒ 与本文件既有的 {@link redactErrorMessage} / `redactDiagnosticText` / `redactArtifactText`
|
|
78
|
+
* 三座**同一条**处置:抛就换占位、计数、留痕([ref] 的 loud,不是静默 fail-open)。丢的是这一条病态文本,
|
|
79
|
+
* 不是这条 run。根因(那条正则)是 S-106 的独立车,本函数不在复审轮里改它。
|
|
80
|
+
* ③ **不设体量上限**(裁定):`result` 住在 `result` JSON/JSONB 列(亲核 `plugins/tidb-pool.ts` 与
|
|
81
|
+
* `plugins/pg-pool.ts` 的 DDL),数 MB 终稿是正常形 —— {@link redactErrorMessage} 的两道上限是为
|
|
82
|
+
* `task_run.error` 那一列 `TEXT(65 535 字节)` 定的,套到这里等于用静默的数据损坏换一次脱敏。
|
|
83
|
+
*/
|
|
84
|
+
export declare function redactModelText(raw: string): string;
|
|
57
85
|
/** {@link redactTerminalResult} 会碰的全部顶层键 = 现行形的 `"redacted"` 行 ∪ 旧形平面键;
|
|
58
86
|
* `memoRedactTerminalResult` 的记忆键由它派生(两处同源,见 `RESULT_KEY_TREATMENT` 顶注)。 */
|
|
59
87
|
export declare const REDACTABLE_RESULT_LEAVES: readonly string[];
|
|
@@ -66,6 +94,10 @@ export declare function redactTerminalResult<T extends {
|
|
|
66
94
|
remoteEnvFailures?: readonly {
|
|
67
95
|
message?: string | undefined;
|
|
68
96
|
}[] | undefined;
|
|
97
|
+
/** B-106:模型自由文本两叶 + 引擎披露散文一族。形上同样取 `unknown`(读面纪律,见下)。 */
|
|
98
|
+
result?: unknown;
|
|
99
|
+
salvagedOutput?: unknown;
|
|
100
|
+
rewindNotes?: unknown;
|
|
69
101
|
/** 🔴 形上**故意**是 `unknown`:本函数也是**读面**(旧持久 blob / 非 core Runner 的形不可信),
|
|
70
102
|
* 声明成 `{path,edits}[]` 会逼每一个喂坏形的调用点铸一次宽松断言 —— 而「坏形从哪来」正是本座要
|
|
71
103
|
* 处理的事(同一份不信任在 `remoteEnvFailures` 的实现里也已成文)。运行期逐项判形,不抛。 */
|
|
@@ -121,12 +153,18 @@ export declare function redactLeaderResultWithFlag(result: unknown): {
|
|
|
121
153
|
changed: boolean;
|
|
122
154
|
};
|
|
123
155
|
export declare function memoRedactedRead<T>(seat: string, raw: T, redact: (v: T) => T): T;
|
|
124
|
-
/** run 终态 result 的读面记忆只按**可脱敏的那几叶**(
|
|
125
|
-
* `
|
|
126
|
-
* 第十六轮重扫:按整块 result 摘要每次轮询要 stringify 一份可能数 MB 的 result,且大 result
|
|
156
|
+
/** run 终态 result 的读面记忆只按**可脱敏的那几叶**({@link REDACTABLE_RESULT_LEAVES},属主是
|
|
157
|
+
* `RESULT_KEY_TREATMENT`)做键与留体——
|
|
158
|
+
* 第十六轮重扫:按整块 result 摘要每次轮询要 stringify 一份可能数 MB 的 result,且大 result 的脏行永远存不进去。叶全缺席
|
|
127
159
|
* ⇒ O(1) 直返(`redactTerminalResult` 本就同引用)。
|
|
128
160
|
* 🔴 S-136(core 7.6.0 D-8):新形把前两片搬进了 `terminal`(同属一个键,按它整只做叶更省);旧形的两片
|
|
129
|
-
* **照留**——盘上真有那样的行,而读面纵深正是为它们存在的(codex 对抗复审 r1 [high] 验真后补)。
|
|
161
|
+
* **照留**——盘上真有那样的行,而读面纵深正是为它们存在的(codex 对抗复审 r1 [high] 验真后补)。
|
|
162
|
+
* 🔴 **B-106 起「叶全缺席」这条捷径实际上不再触发**(如实写明,不是漏改):`result` 在 core 类型上是
|
|
163
|
+
* **必填**串,进了叶集就恒在场 ⇒ 每次读面都要对叶子做一次 sha256。代价是有意接受的,因为省下的那条
|
|
164
|
+
* 捷径对**含自由文本的 run(即全部 run)**本来就不成立 —— 不把 `result` 放进叶集才是真的漏:
|
|
165
|
+
* `memoRedactTerminalResult` 会对它 O(1) 直返明文,读面纵深对这一叶恒不生效(这正是 [ref] 立案时
|
|
166
|
+
* `editedFiles` 那一格的病)。读数:8MB result 的一次摘要 ~10ms 量级,远小于一次全扫;干净行仍只摘要
|
|
167
|
+
* 一次即进 clean 集,脏行超过 {@link READ_MEMO_DIRTY_MAX_ENTRY_BYTES} 时退化为每次重扫(既有形)。 */
|
|
130
168
|
export declare function memoRedactTerminalResult<T extends Parameters<typeof redactTerminalResult>[0]>(seat: string, result: T): T;
|
|
131
169
|
export declare function diffScanShadowCountForTest(): number;
|
|
132
170
|
export declare function resetDiffScanShadowCountForTest(): void;
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { createHash } from "node:crypto";
|
|
2
|
-
import { redactSecrets } from "../trace/redact.js";
|
|
3
|
-
import { isParkLedgerEventType } from "../trace/ledger-events.js";
|
|
2
|
+
import { redactSecrets, normalizeForRedaction } from "../trace/redact.js";
|
|
3
|
+
import { isModelTextLedgerEventType, isParkLedgerEventType } from "../trace/ledger-events.js";
|
|
4
4
|
import { isUnsuccessfulRunStatus } from "../plugins/store-contracts.js";
|
|
5
5
|
import { errorText } from "./err-text.js";
|
|
6
6
|
import { recordFailOpen } from "./fail-open.js";
|
|
@@ -64,6 +64,20 @@ export function redactErrorMessage(raw) {
|
|
|
64
64
|
return OVERSIZED_REDACTION_PLACEHOLDER;
|
|
65
65
|
}
|
|
66
66
|
}
|
|
67
|
+
export function redactModelText(raw) {
|
|
68
|
+
try {
|
|
69
|
+
const out = redactSecrets(raw);
|
|
70
|
+
if (out === raw)
|
|
71
|
+
return raw;
|
|
72
|
+
if (out === normalizeForRedaction(raw))
|
|
73
|
+
return raw;
|
|
74
|
+
return out;
|
|
75
|
+
}
|
|
76
|
+
catch (err) {
|
|
77
|
+
recordFailOpen("server.run-terminal.redact-threw", `model-text len=${raw.length} err=${errorText(err)}`);
|
|
78
|
+
return OVERSIZED_REDACTION_PLACEHOLDER;
|
|
79
|
+
}
|
|
80
|
+
}
|
|
67
81
|
const MAX_EDITED_FILES = 1000;
|
|
68
82
|
const RESULT_KEY_TREATMENT = {
|
|
69
83
|
terminal: "redacted",
|
|
@@ -74,13 +88,13 @@ const RESULT_KEY_TREATMENT = {
|
|
|
74
88
|
sessionId: "verbatim",
|
|
75
89
|
model: "verbatim",
|
|
76
90
|
effectiveReasoning: "verbatim",
|
|
77
|
-
result: "
|
|
78
|
-
salvagedOutput: "
|
|
91
|
+
result: "redacted",
|
|
92
|
+
salvagedOutput: "redacted",
|
|
79
93
|
apiFailure: "verbatim",
|
|
80
94
|
retryAfterMs: "verbatim",
|
|
81
95
|
degraded: "verbatim",
|
|
82
96
|
structuredOutput: "verbatim",
|
|
83
|
-
rewindNotes: "
|
|
97
|
+
rewindNotes: "redacted",
|
|
84
98
|
haltedOnUserRejection: "verbatim",
|
|
85
99
|
haltedByUser: "verbatim",
|
|
86
100
|
strandedHumanAnswers: "verbatim",
|
|
@@ -167,6 +181,31 @@ export function redactTerminalResult(result) {
|
|
|
167
181
|
: r.editedFiles;
|
|
168
182
|
changed = true;
|
|
169
183
|
}
|
|
184
|
+
if (typeof r.result === "string") {
|
|
185
|
+
const next = redactModelText(r.result);
|
|
186
|
+
if (next !== r.result) {
|
|
187
|
+
out.result = next;
|
|
188
|
+
changed = true;
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
if (typeof r.salvagedOutput === "string") {
|
|
192
|
+
const next = redactModelText(r.salvagedOutput);
|
|
193
|
+
if (next !== r.salvagedOutput) {
|
|
194
|
+
out.salvagedOutput = next;
|
|
195
|
+
changed = true;
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
if (r.rewindNotes !== undefined) {
|
|
199
|
+
out.rewindNotes = Array.isArray(r.rewindNotes)
|
|
200
|
+
? r.rewindNotes.map((n) => {
|
|
201
|
+
if (n === null || typeof n !== "object")
|
|
202
|
+
return n;
|
|
203
|
+
const message = n.message;
|
|
204
|
+
return typeof message === "string" ? { ...n, message: redactErrorMessage(message) } : n;
|
|
205
|
+
})
|
|
206
|
+
: r.rewindNotes;
|
|
207
|
+
changed = true;
|
|
208
|
+
}
|
|
170
209
|
return changed ? out : result;
|
|
171
210
|
}
|
|
172
211
|
export function redactLedgerEventData(type, data) {
|
|
@@ -184,6 +223,13 @@ export function redactLedgerEventData(type, data) {
|
|
|
184
223
|
return data;
|
|
185
224
|
return { ...data, errorMessage: redactErrorMessage(msg) };
|
|
186
225
|
}
|
|
226
|
+
if (isModelTextLedgerEventType(type)) {
|
|
227
|
+
const text = data.text;
|
|
228
|
+
if (typeof text !== "string")
|
|
229
|
+
return data;
|
|
230
|
+
const next = redactModelText(text);
|
|
231
|
+
return next === text ? data : { ...data, text: next };
|
|
232
|
+
}
|
|
187
233
|
if (isParkLedgerEventType(type)) {
|
|
188
234
|
const gate = data.gate;
|
|
189
235
|
const next = redactCheckpointGate(gate);
|
|
@@ -77,6 +77,18 @@ export type LedgerEventType = "reasoning" | "text" | "tool_start" | "tool_end" |
|
|
|
77
77
|
*/
|
|
78
78
|
export declare const PARK_LEDGER_EVENT_TYPES: readonly ["suspended", "needs_review"];
|
|
79
79
|
export type ParkLedgerEventType = (typeof PARK_LEDGER_EVENT_TYPES)[number];
|
|
80
|
+
/**
|
|
81
|
+
* 🔴 **模型自由文本行名的运行时闭集**(B-106 / S-268)—— 与 {@link PARK_LEDGER_EVENT_TYPES} 同一条理由、
|
|
82
|
+
* 同一种锁步形。
|
|
83
|
+
*
|
|
84
|
+
* 这两行(`text` = 模型写给用户的文本,`reasoning` = 同一支笔的 CoT)是账本上**唯二**逐字携带模型散文的型,
|
|
85
|
+
* 所以脱敏臂必须按词分派;把词表留在脱敏臂里手抄,就会重演 park 那一案:「仓里加第三个自由文本行名并登记进
|
|
86
|
+
* `LedgerEventType`,写口照写照落,回放时明文,脱敏臂一声不吭」。
|
|
87
|
+
*/
|
|
88
|
+
export declare const MODEL_TEXT_LEDGER_EVENT_TYPES: readonly ["text", "reasoning"];
|
|
89
|
+
export type ModelTextLedgerEventType = (typeof MODEL_TEXT_LEDGER_EVENT_TYPES)[number];
|
|
90
|
+
/** 模型自由文本行名的**收窄谓词**(与 park 谓词同形)。 */
|
|
91
|
+
export declare function isModelTextLedgerEventType(v: string): v is ModelTextLedgerEventType;
|
|
80
92
|
/** park 行名的**收窄谓词**(消费点要的是「这个词是不是 park 词」+ 类型收窄,不是一次裸 includes)。 */
|
|
81
93
|
export declare function isParkLedgerEventType(v: string): v is ParkLedgerEventType;
|
|
82
94
|
//# sourceMappingURL=ledger-events.d.ts.map
|
|
@@ -1,6 +1,12 @@
|
|
|
1
1
|
export const PARK_LEDGER_EVENT_TYPES = ["suspended", "needs_review"];
|
|
2
2
|
const _parkWordsAreLedgerTypes = true;
|
|
3
3
|
void _parkWordsAreLedgerTypes;
|
|
4
|
+
export const MODEL_TEXT_LEDGER_EVENT_TYPES = ["text", "reasoning"];
|
|
5
|
+
const _modelTextWordsAreLedgerTypes = true;
|
|
6
|
+
void _modelTextWordsAreLedgerTypes;
|
|
7
|
+
export function isModelTextLedgerEventType(v) {
|
|
8
|
+
return MODEL_TEXT_LEDGER_EVENT_TYPES.includes(v);
|
|
9
|
+
}
|
|
4
10
|
export function isParkLedgerEventType(v) {
|
|
5
11
|
return PARK_LEDGER_EVENT_TYPES.includes(v);
|
|
6
12
|
}
|
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
import { terminalProjection } from "@sema-agent/core";
|
|
2
|
-
import {
|
|
3
|
-
import { toolStartEventData, toolEndEventData, taskProgressEventData, taskNotificationEventData, compactedEventData, diagnosticsEventData, brainStatusEventData, steeringInjectedEventData, compactionOutcomeEventData, workspaceChangedEventData, wiringManifestEventData, toolRosterDeltaEventData, humanInputEventData, turnEndEventData, contextUsageEventData } from "./project.js";
|
|
2
|
+
import { modelTextEventData, toolStartEventData, toolEndEventData, taskProgressEventData, taskNotificationEventData, compactedEventData, diagnosticsEventData, brainStatusEventData, steeringInjectedEventData, compactionOutcomeEventData, workspaceChangedEventData, wiringManifestEventData, toolRosterDeltaEventData, humanInputEventData, turnEndEventData, contextUsageEventData } from "./project.js";
|
|
4
3
|
import { NotifiedKeys, taskNotificationStreamKey } from "../orchestration/workflow-completion-inbox.js";
|
|
5
4
|
import { isParkLedgerEventType } from "./ledger-events.js";
|
|
6
5
|
export function createSerialLedgerAppend(appendEvent, startSeq) {
|
|
@@ -34,11 +33,11 @@ export function createLedgerSink(opts) {
|
|
|
34
33
|
};
|
|
35
34
|
const flush = async () => {
|
|
36
35
|
if (reasoning) {
|
|
37
|
-
await append("reasoning", { text:
|
|
36
|
+
await append("reasoning", modelTextEventData({ text: reasoning }));
|
|
38
37
|
reasoning = "";
|
|
39
38
|
}
|
|
40
39
|
if (text) {
|
|
41
|
-
await append("text", { text,
|
|
40
|
+
await append("text", modelTextEventData({ text, eventId: textEventId }));
|
|
42
41
|
text = "";
|
|
43
42
|
}
|
|
44
43
|
};
|
package/dist/trace/project.d.ts
CHANGED
|
@@ -107,6 +107,28 @@ export type TraceBlock = {
|
|
|
107
107
|
sections?: unknown;
|
|
108
108
|
tools?: unknown;
|
|
109
109
|
};
|
|
110
|
+
/**
|
|
111
|
+
* 🔴 **模型自由文本账本行(`text` / `reasoning`)的唯一载荷铸点**(B-106 / S-268,7.75.1)。
|
|
112
|
+
*
|
|
113
|
+
* ## 为什么是一只函数,而不是两处各写一句
|
|
114
|
+
* 这两行的载荷此前由**两条腿各自手拼**:`trace/ledger-sink.ts` 的 turn flush 与 `http/server.ts` 的
|
|
115
|
+
* durable resume 腿 flush。手拼的代价本文件已经付过一次:{@link toolEndEventData} 顶注逐字记着 `label`
|
|
116
|
+
* 那一案 —— 七个调用点只有三个记得拼,**漏的正是 resume 腿与 ledger-sink 落盘这两处**。
|
|
117
|
+
*
|
|
118
|
+
* ## 🔴 脱敏**不在这里**(写口不是脱敏属主)
|
|
119
|
+
* 两行的字节由模型写,必须脱 —— 但脱敏口是 `observability/run-terminal-log.ts` 的
|
|
120
|
+
* `redactLedgerEventData`:三孪生 store 的 `appendEvent` 落库前 + `GET /v1/runs/:id/events` 读面各过一次
|
|
121
|
+
* (幂等)。理由与 park 行逐字同源(`trace/ledger-sink.ts` 的 `appendParked` 顶注是那条规矩的属主):
|
|
122
|
+
* 写口脱 = 一个语义面上两个写者,且**盖不住存量行**(升级前落下的明文 `text` 行每次重连照样回放)。
|
|
123
|
+
* 所以本函数只管**形**:哪些键、可选键什么时候在场。
|
|
124
|
+
*
|
|
125
|
+
* `eventId` = E18 的 per-message resume 句柄,只有 `text` 行带(`reasoning` 行的调用点不传)——
|
|
126
|
+
* 条件展开写成直写字面量([ref] 门① 的硬形:键名不许对扫描器隐形)。
|
|
127
|
+
*/
|
|
128
|
+
export declare function modelTextEventData(ev: {
|
|
129
|
+
text: string;
|
|
130
|
+
eventId?: string | undefined;
|
|
131
|
+
}): Record<string, unknown>;
|
|
110
132
|
/** The durable `tool_start` event payload, shared by BOTH append sites (runInBackground + the resume leg) so the
|
|
111
133
|
* persisted shape never drifts: redacted args (UNTRUSTED) + E2 identity. `label`([ref]§三族B:durable 腿
|
|
112
134
|
* 此前剥掉——同一 run live 观看有展示名、replay/resume 退化成 wire 名):core 展示名 verbatim(tail 面
|
package/dist/trace/project.js
CHANGED
|
@@ -82,6 +82,9 @@ function identityFields(src) {
|
|
|
82
82
|
...(src.parentToolCallId ? { parentToolCallId: String(src.parentToolCallId) } : {}),
|
|
83
83
|
};
|
|
84
84
|
}
|
|
85
|
+
export function modelTextEventData(ev) {
|
|
86
|
+
return { text: ev.text, ...(ev.eventId ? { eventId: ev.eventId } : {}) };
|
|
87
|
+
}
|
|
85
88
|
export function toolStartEventData(ev) {
|
|
86
89
|
return { toolCallId: ev.toolCallId, toolName: ev.toolName, ...(ev.label !== undefined ? { label: ev.label } : {}), args: redactDeep(ev.args), ...identityFields(ev) };
|
|
87
90
|
}
|
package/dist/trace/redact.d.ts
CHANGED
|
@@ -8,6 +8,26 @@
|
|
|
8
8
|
* tolerated — over-redacting trace text is safe). For sensitive deployments, disable thinking capture entirely
|
|
9
9
|
* via TRACE_THINKING=false. This is a seam — deployments can extend the patterns.
|
|
10
10
|
*
|
|
11
|
+
* 🔴 **脱敏 ≠ 出站控制**:**工具结果送模主路径**不经过本模块 —— 发往模型的请求体是工具结果**原文**
|
|
12
|
+
* (模型作为执行体必须见原文;出站改写等于静默改坏用户的真实数据)。⚠️ 这句边界只管这一条路径,别读成
|
|
13
|
+
* 「本模块从不出现在模型输入路径上」——本模块同时被两条**反方向**调用复用:项目记忆片段注入
|
|
14
|
+
* (`src/project-memory.ts`,git log/status 与指令文件摘录先脱敏、脱敏结果才拼进注入文本)与 hook
|
|
15
|
+
* asyncRewake 的唤醒文本(`src/boot/resolve-spec.ts` 的 `wake` 回调,先脱敏再经 `stream.steer` 注入模型)。
|
|
16
|
+
* 这两条路径是「先脱敏、脱敏后的结果才给模型看」,与工具结果路径的方向相反,不要混淆。要让内容不出机器,
|
|
17
|
+
* 核对的是**该 run 实际选中的模型路由**——默认网关(`MODEL_GATEWAY_BASEURL`)只是其中一条:还有备用网关
|
|
18
|
+
* (`MODEL_GATEWAY_FALLBACK_URLS`)、`provider:"anthropic"` 的独立直连路由(缺省根
|
|
19
|
+
* `https://api.anthropic.com`,除非显式配 `ANTHROPIC_BASEURL`)、以及配置中心可对单个目录模型下发的
|
|
20
|
+
* per-model `baseUrl` 覆盖(见 `src/brain.ts` 的路由装配与 `src/config-center/apply-effective.ts:416`)。
|
|
21
|
+
* 靠**读侧**敏感路径门与确保**全部**生效路由都指向自己控制的基础设施,不靠本模块、也不是设一根 env 就够。
|
|
22
|
+
*
|
|
23
|
+
* 🔴 **射程自 7.75.1 起含「模型自由文本」全族**(B-106 / S-268):本模块此前在结果面只管诊断/披露文本,
|
|
24
|
+
* 而**模型自己写的**那一份(`TaskResult.result` / `TaskResult.salvagedOutput` / 账本 `text` 行)整条逐字
|
|
25
|
+
* 上 wire —— 工具结果送模路径给的是原文,模型把里面的凭据**逐字引用**进答复,那一份就绕过了本模块。
|
|
26
|
+
* 现在三面与 CoT(账本 `reasoning` 行)走**同一只** `redactSecrets`、同一张替换串表:零第二只脱敏器、
|
|
27
|
+
* 零 per-face 开关、零体量上限。**装载口只有一只** —— `observability/run-terminal-log.ts` 的
|
|
28
|
+
* `redactModelText`(四座同调:结果两叶经 `redactTerminalResult`,账本两行经 `redactLedgerEventData`);
|
|
29
|
+
* 它定的是**写回**判据(认出凭据形才改字节、抛则占位),不是第二张规则表。
|
|
30
|
+
*
|
|
11
31
|
* 两组臂,顺序即优先序:{@link FULL_SECRET_PATTERNS}(完整形)在前,{@link FRAGMENT_SECRET_PATTERNS}
|
|
12
32
|
* ([ref] 前缀残片,上游先截后脱的产物 ⇒ 带 `:partial` 后缀的 token)在后。
|
|
13
33
|
*/
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@sema-agent/server",
|
|
3
|
-
"version": "7.75.
|
|
3
|
+
"version": "7.75.1",
|
|
4
4
|
"description": "Sema Server — the server/API implementation layer for Sema, wiring core, registry, model providers, and cloud agent execution. Built on @sema-agent/core.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "BUSL-1.1",
|
|
@@ -54,7 +54,7 @@
|
|
|
54
54
|
"build:binary:run-local:darwin-arm64": "bun build --compile --target=bun-darwin-arm64 src/run-local.ts --outfile dist/run-local-darwin-arm64"
|
|
55
55
|
},
|
|
56
56
|
"dependencies": {
|
|
57
|
-
"@sema-agent/core": "7.17.
|
|
57
|
+
"@sema-agent/core": "7.17.4",
|
|
58
58
|
"@sema-agent/settings-schema": "1.10.0",
|
|
59
59
|
"e2b": "^2.28.0",
|
|
60
60
|
"libsodium-wrappers": "^0.8.4",
|
|
@@ -69,7 +69,7 @@
|
|
|
69
69
|
"sharp": "^0.35.3"
|
|
70
70
|
},
|
|
71
71
|
"devDependencies": {
|
|
72
|
-
"@sema-agent/sdk": "9.
|
|
72
|
+
"@sema-agent/sdk": "9.3.0",
|
|
73
73
|
"@types/libsodium-wrappers": "^0.7.14",
|
|
74
74
|
"@types/node": "22.10.2",
|
|
75
75
|
"@types/pg": "^8.20.0",
|