@sema-agent/client-core 0.67.0 → 0.67.1
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 +40 -0
- package/README.md +1 -1
- package/dist/adapter/downstream/terminalToSdkResult.d.ts +20 -1
- package/dist/adapter/downstream/terminalToSdkResult.js +73 -10
- package/dist/adapter/runStream.js +51 -6
- package/dist/hitl/parkResolver.js +9 -2
- package/dist/seam.d.ts +10 -0
- package/dist/seam.js +2 -0
- package/docs/INTEGRATION-CLIENTS.md +135 -2
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -49,6 +49,46 @@
|
|
|
49
49
|
> 挡住 ⇒ 本批把它机械化——④a0 对 `pending` 行**要求段头已是日期形**(`(未发布)` 直接红),阶段一
|
|
50
50
|
> commit 漏转在发布前就红,不再靠人记。
|
|
51
51
|
|
|
52
|
+
## 0.67.1(2026-09-12)
|
|
53
|
+
|
|
54
|
+
**test [7055] 对 0.67.0 的 G32-01~30 验证批回帖三处真实发现的修复批**(patch;零公面导出增删、零
|
|
55
|
+
BREAKING;逐项的形 / 判据 / 三端待办见 `docs/INTEGRATION-CLIENTS.md` §32f、§32g 两段「0.67.1 订正」)。
|
|
56
|
+
|
|
57
|
+
- **B-090 `isSubFlow` 的身份键盲区(最高优先级)** —— 子流判据修前只认 `parentToolCallId`,于是一条
|
|
58
|
+
**只带 `sourceTaskId`** 的子代 `turn_end`(§32g 自己承认的合法组合;core `TaskEventIdentity` 上两键
|
|
59
|
+
**同条件**盖章、顶层任务一位都不带)被判成主流:它的 usage 真的驱动了 `last_turn_usage` /
|
|
60
|
+
`turn_usage`(**§E2 断闸要防的 token-jump 原样重现**),同时整条不进 `_sema_nested_usage_by_task`
|
|
61
|
+
(那只子代的账消失)。⇒ 判据改成**身份信封两键任一在场**(按**键在不在**判,空串 / 坏形也算信封在场
|
|
62
|
+
—— 失效方向是安全的那一侧),分表入表条件同批放宽到「行键读得出」。射程边界如实记:那一形上
|
|
63
|
+
chrome 增量臂 `subagent_turn_usage` **不发**(`LaneProof` 的子流臂硬要求 `parentToolCallId`,拿任务 id
|
|
64
|
+
去填是身份位互串),**主臂断闸照断、终帧分表照带那一行**。判据 **G32-23b**(四组合真值表逐格)。
|
|
65
|
+
- **B-091 `run_cost_reconciled` 臂未取并** —— 「两面共用同一个读器」修前只到 `readRunCostFacts` 这一层,
|
|
66
|
+
**取并那一层**(`stats.usageMissing` ∪ 流内观测 `ctx.usageMissingObserved`)只包在终帧那一面上;臂的
|
|
67
|
+
铸点直接展开 `readRunCostFacts(doneStats).reconcile` ⇒ 「流内观测到缺口、终局 stats 缄默」那一形上
|
|
68
|
+
两面各说各的。⇒ 取并**下沉到唯一读器**:`readRunCostFacts(stats, ctx?)` 加 **additive 第二参**
|
|
69
|
+
(不传 = 旧签名逐位不变),两面读**同一次计算**;臂上 `usageLowerBound` 的在场性与终帧
|
|
70
|
+
`_sema_usage_lower_bound` **逐位相等**,两面同律 never false。判据 **G32-21b**。
|
|
71
|
+
- **`__proto__` 行键陷阱(同形族清剿)** —— 以 wire 给的 id / 键名当对象键的表用裸赋值落键时,
|
|
72
|
+
`taskId === "__proto__"` 那一行走的是 `Object.prototype` 上的 accessor ⇒ **不产生自有属性**,该行在
|
|
73
|
+
`Object.keys` / `JSON.stringify` 里整条消失,连 `partial` 的对账量(行数 vs `nested.tasks`)都被带偏。
|
|
74
|
+
⇒ 落键一律 `Object.defineProperty`(与 `hitl/crashConverged.ts` 交付快照同一条处置),**交付形不变**
|
|
75
|
+
(端拿到的仍是普通原型对象)。同批族扫改完的同形点:分表行 / `modelUsage` 的 `modelId` /
|
|
76
|
+
`costBreakdown` 的开集键名 / `hitl/parkResolver.ts` 的 `answers`·`annotations`。判据 **G32-23c**。
|
|
77
|
+
- **行键出身混合 ⇒ `partial` 恒立(异源复审实抓,B-090 的跟车修)** —— 放宽入表条件之后一条流上的
|
|
78
|
+
行键可以有两种出身(真身份 `sourceTaskId` / 有损回落 `parentToolCallId`);混合出身时**一次拆行**
|
|
79
|
+
与**一次合行**的计数误差方向相反、可以恰好抵消,于是「行数 === `nested.tasks` **且** turns 之和
|
|
80
|
+
=== `nested.turns`」两条对账同时成立,一张归属错误的表被标成「可证完整」。⇒ 出身混合时诚实位
|
|
81
|
+
恒立(失效方向仍只会**多**铸 partial)。判据 **G32-25b**。
|
|
82
|
+
|
|
83
|
+
### 已知局限(本版**刻意不动**,已立案)
|
|
84
|
+
|
|
85
|
+
- **子代正文分流仍只认 `parentToolCallId`** —— `src/adapter/runStream.ts` 的 C1 内容分流六臂判据
|
|
86
|
+
未随 B-090 一起认 `sourceTaskId`:一条只带 `sourceTaskId` 的 `text_delta` 会投影成主流
|
|
87
|
+
`stream_event`(`parent_tool_use_id: null`),即子代正文渲成 leader 的正文。**非本版引入**
|
|
88
|
+
(main 上逐字同形),但本版把用量那一半修对之后,同一只子代的正文与用量在这一形上落到了两条
|
|
89
|
+
车道 —— 该不一致由本版显形。两条 honest 修法(身份位互串 / 留痕丢弃)中可取的那条是**行为面
|
|
90
|
+
改动**,按黑盒验收制要单独出判据帖 + 三端表态 ⇒ 立案候裁,详见 §32g 的「已知局限」段。
|
|
91
|
+
|
|
52
92
|
## 0.67.0(2026-09-12)
|
|
53
93
|
|
|
54
94
|
**core 7.14.0 → 7.16.0 提货批**(逐键处置表见 `docs/INTEGRATION-CLIENTS.md` §32z;每件的键名 / 形 /
|
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.67.
|
|
38
|
+
**Version:** 0.67.1
|
|
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
|
|
@@ -237,8 +237,16 @@ export interface RunCostFacts {
|
|
|
237
237
|
*
|
|
238
238
|
* 🔴 `stats` 不是可读对象(409 拒绝信封 / park 体 / `failed` 事件帧)⇒ 返 `undefined` =
|
|
239
239
|
* **这条帧没有账**,调用方据此「不说话」(不发臂、不铸键),而不是发一条全缺席的空账。
|
|
240
|
+
*
|
|
241
|
+
* 🔴 **第二参 `ctx`(0.67.1 / B-091,additive)**:流内观测面。给了它,对账段上的
|
|
242
|
+
* {@link RunCostReconcile.usageLowerBound} 就是**取并后**的读数(见 {@link usageLowerBoundOf});
|
|
243
|
+
* 不给(旧签名)⇒ 只读 `stats` 那一半,既有端逐位不变。
|
|
244
|
+
* ⚠️ 包内的两个调用点(`costFactParts` 与 `runStream` 的 `run_cost_reconciled` 铸点)**都必须**
|
|
245
|
+
* 传它 —— 少传一处就是把本件修的那条不对称原样种回去。
|
|
240
246
|
*/
|
|
241
|
-
export declare function readRunCostFacts(stats: TaskStats | undefined
|
|
247
|
+
export declare function readRunCostFacts(stats: TaskStats | undefined, ctx?: {
|
|
248
|
+
readonly usageMissingObserved?: boolean;
|
|
249
|
+
}): RunCostFacts | undefined;
|
|
242
250
|
/**
|
|
243
251
|
* L-228(0.67.0)—— 一只子任务在**这条流上被看见的**那本账(`_sema_nested_usage_by_task` 的行形)。
|
|
244
252
|
*
|
|
@@ -267,6 +275,17 @@ export interface MutableSubagentUsageRow {
|
|
|
267
275
|
outputTokens: number;
|
|
268
276
|
cacheReadTokens?: number;
|
|
269
277
|
usageMissing?: true;
|
|
278
|
+
/**
|
|
279
|
+
* 0.67.1(异源复审 [medium] 实抓)—— **这一行的键是回落来的**(`parentToolCallId`),不是真身份
|
|
280
|
+
* `sourceTaskId`。**只进 `partial` 的判据,不进交付行**({@link SemaSubagentUsageRow} 上没有这一位:
|
|
281
|
+
* 它是本层的对账中间量,不是一条要过 wire 的事实)。
|
|
282
|
+
* 🔴 为什么非记不可:B-090 放宽入表条件之后,一条流上的行键**可以有两种出身**。混合出身时
|
|
283
|
+
* **同一只**子任务可以同时占两行(一轮走回落键、一轮走真身份),而另外两只子任务又可能并进同一个
|
|
284
|
+
* 回落行 —— 一次**拆行**与一次**合行**的计数误差方向相反,于是「行数 === `nested.tasks` **且**
|
|
285
|
+
* turns 之和 === `nested.turns`」两条对账**同时成立**,一张归属错误的表被标成「可证完整」。
|
|
286
|
+
* ⇒ 出身混合时 {@link SemaNestedUsageByTask.partial} **恒立**(见 `nestedUsageByTaskParts`)。
|
|
287
|
+
*/
|
|
288
|
+
keyFromParentFallback?: true;
|
|
270
289
|
}
|
|
271
290
|
/** 终帧两个超集键的产物形(见 {@link nestedUsageByTaskParts})。 */
|
|
272
291
|
export interface SemaNestedUsageByTask {
|
|
@@ -158,17 +158,62 @@ function permissionDenialParts(stats) {
|
|
|
158
158
|
...(claimable ? {} : { _sema_permission_denials_absent: true }),
|
|
159
159
|
};
|
|
160
160
|
}
|
|
161
|
+
/**
|
|
162
|
+
* 0.67.1 —— **以 wire 给的 id / 键名当对象键**时的唯一落键姿势(`__proto__` 陷阱)。
|
|
163
|
+
*
|
|
164
|
+
* 🔴 `Object.prototype.__proto__` 是一个 **accessor**:在一只普通对象上写 `o["__proto__"] = v`
|
|
165
|
+
* 走的是那只 setter ——**不产生自有属性**(v 是对象时还顺手改了 `o` 的原型),于是那一行在
|
|
166
|
+
* `Object.keys` / `JSON.stringify` 里**整条消失**,连行数都少一。而本文件这几张表的键全都来自
|
|
167
|
+
* wire(taskId / modelId / core 开集的 costBreakdown 键名),没有任何一条保证它们不等于这个字面。
|
|
168
|
+
* ⇒ 落键一律走 `defineProperty`,与本包 `hitl/crashConverged.ts` 交付快照时的处置**同一条**
|
|
169
|
+
* (那里逐字:「落键仍走 `defineProperty`(`__proto__` 同理)」)。
|
|
170
|
+
*
|
|
171
|
+
* 🔴 **不改成 null 原型对象交付**:端拿到的仍是一只正常对象(`hasOwnProperty` / `toString` 都在),
|
|
172
|
+
* 本修只改「落键」这一步,不改交付形 —— 换原型会在宿主侧造出一类新的 `TypeError`。
|
|
173
|
+
* 描述符与普通赋值**逐位相同**(`writable/enumerable/configurable` 三真),所以除了 `__proto__`
|
|
174
|
+
* 这一个字面,其余每一个键的行为一个字节都没变。
|
|
175
|
+
*/
|
|
176
|
+
function putOwn(table, key, value) {
|
|
177
|
+
Object.defineProperty(table, key, { value, enumerable: true, writable: true, configurable: true });
|
|
178
|
+
}
|
|
161
179
|
/** 有限数窄化(非数 / 非有限 ⇒ 缺席;`0` 是事实不是缺席)。 */
|
|
162
180
|
function finiteOrAbsent(v) {
|
|
163
181
|
return typeof v === 'number' && Number.isFinite(v) ? v : undefined;
|
|
164
182
|
}
|
|
183
|
+
/**
|
|
184
|
+
* 0.67.1 / B-091 —— usage **下界位**的**唯一判据**(纯函数,两面读的是**同一次计算**)。
|
|
185
|
+
*
|
|
186
|
+
* 🔴 它为什么必须是一只函数、而不是两处各写一遍的表达式:下界位有**两个来源**——
|
|
187
|
+
* · `stats.usageMissing`:只在带得出 `TaskResult` 的终帧上有;
|
|
188
|
+
* · `ctx.usageMissingObserved`:流内观测(`failed` 事件帧 / 409 拒绝信封 / park 体**没有 stats**,
|
|
189
|
+
* 只读 stats 的话一条已经观测到缺口的 run 会在终帧上被读成「每一轮都报了 usage」)。
|
|
190
|
+
* 修前取并只包在**终帧**那一面({@link costFactParts}),而 chrome 对账臂的铸点直接展开
|
|
191
|
+
* `readRunCostFacts(stats).reconcile` ⇒ 「流内观测到缺口、终局 stats 对此缄默」那一形上两面各说
|
|
192
|
+
* 各的(终帧铸了 `_sema_usage_lower_bound`、同一拍的臂上没有 `usageLowerBound`)——
|
|
193
|
+
* [paired-mechanisms-must-share-premise] 的教科书形。⇒ 取并**下沉到这里**,两面共用。
|
|
194
|
+
*
|
|
195
|
+
* 🔴 **严格 true 才认**(core 契约:`true` 或缺席,恒不写 `false`/`null`);`stats` 不是可读对象时
|
|
196
|
+
* 它那一半读作「没说」,而流内观测那一半**照旧成立**。
|
|
197
|
+
*/
|
|
198
|
+
function usageLowerBoundOf(stats, ctx) {
|
|
199
|
+
const statsSaid = stats !== null && typeof stats === 'object' && !Array.isArray(stats)
|
|
200
|
+
? stats.usageMissing === true
|
|
201
|
+
: false;
|
|
202
|
+
return statsSaid || ctx?.usageMissingObserved === true;
|
|
203
|
+
}
|
|
165
204
|
/**
|
|
166
205
|
* D-3 / B-068 · L-198 —— 终局成本事实的**唯一读器**(终帧超集键与 chrome 对账臂共用)。
|
|
167
206
|
*
|
|
168
207
|
* 🔴 `stats` 不是可读对象(409 拒绝信封 / park 体 / `failed` 事件帧)⇒ 返 `undefined` =
|
|
169
208
|
* **这条帧没有账**,调用方据此「不说话」(不发臂、不铸键),而不是发一条全缺席的空账。
|
|
209
|
+
*
|
|
210
|
+
* 🔴 **第二参 `ctx`(0.67.1 / B-091,additive)**:流内观测面。给了它,对账段上的
|
|
211
|
+
* {@link RunCostReconcile.usageLowerBound} 就是**取并后**的读数(见 {@link usageLowerBoundOf});
|
|
212
|
+
* 不给(旧签名)⇒ 只读 `stats` 那一半,既有端逐位不变。
|
|
213
|
+
* ⚠️ 包内的两个调用点(`costFactParts` 与 `runStream` 的 `run_cost_reconciled` 铸点)**都必须**
|
|
214
|
+
* 传它 —— 少传一处就是把本件修的那条不对称原样种回去。
|
|
170
215
|
*/
|
|
171
|
-
export function readRunCostFacts(stats) {
|
|
216
|
+
export function readRunCostFacts(stats, ctx) {
|
|
172
217
|
// 数组也不是「一份账」:`typeof [] === 'object'` 会把一条畸形载体放进来,然后它的每一格都读不出
|
|
173
218
|
// ⇒ 发出一条「own 没定价」的臂,而真相是**这条帧根本没有账**(两句话又折成一句)。
|
|
174
219
|
if (stats === null || typeof stats !== 'object' || Array.isArray(stats))
|
|
@@ -181,7 +226,7 @@ export function readRunCostFacts(stats) {
|
|
|
181
226
|
for (const [k, v] of Object.entries(rawBreakdown)) {
|
|
182
227
|
const n = finiteOrAbsent(v);
|
|
183
228
|
if (n !== undefined)
|
|
184
|
-
out
|
|
229
|
+
putOwn(out, k, n);
|
|
185
230
|
}
|
|
186
231
|
if (Object.keys(out).length > 0)
|
|
187
232
|
breakdown = out;
|
|
@@ -215,7 +260,7 @@ export function readRunCostFacts(stats) {
|
|
|
215
260
|
// 0.67.0(core 7.14.0):usage 下界位。**严格 true 才铸**(core 契约:`true` 或缺席,恒不写
|
|
216
261
|
// `false`/`null`;认宽了就会把一个 falsy 值渲成「数得不全」)。它与成本三段正交,所以读在这里、
|
|
217
262
|
// 与三段同一只读器出 —— 两面(终帧超集键 / chrome 对账臂)因此永远不会各算各的。
|
|
218
|
-
const usageLowerBound = stats
|
|
263
|
+
const usageLowerBound = usageLowerBoundOf(stats, ctx);
|
|
219
264
|
const reconcile = {
|
|
220
265
|
...(ownMicroUsd !== undefined ? { ownMicroUsd } : { costAbsent: true }),
|
|
221
266
|
...(usageLowerBound ? { usageLowerBound: true } : {}),
|
|
@@ -239,13 +284,16 @@ export function readRunCostFacts(stats) {
|
|
|
239
284
|
* 这两个超集键自己加 —— 包给的是**可对账的事实**,不是一个改了口径的数。
|
|
240
285
|
*/
|
|
241
286
|
function costFactParts(stats, ctx) {
|
|
242
|
-
const facts = readRunCostFacts(stats);
|
|
287
|
+
const facts = readRunCostFacts(stats, ctx);
|
|
243
288
|
// 🔴 **下界位与成本三段分开算**(异源对抗复审 [medium] 实抓):`stats` 读不出(`failed` 事件帧 /
|
|
244
289
|
// 409 拒绝信封 / park 体)时**成本**那三段确实没有账、一条都不该说;但「这条流观测到过一轮
|
|
245
290
|
// 没有 usage」这件事**照旧成立**,而那种终帧的 `usage` 恰恰是 `flattenUsage(undefined)` 的
|
|
246
291
|
// 全零 —— 早退回空对象等于把「不知道」渲成精确零(正是本位要修的病的另一条路径)。
|
|
247
292
|
// ⇒ 两者**取并**:stats 说了算一半,流内观测算另一半。
|
|
248
|
-
|
|
293
|
+
// 🔴 0.67.1 / B-091:取并本身已经**下沉**到 {@link usageLowerBoundOf} —— 这里与读器内部、与
|
|
294
|
+
// chrome 对账臂读的是**同一只函数**,三面不会各算各的(`facts === undefined` 时读器整只不
|
|
295
|
+
// 返回,所以这一行必须自己再调一次那只判据,而不是回头读 `facts`)。
|
|
296
|
+
const lowerBound = usageLowerBoundOf(stats, ctx);
|
|
249
297
|
const lowerBoundPart = lowerBound ? { _sema_usage_lower_bound: true } : {};
|
|
250
298
|
if (facts === undefined)
|
|
251
299
|
return lowerBoundPart;
|
|
@@ -292,22 +340,36 @@ function nestedUsageByTaskParts(stats, rollup) {
|
|
|
292
340
|
return {};
|
|
293
341
|
const rows = {};
|
|
294
342
|
let turnsSeen = 0;
|
|
343
|
+
// 🔴 0.67.1:行键的**出身**统计(见 {@link MutableSubagentUsageRow.keyFromParentFallback})。
|
|
344
|
+
// 出身按行是均匀的 —— 一行的键要么恒是 `sourceTaskId`、要么恒是回落的父调用 id
|
|
345
|
+
// (`taskId = sourceTaskId ?? parent`:真身份在场时永远不会落到回落键那一行上)。
|
|
346
|
+
let fallbackKeyedRows = 0;
|
|
347
|
+
let sourceKeyedRows = 0;
|
|
295
348
|
for (const [taskId, r] of rollup) {
|
|
296
349
|
turnsSeen += r.turns;
|
|
297
|
-
|
|
350
|
+
if (r.keyFromParentFallback === true)
|
|
351
|
+
fallbackKeyedRows += 1;
|
|
352
|
+
else
|
|
353
|
+
sourceKeyedRows += 1;
|
|
354
|
+
putOwn(rows, taskId, {
|
|
298
355
|
turns: r.turns,
|
|
299
356
|
inputTokens: r.inputTokens,
|
|
300
357
|
outputTokens: r.outputTokens,
|
|
301
358
|
...(r.cacheReadTokens !== undefined ? { cacheReadTokens: r.cacheReadTokens } : {}),
|
|
302
359
|
...(r.usageMissing === true ? { usageMissing: true } : {}),
|
|
303
|
-
};
|
|
360
|
+
});
|
|
304
361
|
}
|
|
305
362
|
const nested = stats !== null && typeof stats === 'object' && !Array.isArray(stats)
|
|
306
363
|
? stats.nested
|
|
307
364
|
: undefined;
|
|
308
365
|
const authTurns = finiteOrAbsent(nested?.turns);
|
|
309
366
|
const authTasks = finiteOrAbsent(nested?.tasks);
|
|
310
|
-
|
|
367
|
+
// 🔴 0.67.1:**出身混合 ⇒ 证不出完整**。计数相等只说明「数目对得上」,说不了「逐任务归属对得上」
|
|
368
|
+
// —— 拆一行与并一行的误差方向相反、可以恰好抵消(见 `keyFromParentFallback` 头注的反例)。
|
|
369
|
+
// 出身一致时既有两条对账才是充分的:全真身份 ⇒ 一行一任务;全回落 ⇒ 多任务共父会让行数 <
|
|
370
|
+
// `nested.tasks`,那一格自己会翻。失效方向仍是安全的那一侧(只会**多**铸 partial)。
|
|
371
|
+
const mixedKeyOrigin = fallbackKeyedRows > 0 && sourceKeyedRows > 0;
|
|
372
|
+
const complete = !mixedKeyOrigin && authTurns !== undefined && authTasks !== undefined &&
|
|
311
373
|
authTurns === turnsSeen && authTasks === Object.keys(rows).length;
|
|
312
374
|
return {
|
|
313
375
|
_sema_nested_usage_by_task: rows,
|
|
@@ -387,7 +449,8 @@ function mapModelUsage(stats) {
|
|
|
387
449
|
if (u === undefined || u === null || typeof u !== 'object')
|
|
388
450
|
continue;
|
|
389
451
|
const m = u;
|
|
390
|
-
|
|
452
|
+
// 0.67.1 同形族:`modelId` 也是 wire 给的键 ⇒ 落键走 {@link putOwn}。
|
|
453
|
+
putOwn(out, modelId, withTerminalUsageFacts(toCcModelUsage({
|
|
391
454
|
inputTokens: numOrAbsent(m.inputTokens),
|
|
392
455
|
outputTokens: numOrAbsent(m.outputTokens),
|
|
393
456
|
cacheReadTokens: numOrAbsent(m.cacheReadTokens),
|
|
@@ -395,7 +458,7 @@ function mapModelUsage(stats) {
|
|
|
395
458
|
costMicroUsd: numOrAbsent(m.costMicroUsd),
|
|
396
459
|
// D-2(0.66.0):per-model 行今天也不带这一格 ⇒ 宽读一次,读不出由 mint 点立判别位。
|
|
397
460
|
webSearchRequests: numOrAbsent(m.webSearchRequests),
|
|
398
|
-
}), m);
|
|
461
|
+
}), m));
|
|
399
462
|
}
|
|
400
463
|
return out;
|
|
401
464
|
}
|
|
@@ -400,10 +400,26 @@ async function* runStreamInner(events, ctx, handle = {}) {
|
|
|
400
400
|
const stopWord = typeof stopReasonRaw === 'string' && stopReasonRaw.length > 0 ? stopReasonRaw : undefined;
|
|
401
401
|
const usage = turnEndUsage(ev);
|
|
402
402
|
// §E2 identity (service 1.78) — a SUB-FLOW's turn_end (orchestration/subagent round, carries
|
|
403
|
-
//
|
|
404
|
-
// child's, and reconciling the leader's responseLength against them is the token-jump bug
|
|
403
|
+
// the identity envelope) must NOT drive the leader's C1a `end` reconcile: its outputTokens are
|
|
404
|
+
// the child's, and reconciling the leader's responseLength against them is the token-jump bug
|
|
405
405
|
// (blackboard dogfood #2 / upstreamBridge TODO #9). Fold its usage into the rollup only.
|
|
406
|
-
|
|
406
|
+
// ── 🔴 0.67.1 / B-090(下游验收轨实抓)—— **身份键盲区** ────────────────────────────────
|
|
407
|
+
// 修前这条判据只认 `parentToolCallId`,而 §32g「归属钥匙」段自己写的读法是「`sourceTaskId`
|
|
408
|
+
// 优先,wire 缺席时回落 `parentToolCallId`」—— 那句话隐含「只带 `sourceTaskId`」是一条合法
|
|
409
|
+
// 组合。于是那一形的子代轮被判成**主流**,后果双重且都是本件当初要防的病:
|
|
410
|
+
// ① `!isSubFlow` 为真 ⇒ 它的 usage 去驱动 `last_turn_usage` chrome 臂与 `turn_usage` 消息臂
|
|
411
|
+
// —— §E2 断闸要防的 token-jump 在这条路上原样重现(子代的窗口占用落进主会话 Context);
|
|
412
|
+
// ② `isSubFlow` 为假 ⇒ 整条不进分表(下面那条 `if (isSubFlow)` 腿)—— 那只子代的账消失。
|
|
413
|
+
// 🔴 判据锚在 core 真字节上(`task-event.d.ts` `TaskEventIdentity`,不是本包的口味):
|
|
414
|
+
// `sourceTaskId` 逐字「stamped ONLY on the content events of a task running AS A SUB-AGENT
|
|
415
|
+
// (same condition as `parentToolCallId`)」,且「A TOP-LEVEL task's own events carry NO
|
|
416
|
+
// `sourceTaskId`」⇒ **两键同条件盖章、顶层一个都不带** ⇒ 任一在场 = 这条帧属于子流。
|
|
417
|
+
// 🔴 判据是**键在不在**(`!== undefined`),不是「是不是非空串」:空串 / 坏形也算信封在场,
|
|
418
|
+
// 于是按子流走 —— 失效方向是安全的那一侧(宁可少发一拍主会话的 usage,也绝不把子代的账
|
|
419
|
+
// 混进主会话;反过来那一侧就是 token-jump)。**行键**那一层才做非空串窄化(见下面的
|
|
420
|
+
// `parent` / `sourceTaskId`),两层问的不是同一个问题:信封在不在 vs 这一行归到谁名下。
|
|
421
|
+
const isSubFlow = ev.parentToolCallId !== undefined ||
|
|
422
|
+
ev.sourceTaskId !== undefined;
|
|
407
423
|
if (usage) {
|
|
408
424
|
handle.latestUsage = usage;
|
|
409
425
|
// [2295] 裁 ② 逐字通道:与镜像同拍存一份引擎原形(六键含 totalInputTokens)。
|
|
@@ -460,12 +476,27 @@ async function* runStreamInner(events, ctx, handle = {}) {
|
|
|
460
476
|
// ⚠️ 回落**有损**:同父调用多子任务会并成一行 —— 那时终帧那张表的 `partial` 判别位会因
|
|
461
477
|
// 行数对不上 `nested.tasks` 而立起来(诚实缺席优先于假装分得开)。
|
|
462
478
|
const taskId = sourceTaskId ?? parent;
|
|
463
|
-
|
|
479
|
+
// 🔴 0.67.1 / B-090:入表条件从「行键 **且** 父调用 id 都读得出」放宽到「**行键**读得出」。
|
|
480
|
+
// 修前那个 `&& parent !== undefined` 把「只带 `sourceTaskId`」的子代整条挡在表外 ——
|
|
481
|
+
// 而 `parent` 在这里的唯一用处是**铸 chrome 增量臂的车道证明**,不是行的身份。
|
|
482
|
+
// 行键读不出(两键都缺 / 都是空串)仍然整条不入表:编一个 `"unknown"` 行会把几只子代
|
|
483
|
+
// 的账混成一只(C3 那一格守的就是这条)。
|
|
484
|
+
if (taskId !== undefined) {
|
|
464
485
|
// 🔴 发臂条件与主臂 0.65.1 / B-088 **逐字同族**:core 真会发**裸**
|
|
465
486
|
// `{type:'turn_end', usageMissing:true}`(无 usage、无 stopReason),旧条件「有 usage 才发」
|
|
466
487
|
// 会让**最诚实的那一帧**整条静默 —— 那是本仓已定谳的病形,子代这条腿不许再犯一次。
|
|
467
488
|
// 三者任一在场即发;三者皆缺席仍不发。
|
|
468
|
-
|
|
489
|
+
// 🔴 **`parent` 缺席时本臂不发,而行照进表**(0.67.1 定谳,理由如实写在这里):
|
|
490
|
+
// chrome 信封的车道证明 `LaneProof` 的子流臂是 `{lane:'subagent', parentToolCallId: string}`
|
|
491
|
+
// (`seam.ts`),座位门 `seatContract.checkLaneProof` 对它是**硬要求**(缺伴随位的信封
|
|
492
|
+
// 当场被吃掉,且 `run-client-core-pure-test.mjs` FIX7 ④ 有专门的投毒格守着)。这一形上
|
|
493
|
+
// **没有**父调用 id ⇒ 唯一能把臂发出去的办法是拿 `sourceTaskId` 去填那一格,而那是在
|
|
494
|
+
// 一个「父调用 id」的位上放一个**任务 id** —— 正是 C4/C5 两格守的身份位互串,也是
|
|
495
|
+
// 车道纪律(REF-CC-064)存在的理由。放宽 `LaneProof` 则是**三端 BREAKING**(宿主今天
|
|
496
|
+
// 按 `parentToolCallId: string` 读),不是一个 patch 能做的事。
|
|
497
|
+
// ⇒ 增量腿在这一形上静默,**收口快照(终帧 `_sema_nested_usage_by_task`)照带这一行**
|
|
498
|
+
// —— 账不丢,少的只是这一形的实时增量;两者本来就是「同一份账的两个时刻」。
|
|
499
|
+
if (parent !== undefined && (usage !== undefined || usageMissing || stopWord !== undefined) && ctx.emitChrome) {
|
|
469
500
|
emitChromeFireAndForget(ctx, {
|
|
470
501
|
kind: 'subagent_turn_usage',
|
|
471
502
|
laneProof: { lane: 'subagent', parentToolCallId: parent },
|
|
@@ -479,6 +510,14 @@ async function* runStreamInner(events, ctx, handle = {}) {
|
|
|
479
510
|
});
|
|
480
511
|
}
|
|
481
512
|
const row = nestedUsageByTask.get(taskId) ?? { turns: 0, inputTokens: 0, outputTokens: 0 };
|
|
513
|
+
// 🔴 0.67.1(异源复审 [medium] 实抓):记下**这一行的键是回落来的**(真身份缺席)。
|
|
514
|
+
// B-090 放宽入表条件之后行键可以有两种出身,而混合出身时「拆一行 + 并一行」的计数
|
|
515
|
+
// 误差方向相反、可以恰好抵消 ⇒ 终帧那张表的 `partial` 判据要看得见出身
|
|
516
|
+
// (理由与反例逐字在 `MutableSubagentUsageRow.keyFromParentFallback` 的头注里)。
|
|
517
|
+
// 出身按行均匀:`taskId = sourceTaskId ?? parent` ⇒ 真身份在场的轮永远不会落到
|
|
518
|
+
// 回落键那一行上,所以这一位只会被同一种出身的轮反复写成同一个值。
|
|
519
|
+
if (sourceTaskId === undefined)
|
|
520
|
+
row.keyFromParentFallback = true;
|
|
482
521
|
row.turns += 1;
|
|
483
522
|
row.inputTokens += usage?.inputTokens ?? 0;
|
|
484
523
|
row.outputTokens += usage?.outputTokens ?? 0;
|
|
@@ -654,12 +693,18 @@ async function* runStreamInner(events, ctx, handle = {}) {
|
|
|
654
693
|
}
|
|
655
694
|
// D-3 / B-068 · L-198(0.66.0):**终局对账那一拍**。与终帧超集键
|
|
656
695
|
// (`_sema_cost_breakdown` / `_sema_nested_usage`)同一个读器 `readRunCostFacts` ⇒ 两面同源。
|
|
696
|
+
// 🔴 0.67.1 / B-091:「同源」此前只到**读器**这一层,取并那一层(下界位的两个来源)还留在
|
|
697
|
+
// 终帧那一面上 ⇒ 两面在「流内观测到缺口、终局 stats 缄默」这一形上各说各的。现在取并
|
|
698
|
+
// 在读器里面,两面读的是**同一次计算**(第二参就是那一半来源)。
|
|
657
699
|
// 🔴 没有账就不说话:`stats` 读不出(409 拒绝信封 / park 体 / `failed` 事件帧)⇒ 不发。
|
|
658
700
|
// 🔴 子流断闸(§E2)一字不动:子代花费只经本臂的 `nestedMicroUsd`(终局 stats.nested)到账。
|
|
659
701
|
// fail-soft 同 plan_review_park:sink 抛错不影响终帧照常投影。
|
|
660
702
|
if (ev.type === 'done' && ctx.emitChrome) {
|
|
661
703
|
const doneStats = ev.result?.stats;
|
|
662
|
-
|
|
704
|
+
// 🔴 0.67.1 / B-091:**第二参必须传** —— 下界位的取并(`stats.usageMissing` ∪ 流内观测
|
|
705
|
+
// `ctx.usageMissingObserved`)已经下沉进读器;不传就是把「臂绕开取并」那条不对称原样
|
|
706
|
+
// 种回去(终帧铸了 `_sema_usage_lower_bound`、同一拍的臂上却没有 `usageLowerBound`)。
|
|
707
|
+
const costFacts = readRunCostFacts(doneStats, ctx);
|
|
663
708
|
if (costFacts !== undefined) {
|
|
664
709
|
emitChromeFireAndForget(ctx, { kind: 'run_cost_reconciled', laneProof: MAIN, ...costFacts.reconcile });
|
|
665
710
|
}
|
|
@@ -146,9 +146,16 @@ export function toAnsweredOutput(questions, answer) {
|
|
|
146
146
|
for (const entry of answer.answers ?? []) {
|
|
147
147
|
const q = questions.find(qq => qq.header === entry.header);
|
|
148
148
|
const key = typeof q?.question === 'string' ? q.question : entry.header;
|
|
149
|
-
|
|
149
|
+
// 🔴 0.67.1 同形族(与 `adapter/downstream/terminalToSdkResult.ts` 的 `putOwn` 同一条):
|
|
150
|
+
// `key` 来自 wire(问题正文 / header),字面等于 `"__proto__"` 时裸赋值走的是
|
|
151
|
+
// `Object.prototype` 上那只 **accessor** —— `answers` 那一格(串值)整条静默丢失,
|
|
152
|
+
// `annotations` 那一格(对象值)还顺手改了表自己的原型。落键一律 `defineProperty`。
|
|
153
|
+
const put = (t, v) => {
|
|
154
|
+
Object.defineProperty(t, key, { value: v, enumerable: true, writable: true, configurable: true });
|
|
155
|
+
};
|
|
156
|
+
put(answers, (entry.selected ?? []).join(', '));
|
|
150
157
|
if (entry.note)
|
|
151
|
-
annotations
|
|
158
|
+
put(annotations, { notes: entry.note });
|
|
152
159
|
}
|
|
153
160
|
return {
|
|
154
161
|
type: 'ask-user-question',
|
package/dist/seam.d.ts
CHANGED
|
@@ -414,6 +414,16 @@ export type ChromeEvent = {
|
|
|
414
414
|
* 至少归得到同一行)。`usage` / `engineUsage` / `usageMissing` / `stopReason` 与
|
|
415
415
|
* {@link ChromeEvent} 的 `last_turn_usage` 臂**逐位同义**(同一只 `turnEndUsage()` 产物,不另铸)。
|
|
416
416
|
*
|
|
417
|
+
* ── 🔴 0.67.1 / B-090:本臂的**射程边界**(如实记,不是遗漏)──────────────────────────────
|
|
418
|
+
* 子流判据(`isSubFlow`)0.67.1 起认**身份信封两键任一在场**(`parentToolCallId` ∪ `sourceTaskId`;
|
|
419
|
+
* core 合同上两键同条件盖章、顶层任务一个都不带)。而**本臂**还多一个前提:车道证明
|
|
420
|
+
* {@link LaneProof} 的子流臂是 `{lane:'subagent', parentToolCallId: string}` ⇒ 一条**只带
|
|
421
|
+
* `sourceTaskId`、没有父调用 id** 的子代轮**发不出本臂**(拿任务 id 去填父调用 id 那一格是身份位
|
|
422
|
+
* 互串,座位门 `checkLaneProof` 也会把缺伴随位的信封吃掉)。
|
|
423
|
+
* ⇒ 那一形上:主臂两处断闸**照断**(不污染主会话用量)、终帧分表
|
|
424
|
+
* `_sema_nested_usage_by_task` **照带那一行**,只有本臂这条**实时增量**腿静默。
|
|
425
|
+
* 🔴 宿主据此:**不许**把「本臂没来过」读成「这只子代没花钱」——收口快照才是那本账的全量面。
|
|
426
|
+
*
|
|
417
427
|
* 宿主消费义务(`required: false`):渲子代详情的 per-agent 用量。不接 = 那一面看不见(不是报错);
|
|
418
428
|
* 🔴 **绝不**把本臂的数加进主会话的 turn 用量 —— 两条腿分工见上;
|
|
419
429
|
* 🔴 `usageMissing` 在场 ⇒ 同行那份 usage 是**下界**,别当已知账落槽。
|
package/dist/seam.js
CHANGED
|
@@ -41,6 +41,8 @@ const CHROME_ARM_TABLE = {
|
|
|
41
41
|
required: false,
|
|
42
42
|
duty: '可选:按 `taskId`(= `sourceTaskId ?? parentToolCallId`,与终帧分表同键)落 per-subagent 的 turn 用量(子代详情面)。' +
|
|
43
43
|
'🔴 `usage` 可缺席(裸 `usageMissing` 轮),那一形要渲「这一轮的账不知道」而不是跳过;' +
|
|
44
|
+
'🔴 0.67.1:只带 `sourceTaskId`(无父调用 id)的子代轮**发不出本臂**(车道证明铸不出来)——' +
|
|
45
|
+
'那一形的账只在终帧 `_sema_nested_usage_by_task` 上,**不许**把「本臂没来过」读成「这只子代没花钱」;' +
|
|
44
46
|
'🔴 **绝不**把它加进主会话的 turn 用量 / statusline 窗口占用(那是 §E2 断闸要防的 token-jump);' +
|
|
45
47
|
'🔴 `usageMissing` 在场时同行 usage 是**下界**,渲「≥」不渲已知账;' +
|
|
46
48
|
'🔴 终帧的 `_sema_nested_usage_by_task` 是同一份账的**收口快照**,两者**不相加**(本臂是增量、那是合计)',
|
|
@@ -19,7 +19,7 @@
|
|
|
19
19
|
|
|
20
20
|
| 项 | 值 | 真源 |
|
|
21
21
|
|---|---|---|
|
|
22
|
-
| 本包 | `@sema-agent/client-core` **0.67.
|
|
22
|
+
| 本包 | `@sema-agent/client-core` **0.67.1**(本批发布版 = patch:内容批 a5ea52c,test [7055] 三修 B-090 isSubFlow 两键任一即子流 / B-091 对账臂取并下沉 / __proto__ 行键 putOwn + 出身混合 partial 恒立,零公面增删零 BREAKING,详见 §32f/§32g 0.67.1 订正段;`CHANGELOG.md` `## 0.67.1(2026-09-12)`;0.67.0 minor = 内容批 df6b2dc,core 7.14.0→7.16.0 提货七件 F-1…F-7,🔴 七条 BREAKING(分类器卡面族 clean-cut、`unresolvable`→`ancestor_marked`、audience 换档、`classifierStatusOf` 第二参语义),详见 §32a–§32z;0.66.0 见 §31;`CHANGELOG.md` `## 0.67.0(2026-09-12)`;bump 与冻结账两阶段由发包批做) | `package.json` `version` |
|
|
23
23
|
| peer:wire 契约 | `@sema-agent/sdk` **>=8.4.0**(value-level,非 type-only;**0.60.0 抬版**,四条硬理由见 §24a 与 `scripts/run-sdk-floor-test.mjs` 的 `FLOOR` 注;上一次是 0.59.0 的 `>=8.3.0`)。🔴 支持窗同批收到 **engine ≥7.64.0**:sdk 8.4.0 与 7.63.0 及以前的 wire **不同窗** | `package.json` `peerDependencies` |
|
|
24
24
|
| peer:会话词汇表 | `@sema-agent/agent-types` **>=0.2.0**(type-only,零运行时) | 同上 |
|
|
25
25
|
| runtime dep | `diff` ^9.0.0(**唯一**一条;portability 门按**等值**钉死) | `package.json` `dependencies` |
|
|
@@ -6544,6 +6544,35 @@ prepare 期拒绝 / 合成 abort)。**数字位仍是必填、仍是数到的那
|
|
|
6544
6544
|
- **G32-20**:`usageMissing` 为 `false` / `null` / `0` / `"true"` / `1` ⇒ 键**不铸**(严格 true 才认)。
|
|
6545
6545
|
- **G32-21**:缺席时终帧上**没有**这个键(never false),chrome 臂上同律。
|
|
6546
6546
|
|
|
6547
|
+
#### 🔧 0.67.1 订正(B-091,test [7055] 实抓):取并**下沉到唯一读器**
|
|
6548
|
+
|
|
6549
|
+
修前「两面共用同一个读器」这句话只对了一半 —— 两面共用的是 `readRunCostFacts`,而**取并那一层**
|
|
6550
|
+
(`stats.usageMissing` ∪ 流内观测)只包在**终帧**那一面上;chrome 对账臂的铸点直接展开
|
|
6551
|
+
`readRunCostFacts(doneStats).reconcile`。于是「流内观测到缺口、而终局 `stats` 对此**缄默**」的那一形上,
|
|
6552
|
+
终帧 `_sema_usage_lower_bound === true` 而**同一拍**的 `run_cost_reconciled` 臂上**没有** `usageLowerBound`
|
|
6553
|
+
—— 两面各说各的([paired-mechanisms-must-share-premise] 的教科书形;按 G32-21 的字面这条技术上仍 PASS,
|
|
6554
|
+
因为两边都没铸 `false`,所以判据这次要往前钉一格)。
|
|
6555
|
+
|
|
6556
|
+
**修形**:取并变成一只纯判据函数,**读器内部**与终帧那一面共用它;公面读器同批加**第二参**
|
|
6557
|
+
`readRunCostFacts(stats, ctx?)`(additive —— 不传 = 旧签名行为逐位不变,端自建管线可以传自己的流内观测)。
|
|
6558
|
+
|
|
6559
|
+
| 面 | 0.67.0 | 0.67.1 |
|
|
6560
|
+
|---|---|---|
|
|
6561
|
+
| 终帧 `_sema_usage_lower_bound` | `stats.usageMissing` ∪ 流内观测 | 不变(同一只判据) |
|
|
6562
|
+
| chrome 臂 `usageLowerBound` | **只**读 `stats.usageMissing` | `stats.usageMissing` ∪ 流内观测 |
|
|
6563
|
+
| `readRunCostFacts(stats)` | 只读 `stats` | 不变(第二参缺席 ⇒ 只读 `stats`) |
|
|
6564
|
+
| `readRunCostFacts(stats, ctx)` | —(签名不存在) | 🆕 取并后的读数 |
|
|
6565
|
+
|
|
6566
|
+
**消费方待办**:无。这一位在臂上**只会从「缺席」变成「在场」**(never false 不变),既有端读不到它时的
|
|
6567
|
+
渲染路径一个字节不用改;接臂的端从此在「流内缺口 + 终局缄默」那一形上也能渲「≥」。
|
|
6568
|
+
|
|
6569
|
+
**黑盒判据(新增,不改既有号)**
|
|
6570
|
+
|
|
6571
|
+
- **G32-21b**:一条流里先来 `turn_end{usageMissing:true}`、`done.result.stats` 上**没有** `usageMissing`
|
|
6572
|
+
⇒ 终帧 `_sema_usage_lower_bound === true` **且**同一拍 `run_cost_reconciled` 臂上
|
|
6573
|
+
`usageLowerBound === true`;两面的**在场性逐位相等**。反向:同一条路上没观测到缺口 ⇒ 两面**都**键缺席
|
|
6574
|
+
(never false)。子流那一轮报的缺口**同样算数**(下界是这条流的性质,不分车道)。
|
|
6575
|
+
|
|
6547
6576
|
---
|
|
6548
6577
|
|
|
6549
6578
|
### 32g. F-6(L-228)per-subagent usage 分表(**ADDITIVE**:新 chrome 臂 + 两个终帧超集键)
|
|
@@ -6578,7 +6607,18 @@ prepare 期拒绝 / 合成 abort)。**数字位仍是必填、仍是数到的那
|
|
|
6578
6607
|
⇒ 本件的承诺是**包边界的**:子流 `turn_end` 到得了这条流,分表就有行;它到不了,两个键就都不在场。
|
|
6579
6608
|
端**不许**把「键不在场」渲成「这条 run 没委派子代」。
|
|
6580
6609
|
|
|
6581
|
-
|
|
6610
|
+
**归属钥匙(0.67.1 订正为真值表 —— 修前这一段的散文读法与实装不一致,见下面的 B-090 段)**:
|
|
6611
|
+
身份信封有**两位**(core `TaskEventIdentity`),**两位同条件盖章**、顶层任务的事件**一位都不带** ⇒
|
|
6612
|
+
**任一在场 = 这条帧属于子流**;行键则是 `sourceTaskId ?? parentToolCallId`。
|
|
6613
|
+
|
|
6614
|
+
| `parentToolCallId` | `sourceTaskId` | 是不是子流(§E2 断闸) | 分表行键 | chrome 增量臂 |
|
|
6615
|
+
|---|---|---|---|---|
|
|
6616
|
+
| 在场 | 在场 | ✅ 子流 | `sourceTaskId` | 发 |
|
|
6617
|
+
| 在场 | 缺席 | ✅ 子流 | `parentToolCallId`(**有损回落**,见下) | 发 |
|
|
6618
|
+
| 缺席 | 在场 | ✅ 子流 | `sourceTaskId` | **不发**(车道证明铸不出来,见 0.67.1 订正段) |
|
|
6619
|
+
| 缺席 | 缺席 | ❌ 主流 | —(不入表) | 不发 |
|
|
6620
|
+
|
|
6621
|
+
两位都在场但都读不出身份(空串 / 非串)⇒ 仍按**子流**断闸(失效方向是安全的那一侧),但**整条不入表**
|
|
6582
6622
|
(编一个 `"unknown"` 行会把几只子代的账混成一只)。
|
|
6583
6623
|
🔴 **身份位是 `sourceTaskId` 不是 `taskId`**(异源对抗复审 [high] 实抓):core 的 `TaskEventIdentity`
|
|
6584
6624
|
顶注逐字「the `WorkflowRun.sourceTaskId` family, **NOT a second `taskId`** — that field already exists on
|
|
@@ -6645,6 +6685,99 @@ core 真会发**裸** `{type:'turn_end', usageMissing:true}`(无 usage、无 sto
|
|
|
6645
6685
|
本来就是这么被复用的)时,两条终帧仍各报自己那一只子代 —— 包侧把「挂表」放在**终帧那一拍**、
|
|
6646
6686
|
与终帧铸点在同一个同步步里,先开的那条不会被后开的那条顶掉。
|
|
6647
6687
|
|
|
6688
|
+
#### 🔧 0.67.1 订正 ①(B-090,test [7055] 最高优先级发现):`isSubFlow` 的**身份键盲区**
|
|
6689
|
+
|
|
6690
|
+
修前的子流判据(`src/adapter/runStream.ts`)只认 `parentToolCallId`,而本节上面那段散文读法写的是
|
|
6691
|
+
「`sourceTaskId` 优先,wire 缺席时回落 `parentToolCallId`」—— 那句话**隐含**「只带 `sourceTaskId`」是一条
|
|
6692
|
+
合法组合,而实装把那一形判成了**主流**。后果是双重的,且**两条都是本件当初要防的病**:
|
|
6693
|
+
|
|
6694
|
+
1. 那条子代轮真的驱动了 `last_turn_usage`(与 `turn_usage` 消息臂)—— **§E2 断闸要防的 token-jump
|
|
6695
|
+
在这条路上原样重现**:子代的窗口占用落进 statusline 的主会话 Context;
|
|
6696
|
+
2. 它整条**不进分表** —— 那只子代的账从 `_sema_nested_usage_by_task` 里消失,与本节已判 PASS 的
|
|
6697
|
+
G32-23 直接矛盾(G32-23 的构造只覆盖了「两键同时在场」)。
|
|
6698
|
+
|
|
6699
|
+
**上游出处(不是本包的口味)**:core `task-event.d.ts` 的 `TaskEventIdentity` 逐字 —— `sourceTaskId`
|
|
6700
|
+
「stamped ONLY on the content events of a task running AS A SUB-AGENT(**same condition as**
|
|
6701
|
+
`parentToolCallId`)」,且「A TOP-LEVEL task's own events carry **NO** `sourceTaskId`」。
|
|
6702
|
+
|
|
6703
|
+
**修形**:`isSubFlow` = **身份信封两键任一在场**(判据是**键在不在**,不是「非空串」—— 空串 / 坏形也算
|
|
6704
|
+
信封在场,于是按子流走:失效方向是安全的那一侧,宁可少发一拍主会话的 usage,也绝不把子代的账混进主
|
|
6705
|
+
会话)。分表入表条件同批从「行键 **且** 父调用 id 都读得出」放宽到「**行键**读得出」。现势见上面的真值表。
|
|
6706
|
+
|
|
6707
|
+
**🔴 一条如实记的射程边界(不是遗漏)**:`{缺席, 在场}` 那一格上 chrome 增量臂 `subagent_turn_usage`
|
|
6708
|
+
**不发**。理由:该臂的车道证明 `LaneProof` 的子流臂是 `{lane:'subagent', parentToolCallId: string}`,
|
|
6709
|
+
座位契约门对它是**硬要求**;这一形上没有父调用 id,唯一能把臂发出去的办法是拿 `sourceTaskId` 去填那一格
|
|
6710
|
+
—— 那是在一个「父调用 id」的位上放一个**任务 id**(身份位互串,正是 G32-28 反向钉守的那类病),而放宽
|
|
6711
|
+
`LaneProof` 是**三端 BREAKING**(宿主今天按 `parentToolCallId: string` 读),不属于一个 patch 的射程。
|
|
6712
|
+
⇒ 那一形上:**主臂两处断闸照断**、**终帧分表照带那一行**,只有**实时增量**这条腿静默。
|
|
6713
|
+
🔴 三端据此:**不许**把「本臂没来过」读成「这只子代没花钱」—— 终帧分表才是那本账的全量面。
|
|
6714
|
+
|
|
6715
|
+
**消费方待办**
|
|
6716
|
+
|
|
6717
|
+
- **cli / desktop / web**:① 分表的**行键**从此可能是一个**没有对应 `subagent_turn_usage` 臂**的
|
|
6718
|
+
`taskId`(只带 `sourceTaskId` 的子代)⇒ 子代详情面按**终帧分表**渲,别拿「收到过增量臂」当行存在的前置;
|
|
6719
|
+
② 接增量臂的端注意本臂**仍然**保证 `parentToolCallId` 在场(型面未改,`LaneProof` 未改)——
|
|
6720
|
+
本订正**零 BREAKING**,只是多了一类「分表有行、增量臂没来过」的组合。
|
|
6721
|
+
|
|
6722
|
+
#### 🔧 0.67.1 订正 ②:分表行键是 wire 给的 id ⇒ 落键不许走裸赋值(`__proto__` 陷阱)
|
|
6723
|
+
|
|
6724
|
+
`Object.prototype.__proto__` 是一个 **accessor**:在普通对象上写 `rows["__proto__"] = row` 走的是那只
|
|
6725
|
+
setter ——**不产生自有属性**(值是对象时还顺手改了表自己的原型),于是 `taskId` 字面等于 `"__proto__"`
|
|
6726
|
+
的那一行在 `Object.keys` / `JSON.stringify` 里**整条消失**,连 `partial` 的对账量(行数 vs `nested.tasks`)
|
|
6727
|
+
都被带偏。⇒ 本包这几张**以 wire 给的 id / 键名为键**的表(分表行 / `modelUsage` 的 `modelId` /
|
|
6728
|
+
`costBreakdown` 的开集键名)一律改走 `Object.defineProperty` 落键。
|
|
6729
|
+
🔴 **交付形不变**:端拿到的仍是一只**普通原型**的对象(`hasOwnProperty` / `toString` 都在)——
|
|
6730
|
+
本订正只改「落键」这一步,描述符与普通赋值逐位相同,除 `__proto__` 这一个字面外其余键行为零改。
|
|
6731
|
+
可达性如实记:真 taskId 是 uuid 形,**实践可达性极低**;守的是**形**(以不可信 id 当对象键),不是现场。
|
|
6732
|
+
|
|
6733
|
+
**黑盒判据(新增,不改既有号)**
|
|
6734
|
+
|
|
6735
|
+
- **G32-23b**:一条**只带 `sourceTaskId`**(无 `parentToolCallId`)的子代 `turn_end` ⇒ ① **零**
|
|
6736
|
+
`last_turn_usage` chrome 臂、**零** `turn_usage` 消息臂(§E2 断闸);② 终帧分表**恰一行**、键 =
|
|
6737
|
+
该 `sourceTaskId`、数就是那一轮的数;③ 同一拍**不发** `subagent_turn_usage` 增量臂。
|
|
6738
|
+
判别力自证(反向):同一形补上 `parentToolCallId` ⇒ 增量臂照发。
|
|
6739
|
+
四组合真值表逐格可跑:`{在场,在场}` / `{在场,缺席}` / `{缺席,在场}` 三格是子流(零 `last_turn_usage`、
|
|
6740
|
+
零 `turn_usage`、恰一行),`{缺席,缺席}` 一格是主流(恰一条 `last_turn_usage`、恰一条 `turn_usage`、
|
|
6741
|
+
分表整键不铸)。
|
|
6742
|
+
- **G32-23c**:`sourceTaskId` 字面等于 `"__proto__"`(以及 `"constructor"`)的两只子代 ⇒ 两行都在终帧
|
|
6743
|
+
分表里、**`JSON.stringify` 之后仍可见**、`Object.keys` 数得到(⇒ `partial` 的对账量不被带偏);
|
|
6744
|
+
且 `_sema_nested_usage_by_task` 自己的原型**没有**被那一行改写。
|
|
6745
|
+
|
|
6746
|
+
#### 🔧 0.67.1 订正 ③(异源复审实抓):行键**出身混合** ⇒ `partial` 恒立
|
|
6747
|
+
|
|
6748
|
+
订正 ① 放宽入表条件之后,一条流上的行键**可以有两种出身**:`sourceTaskId`(真身份)与
|
|
6749
|
+
`parentToolCallId`(有损回落)。**混合出身**时计数对账不再充分 —— 反例:任务 A 第一轮只带回落键
|
|
6750
|
+
`p`、第二轮只带真身份 `A`,任务 B 一轮只带回落键 `p`;终局 `nested = {tasks: 2, turns: 3}`。
|
|
6751
|
+
这张表是 `{p: 2 轮(A 与 B 混在一起), A: 1 轮}` —— 归属是错的,但**行数 2 === tasks 2**、
|
|
6752
|
+
**turns 之和 3 === turns 3**,两条对账同时成立 ⇒ 被标成「可证完整」。根因是**一次拆行**(同一只
|
|
6753
|
+
任务占了两行)与**一次合行**(两只任务并进一行)的计数误差方向相反、恰好抵消。
|
|
6754
|
+
|
|
6755
|
+
⇒ **出身混合时 `partial` 恒立**。出身一致时既有两条对账仍是充分的:全真身份 ⇒ 一行一任务;
|
|
6756
|
+
全回落 ⇒ 多任务共父会让行数 `<` `nested.tasks`,那一格自己会翻。失效方向仍是安全的那一侧
|
|
6757
|
+
(只会**多**铸 partial,永远不会把一张残表说成完整)。
|
|
6758
|
+
|
|
6759
|
+
- **G32-25b**:混合出身(同一条流里既有只带 `sourceTaskId` 的轮、又有只带 `parentToolCallId` 的轮)
|
|
6760
|
+
⇒ 终帧 `_sema_nested_usage_by_task_partial === true`,**即使**行数与 turns 之和都对得上权威合计。
|
|
6761
|
+
判别力自证三形:全真身份对得上 ⇒ 不铸;全回落且父调用与任务一一对应 ⇒ 不铸;全回落且多任务共父
|
|
6762
|
+
⇒ 照旧铸(本条不是那一格的替身)。
|
|
6763
|
+
|
|
6764
|
+
#### ⚠️ 0.67.1 未修的**已知局限**(同族、已立案,本批**刻意不动**):子代**正文**分流仍只认 `parentToolCallId`
|
|
6765
|
+
|
|
6766
|
+
`src/adapter/runStream.ts` 的 C1 子代内容分流(`text_delta` / `reasoning_delta` / `text` / `reasoning` /
|
|
6767
|
+
`tool_start` / `tool_end` 六臂)的判据是 `typeof parentToolCallId === 'string'`,**没有**跟着订正 ① 一起
|
|
6768
|
+
认 `sourceTaskId`。实测(本批构造):一条 `{type:'text_delta', sourceTaskId:'child-1', delta:…}` 会被投影成
|
|
6769
|
+
主流 `stream_event`、`parent_tool_use_id: null` —— 即子代正文渲成 leader 自己的正文,正是那条分流臂
|
|
6770
|
+
整段头注在防的污染形。
|
|
6771
|
+
|
|
6772
|
+
- **它不是本批引入的**:main 上逐字同形(该段未被本批改动),本批只是把**用量**那一半修对了,于是
|
|
6773
|
+
同一只子代的「正文」与「用量」在这一形上落到了两条车道 —— 这个不一致本身是本批显形出来的。
|
|
6774
|
+
- **为什么本批不动它**:honest 的修法只有两条 —— (a) 拿 `sourceTaskId` 去填内容账本的
|
|
6775
|
+
`parentToolCallId` 位(身份位互串,与订正 ① 拒绝的那条同形);(b) 判为「知道是子代但归不到卡」⇒
|
|
6776
|
+
**留痕丢弃**,不许回落主转录。(b) 是**行为面/用户可见面**的改动(今天用户看得到那段文字,修后看不到),
|
|
6777
|
+
按黑盒验收制要单独出判据帖 + 三端表态,**不属于一个 patch 的射程**。
|
|
6778
|
+
- **⇒ 立案**:候 clay / 三端裁 (a)/(b),坐标 `src/adapter/runStream.ts` C1 分流臂的判据行。
|
|
6779
|
+
在它落定之前,消费方按「这一形的正文可能出现在主转录里」办。
|
|
6780
|
+
|
|
6648
6781
|
---
|
|
6649
6782
|
|
|
6650
6783
|
### 32h. 7.15.0 / 7.16.0 的其余各行(逐行理由在 §32z;这里只写**两条例外**)
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@sema-agent/client-core",
|
|
3
|
-
"version": "0.67.
|
|
3
|
+
"version": "0.67.1",
|
|
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",
|