@zhushanwen/pi-pending-notifications 0.10.0 → 0.11.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/package.json +3 -3
- package/src/__tests__/collect-results.test.ts +70 -0
- package/src/__tests__/pending-notifications.test.ts +35 -3
- package/src/index.ts +39 -22
- package/src/render-result.ts +3 -28
- package/src/state.ts +6 -4
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@zhushanwen/pi-pending-notifications",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.11.0",
|
|
4
4
|
"description": "Cross-extension async operation registration/query mechanism for Pi.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "src/index.ts",
|
|
@@ -34,8 +34,8 @@
|
|
|
34
34
|
"typebox": "1.3.27"
|
|
35
35
|
},
|
|
36
36
|
"dependencies": {
|
|
37
|
-
"@zhushanwen/extension-protocol": "0.
|
|
38
|
-
"@zhushanwen/pi-delivery-port": "0.
|
|
37
|
+
"@zhushanwen/extension-protocol": "0.24.0",
|
|
38
|
+
"@zhushanwen/pi-delivery-port": "0.3.0",
|
|
39
39
|
"@zhushanwen/pi-extension-logger": "0.6.2"
|
|
40
40
|
},
|
|
41
41
|
"peerDependenciesMeta": {
|
|
@@ -412,6 +412,76 @@ describe("collect_results tool", () => {
|
|
|
412
412
|
]);
|
|
413
413
|
});
|
|
414
414
|
|
|
415
|
+
it("[结清五值回填 §3.3.6] 注销 reason 事实经结清解析回填 outcome:completed/failed/cancelled/lost/no_result 分别可达", async () => {
|
|
416
|
+
// 回填权威 = 结清视图(reason 事实 → resolveSettlementReason),不经
|
|
417
|
+
// mapReasonToStatus 旧映射——lost/no_result 在旧 status 副档恒落 unknown,
|
|
418
|
+
// 本用例五值逐一可达即锁定断层修复(真 listener 落盘 → 全链路回填)。
|
|
419
|
+
const cases: Array<[string, string]> = [
|
|
420
|
+
["ok-1", "completed"],
|
|
421
|
+
["ok-2", "failed"],
|
|
422
|
+
["ok-3", "cancelled"],
|
|
423
|
+
["ok-4", "lost"],
|
|
424
|
+
["ok-5", "no_result"],
|
|
425
|
+
];
|
|
426
|
+
for (const [id] of cases) setup.entries.push(makeRegisterEntry(id));
|
|
427
|
+
setup.port.collectArm.mockReturnValue(cases.map(([id]) => ({ id, state: "waiting" })));
|
|
428
|
+
|
|
429
|
+
const promise = runCollect(setup, { ids: cases.map(([id]) => id) });
|
|
430
|
+
const arrivalCb = setup.port.onCollectArrival.mock.calls[0][0] as () => void;
|
|
431
|
+
// 真实 listener 落盘注销 entry:reason 事实 + mapReasonToStatus 的 status 副档
|
|
432
|
+
// (lost/no_result 的 status 落 unknown——旧映射通道下 outcome 恒 unknown 的缺陷形态)
|
|
433
|
+
for (const [id, reason] of cases) setup.handlers.pendingUnregister!({ id, reason });
|
|
434
|
+
// 端口 consume 真实返回形态(MF-1-6):只含载荷,type/outcome 归回填
|
|
435
|
+
setup.port.consume.mockReturnValueOnce(cases.map(([id]) => ({ id, content: `payload-${id}` })));
|
|
436
|
+
|
|
437
|
+
arrivalCb();
|
|
438
|
+
const res = await promise;
|
|
439
|
+
|
|
440
|
+
expect(res.details.collected).toEqual(
|
|
441
|
+
cases.map(([id, outcome]) => ({
|
|
442
|
+
id,
|
|
443
|
+
type: "workflow",
|
|
444
|
+
outcome,
|
|
445
|
+
content: `payload-${id}`,
|
|
446
|
+
})),
|
|
447
|
+
);
|
|
448
|
+
});
|
|
449
|
+
|
|
450
|
+
it("[畸形 data 兜底 §3.3.6] 结清视图缺位(reason 非 string)取 legacy status 副档,与端口快照通道同规则", async () => {
|
|
451
|
+
// 两通道统一填充规则(结清优先 + legacy status 兜底):注销 entry 的 reason
|
|
452
|
+
// 非 string 是畸形 data(结清视图不入,pending-entries.ts absorbUnregisterEntry),
|
|
453
|
+
// status 合法在册——回填取 status 值,与端口快照通道(collectFieldsFor 同款
|
|
454
|
+
// `??` 形态)对同一 id 产出一致;reason 合法时结清视图优先(兜底不触发);
|
|
455
|
+
// 两视图皆缺位保持 undefined(诚实缺省)。status 值经真 listener 落盘契约同源
|
|
456
|
+
// (mapReasonToStatus 映射产物),此处直插 entry 镜像落盘形态。
|
|
457
|
+
setup.entries.push(
|
|
458
|
+
makeRegisterEntry("ok-1"),
|
|
459
|
+
makeRegisterEntry("mf-1"),
|
|
460
|
+
makeRegisterEntry("nil-1"),
|
|
461
|
+
{ customType: "pending:unregister", data: { id: "ok-1", reason: "completed", status: "completed" } },
|
|
462
|
+
{ customType: "pending:unregister", data: { id: "mf-1", reason: 42, status: "cancelled" } },
|
|
463
|
+
{ customType: "pending:unregister", data: { id: "nil-1", reason: 42, status: "not-a-status" } },
|
|
464
|
+
);
|
|
465
|
+
setup.port.collectArm.mockReturnValue(
|
|
466
|
+
["ok-1", "mf-1", "nil-1"].map((id) => ({ id, state: "waiting" })),
|
|
467
|
+
);
|
|
468
|
+
|
|
469
|
+
const promise = runCollect(setup, { ids: ["ok-1", "mf-1", "nil-1"] });
|
|
470
|
+
const arrivalCb = setup.port.onCollectArrival.mock.calls[0][0] as () => void;
|
|
471
|
+
setup.port.consume.mockReturnValueOnce(
|
|
472
|
+
["ok-1", "mf-1", "nil-1"].map((id) => ({ id, content: `payload-${id}` })),
|
|
473
|
+
);
|
|
474
|
+
|
|
475
|
+
arrivalCb();
|
|
476
|
+
const res = await promise;
|
|
477
|
+
|
|
478
|
+
expect(res.details.collected).toEqual([
|
|
479
|
+
{ id: "ok-1", type: "workflow", outcome: "completed", content: "payload-ok-1" },
|
|
480
|
+
{ id: "mf-1", type: "workflow", outcome: "cancelled", content: "payload-mf-1" },
|
|
481
|
+
{ id: "nil-1", type: "workflow", outcome: undefined, content: "payload-nil-1" },
|
|
482
|
+
]);
|
|
483
|
+
});
|
|
484
|
+
|
|
415
485
|
it("超时路径:已收部分进 collected、未完成进 stillOpen(诚实返回,不抛错)", async () => {
|
|
416
486
|
setup.entries.push(makeRegisterEntry("fast"), makeRegisterEntry("slow"));
|
|
417
487
|
setup.port.collectArm.mockReturnValue([
|
|
@@ -14,7 +14,7 @@
|
|
|
14
14
|
|
|
15
15
|
/* eslint-disable taste/no-unsafe-cast */
|
|
16
16
|
|
|
17
|
-
import type { ExtensionAPI, ExtensionContext } from "@earendil-works/pi-coding-agent";
|
|
17
|
+
import type { ExtensionAPI, ExtensionContext, Theme } from "@earendil-works/pi-coding-agent";
|
|
18
18
|
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
|
|
19
19
|
|
|
20
20
|
import pendingNotificationsExtension from "../index";
|
|
@@ -422,7 +422,7 @@ describe("pendingNotificationsExtension factory", () => {
|
|
|
422
422
|
}
|
|
423
423
|
});
|
|
424
424
|
|
|
425
|
-
it("U7c: 未知 reason 落 default 兜底 completed
|
|
425
|
+
it("U7c: 未知 reason 落 default 兜底 unknown(fail-visible 缺省——§3.3.11④ fail-open=completed 废除,不产假成功)", () => {
|
|
426
426
|
fireSessionStart(setup);
|
|
427
427
|
setup.handlers.pendingRegister!({ id: "w-unknown", type: "workflow", name: "op" });
|
|
428
428
|
setup.appendEntryMock.mockClear();
|
|
@@ -431,7 +431,24 @@ describe("pendingNotificationsExtension factory", () => {
|
|
|
431
431
|
|
|
432
432
|
expect(setup.appendEntryMock).toHaveBeenCalledWith(
|
|
433
433
|
"pending:unregister",
|
|
434
|
-
expect.objectContaining({ id: "w-unknown", reason: "some-future-reason", status: "
|
|
434
|
+
expect.objectContaining({ id: "w-unknown", reason: "some-future-reason", status: "unknown" }),
|
|
435
|
+
);
|
|
436
|
+
});
|
|
437
|
+
|
|
438
|
+
it("[subagent-observability §3.3.11④] reason 缺失(emit 方漏传的畸形 data)→ 结清 reason = no_result,不产 completed 假成功", () => {
|
|
439
|
+
fireSessionStart(setup);
|
|
440
|
+
setup.handlers.pendingRegister!({ id: "w-no-reason", type: "workflow", name: "op" });
|
|
441
|
+
setup.appendEntryMock.mockClear();
|
|
442
|
+
|
|
443
|
+
setup.handlers.pendingUnregister!({ id: "w-no-reason" });
|
|
444
|
+
|
|
445
|
+
expect(setup.appendEntryMock).toHaveBeenCalledWith(
|
|
446
|
+
"pending:unregister",
|
|
447
|
+
expect.objectContaining({ id: "w-no-reason", reason: "no_result", status: "unknown" }),
|
|
448
|
+
);
|
|
449
|
+
expect(setup.appendEntryMock).not.toHaveBeenCalledWith(
|
|
450
|
+
"pending:unregister",
|
|
451
|
+
expect.objectContaining({ reason: "completed" }),
|
|
435
452
|
);
|
|
436
453
|
});
|
|
437
454
|
});
|
|
@@ -454,6 +471,21 @@ describe("pendingNotificationsExtension factory", () => {
|
|
|
454
471
|
const ids = (res.details as { items: PendingEntry[] }).items.map((i) => i.id);
|
|
455
472
|
expect(ids.sort()).toEqual(["s-1", "w-1"]);
|
|
456
473
|
});
|
|
474
|
+
|
|
475
|
+
it("renderResult 接线(C-ext-05):注册定义携带函数成员 + 折叠态输出 details.summary 一行", async () => {
|
|
476
|
+
fireSessionStart(setup);
|
|
477
|
+
setup.handlers.pendingRegister!({ id: "w-1", type: "workflow", name: "a" });
|
|
478
|
+
const tool = setup.registerToolMock.mock.calls[0][0] as {
|
|
479
|
+
renderResult: (result: unknown, options: { expanded: boolean }, theme: Theme) => { render: (width: number) => string[] };
|
|
480
|
+
};
|
|
481
|
+
expect(typeof tool.renderResult).toBe("function");
|
|
482
|
+
const res = await runTool(setup, { action: "count" });
|
|
483
|
+
const renderTheme = { fg: (_token: string, text: string) => text } as unknown as Theme;
|
|
484
|
+
const lines = tool.renderResult(res, { expanded: false }, renderTheme).render(400);
|
|
485
|
+
// Text.render 按宽度 pad,trim 后断言(折叠态 = details.summary 一行)
|
|
486
|
+
expect(lines.length).toBe(1);
|
|
487
|
+
expect(lines[0].trim()).toBe("1 pending operations");
|
|
488
|
+
});
|
|
457
489
|
});
|
|
458
490
|
|
|
459
491
|
describe("写侧幂等(P2)", () => {
|
package/src/index.ts
CHANGED
|
@@ -259,6 +259,9 @@ export default function pendingNotificationsExtension(pi: ExtensionAPI): void {
|
|
|
259
259
|
debugLog("warn", "listener: pending:unregister parse failed", data);
|
|
260
260
|
return;
|
|
261
261
|
}
|
|
262
|
+
if (parsed.reasonMalformed) {
|
|
263
|
+
debugLog("warn", "listener: pending:unregister reason missing/malformed (emit-side contract breach) → settled no_result", data);
|
|
264
|
+
}
|
|
262
265
|
|
|
263
266
|
debugLog("debug", "listener: pending:unregister parsed", parsed);
|
|
264
267
|
|
|
@@ -424,8 +427,8 @@ export default function pendingNotificationsExtension(pi: ExtensionAPI): void {
|
|
|
424
427
|
await awaitCollectArrivals(port, waitingIds, collected, timeoutMs, signal);
|
|
425
428
|
|
|
426
429
|
// 终局序列(设计 §3.1③):consume 取走剩余已到达挂起条目(超时/abort 路径的
|
|
427
|
-
// 部分收取),collectRelease 解散等待集并把未收挂起条目直通内核 flush
|
|
428
|
-
//
|
|
430
|
+
// 部分收取),collectRelease 解散等待集并把未收挂起条目直通内核 flush(边沿
|
|
431
|
+
// 即投——攒批窗已删 §3.3.7③,恢复 push)。空等待路径不会走到这里(提前返回)。
|
|
429
432
|
collected.push(...port.consume());
|
|
430
433
|
port.collectRelease();
|
|
431
434
|
|
|
@@ -464,8 +467,9 @@ export default function pendingNotificationsExtension(pi: ExtensionAPI): void {
|
|
|
464
467
|
case "ready":
|
|
465
468
|
case "unregistered-no-entry":
|
|
466
469
|
// 快照收取:ready = 日志条目已有终局的读侧快照(不写新终局);
|
|
467
|
-
// unregistered-no-entry = 第四态无载荷有限信息形态(outcome
|
|
468
|
-
// status
|
|
470
|
+
// unregistered-no-entry = 第四态无载荷有限信息形态(outcome 由端口
|
|
471
|
+
// 按结清视图优先 + legacy status 兜底填充、note 说明来源,端口侧
|
|
472
|
+
// 组装)。两态的 entry 契约在册。
|
|
469
473
|
buckets.collected.push({ id: snap.id, ...(snap.entry ?? {}) });
|
|
470
474
|
break;
|
|
471
475
|
case "in-push-pipeline":
|
|
@@ -526,22 +530,27 @@ export default function pendingNotificationsExtension(pi: ExtensionAPI): void {
|
|
|
526
530
|
});
|
|
527
531
|
}
|
|
528
532
|
|
|
529
|
-
/** type/outcome 缺失回填(设计 §3.1③
|
|
530
|
-
*
|
|
531
|
-
*
|
|
532
|
-
*
|
|
533
|
-
*
|
|
534
|
-
*
|
|
535
|
-
*
|
|
536
|
-
*
|
|
537
|
-
*
|
|
538
|
-
*
|
|
533
|
+
/** type/outcome 缺失回填(设计 §3.1③ 字段来源表 + §3.3.6 结清权威):type 与
|
|
534
|
+
* pending:register 的 type 对齐;outcome 与端口快照通道(delivery-port
|
|
535
|
+
* collectFieldsFor / collectArm)统一填充规则——注销结清视图优先(reason 事实
|
|
536
|
+
* 经 resolveSettlementReason 结清解析,封闭五值 + 词表外 unknown fail-visible,
|
|
537
|
+
* protocol scanPendingFields 单点投影),legacy status 副档兜底。结清视图承载
|
|
538
|
+
* no_result/lost 等新写入方 reason 的真值(旧 status 副档恒落 unknown),兜底
|
|
539
|
+
* 仅服务结清视图缺位的畸形 data(status 合法但 reason 非 string——注销 entry
|
|
540
|
+
* 不入结清视图、status 副档在册),此时两通道同取 status 值保持一致;两视图
|
|
541
|
+
* 皆缺位保持 undefined(诚实缺省,不虚构终态)。「等待中同步到达」的挂起条目
|
|
542
|
+
* 在端口 consume 时点,生产方的注销 entry 尚未落盘(managed flushBatch 的
|
|
543
|
+
* deliver 与 unregisterPending 同步段相邻、deliver 先行;端口 consume 由到达
|
|
544
|
+
* 回调在 deliver 栈内同步触发),outcome 恒取空。await 恢复后生产方同步栈已完成
|
|
545
|
+
* (注销 entry 已落盘),按本 session entries 现算回填;注销不在册的条目保持
|
|
546
|
+
* undefined。快照收取条目(ready / unregistered-no-entry)的 type/outcome 已由
|
|
547
|
+
* 端口 collectArm 填充,回填只补缺不覆盖。 */
|
|
539
548
|
function backfillCollectFields(ctx: ExtensionContext, collected: CollectedEntry[]): void {
|
|
540
549
|
if (!collected.some((c) => c.outcome === undefined || c.type === undefined)) return;
|
|
541
550
|
const fields = scanPendingFields(ctx.sessionManager.getEntries());
|
|
542
551
|
for (const c of collected) {
|
|
543
552
|
if (c.type === undefined) c.type = fields.registerTypes.get(c.id);
|
|
544
|
-
if (c.outcome === undefined) c.outcome = fields.unregisterStatuses.get(c.id);
|
|
553
|
+
if (c.outcome === undefined) c.outcome = fields.unregisterSettlements.get(c.id) ?? fields.unregisterStatuses.get(c.id);
|
|
545
554
|
}
|
|
546
555
|
}
|
|
547
556
|
}
|
|
@@ -574,27 +583,35 @@ function parseRegisterEvent(data: unknown): ParsedRegister | null {
|
|
|
574
583
|
interface ParsedUnregister {
|
|
575
584
|
id: string;
|
|
576
585
|
reason: string;
|
|
586
|
+
/** reason 缺失/非 string(emit 方违约的畸形 data)——缺省 no_result 结清时置位,
|
|
587
|
+
* 调用点 debugLog warn 留痕([subagent-observability §3.3.11④])。 */
|
|
588
|
+
reasonMalformed?: boolean;
|
|
577
589
|
}
|
|
578
590
|
|
|
579
591
|
/** 解析 pending:unregister 事件 data(容错缺失/类型错误字段)。
|
|
580
|
-
* T2 通知由 subagent-workflow 自有通道 bg-notify-render 承担,不经本事件。
|
|
592
|
+
* T2 通知由 subagent-workflow 自有通道 bg-notify-render 承担,不经本事件。
|
|
593
|
+
* [subagent-observability §3.3.11④] reason 缺失/非 string(emit 方漏传的违约
|
|
594
|
+
* 形态)缺省 no_result 不产 completed——无事实可依不产假成功结清,与
|
|
595
|
+
* resolveSettlementReason / resolveSettlement 缺省方向一致;畸形经调用点
|
|
596
|
+
* warn 留痕。 */
|
|
581
597
|
function parseUnregisterEvent(data: unknown): ParsedUnregister | null {
|
|
582
598
|
if (typeof data !== "object" || data === null) return null;
|
|
583
599
|
const d = data as Record<string, unknown>;
|
|
584
600
|
if (typeof d.id !== "string") return null;
|
|
585
|
-
|
|
586
|
-
id: d.id,
|
|
587
|
-
|
|
588
|
-
};
|
|
601
|
+
if (typeof d.reason !== "string") {
|
|
602
|
+
return { id: d.id, reason: "no_result", reasonMalformed: true };
|
|
603
|
+
}
|
|
604
|
+
return { id: d.id, reason: d.reason };
|
|
589
605
|
}
|
|
590
606
|
|
|
591
607
|
/** 将事件 reason 映射为 pending:unregister entry 的 status 字段(落盘契约组成部分)。
|
|
592
608
|
* 权威单点在 protocol pending-entries(ext-simplify-17 D10)——本函数是对 protocol
|
|
593
609
|
* 导出的类型收窄包装(PendingStatus 枚举留在本包,protocol 不引 extension 侧类型),
|
|
594
610
|
* 既有调用点零改动;bte 对账写侧同引 protocol 单点,两侧映射不再可能漂移。
|
|
595
|
-
* 词表演化背景(U5 / §3.2.5 通知词表对齐)见 protocol 实现处 JSDoc
|
|
611
|
+
* 词表演化背景(U5 / §3.2.5 通知词表对齐)见 protocol 实现处 JSDoc;unknown 缺省
|
|
612
|
+
* 成员两侧词表同批收录,收窄赋值成立无需断言。 */
|
|
596
613
|
function mapReasonToStatus(reason: string): PendingStatus {
|
|
597
|
-
return mapReasonToStatusImpl(reason)
|
|
614
|
+
return mapReasonToStatusImpl(reason);
|
|
598
615
|
}
|
|
599
616
|
|
|
600
617
|
/** 格式化 active 列表为可读文本 */
|
package/src/render-result.ts
CHANGED
|
@@ -4,32 +4,7 @@
|
|
|
4
4
|
|
|
5
5
|
import type { Theme } from "@earendil-works/pi-coding-agent";
|
|
6
6
|
import { Text } from "@earendil-works/pi-tui";
|
|
7
|
-
import {
|
|
8
|
-
|
|
9
|
-
/** content[0] 的 text 纯文本(content 缺失或非 text 类型时兜底空串)。 */
|
|
10
|
-
function contentText(result: unknown): string {
|
|
11
|
-
const content = fieldValue(result, "content");
|
|
12
|
-
if (!Array.isArray(content)) return "";
|
|
13
|
-
return firstContentText({ content });
|
|
14
|
-
}
|
|
15
|
-
|
|
16
|
-
/** unknown 值按字段名安全取值(index-signature 收窄,替代全可选结构断言)。 */
|
|
17
|
-
function fieldValue(v: unknown, key: string): unknown {
|
|
18
|
-
if (typeof v !== "object" || v === null) return undefined;
|
|
19
|
-
return (v as Record<string, unknown>)[key];
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
/** 从 details 安全读取 summary(容错非对象形态与旧版本 entry)。 */
|
|
23
|
-
function readDetailsSummary(result: unknown): string | undefined {
|
|
24
|
-
const summary = fieldValue(fieldValue(result, "details"), "summary");
|
|
25
|
-
return typeof summary === "string" && summary !== "" ? summary : undefined;
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
/** 折叠态回退:content 文本首行(保持一行摘要形态)。 */
|
|
29
|
-
function firstLine(text: string): string {
|
|
30
|
-
const idx = text.indexOf("\n");
|
|
31
|
-
return idx === -1 ? text : text.slice(0, idx);
|
|
32
|
-
}
|
|
7
|
+
import { firstLine, readDetailsSummary, resultContentText } from "@zhushanwen/extension-protocol";
|
|
33
8
|
|
|
34
9
|
/** 折叠态(expanded=false)= details.summary 一行(dim);展开态 = content 纯文本。 */
|
|
35
10
|
export function renderPendingNotificationsResult(
|
|
@@ -37,7 +12,7 @@ export function renderPendingNotificationsResult(
|
|
|
37
12
|
options: { expanded: boolean },
|
|
38
13
|
theme: Theme,
|
|
39
14
|
): Text {
|
|
40
|
-
if (options.expanded) return new Text(
|
|
41
|
-
const summary = readDetailsSummary(result) ?? firstLine(
|
|
15
|
+
if (options.expanded) return new Text(resultContentText(result), 0, 0);
|
|
16
|
+
const summary = readDetailsSummary(result) ?? firstLine(resultContentText(result));
|
|
42
17
|
return new Text(theme.fg("dim", summary), 0, 0);
|
|
43
18
|
}
|
package/src/state.ts
CHANGED
|
@@ -39,7 +39,9 @@ export type PendingType = "workflow" | "subagent" | "bash" | "session";
|
|
|
39
39
|
export type PendingKind = "lifetime";
|
|
40
40
|
|
|
41
41
|
/** 异步操作终态/过渡状态。active = 仍在运行;其他都视为已结束 */
|
|
42
|
-
|
|
42
|
+
/** unknown = 词表外 reason 的解析缺省(fail-visible——§3.3.11④ fail-open=completed 废除,
|
|
43
|
+
* 不可判读时如实标注不产假成功;仍属「已结束」——非 active)。 */
|
|
44
|
+
export type PendingStatus = "active" | "completed" | "failed" | "cancelled" | "expired" | "time_limited" | "aborted" | "unknown";
|
|
43
45
|
|
|
44
46
|
/** 一个异步操作的完整描述(= pending:register entry 的 data 字段) */
|
|
45
47
|
export interface PendingEntry {
|
|
@@ -125,9 +127,9 @@ export function computeCollectWaitKeys(entries: unknown[], currentSessionId: str
|
|
|
125
127
|
|
|
126
128
|
/**
|
|
127
129
|
* collect 终局回填的字段视图单点在 protocol scanPendingFields(register 首见
|
|
128
|
-
* type + 注销 status
|
|
129
|
-
* MF-2-1
|
|
130
|
-
*
|
|
130
|
+
* type + 注销 status 视图 + 注销 reason 事实的结清视图——回填 outcome 读结清视图
|
|
131
|
+
* (§3.3.6 权威通道),与 delivery-port 快照基线同一扫描规则,MF-2-1;回填时序
|
|
132
|
+
* 背景见 index.ts collectResults 回填段)——本文件不持有字段映射实现。
|
|
131
133
|
*/
|
|
132
134
|
|
|
133
135
|
/**
|