@assistant-ui/react-a2a 0.2.35 → 0.2.37

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.
@@ -7,13 +7,24 @@ import type {
7
7
  ExportedMessageRepository,
8
8
  MessageStatus,
9
9
  ThreadAssistantMessage,
10
+ ThreadAssistantMessagePart,
10
11
  ThreadHistoryAdapter,
11
12
  ThreadMessage,
13
+ ToolCallMessagePart,
14
+ Unstable_RecordToolInteractionOptions,
12
15
  } from "@assistant-ui/core";
13
16
  import {
17
+ appendToolInteraction,
14
18
  createMessageRepositorySession,
15
19
  invokeUserCallback,
16
20
  } from "@assistant-ui/core/internal";
21
+ import type { ReadonlyJSONObject } from "assistant-stream/utils";
22
+ import {
23
+ applyA2uiOperations,
24
+ convertSurfaceToUISpec,
25
+ surfaceToOperations,
26
+ type A2uiState,
27
+ } from "@assistant-ui/react-generative-ui/a2ui";
17
28
  import type { A2AClient } from "./A2AClient";
18
29
  import type {
19
30
  A2AArtifact,
@@ -28,6 +39,7 @@ import type {
28
39
 
29
40
  import {
30
41
  a2aMessageToContent,
42
+ a2uiPartsToOperations,
31
43
  isTerminalTaskState,
32
44
  threadMessageToA2AMessage,
33
45
  taskStateToMessageStatus,
@@ -36,6 +48,14 @@ import {
36
48
  const INITIAL_AGENT_CARD_RETRY_DELAY_MS = 5_000;
37
49
  const MAX_AGENT_CARD_RETRY_DELAY_MS = 5 * 60_000;
38
50
 
51
+ const isRecord = (value: unknown): value is Record<string, unknown> =>
52
+ typeof value === "object" && value !== null && !Array.isArray(value);
53
+
54
+ const storedArtifact = (artifact: A2AArtifact): A2AArtifact => ({
55
+ ...artifact,
56
+ parts: artifact.parts.map(({ raw: _raw, ...part }) => part),
57
+ });
58
+
39
59
  export type A2AThreadRuntimeCoreOptions = {
40
60
  client: A2AClient;
41
61
  contextId?: string | undefined;
@@ -83,18 +103,23 @@ export class A2AThreadRuntimeCore {
83
103
  private readonly session = createMessageRepositorySession();
84
104
  private isRunningFlag = false;
85
105
  private abortController: AbortController | null = null;
106
+ private runGeneration = 0;
86
107
  private pendingError: Error | null = null;
87
108
 
88
109
  // A2A-specific state
89
110
  private currentTask: A2ATask | undefined;
90
111
  private currentArtifacts: A2AArtifact[] = [];
112
+ private a2uiState: A2uiState = new Map();
113
+ private readonly a2uiMessageIds = new Set<string>();
91
114
  private agentCardValue: A2AAgentCard | undefined;
92
115
 
93
116
  // History tracking
94
117
  private readonly assistantHistoryParents = new Map<string, string | null>();
95
118
  private readonly recordedHistoryIds = new Set<string>();
119
+ private readonly historyWrites = new Map<string, Promise<void>>();
96
120
  private _isLoading = false;
97
121
  private _loadPromise: Promise<void> | undefined;
122
+ private _historyLoadGeneration = 0;
98
123
  private _loadRequested = false;
99
124
  private _agentCardPromise: Promise<void> | undefined;
100
125
  private _agentCardRetryAfter = 0;
@@ -146,6 +171,8 @@ export class A2AThreadRuntimeCore {
146
171
  /** Thread-boundary reset: applyExternalMessages alone also serves branch
147
172
  * switches, deletes, and cancel resyncs, which must keep the live context. */
148
173
  resetContext(): void {
174
+ this._historyLoadGeneration++;
175
+ this._isLoading = false;
149
176
  // Restore the seed before aborting: an onCancel callback that starts a
150
177
  // new run must not pick up the old thread's context, and its controller
151
178
  // must not be discarded.
@@ -184,6 +211,54 @@ export class A2AThreadRuntimeCore {
184
211
  return this.session.export();
185
212
  }
186
213
 
214
+ public async recordToolInteraction({
215
+ messageId,
216
+ toolCallId,
217
+ interaction,
218
+ }: Unstable_RecordToolInteractionOptions): Promise<void> {
219
+ const message = this.session.tryGetMessage(messageId)?.message;
220
+ if (!message) {
221
+ throw new Error(
222
+ "Tried to record a tool interaction on a non-existing message",
223
+ );
224
+ }
225
+ if (message.role !== "assistant") {
226
+ throw new Error(
227
+ "Tried to record a tool interaction on a non-assistant message",
228
+ );
229
+ }
230
+ const target = message.content.find(
231
+ (part) => part.type === "tool-call" && part.toolCallId === toolCallId,
232
+ );
233
+ if (!target || target.type !== "tool-call") {
234
+ throw new Error(
235
+ "Tried to record a tool interaction on a non-existing tool call",
236
+ );
237
+ }
238
+
239
+ const touched = this.session.updateMessage(messageId, (current) => {
240
+ if (current.role !== "assistant") return current;
241
+ return {
242
+ ...current,
243
+ content: current.content.map((part) =>
244
+ part.type === "tool-call" && part.toolCallId === toolCallId
245
+ ? {
246
+ ...part,
247
+ unstable_interactions: appendToolInteraction(
248
+ part.unstable_interactions,
249
+ interaction,
250
+ ),
251
+ }
252
+ : part,
253
+ ),
254
+ };
255
+ });
256
+ if (touched) {
257
+ this.notifyUpdate();
258
+ this.persistAssistantHistory(messageId);
259
+ }
260
+ }
261
+
187
262
  getTask(): A2ATask | undefined {
188
263
  return this.currentTask;
189
264
  }
@@ -257,16 +332,19 @@ export class A2AThreadRuntimeCore {
257
332
 
258
333
  this._isLoading = true;
259
334
 
335
+ const generation = this._historyLoadGeneration;
260
336
  const historyPromise = this.history.load();
261
337
 
262
- this._loadPromise = Promise.all([historyPromise, agentCardPromise])
263
- .then(([repo]) => {
338
+ this._loadPromise = historyPromise
339
+ .then((repo) => {
340
+ if (generation !== this._historyLoadGeneration) return;
264
341
  if (repo) {
265
342
  this.session.applyExternalMessageRepository(repo);
266
343
  this.finalizeExternalApply();
267
344
  }
268
345
  })
269
346
  .catch((error) => {
347
+ if (generation !== this._historyLoadGeneration) return;
270
348
  invokeRuntimeCallback(
271
349
  "onError",
272
350
  this.onError,
@@ -274,6 +352,7 @@ export class A2AThreadRuntimeCore {
274
352
  );
275
353
  })
276
354
  .finally(() => {
355
+ if (generation !== this._historyLoadGeneration) return;
277
356
  this._isLoading = false;
278
357
  this.notifyUpdate();
279
358
  });
@@ -305,6 +384,14 @@ export class A2AThreadRuntimeCore {
305
384
  await this.startRun(threadMessage);
306
385
  }
307
386
 
387
+ appendVoiceTranscript(message: ThreadMessage): void {
388
+ const parentId = this.session.headId;
389
+ this.session.addOrUpdateMessage(parentId, message);
390
+ this.session.switchToBranch(message.id);
391
+ this.notifyUpdate();
392
+ this.recordHistoryEntry(parentId, message);
393
+ }
394
+
308
395
  async edit(message: AppendMessage): Promise<void> {
309
396
  await this.append(message);
310
397
  }
@@ -328,14 +415,26 @@ export class A2AThreadRuntimeCore {
328
415
  async cancel(): Promise<void> {
329
416
  if (!this.abortController) return;
330
417
 
418
+ // Read the server target before aborting: the abort listener runs the
419
+ // onCancel callback synchronously, which may clear the thread and with it
420
+ // the task this cancellation is for, or start a new run.
421
+ const task = this.currentTask;
422
+ const generation = this.runGeneration;
423
+
331
424
  // Abort locally first so the stream stops immediately
332
425
  this.abortController.abort();
333
426
 
334
427
  // Then try to cancel the task on the server
335
- if (this.currentTask?.id) {
428
+ if (task?.id) {
336
429
  try {
337
- const updated = await this.client.cancelTask(this.currentTask.id);
338
- this.currentTask = updated;
430
+ const updated = await this.client.cancelTask(task.id);
431
+ // Only apply the response while nothing newer exists. A newer snapshot
432
+ // or a cleared thread replaces the task object; a follow-up run that
433
+ // has not emitted yet keeps it, so the run generation is what rules
434
+ // that case out.
435
+ if (this.currentTask === task && this.runGeneration === generation) {
436
+ this.currentTask = updated;
437
+ }
339
438
  } catch {
340
439
  // Server cancel failed; local abort already handled
341
440
  }
@@ -366,6 +465,8 @@ export class A2AThreadRuntimeCore {
366
465
  }
367
466
  this.currentTask = undefined;
368
467
  this.currentArtifacts = [];
468
+ this.a2uiState = new Map();
469
+ this.a2uiMessageIds.clear();
369
470
  this.notifyUpdate();
370
471
  }
371
472
 
@@ -405,6 +506,8 @@ export class A2AThreadRuntimeCore {
405
506
  // --- Run logic ---
406
507
 
407
508
  private async startRun(userThreadMessage: ThreadMessage): Promise<void> {
509
+ this.runGeneration++;
510
+
408
511
  // Cancel any in-progress run before starting a new one
409
512
  if (this.abortController) {
410
513
  this.abortController.abort();
@@ -429,6 +532,8 @@ export class A2AThreadRuntimeCore {
429
532
  }
430
533
 
431
534
  this.currentArtifacts = [];
535
+ this.a2uiState = new Map();
536
+ this.a2uiMessageIds.clear();
432
537
 
433
538
  const assistantParentId = userThreadMessage.id;
434
539
  const assistantId = this.insertAssistantPlaceholder(assistantParentId);
@@ -561,7 +666,7 @@ export class A2AThreadRuntimeCore {
561
666
  this.handleStatusUpdate(assistantId, event.event);
562
667
  break;
563
668
  case "artifactUpdate":
564
- this.handleArtifactUpdate(event.event);
669
+ this.handleArtifactUpdate(assistantId, event.event);
565
670
  break;
566
671
  case "message":
567
672
  this.handleMessage(assistantId, event.message);
@@ -591,6 +696,7 @@ export class A2AThreadRuntimeCore {
591
696
  }
592
697
 
593
698
  if (event.status.message) {
699
+ this.applyA2uiMessage(event.status.message);
594
700
  const content = a2aMessageToContent(event.status.message);
595
701
  this.updateAssistantContent(assistantId, content);
596
702
  }
@@ -601,7 +707,10 @@ export class A2AThreadRuntimeCore {
601
707
  this.notifyUpdate();
602
708
  }
603
709
 
604
- private handleArtifactUpdate(event: A2ATaskArtifactUpdateEvent) {
710
+ private handleArtifactUpdate(
711
+ assistantId: string,
712
+ event: A2ATaskArtifactUpdateEvent,
713
+ ) {
605
714
  const { append, lastChunk } = event;
606
715
  const artifact = normalizeArtifact(event.artifact);
607
716
  const existingIdx = this.currentArtifacts.findIndex(
@@ -632,6 +741,10 @@ export class A2AThreadRuntimeCore {
632
741
  this.currentArtifacts = [...this.currentArtifacts, updated];
633
742
  }
634
743
 
744
+ this.applyA2uiParts(artifact.parts);
745
+ this.updateAssistantArtifacts(assistantId);
746
+ this.rebuildAssistantA2uiSurfaces(assistantId);
747
+
635
748
  if (lastChunk) {
636
749
  invokeRuntimeCallback(
637
750
  "onArtifactComplete",
@@ -646,6 +759,7 @@ export class A2AThreadRuntimeCore {
646
759
  private handleMessage(assistantId: string, message: A2AMessage) {
647
760
  if (message.role !== "agent") return;
648
761
 
762
+ this.applyA2uiMessage(message);
649
763
  const content = a2aMessageToContent(message);
650
764
  this.updateAssistantContent(assistantId, content);
651
765
  this.notifyUpdate();
@@ -673,13 +787,43 @@ export class A2AThreadRuntimeCore {
673
787
  if (task.contextId) {
674
788
  this.contextId = task.contextId;
675
789
  }
676
- if (artifacts) {
790
+ const isCompleteSnapshot = history !== undefined && artifacts !== undefined;
791
+ const artifactsToApply = isCompleteSnapshot
792
+ ? artifacts
793
+ : artifacts?.filter(
794
+ (artifact) =>
795
+ !this.currentArtifacts.some(
796
+ ({ artifactId }) => artifactId === artifact.artifactId,
797
+ ),
798
+ );
799
+
800
+ if (isCompleteSnapshot) {
677
801
  this.currentArtifacts = artifacts;
802
+ this.a2uiState = new Map();
803
+ this.a2uiMessageIds.clear();
804
+ } else if (artifactsToApply) {
805
+ this.currentArtifacts = [...this.currentArtifacts, ...artifactsToApply];
806
+ }
807
+
808
+ for (const message of history ?? []) {
809
+ if (message.role === "agent") this.applyA2uiMessage(message, true);
810
+ }
811
+ for (const artifact of artifactsToApply ?? []) {
812
+ this.applyA2uiParts(artifact.parts);
813
+ }
814
+ if (task.status.message) {
815
+ this.applyA2uiMessage(task.status.message, true);
816
+ }
817
+
818
+ if (isCompleteSnapshot || artifactsToApply?.length) {
819
+ this.updateAssistantArtifacts(assistantId);
678
820
  }
679
821
 
680
822
  if (task.status.message) {
681
823
  const content = a2aMessageToContent(task.status.message);
682
824
  this.updateAssistantContent(assistantId, content);
825
+ } else {
826
+ this.rebuildAssistantA2uiSurfaces(assistantId);
683
827
  }
684
828
 
685
829
  const status = taskStateToMessageStatus(task.status.state);
@@ -718,10 +862,124 @@ export class A2AThreadRuntimeCore {
718
862
  ) {
719
863
  this.session.updateMessage(messageId, (message) => {
720
864
  if (message.role !== "assistant") return message;
721
- return { ...message, content };
865
+ return {
866
+ ...message,
867
+ content: this.withA2uiSurfaces(content, message.content),
868
+ };
722
869
  });
723
870
  }
724
871
 
872
+ private applyA2uiMessage(message: A2AMessage, replay = false) {
873
+ if (message.messageId) {
874
+ if (replay && this.a2uiMessageIds.has(message.messageId)) return;
875
+ this.a2uiMessageIds.add(message.messageId);
876
+ }
877
+ this.applyA2uiParts(message.parts);
878
+ }
879
+
880
+ private applyA2uiParts(parts: readonly A2AMessage["parts"][number][]) {
881
+ const operations = a2uiPartsToOperations(parts);
882
+ if (operations.length === 0) return;
883
+ this.a2uiState = applyA2uiOperations(this.a2uiState, operations).state;
884
+ }
885
+
886
+ private a2uiSurfaceParts(): ThreadAssistantMessagePart[] {
887
+ const parts: ThreadAssistantMessagePart[] = [];
888
+ for (const [surfaceId, surface] of this.a2uiState) {
889
+ const { spec } = convertSurfaceToUISpec(surface);
890
+ if (!spec) continue;
891
+ parts.push({
892
+ type: "tool-call",
893
+ toolCallId: `a2ui:${surfaceId}`,
894
+ toolName: "present",
895
+ args: spec as unknown as ReadonlyJSONObject,
896
+ argsText: JSON.stringify(spec),
897
+ result: {},
898
+ artifact: { a2ui: surfaceToOperations(surface) },
899
+ });
900
+ }
901
+ return parts;
902
+ }
903
+
904
+ private withA2uiSurfaces(
905
+ content: ThreadAssistantMessage["content"],
906
+ previousContent: ThreadAssistantMessage["content"] = content,
907
+ ): ThreadAssistantMessage["content"] {
908
+ const preserved = content.filter(
909
+ (part) =>
910
+ !(part.type === "tool-call" && part.toolCallId.startsWith("a2ui:")),
911
+ );
912
+ const interactionSource = [...previousContent, ...content];
913
+ return this.withPreservedToolInteractions(interactionSource, [
914
+ ...preserved,
915
+ ...this.a2uiSurfaceParts(),
916
+ ]);
917
+ }
918
+
919
+ private withPreservedToolInteractions(
920
+ previousContent: ThreadAssistantMessage["content"],
921
+ content: ThreadAssistantMessage["content"],
922
+ ): ThreadAssistantMessage["content"] {
923
+ const interactions = new Map<
924
+ string,
925
+ ToolCallMessagePart["unstable_interactions"]
926
+ >();
927
+ for (const part of previousContent) {
928
+ if (
929
+ part.type === "tool-call" &&
930
+ part.unstable_interactions !== undefined
931
+ ) {
932
+ interactions.set(part.toolCallId, part.unstable_interactions);
933
+ }
934
+ }
935
+ if (interactions.size === 0) return content;
936
+ return content.map((part) => {
937
+ if (
938
+ part.type !== "tool-call" ||
939
+ part.unstable_interactions !== undefined
940
+ ) {
941
+ return part;
942
+ }
943
+ const unstable_interactions = interactions.get(part.toolCallId);
944
+ return unstable_interactions === undefined
945
+ ? part
946
+ : { ...part, unstable_interactions };
947
+ });
948
+ }
949
+
950
+ private rebuildAssistantA2uiSurfaces(messageId: string) {
951
+ const touched = this.session.updateMessage(messageId, (message) => {
952
+ if (message.role !== "assistant") return message;
953
+ return {
954
+ ...message,
955
+ content: this.withA2uiSurfaces(message.content),
956
+ };
957
+ });
958
+ if (touched) this.notifyUpdate();
959
+ }
960
+
961
+ private updateAssistantArtifacts(messageId: string) {
962
+ const touched = this.session.updateMessage(messageId, (message) => {
963
+ if (message.role !== "assistant") return message;
964
+ const custom = message.metadata.custom;
965
+ const a2a = isRecord(custom.a2a) ? custom.a2a : {};
966
+ return {
967
+ ...message,
968
+ metadata: {
969
+ ...message.metadata,
970
+ custom: {
971
+ ...custom,
972
+ a2a: {
973
+ ...a2a,
974
+ artifacts: this.currentArtifacts.map(storedArtifact),
975
+ },
976
+ },
977
+ },
978
+ };
979
+ });
980
+ if (touched) this.notifyUpdate();
981
+ }
982
+
725
983
  private updateAssistantStatus(messageId: string, status: MessageStatus) {
726
984
  const touched = this.session.updateMessage(messageId, (message) => {
727
985
  if (message.role !== "assistant") return message;
@@ -729,7 +987,7 @@ export class A2AThreadRuntimeCore {
729
987
  });
730
988
  if (touched) {
731
989
  this.notifyUpdate();
732
- if (status.type === "complete" || status.type === "incomplete") {
990
+ if (this.isPersistableAssistantStatus(status)) {
733
991
  this.persistAssistantHistory(messageId);
734
992
  }
735
993
  }
@@ -741,6 +999,14 @@ export class A2AThreadRuntimeCore {
741
999
  return msg.status;
742
1000
  }
743
1001
 
1002
+ private isPersistableAssistantStatus(status: MessageStatus): boolean {
1003
+ return (
1004
+ status.type === "complete" ||
1005
+ status.type === "incomplete" ||
1006
+ (status.type === "requires-action" && status.reason === "interrupt")
1007
+ );
1008
+ }
1009
+
744
1010
  // --- Lifecycle helpers ---
745
1011
 
746
1012
  private setRunning(running: boolean) {
@@ -757,7 +1023,9 @@ export class A2AThreadRuntimeCore {
757
1023
  // --- History persistence ---
758
1024
 
759
1025
  private recordHistoryEntry(parentId: string | null, message: ThreadMessage) {
760
- this.appendHistoryItem(parentId, message);
1026
+ void this.appendHistoryItem(parentId, message)?.catch((error) => {
1027
+ console.error("[react-a2a] failed to append history entry", error);
1028
+ });
761
1029
  }
762
1030
 
763
1031
  private markPendingAssistantHistory(
@@ -770,24 +1038,107 @@ export class A2AThreadRuntimeCore {
770
1038
 
771
1039
  private persistAssistantHistory(messageId: string) {
772
1040
  if (!this.history) return;
1041
+ const messageData = this.session.tryGetMessage(messageId);
773
1042
  const parentId = this.assistantHistoryParents.get(messageId);
774
- if (parentId === undefined) return;
775
- const message = this.session.tryGetMessage(messageId)?.message;
1043
+ if (parentId === undefined && !this.recordedHistoryIds.has(messageId)) {
1044
+ return;
1045
+ }
1046
+ const resolvedParentId =
1047
+ parentId === undefined ? messageData?.parentId : parentId;
1048
+ if (resolvedParentId === undefined) return;
1049
+ const message = messageData?.message;
776
1050
  if (!message || message.role !== "assistant") return;
777
- if (
778
- message.status?.type !== "complete" &&
779
- message.status?.type !== "incomplete"
780
- )
1051
+ if (!this.isPersistableAssistantStatus(message.status)) return;
1052
+ const isPausing = message.status.type === "requires-action";
1053
+
1054
+ if (isPausing && !this.history.update) return;
1055
+
1056
+ if (this.recordedHistoryIds.has(messageId)) {
1057
+ if (this.history.update) {
1058
+ const update = this.history.update.bind(this.history);
1059
+ const write = this.chainHistoryWrite(messageId, () =>
1060
+ update({ parentId: resolvedParentId, message }),
1061
+ );
1062
+ if (!isPausing) {
1063
+ this.assistantHistoryParents.delete(messageId);
1064
+ }
1065
+ void write.then(
1066
+ () => {
1067
+ this.recordedHistoryIds.add(messageId);
1068
+ },
1069
+ (error) => {
1070
+ const pending = this.historyWrites.get(messageId);
1071
+ if (pending === undefined || pending === write) {
1072
+ this.assistantHistoryParents.set(messageId, resolvedParentId);
1073
+ }
1074
+ console.error("[react-a2a] failed to update history entry", error);
1075
+ },
1076
+ );
1077
+ return;
1078
+ }
1079
+ if (!isPausing) {
1080
+ this.assistantHistoryParents.delete(messageId);
1081
+ }
781
1082
  return;
782
- this.assistantHistoryParents.delete(messageId);
783
- this.appendHistoryItem(parentId, message);
1083
+ }
1084
+ const write = this.appendHistoryItem(resolvedParentId, message);
1085
+ if (!write) return;
1086
+ if (!isPausing) {
1087
+ this.assistantHistoryParents.delete(messageId);
1088
+ }
1089
+ void write.catch((error) => {
1090
+ const pending = this.historyWrites.get(messageId);
1091
+ if (pending === undefined || pending === write) {
1092
+ this.assistantHistoryParents.set(messageId, resolvedParentId);
1093
+ }
1094
+ console.error("[react-a2a] failed to append history entry", error);
1095
+ });
784
1096
  }
785
1097
 
786
- private appendHistoryItem(parentId: string | null, message: ThreadMessage) {
1098
+ private appendHistoryItem(
1099
+ parentId: string | null,
1100
+ message: ThreadMessage,
1101
+ ): Promise<void> | undefined {
787
1102
  if (!this.history || this.recordedHistoryIds.has(message.id)) return;
788
1103
  this.recordedHistoryIds.add(message.id);
789
- void this.history.append({ parentId, message }).catch(() => {
1104
+ const append = this.history.append.bind(this.history);
1105
+ const write = this.chainHistoryWrite(message.id, () =>
1106
+ append({ parentId, message }),
1107
+ );
1108
+ void write.catch(() => {
790
1109
  this.recordedHistoryIds.delete(message.id);
791
1110
  });
1111
+ return write;
1112
+ }
1113
+
1114
+ private chainHistoryWrite(
1115
+ id: string,
1116
+ write: () => Promise<void>,
1117
+ ): Promise<void> {
1118
+ const pending = this.historyWrites.get(id);
1119
+ let next: Promise<void>;
1120
+ if (pending) {
1121
+ next = pending.then(write, write);
1122
+ } else {
1123
+ try {
1124
+ next = Promise.resolve(write());
1125
+ } catch (error) {
1126
+ next = Promise.reject(error);
1127
+ }
1128
+ }
1129
+ this.historyWrites.set(id, next);
1130
+ void next.then(
1131
+ () => {
1132
+ if (this.historyWrites.get(id) === next) {
1133
+ this.historyWrites.delete(id);
1134
+ }
1135
+ },
1136
+ () => {
1137
+ if (this.historyWrites.get(id) === next) {
1138
+ this.historyWrites.delete(id);
1139
+ }
1140
+ },
1141
+ );
1142
+ return next;
792
1143
  }
793
1144
  }