@sema-agent/client-core 0.59.2 → 0.61.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 +245 -0
- package/README.md +4 -2
- package/dist/adapt/toolCards.d.ts +2 -1
- package/dist/adapter/downstream/eventToSdkMessage.d.ts +24 -4
- package/dist/adapter/downstream/eventToSdkMessage.js +42 -70
- package/dist/adapter/downstream/terminalToSdkResult.d.ts +27 -22
- package/dist/adapter/downstream/terminalToSdkResult.js +47 -30
- package/dist/adapter/runStream.js +27 -11
- package/dist/engineCapsCache.d.ts +27 -0
- package/dist/engineCapsCache.js +29 -0
- package/dist/engineErrorCodes.d.ts +12 -0
- package/dist/engineErrorCodes.js +13 -0
- package/dist/gateOutcome.d.ts +157 -0
- package/dist/gateOutcome.js +181 -0
- package/dist/headlessReconnectWire.js +9 -2
- package/dist/hitl/askParkRowRouting.d.ts +2 -0
- package/dist/hitl/askParkRowRouting.js +17 -2
- package/dist/hitl/frameRouter.d.ts +2 -2
- package/dist/hitl/frameRouter.js +49 -35
- package/dist/hitl/hitlBridge.d.ts +49 -2
- package/dist/hitl/hitlBridge.js +22 -1
- package/dist/hitl/parkResolver.d.ts +3 -1
- package/dist/hitl/parkResolver.js +15 -5
- package/dist/hitl/planReviewWire.d.ts +8 -1
- package/dist/hitl/planReviewWire.js +18 -7
- package/dist/hitl/toolApprovalWire.d.ts +37 -3
- package/dist/hitl/toolApprovalWire.js +36 -9
- package/dist/index.d.ts +4 -0
- package/dist/index.js +12 -0
- package/dist/printToolResultFrame.d.ts +34 -49
- package/dist/readFacePosture.d.ts +53 -0
- package/dist/readFacePosture.js +120 -0
- package/dist/resumeRefusalCopy.d.ts +12 -4
- package/dist/resumeRefusalCopy.js +15 -5
- package/dist/runTerminal.d.ts +194 -0
- package/dist/runTerminal.js +189 -0
- package/dist/sqlEngineCapability.d.ts +11 -1
- package/dist/sqlEngineCapability.js +13 -1
- package/dist/writeProtectionCapability.d.ts +142 -0
- package/dist/writeProtectionCapability.js +224 -0
- package/docs/INTEGRATION-CLIENTS.md +433 -11
- package/package.json +3 -3
|
@@ -0,0 +1,224 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* writeProtectionCapability — **写保护名表**姿态的三端共用读面(0.60.0)。
|
|
3
|
+
*
|
|
4
|
+
* 引擎带一张**字面名表**,落在表行上的可定路径写(Write / Edit / NotebookEdit)从 `allow` 降级成
|
|
5
|
+
* `ask` —— 所以端可以**事先**告诉人「这些路径会弹卡」。两个读面同源同一份 boot 产物:
|
|
6
|
+
* · **租户面** `GET /v1/capabilities` 的 `writeProtection`(engine ≥7.63.0 / S-138;sdk 8.4.0
|
|
7
|
+
* `Capabilities.writeProtection`)—— 只有三位计数/开关,**不含行内容**;
|
|
8
|
+
* · **operator 面** `GET /v1/diagnostics/wiring` 的 `writeProtection` —— 多出**行内容**与来源词。
|
|
9
|
+
*
|
|
10
|
+
* ── 🔴 四态,不是两态([honest-absence-not-fabricated-zero])──────────────────────────────────
|
|
11
|
+
* 与 `sqlEngineCapability` 逐字同一套词汇(通用读口答「这一格上有没有值」,窄读器答「值成不成形」):
|
|
12
|
+
* ① `unobserved` —— **本进程一次 caps 响应都没观测到**(一次性 doctor 就是这一档)。
|
|
13
|
+
* ② `not_reported` —— caps 观测到了,而回体上**没有**这个键 ⇒ 老引擎(<7.63.0)。
|
|
14
|
+
* 🔴 它**不是**「这台部署没有写保护表」—— 恰恰相反:引擎侧**座位缺席时缺省表在岗**
|
|
15
|
+
* (按键名 grep 推断「没装」会推错,上游把这一条写进了本位的顶注)。
|
|
16
|
+
* ③ `none` —— 引擎**明确说** `null`:这个进程说不出来(非 composition root 装配的夹具形)。
|
|
17
|
+
* 同样**不是**「没有表」。
|
|
18
|
+
* ④ `present` —— 三位俱全,原样渲。
|
|
19
|
+
*
|
|
20
|
+
* ── 🔴 三位刻意不合成一个布尔(上游顶注逐字)────────────────────────────────────────────────
|
|
21
|
+
* `armed`(生效表非空 ⇒ 真会弹卡)/ `rows`(生效表行数)/ `replaced`(整表替换旋钮被写过)——
|
|
22
|
+
* 合成之后「引擎缺省表在岗」与「运维换了一张自己的表」同为 true,而两者对运维说的是两件事。
|
|
23
|
+
* ⚠️ `replaced` 把「整表替换成**空**表」也算 true:报 false 会把「运维亲手关掉了表」说成
|
|
24
|
+
* 「缺省表没被动过」。
|
|
25
|
+
*
|
|
26
|
+
* ── 🔴 最小披露:逐行 name/kind 不上租户面 ──────────────────────────────────────────────────
|
|
27
|
+
* 部署自定义行可能含内部路径名,列出来等于告诉想绕过的人「哪些名字不受保护」。行内容只在
|
|
28
|
+
* operator 面({@link projectWriteProtectionPosture}),而那个端点本身是 operator-gated。
|
|
29
|
+
* 🔴 与**模式型** write deny(`SENSITIVE_WRITE_PATTERNS`)是**并列机制**,两个读面分开报、
|
|
30
|
+
* 绝不合成一位:两者的解法不同(一套改模式,一套改名表)。
|
|
31
|
+
*/
|
|
32
|
+
import { escapeDisplayControlChars } from './fleetTaskDesc.js';
|
|
33
|
+
import { engineWireTarget } from './engineWireTarget.js';
|
|
34
|
+
import { engineCapsGeneration } from './engineCapsCache.js';
|
|
35
|
+
/**
|
|
36
|
+
* caps 回体 → 本格读数;**畸形一律 `undefined`**(= 这一格不写 ⇒ 读口答 `unobserved`)。
|
|
37
|
+
*
|
|
38
|
+
* 🔴 `undefined`(键不在)与 `null`(引擎说不出来)**是两件事**:前者 ⇒ `not_reported`(老引擎),
|
|
39
|
+
* 后者 ⇒ `none`(夹具形进程)。用 `??` / falsy 判会把两者压成一件。
|
|
40
|
+
* 🔴 三位**任一**形不合 ⇒ 整条判畸形。绝不留一个缺位的对象:一行写着「rows: 」的诊断比不渲这一行
|
|
41
|
+
* 更坏(它看起来像一个答案)。
|
|
42
|
+
* ⚠️ `rows` 按**非负有限整数**判,不按真值判:`0` 是合法读数(表在、但生效行为零),折成缺席会让
|
|
43
|
+
* 「表空了」与「没报」在消费端同形。
|
|
44
|
+
*/
|
|
45
|
+
export function projectWriteProtectionCapability(caps) {
|
|
46
|
+
if (caps === null || typeof caps !== 'object')
|
|
47
|
+
return undefined;
|
|
48
|
+
if (!('writeProtection' in caps))
|
|
49
|
+
return { kind: 'not_reported' };
|
|
50
|
+
const wp = caps.writeProtection;
|
|
51
|
+
if (wp === null)
|
|
52
|
+
return { kind: 'none' };
|
|
53
|
+
if (wp === undefined)
|
|
54
|
+
return { kind: 'not_reported' };
|
|
55
|
+
if (typeof wp !== 'object' || Array.isArray(wp))
|
|
56
|
+
return undefined;
|
|
57
|
+
const w = wp;
|
|
58
|
+
if (typeof w.armed !== 'boolean')
|
|
59
|
+
return undefined;
|
|
60
|
+
if (typeof w.replaced !== 'boolean')
|
|
61
|
+
return undefined;
|
|
62
|
+
if (typeof w.rows !== 'number' || !Number.isInteger(w.rows) || w.rows < 0)
|
|
63
|
+
return undefined;
|
|
64
|
+
return { kind: 'present', view: { armed: w.armed, rows: w.rows, replaced: w.replaced } };
|
|
65
|
+
}
|
|
66
|
+
/**
|
|
67
|
+
* operator 面回体的 `writeProtection` 段 → 读数;不成形 ⇒ `undefined`。
|
|
68
|
+
*
|
|
69
|
+
* 🔴 **空 `rows` 数组是正面事实**(「显式无表」),照铸 `[]` —— 与 `wiring_manifest.mcp[]` 同一条
|
|
70
|
+
* 已定谳的纪律;把它折成缺席等于把引擎明说的一句话读成「不知道」。
|
|
71
|
+
* 🔴 **坏行只丢那一行**(与 mcp 行同判据),但 `source` 读不出 ⇒ **整段缺席**:没有来源词的行表
|
|
72
|
+
* 答不了运维真正要问的那一问(「这是缺省表还是你换过的表」),而半段会被当成完整答案。
|
|
73
|
+
* 🔴 **不校「`droppedDefaultRows` 只在替换族在场」**:那是引擎的段内规矩,server 已按它铸;
|
|
74
|
+
* 包再校一遍只会在上游合法演进的当天把一个真读数判没(与开集词表同规)。
|
|
75
|
+
*/
|
|
76
|
+
export function projectWriteProtectionPosture(wiring) {
|
|
77
|
+
if (wiring === null || typeof wiring !== 'object')
|
|
78
|
+
return undefined;
|
|
79
|
+
const wp = wiring.writeProtection;
|
|
80
|
+
if (wp === null || wp === undefined || typeof wp !== 'object' || Array.isArray(wp))
|
|
81
|
+
return undefined;
|
|
82
|
+
const w = wp;
|
|
83
|
+
const source = typeof w.source === 'string' && w.source.length > 0 ? w.source : undefined;
|
|
84
|
+
if (source === undefined)
|
|
85
|
+
return undefined;
|
|
86
|
+
if (!Array.isArray(w.rows))
|
|
87
|
+
return undefined;
|
|
88
|
+
const rows = [];
|
|
89
|
+
for (const e of w.rows) {
|
|
90
|
+
if (typeof e !== 'object' || e === null || Array.isArray(e))
|
|
91
|
+
continue;
|
|
92
|
+
const r = e;
|
|
93
|
+
if (typeof r.name !== 'string' || r.name.length === 0)
|
|
94
|
+
continue;
|
|
95
|
+
if (typeof r.kind !== 'string' || r.kind.length === 0)
|
|
96
|
+
continue;
|
|
97
|
+
rows.push({ name: r.name, kind: r.kind });
|
|
98
|
+
}
|
|
99
|
+
const dropped = Array.isArray(w.droppedDefaultRows)
|
|
100
|
+
? w.droppedDefaultRows.filter((v) => typeof v === 'string' && v.length > 0)
|
|
101
|
+
: undefined;
|
|
102
|
+
return {
|
|
103
|
+
rows,
|
|
104
|
+
source,
|
|
105
|
+
...(dropped !== undefined ? { droppedDefaultRows: dropped } : {}),
|
|
106
|
+
};
|
|
107
|
+
}
|
|
108
|
+
const readingByBase = new Map();
|
|
109
|
+
/**
|
|
110
|
+
* 宿主 caps probe 的**读面 tee** 落点(与 `noteEngineCapsForSqlEngine` 并列)。绝不 throw ——
|
|
111
|
+
* 读面腿不许反噬 caps 探测链。
|
|
112
|
+
*
|
|
113
|
+
* 🔴 投影 `undefined`(caps 畸形)⇒ 这一格**被删**而不是留着上一台引擎的旧值。
|
|
114
|
+
* ⚠️ 「同端口 respawn / 版本回滚之后新探测失败或还在飞」是**另一条路**(本 tee 一次都不触发),
|
|
115
|
+
* 由 {@link forgetWriteProtectionReading} 在装配口上收 —— 与 SQL 那一格逐字同形。
|
|
116
|
+
* 🔴 **不按 principal 分域**:写保护表是**这台 worker 的 boot 产物**,与调用者是谁无关。
|
|
117
|
+
*
|
|
118
|
+
* 🔴 **`opts.generation` = 关掉「旧探测覆盖新读数」那条竞态的唯一正确供给**(0.60.0;异源对抗复审
|
|
119
|
+
* [medium] 采纳,真病):`kickEngineCapsProbe` 的代际闸在 **probe 返回之后**才跑,而本 tee 是在
|
|
120
|
+
* probe **内部**落地的 ⇒ 一次慢的旧探测能在 `invalidateEngineCaps` + 新探测都走完之后才返回,
|
|
121
|
+
* 把**上一台引擎**的读数重新写回来。宿主在**起探测那一刻**调 `engineCapsGeneration(baseUrl)` 抓号、
|
|
122
|
+
* 在这里原样交回,陈旧那一发就会被整只丢掉。
|
|
123
|
+
* ⚠️ **不传 = 与本位到货之前逐字节同行为**(照写)—— additive 供给,不是强制门;端没跟上之前不会
|
|
124
|
+
* 有任何东西变坏。🔴 **tee 自己抓号是假判据**(陈旧那一发在这里读到的是新代号,永远像当代的)。
|
|
125
|
+
*/
|
|
126
|
+
export function noteEngineCapsForWriteProtection(baseUrl, caps, opts) {
|
|
127
|
+
try {
|
|
128
|
+
if (typeof baseUrl !== 'string' || baseUrl === '')
|
|
129
|
+
return;
|
|
130
|
+
// 陈旧代际的那一发整只丢掉(见本函数顶注的 `opts.generation` 段);不传 ⇒ 照写,零行为变化。
|
|
131
|
+
if (opts?.generation !== undefined && opts.generation !== engineCapsGeneration(baseUrl))
|
|
132
|
+
return;
|
|
133
|
+
const reading = projectWriteProtectionCapability(caps);
|
|
134
|
+
if (reading === undefined) {
|
|
135
|
+
readingByBase.delete(baseUrl);
|
|
136
|
+
return;
|
|
137
|
+
}
|
|
138
|
+
readingByBase.set(baseUrl, reading);
|
|
139
|
+
}
|
|
140
|
+
catch {
|
|
141
|
+
/* fail-soft:本 tee 任何分支都不许打断 caps 探测链 */
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
/** 本进程观测到的写保护姿态;这一格空缺 ⇒ `{kind:'unobserved'}`(**绝不**折成 `none`)。 */
|
|
145
|
+
export function observedWriteProtection(baseUrl = engineWireTarget()?.baseUrl) {
|
|
146
|
+
if (typeof baseUrl !== 'string' || baseUrl === '')
|
|
147
|
+
return { kind: 'unobserved' };
|
|
148
|
+
return readingByBase.get(baseUrl) ?? { kind: 'unobserved' };
|
|
149
|
+
}
|
|
150
|
+
/** 单行 UNTRUSTED 座的展示上限(UTF-16 单元;与 SQL 那一格同值同理由)。 */
|
|
151
|
+
const WP_DETAIL_MAX = 40;
|
|
152
|
+
/**
|
|
153
|
+
* 四态 → doctor 那一行的 detail 串。**唯一措辞真源**(三端共用一句话;别在各端的行装配里另写一遍
|
|
154
|
+
* —— 与 `sqlEngineDoctorDetail` / `autoModeDoctorDetail` 同一条纪律)。
|
|
155
|
+
*
|
|
156
|
+
* 🔴 四句话刻意**互不相同、也互不蕴含**:「未观测」「老引擎不报」「进程说不出来」三种「读不出」
|
|
157
|
+
* 对运维是三条不同的下一步,而**没有一条**等于「这台部署没有写保护」。
|
|
158
|
+
* 🔴 `present` 那一句把三位**分开说**(见文件头「刻意不合成」):`armed` 说会不会弹卡,`rows` 说
|
|
159
|
+
* 几行,`replaced` 说缺省表还在不在岗。
|
|
160
|
+
* 🔴 行内容一个字都不渲(最小披露);`source` 是 operator 面的位,不上这一行。
|
|
161
|
+
*/
|
|
162
|
+
export function writeProtectionDoctorDetail(reading) {
|
|
163
|
+
switch (reading.kind) {
|
|
164
|
+
case 'unobserved':
|
|
165
|
+
return ('not observed — the engine reports it on /v1/capabilities; this process has not received ' +
|
|
166
|
+
'a capabilities response from the engine yet');
|
|
167
|
+
case 'not_reported':
|
|
168
|
+
return ('not reported by this engine — the capability position needs a newer engine; this says ' +
|
|
169
|
+
'nothing about whether a protected-name table is in effect (an absent seat is the engine ' +
|
|
170
|
+
"shipping its own default table, not the absence of one)");
|
|
171
|
+
case 'none':
|
|
172
|
+
return ('the engine cannot state it for this process — the position is reported as unknown, which ' +
|
|
173
|
+
'is not the same as "no protected-name table"');
|
|
174
|
+
case 'present': {
|
|
175
|
+
const v = reading.view;
|
|
176
|
+
const armed = v.armed
|
|
177
|
+
? 'armed (writes onto listed names are downgraded to ask)'
|
|
178
|
+
: 'not armed (the effective table is empty, so no write is downgraded)';
|
|
179
|
+
const table = v.replaced
|
|
180
|
+
? "the default table was replaced by this deployment's own"
|
|
181
|
+
: 'the default table is in effect as shipped';
|
|
182
|
+
return `${armed} · ${String(v.rows)} protected ${v.rows === 1 ? 'name' : 'names'} · ${table}`;
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
/**
|
|
187
|
+
* operator 面读数 → 一行 detail(行名是 UNTRUSTED 自由文本 ⇒ 呈前**逐条**消毒 + 封长)。
|
|
188
|
+
*
|
|
189
|
+
* 🔴 只给**行数与来源**加上「头几行的名字」,不整表倾泻:一行诊断不是一个表格,而 operator 面的
|
|
190
|
+
* 真表读法是那个端点本身。
|
|
191
|
+
* 🔴 `rows: []` 渲的是**显式无表**这句正面事实,不是「读不出」。
|
|
192
|
+
*/
|
|
193
|
+
export function writeProtectionPostureDetail(posture) {
|
|
194
|
+
const source = escapeDisplayControlChars(posture.source.slice(0, WP_DETAIL_MAX));
|
|
195
|
+
if (posture.rows.length === 0)
|
|
196
|
+
return `source ${source} · no protected names in effect`;
|
|
197
|
+
const shown = posture.rows
|
|
198
|
+
.slice(0, 3)
|
|
199
|
+
.map((r) => `${escapeDisplayControlChars(r.name.slice(0, WP_DETAIL_MAX))}(${escapeDisplayControlChars(r.kind.slice(0, WP_DETAIL_MAX))})`)
|
|
200
|
+
.join(', ');
|
|
201
|
+
const more = posture.rows.length > 3 ? `, +${String(posture.rows.length - 3)} more` : '';
|
|
202
|
+
const dropped = posture.droppedDefaultRows !== undefined && posture.droppedDefaultRows.length > 0
|
|
203
|
+
? ` · ${String(posture.droppedDefaultRows.length)} default ${posture.droppedDefaultRows.length === 1 ? 'name' : 'names'} dropped by the replacement`
|
|
204
|
+
: '';
|
|
205
|
+
return `source ${source} · ${String(posture.rows.length)} ${posture.rows.length === 1 ? 'name' : 'names'}: ${shown}${more}${dropped}`;
|
|
206
|
+
}
|
|
207
|
+
/**
|
|
208
|
+
* **换代失效口**(与 `forgetSqlEngineReading` 并列,由宿主在引擎温切成功后调)。
|
|
209
|
+
*
|
|
210
|
+
* 🔴 代际闸挡不住这一形:那道闸挡的是「旧响应写脏新一代」,而本格的病是反过来的 —— 新一代的探测
|
|
211
|
+
* **根本没成功**,tee 一次都没触发,上一台引擎留在同一端口上的读数继续被当成当代事实答出去。
|
|
212
|
+
* 在 doctor 上它的形态最坏:一句**肯定句**(「这台部署有 12 行写保护」),说的却是一台已经不在
|
|
213
|
+
* 的引擎。处置是**清成未观测**,不是留旧值也不是铸一个 `none`。
|
|
214
|
+
* 空串 ⇒ no-op;绝不 throw(它跑在引擎温切路径上)。
|
|
215
|
+
*/
|
|
216
|
+
export function forgetWriteProtectionReading(baseUrl) {
|
|
217
|
+
if (typeof baseUrl !== 'string' || baseUrl === '')
|
|
218
|
+
return;
|
|
219
|
+
readingByBase.delete(baseUrl);
|
|
220
|
+
}
|
|
221
|
+
/** 测试钩子。 */
|
|
222
|
+
export function __resetWriteProtectionReadingsForTests() {
|
|
223
|
+
readingByBase.clear();
|
|
224
|
+
}
|