@sema-agent/client-core 0.67.2 → 0.68.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.
Files changed (50) hide show
  1. package/CHANGELOG.md +310 -0
  2. package/README.md +65 -1
  3. package/dist/adapt/arms.js +67 -9
  4. package/dist/adapt/textStream.d.ts +102 -1
  5. package/dist/adapt/textStream.js +169 -6
  6. package/dist/adapt/turnFlags.d.ts +14 -0
  7. package/dist/adapt/turnFlags.js +4 -1
  8. package/dist/adapt.js +4 -1
  9. package/dist/adapter/activeRunSelfHeal.d.ts +53 -6
  10. package/dist/adapter/activeRunSelfHeal.js +79 -8
  11. package/dist/adapter/downstream/eventToSdkMessage.d.ts +18 -1
  12. package/dist/adapter/downstream/eventToSdkMessage.js +50 -9
  13. package/dist/adapter/downstream/terminalToSdkResult.d.ts +29 -0
  14. package/dist/adapter/downstream/terminalToSdkResult.js +46 -15
  15. package/dist/adapter/runStream.d.ts +22 -2
  16. package/dist/adapter/runStream.js +139 -38
  17. package/dist/adapter/types.d.ts +4 -1
  18. package/dist/autoModeUnavailable.d.ts +17 -9
  19. package/dist/autoModeUnavailable.js +26 -8
  20. package/dist/classifierStatus.d.ts +32 -4
  21. package/dist/classifierStatus.js +5 -3
  22. package/dist/controlRouter.d.ts +16 -0
  23. package/dist/controlRouter.js +6 -0
  24. package/dist/engineErrorCodes.d.ts +52 -0
  25. package/dist/engineErrorCodes.js +117 -0
  26. package/dist/engineNoticeCodes.d.ts +95 -1
  27. package/dist/engineNoticeCodes.js +124 -1
  28. package/dist/gateVocabulary.d.ts +18 -7
  29. package/dist/gateVocabulary.js +21 -8
  30. package/dist/hitl/parkResolver.d.ts +0 -14
  31. package/dist/hitl/parkResolver.js +22 -9
  32. package/dist/hitl/toolApprovalWire.d.ts +2 -1
  33. package/dist/hitl/toolApprovalWire.js +1 -0
  34. package/dist/ownKey.d.ts +34 -0
  35. package/dist/ownKey.js +36 -0
  36. package/dist/request/taskRequest.d.ts +6 -6
  37. package/dist/request/taskRequest.js +45 -0
  38. package/dist/retryStatus.d.ts +13 -2
  39. package/dist/retryStatus.js +4 -1
  40. package/dist/runTerminal.d.ts +87 -14
  41. package/dist/runTerminal.js +89 -15
  42. package/dist/seam.d.ts +78 -8
  43. package/dist/seam.js +16 -2
  44. package/dist/toolResult.js +8 -0
  45. package/dist/toolRoster.d.ts +34 -2
  46. package/dist/toolRoster.js +16 -2
  47. package/dist/workflowClient.d.ts +22 -0
  48. package/dist/workflowClient.js +37 -0
  49. package/docs/INTEGRATION-CLIENTS.md +633 -9
  50. package/package.json +2 -2
@@ -380,6 +380,57 @@ export const DECIDE_WORKFLOW_LANE_CODES = Object.freeze([
380
380
  DECIDE_WORKFLOW_HOST_NOT_PARKED,
381
381
  DECIDE_WORKFLOW_REMEMBER_UNSUPPORTED,
382
382
  ]);
383
+ // ══ 0.68.0 提货批(core 7.17.0 #652;server [7084] D 段)—— workflow **park 真相**四拒码 ══════
384
+ //
385
+ // 背景一句话:workflow run 记录从 7.17.0 起带 `parks[]`(准入输入),而「一条 run 的 park 到底是
386
+ // 什么状态」是 **resume 的前提**。读不出 / 对不上 / 不在 pending / 压根没有 run 店 —— 四种情形此前
387
+ // 混在 `workflow.journal_incompatible` 的「journal entry missing」那一臂里,一个码说四件事,于是
388
+ // 消费端只能渲一句「日志不兼容」,而四条出路完全不同。core 7.17.0 把它们拆成四个码(那条旧臂同批
389
+ // 退役),`workflow.ts:602` 是码集的铸点。
390
+ //
391
+ // 🔴 **它们不是 `SCRIPT_ERROR_CODES` 的成员**(core 顶注逐字:host runtime 类)—— 别拿脚本错误那
392
+ // 张表去判它们,也别把它们塞进那张表。
393
+ // 🔴 **本包不抄 core 的句子**(单铸律,同 `engineNoticeCodes.ts` 顶注):只给码、闭集与每个码的
394
+ // `detail` 键位读法,人话用引擎给的那一句。
395
+ /** 这条 workflow 的 park 真相**读不出来**(店抛了 / 超时 / 记录不在 / 记录还在跑)。
396
+ * `detail.reason` 开集(`checkpoint_store_threw` / `store_threw` / `record_missing` /
397
+ * `record_not_terminal`…;词表属主在 core)⇒ 读得出即原样带,读不懂**不折**已知词。 */
398
+ export const WORKFLOW_PARK_TRUTH_UNREADABLE = 'workflow.park_truth_unreadable';
399
+ /** 记录说这一序号 park 着,而 checkpoint 店说那只审批**已决 / 已过期 / 被回收**。
400
+ * `detail.status` 三词(`absent` / `expired` / `resolved`)+ `detail.ordinal`;
401
+ * `detail.checkpointId` **可缺席**(店里没 id)—— 缺席 ≠ 「没有 checkpoint」。 */
402
+ export const WORKFLOW_PARK_NOT_PENDING = 'workflow.park_not_pending';
403
+ /** 记录里的 park **绑不回**它的 checkpoint(记录自相矛盾)。`detail.ordinal` 在场。 */
404
+ export const WORKFLOW_PARK_BINDING_BROKEN = 'workflow.park_binding_broken';
405
+ /** 这条 workflow 的子代会 park,而部署**没有 run 店**(或没有 checkpoint 店)⇒ 没有耐久行可供
406
+ * 宿主按它路由那只停着的审批。`detail` 恒空对象(这是**配置**问题,不是某一条 run 的事实)。 */
407
+ export const WORKFLOW_PARK_REQUIRES_RUN_STORE = 'workflow.park_requires_run_store';
408
+ /**
409
+ * workflow **park 真相**拒绝码的闭集(四员;core 7.17.0 `workflow.ts:602` 码集)。
410
+ *
411
+ * 🔴 闭的是「**这四个码是 park 真相这一族独有的**」,不是「`workflow.*` 一共有几个码」——
412
+ * 后者仍是开集(`workflow.governance_key_stripped` / `workflow.agent_option_ignored` 是通告码,
413
+ * `workflow.journal_incompatible` 是另一族拒绝码,各有各的既有处置)。
414
+ * 🔴 **绝不靠 `workflow.park_` 前缀放宽**:前缀是个命名巧合,不是契约;上游哪天加第五个码,
415
+ * 该在这里显形并被人处置,而不是被一条前缀判据无声吞进来。
416
+ * 🔴 形制:`Object.freeze` 的数组(同 {@link DECIDE_WORKFLOW_LANE_CODES})。
417
+ */
418
+ export const WORKFLOW_PARK_REFUSAL_CODES = Object.freeze([
419
+ WORKFLOW_PARK_TRUTH_UNREADABLE,
420
+ WORKFLOW_PARK_NOT_PENDING,
421
+ WORKFLOW_PARK_BINDING_BROKEN,
422
+ WORKFLOW_PARK_REQUIRES_RUN_STORE,
423
+ ]);
424
+ const WORKFLOW_PARK_REFUSAL_CODE_SET = new Set(WORKFLOW_PARK_REFUSAL_CODES);
425
+ /**
426
+ * 这个码是不是 workflow park 真相族的拒绝码({@link WORKFLOW_PARK_REFUSAL_CODES} 的成员)。
427
+ *
428
+ * 🔴 表外码 ⇒ `false` = 「本端认不出它属于这一族」,**不是**「这次 resume 没问题」。
429
+ * 🔴 非串 / 空串 ⇒ `false`;认**原始值**(消毒只进文案,不进判据)。
430
+ */
431
+ export function isWorkflowParkRefusalCode(code) {
432
+ return typeof code === 'string' && WORKFLOW_PARK_REFUSAL_CODE_SET.has(code);
433
+ }
383
434
  // ── drain / 场景执法族(A-028.11/.13 单源化,#244 族E,2026-08-15)────────────────────────────
384
435
  /**
385
436
  * server 温切 drain 门的 pre-stream 拒收码(503 + `errorCode:"draining"`;server 侧
@@ -405,3 +456,69 @@ export const MODEL_FALLBACK_INHERIT_NO_TIER_BINDING = 'inherit_no_tier_binding';
405
456
  export function asModelFallbackReason(v) {
406
457
  return v === MODEL_FALLBACK_INHERIT_NO_TIER_BINDING ? MODEL_FALLBACK_INHERIT_NO_TIER_BINDING : undefined;
407
458
  }
459
+ // ══════════════════════════════════════════════════════════════════════════════════════════════
460
+ // 0.68.0(L-246 A13)—— 引擎的 **turn 停止原词** → CC `stop_reason`(**唯一映射铸点**)
461
+ // ══════════════════════════════════════════════════════════════════════════════════════════════
462
+ //
463
+ // ── 为什么它下沉到包里 ────────────────────────────────────────────────────────────────────────
464
+ // 这张表此前住在壳里(cli `printStreamJsonContract.ts` 的 `ENGINE_STOP_REASON_TO_CC`),而它是一张
465
+ // **CC 皮肤词汇表** —— 本包的立意逐字是「Every CC-skin shape is collected here so the wire itself
466
+ // stays neutral」。住在壳里的后果是三端各铸一份:web / desktop 要渲同一个 `stop_reason` 时只能
467
+ // 自己再抄一遍五个词,而下一次 core 归一化词表变动时,漏掉的那一份会把**这一轮为什么停**渲错 ——
468
+ // 而那恰恰是出错/被截断那一刻最需要读对的一格。
469
+ //
470
+ // ── 左右两列的出处 ────────────────────────────────────────────────────────────────────────────
471
+ // 左列 = `turn_end.stopReason`(core `StopReason` 归一化后的五词;core `task-event.d.ts` 逐字:
472
+ // 「Typed `string` (not the internal `StopReason`) to keep the engine's union private, but the
473
+ // value set is exactly those five tokens」)—— 本包的 `turn_usage._sema_stop_reason` 原词透传它。
474
+ // 右列 = CC stdout 契约上的 `stop_reason`。
475
+ /**
476
+ * 引擎 turn 停止原词的**识别表**(五词;core `StopReason` 归一化后的值集)。
477
+ *
478
+ * 🔴 **它是识别表不是闭集判据**:`turn_end.stopReason` 在 sdk 型面上是开放 `string`(上游刻意让
479
+ * 引擎的 union 保持私有)⇒ 引擎比这一端新时真的会送一个第六个词过来。本表回答的是
480
+ * 「我认不认得这个词」,**绝不是**「合法的词只有这五个」。
481
+ */
482
+ export const ENGINE_STOP_REASONS = Object.freeze([
483
+ 'stop',
484
+ 'length',
485
+ 'toolUse',
486
+ 'error',
487
+ 'aborted',
488
+ ]);
489
+ /**
490
+ * 五词 → CC `stop_reason`。`null` **不是**「没映射」,是**映射到诚实缺席**。
491
+ *
492
+ * 🔴 两条纪律(逐字自壳侧原表搬入,一个字节没改语义):
493
+ * · `error` / `aborted` 在 CC 的词表里**没有对应词**,而既有结算规则对出错的一轮本来就留 `null`
494
+ * (schema 可空)⇒ 这里同样**不编一个词出来**;
495
+ * · **表外词一律不映射**(见 {@link engineStopReasonToCc}):引擎长出第六个词时既不猜也不改既有
496
+ * 结算,让它按修前那条路走完 —— 猜一个 CC 词会让消费者据此判错这一轮的结局。
497
+ */
498
+ const ENGINE_STOP_REASON_TO_CC = Object.freeze({
499
+ stop: 'end_turn',
500
+ length: 'max_tokens',
501
+ toolUse: 'tool_use',
502
+ error: null,
503
+ aborted: null,
504
+ });
505
+ /**
506
+ * 引擎停止原词 → CC `stop_reason` 的**唯一映射口**。**三态**,刻意不合并:
507
+ * · `string` —— 映到了一个 CC 词;
508
+ * · `null` —— 映到了**诚实缺席**(`error` / `aborted`:CC 没有对应词,不编一个);
509
+ * · `undefined` —— **这个词本端不认识**(表外词 / 非串 / 空串)⇒ 调用方按「不映射」走原路。
510
+ *
511
+ * 🔴 `null` 与 `undefined` 必须分开:前者是「我认得这个词,而它的正确渲法就是没有词」,后者是
512
+ * 「我不认得这个词」。合成一个值 ⇒ 引擎加第六个词那天,它会被渲成一次「这一轮没有 stop_reason」
513
+ * 的**肯定事实**,而真相是这一端读不懂 —— 本仓反复在修的那条「把不知道渲成事实」的病。
514
+ * 🔴 **按自有属性查表**(`Object.freeze` 不移除原型):一个来自 wire 的 `constructor` / `toString`
515
+ * 会命中 `Object.prototype` 上的**函数**并被当成一个 CC 词交出去。
516
+ */
517
+ export function engineStopReasonToCc(stopReason) {
518
+ if (typeof stopReason !== 'string' || stopReason.length === 0)
519
+ return undefined;
520
+ if (!Object.hasOwn(ENGINE_STOP_REASON_TO_CC, stopReason))
521
+ return undefined;
522
+ const row = ENGINE_STOP_REASON_TO_CC[stopReason];
523
+ return typeof row === 'string' || row === null ? row : undefined;
524
+ }
@@ -22,6 +22,10 @@
22
22
  * · **core 7.10.0 +2**:`delegation.ask_unresolvable`(audience **user** —— 委派链上那只 ask 判不出
23
23
  * 归属、没有人可问,这件事的收件人是发起这次委派的**用户**)/ `config.read_face_swapped`
24
24
  * (audience **operator** —— READ 容纳面被换过档,是部署事实)。四十八码 ⇒ **五十码**。
25
+ * · **core 7.17.0 +1**(0.68.0 提货批,同样由本门 A1 段当天红抓出:「上游 52 / 本包 51」):
26
+ * `config.artifact_host_invalid`(audience **operator** —— 宿主接了 artifact 席但它的
27
+ * `capabilities()` 声明不过席上的不变量、或 `onMount` 抛了 ⇒ 那只工具没挂上。上游逐字:
28
+ * 「an absent seat is silent; a broken one must not look the same」)。五十一码 ⇒ **五十二码**。
25
29
  * · **core ≤7.16.0 +1**(0.67.0 提货批,由本门的 A1 段当天红抓出 —— 不是人眼发现的):
26
30
  * `task.interrupt_unconsumed`(audience **user**;位置在 `task.late_approval` 与
27
31
  * `memory.capture_opted_out` 之间,`governance-codes.js:146` / `NOTICE_AUDIENCE_TABLE:165` 真字节)。
@@ -50,7 +54,7 @@
50
54
  /** 一条通告是**说给谁听**的。两值,刻意不设第三值(见模块顶注)。 */
51
55
  export type NoticeAudience = 'user' | 'operator';
52
56
  /**
53
- * 引擎**成文码册**的逐字镜像(core `ENGINE_NOTICE_CODES`,**≤7.16.0 = 五十一码**;顺序同源)。
57
+ * 引擎**成文码册**的逐字镜像(core `ENGINE_NOTICE_CODES`,**7.17.x = 五十二码**;顺序同源)。
54
58
  * 🔴 这是一份**抄件**,不是本包的意见 —— 改它必须同 commit 附 core 坐标,且门会先红。
55
59
  * ⚠️ `EngineNotice.code` 的型面在上游**故意留 `string`**(宿主把自己的通告转发进同一个水槽是
56
60
  * 被支持的形),所以本表是**判据**不是型 —— 别拿它去窄化那个字段。
@@ -106,3 +110,93 @@ export interface McpInjectionDropFactsView {
106
110
  * (要渲一句话时,用引擎给的 `message`,见模块顶注的单铸律。)
107
111
  */
108
112
  export declare function readMcpInjectionDrop(notice: unknown): McpInjectionDropFactsView | undefined;
113
+ /**
114
+ * `delegation.ask_unresolvable` 的三个成因(core `ASK_UNRESOLVABLE_CAUSES` 逐词镜像,
115
+ * `dist/core/ask-unresolvable-notice.d.ts:31`)。
116
+ *
117
+ * 🔴 **0.68.0 BREAKING(core #709 ②)**:这一位**取代**了 7.16.0 及以前那条通告 `detail` 上的
118
+ * `settlementKind`(core `gate-exit.ts` 的一处铸点;`settlementKind` 在 7.17.x 的 core 与 sdk
119
+ * 真字节上**零命中**,本包按 clean-cut 不留双读,登记在 `run-retired-vocabulary-census-test.mjs`)。
120
+ * 两者不是改名:`settlementKind` 只说得出「结算是怎么落的」,而 in-fold 拒绝那一臂**根本没有
121
+ * 结算**骑在帧上(`mandate_unreconstructible`)—— 旧位在那一形上恒缺席。
122
+ *
123
+ * 三词各答「为什么这只 ask 判不出来」(上游逐字):
124
+ * · `approver_unavailable` —— 被问到的审批席(本 run 自己的,或委派子代身上那张冻结的祖先席)
125
+ * 对路由问题答了 `unavailable`,而其后**没有**耐久 park 接下这次调用;
126
+ * · `no_approver_wired` —— 这条 run 根本没接审批席(或问答面),或者席位就是拒绝姿态串
127
+ * (headless 自动拒);
128
+ * · `mandate_unreconstructible` —— 继承来的耐久审批 mandate 绑住了这只 ask,而**这条腿没有 park
129
+ * 设施**去兑现它,于是委派子代就地拒绝(in-fold 拒,帧上不带 settlement)。
130
+ *
131
+ * 🔴 形制:`Object.freeze` 的数组(同 `MCP_INJECTION_DROP_REASONS` 的已定谳病形)。
132
+ */
133
+ export declare const ASK_UNRESOLVABLE_CAUSES: readonly string[];
134
+ /** `delegation.ask_unresolvable` 通告 `detail` 上的事实(引擎在拒绝出口知道的那几件)。 */
135
+ export interface AskUnresolvableFactsView {
136
+ /** 这条通告投给哪个会话。**必填** —— user audience 的通告没有会话就无处投递(上游在铸点抛)。 */
137
+ sessionId: string;
138
+ /** 这只 ask 是为哪个工具问的(引擎的 message 里逐字引了它)。 */
139
+ toolName: string;
140
+ /** 被拒的那次调用 id —— 与带着同一次拒绝的 `tool_end` 帧对得上的关联键。 */
141
+ toolCallId: string;
142
+ /** 三词之一({@link ASK_UNRESOLVABLE_CAUSES})。 */
143
+ cause: string;
144
+ /**
145
+ * 这条 run 上**有没有**武装过耐久审批 park 车道。
146
+ * `true` = park 有过它那一次机会而没接下(它拒了或失败了);`false` = 压根没有 park 车道(常见形)。
147
+ * 🔴 **读不出 ⇒ 键不铸**(不折 `false`):「没有 park 车道」与「不知道有没有」的下一步不同 ——
148
+ * 前者该去接一个 checkpoint 店,后者该去问引擎为什么没说。
149
+ */
150
+ parkLaneExisted?: boolean;
151
+ }
152
+ /**
153
+ * `delegation.ask_unresolvable` 通告 → 事实;不成形 ⇒ `undefined`,绝不抛出。
154
+ *
155
+ * 🔴 `cause` **按闭集读**:它是消费端分支的键(三条恢复路不同 —— 接一个耐久店 / 接一个审批席 /
156
+ * 别在无 park 设施的腿上继承 mandate)。读不懂的词 ⇒ **整只缺席**,而不是交一个分不了支的串。
157
+ */
158
+ export declare function readAskUnresolvable(notice: unknown): AskUnresolvableFactsView | undefined;
159
+ /**
160
+ * 一条腿上的耐久审批 **mandate 从哪儿来**(core `DURABLE_MANDATE_SOURCES` 逐词镜像,
161
+ * `dist/core/tool-policy.d.ts:1403`)。两词的**恢复动作不同**,这正是上游把它铸上通告的理由:
162
+ * · `force_durable_gate` —— per-principal 的控制面权利,与席位无关 ⇒ 要么给这套部署接一个
163
+ * checkpoint 店,要么撤掉这批 principal 的这项权利;
164
+ * · `durable_approval` —— 任务自己的 opt-in 且**没有**活审批席 ⇒ 给任务一个活席位,或者不要
165
+ * 再传 `durableApproval`。
166
+ *
167
+ * 🔴 形制同上(冻结数组)。
168
+ */
169
+ export declare const DURABLE_MANDATE_SOURCES: readonly string[];
170
+ /** `config.durable_gate_unavailable` 通告 `detail` 上的事实。 */
171
+ export interface DurableGateUnavailableFactsView {
172
+ /** 这条通告投给哪个会话(user audience ⇒ 必填)。 */
173
+ sessionId: string;
174
+ /** 哪一条 run。 */
175
+ runId: string;
176
+ /** 这条腿服务的 principal;上游只在有值时铸 ⇒ 读不出即缺席。 */
177
+ principal?: string;
178
+ /**
179
+ * **哪一个 mandate 没被兑现**({@link DURABLE_MANDATE_SOURCES} 之一)。
180
+ * 🔴 读不懂的词 ⇒ **整只缺席**(它是恢复动作的分支键;猜错会把人指去撤一项他根本没开的权利)。
181
+ */
182
+ source: string;
183
+ /**
184
+ * **为什么没有 park 设施**:上游今天两词 `no_deployment_store`(这套部署一个店都没接)/
185
+ * `task_store_disabled`(`TaskSpec.checkpointStore: "disabled"`,这一次 run 自己关掉的)。
186
+ * 🔴 **开集透传**:core 今天**不导出**这两个词的闭集(它在铸点是一个三元表达式,
187
+ * `prepare-wiring-manifest.js:90`)⇒ 抄一份就是本包自铸词表,加员那天还是本包先错。
188
+ * 非空串即原样带回,词表属主在 core;要分支的消费端自己按已知词判,别拿它当穷举。
189
+ */
190
+ cause: string;
191
+ /** 这条腿的权限 ask **会不会**由一个活席位当场答(`false` ⇒ fail-closed 拒)。读不出 ⇒ 键不铸。 */
192
+ liveApprover?: boolean;
193
+ /** `AskUserQuestion` **会不会**由一个活问答面当场答。读不出 ⇒ 键不铸。 */
194
+ liveQuestionFace?: boolean;
195
+ }
196
+ /**
197
+ * `config.durable_gate_unavailable` 通告 → 事实;不成形 ⇒ `undefined`,绝不抛出。
198
+ *
199
+ * 🔴 **两个布尔位缺席不折 `false`**:core 在铸点把它们写成必填,但「这一格读不出」与
200
+ * 「这一格是 false」对用户的下一步不同(前者是「引擎没说」,后者是「没有人会答你这条 ask」)。
201
+ */
202
+ export declare function readDurableGateUnavailable(notice: unknown): DurableGateUnavailableFactsView | undefined;
@@ -22,6 +22,10 @@
22
22
  * · **core 7.10.0 +2**:`delegation.ask_unresolvable`(audience **user** —— 委派链上那只 ask 判不出
23
23
  * 归属、没有人可问,这件事的收件人是发起这次委派的**用户**)/ `config.read_face_swapped`
24
24
  * (audience **operator** —— READ 容纳面被换过档,是部署事实)。四十八码 ⇒ **五十码**。
25
+ * · **core 7.17.0 +1**(0.68.0 提货批,同样由本门 A1 段当天红抓出:「上游 52 / 本包 51」):
26
+ * `config.artifact_host_invalid`(audience **operator** —— 宿主接了 artifact 席但它的
27
+ * `capabilities()` 声明不过席上的不变量、或 `onMount` 抛了 ⇒ 那只工具没挂上。上游逐字:
28
+ * 「an absent seat is silent; a broken one must not look the same」)。五十一码 ⇒ **五十二码**。
25
29
  * · **core ≤7.16.0 +1**(0.67.0 提货批,由本门的 A1 段当天红抓出 —— 不是人眼发现的):
26
30
  * `task.interrupt_unconsumed`(audience **user**;位置在 `task.late_approval` 与
27
31
  * `memory.capture_opted_out` 之间,`governance-codes.js:146` / `NOTICE_AUDIENCE_TABLE:165` 真字节)。
@@ -48,7 +52,7 @@
48
52
  * 出现在本包源码或产物里 => 当场红。
49
53
  */
50
54
  /**
51
- * 引擎**成文码册**的逐字镜像(core `ENGINE_NOTICE_CODES`,**≤7.16.0 = 五十一码**;顺序同源)。
55
+ * 引擎**成文码册**的逐字镜像(core `ENGINE_NOTICE_CODES`,**7.17.x = 五十二码**;顺序同源)。
52
56
  * 🔴 这是一份**抄件**,不是本包的意见 —— 改它必须同 commit 附 core 坐标,且门会先红。
53
57
  * ⚠️ `EngineNotice.code` 的型面在上游**故意留 `string`**(宿主把自己的通告转发进同一个水槽是
54
58
  * 被支持的形),所以本表是**判据**不是型 —— 别拿它去窄化那个字段。
@@ -66,6 +70,9 @@ export const ENGINE_NOTICE_CODES = Object.freeze([
66
70
  'config.tool_model_gate_removed',
67
71
  'config.tool_model_gate_unknown_class',
68
72
  'config.tool_model_gate_env_invalid',
73
+ // 0.68.0 提货批(core 7.17.0 加员;位置 = `governance-codes.js:108` 真字节,在
74
+ // `config.tool_model_gate_env_invalid` 与 `config.tool_card_undeclared` 之间)。
75
+ 'config.artifact_host_invalid',
69
76
  'config.tool_card_undeclared',
70
77
  'config.tool_face_undeclared',
71
78
  'config.tool_face_invalid',
@@ -133,6 +140,8 @@ export const ENGINE_NOTICE_AUDIENCE = Object.freeze({
133
140
  'config.tool_model_gate_removed': 'operator',
134
141
  'config.tool_model_gate_unknown_class': 'operator',
135
142
  'config.tool_model_gate_env_invalid': 'operator',
143
+ // 0.68.0 提货批:`NOTICE_AUDIENCE_TABLE:181` 真字节 —— 一条「宿主把 artifact 席接坏了」的部署事实。
144
+ 'config.artifact_host_invalid': 'operator',
136
145
  'config.tool_card_undeclared': 'operator',
137
146
  'config.tool_face_undeclared': 'operator',
138
147
  'config.tool_face_invalid': 'operator',
@@ -235,3 +244,117 @@ export function readMcpInjectionDrop(notice) {
235
244
  const field = typeof o.field === 'string' && o.field.length > 0 ? o.field : undefined;
236
245
  return { sessionId, server, reason, ...(field !== undefined ? { field } : {}) };
237
246
  }
247
+ // ══════════════════════════════════════════════════════════════════════════════════════════════
248
+ // 0.68.0 提货批(core 7.17.0 #709 ①②)—— 两条通告的**事实窄读器**
249
+ // ══════════════════════════════════════════════════════════════════════════════════════════════
250
+ // 与 {@link readMcpInjectionDrop} 同一套规矩(单铸律不变:本模块只给码册、闭集与**事实**,
251
+ // 一句措辞都不抄 —— 转录用引擎给的 `message`):
252
+ // · **只认自己那一个码**(读器按码分家);
253
+ // · 必填格缺一 ⇒ **整只缺席**(读点判缺席而不抛:读点抛只会把一条读不懂的通告升级成一次崩);
254
+ // · **分支用的键按闭集读**(读不懂的词不许被当成一个能拿去分支的原因);**非分支键开集透传**。
255
+ /**
256
+ * `delegation.ask_unresolvable` 的三个成因(core `ASK_UNRESOLVABLE_CAUSES` 逐词镜像,
257
+ * `dist/core/ask-unresolvable-notice.d.ts:31`)。
258
+ *
259
+ * 🔴 **0.68.0 BREAKING(core #709 ②)**:这一位**取代**了 7.16.0 及以前那条通告 `detail` 上的
260
+ * `settlementKind`(core `gate-exit.ts` 的一处铸点;`settlementKind` 在 7.17.x 的 core 与 sdk
261
+ * 真字节上**零命中**,本包按 clean-cut 不留双读,登记在 `run-retired-vocabulary-census-test.mjs`)。
262
+ * 两者不是改名:`settlementKind` 只说得出「结算是怎么落的」,而 in-fold 拒绝那一臂**根本没有
263
+ * 结算**骑在帧上(`mandate_unreconstructible`)—— 旧位在那一形上恒缺席。
264
+ *
265
+ * 三词各答「为什么这只 ask 判不出来」(上游逐字):
266
+ * · `approver_unavailable` —— 被问到的审批席(本 run 自己的,或委派子代身上那张冻结的祖先席)
267
+ * 对路由问题答了 `unavailable`,而其后**没有**耐久 park 接下这次调用;
268
+ * · `no_approver_wired` —— 这条 run 根本没接审批席(或问答面),或者席位就是拒绝姿态串
269
+ * (headless 自动拒);
270
+ * · `mandate_unreconstructible` —— 继承来的耐久审批 mandate 绑住了这只 ask,而**这条腿没有 park
271
+ * 设施**去兑现它,于是委派子代就地拒绝(in-fold 拒,帧上不带 settlement)。
272
+ *
273
+ * 🔴 形制:`Object.freeze` 的数组(同 `MCP_INJECTION_DROP_REASONS` 的已定谳病形)。
274
+ */
275
+ export const ASK_UNRESOLVABLE_CAUSES = Object.freeze([
276
+ 'approver_unavailable',
277
+ 'no_approver_wired',
278
+ 'mandate_unreconstructible',
279
+ ]);
280
+ const ASK_UNRESOLVABLE_CAUSE_SET = new Set(ASK_UNRESOLVABLE_CAUSES);
281
+ /**
282
+ * `delegation.ask_unresolvable` 通告 → 事实;不成形 ⇒ `undefined`,绝不抛出。
283
+ *
284
+ * 🔴 `cause` **按闭集读**:它是消费端分支的键(三条恢复路不同 —— 接一个耐久店 / 接一个审批席 /
285
+ * 别在无 park 设施的腿上继承 mandate)。读不懂的词 ⇒ **整只缺席**,而不是交一个分不了支的串。
286
+ */
287
+ export function readAskUnresolvable(notice) {
288
+ if (typeof notice !== 'object' || notice === null || Array.isArray(notice))
289
+ return undefined;
290
+ const n = notice;
291
+ if (n.code !== 'delegation.ask_unresolvable')
292
+ return undefined;
293
+ const d = n.detail;
294
+ if (typeof d !== 'object' || d === null || Array.isArray(d))
295
+ return undefined;
296
+ const o = d;
297
+ const sessionId = typeof o.sessionId === 'string' && o.sessionId.length > 0 ? o.sessionId : undefined;
298
+ const toolName = typeof o.toolName === 'string' && o.toolName.length > 0 ? o.toolName : undefined;
299
+ const toolCallId = typeof o.toolCallId === 'string' && o.toolCallId.length > 0 ? o.toolCallId : undefined;
300
+ const cause = typeof o.cause === 'string' && ASK_UNRESOLVABLE_CAUSE_SET.has(o.cause) ? o.cause : undefined;
301
+ if (sessionId === undefined || toolName === undefined || toolCallId === undefined || cause === undefined) {
302
+ return undefined;
303
+ }
304
+ return {
305
+ sessionId,
306
+ toolName,
307
+ toolCallId,
308
+ cause,
309
+ ...(typeof o.parkLaneExisted === 'boolean' ? { parkLaneExisted: o.parkLaneExisted } : {}),
310
+ };
311
+ }
312
+ /**
313
+ * 一条腿上的耐久审批 **mandate 从哪儿来**(core `DURABLE_MANDATE_SOURCES` 逐词镜像,
314
+ * `dist/core/tool-policy.d.ts:1403`)。两词的**恢复动作不同**,这正是上游把它铸上通告的理由:
315
+ * · `force_durable_gate` —— per-principal 的控制面权利,与席位无关 ⇒ 要么给这套部署接一个
316
+ * checkpoint 店,要么撤掉这批 principal 的这项权利;
317
+ * · `durable_approval` —— 任务自己的 opt-in 且**没有**活审批席 ⇒ 给任务一个活席位,或者不要
318
+ * 再传 `durableApproval`。
319
+ *
320
+ * 🔴 形制同上(冻结数组)。
321
+ */
322
+ export const DURABLE_MANDATE_SOURCES = Object.freeze([
323
+ 'force_durable_gate',
324
+ 'durable_approval',
325
+ ]);
326
+ const DURABLE_MANDATE_SOURCE_SET = new Set(DURABLE_MANDATE_SOURCES);
327
+ /**
328
+ * `config.durable_gate_unavailable` 通告 → 事实;不成形 ⇒ `undefined`,绝不抛出。
329
+ *
330
+ * 🔴 **两个布尔位缺席不折 `false`**:core 在铸点把它们写成必填,但「这一格读不出」与
331
+ * 「这一格是 false」对用户的下一步不同(前者是「引擎没说」,后者是「没有人会答你这条 ask」)。
332
+ */
333
+ export function readDurableGateUnavailable(notice) {
334
+ if (typeof notice !== 'object' || notice === null || Array.isArray(notice))
335
+ return undefined;
336
+ const n = notice;
337
+ if (n.code !== 'config.durable_gate_unavailable')
338
+ return undefined;
339
+ const d = n.detail;
340
+ if (typeof d !== 'object' || d === null || Array.isArray(d))
341
+ return undefined;
342
+ const o = d;
343
+ const sessionId = typeof o.sessionId === 'string' && o.sessionId.length > 0 ? o.sessionId : undefined;
344
+ const runId = typeof o.runId === 'string' && o.runId.length > 0 ? o.runId : undefined;
345
+ const source = typeof o.source === 'string' && DURABLE_MANDATE_SOURCE_SET.has(o.source) ? o.source : undefined;
346
+ const cause = typeof o.cause === 'string' && o.cause.length > 0 ? o.cause : undefined;
347
+ if (sessionId === undefined || runId === undefined || source === undefined || cause === undefined) {
348
+ return undefined;
349
+ }
350
+ const principal = typeof o.principal === 'string' && o.principal.length > 0 ? o.principal : undefined;
351
+ return {
352
+ sessionId,
353
+ runId,
354
+ ...(principal !== undefined ? { principal } : {}),
355
+ source,
356
+ cause,
357
+ ...(typeof o.liveApprover === 'boolean' ? { liveApprover: o.liveApprover } : {}),
358
+ ...(typeof o.liveQuestionFace === 'boolean' ? { liveQuestionFace: o.liveQuestionFace } : {}),
359
+ };
360
+ }
@@ -94,10 +94,13 @@ export declare function askOriginDetail(origin: unknown, message?: unknown): str
94
94
  * 结构上到不了消费端。
95
95
  * 🔴 形制:`Object.freeze` 的数组,理由同上面两张表。
96
96
  */
97
- export declare const RULE_STORE_UNREADABLE_KINDS: readonly string[];
97
+ export declare const RULE_STORE_UNREADABLE_KINDS: readonly ["store", "call"];
98
+ /** {@link RULE_STORE_UNREADABLE_KINDS} 的成员型(端的型面改派生,不再手抄两词)。 */
99
+ export type RuleStoreUnreadableKind = (typeof RULE_STORE_UNREADABLE_KINDS)[number];
98
100
  /** 这个值是不是 {@link RULE_STORE_UNREADABLE_KINDS} 的成员(core `isRuleStoreUnreadable` 的镜像)。
99
- * 🔴 **唯一判据口**:窄读器与端都调它,谁都不许再抄一份字面量(B-025 的根因就是手抄)。 */
100
- export declare function isRuleStoreUnreadableKind(v: unknown): boolean;
101
+ * 🔴 **唯一判据口**:窄读器与端都调它,谁都不许再抄一份字面量(B-025 的根因就是手抄)。
102
+ * 🔴 0.68.0 / L-245:改成**型守卫** —— 端用它一判就拿到闭集成员型,下一步喂措辞铸点时不必 cast。 */
103
+ export declare function isRuleStoreUnreadableKind(v: unknown): v is RuleStoreUnreadableKind;
101
104
  /**
102
105
  * 一个 `ruleStoreUnreadable` 词 → 一句人话。**唯一铸点**(三端共用;端零自拼)。
103
106
  *
@@ -105,8 +108,16 @@ export declare function isRuleStoreUnreadableKind(v: unknown): boolean;
105
108
  * **哪一头**」。`rule_store_unavailable` 那只 ask 上两句都该在,渲一句丢一句就等于把人指去
106
109
  * 错的地方 —— 而这正是 core 把这一位单独铸出来的理由。
107
110
  * 🔴 **查表用 `Object.hasOwn`**(冻结不移除原型;理由与本文件另两个铸点逐字相同)。
108
- * 🔴 表外词 / 坏值 ⇒ 一句**兜底**:原样带上那个词,并明说这次仍然是在问人。
109
- * ⚠️ 与 {@link isRuleStoreUnreadableKind} 的闭集判据不矛盾:那一层答「这条事实在不在」(闭集,
110
- * 出集 = 坏形),本层答「拿到一个词怎么渲」—— 一条从旧持久态恢复回来的视图必须渲出一句诚实的话。
111
+ *
112
+ * ── 🔴 0.68.0 / L-245:入参从 `unknown` 收窄成**闭集成员型** ─────────────────────────────────
113
+ * 修前这里同样留着一句「is **a word newer than this client**」的兜底,而它**结构上走不到**:
114
+ * 唯一到达本铸点的路是卡口的窄读器(`toolApprovalWire` 的 `readRuleStoreUnreadable`),那一层已经
115
+ * 按闭集把表外值判成了缺席;而这一面在上游是**真闭集**(core 在唯一 stamp 点把政策自报值剥掉之后
116
+ * 才盖章)⇒ 一个「比这一端新」的值根本到不了。
117
+ * ⚠️ 与**隔壁那一句**({@link askOriginDetail})的处置刻意相反,而两边都是对的:`AskOrigin` 在 wire
118
+ * 上是**开集**(server 只判非空串,core 加词当天合法的帧就带着它到达)⇒ 那一句兜底**真的会被
119
+ * 走到**,必须留。一套面一条规矩:闭集则收窄 + 编译期围栏,开集则保留兜底。
120
+ * 🔴 渲染路径不许抛,所以 never 分支照样交一句话;只是它现在说的是「这个词不在本端的闭集里」
121
+ * (一句真话),而不再冒充「上游比我新」。
111
122
  */
112
- export declare function ruleStoreUnreadableDetail(kind: unknown): string;
123
+ export declare function ruleStoreUnreadableDetail(kind: RuleStoreUnreadableKind): string;
@@ -203,14 +203,19 @@ void _deniedByWordsPin;
203
203
  * 结构上到不了消费端。
204
204
  * 🔴 形制:`Object.freeze` 的数组,理由同上面两张表。
205
205
  */
206
+ // 🔴 0.68.0 / L-245:形制从 `readonly string[]` 改成**字面元组**(理由同 `CLASSIFIER_DENY_CAUSES`:
207
+ // 型面交得出成员字面量,端才能派生型而不是再抄一遍两个词)。
206
208
  export const RULE_STORE_UNREADABLE_KINDS = Object.freeze(['store', 'call']);
207
209
  const RULE_STORE_UNREADABLE_SET = new Set(RULE_STORE_UNREADABLE_KINDS);
208
210
  /** 这个值是不是 {@link RULE_STORE_UNREADABLE_KINDS} 的成员(core `isRuleStoreUnreadable` 的镜像)。
209
- * 🔴 **唯一判据口**:窄读器与端都调它,谁都不许再抄一份字面量(B-025 的根因就是手抄)。 */
211
+ * 🔴 **唯一判据口**:窄读器与端都调它,谁都不许再抄一份字面量(B-025 的根因就是手抄)。
212
+ * 🔴 0.68.0 / L-245:改成**型守卫** —— 端用它一判就拿到闭集成员型,下一步喂措辞铸点时不必 cast。 */
210
213
  export function isRuleStoreUnreadableKind(v) {
211
214
  return typeof v === 'string' && RULE_STORE_UNREADABLE_SET.has(v);
212
215
  }
213
- /** 逐词一句人话。**两句刻意逐字互异** —— 两条不同的下一步(去修部署的规则店 / 去改自己那条规则)。 */
216
+ /** 逐词一句人话。**两句刻意逐字互异** —— 两条不同的下一步(去修部署的规则店 / 去改自己那条规则)。
217
+ * 🔴 0.68.0 / L-245:表型改 `Record<RuleStoreUnreadableKind, string>` —— core 加第三个机制词那天
218
+ * **这张表少一个键 ⇒ 编译期当场红**,逼人同批补那一句话(修前是 `Record<string,…>`,加词一声不响)。 */
214
219
  const RULE_STORE_UNREADABLE_SENTENCES = Object.freeze({
215
220
  store: 'your standing deny/ask rules could not be read at all (the rule store did not answer), so this call asks',
216
221
  call: 'this command could not be read against your standing deny/ask rules (the engine’s own sentence is on this ask), so it asks',
@@ -222,16 +227,24 @@ const RULE_STORE_UNREADABLE_SENTENCES = Object.freeze({
222
227
  * **哪一头**」。`rule_store_unavailable` 那只 ask 上两句都该在,渲一句丢一句就等于把人指去
223
228
  * 错的地方 —— 而这正是 core 把这一位单独铸出来的理由。
224
229
  * 🔴 **查表用 `Object.hasOwn`**(冻结不移除原型;理由与本文件另两个铸点逐字相同)。
225
- * 🔴 表外词 / 坏值 ⇒ 一句**兜底**:原样带上那个词,并明说这次仍然是在问人。
226
- * ⚠️ 与 {@link isRuleStoreUnreadableKind} 的闭集判据不矛盾:那一层答「这条事实在不在」(闭集,
227
- * 出集 = 坏形),本层答「拿到一个词怎么渲」—— 一条从旧持久态恢复回来的视图必须渲出一句诚实的话。
230
+ *
231
+ * ── 🔴 0.68.0 / L-245:入参从 `unknown` 收窄成**闭集成员型** ─────────────────────────────────
232
+ * 修前这里同样留着一句「is **a word newer than this client**」的兜底,而它**结构上走不到**:
233
+ * 唯一到达本铸点的路是卡口的窄读器(`toolApprovalWire` 的 `readRuleStoreUnreadable`),那一层已经
234
+ * 按闭集把表外值判成了缺席;而这一面在上游是**真闭集**(core 在唯一 stamp 点把政策自报值剥掉之后
235
+ * 才盖章)⇒ 一个「比这一端新」的值根本到不了。
236
+ * ⚠️ 与**隔壁那一句**({@link askOriginDetail})的处置刻意相反,而两边都是对的:`AskOrigin` 在 wire
237
+ * 上是**开集**(server 只判非空串,core 加词当天合法的帧就带着它到达)⇒ 那一句兜底**真的会被
238
+ * 走到**,必须留。一套面一条规矩:闭集则收窄 + 编译期围栏,开集则保留兜底。
239
+ * 🔴 渲染路径不许抛,所以 never 分支照样交一句话;只是它现在说的是「这个词不在本端的闭集里」
240
+ * (一句真话),而不再冒充「上游比我新」。
228
241
  */
229
242
  export function ruleStoreUnreadableDetail(kind) {
230
- const known = typeof kind === 'string' && Object.hasOwn(RULE_STORE_UNREADABLE_SENTENCES, kind)
243
+ const known = Object.hasOwn(RULE_STORE_UNREADABLE_SENTENCES, kind)
231
244
  ? RULE_STORE_UNREADABLE_SENTENCES[kind]
232
245
  : undefined;
233
- if (known !== undefined)
246
+ if (typeof known === 'string')
234
247
  return known;
235
248
  const word = typeof kind === 'string' && kind.length > 0 ? kind : '(none)';
236
- return `your standing deny/ask rules could not be applied to this call; the reported reason ${word} is a word newer than this client, so answer this ask as usual`;
249
+ return `your standing deny/ask rules could not be applied to this call; the reported reason ${word} is not one of the mechanisms this client's closed set knows, so answer this ask as usual`;
237
250
  }
@@ -1,17 +1,3 @@
1
- /**
2
- * parkResolver — HITL gate 桥**外环**的 park 决断(REF-CC-032 / SPLIT-12 第三刀,2026-08-02)。
3
- *
4
- * 内环(`frameRouter.ts`)带出一个 `GatePark` 之后,这里回答唯一一个问题:**接着读流,还是
5
- * fail-soft 收场**。三条出路:
6
- * · 决断成功 ⇒ `reattach`(驱动 attach `runs.events`,`lastEventId` 续传);
7
- * · 「这个 gate 早就被解决了」(durable re-attach 必然重放已决断的 park)⇒ 同样 `reattach`;
8
- * · 其余(overlay 缺席 / pending 蒸发 / decide 409/404 / turn 中断 / hop 预算耗尽)⇒ `failsoft`:
9
- * flush HOLD 的毒化帧 + 原样吐 suspended 终帧(durable 腿无终帧则合成一条可见的 failed)。
10
- *
11
- * 🔴 本文件不碰流、不 yield:events 以数组交回驱动。`resolvePark` 的**返回值**就是它的全部对外
12
- * 效果(台账写入除外,且台账写入全经 `GateLedger` 的动词)—— 于是「禁掉某条真臂」这种变异
13
- * 在驱动的消费点上必然显形,而不是被本文件自己吞掉。
14
- */
15
1
  import type { AgentEvent } from '@sema-agent/sdk';
16
2
  import { type GateCurrentPending, type HitlFailureStage } from './hitlBridge.js';
17
3
  import { type QuestionAnswer } from '../liveQuestionStore.js';
@@ -1,3 +1,18 @@
1
+ /**
2
+ * parkResolver — HITL gate 桥**外环**的 park 决断(REF-CC-032 / SPLIT-12 第三刀,2026-08-02)。
3
+ *
4
+ * 内环(`frameRouter.ts`)带出一个 `GatePark` 之后,这里回答唯一一个问题:**接着读流,还是
5
+ * fail-soft 收场**。三条出路:
6
+ * · 决断成功 ⇒ `reattach`(驱动 attach `runs.events`,`lastEventId` 续传);
7
+ * · 「这个 gate 早就被解决了」(durable re-attach 必然重放已决断的 park)⇒ 同样 `reattach`;
8
+ * · 其余(overlay 缺席 / pending 蒸发 / decide 409/404 / turn 中断 / hop 预算耗尽)⇒ `failsoft`:
9
+ * flush HOLD 的毒化帧 + 原样吐 suspended 终帧(durable 腿无终帧则合成一条可见的 failed)。
10
+ *
11
+ * 🔴 本文件不碰流、不 yield:events 以数组交回驱动。`resolvePark` 的**返回值**就是它的全部对外
12
+ * 效果(台账写入除外,且台账写入全经 `GateLedger` 的动词)—— 于是「禁掉某条真臂」这种变异
13
+ * 在驱动的消费点上必然显形,而不是被本文件自己吞掉。
14
+ */
15
+ import { putOwnKey } from '../ownKey.js';
1
16
  import { DecideTransportRetryExhaustedError, HitlBridge, HitlSafetyError, findPendingForTask, readDecideCurrentPending, readWireErrorCode, } from './hitlBridge.js';
2
17
  import { publishQuestionFrame, registerLocalQuestionResponder, hasQuestionOverlay, } from '../liveQuestionStore.js';
3
18
  import { hostLog } from '../host.js';
@@ -146,16 +161,14 @@ export function toAnsweredOutput(questions, answer) {
146
161
  for (const entry of answer.answers ?? []) {
147
162
  const q = questions.find(qq => qq.header === entry.header);
148
163
  const key = typeof q?.question === 'string' ? q.question : entry.header;
149
- // 🔴 0.67.1 同形族(与 `adapter/downstream/terminalToSdkResult.ts` 的 `putOwn` 同一条):
150
- // `key` 来自 wire(问题正文 / header),字面等于 `"__proto__"` 时裸赋值走的是
151
- // `Object.prototype` 上那只 **accessor** —— `answers` 那一格(串值)整条静默丢失,
152
- // `annotations` 那一格(对象值)还顺手改了表自己的原型。落键一律 `defineProperty`。
153
- const put = (t, v) => {
154
- Object.defineProperty(t, key, { value: v, enumerable: true, writable: true, configurable: true });
155
- };
156
- put(answers, (entry.selected ?? []).join(', '));
164
+ // 🔴 0.68.0 / L-246 B2:落键走**单源** `putOwnKey`(`src/ownKey.ts`)。修前这里是一只内联的
165
+ // 同形实现,与 `adapter/downstream/terminalToSdkResult.ts` 的那一份各写各的 —— 陷阱与取舍
166
+ // 的完整说明现在只在那个模块的头注里一份。`key` 来自 wire(问题正文 / header),字面等于
167
+ // `"__proto__"` 时裸赋值走的是 `Object.prototype` 上那只 accessor:`answers` 那一格(串值)
168
+ // 整条静默丢失,`annotations` 那一格(对象值)还顺手改了表自己的原型。
169
+ putOwnKey(answers, key, (entry.selected ?? []).join(', '));
157
170
  if (entry.note)
158
- put(annotations, { notes: entry.note });
171
+ putOwnKey(annotations, key, { notes: entry.note });
159
172
  }
160
173
  return {
161
174
  type: 'ask-user-question',
@@ -78,6 +78,7 @@
78
78
  * 🔴 方向纪律:reason/note 只做归因,绝不参与裁决;缺席 ⇒ 现状字节不变。
79
79
  */
80
80
  import { type GateCurrentPending, type HitlFailureStage, type HitlSafetyCode, type HitlClientLike } from './hitlBridge.js';
81
+ import { type RuleStoreUnreadableKind } from '../gateVocabulary.js';
81
82
  import { type DecideReceiptView } from '../decideReceipt.js';
82
83
  import { RULE_OFFER_MATCHES, RULE_OFFER_BATCH_MEMBER_KINDS, RULE_OFFER_UNCOVERED_REASONS, RULE_OFFERS_ABSENCE_REASONS, DENIAL_LIMIT_KINDS } from '@sema-agent/sdk';
83
84
  import type { RuleSuggestion, ToolApprovalRespondAck, PersistedRuleAnchor as SdkPersistedRuleAnchor, RuleOfferMatch as SdkRuleOfferMatch, RuleOfferBatchMember as SdkRuleOfferBatchMember, RuleOfferUncoveredDetail as SdkRuleOfferUncoveredDetail, RuleOfferUncoveredReason as SdkRuleOfferUncoveredReason, AskOrigin as SdkAskOrigin, RuleOffersAbsence as SdkRuleOffersAbsence, DenialLimitKind as SdkDenialLimitKind, DenialLimitFallback as SdkDenialLimitFallback } from '@sema-agent/sdk';
@@ -708,7 +709,7 @@ export interface ApprovalCardRequest {
708
709
  * value is removed at the stamp」)⇒ 表外值到不了消费端,真读到就是坏形,渲一句成因就是编事实。
709
710
  * 人话两句走**唯一铸点** `ruleStoreUnreadableDetail`(三端零自拼)。
710
711
  */
711
- ruleStoreUnreadable?: string;
712
+ ruleStoreUnreadable?: RuleStoreUnreadableKind;
712
713
  }
713
714
  /**
714
715
  * 🔴 **拆缝口** —— 弹「三选卡」并等人的决断。壳 = vendored CC `PermissionRequest`;
@@ -1016,6 +1016,7 @@ export function readDenialLimitFallback(v) {
1016
1016
  * 入参签名(typeshape 门的 unknown-出境棘轮 +1),而它的行为由两条腿的端到端素材覆盖。
1017
1017
  */
1018
1018
  function readRuleStoreUnreadable(v) {
1019
+ // 🔴 0.68.0 / L-245:谓词已是**型守卫** ⇒ 这里的 `as string` 退役(一处 cast 少一处)。
1019
1020
  return isRuleStoreUnreadableKind(v) ? v : undefined;
1020
1021
  }
1021
1022
  /**
@@ -0,0 +1,34 @@
1
+ /**
2
+ * src/ownKey.ts — 「拿 **wire 给的串**当对象键」时的**唯一落键姿势**(0.68.0 / L-246 B2 单源化)。
3
+ *
4
+ * ── 病形(0.67.1 在 `terminalToSdkResult.ts` 上定谳,同批在 `hitl/parkResolver.ts` 上又长了一份)──
5
+ * `Object.prototype.__proto__` 是一个 **accessor**:在一只普通对象上写 `o["__proto__"] = v` 走的是
6
+ * 那只 setter ——
7
+ * · **不产生自有属性** ⇒ 那一行在 `Object.keys` / `JSON.stringify` 里**整条消失**,连行数都少一;
8
+ * · `v` 是对象时还**顺手改了 `o` 的原型**。
9
+ * 而这几张表的键全都来自 wire(taskId / modelId / core 开集的 costBreakdown 键名 / 一条问题正文),
10
+ * 没有任何一条保证它们不等于这个字面。⇒ 落键一律走 `defineProperty`。
11
+ *
12
+ * ── 为什么要单源(本文件存在的理由)────────────────────────────────────────────────────────
13
+ * 修前同一条处置有**两份实现**(`terminalToSdkResult.ts` 的 `putOwn` 与 `parkResolver.ts` 里那只
14
+ * 内联 `put`),而且两份都带着各自的一段说明。两份同形实现的代价不是重复几行,是**下一次只修一处**:
15
+ * 这条落键姿势将来若要再收紧(例如连 `constructor` 一类也要拦),漏掉的那一份会静默地把老病留住。
16
+ * 本仓「同形存量」纪律的直接落点。
17
+ *
18
+ * 🔴 **不改成 null 原型对象交付**:端拿到的仍是一只正常对象(`hasOwnProperty` / `toString` 都在),
19
+ * 本模块只管「落键」这一步,不改交付形 —— 换原型会在宿主侧造出一类新的 `TypeError`。
20
+ * 描述符与普通赋值**逐位相同**(`writable` / `enumerable` / `configurable` 三真),所以除了
21
+ * `__proto__` 这一个字面,其余每一个键的行为一个字节都没变。
22
+ *
23
+ * 🔴 **零 import**(纯叶):它进 runStream 内核闭包与 index 闭包两条传递闭包,自己一条 import 都没有
24
+ * ⇒ 不可能把任何东西拖进去(portability 门真正守的三件 —— 零 Node 内建 / 零 react·ink /
25
+ * 外部包等值集 —— 一件都不动)。
26
+ */
27
+ /**
28
+ * 把 `value` 落在 `table[key]` 上,**保证产生一个自有属性**(`key` 可以是任何 wire 串,含 `__proto__`)。
29
+ *
30
+ * @param table 目标表(交付形不变:仍是一只普通对象)
31
+ * @param key 来自 wire 的键串
32
+ * @param value 要落的值
33
+ */
34
+ export declare function putOwnKey<T>(table: Record<string, T>, key: string, value: T): void;