@renseiai/agentfactory-server 0.8.0

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 (93) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +71 -0
  3. package/dist/src/a2a-server.d.ts +88 -0
  4. package/dist/src/a2a-server.d.ts.map +1 -0
  5. package/dist/src/a2a-server.integration.test.d.ts +9 -0
  6. package/dist/src/a2a-server.integration.test.d.ts.map +1 -0
  7. package/dist/src/a2a-server.integration.test.js +397 -0
  8. package/dist/src/a2a-server.js +235 -0
  9. package/dist/src/a2a-server.test.d.ts +2 -0
  10. package/dist/src/a2a-server.test.d.ts.map +1 -0
  11. package/dist/src/a2a-server.test.js +311 -0
  12. package/dist/src/a2a-types.d.ts +125 -0
  13. package/dist/src/a2a-types.d.ts.map +1 -0
  14. package/dist/src/a2a-types.js +8 -0
  15. package/dist/src/agent-tracking.d.ts +201 -0
  16. package/dist/src/agent-tracking.d.ts.map +1 -0
  17. package/dist/src/agent-tracking.js +349 -0
  18. package/dist/src/env-validation.d.ts +65 -0
  19. package/dist/src/env-validation.d.ts.map +1 -0
  20. package/dist/src/env-validation.js +134 -0
  21. package/dist/src/governor-dedup.d.ts +15 -0
  22. package/dist/src/governor-dedup.d.ts.map +1 -0
  23. package/dist/src/governor-dedup.js +31 -0
  24. package/dist/src/governor-event-bus.d.ts +54 -0
  25. package/dist/src/governor-event-bus.d.ts.map +1 -0
  26. package/dist/src/governor-event-bus.js +152 -0
  27. package/dist/src/governor-storage.d.ts +28 -0
  28. package/dist/src/governor-storage.d.ts.map +1 -0
  29. package/dist/src/governor-storage.js +52 -0
  30. package/dist/src/index.d.ts +26 -0
  31. package/dist/src/index.d.ts.map +1 -0
  32. package/dist/src/index.js +50 -0
  33. package/dist/src/issue-lock.d.ts +129 -0
  34. package/dist/src/issue-lock.d.ts.map +1 -0
  35. package/dist/src/issue-lock.js +508 -0
  36. package/dist/src/logger.d.ts +76 -0
  37. package/dist/src/logger.d.ts.map +1 -0
  38. package/dist/src/logger.js +218 -0
  39. package/dist/src/orphan-cleanup.d.ts +64 -0
  40. package/dist/src/orphan-cleanup.d.ts.map +1 -0
  41. package/dist/src/orphan-cleanup.js +369 -0
  42. package/dist/src/pending-prompts.d.ts +67 -0
  43. package/dist/src/pending-prompts.d.ts.map +1 -0
  44. package/dist/src/pending-prompts.js +176 -0
  45. package/dist/src/processing-state-storage.d.ts +38 -0
  46. package/dist/src/processing-state-storage.d.ts.map +1 -0
  47. package/dist/src/processing-state-storage.js +61 -0
  48. package/dist/src/quota-tracker.d.ts +62 -0
  49. package/dist/src/quota-tracker.d.ts.map +1 -0
  50. package/dist/src/quota-tracker.js +155 -0
  51. package/dist/src/rate-limit.d.ts +111 -0
  52. package/dist/src/rate-limit.d.ts.map +1 -0
  53. package/dist/src/rate-limit.js +171 -0
  54. package/dist/src/redis-circuit-breaker.d.ts +67 -0
  55. package/dist/src/redis-circuit-breaker.d.ts.map +1 -0
  56. package/dist/src/redis-circuit-breaker.js +290 -0
  57. package/dist/src/redis-rate-limiter.d.ts +51 -0
  58. package/dist/src/redis-rate-limiter.d.ts.map +1 -0
  59. package/dist/src/redis-rate-limiter.js +168 -0
  60. package/dist/src/redis.d.ts +146 -0
  61. package/dist/src/redis.d.ts.map +1 -0
  62. package/dist/src/redis.js +343 -0
  63. package/dist/src/session-hash.d.ts +48 -0
  64. package/dist/src/session-hash.d.ts.map +1 -0
  65. package/dist/src/session-hash.js +80 -0
  66. package/dist/src/session-storage.d.ts +166 -0
  67. package/dist/src/session-storage.d.ts.map +1 -0
  68. package/dist/src/session-storage.js +397 -0
  69. package/dist/src/token-storage.d.ts +118 -0
  70. package/dist/src/token-storage.d.ts.map +1 -0
  71. package/dist/src/token-storage.js +263 -0
  72. package/dist/src/types.d.ts +11 -0
  73. package/dist/src/types.d.ts.map +1 -0
  74. package/dist/src/types.js +7 -0
  75. package/dist/src/webhook-idempotency.d.ts +44 -0
  76. package/dist/src/webhook-idempotency.d.ts.map +1 -0
  77. package/dist/src/webhook-idempotency.js +148 -0
  78. package/dist/src/work-queue.d.ts +120 -0
  79. package/dist/src/work-queue.d.ts.map +1 -0
  80. package/dist/src/work-queue.js +384 -0
  81. package/dist/src/worker-auth.d.ts +29 -0
  82. package/dist/src/worker-auth.d.ts.map +1 -0
  83. package/dist/src/worker-auth.js +49 -0
  84. package/dist/src/worker-storage.d.ts +108 -0
  85. package/dist/src/worker-storage.d.ts.map +1 -0
  86. package/dist/src/worker-storage.js +295 -0
  87. package/dist/src/workflow-state-integration.test.d.ts +2 -0
  88. package/dist/src/workflow-state-integration.test.d.ts.map +1 -0
  89. package/dist/src/workflow-state-integration.test.js +342 -0
  90. package/dist/src/workflow-state.test.d.ts +2 -0
  91. package/dist/src/workflow-state.test.d.ts.map +1 -0
  92. package/dist/src/workflow-state.test.js +113 -0
  93. package/package.json +72 -0
@@ -0,0 +1,397 @@
1
+ import { isRedisConfigured, redisSet, redisGet, redisDel, redisKeys } from './redis.js';
2
+ const log = {
3
+ info: (msg, data) => console.log(`[session] ${msg}`, data ? JSON.stringify(data) : ''),
4
+ warn: (msg, data) => console.warn(`[session] ${msg}`, data ? JSON.stringify(data) : ''),
5
+ error: (msg, data) => console.error(`[session] ${msg}`, data ? JSON.stringify(data) : ''),
6
+ debug: (_msg, _data) => { },
7
+ };
8
+ /**
9
+ * Key prefix for session state in KV
10
+ */
11
+ const SESSION_KEY_PREFIX = 'agent:session:';
12
+ /**
13
+ * Session state TTL in seconds (24 hours)
14
+ * Sessions older than this are automatically cleaned up by KV
15
+ */
16
+ const SESSION_TTL_SECONDS = 24 * 60 * 60;
17
+ /**
18
+ * Build the KV key for a session
19
+ */
20
+ function buildSessionKey(linearSessionId) {
21
+ return `${SESSION_KEY_PREFIX}${linearSessionId}`;
22
+ }
23
+ /**
24
+ * Store agent session state in Redis
25
+ *
26
+ * @param linearSessionId - The Linear session ID from webhook
27
+ * @param state - The session state to store
28
+ */
29
+ export async function storeSessionState(linearSessionId, state) {
30
+ if (!isRedisConfigured()) {
31
+ log.warn('Redis not configured, session state will not be persisted');
32
+ const now = Math.floor(Date.now() / 1000);
33
+ return {
34
+ ...state,
35
+ linearSessionId,
36
+ createdAt: now,
37
+ updatedAt: now,
38
+ };
39
+ }
40
+ const now = Math.floor(Date.now() / 1000);
41
+ const key = buildSessionKey(linearSessionId);
42
+ // Check for existing session to preserve createdAt
43
+ const existing = await redisGet(key);
44
+ const sessionState = {
45
+ ...state,
46
+ linearSessionId,
47
+ createdAt: existing?.createdAt ?? now,
48
+ updatedAt: now,
49
+ };
50
+ await redisSet(key, sessionState, SESSION_TTL_SECONDS);
51
+ log.info('Stored session state', {
52
+ linearSessionId,
53
+ issueId: state.issueId,
54
+ status: state.status,
55
+ hasProviderSessionId: !!state.providerSessionId,
56
+ });
57
+ return sessionState;
58
+ }
59
+ /**
60
+ * Retrieve agent session state from Redis
61
+ *
62
+ * @param linearSessionId - The Linear session ID
63
+ * @returns The session state or null if not found
64
+ */
65
+ export async function getSessionState(linearSessionId) {
66
+ if (!isRedisConfigured()) {
67
+ log.debug('Redis not configured, cannot retrieve session state');
68
+ return null;
69
+ }
70
+ const key = buildSessionKey(linearSessionId);
71
+ const state = await redisGet(key);
72
+ if (state) {
73
+ log.debug('Retrieved session state', {
74
+ linearSessionId,
75
+ issueId: state.issueId,
76
+ status: state.status,
77
+ });
78
+ }
79
+ return state;
80
+ }
81
+ /**
82
+ * Update the provider session ID for a session
83
+ * Called when the Claude init event is received with the session ID
84
+ *
85
+ * @param linearSessionId - The Linear session ID
86
+ * @param providerSessionId - The Provider CLI session ID
87
+ */
88
+ export async function updateProviderSessionId(linearSessionId, providerSessionId) {
89
+ if (!isRedisConfigured()) {
90
+ log.warn('Redis not configured, cannot update provider session ID');
91
+ return false;
92
+ }
93
+ const existing = await getSessionState(linearSessionId);
94
+ if (!existing) {
95
+ log.warn('Session not found for provider session ID update', { linearSessionId });
96
+ return false;
97
+ }
98
+ const key = buildSessionKey(linearSessionId);
99
+ const now = Math.floor(Date.now() / 1000);
100
+ const updated = {
101
+ ...existing,
102
+ providerSessionId,
103
+ updatedAt: now,
104
+ };
105
+ await redisSet(key, updated, SESSION_TTL_SECONDS);
106
+ log.info('Updated provider session ID', { linearSessionId, providerSessionId });
107
+ return true;
108
+ }
109
+ /**
110
+ * Update session status
111
+ *
112
+ * @param linearSessionId - The Linear session ID
113
+ * @param status - The new status
114
+ */
115
+ export async function updateSessionStatus(linearSessionId, status) {
116
+ if (!isRedisConfigured()) {
117
+ log.warn('Redis not configured, cannot update session status');
118
+ return false;
119
+ }
120
+ const existing = await getSessionState(linearSessionId);
121
+ if (!existing) {
122
+ log.warn('Session not found for status update', { linearSessionId });
123
+ return false;
124
+ }
125
+ const key = buildSessionKey(linearSessionId);
126
+ const now = Math.floor(Date.now() / 1000);
127
+ const updated = {
128
+ ...existing,
129
+ status,
130
+ updatedAt: now,
131
+ };
132
+ await redisSet(key, updated, SESSION_TTL_SECONDS);
133
+ log.info('Updated session status', { linearSessionId, status });
134
+ return true;
135
+ }
136
+ /**
137
+ * Update session cost data (tokens and USD)
138
+ *
139
+ * @param linearSessionId - The Linear session ID
140
+ * @param costData - Cost fields to persist
141
+ */
142
+ export async function updateSessionCostData(linearSessionId, costData) {
143
+ if (!isRedisConfigured()) {
144
+ log.warn('Redis not configured, cannot update session cost data');
145
+ return false;
146
+ }
147
+ const existing = await getSessionState(linearSessionId);
148
+ if (!existing) {
149
+ log.warn('Session not found for cost update', { linearSessionId });
150
+ return false;
151
+ }
152
+ const key = buildSessionKey(linearSessionId);
153
+ const now = Math.floor(Date.now() / 1000);
154
+ const updated = {
155
+ ...existing,
156
+ totalCostUsd: costData.totalCostUsd ?? existing.totalCostUsd,
157
+ inputTokens: costData.inputTokens ?? existing.inputTokens,
158
+ outputTokens: costData.outputTokens ?? existing.outputTokens,
159
+ updatedAt: now,
160
+ };
161
+ await redisSet(key, updated, SESSION_TTL_SECONDS);
162
+ log.info('Updated session cost data', {
163
+ linearSessionId,
164
+ totalCostUsd: updated.totalCostUsd,
165
+ });
166
+ return true;
167
+ }
168
+ /**
169
+ * Reset a session for re-queuing after orphan cleanup
170
+ * Clears workerId and resets status to pending so a new worker can claim it
171
+ *
172
+ * @param linearSessionId - The Linear session ID
173
+ */
174
+ export async function resetSessionForRequeue(linearSessionId) {
175
+ if (!isRedisConfigured()) {
176
+ log.warn('Redis not configured, cannot reset session');
177
+ return false;
178
+ }
179
+ const existing = await getSessionState(linearSessionId);
180
+ if (!existing) {
181
+ log.warn('Session not found for reset', { linearSessionId });
182
+ return false;
183
+ }
184
+ const key = buildSessionKey(linearSessionId);
185
+ const now = Math.floor(Date.now() / 1000);
186
+ const updated = {
187
+ ...existing,
188
+ status: 'pending',
189
+ workerId: undefined, // Clear workerId so new worker can claim
190
+ claimedAt: undefined,
191
+ updatedAt: now,
192
+ };
193
+ await redisSet(key, updated, SESSION_TTL_SECONDS);
194
+ log.info('Reset session for requeue', {
195
+ linearSessionId,
196
+ previousWorkerId: existing.workerId,
197
+ });
198
+ return true;
199
+ }
200
+ /**
201
+ * Delete session state from KV
202
+ *
203
+ * @param linearSessionId - The Linear session ID
204
+ * @returns Whether the deletion was successful
205
+ */
206
+ export async function deleteSessionState(linearSessionId) {
207
+ if (!isRedisConfigured()) {
208
+ return false;
209
+ }
210
+ const key = buildSessionKey(linearSessionId);
211
+ const result = await redisDel(key);
212
+ log.info('Deleted session state', { linearSessionId });
213
+ return result > 0;
214
+ }
215
+ /**
216
+ * Get session state by issue ID
217
+ * Useful when we have the issue but not the session ID
218
+ *
219
+ * When multiple sessions exist for the same issue (e.g., one running + several
220
+ * failed), this function returns the most relevant one — preferring active
221
+ * sessions (running/claimed/pending) over inactive ones. Without this
222
+ * prioritization, an arbitrary first-match could hide a running session behind
223
+ * a failed one, causing the governor to re-dispatch work for an issue that
224
+ * already has an agent in-flight.
225
+ *
226
+ * @param issueId - The Linear issue ID
227
+ * @returns The most relevant session state for this issue or null
228
+ */
229
+ export async function getSessionStateByIssue(issueId) {
230
+ if (!isRedisConfigured()) {
231
+ return null;
232
+ }
233
+ // Scan for sessions with this issue ID
234
+ // Note: This is less efficient than direct lookup, use sparingly
235
+ const keys = await redisKeys(`${SESSION_KEY_PREFIX}*`);
236
+ const activeStatuses = ['running', 'claimed', 'pending'];
237
+ let fallback = null;
238
+ for (const key of keys) {
239
+ const state = await redisGet(key);
240
+ if (state?.issueId === issueId) {
241
+ // Prefer active sessions — return immediately if found
242
+ if (activeStatuses.includes(state.status)) {
243
+ return state;
244
+ }
245
+ // Keep first non-active match as fallback
246
+ if (!fallback) {
247
+ fallback = state;
248
+ }
249
+ }
250
+ }
251
+ return fallback;
252
+ }
253
+ // ============================================
254
+ // Worker Pool Operations
255
+ // ============================================
256
+ /**
257
+ * Mark a session as claimed by a worker
258
+ *
259
+ * @param linearSessionId - The Linear session ID
260
+ * @param workerId - The worker claiming the session
261
+ */
262
+ export async function claimSession(linearSessionId, workerId) {
263
+ if (!isRedisConfigured()) {
264
+ return false;
265
+ }
266
+ const existing = await getSessionState(linearSessionId);
267
+ if (!existing) {
268
+ log.warn('Session not found for claim', { linearSessionId });
269
+ return false;
270
+ }
271
+ if (existing.status !== 'pending') {
272
+ log.warn('Session not in pending status', {
273
+ linearSessionId,
274
+ status: existing.status,
275
+ });
276
+ return false;
277
+ }
278
+ const key = buildSessionKey(linearSessionId);
279
+ const now = Math.floor(Date.now() / 1000);
280
+ const updated = {
281
+ ...existing,
282
+ status: 'claimed',
283
+ workerId,
284
+ claimedAt: now,
285
+ updatedAt: now,
286
+ };
287
+ await redisSet(key, updated, SESSION_TTL_SECONDS);
288
+ log.info('Session claimed', { linearSessionId, workerId });
289
+ return true;
290
+ }
291
+ /**
292
+ * Update session with worker info when work starts
293
+ *
294
+ * @param linearSessionId - The Linear session ID
295
+ * @param workerId - The worker processing the session
296
+ * @param worktreePath - Path to the git worktree
297
+ */
298
+ export async function startSession(linearSessionId, workerId, worktreePath) {
299
+ if (!isRedisConfigured()) {
300
+ return false;
301
+ }
302
+ const existing = await getSessionState(linearSessionId);
303
+ if (!existing) {
304
+ log.warn('Session not found for start', { linearSessionId });
305
+ return false;
306
+ }
307
+ const key = buildSessionKey(linearSessionId);
308
+ const now = Math.floor(Date.now() / 1000);
309
+ const updated = {
310
+ ...existing,
311
+ status: 'running',
312
+ workerId,
313
+ worktreePath,
314
+ updatedAt: now,
315
+ };
316
+ await redisSet(key, updated, SESSION_TTL_SECONDS);
317
+ log.info('Session started', { linearSessionId, workerId, worktreePath });
318
+ return true;
319
+ }
320
+ /**
321
+ * Get all sessions from Redis
322
+ * For dashboard display
323
+ */
324
+ export async function getAllSessions() {
325
+ if (!isRedisConfigured()) {
326
+ return [];
327
+ }
328
+ try {
329
+ const keys = await redisKeys(`${SESSION_KEY_PREFIX}*`);
330
+ const sessions = [];
331
+ for (const key of keys) {
332
+ const state = await redisGet(key);
333
+ if (state) {
334
+ sessions.push(state);
335
+ }
336
+ }
337
+ // Sort by updatedAt descending (most recent first)
338
+ sessions.sort((a, b) => b.updatedAt - a.updatedAt);
339
+ return sessions;
340
+ }
341
+ catch (error) {
342
+ log.error('Failed to get all sessions', { error });
343
+ return [];
344
+ }
345
+ }
346
+ /**
347
+ * Get sessions by status
348
+ */
349
+ export async function getSessionsByStatus(status) {
350
+ const allSessions = await getAllSessions();
351
+ const statusArray = Array.isArray(status) ? status : [status];
352
+ return allSessions.filter((s) => statusArray.includes(s.status));
353
+ }
354
+ /**
355
+ * Transfer session ownership to a new worker
356
+ * Used when a worker re-registers after disconnection and gets a new ID
357
+ *
358
+ * @param linearSessionId - The Linear session ID
359
+ * @param newWorkerId - The new worker ID to assign
360
+ * @param oldWorkerId - The previous worker ID (for validation)
361
+ * @returns Whether the transfer was successful
362
+ */
363
+ export async function transferSessionOwnership(linearSessionId, newWorkerId, oldWorkerId) {
364
+ if (!isRedisConfigured()) {
365
+ return { transferred: false, reason: 'Redis not configured' };
366
+ }
367
+ const existing = await getSessionState(linearSessionId);
368
+ if (!existing) {
369
+ return { transferred: false, reason: 'Session not found' };
370
+ }
371
+ // Validate that the old worker ID matches (security check)
372
+ if (existing.workerId && existing.workerId !== oldWorkerId) {
373
+ log.warn('Session ownership transfer rejected - worker ID mismatch', {
374
+ linearSessionId,
375
+ expectedWorkerId: oldWorkerId,
376
+ actualWorkerId: existing.workerId,
377
+ });
378
+ return {
379
+ transferred: false,
380
+ reason: `Session owned by different worker: ${existing.workerId}`,
381
+ };
382
+ }
383
+ const key = buildSessionKey(linearSessionId);
384
+ const now = Math.floor(Date.now() / 1000);
385
+ const updated = {
386
+ ...existing,
387
+ workerId: newWorkerId,
388
+ updatedAt: now,
389
+ };
390
+ await redisSet(key, updated, SESSION_TTL_SECONDS);
391
+ log.info('Session ownership transferred', {
392
+ linearSessionId,
393
+ oldWorkerId,
394
+ newWorkerId,
395
+ });
396
+ return { transferred: true };
397
+ }
@@ -0,0 +1,118 @@
1
+ /**
2
+ * OAuth Token Storage Module
3
+ *
4
+ * Manages Linear OAuth token lifecycle in Redis:
5
+ * - Store, retrieve, refresh, and revoke tokens
6
+ * - Automatic token refresh before expiration
7
+ * - Multi-workspace token support
8
+ */
9
+ /**
10
+ * OAuth token data stored in Redis
11
+ */
12
+ export interface StoredToken {
13
+ /** The OAuth access token for API calls */
14
+ accessToken: string;
15
+ /** The refresh token for obtaining new access tokens */
16
+ refreshToken?: string;
17
+ /** Token type (usually "Bearer") */
18
+ tokenType: string;
19
+ /** OAuth scopes granted */
20
+ scope?: string;
21
+ /** Unix timestamp when the token expires */
22
+ expiresAt?: number;
23
+ /** Unix timestamp when the token was stored */
24
+ storedAt: number;
25
+ /** Workspace/organization ID this token belongs to */
26
+ workspaceId: string;
27
+ /** Workspace name for display purposes */
28
+ workspaceName?: string;
29
+ }
30
+ /**
31
+ * Response from Linear OAuth token exchange
32
+ */
33
+ export interface LinearTokenResponse {
34
+ access_token: string;
35
+ refresh_token?: string;
36
+ token_type: string;
37
+ expires_in?: number;
38
+ scope?: string;
39
+ }
40
+ /**
41
+ * Organization info from Linear API
42
+ */
43
+ export interface LinearOrganization {
44
+ id: string;
45
+ name: string;
46
+ urlKey: string;
47
+ }
48
+ /**
49
+ * Store OAuth token for a workspace in Redis
50
+ *
51
+ * @param workspaceId - The Linear organization ID
52
+ * @param tokenResponse - The token data from OAuth exchange
53
+ * @param workspaceName - Optional workspace name for display
54
+ */
55
+ export declare function storeToken(workspaceId: string, tokenResponse: LinearTokenResponse, workspaceName?: string): Promise<StoredToken>;
56
+ /**
57
+ * Retrieve OAuth token for a workspace from Redis
58
+ *
59
+ * @param workspaceId - The Linear organization ID
60
+ * @returns The stored token or null if not found
61
+ */
62
+ export declare function getToken(workspaceId: string): Promise<StoredToken | null>;
63
+ /**
64
+ * Check if a token needs to be refreshed
65
+ * Returns true if token expires within the buffer period
66
+ *
67
+ * @param token - The stored token to check
68
+ * @returns Whether the token should be refreshed
69
+ */
70
+ export declare function shouldRefreshToken(token: StoredToken): boolean;
71
+ /**
72
+ * Refresh an OAuth token using the refresh token
73
+ *
74
+ * @param token - The current stored token with refresh token
75
+ * @param clientId - The Linear OAuth client ID
76
+ * @param clientSecret - The Linear OAuth client secret
77
+ * @returns The new stored token or null if refresh failed
78
+ */
79
+ export declare function refreshToken(token: StoredToken, clientId: string, clientSecret: string): Promise<StoredToken | null>;
80
+ /**
81
+ * Get a valid access token for a workspace, refreshing if necessary
82
+ *
83
+ * @param workspaceId - The Linear organization ID
84
+ * @param clientId - Optional OAuth client ID for refresh (defaults to env var)
85
+ * @param clientSecret - Optional OAuth client secret for refresh (defaults to env var)
86
+ * @returns The access token or null if not available
87
+ */
88
+ export declare function getAccessToken(workspaceId: string, clientId?: string, clientSecret?: string): Promise<string | null>;
89
+ /**
90
+ * Delete a token from Redis (for cleanup or revocation)
91
+ *
92
+ * @param workspaceId - The Linear organization ID
93
+ * @returns Whether the deletion was successful
94
+ */
95
+ export declare function deleteToken(workspaceId: string): Promise<boolean>;
96
+ /**
97
+ * List all stored workspace tokens (for admin purposes)
98
+ * Note: This scans all keys with the token prefix
99
+ *
100
+ * @returns Array of workspace IDs with stored tokens
101
+ */
102
+ export declare function listStoredWorkspaces(): Promise<string[]>;
103
+ /**
104
+ * Clean up expired tokens from Redis storage
105
+ * Should be called periodically (e.g., via cron job)
106
+ *
107
+ * @returns Number of tokens cleaned up
108
+ */
109
+ export declare function cleanupExpiredTokens(): Promise<number>;
110
+ /**
111
+ * Fetch the current user's organization from Linear API
112
+ * Used after OAuth to determine which workspace the token belongs to
113
+ *
114
+ * @param accessToken - The OAuth access token
115
+ * @returns Organization info or null if fetch failed
116
+ */
117
+ export declare function fetchOrganization(accessToken: string): Promise<LinearOrganization | null>;
118
+ //# sourceMappingURL=token-storage.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"token-storage.d.ts","sourceRoot":"","sources":["../../src/token-storage.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAOH;;GAEG;AACH,MAAM,WAAW,WAAW;IAC1B,2CAA2C;IAC3C,WAAW,EAAE,MAAM,CAAA;IACnB,wDAAwD;IACxD,YAAY,CAAC,EAAE,MAAM,CAAA;IACrB,oCAAoC;IACpC,SAAS,EAAE,MAAM,CAAA;IACjB,2BAA2B;IAC3B,KAAK,CAAC,EAAE,MAAM,CAAA;IACd,4CAA4C;IAC5C,SAAS,CAAC,EAAE,MAAM,CAAA;IAClB,+CAA+C;IAC/C,QAAQ,EAAE,MAAM,CAAA;IAChB,sDAAsD;IACtD,WAAW,EAAE,MAAM,CAAA;IACnB,0CAA0C;IAC1C,aAAa,CAAC,EAAE,MAAM,CAAA;CACvB;AAED;;GAEG;AACH,MAAM,WAAW,mBAAmB;IAClC,YAAY,EAAE,MAAM,CAAA;IACpB,aAAa,CAAC,EAAE,MAAM,CAAA;IACtB,UAAU,EAAE,MAAM,CAAA;IAClB,UAAU,CAAC,EAAE,MAAM,CAAA;IACnB,KAAK,CAAC,EAAE,MAAM,CAAA;CACf;AAED;;GAEG;AACH,MAAM,WAAW,kBAAkB;IACjC,EAAE,EAAE,MAAM,CAAA;IACV,IAAI,EAAE,MAAM,CAAA;IACZ,MAAM,EAAE,MAAM,CAAA;CACf;AAoBD;;;;;;GAMG;AACH,wBAAsB,UAAU,CAC9B,WAAW,EAAE,MAAM,EACnB,aAAa,EAAE,mBAAmB,EAClC,aAAa,CAAC,EAAE,MAAM,GACrB,OAAO,CAAC,WAAW,CAAC,CAsBtB;AAED;;;;;GAKG;AACH,wBAAsB,QAAQ,CAAC,WAAW,EAAE,MAAM,GAAG,OAAO,CAAC,WAAW,GAAG,IAAI,CAAC,CAU/E;AAED;;;;;;GAMG;AACH,wBAAgB,kBAAkB,CAAC,KAAK,EAAE,WAAW,GAAG,OAAO,CAU9D;AAED;;;;;;;GAOG;AACH,wBAAsB,YAAY,CAChC,KAAK,EAAE,WAAW,EAClB,QAAQ,EAAE,MAAM,EAChB,YAAY,EAAE,MAAM,GACnB,OAAO,CAAC,WAAW,GAAG,IAAI,CAAC,CAgD7B;AAED;;;;;;;GAOG;AACH,wBAAsB,cAAc,CAClC,WAAW,EAAE,MAAM,EACnB,QAAQ,CAAC,EAAE,MAAM,EACjB,YAAY,CAAC,EAAE,MAAM,GACpB,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CA8BxB;AAED;;;;;GAKG;AACH,wBAAsB,WAAW,CAAC,WAAW,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,CAYvE;AAED;;;;;GAKG;AACH,wBAAsB,oBAAoB,IAAI,OAAO,CAAC,MAAM,EAAE,CAAC,CAQ9D;AAED;;;;;GAKG;AACH,wBAAsB,oBAAoB,IAAI,OAAO,CAAC,MAAM,CAAC,CAsB5D;AAED;;;;;;GAMG;AACH,wBAAsB,iBAAiB,CACrC,WAAW,EAAE,MAAM,GAClB,OAAO,CAAC,kBAAkB,GAAG,IAAI,CAAC,CA6CpC"}