@supaku/agentfactory-server 0.7.37 → 0.7.39

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.
@@ -110,11 +110,14 @@ export declare function isSessionParkedForIssue(issueId: string, sessionId: stri
110
110
  */
111
111
  export declare function cleanupExpiredLocksWithPendingWork(): Promise<number>;
112
112
  /**
113
- * Release issue locks held by sessions that have already reached a terminal state.
113
+ * Release issue locks held by sessions that should no longer hold them.
114
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.
115
+ * This handles cases where:
116
+ * - A session completes but the lock release failed (network error during cleanup)
117
+ * - Orphan cleanup resets a session to 'pending' but the lock wasn't released
118
+ *
119
+ * The lock's 2-hour TTL would eventually expire, but this proactively clears it
120
+ * when workers have idle capacity.
118
121
  *
119
122
  * Only runs when workers are online -- if no workers are available, there's no
120
123
  * point promoting parked work since nothing can pick it up.
@@ -1 +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,iBAAiB,CAAA;AAE5D,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,YAAY,CAAA;AAoB/C;;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,CA6C1E;AAID;;;;;;;;;;;;GAYG;AACH,wBAAsB,gCAAgC,CACpD,cAAc,EAAE,OAAO,GACtB,OAAO,CAAC,MAAM,CAAC,CAgEjB"}
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,iBAAiB,CAAA;AAE5D,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,YAAY,CAAA;AAoB/C;;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,CA6C1E;AAOD;;;;;;;;;;;;;;;GAeG;AACH,wBAAsB,gCAAgC,CACpD,cAAc,EAAE,OAAO,GACtB,OAAO,CAAC,MAAM,CAAC,CAgEjB"}
@@ -424,13 +424,19 @@ export async function cleanupExpiredLocksWithPendingWork() {
424
424
  }
425
425
  return promoted;
426
426
  }
427
- const TERMINAL_STATUSES = new Set(['completed', 'failed', 'stopped']);
427
+ // Statuses where a session should NOT be holding an issue lock.
428
+ // Terminal: session finished (completed/failed/stopped) but lock release failed.
429
+ // Pending: session was reset by orphan cleanup but lock wasn't released (see orphan-cleanup.ts).
430
+ const STALE_LOCK_STATUSES = new Set(['completed', 'failed', 'stopped', 'pending']);
428
431
  /**
429
- * Release issue locks held by sessions that have already reached a terminal state.
432
+ * Release issue locks held by sessions that should no longer hold them.
430
433
  *
431
- * This handles the case where a session completes but the lock release failed
432
- * (e.g., network error during cleanup). The lock's 2-hour TTL would eventually
433
- * expire, but this proactively clears it when workers have idle capacity.
434
+ * This handles cases where:
435
+ * - A session completes but the lock release failed (network error during cleanup)
436
+ * - Orphan cleanup resets a session to 'pending' but the lock wasn't released
437
+ *
438
+ * The lock's 2-hour TTL would eventually expire, but this proactively clears it
439
+ * when workers have idle capacity.
434
440
  *
435
441
  * Only runs when workers are online -- if no workers are available, there's no
436
442
  * point promoting parked work since nothing can pick it up.
@@ -471,8 +477,8 @@ export async function cleanupStaleLocksWithIdleWorkers(hasIdleWorkers) {
471
477
  promoted++;
472
478
  continue;
473
479
  }
474
- if (TERMINAL_STATUSES.has(session.status)) {
475
- log.info('Releasing stale lock (session already terminal)', {
480
+ if (STALE_LOCK_STATUSES.has(session.status)) {
481
+ log.info('Releasing stale lock (session no longer needs lock)', {
476
482
  issueId,
477
483
  sessionId: lock.sessionId,
478
484
  sessionStatus: session.status,
@@ -1 +1 @@
1
- {"version":3,"file":"orphan-cleanup.d.ts","sourceRoot":"","sources":["../../src/orphan-cleanup.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAGH,OAAO,EAGL,KAAK,iBAAiB,EACvB,MAAM,sBAAsB,CAAA;AAmB7B;;GAEG;AACH,MAAM,WAAW,sBAAsB;IACrC,sFAAsF;IACtF,gBAAgB,CAAC,EAAE,CAAC,OAAO,EAAE,iBAAiB,KAAK,OAAO,CAAC,IAAI,CAAC,CAAA;IAChE,2FAA2F;IAC3F,iBAAiB,CAAC,EAAE,CAAC,OAAO,EAAE,iBAAiB,KAAK,OAAO,CAAC,IAAI,CAAC,CAAA;CAClE;AAED,MAAM,WAAW,mBAAmB;IAClC,OAAO,EAAE,MAAM,CAAA;IACf,QAAQ,EAAE,MAAM,CAAA;IAChB,QAAQ,EAAE,MAAM,CAAA;IAChB,MAAM,EAAE,MAAM,CAAA;IACd,OAAO,EAAE,KAAK,CAAC;QACb,SAAS,EAAE,MAAM,CAAA;QACjB,eAAe,EAAE,MAAM,CAAA;QACvB,MAAM,EAAE,UAAU,GAAG,QAAQ,CAAA;QAC7B,MAAM,CAAC,EAAE,MAAM,CAAA;QACf,oEAAoE;QACpE,YAAY,CAAC,EAAE,MAAM,CAAA;KACtB,CAAC,CAAA;IACF,0DAA0D;IAC1D,sBAAsB,EAAE,MAAM,EAAE,CAAA;CACjC;AAED;;GAEG;AACH,wBAAsB,oBAAoB,IAAI,OAAO,CAAC,iBAAiB,EAAE,CAAC,CAuDzE;AAKD;;;;;;;GAOG;AACH,wBAAsB,yBAAyB,IAAI,OAAO,CAAC,iBAAiB,EAAE,CAAC,CAiC9E;AAED;;;;GAIG;AACH,wBAAsB,uBAAuB,CAC3C,SAAS,CAAC,EAAE,sBAAsB,GACjC,OAAO,CAAC,mBAAmB,CAAC,CA+N9B;AASD,wBAAgB,gBAAgB,IAAI,OAAO,CAO1C;AAED;;;;;GAKG;AACH,wBAAsB,mBAAmB,CACvC,SAAS,CAAC,EAAE,sBAAsB,GACjC,OAAO,CAAC,mBAAmB,GAAG,IAAI,CAAC,CAKrC"}
1
+ {"version":3,"file":"orphan-cleanup.d.ts","sourceRoot":"","sources":["../../src/orphan-cleanup.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAGH,OAAO,EAGL,KAAK,iBAAiB,EACvB,MAAM,sBAAsB,CAAA;AAqB7B;;GAEG;AACH,MAAM,WAAW,sBAAsB;IACrC,sFAAsF;IACtF,gBAAgB,CAAC,EAAE,CAAC,OAAO,EAAE,iBAAiB,KAAK,OAAO,CAAC,IAAI,CAAC,CAAA;IAChE,2FAA2F;IAC3F,iBAAiB,CAAC,EAAE,CAAC,OAAO,EAAE,iBAAiB,KAAK,OAAO,CAAC,IAAI,CAAC,CAAA;CAClE;AAED,MAAM,WAAW,mBAAmB;IAClC,OAAO,EAAE,MAAM,CAAA;IACf,QAAQ,EAAE,MAAM,CAAA;IAChB,QAAQ,EAAE,MAAM,CAAA;IAChB,MAAM,EAAE,MAAM,CAAA;IACd,OAAO,EAAE,KAAK,CAAC;QACb,SAAS,EAAE,MAAM,CAAA;QACjB,eAAe,EAAE,MAAM,CAAA;QACvB,MAAM,EAAE,UAAU,GAAG,QAAQ,CAAA;QAC7B,MAAM,CAAC,EAAE,MAAM,CAAA;QACf,oEAAoE;QACpE,YAAY,CAAC,EAAE,MAAM,CAAA;KACtB,CAAC,CAAA;IACF,0DAA0D;IAC1D,sBAAsB,EAAE,MAAM,EAAE,CAAA;CACjC;AAED;;GAEG;AACH,wBAAsB,oBAAoB,IAAI,OAAO,CAAC,iBAAiB,EAAE,CAAC,CAuDzE;AAKD;;;;;;;GAOG;AACH,wBAAsB,yBAAyB,IAAI,OAAO,CAAC,iBAAiB,EAAE,CAAC,CAiC9E;AAED;;;;GAIG;AACH,wBAAsB,uBAAuB,CAC3C,SAAS,CAAC,EAAE,sBAAsB,GACjC,OAAO,CAAC,mBAAmB,CAAC,CAyP9B;AASD,wBAAgB,gBAAgB,IAAI,OAAO,CAO1C;AAED;;;;;GAKG;AACH,wBAAsB,mBAAmB,CACvC,SAAS,CAAC,EAAE,sBAAsB,GACjC,OAAO,CAAC,mBAAmB,GAAG,IAAI,CAAC,CAKrC"}
@@ -12,7 +12,7 @@ import { createLogger } from './logger.js';
12
12
  import { getAllSessions, resetSessionForRequeue, } from './session-storage.js';
13
13
  import { listWorkers } from './worker-storage.js';
14
14
  import { releaseClaim, isSessionInQueue, } from './work-queue.js';
15
- import { dispatchWork, cleanupExpiredLocksWithPendingWork, cleanupStaleLocksWithIdleWorkers, isSessionParkedForIssue, } from './issue-lock.js';
15
+ import { dispatchWork, cleanupExpiredLocksWithPendingWork, cleanupStaleLocksWithIdleWorkers, isSessionParkedForIssue, getIssueLock, releaseIssueLock, } from './issue-lock.js';
16
16
  const log = createLogger('orphan-cleanup');
17
17
  // How long a session can be running without a valid worker before being considered orphaned
18
18
  const ORPHAN_THRESHOLD_MS = 120_000; // 2 minutes (worker TTL + buffer)
@@ -138,6 +138,19 @@ export async function cleanupOrphanedSessions(callbacks) {
138
138
  });
139
139
  // Release any existing claim
140
140
  await releaseClaim(session.linearSessionId);
141
+ // Release the issue lock if held by this orphaned session.
142
+ // Without this, dispatchWork() below would fail to acquire the lock
143
+ // (SET NX) and park the work instead — leaving it stuck until the
144
+ // lock's 2-hour TTL expires, since the session is reset to 'pending'
145
+ // which the stale-lock cleanup doesn't consider terminal.
146
+ const existingLock = await getIssueLock(session.issueId);
147
+ if (existingLock && existingLock.sessionId === session.linearSessionId) {
148
+ log.info('Releasing issue lock held by orphaned session', {
149
+ sessionId: session.linearSessionId,
150
+ issueId: session.issueId,
151
+ });
152
+ await releaseIssueLock(session.issueId);
153
+ }
141
154
  // Reset session for requeue (clears workerId so new worker can claim)
142
155
  await resetSessionForRequeue(session.linearSessionId);
143
156
  // Re-queue the work with higher priority
@@ -153,6 +166,7 @@ export async function cleanupOrphanedSessions(callbacks) {
153
166
  prompt: session.promptContext,
154
167
  // providerSessionId intentionally omitted - don't resume crashed sessions
155
168
  workType: session.workType,
169
+ projectName: session.projectName,
156
170
  };
157
171
  const dispatchResult = await dispatchWork(work);
158
172
  if (dispatchResult.dispatched || dispatchResult.parked) {
@@ -214,6 +228,15 @@ export async function cleanupOrphanedSessions(callbacks) {
214
228
  sessionId: session.linearSessionId,
215
229
  issueIdentifier,
216
230
  });
231
+ // Release issue lock if held by this zombie session (same rationale as orphan cleanup)
232
+ const existingLock = await getIssueLock(session.issueId);
233
+ if (existingLock && existingLock.sessionId === session.linearSessionId) {
234
+ log.info('Releasing issue lock held by zombie session', {
235
+ sessionId: session.linearSessionId,
236
+ issueId: session.issueId,
237
+ });
238
+ await releaseIssueLock(session.issueId);
239
+ }
217
240
  const work = {
218
241
  sessionId: session.linearSessionId,
219
242
  issueId: session.issueId,
@@ -222,6 +245,7 @@ export async function cleanupOrphanedSessions(callbacks) {
222
245
  queuedAt: Date.now(),
223
246
  prompt: session.promptContext,
224
247
  workType: session.workType,
248
+ projectName: session.projectName,
225
249
  };
226
250
  const dispatchResult = await dispatchWork(work);
227
251
  if (dispatchResult.dispatched || dispatchResult.parked) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@supaku/agentfactory-server",
3
- "version": "0.7.37",
3
+ "version": "0.7.39",
4
4
  "type": "module",
5
5
  "description": "Webhook server and distributed worker pool for AgentFactory — Redis queues, issue locks, session management",
6
6
  "author": "Supaku (https://supaku.com)",
@@ -44,8 +44,8 @@
44
44
  ],
45
45
  "dependencies": {
46
46
  "ioredis": "^5.4.2",
47
- "@supaku/agentfactory": "0.7.37",
48
- "@supaku/agentfactory-linear": "0.7.37"
47
+ "@supaku/agentfactory": "0.7.39",
48
+ "@supaku/agentfactory-linear": "0.7.39"
49
49
  },
50
50
  "devDependencies": {
51
51
  "@types/node": "^22.5.4",