@osolmaz/pi-workflows 0.15.1 → 0.15.3

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.
@@ -402,15 +402,37 @@ export class WorkflowHost {
402
402
  case "run.pause": {
403
403
  const runId = requireRunId(request);
404
404
  const active = this.activeRuns.get(runId);
405
- if (active === undefined)
406
- return { outcome: "rejected", error: "Run is not active" };
407
- this.commitActivePause(active);
408
- active.control = "pause";
409
- afterCommit.push(() => void active.supervisor.stop("cancelled"));
410
- return { outcome: "accepted", receipt: { runId, status: "parked", paused: true } };
405
+ if (active !== undefined) {
406
+ if (active.control === "pause") {
407
+ return { outcome: "adopted", receipt: { runId, status: "parked", paused: true } };
408
+ }
409
+ if (active.control === "handoff") {
410
+ const paused = this.queue.pauseParkedWorkflowRun({ runId });
411
+ return paused
412
+ ? { outcome: "accepted", receipt: { runId, status: "parked", paused: true } }
413
+ : { outcome: "rejected", error: "Run handoff is not pausable" };
414
+ }
415
+ if (active.control !== undefined) {
416
+ return { outcome: "rejected", error: `Run is already handling ${active.control}` };
417
+ }
418
+ this.commitActivePause(active);
419
+ active.control = "pause";
420
+ afterCommit.push(() => void active.supervisor.stop("cancelled"));
421
+ return { outcome: "accepted", receipt: { runId, status: "parked", paused: true } };
422
+ }
423
+ const paused = this.queue.pauseParkedWorkflowRun({ runId });
424
+ return paused
425
+ ? { outcome: "accepted", receipt: { runId, status: "parked", paused: true } }
426
+ : { outcome: "rejected", error: "Run is not pausable" };
411
427
  }
412
428
  case "run.resume": {
413
429
  const runId = requireRunId(request);
430
+ if (this.queue.resumePausedInteraction({ runId })) {
431
+ return {
432
+ outcome: "accepted",
433
+ receipt: { runId, status: "parked", paused: false, waitingForInteraction: true },
434
+ };
435
+ }
414
436
  if (this.activeRuns.has(runId) || this.pendingStarts.has(runId)) {
415
437
  return { outcome: "adopted", receipt: { runId, active: true } };
416
438
  }
@@ -448,17 +470,25 @@ export class WorkflowHost {
448
470
  case "interaction.update": {
449
471
  const payload = requireRecord(request.payload, "interaction update payload");
450
472
  if (payload.claimPresentation === true) {
451
- const interaction = this.hostState.claimInteractionPresentation({
452
- requestId: requireString(payload.requestId, "requestId"),
453
- expectedRevision: requireNonNegativeInteger(request.expectedRevision, "expectedRevision"),
454
- presenterId: request.clientId,
455
- leaseMs: PRESENTATION_CLAIM_LEASE_MS,
456
- });
457
- return {
458
- outcome: "accepted",
459
- revision: interaction.revision,
460
- receipt: interaction,
461
- };
473
+ try {
474
+ const interaction = this.hostState.claimInteractionPresentation({
475
+ requestId: requireString(payload.requestId, "requestId"),
476
+ expectedRevision: requireNonNegativeInteger(request.expectedRevision, "expectedRevision"),
477
+ presenterId: request.clientId,
478
+ leaseMs: PRESENTATION_CLAIM_LEASE_MS,
479
+ });
480
+ return {
481
+ outcome: "accepted",
482
+ revision: interaction.revision,
483
+ receipt: interaction,
484
+ };
485
+ }
486
+ catch (error) {
487
+ if (errorMessage(error) === "Interactive request presentation claim conflict") {
488
+ return { outcome: "conflict", error: errorMessage(error) };
489
+ }
490
+ throw error;
491
+ }
462
492
  }
463
493
  if (typeof payload.sessionEntryId === "string") {
464
494
  const interaction = this.hostState.markInteractionPresented({
@@ -585,11 +615,9 @@ export class WorkflowHost {
585
615
  this.deliveryClaims.delete(claimId);
586
616
  }
587
617
  const claimId = randomUUID();
588
- this.deliveryClaims.set(claimId, {
589
- ...options,
590
- expiresAt: now + DELIVERY_CLAIM_LEASE_MS,
591
- });
592
- return claimId;
618
+ const expiresAt = now + DELIVERY_CLAIM_LEASE_MS;
619
+ this.deliveryClaims.set(claimId, { ...options, expiresAt });
620
+ return { claimId, claimExpiresAt: new Date(expiresAt).toISOString() };
593
621
  }
594
622
  deliveryClaim(claimId, clientId, kind, resourceId, targetSessionId) {
595
623
  const claim = this.deliveryClaims.get(claimId);
@@ -604,8 +632,34 @@ export class WorkflowHost {
604
632
  }
605
633
  return claim;
606
634
  }
635
+ validateDelivery(command, payload, kind) {
636
+ const resourceId = requireString(payload.resourceId, "resourceId");
637
+ const targetSessionId = requireString(payload.targetSessionId, "targetSessionId");
638
+ const claimId = requireString(payload.claimId, "claimId");
639
+ const claim = this.deliveryClaim(claimId, command.clientId, kind, resourceId, targetSessionId);
640
+ if (claim === undefined) {
641
+ return { outcome: "accepted", receipt: { claimId, live: false } };
642
+ }
643
+ const live = kind === "notification"
644
+ ? this.queue.isWorkflowNotificationClaimLive({
645
+ notificationId: resourceId,
646
+ targetSessionId,
647
+ claimToken: claim.token,
648
+ })
649
+ : this.queue.isWorkflowTurnIntentClaimLive({
650
+ intentId: resourceId,
651
+ targetSessionId,
652
+ claimToken: claim.token,
653
+ });
654
+ if (!live)
655
+ this.deliveryClaims.delete(claimId);
656
+ return { outcome: "accepted", receipt: { claimId, live } };
657
+ }
607
658
  claimNotification(command) {
608
659
  const payload = requireRecord(command.payload, "notification claim payload");
660
+ if (payload.validateClaim === true) {
661
+ return this.validateDelivery(command, payload, "notification");
662
+ }
609
663
  const targetSessionId = requireString(payload.targetSessionId, "targetSessionId");
610
664
  const token = randomUUID();
611
665
  const notification = this.queue.claimPendingWorkflowNotifications({
@@ -617,7 +671,7 @@ export class WorkflowHost {
617
671
  if (notification === undefined) {
618
672
  return { outcome: "accepted", receipt: { notification: null } };
619
673
  }
620
- const claimId = this.rememberDeliveryClaim({
674
+ const claim = this.rememberDeliveryClaim({
621
675
  clientId: command.clientId,
622
676
  token,
623
677
  targetSessionId,
@@ -626,7 +680,7 @@ export class WorkflowHost {
626
680
  });
627
681
  return {
628
682
  outcome: "accepted",
629
- receipt: { claimId, notification },
683
+ receipt: { ...claim, notification },
630
684
  };
631
685
  }
632
686
  deliverNotification(command) {
@@ -650,6 +704,9 @@ export class WorkflowHost {
650
704
  }
651
705
  claimTurn(command) {
652
706
  const payload = requireRecord(command.payload, "turn claim payload");
707
+ if (payload.validateClaim === true) {
708
+ return this.validateDelivery(command, payload, "turn");
709
+ }
653
710
  const targetSessionId = requireString(payload.targetSessionId, "targetSessionId");
654
711
  const token = randomUUID();
655
712
  const intent = this.queue.claimEligibleWorkflowTurnIntents({
@@ -661,7 +718,7 @@ export class WorkflowHost {
661
718
  if (intent === undefined) {
662
719
  return { outcome: "accepted", receipt: { turn: null } };
663
720
  }
664
- const claimId = this.rememberDeliveryClaim({
721
+ const claim = this.rememberDeliveryClaim({
665
722
  clientId: command.clientId,
666
723
  token,
667
724
  targetSessionId,
@@ -670,7 +727,7 @@ export class WorkflowHost {
670
727
  });
671
728
  return {
672
729
  outcome: "accepted",
673
- receipt: { claimId, turn: intent },
730
+ receipt: { ...claim, turn: intent },
674
731
  };
675
732
  }
676
733
  resolveTurn(command) {
@@ -740,6 +797,9 @@ export class WorkflowHost {
740
797
  if (interaction === undefined || interaction.kind !== "decision") {
741
798
  return { outcome: "notFound", error: `Decision request not found: ${requestId}` };
742
799
  }
800
+ if (this.queue.isWorkflowRunPaused(interaction.runId)) {
801
+ return { outcome: "conflict", error: "Workflow run is paused" };
802
+ }
743
803
  const request = interaction.contract;
744
804
  const response = payload.response;
745
805
  const accepted = this.decisions.acceptSync(request, {
@@ -878,6 +938,9 @@ export class WorkflowHost {
878
938
  if (interaction === undefined || interaction.runId !== runId) {
879
939
  return { outcome: "notFound", error: `Interactive request not found: ${requestId}` };
880
940
  }
941
+ if (this.queue.isWorkflowRunPaused(runId)) {
942
+ return { outcome: "conflict", error: "Workflow run is paused" };
943
+ }
881
944
  const attemptId = requireString(payload.attempt, "attempt");
882
945
  const nodeId = requireString(payload.step, "step");
883
946
  const storedContract = requireRecord(interaction.contract, "interactive contract");
@@ -931,6 +994,9 @@ export class WorkflowHost {
931
994
  if (current === undefined || current.runId !== requireRunId(request)) {
932
995
  return { outcome: "notFound", error: `Interactive request not found: ${requestId}` };
933
996
  }
997
+ if (this.queue.isWorkflowRunPaused(current.runId)) {
998
+ return { outcome: "conflict", error: "Workflow run is paused" };
999
+ }
934
1000
  const attemptId = requireString(payload.attempt, "attempt");
935
1001
  const nodeId = requireString(payload.step, "step");
936
1002
  const storedContract = requireRecord(current.contract, "interactive contract");