@sema-agent/client-core 0.72.13 → 0.72.14

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,24 @@
49
49
  > 挡住 ⇒ 本批把它机械化——④a0 对 `pending` 行**要求段头已是日期形**(`(未发布)` 直接红),阶段一
50
50
  > commit 漏转在发布前就红,不再靠人记。
51
51
 
52
+ ## 0.72.14(2026-09-18)
53
+
54
+ > 主题:**悬挂的流内 ask 的拉半场**(CC-55;cli L-397 P1)。流内 ask 在铸造那一刻若没有活着的宿主流可投,引擎把它转成悬挂(窗约一小时,不补发),此后只在 `GET /v1/approvals` 回体的 `livePending` 段可发现 —— 后台 workflow 的子代恒落这一形(宿主 turn 先结束),用户面零帧零卡、代理静默卡到窗尽。**patch**:型面纯 additive(+1 常量 +7 函数,公面 1011 → 1019;`ApprovalsFeedSnapshot` / `ApprovalsFeedStats` / `ApprovalsFeedOptions` / `ToolApprovalFrameLaneOpts` / `ApprovalCardRequest` / `ToolApprovalFrameOutcome` 各加一可选或新增位;零 wire 新键;peer 不动)。接入面 §61。
55
+
56
+ ### Added
57
+
58
+ - **`ApprovalsFeedSnapshot.livePending?`**:`startApprovalsFeed` 的快照此前**只含 `pending`**,变化摘要也只算 `pending` ⇒ 悬挂 ask 出现 / 结算都不触发快照,端即使挂了 feed 也看不见。现在 `list()` 回体的 `livePending` 段按结构窄读进快照(sdk barrel 不导出该行型),并进变化摘要。🔴 **键缺席 = 引擎没报这一段**(老引擎),不是「没有」—— 快照上键缺席,绝不铸 `[]`;引擎报了且为空才是 `[]`;键在场却读不懂 ⇒ **整次取件不提交**(保留上一张,不把读不懂渲成没有,计入 `listErrors`)。
59
+ - **`src/hitl/livePendingAsk.ts`**:`readLivePendingRows(body)` 三态(`not_reported` / `malformed` / `present{rows, dropped}`;四必填位坏形丢行计数、同 `approvalId` 留第一条、可选旗标 only-if-true、多余成员不过境);`suspendedSubagentAsks(snapshot, {sessionId?})`(只留子代行;给了会话就只留该会话的,行上没有 `sessionId` = 归不了属 ⇒ 不出卡);`countApprovalsAwaitingDecision(snapshot, filter?)`(`durable` / `live` / `suspendedSubagent`,**不知道 = `null` 不是 0** —— 端的「在等你决定」计数读它,不再只认 durable 停驻一条来源);`createSuspendedAskTracker(filter?)`(快照 → `appeared` / `gone` 的去重账:同 `approvalId` 只出一次;`noteSurfacedByStream` = **双向认领口**,返回 `true` 帧腿可出卡且快照不再出第二张、返回 `false` = 已从快照出过卡 ⇒ 帧腿跳过;`requeue` = 让一只 ask 重新出卡;两本账都**不按条数淘汰活跃条目**(出过卡的随「从列表消失」离账;认领在列表里见过之后消失才离账;只有「认领了但从未在快照里见过」的那一小类受 512 上界管);`noteDecided` = 已决断的在引擎结算前不重新出卡;条目消失恰一次 `gone` = 撤卡;`livePending` 键缺席 ⇒ 已出的卡全部 `gone`)。
60
+ - **决断口 `surfaceSuspendedAskAndRespond(row, respond, signal?, lane?)`**:悬挂行经 `livePendingRowToApprovalFrame` 适配后走**流内帧腿同一条链**(同一个卡口、同一个身份键铸口 `liveFrameCallKey(approvalId)`、同一套回执 / 拒绝读法)。🔴 **数组名即路由判据**:`livePending` 行的决议口是 `POST /v1/tool-approvals/{approvalId}/respond`,不是 durable 行的 decide 口;幂等键 = `approvalId`。
61
+ - **`ToolApprovalFrameLaneOpts.argsUnavailable?: true`**:`livePending` 段刻意不带工具入参 ⇒ 这张卡上 `wireNote` 明说「看不到入参、你是在没看到它们的情况下做决定、这张卡不接受编辑」,卡请求带结构化位 `ApprovalCardRequest.argsUnavailable: true`(端据此不给「编辑后批准」)。🔴 真收到带改写的批准 ⇒ **整次不发**:丢掉改写再发 allow = 批准了人没看到的原始入参,把改写转发 = 拿卡上的空对象替换真实入参,两条都不是人按下的那个决定 ⇒ 结局 `decision:'unresolved'` + `ToolApprovalFrameOutcome.editRefused: true`,屏上一条告知(`EDIT_REFUSED_ON_BLIND_ASK_WARN_TEXT` / `surfaceEditRefusedOnBlindAsk`),ask 仍挂着由端 `requeue` 重出。只由上面那只决断口置位,流内帧腿行为零变化。
62
+ - **对账节拍 `ApprovalsFeedOptions.reconcile?`**(`{ shouldReconcile, intervalMs?, maxIntervalMs? }`)+ `ApprovalsFeedStats.reconciles`:较老的引擎在悬挂 ask 出现 / 结算时不在 `approvals.stream` 上发任何事件 ⇒ push 腿连着也看不见;而「仅活跃 run 时轮询」的 `shouldPoll` 恰好在宿主 turn 结束后关着。本旋钮是**独立**的宿主闸:为真期间(典型:会话有活跃后台代理 ∧ 没有活着的宿主 run)无论哪条腿都定时重取;闸关零额外取件;快照没变则间隔翻倍到上限,变了 / 闸重开回基础间隔;不计 `polls`、不动轮询腿的连续失败计数;谓词抛错 / 非布尔按「查」处理。不传 = 连定时器都不起,行为一字不差。
63
+ - 新门 `scripts/run-live-pending-ask-test.mjs`(52 格;变异反证逐格见红)。
64
+
65
+ ### Known limits(本版新增)
66
+
67
+ - 悬挂 ask 的卡**看不到工具入参**(引擎读面今天不带;已请引擎在该段补入参摘要)。卡上如实明说;到货后本包把摘要过到卡上,届时另版。
68
+ - 窗的倒计时不上这张卡:行上只有绝对死线、没有出帧时刻,凑不出可校偏的窗三键;`expiresAtMs` 原样在视图上,端要渲自己按本地钟渲并自担偏差。
69
+
52
70
  ## 0.72.13(2026-09-18)
53
71
 
54
72
  > 主题:**server 7.86.0 提货**(发车帖 [7549] @client-core 两点,表态 [7553] ⓪;CC-46 `plan_review` 决断体 `permissionModeAfter` 发口 —— cli L-385 / S-433;CC-54 `capabilities.executionLane` 四态读面 —— S-426)+ **CC-53** `engineSupportsDetach` 三态化(审计 #60 A-25,「异常吞成健康值」)。**patch**:型面纯 additive(+4 常量 +14 函数,公面 993 → 1011;测试钩 45 → 47;`HitlSafetyError.code` 闭集 +1 `bad_plan_mode`;`PlanReviewOutcome` / `armPlanReviewApproval` / `decidePlanReview` 各加一可选位;零 wire 新键 —— 键由 server 7.86.0 铸;peer 不动)。接入面 §60。
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.72.13
38
+ **Version:** 0.72.14
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
@@ -313,6 +313,7 @@ public-surface guard checks that last one).
313
313
  | `scripts/run-mcp-panel-projection-test.mjs` | The `GET /v1/sessions/:id/mcp` panel reader (`projectMcpPanel`; server >=7.77.0 adds the optional `lastLegMcp` key) and the single wording mint for its "last leg" line. Absence of `lastLegMcp` is one literal sentence that never blames the engine version (a new session, a leg outside the retention window, a leg without a manifest and an older engine all look the same on the wire); a key that is present but unreadable is a different sentence plus a `lastLegMcpUnreadable: true` mark, never folded into absence. The `mcp[]` roster goes through the same reader as the live `wiring_manifest` third section, so a replayed roster and a live one have one shape. The two faces of the panel (`servers[]` and the last-leg roster) may legitimately differ, so the view carries no agreement flag and none of the five sentences mentions `servers`. Required keys are pinned to the SDK `openapi.yaml` component bytes **0.69.0:** `fetchMcpPanel` fetches the panel through the SDK client's own `sessions.mcp` call (same transport and auth as every other read) and projects it; transport failure, an unreadable body and an empty session id all come back as `undefined`, never as a fabricated empty panel 0.71.0 adds section K: `mcpEngineLegPresence(view)` — the engine-side MCP presence tri-state read only off the panel view (`unknown` when the view could not be read, never rendered as "no MCP configured") |
314
314
  | `scripts/run-absence-fold-census-test.mjs` | A package-wide census of the "absence folded into a positive outcome" defect shape, so that fixing the six sites this release does not merely move the shape somewhere else. The defect is defined by position, not syntax: a fallback position (the unconditional tail return, the `default:` arm, the literal minted when there is nothing to pass on, the value returned from an error path) may only say `unknown` or stay absent, never a positive word. Detection walks the syntax tree of every source file, so comments, strings and multi-line spellings cannot hide or fake a hit, and covers five forms: the right arm of `??` / `||`, the else arm of a ternary, the first return of an explicit `default:`, a `catch` block or `.catch(() => …)` arrow returning a healthy value, and a function whose last statement returns a positive word after other returns. Every remaining hit must be registered with a written reason, an unregistered hit fails the gate naming the file and line, the registered count must equal the real count so a cleared site cannot leave a spare allowance behind, and the gate proves its own teeth behind a fence (a failed self-proof refuses to report any count): each form injected into an in-memory copy must add exactly one hit, two correct spellings are pinned as non-hits, and samples inside comments or strings do not count. It also pins the headline site: the fleet panel projection no longer mints an `end` with `isError: false` on absence |
315
315
  | `scripts/run-execution-lane-capability-test.mjs` | The deployment execution-lane self-description (`capabilities.executionLane`, engine ≥7.86.0), read the same four-state way as its three sibling capability readers. An absent key is reported as "not reported" and never as "tools do not run on this host": on an older engine the client keeps inferring the lane the way it did before, because reading absence as `false` would silently stop every host deployment from sending skill `baseDir`. The implication is one-way (`toolsOnThisHost: false` means `baseDir` is never sent; `true` is only a necessary condition), `provider` is read as an open non-empty string rather than a hand-copied closed set and is sanitised and bounded before display, `toolsOnThisHost` must be a strict boolean, extra members never cross, the tee never throws, stale generations are dropped whole, and the single "do tools run here" predicate uses the bit when it is present and the caller's own inference, unchanged, when it is not |
316
+ | `scripts/run-live-pending-ask-test.mjs` | Suspended in-stream asks (the `livePending` section of `GET /v1/approvals`): an absent key reads as not reported (never an empty list or a zero count), an unreadable section leaves the previous snapshot in place, rows are narrow-read (malformed rows dropped and counted, optional flags only-if-true, extra members never cross), the feed digest covers the section so an ask appearing or settling produces a snapshot, the host-gated reconcile cadence re-lists while the push leg is connected and backs off when nothing changes, the tracker surfaces each `approvalId` once and reports it gone once, and the decision runs through the same respond chain as the in-stream frame leg — with the card stating that the tool arguments are not visible and edits on that card never forwarded. |
316
317
  | `scripts/run-leader-conflict-test.mjs` | The leader-run terminal `needs_human` + `result.conflict` (engine ≥7.83.0), read once for all three shells. A `result` without a `conflict` key is reported as `none` and worded as "no conflict details" rather than "no conflict": on the wire it is indistinguishable from an older engine that never reports one, and the reader does not pick a side. A `conflict` that is present but unreadable is a third word, never folded into `none`, because a tree that carries conflict markers must not be rendered as clean. `filesTruncated` is honoured only as `true` (absent means the list is complete; any other value makes the section unreadable), `workers[].applied` is passed through as the boolean fact it is, `salvaged[].patch` is handed to the save path byte-for-byte (an artifact, not screen text; a malformed row is dropped alone, and a salvage list that is present but cannot be fully read is flagged rather than rendered as "no patches"), and `rejHead` — the one on-screen diagnostic — is escaped and bounded before display. The record never throws on hostile input, extra members do not cross, arrays are fresh copies, and the single sentence minted here names the base, the files, which branches landed and which did not, and how many patches can be saved, without ever suggesting a retry: `needs_human` is a run waiting for a person, and the no-details sentence says plainly that it is not proof of a merge |
317
318
  | `scripts/run-mcp-reconnect-test.mjs` | The in-session MCP re-dial verb (`POST /v1/sessions/:id/mcp/reconnect`, engine ≥7.85.0), consumed. The single discriminant is `outcome` and all three answers are HTTP 200, so the reader branches on the word and never on the status; the `unsupported` answer carries exactly five keys and the reader refuses to invent a zero or an empty list for the four fields the engine did not produce, while `accepted` / `refused` treat those four as required and go malformed when one is missing. The tool roster follows the **presence** of `toolNames` (absent = untouched, empty = withdrawn), the connection record passes `errorCode` through as an open set, and every remote-authored string is sanitised and bounded before display. Failures are classified by `errorCode` alone, a missing code is reported as unknown rather than guessed, the verb never throws, and the request-side guard (non-empty name, ≤190 chars) stops a call that the contract would reject anyway. The capability bit reads absent as "cannot tell" rather than "unavailable", and the one sentence the contract insists every UI carries — that re-dialing is a transaction, not a refresh — is minted here once |
318
319
  | `scripts/run-core-value-ports-test.mjs` | The port-injection seam for ten **engine value-level** facilities (autonomous-loop prompt assembly, permission-rule loosening, tool-policy composition, protocol/retired-name/grammar lookups, rule compilation, the discussion workflow name). This package cannot re-export them (the engine barrel drags Node built-ins into the browser bundle), so it declares the ports and honest-absence readers; a Node host installs the engine's own functions verbatim. The guard pins: every reader returns `undefined` when nothing is installed (never a fabricated empty array or default policy), arguments and results pass through by reference, engine errors propagate unchanged, partial installs read partially, restore functions unwind to the previous bag, and the module source has zero engine imports |
@@ -44,6 +44,7 @@
44
44
  * 端的自检可以断言「跑了 N 秒之后 mode 不是 idle 且 polls+pushEvents > 0」。
45
45
  */
46
46
  import type { PendingCheckpoint } from '@sema-agent/sdk';
47
+ import { type LivePendingAskView } from './livePendingAsk.js';
47
48
  /** feed 消费的 wire 切片。`stream` 可选 —— 老 SDK / mock 没有它就直接走轮询腿。 */
48
49
  export interface ApprovalsFeedClientLike {
49
50
  approvals: {
@@ -51,6 +52,7 @@ export interface ApprovalsFeedClientLike {
51
52
  signal?: AbortSignal;
52
53
  }): Promise<{
53
54
  pending: PendingCheckpoint[];
55
+ livePending?: unknown[];
54
56
  }>;
55
57
  stream?(opts?: {
56
58
  signal?: AbortSignal;
@@ -64,7 +66,12 @@ export type ApprovalsFeedMode = 'push' | 'poll' | 'idle';
64
66
  export interface ApprovalsFeedSnapshot {
65
67
  /** 权威 pending 列表(**恒来自 `list()`**,不是 delta 拼出来的)。 */
66
68
  pending: PendingCheckpoint[];
67
- /** 这次快照是被哪条腿触发的。 */
69
+ /**
70
+ * 0.72.14:**流内 ask**(含悬挂的子代 ask)的窄读行,同样恒来自 `list()`。
71
+ * 🔴 **键缺席 = 引擎没报这一段**(老引擎),不是「没有」;引擎报了且为空 ⇒ `[]`。读法见 `livePendingAsk.ts`。
72
+ */
73
+ livePending?: LivePendingAskView[];
74
+ /** 这次快照是被哪条腿触发的(对账节拍触发的取件记 `'poll'`:它就是一次定时 `list()`)。 */
68
75
  mode: 'push' | 'poll';
69
76
  /** 单调递增的修订号(端可用它判「我看到的是不是最新的」)。 */
70
77
  revision: number;
@@ -82,6 +89,24 @@ export interface ApprovalsFeedStats {
82
89
  streamFailures: number;
83
90
  /** 真正发给订阅者的快照数(内容无变化时不发 ⇒ 这个数会小于 polls+pushEvents)。 */
84
91
  snapshots: number;
92
+ /** 0.72.14:对账节拍真取件的次数(与 `polls` 分开记 —— 它不属于回落轮询腿)。 */
93
+ reconciles: number;
94
+ }
95
+ /**
96
+ * 0.72.14 **对账节拍**:悬挂的流内 ask 出现 / 结算时,较老的引擎不在 `approvals.stream` 上发任何事件 ⇒ push 腿连着也
97
+ * 看不见它;而「仅活跃 run 时轮询」的 `shouldPoll` 恰好在宿主 turn 结束后关着。本旋钮给宿主一个**独立**的闸:
98
+ * `shouldReconcile()` 为真(典型:会话有活跃后台代理 ∧ 没有活着的宿主 run)期间,无论当前是哪条腿都定时重取 `list()`。
99
+ * · 闸关 ⇒ 零额外取件;不传本旋钮 ⇒ 连定时器都不起,行为与此前一字不差。
100
+ * · 取件后快照没变 ⇒ 间隔翻倍直到 `maxIntervalMs`;变了 / 闸由关转开 ⇒ 回到 `intervalMs`。
101
+ * · 谓词抛错 / 返回非布尔 ⇒ 按「查」处理(与 `shouldPoll` 同一个 fail-safe 方向:少查一次 = 一只等人的 ask 迟迟不出现)。
102
+ * · 引擎开始在 stream 上为这一段发事件之后,push 腿自己就会重取,本节拍只剩兜底意义,宿主可以不再传。
103
+ */
104
+ export interface ApprovalsFeedReconcileOptions {
105
+ shouldReconcile: () => boolean;
106
+ /** 基础间隔;缺省 5000ms。 */
107
+ intervalMs?: number;
108
+ /** 退避上限;缺省 30_000ms。 */
109
+ maxIntervalMs?: number;
85
110
  }
86
111
  export interface ApprovalsFeedOptions {
87
112
  /** 回落轮询间隔;缺省 5000ms。 */
@@ -107,6 +132,8 @@ export interface ApprovalsFeedOptions {
107
132
  * · **`refresh()`** 不受管 —— 那是端自己按下的显式动作。
108
133
  */
109
134
  shouldPoll?: () => boolean;
135
+ /** 0.72.14:对账节拍(见 {@link ApprovalsFeedReconcileOptions})。缺省不起。 */
136
+ reconcile?: ApprovalsFeedReconcileOptions;
110
137
  signal?: AbortSignal;
111
138
  }
112
139
  export interface ApprovalsFeedHandle {
@@ -1,5 +1,6 @@
1
1
  import { hostLog } from '../host.js';
2
2
  import { unrefTimer } from '../unrefTimer.js';
3
+ import { readLivePendingRows } from './livePendingAsk.js';
3
4
  /** [2393] hitl-F5:poll 回调体**连续**逃逸多少次之后判定「这条腿真挂了」并把 `mode` 打成 idle。
4
5
  * 取 5 是为了让「偶发一拍畸形 payload」不误判(单次逃逸下一拍就归零),同时不让一条永远抛的腿
5
6
  * 无限期地对着端的自检假装自己还活着。 */
@@ -9,13 +10,29 @@ const MAX_CONSECUTIVE_POLL_ESCAPES = 5;
9
10
  * 键里带 `boundInputHash`:同一条 pending 的绑定被服务端换掉(TOCTOU 场景)也算变化,
10
11
  * 否则端会拿着一个「id 没变但绑定变了」的行继续渲染。
11
12
  */
12
- function digestOf(rows) {
13
+ function durableDigestOf(rows) {
13
14
  return rows
14
15
  .map(r => [r.sessionId ?? '', r.taskId ?? '', r.toolCallId ?? '', r.boundCallId ?? '', r.boundInputHash ?? '', r.toolName ?? '']
15
16
  .join(''))
16
17
  .sort()
17
18
  .join('');
18
19
  }
20
+ const LIVE_FIELD_SEP = String.fromCharCode(1);
21
+ const LIVE_ROW_SEP = String.fromCharCode(2);
22
+ const LIVE_SECTION_SEP = String.fromCharCode(3);
23
+ /**
24
+ * 0.72.14:流内 ask 段进摘要 —— 此前只算 `pending`,悬挂 ask 出现 / 消失都不触发快照。
25
+ * 「未报」与「报了且为空」是两个不同的事实(前者读不出计数),摘要上也分开。
26
+ */
27
+ function digestOf(rows, live) {
28
+ const liveDigest = live === undefined
29
+ ? 'absent'
30
+ : live
31
+ .map(r => [r.approvalId, r.toolName, String(r.expiresAtMs), r.sessionId ?? '', r.originTaskId ?? ''].join(LIVE_FIELD_SEP))
32
+ .sort()
33
+ .join(LIVE_ROW_SEP);
34
+ return `${durableDigestOf(rows)}${LIVE_SECTION_SEP}${liveDigest}`;
35
+ }
19
36
  /**
20
37
  * 起一条 pending-approvals feed。**幂等性归调用方**:一个 client 起一条就够了,起两条 =
21
38
  * 两倍取件(不是错误,但没意义)。
@@ -51,7 +68,7 @@ export function startApprovalsFeed(client, onSnapshot, opts) {
51
68
  let takeSeq = 0;
52
69
  let lastObservedSeq = 0;
53
70
  const stats = {
54
- pushEvents: 0, heartbeats: 0, polls: 0, listErrors: 0, streamFailures: 0, snapshots: 0,
71
+ pushEvents: 0, heartbeats: 0, polls: 0, listErrors: 0, streamFailures: 0, snapshots: 0, reconciles: 0,
55
72
  };
56
73
  /** 域词表-14 收编:「怎么调 unref」的单一实现见 unrefTimer.ts,本函数只是就地起个短名。 */
57
74
  function arm(t) {
@@ -90,8 +107,23 @@ export function startApprovalsFeed(client, onSnapshot, opts) {
90
107
  return;
91
108
  const seq = ++takeSeq;
92
109
  let rows;
110
+ let live;
93
111
  try {
94
- rows = (await client.approvals.list({ signal: ac.signal })).pending;
112
+ const body = await client.approvals.list({ signal: ac.signal });
113
+ rows = body.pending;
114
+ // 0.72.14:`livePending` 段按结构窄读。键在场却读不懂 ⇒ **整次取件不提交**(保留上一张快照):
115
+ // 把「读不懂」提交成「未报 / 为空」会让端撤掉一张还在等人的卡。
116
+ const reading = readLivePendingRows(body);
117
+ if (reading.kind === 'malformed') {
118
+ stats.listErrors++;
119
+ hostLog('debug', `approvalsFeed: list() body has an unreadable livePending section (${via} leg) — snapshot not committed`);
120
+ return;
121
+ }
122
+ if (reading.kind === 'present') {
123
+ live = reading.rows;
124
+ if (reading.dropped > 0)
125
+ hostLog('debug', `approvalsFeed: dropped ${reading.dropped} malformed livePending row(s)`);
126
+ }
95
127
  }
96
128
  catch (e) {
97
129
  stats.listErrors++;
@@ -100,7 +132,7 @@ export function startApprovalsFeed(client, onSnapshot, opts) {
100
132
  }
101
133
  if (stopped)
102
134
  return;
103
- const d = digestOf(rows);
135
+ const d = digestOf(rows, live);
104
136
  // 🔴 [F4957-1 二审 finding②] 代际闸必须排在**提交之前**:下面四行(lastDigest / revision /
105
137
  // last / onSnapshot)一落,视图就已经被改了 —— 守卫排在 take() 之外只挡得住计数与续排,挡不住
106
138
  // 回滚。乱序是真的会发生的:旧 poll 在途 → push 重连并发布新快照 → 旧 poll 才带着**更老**的
@@ -122,7 +154,7 @@ export function startApprovalsFeed(client, onSnapshot, opts) {
122
154
  return; // 内容没变 ⇒ 不打扰订阅者
123
155
  lastDigest = d;
124
156
  revision++;
125
- const snap = { pending: rows, mode: via, revision };
157
+ const snap = { pending: rows, ...(live !== undefined ? { livePending: live } : {}), mode: via, revision };
126
158
  last = snap;
127
159
  stats.snapshots++;
128
160
  try {
@@ -205,6 +237,54 @@ export function startApprovalsFeed(client, onSnapshot, opts) {
205
237
  });
206
238
  }, pollIntervalMs));
207
239
  }
240
+ /** 0.72.14 对账节拍(见 {@link ApprovalsFeedReconcileOptions})。独立于两条腿:不读 `mode`、不动 F5 计数、不计 `polls`。 */
241
+ const reconcileBaseMs = Math.max(1, opts?.reconcile?.intervalMs ?? 5000);
242
+ const reconcileMaxMs = Math.max(reconcileBaseMs, opts?.reconcile?.maxIntervalMs ?? 30_000);
243
+ let reconcileTimer;
244
+ let reconcileDelayMs = reconcileBaseMs;
245
+ function reconcileGateOpen() {
246
+ let verdict;
247
+ try {
248
+ verdict = opts?.reconcile?.shouldReconcile();
249
+ }
250
+ catch (e) {
251
+ hostLog('debug', `approvalsFeed: shouldReconcile() threw — treating this tick as "reconcile" (fail-safe): ${String(e)}`);
252
+ return true;
253
+ }
254
+ if (typeof verdict !== 'boolean') {
255
+ hostLog('debug', `approvalsFeed: shouldReconcile() returned ${typeof verdict} (not boolean) — treating this tick as "reconcile"`);
256
+ return true;
257
+ }
258
+ return verdict;
259
+ }
260
+ function scheduleReconcile() {
261
+ if (opts?.reconcile === undefined)
262
+ return;
263
+ if (stopped || ac.signal.aborted)
264
+ return;
265
+ if (reconcileTimer !== undefined)
266
+ return;
267
+ reconcileTimer = arm(setTimeout(() => {
268
+ reconcileTimer = undefined;
269
+ if (stopped || ac.signal.aborted)
270
+ return;
271
+ if (!reconcileGateOpen()) {
272
+ reconcileDelayMs = reconcileBaseMs; // 闸关 ⇒ 下次转开时从基础间隔起
273
+ scheduleReconcile();
274
+ return;
275
+ }
276
+ void (async () => {
277
+ const before = revision;
278
+ stats.reconciles++;
279
+ await take('poll');
280
+ reconcileDelayMs = revision !== before ? reconcileBaseMs : Math.min(reconcileDelayMs * 2, reconcileMaxMs);
281
+ })()
282
+ .catch(e => {
283
+ hostLog('debug', `approvalsFeed: reconcile tick threw unexpectedly (take() should have self-caught): ${String(e)}`);
284
+ })
285
+ .then(() => scheduleReconcile());
286
+ }, reconcileDelayMs));
287
+ }
208
288
  /**
209
289
  * 与 schedulePoll 对称(域词表-18):**幂等卫兵**(retryTimer 已在飞则不再排一个)+ **回调首行
210
290
  * 自清**(否则 retryTimer 触发后仍持有已死句柄,degradeToPoll 若被再次调用会直接覆盖它而不清掉
@@ -312,6 +392,7 @@ export function startApprovalsFeed(client, onSnapshot, opts) {
312
392
  void runStream();
313
393
  else
314
394
  degradeToPoll('SDK has no approvals.stream (old SDK / mock)', false);
395
+ scheduleReconcile();
315
396
  });
316
397
  return {
317
398
  stop() {
@@ -323,6 +404,8 @@ export function startApprovalsFeed(client, onSnapshot, opts) {
323
404
  clearTimeout(pollTimer);
324
405
  if (retryTimer !== undefined)
325
406
  clearTimeout(retryTimer);
407
+ if (reconcileTimer !== undefined)
408
+ clearTimeout(reconcileTimer);
326
409
  opts?.signal?.removeEventListener('abort', onOuterAbort);
327
410
  ac.abort();
328
411
  },
@@ -88,7 +88,8 @@ export interface CrashConvergedRow {
88
88
  * `GET /v1/approvals` 的**信封**形。`pending` 是老形的全部;后两键 additive
89
89
  * (server 按 deps 在场才发)⇒ **老形 `{ pending }` 仍然可赋值到本型**,现有消费点一个字节不用改。
90
90
  *
91
- * 🔴 `livePending` 在本包是**不透明载体**,留 `unknown[]` 有两条**互相独立**的理由:
91
+ * 🔴 `livePending` 在**本型上**留 `unknown[]`(0.72.14 起它有了包内读器 `readLivePendingRows`,按结构窄读;
92
+ * 下面 ① 说的「零消费点」自那版起不再成立,② 仍然成立,也是读器按结构读而不引类型的原因):
92
93
  * ① 本包对它零消费点,在这里手抄一份结构 = 手抄一份会漂的上游形(与 `ToolApprovalFrame.probeCause`
93
94
  * 同一条理由);
94
95
  * ② **SDK barrel 根本没把 `LivePendingRow` 导出**(实证:`@sema-agent/sdk` 7.4.0 的 `dist/index.d.ts`
@@ -114,6 +114,10 @@ export declare function surfaceRememberNotApplied(): void;
114
114
  * 已记进 docs/refactor/README.md 的宿主/上游工单表,本层不做旁路补偿(只做如实告知)。
115
115
  */
116
116
  export declare function surfaceEditNotForwarded(): void;
117
+ /** 0.72.14:入参不可得的卡上收到了「编辑后批准」——什么都没发,ask 仍挂着(文案测试锁字面)。 */
118
+ export declare const EDIT_REFUSED_ON_BLIND_ASK_WARN_TEXT: string;
119
+ /** 见 {@link EDIT_REFUSED_ON_BLIND_ASK_WARN_TEXT};无宿主口 ⇒ 静默(与同族通知同语义)。 */
120
+ export declare function surfaceEditRefusedOnBlindAsk(): void;
117
121
  /**
118
122
  * 文案(测试锁字面)。措辞刻意描述**后果**,并且**只说证得出的话**(异源对抗复审四轮 [medium] 修):
119
123
  * · 不写「这台引擎不支持」——能力位缺席有三种同形成因(宿主没接这个字段 / 探测还没回来 / 探测失败),
@@ -163,6 +163,14 @@ export function surfaceRememberNotApplied() {
163
163
  export function surfaceEditNotForwarded() {
164
164
  surfaceSelfClearingWarn(EDIT_NOT_FORWARDED_KEY, EDIT_NOT_FORWARDED_WARN_TEXT, EDIT_NOT_FORWARDED_TIMEOUT_MS);
165
165
  }
166
+ /** 0.72.14:入参不可得的卡上收到了「编辑后批准」——什么都没发,ask 仍挂着(文案测试锁字面)。 */
167
+ export const EDIT_REFUSED_ON_BLIND_ASK_WARN_TEXT = 'That approval was NOT sent: this request does not expose its tool arguments here, so an edited approval cannot be applied. ' +
168
+ 'The request is still waiting — approve or deny it as it is.';
169
+ const EDIT_REFUSED_ON_BLIND_ASK_KEY = 'hitl-edit-refused-blind-ask';
170
+ /** 见 {@link EDIT_REFUSED_ON_BLIND_ASK_WARN_TEXT};无宿主口 ⇒ 静默(与同族通知同语义)。 */
171
+ export function surfaceEditRefusedOnBlindAsk() {
172
+ surfaceSelfClearingWarn(EDIT_REFUSED_ON_BLIND_ASK_KEY, EDIT_REFUSED_ON_BLIND_ASK_WARN_TEXT, EDIT_NOT_FORWARDED_TIMEOUT_MS);
173
+ }
166
174
  /**
167
175
  * 文案(测试锁字面)。措辞刻意描述**后果**,并且**只说证得出的话**(异源对抗复审四轮 [medium] 修):
168
176
  * · 不写「这台引擎不支持」——能力位缺席有三种同形成因(宿主没接这个字段 / 探测还没回来 / 探测失败),
@@ -0,0 +1,107 @@
1
+ /**
2
+ * livePendingAsk — 「悬挂的流内 ask」的三端共用读面与决断口(0.72.14)。
3
+ *
4
+ * ── 这个模块补的是哪一个缺口 ────────────────────────────────────────────────────────────────
5
+ * 流内 ask 在铸造那一刻若没有活着的宿主流可投,引擎把它转成**悬挂**(窗约一小时,不补发、不重算),此后只在
6
+ * `GET /v1/approvals` 回体的第二顶层键 `livePending` 里可发现。后台 workflow 的子代恒落这一形:宿主 turn 先结束,
7
+ * 子代稍后才撞上审批门 ⇒ 用户面零帧零卡,代理静默卡到窗尽。本模块让端从 {@link startApprovalsFeed} 的快照里
8
+ * 读到这些行、出带子代身份的卡、并经**与流内帧腿同一条链**决断。
9
+ *
10
+ * ── 🔴 四条读法 ────────────────────────────────────────────────────────────────────────────
11
+ * ① **键缺席 ≠ 没有悬挂 ask**:老引擎不发这个键 ⇒ `not_reported`;快照上键缺席,计数读 `null`(不知道),绝不铸 `[]` / `0`。
12
+ * ② **数组名即路由判据**:`livePending` 行的决议口是 `POST /v1/tool-approvals/{approvalId}/respond`(流内审批那个口),
13
+ * **不是** durable 行的 decide 口;两数组不混编。幂等键 = `approvalId`(与流内帧同一个 id)。
14
+ * ③ **可选旗标 only-if-true**:`fromSubagent` / `governanceForced` / `requiresRealApproval` 缺席绝不编码成 `false`。
15
+ * ④ **行上没有工具入参**(引擎刻意不在这个读面开第二道脱敏面)⇒ 卡必须明说「看不到入参」,不许渲一个空命令冒充全貌。
16
+ *
17
+ * sdk 的 barrel 没有导出这一行的类型 ⇒ 按**结构**窄读(与 `capabilities.executionLane` 同一姿势),多余成员不过境。
18
+ */
19
+ import type { ApprovalsFeedSnapshot } from './approvalsFeed.js';
20
+ import { type RespondToolApprovalFn, type ToolApprovalFrame, type ToolApprovalFrameLaneOpts, type ToolApprovalFrameOutcome } from './toolApprovalWire.js';
21
+ /** 一条悬挂的流内 ask(窄读后的形;键集即全部)。 */
22
+ export interface LivePendingAskView {
23
+ /** 与 respond 口收的同一个 id。 */
24
+ approvalId: string;
25
+ toolName: string;
26
+ /** 登记时刻(epoch ms)。 */
27
+ ts: number;
28
+ /** 窗死线(绝对 epoch ms;引擎铸定,本包不重算)。 */
29
+ expiresAtMs: number;
30
+ sessionId?: string;
31
+ requiresRealApproval?: true;
32
+ governanceForced?: true;
33
+ /** 出自委派子代。 */
34
+ fromSubagent?: true;
35
+ /** 子代 taskId(与 `fromSubagent` 同生同缺)。 */
36
+ originTaskId?: string;
37
+ }
38
+ /** `livePending` 段的三态读数。`malformed` = 键在场但读不懂(**不**折成 `not_reported`,也不折成空)。 */
39
+ export type LivePendingReading = {
40
+ kind: 'not_reported';
41
+ } | {
42
+ kind: 'malformed';
43
+ } | {
44
+ kind: 'present';
45
+ rows: LivePendingAskView[];
46
+ dropped: number;
47
+ };
48
+ /** `GET /v1/approvals` 回体 → `livePending` 段读数。四必填位坏形的行丢弃并计数;同 `approvalId` 重复行留第一条。 */
49
+ export declare function readLivePendingRows(body: unknown): LivePendingReading;
50
+ /** 视图过滤:给了 `sessionId` ⇒ 只留该会话的行;行上没有 `sessionId` = 归不了属 ⇒ 不留(不替别的会话出卡)。 */
51
+ export interface SuspendedAskFilter {
52
+ sessionId?: string;
53
+ }
54
+ /** 快照里**出自子代**的悬挂 ask(宿主自己的流内 ask 走流内帧腿,不在这里出卡)。未报 / 没有快照 ⇒ `[]`。 */
55
+ export declare function suspendedSubagentAsks(snapshot: ApprovalsFeedSnapshot | null | undefined, filter?: SuspendedAskFilter): LivePendingAskView[];
56
+ /** 「在等人决定」的计数。`null` = 不知道(没有快照 / 引擎没报这一段),**不是** 0。 */
57
+ export interface ApprovalsAwaitingDecisionCount {
58
+ /** durable 停驻行数。 */
59
+ durable: number | null;
60
+ /** 流内 ask 行数(含宿主自己的与子代的)。 */
61
+ live: number | null;
62
+ /** 其中出自子代的行数。 */
63
+ suspendedSubagent: number | null;
64
+ }
65
+ export declare function countApprovalsAwaitingDecision(snapshot: ApprovalsFeedSnapshot | null | undefined, filter?: SuspendedAskFilter): ApprovalsAwaitingDecisionCount;
66
+ /**
67
+ * 悬挂行 → 流内帧腿的输入。只带行上**真有**的位:窗三键不带(行上只有绝对死线,没有出帧时刻,凑不出可校偏的窗),
68
+ * `args` 不带(行上没有)。`sourceTaskId` = `originTaskId`(卡头子代身份徽章的来源)。
69
+ */
70
+ export declare function livePendingRowToApprovalFrame(row: LivePendingAskView): ToolApprovalFrame;
71
+ /**
72
+ * 出卡并决断一条悬挂 ask —— **与流内帧腿同一条链**({@link surfaceToolApprovalFrameAndRespond}):同一个卡口、
73
+ * 同一个身份键铸口(`approvalId`)、同一套回执 / 拒绝读法。差别只有一处:行上没有入参 ⇒ 卡上明说
74
+ * (`argsUnavailable`),且这张卡上收到「编辑后批准」时**整次不发**(结局 `editRefused`;没有原文可编,丢掉改写再批准 =
75
+ * 批准了人没看到的原始入参)。
76
+ */
77
+ export declare function surfaceSuspendedAskAndRespond(row: LivePendingAskView, respond: RespondToolApprovalFn, signal?: AbortSignal, lane?: ToolApprovalFrameLaneOpts): Promise<ToolApprovalFrameOutcome>;
78
+ /** {@link createSuspendedAskTracker} 每次吞一张快照的产出。 */
79
+ export interface SuspendedAskDelta {
80
+ /** 首次见到、该出卡的行。 */
81
+ appeared: LivePendingAskView[];
82
+ /** 此前 appeared 过、现在不在列表里的 approvalId(已被别处决断 / 过期 / 引擎换代)⇒ 撤卡。 */
83
+ gone: string[];
84
+ }
85
+ export interface SuspendedAskTracker {
86
+ ingest(snapshot: ApprovalsFeedSnapshot | null | undefined): SuspendedAskDelta;
87
+ /**
88
+ * 流内帧腿出卡**之前**调:认领这只 ask。返回 `true` = 帧腿可以出卡(此后快照不再为它出第二张);
89
+ * 返回 `false` = 它已经从快照出过卡 / 已被认领 / 已决断 ⇒ 帧腿**跳过出卡**(两条通道同一只 ask 只有一张卡、一次决断)。
90
+ */
91
+ noteSurfacedByStream(approvalId: string): boolean;
92
+ /** 让一只已出过卡的 ask 在下一张快照里重新 `appeared`(卡被关掉 / 改写被拒 `editRefused` 之后)。 */
93
+ requeue(approvalId: string): void;
94
+ /** 这只 ask 已在本端决断 ⇒ 引擎结算前它还会在列表里待一会儿,不重新出卡;之后消失也不再报 gone。 */
95
+ noteDecided(approvalId: string): void;
96
+ }
97
+ /**
98
+ * 快照 → 出卡 / 撤卡的去重账(每个宿主会话一只;不是模块级单例)。同一 `approvalId` 只 `appeared` 一次;
99
+ * `livePending` 键缺席按「现在一条都看不到」处理已出的卡(引擎换代后旧卡没有决断口可用)。
100
+ *
101
+ * 两本账,**都不按条数淘汰活跃条目**(异源对抗复审:无差别的「满了删最旧」会让仍挂着的卡重出、漏撤):
102
+ * · `shown` = 从快照出过卡、且仍在列表里的 ask。条目随「从列表消失」离账 ⇒ 大小恒 ≤ 列表长度,不设上界。
103
+ * · `claimed` = 被流内帧腿认领 / 已在本端决断的 ask,值 = 是否已在某张快照里见过。见过之后从列表消失 ⇒ 这只 ask
104
+ * 的一生结束,离账(同 id 日后再出现是新的一只)。**从未见过**的认领才受上界管(帧先到、快照一直没列到它的那一小段),
105
+ * 满了只淘汰这一类里最旧的。
106
+ */
107
+ export declare function createSuspendedAskTracker(filter?: SuspendedAskFilter): SuspendedAskTracker;
@@ -0,0 +1,198 @@
1
+ /**
2
+ * livePendingAsk — 「悬挂的流内 ask」的三端共用读面与决断口(0.72.14)。
3
+ *
4
+ * ── 这个模块补的是哪一个缺口 ────────────────────────────────────────────────────────────────
5
+ * 流内 ask 在铸造那一刻若没有活着的宿主流可投,引擎把它转成**悬挂**(窗约一小时,不补发、不重算),此后只在
6
+ * `GET /v1/approvals` 回体的第二顶层键 `livePending` 里可发现。后台 workflow 的子代恒落这一形:宿主 turn 先结束,
7
+ * 子代稍后才撞上审批门 ⇒ 用户面零帧零卡,代理静默卡到窗尽。本模块让端从 {@link startApprovalsFeed} 的快照里
8
+ * 读到这些行、出带子代身份的卡、并经**与流内帧腿同一条链**决断。
9
+ *
10
+ * ── 🔴 四条读法 ────────────────────────────────────────────────────────────────────────────
11
+ * ① **键缺席 ≠ 没有悬挂 ask**:老引擎不发这个键 ⇒ `not_reported`;快照上键缺席,计数读 `null`(不知道),绝不铸 `[]` / `0`。
12
+ * ② **数组名即路由判据**:`livePending` 行的决议口是 `POST /v1/tool-approvals/{approvalId}/respond`(流内审批那个口),
13
+ * **不是** durable 行的 decide 口;两数组不混编。幂等键 = `approvalId`(与流内帧同一个 id)。
14
+ * ③ **可选旗标 only-if-true**:`fromSubagent` / `governanceForced` / `requiresRealApproval` 缺席绝不编码成 `false`。
15
+ * ④ **行上没有工具入参**(引擎刻意不在这个读面开第二道脱敏面)⇒ 卡必须明说「看不到入参」,不许渲一个空命令冒充全貌。
16
+ *
17
+ * sdk 的 barrel 没有导出这一行的类型 ⇒ 按**结构**窄读(与 `capabilities.executionLane` 同一姿势),多余成员不过境。
18
+ */
19
+ import { surfaceToolApprovalFrameAndRespond, } from './toolApprovalWire.js';
20
+ function nonEmptyString(v) {
21
+ return typeof v === 'string' && v !== '';
22
+ }
23
+ function nonNegativeFinite(v) {
24
+ return typeof v === 'number' && Number.isFinite(v) && v >= 0;
25
+ }
26
+ /** `GET /v1/approvals` 回体 → `livePending` 段读数。四必填位坏形的行丢弃并计数;同 `approvalId` 重复行留第一条。 */
27
+ export function readLivePendingRows(body) {
28
+ if (body === null || typeof body !== 'object' || Array.isArray(body))
29
+ return { kind: 'malformed' };
30
+ if (!('livePending' in body))
31
+ return { kind: 'not_reported' };
32
+ const raw = body.livePending;
33
+ if (raw === undefined)
34
+ return { kind: 'not_reported' };
35
+ if (!Array.isArray(raw))
36
+ return { kind: 'malformed' };
37
+ const rows = [];
38
+ const seen = new Set();
39
+ let dropped = 0;
40
+ for (const item of raw) {
41
+ if (item === null || typeof item !== 'object' || Array.isArray(item)) {
42
+ dropped++;
43
+ continue;
44
+ }
45
+ const o = item;
46
+ if (!nonEmptyString(o.approvalId) || !nonEmptyString(o.toolName) || !nonNegativeFinite(o.ts) || !nonNegativeFinite(o.expiresAtMs)) {
47
+ dropped++;
48
+ continue;
49
+ }
50
+ if (seen.has(o.approvalId)) {
51
+ dropped++;
52
+ continue;
53
+ }
54
+ seen.add(o.approvalId);
55
+ rows.push({
56
+ approvalId: o.approvalId,
57
+ toolName: o.toolName,
58
+ ts: o.ts,
59
+ expiresAtMs: o.expiresAtMs,
60
+ ...(nonEmptyString(o.sessionId) ? { sessionId: o.sessionId } : {}),
61
+ ...(o.requiresRealApproval === true ? { requiresRealApproval: true } : {}),
62
+ ...(o.governanceForced === true ? { governanceForced: true } : {}),
63
+ ...(o.fromSubagent === true ? { fromSubagent: true } : {}),
64
+ ...(nonEmptyString(o.originTaskId) ? { originTaskId: o.originTaskId } : {}),
65
+ });
66
+ }
67
+ return { kind: 'present', rows, dropped };
68
+ }
69
+ /** 快照里**出自子代**的悬挂 ask(宿主自己的流内 ask 走流内帧腿,不在这里出卡)。未报 / 没有快照 ⇒ `[]`。 */
70
+ export function suspendedSubagentAsks(snapshot, filter) {
71
+ const rows = snapshot?.livePending;
72
+ if (!Array.isArray(rows))
73
+ return [];
74
+ return rows.filter(r => r.fromSubagent === true && (filter?.sessionId === undefined || r.sessionId === filter.sessionId));
75
+ }
76
+ export function countApprovalsAwaitingDecision(snapshot, filter) {
77
+ if (snapshot === null || snapshot === undefined)
78
+ return { durable: null, live: null, suspendedSubagent: null };
79
+ const bySession = (rows) => filter?.sessionId === undefined ? [...rows] : rows.filter(r => r.sessionId === filter.sessionId);
80
+ const durable = Array.isArray(snapshot.pending) ? bySession(snapshot.pending).length : null;
81
+ if (!Array.isArray(snapshot.livePending))
82
+ return { durable, live: null, suspendedSubagent: null };
83
+ const live = bySession(snapshot.livePending);
84
+ return { durable, live: live.length, suspendedSubagent: live.filter(r => r.fromSubagent === true).length };
85
+ }
86
+ /**
87
+ * 悬挂行 → 流内帧腿的输入。只带行上**真有**的位:窗三键不带(行上只有绝对死线,没有出帧时刻,凑不出可校偏的窗),
88
+ * `args` 不带(行上没有)。`sourceTaskId` = `originTaskId`(卡头子代身份徽章的来源)。
89
+ */
90
+ export function livePendingRowToApprovalFrame(row) {
91
+ return {
92
+ type: 'tool_approval',
93
+ approvalId: row.approvalId,
94
+ toolName: row.toolName,
95
+ ...(row.fromSubagent === true ? { fromSubagent: true } : {}),
96
+ ...(row.originTaskId !== undefined ? { sourceTaskId: row.originTaskId } : {}),
97
+ ...(row.governanceForced === true ? { governanceForced: true } : {}),
98
+ ...(row.requiresRealApproval === true ? { requiresRealApproval: true } : {}),
99
+ };
100
+ }
101
+ /**
102
+ * 出卡并决断一条悬挂 ask —— **与流内帧腿同一条链**({@link surfaceToolApprovalFrameAndRespond}):同一个卡口、
103
+ * 同一个身份键铸口(`approvalId`)、同一套回执 / 拒绝读法。差别只有一处:行上没有入参 ⇒ 卡上明说
104
+ * (`argsUnavailable`),且这张卡上收到「编辑后批准」时**整次不发**(结局 `editRefused`;没有原文可编,丢掉改写再批准 =
105
+ * 批准了人没看到的原始入参)。
106
+ */
107
+ export function surfaceSuspendedAskAndRespond(row, respond, signal, lane) {
108
+ return surfaceToolApprovalFrameAndRespond(livePendingRowToApprovalFrame(row), respond, undefined, signal, {
109
+ ...lane,
110
+ argsUnavailable: true,
111
+ });
112
+ }
113
+ /** 「认领了但还没在任何一张快照里见过」的条目上界(见 {@link createSuspendedAskTracker} 的账目说明)。 */
114
+ const TRACKER_UNSEEN_CLAIMS_MAX = 512;
115
+ /**
116
+ * 快照 → 出卡 / 撤卡的去重账(每个宿主会话一只;不是模块级单例)。同一 `approvalId` 只 `appeared` 一次;
117
+ * `livePending` 键缺席按「现在一条都看不到」处理已出的卡(引擎换代后旧卡没有决断口可用)。
118
+ *
119
+ * 两本账,**都不按条数淘汰活跃条目**(异源对抗复审:无差别的「满了删最旧」会让仍挂着的卡重出、漏撤):
120
+ * · `shown` = 从快照出过卡、且仍在列表里的 ask。条目随「从列表消失」离账 ⇒ 大小恒 ≤ 列表长度,不设上界。
121
+ * · `claimed` = 被流内帧腿认领 / 已在本端决断的 ask,值 = 是否已在某张快照里见过。见过之后从列表消失 ⇒ 这只 ask
122
+ * 的一生结束,离账(同 id 日后再出现是新的一只)。**从未见过**的认领才受上界管(帧先到、快照一直没列到它的那一小段),
123
+ * 满了只淘汰这一类里最旧的。
124
+ */
125
+ export function createSuspendedAskTracker(filter) {
126
+ const shown = new Set();
127
+ const claimed = new Map();
128
+ const claim = (id) => {
129
+ if (claimed.has(id))
130
+ return;
131
+ claimed.set(id, false);
132
+ let unseen = 0;
133
+ for (const seen of claimed.values())
134
+ if (!seen)
135
+ unseen++;
136
+ if (unseen <= TRACKER_UNSEEN_CLAIMS_MAX)
137
+ return;
138
+ for (const [oldId, seen] of claimed) {
139
+ if (seen)
140
+ continue;
141
+ claimed.delete(oldId);
142
+ break;
143
+ }
144
+ };
145
+ return {
146
+ ingest(snapshot) {
147
+ const rows = suspendedSubagentAsks(snapshot, filter);
148
+ const present = new Set(rows.map(r => r.approvalId));
149
+ const appeared = [];
150
+ for (const r of rows) {
151
+ if (claimed.has(r.approvalId)) {
152
+ claimed.set(r.approvalId, true);
153
+ continue;
154
+ }
155
+ if (shown.has(r.approvalId))
156
+ continue;
157
+ shown.add(r.approvalId);
158
+ appeared.push(r);
159
+ }
160
+ const gone = [];
161
+ for (const id of [...shown]) {
162
+ if (present.has(id))
163
+ continue;
164
+ shown.delete(id);
165
+ gone.push(id);
166
+ }
167
+ // 见过、现在不在列表里的认领 = 这只 ask 已结算 ⇒ 离账
168
+ for (const [id, seen] of [...claimed])
169
+ if (seen && !present.has(id))
170
+ claimed.delete(id);
171
+ return { appeared, gone };
172
+ },
173
+ noteSurfacedByStream(approvalId) {
174
+ if (typeof approvalId !== 'string' || approvalId === '')
175
+ return false;
176
+ if (shown.has(approvalId) || claimed.has(approvalId))
177
+ return false;
178
+ claim(approvalId);
179
+ return true;
180
+ },
181
+ noteDecided(approvalId) {
182
+ if (typeof approvalId !== 'string' || approvalId === '')
183
+ return;
184
+ // 从快照出过卡的 ask 已在列表里见过 ⇒ 认领直接记「见过」,结算后随列表消失离账
185
+ const wasShown = shown.delete(approvalId);
186
+ if (wasShown)
187
+ claimed.set(approvalId, true);
188
+ else
189
+ claim(approvalId);
190
+ },
191
+ requeue(approvalId) {
192
+ if (typeof approvalId !== 'string' || approvalId === '')
193
+ return;
194
+ shown.delete(approvalId);
195
+ claimed.delete(approvalId);
196
+ },
197
+ };
198
+ }
@@ -486,6 +486,11 @@ export interface ApprovalCardRequest {
486
486
  /** wire 层降级说明(卡 content 上方 dim 行,ToolUseConfirm.wireNote 超集位)——argsOmitted 等
487
487
  * 不可注入 args 的提示走这里([1543]③ 记账的 note 位,2026-07-23 落位)。 */
488
488
  wireNote?: string;
489
+ /**
490
+ * 0.72.14:这只 ask 的工具入参在本读面上**不可得**(悬挂的流内 ask)。为真 ⇒ 卡上渲的 `args` 不是工具的真实入参,
491
+ * 端**不要**给「编辑后批准」;包侧收到带改写的批准会拒发(见 `ToolApprovalFrameOutcome.editRefused`)。缺席 ≠ false。
492
+ */
493
+ argsUnavailable?: true;
489
494
  /**
490
495
  * 治理强制位(server ≥7.5.0,2026-08-08 补透传)。**缺席 ≠ false**:只在为真时在场,缺席 = 无治理
491
496
  * 来源的证据。壳应据此把门呈成**表态掀不掉**(而不是引导用户去改 `permissionMode`);呈现形是壳
@@ -1284,6 +1289,11 @@ export interface ToolApprovalFrameOutcome {
1284
1289
  * (传输层失败)都是真实形。
1285
1290
  */
1286
1291
  respondRefusal?: ToolApprovalRespondRefusal;
1292
+ /**
1293
+ * 0.72.14:入参不可得的卡(悬挂的流内 ask)上收到了「编辑后批准」⇒ 包侧**一个字节都没发**,`decision` 为 `'unresolved'`,
1294
+ * 这只 ask 在引擎上仍然挂着。端应重新出卡(跟踪器 `requeue`)。缺席 = 没发生这件事。
1295
+ */
1296
+ editRefused?: true;
1287
1297
  }
1288
1298
  /**
1289
1299
  * #225 件5:respond 抛错的**结构化原文**(不是新的决断态,见
@@ -1484,5 +1494,11 @@ export interface ToolApprovalFrameLaneOpts {
1484
1494
  * 老宿主不传本位时行为 = 0.38.1,零回归)。live 腿的宿主接线随各端提货批补 `true`。
1485
1495
  */
1486
1496
  windowIsCurrent?: boolean;
1497
+ /**
1498
+ * 0.72.14:这只 ask 的**工具入参在本读面上不可得**(悬挂的流内 ask 从 `GET /v1/approvals` 的 `livePending` 段读出,
1499
+ * 该段刻意不带入参)。为真 ⇒ 卡上明说「看不到入参」,且「编辑后批准」的改写不转发(没有原文可编,转发一个空对象
1500
+ * 会把工具的真实入参换掉)—— 收到带改写的批准时**整次不发**,结局标 `editRefused`。只由 `surfaceSuspendedAskAndRespond` 置;流内帧腿不传。
1501
+ */
1502
+ argsUnavailable?: true;
1487
1503
  }
1488
1504
  export declare function surfaceToolApprovalFrameAndRespond(frame: ToolApprovalFrame, respond: RespondToolApprovalFn, streamArgs: unknown | undefined, signal?: AbortSignal, lane?: ToolApprovalFrameLaneOpts): Promise<ToolApprovalFrameOutcome>;
@@ -87,7 +87,7 @@ import { askParkRowArm } from './askParkRowRouting.js';
87
87
  import { hostLog } from '../host.js';
88
88
  import { createSessionSlot, DEFAULT_SESSION_KEY } from '../sessionSlot.js';
89
89
  import { readEngineActiveBgTasks } from '../fleet/fleetLedger.js';
90
- import { observeCancelByDeny, surfaceRememberNotApplied, surfaceEditNotForwarded, surfaceRuleArmNotSent, surfaceRuleArmRejected } from './hitlHostSurface.js';
90
+ import { observeCancelByDeny, surfaceRememberNotApplied, surfaceEditNotForwarded, surfaceEditRefusedOnBlindAsk, surfaceRuleArmNotSent, surfaceRuleArmRejected } from './hitlHostSurface.js';
91
91
  import { approvalCallKey, liveFrameCallKey } from './gateIdentity.js';
92
92
  // 0.67.0(core 7.14.0 #688 C3):`ruleStoreUnreadable` 的**闭二词判据**。词表属主 = core
93
93
  // (`RULE_STORE_UNREADABLE_KINDS`),镜像与措辞都在 `gateVocabulary.ts` 那个唯一铸点上 ——
@@ -1353,6 +1353,12 @@ export async function surfaceToolApprovalFrameAndRespond(frame, respond, streamA
1353
1353
  wireNote = 'tool arguments exceeded the wire cap and were omitted — the diff below is reconstructed from the gate message, not the full payload';
1354
1354
  hostLog('debug', `liveToolApprovalWire: frame ${frame.approvalId} args omitted (>16KiB wire cap) — card falls back to message-derived path`);
1355
1355
  }
1356
+ if (lane?.argsUnavailable === true) {
1357
+ wireNote =
1358
+ "this request's tool arguments are not available on this surface — it was raised while no client was connected, " +
1359
+ 'so you are deciding without seeing them; deny it if you are not sure what it will do. ' +
1360
+ 'Edits are not accepted on this card: there is no original input to edit';
1361
+ }
1356
1362
  const p = pathFromGateMessage(frame.message);
1357
1363
  args = p !== undefined ? { file_path: p } : {};
1358
1364
  }
@@ -1382,6 +1388,7 @@ export async function surfaceToolApprovalFrameAndRespond(frame, respond, streamA
1382
1388
  ...(signal ? { signal } : {}),
1383
1389
  ...(isFromSubagent(frame) ? { workerBadge: subagentBadgeFor(frame) } : {}),
1384
1390
  ...(wireNote !== undefined ? { wireNote } : {}),
1391
+ ...(lane?.argsUnavailable === true ? { argsUnavailable: true } : {}),
1385
1392
  ...(frame.governanceForced === true ? { governanceForced: true } : {}),
1386
1393
  // [4851]:窗三键透传,三闸合取(异源对抗复审 P1/P2 收编):
1387
1394
  // ① `lane.windowIsCurrent === true` —— durable 账本重放的历史帧带的是铸帧时刻的旧余量,
@@ -1468,6 +1475,14 @@ export async function surfaceToolApprovalFrameAndRespond(frame, respond, streamA
1468
1475
  // `"policy"`**:那是一个正面事实(出自部署 ToolPolicy),把「老引擎没报」折进去 = 替引擎编话。
1469
1476
  ...(typeof frame.origin === 'string' && frame.origin !== '' ? { origin: frame.origin } : {}),
1470
1477
  });
1478
+ // 0.72.14:入参不可得的卡上收到「编辑后批准」⇒ **什么都不发**。丢掉改写再发 allow = 批准了人没看到、也不是他改成的那份
1479
+ // 原始入参;把改写转发出去 = 拿卡上那个空对象替换工具的真实入参。两条都不是人按下的那个决定 ⇒ 这只 ask 保持悬挂,
1480
+ // 结局如实标 `editRefused`,由端重新出卡(异源对抗复审 [high])。
1481
+ if (lane?.argsUnavailable === true && card.kind === 'allow' && card.updatedInput !== undefined) {
1482
+ hostLog('error', `liveToolApprovalWire: ${frame.approvalId} card returned an edited approval on an args-unavailable ask — nothing sent (the ask stays pending)`);
1483
+ surfaceEditRefusedOnBlindAsk();
1484
+ return { decision: 'unresolved', editRefused: true };
1485
+ }
1471
1486
  const decision = card.kind === 'allow' ? (card.allowSession ? 'allow_session' : 'allow') : 'deny';
1472
1487
  if (card.kind === 'failed') {
1473
1488
  hostLog('debug', `liveToolApprovalWire: approval card unavailable (${card.reason}) — fail-closed deny for ${frame.approvalId}`);
package/dist/index.d.ts CHANGED
@@ -267,6 +267,7 @@ export * from './hitl/resumeRunningCard.js';
267
267
  export * from './hitl/persistedRulesWire.js';
268
268
  export * from './hitl/localAllowRule.js';
269
269
  export * from './hitl/approvalsFeed.js';
270
+ export * from './hitl/livePendingAsk.js';
270
271
  export * from './hitl/crashConverged.js';
271
272
  export * from './interactiveHalt.js';
272
273
  export * from './compensations.js';
package/dist/index.js CHANGED
@@ -492,6 +492,8 @@ export * from './hitl/localAllowRule.js';
492
492
  // B7 ③(census G20,**行为改动**不是搬迁):pending-approvals 推送 feed(stream 优先 / 断流回落
493
493
  // 轮询 / 定期再试)。🔴 它**不替换** D-1 的取件 —— 那三处必须继续走权威 `list()`(见文件头)。
494
494
  export * from './hitl/approvalsFeed.js';
495
+ // 0.72.14:悬挂的流内 ask(`GET /v1/approvals` 的 `livePending` 段)的读面、视图、去重账与决断口。
496
+ export * from './hitl/livePendingAsk.js';
495
497
  // ── L-38:`/v1/approvals` additive 键 `crashConverged` 的读面 + 纯投影 ─────────────────────────
496
498
  // local 引擎崩在审批门上时,那些孤儿 ask 被 server 重启后收敛成 DENIED 同码;这一键把「上一条命
497
499
  // 留下了什么」交到端手上。收在库里的理由是**两处判定**三端各写一遍必然各错一遍:① **缺席 vs
@@ -19,11 +19,11 @@
19
19
 
20
20
  | 项 | 值 | 真源 |
21
21
  |---|---|---|
22
- | 本包 | `@sema-agent/client-core` **0.72.13**(本批发布版 = **patch**:server 7.86.0 提货 —— CC-46 plan_review `permissionModeAfter` 发口(三选卡双闸:宿主声明只读起步 ∧ 引擎 ≥7.86.0;400 field_conflict 两种处置)+ CC-54 `capabilities.executionLane` 四态读面(键缺席 ≠ 不是本机车道;`toolsRunHere` 判据归包)+ CC-53 detach 版本闸三态;型面 additive(另撤回 0.72.12 的主通道 `absent` 臂,改独立缺席订阅口)、公面 1011、peer 不动;§60;**预告 0.73.0 BREAKING**:fleet 视图数值键改可选。上一版 0.72.12 = **patch**:「缺席被折成正向终态」按类清剿 —— CC-50 M1 头条(fleet 行缺席改发 `absent` 不再铸 `end{isError:false}`)+ 六站一次改齐(缺席 ⇒ `unknown`)+ 结果边界 fail-closed 取舍 + `_sema_outcome` + `Outcome unknown:` 行 + CC-51 M6(default 臂不再谎报 running)+ 普查门 + DS-06;型面 additive、公面 993、peer 不动;行为面三处真变化见 CHANGELOG 取舍段;§59。上一版 0.72.11 = **patch**:core 7.23.0 提货 —— CC-49 通告码 66 → 67 `config.write_protection_unresolved`(operator;兄弟码 `target_view_absent` = 没有视图,这枚 = 有视图解析不出)+ `permission_denials` 自动升级腿通电(`gates[].toolCallId` ⇒ `tool_use_id`;CC-34 引擎半场);型面零变化、公面 989、peer 不动、devDep core ~7.23.0;§58。上一版 0.72.10 = **patch**:CC-41 leader run 终局 `needs_human` + `result.conflict` 三形读面(none 缺席不可分 / unreadable 在场读不懂 / conflict;`salvaged[]` 原样给落盘口;措辞单源不提重试);型面纯 additive、公面 989、peer 不动;§57。上一版 0.72.9 = **patch**:server 7.85.0 提货 —— CC-30 会话内 MCP re-dial 消费口(`reconnectMcpServer` 三态 / unsupported 恒五键 / 交易文案单源)+ CC-39② memory-status `autoConsolidationArmed` 三态 + CC-45 resume 补充句渲保留秒数;型面纯 additive、公面 985、peer 不动;§56。上一版 0.72.8 = **patch**:清账三票 —— `memory:"off"` 请求键(CC-39③,server 7.84.0 S-405)+ `runStream` 第二条中性臂 `Model output error:`(CC-43)+ 注入件撞活 run 自动 steer 零卡(CC-44);型面纯 additive、公面 977、peer 不动;§55。上一版 0.72.7 = **patch**:core 7.22.0 提货 —— 通告码 66 `mcp.server_redialed` + 读器 `readMcpServerRedialed` + `wiring_manifest.readDeny` 第九段 `_sema_readDeny`(缺席 = 没有内置行判这条腿);型面纯 additive、公面 971、peer 不动、devDep core ~7.22.0;§54。上一版 0.72.6 = **patch**:cli L-379 包侧半场 —— `steerFollowIntent` steer 后续听意图三态(tail / after-decision / after-wake)+ `activeRunSelfHealRow` 续听声明第五参;型面纯 additive、公面 969、peer 不动;§53。上一版 0.72.5 = **patch**:server 7.82.1 提货 —— `capabilities.webSearch.backend` 部署默认 WebSearch 后端四态读面(键缺席 = 老引擎判不了 ≠ `none`);型面纯 additive、公面 968、peer 不动;§52。上一版 0.72.4 = **patch**:core 7.21.x 三件经 server 7.82.0 提货 —— `wiring_manifest.autoConsolidation` 第八段 `_sema_autoConsolidation`(缺席 = indeterminate 不折 false);型面纯 additive、公面 962、peer 不动;§51。上一版 0.72.3 = **patch**:core #863 记忆单系统半场 ① —— `projectAgentsForWire` 投影 `agents[].memory`(user|project|local 三词同映射引擎平面 agent:<type>);型面纯 additive、公面 962、零 wire 新键、peer 不动;§50。上一版 0.72.2 = **patch**:workflow 视图两票 —— `LiveWorkflowController.resolution()` 六态(snapshot() 的 null 三义拆开)+ `noteOwnWorkflowRun` own 台账同拍登记;型面纯 additive、公面 961、零 wire 键、peer 不动;§49。上一版 0.72.1 = **patch**:server 7.80.1 提货 —— 409 `conflict.resume_context_unavailable` 结构读数(`staleAfterSec` / `runId`;sdk 9.6.0 尚未带键,经 sdk 今天恒缺席)+ `SkillSpec.baseDir`(只在本机车道带);型面纯 additive、公面 960、peer 不动;§48。上一版 0.72.0 = **minor**:型面 BREAKING —— 39 个零消费导出退出公面(CC-32 退役批,[7419] §45 预告 / cli [7422] 同意 / web-client·desktop 过节拍沉默 = 同意;名单见 §47a S-1);运行期零 BREAKING、公面 956、peer 不动、devDep core ~7.20.1;随批:已退役名账 `removed` + export-liveness 门 G 段 + 冻结账门 ⑧c;§47。上一版 0.71.4 = **patch**:core 7.20.1 提货 —— `ENGINE_NOTICE_CODES` 65 + `wiring_manifest.writeProtection` 第七段 `_sema_writeProtection`;型面纯 additive、公面 995、peer 不动、devDep core `~7.20.1`;§46。上一版 0.71.3 = **patch**:门体系清账批 —— CC-24 export-liveness 门 + 39 个导出退役预告(0.72.0 删,表态制)/ CC-26 `as unknown as` 铸链棘轮 / CC-25 冻结账门 ⑧ 段 / CC-06 B-081 包侧半场(终帧 `_sema_effective_reasoning` / `_sema_effective_memory_scopes` + resume reopen 族 + `routePairingVerdict`);型面纯 additive、公面 986 → 995、peer 不动;§45。上一版 0.71.2 = **patch**:接入方验收两票归包 —— CC-28 B-132 decide 拒体三句去内部车道词 / CC-29 B-133 committedUuids 唯一性(重复 ⇒ 形 f);零型面变化、公面 986、peer 不动;§44。上一版 0.71.1 = **patch**:接入档打回修正 —— CC-27 `ApprovalCardRequest.readRootCandidate?` 帧→卡透传位 + 新导出型 `ReadRootCandidateView`;型面纯 additive、公面 986 不变、peer 不动;§43。上一版 0.71.0 = **minor**:型面 BREAKING 六件 —— peer sdk 地板 >=9.6.0 + agent-types >=0.3.0 / `SkillsListingTruncatedFactsView` 删 `requestedBudgetBytes?`·`laneClamped?` / `WiringManifestMcpEntry` → `WiringManifestMcpEntryView` / `MAX_AGENT_SKILLS` 删 / `decide.workflow_host_not_parked` 退役;运行期零 BREAKING。内容:core 7.20.0 提货(闭集 +5 六十四码)/ 🔴 CC-16 `text_segment_end.committedUuids[]` / CC-08 全五件(tool_disclosure / tool_progress 两 chrome 臂、wiring_manifest tools·hooks·lsp 进切片、tool_roster_delta 接上、context_usage 进切片、`readRootCandidate` 镜像)/ CC-19 `mcpEngineLegPresence` 归包 / CC-20 not_parked clean-cut;公面 986;§42 / 42y 八条)|
22
+ | 本包 | `@sema-agent/client-core` **0.72.14**(本批发布版 = **patch**:悬挂的流内 ask 拉半场 —— CC-55 `startApprovalsFeed` 快照加 `livePending` 段并进变化摘要(键缺席 = 未报,不铸空)+ 视图 / 去重账 / 与流内帧腿同链的决断口(入参不可见上卡明说、改写不转发)+ 宿主闸对账节拍 + 待决计数读口(不知道 = null);型面纯 additive、公面 1019、peer 不动;§61。上一版 0.72.13 = **patch**:server 7.86.0 提货 —— CC-46 plan_review `permissionModeAfter` 发口(三选卡双闸:宿主声明只读起步 ∧ 引擎 ≥7.86.0;400 field_conflict 两种处置)+ CC-54 `capabilities.executionLane` 四态读面(键缺席 ≠ 不是本机车道;`toolsRunHere` 判据归包)+ CC-53 detach 版本闸三态;型面 additive(另撤回 0.72.12 的主通道 `absent` 臂,改独立缺席订阅口)、公面 1011、peer 不动;§60;**预告 0.73.0 BREAKING**:fleet 视图数值键改可选。上一版 0.72.12 = **patch**:「缺席被折成正向终态」按类清剿 —— CC-50 M1 头条(fleet 行缺席改发 `absent` 不再铸 `end{isError:false}`)+ 六站一次改齐(缺席 ⇒ `unknown`)+ 结果边界 fail-closed 取舍 + `_sema_outcome` + `Outcome unknown:` 行 + CC-51 M6(default 臂不再谎报 running)+ 普查门 + DS-06;型面 additive、公面 993、peer 不动;行为面三处真变化见 CHANGELOG 取舍段;§59。上一版 0.72.11 = **patch**:core 7.23.0 提货 —— CC-49 通告码 66 → 67 `config.write_protection_unresolved`(operator;兄弟码 `target_view_absent` = 没有视图,这枚 = 有视图解析不出)+ `permission_denials` 自动升级腿通电(`gates[].toolCallId` ⇒ `tool_use_id`;CC-34 引擎半场);型面零变化、公面 989、peer 不动、devDep core ~7.23.0;§58。上一版 0.72.10 = **patch**:CC-41 leader run 终局 `needs_human` + `result.conflict` 三形读面(none 缺席不可分 / unreadable 在场读不懂 / conflict;`salvaged[]` 原样给落盘口;措辞单源不提重试);型面纯 additive、公面 989、peer 不动;§57。上一版 0.72.9 = **patch**:server 7.85.0 提货 —— CC-30 会话内 MCP re-dial 消费口(`reconnectMcpServer` 三态 / unsupported 恒五键 / 交易文案单源)+ CC-39② memory-status `autoConsolidationArmed` 三态 + CC-45 resume 补充句渲保留秒数;型面纯 additive、公面 985、peer 不动;§56。上一版 0.72.8 = **patch**:清账三票 —— `memory:"off"` 请求键(CC-39③,server 7.84.0 S-405)+ `runStream` 第二条中性臂 `Model output error:`(CC-43)+ 注入件撞活 run 自动 steer 零卡(CC-44);型面纯 additive、公面 977、peer 不动;§55。上一版 0.72.7 = **patch**:core 7.22.0 提货 —— 通告码 66 `mcp.server_redialed` + 读器 `readMcpServerRedialed` + `wiring_manifest.readDeny` 第九段 `_sema_readDeny`(缺席 = 没有内置行判这条腿);型面纯 additive、公面 971、peer 不动、devDep core ~7.22.0;§54。上一版 0.72.6 = **patch**:cli L-379 包侧半场 —— `steerFollowIntent` steer 后续听意图三态(tail / after-decision / after-wake)+ `activeRunSelfHealRow` 续听声明第五参;型面纯 additive、公面 969、peer 不动;§53。上一版 0.72.5 = **patch**:server 7.82.1 提货 —— `capabilities.webSearch.backend` 部署默认 WebSearch 后端四态读面(键缺席 = 老引擎判不了 ≠ `none`);型面纯 additive、公面 968、peer 不动;§52。上一版 0.72.4 = **patch**:core 7.21.x 三件经 server 7.82.0 提货 —— `wiring_manifest.autoConsolidation` 第八段 `_sema_autoConsolidation`(缺席 = indeterminate 不折 false);型面纯 additive、公面 962、peer 不动;§51。上一版 0.72.3 = **patch**:core #863 记忆单系统半场 ① —— `projectAgentsForWire` 投影 `agents[].memory`(user|project|local 三词同映射引擎平面 agent:<type>);型面纯 additive、公面 962、零 wire 新键、peer 不动;§50。上一版 0.72.2 = **patch**:workflow 视图两票 —— `LiveWorkflowController.resolution()` 六态(snapshot() 的 null 三义拆开)+ `noteOwnWorkflowRun` own 台账同拍登记;型面纯 additive、公面 961、零 wire 键、peer 不动;§49。上一版 0.72.1 = **patch**:server 7.80.1 提货 —— 409 `conflict.resume_context_unavailable` 结构读数(`staleAfterSec` / `runId`;sdk 9.6.0 尚未带键,经 sdk 今天恒缺席)+ `SkillSpec.baseDir`(只在本机车道带);型面纯 additive、公面 960、peer 不动;§48。上一版 0.72.0 = **minor**:型面 BREAKING —— 39 个零消费导出退出公面(CC-32 退役批,[7419] §45 预告 / cli [7422] 同意 / web-client·desktop 过节拍沉默 = 同意;名单见 §47a S-1);运行期零 BREAKING、公面 956、peer 不动、devDep core ~7.20.1;随批:已退役名账 `removed` + export-liveness 门 G 段 + 冻结账门 ⑧c;§47。上一版 0.71.4 = **patch**:core 7.20.1 提货 —— `ENGINE_NOTICE_CODES` 65 + `wiring_manifest.writeProtection` 第七段 `_sema_writeProtection`;型面纯 additive、公面 995、peer 不动、devDep core `~7.20.1`;§46。上一版 0.71.3 = **patch**:门体系清账批 —— CC-24 export-liveness 门 + 39 个导出退役预告(0.72.0 删,表态制)/ CC-26 `as unknown as` 铸链棘轮 / CC-25 冻结账门 ⑧ 段 / CC-06 B-081 包侧半场(终帧 `_sema_effective_reasoning` / `_sema_effective_memory_scopes` + resume reopen 族 + `routePairingVerdict`);型面纯 additive、公面 986 → 995、peer 不动;§45。上一版 0.71.2 = **patch**:接入方验收两票归包 —— CC-28 B-132 decide 拒体三句去内部车道词 / CC-29 B-133 committedUuids 唯一性(重复 ⇒ 形 f);零型面变化、公面 986、peer 不动;§44。上一版 0.71.1 = **patch**:接入档打回修正 —— CC-27 `ApprovalCardRequest.readRootCandidate?` 帧→卡透传位 + 新导出型 `ReadRootCandidateView`;型面纯 additive、公面 986 不变、peer 不动;§43。上一版 0.71.0 = **minor**:型面 BREAKING 六件 —— peer sdk 地板 >=9.6.0 + agent-types >=0.3.0 / `SkillsListingTruncatedFactsView` 删 `requestedBudgetBytes?`·`laneClamped?` / `WiringManifestMcpEntry` → `WiringManifestMcpEntryView` / `MAX_AGENT_SKILLS` 删 / `decide.workflow_host_not_parked` 退役;运行期零 BREAKING。内容:core 7.20.0 提货(闭集 +5 六十四码)/ 🔴 CC-16 `text_segment_end.committedUuids[]` / CC-08 全五件(tool_disclosure / tool_progress 两 chrome 臂、wiring_manifest tools·hooks·lsp 进切片、tool_roster_delta 接上、context_usage 进切片、`readRootCandidate` 镜像)/ CC-19 `mcpEngineLegPresence` 归包 / CC-20 not_parked clean-cut;公面 986;§42 / 42y 八条)|
23
23
  | peer:wire 契约 | `@sema-agent/sdk` **>=9.6.0**(value-level,非 type-only;**0.71.0 抬版**:`tool_disclosure` / `tool_progress` 两帧型与 `ToolApprovalFrame.readRootCandidate` 自 9.6.0 起;0.69.0 曾抬到 >=9.4.0(`reasoning_end` / `lastLegMcp`),0.60.0 抬到 >=8.4.0;地板由 `scripts/run-sdk-floor-test.mjs` 的 `FLOOR` 与 public-surface 门 C-CC-3「不低于见证到的最低实装版」双钉)| `package.json` `peerDependencies` |
24
24
  | peer:会话词汇表 | `@sema-agent/agent-types` **>=0.3.0**(type-only,零运行时;0.71.0 抬版:devDep 与 peer 同步,type-superset 门 ④ 撞名预警对着 0.3.0 的 .d.ts 求值) | 同上 |
25
25
  | runtime dep | `diff` ^9.0.0(**唯一**一条;portability 门按**等值**钉死) | `package.json` `dependencies` |
26
- | 公开导出面 | **1011** 个运行期符号(+ 47 个测试钩;= 0.72.13 的值:相对 0.72.12 +18 —— CC-46 八件 / CC-54 六件 / CC-53 两件 / CC-50 缺席口两件,名单见 §2b 域 3 / 4 / 9 / 10;993 = 0.72.12 的值(测试钩 45):相对 0.72.11 +4 —— CC-50 `OUTCOME_UNKNOWN_ROW_PREFIX` / `isOutcomeUnknownRowText` / `TASK_NOTIFICATION_TERMINAL_STATUSES` / `isTaskNotificationTerminalStatus`;989 = 0.72.10 的值:相对 0.72.9 +4 —— CC-41 `LEADER_RUN_STATUSES` / `LEADER_REJ_HEAD_DISPLAY_MAX` / `projectLeaderConflict` / `leaderConflictDetail`;985 = 0.72.9 的值:相对 0.72.8 +8 —— CC-30 `MCP_RECONNECT_OUTCOMES` / `MCP_RECONNECT_TRANSACTION_NOTICE` / `projectMcpReconnectResult` / `classifyMcpReconnectFailure` / `reconnectMcpServer` / `projectMcpReconnectCapability` / `mcpReconnectOutcomeDetail`、CC-39② `readAutoConsolidationArmed`;977 = 0.72.8 的值:相对 0.72.7 +6 —— CC-39③ `MEMORY_OFF` / `memoryOffDeclarationField` / `memoryOffDeclared`、CC-43 `MODEL_OUTPUT_ERROR_PREFIX` / `isModelOutputErrorText` / `isModelOutputErrorRowText`;971 = 0.72.7 的值:相对 0.72.6 +2 —— CC-42 core 7.22.0 提货 `MCP_REDIAL_OUTCOMES` / `readMcpServerRedialed`;969 = 0.72.6 的值:相对 0.72.5 +1 —— CC-40 `steerFollowIntent`;968 = 0.72.5 的值:相对 0.72.4 +6 —— CC-31 server 7.82.1 提货 `capabilities.webSearch.backend` 四态读面:`WEB_SEARCH_BACKEND_NONE` / `projectWebSearchBackendCapability` / `observedWebSearchBackend` / `noteEngineCapsForWebSearchBackend` / `forgetWebSearchBackendReading` / `webSearchBackendDoctorDetail`;962 = 0.72.3 的值:相对 0.72.2 +1 —— CC-39① `AGENT_MEMORY_WORDS`;0.72.2 相对 0.72.1 +1 —— CC-37 `noteOwnWorkflowRun`;0.72.1 相对 0.72.0 +4 —— CC-33 server 7.80.1 提货:`RESUME_CONTEXT_UNAVAILABLE` / `resumeContextUnavailableFromError` / `resumeContextUnavailableContent` / `skillBaseDirOf`;0.72.0 相对 0.71.4 −39 —— CC-32 退役批删 export-liveness 登记 retire 的 39 个零消费导出(23 常量 + 16 函数,名单见 §47);995 = 0.71.3 的值:相对 0.71.2 +9 —— CC-06 `readEffectiveReasoning` / `readEffectiveMemoryScopes` / `resumeReopenFromError` / `resumeReopenContent` / `routePairingVerdict` 五函数 + `RESUME_REOPEN_CODES` / `RESUME_ENV_FAILED` / `RESUME_TOOL_UNAVAILABLE` / `RESUME_TOOL_CONTRACT_MISMATCH` 四常量;986 = 0.71.0 的值:相对 0.70.2 −`MAX_AGENT_SKILLS` −`DECIDE_WORKFLOW_HOST_NOT_PARKED` +`projectToolRosterDelta` +`mcpEngineLegPresence`;986 = 0.70.0 的值:相对 0.69.2 **一件 additive(`readSkillsListingTruncated`,§39)、零删除**;985 = 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`(四个单码常量 🔧 0.72.0 退役)/`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`(两个探针常量 🔧 0.72.0 退役)/`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.72.0 退役);`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
+ | 公开导出面 | **1019** 个运行期符号(+ 47 个测试钩;= 0.72.14 的值:相对 0.72.13 +8 —— CC-55 八件,名单见 §2b 域 3;1011 = 0.72.13 的值:相对 0.72.12 +18 —— CC-46 八件 / CC-54 六件 / CC-53 两件 / CC-50 缺席口两件,名单见 §2b 域 3 / 4 / 9 / 10;993 = 0.72.12 的值(测试钩 45):相对 0.72.11 +4 —— CC-50 `OUTCOME_UNKNOWN_ROW_PREFIX` / `isOutcomeUnknownRowText` / `TASK_NOTIFICATION_TERMINAL_STATUSES` / `isTaskNotificationTerminalStatus`;989 = 0.72.10 的值:相对 0.72.9 +4 —— CC-41 `LEADER_RUN_STATUSES` / `LEADER_REJ_HEAD_DISPLAY_MAX` / `projectLeaderConflict` / `leaderConflictDetail`;985 = 0.72.9 的值:相对 0.72.8 +8 —— CC-30 `MCP_RECONNECT_OUTCOMES` / `MCP_RECONNECT_TRANSACTION_NOTICE` / `projectMcpReconnectResult` / `classifyMcpReconnectFailure` / `reconnectMcpServer` / `projectMcpReconnectCapability` / `mcpReconnectOutcomeDetail`、CC-39② `readAutoConsolidationArmed`;977 = 0.72.8 的值:相对 0.72.7 +6 —— CC-39③ `MEMORY_OFF` / `memoryOffDeclarationField` / `memoryOffDeclared`、CC-43 `MODEL_OUTPUT_ERROR_PREFIX` / `isModelOutputErrorText` / `isModelOutputErrorRowText`;971 = 0.72.7 的值:相对 0.72.6 +2 —— CC-42 core 7.22.0 提货 `MCP_REDIAL_OUTCOMES` / `readMcpServerRedialed`;969 = 0.72.6 的值:相对 0.72.5 +1 —— CC-40 `steerFollowIntent`;968 = 0.72.5 的值:相对 0.72.4 +6 —— CC-31 server 7.82.1 提货 `capabilities.webSearch.backend` 四态读面:`WEB_SEARCH_BACKEND_NONE` / `projectWebSearchBackendCapability` / `observedWebSearchBackend` / `noteEngineCapsForWebSearchBackend` / `forgetWebSearchBackendReading` / `webSearchBackendDoctorDetail`;962 = 0.72.3 的值:相对 0.72.2 +1 —— CC-39① `AGENT_MEMORY_WORDS`;0.72.2 相对 0.72.1 +1 —— CC-37 `noteOwnWorkflowRun`;0.72.1 相对 0.72.0 +4 —— CC-33 server 7.80.1 提货:`RESUME_CONTEXT_UNAVAILABLE` / `resumeContextUnavailableFromError` / `resumeContextUnavailableContent` / `skillBaseDirOf`;0.72.0 相对 0.71.4 −39 —— CC-32 退役批删 export-liveness 登记 retire 的 39 个零消费导出(23 常量 + 16 函数,名单见 §47);995 = 0.71.3 的值:相对 0.71.2 +9 —— CC-06 `readEffectiveReasoning` / `readEffectiveMemoryScopes` / `resumeReopenFromError` / `resumeReopenContent` / `routePairingVerdict` 五函数 + `RESUME_REOPEN_CODES` / `RESUME_ENV_FAILED` / `RESUME_TOOL_UNAVAILABLE` / `RESUME_TOOL_CONTRACT_MISMATCH` 四常量;986 = 0.71.0 的值:相对 0.70.2 −`MAX_AGENT_SKILLS` −`DECIDE_WORKFLOW_HOST_NOT_PARKED` +`projectToolRosterDelta` +`mcpEngineLegPresence`;986 = 0.70.0 的值:相对 0.69.2 **一件 additive(`readSkillsListingTruncated`,§39)、零删除**;985 = 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`(四个单码常量 🔧 0.72.0 退役)/`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`(两个探针常量 🔧 0.72.0 退役)/`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.72.0 退役);`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
 
@@ -123,7 +123,7 @@ SDK 核对「本包 import 的每个值级符号仍然导出」「`TaskStats.cos
123
123
 
124
124
  > 📦 **随包 / 不随包**:npm 包只带 `dist` / `README` / `CHANGELOG` / 本档 / `LICENSE`。本档各处提到的 `docs/type-superset.json`、`scripts/*.json`、`scripts/run-*-test.mjs` 都是**仓内登记物与门,不随包发出** —— 装包的消费者找不到它们是预期;`_sema_` 超集键的**随包契约**就是本档各 §NNz 逐键处置表(键名 / 值域 / 缺席语义逐条在表里)。
125
125
  >
126
- > 全集真源 = `scripts/public-export-baseline.json` 的 `names`(**1011** 项)。
126
+ > 全集真源 = `scripts/public-export-baseline.json` 的 `names`(**1019** 项)。
127
127
  > 本节**不逐名抄**,只给「域 → 承重导出 → 用途 → 实现锚」。承重导出 = 一个端为了让这个域干活
128
128
  > **必须**直接调到的那几个符号;其余是它们的类型、变体与辅助位。
129
129
  > 单一入口:`import { … } from '@sema-agent/client-core'`(`exports` 只有 `.` 一个;
@@ -147,18 +147,18 @@ SDK 核对「本包 import 的每个值级符号仍然导出」「`TaskStats.cos
147
147
  `WorkflowsGateUnknownDenial` 四形**不在**基线里,`src/selfOrchestrationDenial.ts` 对基线贡献
148
148
  **4** 项运行期导出(三个函数 + `SELF_ORCHESTRATION_RETRY_WITHOUT`)。
149
149
 
150
- 1011 项的内部构成(帮助端估读表大小;0.72.13 +4 常量 +14 函数(CC-46 / CC-54 / CC-53 / CC-50 缺席口);0.72.12 +2 常量 +2 函数(CC-50);0.72.10 +2 常量 +2 函数(CC-41);0.72.9 +2 常量 +6 函数(CC-30 / CC-39②);0.72.8 +2 常量 +4 函数(CC-39③ / CC-43);0.72.7 +1 常量 +1 函数(CC-42);0.72.6 +1 函数(CC-40);0.72.5 +1 常量 +5 函数(CC-31);0.72.3 +1 常量(CC-39①);0.72.2 +1 函数(CC-37);0.72.1 +1 常量 +3 函数(CC-33);0.72.0 −23 常量 −16 函数(CC-32 退役批);0.71.3 +4 常量 +5 函数(CC-06);0.71.0 −2 常量 `MAX_AGENT_SKILLS` / `DECIDE_WORKFLOW_HOST_NOT_PARKED` +2 函数 `projectToolRosterDelta` / `mcpEngineLegPresence`;0.70.0 起 +1 函数;0.69.2 起 +3:常量 1 / 函数 2):**294** 项是 `SCREAMING_SNAKE` 常量数据表/词汇表
150
+ 1019 项的内部构成(帮助端估读表大小;0.72.14 +1 常量 +7 函数(CC-55);0.72.13 +4 常量 +14 函数(CC-46 / CC-54 / CC-53 / CC-50 缺席口);0.72.12 +2 常量 +2 函数(CC-50);0.72.10 +2 常量 +2 函数(CC-41);0.72.9 +2 常量 +6 函数(CC-30 / CC-39②);0.72.8 +2 常量 +4 函数(CC-39③ / CC-43);0.72.7 +1 常量 +1 函数(CC-42);0.72.6 +1 函数(CC-40);0.72.5 +1 常量 +5 函数(CC-31);0.72.3 +1 常量(CC-39①);0.72.2 +1 函数(CC-37);0.72.1 +1 常量 +3 函数(CC-33);0.72.0 −23 常量 −16 函数(CC-32 退役批);0.71.3 +4 常量 +5 函数(CC-06);0.71.0 −2 常量 `MAX_AGENT_SKILLS` / `DECIDE_WORKFLOW_HOST_NOT_PARKED` +2 函数 `projectToolRosterDelta` / `mcpEngineLegPresence`;0.70.0 起 +1 函数;0.69.2 起 +3:常量 1 / 函数 2):**295** 项是 `SCREAMING_SNAKE` 常量数据表/词汇表
151
151
  (矩阵、键集、env 名、锚串)而非可调用物;**6** 项是 PascalCase 运行期值
152
152
  (`ControlRouter` / `ControlSafetyError` / `HitlBridge` / `HitlSafetyError` / `DecideTransportRetryExhaustedError`);
153
153
  **37** 项是 `*For(sessionKey, …)` 的 per-session 变体(0.72.0 −4:`clearArmedGateFor` / `onGateArmedFor` / `registerArmedGateFromQuestionIdFor` / `notePlanReviewAnsweredIfDecisiveFor` 退出公面)(§6;其中 `engineNamespaceKeyFor` 是命名巧合 —— 参数是 baseUrl 不是 sessionKey,见域 14)。
154
154
 
155
- ### 2b. 域图(16 域,逐域计数之和 = 1011)
155
+ ### 2b. 域图(16 域,逐域计数之和 = 1019)
156
156
 
157
157
  | # | 域 | 名数 | 承重导出 | 用途 | 实现锚 |
158
158
  |---|---|---|---|---|---|
159
159
  | 1 | **适配内核(下行主链)** | 62 | `OUTCOME_UNKNOWN_ROW_PREFIX` / `isOutcomeUnknownRowText`(0.72.12 CC-50:第三条中性终局行「结局不知道」,读结果帧 `_sema_outcome`;§59)· `MODEL_OUTPUT_ERROR_PREFIX` / `isModelOutputErrorText` / `isModelOutputErrorRowText`(0.72.8 CC-43:runStream 第二条中性臂 —— core malformed tool-call 一族的行身份;§55)· `readEffectiveReasoning` · `readEffectiveMemoryScopes`(0.71.3 CC-06:终帧 `_sema_effective_reasoning` / `_sema_effective_memory_scopes` 的窄读器,§45)· `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` |
160
160
  | 2 | **seam 公共契约** | 2(其余为 type-only) | `CHROME_ARMS` · `deriveTranscriptId` | 公共词汇 + **id 确定性不变量**(同一条流重放 ⇒ 同一串 id)。`CHROME_ARMS` = 端「我要消费哪些 chrome 臂」的对照清单 | `src/seam.ts` |
161
- | 3 | **HITL 决断卡链**(§4/§5 主战场) | 168 | `planReviewCardOptions` / `planReviewChoiceFromAnswer` / `versionSupportsPlanReviewModeAfter` / `isPlanReviewModeAfter` + `PLAN_REVIEW_APPROVE_AUTO_LABEL` / `PLAN_REVIEW_APPROVE_MANUAL_LABEL` / `PLAN_REVIEW_MODE_AFTER_WORDS` / `PLAN_REVIEW_MODE_AFTER_MIN_ENGINE`(0.72.13 CC-46:plan_review 批准后模式 `permissionModeAfter` 发口与三选卡双闸;§60)· `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` · `waitForGateArmed(For)`/`onGateArmed`/`gateArmedWaitMs`(#244 F1 呈现回执事件源) · `planReviewArmedKey(For)`/`notePlanReviewAnswered(For)`/`notePlanReviewAnsweredIfDecisive`(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` |
161
+ | 3 | **HITL 决断卡链**(§4/§5 主战场) | 176 | `readLivePendingRows` / `suspendedSubagentAsks` / `countApprovalsAwaitingDecision` / `createSuspendedAskTracker` / `livePendingRowToApprovalFrame` / `surfaceSuspendedAskAndRespond` / `surfaceEditRefusedOnBlindAsk` + `EDIT_REFUSED_ON_BLIND_ASK_WARN_TEXT`(0.72.14 CC-55:悬挂的流内 ask 读面 / 视图 / 去重账 / 决断口;§61)· `planReviewCardOptions` / `planReviewChoiceFromAnswer` / `versionSupportsPlanReviewModeAfter` / `isPlanReviewModeAfter` + `PLAN_REVIEW_APPROVE_AUTO_LABEL` / `PLAN_REVIEW_APPROVE_MANUAL_LABEL` / `PLAN_REVIEW_MODE_AFTER_WORDS` / `PLAN_REVIEW_MODE_AFTER_MIN_ENGINE`(0.72.13 CC-46:plan_review 批准后模式 `permissionModeAfter` 发口与三选卡双闸;§60)· `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` · `waitForGateArmed(For)`/`onGateArmed`/`gateArmedWaitMs`(#244 F1 呈现回执事件源) · `planReviewArmedKey(For)`/`notePlanReviewAnswered(For)`/`notePlanReviewAnsweredIfDecisive`(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` |
162
162
  | 4 | **子代 wire + 面板侧信道台账** | 88 | `subscribeEngineAgentPanelAbsence` / `publishEngineAgentPanelAbsence`(0.72.13:fleet 行缺席上报的**独立订阅口** —— 不上主通道,老消费者结构上收不到;§60 S-5)· `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` |
163
163
  | 5 | **fleet 投影** | 38 | `createFleetLedger` · `projectTasks` · `projectWorkflows` · `projectFleetAgentRows` · `readEngineActiveBgTasks` · `FLEET_TASK_VIEW_KEYS` · `escapeDisplayControlChars`(不可见字符可见化,行标签/描述消毒的共享底座)· `capForDisplay`(0.62.0:呈前**消毒 + 封长**的共用铸点 —— 先转义、后按转义结果封长,截点避开半截转义 token 与合法代理对;本包该族四处此前各写一份、其中三份顺序是反的,详见 §26⑦)· `wireCycleSeq`(0.38.0 提货补投的 #261 §2 两位之一,另一位 `wireRetiredBy` 🔧 0.72.0 退出公面:代际号 = SendMessage 复活即 +1,**缺席 ≠ 第一代**;`retiredBy` 在场 = 这条终态是对账腿从 durable run 行投影出来的、**不是**发布方亲报 —— 幽灵行与正常收尾唯一的 wire 判据。两位都只在场才落键) | 老 `fleetClient` 那一刀的成品:**帧体归库、连接归端** —— 端持 SSE 连接,库做行投影 + 保留台账 | `src/fleet/fleetProjection.ts`、`src/fleet/fleetLedger.ts`、`src/fleetAgentPanelProjection.ts`、`src/fleetTaskDesc.ts` |
164
164
  | 6 | **请求装配(上行唯一构造口)** | 13 | `MEMORY_OFF` / `memoryOffDeclarationField` / `memoryOffDeclared`(0.72.8 CC-39③:`memory:"off"` 请求级整面关的意图位片段;§55)· `buildTaskRequest` · `REQUEST_FIELD_MATRIX` · `unregisteredRequestKeys` · `applyLiveRequestDefaults` · `taskNotificationToPrintFrame` | 两条车道(`interactive`/`print`)出站请求的**唯一**构造器;`unregisteredRequestKeys` 是可执行门 —— 端偷带一个未登记键上 wire 就红 | `src/request/taskRequest.ts`、`src/request/printNotification.ts` |
@@ -8698,3 +8698,39 @@ installCoreValuePorts({ resolveAutonomousLoopPrompt, loosenReasons, combinePolic
8698
8698
  3. peer sdk >=9.6.0 / agent-types >=0.3.0 不动;server <7.86.0:版本闸 `unsupported` ⇒ 不出三选卡,`executionLane` 键缺席 ⇒ `not_reported`。
8699
8699
 
8700
8700
  **包侧缺口:** ⓪ CC-52 M4 `fleetProjection` 数值键 `?? 0` → 缺席不落键:`FleetTaskView` / `FleetWorkflowView` 的 `elapsedMs` / `tokens` / `queuedCount` / `doneCount` / `totalCount` / `failedCount` 由必填改可选 = **型面 BREAKING ⇒ 0.73.0(minor,窗 09-20)**,本段即**预告**,三端请表态(tsc 会红一次;渲染面改「—」);① CC-34 `permission_denials` 自动升级验证(core #897 已到;候 server 7.87.0 透传半场);② CC-36 托管 MCP 活性三态读口(候 core 7.23.x #874);③ `staleAfterSec` / `runId` 经 sdk 到端(候 sdk 出键);④ 九件 wire 面经 sdk 声明(含 `PlanReviewRequest.permissionModeAfter` / `Capabilities.executionLane`;候 sdk 出键);⑤ `agents.listing_truncated` detail 读器候消费场景(by-design);⑥ `ClassifierSeat.defaulted?` 候 sdk 出键(by-design);⑦ §NNz「declined 键真零命中」机读第二刀(候形式化);⑧ `absent` 事件附 `removeReason`(候 server 7.87.0 S-449 + 壳透传);⑨ plan_review 投影加「只读起步」判别键后三选卡可去掉宿主声明闸(候 server 独立一车,契约 §4c 已自认)。
8701
+
8702
+ ## §61 🆕 0.72.14 悬挂的流内 ask 拉半场(CC-55:feed 快照 `livePending` 段 + 视图 / 去重账 / 决断口 + 对账节拍);patch
8703
+
8704
+ > **patch**:型面纯 additive(+1 常量 +7 函数,公面 1011 → 1019;测试钩 47 不变;六个既有 interface 各加一位;零 wire 新键;sdk 9.6.0 的 barrel 不导出 `livePending` 行型,本包按结构读;peer 不动)。票来源:cli L-397(P1;后台 workflow 子代的审批门在引擎上活着、壳零帧零卡)。
8705
+
8706
+ ### 61a. 处置表
8707
+
8708
+ | # | 面 | 本包做了什么 | 端要做什么 |
8709
+ |---|---|---|---|
8710
+ | S-1 | **`GET /v1/approvals` 回体第二顶层键 `livePending[]`**(流内 ask;铸造时无活宿主流 ⇒ 悬挂,窗约一小时) | **consumed**:`startApprovalsFeed` 快照加 `livePending?`(窄读行)并进变化摘要;`readLivePendingRows` 三态 | 🔴 **挂上 feed**(`startApprovalsFeed(client, onSnapshot, opts)`);`client.approvals.list` 的注入面回体原样带 `livePending`(别在注入面剥掉) |
8711
+ | S-2 | 悬挂 ask 出现 / 结算在较老引擎上**不触发** `approvals.stream` 事件 | **consumed(兜底)**:`opts.reconcile = { shouldReconcile, intervalMs?, maxIntervalMs? }` 独立对账节拍 + `stats().reconciles` | 传 `shouldReconcile: () => 会话有活跃后台代理 && 没有活着的宿主 run`;引擎开始为这一段发 stream 事件后可以不再传(push 腿自己会重取) |
8712
+ | S-3 | 出卡 / 撤卡的去重 | `createSuspendedAskTracker({ sessionId })`:每张快照 `ingest` ⇒ `{ appeared, gone }`;`noteSurfacedByStream` 是**双向认领口**(返回布尔);`requeue` 让一只 ask 重新出卡 | 每个宿主会话一只;🔴 流内帧腿出卡**之前**调 `noteSurfacedByStream(approvalId)`,**返回 `false` 就跳过这一帧的出卡**(它已从快照出过卡 —— 两条通道同一只 ask 只有一张卡、一次决断);本端决断后调 `noteDecided(approvalId)`;`gone` ⇒ 撤卡(别处已决断 / 过期 / 引擎换代) |
8713
+ | S-4 | 决断 | `surfaceSuspendedAskAndRespond(row, respond, signal?, lane?)` —— 与流内帧腿同一条链,`respond` 传你接流内帧腿时的**同一只**注入函数 | 🔴 **不要**把 `livePending` 行送进 durable 的 decide 口(数组名即路由判据);卡上渲 `wireNote`;卡请求带 `argsUnavailable: true` ⇒ **别给「编辑后批准」**;真收到带改写的批准,包侧**整次不发**(结局 `decision:'unresolved'` + `editRefused:true`,屏上一条告知,ask 仍挂着)⇒ 端调 `tracker.requeue(approvalId)` 重新出卡 |
8714
+ | S-5 | 「在等你决定」的计数 | `countApprovalsAwaitingDecision(snapshot, { sessionId })` ⇒ `{ durable, live, suspendedSubagent }`,不知道 = `null` | 页脚 / 状态行的待决计数读它(此前只认 durable 停驻一条来源 ⇒ 悬挂 ask 恒计 0);`null` 渲「—」不渲 0 |
8715
+
8716
+ ### 61z. 逐键处置表
8717
+
8718
+ | # | 键 / 面 | 处置 | 坐标 / 理由 |
8719
+ |---|---|---|---|
8720
+ | 1 | `livePending[].approvalId` / `.toolName` / `.ts` / `.expiresAtMs` | **consumed(四必填;坏形丢行计数)** | `src/hitl/livePendingAsk.ts` `#readLivePendingRows`;新门 S1 |
8721
+ | 2 | `livePending[].sessionId` | **consumed(会话过滤;缺席 = 归不了属 ⇒ 过滤下不出卡)** | `#suspendedSubagentAsks`;新门 S2b / S4g |
8722
+ | 3 | `livePending[].fromSubagent` / `.originTaskId` | **consumed(only-if-true;`originTaskId` ⇒ 卡头子代身份)** | `#livePendingRowToApprovalFrame`;新门 S1e / S5a / S5c |
8723
+ | 4 | `livePending[].governanceForced` / `.requiresRealApproval` | **consumed(only-if-true,原样过到帧腿)** | 同上;新门 S1f / S5a |
8724
+ | 5 | `livePending` 键缺席 | **consumed(`not_reported`:快照键缺席、计数 `null`)** | 新门 S1a / S2d / S3a |
8725
+ | 6 | `livePending` 键在场但非数组 | **consumed(`malformed`:整次取件不提交)** | `src/hitl/approvalsFeed.ts` `#take`;新门 S1b / S3e |
8726
+ | 7 | 行上的工具入参 | **n/a(引擎读面不带)** ⇒ 卡上明说 + 卡请求 `argsUnavailable` + 带改写的批准整次不发 | `ToolApprovalFrameLaneOpts.argsUnavailable` / `ApprovalCardRequest.argsUnavailable` / `ToolApprovalFrameOutcome.editRefused`;新门 S5d / S5f–S5i |
8727
+ | 8 | `approvals.stream` 事件载荷 | **declined(一贯:不解析,只当「变了」的信号重取 `list()`)** | `approvalsFeed.ts` 头注 |
8728
+ | 9 | `crashConverged[]` | **n/a(本版不动;读面见 §12)** | — |
8729
+
8730
+ ### 61y. 🔴 三端升级必读(固定段式,[C295])
8731
+
8732
+ 1. 换钉 `^0.72.14`:挂 feed + 传 `reconcile` 闸 + 每会话一只 tracker + `appeared` 走 `surfaceSuspendedAskAndRespond`(结局 `editRefused` ⇒ `requeue`)、`gone` 撤卡;流内帧腿出卡**前**问 `noteSurfacedByStream`,`false` 就跳过;待决计数改读 `countApprovalsAwaitingDecision`。
8733
+ 2. 不接线的端零变化:不传 `reconcile` 不起定时器;快照多一个可选键(老消费者读 `pending` 不受影响;变化摘要含新段 ⇒ 引擎报 `livePending` 的部署上快照会比以前多发几张,内容是真的变了);流内帧腿不传 `argsUnavailable` ⇒ 行为逐字同旧。
8734
+ 3. peer sdk >=9.6.0 / agent-types >=0.3.0 不动;引擎不报 `livePending`(较老版本)⇒ 快照键缺席、tracker 恒空、计数 `null`。
8735
+
8736
+ **包侧缺口:** ⓪ CC-52 M4 fleet 视图数值键改可选 = **0.73.0(minor,型面 BREAKING,窗 09-20)**,已预告,候三端表态;① 悬挂 ask 卡上的工具入参摘要(候引擎在 `livePending` 段出键);② 引擎为 `livePending` 的出现 / 结算 / 过期发 `approvals.stream` 事件后对账节拍降为兜底(候引擎);③ CC-34 `permission_denials` 自动升级验证(候引擎透传半场);④ CC-36 托管 MCP 活性三态读口(候上游);⑤ `staleAfterSec` / `runId` 经 sdk 到端(候 sdk 出键);⑥ 九件 wire 面经 sdk 声明(含 `livePending` 行型经 barrel 导出;候 sdk);⑦ `agents.listing_truncated` detail 读器候消费场景(by-design);⑧ `ClassifierSeat.defaulted?` 候 sdk 出键(by-design);⑨ §NNz「declined 键真零命中」机读第二刀(候形式化);⑩ 缺席上报附 `removeReason`(候引擎 + 壳透传);⑪ 三选卡宿主声明闸候引擎投影判别键。
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sema-agent/client-core",
3
- "version": "0.72.13",
3
+ "version": "0.72.14",
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",