github-issue-tower-defence-management 1.116.2 → 1.116.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.github/workflows/commit-lint.yml +1 -1
- package/.github/workflows/configs/commitlint.config.cjs +32 -0
- package/.github/workflows/umino-project.yml +9 -3
- package/CHANGELOG.md +14 -0
- package/README.md +2 -2
- package/bin/adapter/repositories/ProcClaudeLiveSessionRepository.js +18 -6
- package/bin/adapter/repositories/ProcClaudeLiveSessionRepository.js.map +1 -1
- package/bin/adapter/repositories/issue/ApiV3CheerioRestIssueRepository.js +74 -49
- package/bin/adapter/repositories/issue/ApiV3CheerioRestIssueRepository.js.map +1 -1
- package/bin/adapter/repositories/issue/githubRateLimitRetry.js +77 -0
- package/bin/adapter/repositories/issue/githubRateLimitRetry.js.map +1 -0
- package/bin/domain/usecases/LiveSessionOauthTokenSelectUseCase.js +6 -6
- package/bin/domain/usecases/LiveSessionOauthTokenSelectUseCase.js.map +1 -1
- package/bin/domain/usecases/OauthTokenSelectUseCase.js +1 -1
- package/bin/domain/usecases/OauthTokenSelectUseCase.js.map +1 -1
- package/package.json +1 -1
- package/renovate.json +1 -0
- package/src/adapter/entry-points/handlers/LiveSessionOauthTokenSelectHandler.test.ts +11 -11
- package/src/adapter/repositories/ProcClaudeLiveSessionRepository.test.ts +69 -7
- package/src/adapter/repositories/ProcClaudeLiveSessionRepository.ts +19 -8
- package/src/adapter/repositories/issue/ApiV3CheerioRestIssueRepository.test.ts +111 -0
- package/src/adapter/repositories/issue/ApiV3CheerioRestIssueRepository.ts +243 -193
- package/src/adapter/repositories/issue/githubRateLimitRetry.test.ts +167 -0
- package/src/adapter/repositories/issue/githubRateLimitRetry.ts +105 -0
- package/src/domain/usecases/LiveSessionOauthTokenSelectUseCase.test.ts +25 -3
- package/src/domain/usecases/LiveSessionOauthTokenSelectUseCase.ts +7 -7
- package/src/domain/usecases/OauthTokenSelectUseCase.test.ts +13 -4
- package/src/domain/usecases/OauthTokenSelectUseCase.ts +1 -1
- package/src/domain/usecases/adapter-interfaces/ClaudeLiveSessionRepository.ts +1 -1
- package/types/adapter/repositories/ProcClaudeLiveSessionRepository.d.ts +1 -0
- package/types/adapter/repositories/ProcClaudeLiveSessionRepository.d.ts.map +1 -1
- package/types/adapter/repositories/issue/ApiV3CheerioRestIssueRepository.d.ts +4 -1
- package/types/adapter/repositories/issue/ApiV3CheerioRestIssueRepository.d.ts.map +1 -1
- package/types/adapter/repositories/issue/githubRateLimitRetry.d.ts +10 -0
- package/types/adapter/repositories/issue/githubRateLimitRetry.d.ts.map +1 -0
- package/types/domain/usecases/OauthTokenSelectUseCase.d.ts +1 -1
- package/types/domain/usecases/OauthTokenSelectUseCase.d.ts.map +1 -1
- package/types/domain/usecases/adapter-interfaces/ClaudeLiveSessionRepository.d.ts +1 -1
- package/types/domain/usecases/adapter-interfaces/ClaudeLiveSessionRepository.d.ts.map +1 -1
|
@@ -0,0 +1,167 @@
|
|
|
1
|
+
import {
|
|
2
|
+
hasRateLimitSignals,
|
|
3
|
+
computeBoundedBackoffMs,
|
|
4
|
+
computeRateLimitResetIso,
|
|
5
|
+
fetchWithGitHubRateLimitRetry,
|
|
6
|
+
RATE_LIMIT_MAX_RETRIES,
|
|
7
|
+
RATE_LIMIT_TOTAL_BACKOFF_CAP_MS,
|
|
8
|
+
} from './githubRateLimitRetry';
|
|
9
|
+
|
|
10
|
+
describe('githubRateLimitRetry', () => {
|
|
11
|
+
describe('hasRateLimitSignals', () => {
|
|
12
|
+
it('detects a 403 with x-ratelimit-remaining: 0', () => {
|
|
13
|
+
const headers = new Headers({ 'x-ratelimit-remaining': '0' });
|
|
14
|
+
expect(hasRateLimitSignals(403, headers, '')).toBe(true);
|
|
15
|
+
});
|
|
16
|
+
|
|
17
|
+
it('detects a 429 with a Retry-After header', () => {
|
|
18
|
+
const headers = new Headers({ 'retry-after': '2' });
|
|
19
|
+
expect(hasRateLimitSignals(429, headers, '')).toBe(true);
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
it('detects a secondary rate limit message in the body', () => {
|
|
23
|
+
const headers = new Headers();
|
|
24
|
+
expect(
|
|
25
|
+
hasRateLimitSignals(
|
|
26
|
+
403,
|
|
27
|
+
headers,
|
|
28
|
+
JSON.stringify({
|
|
29
|
+
message: 'You have exceeded a secondary rate limit',
|
|
30
|
+
}),
|
|
31
|
+
),
|
|
32
|
+
).toBe(true);
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
it('does not flag a 403 without any rate-limit signal', () => {
|
|
36
|
+
const headers = new Headers({ 'x-ratelimit-remaining': '4999' });
|
|
37
|
+
expect(
|
|
38
|
+
hasRateLimitSignals(
|
|
39
|
+
403,
|
|
40
|
+
headers,
|
|
41
|
+
JSON.stringify({ message: 'Resource not accessible by integration' }),
|
|
42
|
+
),
|
|
43
|
+
).toBe(false);
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
it('does not flag non-403/429 statuses', () => {
|
|
47
|
+
const headers = new Headers({ 'retry-after': '2' });
|
|
48
|
+
expect(hasRateLimitSignals(404, headers, 'rate limit')).toBe(false);
|
|
49
|
+
});
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
describe('computeBoundedBackoffMs', () => {
|
|
53
|
+
it('grows exponentially from the base when no Retry-After is present', () => {
|
|
54
|
+
const headers = new Headers();
|
|
55
|
+
expect(computeBoundedBackoffMs(headers, 0, 0)).toBe(250);
|
|
56
|
+
expect(computeBoundedBackoffMs(headers, 1, 0)).toBe(500);
|
|
57
|
+
expect(computeBoundedBackoffMs(headers, 2, 0)).toBe(1000);
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
it('honors Retry-After only within the remaining budget', () => {
|
|
61
|
+
const headers = new Headers({ 'retry-after': '2' });
|
|
62
|
+
expect(computeBoundedBackoffMs(headers, 0, 0)).toBe(2000);
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
it('caps the wait so the total never exceeds the budget', () => {
|
|
66
|
+
const headers = new Headers({ 'retry-after': '3600' });
|
|
67
|
+
expect(computeBoundedBackoffMs(headers, 0, 4000)).toBe(
|
|
68
|
+
RATE_LIMIT_TOTAL_BACKOFF_CAP_MS - 4000,
|
|
69
|
+
);
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
it('returns 0 when the backoff budget is exhausted', () => {
|
|
73
|
+
const headers = new Headers({ 'retry-after': '3600' });
|
|
74
|
+
expect(
|
|
75
|
+
computeBoundedBackoffMs(headers, 0, RATE_LIMIT_TOTAL_BACKOFF_CAP_MS),
|
|
76
|
+
).toBe(0);
|
|
77
|
+
});
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
describe('computeRateLimitResetIso', () => {
|
|
81
|
+
it('converts the x-ratelimit-reset epoch seconds to an ISO timestamp', () => {
|
|
82
|
+
const headers = new Headers({ 'x-ratelimit-reset': '1700000000' });
|
|
83
|
+
expect(computeRateLimitResetIso(headers)).toBe(
|
|
84
|
+
new Date(1700000000 * 1000).toISOString(),
|
|
85
|
+
);
|
|
86
|
+
});
|
|
87
|
+
|
|
88
|
+
it('returns null when no reset header is present', () => {
|
|
89
|
+
expect(computeRateLimitResetIso(new Headers())).toBeNull();
|
|
90
|
+
});
|
|
91
|
+
});
|
|
92
|
+
|
|
93
|
+
describe('fetchWithGitHubRateLimitRetry', () => {
|
|
94
|
+
const rateLimitResponse = (): Response =>
|
|
95
|
+
new Response(JSON.stringify({ message: 'API rate limit exceeded' }), {
|
|
96
|
+
status: 403,
|
|
97
|
+
headers: { 'x-ratelimit-remaining': '0' },
|
|
98
|
+
});
|
|
99
|
+
|
|
100
|
+
it('retries a rate-limit response and resolves with the eventual success', async () => {
|
|
101
|
+
const sleep = jest.fn().mockResolvedValue(undefined);
|
|
102
|
+
const request = jest
|
|
103
|
+
.fn<Promise<Response>, []>()
|
|
104
|
+
.mockResolvedValueOnce(rateLimitResponse())
|
|
105
|
+
.mockResolvedValueOnce(
|
|
106
|
+
new Response(JSON.stringify({ ok: true }), { status: 200 }),
|
|
107
|
+
);
|
|
108
|
+
|
|
109
|
+
const response = await fetchWithGitHubRateLimitRetry(request, sleep);
|
|
110
|
+
|
|
111
|
+
expect(response.status).toBe(200);
|
|
112
|
+
expect(request).toHaveBeenCalledTimes(2);
|
|
113
|
+
expect(sleep).toHaveBeenCalledTimes(1);
|
|
114
|
+
});
|
|
115
|
+
|
|
116
|
+
it('does not retry a genuine permission 403 without rate-limit signals', async () => {
|
|
117
|
+
const sleep = jest.fn().mockResolvedValue(undefined);
|
|
118
|
+
const request = jest.fn<Promise<Response>, []>().mockResolvedValue(
|
|
119
|
+
new Response(
|
|
120
|
+
JSON.stringify({
|
|
121
|
+
message: 'Resource not accessible by integration',
|
|
122
|
+
}),
|
|
123
|
+
{ status: 403, headers: { 'x-ratelimit-remaining': '4999' } },
|
|
124
|
+
),
|
|
125
|
+
);
|
|
126
|
+
|
|
127
|
+
const response = await fetchWithGitHubRateLimitRetry(request, sleep);
|
|
128
|
+
|
|
129
|
+
expect(response.status).toBe(403);
|
|
130
|
+
expect(request).toHaveBeenCalledTimes(1);
|
|
131
|
+
expect(sleep).not.toHaveBeenCalled();
|
|
132
|
+
});
|
|
133
|
+
|
|
134
|
+
it('stops retrying after the bounded retry cap and never blocks past the budget', async () => {
|
|
135
|
+
const sleeps: number[] = [];
|
|
136
|
+
const sleep = jest.fn(async (milliseconds: number) => {
|
|
137
|
+
sleeps.push(milliseconds);
|
|
138
|
+
});
|
|
139
|
+
const request = jest
|
|
140
|
+
.fn<Promise<Response>, []>()
|
|
141
|
+
.mockResolvedValue(rateLimitResponse());
|
|
142
|
+
|
|
143
|
+
const response = await fetchWithGitHubRateLimitRetry(request, sleep);
|
|
144
|
+
|
|
145
|
+
expect(response.status).toBe(403);
|
|
146
|
+
expect(request).toHaveBeenCalledTimes(RATE_LIMIT_MAX_RETRIES + 1);
|
|
147
|
+
expect(sleep).toHaveBeenCalledTimes(RATE_LIMIT_MAX_RETRIES);
|
|
148
|
+
const totalSlept = sleeps.reduce((sum, value) => sum + value, 0);
|
|
149
|
+
expect(totalSlept).toBeLessThanOrEqual(RATE_LIMIT_TOTAL_BACKOFF_CAP_MS);
|
|
150
|
+
});
|
|
151
|
+
|
|
152
|
+
it('preserves the response body for the caller after inspecting it for signals', async () => {
|
|
153
|
+
const sleep = jest.fn().mockResolvedValue(undefined);
|
|
154
|
+
const request = jest
|
|
155
|
+
.fn<Promise<Response>, []>()
|
|
156
|
+
.mockResolvedValueOnce(rateLimitResponse())
|
|
157
|
+
.mockResolvedValueOnce(
|
|
158
|
+
new Response(JSON.stringify({ value: 'final' }), { status: 200 }),
|
|
159
|
+
);
|
|
160
|
+
|
|
161
|
+
const response = await fetchWithGitHubRateLimitRetry(request, sleep);
|
|
162
|
+
const body: unknown = await response.json();
|
|
163
|
+
|
|
164
|
+
expect(body).toEqual({ value: 'final' });
|
|
165
|
+
});
|
|
166
|
+
});
|
|
167
|
+
});
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
export const RATE_LIMIT_MAX_RETRIES = 3;
|
|
2
|
+
export const RATE_LIMIT_TOTAL_BACKOFF_CAP_MS = 5000;
|
|
3
|
+
export const RATE_LIMIT_BASE_BACKOFF_MS = 250;
|
|
4
|
+
|
|
5
|
+
const RATE_LIMIT_MESSAGE_PATTERN = /rate limit|secondary rate limit|abuse/i;
|
|
6
|
+
|
|
7
|
+
export type Sleep = (milliseconds: number) => Promise<void>;
|
|
8
|
+
|
|
9
|
+
export const realSleep: Sleep = (milliseconds) =>
|
|
10
|
+
new Promise((resolve) => setTimeout(resolve, milliseconds));
|
|
11
|
+
|
|
12
|
+
const parseNonNegativeIntegerHeader = (value: string | null): number | null => {
|
|
13
|
+
if (value === null) {
|
|
14
|
+
return null;
|
|
15
|
+
}
|
|
16
|
+
const trimmed = value.trim();
|
|
17
|
+
if (!/^\d+$/.test(trimmed)) {
|
|
18
|
+
return null;
|
|
19
|
+
}
|
|
20
|
+
const parsed = Number.parseInt(trimmed, 10);
|
|
21
|
+
return Number.isFinite(parsed) ? parsed : null;
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
export const hasRateLimitSignals = (
|
|
25
|
+
status: number,
|
|
26
|
+
headers: Headers,
|
|
27
|
+
bodyText: string,
|
|
28
|
+
): boolean => {
|
|
29
|
+
if (status !== 403 && status !== 429) {
|
|
30
|
+
return false;
|
|
31
|
+
}
|
|
32
|
+
if (
|
|
33
|
+
parseNonNegativeIntegerHeader(headers.get('x-ratelimit-remaining')) === 0
|
|
34
|
+
) {
|
|
35
|
+
return true;
|
|
36
|
+
}
|
|
37
|
+
if (headers.get('retry-after') !== null) {
|
|
38
|
+
return true;
|
|
39
|
+
}
|
|
40
|
+
return RATE_LIMIT_MESSAGE_PATTERN.test(bodyText);
|
|
41
|
+
};
|
|
42
|
+
|
|
43
|
+
export const computeRateLimitResetIso = (headers: Headers): string | null => {
|
|
44
|
+
const resetEpochSeconds = parseNonNegativeIntegerHeader(
|
|
45
|
+
headers.get('x-ratelimit-reset'),
|
|
46
|
+
);
|
|
47
|
+
if (resetEpochSeconds === null) {
|
|
48
|
+
return null;
|
|
49
|
+
}
|
|
50
|
+
return new Date(resetEpochSeconds * 1000).toISOString();
|
|
51
|
+
};
|
|
52
|
+
|
|
53
|
+
export const computeBoundedBackoffMs = (
|
|
54
|
+
headers: Headers,
|
|
55
|
+
attempt: number,
|
|
56
|
+
elapsedMs: number,
|
|
57
|
+
): number => {
|
|
58
|
+
const remainingBudgetMs = RATE_LIMIT_TOTAL_BACKOFF_CAP_MS - elapsedMs;
|
|
59
|
+
if (remainingBudgetMs <= 0) {
|
|
60
|
+
return 0;
|
|
61
|
+
}
|
|
62
|
+
const exponentialMs = RATE_LIMIT_BASE_BACKOFF_MS * Math.pow(2, attempt);
|
|
63
|
+
const retryAfterSeconds = parseNonNegativeIntegerHeader(
|
|
64
|
+
headers.get('retry-after'),
|
|
65
|
+
);
|
|
66
|
+
const requestedMs =
|
|
67
|
+
retryAfterSeconds !== null ? retryAfterSeconds * 1000 : exponentialMs;
|
|
68
|
+
return Math.min(requestedMs, remainingBudgetMs);
|
|
69
|
+
};
|
|
70
|
+
|
|
71
|
+
export const fetchWithGitHubRateLimitRetry = async (
|
|
72
|
+
request: () => Promise<Response>,
|
|
73
|
+
sleep: Sleep = realSleep,
|
|
74
|
+
now: () => number = Date.now,
|
|
75
|
+
): Promise<Response> => {
|
|
76
|
+
const startMs = now();
|
|
77
|
+
let attempt = 0;
|
|
78
|
+
for (;;) {
|
|
79
|
+
const response = await request();
|
|
80
|
+
if (response.ok) {
|
|
81
|
+
return response;
|
|
82
|
+
}
|
|
83
|
+
const bodyText = await response.clone().text();
|
|
84
|
+
if (
|
|
85
|
+
attempt >= RATE_LIMIT_MAX_RETRIES ||
|
|
86
|
+
!hasRateLimitSignals(response.status, response.headers, bodyText)
|
|
87
|
+
) {
|
|
88
|
+
return response;
|
|
89
|
+
}
|
|
90
|
+
const elapsedMs = now() - startMs;
|
|
91
|
+
const backoffMs = computeBoundedBackoffMs(
|
|
92
|
+
response.headers,
|
|
93
|
+
attempt,
|
|
94
|
+
elapsedMs,
|
|
95
|
+
);
|
|
96
|
+
if (backoffMs <= 0) {
|
|
97
|
+
return response;
|
|
98
|
+
}
|
|
99
|
+
console.log(
|
|
100
|
+
`GitHub returned ${response.status} (rate limit). Backing off ${backoffMs}ms before retry ${attempt + 1}/${RATE_LIMIT_MAX_RETRIES}.`,
|
|
101
|
+
);
|
|
102
|
+
await sleep(backoffMs);
|
|
103
|
+
attempt++;
|
|
104
|
+
}
|
|
105
|
+
};
|
|
@@ -32,9 +32,9 @@ const candidate = (
|
|
|
32
32
|
unifiedRejected,
|
|
33
33
|
});
|
|
34
34
|
|
|
35
|
-
const session = (name: string,
|
|
35
|
+
const session = (name: string, sessionKey: string): ClaudeLiveSession => ({
|
|
36
36
|
token: `fake-token-${name}`,
|
|
37
|
-
|
|
37
|
+
sessionKey,
|
|
38
38
|
});
|
|
39
39
|
|
|
40
40
|
describe('LiveSessionOauthTokenSelectUseCase', () => {
|
|
@@ -98,7 +98,7 @@ describe('LiveSessionOauthTokenSelectUseCase', () => {
|
|
|
98
98
|
expect(blocked?.liveSessionCount).toBe(0);
|
|
99
99
|
});
|
|
100
100
|
|
|
101
|
-
it('counts distinct session
|
|
101
|
+
it('counts distinct session keys and dedupes child processes sharing one session key', () => {
|
|
102
102
|
const result = useCase.run(
|
|
103
103
|
[
|
|
104
104
|
candidate('oneSession', snapshot({ sevenDayReset: NOW + 2 * DAY })),
|
|
@@ -121,6 +121,28 @@ describe('LiveSessionOauthTokenSelectUseCase', () => {
|
|
|
121
121
|
expect(twoSessions?.liveSessionCount).toBe(2);
|
|
122
122
|
});
|
|
123
123
|
|
|
124
|
+
it('counts resumed sessions keyed by config dir so they are not under-counted', () => {
|
|
125
|
+
const result = useCase.run(
|
|
126
|
+
[
|
|
127
|
+
candidate('resumedHeavy', snapshot({ sevenDayReset: NOW + 2 * DAY })),
|
|
128
|
+
candidate('fresh', snapshot({ sevenDayReset: NOW + 6 * DAY })),
|
|
129
|
+
],
|
|
130
|
+
[
|
|
131
|
+
session('resumedHeavy', '/home/user/.config/claude-1'),
|
|
132
|
+
session('resumedHeavy', '/home/user/.config/claude-1'),
|
|
133
|
+
session('resumedHeavy', '/home/user/.config/claude-2'),
|
|
134
|
+
session('fresh', 'session-fresh'),
|
|
135
|
+
],
|
|
136
|
+
NOW,
|
|
137
|
+
);
|
|
138
|
+
|
|
139
|
+
expect(result.selected?.name).toBe('fresh');
|
|
140
|
+
const resumedHeavy = result.metrics.find((m) => m.name === 'resumedHeavy');
|
|
141
|
+
const fresh = result.metrics.find((m) => m.name === 'fresh');
|
|
142
|
+
expect(resumedHeavy?.liveSessionCount).toBe(2);
|
|
143
|
+
expect(fresh?.liveSessionCount).toBe(1);
|
|
144
|
+
});
|
|
145
|
+
|
|
124
146
|
it('returns null selection when no token passes the rate-limit filter', () => {
|
|
125
147
|
const result = useCase.run(
|
|
126
148
|
[candidate('blocked', snapshot({ fiveHourUtilization: 0.9 }))],
|
|
@@ -84,16 +84,16 @@ export class LiveSessionOauthTokenSelectUseCase {
|
|
|
84
84
|
private liveSessionCountByToken = (
|
|
85
85
|
liveSessions: ClaudeLiveSession[],
|
|
86
86
|
): Map<string, number> => {
|
|
87
|
-
const
|
|
87
|
+
const sessionKeysByToken = new Map<string, Set<string>>();
|
|
88
88
|
for (const liveSession of liveSessions) {
|
|
89
|
-
const
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
89
|
+
const sessionKeys =
|
|
90
|
+
sessionKeysByToken.get(liveSession.token) ?? new Set<string>();
|
|
91
|
+
sessionKeys.add(liveSession.sessionKey);
|
|
92
|
+
sessionKeysByToken.set(liveSession.token, sessionKeys);
|
|
93
93
|
}
|
|
94
94
|
const countByToken = new Map<string, number>();
|
|
95
|
-
for (const [token,
|
|
96
|
-
countByToken.set(token,
|
|
95
|
+
for (const [token, sessionKeys] of sessionKeysByToken.entries()) {
|
|
96
|
+
countByToken.set(token, sessionKeys.size);
|
|
97
97
|
}
|
|
98
98
|
return countByToken;
|
|
99
99
|
};
|
|
@@ -81,11 +81,11 @@ describe('OauthTokenSelectUseCase', () => {
|
|
|
81
81
|
expect(result.selected?.name).toBe('boundary');
|
|
82
82
|
});
|
|
83
83
|
|
|
84
|
-
it('excludes a token whose 7d window is less than
|
|
84
|
+
it('excludes a token whose 7d window is less than 7% free', () => {
|
|
85
85
|
const result = useCase.run(
|
|
86
86
|
[
|
|
87
|
-
candidate('busy7d', snapshot({ sevenDayUtilization: 0.
|
|
88
|
-
candidate('ok', snapshot({ sevenDayUtilization: 0.
|
|
87
|
+
candidate('busy7d', snapshot({ sevenDayUtilization: 0.94 })),
|
|
88
|
+
candidate('ok', snapshot({ sevenDayUtilization: 0.92 })),
|
|
89
89
|
],
|
|
90
90
|
NOW,
|
|
91
91
|
);
|
|
@@ -96,6 +96,15 @@ describe('OauthTokenSelectUseCase', () => {
|
|
|
96
96
|
expect(busy?.exclusionReason).toContain('7d window');
|
|
97
97
|
});
|
|
98
98
|
|
|
99
|
+
it('treats a 7d window with at least 7% free as eligible (boundary)', () => {
|
|
100
|
+
const result = useCase.run(
|
|
101
|
+
[candidate('boundary', snapshot({ sevenDayUtilization: 0.92 }))],
|
|
102
|
+
NOW,
|
|
103
|
+
);
|
|
104
|
+
|
|
105
|
+
expect(result.selected?.name).toBe('boundary');
|
|
106
|
+
});
|
|
107
|
+
|
|
99
108
|
it('treats a token with no snapshot as fully free', () => {
|
|
100
109
|
const result = useCase.run([candidate('fresh', null)], NOW);
|
|
101
110
|
|
|
@@ -154,7 +163,7 @@ describe('OauthTokenSelectUseCase', () => {
|
|
|
154
163
|
const result = useCase.run(
|
|
155
164
|
[
|
|
156
165
|
candidate('busy', snapshot({ fiveHourUtilization: 0.9 })),
|
|
157
|
-
candidate('alsoBusy', snapshot({ sevenDayUtilization: 0.
|
|
166
|
+
candidate('alsoBusy', snapshot({ sevenDayUtilization: 0.95 })),
|
|
158
167
|
],
|
|
159
168
|
NOW,
|
|
160
169
|
);
|
|
@@ -31,7 +31,7 @@ const SECONDS_PER_DAY = 86400;
|
|
|
31
31
|
const SEVEN_DAYS_IN_SECONDS = 7 * SECONDS_PER_DAY;
|
|
32
32
|
|
|
33
33
|
export const FIVE_HOUR_MIN_FREE_RATIO = 0.6;
|
|
34
|
-
export const SEVEN_DAY_MIN_FREE_RATIO = 0.
|
|
34
|
+
export const SEVEN_DAY_MIN_FREE_RATIO = 0.07;
|
|
35
35
|
|
|
36
36
|
export class OauthTokenSelectUseCase {
|
|
37
37
|
run = (
|
|
@@ -5,6 +5,7 @@ export declare class ProcClaudeLiveSessionRepository implements ClaudeLiveSessio
|
|
|
5
5
|
listLiveSessions: () => ClaudeLiveSession[];
|
|
6
6
|
private listProcessIdDirectories;
|
|
7
7
|
private readLiveSession;
|
|
8
|
+
private deriveSessionKey;
|
|
8
9
|
private isClaudeProcess;
|
|
9
10
|
private readEnviron;
|
|
10
11
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ProcClaudeLiveSessionRepository.d.ts","sourceRoot":"","sources":["../../../src/adapter/repositories/ProcClaudeLiveSessionRepository.ts"],"names":[],"mappings":"AAEA,OAAO,EACL,iBAAiB,EACjB,2BAA2B,EAC5B,MAAM,sEAAsE,CAAC;
|
|
1
|
+
{"version":3,"file":"ProcClaudeLiveSessionRepository.d.ts","sourceRoot":"","sources":["../../../src/adapter/repositories/ProcClaudeLiveSessionRepository.ts"],"names":[],"mappings":"AAEA,OAAO,EACL,iBAAiB,EACjB,2BAA2B,EAC5B,MAAM,sEAAsE,CAAC;AAkB9E,qBAAa,+BAAgC,YAAW,2BAA2B;IAE/E,OAAO,CAAC,QAAQ,CAAC,aAAa;gBAAb,aAAa,GAAE,MAA+B;IAGjE,gBAAgB,QAAO,iBAAiB,EAAE,CASxC;IAEF,OAAO,CAAC,wBAAwB,CAQ9B;IAEF,OAAO,CAAC,eAAe,CAmBrB;IAEF,OAAO,CAAC,gBAAgB,CAUtB;IAEF,OAAO,CAAC,eAAe,CAgBrB;IAEF,OAAO,CAAC,WAAW,CA4BjB;CACH"}
|
|
@@ -9,6 +9,7 @@ import { LocalStorageCacheRepository } from '../LocalStorageCacheRepository';
|
|
|
9
9
|
import { BaseGitHubRepository } from '../BaseGitHubRepository';
|
|
10
10
|
import { LocalStorageRepository } from '../LocalStorageRepository';
|
|
11
11
|
import { Member } from '../../../domain/entities/Member';
|
|
12
|
+
import { Sleep } from './githubRateLimitRetry';
|
|
12
13
|
export declare class ApiV3CheerioRestIssueRepository extends BaseGitHubRepository implements IssueRepository {
|
|
13
14
|
readonly apiV3IssueRepository: Pick<ApiV3IssueRepository, 'searchIssue'>;
|
|
14
15
|
readonly restIssueRepository: Pick<RestIssueRepository, 'createNewIssue' | 'updateIssue' | 'createComment' | 'getIssue' | 'updateLabels' | 'removeLabel' | 'updateAssigneeList'>;
|
|
@@ -16,7 +17,9 @@ export declare class ApiV3CheerioRestIssueRepository extends BaseGitHubRepositor
|
|
|
16
17
|
readonly localStorageCacheRepository: Pick<LocalStorageCacheRepository, 'getLatest' | 'set'>;
|
|
17
18
|
readonly localStorageRepository: LocalStorageRepository;
|
|
18
19
|
readonly ghToken: string;
|
|
19
|
-
|
|
20
|
+
readonly sleep: Sleep;
|
|
21
|
+
constructor(apiV3IssueRepository: Pick<ApiV3IssueRepository, 'searchIssue'>, restIssueRepository: Pick<RestIssueRepository, 'createNewIssue' | 'updateIssue' | 'createComment' | 'getIssue' | 'updateLabels' | 'removeLabel' | 'updateAssigneeList'>, graphqlProjectItemRepository: Pick<GraphqlProjectItemRepository, 'fetchProjectItems' | 'fetchProjectItemByUrl' | 'updateProjectField' | 'clearProjectField' | 'updateProjectTextField' | 'addIssueToProject'>, localStorageCacheRepository: Pick<LocalStorageCacheRepository, 'getLatest' | 'set'>, localStorageRepository: LocalStorageRepository, ghToken?: string, sleep?: Sleep);
|
|
22
|
+
private fetchWithRateLimitRetry;
|
|
20
23
|
updateStatus: (project: Project, issue: Issue, statusId: string) => Promise<void>;
|
|
21
24
|
convertProjectItemToIssue: (item: ProjectItem) => Issue;
|
|
22
25
|
getAllIssuesFromCache: (cacheKey: string, allowCacheMinutes: number) => Promise<Issue[] | null>;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ApiV3CheerioRestIssueRepository.d.ts","sourceRoot":"","sources":["../../../../src/adapter/repositories/issue/ApiV3CheerioRestIssueRepository.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,eAAe,EACf,kBAAkB,EAClB,YAAY,EACZ,iBAAiB,EAEjB,iBAAiB,EACjB,4BAA4B,EAC7B,MAAM,6DAA6D,CAAC;AACrE,OAAO,EAAE,OAAO,EAAE,MAAM,kCAAkC,CAAC;AAC3D,OAAO,EAAE,KAAK,EAAE,MAAM,gCAAgC,CAAC;AACvD,OAAO,EAAE,cAAc,EAAE,MAAM,yCAAyC,CAAC;AACzE,OAAO,EAAE,oBAAoB,EAAE,MAAM,wBAAwB,CAAC;AAC9D,OAAO,EAAE,mBAAmB,EAAE,MAAM,uBAAuB,CAAC;AAC5D,OAAO,EACL,4BAA4B,EAC5B,WAAW,EACZ,MAAM,gCAAgC,CAAC;AACxC,OAAO,EAAE,2BAA2B,EAAE,MAAM,gCAAgC,CAAC;AAE7E,OAAO,EAAE,oBAAoB,EAAE,MAAM,yBAAyB,CAAC;AAE/D,OAAO,EAAE,sBAAsB,EAAE,MAAM,2BAA2B,CAAC;AACnE,OAAO,EAAE,MAAM,EAAE,MAAM,iCAAiC,CAAC;
|
|
1
|
+
{"version":3,"file":"ApiV3CheerioRestIssueRepository.d.ts","sourceRoot":"","sources":["../../../../src/adapter/repositories/issue/ApiV3CheerioRestIssueRepository.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
export declare const RATE_LIMIT_MAX_RETRIES = 3;
|
|
2
|
+
export declare const RATE_LIMIT_TOTAL_BACKOFF_CAP_MS = 5000;
|
|
3
|
+
export declare const RATE_LIMIT_BASE_BACKOFF_MS = 250;
|
|
4
|
+
export type Sleep = (milliseconds: number) => Promise<void>;
|
|
5
|
+
export declare const realSleep: Sleep;
|
|
6
|
+
export declare const hasRateLimitSignals: (status: number, headers: Headers, bodyText: string) => boolean;
|
|
7
|
+
export declare const computeRateLimitResetIso: (headers: Headers) => string | null;
|
|
8
|
+
export declare const computeBoundedBackoffMs: (headers: Headers, attempt: number, elapsedMs: number) => number;
|
|
9
|
+
export declare const fetchWithGitHubRateLimitRetry: (request: () => Promise<Response>, sleep?: Sleep, now?: () => number) => Promise<Response>;
|
|
10
|
+
//# sourceMappingURL=githubRateLimitRetry.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"githubRateLimitRetry.d.ts","sourceRoot":"","sources":["../../../../src/adapter/repositories/issue/githubRateLimitRetry.ts"],"names":[],"mappings":"AAAA,eAAO,MAAM,sBAAsB,IAAI,CAAC;AACxC,eAAO,MAAM,+BAA+B,OAAO,CAAC;AACpD,eAAO,MAAM,0BAA0B,MAAM,CAAC;AAI9C,MAAM,MAAM,KAAK,GAAG,CAAC,YAAY,EAAE,MAAM,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;AAE5D,eAAO,MAAM,SAAS,EAAE,KACqC,CAAC;AAc9D,eAAO,MAAM,mBAAmB,GAC9B,QAAQ,MAAM,EACd,SAAS,OAAO,EAChB,UAAU,MAAM,KACf,OAaF,CAAC;AAEF,eAAO,MAAM,wBAAwB,GAAI,SAAS,OAAO,KAAG,MAAM,GAAG,IAQpE,CAAC;AAEF,eAAO,MAAM,uBAAuB,GAClC,SAAS,OAAO,EAChB,SAAS,MAAM,EACf,WAAW,MAAM,KAChB,MAYF,CAAC;AAEF,eAAO,MAAM,6BAA6B,GACxC,SAAS,MAAM,OAAO,CAAC,QAAQ,CAAC,EAChC,QAAO,KAAiB,EACxB,MAAK,MAAM,MAAiB,KAC3B,OAAO,CAAC,QAAQ,CA8BlB,CAAC"}
|
|
@@ -24,7 +24,7 @@ export type OauthTokenSelectResult = {
|
|
|
24
24
|
metrics: OauthTokenCandidateMetrics[];
|
|
25
25
|
};
|
|
26
26
|
export declare const FIVE_HOUR_MIN_FREE_RATIO = 0.6;
|
|
27
|
-
export declare const SEVEN_DAY_MIN_FREE_RATIO = 0.
|
|
27
|
+
export declare const SEVEN_DAY_MIN_FREE_RATIO = 0.07;
|
|
28
28
|
export declare class OauthTokenSelectUseCase {
|
|
29
29
|
run: (candidates: OauthTokenCandidate[], nowEpochSeconds: number) => OauthTokenSelectResult;
|
|
30
30
|
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;CAC1B,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,MAAM,CAAC;AAC5C,eAAO,MAAM,wBAAwB,
|
|
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;CAC1B,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,MAAM,CAAC;AAC5C,eAAO,MAAM,wBAAwB,OAAO,CAAC;AAE7C,qBAAa,uBAAuB;IAClC,GAAG,GACD,YAAY,mBAAmB,EAAE,EACjC,iBAAiB,MAAM,KACtB,sBAAsB,CAoBvB;IAEF,OAAO,CAAC,QAAQ,CAgCd;IAEF,OAAO,CAAC,eAAe,CAmBrB;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 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ClaudeLiveSessionRepository.d.ts","sourceRoot":"","sources":["../../../../src/domain/usecases/adapter-interfaces/ClaudeLiveSessionRepository.ts"],"names":[],"mappings":"AAAA,MAAM,MAAM,iBAAiB,GAAG;IAC9B,KAAK,EAAE,MAAM,CAAC;IACd,
|
|
1
|
+
{"version":3,"file":"ClaudeLiveSessionRepository.d.ts","sourceRoot":"","sources":["../../../../src/domain/usecases/adapter-interfaces/ClaudeLiveSessionRepository.ts"],"names":[],"mappings":"AAAA,MAAM,MAAM,iBAAiB,GAAG;IAC9B,KAAK,EAAE,MAAM,CAAC;IACd,UAAU,EAAE,MAAM,CAAC;CACpB,CAAC;AAEF,MAAM,WAAW,2BAA2B;IAC1C,gBAAgB,EAAE,MAAM,iBAAiB,EAAE,CAAC;CAC7C"}
|