@sema-agent/client-core 0.55.0 → 0.56.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 +66 -0
- package/README.md +2 -1
- package/dist/adapt/arms.js +33 -0
- package/dist/adapter/downstream/eventToSdkMessage.d.ts +54 -0
- package/dist/adapter/downstream/eventToSdkMessage.js +110 -35
- package/dist/seam.d.ts +31 -1
- package/dist/seam.js +6 -0
- package/docs/INTEGRATION-CLIENTS.md +76 -1
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -49,6 +49,72 @@
|
|
|
49
49
|
> 挡住 ⇒ 本批把它机械化——④a0 对 `pending` 行**要求段头已是日期形**(`(未发布)` 直接红),阶段一
|
|
50
50
|
> commit 漏转在发布前就红,不再靠人记。
|
|
51
51
|
|
|
52
|
+
## 0.56.0(2026-09-05)
|
|
53
|
+
|
|
54
|
+
> 发包批阶段一(2026-09-05,主会话):`package.json` 0.55.0→0.56.0、README Version 行、本段头转日期形、`FROZEN` pending 行同批;内容由 1.0.100 提货车 B 在隔离树交付,主会话收货补包侧门与 eventId 修钉(见「门与收货修钉」)。
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
### 新增 —— `wiring_manifest` 帧的两段用户面事实(L-70 / L-108②③;core #524 + core 147③,server ≥7.58.0)
|
|
58
|
+
|
|
59
|
+
`wiring_manifest` 此前与两条 HITL 帧并列走 `nothing('not_in_slice')`。那句话对整份 manifest 的
|
|
60
|
+
**其余每一段**今天仍然成立 —— 本批只把两段**终端用户面**的事实取出来:
|
|
61
|
+
|
|
62
|
+
- `modelGate?: { class, removed[], restore }` —— 「本 run 被模型门卸掉了哪些工具、怎么恢复」。
|
|
63
|
+
**仅真有门卸时在场**(core 只在卸的时候铸段),缺席 = 没卸。
|
|
64
|
+
- `autoMode?: { armed, reason }` —— 「auto 到底武没武装、为什么」。effective 腿恒在,缺席 =
|
|
65
|
+
老 mint / 外部 derive **没报**。
|
|
66
|
+
|
|
67
|
+
新公面:
|
|
68
|
+
|
|
69
|
+
- `wiringManifestSupersetBody(ev)`(纯函数)+ `WiringManifestModelGate` / `WiringManifestAutoMode`
|
|
70
|
+
两个类型;
|
|
71
|
+
- 内部臂 `wiring_manifest`(`INTERNAL_SDK_ARM_TYPES` 加员,载两个 `_sema_` 超集键);
|
|
72
|
+
- chrome 臂 `wiring_manifest`(`WiringManifestChromeEvent`,`CHROME_ARM_TABLE` 加员,`required:false`)。
|
|
73
|
+
|
|
74
|
+
### 门与收货修钉(主会话收货,2026-09-05)
|
|
75
|
+
|
|
76
|
+
- 新门 `scripts/run-wiring-manifest-projection-test.mjs`(36 判据;gates-manifest / README Guards 同批登记):
|
|
77
|
+
施工车只在壳侧写了门,包侧两发变异(`removed: []` 当真读数 / 子流断闸删除)在当时的 `npm test` 下
|
|
78
|
+
**全绿逃逸** —— 包是三端公共抽象,判据必须在包里。两发变异对新门当场红,复原绿。
|
|
79
|
+
- 修钉:`wiring_manifest` 投影臂原来**没带 `eventId`**,适配层 `wiringManifestArm` 那句透传永远是空
|
|
80
|
+
(新门 A1c 红先抓出)。现与别的臂同律:wire 带就上行、缺席键不在场。
|
|
81
|
+
|
|
82
|
+
### 🔴 三条读法(写在臂注里,是消费端的硬约束)
|
|
83
|
+
|
|
84
|
+
1. **帧是 FLAT 的**:读 `ev.modelGate` / `ev.autoMode`,**不是** `ev.manifest.*`。6.7.0 契约声明过的
|
|
85
|
+
那层 `manifest:{...}` 嵌套 wire 上从未有过(SDK `events.d.ts` 该臂头注 2026-08-07 已按真 capture 更正);
|
|
86
|
+
server 把 core `TaskEvent` 的 `ev.manifest` 过白名单 builder 后 **spread 进帧**,三条腿
|
|
87
|
+
(live SSE / durable 账本行 / resume 重放)同源。
|
|
88
|
+
2. **缺席不可反推**:两段缺席都**不许**渲成肯定句(「没有工具被卸掉」/「auto 未武装」)。
|
|
89
|
+
两段都不成形时本臂**根本不铸**(不产空臂)—— 所以宿主见到本臂就至少有一段是真读数。
|
|
90
|
+
3. **`autoMode.reason` 六词逐字呈现,绝不映射** `/v1/capabilities.permissionModeAuto.reason` 的六词:
|
|
91
|
+
两套词表**同名不同义**(`settings_denied` 在 capabilities 那边折 `no_intent` 不折 `denied`),
|
|
92
|
+
归一会造出一个假读数。`modelGate.restore` 同理原样呈现 —— 宿主自己拼一句恢复办法,
|
|
93
|
+
等于替引擎编了一条它没说过的出口。
|
|
94
|
+
|
|
95
|
+
### 形校验(两段各自独立,一段坏不牵连另一段)
|
|
96
|
+
|
|
97
|
+
- `modelGate`:`class` / `restore` 非空串 **且** `removed` 是全串数组 **且** 过滤空串后**非空**,
|
|
98
|
+
三件缺一整段不收(不折 `''`、不折空数组、不编默认恢复办法)。空 `removed` 与「没卸」在屏面上
|
|
99
|
+
不可区分,而 core 的契约是「有门卸才铸段」⇒ 它是坏值不是真读数。
|
|
100
|
+
- `autoMode`:`armed` 是 boolean **且** `reason` 非空串 **且** 满足 server 侧同一条互证式
|
|
101
|
+
`armed === (reason === 'armed')`。照抄这条不是「重复上游」——**非投影口**(宿主自建管线 /
|
|
102
|
+
重放存量转录)喂进来的帧不过 server,一个 `{armed:true, reason:'settings_denied'}` 会让消费端
|
|
103
|
+
同时读到「武装了」和「被拒了」。
|
|
104
|
+
- 子流断闸(带 `parentToolCallId` 的自述不上 leader 面)按「**键在不在**」判、不按「是不是串」判 ——
|
|
105
|
+
与 `text_segment_end` 同因:本臂决定的是「这份自述算谁的」,一个坏值会把子代的门卸清单
|
|
106
|
+
擦成 leader 的,用户会以为**自己这一 turn**少了工具。
|
|
107
|
+
|
|
108
|
+
### 已知局限(本版新增)
|
|
109
|
+
|
|
110
|
+
- **超集键名是临时形**:`modelGate` / `autoMode` 今天**不在**已发布 SDK(8.1.0)的 `AgentEvent`
|
|
111
|
+
wiring_manifest 臂里(全树零命中,亲验)⇒ 本包用 `_sema_` 前缀而不自铸 SDK 同名形。SDK 声明该臂
|
|
112
|
+
之后改名是 **BREAKING**,另立一批。投影层那行 `as unknown as Record<string, unknown>` 就是
|
|
113
|
+
「上游类型还没到货」的可见落点,删掉它编译器会立刻指出所有该跟着改的地方。
|
|
114
|
+
- **整份 manifest 仍不投影**:leg / ask / question / interaction / elicit / parkLane / session /
|
|
115
|
+
fleet / permissionRules / governance / configFingerprint 十一段一件没接。想要它们的宿主今天
|
|
116
|
+
只能走 operator 面的 `GET /v1/diagnostics/wiring`(且那面 403 门另有前提)。
|
|
117
|
+
|
|
52
118
|
## 0.55.0(2026-09-05)
|
|
53
119
|
|
|
54
120
|
### `waiting_first_token` —— 等第一个 token 是一种**状态**,不是一次错误(core 7.3.0 / #530;cli B-003 / L-108①)
|
package/README.md
CHANGED
|
@@ -35,7 +35,7 @@ Renamed from **`@sema-agent/wire-cc-adapter`** (0.1.x, deprecated — see *Migra
|
|
|
35
35
|
|
|
36
36
|
## Scope
|
|
37
37
|
|
|
38
|
-
**Version:** 0.
|
|
38
|
+
**Version:** 0.56.0
|
|
39
39
|
|
|
40
40
|
- **Today** — the adapter seam, the whole `adapt()` pipeline (all 14 A-layer arms plus the
|
|
41
41
|
B/D/E tool-card layers), the notification/caps/model families, the adapter kernel (stream driver
|
|
@@ -264,6 +264,7 @@ public-surface guard checks that last one).
|
|
|
264
264
|
| `scripts/run-additive-key-passthrough-test.mjs` | The one disease shape behind two legs: a **closed whitelist / flattening arm** dropping a fact that is already on the wire, while both sides of the seam look correct. (1) The `task_progress` projection carries a registered **key ledger** — a frame populated with every key the service really projects is pushed through the shipped `eventToSdkMessage`, and the set of wire keys that survive must equal the registered pass-through list **name for name in both directions**, so quietly forwarding one more key is as red as quietly dropping one. `model` (the child run's model id, minted by core as `prepared.model.id` and projected by the server since 7.52.1) is the key this batch adds, with the same conditional the server itself applies: a non-empty string or no key at all — an empty string is neither a model id nor "unknown". The ledger is also checked against the fenced list in `docs/INTEGRATION-CLIENTS.md` §3d, so a doc that still says seven keys while the code forwards eight is red rather than merely stale. (2) The decide-failure arms carry the server's S-02 `currentPending` pointer key from a 409 `approval_stale` refusal onto the outcome the host reads. The reader is structural rather than `instanceof`, because the client is host-injected and the class identity is not this package's to assume; a half triple never mints (half a pointer cannot relocate anything), an empty string is not presence, and `checkpointToken` never transits. Both the allow and the deny leg are driven end to end through the real durable approval path — as is the accept-session leg, where a refusal carrying the pointer key must now re-raise instead of silently re-sending the human's answer for the **old** card as a plain approve (one decide call, pointer preserved), while a legacy 400 still falls back exactly as before — and all three flattening points must call the one shared reader — the same-shape residue check that makes "fixed one arm and left the twin" red instead of invisible |
|
|
265
265
|
| `scripts/run-esc-halt-plan-test.mjs` | The Esc stop decision every client shares: fire the **turn-level** halt first, and escalate to a **run-level** cancel in exactly two cases — the engine itself answered with a 409 from the closed code set (it is saying "there is no in-flight turn here; use cancel for a run-level stop"), or that shot came back with no verdict at all *and* the shell can independently prove a permission card was on screen. Everything else does not escalate. The asymmetry is the whole point and every negative control guards the same direction — deciding *not* to escalate costs the user one more choice on a busy-session card (recoverable), deciding to escalate wrongly tears down a run that was alive and takes every in-flight tool with it (not). So: the closed code set is a **frozen** value, not a `ReadonlySet` — type-level immutability does not stop a consumer's `.add()`, and the guard proves it by really trying to mutate the exported value and then checking the verdict did not drift; the escalation gate is the **conjunction** of that closed set and the 409 status, since honouring the code alone lets a 500 that merely quotes it drive a destructive call; `interrupt.not_held` and `steering.not_running` are deliberately outside the set (the first means *this replica* has no live face — the run may be perfectly alive on another); an unreadable code falls to the no-escalation side; a `parked` flag never overrides a verdict the engine did give, and only strict `true` counts when it did not. The first shot is unconditional by construction — it does not consult `parked`, because the 409 it earns is exactly the verdict the gate wants — and the verdict itself is a closed machine-readable reason word, not display copy |
|
|
266
266
|
| `scripts/run-peer-frame-projection-test.mjs` | The three engine-injected lanes design/385 puts on the **one** `task_notification` carrier, which are not the same kind of thing at all: a delegated child's uplink (`agentMessage`), another session's message drained from this session's own box (`crossSessionMessage`), and a receipt about one of *this* session's own outbound messages (`crossSessionNotice`). The engine renders none of them inside a `<task-notification>` shell, so a client that projects them as the generic completion card shows "background task finished" while the model read a colleague's sentence — two faces describing different events. The discriminator is pinned to the **typed carrier being present**, never to the `summary` text: those carriers can only be minted by the engine's injection legs (the external `notify()` input is a strict subset of the payload and can wear none of them), while `summary` is filled by every notification there is — so anchoring on text would let any background task impersonate a colleague's message by writing `<agent-message from="…">` into its own summary, and a positive control asserts exactly that payload still projects as the generic card. Fail-closed has two tiers rather than one: a broken **required** field (empty `from`, a non-string `body`, a notice `kind` outside the closed set) returns absence so the caller falls back to the generic card — an honest downgrade where the user still sees the notification — while a broken **optional** field drops only itself, because losing an attribution note and losing a colleague's whole message are not the same magnitude. The provenance side record is **required and must agree on four points** (`kind` matches the lane; `from`/`taskId`/`seq` are present and equal the carrier/payload — each equality is anchored on a core mint site and pinned by the cli wire-anchor A-K24), so a carrier signed with a trusted name but a disagreeing provenance falls back to the generic card; peer bodies pass the same authority-envelope neutralization core applies (`<task-notification>` etc. are defused) so a colleague's text can never seed the resume dedup ledger. Lane precedence copies the engine renderer's own order, because the model already read the frame in that order and a client ordering of its own would put a card on screen that disagrees with the frame the model saw. Rendering and parsing of the transcript line live in the same module and are round-tripped in both directions, including a body carrying a forged closing tag (a parser fooled there hands half a message to the next row) and a quote inside the sender label (which must not forge a second attribute); the notice lane is deliberately kept **out** of the parser, since recognising it would mean anchoring the `[Cross-session …]` prefix and a user typing that same line would be rendered as engine speech. Hostile carriers are read as own **data** descriptors only and accessors are never invoked at all — `catch` catches throwing, not never returning — proven by a counting getter that must stay at zero calls, alongside a revoked proxy and a prototype-only carrier; and four legacy payload shapes assert the no-carrier path is byte-identical to before, which is the executable form of "zero difference for an older host" |
|
|
267
|
+
| `scripts/run-wiring-manifest-projection-test.mjs` | The two end-user facts carried on the engine's `wiring_manifest` frame (`modelGate`: which tools this run's model gate removed and the verbatim restore hint; `autoMode`: whether auto mode is actually armed and the engine's own reason word). Projection: both sections ride as `_sema_`-prefixed superset keys, verbatim, and no SDK-named key is minted; a frame where neither section is well-formed projects to `none/not_in_slice` (no empty arm); `modelGate` needs all three keys and treats `removed: []` as a bad value rather than a reading; `autoMode` needs a boolean plus a non-empty reason that agrees with it, and the reason word is never mapped onto the capabilities vocabulary; the frame is flat (a nested `manifest:{}` wrapper is not a supply); `eventId` rides like every other arm. Adapter: exactly one chrome event on the main lane, a sub-flow frame (any `parentToolCallId`, `null` included) yields nothing, and an absent `eventId` leaves the key absent. Added at receiving time because the shell-side gate could not see this package's behaviour: two mutations (empty `removed` accepted, sub-flow gate removed) had passed the package suite untouched |
|
|
267
268
|
|
|
268
269
|
Each suite carries a floor that only moves up — a refactor that stops executing a group of
|
|
269
270
|
assertions is a failure, not a quieter pass. Guards anchor on the **installed artefact's content**
|
package/dist/adapt/arms.js
CHANGED
|
@@ -294,6 +294,38 @@ const engineNoticeArm = function* (m) {
|
|
|
294
294
|
...(typeof m.eventSeq === 'string' && m.eventSeq.length > 0 ? { eventSeq: m.eventSeq } : {}),
|
|
295
295
|
});
|
|
296
296
|
};
|
|
297
|
+
/**
|
|
298
|
+
* `wiring_manifest` 内部臂 → chrome `wiring_manifest`(core #524 + core 147③,server ≥7.58.0)。
|
|
299
|
+
*
|
|
300
|
+
* 契约本体在 `seam.ts` 的 {@link WiringManifestChromeEvent} 头注(它是宿主要读的那一份)。
|
|
301
|
+
* 本臂只做两件:
|
|
302
|
+
* 🔴 **子流断闸**(与 `engine_notice`/`text_segment_end` 同族):带 `parentToolCallId` 的接线自述
|
|
303
|
+
* 属于子代/编排腿,把它的门卸清单渲到 leader 面上,用户会以为**自己这一 turn**少了工具。
|
|
304
|
+
* 断闸按「**键在不在**」判不按「是不是串」判(与 `textSegmentEndArm` 同因:本臂决定的是
|
|
305
|
+
* 「这份自述算谁的」,方向必须更严;`null` 算在场,不给坏值开口子)。
|
|
306
|
+
* 🔴 **第二道成形判**:投影层已把两段各自校验过并保证「至少一段在场」,这里只再确认一次
|
|
307
|
+
* ——防的是**非投影口**喂进来的帧(宿主自建管线 / 重放存量转录),不是重复判据。
|
|
308
|
+
* 两段都不在场 ⇒ 不产事件(与投影层的「不铸空臂」同一条规矩,两层不许各说各话)。
|
|
309
|
+
* 🔴 **一个字都不改**:段内容原样转发(`restore` 逐字、`reason` 六词不映射)——任何加工都必须
|
|
310
|
+
* 发生在渲染面,理由见臂注义务②③。
|
|
311
|
+
*/
|
|
312
|
+
const wiringManifestArm = function* (m) {
|
|
313
|
+
if (m.parentToolCallId !== undefined)
|
|
314
|
+
return;
|
|
315
|
+
const modelGate = m._sema_modelGate;
|
|
316
|
+
const autoMode = m._sema_autoMode;
|
|
317
|
+
const hasGate = typeof modelGate === 'object' && modelGate !== null;
|
|
318
|
+
const hasAuto = typeof autoMode === 'object' && autoMode !== null;
|
|
319
|
+
if (!hasGate && !hasAuto)
|
|
320
|
+
return;
|
|
321
|
+
yield chrome({
|
|
322
|
+
kind: 'wiring_manifest',
|
|
323
|
+
laneProof: MAIN,
|
|
324
|
+
...(hasGate ? { modelGate: modelGate } : {}),
|
|
325
|
+
...(hasAuto ? { autoMode: autoMode } : {}),
|
|
326
|
+
...(typeof m.eventId === 'string' && m.eventId.length > 0 ? { eventId: m.eventId } : {}),
|
|
327
|
+
});
|
|
328
|
+
};
|
|
297
329
|
/**
|
|
298
330
|
* `text_end` 内部臂 → chrome `text_segment_end`(#323 / core #447)。
|
|
299
331
|
*
|
|
@@ -833,6 +865,7 @@ export const ARMS = new Map([
|
|
|
833
865
|
['human_input', humanInputArm],
|
|
834
866
|
['engine_notice', engineNoticeArm],
|
|
835
867
|
['text_end', textSegmentEndArm],
|
|
868
|
+
['wiring_manifest', wiringManifestArm],
|
|
836
869
|
['prompt_suggestions', promptSuggestionsArm],
|
|
837
870
|
['retry_status', retryStatusArm],
|
|
838
871
|
]);
|
|
@@ -116,6 +116,60 @@ export type EventProjection = {
|
|
|
116
116
|
* 旧写法 `if (msg)` 在新返回型上恒真(对象永远 truthy),所以这是**必须点名**的一类改动。
|
|
117
117
|
*/
|
|
118
118
|
export declare function eventToSdkMessage(ev: AgentEvent, ctx: EmitContext): EventProjection;
|
|
119
|
+
/**
|
|
120
|
+
* `engine_notice` → 中性内部通告臂(#310 / #318 件①,契约 = server `ASSISTANT-WIRE-CONTRACT` 附录 D
|
|
121
|
+
* + openapi `Event_engine_notice`)。
|
|
122
|
+
*
|
|
123
|
+
* ── 这是什么 ────────────────────────────────────────────────────────────────────────────────
|
|
124
|
+
* 引擎的**结构化通告**里,被 server 判为面向**本会话终端用户**的那一小撮,按 `sessionId` 路由到
|
|
125
|
+
* 这条会话的流上。live 与 durable **两腿都有**(server 三条 run 腿都挂了口)⇒ 断连重连的重放里
|
|
126
|
+
* 会**再看到它**,与 `workspace_changed` 同一条**幂等消费**纪律(附录 D.1 逐字)。
|
|
127
|
+
*
|
|
128
|
+
* ── 🔴 开集三条(本函数的全部判据,逐条都是「不许做什么」)────────────────────────────────
|
|
129
|
+
* ① **按 `code` + `detail` 消费,`message` 只作 fallback 展示**。core 明写
|
|
130
|
+
* `memory.session_polluted` 的 message 随 `memoryProvenance` 模式变文 ⇒ 按 message 文本匹配
|
|
131
|
+
* **必碎**(5.41 合流码形退役同教训)。所以本层把三者**分别**上臂,绝不把 detail 折进文案。
|
|
132
|
+
* ② **`code` 是开集,认不得也绝不丢帧**。server 的白名单会随 core 码册增长(起步三码 →
|
|
133
|
+
* core 5.47/5.48 的 hold 三码入册,附录 D.3 的表尚未跟上)。本层因此**一个码都不硬编**:
|
|
134
|
+
* 没有识别表、没有 switch、没有「已知才投」——认不认得是**渲染面**的判断,不是投影面的门。
|
|
135
|
+
* ⇒ 本函数对 `code` 唯一的要求是「非空串」(判别键本身缺席才叫畸形)。
|
|
136
|
+
* ③ **能力位不当两值门**:本臂不读任何 caps、不问「引擎支不支持 engine_notice」。帧到了就投影
|
|
137
|
+
* (A-022「到帧即服务」先例)—— 拿一个探测位去 gate 一条**已经到手的事实**,只会在探测未判/
|
|
138
|
+
* 失败时把真事实丢掉。
|
|
139
|
+
*
|
|
140
|
+
* ── 🔴 消费端纪律(写在臂上,因为三端各写一遍必漂)────────────────────────────────────────
|
|
141
|
+
* · `detail` 已过 server 的 `redactSecrets` + 尺寸 bound(自由文本 1000 字符;深 4 / 键 32 /
|
|
142
|
+
* 数组 32),**仍按外部串处理**(呈现面字符处理走消费方自己的单源)。
|
|
143
|
+
* · `ts` 是 **server 观察时刻**(ms epoch),**不是**引擎铸造时刻 —— `EngineNotice` 自身不带时间戳。
|
|
144
|
+
* · 🔴 `memory.harvest_quarantined` 的 `moved` 与 `escalated` **不可相减**(就地墓碑同时计入两者,
|
|
145
|
+
* core 顶注):两个数各自读、并列呈现,任何减法都会得出一个**不存在的量**。本层原样透传 detail
|
|
146
|
+
* 正是为了让这条纪律只在渲染面兑现一次,而不是被投影层先算一个差值出来。
|
|
147
|
+
* · **本帧的缺席不代表「没发生」**:非白名单码 / 缺 `sessionId` 的通告 server **如实不投**
|
|
148
|
+
* (宁缺席不串台),全族那一份始终在 server 的结构化日志里(运维面)。所以消费端**绝不许**
|
|
149
|
+
* 从「没收到 engine_notice」反推「记忆姿态正常」。
|
|
150
|
+
*
|
|
151
|
+
* ── 畸形判据(fail-closed 方向)────────────────────────────────────────────────────────────
|
|
152
|
+
* `code` 非串/空串 ⇒ `malformed`(判别键都没有的通告,渲出去只是一行没有主语的噪声,而 dropped
|
|
153
|
+
* 至少会经 `reportDroppedFrame` 留痕)。其余四键**各自**按诚实缺席处理:`message` 非串 ⇒ 空串
|
|
154
|
+
* (fallback 位缺席,渲染面据此走纯 code 呈现)、`detail` 非对象 ⇒ 空对象(**不是**丢帧:通告的
|
|
155
|
+
* 承重物是 code,detail 坏了不该连带把「这件事发生过」一起吞掉)、`sessionId` / `ts` 非法 ⇒ 键不
|
|
156
|
+
* stamp(绝不铸 `0` 这种看起来合法的假读数)。
|
|
157
|
+
*/
|
|
158
|
+
/** {@link wiringManifestSupersetBody} 的 `modelGate` 段(core #524 / server S-86 的形)。 */
|
|
159
|
+
export interface WiringManifestModelGate {
|
|
160
|
+
/** 门的类别词(core 开集,原样透传;例:模型族名)。 */
|
|
161
|
+
class: string;
|
|
162
|
+
/** 本 run 被门卸掉的工具名(**非空**;空数组不是真读数,见投影函数头注)。 */
|
|
163
|
+
removed: readonly string[];
|
|
164
|
+
/** 恢复办法的**逐字**提示(core 铸,例:环境变量赋值串)。渲染面原样呈现,不要自己拼。 */
|
|
165
|
+
restore: string;
|
|
166
|
+
}
|
|
167
|
+
/** {@link wiringManifestSupersetBody} 的 `autoMode` 段(core 147③ 的形)。 */
|
|
168
|
+
export interface WiringManifestAutoMode {
|
|
169
|
+
armed: boolean;
|
|
170
|
+
/** core **六词逐字透传**。🔴 不映射 `/v1/capabilities.permissionModeAuto.reason` —— 见投影函数头注。 */
|
|
171
|
+
reason: string;
|
|
172
|
+
}
|
|
119
173
|
/**
|
|
120
174
|
* CS-7 §2.7 — turn_end usage → CC `ModelUsage` (pinned name mapping;
|
|
121
175
|
* costMicroUsd/1e6 → costUSD). Surfaced separately because the slice has no
|
|
@@ -46,6 +46,10 @@ export const INTERNAL_SDK_ARM_TYPES = new Set([
|
|
|
46
46
|
'engine_notice',
|
|
47
47
|
// #323 / core #447:assistant 流式**散文段边界**(raw 预分派铸点,见 `textEndProjection` 头注)。
|
|
48
48
|
'text_end',
|
|
49
|
+
// L-70 / L-108②③(core #524 + core 147③,server ≥7.58.0):`wiring_manifest` 帧上**两个新段**的
|
|
50
|
+
// 超集投影(`_sema_modelGate` / `_sema_autoMode`)。整份 manifest 的其余段仍不在本切片里 ——
|
|
51
|
+
// 射程写在 `case 'wiring_manifest'` 头注,别读成「manifest 接上了」。
|
|
52
|
+
'wiring_manifest',
|
|
49
53
|
]);
|
|
50
54
|
/** Wrap neutral content blocks in the CC `assistant` message envelope. */
|
|
51
55
|
function assistantArm(ctx, content) {
|
|
@@ -492,6 +496,43 @@ export function eventToSdkMessage(ev, ctx) {
|
|
|
492
496
|
// rides on `notification`). Projects to a NEUTRAL internal arm: the REPL bridge drops it (the ENGINE
|
|
493
497
|
// already steer-injects the model-facing XML), the print-mode `ask` converts it to CC's
|
|
494
498
|
// `system/task_notification` SDK frame.
|
|
499
|
+
/**
|
|
500
|
+
* `wiring_manifest` —— 引擎的**接线自述**(leg / ask / question / parkLane / … 指纹)。
|
|
501
|
+
*
|
|
502
|
+
* 🔴 **本臂只投两段,不是整份 manifest**(射程写在这里,免得下一个人读成「manifest 接上了」)。
|
|
503
|
+
* 整份 manifest 是 operator / 诊断面的读物,CC transcript 无对位物 —— 此前本臂与两条 HITL 帧
|
|
504
|
+
* 并列走 `nothing('not_in_slice')`,那句话对**其余每一段**今天仍然成立。改的只是:`modelGate`
|
|
505
|
+
* 与 `autoMode` 这两段是**终端用户面**的事实(「这一 run 你少了哪些工具」/「auto 到底武没武装」),
|
|
506
|
+
* 它们没有第二条到达消费端的路,留在 not_in_slice 里就是静默蒸发。
|
|
507
|
+
*
|
|
508
|
+
* 🔴 **帧是 FLAT 的,没有 `manifest` 包层**(SDK `events.d.ts` 的 wiring_manifest 臂逐字:
|
|
509
|
+
* server 把 core `TaskEvent` 的 `ev.manifest` 过白名单 builder 后 **spread 进帧**,三条腿
|
|
510
|
+
* (live SSE / durable 账本行 / resume 重放)同源)⇒ 读 `ev.modelGate` / `ev.autoMode`,
|
|
511
|
+
* **不是** `ev.manifest.modelGate`。6.7.0 契约声明过的那层 `manifest:{...}` 嵌套 wire 上从未有过。
|
|
512
|
+
*
|
|
513
|
+
* 🔴 **两段各自独立**:一段畸形不牵连另一段;两段都不成形 ⇒ 仍回 `none/not_in_slice`,
|
|
514
|
+
* **不铸空臂** —— 每条腿都会来一帧 wiring_manifest,为它们各铸一条什么都不说的内部臂只会让
|
|
515
|
+
* 消费端每 run 多收一条噪声,还把「本 run 真有门卸」这件事稀释掉。
|
|
516
|
+
*
|
|
517
|
+
* 🔴 **超集键走 `_sema_` 前缀**:`modelGate`/`autoMode` 今天**不在**已发布 SDK 的 `AgentEvent`
|
|
518
|
+
* wiring_manifest 臂里(sdk 8.1.0 `dist/events.d.ts` 全树零命中,亲验)。本包不许自铸 SDK 同名
|
|
519
|
+
* 形去冒充上游词汇;等 SDK 声明该臂后再谈改名(那是 BREAKING,另立)。
|
|
520
|
+
*/
|
|
521
|
+
case 'wiring_manifest': {
|
|
522
|
+
// 🔴 `as unknown as Record<…>` 不是图省事:sdk 8.1.0 的 wiring_manifest 臂**没有**这两段
|
|
523
|
+
// (亲验全树零命中),所以 `ev` 在这里被窄成一个不含它们的具名形 —— 直接读会是编译期
|
|
524
|
+
// 「属性不存在」。这行 cast 就是「上游类型还没到货」这件事的**可见落点**;SDK 声明该臂
|
|
525
|
+
// 之后把它删掉,编译器会立刻指出所有该跟着改的地方。
|
|
526
|
+
const raw = ev;
|
|
527
|
+
const body = wiringManifestSupersetBody(raw);
|
|
528
|
+
if (body === undefined)
|
|
529
|
+
return nothing('not_in_slice');
|
|
530
|
+
// `eventId` 与别的臂同律带上(core 铸的事件身份;wire 未必带 ⇒ 缺席时键不在场)。
|
|
531
|
+
// 🔴 主会话收货门 A1c 抓出的死臂:适配层 `wiringManifestArm` 读 `m.eventId`,而本层原来没投它,
|
|
532
|
+
// 那句透传永远是空 —— 消费端拿不到幂等键,durable 重放只能退到内容去重。
|
|
533
|
+
const eventId = typeof raw.eventId === 'string' && raw.eventId.length > 0 ? { eventId: raw.eventId } : {};
|
|
534
|
+
return projected(stamp(ctx, armBody({ type: 'wiring_manifest', ...body, ...eventId })));
|
|
535
|
+
}
|
|
495
536
|
case 'task_notification': {
|
|
496
537
|
// 载荷两形:嵌在 `notification` 里(server 早期形)或直接摊在帧上(今天的 union 形)。
|
|
497
538
|
const nested = ev.notification;
|
|
@@ -645,8 +686,6 @@ export function eventToSdkMessage(ev, ctx) {
|
|
|
645
686
|
// (adapt 臂表同样无此臂),缺口记 0.13.0 候选,不许由本切片顺手编一个假 transcript 形。
|
|
646
687
|
case 'compaction_outcome':
|
|
647
688
|
// ── sdk 6.9.0 提货批(2026-08-08)新入 union 的四条臂 ─────────────────────────────────────
|
|
648
|
-
// `wiring_manifest`:引擎的接线自述(leg/ask/tools 指纹)—— 可观测面,CC transcript 无对位物。
|
|
649
|
-
case 'wiring_manifest':
|
|
650
689
|
// `tool_approval` / `tool_approval_complete`:HITL 审批帧。**#185a 起它们是 `AgentEvent` 的臂**
|
|
651
690
|
// (此前只是 named SSE frame),但本切片不是它们的消费口 —— 它们由
|
|
652
691
|
// `hitl/toolApprovalWire.isToolApprovalFrame` + `hitl/frameRouter` 走审批卡那条链。在这里投一
|
|
@@ -773,44 +812,80 @@ function textEndProjection(ev, ctx) {
|
|
|
773
812
|
})));
|
|
774
813
|
}
|
|
775
814
|
/**
|
|
776
|
-
* `
|
|
777
|
-
* + openapi `Event_engine_notice`)。
|
|
815
|
+
* `wiring_manifest.modelGate` 的形校验(core #524 / server S-86,server ≥7.58.0)。
|
|
778
816
|
*
|
|
779
|
-
*
|
|
780
|
-
*
|
|
781
|
-
*
|
|
782
|
-
*
|
|
817
|
+
* 语义(core 逐字):**仅本 run 真有门卸时在场**;缺席 = 没卸,**不是**「不知道」也不是
|
|
818
|
+
* 「老引擎」—— 所以消费端拿不到本段时唯一合法的行为是**什么都不渲**,绝不许渲一句
|
|
819
|
+
* 「没有工具被卸掉」(那是把「没报」说成「报了个零」)。
|
|
820
|
+
*
|
|
821
|
+
* 三键**全有才收**(与 server 侧同判据)。理由是这一段的用户价值是**一句完整的话**:
|
|
822
|
+
* 「(class)门卸掉了(removed…),恢复用(restore)」—— 少任何一件都只能渲出半句没有出口的话。
|
|
823
|
+
* 缺件 ⇒ 整段不收:不折 `''`、不折空数组、不编默认恢复办法。
|
|
824
|
+
*/
|
|
825
|
+
function projectModelGateSection(raw) {
|
|
826
|
+
if (typeof raw !== 'object' || raw === null || Array.isArray(raw))
|
|
827
|
+
return undefined;
|
|
828
|
+
const g = raw;
|
|
829
|
+
const cls = typeof g.class === 'string' && g.class.length > 0 ? g.class : undefined;
|
|
830
|
+
const restore = typeof g.restore === 'string' && g.restore.length > 0 ? g.restore : undefined;
|
|
831
|
+
const removed = Array.isArray(g.removed) && g.removed.every((r) => typeof r === 'string')
|
|
832
|
+
? g.removed.filter((r) => r.length > 0)
|
|
833
|
+
: undefined;
|
|
834
|
+
if (cls === undefined || restore === undefined || removed === undefined)
|
|
835
|
+
return undefined;
|
|
836
|
+
// 🔴 空数组**不收**:`removed: []` 说的是「卸了零件工具」,与「没卸」在屏面上不可区分,
|
|
837
|
+
// 而 core 的契约是「有门卸才铸段」⇒ 空数组是坏值,不是一个真读数。
|
|
838
|
+
if (removed.length === 0)
|
|
839
|
+
return undefined;
|
|
840
|
+
return { class: cls, removed, restore };
|
|
841
|
+
}
|
|
842
|
+
/**
|
|
843
|
+
* `wiring_manifest.autoMode` 的形校验(core 147③,server ≥7.58.0)。
|
|
783
844
|
*
|
|
784
|
-
*
|
|
785
|
-
*
|
|
786
|
-
*
|
|
787
|
-
*
|
|
788
|
-
* ② **`code` 是开集,认不得也绝不丢帧**。server 的白名单会随 core 码册增长(起步三码 →
|
|
789
|
-
* core 5.47/5.48 的 hold 三码入册,附录 D.3 的表尚未跟上)。本层因此**一个码都不硬编**:
|
|
790
|
-
* 没有识别表、没有 switch、没有「已知才投」——认不认得是**渲染面**的判断,不是投影面的门。
|
|
791
|
-
* ⇒ 本函数对 `code` 唯一的要求是「非空串」(判别键本身缺席才叫畸形)。
|
|
792
|
-
* ③ **能力位不当两值门**:本臂不读任何 caps、不问「引擎支不支持 engine_notice」。帧到了就投影
|
|
793
|
-
* (A-022「到帧即服务」先例)—— 拿一个探测位去 gate 一条**已经到手的事实**,只会在探测未判/
|
|
794
|
-
* 失败时把真事实丢掉。
|
|
845
|
+
* 语义(server 发车帖逐字):**effective 腿恒在**;缺席 = 老 mint / 外部 derive,**不许折成
|
|
846
|
+
* `armed:false`**(那是把「没报」渲成「没武装」)。`reason` 是 **core 六词逐字透传**,
|
|
847
|
+
* 🔴 **不映射** `/v1/capabilities.permissionModeAuto.reason` 的六词 —— 两套词表**同名不同义**
|
|
848
|
+
* (`settings_denied` 在 capabilities 那边折 `no_intent` 不折 `denied`),归一会造出一个假读数。
|
|
795
849
|
*
|
|
796
|
-
*
|
|
797
|
-
*
|
|
798
|
-
*
|
|
799
|
-
|
|
800
|
-
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
|
|
804
|
-
|
|
805
|
-
|
|
850
|
+
* server 侧另有一条**互证**判据:`armed === (reason === 'armed')`,不满足整段不投。本层照抄它,
|
|
851
|
+
* 理由不是「重复上游」而是**非投影口**(宿主自建管线 / 重放存量转录)喂进来的帧不过 server ——
|
|
852
|
+
* 一个 `{armed:true, reason:'settings_denied'}` 会让消费端同时读到「武装了」和「被拒了」。
|
|
853
|
+
*/
|
|
854
|
+
function projectAutoModeSection(raw) {
|
|
855
|
+
if (typeof raw !== 'object' || raw === null || Array.isArray(raw))
|
|
856
|
+
return undefined;
|
|
857
|
+
const a = raw;
|
|
858
|
+
const armed = typeof a.armed === 'boolean' ? a.armed : undefined;
|
|
859
|
+
const reason = typeof a.reason === 'string' && a.reason.length > 0 ? a.reason : undefined;
|
|
860
|
+
if (armed === undefined || reason === undefined)
|
|
861
|
+
return undefined;
|
|
862
|
+
if (armed !== (reason === 'armed'))
|
|
863
|
+
return undefined;
|
|
864
|
+
return { armed, reason };
|
|
865
|
+
}
|
|
866
|
+
/**
|
|
867
|
+
* `wiring_manifest` 帧 → 两个超集键的**纯投影**(公面导出;三端共用,壳侧绝不自抄一份形校验)。
|
|
806
868
|
*
|
|
807
|
-
*
|
|
808
|
-
* `
|
|
809
|
-
*
|
|
810
|
-
* (
|
|
811
|
-
*
|
|
812
|
-
*
|
|
869
|
+
* 返回 `undefined` = 这一帧两段都不在场 / 都不成形 ⇒ 调用方**不铸臂**(见 case 头注)。
|
|
870
|
+
* 🔴 帧是 FLAT 的:读 `ev.modelGate` / `ev.autoMode`,**不读** `ev.manifest.*`。
|
|
871
|
+
*
|
|
872
|
+
* 🔴 **刻意不导出**(typeshape 棘轮「unknown 出境只降」逼出来的正解,不是绕过门):本函数的入参是
|
|
873
|
+
* 一个**未校验的 wire 帧**,唯一诚实的声明形就是 `Record<string, unknown>` —— 把它推上公面等于在
|
|
874
|
+
* 包的公面上开一个无类型边界,而那正是那道棘轮存在的理由。三端要消费这两段,走的是**已定型**的
|
|
875
|
+
* chrome 臂 {@link WiringManifestChromeEvent}(判别键 + 两个具名 interface),不是这个原始投影口;
|
|
876
|
+
* 两个具名形({@link WiringManifestModelGate} / {@link WiringManifestAutoMode})照常导出。
|
|
877
|
+
* 若将来真有宿主要自建管线直调本函数,正解是**先给帧定一个具名 interface** 再导出,不是抬棘轮。
|
|
813
878
|
*/
|
|
879
|
+
function wiringManifestSupersetBody(ev) {
|
|
880
|
+
const modelGate = projectModelGateSection(ev.modelGate);
|
|
881
|
+
const autoMode = projectAutoModeSection(ev.autoMode);
|
|
882
|
+
if (modelGate === undefined && autoMode === undefined)
|
|
883
|
+
return undefined;
|
|
884
|
+
return {
|
|
885
|
+
...(modelGate !== undefined ? { _sema_modelGate: modelGate } : {}),
|
|
886
|
+
...(autoMode !== undefined ? { _sema_autoMode: autoMode } : {}),
|
|
887
|
+
};
|
|
888
|
+
}
|
|
814
889
|
function engineNoticeProjection(ev, ctx) {
|
|
815
890
|
const code = typeof ev.code === 'string' && ev.code.length > 0 ? ev.code : undefined;
|
|
816
891
|
if (code === undefined)
|
package/dist/seam.d.ts
CHANGED
|
@@ -8,6 +8,7 @@
|
|
|
8
8
|
*/
|
|
9
9
|
import type { ModelUsage, SDKMessage } from '@sema-agent/agent-types';
|
|
10
10
|
import type { EngineTurnUsage } from './adapter/downstream/turnUsageToModelUsage.js';
|
|
11
|
+
import type { WiringManifestAutoMode, WiringManifestModelGate } from './adapter/downstream/eventToSdkMessage.js';
|
|
11
12
|
/**
|
|
12
13
|
* `AbortSignal` 的结构型(B3 扩容,SEAM-GAP-4)。
|
|
13
14
|
*
|
|
@@ -389,7 +390,36 @@ export type ChromeEvent = {
|
|
|
389
390
|
* `actor.hostAsserted` 是消费端唯一能判「这个署名可信吗」的位:渲署名而不渲这个位 = 把一个
|
|
390
391
|
* 未经验证的名字渲成可信的(core 自己的 `[from …]` 渲染就是靠它决定加不加 `(unverified)`)。
|
|
391
392
|
*/
|
|
392
|
-
| HumanInputChromeEvent | EngineNoticeChromeEvent | TextSegmentEndChromeEvent;
|
|
393
|
+
| HumanInputChromeEvent | EngineNoticeChromeEvent | TextSegmentEndChromeEvent | WiringManifestChromeEvent;
|
|
394
|
+
/**
|
|
395
|
+
* {@link ChromeEvent} 的 `wiring_manifest` 臂(core #524 + core 147③,server ≥7.58.0)——
|
|
396
|
+
* 引擎接线自述里**两段面向终端用户的事实**,其余每一段仍不投影(射程见 eventToSdkMessage 的
|
|
397
|
+
* `case 'wiring_manifest'` 头注)。
|
|
398
|
+
*
|
|
399
|
+
* ── 🔴 宿主消费义务(三条,全部是「不许做什么」)────────────────────────────────────────────
|
|
400
|
+
* ① **缺席不可反推**。`modelGate` 缺席 = 本 run 没有门卸(core 只在真卸时铸段),
|
|
401
|
+
* `autoMode` 缺席 = 老 mint / 外部 derive **没报**。两者都**不许**被渲成一句肯定句
|
|
402
|
+
* (「没有工具被卸掉」/「auto 未武装」)—— 那是把「没报」说成「报了个否」。
|
|
403
|
+
* 本臂在两段都不成形时**根本不会到达**,所以宿主见到本臂就至少有一段是真读数。
|
|
404
|
+
* ② **`autoMode.reason` 六词逐字呈现,不许映射**到 `/v1/capabilities.permissionModeAuto.reason`
|
|
405
|
+
* 的六词:两套词表**同名不同义**(`settings_denied` 在 capabilities 那边折 `no_intent`
|
|
406
|
+
* 不折 `denied`)。要两面都说,就两面各自读、各自渲,绝不归一。
|
|
407
|
+
* ③ **`modelGate.restore` 原样呈现**:它是 core 铸的**逐字**恢复办法,宿主自己拼一句
|
|
408
|
+
* 「试试把某某开关关掉」等于替引擎编了一条它没说过的出口。
|
|
409
|
+
* 🔴 **幂等**:durable 腿重放会再送同一帧(与 `workspace_changed`/`engine_notice` 同纪律),
|
|
410
|
+
* 宿主按 run/leg 去重,别按到达次数计数。
|
|
411
|
+
* 缺席(宿主不接本臂)= 这两条披露在该宿主上看不见,**不是**报错。
|
|
412
|
+
*/
|
|
413
|
+
export interface WiringManifestChromeEvent {
|
|
414
|
+
kind: 'wiring_manifest';
|
|
415
|
+
laneProof: LaneProof;
|
|
416
|
+
/** 仅本 run 真有门卸时在场(见义务①)。 */
|
|
417
|
+
modelGate?: WiringManifestModelGate;
|
|
418
|
+
/** effective 腿恒在;缺席只表示「没报」(见义务①)。 */
|
|
419
|
+
autoMode?: WiringManifestAutoMode;
|
|
420
|
+
/** core 铸的事件身份(uuidv7 形);wire 未必带 ⇒ 缺席时本键不在场。 */
|
|
421
|
+
eventId?: string;
|
|
422
|
+
}
|
|
393
423
|
/**
|
|
394
424
|
* {@link ChromeEvent} 的 `text_segment_end` 臂(#323 / core #447,core ≥5.63 / server ≥7.50)——
|
|
395
425
|
* 「**assistant 的这一段散文写完了**」,由引擎明说,不是由本包猜。
|
package/dist/seam.js
CHANGED
|
@@ -48,6 +48,12 @@ const CHROME_ARM_TABLE = {
|
|
|
48
48
|
required: false,
|
|
49
49
|
duty: '可选:渲引擎通告(按 code+detail,message 仅 fallback;未知 code 也必须渲不许丢;durable 重放按 eventId 幂等;harvest 的 moved/escalated 并列呈现绝不相减)',
|
|
50
50
|
},
|
|
51
|
+
wiring_manifest: {
|
|
52
|
+
required: false,
|
|
53
|
+
duty: '可选:渲引擎接线自述里的两段用户面事实(modelGate = 本 run 被模型门卸掉的工具 + 逐字恢复办法;' +
|
|
54
|
+
'autoMode = 武装位 + core 六词原因)。🔴 两段缺席一律不渲肯定句;reason 绝不映射 capabilities 六词;' +
|
|
55
|
+
'restore 原样呈现;durable 重放按 run/leg 幂等',
|
|
56
|
+
},
|
|
51
57
|
text_segment_end: {
|
|
52
58
|
required: false,
|
|
53
59
|
duty: '可选:引擎明报的 assistant 散文段边界(#323/core #447)。🔴 content 是对账/定界用的权威全文,拿它再渲一行 = 同一段上屏两遍;缺席只表示「没报」,绝不等于「段没结束」——要退回自家启发式必须按整条流判、不按单帧判',
|
|
@@ -235,12 +235,18 @@ type EventProjection =
|
|
|
235
235
|
`context_usage` / `config_assembled` / `message_committed`(引擎可观测/审计面,CC transcript 无对位物)·
|
|
236
236
|
`needs_review`(壳消费的是 `done{status:'needs_review'}` 终帧,事件形重复且更早)·
|
|
237
237
|
`compaction_outcome`(压缩**非 compacted 结局**报告;CC transcript 无对位物,见 §7 缺口 **P-6**)·
|
|
238
|
-
`
|
|
238
|
+
`tool_approval` / `tool_approval_complete`(**不是丢帧**:它们走
|
|
239
239
|
`hitl/toolApprovalWire.isToolApprovalFrame` + `hitl/frameRouter` 那条审批卡链;在这里投一条 transcript 行
|
|
240
240
|
只会让同一只 ask 出现两次)· **`error`**(见下,单列)。
|
|
241
241
|
|
|
242
242
|
<!-- /ARM-SET:not_in_slice -->
|
|
243
243
|
|
|
244
|
+
📌 **`wiring_manifest` 自 0.56.0 起离开本表,但它只被接了两段**(L-70 / L-108②③)——
|
|
245
|
+
`modelGate` 与 `autoMode` 投成内部臂 + chrome 臂(§19),整份 manifest 的**其余十一段**
|
|
246
|
+
(leg / ask / question / interaction / elicit / parkLane / session / fleet / permissionRules /
|
|
247
|
+
governance / configFingerprint)仍然一件没接。两段都不成形的那些帧照旧回 `none/not_in_slice`,
|
|
248
|
+
**不铸空臂**。⇒ 别把「wiring_manifest 不在这张表里了」读成「manifest 接上了」。
|
|
249
|
+
|
|
244
250
|
🔴 **`error` 单列点名**(端最容易据 §3a 的规则推错的一条):它的名字骗人 —— 那是流的 **15 分钟帽帧**
|
|
245
251
|
(server `sse-log.ts`,已知 `errorCode` = `STREAM_MAX_DURATION`),帧自己就说 **run 仍然活着**;
|
|
246
252
|
终态臂只有 `done` / `failed`。本切片对它无对位渲染物,所以走 `nothing('not_in_slice')` ——
|
|
@@ -2792,3 +2798,72 @@ CC 2.1.250 语料**无同形提示行** —— `first token` / `firstToken` / `f
|
|
|
2792
2798
|
**常驻门**:`scripts/run-client-core-pure-test.mjs` G6 组(相位→kind 全矩阵 / 缺席位不捏造 /
|
|
2793
2799
|
老宿主零差异)、`scripts/run-additive-key-passthrough-test.mjs` G5 组(全链两段)、
|
|
2794
2800
|
`scripts/run-engine-vocab-floor-test.mjs` G2-b/G2-c(对**实装 core** 逐词逐键等值)。
|
|
2801
|
+
|
|
2802
|
+
|
|
2803
|
+
## §19 🆕 `wiring_manifest` 的两段用户面事实(0.56.0;core #524 + core 147③ / server ≥7.58.0;cli L-70 / L-108②③)
|
|
2804
|
+
|
|
2805
|
+
### 19a. 修的是哪一形
|
|
2806
|
+
|
|
2807
|
+
`wiring_manifest` 此前整帧走 `nothing('not_in_slice')`。那对整份 manifest 的**其余十一段**今天仍然
|
|
2808
|
+
成立(它们是 operator / 诊断面读物,CC transcript 无对位物)。问题只在两段:
|
|
2809
|
+
|
|
2810
|
+
| 段 | 它回答用户的哪个问题 | 不接的后果 |
|
|
2811
|
+
|---|---|---|
|
|
2812
|
+
| `modelGate` | 「这一 run 我为什么少了几个工具?」 | 工具**静默消失**,用户以为模型不听话 |
|
|
2813
|
+
| `autoMode` | 「auto 到底武装了没有?为什么?」 | 只能靠 `/v1/capabilities` 猜,而那套词表**同名不同义** |
|
|
2814
|
+
|
|
2815
|
+
两段都**没有第二条**到达消费端的路,留在 `not_in_slice` 里就是静默蒸发。
|
|
2816
|
+
|
|
2817
|
+
### 19b. 端该怎么用(三步,端零字符串判定)
|
|
2818
|
+
|
|
2819
|
+
1. 接 chrome 臂 `kind: 'wiring_manifest'`(`WiringManifestChromeEvent`)——`CHROME_ARM_TABLE` 里
|
|
2820
|
+
`required:false`:不接 = 这两条披露在该宿主上看不见,不是报错。
|
|
2821
|
+
2. `modelGate` 在场 ⇒ 渲一行:类别 `class`、被卸工具 `removed[]`、**逐字**的 `restore`。
|
|
2822
|
+
3. `autoMode` 在场 ⇒ 渲武装位与 `reason` 原文。
|
|
2823
|
+
|
|
2824
|
+
形已经在包里校验过(见 19c),端**不许再自抄一套形判定**。
|
|
2825
|
+
|
|
2826
|
+
### 19c. 🔴 五条读法(端不许自己重判,也不许自己放宽)
|
|
2827
|
+
|
|
2828
|
+
1. **帧是 FLAT 的**:`ev.modelGate` / `ev.autoMode`,**不是** `ev.manifest.*`。6.7.0 契约声明过的
|
|
2829
|
+
那层 `manifest:{...}` wire 上从未有过(SDK `events.d.ts` 该臂头注 2026-08-07 按真 capture 更正);
|
|
2830
|
+
server 把 core `TaskEvent` 的 `ev.manifest` 过白名单 builder 后 **spread 进帧**,三条腿
|
|
2831
|
+
(live SSE / durable 账本行 / resume 重放)同源。
|
|
2832
|
+
2. **缺席不可反推**。`modelGate` 缺席 = 本 run 没有门卸(core 只在真卸时铸段);`autoMode` 缺席 =
|
|
2833
|
+
老 mint / 外部 derive **没报**。两者都**不许**被渲成肯定句(「没有工具被卸掉」/「auto 未武装」)。
|
|
2834
|
+
两段都不成形时本臂**根本不铸** ⇒ 端见到本臂,至少有一段是真读数。
|
|
2835
|
+
3. **`autoMode.reason` 绝不映射** `/v1/capabilities.permissionModeAuto.reason` 的六词:两套词表
|
|
2836
|
+
**同名不同义**(`settings_denied` 在 capabilities 那边折 `no_intent` 不折 `denied`)。两面都要说
|
|
2837
|
+
就两面各自读、各自渲。
|
|
2838
|
+
4. **`modelGate.restore` 原样呈现**:它是 core 铸的**逐字**恢复办法。端自己拼一句「试试关掉某某开关」
|
|
2839
|
+
等于替引擎编了一条它没说过的出口。
|
|
2840
|
+
5. **幂等**:durable 腿重放会再送同一帧(与 `workspace_changed` / `engine_notice` 同纪律),按 run/leg
|
|
2841
|
+
去重,别按到达次数计数。
|
|
2842
|
+
|
|
2843
|
+
### 19d. 形校验(包侧已做,两段各自独立)
|
|
2844
|
+
|
|
2845
|
+
- `modelGate`:`class` / `restore` 非空串 ∧ `removed` 是全串数组 ∧ 过滤空串后**非空** —— 三件缺一
|
|
2846
|
+
整段不收。空 `removed` 与「没卸」在屏面上不可区分,而 core 契约是「有门卸才铸段」⇒ 它是坏值。
|
|
2847
|
+
- `autoMode`:`armed` 是 boolean ∧ `reason` 非空串 ∧ **`armed === (reason === 'armed')`**(server 侧
|
|
2848
|
+
同一条互证式)。照抄它是因为**非投影口**(宿主自建管线 / 重放存量转录)喂进来的帧不过 server。
|
|
2849
|
+
- 子流断闸按「**键在不在**」判、不按「是不是串」判:本臂决定的是「这份自述算谁的」,一个坏
|
|
2850
|
+
`parentToolCallId` 会把子代的门卸清单擦成 leader 的,用户会以为**自己这一 turn**少了工具。
|
|
2851
|
+
|
|
2852
|
+
### 19e. 上游供给的诚实边界(本版新增的已知局限)
|
|
2853
|
+
|
|
2854
|
+
- **超集键名是临时形**:`modelGate` / `autoMode` 今天**不在**已发布 SDK(8.1.0)的 `AgentEvent`
|
|
2855
|
+
wiring_manifest 臂里(全树零命中,亲验)⇒ 本包内部臂用 `_sema_modelGate` / `_sema_autoMode`
|
|
2856
|
+
前缀,不自铸 SDK 同名形。SDK 声明该臂之后改名是 **BREAKING**,另立一批。
|
|
2857
|
+
- **纯投影口刻意不导出**:`wiringManifestSupersetBody` 的入参是未校验 wire 帧,唯一诚实的声明形
|
|
2858
|
+
是 `Record<string, unknown>` —— 推上公面等于在包公面开一个无类型边界(typeshape 棘轮正是为此
|
|
2859
|
+
存在)。端要消费走**已定型**的 chrome 臂;两个具名形 `WiringManifestModelGate` /
|
|
2860
|
+
`WiringManifestAutoMode` 照常导出。
|
|
2861
|
+
- **`autoMode` 只在有 run 的时候来**:它骑在 per-leg 的 live 帧上。**一次性诊断进程**(没有任何
|
|
2862
|
+
引擎腿)读不到它 —— 那种面上只能诚实报「本进程未观测到」,**不许**折成 `armed:false`。
|
|
2863
|
+
|
|
2864
|
+
**实现锚**:`src/adapter/downstream/eventToSdkMessage.ts`(`case 'wiring_manifest'` +
|
|
2865
|
+
`projectModelGateSection` / `projectAutoModeSection` / `wiringManifestSupersetBody`)、
|
|
2866
|
+
`src/adapt/arms.ts`(`wiringManifestArm`)、`src/seam.ts`(`WiringManifestChromeEvent` +
|
|
2867
|
+
`CHROME_ARM_TABLE` 加员)。
|
|
2868
|
+
**常驻门**:`scripts/run-type-superset-ledger-test.mjs`(两个 `_sema_` 键登记)、
|
|
2869
|
+
`scripts/run-integration-doc-freshness-test.mjs` ③ §3c(臂表双向对账)。
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@sema-agent/client-core",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.56.0",
|
|
4
4
|
"description": "Client-side session runtime shared by every sema human client (TUI / web / desktop): sema wire frames (AgentEvent) -> CC session vocabulary (SDKMessage) with dual-plane output (transcript/chrome), deterministic transcript ids, lane discipline as a type, and the notification/dedup ledgers. Every CC-skin shape is collected here so the wire itself stays neutral. Renamed from @sema-agent/wire-cc-adapter (0.1.x).",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "module",
|