@sema-agent/client-core 0.64.1 → 0.64.2

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.
@@ -334,6 +334,44 @@ parkGatedCallId) {
334
334
  // (run-durable-card-display-keys-test.mjs 的 NOT_PROJECTED 账),上游补位后按 probeCause 的
335
335
  // 双源合流形跟批。
336
336
  const durableProbeCause = pending.riskDescriptor?.probeCause;
337
+ // ── 0.64.2 件⑥(cli B-058 让位臂的输入;**durable 腿两位对偶**)────────────────────────────
338
+ // 病:本处「行 → 卡」重铸此前只投 5 位,而这两位在 `/v1/approvals` 行上**今天就有**:
339
+ // · `persistedRuleShadowed` ← 行的 **`riskDescriptor.shadowedRule`**(⚠️ **键名与活卡帧顶层不同**:
340
+ // 帧上是顶层 `persistedRuleShadowed`,行上 server 把它放在 riskDescriptor 下并逐字脱敏 ——
341
+ // engine 7.69.0 fixture `http/routes/approvals-assistant.js` 的 `redactPendingDisclosures`
342
+ // `typeof rd.shadowedRule === "string" ? redactSecrets(rd.shadowedRule) : undefined`,
343
+ // 再由 `projectPendingForWire` 随行上 wire。名字不同正是它此前被漏掉的原因);
344
+ // · `ruleOffersAbsence` ← 行上**同名**键(server 补投那天零改动生效;今天两只 checkpoint 店的
345
+ // `listPending` 还没把它扁平化上来 —— 与本文件 `classifierUnavailable` 的 durable 腿同一格记账)。
346
+ // 🔴 **为什么是行为面而不只是补一格显示**:这两位是壳「让位臂」的输入 —— 活卡腿早就有,durable 腿
347
+ // 因为不投,一条**被记住的 allow 规则清不掉的 ask** 在耐久路上看起来就是一条普通 ask,于是被
348
+ // 自动 decide approve(cli B-058 离线真跑:default 车道 + `Bash(grep:*)` + 带 shadowedRule 的
349
+ // park 行 ⇒ **零卡 + decide approve**)。包只保证这两位到得了卡口,让位判定归端。
350
+ // 🔴 判据与活卡腿逐字同族:**非空串才 stamp**(空串是坏值不是「空规则」),词表**不校**(开集),
351
+ // 坏形/缺席 ⇒ 键不 stamp(**绝不折成空串**:一格空白的规则解释比没有解释更坏)。
352
+ // 🔴 stamp 的是**原文字节**(不 trim、不截、不改写),与活卡腿同族口径 —— 展示值一改写就不再是
353
+ // 「你那条规则」的原文;长度夹取归端。UNTRUSTED-for-display,且**不是身份通道**
354
+ // (见 {@link ApprovalCardRequest.persistedRuleShadowed} 的同名警示)。
355
+ const durableShadowedRule = pending.riskDescriptor?.shadowedRule;
356
+ const durableRuleOffersAbsence = pending.ruleOffersAbsence;
357
+ // cli L-174②(0.64.2):durable 腿的「分类器跑不了」孪生位 —— 载体 = 行上的 `classifierUnavailable`
358
+ // (core `checkpoint-store.d.ts` 逐字「the PARK twin of `AskRequest.classifierUnavailable`」,写在
359
+ // `PendingAction.tool_approval` 上;行是 `pendingAction` 的**扁平投影**,`ruleOffers`/`hasBidiControls`
360
+ // 都是同一条路落到顶层的)。sdk 8.8.0 的 `PendingCheckpoint` 尚无本键声明 ⇒ **结构视图读**
361
+ // (与同函数 `ruleOffers` / `riskDescriptor.probeCause` 同款姿势),类型半场候 sdk 班车。
362
+ // 🔴 **与 `ruleEvidence` 的「刻意零 stamp」不同裁,理由要说清**:那一位在上游**根本没有耐久对偶**,
363
+ // 读 `riskDescriptor.ruleEvidence` 是在猜一个载体名(猜对了是白写,猜错了是拿另一个量冒充);
364
+ // 本位的对偶**是上游逐字声明的**,键名同形、语义同源 —— 不是猜。
365
+ // ⚠️ **今天的供给缺口,如实记(不是「接通了」)**:engine 7.69.0 的两只 checkpoint 店
366
+ // (`plugins/local-checkpoint-store.js:144 listPending` / `plugins/checkpoint-store-sql.js:500
367
+ // listPending`)把 `pendingAction` 扁平化时**没有**带这一位(`/v1/assistant/inbox` 那条腿带了,
368
+ // 但那不是本腿吃的 `/v1/approvals` 行)⇒ 本腿在今天的引擎上**恒零命中**,server 补投那天零改动
369
+ // 生效。⚠️ **别把 `governanceForced` 也读进这一格**(异源对抗复审 r2 非阻断③ 订正):那一位
370
+ // 确实不在 `listPending` 的产物里,但同包 `http/routes/approvals-assistant.js` 的
371
+ // `projectPendingForWire` 会用 `governanceOriginOf(...)` **现算并补到行上**,所以它今天是**到得了**
372
+ // 本腿的;`riskDescriptor.shadowedRule` 同理(那条路由里的 `redactPendingDisclosures` 脱敏后随行
373
+ // 上 wire,件⑥ 消费的正是它)。本位是这三者里**唯一**今天真的没有供给的一格。
374
+ const durableClassifierUnavailable = readClassifierUnavailable(pending.classifierUnavailable);
337
375
  deps.onPresented?.(); // L-80:真要交给卡口了才算呈过(规则直决 / 取件失败都不走到这一行)
338
376
  const card = await surfaceApprovalCard({
339
377
  toolName,
@@ -350,6 +388,20 @@ parkGatedCallId) {
350
388
  ...(pending.hasBidiControls === true ? { hasBidiControls: true } : {}),
351
389
  ...(ruleOffersReadOnly !== undefined ? { ruleOffersReadOnly } : {}),
352
390
  ...(isWireRecordCarrier(durableProbeCause) ? { probeCause: durableProbeCause } : {}),
391
+ // cli L-174②(0.64.2):与活卡腿**同一把窄读器、同一个卡位**(键路同形 ⇒ 端一把读器吃两条腿)。
392
+ // 缺席 ⇒ 键不 stamp;判据与供给缺口见上面那段头注。
393
+ ...(durableClassifierUnavailable !== undefined
394
+ ? { classifierUnavailable: durableClassifierUnavailable }
395
+ : {}),
396
+ // 件⑥(0.64.2):durable 腿的两位对偶(判据与「为什么是行为面」见上面那段头注)。
397
+ // 🔴 落位是**活卡腿同一个卡位**(`persistedRuleShadowed` / `ruleOffersAbsence`),不是新键:
398
+ // 两条腿说的是同一件事,端读一个形;载体名不同只是上游两条腿各自的形,不该漏到卡面上。
399
+ ...(typeof durableShadowedRule === 'string' && durableShadowedRule.trim() !== ''
400
+ ? { persistedRuleShadowed: durableShadowedRule }
401
+ : {}),
402
+ ...(typeof durableRuleOffersAbsence === 'string' && durableRuleOffersAbsence !== ''
403
+ ? { ruleOffersAbsence: durableRuleOffersAbsence }
404
+ : {}),
353
405
  // #348(0.44.0)durable 腿的对偶:行上**本来就存着**这个值(server `parked-decide.ts`;SDK
354
406
  // `PendingCheckpoint.toolCallId?: string | null` 直证)。与活卡帧腿同一个卡位、同一条缺席纪律 ——
355
407
  // `null`(SDK 声明的第二种缺席形)与空串一并降键缺席,绝不把 `null` 折成串。
@@ -498,6 +550,14 @@ export const TOOL_APPROVAL_FRAME_KEYS_MIRROR = [
498
550
  // 🔴 本键与 `requiresRealApproval` 是孪生键,但两者在镜像里各占一格 —— 合并会让「只有一位在场」
499
551
  // 的坏形无处显形。
500
552
  'denialLimitFallback',
553
+ // cli L-174②(client-core 0.64.2):server 7.69.0 起真发 `classifierUnavailable`(core 7.10.0 #616;
554
+ // engine 7.69.0 fixture `tool-approval.js:847` 条件 stamp + `approval-card.js:244` 窄读器直证)。
555
+ // ⚠️ 与 `ruleOffersAbsence`/`denialLimitFallback` **不同形**、与 `persistedRuleShadowed`/`probeCause`
556
+ // 同形:sdk 8.8.0 的运行期锚**尚无**本键(node 直读实证:锚 27 项)⇒ 这是一次**领先**,进对账门的
557
+ // AHEAD_OF_ANCHOR 带退出条件登记(sdk 8.9.x 同拍补上那天那条登记自红逼删,回到逐元素相等)。
558
+ // 🔴 本键有**已定的退役日期**(server 7.70.0 / core 7.12.0 起三面投影退役,上游发布记录 [6852]),但退役
559
+ // 走的是「缺席」而不是「删码」([6853] 死键保留型)⇒ 镜像里这一格不因退役而动。
560
+ 'classifierUnavailable',
501
561
  // S-125③/#564(client-core 0.59.0):server 7.57.0 起真发 `origin`(core 7.5.0 `ASK_ORIGINS`
502
562
  // 八词;engine 7.60.0 fixture 直证)。同上:sdk 8.3.0 锚已含本键 ⇒ 追平,不进领先表。
503
563
  'origin',
@@ -898,8 +958,19 @@ function isWireRecordCarrier(v) {
898
958
  * 包内**不拿它做判定** —— 词表属主是 core,抄一份就是给自己立第二个判官(B-025 的病形);
899
959
  * server 侧已按闭集拒过词表外的值,包再校一遍只会在 core 加员当天把一个合法值判没。
900
960
  * ⚠️ 这条与「四成员全必填」不矛盾:必填说的是**在场性**,开集说的是**取值域**。
961
+ *
962
+ * 🆕 **0.64.2 起在公面上**(cli L-174③;此前是模块私有):壳侧此前自持一份同判据的副本
963
+ * (`src/sema/askFrameNotes.ts`),那是**第二个判官** —— 上游哪天在四成员上加一位、或把某一位的
964
+ * 定义域改了,两份判据各漂各的,而屏上看到的是哪一份取决于素材走了哪条腿。导出的用途正是让那份
965
+ * 副本整只退役:端读**包内窄读产物**({@link ApprovalCardRequest.denialLimitFallback})时不必再判
966
+ * 一遍,端拿到**裸帧/裸行**时也有同一把读器可用。
967
+ * 🔴 **导出的是读器,不是许可**:`autoDenyAfterMs` 仍然只许渲倒计时(窗的执行全在引擎),
968
+ * 这一条不因它上了公面而松动。
969
+ * ⚠️ **与 server `.strict()` 的一格差**(L-120③,如实写在两侧):对象上**多出的成员**本读器剥后
970
+ * 保留四键,server 侧整只拒收 —— 包比 server 宽一格,方向是「多键不误杀」。端不要把「包读出来了」
971
+ * 当成「server 也会收」。
901
972
  */
902
- function readDenialLimitFallback(v) {
973
+ export function readDenialLimitFallback(v) {
903
974
  if (!isWireRecordCarrier(v))
904
975
  return undefined;
905
976
  const d = v;
@@ -918,6 +989,38 @@ function readDenialLimitFallback(v) {
918
989
  autoDenyAfterMs: d.autoDenyAfterMs,
919
990
  };
920
991
  }
992
+ /**
993
+ * `classifierUnavailable` 的**过境窄读器**(cli L-174②,0.64.2)—— 两条卡腿(活卡帧 / durable park
994
+ * 行)共用的**唯一**一把:两处键路同形,各写一份就是两份台账各漂各的。
995
+ *
996
+ * 🔴 **判据与 server 的唯一铸点同源**(engine 7.69.0 fixture `approval-card.js:240/244`:
997
+ * `z.object({ cause: z.string().min(1).max(200) }).strict()` + envelope 的 safeParse)——
998
+ * · 载体非对象 / null / 数组 ⇒ 缺席;
999
+ * · `cause` 非串 / 空串 ⇒ 缺席(空壳等于说「分类器坏了但说不出为什么」,比不说更坏 —— server
1000
+ * d.ts 顶注逐字);
1001
+ * · **`cause` 按开集收**:词表属主在 core,server 侧已按 `min(1)` 之外零词表校验过,包再抄一张
1002
+ * 闭集表只会在 core 加词那天把一个合法值判没,而丢的正是「这次不可用是新出现的那一类」
1003
+ * 这条信息(与 `origin` / `ruleOffersAbsence` / `denialLimitFallback.limit` 逐字同规)。
1004
+ * 🔴 **只交 `cause` 一格**(与 `classifierUnavailableOf` 的读数形逐字相同):顺手把载体上别的键
1005
+ * 带出来会长成第二份 ask 读面。
1006
+ * ⚠️ **与 server `.strict()` 的一格差**(与 {@link readDenialLimitFallback} 同一条,如实写):
1007
+ * 载体上**多出的成员**本读器剥后保留 `cause`,server 侧整只拒收 —— 包宽一格,方向是「多键不误杀」。
1008
+ * 🔴 **过境 ≠ 显示**:本读器答「这条事实到不到得了卡口」;「渲哪一句」由公面的
1009
+ * `classifierUnavailableOf` / `classifierUnavailableDetail` 答 —— 那一层同样非空串即收,只排除
1010
+ * **熔断轴独占**的词(`parse_error`)。⚠️ 0.64.2 之前那一层是 unavailable **闭集**,于是一个合法的
1011
+ * 新成因词过得了本层却在显示层被判没;订正后两层判据几乎逐字相同,差的只有那条有出处的排除
1012
+ * —— 见 {@link ApprovalCardRequest.classifierUnavailable} 与 INTEGRATION §29e。
1013
+ * **不导出**:它是本模块的边界窄化器,唯一消费点是下面两个卡口调用;导出会给公面再加一个 `unknown`
1014
+ * 入参签名(typeshape 门的 unknown-出境棘轮 +1),而它的行为由两条腿的端到端素材覆盖。
1015
+ */
1016
+ function readClassifierUnavailable(v) {
1017
+ if (!isWireRecordCarrier(v))
1018
+ return undefined;
1019
+ const cause = v.cause;
1020
+ if (typeof cause !== 'string' || cause === '')
1021
+ return undefined;
1022
+ return { cause };
1023
+ }
921
1024
  /**
922
1025
  * `match` 是不是**闭词表成员** —— 表来自 sdk 8.2.0 `RULE_OFFER_MATCHES`(`exact` / `prefix` /
923
1026
  * `wildcard` / `subpath`),**本包不再手抄字面量**。
@@ -1341,6 +1444,14 @@ export async function surfaceToolApprovalFrameAndRespond(frame, respond, streamA
1341
1444
  // 🔴 stamp 的是**窄读产物**而不是原对象:端拿到的四座恒是有限非负数 + 非空 limit 串,
1342
1445
  // 不必各自再写一遍同样的判断(三端各写一遍 = 三份会漂的判官)。
1343
1446
  ...(denialLimitFallback !== undefined ? { denialLimitFallback } : {}),
1447
+ // ── cli L-174②(0.64.2):「问你是因为分类器这次跑不了」的事实透传 ──────────────────────────
1448
+ // 经 `readClassifierUnavailable` 单点窄读(判据本体在该函数顶注:`cause` 非空串即收、**开集**、
1449
+ // 坏形降缺席)。stamp 的是**窄读产物** `{cause}`,与 durable 腿**同一把读器**(键路同形)。
1450
+ // 🔴 缺席 ⇒ 键不 stamp,**绝不折成任何肯定话**:缺席同时覆盖「分类器答上了」「这只 ask 没资格
1451
+ // 走分类器」「本部署没接分类器」三形,端禁读成「分类器好着呢」。
1452
+ // ⚠️ 本键 core 7.12.0 / server 7.70.0 起是死键(帧上恒缺席,事实位随拒绝面走)—— 那天这条
1453
+ // stamp 自然零命中,**不需要**也**不许**在这里加任何熔断/回落判据(echo-only)。
1454
+ ...((c) => (c !== undefined ? { classifierUnavailable: c } : {}))(readClassifierUnavailable(frame.classifierUnavailable)),
1344
1455
  // ── S-125③/#564(0.59.0):ask 出身透传 ────────────────────────────────────────────────────
1345
1456
  // 非空串才 stamp,词表**不校**(同 ruleOffersAbsence)。🔴 缺席 ⇒ 键不 stamp,**绝不折成
1346
1457
  // `"policy"`**:那是一个正面事实(出自部署 ToolPolicy),把「老引擎没报」折进去 = 替引擎编话。
@@ -12,6 +12,21 @@
12
12
  *
13
13
  * Trust: the SHELL only PROJECTS the user's local config; the SERVICE single-user gate (mcpInjectionHonored
14
14
  * = requirePrincipal!==true) decides whether to honor it (fail-closed on multi-tenant). So projecting is safe.
15
+ *
16
+ * ── 🔴 0.64.2:逐键白名单重建的**丢键面**(cli L-167①,包侧缺陷)───────────────────────────────
17
+ * 本文件的两条 transport 臂是**逐键重建**(`{name, transport:{…}, allowTools}`),不是 `...c` 全展开。
18
+ * 这是有意的 —— 全展开会把用户 `.mcp.json` 里的任意键原样送上 wire,而 `TaskRequest.mcpServers` 是
19
+ * 引擎的请求面,多一个引擎不认的键既没人判形也没人负责。代价是本仓一贯要根治的那个病形:
20
+ * **上游 additive 加了一个真键,而这条闭集重建静默把它剥掉**([C170] / `hasBidiControls` /
21
+ * 窗三键的同一形)。`toolFaces` 就是这么丢的 —— 运维在 `.mcp.json` 上声明的 per-tool 面(写围栏读的
22
+ * `pathTarget` 那一格)到不了引擎,而**两侧看起来都对**:配置文件里写着,引擎那边只是「没有声明」。
23
+ * ⇒ 处置 = **按上游的透明键表逐名透传**,不是改成全展开。表的属主是 `@sema-agent/settings-schema`
24
+ * 的 mcp 段(`McpServerSpec` 上 `z.unknown()` 的那些键;1.10.0 实装两员:`source` 1.7.2 / `toolFaces`
25
+ * 1.9.0),判据逐字是「解析透明」:形的校验属**引擎摄入侧**,本层一个字节不改也不拒。
26
+ * 🔴 **`source` 今天不在本条腿上**:它是**部署面**(`config.d/mcp.json` / `/effective.mcp`)的分组
27
+ * 标签,消费者是 server;**请求面**的 `McpServerSpec`(sdk 8.8.0 `types.d.ts`)上根本没有这个键 ——
28
+ * 往这里塞它是给 wire 加一个引擎不认的键,而不是透传一件事实。上游哪天把它加到请求面上,
29
+ * 本表按同一条规矩加一行(门里那条「透明键表 ⇄ 本腿透传集」的对账当天红)。
15
30
  */
16
31
  import type { TaskRequest } from '@sema-agent/sdk';
17
32
  /** The wire shape `TaskRequest.mcpServers` carries. SDK 0.0.44's index no longer re-exports the
@@ -33,6 +48,22 @@ export interface McpConfigLike {
33
48
  url?: string;
34
49
  headers?: Record<string, string>;
35
50
  allowTools?: string[];
51
+ /**
52
+ * **透明键**(settings-schema 1.9.0 `McpServerSpec.toolFaces`,`z.unknown()`;core 7.8.0 design/388
53
+ * 片 1 F7 / server ≥7.66.0)—— 这台 server 的 per-tool 面覆盖层,键 = 工具**裸名**,值形
54
+ * `{ family?, pathTarget?, renderHints?, ruleFace? }`(表的属主是 core,见 sdk `McpToolFace`)。
55
+ *
56
+ * 🔴 **类型是 `unknown`,而且是有意的**:上游把这一格声明成解析透明,理由逐字是「这一域
57
+ * all-or-nothing,任一字段解析拒都会让整套 mcp 服务器静默消失」。本层照抄那条判据 ——
58
+ * **不校形、不改写、不按名剥**,坏形也原样过境。
59
+ * 🔴 **坏形的处置在引擎,不在这里**:JSON 形写坏或出现读器不认的键 ⇒ 引擎让**整条 server 不进**
60
+ * 并在请求腿 `mcp_injection_dropped{dropped:[name]}` 点名;形合而语义坏(`pathTarget.param` 不在
61
+ * 工具物化后的 schema 上)⇒ 引擎拒**那一只**工具并出 `config.tool_face_invalid` 通告。
62
+ * 包在这里替引擎判一次形,只有两种结局:判严了 ⇒ 运维声明的保护被**静默**吞掉(比引擎响亮拒
63
+ * 更坏);判松了 ⇒ 白写一道会漂的第二判官。
64
+ * 缺席 ⇒ 键不铸(`.mcp.json` 是 JSON,`undefined` 只可能来自「没写」)。
65
+ */
66
+ toolFaces?: unknown;
36
67
  }
37
68
  /** Map one CC `.mcp.json` server → core `McpServerSpec`, or null when the transport isn't core-supported
38
69
  * (sse/sse-ide/ws/ws-ide) or required fields are missing. */
@@ -1,5 +1,15 @@
1
1
  /** Server caps mirror the service merge cap (task-mcp.ts: ≤32, fail-closed on malformed). */
2
2
  export const MCP_CAPS = { items: 32 };
3
+ /**
4
+ * 透明键的**原样过境**格(0.64.2)—— 见 {@link McpConfigLike.toolFaces} 与模块顶注。
5
+ * 键在场即铸(`undefined` = 没写),值一个字节不动;`null` / 串 / 数组这些坏形照样过境,由引擎判。
6
+ * 🔴 cast 到 wire 形是**类型面的**让位,不是运行期校验:请求面 `McpServerSpec.toolFaces` 的声明是
7
+ * `Record<string, McpToolFace>`(sdk 8.8.0),而透明键的契约是「什么 JSON 值都可能到」——
8
+ * 在这里按声明形收窄就等于把坏形剥掉,正是本批要修的病。
9
+ */
10
+ function transparentKeys(c) {
11
+ return c.toolFaces !== undefined ? { toolFaces: c.toolFaces } : {};
12
+ }
3
13
  /** Map one CC `.mcp.json` server → core `McpServerSpec`, or null when the transport isn't core-supported
4
14
  * (sse/sse-ide/ws/ws-ide) or required fields are missing. */
5
15
  export function mcpConfigToSpec(name, c) {
@@ -19,6 +29,7 @@ export function mcpConfigToSpec(name, c) {
19
29
  ...(c.env ? { env: c.env } : {}),
20
30
  },
21
31
  ...(c.allowTools && c.allowTools.length > 0 ? { allowTools: c.allowTools } : {}),
32
+ ...transparentKeys(c),
22
33
  };
23
34
  }
24
35
  if (kind === 'http') {
@@ -32,6 +43,7 @@ export function mcpConfigToSpec(name, c) {
32
43
  ...(c.headers ? { headers: c.headers } : {}),
33
44
  },
34
45
  ...(c.allowTools && c.allowTools.length > 0 ? { allowTools: c.allowTools } : {}),
46
+ ...transparentKeys(c),
35
47
  };
36
48
  }
37
49
  // sse / sse-ide / ws / ws-ide → core McpServerSpec has no such transport (StreamableHTTP only) → skip.