github-issue-tower-defence-management 1.138.0 → 1.139.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.
- package/CHANGELOG.md +14 -0
- package/README.md +3 -2
- package/bin/adapter/entry-points/handlers/HandleScheduledEventUseCaseHandler.js +22 -2
- package/bin/adapter/entry-points/handlers/HandleScheduledEventUseCaseHandler.js.map +1 -1
- package/bin/adapter/entry-points/handlers/notifySilentTmuxSessions.js +9 -5
- package/bin/adapter/entry-points/handlers/notifySilentTmuxSessions.js.map +1 -1
- package/bin/adapter/repositories/FileSystemSilentSessionNotifiedStateRepository.js +125 -0
- package/bin/adapter/repositories/FileSystemSilentSessionNotifiedStateRepository.js.map +1 -0
- package/bin/adapter/repositories/FileSystemSubAgentLivenessResolver.js +81 -0
- package/bin/adapter/repositories/FileSystemSubAgentLivenessResolver.js.map +1 -0
- package/bin/adapter/repositories/TmuxSilentSessionNotificationRepository.js +4 -1
- package/bin/adapter/repositories/TmuxSilentSessionNotificationRepository.js.map +1 -1
- package/bin/adapter/repositories/TranscriptSessionSubAgentActivityRepository.js +30 -12
- package/bin/adapter/repositories/TranscriptSessionSubAgentActivityRepository.js.map +1 -1
- package/bin/domain/usecases/NotifySilentLiveSessionsUseCase.js +48 -1
- package/bin/domain/usecases/NotifySilentLiveSessionsUseCase.js.map +1 -1
- package/bin/domain/usecases/adapter-interfaces/SilentSessionNotifiedStateRepository.js +3 -0
- package/bin/domain/usecases/adapter-interfaces/SilentSessionNotifiedStateRepository.js.map +1 -0
- package/bin/domain/usecases/adapter-interfaces/SubAgentLivenessResolver.js +3 -0
- package/bin/domain/usecases/adapter-interfaces/SubAgentLivenessResolver.js.map +1 -0
- package/package.json +1 -1
- package/src/adapter/entry-points/handlers/HandleScheduledEventUseCaseHandler.ts +16 -0
- package/src/adapter/entry-points/handlers/notifySilentTmuxSessions.test.ts +31 -0
- package/src/adapter/entry-points/handlers/notifySilentTmuxSessions.ts +14 -0
- package/src/adapter/repositories/FileSystemSilentSessionNotifiedStateRepository.test.ts +127 -0
- package/src/adapter/repositories/FileSystemSilentSessionNotifiedStateRepository.ts +108 -0
- package/src/adapter/repositories/FileSystemSubAgentLivenessResolver.test.ts +87 -0
- package/src/adapter/repositories/FileSystemSubAgentLivenessResolver.ts +57 -0
- package/src/adapter/repositories/TmuxSilentSessionNotificationRepository.test.ts +23 -2
- package/src/adapter/repositories/TmuxSilentSessionNotificationRepository.ts +5 -1
- package/src/adapter/repositories/TranscriptSessionSubAgentActivityRepository.test.ts +136 -6
- package/src/adapter/repositories/TranscriptSessionSubAgentActivityRepository.ts +37 -12
- package/src/domain/usecases/NotifySilentLiveSessionsUseCase.test.ts +218 -8
- package/src/domain/usecases/NotifySilentLiveSessionsUseCase.ts +60 -0
- package/src/domain/usecases/adapter-interfaces/SilentSessionNotifiedStateRepository.ts +10 -0
- package/src/domain/usecases/adapter-interfaces/SubAgentLivenessResolver.ts +6 -0
- package/types/adapter/entry-points/handlers/HandleScheduledEventUseCaseHandler.d.ts.map +1 -1
- package/types/adapter/entry-points/handlers/notifySilentTmuxSessions.d.ts +2 -0
- package/types/adapter/entry-points/handlers/notifySilentTmuxSessions.d.ts.map +1 -1
- package/types/adapter/repositories/FileSystemSilentSessionNotifiedStateRepository.d.ts +16 -0
- package/types/adapter/repositories/FileSystemSilentSessionNotifiedStateRepository.d.ts.map +1 -0
- package/types/adapter/repositories/FileSystemSubAgentLivenessResolver.d.ts +11 -0
- package/types/adapter/repositories/FileSystemSubAgentLivenessResolver.d.ts.map +1 -0
- package/types/adapter/repositories/TmuxSilentSessionNotificationRepository.d.ts.map +1 -1
- package/types/adapter/repositories/TranscriptSessionSubAgentActivityRepository.d.ts +4 -2
- package/types/adapter/repositories/TranscriptSessionSubAgentActivityRepository.d.ts.map +1 -1
- package/types/domain/usecases/NotifySilentLiveSessionsUseCase.d.ts +3 -1
- package/types/domain/usecases/NotifySilentLiveSessionsUseCase.d.ts.map +1 -1
- package/types/domain/usecases/adapter-interfaces/SilentSessionNotifiedStateRepository.d.ts +11 -0
- package/types/domain/usecases/adapter-interfaces/SilentSessionNotifiedStateRepository.d.ts.map +1 -0
- package/types/domain/usecases/adapter-interfaces/SubAgentLivenessResolver.d.ts +7 -0
- package/types/domain/usecases/adapter-interfaces/SubAgentLivenessResolver.d.ts.map +1 -0
|
@@ -2,9 +2,14 @@ import * as fs from 'fs';
|
|
|
2
2
|
import * as path from 'path';
|
|
3
3
|
import { SubAgentActivity } from '../../domain/entities/LiveSessionActivitySnapshot';
|
|
4
4
|
import { SessionSubAgentActivityRepository } from '../../domain/usecases/adapter-interfaces/SessionSubAgentActivityRepository';
|
|
5
|
+
import { SubAgentLivenessResolver } from '../../domain/usecases/adapter-interfaces/SubAgentLivenessResolver';
|
|
5
6
|
import { SubAgentProcessLister } from '../../domain/usecases/adapter-interfaces/SubAgentProcessLister';
|
|
6
7
|
import { SubAgentTranscriptDirectoryResolver } from '../../domain/usecases/adapter-interfaces/SubAgentTranscriptDirectoryResolver';
|
|
7
8
|
|
|
9
|
+
const alwaysIndeterminateLivenessResolver: SubAgentLivenessResolver = {
|
|
10
|
+
resolveLiveSubAgentIds: async () => null,
|
|
11
|
+
};
|
|
12
|
+
|
|
8
13
|
const isRecord = (value: unknown): value is Record<string, unknown> =>
|
|
9
14
|
typeof value === 'object' && value !== null;
|
|
10
15
|
|
|
@@ -190,7 +195,14 @@ const parseTranscript = (content: string): ParsedTranscript => {
|
|
|
190
195
|
|
|
191
196
|
const clampToZero = (value: number): number => (value > 0 ? value : 0);
|
|
192
197
|
|
|
193
|
-
const
|
|
198
|
+
const TERMINAL_TASK_NOTIFICATION_STATUSES = new Set([
|
|
199
|
+
'completed',
|
|
200
|
+
'killed',
|
|
201
|
+
'failed',
|
|
202
|
+
'stopped',
|
|
203
|
+
]);
|
|
204
|
+
|
|
205
|
+
const parseTerminalAgentIds = (content: string): Set<string> => {
|
|
194
206
|
const result = new Set<string>();
|
|
195
207
|
for (const line of content.split('\n')) {
|
|
196
208
|
const trimmed = line.trim();
|
|
@@ -216,13 +228,16 @@ const parseKilledOrFailedAgentIds = (content: string): Set<string> => {
|
|
|
216
228
|
if (notifContent === null) {
|
|
217
229
|
continue;
|
|
218
230
|
}
|
|
219
|
-
const taskIdMatch = notifContent.match(/<task-id>(a[0-9a-f]+)<\/task-id>/);
|
|
220
231
|
const statusMatch = notifContent.match(/<status>([^<]+)<\/status>/);
|
|
221
|
-
if (
|
|
232
|
+
if (statusMatch === null) {
|
|
222
233
|
continue;
|
|
223
234
|
}
|
|
224
|
-
|
|
225
|
-
|
|
235
|
+
if (!TERMINAL_TASK_NOTIFICATION_STATUSES.has(statusMatch[1])) {
|
|
236
|
+
continue;
|
|
237
|
+
}
|
|
238
|
+
for (const taskIdMatch of notifContent.matchAll(
|
|
239
|
+
/<task-id>(a[0-9a-f]+)<\/task-id>/g,
|
|
240
|
+
)) {
|
|
226
241
|
result.add(taskIdMatch[1]);
|
|
227
242
|
}
|
|
228
243
|
}
|
|
@@ -234,6 +249,7 @@ export class TranscriptSessionSubAgentActivityRepository implements SessionSubAg
|
|
|
234
249
|
private readonly directoryResolver: SubAgentTranscriptDirectoryResolver,
|
|
235
250
|
private readonly processLister: SubAgentProcessLister,
|
|
236
251
|
private readonly now: Date,
|
|
252
|
+
private readonly livenessResolver: SubAgentLivenessResolver = alwaysIndeterminateLivenessResolver,
|
|
237
253
|
) {}
|
|
238
254
|
|
|
239
255
|
listSubAgentActivitiesBySessionName = async (
|
|
@@ -262,12 +278,17 @@ export class TranscriptSessionSubAgentActivityRepository implements SessionSubAg
|
|
|
262
278
|
if (directory === null) {
|
|
263
279
|
continue;
|
|
264
280
|
}
|
|
265
|
-
const
|
|
266
|
-
|
|
281
|
+
const terminalAgentIds = this.loadTerminalAgentIds(mainTranscriptPath);
|
|
282
|
+
const liveSubAgentIds =
|
|
283
|
+
await this.livenessResolver.resolveLiveSubAgentIds({
|
|
284
|
+
sessionName,
|
|
285
|
+
mainTranscriptPath,
|
|
286
|
+
});
|
|
267
287
|
const activities = await this.collectActivities(
|
|
268
288
|
directory,
|
|
269
289
|
nowEpochSeconds,
|
|
270
|
-
|
|
290
|
+
terminalAgentIds,
|
|
291
|
+
liveSubAgentIds,
|
|
271
292
|
loadNormalizedProcessCommandLines,
|
|
272
293
|
);
|
|
273
294
|
if (activities.length > 0) {
|
|
@@ -277,7 +298,7 @@ export class TranscriptSessionSubAgentActivityRepository implements SessionSubAg
|
|
|
277
298
|
return result;
|
|
278
299
|
};
|
|
279
300
|
|
|
280
|
-
private
|
|
301
|
+
private loadTerminalAgentIds = (
|
|
281
302
|
mainTranscriptPath: string | null,
|
|
282
303
|
): Set<string> => {
|
|
283
304
|
if (mainTranscriptPath === null) {
|
|
@@ -289,13 +310,14 @@ export class TranscriptSessionSubAgentActivityRepository implements SessionSubAg
|
|
|
289
310
|
} catch {
|
|
290
311
|
return new Set();
|
|
291
312
|
}
|
|
292
|
-
return
|
|
313
|
+
return parseTerminalAgentIds(content);
|
|
293
314
|
};
|
|
294
315
|
|
|
295
316
|
private collectActivities = async (
|
|
296
317
|
directory: string,
|
|
297
318
|
nowEpochSeconds: number,
|
|
298
|
-
|
|
319
|
+
terminalAgentIds: Set<string>,
|
|
320
|
+
liveSubAgentIds: Set<string> | null,
|
|
299
321
|
loadNormalizedProcessCommandLines: () => Promise<string[]>,
|
|
300
322
|
): Promise<SubAgentActivity[]> => {
|
|
301
323
|
let entries: fs.Dirent[];
|
|
@@ -311,7 +333,10 @@ export class TranscriptSessionSubAgentActivityRepository implements SessionSubAg
|
|
|
311
333
|
continue;
|
|
312
334
|
}
|
|
313
335
|
const agentId = fileName.slice('agent-'.length, -'.jsonl'.length);
|
|
314
|
-
if (
|
|
336
|
+
if (terminalAgentIds.has(agentId)) {
|
|
337
|
+
continue;
|
|
338
|
+
}
|
|
339
|
+
if (liveSubAgentIds !== null && !liveSubAgentIds.has(agentId)) {
|
|
315
340
|
continue;
|
|
316
341
|
}
|
|
317
342
|
const filePath = path.join(directory, fileName);
|
|
@@ -21,6 +21,7 @@ import { OwnerCallStatusProvider } from './adapter-interfaces/OwnerCallStatusPro
|
|
|
21
21
|
import { RefusalTailStatusProvider } from './adapter-interfaces/RefusalTailStatusProvider';
|
|
22
22
|
import { SilentSessionNotificationRepository } from './adapter-interfaces/SilentSessionNotificationRepository';
|
|
23
23
|
import { SilentSessionCandidateStateRepository } from './adapter-interfaces/SilentSessionCandidateStateRepository';
|
|
24
|
+
import { SilentSessionNotifiedStateRepository } from './adapter-interfaces/SilentSessionNotifiedStateRepository';
|
|
24
25
|
import { SilentSessionHubTaskStatusCacheRepository } from './adapter-interfaces/SilentSessionHubTaskStatusCacheRepository';
|
|
25
26
|
import { SilentSessionMessageComposer } from './adapter-interfaces/SilentSessionMessageComposer';
|
|
26
27
|
import { Sleeper } from './adapter-interfaces/Sleeper';
|
|
@@ -50,6 +51,7 @@ describe('NotifySilentLiveSessionsUseCase', () => {
|
|
|
50
51
|
let mockOwnerCallStatusProvider: Mocked<OwnerCallStatusProvider>;
|
|
51
52
|
let mockNotificationRepository: Mocked<SilentSessionNotificationRepository>;
|
|
52
53
|
let mockCandidateStateRepository: Mocked<SilentSessionCandidateStateRepository>;
|
|
54
|
+
let mockNotifiedStateRepository: Mocked<SilentSessionNotifiedStateRepository>;
|
|
53
55
|
let mockMessageComposer: Mocked<SilentSessionMessageComposer>;
|
|
54
56
|
let mockSleeper: Mocked<Sleeper>;
|
|
55
57
|
let mockHubTaskStatusResolver: Mocked<HubTaskStatusResolver>;
|
|
@@ -169,6 +171,12 @@ describe('NotifySilentLiveSessionsUseCase', () => {
|
|
|
169
171
|
.mockResolvedValue(everyNameRecentSet()),
|
|
170
172
|
saveCandidateSessionNames: jest.fn().mockResolvedValue(undefined),
|
|
171
173
|
};
|
|
174
|
+
mockNotifiedStateRepository = {
|
|
175
|
+
loadRecentNotifiedSessionNames: jest
|
|
176
|
+
.fn()
|
|
177
|
+
.mockResolvedValue(new Set<string>()),
|
|
178
|
+
saveNotifiedSessionNames: jest.fn().mockResolvedValue(undefined),
|
|
179
|
+
};
|
|
172
180
|
mockMessageComposer = {
|
|
173
181
|
composeMainStalledSection: jest
|
|
174
182
|
.fn()
|
|
@@ -201,6 +209,7 @@ describe('NotifySilentLiveSessionsUseCase', () => {
|
|
|
201
209
|
mockOwnerCallStatusProvider,
|
|
202
210
|
mockNotificationRepository,
|
|
203
211
|
mockCandidateStateRepository,
|
|
212
|
+
mockNotifiedStateRepository,
|
|
204
213
|
mockMessageComposer,
|
|
205
214
|
mockSleeper,
|
|
206
215
|
mockHubTaskStatusResolver,
|
|
@@ -209,6 +218,20 @@ describe('NotifySilentLiveSessionsUseCase', () => {
|
|
|
209
218
|
);
|
|
210
219
|
});
|
|
211
220
|
|
|
221
|
+
// Simulates the on-disk fire-once latch across the per-cycle fresh monitor
|
|
222
|
+
// process: load returns the currently persisted set, save overwrites it.
|
|
223
|
+
const wireStatefulNotifiedLatch = (): void => {
|
|
224
|
+
let persistedNotifiedSessionNames = new Set<string>();
|
|
225
|
+
mockNotifiedStateRepository.loadRecentNotifiedSessionNames.mockImplementation(
|
|
226
|
+
async () => new Set(persistedNotifiedSessionNames),
|
|
227
|
+
);
|
|
228
|
+
mockNotifiedStateRepository.saveNotifiedSessionNames.mockImplementation(
|
|
229
|
+
async ({ sessionNames }) => {
|
|
230
|
+
persistedNotifiedSessionNames = new Set(sessionNames);
|
|
231
|
+
},
|
|
232
|
+
);
|
|
233
|
+
};
|
|
234
|
+
|
|
212
235
|
const issueFor = (overrides: Partial<Issue>): Issue => ({
|
|
213
236
|
nameWithOwner: 'HiromiShikata/repo',
|
|
214
237
|
number: 42,
|
|
@@ -706,7 +729,8 @@ describe('NotifySilentLiveSessionsUseCase', () => {
|
|
|
706
729
|
).not.toHaveBeenCalled();
|
|
707
730
|
});
|
|
708
731
|
|
|
709
|
-
it('
|
|
732
|
+
it('notifies a persistent stall only once per silent episode instead of every cycle', async () => {
|
|
733
|
+
wireStatefulNotifiedLatch();
|
|
710
734
|
setupLiveInteractiveSession(GITHUB_SESSION);
|
|
711
735
|
mockSessionOutputActivityRepository.listSessionOutputActivities.mockResolvedValue(
|
|
712
736
|
[
|
|
@@ -719,18 +743,60 @@ describe('NotifySilentLiveSessionsUseCase', () => {
|
|
|
719
743
|
],
|
|
720
744
|
);
|
|
721
745
|
|
|
746
|
+
await useCase.run(runParams());
|
|
722
747
|
await useCase.run(runParams());
|
|
723
748
|
await useCase.run(runParams());
|
|
724
749
|
|
|
725
750
|
expect(
|
|
726
751
|
mockNotificationRepository.sendSelfCheckNotification,
|
|
727
|
-
).toHaveBeenCalledTimes(
|
|
752
|
+
).toHaveBeenCalledTimes(1);
|
|
753
|
+
expect(
|
|
754
|
+
mockNotificationRepository.sendSelfCheckNotification,
|
|
755
|
+
).toHaveBeenCalledWith(GITHUB_SESSION, MAIN_STALLED_SECTION);
|
|
756
|
+
});
|
|
757
|
+
|
|
758
|
+
it('re-notifies a session after its silent episode resolves and a new one begins', async () => {
|
|
759
|
+
wireStatefulNotifiedLatch();
|
|
760
|
+
setupLiveInteractiveSession(GITHUB_SESSION);
|
|
761
|
+
const stalled = [
|
|
762
|
+
{
|
|
763
|
+
sessionName: GITHUB_SESSION,
|
|
764
|
+
lastOutputEpochSeconds:
|
|
765
|
+
nowEpochSeconds - DEFAULT_MAIN_SILENT_THRESHOLD_SECONDS,
|
|
766
|
+
hasInProgressToolCall: false,
|
|
767
|
+
},
|
|
768
|
+
];
|
|
769
|
+
const active = [
|
|
770
|
+
{
|
|
771
|
+
sessionName: GITHUB_SESSION,
|
|
772
|
+
lastOutputEpochSeconds: nowEpochSeconds,
|
|
773
|
+
hasInProgressToolCall: false,
|
|
774
|
+
},
|
|
775
|
+
];
|
|
776
|
+
|
|
777
|
+
mockSessionOutputActivityRepository.listSessionOutputActivities.mockResolvedValue(
|
|
778
|
+
stalled,
|
|
779
|
+
);
|
|
780
|
+
await useCase.run(runParams());
|
|
781
|
+
expect(
|
|
782
|
+
mockNotificationRepository.sendSelfCheckNotification,
|
|
783
|
+
).toHaveBeenCalledTimes(1);
|
|
784
|
+
|
|
785
|
+
mockSessionOutputActivityRepository.listSessionOutputActivities.mockResolvedValue(
|
|
786
|
+
active,
|
|
787
|
+
);
|
|
788
|
+
await useCase.run(runParams());
|
|
728
789
|
expect(
|
|
729
790
|
mockNotificationRepository.sendSelfCheckNotification,
|
|
730
|
-
).
|
|
791
|
+
).toHaveBeenCalledTimes(1);
|
|
792
|
+
|
|
793
|
+
mockSessionOutputActivityRepository.listSessionOutputActivities.mockResolvedValue(
|
|
794
|
+
stalled,
|
|
795
|
+
);
|
|
796
|
+
await useCase.run(runParams());
|
|
731
797
|
expect(
|
|
732
798
|
mockNotificationRepository.sendSelfCheckNotification,
|
|
733
|
-
).
|
|
799
|
+
).toHaveBeenCalledTimes(2);
|
|
734
800
|
});
|
|
735
801
|
|
|
736
802
|
describe('two-consecutive-cycle debounce', () => {
|
|
@@ -832,6 +898,147 @@ describe('NotifySilentLiveSessionsUseCase', () => {
|
|
|
832
898
|
});
|
|
833
899
|
});
|
|
834
900
|
|
|
901
|
+
describe('fire-once latch and input-state gating', () => {
|
|
902
|
+
const setupSilentGithubSession = (): void => {
|
|
903
|
+
setupLiveInteractiveSession(GITHUB_SESSION);
|
|
904
|
+
mockSessionOutputActivityRepository.listSessionOutputActivities.mockResolvedValue(
|
|
905
|
+
[
|
|
906
|
+
{
|
|
907
|
+
sessionName: GITHUB_SESSION,
|
|
908
|
+
lastOutputEpochSeconds:
|
|
909
|
+
nowEpochSeconds - DEFAULT_MAIN_SILENT_THRESHOLD_SECONDS,
|
|
910
|
+
hasInProgressToolCall: false,
|
|
911
|
+
},
|
|
912
|
+
],
|
|
913
|
+
);
|
|
914
|
+
};
|
|
915
|
+
|
|
916
|
+
const idleSubAgent = (label: string): SubAgentActivity => ({
|
|
917
|
+
label,
|
|
918
|
+
silentSeconds: DEFAULT_SUBAGENT_SILENT_THRESHOLD_SECONDS,
|
|
919
|
+
runningSeconds: 60,
|
|
920
|
+
waitingOnExternalProcess: false,
|
|
921
|
+
});
|
|
922
|
+
|
|
923
|
+
it('loads the fire-once latch using the configured recency window', async () => {
|
|
924
|
+
setupSilentGithubSession();
|
|
925
|
+
|
|
926
|
+
await useCase.run(
|
|
927
|
+
runParams({ candidateDebounceRecencyWindowSeconds: 900 }),
|
|
928
|
+
);
|
|
929
|
+
|
|
930
|
+
expect(
|
|
931
|
+
mockNotifiedStateRepository.loadRecentNotifiedSessionNames,
|
|
932
|
+
).toHaveBeenCalledWith({ now, recencyWindowSeconds: 900 });
|
|
933
|
+
});
|
|
934
|
+
|
|
935
|
+
it('persists the sent session in the fire-once latch on a send cycle', async () => {
|
|
936
|
+
setupSilentGithubSession();
|
|
937
|
+
|
|
938
|
+
await useCase.run(runParams());
|
|
939
|
+
|
|
940
|
+
expect(
|
|
941
|
+
mockNotificationRepository.sendSelfCheckNotification,
|
|
942
|
+
).toHaveBeenCalledWith(GITHUB_SESSION, MAIN_STALLED_SECTION);
|
|
943
|
+
expect(
|
|
944
|
+
mockNotifiedStateRepository.saveNotifiedSessionNames,
|
|
945
|
+
).toHaveBeenCalledWith({ sessionNames: [GITHUB_SESSION], now });
|
|
946
|
+
});
|
|
947
|
+
|
|
948
|
+
it('does not re-send but keeps the latch for a session already notified this episode', async () => {
|
|
949
|
+
setupSilentGithubSession();
|
|
950
|
+
mockNotifiedStateRepository.loadRecentNotifiedSessionNames.mockResolvedValue(
|
|
951
|
+
new Set([GITHUB_SESSION]),
|
|
952
|
+
);
|
|
953
|
+
|
|
954
|
+
await useCase.run(runParams());
|
|
955
|
+
|
|
956
|
+
expect(
|
|
957
|
+
mockNotificationRepository.sendSelfCheckNotification,
|
|
958
|
+
).not.toHaveBeenCalled();
|
|
959
|
+
expect(
|
|
960
|
+
mockNotifiedStateRepository.saveNotifiedSessionNames,
|
|
961
|
+
).toHaveBeenCalledWith({ sessionNames: [GITHUB_SESSION], now });
|
|
962
|
+
});
|
|
963
|
+
|
|
964
|
+
it('prunes the fire-once latch for a previously notified session that is no longer a candidate', async () => {
|
|
965
|
+
mockNotifiedStateRepository.loadRecentNotifiedSessionNames.mockResolvedValue(
|
|
966
|
+
new Set([GITHUB_SESSION]),
|
|
967
|
+
);
|
|
968
|
+
|
|
969
|
+
await useCase.run(runParams());
|
|
970
|
+
|
|
971
|
+
expect(
|
|
972
|
+
mockNotifiedStateRepository.saveNotifiedSessionNames,
|
|
973
|
+
).toHaveBeenCalledWith({ sessionNames: [], now });
|
|
974
|
+
});
|
|
975
|
+
|
|
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
|
+
);
|
|
990
|
+
|
|
991
|
+
await useCase.run(runParams());
|
|
992
|
+
|
|
993
|
+
expect(mockMessageComposer.composeSubAgentSection).toHaveBeenCalled();
|
|
994
|
+
expect(
|
|
995
|
+
mockNotificationRepository.sendSelfCheckNotification,
|
|
996
|
+
).not.toHaveBeenCalled();
|
|
997
|
+
expect(
|
|
998
|
+
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);
|
|
1039
|
+
});
|
|
1040
|
+
});
|
|
1041
|
+
|
|
835
1042
|
describe('sub-agent state-based reminder judgment', () => {
|
|
836
1043
|
const hungSubAgent = (label: string): SubAgentActivity => ({
|
|
837
1044
|
label,
|
|
@@ -893,7 +1100,8 @@ describe('NotifySilentLiveSessionsUseCase', () => {
|
|
|
893
1100
|
).toHaveBeenCalledWith(GITHUB_SESSION, SUBAGENT_SECTION);
|
|
894
1101
|
});
|
|
895
1102
|
|
|
896
|
-
it('
|
|
1103
|
+
it('notifies a hung sub-agent only once per episode instead of every cycle', async () => {
|
|
1104
|
+
wireStatefulNotifiedLatch();
|
|
897
1105
|
setupSubAgents([hungSubAgent('sub-process-1')]);
|
|
898
1106
|
|
|
899
1107
|
await useCase.run(runParams());
|
|
@@ -901,7 +1109,7 @@ describe('NotifySilentLiveSessionsUseCase', () => {
|
|
|
901
1109
|
|
|
902
1110
|
expect(
|
|
903
1111
|
mockNotificationRepository.sendSelfCheckNotification,
|
|
904
|
-
).toHaveBeenCalledTimes(
|
|
1112
|
+
).toHaveBeenCalledTimes(1);
|
|
905
1113
|
});
|
|
906
1114
|
|
|
907
1115
|
it('never selects a waiting sub-agent for the long-running section even when quiet past both thresholds', async () => {
|
|
@@ -942,7 +1150,8 @@ describe('NotifySilentLiveSessionsUseCase', () => {
|
|
|
942
1150
|
).toHaveBeenCalledWith(GITHUB_SESSION, SUBAGENT_SECTION);
|
|
943
1151
|
});
|
|
944
1152
|
|
|
945
|
-
it('
|
|
1153
|
+
it('notifies a quiet long-running sub-agent only once per episode while still evaluating it every cycle', async () => {
|
|
1154
|
+
wireStatefulNotifiedLatch();
|
|
946
1155
|
setupSubAgents([quietLongRunningSubAgent('sub-process-1', false)]);
|
|
947
1156
|
|
|
948
1157
|
await useCase.run(runParams());
|
|
@@ -950,7 +1159,7 @@ describe('NotifySilentLiveSessionsUseCase', () => {
|
|
|
950
1159
|
|
|
951
1160
|
expect(
|
|
952
1161
|
mockNotificationRepository.sendSelfCheckNotification,
|
|
953
|
-
).toHaveBeenCalledTimes(
|
|
1162
|
+
).toHaveBeenCalledTimes(1);
|
|
954
1163
|
expect(
|
|
955
1164
|
mockMessageComposer.composeSubAgentSection,
|
|
956
1165
|
).toHaveBeenNthCalledWith(2, {
|
|
@@ -1626,6 +1835,7 @@ describe('NotifySilentLiveSessionsUseCase', () => {
|
|
|
1626
1835
|
mockOwnerCallStatusProvider,
|
|
1627
1836
|
mockNotificationRepository,
|
|
1628
1837
|
mockCandidateStateRepository,
|
|
1838
|
+
mockNotifiedStateRepository,
|
|
1629
1839
|
mockMessageComposer,
|
|
1630
1840
|
mockSleeper,
|
|
1631
1841
|
mockHubTaskStatusResolver,
|
|
@@ -9,6 +9,7 @@ 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';
|
|
12
13
|
import { SilentSessionHubTaskStatusCacheRepository } from './adapter-interfaces/SilentSessionHubTaskStatusCacheRepository';
|
|
13
14
|
import { Sleeper } from './adapter-interfaces/Sleeper';
|
|
14
15
|
import { IssueRepository } from './adapter-interfaces/IssueRepository';
|
|
@@ -70,6 +71,7 @@ type NotifyCandidate = {
|
|
|
70
71
|
sessionName: string;
|
|
71
72
|
message: string;
|
|
72
73
|
sectionLabels: string[];
|
|
74
|
+
mainInputBusy: boolean;
|
|
73
75
|
};
|
|
74
76
|
|
|
75
77
|
export class NotifySilentLiveSessionsUseCase {
|
|
@@ -84,6 +86,7 @@ export class NotifySilentLiveSessionsUseCase {
|
|
|
84
86
|
private readonly ownerCallStatusProvider: OwnerCallStatusProvider,
|
|
85
87
|
private readonly notificationRepository: SilentSessionNotificationRepository,
|
|
86
88
|
private readonly candidateStateRepository: SilentSessionCandidateStateRepository,
|
|
89
|
+
private readonly notifiedStateRepository: SilentSessionNotifiedStateRepository,
|
|
87
90
|
private readonly messageComposer: SilentSessionMessageComposer,
|
|
88
91
|
private readonly sleeper: Sleeper,
|
|
89
92
|
private readonly hubTaskStatusResolver: HubTaskStatusResolver | null = null,
|
|
@@ -185,8 +188,44 @@ export class NotifySilentLiveSessionsUseCase {
|
|
|
185
188
|
`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.`,
|
|
186
189
|
);
|
|
187
190
|
|
|
191
|
+
// Fire-once latch: the set of sessions already reminded during their
|
|
192
|
+
// current silent episode. A session that is still a candidate this cycle
|
|
193
|
+
// and was already reminded is NOT re-injected, so a continuous silent
|
|
194
|
+
// episode produces exactly one reminder instead of one every schedule
|
|
195
|
+
// cycle. The latch is keyed by the globally-unique session name and
|
|
196
|
+
// persisted across the per-cycle fresh monitor process on disk.
|
|
197
|
+
const currentCandidateSessionNames = new Set(
|
|
198
|
+
candidates.map((candidate) => candidate.sessionName),
|
|
199
|
+
);
|
|
200
|
+
const previouslyNotifiedSessionNames =
|
|
201
|
+
await this.notifiedStateRepository.loadRecentNotifiedSessionNames({
|
|
202
|
+
now: params.now,
|
|
203
|
+
recencyWindowSeconds: params.candidateDebounceRecencyWindowSeconds,
|
|
204
|
+
});
|
|
205
|
+
|
|
188
206
|
let sentCount = 0;
|
|
207
|
+
const notifiedThisCycleSessionNames: string[] = [];
|
|
189
208
|
for (const candidate of debouncedCandidates) {
|
|
209
|
+
if (previouslyNotifiedSessionNames.has(candidate.sessionName)) {
|
|
210
|
+
console.log(
|
|
211
|
+
`Skipping ${candidate.sessionName}: the current silent-episode reminder was already delivered; not re-injecting until the condition resolves and re-arises.`,
|
|
212
|
+
);
|
|
213
|
+
continue;
|
|
214
|
+
}
|
|
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
|
+
}
|
|
190
229
|
if (
|
|
191
230
|
!(await this.isHubTaskActive(
|
|
192
231
|
candidate.sessionName,
|
|
@@ -205,6 +244,7 @@ export class NotifySilentLiveSessionsUseCase {
|
|
|
205
244
|
candidate.message,
|
|
206
245
|
);
|
|
207
246
|
sentCount += 1;
|
|
247
|
+
notifiedThisCycleSessionNames.push(candidate.sessionName);
|
|
208
248
|
// One line per send, grep-stable on the `Notified ` prefix: the
|
|
209
249
|
// ISO-8601 UTC timestamp disambiguates concurrent schedule runs and
|
|
210
250
|
// the section list records what the message actually contained.
|
|
@@ -212,6 +252,25 @@ export class NotifySilentLiveSessionsUseCase {
|
|
|
212
252
|
`Notified ${candidate.sessionName} at=${params.now.toISOString()} sections=[${candidate.sectionLabels.join(',')}]`,
|
|
213
253
|
);
|
|
214
254
|
}
|
|
255
|
+
|
|
256
|
+
// Persist the latch for the next cycle: keep every already-latched session
|
|
257
|
+
// that is STILL a candidate (refreshing its timestamp so a continuous
|
|
258
|
+
// episode stays latched) plus the sessions notified this cycle, and prune
|
|
259
|
+
// any latched session that is no longer a candidate so its episode ends and
|
|
260
|
+
// a later re-qualification fires a fresh reminder.
|
|
261
|
+
const retainedNotifiedSessionNames = [
|
|
262
|
+
...previouslyNotifiedSessionNames,
|
|
263
|
+
].filter((sessionName) => currentCandidateSessionNames.has(sessionName));
|
|
264
|
+
const notifiedSessionNamesToPersist = Array.from(
|
|
265
|
+
new Set([
|
|
266
|
+
...retainedNotifiedSessionNames,
|
|
267
|
+
...notifiedThisCycleSessionNames,
|
|
268
|
+
]),
|
|
269
|
+
);
|
|
270
|
+
await this.notifiedStateRepository.saveNotifiedSessionNames({
|
|
271
|
+
sessionNames: notifiedSessionNamesToPersist,
|
|
272
|
+
now: params.now,
|
|
273
|
+
});
|
|
215
274
|
};
|
|
216
275
|
|
|
217
276
|
private isHubTaskActive = async (
|
|
@@ -488,6 +547,7 @@ export class NotifySilentLiveSessionsUseCase {
|
|
|
488
547
|
sessionName: snapshot.sessionName,
|
|
489
548
|
message: sections.join('\n\n'),
|
|
490
549
|
sectionLabels,
|
|
550
|
+
mainInputBusy: suppressedByInProgressToolCall,
|
|
491
551
|
};
|
|
492
552
|
};
|
|
493
553
|
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
export interface SilentSessionNotifiedStateRepository {
|
|
2
|
+
loadRecentNotifiedSessionNames: (params: {
|
|
3
|
+
now: Date;
|
|
4
|
+
recencyWindowSeconds: number;
|
|
5
|
+
}) => Promise<Set<string>>;
|
|
6
|
+
saveNotifiedSessionNames: (params: {
|
|
7
|
+
sessionNames: string[];
|
|
8
|
+
now: Date;
|
|
9
|
+
}) => Promise<void>;
|
|
10
|
+
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"HandleScheduledEventUseCaseHandler.d.ts","sourceRoot":"","sources":["../../../../src/adapter/entry-points/handlers/HandleScheduledEventUseCaseHandler.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"HandleScheduledEventUseCaseHandler.d.ts","sourceRoot":"","sources":["../../../../src/adapter/entry-points/handlers/HandleScheduledEventUseCaseHandler.ts"],"names":[],"mappings":"AAoCA,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,CAumBP;CACH"}
|
|
@@ -10,6 +10,7 @@ export type NotifySilentTmuxSessionsParams = {
|
|
|
10
10
|
subAgentOutputRootDirectory: string | null;
|
|
11
11
|
subAgentProcessMatchPattern: string | null;
|
|
12
12
|
subAgentTranscriptRootDirectory: string | null;
|
|
13
|
+
subAgentRuntimeRootDirectory: string | null;
|
|
13
14
|
mainSilentThresholdSeconds: number;
|
|
14
15
|
unansweredOwnerCallGraceSeconds: number;
|
|
15
16
|
subAgentSilentThresholdSeconds: number;
|
|
@@ -17,6 +18,7 @@ export type NotifySilentTmuxSessionsParams = {
|
|
|
17
18
|
staggerSeconds: number;
|
|
18
19
|
candidateDebounceRecencyWindowSeconds: number;
|
|
19
20
|
candidateDebounceStateFilePath: string | null;
|
|
21
|
+
notifiedStateFilePath: string | null;
|
|
20
22
|
activeHubTaskStatus: string | null;
|
|
21
23
|
hubTaskStatusResolver: HubTaskStatusResolver | null;
|
|
22
24
|
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;
|
|
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;AAMrE,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,qBAAqB,EAAE,MAAM,GAAG,IAAI,CAAC;IACrC,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,CAmFd,CAAC;AAEF,eAAO,MAAM,0CAA0C;;;;;;;;CAS7C,CAAC"}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { SilentSessionNotifiedStateRepository } from '../../domain/usecases/adapter-interfaces/SilentSessionNotifiedStateRepository';
|
|
2
|
+
export declare class FileSystemSilentSessionNotifiedStateRepository implements SilentSessionNotifiedStateRepository {
|
|
3
|
+
private readonly stateFilePath;
|
|
4
|
+
constructor(stateFilePath?: string);
|
|
5
|
+
loadRecentNotifiedSessionNames: (params: {
|
|
6
|
+
now: Date;
|
|
7
|
+
recencyWindowSeconds: number;
|
|
8
|
+
}) => Promise<Set<string>>;
|
|
9
|
+
saveNotifiedSessionNames: (params: {
|
|
10
|
+
sessionNames: string[];
|
|
11
|
+
now: Date;
|
|
12
|
+
}) => Promise<void>;
|
|
13
|
+
private readNotifiedEntries;
|
|
14
|
+
private writeState;
|
|
15
|
+
}
|
|
16
|
+
//# sourceMappingURL=FileSystemSilentSessionNotifiedStateRepository.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"FileSystemSilentSessionNotifiedStateRepository.d.ts","sourceRoot":"","sources":["../../../src/adapter/repositories/FileSystemSilentSessionNotifiedStateRepository.ts"],"names":[],"mappings":"AAGA,OAAO,EAAE,oCAAoC,EAAE,MAAM,+EAA+E,CAAC;AAuBrI,qBAAa,8CAA+C,YAAW,oCAAoC;IAEvG,OAAO,CAAC,QAAQ,CAAC,aAAa;gBAAb,aAAa,GAAE,MAA+B;IAGjE,8BAA8B,GAAU,QAAQ;QAC9C,GAAG,EAAE,IAAI,CAAC;QACV,oBAAoB,EAAE,MAAM,CAAC;KAC9B,KAAG,OAAO,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC,CAWtB;IAEF,wBAAwB,GAAU,QAAQ;QACxC,YAAY,EAAE,MAAM,EAAE,CAAC;QACvB,GAAG,EAAE,IAAI,CAAC;KACX,KAAG,OAAO,CAAC,IAAI,CAAC,CAUf;IAEF,OAAO,CAAC,mBAAmB,CAoCzB;IAEF,OAAO,CAAC,UAAU,CAMhB;CACH"}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { SubAgentLivenessResolver } from '../../domain/usecases/adapter-interfaces/SubAgentLivenessResolver';
|
|
2
|
+
export declare class FileSystemSubAgentLivenessResolver implements SubAgentLivenessResolver {
|
|
3
|
+
private readonly runtimeRootDirectory;
|
|
4
|
+
constructor(runtimeRootDirectory: string | null);
|
|
5
|
+
resolveLiveSubAgentIds: (params: {
|
|
6
|
+
sessionName: string;
|
|
7
|
+
mainTranscriptPath: string | null;
|
|
8
|
+
}) => Promise<Set<string> | null>;
|
|
9
|
+
private resolveRunningSubAgentsFilePath;
|
|
10
|
+
}
|
|
11
|
+
//# sourceMappingURL=FileSystemSubAgentLivenessResolver.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"FileSystemSubAgentLivenessResolver.d.ts","sourceRoot":"","sources":["../../../src/adapter/repositories/FileSystemSubAgentLivenessResolver.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,wBAAwB,EAAE,MAAM,mEAAmE,CAAC;AAI7G,qBAAa,kCAAmC,YAAW,wBAAwB;IACrE,OAAO,CAAC,QAAQ,CAAC,oBAAoB;gBAApB,oBAAoB,EAAE,MAAM,GAAG,IAAI;IAEhE,sBAAsB,GAAU,QAAQ;QACtC,WAAW,EAAE,MAAM,CAAC;QACpB,kBAAkB,EAAE,MAAM,GAAG,IAAI,CAAC;KACnC,KAAG,OAAO,CAAC,GAAG,CAAC,MAAM,CAAC,GAAG,IAAI,CAAC,CAqB7B;IAEF,OAAO,CAAC,+BAA+B,CAoBrC;CACH"}
|
|
@@ -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;
|
|
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,CA6Bd;CACH"}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { SubAgentActivity } from '../../domain/entities/LiveSessionActivitySnapshot';
|
|
2
2
|
import { SessionSubAgentActivityRepository } from '../../domain/usecases/adapter-interfaces/SessionSubAgentActivityRepository';
|
|
3
|
+
import { SubAgentLivenessResolver } from '../../domain/usecases/adapter-interfaces/SubAgentLivenessResolver';
|
|
3
4
|
import { SubAgentProcessLister } from '../../domain/usecases/adapter-interfaces/SubAgentProcessLister';
|
|
4
5
|
import { SubAgentTranscriptDirectoryResolver } from '../../domain/usecases/adapter-interfaces/SubAgentTranscriptDirectoryResolver';
|
|
5
6
|
export declare const normalizeCommandFragment: (command: string) => string;
|
|
@@ -7,9 +8,10 @@ export declare class TranscriptSessionSubAgentActivityRepository implements Sess
|
|
|
7
8
|
private readonly directoryResolver;
|
|
8
9
|
private readonly processLister;
|
|
9
10
|
private readonly now;
|
|
10
|
-
|
|
11
|
+
private readonly livenessResolver;
|
|
12
|
+
constructor(directoryResolver: SubAgentTranscriptDirectoryResolver, processLister: SubAgentProcessLister, now: Date, livenessResolver?: SubAgentLivenessResolver);
|
|
11
13
|
listSubAgentActivitiesBySessionName: (sessionNames: string[], transcriptPathBySessionName: Map<string, string>) => Promise<Map<string, SubAgentActivity[]>>;
|
|
12
|
-
private
|
|
14
|
+
private loadTerminalAgentIds;
|
|
13
15
|
private collectActivities;
|
|
14
16
|
private toActivity;
|
|
15
17
|
private hasLiveMatchingProcess;
|