@uipath/uipath-typescript 1.3.11 → 1.4.1

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 (59) hide show
  1. package/dist/agent-memory/index.cjs +1772 -0
  2. package/dist/agent-memory/index.d.ts +588 -0
  3. package/dist/agent-memory/index.mjs +1770 -0
  4. package/dist/agents/index.cjs +1995 -0
  5. package/dist/agents/index.d.ts +961 -0
  6. package/dist/agents/index.mjs +1993 -0
  7. package/dist/assets/index.cjs +171 -39
  8. package/dist/assets/index.d.ts +84 -5
  9. package/dist/assets/index.mjs +171 -39
  10. package/dist/attachments/index.cjs +53 -15
  11. package/dist/attachments/index.d.ts +1 -0
  12. package/dist/attachments/index.mjs +53 -15
  13. package/dist/buckets/index.cjs +151 -130
  14. package/dist/buckets/index.d.ts +198 -84
  15. package/dist/buckets/index.mjs +151 -130
  16. package/dist/cases/index.cjs +220 -23
  17. package/dist/cases/index.d.ts +148 -10
  18. package/dist/cases/index.mjs +220 -24
  19. package/dist/conversational-agent/index.cjs +140 -66
  20. package/dist/conversational-agent/index.d.ts +190 -122
  21. package/dist/conversational-agent/index.mjs +140 -66
  22. package/dist/core/index.cjs +445 -108
  23. package/dist/core/index.d.ts +15 -0
  24. package/dist/core/index.mjs +445 -108
  25. package/dist/entities/index.cjs +365 -102
  26. package/dist/entities/index.d.ts +446 -114
  27. package/dist/entities/index.mjs +365 -102
  28. package/dist/feedback/index.cjs +53 -15
  29. package/dist/feedback/index.d.ts +1 -0
  30. package/dist/feedback/index.mjs +53 -15
  31. package/dist/governance/index.cjs +1789 -0
  32. package/dist/governance/index.d.ts +598 -0
  33. package/dist/governance/index.mjs +1787 -0
  34. package/dist/index.cjs +1453 -444
  35. package/dist/index.d.ts +4150 -1742
  36. package/dist/index.mjs +1452 -445
  37. package/dist/index.umd.js +5035 -4009
  38. package/dist/jobs/index.cjs +53 -15
  39. package/dist/jobs/index.d.ts +1 -0
  40. package/dist/jobs/index.mjs +53 -15
  41. package/dist/maestro-processes/index.cjs +189 -27
  42. package/dist/maestro-processes/index.d.ts +131 -9
  43. package/dist/maestro-processes/index.mjs +189 -27
  44. package/dist/orchestrator-du-module/index.cjs +1788 -0
  45. package/dist/orchestrator-du-module/index.d.ts +757 -0
  46. package/dist/orchestrator-du-module/index.mjs +1785 -0
  47. package/dist/processes/index.cjs +53 -15
  48. package/dist/processes/index.d.ts +1 -0
  49. package/dist/processes/index.mjs +53 -15
  50. package/dist/queues/index.cjs +53 -15
  51. package/dist/queues/index.d.ts +1 -0
  52. package/dist/queues/index.mjs +53 -15
  53. package/dist/tasks/index.cjs +116 -19
  54. package/dist/tasks/index.d.ts +110 -4
  55. package/dist/tasks/index.mjs +117 -20
  56. package/dist/traces/index.cjs +340 -15
  57. package/dist/traces/index.d.ts +483 -2
  58. package/dist/traces/index.mjs +339 -16
  59. package/package.json +42 -2
@@ -777,6 +777,32 @@ function filterUndefined(obj) {
777
777
  */
778
778
  const isBrowser = typeof window !== 'undefined' && typeof window.document !== 'undefined';
779
779
  isBrowser && window.self != window.top && window.location.href.includes('source=ActionCenter');
780
+ const _params = isBrowser ? new URLSearchParams(window.location.search) : null;
781
+ /**
782
+ * True when the coded app has been loaded inside a host frame that explicitly
783
+ * opted into token delegation by adding `?host=embed` to the iframe src URL.
784
+ */
785
+ const isHostEmbedded = isBrowser && window.self !== window.top && _params?.get('host') === 'embed';
786
+ /**
787
+ * The validated parent origin, read from the `?basedomain=` query param set
788
+ * by the embedding host in the iframe src URL.
789
+ * Mirrors the same mechanism used by ActionCenterTokenManager.
790
+ * Non-null only when `?host=embed` is present and `?basedomain=` is a valid URL.
791
+ */
792
+ (() => {
793
+ if (!isHostEmbedded)
794
+ return null;
795
+ const basedomain = _params?.get('basedomain');
796
+ if (!basedomain)
797
+ return null;
798
+ try {
799
+ return new URL(basedomain).origin;
800
+ }
801
+ catch {
802
+ console.warn('embeddingOrigin: basedomain query param is not a valid URL', basedomain);
803
+ return null;
804
+ }
805
+ })();
780
806
 
781
807
  /**
782
808
  * Base64 encoding/decoding
@@ -1312,12 +1338,18 @@ class PaginationHelpers {
1312
1338
  * @returns Promise resolving to a paginated result
1313
1339
  */
1314
1340
  static async getAllPaginated(params) {
1315
- const { serviceAccess, getEndpoint, folderId, headers: providedHeaders, paginationParams, additionalParams, transformFn, method = HTTP_METHODS.GET, options = {} } = params;
1341
+ const { serviceAccess, getEndpoint, folderId, headers: providedHeaders, paginationParams, additionalParams, queryParams, transformFn, method = HTTP_METHODS.GET, options = {} } = params;
1316
1342
  const endpoint = getEndpoint(folderId);
1317
1343
  const headers = providedHeaders ?? (folderId ? createHeaders({ [FOLDER_ID]: folderId }) : {});
1344
+ // On POST, the caller's options go in the body; queryParams stays in the URL.
1345
+ // On GET, everything is URL — queryParams merges with additionalParams.
1346
+ const isPost = method === HTTP_METHODS.POST;
1347
+ const requestSpec = isPost
1348
+ ? { body: additionalParams, params: queryParams }
1349
+ : { params: { ...additionalParams, ...queryParams } };
1318
1350
  const paginatedResponse = await serviceAccess.requestWithPagination(method, endpoint, paginationParams, {
1319
1351
  headers,
1320
- params: additionalParams,
1352
+ ...requestSpec,
1321
1353
  pagination: {
1322
1354
  paginationType: options.paginationType || PaginationType.OFFSET,
1323
1355
  itemsField: options.itemsField || DEFAULT_ITEMS_FIELD,
@@ -1342,7 +1374,7 @@ class PaginationHelpers {
1342
1374
  * @returns Promise resolving to an object with data and totalCount
1343
1375
  */
1344
1376
  static async getAllNonPaginated(params) {
1345
- const { serviceAccess, getAllEndpoint, getByFolderEndpoint, folderId, headers: providedHeaders, additionalParams, transformFn, method = HTTP_METHODS.GET, options = {} } = params;
1377
+ const { serviceAccess, getAllEndpoint, getByFolderEndpoint, folderId, headers: providedHeaders, additionalParams, queryParams, transformFn, method = HTTP_METHODS.GET, options = {} } = params;
1346
1378
  // Set default field names
1347
1379
  const itemsField = options.itemsField || DEFAULT_ITEMS_FIELD;
1348
1380
  const totalCountField = options.totalCountField || DEFAULT_TOTAL_COUNT_FIELD;
@@ -1352,17 +1384,18 @@ class PaginationHelpers {
1352
1384
  // Make the API call based on method
1353
1385
  let response;
1354
1386
  if (method === HTTP_METHODS.POST) {
1355
- response = await serviceAccess.post(endpoint, additionalParams, { headers });
1387
+ response = await serviceAccess.post(endpoint, additionalParams, { headers, params: queryParams });
1356
1388
  }
1357
1389
  else {
1358
1390
  response = await serviceAccess.get(endpoint, {
1359
- params: additionalParams,
1391
+ params: { ...additionalParams, ...queryParams },
1360
1392
  headers
1361
1393
  });
1362
1394
  }
1363
1395
  // Extract and transform items from response
1364
- // Handle both plain array responses and envelope responses ({ value: [...], totalRecordCount: N })
1365
- const rawItems = Array.isArray(response.data) ? response.data : response.data?.[itemsField];
1396
+ // Handle both plain array responses and envelope responses ({ value: [...], totalRecordCount: N }).
1397
+ // itemsField may be a dotted path (e.g. 'data.agents') for nested envelopes.
1398
+ const rawItems = Array.isArray(response.data) ? response.data : resolveNestedField(response.data, itemsField);
1366
1399
  const rawTotalCount = Array.isArray(response.data) ? undefined : resolveNestedField(response.data, totalCountField);
1367
1400
  const totalCount = typeof rawTotalCount === 'number' ? rawTotalCount : undefined;
1368
1401
  // Parse items - automatically handle JSON string responses
@@ -1408,8 +1441,9 @@ class PaginationHelpers {
1408
1441
  getEndpoint: config.getEndpoint,
1409
1442
  folderId,
1410
1443
  headers: config.headers,
1411
- paginationParams: cursor ? { cursor, pageSize } : jumpToPage ? { jumpToPage, pageSize } : { pageSize },
1444
+ paginationParams: cursor ? { cursor, pageSize } : jumpToPage !== undefined ? { jumpToPage, pageSize } : { pageSize },
1412
1445
  additionalParams: prefixedOptions,
1446
+ queryParams: config.queryParams,
1413
1447
  transformFn: config.transformFn,
1414
1448
  method: config.method,
1415
1449
  options: {
@@ -1427,6 +1461,7 @@ class PaginationHelpers {
1427
1461
  folderId,
1428
1462
  headers: config.headers,
1429
1463
  additionalParams: prefixedOptions,
1464
+ queryParams: config.queryParams,
1430
1465
  transformFn: config.transformFn,
1431
1466
  method: config.method,
1432
1467
  options: {
@@ -1618,18 +1653,17 @@ class BaseService {
1618
1653
  const params = this.validateAndPreparePaginationParams(paginationType, paginationOptions);
1619
1654
  // Prepare request parameters based on pagination type
1620
1655
  const requestParams = this.preparePaginationRequestParams(paginationType, params, options.pagination);
1621
- // For POST requests, merge pagination params into body and set params to undefined; for GET, use query params
1656
+ // Route pagination state to wherever the API expects it (body for POST, URL for GET).
1657
+ // Caller-supplied options.body / options.params are respected as-is — the api-client
1658
+ // already handles params (URL) and body (request body) independently for every method.
1622
1659
  if (method.toUpperCase() === 'POST') {
1623
1660
  const existingBody = (options.body && typeof options.body === 'object') ? options.body : {};
1624
1661
  options.body = {
1625
1662
  ...existingBody,
1626
- ...options.params,
1627
1663
  ...requestParams
1628
1664
  };
1629
- options.params = undefined;
1630
1665
  }
1631
1666
  else {
1632
- // Merge pagination parameters with existing parameters
1633
1667
  options.params = {
1634
1668
  ...options.params,
1635
1669
  ...requestParams
@@ -1666,6 +1700,8 @@ class BaseService {
1666
1700
  // When true (default), converts pageNumber to a skip/offset value (e.g., page 3 with pageSize 10 → skip 20).
1667
1701
  // When false, passes pageNumber directly as the offset param — used by APIs that accept a page number instead of a record offset.
1668
1702
  const convertToSkip = paginationParams?.convertToSkip ?? true;
1703
+ // When true, sends pageNumber - 1 (for 0-based APIs). Default false (1-based).
1704
+ const zeroBased = paginationParams?.zeroBased ?? false;
1669
1705
  requestParams[pageSizeParam] = limitedPageSize;
1670
1706
  if (convertToSkip) {
1671
1707
  if (params.pageNumber && params.pageNumber > 1) {
@@ -1673,7 +1709,8 @@ class BaseService {
1673
1709
  }
1674
1710
  }
1675
1711
  else {
1676
- requestParams[offsetParam] = params.pageNumber || 1;
1712
+ const sdkPageNumber = params.pageNumber || 1;
1713
+ requestParams[offsetParam] = zeroBased ? sdkPageNumber - 1 : sdkPageNumber;
1677
1714
  }
1678
1715
  {
1679
1716
  requestParams[countParam] = true;
@@ -1702,8 +1739,9 @@ class BaseService {
1702
1739
  const totalCountField = fields.totalCountField || 'totalRecordCount';
1703
1740
  const continuationTokenField = fields.continuationTokenField || 'continuationToken';
1704
1741
  // Extract items and metadata
1705
- // Handle both plain array responses and envelope responses ({ value: [...], totalRecordCount: N })
1706
- const items = Array.isArray(response.data) ? response.data : (response.data[itemsField] || []);
1742
+ // Handle both plain array responses and envelope responses ({ value: [...], totalRecordCount: N }).
1743
+ // itemsField may be a dotted path (e.g. 'data.agents') for nested envelopes.
1744
+ const items = Array.isArray(response.data) ? response.data : (resolveNestedField(response.data, itemsField) || []);
1707
1745
  const rawTotalCount = Array.isArray(response.data) ? undefined : resolveNestedField(response.data, totalCountField);
1708
1746
  const totalCount = typeof rawTotalCount === 'number' ? rawTotalCount : undefined;
1709
1747
  const continuationToken = response.data[continuationTokenField];
@@ -4945,37 +4983,58 @@ class ExchangeService extends BaseService {
4945
4983
  super(instance, buildConversationalAgentHeaders(options));
4946
4984
  }
4947
4985
  /**
4948
- * Gets all exchanges for a conversation with optional filtering and pagination
4986
+ * Gets exchanges for a conversation with pagination and optional sort parameters
4949
4987
  *
4950
- * The method returns either:
4951
- * - A NonPaginatedResponse with items array (when no pagination parameters are provided)
4952
- * - A PaginatedResponse with navigation cursors (when any pagination parameter is provided)
4988
+ * Returns a paginated response. When called without `pageSize`/`cursor`, the
4989
+ * backend applies its default page size inspect `hasNextPage`/`nextCursor`
4990
+ * to navigate further pages.
4953
4991
  *
4954
4992
  * @param conversationId - The conversation ID to get exchanges for
4955
4993
  * @param options - Options for querying exchanges including optional pagination parameters
4956
- * @returns Promise resolving to either an array of exchanges {@link NonPaginatedResponse}<{@link ExchangeGetResponse}> or a {@link PaginatedResponse}<{@link ExchangeGetResponse}> when pagination options are used
4994
+ * @returns Promise resolving to a {@link PaginatedResponse}<{@link ExchangeGetResponse}>
4957
4995
  *
4958
- * @example
4996
+ * @example Basic usage - default page size and sort order
4959
4997
  * ```typescript
4960
- * // Get all exchanges (non-paginated)
4961
- * const conversationExchanges = await exchanges.getAll(conversationId);
4962
- *
4963
- * // First page with pagination
4964
- * const firstPageOfExchanges = await exchanges.getAll(conversationId, { pageSize: 10 });
4998
+ * // First page
4999
+ * const firstPage = await exchanges.getAll(conversationId);
4965
5000
  *
4966
5001
  * // Navigate using cursor
4967
- * if (firstPageOfExchanges.hasNextPage) {
4968
- * const nextPageOfExchanges = await exchanges.getAll(conversationId, { cursor: firstPageOfExchanges.nextCursor });
5002
+ * if (firstPage.hasNextPage) {
5003
+ * const nextPage = await exchanges.getAll(conversationId, { cursor: firstPage.nextCursor });
5004
+ * }
5005
+ * ```
5006
+ *
5007
+ * @example With explicit page size and exchange/message sort orders
5008
+ * ```typescript
5009
+ * import { SortOrder } from '@uipath/uipath-typescript/conversational-agent';
5010
+ *
5011
+ * const firstPage = await exchanges.getAll(conversationId, {
5012
+ * pageSize: 10,
5013
+ * exchangeSort: SortOrder.Descending,
5014
+ * messageSort: SortOrder.Ascending
5015
+ * });
5016
+ *
5017
+ * // Navigate using cursor and same parameters
5018
+ * if (firstPage.hasNextPage) {
5019
+ * const nextPage = await exchanges.getAll(conversationId, {
5020
+ * pageSize: 10,
5021
+ * exchangeSort: SortOrder.Descending,
5022
+ * messageSort: SortOrder.Ascending,
5023
+ * cursor: firstPage.nextCursor
5024
+ * });
4969
5025
  * }
4970
5026
  * ```
4971
5027
  */
4972
5028
  async getAll(conversationId, options) {
4973
- const transformFn = transformExchange;
4974
- return PaginationHelpers.getAll({
5029
+ const { pageSize, cursor, jumpToPage, ...additionalParams } = options ?? {};
5030
+ const paginationParams = cursor ? { cursor, pageSize } : jumpToPage ? { jumpToPage, pageSize } : { pageSize };
5031
+ return PaginationHelpers.getAllPaginated({
4975
5032
  serviceAccess: this.createPaginationServiceAccess(),
4976
5033
  getEndpoint: () => EXCHANGE_ENDPOINTS.LIST(conversationId),
4977
- transformFn,
4978
- pagination: {
5034
+ paginationParams,
5035
+ additionalParams,
5036
+ transformFn: transformExchange,
5037
+ options: {
4979
5038
  paginationType: PaginationType.TOKEN,
4980
5039
  itemsField: CONVERSATIONAL_PAGINATION.ITEMS_FIELD,
4981
5040
  continuationTokenField: CONVERSATIONAL_PAGINATION.CONTINUATION_TOKEN_FIELD,
@@ -4983,9 +5042,8 @@ class ExchangeService extends BaseService {
4983
5042
  pageSizeParam: CONVERSATIONAL_TOKEN_PARAMS.PAGE_SIZE_PARAM,
4984
5043
  tokenParam: CONVERSATIONAL_TOKEN_PARAMS.TOKEN_PARAM
4985
5044
  }
4986
- },
4987
- excludeFromPrefix: Object.keys(options || {}) // Conversational params are not OData
4988
- }, options);
5045
+ }
5046
+ });
4989
5047
  }
4990
5048
  /**
4991
5049
  * Gets an exchange by ID with its messages
@@ -5967,28 +6025,23 @@ class ConversationService extends BaseService {
5967
6025
  return createConversationWithMethods(transformedData, this, this, this._exchangeService);
5968
6026
  }
5969
6027
  /**
5970
- * Gets all conversations with optional filtering and pagination
6028
+ * Gets conversations with pagination and optional sort/filter parameters
5971
6029
  *
5972
- * The method returns either:
5973
- * - A NonPaginatedResponse with items array (when no pagination parameters are provided)
5974
- * - A PaginatedResponse with navigation cursors (when any pagination parameter is provided)
6030
+ * Returns a paginated response. When called without `pageSize`/`cursor`, a
6031
+ * default page size is applied - inspect `hasNextPage`/`nextCursor`
6032
+ * to navigate further pages.
5975
6033
  *
5976
- * @param options - Options for querying conversations including optional pagination parameters
5977
- * @returns Promise resolving to either an array of conversations {@link NonPaginatedResponse}<{@link ConversationGetResponse}> or a {@link PaginatedResponse}<{@link ConversationGetResponse}> when pagination options are used
6034
+ * @param options - Options for querying conversations
6035
+ * @returns Promise resolving to a {@link PaginatedResponse}<{@link ConversationGetResponse}>
5978
6036
  *
5979
- * @example Basic usage - get all conversations
6037
+ * @example Basic usage - default sort, pagination, and without filtering
5980
6038
  * ```typescript
5981
- * const allConversations = await conversationalAgent.conversations.getAll();
6039
+ * // First page
6040
+ * const firstPage = await conversationalAgent.conversations.getAll();
5982
6041
  *
5983
- * for (const conversation of allConversations.items) {
6042
+ * for (const conversation of firstPage.items) {
5984
6043
  * console.log(`${conversation.label} - created: ${conversation.createdTime}`);
5985
6044
  * }
5986
- * ```
5987
- *
5988
- * @example With pagination
5989
- * ```typescript
5990
- * // First page
5991
- * const firstPage = await conversationalAgent.conversations.getAll({ pageSize: 10 });
5992
6045
  *
5993
6046
  * // Navigate using cursor
5994
6047
  * if (firstPage.hasNextPage) {
@@ -5998,37 +6051,59 @@ class ConversationService extends BaseService {
5998
6051
  * }
5999
6052
  * ```
6000
6053
  *
6001
- * @example Sorted with limit
6054
+ * @example With explicit page size and sort order (by last-activity timestamp)
6002
6055
  * ```typescript
6003
- * const result = await conversationalAgent.conversations.getAll({
6004
- * sort: SortOrder.Descending,
6005
- * pageSize: 20
6056
+ * import { SortOrder } from '@uipath/uipath-typescript/conversational-agent';
6057
+ *
6058
+ * // First page
6059
+ * const firstPage = await conversationalAgent.conversations.getAll({
6060
+ * pageSize: 10,
6061
+ * sort: SortOrder.Descending
6006
6062
  * });
6063
+ *
6064
+ * // Navigate using cursor and same parameters
6065
+ * if (firstPage.hasNextPage) {
6066
+ * const nextPage = await conversationalAgent.conversations.getAll({
6067
+ * pageSize: 10,
6068
+ * sort: SortOrder.Descending,
6069
+ * cursor: firstPage.nextCursor
6070
+ * });
6071
+ * }
6007
6072
  * ```
6008
6073
  *
6009
- * @example Filter by agent and search by label
6074
+ * @example With agent-filter and label-search
6010
6075
  * ```typescript
6011
- * const filtered = await conversationalAgent.conversations.getAll({
6076
+ * const firstPage = await conversationalAgent.conversations.getAll({
6012
6077
  * agentId: <agentId>,
6013
6078
  * label: 'budget'
6014
6079
  * });
6080
+ *
6081
+ * // Navigate using cursor and same parameters
6082
+ * if (firstPage.hasNextPage) {
6083
+ * const nextPage = await conversationalAgent.conversations.getAll({
6084
+ * agentId: <agentId>,
6085
+ * label: 'budget',
6086
+ * cursor: firstPage.nextCursor
6087
+ * });
6088
+ * }
6015
6089
  * ```
6016
6090
  */
6017
6091
  async getAll(options) {
6018
- // Transform function to convert API timestamps to SDK naming convention and add methods
6019
6092
  const transformFn = (conversation) => {
6020
6093
  const transformedData = transformData(conversation, ConversationMap);
6021
6094
  return createConversationWithMethods(transformedData, this, this, this._exchangeService);
6022
6095
  };
6096
+ const { pageSize, cursor, jumpToPage, ...filterOptions } = options ?? {};
6023
6097
  // Translate SDK filter names (agentKey/agentId/label) to backend names before forwarding
6024
- const apiOptions = options
6025
- ? transformRequest(options, ConversationGetAllFilterMap)
6026
- : undefined;
6027
- return PaginationHelpers.getAll({
6098
+ const additionalParams = transformRequest(filterOptions, ConversationGetAllFilterMap);
6099
+ const paginationParams = cursor ? { cursor, pageSize } : jumpToPage ? { jumpToPage, pageSize } : { pageSize };
6100
+ return PaginationHelpers.getAllPaginated({
6028
6101
  serviceAccess: this.createPaginationServiceAccess(),
6029
6102
  getEndpoint: () => CONVERSATION_ENDPOINTS.LIST,
6103
+ paginationParams,
6104
+ additionalParams,
6030
6105
  transformFn,
6031
- pagination: {
6106
+ options: {
6032
6107
  paginationType: PaginationType.TOKEN,
6033
6108
  itemsField: CONVERSATIONAL_PAGINATION.ITEMS_FIELD,
6034
6109
  continuationTokenField: CONVERSATIONAL_PAGINATION.CONTINUATION_TOKEN_FIELD,
@@ -6036,9 +6111,8 @@ class ConversationService extends BaseService {
6036
6111
  pageSizeParam: CONVERSATIONAL_TOKEN_PARAMS.PAGE_SIZE_PARAM,
6037
6112
  tokenParam: CONVERSATIONAL_TOKEN_PARAMS.TOKEN_PARAM
6038
6113
  }
6039
- },
6040
- excludeFromPrefix: Object.keys(apiOptions || {}) // Conversational params are not OData
6041
- }, apiOptions);
6114
+ }
6115
+ });
6042
6116
  }
6043
6117
  /**
6044
6118
  * Updates a conversation by ID
@@ -6072,7 +6146,7 @@ class ConversationService extends BaseService {
6072
6146
  */
6073
6147
  async deleteById(id) {
6074
6148
  const response = await this.delete(CONVERSATION_ENDPOINTS.DELETE(id));
6075
- return response.data;
6149
+ return transformData(response.data, ConversationMap);
6076
6150
  }
6077
6151
  // ==================== Attachments ====================
6078
6152
  /**