@oai404iao/pi-subagent 0.3.0 → 0.4.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.
@@ -0,0 +1,656 @@
1
+ import { uuidv7 } from "@earendil-works/pi-ai";
2
+ import type { SessionEntry } from "@earendil-works/pi-coding-agent";
3
+ import type { SessionView } from "./providers.ts";
4
+ import type {
5
+ SubagentRunResult,
6
+ SubagentStopReason,
7
+ } from "./types.ts";
8
+
9
+ export const COMPLETION_UPDATE_CUSTOM_TYPE =
10
+ "pi-subagent/completion-update";
11
+ export const COMPLETION_DELIVERY_CUSTOM_TYPE =
12
+ "pi-subagent/completion-delivery";
13
+ export const COMPLETION_DELIVERY_RELEASE_CUSTOM_TYPE =
14
+ "pi-subagent/completion-delivery-release";
15
+ export const COMPLETION_FAILURE_CUSTOM_TYPE =
16
+ "pi-subagent/completion-undelivered";
17
+ export const COMPLETION_MAILBOX_VERSION = 1;
18
+ export const MAX_COMPLETION_OUTPUT_CHARS = 2 * 1024 * 1024;
19
+ export const MAX_COMPLETIONS_PER_DELIVERY = 256;
20
+
21
+ const UUID_V7_PATTERN =
22
+ /^[0-9a-f]{8}-[0-9a-f]{4}-7[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i;
23
+ const STOP_REASONS = new Set<SubagentStopReason>([
24
+ "completed",
25
+ "aborted",
26
+ "error",
27
+ "max-tokens",
28
+ ]);
29
+
30
+ export interface CompletionUpdate {
31
+ version: 1;
32
+ completionId: string;
33
+ parentAgentId: string;
34
+ childAgentId: string;
35
+ turnId: string;
36
+ stopReason: SubagentStopReason;
37
+ output: string;
38
+ outputTruncated: boolean;
39
+ omittedBytes?: number;
40
+ createdAt: string;
41
+ }
42
+
43
+ export interface CompletionDelivery {
44
+ version: 1;
45
+ deliveryId: string;
46
+ runtimeId: string;
47
+ parentAgentId: string;
48
+ toolCallId: string;
49
+ completionIds: string[];
50
+ createdAt: string;
51
+ }
52
+
53
+ export interface CompletionDeliveryRelease {
54
+ version: 1;
55
+ releaseId: string;
56
+ runtimeId: string;
57
+ parentAgentId: string;
58
+ deliveryIds: string[];
59
+ reason: string;
60
+ createdAt: string;
61
+ }
62
+
63
+ export interface CompletionMailboxSnapshot {
64
+ updates: CompletionUpdate[];
65
+ unread: CompletionUpdate[];
66
+ available: CompletionUpdate[];
67
+ currentRuntimeReservations: CompletionDelivery[];
68
+ }
69
+
70
+ export type CompletionMailboxFold =
71
+ | { kind: "valid"; snapshot: CompletionMailboxSnapshot }
72
+ | { kind: "corrupt"; message: string };
73
+
74
+ type UnknownRecord = Record<string, unknown>;
75
+
76
+ function record(value: unknown, field: string): UnknownRecord {
77
+ if (value === null || typeof value !== "object" || Array.isArray(value)) {
78
+ throw new Error(`${field} must be an object`);
79
+ }
80
+ return value as UnknownRecord;
81
+ }
82
+
83
+ function uuid(value: unknown, field: string): string {
84
+ if (typeof value !== "string" || !UUID_V7_PATTERN.test(value)) {
85
+ throw new Error(`${field} must be a UUIDv7 id`);
86
+ }
87
+ return value;
88
+ }
89
+
90
+ function text(
91
+ value: unknown,
92
+ field: string,
93
+ options: { allowEmpty?: boolean; maxLength: number },
94
+ ): string {
95
+ if (
96
+ typeof value !== "string"
97
+ || (!options.allowEmpty && value.trim().length === 0)
98
+ || value.length > options.maxLength
99
+ ) {
100
+ throw new Error(
101
+ `${field} must be ${options.allowEmpty ? "a" : "a non-empty"} string of at most ${options.maxLength} characters`,
102
+ );
103
+ }
104
+ return value;
105
+ }
106
+
107
+ function isoDate(value: unknown, field: string): string {
108
+ if (typeof value !== "string" || Number.isNaN(Date.parse(value))) {
109
+ throw new Error(`${field} must be an ISO date string`);
110
+ }
111
+ return value;
112
+ }
113
+
114
+ function boolean(value: unknown, field: string): boolean {
115
+ if (typeof value !== "boolean") throw new Error(`${field} must be a boolean`);
116
+ return value;
117
+ }
118
+
119
+ function optionalNatural(value: unknown, field: string): number | undefined {
120
+ if (value === undefined) return undefined;
121
+ if (!Number.isSafeInteger(value) || (value as number) < 0) {
122
+ throw new Error(`${field} must be a non-negative safe integer`);
123
+ }
124
+ return value as number;
125
+ }
126
+
127
+ function parseUpdate(value: unknown): CompletionUpdate {
128
+ const input = record(value, "completion update");
129
+ if (input.version !== COMPLETION_MAILBOX_VERSION) {
130
+ throw new Error(
131
+ `unsupported completion update version: ${String(input.version)}`,
132
+ );
133
+ }
134
+ const stopReason = text(input.stopReason, "completion update.stopReason", {
135
+ maxLength: 32,
136
+ }) as SubagentStopReason;
137
+ if (!STOP_REASONS.has(stopReason)) {
138
+ throw new Error(`unsupported completion stop reason: ${stopReason}`);
139
+ }
140
+ const outputTruncated = boolean(
141
+ input.outputTruncated,
142
+ "completion update.outputTruncated",
143
+ );
144
+ const omittedBytes = optionalNatural(
145
+ input.omittedBytes,
146
+ "completion update.omittedBytes",
147
+ );
148
+ if (!outputTruncated && omittedBytes !== undefined) {
149
+ throw new Error(
150
+ "completion update.omittedBytes requires outputTruncated",
151
+ );
152
+ }
153
+ return {
154
+ version: COMPLETION_MAILBOX_VERSION,
155
+ completionId: uuid(
156
+ input.completionId,
157
+ "completion update.completionId",
158
+ ),
159
+ parentAgentId: uuid(
160
+ input.parentAgentId,
161
+ "completion update.parentAgentId",
162
+ ),
163
+ childAgentId: uuid(
164
+ input.childAgentId,
165
+ "completion update.childAgentId",
166
+ ),
167
+ turnId: uuid(input.turnId, "completion update.turnId"),
168
+ stopReason,
169
+ output: text(input.output, "completion update.output", {
170
+ allowEmpty: true,
171
+ maxLength: MAX_COMPLETION_OUTPUT_CHARS,
172
+ }),
173
+ outputTruncated,
174
+ ...(omittedBytes !== undefined ? { omittedBytes } : {}),
175
+ createdAt: isoDate(
176
+ input.createdAt,
177
+ "completion update.createdAt",
178
+ ),
179
+ };
180
+ }
181
+
182
+ function parseDelivery(value: unknown): CompletionDelivery {
183
+ const input = record(value, "completion delivery");
184
+ if (input.version !== COMPLETION_MAILBOX_VERSION) {
185
+ throw new Error(
186
+ `unsupported completion delivery version: ${String(input.version)}`,
187
+ );
188
+ }
189
+ if (
190
+ !Array.isArray(input.completionIds)
191
+ || input.completionIds.length === 0
192
+ || input.completionIds.length > MAX_COMPLETIONS_PER_DELIVERY
193
+ ) {
194
+ throw new Error(
195
+ `completion delivery.completionIds must contain 1-${MAX_COMPLETIONS_PER_DELIVERY} ids`,
196
+ );
197
+ }
198
+ const completionIds = input.completionIds.map((value, index) =>
199
+ uuid(value, `completion delivery.completionIds[${index}]`),
200
+ );
201
+ if (new Set(completionIds).size !== completionIds.length) {
202
+ throw new Error("completion delivery.completionIds contains a duplicate id");
203
+ }
204
+ return {
205
+ version: COMPLETION_MAILBOX_VERSION,
206
+ deliveryId: uuid(
207
+ input.deliveryId,
208
+ "completion delivery.deliveryId",
209
+ ),
210
+ runtimeId: uuid(input.runtimeId, "completion delivery.runtimeId"),
211
+ parentAgentId: uuid(
212
+ input.parentAgentId,
213
+ "completion delivery.parentAgentId",
214
+ ),
215
+ toolCallId: text(
216
+ input.toolCallId,
217
+ "completion delivery.toolCallId",
218
+ { maxLength: 512 },
219
+ ),
220
+ completionIds,
221
+ createdAt: isoDate(
222
+ input.createdAt,
223
+ "completion delivery.createdAt",
224
+ ),
225
+ };
226
+ }
227
+
228
+ function parseDeliveryRelease(value: unknown): CompletionDeliveryRelease {
229
+ const input = record(value, "completion delivery release");
230
+ if (input.version !== COMPLETION_MAILBOX_VERSION) {
231
+ throw new Error(
232
+ `unsupported completion delivery release version: ${String(input.version)}`,
233
+ );
234
+ }
235
+ if (
236
+ !Array.isArray(input.deliveryIds)
237
+ || input.deliveryIds.length === 0
238
+ || input.deliveryIds.length > MAX_COMPLETIONS_PER_DELIVERY
239
+ ) {
240
+ throw new Error(
241
+ `completion delivery release.deliveryIds must contain 1-${MAX_COMPLETIONS_PER_DELIVERY} ids`,
242
+ );
243
+ }
244
+ const deliveryIds = input.deliveryIds.map((value, index) =>
245
+ uuid(value, `completion delivery release.deliveryIds[${index}]`),
246
+ );
247
+ if (new Set(deliveryIds).size !== deliveryIds.length) {
248
+ throw new Error(
249
+ "completion delivery release.deliveryIds contains a duplicate id",
250
+ );
251
+ }
252
+ return {
253
+ version: COMPLETION_MAILBOX_VERSION,
254
+ releaseId: uuid(
255
+ input.releaseId,
256
+ "completion delivery release.releaseId",
257
+ ),
258
+ runtimeId: uuid(
259
+ input.runtimeId,
260
+ "completion delivery release.runtimeId",
261
+ ),
262
+ parentAgentId: uuid(
263
+ input.parentAgentId,
264
+ "completion delivery release.parentAgentId",
265
+ ),
266
+ deliveryIds,
267
+ reason: text(input.reason, "completion delivery release.reason", {
268
+ maxLength: 1000,
269
+ }),
270
+ createdAt: isoDate(
271
+ input.createdAt,
272
+ "completion delivery release.createdAt",
273
+ ),
274
+ };
275
+ }
276
+
277
+ function committedToolResultIndexes(
278
+ entries: readonly SessionEntry[],
279
+ ): Map<string, number[]> {
280
+ const indexes = new Map<string, number[]>();
281
+ for (let index = 0; index < entries.length; index++) {
282
+ const entry = entries[index]!;
283
+ if (
284
+ entry.type !== "message"
285
+ || entry.message.role !== "toolResult"
286
+ || entry.message.toolName !== "wait_agent"
287
+ || entry.message.isError
288
+ ) {
289
+ continue;
290
+ }
291
+ const existing = indexes.get(entry.message.toolCallId) ?? [];
292
+ existing.push(index);
293
+ indexes.set(entry.message.toolCallId, existing);
294
+ }
295
+ return indexes;
296
+ }
297
+
298
+ function foldOrThrow(
299
+ entries: readonly SessionEntry[],
300
+ parentAgentId: string,
301
+ activeRuntimeId?: string,
302
+ ): CompletionMailboxSnapshot {
303
+ uuid(parentAgentId, "completion mailbox parentAgentId");
304
+ if (activeRuntimeId !== undefined) {
305
+ uuid(activeRuntimeId, "completion mailbox activeRuntimeId");
306
+ }
307
+ const toolResults = committedToolResultIndexes(entries);
308
+ const updates: CompletionUpdate[] = [];
309
+ const byId = new Map<string, CompletionUpdate>();
310
+ const turnKeys = new Set<string>();
311
+ const deliveryIds = new Set<string>();
312
+ const releaseIds = new Set<string>();
313
+ const releasedDeliveryIds = new Set<string>();
314
+ const deliveries: Array<{
315
+ index: number;
316
+ delivery: CompletionDelivery;
317
+ committed: boolean;
318
+ }> = [];
319
+
320
+ for (let index = 0; index < entries.length; index++) {
321
+ const entry = entries[index]!;
322
+ if (entry.type !== "custom") continue;
323
+ if (entry.customType === COMPLETION_UPDATE_CUSTOM_TYPE) {
324
+ const update = parseUpdate(entry.data);
325
+ if (update.parentAgentId !== parentAgentId) {
326
+ throw new Error(
327
+ `completion ${update.completionId} belongs to another parent`,
328
+ );
329
+ }
330
+ if (byId.has(update.completionId)) {
331
+ throw new Error(
332
+ `duplicate completion id: ${update.completionId}`,
333
+ );
334
+ }
335
+ const turnKey = `${update.childAgentId}:${update.turnId}`;
336
+ if (turnKeys.has(turnKey)) {
337
+ throw new Error(
338
+ `duplicate completion for child turn ${turnKey}`,
339
+ );
340
+ }
341
+ turnKeys.add(turnKey);
342
+ byId.set(update.completionId, update);
343
+ updates.push(update);
344
+ continue;
345
+ }
346
+ if (entry.customType !== COMPLETION_DELIVERY_CUSTOM_TYPE) continue;
347
+ const delivery = parseDelivery(entry.data);
348
+ if (deliveryIds.has(delivery.deliveryId)) {
349
+ throw new Error(
350
+ `duplicate completion delivery id: ${delivery.deliveryId}`,
351
+ );
352
+ }
353
+ deliveryIds.add(delivery.deliveryId);
354
+ if (delivery.parentAgentId !== parentAgentId) {
355
+ throw new Error(
356
+ `completion delivery ${delivery.deliveryId} belongs to another parent`,
357
+ );
358
+ }
359
+ for (const completionId of delivery.completionIds) {
360
+ if (!byId.has(completionId)) {
361
+ throw new Error(
362
+ `completion delivery ${delivery.deliveryId} references unavailable completion ${completionId}`,
363
+ );
364
+ }
365
+ }
366
+ const committed = (toolResults.get(delivery.toolCallId) ?? []).some(
367
+ (toolResultIndex) => toolResultIndex > index,
368
+ );
369
+ deliveries.push({ index, delivery, committed });
370
+ }
371
+
372
+ for (let releaseIndex = 0; releaseIndex < entries.length; releaseIndex++) {
373
+ const entry = entries[releaseIndex]!;
374
+ if (
375
+ entry.type !== "custom"
376
+ || entry.customType !== COMPLETION_DELIVERY_RELEASE_CUSTOM_TYPE
377
+ ) {
378
+ continue;
379
+ }
380
+ const release = parseDeliveryRelease(entry.data);
381
+ if (releaseIds.has(release.releaseId)) {
382
+ throw new Error(
383
+ `duplicate completion delivery release id: ${release.releaseId}`,
384
+ );
385
+ }
386
+ releaseIds.add(release.releaseId);
387
+ if (release.parentAgentId !== parentAgentId) {
388
+ throw new Error(
389
+ `completion delivery release ${release.releaseId} belongs to another parent`,
390
+ );
391
+ }
392
+ for (const deliveryId of release.deliveryIds) {
393
+ const deliveryItem = deliveries.find(
394
+ (item) => item.delivery.deliveryId === deliveryId,
395
+ );
396
+ if (!deliveryItem || deliveryItem.index >= releaseIndex) {
397
+ throw new Error(
398
+ `completion delivery release ${release.releaseId} references unavailable delivery ${deliveryId}`,
399
+ );
400
+ }
401
+ const delivery = deliveryItem.delivery;
402
+ if (delivery.runtimeId !== release.runtimeId) {
403
+ throw new Error(
404
+ `completion delivery release ${release.releaseId} has a mismatched runtime`,
405
+ );
406
+ }
407
+ releasedDeliveryIds.add(deliveryId);
408
+ }
409
+ }
410
+
411
+ const unread = [...updates];
412
+ for (const item of deliveries) {
413
+ if (
414
+ !item.committed
415
+ || releasedDeliveryIds.has(item.delivery.deliveryId)
416
+ ) {
417
+ continue;
418
+ }
419
+ if (item.delivery.completionIds.length > unread.length) {
420
+ throw new Error(
421
+ `completion delivery ${item.delivery.deliveryId} exceeds the unread FIFO`,
422
+ );
423
+ }
424
+ for (
425
+ let index = 0;
426
+ index < item.delivery.completionIds.length;
427
+ index++
428
+ ) {
429
+ const completionId = item.delivery.completionIds[index]!;
430
+ if (completionId !== unread[index]?.completionId) {
431
+ throw new Error(
432
+ `completion delivery ${item.delivery.deliveryId} is not the unread FIFO prefix`,
433
+ );
434
+ }
435
+ }
436
+ unread.splice(0, item.delivery.completionIds.length);
437
+ }
438
+
439
+ const currentRuntimeReservations = deliveries
440
+ .filter(
441
+ (item) =>
442
+ !item.committed
443
+ && !releasedDeliveryIds.has(item.delivery.deliveryId)
444
+ && activeRuntimeId !== undefined
445
+ && item.delivery.runtimeId === activeRuntimeId,
446
+ )
447
+ .map((item) => ({
448
+ ...item.delivery,
449
+ completionIds: [...item.delivery.completionIds],
450
+ }));
451
+ const reserved = new Set(
452
+ currentRuntimeReservations.flatMap(
453
+ (delivery) => delivery.completionIds,
454
+ ),
455
+ );
456
+ return {
457
+ updates: updates.map((update) => ({ ...update })),
458
+ unread: unread.map((update) => ({ ...update })),
459
+ available: unread
460
+ .filter((update) => !reserved.has(update.completionId))
461
+ .map((update) => ({ ...update })),
462
+ currentRuntimeReservations,
463
+ };
464
+ }
465
+
466
+ export function foldCompletionMailbox(
467
+ entries: readonly SessionEntry[],
468
+ options: {
469
+ parentAgentId: string;
470
+ activeRuntimeId?: string;
471
+ },
472
+ ): CompletionMailboxFold {
473
+ try {
474
+ return {
475
+ kind: "valid",
476
+ snapshot: foldOrThrow(
477
+ entries,
478
+ options.parentAgentId,
479
+ options.activeRuntimeId,
480
+ ),
481
+ };
482
+ } catch (error) {
483
+ return {
484
+ kind: "corrupt",
485
+ message: error instanceof Error ? error.message : String(error),
486
+ };
487
+ }
488
+ }
489
+
490
+ export function readCompletionMailbox(
491
+ entries: readonly SessionEntry[],
492
+ options: {
493
+ parentAgentId: string;
494
+ activeRuntimeId?: string;
495
+ },
496
+ ): CompletionMailboxSnapshot {
497
+ const folded = foldCompletionMailbox(entries, options);
498
+ if (folded.kind === "corrupt") {
499
+ throw new Error(`corrupt completion mailbox: ${folded.message}`);
500
+ }
501
+ return folded.snapshot;
502
+ }
503
+
504
+ export function appendCompletionUpdate(
505
+ session: SessionView,
506
+ input: {
507
+ parentAgentId: string;
508
+ childAgentId: string;
509
+ result: SubagentRunResult;
510
+ },
511
+ ): CompletionUpdate {
512
+ const snapshot = readCompletionMailbox(session.getEntries(), {
513
+ parentAgentId: input.parentAgentId,
514
+ });
515
+ const existing = snapshot.updates.find(
516
+ (update) =>
517
+ update.childAgentId === input.childAgentId
518
+ && update.turnId === input.result.turnId,
519
+ );
520
+ if (existing) return { ...existing };
521
+ const update = parseUpdate({
522
+ version: COMPLETION_MAILBOX_VERSION,
523
+ completionId: uuidv7(),
524
+ parentAgentId: input.parentAgentId,
525
+ childAgentId: input.childAgentId,
526
+ turnId: input.result.turnId,
527
+ stopReason: input.result.stopReason,
528
+ output: input.result.output,
529
+ outputTruncated: input.result.outputTruncated ?? false,
530
+ ...(input.result.omittedBytes !== undefined
531
+ ? { omittedBytes: input.result.omittedBytes }
532
+ : {}),
533
+ createdAt: new Date().toISOString(),
534
+ });
535
+ session.appendCustomEntry(COMPLETION_UPDATE_CUSTOM_TYPE, update);
536
+ return update;
537
+ }
538
+
539
+ export function reserveCompletionDelivery(
540
+ session: SessionView,
541
+ input: {
542
+ parentAgentId: string;
543
+ runtimeId: string;
544
+ toolCallId: string;
545
+ completionIds: readonly string[];
546
+ },
547
+ ): CompletionDelivery {
548
+ const snapshot = readCompletionMailbox(session.getEntries(), {
549
+ parentAgentId: input.parentAgentId,
550
+ activeRuntimeId: input.runtimeId,
551
+ });
552
+ if (snapshot.currentRuntimeReservations.length > 0) {
553
+ throw new Error(
554
+ "a previous wait_agent delivery is awaiting its durable tool result",
555
+ );
556
+ }
557
+ if (
558
+ input.completionIds.length === 0
559
+ || input.completionIds.length > snapshot.available.length
560
+ ) {
561
+ throw new Error("completion delivery batch is no longer available");
562
+ }
563
+ for (let index = 0; index < input.completionIds.length; index++) {
564
+ if (
565
+ input.completionIds[index]
566
+ !== snapshot.available[index]?.completionId
567
+ ) {
568
+ throw new Error(
569
+ "completion delivery batch is no longer the available FIFO prefix",
570
+ );
571
+ }
572
+ }
573
+ const delivery = parseDelivery({
574
+ version: COMPLETION_MAILBOX_VERSION,
575
+ deliveryId: uuidv7(),
576
+ runtimeId: input.runtimeId,
577
+ parentAgentId: input.parentAgentId,
578
+ toolCallId: input.toolCallId,
579
+ completionIds: [...input.completionIds],
580
+ createdAt: new Date().toISOString(),
581
+ });
582
+ session.appendCustomEntry(
583
+ COMPLETION_DELIVERY_CUSTOM_TYPE,
584
+ delivery,
585
+ );
586
+ return delivery;
587
+ }
588
+
589
+ export function releaseCompletionDeliveries(
590
+ session: SessionView,
591
+ input: {
592
+ parentAgentId: string;
593
+ runtimeId: string;
594
+ reason: string;
595
+ },
596
+ ): number {
597
+ const snapshot = readCompletionMailbox(session.getEntries(), {
598
+ parentAgentId: input.parentAgentId,
599
+ activeRuntimeId: input.runtimeId,
600
+ });
601
+ if (snapshot.currentRuntimeReservations.length === 0) return 0;
602
+ const release = parseDeliveryRelease({
603
+ version: COMPLETION_MAILBOX_VERSION,
604
+ releaseId: uuidv7(),
605
+ runtimeId: input.runtimeId,
606
+ parentAgentId: input.parentAgentId,
607
+ deliveryIds: snapshot.currentRuntimeReservations.map(
608
+ (delivery) => delivery.deliveryId,
609
+ ),
610
+ reason: input.reason,
611
+ createdAt: new Date().toISOString(),
612
+ });
613
+ session.appendCustomEntry(
614
+ COMPLETION_DELIVERY_RELEASE_CUSTOM_TYPE,
615
+ release,
616
+ );
617
+ return release.deliveryIds.length;
618
+ }
619
+
620
+ export function appendUndeliveredCompletion(
621
+ session: SessionView,
622
+ input: {
623
+ parentAgentId: string;
624
+ childAgentId: string;
625
+ result: SubagentRunResult;
626
+ error: string;
627
+ },
628
+ ): void {
629
+ session.appendCustomEntry(COMPLETION_FAILURE_CUSTOM_TYPE, {
630
+ version: COMPLETION_MAILBOX_VERSION,
631
+ parentAgentId: input.parentAgentId,
632
+ childAgentId: input.childAgentId,
633
+ turnId: input.result.turnId,
634
+ stopReason: input.result.stopReason,
635
+ output: input.result.output,
636
+ outputTruncated: input.result.outputTruncated ?? false,
637
+ ...(input.result.omittedBytes !== undefined
638
+ ? { omittedBytes: input.result.omittedBytes }
639
+ : {}),
640
+ error: input.error.slice(0, 4000),
641
+ createdAt: new Date().toISOString(),
642
+ });
643
+ }
644
+
645
+ export function unreadCompletionCounts(
646
+ snapshot: CompletionMailboxSnapshot,
647
+ ): Map<string, number> {
648
+ const counts = new Map<string, number>();
649
+ for (const update of snapshot.unread) {
650
+ counts.set(
651
+ update.childAgentId,
652
+ (counts.get(update.childAgentId) ?? 0) + 1,
653
+ );
654
+ }
655
+ return counts;
656
+ }