@adcp/sdk 14.0.0-beta.5 → 14.0.0-beta.6

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 (80) hide show
  1. package/dist/lib/core/AgentClient.d.ts.map +1 -1
  2. package/dist/lib/core/AgentClient.js +26 -10
  3. package/dist/lib/core/AgentClient.js.map +1 -1
  4. package/dist/lib/core/AgentClient.mjs +26 -10
  5. package/dist/lib/core/AgentClient.mjs.map +1 -1
  6. package/dist/lib/core/AsyncHandler.d.mts +4 -2
  7. package/dist/lib/core/AsyncHandler.d.ts +4 -2
  8. package/dist/lib/core/AsyncHandler.d.ts.map +1 -1
  9. package/dist/lib/core/AsyncHandler.js +15 -1
  10. package/dist/lib/core/AsyncHandler.js.map +1 -1
  11. package/dist/lib/core/AsyncHandler.mjs +15 -1
  12. package/dist/lib/core/AsyncHandler.mjs.map +1 -1
  13. package/dist/lib/core/SingleAgentClient.d.mts +3 -1
  14. package/dist/lib/core/SingleAgentClient.d.ts +3 -1
  15. package/dist/lib/core/SingleAgentClient.d.ts.map +1 -1
  16. package/dist/lib/core/SingleAgentClient.js +158 -39
  17. package/dist/lib/core/SingleAgentClient.js.map +1 -1
  18. package/dist/lib/core/SingleAgentClient.mjs +167 -40
  19. package/dist/lib/core/SingleAgentClient.mjs.map +1 -1
  20. package/dist/lib/core/TaskExecutor.d.mts +57 -2
  21. package/dist/lib/core/TaskExecutor.d.ts +57 -2
  22. package/dist/lib/core/TaskExecutor.d.ts.map +1 -1
  23. package/dist/lib/core/TaskExecutor.js +382 -44
  24. package/dist/lib/core/TaskExecutor.js.map +1 -1
  25. package/dist/lib/core/TaskExecutor.mjs +382 -45
  26. package/dist/lib/core/TaskExecutor.mjs.map +1 -1
  27. package/dist/lib/core/request-proposals-guard.d.mts +11 -0
  28. package/dist/lib/core/request-proposals-guard.d.ts +12 -0
  29. package/dist/lib/core/request-proposals-guard.d.ts.map +1 -0
  30. package/dist/lib/core/request-proposals-guard.js +65 -0
  31. package/dist/lib/core/request-proposals-guard.js.map +1 -0
  32. package/dist/lib/core/request-proposals-guard.mjs +39 -0
  33. package/dist/lib/core/request-proposals-guard.mjs.map +1 -0
  34. package/dist/lib/core/webhook-registration.d.mts +5 -0
  35. package/dist/lib/core/webhook-registration.d.ts +5 -0
  36. package/dist/lib/core/webhook-registration.d.ts.map +1 -1
  37. package/dist/lib/core/webhook-registration.js +13 -0
  38. package/dist/lib/core/webhook-registration.js.map +1 -1
  39. package/dist/lib/core/webhook-registration.mjs +13 -0
  40. package/dist/lib/core/webhook-registration.mjs.map +1 -1
  41. package/dist/lib/media-buy/compatibility.d.mts +45 -10
  42. package/dist/lib/media-buy/compatibility.d.ts +45 -10
  43. package/dist/lib/media-buy/compatibility.d.ts.map +1 -1
  44. package/dist/lib/media-buy/compatibility.js +295 -113
  45. package/dist/lib/media-buy/compatibility.js.map +1 -1
  46. package/dist/lib/media-buy/compatibility.mjs +295 -113
  47. package/dist/lib/media-buy/compatibility.mjs.map +1 -1
  48. package/dist/lib/schemas-data/v2.5/_provenance.json +1 -1
  49. package/dist/lib/testing/storyboard/validations.d.mts +1 -1
  50. package/dist/lib/testing/storyboard/validations.d.ts +1 -1
  51. package/dist/lib/types/core.generated.d.mts +334 -85
  52. package/dist/lib/types/core.generated.d.ts +334 -85
  53. package/dist/lib/types/core.generated.d.ts.map +1 -1
  54. package/dist/lib/types/core.generated.js.map +1 -1
  55. package/dist/lib/types/decline-proposals.d.ts +0 -5
  56. package/dist/lib/types/index.d.mts +2 -0
  57. package/dist/lib/types/index.d.ts +2 -0
  58. package/dist/lib/types/index.d.ts.map +1 -1
  59. package/dist/lib/types/index.js.map +1 -1
  60. package/dist/lib/types/index.mjs.map +1 -1
  61. package/dist/lib/types/request-proposals.d.ts +254 -36
  62. package/dist/lib/types/schemas.generated.d.ts +3123 -835
  63. package/dist/lib/types/schemas.generated.d.ts.map +1 -1
  64. package/dist/lib/types/schemas.generated.js +192 -85
  65. package/dist/lib/types/schemas.generated.js.map +1 -1
  66. package/dist/lib/types/schemas.generated.mjs +192 -85
  67. package/dist/lib/types/schemas.generated.mjs.map +1 -1
  68. package/dist/lib/types/tools.generated.d.mts +1 -121
  69. package/dist/lib/types/tools.generated.d.ts +1 -121
  70. package/dist/lib/types/tools.generated.d.ts.map +1 -1
  71. package/dist/lib/types/tools.generated.js.map +1 -1
  72. package/dist/lib/version.d.mts +3 -3
  73. package/dist/lib/version.d.ts +3 -3
  74. package/dist/lib/version.js +3 -3
  75. package/dist/lib/version.js.map +1 -1
  76. package/dist/lib/version.mjs +3 -3
  77. package/dist/lib/version.mjs.map +1 -1
  78. package/docs/guides/MEDIA-BUY-3.2-COMPATIBILITY.md +28 -14
  79. package/docs/llms.txt +1 -1
  80. package/package.json +1 -1
@@ -18,6 +18,7 @@ var __copyProps = (to, from, except, desc) => {
18
18
  var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
19
19
  var TaskExecutor_exports = {};
20
20
  __export(TaskExecutor_exports, {
21
+ AfterProtocolDispatchHookError: () => AfterProtocolDispatchHookError,
21
22
  BeforeProtocolDispatchHookError: () => BeforeProtocolDispatchHookError,
22
23
  DeferredTaskError: () => DeferredTaskError,
23
24
  InputRequiredError: () => InputRequiredError,
@@ -80,6 +81,14 @@ class BeforeProtocolDispatchHookError extends Error {
80
81
  }
81
82
  original;
82
83
  }
84
+ class AfterProtocolDispatchHookError extends Error {
85
+ constructor(original) {
86
+ super("An SDK post-dispatch settlement hook failed.", { cause: original });
87
+ this.original = original;
88
+ this.name = "AfterProtocolDispatchHookError";
89
+ }
90
+ original;
91
+ }
83
92
  const GOVERNED_CREDENTIAL_SCAN_MAX_NODES = 1e4;
84
93
  const GOVERNED_CREDENTIAL_SCAN_MAX_DEPTH = 64;
85
94
  const GOVERNED_CALLBACK_FIELDS = /* @__PURE__ */ new Set(["push_notification_config", "reporting_webhook", "artifact_webhook"]);
@@ -245,6 +254,8 @@ function resolveIdempotencyKey(taskName, params, serverVersion) {
245
254
  return void 0;
246
255
  }
247
256
  const COMPACTED_TASK_STATE_LIMIT = 1e4;
257
+ const DEFAULT_EXTERNAL_TASK_SETTLEMENT_RETENTION_MS = 7 * 24 * 60 * 60 * 1e3;
258
+ const MAX_TIMER_DELAY_MS = 2147483647;
248
259
  const TERMINAL_TASK_STATUSES = /* @__PURE__ */ new Set([
249
260
  "completed",
250
261
  "failed",
@@ -253,9 +264,14 @@ const TERMINAL_TASK_STATUSES = /* @__PURE__ */ new Set([
253
264
  "governance-denied",
254
265
  "aborted"
255
266
  ]);
267
+ const MAX_PENDING_EXTERNAL_TASK_OBSERVATIONS = 8;
268
+ const MAX_RETAINED_EXTERNAL_TASK_ERROR_CHARS = 1024;
256
269
  class TaskExecutor {
257
270
  constructor(config = {}) {
258
271
  this.config = config;
272
+ if (config.externalTaskSettlementRetentionMs !== void 0 && (!Number.isSafeInteger(config.externalTaskSettlementRetentionMs) || config.externalTaskSettlementRetentionMs < 1)) {
273
+ throw new import_errors.ConfigurationError("externalTaskSettlementRetentionMs must be a positive safe integer.");
274
+ }
259
275
  this.responseParser = new import_ProtocolResponseParser.ProtocolResponseParser();
260
276
  if (config.enableConversationStorage) {
261
277
  this.conversationStorage = /* @__PURE__ */ new Map();
@@ -280,6 +296,14 @@ class TaskExecutor {
280
296
  }
281
297
  }
282
298
  config;
299
+ deferredTerminalPublicationTaskIds = /* @__PURE__ */ new Set();
300
+ settlementCapacityReservations = /* @__PURE__ */ new Set();
301
+ closedExternalTaskSettlementTaskIds = /* @__PURE__ */ new Set();
302
+ externalTaskSettlementHandlers = /* @__PURE__ */ new Map();
303
+ externalTaskSettlementInFlight = /* @__PURE__ */ new Map();
304
+ settledExternalTaskObservationKeys = /* @__PURE__ */ new Map();
305
+ externalTaskSettlementExpiry = /* @__PURE__ */ new Map();
306
+ pendingExternalTaskObservations = /* @__PURE__ */ new Map();
283
307
  responseParser;
284
308
  activeTasks = /* @__PURE__ */ new Map();
285
309
  compactedTaskIds = /* @__PURE__ */ new Map();
@@ -305,9 +329,43 @@ class TaskExecutor {
305
329
  *
306
330
  * @internal
307
331
  */
308
- observeExternalTaskStatus(taskId, status, result) {
309
- if (!["completed", "failed", "rejected", "canceled"].includes(status)) return;
332
+ async observeExternalTaskStatus(taskId, status, result, identity = {}) {
333
+ if (!["completed", "failed", "rejected", "canceled", "governance-denied", "aborted"].includes(status)) {
334
+ return { settled: false, result };
335
+ }
336
+ const observation = { status, result, ...identity };
337
+ const observationKey = this.externalTaskObservationKey(observation);
338
+ if (this.closedExternalTaskSettlementTaskIds.has(taskId)) {
339
+ const accepted = this.settledExternalTaskObservationKeys.get(taskId);
340
+ if (accepted?.key === observationKey) {
341
+ return {
342
+ settled: true,
343
+ duplicate: true,
344
+ status: accepted.status,
345
+ ...accepted.error !== void 0 && { error: accepted.error }
346
+ };
347
+ }
348
+ throw new Error("This task already completed durable settlement; the pushed result was not accepted.");
349
+ }
350
+ if (this.deferredTerminalPublicationTaskIds.has(taskId)) {
351
+ const handler = this.externalTaskSettlementHandlers.get(taskId);
352
+ if (handler) return this.settleExternalTaskStatus(taskId, handler, observation);
353
+ return new Promise((resolve, reject) => {
354
+ const pending = this.pendingExternalTaskObservations.get(taskId) ?? [];
355
+ if (pending.length >= MAX_PENDING_EXTERNAL_TASK_OBSERVATIONS) {
356
+ reject(new Error("Too many terminal push notifications are awaiting durable task settlement."));
357
+ return;
358
+ }
359
+ pending.push({ observation, resolve, reject });
360
+ this.pendingExternalTaskObservations.set(taskId, pending);
361
+ });
362
+ }
310
363
  this.updateTaskStatus(taskId, status, result);
364
+ return { settled: false, result };
365
+ }
366
+ /** Whether this process can safely settle or reject a durable callback for the operation. */
367
+ hasExternalTaskSettlementRoute(taskId) {
368
+ return this.deferredTerminalPublicationTaskIds.has(taskId) || this.closedExternalTaskSettlementTaskIds.has(taskId) || this.externalTaskSettlementHandlers.has(taskId);
311
369
  }
312
370
  /**
313
371
  * Run the configured pre-send schema check against the user-facing request
@@ -404,6 +462,7 @@ class TaskExecutor {
404
462
  }
405
463
  async executeTaskWithinDeadline(agent, taskName, params, inputHandler, options = {}, serverVersion, targetCapabilities, beforeProtocolDispatch) {
406
464
  if (serverVersion) this.lastKnownServerVersion = serverVersion;
465
+ params = structuredClone(params);
407
466
  const taskId = (0, import_task_deadline.getTaskOperationId)(options) ?? (0, import_crypto.randomUUID)();
408
467
  const startTime = Date.now();
409
468
  const workingTimeout = this.config.workingTimeout || 12e4;
@@ -426,9 +485,14 @@ class TaskExecutor {
426
485
  idempotencyKey
427
486
  };
428
487
  this.activeTasks.set(taskId, taskState);
488
+ let dispatchBoundaryOwnsTerminalState = false;
429
489
  const taskAbortListener = options.signal ? () => {
430
490
  const currentStatus = this.activeTasks.get(taskId)?.status;
431
491
  if (currentStatus && !TERMINAL_TASK_STATUSES.has(currentStatus)) {
492
+ if (dispatchBoundaryOwnsTerminalState) {
493
+ this.compactAbandonedObserverTaskState(taskId);
494
+ return;
495
+ }
432
496
  const reason = options.signal?.reason;
433
497
  this.updateTaskStatus(
434
498
  taskId,
@@ -455,6 +519,9 @@ class TaskExecutor {
455
519
  let governanceResult;
456
520
  let effectiveParams = params;
457
521
  let governanceAdjusted = false;
522
+ let dispatchCommitted = false;
523
+ let dispatchSettlement;
524
+ let dispatchSettlementStarted = false;
458
525
  try {
459
526
  await this.config.onActivity?.({
460
527
  type: "protocol_request",
@@ -513,14 +580,15 @@ class TaskExecutor {
513
580
  effectiveParams = adjustedParams;
514
581
  }
515
582
  }
583
+ const dispatchParams = structuredClone(effectiveParams);
516
584
  const initialMessage = {
517
585
  id: (0, import_crypto.randomUUID)(),
518
586
  role: "user",
519
- content: { tool: taskName, params: effectiveParams },
587
+ content: { tool: taskName, params: dispatchParams },
520
588
  timestamp: (/* @__PURE__ */ new Date()).toISOString(),
521
589
  metadata: { toolName: taskName, type: "request" }
522
590
  };
523
- const preparedCall = (0, import_protocols.prepareProtocolToolCall)(agent, effectiveParams, {
591
+ const preparedCall = (0, import_protocols.prepareProtocolToolCall)(agent, dispatchParams, {
524
592
  toolName: taskName,
525
593
  webhookUrl,
526
594
  webhookSecret: this.config.webhookSecret,
@@ -529,6 +597,7 @@ class TaskExecutor {
529
597
  wireAdcpVersion: this.config.wireAdcpVersion,
530
598
  versionEnvelope: this.config.versionEnvelope
531
599
  });
600
+ let webhookRegistrationPersisted = false;
532
601
  if (webhookUrl && this.config.onWebhookRegistration) {
533
602
  const registration = webhookRegistrationFromPreparedCall(agent, preparedCall);
534
603
  if (registration) {
@@ -538,16 +607,35 @@ class TaskExecutor {
538
607
  operationId: taskId,
539
608
  ...registration
540
609
  });
610
+ webhookRegistrationPersisted = true;
541
611
  }
542
612
  }
543
613
  (0, import_abort.throwIfAborted)(options.signal);
544
- let dispatchCommitted = false;
545
614
  if (beforeProtocolDispatch) {
546
615
  let decision;
616
+ dispatchBoundaryOwnsTerminalState = true;
547
617
  try {
548
- decision = await beforeProtocolDispatch(effectiveParams, { governanceAdjusted });
618
+ if (this.deferredTerminalPublicationTaskIds.size + this.settlementCapacityReservations.size >= COMPACTED_TASK_STATE_LIMIT) {
619
+ throw new Error("The durable task-settlement capacity is exhausted; no mutation claim was attempted.");
620
+ }
621
+ this.settlementCapacityReservations.add(taskId);
622
+ if (webhookRegistrationPersisted) {
623
+ await this.config.onDurableSettlementRequired?.(taskId);
624
+ (0, import_abort.throwIfAborted)(options.signal);
625
+ }
626
+ decision = await beforeProtocolDispatch(dispatchParams, {
627
+ governanceAdjusted,
628
+ publishSettledTaskStatus: (status, data, error) => this.publishSettledTaskStatus(taskId, status, data, error),
629
+ registerExternalTaskSettlement: (handler) => this.registerExternalTaskSettlement(taskId, handler)
630
+ });
631
+ if (decision.action === "dispatch_committed") {
632
+ this.deferredTerminalPublicationTaskIds.add(taskId);
633
+ this.retainExternalTaskSettlementState(taskId);
634
+ }
549
635
  } catch (error) {
550
636
  throw new BeforeProtocolDispatchHookError(error);
637
+ } finally {
638
+ this.settlementCapacityReservations.delete(taskId);
551
639
  }
552
640
  if (decision.action === "return") {
553
641
  const earlyResult = decision.result;
@@ -557,7 +645,10 @@ class TaskExecutor {
557
645
  return (0, import_match.attachMatch)(earlyResult);
558
646
  }
559
647
  dispatchCommitted = true;
648
+ dispatchSettlement = decision;
560
649
  }
650
+ (0, import_abort.throwIfAborted)(options.signal);
651
+ const postDispatchOptions = dispatchCommitted ? { ...options, signal: void 0 } : options;
561
652
  const callOptions = {
562
653
  debugLogs,
563
654
  webhookUrl,
@@ -568,12 +659,9 @@ class TaskExecutor {
568
659
  ...this.config.wireAdcpVersion !== void 0 && { wireAdcpVersion: this.config.wireAdcpVersion },
569
660
  ...this.config.versionEnvelope !== void 0 && { versionEnvelope: this.config.versionEnvelope },
570
661
  transport: options.transport ?? this.config.transport,
571
- // Once a durable continuation claim commits, the seller call itself
572
- // must cross the network boundary even if the caller's deadline fired
573
- // while the atomic store operation was pending. The outer deadline
574
- // still rejects promptly and post-call handling marks the outcome
575
- // ambiguous for reconciliation.
576
- signal: dispatchCommitted ? void 0 : options.signal,
662
+ // Once dispatch begins for a live committed claim, post-call handling
663
+ // owns durable settlement even if the caller's deadline fires later.
664
+ signal: postDispatchOptions.signal,
577
665
  onTransportActivity: this.config.onTransportActivity,
578
666
  transportActivityContext: {
579
667
  operationId: taskId,
@@ -583,10 +671,10 @@ class TaskExecutor {
583
671
  }
584
672
  };
585
673
  const response = await (0, import_prepared_call_context.withPreparedProtocolToolCall)(
586
- { agent, toolName: taskName, args: effectiveParams, preparedCall },
587
- () => import_protocols.ProtocolClient.callTool(agent, taskName, effectiveParams, callOptions)
674
+ { agent, toolName: taskName, args: dispatchParams, preparedCall },
675
+ () => import_protocols.ProtocolClient.callTool(agent, taskName, dispatchParams, callOptions)
588
676
  );
589
- (0, import_abort.throwIfAborted)(options.signal);
677
+ (0, import_abort.throwIfAborted)(postDispatchOptions.signal);
590
678
  const respStatus = this.responseParser.getStatus(response);
591
679
  await this.config.onActivity?.({
592
680
  type: "protocol_response",
@@ -599,7 +687,7 @@ class TaskExecutor {
599
687
  payload: response,
600
688
  timestamp: (/* @__PURE__ */ new Date()).toISOString()
601
689
  });
602
- (0, import_abort.throwIfAborted)(options.signal);
690
+ (0, import_abort.throwIfAborted)(postDispatchOptions.signal);
603
691
  const responseMessage = {
604
692
  id: (0, import_crypto.randomUUID)(),
605
693
  role: "agent",
@@ -608,22 +696,33 @@ class TaskExecutor {
608
696
  metadata: { toolName: taskName, type: "response" }
609
697
  };
610
698
  const messages = [initialMessage, responseMessage];
611
- const result = await this.handleAsyncResponse(
699
+ let result = await this.handleAsyncResponse(
612
700
  agent,
613
701
  taskId,
614
702
  taskName,
615
- effectiveParams,
703
+ dispatchParams,
616
704
  response,
617
705
  messages,
618
706
  inputHandler,
619
- options,
707
+ postDispatchOptions,
620
708
  debugLogs,
621
- startTime
709
+ startTime,
710
+ dispatchCommitted
622
711
  );
623
- (0, import_abort.throwIfAborted)(options.signal);
712
+ (0, import_abort.throwIfAborted)(postDispatchOptions.signal);
624
713
  if (governanceResult) {
625
714
  result.governance = governanceResult;
626
715
  }
716
+ if (dispatchSettlement?.onResult) {
717
+ dispatchSettlementStarted = true;
718
+ try {
719
+ result = await dispatchSettlement.onResult((0, import_match.attachMatch)(result));
720
+ } catch (error) {
721
+ this.closeExternalTaskSettlement(taskId, error);
722
+ this.updateTaskStatus(taskId, "failed", void 0, error instanceof Error ? error.message : String(error));
723
+ throw new AfterProtocolDispatchHookError(error);
724
+ }
725
+ }
627
726
  if (governanceCheckId && this.governanceMiddleware) {
628
727
  const govCtx = governanceResult?.governanceContext;
629
728
  if (result.status === "completed" && govCtx) {
@@ -643,10 +742,10 @@ class TaskExecutor {
643
742
  void 0,
644
743
  debugLogs,
645
744
  govCtx,
646
- options.signal,
745
+ postDispatchOptions.signal,
647
746
  outcomeIdempotencyKey
648
747
  );
649
- (0, import_abort.throwIfAborted)(options.signal);
748
+ (0, import_abort.throwIfAborted)(postDispatchOptions.signal);
650
749
  if (!result.governanceOutcome) {
651
750
  result.governanceOutcomeError = "Outcome reporting to governance agent failed";
652
751
  }
@@ -664,10 +763,10 @@ class TaskExecutor {
664
763
  { message: result.error },
665
764
  debugLogs,
666
765
  govCtx,
667
- options.signal,
766
+ postDispatchOptions.signal,
668
767
  outcomeIdempotencyKey
669
768
  );
670
- (0, import_abort.throwIfAborted)(options.signal);
769
+ (0, import_abort.throwIfAborted)(postDispatchOptions.signal);
671
770
  if (!result.governanceOutcome) {
672
771
  result.governanceOutcomeError = "Outcome reporting to governance agent failed";
673
772
  }
@@ -675,6 +774,12 @@ class TaskExecutor {
675
774
  result.governance = { ...result.governance ?? {}, checkId: governanceCheckId };
676
775
  }
677
776
  }
777
+ if (dispatchCommitted) {
778
+ result = this.attachSettledContinuationTaskStatus(taskId, result);
779
+ if (TERMINAL_TASK_STATUSES.has(result.status)) {
780
+ this.publishSettledTaskStatus(taskId, result.status, result.data, result.error);
781
+ }
782
+ }
678
783
  if (["completed", "failed", "rejected", "canceled", "governance-denied", "aborted"].includes(result.status) && this.activeTasks.get(taskId)?.status !== result.status) {
679
784
  this.updateTaskStatus(taskId, result.status, result.data, result.error);
680
785
  }
@@ -684,6 +789,22 @@ class TaskExecutor {
684
789
  this.updateTaskStatus(taskId, "failed", void 0, error.message);
685
790
  throw error;
686
791
  }
792
+ if (error instanceof AfterProtocolDispatchHookError) throw error;
793
+ if (dispatchCommitted && dispatchSettlement?.onError && !dispatchSettlementStarted) {
794
+ try {
795
+ await dispatchSettlement.onError(error);
796
+ } catch (settlementError) {
797
+ this.closeExternalTaskSettlement(taskId, settlementError);
798
+ this.updateTaskStatus(
799
+ taskId,
800
+ "failed",
801
+ void 0,
802
+ settlementError instanceof Error ? settlementError.message : String(settlementError)
803
+ );
804
+ throw new AfterProtocolDispatchHookError(settlementError);
805
+ }
806
+ this.closeExternalTaskSettlement(taskId, error);
807
+ }
687
808
  if ((0, import_abort.isAbortOrTimeoutError)(error)) {
688
809
  if (idempotencyKey && error && typeof error === "object") {
689
810
  error.idempotency_key = idempotencyKey;
@@ -734,12 +855,12 @@ class TaskExecutor {
734
855
  debug_logs: debugLogs
735
856
  };
736
857
  }
737
- async handleAsyncResponse(agent, taskId, taskName, params, response, messages, inputHandler, options = {}, debugLogs = [], startTime = Date.now()) {
858
+ async handleAsyncResponse(agent, taskId, taskName, params, response, messages, inputHandler, options = {}, debugLogs = [], startTime = Date.now(), deferTerminalTaskStatus = false) {
738
859
  const status = this.responseParser.getStatus(response);
739
860
  switch (status) {
740
861
  case import_ProtocolResponseParser.ADCP_STATUS.COMPLETED:
741
862
  const completedData = this.extractResponseData(response, debugLogs, taskName);
742
- this.updateTaskStatus(taskId, "completed", completedData);
863
+ if (!deferTerminalTaskStatus) this.updateTaskStatus(taskId, "completed", completedData);
743
864
  const operationSuccess = this.isOperationSuccess(completedData, taskName);
744
865
  const validationResult = this.validateResponseSchema(completedData, taskName, debugLogs);
745
866
  const finalSuccess = operationSuccess && validationResult.valid;
@@ -795,7 +916,17 @@ class TaskExecutor {
795
916
  startTime
796
917
  );
797
918
  case import_ProtocolResponseParser.ADCP_STATUS.SUBMITTED:
798
- return this.setupSubmittedTask(agent, taskId, taskName, response, messages, options, debugLogs, startTime);
919
+ return this.setupSubmittedTask(
920
+ agent,
921
+ taskId,
922
+ taskName,
923
+ response,
924
+ messages,
925
+ options,
926
+ debugLogs,
927
+ startTime,
928
+ deferTerminalTaskStatus
929
+ );
799
930
  case import_ProtocolResponseParser.ADCP_STATUS.INPUT_REQUIRED:
800
931
  return this.handleInputRequired(
801
932
  agent,
@@ -807,7 +938,8 @@ class TaskExecutor {
807
938
  inputHandler,
808
939
  options,
809
940
  debugLogs,
810
- startTime
941
+ startTime,
942
+ deferTerminalTaskStatus
811
943
  );
812
944
  case import_ProtocolResponseParser.ADCP_STATUS.FAILED:
813
945
  case import_ProtocolResponseParser.ADCP_STATUS.REJECTED:
@@ -1009,7 +1141,7 @@ class TaskExecutor {
1009
1141
  /**
1010
1142
  * Set up submitted task with webhook
1011
1143
  */
1012
- async setupSubmittedTask(agent, taskId, taskName, response, messages, options = {}, debugLogs = [], startTime = Date.now()) {
1144
+ async setupSubmittedTask(agent, taskId, taskName, response, messages, options = {}, debugLogs = [], startTime = Date.now(), deferTerminalTaskStatus = false) {
1013
1145
  const partialData = this.extractResponseData(response, debugLogs, taskName);
1014
1146
  let webhookUrl = response.webhookUrl;
1015
1147
  if (!webhookUrl && this.config.webhookManager) {
@@ -1033,14 +1165,16 @@ class TaskExecutor {
1033
1165
  webhookUrl,
1034
1166
  track: async (transport) => {
1035
1167
  const task = await this.getTaskStatus(agent, serverTaskId, transport ?? pollingTransport);
1036
- if (["completed", "failed", "rejected", "canceled"].includes(task.status)) {
1168
+ if (!deferTerminalTaskStatus && ["completed", "failed", "rejected", "canceled"].includes(task.status)) {
1037
1169
  this.updateTaskStatus(taskId, task.status, task.result, task.error);
1038
1170
  }
1039
1171
  return task;
1040
1172
  },
1041
1173
  waitForCompletion: async (pollInterval = 6e4, signal) => {
1042
1174
  const completed = await this.pollTaskCompletion(agent, serverTaskId, pollInterval, pollingTransport, signal);
1043
- this.updateTaskStatus(taskId, completed.status, completed.data, completed.error);
1175
+ if (!deferTerminalTaskStatus || !TERMINAL_TASK_STATUSES.has(completed.status)) {
1176
+ this.updateTaskStatus(taskId, completed.status, completed.data, completed.error);
1177
+ }
1044
1178
  return completed;
1045
1179
  }
1046
1180
  };
@@ -1064,6 +1198,193 @@ class TaskExecutor {
1064
1198
  debug_logs: debugLogs
1065
1199
  };
1066
1200
  }
1201
+ /**
1202
+ * A committed compatibility hook may replace resumable continuations with
1203
+ * wrappers that validate and durably persist their terminal result. Attach
1204
+ * local task publication only after those final wrappers return.
1205
+ */
1206
+ attachSettledContinuationTaskStatus(taskId, result) {
1207
+ const publish = (status, data, error) => {
1208
+ this.publishSettledTaskStatus(taskId, status, data, error);
1209
+ };
1210
+ if (result.submitted) {
1211
+ const submitted = result.submitted;
1212
+ result.submitted = {
1213
+ ...submitted,
1214
+ track: async (transport) => {
1215
+ const task = await submitted.track(transport);
1216
+ publish(task.status, task.result, task.error);
1217
+ return task;
1218
+ },
1219
+ waitForCompletion: async (pollInterval, signal) => {
1220
+ const completion = this.attachSettledContinuationTaskStatus(
1221
+ taskId,
1222
+ await submitted.waitForCompletion(pollInterval, signal)
1223
+ );
1224
+ publish(completion.status, completion.data, completion.error);
1225
+ return completion;
1226
+ }
1227
+ };
1228
+ }
1229
+ if (result.deferred) {
1230
+ const deferred = result.deferred;
1231
+ result.deferred = {
1232
+ ...deferred,
1233
+ resume: async (input) => {
1234
+ const completion = this.attachSettledContinuationTaskStatus(taskId, await deferred.resume(input));
1235
+ publish(completion.status, completion.data, completion.error);
1236
+ return completion;
1237
+ }
1238
+ };
1239
+ }
1240
+ return result;
1241
+ }
1242
+ publishSettledTaskStatus(taskId, status, data, error, fromExternalObservation = false) {
1243
+ if (!TERMINAL_TASK_STATUSES.has(status)) return;
1244
+ const task = this.activeTasks.get(taskId);
1245
+ if (!task) return;
1246
+ if (!fromExternalObservation) {
1247
+ this.closeExternalTaskSettlement(
1248
+ taskId,
1249
+ new Error("The task settled through its direct response; a racing pushed result was not accepted.")
1250
+ );
1251
+ }
1252
+ if (task.status !== status) this.updateTaskStatus(taskId, status, data, error);
1253
+ }
1254
+ rejectPendingExternalTaskObservations(taskId, error) {
1255
+ const pending = this.pendingExternalTaskObservations.get(taskId);
1256
+ this.pendingExternalTaskObservations.delete(taskId);
1257
+ for (const waiter of pending ?? []) waiter.reject(error);
1258
+ }
1259
+ closeExternalTaskSettlement(taskId, error) {
1260
+ this.externalTaskSettlementHandlers.delete(taskId);
1261
+ this.externalTaskSettlementInFlight.delete(taskId);
1262
+ this.closedExternalTaskSettlementTaskIds.add(taskId);
1263
+ this.rejectPendingExternalTaskObservations(taskId, error);
1264
+ }
1265
+ clearExternalTaskSettlementState(taskId) {
1266
+ this.deferredTerminalPublicationTaskIds.delete(taskId);
1267
+ this.closedExternalTaskSettlementTaskIds.delete(taskId);
1268
+ this.externalTaskSettlementHandlers.delete(taskId);
1269
+ this.externalTaskSettlementInFlight.delete(taskId);
1270
+ this.settledExternalTaskObservationKeys.delete(taskId);
1271
+ this.externalTaskSettlementExpiry.delete(taskId);
1272
+ this.rejectPendingExternalTaskObservations(
1273
+ taskId,
1274
+ new Error("The durable push-settlement retention window expired before the observation was accepted.")
1275
+ );
1276
+ }
1277
+ retainExternalTaskSettlementState(taskId) {
1278
+ const retentionMs = this.config.externalTaskSettlementRetentionMs ?? DEFAULT_EXTERNAL_TASK_SETTLEMENT_RETENTION_MS;
1279
+ const expiry = Date.now() + retentionMs;
1280
+ if ((this.externalTaskSettlementExpiry.get(taskId) ?? 0) >= expiry) return;
1281
+ this.externalTaskSettlementExpiry.set(taskId, expiry);
1282
+ const expire = () => {
1283
+ if (this.externalTaskSettlementExpiry.get(taskId) !== expiry) return;
1284
+ const remaining = expiry - Date.now();
1285
+ if (remaining > 0) {
1286
+ const timer2 = setTimeout(expire, Math.min(remaining, MAX_TIMER_DELAY_MS));
1287
+ timer2.unref?.();
1288
+ return;
1289
+ }
1290
+ this.clearExternalTaskSettlementState(taskId);
1291
+ };
1292
+ const timer = setTimeout(expire, Math.min(retentionMs, MAX_TIMER_DELAY_MS));
1293
+ timer.unref?.();
1294
+ }
1295
+ /** Release short-lived task inspection state without dropping a live durable push fence. */
1296
+ cleanupTerminalTaskInspectionState(taskId) {
1297
+ this.activeTasks.delete(taskId);
1298
+ this.compactedTaskIds.delete(taskId);
1299
+ if (this.deferredTerminalPublicationTaskIds.has(taskId) || this.closedExternalTaskSettlementTaskIds.has(taskId) || this.externalTaskSettlementHandlers.has(taskId)) {
1300
+ if (this.externalTaskSettlementExpiry.has(taskId)) return;
1301
+ }
1302
+ this.clearExternalTaskSettlementState(taskId);
1303
+ }
1304
+ registerExternalTaskSettlement(taskId, handler) {
1305
+ this.externalTaskSettlementHandlers.set(taskId, handler);
1306
+ const pending = this.pendingExternalTaskObservations.get(taskId);
1307
+ if (!pending) return;
1308
+ this.pendingExternalTaskObservations.delete(taskId);
1309
+ for (const waiter of pending) {
1310
+ void this.settleExternalTaskStatus(taskId, handler, waiter.observation).then(waiter.resolve, waiter.reject);
1311
+ }
1312
+ }
1313
+ externalTaskObservationKey(observation) {
1314
+ return (0, import_crypto.createHash)("sha256").update(
1315
+ (0, import_jcs.canonicalize)({
1316
+ status: observation.status,
1317
+ serverTaskId: observation.serverTaskId ?? null,
1318
+ taskType: observation.taskType ?? null,
1319
+ result: observation.result ?? null
1320
+ })
1321
+ ).digest("base64url");
1322
+ }
1323
+ async settleExternalTaskStatus(taskId, handler, observation) {
1324
+ const observationKey = this.externalTaskObservationKey(observation);
1325
+ const existing = this.externalTaskSettlementInFlight.get(taskId);
1326
+ if (existing) {
1327
+ if (existing.waiterCount >= MAX_PENDING_EXTERNAL_TASK_OBSERVATIONS) {
1328
+ throw new Error("Too many terminal push notifications are undergoing durable task settlement.");
1329
+ }
1330
+ existing.waiterCount += 1;
1331
+ try {
1332
+ const accepted = await existing.settlement;
1333
+ if (existing.observationKey !== observationKey) {
1334
+ throw new Error("This task already completed durable settlement; the pushed result was not accepted.");
1335
+ }
1336
+ return { ...accepted, duplicate: true };
1337
+ } finally {
1338
+ existing.waiterCount -= 1;
1339
+ }
1340
+ }
1341
+ const settlement = (async () => {
1342
+ const settled = await handler(observation);
1343
+ const settledStatus = settled.status;
1344
+ if (this.closedExternalTaskSettlementTaskIds.has(taskId)) {
1345
+ throw new Error("The task settled through its direct response; the racing pushed result was not accepted.");
1346
+ }
1347
+ if (this.settledExternalTaskObservationKeys.has(taskId)) {
1348
+ throw new Error("This task already completed durable settlement; the pushed result was not accepted.");
1349
+ }
1350
+ this.publishSettledTaskStatus(taskId, settledStatus, settled.data, settled.error, true);
1351
+ this.settledExternalTaskObservationKeys.set(taskId, {
1352
+ key: observationKey,
1353
+ status: settledStatus,
1354
+ ...settled.error !== void 0 && {
1355
+ error: settled.error.slice(0, MAX_RETAINED_EXTERNAL_TASK_ERROR_CHARS)
1356
+ }
1357
+ });
1358
+ this.closeExternalTaskSettlement(
1359
+ taskId,
1360
+ new Error("The task already completed durable external settlement; a later pushed result was not accepted.")
1361
+ );
1362
+ return {
1363
+ settled: true,
1364
+ result: settled.data,
1365
+ status: settled.status,
1366
+ ...settled.error !== void 0 && { error: settled.error }
1367
+ };
1368
+ })();
1369
+ const inFlight = { observationKey, settlement, waiterCount: 1 };
1370
+ this.externalTaskSettlementInFlight.set(taskId, inFlight);
1371
+ try {
1372
+ return await settlement;
1373
+ } finally {
1374
+ if (this.externalTaskSettlementInFlight.get(taskId) === inFlight) {
1375
+ this.externalTaskSettlementInFlight.delete(taskId);
1376
+ }
1377
+ }
1378
+ }
1379
+ /** Release abandoned caller data without publishing a premature terminal state. */
1380
+ compactAbandonedObserverTaskState(taskId) {
1381
+ const task = this.activeTasks.get(taskId);
1382
+ if (!task) return;
1383
+ task.params = void 0;
1384
+ task.messages = [];
1385
+ task.options = {};
1386
+ delete task.pendingInput;
1387
+ }
1067
1388
  /**
1068
1389
  * A working/submitted response carries everything needed to poll externally.
1069
1390
  * Keeping the original request in activeTasks after dispatch would pin
@@ -1082,10 +1403,14 @@ class TaskExecutor {
1082
1403
  this.compactedTaskIds.delete(taskId);
1083
1404
  this.compactedTaskIds.set(taskId, true);
1084
1405
  while (this.compactedTaskIds.size > COMPACTED_TASK_STATE_LIMIT) {
1085
- const oldest = this.compactedTaskIds.keys().next().value;
1406
+ const oldest = [...this.compactedTaskIds.keys()].find(
1407
+ (candidate) => !this.deferredTerminalPublicationTaskIds.has(candidate) && !this.externalTaskSettlementHandlers.has(candidate) && !this.externalTaskSettlementInFlight.has(candidate)
1408
+ );
1086
1409
  if (oldest === void 0) break;
1087
1410
  this.compactedTaskIds.delete(oldest);
1088
1411
  this.activeTasks.delete(oldest);
1412
+ this.closeExternalTaskSettlement(oldest, new Error("The compacted task was evicted before push settlement."));
1413
+ this.closedExternalTaskSettlementTaskIds.delete(oldest);
1089
1414
  }
1090
1415
  }
1091
1416
  /**
@@ -1096,7 +1421,7 @@ class TaskExecutor {
1096
1421
  * This is common for HITL (human-in-the-loop) workflows where the agent has already processed
1097
1422
  * the request and is just signaling that async approval may be needed.
1098
1423
  */
1099
- async handleInputRequired(agent, taskId, taskName, params, response, messages, inputHandler, options = {}, debugLogs = [], startTime = Date.now()) {
1424
+ async handleInputRequired(agent, taskId, taskName, params, response, messages, inputHandler, options = {}, debugLogs = [], startTime = Date.now(), deferTerminalTaskStatus = false) {
1100
1425
  const inputRequest = this.responseParser.parseInputRequest(response);
1101
1426
  if (!inputHandler) {
1102
1427
  const partialData = this.extractResponseData(response, debugLogs, taskName);
@@ -1174,7 +1499,7 @@ class TaskExecutor {
1174
1499
  const deferred = {
1175
1500
  token,
1176
1501
  question: inputRequest.question,
1177
- resume: (input) => this.resumeDeferredTask(token, input)
1502
+ resume: (input) => this.resumeDeferredTask(token, input, !deferTerminalTaskStatus)
1178
1503
  };
1179
1504
  return {
1180
1505
  success: true,
@@ -1207,7 +1532,8 @@ class TaskExecutor {
1207
1532
  // Pass handler for multi-round clarification
1208
1533
  options,
1209
1534
  debugLogs,
1210
- startTime
1535
+ startTime,
1536
+ deferTerminalTaskStatus
1211
1537
  );
1212
1538
  }
1213
1539
  /**
@@ -1426,7 +1752,7 @@ class TaskExecutor {
1426
1752
  /**
1427
1753
  * Resume a deferred task (client deferral)
1428
1754
  */
1429
- async resumeDeferredTask(token, input) {
1755
+ async resumeDeferredTask(token, input, publishTerminalTaskStatus = true) {
1430
1756
  if (!this.config.deferredStorage) {
1431
1757
  throw new Error("Deferred storage not configured");
1432
1758
  }
@@ -1443,11 +1769,15 @@ class TaskExecutor {
1443
1769
  state.contextId,
1444
1770
  input,
1445
1771
  state.messages,
1446
- void 0
1772
+ void 0,
1447
1773
  // No handler for deferred tasks - input was provided by human
1774
+ {},
1775
+ [],
1776
+ Date.now(),
1777
+ !publishTerminalTaskStatus
1448
1778
  );
1449
1779
  const remainsPaused = ["input-required", "auth-required", "deferred"].includes(resumed.status);
1450
- if (["completed", "failed", "rejected", "canceled", "governance-denied", "aborted"].includes(resumed.status) && this.activeTasks.get(state.taskId)?.status !== resumed.status) {
1780
+ if (publishTerminalTaskStatus && ["completed", "failed", "rejected", "canceled", "governance-denied", "aborted"].includes(resumed.status) && this.activeTasks.get(state.taskId)?.status !== resumed.status) {
1451
1781
  this.updateTaskStatus(state.taskId, resumed.status, resumed.data, resumed.error);
1452
1782
  }
1453
1783
  if (!remainsPaused) {
@@ -1455,7 +1785,14 @@ class TaskExecutor {
1455
1785
  }
1456
1786
  return (0, import_match.attachMatch)(resumed);
1457
1787
  } catch (error) {
1458
- this.updateTaskStatus(state.taskId, "failed", void 0, error instanceof Error ? error.message : String(error));
1788
+ if (publishTerminalTaskStatus) {
1789
+ this.updateTaskStatus(
1790
+ state.taskId,
1791
+ "failed",
1792
+ void 0,
1793
+ error instanceof Error ? error.message : String(error)
1794
+ );
1795
+ }
1459
1796
  try {
1460
1797
  await this.config.deferredStorage.delete(token);
1461
1798
  } catch {
@@ -1466,7 +1803,7 @@ class TaskExecutor {
1466
1803
  /**
1467
1804
  * Continue a task after receiving input
1468
1805
  */
1469
- async continueTaskWithInput(agent, taskId, taskName, params, contextId, input, messages, inputHandler, options = {}, debugLogs = [], startTime = Date.now()) {
1806
+ async continueTaskWithInput(agent, taskId, taskName, params, contextId, input, messages, inputHandler, options = {}, debugLogs = [], startTime = Date.now(), deferTerminalTaskStatus = false) {
1470
1807
  const inputMessage = {
1471
1808
  id: (0, import_crypto.randomUUID)(),
1472
1809
  role: "user",
@@ -1516,7 +1853,8 @@ class TaskExecutor {
1516
1853
  inputHandler,
1517
1854
  options,
1518
1855
  debugLogs,
1519
- startTime
1856
+ startTime,
1857
+ deferTerminalTaskStatus
1520
1858
  );
1521
1859
  }
1522
1860
  /**
@@ -1801,8 +2139,7 @@ class TaskExecutor {
1801
2139
  task.options = {};
1802
2140
  delete task.pendingInput;
1803
2141
  setTimeout(() => {
1804
- this.activeTasks.delete(taskId);
1805
- this.compactedTaskIds.delete(taskId);
2142
+ this.cleanupTerminalTaskInspectionState(taskId);
1806
2143
  }, 3e4).unref();
1807
2144
  }
1808
2145
  }
@@ -1816,6 +2153,7 @@ class TaskExecutor {
1816
2153
  }
1817
2154
  // Annotate the CommonJS export names for ESM import in node:
1818
2155
  0 && (module.exports = {
2156
+ AfterProtocolDispatchHookError,
1819
2157
  BeforeProtocolDispatchHookError,
1820
2158
  DeferredTaskError,
1821
2159
  InputRequiredError,