@sema-agent/client-core 0.65.1 → 0.67.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -89,6 +89,14 @@ import { createSessionSlot, DEFAULT_SESSION_KEY } from '../sessionSlot.js';
89
89
  import { readEngineActiveBgTasks } from '../fleet/fleetLedger.js';
90
90
  import { observeCancelByDeny, surfaceRememberNotApplied, surfaceEditNotForwarded, surfaceRuleArmNotSent, surfaceRuleArmRejected } from './hitlHostSurface.js';
91
91
  import { approvalCallKey, liveFrameCallKey } from './gateIdentity.js';
92
+ // 0.67.0(core 7.14.0 #688 C3):`ruleStoreUnreadable` 的**闭二词判据**。词表属主 = core
93
+ // (`RULE_STORE_UNREADABLE_KINDS`),镜像与措辞都在 `gateVocabulary.ts` 那个唯一铸点上 ——
94
+ // 本文件**不抄第二份字面量**(B-025 的根因就是手抄)。
95
+ import { isRuleStoreUnreadableKind } from '../gateVocabulary.js';
96
+ // 🔴 0.67.0:`ClassifierUnavailableView` 的 type-only import 随卡面那一格**整只退役**(core 7.14.0
97
+ // 把 `classifierUnavailable` 从 ask / park / carry / summary 六个载体上删掉,继任者是拒绝面的
98
+ // `GateDisposition.denied.cause`)。事实位今天由 `gateOutcome.ts` 的 `disposition.cause` 与公面读器
99
+ // `classifierDenyCauseOf` 承载,卡上不再有它的对偶 —— 见 `autoModeUnavailable.ts` 顶注。
92
100
  // B-070 / L-200(0.65.0):`/decide` 200 回执的**单一读面**(`handoffTaskId` 换挂句柄 +
93
101
  // `executionOutcome` 缺席=未知)。判定归包,端零自拼。
94
102
  import { readDecideReceipt } from '../decideReceipt.js';
@@ -357,24 +365,20 @@ parkGatedCallId) {
357
365
  // (见 {@link ApprovalCardRequest.persistedRuleShadowed} 的同名警示)。
358
366
  const durableShadowedRule = pending.riskDescriptor?.shadowedRule;
359
367
  const durableRuleOffersAbsence = pending.ruleOffersAbsence;
360
- // cli L-174②(0.64.2):durable 腿的「分类器跑不了」孪生位 —— 载体 = 行上的 `classifierUnavailable`
361
- // (core `checkpoint-store.d.ts` 逐字「the PARK twin of `AskRequest.classifierUnavailable`」,写在
362
- // `PendingAction.tool_approval` 上;行是 `pendingAction` 的**扁平投影**,`ruleOffers`/`hasBidiControls`
368
+ // 0.67.0(core 7.14.0 #688 C3):durable 腿的「规则店读不出来」孪生位 —— 载体 = 行上的
369
+ // `ruleStoreUnreadable`(core `checkpoint-store.d.ts:818` 写在 `PendingAction.tool_approval` 上,
370
+ // 摘要行 `:1924-1926` 同名;行是 `pendingAction` 的**扁平投影**,`ruleOffers`/`hasBidiControls`
363
371
  // 都是同一条路落到顶层的)。sdk 8.8.0 的 `PendingCheckpoint` 尚无本键声明 ⇒ **结构视图读**
364
372
  // (与同函数 `ruleOffers` / `riskDescriptor.probeCause` 同款姿势),类型半场候 sdk 班车。
365
373
  // 🔴 **与 `ruleEvidence` 的「刻意零 stamp」不同裁,理由要说清**:那一位在上游**根本没有耐久对偶**,
366
374
  // 读 `riskDescriptor.ruleEvidence` 是在猜一个载体名(猜对了是白写,猜错了是拿另一个量冒充);
367
- // 本位的对偶**是上游逐字声明的**,键名同形、语义同源 —— 不是猜。
368
- // ⚠️ **今天的供给缺口,如实记(不是「接通了」)**:engine 7.69.0 的两只 checkpoint 店
369
- // (`plugins/local-checkpoint-store.js:144 listPending` / `plugins/checkpoint-store-sql.js:500
370
- // listPending`)把 `pendingAction` 扁平化时**没有**带这一位(`/v1/assistant/inbox` 那条腿带了,
371
- // 但那不是本腿吃的 `/v1/approvals` 行)⇒ 本腿在今天的引擎上**恒零命中**,server 补投那天零改动
372
- // 生效。⚠️ **别把 `governanceForced` 也读进这一格**(异源对抗复审 r2 非阻断③ 订正):那一位
373
- // 确实不在 `listPending` 的产物里,但同包 `http/routes/approvals-assistant.js` 的
374
- // `projectPendingForWire` 会用 `governanceOriginOf(...)` **现算并补到行上**,所以它今天是**到得了**
375
- // 本腿的;`riskDescriptor.shadowedRule` 同理(那条路由里的 `redactPendingDisclosures` 脱敏后随行
376
- // 上 wire,件⑥ 消费的正是它)。本位是这三者里**唯一**今天真的没有供给的一格。
377
- const durableClassifierUnavailable = readClassifierUnavailable(pending.classifierUnavailable);
375
+ // 本位的对偶**是上游逐字声明的**(core 摘要行的顶注逐字:「The row's
376
+ // `PendingAction.tool_approval.ruleStoreUnreadable` (#688 C3), echoed ONLY when the stored word is
377
+ // a」member),键名同形、语义同源 —— 不是猜。
378
+ // ⚠️ **今天的供给缺口,如实记(不是「接通了」)**:本腿吃的是 `/v1/approvals` 行,而两只 checkpoint
379
+ // 店的 `listPending` 把 `pendingAction` 扁平化时带不带这一位由 server 决定;本包这一侧零改动
380
+ // 随 server 补投生效 —— 与上一代 `classifierUnavailable` 的 durable 腿同一格记账。
381
+ const durableRuleStoreUnreadable = readRuleStoreUnreadable(pending.ruleStoreUnreadable);
378
382
  deps.onPresented?.(); // L-80:真要交给卡口了才算呈过(规则直决 / 取件失败都不走到这一行)
379
383
  const card = await surfaceApprovalCard({
380
384
  toolName,
@@ -391,10 +395,10 @@ parkGatedCallId) {
391
395
  ...(pending.hasBidiControls === true ? { hasBidiControls: true } : {}),
392
396
  ...(ruleOffersReadOnly !== undefined ? { ruleOffersReadOnly } : {}),
393
397
  ...(isWireRecordCarrier(durableProbeCause) ? { probeCause: durableProbeCause } : {}),
394
- // cli L-174②(0.64.2):与活卡腿**同一把窄读器、同一个卡位**(键路同形 ⇒ 端一把读器吃两条腿)。
398
+ // 0.67.0:与活卡腿**同一把窄读器、同一个卡位**(键路同形 ⇒ 端一把读器吃两条腿)。
395
399
  // 缺席 ⇒ 键不 stamp;判据与供给缺口见上面那段头注。
396
- ...(durableClassifierUnavailable !== undefined
397
- ? { classifierUnavailable: durableClassifierUnavailable }
400
+ ...(durableRuleStoreUnreadable !== undefined
401
+ ? { ruleStoreUnreadable: durableRuleStoreUnreadable }
398
402
  : {}),
399
403
  // 件⑥(0.64.2):durable 腿的两位对偶(判据与「为什么是行为面」见上面那段头注)。
400
404
  // 🔴 落位是**活卡腿同一个卡位**(`persistedRuleShadowed` / `ruleOffersAbsence`),不是新键:
@@ -559,14 +563,12 @@ export const TOOL_APPROVAL_FRAME_KEYS_MIRROR = [
559
563
  // 🔴 本键与 `requiresRealApproval` 是孪生键,但两者在镜像里各占一格 —— 合并会让「只有一位在场」
560
564
  // 的坏形无处显形。
561
565
  'denialLimitFallback',
562
- // cli L-174②(client-core 0.64.2):server 7.69.0 起真发 `classifierUnavailable`(core 7.10.0 #616;
563
- // engine 7.69.0 fixture `tool-approval.js:847` 条件 stamp + `approval-card.js:244` 窄读器直证)。
564
- // ⚠️ 与 `ruleOffersAbsence`/`denialLimitFallback` **不同形**、与 `persistedRuleShadowed`/`probeCause`
565
- // 同形:sdk 8.8.0 的运行期锚**尚无**本键(node 直读实证:锚 27 项)⇒ 这是一次**领先**,进对账门的
566
- // AHEAD_OF_ANCHOR 带退出条件登记(sdk 8.9.x 同拍补上那天那条登记自红逼删,回到逐元素相等)。
567
- // 🔴 本键有**已定的退役日期**(server 7.70.0 / core 7.12.0 起三面投影退役,上游发布记录 [6852]),但退役
568
- // 走的是「缺席」而不是「删码」([6853] 死键保留型)⇒ 镜像里这一格不因退役而动。
569
- 'classifierUnavailable',
566
+ // 0.67.0(core 7.14.0 #688 C3):`ruleStoreUnreadable`。⚠️ 它**换掉**的是上一代同位的
567
+ // `classifierUnavailable` —— core 7.14.0 把那一格从六个载体上整族退役(ask 那几处自 #661 起就没写过),
568
+ // 本包按 clean-cut 删镜像成员、删读器、删卡位,**不留别名**(登记在 run-retired-vocabulary-census)。
569
+ // ⚠️ 与 `persistedRuleShadowed`/`probeCause` 同形:sdk 8.8.0 的运行期锚**尚无**本键 ⇒ **领先**一代,
570
+ // 进对账门的 AHEAD_OF_ANCHOR 带退出条件登记(sdk 8.9.x 补上那天那条登记自红逼删,回到逐元素相等)。
571
+ 'ruleStoreUnreadable',
570
572
  // S-125③/#564(client-core 0.59.0):server 7.57.0 起真发 `origin`(core 7.5.0 `ASK_ORIGINS`
571
573
  // 八词;engine 7.60.0 fixture 直证)。同上:sdk 8.3.0 锚已含本键 ⇒ 追平,不进领先表。
572
574
  'origin',
@@ -999,36 +1001,22 @@ export function readDenialLimitFallback(v) {
999
1001
  };
1000
1002
  }
1001
1003
  /**
1002
- * `classifierUnavailable` 的**过境窄读器**(cli L-174②,0.64.2)—— 两条卡腿(活卡帧 / durable park
1003
- * 行)共用的**唯一**一把:两处键路同形,各写一份就是两份台账各漂各的。
1004
+ * `ruleStoreUnreadable` 的**过境窄读器**(0.67.0;core 7.14.0 #688 C3)—— 两条卡腿(活卡帧 /
1005
+ * durable park 行)共用的**唯一**一把:两处键路同形,各写一份就是两份台账各漂各的。
1004
1006
  *
1005
- * 🔴 **判据与 server 的唯一铸点同源**(engine 7.69.0 fixture `approval-card.js:240/244`:
1006
- * `z.object({ cause: z.string().min(1).max(200) }).strict()` + envelope 的 safeParse)——
1007
- * · 载体非对象 / null / 数组 ⇒ 缺席;
1008
- * · `cause` 非串 / 空串 ⇒ 缺席(空壳等于说「分类器坏了但说不出为什么」,比不说更坏 —— server
1009
- * d.ts 顶注逐字);
1010
- * · **`cause` 按开集收**:词表属主在 core,server 侧已按 `min(1)` 之外零词表校验过,包再抄一张
1011
- * 闭集表只会在 core 加词那天把一个合法值判没,而丢的正是「这次不可用是新出现的那一类」
1012
- * 这条信息(与 `origin` / `ruleOffersAbsence` / `denialLimitFallback.limit` 逐字同规)。
1013
- * 🔴 **只交 `cause` 一格**(与 `classifierUnavailableOf` 的读数形逐字相同):顺手把载体上别的键
1014
- * 带出来会长成第二份 ask 读面。
1015
- * ⚠️ **与 server `.strict()` 的一格差**(与 {@link readDenialLimitFallback} 同一条,如实写):
1016
- * 载体上**多出的成员**本读器剥后保留 `cause`,server 侧整只拒收 —— 包宽一格,方向是「多键不误杀」。
1017
- * 🔴 **过境 ≠ 显示**:本读器答「这条事实到不到得了卡口」;「渲哪一句」由公面的
1018
- * `classifierUnavailableOf` / `classifierUnavailableDetail` 答 —— 那一层同样非空串即收,只排除
1019
- * **熔断轴独占**的词(`parse_error`)。⚠️ 0.64.2 之前那一层是 unavailable **闭集**,于是一个合法的
1020
- * 新成因词过得了本层却在显示层被判没;订正后两层判据几乎逐字相同,差的只有那条有出处的排除
1021
- * —— 见 {@link ApprovalCardRequest.classifierUnavailable} 与 INTEGRATION §29e。
1007
+ * 🔴 **闭二词按闭集判**(与本文件其余开集位刻意不同,理由在上游的形上):core 在**唯一 stamp 点**
1008
+ * (`gate-lanes.ts` `ruleStoreUnreadableOf`)把政策自报的值**剥掉**之后才盖章,合同逐字
1009
+ * 「A policy's self-declared value is removed at the stamp」⇒ 一个表外值**结构上**到不了消费端;
1010
+ * 真读到就是坏形,而把坏形渲成一句机制解释会把人指到错的地方(`store` 去修部署的店 /
1011
+ * `call` 去改自己那条规则,两条下一步相反)。这与 `origin`/`ruleOffersAbsence` 的开集纪律不矛盾:
1012
+ * 那两位在 server 侧**只判非空串**,core 加词的当天一个**合法**值就会带着帧到达。
1013
+ * 🔴 **只交那个词**:载体是一个裸串(不是对象)—— 不顺手读载体上别的东西。
1014
+ * 🔴 **非串 / 空串 / 表外词 ⇒ 缺席**,绝不折成空串(一格空白的机制解释比没有解释更坏)。
1022
1015
  * **不导出**:它是本模块的边界窄化器,唯一消费点是下面两个卡口调用;导出会给公面再加一个 `unknown`
1023
1016
  * 入参签名(typeshape 门的 unknown-出境棘轮 +1),而它的行为由两条腿的端到端素材覆盖。
1024
1017
  */
1025
- function readClassifierUnavailable(v) {
1026
- if (!isWireRecordCarrier(v))
1027
- return undefined;
1028
- const cause = v.cause;
1029
- if (typeof cause !== 'string' || cause === '')
1030
- return undefined;
1031
- return { cause };
1018
+ function readRuleStoreUnreadable(v) {
1019
+ return isRuleStoreUnreadableKind(v) ? v : undefined;
1032
1020
  }
1033
1021
  /**
1034
1022
  * `match` 是不是**闭词表成员** —— 表来自 sdk 8.2.0 `RULE_OFFER_MATCHES`(`exact` / `prefix` /
@@ -1453,14 +1441,14 @@ export async function surfaceToolApprovalFrameAndRespond(frame, respond, streamA
1453
1441
  // 🔴 stamp 的是**窄读产物**而不是原对象:端拿到的四座恒是有限非负数 + 非空 limit 串,
1454
1442
  // 不必各自再写一遍同样的判断(三端各写一遍 = 三份会漂的判官)。
1455
1443
  ...(denialLimitFallback !== undefined ? { denialLimitFallback } : {}),
1456
- // ── cli L-174②(0.64.2):「问你是因为分类器这次跑不了」的事实透传 ──────────────────────────
1457
- // 经 `readClassifierUnavailable` 单点窄读(判据本体在该函数顶注:`cause` 非空串即收、**开集**、
1458
- // 坏形降缺席)。stamp 的是**窄读产物** `{cause}`,与 durable 腿**同一把读器**(键路同形)。
1459
- // 🔴 缺席 ⇒ 键不 stamp,**绝不折成任何肯定话**:缺席同时覆盖「分类器答上了」「这只 ask 没资格
1460
- // 走分类器」「本部署没接分类器」三形,端禁读成「分类器好着呢」。
1461
- // ⚠️ 本键 core 7.12.0 / server 7.70.0 起是死键(帧上恒缺席,事实位随拒绝面走)—— 那天这条
1462
- // stamp 自然零命中,**不需要**也**不许**在这里加任何熔断/回落判据(echo-only)。
1463
- ...((c) => (c !== undefined ? { classifierUnavailable: c } : {}))(readClassifierUnavailable(frame.classifierUnavailable)),
1444
+ // ── 0.67.0(core 7.14.0 #688 C3):「规则店读不出来是哪一头」的事实透传 ────────────────────
1445
+ // 经 `readRuleStoreUnreadable` 单点窄读(判据本体在该函数顶注:**闭二词**、表外/坏形降缺席)。
1446
+ // 与 durable 腿**同一把读器、同一个卡位**(键路同形 ⇒ 端一把读器吃两条腿)。
1447
+ // 🔴 缺席 ⇒ 键不 stamp,**绝不折成任何肯定话**:缺席同时覆盖「店和这次调用都读通了」与
1448
+ // 「本部署没接规则店」两形,端禁读成「你的规则都好着呢」。
1449
+ // ⚠️ 它**换掉**的是上一代同位的 `classifierUnavailable`(core 7.14.0 整族退役;事实位随拒绝面的
1450
+ // `GateDisposition.denied.cause` 走)—— 那一格按 clean-cut 删码不留别名。
1451
+ ...((k) => (k !== undefined ? { ruleStoreUnreadable: k } : {}))(readRuleStoreUnreadable(frame.ruleStoreUnreadable)),
1464
1452
  // ── S-125③/#564(0.59.0):ask 出身透传 ────────────────────────────────────────────────────
1465
1453
  // 非空串才 stamp,词表**不校**(同 ruleOffersAbsence)。🔴 缺席 ⇒ 键不 stamp,**绝不折成
1466
1454
  // `"policy"`**:那是一个正面事实(出自部署 ToolPolicy),把「老引擎没报」折进去 = 替引擎编话。
package/dist/seam.d.ts CHANGED
@@ -8,6 +8,7 @@
8
8
  */
9
9
  import type { ModelUsage, SDKMessage } from '@sema-agent/agent-types';
10
10
  import type { EngineTurnUsage } from './adapter/downstream/turnUsageToModelUsage.js';
11
+ import type { RunCostReconcile } from './adapter/downstream/terminalToSdkResult.js';
11
12
  import type { WiringManifestAutoMode, WiringManifestModelGate, WiringManifestMcpEntry } from './adapter/downstream/eventToSdkMessage.js';
12
13
  /**
13
14
  * `AbortSignal` 的结构型(B3 扩容,SEAM-GAP-4)。
@@ -391,6 +392,66 @@ export type ChromeEvent = {
391
392
  */
392
393
  stopReason?: string;
393
394
  }
395
+ /**
396
+ * **L-228(0.67.0;core 7.14.0 提货批件 F-6)—— per-subagent 的 turn usage 分表。**
397
+ *
398
+ * ── 它修的是什么 ─────────────────────────────────────────────────────────────────────────
399
+ * 一条 run 委派出去的那本账,core 终局**只有合计**(`TaskResult.stats.nested` 的
400
+ * `{tokens, turns, tasks, costMicroUsd}`,`task-result.d.ts` #594-660)——**没有 per-task 分项**。
401
+ * 而流里每一条子流的 `turn_end` 都带着它自己那一轮的 usage,本包此前在 §E2 断闸处**折而未读**
402
+ * (`!isSubFlow` 才发 `last_turn_usage` / 才 yield `turn_usage`)⇒ 子代详情面(壳的
403
+ * `engineAgentView`)只渲得出「这个子代的 usage 不知道」。⇒ 分表**只能**由流内子流累加得出,
404
+ * 本臂就是那条通道。
405
+ *
406
+ * ── 🔴 它**不**改主臂的断闸 ──────────────────────────────────────────────────────────────
407
+ * `last_turn_usage` / `turn_usage` 两处 `!isSubFlow` 一个字节不动(§E2:子代的 outputTokens 拿去
408
+ * 对账 leader 的 responseLength 就是 token-jump 那个病)。本臂是**additive 的第三条腿**,
409
+ * `laneProof` 恒是**子流那条**(`{lane:'subagent', parentToolCallId}`)—— 宿主据此就知道这条不是
410
+ * 主会话的账,不会误落进 statusline 的窗口占用。
411
+ *
412
+ * ── 载荷 ─────────────────────────────────────────────────────────────────────────────────
413
+ * `taskId` = 引擎给这只子任务的 id;wire 上缺席时**回落 `parentToolCallId`**(同一只子代的每一轮
414
+ * 至少归得到同一行)。`usage` / `engineUsage` / `usageMissing` / `stopReason` 与
415
+ * {@link ChromeEvent} 的 `last_turn_usage` 臂**逐位同义**(同一只 `turnEndUsage()` 产物,不另铸)。
416
+ *
417
+ * 宿主消费义务(`required: false`):渲子代详情的 per-agent 用量。不接 = 那一面看不见(不是报错);
418
+ * 🔴 **绝不**把本臂的数加进主会话的 turn 用量 —— 两条腿分工见上;
419
+ * 🔴 `usageMissing` 在场 ⇒ 同行那份 usage 是**下界**,别当已知账落槽。
420
+ */
421
+ | {
422
+ kind: 'subagent_turn_usage';
423
+ laneProof: LaneProof;
424
+ /**
425
+ * **分表的行键** = `sourceTaskId ?? parentToolCallId`;与终帧 `_sema_nested_usage_by_task`
426
+ * 的键**逐字相同**(本臂与那张表的 join 键)。
427
+ * 🔴 它**不是** wire 上的某一个字段名:core 的 `TaskEventIdentity` 上**没有** `taskId`
428
+ * (顶注逐字「NOT a second `taskId` — that field already exists on `task_progress` and a
429
+ * duplicate would bite consumers」),真身份是 {@link sourceTaskId};回落见那一位。
430
+ */
431
+ taskId: string;
432
+ /**
433
+ * core `TaskEventIdentity.sourceTaskId` **原值**:这条子流所属任务的 canonical id
434
+ * (`spec.taskId ?? sessionId`),**只**盖在「作为子代跑」的任务的内容事件上。
435
+ * 缺席 = 引擎没报这一位(老引擎 / 某些腿)⇒ {@link taskId} 回落到 `parentToolCallId`。
436
+ * ⚠️ 回落是**有损**的:同一个父调用下若真有多只子任务,它们会并成一行 —— 这时
437
+ * 终帧那张表的 `partial` 判别位会因行数对不上 `nested.tasks` 而立起来。
438
+ */
439
+ sourceTaskId?: string;
440
+ /** 引擎给这条子流打的父调用 id(`laneProof` 里那一个,平铺一份省得宿主解 union)。 */
441
+ parentToolCallId: string;
442
+ /**
443
+ * 这一轮的 CC `ModelUsage` 镜像。
444
+ * 🔴 **可选**:core 真会发**裸** `{type:'turn_end', usageMissing:true}`(无 usage、无 stopReason)
445
+ * —— 主臂 0.65.1 / B-088 就是为这一形放宽的发臂条件,子代这条腿同律(同形存量不许再犯一次)。
446
+ * 缺席 ⇒ 这一轮**什么账都没报**,读 {@link usageMissing}。
447
+ */
448
+ usage?: ModelUsage;
449
+ engineUsage?: EngineTurnUsage;
450
+ /** 见 `last_turn_usage` 同名位:在场 ⇒ 这一轮的账**不是**一笔已知的账,never false。 */
451
+ usageMissing?: true;
452
+ /** 见 `last_turn_usage` 同名位:原词开集透传,缺席不铸。 */
453
+ stopReason?: string;
454
+ }
394
455
  /**
395
456
  * B3 新臂 ③(runStream 外向边切除,设计稿 §5.2 头号污染源)—— `done{status:"needs_review"}` +
396
457
  * gate `plan_review` 的 park 终帧。此前是 runStream 里一条 `await import('planReviewWire')`,
@@ -405,6 +466,34 @@ export type ChromeEvent = {
405
466
  laneProof: LaneProof;
406
467
  result: unknown;
407
468
  }
469
+ /**
470
+ * D-3 / B-068 · L-198(0.66.0;core ≥7.12.0 `TaskStats.costBreakdown` / `nested`)——
471
+ * **终局那一拍的成本对账**。载荷本体 = {@link RunCostReconcile}(与终帧超集键
472
+ * `_sema_cost_breakdown` / `_sema_nested_usage` **同一个读器**产出,两面不会各算各的)。
473
+ *
474
+ * 🔴 **为什么另起一条臂,而不是扩 `last_turn_usage` 的终局那一拍**:那条臂的宿主义务逐字是
475
+ * 「落**最近一次 turn** 的真 usage(statusline 回落源)」—— 它回答的是「这一轮」的问题,而
476
+ * 本臂回答的是「**这条 run 一共**花了多少、其中子代与压缩各多少」。把两个量塞进一条臂,宿主
477
+ * 就得在同一个载荷上分辨「这是 turn 还是 run」,那正是[same-name-different-meaning]那一族病;
478
+ * 而且 `last_turn_usage` 是 `required: true` 的既有义务面,往上加语义会改已接宿主的读法。
479
+ *
480
+ * 🔴 **子流断闸不动**:`isSubFlow` 的 `turn_end` 仍然**不发** `last_turn_usage`(§E2 判据),
481
+ * 子代花费**只**经本臂的 `nestedMicroUsd`(= 终局 `stats.nested`)到账 —— 流中增量与终局总账
482
+ * 两条腿分工不变,壳把两者相加就是重复计账。
483
+ *
484
+ * 发臂条件 = **这条终帧带得出 stats**;`stats` 缺席(409 拒绝信封 / park 体 / `failed` 事件帧)
485
+ * ⇒ 一条都不发(没有账就不说话,不发一条全缺席的空账)。非成功终局**照发** —— 花掉的钱与
486
+ * 结局好不好无关。
487
+ *
488
+ * 宿主消费义务(`required: false`):终局按本臂对账(`/cost` 面把 own / nested / compaction
489
+ * 三段各渲一行)。不接 = 必须改从终帧那两个超集键自己对账,否则子代与压缩两笔账永远不进成本面
490
+ * (B-068 原病)。🔴 判别位在场时**别渲 0**:`costAbsent` / `nestedCostAbsent` 说的是
491
+ * 「这一段没定价」,渲成 `$0.00` 就是替引擎编了一笔它没报的账。
492
+ */
493
+ | ({
494
+ kind: 'run_cost_reconciled';
495
+ laneProof: LaneProof;
496
+ } & RunCostReconcile)
408
497
  /**
409
498
  * FIX⑦(2026-08-07,[3017]/[3020];core 5.14.0 design/171 新 `TaskEvent` 臂,server 7.4.0
410
499
  * 起真上 SSE)——「**谁把什么喂进了这条 run**」的生命周期账本帧:objective / 实时 steer /
package/dist/seam.js CHANGED
@@ -34,7 +34,29 @@ const CHROME_ARM_TABLE = {
34
34
  inline_task_stats: { required: true, duty: 'tick/settle/settle_all 转发给 engineInlineTaskStats' },
35
35
  prompt_suggestions: { required: false, duty: '推 composer 上方 chips(绝不回喂模型)' },
36
36
  last_turn_usage: { required: true, duty: '落最近一次 turn 真 usage(statusline 回落源)' },
37
+ // L-228(0.67.0):per-subagent 分表。`required:false` —— 不接 = 子代详情的用量面看不见
38
+ // (一个可选披露面),不属「已发生的行为丢失」那一档;但**不接就永远说不出**每只子代花了多少
39
+ // (core 终局只有合计,没有 per-task 分项)。
40
+ subagent_turn_usage: {
41
+ required: false,
42
+ duty: '可选:按 `taskId`(= `sourceTaskId ?? parentToolCallId`,与终帧分表同键)落 per-subagent 的 turn 用量(子代详情面)。' +
43
+ '🔴 `usage` 可缺席(裸 `usageMissing` 轮),那一形要渲「这一轮的账不知道」而不是跳过;' +
44
+ '🔴 **绝不**把它加进主会话的 turn 用量 / statusline 窗口占用(那是 §E2 断闸要防的 token-jump);' +
45
+ '🔴 `usageMissing` 在场时同行 usage 是**下界**,渲「≥」不渲已知账;' +
46
+ '🔴 终帧的 `_sema_nested_usage_by_task` 是同一份账的**收口快照**,两者**不相加**(本臂是增量、那是合计)',
47
+ },
37
48
  plan_review_park: { required: true, duty: '弹 plan 审批卡(fail-soft,不挡后续终态帧)' },
49
+ // D-3 / B-068:终局对账。`required:false` —— 不接不等于哑掉(终帧的两个超集键载着同一份事实),
50
+ // 但**不接就必须自己按那两个键对账**,否则 /cost 恒低报子代与压缩两笔账(B-068 原病)。
51
+ run_cost_reconciled: {
52
+ required: false,
53
+ duty: '可选:终局成本对账(own / nested / compaction 三段 + reconciled = own + nested)。' +
54
+ '不接 ⇒ 必须改从终帧 `_sema_cost_breakdown` / `_sema_nested_usage` 自己对账,否则子代与压缩两笔账不进成本面;' +
55
+ '🔴 `costAbsent` / `nestedCostAbsent` 在场时渲「未定价」而不是 $0.00;' +
56
+ '🔴 0.67.0:`usageLowerBound` 在场时这条 run 的每一个 token / 成本数字都是**下界**(至少有一轮没报 usage)——' +
57
+ '渲「≥」或「至少」,绝不渲成一笔已知的账;它与两个 costAbsent 位**正交**(定了价也可能数不全),三句分别说;' +
58
+ '🔴 流中的 `last_turn_usage` 增量与本臂的终局总账**不相加**(子代花费只经本臂到账,相加 = 重复计账)',
59
+ },
38
60
  // FIX⑦:账本帧,**不带正文** ⇒ 义务是可选的(记进审计/时间线即可);🔴 硬约束是**不许**据此
39
61
  // 铸转录行,渲署名时必须带上 `actor.hostAsserted` 的可信度标注。
40
62
  // B-072 ④:定位账本帧,**不带正文** ⇒ 义务可选(落一张 entryId→已渲消息的映射表);