@sema-agent/client-core 0.58.0 → 0.59.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/CHANGELOG.md CHANGED
@@ -49,6 +49,125 @@
49
49
  > 挡住 ⇒ 本批把它机械化——④a0 对 `pending` 行**要求段头已是日期形**(`(未发布)` 直接红),阶段一
50
50
  > commit 漏转在发布前就红,不再靠人记。
51
51
 
52
+ ## 0.59.1(2026-09-06)
53
+
54
+ > 补丁批(主会话亲做):B-005 修根,零新公面、零型面变更。
55
+
56
+ ### 修复 —— `parkRowBirthWait` 窗尾「注定超时」的末次 probe(B-005;test [6497] 异源复审轨)
57
+
58
+ 有界重查环在窗内剩余已不足上一拍**实测时延**时仍起最后一拍,它必被内部 deadline 切成 `'deadline'` 出站——
59
+ 那一拍一个字都没读到,而宿主(cli `askParkReopen`)每拍先把 evidence 清成 `answered:false`,于是此前成立的
60
+ 「读面答了、零属主行」证据被最后这拍抹掉 ⇒ `pendingRowGone` 不置 ⇒ 陈旧 park 两选卡不出(异源轨 31 次探测 / 15s
61
+ 未出现一次)。修=记录上一拍实测时延,剩余窗小于它就不再起探测,按已有证据出站(`unborn`,reason 不带
62
+ 「did not answer」);瞬答读面(时延 0)不设闸,零窗形(`budgetMs:0`)语义不变。门 `run-park-decision-layer-test`
63
+ +G1⑥i/i2/i3(红先绿后)。宿主消费指引不变(§16b)。
64
+
65
+ ## 0.59.0(2026-09-06)
66
+
67
+ > 内容批(2026-09-06,隔离树交付):三件 —— ①`@sema-agent/sdk` **8.3.0 提货** + 审批帧键镜像与
68
+ > **卡键台账**双腿对账(peer 地板抬版,本版**唯一**的非 additive 面)、②`Capabilities` **74→93
69
+ > 逐键复核** + 能力位**四态通用读口** + **S-131 SQL 姿态读面从壳侧上收**(归层债)、③三件候件投影
70
+ > (`tool_end.autoDenied` / `denialLimitFallback` 上卡 / `wiring_manifest.mcp[]`)。**本段只记内容**:
71
+ > `package.json.version` 未动、README Version 行未动、`FROZEN` 未动 —— bump 与段头转日期形归
72
+ > **发包批**(阶段一义务见本档头注)。
73
+ > 接入面详报见 `docs/INTEGRATION-CLIENTS.md` **§23**(三件各一小节 + 端的迁移动作 + 黑盒判据 + 门表)。
74
+
75
+ ### 🔴 BREAKING(peer 地板)
76
+
77
+ - **`@sema-agent/sdk` peer 地板 `>=8.2.0` → `>=8.3.0`**(devDep `^8.3.0` 同批)。三条硬理由:
78
+ ① `RULE_OFFERS_ABSENCE_REASONS` / `DENIAL_LIMIT_KINDS` 两张闭词表是**值级** import,<8.3.0 上这两个
79
+ 名字不存在、整包编译不过;② type-only 的 `AskOrigin` / `RuleOffersAbsence` / `DenialLimitKind` /
80
+ `DenialLimitFallback` 四个型面(帧上 `denialLimitFallback` 与四个公面别名直接引用);
81
+ ③ 真正的量是三张锚 —— `TOOL_APPROVAL_FRAME_KEYS` 18→**27**、`APPROVAL_CARD_KEYS` 17→**19**、
82
+ `Capabilities` 74→**93**,分别由两道门逐元素对账。
83
+ 地板与 peer 声明由 `run-sdk-floor-test.mjs` **三位逐位等值**互绑,抬一处忘另一处当场红。
84
+
85
+ ### 件① sdk 8.3.0 提货 + 审批帧/卡键双腿对账
86
+
87
+ - **帧上三个新键**(全 additive,`"tool_approval"` only)进 `ToolApprovalFrame` 与键镜像(24→**27**),
88
+ 并**到得了卡口**(`ApprovalCardRequest` 同名三键;单源 = 活卡帧腿):
89
+ - `origin`(S-125③ / core 7.5.0 `ASK_ORIGINS` 闭八词,引擎在门上盖章)—— 🔴 **缺席不是
90
+ `"policy"`**(那是一个正面事实,折进去 = 替引擎编话);按**开集**分支,包内**零闭集校验**
91
+ (词表属主是 core,server 已按闭集拒过,包再校一遍只会在 core 加员当天把合法值判没)。
92
+ - `ruleOffersAbsence`(S-15 第五单 / core #490 修②,闭三词)—— 🔴 **缺席不是断言**;`mandated`
93
+ 臂**不得**指向「去写一条规则」;与 `ruleOffers` 引擎侧互斥,但**包不据此互删**(同时在场 = 上游
94
+ 坏形,原样上卡让端看得见,包替上游把矛盾湮灭掉只会让排障说不清是谁窄没的)。
95
+ - `denialLimitFallback`(S-114 / core 7.4.0 #548,四成员全必填)—— 走包内窄读器,**缺一整只丢**
96
+ (与 server `readDenialLimitFallback` 的 `.strict()` 同判据:半只对象上的计数会被人当成真实次数读);
97
+ 计数与窗按「有限非负数」判(`0` 是**合法读数**不折缺席),`limit` 按开集读。与
98
+ `requiresRealApproval` 是**孪生键**。🔴 `autoDenyAfterMs` **只渲倒计时,绝不据它自铸第二只定时器**。
99
+ - **两张闭词表运行期再导出**:`RULE_OFFERS_ABSENCE_REASONS` / `DENIAL_LIMIT_KINDS`(与 0.58.0 那三张
100
+ 同一条纪律 —— 端拿它做判定,别再手抄字面量;⚠️ 本包自己**不拿它们做窄读判定**,端的 `switch`
101
+ 仍必须带 `default` 臂)。
102
+ - **`AHEAD_OF_ANCHOR` 整表退役**:六条领先记账(`requiresRealApproval` / 窗三键 / `inputHasBidi` /
103
+ `parked`)**同一天到期**,删登记前先让它自红一次留证。✅ 表回空 = 健康态,镜像与锚逐元素相等。
104
+ - 🆕 **对账门加第二条腿(卡面腿)**:sdk `APPROVAL_CARD_KEYS`(19)逐键处置台账 —— 帧腿盖不住卡这
105
+ 一格(帧上有的事实要**到得了卡口**才有人看得见,[4851] 窗三键那次正是这条缝里漏的)。`carried`
106
+ 行必须真在 `ApprovalCardRequest` 上、`not_carried` 行必须真不在且写了理由,与锚**逐元素相等**。
107
+
108
+ ### 件② `Capabilities` 74→93 逐键复核 · 四态读口 · S-131 读面上收
109
+
110
+ - 🆕 **`scripts/run-engine-caps-ledger-test.mjs`**:93 键**逐键处置台账**门。病是「sdk 一版加了 19 个
111
+ 能力位,而本包一个字不动这件事在门盘上**一声不响**」。台账 ↔ SDK 键集**双向等值**(加位缺行红 /
112
+ 删位空转登记也红);`read` 行(14)的**代码位置**实证、`not_read` 行零读点、五形读点反向普查
113
+ (每形先在伪造样本上自证会说话 —— 初版只认一种调用形时 14 个读点只认出 7 个)。
114
+ - **新公面 `engineCapValue(baseUrl, key): EngineCapValueState`**(additive)—— 能力位的**四态**读口
115
+ `unobserved` / `not_reported` / `null` / `value`。既有三口把四种「读不出」全折成 fail-closed 一档,
116
+ 对**放行**问题是对的、对**读面/诊断**问题是错的。既有三口语义**一字未改**。
117
+ - **S-131 SQL 姿态读面从壳侧上收**(additive 六件 + 一个测试钩):`projectSqlEngineCapability` /
118
+ `SqlEngineReading` / `SqlEngineCapabilityView` / `observedSqlEngine` / `noteEngineCapsForSqlEngine` /
119
+ `forgetSqlEngineReading` / `sqlEngineDoctorDetail`。0.58.0 时它长在壳里的理由是**包侧结构上读不
120
+ 出来**(没有嵌套对象读口)—— 归层债,与上一条同批清。
121
+ - 🔴 **四态不是两态**:`unobserved`(本进程没观测过)/ `not_reported`(老引擎不报)/ `none`
122
+ (**引擎明确说没有 SQL 后端**,正面事实)/ `present`,四句 doctor 文案互不相同也互不蕴含。
123
+ - 🔴 `txnMode: null` 是**铸点域内的合法值**(上游逐字:`null` = 这个引擎没有这个指示位,**不是**
124
+ `optimistic`),`innodb`/`pg` 两种引擎恒 `null` —— 判成畸形 = B-025「消费域比铸点域更窄」复发。
125
+ - 🔴 **换代失效口是独立的一条路**:代际闸挡的是「旧响应写脏新一代」,这一格的病是反过来的
126
+ (新一代探测根本没成功 ⇒ tee 一次都没触发 ⇒ 上一台引擎的读数继续被当成当代事实答出去)。
127
+ - 🔴 **导出名与壳侧那份逐字同名 = drift-lock**(壳的同名影子导出门当场红,逼那份副本换装)。
128
+ 包内零 `process`:读锚走 `engineWireTarget()?.baseUrl`(其 env 臂读的就是同一个变量)。
129
+
130
+ ### 件③ 三件候件投影(cli [6476]⑥)
131
+
132
+ - **`tool_end.autoDenied`**(S-125⑧,server ≥7.57.0)进内部臂,**严格 `true` 才上臂**。
133
+ 🔴 它答的是既有两键答不了的那一问:`settledBy:"timeout"` + `resolution:"window_expired"` 覆盖
134
+ **一切**审批窗到期(普通 ask 的 TTL 也在内),分不出「自动拒」与「park 等人」—— 要渲
135
+ 「自动拒(限额回落)」而不是「已转后台候批」,判据只有这一位。缺席**禁**读成「是人拒的」。
136
+ - **`denialLimitFallback` 上卡**:见件①(端渲触限计数 + 哪道界 + 窗,并必须同呈「这一次得真人批」)。
137
+ - **`wiring_manifest.mcp[]`**(S-124 / core 7.5.0,server ≥7.60.0)—— `WiringManifestChromeEvent` 的
138
+ **第三段**,超集键 `_sema_mcp`(已登记 `docs/type-superset.json`)。
139
+ 🔴 **空数组不是缺席**:`mcp: []` = 「这条腿一台 MCP 都没申报」的**正面事实**,缺席才是「没报」;
140
+ 判在场写 `mcp !== undefined`,写 `mcp?.length` 就把两句话折成了一句。
141
+ ⚠️ **与同帧 `modelGate.removed: []` 规矩方向相反**(那一格空数组是坏值)—— 两条相反的规矩同处
142
+ 一帧,门里有一条**反向对照**钉着,不许被顺手统一。
143
+ 🔴 逐条独立(坏行只丢自己)· `toolCount: 0` 是读数不是缺席 · `errorCode` **不套闭集也不套形正则**
144
+ (十个具名词之外还有 `http_<status>` 形,词的属主在 core)· 远端作者的**自由文本 `error` 一格不投**。
145
+
146
+ ### 异源对抗复审 r1 两条 [medium] 全采(真病修)
147
+
148
+ - **「过滤后为空」不是「原本就是空」**(`projectMcpSection`)。一份非空却**零行幸存**的 `mcp`
149
+ (例:唯一那行缺 `status`)此前照样铸出 `[]` ⇒ 消费端按本臂义务④读到的是「这条腿一台都没申报」
150
+ 这句**肯定话**,一份读不出来的回体被伪装成一个确定的零。修 = 非空输入零行幸存 ⇒ **整段缺席**;
151
+ 只要还有幸存者,坏行照旧只丢自己。判据、判别力自证与边界(至少一行幸存)同批进门,
152
+ `seam.ts` 义务④、`type-superset` 条目与 §23c/黑盒判据 G5b 同步。
153
+ - **能力读点普查在对账前把该报的读点丢了**(`run-engine-caps-ledger-test.mjs`)。两条:
154
+ ① 读口表漏了 `engineCapState` ⇒ 经它的读点对本门隐形,台账可以一边写 `not_read` 一边真在读;
155
+ ② 收集时就按 SDK 锚过滤 ⇒ 一个**拼错的键名**(`permissionRulez`)连同它代表的真读点在对账
156
+ 之前蒸发,门原样绿,而产品上那条能力车道从此恒 `false`(权限治理入口永久隐形且不报错)。
157
+ 修 = 读口表补 `State`、键按**第 2 实参的位置事实**解析(不再「取参数表第一个串字面量」——
158
+ base 里的串会抢走它)、读口键**不预过滤**并新增 ④b「读口读的每个键都在 SDK 锚上」。
159
+ 两条复现出来的反例已长驻门内。
160
+
161
+ ### 门与登记
162
+
163
+ - 🆕 两道门 + 三处段级新增:`run-engine-caps-ledger-test.mjs`(新)· `run-sql-engine-capability-test.mjs`
164
+ (新)· `run-approval-frame-keys-test.mjs`(卡面腿)· `run-wiring-manifest-projection-test.mjs`(P6/A4)·
165
+ `run-additive-key-passthrough-test.mjs`(G6)· `run-hitl-gate-honesty-test.mjs`(F15)。
166
+ - 棘轮与登记同批:index 闭包 143→**144**(`sqlEngineCapability.ts`)· typeshape unknown 出境
167
+ 273→**277**(**四格逐格记账**)· 单例清单 281→**282** 条 / dupRisk high 94→**95**(逐条写下
168
+ 「复制两份会怎样」)· 公面导出基线 828→**836**(测试钩 41→**42**)· `docs/type-superset.json` 补
169
+ `_sema_mcp` 一行 · sdk 地板常量 8.2.0→**8.3.0**。
170
+
52
171
  ## 0.58.0(2026-09-06)
53
172
 
54
173
  > 内容批(2026-09-06,隔离树交付):三件 —— ①`@sema-agent/sdk` **8.2.0 提货**(peer 地板抬版,
package/README.md CHANGED
@@ -35,7 +35,7 @@ Renamed from **`@sema-agent/wire-cc-adapter`** (0.1.x, deprecated — see *Migra
35
35
 
36
36
  ## Scope
37
37
 
38
- **Version:** 0.58.0
38
+ **Version:** 0.59.1
39
39
 
40
40
  - **Today** — the adapter seam, the whole `adapt()` pipeline (all 14 A-layer arms plus the
41
41
  B/D/E tool-card layers), the notification/caps/model families, the adapter kernel (stream driver
@@ -67,7 +67,7 @@ Renamed from **`@sema-agent/wire-cc-adapter`** (0.1.x, deprecated — see *Migra
67
67
  against — the tables live upstream precisely so this package does not keep a second copy that can
68
68
  fall behind. The browser bundle really bundles the SDK through (the portability guard would
69
69
  exit 3 rather than quietly mark it external).
70
- - The declared floor is `>=8.2.0`, and it is *witnessed*: the guard checks that an actually
70
+ - The declared floor is `>=8.3.0`, and it is *witnessed*: the guard checks that an actually
71
71
  installed SDK at that line still exports every value-level symbol this package imports and still
72
72
  declares `TaskStats.costMicroUsd` (the key `costOrNull` reads). A floor nobody ever ran is a
73
73
  promise, not a contract.
@@ -234,6 +234,8 @@ public-surface guard checks that last one).
234
234
  | `scripts/run-print-bash-iserror-test.mjs` | The print lane's Bash `is_error` authority (structured over regex) |
235
235
  | `scripts/run-bash-benign-exit-interpretation-test.mjs` | Benign non-zero Bash exits (`returnCodeInterpretation`) stay non-errors across all three derivation arms, and the annotation transits to the card |
236
236
  | `scripts/run-sdk-floor-test.mjs` | The SDK version floor — and, more to the point, that the *installed* type declarations still carry the keys this package reads |
237
+ | `scripts/run-engine-caps-ledger-test.mjs` | A per-key disposition ledger for `GET /v1/capabilities`. The SDK's `Capabilities` grew from 74 keys to 93 in one release and nothing on the board could see it: this package consumes that table through four synchronous readers, and *nineteen new positions arriving while the package does not move* is exactly the disease shape this repo keeps logging on other axes — the fact is already on the wire, the package boundary is the cell that swallows it, and no client can read it however they write their side. So the ledger is reconciled **element-wise against the SDK interface in both directions**: a key the SDK added with no ledger row is red (someone must classify it), and a row for a key the SDK removed is red too (a registration that no longer does anything). Each row then has to survive its own claim — a `read` row names the source file, and the **code** there (comments stripped) must really mention the key, because prose asserting an alignment is the classic way these guards go hollow; a `not_read` row must have **zero** read sites in the tree, so wiring one up while the ledger still says the package ignores it is red rather than invisible. The census behind those two directions recognises five call shapes, each of which really occurs here — a reader whose base argument carries its own parentheses, a direct `caps.<key>`, a narrowing cast, an own-property read helper, and a `*_CAP` constant — and proves it on fabricated samples first, since a census that recognises one shape reports "nothing here" for the other four. What the guard deliberately does **not** judge is whether a position *ought* to be read: that is a design call, and the ledger only pins that every capability was looked at once by a person and that what they wrote down does not contradict the code |
238
+ | `scripts/run-sql-engine-capability-test.mjs` | The SQL-posture read face and the four-state capability reader underneath it. One capability cell here carries **four different things**, and each one points an operator somewhere else: nothing has been observed yet in this process (a one-shot doctor run is always in that state), the response arrived but carries no such key (an older engine), the engine explicitly answered `null` — *this deployment has no SQL backend*, which is a **positive fact** rather than an absence — and a full reading. Fold any two together and the screen states something flatly, confidently, and wrongly, so every positive control here is paired with a control pointing the opposite way, and the four sentences the doctor row can print are checked to be pairwise distinct and non-implying. The reading itself is narrowed no tighter than the mint: `txnMode: null` is a **legal value** — two of the three engines always report it that way, and the upstream type note names reading it as "optimistic" as the error — so treating it as malformed would throw away the entire reading for ordinary deployments, which is the same disease this repo logged when a consumer's domain was narrower than the producer's. A response that cannot be parsed **clears** the cell rather than leaving the previous engine's answer in place, and a separate invalidation port exists for the case the generation latch cannot catch — a same-port respawn whose new probe never succeeded, where the stale reading would otherwise be answered as current fact. Untrusted values (the isolation string is read back from a database server variable) are sanitised and bounded before display, and the bound is applied **before** escaping so a visible escape never gets cut in half. Finally the export names are themselves a guard: the shell still carries a copy that is meant to go red on the package's same-named export and be swapped out, so renaming anything here would silently disarm that lock |
237
239
  | `scripts/run-engine-vocab-floor-test.mjs` | Engine-mirrored vocabularies (structured card whitelist, self-reported tool face, control verbs, recogniser sets) against the *installed* `@sema-agent/core` |
238
240
  | `scripts/run-limits-env-failloud-test.mjs` | `SEMA_HEADLESS_*` env-lane limits reject invalid values as loudly as the flag lane (no silent "no budget" runs) |
239
241
  | `scripts/run-streamjson-timing-honesty-test.mjs` | Stream timing & terminal honesty ([2084]): held errored fs-write results release on model progress; a wall-clock stop maps to `error_during_execution` with a truthful salvage note; the synthetic API-error assistant row carries the `<synthetic>` in-message sentinel. Also ([2489], core 5.8.0): the run-limit `errorCode` -> CC subtype map is pinned code by code (`limits.max_{cost,turns,tokens,walltime}_exceeded`), token/wall-clock stops keep the text the engine already produced, and the `failed` event arm shares the one mapping point. The 5.7 dual-vocabulary legs retired with server 6.0.0 (which bundles core 5.8.0); four **retirement negative controls** stand in their place — the retired `status:'timeout'` and the retired codes must fall to the honest fallback subtype and must never drop back to an empty success, so putting any of them back turns the gate red |
@@ -314,15 +314,20 @@ const wiringManifestArm = function* (m) {
314
314
  return;
315
315
  const modelGate = m._sema_modelGate;
316
316
  const autoMode = m._sema_autoMode;
317
+ const mcp = m._sema_mcp;
317
318
  const hasGate = typeof modelGate === 'object' && modelGate !== null;
318
319
  const hasAuto = typeof autoMode === 'object' && autoMode !== null;
319
- if (!hasGate && !hasAuto)
320
+ // S-124(0.59.0):🔴 `Array.isArray` 判在场,**不判 `.length`** —— `mcp: []` 是「这条腿一台都
321
+ // 没申报」这句正面事实,按长度判会把它和「老 mint 没报」折成同一件事(core 顶注点名的读法)。
322
+ const hasMcp = Array.isArray(mcp);
323
+ if (!hasGate && !hasAuto && !hasMcp)
320
324
  return;
321
325
  yield chrome({
322
326
  kind: 'wiring_manifest',
323
327
  laneProof: MAIN,
324
328
  ...(hasGate ? { modelGate: modelGate } : {}),
325
329
  ...(hasAuto ? { autoMode: autoMode } : {}),
330
+ ...(hasMcp ? { mcp: mcp } : {}),
326
331
  ...(typeof m.eventId === 'string' && m.eventId.length > 0 ? { eventId: m.eventId } : {}),
327
332
  });
328
333
  };
@@ -170,6 +170,33 @@ export interface WiringManifestAutoMode {
170
170
  /** core **六词逐字透传**。🔴 不映射 `/v1/capabilities.permissionModeAuto.reason` —— 见投影函数头注。 */
171
171
  reason: string;
172
172
  }
173
+ /**
174
+ * {@link wiringManifestSupersetBody} 的 `mcp[]` 一条目(S-124 / core 7.5.0,server ≥7.60.0 的形)。
175
+ *
176
+ * 「我这条腿**申报**的一台 MCP 服务器,连上了没有、挂了是哪一类、挂上来几个工具」。
177
+ *
178
+ * 🔴 **`name` + `status` 是必需两座**(与 server 侧 `wiringManifestEventData` 的 `mcp` 臂同判据:
179
+ * `if (name === undefined || status === undefined) continue`)—— 一条既没有名字也没有状态的行
180
+ * 渲不出任何有用的话,而**坏一条只丢那一条**,绝不丢整张表(丢整表 = 用户以为「一台都没申报」)。
181
+ * 🔴 **`errorCode` 不是纯闭集**:core `MCP_FAILURE_CODES` 十个具名词之外还有 `http_<status>` 形
182
+ * (core 自陈)⇒ 本包**既不枚举那十个词、也不套形正则**(词的单一属主在 core;抄一份会把 core
183
+ * 加的新词与新形一起吞成缺席),只判非空串。消费端按具名词写的 `switch` **必须带 default 臂**。
184
+ * 🔴 **`error` 自由文本上游就不投,本包更不铸**:它是这条面上唯一的**远端作者自由文本**,server
185
+ * 侧的裁定逐字是「进来会同时破本段『无自由文本 ⇒ 不脱敏』的段内不变量,和 core 把远端 MCP 错误
186
+ * 文本脱敏收敛成**一个**铸点的单铸律」。可操作的因由在 `errorCode`。
187
+ */
188
+ export interface WiringManifestMcpEntry {
189
+ /** spec 声明名(引擎产的标识,非用户内容)。 */
190
+ name: string;
191
+ /** 三词闭集(core 属主;**按开集读**,认不得的词照渲不丢行)。 */
192
+ status: string;
193
+ /** 声明方自述的层级标签(core 已 single-line + 长度封顶后逐字回显)。缺席 = 没报。 */
194
+ source?: string;
195
+ /** 失败因由的**机器码**(十词 + `http_<status>` 形,开集)。缺席 = 这条没失败,或引擎没给码。 */
196
+ errorCode?: string;
197
+ /** 这台服务器挂上来的工具数(有限数)。缺席 = 没报,**不是** `0`。 */
198
+ toolCount?: number;
199
+ }
173
200
  /**
174
201
  * CS-7 §2.7 — turn_end usage → CC `ModelUsage` (pinned name mapping;
175
202
  * costMicroUsd/1e6 → costUSD). Surfaced separately because the slice has no
@@ -269,6 +269,23 @@ export function eventToSdkMessage(ev, ctx) {
269
269
  // 直拒、以及 durable/decide 腿的结算(它们带自己的 settledBy/reason)都**不带**这一位。
270
270
  // 缺席 ≠「不是拒绝」也 ≠「人拒的」⇒ 缺席就不铸键(绝不折成 null / 空串)。
271
271
  const toolEndResolution = ev.resolution;
272
+ // ── S-125⑧(0.59.0):`autoDenied` —— 这次 deny **是引擎的窗自己拒的**,不是人拒的 ──────
273
+ // 出处 = server ≥7.57.0 `trace/project.js` 的 `toolEndEventData`(条件 stamp
274
+ // `ev.autoDenied === true ? { autoDenied: true } : {}`),源头是 core 7.4.0 auto 模式
275
+ // 分类器的**限额回落窗**:那张必须真人批的卡(`tool_approval.denialLimitFallback` +
276
+ // `requiresRealApproval` 孪生键)在 `autoDenyAfterMs` 走完之后由**引擎**判 deny,同批盖
277
+ // `settledBy:"timeout"` / `resolution:"window_expired"` / 本位。
278
+ // 🔴 **为什么单靠既有两键判不出来**(这一位非补不可的理由):`settledBy:"timeout"` 覆盖的是
279
+ // **一切**审批窗到期(含普通 ask 的 TTL),`resolution:"window_expired"` 同理 —— 两者都
280
+ // 答不了「这一次到期是**自动拒**收的场,还是 park 了等人」。消费面要渲的那句话
281
+ // (「自动拒(限额回落)」vs「已转后台候批」)正卡在这一格上。
282
+ // 🔴 **严格 `true` 才上臂**(与 `_sema_collateral_abort` / `governanceForced` 同族纪律):
283
+ // 机读位是二值的,「在场但不是 true」没有语义,折成在场会让消费端把一个坏形当成肯定。
284
+ // 🔴 **缺席不带语义,不许反推**:缺席同时覆盖「不是自动拒」「老引擎(<7.57.0)不报」
285
+ // 「这次根本没走审批」三形 ⇒ 缺席**禁**读成「是人拒的」。
286
+ // 🔴 **不据它自铸第二只定时器/第二张资格表**:窗的执行全在引擎(上游 `denialLimitFallback`
287
+ // 顶注的同一条禁令),本位只是**事后**的判别位。
288
+ const toolEndAutoDenied = ev.autoDenied;
272
289
  // ── #324 半场(0.40.0):连坐 abort 机读位 `_sema_collateral_abort`([4907] 裁定)──
273
290
  // 出处**不是引擎**,是本包自己的 HITL 桥:`gateLedger.flushHeld` 对「出身 = collateral」的
274
291
  // 扣留帧 stamp(见 `SEMA_COLLATERAL_ABORT_KEY` 头注)。桥在本臂**上游**(宿主先用
@@ -309,6 +326,8 @@ export function eventToSdkMessage(ev, ctx) {
309
326
  ...(typeof toolEndResolution === 'string' && toolEndResolution.length > 0
310
327
  ? { resolution: toolEndResolution }
311
328
  : {}),
329
+ // S-125⑧:自动拒判别位(**严格 true** 才上臂;见上方头注)。
330
+ ...(toolEndAutoDenied === true ? { autoDenied: true } : {}),
312
331
  // #324:连坐 abort 机读位(严格 true 才上臂;见上方头注)。
313
332
  ...(collateralAbort === true ? { _sema_collateral_abort: true } : {}),
314
333
  ...(ev.eventId !== undefined ? { eventId: ev.eventId } : {}),
@@ -830,6 +849,49 @@ function textEndProjection(ev, ctx) {
830
849
  : {}),
831
850
  })));
832
851
  }
852
+ /**
853
+ * `wiring_manifest.mcp` 的形校验(S-124 / core 7.5.0,server ≥7.60.0)。
854
+ *
855
+ * 🔴 **空数组 ≠ 缺席,两者都要能被消费端分辨**(core 顶注逐字:"an empty array is *this leg
856
+ * declared no servers*, absence is an older mint or an external derivation")⇒ **真的**空数组
857
+ * 原样铸成 `[]`。把它折成缺席 = 把「我一台都没申报」这句**正面事实**说成「不知道」。
858
+ * 🔴 **但「过滤后为空」不是「原本就是空」**(异源对抗复审 r1 [medium] 采纳,真病):一份
859
+ * `[{name:'github', errorCode:'http_503'}]`(缺 `status`)进来时,唯一那一行被下面的逐行判据
860
+ * 丢掉 —— 若照样铸出 `[]`,消费端按本臂的义务④读到的是**「这条腿一台都没申报」这句肯定话**,
861
+ * 一份读不懂的回体就此被伪装成一个确定的零申报。⇒ **非空输入而零行幸存 ⇒ 整段缺席**
862
+ * (「我读不出来」不是「我知道是零」)。这与下面「逐条独立」并不矛盾:只要**还有幸存者**,
863
+ * 半张表仍是真读数,坏行照旧只丢自己。
864
+ * 🔴 **逐条独立**:一条坏行只丢自己(与 server 侧 `continue` 同判据),绝不因为一条坏行丢整表。
865
+ * 🔴 **逐键挑,禁 spread**:core 往条目上加第六键时必须由人显式处置 —— 而它下一个加的很可能又是
866
+ * 一个像 `error` 那样的自由文本面。
867
+ * ⚠️ `toolCount` 按**有限数**判(`Number.isFinite`),不按真值判:`0` 是合法读数(连上了、零工具),
868
+ * 折成缺席会让「连上了但没工具」与「没报」在消费端同形。
869
+ */
870
+ function projectMcpSection(raw) {
871
+ if (!Array.isArray(raw))
872
+ return undefined;
873
+ const rows = [];
874
+ for (const e of raw) {
875
+ if (typeof e !== 'object' || e === null || Array.isArray(e))
876
+ continue;
877
+ const r = e;
878
+ const name = typeof r.name === 'string' && r.name.length > 0 ? r.name : undefined;
879
+ const status = typeof r.status === 'string' && r.status.length > 0 ? r.status : undefined;
880
+ if (name === undefined || status === undefined)
881
+ continue;
882
+ rows.push({
883
+ name,
884
+ status,
885
+ ...(typeof r.source === 'string' && r.source.length > 0 ? { source: r.source } : {}),
886
+ ...(typeof r.errorCode === 'string' && r.errorCode.length > 0 ? { errorCode: r.errorCode } : {}),
887
+ ...(typeof r.toolCount === 'number' && Number.isFinite(r.toolCount) ? { toolCount: r.toolCount } : {}),
888
+ });
889
+ }
890
+ // 🔴 见顶注:`raw.length === 0` 是**正面事实**(照铸 `[]`);非空却零行幸存 = 读不出来 ⇒ 段缺席。
891
+ if (raw.length > 0 && rows.length === 0)
892
+ return undefined;
893
+ return rows;
894
+ }
833
895
  /**
834
896
  * `wiring_manifest.modelGate` 的形校验(core #524 / server S-86,server ≥7.58.0)。
835
897
  *
@@ -898,11 +960,16 @@ function projectAutoModeSection(raw) {
898
960
  function wiringManifestSupersetBody(ev) {
899
961
  const modelGate = projectModelGateSection(ev.modelGate);
900
962
  const autoMode = projectAutoModeSection(ev.autoMode);
901
- if (modelGate === undefined && autoMode === undefined)
963
+ // S-124(0.59.0):第三段。🔴 判「三段都不在场」用的是 `mcp === undefined`(**不是** `.length`)——
964
+ // 空数组是一句正面事实(「这条腿一台都没申报」),拿它当「没有这一段」会让 `mcp: []` 的帧连同
965
+ // 另两段一起落回 `not_in_slice`,而那正是 core 顶注点名要防的读法。
966
+ const mcp = projectMcpSection(ev.mcp);
967
+ if (modelGate === undefined && autoMode === undefined && mcp === undefined)
902
968
  return undefined;
903
969
  return {
904
970
  ...(modelGate !== undefined ? { _sema_modelGate: modelGate } : {}),
905
971
  ...(autoMode !== undefined ? { _sema_autoMode: autoMode } : {}),
972
+ ...(mcp !== undefined ? { _sema_mcp: mcp } : {}),
906
973
  };
907
974
  }
908
975
  function engineNoticeProjection(ev, ctx) {
@@ -82,6 +82,46 @@ export declare function engineCapTrue(baseUrl: string | undefined, key: string):
82
82
  * 要分辨成因请自己读 caps 对象;本口只答放行问题。
83
83
  */
84
84
  export declare function engineCapNestedTrue(baseUrl: string | undefined, parentKey: string, key: string): boolean;
85
+ /**
86
+ * 一格能力位上「**有没有值**」的四态(0.59.0,S-131 请托的通用半场)。
87
+ *
88
+ * 🔴 为什么必须是四态而不是「有 / 没有」:`/v1/capabilities` 上有**至少三种**不同的「读不出来」,
89
+ * 它们对运维是三条不同的下一步,折成一件就是把「不知道」渲成一个答案
90
+ * ([honest-absence-not-fabricated-zero]):
91
+ * · `unobserved` —— 这个 base 的 caps **一次都没落地**(没 kick / 还在飞 / 探测失败)。
92
+ * **不是**「引擎没有这个位」;
93
+ * · `not_reported` —— caps 在手,回体上**没有**这个键 ⇒ 老引擎。**不是**「这台部署没接」;
94
+ * · `null` —— 引擎**明确说** `null`。这是一个**正面事实**(上游用 `null` 表达「这台部署确实
95
+ * 没有这个设施」的位就走这一档),把它折进上面两档等于把引擎真给的答案丢掉;
96
+ * · `value` —— 有值(对象 / 数组 / 串 / 数 / 布尔,原样带出)。
97
+ * 🔴 **`value` 不做任何形校验**:形归各自的窄读器(单点铸判据),通用口再校一遍就会在上游 additive
98
+ * 加成员那天把一个合法值挡在包边界外,还让「谁把它窄没的」在排障时说不清。
99
+ */
100
+ export type EngineCapValueState = {
101
+ kind: 'unobserved';
102
+ } | {
103
+ kind: 'not_reported';
104
+ } | {
105
+ kind: 'null';
106
+ } | {
107
+ kind: 'value';
108
+ value: unknown;
109
+ };
110
+ /**
111
+ * 同步读口(**任意值 / 嵌套对象**):该 base 的 caps 里 key 上到底有没有值,四态诚实作答。
112
+ *
113
+ * 为什么要有它:`engineCapTrue` / `engineCapNestedTrue` / `engineCapString` 三口的返回域分别是
114
+ * `boolean` / `boolean` / `string|undefined` —— 它们**把四种「读不出」全折成 fail-closed 的一档**,
115
+ * 那对**放行**问题是对的(能力位缺席就别走那条车道),但对**读面/诊断**问题是错的:一格
116
+ * `sql: {engine,isolation,txnMode} | null`(server ≥7.60.0)用这三口读,`null`(这台部署确实没库)
117
+ * 与「老引擎不报」与「还没探到」在屏上会变成同一句话。S-131 那次因此在**壳里**长出了一份窄读器
118
+ * (归层债),本口是它在包侧的座位。
119
+ *
120
+ * 🔴 本口只答「有没有值」,**不答「值成不成形」** —— 后者归各能力位自己的窄读器
121
+ * (例:`sql` 的 {@link import("./sqlEngineCapability.js").projectSqlEngineCapability})。
122
+ * 两层刻意分开:通用口不认识任何一个具体位的形,窄读器不该重新实现一遍缓存与代际。
123
+ */
124
+ export declare function engineCapValue(baseUrl: string | undefined, key: string): EngineCapValueState;
85
125
  /**
86
126
  * 同步读口(字符串键):该 base 的 caps 里 key 的字符串值,未判/缺键/非字符串 ⇒ undefined。
87
127
  *
@@ -192,6 +192,37 @@ export function engineCapNestedTrue(baseUrl, parentKey, key) {
192
192
  return false;
193
193
  return parent[key] === true;
194
194
  }
195
+ /**
196
+ * 同步读口(**任意值 / 嵌套对象**):该 base 的 caps 里 key 上到底有没有值,四态诚实作答。
197
+ *
198
+ * 为什么要有它:`engineCapTrue` / `engineCapNestedTrue` / `engineCapString` 三口的返回域分别是
199
+ * `boolean` / `boolean` / `string|undefined` —— 它们**把四种「读不出」全折成 fail-closed 的一档**,
200
+ * 那对**放行**问题是对的(能力位缺席就别走那条车道),但对**读面/诊断**问题是错的:一格
201
+ * `sql: {engine,isolation,txnMode} | null`(server ≥7.60.0)用这三口读,`null`(这台部署确实没库)
202
+ * 与「老引擎不报」与「还没探到」在屏上会变成同一句话。S-131 那次因此在**壳里**长出了一份窄读器
203
+ * (归层债),本口是它在包侧的座位。
204
+ *
205
+ * 🔴 本口只答「有没有值」,**不答「值成不成形」** —— 后者归各能力位自己的窄读器
206
+ * (例:`sql` 的 {@link import("./sqlEngineCapability.js").projectSqlEngineCapability})。
207
+ * 两层刻意分开:通用口不认识任何一个具体位的形,窄读器不该重新实现一遍缓存与代际。
208
+ */
209
+ export function engineCapValue(baseUrl, key) {
210
+ if (!baseUrl)
211
+ return { kind: 'unobserved' };
212
+ const caps = capsByBase.get(baseUrl);
213
+ if (caps === undefined)
214
+ return { kind: 'unobserved' };
215
+ if (!(key in caps))
216
+ return { kind: 'not_reported' };
217
+ const v = caps[key];
218
+ // 🔴 `undefined` 与「键不在」同判 `not_reported`:JSON 上没有 `undefined` 这个值,一个显式
219
+ // `undefined` 只可能来自宿主自建管线,把它读成「有值」会让下游拿到一个空手。
220
+ if (v === undefined)
221
+ return { kind: 'not_reported' };
222
+ if (v === null)
223
+ return { kind: 'null' };
224
+ return { kind: 'value', value: v };
225
+ }
195
226
  /**
196
227
  * 同步读口(字符串键):该 base 的 caps 里 key 的字符串值,未判/缺键/非字符串 ⇒ undefined。
197
228
  *
@@ -159,11 +159,24 @@ export async function waitForParkRowBirth(deps) {
159
159
  }
160
160
  }
161
161
  };
162
+ // B-005(test [6497] 异源复审轨,cli 1.0.101 定谳):上一拍读面的**实测时延**。窗尾剩余若已不够再答一次
163
+ // (remaining < lastProbeMs),再起一拍只会被内部 deadline 切成 'deadline' 出站——那一拍一个字都没读到,
164
+ // 却让宿主(每拍先清 evidence 的形,cli askParkReopen)把此前成立的「读面答了、零属主行」证据抹掉
165
+ // ⇒ pendingRowGone 不置 ⇒ 陈旧 park 两选卡不出。窗尾不起注定超时的探测,按已有证据出站。
166
+ let lastProbeMs = 0;
162
167
  for (;;) {
163
168
  if (aborted())
164
169
  return { kind: 'aborted', waitedMs: now() - startedAt, probes };
170
+ {
171
+ const remainingBeforeProbe = budgetMs - (now() - startedAt);
172
+ if (budgetMs > 0 && probes > 0 && lastProbeMs > 0 && remainingBeforeProbe < lastProbeMs) {
173
+ return { kind: 'unborn', reason: lastReason, waitedMs: now() - startedAt, probes };
174
+ }
175
+ }
165
176
  probes += 1;
177
+ const probeStartedAt = now();
166
178
  const outcome = await probeWithinRemaining(probes, budgetMs - (now() - startedAt));
179
+ lastProbeMs = now() - probeStartedAt; // 瞬答(0)不设闸:只有真有时延的读面才可能在窗尾被切
167
180
  // 读面挂着的时候用户按了 Esc ⇒ 当拍停(判决优先于窗尽,两者都成立时中断更准确)。
168
181
  if (outcome === 'aborted' || aborted())
169
182
  return { kind: 'aborted', waitedMs: now() - startedAt, probes };
@@ -78,8 +78,8 @@
78
78
  * 🔴 方向纪律:reason/note 只做归因,绝不参与裁决;缺席 ⇒ 现状字节不变。
79
79
  */
80
80
  import { type GateCurrentPending, type HitlClientLike } from './hitlBridge.js';
81
- import { RULE_OFFER_MATCHES, RULE_OFFER_BATCH_MEMBER_KINDS, RULE_OFFER_UNCOVERED_REASONS } from '@sema-agent/sdk';
82
- import type { RuleSuggestion, ToolApprovalRespondAck, PersistedRuleAnchor as SdkPersistedRuleAnchor, RuleOfferMatch as SdkRuleOfferMatch, RuleOfferBatchMember as SdkRuleOfferBatchMember, RuleOfferUncoveredDetail as SdkRuleOfferUncoveredDetail, RuleOfferUncoveredReason as SdkRuleOfferUncoveredReason } from '@sema-agent/sdk';
81
+ import { RULE_OFFER_MATCHES, RULE_OFFER_BATCH_MEMBER_KINDS, RULE_OFFER_UNCOVERED_REASONS, RULE_OFFERS_ABSENCE_REASONS, DENIAL_LIMIT_KINDS } from '@sema-agent/sdk';
82
+ import type { RuleSuggestion, ToolApprovalRespondAck, PersistedRuleAnchor as SdkPersistedRuleAnchor, RuleOfferMatch as SdkRuleOfferMatch, RuleOfferBatchMember as SdkRuleOfferBatchMember, RuleOfferUncoveredDetail as SdkRuleOfferUncoveredDetail, RuleOfferUncoveredReason as SdkRuleOfferUncoveredReason, AskOrigin as SdkAskOrigin, RuleOffersAbsence as SdkRuleOffersAbsence, DenialLimitKind as SdkDenialLimitKind, DenialLimitFallback as SdkDenialLimitFallback } from '@sema-agent/sdk';
83
83
  /** fs 写权限 gate 判定:未来的一等 kind(tool_approval)或按 toolName(server 桥首批=fs 写三件,
84
84
  * [820] 表)。AskUserQuestion 永不进这里(ask 桥先判)。 */
85
85
  export declare function isFsApprovalGate(gate: {
@@ -190,6 +190,44 @@ export type RuleOfferUncoveredReason = SdkRuleOfferUncoveredReason;
190
190
  * 再导出让三端与包**共用同一份数组对象**,词表加员时一处改、四处跟。
191
191
  */
192
192
  export { RULE_OFFER_MATCHES, RULE_OFFER_BATCH_MEMBER_KINDS, RULE_OFFER_UNCOVERED_REASONS };
193
+ /**
194
+ * 一只 ask 的**出身** —— **sdk 8.3.0 `AskOrigin` 派生**(core `ASK_ORIGINS` 闭八词:
195
+ * `content_question` / `unresolvable` / `org_unavailable` / `org_rule` / `hook` / `ask_rule` /
196
+ * `denial_limit_fallback` / `policy`;**引擎在门上盖章**,不是策略的自称)。
197
+ *
198
+ * 🔴 **词表唯一属主是 core**,本别名只是型面供给(闭集给补全,`(string & {})` 让新词读得出来)——
199
+ * 端一律按**开集**分支:认不得的词渲「未知出身」,**绝不**渲成「没有出身」。
200
+ * 🔴 与 {@link ToolApprovalFrame.governanceForced} **不是**一回事(sdk 顶注逐字):本词答「哪一类
201
+ * 权威提的问」,那一位答「**本部署运维治理层**是不是这只 ask 的门」;`origin:"policy"` 覆盖
202
+ * 任何部署 ToolPolicy 的 ask ⇒ 两者顶替不了对方。
203
+ */
204
+ export type AskOrigin = SdkAskOrigin;
205
+ /**
206
+ * {@link ToolApprovalFrame.ruleOffersAbsence} 的闭三词 —— **sdk 8.3.0 `RuleOffersAbsence` 派生**
207
+ * (`mandated` / `shadowed` / `lane_cannot_speak`)。ADVISORY 展示元数据,**永不是裁决输入**。
208
+ */
209
+ export type RuleOffersAbsence = SdkRuleOffersAbsence;
210
+ /** {@link DenialLimitFallback.limit} 的闭二词 —— **sdk 8.3.0 派生**(`consecutive` / `total`)。 */
211
+ export type DenialLimitKind = SdkDenialLimitKind;
212
+ /**
213
+ * **auto 模式分类器的限额回落卡** —— **sdk 8.3.0 `DenialLimitFallback` 派生**(S-114 /
214
+ * core 7.4.0 #548;四成员全必填)。
215
+ *
216
+ * 🔴 `autoDenyAfterMs` **只用来渲倒计时**:窗的执行全在引擎,消费端(与 server)都**不得**据它
217
+ * 自铸第二只定时器 —— 两只窗重叠比原缺陷更坏且静默(sdk 顶注逐字)。
218
+ * 🔴 与 {@link ToolApprovalFrame.requiresRealApproval} 是**同一次铸造盖的孪生键**;
219
+ * **缺席不是断言**(绝大多数 ask 根本不是回落卡)。
220
+ */
221
+ export type DenialLimitFallback = SdkDenialLimitFallback;
222
+ /**
223
+ * 两张新闭词表的**运行期**再导出(sdk 8.3.0 `as const` 单源;与上面三张同一条纪律)。
224
+ *
225
+ * ⚠️ **本包自己不拿它们做窄读判定**,这与 `RULE_OFFER_MATCHES` 一族刻意不同,理由写在两处帧键的
226
+ * JSDoc 里:`origin` 与 `ruleOffersAbsence` 的词表属主是 core、server 侧已按闭集拒过词表外的值,
227
+ * 包再校一遍只会在 core 加员当天把一个**合法**值判没(`wiring_manifest.autoMode.reason` 的
228
+ * 透传纪律逐字同规)。再导出是给端的 `switch` 一份可数的表 —— 端仍必须带 `default` 臂。
229
+ */
230
+ export { RULE_OFFERS_ABSENCE_REASONS, DENIAL_LIMIT_KINDS };
193
231
  /**
194
232
  * **wire 上的**一条 offer —— 与上游 sdk/core `RuleOffer` 逐字同形(**没有** `offerIndex`)。
195
233
  *
@@ -565,6 +603,40 @@ export interface ApprovalCardRequest {
565
603
  /** 本地**收帧**时刻(`Date.now()`,与窗任一键同拍盖下)。壳读器明文优先这枚锚:卡口串行
566
604
  * 阻塞下,排队靠后的卡若按「呈现那一刻」起算余量,等待的时间会被整段还给窗。 */
567
605
  receivedAtMs?: number;
606
+ /**
607
+ * S-15 第五单(0.59.0;**单源:活卡帧腿**)——「**为什么这张卡没有『不再询问』这一格**」,
608
+ * 原样来自 {@link ToolApprovalFrame.ruleOffersAbsence}(闭三词,按开集读)。
609
+ *
610
+ * 🔴 **与 {@link ruleOffers} 引擎侧互斥**:两者同时在场 = 上游坏形,本包不合成、不裁决,原样上卡
611
+ * 让端看得见(包替上游把矛盾湮灭掉,排障时说不清是谁窄没的)。
612
+ * 🔴 **缺席不是断言**:缺席同时覆盖「有报价」与三扇结构门 ⇒ 端**禁**据缺席渲任何一句肯定话。
613
+ * ⚠️ `mandated` 臂**不得**指向「去写一条规则」(门是强制的,写了也消不掉)。
614
+ */
615
+ ruleOffersAbsence?: string;
616
+ /**
617
+ * S-114(0.59.0;**单源:活卡帧腿** —— durable park 行今天不携本键,见帧上同名键的 ⚠️ 记账)——
618
+ * 这只 ask 是 **auto 模式分类器的限额回落卡**,四成员全在的窄读结果
619
+ * (`{consecutive,total,limit,autoDenyAfterMs}`)。
620
+ *
621
+ * 🔴 **端的渲染判别位**:本键在场 ⇒ 渲「自动拒(限额回落)· 已连续拒 N / 累计 M,触的是
622
+ * (consecutive|total) 这道界」,并**必须**同时把 {@link requiresRealApproval} 的「这一次得真人批」
623
+ * 呈出来(两键孪生)。
624
+ * 🔴 **`autoDenyAfterMs` 只渲倒计时,绝不据它自铸定时器**(引擎已有一只窗;两只窗重叠比原缺陷
625
+ * 更坏且静默)。`0` = 不武装 ⇒ 不渲倒计时,**不是**「立刻拒」。
626
+ * 🔴 **四成员缺一整键不上卡**(与 server `readDenialLimitFallback` 的 `.strict()` 同判据):
627
+ * 半张卡上的计数会被人当成真实次数读。
628
+ */
629
+ denialLimitFallback?: DenialLimitFallback;
630
+ /**
631
+ * S-125③/#564(0.59.0;**单源:活卡帧腿**)—— 这只 ask 的**出身**,原样来自
632
+ * {@link ToolApprovalFrame.origin}(core 闭八词,按开集读;见 {@link AskOrigin})。
633
+ *
634
+ * 🔴 **缺席 = 老引擎 / 非 ask 路径,不是 `"policy"`**。
635
+ * 🔴 与 {@link governanceForced} 分工不互替(见帧上同名键)。
636
+ * ⚠️ 渲「自动拒(限额回落)」的判别位**首选 {@link denialLimitFallback} 对象**(它带计数与窗);
637
+ * 本词是分诊/审计座,单独在场只说明「这类门提的问」,给不出计数。
638
+ */
639
+ origin?: string;
568
640
  }
569
641
  /**
570
642
  * 🔴 **拆缝口** —— 弹「三选卡」并等人的决断。壳 = vendored CC `PermissionRequest`;
@@ -705,6 +777,47 @@ export interface ToolApprovalFrame {
705
777
  * 丢弃压紧过,且无兑付口 ⇒ 下标只是展示座),由 {@link surfaceFsApprovalAndDecide} 消费。
706
778
  */
707
779
  ruleOffers?: readonly WireRuleOffer[];
780
+ /**
781
+ * server ≥7.55.0 / core #490 修②(S-15 第五单;**ADDITIVE**,`"tool_approval"` only。
782
+ * 真发直证 = engine 7.60.0 fixture `@sema-agent/server/dist/tool-approval.d.ts` 的
783
+ * `ruleOffersAbsence?: RuleOffersAbsence` 声明 + 同包 `approval-card.js` 的 `readRuleOffersAbsence`
784
+ * 单点窄读;运行期锚直证 = sdk 8.3.0 `TOOL_APPROVAL_FRAME_KEYS` 27 项已含本键)——
785
+ * {@link ruleOffers} 的**缺席因由**:规则车道在场却无可给时,引擎点名**哪扇门关了**
786
+ * (闭三词 `mandated` / `shadowed` / `lane_cannot_speak`,见 {@link RuleOffersAbsence})。
787
+ *
788
+ * 🔴 **引擎侧与 {@link ruleOffers} 互斥**:有报价就没有本键,有本键就没有报价。
789
+ * 🔴 **缺席不是断言**:缺席同时覆盖「有 offers」与上面三扇结构门,一帧分不出来 ⇒ 端**禁**把
790
+ * 缺席渲成「这台部署没有规则车道」(那句话看 `capabilities.permissionRules`)。
791
+ * 🔴 **ADVISORY 展示元数据,永不是裁决输入**(sdk/server 两侧顶注同文)。
792
+ * 🔴 **本包按开集读、不做闭集校验**(与 `wiring_manifest.autoMode.reason` 的透传纪律逐字同规):
793
+ * 词表属主在 core,server 侧对词表外的值**已经**整键不铸 ⇒ 包再校一遍,只会在 core 加员当天
794
+ * 把一个合法值判没。型面上给了 {@link RuleOffersAbsence} 与运行期表 `RULE_OFFERS_ABSENCE_REASONS`,
795
+ * 端写 `switch` 必须带 `default` 臂。
796
+ * ⚠️ **`mandated` 臂端侧有指路义务**:那一臂**不得**把人指向「去写一条规则」——门是强制的,写了
797
+ * 也消不掉(server 顶注逐字)。
798
+ */
799
+ ruleOffersAbsence?: string;
800
+ /**
801
+ * server ≥7.57.0 / core 7.4.0 #548(S-114;**ADDITIVE**,`"tool_approval"` only。真发直证 =
802
+ * engine 7.60.0 fixture 同文件的 `denialLimitFallback?: DenialLimitFallback` 声明 + 同包
803
+ * `approval-card.js` 的 `readDenialLimitFallback`(`.strict()` 形校验,与 `card_json` 同一函数);
804
+ * 运行期锚直证 = sdk 8.3.0 `TOOL_APPROVAL_FRAME_KEYS` 27 项已含本键)——这只 ask 是 **auto 模式
805
+ * 分类器的限额回落卡**:分类器连续(缺省 3)或累计(缺省 20)拒到限额的**那一次**调用不再静默
806
+ * deny,而是落成一张**必须真人批**的卡。
807
+ *
808
+ * 🔴 与 {@link requiresRealApproval} 是**同一次铸造盖的孪生键** —— 端读到本键时那一位必然也在场;
809
+ * 反向不成立(安全类 ask 有别的出身)。
810
+ * 🔴 **四成员全必填**(core 形上没有可选位):少一位不是「老 core」而是**坏值**,server 整键不铸,
811
+ * 本包的窄读器 {@link readDenialLimitFallback} 同判 —— 绝不半铸一张让人误读计数的卡。
812
+ * 🔴 **`autoDenyAfterMs` 只用来渲倒计时**:窗的执行全在引擎(窗到即 deny 且盖
813
+ * `settledBy:"timeout"` / `resolution:"window_expired"` / `tool_end.autoDenied:true`)。
814
+ * 消费端**不得**据它自铸第二只定时器 —— 两只窗重叠比原缺陷更坏且静默(server/sdk 两侧同一条禁令)。
815
+ * `0` = 不武装(部署把旋钮关了,或 TOTAL 档的卡恒 0 等人)。
816
+ * 🔴 **缺席不是断言**:绝大多数 ask 根本不是回落卡。
817
+ * ⚠️ **耐久腿(park 行)今天不携本键**(core 7.4.0 的 `PendingAction` 上没有这一位)⇒
818
+ * durable 行 → 卡那条腿不 stamp,与 {@link requiresRealApproval} 同一格记账。
819
+ */
820
+ denialLimitFallback?: DenialLimitFallback;
708
821
  /**
709
822
  * server ≥7.13.0(#144 / core 5.25.0,[3438] 接力契约 / [3443] 主件;**ADDITIVE**,
710
823
  * `"tool_approval"` only。来源锚 = engine fixture `@sema-agent/server/dist/tool-approval.d.ts`
@@ -805,6 +918,23 @@ export interface ToolApprovalFrame {
805
918
  * run-durable-card-display-keys-test.mjs ⑨ 段;上游补位后按 {@link probeCause} 的双源合流形跟批。
806
919
  */
807
920
  requiresRealApproval?: true;
921
+ /**
922
+ * server ≥7.57.0 / core 7.5.0(S-125③/#564;**ADDITIVE**,`"tool_approval"` only。真发直证 =
923
+ * engine 7.60.0 fixture `@sema-agent/server/dist/tool-approval.d.ts` 的 `origin?: string` 声明;
924
+ * 运行期锚直证 = sdk 8.3.0 `TOOL_APPROVAL_FRAME_KEYS` 27 项已含本键)——**谁提的这一问**:
925
+ * core 在门上盖章的闭八词(见 {@link AskOrigin};server 逐字透传,**不自铸第二张资格表**)。
926
+ *
927
+ * 🔴 **缺席 = 老引擎 / 非 ask 路径,不是 `"policy"`**(端最容易犯的那一步):`policy` 是一个
928
+ * **正面事实**(这只 ask 出自部署 ToolPolicy),把缺席折进去等于替引擎编一句它没说的话。
929
+ * 🔴 **按开集分支**(词表属主是 core):认不得的词渲「未知出身」而不是「没有出身」。本包
930
+ * **不做闭集校验**,理由与 {@link ruleOffersAbsence} 同 —— 抄一份词表就是给自己立第二个判官。
931
+ * 🔴 与 {@link governanceForced} **顶替不了对方**:本键答「哪一类权威提的问」,那一位答
932
+ * 「**本部署运维治理层**是不是这只 ask 的门」;治理策略产的与普通策略产的在本词上同形。
933
+ * ⚠️ **端的渲染判别位**:`origin === 'denial_limit_fallback'` 与 {@link denialLimitFallback}
934
+ * 在场是同一件事的两个面(core 同一次铸造)—— 渲「自动拒(限额回落)」时**读对象**(它带
935
+ * 计数与窗),本词只作分诊/审计座。
936
+ */
937
+ origin?: string;
808
938
  /**
809
939
  * #341/[5214]③(server ≥7.46.0;E-14 / [4537]② Trojan Source 族;**ADDITIVE**,`"tool_approval"` only。
810
940
  * 来源锚 = engine 7.46.0 fixture `@sema-agent/server/dist/tool-approval.d.ts` 的 `inputHasBidi?: true`
@@ -874,7 +1004,7 @@ export interface ToolApprovalDelegation {
874
1004
  * `TOOL_APPROVAL_FRAME_KEYS` 比对——SDK additive 增键时对账当天红,不再人肉追平。
875
1005
  * 下面两个类型钉保证镜像与 interface 本身不可能漂移(少键/多键都是编译错)。
876
1006
  */
877
- export declare const TOOL_APPROVAL_FRAME_KEYS_MIRROR: readonly ["type", "approvalId", "toolCallId", "toolName", "sourceTaskId", "fromSubagent", "sourceAgentName", "message", "args", "argsOmitted", "governanceForced", "ruleSuggestions", "ruleOffers", "inputHasBidi", "parked", "persistedRuleShadowed", "probeCause", "ruleEvidence", "requiresRealApproval", "expiresInMs", "expiresAtMs", "serverNowMs", "delegation", "outcome"];
1007
+ export declare const TOOL_APPROVAL_FRAME_KEYS_MIRROR: readonly ["type", "approvalId", "toolCallId", "toolName", "sourceTaskId", "fromSubagent", "sourceAgentName", "message", "args", "argsOmitted", "governanceForced", "ruleSuggestions", "ruleOffers", "ruleOffersAbsence", "denialLimitFallback", "origin", "inputHasBidi", "parked", "persistedRuleShadowed", "probeCause", "ruleEvidence", "requiresRealApproval", "expiresInMs", "expiresAtMs", "serverNowMs", "delegation", "outcome"];
878
1008
  /**
879
1009
  * 子代帧判别:显式键 fromSubagent(core 1.378 RB-39②)优先;缺席退 sourceTaskId 在场性权宜式
880
1010
  * (server 1.258 [1549]①3,旧代际兼容)。