UNPKG

@atlaskit/editor-plugin-mentions

Version:

Mentions plugin for @atlaskit/editor-core

1,032 lines 60.3 kB
import _slicedToArray from "@babel/runtime/helpers/slicedToArray";
import _defineProperty from "@babel/runtime/helpers/defineProperty";
function _createForOfIteratorHelper(r, e) { var t = "undefined" != typeof Symbol && r[Symbol.iterator] || r["@@iterator"]; if (!t) { if (Array.isArray(r) || (t = _unsupportedIterableToArray(r)) || e && r && "number" == typeof r.length) { t && (r = t); var _n = 0, F = function F() {}; return { s: F, n: function n() { return _n >= r.length ? { done: !0 } : { done: !1, value: r[_n++] }; }, e: function e(r) { throw r; }, f: F }; } throw new TypeError("Invalid attempt to iterate non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method."); } var o, a = !0, u = !1; return { s: function s() { t = t.call(r); }, n: function n() { var r = t.next(); return a = r.done, r; }, e: function e(r) { u = !0, o = r; }, f: function f() { try { a || null == t.return || t.return(); } finally { if (u) throw o; } } }; }
function _unsupportedIterableToArray(r, a) { if (r) { if ("string" == typeof r) return _arrayLikeToArray(r, a); var t = {}.toString.call(r).slice(8, -1); return "Object" === t && r.constructor && (t = r.constructor.name), "Map" === t || "Set" === t ? Array.from(r) : "Arguments" === t || /^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(t) ? _arrayLikeToArray(r, a) : void 0; } }
function _arrayLikeToArray(r, a) { (null == a || a > r.length) && (a = r.length); for (var e = 0, n = Array(a); e < a; e++) n[e] = r[e]; return n; }
function ownKeys(e, r) { var t = Object.keys(e); if (Object.getOwnPropertySymbols) { var o = Object.getOwnPropertySymbols(e); r && (o = o.filter(function (r) { return Object.getOwnPropertyDescriptor(e, r).enumerable; })), t.push.apply(t, o); } return t; }
function _objectSpread(e) { for (var r = 1; r < arguments.length; r++) { var t = null != arguments[r] ? arguments[r] : {}; r % 2 ? ownKeys(Object(t), !0).forEach(function (r) { _defineProperty(e, r, t[r]); }) : Object.getOwnPropertyDescriptors ? Object.defineProperties(e, Object.getOwnPropertyDescriptors(t)) : ownKeys(Object(t)).forEach(function (r) { Object.defineProperty(e, r, Object.getOwnPropertyDescriptor(t, r)); }); } return e; }
import { ACTION, ACTION_SUBJECT, ACTION_SUBJECT_ID, EVENT_TYPE } from '@atlaskit/editor-common/analytics';
import { SafePlugin } from '@atlaskit/editor-common/safe-plugin';
import { DecorationSet } from '@atlaskit/editor-prosemirror/view';
import { insm } from '@atlaskit/insm';
import { isResolvingMentionProvider, MentionNameStatus, SLI_EVENT_TYPE, SMART_EVENT_TYPE } from '@atlaskit/mention/resource';
import { ComponentNames } from '@atlaskit/mention/types';
import { isExperimentEnabled } from '@atlaskit/platform-feature-experiments/is-experiment-enabled';
import { fg } from '@atlaskit/platform-feature-flags/fg';
import { editorExperiment } from '@atlaskit/tmp-editor-statsig/experiments';
import { MentionNodeView } from '../nodeviews/mentionNodeView';
import { MENTION_PROVIDER_REJECTED, MENTION_PROVIDER_UNDEFINED } from '../types';
import { getAgentMentionParentContext } from './agent-mention-context';
import { nextRunStateDecorations } from './agent-run-state';
import { mentionPluginKey } from './key';
import { canMentionBeCreatedInRange } from './utils';
export var ACTIONS = {
  COMMIT_PENDING_TYPED_AGENT_MENTION: 'COMMIT_PENDING_TYPED_AGENT_MENTION',
  DISCARD_PENDING_TYPED_AGENT_MENTION: 'DISCARD_PENDING_TYPED_AGENT_MENTION',
  SET_PENDING_TYPED_AGENT_MENTION: 'SET_PENDING_TYPED_AGENT_MENTION',
  SET_PROVIDER: 'SET_PROVIDER',
  /**
   * Dispatched by the `setAgentMentionRunStates` action to push the product's run-state
   * map into plugin state, which drives the run-state node decorations.
   */
  SET_AGENT_RUN_STATES: 'SET_AGENT_RUN_STATES',
  /**
   * Dispatched from view().update() when async resolveMentionName() resolves
   * for a pasted agent mention.
   * Updates lastInsertedAgentMentionName so the nudge shows the correct name.
   */
  RESOLVE_PASTED_AGENT_MENTION_NAME: 'RESOLVE_PASTED_AGENT_MENTION_NAME',
  /**
   * Dispatched from view().update() when async resolveMentionName() resolves
   * with a non-OK status for a pasted agent mention.
   * Clears pendingPastedAgentMention so no retry loop occurs.
   */
  CLEAR_PENDING_PASTED_AGENT_MENTION: 'CLEAR_PENDING_PASTED_AGENT_MENTION'
};

// 'AGENT' is not in the ADF schema UserType enum but is used at runtime.

var AGENT_USER_TYPES = new Set(['APP', 'AGENT']);
var isAgentUserType = function isAgentUserType(userType) {
  return typeof userType === 'string' && AGENT_USER_TYPES.has(userType);
};
var getAgentMentionName = function getAgentMentionName(text, fallbackName) {
  var trimmedFallbackName = typeof fallbackName === 'string' ? fallbackName.trim() : '';
  var normalizedFallbackName = (trimmedFallbackName.startsWith('@') ? trimmedFallbackName.slice(1).trim() : trimmedFallbackName) || null;
  if (typeof text !== 'string') {
    return normalizedFallbackName;
  }
  var trimmedText = text.trim();
  var displayName = trimmedText.startsWith('@') ? trimmedText.slice(1).trim() : trimmedText;
  var normalizedName = displayName || normalizedFallbackName;
  return normalizedName;
};
var AI_STREAMING_TRANSFORMATION_META_KEY = 'isAIStreamingTransformation';
var AGENT_MENTION_INACTIVITY_MS = 3000;
var MAX_PENDING_TYPED_AGENT_MENTION_FOCUS_DEFERS = 20;
var PACKAGE_NAME = "@atlaskit/editor-plugin-mentions";
var PACKAGE_VERSION = "18.0.6";
var setProvider = function setProvider(provider) {
  return function (state, dispatch) {
    if (dispatch) {
      dispatch(state.tr.setMeta(mentionPluginKey, {
        action: ACTIONS.SET_PROVIDER,
        params: {
          provider: provider
        }
      }));
    }
    return true;
  };
};

/**
 * Returns true when a transaction represents a local user document edit that
 * should restart pending agent-mention inactivity tracking.
 *
 * Remote/collab updates, replace-document transactions, AI streaming transforms,
 * selection-only movements, and metadata-only transactions are intentionally ignored.
 */
var isQualifyingLocalUserDocChange = function isQualifyingLocalUserDocChange(tr) {
  var isAIStreaming = Boolean(tr.getMeta(AI_STREAMING_TRANSFORMATION_META_KEY));
  return tr.docChanged && !tr.getMeta('isRemote') && !tr.getMeta('replaceDocument') && !isAIStreaming;
};
var isLocalSelectionChange = function isLocalSelectionChange(tr, hasPositionChanged) {
  var isAIStreaming = Boolean(tr.getMeta(AI_STREAMING_TRANSFORMATION_META_KEY));

  // Pressing Enter can move selection through a doc split without setting tr.selectionSet
  // or changing from/to numerically, so local doc changes are checked against the
  // pending mention's current parent before publishing.
  return (hasPositionChanged || tr.docChanged) && !tr.getMeta('isRemote') && !tr.getMeta('replaceDocument') && !isAIStreaming;
};

/**
 * Reads agent-mention details from a known document position without traversing
 * the document. Callers pass a matcher so mapped positions are only accepted
 * when they still point at the same pending/tracked mention.
 */
var getAgentMentionDetailsAtPos = function getAgentMentionDetailsAtPos(state, pos, matchesMention, fallbackName) {
  var _parentNode$type$name;
  if (pos < 0 || pos > state.doc.content.size) {
    return null;
  }
  var node = state.doc.nodeAt(pos);
  var mentionSchema = state.schema.nodes.mention;
  if ((node === null || node === void 0 ? void 0 : node.type) !== mentionSchema || !isAgentUserType(node.attrs.userType) || !matchesMention(node.attrs) || !node.attrs.id || !node.attrs.localId) {
    return null;
  }
  var $mentionPos = state.doc.resolve(Math.min(pos + node.nodeSize, state.doc.content.size));
  var parentNode = $mentionPos.node($mentionPos.depth);
  var id = node.attrs.id;
  var name = getAgentMentionName(node.attrs.text, fallbackName);
  return {
    id: id,
    localId: node.attrs.localId,
    context: getAgentMentionParentContext(parentNode, node.attrs.localId),
    name: name,
    prompt: parentNode.textContent.trim() || null,
    nodeSize: node.nodeSize,
    parentEnd: $mentionPos.end($mentionPos.depth),
    parentNodeType: (_parentNode$type$name = parentNode.type.name) !== null && _parentNode$type$name !== void 0 ? _parentNode$type$name : null,
    parentStart: $mentionPos.start($mentionPos.depth),
    pos: pos
  };
};

/**
 * Finds an agent mention that survived a document change when the changed-range
 * scan did not find one. Uses the tracked localId as the mention instance identity
 * so same-agent mentions elsewhere in the document cannot be selected as fallback.
 */
var getSurvivingAgentMentionDetails = function getSurvivingAgentMentionDetails(state, preferredLocalId, preferredName) {
  var mentionSchema = state.schema.nodes.mention;
  var result = null;
  state.doc.descendants(function (node, pos) {
    if (result) {
      return false;
    }
    if (node.type !== mentionSchema || !isAgentUserType(node.attrs.userType) || node.attrs.localId !== preferredLocalId) {
      return true;
    }
    result = getAgentMentionDetailsAtPos(state, pos, function (attrs) {
      return attrs.localId === preferredLocalId;
    }, preferredName);
    return !result;
  });
  return result;
};

/**
 * Maps a pending typed agent mention through a document-changing transaction and
 * returns the updated pending state. If the mapped position was deleted or no
 * longer points at the same local mention, the pending mention is cleared.
 */
var getPendingTypedAgentMentionAfterDocChange = function getPendingTypedAgentMentionAfterDocChange(state, tr, pendingTypedAgentMention, _ref) {
  var resetTimer = _ref.resetTimer;
  var mappedPos = tr.mapping.mapResult(pendingTypedAgentMention.pos, 1);
  var resetCount = resetTimer ? pendingTypedAgentMention.resetCount + 1 : pendingTypedAgentMention.resetCount;
  if (mappedPos.deleted) {
    return null;
  }
  var pendingMentionDetails = getAgentMentionDetailsAtPos(state, mappedPos.pos, function (attrs) {
    return attrs.localId === pendingTypedAgentMention.localId;
  }, pendingTypedAgentMention.name);
  return pendingMentionDetails ? {
    id: pendingMentionDetails.id,
    localId: pendingTypedAgentMention.localId,
    name: pendingMentionDetails.name,
    nodeSize: pendingMentionDetails.nodeSize,
    parentNodeType: pendingMentionDetails.parentNodeType,
    pos: pendingMentionDetails.pos,
    resetCount: resetCount
  } : null;
};
var hasPendingMentionMovedToNewParent = function hasPendingMentionMovedToNewParent(oldState, tr, previousPendingTypedAgentMention, pendingMentionDetails) {
  if (!previousPendingTypedAgentMention) {
    return false;
  }
  var previousMentionDetails = getAgentMentionDetailsAtPos(oldState, previousPendingTypedAgentMention.pos, function (attrs) {
    return attrs.localId === previousPendingTypedAgentMention.localId;
  });

  // Keep the previous parent boundary associated with the left side of an
  // insertion at that boundary, so typing at the start of the parent does not
  // look like the pending mention moved into a new parent.
  var mappedPreviousParentStart = previousMentionDetails && tr.mapping.map(previousMentionDetails.parentStart, -1);
  return Boolean(previousMentionDetails && mappedPreviousParentStart !== pendingMentionDetails.parentStart);
};
var isSelectionOutsideDirectParent = function isSelectionOutsideDirectParent(state, pendingMentionDetails) {
  return state.selection.from < pendingMentionDetails.parentStart || state.selection.to > pendingMentionDetails.parentEnd;
};

/**
 * Finalises a pending typed agent mention by copying its details into the
 * public lastInserted* plugin state after the caller has already resolved the
 * pending mention from the current document.
 */
var commitResolvedPendingTypedAgentMention = function commitResolvedPendingTypedAgentMention(pluginState, pendingMentionDetails) {
  var _pluginState$lastAgen;
  return {
    hasPublicPluginStateChanged: true,
    pluginState: _objectSpread(_objectSpread({}, pluginState), {}, {
      pendingTypedAgentMention: null,
      lastInsertedAgentMentionId: pendingMentionDetails.id,
      lastInsertedAgentMentionLocalId: pendingMentionDetails.localId,
      lastInsertedAgentMentionContext: pendingMentionDetails.context,
      lastInsertedAgentMentionName: pendingMentionDetails.name,
      lastInsertedAgentMentionPrompt: pendingMentionDetails.prompt,
      lastInsertedAgentMentionParentNodeType: pendingMentionDetails.parentNodeType,
      lastAgentMentionInsertionCount: ((_pluginState$lastAgen = pluginState.lastAgentMentionInsertionCount) !== null && _pluginState$lastAgen !== void 0 ? _pluginState$lastAgen : 0) + 1
    })
  };
};

/**
 * Resolves and finalises a pending typed agent mention. If the tracked mention
 * no longer resolves, the stale pending state is cleared without dispatching a
 * public update.
 */
var commitPendingTypedAgentMention = function commitPendingTypedAgentMention(state, pluginState, pendingTypedAgentMention) {
  var pendingMentionDetails = getAgentMentionDetailsAtPos(state, pendingTypedAgentMention.pos, function (attrs) {
    return attrs.localId === pendingTypedAgentMention.localId;
  }, pendingTypedAgentMention.name);
  if (!pendingMentionDetails) {
    return {
      hasPublicPluginStateChanged: false,
      pluginState: _objectSpread(_objectSpread({}, pluginState), {}, {
        pendingTypedAgentMention: null
      })
    };
  }
  return commitResolvedPendingTypedAgentMention(pluginState, pendingMentionDetails);
};
var hasTrackedAgentMentionState = function hasTrackedAgentMentionState(pluginState) {
  return 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;
};

/**
 * Clears agent mention state that points at a specific document snapshot.
 * replaceDocument swaps content wholesale, so pending typed mentions and
 * lastInserted* details from the previous document must be cleared together.
 */
var clearTrackedAgentMentionState = function clearTrackedAgentMentionState(pluginState) {
  return _objectSpread(_objectSpread(_objectSpread({}, pluginState), {}, {
    pendingTypedAgentMention: null
  }, fg('platform_editor_agent_mentions_drop_one_fixes') ? {
    pendingPastedAgentMention: null
  } : {}), {}, {
    lastInsertedAgentMentionId: null,
    lastInsertedAgentMentionLocalId: null,
    lastInsertedAgentMentionContext: null,
    lastInsertedAgentMentionName: null,
    lastInsertedAgentMentionPrompt: null,
    lastInsertedAgentMentionParentNodeType: null
  });
};

/**
 * Attempts to synchronously resolve an agent mention name from the mention
 * provider's cache. Falls back to undefined if the provider doesn't support
 * name resolution, the result is a Promise (async/cache miss), or the status
 * is not OK.
 */
var resolveCachedAgentMentionName = function resolveCachedAgentMentionName(mentionProvider, params, id) {
  if (params !== null && params !== void 0 && params.name || !isResolvingMentionProvider(mentionProvider)) {
    var _params$name;
    return (_params$name = params === null || params === void 0 ? void 0 : params.name) !== null && _params$name !== void 0 ? _params$name : undefined;
  }
  var result = mentionProvider.resolveMentionName(id);
  if (!(result instanceof Promise) && result.status === MentionNameStatus.OK) {
    return result.name || undefined;
  }
  return undefined;
};

/**
 * Resolves the name of a pasted agent mention via the mention provider and
 * dispatches the appropriate action to update plugin state.
 *
 * Handles both async (Promise) and synchronous cache-hit results:
 * - On OK resolution  → dispatches RESOLVE_PASTED_AGENT_MENTION_NAME so the nudge
 *   shows the correct agent name.
 * - On non-OK result  → dispatches CLEAR_PENDING_PASTED_AGENT_MENTION to prevent
 *   an infinite retry loop on subsequent update() calls.
 *
 * Guards against duplicate in-flight requests via pendingResolveMentionIds.
 */
export var resolveAndDispatchPastedAgentMentionName = function resolveAndDispatchPastedAgentMentionName(agentId, mentionProvider, pendingResolveMentionIds, view) {
  if (pendingResolveMentionIds.has(agentId)) {
    return;
  }
  pendingResolveMentionIds.add(agentId);
  var result = mentionProvider.resolveMentionName(agentId);
  if (result instanceof Promise) {
    result.then(function (nameDetails) {
      pendingResolveMentionIds.delete(agentId);
      if (nameDetails.status === MentionNameStatus.OK && nameDetails.name) {
        view.dispatch(view.state.tr.setMeta(mentionPluginKey, {
          action: ACTIONS.RESOLVE_PASTED_AGENT_MENTION_NAME,
          params: {
            id: agentId,
            name: nameDetails.name
          }
        }));
      } else {
        // Non-OK status — clear pendingPastedAgentMention to prevent
        // an infinite retry loop on subsequent update() calls.
        view.dispatch(view.state.tr.setMeta(mentionPluginKey, {
          action: ACTIONS.CLEAR_PENDING_PASTED_AGENT_MENTION,
          params: {
            id: agentId
          }
        }));
      }
    });
  } else if (result.status === MentionNameStatus.OK && result.name) {
    pendingResolveMentionIds.delete(agentId);
    // Synchronous hit on a second update cycle (e.g. provider warmed up
    // between the paste transaction and this view update).
    view.dispatch(view.state.tr.setMeta(mentionPluginKey, {
      action: ACTIONS.RESOLVE_PASTED_AGENT_MENTION_NAME,
      params: {
        id: agentId,
        name: result.name
      }
    }));
  } else {
    pendingResolveMentionIds.delete(agentId);
    // Non-OK synchronous status — clear pendingPastedAgentMention to prevent
    // an infinite retry loop on subsequent update() calls.
    view.dispatch(view.state.tr.setMeta(mentionPluginKey, {
      action: ACTIONS.CLEAR_PENDING_PASTED_AGENT_MENTION,
      params: {
        id: agentId
      }
    }));
  }
};
export function createMentionPlugin(_ref2) {
  var pmPluginFactoryParams = _ref2.pmPluginFactoryParams,
    fireEvent = _ref2.fireEvent,
    options = _ref2.options,
    api = _ref2.api;
  var mentionProvider;
  var sendAnalytics = function sendAnalytics(event, actionSubject, action, attributes) {
    if (event === SLI_EVENT_TYPE || event === SMART_EVENT_TYPE) {
      fireEvent({
        action: action,
        actionSubject: actionSubject,
        eventType: EVENT_TYPE.OPERATIONAL,
        attributes: _objectSpread({
          packageName: PACKAGE_NAME,
          packageVersion: PACKAGE_VERSION,
          componentName: ComponentNames.MENTION
        }, attributes)
      }, 'fabricElements');
    }
  };
  return new SafePlugin({
    key: mentionPluginKey,
    state: {
      init: function init(_, state) {
        var canInsertMention = canMentionBeCreatedInRange(state.selection.from, state.selection.to)(state);
        return {
          canInsertMention: canInsertMention
        };
      },
      apply: function apply(tr, pluginState, oldState, newState) {
        var _params$runStateByLoc;
        var _ref3 = tr.getMeta(mentionPluginKey) || {
            action: null,
            params: null
          },
          action = _ref3.action,
          params = _ref3.params;
        var hasPublicPluginStateChanged = false;
        var newPluginState = pluginState;
        var hasPositionChanged = oldState.selection.from !== newState.selection.from || oldState.selection.to !== newState.selection.to;
        if (tr.docChanged || tr.selectionSet && hasPositionChanged) {
          newPluginState = _objectSpread(_objectSpread({}, pluginState), {}, {
            canInsertMention: canMentionBeCreatedInRange(newState.selection.from, newState.selection.to)(newState)
          });
          hasPublicPluginStateChanged = true;
        }
        switch (action) {
          case ACTIONS.SET_PROVIDER:
            newPluginState = _objectSpread(_objectSpread({}, newPluginState), {}, {
              mentionProvider: params.provider
            });
            hasPublicPluginStateChanged = true;
            break;
        }
        var nextDecorations = nextRunStateDecorations(newPluginState.runStateDecorations, tr, newState, action === ACTIONS.SET_AGENT_RUN_STATES ? (_params$runStateByLoc = params === null || params === void 0 ? void 0 : params.runStateByLocalId) !== null && _params$runStateByLoc !== void 0 ? _params$runStateByLoc : {} : undefined);
        if (nextDecorations !== newPluginState.runStateDecorations) {
          newPluginState = _objectSpread(_objectSpread({}, newPluginState), {}, {
            runStateDecorations: nextDecorations
          });
        }
        if (!isExperimentEnabled('platform_editor_agent_mentions_separate_pm_plugin') && editorExperiment('platform_editor_agent_mentions', true)) {
          switch (action) {
            case ACTIONS.COMMIT_PENDING_TYPED_AGENT_MENTION:
              {
                var pendingTypedAgentMention = newPluginState.pendingTypedAgentMention;
                // Ignore stale timer callbacks. The localId and resetCount must still match the
                // current pending mention so older timers cannot publish after later user edits.
                if (!pendingTypedAgentMention || pendingTypedAgentMention.localId !== (params === null || params === void 0 ? void 0 : params.localId) || pendingTypedAgentMention.resetCount !== (params === null || params === void 0 ? void 0 : params.resetCount)) {
                  break;
                }
                var commitResult = commitPendingTypedAgentMention(newState, newPluginState, pendingTypedAgentMention);
                newPluginState = commitResult.pluginState;
                hasPublicPluginStateChanged = hasPublicPluginStateChanged || commitResult.hasPublicPluginStateChanged;
                break;
              }
            case ACTIONS.DISCARD_PENDING_TYPED_AGENT_MENTION:
              {
                // Silently discard the pending typed agent mention without committing it.
                var pendingTypedAgentMentionToDiscard = newPluginState.pendingTypedAgentMention;
                if (!pendingTypedAgentMentionToDiscard || pendingTypedAgentMentionToDiscard.localId !== (params === null || params === void 0 ? void 0 : params.localId) || pendingTypedAgentMentionToDiscard.resetCount !== (params === null || params === void 0 ? void 0 : params.resetCount)) {
                  break;
                }
                newPluginState = _objectSpread(_objectSpread({}, newPluginState), {}, {
                  pendingTypedAgentMention: null
                });
                hasPublicPluginStateChanged = true;
                break;
              }
            case ACTIONS.RESOLVE_PASTED_AGENT_MENTION_NAME:
              {
                var _newPluginState$pendi, _newPluginState$pendi2, _newPluginState$pendi3, _newPluginState$pendi4, _newPluginState$pendi5, _newPluginState$pendi6;
                // Guard: only apply if there is a matching pending pasted mention (staleness check).
                if (!(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')) {
                  break;
                }
                newPluginState = _objectSpread(_objectSpread({}, newPluginState), {}, {
                  pendingPastedAgentMention: null,
                  lastInsertedAgentMentionId: (_newPluginState$pendi2 = newPluginState.pendingPastedAgentMention) === null || _newPluginState$pendi2 === void 0 ? void 0 : _newPluginState$pendi2.id,
                  lastInsertedAgentMentionLocalId: (_newPluginState$pendi3 = newPluginState.pendingPastedAgentMention) === null || _newPluginState$pendi3 === void 0 ? void 0 : _newPluginState$pendi3.localId,
                  lastInsertedAgentMentionContext: (_newPluginState$pendi4 = newPluginState.pendingPastedAgentMention) === null || _newPluginState$pendi4 === void 0 ? void 0 : _newPluginState$pendi4.context,
                  lastInsertedAgentMentionName: params.name,
                  lastInsertedAgentMentionPrompt: (_newPluginState$pendi5 = newPluginState.pendingPastedAgentMention) === null || _newPluginState$pendi5 === void 0 ? void 0 : _newPluginState$pendi5.prompt,
                  lastInsertedAgentMentionParentNodeType: (_newPluginState$pendi6 = newPluginState.pendingPastedAgentMention) === null || _newPluginState$pendi6 === void 0 ? void 0 : _newPluginState$pendi6.parentNodeType
                });
                hasPublicPluginStateChanged = true;
                break;
              }
            case ACTIONS.CLEAR_PENDING_PASTED_AGENT_MENTION:
              {
                var _newPluginState$pendi7;
                if (!(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')) {
                  break;
                }
                // resolveMentionName() returned a non-OK status — clear the pending state.
                newPluginState = _objectSpread(_objectSpread({}, newPluginState), {}, {
                  pendingPastedAgentMention: null
                });
                break;
              }
          }
        }

        // When the agent mentions experiment is off, dispatch immediately (original behaviour).
        // When it's on, defer dispatch to after the agent tracking block below so that
        // agent-mention state changes are included in the notification.
        if (hasPublicPluginStateChanged && (isExperimentEnabled('platform_editor_agent_mentions_separate_pm_plugin') || !editorExperiment('platform_editor_agent_mentions', true))) {
          pmPluginFactoryParams.dispatch(mentionPluginKey, newPluginState);
        }
        if (options !== null && options !== void 0 && options.handleMentionsChanged && tr.docChanged) {
          var _insm$session, _insm$session2;
          (_insm$session = insm.session) === null || _insm$session === void 0 || _insm$session.startFeature('mentionDeletionDetection');
          var mentionSchema = newState.schema.nodes.mention;
          var mentionsRemoved = new Map();
          tr.steps.forEach(function (step, index) {
            step.getMap().forEach(function (from, to) {
              var newStart = tr.mapping.slice(index).map(from, -1);
              var newEnd = tr.mapping.slice(index).map(to);
              var oldStart = tr.mapping.invert().map(newStart, -1);
              var oldEnd = tr.mapping.invert().map(newEnd);
              var oldSlice = oldState.doc.slice(oldStart, oldEnd);
              var newSlice = newState.doc.slice(newStart, newEnd);
              var mentionsBefore = new Map();
              var mentionsAfter = new Map();
              oldSlice.content.descendants(function (node) {
                if (node.type.name === mentionSchema.name && node.attrs.localId) {
                  mentionsBefore.set(node.attrs.localId, {
                    id: node.attrs.id,
                    localId: node.attrs.localId
                  });
                }
              });
              newSlice.content.descendants(function (node) {
                if (node.type.name === mentionSchema.name && node.attrs.localId) {
                  mentionsAfter.set(node.attrs.localId, {
                    id: node.attrs.id,
                    localId: node.attrs.localId
                  });
                }
              });

              // Determine which mentions were removed in this step
              mentionsBefore.forEach(function (mention, localId) {
                if (!mentionsAfter.has(localId)) {
                  mentionsRemoved.set(localId, mention);
                }
              });

              // Adjust mentionsRemoved by removing any that reappear
              mentionsAfter.forEach(function (_, localId) {
                if (mentionsRemoved.has(localId)) {
                  mentionsRemoved.delete(localId);
                }
              });
            });
          });
          if (mentionsRemoved.size > 0) {
            var changes = Array.from(mentionsRemoved.values()).map(function (mention) {
              return {
                id: mention.id,
                localId: mention.localId,
                type: 'deleted'
              };
            });
            options.handleMentionsChanged(changes);
          }
          (_insm$session2 = insm.session) === null || _insm$session2 === void 0 || _insm$session2.endFeature('mentionDeletionDetection');
        }
        if (!isExperimentEnabled('platform_editor_agent_mentions_separate_pm_plugin') && editorExperiment('platform_editor_agent_mentions', true) && isQualifyingLocalUserDocChange(tr)) {
          var _mentionSchema = newState.schema.nodes.mention;
          var newDocRanges = [];
          var oldDocRanges = [];
          var stepsTouchMentions = false;
          tr.steps.forEach(function (step) {
            var found = false;
            // Only merge a step's ranges if it actually touched an agent mention,
            // so unrelated steps (e.g. mark-only changes) don't inflate the scan area.
            var stepNewRanges = [];
            var stepOldRanges = [];
            step.getMap().forEach(function (oldFrom, oldTo, newFrom, newTo) {
              stepOldRanges.push([oldFrom, oldTo]);
              stepNewRanges.push([newFrom, newTo]);
              if (!found) {
                // Clamp positions: delete-only steps can produce newTo > doc.content.size.
                var clampedNewFrom = Math.min(newFrom, newState.doc.content.size);
                var clampedNewTo = Math.min(newTo, newState.doc.content.size);
                if (clampedNewFrom < clampedNewTo) {
                  newState.doc.nodesBetween(clampedNewFrom, clampedNewTo, function (node) {
                    if (node.type === _mentionSchema && isAgentUserType(node.attrs.userType)) {
                      found = true;
                    }
                    return !found;
                  });
                }
                if (!found) {
                  var clampedOldFrom = Math.min(oldFrom, oldState.doc.content.size);
                  var clampedOldTo = Math.min(oldTo, oldState.doc.content.size);
                  if (clampedOldFrom < clampedOldTo) {
                    oldState.doc.nodesBetween(clampedOldFrom, clampedOldTo, function (node) {
                      if (node.type === _mentionSchema && AGENT_USER_TYPES.has(node.attrs.userType)) {
                        found = true;
                      }
                      return !found;
                    });
                  }
                }
              }
            });
            if (found) {
              stepsTouchMentions = true;
              newDocRanges.push.apply(newDocRanges, stepNewRanges);
              oldDocRanges.push.apply(oldDocRanges, stepOldRanges);
            }
          });
          var shouldResolveAgentMentionState = stepsTouchMentions || Boolean(newPluginState.lastInsertedAgentMentionLocalId);
          if (shouldResolveAgentMentionState) {
            var _newPluginState$lastI, _newPluginState$lastA, _newPluginState$lastI2, _newPluginState$lastI3, _newPluginState$lastI4, _newPluginState$lastI5, _newPluginState$lastI6;
            var agentMentionId = null;
            var agentMentionLocalId = null;
            var agentMentionContext = null;
            var agentMentionName = null;
            var agentMentionPrompt = null;
            var agentMentionParentNodeType = null;
            var existingAgentMentionLocalIdsInChangedRanges = new Set();
            var pendingTypedAgentMentionDetails = null;
            if (stepsTouchMentions) {
              var _iterator = _createForOfIteratorHelper(newDocRanges),
                _step;
              try {
                for (_iterator.s(); !(_step = _iterator.n()).done;) {
                  var _step$value = _slicedToArray(_step.value, 2),
                    from = _step$value[0],
                    to = _step$value[1];
                  var clampedTo = Math.min(to, newState.doc.content.size);
                  if (from >= clampedTo) continue;
                  newState.doc.nodesBetween(from, clampedTo, function (node, pos) {
                    if (node.type !== _mentionSchema || !isAgentUserType(node.attrs.userType)) {
                      return true;
                    }
                    if (pendingTypedAgentMentionDetails === null && action === ACTIONS.SET_PENDING_TYPED_AGENT_MENTION && node.attrs.localId === (params === null || params === void 0 ? void 0 : params.localId)) {
                      pendingTypedAgentMentionDetails = getAgentMentionDetailsAtPos(newState, pos, function (attrs) {
                        return attrs.localId === params.localId;
                      }, params.name);
                    }
                    if (agentMentionLocalId === null && node.attrs.localId) {
                      // Try to recover the name synchronously from the mention provider's
                      // name cache (populated by cacheMentionName on typed insert).
                      var cachedName = fg('platform_editor_agent_mentions_drop_one_fixes') ? resolveCachedAgentMentionName(mentionProvider, params, node.attrs.id) : params === null || params === void 0 ? void 0 : params.name;
                      var agentMentionDetails = getAgentMentionDetailsAtPos(newState, pos, function (attrs) {
                        return attrs.localId === node.attrs.localId;
                      }, cachedName);
                      if (agentMentionDetails) {
                        agentMentionId = agentMentionDetails.id;
                        agentMentionLocalId = agentMentionDetails.localId;
                        agentMentionContext = agentMentionDetails.context;
                        agentMentionName = agentMentionDetails.name;
                        agentMentionPrompt = agentMentionDetails.prompt;
                        agentMentionParentNodeType = agentMentionDetails.parentNodeType;
                      }
                    }
                    return true;
                  });
                }
              } catch (err) {
                _iterator.e(err);
              } finally {
                _iterator.f();
              }
              var _iterator2 = _createForOfIteratorHelper(oldDocRanges),
                _step2;
              try {
                for (_iterator2.s(); !(_step2 = _iterator2.n()).done;) {
                  var _step2$value = _slicedToArray(_step2.value, 2),
                    _from = _step2$value[0],
                    _to = _step2$value[1];
                  var clampedOldTo = Math.min(_to, oldState.doc.content.size);
                  if (_from >= clampedOldTo) continue;
                  oldState.doc.nodesBetween(_from, clampedOldTo, function (node) {
                    if (node.type !== _mentionSchema || !isAgentUserType(node.attrs.userType)) {
                      return true;
                    }
                    if (node.attrs.localId) {
                      existingAgentMentionLocalIdsInChangedRanges.add(node.attrs.localId);
                    }
                    return true;
                  });
                }
              } catch (err) {
                _iterator2.e(err);
              } finally {
                _iterator2.f();
              }
            }

            // When a deletion collapses the new-doc range to a zero-width point, or when
            // the doc changed but no step covered the tracked mention, the new-doc scan
            // above finds nothing. Check whether any agent mention survived in the document.
            var resolvedFromFullDocFallback = false;
            if (agentMentionId === null && newPluginState.lastInsertedAgentMentionLocalId) {
              var survivorDetails = getSurvivingAgentMentionDetails(newState, newPluginState.lastInsertedAgentMentionLocalId, newPluginState.lastInsertedAgentMentionName);
              if (survivorDetails) {
                agentMentionId = survivorDetails.id;
                agentMentionLocalId = survivorDetails.localId;
                agentMentionContext = survivorDetails.context;
                agentMentionName = survivorDetails.name;
                agentMentionPrompt = survivorDetails.prompt;
                agentMentionParentNodeType = survivorDetails.parentNodeType;
                resolvedFromFullDocFallback = true;
              }
            }
            var trackedAgentMentionLocalId = (_newPluginState$lastI = newPluginState.lastInsertedAgentMentionLocalId) !== null && _newPluginState$lastI !== void 0 ? _newPluginState$lastI : null;
            var changedRangeMentionIsNew = agentMentionLocalId !== null && !existingAgentMentionLocalIdsInChangedRanges.has(agentMentionLocalId);
            if (agentMentionLocalId !== null && !changedRangeMentionIsNew && agentMentionLocalId !== trackedAgentMentionLocalId) {
              var _survivorDetails = trackedAgentMentionLocalId ? getSurvivingAgentMentionDetails(newState, trackedAgentMentionLocalId, newPluginState.lastInsertedAgentMentionName) : null;
              if (_survivorDetails) {
                agentMentionId = _survivorDetails.id;
                agentMentionLocalId = _survivorDetails.localId;
                agentMentionContext = _survivorDetails.context;
                agentMentionName = _survivorDetails.name;
                agentMentionParentNodeType = _survivorDetails.parentNodeType;
                resolvedFromFullDocFallback = true;
              } else {
                agentMentionId = null;
                agentMentionLocalId = null;
                agentMentionContext = null;
                agentMentionName = null;
                agentMentionParentNodeType = null;
              }
            }
            var isNewInsertion = agentMentionId !== null && !resolvedFromFullDocFallback && changedRangeMentionIsNew;
            var isPendingTypedAgentMentionInsertion = isNewInsertion && action === ACTIONS.SET_PENDING_TYPED_AGENT_MENTION && typeof (params === null || params === void 0 ? void 0 : params.localId) === 'string';
            var newInsertionCount = isNewInsertion ? ((_newPluginState$lastA = newPluginState.lastAgentMentionInsertionCount) !== null && _newPluginState$lastA !== void 0 ? _newPluginState$lastA : 0) + 1 : undefined;
            var pendingTypedAgentMentionDetailsForState = pendingTypedAgentMentionDetails;
            if (isPendingTypedAgentMentionInsertion && pendingTypedAgentMentionDetailsForState) {
              var pendingTypedAgentMentionLocalId = params === null || params === void 0 ? void 0 : params.localId;
              newPluginState = _objectSpread(_objectSpread({}, newPluginState), {}, {
                pendingTypedAgentMention: {
                  id: pendingTypedAgentMentionDetailsForState.id,
                  localId: pendingTypedAgentMentionLocalId,
                  name: pendingTypedAgentMentionDetailsForState.name,
                  nodeSize: pendingTypedAgentMentionDetailsForState.nodeSize,
                  parentNodeType: pendingTypedAgentMentionDetailsForState.parentNodeType,
                  pos: pendingTypedAgentMentionDetailsForState.pos,
                  resetCount: 1
                }
              });
            } else if (isPendingTypedAgentMentionInsertion) {
              // Fallback: if the localId-specific scan missed the typed mention,
              // publish immediately so the insertion is not dropped.
              newPluginState = _objectSpread(_objectSpread({}, newPluginState), {}, {
                pendingTypedAgentMention: null,
                lastInsertedAgentMentionId: agentMentionId,
                lastInsertedAgentMentionLocalId: agentMentionLocalId,
                lastInsertedAgentMentionContext: agentMentionContext,
                lastInsertedAgentMentionName: agentMentionName,
                lastInsertedAgentMentionPrompt: agentMentionPrompt,
                lastInsertedAgentMentionParentNodeType: agentMentionParentNodeType
              }, newInsertionCount !== undefined ? {
                lastAgentMentionInsertionCount: newInsertionCount
              } : {});
              hasPublicPluginStateChanged = true;
            } 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) {
              var _options$getIsRovoPan;
              // Suppress the nudge for pasted/non-typed agent mentions when Rovo is already
              // open.
              // Task items are exempt only while
              // platform_editor_agent_mentions_no_task_autofire is off (they auto-fire
              // instead). With it on they are suppressed like every other block.
              var isTaskItemMentionForPaste = agentMentionParentNodeType === 'taskItem' && !fg('platform_editor_agent_mentions_no_task_autofire');
              if (!isTaskItemMentionForPaste && isNewInsertion && options !== null && options !== void 0 && (_options$getIsRovoPan = options.getIsRovoPanelOpen) !== null && _options$getIsRovoPan !== void 0 && _options$getIsRovoPan.call(options)) {
                // Rovo is already open — skip setting lastInsertedAgentMention* so the
                // downstream nudge listener never fires.
              } else if (agentMentionName === null && agentMentionId !== null && isResolvingMentionProvider(newPluginState.mentionProvider) && fg('platform_editor_agent_mentions_drop_one_fixes')) {
                // Store details in pendingPastedAgentMention (private, not broadcast), preventing a double-nudge.
                newPluginState = _objectSpread(_objectSpread({}, newPluginState), {}, {
                  pendingPastedAgentMention: {
                    id: agentMentionId,
                    localId: agentMentionLocalId,
                    context: agentMentionContext,
                    prompt: agentMentionPrompt,
                    parentNodeType: agentMentionParentNodeType
                  }
                }, newInsertionCount !== undefined ? {
                  lastAgentMentionInsertionCount: newInsertionCount
                } : {});
                // the nudge does not fire until the name is resolved.
              } else {
                // Not suppressed — update state so the nudge fires normally.
                newPluginState = _objectSpread(_objectSpread({}, newPluginState), {}, {
                  lastInsertedAgentMentionId: agentMentionId,
                  lastInsertedAgentMentionLocalId: agentMentionLocalId,
                  lastInsertedAgentMentionContext: agentMentionContext,
                  lastInsertedAgentMentionName: agentMentionName,
                  lastInsertedAgentMentionPrompt: agentMentionPrompt,
                  lastInsertedAgentMentionParentNodeType: agentMentionParentNodeType
                }, newInsertionCount !== undefined ? {
                  lastAgentMentionInsertionCount: newInsertionCount
                } : {});
                hasPublicPluginStateChanged = true;
              }
            }
          }
        }
        if (!isExperimentEnabled('platform_editor_agent_mentions_separate_pm_plugin') && editorExperiment('platform_editor_agent_mentions', true) && tr.docChanged && tr.getMeta('replaceDocument') && hasTrackedAgentMentionState(newPluginState)) {
          newPluginState = clearTrackedAgentMentionState(newPluginState);
          hasPublicPluginStateChanged = true;
        }
        if (!isExperimentEnabled('platform_editor_agent_mentions_separate_pm_plugin') && editorExperiment('platform_editor_agent_mentions', true) && newPluginState.pendingTypedAgentMention && action !== ACTIONS.SET_PENDING_TYPED_AGENT_MENTION && action !== ACTIONS.COMMIT_PENDING_TYPED_AGENT_MENTION && tr.docChanged) {
          newPluginState = _objectSpread(_objectSpread({}, newPluginState), {}, {
            pendingTypedAgentMention: getPendingTypedAgentMentionAfterDocChange(newState, tr, newPluginState.pendingTypedAgentMention, {
              resetTimer: isQualifyingLocalUserDocChange(tr)
            })
          });
        }
        if (!isExperimentEnabled('platform_editor_agent_mentions_separate_pm_plugin') && editorExperiment('platform_editor_agent_mentions', true)) {
          // Typed agent mentions stay pending while the user is still editing around them,
          // but leaving the mention's direct parent means they have moved on from that
          // paragraph/block. Publish immediately in that case instead of waiting for the
          // inactivity timer.
          var shouldCheckPendingTypedAgentMentionParent = isLocalSelectionChange(tr, hasPositionChanged);
          if (newPluginState.pendingTypedAgentMention && action !== ACTIONS.SET_PENDING_TYPED_AGENT_MENTION && action !== ACTIONS.COMMIT_PENDING_TYPED_AGENT_MENTION && shouldCheckPendingTypedAgentMentionParent) {
            var _pendingTypedAgentMention = newPluginState.pendingTypedAgentMention;
            var pendingMentionDetails = getAgentMentionDetailsAtPos(newState, _pendingTypedAgentMention.pos, function (attrs) {
              return attrs.localId === _pendingTypedAgentMention.localId;
            }, _pendingTypedAgentMention.name);
            if (!pendingMentionDetails) {
              newPluginState = _objectSpread(_objectSpread({}, newPluginState), {}, {
                pendingTypedAgentMention: null
              });
            } else if (hasPendingMentionMovedToNewParent(oldState, tr, pluginState.pendingTypedAgentMention, pendingMentionDetails) || isSelectionOutsideDirectParent(newState, pendingMentionDetails)) {
              var _options$getIsRovoPan2;
              // Suppress the nudge when Rovo chat is already open, mirroring the same
              // guard used in the inactivity-timer path. Task-item mentions are
              // exempt only while platform_editor_agent_mentions_no_task_autofire is off
              // (they auto-fire the mini modal and never show a nudge). With it on they
              // are suppressed like any block.
              var isTaskItemMention = pendingMentionDetails.parentNodeType === 'taskItem' && !fg('platform_editor_agent_mentions_no_task_autofire');
              if (!isTaskItemMention && options !== null && options !== void 0 && (_options$getIsRovoPan2 = options.getIsRovoPanelOpen) !== null && _options$getIsRovoPan2 !== void 0 && _options$getIsRovoPan2.call(options)) {
                // Discard without committing so lastInsertedAgentMention* fields are
                // never set and the downstream nudge listener never fires.
                newPluginState = _objectSpread(_objectSpread({}, newPluginState), {}, {
                  pendingTypedAgentMention: null
                });
                hasPublicPluginStateChanged = true;
              } else {
                var _commitResult = commitResolvedPendingTypedAgentMention(newPluginState, pendingMentionDetails);
                newPluginState = _commitResult.pluginState;
                hasPublicPluginStateChanged = hasPublicPluginStateChanged || _commitResult.hasPublicPluginStateChanged;
              }
            }
          }
        }
        if (hasPublicPluginStateChanged && !isExperimentEnabled('platform_editor_agent_mentions_separate_pm_plugin') && editorExperiment('platform_editor_agent_mentions', true)) {
          pmPluginFactoryParams.dispatch(mentionPluginKey, newPluginState);
        }
        return newPluginState;
      }
    },
    props: {
      nodeViews: {
        mention: function mention(node, view) {
          return new MentionNodeView(node, {
            options: options,
            api: api,
            editorView: view,
            portalProviderAPI: pmPluginFactoryParams.portalProviderAPI
          });
        }
      },
      // Duplicated in platform/packages/editor/editor-plugin-mentions/src/pm-plugins/agent.ts
      // Can remove once platform_editor_agent_mentions_separate_pm_plugin has been cleaned up
      decorations: function decorations(state) {
        var _mentionPluginKey$get;
        var runStateDecorations = (_mentionPluginKey$get = mentionPluginKey.getState(state)) === null || _mentionPluginKey$get === void 0 ? void 0 : _mentionPluginKey$get.runStateDecorations;
        if (!runStateDecorations || runStateDecorations === DecorationSet.empty) {
          return undefined;
        }
        if (isExperimentEnabled('platform_editor_agent_mentions_separate_pm_plugin') || !editorExperiment('platform_editor_agent_mentions', true) || !isExperimentEnabled('platform_editor_agent_mention_state_anim')) {
          return undefined;
        }
        return runStateDecorations;
      }
    },
    view: function view(editorView) {
      var pendingTypedAgentMentionTimer;
      var pendingTypedAgentMentionTimerKey = null;
      var pendingTypedAgentMentionFocusDeferCount = 0;
      // Tracks agent IDs for which resolveMentionName() is already in-flight,
      // preventing duplicate concurrent Promises for the same ID across update() cycles.
      var pendingResolveMentionIds = new Set();

      /**
       * Clears the currently scheduled pending typed-agent-mention timer.
       *
       * By default this also resets the focus defer count because a new pending mention,
       * local edit reset, or cleanup should start a fresh escape-hatch window. Focus-based
       * retries pass `preserveFocusDeferCount` so repeated defers for the same pending
       * mention are counted toward the bounded retry cap.
       */
      var clearPendingTypedAgentMentionTimer = function clearPendingTypedAgentMentionTimer() {
        var _ref4 = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {},
          _ref4$preserveFocusDe = _ref4.preserveFocusDeferCount,
          preserveFocusDeferCount = _ref4$preserveFocusDe === void 0 ? false : _ref4$preserveFocusDe;
        if (pendingTypedAgentMentionTimer) {
          clearTimeout(pendingTypedAgentMentionTimer);
          pendingTypedAgentMentionTimer = undefined;
        }
        pendingTypedAgentMentionTimerKey = null;
        if (!preserveFocusDeferCount) {
          pendingTypedAgentMentionFocusDeferCount = 0;
        }
      };

      /**
       * Typed agent mentions intentionally wait before invoking the agent so content the
       * user adds next can become context. That context can be authored through
       * editor-adjacent UI, such as mention typeahead, date picker, or status picker,
       * where focus leaves the ProseMirror editor but remains in the active document.
       * Treat that as continued authoring activity by restarting the inactivity window.
       * This only defers the inactivity timer path: existing selection-change handling can
       * still publish or clear the pending mention sooner. After 20 focus defers (~1 minute),
       * publish anyway so pathological focus states cannot keep the mention pending indefinitely.
       */
      var shouldDeferPendingTypedAgentMention = function shouldDeferPendingTypedAgentMention() {
        return typeof document !== 'undefined' && document.hasFocus() && !editorView.hasFocus();
      };
      var _schedulePendingTypedAgentMentionTimer = function schedulePendingTypedAgentMentionTimer(mentionPluginState) {
        var _ref5 = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {},
          _ref5$preserveFocusDe = _ref5.preserveFocusDeferCount,
          preserveFocusDeferCount = _ref5$preserveFocusDe === void 0 ? false : _ref5$preserveFocusDe;
        if (isExperimentEnabled('platform_editor_agent_mentions_separate_pm_plugin') || !editorExperiment('platform_editor_agent_mentions', true)) {
          clearPendingTypedAgentMentionTimer();
          return;
        }
        var pendingTypedAgentMention = mentionPluginState === null || mentionPluginState === void 0 ? void 0 : mentionPluginState.pendingTypedAgentMention;
        if (!pendingTypedAgentMention) {
          clearPendingTypedAgentMentionTimer();
          return;
        }
        var timerKey = "".concat(pendingTypedAgentMention.localId, ":").concat(pendingTypedAgentMention.resetCount);
        if (timerKey === pendingTypedAgentMentionTimerKey) {
          return;
        }
        clearPendingTypedAgentMentionTimer({
          preserveFocusDeferCount: preserveFocusDeferCount
        });
        pendingTypedAgentMentionTimerKey = timerKey;
        pendingTypedAgentMentionTimer = setTimeout(function () {
          var _mentionPluginKey$get2;
          {
            var _options$getIsRovoPan3;
            // Suppress the agent-mention nudge when the Rovo panel is already open. Task
            // items are exempt only while platform_editor_agent_mentions_no_task_autofire
            // is off (they auto-fire instead). With it on they are suppressed like every
            // other block.
            var isTaskItemMention = pendingTypedAgentMention.parentNodeType === 'taskItem' && !fg('platform_editor_agent_mentions_no_task_autofire');
            if (!isTaskItemMention && options !== null && options !== void 0 && (_options$getIsRovoPan3 = options.getIsRovoPanelOpen) !== null && _options$getIsRovoPan3 !== void 0 && _options$getIsRovoPan3.call(options)) {
              // Dispatch a discard so pendingTypedAgentMention is nulled out of plugin
              // state.
              editorView.dispatch(editorView.state.tr.setMeta(mentionPluginKey, {
                action: ACTIONS.DISCARD_PENDING_TYPED_AGENT_MENTION,
                params: {
                  localId: pendingTypedAgentMention.localId,
                  resetCount: pendingTypedAgentMention.resetCount
                }
              }));
              return;
            }
          }
          var latestPendingTypedAgentMention = (_mentionPluginKey$get2 = mentionPluginKey.getState(editorView.state)) === null || _mentionPluginKey$get2 === void 0 ? void 0 : _mentionPluginKey$get2.pendingTypedAgentMention;
          if (!latestPendingTypedAgentMention || latestPendingTypedAgentMention.localId !== pendingTypedAgentMention.localId || latestPendingTypedAgentMention.resetCount !== pendingTypedAgentMention.resetCount) {
            return;
          }
          if (shouldDeferPendingTypedAgentMention() && pendingTypedAgentMentionFocusDeferCount < MAX_PENDING_TYPED_AGENT_MENTION_FOCUS_DEFERS) {
            pendingTypedAgentMentionFocusDeferCount++;
            pendingTypedAgentMentionTimerKey = null;
            _schedulePendingTypedAgentMentionTimer(mentionPluginKey.getState(editorView.state), {
              preserveFocusDeferCount: true
            });
            return;
          }
          pendingTypedAgentMentionFocusDeferCount = 0;
          editorView.dispatch(editorView.state.tr.setMeta(mentionPluginKey, {
            action: ACTIONS.COMMIT_PENDING_TYPED_AGENT_MENTION,
            params: {
              localId: pendingTypedAgentMention.localId,
              resetCount: pendingTypedAgentMention.resetCount
            }
          }));
        }, AGENT_MENTION_INACTIVITY_MS);
      };
      var providerHandler = function providerHandler(name, providerPromise) {
        switch (name) {
          case 'mentionProvider':
            if (!providerPromise) {
              fireEvent({
                action: ACTION.ERRORED,
                actionSubject: ACTION_SUBJECT.MENTION,
                actionSubjectId: ACTION_SUBJECT_ID.MENTION_PROVIDER,
                eventType: EVENT_TYPE.OPERATIONAL,
                attributes: {
                  reason: MENTION_PROVIDER_UNDEFINED
                }
              });
              return setProvider(undefined)(editorView.state, editorView.dispatch);
            }
            providerPromise.then(function (provider) {
              if (mentionProvider) {
                mentionProvider.unsubscribe('mentionPlugin');
              }
              mentionProvider = provider;
              setProvider(provider)(editorView.state, editorView.dispatch);
              provider.subscribe('mentionPlugin', undefined, undefined, undefined, undefined, sendAnalytics);
            }).catch(function () {
              fireEvent({
                action: ACTION.ERRORED,
                actionSubject: ACTION_SUBJECT.MENTION,
                actionSubjectId: ACTION_SUBJECT_ID.MENTION_PROVIDER,
                eventType: EVENT_TYPE.OPERATIONAL,
                attributes: {
                  reason: MENTION_PROVIDER_REJECTED
                }
              });
              return setProvider(undefined)(editorView.state, editorView.dispatch);
            });
            break;
        }
        return;
      };
      var providerViaConfig = fg('platform_editor_mention_provider_via_plugin_config');
      if (providerViaConfig && options !== null && options !== void 0 && options.mentionProvider) {
        providerHandler('mentionProvider', options === null || options === void 0 ? void 0 : options.mentionProvider);
      } else {
        pmPluginFactoryParams.providerFactory.subscribe('mentionProvider', providerHandler);
      }
      return {
        update: function update(view, prevState) {
          var mentionPluginState = mentionPluginKey.getState(view.state);
          if (mentionPluginState === mentionPluginKey.getState(prevState)) {
            return;
          }
          if (!isExperimentEnabled('platform_editor_agent_mentions_separate_pm_plugin')) {
            var _mentionPluginState$p;
            _schedulePendingTypedAgentMentionTimer(mentionPluginState);

            // If a pasted agent mention has no name (cache miss), resolve it async
            // via the mention provider and dispatch RESOLVE_PASTED_AGENT_MENTION_NAME
            // so the nudge displays the correct name.
            if (editorExperiment('platform_editor_agent_mentions', true) && (mentionPluginState === null || mentionPluginState === 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')) {
              resolveAndDispatchPastedAgentMentionName(mentionPluginState.pendingPastedAgentMention.id, mentionPluginState.mentionProvider, pendingResolveMentionIds, view);
            }
          }
        },
        destroy: function destroy() {
          clearPendingTypedAgentMentionTimer();
          if (pmPluginFactoryParams.providerFactory) {
            pmPluginFactoryParams.providerFactory.unsubscribe('mentionProvider', providerHandler);
          }
          if (mentionProvider) {
            mentionProvider.unsubscribe('mentionPlugin');
          }
        }
      };
    }
  });
}