@sema-agent/client-core 0.12.1 → 0.13.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.
Files changed (127) hide show
  1. package/README.md +15 -2
  2. package/dist/abortableSleep.d.ts +29 -0
  3. package/dist/abortableSleep.js +43 -0
  4. package/dist/adapt/arms.d.ts +58 -0
  5. package/dist/adapt/arms.js +613 -0
  6. package/dist/adapt/ids.d.ts +22 -0
  7. package/dist/adapt/ids.js +34 -0
  8. package/dist/adapt/instanceLedger.d.ts +36 -0
  9. package/dist/adapt/instanceLedger.js +50 -0
  10. package/dist/adapt/panelTasks.d.ts +54 -0
  11. package/dist/adapt/panelTasks.js +193 -0
  12. package/dist/adapt/textStream.d.ts +49 -0
  13. package/dist/adapt/textStream.js +141 -0
  14. package/dist/adapt/toolCards.d.ts +65 -0
  15. package/dist/adapt/toolCards.js +100 -0
  16. package/dist/adapt/turnFlags.d.ts +33 -0
  17. package/dist/adapt/turnFlags.js +55 -0
  18. package/dist/adapt/wireShapes.d.ts +93 -0
  19. package/dist/adapt/wireShapes.js +167 -0
  20. package/dist/adapt.d.ts +32 -59
  21. package/dist/adapt.js +78 -1206
  22. package/dist/adapter/downstream/eventToSdkMessage.d.ts +55 -13
  23. package/dist/adapter/downstream/eventToSdkMessage.js +166 -106
  24. package/dist/adapter/downstream/terminalToSdkResult.js +149 -160
  25. package/dist/adapter/downstream/turnUsageToModelUsage.d.ts +36 -0
  26. package/dist/adapter/downstream/turnUsageToModelUsage.js +34 -6
  27. package/dist/adapter/runStream.d.ts +29 -4
  28. package/dist/adapter/runStream.js +129 -13
  29. package/dist/adapter/types.d.ts +2 -2
  30. package/dist/agentSession/backgroundView.js +4 -15
  31. package/dist/agentsWireCaps.d.ts +10 -6
  32. package/dist/agentsWireCaps.js +21 -7
  33. package/dist/argvFlagValue.d.ts +41 -0
  34. package/dist/argvFlagValue.js +69 -0
  35. package/dist/attachmentsWireCaps.d.ts +3 -2
  36. package/dist/attachmentsWireCaps.js +5 -3
  37. package/dist/classifierVerdictWire.d.ts +8 -0
  38. package/dist/classifierVerdictWire.js +8 -0
  39. package/dist/cloudConfigWireCaps.d.ts +28 -1
  40. package/dist/cloudConfigWireCaps.js +51 -11
  41. package/dist/controlRouter.d.ts +14 -8
  42. package/dist/controlRouter.js +15 -21
  43. package/dist/detachWire.d.ts +15 -5
  44. package/dist/detachWire.js +17 -7
  45. package/dist/effortWire.d.ts +0 -21
  46. package/dist/effortWire.js +6 -20
  47. package/dist/engineInlineTaskStats.d.ts +12 -6
  48. package/dist/engineWireSdk.d.ts +12 -0
  49. package/dist/env/localeGeo.js +2 -1
  50. package/dist/envFlag.d.ts +39 -0
  51. package/dist/envFlag.js +51 -0
  52. package/dist/finalVerifyWire.d.ts +7 -5
  53. package/dist/finalVerifyWire.js +6 -5
  54. package/dist/fleet/fleetLedger.d.ts +32 -9
  55. package/dist/fleet/fleetLedger.js +119 -34
  56. package/dist/fleet/fleetProjection.d.ts +44 -6
  57. package/dist/fleet/fleetProjection.js +52 -10
  58. package/dist/fleetAgentPanelProjection.d.ts +18 -1
  59. package/dist/fleetAgentPanelProjection.js +61 -14
  60. package/dist/forkWireCaps.d.ts +2 -1
  61. package/dist/forkWireCaps.js +5 -11
  62. package/dist/goalStopHook.d.ts +142 -0
  63. package/dist/goalStopHook.js +258 -0
  64. package/dist/headlessPermissionModeWire.d.ts +10 -0
  65. package/dist/headlessPermissionModeWire.js +24 -21
  66. package/dist/headlessReconnectWire.d.ts +7 -1
  67. package/dist/headlessReconnectWire.js +20 -2
  68. package/dist/hitl/approvalsFeed.js +31 -8
  69. package/dist/hitl/askGateWire.d.ts +27 -96
  70. package/dist/hitl/askGateWire.js +69 -546
  71. package/dist/hitl/frameRouter.d.ts +86 -0
  72. package/dist/hitl/frameRouter.js +342 -0
  73. package/dist/hitl/gateLedger.d.ts +107 -0
  74. package/dist/hitl/gateLedger.js +113 -0
  75. package/dist/hitl/hitlBridge.d.ts +75 -15
  76. package/dist/hitl/hitlBridge.js +94 -22
  77. package/dist/hitl/hitlHostSurface.d.ts +49 -0
  78. package/dist/hitl/hitlHostSurface.js +155 -0
  79. package/dist/hitl/parkResolver.d.ts +74 -0
  80. package/dist/hitl/parkResolver.js +241 -0
  81. package/dist/hitl/planReviewWire.d.ts +60 -2
  82. package/dist/hitl/planReviewWire.js +152 -74
  83. package/dist/hitl/toolApprovalWire.d.ts +67 -4
  84. package/dist/hitl/toolApprovalWire.js +119 -31
  85. package/dist/hooksWireCaps.d.ts +1 -82
  86. package/dist/hooksWireCaps.js +34 -235
  87. package/dist/host.d.ts +16 -5
  88. package/dist/index.d.ts +2 -0
  89. package/dist/index.js +15 -1
  90. package/dist/interactiveToolsWire.d.ts +15 -4
  91. package/dist/interactiveToolsWire.js +24 -26
  92. package/dist/limitsWire.js +7 -40
  93. package/dist/liveInitToolFace.d.ts +51 -6
  94. package/dist/liveQuestionStore.d.ts +5 -6
  95. package/dist/model/providerPresets.js +11 -1
  96. package/dist/notifications.d.ts +48 -2
  97. package/dist/notifications.js +223 -46
  98. package/dist/retainBackgroundWireCaps.d.ts +3 -2
  99. package/dist/retainBackgroundWireCaps.js +5 -9
  100. package/dist/sandboxWire.d.ts +9 -31
  101. package/dist/sandboxWire.js +51 -50
  102. package/dist/scenarioWire.d.ts +1 -1
  103. package/dist/scenarioWire.js +25 -36
  104. package/dist/seam.d.ts +23 -6
  105. package/dist/seam.js +40 -30
  106. package/dist/seatContract.d.ts +369 -83
  107. package/dist/seatContract.js +585 -198
  108. package/dist/selfOrchestrationWireCaps.d.ts +6 -5
  109. package/dist/selfOrchestrationWireCaps.js +8 -12
  110. package/dist/sessionSlot.d.ts +8 -0
  111. package/dist/sessionSlot.js +1 -0
  112. package/dist/steering.js +2 -2
  113. package/dist/subagent/engineTaskHandleWire.d.ts +3 -0
  114. package/dist/subagent/engineTaskHandleWire.js +17 -2
  115. package/dist/subagentContentStore.d.ts +5 -5
  116. package/dist/toolResult.d.ts +89 -8
  117. package/dist/toolResult.js +99 -30
  118. package/dist/typePins.d.ts +17 -0
  119. package/dist/typePins.js +1 -0
  120. package/dist/ultracodeWireCaps.js +5 -6
  121. package/dist/unrefTimer.d.ts +19 -0
  122. package/dist/unrefTimer.js +5 -0
  123. package/dist/workflow.d.ts +3 -2
  124. package/dist/workflow.js +3 -2
  125. package/dist/workflowClient.d.ts +7 -0
  126. package/dist/workflowClient.js +47 -12
  127. package/package.json +3 -3
@@ -1,5 +1,10 @@
1
1
  import { stamp } from '../types.js';
2
- /** Flatten TaskStats the CC NonNullableUsage placeholder (z.unknown()). */
2
+ import { toCcModelUsage } from './turnUsageToModelUsage.js';
3
+ /**
4
+ * Flatten TaskStats → the CC NonNullableUsage placeholder.
5
+ * 🔴 REF-CC-055(xlate-01):`failedToSdkResult` 此前手抄了一份「五键全零」的字面量,那不是
6
+ * 另一种形状,就是本函数 `stats === undefined` 的退化输出 —— 现在它也走这里(单一构造点)。
7
+ */
3
8
  function flattenUsage(stats) {
4
9
  return {
5
10
  inputTokens: stats?.promptTokens ?? 0,
@@ -9,13 +14,6 @@ function flattenUsage(stats) {
9
14
  webSearchRequests: 0,
10
15
  };
11
16
  }
12
- /**
13
- * MF-25 — map the wire's per-model usage split (`stats.modelUsage`, an open `{ <modelId>: usage }` map;
14
- * e.g. `{ "deepseek-v4-pro": {...} }`) into the CC `SDKResult.modelUsage` record (model-id → CC ModelUsage).
15
- * The wire usage names (`cacheReadTokens`/`cacheWriteTokens`/`costMicroUsd`) are re-pinned to the CC names
16
- * (`cacheReadInputTokens`/`cacheCreationInputTokens`/`costUSD`, costMicroUsd/1e6 → USD), matching
17
- * turnUsageToModelUsage. `contextWindow`/`maxOutputTokens` are not on the wire → 0. Returns `{}` when absent.
18
- */
19
17
  /** P1-5 — real elapsed ms since the stream opened (runStream stamps startedAtMs); 0 only pre-stamp. */
20
18
  function elapsedMs(ctx) {
21
19
  return typeof ctx.startedAtMs === 'number' ? Math.max(0, Date.now() - ctx.startedAtMs) : 0;
@@ -59,36 +57,124 @@ function costOrNull(stats) {
59
57
  }
60
58
  // 件2a(中断事故修复批 G,2026-07-15,症状2 第一环):参数放宽 TaskStats | undefined + 全链守卫。
61
59
  // 409 active-run 拒绝时引擎的 done 帧本体是 {status:'failed', errorMessage, activeTaskId} —— NO stats
62
- // (ai-agent-service server.ts:1526 同型),此前 :59 裸读 (stats).modelUsage 直接 TypeError,炸穿
63
- // runStream → seamQuery catch → 误诊 "engine is unreachable"(件2c)。缺 stats 时产出合法空 usage
64
- // 形状(flattenUsage 本就守卫;这里返回 {},调用方 :98 再按 r.model 合成当前行)。
60
+ // (ai-agent-service server.ts:1526 同型),此前 `mapModelUsage` 裸读 `(stats).modelUsage` 直接
61
+ // TypeError,炸穿 runStream → seamQuery catch → 误诊 "engine is unreachable"(件2c)。缺 stats
62
+ // 产出合法空 usage 形状(flattenUsage 本就守卫;这里返回 {},`modelUsageFor` 再按 r.model 合成当前行)。
63
+ //
64
+ // MF-25 — map the wire's per-model usage split (`stats.modelUsage`, an open `{ <modelId>: usage }` map;
65
+ // e.g. `{ "deepseek-v4-pro": {...} }`) into the CC `SDKResult.modelUsage` record (model-id → CC ModelUsage).
66
+ // 🔴 REF-CC-062(xlate-08):八键形状与那套 load-bearing 改名**不在本文件构造** —— 单源 mint 点是
67
+ // `turnUsageToModelUsage.ts` 的 `toCcModelUsage()`(core 3.0.0 的 inputTokens 语义翻转注也钉在那里)。
68
+ // 本函数只负责 wire 边界的窄化:开集 map 的每个值都是 `unknown`,先取出数值位再交给 mint 点。
65
69
  function mapModelUsage(stats) {
66
70
  const raw = stats?.modelUsage;
67
- if (!raw || typeof raw !== 'object')
71
+ if (raw === undefined || raw === null || typeof raw !== 'object')
68
72
  return {};
69
73
  const out = {};
74
+ /** 边界窄化:非数值一律当缺席交给 mint 点(它把缺席与非有限值一并折 0)。 */
75
+ const numOrAbsent = (v) => (typeof v === 'number' ? v : undefined);
70
76
  for (const [modelId, u] of Object.entries(raw)) {
71
- if (!u || typeof u !== 'object')
77
+ if (u === undefined || u === null || typeof u !== 'object')
72
78
  continue;
73
79
  const m = u;
74
- const num = (v) => (typeof v === 'number' && Number.isFinite(v) ? v : 0);
75
- out[modelId] = {
76
- inputTokens: num(m.inputTokens),
77
- outputTokens: num(m.outputTokens),
78
- cacheReadInputTokens: num(m.cacheReadTokens),
79
- cacheCreationInputTokens: num(m.cacheWriteTokens),
80
- webSearchRequests: 0,
81
- costUSD: microUsdToUsd(m.costMicroUsd) ?? 0, // ⇐ 与 costOrNull 同一个换算口(旧写法 num(...)/1e6 逐值等价)
82
- contextWindow: 0,
83
- maxOutputTokens: 0,
84
- };
80
+ out[modelId] = toCcModelUsage({
81
+ inputTokens: numOrAbsent(m.inputTokens),
82
+ outputTokens: numOrAbsent(m.outputTokens),
83
+ cacheReadTokens: numOrAbsent(m.cacheReadTokens),
84
+ cacheWriteTokens: numOrAbsent(m.cacheWriteTokens),
85
+ costMicroUsd: numOrAbsent(m.costMicroUsd),
86
+ });
85
87
  }
86
88
  return out;
87
89
  }
90
+ /**
91
+ * P1-5 — 终帧的 `modelUsage`:优先 wire 的 per-model split;缺席(老引擎 stats 无 MF-25 分表,或
92
+ * split-less done)时按**当前模型**从扁平 `TaskStats` 合成一行,JSON 消费者才知道这笔 usage 是谁产的。
93
+ * 🔴 REF-CC-055/062:success 臂与五个 error 臂此前各自持有这段逻辑的结果(合成行只在 success 路径
94
+ * 算一次、error 臂靠共享局部变量捎带),现在两条路都调本函数 —— 同一输入同一输出,单一构造点。
95
+ */
96
+ function modelUsageFor(stats, model) {
97
+ const split = mapModelUsage(stats);
98
+ if (Object.keys(split).length > 0)
99
+ return split;
100
+ if (typeof model !== 'string' || model.length === 0)
101
+ return split;
102
+ // 键迁移(见 costOrNull 头注):legacy float `costUsd` → 整数 `costMicroUsd`(/1e6 → USD)。
103
+ // ⚠️ 这一处**类型门抓不到**:TaskStats 带 `[key: string]: unknown` 开集索引,`typeof
104
+ // stats?.costUsd === 'number'` 的旧写法在 SDK 1.x 下照样编译得过,只是恒假 ⇒ 合成行的
105
+ // costUSD 静默恒 0(比报错更隐蔽)。tsc 只红了 6 处直传,这处靠人核 + pure 门的行为钉。
106
+ return {
107
+ [model]: toCcModelUsage({
108
+ inputTokens: stats?.promptTokens,
109
+ outputTokens: stats?.outputTokens,
110
+ cacheReadTokens: stats?.cachedTokens,
111
+ cacheWriteTokens: 0,
112
+ costMicroUsd: stats?.costMicroUsd,
113
+ }),
114
+ };
115
+ }
116
+ /**
117
+ * TaskResult 五键最小消费批([1543] SDK-M,2026-07-23)之一 —— `degraded` = 模型降级链实录
118
+ * (from/to/reason/atTurn;多模型网关部署的关键可观测面)。
119
+ *
120
+ * 🔴 REF-CC-056(xlate-02):文件头共享注一直写着「success/timeout/blocked/unknown 各终态都可能带,
121
+ * **恒 additive 附上**」,而实际只有 timeout 与 success 两臂 spread 了它,blocked/park/未知/failed
122
+ * 四臂全漏 —— 一条经历过降级并最终失败的 run,降级链在终帧上无痕消失,而注释向下一棒承诺它在。
123
+ * 修法不是再补四处 spread(那是第五、六个构造点),而是把它放进 {@link errorResult} 信封本体:
124
+ * 「恒 additive 附上」这句话从此是结构,不是散文。
125
+ *
126
+ * 边界守门(M1):wire 上的值仍按对象守卫 —— 非对象/缺席一律不 stamp 这个键(诚实缺席)。
127
+ */
128
+ function degradedOf(r) {
129
+ const d = r.degraded;
130
+ return d !== undefined && d !== null && typeof d === 'object' ? d : undefined;
131
+ }
132
+ /**
133
+ * 🔴 **CC `SDKResultError` 信封的唯一 mint 点**(REF-CC-055 / xlate-01,E1 单源构造)。
134
+ *
135
+ * 收编前这 13 个位在**六处**逐字重复:done 的 failed/timeout/blocked/park/未知终态五臂,加上
136
+ * `failedToSdkResult` —— 后者看着像「另一种信封」,其实是同一信封 `stats === undefined` 的退化
137
+ * 实例被手抄了一遍(`num_turns: 0` ≡ `stats?.turns ?? 0`、`total_cost_usd: null` ≡ `costOrNull(undefined)`、
138
+ * 手写五键零 usage ≡ `flattenUsage(undefined)`、`modelUsage: {}` ≡ `mapModelUsage(undefined)`)。
139
+ * 六份手抄的代价不是行数,是 REF-CC-056 那种**漏配**:给信封加一个位要改六处,漏掉的那几处没有
140
+ * 任何门会说话。
141
+ */
142
+ function errorResult(ctx, parts) {
143
+ return stamp(ctx, {
144
+ // Explicit (always-undefined) uuid/session_id: stamp() always overwrites session_id from
145
+ // ctx and falls back to a fresh uuid when body.uuid is nullish, so this is a pure type-level
146
+ // nudge (zero runtime effect) — without at least one property name in common with stamp's
147
+ // all-optional constraint, TS's "weak type" detection rejects the call (TS2559/TS2345/TS2353).
148
+ uuid: undefined,
149
+ session_id: undefined,
150
+ type: 'result',
151
+ subtype: parts.subtype,
152
+ duration_ms: parts.durationMs, // P1-5 real elapsed (stream-open → terminal)
153
+ duration_api_ms: parts.durationMs, // engine run == the API call from the shell's view
154
+ is_error: true,
155
+ num_turns: parts.stats?.turns ?? 0, // 件2a 同款守卫 —— 缺 stats 不炸
156
+ stop_reason: null, // dropped (contract 02 §2.10)
157
+ total_cost_usd: costOrNull(parts.stats), // P1-5: null when unknown, never a fake 0
158
+ usage: flattenUsage(parts.stats),
159
+ modelUsage: modelUsageFor(parts.stats, parts.model),
160
+ permission_denials: [],
161
+ errors: [...parts.errors],
162
+ ...(parts.errorCode !== undefined && parts.errorCode.length > 0 ? { errorCode: parts.errorCode } : {}),
163
+ ...(parts.degraded !== undefined ? { degraded: parts.degraded } : {}),
164
+ ...(parts.extra ?? {}),
165
+ });
166
+ }
88
167
  /** `done` → SDKResultSuccess (contract 02 §2.10 / 08 CS-10). */
89
168
  export function doneToSdkResult(ev, ctx) {
90
- const r = ev.result;
91
- // 件2a:409/failed 形态的 done 帧不带 stats —— 本函数全链按 undefined 守卫(:97/:121/:145)。
169
+ // sdk 4.1.0([2395]E 调和):`done.result` 是判别联合 `TaskResult | ActiveRunConflictDoneResult`。
170
+ // 🔄 全窗复审 ADAPTER-1/-2 收口(2026-08-03):首版接线用 `'stats' in r` tr 并让 result/model/
171
+ // degraded 全走 tr?.——把「stats 在不在」错当成了这些键的门控(anchor-on-the-deciding-quantity
172
+ // 反例):R1b 抢救腿从 run row 合成的 done 帧 stats 可缺席但带答案正文,被投影成空成功;且
173
+ // `in` 对非对象 result 抛 TypeError(旧码宽读不抛,件2c 事故形)。正解=各键**独立宽读**
174
+ // (键在就读、缺席 undefined,与拆分前逐键同语义),判别联合只影响类型声明不影响读法。
175
+ const rRaw = ev.result;
176
+ const rec = typeof rRaw === 'object' && rRaw !== null ? rRaw : {};
177
+ const r = rec;
92
178
  const stats = r.stats;
93
179
  // P1-5 — the terminal frame's session_id must be the ENGINE-AUTHORITATIVE id (done.result.sessionId):
94
180
  // on the headless `-p` path ctx was seeded from the REQUEST (historically the mock constant
@@ -97,25 +183,8 @@ export function doneToSdkResult(ev, ctx) {
97
183
  const echoedSid = r.sessionId;
98
184
  if (typeof echoedSid === 'string' && echoedSid.length > 0)
99
185
  ctx.sessionId = echoedSid;
100
- // P1-5 — modelUsage: prefer the wire's per-model split; when absent (local engine pre-MF-25 stats or a
101
- // split-less done), synthesize the CURRENT model's row from the flat TaskStats so JSON consumers always
102
- // see which model produced the run's usage.
103
- const usage = flattenUsage(stats);
104
- let modelUsage = mapModelUsage(stats);
105
- if (Object.keys(modelUsage).length === 0 && typeof r.model === 'string' && r.model.length > 0) {
106
- modelUsage = {
107
- [r.model]: {
108
- ...usage,
109
- // 键迁移(见 costOrNull 头注):legacy float `costUsd` → 整数 `costMicroUsd`(/1e6 → USD)。
110
- // ⚠️ 这一处**类型门抓不到**:TaskStats 带 `[key: string]: unknown` 开集索引,`typeof
111
- // stats?.costUsd === 'number'` 的旧写法在 SDK 1.x 下照样编译得过,只是恒假 ⇒ 合成行的
112
- // costUSD 静默恒 0(比报错更隐蔽)。tsc 只红了 6 处直传,这处靠人核。
113
- costUSD: microUsdToUsd(stats?.costMicroUsd) ?? 0,
114
- contextWindow: 0,
115
- maxOutputTokens: 0,
116
- },
117
- };
118
- }
186
+ // P1-5 — modelUsage/usage 的构造已收敛到 `flattenUsage` / `modelUsageFor`(REF-CC-055/062),
187
+ // 五个 error 臂经 `errorResult` 信封拿到同一份,success 臂在下方直接调 —— 同一输入同一输出。
119
188
  // clay P0 壳半场(黑板 [450],2026-07-05):上游 4xx(如无 vision 模型收到 image)时
120
189
  // 引擎的 done 帧本体带 result.status='failed' + errorMessage —— 传播链没吞,断点在这:
121
190
  // 此前无条件投影 subtype:'success'/is_error:false,把失败洗成"成功空回复"= 用户看到的
@@ -130,8 +199,15 @@ export function doneToSdkResult(ev, ctx) {
130
199
  const rr = r;
131
200
  const durationMs = elapsedMs(ctx);
132
201
  // [909]B1 — errorCode 透传(additive seam 字段;CC schema 无此键,与 success 臂 `model` 同款超集
133
- // 姿势):引擎值原样,绝不铸造。所有 error 臂共用。
134
- const errorCodeField = typeof rr.errorCode === 'string' && rr.errorCode.length > 0 ? { errorCode: rr.errorCode } : {};
202
+ // 姿势):引擎值原样,绝不铸造。所有 error 臂共用(`errorResult` 信封负责 stamp)。
203
+ const errorCode = typeof rr.errorCode === 'string' && rr.errorCode.length > 0 ? rr.errorCode : undefined;
204
+ // TaskResult 五键最小消费批([1543] SDK-M,2026-07-23):additive seam 字段——
205
+ // · degraded = 模型降级链实录,**各终态恒 additive 附上**(见 `degradedOf` 头注 / REF-CC-056);
206
+ // · structuredOutput = outputSchema 任务的结构化产出(success 臂);
207
+ // · salvagedOutput = degenerate/timeout 抢救出的最后模型文本(timeout 臂 salvage 回落源)。
208
+ const degraded = degradedOf(r);
209
+ /** 五个 error 臂共享的固定位(信封其余 13 位见 `errorResult`)。 */
210
+ const errorBase = { durationMs, stats, model: r.model, errorCode, degraded };
135
211
  if (rr.status === 'failed') {
136
212
  // [909]B1 — failed 臂 subtype 语义化(见文件头对表):budget.* → error_max_budget_usd(与
137
213
  // failedToSdkResult 事件臂同款分流,此前 done{failed} 恒 error_during_execution);
@@ -146,24 +222,9 @@ export function doneToSdkResult(ev, ctx) {
146
222
  : rr.errorCode === 'output.invalid'
147
223
  ? 'error_max_structured_output_retries'
148
224
  : 'error_during_execution';
149
- return stamp(ctx, {
150
- // Explicit (always-undefined) uuid/session_id: stamp() always overwrites session_id from
151
- // ctx and falls back to a fresh uuid when body.uuid is nullish, so this is a pure type-level
152
- // nudge (zero runtime effect) — without at least one property name in common with stamp's
153
- // all-optional constraint, TS's "weak type" detection rejects the call (TS2559/TS2345/TS2353).
154
- uuid: undefined,
155
- session_id: undefined,
156
- type: 'result',
225
+ return errorResult(ctx, {
226
+ ...errorBase,
157
227
  subtype: failedSubtype,
158
- duration_ms: durationMs, // P1-5 real elapsed (stream-open → terminal)
159
- duration_api_ms: durationMs, // engine run == the API call from the shell's view
160
- is_error: true,
161
- num_turns: stats?.turns ?? 0,
162
- stop_reason: null,
163
- total_cost_usd: costOrNull(stats), // P1-5: null when unknown, never a fake 0
164
- usage,
165
- modelUsage,
166
- permission_denials: [],
167
228
  // rewind 族错误码透传(server 1.192 [838]③ 实测:stream 腿把 resume_at.before_* /
168
229
  // rewind_snapshot.unresolvable 骑在 done{status:"failed"} 帧的 errorCode 上,非 `failed` 帧)——
169
230
  // 与 failedToSdkResult 的同款 append 对齐,message 后附 code 便于对账。
@@ -174,7 +235,6 @@ export function doneToSdkResult(ev, ctx) {
174
235
  ? `${rr.errorMessage} (${rr.errorCode})`
175
236
  : (rr.errorMessage ?? 'run failed'),
176
237
  ],
177
- ...errorCodeField,
178
238
  });
179
239
  }
180
240
  // [909]B1 — timeout 终态:--deadline 配速的受控截止收尾(assemble-result.js:69/88
@@ -183,15 +243,6 @@ export function doneToSdkResult(ev, ctx) {
183
243
  // 收尾帧([909]B2 关联半场;帧完整落地由 stdout drain 半场保证)。subtype 选型见文件头对表:
184
244
  // CC 词表无 wall-clock 词,唯一 run-limit 族词 = error_max_turns;精确语义靠 errors[] 文案 +
185
245
  // errorCode:"limit.timeout" 透传。rc 语义顺出:print.ts gracefulShutdownSync(is_error ? 1 : 0)。
186
- // TaskResult 五键最小消费批([1543] SDK-M,2026-07-23):additive seam 字段——
187
- // · degraded = 模型降级链实录(from/to/reason/atTurn;多模型网关部署的关键可观测面)——
188
- // success/timeout/blocked/unknown 各终态都可能带,恒 additive 附上;
189
- // · structuredOutput = outputSchema 任务的结构化产出(success 臂);
190
- // · salvagedOutput = degenerate/timeout 抢救出的最后模型文本(timeout 臂 salvage 回落源)。
191
- const degradedField = (() => {
192
- const d = r.degraded;
193
- return d !== undefined && d !== null && typeof d === 'object' ? { degraded: d } : {};
194
- })();
195
246
  if (rr.status === 'timeout') {
196
247
  const salvagedOutput = r.salvagedOutput;
197
248
  const salvaged = typeof r.result === 'string' && r.result.trim().length > 0
@@ -199,10 +250,8 @@ export function doneToSdkResult(ev, ctx) {
199
250
  : typeof salvagedOutput === 'string' && salvagedOutput.trim().length > 0
200
251
  ? salvagedOutput
201
252
  : undefined;
202
- return stamp(ctx, {
203
- uuid: undefined,
204
- session_id: undefined,
205
- type: 'result',
253
+ return errorResult(ctx, {
254
+ ...errorBase,
206
255
  // [2084]②a(2026-07-30,repro/cli-walltime-timeout-reported-as-max-turns):此前硬编码
207
256
  // error_max_turns(选型理由是「CC 词表唯一 run-limit 族词」),但那让同一帧自相矛盾
208
257
  // (errorCode=limit.timeout + num_turns 远小于上限,test 实测 num_turns:2 且根本没设
@@ -211,26 +260,15 @@ export function doneToSdkResult(ev, ctx) {
211
260
  // 真实语义仍靠 errorCode:"limit.timeout" + errors[] 文案判别(原设计轴不动)。
212
261
  // 副产品:真 limit.max_turns 停(failed 臂)与墙钟停在 subtype 上重新可分。
213
262
  subtype: 'error_during_execution',
214
- duration_ms: durationMs,
215
- duration_api_ms: durationMs,
216
- is_error: true,
217
- num_turns: stats?.turns ?? 0,
218
- stop_reason: null,
219
- total_cost_usd: costOrNull(stats),
220
- usage,
221
- modelUsage,
222
- permission_denials: [],
223
263
  // [2084]②b:引擎的 cutoff 句承诺「cut off to preserve a write-out window」,但 pre-call
224
264
  // throw 路径上写出窗可能零兑现(core dist/brain/stream-engine 五处 throw,载体天生空)。
225
265
  // 抢救为空时补诚实尾注——每一层都复述没兑现的承诺,消费者就无从分辨救没救回来。
226
266
  errors: [
227
267
  `${rr.errorMessage ?? 'run hit its wall-clock deadline and was finalized by the engine'} (${rr.errorCode ?? 'limit.timeout'})${salvaged === undefined ? ' — no output survived the cutoff' : ''}`,
228
268
  ],
229
- ...errorCodeField,
230
269
  // 写出窗 salvage(additive seam 字段):deadline 前引擎救回的最终文本非空时随帧带出——
231
270
  // CC error 帧无 result 键,但静默丢弃已救回的答案对 -p 集成面是净损失(TB qe** 实证)。
232
- ...(salvaged !== undefined ? { result: salvaged } : {}),
233
- ...degradedField,
271
+ ...(salvaged !== undefined ? { extra: { result: salvaged } } : {}),
234
272
  });
235
273
  }
236
274
  // [909]B1 — blocked 终态:agent 自报无法推进(assemble-result.js:99,只带 blockedReason 无
@@ -238,26 +276,14 @@ export function doneToSdkResult(ev, ctx) {
238
276
  // subtype:success)。≠空成功 → SDKResultError,blockedReason 如实进 errors[]。
239
277
  if (rr.status === 'blocked') {
240
278
  const blockedReason = r.blockedReason;
241
- return stamp(ctx, {
242
- uuid: undefined,
243
- session_id: undefined,
244
- type: 'result',
279
+ return errorResult(ctx, {
280
+ ...errorBase,
245
281
  subtype: 'error_during_execution',
246
- duration_ms: durationMs,
247
- duration_api_ms: durationMs,
248
- is_error: true,
249
- num_turns: stats?.turns ?? 0,
250
- stop_reason: null,
251
- total_cost_usd: costOrNull(stats),
252
- usage,
253
- modelUsage,
254
- permission_denials: [],
255
282
  errors: [
256
283
  typeof blockedReason === 'string' && blockedReason.length > 0
257
284
  ? `Run blocked: ${blockedReason}`
258
285
  : 'Run blocked (the agent reported it cannot proceed)',
259
286
  ],
260
- ...errorCodeField,
261
287
  });
262
288
  }
263
289
  // [884]A1(TB 公平 P0,2026-07-16):suspended/needs_review park 终帧不许洗成空成功。
@@ -287,49 +313,26 @@ export function doneToSdkResult(ev, ctx) {
287
313
  const wayOut = rr.status === 'needs_review'
288
314
  ? `Review the plan in the interactive REPL${sid ? ` (sema --resume ${sid})` : ''}.`
289
315
  : `Answer it in the interactive REPL${sid ? ` (sema --resume ${sid})` : ''}, or decide the pending approval via the engine approvals API${sid ? ` (POST /v1/approvals/${sid}/decide)` : ''}.`;
290
- return stamp(ctx, {
291
- uuid: undefined,
292
- session_id: undefined,
293
- type: 'result',
316
+ // [909]B1 — needs_review 帧带 review.pending;errorCode 由信封原样透传。
317
+ return errorResult(ctx, {
318
+ ...errorBase,
294
319
  subtype: 'error_during_execution',
295
- duration_ms: durationMs,
296
- duration_api_ms: durationMs,
297
- is_error: true,
298
- num_turns: stats?.turns ?? 0, // 件2a 同款守卫 —— park 帧多数不带 stats,不炸
299
- stop_reason: null,
300
- total_cost_usd: costOrNull(stats),
301
- usage,
302
- modelUsage,
303
- permission_denials: [],
304
320
  errors: [
305
321
  `Turn suspended awaiting ${awaiting} that no one can provide in -p (non-interactive) mode${runId ? ` (run ${runId})` : ''}. ${wayOut}`,
306
322
  ],
307
- ...errorCodeField, // [909]B1 — needs_review 帧带 review.pending,原样透传
308
323
  });
309
324
  }
310
325
  // [909]B1 — 防御臂:词表外的未知非 completed 终态(core 未来加词/未知 wire 变形)绝不洗成
311
326
  // 空成功——error_during_execution + errors[] 如实报出该 status 字面。absent status(mock 车道 /
312
327
  // 旧引擎 done 帧不带 status)维持 success 臂,零行为变化。
313
328
  if (typeof rr.status === 'string' && rr.status.length > 0 && rr.status !== 'completed') {
314
- return stamp(ctx, {
315
- uuid: undefined,
316
- session_id: undefined,
317
- type: 'result',
329
+ return errorResult(ctx, {
330
+ ...errorBase,
318
331
  subtype: 'error_during_execution',
319
- duration_ms: durationMs,
320
- duration_api_ms: durationMs,
321
- is_error: true,
322
- num_turns: stats?.turns ?? 0,
323
- stop_reason: null,
324
- total_cost_usd: costOrNull(stats),
325
- usage,
326
- modelUsage,
327
- permission_denials: [],
328
332
  errors: [
329
333
  rr.errorMessage ??
330
334
  `run ended with terminal status "${rr.status}" (not a success; this CLI predates that status word)`,
331
335
  ],
332
- ...errorCodeField,
333
336
  });
334
337
  }
335
338
  return stamp(ctx, {
@@ -340,19 +343,19 @@ export function doneToSdkResult(ev, ctx) {
340
343
  duration_ms: durationMs, // P1-5 real elapsed (stream-open → terminal); the wire carries none
341
344
  duration_api_ms: durationMs, // engine run == the API call from the shell's view
342
345
  is_error: false,
343
- num_turns: stats?.turns ?? 0, // 件2a:同 :121 —— 缺 stats 不炸
346
+ num_turns: stats?.turns ?? 0, // 件2a:同 costOrNull 那一路 —— 缺 stats 不炸
344
347
  result: r.result ?? '',
345
- // TaskResult 五键(additive):outputSchema 任务的结构化产出 + 降级实录(见上方共享注)。
348
+ // TaskResult 五键(additive):outputSchema 任务的结构化产出 + 降级实录(见 `degradedOf`)。
346
349
  ...(() => {
347
350
  const so = r.structuredOutput;
348
351
  return so !== undefined ? { structuredOutput: so } : {};
349
352
  })(),
350
- ...degradedField,
353
+ ...(degraded !== undefined ? { degraded } : {}),
351
354
  stop_reason: null, // dropped (contract 02 §2.10)
352
355
  total_cost_usd: costOrNull(stats), // P1-5: 零成本 = "unconfigured pricing" → null, never a fake 0
353
- usage,
356
+ usage: flattenUsage(stats),
354
357
  // MF-25/P1-5 — the per-model usage split (wire `stats.modelUsage`, or the synthesized current-model row).
355
- modelUsage,
358
+ modelUsage: modelUsageFor(stats, r.model),
356
359
  permission_denials: [],
357
360
  // MF-25 — the effective served model id (`done.result.model`, e.g. "deepseek-v4-pro"). The CC
358
361
  // SDKResultSuccess schema has no `model` field, so this rides as an additive seam field a cost/overview
@@ -368,27 +371,16 @@ export function failedToSdkResult(ev, ctx) {
368
371
  const subtype = ev.errorCode === 'budget.exceeded' || ev.errorCode === 'budget.precall'
369
372
  ? 'error_max_budget_usd'
370
373
  : 'error_during_execution';
371
- const durationMs = elapsedMs(ctx);
372
- return stamp(ctx, {
373
- uuid: undefined,
374
- session_id: undefined,
375
- type: 'result',
374
+ // 🔴 REF-CC-055:此臂**不是另一种信封**,是 `errorResult` 在 `stats === undefined` 时的退化实例
375
+ // (`num_turns: 0` ≡ `stats?.turns ?? 0`、`total_cost_usd: null` ≡ `costOrNull(undefined)`、
376
+ // 五键零 usage ≡ `flattenUsage(undefined)`、`modelUsage: {}` ≡ `modelUsageFor(undefined, undefined)`)。
377
+ // `failed` 事件帧本体不带 stats/model/degraded,所以这三个位如实缺席 —— 不是「这里少算了」。
378
+ return errorResult(ctx, {
376
379
  subtype,
377
- duration_ms: durationMs, // P1-5 real elapsed (stream-open → terminal)
378
- duration_api_ms: durationMs,
379
- is_error: true,
380
- num_turns: 0,
381
- stop_reason: null,
382
- total_cost_usd: null, // P1-5: a failed run has no known cost — never a fake 0
383
- usage: {
384
- inputTokens: 0,
385
- outputTokens: 0,
386
- cacheReadInputTokens: 0,
387
- cacheCreationInputTokens: 0,
388
- webSearchRequests: 0,
389
- },
390
- modelUsage: {},
391
- permission_denials: [],
380
+ durationMs: elapsedMs(ctx),
381
+ // [909]B1 — errorCode 透传(additive seam 字段;done 臂同款):`failed` 事件的 cancelled/
382
+ // budget.* 等引擎值原样给集成面。
383
+ errorCode: ev.errorCode,
392
384
  // errorCode + errorMessage flattened into the single CC errors[] string.
393
385
  // Rewind-family codes(core 1.292 [833]:resume_at.before_target_not_user /
394
386
  // resume_at.before_root_unsupported / rewind_snapshot.unresolvable)透传给用户 —— 这些是
@@ -400,9 +392,6 @@ export function failedToSdkResult(ev, ctx) {
400
392
  ? `${ev.errorMessage} (${ev.errorCode})`
401
393
  : (ev.errorMessage ?? ev.errorCode ?? 'run failed'),
402
394
  ],
403
- // [909]B1 — errorCode 透传(additive seam 字段;done 臂同款):`failed` 事件的 cancelled/
404
- // budget.* 等引擎值原样给集成面。
405
- ...(typeof ev.errorCode === 'string' && ev.errorCode.length > 0 ? { errorCode: ev.errorCode } : {}),
406
395
  });
407
396
  }
408
397
  /** Dispatch a terminal AgentEvent to its SDKResult arm. */
@@ -36,5 +36,41 @@ export type EngineTurnUsage = NonNullable<Extract<AgentEvent, {
36
36
  type: 'turn_end';
37
37
  }>['usage']>;
38
38
  type TurnUsage = EngineTurnUsage;
39
+ /**
40
+ * 引擎侧 usage 的**读形**(wire 名,全可选)—— `toCcModelUsage` 的入参。
41
+ * 全可选是有理由的:本形状同时承接三种来源(`turn_end.usage` 六键必填 / `stats.modelUsage`
42
+ * 开集 map 里的一行 / 扁平 `TaskStats` 折出来的当前模型行),后两者的键在旧引擎上真的会缺席。
43
+ * 缺席语义 = **该量在 wire 上没有**,统一折 0(CC `ModelUsage` 的形状不承载 `null`);
44
+ * 非有限值(NaN/Infinity)同样折 0 —— 见 {@link toCcModelUsage} 头注。
45
+ */
46
+ export interface EngineUsageLike {
47
+ readonly inputTokens?: number | undefined;
48
+ readonly outputTokens?: number | undefined;
49
+ readonly cacheReadTokens?: number | undefined;
50
+ readonly cacheWriteTokens?: number | undefined;
51
+ readonly costMicroUsd?: number | undefined;
52
+ }
53
+ /**
54
+ * 🔴 **CC `ModelUsage` 八键形状的唯一 mint 点**(REF-CC-062 / xlate-08,E1 单源构造)。
55
+ *
56
+ * 收编前本仓有四个构造点(本文件 + `terminalToSdkResult` 的 `mapModelUsage`、合成当前模型行、
57
+ * `failedToSdkResult` 内联零 usage),四份各写一遍同一套 load-bearing 改名,而 core 3.0.0 的
58
+ * 语义翻转注只挂在其中一处 —— 「同名不同义」在这种分裂下没有任何一层拦得住([2292] 的 cli HUD
59
+ * 双算 cache 就是这一族)。收编后:名字映射与语义注都只有这一份。
60
+ *
61
+ * 名字映射(**load-bearing,一个字节都别动**):
62
+ * inputTokens → inputTokens
63
+ * outputTokens → outputTokens
64
+ * cacheReadTokens → cacheReadInputTokens
65
+ * cacheWriteTokens → cacheCreationInputTokens
66
+ * costMicroUsd → costUSD(/1e6;micro-USD 是 wire 单位,字段名即单位)
67
+ * DROPPED(wire 无此数,填 0 而不是编一个):webSearchRequests / contextWindow / maxOutputTokens。
68
+ *
69
+ * 收编时的**一处语义统一**(REF-CC-062,记在这里以免被读成无意的漂移):cost 的换算此前两份
70
+ * 写法不同 —— 本文件是裸 `/1e6`(非有限值原样传播出 Infinity/NaN),`terminalToSdkResult` 是
71
+ * `microUsdToUsd() ?? 0`(非有限即 0)。统一取后者:Infinity 会被下游当成真数字参与求和,把一条
72
+ * 离谱账静默摊进总计,比 0 更糟(与 SDK `taskCostMicroUsd()` 的规约同义)。
73
+ */
74
+ export declare function toCcModelUsage(raw: EngineUsageLike): ModelUsage;
39
75
  export declare function turnUsageToModelUsage(usage: TurnUsage): ModelUsage;
40
76
  export {};
@@ -1,12 +1,40 @@
1
- export function turnUsageToModelUsage(usage) {
1
+ /** wire 数值 → CC 形状里的数值:非数/非有限一律 0(Infinity 会被当真数字摊进总计,比 0 更糟) */
2
+ function finiteOrZero(v) {
3
+ return typeof v === 'number' && Number.isFinite(v) ? v : 0;
4
+ }
5
+ /**
6
+ * 🔴 **CC `ModelUsage` 八键形状的唯一 mint 点**(REF-CC-062 / xlate-08,E1 单源构造)。
7
+ *
8
+ * 收编前本仓有四个构造点(本文件 + `terminalToSdkResult` 的 `mapModelUsage`、合成当前模型行、
9
+ * `failedToSdkResult` 内联零 usage),四份各写一遍同一套 load-bearing 改名,而 core 3.0.0 的
10
+ * 语义翻转注只挂在其中一处 —— 「同名不同义」在这种分裂下没有任何一层拦得住([2292] 的 cli HUD
11
+ * 双算 cache 就是这一族)。收编后:名字映射与语义注都只有这一份。
12
+ *
13
+ * 名字映射(**load-bearing,一个字节都别动**):
14
+ * inputTokens → inputTokens
15
+ * outputTokens → outputTokens
16
+ * cacheReadTokens → cacheReadInputTokens
17
+ * cacheWriteTokens → cacheCreationInputTokens
18
+ * costMicroUsd → costUSD(/1e6;micro-USD 是 wire 单位,字段名即单位)
19
+ * DROPPED(wire 无此数,填 0 而不是编一个):webSearchRequests / contextWindow / maxOutputTokens。
20
+ *
21
+ * 收编时的**一处语义统一**(REF-CC-062,记在这里以免被读成无意的漂移):cost 的换算此前两份
22
+ * 写法不同 —— 本文件是裸 `/1e6`(非有限值原样传播出 Infinity/NaN),`terminalToSdkResult` 是
23
+ * `microUsdToUsd() ?? 0`(非有限即 0)。统一取后者:Infinity 会被下游当成真数字参与求和,把一条
24
+ * 离谱账静默摊进总计,比 0 更糟(与 SDK `taskCostMicroUsd()` 的规约同义)。
25
+ */
26
+ export function toCcModelUsage(raw) {
2
27
  return {
3
- inputTokens: usage.inputTokens ?? 0,
4
- outputTokens: usage.outputTokens ?? 0,
5
- cacheReadInputTokens: usage.cacheReadTokens ?? 0,
6
- cacheCreationInputTokens: usage.cacheWriteTokens ?? 0,
28
+ inputTokens: finiteOrZero(raw.inputTokens),
29
+ outputTokens: finiteOrZero(raw.outputTokens),
30
+ cacheReadInputTokens: finiteOrZero(raw.cacheReadTokens),
31
+ cacheCreationInputTokens: finiteOrZero(raw.cacheWriteTokens),
7
32
  webSearchRequests: 0, // dropped on the wire
8
- costUSD: usage.costMicroUsd !== undefined ? usage.costMicroUsd / 1_000_000 : 0,
33
+ costUSD: finiteOrZero(raw.costMicroUsd) / 1_000_000,
9
34
  contextWindow: 0, // dropped on the wire → mock-fill (static per-model table)
10
35
  maxOutputTokens: 0, // dropped on the wire → mock-fill
11
36
  };
12
37
  }
38
+ export function turnUsageToModelUsage(usage) {
39
+ return toCcModelUsage(usage);
40
+ }
@@ -2,9 +2,11 @@
2
2
  * ⇄ B3 批搬迁(2026-07-27,多端改造设计稿 §3 B3 / §2.5 末行):cli `src/seam/adapter/runStream.ts`
3
3
  * 整文件搬入。**这个文件是本批的主角** —— 搬迁前它的传递闭包是 **1,571 个文件 / 26 种 Node 内建 /
4
4
  * 412 个 react 文件**(实测,设计稿 §5.2 表),整棵 CC 树都在里面;搬迁后是 5 个文件、零 Node
5
- * 内建、零 react。根因链只有三跳,两条外向边:
6
- * · `:215 await import('../../sema/planReviewWire.js')` → `planReviewWire.ts:26 utils/debug` → 整棵树;
7
- * · `:140 void import('../../sema/lastTurnUsageStore.js')`(闭包本身干净,但同样是壳内向的边)。
5
+ * 内建、零 react。根因链只有三跳,两条外向边(REF-CC-063 同款:原文是两个裸行号锚,已改符号锚):
6
+ * · 终帧前的 plan-review park 臂 `await import('../../sema/planReviewWire.js')` → 该模块的
7
+ * `utils/debug` 值级 import → 整棵树;
8
+ * · turn_end 折叠点的 `void import('../../sema/lastTurnUsageStore.js')`(闭包本身干净,但同样
9
+ * 是壳内向的边)。
8
10
  * 本批把这两条边改成 **chrome 事件**(`plan_review_park` / `last_turn_usage`,见 seam.ts 臂注释),
9
11
  * 由宿主注入 `ctx.emitChrome` 消费 —— 库侧再也不 import 壳。
10
12
  *
@@ -31,6 +33,29 @@
31
33
  import type { AgentEvent } from '@sema-agent/sdk';
32
34
  import { type SDKMessage, type EmitContext, type ModelUsage } from './types.js';
33
35
  import type { EngineTurnUsage } from './downstream/turnUsageToModelUsage.js';
36
+ /** #114/[C65] 单源化:三端共用的 409 active-run 拒收终帧判别(富信号形,包级唯一权威)。
37
+ * 与壳/desktop 各自照抄件同义——0.13.0 到货后各端换包导入删本地抄件。 */
38
+ export interface ActiveRunBusySignal {
39
+ /** 占锁 run 的 id;wire 没带 ⇒ null(诚实缺席,绝不铸造)。 */
40
+ readonly activeTaskId: string | null;
41
+ /** server 3.21+ 真发的占锁 run 状态;缺席 ⇒ null(由调用方走 runs.get 一级)。 */
42
+ readonly activeTaskStatus: string | null;
43
+ /** 409 body 的 pendingGate(wire 给的真路由,不自造);缺席 ⇒ null。 */
44
+ readonly pendingGate: {
45
+ readonly kind: string | null;
46
+ readonly decidePath: string | null;
47
+ } | null;
48
+ }
49
+ /**
50
+ * 判别一个**原始 AgentEvent** 是不是 409 active-run 拒收终帧;非 busy ⇒ null。
51
+ * 判据=结构两腿(canonical `errorCode` 优先 → `activeTaskId` 在场);人话文案腿已随 #117 提货
52
+ * 退役(§B8 禁按错误文案判别)。两种 wire 真形:
53
+ * ① `done{result:{status:'failed', errorCode|activeTaskId,…}}`(交互 tasks/stream 腿,409 骑终帧);
54
+ * ② `failed{errorCode|activeTaskId,…}`(任何把它投影成 failed 臂的上游形)。
55
+ */
56
+ export declare function activeRunBusySignal(ev: unknown): ActiveRunBusySignal | null;
57
+ /** 测试钩:清空「已上报过的臂」去重表(去重是**跨调用**状态,不清就只有第一条用例看得见)。 */
58
+ export declare function _resetDroppedFrameReportForTest(): void;
34
59
  export interface RunStreamHandle {
35
60
  /** The latest folded turn usage (footer counters); updated on each turn_end. */
36
61
  latestUsage?: ModelUsage;
@@ -39,7 +64,7 @@ export interface RunStreamHandle {
39
64
  * `totalInputTokens`)。镜像键求和≠总量(仅 cache 族一致时相等),总量消费面吃这份。
40
65
  * 旧引擎(core <3.0.0)wire 缺形时为 undefined —— 诚实缺席,不造零值。
41
66
  */
42
- latestEngineUsage?: EngineTurnUsage;
67
+ latestEngineUsage?: EngineTurnUsage | undefined;
43
68
  }
44
69
  export declare function isRunStreamActive(): boolean;
45
70
  export declare function runStream(events: AsyncIterable<AgentEvent>, ctx: EmitContext, handle?: RunStreamHandle): AsyncGenerator<SDKMessage>;