github-issue-tower-defence-management 1.143.1 → 1.143.2
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 +7 -0
- package/README.md +2 -2
- package/bin/adapter/entry-points/handlers/HandleScheduledEventUseCaseHandler.js +2 -9
- package/bin/adapter/entry-points/handlers/HandleScheduledEventUseCaseHandler.js.map +1 -1
- package/bin/adapter/entry-points/handlers/notifySilentTmuxSessions.js +2 -5
- package/bin/adapter/entry-points/handlers/notifySilentTmuxSessions.js.map +1 -1
- package/bin/adapter/repositories/TmuxSilentSessionNotificationRepository.js +2 -1
- package/bin/adapter/repositories/TmuxSilentSessionNotificationRepository.js.map +1 -1
- package/bin/domain/usecases/DefaultSilentSessionMessageComposer.js +1 -1
- package/bin/domain/usecases/DefaultSilentSessionMessageComposer.js.map +1 -1
- package/bin/domain/usecases/NotifySilentLiveSessionsUseCase.js +1 -35
- package/bin/domain/usecases/NotifySilentLiveSessionsUseCase.js.map +1 -1
- package/package.json +1 -1
- package/src/adapter/entry-points/handlers/HandleScheduledEventUseCaseHandler.ts +0 -5
- package/src/adapter/entry-points/handlers/notifySilentTmuxSessions.test.ts +3 -8
- package/src/adapter/entry-points/handlers/notifySilentTmuxSessions.ts +0 -8
- package/src/adapter/repositories/TmuxSilentSessionNotificationRepository.test.ts +35 -4
- package/src/adapter/repositories/TmuxSilentSessionNotificationRepository.ts +2 -1
- package/src/adapter/repositories/TranscriptOwnerCallStatusProvider.test.ts +0 -6
- package/src/domain/usecases/DefaultSilentSessionMessageComposer.test.ts +21 -1
- package/src/domain/usecases/DefaultSilentSessionMessageComposer.ts +1 -1
- package/src/domain/usecases/NotifySilentLiveSessionsUseCase.test.ts +21 -98
- package/src/domain/usecases/NotifySilentLiveSessionsUseCase.ts +0 -44
- package/types/adapter/entry-points/handlers/HandleScheduledEventUseCaseHandler.d.ts.map +1 -1
- package/types/adapter/entry-points/handlers/notifySilentTmuxSessions.d.ts +0 -1
- package/types/adapter/entry-points/handlers/notifySilentTmuxSessions.d.ts.map +1 -1
- package/types/adapter/repositories/TmuxSilentSessionNotificationRepository.d.ts.map +1 -1
- package/types/domain/usecases/NotifySilentLiveSessionsUseCase.d.ts +1 -3
- package/types/domain/usecases/NotifySilentLiveSessionsUseCase.d.ts.map +1 -1
- package/src/adapter/repositories/FileSystemSilentSessionNotifiedStateRepository.test.ts +0 -127
- package/src/adapter/repositories/FileSystemSilentSessionNotifiedStateRepository.ts +0 -108
- package/src/domain/usecases/adapter-interfaces/SilentSessionNotifiedStateRepository.ts +0 -10
|
@@ -34,7 +34,6 @@ import {
|
|
|
34
34
|
} from '../../repositories/ConfigurableSilentSessionMessageComposer';
|
|
35
35
|
import { RealSleeper } from '../../repositories/RealSleeper';
|
|
36
36
|
import { FileSystemSilentSessionCandidateStateRepository } from '../../repositories/FileSystemSilentSessionCandidateStateRepository';
|
|
37
|
-
import { FileSystemSilentSessionNotifiedStateRepository } from '../../repositories/FileSystemSilentSessionNotifiedStateRepository';
|
|
38
37
|
import { FileSystemSilentSessionHubTaskStatusCacheRepository } from '../../repositories/FileSystemSilentSessionHubTaskStatusCacheRepository';
|
|
39
38
|
|
|
40
39
|
export type NotifySilentTmuxSessionsParams = {
|
|
@@ -53,7 +52,6 @@ export type NotifySilentTmuxSessionsParams = {
|
|
|
53
52
|
staggerSeconds: number;
|
|
54
53
|
candidateDebounceRecencyWindowSeconds: number;
|
|
55
54
|
candidateDebounceStateFilePath: string | null;
|
|
56
|
-
notifiedStateFilePath: string | null;
|
|
57
55
|
activeHubTaskStatus: string | null;
|
|
58
56
|
hubTaskStatusResolver: HubTaskStatusResolver | null;
|
|
59
57
|
hubTaskStatusCacheStateFilePath: string | null;
|
|
@@ -118,7 +116,6 @@ export const notifySilentTmuxSessions = async (
|
|
|
118
116
|
staggerSeconds,
|
|
119
117
|
candidateDebounceRecencyWindowSeconds,
|
|
120
118
|
candidateDebounceStateFilePath,
|
|
121
|
-
notifiedStateFilePath,
|
|
122
119
|
activeHubTaskStatus,
|
|
123
120
|
hubTaskStatusResolver,
|
|
124
121
|
hubTaskStatusCacheStateFilePath,
|
|
@@ -158,11 +155,6 @@ export const notifySilentTmuxSessions = async (
|
|
|
158
155
|
candidateDebounceStateFilePath,
|
|
159
156
|
)
|
|
160
157
|
: new FileSystemSilentSessionCandidateStateRepository(),
|
|
161
|
-
notifiedStateFilePath !== null
|
|
162
|
-
? new FileSystemSilentSessionNotifiedStateRepository(
|
|
163
|
-
notifiedStateFilePath,
|
|
164
|
-
)
|
|
165
|
-
: new FileSystemSilentSessionNotifiedStateRepository(),
|
|
166
158
|
messageComposer,
|
|
167
159
|
new RealSleeper(),
|
|
168
160
|
hubTaskStatusResolver,
|
|
@@ -38,7 +38,7 @@ describe('TmuxSilentSessionNotificationRepository', () => {
|
|
|
38
38
|
]);
|
|
39
39
|
});
|
|
40
40
|
|
|
41
|
-
it('
|
|
41
|
+
it('flattens a multi-line message into a single line so no newline reaches the input box', async () => {
|
|
42
42
|
const runner = createMockRunner();
|
|
43
43
|
const repository = new TmuxSilentSessionNotificationRepository(runner);
|
|
44
44
|
const multiLineMessage = 'first line\nsecond line\n\nthird line';
|
|
@@ -49,14 +49,45 @@ describe('TmuxSilentSessionNotificationRepository', () => {
|
|
|
49
49
|
);
|
|
50
50
|
|
|
51
51
|
const literalArgument = runner.runCommand.mock.calls[0][1][4];
|
|
52
|
-
expect(literalArgument.
|
|
53
|
-
expect(literalArgument.
|
|
52
|
+
expect(literalArgument).not.toContain('\n');
|
|
53
|
+
expect(literalArgument).not.toContain('\r');
|
|
54
54
|
expect(
|
|
55
55
|
literalArgument.slice('\x1b[200~'.length, -'\x1b[201~'.length),
|
|
56
|
-
).toBe(
|
|
56
|
+
).toBe('first line second line third line');
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
it('keeps the bracketed-paste framing around the flattened message', async () => {
|
|
60
|
+
const runner = createMockRunner();
|
|
61
|
+
const repository = new TmuxSilentSessionNotificationRepository(runner);
|
|
62
|
+
|
|
63
|
+
await repository.sendSelfCheckNotification(
|
|
64
|
+
'https_//github_com/owner/repo/issues/9',
|
|
65
|
+
'first line\nsecond line',
|
|
66
|
+
);
|
|
67
|
+
|
|
68
|
+
const literalArgument = runner.runCommand.mock.calls[0][1][4];
|
|
57
69
|
const literalBytes = Buffer.from(literalArgument, 'utf8').toString('hex');
|
|
58
70
|
expect(literalBytes.startsWith('1b5b3230307e')).toBe(true);
|
|
59
71
|
expect(literalBytes.endsWith('1b5b3230317e')).toBe(true);
|
|
72
|
+
expect(runner.runCommand.mock.calls[1]).toEqual([
|
|
73
|
+
'tmux',
|
|
74
|
+
['send-keys', '-t', 'https_//github_com/owner/repo/issues/9', 'Enter'],
|
|
75
|
+
]);
|
|
76
|
+
});
|
|
77
|
+
|
|
78
|
+
it('collapses the run of spaces left by consecutive line breaks', async () => {
|
|
79
|
+
const runner = createMockRunner();
|
|
80
|
+
const repository = new TmuxSilentSessionNotificationRepository(runner);
|
|
81
|
+
|
|
82
|
+
await repository.sendSelfCheckNotification(
|
|
83
|
+
'https_//github_com/owner/repo/issues/9',
|
|
84
|
+
'section one\n\n\nsection two',
|
|
85
|
+
);
|
|
86
|
+
|
|
87
|
+
const literalArgument = runner.runCommand.mock.calls[0][1][4];
|
|
88
|
+
expect(
|
|
89
|
+
literalArgument.slice('\x1b[200~'.length, -'\x1b[201~'.length),
|
|
90
|
+
).toBe('section one section two');
|
|
60
91
|
});
|
|
61
92
|
|
|
62
93
|
it('throws when sending the message literally fails', async () => {
|
|
@@ -11,7 +11,8 @@ export class TmuxSilentSessionNotificationRepository implements SilentSessionNot
|
|
|
11
11
|
sessionName: string,
|
|
12
12
|
message: string,
|
|
13
13
|
): Promise<void> => {
|
|
14
|
-
const
|
|
14
|
+
const singleLineMessage = message.replace(/\s*[\r\n]+\s*/g, ' ').trim();
|
|
15
|
+
const framedMessage = `${BRACKETED_PASTE_START}${singleLineMessage}${BRACKETED_PASTE_END}`;
|
|
15
16
|
const literalResult = await this.localCommandRunner.runCommand('tmux', [
|
|
16
17
|
'send-keys',
|
|
17
18
|
'-t',
|
|
@@ -709,12 +709,6 @@ describe('TranscriptOwnerCallStatusProvider wired into NotifySilentLiveSessionsU
|
|
|
709
709
|
.mockResolvedValue(new EveryNameRecentSet()),
|
|
710
710
|
saveCandidateSessionNames: jest.fn().mockResolvedValue(undefined),
|
|
711
711
|
},
|
|
712
|
-
{
|
|
713
|
-
loadRecentNotifiedSessionNames: jest
|
|
714
|
-
.fn()
|
|
715
|
-
.mockResolvedValue(new Set<string>()),
|
|
716
|
-
saveNotifiedSessionNames: jest.fn().mockResolvedValue(undefined),
|
|
717
|
-
},
|
|
718
712
|
{
|
|
719
713
|
composeMainStalledSection: jest.fn().mockReturnValue('MAIN_STALLED'),
|
|
720
714
|
composeMainStalledWithStaleOwnerCallSection: jest
|
|
@@ -28,7 +28,7 @@ describe('DefaultSilentSessionMessageComposer', () => {
|
|
|
28
28
|
expect(section).toContain('This is an automated status check.');
|
|
29
29
|
expect(section).toContain('No output has been observed for 10 minutes.');
|
|
30
30
|
expect(section).toContain(
|
|
31
|
-
'If you are waiting on an
|
|
31
|
+
'If you are waiting on an automated process that runs without the owner',
|
|
32
32
|
);
|
|
33
33
|
expect(section).toContain('Otherwise please continue with the next step');
|
|
34
34
|
expect(section).toContain('1.');
|
|
@@ -37,6 +37,26 @@ describe('DefaultSilentSessionMessageComposer', () => {
|
|
|
37
37
|
expect(section).toContain('4.');
|
|
38
38
|
});
|
|
39
39
|
|
|
40
|
+
it('limits the no-action wait to an automated process that runs without the owner', () => {
|
|
41
|
+
const section = composer.composeMainStalledSection(600);
|
|
42
|
+
expect(section).toContain(
|
|
43
|
+
'If you are waiting on an automated process that runs without the owner - a continuous-integration run, an external API, or another process - no action is needed beyond logging one line explaining the wait.',
|
|
44
|
+
);
|
|
45
|
+
expect(section).not.toContain(
|
|
46
|
+
'If you are waiting on an external process, no action is needed',
|
|
47
|
+
);
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
it('asks for an owner-call when the session is waiting on something only the owner can supply', () => {
|
|
51
|
+
const section = composer.composeMainStalledSection(600);
|
|
52
|
+
expect(section).toContain(
|
|
53
|
+
'If you are waiting on something only the owner can supply - a decision, an approval, a credential, or an answer - please raise an owner-call now',
|
|
54
|
+
);
|
|
55
|
+
expect(section).toContain(
|
|
56
|
+
'that wait is resolved only after the owner has been notified',
|
|
57
|
+
);
|
|
58
|
+
});
|
|
59
|
+
|
|
40
60
|
it('states each of the four checkpoints exactly once', () => {
|
|
41
61
|
const section = composer.composeMainStalledSection(600);
|
|
42
62
|
const occurrences = (needle: string): number =>
|
|
@@ -57,7 +57,7 @@ export const composeMainStalledSelfDiagnosisGuidance = (): string => {
|
|
|
57
57
|
const composeMainStalledMessage = (mainSilentSeconds: number): string => {
|
|
58
58
|
const minutes = Math.floor(mainSilentSeconds / 60);
|
|
59
59
|
return [
|
|
60
|
-
`${SILENT_SESSION_REMINDER_SENTINEL} This is an automated status check. No output has been observed for ${minutes} minutes. If you are waiting on an
|
|
60
|
+
`${SILENT_SESSION_REMINDER_SENTINEL} This is an automated status check. No output has been observed for ${minutes} minutes. If you are waiting on an automated process that runs without the owner - a continuous-integration run, an external API, or another process - no action is needed beyond logging one line explaining the wait. If you are waiting on something only the owner can supply - a decision, an approval, a credential, or an answer - please raise an owner-call now, because that wait is resolved only after the owner has been notified. Otherwise please continue with the next step, with these points in mind:`,
|
|
61
61
|
`1. Keep the session task list current, marking finished items as done.`,
|
|
62
62
|
`2. Run independent pieces of work in parallel across sub-agents.`,
|
|
63
63
|
`3. Keep a monitor in place that notices when a sub-agent has produced no output for about 5 minutes.`,
|
|
@@ -20,7 +20,6 @@ import { OwnerCallStatusProvider } from './adapter-interfaces/OwnerCallStatusPro
|
|
|
20
20
|
import { RefusalTailStatusProvider } from './adapter-interfaces/RefusalTailStatusProvider';
|
|
21
21
|
import { SilentSessionNotificationRepository } from './adapter-interfaces/SilentSessionNotificationRepository';
|
|
22
22
|
import { SilentSessionCandidateStateRepository } from './adapter-interfaces/SilentSessionCandidateStateRepository';
|
|
23
|
-
import { SilentSessionNotifiedStateRepository } from './adapter-interfaces/SilentSessionNotifiedStateRepository';
|
|
24
23
|
import { SilentSessionHubTaskStatusCacheRepository } from './adapter-interfaces/SilentSessionHubTaskStatusCacheRepository';
|
|
25
24
|
import { SilentSessionMessageComposer } from './adapter-interfaces/SilentSessionMessageComposer';
|
|
26
25
|
import { Sleeper } from './adapter-interfaces/Sleeper';
|
|
@@ -50,7 +49,6 @@ describe('NotifySilentLiveSessionsUseCase', () => {
|
|
|
50
49
|
let mockOwnerCallStatusProvider: Mocked<OwnerCallStatusProvider>;
|
|
51
50
|
let mockNotificationRepository: Mocked<SilentSessionNotificationRepository>;
|
|
52
51
|
let mockCandidateStateRepository: Mocked<SilentSessionCandidateStateRepository>;
|
|
53
|
-
let mockNotifiedStateRepository: Mocked<SilentSessionNotifiedStateRepository>;
|
|
54
52
|
let mockMessageComposer: Mocked<SilentSessionMessageComposer>;
|
|
55
53
|
let mockSleeper: Mocked<Sleeper>;
|
|
56
54
|
let mockHubTaskStatusResolver: Mocked<HubTaskStatusResolver>;
|
|
@@ -170,12 +168,6 @@ describe('NotifySilentLiveSessionsUseCase', () => {
|
|
|
170
168
|
.mockResolvedValue(everyNameRecentSet()),
|
|
171
169
|
saveCandidateSessionNames: jest.fn().mockResolvedValue(undefined),
|
|
172
170
|
};
|
|
173
|
-
mockNotifiedStateRepository = {
|
|
174
|
-
loadRecentNotifiedSessionNames: jest
|
|
175
|
-
.fn()
|
|
176
|
-
.mockResolvedValue(new Set<string>()),
|
|
177
|
-
saveNotifiedSessionNames: jest.fn().mockResolvedValue(undefined),
|
|
178
|
-
};
|
|
179
171
|
mockMessageComposer = {
|
|
180
172
|
composeMainStalledSection: jest
|
|
181
173
|
.fn()
|
|
@@ -208,7 +200,6 @@ describe('NotifySilentLiveSessionsUseCase', () => {
|
|
|
208
200
|
mockOwnerCallStatusProvider,
|
|
209
201
|
mockNotificationRepository,
|
|
210
202
|
mockCandidateStateRepository,
|
|
211
|
-
mockNotifiedStateRepository,
|
|
212
203
|
mockMessageComposer,
|
|
213
204
|
mockSleeper,
|
|
214
205
|
mockHubTaskStatusResolver,
|
|
@@ -217,20 +208,6 @@ describe('NotifySilentLiveSessionsUseCase', () => {
|
|
|
217
208
|
);
|
|
218
209
|
});
|
|
219
210
|
|
|
220
|
-
// Simulates the on-disk fire-once latch across the per-cycle fresh monitor
|
|
221
|
-
// process: load returns the currently persisted set, save overwrites it.
|
|
222
|
-
const wireStatefulNotifiedLatch = (): void => {
|
|
223
|
-
let persistedNotifiedSessionNames = new Set<string>();
|
|
224
|
-
mockNotifiedStateRepository.loadRecentNotifiedSessionNames.mockImplementation(
|
|
225
|
-
async () => new Set(persistedNotifiedSessionNames),
|
|
226
|
-
);
|
|
227
|
-
mockNotifiedStateRepository.saveNotifiedSessionNames.mockImplementation(
|
|
228
|
-
async ({ sessionNames }) => {
|
|
229
|
-
persistedNotifiedSessionNames = new Set(sessionNames);
|
|
230
|
-
},
|
|
231
|
-
);
|
|
232
|
-
};
|
|
233
|
-
|
|
234
211
|
const issueFor = (overrides: Partial<Issue>): Issue => ({
|
|
235
212
|
nameWithOwner: 'HiromiShikata/repo',
|
|
236
213
|
number: 42,
|
|
@@ -803,33 +780,7 @@ describe('NotifySilentLiveSessionsUseCase', () => {
|
|
|
803
780
|
});
|
|
804
781
|
});
|
|
805
782
|
|
|
806
|
-
it('notifies a persistent stall only once per silent episode instead of every cycle', async () => {
|
|
807
|
-
wireStatefulNotifiedLatch();
|
|
808
|
-
setupLiveInteractiveSession(GITHUB_SESSION);
|
|
809
|
-
mockSessionOutputActivityRepository.listSessionOutputActivities.mockResolvedValue(
|
|
810
|
-
[
|
|
811
|
-
{
|
|
812
|
-
sessionName: GITHUB_SESSION,
|
|
813
|
-
lastOutputEpochSeconds:
|
|
814
|
-
nowEpochSeconds - DEFAULT_MAIN_SILENT_THRESHOLD_SECONDS,
|
|
815
|
-
},
|
|
816
|
-
],
|
|
817
|
-
);
|
|
818
|
-
|
|
819
|
-
await useCase.run(runParams());
|
|
820
|
-
await useCase.run(runParams());
|
|
821
|
-
await useCase.run(runParams());
|
|
822
|
-
|
|
823
|
-
expect(
|
|
824
|
-
mockNotificationRepository.sendSelfCheckNotification,
|
|
825
|
-
).toHaveBeenCalledTimes(1);
|
|
826
|
-
expect(
|
|
827
|
-
mockNotificationRepository.sendSelfCheckNotification,
|
|
828
|
-
).toHaveBeenCalledWith(GITHUB_SESSION, MAIN_STALLED_SECTION);
|
|
829
|
-
});
|
|
830
|
-
|
|
831
783
|
it('re-notifies a session after its silent episode resolves and a new one begins', async () => {
|
|
832
|
-
wireStatefulNotifiedLatch();
|
|
833
784
|
setupLiveInteractiveSession(GITHUB_SESSION);
|
|
834
785
|
const stalled = [
|
|
835
786
|
{
|
|
@@ -968,7 +919,7 @@ describe('NotifySilentLiveSessionsUseCase', () => {
|
|
|
968
919
|
});
|
|
969
920
|
});
|
|
970
921
|
|
|
971
|
-
describe('
|
|
922
|
+
describe('repeated delivery while a session stays silent', () => {
|
|
972
923
|
const setupSilentGithubSession = (): void => {
|
|
973
924
|
setupLiveInteractiveSession(GITHUB_SESSION);
|
|
974
925
|
mockSessionOutputActivityRepository.listSessionOutputActivities.mockResolvedValue(
|
|
@@ -982,70 +933,45 @@ describe('NotifySilentLiveSessionsUseCase', () => {
|
|
|
982
933
|
);
|
|
983
934
|
};
|
|
984
935
|
|
|
985
|
-
it('
|
|
986
|
-
setupSilentGithubSession();
|
|
987
|
-
|
|
988
|
-
await useCase.run(
|
|
989
|
-
runParams({ candidateDebounceRecencyWindowSeconds: 900 }),
|
|
990
|
-
);
|
|
991
|
-
|
|
992
|
-
expect(
|
|
993
|
-
mockNotifiedStateRepository.loadRecentNotifiedSessionNames,
|
|
994
|
-
).toHaveBeenCalledWith({ now, recencyWindowSeconds: 900 });
|
|
995
|
-
});
|
|
996
|
-
|
|
997
|
-
it('persists the sent session in the fire-once latch on a send cycle', async () => {
|
|
936
|
+
it('sends the reminder again on the next cycle to a session that is still silent', async () => {
|
|
998
937
|
setupSilentGithubSession();
|
|
999
938
|
|
|
939
|
+
await useCase.run(runParams());
|
|
1000
940
|
await useCase.run(runParams());
|
|
1001
941
|
|
|
1002
942
|
expect(
|
|
1003
943
|
mockNotificationRepository.sendSelfCheckNotification,
|
|
1004
|
-
).
|
|
1005
|
-
expect(
|
|
1006
|
-
mockNotifiedStateRepository.saveNotifiedSessionNames,
|
|
1007
|
-
).toHaveBeenCalledWith({ sessionNames: [GITHUB_SESSION], now });
|
|
1008
|
-
});
|
|
1009
|
-
|
|
1010
|
-
it('does not re-send but keeps the latch for a session already notified this episode', async () => {
|
|
1011
|
-
setupSilentGithubSession();
|
|
1012
|
-
mockNotifiedStateRepository.loadRecentNotifiedSessionNames.mockResolvedValue(
|
|
1013
|
-
new Set([GITHUB_SESSION]),
|
|
1014
|
-
);
|
|
1015
|
-
|
|
1016
|
-
await useCase.run(runParams());
|
|
1017
|
-
|
|
944
|
+
).toHaveBeenCalledTimes(2);
|
|
1018
945
|
expect(
|
|
1019
946
|
mockNotificationRepository.sendSelfCheckNotification,
|
|
1020
|
-
).
|
|
947
|
+
).toHaveBeenNthCalledWith(1, GITHUB_SESSION, MAIN_STALLED_SECTION);
|
|
1021
948
|
expect(
|
|
1022
|
-
|
|
1023
|
-
).
|
|
949
|
+
mockNotificationRepository.sendSelfCheckNotification,
|
|
950
|
+
).toHaveBeenNthCalledWith(2, GITHUB_SESSION, MAIN_STALLED_SECTION);
|
|
1024
951
|
});
|
|
1025
952
|
|
|
1026
|
-
it('
|
|
1027
|
-
|
|
1028
|
-
new Set([GITHUB_SESSION]),
|
|
1029
|
-
);
|
|
953
|
+
it('sends the reminder for a silent session even when its main REPL is mid-turn, because apparent busyness no longer defers delivery', async () => {
|
|
954
|
+
setupSilentGithubSession();
|
|
1030
955
|
|
|
1031
956
|
await useCase.run(runParams());
|
|
1032
957
|
|
|
1033
958
|
expect(
|
|
1034
|
-
|
|
1035
|
-
).toHaveBeenCalledWith(
|
|
959
|
+
mockNotificationRepository.sendSelfCheckNotification,
|
|
960
|
+
).toHaveBeenCalledWith(GITHUB_SESSION, MAIN_STALLED_SECTION);
|
|
1036
961
|
});
|
|
1037
962
|
|
|
1038
|
-
it('
|
|
963
|
+
it('keeps sending the reminder on every consecutive cycle the session stays silent', async () => {
|
|
1039
964
|
setupSilentGithubSession();
|
|
1040
965
|
|
|
966
|
+
await useCase.run(runParams());
|
|
967
|
+
await useCase.run(runParams());
|
|
968
|
+
await useCase.run(runParams());
|
|
969
|
+
await useCase.run(runParams());
|
|
1041
970
|
await useCase.run(runParams());
|
|
1042
971
|
|
|
1043
972
|
expect(
|
|
1044
973
|
mockNotificationRepository.sendSelfCheckNotification,
|
|
1045
|
-
).
|
|
1046
|
-
expect(
|
|
1047
|
-
mockNotifiedStateRepository.saveNotifiedSessionNames,
|
|
1048
|
-
).toHaveBeenCalledWith({ sessionNames: [GITHUB_SESSION], now });
|
|
974
|
+
).toHaveBeenCalledTimes(5);
|
|
1049
975
|
});
|
|
1050
976
|
});
|
|
1051
977
|
|
|
@@ -1110,8 +1036,7 @@ describe('NotifySilentLiveSessionsUseCase', () => {
|
|
|
1110
1036
|
).toHaveBeenCalledWith(GITHUB_SESSION, SUBAGENT_SECTION);
|
|
1111
1037
|
});
|
|
1112
1038
|
|
|
1113
|
-
it('notifies a hung sub-agent
|
|
1114
|
-
wireStatefulNotifiedLatch();
|
|
1039
|
+
it('notifies a hung sub-agent again on every cycle it stays hung', async () => {
|
|
1115
1040
|
setupSubAgents([hungSubAgent('sub-process-1')]);
|
|
1116
1041
|
|
|
1117
1042
|
await useCase.run(runParams());
|
|
@@ -1119,7 +1044,7 @@ describe('NotifySilentLiveSessionsUseCase', () => {
|
|
|
1119
1044
|
|
|
1120
1045
|
expect(
|
|
1121
1046
|
mockNotificationRepository.sendSelfCheckNotification,
|
|
1122
|
-
).toHaveBeenCalledTimes(
|
|
1047
|
+
).toHaveBeenCalledTimes(2);
|
|
1123
1048
|
});
|
|
1124
1049
|
|
|
1125
1050
|
it('never selects a waiting sub-agent for the long-running section even when quiet past both thresholds', async () => {
|
|
@@ -1160,8 +1085,7 @@ describe('NotifySilentLiveSessionsUseCase', () => {
|
|
|
1160
1085
|
).toHaveBeenCalledWith(GITHUB_SESSION, SUBAGENT_SECTION);
|
|
1161
1086
|
});
|
|
1162
1087
|
|
|
1163
|
-
it('notifies a quiet long-running sub-agent
|
|
1164
|
-
wireStatefulNotifiedLatch();
|
|
1088
|
+
it('notifies a quiet long-running sub-agent again on every cycle while still evaluating it every cycle', async () => {
|
|
1165
1089
|
setupSubAgents([quietLongRunningSubAgent('sub-process-1', false)]);
|
|
1166
1090
|
|
|
1167
1091
|
await useCase.run(runParams());
|
|
@@ -1169,7 +1093,7 @@ describe('NotifySilentLiveSessionsUseCase', () => {
|
|
|
1169
1093
|
|
|
1170
1094
|
expect(
|
|
1171
1095
|
mockNotificationRepository.sendSelfCheckNotification,
|
|
1172
|
-
).toHaveBeenCalledTimes(
|
|
1096
|
+
).toHaveBeenCalledTimes(2);
|
|
1173
1097
|
expect(
|
|
1174
1098
|
mockMessageComposer.composeSubAgentSection,
|
|
1175
1099
|
).toHaveBeenNthCalledWith(2, {
|
|
@@ -1840,7 +1764,6 @@ describe('NotifySilentLiveSessionsUseCase', () => {
|
|
|
1840
1764
|
mockOwnerCallStatusProvider,
|
|
1841
1765
|
mockNotificationRepository,
|
|
1842
1766
|
mockCandidateStateRepository,
|
|
1843
|
-
mockNotifiedStateRepository,
|
|
1844
1767
|
mockMessageComposer,
|
|
1845
1768
|
mockSleeper,
|
|
1846
1769
|
mockHubTaskStatusResolver,
|
|
@@ -9,7 +9,6 @@ import { SessionSubAgentActivityRepository } from './adapter-interfaces/SessionS
|
|
|
9
9
|
import { SilentSessionMessageComposer } from './adapter-interfaces/SilentSessionMessageComposer';
|
|
10
10
|
import { SilentSessionNotificationRepository } from './adapter-interfaces/SilentSessionNotificationRepository';
|
|
11
11
|
import { SilentSessionCandidateStateRepository } from './adapter-interfaces/SilentSessionCandidateStateRepository';
|
|
12
|
-
import { SilentSessionNotifiedStateRepository } from './adapter-interfaces/SilentSessionNotifiedStateRepository';
|
|
13
12
|
import { SilentSessionHubTaskStatusCacheRepository } from './adapter-interfaces/SilentSessionHubTaskStatusCacheRepository';
|
|
14
13
|
import { Sleeper } from './adapter-interfaces/Sleeper';
|
|
15
14
|
import { IssueRepository } from './adapter-interfaces/IssueRepository';
|
|
@@ -76,7 +75,6 @@ export class NotifySilentLiveSessionsUseCase {
|
|
|
76
75
|
private readonly ownerCallStatusProvider: OwnerCallStatusProvider,
|
|
77
76
|
private readonly notificationRepository: SilentSessionNotificationRepository,
|
|
78
77
|
private readonly candidateStateRepository: SilentSessionCandidateStateRepository,
|
|
79
|
-
private readonly notifiedStateRepository: SilentSessionNotifiedStateRepository,
|
|
80
78
|
private readonly messageComposer: SilentSessionMessageComposer,
|
|
81
79
|
private readonly sleeper: Sleeper,
|
|
82
80
|
private readonly hubTaskStatusResolver: HubTaskStatusResolver | null = null,
|
|
@@ -194,30 +192,8 @@ export class NotifySilentLiveSessionsUseCase {
|
|
|
194
192
|
`Silent live session notification: ${debouncedCandidates.length} debounced candidate(s) of ${candidates.length} current candidate(s) across ${interactiveSessions.length} interactive session(s); ${suppressedFirstCycleCount} first-cycle candidate(s) deferred until they persist into the next cycle.`,
|
|
195
193
|
);
|
|
196
194
|
|
|
197
|
-
// Fire-once latch: the set of sessions already reminded during their
|
|
198
|
-
// current silent episode. A session that is still a candidate this cycle
|
|
199
|
-
// and was already reminded is NOT re-injected, so a continuous silent
|
|
200
|
-
// episode produces exactly one reminder instead of one every schedule
|
|
201
|
-
// cycle. The latch is keyed by the globally-unique session name and
|
|
202
|
-
// persisted across the per-cycle fresh monitor process on disk.
|
|
203
|
-
const currentCandidateSessionNames = new Set(
|
|
204
|
-
candidates.map((candidate) => candidate.sessionName),
|
|
205
|
-
);
|
|
206
|
-
const previouslyNotifiedSessionNames =
|
|
207
|
-
await this.notifiedStateRepository.loadRecentNotifiedSessionNames({
|
|
208
|
-
now: params.now,
|
|
209
|
-
recencyWindowSeconds: params.candidateDebounceRecencyWindowSeconds,
|
|
210
|
-
});
|
|
211
|
-
|
|
212
195
|
let sentCount = 0;
|
|
213
|
-
const notifiedThisCycleSessionNames: string[] = [];
|
|
214
196
|
for (const candidate of debouncedCandidates) {
|
|
215
|
-
if (previouslyNotifiedSessionNames.has(candidate.sessionName)) {
|
|
216
|
-
console.log(
|
|
217
|
-
`Skipping ${candidate.sessionName}: the current silent-episode reminder was already delivered; not re-injecting until the condition resolves and re-arises.`,
|
|
218
|
-
);
|
|
219
|
-
continue;
|
|
220
|
-
}
|
|
221
197
|
if (
|
|
222
198
|
!(await this.isHubTaskActive(
|
|
223
199
|
candidate.sessionName,
|
|
@@ -236,7 +212,6 @@ export class NotifySilentLiveSessionsUseCase {
|
|
|
236
212
|
candidate.message,
|
|
237
213
|
);
|
|
238
214
|
sentCount += 1;
|
|
239
|
-
notifiedThisCycleSessionNames.push(candidate.sessionName);
|
|
240
215
|
// One line per send, grep-stable on the `Notified ` prefix: the
|
|
241
216
|
// ISO-8601 UTC timestamp disambiguates concurrent schedule runs and
|
|
242
217
|
// the section list records what the message actually contained.
|
|
@@ -244,25 +219,6 @@ export class NotifySilentLiveSessionsUseCase {
|
|
|
244
219
|
`Notified ${candidate.sessionName} at=${params.now.toISOString()} sections=[${candidate.sectionLabels.join(',')}]`,
|
|
245
220
|
);
|
|
246
221
|
}
|
|
247
|
-
|
|
248
|
-
// Persist the latch for the next cycle: keep every already-latched session
|
|
249
|
-
// that is STILL a candidate (refreshing its timestamp so a continuous
|
|
250
|
-
// episode stays latched) plus the sessions notified this cycle, and prune
|
|
251
|
-
// any latched session that is no longer a candidate so its episode ends and
|
|
252
|
-
// a later re-qualification fires a fresh reminder.
|
|
253
|
-
const retainedNotifiedSessionNames = [
|
|
254
|
-
...previouslyNotifiedSessionNames,
|
|
255
|
-
].filter((sessionName) => currentCandidateSessionNames.has(sessionName));
|
|
256
|
-
const notifiedSessionNamesToPersist = Array.from(
|
|
257
|
-
new Set([
|
|
258
|
-
...retainedNotifiedSessionNames,
|
|
259
|
-
...notifiedThisCycleSessionNames,
|
|
260
|
-
]),
|
|
261
|
-
);
|
|
262
|
-
await this.notifiedStateRepository.saveNotifiedSessionNames({
|
|
263
|
-
sessionNames: notifiedSessionNamesToPersist,
|
|
264
|
-
now: params.now,
|
|
265
|
-
});
|
|
266
222
|
};
|
|
267
223
|
|
|
268
224
|
private isHubTaskActive = async (
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"HandleScheduledEventUseCaseHandler.d.ts","sourceRoot":"","sources":["../../../../src/adapter/entry-points/handlers/HandleScheduledEventUseCaseHandler.ts"],"names":[],"mappings":"AAyCA,OAAO,EAAE,KAAK,EAAE,MAAM,gCAAgC,CAAC;AACvD,OAAO,EAAE,OAAO,EAAE,MAAM,kCAAkC,CAAC;AAkD3D,qBAAa,kCAAkC;IAC7C,MAAM,GACJ,gBAAgB,MAAM,EACtB,UAAU,OAAO,EACjB,6BAA4B,MAAM,EAAE,GAAG,IAAW,KACjD,OAAO,CAAC;QACT,OAAO,EAAE,OAAO,CAAC;QACjB,MAAM,EAAE,KAAK,EAAE,CAAC;QAChB,SAAS,EAAE,OAAO,CAAC;QACnB,eAAe,EAAE,IAAI,EAAE,CAAC;KACzB,GAAG,IAAI,CAAC,
|
|
1
|
+
{"version":3,"file":"HandleScheduledEventUseCaseHandler.d.ts","sourceRoot":"","sources":["../../../../src/adapter/entry-points/handlers/HandleScheduledEventUseCaseHandler.ts"],"names":[],"mappings":"AAyCA,OAAO,EAAE,KAAK,EAAE,MAAM,gCAAgC,CAAC;AACvD,OAAO,EAAE,OAAO,EAAE,MAAM,kCAAkC,CAAC;AAkD3D,qBAAa,kCAAkC;IAC7C,MAAM,GACJ,gBAAgB,MAAM,EACtB,UAAU,OAAO,EACjB,6BAA4B,MAAM,EAAE,GAAG,IAAW,KACjD,OAAO,CAAC;QACT,OAAO,EAAE,OAAO,CAAC;QACjB,MAAM,EAAE,KAAK,EAAE,CAAC;QAChB,SAAS,EAAE,OAAO,CAAC;QACnB,eAAe,EAAE,IAAI,EAAE,CAAC;KACzB,GAAG,IAAI,CAAC,CAyqBP;CACH"}
|
|
@@ -18,7 +18,6 @@ export type NotifySilentTmuxSessionsParams = {
|
|
|
18
18
|
staggerSeconds: number;
|
|
19
19
|
candidateDebounceRecencyWindowSeconds: number;
|
|
20
20
|
candidateDebounceStateFilePath: string | null;
|
|
21
|
-
notifiedStateFilePath: string | null;
|
|
22
21
|
activeHubTaskStatus: string | null;
|
|
23
22
|
hubTaskStatusResolver: HubTaskStatusResolver | null;
|
|
24
23
|
hubTaskStatusCacheStateFilePath: string | null;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"notifySilentTmuxSessions.d.ts","sourceRoot":"","sources":["../../../../src/adapter/entry-points/handlers/notifySilentTmuxSessions.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,kBAAkB,EAAE,MAAM,gEAAgE,CAAC;AAGpG,OAAO,EAAE,oBAAoB,EAAE,MAAM,kEAAkE,CAAC;AACxG,OAAO,EAEL,qBAAqB,EAQtB,MAAM,0DAA0D,CAAC;AAgBlE,OAAO,EAEL,6BAA6B,EAC9B,MAAM,6DAA6D,CAAC;
|
|
1
|
+
{"version":3,"file":"notifySilentTmuxSessions.d.ts","sourceRoot":"","sources":["../../../../src/adapter/entry-points/handlers/notifySilentTmuxSessions.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,kBAAkB,EAAE,MAAM,gEAAgE,CAAC;AAGpG,OAAO,EAAE,oBAAoB,EAAE,MAAM,kEAAkE,CAAC;AACxG,OAAO,EAEL,qBAAqB,EAQtB,MAAM,0DAA0D,CAAC;AAgBlE,OAAO,EAEL,6BAA6B,EAC9B,MAAM,6DAA6D,CAAC;AAKrE,MAAM,MAAM,8BAA8B,GAAG;IAC3C,OAAO,EAAE,OAAO,CAAC;IACjB,kBAAkB,EAAE,kBAAkB,CAAC;IACvC,oBAAoB,CAAC,EAAE,oBAAoB,CAAC;IAC5C,eAAe,EAAE,MAAM,GAAG,IAAI,CAAC;IAC/B,2BAA2B,EAAE,MAAM,GAAG,IAAI,CAAC;IAC3C,2BAA2B,EAAE,MAAM,GAAG,IAAI,CAAC;IAC3C,+BAA+B,EAAE,MAAM,GAAG,IAAI,CAAC;IAC/C,4BAA4B,EAAE,MAAM,GAAG,IAAI,CAAC;IAC5C,0BAA0B,EAAE,MAAM,CAAC;IACnC,+BAA+B,EAAE,MAAM,CAAC;IACxC,8BAA8B,EAAE,MAAM,CAAC;IACvC,+BAA+B,EAAE,MAAM,CAAC;IACxC,cAAc,EAAE,MAAM,CAAC;IACvB,qCAAqC,EAAE,MAAM,CAAC;IAC9C,8BAA8B,EAAE,MAAM,GAAG,IAAI,CAAC;IAC9C,mBAAmB,EAAE,MAAM,GAAG,IAAI,CAAC;IACnC,qBAAqB,EAAE,qBAAqB,GAAG,IAAI,CAAC;IACpD,+BAA+B,EAAE,MAAM,GAAG,IAAI,CAAC;IAC/C,4BAA4B,EAAE,MAAM,CAAC;IACrC,gBAAgB,EAAE,6BAA6B,CAAC;IAChD,GAAG,EAAE,IAAI,CAAC;CACX,CAAC;AAuCF,eAAO,MAAM,wBAAwB,GACnC,QAAQ,8BAA8B,KACrC,OAAO,CAAC,IAAI,CA6Ed,CAAC;AAEF,eAAO,MAAM,0CAA0C;;;;;;;;CAS7C,CAAC"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"TmuxSilentSessionNotificationRepository.d.ts","sourceRoot":"","sources":["../../../src/adapter/repositories/TmuxSilentSessionNotificationRepository.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,mCAAmC,EAAE,MAAM,8EAA8E,CAAC;AACnI,OAAO,EAAE,kBAAkB,EAAE,MAAM,6DAA6D,CAAC;AAKjG,qBAAa,uCAAwC,YAAW,mCAAmC;IACrF,OAAO,CAAC,QAAQ,CAAC,kBAAkB;gBAAlB,kBAAkB,EAAE,kBAAkB;IAEnE,yBAAyB,GACvB,aAAa,MAAM,EACnB,SAAS,MAAM,KACd,OAAO,CAAC,IAAI,CAAC,
|
|
1
|
+
{"version":3,"file":"TmuxSilentSessionNotificationRepository.d.ts","sourceRoot":"","sources":["../../../src/adapter/repositories/TmuxSilentSessionNotificationRepository.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,mCAAmC,EAAE,MAAM,8EAA8E,CAAC;AACnI,OAAO,EAAE,kBAAkB,EAAE,MAAM,6DAA6D,CAAC;AAKjG,qBAAa,uCAAwC,YAAW,mCAAmC;IACrF,OAAO,CAAC,QAAQ,CAAC,kBAAkB;gBAAlB,kBAAkB,EAAE,kBAAkB;IAEnE,yBAAyB,GACvB,aAAa,MAAM,EACnB,SAAS,MAAM,KACd,OAAO,CAAC,IAAI,CAAC,CA8Bd;CACH"}
|
|
@@ -7,7 +7,6 @@ import { SessionSubAgentActivityRepository } from './adapter-interfaces/SessionS
|
|
|
7
7
|
import { SilentSessionMessageComposer } from './adapter-interfaces/SilentSessionMessageComposer';
|
|
8
8
|
import { SilentSessionNotificationRepository } from './adapter-interfaces/SilentSessionNotificationRepository';
|
|
9
9
|
import { SilentSessionCandidateStateRepository } from './adapter-interfaces/SilentSessionCandidateStateRepository';
|
|
10
|
-
import { SilentSessionNotifiedStateRepository } from './adapter-interfaces/SilentSessionNotifiedStateRepository';
|
|
11
10
|
import { SilentSessionHubTaskStatusCacheRepository } from './adapter-interfaces/SilentSessionHubTaskStatusCacheRepository';
|
|
12
11
|
import { Sleeper } from './adapter-interfaces/Sleeper';
|
|
13
12
|
import { IssueRepository } from './adapter-interfaces/IssueRepository';
|
|
@@ -29,14 +28,13 @@ export declare class NotifySilentLiveSessionsUseCase {
|
|
|
29
28
|
private readonly ownerCallStatusProvider;
|
|
30
29
|
private readonly notificationRepository;
|
|
31
30
|
private readonly candidateStateRepository;
|
|
32
|
-
private readonly notifiedStateRepository;
|
|
33
31
|
private readonly messageComposer;
|
|
34
32
|
private readonly sleeper;
|
|
35
33
|
private readonly hubTaskStatusResolver;
|
|
36
34
|
private readonly hubTaskStatusCacheRepository;
|
|
37
35
|
private readonly refusalTailStatusProvider;
|
|
38
36
|
private readonly resolveInteractiveLiveSessions;
|
|
39
|
-
constructor(liveSessionProcessSnapshotProvider: LiveSessionProcessSnapshotProvider, interactiveLiveSessionTranscriptResolver: InteractiveLiveSessionTranscriptResolver, sessionOutputActivityRepository: SessionOutputActivityRepository, subAgentActivityRepository: SessionSubAgentActivityRepository, ownerCallStatusProvider: OwnerCallStatusProvider, notificationRepository: SilentSessionNotificationRepository, candidateStateRepository: SilentSessionCandidateStateRepository,
|
|
37
|
+
constructor(liveSessionProcessSnapshotProvider: LiveSessionProcessSnapshotProvider, interactiveLiveSessionTranscriptResolver: InteractiveLiveSessionTranscriptResolver, sessionOutputActivityRepository: SessionOutputActivityRepository, subAgentActivityRepository: SessionSubAgentActivityRepository, ownerCallStatusProvider: OwnerCallStatusProvider, notificationRepository: SilentSessionNotificationRepository, candidateStateRepository: SilentSessionCandidateStateRepository, messageComposer: SilentSessionMessageComposer, sleeper: Sleeper, hubTaskStatusResolver?: HubTaskStatusResolver | null, hubTaskStatusCacheRepository?: SilentSessionHubTaskStatusCacheRepository | null, refusalTailStatusProvider?: RefusalTailStatusProvider | null);
|
|
40
38
|
run: (params: {
|
|
41
39
|
mainSilentThresholdSeconds: number;
|
|
42
40
|
unansweredOwnerCallGraceSeconds: number;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"NotifySilentLiveSessionsUseCase.d.ts","sourceRoot":"","sources":["../../../src/domain/usecases/NotifySilentLiveSessionsUseCase.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,kCAAkC,EAAE,MAAM,yDAAyD,CAAC;AAC7G,OAAO,EAAE,wCAAwC,EAAE,MAAM,+DAA+D,CAAC;AACzH,OAAO,EAAE,uBAAuB,EAAE,MAAM,8CAA8C,CAAC;AACvF,OAAO,EAAE,yBAAyB,EAAE,MAAM,gDAAgD,CAAC;AAC3F,OAAO,EAAE,+BAA+B,EAAE,MAAM,sDAAsD,CAAC;AACvG,OAAO,EAAE,iCAAiC,EAAE,MAAM,wDAAwD,CAAC;AAC3G,OAAO,EAAE,4BAA4B,EAAE,MAAM,mDAAmD,CAAC;AACjG,OAAO,EAAE,mCAAmC,EAAE,MAAM,0DAA0D,CAAC;AAC/G,OAAO,EAAE,qCAAqC,EAAE,MAAM,4DAA4D,CAAC;AACnH,OAAO,EAAE,
|
|
1
|
+
{"version":3,"file":"NotifySilentLiveSessionsUseCase.d.ts","sourceRoot":"","sources":["../../../src/domain/usecases/NotifySilentLiveSessionsUseCase.ts"],"names":[],"mappings":"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"}
|