@sema-agent/client-core 0.76.0 → 0.76.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.
Files changed (46) hide show
  1. package/CHANGELOG.md +30 -0
  2. package/README.md +7 -1
  3. package/dist/adapt/arms.js +51 -48
  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 +12 -0
  14. package/dist/adapter/downstream/eventToSdkMessage.js +13 -20
  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/approvalsStreamLiveCapability.js +15 -35
  19. package/dist/deviceExecutorManagementCapability.js +15 -35
  20. package/dist/engineCapReader.d.ts +77 -0
  21. package/dist/engineCapReader.js +87 -0
  22. package/dist/executionLaneCapability.js +15 -38
  23. package/dist/hitl/persistedRulesWire.d.ts +20 -0
  24. package/dist/hitl/persistedRulesWire.js +23 -0
  25. package/dist/index.d.ts +6 -0
  26. package/dist/index.js +22 -1
  27. package/dist/mcpLiveness.d.ts +179 -0
  28. package/dist/mcpLiveness.js +218 -0
  29. package/dist/mcpPanel.d.ts +17 -0
  30. package/dist/mcpPanel.js +21 -0
  31. package/dist/memoryComplianceCapability.d.ts +73 -0
  32. package/dist/memoryComplianceCapability.js +109 -0
  33. package/dist/memoryEntriesWire.d.ts +302 -0
  34. package/dist/memoryEntriesWire.js +595 -0
  35. package/dist/memoryOriginCapability.d.ts +68 -0
  36. package/dist/memoryOriginCapability.js +102 -0
  37. package/dist/peerLaneCapability.d.ts +62 -0
  38. package/dist/peerLaneCapability.js +100 -0
  39. package/dist/permissionRulesWriteCapability.d.ts +64 -0
  40. package/dist/permissionRulesWriteCapability.js +98 -0
  41. package/dist/selfOrchestrationDenial.js +7 -2
  42. package/dist/sqlEngineCapability.js +15 -35
  43. package/dist/webSearchBackendCapability.js +15 -35
  44. package/dist/writeProtectionCapability.js +15 -35
  45. package/docs/INTEGRATION-CLIENTS.md +107 -11
  46. package/package.json +1 -1
@@ -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
+ }
@@ -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
  }
@@ -67,6 +67,26 @@ export declare function persistedRulesLaneAvailable(baseUrl: string | undefined)
67
67
  * 只查前者会在「7.11.0 且规则店在」的 worker 上渲出一个恒 404 的治理入口。
68
68
  */
69
69
  export declare function persistedRulesGovernanceAvailable(baseUrl: string | undefined): boolean;
70
+ /**
71
+ * 收紧方向的单步写入口(把某条规则改成 deny/ask)对**这次调用**在不在。
72
+ *
73
+ * 🔴 只派生自 `capabilities.permissionRulesWrite` 自己的四态读数(`permissionRulesWriteAvailable`),
74
+ * **不合取** `permissionRules`、也**不**再读一遍 `engineCapTrue`——那是撤销面(`persistedRulesGovernanceAvailable`)
75
+ * 的读法,上游明说两位的读法别混:撤销面那一位是与调用方身份无关的部署级存在性信号,必须合取车道位
76
+ * 才是完整判据;写口这一位的值本身**已经**编码了「车道在不在 ∧ 这次调用带不带得到身份」两件事,
77
+ * 再合取一次等于给同一件事立第二个判官——车道开着但写口所在的引擎还没起播,或反过来,合取会把
78
+ * 其中一种真实的过渡态错判。
79
+ * 读的也是**同一份**能力位状态(该四态读器自己的 per-baseUrl 表),不是另开一张表。
80
+ *
81
+ * 🔴 **显式 `undefined` 不许回落装好的默认读锚**(异源对抗复审 [medium] 采纳,真病):
82
+ * `observedPermissionRulesWrite(baseUrl = engineWireTarget()?.baseUrl)` 的默认参数只在参数
83
+ * **省略**时生效,而调用方显式传 `undefined` 与省略在 JS 里同判——原样转发会让「这次没有 baseUrl」
84
+ * 被悄悄换成**装好的默认引擎**那一份读数,与上面两口(`engineCapTrue` 对 `undefined` 恒 `unprobed`
85
+ * ⇒ false,零默认读锚回落)不同律;多引擎宿主上会把「这个会话没配 baseUrl」误读成「随便一个已装
86
+ * 引擎的答案」。传空串绕开默认参数(reader 对空串的处置是 `unobserved`,与 `undefined` 的意图一致
87
+ * 但不触发默认值语义)。
88
+ */
89
+ export declare function persistedRulesWriteAvailable(baseUrl: string | undefined): boolean;
70
90
  /** {@link readCcImportRedeemCounts} 的判决体:200 体上真读出来的落库/去重计数。 */
71
91
  export interface CcImportRedeemCounts {
72
92
  persisted: number;
@@ -1,5 +1,6 @@
1
1
  import { engineCapTrue } from '../engineCapsCache.js';
2
2
  import { hostLog } from '../host.js';
3
+ import { observedPermissionRulesWrite, permissionRulesWriteAvailable } from '../permissionRulesWriteCapability.js';
3
4
  import { readToolApprovalRespondAck } from './toolApprovalWire.js';
4
5
  // ── 能力位 gate ──────────────────────────────────────────────────────────────────────────────
5
6
  /** 规则车道在不在(四口 501 同源谓词)。未判/缺键/base 缺席 = false = 整面藏。 */
@@ -15,6 +16,28 @@ export function persistedRulesLaneAvailable(baseUrl) {
15
16
  export function persistedRulesGovernanceAvailable(baseUrl) {
16
17
  return engineCapTrue(baseUrl, 'permissionRulesRevoke') && engineCapTrue(baseUrl, 'permissionRules');
17
18
  }
19
+ /**
20
+ * 收紧方向的单步写入口(把某条规则改成 deny/ask)对**这次调用**在不在。
21
+ *
22
+ * 🔴 只派生自 `capabilities.permissionRulesWrite` 自己的四态读数(`permissionRulesWriteAvailable`),
23
+ * **不合取** `permissionRules`、也**不**再读一遍 `engineCapTrue`——那是撤销面(`persistedRulesGovernanceAvailable`)
24
+ * 的读法,上游明说两位的读法别混:撤销面那一位是与调用方身份无关的部署级存在性信号,必须合取车道位
25
+ * 才是完整判据;写口这一位的值本身**已经**编码了「车道在不在 ∧ 这次调用带不带得到身份」两件事,
26
+ * 再合取一次等于给同一件事立第二个判官——车道开着但写口所在的引擎还没起播,或反过来,合取会把
27
+ * 其中一种真实的过渡态错判。
28
+ * 读的也是**同一份**能力位状态(该四态读器自己的 per-baseUrl 表),不是另开一张表。
29
+ *
30
+ * 🔴 **显式 `undefined` 不许回落装好的默认读锚**(异源对抗复审 [medium] 采纳,真病):
31
+ * `observedPermissionRulesWrite(baseUrl = engineWireTarget()?.baseUrl)` 的默认参数只在参数
32
+ * **省略**时生效,而调用方显式传 `undefined` 与省略在 JS 里同判——原样转发会让「这次没有 baseUrl」
33
+ * 被悄悄换成**装好的默认引擎**那一份读数,与上面两口(`engineCapTrue` 对 `undefined` 恒 `unprobed`
34
+ * ⇒ false,零默认读锚回落)不同律;多引擎宿主上会把「这个会话没配 baseUrl」误读成「随便一个已装
35
+ * 引擎的答案」。传空串绕开默认参数(reader 对空串的处置是 `unobserved`,与 `undefined` 的意图一致
36
+ * 但不触发默认值语义)。
37
+ */
38
+ export function persistedRulesWriteAvailable(baseUrl) {
39
+ return permissionRulesWriteAvailable(observedPermissionRulesWrite(baseUrl ?? ''));
40
+ }
18
41
  /**
19
42
  * cc-import redeem 200 体的兑付计数**双代归一读口**(server 7.46 未声明 BREAKING 的消费半场):
20
43
  * · **新形**(server 7.46+):`result.members[]` 判别联合,member.status ∈ {'persisted','deduped'}
package/dist/index.d.ts CHANGED
@@ -158,6 +158,11 @@ export * from './writeProtectionCapability.js';
158
158
  export * from './webSearchBackendCapability.js';
159
159
  export * from './executionLaneCapability.js';
160
160
  export * from './approvalsStreamLiveCapability.js';
161
+ export * from './peerLaneCapability.js';
162
+ export * from './permissionRulesWriteCapability.js';
163
+ export * from './memoryComplianceCapability.js';
164
+ export * from './memoryOriginCapability.js';
165
+ export * from './memoryEntriesWire.js';
161
166
  export * from './deviceExecutorManagementCapability.js';
162
167
  export * from './mcpReconnect.js';
163
168
  export * from './leaderConflict.js';
@@ -166,6 +171,7 @@ export * from './runCancelContext.js';
166
171
  export * from './argvFlagValue.js';
167
172
  export * from './readFacePosture.js';
168
173
  export * from './mcpPanel.js';
174
+ export * from './mcpLiveness.js';
169
175
  export * from './effectiveFacts.js';
170
176
  export * from './gateOutcome.js';
171
177
  export * from './postureKnob.js';
package/dist/index.js CHANGED
@@ -194,6 +194,23 @@ export * from './executionLaneCapability.js';
194
194
  // · approvalsStreamLiveCapability(0.73.3 CC-61):capabilities.approvalsStreamLive 四态读面 + feed 消费方「要不要靠对账拉」的判据单源。
195
195
  export * from './approvalsStreamLiveCapability.js';
196
196
  // · deviceExecutorManagementCapability(0.74.2 CC-69):capabilities.deviceExecutor.management 四态读面 + 管理动词可用性判据单源。
197
+ // 各只能力位读器的 tee / 读口 / 失效口 / 测试钩共用 `engineCapReader`(0.76.1 CC-75;包内叶,不出 barrel)。
198
+ // · peerLaneCapability(0.76.1 CC-102):capabilities.peerLane 四态读面 —— 键缺席 = 老 worker 判不出、`false` = 明确没挂跨会话目录席(正面事实);
199
+ // 非 HTTP 面,消费方式是渲染/预期,不 gate 某个动词。「要不要露出跨会话入口」三态判据单源 peerLaneAvailable。
200
+ export * from './peerLaneCapability.js';
201
+ // · permissionRulesWriteCapability(0.76.1 CC-102):capabilities.permissionRulesWrite 四态读面 —— 键缺席 = 这份二进制比单步写口老(藏入口)、
202
+ // 键在场按值判「这次调用够不够得着」(与撤销面那只部署级存在性信号刻意不同源,别互相套用读法)。布尔便利口 permissionRulesWriteAvailable 是本位唯一派生点。
203
+ export * from './permissionRulesWriteCapability.js';
204
+ // · memoryComplianceCapability(0.76.1 CC-97a):capabilities.memoryCompliance 四态读面(出处问询 / 抹除两口)——
205
+ // 键缺席 = 老引擎判不出、`false` = 明确的否、畸形删格绝不折否;两口可用性三态判据 + 记忆治理面 501 码单源。
206
+ export * from './memoryComplianceCapability.js';
207
+ // · memoryOriginCapability(0.76.1 CC-97a):capabilities.memoryOrigin 四态读面(外源标记人面三口)——
208
+ // 与上一只**同判据不同位**,刻意各持一张表、不合并(引擎刻意不合并,今天恒同值也不许折)。
209
+ export * from './memoryOriginCapability.js';
210
+ // · memoryEntriesWire(0.76.1 CC-97a):记忆条目面三个 200 回体的窄读口(导出条目行 / 抹除证明 / 外源清标回执)——
211
+ // 逐行窄读三向边界(空数组 = 真 0 / 非空全坏 = malformed / 半坏 = present + dropped)、`historyUnknown` 三态原样过境、
212
+ // 「抹了 0 条」与「200 空体」与「读不懂」判成三回事,以及「空答绝不读作本店干净」的代码落点(判词口的 originAware 必填形参)。
213
+ export * from './memoryEntriesWire.js';
197
214
  export * from './deviceExecutorManagementCapability.js';
198
215
  // S-381 / core 7.22.0 #857(0.72.9 CC-30;server ≥7.85.0,sdk 尚未声明):会话内 MCP re-dial 的消费口 —— 200 三态按 outcome 分形读、
199
216
  // unsupported 恒五键、名册按 toolNames 在场性、失败按 errorCode 分诊、「重连 = 一次交易」措辞单源;能力位 mcpReconnect 三态读。
@@ -214,6 +231,9 @@ export * from './argvFlagValue.js';
214
231
  export * from './readFacePosture.js';
215
232
  // CC-03(0.68.2):`GET /v1/sessions/:id/mcp` 面板体的防御读视图 + lastLegMcp 一行措辞铸点(三端共用)。
216
233
  export * from './mcpPanel.js';
234
+ // CC-36(0.76.1):引擎托管 MCP 服务器的**活性观察**(`wiring_manifest.mcp[].liveness`)结构读口 ——
235
+ // 三词闭集词表 + 行级三向读数(缺席 / 在场读不出 / 一次观察)+ 腿级四读数判词与它的唯一措辞铸点。
236
+ export * from './mcpLiveness.js';
217
237
  export * from './effectiveFacts.js';
218
238
  export * from './gateOutcome.js';
219
239
  // 0.63.0(sdk 8.8.0 / engine ≥7.67.0 / S-178):`serverGates` 三根 posture 旋钮的读数窄读器
@@ -501,7 +521,8 @@ export * from './hitl/resumeRunningCard.js';
501
521
  // RulesFailure 处置分类(404 两支绝不共用一格)、keyset 翻页收口(游标失效重列 + 页数硬帽)、
502
522
  // skipped.reason 前缀分类、persist-ack 三态读口 —— 读口与 `readToolApprovalRespondAck`
503
523
  // **合成一处**(census top-08 #1 的双份台账收口本体);CC settings 三层 fs 读、SDK facade
504
- // 装配、通知呈现留宿主。
524
+ // 装配、通知呈现留宿主。0.76.1 CC-102 补 persistedRulesWriteAvailable:单步写入口便利口,
525
+ // 只派生自 permissionRulesWriteCapability 的四态读数,与撤销面那只合取式刻意不同源。
505
526
  // · localAllowRule:parseLocalAllowRule 窄化五步(①整工具拒/②工具名对齐/③字面锚/⑤裸解释器
506
527
  // 前缀/⑤b canonical 危险谓词)骨架;危险谓词与规则语法经 `LocalAllowRuleDeps` 注入
507
528
  // (parkOwnership deps 同形)—— 谓词表本体(BARE_SHELL_PREFIXES / dangerousPatterns)留宿主,
@@ -0,0 +1,179 @@
1
+ /**
2
+ * mcpLiveness.ts — 引擎托管的 MCP 服务器**活性观察**(`wiring_manifest.mcp[].liveness`)的三端共用
3
+ * **结构读口** + 三词闭集词表 + 腿级判词(CC-36,0.76.1)。引擎内核自 7.24.3 起铸这一格,服务端的转发层自 7.91.2 起把它送上 wire ——
4
+ * 更老的服务端上这一格**恒缺席**(读数 = `indeterminate`,正是缺席语义要兜的那一档,不是故障)。
5
+ *
6
+ * ── 这一格答的是哪一个问题 ────────────────────────────────────────────────────────────────────
7
+ * 「这台服务器**还够得着吗**」。引擎不为它发任何自己的流量 —— 每条记录都是那条腿**已经做过的工作**
8
+ * (拨号、re-dial、传输关闭、一次发现传输没了的请求)顺带盖的章,自带盖章时刻 `observedAt`。
9
+ * ⇒ 它是**每条腿的一张快照**,不是心跳、不是轮询、更不是「此刻」。
10
+ *
11
+ * 🔴 **同一行上的三个位各说各的,谁都不许顶替谁、不许互相校验、不许据其一推另一个**:
12
+ * · `status`(申报 / 连接时判决)—— 这条腿**拨号那一刻**的判词,引擎**有意冻结**它(服务器中途死了这一格
13
+ * 仍读 `connected`);
14
+ * · re-dial 回执的 `outcome`(动作判决)—— **一次动作**的判词,过去式;
15
+ * · `liveness`(本件)—— 连接层**最后学到的那件事**,自带时戳。
16
+ * 最直白的一格:`status: 'failed'` 与 `liveness.state: 'reachable'` **同一行并存是真行** —— 服务器答了
17
+ * 握手、答的是协议错(**活着,且配错了**)。判据是「两格都在」,不是「两格一致」。
18
+ *
19
+ * ── 🔴 四种读数(三个词 + 缺席),缺席**不是**其中任何一个词 ────────────────────────────────────
20
+ * · `reachable` —— **一次交换完成了**(连上并列出了,或服务器自己答了 —— MCP 级的拒绝**也是答**,所以
21
+ * 它证明够得着,即便那次调用失败);
22
+ * · `unreachable` —— 试过,丢在传输或时钟上(拒连 / 重置 / 关闭 / 起不来 / 超时);
23
+ * · `unknown` —— **试过,而回来的东西答不了这个问题**(服务器前面那台网关写的 HTTP 状态、非 MCP 载荷、
24
+ * 没有结构的抛出值)。它是一句**更强**的断言:引擎看了,看不出来。
25
+ * · **缺席 = indeterminate** —— 这一行**没有可用的观察记录**。四个来源在 wire 上**同形**,消费端分不出也
26
+ * 不需要分:① 这条腿没走到那台服务器(`status: 'skipped'`);② 那份声明从来没被拨过(`invalid_config`);
27
+ * ③ 这是一条老引擎 / 老服务端的帧;④ 上游判形没过、那一格被摘掉了。
28
+ * 🔴 **不许据缺席反推「这条腿拨没拨过号」**,也不许把它折进 `unknown`、折成「健康」、折成「关着」。
29
+ * 这四句话在本包里是四句不同的话,任何折叠都是「把不知道说成没有 / 说成好着呢」的变体。
30
+ *
31
+ * ── 为什么是结构读(不靠型面) ─────────────────────────────────────────────────────────────────
32
+ * 本包 peer 地板上的 wire 型面里**还没有这一格**(键形是引擎侧先出、型面后出的常态)。所以本件按契约
33
+ * **结构读**:从 `unknown` 起按键路径窄化,判据自带(见下),**不抬 peer 地板、不等型面**。安全性不靠型
34
+ * 而靠三条:① 三词按**闭集恰等**判,认不得的词一律畸形(词表属主在上游,本包**不放宽**成开集 —— 放宽了
35
+ * 就会把一个没人定义过的词当成一句真话往屏上送);② `observedAt` 按**有限数**判;③ 判形没过 ⇒ 这一格
36
+ * 摘掉、那一行照读(与上游同判),而摘掉的**理由**留在 {@link McpLivenessCell} 的 `unreadable` 臂上,
37
+ * 与「引擎没报」分开。型面出键那天由包侧门逼一次显式处置(见 `run-mcp-liveness-test.mjs` 的上游见证臂)。
38
+ *
39
+ * ── `errorCode` 只随 `unreachable` ────────────────────────────────────────────────────────────
40
+ * 它是那句话的**注脚**(为什么够不着),不是那句话本身。上游的单点铸造保证它**只随 `unreachable`** 出现,
41
+ * 理由逐字可查:`unknown` 的全部意思就是「那次失败并没有决定可达性」,把失败类别挂在那个词上等于请消费端
42
+ * 拿一份说明不了可达性的证据去决定下一步动作。⇒ 本读口在**非 `unreachable`** 臂上**丢掉这只注脚**
43
+ * (整格仍在、`state` 仍读):去掉的恰是上游点名要防的那条误读路,而 `state` 那句话一个字没动。
44
+ * 注脚自己形坏(空串 / 非串)同样**只丢它自己不拆整格** —— 代价如实:此时读到的是「够不着,原因没说」,
45
+ * **不是**「这条记录坏了」。词按**开集**读(十词闭集的属主在上游,抄一张会把新词吞成缺席)。
46
+ */
47
+ /**
48
+ * 活性词的**三词闭集**(与上游同序:能完成交换 / 完不成 / 答不了)。
49
+ *
50
+ * 🔴 **它只有三个词**。腿级判词的第四个读数 `indeterminate`(见 {@link McpLegLivenessReading})**不是**
51
+ * 本表成员,也绝不许被加进来:本表是**上游的词表**,那第四个读数是**缺席**在本包里的名字。
52
+ * 🔴 消费端按本表写的 `switch` 判**恰等**(不 trim、不折大小写、不按前缀猜);上游加词的唯一处置是
53
+ * **本表同批加一格 + 补一条读数格**,而不是在消费点放宽判据。
54
+ */
55
+ export declare const MCP_LIVENESS_STATES: readonly ["reachable", "unreachable", "unknown"];
56
+ /** {@link MCP_LIVENESS_STATES} 的三个词 —— **由那张表派生**,不另手写一份(两份手写迟早各说一套;本包不依赖上游型面,见文件顶注)。 */
57
+ export type McpLivenessState = (typeof MCP_LIVENESS_STATES)[number];
58
+ /** 一台服务器的**一次**活性观察(读视图;逐键挑,开集键不透传)。 */
59
+ export interface McpLivenessView {
60
+ /** 三词闭集(见 {@link MCP_LIVENESS_STATES});认不得的词不到这里(整格判畸形)。 */
61
+ state: McpLivenessState;
62
+ /** 盖章时刻(epoch 毫秒,有限数)。🔴 它是**那条腿**的观察时刻 —— 不是「此刻」,也不与面板的
63
+ * `asOf` 同源,两者**不比**。 */
64
+ observedAt: number;
65
+ /** 够不着的**类别注脚**(开集,上游十词;**只随 `unreachable`** 在场)。缺席 = 原因没说,
66
+ * **不是**「这条记录坏了」,更不是第四态。 */
67
+ errorCode?: string;
68
+ }
69
+ /**
70
+ * 一行上这一格的三向读数(**缺席 / 在场读不出 / 一次观察**三句话分开,任何两句都不折)。
71
+ * · `absent` —— 键不在场 = 没有可用的观察记录(四源同形,见文件顶注);
72
+ * · `unreadable` —— 键**在场**而读不出(整格不是对象 / 词出闭集 / `observedAt` 非有限数)。它与 `absent`
73
+ * **不是**一句话:「引擎送了一份我读不懂的」与「引擎没报」对运维是两件事;
74
+ * · `observation` —— 读得出的那一次观察。
75
+ */
76
+ export type McpLivenessCell = {
77
+ kind: 'absent';
78
+ } | {
79
+ kind: 'unreadable';
80
+ } | {
81
+ kind: 'observation';
82
+ liveness: McpLivenessView;
83
+ };
84
+ /**
85
+ * 从**一行** MCP 名册条目里读这一格(结构读;单一铸点,别在消费点再写一遍判据)。
86
+ *
87
+ * 缺席答什么:`{kind:'absent'}` —— 判据是**自有键在不在**(`Object.hasOwn`),不按真值判。原型链上的
88
+ * 同名键**永远不会**上 wire,拿它当在场 = 把本进程对象的形状当成引擎说过的话;`null` / `undefined`
89
+ * 显式在场是**坏形**(走 `unreadable`),不是缺席形。
90
+ * 畸形答什么:`{kind:'unreadable'}` —— 整行不是对象、整格不是对象(含 `null` / 数组 / 串 / 数)、
91
+ * `state` 不是三词之一(含空串、近形拼法、大小写变体)、`observedAt` 非有限数(含缺席 / 串 / `NaN`)。
92
+ * 🔴 **绝不**折成 `{kind:'absent'}`(会把「送来一份读不懂的」说成「引擎没报」),更**绝不**折成
93
+ * `reachable` 或任何一个词(那是把读不出说成好着呢)。
94
+ * 为什么不折:这三句话在本包下游各对一种不同的处置 —— 缺席 = 什么都不渲(**不许**渲「一台都够不着」也
95
+ * **不许**渲「都好着」);读不出 = 渲「这一格读不出」并让运维去看引擎版本与投影层;观察 = 渲那个词
96
+ * + 那个时刻。折任何一对,渲出来的都是一句谁都没说过的话。
97
+ *
98
+ * ⚠️ 本口**只读这一格**:同行的 `status` / `errorCode` / `delivered` 一个字节都不碰,也不拿它们互相校验
99
+ * (三个位各说各的,见文件顶注)。
100
+ */
101
+ export declare function readMcpLiveness(row: unknown): McpLivenessCell;
102
+ /**
103
+ * 腿级判词能吃的**最小行形**(结构型入参:凡带这两格的行视图都喂得进来,本口刻意不与某一张具体的
104
+ * 行视图型耦合 —— 活体腿的名册与面板上那条腿的回放是**同一只读器**的产物,判词也只该有一份)。
105
+ */
106
+ export interface McpLivenessBearingRow {
107
+ /** 读得出的那次观察(缺席 = 这一行没有可用观察,或那一格读不出 —— 后者看 {@link livenessUnreadable})。 */
108
+ liveness?: McpLivenessView;
109
+ /** never false:这一行的活性格**在场而读不出**(与整格缺席不是一句话)。 */
110
+ livenessUnreadable?: true;
111
+ }
112
+ /**
113
+ * 一条腿的活性读数:三个词 + `indeterminate`(= 这条腿上**没有任何在场的活性格**)。
114
+ *
115
+ * 🔴 `indeterminate` **不是**上游词表的成员(词表仍是三词,见 {@link MCP_LIVENESS_STATES})——
116
+ * 它是**缺席**在本包里的名字,存在的全部理由就是不让缺席借用那三个词里的任何一个。
117
+ */
118
+ export type McpLegLivenessReading = McpLivenessState | 'indeterminate';
119
+ /** 一条腿的活性判词 + 它的两条诚实附注。 */
120
+ export interface McpEngineLegHealth {
121
+ /** 判词(四读数;合成规则见 {@link mcpLivenessRollupOf})。 */
122
+ reading: McpLegLivenessReading;
123
+ /** never false:这条腿上**至少有一行**的活性格在场而读不出(判词为什么退到 `unknown` 的分辨位)。 */
124
+ unreadableCells?: true;
125
+ /**
126
+ * **判词那个词自己的**观察时刻(epoch 毫秒)= 带着决定 {@link reading} 那个词的那些行里**最新的**一格。
127
+ *
128
+ * 🔴 刻意**不是**「这条腿上所有观察里最新的那一格」(异源对抗复审 r1 [medium] 采纳,真病):一行在
129
+ * t=1000 观察到 `unreachable`、另一行在 t=900000 观察到 `reachable` 时,跨行取最大会让「至少有一台
130
+ * 够不着」这句判词后面跟上一个**属于那次成功观察**的时刻 —— 运维会把一次旧故障读成刚刚又被确认一次。
131
+ * ⇒ 时刻与判词**同源**:判词说 `unreachable`,这一格就是最新那次 `unreachable` 的时刻。
132
+ * 🔴 在场 iff 有**读得出的观察**决定了判词:`indeterminate` 上永不在场;判词是 `unknown` 而**只**由
133
+ * 「在场读不出」的格决定(没有一格读得出)时同样不在场 —— 读不出的格没有可用时刻,编一个就是造读数。
134
+ * ⚠️ 它是**那条腿**的观察时刻,不是「此刻」,也与面板的 materialize 时刻不同源、**不比**。
135
+ * 别的词那些行的时刻本口不铸(要逐行时刻就读行视图,不要从一个聚合值反推某一行)。
136
+ */
137
+ observedAt?: number;
138
+ }
139
+ /**
140
+ * 一条腿的名册 → 活性判词(**唯一铸点**:别在各端的状态行装配里另写一遍这套合成规则)。
141
+ *
142
+ * 合成规则 = **最坏事实优先**,不是多数票、不是取最新一格:
143
+ * ① 任一行 `unreachable` ⇒ `unreachable`(有一台**可证**打不通时说「够得着」,就是把坏消息折成健康);
144
+ * ② 否则任一行 `unknown` **或任一行的格读不出**(`livenessUnreadable`,或入参自带一格形坏的 `liveness`)
145
+ * ⇒ `unknown`。在**腿**这一层,`unknown` 的意思恰是
146
+ * 「有观察在场而答不了这个问题」,而一份读不懂的格**同样答不了** ⇒ 🔴 **读不出的格绝不作可达的证据**
147
+ * (否则「一行读不出 + 一行 reachable」会合成一句「这条腿够得着」,而那行读不出的格完全可能本来写着
148
+ * `unreachable`)。为什么答不了,由 {@link McpEngineLegHealth.unreadableCells} 分辨;
149
+ * ③ 否则任一行 `reachable` ⇒ `reachable`(其余行缺席 = 没观察,不是反证);
150
+ * ④ 否则 ⇒ `indeterminate`。
151
+ *
152
+ * 缺席答什么:`{reading:'indeterminate'}` —— 三种入参落这一档且**刻意同形**:段读不出(`undefined`)、
153
+ * 零申报(`[]`,这条腿一台服务器都没申报 ⇒ 没有服务器就没有观察)、每一行的活性格都缺席(老引擎 /
154
+ * 都没走到)。要分「一台都没申报」与「有服务器但没报活性」,问名册本身(行数)或在场判词,**不要**
155
+ * 让本口替那一问答话。
156
+ * 畸形答什么:整个入参不是数组 ⇒ `indeterminate`;行本身不是对象 ⇒ **那一行什么都不贡献**(连「有没有
157
+ * 这一格」都没读出来,不替它认领一格);行在而那一格形坏 ⇒ 按上面的 ② 算「读不出」。**永不抛**。
158
+ * 为什么不折:`indeterminate` 与三个词之间**没有**默认值可回落 —— 回落到 `reachable` 是谎、回落到
159
+ * `unreachable` 是恐慌、回落到 `unknown` 是替引擎认领一次它没做过的观察。
160
+ */
161
+ export declare function mcpLivenessRollupOf(rows: readonly McpLivenessBearingRow[] | undefined): McpEngineLegHealth;
162
+ /**
163
+ * 活性那一行措辞的**唯一铸点**(三端共用;别在各端的行装配里另写一遍)。
164
+ *
165
+ * 🔴 四句刻意逐字互异,且**没有一句**把 `indeterminate` 说成「都好着」或「一台都够不着」:
166
+ * ① `reachable` —— 上次交换完成了(这条腿的观察,带时刻);
167
+ * ② `unreachable` —— 至少一台**可证**够不着;
168
+ * ③ `unknown` —— 看过了,答不了(读不出的格同落这一档,附注多一句);
169
+ * ③′ `unknown` 且**只**由读不出的格决定(判词无时刻 ∧ `unreadableCells`)—— 另一句:记录在场而**本端读不出**。
170
+ * ③ 那句的主语是「回来的东西」(引擎看了、看不出来),③′ 的主语是本端的读器 —— 上游哪天加了第四个词,
171
+ * 老客户端落的正是 ③′;把它说成 ③ 等于替引擎认领一次它没做过的「看不出来」。
172
+ * ④ `indeterminate` —— **没报**:这条腿上没有可用的观察记录,**不武断咎为引擎版本**(四源同形)。
173
+ * 🔴 句子里**不出现**服务器台数与「健康 / 正常」这类词:台数在名册上,而「健康」是本格答不了的问题
174
+ * (它只答「还够得着吗」)。
175
+ * 🔴 句尾那个时刻**属于句子里那个判词**(见 {@link McpEngineLegHealth.observedAt}):说「至少有一台够不着」
176
+ * 时它是最新那次**够不着**的观察时刻,不是这条腿上任何一次观察的最大值 —— 否则一句旧故障的判词会跟上
177
+ * 一个属于别的词的新时刻,读起来像刚刚又确认了一次。时刻缺席时句子里就没有时刻,不编「刚刚」。
178
+ */
179
+ export declare function mcpEngineLegHealthDetail(health: McpEngineLegHealth | undefined): string;