@atlaskit/collab-provider 21.1.3 → 21.2.0

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.
package/CHANGELOG.md CHANGED
@@ -1,5 +1,51 @@
1
1
  # @atlaskit/collab-provider
2
2
 
3
+ ## 21.2.0
4
+
5
+ ### Minor Changes
6
+
7
+ - [`782360c1111dc`](https://bitbucket.org/atlassian/atlassian-frontend-monorepo/commits/782360c1111dc) -
8
+ Agent-edit shimmer review follow-ups (behind the default-OFF `platform_editor_agent_be_streaming`
9
+ experiment):
10
+ - Add a second shimmer phase: after the skeleton loader clears, the changed range shows a purple
11
+ "just edited" highlight (same colours as the editor AI "improve writing" in-editor highlight)
12
+ that eases in and out over its lifetime. The Rovo telepointer stays through both phases.
13
+ - The two phases are sized by independent experiment params: `shimmerDurationMs` (skeleton,
14
+ renamed from `durationMs`) and `highlightDurationMs` (highlight). `0` on either skips just that
15
+ phase, `0` on both shows nothing.
16
+ - Gate the agent-edit shimmer styles behind the experiment in both the emotion and compiled
17
+ `EditorContentContainer` style entries, using the no-exposure check on the hot render path.
18
+ - Add an `agentEditReceived` collab-provider analytics event, fired once per received transaction
19
+ that contains agent-authored steps, with non-PII attributes (agent ids, count, kinds,
20
+ agent/total step counts).
21
+ - Add an `agentEditShimmerNotShown` COLLAB operational analytics event for agent edits that apply
22
+ without the shimmer (`rebasedConcurrentEdit`, `nothingToShow`, `captureThrew`,
23
+ `tornDownMidAnimation`); neutral action + `reason`, non-PII attributes.
24
+ - Hoist the top-level block/position helpers in `agent-shimmer-ranges` to reusable module scope,
25
+ drop the redundant upper-bound clamp in `agent-shimmer-decorations`, and remove the empty
26
+ `@example` JSDoc tags added in the original PR.
27
+
28
+ `@atlaskit/editor-common` gains a new subpath export,
29
+ `@atlaskit/editor-common/analytics/types/agent-edit-shimmer-events`, exposing the
30
+ `agentEditShimmerNotShown` operational event types. Usage:
31
+
32
+ ```ts
33
+ import { ACTION, ACTION_SUBJECT, EVENT_TYPE } from '@atlaskit/editor-common/analytics';
34
+ import type { AgentEditShimmerNotShownReason } from '@atlaskit/editor-common/analytics/types/agent-edit-shimmer-events';
35
+
36
+ const reason: AgentEditShimmerNotShownReason = 'captureThrew';
37
+ editorAnalyticsApi?.fireAnalyticsEvent({
38
+ action: ACTION.AGENT_EDIT_SHIMMER_NOT_SHOWN,
39
+ actionSubject: ACTION_SUBJECT.COLLAB,
40
+ eventType: EVENT_TYPE.OPERATIONAL,
41
+ attributes: { reason },
42
+ });
43
+ ```
44
+
45
+ ### Patch Changes
46
+
47
+ - Updated dependencies
48
+
3
49
  ## 21.1.3
4
50
 
5
51
  ### Patch Changes
@@ -6,6 +6,7 @@ Object.defineProperty(exports, "__esModule", {
6
6
  });
7
7
  exports.DocumentService = void 0;
8
8
  var _regenerator = _interopRequireDefault(require("@babel/runtime/regenerator"));
9
+ var _toConsumableArray2 = _interopRequireDefault(require("@babel/runtime/helpers/toConsumableArray"));
9
10
  var _asyncToGenerator2 = _interopRequireDefault(require("@babel/runtime/helpers/asyncToGenerator"));
10
11
  var _classCallCheck2 = _interopRequireDefault(require("@babel/runtime/helpers/classCallCheck"));
11
12
  var _createClass2 = _interopRequireDefault(require("@babel/runtime/helpers/createClass"));
@@ -948,17 +949,15 @@ var DocumentService = exports.DocumentService = /*#__PURE__*/function () {
948
949
  }
949
950
 
950
951
  /**
951
- * Detect agent-authored steps in a received batch and register
952
- * each distinct agent as a local participant so it appears in the presence facepile.
952
+ * Detect agent-authored steps in a received batch and register each distinct agent as a local
953
+ * participant so it appears in the presence facepile.
953
954
  *
954
- * Gated behind a default-OFF feature gate: `collab-provider` is shared platform infra (used by
955
- * Jira JWM/JPD etc.), so this is a strict no-op everywhere unless the gate is explicitly enabled.
956
- *
957
- * Isolated in its own try/catch so a presence failure can never affect step persistence.
955
+ * Gated behind a default-OFF gate because `collab-provider` is shared platform infra (Jira
956
+ * JWM/JPD etc.), and isolated in its own try/catch so a presence failure can never affect step
957
+ * persistence.
958
958
  *
959
959
  * Known limitation (PoC): steps replayed via catch-up/reconnect can briefly re-surface an agent
960
960
  * whose activity is stale — a freshness guard is a follow-up.
961
- * @example
962
961
  */
963
962
  }, {
964
963
  key: "maybeAddAgentPresenceFromSteps",
@@ -968,28 +967,57 @@ var DocumentService = exports.DocumentService = /*#__PURE__*/function () {
968
967
  return;
969
968
  }
970
969
  try {
970
+ var _this$analyticsHelper34;
971
971
  var providerIds = new Set();
972
+ var agentIds = new Set();
973
+ var agentTypes = new Set();
974
+ var agentStepCount = 0;
972
975
  var _iterator = _createForOfIteratorHelper(steps),
973
976
  _step;
974
977
  try {
975
978
  for (_iterator.s(); !(_step = _iterator.n()).done;) {
976
979
  var step = _step.value;
977
980
  var providerId = (0, _utils.getAgentProviderId)(step);
978
- if (providerId) {
979
- providerIds.add(providerId);
981
+ if (!providerId) {
982
+ continue;
983
+ }
984
+ providerIds.add(providerId);
985
+ agentStepCount++;
986
+ var _ref10 = step,
987
+ agentId = _ref10.agentId,
988
+ agentType = _ref10.agentType;
989
+ if (agentType) {
990
+ agentTypes.add(agentType);
991
+ }
992
+ if (agentId) {
993
+ agentIds.add((0, _utils.normalizeAgentId)(agentId));
980
994
  }
981
995
  }
996
+ // No agent-authored steps in this batch — nothing to register or report.
982
997
  } catch (err) {
983
998
  _iterator.e(err);
984
999
  } finally {
985
1000
  _iterator.f();
986
1001
  }
1002
+ if (providerIds.size === 0) {
1003
+ return;
1004
+ }
1005
+ // Anchor signal that the agent-presence logic kicked off for a received transaction. Fires
1006
+ // once per transaction (agent edits arrive as batches, not per keystroke). Attributes are
1007
+ // non-PII: agent kinds, actor ids, and counts — never document content.
1008
+ (_this$analyticsHelper34 = this.analyticsHelper) === null || _this$analyticsHelper34 === void 0 || _this$analyticsHelper34.sendActionEvent(_const.EVENT_ACTION.AGENT_EDIT_RECEIVED, _const.EVENT_STATUS.SUCCESS, {
1009
+ agentIds: (0, _toConsumableArray2.default)(agentIds),
1010
+ agentCount: providerIds.size,
1011
+ agentTypes: (0, _toConsumableArray2.default)(agentTypes),
1012
+ agentStepCount: agentStepCount,
1013
+ totalStepCount: steps.length
1014
+ });
987
1015
  providerIds.forEach(function (providerId) {
988
1016
  return _this3.participantsService.upsertAIProviderParticipantLocally(providerId);
989
1017
  });
990
1018
  } catch (error) {
991
- var _this$analyticsHelper34;
992
- (_this$analyticsHelper34 = this.analyticsHelper) === null || _this$analyticsHelper34 === void 0 || _this$analyticsHelper34.sendErrorEvent(error, 'Error while adding agent presence from steps');
1019
+ var _this$analyticsHelper35;
1020
+ (_this$analyticsHelper35 = this.analyticsHelper) === null || _this$analyticsHelper35 === void 0 || _this$analyticsHelper35.sendErrorEvent(error, 'Error while adding agent presence from steps');
993
1021
  }
994
1022
  }
995
1023
  }, {
@@ -1013,10 +1041,10 @@ var DocumentService = exports.DocumentService = /*#__PURE__*/function () {
1013
1041
  * @param root0.clientId
1014
1042
  * @example
1015
1043
  */
1016
- function setup(_ref10) {
1017
- var getState = _ref10.getState,
1018
- onSyncUpError = _ref10.onSyncUpError,
1019
- clientId = _ref10.clientId;
1044
+ function setup(_ref11) {
1045
+ var getState = _ref11.getState,
1046
+ onSyncUpError = _ref11.onSyncUpError,
1047
+ clientId = _ref11.clientId;
1020
1048
  this.getState = getState;
1021
1049
  this.onSyncUpError = onSyncUpError || noop;
1022
1050
  this.clientId = clientId;
@@ -1039,8 +1067,8 @@ var DocumentService = exports.DocumentService = /*#__PURE__*/function () {
1039
1067
  var _this$getState0;
1040
1068
  var state = (_this$getState0 = this.getState) === null || _this$getState0 === void 0 ? void 0 : _this$getState0.call(this);
1041
1069
  if (!state) {
1042
- var _this$analyticsHelper35;
1043
- (_this$analyticsHelper35 = this.analyticsHelper) === null || _this$analyticsHelper35 === void 0 || _this$analyticsHelper35.sendErrorEvent(new Error('Editor state is undefined'), 'sendStepsFromCurrentState called without state');
1070
+ var _this$analyticsHelper36;
1071
+ (_this$analyticsHelper36 = this.analyticsHelper) === null || _this$analyticsHelper36 === void 0 || _this$analyticsHelper36.sendErrorEvent(new Error('Editor state is undefined'), 'sendStepsFromCurrentState called without state');
1044
1072
  return;
1045
1073
  }
1046
1074
  this.send(null, null, state, sendAnalyticsEvent, reason);
@@ -1091,8 +1119,8 @@ var DocumentService = exports.DocumentService = /*#__PURE__*/function () {
1091
1119
  // to ensure that analytics events with the number of unconfirmed steps is only
1092
1120
  // sent once on connection (as opposed to on every step)
1093
1121
  if (sendAnalyticsEvent) {
1094
- var _this$analyticsHelper36;
1095
- (_this$analyticsHelper36 = this.analyticsHelper) === null || _this$analyticsHelper36 === void 0 || _this$analyticsHelper36.sendActionEvent(_const.EVENT_ACTION.HAS_UNCONFIRMED_STEPS, _const.EVENT_STATUS.INFO, {
1122
+ var _this$analyticsHelper37;
1123
+ (_this$analyticsHelper37 = this.analyticsHelper) === null || _this$analyticsHelper37 === void 0 || _this$analyticsHelper37.sendActionEvent(_const.EVENT_ACTION.HAS_UNCONFIRMED_STEPS, _const.EVENT_STATUS.INFO, {
1096
1124
  numUnconfirmedSteps: (unconfirmedSteps === null || unconfirmedSteps === void 0 ? void 0 : unconfirmedSteps.length) || 0
1097
1125
  });
1098
1126
  }
@@ -1101,12 +1129,12 @@ var DocumentService = exports.DocumentService = /*#__PURE__*/function () {
1101
1129
  }
1102
1130
  var rebased = tr === null || tr === void 0 ? void 0 : tr.getMeta('rebasedData');
1103
1131
  if (rebased) {
1104
- var _this$analyticsHelper37;
1132
+ var _this$analyticsHelper38;
1105
1133
  var obfuscatedUnconfirmedSteps = this.obfuscateStepsAndState(rebased.unconfirmedSteps).obfuscatedSteps;
1106
1134
  var obfuscatedRemoteSteps = this.obfuscateStepsAndState(rebased.remoteSteps);
1107
1135
  var obfuscatedRebasedSteps = this.obfuscateStepsAndState(unconfirmedSteps).obfuscatedSteps;
1108
1136
  // send analtyics on unconfirmed steps
1109
- (_this$analyticsHelper37 = this.analyticsHelper) === null || _this$analyticsHelper37 === void 0 || _this$analyticsHelper37.sendActionEvent(_const.EVENT_ACTION.STEPS_REBASED, _const.EVENT_STATUS.INFO, {
1137
+ (_this$analyticsHelper38 = this.analyticsHelper) === null || _this$analyticsHelper38 === void 0 || _this$analyticsHelper38.sendActionEvent(_const.EVENT_ACTION.STEPS_REBASED, _const.EVENT_STATUS.INFO, {
1110
1138
  obfuscatedUnconfirmedSteps: obfuscatedUnconfirmedSteps,
1111
1139
  obfuscatedRemoteSteps: obfuscatedRemoteSteps,
1112
1140
  obfuscatedRebasedSteps: obfuscatedRebasedSteps,
@@ -50,7 +50,11 @@ var EVENT_ACTION = exports.EVENT_ACTION = /*#__PURE__*/function (EVENT_ACTION) {
50
50
  EVENT_ACTION["STEPS_REBASED"] = "stepsRebased";
51
51
  // https://data-portal.internal.atlassian.com/analytics/registry/76616
52
52
  EVENT_ACTION["POLLING_FALLBACK"] = "pollingFallback";
53
- EVENT_ACTION["PROCESS_STEPS"] = "processSteps"; // https://data-portal.internal.atlassian.com/analytics/registry/85229
53
+ EVENT_ACTION["PROCESS_STEPS"] = "processSteps";
54
+ // https://data-portal.internal.atlassian.com/analytics/registry/85229
55
+ // Fired once per received transaction that contains agent-authored steps. Needs a data-portal
56
+ // analytics registry entry before the experiment is enabled broadly.
57
+ EVENT_ACTION["AGENT_EDIT_RECEIVED"] = "agentEditReceived";
54
58
  return EVENT_ACTION;
55
59
  }({});
56
60
  var EVENT_STATUS = exports.EVENT_STATUS = /*#__PURE__*/function (EVENT_STATUS) {
@@ -133,7 +133,6 @@ var ParticipantsService = exports.ParticipantsService = /*#__PURE__*/function ()
133
133
  * presence consumer on each streamed step.
134
134
  *
135
135
  * @param providerId `agent:<aaid|type>` id of the agent
136
- * @example
137
136
  */
138
137
  (0, _defineProperty2.default)(this, "upsertAIProviderParticipantLocally", function (providerId) {
139
138
  var payload = _this.buildAIProviderPresencePayload(providerId);
@@ -164,7 +163,6 @@ var ParticipantsService = exports.ParticipantsService = /*#__PURE__*/function ()
164
163
  /**
165
164
  * (Re)starts the sliding 30s inactivity timer for an agent participant. Called on every agent
166
165
  * step so the window slides forward from the agent's most recent activity.
167
- * @example
168
166
  */
169
167
  (0, _defineProperty2.default)(this, "resetAgentPresenceTimer", function (sessionId) {
170
168
  var existingTimer = _this.agentPresenceTimers.get(sessionId);
@@ -178,7 +176,6 @@ var ParticipantsService = exports.ParticipantsService = /*#__PURE__*/function ()
178
176
  /**
179
177
  * Removes an inactive agent participant once its sliding window elapses and emits the `presence`
180
178
  * leave so the facepile drops it.
181
- * @example
182
179
  */
183
180
  (0, _defineProperty2.default)(this, "removeAgentParticipant", function (sessionId) {
184
181
  _this.agentPresenceTimers.delete(sessionId);
@@ -194,7 +191,6 @@ var ParticipantsService = exports.ParticipantsService = /*#__PURE__*/function ()
194
191
  /**
195
192
  * Clears all pending agent presence sliding timers (on disconnect/destroy) so they don't fire
196
193
  * against cleared state.
197
- * @example
198
194
  */
199
195
  (0, _defineProperty2.default)(this, "clearAgentPresenceTimers", function () {
200
196
  _this.agentPresenceTimers.forEach(function (timer) {
@@ -5,7 +5,7 @@ Object.defineProperty(exports, "__esModule", {
5
5
  });
6
6
  exports.version = exports.nextMajorVersion = exports.name = void 0;
7
7
  var name = exports.name = "@atlaskit/collab-provider";
8
- var version = exports.version = "21.1.2";
8
+ var version = exports.version = "21.1.3";
9
9
  var nextMajorVersion = exports.nextMajorVersion = function nextMajorVersion() {
10
10
  return [Number(version.split('.')[0]) + 1, 0, 0].join('.');
11
11
  };
@@ -9,7 +9,7 @@ import { expValEquals } from '@atlaskit/tmp-editor-statsig/exp-val-equals';
9
9
  import { ACK_MAX_TRY, EVENT_ACTION, EVENT_STATUS, CatchupEventReason } from '../helpers/const';
10
10
  import { MEASURE_NAME, startMeasure, stopMeasure } from '../analytics/performance';
11
11
  import { INTERNAL_ERROR_CODE } from '../errors/internal-errors';
12
- import { createLogger, getAgentProviderId, getDocAdfWithObfuscationFromJSON, getObfuscatedSteps, getStepUGCFreeDetails, sleep } from '../helpers/utils';
12
+ import { createLogger, getAgentProviderId, getDocAdfWithObfuscationFromJSON, getObfuscatedSteps, getStepUGCFreeDetails, normalizeAgentId, sleep } from '../helpers/utils';
13
13
  import { MAX_STEP_REJECTED_ERROR, MAX_STEP_REJECTED_ERROR_AGGRESSIVE } from '../provider';
14
14
  import { CommitStepService } from '../provider/commit-step';
15
15
  import { CantSyncUpError, UpdateDocumentError } from '../errors/custom-errors';
@@ -827,34 +827,62 @@ export class DocumentService {
827
827
  }
828
828
 
829
829
  /**
830
- * Detect agent-authored steps in a received batch and register
831
- * each distinct agent as a local participant so it appears in the presence facepile.
830
+ * Detect agent-authored steps in a received batch and register each distinct agent as a local
831
+ * participant so it appears in the presence facepile.
832
832
  *
833
- * Gated behind a default-OFF feature gate: `collab-provider` is shared platform infra (used by
834
- * Jira JWM/JPD etc.), so this is a strict no-op everywhere unless the gate is explicitly enabled.
835
- *
836
- * Isolated in its own try/catch so a presence failure can never affect step persistence.
833
+ * Gated behind a default-OFF gate because `collab-provider` is shared platform infra (Jira
834
+ * JWM/JPD etc.), and isolated in its own try/catch so a presence failure can never affect step
835
+ * persistence.
837
836
  *
838
837
  * Known limitation (PoC): steps replayed via catch-up/reconnect can briefly re-surface an agent
839
838
  * whose activity is stale — a freshness guard is a follow-up.
840
- * @example
841
839
  */
842
840
  maybeAddAgentPresenceFromSteps(steps) {
843
841
  if (!expValEquals('platform_editor_agent_be_streaming', 'isEnabled', true)) {
844
842
  return;
845
843
  }
846
844
  try {
845
+ var _this$analyticsHelper34;
847
846
  const providerIds = new Set();
847
+ const agentIds = new Set();
848
+ const agentTypes = new Set();
849
+ let agentStepCount = 0;
848
850
  for (const step of steps) {
849
851
  const providerId = getAgentProviderId(step);
850
- if (providerId) {
851
- providerIds.add(providerId);
852
+ if (!providerId) {
853
+ continue;
854
+ }
855
+ providerIds.add(providerId);
856
+ agentStepCount++;
857
+ const {
858
+ agentId,
859
+ agentType
860
+ } = step;
861
+ if (agentType) {
862
+ agentTypes.add(agentType);
852
863
  }
864
+ if (agentId) {
865
+ agentIds.add(normalizeAgentId(agentId));
866
+ }
867
+ }
868
+ // No agent-authored steps in this batch — nothing to register or report.
869
+ if (providerIds.size === 0) {
870
+ return;
853
871
  }
872
+ // Anchor signal that the agent-presence logic kicked off for a received transaction. Fires
873
+ // once per transaction (agent edits arrive as batches, not per keystroke). Attributes are
874
+ // non-PII: agent kinds, actor ids, and counts — never document content.
875
+ (_this$analyticsHelper34 = this.analyticsHelper) === null || _this$analyticsHelper34 === void 0 ? void 0 : _this$analyticsHelper34.sendActionEvent(EVENT_ACTION.AGENT_EDIT_RECEIVED, EVENT_STATUS.SUCCESS, {
876
+ agentIds: [...agentIds],
877
+ agentCount: providerIds.size,
878
+ agentTypes: [...agentTypes],
879
+ agentStepCount,
880
+ totalStepCount: steps.length
881
+ });
854
882
  providerIds.forEach(providerId => this.participantsService.upsertAIProviderParticipantLocally(providerId));
855
883
  } catch (error) {
856
- var _this$analyticsHelper34;
857
- (_this$analyticsHelper34 = this.analyticsHelper) === null || _this$analyticsHelper34 === void 0 ? void 0 : _this$analyticsHelper34.sendErrorEvent(error, 'Error while adding agent presence from steps');
884
+ var _this$analyticsHelper35;
885
+ (_this$analyticsHelper35 = this.analyticsHelper) === null || _this$analyticsHelper35 === void 0 ? void 0 : _this$analyticsHelper35.sendErrorEvent(error, 'Error while adding agent presence from steps');
858
886
  }
859
887
  }
860
888
  /**
@@ -897,8 +925,8 @@ export class DocumentService {
897
925
  var _this$getState0;
898
926
  const state = (_this$getState0 = this.getState) === null || _this$getState0 === void 0 ? void 0 : _this$getState0.call(this);
899
927
  if (!state) {
900
- var _this$analyticsHelper35;
901
- (_this$analyticsHelper35 = this.analyticsHelper) === null || _this$analyticsHelper35 === void 0 ? void 0 : _this$analyticsHelper35.sendErrorEvent(new Error('Editor state is undefined'), 'sendStepsFromCurrentState called without state');
928
+ var _this$analyticsHelper36;
929
+ (_this$analyticsHelper36 = this.analyticsHelper) === null || _this$analyticsHelper36 === void 0 ? void 0 : _this$analyticsHelper36.sendErrorEvent(new Error('Editor state is undefined'), 'sendStepsFromCurrentState called without state');
902
930
  return;
903
931
  }
904
932
  this.send(null, null, state, sendAnalyticsEvent, reason);
@@ -944,8 +972,8 @@ export class DocumentService {
944
972
  // to ensure that analytics events with the number of unconfirmed steps is only
945
973
  // sent once on connection (as opposed to on every step)
946
974
  if (sendAnalyticsEvent) {
947
- var _this$analyticsHelper36;
948
- (_this$analyticsHelper36 = this.analyticsHelper) === null || _this$analyticsHelper36 === void 0 ? void 0 : _this$analyticsHelper36.sendActionEvent(EVENT_ACTION.HAS_UNCONFIRMED_STEPS, EVENT_STATUS.INFO, {
975
+ var _this$analyticsHelper37;
976
+ (_this$analyticsHelper37 = this.analyticsHelper) === null || _this$analyticsHelper37 === void 0 ? void 0 : _this$analyticsHelper37.sendActionEvent(EVENT_ACTION.HAS_UNCONFIRMED_STEPS, EVENT_STATUS.INFO, {
949
977
  numUnconfirmedSteps: (unconfirmedSteps === null || unconfirmedSteps === void 0 ? void 0 : unconfirmedSteps.length) || 0
950
978
  });
951
979
  }
@@ -954,12 +982,12 @@ export class DocumentService {
954
982
  }
955
983
  const rebased = tr === null || tr === void 0 ? void 0 : tr.getMeta('rebasedData');
956
984
  if (rebased) {
957
- var _this$analyticsHelper37;
985
+ var _this$analyticsHelper38;
958
986
  const obfuscatedUnconfirmedSteps = this.obfuscateStepsAndState(rebased.unconfirmedSteps).obfuscatedSteps;
959
987
  const obfuscatedRemoteSteps = this.obfuscateStepsAndState(rebased.remoteSteps);
960
988
  const obfuscatedRebasedSteps = this.obfuscateStepsAndState(unconfirmedSteps).obfuscatedSteps;
961
989
  // send analtyics on unconfirmed steps
962
- (_this$analyticsHelper37 = this.analyticsHelper) === null || _this$analyticsHelper37 === void 0 ? void 0 : _this$analyticsHelper37.sendActionEvent(EVENT_ACTION.STEPS_REBASED, EVENT_STATUS.INFO, {
990
+ (_this$analyticsHelper38 = this.analyticsHelper) === null || _this$analyticsHelper38 === void 0 ? void 0 : _this$analyticsHelper38.sendActionEvent(EVENT_ACTION.STEPS_REBASED, EVENT_STATUS.INFO, {
963
991
  obfuscatedUnconfirmedSteps,
964
992
  obfuscatedRemoteSteps,
965
993
  obfuscatedRebasedSteps,
@@ -44,7 +44,11 @@ export let EVENT_ACTION = /*#__PURE__*/function (EVENT_ACTION) {
44
44
  EVENT_ACTION["STEPS_REBASED"] = "stepsRebased";
45
45
  // https://data-portal.internal.atlassian.com/analytics/registry/76616
46
46
  EVENT_ACTION["POLLING_FALLBACK"] = "pollingFallback";
47
- EVENT_ACTION["PROCESS_STEPS"] = "processSteps"; // https://data-portal.internal.atlassian.com/analytics/registry/85229
47
+ EVENT_ACTION["PROCESS_STEPS"] = "processSteps";
48
+ // https://data-portal.internal.atlassian.com/analytics/registry/85229
49
+ // Fired once per received transaction that contains agent-authored steps. Needs a data-portal
50
+ // analytics registry entry before the experiment is enabled broadly.
51
+ EVENT_ACTION["AGENT_EDIT_RECEIVED"] = "agentEditReceived";
48
52
  return EVENT_ACTION;
49
53
  }({});
50
54
  export let EVENT_STATUS = /*#__PURE__*/function (EVENT_STATUS) {
@@ -118,7 +118,6 @@ export class ParticipantsService {
118
118
  * presence consumer on each streamed step.
119
119
  *
120
120
  * @param providerId `agent:<aaid|type>` id of the agent
121
- * @example
122
121
  */
123
122
  _defineProperty(this, "upsertAIProviderParticipantLocally", providerId => {
124
123
  const payload = this.buildAIProviderPresencePayload(providerId);
@@ -152,7 +151,6 @@ export class ParticipantsService {
152
151
  /**
153
152
  * (Re)starts the sliding 30s inactivity timer for an agent participant. Called on every agent
154
153
  * step so the window slides forward from the agent's most recent activity.
155
- * @example
156
154
  */
157
155
  _defineProperty(this, "resetAgentPresenceTimer", sessionId => {
158
156
  const existingTimer = this.agentPresenceTimers.get(sessionId);
@@ -164,7 +162,6 @@ export class ParticipantsService {
164
162
  /**
165
163
  * Removes an inactive agent participant once its sliding window elapses and emits the `presence`
166
164
  * leave so the facepile drops it.
167
- * @example
168
165
  */
169
166
  _defineProperty(this, "removeAgentParticipant", sessionId => {
170
167
  this.agentPresenceTimers.delete(sessionId);
@@ -180,7 +177,6 @@ export class ParticipantsService {
180
177
  /**
181
178
  * Clears all pending agent presence sliding timers (on disconnect/destroy) so they don't fire
182
179
  * against cleared state.
183
- * @example
184
180
  */
185
181
  _defineProperty(this, "clearAgentPresenceTimers", () => {
186
182
  this.agentPresenceTimers.forEach(timer => clearTimeout(timer));
@@ -1,5 +1,5 @@
1
1
  export const name = "@atlaskit/collab-provider";
2
- export const version = "21.1.2";
2
+ export const version = "21.1.3";
3
3
  export const nextMajorVersion = () => {
4
4
  return [Number(version.split('.')[0]) + 1, 0, 0].join('.');
5
5
  };
@@ -1,3 +1,4 @@
1
+ import _toConsumableArray from "@babel/runtime/helpers/toConsumableArray";
1
2
  import _asyncToGenerator from "@babel/runtime/helpers/asyncToGenerator";
2
3
  import _classCallCheck from "@babel/runtime/helpers/classCallCheck";
3
4
  import _createClass from "@babel/runtime/helpers/createClass";
@@ -18,7 +19,7 @@ import { expValEquals } from '@atlaskit/tmp-editor-statsig/exp-val-equals';
18
19
  import { ACK_MAX_TRY, EVENT_ACTION, EVENT_STATUS, CatchupEventReason } from '../helpers/const';
19
20
  import { MEASURE_NAME, startMeasure, stopMeasure } from '../analytics/performance';
20
21
  import { INTERNAL_ERROR_CODE } from '../errors/internal-errors';
21
- import { createLogger, getAgentProviderId, getDocAdfWithObfuscationFromJSON, getObfuscatedSteps, getStepUGCFreeDetails, sleep } from '../helpers/utils';
22
+ import { createLogger, getAgentProviderId, getDocAdfWithObfuscationFromJSON, getObfuscatedSteps, getStepUGCFreeDetails, normalizeAgentId, sleep } from '../helpers/utils';
22
23
  import { MAX_STEP_REJECTED_ERROR, MAX_STEP_REJECTED_ERROR_AGGRESSIVE } from '../provider';
23
24
  import { CommitStepService } from '../provider/commit-step';
24
25
  import { CantSyncUpError, UpdateDocumentError } from '../errors/custom-errors';
@@ -942,17 +943,15 @@ export var DocumentService = /*#__PURE__*/function () {
942
943
  }
943
944
 
944
945
  /**
945
- * Detect agent-authored steps in a received batch and register
946
- * each distinct agent as a local participant so it appears in the presence facepile.
946
+ * Detect agent-authored steps in a received batch and register each distinct agent as a local
947
+ * participant so it appears in the presence facepile.
947
948
  *
948
- * Gated behind a default-OFF feature gate: `collab-provider` is shared platform infra (used by
949
- * Jira JWM/JPD etc.), so this is a strict no-op everywhere unless the gate is explicitly enabled.
950
- *
951
- * Isolated in its own try/catch so a presence failure can never affect step persistence.
949
+ * Gated behind a default-OFF gate because `collab-provider` is shared platform infra (Jira
950
+ * JWM/JPD etc.), and isolated in its own try/catch so a presence failure can never affect step
951
+ * persistence.
952
952
  *
953
953
  * Known limitation (PoC): steps replayed via catch-up/reconnect can briefly re-surface an agent
954
954
  * whose activity is stale — a freshness guard is a follow-up.
955
- * @example
956
955
  */
957
956
  }, {
958
957
  key: "maybeAddAgentPresenceFromSteps",
@@ -962,28 +961,57 @@ export var DocumentService = /*#__PURE__*/function () {
962
961
  return;
963
962
  }
964
963
  try {
964
+ var _this$analyticsHelper34;
965
965
  var providerIds = new Set();
966
+ var agentIds = new Set();
967
+ var agentTypes = new Set();
968
+ var agentStepCount = 0;
966
969
  var _iterator = _createForOfIteratorHelper(steps),
967
970
  _step;
968
971
  try {
969
972
  for (_iterator.s(); !(_step = _iterator.n()).done;) {
970
973
  var step = _step.value;
971
974
  var providerId = getAgentProviderId(step);
972
- if (providerId) {
973
- providerIds.add(providerId);
975
+ if (!providerId) {
976
+ continue;
977
+ }
978
+ providerIds.add(providerId);
979
+ agentStepCount++;
980
+ var _ref10 = step,
981
+ agentId = _ref10.agentId,
982
+ agentType = _ref10.agentType;
983
+ if (agentType) {
984
+ agentTypes.add(agentType);
985
+ }
986
+ if (agentId) {
987
+ agentIds.add(normalizeAgentId(agentId));
974
988
  }
975
989
  }
990
+ // No agent-authored steps in this batch — nothing to register or report.
976
991
  } catch (err) {
977
992
  _iterator.e(err);
978
993
  } finally {
979
994
  _iterator.f();
980
995
  }
996
+ if (providerIds.size === 0) {
997
+ return;
998
+ }
999
+ // Anchor signal that the agent-presence logic kicked off for a received transaction. Fires
1000
+ // once per transaction (agent edits arrive as batches, not per keystroke). Attributes are
1001
+ // non-PII: agent kinds, actor ids, and counts — never document content.
1002
+ (_this$analyticsHelper34 = this.analyticsHelper) === null || _this$analyticsHelper34 === void 0 || _this$analyticsHelper34.sendActionEvent(EVENT_ACTION.AGENT_EDIT_RECEIVED, EVENT_STATUS.SUCCESS, {
1003
+ agentIds: _toConsumableArray(agentIds),
1004
+ agentCount: providerIds.size,
1005
+ agentTypes: _toConsumableArray(agentTypes),
1006
+ agentStepCount: agentStepCount,
1007
+ totalStepCount: steps.length
1008
+ });
981
1009
  providerIds.forEach(function (providerId) {
982
1010
  return _this3.participantsService.upsertAIProviderParticipantLocally(providerId);
983
1011
  });
984
1012
  } catch (error) {
985
- var _this$analyticsHelper34;
986
- (_this$analyticsHelper34 = this.analyticsHelper) === null || _this$analyticsHelper34 === void 0 || _this$analyticsHelper34.sendErrorEvent(error, 'Error while adding agent presence from steps');
1013
+ var _this$analyticsHelper35;
1014
+ (_this$analyticsHelper35 = this.analyticsHelper) === null || _this$analyticsHelper35 === void 0 || _this$analyticsHelper35.sendErrorEvent(error, 'Error while adding agent presence from steps');
987
1015
  }
988
1016
  }
989
1017
  }, {
@@ -1007,10 +1035,10 @@ export var DocumentService = /*#__PURE__*/function () {
1007
1035
  * @param root0.clientId
1008
1036
  * @example
1009
1037
  */
1010
- function setup(_ref10) {
1011
- var getState = _ref10.getState,
1012
- onSyncUpError = _ref10.onSyncUpError,
1013
- clientId = _ref10.clientId;
1038
+ function setup(_ref11) {
1039
+ var getState = _ref11.getState,
1040
+ onSyncUpError = _ref11.onSyncUpError,
1041
+ clientId = _ref11.clientId;
1014
1042
  this.getState = getState;
1015
1043
  this.onSyncUpError = onSyncUpError || noop;
1016
1044
  this.clientId = clientId;
@@ -1033,8 +1061,8 @@ export var DocumentService = /*#__PURE__*/function () {
1033
1061
  var _this$getState0;
1034
1062
  var state = (_this$getState0 = this.getState) === null || _this$getState0 === void 0 ? void 0 : _this$getState0.call(this);
1035
1063
  if (!state) {
1036
- var _this$analyticsHelper35;
1037
- (_this$analyticsHelper35 = this.analyticsHelper) === null || _this$analyticsHelper35 === void 0 || _this$analyticsHelper35.sendErrorEvent(new Error('Editor state is undefined'), 'sendStepsFromCurrentState called without state');
1064
+ var _this$analyticsHelper36;
1065
+ (_this$analyticsHelper36 = this.analyticsHelper) === null || _this$analyticsHelper36 === void 0 || _this$analyticsHelper36.sendErrorEvent(new Error('Editor state is undefined'), 'sendStepsFromCurrentState called without state');
1038
1066
  return;
1039
1067
  }
1040
1068
  this.send(null, null, state, sendAnalyticsEvent, reason);
@@ -1085,8 +1113,8 @@ export var DocumentService = /*#__PURE__*/function () {
1085
1113
  // to ensure that analytics events with the number of unconfirmed steps is only
1086
1114
  // sent once on connection (as opposed to on every step)
1087
1115
  if (sendAnalyticsEvent) {
1088
- var _this$analyticsHelper36;
1089
- (_this$analyticsHelper36 = this.analyticsHelper) === null || _this$analyticsHelper36 === void 0 || _this$analyticsHelper36.sendActionEvent(EVENT_ACTION.HAS_UNCONFIRMED_STEPS, EVENT_STATUS.INFO, {
1116
+ var _this$analyticsHelper37;
1117
+ (_this$analyticsHelper37 = this.analyticsHelper) === null || _this$analyticsHelper37 === void 0 || _this$analyticsHelper37.sendActionEvent(EVENT_ACTION.HAS_UNCONFIRMED_STEPS, EVENT_STATUS.INFO, {
1090
1118
  numUnconfirmedSteps: (unconfirmedSteps === null || unconfirmedSteps === void 0 ? void 0 : unconfirmedSteps.length) || 0
1091
1119
  });
1092
1120
  }
@@ -1095,12 +1123,12 @@ export var DocumentService = /*#__PURE__*/function () {
1095
1123
  }
1096
1124
  var rebased = tr === null || tr === void 0 ? void 0 : tr.getMeta('rebasedData');
1097
1125
  if (rebased) {
1098
- var _this$analyticsHelper37;
1126
+ var _this$analyticsHelper38;
1099
1127
  var obfuscatedUnconfirmedSteps = this.obfuscateStepsAndState(rebased.unconfirmedSteps).obfuscatedSteps;
1100
1128
  var obfuscatedRemoteSteps = this.obfuscateStepsAndState(rebased.remoteSteps);
1101
1129
  var obfuscatedRebasedSteps = this.obfuscateStepsAndState(unconfirmedSteps).obfuscatedSteps;
1102
1130
  // send analtyics on unconfirmed steps
1103
- (_this$analyticsHelper37 = this.analyticsHelper) === null || _this$analyticsHelper37 === void 0 || _this$analyticsHelper37.sendActionEvent(EVENT_ACTION.STEPS_REBASED, EVENT_STATUS.INFO, {
1131
+ (_this$analyticsHelper38 = this.analyticsHelper) === null || _this$analyticsHelper38 === void 0 || _this$analyticsHelper38.sendActionEvent(EVENT_ACTION.STEPS_REBASED, EVENT_STATUS.INFO, {
1104
1132
  obfuscatedUnconfirmedSteps: obfuscatedUnconfirmedSteps,
1105
1133
  obfuscatedRemoteSteps: obfuscatedRemoteSteps,
1106
1134
  obfuscatedRebasedSteps: obfuscatedRebasedSteps,
@@ -44,7 +44,11 @@ export var EVENT_ACTION = /*#__PURE__*/function (EVENT_ACTION) {
44
44
  EVENT_ACTION["STEPS_REBASED"] = "stepsRebased";
45
45
  // https://data-portal.internal.atlassian.com/analytics/registry/76616
46
46
  EVENT_ACTION["POLLING_FALLBACK"] = "pollingFallback";
47
- EVENT_ACTION["PROCESS_STEPS"] = "processSteps"; // https://data-portal.internal.atlassian.com/analytics/registry/85229
47
+ EVENT_ACTION["PROCESS_STEPS"] = "processSteps";
48
+ // https://data-portal.internal.atlassian.com/analytics/registry/85229
49
+ // Fired once per received transaction that contains agent-authored steps. Needs a data-portal
50
+ // analytics registry entry before the experiment is enabled broadly.
51
+ EVENT_ACTION["AGENT_EDIT_RECEIVED"] = "agentEditReceived";
48
52
  return EVENT_ACTION;
49
53
  }({});
50
54
  export var EVENT_STATUS = /*#__PURE__*/function (EVENT_STATUS) {
@@ -126,7 +126,6 @@ export var ParticipantsService = /*#__PURE__*/function () {
126
126
  * presence consumer on each streamed step.
127
127
  *
128
128
  * @param providerId `agent:<aaid|type>` id of the agent
129
- * @example
130
129
  */
131
130
  _defineProperty(this, "upsertAIProviderParticipantLocally", function (providerId) {
132
131
  var payload = _this.buildAIProviderPresencePayload(providerId);
@@ -157,7 +156,6 @@ export var ParticipantsService = /*#__PURE__*/function () {
157
156
  /**
158
157
  * (Re)starts the sliding 30s inactivity timer for an agent participant. Called on every agent
159
158
  * step so the window slides forward from the agent's most recent activity.
160
- * @example
161
159
  */
162
160
  _defineProperty(this, "resetAgentPresenceTimer", function (sessionId) {
163
161
  var existingTimer = _this.agentPresenceTimers.get(sessionId);
@@ -171,7 +169,6 @@ export var ParticipantsService = /*#__PURE__*/function () {
171
169
  /**
172
170
  * Removes an inactive agent participant once its sliding window elapses and emits the `presence`
173
171
  * leave so the facepile drops it.
174
- * @example
175
172
  */
176
173
  _defineProperty(this, "removeAgentParticipant", function (sessionId) {
177
174
  _this.agentPresenceTimers.delete(sessionId);
@@ -187,7 +184,6 @@ export var ParticipantsService = /*#__PURE__*/function () {
187
184
  /**
188
185
  * Clears all pending agent presence sliding timers (on disconnect/destroy) so they don't fire
189
186
  * against cleared state.
190
- * @example
191
187
  */
192
188
  _defineProperty(this, "clearAgentPresenceTimers", function () {
193
189
  _this.agentPresenceTimers.forEach(function (timer) {
@@ -1,5 +1,5 @@
1
1
  export var name = "@atlaskit/collab-provider";
2
- export var version = "21.1.2";
2
+ export var version = "21.1.3";
3
3
  export var nextMajorVersion = function nextMajorVersion() {
4
4
  return [Number(version.split('.')[0]) + 1, 0, 0].join('.');
5
5
  };
@@ -95,17 +95,15 @@ export declare class DocumentService implements DocumentServiceInterface {
95
95
  private isStepsFromNewClientIdForSameUserId;
96
96
  private processSteps;
97
97
  /**
98
- * Detect agent-authored steps in a received batch and register
99
- * each distinct agent as a local participant so it appears in the presence facepile.
98
+ * Detect agent-authored steps in a received batch and register each distinct agent as a local
99
+ * participant so it appears in the presence facepile.
100
100
  *
101
- * Gated behind a default-OFF feature gate: `collab-provider` is shared platform infra (used by
102
- * Jira JWM/JPD etc.), so this is a strict no-op everywhere unless the gate is explicitly enabled.
103
- *
104
- * Isolated in its own try/catch so a presence failure can never affect step persistence.
101
+ * Gated behind a default-OFF gate because `collab-provider` is shared platform infra (Jira
102
+ * JWM/JPD etc.), and isolated in its own try/catch so a presence failure can never affect step
103
+ * persistence.
105
104
  *
106
105
  * Known limitation (PoC): steps replayed via catch-up/reconnect can briefly re-surface an agent
107
106
  * whose activity is stale — a freshness guard is a follow-up.
108
- * @example
109
107
  */
110
108
  private maybeAddAgentPresenceFromSteps;
111
109
  getUnconfirmedStepsOrigins: () => readonly Transaction[] | undefined;
@@ -23,7 +23,8 @@ export declare enum EVENT_ACTION {
23
23
  OUT_OF_SYNC = "outOfSync",// https://data-portal.internal.atlassian.com/analytics/registry/74993
24
24
  STEPS_REBASED = "stepsRebased",// https://data-portal.internal.atlassian.com/analytics/registry/76616
25
25
  POLLING_FALLBACK = "pollingFallback",
26
- PROCESS_STEPS = "processSteps"
26
+ PROCESS_STEPS = "processSteps",// https://data-portal.internal.atlassian.com/analytics/registry/85229
27
+ AGENT_EDIT_RECEIVED = "agentEditReceived"
27
28
  }
28
29
  export declare enum EVENT_STATUS {
29
30
  SUCCESS = "SUCCESS",
@@ -311,7 +312,18 @@ type ProcessStepsAnalyticsEvent = {
311
312
  } & BaseActionAnalyticsEventAttributes;
312
313
  eventAction: EVENT_ACTION.PROCESS_STEPS;
313
314
  };
314
- export type ActionAnalyticsEvent = AddStepsSuccessAnalyticsEvent | AddStepsFailureAnalyticsEvent | ReInitDocFailAnalyticsEvent | ReInitDocSuccessAnalyticsEvent | ConnectionSuccessAnalyticsEvent | ConnectionFailureAnalyticsEvent | CatchUpSuccessAnalyticsEvent | CatchUpFailureAnalyticsEvent | DocumentInitSuccessAnalyticsEvent | UpdateParticipantsSuccessAnalyticsEvent | CommitUnconfirmedStepsSuccessAnalyticsEvent | CommitUnconfirmedStepsFailureAnalyticsEvent | PublishPageSuccessAnalyticsEvent | PublishPageFailureAnalyticsEvent | GetCurrentStateSuccessAnalyticsEvent | GetCurrentStateFailureAnalyticsEvent | InvalidateTokenAnalyticsEvent | SendStepsRetryAnalyticsEvent | CatchupAfterMaxSendStepsRetryAnalyticsEvent | CatchUpDroppedStepsEvent | WebsocketMessageVolumeMetricEvent | ProviderInitializedAnalyticsEvent | ProviderSetupAnalyticsEvent | ProviderHasUnconfirmedStepsAnalyticsEvent | UpdateDocumentAnalyticsEvent | ReconnectionAnalyticsEvent | OutOfSyncAnalyticsEvent | StepsRebasedAnalyticsEvent | PollingFallbackAnalyticsEvent | ProcessStepsAnalyticsEvent;
315
+ type AgentEditReceivedAnalyticsEvent = {
316
+ attributes: {
317
+ agentCount: number;
318
+ agentIds: string[];
319
+ agentStepCount: number;
320
+ agentTypes: string[];
321
+ eventStatus: EVENT_STATUS.SUCCESS;
322
+ totalStepCount: number;
323
+ } & BaseActionAnalyticsEventAttributes;
324
+ eventAction: EVENT_ACTION.AGENT_EDIT_RECEIVED;
325
+ };
326
+ export type ActionAnalyticsEvent = AddStepsSuccessAnalyticsEvent | AddStepsFailureAnalyticsEvent | ReInitDocFailAnalyticsEvent | ReInitDocSuccessAnalyticsEvent | ConnectionSuccessAnalyticsEvent | ConnectionFailureAnalyticsEvent | CatchUpSuccessAnalyticsEvent | CatchUpFailureAnalyticsEvent | DocumentInitSuccessAnalyticsEvent | UpdateParticipantsSuccessAnalyticsEvent | CommitUnconfirmedStepsSuccessAnalyticsEvent | CommitUnconfirmedStepsFailureAnalyticsEvent | PublishPageSuccessAnalyticsEvent | PublishPageFailureAnalyticsEvent | GetCurrentStateSuccessAnalyticsEvent | GetCurrentStateFailureAnalyticsEvent | InvalidateTokenAnalyticsEvent | SendStepsRetryAnalyticsEvent | CatchupAfterMaxSendStepsRetryAnalyticsEvent | CatchUpDroppedStepsEvent | WebsocketMessageVolumeMetricEvent | ProviderInitializedAnalyticsEvent | ProviderSetupAnalyticsEvent | ProviderHasUnconfirmedStepsAnalyticsEvent | UpdateDocumentAnalyticsEvent | ReconnectionAnalyticsEvent | OutOfSyncAnalyticsEvent | StepsRebasedAnalyticsEvent | PollingFallbackAnalyticsEvent | ProcessStepsAnalyticsEvent | AgentEditReceivedAnalyticsEvent;
315
327
  export declare const ACK_MAX_TRY = 60;
316
328
  export declare const CONFLUENCE = "confluence";
317
329
  /** Enumerable for attaching a reason to catchup (v2) call(s) */
@@ -82,25 +82,21 @@ export declare class ParticipantsService {
82
82
  * presence consumer on each streamed step.
83
83
  *
84
84
  * @param providerId `agent:<aaid|type>` id of the agent
85
- * @example
86
85
  */
87
86
  upsertAIProviderParticipantLocally: (providerId: string) => void;
88
87
  /**
89
88
  * (Re)starts the sliding 30s inactivity timer for an agent participant. Called on every agent
90
89
  * step so the window slides forward from the agent's most recent activity.
91
- * @example
92
90
  */
93
91
  private resetAgentPresenceTimer;
94
92
  /**
95
93
  * Removes an inactive agent participant once its sliding window elapses and emits the `presence`
96
94
  * leave so the facepile drops it.
97
- * @example
98
95
  */
99
96
  private removeAgentParticipant;
100
97
  /**
101
98
  * Clears all pending agent presence sliding timers (on disconnect/destroy) so they don't fire
102
99
  * against cleared state.
103
- * @example
104
100
  */
105
101
  private clearAgentPresenceTimers;
106
102
  private hasPresenceActivityChanged;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@atlaskit/collab-provider",
3
- "version": "21.1.3",
3
+ "version": "21.2.0",
4
4
  "description": "A provider for collaborative editing.",
5
5
  "publishConfig": {
6
6
  "registry": "https://registry.npmjs.org/"
@@ -71,6 +71,6 @@
71
71
  }
72
72
  },
73
73
  "peerDependencies": {
74
- "@atlaskit/editor-common": "^116.41.0"
74
+ "@atlaskit/editor-common": "^116.42.0"
75
75
  }
76
76
  }