github-issue-tower-defence-management 1.139.1 → 1.140.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.
Files changed (99) hide show
  1. package/CHANGELOG.md +14 -0
  2. package/README.md +6 -0
  3. package/bin/adapter/entry-points/handlers/HandleScheduledEventUseCaseHandler.js +43 -2
  4. package/bin/adapter/entry-points/handlers/HandleScheduledEventUseCaseHandler.js.map +1 -1
  5. package/bin/adapter/entry-points/handlers/tokenExhaustionHandover.js +31 -0
  6. package/bin/adapter/entry-points/handlers/tokenExhaustionHandover.js.map +1 -0
  7. package/bin/adapter/repositories/FileHandoverStateRepository.js +99 -0
  8. package/bin/adapter/repositories/FileHandoverStateRepository.js.map +1 -0
  9. package/bin/adapter/repositories/FileSystemSessionOutputActivityRepository.js +6 -40
  10. package/bin/adapter/repositories/FileSystemSessionOutputActivityRepository.js.map +1 -1
  11. package/bin/adapter/repositories/NodeProcessSignalRepository.js +44 -0
  12. package/bin/adapter/repositories/NodeProcessSignalRepository.js.map +1 -0
  13. package/bin/adapter/repositories/NodeTmuxSessionRepository.js +67 -1
  14. package/bin/adapter/repositories/NodeTmuxSessionRepository.js.map +1 -1
  15. package/bin/adapter/repositories/ProcClaudeHandoverSessionRepository.js +206 -0
  16. package/bin/adapter/repositories/ProcClaudeHandoverSessionRepository.js.map +1 -0
  17. package/bin/adapter/repositories/RateLimitSnapshotRepository.js +44 -0
  18. package/bin/adapter/repositories/RateLimitSnapshotRepository.js.map +1 -0
  19. package/bin/domain/entities/ClaudeHandoverSession.js +3 -0
  20. package/bin/domain/entities/ClaudeHandoverSession.js.map +1 -0
  21. package/bin/domain/entities/TokenExhaustionHandoverState.js +3 -0
  22. package/bin/domain/entities/TokenExhaustionHandoverState.js.map +1 -0
  23. package/bin/domain/usecases/NotifySilentLiveSessionsUseCase.js +9 -46
  24. package/bin/domain/usecases/NotifySilentLiveSessionsUseCase.js.map +1 -1
  25. package/bin/domain/usecases/TokenExhaustionHandoverUseCase.js +255 -0
  26. package/bin/domain/usecases/TokenExhaustionHandoverUseCase.js.map +1 -0
  27. package/bin/domain/usecases/adapter-interfaces/ClaudeHandoverSessionRepository.js +3 -0
  28. package/bin/domain/usecases/adapter-interfaces/ClaudeHandoverSessionRepository.js.map +1 -0
  29. package/bin/domain/usecases/adapter-interfaces/ProcessSignalRepository.js +3 -0
  30. package/bin/domain/usecases/adapter-interfaces/ProcessSignalRepository.js.map +1 -0
  31. package/bin/domain/usecases/adapter-interfaces/TokenExhaustionHandoverStateRepository.js +3 -0
  32. package/bin/domain/usecases/adapter-interfaces/TokenExhaustionHandoverStateRepository.js.map +1 -0
  33. package/bin/domain/usecases/adapter-interfaces/TokenRateLimitSnapshotRepository.js +3 -0
  34. package/bin/domain/usecases/adapter-interfaces/TokenRateLimitSnapshotRepository.js.map +1 -0
  35. package/package.json +1 -1
  36. package/src/adapter/entry-points/handlers/HandleScheduledEventUseCaseHandler.ts +32 -0
  37. package/src/adapter/entry-points/handlers/tokenExhaustionHandover.ts +79 -0
  38. package/src/adapter/repositories/FileHandoverStateRepository.test.ts +114 -0
  39. package/src/adapter/repositories/FileHandoverStateRepository.ts +76 -0
  40. package/src/adapter/repositories/FileSystemSessionOutputActivityRepository.test.ts +24 -11
  41. package/src/adapter/repositories/FileSystemSessionOutputActivityRepository.ts +8 -50
  42. package/src/adapter/repositories/NodeProcessSignalRepository.test.ts +82 -0
  43. package/src/adapter/repositories/NodeProcessSignalRepository.ts +47 -0
  44. package/src/adapter/repositories/NodeTmuxSessionRepository.test.ts +132 -0
  45. package/src/adapter/repositories/NodeTmuxSessionRepository.ts +100 -0
  46. package/src/adapter/repositories/ProcClaudeHandoverSessionRepository.test.ts +194 -0
  47. package/src/adapter/repositories/ProcClaudeHandoverSessionRepository.ts +208 -0
  48. package/src/adapter/repositories/RateLimitSnapshotRepository.ts +50 -0
  49. package/src/domain/entities/ClaudeHandoverSession.ts +11 -0
  50. package/src/domain/entities/LiveSessionActivitySnapshot.ts +0 -1
  51. package/src/domain/entities/LiveSessionOutputActivity.ts +0 -1
  52. package/src/domain/entities/TokenExhaustionHandoverState.ts +8 -0
  53. package/src/domain/usecases/NotifySilentLiveSessionsUseCase.test.ts +7 -114
  54. package/src/domain/usecases/NotifySilentLiveSessionsUseCase.ts +8 -53
  55. package/src/domain/usecases/TokenExhaustionHandoverUseCase.test.ts +696 -0
  56. package/src/domain/usecases/TokenExhaustionHandoverUseCase.ts +413 -0
  57. package/src/domain/usecases/adapter-interfaces/ClaudeHandoverSessionRepository.ts +5 -0
  58. package/src/domain/usecases/adapter-interfaces/ProcessSignalRepository.ts +5 -0
  59. package/src/domain/usecases/adapter-interfaces/TmuxSessionRepository.ts +2 -0
  60. package/src/domain/usecases/adapter-interfaces/TokenExhaustionHandoverStateRepository.ts +6 -0
  61. package/src/domain/usecases/adapter-interfaces/TokenRateLimitSnapshotRepository.ts +22 -0
  62. package/src/domain/usecases/intmux/InTmuxByHumanSessionReconcileUseCase.test.ts +2 -0
  63. package/types/adapter/entry-points/handlers/HandleScheduledEventUseCaseHandler.d.ts.map +1 -1
  64. package/types/adapter/entry-points/handlers/tokenExhaustionHandover.d.ts +14 -0
  65. package/types/adapter/entry-points/handlers/tokenExhaustionHandover.d.ts.map +1 -0
  66. package/types/adapter/repositories/FileHandoverStateRepository.d.ts +11 -0
  67. package/types/adapter/repositories/FileHandoverStateRepository.d.ts.map +1 -0
  68. package/types/adapter/repositories/FileSystemSessionOutputActivityRepository.d.ts +1 -1
  69. package/types/adapter/repositories/FileSystemSessionOutputActivityRepository.d.ts.map +1 -1
  70. package/types/adapter/repositories/NodeProcessSignalRepository.d.ts +8 -0
  71. package/types/adapter/repositories/NodeProcessSignalRepository.d.ts.map +1 -0
  72. package/types/adapter/repositories/NodeTmuxSessionRepository.d.ts +7 -1
  73. package/types/adapter/repositories/NodeTmuxSessionRepository.d.ts.map +1 -1
  74. package/types/adapter/repositories/ProcClaudeHandoverSessionRepository.d.ts +13 -0
  75. package/types/adapter/repositories/ProcClaudeHandoverSessionRepository.d.ts.map +1 -0
  76. package/types/adapter/repositories/RateLimitSnapshotRepository.d.ts +9 -0
  77. package/types/adapter/repositories/RateLimitSnapshotRepository.d.ts.map +1 -0
  78. package/types/domain/entities/ClaudeHandoverSession.d.ts +10 -0
  79. package/types/domain/entities/ClaudeHandoverSession.d.ts.map +1 -0
  80. package/types/domain/entities/LiveSessionActivitySnapshot.d.ts +0 -1
  81. package/types/domain/entities/LiveSessionActivitySnapshot.d.ts.map +1 -1
  82. package/types/domain/entities/LiveSessionOutputActivity.d.ts +0 -1
  83. package/types/domain/entities/LiveSessionOutputActivity.d.ts.map +1 -1
  84. package/types/domain/entities/TokenExhaustionHandoverState.d.ts +8 -0
  85. package/types/domain/entities/TokenExhaustionHandoverState.d.ts.map +1 -0
  86. package/types/domain/usecases/NotifySilentLiveSessionsUseCase.d.ts +0 -1
  87. package/types/domain/usecases/NotifySilentLiveSessionsUseCase.d.ts.map +1 -1
  88. package/types/domain/usecases/TokenExhaustionHandoverUseCase.d.ts +50 -0
  89. package/types/domain/usecases/TokenExhaustionHandoverUseCase.d.ts.map +1 -0
  90. package/types/domain/usecases/adapter-interfaces/ClaudeHandoverSessionRepository.d.ts +5 -0
  91. package/types/domain/usecases/adapter-interfaces/ClaudeHandoverSessionRepository.d.ts.map +1 -0
  92. package/types/domain/usecases/adapter-interfaces/ProcessSignalRepository.d.ts +6 -0
  93. package/types/domain/usecases/adapter-interfaces/ProcessSignalRepository.d.ts.map +1 -0
  94. package/types/domain/usecases/adapter-interfaces/TmuxSessionRepository.d.ts +2 -0
  95. package/types/domain/usecases/adapter-interfaces/TmuxSessionRepository.d.ts.map +1 -1
  96. package/types/domain/usecases/adapter-interfaces/TokenExhaustionHandoverStateRepository.d.ts +6 -0
  97. package/types/domain/usecases/adapter-interfaces/TokenExhaustionHandoverStateRepository.d.ts.map +1 -0
  98. package/types/domain/usecases/adapter-interfaces/TokenRateLimitSnapshotRepository.d.ts +21 -0
  99. package/types/domain/usecases/adapter-interfaces/TokenRateLimitSnapshotRepository.d.ts.map +1 -0
@@ -10,7 +10,6 @@ import {
10
10
  DEFAULT_NOTIFICATION_STAGGER_SECONDS,
11
11
  DEFAULT_CANDIDATE_DEBOUNCE_RECENCY_WINDOW_SECONDS,
12
12
  DEFAULT_HUB_TASK_STATUS_CACHE_TTL_SECONDS,
13
- IN_PROGRESS_TOOL_CALL_MAX_SUPPRESS_SECONDS,
14
13
  } from './NotifySilentLiveSessionsUseCase';
15
14
  import { Issue } from '../entities/Issue';
16
15
  import { LiveSessionProcessSnapshotProvider } from './adapter-interfaces/LiveSessionProcessSnapshotProvider';
@@ -268,7 +267,6 @@ describe('NotifySilentLiveSessionsUseCase', () => {
268
267
  sessionName,
269
268
  lastOutputEpochSeconds:
270
269
  nowEpochSeconds - DEFAULT_MAIN_SILENT_THRESHOLD_SECONDS,
271
- hasInProgressToolCall: false,
272
270
  },
273
271
  ],
274
272
  );
@@ -299,7 +297,6 @@ describe('NotifySilentLiveSessionsUseCase', () => {
299
297
  sessionName: GITHUB_SESSION,
300
298
  lastOutputEpochSeconds:
301
299
  nowEpochSeconds - DEFAULT_MAIN_SILENT_THRESHOLD_SECONDS,
302
- hasInProgressToolCall: false,
303
300
  },
304
301
  ],
305
302
  );
@@ -323,7 +320,6 @@ describe('NotifySilentLiveSessionsUseCase', () => {
323
320
  sessionName: pullRequestSession,
324
321
  lastOutputEpochSeconds:
325
322
  nowEpochSeconds - DEFAULT_MAIN_SILENT_THRESHOLD_SECONDS,
326
- hasInProgressToolCall: false,
327
323
  },
328
324
  ],
329
325
  );
@@ -345,7 +341,6 @@ describe('NotifySilentLiveSessionsUseCase', () => {
345
341
  sessionName: 'workbench',
346
342
  lastOutputEpochSeconds:
347
343
  nowEpochSeconds - DEFAULT_MAIN_SILENT_THRESHOLD_SECONDS,
348
- hasInProgressToolCall: false,
349
344
  },
350
345
  ],
351
346
  );
@@ -379,13 +374,11 @@ describe('NotifySilentLiveSessionsUseCase', () => {
379
374
  sessionName: GITHUB_SESSION,
380
375
  lastOutputEpochSeconds:
381
376
  nowEpochSeconds - DEFAULT_MAIN_SILENT_THRESHOLD_SECONDS,
382
- hasInProgressToolCall: false,
383
377
  },
384
378
  {
385
379
  sessionName: 'orchestrator',
386
380
  lastOutputEpochSeconds:
387
381
  nowEpochSeconds - DEFAULT_MAIN_SILENT_THRESHOLD_SECONDS,
388
- hasInProgressToolCall: false,
389
382
  },
390
383
  ],
391
384
  );
@@ -501,7 +494,6 @@ describe('NotifySilentLiveSessionsUseCase', () => {
501
494
  sessionName: GITHUB_SESSION,
502
495
  lastOutputEpochSeconds:
503
496
  nowEpochSeconds - DEFAULT_MAIN_SILENT_THRESHOLD_SECONDS + 1,
504
- hasInProgressToolCall: false,
505
497
  },
506
498
  ],
507
499
  );
@@ -529,7 +521,6 @@ describe('NotifySilentLiveSessionsUseCase', () => {
529
521
  sessionName: GITHUB_SESSION,
530
522
  lastOutputEpochSeconds:
531
523
  nowEpochSeconds - DEFAULT_MAIN_SILENT_THRESHOLD_SECONDS,
532
- hasInProgressToolCall: false,
533
524
  },
534
525
  ],
535
526
  );
@@ -550,7 +541,7 @@ describe('NotifySilentLiveSessionsUseCase', () => {
550
541
  ).toHaveBeenCalledWith(GITHUB_SESSION, MAIN_STALLED_SECTION);
551
542
  });
552
543
 
553
- it('does not send the main stalled section when the session is silent past the threshold but is waiting on a running tool call', async () => {
544
+ it('sends the main stalled section for a session silent past the threshold regardless of any in-progress tool call, because apparent busyness never suppresses the reminder', async () => {
554
545
  setupLiveInteractiveSession(GITHUB_SESSION);
555
546
  mockSessionOutputActivityRepository.listSessionOutputActivities.mockResolvedValue(
556
547
  [
@@ -558,34 +549,6 @@ describe('NotifySilentLiveSessionsUseCase', () => {
558
549
  sessionName: GITHUB_SESSION,
559
550
  lastOutputEpochSeconds:
560
551
  nowEpochSeconds - DEFAULT_MAIN_SILENT_THRESHOLD_SECONDS,
561
- hasInProgressToolCall: true,
562
- },
563
- ],
564
- );
565
- mockOwnerCallStatusProvider.listUnansweredOwnerCallEpochSecondsBySessionName.mockResolvedValue(
566
- new Map<string, number>(),
567
- );
568
-
569
- await useCase.run(runParams());
570
-
571
- expect(
572
- mockMessageComposer.composeMainStalledSection,
573
- ).not.toHaveBeenCalled();
574
- expect(
575
- mockNotificationRepository.sendSelfCheckNotification,
576
- ).not.toHaveBeenCalled();
577
- });
578
-
579
- it('sends the main stalled section when the in-progress tool call has been pending longer than the suppression bound', async () => {
580
- setupLiveInteractiveSession(GITHUB_SESSION);
581
- const pendingToolCallAgeSeconds =
582
- IN_PROGRESS_TOOL_CALL_MAX_SUPPRESS_SECONDS + 60 * 60;
583
- mockSessionOutputActivityRepository.listSessionOutputActivities.mockResolvedValue(
584
- [
585
- {
586
- sessionName: GITHUB_SESSION,
587
- lastOutputEpochSeconds: nowEpochSeconds - pendingToolCallAgeSeconds,
588
- hasInProgressToolCall: true,
589
552
  },
590
553
  ],
591
554
  );
@@ -596,7 +559,7 @@ describe('NotifySilentLiveSessionsUseCase', () => {
596
559
  await useCase.run(runParams());
597
560
 
598
561
  expect(mockMessageComposer.composeMainStalledSection).toHaveBeenCalledWith(
599
- pendingToolCallAgeSeconds,
562
+ DEFAULT_MAIN_SILENT_THRESHOLD_SECONDS,
600
563
  );
601
564
  expect(
602
565
  mockNotificationRepository.sendSelfCheckNotification,
@@ -611,7 +574,6 @@ describe('NotifySilentLiveSessionsUseCase', () => {
611
574
  sessionName: GITHUB_SESSION,
612
575
  lastOutputEpochSeconds:
613
576
  nowEpochSeconds - DEFAULT_MAIN_SILENT_THRESHOLD_SECONDS + 1,
614
- hasInProgressToolCall: false,
615
577
  },
616
578
  ],
617
579
  );
@@ -738,7 +700,6 @@ describe('NotifySilentLiveSessionsUseCase', () => {
738
700
  sessionName: GITHUB_SESSION,
739
701
  lastOutputEpochSeconds:
740
702
  nowEpochSeconds - DEFAULT_MAIN_SILENT_THRESHOLD_SECONDS,
741
- hasInProgressToolCall: false,
742
703
  },
743
704
  ],
744
705
  );
@@ -763,14 +724,12 @@ describe('NotifySilentLiveSessionsUseCase', () => {
763
724
  sessionName: GITHUB_SESSION,
764
725
  lastOutputEpochSeconds:
765
726
  nowEpochSeconds - DEFAULT_MAIN_SILENT_THRESHOLD_SECONDS,
766
- hasInProgressToolCall: false,
767
727
  },
768
728
  ];
769
729
  const active = [
770
730
  {
771
731
  sessionName: GITHUB_SESSION,
772
732
  lastOutputEpochSeconds: nowEpochSeconds,
773
- hasInProgressToolCall: false,
774
733
  },
775
734
  ];
776
735
 
@@ -808,7 +767,6 @@ describe('NotifySilentLiveSessionsUseCase', () => {
808
767
  sessionName: GITHUB_SESSION,
809
768
  lastOutputEpochSeconds:
810
769
  nowEpochSeconds - DEFAULT_MAIN_SILENT_THRESHOLD_SECONDS,
811
- hasInProgressToolCall: false,
812
770
  },
813
771
  ],
814
772
  );
@@ -898,7 +856,7 @@ describe('NotifySilentLiveSessionsUseCase', () => {
898
856
  });
899
857
  });
900
858
 
901
- describe('fire-once latch and input-state gating', () => {
859
+ describe('fire-once latch', () => {
902
860
  const setupSilentGithubSession = (): void => {
903
861
  setupLiveInteractiveSession(GITHUB_SESSION);
904
862
  mockSessionOutputActivityRepository.listSessionOutputActivities.mockResolvedValue(
@@ -907,19 +865,11 @@ describe('NotifySilentLiveSessionsUseCase', () => {
907
865
  sessionName: GITHUB_SESSION,
908
866
  lastOutputEpochSeconds:
909
867
  nowEpochSeconds - DEFAULT_MAIN_SILENT_THRESHOLD_SECONDS,
910
- hasInProgressToolCall: false,
911
868
  },
912
869
  ],
913
870
  );
914
871
  };
915
872
 
916
- const idleSubAgent = (label: string): SubAgentActivity => ({
917
- label,
918
- silentSeconds: DEFAULT_SUBAGENT_SILENT_THRESHOLD_SECONDS,
919
- runningSeconds: 60,
920
- waitingOnExternalProcess: false,
921
- });
922
-
923
873
  it('loads the fire-once latch using the configured recency window', async () => {
924
874
  setupSilentGithubSession();
925
875
 
@@ -973,69 +923,17 @@ describe('NotifySilentLiveSessionsUseCase', () => {
973
923
  ).toHaveBeenCalledWith({ sessionNames: [], now });
974
924
  });
975
925
 
976
- it('does not inject into a session whose main REPL has an in-progress tool call even when a sub-agent advisory qualifies', async () => {
977
- setupLiveInteractiveSession(GITHUB_SESSION);
978
- mockSessionOutputActivityRepository.listSessionOutputActivities.mockResolvedValue(
979
- [
980
- {
981
- sessionName: GITHUB_SESSION,
982
- lastOutputEpochSeconds: nowEpochSeconds,
983
- hasInProgressToolCall: true,
984
- },
985
- ],
986
- );
987
- mockSubAgentActivityRepository.listSubAgentActivitiesBySessionName.mockResolvedValue(
988
- new Map([[GITHUB_SESSION, [idleSubAgent('sub-process-1')]]]),
989
- );
926
+ it('sends the reminder for a silent session even when its main REPL is mid-turn, because apparent busyness no longer defers delivery', async () => {
927
+ setupSilentGithubSession();
990
928
 
991
929
  await useCase.run(runParams());
992
930
 
993
- expect(mockMessageComposer.composeSubAgentSection).toHaveBeenCalled();
994
931
  expect(
995
932
  mockNotificationRepository.sendSelfCheckNotification,
996
- ).not.toHaveBeenCalled();
933
+ ).toHaveBeenCalledWith(GITHUB_SESSION, MAIN_STALLED_SECTION);
997
934
  expect(
998
935
  mockNotifiedStateRepository.saveNotifiedSessionNames,
999
- ).toHaveBeenCalledWith({ sessionNames: [], now });
1000
- });
1001
-
1002
- it('delivers the deferred reminder once the main REPL becomes input-ready', async () => {
1003
- wireStatefulNotifiedLatch();
1004
- setupLiveInteractiveSession(GITHUB_SESSION);
1005
- mockSubAgentActivityRepository.listSubAgentActivitiesBySessionName.mockResolvedValue(
1006
- new Map([[GITHUB_SESSION, [idleSubAgent('sub-process-1')]]]),
1007
- );
1008
-
1009
- mockSessionOutputActivityRepository.listSessionOutputActivities.mockResolvedValue(
1010
- [
1011
- {
1012
- sessionName: GITHUB_SESSION,
1013
- lastOutputEpochSeconds: nowEpochSeconds,
1014
- hasInProgressToolCall: true,
1015
- },
1016
- ],
1017
- );
1018
- await useCase.run(runParams());
1019
- expect(
1020
- mockNotificationRepository.sendSelfCheckNotification,
1021
- ).not.toHaveBeenCalled();
1022
-
1023
- mockSessionOutputActivityRepository.listSessionOutputActivities.mockResolvedValue(
1024
- [
1025
- {
1026
- sessionName: GITHUB_SESSION,
1027
- lastOutputEpochSeconds: nowEpochSeconds,
1028
- hasInProgressToolCall: false,
1029
- },
1030
- ],
1031
- );
1032
- await useCase.run(runParams());
1033
- expect(
1034
- mockNotificationRepository.sendSelfCheckNotification,
1035
- ).toHaveBeenCalledTimes(1);
1036
- expect(
1037
- mockNotificationRepository.sendSelfCheckNotification,
1038
- ).toHaveBeenCalledWith(GITHUB_SESSION, SUBAGENT_SECTION);
936
+ ).toHaveBeenCalledWith({ sessionNames: [GITHUB_SESSION], now });
1039
937
  });
1040
938
  });
1041
939
 
@@ -1218,13 +1116,11 @@ describe('NotifySilentLiveSessionsUseCase', () => {
1218
1116
  sessionName: GITHUB_SESSION_ALPHA,
1219
1117
  lastOutputEpochSeconds:
1220
1118
  nowEpochSeconds - DEFAULT_MAIN_SILENT_THRESHOLD_SECONDS,
1221
- hasInProgressToolCall: false,
1222
1119
  },
1223
1120
  {
1224
1121
  sessionName: GITHUB_SESSION_BRAVO,
1225
1122
  lastOutputEpochSeconds:
1226
1123
  nowEpochSeconds - DEFAULT_MAIN_SILENT_THRESHOLD_SECONDS,
1227
- hasInProgressToolCall: false,
1228
1124
  },
1229
1125
  ],
1230
1126
  );
@@ -1260,7 +1156,6 @@ describe('NotifySilentLiveSessionsUseCase', () => {
1260
1156
  sessionName: GITHUB_SESSION,
1261
1157
  lastOutputEpochSeconds:
1262
1158
  nowEpochSeconds - DEFAULT_MAIN_SILENT_THRESHOLD_SECONDS,
1263
- hasInProgressToolCall: false,
1264
1159
  },
1265
1160
  ],
1266
1161
  );
@@ -1802,13 +1697,11 @@ describe('NotifySilentLiveSessionsUseCase', () => {
1802
1697
  sessionName: GITHUB_SESSION_ALPHA,
1803
1698
  lastOutputEpochSeconds:
1804
1699
  nowEpochSeconds - DEFAULT_MAIN_SILENT_THRESHOLD_SECONDS,
1805
- hasInProgressToolCall: false,
1806
1700
  },
1807
1701
  {
1808
1702
  sessionName: GITHUB_SESSION_BRAVO,
1809
1703
  lastOutputEpochSeconds:
1810
1704
  nowEpochSeconds - DEFAULT_MAIN_SILENT_THRESHOLD_SECONDS,
1811
- hasInProgressToolCall: false,
1812
1705
  },
1813
1706
  ],
1814
1707
  );
@@ -26,15 +26,6 @@ export const DEFAULT_SUBAGENT_RUNNING_THRESHOLD_SECONDS = 15 * 60;
26
26
  export const DEFAULT_NOTIFICATION_STAGGER_SECONDS = 25;
27
27
  export const DEFAULT_CANDIDATE_DEBOUNCE_RECENCY_WINDOW_SECONDS = 15 * 60;
28
28
  export const DEFAULT_HUB_TASK_STATUS_CACHE_TTL_SECONDS = 5 * 60;
29
- // Upper bound on how long an in-progress tool call suppresses the main-stall
30
- // reminder. Set to 2 hours: comfortably above the ~1-hour maximum duration of
31
- // the longest legitimate single tool call (the Monitor tool's own timeout
32
- // ceiling), so every legitimate long-running tool call is still suppressed,
33
- // while a session whose transcript tail has been an unanswered tool_use for
34
- // longer than this — for example because the tool call itself hung and never
35
- // returned — still receives the stall reminder instead of being suppressed
36
- // forever.
37
- export const IN_PROGRESS_TOOL_CALL_MAX_SUPPRESS_SECONDS = 2 * 60 * 60;
38
29
 
39
30
  const GITHUB_ISSUE_OR_PULL_URL_PATTERN =
40
31
  /^https:\/\/github\.com\/([^/]+)\/([^/]+)\/(?:issues|pull)\/(\d+)$/;
@@ -71,7 +62,6 @@ type NotifyCandidate = {
71
62
  sessionName: string;
72
63
  message: string;
73
64
  sectionLabels: string[];
74
- mainInputBusy: boolean;
75
65
  };
76
66
 
77
67
  export class NotifySilentLiveSessionsUseCase {
@@ -212,20 +202,6 @@ export class NotifySilentLiveSessionsUseCase {
212
202
  );
213
203
  continue;
214
204
  }
215
- // Input-state gating: a session whose main REPL is mid-turn — currently
216
- // running an in-progress tool call, which includes waiting on a
217
- // long-running sub-agent — cannot accept an injected prompt cleanly, so
218
- // the reminder is deferred rather than piled into a busy input box. The
219
- // session stays a candidate, so it is reminded once it becomes
220
- // input-ready. A tool call pending past
221
- // IN_PROGRESS_TOOL_CALL_MAX_SUPPRESS_SECONDS is treated as hung and no
222
- // longer counts as busy (see composeCandidate).
223
- if (candidate.mainInputBusy) {
224
- console.log(
225
- `Skipping ${candidate.sessionName}: main REPL is not in an input-accepting state (in-progress tool call); deferring reminder until it is idle.`,
226
- );
227
- continue;
228
- }
229
205
  if (
230
206
  !(await this.isHubTaskActive(
231
207
  candidate.sessionName,
@@ -404,16 +380,11 @@ export class NotifySilentLiveSessionsUseCase {
404
380
  transcriptPathBySessionName,
405
381
  );
406
382
  const lastOutputBySessionName = new Map<string, number>();
407
- const inProgressToolCallBySessionName = new Map<string, boolean>();
408
383
  for (const activity of activities) {
409
384
  lastOutputBySessionName.set(
410
385
  activity.sessionName,
411
386
  activity.lastOutputEpochSeconds,
412
387
  );
413
- inProgressToolCallBySessionName.set(
414
- activity.sessionName,
415
- activity.hasInProgressToolCall,
416
- );
417
388
  }
418
389
 
419
390
  const subAgentsBySessionName =
@@ -439,8 +410,6 @@ export class NotifySilentLiveSessionsUseCase {
439
410
  return {
440
411
  sessionName,
441
412
  mainSilentSeconds,
442
- mainHasInProgressToolCall:
443
- inProgressToolCallBySessionName.get(sessionName) ?? false,
444
413
  subAgents: subAgentsBySessionName.get(sessionName) ?? [],
445
414
  unansweredOwnerCallAgeSeconds:
446
415
  unansweredOwnerCallEpochSeconds === undefined
@@ -476,30 +445,17 @@ export class NotifySilentLiveSessionsUseCase {
476
445
  // call signature and is intentionally ignored (treated as infinite).
477
446
  const suppressedByUnansweredOwnerCall =
478
447
  unansweredOwnerCallAgeSeconds !== null;
479
- // A session whose transcript tail is an assistant tool_use with no matching
480
- // tool_result is legitimately busy running one long tool call (e.g. a Bash
481
- // command near its timeout, or a Monitor waiting up to an hour): it appends
482
- // no new assistant line while the tool runs, so mainSilentSeconds crosses
483
- // the threshold even though the session is working, not stalled. Suppress
484
- // the main-stall reminder in that case. Genuine thrashing silence — where
485
- // the last assistant tool_use was already answered (or there is none) — has
486
- // no pending tool call and is still flagged.
487
- // The suppression is bounded by the pending tool call's own age
488
- // (mainSilentSeconds, which for an in-progress tool call equals the age of
489
- // the pending tool_use line) so it cannot last forever: a session stuck
490
- // inside a tool call that never returns is reminded once that age exceeds
491
- // IN_PROGRESS_TOOL_CALL_MAX_SUPPRESS_SECONDS, restoring the ~10-minute
492
- // stall reminder for a truly hung session instead of suppressing it
493
- // indefinitely.
494
- const suppressedByInProgressToolCall =
495
- snapshot.mainHasInProgressToolCall &&
496
- mainSilentSeconds !== null &&
497
- mainSilentSeconds < IN_PROGRESS_TOOL_CALL_MAX_SUPPRESS_SECONDS;
448
+ // The main-stall reminder is driven purely by silence: a session that has
449
+ // produced no assistant output for longer than the threshold is reminded
450
+ // regardless of whether its transcript tail is an in-progress tool_use. A
451
+ // session that merely looks busy (mid-tool-call) can in fact be stuck, so
452
+ // its apparent busyness MUST NOT suppress the reminder; the reminder queues
453
+ // cleanly into the session even when it is mid-turn. The only main-stall
454
+ // suppression is an unanswered owner call, which is a real wait on the owner.
498
455
  const mainTriggered =
499
456
  mainSilentSeconds !== null &&
500
457
  mainSilentSeconds >= thresholds.mainSilentThresholdSeconds &&
501
- !suppressedByUnansweredOwnerCall &&
502
- !suppressedByInProgressToolCall;
458
+ !suppressedByUnansweredOwnerCall;
503
459
  if (mainTriggered) {
504
460
  sections.push(
505
461
  this.messageComposer.composeMainStalledSection(mainSilentSeconds),
@@ -547,7 +503,6 @@ export class NotifySilentLiveSessionsUseCase {
547
503
  sessionName: snapshot.sessionName,
548
504
  message: sections.join('\n\n'),
549
505
  sectionLabels,
550
- mainInputBusy: suppressedByInProgressToolCall,
551
506
  };
552
507
  };
553
508
  }