@mastra/server 1.57.0 → 1.58.0-alpha.1

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 (109) hide show
  1. package/CHANGELOG.md +63 -0
  2. package/dist/{a2a-uPi5sPwb.js → a2a-CHi9AJ0n.js} +598 -178
  3. package/dist/a2a-CHi9AJ0n.js.map +1 -0
  4. package/dist/{a2a-DKnwIgiw.cjs → a2a-D3ZYeXIx.cjs} +598 -178
  5. package/dist/a2a-D3ZYeXIx.cjs.map +1 -0
  6. package/dist/{api-schema-manifest-7-NC9Dyc.cjs → api-schema-manifest-J5hfk5ZZ.cjs} +2 -2
  7. package/dist/{api-schema-manifest-7-NC9Dyc.cjs.map → api-schema-manifest-J5hfk5ZZ.cjs.map} +1 -1
  8. package/dist/{api-schema-manifest-C5hpdIE_.js → api-schema-manifest-R8CsF6RX.js} +2 -2
  9. package/dist/{api-schema-manifest-C5hpdIE_.js.map → api-schema-manifest-R8CsF6RX.js.map} +1 -1
  10. package/dist/{datasets-Cvix7Rms.cjs → datasets-BMeFvFwT.cjs} +43 -1
  11. package/dist/datasets-BMeFvFwT.cjs.map +1 -0
  12. package/dist/{datasets-dhXnHLAL.js → datasets-BeBH9Seg.js} +38 -2
  13. package/dist/datasets-BeBH9Seg.js.map +1 -0
  14. package/dist/{dist-COHIL6fR.cjs → dist-DgY0llRv.cjs} +116 -28
  15. package/dist/dist-DgY0llRv.cjs.map +1 -0
  16. package/dist/{dist-DGqlUp0b.js → dist-IUXZtNBY.js} +116 -28
  17. package/dist/dist-IUXZtNBY.js.map +1 -0
  18. package/dist/docs/SKILL.md +1 -1
  19. package/dist/docs/assets/SOURCE_MAP.json +1 -1
  20. package/dist/docs/references/docs-agents-a2a.md +36 -2
  21. package/dist/route-builder-CIk5k35M.js.map +1 -1
  22. package/dist/route-builder-CgLVYL1c.cjs.map +1 -1
  23. package/dist/{routes-hSY4wTeS.cjs → routes-BzR8iVj4.cjs} +2 -2
  24. package/dist/{routes-hSY4wTeS.cjs.map → routes-BzR8iVj4.cjs.map} +1 -1
  25. package/dist/{routes-CRoTKPvQ.js → routes-C7iRCN_l.js} +2 -2
  26. package/dist/{routes-CRoTKPvQ.js.map → routes-C7iRCN_l.js.map} +1 -1
  27. package/dist/server/a2a/store.cjs +26 -2
  28. package/dist/server/a2a/store.cjs.map +1 -1
  29. package/dist/server/a2a/store.d.ts +18 -2
  30. package/dist/server/a2a/store.d.ts.map +1 -1
  31. package/dist/server/a2a/store.js +26 -3
  32. package/dist/server/a2a/store.js.map +1 -1
  33. package/dist/server/a2a/task-state.d.ts +4 -0
  34. package/dist/server/a2a/task-state.d.ts.map +1 -0
  35. package/dist/server/a2a/tasks.d.ts +1 -1
  36. package/dist/server/a2a/tasks.d.ts.map +1 -1
  37. package/dist/server/handlers/a2a.cjs +1 -1
  38. package/dist/server/handlers/a2a.d.ts +3 -2
  39. package/dist/server/handlers/a2a.d.ts.map +1 -1
  40. package/dist/server/handlers/a2a.js +1 -1
  41. package/dist/server/handlers/agent-builder.cjs +1 -1
  42. package/dist/server/handlers/agent-builder.js +1 -1
  43. package/dist/server/handlers/agent-versions.cjs +6 -0
  44. package/dist/server/handlers/agent-versions.cjs.map +1 -1
  45. package/dist/server/handlers/agent-versions.d.ts.map +1 -1
  46. package/dist/server/handlers/agent-versions.js +6 -0
  47. package/dist/server/handlers/agent-versions.js.map +1 -1
  48. package/dist/server/handlers/datasets.cjs +26 -11
  49. package/dist/server/handlers/datasets.cjs.map +1 -1
  50. package/dist/server/handlers/datasets.d.ts +136 -0
  51. package/dist/server/handlers/datasets.d.ts.map +1 -1
  52. package/dist/server/handlers/datasets.js +26 -11
  53. package/dist/server/handlers/datasets.js.map +1 -1
  54. package/dist/server/handlers/mcp-client-versions.cjs +1 -1
  55. package/dist/server/handlers/mcp-client-versions.js +1 -1
  56. package/dist/server/handlers/observability.cjs +20 -9
  57. package/dist/server/handlers/observability.cjs.map +1 -1
  58. package/dist/server/handlers/observability.d.ts.map +1 -1
  59. package/dist/server/handlers/observability.js +20 -9
  60. package/dist/server/handlers/observability.js.map +1 -1
  61. package/dist/server/handlers/prompt-block-versions.cjs +1 -1
  62. package/dist/server/handlers/prompt-block-versions.js +1 -1
  63. package/dist/server/handlers/scorer-versions.cjs +1 -1
  64. package/dist/server/handlers/scorer-versions.js +1 -1
  65. package/dist/server/handlers/stored-agents.cjs +16 -1
  66. package/dist/server/handlers/stored-agents.cjs.map +1 -1
  67. package/dist/server/handlers/stored-agents.d.ts.map +1 -1
  68. package/dist/server/handlers/stored-agents.js +16 -1
  69. package/dist/server/handlers/stored-agents.js.map +1 -1
  70. package/dist/server/handlers/stored-mcp-clients.cjs +1 -1
  71. package/dist/server/handlers/stored-mcp-clients.js +1 -1
  72. package/dist/server/handlers/stored-prompt-blocks.cjs +1 -1
  73. package/dist/server/handlers/stored-prompt-blocks.js +1 -1
  74. package/dist/server/handlers/stored-scorers.cjs +1 -1
  75. package/dist/server/handlers/stored-scorers.js +1 -1
  76. package/dist/server/handlers/stored-skill-favorites.cjs +1 -1
  77. package/dist/server/handlers/stored-skill-favorites.js +1 -1
  78. package/dist/server/handlers/stored-skills.cjs +1 -1
  79. package/dist/server/handlers/stored-skills.js +1 -1
  80. package/dist/server/handlers/system.cjs +1 -1
  81. package/dist/server/handlers/system.js +1 -1
  82. package/dist/server/handlers/validate-agent-instructions.cjs +52 -0
  83. package/dist/server/handlers/validate-agent-instructions.cjs.map +1 -0
  84. package/dist/server/handlers/validate-agent-instructions.d.ts +13 -0
  85. package/dist/server/handlers/validate-agent-instructions.d.ts.map +1 -0
  86. package/dist/server/handlers/validate-agent-instructions.js +51 -0
  87. package/dist/server/handlers/validate-agent-instructions.js.map +1 -0
  88. package/dist/server/handlers/workflows.cjs +12 -0
  89. package/dist/server/handlers/workflows.cjs.map +1 -1
  90. package/dist/server/handlers/workflows.d.ts.map +1 -1
  91. package/dist/server/handlers/workflows.js +12 -0
  92. package/dist/server/handlers/workflows.js.map +1 -1
  93. package/dist/server/handlers.cjs +1 -1
  94. package/dist/server/handlers.js +1 -1
  95. package/dist/server/schemas/datasets.d.ts +53 -0
  96. package/dist/server/schemas/datasets.d.ts.map +1 -1
  97. package/dist/server/schemas/index.cjs +3 -2
  98. package/dist/server/schemas/index.js +3 -3
  99. package/dist/server/server-adapter/index.cjs +1 -1
  100. package/dist/server/server-adapter/index.js +1 -1
  101. package/dist/server/server-adapter/routes/datasets.d.ts +136 -0
  102. package/dist/server/server-adapter/routes/datasets.d.ts.map +1 -1
  103. package/package.json +5 -5
  104. package/dist/a2a-DKnwIgiw.cjs.map +0 -1
  105. package/dist/a2a-uPi5sPwb.js.map +0 -1
  106. package/dist/datasets-Cvix7Rms.cjs.map +0 -1
  107. package/dist/datasets-dhXnHLAL.js.map +0 -1
  108. package/dist/dist-COHIL6fR.cjs.map +0 -1
  109. package/dist/dist-DGqlUp0b.js.map +0 -1
@@ -2,6 +2,7 @@ const require_rolldown_runtime = require("./rolldown-runtime-CVvi-lCc.cjs");
2
2
  const require_route_builder = require("./route-builder-CgLVYL1c.cjs");
3
3
  const require_utils = require("./utils-wESuBh27.cjs");
4
4
  const require_server_handlers_agents = require("./server/handlers/agents.cjs");
5
+ const require_server_a2a_store = require("./server/a2a/store.cjs");
5
6
  const require_a2a = require("./a2a-oM7aqLz4.cjs");
6
7
  const require_server_handlers_auth = require("./server/handlers/auth.cjs");
7
8
  let zod_v4 = require("zod/v4");
@@ -118,7 +119,7 @@ function createErrorResponse(id, error) {
118
119
  };
119
120
  }
120
121
  function createSuccessResponse(id, result) {
121
- if (!id) throw _mastra_core_a2a.MastraA2AError.internalError("Cannot create success response for null ID.");
122
+ if (id === null) throw _mastra_core_a2a.MastraA2AError.internalError("Cannot create success response for null ID.");
122
123
  return {
123
124
  jsonrpc: "2.0",
124
125
  id,
@@ -306,6 +307,14 @@ var InMemoryPushNotificationStore = class {
306
307
  }
307
308
  };
308
309
  //#endregion
310
+ //#region src/server/a2a/task-state.ts
311
+ function isTerminalTaskState(state) {
312
+ return state === "completed" || state === "failed" || state === "canceled" || state === "rejected";
313
+ }
314
+ function isInterruptedTaskState(state) {
315
+ return state === "input-required" || state === "auth-required";
316
+ }
317
+ //#endregion
309
318
  //#region src/server/a2a/tasks.ts
310
319
  function isTaskStatusUpdate(update) {
311
320
  return "state" in update && !("parts" in update);
@@ -341,54 +350,63 @@ function applyUpdateToTask(current, update) {
341
350
  return newTask;
342
351
  }
343
352
  async function loadOrCreateTask({ agentId, taskId, taskStore, message, contextId, metadata, logger }) {
344
- const data = await taskStore.load({
345
- agentId,
346
- taskId
347
- });
348
- if (!data) {
349
- const initialTask = {
350
- id: taskId,
351
- contextId: contextId || crypto.randomUUID(),
352
- status: {
353
- state: "submitted",
354
- timestamp: (/* @__PURE__ */ new Date()).toISOString(),
355
- message: void 0
356
- },
357
- artifacts: [],
358
- history: [message],
359
- metadata,
360
- kind: "task"
361
- };
362
- logger?.info(`[Task ${taskId}] Created new task.`);
363
- await taskStore.save({
353
+ for (let attempt = 0; attempt < 2; attempt++) {
354
+ const snapshot = taskStore.loadWithVersion({
364
355
  agentId,
365
- data: initialTask
356
+ taskId
366
357
  });
367
- return initialTask;
358
+ const data = snapshot?.task;
359
+ if (!data) {
360
+ const initialTask = {
361
+ id: taskId,
362
+ contextId: contextId || crypto.randomUUID(),
363
+ status: {
364
+ state: "submitted",
365
+ timestamp: (/* @__PURE__ */ new Date()).toISOString(),
366
+ message: void 0
367
+ },
368
+ artifacts: [],
369
+ history: [message],
370
+ metadata,
371
+ kind: "task"
372
+ };
373
+ logger?.info(`[Task ${taskId}] Created new task.`);
374
+ try {
375
+ await taskStore.save({
376
+ agentId,
377
+ data: initialTask,
378
+ expectedVersion: 0
379
+ });
380
+ return initialTask;
381
+ } catch (error) {
382
+ if (error instanceof require_server_a2a_store.TaskStoreVersionConflictError) continue;
383
+ throw error;
384
+ }
385
+ }
386
+ logger?.info(`[Task ${taskId}] Loaded existing task.`);
387
+ const { status } = data;
388
+ if (isTerminalTaskState(status.state)) throw _mastra_core_a2a.MastraA2AError.invalidRequest(`Task ${taskId} is in terminal state ${status.state} and cannot be restarted.`);
389
+ let updatedData = {
390
+ ...data,
391
+ history: [...data.history || [], message]
392
+ };
393
+ if (status.state === "input-required" || status.state === "auth-required") {
394
+ logger?.info(`[Task ${taskId}] Changing state from '${status.state}' to 'working'.`);
395
+ updatedData = applyUpdateToTask(updatedData, { state: "working" });
396
+ } else if (status.state === "working") logger?.warn(`[Task ${taskId}] Received message while already 'working'. Proceeding.`);
397
+ try {
398
+ await taskStore.save({
399
+ agentId,
400
+ data: updatedData,
401
+ expectedVersion: snapshot.version
402
+ });
403
+ return updatedData;
404
+ } catch (error) {
405
+ if (error instanceof require_server_a2a_store.TaskStoreVersionConflictError) continue;
406
+ throw error;
407
+ }
368
408
  }
369
- logger?.info(`[Task ${taskId}] Loaded existing task.`);
370
- let updatedData = data;
371
- updatedData.history = [...data.history || [], message];
372
- const { status } = data;
373
- if ([
374
- "completed",
375
- "failed",
376
- "canceled"
377
- ].includes(status.state)) {
378
- logger?.warn(`[Task ${taskId}] Received message for task in final state ${status.state}. Restarting.`);
379
- updatedData = applyUpdateToTask(updatedData, {
380
- state: "submitted",
381
- message: void 0
382
- });
383
- } else if (status.state === "input-required") {
384
- logger?.info(`[Task ${taskId}] Changing state from 'input-required' to 'working'.`);
385
- updatedData = applyUpdateToTask(updatedData, { state: "working" });
386
- } else if (status.state === "working") logger?.warn(`[Task ${taskId}] Received message while already 'working'. Proceeding.`);
387
- await taskStore.save({
388
- agentId,
389
- data: updatedData
390
- });
391
- return updatedData;
409
+ throw _mastra_core_a2a.MastraA2AError.invalidRequest(`Task ${taskId} was updated concurrently. Retry the request.`);
392
410
  }
393
411
  //#endregion
394
412
  //#region src/server/handlers/a2a.ts
@@ -575,6 +593,176 @@ function createDataArtifactUpdate({ taskId, contextId, data, lastChunk }) {
575
593
  }
576
594
  };
577
595
  }
596
+ /**
597
+ * Task metadata keys that store the resume bookkeeping for a suspended agent
598
+ * run so a follow-up message for an `input-required` task can resume it.
599
+ */
600
+ const SUSPENDED_RUN_ID_METADATA_KEY = "suspendedRunId";
601
+ const SUSPENDED_TOOL_CALL_ID_METADATA_KEY = "suspendedToolCallId";
602
+ const SUSPENDED_REQUIRES_APPROVAL_METADATA_KEY = "suspendedRequiresApproval";
603
+ function toJsonSafe(value) {
604
+ if (value === void 0) return;
605
+ try {
606
+ return JSON.parse(JSON.stringify(value));
607
+ } catch {
608
+ return;
609
+ }
610
+ }
611
+ /**
612
+ * Builds the `input-required` status update for a suspended agent run.
613
+ * The status message carries a human-readable prompt plus a data part with
614
+ * the structured suspend payload and resume schema so A2A clients can
615
+ * render/collect the required input (HITL per the A2A spec).
616
+ */
617
+ function createInputRequiredStatusUpdate({ taskId, contextId, suspendPayload, resumeSchema, logger }) {
618
+ const extractMessage = (value) => {
619
+ if (!value || typeof value !== "object") return;
620
+ const messageValue = value.message;
621
+ return typeof messageValue === "string" && messageValue.length > 0 ? messageValue : void 0;
622
+ };
623
+ const promptText = extractMessage(suspendPayload) ?? extractMessage(suspendPayload?.suspendPayload) ?? "Additional input is required to continue this task.";
624
+ const safeSuspendPayload = toJsonSafe(suspendPayload);
625
+ const safeResumeSchema = toJsonSafe(resumeSchema);
626
+ if (suspendPayload !== void 0 && safeSuspendPayload === void 0) logger?.warn(`[Task ${taskId}] Suspend payload is not JSON-serializable and was omitted from the status message.`);
627
+ if (resumeSchema !== void 0 && safeResumeSchema === void 0) logger?.warn(`[Task ${taskId}] Resume schema is not JSON-serializable and was omitted from the status message.`);
628
+ const data = {
629
+ ...safeSuspendPayload !== void 0 ? { suspendPayload: safeSuspendPayload } : {},
630
+ ...safeResumeSchema !== void 0 ? { resumeSchema: safeResumeSchema } : {}
631
+ };
632
+ return {
633
+ state: "input-required",
634
+ message: {
635
+ messageId: crypto.randomUUID(),
636
+ kind: "message",
637
+ role: "agent",
638
+ taskId,
639
+ contextId,
640
+ parts: [{
641
+ kind: "text",
642
+ text: promptText
643
+ }, ...Object.keys(data).length > 0 ? [{
644
+ kind: "data",
645
+ data
646
+ }] : []]
647
+ }
648
+ };
649
+ }
650
+ /**
651
+ * Extracts resume data from a follow-up message for an `input-required` task.
652
+ * Prefers a structured data part; falls back to parsing the text as JSON
653
+ * (the Mastra A2A client serializes structured resume data as JSON text),
654
+ * and finally to the raw text.
655
+ */
656
+ function extractResumeData(message) {
657
+ const dataPart = message.parts.find((part) => part.kind === "data");
658
+ if (dataPart && "data" in dataPart) return dataPart.data;
659
+ const text = message.parts.filter((part) => part.kind === "text").map((part) => part.text).join("\n").trim();
660
+ if (!text) return;
661
+ try {
662
+ return JSON.parse(text);
663
+ } catch {
664
+ return text;
665
+ }
666
+ }
667
+ function getSuspendedRunId(task) {
668
+ const value = task?.metadata?.[SUSPENDED_RUN_ID_METADATA_KEY];
669
+ return typeof value === "string" ? value : void 0;
670
+ }
671
+ /**
672
+ * Approval suspensions (`requireApproval`) carry `{ toolCallId, toolName, args, resumeSchema }`
673
+ * without a nested `suspendPayload`, while `suspend()` suspensions include one
674
+ * (see `ToolCallApprovalPayload` / `ToolCallSuspendedPayload` in @mastra/core).
675
+ */
676
+ function isApprovalSuspension(suspendPayload) {
677
+ if (!suspendPayload || typeof suspendPayload !== "object") return false;
678
+ const payload = suspendPayload;
679
+ return typeof payload.toolCallId === "string" && payload.suspendPayload === void 0;
680
+ }
681
+ const APPROVAL_AFFIRMATIVE_PATTERN = /^(y|yes|approve|approved|ok|okay|confirm|confirmed|true)[.!]?$/i;
682
+ const APPROVAL_NEGATIVE_PATTERN = /^(n|no|decline|declined|deny|denied|reject|rejected|false)[.!]?$/i;
683
+ /**
684
+ * Approval resumes are driven by `resumeData.approved` in the agentic loop, so
685
+ * plain-text replies from A2A clients ("yes", "no") are coerced to the
686
+ * `{ approved }` shape. Unrecognized values pass through unchanged.
687
+ */
688
+ function normalizeResumeData(resumeData, requiresApproval) {
689
+ if (!requiresApproval || typeof resumeData !== "string") return resumeData;
690
+ const text = resumeData.trim();
691
+ if (APPROVAL_AFFIRMATIVE_PATTERN.test(text)) return { approved: true };
692
+ if (APPROVAL_NEGATIVE_PATTERN.test(text)) return { approved: false };
693
+ return resumeData;
694
+ }
695
+ /**
696
+ * Marks a task `input-required` for a suspended agent run and records the
697
+ * resume bookkeeping (runId, toolCallId, approval flag) in task metadata.
698
+ * Shared by the send and stream paths so both report identical suspensions.
699
+ */
700
+ function applySuspensionToTask({ task, suspendPayload, resumeSchema, runId, logger }) {
701
+ const nextTask = applyUpdateToTask(task, createInputRequiredStatusUpdate({
702
+ taskId: task.id,
703
+ contextId: task.contextId,
704
+ suspendPayload,
705
+ resumeSchema,
706
+ logger
707
+ }));
708
+ const payload = suspendPayload;
709
+ nextTask.metadata = {
710
+ ...clearSuspensionMetadata(nextTask.metadata),
711
+ [SUSPENDED_RUN_ID_METADATA_KEY]: runId,
712
+ ...typeof payload?.toolCallId === "string" ? { [SUSPENDED_TOOL_CALL_ID_METADATA_KEY]: payload.toolCallId } : {},
713
+ ...isApprovalSuspension(suspendPayload) ? { [SUSPENDED_REQUIRES_APPROVAL_METADATA_KEY]: true } : {}
714
+ };
715
+ return nextTask;
716
+ }
717
+ /** Removes the suspension bookkeeping from task metadata once the run completes. */
718
+ function clearSuspensionMetadata(metadata) {
719
+ const { [SUSPENDED_RUN_ID_METADATA_KEY]: _runId, [SUSPENDED_TOOL_CALL_ID_METADATA_KEY]: _toolCallId, [SUSPENDED_REQUIRES_APPROVAL_METADATA_KEY]: _requiresApproval, ...rest } = metadata ?? {};
720
+ return rest;
721
+ }
722
+ /**
723
+ * Claims an interrupted task for resume by transitioning it to `working`.
724
+ * `loadWithVersion` and the body of `InMemoryTaskStore.save` both execute
725
+ * synchronously, so two concurrent follow-up messages cannot both claim (and
726
+ * double-resume) the same suspended run.
727
+ */
728
+ async function claimInterruptedTaskResume({ taskStore, agentId, taskId }) {
729
+ const snapshot = taskStore.loadWithVersion({
730
+ agentId,
731
+ taskId
732
+ });
733
+ if (snapshot?.task.status.state !== "input-required" && snapshot?.task.status.state !== "auth-required") return;
734
+ const task = snapshot.task;
735
+ const toolCallId = task.metadata?.[SUSPENDED_TOOL_CALL_ID_METADATA_KEY];
736
+ const claim = {
737
+ runId: getSuspendedRunId(task) ?? taskId,
738
+ ...typeof toolCallId === "string" ? { toolCallId } : {},
739
+ requiresApproval: task.metadata?.[SUSPENDED_REQUIRES_APPROVAL_METADATA_KEY] === true
740
+ };
741
+ try {
742
+ await taskStore.save({
743
+ agentId,
744
+ data: applyUpdateToTask(task, { state: "working" }),
745
+ expectedVersion: snapshot.version
746
+ });
747
+ } catch (error) {
748
+ if (error instanceof require_server_a2a_store.TaskStoreVersionConflictError) return;
749
+ throw error;
750
+ }
751
+ return claim;
752
+ }
753
+ async function waitForClaimedResume({ taskStore, agentId, taskId }) {
754
+ let snapshot = taskStore.loadWithVersion({
755
+ agentId,
756
+ taskId
757
+ });
758
+ if (!snapshot) throw _mastra_core_a2a.MastraA2AError.taskNotFound(taskId);
759
+ while (snapshot.task.status.state === "working") snapshot = await taskStore.waitForNextUpdate({
760
+ agentId,
761
+ taskId,
762
+ afterVersion: snapshot.version
763
+ });
764
+ return snapshot.task;
765
+ }
578
766
  function resolvePushNotificationPair({ pushNotificationStore, pushNotificationSender }) {
579
767
  if (pushNotificationSender) return {
580
768
  pushNotificationStore: pushNotificationSender.getStore(),
@@ -603,23 +791,45 @@ function shouldSendPushNotification(previousTask, nextTask) {
603
791
  "completed",
604
792
  "failed",
605
793
  "canceled",
606
- "input-required"
794
+ "rejected",
795
+ "input-required",
796
+ "auth-required"
607
797
  ].includes(nextTask.status.state)) return false;
608
798
  return previousTask?.status.state !== nextTask.status.state;
609
799
  }
610
- async function saveTaskAndMaybeSendPushNotification({ taskStore, pushNotificationSender, previousTask, nextTask, agentId, logger }) {
611
- await taskStore.save({
800
+ function createLinkedAbortController(abortSignal) {
801
+ const controller = new AbortController();
802
+ if (!abortSignal) return {
803
+ controller,
804
+ cleanup: () => {}
805
+ };
806
+ const abortFromSignal = () => {
807
+ if (!controller.signal.aborted) controller.abort(abortSignal.reason);
808
+ };
809
+ if (abortSignal.aborted) abortFromSignal();
810
+ else abortSignal.addEventListener("abort", abortFromSignal, { once: true });
811
+ return {
812
+ controller,
813
+ cleanup: () => abortSignal.removeEventListener("abort", abortFromSignal)
814
+ };
815
+ }
816
+ async function saveTaskAndMaybeSendPushNotification({ taskStore, pushNotificationSender, previousTask, nextTask, agentId, expectedVersion, logger }) {
817
+ const storedTask = await taskStore.save({
612
818
  agentId,
613
- data: nextTask
819
+ data: nextTask,
820
+ expectedVersion,
821
+ skipIfCanceled: true
614
822
  });
615
- if (!shouldSendPushNotification(previousTask, nextTask)) return;
823
+ if (storedTask.status.state === "canceled" && nextTask.status.state !== "canceled") return storedTask;
824
+ if (!shouldSendPushNotification(previousTask, storedTask)) return storedTask;
616
825
  pushNotificationSender.sendNotifications({
617
826
  agentId,
618
- task: nextTask,
827
+ task: storedTask,
619
828
  logger
620
829
  }).catch((error) => {
621
830
  logger?.error("Failed to schedule A2A push notification", error);
622
831
  });
832
+ return storedTask;
623
833
  }
624
834
  function extractFullStreamTextDelta(value) {
625
835
  if (typeof value !== "object" || value === null || !("type" in value)) return null;
@@ -642,12 +852,10 @@ function extractFinalStructuredObject(value) {
642
852
  const objectValue = chunk.payload?.object ?? chunk.object;
643
853
  return objectValue && typeof objectValue === "object" ? objectValue : void 0;
644
854
  }
645
- function isTerminalTaskState(state) {
646
- return [
647
- "completed",
648
- "failed",
649
- "canceled"
650
- ].includes(state);
855
+ function isSuspensionChunk(value) {
856
+ if (typeof value !== "object" || value === null || !("type" in value)) return false;
857
+ const type = value.type;
858
+ return type === "tool-call-suspended" || type === "tool-call-approval";
651
859
  }
652
860
  function artifactIdentity(artifact) {
653
861
  return artifact.artifactId || artifact.name;
@@ -692,11 +900,15 @@ function getTaskArtifactUpdates({ previous, next }) {
692
900
  artifact: structuredClone(artifact)
693
901
  }));
694
902
  }
695
- async function executeMessageSend({ requestId, message, metadata, currentData, taskStore, pushNotificationSender, agent, agentId, logger, requestContext }) {
903
+ async function executeMessageSend({ requestId, message, metadata, currentData, taskStore, pushNotificationSender, agent, agentId, logger, requestContext, resume }) {
696
904
  const { contextId } = message;
697
905
  try {
698
906
  const resourceId = metadata?.resourceId ?? message.metadata?.resourceId ?? agentId;
699
- const result = await agent.generate([convertToCoreMessage(message)], {
907
+ const result = resume ? await agent.resumeGenerate(normalizeResumeData(extractResumeData(message), resume.requiresApproval), {
908
+ runId: resume.runId,
909
+ ...resume.toolCallId ? { toolCallId: resume.toolCallId } : {},
910
+ requestContext
911
+ }) : await agent.generate([convertToCoreMessage(message)], {
700
912
  runId: currentData.id,
701
913
  requestContext,
702
914
  ...contextId ? {
@@ -717,13 +929,32 @@ async function executeMessageSend({ requestId, message, metadata, currentData, t
717
929
  data: result.object
718
930
  });
719
931
  if (artifactUpdate) currentData = applyUpdateToTask(currentData, artifactUpdate);
932
+ if (result.finishReason === "suspended") {
933
+ const previousTask = currentData;
934
+ currentData = applySuspensionToTask({
935
+ task: currentData,
936
+ suspendPayload: result.suspendPayload,
937
+ resumeSchema: result.resumeSchema,
938
+ runId: result.runId ?? currentData.id,
939
+ logger
940
+ });
941
+ await saveTaskAndMaybeSendPushNotification({
942
+ taskStore,
943
+ pushNotificationSender,
944
+ previousTask,
945
+ nextTask: currentData,
946
+ agentId,
947
+ logger
948
+ });
949
+ return createSuccessResponse(requestId, currentData);
950
+ }
720
951
  const previousTask = currentData;
721
952
  currentData = applyUpdateToTask(currentData, {
722
953
  state: "completed",
723
954
  message: void 0
724
955
  });
725
956
  currentData.metadata = {
726
- ...currentData.metadata,
957
+ ...clearSuspensionMetadata(currentData.metadata),
727
958
  execution: {
728
959
  toolCalls: result.toolCalls,
729
960
  toolResults: result.toolResults,
@@ -781,11 +1012,28 @@ async function handleMessageSend({ requestId, params, taskStore, pushNotificatio
781
1012
  const { message, metadata } = params;
782
1013
  const { contextId } = message;
783
1014
  const taskId = message.taskId || crypto.randomUUID();
784
- const existingTask = await taskStore.load({
1015
+ const existingTask = taskStore.loadWithVersion({
785
1016
  agentId,
786
1017
  taskId
787
- });
1018
+ })?.task;
1019
+ if (message.taskId && !existingTask) throw _mastra_core_a2a.MastraA2AError.taskNotFound(message.taskId);
788
1020
  if (params.configuration?.blocking === false && existingTask?.status.state === "working") return createSuccessResponse(requestId, existingTask);
1021
+ if (existingTask?.status.state === "working" && getSuspendedRunId(existingTask)) return createSuccessResponse(requestId, await waitForClaimedResume({
1022
+ taskStore,
1023
+ agentId,
1024
+ taskId
1025
+ }));
1026
+ const wasInterrupted = isInterruptedTaskState(existingTask?.status.state);
1027
+ const resume = await claimInterruptedTaskResume({
1028
+ taskStore,
1029
+ agentId,
1030
+ taskId
1031
+ });
1032
+ if (wasInterrupted && !resume) return createSuccessResponse(requestId, await waitForClaimedResume({
1033
+ taskStore,
1034
+ agentId,
1035
+ taskId
1036
+ }));
789
1037
  const { pushNotificationStore: resolvedPushNotificationStore, pushNotificationSender: resolvedPushNotificationSender } = resolvePushNotificationPair({
790
1038
  pushNotificationStore,
791
1039
  pushNotificationSender
@@ -831,7 +1079,8 @@ async function handleMessageSend({ requestId, params, taskStore, pushNotificatio
831
1079
  agent,
832
1080
  agentId,
833
1081
  logger,
834
- requestContext
1082
+ requestContext,
1083
+ resume
835
1084
  });
836
1085
  if (params.configuration?.blocking === false) {
837
1086
  execution.catch((error) => {
@@ -908,11 +1157,20 @@ async function handleDeleteTaskPushNotificationConfig({ requestId, taskStore, pu
908
1157
  })) throw _mastra_core_a2a.MastraA2AError.invalidParams(`Push notification config not found: ${params.pushNotificationConfigId}`);
909
1158
  return createSuccessResponse(requestId, null);
910
1159
  }
911
- async function* handleMessageStream({ requestId, params, taskStore, pushNotificationStore, pushNotificationSender, agent, agentId, logger, requestContext }) {
1160
+ async function* handleMessageStream({ requestId, params, taskStore, pushNotificationStore, pushNotificationSender, agent, agentId, logger, requestContext, abortSignal }) {
912
1161
  validateMessageSendParams(params);
913
1162
  const { message, metadata } = params;
914
1163
  const { contextId } = message;
915
1164
  const taskId = message.taskId || crypto.randomUUID();
1165
+ if (message.taskId && !taskStore.loadWithVersion({
1166
+ agentId,
1167
+ taskId
1168
+ })) throw _mastra_core_a2a.MastraA2AError.taskNotFound(message.taskId);
1169
+ const resume = await claimInterruptedTaskResume({
1170
+ taskStore,
1171
+ agentId,
1172
+ taskId
1173
+ });
916
1174
  const { pushNotificationStore: resolvedPushNotificationStore, pushNotificationSender: resolvedPushNotificationSender } = resolvePushNotificationPair({
917
1175
  pushNotificationStore,
918
1176
  pushNotificationSender
@@ -948,12 +1206,24 @@ async function* handleMessageStream({ requestId, params, taskStore, pushNotifica
948
1206
  agentId,
949
1207
  logger
950
1208
  });
951
- yield createSuccessResponse(requestId, currentData);
1209
+ const { controller: taskAbortController, cleanup: cleanupLinkedAbortController } = createLinkedAbortController(abortSignal);
1210
+ const unregisterTaskAbortController = taskStore.registerAbortController({
1211
+ agentId,
1212
+ taskId,
1213
+ controller: taskAbortController
1214
+ });
952
1215
  try {
1216
+ yield createSuccessResponse(requestId, currentData);
953
1217
  const resourceId = metadata?.resourceId ?? message.metadata?.resourceId ?? agentId;
954
- const result = await agent.stream([convertToCoreMessage(message)], {
1218
+ const result = resume ? await agent.resumeStream(normalizeResumeData(extractResumeData(message), resume.requiresApproval), {
1219
+ runId: resume.runId,
1220
+ ...resume.toolCallId ? { toolCallId: resume.toolCallId } : {},
1221
+ requestContext,
1222
+ abortSignal: taskAbortController.signal
1223
+ }) : await agent.stream([convertToCoreMessage(message)], {
955
1224
  runId: taskId,
956
1225
  requestContext,
1226
+ abortSignal: taskAbortController.signal,
957
1227
  ...contextId ? {
958
1228
  threadId: contextId,
959
1229
  resourceId
@@ -962,7 +1232,22 @@ async function* handleMessageStream({ requestId, params, taskStore, pushNotifica
962
1232
  let sawTextArtifact = false;
963
1233
  let pendingTextChunk;
964
1234
  let structuredData;
1235
+ let suspended = false;
1236
+ let streamCanceled = false;
965
1237
  for await (const chunk of result.fullStream) {
1238
+ if (taskAbortController.signal.aborted) {
1239
+ const latestTask = await taskStore.load({
1240
+ agentId,
1241
+ taskId: currentData.id
1242
+ });
1243
+ if (latestTask) currentData = latestTask;
1244
+ streamCanceled = true;
1245
+ break;
1246
+ }
1247
+ if (isSuspensionChunk(chunk)) {
1248
+ suspended = true;
1249
+ continue;
1250
+ }
966
1251
  const textDelta = extractFullStreamTextDelta(chunk);
967
1252
  if (textDelta !== null) {
968
1253
  if (!pendingTextChunk) {
@@ -977,13 +1262,17 @@ async function* handleMessageStream({ requestId, params, taskStore, pushNotifica
977
1262
  lastChunk: false
978
1263
  });
979
1264
  currentData = applyUpdateToTask(currentData, textUpdate);
980
- await saveTaskAndMaybeSendPushNotification({
1265
+ currentData = await saveTaskAndMaybeSendPushNotification({
981
1266
  taskStore,
982
1267
  pushNotificationSender: resolvedPushNotificationSender,
983
1268
  nextTask: currentData,
984
1269
  agentId,
985
1270
  logger
986
1271
  });
1272
+ if (currentData.status.state === "canceled") {
1273
+ streamCanceled = true;
1274
+ break;
1275
+ }
987
1276
  yield createSuccessResponse(requestId, textUpdate);
988
1277
  sawTextArtifact = true;
989
1278
  pendingTextChunk = textDelta;
@@ -992,98 +1281,217 @@ async function* handleMessageStream({ requestId, params, taskStore, pushNotifica
992
1281
  const finalStructuredObject = extractFinalStructuredObject(chunk);
993
1282
  if (finalStructuredObject) structuredData = finalStructuredObject;
994
1283
  }
995
- structuredData ??= await result.object;
996
- if (!pendingTextChunk && !sawTextArtifact) {
997
- const finalText = await result.text;
998
- if (finalText) pendingTextChunk = finalText;
1284
+ if (!streamCanceled && taskAbortController.signal.aborted) {
1285
+ const latestTask = await taskStore.load({
1286
+ agentId,
1287
+ taskId: currentData.id
1288
+ });
1289
+ if (latestTask) currentData = latestTask;
1290
+ streamCanceled = true;
999
1291
  }
1000
- if (pendingTextChunk) {
1001
- const textUpdate = createTextChunkArtifactUpdate({
1002
- taskId: currentData.id,
1003
- contextId: currentData.contextId,
1004
- text: pendingTextChunk,
1005
- append: sawTextArtifact,
1006
- lastChunk: !structuredData
1292
+ if (streamCanceled && abortSignal?.aborted && currentData.status.state !== "canceled") {
1293
+ const previousTask = currentData;
1294
+ currentData = applyUpdateToTask(currentData, {
1295
+ state: "canceled",
1296
+ message: {
1297
+ messageId: crypto.randomUUID(),
1298
+ role: "agent",
1299
+ parts: [{
1300
+ kind: "text",
1301
+ text: "Task canceled because the request was aborted."
1302
+ }],
1303
+ kind: "message"
1304
+ }
1007
1305
  });
1008
- currentData = applyUpdateToTask(currentData, textUpdate);
1009
- await saveTaskAndMaybeSendPushNotification({
1306
+ currentData = await saveTaskAndMaybeSendPushNotification({
1010
1307
  taskStore,
1011
1308
  pushNotificationSender: resolvedPushNotificationSender,
1309
+ previousTask,
1012
1310
  nextTask: currentData,
1013
1311
  agentId,
1014
1312
  logger
1015
1313
  });
1016
- yield createSuccessResponse(requestId, textUpdate);
1017
- sawTextArtifact = true;
1018
- pendingTextChunk = void 0;
1019
1314
  }
1020
- if (structuredData) {
1021
- const dataUpdate = createDataArtifactUpdate({
1022
- taskId: currentData.id,
1023
- contextId: currentData.contextId,
1024
- data: structuredData,
1025
- lastChunk: true
1315
+ if (!streamCanceled && suspended) {
1316
+ if (pendingTextChunk) {
1317
+ const textUpdate = createTextChunkArtifactUpdate({
1318
+ taskId: currentData.id,
1319
+ contextId: currentData.contextId,
1320
+ text: pendingTextChunk,
1321
+ append: sawTextArtifact,
1322
+ lastChunk: true
1323
+ });
1324
+ currentData = applyUpdateToTask(currentData, textUpdate);
1325
+ currentData = await saveTaskAndMaybeSendPushNotification({
1326
+ taskStore,
1327
+ pushNotificationSender: resolvedPushNotificationSender,
1328
+ nextTask: currentData,
1329
+ agentId,
1330
+ logger
1331
+ });
1332
+ if (currentData.status.state === "canceled") streamCanceled = true;
1333
+ else yield createSuccessResponse(requestId, textUpdate);
1334
+ }
1335
+ const suspensionTask = currentData;
1336
+ currentData = applySuspensionToTask({
1337
+ task: currentData,
1338
+ suspendPayload: await result.suspendPayload,
1339
+ resumeSchema: await result.resumeSchema,
1340
+ runId: result.runId ?? currentData.id,
1341
+ logger
1026
1342
  });
1027
- currentData = applyUpdateToTask(currentData, dataUpdate);
1028
- await saveTaskAndMaybeSendPushNotification({
1343
+ currentData = await saveTaskAndMaybeSendPushNotification({
1029
1344
  taskStore,
1030
1345
  pushNotificationSender: resolvedPushNotificationSender,
1346
+ previousTask: suspensionTask,
1031
1347
  nextTask: currentData,
1032
1348
  agentId,
1033
1349
  logger
1034
1350
  });
1035
- yield createSuccessResponse(requestId, dataUpdate);
1351
+ if (currentData.status.state === "canceled") streamCanceled = true;
1036
1352
  }
1037
- const previousTask = currentData;
1038
- const completedTask = applyUpdateToTask(currentData, {
1039
- state: "completed",
1040
- message: void 0
1041
- });
1042
- completedTask.metadata = {
1043
- ...completedTask.metadata,
1044
- execution: {
1045
- toolCalls: await result.toolCalls,
1046
- toolResults: await result.toolResults,
1047
- usage: await result.usage,
1048
- finishReason: await result.finishReason
1353
+ if (!streamCanceled && !suspended) {
1354
+ structuredData ??= await result.object;
1355
+ if (!pendingTextChunk && !sawTextArtifact) {
1356
+ const finalText = await result.text;
1357
+ if (finalText) pendingTextChunk = finalText;
1049
1358
  }
1050
- };
1051
- currentData = completedTask;
1052
- await saveTaskAndMaybeSendPushNotification({
1053
- taskStore,
1054
- pushNotificationSender: resolvedPushNotificationSender,
1055
- previousTask,
1056
- nextTask: currentData,
1359
+ if (pendingTextChunk) {
1360
+ const textUpdate = createTextChunkArtifactUpdate({
1361
+ taskId: currentData.id,
1362
+ contextId: currentData.contextId,
1363
+ text: pendingTextChunk,
1364
+ append: sawTextArtifact,
1365
+ lastChunk: !structuredData
1366
+ });
1367
+ currentData = applyUpdateToTask(currentData, textUpdate);
1368
+ currentData = await saveTaskAndMaybeSendPushNotification({
1369
+ taskStore,
1370
+ pushNotificationSender: resolvedPushNotificationSender,
1371
+ nextTask: currentData,
1372
+ agentId,
1373
+ logger
1374
+ });
1375
+ if (currentData.status.state === "canceled") streamCanceled = true;
1376
+ else yield createSuccessResponse(requestId, textUpdate);
1377
+ sawTextArtifact = true;
1378
+ pendingTextChunk = void 0;
1379
+ }
1380
+ if (!streamCanceled && structuredData) {
1381
+ const dataUpdate = createDataArtifactUpdate({
1382
+ taskId: currentData.id,
1383
+ contextId: currentData.contextId,
1384
+ data: structuredData,
1385
+ lastChunk: true
1386
+ });
1387
+ currentData = applyUpdateToTask(currentData, dataUpdate);
1388
+ currentData = await saveTaskAndMaybeSendPushNotification({
1389
+ taskStore,
1390
+ pushNotificationSender: resolvedPushNotificationSender,
1391
+ nextTask: currentData,
1392
+ agentId,
1393
+ logger
1394
+ });
1395
+ if (currentData.status.state === "canceled") streamCanceled = true;
1396
+ else yield createSuccessResponse(requestId, dataUpdate);
1397
+ }
1398
+ if (!streamCanceled) {
1399
+ const previousTask = currentData;
1400
+ const completedTask = applyUpdateToTask(currentData, {
1401
+ state: "completed",
1402
+ message: void 0
1403
+ });
1404
+ completedTask.metadata = {
1405
+ ...clearSuspensionMetadata(completedTask.metadata),
1406
+ execution: {
1407
+ toolCalls: await result.toolCalls,
1408
+ toolResults: await result.toolResults,
1409
+ usage: await result.usage,
1410
+ finishReason: await result.finishReason
1411
+ }
1412
+ };
1413
+ currentData = await saveTaskAndMaybeSendPushNotification({
1414
+ taskStore,
1415
+ pushNotificationSender: resolvedPushNotificationSender,
1416
+ previousTask,
1417
+ nextTask: completedTask,
1418
+ agentId,
1419
+ logger
1420
+ });
1421
+ }
1422
+ }
1423
+ } catch (handlerError) {
1424
+ const latestTask = await taskStore.load({
1057
1425
  agentId,
1058
- logger
1426
+ taskId: currentData.id
1059
1427
  });
1060
- } catch (handlerError) {
1061
- const previousTask = currentData;
1062
- currentData = applyUpdateToTask(currentData, {
1063
- state: "failed",
1064
- message: {
1065
- messageId: crypto.randomUUID(),
1066
- role: "agent",
1067
- parts: [{
1068
- kind: "text",
1069
- text: `Handler failed: ${handlerError instanceof Error ? handlerError.message : String(handlerError)}`
1070
- }],
1071
- kind: "message"
1428
+ if (latestTask?.status.state === "canceled") currentData = latestTask;
1429
+ else if (taskAbortController.signal.aborted) {
1430
+ currentData = latestTask ?? currentData;
1431
+ if (abortSignal?.aborted) {
1432
+ const previousTask = currentData;
1433
+ currentData = applyUpdateToTask(currentData, {
1434
+ state: "canceled",
1435
+ message: {
1436
+ messageId: crypto.randomUUID(),
1437
+ role: "agent",
1438
+ parts: [{
1439
+ kind: "text",
1440
+ text: "Task canceled because the request was aborted."
1441
+ }],
1442
+ kind: "message"
1443
+ }
1444
+ });
1445
+ try {
1446
+ currentData = await saveTaskAndMaybeSendPushNotification({
1447
+ taskStore,
1448
+ pushNotificationSender: resolvedPushNotificationSender,
1449
+ previousTask,
1450
+ nextTask: currentData,
1451
+ agentId,
1452
+ logger
1453
+ });
1454
+ } catch (saveError) {
1455
+ logger?.error(`Failed to save task ${currentData.id} after request abort:`, saveError?.message);
1456
+ }
1072
1457
  }
1073
- });
1074
- try {
1075
- await saveTaskAndMaybeSendPushNotification({
1076
- taskStore,
1077
- pushNotificationSender: resolvedPushNotificationSender,
1078
- previousTask,
1079
- nextTask: currentData,
1080
- agentId,
1081
- logger
1458
+ } else {
1459
+ currentData = latestTask ?? currentData;
1460
+ const previousTask = currentData;
1461
+ currentData = applyUpdateToTask(currentData, {
1462
+ state: "failed",
1463
+ message: {
1464
+ messageId: crypto.randomUUID(),
1465
+ role: "agent",
1466
+ parts: [{
1467
+ kind: "text",
1468
+ text: `Handler failed: ${handlerError instanceof Error ? handlerError.message : String(handlerError)}`
1469
+ }],
1470
+ kind: "message"
1471
+ }
1082
1472
  });
1083
- } catch (saveError) {
1084
- logger?.error(`Failed to save task ${currentData.id} after handler error:`, saveError?.message);
1473
+ try {
1474
+ currentData = await saveTaskAndMaybeSendPushNotification({
1475
+ taskStore,
1476
+ pushNotificationSender: resolvedPushNotificationSender,
1477
+ previousTask,
1478
+ nextTask: currentData,
1479
+ agentId,
1480
+ logger
1481
+ });
1482
+ } catch (saveError) {
1483
+ logger?.error(`Failed to save task ${currentData.id} after handler error:`, saveError?.message);
1484
+ }
1085
1485
  }
1486
+ } finally {
1487
+ unregisterTaskAbortController();
1488
+ cleanupLinkedAbortController();
1086
1489
  }
1490
+ const latestTask = await taskStore.load({
1491
+ agentId,
1492
+ taskId: currentData.id
1493
+ });
1494
+ if (latestTask?.status.state === "canceled") currentData = latestTask;
1087
1495
  yield createSuccessResponse(requestId, {
1088
1496
  kind: "status-update",
1089
1497
  taskId: currentData.id,
@@ -1099,7 +1507,7 @@ async function* handleTaskResubscribe({ requestId, taskStore, agentId, taskId, a
1099
1507
  });
1100
1508
  if (!snapshot) throw _mastra_core_a2a.MastraA2AError.taskNotFound(taskId);
1101
1509
  yield createSuccessResponse(requestId, snapshot.task);
1102
- if (isTerminalTaskState(snapshot.task.status.state)) return;
1510
+ if (isTerminalTaskState(snapshot.task.status.state) || isInterruptedTaskState(snapshot.task.status.state)) return;
1103
1511
  while (true) {
1104
1512
  const { task, version } = snapshot;
1105
1513
  const nextUpdate = await taskStore.waitForNextUpdate({
@@ -1112,14 +1520,16 @@ async function* handleTaskResubscribe({ requestId, taskStore, agentId, taskId, a
1112
1520
  previous: task,
1113
1521
  next: nextUpdate.task
1114
1522
  })) yield createSuccessResponse(requestId, artifactUpdate);
1523
+ const nextState = nextUpdate.task.status.state;
1524
+ const streamEnded = isTerminalTaskState(nextState) || isInterruptedTaskState(nextState);
1115
1525
  if (didTaskStatusChange(task, nextUpdate.task)) yield createSuccessResponse(requestId, {
1116
1526
  kind: "status-update",
1117
1527
  taskId: nextUpdate.task.id,
1118
1528
  contextId: nextUpdate.task.contextId,
1119
1529
  status: nextUpdate.task.status,
1120
- final: isTerminalTaskState(nextUpdate.task.status.state)
1530
+ final: streamEnded
1121
1531
  });
1122
- if (isTerminalTaskState(nextUpdate.task.status.state)) return;
1532
+ if (streamEnded) return;
1123
1533
  snapshot = nextUpdate;
1124
1534
  }
1125
1535
  }
@@ -1157,44 +1567,53 @@ function createA2ASSEResponse(payload) {
1157
1567
  } });
1158
1568
  }
1159
1569
  async function handleTaskCancel({ requestId, taskStore, pushNotificationSender, agentId, taskId, logger }) {
1160
- let data = await taskStore.load({
1161
- agentId,
1162
- taskId
1163
- });
1164
- if (!data) throw _mastra_core_a2a.MastraA2AError.taskNotFound(taskId);
1165
- if ([
1166
- "completed",
1167
- "failed",
1168
- "canceled"
1169
- ].includes(data.status.state)) {
1170
- logger?.info(`Task ${taskId} already in final state ${data.status.state}, cannot cancel.`);
1171
- return createSuccessResponse(requestId, data);
1172
- }
1173
- taskStore.activeCancellations.add(taskId);
1174
- const cancelUpdate = {
1175
- state: "canceled",
1176
- message: {
1177
- role: "agent",
1178
- parts: [{
1179
- kind: "text",
1180
- text: "Task cancelled by request."
1181
- }],
1182
- kind: "message",
1183
- messageId: crypto.randomUUID()
1570
+ for (let attempt = 0; attempt < 2; attempt++) {
1571
+ const snapshot = taskStore.loadWithVersion({
1572
+ agentId,
1573
+ taskId
1574
+ });
1575
+ const data = snapshot?.task;
1576
+ if (!data) throw _mastra_core_a2a.MastraA2AError.taskNotFound(taskId);
1577
+ if (isTerminalTaskState(data.status.state)) {
1578
+ logger?.info(`Task ${taskId} already in final state ${data.status.state}, cannot cancel.`);
1579
+ throw _mastra_core_a2a.MastraA2AError.taskNotCancelable(taskId);
1184
1580
  }
1185
- };
1186
- const previousTask = data;
1187
- data = applyUpdateToTask(data, cancelUpdate);
1188
- await saveTaskAndMaybeSendPushNotification({
1189
- taskStore,
1190
- pushNotificationSender: resolvePushNotificationPair({ pushNotificationSender }).pushNotificationSender,
1191
- previousTask,
1192
- nextTask: data,
1193
- agentId,
1194
- logger
1195
- });
1196
- taskStore.activeCancellations.delete(taskId);
1197
- return createSuccessResponse(requestId, data);
1581
+ taskStore.activeCancellations.add(taskId);
1582
+ taskStore.abortTask({
1583
+ agentId,
1584
+ taskId,
1585
+ reason: new DOMException("Task cancelled by request.", "AbortError")
1586
+ });
1587
+ const canceledTask = applyUpdateToTask(data, {
1588
+ state: "canceled",
1589
+ message: {
1590
+ role: "agent",
1591
+ parts: [{
1592
+ kind: "text",
1593
+ text: "Task cancelled by request."
1594
+ }],
1595
+ kind: "message",
1596
+ messageId: crypto.randomUUID()
1597
+ }
1598
+ });
1599
+ try {
1600
+ await saveTaskAndMaybeSendPushNotification({
1601
+ taskStore,
1602
+ pushNotificationSender: resolvePushNotificationPair({ pushNotificationSender }).pushNotificationSender,
1603
+ previousTask: data,
1604
+ nextTask: canceledTask,
1605
+ agentId,
1606
+ expectedVersion: snapshot.version,
1607
+ logger
1608
+ });
1609
+ return createSuccessResponse(requestId, canceledTask);
1610
+ } catch (error) {
1611
+ if (!(error instanceof require_server_a2a_store.TaskStoreVersionConflictError)) throw error;
1612
+ } finally {
1613
+ taskStore.activeCancellations.delete(taskId);
1614
+ }
1615
+ }
1616
+ throw _mastra_core_a2a.MastraA2AError.invalidRequest(`Task ${taskId} was updated concurrently. Retry the request.`);
1198
1617
  }
1199
1618
  async function getAgentExecutionHandler({ requestId, mastra, agentId, requestContext, method, params, taskStore, pushNotificationStore, pushNotificationSender, logger, abortSignal }) {
1200
1619
  const agent = await require_server_handlers_agents.getAgentFromSystem({
@@ -1229,7 +1648,8 @@ async function getAgentExecutionHandler({ requestId, mastra, agentId, requestCon
1229
1648
  agent,
1230
1649
  agentId,
1231
1650
  logger,
1232
- requestContext
1651
+ requestContext,
1652
+ abortSignal
1233
1653
  });
1234
1654
  case "tasks/get": return await handleTaskGet({
1235
1655
  requestId,
@@ -1426,4 +1846,4 @@ Object.defineProperty(exports, "handleTaskResubscribe", {
1426
1846
  }
1427
1847
  });
1428
1848
 
1429
- //# sourceMappingURL=a2a-DKnwIgiw.cjs.map
1849
+ //# sourceMappingURL=a2a-D3ZYeXIx.cjs.map