@sema-agent/server 7.74.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.
@@ -9,8 +9,8 @@
9
9
  * 行永 parked、生命周期分叉)。
10
10
  */
11
11
  import type { HostDecision } from "./host-decision.js";
12
- import type { BackgroundAgentRecord, BackgroundAgentStore, CheckpointStore, QuestionAnswer, ResumeOutcome, TaskRegistry, ToolSpec, WorkflowAgentRun, WorkflowRun, WorkflowRunStore } from "@sema-agent/core";
13
- import { type ParkIdentityLoss, type WorkflowAgentSessionIndex } from "./orchestration/workflow-agent-session-index.js";
12
+ import type { BackgroundAgentRecord, BackgroundAgentStore, CheckpointStore, QuestionAnswer, ResumeOutcome, TaskRegistry, ToolSpec, WorkflowRun, WorkflowRunStore } from "@sema-agent/core";
13
+ import { type WorkflowRunParkKey, type WorkflowAgentSessionIndex } from "./orchestration/workflow-agent-session-index.js";
14
14
  export interface ParkedAgentMatch {
15
15
  handle: string;
16
16
  row: BackgroundAgentRecord;
@@ -40,37 +40,37 @@ export type ParkOrigin = {
40
40
  handle: string;
41
41
  row: BackgroundAgentRecord;
42
42
  }
43
- /** `agent` 是**权威实体**(从 run 的 blob 里按赎回键复核出来的那一行);身份位一律从它读 —— 索引行
44
- * 是可被改盘的派生投影,不做第二个身份写者(7.69.0 合并重扫 c1 同族收口)。 */
43
+ /** `park` 是**权威条目**(从 run 记录的真源 `parks[]` 里按赎回键复核出来的那一条);身份位一律从它读
44
+ * —— 索引行是可被改盘的派生投影,不做第二个身份写者(7.69.0 合并重扫 c1 同族收口)。 */
45
45
  | {
46
46
  kind: "workflow";
47
47
  runId: string;
48
48
  run: WorkflowRun;
49
- agent: WorkflowAgentRun;
49
+ park: WorkflowRunParkKey;
50
50
  }
51
51
  /**
52
- * S-192 —— **恢复身份被店的字节上界裁掉**的那条 park(第四种出身,不是第四条车道)。
52
+ * S-252 —— **这条记录的 park 真源读不出来**(第四种出身,不是第四条车道)。
53
53
  *
54
- * 索引行还在(它由**内存里的整只 run** 派生,店内的瘦身它看不见),run 行也还在,但 blob 里已经没有
55
- * 那一行 —— 而 blob 里的 `parkedCheckpointToken` 是 server 侧**复核**「这条 park 仍在此 token 下」的唯一
56
- * 依据(引擎自己的那份赎回键住在 resume journal 的 parked 条目里,resume 时从那里取,见
57
- * {@link denyWorkflowParkIdentityLost} 顶注亲读的 core 坐标)。此前这一格与「索引落后一拍 / 行已在新
58
- * token 下 re-park」两种**正常** miss 完全同形,于是落 legacy 腿拿一个错因 409。
59
- * 现在店在裁掉它的同时落一个闭集标记({@link readParkIdentityLoss}),判别式据它把这一格分出来,
60
- * 三个消费点各自响亮处置(decide 拒 + 不驱动宿主续跑;SLA sweep 照旧静默跳过——它同样复核不了)。
54
+ * 索引行还在、run 行也还在,但记录上没有一份读得出来的 `parks[]`:旧引擎(core < 7.17.0)写下的存量行
55
+ * 根本没有这一位,或者某一条条目的形坏了。而这一位是**引擎自己**的 park 责任表 —— core 的 resume 准入
56
+ * 读不到它就整条拒(`workflow.park_truth_unreadable` / `parks_unreadable`,亲读安装树
57
+ * `dist/orchestration/workflow.js:2217-2218`;条目形坏 ⇒ `park_binding_broken`,`:2222-2224`)。
58
+ * ⇒ 这条决定**结构上**送不出去:答 200「已交给宿主的下一跑」就是一张兑付不了的回执。
59
+ * 此前这一格与「索引落后一拍 / 这条 run 已不再负责它」两种**正常** miss 完全同形,于是落 legacy 腿拿
60
+ * 一个错因 409。判别式据真源的**可读性**把这一格分出来,三个消费点各自响亮处置(decide 拒 + 不驱动
61
+ * 宿主续跑;SLA sweep 照旧静默跳过——它同样复核不了)。
61
62
  */
62
63
  | {
63
- kind: "workflow_identity_lost";
64
+ kind: "workflow_parks_unreadable";
64
65
  runId: string;
65
66
  run: WorkflowRun;
66
- loss: ParkIdentityLoss;
67
67
  };
68
68
  /** 判别式的两个源。各自可缺席(部署没接就是没接)—— 缺席 ⇒ 那条臂不判,不是失败。 */
69
69
  export interface ParkOriginSources {
70
70
  agentStore?: BackgroundAgentStore;
71
71
  /** S-185:workflow 出身的**有界** join 索引(契约见 orchestration/workflow-agent-session-index.ts)。 */
72
72
  workflowIndex?: WorkflowAgentSessionIndex;
73
- /** 索引只回答「去哪条 run 找」,行本体(含赎回键)仍以 run 的 blob 为准 ⇒ 两件必须成对在场。 */
73
+ /** 索引只回答「去哪条 run 找」,条目本体(含赎回键)仍以 run 记录的 `parks[]` 为准 ⇒ 两件必须成对在场。 */
74
74
  workflowRunStore?: Pick<WorkflowRunStore, "get">;
75
75
  warn?: (event: string, fields: Record<string, unknown>) => void;
76
76
  }
@@ -226,26 +226,23 @@ export type WorkflowParkedDecidePlan = {
226
226
  deny: ParkedDecideResult;
227
227
  };
228
228
  /**
229
- * S-192 —— **恢复身份已被裁掉的 workflow park 的响亮拒**(与上面那族拒臂同一种返回形)。
229
+ * S-252 —— **park 真源读不出来的那条记录的响亮拒**(与上面那族拒臂同一种返回形)。
230
230
  *
231
- * 为什么是拒而不是「尽力而为地投递一次」(机理按 core 7.13.0 `dist/orchestration/workflow.js` 亲读,合并复审
232
- * R9/R23 推翻本注此前「引擎手上没有赎回键 ⇒ 重跑」的推断):
233
- * · 投递用的 token 是调用方 checkpoint 的 `cp.token`(路由拿到的那枚),**不是** blob 里那一行;blob 行在本
234
- * 车道的用途只有一个 —— 复核「这条 park 此刻仍在此 token 下」(`agent.parkedCheckpointToken === cp.token`,
235
- * 行已在新 token 下 re-park ⇒ miss 不错配)。行被裁掉 = 这道复核**失明**,200「决定已交给宿主的下一跑」
236
- * 就证不出它兑付的是哪一条 park。
237
- * · 引擎 resume 时的赎回键取自 **resume journal** 的 parked 条目(`parkedResumeFor(cached.parked.terminal.token)`,
238
- * :1327/:1727),不取自 run blob;不带决定的 resume 用同一 token **重新 park**(`parkAgentLeg(reparkRec, …)`,
239
- * :1345/:1738),不是重跑。blob 的 parked 行在引擎侧唯一的读点是 `prior.agents` 那道「journal 缺条目」防线
240
- * (:2118-2130)—— 身份被裁掉后失明的正是它:**只有 journal 也缺该条目时**,resume 才会把那次调用活跑。
241
- * ⇒ 拒,并把真话与真恢复路径说出来:先不带决定 resume(引擎按 journal re-park 进一只新的、小的 run 行),
242
- * 再对同一 checkpoint decide;re-park 后能否再赎回取决于新 blob 是否装得下身份行(不下 ⇒ 再落本臂)。
243
- * 码是新的,与既有 `decide.workflow_*` 同族。
231
+ * 为什么是拒而不是「尽力而为地投递一次」(机理按安装树 core 7.17.1 `dist/orchestration/workflow.js` 亲读):
232
+ * · 记录上的 `parks[]` 是**引擎自己**的 park 责任表,而且是**下一次 resume 的准入输入**:
233
+ * `!Array.isArray(prior.parks)` ⇒ 整条拒 resume(`workflow.park_truth_unreadable` / `parks_unreadable`,
234
+ * :2217-2218);某一条条目形坏 ⇒ `park_binding_broken`(:2222-2224)。两者都不是「少一条 park」,
235
+ * 是**这条 run 再也 resume 不了** —— 而本车道的投递腿恰恰是让宿主去 `Workflow({resumeFromRunId})`。
236
+ * ⇒ 决定结构上送不出去。答 200「已交给宿主的下一跑」就是一张兑付不了的回执:卡还 pending、人以为决完了。
237
+ * ⇒ 拒,并把真话说出来:这条 run 恢复不了,开一条新的;卡仍 pending,什么都没有被伪造。
244
238
  *
245
- * 体上带 `runId` 与被裁掉的 park 数(`parks`)—— 后者是店给的计数,**不含任何身份位**(带了就等于没裁)。
239
+ * 这条臂的受害集 = **存量持久数据**(core < 7.17.0 的引擎写下、升级后还 parked 着的 run 记录)。硬 breaking
240
+ * 三句里持久数据那一句要的就是这个形:响亮拒,不做静默双读(读 `agents[]` 补一份身份正是被退役的双算)。
241
+ *
242
+ * 体上只带 `runId`(那是操作员唯一用得上的坐标;真源都读不出来,任何计数都是编的)。
246
243
  */
247
- export declare function denyWorkflowParkIdentityLost(origin: Extract<ParkOrigin, {
248
- kind: "workflow_identity_lost";
244
+ export declare function denyWorkflowParksUnreadable(origin: Extract<ParkOrigin, {
245
+ kind: "workflow_parks_unreadable";
249
246
  }>): ParkedDecideResult;
250
247
  export declare function planWorkflowParkedDecide(origin: Extract<ParkOrigin, {
251
248
  kind: "workflow";
@@ -1,4 +1,4 @@
1
- import { readParkIdentityLoss } from "./orchestration/workflow-agent-session-index.js";
1
+ import { readRunParks } from "./orchestration/workflow-agent-session-index.js";
2
2
  import { defaultSubagentTailBus } from "./fleet/subagent-tail-bus.js";
3
3
  import { backgroundScopesForCheckpointScope, decodeCheckpointScope } from "./security.js";
4
4
  import { ASK_USER_QUESTION_TOOL_NAME } from "./approval-content-kind.js";
@@ -25,14 +25,13 @@ export async function findParkOrigin(sources, cp) {
25
25
  const run = await runStore.get(row.runId);
26
26
  if (run === null)
27
27
  return undefined;
28
- const agent = (run.agents ?? []).find((a) => a.sessionId === cp.sessionId && a.status === "parked");
29
- if (agent === undefined) {
30
- const loss = readParkIdentityLoss(run);
31
- return loss === undefined ? undefined : { kind: "workflow_identity_lost", runId: row.runId, run, loss };
32
- }
33
- if (agent.parkedCheckpointToken !== cp.token)
28
+ const parks = readRunParks(run);
29
+ if (parks === undefined)
30
+ return { kind: "workflow_parks_unreadable", runId: row.runId, run };
31
+ const park = parks.find((p) => p.sessionId === cp.sessionId && p.token === cp.token);
32
+ if (park === undefined)
34
33
  return undefined;
35
- return { kind: "workflow", runId: row.runId, run, agent };
34
+ return { kind: "workflow", runId: row.runId, run, park };
36
35
  }
37
36
  catch (e) {
38
37
  quiet(sources.warn, "workflow_park_probe_failed", { error: e instanceof Error ? e.message : String(e) });
@@ -86,14 +85,13 @@ export function buildParkedResumeOutcome(req) {
86
85
  hostDecision: req.hostDecision,
87
86
  };
88
87
  }
89
- export function denyWorkflowParkIdentityLost(origin) {
88
+ export function denyWorkflowParksUnreadable(origin) {
90
89
  return {
91
90
  status: 409,
92
91
  body: {
93
- error: "this parked workflow agent's recovery identity was dropped when the run store slimmed an oversize run blob — the server can no longer verify that the row is still parked under this checkpoint token, so the decision is not delivered. The park itself is kept by the engine's resume journal, not by this blob: resume the run without a decision (the engine re-parks the child under the same token into a fresh run row), then decide again; only if that journal entry is also missing would a resume run the call live",
94
- errorCode: "decide.workflow_park_identity_lost",
92
+ error: "this parked workflow agent's run record carries no readable list of the parks the run owns — the engine writes that list when it parks, and it is the only proof that this checkpoint token belongs to that run. A record written by an engine older than the one deployed here (or one whose entries are damaged) is refused by a resume of the run itself for the same reason, so the decision is NOT delivered rather than answered with a receipt nothing can redeem. The approval card stays pending and nothing is forged: this run cannot be resumed — start a new run",
93
+ errorCode: "decide.workflow_parks_unreadable",
95
94
  runId: origin.runId,
96
- parksDropped: origin.loss.parks,
97
95
  },
98
96
  };
99
97
  }
@@ -131,7 +129,7 @@ export function planWorkflowParkedDecide(origin, req) {
131
129
  hostSessionId,
132
130
  runId: origin.runId,
133
131
  outcome: buildParkedResumeOutcome(req),
134
- message: `The human approval that workflow run "${origin.runId}" parked on (agent callKey "${origin.agent.callKey}") has been ${verdict}. ` +
132
+ message: `The human approval that workflow run "${origin.runId}" parked on (agent callKey "${origin.park.callKey}") has been ${verdict}. ` +
135
133
  `Resume that run now: re-invoke Workflow with the SAME script source you launched it from (script / name / scriptPath) ` +
136
134
  `AND resumeFromRunId: "${origin.runId}" — the decision is already held by the deployment and will be applied to the parked ` +
137
135
  `agent; completed steps replay from the journal. Do not re-issue the original call.`,
@@ -78,38 +78,29 @@ export declare function withRedactedError<T extends {
78
78
  * write (including the TERMINAL one) keeps failing → the durable row is stuck `running` forever and the 24h
79
79
  * orphan sweep finalizes a genuinely-completed run as `abandoned`. Instead degrade the PAYLOAD, not the write.
80
80
  *
81
- * 🔴 **S-192(7.71.0):瘦身只裁展示载荷,parked 行的恢复身份是最后才放弃的东西。**
82
- * 病(修前逐字):step2 写作 `agents: []` ⇒ 一只 parked 的 workflow 子代的 `callKey` / `sessionId` /
83
- * `parkedCheckpointToken` 随整段消失。server 侧复核「这条 park 仍在此 token 下」的依据**只有这一行**(join 索引
84
- * 刻意不存它——它是能力;引擎自己的赎回键另住 resume journal),于是 `/decide` 的 workflow 车道在 `run.agents`
85
- * 里找不到那一行 ⇒ 落 legacy 腿拿一个**错因**的 409 `conflict.resume_context_unavailable`,人的批准无处兑付;
86
- * 同时 core resume 路径上那道「blob 里 parked 而 journal 缺条目 ⇒ 拒 resume」的防线(`prior.agents`)也随之失明。
87
- *
88
- * 🔴 **7.74.0(core 7.17.0 [ref] 义务②):`parks[]` 排在恢复身份之上 —— 一级都不裁。**
81
+ * 🔴 **`parks[]` 是这条阶梯唯一不许碰的东西**(core 7.17.0 [ref] 义务②)。
89
82
  * 它不是观测位,是**下一次 resume 的准入输入**:core 的 resume 准入在任何 agent 被派发之前读记录上的
90
83
  * 这一位,读不到就整条拒(`workflow.park_truth_unreadable` / `parks_unreadable`)——不是「少一条 park」,
91
- * 是**这条 run 再也 resume 不了**。所以阶梯的判据现在是两条,按这个次序:①`parks` 永不放弃;
92
- * ②恢复身份行最后放弃。实现上五级全部走 `{...上一级}` 展开、没有任何一级点名裁它,机器背书 =
93
- * `test/slim-oversize-run.test.ts` 里那两格(最狠的 step 5 上 `parks` 仍逐字完整 + 负控:记录本来没有
84
+ * 是**这条 run 再也 resume 不了**;它同时也是本仓 `/decide` 的 workflow 车道复核「这条 park 属于这条 run」
85
+ * 的**单一真源**(S-252)。实现上三级全部走 `{...上一级}` 展开、没有任何一级点名裁它,机器背书 =
86
+ * `test/slim-oversize-run.test.ts` 里那两格(最狠的一级上 `parks` 仍逐字完整 + 负控:记录本来没有
94
87
  * 这一位时不许凭空补 `[]`)。缺席 ⇒ 不补:补 `[]` 会把「这版引擎没写这个位」伪造成「这条 run 没有 park」,
95
88
  * 而 core 对两者的处置相反。
96
89
  *
97
- * 阶梯 = 一张**按价值排序的放弃清单**,一级一级试到装得下为止(**判据只有一条:恢复身份最后放弃**):
90
+ * 🔴 **S-252(7.75.0):阶梯不再为 park 另存第二份恢复身份。** 7.71.0 的 S-192 曾在 `agents[]` 里逐行保住
91
+ * `callKey`/`sessionId`/`parkedCheckpointToken`(外加两级 `label` 预算与一枚不可恢复标记),因为当时
92
+ * `/decide` 的复核只认那一行。core 7.17.0 之后同一条记录上有了 `parks[]`,那份保留就成了**第二个会漂的
93
+ * 定义**(真案:最后一级清空 `agents` 之后 token 仍躺在同一个 blob 的 `parks` 里,而 `/decide` 照拒)。
94
+ * ⇒ 整段退役,判别式改读单一真源。退役后的阶梯每一级都**不大于**退役前的对应级,物理下界不劣化。
95
+ *
96
+ * 阶梯 = 一张**按价值排序的放弃清单**,一级一级试到装得下为止:
98
97
  * step 1 —— 两个无界字符串(`result` / `error`)截断带标记;
99
- * step 2 —— `phases` / `groups` 整段丢,`agents` 收敛成**恢复身份行**
100
- * ({@link buildParkedRecoveryIdentities};非 parked 的行不带恢复身份 ⇒ 整行丢,与修前同);
101
- * step 3 —— 顶层**观测载荷**(codex r1-[high],红先复现后修):`effectiveArgs` 与 `resultFull` 整段丢、
102
- * `name` / `description` 先脱后切。core 对 `effectiveArgs` 的成文口径逐字是 OBSERVATION-ONLY
103
- * (never a gate input,never re-read by the engine;resume 的身份由 journal 的 callKey 携带,
104
- * 不是这个字段),`resultFull` 同类(取回面),两者都**无界**。修前这一级不存在,于是阶梯
105
- * 一边原样留着它们、一边把唯一那条 parked 行删掉盖标记 —— 自己违反了自己那条判据,
106
- * 一条本来赎得回的审批被判死(生产上界 4 MiB 上真复现);
107
- * step 4 —— 恢复身份行上的**展示名**(`label`)—— 它是这条行上唯一可弃的展示物,身份四位恒在
108
- * (codex r2-[high],红先复现后修:修前阶梯一边留着每行 200 字符的 label、一边把**全部**身份
109
- * 删掉盖标记 —— 同一条判据第二次被自己违反;6000 条 CJK label 的 parked 行在生产上界上
110
- * 实测 `agents:[]` + `parks:6000`,而清空 label 后 6000 条身份**全都装得下**);
111
- * step 5 —— 连只剩身份的行都装不下 ⇒ 丢掉它们并落**显式不可恢复标记**({@link PARK_IDENTITY_LOSS_KEY}),
112
- * `/decide` 与 SLA sweep 读到它就**响亮拒**,而不是让一条赎回不了的 park 看起来像普通 miss。
98
+ * step 2 —— 展示载荷整段丢(`phases` / `agents` / `groups`),`error` 收成短串;
99
+ * step 3 —— 顶层**观测载荷**:`effectiveArgs` 与 `resultFull` 整段丢、`name` / `description` 先脱后切。
100
+ * core 对 `effectiveArgs` 的成文口径逐字是 OBSERVATION-ONLY(never a gate input,never re-read by
101
+ * the engine;resume 的身份由 journal 的 callKey 携带,不是这个字段),`resultFull` 同类(取回面),
102
+ * 两者都**无界**;`name` / `description` 是脚本(LLM 撰写)经 `export const meta` 供的展示头,
103
+ * 同样无界 ⇒ **先脱后切**(切口会把读面脱敏所依赖的闭合引号一起切掉,半截凭据原样上 wire)。
113
104
  * Returns null only when even the marked skeleton is oversize (physics — caller keeps the prior revision,
114
105
  * matching the old behavior for that corner). Dialect-neutral (pure JS, no SQL) — shared verbatim by both twins. */
115
106
  export declare function slimOversizeRun(run: WorkflowRun & {
@@ -1,7 +1,7 @@
1
1
  import { redactErrorMessage, redactHead } from "../observability/run-terminal-log.js";
2
2
  import { summarizeWorkflowRun, isTerminalWorkflowStatus, WorkflowRunStoreError } from "@sema-agent/core";
3
3
  import { foldKeyFamily, MAX_PENDING_PER_SESSION, PURGE_FENCE_MS, SERVED_FENCE_MS, } from "../orchestration/workflow-completion-inbox.js";
4
- import { buildParkedRecoveryIdentities, buildParkIdentityLoss, PARK_IDENTITY_LOSS_KEY } from "../orchestration/workflow-agent-session-index.js";
4
+ import {} from "../orchestration/workflow-agent-session-index.js";
5
5
  import { mysqlDriver, pgDriver } from "./sql-driver.js";
6
6
  import { isDupKeyError } from "./sql-errors.js";
7
7
  export const MAX_RUN_BLOB_BYTES = 4 * 1024 * 1024;
@@ -24,9 +24,8 @@ export function slimOversizeRun(run, maxBytes) {
24
24
  let blob = JSON.stringify(step1);
25
25
  if (Buffer.byteLength(blob) <= maxBytes)
26
26
  return blob;
27
- const identities = buildParkedRecoveryIdentities(run.agents);
28
27
  const shortError = step1.error ? String(step1.error).slice(0, 1000) + MARK : MARK;
29
- const step2 = { ...step1, phases: [], agents: identities, groups: [], error: shortError };
28
+ const step2 = { ...step1, phases: [], agents: [], groups: [], error: shortError };
30
29
  blob = JSON.stringify(step2);
31
30
  if (Buffer.byteLength(blob) <= maxBytes)
32
31
  return blob;
@@ -38,19 +37,6 @@ export function slimOversizeRun(run, maxBytes) {
38
37
  if (typeof step3.description === "string")
39
38
  step3.description = redactHead(step3.description, SLIM_HEADER_MAX);
40
39
  blob = JSON.stringify(step3);
41
- if (Buffer.byteLength(blob) <= maxBytes)
42
- return blob;
43
- const bareIdentities = buildParkedRecoveryIdentities(run.agents, 0);
44
- const step4 = { ...step3, agents: bareIdentities };
45
- blob = JSON.stringify(step4);
46
- if (Buffer.byteLength(blob) <= maxBytes)
47
- return blob;
48
- const step5 = {
49
- ...step4,
50
- agents: [],
51
- ...(identities.length > 0 ? { [PARK_IDENTITY_LOSS_KEY]: buildParkIdentityLoss(identities.length) } : {}),
52
- };
53
- blob = JSON.stringify(step5);
54
40
  return Buffer.byteLength(blob) <= maxBytes ? blob : null;
55
41
  }
56
42
  export class SqlWorkflowRunStore {
@@ -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 { redactSecrets } from "./redact.js";
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: redactSecrets(reasoning) });
36
+ await append("reasoning", modelTextEventData({ text: reasoning }));
38
37
  reasoning = "";
39
38
  }
40
39
  if (text) {
41
- await append("text", { text, ...(textEventId ? { eventId: textEventId } : {}) });
40
+ await append("text", modelTextEventData({ text, eventId: textEventId }));
42
41
  text = "";
43
42
  }
44
43
  };
@@ -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 面
@@ -122,8 +144,9 @@ export declare function toolStartEventData(ev: {
122
144
  /**
123
145
  * 🔴 **筛子是一只总函数**(S-239,codex 对抗复审 r1 [medium] 亲核复现后修)—— 上面纪律②许诺的
124
146
  * 「坏形 ⇒ report + withhold,**不抛**」此前是靠**逐条枚举**坏形兑现的,枚举不到的那些照样抛:
125
- * `projectSettlement` 对 `note` / `who.approver` 先 `redactSecrets` 再截断,而 `redactSecrets` 在 ≥2^23
126
- * 的单段上爆回溯栈(`RangeError`,S-102/S-106 登记的既有上界,见 `redact.ts` 该行顶注),两条叶各能抛一次。
147
+ * `projectSettlement` 对 `note` / `who.approver` 先 `redactSecrets` 再截断,而 `redactSecrets` 会在
148
+ * 病态输入上爆回溯栈(`RangeError`:≥2^23 单段那一形已由 S-219 的 `atLeast` 铸点闭掉,仍能抛的是 JSON 秘密字段臂的
149
+ * 转义对循环,S-102/S-106 登记的既有上界,见 `redact.ts` 该行顶注),两条叶各能抛一次。
127
150
  *
128
151
  * 抛出去的代价按腿算,而且一条比一条重:写腿落账那处的 `.catch` 看不到**同步**投影期的异常;读腿上它
129
152
  * 直接逃出 `formatEvent` / 投影函数 —— 账本裸回放腿的那一次会当场断流(那一行不上帧、游标不前进、后面的
@@ -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
  }
@@ -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
  */