github-issue-tower-defence-management 1.117.0 → 1.117.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.
Files changed (62) hide show
  1. package/.github/workflows/create-pr.yml +17 -2
  2. package/CHANGELOG.md +14 -0
  3. package/README.md +5 -4
  4. package/bin/adapter/entry-points/handlers/HandleScheduledEventUseCaseHandler.js +7 -2
  5. package/bin/adapter/entry-points/handlers/HandleScheduledEventUseCaseHandler.js.map +1 -1
  6. package/bin/adapter/entry-points/handlers/notifySilentTmuxSessions.js +5 -4
  7. package/bin/adapter/entry-points/handlers/notifySilentTmuxSessions.js.map +1 -1
  8. package/bin/adapter/proxy/ClaudeMessageResponseParser.js +5 -1
  9. package/bin/adapter/proxy/ClaudeMessageResponseParser.js.map +1 -1
  10. package/bin/adapter/repositories/FileSystemSubAgentTranscriptDirectoryResolver.js +9 -2
  11. package/bin/adapter/repositories/FileSystemSubAgentTranscriptDirectoryResolver.js.map +1 -1
  12. package/bin/adapter/repositories/GraphqlProjectRepository.js +3 -3
  13. package/bin/adapter/repositories/GraphqlProjectRepository.js.map +1 -1
  14. package/bin/adapter/repositories/LocalStorageCacheRepository.js +1 -1
  15. package/bin/adapter/repositories/LocalStorageCacheRepository.js.map +1 -1
  16. package/bin/adapter/repositories/TranscriptSessionSubAgentActivityRepository.js +8 -3
  17. package/bin/adapter/repositories/TranscriptSessionSubAgentActivityRepository.js.map +1 -1
  18. package/bin/domain/usecases/HandleScheduledEventUseCase.js +1 -1
  19. package/bin/domain/usecases/HandleScheduledEventUseCase.js.map +1 -1
  20. package/bin/domain/usecases/NotifySilentLiveSessionsUseCase.js +15 -4
  21. package/bin/domain/usecases/NotifySilentLiveSessionsUseCase.js.map +1 -1
  22. package/bin/domain/usecases/UpdateIssueStatusByLabelUseCase.js +1 -1
  23. package/bin/domain/usecases/UpdateIssueStatusByLabelUseCase.js.map +1 -1
  24. package/eslint.config.mjs +67 -0
  25. package/package.json +8 -7
  26. package/src/adapter/entry-points/handlers/HandleScheduledEventUseCaseHandler.ts +6 -0
  27. package/src/adapter/entry-points/handlers/dashboardRowWriter.test.ts +1 -1
  28. package/src/adapter/entry-points/handlers/machineStatusWriter.test.ts +1 -1
  29. package/src/adapter/entry-points/handlers/notifySilentTmuxSessions.ts +7 -0
  30. package/src/adapter/entry-points/handlers/tokenStatusWriter.test.ts +1 -1
  31. package/src/adapter/proxy/ClaudeMessageResponseParser.ts +3 -2
  32. package/src/adapter/repositories/FileSystemSubAgentTranscriptDirectoryResolver.test.ts +40 -4
  33. package/src/adapter/repositories/FileSystemSubAgentTranscriptDirectoryResolver.ts +12 -6
  34. package/src/adapter/repositories/GraphqlProjectRepository.ts +3 -2
  35. package/src/adapter/repositories/LocalStorageCacheRepository.ts +1 -1
  36. package/src/adapter/repositories/TranscriptSessionSubAgentActivityRepository.test.ts +224 -28
  37. package/src/adapter/repositories/TranscriptSessionSubAgentActivityRepository.ts +11 -2
  38. package/src/domain/usecases/AssignNoAssigneeIssueToManagerUseCase.test.ts +2 -1
  39. package/src/domain/usecases/HandleScheduledEventUseCase.ts +1 -1
  40. package/src/domain/usecases/NotifySilentLiveSessionsUseCase.test.ts +84 -4
  41. package/src/domain/usecases/NotifySilentLiveSessionsUseCase.ts +17 -3
  42. package/src/domain/usecases/UpdateIssueStatusByLabelUseCase.ts +1 -0
  43. package/src/domain/usecases/adapter-interfaces/SessionSubAgentActivityRepository.ts +1 -0
  44. package/src/domain/usecases/adapter-interfaces/SubAgentTranscriptDirectoryResolver.ts +4 -1
  45. package/tsconfig.json +1 -0
  46. package/types/adapter/entry-points/handlers/HandleScheduledEventUseCaseHandler.d.ts.map +1 -1
  47. package/types/adapter/entry-points/handlers/notifySilentTmuxSessions.d.ts +2 -0
  48. package/types/adapter/entry-points/handlers/notifySilentTmuxSessions.d.ts.map +1 -1
  49. package/types/adapter/proxy/ClaudeMessageResponseParser.d.ts.map +1 -1
  50. package/types/adapter/repositories/FileSystemSubAgentTranscriptDirectoryResolver.d.ts +1 -1
  51. package/types/adapter/repositories/FileSystemSubAgentTranscriptDirectoryResolver.d.ts.map +1 -1
  52. package/types/adapter/repositories/GraphqlProjectRepository.d.ts.map +1 -1
  53. package/types/adapter/repositories/TranscriptSessionSubAgentActivityRepository.d.ts +3 -2
  54. package/types/adapter/repositories/TranscriptSessionSubAgentActivityRepository.d.ts.map +1 -1
  55. package/types/domain/usecases/NotifySilentLiveSessionsUseCase.d.ts +1 -0
  56. package/types/domain/usecases/NotifySilentLiveSessionsUseCase.d.ts.map +1 -1
  57. package/types/domain/usecases/UpdateIssueStatusByLabelUseCase.d.ts.map +1 -1
  58. package/types/domain/usecases/adapter-interfaces/SessionSubAgentActivityRepository.d.ts +1 -1
  59. package/types/domain/usecases/adapter-interfaces/SessionSubAgentActivityRepository.d.ts.map +1 -1
  60. package/types/domain/usecases/adapter-interfaces/SubAgentTranscriptDirectoryResolver.d.ts +1 -1
  61. package/types/domain/usecases/adapter-interfaces/SubAgentTranscriptDirectoryResolver.d.ts.map +1 -1
  62. package/.eslintrc.cjs +0 -59
@@ -2,12 +2,14 @@ import * as fs from 'fs';
2
2
  import * as os from 'os';
3
3
  import * as path from 'path';
4
4
  import { SubAgentTranscriptDirectoryResolver } from '../../domain/usecases/adapter-interfaces/SubAgentTranscriptDirectoryResolver';
5
+ import { FileSystemSubAgentTranscriptDirectoryResolver } from './FileSystemSubAgentTranscriptDirectoryResolver';
5
6
  import { TranscriptSessionSubAgentActivityRepository } from './TranscriptSessionSubAgentActivityRepository';
6
7
 
7
8
  describe('TranscriptSessionSubAgentActivityRepository', () => {
8
9
  let rootDirectory: string;
9
10
  const now = new Date('2026-06-27T12:00:00.000Z');
10
11
  const nowEpochSeconds = Math.floor(now.getTime() / 1000);
12
+ const noOpCeilingSeconds = 365 * 24 * 60 * 60;
11
13
 
12
14
  beforeEach(() => {
13
15
  rootDirectory = fs.mkdtempSync(path.join(os.tmpdir(), 'subagent-tx-'));
@@ -17,10 +19,32 @@ describe('TranscriptSessionSubAgentActivityRepository', () => {
17
19
  fs.rmSync(rootDirectory, { force: true, recursive: true });
18
20
  });
19
21
 
22
+ const cwdSlugFor = (sessionName: string): string =>
23
+ `-home-user-worktrees-${sessionName.replace(/[^a-zA-Z0-9]/g, '-')}`;
24
+
25
+ const sessionUuidFor = (sessionName: string): string =>
26
+ `uuid-${sessionName.replace(/[^a-zA-Z0-9]/g, '-')}`;
27
+
28
+ const mainTranscriptPathFor = (sessionName: string): string =>
29
+ path.join(
30
+ rootDirectory,
31
+ cwdSlugFor(sessionName),
32
+ `${sessionUuidFor(sessionName)}.jsonl`,
33
+ );
34
+
35
+ const transcriptMapFor = (sessionNames: string[]): Map<string, string> =>
36
+ new Map(
37
+ sessionNames.map((sessionName) => [
38
+ sessionName,
39
+ mainTranscriptPathFor(sessionName),
40
+ ]),
41
+ );
42
+
20
43
  const subAgentsDirFor = (sessionName: string): string => {
21
44
  const dir = path.join(
22
45
  rootDirectory,
23
- sessionName.replace(/\//g, '_'),
46
+ cwdSlugFor(sessionName),
47
+ sessionUuidFor(sessionName),
24
48
  'subagents',
25
49
  );
26
50
  fs.mkdirSync(dir, { recursive: true });
@@ -44,10 +68,8 @@ describe('TranscriptSessionSubAgentActivityRepository', () => {
44
68
  return filePath;
45
69
  };
46
70
 
47
- const createResolver = (): SubAgentTranscriptDirectoryResolver => ({
48
- resolveSubAgentsDirectory: (sessionName) =>
49
- path.join(rootDirectory, sessionName.replace(/\//g, '_'), 'subagents'),
50
- });
71
+ const createResolver = (): SubAgentTranscriptDirectoryResolver =>
72
+ new FileSystemSubAgentTranscriptDirectoryResolver(rootDirectory);
51
73
 
52
74
  const runningEntries = (startTimestamp: string): object[] => [
53
75
  { type: 'user', timestamp: startTimestamp, message: { role: 'user' } },
@@ -88,11 +110,13 @@ describe('TranscriptSessionSubAgentActivityRepository', () => {
88
110
  const repository = new TranscriptSessionSubAgentActivityRepository(
89
111
  createResolver(),
90
112
  now,
113
+ noOpCeilingSeconds,
91
114
  );
92
115
 
93
- const result = await repository.listSubAgentActivitiesBySessionName([
94
- sessionName,
95
- ]);
116
+ const result = await repository.listSubAgentActivitiesBySessionName(
117
+ [sessionName],
118
+ transcriptMapFor([sessionName]),
119
+ );
96
120
 
97
121
  expect(result.get(sessionName)).toEqual([
98
122
  { label: 'agent-aaa111', silentSeconds: 120, runningSeconds: 900 },
@@ -110,11 +134,13 @@ describe('TranscriptSessionSubAgentActivityRepository', () => {
110
134
  const repository = new TranscriptSessionSubAgentActivityRepository(
111
135
  createResolver(),
112
136
  now,
137
+ noOpCeilingSeconds,
113
138
  );
114
139
 
115
- const result = await repository.listSubAgentActivitiesBySessionName([
116
- sessionName,
117
- ]);
140
+ const result = await repository.listSubAgentActivitiesBySessionName(
141
+ [sessionName],
142
+ transcriptMapFor([sessionName]),
143
+ );
118
144
 
119
145
  expect(result.size).toBe(0);
120
146
  });
@@ -130,11 +156,13 @@ describe('TranscriptSessionSubAgentActivityRepository', () => {
130
156
  const repository = new TranscriptSessionSubAgentActivityRepository(
131
157
  createResolver(),
132
158
  now,
159
+ noOpCeilingSeconds,
133
160
  );
134
161
 
135
- const result = await repository.listSubAgentActivitiesBySessionName([
136
- sessionName,
137
- ]);
162
+ const result = await repository.listSubAgentActivitiesBySessionName(
163
+ [sessionName],
164
+ transcriptMapFor([sessionName]),
165
+ );
138
166
 
139
167
  expect(result.size).toBe(0);
140
168
  });
@@ -158,11 +186,13 @@ describe('TranscriptSessionSubAgentActivityRepository', () => {
158
186
  const repository = new TranscriptSessionSubAgentActivityRepository(
159
187
  createResolver(),
160
188
  now,
189
+ noOpCeilingSeconds,
161
190
  );
162
191
 
163
- const result = await repository.listSubAgentActivitiesBySessionName([
164
- sessionName,
165
- ]);
192
+ const result = await repository.listSubAgentActivitiesBySessionName(
193
+ [sessionName],
194
+ transcriptMapFor([sessionName]),
195
+ );
166
196
 
167
197
  expect(result.get(sessionName)).toEqual([
168
198
  { label: 'agent-link1', silentSeconds: 60, runningSeconds: 600 },
@@ -174,11 +204,13 @@ describe('TranscriptSessionSubAgentActivityRepository', () => {
174
204
  const repository = new TranscriptSessionSubAgentActivityRepository(
175
205
  { resolveSubAgentsDirectory: () => null },
176
206
  now,
207
+ noOpCeilingSeconds,
177
208
  );
178
209
 
179
- const result = await repository.listSubAgentActivitiesBySessionName([
180
- 'https_//github_com/owner/repo/issues/9',
181
- ]);
210
+ const result = await repository.listSubAgentActivitiesBySessionName(
211
+ ['https_//github_com/owner/repo/issues/9'],
212
+ transcriptMapFor(['https_//github_com/owner/repo/issues/9']),
213
+ );
182
214
 
183
215
  expect(result.size).toBe(0);
184
216
  });
@@ -187,11 +219,13 @@ describe('TranscriptSessionSubAgentActivityRepository', () => {
187
219
  const repository = new TranscriptSessionSubAgentActivityRepository(
188
220
  createResolver(),
189
221
  now,
222
+ noOpCeilingSeconds,
190
223
  );
191
224
 
192
- const result = await repository.listSubAgentActivitiesBySessionName([
193
- 'https_//github_com/owner/repo/issues/404',
194
- ]);
225
+ const result = await repository.listSubAgentActivitiesBySessionName(
226
+ ['https_//github_com/owner/repo/issues/404'],
227
+ transcriptMapFor(['https_//github_com/owner/repo/issues/404']),
228
+ );
195
229
 
196
230
  expect(result.size).toBe(0);
197
231
  });
@@ -213,11 +247,13 @@ describe('TranscriptSessionSubAgentActivityRepository', () => {
213
247
  const repository = new TranscriptSessionSubAgentActivityRepository(
214
248
  createResolver(),
215
249
  now,
250
+ noOpCeilingSeconds,
216
251
  );
217
252
 
218
- const result = await repository.listSubAgentActivitiesBySessionName([
219
- sessionName,
220
- ]);
253
+ const result = await repository.listSubAgentActivitiesBySessionName(
254
+ [sessionName],
255
+ transcriptMapFor([sessionName]),
256
+ );
221
257
 
222
258
  expect(result.get(sessionName)).toHaveLength(2);
223
259
  });
@@ -233,14 +269,174 @@ describe('TranscriptSessionSubAgentActivityRepository', () => {
233
269
  const repository = new TranscriptSessionSubAgentActivityRepository(
234
270
  createResolver(),
235
271
  now,
272
+ noOpCeilingSeconds,
273
+ );
274
+
275
+ const result = await repository.listSubAgentActivitiesBySessionName(
276
+ [sessionName],
277
+ transcriptMapFor([sessionName]),
278
+ );
279
+
280
+ expect(result.get(sessionName)).toEqual([
281
+ { label: 'agent-future', silentSeconds: 0, runningSeconds: 0 },
282
+ ]);
283
+ });
284
+
285
+ it('resolves sub-agent transcripts laid out exactly as Claude Code stores them on disk', async () => {
286
+ const sessionName = 'https_//github_com/HiromiShikata/repo/issues/2355';
287
+ const projectsRoot = path.join(rootDirectory, 'projects');
288
+ const cwdSlug =
289
+ '-home-user-0-workspaces-workspace1-oss-example-repo-worktrees-i2355';
290
+ const sessionUuid = 'ba0637e1-9ff1-41a8-b13c-f45e6a71efc5';
291
+ const mainTranscriptPath = path.join(
292
+ projectsRoot,
293
+ cwdSlug,
294
+ `${sessionUuid}.jsonl`,
295
+ );
296
+ const subagentsDir = path.join(
297
+ projectsRoot,
298
+ cwdSlug,
299
+ sessionUuid,
300
+ 'subagents',
301
+ );
302
+ fs.mkdirSync(subagentsDir, { recursive: true });
303
+ fs.mkdirSync(path.dirname(mainTranscriptPath), { recursive: true });
304
+ fs.writeFileSync(mainTranscriptPath, '', 'utf8');
305
+ const agentFile = path.join(subagentsDir, 'agent-afcbe335fdbec0a28.jsonl');
306
+ fs.writeFileSync(
307
+ agentFile,
308
+ runningEntries('2026-06-27T11:45:00.000Z')
309
+ .map((entry) => JSON.stringify(entry))
310
+ .join('\n'),
311
+ 'utf8',
312
+ );
313
+ fs.utimesSync(agentFile, nowEpochSeconds - 120, nowEpochSeconds - 120);
314
+
315
+ const repository = new TranscriptSessionSubAgentActivityRepository(
316
+ new FileSystemSubAgentTranscriptDirectoryResolver(projectsRoot),
317
+ now,
318
+ noOpCeilingSeconds,
319
+ );
320
+
321
+ const result = await repository.listSubAgentActivitiesBySessionName(
322
+ [sessionName],
323
+ new Map([[sessionName, mainTranscriptPath]]),
324
+ );
325
+
326
+ expect(result.get(sessionName)).toEqual([
327
+ {
328
+ label: 'agent-afcbe335fdbec0a28',
329
+ silentSeconds: 120,
330
+ runningSeconds: 900,
331
+ },
332
+ ]);
333
+ });
334
+
335
+ it('excludes a long-dead sub-agent whose transcript has been silent beyond the ceiling', async () => {
336
+ const sessionName = 'https_//github_com/owner/repo/issues/9';
337
+ const twoDaysSeconds = 2 * 24 * 60 * 60;
338
+ writeAgentTranscript(
339
+ sessionName,
340
+ 'deadone',
341
+ runningEntries('2026-06-25T11:45:00.000Z'),
342
+ nowEpochSeconds - twoDaysSeconds,
343
+ );
344
+ const repository = new TranscriptSessionSubAgentActivityRepository(
345
+ createResolver(),
346
+ now,
347
+ 60 * 60,
348
+ );
349
+
350
+ const result = await repository.listSubAgentActivitiesBySessionName(
351
+ [sessionName],
352
+ transcriptMapFor([sessionName]),
353
+ );
354
+
355
+ expect(result.size).toBe(0);
356
+ });
357
+
358
+ it('keeps a recently-silent sub-agent whose transcript is within the ceiling', async () => {
359
+ const sessionName = 'https_//github_com/owner/repo/issues/9';
360
+ writeAgentTranscript(
361
+ sessionName,
362
+ 'aliveone',
363
+ runningEntries('2026-06-27T11:45:00.000Z'),
364
+ nowEpochSeconds - 360,
365
+ );
366
+ const repository = new TranscriptSessionSubAgentActivityRepository(
367
+ createResolver(),
368
+ now,
369
+ 60 * 60,
370
+ );
371
+
372
+ const result = await repository.listSubAgentActivitiesBySessionName(
373
+ [sessionName],
374
+ transcriptMapFor([sessionName]),
236
375
  );
237
376
 
238
- const result = await repository.listSubAgentActivitiesBySessionName([
377
+ expect(result.get(sessionName)).toEqual([
378
+ { label: 'agent-aliveone', silentSeconds: 360, runningSeconds: 900 },
379
+ ]);
380
+ });
381
+
382
+ it('keeps a sub-agent silent exactly at the ceiling and excludes one past it', async () => {
383
+ const sessionName = 'https_//github_com/owner/repo/issues/9';
384
+ writeAgentTranscript(
239
385
  sessionName,
386
+ 'atceiling',
387
+ runningEntries('2026-06-27T11:00:00.000Z'),
388
+ nowEpochSeconds - 3600,
389
+ );
390
+ writeAgentTranscript(
391
+ sessionName,
392
+ 'pastceiling',
393
+ runningEntries('2026-06-27T11:00:00.000Z'),
394
+ nowEpochSeconds - 3601,
395
+ );
396
+ const repository = new TranscriptSessionSubAgentActivityRepository(
397
+ createResolver(),
398
+ now,
399
+ 3600,
400
+ );
401
+
402
+ const result = await repository.listSubAgentActivitiesBySessionName(
403
+ [sessionName],
404
+ transcriptMapFor([sessionName]),
405
+ );
406
+
407
+ expect(result.get(sessionName)).toEqual([
408
+ { label: 'agent-atceiling', silentSeconds: 3600, runningSeconds: 3600 },
240
409
  ]);
410
+ });
411
+
412
+ it('flags only the recently-silent sub-agent when a session holds both a dead and an alive transcript', async () => {
413
+ const sessionName = 'https_//github_com/owner/repo/issues/9';
414
+ const twoDaysSeconds = 2 * 24 * 60 * 60;
415
+ writeAgentTranscript(
416
+ sessionName,
417
+ 'deadtwo',
418
+ runningEntries('2026-06-25T11:00:00.000Z'),
419
+ nowEpochSeconds - twoDaysSeconds,
420
+ );
421
+ writeAgentTranscript(
422
+ sessionName,
423
+ 'alivetwo',
424
+ runningEntries('2026-06-27T11:45:00.000Z'),
425
+ nowEpochSeconds - 400,
426
+ );
427
+ const repository = new TranscriptSessionSubAgentActivityRepository(
428
+ createResolver(),
429
+ now,
430
+ 60 * 60,
431
+ );
432
+
433
+ const result = await repository.listSubAgentActivitiesBySessionName(
434
+ [sessionName],
435
+ transcriptMapFor([sessionName]),
436
+ );
241
437
 
242
438
  expect(result.get(sessionName)).toEqual([
243
- { label: 'agent-future', silentSeconds: 0, runningSeconds: 0 },
439
+ { label: 'agent-alivetwo', silentSeconds: 400, runningSeconds: 900 },
244
440
  ]);
245
441
  });
246
442
  });
@@ -66,16 +66,22 @@ export class TranscriptSessionSubAgentActivityRepository implements SessionSubAg
66
66
  constructor(
67
67
  private readonly directoryResolver: SubAgentTranscriptDirectoryResolver,
68
68
  private readonly now: Date,
69
+ private readonly silentCeilingSeconds: number,
69
70
  ) {}
70
71
 
71
72
  listSubAgentActivitiesBySessionName = async (
72
73
  sessionNames: string[],
74
+ transcriptPathBySessionName: Map<string, string>,
73
75
  ): Promise<Map<string, SubAgentActivity[]>> => {
74
76
  const result = new Map<string, SubAgentActivity[]>();
75
77
  const nowEpochSeconds = Math.floor(this.now.getTime() / 1000);
76
78
  for (const sessionName of sessionNames) {
77
- const directory =
78
- this.directoryResolver.resolveSubAgentsDirectory(sessionName);
79
+ const mainTranscriptPath =
80
+ transcriptPathBySessionName.get(sessionName) ?? null;
81
+ const directory = this.directoryResolver.resolveSubAgentsDirectory(
82
+ sessionName,
83
+ mainTranscriptPath,
84
+ );
79
85
  if (directory === null) {
80
86
  continue;
81
87
  }
@@ -135,6 +141,9 @@ export class TranscriptSessionSubAgentActivityRepository implements SessionSubAg
135
141
  const silentSeconds = clampToZero(
136
142
  nowEpochSeconds - Math.floor(stats.mtimeMs / 1000),
137
143
  );
144
+ if (silentSeconds > this.silentCeilingSeconds) {
145
+ return null;
146
+ }
138
147
  const runningSeconds =
139
148
  transcript.firstEntryEpochSeconds === null
140
149
  ? 0
@@ -186,7 +186,8 @@ describe('AssignNoAssigneeIssueToManagerUseCase', () => {
186
186
  url: 'https://github.com/testOrg/testRepo/issues/44',
187
187
  };
188
188
  mockIssueRepository.updateAssigneeList.mockImplementationOnce(() => {
189
- throw 'string-failure';
189
+ const nonError: unknown = 'string-failure';
190
+ throw nonError;
190
191
  });
191
192
 
192
193
  await expect(
@@ -228,7 +228,7 @@ export class HandleScheduledEventUseCase {
228
228
  input.manager,
229
229
  );
230
230
 
231
- let rotationOrder: RotationOrderEntry[] | null = null;
231
+ let rotationOrder: RotationOrderEntry[] | null;
232
232
  try {
233
233
  const useCaseResult = await this.runEachUseCases(
234
234
  input,
@@ -277,7 +277,7 @@ describe('NotifySilentLiveSessionsUseCase', () => {
277
277
  );
278
278
  expect(
279
279
  mockSubAgentActivityRepository.listSubAgentActivitiesBySessionName,
280
- ).toHaveBeenCalledWith([]);
280
+ ).toHaveBeenCalledWith([], new Map());
281
281
  expect(
282
282
  mockMessageComposer.composeMainStalledSection,
283
283
  ).not.toHaveBeenCalled();
@@ -335,7 +335,7 @@ describe('NotifySilentLiveSessionsUseCase', () => {
335
335
  ).toHaveBeenCalledWith(expectedMap);
336
336
  expect(
337
337
  mockSubAgentActivityRepository.listSubAgentActivitiesBySessionName,
338
- ).toHaveBeenCalledWith([GITHUB_SESSION]);
338
+ ).toHaveBeenCalledWith([GITHUB_SESSION], expectedMap);
339
339
  });
340
340
 
341
341
  it('suppresses the stalled section and sends nothing when an owner call is pending past the threshold', async () => {
@@ -693,19 +693,99 @@ describe('NotifySilentLiveSessionsUseCase', () => {
693
693
  warnSpy.mockRestore();
694
694
  });
695
695
 
696
- it('parses a github.com issue URL session name and rejects other names', () => {
696
+ it('gates on the real tmux session-name form by resolving its canonical issue URL', async () => {
697
+ const REAL_TMUX_SESSION =
698
+ 'https_//github_com/HiromiShikata/repo/issues/2355';
699
+ const CANONICAL_ISSUE_URL =
700
+ 'https://github.com/HiromiShikata/repo/issues/2355';
701
+ setupSilentMainSession(REAL_TMUX_SESSION);
702
+ mockHubTaskStatusResolver.getIssueByUrl.mockResolvedValue(
703
+ issueFor({
704
+ url: CANONICAL_ISSUE_URL,
705
+ state: 'OPEN',
706
+ status: ACTIVE_STATUS,
707
+ }),
708
+ );
709
+
710
+ await useCase.run(runParams({ activeHubTaskStatus: ACTIVE_STATUS }));
711
+
712
+ expect(mockHubTaskStatusResolver.getIssueByUrl).toHaveBeenCalledWith(
713
+ CANONICAL_ISSUE_URL,
714
+ );
715
+ expect(
716
+ mockNotificationRepository.sendSelfCheckNotification,
717
+ ).toHaveBeenCalledWith(REAL_TMUX_SESSION, MAIN_STALLED_SECTION);
718
+ });
719
+
720
+ it('skips the real tmux session-name form when its canonical hub task is no longer active', async () => {
721
+ const REAL_TMUX_SESSION =
722
+ 'https_//github_com/HiromiShikata/repo/issues/2355';
723
+ const CANONICAL_ISSUE_URL =
724
+ 'https://github.com/HiromiShikata/repo/issues/2355';
725
+ setupSilentMainSession(REAL_TMUX_SESSION);
726
+ mockHubTaskStatusResolver.getIssueByUrl.mockResolvedValue(
727
+ issueFor({
728
+ url: CANONICAL_ISSUE_URL,
729
+ state: 'OPEN',
730
+ status: 'Todo',
731
+ }),
732
+ );
733
+
734
+ await useCase.run(runParams({ activeHubTaskStatus: ACTIVE_STATUS }));
735
+
736
+ expect(mockHubTaskStatusResolver.getIssueByUrl).toHaveBeenCalledWith(
737
+ CANONICAL_ISSUE_URL,
738
+ );
739
+ expect(
740
+ mockNotificationRepository.sendSelfCheckNotification,
741
+ ).not.toHaveBeenCalled();
742
+ });
743
+
744
+ it('parses a clean github.com issue URL session name and rejects non-github names', () => {
697
745
  expect(parseHubTaskIssueUrlFromSessionName(HUB_TASK_SESSION)).toBe(
698
746
  HUB_TASK_SESSION,
699
747
  );
700
748
  expect(parseHubTaskIssueUrlFromSessionName('workbench')).toBeNull();
749
+ expect(
750
+ parseHubTaskIssueUrlFromSessionName(
751
+ 'https://example.com/HiromiShikata/repo/issues/42',
752
+ ),
753
+ ).toBeNull();
754
+ });
755
+
756
+ it('parses the real tmux session-name form produced by toTmuxSessionName for an issue', () => {
757
+ expect(
758
+ parseHubTaskIssueUrlFromSessionName(
759
+ 'https_//github_com/HiromiShikata/repo/issues/2355',
760
+ ),
761
+ ).toBe('https://github.com/HiromiShikata/repo/issues/2355');
762
+ });
763
+
764
+ it('parses the real tmux session-name form for a pull request', () => {
765
+ expect(
766
+ parseHubTaskIssueUrlFromSessionName(
767
+ 'https_//github_com/HiromiShikata/repo/pull/2474',
768
+ ),
769
+ ).toBe('https://github.com/HiromiShikata/repo/pull/2474');
770
+ });
771
+
772
+ it('accepts a clean github.com pull URL session name', () => {
701
773
  expect(
702
774
  parseHubTaskIssueUrlFromSessionName(
703
775
  'https://github.com/HiromiShikata/repo/pull/42',
704
776
  ),
777
+ ).toBe('https://github.com/HiromiShikata/repo/pull/42');
778
+ });
779
+
780
+ it('rejects an encoded non-github host or a non-issue/pull path', () => {
781
+ expect(
782
+ parseHubTaskIssueUrlFromSessionName(
783
+ 'https_//example_com/HiromiShikata/repo/issues/42',
784
+ ),
705
785
  ).toBeNull();
706
786
  expect(
707
787
  parseHubTaskIssueUrlFromSessionName(
708
- 'https://example.com/HiromiShikata/repo/issues/42',
788
+ 'https_//github_com/HiromiShikata/repo/discussions/42',
709
789
  ),
710
790
  ).toBeNull();
711
791
  });
@@ -14,15 +14,28 @@ import { ResolveInteractiveLiveSessionsUseCase } from './ResolveInteractiveLiveS
14
14
  export const DEFAULT_MAIN_SILENT_THRESHOLD_SECONDS = 10 * 60;
15
15
  export const DEFAULT_SUBAGENT_SILENT_THRESHOLD_SECONDS = 5 * 60;
16
16
  export const DEFAULT_SUBAGENT_RUNNING_THRESHOLD_SECONDS = 15 * 60;
17
+ export const DEFAULT_SUBAGENT_SILENT_CEILING_SECONDS = 60 * 60;
17
18
  export const DEFAULT_NOTIFICATION_STAGGER_SECONDS = 25;
18
19
 
19
- const GITHUB_ISSUE_URL_PATTERN =
20
- /^https:\/\/github\.com\/[^/]+\/[^/]+\/issues\/\d+$/;
20
+ const GITHUB_ISSUE_OR_PULL_URL_PATTERN =
21
+ /^https:\/\/github\.com\/([^/]+)\/([^/]+)\/(?:issues|pull)\/(\d+)$/;
22
+
23
+ const GITHUB_TMUX_SESSION_NAME_PATTERN =
24
+ /^https_\/\/github_com\/([^/]+)\/([^/]+)\/(?:issues|pull)\/(\d+)$/;
21
25
 
22
26
  export const parseHubTaskIssueUrlFromSessionName = (
23
27
  sessionName: string,
24
28
  ): string | null => {
25
- return GITHUB_ISSUE_URL_PATTERN.test(sessionName) ? sessionName : null;
29
+ if (GITHUB_ISSUE_OR_PULL_URL_PATTERN.test(sessionName)) {
30
+ return sessionName;
31
+ }
32
+ const tmuxMatch = GITHUB_TMUX_SESSION_NAME_PATTERN.exec(sessionName);
33
+ if (tmuxMatch === null) {
34
+ return null;
35
+ }
36
+ const [, owner, repo, number] = tmuxMatch;
37
+ const target = sessionName.includes('/pull/') ? 'pull' : 'issues';
38
+ return `https://github.com/${owner}/${repo}/${target}/${number}`;
26
39
  };
27
40
 
28
41
  const GITHUB_ISSUE_OR_PULL_REQUEST_SESSION_NAME_PATTERN =
@@ -191,6 +204,7 @@ export class NotifySilentLiveSessionsUseCase {
191
204
  const subAgentsBySessionName =
192
205
  await this.subAgentActivityRepository.listSubAgentActivitiesBySessionName(
193
206
  sessionNames,
207
+ transcriptPathBySessionName,
194
208
  );
195
209
 
196
210
  const sessionNamesWithUnansweredOwnerCall =
@@ -56,6 +56,7 @@ export class UpdateIssueStatusByLabelUseCase {
56
56
  }
57
57
  throw new Error(
58
58
  `Failed to remove label ${statusLabel} from issue ${issue.url}: ${e.message}`,
59
+ { cause: e },
59
60
  );
60
61
  }
61
62
  }
@@ -3,5 +3,6 @@ import { SubAgentActivity } from '../../entities/LiveSessionActivitySnapshot';
3
3
  export interface SessionSubAgentActivityRepository {
4
4
  listSubAgentActivitiesBySessionName: (
5
5
  sessionNames: string[],
6
+ transcriptPathBySessionName: Map<string, string>,
6
7
  ) => Promise<Map<string, SubAgentActivity[]>>;
7
8
  }
@@ -1,3 +1,6 @@
1
1
  export interface SubAgentTranscriptDirectoryResolver {
2
- resolveSubAgentsDirectory: (sessionName: string) => string | null;
2
+ resolveSubAgentsDirectory: (
3
+ sessionName: string,
4
+ mainTranscriptPath: string | null,
5
+ ) => string | null;
3
6
  }
package/tsconfig.json CHANGED
@@ -1,6 +1,7 @@
1
1
  {
2
2
  "compilerOptions": {
3
3
  "target": "es2020",
4
+ "lib": ["es2022", "dom", "dom.iterable"],
4
5
  "module": "CommonJS",
5
6
  "moduleResolution": "Node",
6
7
  "esModuleInterop": true,
@@ -1 +1 @@
1
- {"version":3,"file":"HandleScheduledEventUseCaseHandler.d.ts","sourceRoot":"","sources":["../../../../src/adapter/entry-points/handlers/HandleScheduledEventUseCaseHandler.ts"],"names":[],"mappings":"AAkCA,OAAO,EAAE,KAAK,EAAE,MAAM,gCAAgC,CAAC;AACvD,OAAO,EAAE,OAAO,EAAE,MAAM,kCAAkC,CAAC;AAiD3D,qBAAa,kCAAkC;IAC7C,MAAM,GACJ,gBAAgB,MAAM,EACtB,UAAU,OAAO,KAChB,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,CAmiBP;CACH"}
1
+ {"version":3,"file":"HandleScheduledEventUseCaseHandler.d.ts","sourceRoot":"","sources":["../../../../src/adapter/entry-points/handlers/HandleScheduledEventUseCaseHandler.ts"],"names":[],"mappings":"AAkCA,OAAO,EAAE,KAAK,EAAE,MAAM,gCAAgC,CAAC;AACvD,OAAO,EAAE,OAAO,EAAE,MAAM,kCAAkC,CAAC;AAiD3D,qBAAa,kCAAkC;IAC7C,MAAM,GACJ,gBAAgB,MAAM,EACtB,UAAU,OAAO,KAChB,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,CAyiBP;CACH"}
@@ -13,6 +13,7 @@ export type NotifySilentTmuxSessionsParams = {
13
13
  mainSilentThresholdSeconds: number;
14
14
  subAgentSilentThresholdSeconds: number;
15
15
  subAgentRunningThresholdSeconds: number;
16
+ subAgentSilentCeilingSeconds: number;
16
17
  staggerSeconds: number;
17
18
  activeHubTaskStatus: string | null;
18
19
  hubTaskStatusResolver: HubTaskStatusResolver | null;
@@ -24,6 +25,7 @@ export declare const DEFAULT_NOTIFY_SILENT_TMUX_SESSIONS_PARAMS: {
24
25
  readonly mainSilentThresholdSeconds: number;
25
26
  readonly subAgentSilentThresholdSeconds: number;
26
27
  readonly subAgentRunningThresholdSeconds: number;
28
+ readonly subAgentSilentCeilingSeconds: number;
27
29
  readonly staggerSeconds: 25;
28
30
  };
29
31
  //# sourceMappingURL=notifySilentTmuxSessions.d.ts.map
@@ -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,EAKtB,MAAM,0DAA0D,CAAC;AAclE,OAAO,EAEL,6BAA6B,EAC9B,MAAM,6DAA6D,CAAC;AAGrE,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,0BAA0B,EAAE,MAAM,CAAC;IACnC,8BAA8B,EAAE,MAAM,CAAC;IACvC,+BAA+B,EAAE,MAAM,CAAC;IACxC,cAAc,EAAE,MAAM,CAAC;IACvB,mBAAmB,EAAE,MAAM,GAAG,IAAI,CAAC;IACnC,qBAAqB,EAAE,qBAAqB,GAAG,IAAI,CAAC;IACpD,gBAAgB,EAAE,6BAA6B,CAAC;IAChD,GAAG,EAAE,IAAI,CAAC;CACX,CAAC;AAoCF,eAAO,MAAM,wBAAwB,GACnC,QAAQ,8BAA8B,KACrC,OAAO,CAAC,IAAI,CAwDd,CAAC;AAEF,eAAO,MAAM,0CAA0C;;;;;CAK7C,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,EAMtB,MAAM,0DAA0D,CAAC;AAclE,OAAO,EAEL,6BAA6B,EAC9B,MAAM,6DAA6D,CAAC;AAGrE,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,0BAA0B,EAAE,MAAM,CAAC;IACnC,8BAA8B,EAAE,MAAM,CAAC;IACvC,+BAA+B,EAAE,MAAM,CAAC;IACxC,4BAA4B,EAAE,MAAM,CAAC;IACrC,cAAc,EAAE,MAAM,CAAC;IACvB,mBAAmB,EAAE,MAAM,GAAG,IAAI,CAAC;IACnC,qBAAqB,EAAE,qBAAqB,GAAG,IAAI,CAAC;IACpD,gBAAgB,EAAE,6BAA6B,CAAC;IAChD,GAAG,EAAE,IAAI,CAAC;CACX,CAAC;AAsCF,eAAO,MAAM,wBAAwB,GACnC,QAAQ,8BAA8B,KACrC,OAAO,CAAC,IAAI,CA0Dd,CAAC;AAEF,eAAO,MAAM,0CAA0C;;;;;;CAM7C,CAAC"}
@@ -1 +1 @@
1
- {"version":3,"file":"ClaudeMessageResponseParser.d.ts","sourceRoot":"","sources":["../../../src/adapter/proxy/ClaudeMessageResponseParser.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,IAAI,MAAM,MAAM,CAAC;AAC7B,OAAO,EAAE,qBAAqB,EAAE,MAAM,6CAA6C,CAAC;AAiFpF,eAAO,MAAM,0BAA0B,GACrC,WAAW,MAAM,EACjB,YAAY,MAAM,EAClB,SAAS,IAAI,CAAC,mBAAmB,EACjC,MAAM,MAAM,KACX,qBA4FF,CAAC"}
1
+ {"version":3,"file":"ClaudeMessageResponseParser.d.ts","sourceRoot":"","sources":["../../../src/adapter/proxy/ClaudeMessageResponseParser.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,IAAI,MAAM,MAAM,CAAC;AAC7B,OAAO,EAAE,qBAAqB,EAAE,MAAM,6CAA6C,CAAC;AAkFpF,eAAO,MAAM,0BAA0B,GACrC,WAAW,MAAM,EACjB,YAAY,MAAM,EAClB,SAAS,IAAI,CAAC,mBAAmB,EACjC,MAAM,MAAM,KACX,qBA4FF,CAAC"}
@@ -2,6 +2,6 @@ import { SubAgentTranscriptDirectoryResolver } from '../../domain/usecases/adapt
2
2
  export declare class FileSystemSubAgentTranscriptDirectoryResolver implements SubAgentTranscriptDirectoryResolver {
3
3
  private readonly rootDirectory;
4
4
  constructor(rootDirectory: string | null);
5
- resolveSubAgentsDirectory: (sessionName: string) => string | null;
5
+ resolveSubAgentsDirectory: (_sessionName: string, mainTranscriptPath: string | null) => string | null;
6
6
  }
7
7
  //# sourceMappingURL=FileSystemSubAgentTranscriptDirectoryResolver.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"FileSystemSubAgentTranscriptDirectoryResolver.d.ts","sourceRoot":"","sources":["../../../src/adapter/repositories/FileSystemSubAgentTranscriptDirectoryResolver.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,mCAAmC,EAAE,MAAM,8EAA8E,CAAC;AAEnI,qBAAa,6CAA8C,YAAW,mCAAmC;IAC3F,OAAO,CAAC,QAAQ,CAAC,aAAa;gBAAb,aAAa,EAAE,MAAM,GAAG,IAAI;IAEzD,yBAAyB,GAAI,aAAa,MAAM,KAAG,MAAM,GAAG,IAAI,CAS9D;CACH"}
1
+ {"version":3,"file":"FileSystemSubAgentTranscriptDirectoryResolver.d.ts","sourceRoot":"","sources":["../../../src/adapter/repositories/FileSystemSubAgentTranscriptDirectoryResolver.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,mCAAmC,EAAE,MAAM,8EAA8E,CAAC;AAEnI,qBAAa,6CAA8C,YAAW,mCAAmC;IAC3F,OAAO,CAAC,QAAQ,CAAC,aAAa;gBAAb,aAAa,EAAE,MAAM,GAAG,IAAI;IAEzD,yBAAyB,GACvB,cAAc,MAAM,EACpB,oBAAoB,MAAM,GAAG,IAAI,KAChC,MAAM,GAAG,IAAI,CAYd;CACH"}
@@ -1 +1 @@
1
- {"version":3,"file":"GraphqlProjectRepository.d.ts","sourceRoot":"","sources":["../../../src/adapter/repositories/GraphqlProjectRepository.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,oBAAoB,EAAE,MAAM,wBAAwB,CAAC;AAC9D,OAAO,EAAE,2BAA2B,EAAE,MAAM,+BAA+B,CAAC;AAC5E,OAAO,EAAE,sBAAsB,EAAE,MAAM,0BAA0B,CAAC;AAClE,OAAO,EAAE,iBAAiB,EAAE,MAAM,4DAA4D,CAAC;AAC/F,OAAO,EAAE,WAAW,EAAE,OAAO,EAAE,MAAM,+BAA+B,CAAC;AAOrE,eAAO,MAAM,yBAAyB,GACpC,OAAO,MAAM,KACZ,WAAW,CAAC,OAAO,CAcrB,CAAC;AAEF,qBAAa,wBACX,SAAQ,oBACR,YACE,IAAI,CACF,iBAAiB,EACf,YAAY,GACZ,oBAAoB,GACpB,UAAU,GACV,iBAAiB,GACjB,kBAAkB,CACrB;IAEH,OAAO,CAAC,QAAQ,CAAC,cAAc,CAA6B;IAC5D,OAAO,CAAC,QAAQ,CAAC,sBAAsB,CAA6B;IACpE,OAAO,CAAC,QAAQ,CAAC,YAAY,CAAC,CAG5B;gBAGA,sBAAsB,EAAE,sBAAsB,EAC9C,OAAO,GAAE,MAAwC,EACjD,YAAY,CAAC,EAAE,IAAI,CAAC,2BAA2B,EAAE,WAAW,GAAG,KAAK,CAAC;IAMvE,OAAO,CAAC,0BAA0B,CAwBhC;IAEF,OAAO,CAAC,yBAAyB,CAe/B;IAEF,qBAAqB,GACnB,YAAY,MAAM,KACjB;QACD,KAAK,EAAE,MAAM,CAAC;QACd,aAAa,EAAE,MAAM,CAAC;KACvB,CAMC;IACF,cAAc,GACZ,OAAO,MAAM,EACb,eAAe,MAAM,KACpB,OAAO,CAAC,MAAM,CAAC,CA4FhB;IACF,kBAAkB,GAChB,YAAY,MAAM,KACjB,OAAO,CAAC,OAAO,CAAC,IAAI,CAAC,GAAG,IAAI,CAAC,CAG9B;IACF,UAAU,GAAU,WAAW,OAAO,CAAC,IAAI,CAAC,KAAG,OAAO,CAAC,OAAO,GAAG,IAAI,CAAC,CA8MpE;IACF,QAAQ,GAAU,KAAK,MAAM,KAAG,OAAO,CAAC,OAAO,CAAC,CAU9C;IACF,eAAe,GACb,SAAS,OAAO,EAChB,cAAc,CAAC,IAAI,CAAC,WAAW,EAAE,IAAI,CAAC,GAAG;QACvC,EAAE,EAAE,WAAW,CAAC,IAAI,CAAC,GAAG,IAAI,CAAC;KAC9B,CAAC,EAAE,KACH,OAAO,CAAC,WAAW,EAAE,CAAC,CA+CvB;IACF,gBAAgB,GACd,SAAS,OAAO,EAChB,eAAe,CAAC,IAAI,CAAC,WAAW,EAAE,IAAI,CAAC,GAAG;QACxC,EAAE,EAAE,WAAW,CAAC,IAAI,CAAC,GAAG,IAAI,CAAC;KAC9B,CAAC,EAAE,KACH,OAAO,CAAC,WAAW,EAAE,CAAC,CA4CvB;CACH"}
1
+ {"version":3,"file":"GraphqlProjectRepository.d.ts","sourceRoot":"","sources":["../../../src/adapter/repositories/GraphqlProjectRepository.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,oBAAoB,EAAE,MAAM,wBAAwB,CAAC;AAC9D,OAAO,EAAE,2BAA2B,EAAE,MAAM,+BAA+B,CAAC;AAC5E,OAAO,EAAE,sBAAsB,EAAE,MAAM,0BAA0B,CAAC;AAClE,OAAO,EAAE,iBAAiB,EAAE,MAAM,4DAA4D,CAAC;AAC/F,OAAO,EAAE,WAAW,EAAE,OAAO,EAAE,MAAM,+BAA+B,CAAC;AAOrE,eAAO,MAAM,yBAAyB,GACpC,OAAO,MAAM,KACZ,WAAW,CAAC,OAAO,CAcrB,CAAC;AAEF,qBAAa,wBACX,SAAQ,oBACR,YACE,IAAI,CACF,iBAAiB,EACf,YAAY,GACZ,oBAAoB,GACpB,UAAU,GACV,iBAAiB,GACjB,kBAAkB,CACrB;IAEH,OAAO,CAAC,QAAQ,CAAC,cAAc,CAA6B;IAC5D,OAAO,CAAC,QAAQ,CAAC,sBAAsB,CAA6B;IACpE,OAAO,CAAC,QAAQ,CAAC,YAAY,CAAC,CAG5B;gBAGA,sBAAsB,EAAE,sBAAsB,EAC9C,OAAO,GAAE,MAAwC,EACjD,YAAY,CAAC,EAAE,IAAI,CAAC,2BAA2B,EAAE,WAAW,GAAG,KAAK,CAAC;IAMvE,OAAO,CAAC,0BAA0B,CAwBhC;IAEF,OAAO,CAAC,yBAAyB,CAe/B;IAEF,qBAAqB,GACnB,YAAY,MAAM,KACjB;QACD,KAAK,EAAE,MAAM,CAAC;QACd,aAAa,EAAE,MAAM,CAAC;KACvB,CAMC;IACF,cAAc,GACZ,OAAO,MAAM,EACb,eAAe,MAAM,KACpB,OAAO,CAAC,MAAM,CAAC,CA6FhB;IACF,kBAAkB,GAChB,YAAY,MAAM,KACjB,OAAO,CAAC,OAAO,CAAC,IAAI,CAAC,GAAG,IAAI,CAAC,CAG9B;IACF,UAAU,GAAU,WAAW,OAAO,CAAC,IAAI,CAAC,KAAG,OAAO,CAAC,OAAO,GAAG,IAAI,CAAC,CA8MpE;IACF,QAAQ,GAAU,KAAK,MAAM,KAAG,OAAO,CAAC,OAAO,CAAC,CAU9C;IACF,eAAe,GACb,SAAS,OAAO,EAChB,cAAc,CAAC,IAAI,CAAC,WAAW,EAAE,IAAI,CAAC,GAAG;QACvC,EAAE,EAAE,WAAW,CAAC,IAAI,CAAC,GAAG,IAAI,CAAC;KAC9B,CAAC,EAAE,KACH,OAAO,CAAC,WAAW,EAAE,CAAC,CA+CvB;IACF,gBAAgB,GACd,SAAS,OAAO,EAChB,eAAe,CAAC,IAAI,CAAC,WAAW,EAAE,IAAI,CAAC,GAAG;QACxC,EAAE,EAAE,WAAW,CAAC,IAAI,CAAC,GAAG,IAAI,CAAC;KAC9B,CAAC,EAAE,KACH,OAAO,CAAC,WAAW,EAAE,CAAC,CA4CvB;CACH"}