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

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 (54) hide show
  1. package/.github/dependabot.yml +2 -0
  2. package/.github/workflows/commit-lint.yml +20 -0
  3. package/.github/workflows/umino-project.yml +0 -1
  4. package/CHANGELOG.md +14 -0
  5. package/bin/adapter/entry-points/cli/index.js +2 -2
  6. package/bin/adapter/entry-points/cli/index.js.map +1 -1
  7. package/bin/adapter/entry-points/handlers/LiveSessionOauthTokenSelectHandler.js +4 -3
  8. package/bin/adapter/entry-points/handlers/LiveSessionOauthTokenSelectHandler.js.map +1 -1
  9. package/bin/adapter/entry-points/handlers/OauthTokenSelectHandler.js +4 -3
  10. package/bin/adapter/entry-points/handlers/OauthTokenSelectHandler.js.map +1 -1
  11. package/bin/adapter/entry-points/handlers/notifySilentTmuxSessions.js +3 -2
  12. package/bin/adapter/entry-points/handlers/notifySilentTmuxSessions.js.map +1 -1
  13. package/bin/adapter/entry-points/handlers/resetDegeneratedTmuxSessions.js +1 -1
  14. package/bin/adapter/entry-points/handlers/resetDegeneratedTmuxSessions.js.map +1 -1
  15. package/bin/adapter/repositories/FileSystemSubAgentLivenessResolver.js +19 -1
  16. package/bin/adapter/repositories/FileSystemSubAgentLivenessResolver.js.map +1 -1
  17. package/bin/adapter/repositories/TmuxSilentSessionNotificationRepository.js +73 -2
  18. package/bin/adapter/repositories/TmuxSilentSessionNotificationRepository.js.map +1 -1
  19. package/bin/domain/usecases/LiveSessionOauthTokenSelectUseCase.js +3 -1
  20. package/bin/domain/usecases/LiveSessionOauthTokenSelectUseCase.js.map +1 -1
  21. package/bin/domain/usecases/OauthTokenSelectUseCase.js +13 -4
  22. package/bin/domain/usecases/OauthTokenSelectUseCase.js.map +1 -1
  23. package/package.json +1 -1
  24. package/renovate.json +5 -0
  25. package/src/adapter/entry-points/cli/index.ts +2 -2
  26. package/src/adapter/entry-points/handlers/LiveSessionOauthTokenSelectHandler.test.ts +7 -4
  27. package/src/adapter/entry-points/handlers/LiveSessionOauthTokenSelectHandler.ts +4 -1
  28. package/src/adapter/entry-points/handlers/OauthTokenSelectHandler.test.ts +6 -2
  29. package/src/adapter/entry-points/handlers/OauthTokenSelectHandler.ts +8 -2
  30. package/src/adapter/entry-points/handlers/notifySilentTmuxSessions.test.ts +1 -0
  31. package/src/adapter/entry-points/handlers/notifySilentTmuxSessions.ts +14 -2
  32. package/src/adapter/entry-points/handlers/resetDegeneratedTmuxSessions.ts +4 -1
  33. package/src/adapter/repositories/FileSystemSubAgentLivenessResolver.test.ts +49 -1
  34. package/src/adapter/repositories/FileSystemSubAgentLivenessResolver.ts +21 -1
  35. package/src/adapter/repositories/TmuxSilentSessionNotificationRepository.test.ts +157 -15
  36. package/src/adapter/repositories/TmuxSilentSessionNotificationRepository.ts +115 -1
  37. package/src/domain/usecases/LiveSessionOauthTokenSelectUseCase.test.ts +77 -5
  38. package/src/domain/usecases/LiveSessionOauthTokenSelectUseCase.ts +10 -1
  39. package/src/domain/usecases/OauthTokenSelectUseCase.test.ts +39 -8
  40. package/src/domain/usecases/OauthTokenSelectUseCase.ts +25 -5
  41. package/types/adapter/entry-points/handlers/LiveSessionOauthTokenSelectHandler.d.ts +3 -1
  42. package/types/adapter/entry-points/handlers/LiveSessionOauthTokenSelectHandler.d.ts.map +1 -1
  43. package/types/adapter/entry-points/handlers/OauthTokenSelectHandler.d.ts +3 -2
  44. package/types/adapter/entry-points/handlers/OauthTokenSelectHandler.d.ts.map +1 -1
  45. package/types/adapter/entry-points/handlers/notifySilentTmuxSessions.d.ts +1 -0
  46. package/types/adapter/entry-points/handlers/notifySilentTmuxSessions.d.ts.map +1 -1
  47. package/types/adapter/entry-points/handlers/resetDegeneratedTmuxSessions.d.ts.map +1 -1
  48. package/types/adapter/repositories/FileSystemSubAgentLivenessResolver.d.ts +1 -0
  49. package/types/adapter/repositories/FileSystemSubAgentLivenessResolver.d.ts.map +1 -1
  50. package/types/adapter/repositories/TmuxSilentSessionNotificationRepository.d.ts +14 -1
  51. package/types/adapter/repositories/TmuxSilentSessionNotificationRepository.d.ts.map +1 -1
  52. package/types/domain/usecases/LiveSessionOauthTokenSelectUseCase.d.ts.map +1 -1
  53. package/types/domain/usecases/OauthTokenSelectUseCase.d.ts +4 -1
  54. package/types/domain/usecases/OauthTokenSelectUseCase.d.ts.map +1 -1
@@ -1,11 +1,66 @@
1
1
  import { SilentSessionNotificationRepository } from '../../domain/usecases/adapter-interfaces/SilentSessionNotificationRepository';
2
2
  import { LocalCommandRunner } from '../../domain/usecases/adapter-interfaces/LocalCommandRunner';
3
+ import { Sleeper } from '../../domain/usecases/adapter-interfaces/Sleeper';
3
4
 
4
5
  const BRACKETED_PASTE_START = '\x1b[200~';
5
6
  const BRACKETED_PASTE_END = '\x1b[201~';
6
7
 
8
+ export const DEFAULT_SUBMIT_PUSH_OUT_ATTEMPT_LIMIT = 3;
9
+ export const DEFAULT_SUBMIT_PUSH_OUT_WAIT_MILLISECONDS = 2500;
10
+
11
+ const INPUT_BOX_BORDER_LINE_PATTERN = /^[\s─━╌┄┈│╭╮╰╯]+$/u;
12
+ const INPUT_PROMPT_PREFIX_PATTERN = /^[\s>❯│]+/u;
13
+ const INPUT_BOX_MESSAGE_PROBE_LENGTH = 24;
14
+
15
+ type InputBoxState = 'holdsMessage' | 'clearedMessage' | 'unreadable';
16
+
17
+ const withoutWhitespace = (value: string): string => value.replace(/\s+/gu, '');
18
+
19
+ export const extractTmuxInputBoxContent = (paneText: string): string | null => {
20
+ const lines = paneText.split('\n');
21
+ const borderLineIndexes = lines.flatMap((line, index) =>
22
+ line.trim().length > 0 && INPUT_BOX_BORDER_LINE_PATTERN.test(line)
23
+ ? [index]
24
+ : [],
25
+ );
26
+ if (borderLineIndexes.length < 2) {
27
+ return null;
28
+ }
29
+ const closingBorderIndex = borderLineIndexes[borderLineIndexes.length - 1];
30
+ const openingBorderIndex = borderLineIndexes[borderLineIndexes.length - 2];
31
+ return lines
32
+ .slice(openingBorderIndex + 1, closingBorderIndex)
33
+ .map((line) => line.replace(INPUT_PROMPT_PREFIX_PATTERN, ''))
34
+ .join('');
35
+ };
36
+
37
+ export const resolveInputBoxStateFromPane = (
38
+ paneText: string,
39
+ singleLineMessage: string,
40
+ ): InputBoxState => {
41
+ const inputBoxContent = extractTmuxInputBoxContent(paneText);
42
+ if (inputBoxContent === null) {
43
+ return 'unreadable';
44
+ }
45
+ const messageProbe = withoutWhitespace(singleLineMessage).slice(
46
+ 0,
47
+ INPUT_BOX_MESSAGE_PROBE_LENGTH,
48
+ );
49
+ if (messageProbe.length === 0) {
50
+ return 'clearedMessage';
51
+ }
52
+ return withoutWhitespace(inputBoxContent).includes(messageProbe)
53
+ ? 'holdsMessage'
54
+ : 'clearedMessage';
55
+ };
56
+
7
57
  export class TmuxSilentSessionNotificationRepository implements SilentSessionNotificationRepository {
8
- constructor(private readonly localCommandRunner: LocalCommandRunner) {}
58
+ constructor(
59
+ private readonly localCommandRunner: LocalCommandRunner,
60
+ private readonly sleeper: Sleeper,
61
+ private readonly submitPushOutAttemptLimit: number = DEFAULT_SUBMIT_PUSH_OUT_ATTEMPT_LIMIT,
62
+ private readonly submitPushOutWaitMilliseconds: number = DEFAULT_SUBMIT_PUSH_OUT_WAIT_MILLISECONDS,
63
+ ) {}
9
64
 
10
65
  sendSelfCheckNotification = async (
11
66
  sessionName: string,
@@ -27,6 +82,11 @@ export class TmuxSilentSessionNotificationRepository implements SilentSessionNot
27
82
  }`,
28
83
  );
29
84
  }
85
+ await this.submitInputBox(sessionName);
86
+ await this.pushOutUnsubmittedMessage(sessionName, singleLineMessage);
87
+ };
88
+
89
+ private submitInputBox = async (sessionName: string): Promise<void> => {
30
90
  const submitResult = await this.localCommandRunner.runCommand('tmux', [
31
91
  'send-keys',
32
92
  '-t',
@@ -41,4 +101,58 @@ export class TmuxSilentSessionNotificationRepository implements SilentSessionNot
41
101
  );
42
102
  }
43
103
  };
104
+
105
+ private pushOutUnsubmittedMessage = async (
106
+ sessionName: string,
107
+ singleLineMessage: string,
108
+ ): Promise<void> => {
109
+ for (
110
+ let attempt = 0;
111
+ attempt < this.submitPushOutAttemptLimit;
112
+ attempt += 1
113
+ ) {
114
+ await this.sleeper.sleep(this.submitPushOutWaitMilliseconds);
115
+ const inputBoxState = await this.readInputBoxState(
116
+ sessionName,
117
+ singleLineMessage,
118
+ );
119
+ if (inputBoxState === 'clearedMessage') {
120
+ return;
121
+ }
122
+ if (inputBoxState === 'unreadable') {
123
+ console.log(
124
+ `Could not read back the input box of tmux session "${sessionName}"; leaving the notification as sent without pushing it out.`,
125
+ );
126
+ return;
127
+ }
128
+ await this.submitInputBox(sessionName);
129
+ }
130
+ console.log(
131
+ `Notification to tmux session "${sessionName}" was still held in the input box after ${this.submitPushOutAttemptLimit} push-out attempt(s).`,
132
+ );
133
+ };
134
+
135
+ private readInputBoxState = async (
136
+ sessionName: string,
137
+ singleLineMessage: string,
138
+ ): Promise<InputBoxState> => {
139
+ const captureResult = await this.localCommandRunner.runCommand('tmux', [
140
+ 'capture-pane',
141
+ '-p',
142
+ '-t',
143
+ sessionName,
144
+ ]);
145
+ if (captureResult.exitCode !== 0) {
146
+ console.log(
147
+ `Failed to capture the pane of tmux session "${sessionName}": exit code ${captureResult.exitCode}${
148
+ captureResult.stderr ? `: ${captureResult.stderr}` : ''
149
+ }`,
150
+ );
151
+ return 'unreadable';
152
+ }
153
+ return resolveInputBoxStateFromPane(
154
+ captureResult.stdout,
155
+ singleLineMessage,
156
+ );
157
+ };
44
158
  }
@@ -47,6 +47,7 @@ describe('LiveSessionOauthTokenSelectUseCase', () => {
47
47
  [candidate('busy', snapshot({})), candidate('idle', snapshot({}))],
48
48
  [session('busy', 'session-a')],
49
49
  NOW,
50
+ () => 0.5,
50
51
  );
51
52
 
52
53
  expect(result.selected?.name).toBe('idle');
@@ -63,12 +64,13 @@ describe('LiveSessionOauthTokenSelectUseCase', () => {
63
64
  ],
64
65
  [session('far', 'session-a'), session('soon', 'session-b')],
65
66
  NOW,
67
+ () => 0.5,
66
68
  );
67
69
 
68
70
  expect(result.selected?.name).toBe('soon');
69
71
  });
70
72
 
71
- it('prefers fewer live sessions over a sooner 7d reset', () => {
73
+ it('falls back to fewer live sessions when occupancy exactly cancels the urgency gap', () => {
72
74
  const result = useCase.run(
73
75
  [
74
76
  candidate('soonButBusy', snapshot({ sevenDayReset: NOW + 2 * DAY })),
@@ -79,6 +81,7 @@ describe('LiveSessionOauthTokenSelectUseCase', () => {
79
81
  session('soonButBusy', 'session-b'),
80
82
  ],
81
83
  NOW,
84
+ throwingRandom,
82
85
  );
83
86
 
84
87
  expect(result.selected?.name).toBe('farButIdle');
@@ -114,6 +117,7 @@ describe('LiveSessionOauthTokenSelectUseCase', () => {
114
117
  session('twoSessions', 'session-c'),
115
118
  ],
116
119
  NOW,
120
+ () => 0.5,
117
121
  );
118
122
 
119
123
  expect(result.selected?.name).toBe('oneSession');
@@ -136,6 +140,7 @@ describe('LiveSessionOauthTokenSelectUseCase', () => {
136
140
  session('fresh', 'session-fresh'),
137
141
  ],
138
142
  NOW,
143
+ () => 0.99,
139
144
  );
140
145
 
141
146
  expect(result.selected?.name).toBe('fresh');
@@ -231,15 +236,18 @@ const withSelectionWeight = (
231
236
  describe('LiveSessionOauthTokenSelectUseCase selectionWeight', () => {
232
237
  const useCase = new LiveSessionOauthTokenSelectUseCase();
233
238
 
234
- it('keeps the deterministic fewest-live-sessions selection and never consults random when weights are uniform', () => {
239
+ it('keeps the deterministic selection and never consults random when the urgency weights are identical', () => {
235
240
  const result = useCase.run(
236
- [candidate('busy', snapshot({})), candidate('idle', snapshot({}))],
237
- [session('busy', 'session-a')],
241
+ [
242
+ candidate('firstOfEqualPair', snapshot({})),
243
+ candidate('secondOfEqualPair', snapshot({})),
244
+ ],
245
+ [],
238
246
  NOW,
239
247
  throwingRandom,
240
248
  );
241
249
 
242
- expect(result.selected?.name).toBe('idle');
250
+ expect(result.selected?.name).toBe('firstOfEqualPair');
243
251
  });
244
252
 
245
253
  it('selects a sole eligible low-weight token without consulting random (no starvation)', () => {
@@ -283,3 +291,67 @@ describe('LiveSessionOauthTokenSelectUseCase selectionWeight', () => {
283
291
  expect(Math.abs(light / count - 1 / 3)).toBeLessThan(0.02);
284
292
  });
285
293
  });
294
+
295
+ describe('LiveSessionOauthTokenSelectUseCase seven day urgency weighting', () => {
296
+ const useCase = new LiveSessionOauthTokenSelectUseCase();
297
+
298
+ it('prefers the token whose seven day window resets soonest with capacity left over an idle token with a distant reset', () => {
299
+ const soonResetting = candidate(
300
+ 'soon-resetting',
301
+ snapshot({
302
+ sevenDayUtilization: 0.5,
303
+ sevenDayReset: NOW + 8 * HOUR,
304
+ }),
305
+ );
306
+ const distantReset = candidate(
307
+ 'distant-reset',
308
+ snapshot({
309
+ sevenDayUtilization: 0,
310
+ sevenDayReset: NOW + 160 * HOUR,
311
+ }),
312
+ );
313
+
314
+ const result = useCase.run(
315
+ [soonResetting, distantReset],
316
+ [
317
+ session('soon-resetting', 'session-a'),
318
+ session('soon-resetting', 'session-b'),
319
+ session('soon-resetting', 'session-c'),
320
+ ],
321
+ NOW,
322
+ () => 0.5,
323
+ );
324
+
325
+ expect(result.selected?.name).toBe('soon-resetting');
326
+ });
327
+
328
+ it('leaves the token with a distant reset selectable when the random draw falls in its share', () => {
329
+ const soonResetting = candidate(
330
+ 'soon-resetting',
331
+ snapshot({
332
+ sevenDayUtilization: 0.5,
333
+ sevenDayReset: NOW + 8 * HOUR,
334
+ }),
335
+ );
336
+ const distantReset = candidate(
337
+ 'distant-reset',
338
+ snapshot({
339
+ sevenDayUtilization: 0,
340
+ sevenDayReset: NOW + 160 * HOUR,
341
+ }),
342
+ );
343
+
344
+ const result = useCase.run(
345
+ [soonResetting, distantReset],
346
+ [
347
+ session('soon-resetting', 'session-a'),
348
+ session('soon-resetting', 'session-b'),
349
+ session('soon-resetting', 'session-c'),
350
+ ],
351
+ NOW,
352
+ () => 0.9,
353
+ );
354
+
355
+ expect(result.selected?.name).toBe('distant-reset');
356
+ });
357
+ });
@@ -4,6 +4,8 @@ import {
4
4
  OauthTokenSelectUseCase,
5
5
  SelectionRandom,
6
6
  selectWeightedCandidate,
7
+ selectionWeightOf,
8
+ sevenDayUrgencyFactor,
7
9
  } from './OauthTokenSelectUseCase';
8
10
 
9
11
  export type LiveSessionOauthTokenCandidateMetrics = {
@@ -72,7 +74,14 @@ export class LiveSessionOauthTokenSelectUseCase {
72
74
 
73
75
  const selected = selectWeightedCandidate(
74
76
  eligible,
75
- (entry) => entry.candidate,
77
+ (entry) =>
78
+ (selectionWeightOf(entry.candidate) *
79
+ sevenDayUrgencyFactor(
80
+ entry.metric.sevenDayFreeRatio,
81
+ entry.metric.sevenDayEndEpoch,
82
+ nowEpochSeconds,
83
+ )) /
84
+ (1 + entry.metric.liveSessionCount),
76
85
  deterministicBest,
77
86
  random,
78
87
  );
@@ -2,6 +2,8 @@ import {
2
2
  OauthTokenCandidate,
3
3
  OauthTokenSelectUseCase,
4
4
  OauthTokenWindowSnapshot,
5
+ SEVEN_DAY_WINDOW_HOURS,
6
+ sevenDayUrgencyFactor,
5
7
  } from './OauthTokenSelectUseCase';
6
8
 
7
9
  const NOW = 1_000_000;
@@ -53,6 +55,7 @@ describe('OauthTokenSelectUseCase', () => {
53
55
  ),
54
56
  ],
55
57
  NOW,
58
+ () => 0.5,
56
59
  );
57
60
 
58
61
  expect(result.selected?.name).toBe('soon');
@@ -176,6 +179,7 @@ describe('OauthTokenSelectUseCase', () => {
176
179
  ),
177
180
  ],
178
181
  NOW,
182
+ () => 0.5,
179
183
  );
180
184
 
181
185
  expect(result.selected?.name).toBe('activeSoon');
@@ -291,15 +295,15 @@ const withSelectionWeight = (
291
295
  describe('OauthTokenSelectUseCase selectionWeight', () => {
292
296
  const useCase = new OauthTokenSelectUseCase();
293
297
 
294
- it('keeps the deterministic soonest-7d selection and never consults random when weights are uniform', () => {
298
+ it('keeps the deterministic selection and never consults random when the urgency weights are identical', () => {
295
299
  const result = useCase.run(
296
300
  [
297
301
  candidate(
298
- 'far',
299
- snapshot({ sevenDayUtilization: 0.1, sevenDayReset: NOW + 6 * DAY }),
302
+ 'firstOfEqualPair',
303
+ snapshot({ sevenDayUtilization: 0.1, sevenDayReset: NOW + 2 * DAY }),
300
304
  ),
301
305
  candidate(
302
- 'soon',
306
+ 'secondOfEqualPair',
303
307
  snapshot({ sevenDayUtilization: 0.1, sevenDayReset: NOW + 2 * DAY }),
304
308
  ),
305
309
  ],
@@ -307,23 +311,26 @@ describe('OauthTokenSelectUseCase selectionWeight', () => {
307
311
  throwingRandom,
308
312
  );
309
313
 
310
- expect(result.selected?.name).toBe('soon');
314
+ expect(result.selected?.name).toBe('firstOfEqualPair');
311
315
  });
312
316
 
313
317
  it('treats an absent selectionWeight the same as weight 1 (uniform, deterministic)', () => {
314
318
  const result = useCase.run(
315
319
  [
316
320
  withSelectionWeight(
317
- candidate('far', snapshot({ sevenDayReset: NOW + 6 * DAY })),
321
+ candidate(
322
+ 'explicitWeightOne',
323
+ snapshot({ sevenDayReset: NOW + 2 * DAY }),
324
+ ),
318
325
  1,
319
326
  ),
320
- candidate('soon', snapshot({ sevenDayReset: NOW + 2 * DAY })),
327
+ candidate('absentWeight', snapshot({ sevenDayReset: NOW + 2 * DAY })),
321
328
  ],
322
329
  NOW,
323
330
  throwingRandom,
324
331
  );
325
332
 
326
- expect(result.selected?.name).toBe('soon');
333
+ expect(result.selected?.name).toBe('explicitWeightOne');
327
334
  });
328
335
 
329
336
  it('selects a sole eligible low-weight token without consulting random (no starvation)', () => {
@@ -431,3 +438,27 @@ describe('OauthTokenSelectUseCase selectionWeight', () => {
431
438
  expect(result.selected?.name).toBe('soon');
432
439
  });
433
440
  });
441
+
442
+ describe('sevenDayUrgencyFactor', () => {
443
+ const now = 1_000_000;
444
+
445
+ it('grows as the seven day window gets closer to its reset', () => {
446
+ const nearReset = sevenDayUrgencyFactor(0.5, now + 8 * 3600, now);
447
+ const farReset = sevenDayUrgencyFactor(0.5, now + 160 * 3600, now);
448
+
449
+ expect(nearReset).toBeGreaterThan(farReset);
450
+ });
451
+
452
+ it('scales with the free ratio of the seven day window', () => {
453
+ const halfFree = sevenDayUrgencyFactor(0.5, now + 24 * 3600, now);
454
+ const fullyFree = sevenDayUrgencyFactor(1, now + 24 * 3600, now);
455
+
456
+ expect(fullyFree).toBeCloseTo(halfFree * 2);
457
+ });
458
+
459
+ it('clips the remaining hours at one hour so an imminent reset does not produce an unbounded factor', () => {
460
+ const almostReset = sevenDayUrgencyFactor(1, now + 60, now);
461
+
462
+ expect(almostReset).toBe(SEVEN_DAY_WINDOW_HOURS);
463
+ });
464
+ });
@@ -44,18 +44,32 @@ const SEVEN_DAYS_IN_SECONDS = 7 * SECONDS_PER_DAY;
44
44
  export const FIVE_HOUR_MIN_FREE_RATIO = 0.25;
45
45
  export const SEVEN_DAY_MIN_FREE_RATIO = 0.03;
46
46
 
47
+ export const SEVEN_DAY_WINDOW_HOURS = 168;
48
+ export const MIN_HOURS_TO_RESET = 1;
49
+ const SECONDS_PER_HOUR = 3600;
50
+
51
+ export const sevenDayUrgencyFactor = (
52
+ sevenDayFreeRatio: number,
53
+ sevenDayEndEpoch: number,
54
+ nowEpochSeconds: number,
55
+ ): number => {
56
+ const hoursToReset = Math.max(
57
+ (sevenDayEndEpoch - nowEpochSeconds) / SECONDS_PER_HOUR,
58
+ MIN_HOURS_TO_RESET,
59
+ );
60
+ return sevenDayFreeRatio * (SEVEN_DAY_WINDOW_HOURS / hoursToReset);
61
+ };
62
+
47
63
  export const selectWeightedCandidate = <Entry>(
48
64
  eligible: Entry[],
49
- candidateOf: (entry: Entry) => OauthTokenCandidate,
65
+ weightOf: (entry: Entry) => number,
50
66
  deterministicBest: Entry,
51
67
  random: SelectionRandom,
52
68
  ): Entry => {
53
69
  if (eligible.length <= 1) {
54
70
  return deterministicBest;
55
71
  }
56
- const weights = eligible.map((entry) =>
57
- selectionWeightOf(candidateOf(entry)),
58
- );
72
+ const weights = eligible.map((entry) => Math.max(weightOf(entry), 0));
59
73
  const totalWeight = weights.reduce((sum, weight) => sum + weight, 0);
60
74
  const uniform = weights.every((weight) => weight === weights[0]);
61
75
  if (uniform || totalWeight <= 0) {
@@ -97,7 +111,13 @@ export class OauthTokenSelectUseCase {
97
111
 
98
112
  const selected = selectWeightedCandidate(
99
113
  eligible,
100
- (entry) => entry.candidate,
114
+ (entry) =>
115
+ selectionWeightOf(entry.candidate) *
116
+ sevenDayUrgencyFactor(
117
+ entry.metric.sevenDayFreeRatio,
118
+ entry.metric.sevenDayEndEpoch,
119
+ nowEpochSeconds,
120
+ ),
101
121
  deterministicBest,
102
122
  random,
103
123
  );
@@ -1,5 +1,6 @@
1
1
  import { ClaudeLiveSessionRepository } from '../../../domain/usecases/adapter-interfaces/ClaudeLiveSessionRepository';
2
2
  import { LiveSessionOauthTokenSelectUseCase } from '../../../domain/usecases/LiveSessionOauthTokenSelectUseCase';
3
+ import { SelectionRandom } from '../../../domain/usecases/OauthTokenSelectUseCase';
3
4
  export type LiveSessionOauthTokenSelectHandlerInput = {
4
5
  tokenListJsonPath: string | null;
5
6
  cacheDirectory: string | null;
@@ -13,7 +14,8 @@ export type LiveSessionOauthTokenSelectHandlerOutput = {
13
14
  export declare class LiveSessionOauthTokenSelectHandler {
14
15
  private readonly useCase;
15
16
  private readonly liveSessionRepository;
16
- constructor(useCase?: LiveSessionOauthTokenSelectUseCase, liveSessionRepository?: ClaudeLiveSessionRepository);
17
+ private readonly random;
18
+ constructor(useCase?: LiveSessionOauthTokenSelectUseCase, liveSessionRepository?: ClaudeLiveSessionRepository, random?: SelectionRandom);
17
19
  handle: (input: LiveSessionOauthTokenSelectHandlerInput) => LiveSessionOauthTokenSelectHandlerOutput;
18
20
  private formatDiagnostics;
19
21
  }
@@ -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;AAerE,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;gBADrB,OAAO,GAAE,kCAA6E,EACtF,qBAAqB,GAAE,2BAAmE;IAG7G,MAAM,GACJ,OAAO,uCAAuC,KAC7C,wCAAwC,CAkEzC;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,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,4 +1,4 @@
1
- import { OauthTokenSelectUseCase } from '../../../domain/usecases/OauthTokenSelectUseCase';
1
+ import { OauthTokenSelectUseCase, SelectionRandom } from '../../../domain/usecases/OauthTokenSelectUseCase';
2
2
  export type OauthTokenSelectHandlerInput = {
3
3
  tokenListJsonPath: string | null;
4
4
  cacheDirectory: string | null;
@@ -13,7 +13,8 @@ export declare const resolveTokenListJsonPath: (explicitPath: string | null) =>
13
13
  export declare const resolveCacheDirectory: (explicitDirectory: string | null) => string;
14
14
  export declare class OauthTokenSelectHandler {
15
15
  private readonly useCase;
16
- constructor(useCase?: OauthTokenSelectUseCase);
16
+ private readonly random;
17
+ constructor(useCase?: OauthTokenSelectUseCase, random?: SelectionRandom);
17
18
  handle: (input: OauthTokenSelectHandlerInput) => OauthTokenSelectHandlerOutput;
18
19
  private formatDiagnostics;
19
20
  }
@@ -1 +1 @@
1
- {"version":3,"file":"OauthTokenSelectHandler.d.ts","sourceRoot":"","sources":["../../../../src/adapter/entry-points/handlers/OauthTokenSelectHandler.ts"],"names":[],"mappings":"AAAA,OAAO,EAIL,uBAAuB,EACxB,MAAM,kDAAkD,CAAC;AAQ1D,MAAM,MAAM,4BAA4B,GAAG;IACzC,iBAAiB,EAAE,MAAM,GAAG,IAAI,CAAC;IACjC,cAAc,EAAE,MAAM,GAAG,IAAI,CAAC;IAC9B,eAAe,EAAE,MAAM,CAAC;CACzB,CAAC;AAEF,MAAM,MAAM,6BAA6B,GAAG;IAC1C,aAAa,EAAE,MAAM,GAAG,IAAI,CAAC;IAC7B,YAAY,EAAE,MAAM,GAAG,IAAI,CAAC;IAC5B,WAAW,EAAE,MAAM,EAAE,CAAC;CACvB,CAAC;AAKF,eAAO,MAAM,wBAAwB,GACnC,cAAc,MAAM,GAAG,IAAI,KAC1B,MAAM,GAAG,IASX,CAAC;AAEF,eAAO,MAAM,qBAAqB,GAChC,mBAAmB,MAAM,GAAG,IAAI,KAC/B,MASF,CAAC;AAEF,qBAAa,uBAAuB;IAEhC,OAAO,CAAC,QAAQ,CAAC,OAAO;gBAAP,OAAO,GAAE,uBAAuD;IAGnF,MAAM,GACJ,OAAO,4BAA4B,KAClC,6BAA6B,CA4D9B;IAEF,OAAO,CAAC,iBAAiB,CAuBvB;CACH"}
1
+ {"version":3,"file":"OauthTokenSelectHandler.d.ts","sourceRoot":"","sources":["../../../../src/adapter/entry-points/handlers/OauthTokenSelectHandler.ts"],"names":[],"mappings":"AAAA,OAAO,EAIL,uBAAuB,EACvB,eAAe,EAChB,MAAM,kDAAkD,CAAC;AAQ1D,MAAM,MAAM,4BAA4B,GAAG;IACzC,iBAAiB,EAAE,MAAM,GAAG,IAAI,CAAC;IACjC,cAAc,EAAE,MAAM,GAAG,IAAI,CAAC;IAC9B,eAAe,EAAE,MAAM,CAAC;CACzB,CAAC;AAEF,MAAM,MAAM,6BAA6B,GAAG;IAC1C,aAAa,EAAE,MAAM,GAAG,IAAI,CAAC;IAC7B,YAAY,EAAE,MAAM,GAAG,IAAI,CAAC;IAC5B,WAAW,EAAE,MAAM,EAAE,CAAC;CACvB,CAAC;AAKF,eAAO,MAAM,wBAAwB,GACnC,cAAc,MAAM,GAAG,IAAI,KAC1B,MAAM,GAAG,IASX,CAAC;AAEF,eAAO,MAAM,qBAAqB,GAChC,mBAAmB,MAAM,GAAG,IAAI,KAC/B,MASF,CAAC;AAEF,qBAAa,uBAAuB;IAEhC,OAAO,CAAC,QAAQ,CAAC,OAAO;IACxB,OAAO,CAAC,QAAQ,CAAC,MAAM;gBADN,OAAO,GAAE,uBAAuD,EAChE,MAAM,GAAE,eAA6B;IAGxD,MAAM,GACJ,OAAO,4BAA4B,KAClC,6BAA6B,CAgE9B;IAEF,OAAO,CAAC,iBAAiB,CAuBvB;CACH"}
@@ -23,6 +23,7 @@ export type NotifySilentTmuxSessionsParams = {
23
23
  hubTaskStatusCacheStateFilePath: string | null;
24
24
  hubTaskStatusCacheTtlSeconds: number;
25
25
  messageTemplates: SilentSessionMessageTemplates;
26
+ submitPushOutWaitMilliseconds?: number;
26
27
  now: Date;
27
28
  };
28
29
  export declare const notifySilentTmuxSessions: (params: NotifySilentTmuxSessionsParams) => Promise<void>;
@@ -1 +1 @@
1
- {"version":3,"file":"notifySilentTmuxSessions.d.ts","sourceRoot":"","sources":["../../../../src/adapter/entry-points/handlers/notifySilentTmuxSessions.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,kBAAkB,EAAE,MAAM,gEAAgE,CAAC;AAGpG,OAAO,EAAE,oBAAoB,EAAE,MAAM,kEAAkE,CAAC;AACxG,OAAO,EAEL,qBAAqB,EAQtB,MAAM,0DAA0D,CAAC;AAgBlE,OAAO,EAEL,6BAA6B,EAC9B,MAAM,6DAA6D,CAAC;AAKrE,MAAM,MAAM,8BAA8B,GAAG;IAC3C,OAAO,EAAE,OAAO,CAAC;IACjB,kBAAkB,EAAE,kBAAkB,CAAC;IACvC,oBAAoB,CAAC,EAAE,oBAAoB,CAAC;IAC5C,eAAe,EAAE,MAAM,GAAG,IAAI,CAAC;IAC/B,2BAA2B,EAAE,MAAM,GAAG,IAAI,CAAC;IAC3C,2BAA2B,EAAE,MAAM,GAAG,IAAI,CAAC;IAC3C,+BAA+B,EAAE,MAAM,GAAG,IAAI,CAAC;IAC/C,4BAA4B,EAAE,MAAM,GAAG,IAAI,CAAC;IAC5C,0BAA0B,EAAE,MAAM,CAAC;IACnC,+BAA+B,EAAE,MAAM,CAAC;IACxC,8BAA8B,EAAE,MAAM,CAAC;IACvC,+BAA+B,EAAE,MAAM,CAAC;IACxC,cAAc,EAAE,MAAM,CAAC;IACvB,qCAAqC,EAAE,MAAM,CAAC;IAC9C,8BAA8B,EAAE,MAAM,GAAG,IAAI,CAAC;IAC9C,mBAAmB,EAAE,MAAM,GAAG,IAAI,CAAC;IACnC,qBAAqB,EAAE,qBAAqB,GAAG,IAAI,CAAC;IACpD,+BAA+B,EAAE,MAAM,GAAG,IAAI,CAAC;IAC/C,4BAA4B,EAAE,MAAM,CAAC;IACrC,gBAAgB,EAAE,6BAA6B,CAAC;IAChD,GAAG,EAAE,IAAI,CAAC;CACX,CAAC;AAuCF,eAAO,MAAM,wBAAwB,GACnC,QAAQ,8BAA8B,KACrC,OAAO,CAAC,IAAI,CA6Ed,CAAC;AAEF,eAAO,MAAM,0CAA0C;;;;;;;;CAS7C,CAAC"}
1
+ {"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;AAoBlE,OAAO,EAEL,6BAA6B,EAC9B,MAAM,6DAA6D,CAAC;AAKrE,MAAM,MAAM,8BAA8B,GAAG;IAC3C,OAAO,EAAE,OAAO,CAAC;IACjB,kBAAkB,EAAE,kBAAkB,CAAC;IACvC,oBAAoB,CAAC,EAAE,oBAAoB,CAAC;IAC5C,eAAe,EAAE,MAAM,GAAG,IAAI,CAAC;IAC/B,2BAA2B,EAAE,MAAM,GAAG,IAAI,CAAC;IAC3C,2BAA2B,EAAE,MAAM,GAAG,IAAI,CAAC;IAC3C,+BAA+B,EAAE,MAAM,GAAG,IAAI,CAAC;IAC/C,4BAA4B,EAAE,MAAM,GAAG,IAAI,CAAC;IAC5C,0BAA0B,EAAE,MAAM,CAAC;IACnC,+BAA+B,EAAE,MAAM,CAAC;IACxC,8BAA8B,EAAE,MAAM,CAAC;IACvC,+BAA+B,EAAE,MAAM,CAAC;IACxC,cAAc,EAAE,MAAM,CAAC;IACvB,qCAAqC,EAAE,MAAM,CAAC;IAC9C,8BAA8B,EAAE,MAAM,GAAG,IAAI,CAAC;IAC9C,mBAAmB,EAAE,MAAM,GAAG,IAAI,CAAC;IACnC,qBAAqB,EAAE,qBAAqB,GAAG,IAAI,CAAC;IACpD,+BAA+B,EAAE,MAAM,GAAG,IAAI,CAAC;IAC/C,4BAA4B,EAAE,MAAM,CAAC;IACrC,gBAAgB,EAAE,6BAA6B,CAAC;IAChD,6BAA6B,CAAC,EAAE,MAAM,CAAC;IACvC,GAAG,EAAE,IAAI,CAAC;CACX,CAAC;AAuCF,eAAO,MAAM,wBAAwB,GACnC,QAAQ,8BAA8B,KACrC,OAAO,CAAC,IAAI,CAoFd,CAAC;AAEF,eAAO,MAAM,0CAA0C;;;;;;;;CAS7C,CAAC"}
@@ -1 +1 @@
1
- {"version":3,"file":"resetDegeneratedTmuxSessions.d.ts","sourceRoot":"","sources":["../../../../src/adapter/entry-points/handlers/resetDegeneratedTmuxSessions.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,kBAAkB,EAAE,MAAM,gEAAgE,CAAC;AACpG,OAAO,EAAE,oBAAoB,EAAE,MAAM,kEAAkE,CAAC;AAgBxG,MAAM,MAAM,kCAAkC,GAAG;IAC/C,OAAO,EAAE,OAAO,CAAC;IACjB,kBAAkB,EAAE,kBAAkB,CAAC;IACvC,oBAAoB,CAAC,EAAE,oBAAoB,CAAC;IAC5C,cAAc,EAAE,MAAM,CAAC;IACvB,YAAY,EAAE,MAAM,CAAC;IACrB,eAAe,EAAE,MAAM,CAAC;IACxB,qBAAqB,EAAE,MAAM,GAAG,IAAI,CAAC;IACrC,GAAG,EAAE,IAAI,CAAC;CACX,CAAC;AAEF,eAAO,MAAM,4BAA4B,GACvC,QAAQ,kCAAkC,KACzC,OAAO,CAAC,IAAI,CAkCd,CAAC;AAEF,eAAO,MAAM,8CAA8C;;;;CAIjD,CAAC"}
1
+ {"version":3,"file":"resetDegeneratedTmuxSessions.d.ts","sourceRoot":"","sources":["../../../../src/adapter/entry-points/handlers/resetDegeneratedTmuxSessions.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,kBAAkB,EAAE,MAAM,gEAAgE,CAAC;AACpG,OAAO,EAAE,oBAAoB,EAAE,MAAM,kEAAkE,CAAC;AAgBxG,MAAM,MAAM,kCAAkC,GAAG;IAC/C,OAAO,EAAE,OAAO,CAAC;IACjB,kBAAkB,EAAE,kBAAkB,CAAC;IACvC,oBAAoB,CAAC,EAAE,oBAAoB,CAAC;IAC5C,cAAc,EAAE,MAAM,CAAC;IACvB,YAAY,EAAE,MAAM,CAAC;IACrB,eAAe,EAAE,MAAM,CAAC;IACxB,qBAAqB,EAAE,MAAM,GAAG,IAAI,CAAC;IACrC,GAAG,EAAE,IAAI,CAAC;CACX,CAAC;AAEF,eAAO,MAAM,4BAA4B,GACvC,QAAQ,kCAAkC,KACzC,OAAO,CAAC,IAAI,CAqCd,CAAC;AAEF,eAAO,MAAM,8CAA8C;;;;CAIjD,CAAC"}
@@ -6,6 +6,7 @@ export declare class FileSystemSubAgentLivenessResolver implements SubAgentLiven
6
6
  sessionName: string;
7
7
  mainTranscriptPath: string | null;
8
8
  }) => Promise<Set<string> | null>;
9
+ private hasReadableRuntimeRootDirectory;
9
10
  private resolveRunningSubAgentsFilePath;
10
11
  }
11
12
  //# sourceMappingURL=FileSystemSubAgentLivenessResolver.d.ts.map
@@ -1 +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
+ {"version":3,"file":"FileSystemSubAgentLivenessResolver.d.ts","sourceRoot":"","sources":["../../../src/adapter/repositories/FileSystemSubAgentLivenessResolver.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,wBAAwB,EAAE,MAAM,mEAAmE,CAAC;AAU7G,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,CAwB7B;IAEF,OAAO,CAAC,+BAA+B,CASrC;IAEF,OAAO,CAAC,+BAA+B,CAoBrC;CACH"}
@@ -1,8 +1,21 @@
1
1
  import { SilentSessionNotificationRepository } from '../../domain/usecases/adapter-interfaces/SilentSessionNotificationRepository';
2
2
  import { LocalCommandRunner } from '../../domain/usecases/adapter-interfaces/LocalCommandRunner';
3
+ import { Sleeper } from '../../domain/usecases/adapter-interfaces/Sleeper';
4
+ export declare const DEFAULT_SUBMIT_PUSH_OUT_ATTEMPT_LIMIT = 3;
5
+ export declare const DEFAULT_SUBMIT_PUSH_OUT_WAIT_MILLISECONDS = 2500;
6
+ type InputBoxState = 'holdsMessage' | 'clearedMessage' | 'unreadable';
7
+ export declare const extractTmuxInputBoxContent: (paneText: string) => string | null;
8
+ export declare const resolveInputBoxStateFromPane: (paneText: string, singleLineMessage: string) => InputBoxState;
3
9
  export declare class TmuxSilentSessionNotificationRepository implements SilentSessionNotificationRepository {
4
10
  private readonly localCommandRunner;
5
- constructor(localCommandRunner: LocalCommandRunner);
11
+ private readonly sleeper;
12
+ private readonly submitPushOutAttemptLimit;
13
+ private readonly submitPushOutWaitMilliseconds;
14
+ constructor(localCommandRunner: LocalCommandRunner, sleeper: Sleeper, submitPushOutAttemptLimit?: number, submitPushOutWaitMilliseconds?: number);
6
15
  sendSelfCheckNotification: (sessionName: string, message: string) => Promise<void>;
16
+ private submitInputBox;
17
+ private pushOutUnsubmittedMessage;
18
+ private readInputBoxState;
7
19
  }
20
+ export {};
8
21
  //# sourceMappingURL=TmuxSilentSessionNotificationRepository.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"TmuxSilentSessionNotificationRepository.d.ts","sourceRoot":"","sources":["../../../src/adapter/repositories/TmuxSilentSessionNotificationRepository.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,mCAAmC,EAAE,MAAM,8EAA8E,CAAC;AACnI,OAAO,EAAE,kBAAkB,EAAE,MAAM,6DAA6D,CAAC;AAKjG,qBAAa,uCAAwC,YAAW,mCAAmC;IACrF,OAAO,CAAC,QAAQ,CAAC,kBAAkB;gBAAlB,kBAAkB,EAAE,kBAAkB;IAEnE,yBAAyB,GACvB,aAAa,MAAM,EACnB,SAAS,MAAM,KACd,OAAO,CAAC,IAAI,CAAC,CA8Bd;CACH"}
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;AACjG,OAAO,EAAE,OAAO,EAAE,MAAM,kDAAkD,CAAC;AAK3E,eAAO,MAAM,qCAAqC,IAAI,CAAC;AACvD,eAAO,MAAM,yCAAyC,OAAO,CAAC;AAM9D,KAAK,aAAa,GAAG,cAAc,GAAG,gBAAgB,GAAG,YAAY,CAAC;AAItE,eAAO,MAAM,0BAA0B,GAAI,UAAU,MAAM,KAAG,MAAM,GAAG,IAgBtE,CAAC;AAEF,eAAO,MAAM,4BAA4B,GACvC,UAAU,MAAM,EAChB,mBAAmB,MAAM,KACxB,aAeF,CAAC;AAEF,qBAAa,uCAAwC,YAAW,mCAAmC;IAE/F,OAAO,CAAC,QAAQ,CAAC,kBAAkB;IACnC,OAAO,CAAC,QAAQ,CAAC,OAAO;IACxB,OAAO,CAAC,QAAQ,CAAC,yBAAyB;IAC1C,OAAO,CAAC,QAAQ,CAAC,6BAA6B;gBAH7B,kBAAkB,EAAE,kBAAkB,EACtC,OAAO,EAAE,OAAO,EAChB,yBAAyB,GAAE,MAA8C,EACzE,6BAA6B,GAAE,MAAkD;IAGpG,yBAAyB,GACvB,aAAa,MAAM,EACnB,SAAS,MAAM,KACd,OAAO,CAAC,IAAI,CAAC,CAmBd;IAEF,OAAO,CAAC,cAAc,CAcpB;IAEF,OAAO,CAAC,yBAAyB,CA4B/B;IAEF,OAAO,CAAC,iBAAiB,CAsBvB;CACH"}
@@ -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,EAEhB,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,CA+ClC;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,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"}
@@ -30,7 +30,10 @@ export type OauthTokenSelectResult = {
30
30
  };
31
31
  export declare const FIVE_HOUR_MIN_FREE_RATIO = 0.25;
32
32
  export declare const SEVEN_DAY_MIN_FREE_RATIO = 0.03;
33
- export declare const selectWeightedCandidate: <Entry>(eligible: Entry[], candidateOf: (entry: Entry) => OauthTokenCandidate, deterministicBest: Entry, random: SelectionRandom) => Entry;
33
+ export declare const SEVEN_DAY_WINDOW_HOURS = 168;
34
+ export declare const MIN_HOURS_TO_RESET = 1;
35
+ export declare const sevenDayUrgencyFactor: (sevenDayFreeRatio: number, sevenDayEndEpoch: number, nowEpochSeconds: number) => number;
36
+ export declare const selectWeightedCandidate: <Entry>(eligible: Entry[], weightOf: (entry: Entry) => number, deterministicBest: Entry, random: SelectionRandom) => Entry;
34
37
  export declare class OauthTokenSelectUseCase {
35
38
  run: (candidates: OauthTokenCandidate[], nowEpochSeconds: number, random?: SelectionRandom) => OauthTokenSelectResult;
36
39
  private evaluate;
@@ -1 +1 @@
1
- {"version":3,"file":"OauthTokenSelectUseCase.d.ts","sourceRoot":"","sources":["../../../src/domain/usecases/OauthTokenSelectUseCase.ts"],"names":[],"mappings":"AAAA,MAAM,MAAM,wBAAwB,GAAG;IACrC,mBAAmB,EAAE,MAAM,CAAC;IAC5B,aAAa,EAAE,MAAM,CAAC;IACtB,mBAAmB,EAAE,MAAM,CAAC;IAC5B,aAAa,EAAE,MAAM,CAAC;CACvB,CAAC;AAEF,MAAM,MAAM,mBAAmB,GAAG;IAChC,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,EAAE,MAAM,CAAC;IACd,QAAQ,EAAE,wBAAwB,GAAG,IAAI,CAAC;IAC1C,oBAAoB,EAAE,OAAO,CAAC;IAC9B,eAAe,EAAE,OAAO,CAAC;IACzB,aAAa,EAAE,OAAO,CAAC;IACvB,eAAe,CAAC,EAAE,MAAM,CAAC;CAC1B,CAAC;AAEF,MAAM,MAAM,eAAe,GAAG,MAAM,MAAM,CAAC;AAE3C,eAAO,MAAM,wBAAwB,IAAI,CAAC;AAE1C,eAAO,MAAM,iBAAiB,GAAI,WAAW,mBAAmB,KAAG,MAGlE,CAAC;AAEF,MAAM,MAAM,0BAA0B,GAAG;IACvC,IAAI,EAAE,MAAM,CAAC;IACb,iBAAiB,EAAE,MAAM,CAAC;IAC1B,iBAAiB,EAAE,MAAM,CAAC;IAC1B,gBAAgB,EAAE,MAAM,CAAC;IACzB,QAAQ,EAAE,OAAO,CAAC;IAClB,eAAe,EAAE,MAAM,GAAG,IAAI,CAAC;CAChC,CAAC;AAEF,MAAM,MAAM,sBAAsB,GAAG;IACnC,QAAQ,EAAE,mBAAmB,GAAG,IAAI,CAAC;IACrC,OAAO,EAAE,0BAA0B,EAAE,CAAC;CACvC,CAAC;AAKF,eAAO,MAAM,wBAAwB,OAAO,CAAC;AAC7C,eAAO,MAAM,wBAAwB,OAAO,CAAC;AAE7C,eAAO,MAAM,uBAAuB,GAAI,KAAK,EAC3C,UAAU,KAAK,EAAE,EACjB,aAAa,CAAC,KAAK,EAAE,KAAK,KAAK,mBAAmB,EAClD,mBAAmB,KAAK,EACxB,QAAQ,eAAe,KACtB,KAoBF,CAAC;AAEF,qBAAa,uBAAuB;IAClC,GAAG,GACD,YAAY,mBAAmB,EAAE,EACjC,iBAAiB,MAAM,EACvB,SAAQ,eAA6B,KACpC,sBAAsB,CA2BvB;IAEF,OAAO,CAAC,QAAQ,CAiCd;IAEF,OAAO,CAAC,eAAe,CAuBrB;IAEF,OAAO,CAAC,iBAAiB,CAWvB;IAEF,OAAO,CAAC,iBAAiB,CAWvB;IAEF,OAAO,CAAC,gBAAgB,CActB;IAEF,OAAO,CAAC,aAAa,CAGwC;IAE7D,OAAO,CAAC,wBAAwB,CAG9B;IAEF,OAAO,CAAC,SAAS,CAAsD;CACxE"}
1
+ {"version":3,"file":"OauthTokenSelectUseCase.d.ts","sourceRoot":"","sources":["../../../src/domain/usecases/OauthTokenSelectUseCase.ts"],"names":[],"mappings":"AAAA,MAAM,MAAM,wBAAwB,GAAG;IACrC,mBAAmB,EAAE,MAAM,CAAC;IAC5B,aAAa,EAAE,MAAM,CAAC;IACtB,mBAAmB,EAAE,MAAM,CAAC;IAC5B,aAAa,EAAE,MAAM,CAAC;CACvB,CAAC;AAEF,MAAM,MAAM,mBAAmB,GAAG;IAChC,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,EAAE,MAAM,CAAC;IACd,QAAQ,EAAE,wBAAwB,GAAG,IAAI,CAAC;IAC1C,oBAAoB,EAAE,OAAO,CAAC;IAC9B,eAAe,EAAE,OAAO,CAAC;IACzB,aAAa,EAAE,OAAO,CAAC;IACvB,eAAe,CAAC,EAAE,MAAM,CAAC;CAC1B,CAAC;AAEF,MAAM,MAAM,eAAe,GAAG,MAAM,MAAM,CAAC;AAE3C,eAAO,MAAM,wBAAwB,IAAI,CAAC;AAE1C,eAAO,MAAM,iBAAiB,GAAI,WAAW,mBAAmB,KAAG,MAGlE,CAAC;AAEF,MAAM,MAAM,0BAA0B,GAAG;IACvC,IAAI,EAAE,MAAM,CAAC;IACb,iBAAiB,EAAE,MAAM,CAAC;IAC1B,iBAAiB,EAAE,MAAM,CAAC;IAC1B,gBAAgB,EAAE,MAAM,CAAC;IACzB,QAAQ,EAAE,OAAO,CAAC;IAClB,eAAe,EAAE,MAAM,GAAG,IAAI,CAAC;CAChC,CAAC;AAEF,MAAM,MAAM,sBAAsB,GAAG;IACnC,QAAQ,EAAE,mBAAmB,GAAG,IAAI,CAAC;IACrC,OAAO,EAAE,0BAA0B,EAAE,CAAC;CACvC,CAAC;AAKF,eAAO,MAAM,wBAAwB,OAAO,CAAC;AAC7C,eAAO,MAAM,wBAAwB,OAAO,CAAC;AAE7C,eAAO,MAAM,sBAAsB,MAAM,CAAC;AAC1C,eAAO,MAAM,kBAAkB,IAAI,CAAC;AAGpC,eAAO,MAAM,qBAAqB,GAChC,mBAAmB,MAAM,EACzB,kBAAkB,MAAM,EACxB,iBAAiB,MAAM,KACtB,MAMF,CAAC;AAEF,eAAO,MAAM,uBAAuB,GAAI,KAAK,EAC3C,UAAU,KAAK,EAAE,EACjB,UAAU,CAAC,KAAK,EAAE,KAAK,KAAK,MAAM,EAClC,mBAAmB,KAAK,EACxB,QAAQ,eAAe,KACtB,KAkBF,CAAC;AAEF,qBAAa,uBAAuB;IAClC,GAAG,GACD,YAAY,mBAAmB,EAAE,EACjC,iBAAiB,MAAM,EACvB,SAAQ,eAA6B,KACpC,sBAAsB,CAiCvB;IAEF,OAAO,CAAC,QAAQ,CAiCd;IAEF,OAAO,CAAC,eAAe,CAuBrB;IAEF,OAAO,CAAC,iBAAiB,CAWvB;IAEF,OAAO,CAAC,iBAAiB,CAWvB;IAEF,OAAO,CAAC,gBAAgB,CActB;IAEF,OAAO,CAAC,aAAa,CAGwC;IAE7D,OAAO,CAAC,wBAAwB,CAG9B;IAEF,OAAO,CAAC,SAAS,CAAsD;CACxE"}