github-issue-tower-defence-management 1.146.7 → 1.146.9

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 (30) hide show
  1. package/CHANGELOG.md +14 -0
  2. package/README.md +5 -4
  3. package/bin/adapter/entry-points/cli/fleetConfig.js +101 -0
  4. package/bin/adapter/entry-points/cli/fleetConfig.js.map +1 -0
  5. package/bin/adapter/entry-points/cli/index.js +21 -4
  6. package/bin/adapter/entry-points/cli/index.js.map +1 -1
  7. package/bin/adapter/entry-points/handlers/LiveSessionOauthTokenSelectHandler.js +4 -5
  8. package/bin/adapter/entry-points/handlers/LiveSessionOauthTokenSelectHandler.js.map +1 -1
  9. package/bin/domain/usecases/LiveSessionOauthTokenSelectUseCase.js +24 -9
  10. package/bin/domain/usecases/LiveSessionOauthTokenSelectUseCase.js.map +1 -1
  11. package/package.json +1 -1
  12. package/src/adapter/entry-points/cli/fleetConfig.test.ts +177 -0
  13. package/src/adapter/entry-points/cli/fleetConfig.ts +109 -0
  14. package/src/adapter/entry-points/cli/index.test.ts +82 -0
  15. package/src/adapter/entry-points/cli/index.ts +31 -3
  16. package/src/adapter/entry-points/handlers/GetStoryObjectMapUseCaseHandler.test.ts +8 -0
  17. package/src/adapter/entry-points/handlers/HandleScheduledEventUseCaseHandler.test.ts +8 -0
  18. package/src/adapter/entry-points/handlers/LiveSessionOauthTokenSelectHandler.test.ts +25 -10
  19. package/src/adapter/entry-points/handlers/LiveSessionOauthTokenSelectHandler.ts +5 -5
  20. package/src/adapter/repositories/issue/ApiV3CheerioRestIssueRepository.test.ts +12 -0
  21. package/src/domain/usecases/LiveSessionOauthTokenSelectUseCase.test.ts +325 -156
  22. package/src/domain/usecases/LiveSessionOauthTokenSelectUseCase.ts +50 -22
  23. package/types/adapter/entry-points/cli/fleetConfig.d.ts +6 -0
  24. package/types/adapter/entry-points/cli/fleetConfig.d.ts.map +1 -0
  25. package/types/adapter/entry-points/cli/index.d.ts +2 -0
  26. package/types/adapter/entry-points/cli/index.d.ts.map +1 -1
  27. package/types/adapter/entry-points/handlers/LiveSessionOauthTokenSelectHandler.d.ts +3 -4
  28. package/types/adapter/entry-points/handlers/LiveSessionOauthTokenSelectHandler.d.ts.map +1 -1
  29. package/types/domain/usecases/LiveSessionOauthTokenSelectUseCase.d.ts +10 -2
  30. package/types/domain/usecases/LiveSessionOauthTokenSelectUseCase.d.ts.map +1 -1
@@ -2,18 +2,47 @@ import { ClaudeLiveSession } from './adapter-interfaces/ClaudeLiveSessionReposit
2
2
  import {
3
3
  OauthTokenCandidate,
4
4
  OauthTokenSelectUseCase,
5
- SelectionRandom,
6
- selectWeightedCandidate,
7
5
  selectionWeightOf,
8
- sevenDayUrgencyFactor,
9
6
  } from './OauthTokenSelectUseCase';
10
7
 
8
+ export type LiveSessionOauthTokenSelectionSettings = {
9
+ maxConcurrentSessionCount: number;
10
+ fullSpeedFiveHourFreeRatio: number;
11
+ };
12
+
13
+ export const DEFAULT_LIVE_SESSION_OAUTH_TOKEN_SELECTION_SETTINGS: LiveSessionOauthTokenSelectionSettings =
14
+ {
15
+ maxConcurrentSessionCount: 10,
16
+ fullSpeedFiveHourFreeRatio: 0.5,
17
+ };
18
+
19
+ export const liveSessionConcurrentLimitOf = (
20
+ fiveHourFreeRatio: number,
21
+ selectionWeight: number,
22
+ settings: LiveSessionOauthTokenSelectionSettings,
23
+ ): number => {
24
+ const fiveHourThrottleFactor = Math.min(
25
+ fiveHourFreeRatio / settings.fullSpeedFiveHourFreeRatio,
26
+ 1,
27
+ );
28
+ return Math.max(
29
+ Math.floor(
30
+ settings.maxConcurrentSessionCount *
31
+ selectionWeight *
32
+ fiveHourThrottleFactor,
33
+ ),
34
+ 1,
35
+ );
36
+ };
37
+
11
38
  export type LiveSessionOauthTokenCandidateMetrics = {
12
39
  name: string;
13
40
  fiveHourFreeRatio: number;
14
41
  sevenDayFreeRatio: number;
15
42
  sevenDayEndEpoch: number;
16
43
  liveSessionCount: number;
44
+ concurrentSessionLimit: number;
45
+ hasConcurrencyHeadroom: boolean;
17
46
  eligible: boolean;
18
47
  exclusionReason: string | null;
19
48
  };
@@ -32,7 +61,7 @@ export class LiveSessionOauthTokenSelectUseCase {
32
61
  candidates: OauthTokenCandidate[],
33
62
  liveSessions: ClaudeLiveSession[],
34
63
  nowEpochSeconds: number,
35
- random: SelectionRandom = Math.random,
64
+ settings: LiveSessionOauthTokenSelectionSettings,
36
65
  ): LiveSessionOauthTokenSelectResult => {
37
66
  const rateLimitResult = this.rateLimitSelectUseCase.run(
38
67
  candidates,
@@ -45,6 +74,11 @@ export class LiveSessionOauthTokenSelectUseCase {
45
74
  const rateLimitMetric = rateLimitResult.metrics[index];
46
75
  const liveSessionCount =
47
76
  liveSessionCountByToken.get(candidate.token) ?? 0;
77
+ const concurrentSessionLimit = liveSessionConcurrentLimitOf(
78
+ rateLimitMetric.fiveHourFreeRatio,
79
+ selectionWeightOf(candidate),
80
+ settings,
81
+ );
48
82
  return {
49
83
  candidate,
50
84
  metric: {
@@ -53,6 +87,8 @@ export class LiveSessionOauthTokenSelectUseCase {
53
87
  sevenDayFreeRatio: rateLimitMetric.sevenDayFreeRatio,
54
88
  sevenDayEndEpoch: rateLimitMetric.sevenDayEndEpoch,
55
89
  liveSessionCount,
90
+ concurrentSessionLimit,
91
+ hasConcurrencyHeadroom: liveSessionCount < concurrentSessionLimit,
56
92
  eligible: rateLimitMetric.eligible,
57
93
  exclusionReason: rateLimitMetric.exclusionReason,
58
94
  },
@@ -66,26 +102,12 @@ export class LiveSessionOauthTokenSelectUseCase {
66
102
  return { selected: null, metrics };
67
103
  }
68
104
 
69
- const deterministicBest = eligible.reduce((bestEntry, currentEntry) =>
105
+ const selected = eligible.reduce((bestEntry, currentEntry) =>
70
106
  this.preferred(currentEntry.metric, bestEntry.metric)
71
107
  ? currentEntry
72
108
  : bestEntry,
73
109
  );
74
110
 
75
- const selected = selectWeightedCandidate(
76
- eligible,
77
- (entry) =>
78
- (selectionWeightOf(entry.candidate) *
79
- sevenDayUrgencyFactor(
80
- entry.metric.sevenDayFreeRatio,
81
- entry.metric.sevenDayEndEpoch,
82
- nowEpochSeconds,
83
- )) /
84
- (1 + entry.metric.liveSessionCount),
85
- deterministicBest,
86
- random,
87
- );
88
-
89
111
  return { selected: selected.candidate, metrics };
90
112
  };
91
113
 
@@ -93,12 +115,18 @@ export class LiveSessionOauthTokenSelectUseCase {
93
115
  candidateMetric: LiveSessionOauthTokenCandidateMetrics,
94
116
  incumbentMetric: LiveSessionOauthTokenCandidateMetrics,
95
117
  ): boolean => {
96
- if (candidateMetric.liveSessionCount !== incumbentMetric.liveSessionCount) {
118
+ if (
119
+ candidateMetric.hasConcurrencyHeadroom !==
120
+ incumbentMetric.hasConcurrencyHeadroom
121
+ ) {
122
+ return candidateMetric.hasConcurrencyHeadroom;
123
+ }
124
+ if (candidateMetric.sevenDayEndEpoch !== incumbentMetric.sevenDayEndEpoch) {
97
125
  return (
98
- candidateMetric.liveSessionCount < incumbentMetric.liveSessionCount
126
+ candidateMetric.sevenDayEndEpoch < incumbentMetric.sevenDayEndEpoch
99
127
  );
100
128
  }
101
- return candidateMetric.sevenDayEndEpoch < incumbentMetric.sevenDayEndEpoch;
129
+ return candidateMetric.liveSessionCount < incumbentMetric.liveSessionCount;
102
130
  };
103
131
 
104
132
  private liveSessionCountByToken = (
@@ -0,0 +1,6 @@
1
+ import { LiveSessionOauthTokenSelectionSettings } from '../../../domain/usecases/LiveSessionOauthTokenSelectUseCase';
2
+ export declare const FLEET_CONFIG_FILE_PATH_ENVIRONMENT_VARIABLE = "TDPM_FLEET_CONFIG";
3
+ export declare const LIVE_SESSION_OAUTH_TOKEN_SELECTION_SECTION_KEY = "liveSessionOauthTokenSelection";
4
+ export declare const resolveFleetConfigFilePath: (cliValue: string | null) => string | null;
5
+ export declare const loadLiveSessionOauthTokenSelectionSettings: (fleetConfigFilePath: string | null) => LiveSessionOauthTokenSelectionSettings;
6
+ //# sourceMappingURL=fleetConfig.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"fleetConfig.d.ts","sourceRoot":"","sources":["../../../../src/adapter/entry-points/cli/fleetConfig.ts"],"names":[],"mappings":"AAEA,OAAO,EAEL,sCAAsC,EACvC,MAAM,6DAA6D,CAAC;AAErE,eAAO,MAAM,2CAA2C,sBAAsB,CAAC;AAE/E,eAAO,MAAM,8CAA8C,mCACzB,CAAC;AAEnC,eAAO,MAAM,0BAA0B,GACrC,UAAU,MAAM,GAAG,IAAI,KACtB,MAAM,GAAG,IAUX,CAAC;AAwDF,eAAO,MAAM,0CAA0C,GACrD,qBAAqB,MAAM,GAAG,IAAI,KACjC,sCA0BF,CAAC"}
@@ -2,4 +2,6 @@
2
2
  import { Command } from 'commander';
3
3
  export { ConfigFile, loadConfigFile, parseProjectReadmeConfig, mergeConfigs, fetchProjectReadme, } from './projectConfig';
4
4
  export declare const program: Command;
5
+ export declare const reportFatalErrorAndExit: (error: unknown) => void;
6
+ export declare const runCliProgram: (argv: string[], handleFatalError: (error: unknown) => void) => Promise<void>;
5
7
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../src/adapter/entry-points/cli/index.ts"],"names":[],"mappings":";AAEA,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AACpC,OAAO,EACL,UAAU,EACV,cAAc,EACd,wBAAwB,EACxB,YAAY,EACZ,kBAAkB,GACnB,MAAM,iBAAiB,CAAC;AA+JzB,eAAO,MAAM,OAAO,SAAgB,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../src/adapter/entry-points/cli/index.ts"],"names":[],"mappings":";AAEA,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AACpC,OAAO,EACL,UAAU,EACV,cAAc,EACd,wBAAwB,EACxB,YAAY,EACZ,kBAAkB,GACnB,MAAM,iBAAiB,CAAC;AAoKzB,eAAO,MAAM,OAAO,SAAgB,CAAC;AAq8BrC,eAAO,MAAM,uBAAuB,GAAI,OAAO,OAAO,KAAG,IAGxD,CAAC;AAEF,eAAO,MAAM,aAAa,GACxB,MAAM,MAAM,EAAE,EACd,kBAAkB,CAAC,KAAK,EAAE,OAAO,KAAK,IAAI,KACzC,OAAO,CAAC,IAAI,CAMd,CAAC"}
@@ -1,10 +1,10 @@
1
1
  import { ClaudeLiveSessionRepository } from '../../../domain/usecases/adapter-interfaces/ClaudeLiveSessionRepository';
2
- import { LiveSessionOauthTokenSelectUseCase } from '../../../domain/usecases/LiveSessionOauthTokenSelectUseCase';
3
- import { SelectionRandom } from '../../../domain/usecases/OauthTokenSelectUseCase';
2
+ import { LiveSessionOauthTokenSelectUseCase, LiveSessionOauthTokenSelectionSettings } from '../../../domain/usecases/LiveSessionOauthTokenSelectUseCase';
4
3
  export type LiveSessionOauthTokenSelectHandlerInput = {
5
4
  tokenListJsonPath: string | null;
6
5
  cacheDirectory: string | null;
7
6
  nowEpochSeconds: number;
7
+ selectionSettings: LiveSessionOauthTokenSelectionSettings;
8
8
  };
9
9
  export type LiveSessionOauthTokenSelectHandlerOutput = {
10
10
  selectedToken: string | null;
@@ -14,8 +14,7 @@ export type LiveSessionOauthTokenSelectHandlerOutput = {
14
14
  export declare class LiveSessionOauthTokenSelectHandler {
15
15
  private readonly useCase;
16
16
  private readonly liveSessionRepository;
17
- private readonly random;
18
- constructor(useCase?: LiveSessionOauthTokenSelectUseCase, liveSessionRepository?: ClaudeLiveSessionRepository, random?: SelectionRandom);
17
+ constructor(useCase?: LiveSessionOauthTokenSelectUseCase, liveSessionRepository?: ClaudeLiveSessionRepository);
19
18
  handle: (input: LiveSessionOauthTokenSelectHandlerInput) => LiveSessionOauthTokenSelectHandlerOutput;
20
19
  private formatDiagnostics;
21
20
  }
@@ -1 +1 @@
1
- {"version":3,"file":"LiveSessionOauthTokenSelectHandler.d.ts","sourceRoot":"","sources":["../../../../src/adapter/entry-points/handlers/LiveSessionOauthTokenSelectHandler.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,2BAA2B,EAAE,MAAM,yEAAyE,CAAC;AACtH,OAAO,EAEL,kCAAkC,EACnC,MAAM,6DAA6D,CAAC;AACrE,OAAO,EAKL,eAAe,EAChB,MAAM,kDAAkD,CAAC;AAS1D,MAAM,MAAM,uCAAuC,GAAG;IACpD,iBAAiB,EAAE,MAAM,GAAG,IAAI,CAAC;IACjC,cAAc,EAAE,MAAM,GAAG,IAAI,CAAC;IAC9B,eAAe,EAAE,MAAM,CAAC;CACzB,CAAC;AAEF,MAAM,MAAM,wCAAwC,GAAG;IACrD,aAAa,EAAE,MAAM,GAAG,IAAI,CAAC;IAC7B,YAAY,EAAE,MAAM,GAAG,IAAI,CAAC;IAC5B,WAAW,EAAE,MAAM,EAAE,CAAC;CACvB,CAAC;AAEF,qBAAa,kCAAkC;IAE3C,OAAO,CAAC,QAAQ,CAAC,OAAO;IACxB,OAAO,CAAC,QAAQ,CAAC,qBAAqB;IACtC,OAAO,CAAC,QAAQ,CAAC,MAAM;gBAFN,OAAO,GAAE,kCAA6E,EACtF,qBAAqB,GAAE,2BAAmE,EAC1F,MAAM,GAAE,eAA6B;IAGxD,MAAM,GACJ,OAAO,uCAAuC,KAC7C,wCAAwC,CAmEzC;IAEF,OAAO,CAAC,iBAAiB,CAyBvB;CACH"}
1
+ {"version":3,"file":"LiveSessionOauthTokenSelectHandler.d.ts","sourceRoot":"","sources":["../../../../src/adapter/entry-points/handlers/LiveSessionOauthTokenSelectHandler.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,2BAA2B,EAAE,MAAM,yEAAyE,CAAC;AACtH,OAAO,EAEL,kCAAkC,EAClC,sCAAsC,EACvC,MAAM,6DAA6D,CAAC;AAerE,MAAM,MAAM,uCAAuC,GAAG;IACpD,iBAAiB,EAAE,MAAM,GAAG,IAAI,CAAC;IACjC,cAAc,EAAE,MAAM,GAAG,IAAI,CAAC;IAC9B,eAAe,EAAE,MAAM,CAAC;IACxB,iBAAiB,EAAE,sCAAsC,CAAC;CAC3D,CAAC;AAEF,MAAM,MAAM,wCAAwC,GAAG;IACrD,aAAa,EAAE,MAAM,GAAG,IAAI,CAAC;IAC7B,YAAY,EAAE,MAAM,GAAG,IAAI,CAAC;IAC5B,WAAW,EAAE,MAAM,EAAE,CAAC;CACvB,CAAC;AAEF,qBAAa,kCAAkC;IAE3C,OAAO,CAAC,QAAQ,CAAC,OAAO;IACxB,OAAO,CAAC,QAAQ,CAAC,qBAAqB;gBADrB,OAAO,GAAE,kCAA6E,EACtF,qBAAqB,GAAE,2BAAmE;IAG7G,MAAM,GACJ,OAAO,uCAAuC,KAC7C,wCAAwC,CAmEzC;IAEF,OAAO,CAAC,iBAAiB,CAyBvB;CACH"}
@@ -1,11 +1,19 @@
1
1
  import { ClaudeLiveSession } from './adapter-interfaces/ClaudeLiveSessionRepository';
2
- import { OauthTokenCandidate, OauthTokenSelectUseCase, SelectionRandom } from './OauthTokenSelectUseCase';
2
+ import { OauthTokenCandidate, OauthTokenSelectUseCase } from './OauthTokenSelectUseCase';
3
+ export type LiveSessionOauthTokenSelectionSettings = {
4
+ maxConcurrentSessionCount: number;
5
+ fullSpeedFiveHourFreeRatio: number;
6
+ };
7
+ export declare const DEFAULT_LIVE_SESSION_OAUTH_TOKEN_SELECTION_SETTINGS: LiveSessionOauthTokenSelectionSettings;
8
+ export declare const liveSessionConcurrentLimitOf: (fiveHourFreeRatio: number, selectionWeight: number, settings: LiveSessionOauthTokenSelectionSettings) => number;
3
9
  export type LiveSessionOauthTokenCandidateMetrics = {
4
10
  name: string;
5
11
  fiveHourFreeRatio: number;
6
12
  sevenDayFreeRatio: number;
7
13
  sevenDayEndEpoch: number;
8
14
  liveSessionCount: number;
15
+ concurrentSessionLimit: number;
16
+ hasConcurrencyHeadroom: boolean;
9
17
  eligible: boolean;
10
18
  exclusionReason: string | null;
11
19
  };
@@ -16,7 +24,7 @@ export type LiveSessionOauthTokenSelectResult = {
16
24
  export declare class LiveSessionOauthTokenSelectUseCase {
17
25
  private readonly rateLimitSelectUseCase;
18
26
  constructor(rateLimitSelectUseCase?: OauthTokenSelectUseCase);
19
- run: (candidates: OauthTokenCandidate[], liveSessions: ClaudeLiveSession[], nowEpochSeconds: number, random?: SelectionRandom) => LiveSessionOauthTokenSelectResult;
27
+ run: (candidates: OauthTokenCandidate[], liveSessions: ClaudeLiveSession[], nowEpochSeconds: number, settings: LiveSessionOauthTokenSelectionSettings) => LiveSessionOauthTokenSelectResult;
20
28
  private preferred;
21
29
  private liveSessionCountByToken;
22
30
  }
@@ -1 +1 @@
1
- {"version":3,"file":"LiveSessionOauthTokenSelectUseCase.d.ts","sourceRoot":"","sources":["../../../src/domain/usecases/LiveSessionOauthTokenSelectUseCase.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,iBAAiB,EAAE,MAAM,kDAAkD,CAAC;AACrF,OAAO,EACL,mBAAmB,EACnB,uBAAuB,EACvB,eAAe,EAIhB,MAAM,2BAA2B,CAAC;AAEnC,MAAM,MAAM,qCAAqC,GAAG;IAClD,IAAI,EAAE,MAAM,CAAC;IACb,iBAAiB,EAAE,MAAM,CAAC;IAC1B,iBAAiB,EAAE,MAAM,CAAC;IAC1B,gBAAgB,EAAE,MAAM,CAAC;IACzB,gBAAgB,EAAE,MAAM,CAAC;IACzB,QAAQ,EAAE,OAAO,CAAC;IAClB,eAAe,EAAE,MAAM,GAAG,IAAI,CAAC;CAChC,CAAC;AAEF,MAAM,MAAM,iCAAiC,GAAG;IAC9C,QAAQ,EAAE,mBAAmB,GAAG,IAAI,CAAC;IACrC,OAAO,EAAE,qCAAqC,EAAE,CAAC;CAClD,CAAC;AAEF,qBAAa,kCAAkC;IAE3C,OAAO,CAAC,QAAQ,CAAC,sBAAsB;gBAAtB,sBAAsB,GAAE,uBAAuD;IAGlG,GAAG,GACD,YAAY,mBAAmB,EAAE,EACjC,cAAc,iBAAiB,EAAE,EACjC,iBAAiB,MAAM,EACvB,SAAQ,eAA6B,KACpC,iCAAiC,CAsDlC;IAEF,OAAO,CAAC,SAAS,CAUf;IAEF,OAAO,CAAC,uBAAuB,CAe7B;CACH"}
1
+ {"version":3,"file":"LiveSessionOauthTokenSelectUseCase.d.ts","sourceRoot":"","sources":["../../../src/domain/usecases/LiveSessionOauthTokenSelectUseCase.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,iBAAiB,EAAE,MAAM,kDAAkD,CAAC;AACrF,OAAO,EACL,mBAAmB,EACnB,uBAAuB,EAExB,MAAM,2BAA2B,CAAC;AAEnC,MAAM,MAAM,sCAAsC,GAAG;IACnD,yBAAyB,EAAE,MAAM,CAAC;IAClC,0BAA0B,EAAE,MAAM,CAAC;CACpC,CAAC;AAEF,eAAO,MAAM,mDAAmD,EAAE,sCAI/D,CAAC;AAEJ,eAAO,MAAM,4BAA4B,GACvC,mBAAmB,MAAM,EACzB,iBAAiB,MAAM,EACvB,UAAU,sCAAsC,KAC/C,MAaF,CAAC;AAEF,MAAM,MAAM,qCAAqC,GAAG;IAClD,IAAI,EAAE,MAAM,CAAC;IACb,iBAAiB,EAAE,MAAM,CAAC;IAC1B,iBAAiB,EAAE,MAAM,CAAC;IAC1B,gBAAgB,EAAE,MAAM,CAAC;IACzB,gBAAgB,EAAE,MAAM,CAAC;IACzB,sBAAsB,EAAE,MAAM,CAAC;IAC/B,sBAAsB,EAAE,OAAO,CAAC;IAChC,QAAQ,EAAE,OAAO,CAAC;IAClB,eAAe,EAAE,MAAM,GAAG,IAAI,CAAC;CAChC,CAAC;AAEF,MAAM,MAAM,iCAAiC,GAAG;IAC9C,QAAQ,EAAE,mBAAmB,GAAG,IAAI,CAAC;IACrC,OAAO,EAAE,qCAAqC,EAAE,CAAC;CAClD,CAAC;AAEF,qBAAa,kCAAkC;IAE3C,OAAO,CAAC,QAAQ,CAAC,sBAAsB;gBAAtB,sBAAsB,GAAE,uBAAuD;IAGlG,GAAG,GACD,YAAY,mBAAmB,EAAE,EACjC,cAAc,iBAAiB,EAAE,EACjC,iBAAiB,MAAM,EACvB,UAAU,sCAAsC,KAC/C,iCAAiC,CA+ClC;IAEF,OAAO,CAAC,SAAS,CAgBf;IAEF,OAAO,CAAC,uBAAuB,CAe7B;CACH"}