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