@workerdeck/queue 3.0.1 → 3.2.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/build/index.d.mts CHANGED
@@ -14,6 +14,7 @@ interface QueueAdapter {
14
14
  prune(olderThanMs: number): Promise<number>;
15
15
  addDailyTokens(dayKey: string, tokens: number): Promise<number>;
16
16
  dailyTokens(dayKey: string): Promise<number>;
17
+ nextRunAt?(): Promise<number | undefined>;
17
18
  onWork?(listener: () => void): () => void;
18
19
  }
19
20
  declare class InMemoryQueueAdapter implements QueueAdapter {
@@ -26,6 +27,7 @@ declare class InMemoryQueueAdapter implements QueueAdapter {
26
27
  prune(olderThanMs: number): Promise<number>;
27
28
  addDailyTokens(dayKey: string, tokens: number): Promise<number>;
28
29
  dailyTokens(dayKey: string): Promise<number>;
30
+ nextRunAt(): Promise<number | undefined>;
29
31
  }
30
32
  //#endregion
31
33
  //#region src/queue.d.ts
@@ -48,6 +50,12 @@ type JobQueueOptions = {
48
50
  webhookAttempts?: number;
49
51
  webhookRetryDelayMs?: number;
50
52
  onEvent?: (event: JobEvent) => void;
53
+ onError?: (error: unknown, context: JobQueueErrorContext) => void;
54
+ };
55
+ type JobQueueErrorPhase = 'pump' | 'start' | 'event' | 'finalize' | 'park' | 'resume';
56
+ type JobQueueErrorContext = {
57
+ jobId?: string;
58
+ phase: JobQueueErrorPhase;
51
59
  };
52
60
  declare class JobQueue {
53
61
  #private;
@@ -55,12 +63,16 @@ declare class JobQueue {
55
63
  submit(request: CreateJobRequest): Promise<JobInfo>;
56
64
  get(id: string): Promise<JobInfo | null>;
57
65
  list(): Promise<JobInfo[]>;
66
+ canParkSession(sessionId: string): boolean;
58
67
  onSessionParking(sessionId: string, executionId: string): boolean;
59
68
  onSessionResumed(sessionId: string, runner: Runner): void;
60
69
  cancel(id: string): Promise<JobInfo | null>;
70
+ pause(): void;
71
+ resume(): void;
72
+ get paused(): boolean;
61
73
  stats(): Promise<QueueStats>;
62
74
  close(): void;
63
75
  }
64
76
  //#endregion
65
- export { InMemoryQueueAdapter, JobQueue, type JobQueueOptions, type JobRecord, type QueueAdapter };
77
+ export { InMemoryQueueAdapter, JobQueue, type JobQueueErrorContext, type JobQueueErrorPhase, type JobQueueOptions, type JobRecord, type QueueAdapter };
66
78
  //# sourceMappingURL=index.d.mts.map
package/build/index.mjs CHANGED
@@ -1,6 +1,11 @@
1
1
  import { randomUUID } from "node:crypto";
2
2
  import { pickCreateSessionRequest } from "@workerdeck/protocol";
3
3
  //#region src/adapter.ts
4
+ function earliestRunAt(jobs, now) {
5
+ let earliest;
6
+ for (const { info } of jobs) if (info.status === "queued" && info.nextRunAt !== void 0 && info.nextRunAt > now) earliest = earliest === void 0 ? info.nextRunAt : Math.min(earliest, info.nextRunAt);
7
+ return earliest;
8
+ }
4
9
  var InMemoryQueueAdapter = class {
5
10
  #jobs = /* @__PURE__ */ new Map();
6
11
  #dailyTokens = /* @__PURE__ */ new Map();
@@ -54,9 +59,22 @@ var InMemoryQueueAdapter = class {
54
59
  dailyTokens(dayKey) {
55
60
  return Promise.resolve(this.#dailyTokens.get(dayKey) ?? 0);
56
61
  }
62
+ nextRunAt() {
63
+ return Promise.resolve(earliestRunAt(this.#jobs.values(), Date.now()));
64
+ }
57
65
  };
58
66
  //#endregion
59
67
  //#region src/queue.ts
68
+ const MAX_TIMER_MS = 2 ** 31 - 1;
69
+ const PUMP_RETRY_MS = 5e3;
70
+ function nextUtcMidnight(epochMs) {
71
+ const now = new Date(epochMs);
72
+ return Date.UTC(now.getUTCFullYear(), now.getUTCMonth(), now.getUTCDate() + 1);
73
+ }
74
+ function reportToConsole(error, context) {
75
+ const job = context.jobId ? ` (job ${context.jobId})` : "";
76
+ console.warn(`[workerdeck] queue ${context.phase} failed${job}: ${error instanceof Error ? error.message : String(error)}`);
77
+ }
60
78
  function dayKey(epochMs) {
61
79
  return new Date(epochMs).toISOString().slice(0, 10);
62
80
  }
@@ -94,15 +112,20 @@ var JobQueue = class {
94
112
  #adapter;
95
113
  #running = /* @__PURE__ */ new Map();
96
114
  #parked = /* @__PURE__ */ new Map();
115
+ #starting = /* @__PURE__ */ new Map();
97
116
  #pumping = false;
117
+ #pumpRequested = false;
118
+ #paused = false;
98
119
  #closed = false;
120
+ #wakeTimer;
121
+ #wakeAt = Number.POSITIVE_INFINITY;
99
122
  #offWork;
100
123
  #sweepTimer;
101
124
  #retryTimers = /* @__PURE__ */ new Set();
102
125
  constructor(options) {
103
126
  this.#options = options;
104
127
  this.#adapter = options.adapter ?? new InMemoryQueueAdapter();
105
- this.#offWork = this.#adapter.onWork?.(() => void this.#pump());
128
+ this.#offWork = this.#adapter.onWork?.(() => this.#pump());
106
129
  const retention = options.retention;
107
130
  if (retention) {
108
131
  const interval = retention.sweepIntervalMs ?? Math.min(retention.maxAgeMs, 6e4);
@@ -153,6 +176,10 @@ var JobQueue = class {
153
176
  async list() {
154
177
  return (await this.#adapter.list()).map((j) => j.info);
155
178
  }
179
+ canParkSession(sessionId) {
180
+ const job = this.#bySession(this.#running, sessionId);
181
+ return !job || !job.finalized && !job.killReason;
182
+ }
156
183
  onSessionParking(sessionId, executionId) {
157
184
  const job = this.#bySession(this.#running, sessionId);
158
185
  if (!job) return true;
@@ -166,28 +193,22 @@ var JobQueue = class {
166
193
  job.parkedExecutionId = executionId;
167
194
  this.#running.delete(job.record.info.id);
168
195
  this.#parked.set(job.record.info.id, job);
169
- const parkedLimit = this.#options.maxParkedDurationMs;
170
- if (parkedLimit !== void 0) {
171
- job.parkTimer = setTimeout(() => this.#kill(job, `job exceeded max parked duration (${parkedLimit}ms)`), Math.max(0, parkedLimit - job.parkedMs));
172
- job.parkTimer.unref?.();
173
- }
174
- this.#recordPark(job, executionId);
175
- return true;
176
- }
177
- async #recordPark(job, executionId) {
178
- const updated = await this.#adapter.update(job.record.info.id, {
196
+ this.#armLimit(job, "parkTimer", this.#options.maxParkedDurationMs, job.parkedMs, "max parked duration");
197
+ const patch = {
179
198
  status: "parked",
180
199
  parkedAt: job.parkedAt,
181
200
  parkedExecutionId: executionId
201
+ };
202
+ this.#spawn("park", job.record.info.id, async () => {
203
+ await this.#recordTransition(job, patch, (info) => ({
204
+ type: "job_parked",
205
+ job: info,
206
+ executionId,
207
+ ts: Date.now()
208
+ }));
209
+ this.#pump();
182
210
  });
183
- if (updated) job.record = updated;
184
- this.#emit(job.record, {
185
- type: "job_parked",
186
- job: job.record.info,
187
- executionId,
188
- ts: Date.now()
189
- }, job);
190
- this.#pump();
211
+ return true;
191
212
  }
192
213
  onSessionResumed(sessionId, runner) {
193
214
  const job = this.#bySession(this.#parked, sessionId);
@@ -203,27 +224,30 @@ var JobQueue = class {
203
224
  job.runner = runner;
204
225
  this.#parked.delete(job.record.info.id);
205
226
  this.#running.set(job.record.info.id, job);
206
- const durationLimit = this.#effectiveDurationLimit(job.record.request);
207
- if (durationLimit !== void 0) {
208
- job.durationTimer = setTimeout(() => this.#kill(job, `job exceeded max duration (${durationLimit}ms)`), Math.max(0, durationLimit - job.runningMs));
209
- job.durationTimer.unref?.();
210
- }
211
- job.unsubscribe = runner.subscribe((event) => void this.#handleEvent(job, event), job.lastSeq);
212
- this.#recordResume(job, executionId);
213
- }
214
- async #recordResume(job, executionId) {
215
- const updated = await this.#adapter.update(job.record.info.id, {
227
+ this.#armLimit(job, "durationTimer", this.#effectiveDurationLimit(job.record.request), job.runningMs, "max duration");
228
+ job.unsubscribe = this.#subscribe(job, job.lastSeq);
229
+ const patch = {
216
230
  status: "running",
217
231
  parkedAt: void 0,
218
232
  parkedExecutionId: void 0
219
- });
220
- if (updated) job.record = updated;
221
- this.#emit(job.record, {
233
+ };
234
+ this.#spawn("resume", job.record.info.id, () => this.#recordTransition(job, patch, (info) => ({
222
235
  type: "job_resumed",
223
- job: job.record.info,
236
+ job: info,
224
237
  executionId,
225
238
  ts: Date.now()
226
- }, job);
239
+ })));
240
+ }
241
+ async #recordTransition(job, patch, event) {
242
+ const updated = await this.#adapter.update(job.record.info.id, patch);
243
+ if (updated) job.record = updated;
244
+ this.#emit(job.record, event(job.record.info), job);
245
+ }
246
+ #armLimit(job, key, limit, spentMs, label) {
247
+ if (limit === void 0) return;
248
+ const timer = setTimeout(() => this.#kill(job, `job exceeded ${label} (${limit}ms)`), Math.max(0, limit - spentMs));
249
+ timer.unref?.();
250
+ job[key] = timer;
227
251
  }
228
252
  #bySession(jobs, sessionId) {
229
253
  for (const job of jobs.values()) if (job.record.info.sessionId === sessionId) return job;
@@ -238,7 +262,16 @@ var JobQueue = class {
238
262
  await this.#finalize(running, this.#abortPatch(running, "canceled"));
239
263
  return running.record.info;
240
264
  }
265
+ const starting = this.#starting.get(id);
266
+ if (starting) {
267
+ if (starting.canceled) return record.info;
268
+ starting.canceled = true;
269
+ return this.#markCanceled(id);
270
+ }
241
271
  if (record.info.status !== "queued") return record.info;
272
+ return this.#markCanceled(id);
273
+ }
274
+ async #markCanceled(id) {
242
275
  const updated = await this.#adapter.update(id, {
243
276
  status: "canceled",
244
277
  finishedAt: Date.now(),
@@ -251,25 +284,38 @@ var JobQueue = class {
251
284
  });
252
285
  return updated?.info ?? null;
253
286
  }
287
+ pause() {
288
+ this.#paused = true;
289
+ }
290
+ resume() {
291
+ if (!this.#paused) return;
292
+ this.#paused = false;
293
+ this.#pump();
294
+ }
295
+ get paused() {
296
+ return this.#paused;
297
+ }
254
298
  async stats() {
255
299
  const jobs = await this.#adapter.list();
256
300
  const dailyTokensUsed = await this.#adapter.dailyTokens(dayKey(Date.now()));
257
301
  const dailyTokenLimit = this.#options.dailyTokenLimit;
258
302
  return {
259
303
  maxConcurrency: this.#options.maxConcurrency ?? 1,
260
- running: this.#running.size,
304
+ running: this.#running.size + this.#starting.size,
261
305
  parked: this.#parked.size,
262
306
  queued: jobs.filter((j) => j.info.status === "queued").length,
263
307
  sessionTokenLimit: this.#options.sessionTokenLimit,
264
308
  dailyTokenLimit,
265
309
  dailyTokensUsed,
266
- paused: dailyTokenLimit !== void 0 && dailyTokensUsed >= dailyTokenLimit
310
+ paused: this.#paused || dailyTokenLimit !== void 0 && dailyTokensUsed >= dailyTokenLimit
267
311
  };
268
312
  }
269
313
  close() {
270
314
  this.#closed = true;
271
315
  this.#offWork?.();
272
316
  clearInterval(this.#sweepTimer);
317
+ clearTimeout(this.#wakeTimer);
318
+ this.#wakeTimer = void 0;
273
319
  for (const timer of this.#retryTimers) clearTimeout(timer);
274
320
  this.#retryTimers.clear();
275
321
  }
@@ -278,24 +324,69 @@ var JobQueue = class {
278
324
  if (!retention) return;
279
325
  this.#adapter.prune(retention.maxAgeMs).catch(() => {});
280
326
  }
281
- async #pump() {
282
- if (this.#pumping || this.#closed) return;
327
+ #pump() {
328
+ if (this.#closed || this.#paused) return;
329
+ if (this.#pumping) {
330
+ this.#pumpRequested = true;
331
+ return;
332
+ }
283
333
  this.#pumping = true;
334
+ this.#spawn("pump", void 0, () => this.#drainRequests());
335
+ }
336
+ async #drainRequests() {
284
337
  try {
285
- const maxConcurrency = this.#options.maxConcurrency ?? 1;
286
- while (this.#running.size < maxConcurrency) {
287
- const limit = this.#options.dailyTokenLimit;
288
- if (limit !== void 0 && await this.#adapter.dailyTokens(dayKey(Date.now())) >= limit) return;
289
- const record = await this.#adapter.claimNext();
290
- if (!record) return;
291
- await this.#start(record);
292
- }
338
+ do {
339
+ this.#pumpRequested = false;
340
+ await this.#fill();
341
+ } while (this.#pumpRequested && !this.#closed && !this.#paused);
342
+ } catch (error) {
343
+ this.#wake(Date.now() + PUMP_RETRY_MS);
344
+ throw error;
293
345
  } finally {
294
346
  this.#pumping = false;
295
347
  }
296
348
  }
349
+ async #fill() {
350
+ const maxConcurrency = this.#options.maxConcurrency ?? 1;
351
+ while (this.#running.size + this.#starting.size < maxConcurrency && !this.#closed && !this.#paused) {
352
+ const limit = this.#options.dailyTokenLimit;
353
+ if (limit !== void 0 && await this.#adapter.dailyTokens(dayKey(Date.now())) >= limit) {
354
+ this.#wake(nextUtcMidnight(Date.now()));
355
+ return;
356
+ }
357
+ const record = await this.#adapter.claimNext();
358
+ if (!record) {
359
+ const next = await this.#nextRunAt();
360
+ if (next !== void 0) this.#wake(next);
361
+ return;
362
+ }
363
+ if (this.#closed || this.#paused) {
364
+ await this.#adapter.update(record.info.id, { status: "queued" });
365
+ return;
366
+ }
367
+ const id = record.info.id;
368
+ this.#starting.set(id, { canceled: false });
369
+ this.#spawn("start", id, () => this.#start(record));
370
+ }
371
+ }
372
+ async #nextRunAt() {
373
+ if (this.#adapter.nextRunAt) return this.#adapter.nextRunAt();
374
+ return earliestRunAt(await this.#adapter.list(), Date.now());
375
+ }
376
+ #wake(at) {
377
+ if (this.#closed || this.#wakeTimer !== void 0 && this.#wakeAt <= at) return;
378
+ clearTimeout(this.#wakeTimer);
379
+ this.#wakeAt = at;
380
+ this.#wakeTimer = setTimeout(() => {
381
+ this.#wakeTimer = void 0;
382
+ this.#wakeAt = Number.POSITIVE_INFINITY;
383
+ this.#pump();
384
+ }, Math.min(MAX_TIMER_MS, Math.max(0, at - Date.now())));
385
+ this.#wakeTimer.unref?.();
386
+ }
297
387
  async #start(record) {
298
388
  const id = record.info.id;
389
+ const starting = this.#starting.get(id) ?? { canceled: false };
299
390
  const build = this.#options.buildRunnerConfig ?? ((req) => req);
300
391
  let runner;
301
392
  try {
@@ -308,6 +399,9 @@ var JobQueue = class {
308
399
  };
309
400
  runner = await this.#options.createRunner(build(request));
310
401
  } catch (error) {
402
+ this.#starting.delete(id);
403
+ this.#pump();
404
+ if (starting.canceled) return;
311
405
  const failed = await this.#adapter.update(id, {
312
406
  status: "failed",
313
407
  finishedAt: Date.now(),
@@ -320,6 +414,12 @@ var JobQueue = class {
320
414
  });
321
415
  return;
322
416
  }
417
+ this.#starting.delete(id);
418
+ if (starting.canceled) {
419
+ runner.close("server");
420
+ this.#pump();
421
+ return;
422
+ }
323
423
  const job = {
324
424
  record,
325
425
  runner,
@@ -337,6 +437,12 @@ var JobQueue = class {
337
437
  const updated = await this.#adapter.update(id, {
338
438
  startedAt: Date.now(),
339
439
  sessionId: runner.id
440
+ }).catch((error) => {
441
+ this.#report(error, {
442
+ jobId: id,
443
+ phase: "start"
444
+ });
445
+ return null;
340
446
  });
341
447
  if (updated) job.record = updated;
342
448
  this.#emit(job.record, {
@@ -344,12 +450,29 @@ var JobQueue = class {
344
450
  job: job.record.info,
345
451
  ts: Date.now()
346
452
  });
347
- const durationLimit = this.#effectiveDurationLimit(record.request);
348
- if (durationLimit !== void 0) {
349
- job.durationTimer = setTimeout(() => this.#kill(job, `job exceeded max duration (${durationLimit}ms)`), durationLimit);
350
- job.durationTimer.unref?.();
453
+ this.#armLimit(job, "durationTimer", this.#effectiveDurationLimit(record.request), 0, "max duration");
454
+ job.unsubscribe = this.#subscribe(job);
455
+ }
456
+ #subscribe(job, afterSeq) {
457
+ return job.runner.subscribe((event) => this.#spawn("event", job.record.info.id, () => this.#handleEvent(job, event)), afterSeq);
458
+ }
459
+ #spawn(phase, jobId, run) {
460
+ try {
461
+ run().catch((error) => this.#report(error, {
462
+ jobId,
463
+ phase
464
+ }));
465
+ } catch (error) {
466
+ this.#report(error, {
467
+ jobId,
468
+ phase
469
+ });
351
470
  }
352
- job.unsubscribe = runner.subscribe((event) => void this.#handleEvent(job, event));
471
+ }
472
+ #report(error, context) {
473
+ try {
474
+ (this.#options.onError ?? reportToConsole)(error, context);
475
+ } catch {}
353
476
  }
354
477
  #abortPatch(job, error) {
355
478
  return {
@@ -365,13 +488,14 @@ var JobQueue = class {
365
488
  #kill(job, reason) {
366
489
  if (job.finalized || job.killReason) return;
367
490
  job.killReason = reason;
491
+ const id = job.record.info.id;
368
492
  if (job.parkedAt !== void 0) {
369
- this.#finalize(job, this.#abortPatch(job, reason));
493
+ this.#spawn("finalize", id, () => this.#finalize(job, this.#abortPatch(job, reason)));
370
494
  return;
371
495
  }
372
496
  job.runner.interrupt().catch(() => {});
373
497
  job.forceTimer = setTimeout(() => {
374
- this.#finalize(job, this.#abortPatch(job, reason));
498
+ this.#spawn("finalize", id, () => this.#finalize(job, this.#abortPatch(job, reason)));
375
499
  }, this.#options.killGraceMs ?? 5e3);
376
500
  job.forceTimer.unref?.();
377
501
  }
@@ -435,15 +559,20 @@ var JobQueue = class {
435
559
  }
436
560
  }
437
561
  #effectiveTokenLimit(request) {
438
- const limits = [request.maxTokens, this.#options.sessionTokenLimit].filter((n) => typeof n === "number");
439
- return limits.length > 0 ? Math.min(...limits) : void 0;
562
+ return tightest(request.maxTokens, this.#options.sessionTokenLimit);
440
563
  }
441
564
  #effectiveDurationLimit(request) {
442
- const limits = [request.maxDurationMs, this.#options.maxJobDurationMs].filter((n) => typeof n === "number");
443
- return limits.length > 0 ? Math.min(...limits) : void 0;
565
+ return tightest(request.maxDurationMs, this.#options.maxJobDurationMs);
444
566
  }
445
567
  async #finalize(job, patch) {
446
568
  if (job.finalized) return;
569
+ try {
570
+ await this.#settle(job, patch);
571
+ } finally {
572
+ this.#pump();
573
+ }
574
+ }
575
+ async #settle(job, patch) {
447
576
  job.finalized = true;
448
577
  job.unsubscribe();
449
578
  clearTimeout(job.durationTimer);
@@ -497,7 +626,6 @@ var JobQueue = class {
497
626
  timer.unref?.();
498
627
  this.#retryTimers.add(timer);
499
628
  }
500
- this.#pump();
501
629
  return;
502
630
  }
503
631
  const updated = await this.#adapter.update(job.record.info.id, {
@@ -515,7 +643,6 @@ var JobQueue = class {
515
643
  }, job);
516
644
  }
517
645
  this.#sweep();
518
- this.#pump();
519
646
  }
520
647
  #progress(job, progress) {
521
648
  const event = {
@@ -562,6 +689,10 @@ var JobQueue = class {
562
689
  }
563
690
  }
564
691
  };
692
+ function tightest(...limits) {
693
+ const set = limits.filter((n) => typeof n === "number");
694
+ return set.length > 0 ? Math.min(...set) : void 0;
695
+ }
565
696
  //#endregion
566
697
  export { InMemoryQueueAdapter, JobQueue };
567
698
 
@@ -1 +1 @@
1
- {"version":3,"file":"index.mjs","names":[],"sources":["../src/adapter.ts","../src/queue.ts"],"sourcesContent":["import type { CreateJobRequest, JobInfo } from '@workerdeck/protocol'\n\nexport type JobRecord = {\n info: JobInfo\n request: CreateJobRequest\n}\n\nexport interface QueueAdapter {\n add(job: JobRecord): Promise<void>\n claimNext(): Promise<JobRecord | null>\n get(id: string): Promise<JobRecord | null>\n list(): Promise<JobRecord[]>\n update(id: string, patch: Partial<JobInfo>): Promise<JobRecord | null>\n prune(olderThanMs: number): Promise<number>\n addDailyTokens(dayKey: string, tokens: number): Promise<number>\n dailyTokens(dayKey: string): Promise<number>\n onWork?(listener: () => void): () => void\n}\n\nexport class InMemoryQueueAdapter implements QueueAdapter {\n #jobs = new Map<string, JobRecord>()\n #dailyTokens = new Map<string, number>()\n\n add(job: JobRecord): Promise<void> {\n this.#jobs.set(job.info.id, job)\n return Promise.resolve()\n }\n\n claimNext(): Promise<JobRecord | null> {\n const now = Date.now()\n for (const job of this.#jobs.values()) {\n if (job.info.status === 'queued' && (job.info.nextRunAt === undefined || job.info.nextRunAt <= now)) {\n job.info = { ...job.info, status: 'running' }\n return Promise.resolve(job)\n }\n }\n return Promise.resolve(null)\n }\n\n get(id: string): Promise<JobRecord | null> {\n return Promise.resolve(this.#jobs.get(id) ?? null)\n }\n\n list(): Promise<JobRecord[]> {\n return Promise.resolve([...this.#jobs.values()])\n }\n\n update(id: string, patch: Partial<JobInfo>): Promise<JobRecord | null> {\n const job = this.#jobs.get(id)\n if (!job) {\n return Promise.resolve(null)\n }\n job.info = { ...job.info, ...patch }\n return Promise.resolve(job)\n }\n\n prune(olderThanMs: number): Promise<number> {\n const cutoff = Date.now() - olderThanMs\n let removed = 0\n for (const [id, job] of this.#jobs) {\n const { status, finishedAt } = job.info\n const terminal = status === 'succeeded' || status === 'failed' || status === 'canceled'\n if (terminal && (finishedAt ?? 0) <= cutoff) {\n this.#jobs.delete(id)\n removed++\n }\n }\n return Promise.resolve(removed)\n }\n\n addDailyTokens(dayKey: string, tokens: number): Promise<number> {\n const next = (this.#dailyTokens.get(dayKey) ?? 0) + tokens\n this.#dailyTokens.set(dayKey, next)\n return Promise.resolve(next)\n }\n\n dailyTokens(dayKey: string): Promise<number> {\n return Promise.resolve(this.#dailyTokens.get(dayKey) ?? 0)\n }\n}\n","import { randomUUID } from 'node:crypto'\nimport type { Runner, SessionRunnerConfig } from '@workerdeck/core'\nimport {\n pickCreateSessionRequest,\n type ApiMessage,\n type CreateJobRequest,\n type CreateSessionRequest,\n type JobEvent,\n type JobInfo,\n type JobProgress,\n type JobUsage,\n type QueueStats,\n type SessionEvent,\n} from '@workerdeck/protocol'\nimport { InMemoryQueueAdapter, type JobRecord, type QueueAdapter } from './adapter.ts'\n\nexport type JobQueueOptions = {\n createRunner: (config: SessionRunnerConfig) => Runner | Promise<Runner>\n adapter?: QueueAdapter\n maxConcurrency?: number\n sessionTokenLimit?: number\n dailyTokenLimit?: number\n maxJobDurationMs?: number\n maxParkedDurationMs?: number\n killGraceMs?: number\n retention?: { maxAgeMs: number; sweepIntervalMs?: number }\n buildRunnerConfig?: (req: CreateSessionRequest) => SessionRunnerConfig\n discardSession?: (sessionId: string) => void | Promise<void>\n fetchImpl?: typeof fetch\n webhookAttempts?: number\n webhookRetryDelayMs?: number\n onEvent?: (event: JobEvent) => void\n}\n\ntype RunningJob = {\n record: JobRecord\n runner: Runner\n unsubscribe: () => void\n lastSeq: number\n estimatedTokens: number\n killReason?: string\n canceled: boolean\n finalized: boolean\n deliveries: Promise<void>\n durationTimer?: ReturnType<typeof setTimeout>\n forceTimer?: ReturnType<typeof setTimeout>\n runningMs: number\n legStartedAt: number\n parkedAt?: number\n parkedExecutionId?: string\n parkTimer?: ReturnType<typeof setTimeout>\n parkedMs: number\n}\n\nfunction dayKey(epochMs: number): string {\n return new Date(epochMs).toISOString().slice(0, 10)\n}\n\n// An attempt that reported no priced figure at all stays unknown rather than banking a zero:\n// only the claude engine reports a cost of its own, and `$0.00` is a claim.\nfunction addOptionalCost(prior: number | undefined, attempt: number | undefined): number | undefined {\n if (prior === undefined && attempt === undefined) {\n return undefined\n }\n return (prior ?? 0) + (attempt ?? 0)\n}\n\nfunction sumUsage(usage: unknown): number {\n if (typeof usage !== 'object' || usage === null) {\n return 0\n }\n const u = usage as Record<string, unknown>\n return (\n (typeof u.input_tokens === 'number' ? u.input_tokens : 0) +\n (typeof u.output_tokens === 'number' ? u.output_tokens : 0) +\n (typeof u.cache_creation_input_tokens === 'number' ? u.cache_creation_input_tokens : 0) +\n (typeof u.cache_read_input_tokens === 'number' ? u.cache_read_input_tokens : 0)\n )\n}\n\nfunction textPreview(message: ApiMessage, max = 140): JobProgress | null {\n const blocks = typeof message.content === 'string' ? [{ type: 'text', text: message.content }] : message.content\n for (const block of blocks) {\n if (block.type === 'tool_use') {\n return { kind: 'tool_use', preview: (block as { name?: string }).name }\n }\n if (block.type === 'text') {\n const text = (block as { text?: string }).text ?? ''\n if (text.trim()) {\n return {\n kind: 'assistant_text',\n preview: text.length > max ? text.slice(0, max - 1) + '…' : text,\n }\n }\n }\n }\n return null\n}\n\nexport class JobQueue {\n #options: JobQueueOptions\n #adapter: QueueAdapter\n #running = new Map<string, RunningJob>()\n #parked = new Map<string, RunningJob>()\n #pumping = false\n #closed = false\n #offWork: (() => void) | undefined\n #sweepTimer: ReturnType<typeof setInterval> | undefined\n #retryTimers = new Set<ReturnType<typeof setTimeout>>()\n\n constructor(options: JobQueueOptions) {\n this.#options = options\n this.#adapter = options.adapter ?? new InMemoryQueueAdapter()\n this.#offWork = this.#adapter.onWork?.(() => void this.#pump())\n const retention = options.retention\n if (retention) {\n const interval = retention.sweepIntervalMs ?? Math.min(retention.maxAgeMs, 60_000)\n this.#sweepTimer = setInterval(() => this.#sweep(), interval)\n this.#sweepTimer.unref?.()\n }\n }\n\n async submit(request: CreateJobRequest): Promise<JobInfo> {\n if (this.#closed) {\n throw new Error('queue is closed')\n }\n if (!request.session?.prompt?.trim()) {\n throw new Error('session.prompt is required')\n }\n if (request.session.resume || request.session.forkSession) {\n throw new Error('resume/forkSession are not supported for queued jobs')\n }\n const attempts = request.attempts ?? 1\n if (!Number.isInteger(attempts) || attempts < 1) {\n throw new Error('attempts must be a positive integer')\n }\n if (request.retryDelayMs !== undefined && !(request.retryDelayMs >= 0)) {\n throw new Error('retryDelayMs must be >= 0')\n }\n const info: JobInfo = {\n id: randomUUID(),\n status: 'queued',\n cwd: request.session.cwd ?? '',\n profile: request.session.profile,\n prompt: request.session.prompt,\n createdAt: Date.now(),\n attempt: 1,\n maxAttempts: attempts,\n usage: { tokens: 0, totalCostUsd: 0, numTurns: 0 },\n meta: request.meta,\n scope: request.session.scope,\n }\n const record: JobRecord = { info, request }\n await this.#adapter.add(record)\n this.#emit(record, { type: 'job_submitted', job: info, ts: Date.now() }, undefined, {\n skipWebhook: true,\n })\n void this.#pump()\n return info\n }\n\n async get(id: string): Promise<JobInfo | null> {\n return (await this.#adapter.get(id))?.info ?? null\n }\n\n async list(): Promise<JobInfo[]> {\n return (await this.#adapter.list()).map((j) => j.info)\n }\n\n onSessionParking(sessionId: string, executionId: string): boolean {\n const job = this.#bySession(this.#running, sessionId)\n if (!job) {\n return true\n }\n if (job.finalized || job.killReason) {\n return false\n }\n const now = Date.now()\n job.unsubscribe()\n job.runningMs += now - job.legStartedAt\n clearTimeout(job.durationTimer)\n job.durationTimer = undefined\n job.parkedAt = now\n job.parkedExecutionId = executionId\n this.#running.delete(job.record.info.id)\n this.#parked.set(job.record.info.id, job)\n const parkedLimit = this.#options.maxParkedDurationMs\n if (parkedLimit !== undefined) {\n job.parkTimer = setTimeout(\n () => this.#kill(job, `job exceeded max parked duration (${parkedLimit}ms)`),\n Math.max(0, parkedLimit - job.parkedMs),\n )\n job.parkTimer.unref?.()\n }\n void this.#recordPark(job, executionId)\n return true\n }\n\n async #recordPark(job: RunningJob, executionId: string): Promise<void> {\n const updated = await this.#adapter.update(job.record.info.id, {\n status: 'parked',\n parkedAt: job.parkedAt,\n parkedExecutionId: executionId,\n })\n if (updated) {\n job.record = updated\n }\n this.#emit(job.record, { type: 'job_parked', job: job.record.info, executionId, ts: Date.now() }, job)\n void this.#pump()\n }\n\n onSessionResumed(sessionId: string, runner: Runner): void {\n const job = this.#bySession(this.#parked, sessionId)\n if (!job || job.finalized) {\n return\n }\n const now = Date.now()\n const executionId = job.parkedExecutionId ?? ''\n clearTimeout(job.parkTimer)\n job.parkTimer = undefined\n job.parkedMs += now - (job.parkedAt ?? now)\n job.parkedAt = undefined\n job.parkedExecutionId = undefined\n job.legStartedAt = now\n job.runner = runner\n this.#parked.delete(job.record.info.id)\n this.#running.set(job.record.info.id, job)\n const durationLimit = this.#effectiveDurationLimit(job.record.request)\n if (durationLimit !== undefined) {\n job.durationTimer = setTimeout(\n () => this.#kill(job, `job exceeded max duration (${durationLimit}ms)`),\n Math.max(0, durationLimit - job.runningMs),\n )\n job.durationTimer.unref?.()\n }\n job.unsubscribe = runner.subscribe((event) => void this.#handleEvent(job, event), job.lastSeq)\n void this.#recordResume(job, executionId)\n }\n\n async #recordResume(job: RunningJob, executionId: string): Promise<void> {\n const updated = await this.#adapter.update(job.record.info.id, {\n status: 'running',\n parkedAt: undefined,\n parkedExecutionId: undefined,\n })\n if (updated) {\n job.record = updated\n }\n this.#emit(job.record, { type: 'job_resumed', job: job.record.info, executionId, ts: Date.now() }, job)\n }\n\n #bySession(jobs: Map<string, RunningJob>, sessionId: string): RunningJob | undefined {\n for (const job of jobs.values()) {\n if (job.record.info.sessionId === sessionId) {\n return job\n }\n }\n return undefined\n }\n\n async cancel(id: string): Promise<JobInfo | null> {\n const record = await this.#adapter.get(id)\n if (!record) {\n return null\n }\n const running = this.#running.get(id) ?? this.#parked.get(id)\n if (running) {\n running.canceled = true\n running.killReason = 'canceled'\n await this.#finalize(running, this.#abortPatch(running, 'canceled'))\n return running.record.info\n }\n if (record.info.status !== 'queued') {\n return record.info\n }\n const updated = await this.#adapter.update(id, {\n status: 'canceled',\n finishedAt: Date.now(),\n error: 'canceled',\n })\n if (updated) {\n this.#emit(updated, { type: 'job_completed', job: updated.info, ts: Date.now() })\n }\n return updated?.info ?? null\n }\n\n async stats(): Promise<QueueStats> {\n const jobs = await this.#adapter.list()\n const dailyTokensUsed = await this.#adapter.dailyTokens(dayKey(Date.now()))\n const dailyTokenLimit = this.#options.dailyTokenLimit\n return {\n maxConcurrency: this.#options.maxConcurrency ?? 1,\n running: this.#running.size,\n parked: this.#parked.size,\n queued: jobs.filter((j) => j.info.status === 'queued').length,\n sessionTokenLimit: this.#options.sessionTokenLimit,\n dailyTokenLimit,\n dailyTokensUsed,\n paused: dailyTokenLimit !== undefined && dailyTokensUsed >= dailyTokenLimit,\n }\n }\n\n close(): void {\n this.#closed = true\n this.#offWork?.()\n clearInterval(this.#sweepTimer)\n for (const timer of this.#retryTimers) {\n clearTimeout(timer)\n }\n this.#retryTimers.clear()\n }\n\n #sweep(): void {\n const retention = this.#options.retention\n if (!retention) {\n return\n }\n this.#adapter.prune(retention.maxAgeMs).catch(() => {})\n }\n\n async #pump(): Promise<void> {\n if (this.#pumping || this.#closed) {\n return\n }\n this.#pumping = true\n try {\n const maxConcurrency = this.#options.maxConcurrency ?? 1\n while (this.#running.size < maxConcurrency) {\n const limit = this.#options.dailyTokenLimit\n if (limit !== undefined && (await this.#adapter.dailyTokens(dayKey(Date.now()))) >= limit) {\n return\n }\n const record = await this.#adapter.claimNext()\n if (!record) {\n return\n }\n await this.#start(record)\n }\n } finally {\n this.#pumping = false\n }\n }\n\n async #start(record: JobRecord): Promise<void> {\n const id = record.info.id\n const build = this.#options.buildRunnerConfig ?? ((req: CreateSessionRequest) => req)\n let runner: Runner\n try {\n // Projected again here, not only at the HTTP door: a durable adapter can hold a record written before the door did.\n const request: CreateSessionRequest = {\n ...pickCreateSessionRequest(record.request.session),\n meta: { ...record.request.session.meta, jobId: id },\n }\n runner = await this.#options.createRunner(build(request))\n } catch (error) {\n const failed = await this.#adapter.update(id, {\n status: 'failed',\n finishedAt: Date.now(),\n error: error instanceof Error ? error.message : String(error),\n })\n if (failed) {\n this.#emit(failed, { type: 'job_completed', job: failed.info, ts: Date.now() })\n }\n return\n }\n const job: RunningJob = {\n record,\n runner,\n unsubscribe: () => {},\n lastSeq: 0,\n estimatedTokens: 0,\n canceled: false,\n finalized: false,\n deliveries: Promise.resolve(),\n runningMs: 0,\n legStartedAt: Date.now(),\n parkedMs: 0,\n }\n this.#running.set(id, job)\n const updated = await this.#adapter.update(id, {\n startedAt: Date.now(),\n sessionId: runner.id,\n })\n if (updated) {\n job.record = updated\n }\n this.#emit(job.record, { type: 'job_started', job: job.record.info, ts: Date.now() })\n const durationLimit = this.#effectiveDurationLimit(record.request)\n if (durationLimit !== undefined) {\n job.durationTimer = setTimeout(() => this.#kill(job, `job exceeded max duration (${durationLimit}ms)`), durationLimit)\n job.durationTimer.unref?.()\n }\n job.unsubscribe = runner.subscribe((event) => void this.#handleEvent(job, event))\n }\n\n // How a run that never produced a `turn_result` is written down. There is no authoritative\n // usage for such a run, so it reports the tokens counted from the message stream, no cost\n // and no turns. `turn_result` is the one path that does NOT come through here - it has real\n // numbers - which is exactly the distinction five inline copies of this used to hide.\n #abortPatch(job: RunningJob, error: string): Partial<JobInfo> {\n return {\n usage: { tokens: job.estimatedTokens, totalCostUsd: 0, numTurns: 0 },\n status: job.canceled ? 'canceled' : 'failed',\n error,\n }\n }\n\n #kill(job: RunningJob, reason: string): void {\n if (job.finalized || job.killReason) {\n return\n }\n job.killReason = reason\n if (job.parkedAt !== undefined) {\n void this.#finalize(job, this.#abortPatch(job, reason))\n return\n }\n void job.runner.interrupt().catch(() => {})\n job.forceTimer = setTimeout(() => {\n void this.#finalize(job, this.#abortPatch(job, reason))\n }, this.#options.killGraceMs ?? 5000)\n job.forceTimer.unref?.()\n }\n\n async #handleEvent(job: RunningJob, event: SessionEvent): Promise<void> {\n if (job.finalized) {\n return\n }\n job.lastSeq = Math.max(job.lastSeq, event.seq)\n switch (event.type) {\n case 'system_init': {\n await this.#adapter.update(job.record.info.id, { sdkSessionId: event.sdkSessionId })\n return\n }\n case 'assistant_message': {\n if (event.replay) {\n return\n }\n job.estimatedTokens += sumUsage(event.message.usage)\n const limit = this.#effectiveTokenLimit(job.record.request)\n if (limit !== undefined && job.estimatedTokens > limit) {\n this.#kill(job, `session token limit exceeded (${job.estimatedTokens} > ${limit})`)\n }\n const progress = textPreview(event.message)\n if (progress) {\n this.#progress(job, progress)\n }\n return\n }\n case 'permission_requested': {\n this.#progress(job, {\n kind: 'permission_requested',\n preview: event.request.title ?? event.request.toolName,\n request: event.request,\n })\n return\n }\n case 'permission_resolved': {\n this.#progress(job, { kind: 'permission_resolved', preview: event.behavior })\n return\n }\n case 'turn_result': {\n const tokens = sumUsage(event.usage) || job.estimatedTokens\n await this.#finalize(job, {\n usage: {\n tokens,\n totalCostUsd: event.totalCostUsd,\n numTurns: event.numTurns,\n costUsd: event.costUsd,\n },\n result: {\n subtype: event.subtype,\n isError: event.isError,\n result: event.result,\n errors: event.errors,\n durationMs: event.durationMs,\n },\n status: job.killReason ? (job.canceled ? 'canceled' : 'failed') : event.isError ? 'failed' : 'succeeded',\n error: job.killReason ?? (event.isError ? event.errors?.join('; ') || event.subtype : undefined),\n })\n return\n }\n case 'session_error': {\n await this.#finalize(job, this.#abortPatch(job, job.killReason ?? event.message))\n return\n }\n case 'session_closed': {\n await this.#finalize(job, this.#abortPatch(job, job.killReason ?? 'session closed before completing'))\n return\n }\n default: {\n return\n }\n }\n }\n\n #effectiveTokenLimit(request: CreateJobRequest): number | undefined {\n const limits = [request.maxTokens, this.#options.sessionTokenLimit].filter((n): n is number => typeof n === 'number')\n return limits.length > 0 ? Math.min(...limits) : undefined\n }\n\n #effectiveDurationLimit(request: CreateJobRequest): number | undefined {\n const limits = [request.maxDurationMs, this.#options.maxJobDurationMs].filter((n): n is number => typeof n === 'number')\n return limits.length > 0 ? Math.min(...limits) : undefined\n }\n\n async #finalize(job: RunningJob, patch: Partial<JobInfo>): Promise<void> {\n if (job.finalized) {\n return\n }\n job.finalized = true\n job.unsubscribe()\n clearTimeout(job.durationTimer)\n clearTimeout(job.forceTimer)\n clearTimeout(job.parkTimer)\n this.#running.delete(job.record.info.id)\n const wasParked = this.#parked.delete(job.record.info.id)\n job.runner.close('server')\n if (wasParked && job.record.info.sessionId) {\n try {\n void Promise.resolve(this.#options.discardSession?.(job.record.info.sessionId)).catch(() => {})\n } catch {}\n }\n const attemptUsage = patch.usage ?? { tokens: 0, totalCostUsd: 0, numTurns: 0 }\n if (attemptUsage.tokens > 0) {\n await this.#adapter.addDailyTokens(dayKey(Date.now()), attemptUsage.tokens)\n }\n const prior = job.record.info.usage\n const usage: JobUsage = {\n tokens: prior.tokens + attemptUsage.tokens,\n totalCostUsd: prior.totalCostUsd + attemptUsage.totalCostUsd,\n numTurns: prior.numTurns + attemptUsage.numTurns,\n costUsd: addOptionalCost(prior.costUsd, attemptUsage.costUsd),\n }\n const attempt = job.record.info.attempt ?? 1\n const maxAttempts = job.record.request.attempts ?? 1\n if (patch.status === 'failed' && attempt < maxAttempts && !this.#closed) {\n const baseDelay = job.record.request.retryDelayMs ?? 5000\n const delay = baseDelay * 2 ** (attempt - 1)\n const updated = await this.#adapter.update(job.record.info.id, {\n status: 'queued',\n attempt: attempt + 1,\n nextRunAt: Date.now() + delay,\n error: patch.error,\n usage,\n sessionId: undefined,\n sdkSessionId: undefined,\n startedAt: undefined,\n result: undefined,\n })\n if (updated) {\n job.record = updated\n this.#emit(updated, { type: 'job_retrying', job: updated.info, ts: Date.now() }, job)\n const timer = setTimeout(() => {\n this.#retryTimers.delete(timer)\n void this.#pump()\n }, delay)\n timer.unref?.()\n this.#retryTimers.add(timer)\n }\n void this.#pump()\n return\n }\n const updated = await this.#adapter.update(job.record.info.id, {\n ...patch,\n usage,\n nextRunAt: undefined,\n finishedAt: Date.now(),\n })\n if (updated) {\n job.record = updated\n this.#emit(job.record, { type: 'job_completed', job: updated.info, ts: Date.now() }, job)\n }\n this.#sweep()\n void this.#pump()\n }\n\n #progress(job: RunningJob, progress: JobProgress): void {\n const event: JobEvent = { type: 'job_progress', job: job.record.info, progress, ts: Date.now() }\n if (job.record.request.webhook?.progress === 'completion') {\n try {\n this.#options.onEvent?.(event)\n } catch {}\n return\n }\n this.#emit(job.record, event, job)\n }\n\n #emit(record: JobRecord, event: JobEvent, chainOwner?: RunningJob, { skipWebhook = false }: { skipWebhook?: boolean } = {}): void {\n try {\n this.#options.onEvent?.(event)\n } catch {}\n const webhook = record.request.webhook\n if (!webhook || skipWebhook) {\n return\n }\n const running = chainOwner ?? this.#running.get(record.info.id)\n const deliver = () => this.#deliver(webhook.url, webhook.headers, event)\n if (running) {\n running.deliveries = running.deliveries.then(deliver)\n } else {\n void deliver()\n }\n }\n\n async #deliver(url: string, headers: Record<string, string> | undefined, event: JobEvent): Promise<void> {\n const fetchImpl = this.#options.fetchImpl ?? fetch\n const attempts = this.#options.webhookAttempts ?? 3\n const baseDelay = this.#options.webhookRetryDelayMs ?? 500\n for (let attempt = 0; attempt < attempts; attempt++) {\n try {\n const res = await fetchImpl(url, {\n method: 'POST',\n headers: { 'content-type': 'application/json', ...headers },\n body: JSON.stringify(event),\n })\n if (res.ok) {\n return\n }\n } catch {}\n if (attempt < attempts - 1) {\n await new Promise((resolve) => setTimeout(resolve, baseDelay * 2 ** attempt))\n }\n }\n }\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"index.mjs","names":[],"sources":["../src/adapter.ts","../src/queue.ts"],"sourcesContent":["import type { CreateJobRequest, JobInfo } from '@workerdeck/protocol'\n\nexport type JobRecord = {\n info: JobInfo\n request: CreateJobRequest\n}\n\nexport interface QueueAdapter {\n add(job: JobRecord): Promise<void>\n claimNext(): Promise<JobRecord | null>\n get(id: string): Promise<JobRecord | null>\n list(): Promise<JobRecord[]>\n update(id: string, patch: Partial<JobInfo>): Promise<JobRecord | null>\n prune(olderThanMs: number): Promise<number>\n addDailyTokens(dayKey: string, tokens: number): Promise<number>\n dailyTokens(dayKey: string): Promise<number>\n nextRunAt?(): Promise<number | undefined>\n onWork?(listener: () => void): () => void\n}\n\nexport function earliestRunAt(jobs: Iterable<JobRecord>, now: number): number | undefined {\n let earliest: number | undefined\n for (const { info } of jobs) {\n if (info.status === 'queued' && info.nextRunAt !== undefined && info.nextRunAt > now) {\n earliest = earliest === undefined ? info.nextRunAt : Math.min(earliest, info.nextRunAt)\n }\n }\n return earliest\n}\n\nexport class InMemoryQueueAdapter implements QueueAdapter {\n #jobs = new Map<string, JobRecord>()\n #dailyTokens = new Map<string, number>()\n\n add(job: JobRecord): Promise<void> {\n this.#jobs.set(job.info.id, job)\n return Promise.resolve()\n }\n\n claimNext(): Promise<JobRecord | null> {\n const now = Date.now()\n for (const job of this.#jobs.values()) {\n if (job.info.status === 'queued' && (job.info.nextRunAt === undefined || job.info.nextRunAt <= now)) {\n job.info = { ...job.info, status: 'running' }\n return Promise.resolve(job)\n }\n }\n return Promise.resolve(null)\n }\n\n get(id: string): Promise<JobRecord | null> {\n return Promise.resolve(this.#jobs.get(id) ?? null)\n }\n\n list(): Promise<JobRecord[]> {\n return Promise.resolve([...this.#jobs.values()])\n }\n\n update(id: string, patch: Partial<JobInfo>): Promise<JobRecord | null> {\n const job = this.#jobs.get(id)\n if (!job) {\n return Promise.resolve(null)\n }\n job.info = { ...job.info, ...patch }\n return Promise.resolve(job)\n }\n\n prune(olderThanMs: number): Promise<number> {\n const cutoff = Date.now() - olderThanMs\n let removed = 0\n for (const [id, job] of this.#jobs) {\n const { status, finishedAt } = job.info\n const terminal = status === 'succeeded' || status === 'failed' || status === 'canceled'\n if (terminal && (finishedAt ?? 0) <= cutoff) {\n this.#jobs.delete(id)\n removed++\n }\n }\n return Promise.resolve(removed)\n }\n\n addDailyTokens(dayKey: string, tokens: number): Promise<number> {\n const next = (this.#dailyTokens.get(dayKey) ?? 0) + tokens\n this.#dailyTokens.set(dayKey, next)\n return Promise.resolve(next)\n }\n\n dailyTokens(dayKey: string): Promise<number> {\n return Promise.resolve(this.#dailyTokens.get(dayKey) ?? 0)\n }\n\n nextRunAt(): Promise<number | undefined> {\n return Promise.resolve(earliestRunAt(this.#jobs.values(), Date.now()))\n }\n}\n","import { randomUUID } from 'node:crypto'\nimport type { Runner, SessionRunnerConfig } from '@workerdeck/core'\nimport {\n pickCreateSessionRequest,\n type ApiMessage,\n type CreateJobRequest,\n type CreateSessionRequest,\n type JobEvent,\n type JobInfo,\n type JobProgress,\n type JobUsage,\n type QueueStats,\n type SessionEvent,\n} from '@workerdeck/protocol'\nimport { earliestRunAt, InMemoryQueueAdapter, type JobRecord, type QueueAdapter } from './adapter.ts'\n\nexport type JobQueueOptions = {\n createRunner: (config: SessionRunnerConfig) => Runner | Promise<Runner>\n adapter?: QueueAdapter\n maxConcurrency?: number\n sessionTokenLimit?: number\n dailyTokenLimit?: number\n maxJobDurationMs?: number\n maxParkedDurationMs?: number\n killGraceMs?: number\n retention?: { maxAgeMs: number; sweepIntervalMs?: number }\n buildRunnerConfig?: (req: CreateSessionRequest) => SessionRunnerConfig\n discardSession?: (sessionId: string) => void | Promise<void>\n fetchImpl?: typeof fetch\n webhookAttempts?: number\n webhookRetryDelayMs?: number\n onEvent?: (event: JobEvent) => void\n onError?: (error: unknown, context: JobQueueErrorContext) => void\n}\n\nexport type JobQueueErrorPhase = 'pump' | 'start' | 'event' | 'finalize' | 'park' | 'resume'\n\nexport type JobQueueErrorContext = { jobId?: string; phase: JobQueueErrorPhase }\n\ntype StartingJob = { canceled: boolean }\n\ntype RunningJob = {\n record: JobRecord\n runner: Runner\n unsubscribe: () => void\n lastSeq: number\n estimatedTokens: number\n killReason?: string\n canceled: boolean\n finalized: boolean\n deliveries: Promise<void>\n durationTimer?: ReturnType<typeof setTimeout>\n forceTimer?: ReturnType<typeof setTimeout>\n runningMs: number\n legStartedAt: number\n parkedAt?: number\n parkedExecutionId?: string\n parkTimer?: ReturnType<typeof setTimeout>\n parkedMs: number\n}\n\nconst MAX_TIMER_MS = 2 ** 31 - 1\n\nconst PUMP_RETRY_MS = 5_000\n\nfunction nextUtcMidnight(epochMs: number): number {\n const now = new Date(epochMs)\n return Date.UTC(now.getUTCFullYear(), now.getUTCMonth(), now.getUTCDate() + 1)\n}\n\nfunction reportToConsole(error: unknown, context: JobQueueErrorContext): void {\n const job = context.jobId ? ` (job ${context.jobId})` : ''\n console.warn(`[workerdeck] queue ${context.phase} failed${job}: ${error instanceof Error ? error.message : String(error)}`)\n}\n\nfunction dayKey(epochMs: number): string {\n return new Date(epochMs).toISOString().slice(0, 10)\n}\n\n// An attempt that reported no priced figure at all stays unknown rather than banking a zero:\n// only the claude engine reports a cost of its own, and `$0.00` is a claim.\nfunction addOptionalCost(prior: number | undefined, attempt: number | undefined): number | undefined {\n if (prior === undefined && attempt === undefined) {\n return undefined\n }\n return (prior ?? 0) + (attempt ?? 0)\n}\n\nfunction sumUsage(usage: unknown): number {\n if (typeof usage !== 'object' || usage === null) {\n return 0\n }\n const u = usage as Record<string, unknown>\n return (\n (typeof u.input_tokens === 'number' ? u.input_tokens : 0) +\n (typeof u.output_tokens === 'number' ? u.output_tokens : 0) +\n (typeof u.cache_creation_input_tokens === 'number' ? u.cache_creation_input_tokens : 0) +\n (typeof u.cache_read_input_tokens === 'number' ? u.cache_read_input_tokens : 0)\n )\n}\n\nfunction textPreview(message: ApiMessage, max = 140): JobProgress | null {\n const blocks = typeof message.content === 'string' ? [{ type: 'text', text: message.content }] : message.content\n for (const block of blocks) {\n if (block.type === 'tool_use') {\n return { kind: 'tool_use', preview: (block as { name?: string }).name }\n }\n if (block.type === 'text') {\n const text = (block as { text?: string }).text ?? ''\n if (text.trim()) {\n return {\n kind: 'assistant_text',\n preview: text.length > max ? text.slice(0, max - 1) + '…' : text,\n }\n }\n }\n }\n return null\n}\n\nexport class JobQueue {\n #options: JobQueueOptions\n #adapter: QueueAdapter\n #running = new Map<string, RunningJob>()\n #parked = new Map<string, RunningJob>()\n #starting = new Map<string, StartingJob>()\n #pumping = false\n #pumpRequested = false\n #paused = false\n #closed = false\n #wakeTimer: ReturnType<typeof setTimeout> | undefined\n #wakeAt = Number.POSITIVE_INFINITY\n #offWork: (() => void) | undefined\n #sweepTimer: ReturnType<typeof setInterval> | undefined\n #retryTimers = new Set<ReturnType<typeof setTimeout>>()\n\n constructor(options: JobQueueOptions) {\n this.#options = options\n this.#adapter = options.adapter ?? new InMemoryQueueAdapter()\n this.#offWork = this.#adapter.onWork?.(() => this.#pump())\n const retention = options.retention\n if (retention) {\n const interval = retention.sweepIntervalMs ?? Math.min(retention.maxAgeMs, 60_000)\n this.#sweepTimer = setInterval(() => this.#sweep(), interval)\n this.#sweepTimer.unref?.()\n }\n }\n\n async submit(request: CreateJobRequest): Promise<JobInfo> {\n if (this.#closed) {\n throw new Error('queue is closed')\n }\n if (!request.session?.prompt?.trim()) {\n throw new Error('session.prompt is required')\n }\n if (request.session.resume || request.session.forkSession) {\n throw new Error('resume/forkSession are not supported for queued jobs')\n }\n const attempts = request.attempts ?? 1\n if (!Number.isInteger(attempts) || attempts < 1) {\n throw new Error('attempts must be a positive integer')\n }\n if (request.retryDelayMs !== undefined && !(request.retryDelayMs >= 0)) {\n throw new Error('retryDelayMs must be >= 0')\n }\n const info: JobInfo = {\n id: randomUUID(),\n status: 'queued',\n cwd: request.session.cwd ?? '',\n profile: request.session.profile,\n prompt: request.session.prompt,\n createdAt: Date.now(),\n attempt: 1,\n maxAttempts: attempts,\n usage: { tokens: 0, totalCostUsd: 0, numTurns: 0 },\n meta: request.meta,\n scope: request.session.scope,\n }\n const record: JobRecord = { info, request }\n await this.#adapter.add(record)\n this.#emit(record, { type: 'job_submitted', job: info, ts: Date.now() }, undefined, {\n skipWebhook: true,\n })\n this.#pump()\n return info\n }\n\n async get(id: string): Promise<JobInfo | null> {\n return (await this.#adapter.get(id))?.info ?? null\n }\n\n async list(): Promise<JobInfo[]> {\n return (await this.#adapter.list()).map((j) => j.info)\n }\n\n canParkSession(sessionId: string): boolean {\n const job = this.#bySession(this.#running, sessionId)\n return !job || (!job.finalized && !job.killReason)\n }\n\n onSessionParking(sessionId: string, executionId: string): boolean {\n const job = this.#bySession(this.#running, sessionId)\n if (!job) {\n return true\n }\n if (job.finalized || job.killReason) {\n return false\n }\n const now = Date.now()\n job.unsubscribe()\n job.runningMs += now - job.legStartedAt\n clearTimeout(job.durationTimer)\n job.durationTimer = undefined\n job.parkedAt = now\n job.parkedExecutionId = executionId\n this.#running.delete(job.record.info.id)\n this.#parked.set(job.record.info.id, job)\n this.#armLimit(job, 'parkTimer', this.#options.maxParkedDurationMs, job.parkedMs, 'max parked duration')\n const patch: Partial<JobInfo> = { status: 'parked', parkedAt: job.parkedAt, parkedExecutionId: executionId }\n this.#spawn('park', job.record.info.id, async () => {\n await this.#recordTransition(job, patch, (info) => ({ type: 'job_parked', job: info, executionId, ts: Date.now() }))\n this.#pump()\n })\n return true\n }\n\n onSessionResumed(sessionId: string, runner: Runner): void {\n const job = this.#bySession(this.#parked, sessionId)\n if (!job || job.finalized) {\n return\n }\n const now = Date.now()\n const executionId = job.parkedExecutionId ?? ''\n clearTimeout(job.parkTimer)\n job.parkTimer = undefined\n job.parkedMs += now - (job.parkedAt ?? now)\n job.parkedAt = undefined\n job.parkedExecutionId = undefined\n job.legStartedAt = now\n job.runner = runner\n this.#parked.delete(job.record.info.id)\n this.#running.set(job.record.info.id, job)\n this.#armLimit(job, 'durationTimer', this.#effectiveDurationLimit(job.record.request), job.runningMs, 'max duration')\n job.unsubscribe = this.#subscribe(job, job.lastSeq)\n const patch: Partial<JobInfo> = { status: 'running', parkedAt: undefined, parkedExecutionId: undefined }\n this.#spawn('resume', job.record.info.id, () =>\n this.#recordTransition(job, patch, (info) => ({ type: 'job_resumed', job: info, executionId, ts: Date.now() })),\n )\n }\n\n async #recordTransition(job: RunningJob, patch: Partial<JobInfo>, event: (info: JobInfo) => JobEvent): Promise<void> {\n const updated = await this.#adapter.update(job.record.info.id, patch)\n if (updated) {\n job.record = updated\n }\n this.#emit(job.record, event(job.record.info), job)\n }\n\n #armLimit(job: RunningJob, key: 'durationTimer' | 'parkTimer', limit: number | undefined, spentMs: number, label: string): void {\n if (limit === undefined) {\n return\n }\n const timer = setTimeout(() => this.#kill(job, `job exceeded ${label} (${limit}ms)`), Math.max(0, limit - spentMs))\n timer.unref?.()\n job[key] = timer\n }\n\n #bySession(jobs: Map<string, RunningJob>, sessionId: string): RunningJob | undefined {\n for (const job of jobs.values()) {\n if (job.record.info.sessionId === sessionId) {\n return job\n }\n }\n return undefined\n }\n\n async cancel(id: string): Promise<JobInfo | null> {\n const record = await this.#adapter.get(id)\n if (!record) {\n return null\n }\n const running = this.#running.get(id) ?? this.#parked.get(id)\n if (running) {\n running.canceled = true\n running.killReason = 'canceled'\n await this.#finalize(running, this.#abortPatch(running, 'canceled'))\n return running.record.info\n }\n const starting = this.#starting.get(id)\n if (starting) {\n if (starting.canceled) {\n return record.info\n }\n starting.canceled = true\n return this.#markCanceled(id)\n }\n if (record.info.status !== 'queued') {\n return record.info\n }\n return this.#markCanceled(id)\n }\n\n async #markCanceled(id: string): Promise<JobInfo | null> {\n const updated = await this.#adapter.update(id, {\n status: 'canceled',\n finishedAt: Date.now(),\n error: 'canceled',\n })\n if (updated) {\n this.#emit(updated, { type: 'job_completed', job: updated.info, ts: Date.now() })\n }\n return updated?.info ?? null\n }\n\n pause(): void {\n this.#paused = true\n }\n\n resume(): void {\n if (!this.#paused) {\n return\n }\n this.#paused = false\n this.#pump()\n }\n\n get paused(): boolean {\n return this.#paused\n }\n\n async stats(): Promise<QueueStats> {\n const jobs = await this.#adapter.list()\n const dailyTokensUsed = await this.#adapter.dailyTokens(dayKey(Date.now()))\n const dailyTokenLimit = this.#options.dailyTokenLimit\n return {\n maxConcurrency: this.#options.maxConcurrency ?? 1,\n running: this.#running.size + this.#starting.size,\n parked: this.#parked.size,\n queued: jobs.filter((j) => j.info.status === 'queued').length,\n sessionTokenLimit: this.#options.sessionTokenLimit,\n dailyTokenLimit,\n dailyTokensUsed,\n paused: this.#paused || (dailyTokenLimit !== undefined && dailyTokensUsed >= dailyTokenLimit),\n }\n }\n\n close(): void {\n this.#closed = true\n this.#offWork?.()\n clearInterval(this.#sweepTimer)\n clearTimeout(this.#wakeTimer)\n this.#wakeTimer = undefined\n for (const timer of this.#retryTimers) {\n clearTimeout(timer)\n }\n this.#retryTimers.clear()\n }\n\n #sweep(): void {\n const retention = this.#options.retention\n if (!retention) {\n return\n }\n this.#adapter.prune(retention.maxAgeMs).catch(() => {})\n }\n\n #pump(): void {\n if (this.#closed || this.#paused) {\n return\n }\n if (this.#pumping) {\n this.#pumpRequested = true\n return\n }\n this.#pumping = true\n this.#spawn('pump', undefined, () => this.#drainRequests())\n }\n\n async #drainRequests(): Promise<void> {\n try {\n do {\n this.#pumpRequested = false\n await this.#fill()\n } while (this.#pumpRequested && !this.#closed && !this.#paused)\n } catch (error) {\n this.#wake(Date.now() + PUMP_RETRY_MS)\n throw error\n } finally {\n this.#pumping = false\n }\n }\n\n async #fill(): Promise<void> {\n const maxConcurrency = this.#options.maxConcurrency ?? 1\n while (this.#running.size + this.#starting.size < maxConcurrency && !this.#closed && !this.#paused) {\n const limit = this.#options.dailyTokenLimit\n if (limit !== undefined && (await this.#adapter.dailyTokens(dayKey(Date.now()))) >= limit) {\n this.#wake(nextUtcMidnight(Date.now()))\n return\n }\n const record = await this.#adapter.claimNext()\n if (!record) {\n const next = await this.#nextRunAt()\n if (next !== undefined) {\n this.#wake(next)\n }\n return\n }\n if (this.#closed || this.#paused) {\n await this.#adapter.update(record.info.id, { status: 'queued' })\n return\n }\n const id = record.info.id\n this.#starting.set(id, { canceled: false })\n this.#spawn('start', id, () => this.#start(record))\n }\n }\n\n async #nextRunAt(): Promise<number | undefined> {\n if (this.#adapter.nextRunAt) {\n return this.#adapter.nextRunAt()\n }\n return earliestRunAt(await this.#adapter.list(), Date.now())\n }\n\n #wake(at: number): void {\n if (this.#closed || (this.#wakeTimer !== undefined && this.#wakeAt <= at)) {\n return\n }\n clearTimeout(this.#wakeTimer)\n this.#wakeAt = at\n this.#wakeTimer = setTimeout(\n () => {\n this.#wakeTimer = undefined\n this.#wakeAt = Number.POSITIVE_INFINITY\n this.#pump()\n },\n Math.min(MAX_TIMER_MS, Math.max(0, at - Date.now())),\n )\n this.#wakeTimer.unref?.()\n }\n\n async #start(record: JobRecord): Promise<void> {\n const id = record.info.id\n const starting = this.#starting.get(id) ?? { canceled: false }\n const build = this.#options.buildRunnerConfig ?? ((req: CreateSessionRequest) => req)\n let runner: Runner\n try {\n // Projected again here, not only at the HTTP door: a durable adapter can hold a record written before the door did.\n const request: CreateSessionRequest = {\n ...pickCreateSessionRequest(record.request.session),\n meta: { ...record.request.session.meta, jobId: id },\n }\n runner = await this.#options.createRunner(build(request))\n } catch (error) {\n this.#starting.delete(id)\n this.#pump()\n if (starting.canceled) {\n return\n }\n const failed = await this.#adapter.update(id, {\n status: 'failed',\n finishedAt: Date.now(),\n error: error instanceof Error ? error.message : String(error),\n })\n if (failed) {\n this.#emit(failed, { type: 'job_completed', job: failed.info, ts: Date.now() })\n }\n return\n }\n this.#starting.delete(id)\n if (starting.canceled) {\n runner.close('server')\n this.#pump()\n return\n }\n const job: RunningJob = {\n record,\n runner,\n unsubscribe: () => {},\n lastSeq: 0,\n estimatedTokens: 0,\n canceled: false,\n finalized: false,\n deliveries: Promise.resolve(),\n runningMs: 0,\n legStartedAt: Date.now(),\n parkedMs: 0,\n }\n this.#running.set(id, job)\n const updated = await this.#adapter.update(id, { startedAt: Date.now(), sessionId: runner.id }).catch((error: unknown) => {\n this.#report(error, { jobId: id, phase: 'start' })\n return null\n })\n if (updated) {\n job.record = updated\n }\n this.#emit(job.record, { type: 'job_started', job: job.record.info, ts: Date.now() })\n this.#armLimit(job, 'durationTimer', this.#effectiveDurationLimit(record.request), 0, 'max duration')\n job.unsubscribe = this.#subscribe(job)\n }\n\n #subscribe(job: RunningJob, afterSeq?: number): () => void {\n return job.runner.subscribe((event) => this.#spawn('event', job.record.info.id, () => this.#handleEvent(job, event)), afterSeq)\n }\n\n #spawn(phase: JobQueueErrorPhase, jobId: string | undefined, run: () => Promise<void>): void {\n try {\n run().catch((error: unknown) => this.#report(error, { jobId, phase }))\n } catch (error) {\n this.#report(error, { jobId, phase })\n }\n }\n\n #report(error: unknown, context: JobQueueErrorContext): void {\n try {\n ;(this.#options.onError ?? reportToConsole)(error, context)\n } catch {}\n }\n\n // How a run that never produced a `turn_result` is written down. There is no authoritative\n // usage for such a run, so it reports the tokens counted from the message stream, no cost\n // and no turns. `turn_result` is the one path that does NOT come through here - it has real\n // numbers - which is exactly the distinction five inline copies of this used to hide.\n #abortPatch(job: RunningJob, error: string): Partial<JobInfo> {\n return {\n usage: { tokens: job.estimatedTokens, totalCostUsd: 0, numTurns: 0 },\n status: job.canceled ? 'canceled' : 'failed',\n error,\n }\n }\n\n #kill(job: RunningJob, reason: string): void {\n if (job.finalized || job.killReason) {\n return\n }\n job.killReason = reason\n const id = job.record.info.id\n if (job.parkedAt !== undefined) {\n this.#spawn('finalize', id, () => this.#finalize(job, this.#abortPatch(job, reason)))\n return\n }\n void job.runner.interrupt().catch(() => {})\n job.forceTimer = setTimeout(() => {\n this.#spawn('finalize', id, () => this.#finalize(job, this.#abortPatch(job, reason)))\n }, this.#options.killGraceMs ?? 5000)\n job.forceTimer.unref?.()\n }\n\n async #handleEvent(job: RunningJob, event: SessionEvent): Promise<void> {\n if (job.finalized) {\n return\n }\n job.lastSeq = Math.max(job.lastSeq, event.seq)\n switch (event.type) {\n case 'system_init': {\n await this.#adapter.update(job.record.info.id, { sdkSessionId: event.sdkSessionId })\n return\n }\n case 'assistant_message': {\n if (event.replay) {\n return\n }\n job.estimatedTokens += sumUsage(event.message.usage)\n const limit = this.#effectiveTokenLimit(job.record.request)\n if (limit !== undefined && job.estimatedTokens > limit) {\n this.#kill(job, `session token limit exceeded (${job.estimatedTokens} > ${limit})`)\n }\n const progress = textPreview(event.message)\n if (progress) {\n this.#progress(job, progress)\n }\n return\n }\n case 'permission_requested': {\n this.#progress(job, {\n kind: 'permission_requested',\n preview: event.request.title ?? event.request.toolName,\n request: event.request,\n })\n return\n }\n case 'permission_resolved': {\n this.#progress(job, { kind: 'permission_resolved', preview: event.behavior })\n return\n }\n case 'turn_result': {\n const tokens = sumUsage(event.usage) || job.estimatedTokens\n await this.#finalize(job, {\n usage: {\n tokens,\n totalCostUsd: event.totalCostUsd,\n numTurns: event.numTurns,\n costUsd: event.costUsd,\n },\n result: {\n subtype: event.subtype,\n isError: event.isError,\n result: event.result,\n errors: event.errors,\n durationMs: event.durationMs,\n },\n status: job.killReason ? (job.canceled ? 'canceled' : 'failed') : event.isError ? 'failed' : 'succeeded',\n error: job.killReason ?? (event.isError ? event.errors?.join('; ') || event.subtype : undefined),\n })\n return\n }\n case 'session_error': {\n await this.#finalize(job, this.#abortPatch(job, job.killReason ?? event.message))\n return\n }\n case 'session_closed': {\n await this.#finalize(job, this.#abortPatch(job, job.killReason ?? 'session closed before completing'))\n return\n }\n default: {\n return\n }\n }\n }\n\n #effectiveTokenLimit(request: CreateJobRequest): number | undefined {\n return tightest(request.maxTokens, this.#options.sessionTokenLimit)\n }\n\n #effectiveDurationLimit(request: CreateJobRequest): number | undefined {\n return tightest(request.maxDurationMs, this.#options.maxJobDurationMs)\n }\n\n async #finalize(job: RunningJob, patch: Partial<JobInfo>): Promise<void> {\n if (job.finalized) {\n return\n }\n try {\n await this.#settle(job, patch)\n } finally {\n this.#pump()\n }\n }\n\n async #settle(job: RunningJob, patch: Partial<JobInfo>): Promise<void> {\n job.finalized = true\n job.unsubscribe()\n clearTimeout(job.durationTimer)\n clearTimeout(job.forceTimer)\n clearTimeout(job.parkTimer)\n this.#running.delete(job.record.info.id)\n const wasParked = this.#parked.delete(job.record.info.id)\n job.runner.close('server')\n if (wasParked && job.record.info.sessionId) {\n try {\n void Promise.resolve(this.#options.discardSession?.(job.record.info.sessionId)).catch(() => {})\n } catch {}\n }\n const attemptUsage = patch.usage ?? { tokens: 0, totalCostUsd: 0, numTurns: 0 }\n if (attemptUsage.tokens > 0) {\n await this.#adapter.addDailyTokens(dayKey(Date.now()), attemptUsage.tokens)\n }\n const prior = job.record.info.usage\n const usage: JobUsage = {\n tokens: prior.tokens + attemptUsage.tokens,\n totalCostUsd: prior.totalCostUsd + attemptUsage.totalCostUsd,\n numTurns: prior.numTurns + attemptUsage.numTurns,\n costUsd: addOptionalCost(prior.costUsd, attemptUsage.costUsd),\n }\n const attempt = job.record.info.attempt ?? 1\n const maxAttempts = job.record.request.attempts ?? 1\n if (patch.status === 'failed' && attempt < maxAttempts && !this.#closed) {\n const baseDelay = job.record.request.retryDelayMs ?? 5000\n const delay = baseDelay * 2 ** (attempt - 1)\n const updated = await this.#adapter.update(job.record.info.id, {\n status: 'queued',\n attempt: attempt + 1,\n nextRunAt: Date.now() + delay,\n error: patch.error,\n usage,\n sessionId: undefined,\n sdkSessionId: undefined,\n startedAt: undefined,\n result: undefined,\n })\n if (updated) {\n job.record = updated\n this.#emit(updated, { type: 'job_retrying', job: updated.info, ts: Date.now() }, job)\n const timer = setTimeout(() => {\n this.#retryTimers.delete(timer)\n this.#pump()\n }, delay)\n timer.unref?.()\n this.#retryTimers.add(timer)\n }\n return\n }\n const updated = await this.#adapter.update(job.record.info.id, {\n ...patch,\n usage,\n nextRunAt: undefined,\n finishedAt: Date.now(),\n })\n if (updated) {\n job.record = updated\n this.#emit(job.record, { type: 'job_completed', job: updated.info, ts: Date.now() }, job)\n }\n this.#sweep()\n }\n\n #progress(job: RunningJob, progress: JobProgress): void {\n const event: JobEvent = { type: 'job_progress', job: job.record.info, progress, ts: Date.now() }\n if (job.record.request.webhook?.progress === 'completion') {\n try {\n this.#options.onEvent?.(event)\n } catch {}\n return\n }\n this.#emit(job.record, event, job)\n }\n\n #emit(record: JobRecord, event: JobEvent, chainOwner?: RunningJob, { skipWebhook = false }: { skipWebhook?: boolean } = {}): void {\n try {\n this.#options.onEvent?.(event)\n } catch {}\n const webhook = record.request.webhook\n if (!webhook || skipWebhook) {\n return\n }\n const running = chainOwner ?? this.#running.get(record.info.id)\n const deliver = () => this.#deliver(webhook.url, webhook.headers, event)\n if (running) {\n running.deliveries = running.deliveries.then(deliver)\n } else {\n void deliver()\n }\n }\n\n async #deliver(url: string, headers: Record<string, string> | undefined, event: JobEvent): Promise<void> {\n const fetchImpl = this.#options.fetchImpl ?? fetch\n const attempts = this.#options.webhookAttempts ?? 3\n const baseDelay = this.#options.webhookRetryDelayMs ?? 500\n for (let attempt = 0; attempt < attempts; attempt++) {\n try {\n const res = await fetchImpl(url, {\n method: 'POST',\n headers: { 'content-type': 'application/json', ...headers },\n body: JSON.stringify(event),\n })\n if (res.ok) {\n return\n }\n } catch {}\n if (attempt < attempts - 1) {\n await new Promise((resolve) => setTimeout(resolve, baseDelay * 2 ** attempt))\n }\n }\n }\n}\n\nfunction tightest(...limits: (number | undefined)[]): number | undefined {\n const set = limits.filter((n): n is number => typeof n === 'number')\n return set.length > 0 ? Math.min(...set) : undefined\n}\n"],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@workerdeck/queue",
3
- "version": "3.0.1",
3
+ "version": "3.2.0",
4
4
  "type": "module",
5
5
  "description": "Job queue over the WorkerDeck session runner: remote services schedule one-shot runs; the queue executes them as sessions with bounded concurrency and token budgets, delivering progress and completion via webhooks. Pluggable adapter interface (in-memory bundled; redis/bullmq/pubsub adapters can implement the same contract).",
6
6
  "license": "MIT",
@@ -17,11 +17,11 @@
17
17
  }
18
18
  },
19
19
  "dependencies": {
20
- "@workerdeck/core": "3.0.1",
21
- "@workerdeck/protocol": "3.0.1"
20
+ "@workerdeck/core": "3.2.0",
21
+ "@workerdeck/protocol": "3.2.0"
22
22
  },
23
23
  "devDependencies": {
24
- "@anthropic-ai/claude-agent-sdk": "^0.3.280",
24
+ "@anthropic-ai/claude-agent-sdk": "^0.3.284",
25
25
  "@types/node": "^26.6.1",
26
26
  "rimraf": "^6.1.3",
27
27
  "tsdown": "^0.22.14",