@sema-agent/client-core 0.75.1 → 0.76.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 (65) hide show
  1. package/CHANGELOG.md +53 -0
  2. package/README.md +11 -1
  3. package/dist/adapt/arms.js +137 -35
  4. package/dist/adapt/ids.d.ts +29 -2
  5. package/dist/adapt/ids.js +29 -2
  6. package/dist/adapt/panelTasks.d.ts +4 -3
  7. package/dist/adapt/panelTasks.js +29 -11
  8. package/dist/adapt/textStream.js +5 -5
  9. package/dist/adapt/toolCards.js +2 -2
  10. package/dist/adapt/turnFlags.js +5 -5
  11. package/dist/adapt.d.ts +1 -1
  12. package/dist/adapt.js +8 -8
  13. package/dist/adapter/downstream/eventToSdkMessage.d.ts +57 -0
  14. package/dist/adapter/downstream/eventToSdkMessage.js +125 -17
  15. package/dist/adapter/runStream.js +14 -5
  16. package/dist/adapter/types.d.ts +12 -0
  17. package/dist/adapter/types.js +30 -0
  18. package/dist/agentsWireCaps.d.ts +15 -2
  19. package/dist/approvalsStreamLiveCapability.js +15 -35
  20. package/dist/deviceExecutorManagementCapability.js +15 -35
  21. package/dist/engineAgentPanelStore.js +13 -1
  22. package/dist/engineCapReader.d.ts +77 -0
  23. package/dist/engineCapReader.js +87 -0
  24. package/dist/engineErrorCodes.d.ts +4 -0
  25. package/dist/engineErrorCodes.js +14 -0
  26. package/dist/executionLaneCapability.js +15 -38
  27. package/dist/fleet/fleetRowAgentType.js +1 -1
  28. package/dist/hitl/approvalOutcomeNote.d.ts +0 -10
  29. package/dist/hitl/approvalOutcomeNote.js +35 -8
  30. package/dist/hitl/approvalResolution.d.ts +148 -0
  31. package/dist/hitl/approvalResolution.js +199 -0
  32. package/dist/hitl/approvalsFeed.d.ts +100 -2
  33. package/dist/hitl/approvalsFeed.js +234 -18
  34. package/dist/hitl/livePendingAsk.d.ts +26 -6
  35. package/dist/hitl/livePendingAsk.js +52 -12
  36. package/dist/hitl/persistedRulesWire.d.ts +20 -0
  37. package/dist/hitl/persistedRulesWire.js +23 -0
  38. package/dist/index.d.ts +8 -0
  39. package/dist/index.js +25 -1
  40. package/dist/mcpLiveness.d.ts +179 -0
  41. package/dist/mcpLiveness.js +218 -0
  42. package/dist/mcpPanel.d.ts +17 -0
  43. package/dist/mcpPanel.js +21 -0
  44. package/dist/mcpProbeCapability.d.ts +84 -0
  45. package/dist/mcpProbeCapability.js +124 -0
  46. package/dist/memoryComplianceCapability.d.ts +73 -0
  47. package/dist/memoryComplianceCapability.js +109 -0
  48. package/dist/memoryEntriesWire.d.ts +302 -0
  49. package/dist/memoryEntriesWire.js +595 -0
  50. package/dist/memoryOriginCapability.d.ts +68 -0
  51. package/dist/memoryOriginCapability.js +102 -0
  52. package/dist/memorySpecWire.d.ts +175 -0
  53. package/dist/memorySpecWire.js +320 -0
  54. package/dist/peerLaneCapability.d.ts +62 -0
  55. package/dist/peerLaneCapability.js +100 -0
  56. package/dist/permissionRulesWriteCapability.d.ts +64 -0
  57. package/dist/permissionRulesWriteCapability.js +98 -0
  58. package/dist/seam.d.ts +53 -5
  59. package/dist/seam.js +10 -1
  60. package/dist/selfOrchestrationDenial.js +7 -2
  61. package/dist/sqlEngineCapability.js +15 -35
  62. package/dist/webSearchBackendCapability.js +15 -35
  63. package/dist/writeProtectionCapability.js +15 -35
  64. package/docs/INTEGRATION-CLIENTS.md +148 -15
  65. package/package.json +1 -1
@@ -17,7 +17,7 @@
17
17
  * present ∧ false 与 `lane_absent` ⇒ `'no'`(明确的否);其余 ⇒ `'unknown'`(别当 no,也别当 yes)。
18
18
  */
19
19
  import { engineWireTarget } from './engineWireTarget.js';
20
- import { snapshotCapsGeneration, capsGenerationStillCurrent } from './engineCapsGenerationGuard.js';
20
+ import { createEngineCapReader } from './engineCapReader.js';
21
21
  /** caps 回体 → 本格读数;畸形一律 `undefined`(= 这一格不写 ⇒ 读口答 `unobserved`)。 */
22
22
  export function projectDeviceExecutorManagementCapability(caps) {
23
23
  if (caps === null || typeof caps !== 'object')
@@ -45,41 +45,23 @@ export function projectDeviceExecutorManagementCapability(caps) {
45
45
  return undefined;
46
46
  return { kind: 'present', management: mgmt };
47
47
  }
48
- const readingByBase = new Map();
48
+ /**
49
+ * 本格的 per-baseUrl 读账 + tee / 读口 / 失效口 / 测试钩四口 —— 各只能力位读器**共用同一份实现**
50
+ * ({@link createEngineCapReader});本文件只留这一格真正不同的部分:投影函数 {@link projectDeviceExecutorManagementCapability}、doctor 措辞表,以及本位自己的判据助手(如有)。
51
+ * 🔴 Map 在工厂闭包里,**一只读器一张**:收到读不懂的回体只删自己这一格,绝不连坐别的能力面。
52
+ */
53
+ const reader = createEngineCapReader({
54
+ name: 'deviceExecutor.management',
55
+ project: projectDeviceExecutorManagementCapability,
56
+ makeUnobserved: () => ({ kind: 'unobserved' }),
57
+ });
49
58
  /** 宿主 caps probe 的读面 tee 落点(与五只兄弟并列)。绝不 throw;畸形 ⇒ 删格;`opts.generation` 关掉旧探测覆盖新读数的竞态。 */
50
59
  export function noteEngineCapsForDeviceExecutorManagement(baseUrl, caps, opts) {
51
- if (typeof baseUrl !== 'string' || baseUrl === '')
52
- return;
53
- // 🔴 代际只在入口**快照一次**(异源对抗复审 R3):opts 是调用方的对象,回体上的 getter 能在投影中途改它;首末两次核对都用这份快照,
54
- // 读 opts / 读代际抛出 ⇒ 不动格(与陈旧代际同一处置)。
55
- const gen = snapshotCapsGeneration(opts);
56
- if (gen === null)
57
- return; // 调用方 opts 的 getter 抛出 = 供给坏了 ⇒ 不动格
58
- if (!capsGenerationStillCurrent(baseUrl, gen))
59
- return;
60
- let reading;
61
- try {
62
- reading = projectDeviceExecutorManagementCapability(caps);
63
- }
64
- catch {
65
- // 🔴 抛 getter / Proxy 的回体 = 畸形(异源对抗复审 R1):与「投影答 undefined」同一处置 —— 删格,不许留着上一台的 yes / no。
66
- reading = undefined;
67
- }
68
- // 🔴 投影之后**再核一次代际**(异源对抗复审 R2):投影会执行回体上不可信的 getter,它可以在中途让这一格换代
69
- // (invalidate + 新代际的 tee 已写入);此时无论投影返回还是抛出,都不许动新代际的格。
70
- if (!capsGenerationStillCurrent(baseUrl, gen))
71
- return;
72
- if (reading === undefined) {
73
- readingByBase.delete(baseUrl);
74
- return;
75
- }
76
- readingByBase.set(baseUrl, reading);
60
+ reader.note(baseUrl, caps, opts);
77
61
  }
78
62
  /** 本进程观测到的读数;这一格空缺 ⇒ `{kind:'unobserved'}`。 */
79
63
  export function observedDeviceExecutorManagement(baseUrl = engineWireTarget()?.baseUrl) {
80
- if (typeof baseUrl !== 'string' || baseUrl === '')
81
- return { kind: 'unobserved' };
82
- return readingByBase.get(baseUrl) ?? { kind: 'unobserved' };
64
+ return reader.observed(baseUrl);
83
65
  }
84
66
  /**
85
67
  * 「`/v1/devices/*` 管理动词能不能用」的判据单源(三态):只有引擎明说在场(present ∧ true)才 `'yes'`;
@@ -111,11 +93,9 @@ export function deviceExecutorManagementDoctorDetail(reading) {
111
93
  }
112
94
  /** 换代失效口(引擎温切成功后调):清成未观测。空串 ⇒ no-op;绝不 throw。 */
113
95
  export function forgetDeviceExecutorManagementReading(baseUrl) {
114
- if (typeof baseUrl !== 'string' || baseUrl === '')
115
- return;
116
- readingByBase.delete(baseUrl);
96
+ reader.forget(baseUrl);
117
97
  }
118
98
  /** 测试钩子。 */
119
99
  export function __resetDeviceExecutorManagementReadingsForTests() {
120
- readingByBase.clear();
100
+ reader.__resetForTests();
121
101
  }
@@ -549,7 +549,19 @@ export function publishEngineAgentPanelEvent(ev) {
549
549
  // 换成「每行各自补一条 end」则要在这里替主路决定消费端键,会吞掉原 wire 键那一行自己的终态。身份对不上就
550
550
  // **一帧都不摘**是本包扣留表的既有律(见 `hitl/gateLedger.ts` 的「判不出 ⇒ 一条都不摘」)。代价 = 这种形下
551
551
  // 那几条行的用量仍缺席(与本件修之前同),换来的是绝不凭空多长一行、绝不替谁编一条终态。
552
- // 真字节里两个行尾段共享一个转录锚不成立(转录锚按代际唯一),所以这一支是防御面,不是常态路径。
552
+ // 🔴 **理由更正(0.76.0;外部验收方 [7764] 逐条回一级源码亲核后证伪了本句旧版)**:旧注释写的是「两个行尾段共享一个
553
+ // **转录锚**不成立(转录锚按代际唯一),所以这一支是防御面」—— 那句话**只论证了转录钥匙那条腿**,而 `heldMatches`
554
+ // 是**转录锚腿 ∪ 委派卡腿**的并集,两条腿异质:
555
+ // · 委派卡那条腿的前提**为假** —— workflow 扇出下一张卡整跑冻结成常量、尾段逐 callKey 不同,**最多 50 个**
556
+ // 尾段共享同一张卡且可同时在飞(上游坐标:core `workflow.ts` 的 `bceParentToolCall` 与 `waIdOf(callKey)`;
557
+ // 默认 `maxAgents` 50)。这正是本包 lane 判据里 `workflow-card-parent` 那一支的上游本体。
558
+ // 🔴 上游当事方 09-20 直讯确认了这一形:**一次扇出的全部子代共享同一张 `parentToolCallId`**(那次 workflow
559
+ // 工具调用自己的卡),逐子代能区分的键**只有 `taskId`**(`wa*` 形)—— 也就是说委派卡这条腿在扇出下**天然多对一**。
560
+ // · 更要紧:**凑够 ≥2 根本不需要两行共享任何一个键** —— 一行只经转录锚命中、另一行只经委派卡命中即可。
561
+ // ⇒ 今天这一支之所以罕见,**靠的不是上面那条律**,而是「workflow 子代的 tick 被 lane 门拦掉 ⇒ 委派卡那条腿的
562
+ // `publishedWireByParent` 没有写入」这条**更脆**的保证 —— 它历史上塌过(上游投影白名单曾把 `workflowRunId` 整个剥掉)。
563
+ // **失效条件**:workflow 子代的 tick 一旦上屏(白名单变宽 / lane 门放行),这一支立刻变成常态路径。
564
+ // 所以本分支**不是**「理论上才会走到的防御面」,它是**有真上游机制**的一支,只是今天被一条脆保证挡着。
553
565
  // 🔴 无论放没放,**主路照走**:原 wire 键那一行(它可能早就上过屏)自己的 end 绝不能被这条腿吞掉。
554
566
  const releasable = [];
555
567
  for (const tail of heldMatches) {
@@ -0,0 +1,77 @@
1
+ /**
2
+ * engineCapReader — 能力位窄读器的**四口共用实现**(0.76.1 CC-75;包内叶,不出 barrel、不上公面)。
3
+ * 🔴 两个 interface 刻意**不导出**:消费方(各只读器)只需要 `createEngineCapReader`,型由推断得出 ⇒
4
+ * 不给包内再添一圈可依赖的型面,也不让边界 `unknown` 在导出签名上多占两格。
5
+ *
6
+ * ── 它收的是哪四份 ────────────────────────────────────────────────────────────────────────────
7
+ * `GET /v1/capabilities` 的每一个窄读器(`sqlEngineCapability` / `writeProtectionCapability` /
8
+ * `webSearchBackendCapability` / `executionLaneCapability` / `approvalsStreamLiveCapability` /
9
+ * `deviceExecutorManagementCapability`)此前各自持有**逐字相同**的四份代码(此后新增的读器直接建在本件上):
10
+ * ① `note(baseUrl, caps, opts?)` 的 **tee**(宿主 caps 探测链上的读面落点);
11
+ * ② `observed(baseUrl?)` 的 per-baseUrl 读口(空缺 ⇒ 未观测);
12
+ * ③ `forget(baseUrl)` 的换代失效口;
13
+ * ④ `__resetForTests()` 测试钩。
14
+ * 各只真正**互不相同**的只有两件:**投影函数**(键路径 / 在场判据 / 值形校验)与 **doctor 措辞表** ——
15
+ * 那两件留在各自文件里。
16
+ * 🔴 **刻意不做声明式键路径表**:`sql` 的三座、`writeProtection` 的三位、`deviceExecutor` 的对象臂 +
17
+ * `why` 判别子各有自己的合形判据(开集串 / 非负有限整数 / 严格布尔 / `null` 是合法值还是畸形),
18
+ * 把它们压进一张通用描述表 = 给同一份判据立第二个判官(而投影函数本身**就是**那一份判据,不是它的抄件)。
19
+ *
20
+ * ── 🔴 tee 的五条纪律(逐条在 {@link createEngineCapReader} 里实现,各只共享同一份)──────────────
21
+ * ① 空 / 非串 `baseUrl` ⇒ no-op(读账的键就是 baseUrl,没有键就没有格);
22
+ * ② 代际只在**入口快照一次**(`opts` 是调用方的对象,回体上的 getter 能在投影中途改它);
23
+ * ③ 投影抛出(getter / Proxy 的回体)⇒ 与「投影答 `undefined`」**同一处置**,当畸形;
24
+ * ④ 投影**之后再核一次代际**(投影执行的是不可信 getter,它能在中途让这一格换代);
25
+ * ⑤ 畸形 ⇒ **删格**,不留上一台引擎的旧读数。
26
+ * 并且 **绝不 throw** —— 读面腿不许反噬 caps 探测链。
27
+ * ⚠️ **不传 `opts.generation` = 与本件到货之前逐字节同行为**(照写):那是 additive 供给,不是强制门。
28
+ * 本件**不**替调用方补一个「自己抓号」的代际(tee 自己抓号是假判据 —— 陈旧那一发在这里读到的
29
+ * 永远是新代号),也不因为收编而把缺供给那一档悄悄变严。
30
+ */
31
+ /**
32
+ * 一只能力位读器的**全部差异**(其余一律共用)。
33
+ *
34
+ * 🔴 `makeUnobserved` 是**工厂**而不是一个共享常量(异源对抗复审采纳,真病):各只读器的读数是**可变对象**
35
+ * (`{ kind: 'unobserved' }`),共享同一份会在两个方向上出事 —— ① 一个消费端就地改了拿到的读数
36
+ * (`r.kind = …`),另一个 baseUrl、另一个会话下次读到的「未观测」就已经不是未观测了,而 `forget` /
37
+ * `__resetForTests` 只清 Map、救不回那份被改坏的常量;② 即使无人改值,「两次未命中的返回值**引用相等**」
38
+ * 本身就是收编**新增**的别名关系(六只原码每次未命中都新建一个对象)。冻结共享对象**不算**等价替代:
39
+ * 那把一次静默串味换成了一次抛错,仍与原行为不同。
40
+ * 🔴 `R` 不绑任何词表:`executionLane` / `approvalsStreamLive` 只有三种 `kind`,`sql` / `writeProtection` /
41
+ * `webSearchBackend` 四种,`deviceExecutor.management` 四种带判别子 —— 工厂只管缓存 / 代际 / tee / forget,
42
+ * 读数的域由各读器自己的型说,本件一个词都不认识。
43
+ */
44
+ interface EngineCapReaderSpec<R> {
45
+ /** 这只读器守的能力位名(阅读与诊断锚;**不参与任何判定** —— 读账按 baseUrl 键,每只各持一份闭包 Map)。 */
46
+ name: string;
47
+ /** caps 回体 → 本格读数;畸形一律 `undefined`(= 这一格不写)。抛出与 `undefined` 同一处置。 */
48
+ project: (caps: unknown) => R | undefined;
49
+ /** 这一格空缺时铸的读数;**每次**未命中都调一次(见上)。 */
50
+ makeUnobserved: () => R;
51
+ }
52
+ /** 一只能力位读器的四口(名字由各读器的公面导出逐字包装,型与行为逐字不变)。 */
53
+ interface EngineCapReader<R> {
54
+ /** 宿主 caps probe 的读面 tee 落点;绝不 throw。 */
55
+ note(baseUrl: string, caps: unknown, opts?: {
56
+ generation?: number;
57
+ }): void;
58
+ /**
59
+ * 本进程观测到的读数;空 / 非串 `baseUrl` ⇒ `makeUnobserved()`。
60
+ *
61
+ * 🔴 **缺省读锚不在这里**:`engineWireTarget()?.baseUrl` 写在**每只读器公面函数的默认参数**上
62
+ * (d.ts 上它显形为 `observedSqlEngine(baseUrl?: string | undefined)` —— 默认值表达式不进声明面,
63
+ * 但**求值时机**是对外行为的一部分:零参与显式 `undefined` 都在**调用那一刻**取读锚)。两处都写默认值 ⇒
64
+ * 没装读锚时 `engineWireTarget()` 会被调**两次**;所以这一口收**必填可空**参数,默认值留在各读器。
65
+ */
66
+ observed(baseUrl: string | undefined): R;
67
+ /** 换代失效口:清成未观测。空 / 非串 ⇒ no-op;绝不 throw。 */
68
+ forget(baseUrl: string | undefined): void;
69
+ /** 测试钩:只清**本只**读器的那张 Map —— 不动装好的读锚、不动 caps 缓存的代际号、不动别只读器的表。 */
70
+ __resetForTests(): void;
71
+ }
72
+ /**
73
+ * 建一只能力位读器。per-baseUrl 读账 = **本次调用闭包里**的一张 Map ⇒ 一只读器一张表,
74
+ * 一只读器收到畸形回体删自己的格,绝不连坐另一只(这是六只原码分表的理由,收编后逐字保留)。
75
+ */
76
+ export declare function createEngineCapReader<R>(spec: EngineCapReaderSpec<R>): EngineCapReader<R>;
77
+ export {};
@@ -0,0 +1,87 @@
1
+ /**
2
+ * engineCapReader — 能力位窄读器的**四口共用实现**(0.76.1 CC-75;包内叶,不出 barrel、不上公面)。
3
+ * 🔴 两个 interface 刻意**不导出**:消费方(各只读器)只需要 `createEngineCapReader`,型由推断得出 ⇒
4
+ * 不给包内再添一圈可依赖的型面,也不让边界 `unknown` 在导出签名上多占两格。
5
+ *
6
+ * ── 它收的是哪四份 ────────────────────────────────────────────────────────────────────────────
7
+ * `GET /v1/capabilities` 的每一个窄读器(`sqlEngineCapability` / `writeProtectionCapability` /
8
+ * `webSearchBackendCapability` / `executionLaneCapability` / `approvalsStreamLiveCapability` /
9
+ * `deviceExecutorManagementCapability`)此前各自持有**逐字相同**的四份代码(此后新增的读器直接建在本件上):
10
+ * ① `note(baseUrl, caps, opts?)` 的 **tee**(宿主 caps 探测链上的读面落点);
11
+ * ② `observed(baseUrl?)` 的 per-baseUrl 读口(空缺 ⇒ 未观测);
12
+ * ③ `forget(baseUrl)` 的换代失效口;
13
+ * ④ `__resetForTests()` 测试钩。
14
+ * 各只真正**互不相同**的只有两件:**投影函数**(键路径 / 在场判据 / 值形校验)与 **doctor 措辞表** ——
15
+ * 那两件留在各自文件里。
16
+ * 🔴 **刻意不做声明式键路径表**:`sql` 的三座、`writeProtection` 的三位、`deviceExecutor` 的对象臂 +
17
+ * `why` 判别子各有自己的合形判据(开集串 / 非负有限整数 / 严格布尔 / `null` 是合法值还是畸形),
18
+ * 把它们压进一张通用描述表 = 给同一份判据立第二个判官(而投影函数本身**就是**那一份判据,不是它的抄件)。
19
+ *
20
+ * ── 🔴 tee 的五条纪律(逐条在 {@link createEngineCapReader} 里实现,各只共享同一份)──────────────
21
+ * ① 空 / 非串 `baseUrl` ⇒ no-op(读账的键就是 baseUrl,没有键就没有格);
22
+ * ② 代际只在**入口快照一次**(`opts` 是调用方的对象,回体上的 getter 能在投影中途改它);
23
+ * ③ 投影抛出(getter / Proxy 的回体)⇒ 与「投影答 `undefined`」**同一处置**,当畸形;
24
+ * ④ 投影**之后再核一次代际**(投影执行的是不可信 getter,它能在中途让这一格换代);
25
+ * ⑤ 畸形 ⇒ **删格**,不留上一台引擎的旧读数。
26
+ * 并且 **绝不 throw** —— 读面腿不许反噬 caps 探测链。
27
+ * ⚠️ **不传 `opts.generation` = 与本件到货之前逐字节同行为**(照写):那是 additive 供给,不是强制门。
28
+ * 本件**不**替调用方补一个「自己抓号」的代际(tee 自己抓号是假判据 —— 陈旧那一发在这里读到的
29
+ * 永远是新代号),也不因为收编而把缺供给那一档悄悄变严。
30
+ */
31
+ import { snapshotCapsGeneration, capsGenerationStillCurrent } from './engineCapsGenerationGuard.js';
32
+ /**
33
+ * 建一只能力位读器。per-baseUrl 读账 = **本次调用闭包里**的一张 Map ⇒ 一只读器一张表,
34
+ * 一只读器收到畸形回体删自己的格,绝不连坐另一只(这是六只原码分表的理由,收编后逐字保留)。
35
+ */
36
+ export function createEngineCapReader(spec) {
37
+ const readingByBase = new Map();
38
+ return {
39
+ note(baseUrl, caps, opts) {
40
+ // ① 空 baseUrl no-op
41
+ if (typeof baseUrl !== 'string' || baseUrl === '')
42
+ return;
43
+ // ② 代际只在入口**快照一次**:`opts` 是调用方的对象,回体上的 getter 能在投影中途改它;首末两次核对都用这份快照,
44
+ // 读 opts / 读代际抛出 ⇒ 不动格(与陈旧代际同一处置)。
45
+ const gen = snapshotCapsGeneration(opts);
46
+ if (gen === null)
47
+ return; // 调用方 opts 的 getter 抛出 = 供给坏了 ⇒ 不动格
48
+ // 陈旧代际在**投影之前**就退场:不执行回体上的 getter,也不动格。
49
+ if (!capsGenerationStillCurrent(baseUrl, gen))
50
+ return;
51
+ let reading;
52
+ try {
53
+ reading = spec.project(caps);
54
+ }
55
+ catch {
56
+ // ③ 抛 getter / Proxy 的回体 = 畸形,与「投影答 undefined」同一处置 —— 删格,不许留着上一台的读数。
57
+ reading = undefined;
58
+ }
59
+ // ④ 投影之后**再核一次代际**:投影会执行回体上不可信的 getter,它可以在中途让这一格换代
60
+ // (失效 + 新代际的 tee 已写入);此时无论投影返回还是抛出,都不许动新代际的格。
61
+ if (!capsGenerationStillCurrent(baseUrl, gen))
62
+ return;
63
+ // ⑤ 畸形 ⇒ 删格
64
+ if (reading === undefined) {
65
+ readingByBase.delete(baseUrl);
66
+ return;
67
+ }
68
+ readingByBase.set(baseUrl, reading);
69
+ },
70
+ observed(baseUrl) {
71
+ if (typeof baseUrl !== 'string' || baseUrl === '')
72
+ return spec.makeUnobserved();
73
+ const hit = readingByBase.get(baseUrl);
74
+ if (hit !== undefined)
75
+ return hit;
76
+ return spec.makeUnobserved();
77
+ },
78
+ forget(baseUrl) {
79
+ if (typeof baseUrl !== 'string' || baseUrl === '')
80
+ return;
81
+ readingByBase.delete(baseUrl);
82
+ },
83
+ __resetForTests() {
84
+ readingByBase.clear();
85
+ },
86
+ };
87
+ }
@@ -66,6 +66,10 @@ export declare const CONFIG_LIMIT_UNKNOWN_KEY = "config.limit_unknown_key";
66
66
  * 🔴 与本族其余成员同律:**坏旋钮响亮拒,绝不静默折回缺省**。
67
67
  */
68
68
  export declare const CONFIG_DELEGATION_ENTRY_CAPS = "config.delegation_entry_caps";
69
+ /** 记忆面 project key 的拼写不合法(**400**;构造期拒,什么都没跑)。 */
70
+ export declare const CONFIG_MEMORY_PROJECT_KEY_SPELLING = "config.memory_project_key_spelling";
71
+ /** 这次请求的记忆写入范围与已生效的那一份不一致(**409**;构造期拒,什么都没跑 —— 冲突,不是拼写错)。 */
72
+ export declare const CONFIG_MEMORY_WRITE_SCOPE_MISMATCH = "config.memory_write_scope_mismatch";
69
73
  /**
70
74
  * 已知的配置拒绝码(**识别表,非白名单**)。判「这是不是一条配置拒绝」请用
71
75
  * {@link isConfigRefusalCode} —— 它按 `config.` 前缀判,未来新成员自动落进来。
@@ -76,6 +76,18 @@ export const CONFIG_LIMIT_UNKNOWN_KEY = 'config.limit_unknown_key';
76
76
  * 🔴 与本族其余成员同律:**坏旋钮响亮拒,绝不静默折回缺省**。
77
77
  */
78
78
  export const CONFIG_DELEGATION_ENTRY_CAPS = 'config.delegation_entry_caps';
79
+ // ── 记忆面的两个**构造期**拒码(CC-96;`agents[].memory` / 请求记忆面的同族第二波)────────────
80
+ //
81
+ // 两码与本族其余成员同律(「配置错了」不是「跑失败了」),但**多一条**:它们拒在**受理之前** ——
82
+ // 这一次运行什么都没跑,所以出路一律是「改请求再发」,不是等、也不是去查运行结果。
83
+ // 🔴 **分诊归引擎,本包不预铸第二判官**:拼写合不合法 / 范围算不算不匹配,判据都在引擎侧;本端照码渲一句话
84
+ // (单铸口 = `src/memorySpecWire.ts` 的 `memoryConfigRefusalNoteOf`),绝不自己先算一遍再「帮」它判。
85
+ // ⚠️ 两码的 HTTP 状态**不同**(本族此前清一色 400):拼写 400 / 不匹配 409。状态不是判据(判据是码),
86
+ // 但它是**处置差**的来源 —— 一条是打字错,一条是与已生效的那一份冲突,渲成同一句会把两种出路混掉。
87
+ /** 记忆面 project key 的拼写不合法(**400**;构造期拒,什么都没跑)。 */
88
+ export const CONFIG_MEMORY_PROJECT_KEY_SPELLING = 'config.memory_project_key_spelling';
89
+ /** 这次请求的记忆写入范围与已生效的那一份不一致(**409**;构造期拒,什么都没跑 —— 冲突,不是拼写错)。 */
90
+ export const CONFIG_MEMORY_WRITE_SCOPE_MISMATCH = 'config.memory_write_scope_mismatch';
79
91
  /**
80
92
  * 已知的配置拒绝码(**识别表,非白名单**)。判「这是不是一条配置拒绝」请用
81
93
  * {@link isConfigRefusalCode} —— 它按 `config.` 前缀判,未来新成员自动落进来。
@@ -86,6 +98,8 @@ export const CONFIG_REFUSAL_CODES = new Set([
86
98
  CONFIG_STRATEGY_MAX_SIZE_INVALID,
87
99
  CONFIG_LIMIT_UNKNOWN_KEY,
88
100
  CONFIG_DELEGATION_ENTRY_CAPS,
101
+ CONFIG_MEMORY_PROJECT_KEY_SPELLING,
102
+ CONFIG_MEMORY_WRITE_SCOPE_MISMATCH,
89
103
  ]);
90
104
  // ── 委派席位到限族(core 5.48.0 design/323,[4743] @cli 点名的「两新 coded 拒绝」)──────────────
91
105
  //
@@ -25,7 +25,7 @@
25
25
  */
26
26
  import { capForDisplay } from './fleetTaskDesc.js';
27
27
  import { engineWireTarget } from './engineWireTarget.js';
28
- import { snapshotCapsGeneration, capsGenerationStillCurrent } from './engineCapsGenerationGuard.js';
28
+ import { createEngineCapReader } from './engineCapReader.js';
29
29
  /**
30
30
  * caps 回体 → 本格读数;**畸形一律 `undefined`**(= 这一格不写 ⇒ 读口答 `unobserved`)。
31
31
  * 键不在 / `undefined` ⇒ `not_reported`(老 server);`null` / 数组 / 非对象 / 缺任一座 / 座坏形 ⇒ 畸形。
@@ -48,47 +48,26 @@ export function projectExecutionLaneCapability(caps) {
48
48
  return undefined;
49
49
  return { kind: 'present', view: { provider, toolsOnThisHost } };
50
50
  }
51
- const readingByBase = new Map();
51
+ /**
52
+ * 本格的 per-baseUrl 读账 + tee / 读口 / 失效口 / 测试钩四口 —— 各只能力位读器**共用同一份实现**
53
+ * ({@link createEngineCapReader});本文件只留这一格真正不同的部分:投影函数 {@link projectExecutionLaneCapability}、doctor 措辞表,以及本位自己的判据助手(如有)。
54
+ * 🔴 Map 在工厂闭包里,**一只读器一张**:收到读不懂的回体只删自己这一格,绝不连坐别的能力面。
55
+ */
56
+ const reader = createEngineCapReader({
57
+ name: 'executionLane',
58
+ project: projectExecutionLaneCapability,
59
+ makeUnobserved: () => ({ kind: 'unobserved' }),
60
+ });
52
61
  /**
53
62
  * 宿主 caps probe 的**读面 tee** 落点(与三只兄弟并列;宿主在同一处按序调)。绝不 throw。
54
63
  * 投影 `undefined`(畸形)⇒ 这一格**被删**而不是留着上一台引擎的旧值;`opts.generation` 关掉「旧探测覆盖新读数」竞态。
55
64
  */
56
65
  export function noteEngineCapsForExecutionLane(baseUrl, caps, opts) {
57
- if (typeof baseUrl !== 'string' || baseUrl === '')
58
- return;
59
- // 🔴 代际只在入口**快照一次**(异源对抗复审 R3):opts 是调用方的对象,回体上的 getter 能在投影中途改它;首末两次核对都用这份快照,
60
- // 读 opts / 读代际抛出 ⇒ 不动格(与陈旧代际同一处置)。
61
- const gen = snapshotCapsGeneration(opts);
62
- if (gen === null)
63
- return; // 调用方 opts 的 getter 抛出 = 供给坏了 ⇒ 不动格
64
- if (!capsGenerationStillCurrent(baseUrl, gen))
65
- return;
66
- let reading;
67
- try {
68
- reading = projectExecutionLaneCapability(caps);
69
- }
70
- catch {
71
- // 🔴 0.74.2 同形存量(CC-69 复审 R1 族扫):抛 getter / Proxy 的回体 = 畸形,与「投影答 undefined」同一处置 —— 删格,不许留着上一台的读数。
72
- reading = undefined;
73
- }
74
- // 🔴 投影之后**再核一次代际**(异源对抗复审 R2):投影会执行回体上不可信的 getter,它可以在中途让这一格换代
75
- // (invalidate + 新代际的 tee 已写入);此时无论投影返回还是抛出,都不许动新代际的格。
76
- if (!capsGenerationStillCurrent(baseUrl, gen))
77
- return;
78
- if (reading === undefined) {
79
- readingByBase.delete(baseUrl);
80
- return;
81
- }
82
- readingByBase.set(baseUrl, reading);
66
+ reader.note(baseUrl, caps, opts);
83
67
  }
84
68
  /** 本进程观测到的执行车道;这一格空缺 ⇒ `{kind:'unobserved'}`(**绝不**编一条车道)。 */
85
69
  export function observedExecutionLane(baseUrl = engineWireTarget()?.baseUrl) {
86
- if (typeof baseUrl !== 'string' || baseUrl === '')
87
- return { kind: 'unobserved' };
88
- const hit = readingByBase.get(baseUrl);
89
- if (hit !== undefined)
90
- return hit;
91
- return { kind: 'unobserved' };
70
+ return reader.observed(baseUrl);
92
71
  }
93
72
  /**
94
73
  * 「工具是否跑在本机」的**判据单源**(此前三端各拼旁证三合取):读到位就用位 —— `true` 是发 `baseDir` 的必要条件、
@@ -126,11 +105,9 @@ export function executionLaneDoctorDetail(reading) {
126
105
  }
127
106
  /** **换代失效口**(由宿主在引擎温切成功后调):清成未观测,不是留旧值。空串 ⇒ no-op;绝不 throw。 */
128
107
  export function forgetExecutionLaneReading(baseUrl) {
129
- if (typeof baseUrl !== 'string' || baseUrl === '')
130
- return;
131
- readingByBase.delete(baseUrl);
108
+ reader.forget(baseUrl);
132
109
  }
133
110
  /** 测试钩子。 */
134
111
  export function __resetExecutionLaneReadingsForTests() {
135
- readingByBase.clear();
112
+ reader.__resetForTests();
136
113
  }
@@ -8,7 +8,7 @@
8
8
  *
9
9
  * 诚实来源亲核(sdk 9.8.1 实装字节):
10
10
  * · `task_progress` 臂(`dist/events.d.ts`)上**没有** `agentType`、也没有 `spawnAgentType` —— tick 这条
11
- * lane 今天根本不报类型,所以本层**不读**无出处的键(读了就是替上游发明一个字段);
11
+ * lane 今天根本不报类型,所以本层**不读**无出处的键(读了就是替上游发明一个字段)。🔴 **措辞更正(外部验收方 [7764])**:因此 tick 这一档**不是一条「今天恒不命中的路径」,它根本没有代码** —— 本文件里 `agentType` 的三处出现全在注释,唯一的代码走的是 fleet 行帧那一档。相应地,「上游铸位当天零改生效」这句**不成立**:上游若铸出 `spawnAgentType`,本包**没有读口**,仍需改码补一档;
12
12
  * · `FleetTaskRow.agentType`(`dist/resources/fleet.d.ts`,§K-7 的短类型 label,如 `Explore` /
13
13
  * `general-purpose`)**有出处** —— 但 `fleet/fleetProjection.ts` 把它**折进** `FleetTaskView.name`
14
14
  * (`deriveAgentLabel` = `agentName ?? agentType`,登记在
@@ -1,13 +1,3 @@
1
- /**
2
- * approvalOutcomeNote — 流内帧腿的审批结局回交(`AskGateWireDeps.onToolApprovalOutcome(frame, outcome)`,0.74.1 CC-68)
3
- * 到宿主「审批结局便签」的**单源映射**(0.74.5 CC-88;此前三端装配点各手拼一份,语义以 cli 1.0.123 现行为准):
4
- * · `approvalId` = 帧上的原词;
5
- * · `settled` = `outcome.decision !== 'unresolved'`(respond 送达并落定;**不看** ack 在不在 —— ack 是 server 的回执形,宿主不读);
6
- * · `retracted` 只在 `outcome.retracted === true` 时带键(**独立判别位**,不折进 detail:撤卡 ≠ 投递失败 —— 释放认领但不扣重出卡预算);
7
- * `editRefused` **不算** retracted,只折进 detail;
8
- * · `detail` 三支优先序:撤卡固定句 → 编辑被拒固定句 → `respondRefusal` 的 errorCode / message 过滤非空后 ` · ` 拼接;
9
- * 拼出空串 ⇒ 键缺席(绝不编一句;不用决断词、不用 ack 摘要)。
10
- */
11
1
  import type { ToolApprovalFrame, ToolApprovalFrameOutcome } from './toolApprovalWire.js';
12
2
  /** 宿主便签形(三端同一形;`retracted` / `detail` 在场才带)。 */
13
3
  export interface StreamApprovalOutcomeNote {
@@ -1,20 +1,47 @@
1
+ /**
2
+ * approvalOutcomeNote — 流内帧腿的审批结局回交(`AskGateWireDeps.onToolApprovalOutcome(frame, outcome)`,0.74.1 CC-68)
3
+ * 到宿主「审批结局便签」的**单源映射**(0.74.5 CC-88;此前三端装配点各手拼一份,语义以 cli 1.0.123 现行为准):
4
+ * · `approvalId` = 帧上的原词;
5
+ * · `settled` = 这次决断**落定了**(respond 送达并落定;**不看** ack 在不在 —— ack 是 server 的回执形,宿主不读);
6
+ * 判据 = {@link approvalResolutionOf} 的落定臂,与旧口径 `outcome.decision !== 'unresolved'` 在实装可达的每一形上同值
7
+ * (撤卡 / 编辑被拒两形只与 `'unresolved'` 同场铸出);
8
+ * · `retracted` 只在 `outcome.retracted === true` 时带键(**独立判别位**,不折进 detail:撤卡 ≠ 投递失败 —— 释放认领但不扣重出卡预算);
9
+ * `editRefused` **不算** retracted,只折进 detail;
10
+ * · `detail` 三支优先序:撤卡固定句 → 编辑被拒固定句 → `respondRefusal` 的 errorCode / message 过滤非空后 ` · ` 拼接;
11
+ * 拼出空串 ⇒ 键缺席(绝不编一句;不用决断词、不用 ack 摘要)。
12
+ *
13
+ * 🔴 **三位的判据自本版起由 {@link approvalResolutionOf} 派生**(「一次决断最后怎么了」的单源判别联合):
14
+ * `settled` = 落定臂;`retracted` = 「零字节 · 撤卡」这一 cause;`detail` 的三支 = 那三个臂/cause 的
15
+ * 三个分支。本便签的**导出形与逐字输出不变** —— 变的只是判据不再在这里第二次手读旁挂布尔位
16
+ * (同一个问题两处各判一次 = 两份判据必然分叉)。
17
+ */
18
+ import { approvalNotSentCause, approvalResolutionOf, isApprovalDecided, } from './approvalResolution.js';
1
19
  export const APPROVAL_NOTE_RETRACTED_DETAIL = 'the card was retracted (this host no longer has a decision port for it)';
2
20
  export const APPROVAL_NOTE_EDIT_REFUSED_DETAIL = 'edits are not accepted on this card';
3
21
  function nonEmpty(v) {
4
22
  return typeof v === 'string' && v.length > 0 ? v : undefined;
5
23
  }
24
+ /** 结局的三支 detail(与 {@link ApprovalResolution} 的臂/cause 一一对应;拼出空串 = 没有可交的原文)。 */
25
+ function detailOf(r) {
26
+ const cause = approvalNotSentCause(r);
27
+ if (cause === 'retracted')
28
+ return APPROVAL_NOTE_RETRACTED_DETAIL;
29
+ if (cause === 'edit_refused')
30
+ return APPROVAL_NOTE_EDIT_REFUSED_DETAIL;
31
+ // 引擎那一头给的原文:机器码 + 拒句,各自非空才进,` · ` 拼(不复述决断词、不摘 ack)。
32
+ if (r.kind === 'unsettled') {
33
+ return [nonEmpty(r.errorCode), nonEmpty(r.detail)].filter((x) => x !== undefined).join(' · ');
34
+ }
35
+ return '';
36
+ }
6
37
  /** 帧 + 结局 → 便签(纯函数;结局上的坏形按缺席读,不抛)。 */
7
38
  export function approvalOutcomeNoteOf(frame, outcome) {
8
- const retracted = outcome.retracted === true;
9
- const refusal = outcome.respondRefusal;
10
- const detail = retracted
11
- ? APPROVAL_NOTE_RETRACTED_DETAIL
12
- : outcome.editRefused === true
13
- ? APPROVAL_NOTE_EDIT_REFUSED_DETAIL
14
- : [nonEmpty(refusal?.errorCode), nonEmpty(refusal?.message)].filter((x) => x !== undefined).join(' · ');
39
+ const r = approvalResolutionOf(outcome);
40
+ const retracted = approvalNotSentCause(r) === 'retracted';
41
+ const detail = detailOf(r);
15
42
  return {
16
43
  approvalId: frame.approvalId,
17
- settled: outcome.decision !== 'unresolved',
44
+ settled: isApprovalDecided(r),
18
45
  ...(retracted ? { retracted: true } : {}),
19
46
  ...(detail.length > 0 ? { detail } : {}),
20
47
  };
@@ -0,0 +1,148 @@
1
+ /**
2
+ * approvalResolution — 「**一次审批决断最后怎么了**」的单源判别联合({@link ApprovalResolution})
3
+ * 与它的唯一映射口({@link approvalResolutionOf})。
4
+ *
5
+ * ── 这一件修的是什么 ────────────────────────────────────────────────────────────────────────
6
+ * 同一个问题今天有**两种结局形**,而且第二种把三件互斥的事挤进同一个词:
7
+ * · durable-park 腿 {@link import('./toolApprovalWire.js').FsApprovalOutcome} —— 四个 `kind`
8
+ * + `failed.stage` 五词;
9
+ * · 流内帧腿 / 悬挂 ask(盲卡)腿 {@link import('./toolApprovalWire.js').ToolApprovalFrameOutcome}
10
+ * —— `decision` 四词,其中 `'unresolved'` 有 **3 种互斥含义**(宿主撤卡 / 编辑被拒 / respond 没落定),
11
+ * 要再读兄弟布尔位(`retracted` / `editRefused`)才分得开。
12
+ * ⇒ 本模块给出**一个**判别联合,两条腿的每一种结局各落恰一个臂 + 一个 cause 词,`'unresolved'`
13
+ * 的三义各落一臂。消费端从此一次判别拿到答案,不再靠读旁挂布尔位。
14
+ *
15
+ * ── 🔴 本版是 **additive**:既有型面一字未动 ───────────────────────────────────────────────
16
+ * `FsApprovalOutcome` / `ToolApprovalFrameOutcome` / `ApprovalCardDecision` 的键、值、语义
17
+ * **逐字节不变**;本模块只是**读**它们。端可以立刻改读本联合,也可以照旧读原结局对象。
18
+ *
19
+ * ── 三个臂各自答什么(边界写清,别处不许重铸)────────────────────────────────────────────────
20
+ * · `decided` —— 决断**送到了引擎并拿到语义答复**;
21
+ * · `not_sent` —— 包侧**一个字节都没发**(引擎那头的 ask 照旧挂着,`'no_pending'` 这一 cause 例外:
22
+ * 那一行本来就已经不在了);
23
+ * · `unsettled` —— **没落定**。🔴 它**刻意不承诺**「字节已经出站」:见
24
+ * {@link ApprovalUnsettledCause} 的 `'safety_stop'` / `'orchestration'` 两词的头注 ——
25
+ * 那两格在今天的结局对象上**分不出**决断有没有出站,而一个「保证发了」的承诺在这里是编的。
26
+ */
27
+ import type { DecideReceiptView } from '../decideReceipt.js';
28
+ import type { HitlSafetyCode } from './hitlBridge.js';
29
+ import type { FsApprovalOutcome, ToolApprovalFrameOutcome, ToolApprovalRespondAckView, ToolApprovalRespondDecision } from './toolApprovalWire.js';
30
+ /**
31
+ * 「包侧一个字节都没发」的因由闭集(**唯一真源**;端零手抄)。
32
+ * · `'retracted'` —— 宿主撤掉了一张已经没有决断口的卡(两条腿都有这一形)。
33
+ * · `'edit_refused'` —— 入参不可得的卡(悬挂 ask)上收到「编辑后批准」:丢掉改写再批准、或把改写
34
+ * 转发出去,两条都不是人按下的那个决定 ⇒ 整次不发。
35
+ * · `'no_pending'` —— 取件那一拍队列里没有本 run 的可决行(结构化判别位,不是文案猜的)。
36
+ * · `'fetch_failed'` —— 取件这一步**自己**失败了(与上一词结构可分:那是「没有行」,这是「问不出来」)。
37
+ * · `'no_input'` —— 行上没有可呈现的工具入参,卡渲不出来。
38
+ * · `'card_unavailable'` —— 卡口那一段失败(没装卡口 / 卡口自报失败)。
39
+ */
40
+ export declare const APPROVAL_NOT_SENT_CAUSES: readonly ["retracted", "edit_refused", "no_pending", "fetch_failed", "no_input", "card_unavailable"];
41
+ /** {@link APPROVAL_NOT_SENT_CAUSES} 的成员型。 */
42
+ export type ApprovalNotSentCause = (typeof APPROVAL_NOT_SENT_CAUSES)[number];
43
+ /**
44
+ * 「没落定」的因由闭集(**唯一真源**;端零手抄)。
45
+ * · `'engine_refused'` —— 决断发出去了,引擎**真的答复并拒了**(卡通常仍 pending、决定没被消费)。
46
+ * 🔴 **只在有肯定证据时才给这个词**:结局对象上带着引擎的 wire 机器码,或带着引擎在拒体上铸的指路键。
47
+ * 没有证据的 decide 失败落 `'decide_failed'` —— 见下一词。
48
+ * · `'decide_failed'` —— decide 这一段失败了,而结局对象上**没有任何「引擎答复过」的证据**:
49
+ * 调用方中止(人按了 Esc,出站前就抛)、抛出物身上根本没有码、老引擎的无码失败,三形同落这里。
50
+ * 🔴 **不许**据此说「引擎拒了」:那三形里有两形连字节都没出去。要不要重呈看别的位,不看这个词。
51
+ * · `'transport_exhausted'` —— 出站瞬断类失败重试后仍未送达:**两发都没拿到语义答复**。
52
+ * · `'safety_stop'` —— 本包的安全停闭集({@link HitlSafetyCode})命中 ⇒ 要重新呈现给人,**绝不自动重发**。
53
+ * 🔴 **这一格不承诺字节已出站**:`'binding_mismatch'` 既可能是本地绑定守卫在发送**前**拦下的,
54
+ * 也可能是引擎 409 答复重抛的 —— 今天的结局对象上这两个出处**不可分**,所以这里只说「没落定」。
55
+ * · `'respond_failed'` —— 流内帧腿的 respond 抛错:这次决断没落定,**引擎按 TTL / abort 自决**。
56
+ * · `'interrupted'` —— turn 被中断:包侧**发起**了一次工具级拒绝来结算挂着的 ask,但那一发是不观察结果的,
57
+ * 而且它自己也可能在出站前本地失败(绑定键缺失一类)。⇒ 既不能说落定,也**不能**说「一个字节都没发」。
58
+ * 🔴 它**不是** `not_sent`:`not_sent` 是「零字节」的承诺,这一格给不出那个承诺。
59
+ * · `'orchestration'` —— 重挂环在硬上限 / 连续零进展上收口:那不属于决断链的任何一段,
60
+ * 结局对象上**分不出**决断有没有出站。
61
+ * · `'unreadable'` —— 这个结局对象读不出来(不是对象 / 两个判别位都不成词 / 段词不认识)。
62
+ * 🔴 读不出**绝不猜 `decided`**,也不敢说「零字节」——两个方向猜错的代价都是替人说一句他没说的话。
63
+ */
64
+ export declare const APPROVAL_UNSETTLED_CAUSES: readonly ["engine_refused", "decide_failed", "transport_exhausted", "safety_stop", "respond_failed", "interrupted", "orchestration", "unreadable"];
65
+ /** {@link APPROVAL_UNSETTLED_CAUSES} 的成员型。 */
66
+ export type ApprovalUnsettledCause = (typeof APPROVAL_UNSETTLED_CAUSES)[number];
67
+ /** 决断送到了引擎并拿到语义答复。 */
68
+ export interface ApprovalResolutionDecided {
69
+ kind: 'decided';
70
+ /**
71
+ * 落定的决断词,**原样透传**。
72
+ * 🔴 **不在这里复校闭集**:帧腿的词已经在它自己的发送段窄化过,边界上再校一遍等于装第二个判官
73
+ * (一个表外的词会被降成「读不出」,而它其实是引擎收下的那个决断)。
74
+ * 🔴 durable-park 腿只可能给 `'allow'` / `'deny'`:那一腿的结局对象上**没有**「remember 送成了」
75
+ * 这一位,所以这里**不铸** `'allow_session'`(铸一个就是替引擎说一句它没说的话)。
76
+ * 🔴 流内帧腿上 **fail-closed 的拒绝与人按的拒绝不可分**(卡口的 `aborted` / `failed` 与 `deny`
77
+ * 在发送段折成同一个词)—— 今天的真相如实说,本联合不发明新位。
78
+ */
79
+ decision: ToolApprovalRespondDecision;
80
+ /** 待批 call 身份(durable-park 腿在场;帧腿没有这一位 —— 身份在帧的 `approvalId` 上,**不是**「没有身份」)。 */
81
+ gatedCallId?: string;
82
+ /** decide 的 200 回执读数(缺席 = 老 server / 终局形 / 回体读不动,三形同缺);🔴 它**不是**「门已解决」的证据。 */
83
+ receipt?: DecideReceiptView;
84
+ /** respond 的 200 ack(缺席 = **未知**:注入面回 void / 旧 server / 相关性门丢弃,绝不当成 false)。 */
85
+ ack?: ToolApprovalRespondAckView;
86
+ }
87
+ /** 包侧一个字节都没发。 */
88
+ export interface ApprovalResolutionNotSent {
89
+ kind: 'not_sent';
90
+ cause: ApprovalNotSentCause;
91
+ gatedCallId?: string;
92
+ /** 本包铸的诊断文案(原样;读不出 / 空串 ⇒ 键缺席,绝不编一句)。 */
93
+ detail?: string;
94
+ }
95
+ /** 没落定(见模块顶注:**不承诺**字节已出站)。 */
96
+ export interface ApprovalResolutionUnsettled {
97
+ kind: 'unsettled';
98
+ cause: ApprovalUnsettledCause;
99
+ gatedCallId?: string;
100
+ /** 本包的安全停判别位(`'safety_stop'` 这一 cause 上在场)。 */
101
+ safetyCode?: HitlSafetyCode;
102
+ /** 引擎的 wire 机器码(开集,原样;缺席**不带语义** —— 缺席 ≠「不是引擎拒的」)。 */
103
+ errorCode?: string;
104
+ /** 应答的 HTTP 状态(在场 = 引擎真应答了;缺席 = 传输层失败 / 注入面自抛,**不许**反推成 0)。 */
105
+ status?: number;
106
+ /** 可呈现的原文(帧腿 = 引擎的拒句原文;durable 腿 = 本包铸的诊断文案)。读不出 ⇒ 键缺席。 */
107
+ detail?: string;
108
+ }
109
+ /**
110
+ * 一次审批决断的**结局**(判别联合;三臂穷举,`'unresolved'` 的三义各落一臂)。
111
+ *
112
+ * 与旧读法的对照(消费端迁移判据一句话):
113
+ * · `out.decision !== 'unresolved'`(帧腿)⇒ `approvalResolutionOf(out).kind === 'decided'`;
114
+ * · `out.retracted === true` ⇒ `approvalNotSentCause(r) === 'retracted'`;
115
+ * · `out.editRefused === true` ⇒ `approvalNotSentCause(r) === 'edit_refused'`;
116
+ * · 裸 `'unresolved'` ⇒ `approvalUnsettledCause(r) === 'respond_failed'`。
117
+ *
118
+ * 🔴 **不并表的位**:指路键(`currentPending`)、ack 的逐位解释、receipt 的逐位语义照旧读原结局
119
+ * 对象 —— 本联合只答「怎么了」这一个问题。
120
+ */
121
+ export type ApprovalResolution = ApprovalResolutionDecided | ApprovalResolutionNotSent | ApprovalResolutionUnsettled;
122
+ /**
123
+ * {@link approvalResolutionOf} 认的入参:两条腿今天的结局对象。
124
+ * 🔴 ask / plan-review 腿的 `GateOutcome` **刻意不在**这个联合里(它多一个 `answered` 位与一张
125
+ * 另外的失败码闭集),plan-review 的决断生效读数也不在(它自己是另一张六词表)。
126
+ * ⚠️ **这是分工边界,不是一道编译期围栏**,如实说:只有 `failed` 那一臂因为失败码闭集不同而赋不进来;
127
+ * `decided` / `aborted` 两臂在结构上仍然兼容,硬喂进来会被按本腿的口径读(`answered` 被忽略、
128
+ * 别的码表词读不出判别位)。把别的腿的结局喂进本口是调用方的错,本函数不替它兜。
129
+ */
130
+ export type ApprovalLegOutcome = FsApprovalOutcome | ToolApprovalFrameOutcome;
131
+ /**
132
+ * 今天各腿的结局对象 → {@link ApprovalResolution}。**纯函数,永不抛,永不造**。
133
+ *
134
+ * 判别序(固定,不随键序变):
135
+ * ① `kind` 是 durable-park 腿的闭四词 ⇒ 按那一腿读;
136
+ * ② 否则决断词是串 ⇒ 按流内帧腿 / 盲卡腿读;
137
+ * ③ 都不成立 ⇒ `unsettled{cause:'unreadable'}`(🔴 不猜 `decided`,也不说「零字节」)。
138
+ *
139
+ * 🔴 **旁挂位在场压过决断词**(帧腿):撤卡 / 编辑被拒这两形是「一个字节都没发」,而它们今天与
140
+ * `decision` 同在一只对象上;先读决断词就会把一张没发出去的卡说成「批过了」。
141
+ */
142
+ export declare function approvalResolutionOf(outcome: ApprovalLegOutcome): ApprovalResolution;
143
+ /** 这次决断**落定了吗**(唯一判据;`true` ⇒ 决断送到并拿到语义答复)。坏形入参 ⇒ `false`。 */
144
+ export declare function isApprovalDecided(r: ApprovalResolution): r is ApprovalResolutionDecided;
145
+ /** 「一个字节都没发」的因由;不是那一臂 ⇒ `undefined`(**不是**「发了」的证据,只是「不是这一臂」)。 */
146
+ export declare function approvalNotSentCause(r: ApprovalResolution): ApprovalNotSentCause | undefined;
147
+ /** 「没落定」的因由;不是那一臂 ⇒ `undefined`。 */
148
+ export declare function approvalUnsettledCause(r: ApprovalResolution): ApprovalUnsettledCause | undefined;