@sema-agent/client-core 0.74.5 → 0.75.1
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 +45 -0
- package/README.md +4 -3
- package/dist/adapt/arms.js +51 -5
- package/dist/adapt/panelTasks.d.ts +6 -2
- package/dist/adapt/panelTasks.js +6 -3
- package/dist/adapter/downstream/eventToSdkMessage.js +40 -11
- package/dist/agentSession/backgroundView.js +2 -1
- package/dist/deviceExecutorManagementCapability.d.ts +1 -1
- package/dist/deviceExecutorManagementCapability.js +1 -1
- package/dist/engineAgentPanelStore.d.ts +12 -1
- package/dist/engineAgentPanelStore.js +166 -18
- package/dist/fleet/fleetLedger.js +6 -0
- package/dist/fleet/fleetProjection.js +7 -2
- package/dist/fleet/fleetRowAgentType.d.ts +9 -0
- package/dist/fleet/fleetRowAgentType.js +59 -0
- package/dist/fleetAgentPanelProjection.js +63 -15
- package/dist/panelRunningHistory.d.ts +43 -2
- package/dist/panelRunningHistory.js +90 -3
- package/dist/seam.d.ts +27 -1
- package/dist/seam.js +5 -0
- package/docs/INTEGRATION-CLIENTS.md +104 -4
- package/package.json +3 -3
|
@@ -22,7 +22,7 @@
|
|
|
22
22
|
* Buffering: ticks published before the panel mounts are buffered (bounded) and replayed to the first
|
|
23
23
|
* subscriber, so early frames in a fast turn aren't lost to mount timing.
|
|
24
24
|
*/
|
|
25
|
-
import { retirePanelRowRunning } from './panelRunningHistory.js';
|
|
25
|
+
import { retirePanelRowRunning, notePanelRowEnded, panelRowEndedIdentity, peekHeldNewbornTerminalRow, takeHeldNewbornTerminalRow, dropHeldNewbornTerminalRow, heldNewbornTerminalRefs } from './panelRunningHistory.js';
|
|
26
26
|
const _panelToolUsesLanePins = [true, true];
|
|
27
27
|
void _panelToolUsesLanePins;
|
|
28
28
|
/**
|
|
@@ -331,7 +331,12 @@ export function clearEnginePanelTaskResidentByWire(wireTaskId, cycle) {
|
|
|
331
331
|
return;
|
|
332
332
|
residentTaskIds.delete(id.taskId);
|
|
333
333
|
}
|
|
334
|
-
/**
|
|
334
|
+
/**
|
|
335
|
+
* 测试钩。逐表清单(每加一张表同批更新这里):① 转录钥匙表(UUID → 行尾段)② 父调用钥匙表(parentToolCallId → 行尾段)
|
|
336
|
+
* ③ 缓着的未绑卡 tick ④ 每行最近一次行帧的周期水位 ⑤ 「先发布的键」集合(UUID)⑥ 同上按父调用记的那一份 ⑦ 反向所有权表(行尾段 → UUID)。
|
|
337
|
+
* 🔴 不清:订阅者 / 回放缓冲、常驻标、running 历史(那一本由 `__resetPanelRunningHistoryForTests` 清)。
|
|
338
|
+
* 🔴 连续场景(同一只子代跨多步的用例)**中途别调**:清掉 ④–⑦ 之后,陈旧判据与「先发布的键为准」都会从零开始,前后两半不再是同一段历史。
|
|
339
|
+
*/
|
|
335
340
|
export function __resetEngineAgentPanelIdentityForTests() {
|
|
336
341
|
fleetIdByTranscriptId.clear();
|
|
337
342
|
fleetIdByParentToolCallId.clear();
|
|
@@ -341,6 +346,87 @@ export function __resetEngineAgentPanelIdentityForTests() {
|
|
|
341
346
|
publishedWireByParent.clear();
|
|
342
347
|
wireIdByFleetId.clear();
|
|
343
348
|
}
|
|
349
|
+
/**
|
|
350
|
+
* 0.75.0 W2:一条 end 递达之前,先把投影缓着的「首见即终态」行放出来(它带这一行唯一的一份最终用量),经漏斗原路发布(身份归一照走);
|
|
351
|
+
* 递达之后记「本进程结过这一行」—— 投影据此把之后到的终态行当成短命子代的收尾(照发用量)而不是历史行。两步都只对当代 end(陈旧判据与销 running 历史同一只)。
|
|
352
|
+
*/
|
|
353
|
+
/**
|
|
354
|
+
* 这一条缓行**放出去会落在哪把消费端键上**(纯读口,不动表;与 fleet-row 臂的改写次序同序:
|
|
355
|
+
* 反向所有权 → 已发布的 wire 键 → 自己的行尾段)。第四条腿据它核「行与 end 是否同键」。
|
|
356
|
+
* 🔴 保守方向:所有权在表里就按它答(`wireOwnerOf` 遇到新一代会把它删掉再落到下一档 —— 那一档答错只会让本腿
|
|
357
|
+
* **少放**一次,不会让一只子代分成两行)。
|
|
358
|
+
*/
|
|
359
|
+
function heldRowConsumerKey(tail, held) {
|
|
360
|
+
const owner = wireIdByFleetId.get(tail);
|
|
361
|
+
if (owner !== undefined)
|
|
362
|
+
return owner.wireId;
|
|
363
|
+
return publishedWireFor(held) ?? tail;
|
|
364
|
+
}
|
|
365
|
+
/**
|
|
366
|
+
* 这一条 end 对那一行缓着的终态行的**代际三向**(纯读口,不动表)。先 peek 再决定:先 take 会让前代
|
|
367
|
+
* end 把当代唯一的缓行取走丢掉;
|
|
368
|
+
* · `'keep'` = end 可证比缓行**旧** ⇒ 缓行留着(等当代的那条 end);
|
|
369
|
+
* · `'drop'` = end 可证比缓行**新** ⇒ 缓行作废(前代的用量不许盖当代行);
|
|
370
|
+
* · `'release'` = 同代 / 判不出 ⇒ 可以放出;
|
|
371
|
+
* · `'none'` = 这一行没有缓着的终态行。
|
|
372
|
+
*/
|
|
373
|
+
function heldNewbornTerminalVerdict(taskId, endCycle) {
|
|
374
|
+
const held = peekHeldNewbornTerminalRow(taskId);
|
|
375
|
+
if (held === undefined || held.kind !== 'fleet-row')
|
|
376
|
+
return 'none';
|
|
377
|
+
const heldCycle = { ...(held.cycleSeq !== undefined ? { cycleSeq: held.cycleSeq } : {}), ...(held.startedAt !== undefined ? { startedAt: held.startedAt } : {}) };
|
|
378
|
+
if (isStaleEngineAgentPanelEnd(endCycle, heldCycle))
|
|
379
|
+
return 'keep';
|
|
380
|
+
if (isStaleEngineAgentPanelEnd(heldCycle, endCycle))
|
|
381
|
+
return 'drop';
|
|
382
|
+
return 'release';
|
|
383
|
+
}
|
|
384
|
+
/**
|
|
385
|
+
* 🔴 放行前的**同键核**:这一条缓行放出去,行帧与随后那条 end 会不会落在**同一把消费端键**上?
|
|
386
|
+
*
|
|
387
|
+
* 反例(实测):一只子代的委派卡被复用 —— `tick{V, 卡 P}` 已按 V 上过屏;缓着的终态行是
|
|
388
|
+
* `{尾段 X, 转录锚 U, 卡 P}`;来的是 `end{U}`。第四条腿按转录锚找到唯一候选 X,可它一放行就被
|
|
389
|
+
* 「先发布的键为准」经卡 P 改写到 **V** 上,而这条 `end{U}` 在主路上谁也不命中、原样落在 **U**:
|
|
390
|
+
* 用量记到了 V,X 的终态却发给了 U,消费端还可能凭 `end{U}` 另开一行。
|
|
391
|
+
*
|
|
392
|
+
* 所以只在两种形下放行(其余留着缓行,等真正属于它的那条 end —— 宁可晚交一份用量,不让一只子代分成两行):
|
|
393
|
+
* ① 行会被改写到**这条 end 自己那把键**上(随后主路也落在同一把键,`ownedFleetIds` 连带结账);
|
|
394
|
+
* ② 行按**自己的尾段**上屏,而这条 end 的键正是它的转录锚且那把键没被发布过 —— 放行登记了转录钥匙,
|
|
395
|
+
* 主路随即经钥匙表归一到同一条尾段。
|
|
396
|
+
*/
|
|
397
|
+
function heldRowReleasesOntoSameKey(tail, end) {
|
|
398
|
+
const held = peekHeldNewbornTerminalRow(tail);
|
|
399
|
+
if (held === undefined || held.kind !== 'fleet-row')
|
|
400
|
+
return false;
|
|
401
|
+
const rowKey = heldRowConsumerKey(tail, held);
|
|
402
|
+
if (rowKey === end.taskId)
|
|
403
|
+
return true;
|
|
404
|
+
// 末一项 `!publishedWireIds.has(...)` 是**可证冗余**的写方守卫(删掉行为不变:若这把键发布过,
|
|
405
|
+
// 上面的 `heldRowConsumerKey` 就会把 rowKey 算成它、第一档已经返回 true,走不到这里)。留着是把
|
|
406
|
+
// 「② 只成立于那把键没上过屏」这句话钉在代码里,别当成活判据去删第一档。
|
|
407
|
+
return rowKey === tail && held.transcriptId === end.taskId && !publishedWireIds.has(end.taskId);
|
|
408
|
+
}
|
|
409
|
+
function releaseHeldNewbornTerminal(taskId, endCycle) {
|
|
410
|
+
const verdict = heldNewbornTerminalVerdict(taskId, endCycle);
|
|
411
|
+
if (verdict === 'drop') {
|
|
412
|
+
dropHeldNewbornTerminalRow(taskId);
|
|
413
|
+
return;
|
|
414
|
+
}
|
|
415
|
+
if (verdict !== 'release')
|
|
416
|
+
return;
|
|
417
|
+
const held = takeHeldNewbornTerminalRow(taskId);
|
|
418
|
+
if (held !== undefined)
|
|
419
|
+
publishEngineAgentPanelEvent(held);
|
|
420
|
+
}
|
|
421
|
+
/** 结算(end 已递达):running 历史销、ended 账单调写(可证比已结周期旧的 end 不倒写)。 */
|
|
422
|
+
function settleRowHistory(taskId, cycle) {
|
|
423
|
+
retirePanelRowRunning(taskId);
|
|
424
|
+
notePanelRowEnded(taskId, cycle, isStaleEngineAgentPanelEnd(cycle, panelRowEndedIdentity(taskId)));
|
|
425
|
+
}
|
|
426
|
+
/** end 的陈旧判据同时参考行帧水位与 ended 账(短命子代常常没有行帧水位 —— 异源复审 R3 [high])。 */
|
|
427
|
+
function endOlderThanKnown(end, taskId, rowCycle) {
|
|
428
|
+
return isStaleEngineAgentPanelEnd(end, rowCycle) || isStaleEngineAgentPanelEnd(end, panelRowEndedIdentity(taskId));
|
|
429
|
+
}
|
|
344
430
|
/** 宿主的唯一发布口(三条 lane 都经这里):先归一身份,再进主通道(订阅者 / 缓冲)。 */
|
|
345
431
|
export function publishEngineAgentPanelEvent(ev) {
|
|
346
432
|
// 行帧先登记、先放自己匹配的 tick,再给别的缓着的 tick 记拍(异源复审 R2 [high]:先记拍会让第 16 拍那条正好是自己行帧的 tick 过期);
|
|
@@ -422,33 +508,93 @@ export function publishEngineAgentPanelEvent(ev) {
|
|
|
422
508
|
return;
|
|
423
509
|
}
|
|
424
510
|
case 'end': {
|
|
511
|
+
// 0.75.0 W2:缓着的「首见即终态」行**先于一切归一**放出(异源复审 R3 [high]:放出的行帧会登记钥匙 / 所有权,之后的 end 归一必须看到它,
|
|
512
|
+
// 否则同一子代长成 UUID 行 + 尾段行两行)。
|
|
513
|
+
// 🔴 0.75.1 —— 前三把键的**真实射程**(实测反例):尾段本身 / UUID 经钥匙解析到的尾段 / 这把 UUID 名下的反向所有权尾段,
|
|
514
|
+
// 后两把**只在行帧真发布过时才登记**,而缓行的定义就是「还没发布」⇒ `tick{UUID}` 先上屏、终态行 `{尾段, transcriptId: UUID}`
|
|
515
|
+
// 被缓住时,这三把全查不到,缓行永远放不出。所以有第四条腿:按缓行**自带的** wire 侧键(transcriptId / 退路键)反查。
|
|
516
|
+
releaseHeldNewbornTerminal(ev.taskId, ev);
|
|
517
|
+
{
|
|
518
|
+
const pre = resolveEnginePanelTaskId(ev.taskId);
|
|
519
|
+
if (pre.origin === 'fleet-row' && pre.taskId !== ev.taskId) {
|
|
520
|
+
releaseHeldNewbornTerminal(pre.taskId, {
|
|
521
|
+
...(ev.cycleSeq !== undefined ? { cycleSeq: ev.cycleSeq } : pre.cycleSeq !== undefined ? { cycleSeq: pre.cycleSeq } : {}),
|
|
522
|
+
...(ev.startedAt !== undefined ? { startedAt: ev.startedAt } : pre.startedAt !== undefined ? { startedAt: pre.startedAt } : {}),
|
|
523
|
+
});
|
|
524
|
+
}
|
|
525
|
+
for (const [fleetId, o] of wireIdByFleetId)
|
|
526
|
+
if (o.wireId === ev.taskId && !o.retired)
|
|
527
|
+
releaseHeldNewbornTerminal(fleetId, ev);
|
|
528
|
+
// 第四条腿:缓着的行自带 `transcriptId`(主键)与委派卡 id(退路键)—— 两把与 `publishedWireFor` 的**同一条律**:
|
|
529
|
+
// 转录键命中不看退役(同一子代的迟到终态);退路键只认**未退役**的那把 wire 键(复活沿用同一张卡)。
|
|
530
|
+
// 放行仍走 `releaseHeldNewbornTerminal`(代际三向 peek + 经原漏斗归一),所以前代的缓行会被当代终态作废而不是盖上去。
|
|
531
|
+
const heldMatches = [];
|
|
532
|
+
for (const ref of heldNewbornTerminalRefs()) {
|
|
533
|
+
if (ref.taskId === ev.taskId)
|
|
534
|
+
continue; // 第一条腿已经试过这把键
|
|
535
|
+
const byTranscript = ref.transcriptId !== undefined && ref.transcriptId === ev.taskId;
|
|
536
|
+
const byParent = ref.parentToolCallId !== undefined &&
|
|
537
|
+
publishedWireByParent.get(ref.parentToolCallId) === ev.taskId &&
|
|
538
|
+
publishedWireIds.get(ev.taskId)?.retired === false;
|
|
539
|
+
if (byTranscript || byParent)
|
|
540
|
+
heldMatches.push(ref.taskId);
|
|
541
|
+
}
|
|
542
|
+
// 命中之后**先逐条按代际三向判、不动表**,再决定放谁:
|
|
543
|
+
// · 可证更旧的缓行(前代那一行)⇒ 当场作废(它的用量不许盖当代);
|
|
544
|
+
// · end 可证更旧 ⇒ 那条缓行留着,等属于它自己的那条 end;
|
|
545
|
+
// · 剩下的「可放行」候选**恰一条** ⇒ 放行 —— 它一上屏就登记了自己的钥匙 / 所有权,本条 end 随后在主路上
|
|
546
|
+
// 归一到同一把键,所以仍是「一行一条 end」。
|
|
547
|
+
// · 🔴 候选 **≥2**(两个行尾段自称同一个转录锚 / 同一张委派卡)= **身份两义 ⇒ 一条都不放**:转录钥匙表是
|
|
548
|
+
// **单值**的(后写覆盖先写),放两条就会各自上屏成两行而主路只归一到最后那一条 ⇒ 前面那些行永远收不到结算;
|
|
549
|
+
// 换成「每行各自补一条 end」则要在这里替主路决定消费端键,会吞掉原 wire 键那一行自己的终态。身份对不上就
|
|
550
|
+
// **一帧都不摘**是本包扣留表的既有律(见 `hitl/gateLedger.ts` 的「判不出 ⇒ 一条都不摘」)。代价 = 这种形下
|
|
551
|
+
// 那几条行的用量仍缺席(与本件修之前同),换来的是绝不凭空多长一行、绝不替谁编一条终态。
|
|
552
|
+
// 真字节里两个行尾段共享一个转录锚不成立(转录锚按代际唯一),所以这一支是防御面,不是常态路径。
|
|
553
|
+
// 🔴 无论放没放,**主路照走**:原 wire 键那一行(它可能早就上过屏)自己的 end 绝不能被这条腿吞掉。
|
|
554
|
+
const releasable = [];
|
|
555
|
+
for (const tail of heldMatches) {
|
|
556
|
+
const verdict = heldNewbornTerminalVerdict(tail, ev);
|
|
557
|
+
if (verdict === 'drop')
|
|
558
|
+
dropHeldNewbornTerminalRow(tail);
|
|
559
|
+
else if (verdict === 'release' && heldRowReleasesOntoSameKey(tail, ev))
|
|
560
|
+
releasable.push(tail);
|
|
561
|
+
}
|
|
562
|
+
if (releasable.length === 1)
|
|
563
|
+
releaseHeldNewbornTerminal(releasable[0], ev);
|
|
564
|
+
}
|
|
425
565
|
// 先发布的键为准(CC-83):尾段已被改写成 UUID 的子代,fleet 侧(投影 / 通知)的 end{尾段} 也改写成 UUID
|
|
426
566
|
const wireOwner = wireOwnerOf(ev.taskId, ev);
|
|
427
567
|
if (wireOwner !== undefined) {
|
|
428
568
|
const ownerRec = wireIdByFleetId.get(ev.taskId);
|
|
569
|
+
const staleA = endOlderThanKnown(ev, ev.taskId, ownerRec) || isStaleEngineAgentPanelEnd(ev, panelRowEndedIdentity(wireOwner));
|
|
429
570
|
deliverEngineAgentPanelEvent({ ...ev, taskId: wireOwner, wireTaskId: ev.taskId, taskIdOrigin: 'wire' });
|
|
430
571
|
// 前代迟到的 end(比所有权登记时的周期旧)只过境让消费端判陈旧,不退役当代所有权(异源复审 R3)、不销当代 running 历史
|
|
431
|
-
if (!
|
|
572
|
+
if (!staleA) {
|
|
432
573
|
retireWire(wireOwner);
|
|
433
|
-
|
|
434
|
-
|
|
574
|
+
settleRowHistory(ev.taskId, ev);
|
|
575
|
+
settleRowHistory(wireOwner, ev);
|
|
435
576
|
}
|
|
436
577
|
return;
|
|
437
578
|
}
|
|
438
579
|
if (publishedWireIds.has(ev.taskId)) {
|
|
439
580
|
// 这把 UUID 是先发布的键 ⇒ 它的 end 落在它自己的行上(不归一;一把已发布的键永远不改写)
|
|
440
581
|
flushHeld(ev.taskId);
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
retirePanelRowRunning(ev.taskId);
|
|
444
|
-
// 这把 UUID 名下的尾段(反向所有权 / 转录别名)在投影里以尾段记的 running 历史一起销(复审 R3 [medium]),各自带周期守卫
|
|
582
|
+
// 这把 UUID 名下的尾段(反向所有权 / 转录别名)在投影里以尾段记账(复审 R3 [medium]),各自带周期守卫:先放缓着的终态行,end 之后再销
|
|
583
|
+
const ownedFleetIds = [];
|
|
445
584
|
for (const [fleetId, o] of wireIdByFleetId) {
|
|
446
|
-
if (o.wireId === ev.taskId && !
|
|
447
|
-
|
|
585
|
+
if (o.wireId === ev.taskId && !endOlderThanKnown(ev, fleetId, o))
|
|
586
|
+
ownedFleetIds.push(fleetId);
|
|
448
587
|
}
|
|
449
588
|
const alias = fleetIdByTranscriptId.get(ev.taskId);
|
|
450
|
-
if (alias !== undefined && !
|
|
451
|
-
|
|
589
|
+
if (alias !== undefined && !endOlderThanKnown(ev, alias.fleetId, alias) && !ownedFleetIds.includes(alias.fleetId))
|
|
590
|
+
ownedFleetIds.push(alias.fleetId);
|
|
591
|
+
deliverEngineAgentPanelEvent(ev);
|
|
592
|
+
retireWire(ev.taskId);
|
|
593
|
+
// UUID 自己的 ended 账也带守卫(end(U,2) 之后的 end(U,1) 不倒写)
|
|
594
|
+
if (!isStaleEngineAgentPanelEnd(ev, panelRowEndedIdentity(ev.taskId)))
|
|
595
|
+
settleRowHistory(ev.taskId, ev);
|
|
596
|
+
for (const fleetId of ownedFleetIds)
|
|
597
|
+
settleRowHistory(fleetId, ev);
|
|
452
598
|
return;
|
|
453
599
|
}
|
|
454
600
|
const id = resolveEnginePanelTaskId(ev.taskId);
|
|
@@ -461,8 +607,7 @@ export function publishEngineAgentPanelEvent(ev) {
|
|
|
461
607
|
...(ev.cycleSeq !== undefined ? { cycleSeq: ev.cycleSeq } : id.cycleSeq !== undefined ? { cycleSeq: id.cycleSeq } : {}),
|
|
462
608
|
...(ev.startedAt !== undefined ? { startedAt: ev.startedAt } : id.startedAt !== undefined ? { startedAt: id.startedAt } : {}),
|
|
463
609
|
};
|
|
464
|
-
|
|
465
|
-
retirePanelRowRunning(id.taskId);
|
|
610
|
+
const staleC = endOlderThanKnown(effectiveEnd, id.taskId, latestCycleByFleetId.get(id.taskId));
|
|
466
611
|
deliverEngineAgentPanelEvent({
|
|
467
612
|
...ev,
|
|
468
613
|
taskId: id.taskId,
|
|
@@ -472,14 +617,17 @@ export function publishEngineAgentPanelEvent(ev) {
|
|
|
472
617
|
...(ev.cycleSeq === undefined && id.cycleSeq !== undefined ? { cycleSeq: id.cycleSeq } : {}),
|
|
473
618
|
...(ev.startedAt === undefined && id.startedAt !== undefined ? { startedAt: id.startedAt } : {}),
|
|
474
619
|
});
|
|
620
|
+
if (!staleC)
|
|
621
|
+
settleRowHistory(id.taskId, effectiveEnd);
|
|
475
622
|
return;
|
|
476
623
|
}
|
|
477
624
|
flushHeld(ev.taskId); // 缓着的 tick 先落行,终态才有行可结
|
|
625
|
+
// 通知车道 / fleet 投影铸的 end(尾段键):只对当代(前代迟到的 end 带旧 seq / cycleSeq ⇒ 不擦历史、不放缓着的行)
|
|
626
|
+
const staleD = endOlderThanKnown(ev, ev.taskId, latestCycleByFleetId.get(ev.taskId));
|
|
478
627
|
deliverEngineAgentPanelEvent(ev);
|
|
479
628
|
retireWire(ev.taskId); // 这把 UUID 若是先发布的键,所有权退役(容量满时先让位)
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
retirePanelRowRunning(ev.taskId);
|
|
629
|
+
if (!staleD)
|
|
630
|
+
settleRowHistory(ev.taskId, ev);
|
|
483
631
|
return;
|
|
484
632
|
}
|
|
485
633
|
default:
|
|
@@ -7,6 +7,7 @@ import { enqueueBgChildNotification, isOwnWorkflowRun } from '../notifications.j
|
|
|
7
7
|
import { clearBgTerminalFacts, getBgTerminalFacts, isOwnEngineRun, recordBgParentRun, recordBgTerminalFacts, recordOwnEngineRun, } from '../subagentContentStore.js';
|
|
8
8
|
import { rowIdTail } from '../workflow.js';
|
|
9
9
|
import { recordEngineTranscriptId } from '../subagent/engineDelegatedPrompt.js';
|
|
10
|
+
import { recordFleetRowAgentType } from './fleetRowAgentType.js';
|
|
10
11
|
import { DEFAULT_SESSION_KEY } from '../sessionSlot.js';
|
|
11
12
|
import { TERMINAL_FLEET_TASK_STATUSES, projectTasks, projectWorkflows, wireCycleSeq, wireParentId, } from './fleetProjection.js';
|
|
12
13
|
/** 终态行留存宽限窗(187 的 agents 页把完成会话留在列表;窗过即净,防长会话堆行)。 */
|
|
@@ -345,6 +346,11 @@ export function createFleetLedger(hooks = {}, opts = {}) {
|
|
|
345
346
|
if (typeof row.transcriptId === 'string' && row.transcriptId.length > 0) {
|
|
346
347
|
recordEngineTranscriptId(rowIdTail(row.id), row.transcriptId);
|
|
347
348
|
}
|
|
349
|
+
// 0.75.1:`agentType`(§K-7 短类型 label)在 `projectTasks` 里会被**折进** `name`
|
|
350
|
+
// (`deriveAgentLabel` = agentName ?? agentType),折完就与显示名分不开了 —— 而 `subagent_lifecycle`
|
|
351
|
+
// 的 `agentType` 需要的正是折之前那一位。所以在这里(折之前)按行 id 尾段留一份;缺席 / 坏形不入账
|
|
352
|
+
// (读口答缺席,tick 臂让键缺席,绝不拿展示名冒充)。写方只有这一处,与转录锚台账同族同作用域。
|
|
353
|
+
recordFleetRowAgentType(rowIdTail(row.id), row.agentType);
|
|
348
354
|
// 终态帧 → 留存池(remove 同毫秒尾随,probe 实证;留存让 completed band 数得到它)。
|
|
349
355
|
// REF-CC-040(fleet2-02):非终态分支必须显式 delete —— 否则 bg 复活(SendMessage 后
|
|
350
356
|
// seq+1)时,`project()` 因 `taskMap.has(id)` 恒 continue 而永不清理过期留存条目,belt
|
|
@@ -341,7 +341,8 @@ export function projectTasks(allRows, opts) {
|
|
|
341
341
|
...(transcriptId !== undefined ? { transcriptId } : {}),
|
|
342
342
|
// launch 真锚原值透传(> 0 才算有值)。ledger 拿它算 elapsedMs 是**另一件事** —— 下游要的是
|
|
343
343
|
// 时刻本身(自己的钟去减),elapsedMs 是发帧那一刻的快照,客户端存着会漂([C36] server 判据)。
|
|
344
|
-
|
|
344
|
+
// launch 锚只认**有限正数**;非有限值按缺席(它序列化成 `null` 发出去 = 一个不存在的 launch 时刻)。
|
|
345
|
+
...(typeof r.startedAt === 'number' && Number.isFinite(r.startedAt) && r.startedAt > 0 ? { startedAt: r.startedAt } : {}),
|
|
345
346
|
...(currentTool !== undefined ? { currentTool } : {}),
|
|
346
347
|
...(parentToolCallId !== undefined ? { parentToolCallId } : {}),
|
|
347
348
|
...(stoppedBy !== undefined ? { stoppedBy } : {}),
|
|
@@ -511,7 +512,11 @@ export const FLEET_TASK_ROW_KEYS_NOT_PROJECTED = {
|
|
|
511
512
|
agentType: {
|
|
512
513
|
kind: 'by-design',
|
|
513
514
|
owner: 'client-core 规范重构轮',
|
|
514
|
-
reason: '折进 `name`(deriveAgentLabel:agentName ?? agentType,都缺才退 objective 派生)
|
|
515
|
+
reason: '折进 `name`(deriveAgentLabel:agentName ?? agentType,都缺才退 objective 派生)——渲染契约只有一个身份列,与上游的身份选择器同形。' +
|
|
516
|
+
'🔴 0.75.1 CC-95(b) 补一句射程:**本视图**仍不带它(渲染面一个身份列不变),但它不再只被折掉 —— ' +
|
|
517
|
+
'`fleet/fleetLedger` 的行帧臂在折之前把它记进 `fleet/fleetRowAgentType`(包内叶),adapt 的 tick 臂据此给 ' +
|
|
518
|
+
'`subagent_lifecycle.agentType` 取诚实来源(此前取的是展示名 = 委派卡 description 派生的串)。' +
|
|
519
|
+
'所以这条是「不进本视图」,不是「零消费者」;哪天渲染面要第二个身份列,再把它投影出来并删本条。',
|
|
515
520
|
},
|
|
516
521
|
agentName: {
|
|
517
522
|
kind: 'by-design',
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 记一条 fleet 行的 agentType(幂等;行帧不带这一位 / 坏形 ⇒ 整条不动,不擦掉已知值 ——
|
|
3
|
+
* 同一行的后续帧缺席这一位是「这帧没说」,不是「类型没了」)。
|
|
4
|
+
*/
|
|
5
|
+
export declare function recordFleetRowAgentType(taskIdTail: string, agentType: string | undefined): void;
|
|
6
|
+
/** 读口:这一行的子代类型;`undefined` = **诚实不知道**(调用方让键缺席,不许回落到展示名)。 */
|
|
7
|
+
export declare function fleetRowAgentType(taskIdTail: string | undefined): string | undefined;
|
|
8
|
+
/** 测试钩:清台账(module 级单例,同进程多组断言必须能清)。 */
|
|
9
|
+
export declare function __resetFleetRowAgentTypeForTests(): void;
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* fleetRowAgentType — 「这一条 fleet 行的子代是什么**类型**」的台账(0.75.1 CC-95(b);包内叶,不出公面)。
|
|
3
|
+
*
|
|
4
|
+
* ── 为什么要有它 ──────────────────────────────────────────────────────────────────────────────
|
|
5
|
+
* `subagent_lifecycle` 的 `agentType`(CC `SubagentStart.agent_type` 那一位)此前取的是 tick 的**展示名**
|
|
6
|
+
* `task_progress.name` —— 那一位是上游由委派卡的 `description` 派生的**显示串**,不是类型。于是宿主的
|
|
7
|
+
* SubagentStart hook 收到的是任务描述:一个看起来合理的错答案。
|
|
8
|
+
*
|
|
9
|
+
* 诚实来源亲核(sdk 9.8.1 实装字节):
|
|
10
|
+
* · `task_progress` 臂(`dist/events.d.ts`)上**没有** `agentType`、也没有 `spawnAgentType` —— tick 这条
|
|
11
|
+
* lane 今天根本不报类型,所以本层**不读**无出处的键(读了就是替上游发明一个字段);
|
|
12
|
+
* · `FleetTaskRow.agentType`(`dist/resources/fleet.d.ts`,§K-7 的短类型 label,如 `Explore` /
|
|
13
|
+
* `general-purpose`)**有出处** —— 但 `fleet/fleetProjection.ts` 把它**折进** `FleetTaskView.name`
|
|
14
|
+
* (`deriveAgentLabel` = `agentName ?? agentType`,登记在
|
|
15
|
+
* `FLEET_TASK_ROW_KEYS_NOT_PROJECTED` 里),折完之后它与显示名再也分不开。
|
|
16
|
+
* ⇒ 所以类型这一位要在**折之前**留一份:行帧消费臂按行 id 尾段记,tick 臂按归一后的尾段读。
|
|
17
|
+
*
|
|
18
|
+
* ── 纪律 ────────────────────────────────────────────────────────────────────────────────────
|
|
19
|
+
* 🔴 **只记 wire 真给了的**:空串 / 非串一律不入账(空串既不是类型也不是「不知道」);账上没有 ⇒ 读口
|
|
20
|
+
* 答 `undefined` = **诚实缺席**,调用方让键缺席,绝不回落到展示名或编一个 `subagent`。
|
|
21
|
+
* 🔴 作用域 = 进程级(与本包转录锚台账逐字同族:写方 = 行帧消费臂,读方 = tick 臂;两份模块实例 ⇒ 读口恒空、
|
|
22
|
+
* `agentType` 恒缺席,而两边代码看着都对)。
|
|
23
|
+
* 有界 LRU(命中即续命):上限是内存约束不是语义,被挤掉只退化成「这一行的类型不知道」。
|
|
24
|
+
*/
|
|
25
|
+
const MAX_ROWS = 2048;
|
|
26
|
+
const agentTypeByTail = new Map();
|
|
27
|
+
/**
|
|
28
|
+
* 记一条 fleet 行的 agentType(幂等;行帧不带这一位 / 坏形 ⇒ 整条不动,不擦掉已知值 ——
|
|
29
|
+
* 同一行的后续帧缺席这一位是「这帧没说」,不是「类型没了」)。
|
|
30
|
+
*/
|
|
31
|
+
export function recordFleetRowAgentType(taskIdTail, agentType) {
|
|
32
|
+
if (typeof taskIdTail !== 'string' || taskIdTail.length === 0)
|
|
33
|
+
return;
|
|
34
|
+
// 形核仍在本层做(wire 值不可信,型面说是 string 不等于到货一定是 string;空串既不是类型也不是「不知道」)。
|
|
35
|
+
if (typeof agentType !== 'string' || agentType.length === 0)
|
|
36
|
+
return;
|
|
37
|
+
agentTypeByTail.delete(taskIdTail);
|
|
38
|
+
agentTypeByTail.set(taskIdTail, agentType);
|
|
39
|
+
if (agentTypeByTail.size > MAX_ROWS) {
|
|
40
|
+
const oldest = agentTypeByTail.keys().next().value;
|
|
41
|
+
if (oldest !== undefined)
|
|
42
|
+
agentTypeByTail.delete(oldest);
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
/** 读口:这一行的子代类型;`undefined` = **诚实不知道**(调用方让键缺席,不许回落到展示名)。 */
|
|
46
|
+
export function fleetRowAgentType(taskIdTail) {
|
|
47
|
+
if (typeof taskIdTail !== 'string' || taskIdTail.length === 0)
|
|
48
|
+
return undefined;
|
|
49
|
+
const v = agentTypeByTail.get(taskIdTail);
|
|
50
|
+
if (v === undefined)
|
|
51
|
+
return undefined;
|
|
52
|
+
agentTypeByTail.delete(taskIdTail); // LRU 续命:还在飞的子代不被一阵新登记挤掉
|
|
53
|
+
agentTypeByTail.set(taskIdTail, v);
|
|
54
|
+
return v;
|
|
55
|
+
}
|
|
56
|
+
/** 测试钩:清台账(module 级单例,同进程多组断言必须能清)。 */
|
|
57
|
+
export function __resetFleetRowAgentTypeForTests() {
|
|
58
|
+
agentTypeByTail.clear();
|
|
59
|
+
}
|
|
@@ -48,7 +48,7 @@
|
|
|
48
48
|
* 就地维护第二份;该函数出过等价性事故,0.1.1 修,见 `workflow.ts` 头注)。
|
|
49
49
|
*/
|
|
50
50
|
import { isStaleEngineAgentPanelEnd, publishEngineAgentPanelAbsence, publishEngineAgentPanelEvent } from './engineAgentPanelStore.js';
|
|
51
|
-
import { __resetPanelRunningHistoryForTests, notePanelRowRunning,
|
|
51
|
+
import { __resetPanelRunningHistoryForTests, notePanelRowRunning, panelRowUnknownToConsumer, panelRowEndedIdentity, holdNewbornTerminalRow, peekHeldNewbornTerminalRow, dropHeldNewbornTerminalRow } from './panelRunningHistory.js';
|
|
52
52
|
import { rowIdTail } from './workflow.js';
|
|
53
53
|
import { TERMINAL_FLEET_TASK_STATUSES } from './fleet/fleetProjection.js';
|
|
54
54
|
import { DEFAULT_SESSION_KEY } from './sessionSlot.js';
|
|
@@ -146,7 +146,11 @@ export function projectFleetAgentRowsFor(sessionKey, rows, nowMs = Date.now()) {
|
|
|
146
146
|
const toolUses = typeof row.toolUses === 'number' ? row.toolUses : undefined;
|
|
147
147
|
const transcriptId = row.transcriptId || undefined;
|
|
148
148
|
const parentToolCallId = typeof row.parentToolCallId === 'string' && row.parentToolCallId.length > 0 ? row.parentToolCallId : undefined;
|
|
149
|
-
|
|
149
|
+
// 🔴 形核与周期判据(`isStaleEngineAgentPanelEnd` / `isNewEngineAgentPanelCycle`)**逐字同源**:
|
|
150
|
+
// 有限正数才算数。少一个 `Number.isFinite` 就会让 `Infinity` 进台账 —— 那两只判据按缺席看它,
|
|
151
|
+
// 而本层按在场看它,同一帧在两处得出相反的「可比不可比」。
|
|
152
|
+
// `Infinity` 还会被序列化成 `null` 发给消费端,渲成一个不存在的 launch 时刻。
|
|
153
|
+
const startedAt = typeof row.startedAt === 'number' && Number.isFinite(row.startedAt) && row.startedAt > 0 ? row.startedAt : undefined;
|
|
150
154
|
// 0.74.0:代际号(正整数才算;坏形 / 缺席 ⇒ undefined,绝不补 1)。
|
|
151
155
|
const cycleSeq = typeof row.cycleSeq === 'number' && Number.isInteger(row.cycleSeq) && row.cycleSeq >= 1 ? row.cycleSeq : undefined;
|
|
152
156
|
const currentTool = currentToolKeyOf(row.currentTool) !== undefined ? row.currentTool : undefined;
|
|
@@ -188,9 +192,34 @@ export function projectFleetAgentRowsFor(sessionKey, rows, nowMs = Date.now()) {
|
|
|
188
192
|
// (交最终用量)再发 end ⇒ 宿主为本会话从没跑过的任务建行随即 settle = 面板幽灵完成行(reattach / resume 后引擎
|
|
189
193
|
// fleet 列表里的历史终态行全部上屏)。本会话内起步又结束、面板从没见过 running 的后台任务由通知车道的 end 收尸;
|
|
190
194
|
// 复活(prev 已 settled 且这一帧周期更新)不在此列,照发。
|
|
191
|
-
|
|
195
|
+
// 0.75.0 W2:「首见」= 消费端没有这一行(没发布过 running **也没递达过 end**);通知 end 先到的短命子代 ⇒ 终态行照发(交最终用量,不再第二次 end)。
|
|
196
|
+
const newbornTerminal = prev === undefined && panelRowUnknownToConsumer(taskId);
|
|
192
197
|
const settledThisCycle = prev?.settled === true && !rowNewerCycle;
|
|
193
|
-
|
|
198
|
+
// 结过的行(本进程递达过 end)再来终态帧:同代或判不出代际 = 短命子代的最终用量到了 ⇒ 只发 fleet-row、不再第二次 end;
|
|
199
|
+
// 可证更新的一代 = 结过之后复活又结束 ⇒ 照发 fleet-row + end。
|
|
200
|
+
const endedRec = prev === undefined ? panelRowEndedIdentity(taskId) : undefined;
|
|
201
|
+
const frameCycle = { ...(cycleSeq !== undefined ? { cycleSeq } : {}), ...(startedAt !== undefined ? { startedAt } : {}) };
|
|
202
|
+
// 方向判(异源复审 R3 [high]:只判「不等」会把迟到的前代终态当成新一代 ⇒ 倒写用量 + 第二次 end):
|
|
203
|
+
// 帧可证比已结周期**旧** ⇒ 整帧丢(不进台账,不发);可证**新** ⇒ 复活又结束,fleet-row + end;同代 / 判不出 ⇒ 只补用量。
|
|
204
|
+
if (endedRec !== undefined && isStaleEngineAgentPanelEnd(frameCycle, endedRec))
|
|
205
|
+
continue;
|
|
206
|
+
const endedSameCycle = endedRec !== undefined && !isStaleEngineAgentPanelEnd(endedRec, frameCycle);
|
|
207
|
+
if (newbornTerminal) {
|
|
208
|
+
// 反序保护:end 也许下一拍才到 ⇒ 终态行(带用量)缓在历史叶里,end 到漏斗时先放它再放 end;真历史行永远等不到 end,缓着直到被挤掉。
|
|
209
|
+
holdNewbornTerminalRow(taskId, {
|
|
210
|
+
kind: 'fleet-row',
|
|
211
|
+
taskId,
|
|
212
|
+
...(row.name ? { name: row.name } : {}),
|
|
213
|
+
...(row.description ? { description: row.description } : {}),
|
|
214
|
+
...(knownTokens !== undefined ? { totalTokens: knownTokens } : {}),
|
|
215
|
+
...(toolUses !== undefined ? { toolUses } : {}),
|
|
216
|
+
...(transcriptId !== undefined ? { transcriptId } : {}),
|
|
217
|
+
...(parentToolCallId !== undefined ? { parentToolCallId } : {}),
|
|
218
|
+
...(knownStartedAt !== undefined ? { startedAt: knownStartedAt } : {}),
|
|
219
|
+
...(knownCycleSeq !== undefined ? { cycleSeq: knownCycleSeq } : {}),
|
|
220
|
+
});
|
|
221
|
+
}
|
|
222
|
+
else if (!settledThisCycle) {
|
|
194
223
|
// 终态行也把最后一拍的累计工具数/转录锚带上:详情页在行翻终态后仍要显示真计数与
|
|
195
224
|
// 委派 prompt,而终态帧常常是这一行**唯一**带全 usage 的一帧(bg lane 只在活动边界发行帧)。
|
|
196
225
|
if (
|
|
@@ -216,17 +245,18 @@ export function projectFleetAgentRowsFor(sessionKey, rows, nowMs = Date.now()) {
|
|
|
216
245
|
...(knownCycleSeq !== undefined ? { cycleSeq: knownCycleSeq } : {}),
|
|
217
246
|
});
|
|
218
247
|
}
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
248
|
+
if (!endedSameCycle)
|
|
249
|
+
publishEngineAgentPanelEvent({
|
|
250
|
+
kind: 'end',
|
|
251
|
+
taskId,
|
|
252
|
+
// L-215② 族扫(0.65.0):与 `notifications.enqueueBgChildNotification` 的 `isError` 位
|
|
253
|
+
// **同一个病形**(内联两词 ⇒ core [6908] 的 `blocked` 漏成「成功」)。两处一次改齐,
|
|
254
|
+
// 读同一个单铸谓词;只修当格 = 下一次加词又漏一处。
|
|
255
|
+
isError: isTerminalNotSuccess(status),
|
|
256
|
+
// 0.74.0:这条终态属于哪个周期(在场才带)—— 消费端据此挡掉上一周期迟到的 end。
|
|
257
|
+
...(knownCycleSeq !== undefined ? { cycleSeq: knownCycleSeq } : {}),
|
|
258
|
+
...(knownStartedAt !== undefined ? { startedAt: knownStartedAt } : {}),
|
|
259
|
+
});
|
|
230
260
|
}
|
|
231
261
|
seenMap.set(taskId, {
|
|
232
262
|
status,
|
|
@@ -245,6 +275,23 @@ export function projectFleetAgentRowsFor(sessionKey, rows, nowMs = Date.now()) {
|
|
|
245
275
|
});
|
|
246
276
|
continue;
|
|
247
277
|
}
|
|
278
|
+
// 🔴 0.75.1:缓着一条「首见即终态」的行时,后到的 running 帧有**三向**(0.75.0 是无条件作废 ⇒ 同代陈旧快照
|
|
279
|
+
// 把终态那条的最终用量顶掉,面板最后显示的是快照里的小数字):
|
|
280
|
+
// · **可证同代**(两侧有可比的同一种周期键且相等)= 陈旧快照 —— 与终态分支 `settledThisCycle` 同律:
|
|
281
|
+
// 整帧不落地(零事件、台账保持 settled),缓行留着等 end;
|
|
282
|
+
// · **可证更新** ⇒ 真复活:照发 running 行,缓着的前代终态行作废(下面那句 drop);
|
|
283
|
+
// · **判不出**(任一侧没有可比的周期键)⇒ 维持 0.75.0 的行为(当复活、作废缓行)—— 缺席不制造周期,
|
|
284
|
+
// 这一向是**已知并接受**的限制(接入文档的缺口行有记)。
|
|
285
|
+
// 「可证更旧」不在这三向里:它在本循环开头就被 `isStaleEngineAgentPanelEnd(rowIdentity, prevIdentity)` 整只跳过了。
|
|
286
|
+
// 可比性只由**帧自带的**两键与台账里记着的两键决定(不能用 `knownCycleSeq` / `knownStartedAt` —— 那两位会把
|
|
287
|
+
// 缺席沿用成「与台账相等」,把「判不出」误判成「可证同代」)。判据与 `isNewEngineAgentPanelCycle` 的前件同形。
|
|
288
|
+
const cycleComparable = (cycleSeq !== undefined && prev?.cycleSeq !== undefined) || (startedAt !== undefined && prev?.startedAt !== undefined);
|
|
289
|
+
// 🔴 再加一道 `prev.settled` 的窄化:缓行只由**终态**分支铸(铸完台账即 settled),所以「缓着行而台账说它在跑」
|
|
290
|
+
// 在本会话的台账里不可达;写出来是为了让这一格永远只对**结过的行**生效 —— 缓行表是进程级、seenMap 是按
|
|
291
|
+
// sessionKey 分桶的两个作用域,不写这一句的话,别的会话缓的行会让本会话一条真在跑的行被判成陈旧快照。
|
|
292
|
+
const staleSnapshotOverHeldRow = prev !== undefined && prev.settled === true && cycleComparable && !rowNewerCycle && peekHeldNewbornTerminalRow(taskId) !== undefined;
|
|
293
|
+
if (staleSnapshotOverHeldRow)
|
|
294
|
+
continue;
|
|
248
295
|
// ① 只发变化(新行 / status 翻 / token 涨 / 累计工具数涨 / 转录锚首次到)。
|
|
249
296
|
// 🔴 后两项的比较是**单向**的:只有「这一帧真给了值且与台账不同」才算变化。帧上**缺席**
|
|
250
297
|
// 不算变化(缺席=引擎这帧没说,不是「回到不知道」)——否则新旧引擎混发时台账会在
|
|
@@ -261,6 +308,7 @@ export function projectFleetAgentRowsFor(sessionKey, rows, nowMs = Date.now()) {
|
|
|
261
308
|
(cycleSeq !== undefined && prev.cycleSeq !== cycleSeq) ||
|
|
262
309
|
(currentToolKey !== undefined && prev.currentToolKey !== currentToolKey)) {
|
|
263
310
|
notePanelRowRunning(taskId); // CC-86:真发布了这一行在跑才记历史(未变的陈旧快照零事件也零记账,复审 R3)
|
|
311
|
+
dropHeldNewbornTerminalRow(taskId); // 复活为 running ⇒ 缓着的旧终态行作废(用量以新一代为准);可证同代的陈旧快照到不了这里(三向判定,见上)
|
|
264
312
|
publishEngineAgentPanelEvent({
|
|
265
313
|
kind: 'fleet-row',
|
|
266
314
|
taskId,
|
|
@@ -1,8 +1,49 @@
|
|
|
1
1
|
export declare function notePanelRowRunning(taskId: string): void;
|
|
2
2
|
/** 结掉了(任何 lane 的 end 递达):历史不再需要。 */
|
|
3
3
|
export declare function retirePanelRowRunning(taskId: string): void;
|
|
4
|
-
/**
|
|
5
|
-
export
|
|
4
|
+
/** 结过的行 → 那条 end 的周期身份(两键都可缺席 = 判不出代际);投影据此分「同代的最终用量到了」与「结过之后复活的新一代」。 */
|
|
5
|
+
export interface PanelRowEndedIdentity {
|
|
6
|
+
cycleSeq?: number;
|
|
7
|
+
startedAt?: number;
|
|
8
|
+
}
|
|
9
|
+
/**
|
|
10
|
+
* 单调写(异源复审 R3 [high]:旧 end 后到不许把已结周期倒写回前代):`older(cycle, existing)` 由调用方(漏斗)用陈旧判据算 ——
|
|
11
|
+
* 可证更旧 ⇒ 不写;同代 / 判不出 ⇒ 与已有记录合并(保留已知的另一半身份);可证更新 ⇒ 调用方传 older=false,本函数整只替换已知键。
|
|
12
|
+
*/
|
|
13
|
+
export declare function notePanelRowEnded(taskId: string, cycle: PanelRowEndedIdentity, older: boolean): void;
|
|
14
|
+
/** 本进程递达过这一行的 end ⇒ 那条 end 的周期身份;没有 ⇒ undefined。 */
|
|
15
|
+
export declare function panelRowEndedIdentity(taskId: string): PanelRowEndedIdentity | undefined;
|
|
16
|
+
/** 首见即终态且**确知**消费端没有这一行(running 表没溢出、不在 running 表、也没递达过 end)⇒ 历史行。 */
|
|
17
|
+
export declare function panelRowUnknownToConsumer(taskId: string): boolean;
|
|
18
|
+
export declare function holdNewbornTerminalRow(taskId: string, ev: object): void;
|
|
19
|
+
export declare function peekHeldNewbornTerminalRow<T extends object>(taskId: string): T | undefined;
|
|
20
|
+
export declare function takeHeldNewbornTerminalRow<T extends object>(taskId: string): T | undefined;
|
|
21
|
+
export declare function dropHeldNewbornTerminalRow(taskId: string): void;
|
|
22
|
+
/** 一条缓行**自带**的 wire 侧身份键(它是一条 fleet 行帧,两把键都可缺席)。 */
|
|
23
|
+
export interface HeldNewbornTerminalRef {
|
|
24
|
+
/** 缓行在本表里的键 = fleet 行 id 尾段。 */
|
|
25
|
+
readonly taskId: string;
|
|
26
|
+
readonly transcriptId?: string;
|
|
27
|
+
readonly parentToolCallId?: string;
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* 0.75.1(实测反例):`end` 归一的三把可能的键里,**后两把只在行帧真发布过时才登记**
|
|
31
|
+
* (`transcriptId → 尾段` 钥匙表与 `尾段 → UUID` 反向所有权都由 `publishEngineAgentPanelEvent`
|
|
32
|
+
* 的 fleet-row 臂写),而缓行的定义就是「还没发布」⇒ `tick{UUID}` 先上屏、终态行 `{尾段,
|
|
33
|
+
* transcriptId: UUID}` 被缓住、`end{UUID}` 到达时三把键全查不到,缓行永远放不出、短命子代的
|
|
34
|
+
* 最终用量丢。所以漏斗还需要第四条腿:按缓行**自己带着的** wire 侧键反查。
|
|
35
|
+
*
|
|
36
|
+
* 🔴 **为什么是遍历、不是第六本索引**:面板这一族已经有五本账(running 历史 / ended 账 / 缓行 /
|
|
37
|
+
* 所有权 / 周期水位),再加一张 `transcriptId → 缓行键` 的索引就要在 hold / take / drop / 容量淘汰
|
|
38
|
+
* 四个写口上各维护一次同步 —— 那是本族出过真回归的形(一本账写了另一本没写 ⇒ 缓行放两次或永不放)。
|
|
39
|
+
* 缓行表有界 4096 且只在 **`end` 递达**时被扫(不是每拍;一条子代一生至多几次),所以遍历的成本
|
|
40
|
+
* 上限是一条 end 读 4096 个小对象,换来的是「缓行表自己就是唯一真源」。
|
|
41
|
+
*/
|
|
42
|
+
export declare function heldNewbornTerminalRefs(): HeldNewbornTerminalRef[];
|
|
43
|
+
/**
|
|
44
|
+
* 测试钩。清四样:① 「发布过 running 且还没结」的行 id 集合 ② 溢出标(溢出后读口一律答保守)③ 「递达过 end」的行 id 集合 ④ 缓着的首见终态行。
|
|
45
|
+
* 🔴 连续场景中途别调:清掉之后,「首见即终态」的判定会把此前见过 running / 结过的行当成从没见过。
|
|
46
|
+
*/
|
|
6
47
|
export declare function __resetPanelRunningHistoryForTests(): void;
|
|
7
48
|
/** 测试 / 诊断读面。 */
|
|
8
49
|
export declare function __panelRunningHistorySize(): number;
|