@sema-agent/client-core 0.41.0 → 0.43.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 +340 -0
- package/README.md +1 -1
- package/dist/adapt/textStream.d.ts +11 -1
- package/dist/adapt/textStream.js +68 -2
- package/dist/adapt/wireShapes.d.ts +8 -0
- package/dist/adapt/wireShapes.js +8 -0
- package/dist/adapt.js +13 -2
- package/dist/adapter/downstream/eventToSdkMessage.js +24 -0
- package/dist/attachmentsWireCaps.d.ts +43 -8
- package/dist/attachmentsWireCaps.js +64 -14
- package/dist/hitl/askGateWire.d.ts +5 -2
- package/dist/hitl/askGateWire.js +15 -4
- package/dist/hitl/editedRuleTextPrecheck.d.ts +102 -0
- package/dist/hitl/editedRuleTextPrecheck.js +91 -0
- package/dist/hitl/frameRouter.d.ts +59 -1
- package/dist/hitl/frameRouter.js +107 -3
- package/dist/hitl/gateLedger.d.ts +9 -0
- package/dist/hitl/gateLedger.js +3 -0
- package/dist/hitl/hitlBridge.d.ts +17 -3
- package/dist/hitl/hitlBridge.js +20 -4
- package/dist/hitl/hitlHostSurface.d.ts +83 -3
- package/dist/hitl/hitlHostSurface.js +92 -3
- package/dist/hitl/parkResolver.js +29 -5
- package/dist/hitl/toolApprovalWire.d.ts +435 -24
- package/dist/hitl/toolApprovalWire.js +429 -24
- package/dist/index.d.ts +1 -0
- package/dist/index.js +7 -0
- package/dist/model/catalogLoader.js +151 -45
- package/dist/printToolResultFrame.d.ts +19 -0
- package/dist/seatContract.d.ts +25 -1
- package/dist/seatContract.js +29 -2
- package/docs/INTEGRATION-CLIENTS.md +332 -16
- package/package.json +2 -2
package/CHANGELOG.md
CHANGED
|
@@ -28,6 +28,346 @@
|
|
|
28
28
|
> 不许悄悄漂:豁免登记的 `releasedAt` 与 `FROZEN` 账上 0.36.0 那一行逐字相等;本段(点名版本号
|
|
29
29
|
> `0.36.0` + 关键字「勘误」)必须还在这份头注里 —— 删掉本段而不同批把门侧豁免一起处理,门当场红。
|
|
30
30
|
|
|
31
|
+
## 0.43.0(2026-08-25)
|
|
32
|
+
|
|
33
|
+
> 🔴 **两阶段协议提醒(发包批的义务,见本档头注)**:本段发布前须由发包批做**阶段一** ——
|
|
34
|
+
> `package.json` bump `0.42.0 → 0.43.0` + 本段标题转日期 + README `Version` 行跟版 +
|
|
35
|
+
> `run-integration-doc-freshness-test.mjs` 的 `FROZEN` 账**追加**一行 `pending: true`;发布后
|
|
36
|
+
> **阶段二**补 `releasedAt` 与段 `sha256` 并删 `pending`。**施工批不做 bump、不动 `FROZEN`**。
|
|
37
|
+
|
|
38
|
+
**批面**:①#323 两件呈现面修复(clay 三裁 08-25,已落 `4c364c1`)②#334/[5223] `ruleSuggestions`
|
|
39
|
+
→ `ruleOffers` 判别联合换形三腿消费(**BREAKING**)③#341/[5214]③ `inputHasBidi` 过境
|
|
40
|
+
④`requiresRealApproval` 过境复核 + 帧键镜像同形族扫。
|
|
41
|
+
|
|
42
|
+
### 🔴 BREAKING(#334/[5223];server ≥7.46.0 / core 5.58.0 design/375)
|
|
43
|
+
|
|
44
|
+
上游把审批卡的「不再询问」候选从 `ruleSuggestions` 整体换成判别联合 `ruleOffers`
|
|
45
|
+
(`kind: "single" | "batch"`),**live `tool_approval` 帧 / `card_json` 呈卡帧 / durable
|
|
46
|
+
`PendingCheckpoint` 三腿同换**;7.46 引擎上旧键在这三腿一个都不发(engine 7.46.0 fixture 直证)。
|
|
47
|
+
|
|
48
|
+
**端要改的三处**:
|
|
49
|
+
|
|
50
|
+
| 面 | 0.42.0 及更早 | 0.43.0 |
|
|
51
|
+
|---|---|---|
|
|
52
|
+
| 卡入参(活卡帧腿) | `ApprovalCardRequest.ruleSuggestions?: RuleSuggestion[]` | **`ruleOffers?: RuleOffer[]`** |
|
|
53
|
+
| 卡入参(durable 行腿,只读) | `ruleSuggestionsReadOnly?: RuleSuggestion[]` | **`ruleOffersReadOnly?: RuleOffer[]`** |
|
|
54
|
+
| 卡决断(兑付) | `persistRule` / `persistRuleEdited`(**字节不变**) | 两位不动 + 新增 **`persistRuleBatchOfferIndex?: number`** |
|
|
55
|
+
|
|
56
|
+
- **两代 wire 键经同一把窄读器归一成包内单一形**:新键优先、旧键(≤7.45)归一成 `kind:'single'`,
|
|
57
|
+
**新键在场即定局绝不回落**(`null` 与缺席同视,理由见 `readOfferSupply` 顶注的代价不对称段)⇒
|
|
58
|
+
混舰队部署上端**不需要任何版本分支**;
|
|
59
|
+
- **`RuleOffer.offerIndex` 是本包铸的座**(上游形上没有):batch 臂的兑付键就是**原始 wire 下标**,
|
|
60
|
+
窄读器逐条丢坏形但**下标不前移**(压紧 = 人点的第 k 个与服务端兑的第 k 个指向两条不同规则)。
|
|
61
|
+
🔴 **两条腿下标语义不同**:活卡帧腿恒等于 core 的 offer index(**是**选择键);durable 行腿
|
|
62
|
+
server 已压紧过且无兑付口(**只是展示座**);
|
|
63
|
+
- **入参型与出参型分开**:`ToolApprovalFrame.ruleOffers` 收的是 `readonly WireRuleOffer[]`
|
|
64
|
+
(与上游逐字同形、**无** `offerIndex`),归一后的 `RuleOffer` 只出现在卡入参上;
|
|
65
|
+
- **批臂的兑付**:`persistRuleBatchOfferIndex` 与 `persistRule`/`persistRuleEdited` **三臂互斥**
|
|
66
|
+
(server 对同场组合响亮 400 且**连决断一起拒** ⇒ 包边界整条丢持久臂 + 留痕,决断照送);
|
|
67
|
+
下标必须在本帧 offers 里真指向一条 `kind:'batch'`,越界/指向 single/负数/非整数一律丢键;
|
|
68
|
+
- 🔴 **批臂要能力位**:新增 `ToolApprovalFrameLaneOpts.respondBatchRuleOffersCapable`
|
|
69
|
+
(`capabilities.respondBatchRuleOffers`,server ≥7.46.0)。**宿主漏供 ⇒ 现代引擎上每一次批臂勾选
|
|
70
|
+
都被丢**。只闸兑付不闸展示(offer 照旧上卡,端可走本地落规则那条路);
|
|
71
|
+
- **ack 超集视图** `ToolApprovalRespondAckView`(sdk 7.2.0 锚尚无这两位):`persistedRule`(编辑臂
|
|
72
|
+
落盘的**规范化后**文本 —— 界面回显这一份而不是输入框那份)· `persistedRules`(批臂全体成员规范
|
|
73
|
+
文本,展示序)。两位过**三道包内闸**:单复数同场 ⇒ 两位一起丢 · `rulePersisted !== true` ⇒ 丢 ·
|
|
74
|
+
臂相关性(单数只属编辑臂;复数条数须等于所选 batch 的成员数)。⚠️ 逐条归属今天 wire 上证不出,
|
|
75
|
+
端渲成「**引擎报告**落盘的规则」而不是「你选的那批」——在册 **P-39**,已向 server 索要 additive 锚。
|
|
76
|
+
|
|
77
|
+
### 新增(additive)
|
|
78
|
+
|
|
79
|
+
- **`inputHasBidi` 过境**(#341/[5214]③;server ≥7.46.0 E-14 Trojan Source 族):帧顶层 `?: true`
|
|
80
|
+
→ `ApprovalCardRequest.inputHasBidi`,条件 stamp **只认严格 true**。🔴 **披露位不是清洗位,本包
|
|
81
|
+
字节零改**(清洗会让卡上显示的与真跑的不是同一个东西,比不披露更坏);显形归端。
|
|
82
|
+
**缺席禁读成「已确认干净」**(同时覆盖「真的没有」与「args 序列化不了所以扫不了」)。**单腿**:
|
|
83
|
+
durable 行腿今天无对偶(与 `requiresRealApproval` 同族,§7 P-32);
|
|
84
|
+
- **持久臂被丢时的两条诚实告知**(公开导出 772 → **776**;与 `surfaceRememberNotApplied` 同族,
|
|
85
|
+
要看得见得先装 `HitlHostSurface` 口):
|
|
86
|
+
· `surfaceRuleArmNotSent()` / `RULE_NOT_SENT_WARN_TEXT` —— **能力位未确认**导致持久臂整条没发
|
|
87
|
+
(编辑臂与批臂**共用一条**,同形存量一并清 —— 编辑臂此前也只写 debug);
|
|
88
|
+
· `surfaceRuleArmRejected()` / `RULE_NOT_SENT_REJECTED_WARN_TEXT` —— 这次选择**没过包内表核/互斥核**
|
|
89
|
+
(表外文本 / 坏下标 / 两臂同场;候选臂那条表核自 0.26.0 起也只写 error 日志,同批一起清)。
|
|
90
|
+
🔴 **两条刻意分开**:对用户的下一步建议不同(换台引擎/等探测 vs 换引擎也不会变),折成一条会
|
|
91
|
+
谎报原因。共用两条口径纪律:文案**不说「这台引擎不支持」**(能力位缺席三种同形成因);通知
|
|
92
|
+
**只在 respond 真成功之后**发(文案里有「审批本身过了」,respond 抛错那条路上那句是假的)。
|
|
93
|
+
🔴 **同一次 respond 的通知顺序契约**(`showNotice` 直接顶替 current、不排队 ⇒ 最后发的才看得见):
|
|
94
|
+
三条按**严重度升序**发、最重的压轴 —— 规则没存 < `allow_session` 没记住 < **编辑没转发(工具正在
|
|
95
|
+
用原始入参跑,唯一一条 [high])**。端自己重排/合并时必须保住这条不变量;
|
|
96
|
+
- **帧键镜像同形族扫**(21 → **24** 键):补 `ruleOffers` / `inputHasBidi`,并把族扫捞出的**存量**
|
|
97
|
+
漏键 `parked`(#329,server 7.44.0 及更早就在,`tool_approval_complete` only)一并补进 ——
|
|
98
|
+
镜像补齐,消费面另立项(§7 **P-37**);
|
|
99
|
+
- **`run-approval-frame-keys-test.mjs` 新增 server fixture 腿**:直接解析
|
|
100
|
+
`@sema-agent/server/dist/tool-approval.d.ts` 与本仓镜像做**机器对表**。SDK 运行期锚按构造滞后于
|
|
101
|
+
server,只对 SDK 锚比对的门对「server 已发、SDK 与本仓同时没有」的键是**盲的** —— 那正是 [5223]
|
|
102
|
+
这次静默换键的成因形。`SEMA_CC_SERVER_FIXTURE=<dir>` 开;**提货/抬 pin/发包批必须另加
|
|
103
|
+
`SEMA_CC_REQUIRE_SERVER_FIXTURE=1`** 把它变成硬门(缺 fixture 当场红)。
|
|
104
|
+
|
|
105
|
+
### 呈现面修复(#323,clay 三裁 2026-08-25;已落 `4c364c1`)
|
|
106
|
+
|
|
107
|
+
- **idle-flush 段提交双准入**(每段一刀 + 句末边界):长工具参数生成期的 assistant 文本不再被切成
|
|
108
|
+
碎片(实测碎片 19 → 2 / 5 → 1);
|
|
109
|
+
- **用户中断的 `tool_result` 归一 CC 形**:双模判据 fail-safe,整帧只改 `output` 机读位全过境
|
|
110
|
+
(`isError` / `errorCode` / `_sema_collateral_abort` 一个都不动),五条排水出口统一 + 结构门。
|
|
111
|
+
⇒ **端做归因的 `output` 三分**(§7 P-30 已订正):`'Operation aborted'` = 非中断原因的 fail-soft ·
|
|
112
|
+
CC 中断串 `[Request interrupted by user for tool use]` = 用户中断 · CC REJECT 串 = 用户真拒绝;
|
|
113
|
+
- 同批捡修两真缺陷:中途出口绕过 · 剥尾不判换行;新登记 known-gap **P-36**(中断文案归一只覆盖
|
|
114
|
+
`Operation aborted` 这一串,另两族刻意不并入 —— core 显式拒绝合并两串向)。
|
|
115
|
+
|
|
116
|
+
### 已知局限(本版新增;完整台账见 `docs/INTEGRATION-CLIENTS.md` §6e/§7)
|
|
117
|
+
|
|
118
|
+
- **P-37**:`parked` 已进键镜像但本包零消费(只长在 `tool_approval_complete` 上,那种帧不进卡口);
|
|
119
|
+
- **P-38**(**存量、非本批引入**,基线 `4c364c1` 逐字相同):`frameRouter` 的标准帧路由**只解构
|
|
120
|
+
`decision`**,`ack` 与 `respondRefusal` 整段丢掉 ⇒ 走标准集成面的端读不到规则回显与 400 拒句
|
|
121
|
+
(⚠️ `rememberApplied` / `updatedInputForwarded` 两条更重的告知**不在此列** —— 它们经
|
|
122
|
+
`HitlHostSurface` 就地投递,不经本路由;端自己直调 `surfaceToolApprovalFrameAndRespond` 也拿得到);
|
|
123
|
+
- **P-39**:批臂回执 `persistedRules` 的**逐条归属** wire 上证不出(只核基数),端的呈现口径见上;
|
|
124
|
+
- **P-40**(**系统性、非本批引入**):三个能力位是 **per-baseUrl 的布尔缓存,不与「收 respond 的那个
|
|
125
|
+
副本」绑定**。引擎温切 / 滚动升级 / 多副本代理下,一个陈旧的 `true` 仍会把新形送到老副本上,而
|
|
126
|
+
编辑臂/批臂在老解析器上是**响亮 400 且连决断一起拒**。
|
|
127
|
+
⚠️ ⇒ **「能力位闸让老引擎零受迫」这句话有前提**:能力读数必须与实际 responder 一致。
|
|
128
|
+
0.42.0 段第 4 条里那句无前提的「老引擎零受迫」按本条订正(⚠️ 那一段是**冻结面**,按 #252 宪法
|
|
129
|
+
**不回改**,订正落在本段)。**宿主义务**:引擎 respawn/restart 后调
|
|
130
|
+
`invalidateEngineCaps(baseUrl, probe?)`(推荐两参形);滚动升级窗口内宁可把两个规则能力位按
|
|
131
|
+
`false` 供(丢持久臂 + 一条诚实通知,远好于把人按下的决断打掉)。
|
|
132
|
+
|
|
133
|
+
## 0.42.0(2026-08-24)
|
|
134
|
+
|
|
135
|
+
> 🔴 **两阶段协议提醒(发包批的义务,见本档头注)**:本段发布前须由发包批做**阶段一** ——
|
|
136
|
+
> `package.json` bump `0.41.0 → 0.42.0` + 本段标题转日期 + README `Version` 行跟版 +
|
|
137
|
+
> `run-integration-doc-freshness-test.mjs` 的 `FROZEN` 账**追加**一行 `pending: true`(且必须恰是
|
|
138
|
+
> `package.json` 当前版本);发布后**阶段二**把 `releasedAt`(npm gitHead 回读实证)与段 `sha256`
|
|
139
|
+
> 补上并删 `pending`。**施工批不做 bump、不动 `FROZEN`** —— pending 行的豁免押在「尚未发布」上,
|
|
140
|
+
> 而施工批与发布之间还会有别的改动落进本段,提前钉哈希必然自相矛盾。
|
|
141
|
+
|
|
142
|
+
**九件窗([5125]③):#225 编辑臂五条 additive / [5076] precheck 转出口 / A-D2 `resolution` 透传 /
|
|
143
|
+
[4982] 两条 P0-KPI / [C195] 四臂等值断言 / contract §2.4 撤稿 / seatContract 证据等级标注;
|
|
144
|
+
两件经四源重验后翻案(见文末「翻案与如实登记」)。公开面 **+4 运行期导出**(767 → 771)+ 1 测试钩。**
|
|
145
|
+
|
|
146
|
+
### 🆕 additive:#225 编辑臂五条([5111] 需求单 / server [5071] `respondFreeFormRules`,server ≥7.44)
|
|
147
|
+
|
|
148
|
+
上游是 server #340 开出的自由文本规则臂。壳侧 [5111] 探针在 0.41.0 上实测到**闭集三处**——
|
|
149
|
+
①`persistRule` 是裸 string 无 `edited` 位;②运行期候选表核对自由文本**恒丢键**;③respond 400 原文被
|
|
150
|
+
编排层折成 `debug` 吞掉(错误反馈面不可达)。五条逐条:
|
|
151
|
+
|
|
152
|
+
1. **`ApprovalCardAllowDecision.persistRuleEdited?: true`** —— 兄弟位,既有 `persistRule: string`
|
|
153
|
+
**字节不变**。它是**出身声明**不是放行凭据:带上它只让编排层放弃表核,真判官仍在引擎侧。
|
|
154
|
+
2. **`RespondToolApprovalOpts.persistRuleEdited?: true`** —— 同款兄弟位。🔴 **注入面的映射义务**:
|
|
155
|
+
server 的 respond 体形是 `persistRule: {rule, edited: true}`,本包**刻意保持扁平**(改嵌套形是既有
|
|
156
|
+
消费者的 BREAKING),由注入面合成。缺席 = 候选臂,`rule_not_offered` 语义一字不变。
|
|
157
|
+
3. **运行期候选表核在编辑臂在场时让位** —— 判据锚 `card.persistRuleEdited === true`,
|
|
158
|
+
🔴 **绝不认「文本恰好不在候选表里」**:后者会让一个拼错候选原文的坏卡口自动升级成自由文本臂,
|
|
159
|
+
把「never sending un-offered text to the rule store」那道核对彻底架空。
|
|
160
|
+
4. **`ToolApprovalFrameLaneOpts.respondFreeFormRulesCapable?: boolean`** —— 形与判据**逐字照**既有的
|
|
161
|
+
`approvalDecisionNoteCapable`(同一条 SDK 6.16 纪律:**位缺席就别发**)。缺席/false ⇒ 编辑臂的
|
|
162
|
+
`persistRule` **整条不发**,决断照送,老引擎零受迫、行为逐字节 = 0.41.0。
|
|
163
|
+
5. **`ToolApprovalFrameOutcome.respondRefusal`(+ `readToolApprovalRespondRefusal` 读口)** ——
|
|
164
|
+
respond 抛错时把 server 的响亮拒(400 / `edit-rejected`)**原文交还调用方**。
|
|
165
|
+
🔴 `decision` 语义**一字未改**(仍是 `'unresolved'`);新位是**附加**诊断面,不是第五种 decision。
|
|
166
|
+
原样交还零加工(不 trim/不截断/不改写),`status` 缺席时**不折 0**。
|
|
167
|
+
🔴 **三位全可选、三位皆缺席时整只不铸**(异源复审 [medium] 采纳):`message` 不再无条件退
|
|
168
|
+
`String(err)` —— `throw {}` 铸出来的 `"[object Object]"` 不是 server 说的话,是本层编的。
|
|
169
|
+
逐位读属性各自 `try`(抛出物可以带 `get message() { throw }`)、`String()` 也包 try
|
|
170
|
+
(`Symbol.toPrimitive` 可以抛):这条收敛路径**永不因不可信抛出物而 reject**。
|
|
171
|
+
🔴 **日志侧净化**:`errorCode` 交还调用方时**原样**,写进宿主日志时只放行机读码字符集
|
|
172
|
+
(`[A-Za-z0-9._:-]{1,64}`),形不合只报长度 —— 它是 UNTRUSTED 应答体,原样插日志可伪造日志行、
|
|
173
|
+
可注入终端控制序列。
|
|
174
|
+
|
|
175
|
+
**常驻门**:`run-hitl-gate-honesty-test.mjs` **F12** 三腿(能力位缺席不发 / 表核让位负控 / 400 原文可达),
|
|
176
|
+
各带负控;`run-client-core-pure-test.mjs` B7 段 16 条。接入契约见 `docs/INTEGRATION-CLIENTS.md` **§4b-2**。
|
|
177
|
+
|
|
178
|
+
### 🆕 additive:[5076] `precheckEditedRuleText` 转出口(端口注入形,非 re-export)
|
|
179
|
+
|
|
180
|
+
core 5.57.0 导出了与 `confirmRuleApproval` **同一函数体**的 text×command 三步门。新增四个公面符号:
|
|
181
|
+
`EditedRuleTextPrecheck` / `EditedRuleTextPrechecker`(类型面)、`installEditedRuleTextPrechecker` /
|
|
182
|
+
`hasEditedRuleTextPrechecker` / `precheckEditedRuleText`(注入口 + 存在性读口 + 取值读口)。
|
|
183
|
+
|
|
184
|
+
🔴 **为什么不是 `export { precheckEditedRuleText } from '@sema-agent/core'`**([5076] 自领件的原话):
|
|
185
|
+
value 级 re-export 在本包**做不到**,两条门各自独立否掉它 —— `run-client-core-portability-test.mjs` 的
|
|
186
|
+
`EXPECTED_PACKAGES_INDEX` 是**等值门**(闭包外部包恒等于 `{diff, @sema-agent/sdk}`),而它 ③ 段拿
|
|
187
|
+
esbuild `--platform=browser` **真打一次包**,`@sema-agent/core` 的 barrel 值级拉 `node:crypto`
|
|
188
|
+
(`engine/session/log-digest.js`)、`node:fs`/`node:path`(`core/skills-directory.js`),浏览器腿当场打不成。
|
|
189
|
+
一条这样的边会把**整台引擎**焊进每一个装本包的端,换来一只纯函数。⇒ 取端口注入形:Node 宿主原样装,
|
|
190
|
+
**浏览器 lane 留缺席走往返形**(登记为 §7 缺口 **P-34**,不是漏装)。
|
|
191
|
+
🔴 未装时读口返 **`undefined`**(诚实缺席),绝不编一个 `{ok:true}` —— 那是替一只不在场的判官发言。
|
|
192
|
+
🔴 调用方 bug(`command` 缺席/非串)的响亮拒**原样上抛不吞**:吞掉会把调用方的错渲成对人的判决。
|
|
193
|
+
|
|
194
|
+
### 🆕 additive:A-D2 —— `tool_end.resolution` 透传(cli wire 锚台账 cli-191 镜头 A)
|
|
195
|
+
|
|
196
|
+
`case 'tool_end'` 的闭形白名单又剥掉了一条**已经在 wire 上**的事实,这是同一病形的**第五例**
|
|
197
|
+
(`toolCallId` [1947] → `governanceForced` [2942] → `settledBy`/`approver` #263 → `_sema_collateral_abort`
|
|
198
|
+
#324 → 本条)。`resolution` = core ≥5.35 `AskDenyResolution` 九词。
|
|
199
|
+
|
|
200
|
+
**消费面已经在等它**:判「审批窗口自己走完了」时单锚 `settledBy` 会把「审批方违约」那一臂一起收进来
|
|
201
|
+
(approver 回了契约外的东西,窗口并没有自然走完)。这一位不过境 ⇒ 判据**缺一臂**,消费端只能退到锚
|
|
202
|
+
core 散文(文案一改就漂)。
|
|
203
|
+
|
|
204
|
+
🔴 **给消费端的判据是正面匹配**(异源复审 [medium] 采纳,与接入档 §3d-2 同口径):
|
|
205
|
+
`settledBy === 'timeout' && resolution === 'window_expired'`。**绝不**写
|
|
206
|
+
`resolution !== 'approver_contract'` —— 那个不等式对**缺席**(旧引擎不发这一位)与对**任何未来
|
|
207
|
+
新码**都为真,会把「不知道是怎么拒的」折成「窗口自然结束」。⚠️ **行为收紧披露**:旧引擎(不发
|
|
208
|
+
`resolution`)在正面匹配下**不再可折叠** —— 这是刻意的 fail-closed 方向:没有证据就不声称「窗口
|
|
209
|
+
走完了」。四条负控钉在 pure 门 A-D2②b-②e。
|
|
210
|
+
|
|
211
|
+
开集防御读、缺席不铸键、在场原样(与同臂三键同姿势);`ToolEndResultArmLike.resolution` 同批补声明。
|
|
212
|
+
接入契约见 **§3d-2**。
|
|
213
|
+
|
|
214
|
+
### 🔴 修复:[4982] 候选① —— `SEMA_ATTACHMENTS=off` 关不掉 `backgroundTasks`(P0-KPI)
|
|
215
|
+
|
|
216
|
+
core **5.12.0(BREAKING)** 把 `attachments.backgroundTasks` 与 `agentListing`/`skillsListing` **同批**
|
|
217
|
+
翻成 DEFAULT-ON(core `types.d.ts` 逐字:「boolean, not `true`: explicit `false` is the opt-out;
|
|
218
|
+
**same contract as the listing family below**」)。本模块当时**跟修了后两个、漏了第一个** ——
|
|
219
|
+
`backgroundTasks` 一直留在「off = 删键,永不发 false」那一族。后果是这个旋钮上最坏的一种失效:
|
|
220
|
+
`SEMA_ATTACHMENTS=off`(文件自称「真·全关」)对这一位与**什么都不配**逐字节同效(两条路都不发这个键
|
|
221
|
+
= 都是「开」),且**零报错**。
|
|
222
|
+
|
|
223
|
+
**修法 = 把「能不能显式开」与「能不能显式关」拆成两条正交的轴**,`backgroundTasks` 两族兼属:
|
|
224
|
+
- 轴 A(可显式开,`full` 全开的 7 员)—— membership **不动**;
|
|
225
|
+
- 轴 B(可显式关,core DEFAULT-ON)—— `backgroundTasks` **新入**,`off` 与 `-backgroundTasks` 产出 `false`。
|
|
226
|
+
|
|
227
|
+
🔴 **为什么不把它整个搬进 DEFAULT-ON 族**(那是错的修法,两条理由都是硬的):①`full`/`default`/
|
|
228
|
+
`+backgroundTasks` 三条既有路会当场少发一个 `true` —— 对 core ≥5.12.0 行为等价,但对**更旧引擎**
|
|
229
|
+
那是真的从「开」变「关」,而本模块手里没有 server 版本这个量;②空 env 的默认产出
|
|
230
|
+
`{backgroundTasks: true, toolsDelta: true}` 是 [487]② CC-parity 的字面承诺。
|
|
231
|
+
⇒ 本批**只加一条出路**,其余取值**逐字节不变**(三条正控钉住)。
|
|
232
|
+
|
|
233
|
+
**行为变化(端可感,收紧方向)**:`SEMA_ATTACHMENTS=off|0|false|no|none` 现在多发一个
|
|
234
|
+
`backgroundTasks: false`。受损方 = 无(旧行为在**所有** server 代际上都关不掉它)。
|
|
235
|
+
|
|
236
|
+
### 🔴 修复:[4982] 候选② —— 线上目录「传输 ok、内容被拒」时缓存腿从未被问过(P0-KPI)
|
|
237
|
+
|
|
238
|
+
`loadCatalogWithSources` 的缓存腿门槛是 `hit === null`,而 `hit` 只记录**传输层**结果(200 + 旁签过
|
|
239
|
+
就算 hit)。于是「线上目录发得下来、但 `schemaVersion` 超区间被 `validateOnlineCatalog` 拒」这条缝里:
|
|
240
|
+
loader 认为「有 hit」⇒ **永不查缓存**(哪怕缓存里躺着一份上次真正被接受过的合法目录),用户只拿到
|
|
241
|
+
内置精简表;而 `cacheHit:false` 这个**诚实位反过来说谎** —— 读起来是「没有缓存」,真相是「有缓存但
|
|
242
|
+
从头到尾没被问过」。
|
|
243
|
+
|
|
244
|
+
**修法**:判据锚在**真正决定结果的量**上 —— 决定「用不用得上目录」的是 `base.online.ok`(内容判决),
|
|
245
|
+
不是 `hit !== null`(传输判决)。缓存腿的读法提成一处(两个调用点不许有两份读法),内容被拒后补一条
|
|
246
|
+
回落腿。🔴 缓存那份**不开后门**:它照样过同一个 `resolveModelCatalog`;也被拒时**保持第一次的结果**
|
|
247
|
+
(不拿一份同样不合格的文档顶替),`cacheHit` 诚实留在 `false`——本位的语义是「这次的目录**是从缓存
|
|
248
|
+
来的**」,不是「问过缓存」,抬成 true 会让「缓存救场了」与「缓存也坏了」在读数上不可分。两种情形
|
|
249
|
+
各有一条**措辞可分**的 warning。
|
|
250
|
+
|
|
251
|
+
### 🆕 [C195] `classifyTurnWireError` ⇄ web `turn-error-classify` **四臂**等值断言(常驻门)
|
|
252
|
+
|
|
253
|
+
**账目先说清楚**(四源重验):[C195] 说「包内 web 同形样本三臂等值断言已常驻」,C 板 [3903]⑤ 判
|
|
254
|
+
「查无实物」并让 0.42.0 真补 —— **两句都不完全对**。那三条 `web 等值样本①②③` 自 `6257fe1`
|
|
255
|
+
(`package.json` version = **0.30.3**)起就一直在 `run-client-core-pure-test.mjs` 里,0.36.0 那会儿也在;
|
|
256
|
+
「查无实物」是**假阴**(测试面不进 `dist`,按 dist 找必然零命中),当时的认账是没核实就跟着认了。
|
|
257
|
+
|
|
258
|
+
**但真缺口确实存在,只是不在那三条上**:它们是**手造近似样本**,不是照 web
|
|
259
|
+
`ui/lib/turn-error-classify.ts` 的**四条真臂**取的;而且那份文件里两处**刻意的**跨端差异一条断言都没有。
|
|
260
|
+
本批补齐:①http 臂四个状态码逐条;②network↔transport **共用基表 13 个 token 逐 token**对拍
|
|
261
|
+
(抽样对「某条词被谁删掉了」零判别力);③**负控**=浏览器-only 三种说法(`Failed to fetch` /
|
|
262
|
+
`NetworkError` / `Load failed`)在本包判 `internal` —— 宿主词归 web 叠加,包不替它判;
|
|
263
|
+
④unknown↔internal;⑤**差异登记**=web 的 `timeout` 臂本包无对位,钉住真实落点 + 判别力自证;
|
|
264
|
+
⑥本包独有的 park 臂登记为超集。
|
|
265
|
+
|
|
266
|
+
🔴 **异源复审 [medium] 再修一层:token 字符串对拍换成**行为**对拍**。第一版把两边归一成小写、
|
|
267
|
+
还删掉 `\b` 再比 —— 那样 web 把 `/fetch failed/i` 改成 `/fetch failed/` 时对拍**照绿**,而真实
|
|
268
|
+
分类行为已经分叉。现在按两边的**真 RegExp**(source + flags 原样重建)在生成样本(原样/全大写/
|
|
269
|
+
全小写)上逐条跑 `test()` 对判决。同批把容器判据从「那一份文件在不在」换成**仓标志物** ——
|
|
270
|
+
**仓在而分类器被搬走/改名 ⇒ 直接红**(那正是本腿最该报警的场景,第一版会把它当「容器缺席」放过)。
|
|
271
|
+
|
|
272
|
+
🚩 **对拍当场测出一条真实跨端分叉,如实登记(接入档 §7 缺口 P-35)**:本包 `WIRE_NETWORK_ERROR_PATTERN`
|
|
273
|
+
整条带 `i`,而 web 的 errno 类 token(`ECONNREFUSED` / `ECONNRESET` / `ETIMEDOUT` / `EAI_AGAIN` /
|
|
274
|
+
`ENOTFOUND` / `EHOSTUNREACH` / `ENETUNREACH` / `EPIPE` / `UND_ERR`)**逐条无 `i`** ⇒ 同一条小写 errno
|
|
275
|
+
文本,本包判 `transport`、web 判 `unknown`。分叉**不抹平**(抹平就是替 web 改它的判定),而是
|
|
276
|
+
登记 + 用门钉住:未登记的新分歧红、**登记的分叉消失也红**(逼下一棒删登记而不是让它长住)。
|
|
277
|
+
|
|
278
|
+
### 🔴 撤稿:contract §2.4「DENY-abort」句(server [4833] 明请,契约成文 `4631a0f` 随 7.39 出)
|
|
279
|
+
|
|
280
|
+
server `ASSISTANT-WIRE-CONTRACT.md` **§4a** 逐字:**DENY is a TOOL-level answer, NEVER a run kill**;
|
|
281
|
+
客户端不得把用户的拒绝译成 cancel;两动词的 wire 判别式 = `cancelled` vs `gate.batch_halted`。
|
|
282
|
+
包内四处注释引 `contract/04 §2.4` 当权威,其中两条断言已被证伪,逐条撤:
|
|
283
|
+
|
|
284
|
+
- **「deny 用来 cancel 一条 run」** —— §4a 反过来说。四处引用改锚 §4a
|
|
285
|
+
(`hitlBridge.decideTool` 头注 + `canUseTool` deny 臂 / `parkResolver` 中断臂 / `toolApprovalWire`
|
|
286
|
+
aborted 臂 / `askGateWire` 模块头注);
|
|
287
|
+
- **「`runs.cancel` 对 suspended run 回 409」** —— 自 server [868] 起**就地取消**已实装
|
|
288
|
+
(`cancelSuspended` 有实体,409 只剩 `conflict.approval_settled` 一条;本仓
|
|
289
|
+
`docs/fresh-scan-client-core-2026-08-08.md` B型-5 早已按真字节证伪,当时未跟修注释)。
|
|
290
|
+
`parkResolver` 那句「引擎侧解锁腿**到货前**这是壳能做的最诚实半场」同批订正 —— 那条腿早就到货了。
|
|
291
|
+
|
|
292
|
+
**`CANCEL_DENY_BUDGET_MS` 论证前提重审**:2s 预算的原理由(「引擎收到 deny 即终结 run」)**作废**;
|
|
293
|
+
它换一个理由仍然成立(观察上限,给「decide 永不返回」一条出声的路),但**数值本批不动** ——
|
|
294
|
+
动它是行为面改动,判据只有真实 decide 往返分布说了算,而那份实测在消费端;且下游 `sema-cli` 的
|
|
295
|
+
`hitlCancelDeny.test.ts` 按现值锁着行为,单边改会当场把消费端打红。残余如实登记为 §7 缺口 **P-33**。
|
|
296
|
+
|
|
297
|
+
**行为零变化**:本件只改散文与注释锚点,`decideTool` 本来走的就是 §4a 说的那条 TOOL 级 decide 通路。
|
|
298
|
+
|
|
299
|
+
### 🆕 seatContract 证据等级标注(test [5087] 的「语料**种类**缺口」/ cli [5088] 认领件①)
|
|
300
|
+
|
|
301
|
+
`src/seatContract.ts` 里所有以「CC 如何如何」为形的断言 —— `212 methods` / `854-channel census` /
|
|
302
|
+
方法名逐字保留 / `fQe` 逐字段对照 / 一切 `.vite/build/index.chunk-*.js` 坐标 —— 加**证据等级标注**:
|
|
303
|
+
**桌面 unpack(CC Electron app 的 `.vite/build` 产物),本地语料库不可复验**。
|
|
304
|
+
|
|
305
|
+
本仓手边可复验的参照语料**只覆盖终端 CLI 形态**的静态产物,这不是「语料版本旧」而是**产物形态不对**
|
|
306
|
+
—— 拿 CLI 形态的语料去 grep 桌面壳里的符号只会零命中,而零命中在这里**既不能证真也不能证伪**。
|
|
307
|
+
🔴 标注写在源码里(而不是只写进不进 tarball 的台账),因为**这段注释随 `dist/*.d.ts` 出包**,
|
|
308
|
+
三端读到的就是它;只写台账 = 消费端仍读到一句看起来可复验、实际不可复验的断言(与本文件
|
|
309
|
+
`workerBadge` 出身订正同一条理由)。**对下游的判据**:复核这些断言**不得**拿本仓 cc-decoded 当反证。
|
|
310
|
+
|
|
311
|
+
### 🚩 翻案与如实登记(四源重验,不硬做)
|
|
312
|
+
|
|
313
|
+
- **`engine_notice` 投影臂([5069] S13 第二实例)—— 本仓半场早在 0.38.0 就已落地,0.42.0 无施工面**。
|
|
314
|
+
直证:`016e257`(#318 合批四件)已落 `eventToSdkMessage` raw 预分派 + `adapt` chrome 臂
|
|
315
|
+
`engineNoticeArm` + `INTERNAL_SDK_ARM_TYPES` 登记 + 接入档 **§3f**(键形 + 端的五条消费纪律 + 两条
|
|
316
|
+
如实登记),常驻门在 `#318 段` 24 条。[5069] 自己的核实动作④也写着「client-core `adapt/arms.ts`
|
|
317
|
+
确认真实注册在生产投影表里」。⇒ 缺的**只有**壳侧渲染腿(cli `upstreamBridge.consumeChrome` 的
|
|
318
|
+
`default` 静默吞),那是 cli 仓的批面,本仓无对应施工。0.42.0 顺手复验了 §3f 那条「SDK 类型面尚未
|
|
319
|
+
到货」的登记:`@sema-agent/sdk@7.2.0` 的 `dist/` 全树 `engine_notice` 仍零命中(正控 `cycleSeq` 有命中,
|
|
320
|
+
证明探针会说话)⇒ 该登记**仍然成立**,raw 预分派与自退休钉原样保留。
|
|
321
|
+
- **[C195] 三臂等值断言「查无实物」= 假阴**,详见上文 [C195] 段的账目说明;本批补的是**四臂真样本 +
|
|
322
|
+
两处刻意差异的负控**,不是「从无到有」。
|
|
323
|
+
|
|
324
|
+
### 门与验证
|
|
325
|
+
|
|
326
|
+
- **全门族 32/32 绿**;`run-client-core-pure-test.mjs` 3062 条(B3 226→252 / B7-core 291→330 /
|
|
327
|
+
FIX7 82→89,均**贴当日实测零松量**)、`run-hitl-gate-honesty-test.mjs` 179→196、
|
|
328
|
+
`run-catalog-loader-gates-test.mjs` 118→137、`run-integration-doc-freshness-test.mjs` 464→468、
|
|
329
|
+
`run-engine-vocab-floor-test.mjs` 76→93。
|
|
330
|
+
- **🔴 devDep `@sema-agent/core` `^5.50.0` → `^5.57.0` + `CORE_FLOOR` 5.50 → 5.57**(异源复审
|
|
331
|
+
[high] 采纳):本批的类型面自称「逐形镜像 core 5.57.0 的 `precheckEditedRuleText`」,而对账物
|
|
332
|
+
停在 5.50(lock 实装 5.52.0,**根本没有那个导出**)⇒「导出不存在 / 返回形漂了」两种真故障在门上
|
|
333
|
+
一条都看不见,而 Node 宿主照接入档 import 会当场炸。同批加 **G4 段**:直接
|
|
334
|
+
`import('@sema-agent/core')` 拿**真函数**装进本包注入口跑正负例 + 返回形逐键对账 + 调用方 bug
|
|
335
|
+
响亮抛。**这是恒绿病根的第四次复发**(前三次 5.10→5.20 / 5.20→5.43 / 5.43→5.50,各有记账)。
|
|
336
|
+
- **🔴 地板一抬,G3-a 当场翻红(红先绿后留证)**:core 5.5x 中段把 `capRefusal` helper 拆掉了 ——
|
|
337
|
+
拒绝对象改在 `admit(kind)` 里就地铸,孪生拼法 `{error, code}` 挪到四个 `errorResult(...)` 调用位。
|
|
338
|
+
老锚(函数名字面)在 5.57 上抽不出体 ⇒ 门走 exit 9「判据没有判别力」。**重锚不锚函数名**(那正是
|
|
339
|
+
刚烂掉的东西),改成对「凡交出 `refusal.text` 的 `errorResult` 调用」做**全称**断言 + 两码字面锚
|
|
340
|
+
`code:` 铸点位。这正是本地板存在的理由。
|
|
341
|
+
- **棘轮登记**:portability `MAX_INDEX_CLOSURE_FILES` 136→137(新叶件零 import、
|
|
342
|
+
**不动 `EXPECTED_PACKAGES_INDEX`**)· singleton `HIGH_RISK_CEILING` 92→93(注入槽的装-读分裂属
|
|
343
|
+
`queuePort`/`tailMetaSink` 同族,逐条理由在册)· typeshape `unknownExport` 253→255
|
|
344
|
+
(`ToolEndResultArmLike.resolution` 与 `readToolApprovalRespondRefusal(err: unknown)`,各与同族既有位
|
|
345
|
+
同理由)。
|
|
346
|
+
- **异源复审第三轮四条全采纳(门本身的假绿口)**:
|
|
347
|
+
· **G4 对拍了另一份 core**([high])—— 裸 `import('@sema-agent/core')` 走本仓 node_modules,而本门
|
|
348
|
+
前半段的 `CORE` 可来自 `SEMA_CORE_PKG` / 兄弟树 ⇒ 会「拿 A 的版本号报 B 的行为」。改成按选定
|
|
349
|
+
`CORE` 目录的真入口导入 + 实例一致性断言;
|
|
350
|
+
· **[C195] 动态登记掩盖 web 删词**([high])—— 「含大写就算已登记」会让 web **删掉**
|
|
351
|
+
`/ECONNRESET/` 这种真回归自动过关。登记表改**静态审核表** + 补一条独立的**存在性**双向断言
|
|
352
|
+
(shell ⊆ web)+ 登记表僵尸行反钉;变异 M14(模拟 web 删词)实撞 RED;
|
|
353
|
+
· **`errorResult` 括号平衡抽取会吞并调用**([medium])—— 字符串/模板里一个不配对的 `(` 就能把两个
|
|
354
|
+
真实调用合成一片,前一个缺 `code` 也被后一个命中。换 **TS AST** 逐 `CallExpression` 取,
|
|
355
|
+
正控样本专门造「字符串里有不配对括号」的形;
|
|
356
|
+
· **refusal 的「在场即至少一位」没进类型**([medium])—— 三位全 optional 允许 `{}`。改成**三选一
|
|
357
|
+
联合**并在构造侧逐位收窄,编译期与运行期是同一条不变量。
|
|
358
|
+
|
|
359
|
+
- **变异自证 13 条**(cp 备份 + 逐字节复原核对,全部 IDENTICAL):
|
|
360
|
+
M1 表核让位 / M2b 能力位闸反接 / M3 400 原文交还 / M4b `resolution` 恒不铸键 /
|
|
361
|
+
M5 `backgroundTasks` 归族 / M6b 缓存回落腿判据反接 / M7 预检诚实缺席 /
|
|
362
|
+
M8 候选链内容级 failover / M9 归因分类(混合故障)/ M10c `errorCode` 日志净化 /
|
|
363
|
+
M11b refusal 占位串黑名单 / M12 C195 行为对拍(基表掉一词)/ M13b G4 真 core 装口 /
|
|
364
|
+
M14 C195 存在性(模拟 web 删 `/ECONNRESET/`)——
|
|
365
|
+
**各自打点后单跑对应门,全部 RED**,复原后全部 GREEN。
|
|
366
|
+
🔴 **凡首轮判「编译期红」的一律换成编译得过的等效变异重跑**(编译红只证明动了源码,不证明断言
|
|
367
|
+
抓得住)。**M10 第一次跑出 GREEN = 真覆盖缺口**:`logSafeErrorCode` 当时没有任何断言看着它,
|
|
368
|
+
按 [mutation-must-match-guard-class] 补了 F12 腿3b 三条(日志零脏字节 / 交还原样 / 形合码照写)
|
|
369
|
+
之后重跑才 RED —— 这一格如实记账,不当作「变异没抓到所以没问题」。
|
|
370
|
+
|
|
31
371
|
## 0.41.0(2026-08-23)
|
|
32
372
|
|
|
33
373
|
**patch:七件真缺陷修(逃逸计数跨腿残留 / 面板缓冲无界 / 单源闸的撞名绕行口 / 决断成功摘错扣留帧 /
|
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.43.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
|
|
@@ -31,8 +31,18 @@ export interface TextStream {
|
|
|
31
31
|
drainLive(): Generator<AdapterOutput>;
|
|
32
32
|
/** 提交累积的思考块。 */
|
|
33
33
|
takeThinking(): Generator<AdapterOutput>;
|
|
34
|
-
/** 提交累积的文本段(#27 段序铁律)。 */
|
|
34
|
+
/** 提交累积的文本段(#27 段序铁律)。**正常边界**专用(工具卡前 / turn 收口)。 */
|
|
35
35
|
takeAnswerSegment(): Generator<AdapterOutput>;
|
|
36
|
+
/**
|
|
37
|
+
* T40 IDLE-FLUSH 专用的段提交口(#323 症状①,2026-08-25 clay 裁 (a)+(b) **合取**)。
|
|
38
|
+
*
|
|
39
|
+
* 与 {@link takeAnswerSegment} 的差别只在**准入**,提交本体逐字同一份实现:
|
|
40
|
+
* (a) **每段至多一刀**:idle 提交过一次之后,该段后续 idle 一律跳过,直到一次**正常边界**
|
|
41
|
+
* (工具卡前 / turn 收口的 `takeAnswerSegment`)重开新段;
|
|
42
|
+
* (b) **只在句末/段末边界提交**:段尾不是句末字符或换行 ⇒ 跳过本拍,等下一拍。
|
|
43
|
+
* 两条都不满足时**什么都不做**(缓冲原样押着,活体预览照常由 `drainLive` 走)。
|
|
44
|
+
*/
|
|
45
|
+
takeAnswerSegmentOnIdle(): Generator<AdapterOutput>;
|
|
36
46
|
/** D3 IDLE-FLUSH 竞速判据:四个缓冲有任何一个非空。 */
|
|
37
47
|
hasPendingContent(): boolean;
|
|
38
48
|
/** D5 攒批节拍:一个间隔至多一次 flush(判据与 `lastFlushAt` 一起收在本模块内)。 */
|
package/dist/adapt/textStream.js
CHANGED
|
@@ -1,5 +1,46 @@
|
|
|
1
1
|
import { chrome, MAIN, transcript } from './ids.js';
|
|
2
2
|
import { estimateCjkTokens, FLUSH_INTERVAL_MS } from './wireShapes.js';
|
|
3
|
+
/**
|
|
4
|
+
* 件 A(#323 症状①)判据 (b) 的句末字符集 —— 中英文各一套,**闭集**(开集匹配会把「代码里的点」
|
|
5
|
+
* 之外的一切标点都当句末,等于没收紧)。换行单独在 {@link endsAtSentenceBoundary} 里判(段末)。
|
|
6
|
+
*/
|
|
7
|
+
const SENTENCE_END_CHARS = new Set(['。', '!', '?', '…', '.', '!', '?']);
|
|
8
|
+
/**
|
|
9
|
+
* 句末标点**之后**还可以合法跟着的收尾符(闭集):引号 / 括号 / markdown 强调与行内代码标记。
|
|
10
|
+
* 🔴 有它的理由(异源复审 finding 采纳):真句末很常见地**不以句末标点结尾** ——
|
|
11
|
+
* `他说:“完成了。”` 的末字符是右引号、`**Done.**` 的末字符是 `*`。不剥这一层,判据 (b) 会把
|
|
12
|
+
* 这些**真句末**永久判否(每一拍 idle 都返回 false),等于对带引号/强调的回复整条腿失效。
|
|
13
|
+
* 🔴 只剥**收尾**符,不做括号配对分析:未闭合的行内代码 `` `a.b `` 末字符是 `b`,本来就判否;
|
|
14
|
+
* 而 `` `a.b` `` 剥掉反引号后是 `b`,同样判否 —— 代码跨度里的点不会被误当句末。
|
|
15
|
+
*/
|
|
16
|
+
const SENTENCE_CLOSING_CHARS = new Set([
|
|
17
|
+
'"', "'", '”', '’', '』', '」', '》', '〉', '】', '〕', ')', ')', ']', ']', '}', '}', '*', '_', '~', '`',
|
|
18
|
+
]);
|
|
19
|
+
/** 收尾符最多剥这么多层 —— 剥的是收尾符,不是「一直回溯到找得着句号为止」。 */
|
|
20
|
+
const MAX_TRAILING_CLOSERS = 8;
|
|
21
|
+
/**
|
|
22
|
+
* 段尾是不是句末/段末边界(件 A 判据 (b))。
|
|
23
|
+
* 行末空格/制表符先剥掉再看最后一个字符 —— 流式 delta 常把 `". "` 拆成两拍,不剥就恒判否。
|
|
24
|
+
* 然后剥一层 {@link SENTENCE_CLOSING_CHARS} 收尾符,最后一次性判「换行(段末)∪ 句末标点」。
|
|
25
|
+
*
|
|
26
|
+
* 🔴 **换行判在剥尾之后**(异源复审第四轮 finding 采纳):换行检查若只做在剥尾**之前**,
|
|
27
|
+
* `Done.\n**`、整块围栏代码块(```` ```\nx\n``` ````)、`“完成了\n”` 这些**剥掉收尾符才露出换行**
|
|
28
|
+
* 的形会全部判否 —— 恰好是「已经写完一段」最常见的三种收尾,判否等于把它们永久押到工具边界/
|
|
29
|
+
* turn 收口。换行本身不在收尾符集里,所以「先剥后判」对「换行就在最外层」那些形是恒等的。
|
|
30
|
+
*/
|
|
31
|
+
function endsAtSentenceBoundary(s) {
|
|
32
|
+
let t = s.replace(/[ \t]+$/u, '');
|
|
33
|
+
if (t.length === 0)
|
|
34
|
+
return false;
|
|
35
|
+
let last = t[t.length - 1];
|
|
36
|
+
for (let i = 0; i < MAX_TRAILING_CLOSERS && SENTENCE_CLOSING_CHARS.has(last); i++) {
|
|
37
|
+
t = t.slice(0, -1).replace(/[ \t]+$/u, '');
|
|
38
|
+
if (t.length === 0)
|
|
39
|
+
return false;
|
|
40
|
+
last = t[t.length - 1];
|
|
41
|
+
}
|
|
42
|
+
return last === '\n' || last === '\r' || SENTENCE_END_CHARS.has(last);
|
|
43
|
+
}
|
|
3
44
|
export function createTextStream(ctx, idOf) {
|
|
4
45
|
// ── turn 级状态(cli generator 局部变量的等价;矩阵 §1.1 逐行对位)──────────────────────────
|
|
5
46
|
let answer = '';
|
|
@@ -11,6 +52,12 @@ export function createTextStream(ctx, idOf) {
|
|
|
11
52
|
let textBlockOpen = false;
|
|
12
53
|
let thinkingBlockOpen = false;
|
|
13
54
|
let lastFlushAt = 0;
|
|
55
|
+
/**
|
|
56
|
+
* 件 A 判据 (a):**本段已经用掉 idle 的那一刀**。写于 `takeAnswerSegmentOnIdle` 真提交那一拍,
|
|
57
|
+
* 复位于 `takeAnswerSegment`(= 正常边界到达 ⇒ 重开新段)——**空段的正常边界也复位**:
|
|
58
|
+
* 「工具帧来过」本身就是重开新段的事实,与那一拍有没有散文可提交无关。
|
|
59
|
+
*/
|
|
60
|
+
let idleFlushUsedInSegment = false;
|
|
14
61
|
/** T29 攒批节拍:宿主策略优先(桌面/web 可传 16ms 对齐 CC 桌面端),缺席=207 CLI 口径 100ms。 */
|
|
15
62
|
const flushEveryMs = typeof ctx.coalesceIntervalMs === 'number' && ctx.coalesceIntervalMs >= 0
|
|
16
63
|
? ctx.coalesceIntervalMs
|
|
@@ -37,8 +84,9 @@ export function createTextStream(ctx, idOf) {
|
|
|
37
84
|
yield chrome({ kind: 'thinking_activity', laneProof: MAIN, active: false });
|
|
38
85
|
yield transcript(msg, ctx.now());
|
|
39
86
|
}
|
|
40
|
-
/**
|
|
41
|
-
|
|
87
|
+
/** 段提交的**唯一本体**(两个入口 `takeAnswerSegment` / `takeAnswerSegmentOnIdle` 共用;
|
|
88
|
+
* 两个入口的差别只在准入判据,提交出来的消息一个字节都不分叉)。 */
|
|
89
|
+
function* emitAnswerSegment() {
|
|
42
90
|
if (answerSegment.length === 0)
|
|
43
91
|
return;
|
|
44
92
|
const anchor = segmentAnchor ?? {};
|
|
@@ -55,6 +103,23 @@ export function createTextStream(ctx, idOf) {
|
|
|
55
103
|
textBlockOpen = false;
|
|
56
104
|
yield transcript(msg, ctx.now());
|
|
57
105
|
}
|
|
106
|
+
/** 提交累积的文本段(cli takeAnswerSegment:#27 段序铁律——工具卡前/turn 末各 flush 一次)。
|
|
107
|
+
* 🔴 **正常边界**入口:除了复位件 A 的 idle 配额,行为与拆分前逐字一致(零改动面)。 */
|
|
108
|
+
function* takeAnswerSegment() {
|
|
109
|
+
idleFlushUsedInSegment = false;
|
|
110
|
+
yield* emitAnswerSegment();
|
|
111
|
+
}
|
|
112
|
+
/** 件 A:IDLE-FLUSH 专用入口(准入 = 每段一刀 + 句末/段末边界;两条都过才提交)。 */
|
|
113
|
+
function* takeAnswerSegmentOnIdle() {
|
|
114
|
+
if (answerSegment.length === 0)
|
|
115
|
+
return;
|
|
116
|
+
if (idleFlushUsedInSegment)
|
|
117
|
+
return;
|
|
118
|
+
if (!endsAtSentenceBoundary(answerSegment))
|
|
119
|
+
return;
|
|
120
|
+
idleFlushUsedInSegment = true;
|
|
121
|
+
yield* emitAnswerSegment();
|
|
122
|
+
}
|
|
58
123
|
/** 合并后的活体增量下泄(cli drainLive:思考先于文本 = 持久臂序)。 */
|
|
59
124
|
function* drainLive() {
|
|
60
125
|
if (thinkingPending.length > 0) {
|
|
@@ -114,6 +179,7 @@ export function createTextStream(ctx, idOf) {
|
|
|
114
179
|
drainLive,
|
|
115
180
|
takeThinking,
|
|
116
181
|
takeAnswerSegment,
|
|
182
|
+
takeAnswerSegmentOnIdle,
|
|
117
183
|
hasPendingContent: () => thinking.length > 0 ||
|
|
118
184
|
answerSegment.length > 0 ||
|
|
119
185
|
thinkingPending.length > 0 ||
|
|
@@ -41,6 +41,14 @@ export declare const FLUSH_INTERVAL_MS = 100;
|
|
|
41
41
|
* 🔴 这是**过渡修**,正解在 core [817](wire 侧给事件);且**需要宿主定时器**:
|
|
42
42
|
* `ctx.setTimer` 缺席 ⇒ 整条竞速不启用(纯等下一帧),与 0.4.0 逐字同行为。
|
|
43
43
|
*
|
|
44
|
+
* 🔴 **适用域收紧**(#323 症状①,2026-08-25 clay 裁 (a)+(b) 合取):上面这条「缓冲非空就提交」
|
|
45
|
+
* 的原文**外溢**到了它没打算管的场景 —— 慢模型的正常吐字节奏本身就带 >1.5s 的卡顿,于是每卡一次
|
|
46
|
+
* 切一刀,一轮回复碎成 N 条 assistant 消息(屏上 N 个 ⏺;离线复现:1600ms 间隔 ⇒ 19 条)。
|
|
47
|
+
* 段提交因此改走 `textStream.takeAnswerSegmentOnIdle` 的**两条准入**:
|
|
48
|
+
* (a) 每个 answer segment 至多一刀(用掉之后要等一次正常边界重开新段);
|
|
49
|
+
* (b) 只在段尾是句末字符(`。!?…` / `.!?`)或换行时提交,否则跳过本拍等下一拍。
|
|
50
|
+
* 原意(大 Write 静默窗:开场白句末收尾 + 长静默 ⇒ 开场白先上屏)在两条准入下**原样成立**。
|
|
51
|
+
*
|
|
44
52
|
* 公面。
|
|
45
53
|
*/
|
|
46
54
|
export declare const IDLE_FLUSH_MS = 1500;
|
package/dist/adapt/wireShapes.js
CHANGED
|
@@ -26,6 +26,14 @@ export const FLUSH_INTERVAL_MS = 100;
|
|
|
26
26
|
* 🔴 这是**过渡修**,正解在 core [817](wire 侧给事件);且**需要宿主定时器**:
|
|
27
27
|
* `ctx.setTimer` 缺席 ⇒ 整条竞速不启用(纯等下一帧),与 0.4.0 逐字同行为。
|
|
28
28
|
*
|
|
29
|
+
* 🔴 **适用域收紧**(#323 症状①,2026-08-25 clay 裁 (a)+(b) 合取):上面这条「缓冲非空就提交」
|
|
30
|
+
* 的原文**外溢**到了它没打算管的场景 —— 慢模型的正常吐字节奏本身就带 >1.5s 的卡顿,于是每卡一次
|
|
31
|
+
* 切一刀,一轮回复碎成 N 条 assistant 消息(屏上 N 个 ⏺;离线复现:1600ms 间隔 ⇒ 19 条)。
|
|
32
|
+
* 段提交因此改走 `textStream.takeAnswerSegmentOnIdle` 的**两条准入**:
|
|
33
|
+
* (a) 每个 answer segment 至多一刀(用掉之后要等一次正常边界重开新段);
|
|
34
|
+
* (b) 只在段尾是句末字符(`。!?…` / `.!?`)或换行时提交,否则跳过本拍等下一拍。
|
|
35
|
+
* 原意(大 Write 静默窗:开场白句末收尾 + 长静默 ⇒ 开场白先上屏)在两条准入下**原样成立**。
|
|
36
|
+
*
|
|
29
37
|
* 公面。
|
|
30
38
|
*/
|
|
31
39
|
export const IDLE_FLUSH_MS = 1500;
|
package/dist/adapt.js
CHANGED
|
@@ -228,6 +228,12 @@ class WireToCcAdapterImpl {
|
|
|
228
228
|
for (;;) {
|
|
229
229
|
const nextPromise = iterator.next();
|
|
230
230
|
let step;
|
|
231
|
+
// 🔴 件 A 跟修的**留钉**(2026-08-25,一次证伪留档,免得下一棒再走一遍):判据收紧后同一个
|
|
232
|
+
// `nextPromise` 会被 race **多圈**(判据没过就让位),曾疑心「每圈现造的派生 promise 没人
|
|
233
|
+
// await ⇒ 上游 reject 时 unhandledRejection」。**实测证伪**:`Promise.race` 会给每个入参
|
|
234
|
+
// 挂反应,让位那些圈的派生 promise 因此都是 handled 的;上游的错照常经这一圈 await 抛出去。
|
|
235
|
+
// 对应常驻断言 = pure 门 B3 T40「上游 reject 照常抛给调用方」。所以这里**不改**(改成
|
|
236
|
+
// 只派生一次是纯分配面的微优化,零行为差,不值一处 diff)。
|
|
231
237
|
while (step === undefined) {
|
|
232
238
|
// 缓冲全空 ⇒ 零开销路径;宿主没给定时器 ⇒ 竞速整条不启用(绝不 setTimeout 兜底)。
|
|
233
239
|
// 判据本体(四项 or)已随 12 行状态收进 M1 —— 这里读的是它的具名形。
|
|
@@ -254,8 +260,13 @@ class WireToCcAdapterImpl {
|
|
|
254
260
|
// committed 序 —— 与工具卡前的边界 flush 逐字同序,只是提前发生)。
|
|
255
261
|
yield* text.drainLive();
|
|
256
262
|
yield* text.takeThinking();
|
|
257
|
-
|
|
258
|
-
//
|
|
263
|
+
// 🔴 件 A(#323 症状①,2026-08-25):文本段走**专用入口** `takeAnswerSegmentOnIdle`
|
|
264
|
+
// (每段一刀 + 只在句末/段末提交),不是通用的 `takeAnswerSegment`。
|
|
265
|
+
// 病:慢模型每 1.5s 一卡顿 = 一刀,一轮回复被切成 N 条 assistant 消息(屏上 N 个 ⏺)。
|
|
266
|
+
// 适用域:本竞速的原意只是「大 Write 的静默窗别把已生成的开场白押着」,不是
|
|
267
|
+
// 「每停 1.5s 就切一段」。终局 flush(工具卡前 / turn 收口)那条路**零改动**。
|
|
268
|
+
yield* text.takeAnswerSegmentOnIdle();
|
|
269
|
+
// 缓冲(可能)已空 ⇒ 下一圈直接 plain-await;判据没过时段缓冲还在,下一拍再race。
|
|
259
270
|
}
|
|
260
271
|
}
|
|
261
272
|
if (step === undefined)
|
|
@@ -245,6 +245,26 @@ export function eventToSdkMessage(ev, ctx) {
|
|
|
245
245
|
// (core 不鉴别、不比对、不回读)—— 只渲染,绝不回喂模型/工具入参,更不许当鉴权判据。
|
|
246
246
|
const toolEndSettledBy = ev.settledBy;
|
|
247
247
|
const toolEndApprover = ev.approver;
|
|
248
|
+
// ── A-D2(0.42.0,cli wire 锚台账 cli-191 镜头 A):`resolution`(core ≥5.35 `AskDenyResolution`)
|
|
249
|
+
// —— 这次 deny **是哪一条拒绝臂**(`human_refused` / `window_expired` / `no_approver` /
|
|
250
|
+
// `blanket_allow_refused` / `approver_unavailable` / `task_aborted` / `presentation_failed` /
|
|
251
|
+
// `approver_error` / `approver_contract`)。与 {@link toolEndSettledBy} **合读而不互替**:
|
|
252
|
+
// settledBy 只说「这次等待是哪一**种**收场」,resolution 分类的是**拒绝臂本身**。
|
|
253
|
+
// 🔴 为什么这一位非补不可(消费面已经在等它):要判「审批窗口自己走完了」,单锚 settledBy 会把
|
|
254
|
+
// 「审批方违约」那一臂一起收进来(approver 回了契约外的东西,窗口并没有自然走完)。本臂此前
|
|
255
|
+
// 是闭形白名单,`resolution` 在包边界被整段剥掉 ⇒ 消费端**缺一臂**,只能退到锚 core 散文
|
|
256
|
+
// (文案一改就漂)。[1947] `toolCallId` / [2942] `governanceForced` / #263 两键 / #324 机读位
|
|
257
|
+
// 之后的**同款病形第五例**。
|
|
258
|
+
// 🔴 **给消费端的判据是正面匹配**(异源复审 [medium] 采纳):
|
|
259
|
+
// `settledBy === 'timeout' && resolution === 'window_expired'`;**绝不**写
|
|
260
|
+
// `resolution !== 'approver_contract'` —— 那个不等式对**缺席**(旧引擎)与**任何未来新码**
|
|
261
|
+
// 都为真,会把「不知道是怎么拒的」折成「窗口自然结束」。
|
|
262
|
+
// 🔴 **开集防御读**(与上面三键同姿势):类型面虽是九词闭集,词表属主仍是引擎 —— 窄读成枚举
|
|
263
|
+
// 会在加员当天把一个合法值判没。非空串即原样透传,绝不按识别表过滤、绝不 trim/截断/改写。
|
|
264
|
+
// 🔴 **缺席不带语义,不许反推**(core 头注逐字):每一次真正执行了的调用、每一次 policy/hook
|
|
265
|
+
// 直拒、以及 durable/decide 腿的结算(它们带自己的 settledBy/reason)都**不带**这一位。
|
|
266
|
+
// 缺席 ≠「不是拒绝」也 ≠「人拒的」⇒ 缺席就不铸键(绝不折成 null / 空串)。
|
|
267
|
+
const toolEndResolution = ev.resolution;
|
|
248
268
|
// ── #324 半场(0.40.0):连坐 abort 机读位 `_sema_collateral_abort`([4907] 裁定)──
|
|
249
269
|
// 出处**不是引擎**,是本包自己的 HITL 桥:`gateLedger.flushHeld` 对「出身 = collateral」的
|
|
250
270
|
// 扣留帧 stamp(见 `SEMA_COLLATERAL_ABORT_KEY` 头注)。桥在本臂**上游**(宿主先用
|
|
@@ -281,6 +301,10 @@ export function eventToSdkMessage(ev, ctx) {
|
|
|
281
301
|
...(typeof toolEndApprover === 'string' && toolEndApprover.length > 0
|
|
282
302
|
? { approver: toolEndApprover }
|
|
283
303
|
: {}),
|
|
304
|
+
// A-D2:拒绝臂分类(开集、缺席不铸、在场原样;判据本体是上方那段头注)。
|
|
305
|
+
...(typeof toolEndResolution === 'string' && toolEndResolution.length > 0
|
|
306
|
+
? { resolution: toolEndResolution }
|
|
307
|
+
: {}),
|
|
284
308
|
// #324:连坐 abort 机读位(严格 true 才上臂;见上方头注)。
|
|
285
309
|
...(collateralAbort === true ? { _sema_collateral_abort: true } : {}),
|
|
286
310
|
...(ev.eventId !== undefined ? { eventId: ev.eventId } : {}),
|