@sema-agent/client-core 0.74.3 → 0.74.4

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,18 @@
49
49
  > 挡住 ⇒ 本批把它机械化——④a0 对 `pending` 行**要求段头已是日期形**(`(未发布)` 直接红),阶段一
50
50
  > commit 漏转在发布前就红,不再靠人记。
51
51
 
52
+ ## 0.74.4(2026-09-19)
53
+
54
+ > 主题:**面板键「先发布的键为准」**(CC-83;test [7702] 证伪 0.74.3「面板桥零改」)+ **前代 UUID 的迟到 tick 丢弃**(CC-84;test [7702] W2 tick 腿)+ `readRunCancelContext` 下钻收紧。**patch**:型面 additive(`fleet-row` 臂 +`wireTaskId?` / `taskIdOrigin?`,`end` 臂 `taskIdOrigin` 多一词 `'wire'`,`EngineAgentPanelAbsence` +`wireTaskId?`;公面导出名零增减 1061;零 wire 新键;peer 不动)。接入面 §72。
55
+
56
+ ### Fixed
57
+
58
+ - **面板键「先发布的键为准」(CC-83)**:0.74.3 把 tick / end 归一到 fleet 尾段,但 tick 归不了一而按 UUID 原样发出的三种序(行帧前两条 tick / 缓一拍拍数耗尽 / 绑卡 tick 后来了行帧)里,随后的行帧仍按尾段发 ⇒ 按 `ev.taskId` 落表的宿主仍长成两行,UUID 那行永远收不到 end(test [7702] N-3,本包亲核复现)。现在漏斗记「已按 UUID 发布」的子代(transcriptId 与 parentToolCallId 两把钥匙),之后的 `fleet-row` 与 fleet 侧 `end`(投影 / 通知)改写成那把 UUID —— 原尾段进 `wireTaskId`,`taskIdOrigin:'wire'`;常态序(行帧先到)仍归一到尾段。**消费端在任何到达序上只见一把键**;要 fleet 尾段的宿主读 `wireTaskId` / `taskIdOrigin`。
59
+ - **前代 UUID 的迟到 tick 不进复活行(CC-84)**:`end` 有陈旧判据(0.74.0 / 0.74.3),tick 没有 ⇒ 复活(cycle 1 → 2)后前代 UUID 的迟到 tick 归一成当代尾段,把前代的累计数写进复活行(test 读数 toolUses 0 → 99,做对 end 判据的宿主也躲不开)。现在漏斗按钥匙周期对最近行帧周期判(与 `end` 同一只 `isStaleEngineAgentPanelEnd`),前代 tick 丢弃;判不出不丢。
60
+ - **异源对抗复审(0.74.4 R1)三条**:① 尾段 → UUID 的反向所有权此前不带周期 ⇒ 复活的新周期(新 transcriptId)也被改写到旧 UUID、其 tick 却归一到尾段(同一子代再度两键)⇒ 所有权带登记时周期,行帧 / end 更新的周期到了就退出所有权、新周期按常态登记;② 所有权表按容量 FIFO 会把仍活着的已发布键忘掉 ⇒ 改成按生命周期退役(收到 end 后先让位,活着的键不被顶;上限 8192);③ 缺席上报与通知车道清常驻标仍按尾段 ⇒ 侧信道与主通道两把键 ⇒ 缺席事件按所有者 UUID 发(原尾段进新键 `EngineAgentPanelAbsence.wireTaskId?`),通知车道改走 `clearEnginePanelTaskResidentByWire`(尾段的所有者一起清)。**R2 两条**:④ 新周期行帧退出旧所有权时旧 UUID 未退役 ⇒ 同一张卡的退路键把新周期行帧又拉回旧 UUID ⇒ 退出即退役;⑤ 已发布的 UUID 的后续 tick / end 仍会经退路键归一到尾段 ⇒ 前代迟到 tick 进新周期的行 ⇒ **一把已发布的键永远不改写**(它的 tick 留在它自己的键上、它的 end 落在它自己的行上)。**R3 两条**:⑥ 迟到 tick 把已退役的键重置成未退役 ⇒ 退路键又能拉回它 ⇒ 退役位不被后续 tick 重置;⑦ 通知车道按尾段清常驻标时不带周期 ⇒ 前代通知(seq 更旧)清掉当代 UUID 的常驻标 ⇒ `clearEnginePanelTaskResidentByWire(id, cycle?)` 带通知周期,比所有权旧的不清;陈旧 end 过境让消费端判、不退役当代所有权。
61
+ - 门 `run-engine-identity-test.mjs` E2–E5 重钉:引擎产物的 `instanceId` 铸点扫 `main.js` + `boot/*.js`、`/health` 体扫 `http/*.js`(server 7.88.x 把启动拆成 `dist/boot/stage-*.js`、`/health` 搬进 `http/dispatch.js`;铸点仍无条件、体仍带 `instanceId`;此前只扫 `main.js` / `http/server.js` ⇒ 消费端换钉实跑本门假红)。7.88.2 发布字节与 7.89.0 本机树两形实跑绿。
62
+ - **`readRunCancelContext` / `classifyRunAbortCause` 的 `result` 下钻**只认像 run 结果的对象(带 `terminal` / `status` / `cancelContext` 任一键);`TaskResult` 自己的 `result` 键哪天成了对象不再被误当成结果记录(test [7702] §七 ④)。
63
+
52
64
  ## 0.74.3(2026-09-19)
53
65
 
54
66
  > 主题:**面板子代身份归一**(CC-70;cli L-407 / test 定谳:同一后台子代在 wire 上两把身份)+ **run 记录取消现场读口与成因闭集**(CC-71;cli L-212 (a))+ **workflow 列表行 run 级 errorCode**(CC-72;core 7.23.5 #932)+ **reopen 族第二臂两件**(CC-73;cli L-56)。**patch**:型面纯 additive(+2 常量 +7 函数,公面 1052 → 1061;测试钩 50 → 51;`EngineAgentPanelEvent` 三臂各加可选键;零 wire 新键;peer 不动)。接入面 §71。
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.74.3
38
+ **Version:** 0.74.4
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
@@ -316,7 +316,7 @@ public-surface guard checks that last one).
316
316
  | `scripts/run-device-executor-management-capability-test.mjs` | The engine's device-management self-description (`capabilities.deviceExecutor.management`, engine ≥7.88.0), read the same four-state way as its five sibling capability readers: an absent `management` key is reported as not reported (never folded into `false`; an older engine really ships the lane object without it), an absent `deviceExecutor` key is likewise not reported, `deviceExecutor: false` is the lane being absent, presence is judged by own-property not truthiness, the value must be a strict boolean, the tee never throws and drops stale generations, and the package owns the verdict on whether the `/v1/devices` management verbs are usable (`yes` only when present and true, `no` when present-false or lane-absent, otherwise `unknown`) |
317
317
  | `scripts/run-run-cancel-context-test.mjs` | The run record's `cancelContext` side-note (engine ≥7.87.3) read structurally, and the cause of a `turn_aborted{engine_error}` classified from machine-readable evidence only: `cancelled` (code `cancelled`, with the cancel-time context when present) / `engine_error` (any other failure code, passed through verbatim) / `run_still_live` (the record is not terminal — a dropped stream is a client-side fact, not the run's cause) / `unknown` (never guessed). An absent `cancelContext` reads as *not reported*, never as "not cancelled"; `elapsedMs` is never folded to 0. |
318
318
  | `scripts/run-suspended-reopen-projection-test.mjs` | The durable `suspended` event's `reopened` key read as three distinct states — `reopened` (with the engine's code, verbatim), `not_reopened` (an explicit `null`), `unstated` (key absent or unreadable) — and carried on the HITL bridge's active gate (`currentGateReopen()`), re-read on every `suspended` and cleared with the gate. |
319
- | `scripts/run-panel-identity-normalization-test.mjs` | One background subagent has two ids on the wire — the fleet row id tail and the `task_progress` task id (its transcript id). Every panel event goes through one funnel that rewrites the `tick` / `end` task id onto the fleet row's id once a `fleet-row` has registered the key (`transcriptId` first, `parentToolCallId` as the fallback), carrying the original as `wireTaskId` and marking `taskIdOrigin`; an unbound tick whose row has not arrived yet waits one beat (bounded) and is released verbatim on the next tick / `end`, when the buffer is full, or after `MAX_HELD_WIRE_TICK_BEATS` other fleet-row / end / sweep events (a `sweep` itself leaves it alone: there is no row to settle yet); a normalized `end` that carries no cycle identity borrows the registering row's, so a late close of a revived task is recognized as stale; the key table is an LRU (a task that keeps ticking is never evicted by newer registrations); the residency mark migrates with the id and both keys are cleared on settle — except that a stale (previous-cycle) terminal never clears the revived row's mark — so the notification lane can clear it. |
319
+ | `scripts/run-panel-identity-normalization-test.mjs` | One background subagent has two ids on the wire — the fleet row id tail and the `task_progress` task id (its transcript id). Every panel event goes through one funnel that rewrites the `tick` / `end` task id onto the fleet row's id once a `fleet-row` has registered the key (`transcriptId` first, `parentToolCallId` as the fallback), carrying the original as `wireTaskId` and marking `taskIdOrigin`; an unbound tick whose row has not arrived yet waits one beat (bounded) and is released verbatim on the next tick / `end`, when the buffer is full, or after `MAX_HELD_WIRE_TICK_BEATS` other fleet-row / end / sweep events (a `sweep` itself leaves it alone: there is no row to settle yet); a normalized `end` that carries no cycle identity borrows the registering row's, so a late close of a revived task is recognized as stale; the key table is an LRU (a task that keeps ticking is never evicted by newer registrations); the residency mark migrates with the id and both keys are cleared on settle — except that a stale (previous-cycle) terminal never clears the revived row's mark — so the notification lane can clear it. Once a tick has been delivered verbatim under its UUID, that UUID is the subagent's key: later fleet rows and fleet-side ends are rewritten onto it (the tail kept in `wireTaskId`), so a consumer sees one row in every arrival order; a late tick from a previous cycle is dropped rather than folded into the revived row. |
320
320
  | `scripts/run-approval-card-retract-test.mjs` | The approval card's **decision-free retraction** and the in-stream frame leg's **outcome hand-back**: a host that must withdraw a card that no longer has a decision channel (session switch, engine switch, a tracker reporting the ask gone) answers `{ kind: 'retracted' }` and the package sends nothing on any of the three legs (in-stream frame, suspended ask, durable park), reporting `decision: 'unresolved'` with a `retracted` flag; `aborted` / `failed` / `deny` keep their meaning (a real deny is still posted), and `onToolApprovalOutcome` hands every in-stream outcome back to the host exactly once, tolerating a throwing or rejecting callback |
321
321
  | `scripts/run-panel-cycle-identity-test.mjs` | The **cycle identity** on agent-panel events and the fleet ledger's **departure read-out**: a background agent may be revived under the same id, so `fleet-row` and `end` events now carry the wire's own `cycleSeq` / `startedAt` when present (absent means the row has no notion of generations, never "generation one"), `isStaleEngineAgentPanelEnd` is the single rule for ignoring a late `end` from a previous cycle (only when both sides carry a comparable identity; absence never drops a real terminal), a changed `cycleSeq` is a new cycle for usage stickiness and buffer coalescing, the notification lane carries `cycleSeq` only when the wire really sent `seq`, and `task_remove` frames reach the host through `onTaskRemoved` with `removeReason` / `cycleSeq` verbatim, a stale previous-generation removal leaving the newer row in place |
322
322
  | `scripts/run-workflow-size-warning-test.mjs` | The **workflow size warning** verdict shared by every host footer / panel: a three-state result (`warn` / `ok` / `unknown`) read off the optional fleet view keys, where an unknown size is never reported as a normal one (absent `totalCount` / `tokens` without positive over-cap evidence is `unknown`, naming the missing keys), positive evidence on either axis wins regardless of absent keys, the per-agent denominator uses the engine's started count only when it is not below done+failed (a smaller value is a stale reading), otherwise falls back to the done+failed lower bound only when both keys are present — and a lower-bound denominator only yields an upper bound of the projection, which can prove *within cap* (`ok`, flagged) but never *over cap* (`unknown`, with the upper bound exposed) — and the prior is used only when the engine itself reports zero started agents; cap precedence env > explicit guideline > default, prototype keys never act as a guideline, the env reader is pure and does not fall through to the second name on a bad first value; caps, guideline table, env names and the three copy variants are single-sourced |
@@ -126,6 +126,9 @@ export type EngineAgentPanelEvent = {
126
126
  * 身份归一的退路(`transcriptId` 缺席时凭它把 task_progress 的 UUID 反查到本行)。
127
127
  */
128
128
  parentToolCallId?: string;
129
+ /** 0.74.4 CC-83:先发布的键为准 —— 这个子代已按 UUID 上屏时,行帧的 `taskId` 改写成那把 UUID,原尾段进这里;缺席 = 未改写。 */
130
+ wireTaskId?: string;
131
+ taskIdOrigin?: 'wire';
129
132
  } | {
130
133
  kind: 'end';
131
134
  taskId: string;
@@ -144,8 +147,8 @@ export type EngineAgentPanelEvent = {
144
147
  report?: string;
145
148
  /** 0.74.3 CC-70:同 tick 臂 —— 归一后才有(流内 close 臂 / 终态 tick 的 `end` 带的是 UUID,改写成 fleet 尾段时记原词)。 */
146
149
  wireTaskId?: string;
147
- /** 0.74.3 CC-70:只在归一时带 `'fleet-row'`;通知车道 / fleet 投影铸的 `end` 本来就是 fleet 尾段,不带。 */
148
- taskIdOrigin?: 'fleet-row';
150
+ /** 0.74.3 CC-70:归一时带 `'fleet-row'`;0.74.4 CC-83:尾段被改写成先发布的 UUID 时带 `'wire'`(原尾段进 `wireTaskId`);其余不带。 */
151
+ taskIdOrigin?: 'fleet-row' | 'wire';
149
152
  } | {
150
153
  /** Exception-path safety net (published by seamQuery after every turn, throw or return):
151
154
  * the consumer settles ALL rows it owns that are still running. Idempotent. */
@@ -195,8 +198,9 @@ export declare function resolveEnginePanelTaskId(wireTaskId: string, parentToolC
195
198
  * 0.74.3 CC-70(异源复审 R3 [high]):按 wire 上的子代 id 清常驻标 —— 原键照清;归一到 fleet 尾段的那把键**只在这条终态属当代时**才清:
196
199
  * 复活后迟到的前代终态(旧 UUID 的流内 close / 终态 tick)归一后带旧代际,清了尾段键 = 撤掉复活行的 sweep 保护 ⇒ 下一次 sweep 假结它。
197
200
  * 判据与 `end` 的陈旧判据同一只(`isStaleEngineAgentPanelEnd`,拿钥匙上的周期对最近一条行帧的周期);判不出 ⇒ 照清(不因缺席留下永久常驻)。
201
+ * `cycle`(0.74.4):调用方(通知车道)带得出这条终态的周期时传进来 —— 尾段归某把 UUID 所有而通知比所有权旧 ⇒ 不清那把 UUID 的常驻标。
198
202
  */
199
- export declare function clearEnginePanelTaskResidentByWire(wireTaskId: string): void;
203
+ export declare function clearEnginePanelTaskResidentByWire(wireTaskId: string, cycle?: EngineAgentPanelCycleIdentity): void;
200
204
  /** 测试钩:清两张钥匙表、缓一拍与周期水位。 */
201
205
  export declare function __resetEngineAgentPanelIdentityForTests(): void;
202
206
  /** 宿主的唯一发布口(三条 lane 都经这里):先归一身份,再进主通道(订阅者 / 缓冲)。 */
@@ -210,6 +214,8 @@ export interface EngineAgentPanelAbsence {
210
214
  lastSeenAtMs: number;
211
215
  /** 发出这条上报时已缺席多久(ms;> `ABSENT_SETTLE_MS`)。 */
212
216
  absentForMs: number;
217
+ /** 0.74.4 CC-83:`taskId` 被改写成先发布的 UUID 时,原尾段在这里;缺席 = 按尾段发。 */
218
+ wireTaskId?: string;
213
219
  }
214
220
  type AbsenceListener = (ev: EngineAgentPanelAbsence) => void;
215
221
  /** 发布一条缺席上报(由 `projectFleetAgentRows` 的缺席路径调;消费者抛错不反噬投影)。 */
@@ -108,6 +108,12 @@ export function isStaleEngineAgentPanelEnd(end, current) {
108
108
  // isStaleEngineAgentPanelEnd 才判得出它是前代;
109
109
  // · tick 经退路键(parentToolCallId)归一时,顺手把 UUID → 尾段 记进主表(复审 R1 [high]):随后的 end{UUID} 不带 parentToolCallId,
110
110
  // 没有这一步它永远归不了一。
111
+ // · 🔴 **先发布的键为准**(0.74.4 CC-83;test [7702] N-3 证伪「零改」:tick 归不了一而原样发出之后,行帧再按尾段发 = 消费端仍两行):
112
+ // 一条 tick 已按 UUID 原样发出(第二拍 / 拍数耗尽 / 绑卡),这个子代在消费端的键就是 UUID —— 之后到的 fleet-row / fleet 侧 end
113
+ // 改写成 UUID(原词进 wireTaskId,taskIdOrigin:'wire'),不再把尾段当第二把键发出去。消费端从此在任何到达序上都只见一把键;
114
+ // 常态序(行帧先到)仍归一到尾段。
115
+ // · 🔴 前代 UUID 的迟到 tick **丢弃**(0.74.4 CC-84;test [7702] W2 tick 腿):钥匙上的周期比最近行帧旧 ⇒ 这条 progress 属已结束的
116
+ // 周期,归一发出去会把前代的累计数写进复活行;end 有陈旧判据(消费端按周期挡),tick 没有 ⇒ 在漏斗里挡。
111
117
  /** 钥匙表上限(LRU:命中即续命,真在飞的子代不会被一阵新登记挤掉;超出只退化成「原样发」)。 */
112
118
  const MAX_IDENTITY_KEYS = 2048;
113
119
  const MAX_HELD_WIRE_TICKS = 64;
@@ -118,6 +124,80 @@ const fleetIdByParentToolCallId = new Map();
118
124
  const heldWireTicks = new Map();
119
125
  /** 每个 fleet 尾段最近一条行帧带的周期身份(在场才记):清常驻标时用它判「这条终态是不是前代的」(异源复审 R3 [high])。 */
120
126
  const latestCycleByFleetId = new Map();
127
+ /**
128
+ * 已按 UUID 原样发出过 tick 的子代(先发布的键为准,CC-83):transcriptId(= UUID)与 parentToolCallId 两把钥匙都记。
129
+ * 🔴 所有权表**按生命周期退役、不按容量改写**(异源复审 0.74.4 R1 [medium]):一把已发布的键在消费端就是一行,容量到了把它忘掉 =
130
+ * 同一子代重新长成两行。所以 `retired`(收到 end)的条目先让位;都没退役时才让最旧的(上限 MAX_OWNERSHIP_KEYS,远大于同时在飞的子代数)。
131
+ */
132
+ const MAX_OWNERSHIP_KEYS = 8192;
133
+ const publishedWireIds = new Map();
134
+ const publishedWireByParent = new Map();
135
+ /** 尾段 → 已发布的 UUID(CC-83):fleet 侧的 fleet-row / end / 缺席 到时改写成 UUID;带登记时行帧的周期,复活的新周期不继承。 */
136
+ const wireIdByFleetId = new Map();
137
+ function bounded(map, key, value) {
138
+ map.delete(key);
139
+ map.set(key, value);
140
+ if (map.size > MAX_IDENTITY_KEYS) {
141
+ const oldest = map.keys().next().value;
142
+ if (oldest !== undefined)
143
+ map.delete(oldest);
144
+ }
145
+ }
146
+ /** 所有权表的有界:退役的先让位,都没退役才让最旧的。 */
147
+ function boundedOwnership(map, key, value) {
148
+ map.delete(key);
149
+ map.set(key, value);
150
+ if (map.size <= MAX_OWNERSHIP_KEYS)
151
+ return;
152
+ for (const [k, v] of map) {
153
+ if (v.retired) {
154
+ map.delete(k);
155
+ return;
156
+ }
157
+ }
158
+ const oldest = map.keys().next().value;
159
+ if (oldest !== undefined)
160
+ map.delete(oldest);
161
+ }
162
+ /** 一条 tick 按 UUID 原样发出去了:从此这个子代的键 = UUID。 */
163
+ function notePublishedWire(ev) {
164
+ // 已退役的键不因一条迟到 tick 复活(异源复审 R3 [high]):退役 = 它的行已结,迟到帧落在它自己的行上,所有权不回来
165
+ boundedOwnership(publishedWireIds, ev.taskId, { retired: publishedWireIds.get(ev.taskId)?.retired ?? false });
166
+ if (ev.parentToolCallId !== undefined && ev.parentToolCallId.length > 0)
167
+ bounded(publishedWireByParent, ev.parentToolCallId, ev.taskId);
168
+ }
169
+ /** 这把 UUID 的子代收到了终态:所有权可退役(容量满时先让位;不删 —— 迟到的同周期行帧 / end 仍要改写到它)。 */
170
+ function retireWire(wireId) {
171
+ const w = publishedWireIds.get(wireId);
172
+ if (w !== undefined)
173
+ w.retired = true;
174
+ for (const o of wireIdByFleetId.values())
175
+ if (o.wireId === wireId)
176
+ o.retired = true;
177
+ }
178
+ /** fleet-row 对应的子代是否已经按 UUID 发布过;transcriptId 命中不看退役(同一子代的迟到行帧),parentToolCallId 只认未退役(复活会沿用同一张卡)。 */
179
+ function publishedWireFor(ev) {
180
+ if (ev.transcriptId !== undefined && publishedWireIds.has(ev.transcriptId))
181
+ return ev.transcriptId;
182
+ if (ev.parentToolCallId !== undefined) {
183
+ const u = publishedWireByParent.get(ev.parentToolCallId);
184
+ if (u !== undefined && publishedWireIds.get(u)?.retired === false)
185
+ return u;
186
+ }
187
+ return undefined;
188
+ }
189
+ /** 尾段今天归哪把 UUID(在场 = 这个子代已按 UUID 上屏;复活的新周期不算)。 */
190
+ function wireOwnerOf(fleetId, current) {
191
+ const o = wireIdByFleetId.get(fleetId);
192
+ if (o === undefined)
193
+ return undefined;
194
+ if (current !== undefined && isStaleEngineAgentPanelEnd(o, current)) {
195
+ wireIdByFleetId.delete(fleetId); // 行帧已是更新的周期:旧 UUID 的所有权到此为止,新周期按常态登记
196
+ retireWire(o.wireId); // 旧周期已过:那把 UUID 退役,退路键(同一张卡)不再把新周期的行帧拉回它(异源复审 R2 [high])
197
+ return undefined;
198
+ }
199
+ return o.wireId;
200
+ }
121
201
  function remember(map, key, alias) {
122
202
  map.delete(key);
123
203
  map.set(key, alias);
@@ -189,25 +269,44 @@ function normalizedTick(ev, fleetId) {
189
269
  function asIsTick(ev) {
190
270
  const { cardBound: _cardBound, ...rest } = ev;
191
271
  void _cardBound;
272
+ notePublishedWire(ev);
192
273
  return { ...rest, taskIdOrigin: 'wire' };
193
274
  }
275
+ /** 钥匙上的周期比最近行帧旧 = 这条 tick 属已结束的周期(CC-84);判不出 ⇒ 不算陈旧。 */
276
+ function staleWireTick(id) {
277
+ return isStaleEngineAgentPanelEnd(id, latestCycleByFleetId.get(id.taskId));
278
+ }
194
279
  function flushHeld(wireTaskId) {
195
280
  const held = heldWireTicks.get(wireTaskId);
196
281
  if (held === undefined)
197
282
  return;
198
283
  heldWireTicks.delete(wireTaskId);
284
+ if (publishedWireIds.has(held.ev.taskId)) {
285
+ deliverEngineAgentPanelEvent(asIsTick(held.ev));
286
+ return;
287
+ }
199
288
  const id = resolveEnginePanelTaskId(held.ev.taskId, held.ev.parentToolCallId);
200
- if (id.origin === 'fleet-row')
289
+ if (id.origin === 'fleet-row') {
290
+ if (staleWireTick(id))
291
+ return; // 前代周期的 progress:丢
201
292
  rememberResolvedWire(held.ev.taskId, id);
202
- deliverEngineAgentPanelEvent(id.origin === 'fleet-row' ? normalizedTick(held.ev, id.taskId) : asIsTick(held.ev));
293
+ deliverEngineAgentPanelEvent(normalizedTick(held.ev, id.taskId));
294
+ return;
295
+ }
296
+ deliverEngineAgentPanelEvent(asIsTick(held.ev));
203
297
  }
204
298
  /**
205
299
  * 0.74.3 CC-70(异源复审 R3 [high]):按 wire 上的子代 id 清常驻标 —— 原键照清;归一到 fleet 尾段的那把键**只在这条终态属当代时**才清:
206
300
  * 复活后迟到的前代终态(旧 UUID 的流内 close / 终态 tick)归一后带旧代际,清了尾段键 = 撤掉复活行的 sweep 保护 ⇒ 下一次 sweep 假结它。
207
301
  * 判据与 `end` 的陈旧判据同一只(`isStaleEngineAgentPanelEnd`,拿钥匙上的周期对最近一条行帧的周期);判不出 ⇒ 照清(不因缺席留下永久常驻)。
302
+ * `cycle`(0.74.4):调用方(通知车道)带得出这条终态的周期时传进来 —— 尾段归某把 UUID 所有而通知比所有权旧 ⇒ 不清那把 UUID 的常驻标。
208
303
  */
209
- export function clearEnginePanelTaskResidentByWire(wireTaskId) {
304
+ export function clearEnginePanelTaskResidentByWire(wireTaskId, cycle) {
210
305
  residentTaskIds.delete(wireTaskId);
306
+ const owner = wireIdByFleetId.get(wireTaskId); // 传进来的是尾段而尾段归某把 UUID 所有(通知车道)⇒ 那把键下的常驻标一起清
307
+ // 通知带周期(seq)且比所有权登记时的周期旧 = 前代的终态 ⇒ 不清当代 UUID 的常驻标(异源复审 R3 [high])
308
+ if (owner !== undefined && !(cycle !== undefined && isStaleEngineAgentPanelEnd(cycle, owner)))
309
+ residentTaskIds.delete(owner.wireId);
211
310
  const id = resolveEnginePanelTaskId(wireTaskId);
212
311
  if (id.taskId === wireTaskId)
213
312
  return;
@@ -221,6 +320,9 @@ export function __resetEngineAgentPanelIdentityForTests() {
221
320
  fleetIdByParentToolCallId.clear();
222
321
  heldWireTicks.clear();
223
322
  latestCycleByFleetId.clear();
323
+ publishedWireIds.clear();
324
+ publishedWireByParent.clear();
325
+ wireIdByFleetId.clear();
224
326
  }
225
327
  /** 宿主的唯一发布口(三条 lane 都经这里):先归一身份,再进主通道(订阅者 / 缓冲)。 */
226
328
  export function publishEngineAgentPanelEvent(ev) {
@@ -230,6 +332,16 @@ export function publishEngineAgentPanelEvent(ev) {
230
332
  ageHeld(ev.kind === 'sweep' ? undefined : ev.taskId);
231
333
  switch (ev.kind) {
232
334
  case 'fleet-row': {
335
+ // 先发布的键为准(CC-83):这个子代已经按 UUID 上过屏 ⇒ 行帧改写成 UUID 发,并记 尾段 → UUID 供 fleet 侧 end 改写;不登记尾段钥匙。
336
+ const rowCycle = { ...(ev.cycleSeq !== undefined ? { cycleSeq: ev.cycleSeq } : {}), ...(ev.startedAt !== undefined ? { startedAt: ev.startedAt } : {}) };
337
+ const published = wireOwnerOf(ev.taskId, rowCycle) ?? publishedWireFor(ev);
338
+ if (published !== undefined && published !== ev.taskId) {
339
+ const prev = wireIdByFleetId.get(ev.taskId);
340
+ boundedOwnership(wireIdByFleetId, ev.taskId, { wireId: published, retired: prev?.wireId === published ? prev.retired : false, ...rowCycle });
341
+ deliverEngineAgentPanelEvent({ ...ev, taskId: published, wireTaskId: ev.taskId, taskIdOrigin: 'wire' });
342
+ ageHeld(undefined);
343
+ return;
344
+ }
233
345
  const alias = {
234
346
  fleetId: ev.taskId,
235
347
  ...(ev.cycleSeq !== undefined ? { cycleSeq: ev.cycleSeq } : {}),
@@ -258,9 +370,18 @@ export function publishEngineAgentPanelEvent(ev) {
258
370
  return;
259
371
  }
260
372
  case 'tick': {
373
+ // 先发布的键为准也管 tick 自己(异源复审 R2 [high]):这把 UUID 已经是消费端的键,之后的 tick 不再经退路键归一到尾段
374
+ //(否则复活后同一张卡的退路键把前代 UUID 的迟到 tick 拉进新周期的行)。
375
+ if (publishedWireIds.has(ev.taskId)) {
376
+ heldWireTicks.delete(ev.taskId);
377
+ deliverEngineAgentPanelEvent(asIsTick(ev));
378
+ return;
379
+ }
261
380
  const id = resolveEnginePanelTaskId(ev.taskId, ev.parentToolCallId);
262
381
  if (id.origin === 'fleet-row') {
263
382
  heldWireTicks.delete(ev.taskId);
383
+ if (staleWireTick(id))
384
+ return; // CC-84:前代 UUID 的迟到 tick 不进复活行
264
385
  rememberResolvedWire(ev.taskId, id);
265
386
  deliverEngineAgentPanelEvent(normalizedTick(ev, id.taskId));
266
387
  return;
@@ -284,6 +405,23 @@ export function publishEngineAgentPanelEvent(ev) {
284
405
  return;
285
406
  }
286
407
  case 'end': {
408
+ // 先发布的键为准(CC-83):尾段已被改写成 UUID 的子代,fleet 侧(投影 / 通知)的 end{尾段} 也改写成 UUID
409
+ const wireOwner = wireOwnerOf(ev.taskId, ev);
410
+ if (wireOwner !== undefined) {
411
+ const ownerRec = wireIdByFleetId.get(ev.taskId);
412
+ deliverEngineAgentPanelEvent({ ...ev, taskId: wireOwner, wireTaskId: ev.taskId, taskIdOrigin: 'wire' });
413
+ // 前代迟到的 end(比所有权登记时的周期旧)只过境让消费端判陈旧,不退役当代所有权(异源复审 R3)
414
+ if (!isStaleEngineAgentPanelEnd(ev, ownerRec))
415
+ retireWire(wireOwner);
416
+ return;
417
+ }
418
+ if (publishedWireIds.has(ev.taskId)) {
419
+ // 这把 UUID 是先发布的键 ⇒ 它的 end 落在它自己的行上(不归一;一把已发布的键永远不改写)
420
+ flushHeld(ev.taskId);
421
+ deliverEngineAgentPanelEvent(ev);
422
+ retireWire(ev.taskId);
423
+ return;
424
+ }
287
425
  const id = resolveEnginePanelTaskId(ev.taskId);
288
426
  if (id.origin === 'fleet-row' && id.taskId !== ev.taskId) {
289
427
  flushHeld(ev.taskId); // 同 UUID 还缓着一条 tick ⇒ 先归一放出(行先有最后一拍的数字与名字),再结
@@ -301,6 +439,7 @@ export function publishEngineAgentPanelEvent(ev) {
301
439
  }
302
440
  flushHeld(ev.taskId); // 缓着的 tick 先落行,终态才有行可结
303
441
  deliverEngineAgentPanelEvent(ev);
442
+ retireWire(ev.taskId); // 这把 UUID 若是先发布的键,所有权退役(容量满时先让位)
304
443
  return;
305
444
  }
306
445
  default:
@@ -459,6 +598,10 @@ let absenceListener = null;
459
598
  const absenceBuffer = new Map();
460
599
  /** 发布一条缺席上报(由 `projectFleetAgentRows` 的缺席路径调;消费者抛错不反噬投影)。 */
461
600
  export function publishEngineAgentPanelAbsence(ev) {
601
+ // 0.74.4 CC-83(异源复审 R1 [medium]):尾段已归先发布的 UUID 所有 ⇒ 缺席也按那把键发(主通道与侧信道同一把键),原尾段进 wireTaskId
602
+ const owner = wireOwnerOf(ev.taskId);
603
+ if (owner !== undefined)
604
+ ev = { ...ev, taskId: owner, wireTaskId: ev.taskId };
462
605
  if (absenceListener) {
463
606
  try {
464
607
  absenceListener(ev);
@@ -6,7 +6,7 @@
6
6
  */
7
7
  import { hostEnv } from './hostEnv.js';
8
8
  import { unrefTimer } from './unrefTimer.js';
9
- import { clearEnginePanelTaskResident, publishEngineAgentPanelEvent, } from './engineAgentPanelStore.js';
9
+ import { clearEnginePanelTaskResidentByWire, publishEngineAgentPanelEvent, } from './engineAgentPanelStore.js';
10
10
  // L-215②(0.65.0;core [6908]):「非成功终局」的**单铸谓词** —— 面板 settle 的 `isError` 位与
11
11
  // 三端自己的后台白名单必须读同一个判据;内联一个 `=== 'failed' || === 'killed'` 就是第二份会漂的台账。
12
12
  import { isTaskNotificationTerminalStatus, isTerminalNotSuccess, isTerminalStatus } from './runTerminal.js';
@@ -922,7 +922,8 @@ export function enqueueBgChildNotification(n) {
922
922
  // 0.72.13(test 0.72.12 验收 ①;DS-06 同形存量):只对**通知自己的终态词**(completed / failed / killed / cancelled)settle 面板行。
923
923
  // 此前对任何 status 都铸 `end`,而 `isError: isTerminalNotSuccess(status)` 对 running / queued / event / 未来词答 false ⇒
924
924
  // 面板把一条还在跑 / 认不出状态的行结成 **completed** —— 该谓词的 JSDoc 明禁拿它的 false 当成功。非终态 ⇒ 不 settle、常驻标不摘。
925
- clearEnginePanelTaskResident(n.taskId);
925
+ // 0.74.4 CC-83:按 wire 词清 —— 尾段若归先发布的 UUID 所有,那把键下的常驻标一起清(主通道与常驻账同一把键)
926
+ clearEnginePanelTaskResidentByWire(n.taskId, typeof n.seq === 'number' && Number.isInteger(n.seq) && n.seq >= BG_FIRST_SEQ ? { cycleSeq: n.seq } : undefined);
926
927
  publishEngineAgentPanelEvent({
927
928
  kind: 'end',
928
929
  taskId: n.taskId,
@@ -14,13 +14,24 @@
14
14
  function nonNegFinite(v) {
15
15
  return typeof v === 'number' && Number.isFinite(v) && v >= 0 ? v : undefined;
16
16
  }
17
+ /**
18
+ * 记录形两种:顶层 `result`(PersistedTaskResult)或已经是 result 本身。只有 `result` 长得像一份 run 结果(带 terminal / status /
19
+ * cancelContext 任一键)才下钻 —— `TaskResult` 自己也有个 `result` 键(今天是串),哪天成了对象也不该被当成结果记录(test [7702] §七 ④)。
20
+ */
21
+ function runResultHolder(r) {
22
+ const inner = r.result;
23
+ if (inner === null || typeof inner !== 'object' || Array.isArray(inner))
24
+ return r;
25
+ const o = inner;
26
+ return Object.hasOwn(o, 'terminal') || Object.hasOwn(o, 'status') || Object.hasOwn(o, 'cancelContext') ? o : r;
27
+ }
17
28
  /** run 记录(`runs.get` 回体 / `done.result`)→ 取消现场读数;记录非对象 ⇒ `undefined`(判不出,与 not_reported 分开)。 */
18
29
  export function readRunCancelContext(record) {
19
30
  if (record === null || typeof record !== 'object')
20
31
  return undefined;
21
32
  const r = record;
22
33
  // 记录形两种:顶层 `result.cancelContext`(PersistedTaskResult)或已经是 result 本身。
23
- const holder = r.result !== null && typeof r.result === 'object' ? r.result : r;
34
+ const holder = runResultHolder(r);
24
35
  if (!Object.hasOwn(holder, 'cancelContext') || holder.cancelContext === undefined)
25
36
  return { kind: 'not_reported' };
26
37
  const c = holder.cancelContext;
@@ -60,8 +71,7 @@ export function classifyRunAbortCause(record) {
60
71
  return { kind: 'unknown' };
61
72
  const r = record;
62
73
  const status = typeof r.status === 'string' && r.status.length > 0 ? r.status : undefined;
63
- const holder = r.result !== null && typeof r.result === 'object' ? r.result : record;
64
- const read = readRunTerminal(holder);
74
+ const read = readRunTerminal(runResultHolder(r));
65
75
  if (read !== null && read.kind === 'failed') {
66
76
  if (read.code === RUN_CANCELLED_CODE) {
67
77
  const cc = readRunCancelContext(record);
@@ -19,7 +19,7 @@
19
19
 
20
20
  | 项 | 值 | 真源 |
21
21
  |---|---|---|
22
- | 本包 | `@sema-agent/client-core` **0.74.3**(本批发布版 = **patch**:面板子代身份归一(tick / end 的 UUID 归到 fleet 尾段,宿主零改)+ run 记录 `cancelContext` 读口与成因四词 + workflow 行 `errorCode` + reopen 第二臂两件;型面纯 additive、公面 1061、peer 不动;§71。上一版 0.74.2 = **patch**:`capabilities.deviceExecutor.management` 四态读口(缺席 ≠ false;`deviceExecutor: false` = 车道缺席;管理动词可用性三态归包);公面 1052;§70。上一版 0.74.1 = **patch**:审批卡无决断收口 + 结局回交;§69。上一版 0.74.0 = **minor**:peer sdk 地板 >=9.7.1 + covers 过境 + 面板周期身份 + 离场读口;§68。上一版 0.73.5 = **patch**:workflow 规模告警判定上收进包(`evaluateWorkflowSize` 三态:未知的规模不当作规模正常;分母口径与帽优先级单源;env 读口纯函数);型面纯 additive、公面 1044、peer 不动;§67。上一版 0.73.4 = **patch**:终帧 `permission_denials` 第三键 ⇐ 同一条流 `tool_start` 入参按 `toolCallId` join(三道闸;join 不上留 sema 载体;🔴 更正 0.73.2「自动升级收口」一句)+ 审批 feed push 侧取件失败的欠账退避重试;型面纯 additive、公面 1033、peer 不动;§66。上一版 0.73.3 = **patch**:CC-61 sdk 9.7.1 提货 + `capabilities.approvalsStreamLive` 四态读口(缺席 = 判不出;`livePendingNeedsReconcile` 只在引擎明说会推时才让对账节拍降兜底)+ CC-60 五条模型面文案锚包侧半场(cursorSemantics 机读位 / result 行注记 / bash 首行两变体与良性槽 / 通知 seed 并集 / parked 臂);型面纯 additive、公面 1032、peer 不动;§65。上一版 0.73.2 = **patch**:CC-59 悬挂 ask 全卡 —— `livePending[].frame`(server ≥7.87.0)两道闸窄读进视图,在场即走流内帧腿同一条路(入参可见、改写可转发),缺席保持盲卡;CC-34 收口(引擎半场随 7.87.0 到货,判据交黑盒);型面纯 additive、公面 1026、peer 不动;§64。上一版 0.73.1 = **patch**:CC-57 `decidePlanReview` outcome 尾句按「决断有没有生效」四态选(单铸点 `planReviewOutcomeTail`;此前本地拒发仍让模型汇报已拒绝)+ CC-56 cancel 的 409 按机器码分家(`cancelConflictFromError` 三形 / 新安全码 `cancel_run_not_running`)与提交面 429 用量窗读口 + 人话;型面纯 additive、公面 1026、peer 不动;§63。上一版 0.73.0 = **minor,型面 BREAKING**:fleet 视图八个数值键改可选 —— CC-52 `FleetTaskView.elapsedMs` / `tokens` / `queuedCount` 与 `FleetWorkflowView.doneCount` / `totalCount` / `failedCount` / `tokens` / `elapsedMs` 不再 `?? 0`(有键 = 真值含真 0,缺键 = 不知道;逐键缺席语义见 §62b)+ 台账补偿链无基数不铸;公面导出名零增减 1019、零 wire 变化、peer 不动;§62。上一版 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 八条)|
22
+ | 本包 | `@sema-agent/client-core` **0.74.4**(本批发布版 = **patch**:面板键「先发布的键为准」(tick 归不了一的三序不再两行)+ 前代 UUID 迟到 tick 丢弃 + `readRunCancelContext` 下钻收紧;型面 additive、公面 1061、peer 不动;§72。上一版 0.74.3 = **patch**:面板子代身份归一(tick / end 的 UUID 归到 fleet 尾段,宿主零改)+ run 记录 `cancelContext` 读口与成因四词 + workflow 行 `errorCode` + reopen 第二臂两件;型面纯 additive、公面 1061、peer 不动;§71。上一版 0.74.2 = **patch**:`capabilities.deviceExecutor.management` 四态读口(缺席 ≠ false;`deviceExecutor: false` = 车道缺席;管理动词可用性三态归包);公面 1052;§70。上一版 0.74.1 = **patch**:审批卡无决断收口 + 结局回交;§69。上一版 0.74.0 = **minor**:peer sdk 地板 >=9.7.1 + covers 过境 + 面板周期身份 + 离场读口;§68。上一版 0.73.5 = **patch**:workflow 规模告警判定上收进包(`evaluateWorkflowSize` 三态:未知的规模不当作规模正常;分母口径与帽优先级单源;env 读口纯函数);型面纯 additive、公面 1044、peer 不动;§67。上一版 0.73.4 = **patch**:终帧 `permission_denials` 第三键 ⇐ 同一条流 `tool_start` 入参按 `toolCallId` join(三道闸;join 不上留 sema 载体;🔴 更正 0.73.2「自动升级收口」一句)+ 审批 feed push 侧取件失败的欠账退避重试;型面纯 additive、公面 1033、peer 不动;§66。上一版 0.73.3 = **patch**:CC-61 sdk 9.7.1 提货 + `capabilities.approvalsStreamLive` 四态读口(缺席 = 判不出;`livePendingNeedsReconcile` 只在引擎明说会推时才让对账节拍降兜底)+ CC-60 五条模型面文案锚包侧半场(cursorSemantics 机读位 / result 行注记 / bash 首行两变体与良性槽 / 通知 seed 并集 / parked 臂);型面纯 additive、公面 1032、peer 不动;§65。上一版 0.73.2 = **patch**:CC-59 悬挂 ask 全卡 —— `livePending[].frame`(server ≥7.87.0)两道闸窄读进视图,在场即走流内帧腿同一条路(入参可见、改写可转发),缺席保持盲卡;CC-34 收口(引擎半场随 7.87.0 到货,判据交黑盒);型面纯 additive、公面 1026、peer 不动;§64。上一版 0.73.1 = **patch**:CC-57 `decidePlanReview` outcome 尾句按「决断有没有生效」四态选(单铸点 `planReviewOutcomeTail`;此前本地拒发仍让模型汇报已拒绝)+ CC-56 cancel 的 409 按机器码分家(`cancelConflictFromError` 三形 / 新安全码 `cancel_run_not_running`)与提交面 429 用量窗读口 + 人话;型面纯 additive、公面 1026、peer 不动;§63。上一版 0.73.0 = **minor,型面 BREAKING**:fleet 视图八个数值键改可选 —— CC-52 `FleetTaskView.elapsedMs` / `tokens` / `queuedCount` 与 `FleetWorkflowView.doneCount` / `totalCount` / `failedCount` / `tokens` / `elapsedMs` 不再 `?? 0`(有键 = 真值含真 0,缺键 = 不知道;逐键缺席语义见 §62b)+ 台账补偿链无基数不铸;公面导出名零增减 1019、零 wire 变化、peer 不动;§62。上一版 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.7.1**(value-level,非 type-only;**0.74.0 抬版**:9.7.x 追平的六处型面是本包 0.73.3 起的编译基线,且 9.6.0 失去物料见证;**0.71.0 曾抬到 >=9.6.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` |
@@ -9165,3 +9165,47 @@ installCoreValuePorts({ resolveAutonomousLoopPrompt, loosenReasons, combinePolic
9165
9165
  3. peer 不动;server <7.87.3 ⇒ `cancelContext` `not_reported`;sdk 未声明 `errorCode` 前 workflow 行按结构读。
9166
9166
 
9167
9167
  **包侧缺口:** ⓪ CC-74 `mcpProbe` 四态读口候 server S-481 发车(走能力位读器通用工厂,见兔子洞自查 ①);① CC-36 候 core #874;② 面板缓冲合同一句:`publishEngineAgentPanelEvent` 无订阅者时按 (kind, taskId) 合并缓冲、下次 `subscribe` 原样回放(先 publish 后 subscribe 会收到过去的事件;归一后的键),来源 test [7646] ④ ⑥;其余同 §70。
9168
+
9169
+ ## §72 🆕 0.74.4 面板键「先发布的键为准」(CC-83)+ 前代 tick 丢弃(CC-84);patch
9170
+
9171
+ > **patch**:型面 additive(`fleet-row` 臂 +`wireTaskId?` / `taskIdOrigin?: 'wire'`,`end.taskIdOrigin` 多一词 `'wire'`,`EngineAgentPanelAbsence` +`wireTaskId?`;公面导出名零增减 1061;零 wire 新键;peer 不动)。票来源:test [7702](0.74.3 靶子两红,本包亲核复现 [7704])。🔴 **更正 §71**:「面板桥零改」在 tick 归不了一的序上不成立(0.74.3 那三序仍两行);本版把规则改成「先发布的键为准」,零改的正确形 = **任何到达序消费端只见一把键**。
9172
+
9173
+ ### 72a. 处置表
9174
+
9175
+ | # | 面 | 本包做了什么 | 端要做什么 |
9176
+ |---|---|---|---|
9177
+ | S-1 | 面板事件的键(所有 lane) | **fixed**:先发布的键为准 —— 已按 UUID 发出过 tick 的子代,后到的 `fleet-row` / fleet 侧 `end` 改写成 UUID(`wireTaskId` = 原尾段,`taskIdOrigin:'wire'`) | 按 `ev.taskId` 落表的宿主零改(任何序一行);要按尾段(= 本地 agentId)对账的宿主:`taskIdOrigin === 'wire' && wireTaskId` 在场时尾段在 `wireTaskId` |
9178
+ | S-2 | 前代 UUID 的迟到 tick | **fixed**:漏斗丢弃(钥匙周期 < 最近行帧周期) | 零改;复活行的计数不再被前代污染 |
9179
+ | S-3 | `readRunCancelContext` 的 `result` 下钻 | **fixed**:只认像 run 结果的对象 | 零改 |
9180
+
9181
+ ### 72b. 缺席语义(逐键)
9182
+
9183
+ - `fleet-row.wireTaskId` / `taskIdOrigin` 缺席 = 这条行帧按尾段发(常态);在场(`'wire'`)= 这个子代已按 UUID 上屏,`taskId` 是 UUID、尾段在 `wireTaskId`。
9184
+ - `end.taskIdOrigin`:`'fleet-row'` = UUID 归一到尾段;`'wire'` = 尾段改写成先发布的 UUID;缺席 = 原样。
9185
+ - `EngineAgentPanelAbsence.wireTaskId` 缺席 = 缺席上报按尾段发;在场 = 已改写成所有者 UUID(与主通道同一把键)。所有权按生命周期退役:收到 `end` 后表满时先让位;复活的新周期(行帧周期更新)不继承旧 UUID(旧 UUID 即退役,同一张卡的退路键不再拉回它),按常态归一到尾段。**一把已发布的 UUID 永远不改写**:它之后的 tick 留在它自己的键上、它的 end 落在它自己的行上(前代迟到帧因此进不了复活行);退役位不被迟到 tick 重置。通知车道清常驻标带 `seq` 当周期:比所有权登记时的周期旧 ⇒ 不清当代 UUID 的常驻标(`clearEnginePanelTaskResidentByWire(id, cycle?)`)。
9186
+ - 前代 tick 的「陈旧」判不出(钥匙或最近行帧无周期身份)⇒ 不丢,照归一 —— 只挡有证据的前代。
9187
+ - `suspended.reopened` 键在场而值为 `undefined`(JSON 不可达,只对进程内对象成立)⇒ `unstated`(补 §71 未成文的一形)。
9188
+ - 缓一拍的 16 拍 = **事件计数**(别的 fleet-row / end / sweep),store 无计时器,折不成秒;真机重连成批重发时会在一次事件风暴里耗尽。
9189
+ - W3「外层 `status` 与 `result.status` 矛盾 ⇒ unknown」那格在真 server 7.88.2 上够不着(`result.status` 不存在),只对夹具形 / 未来记录形有意义,不当真机判据。
9190
+
9191
+ ### 72c. 黑盒判据骨架
9192
+
9193
+ - **G1(CC-83)**:test [7702] S3 / S4 / S5 三序,零改壳恒 **1 行**且能收到 end;常态序(S1 / S2)仍归一到尾段;0.74.3 红基线三序 = 2 行。
9194
+ - **G2(CC-84)**:复活后前代 UUID 的迟到 tick(六种到达序)⇒ 复活行 `toolUses` 不变;当代 tick 照进。
9195
+ - **G3**:`readRunCancelContext({ status, cancelContext, result: { text } })` ⇒ present(不下钻)。
9196
+
9197
+ ### 72z. 逐键处置表
9198
+
9199
+ | # | 键 / 面 | 处置 | 坐标 / 理由 |
9200
+ |---|---|---|---|
9201
+ | 1 | `fleet-row.wireTaskId` / `taskIdOrigin` | **additive(在场才带)** | `src/engineAgentPanelStore.ts` 漏斗 fleet-row 臂 |
9202
+ | 2 | tick 陈旧判据 | **fixed(漏斗内,不出公面)** | 同上 tick 臂 / flushHeld;判据复用 `isStaleEngineAgentPanelEnd` |
9203
+ | 3 | `runs.get` 记录 `result` 下钻 | **fixed** | `src/runCancelContext.ts` `runResultHolder` |
9204
+
9205
+ ### 72y. 🔴 三端升级必读(固定段式,[C295])
9206
+
9207
+ 1. 换钉 `^0.74.4`:面板桥零改;若按尾段对账,读 `wireTaskId`(`taskIdOrigin:'wire'` 时)。
9208
+ 2. 不接线零变化 —— 除面板:三种非常态序不再两行;复活行计数不再被前代 tick 污染。
9209
+ 3. peer 不动。
9210
+
9211
+ **包侧缺口:** ⓪ CC-74 mcpProbe 候 server S-481;① RH-2 第二步(收 end 铸点 / 删投影侧代际比较)候 clay 排序;② CC-81 sdk 9.8.0 换钉随下一 minor;③ CC-36 候 core #874;其余同 §71。
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sema-agent/client-core",
3
- "version": "0.74.3",
3
+ "version": "0.74.4",
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",