@atlaskit/editor-plugin-mentions 15.0.2 → 15.0.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,733 @@
1
+ import { SafePlugin } from '@atlaskit/editor-common/safe-plugin';
2
+ import { PluginKey } from '@atlaskit/editor-prosemirror/state';
3
+ import { isResolvingMentionProvider, MentionNameStatus } from '@atlaskit/mention/resource';
4
+ import { fg } from '@atlaskit/platform-feature-flags';
5
+ import { editorExperiment } from '@atlaskit/tmp-editor-statsig/experiments';
6
+ import { getAgentMentionParentContext } from './agent-mention-context';
7
+ import { mentionPluginKey } from './key';
8
+ import { ACTIONS } from './main';
9
+
10
+ // 'AGENT' is not in the ADF schema UserType enum but is used at runtime.
11
+
12
+ const AGENT_USER_TYPES = new Set(['APP', 'AGENT']);
13
+ const isAgentUserType = userType => {
14
+ return typeof userType === 'string' && AGENT_USER_TYPES.has(userType);
15
+ };
16
+ const getAgentMentionName = (text, fallbackName) => {
17
+ const trimmedFallbackName = typeof fallbackName === 'string' ? fallbackName.trim() : '';
18
+ const normalizedFallbackName = (trimmedFallbackName.startsWith('@') ? trimmedFallbackName.slice(1).trim() : trimmedFallbackName) || null;
19
+ if (typeof text !== 'string') {
20
+ return normalizedFallbackName;
21
+ }
22
+ const trimmedText = text.trim();
23
+ const displayName = trimmedText.startsWith('@') ? trimmedText.slice(1).trim() : trimmedText;
24
+ const normalizedName = displayName || normalizedFallbackName;
25
+ return normalizedName;
26
+ };
27
+ const AI_STREAMING_TRANSFORMATION_META_KEY = 'isAIStreamingTransformation';
28
+ const AGENT_MENTION_INACTIVITY_MS = 3000;
29
+ const MAX_PENDING_TYPED_AGENT_MENTION_FOCUS_DEFERS = 20;
30
+ /**
31
+ * Returns true when a transaction represents a local user document edit that
32
+ * should restart pending agent-mention inactivity tracking.
33
+ *
34
+ * Remote/collab updates, replace-document transactions, AI streaming transforms,
35
+ * selection-only movements, and metadata-only transactions are intentionally ignored.
36
+ */
37
+ const isQualifyingLocalUserDocChange = tr => {
38
+ const isAIStreaming = Boolean(tr.getMeta(AI_STREAMING_TRANSFORMATION_META_KEY));
39
+ return tr.docChanged && !tr.getMeta('isRemote') && !tr.getMeta('replaceDocument') && !isAIStreaming;
40
+ };
41
+ const isLocalSelectionChange = (tr, hasPositionChanged) => {
42
+ const isAIStreaming = Boolean(tr.getMeta(AI_STREAMING_TRANSFORMATION_META_KEY));
43
+
44
+ // Pressing Enter can move selection through a doc split without setting tr.selectionSet
45
+ // or changing from/to numerically, so local doc changes are checked against the
46
+ // pending mention's current parent before publishing.
47
+ return (hasPositionChanged || tr.docChanged) && !tr.getMeta('isRemote') && !tr.getMeta('replaceDocument') && !isAIStreaming;
48
+ };
49
+
50
+ /**
51
+ * Reads agent-mention details from a known document position without traversing
52
+ * the document. Callers pass a matcher so mapped positions are only accepted
53
+ * when they still point at the same pending/tracked mention.
54
+ */
55
+ const getAgentMentionDetailsAtPos = (state, pos, matchesMention, fallbackName) => {
56
+ var _parentNode$type$name;
57
+ if (pos < 0 || pos > state.doc.content.size) {
58
+ return null;
59
+ }
60
+ const node = state.doc.nodeAt(pos);
61
+ const mentionSchema = state.schema.nodes.mention;
62
+ if ((node === null || node === void 0 ? void 0 : node.type) !== mentionSchema || !isAgentUserType(node.attrs.userType) || !matchesMention(node.attrs) || !node.attrs.id || !node.attrs.localId) {
63
+ return null;
64
+ }
65
+ const $mentionPos = state.doc.resolve(Math.min(pos + node.nodeSize, state.doc.content.size));
66
+ const parentNode = $mentionPos.node($mentionPos.depth);
67
+ const id = node.attrs.id;
68
+ const name = getAgentMentionName(node.attrs.text, fallbackName);
69
+ return {
70
+ id,
71
+ localId: node.attrs.localId,
72
+ context: getAgentMentionParentContext(parentNode, node.attrs.localId),
73
+ name,
74
+ prompt: parentNode.textContent.trim() || null,
75
+ nodeSize: node.nodeSize,
76
+ parentEnd: $mentionPos.end($mentionPos.depth),
77
+ parentNodeType: (_parentNode$type$name = parentNode.type.name) !== null && _parentNode$type$name !== void 0 ? _parentNode$type$name : null,
78
+ parentStart: $mentionPos.start($mentionPos.depth),
79
+ pos
80
+ };
81
+ };
82
+
83
+ /**
84
+ * Finds an agent mention that survived a document change when the changed-range
85
+ * scan did not find one. Uses the tracked localId as the mention instance identity
86
+ * so same-agent mentions elsewhere in the document cannot be selected as fallback.
87
+ */
88
+ const getSurvivingAgentMentionDetails = (state, preferredLocalId, preferredName) => {
89
+ const mentionSchema = state.schema.nodes.mention;
90
+ let result = null;
91
+ state.doc.descendants((node, pos) => {
92
+ if (result) {
93
+ return false;
94
+ }
95
+ if (node.type !== mentionSchema || !isAgentUserType(node.attrs.userType) || node.attrs.localId !== preferredLocalId) {
96
+ return true;
97
+ }
98
+ result = getAgentMentionDetailsAtPos(state, pos, attrs => attrs.localId === preferredLocalId, preferredName);
99
+ return !result;
100
+ });
101
+ return result;
102
+ };
103
+
104
+ /**
105
+ * Maps a pending typed agent mention through a document-changing transaction and
106
+ * returns the updated pending state. If the mapped position was deleted or no
107
+ * longer points at the same local mention, the pending mention is cleared.
108
+ */
109
+ const getPendingTypedAgentMentionAfterDocChange = (state, tr, pendingTypedAgentMention, {
110
+ resetTimer
111
+ }) => {
112
+ const mappedPos = tr.mapping.mapResult(pendingTypedAgentMention.pos, 1);
113
+ const resetCount = resetTimer ? pendingTypedAgentMention.resetCount + 1 : pendingTypedAgentMention.resetCount;
114
+ if (mappedPos.deleted) {
115
+ return null;
116
+ }
117
+ const pendingMentionDetails = getAgentMentionDetailsAtPos(state, mappedPos.pos, attrs => attrs.localId === pendingTypedAgentMention.localId, pendingTypedAgentMention.name);
118
+ return pendingMentionDetails ? {
119
+ id: pendingMentionDetails.id,
120
+ localId: pendingTypedAgentMention.localId,
121
+ name: pendingMentionDetails.name,
122
+ nodeSize: pendingMentionDetails.nodeSize,
123
+ parentNodeType: pendingMentionDetails.parentNodeType,
124
+ pos: pendingMentionDetails.pos,
125
+ resetCount
126
+ } : null;
127
+ };
128
+ const hasPendingMentionMovedToNewParent = (oldState, tr, previousPendingTypedAgentMention, pendingMentionDetails) => {
129
+ if (!previousPendingTypedAgentMention) {
130
+ return false;
131
+ }
132
+ const previousMentionDetails = getAgentMentionDetailsAtPos(oldState, previousPendingTypedAgentMention.pos, attrs => attrs.localId === previousPendingTypedAgentMention.localId);
133
+
134
+ // Keep the previous parent boundary associated with the left side of an
135
+ // insertion at that boundary, so typing at the start of the parent does not
136
+ // look like the pending mention moved into a new parent.
137
+ const mappedPreviousParentStart = previousMentionDetails && tr.mapping.map(previousMentionDetails.parentStart, -1);
138
+ return Boolean(previousMentionDetails && mappedPreviousParentStart !== pendingMentionDetails.parentStart);
139
+ };
140
+ const isSelectionOutsideDirectParent = (state, pendingMentionDetails) => {
141
+ return state.selection.from < pendingMentionDetails.parentStart || state.selection.to > pendingMentionDetails.parentEnd;
142
+ };
143
+
144
+ /**
145
+ * Finalises a pending typed agent mention by copying its details into the
146
+ * public lastInserted* plugin state after the caller has already resolved the
147
+ * pending mention from the current document.
148
+ */
149
+ const commitResolvedPendingTypedAgentMention = (pluginState, pendingMentionDetails) => {
150
+ var _pluginState$lastAgen;
151
+ return {
152
+ hasPublicPluginStateChanged: true,
153
+ pluginState: {
154
+ ...pluginState,
155
+ pendingTypedAgentMention: null,
156
+ lastInsertedAgentMentionId: pendingMentionDetails.id,
157
+ lastInsertedAgentMentionLocalId: pendingMentionDetails.localId,
158
+ lastInsertedAgentMentionContext: pendingMentionDetails.context,
159
+ lastInsertedAgentMentionName: pendingMentionDetails.name,
160
+ lastInsertedAgentMentionPrompt: pendingMentionDetails.prompt,
161
+ lastInsertedAgentMentionParentNodeType: pendingMentionDetails.parentNodeType,
162
+ lastAgentMentionInsertionCount: ((_pluginState$lastAgen = pluginState.lastAgentMentionInsertionCount) !== null && _pluginState$lastAgen !== void 0 ? _pluginState$lastAgen : 0) + 1
163
+ }
164
+ };
165
+ };
166
+
167
+ /**
168
+ * Resolves and finalises a pending typed agent mention. If the tracked mention
169
+ * no longer resolves, the stale pending state is cleared without dispatching a
170
+ * public update.
171
+ */
172
+ const commitPendingTypedAgentMention = (state, pluginState, pendingTypedAgentMention) => {
173
+ const pendingMentionDetails = getAgentMentionDetailsAtPos(state, pendingTypedAgentMention.pos, attrs => attrs.localId === pendingTypedAgentMention.localId, pendingTypedAgentMention.name);
174
+ if (!pendingMentionDetails) {
175
+ return {
176
+ hasPublicPluginStateChanged: false,
177
+ pluginState: {
178
+ ...pluginState,
179
+ pendingTypedAgentMention: null
180
+ }
181
+ };
182
+ }
183
+ return commitResolvedPendingTypedAgentMention(pluginState, pendingMentionDetails);
184
+ };
185
+ const hasTrackedAgentMentionState = pluginState => Boolean(pluginState.pendingTypedAgentMention) || Boolean(pluginState.pendingPastedAgentMention) && fg('platform_editor_agent_mentions_drop_one_fixes') || pluginState.lastInsertedAgentMentionId != null || pluginState.lastInsertedAgentMentionLocalId != null || pluginState.lastInsertedAgentMentionContext != null || pluginState.lastInsertedAgentMentionName != null || pluginState.lastInsertedAgentMentionPrompt != null || pluginState.lastInsertedAgentMentionParentNodeType != null;
186
+
187
+ /**
188
+ * Clears agent mention state that points at a specific document snapshot.
189
+ * replaceDocument swaps content wholesale, so pending typed mentions and
190
+ * lastInserted* details from the previous document must be cleared together.
191
+ */
192
+ const clearTrackedAgentMentionState = pluginState => {
193
+ return {
194
+ ...pluginState,
195
+ pendingTypedAgentMention: null,
196
+ ...(fg('platform_editor_agent_mentions_drop_one_fixes') ? {
197
+ pendingPastedAgentMention: null
198
+ } : {}),
199
+ lastInsertedAgentMentionId: null,
200
+ lastInsertedAgentMentionLocalId: null,
201
+ lastInsertedAgentMentionContext: null,
202
+ lastInsertedAgentMentionName: null,
203
+ lastInsertedAgentMentionPrompt: null,
204
+ lastInsertedAgentMentionParentNodeType: null
205
+ };
206
+ };
207
+
208
+ /**
209
+ * Attempts to synchronously resolve an agent mention name from the mention
210
+ * provider's cache. Falls back to undefined if the provider doesn't support
211
+ * name resolution, the result is a Promise (async/cache miss), or the status
212
+ * is not OK.
213
+ */
214
+ const resolveCachedAgentMentionName = (mentionProvider, params, id) => {
215
+ if (params !== null && params !== void 0 && params.name || !isResolvingMentionProvider(mentionProvider)) {
216
+ var _params$name;
217
+ return (_params$name = params === null || params === void 0 ? void 0 : params.name) !== null && _params$name !== void 0 ? _params$name : undefined;
218
+ }
219
+ const result = mentionProvider.resolveMentionName(id);
220
+ if (!(result instanceof Promise) && result.status === MentionNameStatus.OK) {
221
+ return result.name || undefined;
222
+ }
223
+ return undefined;
224
+ };
225
+
226
+ /**
227
+ * Resolves the name of a pasted agent mention via the mention provider and
228
+ * dispatches the appropriate action to update plugin state.
229
+ */
230
+ export const resolveAndDispatchPastedAgentMentionName = (agentId, mentionProvider, pendingResolveMentionIds, view) => {
231
+ if (pendingResolveMentionIds.has(agentId)) {
232
+ return;
233
+ }
234
+ pendingResolveMentionIds.add(agentId);
235
+ const result = mentionProvider.resolveMentionName(agentId);
236
+ if (result instanceof Promise) {
237
+ result.then(nameDetails => {
238
+ pendingResolveMentionIds.delete(agentId);
239
+ if (nameDetails.status === MentionNameStatus.OK && nameDetails.name) {
240
+ view.dispatch(view.state.tr.setMeta(mentionPluginKey, {
241
+ action: ACTIONS.RESOLVE_PASTED_AGENT_MENTION_NAME,
242
+ params: {
243
+ id: agentId,
244
+ name: nameDetails.name
245
+ }
246
+ }));
247
+ } else {
248
+ view.dispatch(view.state.tr.setMeta(mentionPluginKey, {
249
+ action: ACTIONS.CLEAR_PENDING_PASTED_AGENT_MENTION,
250
+ params: {
251
+ id: agentId
252
+ }
253
+ }));
254
+ }
255
+ }).catch(() => {
256
+ pendingResolveMentionIds.delete(agentId);
257
+ });
258
+ } else if (result.status === MentionNameStatus.OK && result.name) {
259
+ pendingResolveMentionIds.delete(agentId);
260
+ view.dispatch(view.state.tr.setMeta(mentionPluginKey, {
261
+ action: ACTIONS.RESOLVE_PASTED_AGENT_MENTION_NAME,
262
+ params: {
263
+ id: agentId,
264
+ name: result.name
265
+ }
266
+ }));
267
+ } else {
268
+ pendingResolveMentionIds.delete(agentId);
269
+ view.dispatch(view.state.tr.setMeta(mentionPluginKey, {
270
+ action: ACTIONS.CLEAR_PENDING_PASTED_AGENT_MENTION,
271
+ params: {
272
+ id: agentId
273
+ }
274
+ }));
275
+ }
276
+ };
277
+ export const applyAgentMentionState = ({
278
+ tr,
279
+ pluginState,
280
+ oldState,
281
+ newState,
282
+ getIsRovoPanelOpen
283
+ }) => {
284
+ // mentionPluginKey intentionally remains the shared transaction action channel while
285
+ // agentMentionPluginKey owns the extracted agent state.
286
+ const {
287
+ action,
288
+ params
289
+ } = tr.getMeta(mentionPluginKey) || {
290
+ action: null,
291
+ params: null
292
+ };
293
+ let hasPublicPluginStateChanged = false;
294
+ let newPluginState = pluginState;
295
+ const isAgentMentionsExperimentEnabled = editorExperiment('platform_editor_agent_mentions', true);
296
+ const hasPositionChanged = oldState.selection.from !== newState.selection.from || oldState.selection.to !== newState.selection.to;
297
+ switch (action) {
298
+ case ACTIONS.COMMIT_PENDING_TYPED_AGENT_MENTION:
299
+ {
300
+ const pendingTypedAgentMention = newPluginState.pendingTypedAgentMention;
301
+ if (!isAgentMentionsExperimentEnabled || !pendingTypedAgentMention || pendingTypedAgentMention.localId !== (params === null || params === void 0 ? void 0 : params.localId) || pendingTypedAgentMention.resetCount !== (params === null || params === void 0 ? void 0 : params.resetCount)) {
302
+ break;
303
+ }
304
+ const commitResult = commitPendingTypedAgentMention(newState, newPluginState, pendingTypedAgentMention);
305
+ newPluginState = commitResult.pluginState;
306
+ hasPublicPluginStateChanged = hasPublicPluginStateChanged || commitResult.hasPublicPluginStateChanged;
307
+ break;
308
+ }
309
+ case ACTIONS.DISCARD_PENDING_TYPED_AGENT_MENTION:
310
+ {
311
+ const pendingTypedAgentMentionToDiscard = newPluginState.pendingTypedAgentMention;
312
+ if (!isAgentMentionsExperimentEnabled || !pendingTypedAgentMentionToDiscard || pendingTypedAgentMentionToDiscard.localId !== (params === null || params === void 0 ? void 0 : params.localId) || pendingTypedAgentMentionToDiscard.resetCount !== (params === null || params === void 0 ? void 0 : params.resetCount)) {
313
+ break;
314
+ }
315
+ newPluginState = {
316
+ ...newPluginState,
317
+ pendingTypedAgentMention: null
318
+ };
319
+ hasPublicPluginStateChanged = true;
320
+ break;
321
+ }
322
+ case ACTIONS.SET_PROVIDER:
323
+ newPluginState = {
324
+ ...newPluginState,
325
+ mentionProvider: params.provider
326
+ };
327
+ hasPublicPluginStateChanged = true;
328
+ break;
329
+ case ACTIONS.RESOLVE_PASTED_AGENT_MENTION_NAME:
330
+ {
331
+ var _newPluginState$pendi, _newPluginState$pendi2, _newPluginState$pendi3, _newPluginState$pendi4, _newPluginState$pendi5, _newPluginState$pendi6;
332
+ if (!isAgentMentionsExperimentEnabled || !(params !== null && params !== void 0 && params.id) || !(params !== null && params !== void 0 && params.name) || ((_newPluginState$pendi = newPluginState.pendingPastedAgentMention) === null || _newPluginState$pendi === void 0 ? void 0 : _newPluginState$pendi.id) !== params.id || !fg('platform_editor_agent_mentions_drop_one_fixes')) {
333
+ break;
334
+ }
335
+ newPluginState = {
336
+ ...newPluginState,
337
+ pendingPastedAgentMention: null,
338
+ lastInsertedAgentMentionId: (_newPluginState$pendi2 = newPluginState.pendingPastedAgentMention) === null || _newPluginState$pendi2 === void 0 ? void 0 : _newPluginState$pendi2.id,
339
+ lastInsertedAgentMentionLocalId: (_newPluginState$pendi3 = newPluginState.pendingPastedAgentMention) === null || _newPluginState$pendi3 === void 0 ? void 0 : _newPluginState$pendi3.localId,
340
+ lastInsertedAgentMentionContext: (_newPluginState$pendi4 = newPluginState.pendingPastedAgentMention) === null || _newPluginState$pendi4 === void 0 ? void 0 : _newPluginState$pendi4.context,
341
+ lastInsertedAgentMentionName: params.name,
342
+ lastInsertedAgentMentionPrompt: (_newPluginState$pendi5 = newPluginState.pendingPastedAgentMention) === null || _newPluginState$pendi5 === void 0 ? void 0 : _newPluginState$pendi5.prompt,
343
+ lastInsertedAgentMentionParentNodeType: (_newPluginState$pendi6 = newPluginState.pendingPastedAgentMention) === null || _newPluginState$pendi6 === void 0 ? void 0 : _newPluginState$pendi6.parentNodeType
344
+ };
345
+ hasPublicPluginStateChanged = true;
346
+ break;
347
+ }
348
+ case ACTIONS.CLEAR_PENDING_PASTED_AGENT_MENTION:
349
+ {
350
+ var _newPluginState$pendi7;
351
+ if (!isAgentMentionsExperimentEnabled || !(params !== null && params !== void 0 && params.id) || ((_newPluginState$pendi7 = newPluginState.pendingPastedAgentMention) === null || _newPluginState$pendi7 === void 0 ? void 0 : _newPluginState$pendi7.id) !== params.id || !fg('platform_editor_agent_mentions_drop_one_fixes')) {
352
+ break;
353
+ }
354
+ newPluginState = {
355
+ ...newPluginState,
356
+ pendingPastedAgentMention: null
357
+ };
358
+ break;
359
+ }
360
+ }
361
+ if (isAgentMentionsExperimentEnabled && isQualifyingLocalUserDocChange(tr)) {
362
+ const mentionSchema = newState.schema.nodes.mention;
363
+ const newDocRanges = [];
364
+ const oldDocRanges = [];
365
+ let stepsTouchMentions = false;
366
+ tr.steps.forEach(step => {
367
+ let found = false;
368
+ const stepNewRanges = [];
369
+ const stepOldRanges = [];
370
+ step.getMap().forEach((oldFrom, oldTo, newFrom, newTo) => {
371
+ stepOldRanges.push([oldFrom, oldTo]);
372
+ stepNewRanges.push([newFrom, newTo]);
373
+ if (!found) {
374
+ const clampedNewFrom = Math.min(newFrom, newState.doc.content.size);
375
+ const clampedNewTo = Math.min(newTo, newState.doc.content.size);
376
+ if (clampedNewFrom < clampedNewTo) {
377
+ newState.doc.nodesBetween(clampedNewFrom, clampedNewTo, node => {
378
+ if (node.type === mentionSchema && isAgentUserType(node.attrs.userType)) {
379
+ found = true;
380
+ }
381
+ return !found;
382
+ });
383
+ }
384
+ if (!found) {
385
+ const clampedOldFrom = Math.min(oldFrom, oldState.doc.content.size);
386
+ const clampedOldTo = Math.min(oldTo, oldState.doc.content.size);
387
+ if (clampedOldFrom < clampedOldTo) {
388
+ oldState.doc.nodesBetween(clampedOldFrom, clampedOldTo, node => {
389
+ if (node.type === mentionSchema && AGENT_USER_TYPES.has(node.attrs.userType)) {
390
+ found = true;
391
+ }
392
+ return !found;
393
+ });
394
+ }
395
+ }
396
+ }
397
+ });
398
+ if (found) {
399
+ stepsTouchMentions = true;
400
+ newDocRanges.push(...stepNewRanges);
401
+ oldDocRanges.push(...stepOldRanges);
402
+ }
403
+ });
404
+ const shouldResolveAgentMentionState = stepsTouchMentions || Boolean(newPluginState.lastInsertedAgentMentionLocalId);
405
+ if (shouldResolveAgentMentionState) {
406
+ var _newPluginState$lastI, _newPluginState$lastA, _newPluginState$lastI2, _newPluginState$lastI3, _newPluginState$lastI4, _newPluginState$lastI5, _newPluginState$lastI6;
407
+ let agentMentionId = null;
408
+ let agentMentionLocalId = null;
409
+ let agentMentionContext = null;
410
+ let agentMentionName = null;
411
+ let agentMentionPrompt = null;
412
+ let agentMentionParentNodeType = null;
413
+ const existingAgentMentionLocalIdsInChangedRanges = new Set();
414
+ let pendingTypedAgentMentionDetails = null;
415
+ if (stepsTouchMentions) {
416
+ for (const [from, to] of newDocRanges) {
417
+ const clampedTo = Math.min(to, newState.doc.content.size);
418
+ if (from >= clampedTo) {
419
+ continue;
420
+ }
421
+ newState.doc.nodesBetween(from, clampedTo, (node, pos) => {
422
+ if (node.type !== mentionSchema || !isAgentUserType(node.attrs.userType)) {
423
+ return true;
424
+ }
425
+ if (pendingTypedAgentMentionDetails === null && action === ACTIONS.SET_PENDING_TYPED_AGENT_MENTION && node.attrs.localId === (params === null || params === void 0 ? void 0 : params.localId)) {
426
+ pendingTypedAgentMentionDetails = getAgentMentionDetailsAtPos(newState, pos, attrs => attrs.localId === params.localId, params.name);
427
+ }
428
+ if (agentMentionLocalId === null && node.attrs.localId) {
429
+ const cachedName = fg('platform_editor_agent_mentions_drop_one_fixes') ? resolveCachedAgentMentionName(newPluginState.mentionProvider, params, node.attrs.id) : params === null || params === void 0 ? void 0 : params.name;
430
+ const agentMentionDetails = getAgentMentionDetailsAtPos(newState, pos, attrs => attrs.localId === node.attrs.localId, cachedName);
431
+ if (agentMentionDetails) {
432
+ agentMentionId = agentMentionDetails.id;
433
+ agentMentionLocalId = agentMentionDetails.localId;
434
+ agentMentionContext = agentMentionDetails.context;
435
+ agentMentionName = agentMentionDetails.name;
436
+ agentMentionPrompt = agentMentionDetails.prompt;
437
+ agentMentionParentNodeType = agentMentionDetails.parentNodeType;
438
+ }
439
+ }
440
+ return true;
441
+ });
442
+ }
443
+ for (const [from, to] of oldDocRanges) {
444
+ const clampedOldTo = Math.min(to, oldState.doc.content.size);
445
+ if (from >= clampedOldTo) {
446
+ continue;
447
+ }
448
+ oldState.doc.nodesBetween(from, clampedOldTo, node => {
449
+ if (node.type !== mentionSchema || !isAgentUserType(node.attrs.userType)) {
450
+ return true;
451
+ }
452
+ if (node.attrs.localId) {
453
+ existingAgentMentionLocalIdsInChangedRanges.add(node.attrs.localId);
454
+ }
455
+ return true;
456
+ });
457
+ }
458
+ }
459
+ let resolvedFromFullDocFallback = false;
460
+ if (agentMentionId === null && newPluginState.lastInsertedAgentMentionLocalId) {
461
+ const survivorDetails = getSurvivingAgentMentionDetails(newState, newPluginState.lastInsertedAgentMentionLocalId, newPluginState.lastInsertedAgentMentionName);
462
+ if (survivorDetails) {
463
+ agentMentionId = survivorDetails.id;
464
+ agentMentionLocalId = survivorDetails.localId;
465
+ agentMentionContext = survivorDetails.context;
466
+ agentMentionName = survivorDetails.name;
467
+ agentMentionPrompt = survivorDetails.prompt;
468
+ agentMentionParentNodeType = survivorDetails.parentNodeType;
469
+ resolvedFromFullDocFallback = true;
470
+ }
471
+ }
472
+ const trackedAgentMentionLocalId = (_newPluginState$lastI = newPluginState.lastInsertedAgentMentionLocalId) !== null && _newPluginState$lastI !== void 0 ? _newPluginState$lastI : null;
473
+ const changedRangeMentionIsNew = agentMentionLocalId !== null && !existingAgentMentionLocalIdsInChangedRanges.has(agentMentionLocalId);
474
+ if (agentMentionLocalId !== null && !changedRangeMentionIsNew && agentMentionLocalId !== trackedAgentMentionLocalId) {
475
+ const survivorDetails = trackedAgentMentionLocalId ? getSurvivingAgentMentionDetails(newState, trackedAgentMentionLocalId, newPluginState.lastInsertedAgentMentionName) : null;
476
+ if (survivorDetails) {
477
+ agentMentionId = survivorDetails.id;
478
+ agentMentionLocalId = survivorDetails.localId;
479
+ agentMentionContext = survivorDetails.context;
480
+ agentMentionName = survivorDetails.name;
481
+ agentMentionParentNodeType = survivorDetails.parentNodeType;
482
+ resolvedFromFullDocFallback = true;
483
+ } else {
484
+ agentMentionId = null;
485
+ agentMentionLocalId = null;
486
+ agentMentionContext = null;
487
+ agentMentionName = null;
488
+ agentMentionParentNodeType = null;
489
+ }
490
+ }
491
+ const isNewInsertion = agentMentionId !== null && !resolvedFromFullDocFallback && changedRangeMentionIsNew;
492
+ const isPendingTypedAgentMentionInsertion = isNewInsertion && action === ACTIONS.SET_PENDING_TYPED_AGENT_MENTION && typeof (params === null || params === void 0 ? void 0 : params.localId) === 'string';
493
+ const newInsertionCount = isNewInsertion ? ((_newPluginState$lastA = newPluginState.lastAgentMentionInsertionCount) !== null && _newPluginState$lastA !== void 0 ? _newPluginState$lastA : 0) + 1 : undefined;
494
+ const pendingTypedAgentMentionDetailsForState = pendingTypedAgentMentionDetails;
495
+ if (isPendingTypedAgentMentionInsertion && pendingTypedAgentMentionDetailsForState) {
496
+ const pendingTypedAgentMentionLocalId = params === null || params === void 0 ? void 0 : params.localId;
497
+ newPluginState = {
498
+ ...newPluginState,
499
+ pendingTypedAgentMention: {
500
+ id: pendingTypedAgentMentionDetailsForState.id,
501
+ localId: pendingTypedAgentMentionLocalId,
502
+ name: pendingTypedAgentMentionDetailsForState.name,
503
+ nodeSize: pendingTypedAgentMentionDetailsForState.nodeSize,
504
+ parentNodeType: pendingTypedAgentMentionDetailsForState.parentNodeType,
505
+ pos: pendingTypedAgentMentionDetailsForState.pos,
506
+ resetCount: 1
507
+ }
508
+ };
509
+ } else if (isPendingTypedAgentMentionInsertion) {
510
+ newPluginState = {
511
+ ...newPluginState,
512
+ pendingTypedAgentMention: null,
513
+ lastInsertedAgentMentionId: agentMentionId,
514
+ lastInsertedAgentMentionLocalId: agentMentionLocalId,
515
+ lastInsertedAgentMentionContext: agentMentionContext,
516
+ lastInsertedAgentMentionName: agentMentionName,
517
+ lastInsertedAgentMentionPrompt: agentMentionPrompt,
518
+ lastInsertedAgentMentionParentNodeType: agentMentionParentNodeType,
519
+ ...(newInsertionCount !== undefined ? {
520
+ lastAgentMentionInsertionCount: newInsertionCount
521
+ } : {})
522
+ };
523
+ hasPublicPluginStateChanged = true;
524
+ } else if (agentMentionId !== ((_newPluginState$lastI2 = newPluginState.lastInsertedAgentMentionId) !== null && _newPluginState$lastI2 !== void 0 ? _newPluginState$lastI2 : null) || agentMentionLocalId !== ((_newPluginState$lastI3 = newPluginState.lastInsertedAgentMentionLocalId) !== null && _newPluginState$lastI3 !== void 0 ? _newPluginState$lastI3 : null) || agentMentionName !== ((_newPluginState$lastI4 = newPluginState.lastInsertedAgentMentionName) !== null && _newPluginState$lastI4 !== void 0 ? _newPluginState$lastI4 : null) || agentMentionPrompt !== ((_newPluginState$lastI5 = newPluginState.lastInsertedAgentMentionPrompt) !== null && _newPluginState$lastI5 !== void 0 ? _newPluginState$lastI5 : null) || agentMentionParentNodeType !== ((_newPluginState$lastI6 = newPluginState.lastInsertedAgentMentionParentNodeType) !== null && _newPluginState$lastI6 !== void 0 ? _newPluginState$lastI6 : null) || newInsertionCount !== undefined) {
525
+ const isTaskItemMentionForPaste = agentMentionParentNodeType === 'taskItem';
526
+ if (!isTaskItemMentionForPaste && isNewInsertion && getIsRovoPanelOpen !== null && getIsRovoPanelOpen !== void 0 && getIsRovoPanelOpen()) {
527
+ // Rovo is already open, so do not publish a nudge-triggering state change.
528
+ } else if (agentMentionName === null && agentMentionId !== null && isResolvingMentionProvider(newPluginState.mentionProvider) && fg('platform_editor_agent_mentions_drop_one_fixes')) {
529
+ newPluginState = {
530
+ ...newPluginState,
531
+ pendingPastedAgentMention: {
532
+ id: agentMentionId,
533
+ localId: agentMentionLocalId,
534
+ context: agentMentionContext,
535
+ prompt: agentMentionPrompt,
536
+ parentNodeType: agentMentionParentNodeType
537
+ },
538
+ ...(newInsertionCount !== undefined ? {
539
+ lastAgentMentionInsertionCount: newInsertionCount
540
+ } : {})
541
+ };
542
+ } else {
543
+ newPluginState = {
544
+ ...newPluginState,
545
+ lastInsertedAgentMentionId: agentMentionId,
546
+ lastInsertedAgentMentionLocalId: agentMentionLocalId,
547
+ lastInsertedAgentMentionContext: agentMentionContext,
548
+ lastInsertedAgentMentionName: agentMentionName,
549
+ lastInsertedAgentMentionPrompt: agentMentionPrompt,
550
+ lastInsertedAgentMentionParentNodeType: agentMentionParentNodeType,
551
+ ...(newInsertionCount !== undefined ? {
552
+ lastAgentMentionInsertionCount: newInsertionCount
553
+ } : {})
554
+ };
555
+ hasPublicPluginStateChanged = true;
556
+ }
557
+ }
558
+ }
559
+ }
560
+ if (isAgentMentionsExperimentEnabled && tr.docChanged && tr.getMeta('replaceDocument') && hasTrackedAgentMentionState(newPluginState)) {
561
+ newPluginState = clearTrackedAgentMentionState(newPluginState);
562
+ hasPublicPluginStateChanged = true;
563
+ }
564
+ if (isAgentMentionsExperimentEnabled && newPluginState.pendingTypedAgentMention && action !== ACTIONS.SET_PENDING_TYPED_AGENT_MENTION && action !== ACTIONS.COMMIT_PENDING_TYPED_AGENT_MENTION && tr.docChanged) {
565
+ newPluginState = {
566
+ ...newPluginState,
567
+ pendingTypedAgentMention: getPendingTypedAgentMentionAfterDocChange(newState, tr, newPluginState.pendingTypedAgentMention, {
568
+ resetTimer: isQualifyingLocalUserDocChange(tr)
569
+ })
570
+ };
571
+ }
572
+ const shouldCheckPendingTypedAgentMentionParent = isLocalSelectionChange(tr, hasPositionChanged);
573
+ if (isAgentMentionsExperimentEnabled && newPluginState.pendingTypedAgentMention && action !== ACTIONS.SET_PENDING_TYPED_AGENT_MENTION && action !== ACTIONS.COMMIT_PENDING_TYPED_AGENT_MENTION && shouldCheckPendingTypedAgentMentionParent) {
574
+ const pendingTypedAgentMention = newPluginState.pendingTypedAgentMention;
575
+ const pendingMentionDetails = getAgentMentionDetailsAtPos(newState, pendingTypedAgentMention.pos, attrs => attrs.localId === pendingTypedAgentMention.localId, pendingTypedAgentMention.name);
576
+ if (!pendingMentionDetails) {
577
+ newPluginState = {
578
+ ...newPluginState,
579
+ pendingTypedAgentMention: null
580
+ };
581
+ } else if (hasPendingMentionMovedToNewParent(oldState, tr, pluginState.pendingTypedAgentMention, pendingMentionDetails) || isSelectionOutsideDirectParent(newState, pendingMentionDetails)) {
582
+ if (pendingMentionDetails.parentNodeType !== 'taskItem' && getIsRovoPanelOpen !== null && getIsRovoPanelOpen !== void 0 && getIsRovoPanelOpen()) {
583
+ newPluginState = {
584
+ ...newPluginState,
585
+ pendingTypedAgentMention: null
586
+ };
587
+ hasPublicPluginStateChanged = true;
588
+ } else {
589
+ const commitResult = commitResolvedPendingTypedAgentMention(newPluginState, pendingMentionDetails);
590
+ newPluginState = commitResult.pluginState;
591
+ hasPublicPluginStateChanged = hasPublicPluginStateChanged || commitResult.hasPublicPluginStateChanged;
592
+ }
593
+ }
594
+ }
595
+ return {
596
+ hasPublicPluginStateChanged,
597
+ pluginState: newPluginState
598
+ };
599
+ };
600
+ export const createAgentMentionPluginView = ({
601
+ editorView,
602
+ getIsRovoPanelOpen,
603
+ stateKey
604
+ }) => {
605
+ const isAgentMentionsEnabled = editorExperiment('platform_editor_agent_mentions', true);
606
+ let pendingTypedAgentMentionTimer;
607
+ let pendingTypedAgentMentionTimerKey = null;
608
+ let pendingTypedAgentMentionFocusDeferCount = 0;
609
+ const pendingResolveMentionIds = new Set();
610
+ const clearPendingTypedAgentMentionTimer = ({
611
+ preserveFocusDeferCount = false
612
+ } = {}) => {
613
+ if (pendingTypedAgentMentionTimer) {
614
+ clearTimeout(pendingTypedAgentMentionTimer);
615
+ pendingTypedAgentMentionTimer = undefined;
616
+ }
617
+ pendingTypedAgentMentionTimerKey = null;
618
+ if (!preserveFocusDeferCount) {
619
+ pendingTypedAgentMentionFocusDeferCount = 0;
620
+ }
621
+ };
622
+ const shouldDeferPendingTypedAgentMention = () => typeof document !== 'undefined' && document.hasFocus() && !editorView.hasFocus();
623
+ const schedulePendingTypedAgentMentionTimer = (mentionPluginState, {
624
+ preserveFocusDeferCount = false
625
+ } = {}) => {
626
+ if (!isAgentMentionsEnabled) {
627
+ clearPendingTypedAgentMentionTimer();
628
+ return;
629
+ }
630
+ const pendingTypedAgentMention = mentionPluginState === null || mentionPluginState === void 0 ? void 0 : mentionPluginState.pendingTypedAgentMention;
631
+ if (!pendingTypedAgentMention) {
632
+ clearPendingTypedAgentMentionTimer();
633
+ return;
634
+ }
635
+ const timerKey = `${pendingTypedAgentMention.localId}:${pendingTypedAgentMention.resetCount}`;
636
+ if (timerKey === pendingTypedAgentMentionTimerKey) {
637
+ return;
638
+ }
639
+ clearPendingTypedAgentMentionTimer({
640
+ preserveFocusDeferCount
641
+ });
642
+ pendingTypedAgentMentionTimerKey = timerKey;
643
+ pendingTypedAgentMentionTimer = setTimeout(() => {
644
+ var _stateKey$getState;
645
+ const isTaskItemMention = pendingTypedAgentMention.parentNodeType === 'taskItem';
646
+ if (!isTaskItemMention && getIsRovoPanelOpen !== null && getIsRovoPanelOpen !== void 0 && getIsRovoPanelOpen()) {
647
+ // Agent actions continue to use mentionPluginKey as the shared transaction meta channel.
648
+ editorView.dispatch(editorView.state.tr.setMeta(mentionPluginKey, {
649
+ action: ACTIONS.DISCARD_PENDING_TYPED_AGENT_MENTION,
650
+ params: {
651
+ localId: pendingTypedAgentMention.localId,
652
+ resetCount: pendingTypedAgentMention.resetCount
653
+ }
654
+ }));
655
+ return;
656
+ }
657
+ const latestPendingTypedAgentMention = (_stateKey$getState = stateKey.getState(editorView.state)) === null || _stateKey$getState === void 0 ? void 0 : _stateKey$getState.pendingTypedAgentMention;
658
+ if (!latestPendingTypedAgentMention || latestPendingTypedAgentMention.localId !== pendingTypedAgentMention.localId || latestPendingTypedAgentMention.resetCount !== pendingTypedAgentMention.resetCount) {
659
+ return;
660
+ }
661
+ if (shouldDeferPendingTypedAgentMention() && pendingTypedAgentMentionFocusDeferCount < MAX_PENDING_TYPED_AGENT_MENTION_FOCUS_DEFERS) {
662
+ pendingTypedAgentMentionFocusDeferCount++;
663
+ pendingTypedAgentMentionTimerKey = null;
664
+ schedulePendingTypedAgentMentionTimer(stateKey.getState(editorView.state), {
665
+ preserveFocusDeferCount: true
666
+ });
667
+ return;
668
+ }
669
+ pendingTypedAgentMentionFocusDeferCount = 0;
670
+ editorView.dispatch(editorView.state.tr.setMeta(mentionPluginKey, {
671
+ action: ACTIONS.COMMIT_PENDING_TYPED_AGENT_MENTION,
672
+ params: {
673
+ localId: pendingTypedAgentMention.localId,
674
+ resetCount: pendingTypedAgentMention.resetCount
675
+ }
676
+ }));
677
+ }, AGENT_MENTION_INACTIVITY_MS);
678
+ };
679
+ return {
680
+ update(view, prevState) {
681
+ var _mentionPluginState$p;
682
+ const mentionPluginState = stateKey.getState(view.state);
683
+ if (mentionPluginState === stateKey.getState(prevState)) {
684
+ return;
685
+ }
686
+ schedulePendingTypedAgentMentionTimer(mentionPluginState);
687
+ if (editorExperiment('platform_editor_agent_mentions', true) && (mentionPluginState === null || mentionPluginState === void 0 ? void 0 : (_mentionPluginState$p = mentionPluginState.pendingPastedAgentMention) === null || _mentionPluginState$p === void 0 ? void 0 : _mentionPluginState$p.id) != null && mentionPluginState !== null && mentionPluginState !== void 0 && mentionPluginState.mentionProvider && isResolvingMentionProvider(mentionPluginState.mentionProvider) && fg('platform_editor_agent_mentions_drop_one_fixes')) {
688
+ resolveAndDispatchPastedAgentMentionName(mentionPluginState.pendingPastedAgentMention.id, mentionPluginState.mentionProvider, pendingResolveMentionIds, view);
689
+ }
690
+ },
691
+ destroy() {
692
+ clearPendingTypedAgentMentionTimer();
693
+ }
694
+ };
695
+ };
696
+ export const agentMentionPluginKey = new PluginKey('agentMention');
697
+ export const createAgentMentionPlugin = ({
698
+ options,
699
+ pmPluginFactoryParams
700
+ }) => {
701
+ return new SafePlugin({
702
+ key: agentMentionPluginKey,
703
+ state: {
704
+ init() {
705
+ return {};
706
+ },
707
+ apply(tr, pluginState, oldState, newState) {
708
+ const result = applyAgentMentionState({
709
+ tr,
710
+ pluginState,
711
+ oldState,
712
+ newState,
713
+ getIsRovoPanelOpen: options === null || options === void 0 ? void 0 : options.getIsRovoPanelOpen
714
+ });
715
+ const mentionPluginState = mentionPluginKey.getState(newState);
716
+ if (result.hasPublicPluginStateChanged) {
717
+ pmPluginFactoryParams.dispatch(mentionPluginKey, {
718
+ ...mentionPluginState,
719
+ ...result.pluginState
720
+ });
721
+ }
722
+ return result.pluginState;
723
+ }
724
+ },
725
+ view(editorView) {
726
+ return createAgentMentionPluginView({
727
+ editorView,
728
+ getIsRovoPanelOpen: options === null || options === void 0 ? void 0 : options.getIsRovoPanelOpen,
729
+ stateKey: agentMentionPluginKey
730
+ });
731
+ }
732
+ });
733
+ };