@sema-agent/client-core 0.74.2 → 0.74.4
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 +31 -0
- package/README.md +4 -1
- package/dist/adapt/arms.js +7 -1
- package/dist/adapt/panelTasks.d.ts +2 -1
- package/dist/adapt/panelTasks.js +8 -6
- package/dist/approvalsStreamLiveCapability.js +1 -1
- package/dist/decideReceipt.d.ts +15 -0
- package/dist/decideReceipt.js +21 -1
- package/dist/deviceExecutorManagementCapability.js +6 -2
- package/dist/engineAgentPanelStore.d.ts +60 -0
- package/dist/engineAgentPanelStore.js +363 -0
- package/dist/executionLaneCapability.js +1 -1
- package/dist/fleet/fleetProjection.d.ts +5 -0
- package/dist/fleet/fleetProjection.js +8 -0
- package/dist/fleetAgentPanelProjection.d.ts +2 -0
- package/dist/fleetAgentPanelProjection.js +7 -0
- package/dist/hitl/hitlBridge.d.ts +9 -0
- package/dist/hitl/hitlBridge.js +11 -2
- package/dist/hitl/suspendedReopen.d.ts +30 -0
- package/dist/hitl/suspendedReopen.js +27 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.js +6 -0
- package/dist/notifications.js +3 -2
- package/dist/runCancelContext.d.ts +49 -0
- package/dist/runCancelContext.js +91 -0
- package/dist/sqlEngineCapability.js +2 -2
- package/dist/webSearchBackendCapability.js +1 -1
- package/dist/writeProtectionCapability.js +2 -2
- package/docs/INTEGRATION-CLIENTS.md +107 -9
- package/package.json +1 -1
|
@@ -88,7 +88,366 @@ export function isStaleEngineAgentPanelEnd(end, current) {
|
|
|
88
88
|
return es < cs;
|
|
89
89
|
return false;
|
|
90
90
|
}
|
|
91
|
+
// ── 0.74.3 CC-70:子代身份归一(所有 lane 的唯一漏斗)────────────────────────────────────────
|
|
92
|
+
//
|
|
93
|
+
// 病(cli L-407 / test 定谳,三端同撞):同一个后台子代在 wire 上有**两把身份** —— fleet 行 id 尾段(17 位,= 注册表 task_id
|
|
94
|
+
// = 壳本地 agentId,core #948 按构造)与主流 `task_progress.taskId`(引擎 UUIDv7 = transcriptId = childSessionId)。本包的
|
|
95
|
+
// `fleet-row` / 通知 `end` 用前者,tick / 流内 close 臂的 `end` 用后者 ⇒ 壳面板按 `ev.taskId` 落 Map 长成两行;包内常驻标也是
|
|
96
|
+
// 「UUID 记、17 位清」永不清。修法 = 在这一只漏斗里把 tick / end 的 `taskId` 归一到 fleet 尾段(消费端零改):
|
|
97
|
+
// · 钥匙两把,fleet-row 一到就登记:`transcriptId → 尾段`(主)、`parentToolCallId → 尾段`(退路);
|
|
98
|
+
// · 查得到 ⇒ 改写 `taskId`,原词进 `wireTaskId`,`taskIdOrigin:'fleet-row'`;常驻标若记在 UUID 下随之迁移;
|
|
99
|
+
// · 查不到:绑卡的 tick(同步委派,`cardBound`)直发;未绑的 tick **缓一拍**等 fleet-row(常态 fleet-row 先到几十秒,但非保证)——
|
|
100
|
+
// 触发 = 同 UUID 的 fleet-row 到(归一发)/ 同 UUID 第二条 tick(新的原样发,旧的丢)/ 同 UUID `end`(先 tick 后 end 原样发)/
|
|
101
|
+
// 缓冲满(最旧的原样发);原样发的带 `taskIdOrigin:'wire'`。🔴 `sweep` **不放**缓着的 tick:那条 tick 还没有行,sweep 没有东西可结;
|
|
102
|
+
// 在「tick 先到、父 turn 先结、行帧后到」的窗里放出去反而把 UUID 行造出来、接着行帧再造第二行 —— 正是要修的病。
|
|
103
|
+
// · 两张钥匙表与缓一拍都有界(常量),溢出先进先出 —— 上限是内存约束不是语义,超出只退化成「原样发」;
|
|
104
|
+
// · 缓一拍还按**节拍**有界(异源复审 R1 [medium]):别的 fleet-row / end / sweep 每过一条算一拍(别的 tick 不算 —— 它说明不了行帧车道
|
|
105
|
+
// 活不活,一阵子代同时起步时也不该互相挤),过 MAX_HELD_WIRE_TICK_BEATS 拍还没等到自己的行帧 ⇒ 原样放(store 无计时器,拍是唯一可数的时间);
|
|
106
|
+
// · 钥匙带**周期身份**(复审 R1 [high]):行帧登记时连 cycleSeq / startedAt 一起记,归一后的 end 若自己不带周期就借钥匙上的
|
|
107
|
+
// (流内 close 臂的 end 恒不带)—— 复活后旧 transcriptId 的钥匙仍在,迟到的 end{旧 UUID} 归一后带着旧代际,
|
|
108
|
+
// isStaleEngineAgentPanelEnd 才判得出它是前代;
|
|
109
|
+
// · tick 经退路键(parentToolCallId)归一时,顺手把 UUID → 尾段 记进主表(复审 R1 [high]):随后的 end{UUID} 不带 parentToolCallId,
|
|
110
|
+
// 没有这一步它永远归不了一。
|
|
111
|
+
// · 🔴 **先发布的键为准**(0.74.4 CC-83;test [7702] N-3 证伪「零改」:tick 归不了一而原样发出之后,行帧再按尾段发 = 消费端仍两行):
|
|
112
|
+
// 一条 tick 已按 UUID 原样发出(第二拍 / 拍数耗尽 / 绑卡),这个子代在消费端的键就是 UUID —— 之后到的 fleet-row / fleet 侧 end
|
|
113
|
+
// 改写成 UUID(原词进 wireTaskId,taskIdOrigin:'wire'),不再把尾段当第二把键发出去。消费端从此在任何到达序上都只见一把键;
|
|
114
|
+
// 常态序(行帧先到)仍归一到尾段。
|
|
115
|
+
// · 🔴 前代 UUID 的迟到 tick **丢弃**(0.74.4 CC-84;test [7702] W2 tick 腿):钥匙上的周期比最近行帧旧 ⇒ 这条 progress 属已结束的
|
|
116
|
+
// 周期,归一发出去会把前代的累计数写进复活行;end 有陈旧判据(消费端按周期挡),tick 没有 ⇒ 在漏斗里挡。
|
|
117
|
+
/** 钥匙表上限(LRU:命中即续命,真在飞的子代不会被一阵新登记挤掉;超出只退化成「原样发」)。 */
|
|
118
|
+
const MAX_IDENTITY_KEYS = 2048;
|
|
119
|
+
const MAX_HELD_WIRE_TICKS = 64;
|
|
120
|
+
/** 缓一拍的节拍上限:别的 fleet-row / end / sweep 每过一条算一拍(别的 tick 不算)。 */
|
|
121
|
+
export const MAX_HELD_WIRE_TICK_BEATS = 16;
|
|
122
|
+
const fleetIdByTranscriptId = new Map();
|
|
123
|
+
const fleetIdByParentToolCallId = new Map();
|
|
124
|
+
const heldWireTicks = new Map();
|
|
125
|
+
/** 每个 fleet 尾段最近一条行帧带的周期身份(在场才记):清常驻标时用它判「这条终态是不是前代的」(异源复审 R3 [high])。 */
|
|
126
|
+
const latestCycleByFleetId = new Map();
|
|
127
|
+
/**
|
|
128
|
+
* 已按 UUID 原样发出过 tick 的子代(先发布的键为准,CC-83):transcriptId(= UUID)与 parentToolCallId 两把钥匙都记。
|
|
129
|
+
* 🔴 所有权表**按生命周期退役、不按容量改写**(异源复审 0.74.4 R1 [medium]):一把已发布的键在消费端就是一行,容量到了把它忘掉 =
|
|
130
|
+
* 同一子代重新长成两行。所以 `retired`(收到 end)的条目先让位;都没退役时才让最旧的(上限 MAX_OWNERSHIP_KEYS,远大于同时在飞的子代数)。
|
|
131
|
+
*/
|
|
132
|
+
const MAX_OWNERSHIP_KEYS = 8192;
|
|
133
|
+
const publishedWireIds = new Map();
|
|
134
|
+
const publishedWireByParent = new Map();
|
|
135
|
+
/** 尾段 → 已发布的 UUID(CC-83):fleet 侧的 fleet-row / end / 缺席 到时改写成 UUID;带登记时行帧的周期,复活的新周期不继承。 */
|
|
136
|
+
const wireIdByFleetId = new Map();
|
|
137
|
+
function bounded(map, key, value) {
|
|
138
|
+
map.delete(key);
|
|
139
|
+
map.set(key, value);
|
|
140
|
+
if (map.size > MAX_IDENTITY_KEYS) {
|
|
141
|
+
const oldest = map.keys().next().value;
|
|
142
|
+
if (oldest !== undefined)
|
|
143
|
+
map.delete(oldest);
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
/** 所有权表的有界:退役的先让位,都没退役才让最旧的。 */
|
|
147
|
+
function boundedOwnership(map, key, value) {
|
|
148
|
+
map.delete(key);
|
|
149
|
+
map.set(key, value);
|
|
150
|
+
if (map.size <= MAX_OWNERSHIP_KEYS)
|
|
151
|
+
return;
|
|
152
|
+
for (const [k, v] of map) {
|
|
153
|
+
if (v.retired) {
|
|
154
|
+
map.delete(k);
|
|
155
|
+
return;
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
const oldest = map.keys().next().value;
|
|
159
|
+
if (oldest !== undefined)
|
|
160
|
+
map.delete(oldest);
|
|
161
|
+
}
|
|
162
|
+
/** 一条 tick 按 UUID 原样发出去了:从此这个子代的键 = UUID。 */
|
|
163
|
+
function notePublishedWire(ev) {
|
|
164
|
+
// 已退役的键不因一条迟到 tick 复活(异源复审 R3 [high]):退役 = 它的行已结,迟到帧落在它自己的行上,所有权不回来
|
|
165
|
+
boundedOwnership(publishedWireIds, ev.taskId, { retired: publishedWireIds.get(ev.taskId)?.retired ?? false });
|
|
166
|
+
if (ev.parentToolCallId !== undefined && ev.parentToolCallId.length > 0)
|
|
167
|
+
bounded(publishedWireByParent, ev.parentToolCallId, ev.taskId);
|
|
168
|
+
}
|
|
169
|
+
/** 这把 UUID 的子代收到了终态:所有权可退役(容量满时先让位;不删 —— 迟到的同周期行帧 / end 仍要改写到它)。 */
|
|
170
|
+
function retireWire(wireId) {
|
|
171
|
+
const w = publishedWireIds.get(wireId);
|
|
172
|
+
if (w !== undefined)
|
|
173
|
+
w.retired = true;
|
|
174
|
+
for (const o of wireIdByFleetId.values())
|
|
175
|
+
if (o.wireId === wireId)
|
|
176
|
+
o.retired = true;
|
|
177
|
+
}
|
|
178
|
+
/** fleet-row 对应的子代是否已经按 UUID 发布过;transcriptId 命中不看退役(同一子代的迟到行帧),parentToolCallId 只认未退役(复活会沿用同一张卡)。 */
|
|
179
|
+
function publishedWireFor(ev) {
|
|
180
|
+
if (ev.transcriptId !== undefined && publishedWireIds.has(ev.transcriptId))
|
|
181
|
+
return ev.transcriptId;
|
|
182
|
+
if (ev.parentToolCallId !== undefined) {
|
|
183
|
+
const u = publishedWireByParent.get(ev.parentToolCallId);
|
|
184
|
+
if (u !== undefined && publishedWireIds.get(u)?.retired === false)
|
|
185
|
+
return u;
|
|
186
|
+
}
|
|
187
|
+
return undefined;
|
|
188
|
+
}
|
|
189
|
+
/** 尾段今天归哪把 UUID(在场 = 这个子代已按 UUID 上屏;复活的新周期不算)。 */
|
|
190
|
+
function wireOwnerOf(fleetId, current) {
|
|
191
|
+
const o = wireIdByFleetId.get(fleetId);
|
|
192
|
+
if (o === undefined)
|
|
193
|
+
return undefined;
|
|
194
|
+
if (current !== undefined && isStaleEngineAgentPanelEnd(o, current)) {
|
|
195
|
+
wireIdByFleetId.delete(fleetId); // 行帧已是更新的周期:旧 UUID 的所有权到此为止,新周期按常态登记
|
|
196
|
+
retireWire(o.wireId); // 旧周期已过:那把 UUID 退役,退路键(同一张卡)不再把新周期的行帧拉回它(异源复审 R2 [high])
|
|
197
|
+
return undefined;
|
|
198
|
+
}
|
|
199
|
+
return o.wireId;
|
|
200
|
+
}
|
|
201
|
+
function remember(map, key, alias) {
|
|
202
|
+
map.delete(key);
|
|
203
|
+
map.set(key, alias);
|
|
204
|
+
if (map.size > MAX_IDENTITY_KEYS) {
|
|
205
|
+
const oldest = map.keys().next().value;
|
|
206
|
+
if (oldest !== undefined)
|
|
207
|
+
map.delete(oldest);
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
/**
|
|
211
|
+
* 0.74.3 CC-70:把 wire 上的子代 id(task_progress 的 UUID)反查成 fleet 行 id 尾段;`transcriptId` 表优先,退 `parentToolCallId` 表;
|
|
212
|
+
* 都查不到 ⇒ 原样(`origin:'wire'`)。fleet 尾段本身传进来也原样(它就是目标键)。
|
|
213
|
+
*/
|
|
214
|
+
export function resolveEnginePanelTaskId(wireTaskId, parentToolCallId) {
|
|
215
|
+
const byTranscript = fleetIdByTranscriptId.get(wireTaskId);
|
|
216
|
+
if (byTranscript !== undefined) {
|
|
217
|
+
remember(fleetIdByTranscriptId, wireTaskId, byTranscript); // LRU 续命(异源复审 R2 [high]:FIFO 会把仍在飞的子代挤掉)
|
|
218
|
+
return identityOf(byTranscript);
|
|
219
|
+
}
|
|
220
|
+
const byParent = parentToolCallId !== undefined ? fleetIdByParentToolCallId.get(parentToolCallId) : undefined;
|
|
221
|
+
if (byParent !== undefined) {
|
|
222
|
+
remember(fleetIdByParentToolCallId, parentToolCallId, byParent);
|
|
223
|
+
return identityOf(byParent);
|
|
224
|
+
}
|
|
225
|
+
return { taskId: wireTaskId, origin: 'wire' };
|
|
226
|
+
}
|
|
227
|
+
function identityOf(alias) {
|
|
228
|
+
return {
|
|
229
|
+
taskId: alias.fleetId,
|
|
230
|
+
origin: 'fleet-row',
|
|
231
|
+
...(alias.cycleSeq !== undefined ? { cycleSeq: alias.cycleSeq } : {}),
|
|
232
|
+
...(alias.startedAt !== undefined ? { startedAt: alias.startedAt } : {}),
|
|
233
|
+
};
|
|
234
|
+
}
|
|
235
|
+
/** 经退路键归一的 tick:把 UUID → 尾段 记进主表,随后不带 parentToolCallId 的 end{UUID} 才归得了一。 */
|
|
236
|
+
function rememberResolvedWire(wireTaskId, id) {
|
|
237
|
+
if (fleetIdByTranscriptId.has(wireTaskId))
|
|
238
|
+
return;
|
|
239
|
+
remember(fleetIdByTranscriptId, wireTaskId, {
|
|
240
|
+
fleetId: id.taskId,
|
|
241
|
+
...(id.cycleSeq !== undefined ? { cycleSeq: id.cycleSeq } : {}),
|
|
242
|
+
...(id.startedAt !== undefined ? { startedAt: id.startedAt } : {}),
|
|
243
|
+
});
|
|
244
|
+
}
|
|
245
|
+
/** 别的事件过了一拍:缓着的 tick 各记一拍,过上限的原样放出(先进先出)。 */
|
|
246
|
+
function ageHeld(except) {
|
|
247
|
+
for (const [wireId, held] of [...heldWireTicks]) {
|
|
248
|
+
if (wireId === except)
|
|
249
|
+
continue;
|
|
250
|
+
held.beats += 1;
|
|
251
|
+
if (held.beats >= MAX_HELD_WIRE_TICK_BEATS) {
|
|
252
|
+
heldWireTicks.delete(wireId);
|
|
253
|
+
deliverEngineAgentPanelEvent(asIsTick(held.ev));
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
function migrateResidency(wireTaskId, fleetId) {
|
|
258
|
+
if (wireTaskId !== fleetId && residentTaskIds.has(wireTaskId)) {
|
|
259
|
+
residentTaskIds.delete(wireTaskId);
|
|
260
|
+
residentTaskIds.add(fleetId);
|
|
261
|
+
}
|
|
262
|
+
}
|
|
263
|
+
function normalizedTick(ev, fleetId) {
|
|
264
|
+
const { cardBound: _cardBound, ...rest } = ev;
|
|
265
|
+
void _cardBound;
|
|
266
|
+
migrateResidency(ev.taskId, fleetId);
|
|
267
|
+
return { ...rest, taskId: fleetId, wireTaskId: ev.taskId, taskIdOrigin: 'fleet-row' };
|
|
268
|
+
}
|
|
269
|
+
function asIsTick(ev) {
|
|
270
|
+
const { cardBound: _cardBound, ...rest } = ev;
|
|
271
|
+
void _cardBound;
|
|
272
|
+
notePublishedWire(ev);
|
|
273
|
+
return { ...rest, taskIdOrigin: 'wire' };
|
|
274
|
+
}
|
|
275
|
+
/** 钥匙上的周期比最近行帧旧 = 这条 tick 属已结束的周期(CC-84);判不出 ⇒ 不算陈旧。 */
|
|
276
|
+
function staleWireTick(id) {
|
|
277
|
+
return isStaleEngineAgentPanelEnd(id, latestCycleByFleetId.get(id.taskId));
|
|
278
|
+
}
|
|
279
|
+
function flushHeld(wireTaskId) {
|
|
280
|
+
const held = heldWireTicks.get(wireTaskId);
|
|
281
|
+
if (held === undefined)
|
|
282
|
+
return;
|
|
283
|
+
heldWireTicks.delete(wireTaskId);
|
|
284
|
+
if (publishedWireIds.has(held.ev.taskId)) {
|
|
285
|
+
deliverEngineAgentPanelEvent(asIsTick(held.ev));
|
|
286
|
+
return;
|
|
287
|
+
}
|
|
288
|
+
const id = resolveEnginePanelTaskId(held.ev.taskId, held.ev.parentToolCallId);
|
|
289
|
+
if (id.origin === 'fleet-row') {
|
|
290
|
+
if (staleWireTick(id))
|
|
291
|
+
return; // 前代周期的 progress:丢
|
|
292
|
+
rememberResolvedWire(held.ev.taskId, id);
|
|
293
|
+
deliverEngineAgentPanelEvent(normalizedTick(held.ev, id.taskId));
|
|
294
|
+
return;
|
|
295
|
+
}
|
|
296
|
+
deliverEngineAgentPanelEvent(asIsTick(held.ev));
|
|
297
|
+
}
|
|
298
|
+
/**
|
|
299
|
+
* 0.74.3 CC-70(异源复审 R3 [high]):按 wire 上的子代 id 清常驻标 —— 原键照清;归一到 fleet 尾段的那把键**只在这条终态属当代时**才清:
|
|
300
|
+
* 复活后迟到的前代终态(旧 UUID 的流内 close / 终态 tick)归一后带旧代际,清了尾段键 = 撤掉复活行的 sweep 保护 ⇒ 下一次 sweep 假结它。
|
|
301
|
+
* 判据与 `end` 的陈旧判据同一只(`isStaleEngineAgentPanelEnd`,拿钥匙上的周期对最近一条行帧的周期);判不出 ⇒ 照清(不因缺席留下永久常驻)。
|
|
302
|
+
* `cycle`(0.74.4):调用方(通知车道)带得出这条终态的周期时传进来 —— 尾段归某把 UUID 所有而通知比所有权旧 ⇒ 不清那把 UUID 的常驻标。
|
|
303
|
+
*/
|
|
304
|
+
export function clearEnginePanelTaskResidentByWire(wireTaskId, cycle) {
|
|
305
|
+
residentTaskIds.delete(wireTaskId);
|
|
306
|
+
const owner = wireIdByFleetId.get(wireTaskId); // 传进来的是尾段而尾段归某把 UUID 所有(通知车道)⇒ 那把键下的常驻标一起清
|
|
307
|
+
// 通知带周期(seq)且比所有权登记时的周期旧 = 前代的终态 ⇒ 不清当代 UUID 的常驻标(异源复审 R3 [high])
|
|
308
|
+
if (owner !== undefined && !(cycle !== undefined && isStaleEngineAgentPanelEnd(cycle, owner)))
|
|
309
|
+
residentTaskIds.delete(owner.wireId);
|
|
310
|
+
const id = resolveEnginePanelTaskId(wireTaskId);
|
|
311
|
+
if (id.taskId === wireTaskId)
|
|
312
|
+
return;
|
|
313
|
+
if (isStaleEngineAgentPanelEnd(id, latestCycleByFleetId.get(id.taskId)))
|
|
314
|
+
return;
|
|
315
|
+
residentTaskIds.delete(id.taskId);
|
|
316
|
+
}
|
|
317
|
+
/** 测试钩:清两张钥匙表、缓一拍与周期水位。 */
|
|
318
|
+
export function __resetEngineAgentPanelIdentityForTests() {
|
|
319
|
+
fleetIdByTranscriptId.clear();
|
|
320
|
+
fleetIdByParentToolCallId.clear();
|
|
321
|
+
heldWireTicks.clear();
|
|
322
|
+
latestCycleByFleetId.clear();
|
|
323
|
+
publishedWireIds.clear();
|
|
324
|
+
publishedWireByParent.clear();
|
|
325
|
+
wireIdByFleetId.clear();
|
|
326
|
+
}
|
|
327
|
+
/** 宿主的唯一发布口(三条 lane 都经这里):先归一身份,再进主通道(订阅者 / 缓冲)。 */
|
|
91
328
|
export function publishEngineAgentPanelEvent(ev) {
|
|
329
|
+
// 行帧先登记、先放自己匹配的 tick,再给别的缓着的 tick 记拍(异源复审 R2 [high]:先记拍会让第 16 拍那条正好是自己行帧的 tick 过期);
|
|
330
|
+
// end / sweep 没有「自己的」匹配 ⇒ 先记拍。
|
|
331
|
+
if (ev.kind === 'end' || ev.kind === 'sweep')
|
|
332
|
+
ageHeld(ev.kind === 'sweep' ? undefined : ev.taskId);
|
|
333
|
+
switch (ev.kind) {
|
|
334
|
+
case 'fleet-row': {
|
|
335
|
+
// 先发布的键为准(CC-83):这个子代已经按 UUID 上过屏 ⇒ 行帧改写成 UUID 发,并记 尾段 → UUID 供 fleet 侧 end 改写;不登记尾段钥匙。
|
|
336
|
+
const rowCycle = { ...(ev.cycleSeq !== undefined ? { cycleSeq: ev.cycleSeq } : {}), ...(ev.startedAt !== undefined ? { startedAt: ev.startedAt } : {}) };
|
|
337
|
+
const published = wireOwnerOf(ev.taskId, rowCycle) ?? publishedWireFor(ev);
|
|
338
|
+
if (published !== undefined && published !== ev.taskId) {
|
|
339
|
+
const prev = wireIdByFleetId.get(ev.taskId);
|
|
340
|
+
boundedOwnership(wireIdByFleetId, ev.taskId, { wireId: published, retired: prev?.wireId === published ? prev.retired : false, ...rowCycle });
|
|
341
|
+
deliverEngineAgentPanelEvent({ ...ev, taskId: published, wireTaskId: ev.taskId, taskIdOrigin: 'wire' });
|
|
342
|
+
ageHeld(undefined);
|
|
343
|
+
return;
|
|
344
|
+
}
|
|
345
|
+
const alias = {
|
|
346
|
+
fleetId: ev.taskId,
|
|
347
|
+
...(ev.cycleSeq !== undefined ? { cycleSeq: ev.cycleSeq } : {}),
|
|
348
|
+
...(ev.startedAt !== undefined ? { startedAt: ev.startedAt } : {}),
|
|
349
|
+
};
|
|
350
|
+
if (alias.cycleSeq !== undefined || alias.startedAt !== undefined) {
|
|
351
|
+
latestCycleByFleetId.delete(ev.taskId);
|
|
352
|
+
latestCycleByFleetId.set(ev.taskId, { ...(alias.cycleSeq !== undefined ? { cycleSeq: alias.cycleSeq } : {}), ...(alias.startedAt !== undefined ? { startedAt: alias.startedAt } : {}) });
|
|
353
|
+
if (latestCycleByFleetId.size > MAX_IDENTITY_KEYS) {
|
|
354
|
+
const oldest = latestCycleByFleetId.keys().next().value;
|
|
355
|
+
if (oldest !== undefined)
|
|
356
|
+
latestCycleByFleetId.delete(oldest);
|
|
357
|
+
}
|
|
358
|
+
}
|
|
359
|
+
if (ev.transcriptId !== undefined && ev.transcriptId.length > 0)
|
|
360
|
+
remember(fleetIdByTranscriptId, ev.transcriptId, alias);
|
|
361
|
+
if (ev.parentToolCallId !== undefined && ev.parentToolCallId.length > 0)
|
|
362
|
+
remember(fleetIdByParentToolCallId, ev.parentToolCallId, alias);
|
|
363
|
+
// 先发行帧再放缓着的 tick:tick 一到 fleet lane 就永久让位,行帧说的话(transcriptId / startedAt)得先落在行上。
|
|
364
|
+
deliverEngineAgentPanelEvent(ev);
|
|
365
|
+
for (const [wireId, held] of [...heldWireTicks]) {
|
|
366
|
+
if (wireId === ev.transcriptId || (held.ev.parentToolCallId !== undefined && held.ev.parentToolCallId === ev.parentToolCallId))
|
|
367
|
+
flushHeld(wireId);
|
|
368
|
+
}
|
|
369
|
+
ageHeld(undefined);
|
|
370
|
+
return;
|
|
371
|
+
}
|
|
372
|
+
case 'tick': {
|
|
373
|
+
// 先发布的键为准也管 tick 自己(异源复审 R2 [high]):这把 UUID 已经是消费端的键,之后的 tick 不再经退路键归一到尾段
|
|
374
|
+
//(否则复活后同一张卡的退路键把前代 UUID 的迟到 tick 拉进新周期的行)。
|
|
375
|
+
if (publishedWireIds.has(ev.taskId)) {
|
|
376
|
+
heldWireTicks.delete(ev.taskId);
|
|
377
|
+
deliverEngineAgentPanelEvent(asIsTick(ev));
|
|
378
|
+
return;
|
|
379
|
+
}
|
|
380
|
+
const id = resolveEnginePanelTaskId(ev.taskId, ev.parentToolCallId);
|
|
381
|
+
if (id.origin === 'fleet-row') {
|
|
382
|
+
heldWireTicks.delete(ev.taskId);
|
|
383
|
+
if (staleWireTick(id))
|
|
384
|
+
return; // CC-84:前代 UUID 的迟到 tick 不进复活行
|
|
385
|
+
rememberResolvedWire(ev.taskId, id);
|
|
386
|
+
deliverEngineAgentPanelEvent(normalizedTick(ev, id.taskId));
|
|
387
|
+
return;
|
|
388
|
+
}
|
|
389
|
+
if (ev.cardBound === true) {
|
|
390
|
+
deliverEngineAgentPanelEvent(asIsTick(ev));
|
|
391
|
+
return;
|
|
392
|
+
}
|
|
393
|
+
if (heldWireTicks.has(ev.taskId)) {
|
|
394
|
+
// 第二拍还没等到 fleet-row ⇒ 不再等:新的原样发,旧的丢(progress 合并语义,旧拍没有信息)。
|
|
395
|
+
heldWireTicks.delete(ev.taskId);
|
|
396
|
+
deliverEngineAgentPanelEvent(asIsTick(ev));
|
|
397
|
+
return;
|
|
398
|
+
}
|
|
399
|
+
if (heldWireTicks.size >= MAX_HELD_WIRE_TICKS) {
|
|
400
|
+
const oldest = heldWireTicks.keys().next().value;
|
|
401
|
+
if (oldest !== undefined)
|
|
402
|
+
flushHeld(oldest);
|
|
403
|
+
}
|
|
404
|
+
heldWireTicks.set(ev.taskId, { ev, beats: 0 });
|
|
405
|
+
return;
|
|
406
|
+
}
|
|
407
|
+
case 'end': {
|
|
408
|
+
// 先发布的键为准(CC-83):尾段已被改写成 UUID 的子代,fleet 侧(投影 / 通知)的 end{尾段} 也改写成 UUID
|
|
409
|
+
const wireOwner = wireOwnerOf(ev.taskId, ev);
|
|
410
|
+
if (wireOwner !== undefined) {
|
|
411
|
+
const ownerRec = wireIdByFleetId.get(ev.taskId);
|
|
412
|
+
deliverEngineAgentPanelEvent({ ...ev, taskId: wireOwner, wireTaskId: ev.taskId, taskIdOrigin: 'wire' });
|
|
413
|
+
// 前代迟到的 end(比所有权登记时的周期旧)只过境让消费端判陈旧,不退役当代所有权(异源复审 R3)
|
|
414
|
+
if (!isStaleEngineAgentPanelEnd(ev, ownerRec))
|
|
415
|
+
retireWire(wireOwner);
|
|
416
|
+
return;
|
|
417
|
+
}
|
|
418
|
+
if (publishedWireIds.has(ev.taskId)) {
|
|
419
|
+
// 这把 UUID 是先发布的键 ⇒ 它的 end 落在它自己的行上(不归一;一把已发布的键永远不改写)
|
|
420
|
+
flushHeld(ev.taskId);
|
|
421
|
+
deliverEngineAgentPanelEvent(ev);
|
|
422
|
+
retireWire(ev.taskId);
|
|
423
|
+
return;
|
|
424
|
+
}
|
|
425
|
+
const id = resolveEnginePanelTaskId(ev.taskId);
|
|
426
|
+
if (id.origin === 'fleet-row' && id.taskId !== ev.taskId) {
|
|
427
|
+
flushHeld(ev.taskId); // 同 UUID 还缓着一条 tick ⇒ 先归一放出(行先有最后一拍的数字与名字),再结
|
|
428
|
+
migrateResidency(ev.taskId, id.taskId);
|
|
429
|
+
deliverEngineAgentPanelEvent({
|
|
430
|
+
...ev,
|
|
431
|
+
taskId: id.taskId,
|
|
432
|
+
wireTaskId: ev.taskId,
|
|
433
|
+
taskIdOrigin: 'fleet-row',
|
|
434
|
+
// end 自己不带周期身份(流内 close 臂恒不带)⇒ 借钥匙上的:复活后迟到的 end{旧 UUID} 由此带着旧代际,消费端按 isStaleEngineAgentPanelEnd 挡掉
|
|
435
|
+
...(ev.cycleSeq === undefined && id.cycleSeq !== undefined ? { cycleSeq: id.cycleSeq } : {}),
|
|
436
|
+
...(ev.startedAt === undefined && id.startedAt !== undefined ? { startedAt: id.startedAt } : {}),
|
|
437
|
+
});
|
|
438
|
+
return;
|
|
439
|
+
}
|
|
440
|
+
flushHeld(ev.taskId); // 缓着的 tick 先落行,终态才有行可结
|
|
441
|
+
deliverEngineAgentPanelEvent(ev);
|
|
442
|
+
retireWire(ev.taskId); // 这把 UUID 若是先发布的键,所有权退役(容量满时先让位)
|
|
443
|
+
return;
|
|
444
|
+
}
|
|
445
|
+
default:
|
|
446
|
+
// sweep:缓着的 tick 不放(见头注)—— 原样进主通道。
|
|
447
|
+
deliverEngineAgentPanelEvent(ev);
|
|
448
|
+
}
|
|
449
|
+
}
|
|
450
|
+
function deliverEngineAgentPanelEvent(ev) {
|
|
92
451
|
// 同 taskId 的任何主通道事实(tick / fleet-row = 行在场;end = 真终态)都让**缓冲里**那条缺席过期 —— 迟订阅的消费者
|
|
93
452
|
// 不许先恢复了行、再被一条旧缺席标成 unknown(已递达订阅者的缺席由订阅者按契约在这些事件上撤标)。
|
|
94
453
|
if (ev.kind !== 'sweep')
|
|
@@ -239,6 +598,10 @@ let absenceListener = null;
|
|
|
239
598
|
const absenceBuffer = new Map();
|
|
240
599
|
/** 发布一条缺席上报(由 `projectFleetAgentRows` 的缺席路径调;消费者抛错不反噬投影)。 */
|
|
241
600
|
export function publishEngineAgentPanelAbsence(ev) {
|
|
601
|
+
// 0.74.4 CC-83(异源复审 R1 [medium]):尾段已归先发布的 UUID 所有 ⇒ 缺席也按那把键发(主通道与侧信道同一把键),原尾段进 wireTaskId
|
|
602
|
+
const owner = wireOwnerOf(ev.taskId);
|
|
603
|
+
if (owner !== undefined)
|
|
604
|
+
ev = { ...ev, taskId: owner, wireTaskId: ev.taskId };
|
|
242
605
|
if (absenceListener) {
|
|
243
606
|
try {
|
|
244
607
|
absenceListener(ev);
|
|
@@ -111,7 +111,7 @@ export function executionLaneDoctorDetail(reading) {
|
|
|
111
111
|
switch (reading.kind) {
|
|
112
112
|
case 'unobserved':
|
|
113
113
|
return ('execution lane not observed — the engine reports it on /v1/capabilities; ' +
|
|
114
|
-
'this process has
|
|
114
|
+
'this process has no usable capabilities reading cached for it (none received, or the last one was unreadable)');
|
|
115
115
|
case 'not_reported':
|
|
116
116
|
return ('execution lane not reported by this engine — only newer engines advertise it; ' +
|
|
117
117
|
'this does not say where tools run, so the client keeps inferring it the way it did before');
|
|
@@ -140,6 +140,11 @@ export interface FleetWorkflowView {
|
|
|
140
140
|
failedCount?: number;
|
|
141
141
|
/** 🔴 缺席 ≠ 0:0 = 引擎说「一个都没起」,undefined = 老引擎「不知道」(退 done+failed 下界)。 */
|
|
142
142
|
startedCount?: number;
|
|
143
|
+
/**
|
|
144
|
+
* 0.74.3 CC-72(core 7.23.5 #932;cli L-235 ②):run 级 errorCode **原词逐字**(开集,不枚举)。缺席 = 无码 ——
|
|
145
|
+
* **不是成功也不是失败**,终态仍看 `status`;server 透传 / sdk 声明之前按结构读(sdk 9.7.1 `FleetWorkflowRow` 未声明)。
|
|
146
|
+
*/
|
|
147
|
+
errorCode?: string;
|
|
143
148
|
}
|
|
144
149
|
/**
|
|
145
150
|
* SDK `FleetTaskStatus` 的终态词汇 —— 非终态即在飞(未知未来值按在飞算,保守)。
|
|
@@ -373,6 +373,7 @@ function wireNumberKey(key, v) {
|
|
|
373
373
|
export function projectWorkflows(rows) {
|
|
374
374
|
return rows.map(r => {
|
|
375
375
|
const startedCount = wireStartedCount(r);
|
|
376
|
+
const errorCode = wireErrorCode(r);
|
|
376
377
|
return {
|
|
377
378
|
id: r.id,
|
|
378
379
|
// 187 workflow label = `e.summary ?? e.description`(短 label);折成单行,空 → 187 占位符。
|
|
@@ -385,9 +386,15 @@ export function projectWorkflows(rows) {
|
|
|
385
386
|
...wireNumberKey('tokens', wireCount(r.tokens)),
|
|
386
387
|
...wireNumberKey('failedCount', wireCount(r.failedCount)),
|
|
387
388
|
...(startedCount !== undefined ? { startedCount } : {}),
|
|
389
|
+
...(errorCode !== undefined ? { errorCode } : {}),
|
|
388
390
|
};
|
|
389
391
|
});
|
|
390
392
|
}
|
|
393
|
+
/** CC-72:sdk 未声明的 run 级码,按结构读 —— 非空串才算在场;空串 / 非串 / null 一律缺席(不折串)。 */
|
|
394
|
+
function wireErrorCode(r) {
|
|
395
|
+
const v = r.errorCode;
|
|
396
|
+
return typeof v === 'string' && v.length > 0 ? v : undefined;
|
|
397
|
+
}
|
|
391
398
|
// ── 键集契约(`run-fleet-view-keys-test.mjs` 的运行期锚)─────────────────────────────────────
|
|
392
399
|
//
|
|
393
400
|
// 两跳链条,一跳都断不了(形与 `seatContract.ts` 的座位门同款):
|
|
@@ -436,6 +443,7 @@ const FLEET_WORKFLOW_VIEW_KEY_TUPLE = [
|
|
|
436
443
|
'tokens',
|
|
437
444
|
'failedCount',
|
|
438
445
|
'startedCount',
|
|
446
|
+
'errorCode',
|
|
439
447
|
];
|
|
440
448
|
/** `FleetWorkflowView` 的键名清单(同上)。 */
|
|
441
449
|
export const FLEET_WORKFLOW_VIEW_KEYS = FLEET_WORKFLOW_VIEW_KEY_TUPLE;
|
|
@@ -38,6 +38,8 @@ export interface FleetAgentRowLike {
|
|
|
38
38
|
startedAt?: number;
|
|
39
39
|
/** 0.74.0 —— 行的代际号(fleet 视图 `FleetTaskView.cycleSeq` 原样交)。缺席 = 这一行没有代际概念,不是第一代。 */
|
|
40
40
|
cycleSeq?: number;
|
|
41
|
+
/** 0.74.3 CC-70:行帧上的委派卡 id(sdk `FleetTaskRow.parentToolCallId`);身份归一的退路键,在场才投影。 */
|
|
42
|
+
parentToolCallId?: string;
|
|
41
43
|
}
|
|
42
44
|
/** 行从投影集消失多久之后上报 `absent`(0.72.12 起不再 settle;重连空窗 / snapshot 清表远小于此)。名字沿用(公面导出)。 */
|
|
43
45
|
export declare const ABSENT_SETTLE_MS = 30000;
|
|
@@ -141,6 +141,7 @@ export function projectFleetAgentRowsFor(sessionKey, rows, nowMs = Date.now()) {
|
|
|
141
141
|
// 🔴 三态直取:`row.toolUses` 缺席就是 undefined,这里**不许**兜 0(见 FleetAgentRowLike 注)。
|
|
142
142
|
const toolUses = typeof row.toolUses === 'number' ? row.toolUses : undefined;
|
|
143
143
|
const transcriptId = row.transcriptId || undefined;
|
|
144
|
+
const parentToolCallId = typeof row.parentToolCallId === 'string' && row.parentToolCallId.length > 0 ? row.parentToolCallId : undefined;
|
|
144
145
|
const startedAt = typeof row.startedAt === 'number' && row.startedAt > 0 ? row.startedAt : undefined;
|
|
145
146
|
// 0.74.0:代际号(正整数才算;坏形 / 缺席 ⇒ undefined,绝不补 1)。
|
|
146
147
|
const cycleSeq = typeof row.cycleSeq === 'number' && Number.isInteger(row.cycleSeq) && row.cycleSeq >= 1 ? row.cycleSeq : undefined;
|
|
@@ -191,6 +192,7 @@ export function projectFleetAgentRowsFor(sessionKey, rows, nowMs = Date.now()) {
|
|
|
191
192
|
(tokens !== undefined && prev?.tokens !== tokens) ||
|
|
192
193
|
(toolUses !== undefined && prev?.toolUses !== toolUses) ||
|
|
193
194
|
(transcriptId !== undefined && prev?.transcriptId !== transcriptId) ||
|
|
195
|
+
(parentToolCallId !== undefined && prev?.parentToolCallId !== parentToolCallId) ||
|
|
194
196
|
(startedAt !== undefined && prev?.startedAt !== startedAt)) {
|
|
195
197
|
publishEngineAgentPanelEvent({
|
|
196
198
|
kind: 'fleet-row',
|
|
@@ -200,6 +202,7 @@ export function projectFleetAgentRowsFor(sessionKey, rows, nowMs = Date.now()) {
|
|
|
200
202
|
...(knownTokens !== undefined ? { totalTokens: knownTokens } : {}),
|
|
201
203
|
...(toolUses !== undefined ? { toolUses } : {}),
|
|
202
204
|
...(transcriptId !== undefined ? { transcriptId } : {}),
|
|
205
|
+
...(parentToolCallId !== undefined ? { parentToolCallId } : {}),
|
|
203
206
|
...(knownStartedAt !== undefined ? { startedAt: knownStartedAt } : {}),
|
|
204
207
|
...(knownCycleSeq !== undefined ? { cycleSeq: knownCycleSeq } : {}),
|
|
205
208
|
});
|
|
@@ -221,6 +224,7 @@ export function projectFleetAgentRowsFor(sessionKey, rows, nowMs = Date.now()) {
|
|
|
221
224
|
tokens: knownTokens,
|
|
222
225
|
toolUses: toolUses !== undefined ? toolUses : prev?.toolUses,
|
|
223
226
|
transcriptId: transcriptId !== undefined ? transcriptId : prev?.transcriptId,
|
|
227
|
+
parentToolCallId: parentToolCallId !== undefined ? parentToolCallId : prev?.parentToolCallId,
|
|
224
228
|
startedAt: knownStartedAt,
|
|
225
229
|
cycleSeq: knownCycleSeq,
|
|
226
230
|
currentToolKey: currentToolKey !== undefined ? currentToolKey : prev?.currentToolKey,
|
|
@@ -243,6 +247,7 @@ export function projectFleetAgentRowsFor(sessionKey, rows, nowMs = Date.now()) {
|
|
|
243
247
|
prev.tokens !== knownTokens ||
|
|
244
248
|
(toolUses !== undefined && prev.toolUses !== toolUses) ||
|
|
245
249
|
(transcriptId !== undefined && prev.transcriptId !== transcriptId) ||
|
|
250
|
+
(parentToolCallId !== undefined && prev.parentToolCallId !== parentToolCallId) ||
|
|
246
251
|
(startedAt !== undefined && prev.startedAt !== startedAt) ||
|
|
247
252
|
(cycleSeq !== undefined && prev.cycleSeq !== cycleSeq) ||
|
|
248
253
|
(currentToolKey !== undefined && prev.currentToolKey !== currentToolKey)) {
|
|
@@ -255,6 +260,7 @@ export function projectFleetAgentRowsFor(sessionKey, rows, nowMs = Date.now()) {
|
|
|
255
260
|
// 🔴 缺席即不落键(消费端据「键在不在」判三态)。
|
|
256
261
|
...(toolUses !== undefined ? { toolUses } : {}),
|
|
257
262
|
...(transcriptId !== undefined ? { transcriptId } : {}),
|
|
263
|
+
...(parentToolCallId !== undefined ? { parentToolCallId } : {}),
|
|
258
264
|
...(knownStartedAt !== undefined ? { startedAt: knownStartedAt } : {}),
|
|
259
265
|
...(knownCycleSeq !== undefined ? { cycleSeq: knownCycleSeq } : {}),
|
|
260
266
|
...(currentTool !== undefined ? { currentTool } : {}),
|
|
@@ -266,6 +272,7 @@ export function projectFleetAgentRowsFor(sessionKey, rows, nowMs = Date.now()) {
|
|
|
266
272
|
tokens: knownTokens,
|
|
267
273
|
toolUses: toolUses !== undefined ? toolUses : prev?.toolUses,
|
|
268
274
|
transcriptId: transcriptId !== undefined ? transcriptId : prev?.transcriptId,
|
|
275
|
+
parentToolCallId: parentToolCallId !== undefined ? parentToolCallId : prev?.parentToolCallId,
|
|
269
276
|
startedAt: knownStartedAt,
|
|
270
277
|
cycleSeq: knownCycleSeq,
|
|
271
278
|
currentToolKey: currentToolKey !== undefined ? currentToolKey : prev?.currentToolKey,
|
|
@@ -67,6 +67,7 @@
|
|
|
67
67
|
*/
|
|
68
68
|
import type { AgentEvent, ApprovalDecision, ApprovalStaleError, PendingCheckpoint, CheckpointGate, PlanReviewRequest, AssistantTaskStatus } from '@sema-agent/sdk';
|
|
69
69
|
import type { ApprovalsListEnvelope } from './crashConverged.js';
|
|
70
|
+
import { type SuspendedReopenReading } from './suspendedReopen.js';
|
|
70
71
|
/** durable `/decide` 腿的既有缺省拒因(不带归因时逐字不变 —— 0.27.0 及之前的 wire 字节)。 */
|
|
71
72
|
export declare const DEFAULT_DENY_REASON = "The user rejected this tool use";
|
|
72
73
|
/**
|
|
@@ -300,6 +301,8 @@ export interface ActiveGate {
|
|
|
300
301
|
gate: CheckpointGate;
|
|
301
302
|
/** The durable seq of the `suspended` event, for rewind/dedup correlation (types.ts:48 eventSeq). */
|
|
302
303
|
seq?: string | undefined;
|
|
304
|
+
/** 0.74.3 CC-73:这条挂起是不是一次 reopen(`suspended.reopened` 三态,见 {@link suspendedReopenOf});每条 suspended 重读,不粘上一条。 */
|
|
305
|
+
reopened: SuspendedReopenReading;
|
|
303
306
|
}
|
|
304
307
|
/** Outcome of {@link findPendingForTask}. */
|
|
305
308
|
export type FindPendingOutcome = {
|
|
@@ -394,6 +397,12 @@ export declare class HitlBridge {
|
|
|
394
397
|
observe(ev: AgentEvent): void;
|
|
395
398
|
/** The gate currently awaiting a human, if any. The shell branches on `gate.kind` to pick the chrome. */
|
|
396
399
|
currentGate(): CheckpointGate | null;
|
|
400
|
+
/**
|
|
401
|
+
* 0.74.3 CC-73:当前挂起是不是一次 reopen(`suspended.reopened` 三态);没挂起 ⇒ `null`。
|
|
402
|
+
* 消费方据此把「真 reopen 成功、重试会重放」那半句说出来:`reopened` 在场码 ⇒ 卡仍 pending、决定没被消费、可再决;
|
|
403
|
+
* `not_reopened` / `unstated` ⇒ 普通挂起,一个字都不多说。
|
|
404
|
+
*/
|
|
405
|
+
currentGateReopen(): SuspendedReopenReading | null;
|
|
397
406
|
/**
|
|
398
407
|
* Fetch the `PendingCheckpoint` this decide/answer is about to resolve, for the callers that do NOT
|
|
399
408
|
* already have one in hand (see `decideTool`/`answerQuestion`'s `preResolvedPending` param — the wire
|
package/dist/hitl/hitlBridge.js
CHANGED
|
@@ -2,6 +2,7 @@ import { askParkForeignGateKind } from '../adapter/activeRunSelfHeal.js';
|
|
|
2
2
|
import { eventSeq } from '../adapter/types.js';
|
|
3
3
|
import { hostLog } from '../host.js';
|
|
4
4
|
import { abortableSleep } from '../abortableSleep.js';
|
|
5
|
+
import { suspendedReopenOf } from './suspendedReopen.js';
|
|
5
6
|
// ── deny/plan-review 归因的 wire 窄化(0.28.0 发版扫描 F1/F2/F3 收编;单源,三条腿共用)──────
|
|
6
7
|
//
|
|
7
8
|
// server 对 `/decide` 与 plan-review 两条腿的 `reason` 同限 `MAX_APPROVAL_REASON_CHARS`(4096 字符,
|
|
@@ -417,10 +418,10 @@ export class HitlBridge {
|
|
|
417
418
|
case 'suspended':
|
|
418
419
|
// gate may be null (Event_suspended 'null' arm) → a generic "needs you" pause (contract/08 CS-9).
|
|
419
420
|
if (ev.gate) {
|
|
420
|
-
this.active = { gate: ev.gate, seq: eventSeq(ev) };
|
|
421
|
+
this.active = { gate: ev.gate, seq: eventSeq(ev), reopened: suspendedReopenOf(ev) };
|
|
421
422
|
}
|
|
422
423
|
else {
|
|
423
|
-
this.active = { gate: { kind: 'human' }, seq: eventSeq(ev) };
|
|
424
|
+
this.active = { gate: { kind: 'human' }, seq: eventSeq(ev), reopened: suspendedReopenOf(ev) };
|
|
424
425
|
}
|
|
425
426
|
break;
|
|
426
427
|
case 'done':
|
|
@@ -439,6 +440,14 @@ export class HitlBridge {
|
|
|
439
440
|
currentGate() {
|
|
440
441
|
return this.active?.gate ?? null;
|
|
441
442
|
}
|
|
443
|
+
/**
|
|
444
|
+
* 0.74.3 CC-73:当前挂起是不是一次 reopen(`suspended.reopened` 三态);没挂起 ⇒ `null`。
|
|
445
|
+
* 消费方据此把「真 reopen 成功、重试会重放」那半句说出来:`reopened` 在场码 ⇒ 卡仍 pending、决定没被消费、可再决;
|
|
446
|
+
* `not_reopened` / `unstated` ⇒ 普通挂起,一个字都不多说。
|
|
447
|
+
*/
|
|
448
|
+
currentGateReopen() {
|
|
449
|
+
return this.active?.reopened ?? null;
|
|
450
|
+
}
|
|
442
451
|
/**
|
|
443
452
|
* Fetch the `PendingCheckpoint` this decide/answer is about to resolve, for the callers that do NOT
|
|
444
453
|
* already have one in hand (see `decideTool`/`answerQuestion`'s `preResolvedPending` param — the wire
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* suspendedReopen — durable `suspended` 事件上 server 的**重开侧记键** `reopened` 的三态读口(0.74.3 CC-73;
|
|
3
|
+
* 契约 reopen 族段:`GET /v1/runs/{taskId}/events` 的 `suspended` 事件带 `reopened: <errorCode>`,「缺席 / null =
|
|
4
|
+
* 这次 park 不是重开」;sdk `events.d.ts` `reopened?: string | null`,普通挂起帧不带此键)。
|
|
5
|
+
*
|
|
6
|
+
* ── 🔴 为什么是三态而不是「是 / 不是」两态 ─────────────────────────────────────────────────
|
|
7
|
+
* 契约把「缺席」和「null」判成同一句话,但两者的**说话人不同**:`null` 是新 server 明说「不是重开」;键缺席既可能是
|
|
8
|
+
* 新 server 的普通挂起帧,也可能是根本没有这个键的老 server。把两者折成一个值,消费方就再也分不出「server 说了不是」
|
|
9
|
+
* 与「server 什么都没说」。读口把它们分开带出,消费方按契约把 `unstated` **当作**不是重开即可,但不替 server 说那句话。
|
|
10
|
+
*
|
|
11
|
+
* 码是**开集原词**(词表属主在 server:今天三员 `resume.env_failed` / `resume.tool_unavailable` /
|
|
12
|
+
* `resume.tool_contract_mismatch`,见 `engineErrorCodes.ts` `RESUME_REOPEN_CODES`),本读口不过滤;要文案的消费方拿码去查表。
|
|
13
|
+
*/
|
|
14
|
+
/** `suspended.reopened` 的读数。 */
|
|
15
|
+
export type SuspendedReopenReading =
|
|
16
|
+
/** 键在场且是非空串:这条挂起是一次**重开**,`code` = server 给的 errorCode 原词。 */
|
|
17
|
+
{
|
|
18
|
+
readonly kind: 'reopened';
|
|
19
|
+
readonly code: string;
|
|
20
|
+
}
|
|
21
|
+
/** 键在场且显式 `null`:server 明说这次 park 不是重开。 */
|
|
22
|
+
| {
|
|
23
|
+
readonly kind: 'not_reopened';
|
|
24
|
+
}
|
|
25
|
+
/** 键缺席(或值读不出):server 没说;按契约当作不是重开,但不冒充 `null`。 */
|
|
26
|
+
| {
|
|
27
|
+
readonly kind: 'unstated';
|
|
28
|
+
};
|
|
29
|
+
/** 任意事件体 → 三态读数;非对象 / 坏形一律 `unstated`,永不抛。 */
|
|
30
|
+
export declare function suspendedReopenOf(ev: unknown): SuspendedReopenReading;
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* suspendedReopen — durable `suspended` 事件上 server 的**重开侧记键** `reopened` 的三态读口(0.74.3 CC-73;
|
|
3
|
+
* 契约 reopen 族段:`GET /v1/runs/{taskId}/events` 的 `suspended` 事件带 `reopened: <errorCode>`,「缺席 / null =
|
|
4
|
+
* 这次 park 不是重开」;sdk `events.d.ts` `reopened?: string | null`,普通挂起帧不带此键)。
|
|
5
|
+
*
|
|
6
|
+
* ── 🔴 为什么是三态而不是「是 / 不是」两态 ─────────────────────────────────────────────────
|
|
7
|
+
* 契约把「缺席」和「null」判成同一句话,但两者的**说话人不同**:`null` 是新 server 明说「不是重开」;键缺席既可能是
|
|
8
|
+
* 新 server 的普通挂起帧,也可能是根本没有这个键的老 server。把两者折成一个值,消费方就再也分不出「server 说了不是」
|
|
9
|
+
* 与「server 什么都没说」。读口把它们分开带出,消费方按契约把 `unstated` **当作**不是重开即可,但不替 server 说那句话。
|
|
10
|
+
*
|
|
11
|
+
* 码是**开集原词**(词表属主在 server:今天三员 `resume.env_failed` / `resume.tool_unavailable` /
|
|
12
|
+
* `resume.tool_contract_mismatch`,见 `engineErrorCodes.ts` `RESUME_REOPEN_CODES`),本读口不过滤;要文案的消费方拿码去查表。
|
|
13
|
+
*/
|
|
14
|
+
const UNSTATED = Object.freeze({ kind: 'unstated' });
|
|
15
|
+
const NOT_REOPENED = Object.freeze({ kind: 'not_reopened' });
|
|
16
|
+
/** 任意事件体 → 三态读数;非对象 / 坏形一律 `unstated`,永不抛。 */
|
|
17
|
+
export function suspendedReopenOf(ev) {
|
|
18
|
+
if (ev === null || typeof ev !== 'object' || Array.isArray(ev))
|
|
19
|
+
return UNSTATED;
|
|
20
|
+
const o = ev;
|
|
21
|
+
if (!Object.hasOwn(o, 'reopened'))
|
|
22
|
+
return UNSTATED;
|
|
23
|
+
const v = o.reopened;
|
|
24
|
+
if (v === null)
|
|
25
|
+
return NOT_REOPENED;
|
|
26
|
+
return typeof v === 'string' && v.length > 0 ? { kind: 'reopened', code: v } : UNSTATED;
|
|
27
|
+
}
|
package/dist/index.d.ts
CHANGED
|
@@ -161,6 +161,8 @@ export * from './deviceExecutorManagementCapability.js';
|
|
|
161
161
|
export * from './mcpReconnect.js';
|
|
162
162
|
export * from './leaderConflict.js';
|
|
163
163
|
export * from './runTerminal.js';
|
|
164
|
+
export * from './runCancelContext.js';
|
|
165
|
+
export * from './argvFlagValue.js';
|
|
164
166
|
export * from './readFacePosture.js';
|
|
165
167
|
export * from './mcpPanel.js';
|
|
166
168
|
export * from './effectiveFacts.js';
|
|
@@ -253,6 +255,7 @@ export * from './hooksWireCaps.js';
|
|
|
253
255
|
export * from './liveInitToolFace.js';
|
|
254
256
|
export * from './model/providerPresets.js';
|
|
255
257
|
export * from './hitl/hitlBridge.js';
|
|
258
|
+
export * from './hitl/suspendedReopen.js';
|
|
256
259
|
export { toolEndOutputText, ENGINE_ABORT_TOOL_RESULT } from './hitl/frameRouter.js';
|
|
257
260
|
export { isAskTool } from './hitl/frameRouter.js';
|
|
258
261
|
export * from './hitl/hitlHostSurface.js';
|