@atlaskit/rovo-triggers 10.30.0 → 10.33.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.
Files changed (21) hide show
  1. package/CHANGELOG.md +46 -0
  2. package/dist/cjs/common/utils/extension-context-bridge/ExtensionContextBridgeClient.js +4 -0
  3. package/dist/cjs/common/utils/extension-context-bridge/ExtensionContextBridgeHost.js +66 -2
  4. package/dist/cjs/common/utils/extension-context-bridge/constants.js +11 -2
  5. package/dist/cjs/common/utils/extension-context-bridge/transport.js +14 -2
  6. package/dist/cjs/common/utils/params/constants.js +1 -1
  7. package/dist/es2019/common/utils/extension-context-bridge/ExtensionContextBridgeClient.js +5 -1
  8. package/dist/es2019/common/utils/extension-context-bridge/ExtensionContextBridgeHost.js +71 -8
  9. package/dist/es2019/common/utils/extension-context-bridge/constants.js +10 -1
  10. package/dist/es2019/common/utils/extension-context-bridge/transport.js +12 -2
  11. package/dist/es2019/common/utils/params/constants.js +1 -1
  12. package/dist/esm/common/utils/extension-context-bridge/ExtensionContextBridgeClient.js +5 -1
  13. package/dist/esm/common/utils/extension-context-bridge/ExtensionContextBridgeHost.js +69 -5
  14. package/dist/esm/common/utils/extension-context-bridge/constants.js +10 -1
  15. package/dist/esm/common/utils/extension-context-bridge/transport.js +14 -2
  16. package/dist/esm/common/utils/params/constants.js +1 -1
  17. package/dist/types/common/utils/extension-context-bridge/constants.d.ts +8 -0
  18. package/dist/types/common/utils/extension-context-bridge/transport.d.ts +14 -3
  19. package/dist/types/common/utils/params/types.d.ts +1 -0
  20. package/dist/types/types.d.ts +9 -0
  21. package/package.json +2 -2
package/CHANGELOG.md CHANGED
@@ -1,5 +1,51 @@
1
1
  # @atlaskit/rovo-triggers
2
2
 
3
+ ## 10.33.0
4
+
5
+ ### Minor Changes
6
+
7
+ - [`692f360535ec2`](https://bitbucket.org/atlassian/atlassian-frontend-monorepo/commits/692f360535ec2) -
8
+ Under sprt_hix_9681_workflow_agent_edit_status, group workflow actions under numbered headings in
9
+ a fixed order, show a consistent previous/updated status diff, fold the shared-workflow warning
10
+ into the update-status card, show a trash can icon on delete cards, and show the transition name
11
+ with inline crossed-out previous name and statuses on update-transition cards.
12
+
13
+ Adds optional previous-state fields on UpdateTransitionRovoPayload (existingName,
14
+ existingToStatusId, existingToStatusName, existingToStatusCategory, existingLinks) and exports
15
+ DeleteTransitionRovoPayloadOld.
16
+
17
+ ## 10.32.0
18
+
19
+ ### Minor Changes
20
+
21
+ - [`2aa696d5503d0`](https://bitbucket.org/atlassian/atlassian-frontend-monorepo/commits/2aa696d5503d0) -
22
+ Add a control-message channel to the extension context bridge.
23
+
24
+ `request-context` is a message the extension chat may send a product, asking it to re-send the
25
+ context it has already published. It carries no data: it is a request, not content, and is
26
+ mutually exclusive with payload messages by construction, so it cannot be used to move content in
27
+ either direction or to bypass the relay allowlists.
28
+
29
+ This exists because the bridge is otherwise fire-and-forget, relaying only what it observes while
30
+ a chat is listening. That suits a product whose publisher runs continuously, such as the
31
+ Confluence editor, but not one that publishes once per navigation, such as a Jira view: a chat
32
+ opened after the page settled would observe nothing at all.
33
+
34
+ Additive. `Transport.subscribe` now also carries control messages, so consumers that assume every
35
+ message has a payload should narrow with the exported `isBridgeControlMessage` guard. Nothing
36
+ sends or answers requests yet.
37
+
38
+ ## 10.31.0
39
+
40
+ ### Minor Changes
41
+
42
+ - [`4d3888ae4550d`](https://bitbucket.org/atlassian/atlassian-frontend-monorepo/commits/4d3888ae4550d) -
43
+ Allow shared Rovo URL parameters to carry a search query using the `rovoChatSearchQuery` key.
44
+
45
+ ### Patch Changes
46
+
47
+ - Updated dependencies
48
+
3
49
  ## 10.30.0
4
50
 
5
51
  ### Minor Changes
@@ -93,6 +93,10 @@ var ExtensionContextBridgeClient = exports.ExtensionContextBridgeClient = functi
93
93
  return;
94
94
  }
95
95
  return activeTransport.subscribe(function (message) {
96
+ // Control messages are requests to the product, never context for the chat.
97
+ if ((0, _transport.isBridgeControlMessage)(message)) {
98
+ return;
99
+ }
96
100
  if (message.direction !== _constants.BRIDGE_TO_EXTENSION) {
97
101
  return;
98
102
  }
@@ -6,6 +6,7 @@ Object.defineProperty(exports, "__esModule", {
6
6
  });
7
7
  exports.ExtensionContextBridgeHost = void 0;
8
8
  var _defineProperty2 = _interopRequireDefault(require("@babel/runtime/helpers/defineProperty"));
9
+ var _typeof2 = _interopRequireDefault(require("@babel/runtime/helpers/typeof"));
9
10
  var _react = require("react");
10
11
  var _fg = require("@atlaskit/platform-feature-flags/fg");
11
12
  var _main = require("../../../main");
@@ -32,6 +33,22 @@ var stampProductContentId = function stampProductContentId(serialized, targetCon
32
33
  data.contentMauiId = targetContentId;
33
34
  }
34
35
  };
36
+
37
+ /**
38
+ * Identity of a context *slot*. `set-message-context` is keyed by `contextKey` because a product
39
+ * publishes several independent contexts, each superseding only its own previous value; everything
40
+ * else is keyed by payload type, of which there is one live instance at a time.
41
+ */
42
+ var getContextSlotKey = function getContextSlotKey(payload) {
43
+ if (payload.type === 'set-message-context') {
44
+ var data = payload.data;
45
+ var contextKey = (0, _typeof2.default)(data) === 'object' && data !== null && 'contextKey' in data ? data.contextKey : undefined;
46
+ if (typeof contextKey === 'string') {
47
+ return "".concat(payload.type, ":").concat(contextKey);
48
+ }
49
+ }
50
+ return payload.type;
51
+ };
35
52
  /**
36
53
  * Product-window half of the bridge. Observes the `ai-mate` bus and mirrors allowlisted context out
37
54
  * to the extension via same-window `postMessage` (see `./policy` for the allow/deny rules).
@@ -60,6 +77,43 @@ var ExtensionContextBridgeHost = exports.ExtensionContextBridgeHost = function E
60
77
  targetWindow: window
61
78
  });
62
79
  }, [active, transport]);
80
+
81
+ /**
82
+ * The last message relayed for each context slot, kept so a chat opened later can be caught up.
83
+ *
84
+ * Deliberately held here rather than in the extension. This is the page's own context, already in
85
+ * this page's memory, scoped to this document and gone when it unloads — so answering a request
86
+ * creates no copy that outlives the page, and a chat that is never opened is never sent anything.
87
+ */
88
+ var lastRelayedRef = (0, _react.useRef)(new Map());
89
+
90
+ // Answers `request-context` from the chat, which cannot observe context published before it
91
+ // mounted. Products such as Jira publish once per navigation, so without this a chat opened after
92
+ // the page settled would describe the wrong view — or none at all.
93
+ (0, _react.useEffect)(function () {
94
+ if (!activeTransport) {
95
+ return;
96
+ }
97
+ return activeTransport.subscribe(function (message) {
98
+ if (!(0, _transport.isBridgeControlMessage)(message) || message.direction !== _constants.BRIDGE_TO_PRODUCT) {
99
+ return;
100
+ }
101
+ if (message.control !== _constants.BRIDGE_CONTROL_REQUEST_CONTEXT) {
102
+ return;
103
+ }
104
+ // Re-checked here, not just at relay time: entitlement can change while the page is open.
105
+ if (isAiEnabled === false) {
106
+ return;
107
+ }
108
+ lastRelayedRef.current.forEach(function (relayed) {
109
+ try {
110
+ activeTransport.send(relayed);
111
+ } catch (_unused) {
112
+ // Never let bridging break the host page.
113
+ }
114
+ });
115
+ });
116
+ }, [activeTransport, isAiEnabled]);
63
117
  (0, _main.useSubscribeAll)(function (payload) {
64
118
  if (!activeTransport) {
65
119
  return;
@@ -82,9 +136,19 @@ var ExtensionContextBridgeHost = exports.ExtensionContextBridgeHost = function E
82
136
  return;
83
137
  }
84
138
  stampProductContentId(serialized, getContentId === null || getContentId === void 0 ? void 0 : getContentId());
139
+ var message = (0, _defineProperty2.default)((0, _defineProperty2.default)((0, _defineProperty2.default)({}, _constants.BRIDGE_MESSAGE_MARKER, true), "direction", _constants.BRIDGE_TO_EXTENSION), "payload", serialized);
140
+ // Keyed by context slot, not by message, so a re-publish replaces rather than accumulates.
141
+ // A cleared context is recorded too: replaying it clears the chat rather than resurrecting
142
+ // context the page no longer shows.
143
+ //
144
+ // Publisher contract: in an SPA this component outlives any single route, so a slot stays
145
+ // filled until its publisher says otherwise. Publishers must clear on unmount (publish with
146
+ // `setContext` returning undefined) or a later route could be answered with a previous
147
+ // view's context. Every Jira view-context publisher does this today.
148
+ lastRelayedRef.current.set(getContextSlotKey(serialized), message);
85
149
  try {
86
- activeTransport.send((0, _defineProperty2.default)((0, _defineProperty2.default)((0, _defineProperty2.default)({}, _constants.BRIDGE_MESSAGE_MARKER, true), "direction", _constants.BRIDGE_TO_EXTENSION), "payload", serialized));
87
- } catch (_unused) {
150
+ activeTransport.send(message);
151
+ } catch (_unused2) {
88
152
  // Never let bridging break the host page.
89
153
  }
90
154
  });
@@ -3,7 +3,7 @@
3
3
  Object.defineProperty(exports, "__esModule", {
4
4
  value: true
5
5
  });
6
- exports.BRIDGE_TO_PRODUCT = exports.BRIDGE_TO_EXTENSION = exports.BRIDGE_SOURCE = exports.BRIDGE_MESSAGE_MARKER = exports.BRIDGE_FEATURE_GATE = void 0;
6
+ exports.BRIDGE_TO_PRODUCT = exports.BRIDGE_TO_EXTENSION = exports.BRIDGE_SOURCE = exports.BRIDGE_MESSAGE_MARKER = exports.BRIDGE_FEATURE_GATE = exports.BRIDGE_CONTROL_REQUEST_CONTEXT = void 0;
7
7
  /**
8
8
  * Feature gate for the extension context bridge; both Host and Client self-gate on it.
9
9
  * `fg()` requires a static literal, so call sites inline this value rather than importing it;
@@ -19,4 +19,13 @@ var BRIDGE_TO_EXTENSION = exports.BRIDGE_TO_EXTENSION = 'to-extension';
19
19
  var BRIDGE_TO_PRODUCT = exports.BRIDGE_TO_PRODUCT = 'to-product';
20
20
 
21
21
  /** Loop-guard sentinel stamped on re-published payloads so they never ping-pong. */
22
- var BRIDGE_SOURCE = exports.BRIDGE_SOURCE = 'rovo-extension-bridge';
22
+ var BRIDGE_SOURCE = exports.BRIDGE_SOURCE = 'rovo-extension-bridge';
23
+
24
+ /**
25
+ * Control-plane message asking the product to re-send the context it last relayed.
26
+ *
27
+ * Carries no data and is not a bus payload, so it deliberately bypasses the relay allow/deny lists
28
+ * in `./policy` — there is nothing in it to inject. The Host answers by re-sending what it already
29
+ * sent once; a request can never make it relay something the policy would have dropped.
30
+ */
31
+ var BRIDGE_CONTROL_REQUEST_CONTEXT = exports.BRIDGE_CONTROL_REQUEST_CONTEXT = 'request-context';
@@ -4,7 +4,7 @@ var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefau
4
4
  Object.defineProperty(exports, "__esModule", {
5
5
  value: true
6
6
  });
7
- exports.isBridgeMessage = exports.createWindowTransport = void 0;
7
+ exports.isBridgeMessage = exports.isBridgeControlMessage = exports.createWindowTransport = void 0;
8
8
  var _typeof2 = _interopRequireDefault(require("@babel/runtime/helpers/typeof"));
9
9
  var _bindEventListener = require("bind-event-listener");
10
10
  var _constants = require("./constants");
@@ -13,6 +13,12 @@ var _constants = require("./constants");
13
13
  * `set-message-context`'s `setContext`) are resolved to values by `./registry` first.
14
14
  */
15
15
 
16
+ /**
17
+ * Control-plane message. Distinguished from `BridgeMessage` by carrying `control` instead of
18
+ * `payload`, so a control message can never be mistaken for relayed context: the guards are
19
+ * mutually exclusive on the presence of those fields.
20
+ */
21
+
16
22
  /**
17
23
  * Transport abstraction so Host/Client work over any carrier: same-window `postMessage` on the
18
24
  * product side, iframe/side-panel `postMessage` in the chat iframe. Carries `BridgeMessage` only;
@@ -22,6 +28,12 @@ var _constants = require("./constants");
22
28
  var isBridgeMessage = exports.isBridgeMessage = function isBridgeMessage(value) {
23
29
  return (0, _typeof2.default)(value) === 'object' && value !== null && value[_constants.BRIDGE_MESSAGE_MARKER] === true && typeof value.direction === 'string' && (0, _typeof2.default)(value.payload) === 'object' && value.payload !== null;
24
30
  };
31
+ var isBridgeControlMessage = exports.isBridgeControlMessage = function isBridgeControlMessage(value) {
32
+ return (0, _typeof2.default)(value) === 'object' && value !== null && value[_constants.BRIDGE_MESSAGE_MARKER] === true && typeof value.direction === 'string' && value.control === _constants.BRIDGE_CONTROL_REQUEST_CONTEXT &&
33
+ // Mutually exclusive with `isBridgeMessage`: a control message carries no payload to relay, so a
34
+ // message bearing both is treated as a payload and held to the relay policy.
35
+ value.payload === undefined;
36
+ };
25
37
 
26
38
  /** `postMessage`-based transport. Callers override `targetWindow`/`self` for the iframe side. */
27
39
  var createWindowTransport = exports.createWindowTransport = function createWindowTransport(_ref) {
@@ -47,7 +59,7 @@ var createWindowTransport = exports.createWindowTransport = function createWindo
47
59
  if (event.origin !== acceptOrigin) {
48
60
  return;
49
61
  }
50
- if (isBridgeMessage(event.data)) {
62
+ if (isBridgeMessage(event.data) || isBridgeControlMessage(event.data)) {
51
63
  handler(event.data);
52
64
  }
53
65
  }
@@ -5,4 +5,4 @@ Object.defineProperty(exports, "__esModule", {
5
5
  });
6
6
  exports.ROVO_VALID_PARAMS = exports.ROVO_PARAM_PREFIX = void 0;
7
7
  var ROVO_PARAM_PREFIX = exports.ROVO_PARAM_PREFIX = 'rovoChat';
8
- var ROVO_VALID_PARAMS = exports.ROVO_VALID_PARAMS = ['pathway', 'agentId', 'conversationId', 'prompt', 'cloudId', 'triggerOpen', 'insertPrompt', 'stagingAreaOpen', 'messageIdSelectedForPreview', 'invocationIdSelectedForPreview', 'promptLibraryOpen', 'openChatMode', 'rovoJourneyId'];
8
+ var ROVO_VALID_PARAMS = exports.ROVO_VALID_PARAMS = ['pathway', 'agentId', 'conversationId', 'prompt', 'cloudId', 'triggerOpen', 'insertPrompt', 'stagingAreaOpen', 'messageIdSelectedForPreview', 'invocationIdSelectedForPreview', 'promptLibraryOpen', 'openChatMode', 'rovoJourneyId', 'searchQuery'];
@@ -4,7 +4,7 @@ import { usePublish, useSubscribeAll } from '../../../main';
4
4
  import { BRIDGE_MESSAGE_MARKER, BRIDGE_SOURCE, BRIDGE_TO_EXTENSION, BRIDGE_TO_PRODUCT } from './constants';
5
5
  import { shouldRelayToProduct } from './policy';
6
6
  import { deserializePayload, serializePayload } from './registry';
7
- import { createWindowTransport } from './transport';
7
+ import { createWindowTransport, isBridgeControlMessage } from './transport';
8
8
 
9
9
  /** Selection shape preserved across editor focus loss. */
10
10
 
@@ -85,6 +85,10 @@ export const ExtensionContextBridgeClient = ({
85
85
  return;
86
86
  }
87
87
  return activeTransport.subscribe(message => {
88
+ // Control messages are requests to the product, never context for the chat.
89
+ if (isBridgeControlMessage(message)) {
90
+ return;
91
+ }
88
92
  if (message.direction !== BRIDGE_TO_EXTENSION) {
89
93
  return;
90
94
  }
@@ -1,10 +1,10 @@
1
- import { useMemo } from 'react';
1
+ import { useEffect, useMemo, useRef } from 'react';
2
2
  import { fg } from '@atlaskit/platform-feature-flags/fg';
3
3
  import { useSubscribeAll } from '../../../main';
4
- import { BRIDGE_MESSAGE_MARKER, BRIDGE_SOURCE, BRIDGE_TO_EXTENSION } from './constants';
4
+ import { BRIDGE_CONTROL_REQUEST_CONTEXT, BRIDGE_MESSAGE_MARKER, BRIDGE_SOURCE, BRIDGE_TO_EXTENSION, BRIDGE_TO_PRODUCT } from './constants';
5
5
  import { getInboundDecision } from './policy';
6
6
  import { serializePayload } from './registry';
7
- import { createWindowTransport } from './transport';
7
+ import { createWindowTransport, isBridgeControlMessage } from './transport';
8
8
  /**
9
9
  * Stamps the page id onto `editor-context-payload` when the product left it unpopulated, since the
10
10
  * extension chat cannot derive its own `contentId` from `window.location` (that's the side panel, not
@@ -24,6 +24,22 @@ const stampProductContentId = (serialized, targetContentId) => {
24
24
  data.contentMauiId = targetContentId;
25
25
  }
26
26
  };
27
+
28
+ /**
29
+ * Identity of a context *slot*. `set-message-context` is keyed by `contextKey` because a product
30
+ * publishes several independent contexts, each superseding only its own previous value; everything
31
+ * else is keyed by payload type, of which there is one live instance at a time.
32
+ */
33
+ const getContextSlotKey = payload => {
34
+ if (payload.type === 'set-message-context') {
35
+ const data = payload.data;
36
+ const contextKey = typeof data === 'object' && data !== null && 'contextKey' in data ? data.contextKey : undefined;
37
+ if (typeof contextKey === 'string') {
38
+ return `${payload.type}:${contextKey}`;
39
+ }
40
+ }
41
+ return payload.type;
42
+ };
27
43
  /**
28
44
  * Product-window half of the bridge. Observes the `ai-mate` bus and mirrors allowlisted context out
29
45
  * to the extension via same-window `postMessage` (see `./policy` for the allow/deny rules).
@@ -53,6 +69,43 @@ export const ExtensionContextBridgeHost = ({
53
69
  targetWindow: window
54
70
  });
55
71
  }, [active, transport]);
72
+
73
+ /**
74
+ * The last message relayed for each context slot, kept so a chat opened later can be caught up.
75
+ *
76
+ * Deliberately held here rather than in the extension. This is the page's own context, already in
77
+ * this page's memory, scoped to this document and gone when it unloads — so answering a request
78
+ * creates no copy that outlives the page, and a chat that is never opened is never sent anything.
79
+ */
80
+ const lastRelayedRef = useRef(new Map());
81
+
82
+ // Answers `request-context` from the chat, which cannot observe context published before it
83
+ // mounted. Products such as Jira publish once per navigation, so without this a chat opened after
84
+ // the page settled would describe the wrong view — or none at all.
85
+ useEffect(() => {
86
+ if (!activeTransport) {
87
+ return;
88
+ }
89
+ return activeTransport.subscribe(message => {
90
+ if (!isBridgeControlMessage(message) || message.direction !== BRIDGE_TO_PRODUCT) {
91
+ return;
92
+ }
93
+ if (message.control !== BRIDGE_CONTROL_REQUEST_CONTEXT) {
94
+ return;
95
+ }
96
+ // Re-checked here, not just at relay time: entitlement can change while the page is open.
97
+ if (isAiEnabled === false) {
98
+ return;
99
+ }
100
+ lastRelayedRef.current.forEach(relayed => {
101
+ try {
102
+ activeTransport.send(relayed);
103
+ } catch {
104
+ // Never let bridging break the host page.
105
+ }
106
+ });
107
+ });
108
+ }, [activeTransport, isAiEnabled]);
56
109
  useSubscribeAll(payload => {
57
110
  if (!activeTransport) {
58
111
  return;
@@ -75,12 +128,22 @@ export const ExtensionContextBridgeHost = ({
75
128
  return;
76
129
  }
77
130
  stampProductContentId(serialized, getContentId === null || getContentId === void 0 ? void 0 : getContentId());
131
+ const message = {
132
+ [BRIDGE_MESSAGE_MARKER]: true,
133
+ direction: BRIDGE_TO_EXTENSION,
134
+ payload: serialized
135
+ };
136
+ // Keyed by context slot, not by message, so a re-publish replaces rather than accumulates.
137
+ // A cleared context is recorded too: replaying it clears the chat rather than resurrecting
138
+ // context the page no longer shows.
139
+ //
140
+ // Publisher contract: in an SPA this component outlives any single route, so a slot stays
141
+ // filled until its publisher says otherwise. Publishers must clear on unmount (publish with
142
+ // `setContext` returning undefined) or a later route could be answered with a previous
143
+ // view's context. Every Jira view-context publisher does this today.
144
+ lastRelayedRef.current.set(getContextSlotKey(serialized), message);
78
145
  try {
79
- activeTransport.send({
80
- [BRIDGE_MESSAGE_MARKER]: true,
81
- direction: BRIDGE_TO_EXTENSION,
82
- payload: serialized
83
- });
146
+ activeTransport.send(message);
84
147
  } catch {
85
148
  // Never let bridging break the host page.
86
149
  }
@@ -13,4 +13,13 @@ export const BRIDGE_TO_EXTENSION = 'to-extension';
13
13
  export const BRIDGE_TO_PRODUCT = 'to-product';
14
14
 
15
15
  /** Loop-guard sentinel stamped on re-published payloads so they never ping-pong. */
16
- export const BRIDGE_SOURCE = 'rovo-extension-bridge';
16
+ export const BRIDGE_SOURCE = 'rovo-extension-bridge';
17
+
18
+ /**
19
+ * Control-plane message asking the product to re-send the context it last relayed.
20
+ *
21
+ * Carries no data and is not a bus payload, so it deliberately bypasses the relay allow/deny lists
22
+ * in `./policy` — there is nothing in it to inject. The Host answers by re-sending what it already
23
+ * sent once; a request can never make it relay something the policy would have dropped.
24
+ */
25
+ export const BRIDGE_CONTROL_REQUEST_CONTEXT = 'request-context';
@@ -1,11 +1,17 @@
1
1
  import { bind } from 'bind-event-listener';
2
- import { BRIDGE_MESSAGE_MARKER } from './constants';
2
+ import { BRIDGE_CONTROL_REQUEST_CONTEXT, BRIDGE_MESSAGE_MARKER } from './constants';
3
3
 
4
4
  /**
5
5
  * A `rovo-triggers` payload reduced to a structured-clone-safe shape. Functions (e.g.
6
6
  * `set-message-context`'s `setContext`) are resolved to values by `./registry` first.
7
7
  */
8
8
 
9
+ /**
10
+ * Control-plane message. Distinguished from `BridgeMessage` by carrying `control` instead of
11
+ * `payload`, so a control message can never be mistaken for relayed context: the guards are
12
+ * mutually exclusive on the presence of those fields.
13
+ */
14
+
9
15
  /**
10
16
  * Transport abstraction so Host/Client work over any carrier: same-window `postMessage` on the
11
17
  * product side, iframe/side-panel `postMessage` in the chat iframe. Carries `BridgeMessage` only;
@@ -13,6 +19,10 @@ import { BRIDGE_MESSAGE_MARKER } from './constants';
13
19
  */
14
20
 
15
21
  export const isBridgeMessage = value => typeof value === 'object' && value !== null && value[BRIDGE_MESSAGE_MARKER] === true && typeof value.direction === 'string' && typeof value.payload === 'object' && value.payload !== null;
22
+ export const isBridgeControlMessage = value => typeof value === 'object' && value !== null && value[BRIDGE_MESSAGE_MARKER] === true && typeof value.direction === 'string' && value.control === BRIDGE_CONTROL_REQUEST_CONTEXT &&
23
+ // Mutually exclusive with `isBridgeMessage`: a control message carries no payload to relay, so a
24
+ // message bearing both is treated as a payload and held to the relay policy.
25
+ value.payload === undefined;
16
26
 
17
27
  /** `postMessage`-based transport. Callers override `targetWindow`/`self` for the iframe side. */
18
28
  export const createWindowTransport = ({
@@ -35,7 +45,7 @@ export const createWindowTransport = ({
35
45
  if (event.origin !== acceptOrigin) {
36
46
  return;
37
47
  }
38
- if (isBridgeMessage(event.data)) {
48
+ if (isBridgeMessage(event.data) || isBridgeControlMessage(event.data)) {
39
49
  handler(event.data);
40
50
  }
41
51
  }
@@ -1,2 +1,2 @@
1
1
  export const ROVO_PARAM_PREFIX = 'rovoChat';
2
- export const ROVO_VALID_PARAMS = ['pathway', 'agentId', 'conversationId', 'prompt', 'cloudId', 'triggerOpen', 'insertPrompt', 'stagingAreaOpen', 'messageIdSelectedForPreview', 'invocationIdSelectedForPreview', 'promptLibraryOpen', 'openChatMode', 'rovoJourneyId'];
2
+ export const ROVO_VALID_PARAMS = ['pathway', 'agentId', 'conversationId', 'prompt', 'cloudId', 'triggerOpen', 'insertPrompt', 'stagingAreaOpen', 'messageIdSelectedForPreview', 'invocationIdSelectedForPreview', 'promptLibraryOpen', 'openChatMode', 'rovoJourneyId', 'searchQuery'];
@@ -7,7 +7,7 @@ import { usePublish, useSubscribeAll } from '../../../main';
7
7
  import { BRIDGE_MESSAGE_MARKER, BRIDGE_SOURCE, BRIDGE_TO_EXTENSION, BRIDGE_TO_PRODUCT } from './constants';
8
8
  import { shouldRelayToProduct } from './policy';
9
9
  import { deserializePayload, serializePayload } from './registry';
10
- import { createWindowTransport } from './transport';
10
+ import { createWindowTransport, isBridgeControlMessage } from './transport';
11
11
 
12
12
  /** Selection shape preserved across editor focus loss. */
13
13
 
@@ -87,6 +87,10 @@ export var ExtensionContextBridgeClient = function ExtensionContextBridgeClient(
87
87
  return;
88
88
  }
89
89
  return activeTransport.subscribe(function (message) {
90
+ // Control messages are requests to the product, never context for the chat.
91
+ if (isBridgeControlMessage(message)) {
92
+ return;
93
+ }
90
94
  if (message.direction !== BRIDGE_TO_EXTENSION) {
91
95
  return;
92
96
  }
@@ -1,11 +1,12 @@
1
1
  import _defineProperty from "@babel/runtime/helpers/defineProperty";
2
- import { useMemo } from 'react';
2
+ import _typeof from "@babel/runtime/helpers/typeof";
3
+ import { useEffect, useMemo, useRef } from 'react';
3
4
  import { fg } from '@atlaskit/platform-feature-flags/fg';
4
5
  import { useSubscribeAll } from '../../../main';
5
- import { BRIDGE_MESSAGE_MARKER, BRIDGE_SOURCE, BRIDGE_TO_EXTENSION } from './constants';
6
+ import { BRIDGE_CONTROL_REQUEST_CONTEXT, BRIDGE_MESSAGE_MARKER, BRIDGE_SOURCE, BRIDGE_TO_EXTENSION, BRIDGE_TO_PRODUCT } from './constants';
6
7
  import { getInboundDecision } from './policy';
7
8
  import { serializePayload } from './registry';
8
- import { createWindowTransport } from './transport';
9
+ import { createWindowTransport, isBridgeControlMessage } from './transport';
9
10
  /**
10
11
  * Stamps the page id onto `editor-context-payload` when the product left it unpopulated, since the
11
12
  * extension chat cannot derive its own `contentId` from `window.location` (that's the side panel, not
@@ -25,6 +26,22 @@ var stampProductContentId = function stampProductContentId(serialized, targetCon
25
26
  data.contentMauiId = targetContentId;
26
27
  }
27
28
  };
29
+
30
+ /**
31
+ * Identity of a context *slot*. `set-message-context` is keyed by `contextKey` because a product
32
+ * publishes several independent contexts, each superseding only its own previous value; everything
33
+ * else is keyed by payload type, of which there is one live instance at a time.
34
+ */
35
+ var getContextSlotKey = function getContextSlotKey(payload) {
36
+ if (payload.type === 'set-message-context') {
37
+ var data = payload.data;
38
+ var contextKey = _typeof(data) === 'object' && data !== null && 'contextKey' in data ? data.contextKey : undefined;
39
+ if (typeof contextKey === 'string') {
40
+ return "".concat(payload.type, ":").concat(contextKey);
41
+ }
42
+ }
43
+ return payload.type;
44
+ };
28
45
  /**
29
46
  * Product-window half of the bridge. Observes the `ai-mate` bus and mirrors allowlisted context out
30
47
  * to the extension via same-window `postMessage` (see `./policy` for the allow/deny rules).
@@ -53,6 +70,43 @@ export var ExtensionContextBridgeHost = function ExtensionContextBridgeHost(_ref
53
70
  targetWindow: window
54
71
  });
55
72
  }, [active, transport]);
73
+
74
+ /**
75
+ * The last message relayed for each context slot, kept so a chat opened later can be caught up.
76
+ *
77
+ * Deliberately held here rather than in the extension. This is the page's own context, already in
78
+ * this page's memory, scoped to this document and gone when it unloads — so answering a request
79
+ * creates no copy that outlives the page, and a chat that is never opened is never sent anything.
80
+ */
81
+ var lastRelayedRef = useRef(new Map());
82
+
83
+ // Answers `request-context` from the chat, which cannot observe context published before it
84
+ // mounted. Products such as Jira publish once per navigation, so without this a chat opened after
85
+ // the page settled would describe the wrong view — or none at all.
86
+ useEffect(function () {
87
+ if (!activeTransport) {
88
+ return;
89
+ }
90
+ return activeTransport.subscribe(function (message) {
91
+ if (!isBridgeControlMessage(message) || message.direction !== BRIDGE_TO_PRODUCT) {
92
+ return;
93
+ }
94
+ if (message.control !== BRIDGE_CONTROL_REQUEST_CONTEXT) {
95
+ return;
96
+ }
97
+ // Re-checked here, not just at relay time: entitlement can change while the page is open.
98
+ if (isAiEnabled === false) {
99
+ return;
100
+ }
101
+ lastRelayedRef.current.forEach(function (relayed) {
102
+ try {
103
+ activeTransport.send(relayed);
104
+ } catch (_unused) {
105
+ // Never let bridging break the host page.
106
+ }
107
+ });
108
+ });
109
+ }, [activeTransport, isAiEnabled]);
56
110
  useSubscribeAll(function (payload) {
57
111
  if (!activeTransport) {
58
112
  return;
@@ -75,9 +129,19 @@ export var ExtensionContextBridgeHost = function ExtensionContextBridgeHost(_ref
75
129
  return;
76
130
  }
77
131
  stampProductContentId(serialized, getContentId === null || getContentId === void 0 ? void 0 : getContentId());
132
+ var message = _defineProperty(_defineProperty(_defineProperty({}, BRIDGE_MESSAGE_MARKER, true), "direction", BRIDGE_TO_EXTENSION), "payload", serialized);
133
+ // Keyed by context slot, not by message, so a re-publish replaces rather than accumulates.
134
+ // A cleared context is recorded too: replaying it clears the chat rather than resurrecting
135
+ // context the page no longer shows.
136
+ //
137
+ // Publisher contract: in an SPA this component outlives any single route, so a slot stays
138
+ // filled until its publisher says otherwise. Publishers must clear on unmount (publish with
139
+ // `setContext` returning undefined) or a later route could be answered with a previous
140
+ // view's context. Every Jira view-context publisher does this today.
141
+ lastRelayedRef.current.set(getContextSlotKey(serialized), message);
78
142
  try {
79
- activeTransport.send(_defineProperty(_defineProperty(_defineProperty({}, BRIDGE_MESSAGE_MARKER, true), "direction", BRIDGE_TO_EXTENSION), "payload", serialized));
80
- } catch (_unused) {
143
+ activeTransport.send(message);
144
+ } catch (_unused2) {
81
145
  // Never let bridging break the host page.
82
146
  }
83
147
  });
@@ -13,4 +13,13 @@ export var BRIDGE_TO_EXTENSION = 'to-extension';
13
13
  export var BRIDGE_TO_PRODUCT = 'to-product';
14
14
 
15
15
  /** Loop-guard sentinel stamped on re-published payloads so they never ping-pong. */
16
- export var BRIDGE_SOURCE = 'rovo-extension-bridge';
16
+ export var BRIDGE_SOURCE = 'rovo-extension-bridge';
17
+
18
+ /**
19
+ * Control-plane message asking the product to re-send the context it last relayed.
20
+ *
21
+ * Carries no data and is not a bus payload, so it deliberately bypasses the relay allow/deny lists
22
+ * in `./policy` — there is nothing in it to inject. The Host answers by re-sending what it already
23
+ * sent once; a request can never make it relay something the policy would have dropped.
24
+ */
25
+ export var BRIDGE_CONTROL_REQUEST_CONTEXT = 'request-context';
@@ -1,12 +1,18 @@
1
1
  import _typeof from "@babel/runtime/helpers/typeof";
2
2
  import { bind } from 'bind-event-listener';
3
- import { BRIDGE_MESSAGE_MARKER } from './constants';
3
+ import { BRIDGE_CONTROL_REQUEST_CONTEXT, BRIDGE_MESSAGE_MARKER } from './constants';
4
4
 
5
5
  /**
6
6
  * A `rovo-triggers` payload reduced to a structured-clone-safe shape. Functions (e.g.
7
7
  * `set-message-context`'s `setContext`) are resolved to values by `./registry` first.
8
8
  */
9
9
 
10
+ /**
11
+ * Control-plane message. Distinguished from `BridgeMessage` by carrying `control` instead of
12
+ * `payload`, so a control message can never be mistaken for relayed context: the guards are
13
+ * mutually exclusive on the presence of those fields.
14
+ */
15
+
10
16
  /**
11
17
  * Transport abstraction so Host/Client work over any carrier: same-window `postMessage` on the
12
18
  * product side, iframe/side-panel `postMessage` in the chat iframe. Carries `BridgeMessage` only;
@@ -16,6 +22,12 @@ import { BRIDGE_MESSAGE_MARKER } from './constants';
16
22
  export var isBridgeMessage = function isBridgeMessage(value) {
17
23
  return _typeof(value) === 'object' && value !== null && value[BRIDGE_MESSAGE_MARKER] === true && typeof value.direction === 'string' && _typeof(value.payload) === 'object' && value.payload !== null;
18
24
  };
25
+ export var isBridgeControlMessage = function isBridgeControlMessage(value) {
26
+ return _typeof(value) === 'object' && value !== null && value[BRIDGE_MESSAGE_MARKER] === true && typeof value.direction === 'string' && value.control === BRIDGE_CONTROL_REQUEST_CONTEXT &&
27
+ // Mutually exclusive with `isBridgeMessage`: a control message carries no payload to relay, so a
28
+ // message bearing both is treated as a payload and held to the relay policy.
29
+ value.payload === undefined;
30
+ };
19
31
 
20
32
  /** `postMessage`-based transport. Callers override `targetWindow`/`self` for the iframe side. */
21
33
  export var createWindowTransport = function createWindowTransport(_ref) {
@@ -41,7 +53,7 @@ export var createWindowTransport = function createWindowTransport(_ref) {
41
53
  if (event.origin !== acceptOrigin) {
42
54
  return;
43
55
  }
44
- if (isBridgeMessage(event.data)) {
56
+ if (isBridgeMessage(event.data) || isBridgeControlMessage(event.data)) {
45
57
  handler(event.data);
46
58
  }
47
59
  }
@@ -1,2 +1,2 @@
1
1
  export var ROVO_PARAM_PREFIX = 'rovoChat';
2
- export var ROVO_VALID_PARAMS = ['pathway', 'agentId', 'conversationId', 'prompt', 'cloudId', 'triggerOpen', 'insertPrompt', 'stagingAreaOpen', 'messageIdSelectedForPreview', 'invocationIdSelectedForPreview', 'promptLibraryOpen', 'openChatMode', 'rovoJourneyId'];
2
+ export var ROVO_VALID_PARAMS = ['pathway', 'agentId', 'conversationId', 'prompt', 'cloudId', 'triggerOpen', 'insertPrompt', 'stagingAreaOpen', 'messageIdSelectedForPreview', 'invocationIdSelectedForPreview', 'promptLibraryOpen', 'openChatMode', 'rovoJourneyId', 'searchQuery'];
@@ -11,3 +11,11 @@ export declare const BRIDGE_TO_EXTENSION: 'to-extension';
11
11
  export declare const BRIDGE_TO_PRODUCT: 'to-product';
12
12
  /** Loop-guard sentinel stamped on re-published payloads so they never ping-pong. */
13
13
  export declare const BRIDGE_SOURCE: 'rovo-extension-bridge';
14
+ /**
15
+ * Control-plane message asking the product to re-send the context it last relayed.
16
+ *
17
+ * Carries no data and is not a bus payload, so it deliberately bypasses the relay allow/deny lists
18
+ * in `./policy` — there is nothing in it to inject. The Host answers by re-sending what it already
19
+ * sent once; a request can never make it relay something the policy would have dropped.
20
+ */
21
+ export declare const BRIDGE_CONTROL_REQUEST_CONTEXT: 'request-context';
@@ -1,5 +1,5 @@
1
1
  import { type Payload } from '../../../types';
2
- import { BRIDGE_MESSAGE_MARKER } from './constants';
2
+ import { BRIDGE_CONTROL_REQUEST_CONTEXT, BRIDGE_MESSAGE_MARKER } from './constants';
3
3
  import type { BRIDGE_TO_EXTENSION, BRIDGE_TO_PRODUCT } from './constants';
4
4
  type BridgeDirection = typeof BRIDGE_TO_EXTENSION | typeof BRIDGE_TO_PRODUCT;
5
5
  /**
@@ -18,17 +18,28 @@ export type BridgeMessage = {
18
18
  direction: BridgeDirection;
19
19
  payload: SerializedPayload;
20
20
  };
21
+ /**
22
+ * Control-plane message. Distinguished from `BridgeMessage` by carrying `control` instead of
23
+ * `payload`, so a control message can never be mistaken for relayed context: the guards are
24
+ * mutually exclusive on the presence of those fields.
25
+ */
26
+ export type BridgeControlMessage = {
27
+ [BRIDGE_MESSAGE_MARKER]: true;
28
+ direction: BridgeDirection;
29
+ control: typeof BRIDGE_CONTROL_REQUEST_CONTEXT;
30
+ };
21
31
  /**
22
32
  * Transport abstraction so Host/Client work over any carrier: same-window `postMessage` on the
23
33
  * product side, iframe/side-panel `postMessage` in the chat iframe. Carries `BridgeMessage` only;
24
34
  * `rovo-content-bridge-api` commands travel separately, relayed verbatim by the extension.
25
35
  */
26
36
  export type Transport = {
27
- send: (message: BridgeMessage) => void;
37
+ send: (message: BridgeMessage | BridgeControlMessage) => void;
28
38
  /** Returns an unsubscribe function. */
29
- subscribe: (handler: (message: BridgeMessage) => void) => () => void;
39
+ subscribe: (handler: (message: BridgeMessage | BridgeControlMessage) => void) => () => void;
30
40
  };
31
41
  export declare const isBridgeMessage: (value: unknown) => value is BridgeMessage;
42
+ export declare const isBridgeControlMessage: (value: unknown) => value is BridgeControlMessage;
32
43
  /** `postMessage`-based transport. Callers override `targetWindow`/`self` for the iframe side. */
33
44
  export declare const createWindowTransport: ({ targetWindow, self: selfWindow, targetOrigin, acceptOrigin, }: {
34
45
  targetWindow: Window;
@@ -15,6 +15,7 @@ export interface BaseRovoChatParams {
15
15
  promptLibraryOpen: boolean;
16
16
  openChatMode: RovoChatOpenMode;
17
17
  rovoJourneyId: string;
18
+ searchQuery: string;
18
19
  }
19
20
  export type ValidPrefix = 'rovoChat';
20
21
  export type ValidParam = keyof BaseRovoChatParams;
@@ -513,6 +513,15 @@ export type UpdateTransitionRovoPayload = {
513
513
  fromStatusName: string;
514
514
  fromStatusCategory: StatusCategory;
515
515
  }[];
516
+ existingName?: string;
517
+ existingToStatusId?: StatusId;
518
+ existingToStatusName?: string;
519
+ existingToStatusCategory?: StatusCategory;
520
+ existingLinks?: {
521
+ fromStatusId: StatusId;
522
+ fromStatusName: string;
523
+ fromStatusCategory: StatusCategory;
524
+ }[];
516
525
  };
517
526
  export type DeleteTransitionRovoPayloadOld = {
518
527
  transitionId: TransitionId;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@atlaskit/rovo-triggers",
3
- "version": "10.30.0",
3
+ "version": "10.33.0",
4
4
  "description": "Provides various trigger events to drive Rovo Chat functionality, such as a publish-subscribe and URL parameter hooks",
5
5
  "author": "Atlassian Pty Ltd",
6
6
  "publishConfig": {
@@ -44,7 +44,7 @@
44
44
  "@atlaskit/primitives": "^22.5.0",
45
45
  "@atlaskit/ssr": "workspace:^",
46
46
  "@atlassian/a11y-jest-testing": "^0.17.0",
47
- "@atlassian/conversation-assistant-service-api": "^17.7.0",
47
+ "@atlassian/conversation-assistant-service-api": "^17.10.0",
48
48
  "@atlassian/feature-flags-test-utils": "^1.3.0",
49
49
  "@atlassian/testing-library": "^0.11.0",
50
50
  "@atlassian/workbench": "workspace:*",