@sema-agent/client-core 0.64.2 → 0.65.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 (48) hide show
  1. package/CHANGELOG.md +138 -0
  2. package/README.md +11 -3
  3. package/dist/adapt/arms.js +125 -7
  4. package/dist/adapt/ids.d.ts +22 -0
  5. package/dist/adapt/ids.js +29 -0
  6. package/dist/adapt/panelTasks.d.ts +22 -0
  7. package/dist/adapt/panelTasks.js +45 -0
  8. package/dist/adapt/textStream.js +6 -3
  9. package/dist/adapt.d.ts +1 -1
  10. package/dist/adapt.js +3 -0
  11. package/dist/adapter/downstream/eventToSdkMessage.d.ts +0 -10
  12. package/dist/adapter/downstream/eventToSdkMessage.js +207 -65
  13. package/dist/adapter/downstream/terminalToSdkResult.d.ts +4 -4
  14. package/dist/adapter/downstream/terminalToSdkResult.js +43 -12
  15. package/dist/adapter/downstream/turnUsageToModelUsage.d.ts +23 -2
  16. package/dist/adapter/downstream/turnUsageToModelUsage.js +7 -1
  17. package/dist/adapter/runStream.js +39 -2
  18. package/dist/adapter/types.d.ts +12 -0
  19. package/dist/autoModeUnavailable.d.ts +39 -83
  20. package/dist/autoModeUnavailable.js +58 -111
  21. package/dist/classifierStatus.d.ts +25 -71
  22. package/dist/classifierStatus.js +110 -105
  23. package/dist/decideReceipt.d.ts +117 -0
  24. package/dist/decideReceipt.js +142 -0
  25. package/dist/engineErrorCodes.d.ts +32 -0
  26. package/dist/engineErrorCodes.js +42 -0
  27. package/dist/fleet/fleetProjection.d.ts +24 -1
  28. package/dist/fleet/fleetProjection.js +26 -1
  29. package/dist/fleetAgentPanelProjection.js +6 -1
  30. package/dist/gateVocabulary.d.ts +9 -1
  31. package/dist/gateVocabulary.js +46 -4
  32. package/dist/hitl/askGateWire.js +22 -1
  33. package/dist/hitl/gateLedger.d.ts +24 -0
  34. package/dist/hitl/gateLedger.js +8 -0
  35. package/dist/hitl/hitlBridge.js +14 -2
  36. package/dist/hitl/parkResolver.d.ts +23 -2
  37. package/dist/hitl/parkResolver.js +34 -6
  38. package/dist/hitl/toolApprovalWire.d.ts +12 -1
  39. package/dist/hitl/toolApprovalWire.js +15 -6
  40. package/dist/index.d.ts +1 -0
  41. package/dist/index.js +16 -6
  42. package/dist/notifications.js +11 -2
  43. package/dist/runTerminal.d.ts +48 -0
  44. package/dist/runTerminal.js +59 -0
  45. package/dist/seam.d.ts +131 -1
  46. package/dist/seam.js +22 -0
  47. package/docs/INTEGRATION-CLIENTS.md +673 -63
  48. package/package.json +2 -2
@@ -5,6 +5,15 @@ import { toCcModelUsage } from './turnUsageToModelUsage.js';
5
5
  // G1 去字面化(2026-08-04):到限/结构化输出/rewind 三族的码字面收编进单一真源,本文件只 import。
6
6
  // 开集纪律不变——下面三个集合仍是**识别表**,`subtypeForErrorCode` 的 default 臂才是开集的兑现处。
7
7
  import { LIMITS_MAX_COST_EXCEEDED, LIMITS_MAX_TOKENS_EXCEEDED, LIMITS_MAX_TURNS_EXCEEDED, LIMITS_MAX_WALLTIME_EXCEEDED, OUTPUT_INVALID, isRewindFamilyCode, } from '../../engineErrorCodes.js';
8
+ /**
9
+ * wire 数值 → CC 扁平 usage 里的数值:非数 / 非有限一律 0。
10
+ * 与 `turnUsageToModelUsage.finiteOrZero` 同一条规约(那边是 `ModelUsage` 八键的 mint 点,
11
+ * 这边是 `NonNullableUsage` 五键的);两处**刻意不共用一个导出**,因为它们分属两个可移植闭包
12
+ * 的叶,而这一行判据本身没有会漂的内容(有会漂的内容时才值得上收)。
13
+ */
14
+ function finiteOr0(v) {
15
+ return typeof v === 'number' && Number.isFinite(v) ? v : 0;
16
+ }
8
17
  /**
9
18
  * Flatten TaskStats → the CC NonNullableUsage placeholder.
10
19
  * 🔴 REF-CC-055(xlate-01):`failedToSdkResult` 此前手抄了一份「五键全零」的字面量,那不是
@@ -12,10 +21,21 @@ import { LIMITS_MAX_COST_EXCEEDED, LIMITS_MAX_TOKENS_EXCEEDED, LIMITS_MAX_TURNS_
12
21
  */
13
22
  function flattenUsage(stats) {
14
23
  return {
15
- inputTokens: stats?.promptTokens ?? 0,
16
- outputTokens: stats?.outputTokens ?? 0,
17
- cacheReadInputTokens: stats?.cachedTokens ?? 0,
18
- cacheCreationInputTokens: 0,
24
+ // 🔴 B-073 ③ 族扫(0.65.0):四格一律走 {@link finiteOr0},不再用 `?? 0`。
25
+ // `??` 只挡 `null`/`undefined` —— 而 `TaskStats` 带 `[key: string]: unknown` 开集索引,
26
+ // wire JSON,一个 `"12"` / `NaN` / `Infinity` 会**原样落进** CC 侧型面写着 `number`
27
+ // 的槽位(编译期全绿,消费端在求和时才炸或静默出错)。`Infinity` 尤其糟:它会被当成真
28
+ // 数字摊进总计(与本文件 `microUsdToUsd` 头注同一条规约)。
29
+ inputTokens: finiteOr0(stats?.promptTokens),
30
+ outputTokens: finiteOr0(stats?.outputTokens),
31
+ cacheReadInputTokens: finiteOr0(stats?.cachedTokens),
32
+ // B-073 ③(L-192② 姊妹):此前是**字面量 0**,而 wire 上一直有这一格
33
+ // (`TaskStats.cacheWriteTokens`,sdk `types.d.ts` 真字节)—— 硬编 0 把一笔真实发生的缓存
34
+ // 写入抹成「没发生」,而 CC 的 footer/账单面正是拿这一格算缓存成本的。
35
+ // 缺席仍折 0:CC `NonNullableUsage` 的这一格型面上是必填 number,而它与**成本**那一格不同
36
+ // (token 计数没报就是没写过),缺席与真 0 在 wire 上本来就同义 —— 成本那一格的缺席是 core
37
+ // 刻意造出来的第三档,故单有判别位(见 `toCcModelUsage`)。
38
+ cacheCreationInputTokens: finiteOr0(stats?.cacheWriteTokens),
19
39
  webSearchRequests: 0,
20
40
  };
21
41
  }
@@ -38,11 +58,13 @@ function microUsdToUsd(micro) {
38
58
  return typeof micro === 'number' && Number.isFinite(micro) ? micro / 1e6 : null;
39
59
  }
40
60
  /**
41
- * P1-5 — `total_cost_usd` truthfulness: 成本 `0` is AMBIGUOUS (free vs. no MODEL_COST_* configured
42
- * on the worker — the local TOC engine ships unconfigured, so `-p --output-format json` was reporting a
43
- * fake `0`). Emit the number only when it is a REAL positive cost; otherwise `null` (JSON consumers see
44
- * "unknown", never a fabricated zero). CC's schema field is numeric — number-guarded readers
45
- * (replEntry cost fold) already treat non-number as absent.
61
+ * P1-5 — `total_cost_usd` truthfulness: 报**引擎真的说出来的那个数**;引擎**没说**(键缺席 /
62
+ * 非有限)才报 `null`(JSON 消费者看到 "unknown",绝不是一个编出来的零)。
63
+ * 🔴 **0.65.0(B-073 ①)口径订正**:P1-5 当初写的是「只有真正的正成本才报,其余一律 null」,
64
+ * 前提是那时 `0` 分不清「免费」与「没配 MODEL_COST_*」。core 之后把这两件事在 wire 上**分开**
65
+ * (缺席 = 没定价;显式 0 = 声明免费),旧口径于是开始把一条真免费的 run 渲成「不知道」——
66
+ * 那是同一条病的反向。现在的口径见 {@link costOrNull}。CC 的 schema 字段是 numeric ——
67
+ * number-guarded 的读者(replEntry cost fold)本来就把非 number 当缺席。
46
68
  *
47
69
  * 🔴 键迁移([2006] cli 自查最重命中,2026-07-29):取数源从 legacy float `TaskStats.costUsd` 换成
48
70
  * 整数 `TaskStats.costMicroUsd`。`costUsd` 已被 **core 2.0.0 / server 1.319.0 / SDK 1.0.0 同批删除**
@@ -57,8 +79,16 @@ function microUsdToUsd(micro) {
57
79
  * 本仓 [clean-cut-no-legacy-compat])。
58
80
  */
59
81
  function costOrNull(stats) {
60
- const usd = microUsdToUsd(stats?.costMicroUsd);
61
- return usd !== null && usd > 0 ? usd : null;
82
+ // 🔴 B-073 (0.65.0):修前是 `usd !== null && usd > 0 ? usd : null` —— 一条**数值真值判定
83
+ // 当存在性判定**(§B10 病族)的教科书例。core 早已把两件事在 wire 上分开:
84
+ // · `costMicroUsd` **ABSENT** = 有一笔花销没定价 ⇒ 报 `null`(不知道);
85
+ // · `costMicroUsd === 0` = 这台模型**显式声明免费**(core 逐字
86
+ // `an explicit all-zero Model.cost still reports 0`)⇒ 就该报 `0`。
87
+ // 旧写法把「声明免费」也渲成 `null`,于是一条真免费的 run 在 `-p --output-format json` 上
88
+ // 说不出「免费」;而它当初写成 `> 0` 的理由(「0 可能是没配 MODEL_COST_*」)在 core 把缺席
89
+ // 做成可表达的那一刻就失效了 —— 那一档现在由**缺席**自己承载,不必再借 0 当哨兵。
90
+ // 负数照报:退款/修正在引擎账面上是合法值,包不当第二个会计(非有限值仍由 microUsdToUsd 判掉)。
91
+ return microUsdToUsd(stats?.costMicroUsd);
62
92
  }
63
93
  // 件2a(中断事故修复批 G,2026-07-15,症状2 第一环):参数放宽 TaskStats | undefined + 全链守卫。
64
94
  // 409 active-run 拒绝时引擎的 done 帧本体是 {status:'failed', errorMessage, activeTaskId} —— NO stats
@@ -113,7 +143,8 @@ function modelUsageFor(stats, model) {
113
143
  inputTokens: stats?.promptTokens,
114
144
  outputTokens: stats?.outputTokens,
115
145
  cacheReadTokens: stats?.cachedTokens,
116
- cacheWriteTokens: 0,
146
+ // B-073 ③ 族扫:合成行是 `flattenUsage` 的**同形第二处**,同批一并改读真值。
147
+ cacheWriteTokens: stats?.cacheWriteTokens,
117
148
  costMicroUsd: stats?.costMicroUsd,
118
149
  }),
119
150
  };
@@ -50,6 +50,27 @@ export interface EngineUsageLike {
50
50
  readonly cacheWriteTokens?: number | undefined;
51
51
  readonly costMicroUsd?: number | undefined;
52
52
  }
53
+ /**
54
+ * B-073 ②(0.65.0)—— CC `ModelUsage` 的 sema 超集形:多一个**成本缺席判别位**。
55
+ *
56
+ * 🔴 **为什么这一位必须长在 CC 镜像上,而 `totalInputTokens` 不可以**(与本文件头注引的 [2295]
57
+ * 「CC 形状不承载非 CC 语义」**不矛盾**,差别有出处,写在这里免得下一棒读成漂移):
58
+ * · `totalInputTokens` 有**另一条逐字通道**(`EngineTurnUsage` / runStream 的 `engineUsage` 键),
59
+ * 要总量的消费面去那儿取即可 ⇒ 镜像不必长第二个座位;
60
+ * · **成本缺席没有任何别的载体**:CC 的 `costUSD` 型面上是**必填 number**,「这笔花销没定价」
61
+ * 在 CC 形里**根本不可表达**。不加这一位,唯一的写法就是 `costUSD: 0` —— 那正是 core 逐字
62
+ * 禁止的 `a fabricated 0`(它把「没有价表」和「声明免费」两件事折成同一个字节)。
63
+ * 🔴 **两键合读**,这一位单独没有意义:
64
+ * · `costUSD === 0` 且本位**缺席** ⇒ 引擎显式报了 0 = **声明免费**;
65
+ * · `costUSD === 0` 且本位 `true` ⇒ 引擎**没报** = 没定价,这一行的账**不知道**,别进总计。
66
+ * 只读 `costUSD` 的旧消费者行为**逐字不变**(它仍是那个 0)—— 这是 additive 的全部含义。
67
+ * 🔴 **只在缺席时铸,绝不铸 `false`**(本包 additive 一贯纪律:诚实缺席 = 键不在场;
68
+ * 「键在值假」会让消费端以为这是一个三态位)。
69
+ */
70
+ export type SemaModelUsage = ModelUsage & {
71
+ /** 在场且为 `true` ⇒ 这一行的 `costUSD: 0` 是「**没定价**」,不是「免费」。 */
72
+ readonly _sema_cost_absent?: true;
73
+ };
53
74
  /**
54
75
  * 🔴 **CC `ModelUsage` 八键形状的唯一 mint 点**(REF-CC-062 / xlate-08,E1 单源构造)。
55
76
  *
@@ -71,6 +92,6 @@ export interface EngineUsageLike {
71
92
  * `microUsdToUsd() ?? 0`(非有限即 0)。统一取后者:Infinity 会被下游当成真数字参与求和,把一条
72
93
  * 离谱账静默摊进总计,比 0 更糟(与 SDK `taskCostMicroUsd()` 的规约同义)。
73
94
  */
74
- export declare function toCcModelUsage(raw: EngineUsageLike): ModelUsage;
75
- export declare function turnUsageToModelUsage(usage: TurnUsage): ModelUsage;
95
+ export declare function toCcModelUsage(raw: EngineUsageLike): SemaModelUsage;
96
+ export declare function turnUsageToModelUsage(usage: TurnUsage): SemaModelUsage;
76
97
  export {};
@@ -24,15 +24,21 @@ function finiteOrZero(v) {
24
24
  * 离谱账静默摊进总计,比 0 更糟(与 SDK `taskCostMicroUsd()` 的规约同义)。
25
25
  */
26
26
  export function toCcModelUsage(raw) {
27
+ // B-073 ②:成本这一格与其余七格**判据不同** —— 其余格的缺席在 wire 上与真 0 同义(token 计数
28
+ // 没报就是没花),而成本的缺席是 core **刻意**造出来的一档语义(`ABSENT when any spend was
29
+ // unpriced … rather than a fabricated 0`)。故这里单独判一次,而不是继续走 `finiteOrZero`。
30
+ const costPriced = typeof raw.costMicroUsd === 'number' && Number.isFinite(raw.costMicroUsd);
27
31
  return {
28
32
  inputTokens: finiteOrZero(raw.inputTokens),
29
33
  outputTokens: finiteOrZero(raw.outputTokens),
30
34
  cacheReadInputTokens: finiteOrZero(raw.cacheReadTokens),
31
35
  cacheCreationInputTokens: finiteOrZero(raw.cacheWriteTokens),
32
36
  webSearchRequests: 0, // dropped on the wire
33
- costUSD: finiteOrZero(raw.costMicroUsd) / 1_000_000,
37
+ // CC 形不破:值仍是 number。「不知道」由同行的判别位说,见 {@link SemaModelUsage}。
38
+ costUSD: costPriced ? raw.costMicroUsd / 1_000_000 : 0,
34
39
  contextWindow: 0, // dropped on the wire → mock-fill (static per-model table)
35
40
  maxOutputTokens: 0, // dropped on the wire → mock-fill
41
+ ...(costPriced ? {} : { _sema_cost_absent: true }),
36
42
  };
37
43
  }
38
44
  export function turnUsageToModelUsage(usage) {
@@ -351,6 +351,13 @@ async function* runStreamInner(events, ctx, handle = {}) {
351
351
  // the spinner-token feeder, verify/TOKEN-187-PORT.md step 3). `turn_end` carries no renderable content, so
352
352
  // this arm is metrics-only; the bridge converts it to a `message_delta{usage.output_tokens}` StreamEvent.
353
353
  if (ev.type === 'turn_end') {
354
+ // 🔴 L-215③ 异源对抗复审 [medium]②:`usageMissing` 是 core **刻意**造出来的诚实缺席位
355
+ // (臂注逐字:「Consumers must treat the missing usage as UNKNOWN — not zero」;它与
356
+ // `usage` **可以同帧**)。把它剥掉,本批新开的这条 usage 通道就会把「不知道」渲成一笔
357
+ // 全零的已知账 —— 与本批要根治的病(B-073 的成本 0/缺席)逐字同形,只是换了个量。
358
+ const usageMissing = ev.usageMissing === true;
359
+ const stopReasonRaw = ev.stopReason;
360
+ const stopWord = typeof stopReasonRaw === 'string' && stopReasonRaw.length > 0 ? stopReasonRaw : undefined;
354
361
  const usage = turnEndUsage(ev);
355
362
  // §E2 identity (service 1.78) — a SUB-FLOW's turn_end (orchestration/subagent round, carries
356
363
  // parentToolCallId) must NOT drive the leader's C1a `end` reconcile: its outputTokens are the
@@ -379,14 +386,44 @@ async function* runStreamInner(events, ctx, handle = {}) {
379
386
  laneProof: MAIN,
380
387
  usage,
381
388
  ...(ev.usage !== undefined ? { engineUsage: ev.usage } : {}),
389
+ // L-215③:chrome 腿同批带这两位(message 腿的对偶在 `turn_usage` 臂的 `_sema_` 键上)。
390
+ // 🔴 `usageMissing` 在这条腿上**不能**靠「不发 usage」表达 —— 本臂的 `usage` 是必填位
391
+ // (宿主义务是「落最近一次 turn 真 usage」),所以它只能以判别位在场:
392
+ // `usageMissing === true` ⇒ 同行那份 usage **不是**一笔已知的账,别当真值落槽。
393
+ ...(usageMissing ? { usageMissing: true } : {}),
394
+ ...(stopWord !== undefined ? { stopReason: stopWord } : {}),
382
395
  });
383
396
  }
384
397
  catch { /* fail-soft — statusline 退回 null,原语义 */ }
385
398
  }
386
399
  }
387
400
  const outputTokens = ev.usage?.outputTokens;
388
- if (typeof outputTokens === 'number' && !isSubFlow) {
389
- yield { type: 'turn_usage', outputTokens };
401
+ // 🔴 异源对抗复审 [medium]③:发臂条件从「有 outputTokens」放宽到「**有话可说**」——
402
+ // core 会发 `{type:'turn_end', usageMissing:true, stopReason:'error'}` 这种合法帧,而
403
+ // 旧条件让它整条静默 ⇒ 「这一轮为什么停」这条机读位在最需要它的那一刻(出错/中止)不见了。
404
+ // ⚠️ 旧消费者零影响:`outputTokens` 读不出时**整键不铸**,而 adapt 的 `turnUsageArm`
405
+ // 本来就以 `typeof m.outputTokens === 'number'` 开门 ⇒ 这种帧对它是 no-op。
406
+ if ((typeof outputTokens === 'number' || stopWord !== undefined) && !isSubFlow) {
407
+ // ── L-215③(0.65.0):assistant 行那两个**算不出来**的键的真值出口 ─────────────────
408
+ // `eventToSdkMessage` 的 `assistantArm` 刻意**不**在内容臂上铸 `usage` / `stop_reason`
409
+ // (帧序:内容臂先到、`turn_end` 后到 ⇒ 臂发出时引擎还没说这一轮花了多少;在那里铸只能
410
+ // 是估算,而估算正是本件要根治的病)。真值只能在**这里**给 —— 这条臂本来就是 turn 收尾
411
+ // 那一拍的中性出口。两个都是 `_sema_` 超集键,CC 同名键语义零改:
412
+ // · `_sema_last_assistant_usage` —— 这一轮的 CC `ModelUsage` 镜像(与 footer 折叠用的
413
+ // 是**同一只** `turnEndUsage()` 产物,不另铸第二份 ⇒ 两面永远不会各漂各的);
414
+ // · `_sema_stop_reason` —— `turn_end.stopReason` **原词透传**(core 归一化后的五词
415
+ // `stop`/`length`/`toolUse`/`error`/`aborted`,sdk 型面是开放 string ⇒ 按开集读;
416
+ // 「这一轮是不是被 max_tokens 截了」就靠它,此前 stream 与 trace 两面互盲)。
417
+ // 缺席一律不铸(旧引擎不发 `stopReason`;`usage` 整体缺席时本臂根本不发,见上面的 if)。
418
+ yield {
419
+ type: 'turn_usage',
420
+ ...(typeof outputTokens === 'number' ? { outputTokens } : {}),
421
+ // 🔴 `usageMissing` 在场 ⇒ **不铸镜像**(铸了就是把「不知道」写成一笔全零的已知账),
422
+ // 改铸判别位。两键互斥,消费方一看就知道这一轮的账是不是可信。
423
+ ...(usage !== undefined && !usageMissing ? { _sema_last_assistant_usage: usage } : {}),
424
+ ...(usageMissing ? { _sema_usage_missing: true } : {}),
425
+ ...(stopWord !== undefined ? { _sema_stop_reason: stopWord } : {}),
426
+ };
390
427
  }
391
428
  continue;
392
429
  }
@@ -65,6 +65,18 @@ export interface EmitContext {
65
65
  /** Wall-clock ms when the stream started draining (runStream stamps it once) — the terminal projector
66
66
  * derives the CC `duration_ms` from it (P1-5: the wire carries no duration; 0 was a fake constant). */
67
67
  startedAtMs?: number;
68
+ /**
69
+ * L-215③(0.65.0)—— **这条流跑在哪个模型上**,由**宿主开流时钉**(与 `sessionId` 同一类:
70
+ * 请求是宿主构造的,模型 id 是它自己写进 `TaskRequest` 的那一个;wire 的内容帧上没有这一格)。
71
+ *
72
+ * 用途:`assistant` 臂的 `message.model` —— CC 的 `SDKAssistantMessage.message` 上本来就有这一位,
73
+ * 而本包此前交给渲染端的是**裸** `{role, content}`,于是宿主十几处消费面只能各自猜。
74
+ * 🔴 **缺席 ⇒ 那一位不铸,绝不猜**:一个猜错的模型名比没有更坏(账单/能力面会拿它去查表)。
75
+ * ⚠️ **它是「这条流声明的模型」,不是「这一轮真正服务的模型」**:mid-run degrade / 网关重路由
76
+ * 不改写它(与 `TaskResult.model` 同一条纪律,core 的 `task_progress.model` 臂注逐字写过)。
77
+ * 要「真正服务的那一个」请读终帧的 `TaskResult.model` / `degraded`。
78
+ */
79
+ model?: string;
68
80
  /**
69
81
  * B3 新增(设计稿 §5.2 两条外向边的落点)—— chrome 事件下泄口。runStream 里那两条
70
82
  * `import('../../sema/…')` 动态边改走这里:宿主注入一个 sink,库侧只发事件。
@@ -1,6 +1,6 @@
1
1
  /**
2
2
  * src/autoModeUnavailable.ts — 「这只 ask 是因为**分类器跑不了**才问人」的事实读器 + 唯一措辞铸点
3
- * (0.63.0 件⑧;core 7.10.0 #616)。
3
+ * (0.63.0 件⑧;core 7.10.0 #616 —— **0.65.0 随 core 7.12.0 收窄,见下方「熔断族退役」段**)。
4
4
  *
5
5
  * -- 它答的是哪一问 ---------------------------------------------------------------------------
6
6
  * auto 模式下,门会就一只 ask 去咨询分类器。分类器**没跑成**时,引擎在这只 ask 上盖一格
@@ -10,70 +10,52 @@
10
10
  * 🔴 **缺席 ≠「分类器跑成了」**:绝大多数 ask 根本没咨询过分类器(部署没武装 auto、或这只 ask 走的
11
11
  * 是别的门)。缺席只意味着「这只 ask 上没有这条事实」。
12
12
  *
13
- * -- 🔴 两条 cause 轴,不是一张表 --------------------------------------------------------------
14
- * · {@link AUTO_MODE_UNAVAILABLE_CAUSES}(`error` / `timeout` / `breaker_open`)—— 「这一轮分类
15
- * **为什么没跑成**」。这是 `classifierUnavailable.cause` 的值域。
16
- * · {@link AUTO_MODE_BREAKER_CAUSES}(`error` / `timeout` / `parse_error`)—— 「**熔断闩为什么合上**」。
17
- * 两集交于 `error` / `timeout`,各有一个独占成员。合成一张表就把两条轴的差别扔了。
18
- * 🔴 **`parse_error` 只在熔断轴上**:core 顶注逐字 ——「`parse_error` stamps nothing」:分类器
19
- * **跑了并且答了**,只是答在契约之外,那是**另一句话**。所以它**一个字节都不 stamp** 到
20
- * `classifierUnavailable` 上;本读器把**熔断轴独占的词**({@link BREAKER_ONLY},今天 = `parse_error`)
21
- * 判**缺席** —— 把它读成一个「没跑成」的成因就是替引擎编一件它明说没发生的事。
22
- * ⚠️ **0.64.2 订正(行为面)**:修前这只读器按 unavailable **闭集**收窄,于是一台比本端新的引擎发一个
23
- * **合法的新成因词**时,卡上那一行整段消失(而 {@link classifierUnavailableDetail} 的兜底句因此
24
- * **永远不可达**)。现改为「非空串即收 + 排除熔断轴独占词」——「表外」与「另一条轴上的词」是两件事,
25
- * 修前把它们判成了同一件。`parse_error` 的行为一字未变(黑盒判据 G-19 照旧成立)。
13
+ * -- 🔴 熔断族退役(0.65.0;core 7.12.0 CHANGELOG「Removed (BREAKING)」)--------------------------
14
+ * core 7.12.0 `AUTO_MODE_BREAKER_CAUSES` `WiringManifest.autoMode.breaker` 整族删掉,
15
+ * `AUTO_MODE_UNAVAILABLE_CAUSES` 同批收成 `["error","timeout"]`(`dist/core/auto-mode.d.ts:9` 真字节);
16
+ * server 7.70.0([6927])读面同批收窄,`classifierUnavailable.cause` 不再发 `breaker_open`。
17
+ * ⇒ 本模块把 `AUTO_MODE_BREAKER_CAUSES` / `ClassifierBreakerView` / `classifierBreakerOf` 与
18
+ * `breaker_open` 这个词**干净切**(clean-cut,不留别名、不留兼容读):留一个读不到的读器只会让
19
+ * 三端继续为一件上游已经不发的事实写渲染分支,而那正是「校旧物」的门自己要拦的形。
20
+ * 🔴 **`cause` 的开集读一个字节不动**:一台比本端**新**的引擎发一个新成因词时照收(
21
+ * {@link classifierUnavailableOf});退役掉的只是本端**认得**的那张表里的一个成员 —— 旧引擎真发
22
+ * `breaker_open` 时它照旧过境,只是走 {@link classifierUnavailableDetail} 的兜底句
23
+ * (「这个词比这一端新/旧」那一类),不再冒充一句本端自铸的解释。
24
+ *
25
+ * -- 🔴 判据轴与成因轴不是一张表 --------------------------------------------------------------
26
+ * · {@link AUTO_MODE_UNAVAILABLE_CAUSES}(`error` / `timeout`)—— 「这一轮分类**为什么没跑成**」。
27
+ * 这是 `classifierUnavailable.cause` 的值域(词表属主 = core)。
28
+ * · core `AutoModeVerdict` 的**判据轴**另有一个 `parse_error` 臂 —— 分类器**跑了并且答了**,
29
+ * 只是答在契约之外(`dist/core/auto-mode.d.ts:29-32` 逐字:「The model responded but not in the
30
+ * `<block>…` contract shape. Handled as a BLOCK …」)。它**一个字节都不 stamp** 到
31
+ * `classifierUnavailable` 上,那是**另一句话**。
32
+ * 🔴 本读器因此把 {@link NEVER_STAMPED_CAUSES}(今天 = `{parse_error}`)判**缺席** —— 把一个判据词
33
+ * 读成「没跑成」的成因,就是替引擎编一件它明说没发生的事。0.63.0 起的黑盒判据 G-19 钉的正是这
34
+ * 一条,退役熔断族**没有改动它的行为**(修前这一格由「熔断轴 − 不可用轴」的差集派生,而差集的
35
+ * 右操作数随熔断表一起没了;排除的**理由**从来就不在熔断轴上,现按理由的真出处直写)。
26
36
  *
27
37
  * -- 为什么是镜像而不是 import ---------------------------------------------------------------
28
- * 这两张表与 `classifierUnavailable` 的型面在 sdk 8.8.0 与 agent-types 上**都还没有**(亲验:两棵树
29
- * 全树零命中),唯一的出处是 `@sema-agent/core` 的 `dist/core/auto-mode.js` / `tool-policy.d.ts` /
30
- * `checkpoint-store.d.ts`。而 core **不是本包消费者的依赖**(既非 peer 也非 runtime dep),本包的
31
- * `.d.ts` 一旦引用它,装了本包却没装 core 的下游会当场编译不过。⇒ 与 `engineNoticeCodes.ts` /
32
- * `toolResult.ts` / `retryStatus.ts` 同一条处置:**按真字节镜像,把代价交给门** ——
38
+ * 这张表与 `classifierUnavailable` 的型面在 sdk 8.8.0 与 agent-types 上**都还没有**(亲验:两棵树
39
+ * 全树零命中),唯一的出处是 `@sema-agent/core` 的 `dist/core/auto-mode.js` / `tool-policy.d.ts`。
40
+ * core **不是本包消费者的依赖**(既非 peer 也非 runtime dep),本包的 `.d.ts` 一旦引用它,装了
41
+ * 本包却没装 core 的下游会当场编译不过。⇒ 与 `engineNoticeCodes.ts` / `toolResult.ts` /
42
+ * `retryStatus.ts` 同一条处置:**按真字节镜像,把代价交给门** ——
33
43
  * `run-auto-mode-unavailable-test.mjs` 对**实装 devDep core** 的产物逐词双向对账,core 一动这里就先红。
34
- * 🔴 **候上游导出即换**:sdk 哪天镜像了这两张表与那一格,本模块的表应当整只退役改成从 sdk 取。
35
- *
36
- * -- 0.64.0 件② 加员:**会话轴**的窄读 --------------------------------------------------------
37
- * {@link classifierBreakerOf} 读的是同一条熔断轴的**另一端**:不是「这一轮为什么没跑成」,而是
38
- * 「这个**会话**的闩什么时候合上的、因为什么、连着失败了几次」(core 7.10.0 `AutoModeBreakerTrip`,
39
- * 挂在 `WiringManifest.autoMode.breaker` 上;**server ≥7.69.0 才投**)。
40
- * 🔴 **它是历史,不是当前闩状态**:core 顶注逐字「a trip names one leg … the ledger carries the most
41
- * recent one forward per session」—— 同一会话的后一条腿完全可以重新武装。判「分类器现在跑不跑」
42
- * 的量是那条腿的 `armed`,不是这条记录在不在(判定在 `classifierStatus.ts`)。
43
- * 🔴 **缺席有四种成因,不可分辨**:从没熔断过 / 账本(有界 FIFO)把它淘汰了 / 这一次是 standalone
44
- * prepare(账本没接上)/ 老引擎不投。所以缺席只能读成「**没有可用的熔断记录**」——
45
- * 既不是「没熔断过」,也不是「一定是老引擎」。
46
- * 它落在本文件是因为
47
- * {@link AUTO_MODE_BREAKER_CAUSES} 本来就在这里 —— 表与读它的窄读同居,而不是隔一个文件遥指。
48
- * 🔴 它同时是那一处的**唯一**窄读:`wiring_manifest` 的投影臂与 `classifierStatus.ts` 的状态读器
49
- * 共用它。本文件是**零 import 的纯叶**,所以投影臂那条内核闭包只多一件叶子(见
50
- * `run-client-core-portability-test.mjs` 的内核上限记账)。
51
- * 🔴 **退役条款**:上游若改采 CC 形而把 `breaker` 这一键整只退役(它是 sema 在 CC 之外自己加的
52
- * 一格),本读器**零改** —— 它的**缺席臂**当天就是正解:读不到就是没有,状态面自然回落到
53
- * 本轮轴与「可用」。退役一个 additive 键不该逼三端各改一次。
44
+ * 🔴 **候上游导出即换**:sdk 哪天镜像了这张表与那一格,本模块的表应当整只退役改成从 sdk 取。
54
45
  */
55
46
  /**
56
- * 一轮分类**为什么没跑成**(core `AUTO_MODE_UNAVAILABLE_CAUSES`;逐词逐序镜像)。
47
+ * 一轮分类**为什么没跑成**(core `AUTO_MODE_UNAVAILABLE_CAUSES`;逐词逐序镜像,core 7.12.0 起两词)。
57
48
  * · `error` —— 模型那条腿抛了/被拒(分类时的路由失败也读在这里:派生路由的前置在任何 decide
58
49
  * 之前就回落了,所以没有单独的词);
59
- * · `timeout` —— 往返上限到了;
60
- * · `breaker_open` —— 本会话的熔断闩**已经**合上,这一轮被短路,压根没发出去。
50
+ * · `timeout` —— 往返上限到了。
61
51
 
62
52
  * 🔴 形制:`Object.freeze` 的数组,**不是**只在类型面只读的 `readonly T[]` —— 后者一行 `.splice()`
63
53
  * 就能改,而公面消费者拿到的正是这个实例(本仓已定谳的病形,同 `RESUME_RETRY_LATER_CODES`)。
64
54
  */
65
55
  export declare const AUTO_MODE_UNAVAILABLE_CAUSES: readonly string[];
66
- /**
67
- * **熔断闩为什么合上**(core `AUTO_MODE_BREAKER_CAUSES`;逐词逐序镜像)——**另一条轴**,
68
- * 与上面那张表刻意不合并(见模块顶注)。`parse_error` 是它的独占成员。
69
-
70
- * 🔴 形制:`Object.freeze` 的数组,**不是**只在类型面只读的 `readonly T[]` —— 后者一行 `.splice()`
71
- * 就能改,而公面消费者拿到的正是这个实例(本仓已定谳的病形,同 `RESUME_RETRY_LATER_CODES`)。
72
- */
73
- export declare const AUTO_MODE_BREAKER_CAUSES: readonly string[];
74
56
  /** 「分类器这次跑不了」的事实(只有成因一格 —— 它是显示元数据,不是裁决位)。 */
75
57
  export interface ClassifierUnavailableView {
76
- /** {@link AUTO_MODE_UNAVAILABLE_CAUSES} 之一。 */
58
+ /** {@link AUTO_MODE_UNAVAILABLE_CAUSES} 之一(**开集读**:比本端新的引擎的新词照收)。 */
77
59
  cause: string;
78
60
  }
79
61
  /**
@@ -83,7 +65,7 @@ export interface ClassifierUnavailableView {
83
65
  * 🔴 **两处同一只读器**:`AskRequest.classifierUnavailable` 与
84
66
  * `PendingAction.tool_approval.classifierUnavailable`(durable park 行的孪生位)**键路同形**,
85
67
  * 所以一只读器吃两处 —— 各写一份就是两份台账各漂各的,本包一贯要根治的形。
86
- * 🔴 **`cause` 按开集读 + 一条派生的排除**(**0.64.2 订正,行为面**;修前是「unavailable 闭集」):
68
+ * 🔴 **`cause` 按开集读 + 一条有出处的排除**(**0.64.2 订正,行为面**;修前是「unavailable 闭集」):
87
69
  * · **非空串即收** —— 词表的属主是 core,而 server 侧除了「非空串」之外**不做词表校验**
88
70
  * (engine fixture `approval-card.js:240` 的 `z.string().min(1)`)。在这里抄一份闭集表,只会在
89
71
  * core 加词那天把一个**合法**值判没,而丢掉的正是「这次不可用是**新出现的那一类**」这条信息
@@ -91,8 +73,10 @@ export interface ClassifierUnavailableView {
91
73
  * 修前那条路是可复现的:一台比本端新的引擎发一个新成因词 ⇒ 卡上那一行**整段消失**,而
92
74
  * {@link classifierUnavailableDetail} 早就为这一形备好了兜底句(「a word newer than this client」)
93
75
  * —— 那句话在修前**永远不可达**,这本身就是判据写错了的证据。
94
- * · **{@link BREAKER_ONLY} 里的词仍判缺席** —— 今天只有 `parse_error`。这不是「表外词一律拒」,
95
- * 而是一条**有出处的排除**:core 顶注逐字「`parse_error` stamps nothing」,读它就是替引擎编一件
76
+ * ⚠️ 0.65.0 `breaker_open` 也走这一条(core 7.12.0 已删该词)—— **旧引擎的合法帧照旧过境**,
77
+ * 只是本端不再为它自铸一句解释。
78
+ * · **{@link NEVER_STAMPED_CAUSES} 里的词仍判缺席** —— 今天只有 `parse_error`。这不是「表外词一律拒」,
79
+ * 而是一条**有出处的排除**:core 的判据轴逐字说它「跑了并且答了」,读它就是替引擎编一件
96
80
  * 它明说没发生的事。0.63.0 起的黑盒判据 G-19 钉的正是这一条,行为**一字未变**。
97
81
  * 🔴 **只交 `cause` 一格**:顺手把整只 ask 的别的键带出来会长成第二份 ask 读面。
98
82
  * 🔴 **端不许在自己那一侧再补一张闭集表**:未知词的正解是渲兜底句(措辞铸点已经有),不是不渲。
@@ -104,34 +88,6 @@ export declare function classifierUnavailableOf(ask: unknown): ClassifierUnavail
104
88
  * 🔴 **按自有属性查表**(与本包其余措辞铸点同一条纪律):`Object.freeze` 不移除原型,裸下标会让
105
89
  * 一个来自 wire 的 `constructor` / `toString` 命中 `Object.prototype` 上的**函数**并被当成一句话。
106
90
  * 🔴 表外词 / 坏值 ⇒ 一句**兜底**:仍然告诉用户「这只 ask 是分类器那条腿引出来的」,但**不冒充**
107
- * 四句里的任何一句(成因读不懂 ≠ 成因是别的什么);原样带上那个词供运维追问上游。
91
+ * 三句里的任何一句(成因读不懂 ≠ 成因是别的什么);原样带上那个词供运维追问上游。
108
92
  */
109
93
  export declare function classifierUnavailableDetail(cause: unknown): string;
110
- /**
111
- * 这个**会话**上最近一次熔断闩合上的记录(core `AutoModeBreakerTrip` 逐键镜像)。
112
- * 🔴 键名与形的属主是 core;门对实装 devDep core 的 `dist/core/auto-mode.d.ts` 逐键对账。
113
- */
114
- export interface ClassifierBreakerView {
115
- /** 闩合上的时刻(epoch ms;已按 `Date` 值域收窄,渲染路径拿它去 `toISOString()` 不会抛)。 */
116
- openedAtMs: number;
117
- /** 压垮它的那一次失败的成因(core `AutoModeBreakerCause`;🔴 **开集读** —— 词的属主在 core)。 */
118
- lastCause: string;
119
- /** 合闩时的连续失败次数(正整数;阈值,或并发轮次下更多)。 */
120
- failures: number;
121
- /** 哪一条腿的裁决器合的闩(运维追查用;不进那句话,所以缺席只丢自己)。 */
122
- runId?: string;
123
- }
124
- /**
125
- * `wiring_manifest.autoMode` → 熔断记录;没有这条事实 / 形坏 ⇒ `undefined`,绝不抛。
126
- *
127
- * 🔴 **三键承重、一键可缺**:`openedAtMs` / `lastCause` / `failures` 是那句话的承重物
128
- * (「什么时候、因为什么、几次」),缺任一 ⇒ **整段缺席**(半句没有出口的话不如不渲);
129
- * `runId` 不进那句话 ⇒ 缺席只丢它自己。这与本包 `projectMcpSection` 的「必需两座 + 其余可缺」
130
- * 同一条判据。
131
- * 🔴 **`lastCause` 开集读**:表外词照收 —— 词的属主在 core,抄一张表在这里只会把 core 加的新词
132
- * 吞成缺席(与 `WiringManifestMcpEntry.errorCode` 同规)。消费端按具名词写的 `switch` 必须带
133
- * `default` 臂;本模块的措辞铸点自带兜底句。
134
- * 🔴 **按自有属性读**:一只来自 wire 的对象可以带原型;`Object.create({openedAtMs:1,…})` 上那些
135
- * 值不是这条帧带来的事实。
136
- */
137
- export declare function classifierBreakerOf(autoMode: unknown): ClassifierBreakerView | undefined;