@sema-agent/client-core 0.47.0 → 0.49.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 +155 -0
- package/README.md +4 -2
- package/dist/adapt/arms.js +10 -0
- package/dist/adapter/downstream/eventToSdkMessage.js +62 -28
- package/dist/hitl/crashConverged.d.ts +176 -0
- package/dist/hitl/crashConverged.js +323 -0
- package/dist/hitl/hitlBridge.d.ts +38 -24
- package/dist/hitl/hitlBridge.js +16 -2
- package/dist/hitl/toolApprovalWire.d.ts +20 -0
- package/dist/hitl/toolApprovalWire.js +7 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +14 -0
- package/dist/retryStatus.d.ts +51 -3
- package/dist/retryStatus.js +31 -7
- package/dist/sessionMemoryStatus.d.ts +138 -0
- package/dist/sessionMemoryStatus.js +181 -0
- package/dist/typePins.d.ts +40 -0
- package/docs/INTEGRATION-CLIENTS.md +412 -22
- package/package.json +4 -4
|
@@ -0,0 +1,323 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* crashConverged.ts — `GET /v1/approvals` 的 additive 键 `crashConverged` 的**三端公共读面**
|
|
3
|
+
* (L-38;server 7.55.0 起在场,fixture 直证坐标 `dist/boot/coordinators.js` 的 `GET /v1/approvals`
|
|
4
|
+
* 分支 + `dist/approval-ask-audit-store.d.ts` 的 `CrashConvergedRow`)。
|
|
5
|
+
*
|
|
6
|
+
* ## 这一格是什么(先把语义说准,文案才有得写)
|
|
7
|
+
* local 引擎在**人还挂在审批门上**的时候崩了:那一条 ask 既没被批也没被拒,进程一死就成了孤儿。
|
|
8
|
+
* server 重启后把这些孤儿**收敛成 DENIED 同码**(`decision:'denied'` + `cause:'crashed_before_park'`),
|
|
9
|
+
* 并把收敛结果作为一条 `crashConverged` 行挂在同一个 `/v1/approvals` 信封上。
|
|
10
|
+
* 对端(TUI / web / desktop)而言这是**上一条命的残留**:重连 / `--resume` 之后,人有权知道
|
|
11
|
+
* 「上次那次崩溃,把哪些审批替我拒掉了、其中哪几件可能已经落了一半副作用」。
|
|
12
|
+
*
|
|
13
|
+
* ## 两个桶的语义(端**唯一**需要分的那一刀)
|
|
14
|
+
* · `orphanState === 'pending'` = 崩的时候**工具一步都没执行**(还停在门上)。这一档配
|
|
15
|
+
* `resumeSafe === true` 时,重跑同一件事是安全的 ⇒ **resumeSafe 桶**。
|
|
16
|
+
* · `orphanState === 'decided'` = 人**当时已经批了 approve**,收敛把它翻成了 denied ——
|
|
17
|
+
* 可能有半截副作用落地了(文件写了一半 / 命令跑了一半)。⇒ **needsHuman 桶**,人工确认。
|
|
18
|
+
* · 其余一切(`resumeSafe === false`、字段自相矛盾、说不清的组合)一律进 **needsHuman**:
|
|
19
|
+
* 判**不**安全的代价是多问人一句(可恢复);判**错**安全的代价是让人闭眼重跑一件已经落过
|
|
20
|
+
* 副作用的事(不可恢复)。凡证不出来一律落保守侧。
|
|
21
|
+
*
|
|
22
|
+
* ## 🔴 caveat(DEBTS L-38 收执逐字,写文案前必读)
|
|
23
|
+
* `resumeSafe` 是**以账本完整为前提**算出来的**缺省值**,不是铁证 —— 崩溃现场本来就是账本最可能
|
|
24
|
+
* 缺页的时刻。⇒ 端的文案只许写「按记录看可以重跑」,**绝不**写「已确认没有副作用」。
|
|
25
|
+
* 同理:**键在场 ≠ 流内协议上场**。`crashConverged` 只是这一次 `list()` 回体上的一个 additive 键,
|
|
26
|
+
* 它既不宣示引擎具备什么能力,也不代表有一条推送通道会再告诉端第二次。别拿它当能力位读。
|
|
27
|
+
*
|
|
28
|
+
* ## 🔴 非目标与已知边界(对手模型成文,详见 `docs/INTEGRATION-CLIENTS.md` §12e)
|
|
29
|
+
* 真供给 = server JSON → SDK `JSON.parse` → 端:每一位都是**自有数据属性**,无代理、无 accessor、
|
|
30
|
+
* 原型恒 `Object.prototype`。**被中间层合成的非 JSON 载荷 / 敌意 `Proxy` / 原型注射**这一族**不在
|
|
31
|
+
* 射程内**:本件对它们只承诺三件 —— **不抛**、**不同步阻塞**、**绝不产出「可安全重跑」这个判决**
|
|
32
|
+
* (一律落 needsHuman 或 dropped);**不承诺**还原出「真实内容到底是什么」。一只代理在它唯一那次
|
|
33
|
+
* 被观察时就能给出假答案,而那与「宿主注入了一个会撒谎的传输层」是同一件事 —— 那种进程里每个对象
|
|
34
|
+
* 都不可信,本件不是能修好它的那一层。下面那一串防御的**唯一**目的是把这一族挡在「安全」判决之外
|
|
35
|
+
* 并保住线程,不是为了在敌意宿主上还原真相。
|
|
36
|
+
*
|
|
37
|
+
* ## 分工(与仓内既有形同款)
|
|
38
|
+
* **纯投影,零 IO、零 module 级可变态、零文案**:本件不构造 client、不认 baseUrl、不碰凭据,
|
|
39
|
+
* 也**一句面向用户的话都不铸** —— 措辞、是否上屏、排序与折叠全归端。
|
|
40
|
+
* 取件仍走既有的权威 `client.approvals.list()`(见 `hitlBridge.ts` 的 `ApprovalsResourceLike`),
|
|
41
|
+
* 本件只吃它的回体。
|
|
42
|
+
*/
|
|
43
|
+
/**
|
|
44
|
+
* 🔴 **校验的尺子 = 类型面地板,不是「数据好不好看」**(L-38 复审采纳)。
|
|
45
|
+
* 本文件产出的行被声明成 {@link CrashConvergedRow},所以**类型对不上必须丢**(放过去就是在类型面
|
|
46
|
+
* 撒谎,端读到 `undefined` 时编译器不会提醒任何人)。但**超出类型面的严格**是另一回事:
|
|
47
|
+
* `ts: NaN`、`toolName: ''` 这类**退化但合型**的值只是难看,而为它们丢掉整行的代价是
|
|
48
|
+
* **一条真孤儿审批从人眼前消失** —— 那正是本面存在要防的那件事。⇒ 只在两处越过类型面:
|
|
49
|
+
* · `approvalId` 要**非空**(它是行身份:重复取件时的去重键、端要引用的那个 id;空串让这一行
|
|
50
|
+
* 结构上不可用,而不只是难看);
|
|
51
|
+
* · 三个闭集判别式(`decision`/`cause`/`orphanState`)—— 对不上不是「退化」,是**别族的行**。
|
|
52
|
+
* 其余一律只判类型。`dropped` 因此只数「**类型面**读不出的行」,不数「数据不好看的行」。
|
|
53
|
+
*/
|
|
54
|
+
const isString = (v) => typeof v === 'string';
|
|
55
|
+
const isNumber = (v) => typeof v === 'number';
|
|
56
|
+
/** 行身份位:空串既去不了重也引用不了,结构上不可用(唯一一处越过类型面的串判)。 */
|
|
57
|
+
const nonEmptyString = (v) => typeof v === 'string' && v.length > 0;
|
|
58
|
+
/**
|
|
59
|
+
* 从一个**不可信**对象上取一位:只认**自有数据描述符**的 `value`。
|
|
60
|
+
*
|
|
61
|
+
* 🔴 为什么不用 `obj[key]`(复审采纳,L-38):普通属性读取会 ① **执行** accessor,② 一路查到
|
|
62
|
+
* **原型**上去。执行意味着同步跑别人的代码 —— 而 `try/catch` 接得住「抛」,接不住「不返回」:
|
|
63
|
+
* 一只忙等 / 死循环的 getter 会把启动 / `--resume` 路的线程**永久**钉住,行数上限对它无效。
|
|
64
|
+
* ⇒ 三处不可信读取(信封的 `crashConverged`、载体的 `length`、载体的每个数字下标)统一走这里:
|
|
65
|
+
* accessor / 缺席 / 只挂在原型上的东西**一律当缺席**,一次别人的代码都不执行。
|
|
66
|
+
* 真供给来自 `JSON.parse`,每一位都是自有数据位 ⇒ 这条对真行零影响。
|
|
67
|
+
* ⚠️ `Object.getOwnPropertyDescriptor` 自己对代理会触发 trap(可能抛)⇒ 调用点都在保护块内。
|
|
68
|
+
*/
|
|
69
|
+
const ownDataValue = (obj, key) => {
|
|
70
|
+
const d = Object.getOwnPropertyDescriptor(obj, key);
|
|
71
|
+
if (d === undefined || 'get' in d || 'set' in d)
|
|
72
|
+
return undefined;
|
|
73
|
+
return d.value;
|
|
74
|
+
};
|
|
75
|
+
/**
|
|
76
|
+
* 载体行数的**硬上限**(复审采纳,L-38)。`length` 只判「非负整数」是不够的:一个只装着一行、
|
|
77
|
+
* 却让 `length` trap 答十亿的数组代理能过掉那道判据,随后本函数**同步**空转十亿次 —— 浏览器 /
|
|
78
|
+
* TUI 主线程当场冻住,而这条面恰恰跑在**启动/`--resume` 路**上。
|
|
79
|
+
* 取值理由:`/v1/approvals` 是 per-scope 的挂起队列,真实供给远在这个数量级之下;
|
|
80
|
+
* 10 万行的遍历是毫秒级,而十亿行不是「多等一会」而是「回不来」。
|
|
81
|
+
* 超限 ⇒ 与「载体读不出」同一档(`undefined`,端零渲染):一个说不清有多少行的供给,
|
|
82
|
+
* 本包不假装数得清。
|
|
83
|
+
*/
|
|
84
|
+
const MAX_CRASH_CONVERGED_ROWS = 100_000;
|
|
85
|
+
function readCrashConvergedRow(v) {
|
|
86
|
+
// 🔴 **载体形判也在 try 内**(复审采纳,L-38):`Array.isArray(v)` 对一只**已撤销**的 `Proxy`
|
|
87
|
+
// 会抛 —— 它此前在 try 之外,于是一行读不出的载荷会把异常一路交给**外层**的 catch,
|
|
88
|
+
// 整只投影退化成 `undefined`:一条坏行**连坐**抹掉同批全部真孤儿,而端还会把这个结果
|
|
89
|
+
// 读成「本部署没提供这个面」。坏行的正确归宿是 `dropped` 一格,不是整批消失。
|
|
90
|
+
// (`typeof` 自己不会抛;会抛的是 `Array.isArray` 与后面每一次取属性。)
|
|
91
|
+
// 🔴 取属性本身要设防:供给来自宿主注入的传输层,它完全可以是一个带抛错 getter 的对象或敌意
|
|
92
|
+
// `Proxy`(与 `classifyMemoryStatusFailure` 同一条理由)。读一下就抛 ⇒ 判这一行读不出,
|
|
93
|
+
// 而不是让整只投影向外 reject —— 调用方是照着「本函数不抛」写的。
|
|
94
|
+
try {
|
|
95
|
+
if (typeof v !== 'object' || v === null || Array.isArray(v))
|
|
96
|
+
return null;
|
|
97
|
+
// 🔴 **恰一次枚举**(见头注):`Object.getOwnPropertyDescriptors` 是本函数对这一行的
|
|
98
|
+
// **唯一**一次观察 —— 它一次性拿到键集 + 每一位的描述符,且**不触发任何 getter**。
|
|
99
|
+
// 🔴 判据与快照必须出自**同一次**枚举:上一版先用它判 accessor、再用 `{ ...v }` **重新枚举
|
|
100
|
+
// 一遍**取值,于是一只**不抛**的 `Proxy` 只要在第一次 `ownKeys` 里亮出
|
|
101
|
+
// `originalDecision:'approve'`(⇒ 判定为纯数据、`unstable=false`)、在第二次 `ownKeys` 里
|
|
102
|
+
// 把这个可配置位**省掉**,快照就成了「pending + resumeSafe 且无已批证据」⇒ 落进 resumeSafe。
|
|
103
|
+
// 两次独立观察之间的任何不一致都是攻击面;一次观察则没有「另一次」可以与之矛盾。
|
|
104
|
+
// (代理仍可能在这唯一一次观察里撒谎 —— 那等同于「宿主注入了会撒谎的传输层」,见 §12 边界。)
|
|
105
|
+
const descs = Object.getOwnPropertyDescriptors(v);
|
|
106
|
+
const o = Object.create(null);
|
|
107
|
+
// 🔴 **自带原型的行一律 `unstable`**(复审采纳,L-38):快照只枚举**自有**位,所以挂在
|
|
108
|
+
// 原型上的 `originalDecision` / `decidedAtMs` 进不了快照 —— 跨位矛盾闸于是看不见证据,
|
|
109
|
+
// 一个**普通、无代理、无 accessor、观察完全稳定**的对象
|
|
110
|
+
// (`Object.assign(Object.create({originalDecision:'approve'}), row)`)就能落进 resumeSafe。
|
|
111
|
+
// 这不在「宿主注入的传输层整体撒谎」那条边界之内:它没撒谎,是本包少看了一层。
|
|
112
|
+
// ⇒ 原型不是 `Object.prototype` / `null` 的行,一律只挡「安全」这一侧(落 needsHuman,不丢)。
|
|
113
|
+
// 真供给来自 `JSON.parse`,原型恒是 `Object.prototype` ⇒ 这条对真行零影响。
|
|
114
|
+
const proto = Object.getPrototypeOf(v);
|
|
115
|
+
let unstable = proto !== Object.prototype && proto !== null;
|
|
116
|
+
// 🔴 **校验用的字典是 null 原型**(复审采纳,L-38):下面每一次 `o.xxx` 都是一次属性查找,
|
|
117
|
+
// 普通 `{}` 的查找会**落到 `Object.prototype` 上**。如果本进程里那份原型被污染过
|
|
118
|
+
// ——例如 `Object.prototype.sessionId` 被装成一只 `delete this.originalDecision` 的 getter——
|
|
119
|
+
// 那么「校验可选位」这一步就会把**快照里的已批证据**抹掉,随后跨位矛盾闸看不见证据,
|
|
120
|
+
// 危险行落进 resumeSafe(实测修前如此)。null 原型的字典**没有可查找的上一层**,
|
|
121
|
+
// 这条路径按构造消失。
|
|
122
|
+
// 🔴 **落键一律走 `Object.defineProperty`,绝不用 `o[k] = …`**(复审采纳,L-38):
|
|
123
|
+
// `'__proto__'` 是一个**合法的自有可枚举键**(JSON 里就出得来),而普通赋值对它**不是存值**
|
|
124
|
+
// —— 它会调用 `Object.prototype.__proto__` 的 **setter**,把那个值装成快照的**原型**。
|
|
125
|
+
// 于是一行「自有位全是纯数据」(⇒ `unstable` 为假)的载荷,可以把一只带 `sessionId` getter 的
|
|
126
|
+
// 对象注射成快照的原型;下面校验可选位读 `o.sessionId` 时那只 getter 就跑起来,
|
|
127
|
+
// `delete this.originalDecision` 把**快照里**的已批证据抹掉 ⇒ 危险行落进 resumeSafe
|
|
128
|
+
// (实测修前 `resumeSafe=1`)。accessor 闸看不见它:抛错的那一位在**注射进来的原型**上,
|
|
129
|
+
// 不在被枚举的行上。
|
|
130
|
+
// `defineProperty` 不触发任何 setter ⇒ `__proto__` 老老实实变成一个自有数据位
|
|
131
|
+
// (它遮住原型上那个 accessor),快照的原型仍是 `Object.prototype`,additive 键照样保全。
|
|
132
|
+
const put = (k, value) => {
|
|
133
|
+
Object.defineProperty(o, k, { value, enumerable: true, writable: true, configurable: true });
|
|
134
|
+
};
|
|
135
|
+
for (const k of Object.keys(descs)) {
|
|
136
|
+
const d = descs[k];
|
|
137
|
+
if (d === undefined || d.enumerable !== true)
|
|
138
|
+
continue;
|
|
139
|
+
if ('get' in d || 'set' in d) {
|
|
140
|
+
// 🔴 **accessor 一律不执行**(复审采纳,L-38):`d.get.call(v)` 是**同步**执行别人的代码,
|
|
141
|
+
// 而 `catch` 只接得住「抛」,接不住「不返回」—— 一只死循环 / 忙等的 getter 会把
|
|
142
|
+
// 启动 / `--resume` 路的线程**永久**钉住,10 万行上限对这一形完全无效。
|
|
143
|
+
// ⇒ 这一位当**缺席**处理,并把整行标 `unstable`。后果分两档,都是可接受的一侧:
|
|
144
|
+
// · 缺的是**必填位** ⇒ 校验过不了 ⇒ 这一行计入 `dropped`(响亮,不是静默);
|
|
145
|
+
// · 缺的是**可选 / additive 位** ⇒ 行照留,只是永远拿不到「可安全重跑」这个判决。
|
|
146
|
+
// 真供给来自 `JSON.parse`,每一位都是数据描述符 ⇒ 这条对真行零影响。
|
|
147
|
+
unstable = true;
|
|
148
|
+
continue;
|
|
149
|
+
}
|
|
150
|
+
put(k, d.value);
|
|
151
|
+
}
|
|
152
|
+
// 行身份:唯一一处越过类型面的严格(空 id 结构上不可用,见 nonEmptyString 头注)。
|
|
153
|
+
if (!nonEmptyString(o.approvalId))
|
|
154
|
+
return null;
|
|
155
|
+
// 其余必填位:只判**类型**。退化但合型的值(空 toolName / NaN 时刻)难看归难看,
|
|
156
|
+
// 为它丢掉整行 = 让一条真孤儿从人眼前消失。
|
|
157
|
+
if (!isString(o.toolName))
|
|
158
|
+
return null;
|
|
159
|
+
if (!isString(o.taskId))
|
|
160
|
+
return null;
|
|
161
|
+
if (!isNumber(o.ts))
|
|
162
|
+
return null;
|
|
163
|
+
if (!isNumber(o.expiresAtMs))
|
|
164
|
+
return null;
|
|
165
|
+
if (!isNumber(o.convergedAtMs))
|
|
166
|
+
return null;
|
|
167
|
+
if (typeof o.resumeSafe !== 'boolean')
|
|
168
|
+
return null;
|
|
169
|
+
// 判别式三位:对不上 = 这不是本族的行(见 CrashConvergedRow 头注的闭集说明)。
|
|
170
|
+
if (o.decision !== 'denied')
|
|
171
|
+
return null;
|
|
172
|
+
if (o.cause !== 'crashed_before_park')
|
|
173
|
+
return null;
|
|
174
|
+
if (o.orphanState !== 'pending' && o.orphanState !== 'decided')
|
|
175
|
+
return null;
|
|
176
|
+
// 可选三位:缺席合法,在场必须**合型**(同上,不再加类型面之外的严格)。
|
|
177
|
+
if (o.sessionId !== undefined && !isString(o.sessionId))
|
|
178
|
+
return null;
|
|
179
|
+
if (o.originalDecision !== undefined && o.originalDecision !== 'approve')
|
|
180
|
+
return null;
|
|
181
|
+
if (o.decidedAtMs !== undefined && !isNumber(o.decidedAtMs))
|
|
182
|
+
return null;
|
|
183
|
+
// 🔴 交还的是**普通原型**的对象(端拿到的是一只正常对象:`hasOwnProperty` / `toString` 都在),
|
|
184
|
+
// 但它的每一位都逐字来自上面那份 null 原型快照 —— 校验与分桶都发生在快照上,
|
|
185
|
+
// 交付只是把同一批值换个原型装出去。落键仍走 `defineProperty`(`__proto__` 同理)。
|
|
186
|
+
const delivered = {};
|
|
187
|
+
for (const k of Object.keys(o)) {
|
|
188
|
+
Object.defineProperty(delivered, k, { value: o[k], enumerable: true, writable: true, configurable: true });
|
|
189
|
+
}
|
|
190
|
+
return {
|
|
191
|
+
row: delivered,
|
|
192
|
+
unstable,
|
|
193
|
+
orphanState: o.orphanState,
|
|
194
|
+
resumeSafe: o.resumeSafe,
|
|
195
|
+
hasApprovalEvidence: o.originalDecision !== undefined || o.decidedAtMs !== undefined,
|
|
196
|
+
};
|
|
197
|
+
}
|
|
198
|
+
catch {
|
|
199
|
+
// 敌意行(已撤销 Proxy / 抛错 getter):判**这一行**读不出,由调用方计入 `dropped`。
|
|
200
|
+
// 不向外抛,更不让它连坐同批的好行。
|
|
201
|
+
return null;
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
/**
|
|
205
|
+
* `GET /v1/approvals` 回体 → `crashConverged` 的**分桶投影**。纯函数,**永不抛**。
|
|
206
|
+
*
|
|
207
|
+
* ## 🔴 缺席 vs 空数组:两件不同的事,判据不许合流
|
|
208
|
+
* · **键缺席**(老 server / deps 不在场 / 读不动)⇒ 返回 `undefined` ——「本部署没告诉我这件事」。
|
|
209
|
+
* 端此时**零渲染**:绝不渲「0 个」「本次无崩溃遗留」之类的话,那是替 server 下一个它没说过的
|
|
210
|
+
* 断言([honest-absence-not-fabricated-zero])。
|
|
211
|
+
* · **键在场且是空数组** ⇒ 返回 `{ total: 0, resumeSafe: [], needsHuman: [], dropped: 0 }` ——
|
|
212
|
+
* 「server 明说:一条都没有」。这一档端**可以**渲「没有崩溃遗留」。
|
|
213
|
+
* ⇒ 两档的**返回形不同**(`undefined` vs 对象),端拿 `=== undefined` 一刀分开,不必读计数。
|
|
214
|
+
*
|
|
215
|
+
* ## 🔴 载体在场却不是数组 ⇒ 同样 `undefined`(**不**折成 `total:0`)
|
|
216
|
+
* 那是形漂了或中间层改写了,本包**读不出**这次的供给。折成 `{total:0}` 会让端渲出
|
|
217
|
+
* 「没有崩溃遗留」——在一条给人判断「能不能闭眼重跑」的面上,这是最坏方向的假断言。
|
|
218
|
+
* 两档合流的代价只是「都零渲染」,而分错的代价是一句用户会照着去操作的谎。
|
|
219
|
+
*
|
|
220
|
+
* ## 🔴 三处不可信读取只认**自有数据位**
|
|
221
|
+
* 信封的 `crashConverged`、载体的 `length`、载体的每个数字下标 —— 三处都走 `ownDataValue`:
|
|
222
|
+
* accessor / 缺席 / 只挂在原型上的东西一律**当缺席**,一次别人的代码都不执行(`catch` 接得住
|
|
223
|
+
* 「抛」,接不住「不返回」)。真供给来自 `JSON.parse`,每一位都是自有数据位 ⇒ 对真行零影响。
|
|
224
|
+
*
|
|
225
|
+
* ## 🔴 载体**读不出**(已撤销 `Proxy` / `length` 或描述符取值抛)⇒ 也是 `undefined`
|
|
226
|
+
* 触碰载体的每一处都在保护内 —— 连 `Array.isArray()` 自己都是(对已撤销的 `Proxy` 调用它直接抛)。
|
|
227
|
+
* 走到一半炸掉时刻意**不交还半程结果**:一个自己都知道不全的计数,拿去渲「上次崩溃影响了 N 件」
|
|
228
|
+
* 比不说话更坏。
|
|
229
|
+
* 🔴 遍历**按数字下标**,不用载体自己的迭代协议:自带 `Symbol.iterator` 覆盖的数组能一条都不产出
|
|
230
|
+
* (于是真孤儿被伪造成「server 明说一条都没有」),也能把危险行替换成安全行。下标读不问载体
|
|
231
|
+
* 「有哪些行」这个问题。⚠️ 边界:代理仍能在 `length`/下标 trap 上撒谎 —— 那等同于「宿主注入了
|
|
232
|
+
* 会撒谎的传输层」,本包挡不住也不假装挡得住;本条守的是协议面。
|
|
233
|
+
*
|
|
234
|
+
* ## 🔴 进 resumeSafe 桶的合取有**五项**(两项主判据 + 两项跨位自洽 + 一项载体自证)
|
|
235
|
+
* `orphanState==='pending'`(工具零执行)∧ `resumeSafe===true` ∧ `originalDecision` 缺席 ∧
|
|
236
|
+
* `decidedAtMs` 缺席 ∧ 这一行**不带 accessor**。中间两项是**矛盾闸**:`pending` 说「一步都没执行」,
|
|
237
|
+
* 而那两位是「人已经按下过 approve」的证据 —— 同时在场 = 自相矛盾的载荷。最后一项是**顺序重入闸**:
|
|
238
|
+
* 带 getter 的行能在展开过程中把证据位删掉(见行读口头注的实测)。三者都**只挡「安全」这一侧**,
|
|
239
|
+
* 一律落 needsHuman 而**不丢** —— 它们可能是真孤儿,而且恰恰是最该给人看的那几条。
|
|
240
|
+
*
|
|
241
|
+
* ## 🔴 行数硬上限
|
|
242
|
+
* `length` 超过 100000 ⇒ 与「载体读不出」同一档(`undefined`)。判在遍历**之前**,所以一个谎报
|
|
243
|
+
* 十亿的 `length` trap 连一次下标读都触发不了 —— 否则同步空转会把启动/`--resume` 路的主线程冻住。
|
|
244
|
+
*
|
|
245
|
+
* ## 🔴 入参是 `unknown`,不是「带一个可选 `crashConverged` 的对象」
|
|
246
|
+
* 写成 `{ crashConverged?: unknown }` 会造出一个 TypeScript **弱类型**(成员全可选):把真
|
|
247
|
+
* `client.approvals.list()` 的回体喂进来时,SDK 7.4.0 声明的 `{ pending; livePending? }` 与它
|
|
248
|
+
* **一个共同属性都没有** ⇒ TS2559「has no properties in common」,文档里那句最主要的用法当场编不过
|
|
249
|
+
* (而只用本包自己的 `ApprovalsListEnvelope` 去测发现不了 —— 那个形恰好声明了 `crashConverged`)。
|
|
250
|
+
* 收 `unknown` 也更诚实:本函数对入参的全部工作**就是**窄化它,形状是运行期才知道的事。
|
|
251
|
+
*
|
|
252
|
+
* @param env 任意 `/v1/approvals` 回体(只读 `crashConverged` 一键;非对象 / `null` ⇒ `undefined`)。
|
|
253
|
+
*/
|
|
254
|
+
export function projectCrashConverged(env) {
|
|
255
|
+
const resumeSafe = [];
|
|
256
|
+
const needsHuman = [];
|
|
257
|
+
let dropped = 0;
|
|
258
|
+
// 🔴 **整段读取都在保护内**(复审采纳,L-38):载体上的每一次触碰都可能执行**别人的**代码。
|
|
259
|
+
// ① 信封取属性(抛错 getter / `Proxy`);
|
|
260
|
+
// ② `Array.isArray()` 自己 —— 对一只**已撤销**的 `Proxy` 调用它直接抛 `TypeError`
|
|
261
|
+
// (它此前在 try 之外,那正是「永不抛」承诺上最后一个没堵的洞);
|
|
262
|
+
// ③ `length` 与逐个下标取值。
|
|
263
|
+
// 任一处抛 ⇒ 与「载体不是数组」同一档:这次供给**读不出** ⇒ 诚实缺席。
|
|
264
|
+
// ⚠️ 刻意**不**交还半程结果:走到一半才炸,已读到的行数说明不了总数,拿它去渲
|
|
265
|
+
// 「上次崩溃影响了 N 件」就是拿一个自己都知道不全的数当结论。
|
|
266
|
+
try {
|
|
267
|
+
if (typeof env !== 'object' || env === null)
|
|
268
|
+
return undefined;
|
|
269
|
+
// 🔴 信封这一位也只认**自有数据描述符**(见 `ownDataValue` 头注):普通读取会执行 accessor,
|
|
270
|
+
// 而一只不返回的 getter 挡不住 —— `catch` 接不住「不返回」。
|
|
271
|
+
const raw = ownDataValue(env, 'crashConverged');
|
|
272
|
+
if (raw === undefined)
|
|
273
|
+
return undefined;
|
|
274
|
+
if (!Array.isArray(raw))
|
|
275
|
+
return undefined;
|
|
276
|
+
// 🔴 **按数字下标走,不用载体自己的迭代协议**(复审采纳,L-38):`for…of` 把「这个数组里
|
|
277
|
+
// 到底有哪些元素」这件事**交给载体自己回答** —— 一个自带 `Symbol.iterator` 覆盖的数组
|
|
278
|
+
// (中间层改写 / 反序列化器的产物都造得出)可以:一条都不产出 ⇒ 本函数答
|
|
279
|
+
// `{total:0}`,而端会把它读成「server 明说一条都没有」,一条真孤儿就此人间蒸发;
|
|
280
|
+
// 或者把一条 `decided` 的危险行换成一条 `pending/resumeSafe` 的安全行 ⇒ 直接误导重跑。
|
|
281
|
+
// 下标读**读不到**这两种伪造(它不问载体「你有几个元素」以外的任何问题)。
|
|
282
|
+
// ⚠️ 边界说清楚:一只**代理**仍然能在 `length` / 下标 trap 上撒谎 —— 那与「宿主注入了一个
|
|
283
|
+
// 会撒谎的传输层」是同一件事,本包挡不住也不假装挡得住。本条守的是**协议面**:
|
|
284
|
+
// 不把「有哪些行」的解释权交给载体的迭代器。
|
|
285
|
+
const len = ownDataValue(raw, 'length');
|
|
286
|
+
if (typeof len !== 'number' || !Number.isInteger(len) || len < 0)
|
|
287
|
+
return undefined;
|
|
288
|
+
// 🔴 行数硬上限(见 MAX_CRASH_CONVERGED_ROWS 头注):**先判再遍历** —— 判在循环之前,
|
|
289
|
+
// 所以一个谎报十亿的 `length` 连一次下标读都触发不了。
|
|
290
|
+
if (len > MAX_CRASH_CONVERGED_ROWS)
|
|
291
|
+
return undefined;
|
|
292
|
+
for (let i = 0; i < len; i++) {
|
|
293
|
+
const reading = readCrashConvergedRow(ownDataValue(raw, String(i)));
|
|
294
|
+
if (reading === null) {
|
|
295
|
+
dropped++;
|
|
296
|
+
continue;
|
|
297
|
+
}
|
|
298
|
+
const row = reading.row;
|
|
299
|
+
// 🔴 **保守侧的合取有四项**(复审采纳,L-38):前两项是主判据,后两项是
|
|
300
|
+
// **跨位自洽**——`orphanState:'pending'` 的含义是「崩的时候工具一步都没执行」,而
|
|
301
|
+
// `originalDecision`/`decidedAtMs` 是「人当时已经按下过 approve」的证据。两者同时在场
|
|
302
|
+
// 是**自相矛盾**的载荷(版本斜差 / 畸形体 / 中间层改写都造得出),而它落错方向的代价正是
|
|
303
|
+
// 本面要防的那件事:让人闭眼重跑一件**可能已经开始落副作用**的事。
|
|
304
|
+
// ⇒ 矛盾形不产出「安全」这个确定判决,落 needsHuman(与 `readCaptureOptOut` 的完整
|
|
305
|
+
// 真值表同一条纪律:不在契约表上的组合一律不给确定答案)。
|
|
306
|
+
// 🔴 **不丢**:它是一条真孤儿,而且恰恰是最该给人看的那一条 —— 丢掉它比渲错更坏。
|
|
307
|
+
// 🔴 第五项:`unstable`(这一行的载体带 accessor 或自带原型,见行读口头注)—— 同样只把它
|
|
308
|
+
// 挡在「安全」这一侧,不丢。
|
|
309
|
+
// 🔴 三个判据一律读 `reading` 而**不是** `row`(见 {@link CrashConvergedRowReading} 头注):
|
|
310
|
+
// 它们是校验那一刻定下来的值,回头再读一遍交付物等于开第二次观察窗口。
|
|
311
|
+
const consistentlyUnexecuted = reading.orphanState === 'pending' && !reading.hasApprovalEvidence;
|
|
312
|
+
if (!reading.unstable && consistentlyUnexecuted && reading.resumeSafe === true)
|
|
313
|
+
resumeSafe.push(row);
|
|
314
|
+
else
|
|
315
|
+
needsHuman.push(row);
|
|
316
|
+
}
|
|
317
|
+
}
|
|
318
|
+
catch {
|
|
319
|
+
// 敌意/坏载体(已撤销 Proxy / `length` 或下标取值抛):供给读不出 ⇒ 诚实缺席,不向外抛。
|
|
320
|
+
return undefined;
|
|
321
|
+
}
|
|
322
|
+
return { total: resumeSafe.length + needsHuman.length, resumeSafe, needsHuman, dropped };
|
|
323
|
+
}
|
|
@@ -65,7 +65,8 @@
|
|
|
65
65
|
* vocabulary, no claude.ai permission-rule destinations, no widget/glyph/keybinding crosses this seam. The
|
|
66
66
|
* backend supplies the SIGNAL; the shell owns the chrome.
|
|
67
67
|
*/
|
|
68
|
-
import type { AgentEvent, ApprovalDecision, PendingCheckpoint, CheckpointGate, PlanReviewRequest, AssistantTaskStatus } from '@sema-agent/sdk';
|
|
68
|
+
import type { AgentEvent, ApprovalDecision, ApprovalStaleError, PendingCheckpoint, CheckpointGate, PlanReviewRequest, AssistantTaskStatus } from '@sema-agent/sdk';
|
|
69
|
+
import type { ApprovalsListEnvelope } from './crashConverged.js';
|
|
69
70
|
/** durable `/decide` 腿的既有缺省拒因(不带归因时逐字不变 —— 0.27.0 及之前的 wire 字节)。 */
|
|
70
71
|
export declare const DEFAULT_DENY_REASON = "The user rejected this tool use";
|
|
71
72
|
/** server 两条腿共用的 reason 字符上限(超限 413,决断被打回)。 */
|
|
@@ -96,12 +97,15 @@ export type HitlCanUseToolFn<D extends HitlPermissionDecisionLike = HitlPermissi
|
|
|
96
97
|
}, input: Record<string, unknown>, toolUseContext: never, assistantMessage: never, toolUseID: string, forceDecision?: D) => Promise<D>;
|
|
97
98
|
export interface ApprovalsResourceLike {
|
|
98
99
|
/** GET /v1/approvals — the rich decide-ready queue (approvals.ts:45). NEVER carries a capability token.
|
|
99
|
-
* SDK ≥0.1.0([1908] 信封归一):wire 信封原样 `{ pending }`。
|
|
100
|
+
* SDK ≥0.1.0([1908] 信封归一):wire 信封原样 `{ pending }`。
|
|
101
|
+
* 🔴 L-38(server 7.55.0):回体是 {@link ApprovalsListEnvelope} —— `pending` 之外还可能带
|
|
102
|
+
* additive 的 `livePending` / `crashConverged`(按 deps 在场才发)。**放宽是 additive**:
|
|
103
|
+
* 老 mock 的 `{ pending }` 仍然可赋值,本文件与 `approvalsFeed.ts` 的 `.pending` 消费点
|
|
104
|
+
* 一个字节不动。`crashConverged` 的读法见 `hitl/crashConverged.ts` 的 `projectCrashConverged`
|
|
105
|
+
* —— 🔴 它是**上一条命的残留**读面,与本桥这条 D-1 取件路**互不相干**,别在这里顺手消费它。 */
|
|
100
106
|
list(opts?: {
|
|
101
107
|
signal?: AbortSignal;
|
|
102
|
-
}): Promise<
|
|
103
|
-
pending: PendingCheckpoint[];
|
|
104
|
-
}>;
|
|
108
|
+
}): Promise<ApprovalsListEnvelope>;
|
|
105
109
|
/** POST /v1/approvals/:sessionId/decide — resolve THAT checkpoint; the resumed run continues its stream
|
|
106
110
|
* (approvals.ts:52). NOT a submit → no retry (a decide must never double-act). */
|
|
107
111
|
decide(sessionId: string, decision: ApprovalDecision, opts?: {
|
|
@@ -168,24 +172,32 @@ export declare class HitlSafetyError extends Error {
|
|
|
168
172
|
* 🔴 **恒不含 `checkpointToken`**(server 侧 resume 凭证不外发纪律)——本形也刻意没有那一位:
|
|
169
173
|
* 多一个位就是给一个「凭证能过境」的错觉。
|
|
170
174
|
*
|
|
171
|
-
* ──
|
|
172
|
-
*
|
|
173
|
-
* `
|
|
174
|
-
*
|
|
175
|
-
*
|
|
176
|
-
* (
|
|
177
|
-
*
|
|
178
|
-
*
|
|
179
|
-
*
|
|
175
|
+
* ── 自铸形已退役,本名现在是上游形的**别名**(0.48.0)──────────────────────────────────
|
|
176
|
+
* 0.47.0 这里是一个**自铸 interface** + 一段记账:`@sema-agent/sdk` 7.3.0 起已有逐字同形的
|
|
177
|
+
* `ApprovalStaleCurrentPending`,但当时本包 peer 地板是 `>=7.2.0`,那一版上这个名字不存在 ⇒
|
|
178
|
+
* `import type` 会让装 7.2.0 的端当场编不过。记账段同批写死了退役条件:「peer 地板抬到 `>=7.3.0`
|
|
179
|
+
* 的那一批,把本形改成 `import type` 的别名并删掉这段记账」。
|
|
180
|
+
* 本批(0.48.0)peer 地板抬到 **`>=7.4.0`**(四件新投影都吃 7.4.0 的类型面)⇒ 条件满足,兑现。
|
|
181
|
+
* 🔴 **别名而不是删名**:`GateCurrentPending` 是 `FsApprovalOutcome` / `GateOutcome` 两条腿的**已发布
|
|
182
|
+
* 导出名**,删它是 BREAKING。别名让上游形成为**唯一真源**(上游改一个位,本包的两条腿当天跟着
|
|
183
|
+
* 动,不再有第二份可能漂的声明),而本名继续对已接入的端有效。
|
|
184
|
+
* 🔴 **同名不同源的风险随之消失**:0.47.0 刻意取了个**不同名**(免得地板线下出现一个与上游同名却
|
|
185
|
+
* 不同源的形,[same-name-different-meaning-crosses-layers]);现在两者是同一个形,那条顾虑不再
|
|
186
|
+
* 适用 —— 但名字**不回改**成上游名,理由见上一条(已发布导出名)。
|
|
187
|
+
*
|
|
188
|
+
* 🔴 **为什么绕道 `ApprovalStaleError['currentPending']` 而不是直接 `import type
|
|
189
|
+
* { ApprovalStaleCurrentPending }`(上游缺口,本批亲验实证)**:那个名字在 sdk 7.4.0 里**声明了
|
|
190
|
+
* 但没有从包入口导出** —— `dist/types.d.ts:2359` 有 `export interface ApprovalStaleCurrentPending`,
|
|
191
|
+
* 而 `dist/index.d.ts` 的 re-export 名单里**没有它**(同文件只导出了 `ApprovalStaleError`),
|
|
192
|
+
* 且包 `exports` 映射只开 `.` / `./registry` 两个子路径,深引 `@sema-agent/sdk/dist/types.js`
|
|
193
|
+
* 这条路根本不通。⇒ 按名 import 是**编译错**,不是风格选择。
|
|
194
|
+
* 绕道取的是**同一个声明**(`currentPending?: ApprovalStaleCurrentPending | undefined`,
|
|
195
|
+
* `NonNullable` 剥掉可选位),所以单源化的目的一字不打折:上游改那个形,本包当天跟着动。
|
|
196
|
+
* 📋 **上游候件(已登记,不在本批修)**:请 sdk 把 `ApprovalStaleCurrentPending` 加进 `index.d.ts`
|
|
197
|
+
* 的 `export type {…}` 名单(纯 additive,零行为)。到货后本别名可改回按名 import,
|
|
198
|
+
* **但那是等价重构、不是修复** —— 今天这条路已经拿到上游形本身,不存在第二份可能漂的声明。
|
|
180
199
|
*/
|
|
181
|
-
export
|
|
182
|
-
/** 当前 pending 被门住的工具名(与 pending 行 `toolName` 同源同值)。UNTRUSTED-for-display。 */
|
|
183
|
-
readonly toolName: string;
|
|
184
|
-
/** 当前 pending 的 `pendingAction.toolCallId` —— 壳重定位后 decide 要**逐字回显**的 D-1 锚。 */
|
|
185
|
-
readonly boundCallId: string;
|
|
186
|
-
/** server 铸的 `boundInputHash`(行上有才带)。🔴 逐字回显,**绝不本地重算**(§9.1 同一条铁律)。 */
|
|
187
|
-
readonly boundInputHash?: string;
|
|
188
|
-
}
|
|
200
|
+
export type GateCurrentPending = Readonly<NonNullable<ApprovalStaleError['currentPending']>>;
|
|
189
201
|
/**
|
|
190
202
|
* 从一次 decide 失败(任意抛出物)里读出 {@link GateCurrentPending} —— 读不出即 `undefined`。
|
|
191
203
|
*
|
|
@@ -194,8 +206,10 @@ export interface GateCurrentPending {
|
|
|
194
206
|
* SDK 的 `ApprovalStaleError` 由**宿主**决定 —— web/desktop 完全可以注入自己的传输层。拿
|
|
195
207
|
* `instanceof` 当闸,等于让「宿主用的是哪一份 SDK 实例」决定一个纯读值的位在不在
|
|
196
208
|
* (跨 realm / 双实例 = `HitlSafetyError` 判型契约踩过的同一个坑,见 §4e)。
|
|
197
|
-
* ②
|
|
198
|
-
*
|
|
209
|
+
* ② **类型面**(0.48.0 就地订正):0.47.0 这条写的是「`ApprovalStaleError.currentPending` 只在
|
|
210
|
+
* sdk ≥7.3.0 上声明,而本包地板是 7.2.0」——**地板已抬到 `>=7.4.0`,那半条不再成立**。
|
|
211
|
+
* 但本条的**结论不变**,因为承重的从来是 ① 那半:即使类型面现在够得着,抛出来的对象仍由宿主
|
|
212
|
+
* 的传输层决定,`instanceof` 依旧是错的判据。⇒ 保留宽读,只订正理由。
|
|
199
213
|
* 🔴 **判据锚在「值本身合不合形」,不锚它骑在哪个类/哪个 errorCode 上**
|
|
200
214
|
* ([anchor-on-the-deciding-quantity]):真正决定「壳能不能一跳重定位」的量,是**这个三元组的
|
|
201
215
|
* 两件必填位在不在且是非空串** —— 而不是错误的类名。宽读在这里是**方向安全**的:本位不参与
|
package/dist/hitl/hitlBridge.js
CHANGED
|
@@ -53,6 +53,18 @@ export class HitlSafetyError extends Error {
|
|
|
53
53
|
this.name = 'HitlSafetyError';
|
|
54
54
|
}
|
|
55
55
|
}
|
|
56
|
+
/**
|
|
57
|
+
* 🔴 **编译期负控**(对抗复审 [medium] 采纳,0.48.0):三位必须**全 `readonly`**。
|
|
58
|
+
*
|
|
59
|
+
* 0.47.0 的自铸形三位逐个写着 `readonly`,而上游 `ApprovalStaleCurrentPending` 的成员是**可写**的
|
|
60
|
+
* —— 首版别名(`NonNullable<…>`,不带 `Readonly<>`)把这条已发布的类型契约悄悄弱了一档:
|
|
61
|
+
* 消费方从此可以直接改写 `boundCallId` / `boundInputHash`,而那两位的契约恰恰是
|
|
62
|
+
* **逐字回显、绝不本地重算**(§9.1;本地重算一次就会把一次合法审批 fail-close 掉)。
|
|
63
|
+
* 🔴 这类退化**没有任何行为门看得见**(它不改变任何运行期字节)—— 名字在、字段在、行为门全绿。
|
|
64
|
+
* 所以钉必须下在编译期:`Readonly<>` 哪天被顺手摘掉,这一行当场编译错。
|
|
65
|
+
*/
|
|
66
|
+
const _gateCurrentPendingReadonlyPin = true;
|
|
67
|
+
void _gateCurrentPendingReadonlyPin;
|
|
56
68
|
/**
|
|
57
69
|
* 从一次 decide 失败(任意抛出物)里读出 {@link GateCurrentPending} —— 读不出即 `undefined`。
|
|
58
70
|
*
|
|
@@ -61,8 +73,10 @@ export class HitlSafetyError extends Error {
|
|
|
61
73
|
* SDK 的 `ApprovalStaleError` 由**宿主**决定 —— web/desktop 完全可以注入自己的传输层。拿
|
|
62
74
|
* `instanceof` 当闸,等于让「宿主用的是哪一份 SDK 实例」决定一个纯读值的位在不在
|
|
63
75
|
* (跨 realm / 双实例 = `HitlSafetyError` 判型契约踩过的同一个坑,见 §4e)。
|
|
64
|
-
* ②
|
|
65
|
-
*
|
|
76
|
+
* ② **类型面**(0.48.0 就地订正):0.47.0 这条写的是「`ApprovalStaleError.currentPending` 只在
|
|
77
|
+
* sdk ≥7.3.0 上声明,而本包地板是 7.2.0」——**地板已抬到 `>=7.4.0`,那半条不再成立**。
|
|
78
|
+
* 但本条的**结论不变**,因为承重的从来是 ① 那半:即使类型面现在够得着,抛出来的对象仍由宿主
|
|
79
|
+
* 的传输层决定,`instanceof` 依旧是错的判据。⇒ 保留宽读,只订正理由。
|
|
66
80
|
* 🔴 **判据锚在「值本身合不合形」,不锚它骑在哪个类/哪个 errorCode 上**
|
|
67
81
|
* ([anchor-on-the-deciding-quantity]):真正决定「壳能不能一跳重定位」的量,是**这个三元组的
|
|
68
82
|
* 两件必填位在不在且是非空串** —— 而不是错误的类名。宽读在这里是**方向安全**的:本位不参与
|
|
@@ -415,6 +415,26 @@ export interface ApprovalCardRequest {
|
|
|
415
415
|
* `renderUntrustedCommandText`(core 导出的展示基线)。本包只保证这一位到得了卡口。
|
|
416
416
|
*/
|
|
417
417
|
inputHasBidi?: true;
|
|
418
|
+
/**
|
|
419
|
+
* **S-30①(0.48.0;server ≥7.53 / core 5.60.0 #438;sdk 7.4.0 起在 `PendingCheckpoint` 上声明)**
|
|
420
|
+
* —— 原样来自 **durable park 行** `PendingCheckpoint.hasBidiControls`:这条 park 行的**执行载荷**里
|
|
421
|
+
* 含至少一个 DIRECTIONAL 格式控制符(Trojan Source:人眼读到的顺序 ≠ 真正执行的字节顺序)。
|
|
422
|
+
*
|
|
423
|
+
* 🔴 **与 {@link inputHasBidi} 刻意分键不合流**(上游把名字取得不同,正是为了不让人合并):
|
|
424
|
+
* · {@link inputHasBidi} = **同步腿**(`tool_approval` 活卡帧),server 对**帧自身序列化后的 args**
|
|
425
|
+
* 现算(E-14);
|
|
426
|
+
* · 本键 = **durable 腿**,值**完全由 core 供**(park mint 时 `PendingAction.hasBidiControls`,
|
|
427
|
+
* 反范式成 durable 行的 `has_bidi_controls` 列;server `listPending` 读列 `=== 1` 才铸,**不重算**)。
|
|
428
|
+
* 两者是**同语义、不同源、不同算点**的两个量 ⇒ 同一只 ask 的两条腿在场性**可以不一致**,合成一位
|
|
429
|
+
* 就是拿一个量冒充另一个([same-name-different-meaning-crosses-layers])。端要渲一个徽标的话,
|
|
430
|
+
* 读**两位的并**是允许的(那是端的呈现决定),但两位在本层必须各自到货。
|
|
431
|
+
* 🔴 **缺席绝不折成 `false`**(类型 `true`,与 {@link governanceForced} / {@link inputHasBidi} 同族):
|
|
432
|
+
* 缺席 = **没检出**(干净 / core 有界扫描没够着 / 列诞生前 park 的老行),消费端**禁**读成
|
|
433
|
+
* 「已确认干净」—— 那是对用户下一个证不出的断言。
|
|
434
|
+
* 🔴 **披露位,不是清洗位;本包字节零改**:清洗会改掉即将被执行的那串字节(卡上显示的与真跑的不是
|
|
435
|
+
* 同一个东西),比不披露更坏。显形(转义/高亮/加标记)归端。
|
|
436
|
+
*/
|
|
437
|
+
hasBidiControls?: true;
|
|
418
438
|
/**
|
|
419
439
|
* 被越级的持久 allow 规则**原文**(#144,原样来自 {@link ToolApprovalFrame.persistedRuleShadowed}
|
|
420
440
|
* 的合形值)——壳据此渲「你的规则仍在,只是这次调用被要求逐次确认」;缺席 = 卡形与 0.27.0
|
|
@@ -295,6 +295,13 @@ parkGatedCallId) {
|
|
|
295
295
|
callKey,
|
|
296
296
|
...(signal ? { signal } : {}),
|
|
297
297
|
...(pending.governanceForced === true ? { governanceForced: true } : {}),
|
|
298
|
+
// S-30①(0.48.0):durable 行的 bidi 披露位随卡透传。**同一个病形的第 N 例** —— 这处「行 → 卡」
|
|
299
|
+
// 重铸是闭形的,server ≥7.53 起行上就有的这一位在这里整键丢失([C170] 那次 ruleSuggestions /
|
|
300
|
+
// governanceForced 逐字同一个坐标)。本批由 durable-card 门 ⑦ 段的**富行键集普查**当天抓出
|
|
301
|
+
// (红文逐字:「无账:hasBidiControls」),不是人眼发现的。
|
|
302
|
+
// 🔴 条件 stamp **只认严格 true**,与 governanceForced / inputHasBidi 同族:上游契约是
|
|
303
|
+
// 「`true` 或缺席,恒不写 false/null」,这里认宽了就会把一个 falsy 值渲成披露。
|
|
304
|
+
...(pending.hasBidiControls === true ? { hasBidiControls: true } : {}),
|
|
298
305
|
...(ruleOffersReadOnly !== undefined ? { ruleOffersReadOnly } : {}),
|
|
299
306
|
...(isWireRecordCarrier(durableProbeCause) ? { probeCause: durableProbeCause } : {}),
|
|
300
307
|
// #348(0.44.0)durable 腿的对偶:行上**本来就存着**这个值(server `parked-decide.ts`;SDK
|
package/dist/index.d.ts
CHANGED
|
@@ -138,6 +138,7 @@ export * from './notifications.js';
|
|
|
138
138
|
export * from './steering.js';
|
|
139
139
|
export * from './diagnostics.js';
|
|
140
140
|
export * from './retryStatus.js';
|
|
141
|
+
export * from './sessionMemoryStatus.js';
|
|
141
142
|
export * from './adapt.js';
|
|
142
143
|
export * from './subagentContentStore.js';
|
|
143
144
|
export * from './engineAgentPanelStore.js';
|
|
@@ -237,6 +238,7 @@ export * from './hitl/resumeRunningCard.js';
|
|
|
237
238
|
export * from './hitl/persistedRulesWire.js';
|
|
238
239
|
export * from './hitl/localAllowRule.js';
|
|
239
240
|
export * from './hitl/approvalsFeed.js';
|
|
241
|
+
export * from './hitl/crashConverged.js';
|
|
240
242
|
export * from './interactiveHalt.js';
|
|
241
243
|
export * from './compensations.js';
|
|
242
244
|
export * from './request/printNotification.js';
|
package/dist/index.js
CHANGED
|
@@ -138,6 +138,12 @@ export * from './notifications.js';
|
|
|
138
138
|
export * from './steering.js';
|
|
139
139
|
export * from './diagnostics.js';
|
|
140
140
|
export * from './retryStatus.js';
|
|
141
|
+
// ── S-53(0.48.0):会话记忆姿态的三端公共读面 ────────────────────────────────────────────────
|
|
142
|
+
// 收在库里的理由是**两处判定**(见文件头):① 同 status 不同码 —— 本路由的 404 有两个互不相干的
|
|
143
|
+
// 含义(`not_found.session` 会话未知 / `not_found.route` 老 server 没这条路由),按 status 分诊必然
|
|
144
|
+
// 把「部署没这个面」说成「你这个会话不存在」;② 五键缺席语义**逐键不同**,健康会话上就有两键
|
|
145
|
+
// 合法缺席,一律读成「没有/关着/0」就是对用户下一个证不出的断言。
|
|
146
|
+
export * from './sessionMemoryStatus.js';
|
|
141
147
|
export * from './adapt.js';
|
|
142
148
|
// ── B1 批:纯函数 / 侧信道台账 / 投影闸(2026-07-27)──────────────────────────────────────────
|
|
143
149
|
export * from './subagentContentStore.js';
|
|
@@ -377,6 +383,14 @@ export * from './hitl/localAllowRule.js';
|
|
|
377
383
|
// B7 ③(census G20,**行为改动**不是搬迁):pending-approvals 推送 feed(stream 优先 / 断流回落
|
|
378
384
|
// 轮询 / 定期再试)。🔴 它**不替换** D-1 的取件 —— 那三处必须继续走权威 `list()`(见文件头)。
|
|
379
385
|
export * from './hitl/approvalsFeed.js';
|
|
386
|
+
// ── L-38:`/v1/approvals` additive 键 `crashConverged` 的读面 + 纯投影 ─────────────────────────
|
|
387
|
+
// local 引擎崩在审批门上时,那些孤儿 ask 被 server 重启后收敛成 DENIED 同码;这一键把「上一条命
|
|
388
|
+
// 留下了什么」交到端手上。收在库里的理由是**两处判定**三端各写一遍必然各错一遍:① **缺席 vs
|
|
389
|
+
// 空数组**是两件事(键缺席 ⇒ `undefined`,端零渲染;空数组 ⇒ server 明说「一条都没有」),折成
|
|
390
|
+
// 「0 个」就是替 server 下一个它没说过的断言;② **分桶只有一个合取**(零执行 ∧ 按记录看安全),
|
|
391
|
+
// decided 臂与任何说不清的组合一律落人工确认侧 —— 判错「安全」会让人闭眼重跑一件已经落过副作用
|
|
392
|
+
// 的事。🔴 `resumeSafe` 是以账本完整为前提的**缺省值不是铁证**,文案纪律见文件头 caveat。
|
|
393
|
+
export * from './hitl/crashConverged.js';
|
|
380
394
|
// ── #363 件③(0.47.0):交互 Esc 的**停止判定**三端公共上收 ──────────────────────────────────
|
|
381
395
|
// 「Esc ⇒ 先发 turn 级 halt;只有那一发连判决都拿不到、而屏上又确实挂着审批卡时,才升级成 run 级
|
|
382
396
|
// cancel」——TUI/desktop/web 三端都会 Esc、都会撞同一个 parked 格,判定本该在库里。此前整条住在
|