@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
package/dist/index.js CHANGED
@@ -192,18 +192,22 @@ export * from './toolRoster.js';
192
192
  // 0.63.0(L-167;core 7.9.x):engine_notice 码册与 audience 表的镜像 + mcp.injection_dropped
193
193
  // 事实窄读器。措辞**不镜像**(上游立了 single_mint 契约:core 铸句子,消费端用它给的 message)。
194
194
  export * from './engineNoticeCodes.js';
195
- // 0.63.0 件⑧(core 7.10.0 #616):「这只 ask 是因为分类器跑不了才问人」的事实读器 + 措辞铸点。
196
- // 两条 cause 轴刻意不合并(不可用 / 熔断),`parse_error` 只在熔断轴上 —— 读器按不可用轴收窄。
195
+ // 0.63.0 件⑧(core 7.10.0 #616;**0.65.0 随 core 7.12.0 收窄**):「这只 ask 是因为分类器跑不了
196
+ // 才问人」的事实读器 + 措辞铸点。成因表两词(`error`/`timeout`),`cause` 开集读;判据轴的
197
+ // `parse_error` 判缺席(core 明说它不 stamp 到这一格)。熔断族(`AUTO_MODE_BREAKER_CAUSES` /
198
+ // `classifierBreakerOf` / `ClassifierBreakerView`)随 core 7.12.0 的 Removed(BREAKING)整只退役。
197
199
  export * from './autoModeUnavailable.js';
198
200
  // 0.64.0 件①(core 7.10.0 `OpenAICompletionsCompat.thinkingFormat`):一条 OpenAI-completions
199
201
  // 车道的模型「会不会思考、关不关得掉、用哪种拼法关」的三端公共探测 + 落笔纯函数。
200
202
  // 🔴 零 fetch:网络那半场经注入的 `ProbeSend` 端口,凭证一个字节都不经过本包(与
201
203
  // `model/catalogLoader.ts` 的 `CatalogFetchJson` 同一条纪律)。试关序是**公面承诺**,门钉死。
202
204
  export * from './modelCapabilityProbe.js';
203
- // 0.64.0 件②(core 7.10.0 `AutoModeBreakerTrip`;server 7.69.0 才投那一段):auto 分类器的
204
- // **状态面**读器与措辞铸点(诊断行 / 模型设置页 / 权限卡状态栏)。与 `autoModeUnavailable.ts` 的
205
- // **卡面**那一问刻意分家:卡答「这一刻为什么问我」,状态答「这个会话上分类器现在是什么状态」。
206
- // 🔴 `classifierBreakerOf` 是会话轴那一处的**唯一**窄读 —— `wiring_manifest` 投影臂共用它。
205
+ // 0.64.0 件②(**0.65.0 两处同批改口**:随 core 7.12.0 收掉熔断族 + B-071 把 `armed` 与
206
+ // `available` 拆成两个词 三态):auto 分类器的**状态面**读器与措辞铸点
207
+ // (诊断行 / 模型设置页 / 权限卡状态栏)。与 `autoModeUnavailable.ts` 的**卡面**那一问刻意分家:
208
+ // 卡答「这一刻为什么问我」,状态答「这个会话上分类器现在是什么状态」。
209
+ // 🔴 会话轴(熔断记录)随 core 7.12.0 的 Removed(BREAKING)整只退役;同批 B-071 拆词 ⇒ 三态 =
210
+ // 本轮不可用 / 这条腿武装了 / 本轮真的跑过(后者只从 `denial_limit_fallback` 那条肯定事实得出)。
207
211
  export * from './classifierStatus.js';
208
212
  export * from './engineToolLabelStore.js';
209
213
  export * from './fleetTaskDesc.js';
@@ -350,6 +354,12 @@ export * from './wireErrorTriage.js';
350
354
  // 本口答「该对人说什么」(文案),`preflight_rejected` 的窗与可等性**转调**前者不复制判定。
351
355
  // 壳侧对位 = cli `src/sema/resumeRefusalCopy.ts`(1.0.101 起改薄成适配层)。
352
356
  export * from './resumeRefusalCopy.js';
357
+ // · decideReceipt:B-070 / L-200(0.65.x)—— `/decide` **答了什么**的三端单一读面。
358
+ // 🔴 两件事靠它,而两件都不是「门解决了没有」(200 只是**投递受理**,sdk README §9.0.0):
359
+ // ① `handoffTaskId` —— workflow 车道把续跑交给**宿主新铸**的 run(「poll 它,不要盯那张卡」);
360
+ // ② `executionOutcome` —— 那个已决动作**最后被怎么处置**;🔴 **缺席 = 未知,禁读成 allowed**。
361
+ // 三条 workflow 车道拒绝码的一句人话同居于此(单铸;码常量在 `engineErrorCodes.ts`)。
362
+ export * from './decideReceipt.js';
353
363
  // · sessionMap:「客户端会话 id ↔ 引擎会话 id」映射单一键形 + merge 判定(A-028.12;存储经
354
364
  // SessionMapStorePort 归端 —— cli 文件锁/原子写,web localStorage)。
355
365
  export * from './sessionMap.js';
@@ -7,6 +7,9 @@
7
7
  import { hostEnv } from './hostEnv.js';
8
8
  import { unrefTimer } from './unrefTimer.js';
9
9
  import { clearEnginePanelTaskResident, publishEngineAgentPanelEvent, } from './engineAgentPanelStore.js';
10
+ // L-215②(0.65.0;core [6908]):「非成功终局」的**单铸谓词** —— 面板 settle 的 `isError` 位与
11
+ // 三端自己的后台白名单必须读同一个判据;内联一个 `=== 'failed' || === 'killed'` 就是第二份会漂的台账。
12
+ import { isTerminalNotSuccess, isTerminalStatus } from './runTerminal.js';
10
13
  function escapeXml(v) {
11
14
  return v.replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;');
12
15
  }
@@ -165,7 +168,9 @@ export function parseTranscriptNotificationSeeds(messages) {
165
168
  continue;
166
169
  const external = /\btype="external"/.test(attrs);
167
170
  renderedKeys.push(`${external ? 'external:' : ''}${taskId}:${status}`);
168
- if (!external && (status === 'completed' || status === 'failed' || status === 'killed')) {
171
+ // L-215② 族扫(0.65.0):终局判据走单铸谓词 —— 修前这里手抄三词,core [6908] `blocked`
172
+ // 漏在外面 ⇒ 一条已经渲过的 `blocked` 通知不进 `notifiedKeys`,重开会话后**再投一次**。
173
+ if (!external && isTerminalStatus(status)) {
169
174
  notifiedKeys.push(taskId);
170
175
  }
171
176
  seeded++;
@@ -893,7 +898,11 @@ export function enqueueBgChildNotification(n) {
893
898
  publishEngineAgentPanelEvent({
894
899
  kind: 'end',
895
900
  taskId: n.taskId,
896
- isError: n.status === 'failed' || n.status === 'killed',
901
+ // L-215②(0.65.0):读**单铸谓词**而不是内联两词 —— 修前这里只认 `failed`/`killed`,
902
+ // 而 core [6908] 的 `blocked` 是 **agent 自报的终态**(不是等人)⇒ 一条自报走不下去的
903
+ // 后台 run 在面板上被 settle 成**成功**。🔴 `suspended`/`needs_review` 仍不在表里
904
+ // (那两词是「等一次人的决定」,判成终局会把一条正等着你的 run 在面板上判死)。
905
+ isError: isTerminalNotSuccess(n.status),
897
906
  });
898
907
  }
899
908
  catch {
@@ -192,3 +192,51 @@ export declare function runTerminalCode(read: RunTerminalRead | null): string |
192
192
  * 非 paused 的读数、以及两个来源都答不出的 paused,一律 `false`(更宽的那一句兜底)。
193
193
  */
194
194
  export declare function isReviewPark(read: RunTerminalRead | null): boolean;
195
+ /**
196
+ * **非成功终局**的状态词(闭集;0.65.0 新铸,L-215② / core [6908])——「这条 run **不会再自己动了**,
197
+ * 而且它没有成功」。
198
+ *
199
+ * 🔴 三员各有出处,且 **`blocked` 与 `suspended`/`needs_review` 的分界是本表存在的全部理由**
200
+ * (core [6908] 定谳):
201
+ * · `failed` —— 引擎自报失败;
202
+ * · `killed` —— 外力终止;
203
+ * · `blocked` —— **agent 自报的终态**:它自己判定这条 run 走不下去了(core `terminal.blocked`
204
+ * 的 `reason` 是它的说明)。🔴 它**不是「等人」** —— 这正是修前被漏掉的那一格;
205
+ * · 🔴 **`suspended` / `needs_review` 刻意不在表里**:那两个词是**等一次人的决定**,run 还活着、
206
+ * 决定给了就继续跑。把它们读成「非成功终局」会把一条**正等着你**的 run 在面板上判死,
207
+ * 而用户从此不知道有一张卡在等他 —— 与本表要修的方向相反的同一类错。
208
+ *
209
+ * 🔴 形制:`Object.freeze` 的数组,不是 `ReadonlySet`(同 `RESUME_RETRY_LATER_CODES` 的已定谳病形:
210
+ * `ReadonlySet` 只在类型面只读,而判定查的就是公面上这同一个实例)。
211
+ */
212
+ export declare const TERMINAL_NOT_SUCCESS_STATUSES: readonly string[];
213
+ /**
214
+ * 一个状态词是不是**非成功终局**({@link TERMINAL_NOT_SUCCESS_STATUSES} 的成员)。
215
+ *
216
+ * **单铸谓词**(L-215②):本包的通知链(面板 settle 的 `isError` 位)与三端各自的后台白名单
217
+ * 读的必须是**同一个判据** —— 修前包里是一处内联的 `status === 'failed' || status === 'killed'`,
218
+ * 壳里另有一份白名单,两处各写各的,于是 core 加 `blocked` 那天两边一起漏,而漏的后果是
219
+ * **面板把一条自报失败的 run 渲成成功**。
220
+ *
221
+ * 🔴 **开集外的词答 `false`,而 `false` 不等于「成功」**:它只说「这个词不在本端认得的非成功终局
222
+ * 表里」。消费点要判「成功」必须去读成功那一侧的正向证据({@link readRunTerminal} 的
223
+ * `completed` 臂),不能拿本谓词的取反当成功判据 —— 那正是「不认识的终态词折成成功」那条
224
+ * 已定谳的事故形。
225
+ * 🔴 非串 / 空串 ⇒ `false`(同上:是「答不出」,不是「成功」)。
226
+ */
227
+ export declare function isTerminalNotSuccess(status: unknown): boolean;
228
+ /**
229
+ * **终局**的状态词(闭集;0.65.0 新铸)—— 成功那一员加上 {@link TERMINAL_NOT_SUCCESS_STATUSES}
230
+ * 的三员。答的是「这条 run 还会不会自己动」,与上一张表答的「它成没成功」是**两个问题**,所以
231
+ * 两张表分开、后者由前者派生(加员只有一处要改)。
232
+ *
233
+ * 🔴 同样**不含** `suspended` / `needs_review`:那两个词是「等一次人的决定」——run 没有结束。
234
+ */
235
+ export declare const TERMINAL_STATUSES: readonly string[];
236
+ /**
237
+ * 一个状态词是不是**终局**({@link TERMINAL_STATUSES} 的成员)。
238
+ *
239
+ * 用在「这条通知/这条行是不是可以收摊了」这一问上(通知去重的 seed 扫描、后台行 settle 门)。
240
+ * 🔴 表外词 ⇒ `false` = 「本端认不出这是个终局」,**不是**「它还在跑」;要判「在跑」请读正向证据。
241
+ */
242
+ export declare function isTerminalStatus(status: unknown): boolean;
@@ -187,3 +187,62 @@ export function isReviewPark(read) {
187
187
  return true;
188
188
  return read !== null && read.kind === 'paused' && read.flatStatus === FLAT_REVIEW_STATUS;
189
189
  }
190
+ /**
191
+ * **非成功终局**的状态词(闭集;0.65.0 新铸,L-215② / core [6908])——「这条 run **不会再自己动了**,
192
+ * 而且它没有成功」。
193
+ *
194
+ * 🔴 三员各有出处,且 **`blocked` 与 `suspended`/`needs_review` 的分界是本表存在的全部理由**
195
+ * (core [6908] 定谳):
196
+ * · `failed` —— 引擎自报失败;
197
+ * · `killed` —— 外力终止;
198
+ * · `blocked` —— **agent 自报的终态**:它自己判定这条 run 走不下去了(core `terminal.blocked`
199
+ * 的 `reason` 是它的说明)。🔴 它**不是「等人」** —— 这正是修前被漏掉的那一格;
200
+ * · 🔴 **`suspended` / `needs_review` 刻意不在表里**:那两个词是**等一次人的决定**,run 还活着、
201
+ * 决定给了就继续跑。把它们读成「非成功终局」会把一条**正等着你**的 run 在面板上判死,
202
+ * 而用户从此不知道有一张卡在等他 —— 与本表要修的方向相反的同一类错。
203
+ *
204
+ * 🔴 形制:`Object.freeze` 的数组,不是 `ReadonlySet`(同 `RESUME_RETRY_LATER_CODES` 的已定谳病形:
205
+ * `ReadonlySet` 只在类型面只读,而判定查的就是公面上这同一个实例)。
206
+ */
207
+ export const TERMINAL_NOT_SUCCESS_STATUSES = Object.freeze([
208
+ 'failed',
209
+ 'killed',
210
+ 'blocked',
211
+ ]);
212
+ /**
213
+ * 一个状态词是不是**非成功终局**({@link TERMINAL_NOT_SUCCESS_STATUSES} 的成员)。
214
+ *
215
+ * **单铸谓词**(L-215②):本包的通知链(面板 settle 的 `isError` 位)与三端各自的后台白名单
216
+ * 读的必须是**同一个判据** —— 修前包里是一处内联的 `status === 'failed' || status === 'killed'`,
217
+ * 壳里另有一份白名单,两处各写各的,于是 core 加 `blocked` 那天两边一起漏,而漏的后果是
218
+ * **面板把一条自报失败的 run 渲成成功**。
219
+ *
220
+ * 🔴 **开集外的词答 `false`,而 `false` 不等于「成功」**:它只说「这个词不在本端认得的非成功终局
221
+ * 表里」。消费点要判「成功」必须去读成功那一侧的正向证据({@link readRunTerminal} 的
222
+ * `completed` 臂),不能拿本谓词的取反当成功判据 —— 那正是「不认识的终态词折成成功」那条
223
+ * 已定谳的事故形。
224
+ * 🔴 非串 / 空串 ⇒ `false`(同上:是「答不出」,不是「成功」)。
225
+ */
226
+ export function isTerminalNotSuccess(status) {
227
+ return typeof status === 'string' && TERMINAL_NOT_SUCCESS_STATUSES.includes(status);
228
+ }
229
+ /**
230
+ * **终局**的状态词(闭集;0.65.0 新铸)—— 成功那一员加上 {@link TERMINAL_NOT_SUCCESS_STATUSES}
231
+ * 的三员。答的是「这条 run 还会不会自己动」,与上一张表答的「它成没成功」是**两个问题**,所以
232
+ * 两张表分开、后者由前者派生(加员只有一处要改)。
233
+ *
234
+ * 🔴 同样**不含** `suspended` / `needs_review`:那两个词是「等一次人的决定」——run 没有结束。
235
+ */
236
+ export const TERMINAL_STATUSES = Object.freeze([
237
+ 'completed',
238
+ ...TERMINAL_NOT_SUCCESS_STATUSES,
239
+ ]);
240
+ /**
241
+ * 一个状态词是不是**终局**({@link TERMINAL_STATUSES} 的成员)。
242
+ *
243
+ * 用在「这条通知/这条行是不是可以收摊了」这一问上(通知去重的 seed 扫描、后台行 settle 门)。
244
+ * 🔴 表外词 ⇒ `false` = 「本端认不出这是个终局」,**不是**「它还在跑」;要判「在跑」请读正向证据。
245
+ */
246
+ export function isTerminalStatus(status) {
247
+ return typeof status === 'string' && TERMINAL_STATUSES.includes(status);
248
+ }
package/dist/seam.d.ts CHANGED
@@ -59,6 +59,18 @@ export interface AdapterContext {
59
59
  /** 兜底铸号——仅当帧无稳定键时使用(见 deriveTranscriptId)。 */
60
60
  uuid(): string;
61
61
  now(): number;
62
+ /**
63
+ * L-215③(0.65.0)—— **这条流跑在哪个模型上**,由宿主开流时钉(同 `sessionId` 一类:请求是宿主
64
+ * 构造的,模型 id 是它自己写进 `TaskRequest` 的那一个)。
65
+ *
66
+ * 用途:本层铸出的 assistant 转录消息的 `message.model`。**取值序 = 帧优先、ctx 兜底** ——
67
+ * 上游投影器(`eventToSdkMessage` 的 `assistantArm`)已经在 durable 内容帧上铸过这一位,本层
68
+ * 原样接住;只有 **live 分段腿**(`stream_event` 锚)帧上没有它,才回落到这一格。
69
+ * 🔴 **缺席 ⇒ 那一位不铸,绝不猜**(同 {@link import('./adapter/types.js').EmitContext.model})。
70
+ * ⚠️ 它是「这条流**声明**的模型」,不是「这一轮真正服务的模型」:mid-run degrade / 网关重路由
71
+ * 不改写它(与 `TaskResult.model` 同一条纪律)。
72
+ */
73
+ model?: string;
62
74
  /**
63
75
  * 诊断日志(替 cli `utils/debug.logForDebugging` —— 那一行 import 就把 1565 个文件拉进闭包,
64
76
  * 是浏览器可移植性的头号污染源,设计稿 §2.1.3)。缺席 = 内核静默(绝不回落 console)。
@@ -362,6 +374,22 @@ export type ChromeEvent = {
362
374
  laneProof: LaneProof;
363
375
  usage: ModelUsage;
364
376
  engineUsage?: EngineTurnUsage;
377
+ /**
378
+ * L-215③(0.65.0)—— core 的 `turn_end.usageMissing`:这一轮**没有** provider usage 帧
379
+ * (臂注逐字「Consumers must treat the missing usage as UNKNOWN — **not zero**」;它与
380
+ * `usage` 可以同帧)。🔴 在场 ⇒ 同行那份 `usage` **不是一笔已知的账**,别当真值落进
381
+ * statusline / 账单槽;缺席 = 这一轮的账是真的。本位**只在 true 时在场**,绝不铸 false。
382
+ * ⚠️ 它不能靠「不发 `usage`」表达:`usage` 是本臂的必填位(宿主义务是「落最近一次 turn
383
+ * 真 usage」),所以「不知道」只能以判别位在场。
384
+ */
385
+ usageMissing?: true;
386
+ /**
387
+ * L-215③ —— core 的 `turn_end.stopReason` **原词**透传(归一化后的五词
388
+ * `stop`/`length`/`toolUse`/`error`/`aborted`;sdk 型面是开放 `string` ⇒ **按开集读**)。
389
+ * 「这一轮是不是被 max_tokens 截了」这条机读位此前 stream 与 trace 两面互盲。
390
+ * 🔴 缺席 ⇒ 键不铸,**绝不**读作 `"stop"`。
391
+ */
392
+ stopReason?: string;
365
393
  }
366
394
  /**
367
395
  * B3 新臂 ③(runStream 外向边切除,设计稿 §5.2 头号污染源)—— `done{status:"needs_review"}` +
@@ -390,7 +418,29 @@ export type ChromeEvent = {
390
418
  * `actor.hostAsserted` 是消费端唯一能判「这个署名可信吗」的位:渲署名而不渲这个位 = 把一个
391
419
  * 未经验证的名字渲成可信的(core 自己的 `[from …]` 渲染就是靠它决定加不加 `(unverified)`)。
392
420
  */
393
- | HumanInputChromeEvent | EngineNoticeChromeEvent | TextSegmentEndChromeEvent | WiringManifestChromeEvent | ResultTextDivergedChromeEvent;
421
+ | HumanInputChromeEvent
422
+ /**
423
+ * B-072 ④(0.65.0;core message-identity Phase 1,server ≥7.69.0 **live 腿**真发)——
424
+ * 一条**已经流过**的可渲染消息在会话树里落账了。`entryId` 是持久、副本稳定的条目 id
425
+ * (uuidv7),与 `compacted.preserved_segment.firstKeptEntryId` 是**同一个 id 空间**。
426
+ *
427
+ * 🔴 **它存在的唯一理由是解析压缩分割线**:core 亲口说消费方用本帧自建 `entryId → message`
428
+ * 映射,再拿 `_sema_preserved_segment` 去查「保留尾段从哪条消息开始」。没有本帧,那个超集
429
+ * 键就是一个查不出东西的锚。绑定规则同 core:`toolResult` 腿按 `toolCallId` 绑,
430
+ * user/assistant 腿按**流序**绑(本帧是刚刚流完那段内容的终止边界)。
431
+ * 🔴 **本帧不带正文** ⇒ 宿主**不许**据此铸任何 transcript 行(同 `human_input` 那条硬约束);
432
+ * 义务是**可选**的:落一张 `entryId → 已渲消息` 的映射表即可,不落 = 压缩分割线画不出来,
433
+ * 别的什么都不受影响。
434
+ * ⚠️ **只有 live 腿有本帧**(server 7.69.0 亲读:durable/bg 腿把它喂给 rewind 锚而不入账本)
435
+ * ⇒ replay/resume 上映射表是空的;那时正解是**不画分割线**,不是回退成「画在最后一条」。
436
+ */
437
+ | MessageCommittedChromeEvent | EngineNoticeChromeEvent | TextSegmentEndChromeEvent | WiringManifestChromeEvent | ResultTextDivergedChromeEvent
438
+ /**
439
+ * B-078 / L-208(0.65.0):design/172 流内审批协议的**两条帧**。修前它们在标准管线上落
440
+ * `dropped('unsupported_arm')`,壳自己另接一份 ⇒ 只走包管线的宿主(desktop/web)拿不到
441
+ * 卡集与撤卡。契约本体在两个接口的头注(那是宿主要读的那一份)。
442
+ */
443
+ | ApprovalRequestChromeEvent | ApprovalRevokeChromeEvent;
394
444
  /**
395
445
  * {@link ChromeEvent} 的 `wiring_manifest` 臂(core #524 + core 147③,server ≥7.58.0)——
396
446
  * 引擎接线自述里**三段面向终端用户的事实**(S-124 起 `mcp[]` 是第三段),其余每一段仍不投影(射程见 eventToSdkMessage 的
@@ -480,6 +530,73 @@ export interface TextSegmentEndChromeEvent {
480
530
  /** core 铸的事件身份(uuidv7 形);wire 未必带 ⇒ 缺席时本键不在场。 */
481
531
  eventId?: string;
482
532
  }
533
+ /**
534
+ * {@link ChromeEvent} 的 `approval_request` 臂(B-078 / L-208,0.65.0;design/172 流内审批协议,
535
+ * server ≥7.3.0、7.5.0 起默认开)——「**引擎在这条流上呈了一张审批卡**」。
536
+ *
537
+ * ── 为什么 0.65.0 才有它(归层修,不是新功能)──────────────────────────────────────────────
538
+ * 本臂之前,标准管线对 `approval_request` / `approval_revoke` 两帧的处置是
539
+ * `dropped('unsupported_arm')` —— 有痕,但**包内零消费口**。壳(cli)于是在自己的
540
+ * `approvalStreamWire.ts` 里单独接了这两帧,而 desktop / web **只走包管线** ⇒ 它们
541
+ * ①开流 preamble 恢复不了 pending 卡集、②收到撤卡帧后本地卡不清。同一条 wire 上的同一件事,
542
+ * 一个端有、两个端没有 —— 那正是归层要根治的形(判定归包、呈现归端)。
543
+ * ⇒ 本臂把两帧**送到宿主手上**;卡的呈现与决断仍归端。
544
+ *
545
+ * ── 🔴 载荷是**信封**,不是 v1 卡(sdk 的顶注当场挡下来的那条)──────────────────────────────
546
+ * sdk `AgentEvent` 的这一臂声明成 {@link "@sema-agent/sdk".ApprovalFrameEnvelope}(`type` +
547
+ * `schemaVersion: number` + 开集键),**刻意不是** `ApprovalRequestFrame`(那只型的判别位是
548
+ * `schemaVersion: 1` + `kind: "permission"` 两个字面量)。理由 sdk events.d.ts:961-968 逐字:
549
+ * SSE 解析腿只做 `JSON.parse(...) as AgentEvent`、**不跑**谓词,若臂直接窄化到 v1,一条
550
+ * `schemaVersion: 2` 的合法帧会被类型系统当成 v1 端上来,消费端于是心安理得地去取
551
+ * `card.risk.requiresRealApproval` —— 而未知版本的卡根本不保证有这些键。
552
+ * ⇒ 本臂**原样窄读**:只保证 `frame` 是一只对象、`schemaVersion` 是一个数;**不替宿主窄化**。
553
+ *
554
+ * ── 🔴 宿主消费义务(不实现 = 这一面在该宿主上是哑的)────────────────────────────────────────
555
+ * ① 用 sdk 的 {@link "@sema-agent/sdk".isApprovalRequestFrameV1} 窄化 `frame` 才能拿
556
+ * `card` / `askId` / 窗三键;**窄不下来 ⇒ 呈通用卡(安全元数据 + 人工 approve/deny),
557
+ * 🔴 永不 auto-deny**(design/172 §3.1)。
558
+ * ② 按 `approvalId` 与 legacy `tool_approval` 帧**去重**:开关打开时同一只 ask 出两帧
559
+ * (顺序钉死「先 legacy、后本帧」,两帧同带 `approvalId`),优先渲本帧。
560
+ * ③ 🔴 **账本里重放的历史帧只用于时间线渲染,不是卡集基准**(它带的 `expiresInMs` 是铸帧时刻的
561
+ * 旧值)——卡集的**全量对账基准是开流 preamble**(sdk events.d.ts:974-975 逐字)。
562
+ * ④ `card` 的散文位(`message` / `args`)是**模型/工具产文**:只渲染,**永不回喂模型**。
563
+ */
564
+ export interface ApprovalRequestChromeEvent {
565
+ kind: 'approval_request';
566
+ laneProof: LaneProof;
567
+ /**
568
+ * 帧**原样**(信封视角)。🔴 本包不替宿主窄化到 v1,也不摘键重铸 —— 摘键就是在包里立第二份
569
+ * 卡形台账,而未知 `schemaVersion` 的帧上那些键根本不保证在。
570
+ */
571
+ frame: unknown;
572
+ /** 信封上的 `schemaVersion`(窄读保证是一个有限数);`!== 1` ⇒ 宿主走通用卡腿。 */
573
+ schemaVersion: number;
574
+ }
575
+ /**
576
+ * {@link ChromeEvent} 的 `approval_revoke` 臂(B-078 / L-208,0.65.0;design/172 §3.3 批级撤卡帧,
577
+ * [5924]/S-58,server ≥7.55.0 起也进 durable 账本)——「**这一批卡不该再被决断了**」。
578
+ *
579
+ * 与 {@link ApprovalRequestChromeEvent} **同源同纪律**(载荷同是信封;窄化谓词是
580
+ * {@link "@sema-agent/sdk".isApprovalRevokeFrameV1},窄不下来按通用撤卡处理:**该批本地卡照清**)。
581
+ *
582
+ * ── 🔴 宿主消费义务(sdk events.d.ts:986-988 的三纪律,逐条搬来)────────────────────────────
583
+ * ① **按 `taskId` 归属/过滤**(server ≥7.55.0 恒带):分发集合是 broker 的 session 形,晚入集合的
584
+ * run 账本里可以出现**无配对卡帧的孤儿撤帧行** —— 非我即忽略;
585
+ * ② **账本重放的撤帧只用于时间线渲染**,卡集对账基准恒为开流 preamble(撤帧**可能整帧丢失** ——
586
+ * 帧是通知,行才是真源);
587
+ * ③ 已 `DECIDED` 的兄弟**不在** `askIds` 里(决议已被接受,不受撤卡影响)。
588
+ * 🔴 清卡时要给用户**一行归因**(`reason` 是引擎生成文本,消毒后呈现):一张卡凭空消失比一条
589
+ * 「引擎撤回了这张卡」更坏 —— 用户对前者没有任何线索。零命中时**不要**多说那一行
590
+ * (headless 车道自动拒的卡从没呈现过,对它归因是凭空多说一句话)。
591
+ */
592
+ export interface ApprovalRevokeChromeEvent {
593
+ kind: 'approval_revoke';
594
+ laneProof: LaneProof;
595
+ /** 帧**原样**(信封视角);窄化归宿主,理由与呈卡臂逐字相同。 */
596
+ frame: unknown;
597
+ /** 信封上的 `schemaVersion`(窄读保证是一个有限数);`!== 1` ⇒ 宿主走通用撤卡腿。 */
598
+ schemaVersion: number;
599
+ }
483
600
  /**
484
601
  * {@link ChromeEvent} 的 `result_text_diverged` 臂(0.62.0)——「**终帧正文与已上屏的正文对不上**」。
485
602
  *
@@ -579,6 +696,19 @@ export interface HumanInputChromeEvent {
579
696
  * 落审计/时间线时**按这个键幂等**(`inputId`/`entryId` 都是可选位,靠不住)。 */
580
697
  eventId?: string;
581
698
  }
699
+ /** {@link ChromeEvent} 的 `message_committed` 臂(具名:本仓 typeshape 门对内联匿名形有棘轮)。 */
700
+ export interface MessageCommittedChromeEvent {
701
+ kind: 'message_committed';
702
+ laneProof: LaneProof;
703
+ /** 持久、副本稳定的会话树条目 id(uuidv7)—— 与 `compacted` 的 `firstKeptEntryId` 同一 id 空间。 */
704
+ entryId: string;
705
+ /** core 今天三词 `user` / `assistant` / `toolResult`,**按开集读**(上游加词照过)。 */
706
+ role: string;
707
+ /** 只在 `role === 'toolResult'` 腿在场:绑回已渲那张工具卡的 join 键。 */
708
+ toolCallId?: string;
709
+ /** 引擎铸的稳定事件身份 —— 重放同一条账本帧时宿主**按这个键幂等**。 */
710
+ eventId?: string;
711
+ }
582
712
  /** `ChromeEvent` 的判别键(臂名)—— 覆盖率断言与宿主装配自检都锚在它上。 */
583
713
  export type ChromeArmKind = ChromeEvent['kind'];
584
714
  /**
package/dist/seam.js CHANGED
@@ -37,6 +37,12 @@ const CHROME_ARM_TABLE = {
37
37
  plan_review_park: { required: true, duty: '弹 plan 审批卡(fail-soft,不挡后续终态帧)' },
38
38
  // FIX⑦:账本帧,**不带正文** ⇒ 义务是可选的(记进审计/时间线即可);🔴 硬约束是**不许**据此
39
39
  // 铸转录行,渲署名时必须带上 `actor.hostAsserted` 的可信度标注。
40
+ // B-072 ④:定位账本帧,**不带正文** ⇒ 义务可选(落一张 entryId→已渲消息的映射表);
41
+ // 🔴 硬约束同 human_input:不许据此铸任何 transcript 行。
42
+ message_committed: {
43
+ required: false,
44
+ duty: '可选:落一张 entryId → 已渲消息 的映射表(压缩分割线 `_sema_preserved_segment` 靠它解析;toolResult 按 toolCallId 绑、其余按流序绑)。绝不铸 transcript 行(本帧不带正文);只有 live 腿有本帧,replay/resume 上映射表为空时不画分割线而不是画在最后一条',
45
+ },
40
46
  human_input: {
41
47
  required: false,
42
48
  duty: '可选:记进会话审计/时间线(谁把什么喂进了这条 run)。绝不铸 transcript 行(本帧不带正文);渲署名必须跟渲 actor.hostAsserted 的可信度标注',
@@ -63,6 +69,22 @@ const CHROME_ARM_TABLE = {
63
69
  '把终帧那份接在屏上那段后面会拼出一段谁都没说过的话。不接 = 这条披露看不见(不是报错),' +
64
70
  '但绝不许把它渲成「已完成」的正面确认;两个位是长度不是内容,别拿它们当正文来源',
65
71
  },
72
+ approval_request: {
73
+ // 🔴 `required: true` —— 不接 = 这个宿主上**流内审批卡整面哑掉**(重连后 pending 卡恢复不了),
74
+ // 而卡是「用户不点就不往下走」的东西,属「已发生的行为丢失」那一档,不是可选披露面。
75
+ required: true,
76
+ duty: '用 sdk isApprovalRequestFrameV1 窄化 frame 才取 card/askId/窗三键;窄不下来 ⇒ 呈通用卡(安全元数据 + 人工 approve/deny),' +
77
+ '🔴 永不 auto-deny;按 approvalId 与 legacy tool_approval 帧去重(同一只 ask 出两帧,优先渲本帧);' +
78
+ '🔴 账本重放的历史帧只用于时间线渲染,卡集的全量对账基准是开流 preamble(它带的 expiresInMs 是铸帧时刻的旧值);' +
79
+ 'card 的 message/args 是模型/工具产文,只渲染永不回喂模型',
80
+ },
81
+ approval_revoke: {
82
+ // 🔴 `required: true` —— 不接 = 撤销之后本地卡**不清**,用户对着一张已经决断不了的卡按 Yes。
83
+ required: true,
84
+ duty: '用 sdk isApprovalRevokeFrameV1 窄化 frame 取 batchId/askIds/taskId;窄不下来按通用撤卡处理(该批本地卡照清);' +
85
+ '🔴 三纪律:①按 taskId 归属过滤(孤儿撤帧行非我即忽略)②账本重放的撤帧只用于时间线渲染,卡集基准恒为开流 preamble' +
86
+ '(撤帧可能整帧丢失)③已 DECIDED 的兄弟不在 askIds 里;真撤到本地卡时给一行归因(reason 消毒后呈现),零命中不要多说那一行',
87
+ },
66
88
  text_segment_end: {
67
89
  required: false,
68
90
  duty: '可选:引擎明报的 assistant 散文段边界(#323/core #447)。🔴 content 是对账/定界用的权威全文,拿它再渲一行 = 同一段上屏两遍;缺席只表示「没报」,绝不等于「段没结束」——要退回自家启发式必须按整条流判、不按单帧判',