@sema-agent/client-core 0.16.2 → 0.17.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/README.md CHANGED
@@ -23,7 +23,7 @@ Renamed from **`@sema-agent/wire-cc-adapter`** (0.1.x, deprecated — see *Migra
23
23
 
24
24
  ## Scope
25
25
 
26
- **Version:** 0.16.2
26
+ **Version:** 0.17.1
27
27
 
28
28
  - **Today** — the adapter seam, the whole `adapt()` pipeline (all 14 A-layer arms plus the
29
29
  B/D/E tool-card layers), the notification/caps/model families, the adapter kernel (stream driver
@@ -53,7 +53,7 @@ Renamed from **`@sema-agent/wire-cc-adapter`** (0.1.x, deprecated — see *Migra
53
53
  `SseIdleError`, `probeHealth`, `APIError` and `TaskStopConflictError` are imported as values in
54
54
  five modules, and the browser bundle really bundles the SDK through (the portability guard would
55
55
  exit 3 rather than quietly mark it external).
56
- - The declared floor is `>=4.1.0`, and it is *witnessed*: the guard checks that an actually
56
+ - The declared floor is `>=6.3.0`, and it is *witnessed*: the guard checks that an actually
57
57
  installed SDK at that line still exports every value-level symbol this package imports and still
58
58
  declares `TaskStats.costMicroUsd` (the key `costOrNull` reads). A floor nobody ever ran is a
59
59
  promise, not a contract.
@@ -63,27 +63,27 @@ function assertNeverArm(_ev) {
63
63
  * 旧写法 `if (msg)` 在新返回型上恒真(对象永远 truthy),所以这是**必须点名**的一类改动。
64
64
  */
65
65
  export function eventToSdkMessage(ev, ctx) {
66
- // service 1.75 — `workflow_complete`: an out-of-band background-workflow completion push, ridden onto
67
- // the session's next stream open (BEFORE the run's own events). Projects to a NEUTRAL internal arm;
68
- // the upstream bridge turns it into CC's `<task-notification>` injection.
69
- // 🔴 REF-CC-060(xlate-06)真码核对:P1 镜头按注释判「五条 raw 臂里四条已入 union」,逐字核
70
- // `@sema-agent/sdk` 3.0.0 的 `AgentEvent` 后**部分证伪** —— task_notification / diagnostics /
71
- // steering_injected / workspace_changed 四条确已入 union(已搬进下方 switch 成为正式 case),
72
- // 而 `workflow_complete` **至今不在 union 里**(与那几条注释自称的相反)。故本臂是**唯一**
73
- // 保留的 raw 预分派臂,`raw` 变量也随之收窄到这一条。到期复核:每次 SDK major 提货时重查
74
- // (判据 = SDK d.ts 里出现 `type: "workflow_complete"`),入 union 当天搬进 switch。
75
- const raw = ev;
76
- if (raw.type === 'workflow_complete') {
77
- if (typeof raw.runId !== 'string')
78
- return dropped('malformed', 'workflow_complete');
79
- return projected(stamp(ctx, armBody({
80
- type: 'workflow_complete',
81
- runId: raw.runId,
82
- status: raw.status === 'failed' ? 'failed' : 'completed',
83
- summary: typeof raw.summary === 'string' ? raw.summary : '',
84
- })));
85
- }
86
66
  switch (ev.type) {
67
+ // service 1.75 — `workflow_complete`: an out-of-band background-workflow completion push, ridden onto
68
+ // the session's next stream open (BEFORE the run's own events). Projects to a NEUTRAL internal arm;
69
+ // the upstream bridge turns it into CC's `<task-notification>` injection.
70
+ // 🔴 **到期复核已兑现(sdk 6.2.0 提货批,2026-08-04)**:本臂此前是 switch 之前的**唯一** raw
71
+ // 预分派臂,理由写着「`workflow_complete` 至今不在 union 里……入 union 当天搬进 switch」。
72
+ // sdk 6.2.0 的 TR-7 批把它连同 question/elicitation 族一并加进了 `AgentEvent` union
73
+ // (判据 = `dist/events.d.ts` 出现 `type: "workflow_complete"`),故按原定计划搬进来 ——
74
+ // raw 预分派臂与 `raw` 变量一并退役,本函数从此**每一条臂都受 B5 编译期穷举保护**。
75
+ // 语义不变:至少一次投递(两条腿同开可能双发),消费方按 `runId` 去重(notifications 台账干这件事)。
76
+ case 'workflow_complete': {
77
+ if (typeof ev.runId !== 'string' || ev.runId.length === 0) {
78
+ return dropped('malformed', 'workflow_complete');
79
+ }
80
+ return projected(stamp(ctx, armBody({
81
+ type: 'workflow_complete',
82
+ runId: ev.runId,
83
+ status: ev.status === 'failed' ? 'failed' : 'completed',
84
+ summary: typeof ev.summary === 'string' ? ev.summary : '',
85
+ })));
86
+ }
87
87
  // CS-1 §2.2 — turn-aggregated answer text (durable). Drop raw block array.
88
88
  case 'text':
89
89
  return projected(assistantArm(ctx, [{ type: 'text', text: ev.text }]));
@@ -148,11 +148,23 @@ export function eventToSdkMessage(ev, ctx) {
148
148
  // AgentToolResult.details on the wire; not yet in the SDK tool_end arm — raw defensive read).
149
149
  // Rides the neutral arm so the bridge can render the REAL rich card (diff / stdout panels).
150
150
  const structured = ev.structured;
151
+ // core 5.10.0 — 被**中断**的 call 的合成 `tool_end` 终于带机器码(`interrupted_never_started` /
152
+ // `interrupted_outcome_unknown`,来自持久化的 `details.errorKind`)。此前这一帧只有
153
+ // `isError:true` 而无正文、无码 ⇒ 从帧渲染工具输出的消费者看到空结果,还只能去正则匹配散文
154
+ // 才知道是哪一种中断。`output`/`label` 本臂本来就透传,缺的是这个**判别位**。
155
+ // 🔴 开集 + 防御读:`tool_end.errorCode` 不在 sdk 6.2.0 的 tool_end 臂类型里(SDK 面尚未跟上
156
+ // core 5.10.0),故按 unknown 读;两员之外的未来码**照样原样透传**,窄化只做「是不是串」,
157
+ // 绝不按识别表过滤(过滤 = 替引擎决定哪些原因配得上被看见)。判别用
158
+ // `engineErrorCodes.isInterruptedToolEndCode`,不要在消费点重新写字面比较。
159
+ const toolEndErrorCode = ev.errorCode;
151
160
  return projected(stamp(ctx, armBody({
152
161
  type: 'tool_end_result',
153
162
  toolCallId: ev.toolCallId,
154
163
  toolName: ev.toolName,
155
164
  isError: ev.isError,
165
+ ...(typeof toolEndErrorCode === 'string' && toolEndErrorCode.length > 0
166
+ ? { errorCode: toolEndErrorCode }
167
+ : {}),
156
168
  // P0-2([1725] 清单A)— `tool_end.label` 同 tool_start 待遇(server trace/project.js:61 白名单
157
169
  // 本体产出)。此臂是壳内部侧信道(不进 transcript),所以这里带着无 provider 风险;
158
170
  // tool_start 先到时 bridge 已登记过,这条是补位(只有 tool_end 带 label 的场景)。
@@ -329,7 +341,35 @@ export function eventToSdkMessage(ev, ctx) {
329
341
  return nothing('usage_only');
330
342
  // §2.9 — suspended 的 HITL 登记是 run driver 的活(hitlBridge.observe),本层不投影。
331
343
  case 'suspended':
344
+ // ── sdk 6.2.0 TR-7 新入 union 的 HITL 带外族(2026-08-04 提货批)────────────────────────
345
+ // `question` / `question_complete`(AskUserQuestion 的开问/收尾)与 `elicitation` /
346
+ // `elicitation_complete`(入站 MCP server 向用户要输入)是**对话框覆盖层**的词汇,不是
347
+ // transcript 的。它们此前只走 live 腿的 SSE `event:` 名分派(`liveQuestionStore.ts` 的 demux
348
+ // 口),6.2.0 把 durable 腿的重放形也纳入了 union —— 于是它们第一次**流经本函数**。
349
+ // 🔴 处置理由与 `suspended` 同族、也与本包既有分工一致:HITL 的挂起/应答由
350
+ // `hitl/hitlBridge.ts` 的 `observe()` 与 `liveQuestionStore` 负责,本切片(CC transcript
351
+ // 投影)对它们**没有对位渲染物** —— 硬投一个 transcript 形就是替覆盖层编一条假消息。
352
+ // 所以走 `hitl_out_of_slice`(可分辨的三态之一),不是 `dropped`(那是「我看不懂」)。
353
+ // 🔴 记档给下一棒(诚实缺席,不是无事发生):durable 重放腿上,一条被重放的 `question` 今天
354
+ // **不会**再打开覆盖层 —— 覆盖层的入口是 liveQuestionStore 的 live demux 写口,而不是本函数。
355
+ // 这在交互 REPL 上无损(live 腿原路不变),在「断线后按 Last-Event-ID 续读」的场景下是一个
356
+ // 真缺口。补它属**行为面**改动(要先答「重放一条已经过期 5min TTL 的问题该不该弹窗」),
357
+ // 按宪法三问单独走,不在本提货批里顺手加 —— 但缺口写在这里,不留白。
358
+ case 'question':
359
+ case 'question_complete':
360
+ case 'elicitation':
361
+ case 'elicitation_complete':
332
362
  return nothing('hitl_out_of_slice');
363
+ // ── sdk 6.2.0 CB-1/TR-6 `error` 臂(2026-08-04 提货批)──────────────────────────────────
364
+ // 🔴 **名字骗人,别当终态**:这是流的 15 分钟帽帧(server `sse-log.ts`),帧自己就说
365
+ // 「run 仍然活着」。终态臂只有 `done` / `failed`。把它渲成终态 = 把一条还在跑的任务判死。
366
+ // 正确处置 = 按 `Last-Event-ID` 重连续读,而那件事 SDK 的 `runs.events` 车道内部就做了
367
+ // (本包侧的坐标 = `headlessReconnectWire.ts` 的 STREAM_MAX_DURATION 段)。
368
+ // ⇒ 本切片无对位渲染物,也**绝不终止**调用方的循环(返回 none,不是 terminal)。
369
+ // 🔴 `errorCode` 开集(已知 `STREAM_MAX_DURATION`):未知码同样按「可重连的流控信号」降级,
370
+ // 不要按成员判死 —— 词表见 `engineErrorCodes.ts`。
371
+ case 'error':
372
+ return nothing('not_in_slice');
333
373
  // CS-10 / CS-11 — terminals are projected by terminalToSdkResult.
334
374
  case 'done':
335
375
  case 'failed':
@@ -1,5 +1,8 @@
1
1
  import { stamp } from '../types.js';
2
2
  import { toCcModelUsage } from './turnUsageToModelUsage.js';
3
+ // G1 去字面化(2026-08-04):到限/结构化输出/rewind 三族的码字面收编进单一真源,本文件只 import。
4
+ // 开集纪律不变——下面三个集合仍是**识别表**,`subtypeForErrorCode` 的 default 臂才是开集的兑现处。
5
+ import { LIMITS_MAX_COST_EXCEEDED, LIMITS_MAX_TOKENS_EXCEEDED, LIMITS_MAX_TURNS_EXCEEDED, LIMITS_MAX_WALLTIME_EXCEEDED, OUTPUT_INVALID, isRewindFamilyCode, } from '../../engineErrorCodes.js';
3
6
  /**
4
7
  * Flatten TaskStats → the CC NonNullableUsage placeholder.
5
8
  * 🔴 REF-CC-055(xlate-01):`failedToSdkResult` 此前手抄了一份「五键全零」的字面量,那不是
@@ -133,22 +136,20 @@ function degradedOf(r) {
133
136
  // 退役批(server 6.0.0 捆 core 5.8.0 发车,2026-08-04):三个集合里的 5.7 旧码成员
134
137
  // (`budget.exceeded`/`budget.precall`/`limit.max_turns`)已删,只留新码。放成集合而不是就地 `||`
135
138
  // 链的原因不变:词表变动集中在一处可数,不必满文件找 `===` 比较。
139
+ // G1 去字面化(2026-08-04):**码的字面**已收编进 `engineErrorCodes.ts`(单一真源),这里留的是
140
+ // 「哪些码映射到哪个 CC subtype」的**映射事实** —— 那是本文件的职责,不是词表的。
136
141
  /** 成本到限 → CC `error_max_budget_usd`(唯一一个 CC 有专词的预算轴)。 */
137
- const COST_EXCEEDED_CODES = new Set([
138
- 'limits.max_cost_exceeded',
139
- ]);
142
+ const COST_EXCEEDED_CODES = new Set([LIMITS_MAX_COST_EXCEEDED]);
140
143
  /** 轮数到限 → CC `error_max_turns`。 */
141
- const TURNS_EXCEEDED_CODES = new Set([
142
- 'limits.max_turns_exceeded',
143
- ]);
144
+ const TURNS_EXCEEDED_CODES = new Set([LIMITS_MAX_TURNS_EXCEEDED]);
144
145
  /**
145
146
  * 「截断」族到限码:CC 4 词表里**没有**对应词(token 账不是美元账、墙钟停不是轮数耗尽),
146
147
  * 所以 subtype 落诚实兜底词,真实语义靠 errorCode 透传;且这两种停因都可能已产出正文
147
148
  * ([2489] 对 max_tokens 明记 result 带正文;墙钟停是既有的写出窗 salvage 语义)⇒ 共用 salvage 腿。
148
149
  */
149
150
  const CUTOFF_EXCEEDED_CODES = new Set([
150
- 'limits.max_tokens_exceeded',
151
- 'limits.max_walltime_exceeded',
151
+ LIMITS_MAX_TOKENS_EXCEEDED,
152
+ LIMITS_MAX_WALLTIME_EXCEEDED,
152
153
  ]);
153
154
  /**
154
155
  * 🔴 **errorCode → CC subtype 的唯一映射点**。收编前这段 `||`/三元链在 done{failed} 与
@@ -165,8 +166,11 @@ function subtypeForErrorCode(code) {
165
166
  return 'error_max_turns';
166
167
  // assemble-result.js:58 该 code 的语义字面就是 CC 这词:「did not produce a valid structured
167
168
  // output within the retry limit」。
168
- if (code === 'output.invalid')
169
+ if (code === OUTPUT_INVALID)
169
170
  return 'error_max_structured_output_retries';
171
+ // 🔴 开集兑现处:未知/未来码(core 5.10.0 的 `config.*` 拒绝族、`usage.window_exhausted`、
172
+ // `env.lifetime_expired` 都落这里)一律诚实兜底词 —— CC 四词表里没有它们的对位词,真实语义
173
+ // 靠上面 `errorCode` 原样透传辨认。绝不为了「有个更像的词」把它们塞进任何一个已知 subtype。
170
174
  return 'error_during_execution';
171
175
  }
172
176
  /**
@@ -293,7 +297,7 @@ export function doneToSdkResult(ev, ctx) {
293
297
  return errorResult(ctx, {
294
298
  ...errorBase,
295
299
  subtype: failedSubtype,
296
- ...cutoffParts(r, rr.errorMessage, rr.errorCode === 'limits.max_tokens_exceeded'
300
+ ...cutoffParts(r, rr.errorMessage, rr.errorCode === LIMITS_MAX_TOKENS_EXCEEDED
297
301
  ? 'run exhausted its token budget'
298
302
  : 'run hit its wall-clock limit', rr.errorCode),
299
303
  });
@@ -305,9 +309,7 @@ export function doneToSdkResult(ev, ctx) {
305
309
  // rewind_snapshot.unresolvable 骑在 done{status:"failed"} 帧的 errorCode 上,非 `failed` 帧)——
306
310
  // 与 failedToSdkResult 的同款 append 对齐,message 后附 code 便于对账。
307
311
  errors: [
308
- typeof rr.errorCode === 'string' &&
309
- (rr.errorCode.startsWith('resume_at.') || rr.errorCode.startsWith('rewind_snapshot.')) &&
310
- rr.errorMessage
312
+ isRewindFamilyCode(rr.errorCode) && rr.errorMessage
311
313
  ? `${rr.errorMessage} (${rr.errorCode})`
312
314
  : (rr.errorMessage ?? 'run failed'),
313
315
  ],
@@ -437,9 +439,7 @@ export function failedToSdkResult(ev, ctx) {
437
439
  // resume_at.before_root_unsupported / rewind_snapshot.unresolvable)透传给用户 —— 这些是
438
440
  // 用户可自解的操作性错误(选错目标/回退过根/快照缺失),code 附在 message 后便于对账。
439
441
  errors: [
440
- typeof ev.errorCode === 'string' &&
441
- (ev.errorCode.startsWith('resume_at.') || ev.errorCode.startsWith('rewind_snapshot.')) &&
442
- ev.errorMessage
442
+ isRewindFamilyCode(ev.errorCode) && ev.errorMessage
443
443
  ? `${ev.errorMessage} (${ev.errorCode})`
444
444
  : (ev.errorMessage ?? ev.errorCode ?? 'run failed'),
445
445
  ],
@@ -2,13 +2,14 @@ import { eventSeq, } from './types.js';
2
2
  import { eventToSdkMessage, turnEndUsage } from './downstream/eventToSdkMessage.js';
3
3
  import { terminalToSdkResult } from './downstream/terminalToSdkResult.js';
4
4
  import { publishSubagentContentEvent } from '../subagentContentStore.js';
5
- /** 本文件发的 chrome 事件全在 leader lane(子代内容在上面就被 divert 走了)。 */
6
- const MAIN = { lane: 'main' };
7
5
  /**
8
6
  * 409 session-busy 拒绝的 **canonical errorCode**([2377]C-1,server main `049ff2c`,随 5.0.0 发)。
9
7
  * 引擎把它 stamp 在 `done{status:'failed'}` / `failed` 终帧上,壳据此**结构判读**,不再读人话。
8
+ * G1 去字面化(2026-08-04):字面收编进 `engineErrorCodes.ts` 单一真源,本文件只 import。
10
9
  */
11
- const ACTIVE_RUN_BUSY_ERROR_CODE = 'conflict.session_active_run';
10
+ import { ACTIVE_RUN_BUSY_ERROR_CODE } from '../engineErrorCodes.js';
11
+ /** 本文件发的 chrome 事件全在 leader lane(子代内容在上面就被 divert 走了)。 */
12
+ const MAIN = { lane: 'main' };
12
13
  /**
13
14
  * 判别一个**原始 AgentEvent** 是不是 409 active-run 拒收终帧;非 busy ⇒ null。
14
15
  * 判据=结构两腿(canonical `errorCode` 优先 → `activeTaskId` 在场);人话文案腿已随 #117 提货
@@ -0,0 +1,119 @@
1
+ /**
2
+ * src/engineErrorCodes.ts — **引擎 wire 码词表的单一真源**(G1 去字面化,2026-08-04,
3
+ * core 5.10.0 / sdk 6.2.0 消费半场)。
4
+ *
5
+ * ── 为什么要有这个文件 ────────────────────────────────────────────────────────────────────────
6
+ * 引擎的 `errorCode` 是**机器码**(结构位),壳的四条判别链都吃它:终帧 subtype 映射
7
+ * (`adapter/downstream/terminalToSdkResult.ts`)、409 busy 判别(`adapter/runStream.ts`)、
8
+ * `stop.*` 五形分类(`subagent/engineTaskHandleWire.ts`)、限额面文案(`limitsWire.ts`)。
9
+ * 收编前这些码以**裸字面量**散在五个文件、二十余处:core 每次词表迁移(5.8.0 的 `budget.*` →
10
+ * `limits.*`、5.10.0 的 `config.*` 第四波)都要满仓 grep `===` 比较,漏一处的后果不是报错而是
11
+ * **判别链静默走错臂** —— 这正是 [dep-bump-follow-on-checklist] 那类跟车事故的载体。
12
+ *
13
+ * ── 🔴 开集纪律(本文件最重要的一条)──────────────────────────────────────────────────────────
14
+ * **wire 的 `errorCode` 是开集**:引擎完全可能比本包新一版,发一个这里没有的码。所以:
15
+ * · 本文件的 `ReadonlySet` / 前缀谓词一律是**识别表**(recognition table),回答的是
16
+ * 「我认不认得这个码」,**绝不是**「合法码只有这些」;
17
+ * · 消费点的 `switch` 必须留 `default`、`if` 必须留 else 臂,未知码一律**原样透传**
18
+ * + 落诚实兜底臂,绝不塌进任何一个已知形(塌进去 = 替 server 编了一个它没说的原因);
19
+ * · 唯一的**闭集**是 {@link ModelFallbackReason} —— 它是 core 亲自声明的闭集单成员
20
+ * ([2577] 定谳),加成员 = BREAKING 清单行,所以它写成 string literal union 而不是 `string`。
21
+ * 这条例外**只此一处**,别照抄到别的码上。
22
+ *
23
+ * ── 命名与来源 ────────────────────────────────────────────────────────────────────────────────
24
+ * 每一族下方注出它的 core 版本与出处帖号;码的字面**永远以引擎为准**,本文件只是镜子。
25
+ * 词表变动集中在一处可数(与 `terminalToSdkResult.ts` 那三个集合原本的立意同),提货批 diff 里
26
+ * 一眼能看出「这一版引擎加了哪几个词」。
27
+ */
28
+ /** token 预算到限。`result` 可能带已产出正文([2489] 明记)。 */
29
+ export declare const LIMITS_MAX_TOKENS_EXCEEDED = "limits.max_tokens_exceeded";
30
+ /** 成本预算到限(唯一一个 CC 有专门 subtype 的预算轴)。 */
31
+ export declare const LIMITS_MAX_COST_EXCEEDED = "limits.max_cost_exceeded";
32
+ /** 轮数预算到限。 */
33
+ export declare const LIMITS_MAX_TURNS_EXCEEDED = "limits.max_turns_exceeded";
34
+ /** 墙钟预算到限(5.8.0 起是**响亮终局** `status:'failed'`;`status:'timeout'` 该终态词整体退役)。 */
35
+ export declare const LIMITS_MAX_WALLTIME_EXCEEDED = "limits.max_walltime_exceeded";
36
+ /** 部署级 token 治理窗耗尽。**唯一携带可执行等待量的停因**,与 `retryAfterMs` 配对到货。
37
+ * 5.10.0 起:治理窗与环境寿命同一轮边界到期时,**窗赢**(此前 env-first 报告把它丢了)。 */
38
+ export declare const USAGE_WINDOW_EXHAUSTED = "usage.window_exhausted";
39
+ /** 环境寿命到期(平台侧)。与上一条同轮到期时**让位**给它。 */
40
+ export declare const ENV_LIFETIME_EXPIRED = "env.lifetime_expired";
41
+ /** `TaskRequest.limits` 各门(runTeamDiscussion 累计额 / workflow 治理 TRUSTED 半 /
42
+ * `resourceSuspend` 四键)拒绝不可求值的值。 */
43
+ export declare const CONFIG_LIMIT_INVALID = "config.limit_invalid";
44
+ /** `StrategyStore.find(scope, query, limit)` 的 limit 非「非负整数或 Infinity」。 */
45
+ export declare const CONFIG_STRATEGY_FIND_LIMIT_INVALID = "config.strategy_find_limit_invalid";
46
+ /** `StrategyStore.prune` / `InMemoryStrategyStore` 构造的容量上限非非负整数(含点名拒 `Infinity`
47
+ * —— 容量帽可以放宽,不可以关掉)。 */
48
+ export declare const CONFIG_STRATEGY_MAX_SIZE_INVALID = "config.strategy_max_size_invalid";
49
+ /** 未知的 limits 键(5.8.0 起:同时发新旧两代键会在这里当场失败,所以写面只发单一新形)。 */
50
+ export declare const CONFIG_LIMIT_UNKNOWN_KEY = "config.limit_unknown_key";
51
+ /**
52
+ * 已知的配置拒绝码(**识别表,非白名单**)。判「这是不是一条配置拒绝」请用
53
+ * {@link isConfigRefusalCode} —— 它按 `config.` 前缀判,未来新成员自动落进来。
54
+ */
55
+ export declare const CONFIG_REFUSAL_CODES: ReadonlySet<string>;
56
+ /** `config.` 前缀谓词 —— **开集**判别:5.10.0 之后每一波「不许静默折叠」都会往这一族加词,
57
+ * 按前缀判的消费点不必跟车,按成员判的必须跟车。缺席/空串 ⇒ false。 */
58
+ export declare function isConfigRefusalCode(code: string | undefined): boolean;
59
+ /** 该 call 从未真正开始执行(可安全重发)。 */
60
+ export declare const TOOL_END_INTERRUPTED_NEVER_STARTED = "interrupted_never_started";
61
+ /** 该 call 的结局未知(持有它的进程死了 —— 副作用可能已经发生,**不可**当作没跑过)。 */
62
+ export declare const TOOL_END_INTERRUPTED_OUTCOME_UNKNOWN = "interrupted_outcome_unknown";
63
+ /**
64
+ * 中断码识别表(**开集**:`tool_end.errorCode` 整体是开集,未来可能有第三种中断成因)。
65
+ * 🔴 两员**语义不对称**,别合并处置:`never_started` 可以重发,`outcome_unknown` 不可以。
66
+ */
67
+ export declare const TOOL_END_INTERRUPTED_CODES: ReadonlySet<string>;
68
+ /** 这条 `tool_end` 是不是「中断留下的合成收口帧」。未知码 ⇒ false(开集:不认得就不认得,
69
+ * 绝不猜)。 */
70
+ export declare function isInterruptedToolEndCode(code: string | undefined): boolean;
71
+ /** 409 session-busy 的 canonical 码([2377]C-1,server main `049ff2c`,随 5.0.0 发)。 */
72
+ export declare const ACTIVE_RUN_BUSY_ERROR_CODE = "conflict.session_active_run";
73
+ /** 仲裁店不可达 ⇒ **真相未知**(core 1.397 三分的第三形;塌进 `not_landed` 是诚实缺陷)。 */
74
+ export declare const STOP_PARK_ARBITER_UNREACHABLE = "stop.park_arbiter_unreachable";
75
+ /** park 的 resume 赢了这场竞争。 */
76
+ export declare const STOP_PARK_RESUME_WON = "stop.park_resume_won";
77
+ /** kill 没落地。 */
78
+ export declare const STOP_NOT_LANDED = "stop.not_landed";
79
+ /** 该任务不在本副本。 */
80
+ export declare const STOP_NOT_LOCAL = "stop.not_local";
81
+ /** 该任务已 park。 */
82
+ export declare const STOP_PARKED = "stop.parked";
83
+ /**
84
+ * 兜底**字面认码**用的已知集(typed 错误缺席时才走这条回落腿)。
85
+ * 🔴 **顺序即语义**:长码优先 —— `stop.parked` 是 `stop.park_resume_won` 的前缀,顺序反了
86
+ * `detail.includes()` 会让短码抢走长码的命中。数组(有序)不是 Set(无序),这是判据的一部分。
87
+ */
88
+ export declare const STOP_CONFLICT_CODES: readonly ["stop.park_arbiter_unreachable", "stop.park_resume_won", "stop.not_landed", "stop.not_local", "stop.parked"];
89
+ /** `outputSchema` 任务在重试上限内没能产出合法结构化输出(语义字面就是 CC 那个 subtype 的话)。 */
90
+ export declare const OUTPUT_INVALID = "output.invalid";
91
+ /**
92
+ * 用户**可自解**的操作性错误的码前缀(选错回退目标 / 回退过根 / 快照缺失)。
93
+ * 消费点把 code 附在 message 后便于对账 —— 这一族是「你的操作有问题」,不是「引擎坏了」。
94
+ * 前缀形(不是成员形)= 开集:这一族里每加一个新码,判别自动跟上。
95
+ */
96
+ export declare const REWIND_ERROR_CODE_PREFIXES: readonly ["resume_at.", "rewind_snapshot."];
97
+ /** 该码是否属 rewind/resume 可自解族。缺席 ⇒ false。 */
98
+ export declare function isRewindFamilyCode(code: string | undefined): boolean;
99
+ /**
100
+ * 15 分钟流帽(server `src/http/sse-log.ts`)。
101
+ * 🔴 **到达 ≠ run 死了** —— 帧自己就说「run 仍然活着」。正确处置 = 按 `Last-Event-ID` 重连续读
102
+ * (`runs.events` 车道由 SDK 内部自动做)。把它当终态渲染会把一条还在跑的任务在 UI 上判死。
103
+ * `errorCode` 整体开集:未知码按「可重连的流控信号」通用降级,不要按成员判死。
104
+ */
105
+ export declare const STREAM_MAX_DURATION = "STREAM_MAX_DURATION";
106
+ /**
107
+ * 🔴 **本文件唯一的闭集**([2577] core 亲自定谳:"closed set, single member today")。
108
+ * 语义 = 请求的模型词**没绑上**(没有 roster / 不在 roster 上),子代跑在继承来的默认模型上。
109
+ * 这**不是错误**:回落行为本身没变,只是现在说出来了。缺席 = 正常绑定,或压根没请求过模型词。
110
+ *
111
+ * 🔴 **加成员 = BREAKING 清单行** —— 所以此处写 string literal union 而不是 `string`:
112
+ * 消费端可以安全地按闭集分臂,而下一次 core 加成员时本包的类型面会**当场编译红**,
113
+ * 逼提货批显式处理,而不是让一个没人认得的原因静默落进 default 臂。
114
+ */
115
+ export type ModelFallbackReason = 'inherit_no_tier_binding';
116
+ /** {@link ModelFallbackReason} 的唯一成员(今天)。 */
117
+ export declare const MODEL_FALLBACK_INHERIT_NO_TIER_BINDING: ModelFallbackReason;
118
+ /** wire 上的 `modelFallback` 窄化:是闭集成员才认,别的一律当缺席(不认得的原因 ≠ 编一个)。 */
119
+ export declare function asModelFallbackReason(v: unknown): ModelFallbackReason | undefined;
@@ -0,0 +1,147 @@
1
+ /**
2
+ * src/engineErrorCodes.ts — **引擎 wire 码词表的单一真源**(G1 去字面化,2026-08-04,
3
+ * core 5.10.0 / sdk 6.2.0 消费半场)。
4
+ *
5
+ * ── 为什么要有这个文件 ────────────────────────────────────────────────────────────────────────
6
+ * 引擎的 `errorCode` 是**机器码**(结构位),壳的四条判别链都吃它:终帧 subtype 映射
7
+ * (`adapter/downstream/terminalToSdkResult.ts`)、409 busy 判别(`adapter/runStream.ts`)、
8
+ * `stop.*` 五形分类(`subagent/engineTaskHandleWire.ts`)、限额面文案(`limitsWire.ts`)。
9
+ * 收编前这些码以**裸字面量**散在五个文件、二十余处:core 每次词表迁移(5.8.0 的 `budget.*` →
10
+ * `limits.*`、5.10.0 的 `config.*` 第四波)都要满仓 grep `===` 比较,漏一处的后果不是报错而是
11
+ * **判别链静默走错臂** —— 这正是 [dep-bump-follow-on-checklist] 那类跟车事故的载体。
12
+ *
13
+ * ── 🔴 开集纪律(本文件最重要的一条)──────────────────────────────────────────────────────────
14
+ * **wire 的 `errorCode` 是开集**:引擎完全可能比本包新一版,发一个这里没有的码。所以:
15
+ * · 本文件的 `ReadonlySet` / 前缀谓词一律是**识别表**(recognition table),回答的是
16
+ * 「我认不认得这个码」,**绝不是**「合法码只有这些」;
17
+ * · 消费点的 `switch` 必须留 `default`、`if` 必须留 else 臂,未知码一律**原样透传**
18
+ * + 落诚实兜底臂,绝不塌进任何一个已知形(塌进去 = 替 server 编了一个它没说的原因);
19
+ * · 唯一的**闭集**是 {@link ModelFallbackReason} —— 它是 core 亲自声明的闭集单成员
20
+ * ([2577] 定谳),加成员 = BREAKING 清单行,所以它写成 string literal union 而不是 `string`。
21
+ * 这条例外**只此一处**,别照抄到别的码上。
22
+ *
23
+ * ── 命名与来源 ────────────────────────────────────────────────────────────────────────────────
24
+ * 每一族下方注出它的 core 版本与出处帖号;码的字面**永远以引擎为准**,本文件只是镜子。
25
+ * 词表变动集中在一处可数(与 `terminalToSdkResult.ts` 那三个集合原本的立意同),提货批 diff 里
26
+ * 一眼能看出「这一版引擎加了哪几个词」。
27
+ */
28
+ // ── 限额到限族(core 5.8.0 [2489] 词表迁移;5.7 的 `budget.*` / `limit.*` 已随退役批删净)────
29
+ /** token 预算到限。`result` 可能带已产出正文([2489] 明记)。 */
30
+ export const LIMITS_MAX_TOKENS_EXCEEDED = 'limits.max_tokens_exceeded';
31
+ /** 成本预算到限(唯一一个 CC 有专门 subtype 的预算轴)。 */
32
+ export const LIMITS_MAX_COST_EXCEEDED = 'limits.max_cost_exceeded';
33
+ /** 轮数预算到限。 */
34
+ export const LIMITS_MAX_TURNS_EXCEEDED = 'limits.max_turns_exceeded';
35
+ /** 墙钟预算到限(5.8.0 起是**响亮终局** `status:'failed'`;`status:'timeout'` 该终态词整体退役)。 */
36
+ export const LIMITS_MAX_WALLTIME_EXCEEDED = 'limits.max_walltime_exceeded';
37
+ // ── 停钟族(core 5.10.0「三时钟序」:usage_window > env_lifetime > stall)─────────────────────
38
+ /** 部署级 token 治理窗耗尽。**唯一携带可执行等待量的停因**,与 `retryAfterMs` 配对到货。
39
+ * 5.10.0 起:治理窗与环境寿命同一轮边界到期时,**窗赢**(此前 env-first 报告把它丢了)。 */
40
+ export const USAGE_WINDOW_EXHAUSTED = 'usage.window_exhausted';
41
+ /** 环境寿命到期(平台侧)。与上一条同轮到期时**让位**给它。 */
42
+ export const ENV_LIFETIME_EXPIRED = 'env.lifetime_expired';
43
+ // ── 配置拒绝族(core 5.10.0「不许静默折叠」第四波)────────────────────────────────────────────
44
+ // 语义:引擎拿到一个**无法求值**的旋钮值(NaN / 负数 / 分数 / 该轴不许的 Infinity)时**响亮拒**,
45
+ // 而不是折成 0 / 静默停用那道闸。这一族全部是「配置错了」,不是「跑失败了」—— CC 的四词 subtype
46
+ // 里没有对位词,故一律落诚实兜底 `error_during_execution`,真实语义靠码本身透传。
47
+ /** `TaskRequest.limits` 各门(runTeamDiscussion 累计额 / workflow 治理 TRUSTED 半 /
48
+ * `resourceSuspend` 四键)拒绝不可求值的值。 */
49
+ export const CONFIG_LIMIT_INVALID = 'config.limit_invalid';
50
+ /** `StrategyStore.find(scope, query, limit)` 的 limit 非「非负整数或 Infinity」。 */
51
+ export const CONFIG_STRATEGY_FIND_LIMIT_INVALID = 'config.strategy_find_limit_invalid';
52
+ /** `StrategyStore.prune` / `InMemoryStrategyStore` 构造的容量上限非非负整数(含点名拒 `Infinity`
53
+ * —— 容量帽可以放宽,不可以关掉)。 */
54
+ export const CONFIG_STRATEGY_MAX_SIZE_INVALID = 'config.strategy_max_size_invalid';
55
+ /** 未知的 limits 键(5.8.0 起:同时发新旧两代键会在这里当场失败,所以写面只发单一新形)。 */
56
+ export const CONFIG_LIMIT_UNKNOWN_KEY = 'config.limit_unknown_key';
57
+ /**
58
+ * 已知的配置拒绝码(**识别表,非白名单**)。判「这是不是一条配置拒绝」请用
59
+ * {@link isConfigRefusalCode} —— 它按 `config.` 前缀判,未来新成员自动落进来。
60
+ */
61
+ export const CONFIG_REFUSAL_CODES = new Set([
62
+ CONFIG_LIMIT_INVALID,
63
+ CONFIG_STRATEGY_FIND_LIMIT_INVALID,
64
+ CONFIG_STRATEGY_MAX_SIZE_INVALID,
65
+ CONFIG_LIMIT_UNKNOWN_KEY,
66
+ ]);
67
+ /** `config.` 前缀谓词 —— **开集**判别:5.10.0 之后每一波「不许静默折叠」都会往这一族加词,
68
+ * 按前缀判的消费点不必跟车,按成员判的必须跟车。缺席/空串 ⇒ false。 */
69
+ export function isConfigRefusalCode(code) {
70
+ return typeof code === 'string' && code.startsWith('config.');
71
+ }
72
+ // ── 中断族(core 5.10.0:被中断的 tool call 的合成 `tool_end` 终于带正文)──────────────────────
73
+ // 5.10.0 之前两条 reconcile 腿只把 `[INTERRUPTED]` 解释写进 transcript,事件流上只有
74
+ // `isError:true` 而无正文 ⇒ 从帧渲染工具输出的消费者看到的是**空结果**,一次调用的两个面互相矛盾。
75
+ // 现在帧上带 `output`(与所有 live 工具结果同一投影)+ `errorCode`(下面两员,来自持久化的
76
+ // `details.errorKind`)+ `label`,消费者**按码判别**而不是去正则匹配散文。
77
+ /** 该 call 从未真正开始执行(可安全重发)。 */
78
+ export const TOOL_END_INTERRUPTED_NEVER_STARTED = 'interrupted_never_started';
79
+ /** 该 call 的结局未知(持有它的进程死了 —— 副作用可能已经发生,**不可**当作没跑过)。 */
80
+ export const TOOL_END_INTERRUPTED_OUTCOME_UNKNOWN = 'interrupted_outcome_unknown';
81
+ /**
82
+ * 中断码识别表(**开集**:`tool_end.errorCode` 整体是开集,未来可能有第三种中断成因)。
83
+ * 🔴 两员**语义不对称**,别合并处置:`never_started` 可以重发,`outcome_unknown` 不可以。
84
+ */
85
+ export const TOOL_END_INTERRUPTED_CODES = new Set([
86
+ TOOL_END_INTERRUPTED_NEVER_STARTED,
87
+ TOOL_END_INTERRUPTED_OUTCOME_UNKNOWN,
88
+ ]);
89
+ /** 这条 `tool_end` 是不是「中断留下的合成收口帧」。未知码 ⇒ false(开集:不认得就不认得,
90
+ * 绝不猜)。 */
91
+ export function isInterruptedToolEndCode(code) {
92
+ return typeof code === 'string' && TOOL_END_INTERRUPTED_CODES.has(code);
93
+ }
94
+ // ── 会话冲突族 ────────────────────────────────────────────────────────────────────────────────
95
+ /** 409 session-busy 的 canonical 码([2377]C-1,server main `049ff2c`,随 5.0.0 发)。 */
96
+ export const ACTIVE_RUN_BUSY_ERROR_CODE = 'conflict.session_active_run';
97
+ // ── `runs.taskStop` 409 冲突族([1833] G13;SDK `TaskStopConflictError.errorCode` 判别)────────
98
+ /** 仲裁店不可达 ⇒ **真相未知**(core 1.397 三分的第三形;塌进 `not_landed` 是诚实缺陷)。 */
99
+ export const STOP_PARK_ARBITER_UNREACHABLE = 'stop.park_arbiter_unreachable';
100
+ /** park 的 resume 赢了这场竞争。 */
101
+ export const STOP_PARK_RESUME_WON = 'stop.park_resume_won';
102
+ /** kill 没落地。 */
103
+ export const STOP_NOT_LANDED = 'stop.not_landed';
104
+ /** 该任务不在本副本。 */
105
+ export const STOP_NOT_LOCAL = 'stop.not_local';
106
+ /** 该任务已 park。 */
107
+ export const STOP_PARKED = 'stop.parked';
108
+ /**
109
+ * 兜底**字面认码**用的已知集(typed 错误缺席时才走这条回落腿)。
110
+ * 🔴 **顺序即语义**:长码优先 —— `stop.parked` 是 `stop.park_resume_won` 的前缀,顺序反了
111
+ * `detail.includes()` 会让短码抢走长码的命中。数组(有序)不是 Set(无序),这是判据的一部分。
112
+ */
113
+ export const STOP_CONFLICT_CODES = [
114
+ STOP_PARK_ARBITER_UNREACHABLE,
115
+ STOP_PARK_RESUME_WON,
116
+ STOP_NOT_LANDED,
117
+ STOP_NOT_LOCAL,
118
+ STOP_PARKED,
119
+ ];
120
+ // ── 结构化输出族 ──────────────────────────────────────────────────────────────────────────────
121
+ /** `outputSchema` 任务在重试上限内没能产出合法结构化输出(语义字面就是 CC 那个 subtype 的话)。 */
122
+ export const OUTPUT_INVALID = 'output.invalid';
123
+ // ── rewind / resume 族(core 1.292 [833])────────────────────────────────────────────────────
124
+ /**
125
+ * 用户**可自解**的操作性错误的码前缀(选错回退目标 / 回退过根 / 快照缺失)。
126
+ * 消费点把 code 附在 message 后便于对账 —— 这一族是「你的操作有问题」,不是「引擎坏了」。
127
+ * 前缀形(不是成员形)= 开集:这一族里每加一个新码,判别自动跟上。
128
+ */
129
+ export const REWIND_ERROR_CODE_PREFIXES = ['resume_at.', 'rewind_snapshot.'];
130
+ /** 该码是否属 rewind/resume 可自解族。缺席 ⇒ false。 */
131
+ export function isRewindFamilyCode(code) {
132
+ return typeof code === 'string' && REWIND_ERROR_CODE_PREFIXES.some((p) => code.startsWith(p));
133
+ }
134
+ // ── 流控族(SDK 6.2.0 CB-1/TR-6 的 `error` 臂)───────────────────────────────────────────────
135
+ /**
136
+ * 15 分钟流帽(server `src/http/sse-log.ts`)。
137
+ * 🔴 **到达 ≠ run 死了** —— 帧自己就说「run 仍然活着」。正确处置 = 按 `Last-Event-ID` 重连续读
138
+ * (`runs.events` 车道由 SDK 内部自动做)。把它当终态渲染会把一条还在跑的任务在 UI 上判死。
139
+ * `errorCode` 整体开集:未知码按「可重连的流控信号」通用降级,不要按成员判死。
140
+ */
141
+ export const STREAM_MAX_DURATION = 'STREAM_MAX_DURATION';
142
+ /** {@link ModelFallbackReason} 的唯一成员(今天)。 */
143
+ export const MODEL_FALLBACK_INHERIT_NO_TIER_BINDING = 'inherit_no_tier_binding';
144
+ /** wire 上的 `modelFallback` 窄化:是闭集成员才认,别的一律当缺席(不认得的原因 ≠ 编一个)。 */
145
+ export function asModelFallbackReason(v) {
146
+ return v === MODEL_FALLBACK_INHERIT_NO_TIER_BINDING ? MODEL_FALLBACK_INHERIT_NO_TIER_BINDING : undefined;
147
+ }
@@ -1,22 +1,23 @@
1
1
  /**
2
- * src/sema/finalVerifyWire.ts — headless `-p` finalVerification wire (TB2.0 反馈批 A · P1-1, 2026-07-15):
3
- * stamp the ENGINE-EXISTING `TaskRequest.finalVerification` field on the headless `-p` path, DEFAULT ON.
2
+ * src/sema/finalVerifyWire.ts — headless `-p` finalVerification wire(TB2.0 反馈批 A · P1-1 建,
3
+ * 2026-07-15 DEFAULT ON;**clay 裁定 2026-08-04 翻面 DEFAULT OFF**,[2561]→[2562]/[2563] 案)。
4
4
  *
5
- * WHY: TB2.0 无人值守回归里 14/19 题 agent 自认完成判 0(写完不验/即兴自测即收尾)。引擎的
6
- * finalVerification stop-gate(core runtask.js:1429-1493 —— 最多追加 2 条 '[final verification]'
7
- * system-reminder,wroteThisRun && 非 structured-output && 未贴 maxTurns 才触发)+ server wire
8
- * (http/server.js:3711 校验 / main.js:1383 转发)早已在线,壳侧从未 stamp——本模块补上这最后一段 wire。
5
+ * 为什么默认关(裁定理由,按案情留档):机制是 TB 应试期为「模型自吹已完成」建的遗产;core
6
+ * 本就 opt-in([2562]:默认开是本消费层的选择,消费层持有重估义务);实锤=git-multibranch 一题
7
+ * 已构建出会通过验收的正确终态,终验注入后模型用自造哨兵串把正确产出覆盖坏(reward 1→0),注入
8
+ * 文案的两条护栏无代码强制力。配套的时间预算感知已被 design/164 拆除——**时间失明是设计内**,
9
+ * clay 同案否决了 core 的两个补偿选项(walltime 回 approach-notice / 注入带预算快照),不接。
10
+ * 引擎能力保留(core stop-gate 机制原样),要终验的场景显式开。
9
11
  *
10
- * PRECEDENCE(off 优先,任何一条命中即不 stamp;否则默认 true):
11
- * a) `--no-final-verify` flag → off(一键回上游 2.1.207 严格对齐态:上游
12
- * `-p` 全文 0 final verification,默认开属超集;上游对应扩展点是 Stop hook)
13
- * b) `SEMA_HEADLESS_FINAL_VERIFY` env(envFlagOff 拼写集,REF-CC-141 dup-02 单源) off。
14
- * settings.json `env` 1a-envseed → process.env,其它 wire 配置(SEMA_HEADLESS_SCENARIO 等)
15
- * 同一车道。
16
- * c) 用户配置了 Stop hook(settings hooks.Stop,经 hooksForWire 投影)off【对抗轮硬性 amendment】:
17
- * 引擎 finalVerification 分支在 Stop hook 查询之前 return(runtask.js:1430-1443),默认开会把
18
- * 用户 Stop hook 延迟至多 2 turn——对上游迁移来的 hook 用户是真行为变化,故有 Stop hook 即让位。
19
- * d) 都没有 → true(TB 默认态吃满终验闸杠杆)。
12
+ * PRECEDENCE(off 优先;显式 on 才可能 stamp;否则默认 false):
13
+ * a) `--no-final-verify` flag → off(显式关,恒赢)。
14
+ * b) `SEMA_HEADLESS_FINAL_VERIFY` env OFF 拼写(envFlagOff,REF-CC-141 dup-02 单源)→ off
15
+ * c) 显式 on:`--final-verify` flag / env ON 拼写(envFlagOn)——此时若用户配置了 Stop hook
16
+ * (settings hooks.Stop,经 hooksForWire 投影)仍**让位**(#106 B:引擎 finalVerification
17
+ * 分支在 Stop hook 查询之前 return(runtask.js:1430-1443),开着会把用户 Stop hook 延迟至多
18
+ * 2 turn)+ stderr 告知;无 Stop hook → on。
19
+ * d) 都没有 → false(默认关,静默,无告知——没开过的
20
+ * 东西无从让位,让位告知仅在显式 on Stop hook 压下时出现)。
20
21
  *
21
22
  * LIVE-GATE:与 scenarioWire 同款——调用点(seamQueryEngine.ask)以 SEMA_LIVE_BASEURL 为门,
22
23
  * mock/pty fixture 车道永不 stamp 该字段(离线投影字节等价)。
@@ -39,6 +40,12 @@ export declare const HEADLESS_FINAL_VERIFY_ENV = "SEMA_HEADLESS_FINAL_VERIFY";
39
40
  * never flags,scenarioWire 同款纪律)。Boolean flag,无值形态,重复无害。
40
41
  */
41
42
  export declare function parseNoFinalVerifyArgv(argv: string[]): boolean;
43
+ /**
44
+ * Parse the explicit opt-in `--final-verify` out of an argv slice(同 `--no-final-verify` 的扫描
45
+ * 纪律:bare `--` 后是 positionals)。默认关时代的唯一 flag 开口;`--no-final-verify` 恒赢它
46
+ * (off 优先纪律)。壳侧 commander 需在 0.17.x 提货时注册本 flag(提货单点名)。
47
+ */
48
+ export declare function parseFinalVerifyArgv(argv: string[]): boolean;
42
49
  /**
43
50
  * The settings-lane off-switch:`SEMA_HEADLESS_FINAL_VERIFY` 设成 {@link envFlagOff} 拼写集
44
51
  * (REF-CC-141 dup-02 单源:`0`/`false`/`no`/`off`/`none`,大小写不敏感)disables the stamp。
@@ -58,17 +65,15 @@ export declare function headlessFinalVerifyDisabledByEnv(env?: EnvLike): boolean
58
65
  */
59
66
  export declare function hasUserStopHook(wireHooks: WireHooksConfig | undefined): boolean;
60
67
  /**
61
- * Resolve the finalVerification stamp for a headless `-p` submit:默认 true;
62
- * `--no-final-verify` flag / SEMA_HEADLESS_FINAL_VERIFY=false|0 / 用户 Stop hook 在场 → false。
68
+ * Resolve the finalVerification stamp for a headless `-p` submit:**默认 false**(clay 裁定
69
+ * 2026-08-04,头注案情);仅显式 `--final-verify` / env ON 拼写才可能 on,且 Stop hook 仍让位。
63
70
  * 调用点必须 live-gate(SEMA_LIVE_BASEURL)——mock/offline 形状不变。
64
71
  */
65
72
  export declare function resolveHeadlessFinalVerify(argv: string[], env?: EnvLike, wireHooks?: WireHooksConfig): boolean;
66
73
  /**
67
- * #106 裁 B(clay 2026-07-26):让位维持 + 诚实告知。detail 形暴露 off 原因供调用点分诊 ——
68
- * flag/env 关闭是用户显式意愿(不聒噪);`stop-hook` 让位是用户**没显式要求**的关闭
69
- * (Stop hook 可能来自 /goal 自注册或 settings hook),必须告知(修前静默,用户不知道自己
70
- * 失去了终验)。方向注记:finalVerification 后续默认关(CC Stop hook 机制同,不叠超集)——
71
- * 见记忆 final-verification-direction;届时本函数的默认臂翻转,detail 形不动。
74
+ * #106 裁 B(clay 2026-07-26)让位+告知维持,但默认臂已按 2026-08-04 裁定翻转(头注案情):
75
+ * 默认 off 且**无 offReason**(静默——没开过的东西无从让位,告知只在显式 on 被 Stop hook
76
+ * 压下时出现);flag/env 显式关同样不聒噪。detail 形不动(offReason 词表原样)
72
77
  */
73
78
  export declare function resolveHeadlessFinalVerifyDetail(argv: string[], env?: EnvLike, wireHooks?: WireHooksConfig): {
74
79
  on: boolean;
@@ -1,5 +1,5 @@
1
1
  import { hostEnv } from './hostEnv.js';
2
- import { envFlagOff } from './envFlag.js';
2
+ import { envFlagOff, envFlagOn } from './envFlag.js';
3
3
  /** Settings-lane env knob(settings.json `env` 块 → 1a-envseed → process.env)。'false'/'0' = off。 */
4
4
  export const HEADLESS_FINAL_VERIFY_ENV = 'SEMA_HEADLESS_FINAL_VERIFY';
5
5
  /**
@@ -15,6 +15,20 @@ export function parseNoFinalVerifyArgv(argv) {
15
15
  }
16
16
  return false;
17
17
  }
18
+ /**
19
+ * Parse the explicit opt-in `--final-verify` out of an argv slice(同 `--no-final-verify` 的扫描
20
+ * 纪律:bare `--` 后是 positionals)。默认关时代的唯一 flag 开口;`--no-final-verify` 恒赢它
21
+ * (off 优先纪律)。壳侧 commander 需在 0.17.x 提货时注册本 flag(提货单点名)。
22
+ */
23
+ export function parseFinalVerifyArgv(argv) {
24
+ for (const a of argv) {
25
+ if (a === '--')
26
+ break; // positionals — never flags
27
+ if (a === '--final-verify')
28
+ return true;
29
+ }
30
+ return false;
31
+ }
18
32
  /**
19
33
  * The settings-lane off-switch:`SEMA_HEADLESS_FINAL_VERIFY` 设成 {@link envFlagOff} 拼写集
20
34
  * (REF-CC-141 dup-02 单源:`0`/`false`/`no`/`off`/`none`,大小写不敏感)disables the stamp。
@@ -39,28 +53,30 @@ export function hasUserStopHook(wireHooks) {
39
53
  return Array.isArray(groups) && groups.length > 0;
40
54
  }
41
55
  /**
42
- * Resolve the finalVerification stamp for a headless `-p` submit:默认 true;
43
- * `--no-final-verify` flag / SEMA_HEADLESS_FINAL_VERIFY=false|0 / 用户 Stop hook 在场 → false。
56
+ * Resolve the finalVerification stamp for a headless `-p` submit:**默认 false**(clay 裁定
57
+ * 2026-08-04,头注案情);仅显式 `--final-verify` / env ON 拼写才可能 on,且 Stop hook 仍让位。
44
58
  * 调用点必须 live-gate(SEMA_LIVE_BASEURL)——mock/offline 形状不变。
45
59
  */
46
60
  export function resolveHeadlessFinalVerify(argv, env = hostEnv(), wireHooks) {
47
61
  return resolveHeadlessFinalVerifyDetail(argv, env, wireHooks).on;
48
62
  }
49
63
  /**
50
- * #106 裁 B(clay 2026-07-26):让位维持 + 诚实告知。detail 形暴露 off 原因供调用点分诊 ——
51
- * flag/env 关闭是用户显式意愿(不聒噪);`stop-hook` 让位是用户**没显式要求**的关闭
52
- * (Stop hook 可能来自 /goal 自注册或 settings hook),必须告知(修前静默,用户不知道自己
53
- * 失去了终验)。方向注记:finalVerification 后续默认关(CC Stop hook 机制同,不叠超集)——
54
- * 见记忆 final-verification-direction;届时本函数的默认臂翻转,detail 形不动。
64
+ * #106 裁 B(clay 2026-07-26)让位+告知维持,但默认臂已按 2026-08-04 裁定翻转(头注案情):
65
+ * 默认 off 且**无 offReason**(静默——没开过的东西无从让位,告知只在显式 on 被 Stop hook
66
+ * 压下时出现);flag/env 显式关同样不聒噪。detail 形不动(offReason 词表原样)
55
67
  */
56
68
  export function resolveHeadlessFinalVerifyDetail(argv, env = hostEnv(), wireHooks) {
57
69
  if (parseNoFinalVerifyArgv(argv))
58
70
  return { on: false, offReason: 'flag' };
59
71
  if (headlessFinalVerifyDisabledByEnv(env))
60
72
  return { on: false, offReason: 'env' };
61
- if (hasUserStopHook(wireHooks))
62
- return { on: false, offReason: 'stop-hook' };
63
- return { on: true };
73
+ const explicitOn = parseFinalVerifyArgv(argv) || envFlagOn(env[HEADLESS_FINAL_VERIFY_ENV]);
74
+ if (explicitOn) {
75
+ if (hasUserStopHook(wireHooks))
76
+ return { on: false, offReason: 'stop-hook' };
77
+ return { on: true };
78
+ }
79
+ return { on: false }; // 默认关(clay 裁定 2026-08-04):无 offReason = 静默缺省态
64
80
  }
65
81
  /**
66
82
  * stop-hook 让位的告知文案(单一措辞源;stderr 一行)。措辞纪律与 hook_notice 同族:
@@ -27,6 +27,17 @@ export declare const HITL_REJECT_MESSAGE = "The user doesn't want to proceed wit
27
27
  /** core 对被 gate/连坐 abort 的 call 铸的 tool_end 载体(逐字;desktop session-host 真引擎实测
28
28
  * 同款)——HOLD 谓词锚它做**精确等值**,普通工具错的输出是各自错误文案,永不进 HOLD。 */
29
29
  export declare const ENGINE_ABORT_TOOL_RESULT = "Operation aborted";
30
+ /**
31
+ * tool_end.output 的文本归一(P0 案B,2026-08-03,server 6.0.0 wire 实测):sync leg 的毒化帧
32
+ * `output` 不再是裸 string,而是 content-block 数组 `[{"type":"text","text":"Operation aborted"}]`
33
+ * (8790 裸引擎 POST /v1/tasks/stream 逐帧记录字节形)。abort 标记的**等值锚**必须先归一再比,
34
+ * 否则 6.0.0 下 hold-poison 臂全盲:毒化帧当场上屏 + generic-close 记 ended ⇒ decide 后重放的
35
+ * 真收口帧被 isEnded 早退去重 —— 「按 Yes 屏幕永远停在 Error: Operation aborted」整案复发。
36
+ * 只归一**判定**,帧本身一个字节不改;等值语义不放宽([2084]①-b 收窄仍然成立,负控见 gate ⑩)。
37
+ * 🆕 [C93] 公面导出(0.17.1):web/desktop 被点名「两端务必接」的同款归一各自手抄=漂移温床,
38
+ * 导出做单一真源;签名不变,加导出=additive。
39
+ */
40
+ export declare function toolEndOutputText(output: unknown): string | undefined;
30
41
  /** 本桥消费的 wire 面(@sema-agent/sdk AgentClient 的结构切片,mock 可注入)。 */
31
42
  export interface AskGateWireDeps {
32
43
  /** approvals.list/decide + assistant(HitlBridge 的 client 切片)。 */
@@ -21,8 +21,10 @@ export const ENGINE_ABORT_TOOL_RESULT = 'Operation aborted';
21
21
  * 否则 6.0.0 下 hold-poison 臂全盲:毒化帧当场上屏 + generic-close 记 ended ⇒ decide 后重放的
22
22
  * 真收口帧被 isEnded 早退去重 —— 「按 Yes 屏幕永远停在 Error: Operation aborted」整案复发。
23
23
  * 只归一**判定**,帧本身一个字节不改;等值语义不放宽([2084]①-b 收窄仍然成立,负控见 gate ⑩)。
24
+ * 🆕 [C93] 公面导出(0.17.1):web/desktop 被点名「两端务必接」的同款归一各自手抄=漂移温床,
25
+ * 导出做单一真源;签名不变,加导出=additive。
24
26
  */
25
- function toolEndOutputText(output) {
27
+ export function toolEndOutputText(output) {
26
28
  if (typeof output === 'string')
27
29
  return output;
28
30
  if (!Array.isArray(output))
package/dist/index.d.ts CHANGED
@@ -155,6 +155,7 @@ export * from './controlRouter.js';
155
155
  export * from './sseIdleTriage.js';
156
156
  export * from './engineWireSdk.js';
157
157
  export * from './classifierVerdictWire.js';
158
+ export * from './engineErrorCodes.js';
158
159
  export * from './agentsWireCaps.js';
159
160
  export * from './attachmentsWireCaps.js';
160
161
  export * from './clientContextWireCaps.js';
@@ -212,6 +213,7 @@ export * from './hooksWireCaps.js';
212
213
  export * from './liveInitToolFace.js';
213
214
  export * from './model/providerPresets.js';
214
215
  export * from './hitl/hitlBridge.js';
216
+ export { toolEndOutputText, ENGINE_ABORT_TOOL_RESULT } from './hitl/frameRouter.js';
215
217
  export * from './hitl/hitlHostSurface.js';
216
218
  export * from './hitl/toolApprovalWire.js';
217
219
  export * from './hitl/askGateWire.js';
package/dist/index.js CHANGED
@@ -171,6 +171,10 @@ export * from './controlRouter.js';
171
171
  export * from './sseIdleTriage.js';
172
172
  export * from './engineWireSdk.js';
173
173
  export * from './classifierVerdictWire.js';
174
+ // G1 去字面化(2026-08-04,core 5.10.0 / sdk 6.2.0 消费半场):引擎 `errorCode` 词表的单一真源。
175
+ // 进公面是**有意**的(与 envFlag 那条包内叶相反):壳/web/desktop 三端都要按同一批码分臂,
176
+ // 三端各抄一份字面正是本文件要根治的病;开集纪律与闭集唯一例外见该文件头注。
177
+ export * from './engineErrorCodes.js';
174
178
  // caps / wire 门族(17 个 *WireCaps。B2 时 hooksWireCaps / scratchpadWireCaps 因宿主耦合未搬;
175
179
  // B4 已搬 scratchpadWireCaps(走 FsPort),hooksWireCaps 仍在壳里等 SettingsPort 接线 —— 见交接报告)
176
180
  export * from './agentsWireCaps.js';
@@ -290,6 +294,10 @@ export * from './model/providerPresets.js';
290
294
  // 共享宿主通知面 + cancel-by-deny 有界观察 —— `askGateWire.ts` 已 import `toolApprovalWire.ts`,
291
295
  // 反向 import 会成环,所以两条腿共用的那一组挪进一个两边都能到达的独立模块。
292
296
  export * from './hitl/hitlBridge.js';
297
+ // [C93] P0 直讨(0.17.1):toolEndOutputText 单一真源导出——web/desktop 被点名「两端务必接」的
298
+ // 6.0.0 output content-block 归一,此前 frameRouter 内私有,两端手抄=漂移温床。frameRouter 只
299
+ // 挑名导出这一件(整星导出会把路由内部面全泄进公面)。
300
+ export { toolEndOutputText, ENGINE_ABORT_TOOL_RESULT } from './hitl/frameRouter.js';
293
301
  export * from './hitl/hitlHostSurface.js';
294
302
  export * from './hitl/toolApprovalWire.js';
295
303
  export * from './hitl/askGateWire.js';
@@ -11,6 +11,16 @@
11
11
  * 该工具)= [909]A3(headless EnterPlanMode→park 弃 86% 预算)类事故的根治,比 [884]A1 的
12
12
  * park→error 终帧防御纵深更靠前(现在根本不 park)。
13
13
  *
14
+ * 🆕 **core 5.10.0 把这一 stamp 的作用域从「本 run」扩到「本 run 及其全部委派子代」**
15
+ * ([2575] 消费端自查④,委托继承收窄五座之②):此前子代 roster 是「**恒无** AskUserQuestion」,
16
+ * 5.10.0 起改成「**父面允许才有**」——`onQuestion` 在 prepare 期解析一次并**原样**传给子代
17
+ * (父子共用同一个函数对象)。对本模块是**净收益且零改动**:`-p` stamp `false` 之后,硬 headless
18
+ * 父的子代**压根不挂** AskUserQuestion,而不是挂了再靠子代自己的判断兜。同族一并单向收紧的还有
19
+ * `handsReadOnly`(只读父 ⇒ 子代无写手)与 `oneShot`(true 向传播)。
20
+ * 🔴 给下一棒的判据纪律:因此**别再用闭包身份判「谁在问」** —— 父子是同一个 `onQuestion` 函数对象,
21
+ * 闭包身份在 5.10.0 上不再有判别力;判身要读 `AskQuestionRequest.sourceTaskId` / `principal`
22
+ * (本包 hitl 面已是这个姿势,见 `hitl/toolApprovalWire.ts` 的 `sourceTaskId` 位)。
23
+ *
14
24
  * 语义(headlessPermissionModeWire 同点同型 wire):
15
25
  * · 仅 headless `-p` 车道(本模块只被 seamQueryEngine.ask import;交互 REPL 一根毛不动);
16
26
  * · 显式交互意图恒赢:CLI 无 --interactive-tools 类旗(gap-check 2026-07-16:上游 CC 2.1.207
@@ -11,6 +11,16 @@
11
11
  * 该工具)= [909]A3(headless EnterPlanMode→park 弃 86% 预算)类事故的根治,比 [884]A1 的
12
12
  * park→error 终帧防御纵深更靠前(现在根本不 park)。
13
13
  *
14
+ * 🆕 **core 5.10.0 把这一 stamp 的作用域从「本 run」扩到「本 run 及其全部委派子代」**
15
+ * ([2575] 消费端自查④,委托继承收窄五座之②):此前子代 roster 是「**恒无** AskUserQuestion」,
16
+ * 5.10.0 起改成「**父面允许才有**」——`onQuestion` 在 prepare 期解析一次并**原样**传给子代
17
+ * (父子共用同一个函数对象)。对本模块是**净收益且零改动**:`-p` stamp `false` 之后,硬 headless
18
+ * 父的子代**压根不挂** AskUserQuestion,而不是挂了再靠子代自己的判断兜。同族一并单向收紧的还有
19
+ * `handsReadOnly`(只读父 ⇒ 子代无写手)与 `oneShot`(true 向传播)。
20
+ * 🔴 给下一棒的判据纪律:因此**别再用闭包身份判「谁在问」** —— 父子是同一个 `onQuestion` 函数对象,
21
+ * 闭包身份在 5.10.0 上不再有判别力;判身要读 `AskQuestionRequest.sourceTaskId` / `principal`
22
+ * (本包 hitl 面已是这个姿势,见 `hitl/toolApprovalWire.ts` 的 `sourceTaskId` 位)。
23
+ *
14
24
  * 语义(headlessPermissionModeWire 同点同型 wire):
15
25
  * · 仅 headless `-p` 车道(本模块只被 seamQueryEngine.ask import;交互 REPL 一根毛不动);
16
26
  * · 显式交互意图恒赢:CLI 无 --interactive-tools 类旗(gap-check 2026-07-16:上游 CC 2.1.207
@@ -6,7 +6,8 @@
6
6
  * `limits` 是**唯一预算面,默认全空 = 无限制**。本模块拼的三个键都骑 5.8.0 的新词表:
7
7
  * · `maxWalltimeMs`(**毫秒**;5.7 的 `limits.timeoutSec` 已删,单位一并变)
8
8
  * · `maxTurns`
9
- * · `maxTokens`(5.8.0 的**主限**——time 退出任务轴之后,token 是首选的预算旋钮)
9
+ * · `maxOutputTokens`(5.8.0 的**主限**——time 退出任务轴之后,token 是首选的预算旋钮;
10
+ * wire 键名自 5.7 起一直是 maxOutputTokens,core 内部才叫 maxTokens——[2569] 案订正)
10
11
  * 到限语义也换了:**没有配速机器了**。5.8.0 删掉了 deadlineNudge / callCapByDeadline /
11
12
  * gracefulFinalize 那一套(以及引擎侧 1000 轮安全网、fork 200、session-bg 30min 暗注等全部隐式
12
13
  * 默认),走到 `maxWalltimeMs` 不再是「引擎自己减速然后优雅收尾」,而是**响亮终局**:
@@ -28,7 +29,8 @@
28
29
  * 793380) → ALIGNMENT-GAP fill: the flag was already registered (main.tsx CC-inherited surface) and
29
30
  * drove the local query loop, but the seam path executes turns ENGINE-side — this wire finally
30
31
  * carries the cap to where the turns actually run.
31
- * · `--max-tokens <n>` → limits.maxTokens(5.8.0 主推键)。Upstream CC 2.1.207 has no run-level
32
+ * · `--max-tokens <n>` → limits.maxOutputTokens(5.8.0 主限;wire 键名见接口注——绝不直发
33
+ * core 内部键 maxTokens,[2569] 案)。Upstream CC 2.1.207 has no run-level
32
34
  * token budget flag → SUPERSET.
33
35
  *
34
36
  * PRECEDENCE per knob (flag > settings > none), scenarioWire 三件套同款:
@@ -57,9 +59,10 @@
57
59
  * · 第一地板 `LIMITS_WIRE_MIN_ENGINE = 1.196.0` = 「`limits` 这个**字段**存不存在」(server 1.196.0
58
60
  * 起接受 body.limits);低于它,整个预算面被丢。
59
61
  * · 第二地板 **server ≥ 6.0.0** = 「**新键集**认不认」。6.0.0 是第一个捆 core 5.8.0 的 server 版本,
60
- * 也就是第一个认识 `maxWalltimeMs`/`maxTokens` 的版本(5.7 只认 `timeoutSec`/`maxOutputTokens`,
61
- * 未知键静默丢弃 —— `config.limit_unknown_key` 那种响亮拒是 5.8.0 才有的)。所以对 1.196.0
62
- * version < 6.0.0 的引擎,`maxTurns` 生效而 `maxWalltimeMs`/`maxTokens` 会被**静默无视**。
62
+ * 也就是第一个认识 `maxWalltimeMs` 的版本(5.7 只认 `timeoutSec`/`maxOutputTokens`/`maxTurns`,
63
+ * 未知键静默丢弃)。**[2569] 案订正**:`maxOutputTokens` 5.7 就在 wire 词表,本模块此前
64
+ * 直发 core 内部键 `maxTokens` 才是被静默无视的那个(server 6.3.0 400);对 1.196.0
65
+ * version < 6.0.0 的引擎,`maxTurns`/`maxOutputTokens` 生效而 `maxWalltimeMs` 被静默无视。
63
66
  * 之所以过去写不出这条:/health 自报的是 server 版本,而键名代际取决于它捆的 core 版本,当时
64
67
  * 两者没有可从 wire 读出的映射;6.0.0 这班车把映射钉死了(6.0.0 ⇒ core ≥5.8),这条地板才成立。
65
68
  * · 本文件的运行期探针目前只对第一地板发声(warn, don't block);第二地板先落成契约事实写在这里,
@@ -97,8 +100,12 @@ export interface HeadlessLimits {
97
100
  maxWalltimeMs?: number;
98
101
  /** 轮数预算。到限 ⇒ failed + `limits.max_turns_exceeded`。 */
99
102
  maxTurns?: number;
100
- /** token 预算(5.8.0 主限)。到限 ⇒ failed + `limits.max_tokens_exceeded`(result 带已产出正文)。 */
101
- maxTokens?: number;
103
+ /** token 预算(5.8.0 主限)。到限 ⇒ failed + `limits.max_tokens_exceeded`(result 带已产出正文)。
104
+ * 🔴 wire 键=**maxOutputTokens**(sdk 6.2.0 types.d.ts:472 实测词表;[2569] 案定谳):
105
+ * `maxTokens` 是 core TaskSpec 内部键,server 从未从 wire 采纳它(此前 200 静默空转,
106
+ * 6.3.0 起对 limits.maxTokens 直接 400)。用户面 `--max-tokens`/`SEMA_HEADLESS_MAX_TOKENS`
107
+ * 名称不变——变的只是上 wire 的键名。[same-name-different-meaning-crosses-layers] 同族。 */
108
+ maxOutputTokens?: number;
102
109
  }
103
110
  export type LimitsParseResult = {
104
111
  ok: true;
@@ -6,7 +6,8 @@
6
6
  * `limits` 是**唯一预算面,默认全空 = 无限制**。本模块拼的三个键都骑 5.8.0 的新词表:
7
7
  * · `maxWalltimeMs`(**毫秒**;5.7 的 `limits.timeoutSec` 已删,单位一并变)
8
8
  * · `maxTurns`
9
- * · `maxTokens`(5.8.0 的**主限**——time 退出任务轴之后,token 是首选的预算旋钮)
9
+ * · `maxOutputTokens`(5.8.0 的**主限**——time 退出任务轴之后,token 是首选的预算旋钮;
10
+ * wire 键名自 5.7 起一直是 maxOutputTokens,core 内部才叫 maxTokens——[2569] 案订正)
10
11
  * 到限语义也换了:**没有配速机器了**。5.8.0 删掉了 deadlineNudge / callCapByDeadline /
11
12
  * gracefulFinalize 那一套(以及引擎侧 1000 轮安全网、fork 200、session-bg 30min 暗注等全部隐式
12
13
  * 默认),走到 `maxWalltimeMs` 不再是「引擎自己减速然后优雅收尾」,而是**响亮终局**:
@@ -28,7 +29,8 @@
28
29
  * 793380) → ALIGNMENT-GAP fill: the flag was already registered (main.tsx CC-inherited surface) and
29
30
  * drove the local query loop, but the seam path executes turns ENGINE-side — this wire finally
30
31
  * carries the cap to where the turns actually run.
31
- * · `--max-tokens <n>` → limits.maxTokens(5.8.0 主推键)。Upstream CC 2.1.207 has no run-level
32
+ * · `--max-tokens <n>` → limits.maxOutputTokens(5.8.0 主限;wire 键名见接口注——绝不直发
33
+ * core 内部键 maxTokens,[2569] 案)。Upstream CC 2.1.207 has no run-level
32
34
  * token budget flag → SUPERSET.
33
35
  *
34
36
  * PRECEDENCE per knob (flag > settings > none), scenarioWire 三件套同款:
@@ -57,9 +59,10 @@
57
59
  * · 第一地板 `LIMITS_WIRE_MIN_ENGINE = 1.196.0` = 「`limits` 这个**字段**存不存在」(server 1.196.0
58
60
  * 起接受 body.limits);低于它,整个预算面被丢。
59
61
  * · 第二地板 **server ≥ 6.0.0** = 「**新键集**认不认」。6.0.0 是第一个捆 core 5.8.0 的 server 版本,
60
- * 也就是第一个认识 `maxWalltimeMs`/`maxTokens` 的版本(5.7 只认 `timeoutSec`/`maxOutputTokens`,
61
- * 未知键静默丢弃 —— `config.limit_unknown_key` 那种响亮拒是 5.8.0 才有的)。所以对 1.196.0
62
- * version < 6.0.0 的引擎,`maxTurns` 生效而 `maxWalltimeMs`/`maxTokens` 会被**静默无视**。
62
+ * 也就是第一个认识 `maxWalltimeMs` 的版本(5.7 只认 `timeoutSec`/`maxOutputTokens`/`maxTurns`,
63
+ * 未知键静默丢弃)。**[2569] 案订正**:`maxOutputTokens` 5.7 就在 wire 词表,本模块此前
64
+ * 直发 core 内部键 `maxTokens` 才是被静默无视的那个(server 6.3.0 400);对 1.196.0
65
+ * version < 6.0.0 的引擎,`maxTurns`/`maxOutputTokens` 生效而 `maxWalltimeMs` 被静默无视。
63
66
  * 之所以过去写不出这条:/health 自报的是 server 版本,而键名代际取决于它捆的 core 版本,当时
64
67
  * 两者没有可从 wire 读出的映射;6.0.0 这班车把映射钉死了(6.0.0 ⇒ core ≥5.8),这条地板才成立。
65
68
  * · 本文件的运行期探针目前只对第一地板发声(warn, don't block);第二地板先落成契约事实写在这里,
@@ -122,7 +125,7 @@ function parseIntInDomain(raw, min, max) {
122
125
  }
123
126
  /** 三个键任一在场 ⇒ 值得 stamp(空对象绝不上 wire —— 那是「配了个空预算」不是「没配」)。 */
124
127
  function hasAnyLimit(l) {
125
- return l.maxWalltimeMs !== undefined || l.maxTurns !== undefined || l.maxTokens !== undefined;
128
+ return l.maxWalltimeMs !== undefined || l.maxTurns !== undefined || l.maxOutputTokens !== undefined;
126
129
  }
127
130
  /**
128
131
  * Parse `--deadline` + `--max-turns` + `--max-tokens` out of an argv slice. No flags ⇒ `{ok:true}`
@@ -156,7 +159,7 @@ export function parseLimitsArgv(argv) {
156
159
  const n = mk.raw !== undefined ? parseIntInDomain(mk.raw, MAX_TOKENS_MIN, MAX_TOKENS_MAX) : null;
157
160
  if (n === null)
158
161
  return { ok: false, error: MAX_TOKENS_USAGE };
159
- limits.maxTokens = n;
162
+ limits.maxOutputTokens = n;
160
163
  flaggedFlags.push('--max-tokens');
161
164
  }
162
165
  return {
@@ -192,7 +195,7 @@ export function headlessLimitsFromEnv(env = hostEnv()) {
192
195
  if (n === null) {
193
196
  return { ok: false, error: `Error: ${HEADLESS_MAX_TOKENS_ENV}="${mk}" is not a positive integer. Fix or unset it.` };
194
197
  }
195
- out.maxTokens = n;
198
+ out.maxOutputTokens = n;
196
199
  }
197
200
  return { ok: true, limits: hasAnyLimit(out) ? out : undefined };
198
201
  }
@@ -86,6 +86,14 @@ export type EngineTaskStopOutcome =
86
86
  * 照样带原始 `code`),绝不塌进任何一个已知形 —— 塌进去就等于替 server 编了一个它没说的原因。
87
87
  */
88
88
  export declare function classifyTaskStopConflict(e: unknown): EngineTaskStopOutcome;
89
+ /**
90
+ * 🟢 B8 ALLOW 清单登记(REF-CC-域词表-05,属主=client-core 规范重构轮,到期复议=SDK typed 错误类
91
+ * 覆盖全部 `stop.*` 码后或 2026-Q4 复审以先到者为准):兜底字面认码,前置合取 = typed
92
+ * `TaskStopConflictError` 缺席时才落到这里(见 `stopEngineTask` 的 `if (e instanceof
93
+ * TaskStopConflictError || status === 409)`)。同批同表的另两处:`detachWire.ts`
94
+ * `DETACH_DURABLE_OFF_400_ANCHOR`(server 契约话)、`classifierVerdictWire.ts`
95
+ * `CLASSIFIER_DENY_SIGNATURE`(core lockstep 机器签名)——三处互指,理由逐条写在各自站点。
96
+ */
89
97
  /** 停一个引擎侧任务句柄(新面 B)。只有 ok:true(=HTTP 200)算停了。绝不 throw。 */
90
98
  export declare function stopEngineTask(handle: string, opts?: {
91
99
  signal?: AbortSignal;
@@ -47,6 +47,9 @@ import { engineTaskHandlesCapable } from './engineRowStopGate.js';
47
47
  // REF-CC-域词表-06 提单源:spool 全量/增量判读的单一真源现在在 toolResult.ts(它也消费同一份
48
48
  // 协议标记表 PROTOCOL_MARKERS)—— 本文件不再自己 `.includes()` 抄一份判读。
49
49
  import { spoolMarkerOf } from '../toolResult.js';
50
+ // G1 去字面化(2026-08-04):`stop.*` 五形的码字面收编进 `engineErrorCodes.ts` 单一真源。
51
+ // 顺序敏感的兜底认码数组也一并搬过去(顺序即语义,见那边的头注),本文件只 import。
52
+ import { STOP_CONFLICT_CODES, STOP_NOT_LANDED, STOP_NOT_LOCAL, STOP_PARKED, STOP_PARK_ARBITER_UNREACHABLE, STOP_PARK_RESUME_WON, } from '../engineErrorCodes.js';
50
53
  // 能力门同步读口:真源在零依赖叶 `engineRowStopGate`(batch-stop 同步分类不拉本模块 SDK 图)。
51
54
  // ⚠️ 此处**不再 re-export**——它与原定义同进 index.ts 的 `export *` barrel 会构成双出口,
52
55
  // esbuild 对 star-export 歧义直接 build 失败(tsc 同源 symbol 不报=假绿;0.8.0 壳收批实撞,
@@ -153,15 +156,15 @@ export function classifyTaskStopConflict(e) {
153
156
  ? raw
154
157
  : (STOP_CONFLICT_CODES.find(c => detail.includes(c)) ?? '');
155
158
  switch (code) {
156
- case 'stop.not_local':
159
+ case STOP_NOT_LOCAL:
157
160
  return { ok: false, reason: 'not_local', detail };
158
- case 'stop.parked':
161
+ case STOP_PARKED:
159
162
  return { ok: false, reason: 'parked', detail };
160
- case 'stop.park_resume_won':
163
+ case STOP_PARK_RESUME_WON:
161
164
  return { ok: false, reason: 'park_resume_won', detail };
162
- case 'stop.park_arbiter_unreachable':
165
+ case STOP_PARK_ARBITER_UNREACHABLE:
163
166
  return { ok: false, reason: 'park_arbiter_unreachable', detail };
164
- case 'stop.not_landed':
167
+ case STOP_NOT_LANDED:
165
168
  return { ok: false, reason: 'not_landed', detail };
166
169
  default:
167
170
  // 开集:未知/未来 `stop.*`(以及 code 完全缺席的残破形)。诚实说「有冲突但我不认识它」,
@@ -177,14 +180,8 @@ export function classifyTaskStopConflict(e) {
177
180
  * `DETACH_DURABLE_OFF_400_ANCHOR`(server 契约话)、`classifierVerdictWire.ts`
178
181
  * `CLASSIFIER_DENY_SIGNATURE`(core lockstep 机器签名)——三处互指,理由逐条写在各自站点。
179
182
  */
180
- /** 兜底字面认码用的已知集(顺序 = 长码优先,避免 `stop.parked` 抢走 `stop.park_resume_won`)。 */
181
- const STOP_CONFLICT_CODES = [
182
- 'stop.park_arbiter_unreachable',
183
- 'stop.park_resume_won',
184
- 'stop.not_landed',
185
- 'stop.not_local',
186
- 'stop.parked',
187
- ];
183
+ /* 兜底字面认码用的已知集(顺序 = 长码优先,避免 `stop.parked` 抢走 `stop.park_resume_won`)
184
+ * 已随 G1 去字面化搬到 `engineErrorCodes.ts` 的 `STOP_CONFLICT_CODES` —— 顺序即语义那条注也在那边。 */
188
185
  /** 停一个引擎侧任务句柄(新面 B)。只有 ok:true(=HTTP 200)算停了。绝不 throw。 */
189
186
  export async function stopEngineTask(handle, opts) {
190
187
  const cfg = engineWireTarget();
@@ -1,3 +1,5 @@
1
+ import type { TaskResult, TaskStats } from '@sema-agent/sdk';
2
+ import { type ModelFallbackReason } from './engineErrorCodes.js';
1
3
  /**
2
4
  * 引擎会在 `tool_end.structured` 顶层 `type` 上发的**全部**取值。
3
5
  * 用途**不是**「只处理这些」——下面的 switch 只认得其中一部分,认不得的照旧走 text 回落;
@@ -197,3 +199,87 @@ export declare function todoWriteToolUseResult(rawInput: unknown, oldTodos: unkn
197
199
  export declare function reportFindingsToolUseResult(structured: unknown, rawInput: unknown): {
198
200
  toolUseResult: unknown;
199
201
  } | null;
202
+ /**
203
+ * 子代**完成着陆报告**卡(core `agents/subagent.ts#completedAgentCard` 的 wire 形)。
204
+ *
205
+ * 沿革:5.10.0 之前这张卡被 core 的投影白名单在上 wire 前整张丢掉 —— `tool_end.structured` 自己的
206
+ * 契约点名了它,而实际只有 `async_launched` 回执到得了消费方。5.10.0 修好了那条白名单,于是
207
+ * `type:"agent"` 从「只有一形」变成「两形按 `status` 分」({@link structuredToToolUseResult} 的
208
+ * `case 'agent'` 头注写了为什么本包不把它投成 CC 卡)。
209
+ *
210
+ * 🔴 **本读口只做窄化,不做解释**:每个键要么原样带出,要么(类型不符时)当缺席。绝不为缺席的量
211
+ * 造零值([honest-absence-not-fabricated-zero])—— `stats` 取不到就是 `undefined`,不是 `{}`。
212
+ * 🔴 **白名单不是闭集断言**:core 明写这张卡是「显式字段白名单」而非 spread(`checkpointToken` /
213
+ * `checkpointGate` 这两个**能力**位永不上 wire)。未来 core 往卡上加字段时,本读口读不到它们 ——
214
+ * 那是**加一个字段**的跟车工单,不是错误;宿主要原始全量随时可读 `structured` 本身。
215
+ */
216
+ export interface CompletedAgentCard {
217
+ /** 子代**真实终态**(`completed` / `failed` / `blocked` / `suspended.*` …)。判别位;开集。 */
218
+ readonly status: string;
219
+ /** 子代任务 id(注意:完成卡是 camelCase `taskId`,启动回执是 snake_case `task_id`)。 */
220
+ readonly taskId: string;
221
+ /** 子代 transcript 句柄 —— 查询键,**不是**能力(续跑要部署侧的 resume 句柄)。 */
222
+ readonly sessionId?: string;
223
+ /** 子代类型名(roster 上的名字)。 */
224
+ readonly subagentType?: string;
225
+ /** 着陆报告正文 = 子代最终助手文本,**不含**模型面那层导向尾注。 */
226
+ readonly result?: string;
227
+ /** 到限截断时抢救出的最后模型文本。 */
228
+ readonly salvagedOutput?: string;
229
+ /** `outputSchema` 任务的结构化产出(不解释,原样)。 */
230
+ readonly structuredOutput?: unknown;
231
+ /** 失败归因三件之一:agent 自报无法推进的理由。 */
232
+ readonly blockedReason?: string;
233
+ /** 失败归因三件之二:人话。 */
234
+ readonly errorMessage?: string;
235
+ /** 失败归因三件之三:机器码(开集;词表见 `engineErrorCodes.ts`)。 */
236
+ readonly errorCode?: string;
237
+ /**
238
+ * 🆕 core 5.10.0 —— `errorCode` 的**机器可执行配对**:refusal 什么时候解除。
239
+ * 🔴 与 `retryable` 是**两个不同的问题**,可能同时出现且看起来矛盾(core 亲注):
240
+ * `retryable` = 「现在原样重发行不行」(`usage.window_exhausted` 上它是 false,且是对的);
241
+ * `retryAfterMs` = 「什么时候不再被拒」。调度器读前者+后者,立即重发的决策只读 `retryable`。
242
+ * 在场条件 = `usage.window_exhausted` 终局;**别拿它的在场性去判别的事**([2575] 自查③)。
243
+ */
244
+ readonly retryAfterMs?: number;
245
+ /**
246
+ * 🆕 core 5.10.0 —— 降级实录(from/to/reason/atTurn)。形状与 `TaskResult.degraded` **同一个**
247
+ * (core 把子代 TaskResult 的这一位原样搬上卡),故直接借 SDK 的类型而不是再开一个 `unknown` 出口。
248
+ * 窄化 = 对象守卫(与 `terminalToSdkResult.degradedOf` 同一姿势):非对象一律当缺席。
249
+ */
250
+ readonly degraded?: NonNullable<TaskResult['degraded']>;
251
+ /** 失败分类:错误种类。 */
252
+ readonly errorKind?: string;
253
+ /** 失败分类:原样重发是否可行(见 `retryAfterMs` 的配对说明)。 */
254
+ readonly retryable?: boolean;
255
+ /**
256
+ * run 总账(CC `AgentToolCompletedOutput` 对位)。同 `degraded`:core 搬的就是子代
257
+ * `TaskResult.stats`,所以借 SDK 的 `TaskStats`(它自带 `[key: string]: unknown` 开集索引,
258
+ * 新键不会因此被类型面挡住)。数值重算是渲染层的活,本层只窄化不解释。
259
+ */
260
+ readonly stats?: TaskStats;
261
+ /** 实际服务该子代的模型 id(与 `resolvedModel` 同值,core 两个键都发)。 */
262
+ readonly model?: string;
263
+ /** 同上 —— core 的显式别名键;两键择一读即可,本读口都带出以免宿主二次猜。 */
264
+ readonly resolvedModel?: string;
265
+ /**
266
+ * 🆕 core 5.10.0 / [2577] —— **闭集单成员**:请求的模型词没绑上,子代跑在继承来的默认模型上。
267
+ * 🔴 **这不是错误**(回落行为本身没变,只是现在说出来了);缺席 = 正常绑定或压根没请求过词。
268
+ * 渲染建议(core 给的语义,UI 语言各端自裁):在生效 model 旁加「requested word did not bind /
269
+ * inherited」注记,**不因它的在场性改变别的渲染**。加成员 = BREAKING,见 {@link ModelFallbackReason}。
270
+ */
271
+ readonly modelFallback?: ModelFallbackReason;
272
+ /** 子代跑在哪个 worktree(`isolation:'worktree'` 时)。 */
273
+ readonly worktreePath?: string;
274
+ /** 子代的逐工具调用统计。原样带出。 */
275
+ readonly toolStats?: unknown;
276
+ }
277
+ /**
278
+ * 把一张 `tool_end.structured` 窄化成 {@link CompletedAgentCard} —— **仅当**它是 `type:"agent"` 的
279
+ * **完成**臂时。不是 agent 卡 / 是 `async_launched` 回执 / 缺 `status` 或 `taskId` ⇒ `null`。
280
+ *
281
+ * 🔴 分臂判据 = `status`(core 原话:"Branch on `status`, never on which keys are present")。
282
+ * 本函数是那条纪律在包内的**单一实现点**:三端(TUI / web / desktop)按同一份窄化读,
283
+ * 而不是各自抄一遍键名与 `!== 'async_launched'`。
284
+ */
285
+ export declare function readCompletedAgentCard(structured: unknown): CompletedAgentCard | null;
@@ -27,6 +27,7 @@
27
27
  import { convertLeadingTabsToSpaces, getPatchFromContents } from './diff/patch.js';
28
28
  import { markEngineWorkflowNotified, registerOutstandingBgTask, registerOutstandingWorkflowRun, } from './notifications.js';
29
29
  import { parseWorkflowPollEnvelope, projectWorkflowTaskOutput } from './workflow.js';
30
+ import { asModelFallbackReason } from './engineErrorCodes.js';
30
31
  // ══════════════════════════════════════════════════════════════════════════════════════════════
31
32
  // ① structured 白名单(core design/116 的 details 顶层 `type` 集;[1840]§一 清单逐字)
32
33
  // ══════════════════════════════════════════════════════════════════════════════════════════════
@@ -41,7 +42,6 @@ import { parseWorkflowPollEnvelope, projectWorkflowTaskOutput } from './workflow
41
42
  */
42
43
  export const STRUCTURED_DETAIL_TYPES = new Set([
43
44
  'edit',
44
- 'multiedit',
45
45
  'create',
46
46
  'update',
47
47
  'bash',
@@ -59,7 +59,6 @@ export const STRUCTURED_DETAIL_TYPES = new Set([
59
59
  'task',
60
60
  'task-list',
61
61
  'task-output',
62
- 'memory-saved',
63
62
  'workflow-run',
64
63
  'web-fetch',
65
64
  'web-search',
@@ -70,7 +69,6 @@ export const STRUCTURED_DETAIL_TYPES = new Set([
70
69
  'image',
71
70
  'task-stop',
72
71
  'tool-search',
73
- 'memory-recall',
74
72
  'repo-map',
75
73
  'fork',
76
74
  'enter-plan-mode',
@@ -82,7 +80,29 @@ export const STRUCTURED_DETAIL_TYPES = new Set([
82
80
  'worktree',
83
81
  'monitor-start',
84
82
  'path_not_in_root',
83
+ // ── core 5.10.0 词表换代(2026-08-04 提货批;engine-vocab 等值门对 5.10.0 实装物直证)──────
84
+ // 5.10.0 的 structured-card 审计把这张表变成**双向契约**(每个铸点都登记 / 每个登记词都真被产出)。
85
+ // 新登记 7 词 —— 它们的工具**一直**在铸卡,漏的只是登记,于是这 7 类卡从来没上过 wire:
86
+ // · `report-findings` 最坏(ReportFindings 的模型面被**故意**压成「N findings reported.」,
87
+ // 卡又丢了 ⇒ findings 模型看不到、宿主也看不到,两头落空);
88
+ // · `readonly_out_of_root` 与既有 `path_not_in_root` 是同一份「按字段数拒绝次数」契约的两半
89
+ // (一个在 shell 面、一个在 fs 面);
90
+ // · `schedule-wakeup` / `send-message` / `agent-transcript` / `a2a` / `document` 同理。
91
+ 'readonly_out_of_root',
92
+ 'report-findings',
93
+ 'schedule-wakeup',
94
+ 'send-message',
95
+ 'agent-transcript',
96
+ 'a2a',
97
+ 'document',
85
98
  ]);
99
+ // 🔴 同批**删三词**(core 5.10.0 BREAKING「幽灵卡」清仓):`multiedit` / `memory-saved` /
100
+ // `memory-recall` —— 全树零铸点(MultiEdit/批量重放铸的是 `type:"edit"` 带 `edits[]`;core
101
+ // 根本不挂 memory 工具,memory 引擎是 runner 面子系统,没有工具面)。它们是「一个没人能兑现的
102
+ // 承诺」:按闭集分支的消费者为它们留了永不执行的臂。core 明写**不是**预留位——将来哪个工具
103
+ // 铸这种卡,和那个工具同一批加回来。
104
+ // ⚠️ 对本表的实际后果:这三个词此前会让 `structuredDetailType()` 判「structured 在场」⇒ 正则
105
+ // 退位。既然引擎从来不发它们,删掉是**零行为影响**的诚实化(不是收窄)。
86
106
  /** structured 在场判别:顶层 `type` ∈ 白名单 ⇒ 返回该 type,否则 undefined(= 不在场)。 */
87
107
  export function structuredDetailType(structured) {
88
108
  if (typeof structured !== 'object' || structured === null)
@@ -686,6 +706,21 @@ modelText) {
686
706
  case 'agent': {
687
707
  // Agent 异步启动(core 1.272+ [666] 默认后台化主路径)。outputFile 是 schema 必填但 live lane
688
708
  // 没有本地文件 ⇒ '' + canReadOutputFile:false(渲染文本因此省掉 file-tail 分支,与引擎模型面一致)。
709
+ //
710
+ // 🔴 **`type:"agent"` 自 core 5.10.0 起是两形一卡**([2575] 消费端自查②):`status` 是判别位 ——
711
+ // `"async_launched"` = 本臂的启动回执;**其它任何值** = 子代的**完成着陆报告**
712
+ // (`completedAgentCard`,core `agents/subagent.ts`;5.10.0 之前它被投影白名单在上 wire 前丢掉,
713
+ // 所以「`type:"agent"` ⇒ 后台启动回执」这个等式当时恰好成立,现在不成立了)。
714
+ // 🔴 **按 `status` 分臂,绝不按键集分臂** —— 这是 core 的原话,也是本行 `!==` 的全部理由。
715
+ // 键集判别在两形共有 `taskId`/`status`/`prompt`-无 的现实下必然错分。
716
+ // 🔴 完成卡**本批不投 CC 卡**(如实留白,不假装搬完):壳的 `AgentTool.outputSchema` 完成臂
717
+ // (`agentToolResultSchema` + `status:'completed'`)要 `content`/`totalToolUseCount`/
718
+ // `totalDurationMs`/`totalTokens`/`usage`/`prompt` 六件,而完成卡上**没有 `prompt`**、
719
+ // `status` 是子代真实终态(可能是 `failed`/`blocked`/`suspended.*`,壳的 zod 会当场拒),
720
+ // 统计也要从 `stats`/`toolStats` 重算 —— 那是**行为面**改动(要先答「失败的子代该渲成什么」),
721
+ // 按宪法三问单独走。返回 null ⇒ 回落模型面 text 路径,与 5.10.0 之前逐字同形,零回归。
722
+ // ⚠️ 完成卡本身**没有被吞**:`eventToSdkMessage` 的 `tool_end_result` 臂把 `structured`
723
+ // 原样透传给宿主,typed 读口见本文件的 {@link readCompletedAgentCard}。
689
724
  if (s.status !== 'async_launched')
690
725
  return null;
691
726
  const agentId = typeof s.task_id === 'string' ? s.task_id : undefined;
@@ -853,3 +888,56 @@ export function reportFindingsToolUseResult(structured, rawInput) {
853
888
  },
854
889
  };
855
890
  }
891
+ /**
892
+ * 把一张 `tool_end.structured` 窄化成 {@link CompletedAgentCard} —— **仅当**它是 `type:"agent"` 的
893
+ * **完成**臂时。不是 agent 卡 / 是 `async_launched` 回执 / 缺 `status` 或 `taskId` ⇒ `null`。
894
+ *
895
+ * 🔴 分臂判据 = `status`(core 原话:"Branch on `status`, never on which keys are present")。
896
+ * 本函数是那条纪律在包内的**单一实现点**:三端(TUI / web / desktop)按同一份窄化读,
897
+ * 而不是各自抄一遍键名与 `!== 'async_launched'`。
898
+ */
899
+ export function readCompletedAgentCard(structured) {
900
+ if (structured === null || typeof structured !== 'object')
901
+ return null;
902
+ const s = structured;
903
+ if (s.type !== 'agent')
904
+ return null;
905
+ const status = s.status;
906
+ // 分臂:`async_launched` 是回执臂(归 `structuredToToolUseResult` 的 `case 'agent'`),不是本臂。
907
+ if (typeof status !== 'string' || status.length === 0 || status === 'async_launched')
908
+ return null;
909
+ const taskId = s.taskId;
910
+ // 身份缺席 ⇒ 这不是一张可用的完成卡(诚实拒,不铸空 id)。
911
+ if (typeof taskId !== 'string' || taskId.length === 0)
912
+ return null;
913
+ const str = (v) => (typeof v === 'string' && v.length > 0 ? v : undefined);
914
+ const num = (v) => (typeof v === 'number' && Number.isFinite(v) ? v : undefined);
915
+ return {
916
+ status,
917
+ taskId,
918
+ ...(str(s.sessionId) !== undefined ? { sessionId: str(s.sessionId) } : {}),
919
+ ...(str(s.subagent_type) !== undefined ? { subagentType: str(s.subagent_type) } : {}),
920
+ // `result` 允许空串(子代没产出正文是一个诚实的事实),故这里不用 `str` 的非空过滤。
921
+ ...(typeof s.result === 'string' ? { result: s.result } : {}),
922
+ ...(typeof s.salvagedOutput === 'string' ? { salvagedOutput: s.salvagedOutput } : {}),
923
+ ...(s.structuredOutput !== undefined ? { structuredOutput: s.structuredOutput } : {}),
924
+ ...(str(s.blockedReason) !== undefined ? { blockedReason: str(s.blockedReason) } : {}),
925
+ ...(str(s.errorMessage) !== undefined ? { errorMessage: str(s.errorMessage) } : {}),
926
+ ...(str(s.errorCode) !== undefined ? { errorCode: str(s.errorCode) } : {}),
927
+ ...(num(s.retryAfterMs) !== undefined ? { retryAfterMs: num(s.retryAfterMs) } : {}),
928
+ // 对象守卫(与 terminalToSdkResult.degradedOf 同姿势):非对象/null 一律当缺席,不硬塞。
929
+ ...(typeof s.degraded === 'object' && s.degraded !== null
930
+ ? { degraded: s.degraded }
931
+ : {}),
932
+ ...(str(s.error_kind) !== undefined ? { errorKind: str(s.error_kind) } : {}),
933
+ ...(typeof s.retryable === 'boolean' ? { retryable: s.retryable } : {}),
934
+ ...(typeof s.stats === 'object' && s.stats !== null ? { stats: s.stats } : {}),
935
+ ...(str(s.model) !== undefined ? { model: str(s.model) } : {}),
936
+ ...(str(s.resolvedModel) !== undefined ? { resolvedModel: str(s.resolvedModel) } : {}),
937
+ ...(asModelFallbackReason(s.modelFallback) !== undefined
938
+ ? { modelFallback: asModelFallbackReason(s.modelFallback) }
939
+ : {}),
940
+ ...(str(s.worktreePath) !== undefined ? { worktreePath: str(s.worktreePath) } : {}),
941
+ ...(s.toolStats !== undefined ? { toolStats: s.toolStats } : {}),
942
+ };
943
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sema-agent/client-core",
3
- "version": "0.16.2",
3
+ "version": "0.17.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. Blackboard [1832] design axioms; [1651]/[1652]/[1653] signed seam design. Renamed from @sema-agent/wire-cc-adapter (0.1.x).",
5
5
  "license": "MIT",
6
6
  "type": "module",
@@ -28,11 +28,12 @@
28
28
  },
29
29
  "peerDependencies": {
30
30
  "@sema-agent/agent-types": ">=0.2.0",
31
- "@sema-agent/sdk": ">=4.1.0"
31
+ "@sema-agent/sdk": ">=6.3.0"
32
32
  },
33
33
  "devDependencies": {
34
34
  "@sema-agent/agent-types": "^0.2.0",
35
- "@sema-agent/sdk": "^4.1.0",
35
+ "@sema-agent/core": "^5.10.0",
36
+ "@sema-agent/sdk": "^6.3.0",
36
37
  "esbuild": "^0.27.4",
37
38
  "typescript": "^6.0.2"
38
39
  }