@sema-agent/client-core 0.64.2 → 0.65.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 +154 -0
- package/README.md +11 -3
- package/dist/adapt/arms.js +125 -7
- package/dist/adapt/ids.d.ts +22 -0
- package/dist/adapt/ids.js +29 -0
- package/dist/adapt/panelTasks.d.ts +24 -2
- package/dist/adapt/panelTasks.js +65 -15
- package/dist/adapt/textStream.js +6 -3
- package/dist/adapt.d.ts +1 -1
- package/dist/adapt.js +3 -0
- package/dist/adapter/downstream/eventToSdkMessage.d.ts +0 -10
- package/dist/adapter/downstream/eventToSdkMessage.js +207 -65
- package/dist/adapter/downstream/terminalToSdkResult.d.ts +4 -4
- package/dist/adapter/downstream/terminalToSdkResult.js +43 -12
- package/dist/adapter/downstream/turnUsageToModelUsage.d.ts +23 -2
- package/dist/adapter/downstream/turnUsageToModelUsage.js +7 -1
- package/dist/adapter/runStream.js +43 -2
- package/dist/adapter/types.d.ts +12 -0
- package/dist/autoModeUnavailable.d.ts +39 -83
- package/dist/autoModeUnavailable.js +58 -111
- package/dist/classifierStatus.d.ts +25 -71
- package/dist/classifierStatus.js +110 -105
- package/dist/decideReceipt.d.ts +117 -0
- package/dist/decideReceipt.js +142 -0
- package/dist/engineErrorCodes.d.ts +32 -0
- package/dist/engineErrorCodes.js +42 -0
- package/dist/fleet/fleetProjection.d.ts +24 -1
- package/dist/fleet/fleetProjection.js +26 -1
- package/dist/fleetAgentPanelProjection.js +6 -1
- package/dist/gateVocabulary.d.ts +9 -1
- package/dist/gateVocabulary.js +46 -4
- package/dist/hitl/askGateWire.js +22 -1
- package/dist/hitl/gateLedger.d.ts +24 -0
- package/dist/hitl/gateLedger.js +8 -0
- package/dist/hitl/hitlBridge.js +14 -2
- package/dist/hitl/parkResolver.d.ts +23 -2
- package/dist/hitl/parkResolver.js +34 -6
- package/dist/hitl/toolApprovalWire.d.ts +12 -1
- package/dist/hitl/toolApprovalWire.js +15 -6
- package/dist/index.d.ts +1 -0
- package/dist/index.js +16 -6
- package/dist/notifications.js +11 -2
- package/dist/runTerminal.d.ts +48 -0
- package/dist/runTerminal.js +59 -0
- package/dist/seam.d.ts +131 -1
- package/dist/seam.js +22 -0
- package/docs/INTEGRATION-CLIENTS.md +680 -63
- package/package.json +2 -2
package/CHANGELOG.md
CHANGED
|
@@ -49,6 +49,160 @@
|
|
|
49
49
|
> 挡住 ⇒ 本批把它机械化——④a0 对 `pending` 行**要求段头已是日期形**(`(未发布)` 直接红),阶段一
|
|
50
50
|
> commit 漏转在发布前就红,不再靠人记。
|
|
51
51
|
|
|
52
|
+
## 0.65.1(2026-09-11)
|
|
53
|
+
|
|
54
|
+
test [6961] 对 0.65.0 十件五路交叉验证全 PASS,对抗复审轨另抓两处带坐标的实现缺口(cli 亲核源码成立,两处都是 patch):
|
|
55
|
+
|
|
56
|
+
- **B-087 通知腿判重次序**(`adapt/panelTasks.ts` `settleFromNotification`):拆分时逐字保留了「先发 `panel_task{kind:'end'}`
|
|
57
|
+
再判重」的旧次序,判重只护着 if-started stop;B-074 让终态 tick 也能结 bg 行之后,`tick(completed) → task_notification`
|
|
58
|
+
这个顺序在面板上落**两条** end(反序只一条)。修=判重移到该腿唯一的 yield 之前,与 tick 腿 / 关卡腿同形;常驻账照清。
|
|
59
|
+
§30d G30-11 补注;门 `run-task-progress-terminal-projection` +B5a/B5b(两个到达顺序都恰一条)。
|
|
60
|
+
- **B-088 裸 `usageMissing` 帧静默**(`adapter/runStream.ts` turn_end 折叠点):`turn_usage` 发臂条件只认「数值
|
|
61
|
+
`outputTokens` ∨ 非空 `stopReason`」,判别位自己不算话 ⇒ core 真铸形(`run-harness-handlers.ts:491`,该轮零 usage
|
|
62
|
+
帧时无 `usage` 字段;core [6962] 证实)`{type:'turn_end', usageMissing:true}` 整条静默,G30-23 那一档在最诚实的帧上
|
|
63
|
+
反而拿不到 `_sema_usage_missing`。修=三者任一在场即发;三者皆缺席仍不发(F4 逐字不变)。§30e 情形表 +一行、
|
|
64
|
+
G30-24b;门 `run-assistant-arm-identity` +F5a–c。
|
|
65
|
+
- 消费方零改:新发的那条臂只带判别位,adapt `turnUsageArm` 以 `typeof outputTokens === 'number'` 开门 ⇒ no-op;
|
|
66
|
+
面板 end 少一条重复,端侧无需处置。
|
|
67
|
+
|
|
68
|
+
## 0.65.0(2026-09-11)
|
|
69
|
+
|
|
70
|
+
> 两半场同版:**① 投影臂族归层修**(B-071 / B-072 / B-073 / B-074 / L-215③)与
|
|
71
|
+
> **② 接入审计 B 战役的包侧归层四件**(B-070 / B-078 / L-215①② / B-080)。接入面详解见
|
|
72
|
+
> `docs/INTEGRATION-CLIENTS.md` §30(①–⑥ = §30a–§30g,⑦–⑩ = §30h–§30n)。
|
|
73
|
+
> 🔴 **本段有 BREAKING**(熔断族公面删除,见 B-080 那节)——其余全为 additive。
|
|
74
|
+
|
|
75
|
+
### 投影臂族归层修(B-071 / B-072 / B-073 / B-074 / L-215③)
|
|
76
|
+
|
|
77
|
+
本批五件共一条形:**上游早已把事实摆在 wire 上,而包边界这一格空转或把它折掉了**。四件是「键被剥/
|
|
78
|
+
被折/被铸成假值」,一件是「读器拿装配期事实回答了每次调用的问题」。**本半场**全部 additive、零破坏性
|
|
79
|
+
变更(BREAKING 只出在下半场的 B-080;详见 `docs/INTEGRATION-CLIENTS.md` §30a–§30g)。
|
|
80
|
+
|
|
81
|
+
- **B-072 压缩边界帧三件**(`adapter/downstream/eventToSdkMessage.ts`)
|
|
82
|
+
- `compact_metadata.trigger` 由 `manual ? manual : auto` 的**三词折叠**改为**开集透传**:core 的臂注
|
|
83
|
+
逐字说 wire 现在带的是钩子层看见的那个值(`the SAME value the hooks saw instead of flattening it
|
|
84
|
+
to "auto"`),包在下游又折一次正是上游刚停止做的事。`forced`(压缩不是可选的那两条腿)从此到得了
|
|
85
|
+
宿主;缺席 / 空串 / 非串仍回落 `auto`。**CC 消费者按 `!== 'manual'` 分支的行为逐字不变。**
|
|
86
|
+
- 新增超集键 `compact_metadata._sema_preserved_segment`(⇐ core `preserved_segment`,压缩分割线的
|
|
87
|
+
保留段锚)与 `_sema_clamped_ratio`(⇐ core `clampedRatio`,摘要输入被钳掉的比例,真 0 是披露事实)。
|
|
88
|
+
两者**缺席一律不铸**;`preserved_segment` 载体在而 id 读不出 ⇒ 整键不出现(半个锚比没有锚更坏)。
|
|
89
|
+
- `message_committed` 由 `not_in_slice` 改投**中性内部臂**,并新增**可选** chrome 臂
|
|
90
|
+
`message_committed`(`entryId` / `role` / `toolCallId?` / `eventId?`)。它是上一条那个锚的**唯一**
|
|
91
|
+
解析腿(core 亲口:消费方用本帧自建 `entryId → message` 映射)。仍**绝不铸 transcript 行**。
|
|
92
|
+
- **B-073 成本可分性两件**(`terminalToSdkResult.ts` / `turnUsageToModelUsage.ts`)
|
|
93
|
+
- `total_cost_usd` 由 `usd > 0` 改为照实报:**显式 0 = 声明免费**(core 逐字 `an explicit all-zero
|
|
94
|
+
Model.cost still reports 0`),缺席 / 非有限 = 没定价 ⇒ `null`。负数原样过境。
|
|
95
|
+
- `ModelUsage.costUSD` 保持 CC 形不破(必填 number),缺席由**同行**新增的超集判别位
|
|
96
|
+
`_sema_cost_absent: true` 说 ⇒ **两键合读**:`costUSD===0` + 位缺席 = 免费;+ 位 `true` = 没定价。
|
|
97
|
+
判别位**从不铸 `false`**。新导出类型 `SemaModelUsage`(= `ModelUsage` 的超集,旧消费者零改)。
|
|
98
|
+
- `usage.cacheCreationInputTokens` 不再硬编 `0`,读 `TaskStats.cacheWriteTokens`(合成 modelUsage 行
|
|
99
|
+
同形第二处一并扫);扁平 usage 四格由 `?? 0` 改**有限数守卫**(开集索引 + JSON wire 会让 `"12"` /
|
|
100
|
+
`NaN` / `Infinity` 原样落进型面写着 `number` 的槽)。
|
|
101
|
+
- **B-074 子代终态那一拍**(`adapter/downstream/eventToSdkMessage.ts` / `adapt/arms.ts` /
|
|
102
|
+
`adapt/panelTasks.ts`)
|
|
103
|
+
- 投影层补 `task_progress.status`(**开集**透传,缺席 / 空串 / 非串不铸)。
|
|
104
|
+
- adapt 层:终态 tick(`completed` / `failed`)**落行** —— 新增 `PanelTaskLedger.settleFromTerminalTick`
|
|
105
|
+
(按 taskId、走 `laneOf` 的车道证明、冻结 inline 孪生、清常驻账、`SubagentStop` 走 if-started 门、
|
|
106
|
+
与另两条 settle 腿共用 `endedPanelTasks` 判重)+ 新增只读口 `isLiveBound`。
|
|
107
|
+
🔴 **两条承重细节**(异源对抗复审逼出):① 这一拍**先把最终累计数字落到行上**再落终态
|
|
108
|
+
(早退会让行冻在倒数第二拍);② **只落「关卡腿不会来收」的行**(`!isLiveBound`)——
|
|
109
|
+
只有关卡那条腿带得动子代的**最终报告**,抢先落行会让它撞判重整条让位。
|
|
110
|
+
- ⚠️ **已知边界**(如实移交):代际 `seq` 不参与判重 ⇒ 同一 taskId 复活后第二代的终态落不出来。
|
|
111
|
+
不在本批修(三条 settle 腿里关卡腿根本没有 `seq`,半个代际模型会导致**重复**落终态);
|
|
112
|
+
今天的行为已由 `run-task-progress-terminal-projection-test.mjs` **B9** 段钉住。
|
|
113
|
+
- **三条不改判**:`running`、**缺席**(旧引擎不发这一格)、以及开集里的第四个词 —— 都走修前那条路。
|
|
114
|
+
- **L-215③ assistant 行身份键**(`adapter/types.ts` / `eventToSdkMessage.ts` / `adapter/runStream.ts`)
|
|
115
|
+
- `message.id` ⇐ wire `eventId`(**确定性**:live 与 durable replay 同值;缺席不铸,绝不现铸随机 id)。
|
|
116
|
+
- `message.model` ⇐ 新增的 `EmitContext.model`(宿主开流时钉;缺席不铸,**绝不猜**)。
|
|
117
|
+
- 🔴 `usage` / `stop_reason` **刻意不铸在 assistant 臂上**(帧序:内容臂先到、`turn_end` 后到,臂发出
|
|
118
|
+
时引擎还没报账 ⇒ 在那里铸只能是估算)。真值出口 = `turn_usage` 臂的两个超集键
|
|
119
|
+
`_sema_last_assistant_usage`(与 footer 折叠共用同一只 `turnEndUsage()` 产物)与 `_sema_stop_reason`
|
|
120
|
+
(`turn_end.stopReason` 原词开集透传);chrome 腿的对偶 = `last_turn_usage` 新增的
|
|
121
|
+
`stopReason` / `usageMissing` 两位。
|
|
122
|
+
- 🔴 **两条诚实缺席**(异源对抗复审逼出):`turn_end.usageMissing === true` ⇒ **不铸**镜像、改铸
|
|
123
|
+
`_sema_usage_missing`(全零的「不知道」绝不冒充一笔已知的零账);`stopReason` **不再被 usage 的
|
|
124
|
+
存在性吞掉** —— 发臂条件由「有 `outputTokens`」放宽到「有话可说」。**有意的行为变更**:此前发
|
|
125
|
+
0 条的那一格(`{usageMissing:true, stopReason:…}`)现在发 1 条,且不带 `outputTokens` 键 ⇒ 以
|
|
126
|
+
`typeof === 'number'` 开门的既有消费者对它 no-op。
|
|
127
|
+
- 🔴 **身份两位穿过第二层投影**:`adapt` 的五个 assistant 构造点都是重建 `{role,content}` 的,现由
|
|
128
|
+
`adapt/ids.ts` 的 `messageIdentityOf` 单点接力(`model` 帧优先 ctx 兜底、`id` 只认帧)。
|
|
129
|
+
两处**刻意例外**:tool_use 卡臂与终帧补差腿只接 `model`(那两条的 `id` 是另一种身份)。
|
|
130
|
+
`AdapterContext` 同批新增 `model?: string` 座位(live 分段腿的兜底)。
|
|
131
|
+
- **B-071 分类器状态面拆词**(`classifierStatus.ts`)
|
|
132
|
+
- `CLASSIFIER_STATUS_STATES` 新增 `armed`(插在 `available` 之前);与本批 B-080 的熔断族退役合成后
|
|
133
|
+
是**三词** `armed` / `available` / `unavailable_this_round`(`breaker_open` 同批删)。
|
|
134
|
+
`autoMode.armed === true` 现在判 **`armed`** 而不是 `available` —— core 把「decider 为这条腿铸出来了」
|
|
135
|
+
(装配期)与「可用性是 per-CALL 事实」分得很清楚,读器此前拿前者答了后者。
|
|
136
|
+
- `available` 只从**本轮肯定事实**得出:一只 `origin === 'denial_limit_fallback'` 的 ask / 门记录
|
|
137
|
+
(core:`the classifier stands BEFORE this ask`)。别的 origin 一个都不算,理由逐条写在源码里。
|
|
138
|
+
- `classifierStatusDetail` 新增 `armed` **一句**(熔断那半句随 B-080 的退役一并不存在),与既有各句
|
|
139
|
+
**逐字互异**且不说「在跑」。
|
|
140
|
+
- 端影响:`/doctor` 那行的词由本包单铸 ⇒ **端零改即接通新词**;但若端自己 `switch` 过状态词,
|
|
141
|
+
需要加 `armed` 一臂(措辞面无此义务)。
|
|
142
|
+
|
|
143
|
+
### B-070 / L-200 `/decide` 的 200 是**投递受理**,不是「门已解决」
|
|
144
|
+
|
|
145
|
+
- 🔴 `hitlBridge.decideRaw` 的 decide 成功臂**不再清 `active`** —— sdk README §9.0.0 逐字:「workflow
|
|
146
|
+
车道的 200 只是**投递受理**……据 200 立刻把卡从 UI 抹掉,用户会看到一张『批过了却还在』的幽灵卡」。
|
|
147
|
+
判据锚换到流上的**下一条 running 臂**(`observe()` 那条腿修前就在,本批只是拿掉抢跑的那一行)。
|
|
148
|
+
- 🔴 **换挂**:`ParkResolution` 新增 `handoffTaskId`(判据 = 回执 `status:"resuming"` ∧ 非空 `taskId`,
|
|
149
|
+
且与当前 taskId 不同),`askGateWire` 据它换挂 `runs.events` 并**同时丢掉 `lastEventId`**
|
|
150
|
+
(durable seq 是 per-run 的);`GateLedger` 新增 `forgetSeq()`。
|
|
151
|
+
- **新模块 `src/decideReceipt.ts`**(additive 公面 7 件):`readDecideReceipt` 读 200 的四种形;
|
|
152
|
+
`executionOutcome` 走 `gateOutcome.ts` 的**同一只**读器,🔴 **缺席 = 未知,禁读成 allowed**;
|
|
153
|
+
`decideRefusalFromError` 给三条 workflow 车道拒绝码各一句人话 + 按出处取值的 `resendable`。
|
|
154
|
+
码常量(4 件)在 `engineErrorCodes.ts`。
|
|
155
|
+
|
|
156
|
+
### B-078 / L-208 `approval_request` / `approval_revoke` 两条 chrome 臂(归层修)
|
|
157
|
+
|
|
158
|
+
- 两臂不再 `dropped('unsupported_arm')`,改投同名 chrome 事件,**载荷 = 帧原样窄读**(信封纪律:
|
|
159
|
+
不窄化到 v1、不摘键);坏信封 ⇒ `dropped('malformed')` 而不是静默 `none`。
|
|
160
|
+
- `CHROME_ARM_TABLE` 新增两行 **`required: true`**,消费义务写在 `seam.ts` 的两只接口头注里。
|
|
161
|
+
- 🔴 **对宿主是新义务**:不接 = 流内审批卡整面哑掉(重连后 pending 卡恢复不了 / 撤销后卡不清)。
|
|
162
|
+
|
|
163
|
+
### L-215①② fleet `parked` 与「非成功终局」的单铸谓词
|
|
164
|
+
|
|
165
|
+
- `coerceWorkflowStatus` 认 `parked` ⇒ `awaiting approval`(与 `coerceTaskStatus` 同落点)。
|
|
166
|
+
- `runTerminal.ts` 新增 `TERMINAL_NOT_SUCCESS_STATUSES` / `isTerminalNotSuccess` /
|
|
167
|
+
`TERMINAL_STATUSES` / `isTerminalStatus`(additive 公面 4 件)。🔴 `blocked` 在表里(core [6908]:
|
|
168
|
+
agent 自报终态),`suspended`/`needs_review` **不在**(那是等人)。
|
|
169
|
+
- **同形存量族扫**:`notifications.ts` 两处、`fleetAgentPanelProjection.ts` 一处、`adapt/arms.ts` 一处
|
|
170
|
+
的内联三词判据一次改齐。
|
|
171
|
+
|
|
172
|
+
### B-080 / L-210 闭集镜像过期族
|
|
173
|
+
|
|
174
|
+
- **devDep `@sema-agent/core` `~7.10.0` → `~7.12.0`**(镜像门参照物随引擎)。
|
|
175
|
+
- 🔴 **BREAKING(公面删两件)**:`AUTO_MODE_BREAKER_CAUSES` / `classifierBreakerOf` 随 core 7.12.0 的
|
|
176
|
+
Removed(BREAKING) 整只退役(clean-cut,不留别名);同批删 **型** `ClassifierBreakerView`、
|
|
177
|
+
`ClassifierStatusView.breaker` 一位、`WiringManifestAutoMode.breaker` 一位,
|
|
178
|
+
`CLASSIFIER_STATUS_STATES` 删 `breaker_open` 一态,`AUTO_MODE_UNAVAILABLE_CAUSES` 收成 `['error','timeout']`。
|
|
179
|
+
⚠️ 读过它们的宿主编译红 —— 删读点即可(状态面自然回落;与本批 ⑥ B-071 新增的 `armed` 合成后是**三态**
|
|
180
|
+
`armed` / `available` / `unavailable_this_round`)。
|
|
181
|
+
- **AskOrigin 三句订正**(行为面,措辞):`unresolvable`(祖先标记 + 必达 park)/
|
|
182
|
+
`shell_gate_tighten`(这一档以上,不是每一条)/ `rule_store_unavailable`(中性定义句 +
|
|
183
|
+
`askOriginDetail(origin, message?)` 的 additive 第二参让位给引擎的 `message`)。
|
|
184
|
+
|
|
185
|
+
### 常驻门
|
|
186
|
+
|
|
187
|
+
🆕 = **新增八道**(四道来自 ①–⑥,四道来自 ⑦–⑩);其余行是同批**改口**的既有门。
|
|
188
|
+
|
|
189
|
+
| 门 | 守什么 |
|
|
190
|
+
|---|---|
|
|
191
|
+
| `scripts/run-compaction-boundary-projection-test.mjs` | 🆕 B-072 三键 + `message_committed` 两面(中性臂 / chrome 臂);`clampedRatio` 的 sdk 座位带**退出条件**(补上当天红逼复核 raw 读) |
|
|
192
|
+
| `scripts/run-cost-absence-projection-test.mjs` | 🆕 B-073:`total_cost_usd` 显式 0 ≠ 缺席、`costUSD` + `_sema_cost_absent` 两键合读、`cacheCreationInputTokens` 读真值、扁平四格有限数守卫 |
|
|
193
|
+
| `scripts/run-task-progress-terminal-projection-test.mjs` | 🆕 B-074:`status` 开集透传 + 终态落行 + 三条不改判 + 幂等 + workflow lane 门在先 |
|
|
194
|
+
| `scripts/run-assistant-arm-identity-test.mjs` | 🆕 L-215③:`id`/`model` 两键与它们的缺席语义、`usage`/`stop_reason` **不在臂上**的反向钉、`turn_usage` 两超集键 |
|
|
195
|
+
| `scripts/run-classifier-status-test.mjs` | 🆕 **G 段**(B-071):三词表、`armed` ≠ `available`、`denial_limit_fallback` 是唯一正向证据(其余十一词逐词负控)、优先序、措辞三句互异;`per-CALL` 那句的 core 7.12.0 **逐字锚** —— B-080 把 devDep 抬到 `~7.12.0`,C1 车那条「领先登记」按它自己写的退出条件当场销账换成正向断言。🔁 同批:熔断族两侧退役反钉(A 段) |
|
|
196
|
+
| `scripts/run-integration-doc-freshness-test.mjs` | §3c 臂表(`message_committed` 离表)、`CLASSIFIER_STATUS_STATES` 成员换代(表长仍 3)、`AUTO_MODE_UNAVAILABLE_CAUSES` 表长入档、本节新增坐标 |
|
|
197
|
+
| `scripts/run-decide-receipt-test.mjs` | 🆕 B-070:`/decide` 200 = 投递受理的**源码反钉**(成功臂零 `this.active = null`、`observe()` 清除腿还在)、四种 200 形、`handoffTaskId` 两件合取、`executionOutcome` 缺席 ⇒ undefined、三码三句 + 按出处取 `resendable`、表外 `decide.*` 码判 `null` |
|
|
198
|
+
| `scripts/run-approval-frame-chrome-arms-test.mjs` | 🆕 B-078:两条 chrome 臂的**信封原样**过境(不窄化到 v1、未知 schemaVersion 照过)、坏信封 ⇒ `dropped('malformed')` 非静默、`CHROME_ARM_TABLE` 两行 `required:true` 的义务文本、**刻意无子流断闸** |
|
|
199
|
+
| `scripts/run-terminal-status-vocabulary-test.mjs` | 🆕 L-215①②:`isTerminalNotSuccess` / `isTerminalStatus` 单铸谓词(`blocked` 在表、`suspended`/`needs_review` 不在)、`parked ⇒ awaiting approval` 端到端、**同形存量族扫**反钉(`src/` 零内联三词判词) |
|
|
200
|
+
| `scripts/run-retired-vocabulary-census-test.mjs` | 🆕 B-080②:退役词汇普查 —— 每条登记项「下游代码位置零命中 + 公面零导出」+ 🔴「上游存活断言」两件;今天登记五条(熔断族四符号 + `breaker_open` 词);反向钉:退役**说明**必须留在注释里 |
|
|
201
|
+
|
|
202
|
+
另有四道既有门同批改口:`run-auto-mode-unavailable-test.mjs`(熔断族两侧退役反钉 + 成因表两词)/
|
|
203
|
+
`run-gate-vocabulary-test.mjs`(含 D2 uplift 锚的归属裁定:候 sdk 镜像)/ `run-client-core-pure-test.mjs` /
|
|
204
|
+
`run-hitl-gate-honesty-test.mjs`(`armed ⇒ available` 反向控随 B-071 订正)。
|
|
205
|
+
|
|
52
206
|
## 0.64.2(2026-09-10)
|
|
53
207
|
|
|
54
208
|
> server 7.69.0 提货批的包侧六件:**四件 additive**(零删除;只在上游真发那一位在场时多铸一个键)
|
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.65.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
|
|
@@ -237,7 +237,7 @@ public-surface guard checks that last one).
|
|
|
237
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
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 |
|
|
239
239
|
| `scripts/run-terminal-cause-projection-test.mjs` | The `7.64.0` wire reshape, projected. A run's ending stopped being eight parallel flat keys and became **one tagged cause** (`completed | failed | blocked | paused`), and a tool call's gate stopped being four orthogonal words and became **one record** (`disposition` / `settlement?` / `origin?`). Both are read in exactly one place in this package, and this guard pins them at **two levels**, because the dangerous seam is "the reader was updated, the consumer was not": each terminal arm is checked on the reader *and* on the `subtype` / `is_error` / `errors[]` the projector actually emits. Two properties carry most of the weight. First, a terminal word this reader does not know is **never** laundered into an empty success — it lands on an `unknown` arm carrying the word verbatim, while a payload with no terminal word at all (the mock lane) keeps the success arm exactly as before, which is the one and only case the reader answers `null`. Second, the three window words (`approval_window_expired`, `denial_limit_window_expired`, `park_sla_expired`) must each be told apart by a different predicate: the previous generation collapsed all three onto one `timeout`, and re-merging them would throw away the discrimination this reshape just restored. Two byte generations are read by one reader, keyed on the discriminator upstream nailed (`"terminal" in result`): the current cause form, and the **flat** form that a current engine still emits on two lanes — replayed persisted bytes, which the service passes through verbatim rather than back-filling, and the service's own rejection envelope. A cause-form payload that also carries stale flat keys must ignore them entirely: keeping one compatibility read is what gives a single fact two sources. The same file also pins the MCP delivery verdict and HTTP status riding the wiring manifest, the four-state write-protection reading (where three of the four states mean *cannot tell*, and none of them may be printed as "there is no table"), and the park-reopen fetch identity: that predicate is asserted through the **real entry point**, since the defect being fixed was precisely a call site wired to a different predicate than the one that routed the row there |
|
|
240
|
-
| `scripts/run-auto-mode-unavailable-test.mjs` | The fact behind "you are being asked because the auto-mode classifier could not run", and the one place its sentence is minted.
|
|
240
|
+
| `scripts/run-auto-mode-unavailable-test.mjs` | The fact behind "you are being asked because the auto-mode classifier could not run", and the one place its sentence is minted. The cause table is a **copy**, reconciled word for word in both directions against the installed engine's own bytes — it narrowed upstream, and the guard follows rather than keeping the old shape: a table checked against something nobody ships any more is the oldest way for a guard to be green and wrong. The retirement is held from both sides — the removed table must really be gone upstream, and the removed reader and word must really be gone here — while the word that left keeps arriving cleanly from an older engine, because the reader takes the cause as an **open set**: the vocabulary belongs upstream, so a copied list here would discard a legal value the day one is added, and the value discarded is precisely "this outage is a NEW kind". The reader's one exclusion is the word the engine says it never stamps here — the classifier did run and did answer, just outside its contract, so reading it as a failure would invent an event the engine denies. That exclusion used to be derived from a second table which no longer exists; the reason for it never lived in that table, so it is now stated where it actually comes from, pinned as a **named** set (a magic literal scattered through the reader reds) and cross-checked against the engine's own verdict declaration and against the reader having exactly one such comparison. One reader serves both the live ask and its durable parked twin, since the two carry the same key path and a second copy is how two ledgers drift apart. Absence is pinned as absence — most asks never consulted a classifier at all — and the sentences are checked mutually distinct, prototype-safe, and walked end to end: an unknown word reaches the sentence a person reads (the fallback that names it verbatim) and the status reading (unavailable for this round, never a fallback to "available"), with counter-controls proving neither assertion is vacuous |
|
|
241
241
|
| `scripts/run-engine-notice-catalog-test.mjs` | The engine-notice catalog and its audience table. Whether a notice deserves a person's attention is not decided by whether this end happens to have a phrasing for it — that drifts with each client's build order — but by whether the engine minted the code into its own written catalog; the audience row answers the separate question of *who* the fact is for, since an operations fact pushed at an end user is noise and a user-facing fact buried in an operator log is something withheld from the person who could act on it. Both tables are reconciled against the installed engine's own artefacts in both directions and pinned in lockstep with each other, unknown codes fall back to the conservative operator side, and catalog membership is tested on the raw value so a code carrying control characters cannot impersonate a registered one after sanitizing. The reader for a dropped MCP injection keys on its own code alone and treats a missing session, server or reason as absence rather than throwing at a read site. A reverse pin enforces the upstream's single-mint contract: the engine composes those sentences from the host's facts, so a copy of them appearing in this package's source or build is a second source that would drift, and fails |
|
|
242
242
|
| `scripts/run-tool-roster-projection-test.mjs` | The leg's tool roster — what the engine says it actually mounted and what face each tool wears — replacing three word lists that were only ever an estimate taken from one traffic capture against one pinned engine. The reader copies the engine's own all-or-nothing discipline: a roster whose row cannot be read, or whose declared count disagrees with the rows, is dropped whole rather than handed over short, because a consumer reading a short roster concludes the missing tools are not mounted — the upstream says in as many words that this is worse than sending nothing. A malformed *face* on a row (path target, render hints) drops only that face, since a face is not an identity. Shims are built strictly from roster rows and never guessed from a tool's name, and an axis that cannot be read stays absent rather than defaulting to `false` or `never`, which would render "unknown" as "safe". For run-time changes the guard pins the one hard rule in the contract: a digest that does not match is **not** a rejection — the carried roster is the new state regardless and only the summary becomes unusable, because refusing the swap would leave the consumer holding a stale roster forever |
|
|
243
243
|
| `scripts/run-permission-rule-issue-codes-test.mjs` | The rule-lint refusal codes an engine reports when it will not compile a permission rule. The SDK publishes neither a schema nor a type for them, so the package mints the table from the engine's own bytes and the guard pays the cost of that copy instead of leaving it to somebody remembering: it parses the codes the engine actually mints and reconciles them against the table in both directions, so a code added upstream (the user would see a bare code) and a code only the package believes in (a branch that can never fire) both fail. It also reconciles the table plus a small retired ledger against the engine's declared union, which is deliberately not the same set — one member was renamed and its old name is still declared — so reviving a code the engine will never mint again is impossible and a future stale member shows up immediately. Sentences are pinned one per code, mutually distinct, and split by family: a rule that is wrong and a rule that is legal but unsupported on this lane are different next steps and may not share a sentence. The engine's own message rides along as prose — sanitized and capped after escaping, never matched on |
|
|
@@ -288,7 +288,15 @@ public-surface guard checks that last one).
|
|
|
288
288
|
| `scripts/run-resume-refusal-copy-test.mjs` | The **words** a client says when a resume is refused, minted once here instead of three times. The facts behind them already lived in this package; the sentences did not, so each client wrote its own — and those sentences answer a safety question (was my decision consumed, can this token still be redeemed), which is exactly the kind of answer that must not vary by client. Two closed sets meet here and the guard pins their relationship in both directions, because it is a premise rather than a coincidence: one set answers *can waiting help* (the codes the server mints a wait on), the other answers *what should a person be told*, they **intersect in exactly one code**, and each keeps a member the other must not have — a placement mismatch is never waitable no matter what arrives on the response, since its remedy is a changed argument rather than elapsed time, and a full governance window needs no prose because "you can wait" is the whole message. The overlapping code delegates its wait and its disposition to the existing reading rather than judging again: nine shapes of input drive both entry points and the two readings must agree byte for byte, the absent case included, because two judges always diverge somewhere. The wait is narrowed to the domain the server mints it in, which is **stricter than the shell's own copy was** — a zero now reads as no window rather than as "retry now", and the wake-up it would retry is an at-most-once action with real side effects. The third sentence is chosen by the disposition, never by the engine's prose: rewriting the message to either upstream branch's exact wording, with the window untouched, must leave all three sentences unchanged, while adding a window must change the third one and only the third one |
|
|
289
289
|
| `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 |
|
|
290
290
|
| `scripts/run-model-capability-probe-test.mjs` | Whether a model on the OpenAI-completions lane **thinks**, and whether that thinking can be **turned off** — a question nobody can answer by looking at a model name, and one whose wrong answer costs every later call. The probe is judgement only: the network half arrives as an injected port, so the package mints no URL, reads no credential and never calls `fetch` — pinned by a source-level assertion, because a package that reaches the network once has changed what every host must trust it with. The seven dialect words are a **copy**, reconciled element-wise against the installed engine’s own bytes in both directions, since the words belong upstream and a private table drifts the day a dialect is added; the settings package deliberately declines to restate them, so the table cannot be imported from there and this guard is what stands in for the import. The **order** the dialects are tried in is a public promise rather than an implementation detail — each extra attempt is real money and real latency against someone’s gateway — so the guard pins the exact call sequence a stub records, and reversing it reds on the wasted round trip; the template-parameter spelling leads because an observed gateway keeps thinking, and answers with an empty body, when handed the top-level switch instead. That observation is also why an empty answer is **not** accepted as *thinking is off*: a knob that deletes the reply is not a knob that disabled reasoning, and accepting it would write a spelling into the catalogue that the gateway does not honour. Two dialect words whose request bytes are identical to another’s do not each burn an attempt. The two verdicts that look alike are held apart from both directions: *tried everything, still thinking* requires at least one attempt to have **cleanly answered**, and when every attempt was refused the verdict is *could not tell* instead — and on the unanswerable path the result carries **no** thinking flag at all rather than a fabricated `false`, while the pure write-back returns the very same entry object untouched. A verdict that reasoning cannot be disabled **removes** a previously declared spelling rather than leaving it, since a refuted spelling keeps the engine sending bytes the gateway ignores while the catalogue still renders it as already off. Evidence is lengths, finish positions and status codes only — a planted secret in both the answer and the reasoning channel must appear nowhere in the result, so the record can go into a log or a ticket whole One cross-package premise is checked by really running the other package’s parser rather than quoting its documentation: everything this probe writes eventually passes through the settings schema on its way into a catalogue, and that field is declared parse-transparent precisely so the vocabulary can live on the consuming side. If it ever narrows, the spelling is stripped **silently** — indistinguishable from the probe never having run — so the guard feeds the probe’s real output through the real parser, checks the compat object comes back key for key, and checks a dialect word this client has never heard of survives too. Two shapes that must be rejected really are rejected, since otherwise the survival checks would hold on a parser that accepts anything, and a bare entry is asserted valid first, because the first run of this section reddened on a space in a fixture’s name — a fixture that cannot pass would disguise the real alarm as already having fired |
|
|
291
|
-
| `scripts/run-
|
|
291
|
+
| `scripts/run-decide-receipt-test.mjs` | What a decision verb actually **answered** — and, more importantly, what it did not. A success response on the newest lane is only an acknowledgement that the decision was accepted for delivery: the approval is still pending, and a client that clears the card on it shows either a ghost card that was already approved or a card that vanished while the decision was lost. So the package deliberately has **no** "was it resolved" predicate — nothing in that body can answer it — only the opposite one, whose `false` is likewise not evidence of resolution; resolution is only ever the next running arm on the stream. The guard pins that inversion in the product source too: the success path must no longer clear the latched gate, while the stream-observing path that really clears it must still be there. The body has four shapes with **no** key common to all of them, so every position is read as honestly absent, and the handoff handle — which run to watch from here on — requires **two** facts together, since either one alone would either point the stream at the run it already had or mint an empty handle. The record of what finally happened to an already-decided action is read through the **same** reader as every other gate record rather than a second copy, and its absence means **unknown**, never *it was allowed* — the two can even contradict each other, so the card says nothing at all when it is missing. The three refusals on that lane each get one distinct sentence and a disposition taken from **why** each was refused rather than from severity: one cannot be helped by re-sending at all, one waits on the host, one just drops an option — and none of them carries a countdown, because the server never mints a wait for them. Recognition is a **closed set**: an unrecognised code on the same prefix returns nothing rather than a guess, since that prefix also houses a safety signal whose whole rule is never to retry automatically, and the recovery handle is read as absent when unreadable rather than substituted from a different identifier that no longer appears on that lane |
|
|
292
|
+
| `scripts/run-approval-frame-chrome-arms-test.mjs` | The two in-stream approval frames finally reaching every host through the shared pipeline instead of one shell's private branch — the shape of a layering defect: hosts that only consume the package could not rebuild their pending cards after a reconnect, and did not clear a card the engine had withdrawn. The payload is deliberately carried as the **envelope** the upstream types declare rather than the first-version card: the stream parser applies no predicate, so narrowing here would let a legitimately newer frame pass as the older shape and invite consumers to read keys a newer card never promised. The guard therefore pins that every open key survives untouched, that an unknown version still passes through, and that narrowing is left to the host's own predicates — with the fallback being a generic card and a person, **never** an automatic denial. A frame whose version cannot be read at all is reported as malformed rather than dropped in silence, because both frames carry user-visible decisions and state changes. Both arms are registered as **required** host duties, and their duty text names the load-bearing rules a host would otherwise have to rediscover: which predicate to narrow with, that the reconnect preamble — not a replayed historical frame — is the authority on which cards exist, and that a withdrawal frame can be lost entirely. Unlike the sibling arms, these carry **no** sub-stream cutoff: an approval raised under a delegated call still has to reach a person, and filtering it by ownership is the host's job, not a reason to discard it. Finally the upstream bytes that justify the envelope discipline are checked to still be there, since the whole design rests on them |
|
|
293
|
+
| `scripts/run-terminal-status-vocabulary-test.mjs` | One place that decides whether a run has **ended** and whether it ended badly — written because that judgement had already been hand-copied three times, so the day the engine added a word for *the agent itself reported it cannot continue*, every copy missed it and a panel settled a self-reported failure as a success. The distinction the table exists for is pinned from both sides: that word belongs in it, while the two words meaning *waiting for a person to decide* deliberately do **not** — reading those as endings would bury a run that is actively waiting on the reader. A word this client does not know answers *no*, and the guard states plainly that *no* is not evidence of success: proving success means reading the positive side, so negating this predicate is the very mistake that caused two earlier incidents. The fleet lane gets the same treatment from the other direction: a workflow parked on a durable approval used to fall through to *running*, leaving the person with no hint that a card was waiting, and it now lands on the same rendered word the task lane already used — same fact, same word, checked end to end on a real row. Why the word was added directly rather than carried as a private superset key is checked mechanically against the upstream declaration being open, so the day it closes this reds and the decision gets revisited. The residue sweep is the point: the source tree must contain **no** further inlined copy of the judgement, each of the three former sites is checked to really read the single predicate, and the one reviewed exemption carries its reason **and** a liveness assertion, so an exemption whose justification expires cannot quietly keep standing |
|
|
294
|
+
| `scripts/run-retired-vocabulary-census-test.mjs` | Whether a retirement really happened. When upstream removes a family, a downstream package can cut it out or keep a courteous alias — and the alias is the worse outcome: three clients keep writing branches for something nobody emits, and a status line advertises a state it can never reach. Choosing the clean cut only means something if a guard holds it, since a comment saying *retired* is not an exit code. Each registered entry is held two ways: the name must be gone from **code positions** in this package (comments stripped first, because the explanation is supposed to stay) and off the published surface, and — the half that keeps this from being self-congratulation — it must really be gone **upstream**, since that is the entire reason it was removed here; if it comes back, the disposition deserves reconsideration rather than silence. The scanner proves it can speak by finding a symbol that is genuinely present before any absence is believed, and distinguishes a mention inside a comment from one in a string literal, which is exactly the form being cleared. A closing check runs the other way: the retirement **story** must remain in the comments, including a promise this package made earlier and has now had to withdraw — deleting the history alongside the code is a bad way to satisfy *zero hits*, and leaves the next reader with code that has no reason |
|
|
295
|
+
| `scripts/run-classifier-status-test.mjs` | What state the auto-mode classifier is in **on this session** — the question a doctor line, a model settings page and a permission card’s status row all ask, and a different question from the one the approval card asks (*why am I being asked right now*), so the sentences are pinned mutually distinct from that face’s as well as from each other. The session-level half of this reading — a breaker record the engine used to keep — was **retired upstream**, and the guard now holds that retirement from **both** sides: the engine's own declarations must really no longer carry it (a fact coming back would mean the removal here was the wrong disposition, and that deserves a conversation rather than silence), and this package must carry no alias, no state word and no leftover narrowing for it — a reading kept alive for something nobody emits any more is a promise the interface cannot keep, and it left the doctor line advertising a state it can never reach. What remains is ordered by the quantity that actually decides whether the classifier is running: the fact from **this round** first, then whether this leg is armed — a decider is minted per run, so a later leg can be armed again. Not armed, and a section that never arrived, both answer **undefined** rather than *available*; that arming question has its own field and answering it twice grows a second ledger. Arming and availability are also **two words, not one**: the engine says a decider was minted *for this leg*, which is an assembly-time fact, while whether that decider answers any given round is a **per-call** one — so an armed leg reads `armed` and only a positive per-call fact (an ask whose origin is the classifier's own denial-bound fallback, which by construction stands *after* the classifier ran) reads `available`. Every other ask origin is refused as evidence and for a stated reason rather than out of caution: several are ones the classifier is structurally forbidden to answer, and for the rest a surviving ask is precisely the case where it did **not** resolve one — so reading availability off them would be a guess. The projection is a **whitelist**, so an older engine still sending the retired member loses it at the boundary while the two live facts beside it ride through untouched. Rendering never throws and never impersonates: a state word this client does not know — including the retired one, which a restored view can still carry — reaches an honest fallback that names it verbatim, carries no invented explanation of a mechanism that no longer exists, and is proven distinct from all three real sentences; prototype keys reach that same fallback rather than a function body, checked against a real out-of-table word so the comparison cannot hold vacuously |
|
|
296
|
+
| `scripts/run-compaction-boundary-projection-test.mjs` | The compaction divider and the one frame that makes its anchor resolvable. The trigger word is passed through as an **open set** instead of being folded to two: the engine deliberately stopped flattening its third value (a compaction that was not optional — a prompt-too-long recovery or trim pressure) and carries what the hook layer saw, so folding it again at the package boundary re-introduces exactly what upstream had just removed, while a consumer branching on *is it manual* keeps its behaviour byte for byte. Only an unreadable word (absent, empty, non-string) falls back — that is *could not read it*, not *read it and did not recognise it*. Two superset keys ride the metadata and neither fabricates: the preserved-segment anchor is minted only when its id really reads out, because half an anchor sends the host looking up an empty string in its map, and the clamp ratio is a **disclosure** whose real zero is a fact rather than an absence. The clamp ratio also carries a registered exit condition — the service really sends it while the SDK arm has no seat for it yet, so the read is defensive and this guard reds the day that seat appears, forcing a re-check instead of leaving a cast to rot. The committed-message frame moves out of *deliberately not projected*: that classification was true about transcript rows and false about **positioning**, since the engine states that consumers build their own id-to-message map from this frame to place the divider — projecting the anchor without it hands the host something it cannot resolve. It becomes a neutral internal arm and an optional chrome ledger event, never a transcript row (the frame carries no body, so minting one would put words in the engine's mouth), with both required ids narrowed and a malformed frame recorded rather than half-minted |
|
|
297
|
+
| `scripts/run-cost-absence-projection-test.mjs` | Telling **declared free** apart from **never priced**, in both directions, because the package was getting each one wrong in the opposite way. The engine separates them on the wire — an absent cost means some spend had no price table, an explicit zero means the model declared itself free — and the result projector used to require a *positive* number, so a genuinely free run could not say so; while the per-model mirror folded absence to zero, so an unpriced run told a billing consumer it cost nothing. The total is now reported as the engine stated it, with absence and non-finite values alone reading as unknown, and a negative passed through rather than corrected, since a refund is a legal figure and the package is not a second accountant. The per-model figure keeps the CC shape intact — that field is a required number and *unknown* is simply not expressible in it — so the value stays zero and a **companion superset bit** carries the distinction, which means the two are read together and a reader that only ever looked at the number is unchanged; the bit is minted only in the absent case and never as `false`, since a key present with a false value reads as a third state. The same mint point serves both the wire's per-model split and the synthesised current-model row, so neither can drift. Alongside it the cache-write figure stops being a hardcoded zero and reads the field the wire has always carried, in both the flat usage and the synthesised row, and all four flat token slots move from a null-coalesce to a finite-number guard — the stats object has an open index signature and the wire is JSON, so a string or an infinity would otherwise land in a slot the types promise is a number, compiling green and surfacing only when something sums it |
|
|
298
|
+
| `scripts/run-task-progress-terminal-projection-test.mjs` | The one tick that says a delegated child **finished**. The engine fires exactly one final beat carrying a terminal face, and says in the same breath why it exists — so a consumer sees the row finish instead of watching it vanish after the last running beat — but the package's projection whitelist had no seat for that field and its adapter still carried the older premise in a comment, so the terminal beat arrived byte-identical to another running one: the panel row stayed up waiting for a defensive sweep (which only ever settles rows bound to a card still open this turn) or for a separate notification frame. The status now rides through as an **open set** with the vocabulary left upstream, while the question *which words are terminal* is answered by a closed pair on the adapter side — an unrecognised new word takes the running path, because guessing it terminal ends a row that is still working whereas one extra running beat merely renders late. A terminal beat settles the row directly under the lane proof its binding gives it (not the main lane a notification would use, and not by card id, since the engine is naming a child rather than closing a card), freezes the inline group-row twin in the same beat so a later sweep cannot reset the real tool count, clears the session-resident ledger, and fires the stop hook only for a child whose start really fired. It does not mark the row live or emit a second progress beat, and it shares the settled-row ledger with the other two settle legs so a replay or a double-delivery cannot produce a second end. Three things are pinned **unchanged**: a running beat, an absent status (older engines never send the field, and reading absence as terminal would make every child row disappear on its first beat), and the workflow lane gate, which still runs before any of this |
|
|
299
|
+
| `scripts/run-assistant-arm-identity-test.mjs` | The identity keys on an assistant row, and an explicit account of the two that are **deliberately not** there. What the renderer received was a bare role-and-content object, so a dozen consumer sites downstream were each estimating what the message envelope should have told them. The id is taken from the engine's own event id rather than minted locally, because it has to be **the same value** on the live leg and on a durable replay — a freshly minted one would make a replayed message look new to a host's dedup and to rewind — and when the wire carries none the key is simply absent rather than filled with a random stand-in wearing an identity it does not have; it is also kept distinct from the envelope's own local render key, which is a different identity. The model name comes from what the host pinned when it opened the stream (the request was the host's to build) and is never guessed, since a wrong model name is worse than none once a billing or capability face looks it up. Usage and stop reason are **not** minted on this arm, and the reason is frame order rather than effort: content arms arrive before the turn's closing frame, so at the moment the arm is emitted the engine has not yet said what the round cost — anything put there would be an estimate, which is the very thing this work exists to remove — and synthesising a follow-up assistant update when the real figure lands is also refused, because that shape does not exist upstream and would place a message in the transcript the engine never sent. Their real values leave through the turn's own neutral arm as two superset keys, the usage one reusing the **same single mint point** the footer rollup already folds so the two faces cannot diverge, and the stop reason passed through verbatim as an open set — the machine signal for *was this turn cut short*, previously blind on both the stream and the trace. The existing behaviours beside them are pinned too: no arm at all when usage is wholly absent, and the sub-flow cut-out that keeps a child's turn from driving the leader's face |
|
|
292
300
|
|
|
293
301
|
Each suite carries a floor that only moves up — a refactor that stops executing a group of
|
|
294
302
|
assertions is a failure, not a quieter pass. Guards anchor on the **installed artefact's content**
|
package/dist/adapt/arms.js
CHANGED
|
@@ -7,7 +7,9 @@ import { recordEngineToolLabel } from '../engineToolLabelStore.js';
|
|
|
7
7
|
import { registerSubagentAlias, registerSubagentContentAlias } from '../subagentContentStore.js';
|
|
8
8
|
import { readAsyncLaunchedAgentReceipt } from '../toolResult.js';
|
|
9
9
|
import { isWorkflowAgentTaskId, recordWorkflowAgentTaskId } from '../workflow.js';
|
|
10
|
-
|
|
10
|
+
// L-215+(0.65.0;core [6908]):终局状态词的单铸谓词(三处同形一次改齐)。
|
|
11
|
+
import { isTerminalStatus } from '../runTerminal.js';
|
|
12
|
+
import { chrome, MAIN, transcript, messageIdentityOf } from './ids.js';
|
|
11
13
|
import { CANCEL_MESSAGE, decisionOf, flattenWireOutput, REJECT_MESSAGE, sanitizeToolUseBlock, shortTaskLabel, TASK_TOOL_NAMES, WORKFLOW_TOOL_NAMES, } from './wireShapes.js';
|
|
12
14
|
// ══ M2/M1 —— assistant(tool_use 半场 + durable 文本/思考半场)══════════════════════════════════
|
|
13
15
|
const assistantArm = function* (m, { ctx, idOf, text, cards, inst }) {
|
|
@@ -60,11 +62,21 @@ const assistantArm = function* (m, { ctx, idOf, text, cards, inst }) {
|
|
|
60
62
|
// 0.62.0:卡之前的散文已经落账,卡**之后**的散文属于下一条 assistant 消息 —— 划界。
|
|
61
63
|
// (终帧 `result` 在引擎侧只取**最后一条** assistant 消息的文本,补差判据的两边必须是同一种东西。)
|
|
62
64
|
text.beginAssistantMessage();
|
|
65
|
+
// L-215③:身份位取一次(下面只用 `model` —— `id` 是 per-RESPONSE 分组键,见块内注)。
|
|
66
|
+
const cardIdentity = messageIdentityOf(m, ctx);
|
|
63
67
|
yield transcript({
|
|
64
68
|
type: 'assistant',
|
|
65
69
|
// S1:per-RESPONSE 铸 id —— CC 的 grouped-agent 渲染按 `${message.id}:${toolName}`
|
|
66
70
|
// 分组;id-less 帧会把整个 session 的 Agent 卡塌成一张。tool_use 帧才盖章。
|
|
67
|
-
|
|
71
|
+
// 🔴 L-215③:这条臂**只接 `model`,不接上游那个 `id`** —— 两个 `id` 是**两种身份**
|
|
72
|
+
// (上游铸的是 per-EVENT 引擎身份,这里要的是 per-RESPONSE 分组键);换掉会把一张
|
|
73
|
+
// Agent 卡拆成 N 张。`model` 没有这层冲突,照接(不接就在重建时原地丢掉)。
|
|
74
|
+
message: {
|
|
75
|
+
id: cards.mintResponseId(m),
|
|
76
|
+
role: 'assistant',
|
|
77
|
+
content: renderBlocks,
|
|
78
|
+
...(cardIdentity.model !== undefined ? { model: cardIdentity.model } : {}),
|
|
79
|
+
},
|
|
68
80
|
uuid: idOf(m),
|
|
69
81
|
session_id: ctx.sessionId,
|
|
70
82
|
parent_tool_use_id: null,
|
|
@@ -134,7 +146,8 @@ const assistantArm = function* (m, { ctx, idOf, text, cards, inst }) {
|
|
|
134
146
|
yield* text.takeThinking();
|
|
135
147
|
yield transcript({
|
|
136
148
|
type: 'assistant',
|
|
137
|
-
|
|
149
|
+
// L-215③ 族扫:durable 内容腿也是**重建**点,身份位在这里接力(见 ids.ts messageIdentityOf)。
|
|
150
|
+
message: { role: 'assistant', content: [{ type: 'text', text: textBlock.text }], ...messageIdentityOf(m, ctx) },
|
|
138
151
|
uuid: idOf(m),
|
|
139
152
|
session_id: ctx.sessionId,
|
|
140
153
|
parent_tool_use_id: null,
|
|
@@ -146,7 +159,8 @@ const assistantArm = function* (m, { ctx, idOf, text, cards, inst }) {
|
|
|
146
159
|
if (thinkingBlock && typeof thinkingBlock.thinking === 'string') {
|
|
147
160
|
yield transcript({
|
|
148
161
|
type: 'assistant',
|
|
149
|
-
|
|
162
|
+
// L-215③ 族扫:同上(durable 思考块)。
|
|
163
|
+
message: { role: 'assistant', content: [thinkingBlock], ...messageIdentityOf(m, ctx) },
|
|
150
164
|
uuid: idOf(m, 'durable-thinking'),
|
|
151
165
|
session_id: ctx.sessionId,
|
|
152
166
|
parent_tool_use_id: null,
|
|
@@ -262,6 +276,35 @@ const humanInputArm = function* (m) {
|
|
|
262
276
|
...(str(m.eventId) !== undefined ? { eventId: str(m.eventId) } : {}),
|
|
263
277
|
});
|
|
264
278
|
};
|
|
279
|
+
/**
|
|
280
|
+
* B-072 ④(0.65.0)—— `message_committed` **定位账本帧**。
|
|
281
|
+
*
|
|
282
|
+
* 姿势逐条抄同族的 `human_input`(引擎侧发生了一件事、壳记一笔的非持久帧,归 chrome;**不走
|
|
283
|
+
* attachment / 不铸 transcript**:本帧不带正文,铸一行就是替引擎编一句它没说过的话)。
|
|
284
|
+
* 它与 `human_input` 的唯一差别是**用途**:那一条是审计,这一条是**定位** —— 端拿它自建
|
|
285
|
+
* `entryId → 已渲消息` 的映射表,压缩分割线(`compact_metadata._sema_preserved_segment`)
|
|
286
|
+
* 就是靠这张表落到「保留尾段的第一条消息之前」。
|
|
287
|
+
*
|
|
288
|
+
* 子流帧(带 parentToolCallId)同族让位:不上 leader 面(子代的条目属子代那条流)。
|
|
289
|
+
* 两个必填位的**形**已由投影层判过(读不出即 malformed 留痕);这里是第二道,读不出就不发。
|
|
290
|
+
*/
|
|
291
|
+
const messageCommittedArm = function* (m) {
|
|
292
|
+
if (typeof m.parentToolCallId === 'string')
|
|
293
|
+
return;
|
|
294
|
+
const entryId = typeof m.entryId === 'string' && m.entryId.length > 0 ? m.entryId : undefined;
|
|
295
|
+
const role = typeof m.role === 'string' && m.role.length > 0 ? m.role : undefined;
|
|
296
|
+
if (entryId === undefined || role === undefined)
|
|
297
|
+
return;
|
|
298
|
+
yield chrome({
|
|
299
|
+
kind: 'message_committed',
|
|
300
|
+
laneProof: MAIN,
|
|
301
|
+
entryId,
|
|
302
|
+
role,
|
|
303
|
+
...(typeof m.toolCallId === 'string' && m.toolCallId.length > 0 ? { toolCallId: m.toolCallId } : {}),
|
|
304
|
+
// 宿主幂等落表的键(同 human_input):durable 续读会重放同一条账本帧。
|
|
305
|
+
...(typeof m.eventId === 'string' && m.eventId.length > 0 ? { eventId: m.eventId } : {}),
|
|
306
|
+
});
|
|
307
|
+
};
|
|
265
308
|
/**
|
|
266
309
|
* #310 / #318 件① —— `engine_notice` 引擎结构化通告(server ≥7.36,契约 = ASSISTANT-WIRE-CONTRACT 附录 D)。
|
|
267
310
|
*
|
|
@@ -338,6 +381,31 @@ const wiringManifestArm = function* (m) {
|
|
|
338
381
|
...(typeof m.eventId === 'string' && m.eventId.length > 0 ? { eventId: m.eventId } : {}),
|
|
339
382
|
});
|
|
340
383
|
};
|
|
384
|
+
/**
|
|
385
|
+
* `approval_request` / `approval_revoke` 内部臂 → 同名 chrome 臂(B-078 / L-208,0.65.0;design/172)。
|
|
386
|
+
*
|
|
387
|
+
* 契约本体在 `seam.ts` 的 {@link import('../seam.js').ApprovalRequestChromeEvent} /
|
|
388
|
+
* {@link import('../seam.js').ApprovalRevokeChromeEvent} 头注(那是宿主要读的那一份)。
|
|
389
|
+
* 本臂只做两件:
|
|
390
|
+
* 🔴 **一个字节都不加工**:`frame` 原样转发(信封纪律 —— 窄化到 v1 是宿主拿 sdk 谓词做的动作,
|
|
391
|
+
* 在这里替它窄化会让一条 `schemaVersion: 2` 的合法帧冒充 v1;见投影层头注)。
|
|
392
|
+
* 🔴 **第二道信封判**:投影层已经判过 `schemaVersion` 是有限数,这里再确认一次 —— 防的是
|
|
393
|
+
* **非投影口**喂进来的帧(宿主自建管线 / 重放存量转录),不是重复判据(与 `wiringManifestArm`
|
|
394
|
+
* 的第二道成形判同因)。
|
|
395
|
+
* 🔴 **刻意没有子流断闸**(与 `engine_notice` / `text_segment_end` 那三条相反,这是有理由的差别):
|
|
396
|
+
* 那几条断闸挡的是「把子代腿的东西渲到 leader 面上」;而审批卡**必须**上到人面前——一张属于
|
|
397
|
+
* 子代调用的卡也要人来点,断掉它等于把那条 run 挂死在没人看得见的地方。归属过滤由宿主按
|
|
398
|
+
* 帧上的 `taskId` 做(撤卡臂的纪律①),那是**过滤**不是**丢弃**。
|
|
399
|
+
*/
|
|
400
|
+
const approvalFrameArm = (kind) => function* (m) {
|
|
401
|
+
const schemaVersion = m.schemaVersion;
|
|
402
|
+
if (typeof schemaVersion !== 'number' || !Number.isFinite(schemaVersion))
|
|
403
|
+
return;
|
|
404
|
+
const frame = m.frame;
|
|
405
|
+
if (typeof frame !== 'object' || frame === null)
|
|
406
|
+
return;
|
|
407
|
+
yield chrome({ kind, laneProof: MAIN, frame, schemaVersion });
|
|
408
|
+
};
|
|
341
409
|
/**
|
|
342
410
|
* `text_end` 内部臂 → chrome `text_segment_end`(#323 / core #447)。
|
|
343
411
|
*
|
|
@@ -386,7 +454,9 @@ const promptSuggestionsArm = function* (m) {
|
|
|
386
454
|
// ══ M2 —— tool_end_result(关卡 + B/D/E 层卡本体 + 两个侧产物)══════════════════════════════════
|
|
387
455
|
const toolEndResultArm = function* (m, { ctx, idOf, cards, panel }) {
|
|
388
456
|
// B4 起本臂做三件:① P0-2 label 补位(只有 tool_end 带 label 的场景;台账自身幂等不覆盖)
|
|
389
|
-
// ② MF-10 **关卡 settle**(
|
|
457
|
+
// ② MF-10 **关卡 settle**(父卡的 tool_end 是完成信号;⚠️ 0.65.0/B-074 订正:「子代从不发
|
|
458
|
+
// 终态 tick」这句旧前提已被 core 7.x 推翻 —— 终态 tick 现在会发,由 taskProgressArm 的
|
|
459
|
+
// `TERMINAL_TICK_STATUSES` 分支落行,两条腿共用 `endedPanelTasks` 判重;卡的 output
|
|
390
460
|
// 文本 = 子代最终报告,串到行 end 事件上)③ 卡台账出栈 + response-id 复位(S1)。
|
|
391
461
|
// 🆕 B5:④ **B/D/E 层** —— 铸 `tool_result` 卡本体 + 三个侧产物
|
|
392
462
|
// (Remember→memory_saved / task 族 structured 台账 / #117a+#158 bg Bash+Monitor 桥)。
|
|
@@ -466,6 +536,16 @@ const toolEndResultArm = function* (m, { ctx, idOf, cards, panel }) {
|
|
|
466
536
|
});
|
|
467
537
|
}
|
|
468
538
|
};
|
|
539
|
+
/**
|
|
540
|
+
* B-074(0.65.0)—— core 明写的**终态那一拍**的两个词。
|
|
541
|
+
*
|
|
542
|
+
* 🔴 **闭二词,而 `status` 本身按开集读** —— 这两件事不矛盾,分工是:投影层原样透传任何词
|
|
543
|
+
* (词表属主在 core),本表只回答「哪些词**是**终态」。一个包不认识的新词(比如上游哪天加
|
|
544
|
+
* `cancelled`)走 **running 那条路**,而不是被猜成终态:猜错的代价是把一条还在跑的行提前
|
|
545
|
+
* 结掉,而「多渲一拍 running」只是慢一点。core 的臂注也只承诺这两个词
|
|
546
|
+
* (`timeout/blocked fold to failed`,即上游自己已经折过了)。
|
|
547
|
+
*/
|
|
548
|
+
const TERMINAL_TICK_STATUSES = new Set(['completed', 'failed']);
|
|
469
549
|
// ══ M3 —— task_progress(引擎侧子代的累计 usage tick)══════════════════════════════════════════
|
|
470
550
|
const taskProgressArm = function* (m, { ctx, cards, panel, inst }) {
|
|
471
551
|
// ── B4 A 层最后一臂(MF-10)—— 引擎侧子代的累计 usage tick。
|
|
@@ -599,6 +679,28 @@ const taskProgressArm = function* (m, { ctx, cards, panel, inst }) {
|
|
|
599
679
|
totalTokens: typeof usage.totalTokens === 'number' ? usage.totalTokens : 0,
|
|
600
680
|
},
|
|
601
681
|
});
|
|
682
|
+
// ── B-074(0.65.0)终态那一拍 ────────────────────────────────────────────────────────────
|
|
683
|
+
// 修前本臂的头注写着「**子代从不发终态 tick**,父卡的 tool_end 就是完成信号」—— 那句话在
|
|
684
|
+
// core 7.x 上已经是假的(`task_progress.status` 的臂注逐字:`ONE final tick fires at the
|
|
685
|
+
// sub-run's settle with the terminal face`,理由也写死了:`so a uuid-lane consumer sees the
|
|
686
|
+
// row finish instead of a bare removal after the last running beat`)。
|
|
687
|
+
//
|
|
688
|
+
// 🔴 **为什么落在臂尾、而不是一进来就早退**(异源对抗复审实抓的两条真回归,别按「先判终态再
|
|
689
|
+
// 干活」重排):终态那一拍带的是子代的**最终**累计数字。早退会让
|
|
690
|
+
// ① 面板行与 inline 群组行**冻在倒数第二拍**(实测 3 次工具 /150 tokens 被冻成 2 次 /100);
|
|
691
|
+
// ② 后到的关卡 settle 撞 `endedPanelTasks` 判重而整条让位 ⇒ 子代的**最终报告**(委派卡的
|
|
692
|
+
// output,只有关卡那条腿带得动)从此到不了面板行。
|
|
693
|
+
// 所以这一拍**照常走完上面的 tick 半场**(数字先落地),只在最后多一件事:落终态。
|
|
694
|
+
//
|
|
695
|
+
// 🔴 **只落「关卡腿不会来收」的那些行**(`!panel.isLiveBound`):绑在本 turn 还开着的卡上的行
|
|
696
|
+
// 由 `panel.settle(cardId, isError, report)` 收 —— 那条腿带着最终报告,在这里抢先落行就是
|
|
697
|
+
// 把报告挤掉(上面 ②)。未绑 / inert 绑定(跨 turn bg 子代)= session 常驻行,关卡腿不碰它,
|
|
698
|
+
// 它今天只认通知帧 —— 那正是 core 说的 uuid lane,也正是本件要救的那一条。
|
|
699
|
+
// 🔴 **开集第四词不当终态**:词表属主在 core(`TERMINAL_TICK_STATUSES` 头注)。
|
|
700
|
+
if (typeof m.status === 'string' && TERMINAL_TICK_STATUSES.has(m.status) && !panel.isLiveBound(taskId)) {
|
|
701
|
+
ctx.probe?.('tick', JSON.stringify({ t: ctx.now(), taskId, lane: 'foreground', settle: m.status }));
|
|
702
|
+
yield* panel.settleFromTerminalTick(taskId, m.status === 'failed');
|
|
703
|
+
}
|
|
602
704
|
};
|
|
603
705
|
// ══ M1 / M6a —— stream_event / turn_usage ══════════════════════════════════════════════════════
|
|
604
706
|
const streamEventArm = function* (m, { text }) {
|
|
@@ -655,10 +757,19 @@ const resultArm = function* (m, { ctx, idOf, text, cards, panel, flags }) {
|
|
|
655
757
|
// 🔴 比较对象 = 引擎「最后一条 assistant 消息」在屏上那段(0.63.2):卡后零散文时回退到卡前那条
|
|
656
758
|
// (provider 一条消息 = [text, tool_use];空串是任何终帧的前缀 ⇒ 拿空串比会把整段终帧再渲一遍)。
|
|
657
759
|
const committed = text.lastCommittedAnswerText;
|
|
760
|
+
// L-215③:同上,身份位取一次(终帧补差腿**只接 `model`**,理由见块内注)。
|
|
761
|
+
const terminalIdentity = messageIdentityOf(m, ctx);
|
|
658
762
|
const emitTerminal = function* (body, tag) {
|
|
659
763
|
yield transcript({
|
|
660
764
|
type: 'assistant',
|
|
661
|
-
|
|
765
|
+
// L-215③ 族扫:终帧补差腿。🔴 这条消息的**正文是终帧的 `result`**,不是某条内容帧 ——
|
|
766
|
+
// 所以只接 `model`(那是整条流的事实),**不接 `id`**:把终帧的 eventId 当成一条
|
|
767
|
+
// assistant 消息的身份,会让宿主把「补差出来的那一段」与终帧本身当成同一条东西。
|
|
768
|
+
message: {
|
|
769
|
+
role: 'assistant',
|
|
770
|
+
content: [{ type: 'text', text: body }],
|
|
771
|
+
...(terminalIdentity.model !== undefined ? { model: terminalIdentity.model } : {}),
|
|
772
|
+
},
|
|
662
773
|
uuid: idOf(m, tag),
|
|
663
774
|
session_id: ctx.sessionId,
|
|
664
775
|
parent_tool_use_id: null,
|
|
@@ -718,7 +829,9 @@ const taskNotificationArm = function* (m, { ctx, idOf, panel, inst }) {
|
|
|
718
829
|
return; // 空/缺 task_id:RB-75 同族,丢弃
|
|
719
830
|
const taskId = fields.taskId;
|
|
720
831
|
const rawStatus = fields.status;
|
|
721
|
-
|
|
832
|
+
// L-215② 族扫(0.65.0):终局判据走单铸谓词 —— 修前手抄三词,core [6908] 的 `blocked`
|
|
833
|
+
// 漏在外面 ⇒ 一条自报走不下去的后台行**永远不 settle**(面板上挂着一条不会再动的「在跑」)。
|
|
834
|
+
const terminal = isTerminalStatus(rawStatus);
|
|
722
835
|
// #117a settle 半场 —— **故意在渲染去重之前**:settle 幂等,重放帧仍须 settle 一条
|
|
723
836
|
// 首投之后才注册的行。#47 矩阵 #7:只有 bash/monitor 行(或老引擎无 task_type)能被 settle
|
|
724
837
|
// (#158:monitor 行注册进面板后,它的终局通知也得能关行;事件帧 status="event" 非终态,
|
|
@@ -933,9 +1046,14 @@ export const ARMS = new Map([
|
|
|
933
1046
|
['steering_injected', steeringInjectedArm],
|
|
934
1047
|
['workspace_changed', workspaceChangedArm],
|
|
935
1048
|
['human_input', humanInputArm],
|
|
1049
|
+
['message_committed', messageCommittedArm],
|
|
936
1050
|
['engine_notice', engineNoticeArm],
|
|
937
1051
|
['text_end', textSegmentEndArm],
|
|
938
1052
|
['wiring_manifest', wiringManifestArm],
|
|
1053
|
+
// B-078 / L-208(0.65.0):design/172 流内审批两帧 —— 修前它们在投影层就 `dropped`,
|
|
1054
|
+
// 壳自己另接一份 ⇒ desktop/web 拿不到卡集与撤卡(归层违例)。
|
|
1055
|
+
['approval_request', approvalFrameArm('approval_request')],
|
|
1056
|
+
['approval_revoke', approvalFrameArm('approval_revoke')],
|
|
939
1057
|
['prompt_suggestions', promptSuggestionsArm],
|
|
940
1058
|
['retry_status', retryStatusArm],
|
|
941
1059
|
]);
|
package/dist/adapt/ids.d.ts
CHANGED
|
@@ -5,6 +5,28 @@ export type Frame = Record<string, unknown>;
|
|
|
5
5
|
export declare const MAIN: LaneProof;
|
|
6
6
|
/** 帧 → 稳定转录 id(`suffix` = 同一帧派生多条消息时的去撞位)。 */
|
|
7
7
|
export type IdOf = (frame: Frame, suffix?: string) => string;
|
|
8
|
+
/**
|
|
9
|
+
* L-215③(0.65.0)—— 一条 assistant 转录消息的**身份键**(`message.id` / `message.model`)。
|
|
10
|
+
*
|
|
11
|
+
* 🔴 **本层不是第二个铸点,是接力点**:上游投影器(`adapter/downstream/eventToSdkMessage.ts` 的
|
|
12
|
+
* `assistantArm`)已经把这两位铸在 durable 内容帧的 `message` 上;而本层的三个构造点
|
|
13
|
+
* (tool_use 卡臂 / 散文段 / 思考块)都是**重建** `{role, content}` 的 —— 重建就把上游铸好的
|
|
14
|
+
* 两位原地丢掉了(异源对抗复审实抓:「新增 assistant id/model 到达不了最终转录」)。本函数把
|
|
15
|
+
* 它们接过来,规矩只有两条:
|
|
16
|
+
* · **帧优先、ctx 兜底**(`model`):durable 内容帧上有,live 分段腿的 `stream_event` 锚上没有
|
|
17
|
+
* —— 那时回落到宿主开流时钉的 {@link AdapterContext.model};
|
|
18
|
+
* · **`id` 只认帧**:它是引擎铸的**事件身份**,ctx 上没有、也不该有一个「整条流通用的消息 id」。
|
|
19
|
+
* 🔴 **两位都缺席即键不铸**(不是 `undefined` 在场)。
|
|
20
|
+
* ⚠️ tool_use 卡臂**不**用这里的 `id`:那条臂刻意铸 per-RESPONSE id(CC 的 grouped-agent 渲染按
|
|
21
|
+
* `${message.id}:${toolName}` 分组,换成 per-event id 会把一张 Agent 卡拆成 N 张)——
|
|
22
|
+
* 两个 `id` 是**两种身份**,那条臂只取 `model`。
|
|
23
|
+
*/
|
|
24
|
+
export declare function messageIdentityOf(frame: Frame, ctx: {
|
|
25
|
+
model?: string;
|
|
26
|
+
}): {
|
|
27
|
+
id?: string;
|
|
28
|
+
model?: string;
|
|
29
|
+
};
|
|
8
30
|
export declare const chrome: (event: ChromeEvent) => AdapterOutput;
|
|
9
31
|
/**
|
|
10
32
|
* 🔴 入参从裸 `unknown` 收窄成 `Frame`(拆分同批的真收窄,不是搬运):调用点全是「按 CC 词汇
|
package/dist/adapt/ids.js
CHANGED
|
@@ -1,6 +1,35 @@
|
|
|
1
1
|
import { deriveTranscriptId } from '../seam.js';
|
|
2
2
|
/** 主车道证明——本批实现的臂全在 leader lane(子代/workflow lane 的 tick 面留 #52b)。 */
|
|
3
3
|
export const MAIN = { lane: 'main' };
|
|
4
|
+
/**
|
|
5
|
+
* L-215③(0.65.0)—— 一条 assistant 转录消息的**身份键**(`message.id` / `message.model`)。
|
|
6
|
+
*
|
|
7
|
+
* 🔴 **本层不是第二个铸点,是接力点**:上游投影器(`adapter/downstream/eventToSdkMessage.ts` 的
|
|
8
|
+
* `assistantArm`)已经把这两位铸在 durable 内容帧的 `message` 上;而本层的三个构造点
|
|
9
|
+
* (tool_use 卡臂 / 散文段 / 思考块)都是**重建** `{role, content}` 的 —— 重建就把上游铸好的
|
|
10
|
+
* 两位原地丢掉了(异源对抗复审实抓:「新增 assistant id/model 到达不了最终转录」)。本函数把
|
|
11
|
+
* 它们接过来,规矩只有两条:
|
|
12
|
+
* · **帧优先、ctx 兜底**(`model`):durable 内容帧上有,live 分段腿的 `stream_event` 锚上没有
|
|
13
|
+
* —— 那时回落到宿主开流时钉的 {@link AdapterContext.model};
|
|
14
|
+
* · **`id` 只认帧**:它是引擎铸的**事件身份**,ctx 上没有、也不该有一个「整条流通用的消息 id」。
|
|
15
|
+
* 🔴 **两位都缺席即键不铸**(不是 `undefined` 在场)。
|
|
16
|
+
* ⚠️ tool_use 卡臂**不**用这里的 `id`:那条臂刻意铸 per-RESPONSE id(CC 的 grouped-agent 渲染按
|
|
17
|
+
* `${message.id}:${toolName}` 分组,换成 per-event id 会把一张 Agent 卡拆成 N 张)——
|
|
18
|
+
* 两个 `id` 是**两种身份**,那条臂只取 `model`。
|
|
19
|
+
*/
|
|
20
|
+
export function messageIdentityOf(frame, ctx) {
|
|
21
|
+
const msg = frame.message;
|
|
22
|
+
const inner = typeof msg === 'object' && msg !== null && !Array.isArray(msg)
|
|
23
|
+
? msg
|
|
24
|
+
: undefined;
|
|
25
|
+
const id = typeof inner?.id === 'string' && inner.id.length > 0 ? inner.id : undefined;
|
|
26
|
+
const frameModel = typeof inner?.model === 'string' && inner.model.length > 0 ? inner.model : undefined;
|
|
27
|
+
const model = frameModel ?? (typeof ctx.model === 'string' && ctx.model.length > 0 ? ctx.model : undefined);
|
|
28
|
+
return {
|
|
29
|
+
...(id !== undefined ? { id } : {}),
|
|
30
|
+
...(model !== undefined ? { model } : {}),
|
|
31
|
+
};
|
|
32
|
+
}
|
|
4
33
|
export const chrome = (event) => ({ plane: 'chrome', event });
|
|
5
34
|
/**
|
|
6
35
|
* 🔴 入参从裸 `unknown` 收窄成 `Frame`(拆分同批的真收窄,不是搬运):调用点全是「按 CC 词汇
|