github-issue-tower-defence-management 1.148.25 → 1.149.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.
Files changed (63) hide show
  1. package/CHANGELOG.md +13 -0
  2. package/README.md +4 -4
  3. package/bin/adapter/entry-points/handlers/HandleScheduledEventUseCaseHandler.js +2 -1
  4. package/bin/adapter/entry-points/handlers/HandleScheduledEventUseCaseHandler.js.map +1 -1
  5. package/bin/adapter/repositories/ConfigurableSilentSessionMessageComposer.js +1 -0
  6. package/bin/adapter/repositories/ConfigurableSilentSessionMessageComposer.js.map +1 -1
  7. package/bin/adapter/repositories/FileSystemKevReportWatermarkRepository.js +126 -0
  8. package/bin/adapter/repositories/FileSystemKevReportWatermarkRepository.js.map +1 -0
  9. package/bin/adapter/repositories/ProcessListSessionSubAgentActivityRepository.js +1 -0
  10. package/bin/adapter/repositories/ProcessListSessionSubAgentActivityRepository.js.map +1 -1
  11. package/bin/adapter/repositories/TranscriptSessionSubAgentActivityRepository.js +5 -3
  12. package/bin/adapter/repositories/TranscriptSessionSubAgentActivityRepository.js.map +1 -1
  13. package/bin/domain/entities/KevReportWatermark.js +3 -0
  14. package/bin/domain/entities/KevReportWatermark.js.map +1 -0
  15. package/bin/domain/usecases/DailySecurityScanUseCase.js +56 -7
  16. package/bin/domain/usecases/DailySecurityScanUseCase.js.map +1 -1
  17. package/bin/domain/usecases/DefaultSilentSessionMessageComposer.js +9 -0
  18. package/bin/domain/usecases/DefaultSilentSessionMessageComposer.js.map +1 -1
  19. package/bin/domain/usecases/NotifySilentLiveSessionsUseCase.js +12 -11
  20. package/bin/domain/usecases/NotifySilentLiveSessionsUseCase.js.map +1 -1
  21. package/bin/domain/usecases/adapter-interfaces/KevReportWatermarkRepository.js +3 -0
  22. package/bin/domain/usecases/adapter-interfaces/KevReportWatermarkRepository.js.map +1 -0
  23. package/package.json +1 -1
  24. package/src/adapter/entry-points/handlers/HandleScheduledEventUseCaseHandler.ts +2 -0
  25. package/src/adapter/repositories/ConfigurableSilentSessionMessageComposer.test.ts +35 -0
  26. package/src/adapter/repositories/ConfigurableSilentSessionMessageComposer.ts +7 -0
  27. package/src/adapter/repositories/FileSystemKevReportWatermarkRepository.test.ts +253 -0
  28. package/src/adapter/repositories/FileSystemKevReportWatermarkRepository.ts +130 -0
  29. package/src/adapter/repositories/ProcessListSessionSubAgentActivityRepository.test.ts +2 -0
  30. package/src/adapter/repositories/ProcessListSessionSubAgentActivityRepository.ts +1 -0
  31. package/src/adapter/repositories/TranscriptOwnerCallStatusProvider.test.ts +3 -0
  32. package/src/adapter/repositories/TranscriptSessionSubAgentActivityRepository.test.ts +159 -0
  33. package/src/adapter/repositories/TranscriptSessionSubAgentActivityRepository.ts +7 -1
  34. package/src/domain/entities/KevReportWatermark.ts +4 -0
  35. package/src/domain/entities/LiveSessionActivitySnapshot.ts +1 -0
  36. package/src/domain/usecases/DailySecurityScanUseCase.test.ts +210 -0
  37. package/src/domain/usecases/DailySecurityScanUseCase.ts +84 -7
  38. package/src/domain/usecases/DefaultSilentSessionMessageComposer.test.ts +58 -1
  39. package/src/domain/usecases/DefaultSilentSessionMessageComposer.ts +21 -0
  40. package/src/domain/usecases/NotifySilentLiveSessionsUseCase.test.ts +146 -8
  41. package/src/domain/usecases/NotifySilentLiveSessionsUseCase.ts +19 -10
  42. package/src/domain/usecases/adapter-interfaces/KevReportWatermarkRepository.ts +11 -0
  43. package/src/domain/usecases/adapter-interfaces/SilentSessionMessageComposer.ts +3 -0
  44. package/types/adapter/entry-points/handlers/HandleScheduledEventUseCaseHandler.d.ts.map +1 -1
  45. package/types/adapter/repositories/ConfigurableSilentSessionMessageComposer.d.ts +2 -0
  46. package/types/adapter/repositories/ConfigurableSilentSessionMessageComposer.d.ts.map +1 -1
  47. package/types/adapter/repositories/FileSystemKevReportWatermarkRepository.d.ts +10 -0
  48. package/types/adapter/repositories/FileSystemKevReportWatermarkRepository.d.ts.map +1 -0
  49. package/types/adapter/repositories/ProcessListSessionSubAgentActivityRepository.d.ts.map +1 -1
  50. package/types/adapter/repositories/TranscriptSessionSubAgentActivityRepository.d.ts.map +1 -1
  51. package/types/domain/entities/KevReportWatermark.d.ts +5 -0
  52. package/types/domain/entities/KevReportWatermark.d.ts.map +1 -0
  53. package/types/domain/entities/LiveSessionActivitySnapshot.d.ts +1 -0
  54. package/types/domain/entities/LiveSessionActivitySnapshot.d.ts.map +1 -1
  55. package/types/domain/usecases/DailySecurityScanUseCase.d.ts +3 -1
  56. package/types/domain/usecases/DailySecurityScanUseCase.d.ts.map +1 -1
  57. package/types/domain/usecases/DefaultSilentSessionMessageComposer.d.ts +2 -0
  58. package/types/domain/usecases/DefaultSilentSessionMessageComposer.d.ts.map +1 -1
  59. package/types/domain/usecases/NotifySilentLiveSessionsUseCase.d.ts.map +1 -1
  60. package/types/domain/usecases/adapter-interfaces/KevReportWatermarkRepository.d.ts +15 -0
  61. package/types/domain/usecases/adapter-interfaces/KevReportWatermarkRepository.d.ts.map +1 -0
  62. package/types/domain/usecases/adapter-interfaces/SilentSessionMessageComposer.d.ts +1 -0
  63. package/types/domain/usecases/adapter-interfaces/SilentSessionMessageComposer.d.ts.map +1 -1
@@ -0,0 +1,130 @@
1
+ import * as fs from 'fs';
2
+ import * as os from 'os';
3
+ import * as path from 'path';
4
+ import { KevReportWatermark } from '../../domain/entities/KevReportWatermark';
5
+ import {
6
+ KevReportWatermarkLoadResult,
7
+ KevReportWatermarkRepository,
8
+ } from '../../domain/usecases/adapter-interfaces/KevReportWatermarkRepository';
9
+
10
+ const isRecord = (value: unknown): value is Record<string, unknown> =>
11
+ typeof value === 'object' && value !== null && !Array.isArray(value);
12
+
13
+ const errorCode = (error: unknown): string | null => {
14
+ if (!isRecord(error)) {
15
+ return null;
16
+ }
17
+ const code = error.code;
18
+ return typeof code === 'string' ? code : null;
19
+ };
20
+
21
+ const isCalendarYmd = (value: string): boolean => {
22
+ if (!/^\d{4}-\d{2}-\d{2}$/.test(value)) {
23
+ return false;
24
+ }
25
+ const parsed = new Date(`${value}T00:00:00.000Z`);
26
+ return (
27
+ !Number.isNaN(parsed.getTime()) &&
28
+ parsed.toISOString().slice(0, 10) === value
29
+ );
30
+ };
31
+
32
+ const defaultStateFilePath = (): string => {
33
+ const base = process.env.XDG_CACHE_HOME ?? path.join(os.homedir(), '.cache');
34
+ return path.join(base, 'tdpm', 'kev-report-watermark.json');
35
+ };
36
+
37
+ export class FileSystemKevReportWatermarkRepository implements KevReportWatermarkRepository {
38
+ constructor(
39
+ private readonly stateFilePath: string = defaultStateFilePath(),
40
+ ) {}
41
+
42
+ load = async (): Promise<KevReportWatermarkLoadResult> => {
43
+ let raw: string;
44
+ try {
45
+ raw = fs.readFileSync(this.stateFilePath, 'utf8');
46
+ } catch (error) {
47
+ if (errorCode(error) === 'ENOENT') {
48
+ return { type: 'absent' };
49
+ }
50
+ return this.unreadable(
51
+ `the stored watermark file could not be read (${String(error)})`,
52
+ );
53
+ }
54
+
55
+ let parsed: unknown;
56
+ try {
57
+ parsed = JSON.parse(raw);
58
+ } catch (error) {
59
+ return this.unreadable(
60
+ `the stored watermark file does not contain valid JSON (${String(error)})`,
61
+ );
62
+ }
63
+
64
+ if (!isRecord(parsed)) {
65
+ return this.unreadable(
66
+ 'the stored watermark file does not contain a JSON object',
67
+ );
68
+ }
69
+
70
+ const lastReportedDateAdded = parsed.lastReportedDateAdded;
71
+ if (typeof lastReportedDateAdded !== 'string') {
72
+ return this.unreadable(
73
+ `the stored lastReportedDateAdded is not a string (${JSON.stringify(lastReportedDateAdded)})`,
74
+ );
75
+ }
76
+ if (!isCalendarYmd(lastReportedDateAdded)) {
77
+ return this.unreadable(
78
+ `the stored lastReportedDateAdded is not a calendar date in YYYY-MM-DD form (${JSON.stringify(lastReportedDateAdded)})`,
79
+ );
80
+ }
81
+
82
+ const reportedCveIdsOnLastReportedDateAdded =
83
+ parsed.reportedCveIdsOnLastReportedDateAdded;
84
+ if (!Array.isArray(reportedCveIdsOnLastReportedDateAdded)) {
85
+ return this.unreadable(
86
+ 'the stored reportedCveIdsOnLastReportedDateAdded is not an array',
87
+ );
88
+ }
89
+ if (
90
+ !reportedCveIdsOnLastReportedDateAdded.every(
91
+ (cveId) => typeof cveId === 'string',
92
+ )
93
+ ) {
94
+ return this.unreadable(
95
+ 'the stored reportedCveIdsOnLastReportedDateAdded contains a non-string entry',
96
+ );
97
+ }
98
+
99
+ return {
100
+ type: 'stored',
101
+ watermark: {
102
+ lastReportedDateAdded,
103
+ reportedCveIdsOnLastReportedDateAdded:
104
+ reportedCveIdsOnLastReportedDateAdded,
105
+ },
106
+ };
107
+ };
108
+
109
+ save = async (watermark: KevReportWatermark): Promise<void> => {
110
+ try {
111
+ const directory = path.dirname(this.stateFilePath);
112
+ fs.mkdirSync(directory, { recursive: true });
113
+ const temporaryPath = `${this.stateFilePath}.${process.pid}.tmp`;
114
+ fs.writeFileSync(temporaryPath, JSON.stringify(watermark));
115
+ fs.renameSync(temporaryPath, this.stateFilePath);
116
+ } catch (error) {
117
+ console.error(
118
+ `Unable to write the KEV report watermark to ${this.stateFilePath}: ${String(error)}`,
119
+ );
120
+ throw error;
121
+ }
122
+ };
123
+
124
+ private unreadable = (reason: string): KevReportWatermarkLoadResult => {
125
+ console.error(
126
+ `Unable to use the KEV report watermark stored at ${this.stateFilePath}: ${reason}. The file is left untouched for inspection.`,
127
+ );
128
+ return { type: 'unreadable', reason };
129
+ };
130
+ }
@@ -59,6 +59,7 @@ describe('ProcessListSessionSubAgentActivityRepository', () => {
59
59
  silentSeconds: 180,
60
60
  runningSeconds: 1200,
61
61
  waitingOnExternalProcess: false,
62
+ finishedResultUnconsumed: false,
62
63
  },
63
64
  ]);
64
65
  });
@@ -88,6 +89,7 @@ describe('ProcessListSessionSubAgentActivityRepository', () => {
88
89
  silentSeconds: 0,
89
90
  runningSeconds: 30,
90
91
  waitingOnExternalProcess: false,
92
+ finishedResultUnconsumed: false,
91
93
  },
92
94
  ]);
93
95
  });
@@ -44,6 +44,7 @@ export class ProcessListSessionSubAgentActivityRepository implements SessionSubA
44
44
  silentSeconds: this.silentSecondsResolver.resolveSilentSeconds(label),
45
45
  runningSeconds: process.elapsedSeconds,
46
46
  waitingOnExternalProcess: false,
47
+ finishedResultUnconsumed: false,
47
48
  };
48
49
  const existing = result.get(sessionName);
49
50
  if (existing === undefined) {
@@ -940,6 +940,9 @@ describe('TranscriptOwnerCallStatusProvider wired into NotifySilentLiveSessionsU
940
940
  .fn()
941
941
  .mockReturnValue('MAIN_STALLED_STALE'),
942
942
  composeSubAgentSection: jest.fn().mockReturnValue('SUBAGENT'),
943
+ composeSubAgentUnconsumedResultSection: jest
944
+ .fn()
945
+ .mockReturnValue('SUBAGENT_UNCONSUMED'),
943
946
  },
944
947
  { sleep: jest.fn().mockResolvedValue(undefined) },
945
948
  );
@@ -279,6 +279,7 @@ describe('TranscriptSessionSubAgentActivityRepository', () => {
279
279
  silentSeconds: 120,
280
280
  runningSeconds: 900,
281
281
  waitingOnExternalProcess: false,
282
+ finishedResultUnconsumed: false,
282
283
  },
283
284
  ]);
284
285
  });
@@ -400,6 +401,7 @@ describe('TranscriptSessionSubAgentActivityRepository', () => {
400
401
  silentSeconds: 60,
401
402
  runningSeconds: 600,
402
403
  waitingOnExternalProcess: false,
404
+ finishedResultUnconsumed: false,
403
405
  },
404
406
  ]);
405
407
  });
@@ -474,6 +476,7 @@ describe('TranscriptSessionSubAgentActivityRepository', () => {
474
476
  silentSeconds: 60,
475
477
  runningSeconds: 600,
476
478
  waitingOnExternalProcess: false,
479
+ finishedResultUnconsumed: false,
477
480
  },
478
481
  ]);
479
482
  });
@@ -525,6 +528,7 @@ describe('TranscriptSessionSubAgentActivityRepository', () => {
525
528
  silentSeconds: 600,
526
529
  runningSeconds: 900,
527
530
  waitingOnExternalProcess: false,
531
+ finishedResultUnconsumed: false,
528
532
  },
529
533
  ]);
530
534
  });
@@ -555,6 +559,7 @@ describe('TranscriptSessionSubAgentActivityRepository', () => {
555
559
  silentSeconds: fourDaysSeconds,
556
560
  runningSeconds: fourDaysSeconds,
557
561
  waitingOnExternalProcess: false,
562
+ finishedResultUnconsumed: false,
558
563
  },
559
564
  ]);
560
565
  });
@@ -592,6 +597,7 @@ describe('TranscriptSessionSubAgentActivityRepository', () => {
592
597
  silentSeconds: 60,
593
598
  runningSeconds: 600,
594
599
  waitingOnExternalProcess: false,
600
+ finishedResultUnconsumed: false,
595
601
  },
596
602
  ]);
597
603
  fs.rmSync(targetDir, { force: true, recursive: true });
@@ -680,6 +686,7 @@ describe('TranscriptSessionSubAgentActivityRepository', () => {
680
686
  silentSeconds: 0,
681
687
  runningSeconds: 0,
682
688
  waitingOnExternalProcess: false,
689
+ finishedResultUnconsumed: false,
683
690
  },
684
691
  ]);
685
692
  });
@@ -731,6 +738,7 @@ describe('TranscriptSessionSubAgentActivityRepository', () => {
731
738
  silentSeconds: 120,
732
739
  runningSeconds: 900,
733
740
  waitingOnExternalProcess: false,
741
+ finishedResultUnconsumed: false,
734
742
  },
735
743
  ]);
736
744
  });
@@ -767,6 +775,7 @@ describe('TranscriptSessionSubAgentActivityRepository', () => {
767
775
  silentSeconds: twoDaysSeconds,
768
776
  runningSeconds: twoDaysSeconds,
769
777
  waitingOnExternalProcess: false,
778
+ finishedResultUnconsumed: false,
770
779
  },
771
780
  ]);
772
781
  });
@@ -821,6 +830,7 @@ describe('TranscriptSessionSubAgentActivityRepository', () => {
821
830
  silentSeconds: 600,
822
831
  runningSeconds: 900,
823
832
  waitingOnExternalProcess: true,
833
+ finishedResultUnconsumed: false,
824
834
  },
825
835
  ]);
826
836
  },
@@ -956,6 +966,7 @@ describe('TranscriptSessionSubAgentActivityRepository', () => {
956
966
  silentSeconds: 600,
957
967
  runningSeconds: 900,
958
968
  waitingOnExternalProcess: true,
969
+ finishedResultUnconsumed: false,
959
970
  },
960
971
  ]);
961
972
  });
@@ -987,6 +998,7 @@ describe('TranscriptSessionSubAgentActivityRepository', () => {
987
998
  silentSeconds: 600,
988
999
  runningSeconds: 900,
989
1000
  waitingOnExternalProcess: false,
1001
+ finishedResultUnconsumed: false,
990
1002
  },
991
1003
  ]);
992
1004
  });
@@ -1282,6 +1294,7 @@ describe('TranscriptSessionSubAgentActivityRepository', () => {
1282
1294
  silentSeconds: 120,
1283
1295
  runningSeconds: 900,
1284
1296
  waitingOnExternalProcess: false,
1297
+ finishedResultUnconsumed: false,
1285
1298
  },
1286
1299
  ]);
1287
1300
  });
@@ -1337,6 +1350,7 @@ describe('TranscriptSessionSubAgentActivityRepository', () => {
1337
1350
  silentSeconds: 600,
1338
1351
  runningSeconds: 900,
1339
1352
  waitingOnExternalProcess: false,
1353
+ finishedResultUnconsumed: false,
1340
1354
  },
1341
1355
  ]);
1342
1356
  });
@@ -1367,6 +1381,151 @@ describe('TranscriptSessionSubAgentActivityRepository', () => {
1367
1381
  silentSeconds: 120,
1368
1382
  runningSeconds: 900,
1369
1383
  waitingOnExternalProcess: false,
1384
+ finishedResultUnconsumed: false,
1385
+ },
1386
+ ]);
1387
+ });
1388
+
1389
+ it('reports a finished sub-agent still listed in the running-subagents record as an unconsumed result carrying its silence age', async () => {
1390
+ const sessionName = 'https_//github_com/owner/repo/issues/9';
1391
+ const unconsumedAgentId = 'aaabbbbcccc30001';
1392
+ writeAgentTranscript(
1393
+ sessionName,
1394
+ unconsumedAgentId,
1395
+ finishedEntries('2026-06-27T10:30:00.000Z'),
1396
+ nowEpochSeconds - 5220,
1397
+ );
1398
+ const repository = new TranscriptSessionSubAgentActivityRepository(
1399
+ createResolver(),
1400
+ emptyProcessLister(),
1401
+ now,
1402
+ livenessResolverWith([unconsumedAgentId]),
1403
+ );
1404
+
1405
+ const result = await repository.listSubAgentActivitiesBySessionName(
1406
+ [sessionName],
1407
+ transcriptMapFor([sessionName]),
1408
+ );
1409
+
1410
+ expect(result.get(sessionName)).toEqual([
1411
+ {
1412
+ label: `agent-${unconsumedAgentId}`,
1413
+ silentSeconds: 5220,
1414
+ runningSeconds: 5400,
1415
+ waitingOnExternalProcess: false,
1416
+ finishedResultUnconsumed: true,
1417
+ },
1418
+ ]);
1419
+ });
1420
+
1421
+ it('reports a finished sub-agent whose tail is a final text answer as an unconsumed result while it stays in the running-subagents record', async () => {
1422
+ const sessionName = 'https_//github_com/owner/repo/issues/9';
1423
+ const unconsumedAgentId = 'aaabbbbcccc30002';
1424
+ writeAgentTranscript(
1425
+ sessionName,
1426
+ unconsumedAgentId,
1427
+ finalTextAnswerEntries('2026-06-27T11:00:00.000Z'),
1428
+ nowEpochSeconds - 360,
1429
+ );
1430
+ const repository = new TranscriptSessionSubAgentActivityRepository(
1431
+ createResolver(),
1432
+ emptyProcessLister(),
1433
+ now,
1434
+ livenessResolverWith([unconsumedAgentId]),
1435
+ );
1436
+
1437
+ const result = await repository.listSubAgentActivitiesBySessionName(
1438
+ [sessionName],
1439
+ transcriptMapFor([sessionName]),
1440
+ );
1441
+
1442
+ expect(result.get(sessionName)).toEqual([
1443
+ {
1444
+ label: `agent-${unconsumedAgentId}`,
1445
+ silentSeconds: 360,
1446
+ runningSeconds: 3600,
1447
+ waitingOnExternalProcess: false,
1448
+ finishedResultUnconsumed: true,
1449
+ },
1450
+ ]);
1451
+ });
1452
+
1453
+ it('discards a finished sub-agent when the running-subagents record cannot be resolved', async () => {
1454
+ const sessionName = 'https_//github_com/owner/repo/issues/9';
1455
+ const finishedAgentId = 'aaabbbbcccc30003';
1456
+ writeAgentTranscript(
1457
+ sessionName,
1458
+ finishedAgentId,
1459
+ finishedEntries('2026-06-27T10:30:00.000Z'),
1460
+ nowEpochSeconds - 5220,
1461
+ );
1462
+ const repository = new TranscriptSessionSubAgentActivityRepository(
1463
+ createResolver(),
1464
+ emptyProcessLister(),
1465
+ now,
1466
+ livenessResolverWith(null),
1467
+ );
1468
+
1469
+ const result = await repository.listSubAgentActivitiesBySessionName(
1470
+ [sessionName],
1471
+ transcriptMapFor([sessionName]),
1472
+ );
1473
+
1474
+ expect(result.size).toBe(0);
1475
+ });
1476
+
1477
+ it('discards a finished sub-agent whose id has already been removed from the running-subagents record', async () => {
1478
+ const sessionName = 'https_//github_com/owner/repo/issues/9';
1479
+ const finishedAgentId = 'aaabbbbcccc30004';
1480
+ writeAgentTranscript(
1481
+ sessionName,
1482
+ finishedAgentId,
1483
+ finishedEntries('2026-06-27T10:30:00.000Z'),
1484
+ nowEpochSeconds - 5220,
1485
+ );
1486
+ const repository = new TranscriptSessionSubAgentActivityRepository(
1487
+ createResolver(),
1488
+ emptyProcessLister(),
1489
+ now,
1490
+ livenessResolverWith(['aaabbbbcccc30005']),
1491
+ );
1492
+
1493
+ const result = await repository.listSubAgentActivitiesBySessionName(
1494
+ [sessionName],
1495
+ transcriptMapFor([sessionName]),
1496
+ );
1497
+
1498
+ expect(result.size).toBe(0);
1499
+ });
1500
+
1501
+ it('reports a still-working sub-agent listed in the running-subagents record as having no unconsumed result', async () => {
1502
+ const sessionName = 'https_//github_com/owner/repo/issues/9';
1503
+ const workingAgentId = 'aaabbbbcccc30006';
1504
+ writeAgentTranscript(
1505
+ sessionName,
1506
+ workingAgentId,
1507
+ pendingToolUseEntries('2026-06-27T11:45:00.000Z'),
1508
+ nowEpochSeconds - 600,
1509
+ );
1510
+ const repository = new TranscriptSessionSubAgentActivityRepository(
1511
+ createResolver(),
1512
+ emptyProcessLister(),
1513
+ now,
1514
+ livenessResolverWith([workingAgentId]),
1515
+ );
1516
+
1517
+ const result = await repository.listSubAgentActivitiesBySessionName(
1518
+ [sessionName],
1519
+ transcriptMapFor([sessionName]),
1520
+ );
1521
+
1522
+ expect(result.get(sessionName)).toEqual([
1523
+ {
1524
+ label: `agent-${workingAgentId}`,
1525
+ silentSeconds: 600,
1526
+ runningSeconds: 900,
1527
+ waitingOnExternalProcess: false,
1528
+ finishedResultUnconsumed: false,
1370
1529
  },
1371
1530
  ]);
1372
1531
  });
@@ -344,6 +344,7 @@ export class TranscriptSessionSubAgentActivityRepository implements SessionSubAg
344
344
  filePath,
345
345
  fileName,
346
346
  nowEpochSeconds,
347
+ liveSubAgentIds !== null,
347
348
  loadNormalizedProcessCommandLines,
348
349
  );
349
350
  if (activity !== null) {
@@ -357,6 +358,7 @@ export class TranscriptSessionSubAgentActivityRepository implements SessionSubAg
357
358
  filePath: string,
358
359
  fileName: string,
359
360
  nowEpochSeconds: number,
361
+ listedInRunningSubAgentsRecord: boolean,
360
362
  loadNormalizedProcessCommandLines: () => Promise<string[]>,
361
363
  ): Promise<SubAgentActivity | null> => {
362
364
  let content: string;
@@ -368,7 +370,10 @@ export class TranscriptSessionSubAgentActivityRepository implements SessionSubAg
368
370
  return null;
369
371
  }
370
372
  const transcript = parseTranscript(content);
371
- if (transcript.lastEntryIndicatesCompletion) {
373
+ if (
374
+ transcript.lastEntryIndicatesCompletion &&
375
+ !listedInRunningSubAgentsRecord
376
+ ) {
372
377
  return null;
373
378
  }
374
379
  const silentSeconds = clampToZero(
@@ -388,6 +393,7 @@ export class TranscriptSessionSubAgentActivityRepository implements SessionSubAg
388
393
  transcript.pendingToolCommands,
389
394
  loadNormalizedProcessCommandLines,
390
395
  )),
396
+ finishedResultUnconsumed: transcript.lastEntryIndicatesCompletion,
391
397
  };
392
398
  };
393
399
 
@@ -0,0 +1,4 @@
1
+ export type KevReportWatermark = {
2
+ lastReportedDateAdded: string;
3
+ reportedCveIdsOnLastReportedDateAdded: string[];
4
+ };
@@ -3,6 +3,7 @@ export type SubAgentActivity = {
3
3
  silentSeconds: number;
4
4
  runningSeconds: number;
5
5
  waitingOnExternalProcess: boolean;
6
+ finishedResultUnconsumed: boolean;
6
7
  };
7
8
 
8
9
  export type LiveSessionActivitySnapshot = {