@sema-agent/client-core 0.55.0 → 0.57.0
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 +185 -0
- package/README.md +4 -1
- package/dist/adapt/arms.js +33 -0
- package/dist/adapter/downstream/eventToSdkMessage.d.ts +54 -0
- package/dist/adapter/downstream/eventToSdkMessage.js +110 -35
- package/dist/engineErrorCodes.d.ts +44 -0
- package/dist/engineErrorCodes.js +64 -0
- package/dist/hitl/toolApprovalWire.d.ts +48 -0
- package/dist/hitl/toolApprovalWire.js +24 -6
- package/dist/seam.d.ts +31 -1
- package/dist/seam.js +6 -0
- package/dist/subagent/engineSubagentResume.d.ts +82 -6
- package/dist/subagent/engineSubagentResume.js +19 -2
- package/dist/wireErrorTriage.d.ts +73 -0
- package/dist/wireErrorTriage.js +40 -1
- package/docs/INTEGRATION-CLIENTS.md +254 -8
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -49,6 +49,191 @@
|
|
|
49
49
|
> 挡住 ⇒ 本批把它机械化——④a0 对 `pending` 行**要求段头已是日期形**(`(未发布)` 直接红),阶段一
|
|
50
50
|
> commit 漏转在发布前就红,不再靠人记。
|
|
51
51
|
|
|
52
|
+
## 0.57.0(2026-09-05)
|
|
53
|
+
|
|
54
|
+
> 内容批 D(2026-09-05,隔离树交付):三件 —— L-103/[C228] 窄读器公面化、L-102 resume 时间性拒绝
|
|
55
|
+
> 判型上收、[C233]① SDK 型面缺口登记。**本段只记内容**:`package.json.version` 未动、README Version
|
|
56
|
+
> 行未动、`FROZEN` 未动 —— bump 与段头转日期形归**发包批**(阶段一义务见本档头注)。
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
### 新增 —— 「不再询问」候选窄读器的公面(L-103 / [C228]、[C233]②;additive 导出,语义零变化)
|
|
60
|
+
|
|
61
|
+
两代 wire 键(`ruleOffers` server ≥7.46.0 / 退役键 `ruleSuggestions` ≤7.45)的归一窄读器此前是
|
|
62
|
+
**模块内私有函数**,包外唯一读得到归一形的路径是卡端口(`ApprovalCardRequest.ruleOffers` /
|
|
63
|
+
`.ruleOffersReadOnly`)。不走卡端口架构的宿主(浏览器端自己拿帧渲,没有 Ink 三选卡)因此只能在
|
|
64
|
+
自己那边**重铸一遍** —— 而这把读器承载的是**兑付安全**判据,不是格式化:
|
|
65
|
+
|
|
66
|
+
- 逐条丢坏、**原始 wire 下标不前移**(压紧 = 人点的第 k 个与服务端兑的第 k 个指向两条不同规则);
|
|
67
|
+
- `kind` 是闭集判别位(不认识的臂不猜、不降级成 `single`);
|
|
68
|
+
- 两代取舍序(新键在场即定局、有载体读不出**不**回落旧键、`null` ≡ 缺席)。
|
|
69
|
+
|
|
70
|
+
新公面(两个函数,**签名与函数体一字未改**,只是加了 `export`):
|
|
71
|
+
|
|
72
|
+
- `readRuleOffers(v)` —— 只读新键;
|
|
73
|
+
- `readRuleOfferSupply(offers, legacy)` —— 两代键的取舍口,三端该调的那一个
|
|
74
|
+
(0.56.0 及更早的内部名是 `readOfferSupply`,**纯改名**,零外部消费点)。
|
|
75
|
+
|
|
76
|
+
归一形 `RuleOffer` / `RuleOfferMatch` / `RuleOfferBatchMember` 与入参形 `WireRuleOffer` 照旧导出 ——
|
|
77
|
+
端拿得到值也拿得到形,不必再自铸一份会漂的同名结构。
|
|
78
|
+
|
|
79
|
+
🔴 **`offerIndex` 的定义域随腿不同**,消费前必读 §20c:活卡帧腿上它是合法**选择键**
|
|
80
|
+
(批臂按它回兑 `persistRule.batchOfferIndex`),durable 行腿上它只是展示/对账座(该腿无兑付口)。
|
|
81
|
+
|
|
82
|
+
### 新增 —— resume 族「时间性拒绝」二码的判型(L-102;server ≥7.47.0 / ≥7.51.0,SDK 8.1.0)
|
|
83
|
+
|
|
84
|
+
`resume.*` 这一族的九个成员里,`resume.usage_window_exhausted`(部署治理窗满)与
|
|
85
|
+
`resume.preflight_rejected`(部署自己的 resume 预检拒)是**唯一带得出「等多久」**的两个:409 体
|
|
86
|
+
additive 携 `retryAfterSec`(秒,`ceil`,下限 1)。这是它们被收成一个闭集的**全部理由**。
|
|
87
|
+
🔴 **闭集不是「哪些码可以等」的名单**(异源对抗复审第三轮 [medium] 真病修 —— 本段初稿写成
|
|
88
|
+
「只有这两码可能是『过一会儿行』」,是排他性错断):族内有明确反例 `resume.row_recycling`
|
|
89
|
+
(core 铸文逐字 "this clears on its own; send again in a moment",本包处置 `row-contended`)——
|
|
90
|
+
它可等,只是 server 给不出秒数。⇒ 新读口返回 `null` 只意味着**没命中这两码**,不意味着「等也没用」。
|
|
91
|
+
🔴 命中之后也**不等于一定可等** —— 后一码另有一条 `terminal` 臂(行已被结清、token 不可再赎),
|
|
92
|
+
所以处置不是一格而是**按证据分两格**,见下。
|
|
93
|
+
|
|
94
|
+
修前:本包的 `classifySubagentResumeFailure` 把两码双双落进开集兜底 `error` ⇒ server 明明给了等待窗,
|
|
95
|
+
到端只剩一句泛泛失败;而壳侧自己手接了一份判型 —— 判定长在端里,三端各写一遍必然各错一遍
|
|
96
|
+
(与 0.38.0 收 `resume.row_recycling` / `resume.row_gone` 那次同形)。
|
|
97
|
+
|
|
98
|
+
- 新词表(`src/engineErrorCodes.ts`):`RESUME_USAGE_WINDOW_EXHAUSTED` / `RESUME_PREFLIGHT_REJECTED` +
|
|
99
|
+
闭集 `RESUME_RETRY_LATER_CODES`。**闭的不是「`resume.*` 有几个码」**(那仍是开集),闭的是
|
|
100
|
+
「server 在哪些码上铸 `retryAfterSec`」;**绝不放宽成 `resume.` 前缀判**。
|
|
101
|
+
🔴 形制 = **`Object.freeze` 的只读数组,不是 `ReadonlySet`**(异源对抗复审 [medium] 真病修):
|
|
102
|
+
后者只在类型面只读,运行期就是普通 Set 而判定查的是**同一个实例** ⇒ 公面消费者一行
|
|
103
|
+
`.add('resume.row_gone')` 就能把一条「等也没用」的拒绝翻成带窗的 `retry-later`(实测)。与 #363
|
|
104
|
+
二轮把 `interactiveHalt.RUN_LEVEL_STOP_ERROR_CODES` 改冻结数组是**同一条已定谳的病形**。
|
|
105
|
+
⚠️ **同形存量登记(只登记不顺手改)**:同文件 `CONFIG_REFUSAL_CODES` / `DELEGATION_CAP_CODES` /
|
|
106
|
+
`TOOL_END_INTERRUPTED_CODES` 三张表仍是 `ReadonlySet`,同病;它们已在公面上且消费点用 `.has()`
|
|
107
|
+
⇒ 换形是下游 **BREAKING**(签名变更),不属内容批射程,**另立一批**。
|
|
108
|
+
- 新读口(`src/wireErrorTriage.ts`):`resumeRetryLaterFromError(err)` → `ResumeRetryLaterDetail | null`。
|
|
109
|
+
结构读不 `instanceof`(客户端是宿主注入的,跨 bundle 的同名类是两个类);键位只认 `errorCode`。
|
|
110
|
+
`retryAfterSec` 的窄读域 = **server 的铸键域**(整数 ∧ ≥1),`0` / 负数 / 小数 / `NaN` / 串一律
|
|
111
|
+
**降缺席**(不降 0)——放行 `0` 就是对消费端说「立刻重试」,而 resume 是 AT-MOST-ONCE 的有副作用动作。
|
|
112
|
+
- 🔴 **「命中本族」不等于「可等」**(异源对抗复审 [medium] 真病修,本批当场改形):
|
|
113
|
+
`resume.preflight_rejected` 在 core 侧有两条臂 —— `retry_later`(行留 pending,同一 token 可再赎)
|
|
114
|
+
与 `terminal`(行已被单发 expire CAS 结清,**token 不可再赎**),而**判别位在 message 散文里**。
|
|
115
|
+
仅凭码就宣告「稍后重试」会把一个终局说成暂时等待;而按文案分臂又正是上游改一个词就静默空转的形。
|
|
116
|
+
⇒ 读口新增**处置位** `waitable`(全包对这个问题的单一判断点),只认**正向证据**:
|
|
117
|
+
`resume.usage_window_exhausted` 恒 `true`(core 铸文的不变量就是证据);`resume.preflight_rejected`
|
|
118
|
+
只在 server 真给了窗时 `true`。`false` 读作**不可判**,不是「不可重试」。
|
|
119
|
+
- 分类器新增**两格**(`SubagentResumeFailureKind`):有证据的落 `retry-later`,无证据的预检拒落
|
|
120
|
+
`refused-preflight`(端说两臂都成立的那句 —— 「这一拒发生在提交之前,你的决定没被消费」,
|
|
121
|
+
把「还能不能再赎」交给引擎那行原文)。判决与 `resumeSettledSubagent()` 的失败臂 additive 携
|
|
122
|
+
`code?` 与 `retryAfterSec?`;**既有各格零挪位**,本层照旧**一格都不重试**。
|
|
123
|
+
- `classifySubagentResumeFailure` 的返回位由两成员内联形抽成具名 `SubagentResumeFailureVerdict`
|
|
124
|
+
(结构逐字兼容,TS 结构化类型 ⇒ 消费端零差异)。
|
|
125
|
+
|
|
126
|
+
### 登记 —— SDK 型面缺口([C233]①,只记账不改码)
|
|
127
|
+
|
|
128
|
+
SDK 8.1.0 的 `ToolApprovalsResource.respond` 体型只锚到 `persistRule: { rule: string }`,server ≥7.46.0
|
|
129
|
+
已发的 `edited` / `batchOfferIndex` 两位没跟;同族锚滞后还有 `ToolApprovalFrame.ruleOffers` /
|
|
130
|
+
`PendingCheckpoint.ruleOffers` 与 ack 的三个回显位。🔴 **本包侧零 cast、也不需要 cast** —— 本包不铸
|
|
131
|
+
respond 的 wire 体(`RespondToolApprovalFn` 是宿主注入的函数型,包只交扁平兄弟位),吃这个缺口的是
|
|
132
|
+
三端的注入面。登记进 `docs/INTEGRATION-CLIENTS.md` §7d **P-44**,正位解在 sdk。
|
|
133
|
+
|
|
134
|
+
### 门(三件同批)
|
|
135
|
+
|
|
136
|
+
- 新门 `scripts/run-rule-offers-reader-test.mjs`(109 判据):公面在场(d.ts 导出形)· 两代键各一正控 ·
|
|
137
|
+
逐条丢坏且原始下标不前移(头/中/尾三个方向 × ****十九条丢坏路径各走一遍** —— 坏 single 两形 / 坏 batch 十三形(rules 非数组·空·超帽 + 成员十形:缺 segment / rule 空串·非串 / match 表外词 / 缺 command·空串 / segment 空串·非串 / 成员 null·undefined·非对象)/ 非对象 / null / 不认识的 kind,外加旧键归一臂七形 —— **后两族专门够 `readRuleTriple` 自己那两道守卫**(外层的 null/非对象过滤在 offer 级就把它们拦掉了,成员级与旧键项才喂得到);三轮变异实发的逃逸全在这一格上:只拿「不认识的 kind」当坏条时读器里另外几条 `continue` 改成整只 `return undefined` 照样全绿、成员窄化换成裸 cast 照样全绿、删掉 `readRuleTriple` 的载体守卫照样全绿(而那一形会当场抛 TypeError))· batch 下标语义与成员不逐条丢 · `uncoveredSegments`
|
|
138
|
+
六种坏值 · 空数组/非数组/超帽/全坏 · 两代取舍序(含 `null` ≡ 缺席的明示裁定)· **单一铸点对账**
|
|
139
|
+
(公面读口与活卡帧腿/durable 行腿两条卡端口出口对同一份素材逐字相等)· 纯函数不改写入参。
|
|
140
|
+
- 新门 `scripts/run-resume-retry-later-test.mjs`(115 判据):两码正控(带窗/不带窗)· 闭集恰两员 ·
|
|
141
|
+
负控 16 形(同族七个「等也没用」的码、429 同名不同门、两个下划线族、文本引用、坏载体)·
|
|
142
|
+
`retryAfterSec` 十一种脏值消毒 + 下边界 1 · `waitable` 的证据判据(治理窗满恒 true / 无窗预检拒
|
|
143
|
+
必须 false / 脏窗消毒后不许升格)· **闭集运行期改不动**(试改 + 试改后判定没漂,两半都断言)·
|
|
144
|
+
分类器两格分立与既有各格零挪位 · 未知码上的窗不被采信 · **`null` ≠「等也没用」的可执行反钉**
|
|
145
|
+
(同族 `resume.row_recycling` 在读口上是 `null`、在分类器上却必须落可等的 `row-contended`)·
|
|
146
|
+
端到端三形(带窗 / 无窗预检拒 / 「等也没用」的码)且**一格都不重试**。
|
|
147
|
+
- 两门同批登记 `scripts/gates-manifest.json` + README `Guards` 表(公开文案)。
|
|
148
|
+
- 登记面:`scripts/public-export-baseline.json` 815→821(六个新公面名)· `docs/refactor/p1-scan/singleton-manifest.json` 279→280(`RESUME_RETRY_LATER_CODES` 只读词表,dupRisk low,high 上限不动)·
|
|
149
|
+
`scripts/run-client-core-typeshape-test.mjs` 的 `unknownExport` 棘轮 268→272(四个边界读口入参位,
|
|
150
|
+
逐条登记理由;**B4 恒 20、裸 unknown 返回恒 0 不动**)· `docs/INTEGRATION-CLIENTS.md` §0a/§2/§2b 计数。
|
|
151
|
+
|
|
152
|
+
### 🔴 老宿主必读
|
|
153
|
+
|
|
154
|
+
`SubagentResumeFailureKind` 从八员变**十**员(`retry-later` + `refused-preflight`):
|
|
155
|
+
**结构形免动**(读 `reason`/`detail` 的宿主一字不用改),
|
|
156
|
+
但对该联合做**穷尽 switch**(`never` 兜底)的宿主提货时会编译红 —— 那是设计:一个新的处置落进
|
|
157
|
+
`default` 而无人处理,就是把「等一会儿就好」渲成「失败了」。同 0.55.0 `RetryStatus` 那条老宿主纪律。
|
|
158
|
+
|
|
159
|
+
### 已知局限(本版新增)
|
|
160
|
+
|
|
161
|
+
- **本包不铸 `ResumeRetryLaterError`,也不 `instanceof` 它**:该类是 SDK 8.1.0 的,而本包 peer 地板
|
|
162
|
+
仍是 `>=7.4.0`。挂在 ≤8.0 SDK 上的宿主,这两码走的是无字段的族基类 ⇒ `retryAfterSec` 在错误对象上
|
|
163
|
+
根本不存在,本包如实报缺席(**缺席 ≠ 0**)。
|
|
164
|
+
- **`resume.preflight_rejected` 的两条上游臂(`retry_later` / `terminal`)在客户端分辨不出来** ——
|
|
165
|
+
判别位在 message 散文里,本包**不按文案分臂**,改按**正向证据**分格(见上)。所以 `refused-preflight`
|
|
166
|
+
这一格的语义是**不可判**而不是「不可重试」:端只说两臂都成立的那句,「还能不能再赎」交给引擎那行
|
|
167
|
+
原文。**候上游给机读判别位**(有了它这一格就能再分成两格,届时是 additive 加员)。
|
|
168
|
+
|
|
169
|
+
---
|
|
170
|
+
|
|
171
|
+
## 0.56.0(2026-09-05)
|
|
172
|
+
|
|
173
|
+
> 发包批阶段一(2026-09-05,主会话):`package.json` 0.55.0→0.56.0、README Version 行、本段头转日期形、`FROZEN` pending 行同批;内容由 1.0.100 提货车 B 在隔离树交付,主会话收货补包侧门与 eventId 修钉(见「门与收货修钉」)。
|
|
174
|
+
|
|
175
|
+
|
|
176
|
+
### 新增 —— `wiring_manifest` 帧的两段用户面事实(L-70 / L-108②③;core #524 + core 147③,server ≥7.58.0)
|
|
177
|
+
|
|
178
|
+
`wiring_manifest` 此前与两条 HITL 帧并列走 `nothing('not_in_slice')`。那句话对整份 manifest 的
|
|
179
|
+
**其余每一段**今天仍然成立 —— 本批只把两段**终端用户面**的事实取出来:
|
|
180
|
+
|
|
181
|
+
- `modelGate?: { class, removed[], restore }` —— 「本 run 被模型门卸掉了哪些工具、怎么恢复」。
|
|
182
|
+
**仅真有门卸时在场**(core 只在卸的时候铸段),缺席 = 没卸。
|
|
183
|
+
- `autoMode?: { armed, reason }` —— 「auto 到底武没武装、为什么」。effective 腿恒在,缺席 =
|
|
184
|
+
老 mint / 外部 derive **没报**。
|
|
185
|
+
|
|
186
|
+
新公面:
|
|
187
|
+
|
|
188
|
+
- `wiringManifestSupersetBody(ev)`(纯函数)+ `WiringManifestModelGate` / `WiringManifestAutoMode`
|
|
189
|
+
两个类型;
|
|
190
|
+
- 内部臂 `wiring_manifest`(`INTERNAL_SDK_ARM_TYPES` 加员,载两个 `_sema_` 超集键);
|
|
191
|
+
- chrome 臂 `wiring_manifest`(`WiringManifestChromeEvent`,`CHROME_ARM_TABLE` 加员,`required:false`)。
|
|
192
|
+
|
|
193
|
+
### 门与收货修钉(主会话收货,2026-09-05)
|
|
194
|
+
|
|
195
|
+
- 新门 `scripts/run-wiring-manifest-projection-test.mjs`(36 判据;gates-manifest / README Guards 同批登记):
|
|
196
|
+
施工车只在壳侧写了门,包侧两发变异(`removed: []` 当真读数 / 子流断闸删除)在当时的 `npm test` 下
|
|
197
|
+
**全绿逃逸** —— 包是三端公共抽象,判据必须在包里。两发变异对新门当场红,复原绿。
|
|
198
|
+
- 修钉:`wiring_manifest` 投影臂原来**没带 `eventId`**,适配层 `wiringManifestArm` 那句透传永远是空
|
|
199
|
+
(新门 A1c 红先抓出)。现与别的臂同律:wire 带就上行、缺席键不在场。
|
|
200
|
+
|
|
201
|
+
### 🔴 三条读法(写在臂注里,是消费端的硬约束)
|
|
202
|
+
|
|
203
|
+
1. **帧是 FLAT 的**:读 `ev.modelGate` / `ev.autoMode`,**不是** `ev.manifest.*`。6.7.0 契约声明过的
|
|
204
|
+
那层 `manifest:{...}` 嵌套 wire 上从未有过(SDK `events.d.ts` 该臂头注 2026-08-07 已按真 capture 更正);
|
|
205
|
+
server 把 core `TaskEvent` 的 `ev.manifest` 过白名单 builder 后 **spread 进帧**,三条腿
|
|
206
|
+
(live SSE / durable 账本行 / resume 重放)同源。
|
|
207
|
+
2. **缺席不可反推**:两段缺席都**不许**渲成肯定句(「没有工具被卸掉」/「auto 未武装」)。
|
|
208
|
+
两段都不成形时本臂**根本不铸**(不产空臂)—— 所以宿主见到本臂就至少有一段是真读数。
|
|
209
|
+
3. **`autoMode.reason` 六词逐字呈现,绝不映射** `/v1/capabilities.permissionModeAuto.reason` 的六词:
|
|
210
|
+
两套词表**同名不同义**(`settings_denied` 在 capabilities 那边折 `no_intent` 不折 `denied`),
|
|
211
|
+
归一会造出一个假读数。`modelGate.restore` 同理原样呈现 —— 宿主自己拼一句恢复办法,
|
|
212
|
+
等于替引擎编了一条它没说过的出口。
|
|
213
|
+
|
|
214
|
+
### 形校验(两段各自独立,一段坏不牵连另一段)
|
|
215
|
+
|
|
216
|
+
- `modelGate`:`class` / `restore` 非空串 **且** `removed` 是全串数组 **且** 过滤空串后**非空**,
|
|
217
|
+
三件缺一整段不收(不折 `''`、不折空数组、不编默认恢复办法)。空 `removed` 与「没卸」在屏面上
|
|
218
|
+
不可区分,而 core 的契约是「有门卸才铸段」⇒ 它是坏值不是真读数。
|
|
219
|
+
- `autoMode`:`armed` 是 boolean **且** `reason` 非空串 **且** 满足 server 侧同一条互证式
|
|
220
|
+
`armed === (reason === 'armed')`。照抄这条不是「重复上游」——**非投影口**(宿主自建管线 /
|
|
221
|
+
重放存量转录)喂进来的帧不过 server,一个 `{armed:true, reason:'settings_denied'}` 会让消费端
|
|
222
|
+
同时读到「武装了」和「被拒了」。
|
|
223
|
+
- 子流断闸(带 `parentToolCallId` 的自述不上 leader 面)按「**键在不在**」判、不按「是不是串」判 ——
|
|
224
|
+
与 `text_segment_end` 同因:本臂决定的是「这份自述算谁的」,一个坏值会把子代的门卸清单
|
|
225
|
+
擦成 leader 的,用户会以为**自己这一 turn**少了工具。
|
|
226
|
+
|
|
227
|
+
### 已知局限(本版新增)
|
|
228
|
+
|
|
229
|
+
- **超集键名是临时形**:`modelGate` / `autoMode` 今天**不在**已发布 SDK(8.1.0)的 `AgentEvent`
|
|
230
|
+
wiring_manifest 臂里(全树零命中,亲验)⇒ 本包用 `_sema_` 前缀而不自铸 SDK 同名形。SDK 声明该臂
|
|
231
|
+
之后改名是 **BREAKING**,另立一批。投影层那行 `as unknown as Record<string, unknown>` 就是
|
|
232
|
+
「上游类型还没到货」的可见落点,删掉它编译器会立刻指出所有该跟着改的地方。
|
|
233
|
+
- **整份 manifest 仍不投影**:leg / ask / question / interaction / elicit / parkLane / session /
|
|
234
|
+
fleet / permissionRules / governance / configFingerprint 十一段一件没接。想要它们的宿主今天
|
|
235
|
+
只能走 operator 面的 `GET /v1/diagnostics/wiring`(且那面 403 门另有前提)。
|
|
236
|
+
|
|
52
237
|
## 0.55.0(2026-09-05)
|
|
53
238
|
|
|
54
239
|
### `waiting_first_token` —— 等第一个 token 是一种**状态**,不是一次错误(core 7.3.0 / #530;cli B-003 / L-108①)
|
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.
|
|
38
|
+
**Version:** 0.57.0
|
|
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
|
|
@@ -264,6 +264,9 @@ public-surface guard checks that last one).
|
|
|
264
264
|
| `scripts/run-additive-key-passthrough-test.mjs` | The one disease shape behind two legs: a **closed whitelist / flattening arm** dropping a fact that is already on the wire, while both sides of the seam look correct. (1) The `task_progress` projection carries a registered **key ledger** — a frame populated with every key the service really projects is pushed through the shipped `eventToSdkMessage`, and the set of wire keys that survive must equal the registered pass-through list **name for name in both directions**, so quietly forwarding one more key is as red as quietly dropping one. `model` (the child run's model id, minted by core as `prepared.model.id` and projected by the server since 7.52.1) is the key this batch adds, with the same conditional the server itself applies: a non-empty string or no key at all — an empty string is neither a model id nor "unknown". The ledger is also checked against the fenced list in `docs/INTEGRATION-CLIENTS.md` §3d, so a doc that still says seven keys while the code forwards eight is red rather than merely stale. (2) The decide-failure arms carry the server's S-02 `currentPending` pointer key from a 409 `approval_stale` refusal onto the outcome the host reads. The reader is structural rather than `instanceof`, because the client is host-injected and the class identity is not this package's to assume; a half triple never mints (half a pointer cannot relocate anything), an empty string is not presence, and `checkpointToken` never transits. Both the allow and the deny leg are driven end to end through the real durable approval path — as is the accept-session leg, where a refusal carrying the pointer key must now re-raise instead of silently re-sending the human's answer for the **old** card as a plain approve (one decide call, pointer preserved), while a legacy 400 still falls back exactly as before — and all three flattening points must call the one shared reader — the same-shape residue check that makes "fixed one arm and left the twin" red instead of invisible |
|
|
265
265
|
| `scripts/run-esc-halt-plan-test.mjs` | The Esc stop decision every client shares: fire the **turn-level** halt first, and escalate to a **run-level** cancel in exactly two cases — the engine itself answered with a 409 from the closed code set (it is saying "there is no in-flight turn here; use cancel for a run-level stop"), or that shot came back with no verdict at all *and* the shell can independently prove a permission card was on screen. Everything else does not escalate. The asymmetry is the whole point and every negative control guards the same direction — deciding *not* to escalate costs the user one more choice on a busy-session card (recoverable), deciding to escalate wrongly tears down a run that was alive and takes every in-flight tool with it (not). So: the closed code set is a **frozen** value, not a `ReadonlySet` — type-level immutability does not stop a consumer's `.add()`, and the guard proves it by really trying to mutate the exported value and then checking the verdict did not drift; the escalation gate is the **conjunction** of that closed set and the 409 status, since honouring the code alone lets a 500 that merely quotes it drive a destructive call; `interrupt.not_held` and `steering.not_running` are deliberately outside the set (the first means *this replica* has no live face — the run may be perfectly alive on another); an unreadable code falls to the no-escalation side; a `parked` flag never overrides a verdict the engine did give, and only strict `true` counts when it did not. The first shot is unconditional by construction — it does not consult `parked`, because the 409 it earns is exactly the verdict the gate wants — and the verdict itself is a closed machine-readable reason word, not display copy |
|
|
266
266
|
| `scripts/run-peer-frame-projection-test.mjs` | The three engine-injected lanes design/385 puts on the **one** `task_notification` carrier, which are not the same kind of thing at all: a delegated child's uplink (`agentMessage`), another session's message drained from this session's own box (`crossSessionMessage`), and a receipt about one of *this* session's own outbound messages (`crossSessionNotice`). The engine renders none of them inside a `<task-notification>` shell, so a client that projects them as the generic completion card shows "background task finished" while the model read a colleague's sentence — two faces describing different events. The discriminator is pinned to the **typed carrier being present**, never to the `summary` text: those carriers can only be minted by the engine's injection legs (the external `notify()` input is a strict subset of the payload and can wear none of them), while `summary` is filled by every notification there is — so anchoring on text would let any background task impersonate a colleague's message by writing `<agent-message from="…">` into its own summary, and a positive control asserts exactly that payload still projects as the generic card. Fail-closed has two tiers rather than one: a broken **required** field (empty `from`, a non-string `body`, a notice `kind` outside the closed set) returns absence so the caller falls back to the generic card — an honest downgrade where the user still sees the notification — while a broken **optional** field drops only itself, because losing an attribution note and losing a colleague's whole message are not the same magnitude. The provenance side record is **required and must agree on four points** (`kind` matches the lane; `from`/`taskId`/`seq` are present and equal the carrier/payload — each equality is anchored on a core mint site and pinned by the cli wire-anchor A-K24), so a carrier signed with a trusted name but a disagreeing provenance falls back to the generic card; peer bodies pass the same authority-envelope neutralization core applies (`<task-notification>` etc. are defused) so a colleague's text can never seed the resume dedup ledger. Lane precedence copies the engine renderer's own order, because the model already read the frame in that order and a client ordering of its own would put a card on screen that disagrees with the frame the model saw. Rendering and parsing of the transcript line live in the same module and are round-tripped in both directions, including a body carrying a forged closing tag (a parser fooled there hands half a message to the next row) and a quote inside the sender label (which must not forge a second attribute); the notice lane is deliberately kept **out** of the parser, since recognising it would mean anchoring the `[Cross-session …]` prefix and a user typing that same line would be rendered as engine speech. Hostile carriers are read as own **data** descriptors only and accessors are never invoked at all — `catch` catches throwing, not never returning — proven by a counting getter that must stay at zero calls, alongside a revoked proxy and a prototype-only carrier; and four legacy payload shapes assert the no-carrier path is byte-identical to before, which is the executable form of "zero difference for an older host" |
|
|
267
|
+
| `scripts/run-wiring-manifest-projection-test.mjs` | The two end-user facts carried on the engine's `wiring_manifest` frame (`modelGate`: which tools this run's model gate removed and the verbatim restore hint; `autoMode`: whether auto mode is actually armed and the engine's own reason word). Projection: both sections ride as `_sema_`-prefixed superset keys, verbatim, and no SDK-named key is minted; a frame where neither section is well-formed projects to `none/not_in_slice` (no empty arm); `modelGate` needs all three keys and treats `removed: []` as a bad value rather than a reading; `autoMode` needs a boolean plus a non-empty reason that agrees with it, and the reason word is never mapped onto the capabilities vocabulary; the frame is flat (a nested `manifest:{}` wrapper is not a supply); `eventId` rides like every other arm. Adapter: exactly one chrome event on the main lane, a sub-flow frame (any `parentToolCallId`, `null` included) yields nothing, and an absent `eventId` leaves the key absent. Added at receiving time because the shell-side gate could not see this package's behaviour: two mutations (empty `removed` accepted, sub-flow gate removed) had passed the package suite untouched |
|
|
268
|
+
| `scripts/run-rule-offers-reader-test.mjs` | The narrowing reader behind the "don't ask again" options, now a public entry point rather than a card-port-only one. Hosts that render the frame themselves (a browser has no three-way terminal card) previously had to rebuild this reader on their side, and what it carries is a **redemption-safety** judgement, not a convenience: the batch arm is redeemed by **index**, so a reader that compacts the array after dropping a malformed entry makes the k-th option a person clicked and the k-th rule the server writes two different rules. So: a bad entry is dropped **on its own** (one bad option must not make a real one disappear) while every surviving entry keeps its **original wire index** — pinned from both ends, with the bad entries leading and trailing. A batch's *members* are the opposite: any malformed member drops the whole batch, because a conjunctive batch is one "yes" to all of them and a batch missing a member is a different grant; its honest-remainder count is a reading, not decoration, so a non-integer or negative value drops the batch rather than rendering a fabricated zero. An empty array, a non-array, an over-cap array and an all-bad array all read as **absence** rather than an empty list, because an empty list renders as "there is an option lane with nothing in it". The two wire generations are ordered by a rule, not a preference: the newer key wins outright, a newer key that is **present but unreadable** does not fall back to the retired key (borrowing the older material would pass someone else's options off as this request's), and a `null` newer key reads as absence so a relaying layer that serialises "missing" as null cannot delete the whole lane on older engines. The public entry is finally reconciled against **both** card-port legs on the same material, byte for byte, so the exported reader and the one the card sees can never become two |
|
|
269
|
+
| `scripts/run-resume-retry-later-test.mjs` | The two resume refusals that carry a **wait quantity** — the only members of that refusal family that do, which is the whole reason they form a closed set. Carrying a wait is not the same as being the only ones worth waiting on: a sibling refusal in the same family clears on its own and the engine says so in words, it just cannot put a number on it, so *not recognised here* must never be read as *waiting will not help*. One of the two also has a *terminal* upstream branch that arrives under the same code with the distinguishing detail only in prose, so recognition alone is not permission to say "try again": the disposition is decided by **positive evidence** and pinned from both directions — the quota-window code is evidence in itself, the preflight code counts only when the server really supplied a wait (an upstream fact, not a convention: the terminal branch throws with no detail at all, so a wait value cannot reach the client on that path), and a preflight refusal with no wait reads as *undecidable* (say what is true of both branches — nothing was consumed — and leave redeemability to the engine's own line) rather than being rendered as either a retry or an ending. Every other member means waiting will not help (change a setting, relaunch, the retained session is gone), so the recognition is a **closed set of two codes**: widening it to a family prefix would tell half the users to wait and the other half to keep waiting for something that will never arrive, and the negative controls drive exactly those codes through it, plus a same-named code on a different door (the submission-side quota refusal), the two underscore-form siblings, and a code merely quoted inside a message body. The wait value is narrowed to the same domain the server mints it in (a whole number of seconds, at least one): zero, a negative, a fraction and a non-number all read as **no window given** rather than as zero, because a zero tells the caller to retry immediately and the wake-up it would retry is an at-most-once action with real side effects. Reading is structural rather than `instanceof`, since the client is host-injected and the same class name across two bundles is two classes, and a null-prototype plain object must still be recognised. The failure classifier gains this one disposition without any existing one moving, an unknown code still falls to the honest open-set arm and its wait value is **not** believed, and an end-to-end call proves the disposition and the window reach the host while the call itself is still attempted exactly once. The recognised code set is a **frozen array**, not a type-level readonly set: the latter is a plain mutable collection at runtime and the decision reads the same instance, so one `.add` from any consumer would turn a refusal that waiting cannot fix into one that claims it can — the guard proves it by really trying to mutate the exported value and then checking the verdict did not drift |
|
|
267
270
|
|
|
268
271
|
Each suite carries a floor that only moves up — a refactor that stops executing a group of
|
|
269
272
|
assertions is a failure, not a quieter pass. Guards anchor on the **installed artefact's content**
|
package/dist/adapt/arms.js
CHANGED
|
@@ -294,6 +294,38 @@ const engineNoticeArm = function* (m) {
|
|
|
294
294
|
...(typeof m.eventSeq === 'string' && m.eventSeq.length > 0 ? { eventSeq: m.eventSeq } : {}),
|
|
295
295
|
});
|
|
296
296
|
};
|
|
297
|
+
/**
|
|
298
|
+
* `wiring_manifest` 内部臂 → chrome `wiring_manifest`(core #524 + core 147③,server ≥7.58.0)。
|
|
299
|
+
*
|
|
300
|
+
* 契约本体在 `seam.ts` 的 {@link WiringManifestChromeEvent} 头注(它是宿主要读的那一份)。
|
|
301
|
+
* 本臂只做两件:
|
|
302
|
+
* 🔴 **子流断闸**(与 `engine_notice`/`text_segment_end` 同族):带 `parentToolCallId` 的接线自述
|
|
303
|
+
* 属于子代/编排腿,把它的门卸清单渲到 leader 面上,用户会以为**自己这一 turn**少了工具。
|
|
304
|
+
* 断闸按「**键在不在**」判不按「是不是串」判(与 `textSegmentEndArm` 同因:本臂决定的是
|
|
305
|
+
* 「这份自述算谁的」,方向必须更严;`null` 算在场,不给坏值开口子)。
|
|
306
|
+
* 🔴 **第二道成形判**:投影层已把两段各自校验过并保证「至少一段在场」,这里只再确认一次
|
|
307
|
+
* ——防的是**非投影口**喂进来的帧(宿主自建管线 / 重放存量转录),不是重复判据。
|
|
308
|
+
* 两段都不在场 ⇒ 不产事件(与投影层的「不铸空臂」同一条规矩,两层不许各说各话)。
|
|
309
|
+
* 🔴 **一个字都不改**:段内容原样转发(`restore` 逐字、`reason` 六词不映射)——任何加工都必须
|
|
310
|
+
* 发生在渲染面,理由见臂注义务②③。
|
|
311
|
+
*/
|
|
312
|
+
const wiringManifestArm = function* (m) {
|
|
313
|
+
if (m.parentToolCallId !== undefined)
|
|
314
|
+
return;
|
|
315
|
+
const modelGate = m._sema_modelGate;
|
|
316
|
+
const autoMode = m._sema_autoMode;
|
|
317
|
+
const hasGate = typeof modelGate === 'object' && modelGate !== null;
|
|
318
|
+
const hasAuto = typeof autoMode === 'object' && autoMode !== null;
|
|
319
|
+
if (!hasGate && !hasAuto)
|
|
320
|
+
return;
|
|
321
|
+
yield chrome({
|
|
322
|
+
kind: 'wiring_manifest',
|
|
323
|
+
laneProof: MAIN,
|
|
324
|
+
...(hasGate ? { modelGate: modelGate } : {}),
|
|
325
|
+
...(hasAuto ? { autoMode: autoMode } : {}),
|
|
326
|
+
...(typeof m.eventId === 'string' && m.eventId.length > 0 ? { eventId: m.eventId } : {}),
|
|
327
|
+
});
|
|
328
|
+
};
|
|
297
329
|
/**
|
|
298
330
|
* `text_end` 内部臂 → chrome `text_segment_end`(#323 / core #447)。
|
|
299
331
|
*
|
|
@@ -833,6 +865,7 @@ export const ARMS = new Map([
|
|
|
833
865
|
['human_input', humanInputArm],
|
|
834
866
|
['engine_notice', engineNoticeArm],
|
|
835
867
|
['text_end', textSegmentEndArm],
|
|
868
|
+
['wiring_manifest', wiringManifestArm],
|
|
836
869
|
['prompt_suggestions', promptSuggestionsArm],
|
|
837
870
|
['retry_status', retryStatusArm],
|
|
838
871
|
]);
|
|
@@ -116,6 +116,60 @@ export type EventProjection = {
|
|
|
116
116
|
* 旧写法 `if (msg)` 在新返回型上恒真(对象永远 truthy),所以这是**必须点名**的一类改动。
|
|
117
117
|
*/
|
|
118
118
|
export declare function eventToSdkMessage(ev: AgentEvent, ctx: EmitContext): EventProjection;
|
|
119
|
+
/**
|
|
120
|
+
* `engine_notice` → 中性内部通告臂(#310 / #318 件①,契约 = server `ASSISTANT-WIRE-CONTRACT` 附录 D
|
|
121
|
+
* + openapi `Event_engine_notice`)。
|
|
122
|
+
*
|
|
123
|
+
* ── 这是什么 ────────────────────────────────────────────────────────────────────────────────
|
|
124
|
+
* 引擎的**结构化通告**里,被 server 判为面向**本会话终端用户**的那一小撮,按 `sessionId` 路由到
|
|
125
|
+
* 这条会话的流上。live 与 durable **两腿都有**(server 三条 run 腿都挂了口)⇒ 断连重连的重放里
|
|
126
|
+
* 会**再看到它**,与 `workspace_changed` 同一条**幂等消费**纪律(附录 D.1 逐字)。
|
|
127
|
+
*
|
|
128
|
+
* ── 🔴 开集三条(本函数的全部判据,逐条都是「不许做什么」)────────────────────────────────
|
|
129
|
+
* ① **按 `code` + `detail` 消费,`message` 只作 fallback 展示**。core 明写
|
|
130
|
+
* `memory.session_polluted` 的 message 随 `memoryProvenance` 模式变文 ⇒ 按 message 文本匹配
|
|
131
|
+
* **必碎**(5.41 合流码形退役同教训)。所以本层把三者**分别**上臂,绝不把 detail 折进文案。
|
|
132
|
+
* ② **`code` 是开集,认不得也绝不丢帧**。server 的白名单会随 core 码册增长(起步三码 →
|
|
133
|
+
* core 5.47/5.48 的 hold 三码入册,附录 D.3 的表尚未跟上)。本层因此**一个码都不硬编**:
|
|
134
|
+
* 没有识别表、没有 switch、没有「已知才投」——认不认得是**渲染面**的判断,不是投影面的门。
|
|
135
|
+
* ⇒ 本函数对 `code` 唯一的要求是「非空串」(判别键本身缺席才叫畸形)。
|
|
136
|
+
* ③ **能力位不当两值门**:本臂不读任何 caps、不问「引擎支不支持 engine_notice」。帧到了就投影
|
|
137
|
+
* (A-022「到帧即服务」先例)—— 拿一个探测位去 gate 一条**已经到手的事实**,只会在探测未判/
|
|
138
|
+
* 失败时把真事实丢掉。
|
|
139
|
+
*
|
|
140
|
+
* ── 🔴 消费端纪律(写在臂上,因为三端各写一遍必漂)────────────────────────────────────────
|
|
141
|
+
* · `detail` 已过 server 的 `redactSecrets` + 尺寸 bound(自由文本 1000 字符;深 4 / 键 32 /
|
|
142
|
+
* 数组 32),**仍按外部串处理**(呈现面字符处理走消费方自己的单源)。
|
|
143
|
+
* · `ts` 是 **server 观察时刻**(ms epoch),**不是**引擎铸造时刻 —— `EngineNotice` 自身不带时间戳。
|
|
144
|
+
* · 🔴 `memory.harvest_quarantined` 的 `moved` 与 `escalated` **不可相减**(就地墓碑同时计入两者,
|
|
145
|
+
* core 顶注):两个数各自读、并列呈现,任何减法都会得出一个**不存在的量**。本层原样透传 detail
|
|
146
|
+
* 正是为了让这条纪律只在渲染面兑现一次,而不是被投影层先算一个差值出来。
|
|
147
|
+
* · **本帧的缺席不代表「没发生」**:非白名单码 / 缺 `sessionId` 的通告 server **如实不投**
|
|
148
|
+
* (宁缺席不串台),全族那一份始终在 server 的结构化日志里(运维面)。所以消费端**绝不许**
|
|
149
|
+
* 从「没收到 engine_notice」反推「记忆姿态正常」。
|
|
150
|
+
*
|
|
151
|
+
* ── 畸形判据(fail-closed 方向)────────────────────────────────────────────────────────────
|
|
152
|
+
* `code` 非串/空串 ⇒ `malformed`(判别键都没有的通告,渲出去只是一行没有主语的噪声,而 dropped
|
|
153
|
+
* 至少会经 `reportDroppedFrame` 留痕)。其余四键**各自**按诚实缺席处理:`message` 非串 ⇒ 空串
|
|
154
|
+
* (fallback 位缺席,渲染面据此走纯 code 呈现)、`detail` 非对象 ⇒ 空对象(**不是**丢帧:通告的
|
|
155
|
+
* 承重物是 code,detail 坏了不该连带把「这件事发生过」一起吞掉)、`sessionId` / `ts` 非法 ⇒ 键不
|
|
156
|
+
* stamp(绝不铸 `0` 这种看起来合法的假读数)。
|
|
157
|
+
*/
|
|
158
|
+
/** {@link wiringManifestSupersetBody} 的 `modelGate` 段(core #524 / server S-86 的形)。 */
|
|
159
|
+
export interface WiringManifestModelGate {
|
|
160
|
+
/** 门的类别词(core 开集,原样透传;例:模型族名)。 */
|
|
161
|
+
class: string;
|
|
162
|
+
/** 本 run 被门卸掉的工具名(**非空**;空数组不是真读数,见投影函数头注)。 */
|
|
163
|
+
removed: readonly string[];
|
|
164
|
+
/** 恢复办法的**逐字**提示(core 铸,例:环境变量赋值串)。渲染面原样呈现,不要自己拼。 */
|
|
165
|
+
restore: string;
|
|
166
|
+
}
|
|
167
|
+
/** {@link wiringManifestSupersetBody} 的 `autoMode` 段(core 147③ 的形)。 */
|
|
168
|
+
export interface WiringManifestAutoMode {
|
|
169
|
+
armed: boolean;
|
|
170
|
+
/** core **六词逐字透传**。🔴 不映射 `/v1/capabilities.permissionModeAuto.reason` —— 见投影函数头注。 */
|
|
171
|
+
reason: string;
|
|
172
|
+
}
|
|
119
173
|
/**
|
|
120
174
|
* CS-7 §2.7 — turn_end usage → CC `ModelUsage` (pinned name mapping;
|
|
121
175
|
* costMicroUsd/1e6 → costUSD). Surfaced separately because the slice has no
|
|
@@ -46,6 +46,10 @@ export const INTERNAL_SDK_ARM_TYPES = new Set([
|
|
|
46
46
|
'engine_notice',
|
|
47
47
|
// #323 / core #447:assistant 流式**散文段边界**(raw 预分派铸点,见 `textEndProjection` 头注)。
|
|
48
48
|
'text_end',
|
|
49
|
+
// L-70 / L-108②③(core #524 + core 147③,server ≥7.58.0):`wiring_manifest` 帧上**两个新段**的
|
|
50
|
+
// 超集投影(`_sema_modelGate` / `_sema_autoMode`)。整份 manifest 的其余段仍不在本切片里 ——
|
|
51
|
+
// 射程写在 `case 'wiring_manifest'` 头注,别读成「manifest 接上了」。
|
|
52
|
+
'wiring_manifest',
|
|
49
53
|
]);
|
|
50
54
|
/** Wrap neutral content blocks in the CC `assistant` message envelope. */
|
|
51
55
|
function assistantArm(ctx, content) {
|
|
@@ -492,6 +496,43 @@ export function eventToSdkMessage(ev, ctx) {
|
|
|
492
496
|
// rides on `notification`). Projects to a NEUTRAL internal arm: the REPL bridge drops it (the ENGINE
|
|
493
497
|
// already steer-injects the model-facing XML), the print-mode `ask` converts it to CC's
|
|
494
498
|
// `system/task_notification` SDK frame.
|
|
499
|
+
/**
|
|
500
|
+
* `wiring_manifest` —— 引擎的**接线自述**(leg / ask / question / parkLane / … 指纹)。
|
|
501
|
+
*
|
|
502
|
+
* 🔴 **本臂只投两段,不是整份 manifest**(射程写在这里,免得下一个人读成「manifest 接上了」)。
|
|
503
|
+
* 整份 manifest 是 operator / 诊断面的读物,CC transcript 无对位物 —— 此前本臂与两条 HITL 帧
|
|
504
|
+
* 并列走 `nothing('not_in_slice')`,那句话对**其余每一段**今天仍然成立。改的只是:`modelGate`
|
|
505
|
+
* 与 `autoMode` 这两段是**终端用户面**的事实(「这一 run 你少了哪些工具」/「auto 到底武没武装」),
|
|
506
|
+
* 它们没有第二条到达消费端的路,留在 not_in_slice 里就是静默蒸发。
|
|
507
|
+
*
|
|
508
|
+
* 🔴 **帧是 FLAT 的,没有 `manifest` 包层**(SDK `events.d.ts` 的 wiring_manifest 臂逐字:
|
|
509
|
+
* server 把 core `TaskEvent` 的 `ev.manifest` 过白名单 builder 后 **spread 进帧**,三条腿
|
|
510
|
+
* (live SSE / durable 账本行 / resume 重放)同源)⇒ 读 `ev.modelGate` / `ev.autoMode`,
|
|
511
|
+
* **不是** `ev.manifest.modelGate`。6.7.0 契约声明过的那层 `manifest:{...}` 嵌套 wire 上从未有过。
|
|
512
|
+
*
|
|
513
|
+
* 🔴 **两段各自独立**:一段畸形不牵连另一段;两段都不成形 ⇒ 仍回 `none/not_in_slice`,
|
|
514
|
+
* **不铸空臂** —— 每条腿都会来一帧 wiring_manifest,为它们各铸一条什么都不说的内部臂只会让
|
|
515
|
+
* 消费端每 run 多收一条噪声,还把「本 run 真有门卸」这件事稀释掉。
|
|
516
|
+
*
|
|
517
|
+
* 🔴 **超集键走 `_sema_` 前缀**:`modelGate`/`autoMode` 今天**不在**已发布 SDK 的 `AgentEvent`
|
|
518
|
+
* wiring_manifest 臂里(sdk 8.1.0 `dist/events.d.ts` 全树零命中,亲验)。本包不许自铸 SDK 同名
|
|
519
|
+
* 形去冒充上游词汇;等 SDK 声明该臂后再谈改名(那是 BREAKING,另立)。
|
|
520
|
+
*/
|
|
521
|
+
case 'wiring_manifest': {
|
|
522
|
+
// 🔴 `as unknown as Record<…>` 不是图省事:sdk 8.1.0 的 wiring_manifest 臂**没有**这两段
|
|
523
|
+
// (亲验全树零命中),所以 `ev` 在这里被窄成一个不含它们的具名形 —— 直接读会是编译期
|
|
524
|
+
// 「属性不存在」。这行 cast 就是「上游类型还没到货」这件事的**可见落点**;SDK 声明该臂
|
|
525
|
+
// 之后把它删掉,编译器会立刻指出所有该跟着改的地方。
|
|
526
|
+
const raw = ev;
|
|
527
|
+
const body = wiringManifestSupersetBody(raw);
|
|
528
|
+
if (body === undefined)
|
|
529
|
+
return nothing('not_in_slice');
|
|
530
|
+
// `eventId` 与别的臂同律带上(core 铸的事件身份;wire 未必带 ⇒ 缺席时键不在场)。
|
|
531
|
+
// 🔴 主会话收货门 A1c 抓出的死臂:适配层 `wiringManifestArm` 读 `m.eventId`,而本层原来没投它,
|
|
532
|
+
// 那句透传永远是空 —— 消费端拿不到幂等键,durable 重放只能退到内容去重。
|
|
533
|
+
const eventId = typeof raw.eventId === 'string' && raw.eventId.length > 0 ? { eventId: raw.eventId } : {};
|
|
534
|
+
return projected(stamp(ctx, armBody({ type: 'wiring_manifest', ...body, ...eventId })));
|
|
535
|
+
}
|
|
495
536
|
case 'task_notification': {
|
|
496
537
|
// 载荷两形:嵌在 `notification` 里(server 早期形)或直接摊在帧上(今天的 union 形)。
|
|
497
538
|
const nested = ev.notification;
|
|
@@ -645,8 +686,6 @@ export function eventToSdkMessage(ev, ctx) {
|
|
|
645
686
|
// (adapt 臂表同样无此臂),缺口记 0.13.0 候选,不许由本切片顺手编一个假 transcript 形。
|
|
646
687
|
case 'compaction_outcome':
|
|
647
688
|
// ── sdk 6.9.0 提货批(2026-08-08)新入 union 的四条臂 ─────────────────────────────────────
|
|
648
|
-
// `wiring_manifest`:引擎的接线自述(leg/ask/tools 指纹)—— 可观测面,CC transcript 无对位物。
|
|
649
|
-
case 'wiring_manifest':
|
|
650
689
|
// `tool_approval` / `tool_approval_complete`:HITL 审批帧。**#185a 起它们是 `AgentEvent` 的臂**
|
|
651
690
|
// (此前只是 named SSE frame),但本切片不是它们的消费口 —— 它们由
|
|
652
691
|
// `hitl/toolApprovalWire.isToolApprovalFrame` + `hitl/frameRouter` 走审批卡那条链。在这里投一
|
|
@@ -773,44 +812,80 @@ function textEndProjection(ev, ctx) {
|
|
|
773
812
|
})));
|
|
774
813
|
}
|
|
775
814
|
/**
|
|
776
|
-
* `
|
|
777
|
-
* + openapi `Event_engine_notice`)。
|
|
815
|
+
* `wiring_manifest.modelGate` 的形校验(core #524 / server S-86,server ≥7.58.0)。
|
|
778
816
|
*
|
|
779
|
-
*
|
|
780
|
-
*
|
|
781
|
-
*
|
|
782
|
-
*
|
|
817
|
+
* 语义(core 逐字):**仅本 run 真有门卸时在场**;缺席 = 没卸,**不是**「不知道」也不是
|
|
818
|
+
* 「老引擎」—— 所以消费端拿不到本段时唯一合法的行为是**什么都不渲**,绝不许渲一句
|
|
819
|
+
* 「没有工具被卸掉」(那是把「没报」说成「报了个零」)。
|
|
820
|
+
*
|
|
821
|
+
* 三键**全有才收**(与 server 侧同判据)。理由是这一段的用户价值是**一句完整的话**:
|
|
822
|
+
* 「(class)门卸掉了(removed…),恢复用(restore)」—— 少任何一件都只能渲出半句没有出口的话。
|
|
823
|
+
* 缺件 ⇒ 整段不收:不折 `''`、不折空数组、不编默认恢复办法。
|
|
824
|
+
*/
|
|
825
|
+
function projectModelGateSection(raw) {
|
|
826
|
+
if (typeof raw !== 'object' || raw === null || Array.isArray(raw))
|
|
827
|
+
return undefined;
|
|
828
|
+
const g = raw;
|
|
829
|
+
const cls = typeof g.class === 'string' && g.class.length > 0 ? g.class : undefined;
|
|
830
|
+
const restore = typeof g.restore === 'string' && g.restore.length > 0 ? g.restore : undefined;
|
|
831
|
+
const removed = Array.isArray(g.removed) && g.removed.every((r) => typeof r === 'string')
|
|
832
|
+
? g.removed.filter((r) => r.length > 0)
|
|
833
|
+
: undefined;
|
|
834
|
+
if (cls === undefined || restore === undefined || removed === undefined)
|
|
835
|
+
return undefined;
|
|
836
|
+
// 🔴 空数组**不收**:`removed: []` 说的是「卸了零件工具」,与「没卸」在屏面上不可区分,
|
|
837
|
+
// 而 core 的契约是「有门卸才铸段」⇒ 空数组是坏值,不是一个真读数。
|
|
838
|
+
if (removed.length === 0)
|
|
839
|
+
return undefined;
|
|
840
|
+
return { class: cls, removed, restore };
|
|
841
|
+
}
|
|
842
|
+
/**
|
|
843
|
+
* `wiring_manifest.autoMode` 的形校验(core 147③,server ≥7.58.0)。
|
|
783
844
|
*
|
|
784
|
-
*
|
|
785
|
-
*
|
|
786
|
-
*
|
|
787
|
-
*
|
|
788
|
-
* ② **`code` 是开集,认不得也绝不丢帧**。server 的白名单会随 core 码册增长(起步三码 →
|
|
789
|
-
* core 5.47/5.48 的 hold 三码入册,附录 D.3 的表尚未跟上)。本层因此**一个码都不硬编**:
|
|
790
|
-
* 没有识别表、没有 switch、没有「已知才投」——认不认得是**渲染面**的判断,不是投影面的门。
|
|
791
|
-
* ⇒ 本函数对 `code` 唯一的要求是「非空串」(判别键本身缺席才叫畸形)。
|
|
792
|
-
* ③ **能力位不当两值门**:本臂不读任何 caps、不问「引擎支不支持 engine_notice」。帧到了就投影
|
|
793
|
-
* (A-022「到帧即服务」先例)—— 拿一个探测位去 gate 一条**已经到手的事实**,只会在探测未判/
|
|
794
|
-
* 失败时把真事实丢掉。
|
|
845
|
+
* 语义(server 发车帖逐字):**effective 腿恒在**;缺席 = 老 mint / 外部 derive,**不许折成
|
|
846
|
+
* `armed:false`**(那是把「没报」渲成「没武装」)。`reason` 是 **core 六词逐字透传**,
|
|
847
|
+
* 🔴 **不映射** `/v1/capabilities.permissionModeAuto.reason` 的六词 —— 两套词表**同名不同义**
|
|
848
|
+
* (`settings_denied` 在 capabilities 那边折 `no_intent` 不折 `denied`),归一会造出一个假读数。
|
|
795
849
|
*
|
|
796
|
-
*
|
|
797
|
-
*
|
|
798
|
-
*
|
|
799
|
-
|
|
800
|
-
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
|
|
804
|
-
|
|
805
|
-
|
|
850
|
+
* server 侧另有一条**互证**判据:`armed === (reason === 'armed')`,不满足整段不投。本层照抄它,
|
|
851
|
+
* 理由不是「重复上游」而是**非投影口**(宿主自建管线 / 重放存量转录)喂进来的帧不过 server ——
|
|
852
|
+
* 一个 `{armed:true, reason:'settings_denied'}` 会让消费端同时读到「武装了」和「被拒了」。
|
|
853
|
+
*/
|
|
854
|
+
function projectAutoModeSection(raw) {
|
|
855
|
+
if (typeof raw !== 'object' || raw === null || Array.isArray(raw))
|
|
856
|
+
return undefined;
|
|
857
|
+
const a = raw;
|
|
858
|
+
const armed = typeof a.armed === 'boolean' ? a.armed : undefined;
|
|
859
|
+
const reason = typeof a.reason === 'string' && a.reason.length > 0 ? a.reason : undefined;
|
|
860
|
+
if (armed === undefined || reason === undefined)
|
|
861
|
+
return undefined;
|
|
862
|
+
if (armed !== (reason === 'armed'))
|
|
863
|
+
return undefined;
|
|
864
|
+
return { armed, reason };
|
|
865
|
+
}
|
|
866
|
+
/**
|
|
867
|
+
* `wiring_manifest` 帧 → 两个超集键的**纯投影**(公面导出;三端共用,壳侧绝不自抄一份形校验)。
|
|
806
868
|
*
|
|
807
|
-
*
|
|
808
|
-
* `
|
|
809
|
-
*
|
|
810
|
-
* (
|
|
811
|
-
*
|
|
812
|
-
*
|
|
869
|
+
* 返回 `undefined` = 这一帧两段都不在场 / 都不成形 ⇒ 调用方**不铸臂**(见 case 头注)。
|
|
870
|
+
* 🔴 帧是 FLAT 的:读 `ev.modelGate` / `ev.autoMode`,**不读** `ev.manifest.*`。
|
|
871
|
+
*
|
|
872
|
+
* 🔴 **刻意不导出**(typeshape 棘轮「unknown 出境只降」逼出来的正解,不是绕过门):本函数的入参是
|
|
873
|
+
* 一个**未校验的 wire 帧**,唯一诚实的声明形就是 `Record<string, unknown>` —— 把它推上公面等于在
|
|
874
|
+
* 包的公面上开一个无类型边界,而那正是那道棘轮存在的理由。三端要消费这两段,走的是**已定型**的
|
|
875
|
+
* chrome 臂 {@link WiringManifestChromeEvent}(判别键 + 两个具名 interface),不是这个原始投影口;
|
|
876
|
+
* 两个具名形({@link WiringManifestModelGate} / {@link WiringManifestAutoMode})照常导出。
|
|
877
|
+
* 若将来真有宿主要自建管线直调本函数,正解是**先给帧定一个具名 interface** 再导出,不是抬棘轮。
|
|
813
878
|
*/
|
|
879
|
+
function wiringManifestSupersetBody(ev) {
|
|
880
|
+
const modelGate = projectModelGateSection(ev.modelGate);
|
|
881
|
+
const autoMode = projectAutoModeSection(ev.autoMode);
|
|
882
|
+
if (modelGate === undefined && autoMode === undefined)
|
|
883
|
+
return undefined;
|
|
884
|
+
return {
|
|
885
|
+
...(modelGate !== undefined ? { _sema_modelGate: modelGate } : {}),
|
|
886
|
+
...(autoMode !== undefined ? { _sema_autoMode: autoMode } : {}),
|
|
887
|
+
};
|
|
888
|
+
}
|
|
814
889
|
function engineNoticeProjection(ev, ctx) {
|
|
815
890
|
const code = typeof ev.code === 'string' && ev.code.length > 0 ? ev.code : undefined;
|
|
816
891
|
if (code === undefined)
|
|
@@ -180,6 +180,50 @@ export declare const RESUME_AT_ERROR_CODE_PREFIX = "resume_at.";
|
|
|
180
180
|
export declare const REWIND_ERROR_CODE_PREFIXES: readonly ["resume_at.", "rewind_snapshot."];
|
|
181
181
|
/** 该码是否属 rewind/resume 可自解族。缺席 ⇒ false。 */
|
|
182
182
|
export declare function isRewindFamilyCode(code: string | undefined): boolean;
|
|
183
|
+
/**
|
|
184
|
+
* `resume.usage_window_exhausted`(#449 G1,core 5.60.1;server ≥7.47.0)—— 这一行的账本键上,
|
|
185
|
+
* 本部署的**治理窗**满了。core 铸文的两句不变量:**什么都没消费、什么都没解钉** ⇒ 同一个 token
|
|
186
|
+
* 带同一个决议在窗放开后可**直兑**(所以处置是「等」,不是「重开」也不是「改配置」)。
|
|
187
|
+
* ⚠️ 与 429 的 {@link USAGE_WINDOW_EXHAUSTED}(`usage.window_exhausted`,提交面 pre-admission)
|
|
188
|
+
* **同一本账、不同门、不同码**:本码是 resume/decide 腿的 pre-CAS 拒。别把两者合并判。
|
|
189
|
+
*/
|
|
190
|
+
export declare const RESUME_USAGE_WINDOW_EXHAUSTED = "resume.usage_window_exhausted";
|
|
191
|
+
/**
|
|
192
|
+
* `resume.preflight_rejected`(#376,core 5.65 retry-later 形;server ≥7.51.0)—— 部署自己的
|
|
193
|
+
* `RunnerDeps.resumePreflight` 拒了这次 resume(显式拒 / 抛 / 超时 / 答案读不动,四臂一律
|
|
194
|
+
* fail-closed)。它是 CAS 前的**最后一档**,所以什么都没被消费。
|
|
195
|
+
* 🔴 **core 侧另有一条 `terminal` 臂**(行已被这次拒绝的单发 expire CAS 结清、token 不可再赎),
|
|
196
|
+
* 而两臂的**判别位在 message 散文里**。本包**不按文案分臂** —— 按文案分支正是上游改一个词就
|
|
197
|
+
* 静默空转的形。⇒ 消费端能诚实说的只有两臂都成立的那句:「这一拒发生在提交之前,你的决定
|
|
198
|
+
* 没被消费」;「还能不能再赎」交给引擎那行原文去说,别替它下结论。
|
|
199
|
+
*/
|
|
200
|
+
export declare const RESUME_PREFLIGHT_REJECTED = "resume.preflight_rejected";
|
|
201
|
+
/**
|
|
202
|
+
* 时间性拒绝族的**闭集**。
|
|
203
|
+
*
|
|
204
|
+
* 🔴 这是本文件的第二个闭集,但闭的**不是**「`resume.*` 一共有几个码」(那仍是开集,新码照旧
|
|
205
|
+
* 落 {@link isRewindFamilyCode} 之外的开集兜底),闭的是「**server 在哪些码上铸 `retryAfterSec`**」:
|
|
206
|
+
* server 的铸键判据逐字 =「本码 ∧ 有限正数」,两码之外恒缺席;SDK 8.1.0 `classifyApiError` 同样
|
|
207
|
+
* 按这**两个具名码**铸 `ResumeRetryLaterError`(具名分支排在 `resume.` 前缀兜底**之前**)。
|
|
208
|
+
* 🔴 **绝不放宽成 `resume.` 前缀判**:那会把 `retain_off` / `evicted` / `row_gone` 这些**等也没用**
|
|
209
|
+
* 的码一起说成「过会儿再试」—— 一半用户白等,另一半白重开(与 `row_recycling`/`row_gone` 禁合并
|
|
210
|
+
* 同一条纪律)。加成员 = 上游真在新码上铸了 `retryAfterSec`,必须同批带判据。
|
|
211
|
+
*
|
|
212
|
+
* 🔴 **为什么是 `Object.freeze` 的数组而不是 `ReadonlySet`**(异源对抗复审 [medium] 采纳,真病;
|
|
213
|
+
* 与 `interactiveHalt.RUN_LEVEL_STOP_ERROR_CODES`(#363 二轮)**同一条已定谳的病形**):
|
|
214
|
+
* `ReadonlySet<string>` 只在**类型面**只读 —— 运行期它就是一只普通 `Set`,而判定查的是**同一个
|
|
215
|
+
* 实例**。任何 JS 消费者(公面上它是导出的)`.add('resume.row_gone')` 之后,一条「等也没用」的
|
|
216
|
+
* 拒绝就会当场变成带窗的 `retry-later`(实测:`row_gone` + `retryAfterSec:30` 从 `row-gone` 翻成
|
|
217
|
+
* `retry-later`)—— 闭集与「只认正向证据」两道约束一起被绕过。冻结数组在**运行期**真的改不动
|
|
218
|
+
* (ESM 恒 strict:`push`/下标赋值直接抛),于是「公开面」与「判定源」可以安全地是同一个物。
|
|
219
|
+
* ⚠️ 判据形随之从 `.has()` 改成 `.includes()`(与 `parkResolver.GATE_FAILURE_CODES` 同姿势;
|
|
220
|
+
* 闭集只有两员,查找成本不是这里的量)。
|
|
221
|
+
* ⚠️ **同形存量登记**(只登记不顺手改):同文件的 `CONFIG_REFUSAL_CODES` / `DELEGATION_CAP_CODES` /
|
|
222
|
+
* `TOOL_END_INTERRUPTED_CODES` 三张表今天仍是 `ReadonlySet`,同病。它们**已经在公面上**且消费点
|
|
223
|
+
* 用 `.has()` ⇒ 换形是下游 BREAKING(签名从 `ReadonlySet<string>` 变 `readonly string[]`),
|
|
224
|
+
* 不属内容批射程;本条按现状登记,换形另立一批。本位是**新铸**的,所以在出生那天就用对形。
|
|
225
|
+
*/
|
|
226
|
+
export declare const RESUME_RETRY_LATER_CODES: readonly string[];
|
|
183
227
|
/**
|
|
184
228
|
* server 温切 drain 门的 pre-stream 拒收码(503 + `errorCode:"draining"`;server 侧
|
|
185
229
|
* `error:"draining"` 是冻结契约,SDK toApiError 盖成 `errorCode`)。此前壳/包注释各持裸字面。
|