@supaku/agentfactory-server 0.1.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.
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2025 Supaku
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,98 @@
1
+ /**
2
+ * Agent Tracking Module
3
+ *
4
+ * Tracks which issues the agent has worked on to enable automated QA pickup.
5
+ * Also tracks QA attempts to prevent infinite loops.
6
+ */
7
+ /**
8
+ * Record of an issue that was worked on by an agent
9
+ */
10
+ export interface AgentWorkRecord {
11
+ issueId: string;
12
+ issueIdentifier: string;
13
+ completedAt: number;
14
+ sessionId: string;
15
+ prUrl?: string;
16
+ }
17
+ /**
18
+ * Record of QA attempts for an issue
19
+ */
20
+ export interface QAAttemptRecord {
21
+ issueId: string;
22
+ attemptNumber: number;
23
+ startedAt: number;
24
+ sessionId: string;
25
+ previousAttempts: Array<{
26
+ sessionId: string;
27
+ failedAt: number;
28
+ reason?: string;
29
+ }>;
30
+ }
31
+ /**
32
+ * Mark an issue as having been worked on by the agent
33
+ */
34
+ export declare function markAgentWorked(issueId: string, data: Omit<AgentWorkRecord, 'issueId' | 'completedAt'>): Promise<void>;
35
+ /**
36
+ * Check if an issue was worked on by the agent
37
+ */
38
+ export declare function wasAgentWorked(issueId: string): Promise<AgentWorkRecord | null>;
39
+ /**
40
+ * Record a QA attempt for an issue
41
+ */
42
+ export declare function recordQAAttempt(issueId: string, sessionId: string): Promise<QAAttemptRecord>;
43
+ /**
44
+ * Get QA attempt count for an issue
45
+ */
46
+ export declare function getQAAttemptCount(issueId: string): Promise<number>;
47
+ /**
48
+ * Mark an issue as having just failed QA (prevents immediate re-trigger)
49
+ */
50
+ export declare function markQAFailed(issueId: string, reason?: string): Promise<void>;
51
+ /**
52
+ * Check if an issue just failed QA (within cooldown period)
53
+ */
54
+ export declare function didJustFailQA(issueId: string): Promise<boolean>;
55
+ /**
56
+ * Clear QA failed marker (when issue is fixed and moves to Finished again)
57
+ */
58
+ export declare function clearQAFailed(issueId: string): Promise<void>;
59
+ /**
60
+ * Clear agent worked record (e.g., when issue is moved back to Backlog)
61
+ */
62
+ export declare function clearAgentWorked(issueId: string): Promise<void>;
63
+ /**
64
+ * Clear QA attempt record (e.g., after successful QA)
65
+ */
66
+ export declare function clearQAAttempts(issueId: string): Promise<void>;
67
+ /**
68
+ * Mark an issue as having development work just queued
69
+ * Prevents rapid re-queuing if status is toggled back and forth
70
+ */
71
+ export declare function markDevelopmentQueued(issueId: string): Promise<void>;
72
+ /**
73
+ * Check if development work was just queued for an issue (within cooldown period)
74
+ */
75
+ export declare function didJustQueueDevelopment(issueId: string): Promise<boolean>;
76
+ /**
77
+ * Clear development queued marker
78
+ */
79
+ export declare function clearDevelopmentQueued(issueId: string): Promise<void>;
80
+ /**
81
+ * Mark an issue as having acceptance work just queued
82
+ * Prevents rapid re-queuing if status is toggled back and forth
83
+ */
84
+ export declare function markAcceptanceQueued(issueId: string): Promise<void>;
85
+ /**
86
+ * Check if acceptance work was just queued for an issue (within cooldown period)
87
+ */
88
+ export declare function didJustQueueAcceptance(issueId: string): Promise<boolean>;
89
+ /**
90
+ * Clear acceptance queued marker
91
+ */
92
+ export declare function clearAcceptanceQueued(issueId: string): Promise<void>;
93
+ /**
94
+ * Clean up all tracking data for an accepted issue
95
+ * Called after successful acceptance processing to remove all Redis state
96
+ */
97
+ export declare function cleanupAcceptedIssue(issueId: string): Promise<void>;
98
+ //# sourceMappingURL=agent-tracking.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"agent-tracking.d.ts","sourceRoot":"","sources":["../../src/agent-tracking.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAyBH;;GAEG;AACH,MAAM,WAAW,eAAe;IAC9B,OAAO,EAAE,MAAM,CAAA;IACf,eAAe,EAAE,MAAM,CAAA;IACvB,WAAW,EAAE,MAAM,CAAA;IACnB,SAAS,EAAE,MAAM,CAAA;IACjB,KAAK,CAAC,EAAE,MAAM,CAAA;CACf;AAED;;GAEG;AACH,MAAM,WAAW,eAAe;IAC9B,OAAO,EAAE,MAAM,CAAA;IACf,aAAa,EAAE,MAAM,CAAA;IACrB,SAAS,EAAE,MAAM,CAAA;IACjB,SAAS,EAAE,MAAM,CAAA;IACjB,gBAAgB,EAAE,KAAK,CAAC;QACtB,SAAS,EAAE,MAAM,CAAA;QACjB,QAAQ,EAAE,MAAM,CAAA;QAChB,MAAM,CAAC,EAAE,MAAM,CAAA;KAChB,CAAC,CAAA;CACH;AAED;;GAEG;AACH,wBAAsB,eAAe,CACnC,OAAO,EAAE,MAAM,EACf,IAAI,EAAE,IAAI,CAAC,eAAe,EAAE,SAAS,GAAG,aAAa,CAAC,GACrD,OAAO,CAAC,IAAI,CAAC,CAYf;AAED;;GAEG;AACH,wBAAsB,cAAc,CAClC,OAAO,EAAE,MAAM,GACd,OAAO,CAAC,eAAe,GAAG,IAAI,CAAC,CAGjC;AAED;;GAEG;AACH,wBAAsB,eAAe,CACnC,OAAO,EAAE,MAAM,EACf,SAAS,EAAE,MAAM,GAChB,OAAO,CAAC,eAAe,CAAC,CA2B1B;AAED;;GAEG;AACH,wBAAsB,iBAAiB,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAIxE;AAED;;GAEG;AACH,wBAAsB,YAAY,CAChC,OAAO,EAAE,MAAM,EACf,MAAM,CAAC,EAAE,MAAM,GACd,OAAO,CAAC,IAAI,CAAC,CAIf;AAED;;GAEG;AACH,wBAAsB,aAAa,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,CAGrE;AAED;;GAEG;AACH,wBAAsB,aAAa,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAIlE;AAED;;GAEG;AACH,wBAAsB,gBAAgB,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAIrE;AAED;;GAEG;AACH,wBAAsB,eAAe,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAIpE;AAED;;;GAGG;AACH,wBAAsB,qBAAqB,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAI1E;AAED;;GAEG;AACH,wBAAsB,uBAAuB,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,CAG/E;AAED;;GAEG;AACH,wBAAsB,sBAAsB,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAI3E;AAED;;;GAGG;AACH,wBAAsB,oBAAoB,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAIzE;AAED;;GAEG;AACH,wBAAsB,sBAAsB,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,CAG9E;AAED;;GAEG;AACH,wBAAsB,qBAAqB,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAI1E;AAED;;;GAGG;AACH,wBAAsB,oBAAoB,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAWzE"}
@@ -0,0 +1,185 @@
1
+ /**
2
+ * Agent Tracking Module
3
+ *
4
+ * Tracks which issues the agent has worked on to enable automated QA pickup.
5
+ * Also tracks QA attempts to prevent infinite loops.
6
+ */
7
+ import { redisSet, redisGet, redisExists, redisDel } from './redis';
8
+ const log = {
9
+ info: (msg, data) => console.log(`[tracking] ${msg}`, data ? JSON.stringify(data) : ''),
10
+ warn: (msg, data) => console.warn(`[tracking] ${msg}`, data ? JSON.stringify(data) : ''),
11
+ error: (msg, data) => console.error(`[tracking] ${msg}`, data ? JSON.stringify(data) : ''),
12
+ debug: (_msg, _data) => { },
13
+ };
14
+ // Redis key prefixes
15
+ const AGENT_WORKED_PREFIX = 'agent:worked:';
16
+ const QA_ATTEMPT_PREFIX = 'qa:attempt:';
17
+ const QA_FAILED_PREFIX = 'qa:failed:';
18
+ const DEV_QUEUED_PREFIX = 'agent:dev-queued:';
19
+ const ACCEPTANCE_QUEUED_PREFIX = 'agent:acceptance-queued:';
20
+ // TTLs in seconds
21
+ const AGENT_WORKED_TTL = 7 * 24 * 60 * 60; // 7 days
22
+ const QA_ATTEMPT_TTL = 24 * 60 * 60; // 24 hours
23
+ const QA_FAILED_TTL = 60 * 60; // 1 hour
24
+ const DEV_QUEUED_TTL = 10; // 10 seconds - just enough to prevent duplicate webhooks
25
+ const ACCEPTANCE_QUEUED_TTL = 10; // 10 seconds - just enough to prevent duplicate webhooks
26
+ /**
27
+ * Mark an issue as having been worked on by the agent
28
+ */
29
+ export async function markAgentWorked(issueId, data) {
30
+ const key = `${AGENT_WORKED_PREFIX}${issueId}`;
31
+ const record = {
32
+ ...data,
33
+ issueId,
34
+ completedAt: Date.now(),
35
+ };
36
+ await redisSet(key, record, AGENT_WORKED_TTL);
37
+ log.info('Marked agent worked', {
38
+ issueId,
39
+ issueIdentifier: data.issueIdentifier,
40
+ });
41
+ }
42
+ /**
43
+ * Check if an issue was worked on by the agent
44
+ */
45
+ export async function wasAgentWorked(issueId) {
46
+ const key = `${AGENT_WORKED_PREFIX}${issueId}`;
47
+ return redisGet(key);
48
+ }
49
+ /**
50
+ * Record a QA attempt for an issue
51
+ */
52
+ export async function recordQAAttempt(issueId, sessionId) {
53
+ const key = `${QA_ATTEMPT_PREFIX}${issueId}`;
54
+ const existing = await redisGet(key);
55
+ const record = {
56
+ issueId,
57
+ attemptNumber: (existing?.attemptNumber ?? 0) + 1,
58
+ startedAt: Date.now(),
59
+ sessionId,
60
+ previousAttempts: existing?.previousAttempts ?? [],
61
+ };
62
+ // Add current attempt to history if this is a retry
63
+ if (existing) {
64
+ record.previousAttempts.push({
65
+ sessionId: existing.sessionId,
66
+ failedAt: Date.now(),
67
+ });
68
+ }
69
+ await redisSet(key, record, QA_ATTEMPT_TTL);
70
+ log.info('Recorded QA attempt', {
71
+ issueId,
72
+ attemptNumber: record.attemptNumber,
73
+ });
74
+ return record;
75
+ }
76
+ /**
77
+ * Get QA attempt count for an issue
78
+ */
79
+ export async function getQAAttemptCount(issueId) {
80
+ const key = `${QA_ATTEMPT_PREFIX}${issueId}`;
81
+ const record = await redisGet(key);
82
+ return record?.attemptNumber ?? 0;
83
+ }
84
+ /**
85
+ * Mark an issue as having just failed QA (prevents immediate re-trigger)
86
+ */
87
+ export async function markQAFailed(issueId, reason) {
88
+ const key = `${QA_FAILED_PREFIX}${issueId}`;
89
+ await redisSet(key, { failedAt: Date.now(), reason }, QA_FAILED_TTL);
90
+ log.info('Marked QA failed', { issueId, reason });
91
+ }
92
+ /**
93
+ * Check if an issue just failed QA (within cooldown period)
94
+ */
95
+ export async function didJustFailQA(issueId) {
96
+ const key = `${QA_FAILED_PREFIX}${issueId}`;
97
+ return redisExists(key);
98
+ }
99
+ /**
100
+ * Clear QA failed marker (when issue is fixed and moves to Finished again)
101
+ */
102
+ export async function clearQAFailed(issueId) {
103
+ const key = `${QA_FAILED_PREFIX}${issueId}`;
104
+ await redisDel(key);
105
+ log.debug('Cleared QA failed marker', { issueId });
106
+ }
107
+ /**
108
+ * Clear agent worked record (e.g., when issue is moved back to Backlog)
109
+ */
110
+ export async function clearAgentWorked(issueId) {
111
+ const key = `${AGENT_WORKED_PREFIX}${issueId}`;
112
+ await redisDel(key);
113
+ log.debug('Cleared agent worked marker', { issueId });
114
+ }
115
+ /**
116
+ * Clear QA attempt record (e.g., after successful QA)
117
+ */
118
+ export async function clearQAAttempts(issueId) {
119
+ const key = `${QA_ATTEMPT_PREFIX}${issueId}`;
120
+ await redisDel(key);
121
+ log.debug('Cleared QA attempts', { issueId });
122
+ }
123
+ /**
124
+ * Mark an issue as having development work just queued
125
+ * Prevents rapid re-queuing if status is toggled back and forth
126
+ */
127
+ export async function markDevelopmentQueued(issueId) {
128
+ const key = `${DEV_QUEUED_PREFIX}${issueId}`;
129
+ await redisSet(key, { queuedAt: Date.now() }, DEV_QUEUED_TTL);
130
+ log.info('Marked development queued', { issueId });
131
+ }
132
+ /**
133
+ * Check if development work was just queued for an issue (within cooldown period)
134
+ */
135
+ export async function didJustQueueDevelopment(issueId) {
136
+ const key = `${DEV_QUEUED_PREFIX}${issueId}`;
137
+ return redisExists(key);
138
+ }
139
+ /**
140
+ * Clear development queued marker
141
+ */
142
+ export async function clearDevelopmentQueued(issueId) {
143
+ const key = `${DEV_QUEUED_PREFIX}${issueId}`;
144
+ await redisDel(key);
145
+ log.debug('Cleared development queued marker', { issueId });
146
+ }
147
+ /**
148
+ * Mark an issue as having acceptance work just queued
149
+ * Prevents rapid re-queuing if status is toggled back and forth
150
+ */
151
+ export async function markAcceptanceQueued(issueId) {
152
+ const key = `${ACCEPTANCE_QUEUED_PREFIX}${issueId}`;
153
+ await redisSet(key, { queuedAt: Date.now() }, ACCEPTANCE_QUEUED_TTL);
154
+ log.info('Marked acceptance queued', { issueId });
155
+ }
156
+ /**
157
+ * Check if acceptance work was just queued for an issue (within cooldown period)
158
+ */
159
+ export async function didJustQueueAcceptance(issueId) {
160
+ const key = `${ACCEPTANCE_QUEUED_PREFIX}${issueId}`;
161
+ return redisExists(key);
162
+ }
163
+ /**
164
+ * Clear acceptance queued marker
165
+ */
166
+ export async function clearAcceptanceQueued(issueId) {
167
+ const key = `${ACCEPTANCE_QUEUED_PREFIX}${issueId}`;
168
+ await redisDel(key);
169
+ log.debug('Cleared acceptance queued marker', { issueId });
170
+ }
171
+ /**
172
+ * Clean up all tracking data for an accepted issue
173
+ * Called after successful acceptance processing to remove all Redis state
174
+ */
175
+ export async function cleanupAcceptedIssue(issueId) {
176
+ const keysToDelete = [
177
+ `${AGENT_WORKED_PREFIX}${issueId}`,
178
+ `${QA_ATTEMPT_PREFIX}${issueId}`,
179
+ `${QA_FAILED_PREFIX}${issueId}`,
180
+ `${DEV_QUEUED_PREFIX}${issueId}`,
181
+ `${ACCEPTANCE_QUEUED_PREFIX}${issueId}`,
182
+ ];
183
+ await Promise.all(keysToDelete.map((key) => redisDel(key)));
184
+ log.info('Cleaned up all tracking data for accepted issue', { issueId });
185
+ }
@@ -0,0 +1,9 @@
1
+ export * from './types';
2
+ export * from './redis';
3
+ export * from './session-storage';
4
+ export * from './work-queue';
5
+ export * from './worker-storage';
6
+ export * from './issue-lock';
7
+ export * from './agent-tracking';
8
+ export * from './webhook-idempotency';
9
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":"AACA,cAAc,SAAS,CAAA;AAGvB,cAAc,SAAS,CAAA;AAGvB,cAAc,mBAAmB,CAAA;AAGjC,cAAc,cAAc,CAAA;AAG5B,cAAc,kBAAkB,CAAA;AAGhC,cAAc,cAAc,CAAA;AAG5B,cAAc,kBAAkB,CAAA;AAGhC,cAAc,uBAAuB,CAAA"}
@@ -0,0 +1,16 @@
1
+ // Types
2
+ export * from './types';
3
+ // Redis client
4
+ export * from './redis';
5
+ // Session management
6
+ export * from './session-storage';
7
+ // Work queue
8
+ export * from './work-queue';
9
+ // Worker pool
10
+ export * from './worker-storage';
11
+ // Issue locking
12
+ export * from './issue-lock';
13
+ // Agent tracking
14
+ export * from './agent-tracking';
15
+ // Webhook idempotency
16
+ export * from './webhook-idempotency';
@@ -0,0 +1,126 @@
1
+ /**
2
+ * Issue Lock Module
3
+ *
4
+ * Prevents overlapping agents for the same issue by providing:
5
+ * - Per-issue mutex (Redis SET NX) that gates work dispatch
6
+ * - Per-issue pending queue for parking incoming work while locked
7
+ * - Automatic promotion: releasing a lock dispatches the next pending item
8
+ *
9
+ * Redis Keys:
10
+ * - issue:lock:{issueId} -- String (JSON IssueLock), 2hr TTL
11
+ * - issue:pending:{issueId} -- Sorted Set (priority-ordered session IDs)
12
+ * - issue:pending:items:{issueId} -- Hash (sessionId -> JSON QueuedWork)
13
+ */
14
+ import { type QueuedWork } from './work-queue';
15
+ import type { AgentWorkType } from './types';
16
+ /**
17
+ * Lock payload stored in Redis
18
+ */
19
+ export interface IssueLock {
20
+ sessionId: string;
21
+ workType: AgentWorkType;
22
+ workerId: string | null;
23
+ lockedAt: number;
24
+ issueIdentifier: string;
25
+ }
26
+ /**
27
+ * Result of a dispatchWork call
28
+ */
29
+ export interface DispatchResult {
30
+ dispatched: boolean;
31
+ parked: boolean;
32
+ replaced: boolean;
33
+ }
34
+ /**
35
+ * Acquire an issue-level lock.
36
+ * Uses SET NX for atomicity -- only one caller wins.
37
+ *
38
+ * @returns true if lock was acquired
39
+ */
40
+ export declare function acquireIssueLock(issueId: string, lock: IssueLock): Promise<boolean>;
41
+ /**
42
+ * Read the current lock for an issue.
43
+ */
44
+ export declare function getIssueLock(issueId: string): Promise<IssueLock | null>;
45
+ /**
46
+ * Release an issue lock. Idempotent.
47
+ */
48
+ export declare function releaseIssueLock(issueId: string): Promise<void>;
49
+ /**
50
+ * Refresh the TTL on an issue lock (extend while agent is alive).
51
+ */
52
+ export declare function refreshIssueLockTTL(issueId: string, ttlSeconds?: number): Promise<boolean>;
53
+ /**
54
+ * Park work for a locked issue.
55
+ *
56
+ * Deduplication: at most one parked item per workType per issue.
57
+ * If a parked item with the same workType already exists, it's replaced
58
+ * (the latest webhook wins). Different workTypes can coexist.
59
+ */
60
+ export declare function parkWorkForIssue(issueId: string, work: QueuedWork): Promise<{
61
+ parked: boolean;
62
+ replaced: boolean;
63
+ }>;
64
+ /**
65
+ * Promote the next pending work item for an issue.
66
+ * Pops the highest-priority item, acquires the issue lock for it,
67
+ * and queues it in the global work queue.
68
+ *
69
+ * @returns The promoted work item, or null if nothing to promote
70
+ */
71
+ export declare function promoteNextPendingWork(issueId: string): Promise<QueuedWork | null>;
72
+ /**
73
+ * Get the count of pending work items for an issue.
74
+ */
75
+ export declare function getPendingWorkCount(issueId: string): Promise<number>;
76
+ /**
77
+ * Main entry point for dispatching work.
78
+ *
79
+ * Try to acquire the issue lock:
80
+ * - If acquired -> queue the work in the global queue
81
+ * - If locked -> park the work in the per-issue pending queue
82
+ *
83
+ * @returns DispatchResult indicating what happened
84
+ */
85
+ export declare function dispatchWork(work: QueuedWork): Promise<DispatchResult>;
86
+ /**
87
+ * Remove a parked work item by sessionId.
88
+ *
89
+ * The issue-pending hash is keyed by workType, so we scan all entries
90
+ * to find the one matching the given sessionId.
91
+ *
92
+ * @returns true if a matching parked item was found and removed
93
+ */
94
+ export declare function removeParkedWorkBySessionId(issueId: string, sessionId: string): Promise<boolean>;
95
+ /**
96
+ * Check if a session is parked in any issue-pending queue.
97
+ *
98
+ * Scans the issue:pending:items:{issueId} hash entries for a matching sessionId.
99
+ *
100
+ * @param issueId - The issue to check
101
+ * @param sessionId - The session to look for
102
+ * @returns true if the session is parked for this issue
103
+ */
104
+ export declare function isSessionParkedForIssue(issueId: string, sessionId: string): Promise<boolean>;
105
+ /**
106
+ * Scan for expired issue locks that have pending work.
107
+ * If a lock expired naturally (TTL) but pending items remain, promote them.
108
+ *
109
+ * Called from orphan-cleanup to handle crashed workers that didn't release locks.
110
+ */
111
+ export declare function cleanupExpiredLocksWithPendingWork(): Promise<number>;
112
+ /**
113
+ * Release issue locks held by sessions that have already reached a terminal state.
114
+ *
115
+ * This handles the case where a session completes but the lock release failed
116
+ * (e.g., network error during cleanup). The lock's 2-hour TTL would eventually
117
+ * expire, but this proactively clears it when workers have idle capacity.
118
+ *
119
+ * Only runs when workers are online -- if no workers are available, there's no
120
+ * point promoting parked work since nothing can pick it up.
121
+ *
122
+ * @param hasIdleWorkers - true if at least one worker is online with spare capacity
123
+ * @returns Number of stale locks released and parked work promoted
124
+ */
125
+ export declare function cleanupStaleLocksWithIdleWorkers(hasIdleWorkers: boolean): Promise<number>;
126
+ //# sourceMappingURL=issue-lock.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"issue-lock.d.ts","sourceRoot":"","sources":["../../src/issue-lock.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAmBH,OAAO,EAAa,KAAK,UAAU,EAAE,MAAM,cAAc,CAAA;AAEzD,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,SAAS,CAAA;AAoB5C;;GAEG;AACH,MAAM,WAAW,SAAS;IACxB,SAAS,EAAE,MAAM,CAAA;IACjB,QAAQ,EAAE,aAAa,CAAA;IACvB,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAA;IACvB,QAAQ,EAAE,MAAM,CAAA;IAChB,eAAe,EAAE,MAAM,CAAA;CACxB;AAED;;GAEG;AACH,MAAM,WAAW,cAAc;IAC7B,UAAU,EAAE,OAAO,CAAA;IACnB,MAAM,EAAE,OAAO,CAAA;IACf,QAAQ,EAAE,OAAO,CAAA;CAClB;AAED;;;;;GAKG;AACH,wBAAsB,gBAAgB,CACpC,OAAO,EAAE,MAAM,EACf,IAAI,EAAE,SAAS,GACd,OAAO,CAAC,OAAO,CAAC,CA4BlB;AAED;;GAEG;AACH,wBAAsB,YAAY,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,SAAS,GAAG,IAAI,CAAC,CAY7E;AAED;;GAEG;AACH,wBAAsB,gBAAgB,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAUrE;AAED;;GAEG;AACH,wBAAsB,mBAAmB,CACvC,OAAO,EAAE,MAAM,EACf,UAAU,GAAE,MAAyB,GACpC,OAAO,CAAC,OAAO,CAAC,CAUlB;AAED;;;;;;GAMG;AACH,wBAAsB,gBAAgB,CACpC,OAAO,EAAE,MAAM,EACf,IAAI,EAAE,UAAU,GACf,OAAO,CAAC;IAAE,MAAM,EAAE,OAAO,CAAC;IAAC,QAAQ,EAAE,OAAO,CAAA;CAAE,CAAC,CAoDjD;AAED;;;;;;GAMG;AACH,wBAAsB,sBAAsB,CAC1C,OAAO,EAAE,MAAM,GACd,OAAO,CAAC,UAAU,GAAG,IAAI,CAAC,CAqE5B;AAED;;GAEG;AACH,wBAAsB,mBAAmB,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAU1E;AAED;;;;;;;;GAQG;AACH,wBAAsB,YAAY,CAAC,IAAI,EAAE,UAAU,GAAG,OAAO,CAAC,cAAc,CAAC,CAoD5E;AAED;;;;;;;GAOG;AACH,wBAAsB,2BAA2B,CAC/C,OAAO,EAAE,MAAM,EACf,SAAS,EAAE,MAAM,GAChB,OAAO,CAAC,OAAO,CAAC,CAsClB;AAED;;;;;;;;GAQG;AACH,wBAAsB,uBAAuB,CAC3C,OAAO,EAAE,MAAM,EACf,SAAS,EAAE,MAAM,GAChB,OAAO,CAAC,OAAO,CAAC,CAwBlB;AAED;;;;;GAKG;AACH,wBAAsB,kCAAkC,IAAI,OAAO,CAAC,MAAM,CAAC,CA0C1E;AAID;;;;;;;;;;;;GAYG;AACH,wBAAsB,gCAAgC,CACpD,cAAc,EAAE,OAAO,GACtB,OAAO,CAAC,MAAM,CAAC,CAgEjB"}