@sema-agent/client-core 0.69.0 → 0.69.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -49,6 +49,70 @@
49
49
  > 挡住 ⇒ 本批把它机械化——④a0 对 `pending` 行**要求段头已是日期形**(`(未发布)` 直接红),阶段一
50
50
  > commit 漏转在发布前就红,不再靠人记。
51
51
 
52
+ ## 0.69.3(未发布)
53
+
54
+ > 主题:CC-04 技能帽退役改预算式(读 core 7.19.0 `skills.listing_truncated` 通告;随 server 7.78.1 / core 7.19.0 发布)+ 通告码 +3。接入面 §39。
55
+
56
+ ## 0.69.2(2026-09-16)
57
+
58
+ > 主题:CC-14 思考段账按行所属引擎段记(推理面安全修复)+ CC-13 两件出公面 + 措辞订正。CC-04 技能帽退役改预算式改随 server 7.78.1 / core 7.19.0 进 **0.69.3**。接入面 §38。
59
+
60
+ ### Fixed
61
+
62
+ - 🔴 **CC-14 `thinking_segment_end` 段账按「行所属的引擎段」记,不按「提交时间落在哪一段」**(cli 1.0.115 合并树对抗复审实抓)。
63
+ 帧序 `thinking(A) → reasoning_end(A′) → tool_use → thinking(B) → text_delta → reasoning_end(B′)` 下,第一段整块换(形 a)/ 尾段换(形 b1)
64
+ 之后的活体块要到 `tool_use` 或下一段 `text_delta` 才提交,修前该迟交行被登进**第二段**的 `committedUuids` 首项 ⇒ 宿主按义务把 A′ 行
65
+ 换成 B′、把 B 行清空,上一段定稿内容丢失而披露仍说成功。修后:定稿块提交时不登下一段账(权威已在正文里);定稿块押着时下一段推理增量
66
+ 直接叠上(中间无提交边界)⇒ 先按自己的段提交、新块另起,两段推理绝不同行。判据:该帧序下第二段 `committedUuids` 不含 A′ 行、
67
+ 恰为 B 行一条、`committedPrefixLen` = |B|;(b1) 尾段迟交同律。`run-text-segment-authority-test` T-g 六格(197 → 208)+ X 段十四格(cli 接入方汇编的七种真实帧序整链回归:整段相等 / durable 思考行夹在活体块之间 / 已清尾缓冲 / 同头两段与三段 / 已提交 + 活体混合形;214 → 230)。
68
+
69
+ ### Added
70
+
71
+ - **CC-13 两件「端曾逐字复刻」的判定出公面**(cli 1.0.115 归层复审查得;公面 982 → 985,零删除):
72
+ ① `isSubFlowSegmentEnd(frame)` + `SEGMENT_END_IDENTITY_KEYS`(`parentToolCallId` / `sourceTaskId` / `bgAgentId`,冻结定序)从 `adapt/arms.ts`
73
+ 私有搬到 `adapter/types.ts` 公面 —— §37y ② 要求自建管线的端按三键断子流,判定却只能自铸;现在端改调本判定。投影口 `segmentEndProjection` 同源读该常量。
74
+ ② `droppedWhySentence(why)` 导出(开集 why 落缺省句;按自有属性查表,`constructor` / `toString` 不命中原型函数),且 `ctx.onDroppedFrame`
75
+ 载荷 additive 新带 `sentence?`(= `droppedWhySentence(why)`;`DroppedFrameInfo.sentence?` 可选,宿主自构造的载荷可缺席)——
76
+ 修前判词只在 console 兜底腿拼进日志,自渲运维面的端只能手抄 `duplicate_seq` / `turn_end_usage_absent` 两句。TSA 门 W 段六格(208 → 214)。
77
+
78
+ ### Changed
79
+
80
+ - **`thinking_segment_end` 宿主义务措辞订正**(cli 接入回执实证,零行为变更):`committedPrefixDiverged` 在场时的处置从「删 `committedUuids`
81
+ 全部行、首行位置放 content」改为「**首行就地换成 content,其余行删除或清空其推理正文**(宿主按自身转录结构选)」——
82
+ 转录若有单链 `parentUuid` 遍历 / compact 保留段端点 / resume 链 / 增量游标等依赖,清空优于删(保行身份、父链与压缩元数据);
83
+ 安全目标「未脱敏字节零留盘」等价达成。`CHROME_ARM_TABLE` 义务文案 + 事件 JSDoc + §37y ① 同文;本包投影与三键零改。
84
+
85
+ ## 0.69.1(2026-09-16)
86
+
87
+ > 主题:0.69.0 的**安全面收尾**(cli 接入回执打回 + 对抗复审在合并树上复现):CC-09 runStream 去重**留痕** / CC-10 段末帧子流断闸改按三键 / CC-11 跨工具卡推理段归属交全 / CC-12 park 两新键进视图 + 文档批。CC-04 技能帽退役改预算式改随 server 7.78.1 进 **0.69.2**。接入面 `docs/INTEGRATION-CLIENTS.md` **§37**。
88
+
89
+ ### Fixed
90
+
91
+ - **`runStream` 的 seq 去重丢帧从此留痕**(CC-09)—— 修前 `adapter/runStream.ts` 对「已见过的 durable seq」是一条裸 `continue`
92
+ (零痕)。server 7.77.0 给 `reasoning_end` 的 SSE `id:` **复用**该段首枚 `reasoning_delta` 的 id,于是 `-p` / runStream 链上
93
+ 权威推理段被当作重放帧**静默**丢掉(`text_end` 自带 id 故不受影响);归口 = server 改铸唯一 id(7.78.1)。本层**不改去重律**
94
+ (契约 02 §1.1),只把这次丢弃交给 `ctx.onDroppedFrame({ type, why: 'duplicate_seq' })`(开集 why 词,措辞表加行)。
95
+ 🔴 已知边界:**交互车道 `adapt()` 不经这只去重**(源码直证),`thinking_segment_end` 在交互车道照到;`-p` / runStream 链在
96
+ server <7.78.1 上权威推理段不到端,宿主只能靠留痕知道。门:`run-text-segment-authority-test.mjs` U 段。
97
+ - **段末帧子流断闸改按身份三键**(CC-10;cli 接入回执 ① 实抓的 fail-open)—— 修前 `segmentEndProjection` 只透
98
+ `parentToolCallId`、两臂只按它断闸,而 sdk `EventIdentity` 的子代身份是三键(`parentToolCallId` / `sourceTaskId` / `bgAgentId`,
99
+ core 顶注「stamped ONLY on … AS A SUB-AGENT」):只带 `sourceTaskId` 的子代 `reasoning_end` / `text_end` 被当 leader 帧投上来,
100
+ 端据它换 leader 的思考/文本行 = 子代覆盖 leader。现在三键原样穿上内部臂(`text_end` / `reasoning_end`),任一在场即断,
101
+ 在场非串 ⇒ malformed。门 TSA V1–V3。
102
+ - **跨工具卡的推理段归属交全**(CC-11;回执 ②)—— 修前 `feedThinking` 开新块清对照物,一段推理跨工具卡时 `thinking_segment_end`
103
+ 只交末条已提交行的 `committedUuid`,前面的行无人说属于这一段(端只能整块换末条,其余未脱敏原样留盘)。现在段内已提交思考行
104
+ 按序累积(turn 内有界,`reasoning_end` 用过即清),事件新带 **`committedUuids[]`**(全部按序;`committedUuid` = 末条兼容位),
105
+ `committedPrefixLen` = 全部之和;判分歧按 `concat(rows) + buffer` 对 `content`:前缀对得上 ⇒ 只换还押着的后段(b1),对不上 ⇒
106
+ 整段归宿主(b2,缓冲清空不再交)。**宿主义务改口**:`committedPrefixDiverged` ⇒ 删 `committedUuids` 全部行、首行位置放 `content`。
107
+ 门 TSA V4–V10;臂表义务同文。
108
+ - **park 两新键进 `WorkflowRunState`**(CC-12;回执 ④)—— 0.69.0 只出了 `readWorkflowParks` / `readWorkflowResumeAdmissionIncomplete`
109
+ 两读器,`projectWorkflowRun` 逐键铸视图时丢了它们、`LiveWorkflowController` 不出裸体 ⇒ 端渲染面结构性造不出。现在
110
+ `WorkflowRunState.parks?: readonly WorkflowParkRowView[]` / `resumeAdmissionIncomplete?: true`(additive,由两读器铸,缺席两义同读器)。
111
+ 门 park H 段。
112
+ - 三件文档批(回执 ③⑤⑥):`mcpPanel.ts` jsdoc「四句」订正为五句;`sdkWireTransit` 型面转口加 `SessionPermissionRules`
113
+ (`loosenReasons` 两参的型);§36d 补「`hasCoreValuePorts()`(整袋)与 `coreValuePortMisses()`(逐件)在部分装载时分歧」与
114
+ 「镜像按 core 7.18.0 写,7.17.x 同形」;§37 注明 §35z #15「候 sdk 9.5」已由 §36z #18 收口。
115
+
52
116
  ## 0.69.0(2026-09-15)
53
117
 
54
118
  > 主题:**提货批**(原排 0.68.3;因 peer 地板抬升改 minor)—— sdk 9.4.0 / core 7.18.0 / settings-schema 2.0.0 三家换钉,
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.69.0
38
+ **Version:** 0.69.2
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
@@ -357,7 +357,7 @@ public-surface guard checks that last one).
357
357
  | `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 |
358
358
  | `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 |
359
359
  | `scripts/run-terminal-word-source-test.mjs` | Two tables of ending words, kept apart by **who owns them** — because they used to be one. The engine's own closed set of reasons a run ended, and the server's set of row states a run can finish in, overlap in three words but not in all of them: one word for *something outside stopped it* exists only on the server side, and one for *it paused and can be resumed* exists only on the engine side and means very nearly the opposite of an ending. Merged into a single list, those two sources became indistinguishable, so a new word on either side looked the same as a new word on the other, and the safest-looking move — folding the unknown word into a known one — is the exact mistake that has caused incidents here before. The engine-owned table is checked as a **copy, not an opinion**: it is reconciled word-for-word and in order against the installed engine package, read from both its declaration and its runtime bytes with the two required to agree, so the day upstream adds a fifth reason this reds before anything ships. The two dividing words are each pinned from both sides, including against the upstream declaration directly rather than only against this package's own list. Why the table is copied rather than re-exported is itself an assertion with an expiry: the day upstream publishes the set as a value, this guard reds and the decision gets revisited. The renamed tables leave **no alias** behind, since an alias would let a reader keep consuming the merged list and the split would have bought nothing |
360
- | `scripts/run-workflow-park-truth-projection-test.mjs` | The read face for *which approvals a workflow run left parked* — and the credential that must never ride along with it. Upstream strips the redemption token from that response, and this package's reader is built so the token **cannot** come back: each row is assembled field by field from the three identity keys, never copied wholesale, so an extra key appearing upstream is structurally unable to reach anything this package hands a UI. The guard proves that rather than asserting it — a poisoned row carrying a secret is read, and the secret is searched for across the **entire** serialized result, with the same search proven to find it in the input so a blind search cannot pass; renaming the credential key does not help it through, because the rule is *only these three*, not a blocklist; and the reader's own source is checked to contain no object spread, since one such line would quietly void all of it. The other half is an absence distinction with opposite consequences: a record with **no** parks field at all was written by an older engine and proves nothing about whether approvals are waiting, while an empty list is a positive statement that none are — collapsing those two would let a run whose parked approvals cannot be proven be resumed anyway, so they are kept literally distinguishable, and a payload whose rows are all unreadable answers *unknown* rather than *none*. The four refusal codes for this family are checked code by code against the engine's real bytes, never matched by name prefix, and the older umbrella code they were split out of is asserted to still be **alive** — treating the whole code as retired would make a family of real refusals vanish silently **0.68.3 (core 7.18.0):** two more keys ride the same projection duty as `parks` itself: `originUnconfirmed: true` on a row (never `false`; absence is the confirmed state) and `resumeAdmissionIncomplete: true` on the run (presence means "not a resume base"). Dropping either would turn a refused record back into an admissible one, so the guard pins both, including that neither folds into the other |
360
+ | `scripts/run-workflow-park-truth-projection-test.mjs` | The read face for *which approvals a workflow run left parked* — and the credential that must never ride along with it. Upstream strips the redemption token from that response, and this package's reader is built so the token **cannot** come back: each row is assembled field by field from the three identity keys, never copied wholesale, so an extra key appearing upstream is structurally unable to reach anything this package hands a UI. The guard proves that rather than asserting it — a poisoned row carrying a secret is read, and the secret is searched for across the **entire** serialized result, with the same search proven to find it in the input so a blind search cannot pass; renaming the credential key does not help it through, because the rule is *only these three*, not a blocklist; and the reader's own source is checked to contain no object spread, since one such line would quietly void all of it. The other half is an absence distinction with opposite consequences: a record with **no** parks field at all was written by an older engine and proves nothing about whether approvals are waiting, while an empty list is a positive statement that none are — collapsing those two would let a run whose parked approvals cannot be proven be resumed anyway, so they are kept literally distinguishable, and a payload whose rows are all unreadable answers *unknown* rather than *none*. The four refusal codes for this family are checked code by code against the engine's real bytes, never matched by name prefix, and the older umbrella code they were split out of is asserted to still be **alive** — treating the whole code as retired would make a family of real refusals vanish silently **0.68.3 (core 7.18.0):** two more keys ride the same projection duty as `parks` itself: `originUnconfirmed: true` on a row (never `false`; absence is the confirmed state) and `resumeAdmissionIncomplete: true` on the run (presence means "not a resume base"). Dropping either would turn a refused record back into an admissible one, so the guard pins both, including that neither folds into the other **0.69.1 (CC-12):** both keys now also ride the projected `WorkflowRunState`, so a host that only sees the projection can render them |
361
361
  | `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 |
362
362
  | `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 |
363
363
  | `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 |
@@ -366,7 +366,7 @@ public-surface guard checks that last one).
366
366
  | `scripts/run-cost-reconcile-projection-test.mjs` | The **end-of-run cost reconciliation** reaching consumers at all. The engine splits a run's spend on the wire — the task's own cost, which deliberately excludes delegated sub-agents, the delegated total itself, and the within-task compaction subtotal that sits inside the own figure — and states two reconciliation identities for them. The package used to project none of it, so a cost view could only ever see one number and under-reported both delegated and compaction spend. Both structures are now projected onto the result as superset fields in the wire's integer micro-currency unit, read key by key, with unreadable keys dropped individually, an entirely unreadable structure omitted rather than emitted empty, and unknown categories passed through since the vocabulary belongs upstream. The delegated cost stays **absent when it was never priced**, never a fabricated zero. The same reader also feeds a terminal chrome arm carrying the three parts plus the reconciled total, so the two faces can never compute different answers; the reconciled total is minted only when both sides are known, and otherwise a discriminator bit says which side is unknown. **The reference field for total cost keeps its meaning** — it remains the task's own spend and the delegated total is not folded into it — because that is a shape the wider ecosystem reads; the reconciled figure is offered beside it, not in place of it. A frame that carries no stats emits no arm at all, and the existing rule that in-stream per-turn usage is not published for sub-flows is pinned unchanged, since delegated spend arrives once, at the end. The bit that says those figures are a lower bound is **per stream**, not per context: the emit context belongs to the caller and may be reused across streams, so a gap observed on one run is no evidence at all about the next one — the observation is held for the duration of one stream and handed to both projection faces by value, and the guard drives a reused context both sequentially and concurrently to prove neither direction leaks |
367
367
  | `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 |
368
368
  | `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 |
369
- | `scripts/run-text-segment-authority-test.mjs` | The **authoritative segment replacement** on `text_end` (L-310, server >=7.75.3). `text_end.content` now goes through the same redactor as `result` and the ledger while `text_delta` stays verbatim, so the two **may differ** — an answer that quoted a credential used to be committed to the local transcript in its unredacted form, because the arm only forwarded the boundary signal. Six timing shapes are pinned, two of which an adversarial review reproduced against the installed engine's real bytes and which the first design got wrong in both directions: a second boundary in the same turn (the per-block case on one provider lane) used to make the first segment's prose vanish, and a boundary that arrives *after* the tool card (the other lane emits it at finalize) used to be read as "this package never handled that segment" and reported nothing at all. Three additive keys, all never-false; the two shapes that look alike are told apart by the second one, because the host's action in them is the opposite. The end-to-end legs drive the real pipeline without hand-inserting a segment commit — doing so is exactly what hid the first defect. A second review round then found two combination timings on top of the first fix — a tool card followed by *more* deltas in the same segment, and a byte count that had been documented as a message count — and both are pinned here too. A third round caught a length that the prose called bytes while the code returned UTF-16 units — harmless in ASCII, and on CJK text enough to leave the credential on screen — plus a backfill ledger that had to be kept in step, so the terminal frame does not re-render the segment a second time — kept in step only where the whole stretch sits in one message, because those ledgers are per-message and a fourth round showed that writing across them charges one message's prose to another. A fifth round settled the whole class into one invariant the guard now checks against the previous release's behaviour: this package only rewrites bytes it is still holding in the current message — once a segment has crossed a package-side boundary it emits the three keys and changes nothing else **0.68.2 (CC-01):** the segment identity is now minted here, not by the host: every committed assistant text row carries a top-level `_sema_segment_id` (stamped once at the `adapt()` exit, so the durable whole-message leg and the streamed-segment leg are covered alike; thinking blocks, tool_use-tailed rows and chrome events are left byte-for-byte), `text_segment_end` carries the same value as `segmentId` before rotating, subagent boundaries never rotate, and a replayed stream yields the same identities. Three mutations (no rotation / no stamping / stamping tool_use rows) each turn the guard red **0.69.0 (CC-02):** the same authority replacement now covers the reasoning face (`reasoning_end`, server >=7.77.0): a thinking block still buffered is swapped whole and its live tail recomputed; one already committed at a boundary (the usual timing, since the first text delta commits it) is left untouched and the host is told the row to replace by its uuid, never re-emitted. Subagent boundaries are ignored and the text-segment identity does not rotate |
369
+ | `scripts/run-text-segment-authority-test.mjs` | The **authoritative segment replacement** on `text_end` (L-310, server >=7.75.3). `text_end.content` now goes through the same redactor as `result` and the ledger while `text_delta` stays verbatim, so the two **may differ** — an answer that quoted a credential used to be committed to the local transcript in its unredacted form, because the arm only forwarded the boundary signal. Six timing shapes are pinned, two of which an adversarial review reproduced against the installed engine's real bytes and which the first design got wrong in both directions: a second boundary in the same turn (the per-block case on one provider lane) used to make the first segment's prose vanish, and a boundary that arrives *after* the tool card (the other lane emits it at finalize) used to be read as "this package never handled that segment" and reported nothing at all. Three additive keys, all never-false; the two shapes that look alike are told apart by the second one, because the host's action in them is the opposite. The end-to-end legs drive the real pipeline without hand-inserting a segment commit — doing so is exactly what hid the first defect. A second review round then found two combination timings on top of the first fix — a tool card followed by *more* deltas in the same segment, and a byte count that had been documented as a message count — and both are pinned here too. A third round caught a length that the prose called bytes while the code returned UTF-16 units — harmless in ASCII, and on CJK text enough to leave the credential on screen — plus a backfill ledger that had to be kept in step, so the terminal frame does not re-render the segment a second time — kept in step only where the whole stretch sits in one message, because those ledgers are per-message and a fourth round showed that writing across them charges one message's prose to another. A fifth round settled the whole class into one invariant the guard now checks against the previous release's behaviour: this package only rewrites bytes it is still holding in the current message — once a segment has crossed a package-side boundary it emits the three keys and changes nothing else **0.68.2 (CC-01):** the segment identity is now minted here, not by the host: every committed assistant text row carries a top-level `_sema_segment_id` (stamped once at the `adapt()` exit, so the durable whole-message leg and the streamed-segment leg are covered alike; thinking blocks, tool_use-tailed rows and chrome events are left byte-for-byte), `text_segment_end` carries the same value as `segmentId` before rotating, subagent boundaries never rotate, and a replayed stream yields the same identities. Three mutations (no rotation / no stamping / stamping tool_use rows) each turn the guard red **0.69.0 (CC-02):** the same authority replacement now covers the reasoning face (`reasoning_end`, server >=7.77.0): a thinking block still buffered is swapped whole and its live tail recomputed; one already committed at a boundary (the usual timing, since the first text delta commits it) is left untouched and the host is told the row to replace by its uuid, never re-emitted. Subagent boundaries are ignored and the text-segment identity does not rotate **0.69.1 (CC-09):** the run-stream replay guard still drops a frame whose event id was already seen, but it now reports the drop through the host's dropped-frame sink as `duplicate_seq` instead of vanishing silently (server 7.77.0 reuses the first reasoning delta's id for `reasoning_end`, so that authoritative segment is lost on the print lane until 7.78.1); the interactive adapter has no such guard and keeps receiving it **0.69.1 (CC-10/CC-11):** subagent segment-end frames are fenced on all three identity keys (a frame carrying only `sourceTaskId` no longer masquerades as the leader's), and a reasoning segment that spans tool cards now hands the host every committed row it covers (`committedUuids`) so nothing unredacted is left behind |
370
370
  | `scripts/run-gate-negative-controls-test.mjs` | Whether the registry-shaped guards among the 74 suites above actually turn red when the material they check really breaks — a census had found 16 of them clean enough to rehearse safely (closed sets, mirrors, baselines, floors, a type-shape ratchet) without touching any judgement code. Each is exercised by tampering a disk copy of the real material, spawning the guard's own unmodified script, asserting it exits non-zero and names the disease, then restoring the file byte-for-byte. Seven guards of the same shape and 51 behaviour/projection suites are catalogued rather than rehearsed this round — see `docs/GATE-NEGATIVE-CONTROLS.md` for the full table, the reasons, and a one-minute manual replay recipe for each blind one. The suite cross-checks its own case count against that document's row counts in both directions, so a case quietly dropped from the array without the document following is itself an undeclared blind guard. The backup that makes the restore possible is taken by **exclusive create**: checking for it and then copying are otherwise two steps, and two instances can pass the check together — the later one overwrites the only clean copy with material the earlier one has already tampered, and the rehearsal that promises to leave no trace leaves a permanently corrupted file instead. That interleaving is rehearsed too, in a throwaway directory of its own |
371
371
 
372
372
  Each suite carries a floor that only moves up — a refactor that stops executing a group of
@@ -1,3 +1,4 @@
1
+ import { isSubFlowSegmentEnd } from '../adapter/types.js';
1
2
  import { classifyPeerNotification, renderPeerFrameTranscriptText } from '../peerFrames.js';
2
3
  import { dropQueuedNotificationsForRun, isEngineWorkflowNotified, isWorkflowCompletionCardEnqueued, markEngineWorkflowNotified, normalizeTaskNotification, noteWorkflowCompletionCardEnqueued, renderTaskNotificationXml, taskNotificationDedupKey } from '../notifications.js';
3
4
  import { mapBrainStatusToRetry } from '../retryStatus.js';
@@ -446,6 +447,7 @@ const approvalFrameArm = (kind) => function* (m) {
446
447
  * · 第二道 `content` 在场判(投影层已判 malformed/empty):同 `engine_notice` 的 carrier 二道判,
447
448
  * 防的是**非投影口喂进来的帧**(宿主自建管线 / 重放存量转录),不是重复判据。
448
449
  */
450
+ // CC-10 子流身份三键判定 `isSubFlowSegmentEnd`:CC-13(0.69.2)起住在 `adapter/types.ts` 出公面(端改调,不再复刻)。
449
451
  const textSegmentEndArm = function* (m, { text }) {
450
452
  // 🔴 断闸按「**键在不在**」判,不按「是不是串」判(异源对抗复审第三轮 [medium] 采纳)。
451
453
  // 投影层已对坏 lane 位整帧 fail-closed;这一道是给**非投影口**喂进来的帧(宿主自建管线 /
@@ -453,7 +455,8 @@ const textSegmentEndArm = function* (m, { text }) {
453
455
  // 子代的段边界**擦成 leader 的**上到宿主面。同族臂的 `typeof` 写法在它们那里只影响一行装饰,
454
456
  // 在本臂上决定的是「这条边界算谁的」,方向必须更严。
455
457
  // ⚠️ `null` 也算在场(不给它开口子):wire schema 只允许缺席或 string,`null` 是坏值不是缺席。
456
- if (m.parentToolCallId !== undefined)
458
+ // CC-10(0.69.1):三键任一在场即断(只带 `sourceTaskId` 的子代帧是合法形,修前漏断 = 子代覆盖 leader)。
459
+ if (isSubFlowSegmentEnd(m))
457
460
  return;
458
461
  const content = typeof m.content === 'string' ? m.content : '';
459
462
  if (content.length === 0)
@@ -483,7 +486,7 @@ const textSegmentEndArm = function* (m, { text }) {
483
486
  * 🔴 **不轮换段身份**:段身份是文本行的(CC-01),思考块的收口与它无关。
484
487
  */
485
488
  const thinkingSegmentEndArm = function* (m, { text }) {
486
- if (m.parentToolCallId !== undefined)
489
+ if (isSubFlowSegmentEnd(m))
487
490
  return;
488
491
  const content = typeof m.content === 'string' ? m.content : '';
489
492
  if (content.length === 0)
@@ -497,6 +500,7 @@ const thinkingSegmentEndArm = function* (m, { text }) {
497
500
  ...(r.committedPrefixDiverged ? { committedPrefixDiverged: true } : {}),
498
501
  ...(r.committedPrefixLen > 0 ? { committedPrefixLen: r.committedPrefixLen } : {}),
499
502
  ...(r.committedUuid !== undefined ? { committedUuid: r.committedUuid } : {}),
503
+ ...(r.committedUuids !== undefined && r.committedUuids.length > 0 ? { committedUuids: r.committedUuids } : {}),
500
504
  ...(typeof m.eventId === 'string' && m.eventId.length > 0 ? { eventId: m.eventId } : {}),
501
505
  });
502
506
  };
@@ -105,8 +105,10 @@ export interface ThinkingSegmentReplacement {
105
105
  committedPrefixLen: number;
106
106
  /** 已 committed 的那条思考行与权威全文不相等 ⇒ 本包不再交字节,宿主整块换。 */
107
107
  committedPrefixDiverged: boolean;
108
- /** `committedPrefixDiverged` 为真时在场:那条行的 `uuid`。 */
108
+ /** 有已提交行时在场:**末条**行的 `uuid`(0.69.0 起;兼容位)。 */
109
109
  committedUuid?: string;
110
+ /** CC-11(0.69.1):有已提交行时在场:段内**全部**已提交思考行的 `uuid`(按提交序;跨工具卡时多条)。 */
111
+ committedUuids?: readonly string[];
110
112
  }
111
113
  /** M1 对外的七个动作 + 两个读位(矩阵 §3.2 的 #2/#3 两条跨模块接口就是最后那三件)。 */
112
114
  export interface TextStream {
@@ -250,8 +252,9 @@ export interface TextStream {
250
252
  * (b) 思考已在边界**整块**提交(思考→回答边界最常见:`text_delta` 先到、server 的 `reasoning_end`
251
253
  * 后到)⇒ 撤不回,一个字节都不再交;立 `committedPrefixDiverged` + 那条行的 `uuid`,由宿主整块换。
252
254
  * (f) 本包一个字节都没经手(durable 整块思考臂 / 宿主只喂 end 不喂 delta)⇒ 什么都不做。
253
- * 没有 (b1)/(c)/(e):思考块无 idle-flush、无封存、无跨工具卡累加 —— 提交是整块的,边界一到就清账。
254
- * 🔴 `committedUuid` 只在 (b) 形在场;它是本包在 {@link TextStream.takeThinking} 铸的那个 uuid。
255
+ * CC-11(0.69.1)补上 (b1):一段推理**跨工具卡**时已提交的是多条行,前缀 = 全部行按序拼接 —— 对得上就只换还押着的
256
+ * 后段,对不上就整段归宿主换;两形都把全部行的 uuid 交出去(`committedUuids`,`committedUuid` = 末条兼容位)。
257
+ * 无 idle-flush、无封存(思考块只在正常边界整块提交)。
255
258
  */
256
259
  replaceThinkingSegment(content: string, anchor?: Frame): ThinkingSegmentReplacement;
257
260
  /**
@@ -142,7 +142,14 @@ export function createTextStream(ctx, idOf) {
142
142
  * 写于 `takeThinking` 提交那一拍;清于 `replaceThinkingSegment` 用过之后、以及下一块思考开始
143
143
  * (`feedThinking` 首条)—— 上一块的提交不是这一块的对照物。
144
144
  */
145
- let committedThinking = null;
145
+ let committedThinking = [];
146
+ /**
147
+ * CC-14(0.69.2):活体思考块已被 `reasoning_end` **定稿**(形 (a) 整块换 / 形 (b1) 尾段换)但还没提交。
148
+ * 段账按「行所属的引擎段」记,不按「提交时间落在哪一段」:定稿块迟交(tool_use / 下一段 text_delta 才提交)时
149
+ * **不**登进下一段的 `committedThinking` —— 修前登进去,宿主按义务把上一段定稿行换成下一段权威、其余行清空,
150
+ * 上一段定稿内容丢失而披露仍说成功(cli 1.0.115 合并树对抗复审实抓)。
151
+ */
152
+ let thinkingSettled = false;
146
153
  /**
147
154
  * 段身份窗口(见 {@link SEMA_SEGMENT_ID_KEY} 头注):窗口的第一帧锚 + 序号 + 派生结果缓存。
148
155
  * 🔴 缓存不是优化:锚缺席那一形派生落到 `ctx.uuid()`,不缓存的话同一窗口两次读会得到两个身份。
@@ -169,7 +176,12 @@ export function createTextStream(ctx, idOf) {
169
176
  session_id: ctx.sessionId,
170
177
  parent_tool_use_id: null,
171
178
  };
172
- committedThinking = { text: thinking, uuid: msg.uuid };
179
+ // CC-11(0.69.1):**按序累积**,不覆盖 —— 一段推理可以跨工具卡(openai 车道 finalize 时序),每一块提交都是
180
+ // 这一段的一部分;`reasoning_end` 到达时把它们全部交出去(`committedUuids`),宿主才有整段的归属。
181
+ // CC-14:定稿块属上一段,不登本段账(它的权威已经在正文里)。
182
+ if (!thinkingSettled)
183
+ committedThinking.push({ text: thinking, uuid: msg.uuid });
184
+ thinkingSettled = false;
173
185
  thinking = '';
174
186
  thinkingAnchor = null;
175
187
  thinkingBlockOpen = false;
@@ -281,10 +293,21 @@ export function createTextStream(ctx, idOf) {
281
293
  *feedThinking(delta, frame) {
282
294
  if (segmentWindowAnchor === null)
283
295
  segmentWindowAnchor = frame;
296
+ // CC-14:定稿块还押着、下一段增量直接叠上来(中间无提交边界)⇒ 先按它自己的段提交,新块另起;
297
+ // 两段推理绝不同行(同行 = 下一段 end 的归属会把上一段定稿一并换掉)。
298
+ if (thinkingSettled) {
299
+ if (thinking.length > 0) {
300
+ yield* drainLive();
301
+ yield* takeThinking();
302
+ }
303
+ else {
304
+ thinkingSettled = false;
305
+ }
306
+ }
284
307
  if (thinking.length === 0) {
285
308
  thinkingAnchor = frame;
286
- // CC-02:新一块思考开始 ⇒ 上一块的提交不再是对照物(迟到的 reasoning_end 只认当前块)。
287
- committedThinking = null;
309
+ // CC-11:新一块开始**不**清对照物(修前这里清了 —— 跨工具卡的推理段只剩末条归属,cli 接入回执 ② 实抓);
310
+ // 对照物只在 `reasoning_end` 用过之后清(段界由引擎说了算),turn 结束整只丢,天然有界。
288
311
  // P2d:elapsed 锚在**首条** leader 推理增量(token 累计由 stream_delta.estimatedTokens
289
312
  // 承载,宿主自加;>30s 无增量的 stall 提示同样由宿主按增量时间戳判)。
290
313
  yield chrome({ kind: 'thinking_activity', laneProof: MAIN, active: true });
@@ -431,27 +454,45 @@ export function createTextStream(ctx, idOf) {
431
454
  committedText += committed;
432
455
  },
433
456
  replaceThinkingSegment: (content) => {
434
- // (b) 已整块提交:撤不回 ⇒ 只算分歧、指认行,一个字节都不再交。
435
- if (thinking.length === 0) {
436
- const c = committedThinking;
437
- committedThinking = null;
438
- if (c === null || c.text.length === 0) {
439
- // (f):本包没经手过这一块(durable 整块臂铸过 transcript 行 / 宿主只喂 end)。
440
- return { diverged: false, committedPrefixLen: 0, committedPrefixDiverged: false };
457
+ // CC-11:段 = 段内**全部**已提交思考行(按序)+ 还押着的缓冲。分歧按整段拼文比;归属一次交出全部 uuid。
458
+ const rows = committedThinking;
459
+ committedThinking = [];
460
+ const prefix = rows.map((r) => r.text).join('');
461
+ const uuids = rows.map((r) => r.uuid);
462
+ const last = uuids.length > 0 ? uuids[uuids.length - 1] : undefined;
463
+ // (f) 本包没经手过这一段(durable 整块臂铸过 transcript 行 / 宿主只喂 end)。
464
+ if (prefix.length === 0 && thinking.length === 0) {
465
+ return { diverged: false, committedPrefixLen: 0, committedPrefixDiverged: false };
466
+ }
467
+ const live = prefix + thinking;
468
+ const diverged = live !== content;
469
+ if (prefix.length === 0) {
470
+ // (a) 全部还押在缓冲里:整块换,活体尾巴按已泄前缀重算。
471
+ const emitted = thinking.slice(0, thinking.length - thinkingPending.length);
472
+ thinkingPending = content.startsWith(emitted) ? content.slice(emitted.length) : '';
473
+ thinking = content;
474
+ thinkingSettled = content.length > 0;
475
+ return { diverged, committedPrefixLen: 0, committedPrefixDiverged: false };
476
+ }
477
+ // (b) 有已提交行(一条或跨卡多条):已提交那截撤不回。
478
+ const committedPrefixDiverged = !content.startsWith(prefix);
479
+ if (!committedPrefixDiverged) {
480
+ // (b1) 已提交前缀逐字对得上 ⇒ 只换还押着的尾段(跨卡后段);`thinking` 为空时零字节可换,只报归属。
481
+ if (thinking.length > 0) {
482
+ const tail = content.slice(prefix.length);
483
+ const emitted = thinking.slice(0, thinking.length - thinkingPending.length);
484
+ thinkingPending = tail.startsWith(emitted) ? tail.slice(emitted.length) : '';
485
+ thinking = tail;
486
+ thinkingSettled = tail.length > 0;
441
487
  }
442
- const diverged = c.text !== content;
443
- return diverged
444
- ? { diverged: true, committedPrefixLen: c.text.length, committedPrefixDiverged: true, committedUuid: c.uuid }
445
- : { diverged: false, committedPrefixLen: c.text.length, committedPrefixDiverged: false };
488
+ return { diverged, committedPrefixLen: prefix.length, committedPrefixDiverged: false, ...(last !== undefined ? { committedUuid: last } : {}), committedUuids: uuids };
446
489
  }
447
- // (a) 还押在缓冲里:整块换,活体尾巴按已泄前缀重算(与 replaceAnswerSegment 的活体面同律)。
448
- const prev = thinking;
449
- const diverged = prev !== content;
450
- const emitted = prev.slice(0, prev.length - thinkingPending.length);
451
- thinkingPending = content.startsWith(emitted) ? content.slice(emitted.length) : '';
452
- thinking = content;
453
- committedThinking = null;
454
- return { diverged, committedPrefixLen: 0, committedPrefixDiverged: false };
490
+ // (b2) 已提交那截自己也过期 ⇒ 一个字节都不再交(还押着的尾段清掉,别让它落成第二份),整段归宿主换。
491
+ thinking = '';
492
+ thinkingPending = '';
493
+ thinkingAnchor = null;
494
+ thinkingSettled = false;
495
+ return { diverged: true, committedPrefixLen: prefix.length, committedPrefixDiverged: true, ...(last !== undefined ? { committedUuid: last } : {}), committedUuids: uuids };
455
496
  },
456
497
  segmentId: segmentIdNow,
457
498
  rotateSegmentIdentity: (anchor) => {
@@ -2,6 +2,7 @@ import { stamp, } from '../types.js';
2
2
  import { turnUsageToModelUsage } from './turnUsageToModelUsage.js';
3
3
  // 0.60.0(engine ≥7.64.0 / sdk 8.4.0):`tool_end.gate` 的三端共用窄读器(判定归包,端不许自拼)。
4
4
  import { gateOutcomeOf } from '../../gateOutcome.js';
5
+ import { SEGMENT_END_IDENTITY_KEYS } from '../types.js';
5
6
  // 0.65.0(core 7.12.0 CHANGELOG「Removed (BREAKING)」`WiringManifest.autoMode.breaker`):
6
7
  // 熔断族整只退役 ⇒ 本臂此前对 `autoMode.breaker` 的窄读(`classifierBreakerOf`)与它的型位一并
7
8
  // 删除(clean-cut,不留兼容读)。`autoMode` 段剩下的两位 `armed` / `reason` 一个字节不动。
@@ -960,14 +961,22 @@ function segmentEndProjection(ev, ctx, arm) {
960
961
  // 正是本臂头注点名要防的跨 lane 状态破坏,而且是 fail-**open** 方向。
961
962
  // 三态与 B3-DIRTY 同:键缺席/`undefined` ⇒ 本来就是 leader 帧(照旧);键在场却非串(含 `null`,
962
963
  // wire schema 只允许缺席或 string)⇒ `malformed` 丢弃并留痕,坏值不许买路。
963
- if (ev.parentToolCallId !== undefined && typeof ev.parentToolCallId !== 'string') {
964
- return dropped('malformed', arm);
964
+ // CC-10(0.69.1;cli 接入回执 ① 实抓的 fail-open):子流身份是**三键**(sdk `EventIdentity`:`parentToolCallId` /
965
+ // `sourceTaskId` / `bgAgentId`),core 顶注逐字「stamped ONLY on the content events of a task running AS A SUB-AGENT」——
966
+ // 只带 `sourceTaskId`(无 `parentToolCallId`)是合法的子代帧形。修前只透 `parentToolCallId`,臂只按它断闸 ⇒ 那一形的
967
+ // 子代段末帧被当 leader 投上来,端据它换 leader 的思考/文本行 = 子代推理覆盖 leader 推理面。⇒ 三键**原样穿上内部臂**,
968
+ // 任一键在场却非串 ⇒ 整帧 malformed(与 `parentToolCallId` 同一条 B3-DIRTY 律:坏值不许买路)。
969
+ for (const k of SEGMENT_END_IDENTITY_KEYS) {
970
+ if (ev[k] !== undefined && typeof ev[k] !== 'string')
971
+ return dropped('malformed', arm);
965
972
  }
966
973
  if (content.length === 0)
967
974
  return nothing('empty_payload');
968
975
  const identity = {
969
976
  ...(typeof ev.eventId === 'string' ? { eventId: ev.eventId } : {}),
970
977
  ...(typeof ev.parentToolCallId === 'string' ? { parentToolCallId: ev.parentToolCallId } : {}),
978
+ ...(typeof ev.sourceTaskId === 'string' ? { sourceTaskId: ev.sourceTaskId } : {}),
979
+ ...(typeof ev.bgAgentId === 'string' ? { bgAgentId: ev.bgAgentId } : {}),
971
980
  };
972
981
  // 两处字面量铸点(不是一处参数化):内部臂词表门按 `armBody({ type: '<字面量>'` 抽铸点,参数化会让两臂在账上「多登记」。
973
982
  return arm === 'text_end'
@@ -172,6 +172,14 @@ export declare function isGovernanceTerminal(input: {
172
172
  status: string | undefined;
173
173
  errorCode: string | undefined;
174
174
  }): boolean;
175
+ /** 🔴 **按自有属性查表**(本仓对措辞表的既定纪律):`Object.freeze` 不移除原型,裸下标会让一个
176
+ * 叫 `constructor` / `toString` 的判词命中 `Object.prototype` 上的**函数**并被拼进日志行。 */
177
+ /**
178
+ * CC-13(0.69.2)**出公面**:一条丢帧判词(`why` 开集进、缺省句兜底)。此前只在 console 兜底腿拼进日志,
179
+ * `ctx.onDroppedFrame` 只收 `{ type, why }` ⇒ 自渲运维面的端只能手抄本表。现在载荷同时带 `sentence`,
180
+ * 本函数给宿主在别处(存量留痕 / 别的 why 来源)复用同一份措辞。
181
+ */
182
+ export declare function droppedWhySentence(why: string): string;
175
183
  /** 测试钩:清空「已上报过的臂」去重表(去重是**跨调用**状态,不清就只有第一条用例看得见)。 */
176
184
  export declare function _resetDroppedFrameReportForTest(): void;
177
185
  /** 测试钩:去重表当前条数 —— ADAPTER-F5 的上限断言要读的**决定结果的量**(行数只证「吼了几次」,
@@ -255,13 +255,20 @@ function sanitizeFrameType(type) {
255
255
  * 去看引擎那一侧。
256
256
  */
257
257
  const DROPPED_WHY_SENTENCE = Object.freeze({
258
+ duplicate_seq: 'a frame with this event id was already consumed on this run stream, so it was dropped as a replay (durable idempotency, contract 02 §1.1). ' +
259
+ 'If the engine reuses an id across two DIFFERENT frames (server 7.77.0 does this for `reasoning_end`, fixed upstream in 7.78.1), the second one is lost here — this line is the only trace.',
258
260
  turn_end_usage_absent: "the engine declares `turn_end.usage` as always present (core >= 7.17.0), and this frame has none. " +
259
261
  'The turn is counted as UNKNOWN spend (the run total is reported as a lower bound), and the frame itself renders NOWHERE.',
260
262
  });
261
263
  const DROPPED_WHY_SENTENCE_DEFAULT = "this build's projector has no arm for it (engine newer than the client, or a malformed frame). It renders NOWHERE.";
262
264
  /** 🔴 **按自有属性查表**(本仓对措辞表的既定纪律):`Object.freeze` 不移除原型,裸下标会让一个
263
265
  * 叫 `constructor` / `toString` 的判词命中 `Object.prototype` 上的**函数**并被拼进日志行。 */
264
- function droppedWhySentence(why) {
266
+ /**
267
+ * CC-13(0.69.2)**出公面**:一条丢帧判词(`why` 开集进、缺省句兜底)。此前只在 console 兜底腿拼进日志,
268
+ * `ctx.onDroppedFrame` 只收 `{ type, why }` ⇒ 自渲运维面的端只能手抄本表。现在载荷同时带 `sentence`,
269
+ * 本函数给宿主在别处(存量留痕 / 别的 why 来源)复用同一份措辞。
270
+ */
271
+ export function droppedWhySentence(why) {
265
272
  const row = Object.hasOwn(DROPPED_WHY_SENTENCE, why) ? DROPPED_WHY_SENTENCE[why] : undefined;
266
273
  return typeof row === 'string' ? row : DROPPED_WHY_SENTENCE_DEFAULT;
267
274
  }
@@ -269,7 +276,7 @@ function reportDroppedFrame(why, type, ctx) {
269
276
  if (ctx.onDroppedFrame) {
270
277
  try {
271
278
  // 以 ctx 为 receiver 调用(宿主写成方法形时 `this` 不丢;与 ctx.emitChrome 同款姿势)。
272
- ctx.onDroppedFrame({ type, why });
279
+ ctx.onDroppedFrame({ type, why, sentence: droppedWhySentence(why) });
273
280
  return; // sink 接住了 ⇒ console 那行让位(理由 ② 见头注)
274
281
  }
275
282
  catch (e) {
@@ -354,8 +361,14 @@ async function* runStreamInner(events, ctx, handle = {}) {
354
361
  // event-id idempotency — drop a re-seen durable seq (contract 02 §1.1).
355
362
  const seq = eventSeq(ev);
356
363
  if (seq !== undefined) {
357
- if (seen.has(seq))
364
+ if (seen.has(seq)) {
365
+ // CC-09(0.69.1;cli L-319 `-p` 真根因的包侧半场):**丢也要留痕**。server 7.77.0 给 `reasoning_end` 的
366
+ // SSE `id:` 复用了该段首枚 `reasoning_delta` 的 id ⇒ 这条去重把**权威段**当重放帧丢了,而修前这里是一条裸
367
+ // `continue` —— 比 0.68.2 的 `unknown_arm`(至少有痕)更静默。归口 = server 改铸唯一 id(7.78.1);本层
368
+ // 不猜「这是真重放还是 id 复用」(去重律不变,契约 02 §1.1),只把这次丢弃交给宿主的丢帧留痕口。
369
+ reportDroppedFrame('duplicate_seq', String(ev.type ?? 'unknown'), ctx);
358
370
  continue;
371
+ }
359
372
  seen.add(seq);
360
373
  }
361
374
  // C1 — SUBAGENT content divert (service 1.89 forwardSubagentEvents): a CONTENT event stamped with
@@ -57,6 +57,11 @@ export declare function uuid(): string;
57
57
  export interface DroppedFrameInfo {
58
58
  readonly type: string;
59
59
  readonly why: string;
60
+ /**
61
+ * CC-13(0.69.2,additive):与 `why` 配套的人读判词(= `droppedWhySentence(why)`;开集 why 落缺省句)。
62
+ * 本包铸的载荷恒带;宿主自构造的载荷可缺席 ⇒ 读方 `info.sentence ?? droppedWhySentence(info.why)`。
63
+ */
64
+ readonly sentence?: string;
60
65
  }
61
66
  /**
62
67
  * Per-stream identity context. `session_id` threads every emitted arm; it is the
@@ -112,3 +117,15 @@ export declare function stamp<T extends {
112
117
  uuid: string;
113
118
  session_id: string;
114
119
  };
120
+ /**
121
+ * CC-13(0.69.2):段末帧(`text_end` / `reasoning_end`)的**子流身份三键**(sdk `EventIdentity`)。任一在场 =
122
+ * 这条段边界不是 leader 的。此前只在 `adapt/arms.ts` 私有,自建管线的端(print 车道 / 座位层)只能逐字复刻一份;
123
+ * §37y ② 的义务在包,判定也该在包 —— 端改调本判定,别再自铸。住在本文件 = 内核闭包既有文件(portability 棘轮零抬)。
124
+ */
125
+ export declare const SEGMENT_END_IDENTITY_KEYS: readonly ["parentToolCallId", "sourceTaskId", "bgAgentId"];
126
+ /**
127
+ * CC-13:段末帧是不是子流的(三键任一**在场**)。按「键在不在」判,不按「是不是串」判 —— 坏值已在投影口
128
+ * fail-closed(malformed);这一道是给非投影口喂进来的帧(宿主自建管线 / 重放存量转录)用的。
129
+ * 参数收任意对象(帧型不限;typeshape 纪律:边界层不让 `unknown` 出境)。
130
+ */
131
+ export declare function isSubFlowSegmentEnd(m: object): boolean;
@@ -21,3 +21,21 @@ export function uuid() {
21
21
  export function stamp(ctx, body) {
22
22
  return { ...body, uuid: body.uuid ?? uuid(), session_id: ctx.sessionId };
23
23
  }
24
+ /**
25
+ * CC-13(0.69.2):段末帧(`text_end` / `reasoning_end`)的**子流身份三键**(sdk `EventIdentity`)。任一在场 =
26
+ * 这条段边界不是 leader 的。此前只在 `adapt/arms.ts` 私有,自建管线的端(print 车道 / 座位层)只能逐字复刻一份;
27
+ * §37y ② 的义务在包,判定也该在包 —— 端改调本判定,别再自铸。住在本文件 = 内核闭包既有文件(portability 棘轮零抬)。
28
+ */
29
+ export const SEGMENT_END_IDENTITY_KEYS = Object.freeze(['parentToolCallId', 'sourceTaskId', 'bgAgentId']);
30
+ /**
31
+ * CC-13:段末帧是不是子流的(三键任一**在场**)。按「键在不在」判,不按「是不是串」判 —— 坏值已在投影口
32
+ * fail-closed(malformed);这一道是给非投影口喂进来的帧(宿主自建管线 / 重放存量转录)用的。
33
+ * 参数收任意对象(帧型不限;typeshape 纪律:边界层不让 `unknown` 出境)。
34
+ */
35
+ export function isSubFlowSegmentEnd(m) {
36
+ const rec = m;
37
+ for (const k of SEGMENT_END_IDENTITY_KEYS)
38
+ if (rec[k] !== undefined)
39
+ return true;
40
+ return false;
41
+ }
@@ -93,7 +93,7 @@ export declare function fetchMcpPanel(client: Pick<AgentClient, 'sessions'>, ses
93
93
  /**
94
94
  * `lastLegMcp` 那一行措辞的**唯一铸点**(三端共用;别在各端的行装配里另写一遍)。
95
95
  *
96
- * 🔴 四句刻意逐字互异(黑盒锚),且**没有一句**提到 `servers[]`:
96
+ * 🔴 五句刻意逐字互异(黑盒锚;0.69.1 订正:此前写「四句」漏了「面板体读不出」那一句),且**没有一句**提到 `servers[]`:
97
97
  * ① 在场 —— 名册 + runId + at;
98
98
  * ② `opts.reachable === false` —— 「未观测」:这次进程没读到面板体;
99
99
  * ③ 面板读到了但 `lastLegMcp` 整键缺席 —— 「不报」:三形同形 + 老引擎,**不武断咎为版本**;
package/dist/mcpPanel.js CHANGED
@@ -97,7 +97,7 @@ export async function fetchMcpPanel(client, sessionId, opts) {
97
97
  /**
98
98
  * `lastLegMcp` 那一行措辞的**唯一铸点**(三端共用;别在各端的行装配里另写一遍)。
99
99
  *
100
- * 🔴 四句刻意逐字互异(黑盒锚),且**没有一句**提到 `servers[]`:
100
+ * 🔴 五句刻意逐字互异(黑盒锚;0.69.1 订正:此前写「四句」漏了「面板体读不出」那一句),且**没有一句**提到 `servers[]`:
101
101
  * ① 在场 —— 名册 + runId + at;
102
102
  * ② `opts.reachable === false` —— 「未观测」:这次进程没读到面板体;
103
103
  * ③ 面板读到了但 `lastLegMcp` 整键缺席 —— 「不报」:三形同形 + 老引擎,**不武断咎为版本**;
@@ -38,4 +38,4 @@ export { isApprovalRequestFrameV1, isApprovalRevokeFrameV1 } from '@sema-agent/s
38
38
  * 🔴 **谁是权威**:权威恒是 `@sema-agent/sdk` 本身。本文件是**别名**,上游改名 ⇒ 本文件当场编译红,
39
39
  * 端跟着红 —— 这正是型面转口相对「端各自直连」唯一多出来的那点好处(红在一处,不是散在九处)。
40
40
  */
41
- export type { TaskRequest, SemaSettings, SkillSpec, AgentEvent, RunReceipt, CancelAck, RunRecord, ToolApprovalRespondAck, FleetTaskRow, FleetWorkflowRow, ApprovalCard, ApprovalRequestFrame, ApprovalRiskAxes, AskDecisionAck, AskDecisionBody, } from '@sema-agent/sdk';
41
+ export type { TaskRequest, SessionPermissionRules, SemaSettings, SkillSpec, AgentEvent, RunReceipt, CancelAck, RunRecord, ToolApprovalRespondAck, FleetTaskRow, FleetWorkflowRow, ApprovalCard, ApprovalRequestFrame, ApprovalRiskAxes, AskDecisionAck, AskDecisionBody, } from '@sema-agent/sdk';
package/dist/seam.d.ts CHANGED
@@ -742,8 +742,15 @@ export interface ThinkingSegmentEndChromeEvent {
742
742
  committedPrefixDiverged?: true;
743
743
  /** 已 committed 思考行的全文长度(UTF-16 代码单元;never 0)—— 思考面上恒 = 整块。 */
744
744
  committedPrefixLen?: number;
745
- /** `committedPrefixDiverged` 在场时恒在场:要换掉的那条思考行的 `uuid`(本包铸)。 */
745
+ /** 有已提交思考行时在场:**末条**行的 `uuid`(0.69.0 兼容位;整段归属看 {@link committedUuids})。 */
746
746
  committedUuid?: string;
747
+ /**
748
+ * CC-11(0.69.1):有已提交思考行时在场 —— 段内**全部**已提交行的 `uuid`,按提交序(一段推理跨工具卡时多条)。
749
+ * 🔴 `committedPrefixDiverged` 在场 ⇒ 宿主把首行就地换成 `content`,其余行**删除或清空其推理正文**(宿主按自身转录结构选:
750
+ * 有父链 / 压缩段依赖的转录**清空优于删**,保行身份、父链与压缩元数据;安全目标 = 未脱敏字节零留盘;整段换,不切片);
751
+ * 缺席(`committedPrefixDiverged` 缺席)⇒ 这些行照旧,本包只换了还押着的后段(或零字节)。
752
+ */
753
+ committedUuids?: readonly string[];
747
754
  /** core 铸的事件身份(uuidv7 形);wire 未必带 ⇒ 缺席时本键不在场。 */
748
755
  eventId?: string;
749
756
  }
package/dist/seam.js CHANGED
@@ -83,7 +83,7 @@ const CHROME_ARM_TABLE = {
83
83
  // 本地转录里(b2 形上那条明文思考行已经渲过 = 已发生的行为)⇒ `required: true`,理由同上臂。
84
84
  required: true,
85
85
  duty: '整段替换:①`diverged`(never false)在场 ⇒ 自己拼 thinking stream_delta 上屏的端按 content 重渲该块;' +
86
- '②`committedPrefixDiverged`(never false)在场 ⇒ 所有端把 `uuid === committedUuid` 的那条已 committed 思考行**整块**换成 content(思考面无 idle-flush,前缀全有或全无,`committedPrefixLen` 恒 = 该行全文长度,单位 UTF-16 代码单元);' +
86
+ '②`committedPrefixDiverged`(never false)在场 ⇒ 所有端把 `committedUuids` 首行就地换成 content,其余行**删除或清空其推理正文**(宿主按自身转录结构选:有父链/压缩段依赖的转录清空优于删,保行身份;安全目标 = 未脱敏字节零留盘;整段换不切片;`committedUuid` = 末条兼容位;`committedPrefixLen` = 全部行全文长度之和,单位 UTF-16 代码单元);' +
87
87
  '本包在这一形上一个字节都不再交;③三键全缺席 ⇒ 照旧只当对账/定界;④缺席 ≠ 段没结束,按整条流判。',
88
88
  },
89
89
  wiring_manifest: {
@@ -357,6 +357,7 @@ function synthPhaseStatus(runStatus, agents) {
357
357
  * tolerates absent/permissive fields and never throws (the graceful-degrade contract lives above, in the
358
358
  * source loop; this just maps a well-formed run). Exported for the mock-parity unit cross-check. */
359
359
  export function projectWorkflowRun(run) {
360
+ const parks = readWorkflowParks(run);
360
361
  const agents = (run.agents ?? []).map((r, i) => projectAgent(r, i));
361
362
  const phases = projectPhases(run, agents);
362
363
  const ownTokens = run.stats?.tokens ?? 0;
@@ -367,6 +368,9 @@ export function projectWorkflowRun(run) {
367
368
  const state = {
368
369
  workflowRunId: run.id,
369
370
  status: coerceRunStatus(run.status),
371
+ // CC-12(0.69.1):park 两新键**进视图**(修前只出读器,端拿不到裸体 ⇒ 渲染面结构性造不出);铸法与缺席律逐字同两读器。
372
+ ...(parks !== undefined ? { parks } : {}),
373
+ ...(readWorkflowResumeAdmissionIncomplete(run) ? { resumeAdmissionIncomplete: true } : {}),
370
374
  // The authored script TEXT is not projected to the wire (types.ts:375 — service does not emit it), so leave
371
375
  // it empty: the monitor's "save script" affordance stays off (canSave = script.length > 0). Never fabricate
372
376
  // a script we do not have. scriptPath is likewise absent until the service projects it.
@@ -14,6 +14,7 @@
14
14
  * 留在 TUI 的:`computeVisibleWindow`(滚动窗口数学 = 视口概念,端专属)、glyph/color 映射、
15
15
  * 以及整棵 JSX。壳侧 shim = 那个 tsx 改成从本包 re-export 这两样,自己不再定义。
16
16
  */
17
+ import type { WorkflowParkRowView } from './workflowClient.js';
17
18
  export type AgentState = 'start' | 'progress' | 'queued' | 'done' | 'error' | 'inactive'
18
19
  /** sema 超集:这条腿耐久挂在一张审批卡上(engine core 7.10.0 #642)。见上方头注。 */
19
20
  | 'parked';
@@ -91,6 +92,15 @@ export interface WorkflowRunState {
91
92
  totalTokens: number;
92
93
  /** phases as authored; the component derives the active/clamped windows itself. */
93
94
  phases: WorkflowPhase[];
95
+ /**
96
+ * CC-12(0.69.1;core 7.18.0 #755 / server ≥7.78.0):这条 run **负责**的 park 行(自己的 + 从被 resume 的 run 继承的),
97
+ * 由 `readWorkflowParks` 铸(三键行 + never-false 第四键 `originUnconfirmed`)。🔴 缺席两义(与读器同):整键缺席 =
98
+ * 旧引擎记录 / 读不出(证不出有没有 park);`[]` = 本引擎记录且一个 park 都没有。端渲 resume 候选时按这一位,
99
+ * 不再各自 `run.parks.map(...)`(修前本视图丢了它,端结构性造不出 —— cli 接入回执 ④)。
100
+ */
101
+ parks?: readonly WorkflowParkRowView[];
102
+ /** CC-12:在场 = 这条 resume run 的准入没完成 = **不是 resume 基**(core 逐字);缺席 = 常态。never false。 */
103
+ resumeAdmissionIncomplete?: true;
94
104
  /** the 187 reads e.workflowProgress to recompute phases; the mock pre-builds them. */
95
105
  workflowProgress?: unknown;
96
106
  }
@@ -1,19 +1,3 @@
1
- /**
2
- * ⇄ B6 批搬迁(2026-07-27,设计稿 §3 B6):workflow 监视器的**状态契约**(MF-W ·
3
- * contract/MF-W-workflow-monitor.md)从 cli `src/sema/overrides/workflow-detail-dialog.tsx:118-200`
4
- * 搬入 —— 逐字,一个字段都没动。
5
- *
6
- * 🔴 **为什么类型必须先过来**:`workflowClient.projectWorkflowRun()`(同批搬入)的**产物**就是这套
7
- * 形状。类型留在一个 `.tsx` 渲染文件里,等于「库产出的东西,它的契约住在 TUI 里」——
8
- * web/桌面要消费同一份投影就得手抄一遍 shape,而手抄的那份**没有任何东西**会在它漂移时报红。
9
- * 定位公理([1832])说的「CC 皮肤形状全收本包」,指的正是这种东西。
10
- *
11
- * 🔴 **拆缝**:`workflow-detail-dialog.tsx` 本身是 Ink 渲染件(设计稿 §2 分类表「Ink 渲染」),
12
- * **留 TUI**;搬过来的只有 ① 六个契约类型 ② `agentDisplayStatus`(WPe —— 从 wire 输入词表
13
- * 派生显示态的那条**纯**规则,是本契约的消费侧半场,端端都要用)。
14
- * 留在 TUI 的:`computeVisibleWindow`(滚动窗口数学 = 视口概念,端专属)、glyph/color 映射、
15
- * 以及整棵 JSX。壳侧 shim = 那个 tsx 改成从本包 re-export 这两样,自己不再定义。
16
- */
17
1
  /**
18
2
  * WPe — agentDisplayStatus: agent.state (+ workflowActive) → display status.
19
3
  *
@@ -19,11 +19,11 @@
19
19
 
20
20
  | 项 | 值 | 真源 |
21
21
  |---|---|---|
22
- | 本包 | `@sema-agent/client-core` **0.69.0**(本批发布版 = **minor**:型面 BREAKING 仅 peer `@sema-agent/sdk` 地板 `>=8.8.0` → `>=9.4.0`,运行期零 BREAKING;提货批 = sdk 9.4.0 / core 7.18.0 / settings-schema 2.0.0 换钉 + CC-02 `thinking_segment_end`(`reasoning_end` 推理面权威替换)+ CC-05 core 7.18.0 三件(通告码 +4 / park 两新键 / `gate.disposition.classifier` 视图)+ CC-07 core 值级十件端口注入 `installCoreValuePorts` + CC-03 补件 `fetchMcpPanel`;投影口 `text_end` 进 switch、`tool_roster_delta` 有痕 dropped;§36 / 36y 三端升级必读四条)|
22
+ | 本包 | `@sema-agent/client-core` **0.69.2**(本批发布版 = patch,additive 零 BREAKING:🔴 CC-14 `thinking_segment_end` 段账按行所属引擎段记(定稿后迟交的思考行不再误登下一段 `committedUuids`)/ CC-13 `isSubFlowSegmentEnd` + `SEGMENT_END_IDENTITY_KEYS` + `droppedWhySentence` 出公面、`onDroppedFrame` 载荷 additive 带 `sentence?` / CC-11 义务措辞订正;公面 982 → 985;§38 / 38y 三条;CC-04 改随 server 7.78.1 + core 7.19.0 进 0.69.3)|
23
23
  | peer:wire 契约 | `@sema-agent/sdk` **>=8.4.0**(value-level,非 type-only;**0.60.0 抬版**,四条硬理由见 §24a 与 `scripts/run-sdk-floor-test.mjs` 的 `FLOOR` 注;上一次是 0.59.0 的 `>=8.3.0`)。🔴 支持窗同批收到 **engine ≥7.64.0**:sdk 8.4.0 与 7.63.0 及以前的 wire **不同窗** | `package.json` `peerDependencies` |
24
24
  | peer:会话词汇表 | `@sema-agent/agent-types` **>=0.2.0**(type-only,零运行时) | 同上 |
25
25
  | runtime dep | `diff` ^9.0.0(**唯一**一条;portability 门按**等值**钉死) | `package.json` `dependencies` |
26
- | 公开导出面 | **982** 个运行期符号(+ 44 个测试钩;= 0.69.0 未发布版的值(原排 0.68.3,因 peer 地板抬升改 minor)= 相对 0.68.2 **十七件 additive、零删除**:`fetchMcpPanel`(CC-03 补件,§36e)+ `readWorkflowResumeAdmissionIncomplete`(CC-05 core 7.18.0 #755)+ `installCoreValuePorts`/`hasCoreValuePorts`/`hasCoreValuePort`/`coreValuePortMisses`/`CORE_VALUE_PORT_NAMES` + 十读口(CC-07 core 值级端口,§36d);0.68.2 相对 0.68.1 十三件 additive:`SEMA_SEGMENT_ID_KEY`(CC-01 段身份键,§35f)/ `projectMcpPanel`+`mcpPanelLastLegDetail`+`projectMcpSection`(CC-03 `/mcp` 面板读视图,§35g)/ `resolveTextSegmentAuthority`/`createTextSegmentLedger`(L-318 `text_end` 权威段替换的判决单源 + print 形段账,详见 §35b/§35c)/ `memoryCaptureDeclarationField`/`MEMORY_CAPTURE_OFF`(L-316 会话级隐私声明的字面唯一铸点,§35d)/ `engineUrl`/`probeHealth`(L-61 件④ SDK 纯工具面原样转口,§35d)/ `AgentClient`/`isApprovalRequestFrameV1`/`isApprovalRevokeFrameV1`(L-61 存量清零:SDK **wire 面**原样转口,§35e)。`0.68.1`/`0.68.0` 是 **952**,那批是相对 0.67.2 **二十件 additive、🔴 两件删除(BREAKING)**:additive = `TERMINAL_CAUSE_KINDS`/`isTerminalCauseKind`/`RUN_TERMINAL_NOT_SUCCESS_STATUSES`/`RUN_TERMINAL_STATUSES`(L-247 终态词两表分源,详见 §33b)/ `SEMA_SUBAGENT_USAGE_PARTIAL_KEY`/`subagentUsageIsPartial`(L-244 子代下界位自有名,§33g)/ `ASK_UNRESOLVABLE_CAUSES`/`readAskUnresolvable`/`DURABLE_MANDATE_SOURCES`/`readDurableGateUnavailable`(core 7.17.0 #709 ①② 两只通告事实窄读器,§33c)/ `WORKFLOW_PARK_TRUTH_UNREADABLE`/`WORKFLOW_PARK_NOT_PENDING`/`WORKFLOW_PARK_BINDING_BROKEN`/`WORKFLOW_PARK_REQUIRES_RUN_STORE`/`WORKFLOW_PARK_REFUSAL_CODES`/`isWorkflowParkRefusalCode`/`readWorkflowParks`(core 7.17.0 #652 park 真相四码 + parks 三键读器,§33d)/ `ENGINE_STOP_REASONS`/`engineStopReasonToCc`(L-246 A13 停止原词映射下沉,§33g)/ `readCancelRequested`(L-230 读面,§33f);🔴 **删除两件** `TERMINAL_NOT_SUCCESS_STATUSES`/`TERMINAL_STATUSES`(随 L-247 两表分源更名成 `RUN_TERMINAL_*`,clean-cut **不留别名** —— 留一个别名等于让端可以继续按合起来的那张表读,分源就白做了,详见 §33b)。`0.67.0` 是 **934**,那批是相对 0.66.0 **七件 additive、🔴 两件删除**:additive = `CLASSIFIER_DENY_CAUSES`/`isClassifierDenyCause`/`classifierDenyCauseOf`/`classifierDenyCauseDetail`(core 7.14.0 退役 `classifierUnavailable` 之后的**拒绝面**继任读器与措辞,详见 §32b)/ `RULE_STORE_UNREADABLE_KINDS`/`isRuleStoreUnreadableKind`/`ruleStoreUnreadableDetail`(core 7.14.0 #688 C3 的规则店机制位三件,详见 §32d);🔴 **删除两件** `classifierUnavailableOf`/`classifierUnavailableDetail`(随 core 7.14.0 的 Retired keys 表整族退役,clean-cut 不留别名,详见 §32b;同批删的还有**型** `ClassifierUnavailableView`、卡位与帧位 `classifierUnavailable`、以及一条恒真的型面钉 `_askOriginWordsPin`)。`0.66.0` 是 **929**,那批是一件 additive、零删除:`readRunCostFacts`(终局成本事实的唯一读器:`costBreakdown` / `nested` 窄读 + 对账三段与两个判别位,终帧超集键与 chrome 对账臂共用它;详见 §31c);已发的 **0.65.x 十一件 additive、两件删除**:additive = `DECIDE_WORKFLOW_HOST_UNKNOWN`/`DECIDE_WORKFLOW_HOST_NOT_PARKED`/`DECIDE_WORKFLOW_REMEMBER_UNSUPPORTED`/`DECIDE_WORKFLOW_LANE_CODES`/`readDecideReceipt`/`decideAcceptedNotResolved`/`decideRefusalFromError`(B-070 `/decide` 回执读面与三码人话,详见 §30h)/ `TERMINAL_NOT_SUCCESS_STATUSES`/`isTerminalNotSuccess`/`TERMINAL_STATUSES`/`isTerminalStatus`(L-215② 终局词汇单铸谓词,详见 §30j);🔴 **删除两件** `AUTO_MODE_BREAKER_CAUSES`/`classifierBreakerOf`(熔断族随 core 7.12.0 的 Removed(BREAKING) 整只退役,clean-cut 不留别名,详见 §30k②;同批删的还有**型** `ClassifierBreakerView` 与 `ClassifierStatusView.breaker` 一位)。`0.64.2` 是 **919**,那批是一件 additive、零删除:`readDenialLimitFallback`(限额回落卡的窄读器上公面,壳侧同判据副本退役,详见 §29③);`0.64.0` 是 **918**,那批是件①+件② 十三件 additive、零删除:`CLASSIFIER_STATUS_STATES`/`classifierBreakerOf`/`classifierStatusOf`/`classifierStatusDetail`(分类器状态面,详见 §28②)/ `THINKING_FORMATS`/`THINKING_FORMAT_WIRE_TWINS`/`THINKING_DISABLE_PROBE_ORDER`/`MODEL_PROBE_VERDICTS`/`PROBE_PROMPT`/`PROBE_MAX_TOKENS`/`probeRequestBody`/`probeModelCapability`/`applyProbeToEntry`(模型思考能力探测,详见 §28①);`0.63.0` 是 **905**,那批是八件 additive、零删除:`projectServerGateKnobs`/`postureKnobSourceOf`/`postureSourceIsOperatorPinned`/`postureKnobDetail`/`POSTURE_SOURCE_WORDS`(S-178 三根 posture 旋钮读数,详见 §27b)/ `PERSISTED_RULE_BEHAVIORS`/`PERSISTED_RULE_BEHAVIOR_UNKNOWN`/`persistedRuleBehaviorOf`/`persistedRuleBehaviorLabel`/`revokeTargetFromPersistedRule`(三态规则身份,§27c)/ `engineIdentityOf`/`engineIdentityVerdict`/`engineIdentityChanged`/`engineIdentityChangedBy`/`ENGINE_IDENTITY_ANCHORS`(S-179 代际锚,§27d)/ `GATE_DENIED_BY_WORDS`/`gateDeniedByDetail`/`ASK_ORIGIN_WORDS`/`askOriginDetail`(门词汇 +2,§27e)/ `PERMISSION_RULE_ISSUE_CODES`/`RETIRED_PERMISSION_RULE_ISSUE_CODES`/`permissionRuleIssueDetail`(规则 lint 码表,§27f)/ `projectToolRoster`/`toolRosterNames`/`toolShimFromRoster`/`applyToolRosterDelta`(L-161 工具名册,§27g)/ `ENGINE_NOTICE_CODES`/`ENGINE_NOTICE_AUDIENCE`/`noticeAudienceOf`/`engineNoticeInCatalog`/`MCP_INJECTION_DROP_REASONS`/`readMcpInjectionDrop`(L-167 通告码册,§27h)/ `AUTO_MODE_UNAVAILABLE_CAUSES`/`AUTO_MODE_BREAKER_CAUSES`/`classifierUnavailableOf`/`classifierUnavailableDetail`(件⑧ 分类器不可用事实,§27h2);`0.62.0` 是 **869**,那批是五件 additive、**零删除**:`capForDisplay`(呈前消毒 + 封长的共用铸点,详见 §26⑦)/ `SEAT_SEND_MESSAGE_KEY_ORIGINS`(座位载荷逐键出身表,详见 §26③)/ `configureSubagentContentStore`+`subagentContentStoreConfig`+`SUBAGENT_CONTENT_STORE_DEFAULTS`(子代内容账本字节预算,测试钩 `__resetSubagentContentStoreConfigForTests` +1,详见 §26②);`0.61.0` 是 **864**,那批是三件 additive、**零删除**:`projectReadFacePosture`/`readFacePostureDetail`/`readFaceDisagreement`(S-167 READ 容纳面 operator 读器,详见 §25);`0.60.0` 是 **861**,那批是二十五件 additive、**零删除**:`readRunTerminal`/`runTerminalCode`/`runTerminalGateKind`/`runTerminalGateToolName`/`isReviewPark`/`REVIEW_PARK_GATE_KINDS` 六件终局因由读器 + `gateOutcomeOf`/`isDeniedGate`/`gateDeniedBy`/`gateApprover`/`isApprovalWindowExpiredGate`/`isDenialLimitAutoDeniedGate`/`isParkSlaExpiredGate`/`isHumanSettledGate` 八件门记录读器 + `projectWriteProtectionCapability`/`noteEngineCapsForWriteProtection`/`observedWriteProtection`/`forgetWriteProtectionReading`/`writeProtectionDoctorDetail`/`projectWriteProtectionPosture`/`writeProtectionPostureDetail` 七件写保护读面(测试钩 `__resetWriteProtectionReadingsForTests` +1)+ `readWireErrorCode` + 真机边界①的两件 `isGateParkedToolEnd`/`GATE_PARKED_ERROR_CODE` + 异源复审逼出的 `engineCapsGeneration`;`0.59.0` 是 **836**,那批是八件 additive:`RULE_OFFERS_ABSENCE_REASONS`/`DENIAL_LIMIT_KINDS` 两张闭词表再导出 + `engineCapValue` 能力位四态通用读口 + `projectSqlEngineCapability`/`observedSqlEngine`/`noteEngineCapsForSqlEngine`/`forgetSqlEngineReading`/`sqlEngineDoctorDetail` 五件 SQL 姿态读面(测试钩 `__resetSqlEngineReadingsForTests` +1);`0.58.0` 是 **828**,那批是三件 additive:`RULE_OFFER_MATCHES`/`RULE_OFFER_BATCH_MEMBER_KINDS`/`RULE_OFFER_UNCOVERED_REASONS` 三张闭词表再导出 + `RESUME_REFUSAL_CODES`/`RESUME_PLACEMENT_MISMATCH`/`resumeRefusalFromError`/`resumeRefusalContent` 四件文案铸点;`0.57.0` 是 **821**;`0.56.0`/`0.55.0` 是 **815**;已发 `0.54.0`(design/385 十件 additive 含 `AUTHORITY_ENVELOPE_TAGS`);`0.52.0` 是 **805**(L-69⑨ 两件);`0.51.0` 是 **803**;`0.50.0` 是 **800**(S-81 五件);`0.49.0` 是 **795**;`0.48.0` 是 **794**;npm `0.47.0` 是 **790**,`0.46.0` 是 **787**,`0.44.0` 是 **783**,`0.43.1`/`0.43.0` 是 **776**,`0.42.0` 是 **771**,`0.41.0` 是 **767**,`0.39.0` 是 **766**,`0.38.0` 是 **764**;`0.37.0` 是 **753**,见 `CHANGELOG.md`) | `scripts/public-export-baseline.json` 的 `count` / `testHookCount` —— **别手抄进别处,以该文件为准** |
26
+ | 公开导出面 | **985** 个运行期符号(+ 44 个测试钩;= 0.69.2 未发布版的值:相对 0.69.1 **三件 additive、零删除**(CC-13 `isSubFlowSegmentEnd` / `SEGMENT_END_IDENTITY_KEYS` / `droppedWhySentence`,§38);982 = 0.69.0 未发布版的值(原排 0.68.3,因 peer 地板抬升改 minor)= 相对 0.68.2 **十七件 additive、零删除**:`fetchMcpPanel`(CC-03 补件,§36e)+ `readWorkflowResumeAdmissionIncomplete`(CC-05 core 7.18.0 #755)+ `installCoreValuePorts`/`hasCoreValuePorts`/`hasCoreValuePort`/`coreValuePortMisses`/`CORE_VALUE_PORT_NAMES` + 十读口(CC-07 core 值级端口,§36d);0.68.2 相对 0.68.1 十三件 additive:`SEMA_SEGMENT_ID_KEY`(CC-01 段身份键,§35f)/ `projectMcpPanel`+`mcpPanelLastLegDetail`+`projectMcpSection`(CC-03 `/mcp` 面板读视图,§35g)/ `resolveTextSegmentAuthority`/`createTextSegmentLedger`(L-318 `text_end` 权威段替换的判决单源 + print 形段账,详见 §35b/§35c)/ `memoryCaptureDeclarationField`/`MEMORY_CAPTURE_OFF`(L-316 会话级隐私声明的字面唯一铸点,§35d)/ `engineUrl`/`probeHealth`(L-61 件④ SDK 纯工具面原样转口,§35d)/ `AgentClient`/`isApprovalRequestFrameV1`/`isApprovalRevokeFrameV1`(L-61 存量清零:SDK **wire 面**原样转口,§35e)。`0.68.1`/`0.68.0` 是 **952**,那批是相对 0.67.2 **二十件 additive、🔴 两件删除(BREAKING)**:additive = `TERMINAL_CAUSE_KINDS`/`isTerminalCauseKind`/`RUN_TERMINAL_NOT_SUCCESS_STATUSES`/`RUN_TERMINAL_STATUSES`(L-247 终态词两表分源,详见 §33b)/ `SEMA_SUBAGENT_USAGE_PARTIAL_KEY`/`subagentUsageIsPartial`(L-244 子代下界位自有名,§33g)/ `ASK_UNRESOLVABLE_CAUSES`/`readAskUnresolvable`/`DURABLE_MANDATE_SOURCES`/`readDurableGateUnavailable`(core 7.17.0 #709 ①② 两只通告事实窄读器,§33c)/ `WORKFLOW_PARK_TRUTH_UNREADABLE`/`WORKFLOW_PARK_NOT_PENDING`/`WORKFLOW_PARK_BINDING_BROKEN`/`WORKFLOW_PARK_REQUIRES_RUN_STORE`/`WORKFLOW_PARK_REFUSAL_CODES`/`isWorkflowParkRefusalCode`/`readWorkflowParks`(core 7.17.0 #652 park 真相四码 + parks 三键读器,§33d)/ `ENGINE_STOP_REASONS`/`engineStopReasonToCc`(L-246 A13 停止原词映射下沉,§33g)/ `readCancelRequested`(L-230 读面,§33f);🔴 **删除两件** `TERMINAL_NOT_SUCCESS_STATUSES`/`TERMINAL_STATUSES`(随 L-247 两表分源更名成 `RUN_TERMINAL_*`,clean-cut **不留别名** —— 留一个别名等于让端可以继续按合起来的那张表读,分源就白做了,详见 §33b)。`0.67.0` 是 **934**,那批是相对 0.66.0 **七件 additive、🔴 两件删除**:additive = `CLASSIFIER_DENY_CAUSES`/`isClassifierDenyCause`/`classifierDenyCauseOf`/`classifierDenyCauseDetail`(core 7.14.0 退役 `classifierUnavailable` 之后的**拒绝面**继任读器与措辞,详见 §32b)/ `RULE_STORE_UNREADABLE_KINDS`/`isRuleStoreUnreadableKind`/`ruleStoreUnreadableDetail`(core 7.14.0 #688 C3 的规则店机制位三件,详见 §32d);🔴 **删除两件** `classifierUnavailableOf`/`classifierUnavailableDetail`(随 core 7.14.0 的 Retired keys 表整族退役,clean-cut 不留别名,详见 §32b;同批删的还有**型** `ClassifierUnavailableView`、卡位与帧位 `classifierUnavailable`、以及一条恒真的型面钉 `_askOriginWordsPin`)。`0.66.0` 是 **929**,那批是一件 additive、零删除:`readRunCostFacts`(终局成本事实的唯一读器:`costBreakdown` / `nested` 窄读 + 对账三段与两个判别位,终帧超集键与 chrome 对账臂共用它;详见 §31c);已发的 **0.65.x 十一件 additive、两件删除**:additive = `DECIDE_WORKFLOW_HOST_UNKNOWN`/`DECIDE_WORKFLOW_HOST_NOT_PARKED`/`DECIDE_WORKFLOW_REMEMBER_UNSUPPORTED`/`DECIDE_WORKFLOW_LANE_CODES`/`readDecideReceipt`/`decideAcceptedNotResolved`/`decideRefusalFromError`(B-070 `/decide` 回执读面与三码人话,详见 §30h)/ `TERMINAL_NOT_SUCCESS_STATUSES`/`isTerminalNotSuccess`/`TERMINAL_STATUSES`/`isTerminalStatus`(L-215② 终局词汇单铸谓词,详见 §30j);🔴 **删除两件** `AUTO_MODE_BREAKER_CAUSES`/`classifierBreakerOf`(熔断族随 core 7.12.0 的 Removed(BREAKING) 整只退役,clean-cut 不留别名,详见 §30k②;同批删的还有**型** `ClassifierBreakerView` 与 `ClassifierStatusView.breaker` 一位)。`0.64.2` 是 **919**,那批是一件 additive、零删除:`readDenialLimitFallback`(限额回落卡的窄读器上公面,壳侧同判据副本退役,详见 §29③);`0.64.0` 是 **918**,那批是件①+件② 十三件 additive、零删除:`CLASSIFIER_STATUS_STATES`/`classifierBreakerOf`/`classifierStatusOf`/`classifierStatusDetail`(分类器状态面,详见 §28②)/ `THINKING_FORMATS`/`THINKING_FORMAT_WIRE_TWINS`/`THINKING_DISABLE_PROBE_ORDER`/`MODEL_PROBE_VERDICTS`/`PROBE_PROMPT`/`PROBE_MAX_TOKENS`/`probeRequestBody`/`probeModelCapability`/`applyProbeToEntry`(模型思考能力探测,详见 §28①);`0.63.0` 是 **905**,那批是八件 additive、零删除:`projectServerGateKnobs`/`postureKnobSourceOf`/`postureSourceIsOperatorPinned`/`postureKnobDetail`/`POSTURE_SOURCE_WORDS`(S-178 三根 posture 旋钮读数,详见 §27b)/ `PERSISTED_RULE_BEHAVIORS`/`PERSISTED_RULE_BEHAVIOR_UNKNOWN`/`persistedRuleBehaviorOf`/`persistedRuleBehaviorLabel`/`revokeTargetFromPersistedRule`(三态规则身份,§27c)/ `engineIdentityOf`/`engineIdentityVerdict`/`engineIdentityChanged`/`engineIdentityChangedBy`/`ENGINE_IDENTITY_ANCHORS`(S-179 代际锚,§27d)/ `GATE_DENIED_BY_WORDS`/`gateDeniedByDetail`/`ASK_ORIGIN_WORDS`/`askOriginDetail`(门词汇 +2,§27e)/ `PERMISSION_RULE_ISSUE_CODES`/`RETIRED_PERMISSION_RULE_ISSUE_CODES`/`permissionRuleIssueDetail`(规则 lint 码表,§27f)/ `projectToolRoster`/`toolRosterNames`/`toolShimFromRoster`/`applyToolRosterDelta`(L-161 工具名册,§27g)/ `ENGINE_NOTICE_CODES`/`ENGINE_NOTICE_AUDIENCE`/`noticeAudienceOf`/`engineNoticeInCatalog`/`MCP_INJECTION_DROP_REASONS`/`readMcpInjectionDrop`(L-167 通告码册,§27h)/ `AUTO_MODE_UNAVAILABLE_CAUSES`/`AUTO_MODE_BREAKER_CAUSES`/`classifierUnavailableOf`/`classifierUnavailableDetail`(件⑧ 分类器不可用事实,§27h2);`0.62.0` 是 **869**,那批是五件 additive、**零删除**:`capForDisplay`(呈前消毒 + 封长的共用铸点,详见 §26⑦)/ `SEAT_SEND_MESSAGE_KEY_ORIGINS`(座位载荷逐键出身表,详见 §26③)/ `configureSubagentContentStore`+`subagentContentStoreConfig`+`SUBAGENT_CONTENT_STORE_DEFAULTS`(子代内容账本字节预算,测试钩 `__resetSubagentContentStoreConfigForTests` +1,详见 §26②);`0.61.0` 是 **864**,那批是三件 additive、**零删除**:`projectReadFacePosture`/`readFacePostureDetail`/`readFaceDisagreement`(S-167 READ 容纳面 operator 读器,详见 §25);`0.60.0` 是 **861**,那批是二十五件 additive、**零删除**:`readRunTerminal`/`runTerminalCode`/`runTerminalGateKind`/`runTerminalGateToolName`/`isReviewPark`/`REVIEW_PARK_GATE_KINDS` 六件终局因由读器 + `gateOutcomeOf`/`isDeniedGate`/`gateDeniedBy`/`gateApprover`/`isApprovalWindowExpiredGate`/`isDenialLimitAutoDeniedGate`/`isParkSlaExpiredGate`/`isHumanSettledGate` 八件门记录读器 + `projectWriteProtectionCapability`/`noteEngineCapsForWriteProtection`/`observedWriteProtection`/`forgetWriteProtectionReading`/`writeProtectionDoctorDetail`/`projectWriteProtectionPosture`/`writeProtectionPostureDetail` 七件写保护读面(测试钩 `__resetWriteProtectionReadingsForTests` +1)+ `readWireErrorCode` + 真机边界①的两件 `isGateParkedToolEnd`/`GATE_PARKED_ERROR_CODE` + 异源复审逼出的 `engineCapsGeneration`;`0.59.0` 是 **836**,那批是八件 additive:`RULE_OFFERS_ABSENCE_REASONS`/`DENIAL_LIMIT_KINDS` 两张闭词表再导出 + `engineCapValue` 能力位四态通用读口 + `projectSqlEngineCapability`/`observedSqlEngine`/`noteEngineCapsForSqlEngine`/`forgetSqlEngineReading`/`sqlEngineDoctorDetail` 五件 SQL 姿态读面(测试钩 `__resetSqlEngineReadingsForTests` +1);`0.58.0` 是 **828**,那批是三件 additive:`RULE_OFFER_MATCHES`/`RULE_OFFER_BATCH_MEMBER_KINDS`/`RULE_OFFER_UNCOVERED_REASONS` 三张闭词表再导出 + `RESUME_REFUSAL_CODES`/`RESUME_PLACEMENT_MISMATCH`/`resumeRefusalFromError`/`resumeRefusalContent` 四件文案铸点;`0.57.0` 是 **821**;`0.56.0`/`0.55.0` 是 **815**;已发 `0.54.0`(design/385 十件 additive 含 `AUTHORITY_ENVELOPE_TAGS`);`0.52.0` 是 **805**(L-69⑨ 两件);`0.51.0` 是 **803**;`0.50.0` 是 **800**(S-81 五件);`0.49.0` 是 **795**;`0.48.0` 是 **794**;npm `0.47.0` 是 **790**,`0.46.0` 是 **787**,`0.44.0` 是 **783**,`0.43.1`/`0.43.0` 是 **776**,`0.42.0` 是 **771**,`0.41.0` 是 **767**,`0.39.0` 是 **766**,`0.38.0` 是 **764**;`0.37.0` 是 **753**,见 `CHANGELOG.md`) | `scripts/public-export-baseline.json` 的 `count` / `testHookCount` —— **别手抄进别处,以该文件为准** |
27
27
  | 常驻门 | 以 `scripts/gates-manifest.json` 的 `suites` 长度为准(**本档不抄这个数**) | `scripts/gates-manifest.json`;`npm test` 的名单等值门与它逐名对账 |
28
28
  | 沿革档 | 0.29.0 起建 `CHANGELOG.md`;更早批次记账在 `src/index.ts` 文件头 + `docs/REFACTOR-LEDGER.md` | — |
29
29
 
@@ -111,7 +111,7 @@ SDK 核对「本包 import 的每个值级符号仍然导出」「`TaskStats.cos
111
111
 
112
112
  ## §2 公共导出面地图(按域)
113
113
 
114
- > 全集真源 = `scripts/public-export-baseline.json` 的 `names`(**982** 项)。
114
+ > 全集真源 = `scripts/public-export-baseline.json` 的 `names`(**985** 项)。
115
115
  > 本节**不逐名抄**,只给「域 → 承重导出 → 用途 → 实现锚」。承重导出 = 一个端为了让这个域干活
116
116
  > **必须**直接调到的那几个符号;其余是它们的类型、变体与辅助位。
117
117
  > 单一入口:`import { … } from '@sema-agent/client-core'`(`exports` 只有 `.` 一个;
@@ -135,7 +135,7 @@ SDK 核对「本包 import 的每个值级符号仍然导出」「`TaskStats.cos
135
135
  `WorkflowsGateUnknownDenial` 四形**不在**基线里,`src/selfOrchestrationDenial.ts` 对基线贡献
136
136
  **4** 项运行期导出(三个函数 + `SELF_ORCHESTRATION_RETRY_WITHOUT`)。
137
137
 
138
- 982 项的内部构成(帮助端估读表大小):**298** 项是 `SCREAMING_SNAKE` 常量数据表/词汇表
138
+ 985 项的内部构成(帮助端估读表大小;0.69.2 起 +3:常量 1 / 函数 2):**299** 项是 `SCREAMING_SNAKE` 常量数据表/词汇表
139
139
  (矩阵、键集、env 名、锚串)而非可调用物;**6** 项是 PascalCase 运行期值
140
140
  (`ControlRouter` / `ControlSafetyError` / `HitlBridge` / `HitlSafetyError` / `DecideTransportRetryExhaustedError`);
141
141
  **41** 项是 `*For(sessionKey, …)` 的 per-session 变体(§6;其中 `engineNamespaceKeyFor` 是命名巧合 —— 参数是 baseUrl 不是 sessionKey,见域 14)。
@@ -144,7 +144,7 @@ SDK 核对「本包 import 的每个值级符号仍然导出」「`TaskStats.cos
144
144
 
145
145
  | # | 域 | 名数 | 承重导出 | 用途 | 实现锚 |
146
146
  |---|---|---|---|---|---|
147
- | 1 | **适配内核(下行主链)** | 52 | `adapt` · `createWireToCcAdapter` · `runStream` · `eventToSdkMessage` · `terminalToSdkResult` · `SEMA_SEGMENT_ID_KEY`(0.68.2 CC-01:committed assistant 文本行顶层段身份键的唯一字面量出处,壳只读)· `projectMcpSection`(0.68.2 CC-03:`wiring_manifest.mcp[]` 与 `/mcp` 面板 `lastLegMcp.mcp[]` 共用的同一只条目读器)· `readRunCostFacts`(0.66.0:终局成本事实的**唯一读器** —— `TaskStats.costBreakdown` / `nested` 逐键窄读 + 对账三段(own/nested/compaction)与两个「没定价」判别位;终帧的两个超集键与 chrome 臂 `run_cost_reconciled` **共用它**,两面不会各算各的,详见 §31c)· `TERMINAL_CAUSE_KINDS`/`isTerminalCauseKind`(0.68.0 / L-247:**core 终局因由**闭集,四词 `completed|failed|blocked|paused`)· `RUN_TERMINAL_NOT_SUCCESS_STATUSES`/`isTerminalNotSuccess`/`RUN_TERMINAL_STATUSES`/`isTerminalStatus`(L-215② 终局词汇的**单铸谓词**,0.68.0 随 L-247 **两表分源**更名 —— `killed` 属 server run 行状态面、`paused` 属 core 因由面,合成一张会让两边加词时都读不出该改哪儿;`blocked` 是 agent 自报终态,`suspended`/`needs_review` 刻意不在表里,详见 §33b / §30j)· `SEMA_SUBAGENT_USAGE_PARTIAL_KEY`/`subagentUsageIsPartial`(0.68.0 / L-244:子代用量下界位的**自有名**与唯一判据,与终帧 `_sema_usage_lower_bound` 分名,详见 §33g) · `turnUsageToModelUsage` · `isRunStreamActive` · `ADAPTER_DIVERGENCES` · `readRunTerminal` / `runTerminalCode` / `runTerminalGateKind` / `runTerminalGateToolName` / `isReviewPark` / `REVIEW_PARK_GATE_KINDS`(0.60.0 / engine ≥7.64.0:一次 run **怎么结束的**唯一读器 —— 终局从八个平面键换成一条带标因由 `terminal`,四臂 + `unknown` 防御臂 + `plane` 两代字节标;🔴 不认识的终态词绝不折成成功、本包不提供「因由→五词 status」的兼容投影,详见 §24b)| 引擎 SSE `AgentEvent` → 端要渲的**双面输出**:transcript(`SDKMessage`)+ chrome(瞬态 `ChromeEvent`)。**本包存在的理由** | `src/adapt.ts`、`src/adapt/{arms,wireShapes,panelTasks}.ts`(经 `adapt.ts` 再导出)、`src/adapter/runStream.ts`、`src/adapter/downstream/*`、`src/adapter/types.ts`、`src/runTerminal.ts` |
147
+ | 1 | **适配内核(下行主链)** | 55 | `isSubFlowSegmentEnd` · `SEGMENT_END_IDENTITY_KEYS` · `droppedWhySentence`(0.69.2 CC-13:三件出公面,§38)· `adapt` · `createWireToCcAdapter` · `runStream` · `eventToSdkMessage` · `terminalToSdkResult` · `SEMA_SEGMENT_ID_KEY`(0.68.2 CC-01:committed assistant 文本行顶层段身份键的唯一字面量出处,壳只读)· `projectMcpSection`(0.68.2 CC-03:`wiring_manifest.mcp[]` 与 `/mcp` 面板 `lastLegMcp.mcp[]` 共用的同一只条目读器)· `readRunCostFacts`(0.66.0:终局成本事实的**唯一读器** —— `TaskStats.costBreakdown` / `nested` 逐键窄读 + 对账三段(own/nested/compaction)与两个「没定价」判别位;终帧的两个超集键与 chrome 臂 `run_cost_reconciled` **共用它**,两面不会各算各的,详见 §31c)· `TERMINAL_CAUSE_KINDS`/`isTerminalCauseKind`(0.68.0 / L-247:**core 终局因由**闭集,四词 `completed|failed|blocked|paused`)· `RUN_TERMINAL_NOT_SUCCESS_STATUSES`/`isTerminalNotSuccess`/`RUN_TERMINAL_STATUSES`/`isTerminalStatus`(L-215② 终局词汇的**单铸谓词**,0.68.0 随 L-247 **两表分源**更名 —— `killed` 属 server run 行状态面、`paused` 属 core 因由面,合成一张会让两边加词时都读不出该改哪儿;`blocked` 是 agent 自报终态,`suspended`/`needs_review` 刻意不在表里,详见 §33b / §30j)· `SEMA_SUBAGENT_USAGE_PARTIAL_KEY`/`subagentUsageIsPartial`(0.68.0 / L-244:子代用量下界位的**自有名**与唯一判据,与终帧 `_sema_usage_lower_bound` 分名,详见 §33g) · `turnUsageToModelUsage` · `isRunStreamActive` · `ADAPTER_DIVERGENCES` · `readRunTerminal` / `runTerminalCode` / `runTerminalGateKind` / `runTerminalGateToolName` / `isReviewPark` / `REVIEW_PARK_GATE_KINDS`(0.60.0 / engine ≥7.64.0:一次 run **怎么结束的**唯一读器 —— 终局从八个平面键换成一条带标因由 `terminal`,四臂 + `unknown` 防御臂 + `plane` 两代字节标;🔴 不认识的终态词绝不折成成功、本包不提供「因由→五词 status」的兼容投影,详见 §24b)| 引擎 SSE `AgentEvent` → 端要渲的**双面输出**:transcript(`SDKMessage`)+ chrome(瞬态 `ChromeEvent`)。**本包存在的理由** | `src/adapt.ts`、`src/adapt/{arms,wireShapes,panelTasks}.ts`(经 `adapt.ts` 再导出)、`src/adapter/runStream.ts`、`src/adapter/downstream/*`、`src/adapter/types.ts`、`src/runTerminal.ts` |
148
148
  | 2 | **seam 公共契约** | 2(其余为 type-only) | `CHROME_ARMS` · `deriveTranscriptId` | 公共词汇 + **id 确定性不变量**(同一条流重放 ⇒ 同一串 id)。`CHROME_ARMS` = 端「我要消费哪些 chrome 臂」的对照清单 | `src/seam.ts` |
149
149
  | 3 | **HITL 决断卡链**(§4/§5 主战场) | 167 | `makeHitlCanUseTool` · `HitlBridge` · `findPendingForTask` · `readDecideReceipt`/`decideAcceptedNotResolved`/`decideRefusalFromError`(B-070:`/decide` 200 = **投递受理**不是「门已解决」;`executionOutcome` 缺席 = 未知,详见 §30h) · `HitlSafetyError` · `bridgeAskUserQuestionGates` · `surfaceToolApprovalFrameAndRespond` / `surfaceFsApprovalAndDecide` · `readToolApprovalRespondAck` · `installApprovalCardPort(For)` · `installHitlHostSurface(For)` · `armPlanReviewApproval` · `reopenPlanReviewCard` · `decidePlanReview` · `startApprovalsFeed` · `pendingRowIsOwnedByThisSession` · `approvalCallKey`/`liveFrameCallKey`/`planReviewQuestionId` · `registerArmedGateFor`/`wasGateArmedFor`/`clearArmedGateFor` · `waitForGateArmed(For)`/`onGateArmed(For)`/`gateArmedWaitMs`(#244 F1 呈现回执事件源) · `planReviewArmedKey(For)`/`notePlanReviewAnswered(For)`/`notePlanReviewAnsweredIfDecisive(For)`(A-024.4 plan 呈现分代) · `toolEndOutputText` · `isAskTool` · `waitForParkRowBirth` · `classifyAskParkRows` / `askParkRowArm` / `classifyAskParkChainFailure` · `readDecisionNoteAudit` / `decisionNoteAuditLine` · `resumeRunningOptions` / `resumeChoiceFromLabels` · `persistedRulesLaneAvailable`/`persistedRulesGovernanceAvailable` · `classifyRulesFailure` · `listAllPersistedRules` · `classifySkippedReason` · `readRulePersistOutcome`(#244 F2:persist-ack 读口与 `readToolApprovalRespondAck` 合成一处) · `parseLocalAllowRule`(durable 腿本地落规则窄化骨架,谓词经 `LocalAllowRuleDeps` 注入) · `readToolApprovalRespondRefusal`(#225 件5,0.42.0:respond 抛错的结构化原文读口 —— 三位各自防御读、各自缺席不铸、**三位皆缺席时整只返 `undefined`**;原样交还零加工,UNTRUSTED-for-display)· `installEditedRuleTextPrechecker` / `hasEditedRuleTextPrechecker` / `precheckEditedRuleText`([5076] 转出口,0.42.0:core 5.57.0 `precheckEditedRuleText` 的**端口注入形** —— 类型面 + 注入口 + 诚实缺席读口。🔴 **不是** value 级 re-export,理由见 §7 缺口 **P-34**;未装 ⇒ 返 `undefined`,绝不编一个 `{ok:true}`)· `surfaceRuleArmNotSent` / `RULE_NOT_SENT_WARN_TEXT` + `surfaceRuleArmRejected` / `RULE_NOT_SENT_REJECTED_WARN_TEXT`(#334,0.43.0:人在卡上按下的「不再询问」被整条丢弃时的诚实告知——**两条刻意分开**:前者=**引擎能力位未确认**(换台引擎/等探测就好),后者=**这次选择没过包内表核/互斥核**(表外文本/坏下标/两臂同场,换引擎也不会变) —— 编辑臂 `respondFreeFormRules` 与批臂 `respondBatchRuleOffers` 两条同形存量共用一条,与 `surfaceRememberNotApplied` 同族纪律:决断照送、只是规则没存,静默丢掉用户明确意图 = 让人以为功能坏了)· `projectCrashConverged`(L-38,0.49.0:`/v1/approvals` additive 键 `crashConverged` 的分桶投影 —— 崩溃收敛的孤儿审批读面,**缺席 ≠ 空数组**、分桶恰一个合取、坏行丢弃并计数,详见 §12;同批把 `ApprovalsResourceLike.list()` 的返回位 additive 放宽成 `ApprovalsListEnvelope`,老形 `{pending}` 仍可赋值)· `readRuleOffers` / `readRuleOfferSupply`(L-103/[C228],0.57.0 additive 导出:「不再询问」候选两代 wire 键的窄读器**公面化** —— 此前只经卡端口出包,不走卡端口架构的宿主只能自己重铸一把,而它承载的是兑付安全判据(原始下标不前移/逐条丢坏/闭集 kind/两代取舍序),详见 §20)· `RULE_OFFERS_ABSENCE_REASONS` / `DENIAL_LIMIT_KINDS`(0.59.0 / sdk 8.3.0 提货:`ruleOffersAbsence` 三词与 `denialLimitFallback.limit` 二词的**运行期**闭词表再导出 —— 与 0.58.0 那三张同一条纪律:端拿它做判定别再手抄字面量。⚠️ **本包自己不拿这两张表做窄读判定**(两处都按开集透传,词表属主是 core、server 已按闭集拒过),再导出只是给端的 `switch` 一份可数的表,端仍必须带 `default` 臂,详见 §23a)· `DecideTransportRetryExhaustedError`(Inkglow-1085 P0a:decide 出站瞬断重试耗尽的 typed 判别 —— HitlBridge 内建单次退避重试,耗尽走重呈臂不判死 turn;端一般只消费行为,不需要 instanceof) · `gateOutcomeOf` / `isDeniedGate` / `gateDeniedBy` / `gateApprover` / `isApprovalWindowExpiredGate` / `isDenialLimitAutoDeniedGate` / `isParkSlaExpiredGate` / `isHumanSettledGate`(0.60.0 / engine ≥7.64.0:`tool_end.gate` 整条门记录的窄读器 + 判定谓词 —— 取代退役的四个正交词 `settledBy`/`resolution`/`autoDenied`/`approver`。🔴 **判定归包**:「是不是审批窗自己走完的拒绝」旧形上是端各自拼的两键合取,而三条窗词现在分得开、合并读回去等于把刚修好的判别力扔掉;⚠️ 公面型一律带 `View` 后缀 —— 本文件的 `GateOutcome`(决断腿结局)与 sdk 的 `GateOutcome`(引擎门记录)**同名不同义**,详见 §24c)· `isGateParkedToolEnd`(「这次调用没在这里结算」的**第二形**谓词:gate 缺席 + `errorCode:"gate.parked"`;端判这件事必须与 `isApprovalWindowExpiredGate` **两形合读**) · `readWireErrorCode`(B-037 候包件②:decide/取件失败上引擎 wire 机器码的单源读口,结构视图读不 `instanceof`;与本包铸的 `code` 是两个命名空间,详见 §24g)| suspended→decide→resume 环。🔴 **D-1 两元组 verbatim 回显**是字节级断言的安全不变量,端**不许重实现它的任何一段**。🔴 键空间边界(web [C1] d3 拦截):`gateIdentity` 四常量两函数只覆盖 HITL questionId/callKey 空间;seat 的 `TOOL_PERMISSION_REQUEST_ID_DOMAINS`(`plan:` 等)是另一键空间,**两者绝不合并**(合并=座位校验器静默拒全部 plan-review 卡) | `src/hitl/hitlBridge.ts`、`toolApprovalWire.ts`、`askGateWire.ts`、`planReviewWire.ts`、`hitlHostSurface.ts`、`gateIdentity.ts`、`armedGateRegistry.ts`、`parkOwnership.ts`、`parkResolver.ts`、`approvalsFeed.ts`、`frameRouter.ts`(**只挑名导出** `toolEndOutputText`/`ENGINE_ABORT_TOOL_RESULT`/`isAskTool`/`HITL_REJECT_MESSAGE`/`HITL_INTERRUPT_MESSAGE_FOR_TOOL_USE`)、`parkRowBirthWait.ts`、`approvalDecisionNoteAudit.ts`、`askParkRowRouting.ts`、`resumeRunningCard.ts`(#265 上收的判定层)、`persistedRulesWire.ts`、`localAllowRule.ts`(#244 F2 规则侧)、`crashConverged.ts`(L-38 崩溃收敛读面)、`src/gateOutcome.ts` |
150
150
  | 4 | **子代 wire + 面板侧信道台账** | 87 | `tailEngineSubagent` · `installSubagentActivitySink` · `installSubagentTailMetaSink`(#280 件2:tail meta 帧发布口,`contentFrames` 判别位载体)· `stopEngineTask` + `classifyTaskStopConflict` · `fetchEngineSubagentReport` · `steerEngineSubagent`(0.32.0 未发布 #280 件A:additive 第三参 `childTaskId` —— 端有行上下文时**应当**传,传了就走「台账优先 / 缺席即诚实缺席 + `noteBgOwnerAbsence` 留痕」的 Q3 口径,与 tail·taskOutput·subagentOutput 三腿同姿势、与孪生 resume 腿共用同一个 `resolveOwnerRunId` 判据;**不传**则逐字维持旧行为=回落在飞 run)· `resumeSettledSubagent` + `resolveSubagentResumeContext` + `resolveOwnerRunId` + `classifySubagentResumeFailure` + `subagentResumeAvailable`(#242 批 2 A-028.7:resume 判定半场上收,与 steer 孪生同居;取址三态 = 台账有行用行值 / 指名了行但台账缺席则**诚实缺席绝不回落在飞 run** / 没指名行才回落。出路文案归端)· `recordSubagentOwnerFromProgress` + `getBgParentRunOwner`(A-028.6:「子代 → 宿主 run」**单表**,宿主 run 必须由持 stream-local 值的调用方显式传入,包内绝不从 `activeEngineRunId()` 推断)· `noteBgOwnerAbsence`(#242 批 3 [4000] Q3=B:tail/taskOutput·taskStop/subagentOutput 三腿台账缺席即诚实缺席**绝不回落在飞 run**,缺席 warn 留痕每 (腿,taskId) 一条)· `clearBgTerminalFacts`(#242 批 3 扫码修:复活=新周期,旧周期终态事实作废——fleetLedger 复活两腿按尾段清账,factsAccepted 方向核不再拿上周期终态当先例)· `auditRetainWithoutWake`([4000] Q5:引擎宣示 `subagentResume` + 本端在付 `retainSubagentSessions` + 端未实现 `wakeSubagent` ⇒ 响亮一条;`CLIENT_VERBS.wakeSubagent` 维持 fail-soft)· `subscribeSubagentContent` · `subscribeEngineAgentPanel` · `publishQuestionFrame` / `respondToQuestion` · `configureSubagentContentStore` / `subagentContentStoreConfig` / `SUBAGENT_CONTENT_STORE_DEFAULTS`(0.62.0:子代内容账本的**字节预算** —— 此前只有条数帽而单条无上限,一条 base64 工具结果就能让一个子代槽吃到十几 MB;越限从最早的内容开始丢并留一条可渲染的 `truncated` 记录,总帽淘汰整条最久未用的账本。配置面 fail-loud,详见 §26②)| 驱动与观测委派子代;经 module 级台账喂活体 agent/task 面板。全部**能力位 gate**(§5b) | `src/subagent/*.ts`、`src/subagentContentStore.ts`、`src/engineAgentPanelStore.ts`、`src/engineInlineTaskStats.ts`、`src/engineToolLabelStore.ts`、`src/liveQuestionStore.ts` |
@@ -7882,7 +7882,7 @@ import { resolveAutonomousLoopPrompt, loosenReasons, combinePolicies, createAllo
7882
7882
  installCoreValuePorts({ resolveAutonomousLoopPrompt, loosenReasons, combinePolicies, createAllowDenyPolicy, MCP_NAMESPACE, RETIRED_TOOL_NAMES,
7883
7883
  protocolOf, ruleToolGrammarOf, validatePermissionRules, DISCUSSION_WORKFLOW_NAME })
7884
7884
  ```
7885
- `coreValuePortMisses()` 给 `/doctor` 一行(全装 ⇒ `[]`);`hasCoreValuePorts()` 只答「装没装」,**不判部署形态**。
7885
+ `coreValuePortMisses()` 给 `/doctor` 一行(全装 ⇒ `[]`);`hasCoreValuePorts()` 只答「装没装」,**不判部署形态**。🔴 **部分装载时两口分歧是刻意的**:`hasCoreValuePorts()` 答「整袋在不在」(装了任意一件即 `true`),`coreValuePortMisses()` 答「哪几件缺」(逐件);要判某一件用 `hasCoreValuePort(name)`,别拿整袋位推断单件。**签名口径**:十件签名按 core **7.18.0** `dist/**.d.ts` 写,7.17.x 上同形(这十件在 7.17→7.18 零签名变更);装配根装的是端自己那一版 core 的实现。
7886
7886
 
7887
7887
  **判据**:`run-core-value-ports-test.mjs`(52 格;源码剥注释零 core import 直证)。
7888
7888
 
@@ -7917,3 +7917,69 @@ installCoreValuePorts({ resolveAutonomousLoopPrompt, loosenReasons, combinePolic
7917
7917
  4. **park 两新键必须逐字带**(渲 resume 候选 / park 行的端):`originUnconfirmed` 在场禁当已确认读;`resumeAdmissionIncomplete` 在场 = 不是 resume 基 —— 丢任一 = 已拒记录变回可 resume。
7918
7918
 
7919
7919
  **包侧缺口:** ① `tool_progress` 投影候 DV-741(36z #8);② `tool_disclosure` / `sections` / `hooks`·`lsp` 与 L-315 同批(36z #9);③ `WiringManifestMcpEntry` 同名影子收敛(36z #11,0.70);④ CC-04 技能帽退役随 server 7.78.1 进 0.69.1(36z #14);⑤ 中途翻转 verb 处置表 / 跨消息分段根治(§35 包侧缺口 ③④ 不变;① 面板体传输口已由 36z #18 收口)。
7920
+
7921
+ ---
7922
+
7923
+ ## §37 🆕 0.69.1 收尾批(CC-09 runStream 去重留痕 / CC-04 技能帽退役候 server 7.78.1)
7924
+
7925
+ ### 37a. 本节速览
7926
+
7927
+ | 件 | 一句话 | 端要做什么 |
7928
+ |---|---|---|
7929
+ | S-1 🔴 **已知边界** | **`-p` / runStream 链在 server <7.78.1 上收不到 `reasoning_end`**:server 7.77.0 给它的 SSE `id:` 复用该段首枚 `reasoning_delta` 的 id,`runStream` 的 durable seq 去重把它当重放丢;0.69.1 起**丢了必留痕**(`ctx.onDroppedFrame({ type:'reasoning_end', why:'duplicate_seq' })`)。**交互车道 `adapt()` 不经这只去重**,`thinking_segment_end` 照到 | 装 `onDroppedFrame` 的端把 `duplicate_seq` 当运维面留痕渲(不是用户面);`-p` 端在 server <7.78.1 上**如实披露**「推理面权威段不到」,不许壳侧绕过去重(去重律是契约) |
7930
+ | S-2 🔴 **安全面** | **段末帧子流断闸改按身份三键**(CC-10):`text_end` / `reasoning_end` 内部臂新带 `sourceTaskId?` / `bgAgentId?`(与 `parentToolCallId` 同律原样透传),两臂任一在场即断;只带 `sourceTaskId` 的子代段末帧不再被当 leader 投上来 | 自建管线的端(print 车道 / 座位层)断子流按**三键**判,别只看 `parentToolCallId` |
7931
+ | S-3 🔴 **安全面** | **跨工具卡的推理段归属交全**(CC-11):`thinking_segment_end` 新带 `committedUuids[]`(段内全部已提交思考行,按序;`committedUuid` = 末条兼容位);`committedPrefixLen` = 全部之和;新增 (b1) 形(前缀对得上 ⇒ 只换还押着的后段) | **宿主义务改口**:`committedPrefixDiverged` ⇒ `committedUuids` 首行就地换成 `content`,其余行**删除或清空其推理正文**(宿主按自身转录结构选;不再只换 `committedUuid` 那一条);缺席 ⇒ 行照旧 |
7932
+ | S-4 | **park 两新键进 `WorkflowRunState`**(CC-12):`parks?` / `resumeAdmissionIncomplete?`(additive,由两读器铸) | 渲 resume 候选 / park 行的端改读视图上这两位;cli 的「到货锚」可翻 consumed |
7933
+ | S-5 | 文档批(回执 ③⑤⑥):`mcpPanelLastLegDetail` 是**五句**(源码 jsdoc 已订正);`SessionPermissionRules` 进 `sdkWireTransit` 型面转口;§36d 两口分歧句 + 签名口径句已补;§35z #15「候 sdk 9.5」**作废** —— 已由 §36z #18(`fetchMcpPanel`)收口 | 零动作 |
7934
+ | S-6 | CC-04 `SKILL_CAPS.items` 退役改预算式 | **不在本版**:改随 server 7.78.1 + core 7.18.1 进 **0.69.2**(§38) |
7935
+
7936
+ ### 37z. 逐键处置表(0.69.1)
7937
+
7938
+ | # | 键 / 面 | 处置 | 坐标 / 理由 |
7939
+ |---|---|---|---|
7940
+ | 1 | runStream 对 re-seen durable seq 的裸 `continue` | **consumed(留痕)** | `src/adapter/runStream.ts` 的 `#reportDroppedFrame('duplicate_seq', …)` + `#DROPPED_WHY_SENTENCE` 加行;门 TSA U 段 |
7941
+ | 2 | server 7.77.0 `reasoning_end` id 复用 | **declined(归口 server 7.78.1)** | 本层不猜「真重放 vs id 复用」,去重律不变 |
7942
+ | 3 | 段末帧身份键 `sourceTaskId` / `bgAgentId`(sdk `EventIdentity`) | **consumed** | `src/adapter/downstream/eventToSdkMessage.ts` 的 `#segmentEndProjection`(三键透传 + 非串 malformed);`src/adapt/arms.ts` 的 `#isSubFlowSegmentEnd`;门 TSA V1–V3 |
7943
+ | 4 | `thinking_segment_end.committedUuids[]` + (b1) 形 | **consumed** | `src/adapt/textStream.ts` 的 `#replaceThinkingSegment`(段内行按序累积);`src/seam.ts` 义务文案改口;门 TSA V4–V10 |
7944
+ | 5 | `WorkflowRunState.parks?` / `resumeAdmissionIncomplete?` | **consumed** | `src/workflowClient.ts` 的 `#projectWorkflowRun`;`src/workflowMonitor.ts` 的 `#WorkflowRunState`;门 park H 段 |
7945
+ | 6 | `SessionPermissionRules` 型名 | **consumed(型面转口)** | `src/sdkWireTransit.ts` |
7946
+ | 7 | §35z #15「候 sdk 9.5」 | **作废** | 已由 §36z #18 `fetchMcpPanel` 收口(sdk `sessions.mcp` 自 0.0.75 在场) |
7947
+
7948
+ ### 37y. 🔴 三端升级必读(固定段式,[C295];本批**三条**)
7949
+
7950
+ 1. 🔴 **`thinking_segment_end` 宿主义务改口**:`committedPrefixDiverged` 在场 ⇒ `committedUuids` **首行就地换成 `content`,其余行删除或清空其推理正文**(宿主按自身转录结构选 —— 转录若有单链 `parentUuid` 遍历 / compact 保留段端点 / resume 链 / 增量游标等依赖,**清空优于删**:保行身份、父链与压缩元数据,安全目标「未脱敏字节零留盘」等价达成;cli 接入回执实证删行牵动图结构)。0.69.0 写的「换 `committedUuid` 那一条」在跨工具卡的段上会漏掉前面几条未脱敏行;`committedUuid` 仍在(末条),只作兼容。
7951
+ 2. 🔴 **自建管线的端断子流按三键**:`text_end` / `reasoning_end` 内部臂现带 `sourceTaskId?` / `bgAgentId?`,任一在场 = 子代帧;只看 `parentToolCallId` 会把只带 `sourceTaskId` 的子代段末帧当 leader(0.69.0 包侧同病已修)。
7952
+ 3. `onDroppedFrame` 新 why 词 `duplicate_seq`(开集,宿主不必改型):渲进运维面;`-p` 端在 server <7.78.1 上如实披露推理面权威段不到,不绕过去重。`WorkflowRunState` 新带 `parks?` / `resumeAdmissionIncomplete?`,渲 park/resume 面的端改读视图。
7953
+
7954
+ **包侧缺口:** ① CC-04 技能帽退役随 7.78.1 进 0.69.2;② server <7.78.1 上 `-p` 链收不到 `reasoning_end`(归口 server,本版只留痕);③ 其余同 §36 包侧缺口。
7955
+
7956
+ ## §38 🆕 0.69.2 收尾批(CC-14 思考段账按行所属引擎段 / CC-13 壳抄包两件出公面;CC-04 改随 core 7.19.0 + server 7.78.1 进 0.69.3)
7957
+
7958
+
7959
+ ### 38a. 本节速览
7960
+
7961
+ | 件 | 一句话 | 端要做什么 |
7962
+ |---|---|---|
7963
+ | S-1 🔴 **安全面** | **CC-14 `thinking_segment_end` 段账按「行所属的引擎段」记**:帧序 `thinking(A) → reasoning_end(A′) → tool_use → thinking(B) → text_delta → reasoning_end(B′)` 下,修前第一段定稿后迟交的 A′ 行被登进第二段 `committedUuids` 首项 ⇒ 宿主按 §37y ① 义务把 A′ 换成 B′、把 B 清空,上一段定稿丢失而披露仍说成功。修后定稿块提交时不登下一段账;定稿块押着时下一段推理增量直接叠上(无提交边界)⇒ 先按自己的段提交、新块另起,两段推理绝不同行 | 自拼推理面并按 `committedUuids` 改写转录的端:提货即除;cli 1.0.115 的壳侧自卫(记上一段权威文本命中即剔除)按本版到货退役。渲染层零动作 |
7964
+ | S-2 | **CC-13 ① 子流三键判定出公面**:`isSubFlowSegmentEnd(frame)` + `SEGMENT_END_IDENTITY_KEYS`(`adapter/types.ts`;此前 `adapt/arms.ts` 私有) | 自建管线的端(print 车道 / 座位层)把自铸的三键判定改调本判定后删掉 |
7965
+ | S-3 | **CC-13 ② 丢帧判词出公面**:`droppedWhySentence(why)` 导出;`ctx.onDroppedFrame` 载荷新带 `sentence?`(additive) | 自渲运维面的端删手抄判词表,读 `info.sentence ?? droppedWhySentence(info.why)` |
7966
+ | S-4 | CC-04 `SKILL_CAPS.items` 退役改预算式(读 core 7.19.0 `skills.listing_truncated` 通告)+ `ENGINE_NOTICE_CODES` 镜像 +3 | **不在本版**:改随 core 7.19.0 + server 7.78.1 进 **0.69.3**(§39) |
7967
+
7968
+ ### 38z. 逐键处置表(0.69.2)
7969
+
7970
+ | # | 键 / 面 | 处置 | 坐标 / 理由 |
7971
+ |---|---|---|---|
7972
+ | 1 | 思考段账的登记时机(`takeThinking` 迟交行) | **consumed(修复)** | `src/adapt/textStream.ts` 的 `#thinkingSettled`(形 (a)/(b1) 定稿后置位;提交时不登账;下一段增量叠上先提交再另起);门 TSA T-g 六格 |
7973
+ | 2 | 按 raw/authoritative 文本比对剔除上一段行(cli 直发建议) | **declined** | 恰好同文的两段会被误剔;包内知道「哪一块被定稿」,不需比对 |
7974
+ | 3 | `isSubFlowSegmentEnd` / `SEGMENT_END_IDENTITY_KEYS` | **consumed(出公面)** | `src/adapter/types.ts`;`src/adapt/arms.ts` 与 `src/adapter/downstream/eventToSdkMessage.ts` 的 `#segmentEndProjection` 同源读;门 TSA W1–W3 |
7975
+ | 4 | `droppedWhySentence` / `DroppedFrameInfo.sentence?` | **consumed(出公面,additive)** | `src/adapter/runStream.ts`;`src/adapter/types.ts`;门 TSA W4–W6 |
7976
+ | 5 | `committedUuid` 兼容位语义 | **consumed(明写)** | = 段内**末条**(0.69.0 单行时代的钥匙);首行 = `committedUuids[0]`。端以 `committedUuids[0]` 为首行、仅在列表缺席时回落 `committedUuid` |
7977
+ | 6 | CC-04(`SKILL_CAPS.items` 退役 / `skills.listing_truncated` / 通告码 +3) | **不在本版(0.69.3)** | 候 core 7.19.0 / server 7.78.1 发布;[7315] 预发布判据已表态认领 [7316] |
7978
+
7979
+ ### 38y. 🔴 三端升级必读(固定段式,[C295];本批**三条**)
7980
+
7981
+ 1. 🔴 **CC-14 段账修复**:按 `committedUuids` 改写转录的端提货即得正确归属;为此病自建的壳侧自卫(记上一段权威文本命中即剔除)到货后**必须退役**,否则恰好同文的两段会被误剔。
7982
+ 2. **自建管线的端断子流改调 `isSubFlowSegmentEnd`**(或读 `SEGMENT_END_IDENTITY_KEYS`),删自铸的三键判定;判定语义与 §37y ② 逐字相同(按键在不在)。
7983
+ 3. **`onDroppedFrame` 载荷新带 `sentence?`**:自渲运维面的端删手抄判词表;宿主自构造的载荷可缺席 ⇒ 读方 `info.sentence ?? droppedWhySentence(info.why)`。`committedUuid` 兼容位 = 段内末条,首行取 `committedUuids[0]`(38z #5)。
7984
+
7985
+ **包侧缺口:** ① CC-04 技能帽退役随 core 7.19.0 / server 7.78.1 进 0.69.3(38a S-4);② 其余同 §37 包侧缺口。
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sema-agent/client-core",
3
- "version": "0.69.0",
3
+ "version": "0.69.2",
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",