@microfox/ai-worker 1.0.3 → 1.0.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (69) hide show
  1. package/CHANGELOG.md +14 -0
  2. package/README.md +22 -0
  3. package/dist/chainMapDefaults.d.mts +21 -0
  4. package/dist/chainMapDefaults.d.ts +21 -0
  5. package/dist/chainMapDefaults.js +59 -0
  6. package/dist/chainMapDefaults.js.map +1 -0
  7. package/dist/chainMapDefaults.mjs +10 -0
  8. package/dist/chainMapDefaults.mjs.map +1 -0
  9. package/dist/chunk-BCRJIFKB.mjs +9 -0
  10. package/dist/chunk-BCRJIFKB.mjs.map +1 -0
  11. package/dist/{chunk-72XGFZCE.mjs → chunk-CILTGUUQ.mjs} +14 -3
  12. package/dist/chunk-CILTGUUQ.mjs.map +1 -0
  13. package/dist/{chunk-7LQNS2SG.mjs → chunk-QHX55IML.mjs} +442 -56
  14. package/dist/chunk-QHX55IML.mjs.map +1 -0
  15. package/dist/chunk-SQB5FQCZ.mjs +21 -0
  16. package/dist/chunk-SQB5FQCZ.mjs.map +1 -0
  17. package/dist/{chunk-AOXGONGI.mjs → chunk-T7DRPKR6.mjs} +7 -5
  18. package/dist/chunk-T7DRPKR6.mjs.map +1 -0
  19. package/dist/chunk-XCKWV2WZ.mjs +34 -0
  20. package/dist/chunk-XCKWV2WZ.mjs.map +1 -0
  21. package/dist/chunk-ZW4PNCDH.mjs +17 -0
  22. package/dist/chunk-ZW4PNCDH.mjs.map +1 -0
  23. package/dist/client.d.mts +148 -2
  24. package/dist/client.d.ts +148 -2
  25. package/dist/client.js +13 -2
  26. package/dist/client.js.map +1 -1
  27. package/dist/client.mjs +1 -1
  28. package/dist/handler.d.mts +121 -23
  29. package/dist/handler.d.ts +121 -23
  30. package/dist/handler.js +450 -58
  31. package/dist/handler.js.map +1 -1
  32. package/dist/handler.mjs +5 -2
  33. package/dist/hitlConfig.d.mts +46 -0
  34. package/dist/hitlConfig.d.ts +46 -0
  35. package/dist/hitlConfig.js +33 -0
  36. package/dist/hitlConfig.js.map +1 -0
  37. package/dist/hitlConfig.mjs +8 -0
  38. package/dist/hitlConfig.mjs.map +1 -0
  39. package/dist/index.d.mts +28 -4
  40. package/dist/index.d.ts +28 -4
  41. package/dist/index.js +575 -74
  42. package/dist/index.js.map +1 -1
  43. package/dist/index.mjs +78 -20
  44. package/dist/index.mjs.map +1 -1
  45. package/dist/queue-B5n6YVQV.d.ts +306 -0
  46. package/dist/queue-DaR2UuZi.d.mts +306 -0
  47. package/dist/queue.d.mts +3 -0
  48. package/dist/queue.d.ts +3 -0
  49. package/dist/queue.js +47 -0
  50. package/dist/queue.js.map +1 -0
  51. package/dist/queue.mjs +12 -0
  52. package/dist/queue.mjs.map +1 -0
  53. package/dist/queueInputEnvelope.d.mts +31 -0
  54. package/dist/queueInputEnvelope.d.ts +31 -0
  55. package/dist/queueInputEnvelope.js +42 -0
  56. package/dist/queueInputEnvelope.js.map +1 -0
  57. package/dist/queueInputEnvelope.mjs +10 -0
  58. package/dist/queueInputEnvelope.mjs.map +1 -0
  59. package/dist/queueJobStore.d.mts +3 -2
  60. package/dist/queueJobStore.d.ts +3 -2
  61. package/dist/queueJobStore.js +6 -4
  62. package/dist/queueJobStore.js.map +1 -1
  63. package/dist/queueJobStore.mjs +1 -1
  64. package/package.json +7 -2
  65. package/dist/chunk-72XGFZCE.mjs.map +0 -1
  66. package/dist/chunk-7LQNS2SG.mjs.map +0 -1
  67. package/dist/chunk-AOXGONGI.mjs.map +0 -1
  68. package/dist/client-BqSJQ9mZ.d.mts +0 -183
  69. package/dist/client-BqSJQ9mZ.d.ts +0 -183
@@ -2,7 +2,10 @@ import {
2
2
  appendQueueJobStepInStore,
3
3
  updateQueueJobStepInStore,
4
4
  upsertInitialQueueJob
5
- } from "./chunk-AOXGONGI.mjs";
5
+ } from "./chunk-T7DRPKR6.mjs";
6
+ import {
7
+ QUEUE_ORCHESTRATION_KEYS
8
+ } from "./chunk-SQB5FQCZ.mjs";
6
9
 
7
10
  // src/handler.ts
8
11
  import { SQSClient, SendMessageCommand } from "@aws-sdk/client-sqs";
@@ -47,7 +50,7 @@ async function getJobById(jobId) {
47
50
  return null;
48
51
  }
49
52
  }
50
- function createMongoJobStore(workerId, jobId, input, metadata) {
53
+ function createMongoJobStore(workerId, jobId, input, metadata, userId) {
51
54
  return {
52
55
  update: async (update) => {
53
56
  try {
@@ -86,6 +89,7 @@ function createMongoJobStore(workerId, jobId, input, metadata) {
86
89
  output: update.output,
87
90
  error: update.error,
88
91
  metadata: metadataUpdate,
92
+ ...userId ? { userId } : {},
89
93
  createdAt: now,
90
94
  updatedAt: now,
91
95
  completedAt: set.completedAt
@@ -151,7 +155,7 @@ function createMongoJobStore(workerId, jobId, input, metadata) {
151
155
  }
152
156
  };
153
157
  }
154
- async function upsertJob(jobId, workerId, input, metadata) {
158
+ async function upsertJob(jobId, workerId, input, metadata, userId) {
155
159
  const coll = await getCollection();
156
160
  const now = (/* @__PURE__ */ new Date()).toISOString();
157
161
  await coll.updateOne(
@@ -164,6 +168,7 @@ async function upsertJob(jobId, workerId, input, metadata) {
164
168
  status: "queued",
165
169
  input: input ?? {},
166
170
  metadata: metadata ?? {},
171
+ ...userId ? { userId } : {},
167
172
  createdAt: now,
168
173
  updatedAt: now
169
174
  }
@@ -242,13 +247,14 @@ async function loadJob(jobId) {
242
247
  error: parseJson(data.error),
243
248
  metadata: parseJson(data.metadata) ?? {},
244
249
  internalJobs,
250
+ ...data.userId ? { userId: data.userId } : {},
245
251
  createdAt: data.createdAt,
246
252
  updatedAt: data.updatedAt,
247
253
  completedAt: data.completedAt
248
254
  };
249
255
  return record;
250
256
  }
251
- function createRedisJobStore(workerId, jobId, input, metadata) {
257
+ function createRedisJobStore(workerId, jobId, input, metadata, userId) {
252
258
  return {
253
259
  update: async (update) => {
254
260
  const redis = getRedis();
@@ -306,34 +312,161 @@ function createRedisJobStore(workerId, jobId, input, metadata) {
306
312
  }
307
313
  };
308
314
  }
309
- async function upsertRedisJob(jobId, workerId, input, metadata) {
315
+ async function upsertRedisJob(jobId, workerId, input, metadata, userId) {
310
316
  const redis = getRedis();
311
317
  const key = jobKey(jobId);
312
318
  const now = (/* @__PURE__ */ new Date()).toISOString();
313
- const doc = {
314
- jobId,
315
- workerId,
316
- status: "queued",
317
- input,
318
- metadata,
319
- createdAt: now,
320
- updatedAt: now
321
- };
322
319
  const toSet = {
323
320
  jobId,
324
321
  workerId,
325
- status: doc.status,
326
- input: JSON.stringify(doc.input ?? {}),
327
- metadata: JSON.stringify(doc.metadata ?? {}),
322
+ status: "queued",
323
+ input: JSON.stringify(input ?? {}),
324
+ metadata: JSON.stringify(metadata ?? {}),
328
325
  createdAt: now,
329
326
  updatedAt: now
330
327
  };
328
+ if (userId) toSet.userId = userId;
331
329
  await redis.hset(key, toSet);
332
330
  if (jobTtlSeconds > 0) {
333
331
  await redis.expire(key, jobTtlSeconds);
334
332
  }
335
333
  }
336
334
 
335
+ // src/retryConfig.ts
336
+ var BUILT_IN_PATTERNS = {
337
+ "rate-limit": {
338
+ match: (err) => /rate.?limit|too.?many.?requests/i.test(err.message) || err.status === 429 || err.code === 429 || err.name === "RateLimitError",
339
+ delayMs: (attempt) => attempt * 1e4,
340
+ // 10s, 20s, 30s…
341
+ injectContext: false
342
+ },
343
+ "json-parse": {
344
+ match: (err) => err.name === "SyntaxError" || err.name === "ZodError" || /json|parse|unexpected.?token|invalid.?format/i.test(err.message),
345
+ delayMs: (_attempt) => 0,
346
+ // Immediate — model self-corrects from ctx
347
+ injectContext: true
348
+ },
349
+ overloaded: {
350
+ match: (err) => /overloaded|model.?is.?busy/i.test(err.message) || err.status === 529 || err.code === 529,
351
+ delayMs: (attempt) => attempt * 15e3,
352
+ // 15s, 30s…
353
+ injectContext: false
354
+ },
355
+ "server-error": {
356
+ match: (err) => /internal.?server.?error|service.?unavailable|bad.?gateway/i.test(err.message) || typeof err.status === "number" && err.status >= 500 && err.status < 600,
357
+ delayMs: (attempt) => attempt * 5e3,
358
+ // 5s, 10s…
359
+ injectContext: false
360
+ }
361
+ };
362
+ function matchesRetryPattern(err, patterns, attempt) {
363
+ for (const pattern of patterns) {
364
+ if (typeof pattern === "string") {
365
+ const impl = BUILT_IN_PATTERNS[pattern];
366
+ if (impl.match(err)) {
367
+ return { matched: true, delayMs: impl.delayMs(attempt), injectContext: impl.injectContext };
368
+ }
369
+ } else {
370
+ let matched = false;
371
+ if (pattern.match instanceof RegExp) {
372
+ matched = pattern.match.test(err.message);
373
+ } else {
374
+ try {
375
+ matched = pattern.match(err);
376
+ } catch {
377
+ matched = false;
378
+ }
379
+ }
380
+ if (matched) {
381
+ const delayMs = typeof pattern.delayMs === "function" ? pattern.delayMs(attempt) : pattern.delayMs ?? 0;
382
+ return { matched: true, delayMs, injectContext: pattern.injectContext ?? false };
383
+ }
384
+ }
385
+ }
386
+ return { matched: false, delayMs: 0, injectContext: false };
387
+ }
388
+ async function executeWithRetry(fn, config, onRetry) {
389
+ const maxAttempts = config.maxAttempts ?? 3;
390
+ let lastError;
391
+ for (let attempt = 1; attempt <= maxAttempts; attempt++) {
392
+ const retryCtx = attempt > 1 && lastError ? {
393
+ attempt,
394
+ maxAttempts,
395
+ lastError: {
396
+ message: lastError.message,
397
+ name: lastError.name,
398
+ stack: lastError.stack,
399
+ code: lastError.code ?? lastError.status
400
+ }
401
+ } : void 0;
402
+ try {
403
+ return await fn(retryCtx);
404
+ } catch (err) {
405
+ lastError = err instanceof Error ? err : new Error(String(err));
406
+ if (err?.name === "TokenBudgetExceededError") throw err;
407
+ if (attempt >= maxAttempts) throw err;
408
+ const retryAttemptNumber = attempt;
409
+ const { matched, delayMs } = matchesRetryPattern(lastError, config.on, retryAttemptNumber);
410
+ if (!matched) throw err;
411
+ const nextCtx = {
412
+ attempt: attempt + 1,
413
+ maxAttempts,
414
+ lastError: {
415
+ message: lastError.message,
416
+ name: lastError.name,
417
+ stack: lastError.stack,
418
+ code: lastError.code ?? lastError.status
419
+ }
420
+ };
421
+ onRetry?.(nextCtx, delayMs);
422
+ if (delayMs > 0) {
423
+ await new Promise((r) => setTimeout(r, delayMs));
424
+ }
425
+ }
426
+ }
427
+ throw lastError ?? new Error("executeWithRetry: unknown error");
428
+ }
429
+
430
+ // src/tokenBudget.ts
431
+ var TokenBudgetExceededError = class extends Error {
432
+ constructor(used, budget) {
433
+ super(`Token budget exceeded: used ${used} tokens (budget: ${budget})`);
434
+ this.name = "TokenBudgetExceededError";
435
+ this.used = used;
436
+ this.budget = budget;
437
+ }
438
+ };
439
+ function createTokenTracker(budget) {
440
+ let inputTokens = 0;
441
+ let outputTokens = 0;
442
+ function checkBudget() {
443
+ if (budget !== null) {
444
+ const total = inputTokens + outputTokens;
445
+ if (total > budget) {
446
+ throw new TokenBudgetExceededError(total, budget);
447
+ }
448
+ }
449
+ }
450
+ return {
451
+ report(usage) {
452
+ inputTokens += usage.inputTokens;
453
+ outputTokens += usage.outputTokens;
454
+ checkBudget();
455
+ },
456
+ getState() {
457
+ return { inputTokens, outputTokens, budget };
458
+ },
459
+ getBudgetInfo() {
460
+ const used = inputTokens + outputTokens;
461
+ return {
462
+ used,
463
+ budget,
464
+ remaining: budget !== null ? Math.max(0, budget - used) : null
465
+ };
466
+ }
467
+ };
468
+ }
469
+
337
470
  // src/handler.ts
338
471
  var SQS_MAX_DELAY_SECONDS = 900;
339
472
  function createWorkerLogger(jobId, workerId) {
@@ -356,6 +489,67 @@ function createWorkerLogger(jobId, workerId) {
356
489
  };
357
490
  }
358
491
  var WORKER_QUEUE_KEY = "__workerQueue";
492
+ async function loadPreviousOutputsBeforeStep(queueRuntime, queueJobId, beforeStepIndex) {
493
+ if (!queueJobId || typeof queueRuntime.getQueueJob !== "function") {
494
+ return [];
495
+ }
496
+ try {
497
+ const job = await queueRuntime.getQueueJob(queueJobId);
498
+ if (!job?.steps) return [];
499
+ return job.steps.slice(0, beforeStepIndex).map((s, i) => ({ stepIndex: i, workerId: s.workerId, output: s.output }));
500
+ } catch (e) {
501
+ if (process.env.AI_WORKER_QUEUES_DEBUG === "1") {
502
+ console.warn("[Worker] getQueueJob failed (resume mapping):", e?.message ?? e);
503
+ }
504
+ return [];
505
+ }
506
+ }
507
+ async function maybeApplyHitlResumeMapper(params, queueRuntime) {
508
+ const inputObj = params.input;
509
+ if (!inputObj || typeof inputObj !== "object") return;
510
+ if (!("__hitlInput" in inputObj)) return;
511
+ const wq = inputObj[WORKER_QUEUE_KEY];
512
+ if (!wq?.id || typeof wq.stepIndex !== "number") return;
513
+ const queueId = wq.id;
514
+ const stepIndex = wq.stepIndex;
515
+ const initialInput = wq.initialInput;
516
+ const queueJobId = wq.queueJobId;
517
+ const previousOutputs = await loadPreviousOutputsBeforeStep(queueRuntime, queueJobId, stepIndex);
518
+ const pendingInput = { ...inputObj };
519
+ for (const key of QUEUE_ORCHESTRATION_KEYS) {
520
+ delete pendingInput[key];
521
+ }
522
+ delete pendingInput[WORKER_QUEUE_KEY];
523
+ const reviewerInput = inputObj.__hitlInput;
524
+ const decision = inputObj.__hitlDecision;
525
+ let merged;
526
+ if (typeof queueRuntime.invokeResume === "function") {
527
+ merged = await queueRuntime.invokeResume(queueId, stepIndex, {
528
+ initialInput,
529
+ previousOutputs,
530
+ reviewerInput,
531
+ pendingInput
532
+ });
533
+ } else {
534
+ merged = {
535
+ ...pendingInput,
536
+ ...reviewerInput !== null && typeof reviewerInput === "object" ? reviewerInput : {}
537
+ };
538
+ }
539
+ const mergedObj = merged !== null && typeof merged === "object" ? merged : { value: merged };
540
+ params.input = {
541
+ ...mergedObj,
542
+ [WORKER_QUEUE_KEY]: wq,
543
+ ...decision !== void 0 ? { __hitlDecision: decision } : {}
544
+ };
545
+ }
546
+ function getWorkerQueueContext(input, metadata) {
547
+ const fromInput = input !== null && typeof input === "object" && WORKER_QUEUE_KEY in input ? input[WORKER_QUEUE_KEY] : void 0;
548
+ const fromMeta = metadata !== void 0 && typeof metadata === "object" && WORKER_QUEUE_KEY in metadata ? metadata[WORKER_QUEUE_KEY] : void 0;
549
+ const q = fromInput ?? fromMeta;
550
+ if (q === null || typeof q !== "object") return void 0;
551
+ return q;
552
+ }
359
553
  async function notifyQueueJobStep(queueJobId, action, params) {
360
554
  try {
361
555
  if (action === "append") {
@@ -375,7 +569,7 @@ async function notifyQueueJobStep(queueJobId, action, params) {
375
569
  return;
376
570
  }
377
571
  if (params.stepIndex === void 0) return;
378
- const status = action === "start" ? "running" : action === "complete" ? "completed" : action === "fail" ? "failed" : void 0;
572
+ const status = action === "start" ? "running" : action === "awaiting_approval" ? "awaiting_approval" : action === "complete" ? "completed" : action === "fail" ? "failed" : void 0;
379
573
  if (!status) return;
380
574
  await updateQueueJobStepInStore({
381
575
  queueJobId,
@@ -395,6 +589,13 @@ async function notifyQueueJobStep(queueJobId, action, params) {
395
589
  status
396
590
  });
397
591
  } catch (err) {
592
+ if (action === "append") {
593
+ console.error("[Worker] Queue append failed (rethrowing):", {
594
+ queueJobId,
595
+ error: err?.message ?? String(err)
596
+ });
597
+ throw err;
598
+ }
398
599
  console.warn("[Worker] Queue job update error:", {
399
600
  queueJobId,
400
601
  action,
@@ -404,26 +605,152 @@ async function notifyQueueJobStep(queueJobId, action, params) {
404
605
  }
405
606
  function wrapHandlerForQueue(handler, queueRuntime) {
406
607
  return async (params) => {
407
- const queueContext = params.input?.[WORKER_QUEUE_KEY];
408
- const output = await handler(params);
409
- if (!queueContext || typeof queueContext !== "object" || !queueContext.id) {
608
+ await maybeApplyHitlResumeMapper(params, queueRuntime);
609
+ const inputObj = params.input !== null && typeof params.input === "object" ? params.input : {};
610
+ const queueContextRaw = inputObj[WORKER_QUEUE_KEY];
611
+ const queueCtxForRetry = queueContextRaw && typeof queueContextRaw === "object" ? queueContextRaw : void 0;
612
+ const stepRetryConfig = queueCtxForRetry?.id && typeof queueCtxForRetry.stepIndex === "number" && typeof queueRuntime.getStepAt === "function" ? queueRuntime.getStepAt(queueCtxForRetry.id, queueCtxForRetry.stepIndex)?.retry : void 0;
613
+ const domainInput = { ...inputObj };
614
+ for (const key of QUEUE_ORCHESTRATION_KEYS) {
615
+ delete domainInput[key];
616
+ }
617
+ delete domainInput[WORKER_QUEUE_KEY];
618
+ params.input = domainInput;
619
+ let output;
620
+ if (stepRetryConfig && stepRetryConfig.on.length > 0) {
621
+ output = await executeWithRetry(
622
+ async (retryCtx) => {
623
+ params.ctx.retryContext = retryCtx;
624
+ return handler(params);
625
+ },
626
+ stepRetryConfig,
627
+ (retryCtx, delayMs) => {
628
+ const logger = params.ctx.logger;
629
+ if (logger?.warn) {
630
+ logger.warn(
631
+ `[queue-retry] Retrying step (attempt ${retryCtx.attempt}/${retryCtx.maxAttempts}): ${retryCtx.lastError.message}`,
632
+ { delayMs }
633
+ );
634
+ } else {
635
+ console.warn("[queue-retry] Step retry", { attempt: retryCtx.attempt, error: retryCtx.lastError.message, delayMs });
636
+ }
637
+ }
638
+ );
639
+ } else {
640
+ output = await handler(params);
641
+ }
642
+ if (!queueContextRaw || typeof queueContextRaw !== "object") {
643
+ return output;
644
+ }
645
+ const queueContext = queueContextRaw;
646
+ if (!queueContext.id) {
410
647
  return output;
411
648
  }
412
649
  const { id: queueId, stepIndex, initialInput, queueJobId } = queueContext;
413
- const jobId = params.ctx?.jobId;
414
- const workerId = params.ctx?.workerId ?? "";
650
+ const arrayStepIndex = queueContext.arrayStepIndex ?? stepIndex;
651
+ const jobId = params.ctx.jobId;
652
+ const workerId = params.ctx.workerId ?? "";
415
653
  const next = queueRuntime.getNextStep(queueId, stepIndex);
416
654
  const childJobId = next ? `job-${Date.now()}-${Math.random().toString(36).slice(2, 11)}` : void 0;
655
+ const iterationCount = queueContext.iterationCount ?? 0;
656
+ if (typeof queueRuntime.invokeLoop === "function") {
657
+ const currentStep = typeof queueRuntime.getStepAt === "function" ? queueRuntime.getStepAt(queueId, stepIndex) : void 0;
658
+ const maxIterations = currentStep?.loop?.maxIterations ?? 50;
659
+ if (iterationCount < maxIterations - 1) {
660
+ let previousOutputsForLoop = [];
661
+ let stepsLengthBeforeAppend = arrayStepIndex + 1;
662
+ if (queueJobId && typeof queueRuntime.getQueueJob === "function") {
663
+ try {
664
+ const job = await queueRuntime.getQueueJob(queueJobId);
665
+ if (job?.steps) {
666
+ previousOutputsForLoop = job.steps.slice(0, stepIndex).map((s, i) => ({ stepIndex: i, workerId: s.workerId, output: s.output }));
667
+ stepsLengthBeforeAppend = job.steps.length;
668
+ }
669
+ } catch {
670
+ }
671
+ }
672
+ previousOutputsForLoop = previousOutputsForLoop.concat([{ stepIndex, workerId, output }]);
673
+ const shouldLoop = await queueRuntime.invokeLoop(queueId, stepIndex, {
674
+ output,
675
+ stepIndex,
676
+ iterationCount,
677
+ initialInput,
678
+ previousOutputs: previousOutputsForLoop
679
+ });
680
+ if (shouldLoop) {
681
+ const loopJobId = `job-${Date.now()}-${Math.random().toString(36).slice(2, 11)}`;
682
+ let loopInput = output;
683
+ if (typeof queueRuntime.invokeChain === "function") {
684
+ loopInput = await queueRuntime.invokeChain(queueId, stepIndex, {
685
+ initialInput,
686
+ previousOutputs: previousOutputsForLoop
687
+ });
688
+ }
689
+ const loopInputWithQueue = {
690
+ ...loopInput !== null && typeof loopInput === "object" ? loopInput : { value: loopInput },
691
+ [WORKER_QUEUE_KEY]: {
692
+ id: queueId,
693
+ stepIndex,
694
+ // definition index stays fixed
695
+ arrayStepIndex: stepsLengthBeforeAppend,
696
+ // actual index for next iteration
697
+ initialInput,
698
+ queueJobId,
699
+ iterationCount: iterationCount + 1
700
+ }
701
+ };
702
+ if (queueJobId) {
703
+ await notifyQueueJobStep(queueJobId, "append", { workerJobId: loopJobId, workerId });
704
+ }
705
+ if (queueJobId && typeof arrayStepIndex === "number") {
706
+ await notifyQueueJobStep(queueJobId, "complete", {
707
+ queueId,
708
+ stepIndex: arrayStepIndex,
709
+ workerJobId: jobId,
710
+ workerId,
711
+ output
712
+ });
713
+ }
714
+ if (currentStep?.requiresApproval && queueJobId) {
715
+ const hitlUiSpec = currentStep.hitl && typeof currentStep.hitl === "object" && "ui" in currentStep.hitl ? currentStep.hitl.ui : void 0;
716
+ const pendingInput = {
717
+ ...loopInputWithQueue,
718
+ ...hitlUiSpec !== void 0 ? { hitl: { uiSpec: hitlUiSpec } } : {},
719
+ __hitlPending: {
720
+ queueId,
721
+ queueJobId,
722
+ stepIndex,
723
+ workerId,
724
+ createdAt: (/* @__PURE__ */ new Date()).toISOString()
725
+ }
726
+ };
727
+ await notifyQueueJobStep(queueJobId, "awaiting_approval", {
728
+ queueId,
729
+ stepIndex: stepsLengthBeforeAppend,
730
+ workerJobId: loopJobId,
731
+ workerId,
732
+ input: pendingInput
733
+ });
734
+ return output;
735
+ }
736
+ await params.ctx.dispatchWorker(workerId, loopInputWithQueue, {
737
+ await: false,
738
+ jobId: loopJobId
739
+ });
740
+ return output;
741
+ }
742
+ }
743
+ }
417
744
  if (next && queueJobId) {
418
745
  await notifyQueueJobStep(queueJobId, "append", {
419
746
  workerJobId: childJobId,
420
747
  workerId: next.workerId
421
748
  });
422
749
  }
423
- if (queueJobId && typeof stepIndex === "number") {
750
+ if (queueJobId && typeof arrayStepIndex === "number") {
424
751
  await notifyQueueJobStep(queueJobId, "complete", {
425
752
  queueId,
426
- stepIndex,
753
+ stepIndex: arrayStepIndex,
427
754
  workerJobId: jobId,
428
755
  workerId,
429
756
  output
@@ -433,7 +760,7 @@ function wrapHandlerForQueue(handler, queueRuntime) {
433
760
  return output;
434
761
  }
435
762
  let nextInput = output;
436
- if (next.mapInputFromPrev && typeof queueRuntime.invokeMapInput === "function") {
763
+ if (typeof queueRuntime.invokeChain === "function") {
437
764
  let previousOutputs = [];
438
765
  if (queueJobId && typeof queueRuntime.getQueueJob === "function") {
439
766
  try {
@@ -441,7 +768,7 @@ function wrapHandlerForQueue(handler, queueRuntime) {
441
768
  if (job?.steps) {
442
769
  const fromStore = job.steps.slice(0, stepIndex).map((s, i) => ({ stepIndex: i, workerId: s.workerId, output: s.output }));
443
770
  previousOutputs = fromStore.concat([
444
- { stepIndex, workerId: params.ctx?.workerId ?? "", output }
771
+ { stepIndex, workerId: params.ctx.workerId ?? "", output }
445
772
  ]);
446
773
  }
447
774
  } catch (e) {
@@ -450,12 +777,10 @@ function wrapHandlerForQueue(handler, queueRuntime) {
450
777
  }
451
778
  }
452
779
  }
453
- nextInput = await queueRuntime.invokeMapInput(
454
- queueId,
455
- stepIndex + 1,
780
+ nextInput = await queueRuntime.invokeChain(queueId, stepIndex + 1, {
456
781
  initialInput,
457
782
  previousOutputs
458
- );
783
+ });
459
784
  }
460
785
  const nextInputWithQueue = {
461
786
  ...nextInput !== null && typeof nextInput === "object" ? nextInput : { value: nextInput },
@@ -475,6 +800,37 @@ function wrapHandlerForQueue(handler, queueRuntime) {
475
800
  delaySeconds: next.delaySeconds
476
801
  });
477
802
  }
803
+ if (next.requiresApproval && queueJobId && typeof stepIndex === "number") {
804
+ const hitlUiSpec = next.hitl && typeof next.hitl === "object" && "ui" in next.hitl ? next.hitl.ui : void 0;
805
+ const pendingInput = {
806
+ ...nextInputWithQueue,
807
+ ...hitlUiSpec !== void 0 ? { hitl: { uiSpec: hitlUiSpec } } : {},
808
+ __hitlPending: {
809
+ queueId,
810
+ queueJobId,
811
+ stepIndex: stepIndex + 1,
812
+ workerId: next.workerId,
813
+ createdAt: (/* @__PURE__ */ new Date()).toISOString()
814
+ }
815
+ };
816
+ await notifyQueueJobStep(queueJobId, "awaiting_approval", {
817
+ queueId,
818
+ stepIndex: stepIndex + 1,
819
+ workerJobId: childJobId,
820
+ workerId: next.workerId,
821
+ input: pendingInput
822
+ });
823
+ if (debug) {
824
+ console.log("[Worker] Queue chain paused for HITL approval:", {
825
+ queueId,
826
+ queueJobId,
827
+ nextStep: stepIndex + 1,
828
+ nextWorkerId: next.workerId,
829
+ pendingWorkerJobId: childJobId
830
+ });
831
+ }
832
+ return output;
833
+ }
478
834
  await params.ctx.dispatchWorker(next.workerId, nextInputWithQueue, {
479
835
  await: false,
480
836
  delaySeconds: next.delaySeconds,
@@ -498,6 +854,7 @@ function createDispatchWorker(parentJobId, parentWorkerId, parentContext, jobSto
498
854
  const metadata = options?.metadata ?? {};
499
855
  const serializedContext = {};
500
856
  if (parentContext.requestId) serializedContext.requestId = parentContext.requestId;
857
+ if (parentContext.userId) serializedContext.userId = parentContext.userId;
501
858
  const messageBody = {
502
859
  workerId: calleeWorkerId,
503
860
  jobId: childJobId,
@@ -587,13 +944,14 @@ async function sendWebhook(webhookUrl, payload) {
587
944
  });
588
945
  }
589
946
  }
590
- function createLambdaHandler(handler, outputSchema) {
947
+ function createLambdaHandler(handler, outputSchema, options) {
591
948
  return async (event, lambdaContext) => {
592
949
  const promises = event.Records.map(async (record) => {
593
950
  let messageBody = null;
594
951
  try {
595
952
  messageBody = JSON.parse(record.body);
596
- const { workerId, jobId, input, context, webhookUrl, metadata = {} } = messageBody;
953
+ const { workerId, jobId, input, context, webhookUrl, metadata = {}, userId: messageUserId, maxTokens } = messageBody;
954
+ const userId = context.userId ?? messageUserId;
597
955
  const raw = (process.env.WORKER_DATABASE_TYPE || "upstash-redis").toLowerCase();
598
956
  const jobStoreType = raw === "mongodb" ? "mongodb" : "upstash-redis";
599
957
  if (jobStoreType === "upstash-redis" && isRedisJobStoreConfigured()) {
@@ -619,33 +977,45 @@ function createLambdaHandler(handler, outputSchema) {
619
977
  }
620
978
  let jobStore;
621
979
  if (jobStoreType === "upstash-redis" && isRedisJobStoreConfigured()) {
622
- await upsertRedisJob(jobId, workerId, input, metadata);
623
- jobStore = createRedisJobStore(workerId, jobId, input, metadata);
980
+ await upsertRedisJob(jobId, workerId, input, metadata, userId);
981
+ jobStore = createRedisJobStore(workerId, jobId, input, metadata, userId);
624
982
  } else if (jobStoreType === "mongodb" || isMongoJobStoreConfigured()) {
625
- await upsertJob(jobId, workerId, input, metadata);
626
- jobStore = createMongoJobStore(workerId, jobId, input, metadata);
983
+ await upsertJob(jobId, workerId, input, metadata, userId);
984
+ jobStore = createMongoJobStore(workerId, jobId, input, metadata, userId);
985
+ }
986
+ if (userId) {
987
+ console.log(`[WORKER_USER:${userId}]`, { jobId, workerId, timestamp: (/* @__PURE__ */ new Date()).toISOString() });
627
988
  }
628
989
  const baseContext = {
629
990
  jobId,
630
991
  workerId,
631
992
  requestId: context.requestId || lambdaContext.awsRequestId,
993
+ ...userId ? { userId } : {},
632
994
  ...context
633
995
  };
996
+ const tokenTracker = createTokenTracker(maxTokens ?? null);
997
+ const logger = createWorkerLogger(jobId, workerId);
634
998
  const handlerContext = {
635
999
  ...baseContext,
636
1000
  ...jobStore ? { jobStore } : {},
637
- logger: createWorkerLogger(jobId, workerId),
638
- dispatchWorker: createDispatchWorker(
639
- jobId,
640
- workerId,
641
- baseContext,
642
- jobStore
643
- )
1001
+ logger,
1002
+ dispatchWorker: createDispatchWorker(jobId, workerId, baseContext, jobStore),
1003
+ reportTokenUsage: async (usage) => {
1004
+ tokenTracker.report(usage);
1005
+ const state = tokenTracker.getState();
1006
+ if (jobStore) {
1007
+ await jobStore.update({ metadata: { tokenUsage: state } }).catch((e) => {
1008
+ logger.warn("Failed to persist tokenUsage to job store", { error: e?.message });
1009
+ });
1010
+ }
1011
+ },
1012
+ getTokenBudget: () => tokenTracker.getBudgetInfo(),
1013
+ retryContext: void 0
644
1014
  };
645
1015
  if (jobStore) {
646
1016
  try {
647
1017
  await jobStore.update({ status: "running" });
648
- const queueCtxForLog = input?.__workerQueue ?? metadata?.__workerQueue;
1018
+ const queueCtxForLog = getWorkerQueueContext(input, metadata);
649
1019
  console.log("[Worker] Job status updated to running:", {
650
1020
  jobId,
651
1021
  workerId,
@@ -660,7 +1030,7 @@ function createLambdaHandler(handler, outputSchema) {
660
1030
  });
661
1031
  }
662
1032
  }
663
- const queueCtx = input?.__workerQueue ?? metadata?.__workerQueue;
1033
+ const queueCtx = getWorkerQueueContext(input, metadata);
664
1034
  if (queueCtx?.queueJobId && typeof queueCtx.stepIndex === "number") {
665
1035
  if (queueCtx.stepIndex === 0) {
666
1036
  try {
@@ -669,7 +1039,8 @@ function createLambdaHandler(handler, outputSchema) {
669
1039
  queueId: queueCtx.id,
670
1040
  firstWorkerId: workerId,
671
1041
  firstWorkerJobId: jobId,
672
- metadata
1042
+ metadata,
1043
+ userId
673
1044
  });
674
1045
  } catch (e) {
675
1046
  console.warn("[Worker] Failed to upsert initial queue job:", {
@@ -681,7 +1052,9 @@ function createLambdaHandler(handler, outputSchema) {
681
1052
  }
682
1053
  await notifyQueueJobStep(queueCtx.queueJobId, "start", {
683
1054
  queueId: queueCtx.id,
684
- stepIndex: queueCtx.stepIndex,
1055
+ // Use arrayStepIndex when set — it tracks the actual steps[] position for
1056
+ // looping steps where the definition index stays fixed across iterations.
1057
+ stepIndex: queueCtx.arrayStepIndex ?? queueCtx.stepIndex,
685
1058
  workerJobId: jobId,
686
1059
  workerId,
687
1060
  input
@@ -689,12 +1062,21 @@ function createLambdaHandler(handler, outputSchema) {
689
1062
  }
690
1063
  let output;
691
1064
  try {
692
- output = await handler({
693
- input,
694
- ctx: handlerContext
695
- });
696
- if (outputSchema) {
697
- output = outputSchema.parse(output);
1065
+ const workerRetryConfig = options?.retry;
1066
+ const executeHandler = async (retryCtx) => {
1067
+ handlerContext.retryContext = retryCtx;
1068
+ const result = await handler({ input, ctx: handlerContext });
1069
+ return outputSchema ? outputSchema.parse(result) : result;
1070
+ };
1071
+ if (workerRetryConfig && workerRetryConfig.on.length > 0) {
1072
+ output = await executeWithRetry(executeHandler, workerRetryConfig, (retryCtx, delayMs) => {
1073
+ logger.warn(
1074
+ `[worker-retry] Retrying handler (attempt ${retryCtx.attempt}/${retryCtx.maxAttempts}): ${retryCtx.lastError.message}`,
1075
+ { delayMs }
1076
+ );
1077
+ });
1078
+ } else {
1079
+ output = await executeHandler(void 0);
698
1080
  }
699
1081
  } catch (error) {
700
1082
  const errorPayload = {
@@ -726,7 +1108,7 @@ function createLambdaHandler(handler, outputSchema) {
726
1108
  });
727
1109
  }
728
1110
  }
729
- const queueCtxFail = input?.__workerQueue ?? metadata?.__workerQueue;
1111
+ const queueCtxFail = getWorkerQueueContext(input, metadata);
730
1112
  if (queueCtxFail?.queueJobId && typeof queueCtxFail.stepIndex === "number") {
731
1113
  await notifyQueueJobStep(queueCtxFail.queueJobId, "fail", {
732
1114
  queueId: queueCtxFail.id,
@@ -789,9 +1171,13 @@ function createLambdaHandler(handler, outputSchema) {
789
1171
  }
790
1172
 
791
1173
  export {
1174
+ matchesRetryPattern,
1175
+ executeWithRetry,
1176
+ TokenBudgetExceededError,
1177
+ createTokenTracker,
792
1178
  SQS_MAX_DELAY_SECONDS,
793
1179
  createWorkerLogger,
794
1180
  wrapHandlerForQueue,
795
1181
  createLambdaHandler
796
1182
  };
797
- //# sourceMappingURL=chunk-7LQNS2SG.mjs.map
1183
+ //# sourceMappingURL=chunk-QHX55IML.mjs.map