@sema-agent/client-core 0.57.0 → 0.59.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +280 -0
- package/README.md +9 -4
- package/dist/adapt/arms.js +6 -1
- package/dist/adapter/downstream/eventToSdkMessage.d.ts +27 -0
- package/dist/adapter/downstream/eventToSdkMessage.js +87 -1
- package/dist/engineCapsCache.d.ts +40 -0
- package/dist/engineCapsCache.js +31 -0
- package/dist/engineErrorCodes.d.ts +15 -0
- package/dist/engineErrorCodes.js +15 -0
- package/dist/hitl/toolApprovalWire.d.ts +234 -92
- package/dist/hitl/toolApprovalWire.js +231 -14
- package/dist/index.d.ts +2 -0
- package/dist/index.js +7 -0
- package/dist/printToolResultFrame.d.ts +18 -0
- package/dist/resumeRefusalCopy.d.ts +58 -0
- package/dist/resumeRefusalCopy.js +115 -0
- package/dist/seam.d.ts +25 -5
- package/dist/seam.js +5 -3
- package/dist/sqlEngineCapability.d.ts +131 -0
- package/dist/sqlEngineCapability.js +191 -0
- package/docs/INTEGRATION-CLIENTS.md +408 -16
- package/package.json +3 -3
package/dist/seam.d.ts
CHANGED
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
*/
|
|
9
9
|
import type { ModelUsage, SDKMessage } from '@sema-agent/agent-types';
|
|
10
10
|
import type { EngineTurnUsage } from './adapter/downstream/turnUsageToModelUsage.js';
|
|
11
|
-
import type { WiringManifestAutoMode, WiringManifestModelGate } from './adapter/downstream/eventToSdkMessage.js';
|
|
11
|
+
import type { WiringManifestAutoMode, WiringManifestModelGate, WiringManifestMcpEntry } from './adapter/downstream/eventToSdkMessage.js';
|
|
12
12
|
/**
|
|
13
13
|
* `AbortSignal` 的结构型(B3 扩容,SEAM-GAP-4)。
|
|
14
14
|
*
|
|
@@ -393,22 +393,28 @@ export type ChromeEvent = {
|
|
|
393
393
|
| HumanInputChromeEvent | EngineNoticeChromeEvent | TextSegmentEndChromeEvent | WiringManifestChromeEvent;
|
|
394
394
|
/**
|
|
395
395
|
* {@link ChromeEvent} 的 `wiring_manifest` 臂(core #524 + core 147③,server ≥7.58.0)——
|
|
396
|
-
*
|
|
396
|
+
* 引擎接线自述里**三段面向终端用户的事实**(S-124 起 `mcp[]` 是第三段),其余每一段仍不投影(射程见 eventToSdkMessage 的
|
|
397
397
|
* `case 'wiring_manifest'` 头注)。
|
|
398
398
|
*
|
|
399
|
-
* ── 🔴 宿主消费义务(
|
|
399
|
+
* ── 🔴 宿主消费义务(四条,全部是「不许做什么」)────────────────────────────────────────────
|
|
400
400
|
* ① **缺席不可反推**。`modelGate` 缺席 = 本 run 没有门卸(core 只在真卸时铸段),
|
|
401
401
|
* `autoMode` 缺席 = 老 mint / 外部 derive **没报**。两者都**不许**被渲成一句肯定句
|
|
402
402
|
* (「没有工具被卸掉」/「auto 未武装」)—— 那是把「没报」说成「报了个否」。
|
|
403
|
-
*
|
|
403
|
+
* `mcp` 缺席同理(义务④)。本臂在**三段都不成形**时根本不会到达,所以宿主见到本臂就至少有
|
|
404
|
+
* 一段是真读数。
|
|
404
405
|
* ② **`autoMode.reason` 六词逐字呈现,不许映射**到 `/v1/capabilities.permissionModeAuto.reason`
|
|
405
406
|
* 的六词:两套词表**同名不同义**(`settings_denied` 在 capabilities 那边折 `no_intent`
|
|
406
407
|
* 不折 `denied`)。要两面都说,就两面各自读、各自渲,绝不归一。
|
|
407
408
|
* ③ **`modelGate.restore` 原样呈现**:它是 core 铸的**逐字**恢复办法,宿主自己拼一句
|
|
408
409
|
* 「试试把某某开关关掉」等于替引擎编了一条它没说过的出口。
|
|
410
|
+
* ④ **`mcp: []` 不许当缺席**(S-124,0.59.0)。空数组是「这条腿一台 MCP 都没申报」这句**正面
|
|
411
|
+
* 事实**,缺席才是「老 mint / 外部 derive 没报」—— 判在场写 `mcp !== undefined`,写
|
|
412
|
+
* `mcp?.length` 就把两句话折成了一句。条目里 `toolCount: 0` 同理(连上了、零工具 ≠ 没报)。
|
|
413
|
+
* ⚠️ 反过来本包也**不会**拿 `[]` 骗你:一份非空却整表读不出来的回体在投影层就落成**段缺席**
|
|
414
|
+
* (异源对抗复审 r1 真病),所以你读到的 `[]` 一定是引擎真报的零申报,不是「都被丢光了」。
|
|
409
415
|
* 🔴 **幂等**:durable 腿重放会再送同一帧(与 `workspace_changed`/`engine_notice` 同纪律),
|
|
410
416
|
* 宿主按 run/leg 去重,别按到达次数计数。
|
|
411
|
-
* 缺席(宿主不接本臂)=
|
|
417
|
+
* 缺席(宿主不接本臂)= 这三条披露在该宿主上看不见,**不是**报错。
|
|
412
418
|
*/
|
|
413
419
|
export interface WiringManifestChromeEvent {
|
|
414
420
|
kind: 'wiring_manifest';
|
|
@@ -417,6 +423,20 @@ export interface WiringManifestChromeEvent {
|
|
|
417
423
|
modelGate?: WiringManifestModelGate;
|
|
418
424
|
/** effective 腿恒在;缺席只表示「没报」(见义务①)。 */
|
|
419
425
|
autoMode?: WiringManifestAutoMode;
|
|
426
|
+
/**
|
|
427
|
+
* 本条腿**申报的每台 MCP 服务器**的连接时快照(S-124 / core 7.5.0,server ≥7.60.0)。
|
|
428
|
+
*
|
|
429
|
+
* 🔴 **空数组不是缺席**(core 顶注逐字,也是本臂第四条消费义务):`[]` = 「这条腿一台都没申报」
|
|
430
|
+
* (一句正面事实,该渲成那句话);**缺席** = 老 mint / 外部 derive **没报**(什么都别渲)。
|
|
431
|
+
* 宿主写 `mcp?.length ? … : …` 就把两者折成了一件 —— 判在场用 `mcp !== undefined`。
|
|
432
|
+
* ⚠️ 本包这一侧的对偶承诺:非空却零行幸存(整表读不出来)的回体**不铸 `[]`**,落段缺席。
|
|
433
|
+
* 🔴 **不与 `GET /v1/sessions/:id/mcp` 的二态合并、也不互相校验**(server 裁定逐字):那条端点的
|
|
434
|
+
* 真源是 server 自己的部署面台账,本段的真源是 core 这条腿 materialize 的连接时快照 ——
|
|
435
|
+
* 同名不同源,合并只会造出一个「哪个才算数」的新问题。
|
|
436
|
+
* 🔴 **`errorCode` 按开集分支**(十词 + `http_<status>` 形),`switch` 必须带 `default` 臂;
|
|
437
|
+
* 远端错误**自由文本**(`error`)上游就不投,宿主也拿不到 —— 可操作的因由在 `errorCode`。
|
|
438
|
+
*/
|
|
439
|
+
mcp?: readonly WiringManifestMcpEntry[];
|
|
420
440
|
/** core 铸的事件身份(uuidv7 形);wire 未必带 ⇒ 缺席时本键不在场。 */
|
|
421
441
|
eventId?: string;
|
|
422
442
|
}
|
package/dist/seam.js
CHANGED
|
@@ -50,9 +50,11 @@ const CHROME_ARM_TABLE = {
|
|
|
50
50
|
},
|
|
51
51
|
wiring_manifest: {
|
|
52
52
|
required: false,
|
|
53
|
-
duty: '
|
|
54
|
-
'autoMode = 武装位 + core
|
|
55
|
-
'
|
|
53
|
+
duty: '可选:渲引擎接线自述里的三段用户面事实(modelGate = 本 run 被模型门卸掉的工具 + 逐字恢复办法;' +
|
|
54
|
+
'autoMode = 武装位 + core 六词原因;mcp[] = 本腿申报的每台 MCP 服务器的连接时快照)。' +
|
|
55
|
+
'🔴 三段缺席一律不渲肯定句;reason 绝不映射 capabilities 六词;restore 原样呈现;' +
|
|
56
|
+
'mcp 的空数组是「一台都没申报」这句正面事实、**不是**缺席(判在场写 mcp !== undefined,' +
|
|
57
|
+
'别写 mcp?.length),errorCode 按开集分支必带 default;durable 重放按 run/leg 幂等',
|
|
56
58
|
},
|
|
57
59
|
text_segment_end: {
|
|
58
60
|
required: false,
|
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* sqlEngineCapability — S-131「SQL 引擎姿态」的**三端共用读面**(server ≥7.60.0 的
|
|
3
|
+
* `GET /v1/capabilities` 新增 `sql` 位;sdk 8.3.0 `Capabilities.sql`)。
|
|
4
|
+
*
|
|
5
|
+
* ── 归层出身(不掩盖:这份窄读器**本来就该在这里**)────────────────────────────────────────
|
|
6
|
+
* 0.58.0 时本件写在壳里(cli `src/sema/sqlEngineCapability.ts`),理由是当时 client-core 的
|
|
7
|
+
* `engineCapsCache` 公面只到「平铺布尔 / 平铺串 / 嵌套布尔」三形,**没有嵌套对象的读口** ⇒ 这一格
|
|
8
|
+
* 在包侧结构上读不出来。0.59.0 两件同批补齐:`engineCapsCache` 加四态读口
|
|
9
|
+
* {@link import("./engineCapsCache.js").engineCapValue},本模块把壳那份窄读器**逐字上收**。
|
|
10
|
+
* 🔴 **导出名与壳侧那份逐字相同**,这是 drift-lock:包一发同名符号,壳的
|
|
11
|
+
* `run-layer-shadow-export-gate-test.mjs`(同名影子导出门)当场红,逼那份壳侧副本换装成
|
|
12
|
+
* `export { … } from '@sema-agent/client-core'`,而不是两边各自演进(DISEASE-SHAPES S37 的病形
|
|
13
|
+
* 正是「包侧有了、壳侧没删」)。
|
|
14
|
+
*
|
|
15
|
+
* ── 上游事实(fixture 直证,不采信 CHANGELOG)──────────────────────────────────────────────
|
|
16
|
+
* `@sema-agent/server/dist/http/routes/capabilities.js` 的回体里多一位
|
|
17
|
+
* `sql: projectSqlEngineCapability(deps.sqlEngineFacts?.())`;7.59.0 的同一文件**没有这个键**。
|
|
18
|
+
* 值的形(`server/dist/sql-engine-posture.d.ts` 逐字):
|
|
19
|
+
* · `{ engine, isolation, txnMode }` —— 驱动在**真连接**上 `SET` 完之后**回读**的值
|
|
20
|
+
* (「取实例真值,不按配置推断」);
|
|
21
|
+
* · `null` —— **本部署没有 SQL 后端**(env-only / local 文件后端)。逐字:「缺席就是缺席…
|
|
22
|
+
* 不铸一个看起来像答案的空壳」。
|
|
23
|
+
*
|
|
24
|
+
* ── 🔴 四态,不是两态([honest-absence-not-fabricated-zero])────────────────────────────────
|
|
25
|
+
* 这一格上有**四件互不相同**的事,消费端一件都不许折进另一件:
|
|
26
|
+
* ① `unobserved` —— **本进程一次 caps 响应都没观测到**。一次性 `sema doctor` 就是这一档
|
|
27
|
+
* (它不起 live client ⇒ caps tee 从来没触发过)。它**不是**「引擎没有 SQL」。
|
|
28
|
+
* ② `not_reported` —— caps 观测到了,而回体上**没有** `sql` 这个键 ⇒ 老引擎(<7.60.0)。
|
|
29
|
+
* 它**不是**「这台部署没有 SQL 后端」—— 老引擎有没有库,这个读面答不了。
|
|
30
|
+
* ③ `none` —— 引擎**明确说** `null`:这台部署没有 SQL 后端。这是一个**正面事实**,不是缺席;
|
|
31
|
+
* 把它渲成「未观测」等于把引擎真给的答案丢掉(壳自 spawn 的单机形恒落这一档,是常态读数)。
|
|
32
|
+
* ④ `present` —— 三座俱全,原样渲。
|
|
33
|
+
* 🔴 **绝不渲 0 / 绝不编一个姿态**:读不出来就说读不出来,哪一种读不出来也要分清楚。
|
|
34
|
+
* ⇄ 这四个词与 {@link import("./engineCapsCache.js").EngineCapValueState} 的四态**同一套词汇**
|
|
35
|
+
* (`unobserved` / `not_reported` / `null`→`none` / `value`→`present`):通用读口答「这一格上有
|
|
36
|
+
* 没有值」,本模块答「这一格的值成不成形」,刻意分层 —— 通用口不认识 `sql` 的三座,窄读器不该
|
|
37
|
+
* 重新实现一遍缓存与代际。
|
|
38
|
+
*
|
|
39
|
+
* ── 🔴 UNTRUSTED-for-display ──────────────────────────────────────────────────────────────
|
|
40
|
+
* `isolation` 是从**数据库服务器**变量里回读的串(`@@transaction_isolation` 一族),不是引擎铸的
|
|
41
|
+
* 闭词;三座一律只渲染、绝不参与任何判定,呈现前过本包的单行消毒单源
|
|
42
|
+
* ({@link escapeDisplayControlChars})。
|
|
43
|
+
*/
|
|
44
|
+
/**
|
|
45
|
+
* 三座俱全时的读数(server `SqlEngineCapability` 逐字同形)。
|
|
46
|
+
*
|
|
47
|
+
* 🔴 **`txnMode` 的域含 `null`,而 `null` 不是缺席**(fixture `sql-driver.d.ts` 逐字:
|
|
48
|
+
* 「TiDB's session `tidb_txn_mode`; `null` = this engine has no such indicator (**NOT**
|
|
49
|
+
* "optimistic")」;铸点逐字 `txnMode: engine === "tidb" ? "pessimistic" : null`)⇒
|
|
50
|
+
* **三种引擎里有两种(`innodb` / `pg`)这一位恒 `null`**。把 `null` 判成畸形 = 两种正常部署
|
|
51
|
+
* 的完整读数被整条丢掉、doctor 错报「没有引擎响应」—— 那正是 B-025 的病形(消费域比铸点域
|
|
52
|
+
* 更窄)在新件上复发。
|
|
53
|
+
* 🔴 型写 `string | null` 而**不是**手抄上游那个 `'pessimistic' | null` 闭词:抄一份就是给自己
|
|
54
|
+
* 立第二个判官(上游哪天给别的引擎加一个指示词,手抄的那份会把它判畸形)。窄读域只许**等于
|
|
55
|
+
* 或宽于**铸点域。
|
|
56
|
+
*/
|
|
57
|
+
export interface SqlEngineCapabilityView {
|
|
58
|
+
engine: string;
|
|
59
|
+
isolation: string;
|
|
60
|
+
txnMode: string | null;
|
|
61
|
+
}
|
|
62
|
+
/** 四态读数(见文件头)。`unobserved` 由**读口**在这一格空缺时铸,不由投影铸。 */
|
|
63
|
+
export type SqlEngineReading = {
|
|
64
|
+
kind: 'unobserved';
|
|
65
|
+
} | {
|
|
66
|
+
kind: 'not_reported';
|
|
67
|
+
} | {
|
|
68
|
+
kind: 'none';
|
|
69
|
+
} | {
|
|
70
|
+
kind: 'present';
|
|
71
|
+
view: SqlEngineCapabilityView;
|
|
72
|
+
};
|
|
73
|
+
/**
|
|
74
|
+
* caps 回体 → 本格读数;**畸形一律 `undefined`**(= 这一格不写 ⇒ 读口答 `unobserved`)。
|
|
75
|
+
*
|
|
76
|
+
* 🔴 `undefined`(键不在)与 `null`(引擎说没有)**是两件事**,本函数是本包唯一区分它们的地方:
|
|
77
|
+
* 前者 ⇒ `not_reported`(老引擎),后者 ⇒ `none`(这台部署没库)。用 `??` / falsy 判会把
|
|
78
|
+
* 两者压成一件,而它们对运维的意思完全不同。
|
|
79
|
+
* 🔴 三座**任一**不是非空串 ⇒ 整条判畸形(`undefined`)。绝不留一个缺座的对象:一行写着
|
|
80
|
+
* 「isolation: 」的诊断比不渲这一行更坏(它看起来像一个答案)。
|
|
81
|
+
*/
|
|
82
|
+
export declare function projectSqlEngineCapability(caps: unknown): SqlEngineReading | undefined;
|
|
83
|
+
/**
|
|
84
|
+
* 宿主 caps probe 的**读面 tee** 落点(壳侧与 mcpGate / sessionBackground / projectContext /
|
|
85
|
+
* crashConverged / workflowsGate 五条并列)。绝不 throw —— 读面腿不许反噬 caps 探测链。
|
|
86
|
+
*
|
|
87
|
+
* 🔴 投影 `undefined`(caps 畸形)⇒ 这一格**被删**而不是留着上一台引擎的旧值 —— 留旧值 = 拿一台
|
|
88
|
+
* 已经不在的引擎的读数去回答「这台现在是什么姿态」。
|
|
89
|
+
* ⚠️ **这一条只覆盖「收到了一份读不懂的回体」**。「同端口 respawn / 版本回滚之后新探测**失败或
|
|
90
|
+
* 还在飞**」是**另一条路** —— 那时本 tee 一次都不触发,挡不住旧读数继续被当成当代事实。
|
|
91
|
+
* 那一形由 {@link forgetSqlEngineReading} 在装配口(宿主的引擎温切臂)上收。
|
|
92
|
+
* 🔴 **不按 principal 分域**:SQL 姿态是**这台 worker 的连接事实**,与调用者是谁无关
|
|
93
|
+
* (与 `workflows` 那种 per-caller 的位刻意不同)—— 加一个用不上的去重域只会造出一条
|
|
94
|
+
* 「换主体就读不到」的假缺席。
|
|
95
|
+
*/
|
|
96
|
+
export declare function noteEngineCapsForSqlEngine(baseUrl: string, caps: unknown): void;
|
|
97
|
+
/**
|
|
98
|
+
* 本进程观测到的 SQL 姿态;这一格空缺 ⇒ `{kind:'unobserved'}`(**绝不**折成 `none`)。
|
|
99
|
+
*
|
|
100
|
+
* 缺省读锚 = {@link engineWireTarget} 的 `baseUrl`(本包零 `process` —— portability 门盯着;
|
|
101
|
+
* 壳那份直读 `process.env.SEMA_LIVE_BASEURL`,而 `engineWireTarget()` 的 env 臂读的就是同一个变量
|
|
102
|
+
* ⇒ Node 壳上行为一字节不变,浏览器/桌面宿主则由 `installEngineWireTarget()` 显式装)。
|
|
103
|
+
*/
|
|
104
|
+
export declare function observedSqlEngine(baseUrl?: string | undefined): SqlEngineReading;
|
|
105
|
+
/**
|
|
106
|
+
* 四态 → doctor 那一行的 detail 串。**唯一措辞真源**(三端共用一句话;别在各端的行装配里另写
|
|
107
|
+
* 一遍 —— 那正是这次上收要消灭的东西)。
|
|
108
|
+
*
|
|
109
|
+
* 🔴 三座是 UNTRUSTED-for-display(`isolation` 是从**数据库服务器**变量里回读的串):呈前消毒 +
|
|
110
|
+
* 封长。消毒放在这一处而不是各呈现点,是因为本函数的产物就是屏上那一串(单一出口)。
|
|
111
|
+
* 🔴 四句话刻意**互不相同、也互不蕴含**:「未观测」不许说成「没有库」,「老引擎不报」也不许
|
|
112
|
+
* 说成「没有库」—— 三种「读不出」对运维是三条不同的下一步。
|
|
113
|
+
*/
|
|
114
|
+
export declare function sqlEngineDoctorDetail(reading: SqlEngineReading): string;
|
|
115
|
+
/**
|
|
116
|
+
* **换代失效口**(与 {@link import("./engineCapsCache.js").invalidateEngineCaps} 并列,由宿主在
|
|
117
|
+
* 引擎温切成功后调)。
|
|
118
|
+
*
|
|
119
|
+
* 🔴 **代际闸挡不住这一形**:那道闸挡的是「旧响应**写脏**新一代」(tee 只在确认代际仍当代之后
|
|
120
|
+
* 才落地),而本格的病是反过来的 —— 新一代的探测**根本没成功**(超时 / reject / 还在飞),
|
|
121
|
+
* tee 于是一次都没触发,上一台引擎留在同一端口上的读数就继续被 {@link observedSqlEngine}
|
|
122
|
+
* 当成**当代事实**答出去。同端口 respawn / 版本回滚 / 引擎起不来三条路都走这里,而它在
|
|
123
|
+
* doctor 上的形态最坏:一行**肯定句**(「这台部署是 tidb 悲观事务」/「这台引擎不报这一位,
|
|
124
|
+
* 要升级」),说的却是一台已经不在的引擎。
|
|
125
|
+
* 🔴 处置是**清成未观测**,不是留旧值也不是铸一个 `none` —— 「我这一代还没听到答案」是这一格
|
|
126
|
+
* 唯一诚实的话(与 {@link noteEngineCapsForSqlEngine} 对畸形回体的处置同向)。
|
|
127
|
+
* 空串 ⇒ no-op;绝不 throw(它跑在引擎温切路径上)。
|
|
128
|
+
*/
|
|
129
|
+
export declare function forgetSqlEngineReading(baseUrl: string | undefined): void;
|
|
130
|
+
/** 测试钩子。 */
|
|
131
|
+
export declare function __resetSqlEngineReadingsForTests(): void;
|
|
@@ -0,0 +1,191 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* sqlEngineCapability — S-131「SQL 引擎姿态」的**三端共用读面**(server ≥7.60.0 的
|
|
3
|
+
* `GET /v1/capabilities` 新增 `sql` 位;sdk 8.3.0 `Capabilities.sql`)。
|
|
4
|
+
*
|
|
5
|
+
* ── 归层出身(不掩盖:这份窄读器**本来就该在这里**)────────────────────────────────────────
|
|
6
|
+
* 0.58.0 时本件写在壳里(cli `src/sema/sqlEngineCapability.ts`),理由是当时 client-core 的
|
|
7
|
+
* `engineCapsCache` 公面只到「平铺布尔 / 平铺串 / 嵌套布尔」三形,**没有嵌套对象的读口** ⇒ 这一格
|
|
8
|
+
* 在包侧结构上读不出来。0.59.0 两件同批补齐:`engineCapsCache` 加四态读口
|
|
9
|
+
* {@link import("./engineCapsCache.js").engineCapValue},本模块把壳那份窄读器**逐字上收**。
|
|
10
|
+
* 🔴 **导出名与壳侧那份逐字相同**,这是 drift-lock:包一发同名符号,壳的
|
|
11
|
+
* `run-layer-shadow-export-gate-test.mjs`(同名影子导出门)当场红,逼那份壳侧副本换装成
|
|
12
|
+
* `export { … } from '@sema-agent/client-core'`,而不是两边各自演进(DISEASE-SHAPES S37 的病形
|
|
13
|
+
* 正是「包侧有了、壳侧没删」)。
|
|
14
|
+
*
|
|
15
|
+
* ── 上游事实(fixture 直证,不采信 CHANGELOG)──────────────────────────────────────────────
|
|
16
|
+
* `@sema-agent/server/dist/http/routes/capabilities.js` 的回体里多一位
|
|
17
|
+
* `sql: projectSqlEngineCapability(deps.sqlEngineFacts?.())`;7.59.0 的同一文件**没有这个键**。
|
|
18
|
+
* 值的形(`server/dist/sql-engine-posture.d.ts` 逐字):
|
|
19
|
+
* · `{ engine, isolation, txnMode }` —— 驱动在**真连接**上 `SET` 完之后**回读**的值
|
|
20
|
+
* (「取实例真值,不按配置推断」);
|
|
21
|
+
* · `null` —— **本部署没有 SQL 后端**(env-only / local 文件后端)。逐字:「缺席就是缺席…
|
|
22
|
+
* 不铸一个看起来像答案的空壳」。
|
|
23
|
+
*
|
|
24
|
+
* ── 🔴 四态,不是两态([honest-absence-not-fabricated-zero])────────────────────────────────
|
|
25
|
+
* 这一格上有**四件互不相同**的事,消费端一件都不许折进另一件:
|
|
26
|
+
* ① `unobserved` —— **本进程一次 caps 响应都没观测到**。一次性 `sema doctor` 就是这一档
|
|
27
|
+
* (它不起 live client ⇒ caps tee 从来没触发过)。它**不是**「引擎没有 SQL」。
|
|
28
|
+
* ② `not_reported` —— caps 观测到了,而回体上**没有** `sql` 这个键 ⇒ 老引擎(<7.60.0)。
|
|
29
|
+
* 它**不是**「这台部署没有 SQL 后端」—— 老引擎有没有库,这个读面答不了。
|
|
30
|
+
* ③ `none` —— 引擎**明确说** `null`:这台部署没有 SQL 后端。这是一个**正面事实**,不是缺席;
|
|
31
|
+
* 把它渲成「未观测」等于把引擎真给的答案丢掉(壳自 spawn 的单机形恒落这一档,是常态读数)。
|
|
32
|
+
* ④ `present` —— 三座俱全,原样渲。
|
|
33
|
+
* 🔴 **绝不渲 0 / 绝不编一个姿态**:读不出来就说读不出来,哪一种读不出来也要分清楚。
|
|
34
|
+
* ⇄ 这四个词与 {@link import("./engineCapsCache.js").EngineCapValueState} 的四态**同一套词汇**
|
|
35
|
+
* (`unobserved` / `not_reported` / `null`→`none` / `value`→`present`):通用读口答「这一格上有
|
|
36
|
+
* 没有值」,本模块答「这一格的值成不成形」,刻意分层 —— 通用口不认识 `sql` 的三座,窄读器不该
|
|
37
|
+
* 重新实现一遍缓存与代际。
|
|
38
|
+
*
|
|
39
|
+
* ── 🔴 UNTRUSTED-for-display ──────────────────────────────────────────────────────────────
|
|
40
|
+
* `isolation` 是从**数据库服务器**变量里回读的串(`@@transaction_isolation` 一族),不是引擎铸的
|
|
41
|
+
* 闭词;三座一律只渲染、绝不参与任何判定,呈现前过本包的单行消毒单源
|
|
42
|
+
* ({@link escapeDisplayControlChars})。
|
|
43
|
+
*/
|
|
44
|
+
import { escapeDisplayControlChars } from './fleetTaskDesc.js';
|
|
45
|
+
import { engineWireTarget } from './engineWireTarget.js';
|
|
46
|
+
/**
|
|
47
|
+
* caps 回体 → 本格读数;**畸形一律 `undefined`**(= 这一格不写 ⇒ 读口答 `unobserved`)。
|
|
48
|
+
*
|
|
49
|
+
* 🔴 `undefined`(键不在)与 `null`(引擎说没有)**是两件事**,本函数是本包唯一区分它们的地方:
|
|
50
|
+
* 前者 ⇒ `not_reported`(老引擎),后者 ⇒ `none`(这台部署没库)。用 `??` / falsy 判会把
|
|
51
|
+
* 两者压成一件,而它们对运维的意思完全不同。
|
|
52
|
+
* 🔴 三座**任一**不是非空串 ⇒ 整条判畸形(`undefined`)。绝不留一个缺座的对象:一行写着
|
|
53
|
+
* 「isolation: 」的诊断比不渲这一行更坏(它看起来像一个答案)。
|
|
54
|
+
*/
|
|
55
|
+
export function projectSqlEngineCapability(caps) {
|
|
56
|
+
if (caps === null || typeof caps !== 'object')
|
|
57
|
+
return undefined;
|
|
58
|
+
if (!('sql' in caps))
|
|
59
|
+
return { kind: 'not_reported' };
|
|
60
|
+
const sql = caps.sql;
|
|
61
|
+
if (sql === null)
|
|
62
|
+
return { kind: 'none' };
|
|
63
|
+
if (sql === undefined)
|
|
64
|
+
return { kind: 'not_reported' };
|
|
65
|
+
if (typeof sql !== 'object')
|
|
66
|
+
return undefined;
|
|
67
|
+
const s = sql;
|
|
68
|
+
if (typeof s.engine !== 'string' || s.engine === '')
|
|
69
|
+
return undefined;
|
|
70
|
+
if (typeof s.isolation !== 'string' || s.isolation === '')
|
|
71
|
+
return undefined;
|
|
72
|
+
// 🔴 `txnMode`:`null` 是**铸点域内的合法值**(见 SqlEngineCapabilityView 顶注),不是畸形。
|
|
73
|
+
// 但这一位在铸点上**恒在场** ⇒ 键缺席(`undefined`)与非串非 null 仍判载体坏了。
|
|
74
|
+
if (s.txnMode !== null && (typeof s.txnMode !== 'string' || s.txnMode === ''))
|
|
75
|
+
return undefined;
|
|
76
|
+
return { kind: 'present', view: { engine: s.engine, isolation: s.isolation, txnMode: s.txnMode } };
|
|
77
|
+
}
|
|
78
|
+
const readingByBase = new Map();
|
|
79
|
+
/**
|
|
80
|
+
* 宿主 caps probe 的**读面 tee** 落点(壳侧与 mcpGate / sessionBackground / projectContext /
|
|
81
|
+
* crashConverged / workflowsGate 五条并列)。绝不 throw —— 读面腿不许反噬 caps 探测链。
|
|
82
|
+
*
|
|
83
|
+
* 🔴 投影 `undefined`(caps 畸形)⇒ 这一格**被删**而不是留着上一台引擎的旧值 —— 留旧值 = 拿一台
|
|
84
|
+
* 已经不在的引擎的读数去回答「这台现在是什么姿态」。
|
|
85
|
+
* ⚠️ **这一条只覆盖「收到了一份读不懂的回体」**。「同端口 respawn / 版本回滚之后新探测**失败或
|
|
86
|
+
* 还在飞**」是**另一条路** —— 那时本 tee 一次都不触发,挡不住旧读数继续被当成当代事实。
|
|
87
|
+
* 那一形由 {@link forgetSqlEngineReading} 在装配口(宿主的引擎温切臂)上收。
|
|
88
|
+
* 🔴 **不按 principal 分域**:SQL 姿态是**这台 worker 的连接事实**,与调用者是谁无关
|
|
89
|
+
* (与 `workflows` 那种 per-caller 的位刻意不同)—— 加一个用不上的去重域只会造出一条
|
|
90
|
+
* 「换主体就读不到」的假缺席。
|
|
91
|
+
*/
|
|
92
|
+
export function noteEngineCapsForSqlEngine(baseUrl, caps) {
|
|
93
|
+
try {
|
|
94
|
+
if (typeof baseUrl !== 'string' || baseUrl === '')
|
|
95
|
+
return;
|
|
96
|
+
const reading = projectSqlEngineCapability(caps);
|
|
97
|
+
if (reading === undefined) {
|
|
98
|
+
readingByBase.delete(baseUrl);
|
|
99
|
+
return;
|
|
100
|
+
}
|
|
101
|
+
readingByBase.set(baseUrl, reading);
|
|
102
|
+
}
|
|
103
|
+
catch {
|
|
104
|
+
/* fail-soft:本 tee 任何分支都不许打断 caps 探测链 */
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
/**
|
|
108
|
+
* 本进程观测到的 SQL 姿态;这一格空缺 ⇒ `{kind:'unobserved'}`(**绝不**折成 `none`)。
|
|
109
|
+
*
|
|
110
|
+
* 缺省读锚 = {@link engineWireTarget} 的 `baseUrl`(本包零 `process` —— portability 门盯着;
|
|
111
|
+
* 壳那份直读 `process.env.SEMA_LIVE_BASEURL`,而 `engineWireTarget()` 的 env 臂读的就是同一个变量
|
|
112
|
+
* ⇒ Node 壳上行为一字节不变,浏览器/桌面宿主则由 `installEngineWireTarget()` 显式装)。
|
|
113
|
+
*/
|
|
114
|
+
export function observedSqlEngine(baseUrl = engineWireTarget()?.baseUrl) {
|
|
115
|
+
if (typeof baseUrl !== 'string' || baseUrl === '')
|
|
116
|
+
return { kind: 'unobserved' };
|
|
117
|
+
return readingByBase.get(baseUrl) ?? { kind: 'unobserved' };
|
|
118
|
+
}
|
|
119
|
+
/** 单行 UNTRUSTED 座的展示上限(UTF-16 单元;与壳侧那份同值)。 */
|
|
120
|
+
const SQL_DETAIL_MAX = 40;
|
|
121
|
+
/**
|
|
122
|
+
* 单行 UNTRUSTED 座的呈前规整:**先按 {@link SQL_DETAIL_MAX} 截原字节,再过单行消毒**。
|
|
123
|
+
*
|
|
124
|
+
* 🔴 顺序刻意与壳那份相反,理由是两侧消毒器的**输出形不同**:壳那份用 `.` 占位(1:1,先截后清
|
|
125
|
+
* 与先清后截等价),本包的单源 {@link escapeDisplayControlChars} 用**可见转义** `\uXXXX`
|
|
126
|
+
* (1:6)—— 先清后截会把一个转义序列拦腰截断,屏上留下 `\u20` 这种既不是字符也不是转义的残片。
|
|
127
|
+
* 先截后清则至多让**渲染宽度**超出上限,而语义完整;截点劈开的代理对由消毒器自己收
|
|
128
|
+
* (孤代理项在它的字符集里)。
|
|
129
|
+
* ⚠️ **与壳那份的已知呈现差分**(换装时随批裁一次):壳 `sqlEngineDoctorDetail` 走的是
|
|
130
|
+
* `cleanUntrustedForDisplay`(`.` 占位形),而壳自己的 `untrustedDisplayText` 头注写着
|
|
131
|
+
* 「单行字段(路径/URL/参数键值)**必须**用 cleanUntrustedScalar」(可见转义形)——
|
|
132
|
+
* 三座正是单行标量。本包按那条规则实现,所以对**正常读数**(`tidb` / `REPEATABLE-READ` /
|
|
133
|
+
* `pessimistic`)两侧逐字节相同,只有含控制符的病态值渲染形不同。
|
|
134
|
+
*/
|
|
135
|
+
function cleanSqlDetailScalar(v) {
|
|
136
|
+
return escapeDisplayControlChars(v.slice(0, SQL_DETAIL_MAX));
|
|
137
|
+
}
|
|
138
|
+
/**
|
|
139
|
+
* 四态 → doctor 那一行的 detail 串。**唯一措辞真源**(三端共用一句话;别在各端的行装配里另写
|
|
140
|
+
* 一遍 —— 那正是这次上收要消灭的东西)。
|
|
141
|
+
*
|
|
142
|
+
* 🔴 三座是 UNTRUSTED-for-display(`isolation` 是从**数据库服务器**变量里回读的串):呈前消毒 +
|
|
143
|
+
* 封长。消毒放在这一处而不是各呈现点,是因为本函数的产物就是屏上那一串(单一出口)。
|
|
144
|
+
* 🔴 四句话刻意**互不相同、也互不蕴含**:「未观测」不许说成「没有库」,「老引擎不报」也不许
|
|
145
|
+
* 说成「没有库」—— 三种「读不出」对运维是三条不同的下一步。
|
|
146
|
+
*/
|
|
147
|
+
export function sqlEngineDoctorDetail(reading) {
|
|
148
|
+
switch (reading.kind) {
|
|
149
|
+
case 'unobserved':
|
|
150
|
+
return ('not observed — the engine reports it on /v1/capabilities; this one-shot process ' +
|
|
151
|
+
'has no engine response (run /doctor inside the REPL after a turn)');
|
|
152
|
+
case 'not_reported':
|
|
153
|
+
return 'not reported by this engine — the capability position needs a newer engine';
|
|
154
|
+
case 'none':
|
|
155
|
+
return 'none — the engine reports no SQL backend on this deployment (local file stores)';
|
|
156
|
+
case 'present': {
|
|
157
|
+
const e = cleanSqlDetailScalar(reading.view.engine);
|
|
158
|
+
const i = cleanSqlDetailScalar(reading.view.isolation);
|
|
159
|
+
// 🔴 `null` 是上游明说的「这个引擎没有这个指示位」,**不是** `optimistic`(d.ts 逐字点名的
|
|
160
|
+
// 误读),也不是「读不出来」⇒ 渲一句显式的话,既不留白也不编一个档位。
|
|
161
|
+
// 消毒器对 `null` 无意义(它只吃串),所以这一格必须在消毒**之前**分臂。
|
|
162
|
+
const t = reading.view.txnMode === null
|
|
163
|
+
? 'no txn-mode indicator on this engine'
|
|
164
|
+
: cleanSqlDetailScalar(reading.view.txnMode);
|
|
165
|
+
return `${e} · isolation ${i} · txn ${t}`;
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
/**
|
|
170
|
+
* **换代失效口**(与 {@link import("./engineCapsCache.js").invalidateEngineCaps} 并列,由宿主在
|
|
171
|
+
* 引擎温切成功后调)。
|
|
172
|
+
*
|
|
173
|
+
* 🔴 **代际闸挡不住这一形**:那道闸挡的是「旧响应**写脏**新一代」(tee 只在确认代际仍当代之后
|
|
174
|
+
* 才落地),而本格的病是反过来的 —— 新一代的探测**根本没成功**(超时 / reject / 还在飞),
|
|
175
|
+
* tee 于是一次都没触发,上一台引擎留在同一端口上的读数就继续被 {@link observedSqlEngine}
|
|
176
|
+
* 当成**当代事实**答出去。同端口 respawn / 版本回滚 / 引擎起不来三条路都走这里,而它在
|
|
177
|
+
* doctor 上的形态最坏:一行**肯定句**(「这台部署是 tidb 悲观事务」/「这台引擎不报这一位,
|
|
178
|
+
* 要升级」),说的却是一台已经不在的引擎。
|
|
179
|
+
* 🔴 处置是**清成未观测**,不是留旧值也不是铸一个 `none` —— 「我这一代还没听到答案」是这一格
|
|
180
|
+
* 唯一诚实的话(与 {@link noteEngineCapsForSqlEngine} 对畸形回体的处置同向)。
|
|
181
|
+
* 空串 ⇒ no-op;绝不 throw(它跑在引擎温切路径上)。
|
|
182
|
+
*/
|
|
183
|
+
export function forgetSqlEngineReading(baseUrl) {
|
|
184
|
+
if (typeof baseUrl !== 'string' || baseUrl === '')
|
|
185
|
+
return;
|
|
186
|
+
readingByBase.delete(baseUrl);
|
|
187
|
+
}
|
|
188
|
+
/** 测试钩子。 */
|
|
189
|
+
export function __resetSqlEngineReadingsForTests() {
|
|
190
|
+
readingByBase.clear();
|
|
191
|
+
}
|