@hexabot-ai/api 3.2.2-alpha.10 → 3.2.2-alpha.11

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 (172) hide show
  1. package/dist/actions/actions.service.d.ts +1 -0
  2. package/dist/actions/actions.service.js +19 -1
  3. package/dist/actions/actions.service.js.map +1 -1
  4. package/dist/app.module.js +2 -0
  5. package/dist/app.module.js.map +1 -1
  6. package/dist/extensions/actions/ai/mcp.binding.js +2 -0
  7. package/dist/extensions/actions/ai/mcp.binding.js.map +1 -1
  8. package/dist/extensions/actions/ai/memory.binding.js +2 -0
  9. package/dist/extensions/actions/ai/memory.binding.js.map +1 -1
  10. package/dist/extensions/actions/ai/retrieve-content-rag.action.js +2 -0
  11. package/dist/extensions/actions/ai/retrieve-content-rag.action.js.map +1 -1
  12. package/dist/extensions/actions/messaging/list.action.d.ts +2 -2
  13. package/dist/extensions/actions/messaging/list.action.js +7 -0
  14. package/dist/extensions/actions/messaging/list.action.js.map +1 -1
  15. package/dist/extensions/actions/subscriber/update-labels.action.js +3 -0
  16. package/dist/extensions/actions/subscriber/update-labels.action.js.map +1 -1
  17. package/dist/extensions/actions/workflow/call-workflow.action.d.ts +19 -0
  18. package/dist/extensions/actions/workflow/call-workflow.action.js +83 -0
  19. package/dist/extensions/actions/workflow/call-workflow.action.js.map +1 -0
  20. package/dist/extensions/actions/workflow/i18n/en.translations.json +9 -0
  21. package/dist/extensions/actions/workflow/i18n/fr.translations.json +9 -0
  22. package/dist/extensions/index.d.ts +1 -0
  23. package/dist/extensions/index.js +1 -0
  24. package/dist/extensions/index.js.map +1 -1
  25. package/dist/index.d.ts +1 -0
  26. package/dist/index.js +1 -0
  27. package/dist/index.js.map +1 -1
  28. package/dist/mcp/tools/hexabot-mcp.tools.d.ts +58 -0
  29. package/dist/static/assets/{browser-ponyfill-DN-Mu1Dr.js → browser-ponyfill-C573gsYp.js} +1 -1
  30. package/dist/static/assets/{cssMode-BCWxI_Ux.js → cssMode-Dl3j1m5K.js} +1 -1
  31. package/dist/static/assets/{freemarker2-CFvevPey.js → freemarker2-BC7MYUau.js} +1 -1
  32. package/dist/static/assets/{handlebars-BCStA9Eg.js → handlebars-DF9DBgjd.js} +1 -1
  33. package/dist/static/assets/{html-DkIwCA7q.js → html-CbBSWv6-.js} +1 -1
  34. package/dist/static/assets/{htmlMode-DHeMkUp8.js → htmlMode-BUAbNnKS.js} +1 -1
  35. package/dist/static/assets/{index-C6VeVxYx.js → index-DwNnhpf5.js} +2103 -2103
  36. package/dist/static/assets/{javascript-DLPKJzLc.js → javascript-BG37perB.js} +1 -1
  37. package/dist/static/assets/{jsonMode-BUIDFK3O.js → jsonMode-CTS3-YZB.js} +1 -1
  38. package/dist/static/assets/{liquid-B0XmwdKh.js → liquid-LhmqJOdD.js} +1 -1
  39. package/dist/static/assets/{lspLanguageFeatures-Fcpw3Ev_.js → lspLanguageFeatures-BRCtbLBB.js} +1 -1
  40. package/dist/static/assets/{mdx-DmJKi-dU.js → mdx-DETvz3oa.js} +1 -1
  41. package/dist/static/assets/{python-B1WnWY4I.js → python-F6lZ-u5p.js} +1 -1
  42. package/dist/static/assets/{razor-CL2s-6qt.js → razor-PKvkfOLq.js} +1 -1
  43. package/dist/static/assets/{tsMode-DSFhDcf0.js → tsMode-BpTc9xsq.js} +1 -1
  44. package/dist/static/assets/{typescript-CmHv7Jwl.js → typescript-chpcHZSI.js} +1 -1
  45. package/dist/static/assets/{xml-Cyy6zXDM.js → xml-CZLk_iMt.js} +1 -1
  46. package/dist/static/assets/{yaml-D_D6TAHE.js → yaml-D6hLCygP.js} +1 -1
  47. package/dist/static/index.html +1 -1
  48. package/dist/static/locales/en/translation.json +7 -1
  49. package/dist/static/locales/fr/translation.json +8 -2
  50. package/dist/transfer/adapters/content-type-transfer.adapter.d.ts +14 -0
  51. package/dist/transfer/adapters/content-type-transfer.adapter.js +102 -0
  52. package/dist/transfer/adapters/content-type-transfer.adapter.js.map +1 -0
  53. package/dist/transfer/adapters/credential-transfer.adapter.d.ts +13 -0
  54. package/dist/transfer/adapters/credential-transfer.adapter.js +110 -0
  55. package/dist/transfer/adapters/credential-transfer.adapter.js.map +1 -0
  56. package/dist/transfer/adapters/label-transfer.adapter.d.ts +15 -0
  57. package/dist/transfer/adapters/label-transfer.adapter.js +189 -0
  58. package/dist/transfer/adapters/label-transfer.adapter.js.map +1 -0
  59. package/dist/transfer/adapters/mcp-server-transfer.adapter.d.ts +16 -0
  60. package/dist/transfer/adapters/mcp-server-transfer.adapter.js +169 -0
  61. package/dist/transfer/adapters/mcp-server-transfer.adapter.js.map +1 -0
  62. package/dist/transfer/adapters/memory-definition-transfer.adapter.d.ts +14 -0
  63. package/dist/transfer/adapters/memory-definition-transfer.adapter.js +111 -0
  64. package/dist/transfer/adapters/memory-definition-transfer.adapter.js.map +1 -0
  65. package/dist/transfer/index.d.ts +7 -0
  66. package/dist/transfer/index.js +24 -0
  67. package/dist/transfer/index.js.map +1 -0
  68. package/dist/transfer/workflow-transfer-adapter.registry.d.ts +27 -0
  69. package/dist/transfer/workflow-transfer-adapter.registry.js +164 -0
  70. package/dist/transfer/workflow-transfer-adapter.registry.js.map +1 -0
  71. package/dist/transfer/workflow-transfer-definition.service.d.ts +40 -0
  72. package/dist/transfer/workflow-transfer-definition.service.js +353 -0
  73. package/dist/transfer/workflow-transfer-definition.service.js.map +1 -0
  74. package/dist/transfer/workflow-transfer-resource-adapter.d.ts +33 -0
  75. package/dist/transfer/workflow-transfer-resource-adapter.js +81 -0
  76. package/dist/transfer/workflow-transfer-resource-adapter.js.map +1 -0
  77. package/dist/transfer/workflow-transfer.controller.d.ts +9 -0
  78. package/dist/transfer/workflow-transfer.controller.js +63 -0
  79. package/dist/transfer/workflow-transfer.controller.js.map +1 -0
  80. package/dist/transfer/workflow-transfer.module.d.ts +2 -0
  81. package/dist/transfer/workflow-transfer.module.js +47 -0
  82. package/dist/transfer/workflow-transfer.module.js.map +1 -0
  83. package/dist/transfer/workflow-transfer.service.d.ts +51 -0
  84. package/dist/transfer/workflow-transfer.service.js +509 -0
  85. package/dist/transfer/workflow-transfer.service.js.map +1 -0
  86. package/dist/transfer/workflow-transfer.types.d.ts +31 -0
  87. package/dist/transfer/workflow-transfer.types.js +35 -0
  88. package/dist/transfer/workflow-transfer.types.js.map +1 -0
  89. package/dist/tsconfig.build.tsbuildinfo +1 -1
  90. package/dist/user/dto/user.dto.d.ts +1 -1
  91. package/dist/utils/test/fixtures/label-group.d.ts +1 -1
  92. package/dist/utils/test/fixtures/label.d.ts +1 -1
  93. package/dist/utils/test/fixtures/subscriber.d.ts +1 -1
  94. package/dist/utils/test/fixtures/workflow-run.js +4 -1
  95. package/dist/utils/test/fixtures/workflow-run.js.map +1 -1
  96. package/dist/workflow/contexts/workflow-runtime.context.d.ts +3 -1
  97. package/dist/workflow/contexts/workflow-runtime.context.js +5 -0
  98. package/dist/workflow/contexts/workflow-runtime.context.js.map +1 -1
  99. package/dist/workflow/controllers/workflow-run.controller.js +2 -0
  100. package/dist/workflow/controllers/workflow-run.controller.js.map +1 -1
  101. package/dist/workflow/dto/workflow-run.dto.d.ts +1 -0
  102. package/dist/workflow/dto/workflow-run.dto.js +11 -0
  103. package/dist/workflow/dto/workflow-run.dto.js.map +1 -1
  104. package/dist/workflow/entities/memory-record.entity.d.ts +2 -0
  105. package/dist/workflow/entities/workflow-run.entity.d.ts +62 -0
  106. package/dist/workflow/entities/workflow-run.entity.js +15 -1
  107. package/dist/workflow/entities/workflow-run.entity.js.map +1 -1
  108. package/dist/workflow/index.d.ts +7 -0
  109. package/dist/workflow/index.js +7 -0
  110. package/dist/workflow/index.js.map +1 -1
  111. package/dist/workflow/repositories/workflow-run.repository.js +7 -1
  112. package/dist/workflow/repositories/workflow-run.repository.js.map +1 -1
  113. package/dist/workflow/resource-refs.d.ts +17 -0
  114. package/dist/workflow/resource-refs.js +15 -0
  115. package/dist/workflow/resource-refs.js.map +1 -0
  116. package/dist/workflow/services/agentic.service.d.ts +7 -1
  117. package/dist/workflow/services/agentic.service.js +134 -16
  118. package/dist/workflow/services/agentic.service.js.map +1 -1
  119. package/dist/workflow/services/workflow-run.service.d.ts +3 -0
  120. package/dist/workflow/services/workflow-run.service.js +75 -3
  121. package/dist/workflow/services/workflow-run.service.js.map +1 -1
  122. package/dist/workflow/services/workflow-version.service.d.ts +2 -0
  123. package/dist/workflow/services/workflow-version.service.js +19 -0
  124. package/dist/workflow/services/workflow-version.service.js.map +1 -1
  125. package/dist/workflow/services/workflow.service.d.ts +3 -1
  126. package/dist/workflow/services/workflow.service.js +5 -0
  127. package/dist/workflow/services/workflow.service.js.map +1 -1
  128. package/dist/workflow/types.d.ts +31 -0
  129. package/dist/workflow/types.js +2 -1
  130. package/dist/workflow/types.js.map +1 -1
  131. package/dist/workflow/workflow.module.js +3 -0
  132. package/dist/workflow/workflow.module.js.map +1 -1
  133. package/package.json +4 -4
  134. package/src/actions/actions.service.ts +34 -4
  135. package/src/app.module.ts +2 -0
  136. package/src/extensions/actions/ai/mcp.binding.ts +2 -0
  137. package/src/extensions/actions/ai/memory.binding.ts +2 -0
  138. package/src/extensions/actions/ai/retrieve-content-rag.action.ts +2 -0
  139. package/src/extensions/actions/messaging/list.action.ts +7 -0
  140. package/src/extensions/actions/subscriber/update-labels.action.ts +3 -0
  141. package/src/extensions/actions/workflow/call-workflow.action.ts +112 -0
  142. package/src/extensions/actions/workflow/i18n/en.translations.json +9 -0
  143. package/src/extensions/actions/workflow/i18n/fr.translations.json +9 -0
  144. package/src/extensions/index.ts +2 -0
  145. package/src/index.ts +2 -0
  146. package/src/transfer/adapters/content-type-transfer.adapter.ts +154 -0
  147. package/src/transfer/adapters/credential-transfer.adapter.ts +158 -0
  148. package/src/transfer/adapters/label-transfer.adapter.ts +291 -0
  149. package/src/transfer/adapters/mcp-server-transfer.adapter.ts +255 -0
  150. package/src/transfer/adapters/memory-definition-transfer.adapter.ts +167 -0
  151. package/src/transfer/index.ts +19 -0
  152. package/src/transfer/workflow-transfer-adapter.registry.ts +250 -0
  153. package/src/transfer/workflow-transfer-definition.service.ts +587 -0
  154. package/src/transfer/workflow-transfer-resource-adapter.ts +132 -0
  155. package/src/transfer/workflow-transfer.controller.ts +86 -0
  156. package/src/transfer/workflow-transfer.module.ts +46 -0
  157. package/src/transfer/workflow-transfer.service.ts +858 -0
  158. package/src/transfer/workflow-transfer.types.ts +98 -0
  159. package/src/utils/test/fixtures/workflow-run.ts +4 -1
  160. package/src/workflow/contexts/workflow-runtime.context.ts +12 -1
  161. package/src/workflow/controllers/workflow-run.controller.ts +2 -0
  162. package/src/workflow/dto/workflow-run.dto.ts +10 -0
  163. package/src/workflow/entities/workflow-run.entity.ts +22 -1
  164. package/src/workflow/index.ts +14 -0
  165. package/src/workflow/repositories/workflow-run.repository.ts +7 -1
  166. package/src/workflow/resource-refs.ts +44 -0
  167. package/src/workflow/services/agentic.service.ts +256 -26
  168. package/src/workflow/services/workflow-run.service.ts +120 -7
  169. package/src/workflow/services/workflow-version.service.ts +33 -0
  170. package/src/workflow/services/workflow.service.ts +21 -1
  171. package/src/workflow/types.ts +52 -0
  172. package/src/workflow/workflow.module.ts +3 -0
@@ -0,0 +1,158 @@
1
+ /*
2
+ * Hexabot — Fair Core License (FCL-1.0-ALv2)
3
+ * Copyright (c) 2026 Hexastack.
4
+ * Full terms: see LICENSE.md.
5
+ */
6
+
7
+ import {
8
+ type WorkflowExportBundleCredential,
9
+ type WorkflowImportResourceResult,
10
+ } from '@hexabot-ai/types';
11
+ import { ConflictException, Injectable } from '@nestjs/common';
12
+ import { In } from 'typeorm';
13
+
14
+ import { CredentialOrmEntity } from '@/user/entities/credential.entity';
15
+ import { CredentialService } from '@/user/services/credential.service';
16
+
17
+ import {
18
+ WorkflowTransferAdapter,
19
+ WorkflowTransferExportContext,
20
+ WorkflowTransferImportContext,
21
+ WorkflowTransferResourceAdapter,
22
+ } from '../workflow-transfer-resource-adapter';
23
+ import {
24
+ assertFoundAll,
25
+ buildPostCreateEvent,
26
+ buildResourceResult,
27
+ PLACEHOLDER_CREDENTIAL_VALUE,
28
+ uniqueResourceIds,
29
+ type WorkflowTransferImportAdapterResult,
30
+ } from '../workflow-transfer.types';
31
+
32
+ @WorkflowTransferAdapter()
33
+ @Injectable()
34
+ export class CredentialTransferAdapter extends WorkflowTransferResourceAdapter {
35
+ override readonly kind = 'credential';
36
+
37
+ override readonly resourceKeys = ['credentials'];
38
+
39
+ constructor(private readonly credentialService: CredentialService) {
40
+ super();
41
+ }
42
+
43
+ override async buildExportResources(
44
+ ctx: WorkflowTransferExportContext,
45
+ ): Promise<Record<string, WorkflowExportBundleCredential[]>> {
46
+ return {
47
+ credentials: await this.buildCredentialExportResources(
48
+ ctx.getRefs(this.kind),
49
+ ),
50
+ };
51
+ }
52
+
53
+ private async buildCredentialExportResources(
54
+ ids: string[],
55
+ ): Promise<WorkflowExportBundleCredential[]> {
56
+ const uniqueIds = uniqueResourceIds(ids);
57
+ if (uniqueIds.length === 0) {
58
+ return [];
59
+ }
60
+
61
+ const credentials = await this.credentialService.find({
62
+ where: { id: In(uniqueIds) },
63
+ });
64
+ assertFoundAll(
65
+ 'credential',
66
+ uniqueIds,
67
+ credentials.map((credential) => credential.id),
68
+ );
69
+
70
+ return credentials.map((credential) => ({
71
+ exportId: credential.id,
72
+ name: credential.name,
73
+ exportedOwnerId: credential.owner,
74
+ }));
75
+ }
76
+
77
+ override async importResources(
78
+ ctx: WorkflowTransferImportContext,
79
+ ): Promise<WorkflowTransferImportAdapterResult> {
80
+ const credentials =
81
+ ctx.getResources<WorkflowExportBundleCredential>('credentials');
82
+ const idMap: Record<string, string> = {};
83
+ const resources: WorkflowImportResourceResult[] = [];
84
+ const warnings: string[] = [];
85
+ const postCreateEvents: WorkflowTransferImportAdapterResult['postCreateEvents'] =
86
+ [];
87
+ const placeholderExportIds = new Set<string>();
88
+
89
+ for (const credential of credentials) {
90
+ const existingByName = await ctx.manager.findOne(CredentialOrmEntity, {
91
+ where: { name: credential.name },
92
+ relations: ['owner'],
93
+ });
94
+
95
+ if (existingByName) {
96
+ const existingOwnerId =
97
+ existingByName.owner && typeof existingByName.owner === 'object'
98
+ ? existingByName.owner.id
99
+ : null;
100
+ if (existingOwnerId !== ctx.ownerId) {
101
+ throw new ConflictException(
102
+ `Credential "${credential.name}" already exists for another user`,
103
+ );
104
+ }
105
+
106
+ idMap[credential.exportId] = existingByName.id;
107
+ resources.push(
108
+ buildResourceResult({
109
+ kind: 'credential',
110
+ exportId: credential.exportId,
111
+ localId: existingByName.id,
112
+ name: credential.name,
113
+ action: 'reused',
114
+ }),
115
+ );
116
+ continue;
117
+ }
118
+
119
+ const payload = {
120
+ name: credential.name,
121
+ value: PLACEHOLDER_CREDENTIAL_VALUE,
122
+ owner: { id: ctx.ownerId },
123
+ };
124
+ const created = await ctx.manager.save(
125
+ CredentialOrmEntity,
126
+ ctx.manager.create(CredentialOrmEntity, payload),
127
+ );
128
+
129
+ idMap[credential.exportId] = created.id;
130
+ placeholderExportIds.add(credential.exportId);
131
+ resources.push(
132
+ buildResourceResult({
133
+ kind: 'credential',
134
+ exportId: credential.exportId,
135
+ localId: created.id,
136
+ name: credential.name,
137
+ action: 'placeholder_created',
138
+ }),
139
+ );
140
+ postCreateEvents.push(
141
+ buildPostCreateEvent('credential', created, payload),
142
+ );
143
+ warnings.push(
144
+ `Credential "${credential.name}" was imported as a placeholder and must be updated before use.`,
145
+ );
146
+ }
147
+
148
+ return {
149
+ idMap,
150
+ resources,
151
+ warnings,
152
+ postCreateEvents,
153
+ metadata: {
154
+ placeholderExportIds,
155
+ },
156
+ };
157
+ }
158
+ }
@@ -0,0 +1,291 @@
1
+ /*
2
+ * Hexabot — Fair Core License (FCL-1.0-ALv2)
3
+ * Copyright (c) 2026 Hexastack.
4
+ * Full terms: see LICENSE.md.
5
+ */
6
+
7
+ import {
8
+ type WorkflowExportBundle,
9
+ type WorkflowExportBundleLabel,
10
+ type WorkflowExportBundleLabelGroup,
11
+ type WorkflowImportResourceResult,
12
+ } from '@hexabot-ai/types';
13
+ import {
14
+ BadRequestException,
15
+ ConflictException,
16
+ Injectable,
17
+ } from '@nestjs/common';
18
+ import { EntityManager, In } from 'typeorm';
19
+
20
+ import { LabelGroupOrmEntity } from '@/chat/entities/label-group.entity';
21
+ import { LabelOrmEntity } from '@/chat/entities/label.entity';
22
+ import { LabelService } from '@/chat/services/label.service';
23
+
24
+ import {
25
+ WorkflowTransferAdapter,
26
+ WorkflowTransferExportContext,
27
+ WorkflowTransferImportContext,
28
+ WorkflowTransferResourceAdapter,
29
+ } from '../workflow-transfer-resource-adapter';
30
+ import {
31
+ assertFoundAll,
32
+ buildPostCreateEvent,
33
+ buildResourceResult,
34
+ uniqueResourceIds,
35
+ type WorkflowTransferImportAdapterResult,
36
+ } from '../workflow-transfer.types';
37
+
38
+ type LabelExportResources = {
39
+ labelGroups: WorkflowExportBundleLabelGroup[];
40
+ labels: WorkflowExportBundleLabel[];
41
+ };
42
+
43
+ @WorkflowTransferAdapter()
44
+ @Injectable()
45
+ export class LabelTransferAdapter extends WorkflowTransferResourceAdapter {
46
+ override readonly kind = 'label';
47
+
48
+ override readonly resourceKeys = ['labels', 'labelGroups'];
49
+
50
+ constructor(private readonly labelService: LabelService) {
51
+ super();
52
+ }
53
+
54
+ override async buildExportResources(
55
+ ctx: WorkflowTransferExportContext,
56
+ ): Promise<Record<string, unknown[]>> {
57
+ return this.buildLabelExportResources(ctx.getRefs(this.kind));
58
+ }
59
+
60
+ private async buildLabelExportResources(
61
+ ids: string[],
62
+ ): Promise<LabelExportResources> {
63
+ const uniqueIds = uniqueResourceIds(ids);
64
+ if (uniqueIds.length === 0) {
65
+ return { labelGroups: [], labels: [] };
66
+ }
67
+
68
+ const labels = await this.labelService.findAndPopulate({
69
+ where: { id: In(uniqueIds) },
70
+ });
71
+ assertFoundAll(
72
+ 'label',
73
+ uniqueIds,
74
+ labels.map((label) => label.id),
75
+ );
76
+
77
+ const groupsById = new Map<string, WorkflowExportBundleLabelGroup>();
78
+ for (const label of labels) {
79
+ if (label.group?.id) {
80
+ groupsById.set(label.group.id, {
81
+ exportId: label.group.id,
82
+ name: label.group.name,
83
+ });
84
+ }
85
+ }
86
+
87
+ return {
88
+ labelGroups: Array.from(groupsById.values()),
89
+ labels: labels.map((label) => ({
90
+ exportId: label.id,
91
+ title: label.title,
92
+ name: label.name,
93
+ description: label.description ?? null,
94
+ groupExportId: label.group?.id ?? null,
95
+ })),
96
+ };
97
+ }
98
+
99
+ override async importResources(
100
+ ctx: WorkflowTransferImportContext,
101
+ ): Promise<WorkflowTransferImportAdapterResult> {
102
+ const labelGroups =
103
+ ctx.getResources<
104
+ WorkflowExportBundle['resources']['labelGroups'][number]
105
+ >('labelGroups');
106
+ const labels =
107
+ ctx.getResources<WorkflowExportBundle['resources']['labels'][number]>(
108
+ 'labels',
109
+ );
110
+ const groupResult = await this.importLabelGroups(ctx.manager, labelGroups);
111
+ const labelResult = await this.importLabels(
112
+ ctx.manager,
113
+ labels,
114
+ groupResult.idMap,
115
+ );
116
+
117
+ return {
118
+ idMap: labelResult.idMap,
119
+ resources: [...groupResult.resources, ...labelResult.resources],
120
+ warnings: [],
121
+ postCreateEvents: [
122
+ ...groupResult.postCreateEvents,
123
+ ...labelResult.postCreateEvents,
124
+ ],
125
+ };
126
+ }
127
+
128
+ private async importLabelGroups(
129
+ manager: EntityManager,
130
+ labelGroups: WorkflowExportBundle['resources']['labelGroups'],
131
+ ): Promise<WorkflowTransferImportAdapterResult> {
132
+ const idMap: Record<string, string> = {};
133
+ const resources: WorkflowImportResourceResult[] = [];
134
+ const postCreateEvents: WorkflowTransferImportAdapterResult['postCreateEvents'] =
135
+ [];
136
+
137
+ for (const labelGroup of labelGroups) {
138
+ const existing = await manager.findOne(LabelGroupOrmEntity, {
139
+ where: { name: labelGroup.name },
140
+ });
141
+
142
+ if (existing) {
143
+ idMap[labelGroup.exportId] = existing.id;
144
+ resources.push(
145
+ buildResourceResult({
146
+ kind: 'labelGroup',
147
+ exportId: labelGroup.exportId,
148
+ localId: existing.id,
149
+ name: labelGroup.name,
150
+ action: 'reused',
151
+ }),
152
+ );
153
+ continue;
154
+ }
155
+
156
+ const payload = {
157
+ name: labelGroup.name,
158
+ };
159
+ const created = await manager.save(
160
+ LabelGroupOrmEntity,
161
+ manager.create(LabelGroupOrmEntity, payload),
162
+ );
163
+
164
+ idMap[labelGroup.exportId] = created.id;
165
+ resources.push(
166
+ buildResourceResult({
167
+ kind: 'labelGroup',
168
+ exportId: labelGroup.exportId,
169
+ localId: created.id,
170
+ name: labelGroup.name,
171
+ action: 'created',
172
+ }),
173
+ );
174
+ postCreateEvents.push(
175
+ buildPostCreateEvent('labelGroup', created, payload),
176
+ );
177
+ }
178
+
179
+ return {
180
+ idMap,
181
+ resources,
182
+ warnings: [],
183
+ postCreateEvents,
184
+ };
185
+ }
186
+
187
+ private async importLabels(
188
+ manager: EntityManager,
189
+ labels: WorkflowExportBundle['resources']['labels'],
190
+ labelGroupIdMap: Record<string, string>,
191
+ ): Promise<WorkflowTransferImportAdapterResult> {
192
+ const idMap: Record<string, string> = {};
193
+ const resources: WorkflowImportResourceResult[] = [];
194
+ const postCreateEvents: WorkflowTransferImportAdapterResult['postCreateEvents'] =
195
+ [];
196
+
197
+ for (const label of labels) {
198
+ const groupId = label.groupExportId
199
+ ? labelGroupIdMap[label.groupExportId]
200
+ : null;
201
+
202
+ if (label.groupExportId && !groupId) {
203
+ throw new BadRequestException(
204
+ `Workflow bundle is missing label group "${label.groupExportId}" required by label "${label.name}"`,
205
+ );
206
+ }
207
+
208
+ const existingByName = await manager.findOne(LabelOrmEntity, {
209
+ where: { name: label.name },
210
+ relations: ['group'],
211
+ });
212
+
213
+ if (existingByName) {
214
+ if (!this.isEquivalentLabel(existingByName, label, groupId)) {
215
+ throw new ConflictException(
216
+ `Label "${label.name}" already exists with different configuration`,
217
+ );
218
+ }
219
+
220
+ idMap[label.exportId] = existingByName.id;
221
+ resources.push(
222
+ buildResourceResult({
223
+ kind: 'label',
224
+ exportId: label.exportId,
225
+ localId: existingByName.id,
226
+ name: label.name,
227
+ action: 'reused',
228
+ }),
229
+ );
230
+ continue;
231
+ }
232
+
233
+ const existingByTitle = await manager.findOne(LabelOrmEntity, {
234
+ where: { title: label.title },
235
+ });
236
+ if (existingByTitle) {
237
+ throw new ConflictException(
238
+ `Label title "${label.title}" already exists with a different name`,
239
+ );
240
+ }
241
+
242
+ const payload = {
243
+ title: label.title,
244
+ name: label.name,
245
+ description: label.description ?? null,
246
+ group: groupId ? { id: groupId } : null,
247
+ builtin: false,
248
+ };
249
+ const created = await manager.save(
250
+ LabelOrmEntity,
251
+ manager.create(LabelOrmEntity, payload),
252
+ );
253
+
254
+ idMap[label.exportId] = created.id;
255
+ resources.push(
256
+ buildResourceResult({
257
+ kind: 'label',
258
+ exportId: label.exportId,
259
+ localId: created.id,
260
+ name: label.name,
261
+ action: 'created',
262
+ }),
263
+ );
264
+ postCreateEvents.push(buildPostCreateEvent('label', created, payload));
265
+ }
266
+
267
+ return {
268
+ idMap,
269
+ resources,
270
+ warnings: [],
271
+ postCreateEvents,
272
+ };
273
+ }
274
+
275
+ private isEquivalentLabel(
276
+ existing: LabelOrmEntity,
277
+ imported: WorkflowExportBundle['resources']['labels'][number],
278
+ importedGroupId: string | null,
279
+ ): boolean {
280
+ const existingGroupId =
281
+ existing.group && typeof existing.group === 'object'
282
+ ? existing.group.id
283
+ : null;
284
+
285
+ return (
286
+ existing.title === imported.title &&
287
+ (existing.description ?? null) === (imported.description ?? null) &&
288
+ existingGroupId === importedGroupId
289
+ );
290
+ }
291
+ }
@@ -0,0 +1,255 @@
1
+ /*
2
+ * Hexabot — Fair Core License (FCL-1.0-ALv2)
3
+ * Copyright (c) 2026 Hexastack.
4
+ * Full terms: see LICENSE.md.
5
+ */
6
+
7
+ import { isDeepStrictEqual } from 'node:util';
8
+
9
+ import {
10
+ type WorkflowExportBundle,
11
+ type WorkflowExportBundleMcpServer,
12
+ type WorkflowImportResourceResult,
13
+ } from '@hexabot-ai/types';
14
+ import {
15
+ BadRequestException,
16
+ ConflictException,
17
+ Injectable,
18
+ } from '@nestjs/common';
19
+ import { In } from 'typeorm';
20
+
21
+ import { CredentialOrmEntity } from '@/user/entities/credential.entity';
22
+ import { McpServerOrmEntity } from '@/workflow/entities/mcp-server.entity';
23
+ import { McpServerService } from '@/workflow/services/mcp-server.service';
24
+ import { McpServerTransport } from '@/workflow/types';
25
+
26
+ import {
27
+ WorkflowTransferAdapter,
28
+ WorkflowTransferExportContext,
29
+ WorkflowTransferImportContext,
30
+ WorkflowTransferResourceAdapter,
31
+ } from '../workflow-transfer-resource-adapter';
32
+ import {
33
+ assertFoundAll,
34
+ buildPostCreateEvent,
35
+ buildResourceResult,
36
+ uniqueResourceIds,
37
+ type WorkflowTransferImportAdapterResult,
38
+ } from '../workflow-transfer.types';
39
+
40
+ type McpServerExportResources = {
41
+ credentialIds: string[];
42
+ mcpServers: WorkflowExportBundleMcpServer[];
43
+ };
44
+
45
+ @WorkflowTransferAdapter()
46
+ @Injectable()
47
+ export class McpServerTransferAdapter extends WorkflowTransferResourceAdapter {
48
+ override readonly kind = 'mcpServer';
49
+
50
+ override readonly resourceKeys = ['mcpServers'];
51
+
52
+ override readonly dependsOn = ['credential'];
53
+
54
+ constructor(private readonly mcpServerService: McpServerService) {
55
+ super();
56
+ }
57
+
58
+ override async buildExportResources(
59
+ ctx: WorkflowTransferExportContext,
60
+ ): Promise<Record<string, WorkflowExportBundleMcpServer[]>> {
61
+ const resources = await this.buildMcpServerExportResources(
62
+ ctx.getRefs(this.kind),
63
+ );
64
+ ctx.addResourceRefs('credential', resources.credentialIds);
65
+
66
+ return {
67
+ mcpServers: resources.mcpServers,
68
+ };
69
+ }
70
+
71
+ private async buildMcpServerExportResources(
72
+ ids: string[],
73
+ ): Promise<McpServerExportResources> {
74
+ const uniqueIds = uniqueResourceIds(ids);
75
+ if (uniqueIds.length === 0) {
76
+ return { credentialIds: [], mcpServers: [] };
77
+ }
78
+
79
+ const servers = await this.mcpServerService.findAndPopulate({
80
+ where: { id: In(uniqueIds) },
81
+ });
82
+ assertFoundAll(
83
+ 'MCP server',
84
+ uniqueIds,
85
+ servers.map((server) => server.id),
86
+ );
87
+
88
+ return {
89
+ credentialIds: servers
90
+ .map((server) => server.credential?.id)
91
+ .filter((id): id is string => typeof id === 'string' && !!id),
92
+ mcpServers: servers.map((server) => ({
93
+ exportId: server.id,
94
+ name: server.name,
95
+ enabled: server.enabled,
96
+ transport: server.transport,
97
+ url: server.url ?? null,
98
+ command: server.command ?? null,
99
+ args: server.args ?? null,
100
+ cwd: server.cwd ?? null,
101
+ credentialExportId: server.credential?.id ?? null,
102
+ })),
103
+ };
104
+ }
105
+
106
+ override async importResources(
107
+ ctx: WorkflowTransferImportContext,
108
+ ): Promise<WorkflowTransferImportAdapterResult> {
109
+ const servers =
110
+ ctx.getResources<WorkflowExportBundle['resources']['mcpServers'][number]>(
111
+ 'mcpServers',
112
+ );
113
+ const credentialIdMap = ctx.getIdMap('credential');
114
+ const placeholderCredentialExportIds =
115
+ this.getPlaceholderCredentialExportIds(ctx);
116
+ const idMap: Record<string, string> = {};
117
+ const resources: WorkflowImportResourceResult[] = [];
118
+ const warnings: string[] = [];
119
+ const postCreateEvents: WorkflowTransferImportAdapterResult['postCreateEvents'] =
120
+ [];
121
+
122
+ for (const server of servers) {
123
+ const credentialId = server.credentialExportId
124
+ ? credentialIdMap[server.credentialExportId]
125
+ : null;
126
+ const credential = credentialId
127
+ ? await ctx.manager.findOne(CredentialOrmEntity, {
128
+ where: { id: credentialId },
129
+ })
130
+ : null;
131
+ const importedCredential = server.credentialExportId
132
+ ? credentialId
133
+ ? credential
134
+ : null
135
+ : null;
136
+ const shouldDisable =
137
+ Boolean(server.credentialExportId) &&
138
+ placeholderCredentialExportIds.has(server.credentialExportId!);
139
+ const existing = await ctx.manager.findOne(McpServerOrmEntity, {
140
+ where: { name: server.name },
141
+ relations: ['credential'],
142
+ });
143
+
144
+ if (existing) {
145
+ if (!this.isEquivalentMcpServer(existing, server, importedCredential)) {
146
+ throw new ConflictException(
147
+ `MCP server "${server.name}" already exists with different configuration`,
148
+ );
149
+ }
150
+
151
+ idMap[server.exportId] = existing.id;
152
+ resources.push(
153
+ buildResourceResult({
154
+ kind: 'mcpServer',
155
+ exportId: server.exportId,
156
+ localId: existing.id,
157
+ name: server.name,
158
+ action: 'reused',
159
+ }),
160
+ );
161
+ continue;
162
+ }
163
+
164
+ if (server.credentialExportId && !credentialId) {
165
+ throw new BadRequestException(
166
+ `Workflow bundle is missing credential "${server.credentialExportId}" required by MCP server "${server.name}"`,
167
+ );
168
+ }
169
+
170
+ const payload = {
171
+ name: server.name,
172
+ enabled: shouldDisable ? false : server.enabled,
173
+ transport: server.transport,
174
+ url: server.url,
175
+ command: server.command,
176
+ args: server.args,
177
+ cwd: server.cwd,
178
+ credential:
179
+ credentialId && server.transport === McpServerTransport.http
180
+ ? { id: credentialId }
181
+ : null,
182
+ };
183
+ const created = await ctx.manager.save(
184
+ McpServerOrmEntity,
185
+ ctx.manager.create(McpServerOrmEntity, payload),
186
+ );
187
+
188
+ idMap[server.exportId] = created.id;
189
+ resources.push(
190
+ buildResourceResult({
191
+ kind: 'mcpServer',
192
+ exportId: server.exportId,
193
+ localId: created.id,
194
+ name: server.name,
195
+ action: 'created',
196
+ }),
197
+ );
198
+ postCreateEvents.push(
199
+ buildPostCreateEvent('mcpServer', created, payload),
200
+ );
201
+
202
+ if (shouldDisable) {
203
+ warnings.push(
204
+ `MCP server "${server.name}" was disabled because it depends on an imported placeholder credential.`,
205
+ );
206
+ }
207
+ }
208
+
209
+ return {
210
+ idMap,
211
+ resources,
212
+ warnings,
213
+ postCreateEvents,
214
+ };
215
+ }
216
+
217
+ private getPlaceholderCredentialExportIds(
218
+ ctx: WorkflowTransferImportContext,
219
+ ): Set<string> {
220
+ const placeholderExportIds =
221
+ ctx.getDependencyResult('credential')?.metadata?.placeholderExportIds;
222
+ if (placeholderExportIds instanceof Set) {
223
+ return placeholderExportIds as Set<string>;
224
+ }
225
+
226
+ return Array.isArray(placeholderExportIds)
227
+ ? new Set(
228
+ placeholderExportIds.filter(
229
+ (id): id is string => typeof id === 'string',
230
+ ),
231
+ )
232
+ : new Set<string>();
233
+ }
234
+
235
+ private isEquivalentMcpServer(
236
+ existing: McpServerOrmEntity,
237
+ imported: WorkflowExportBundleMcpServer,
238
+ importedCredential: CredentialOrmEntity | null,
239
+ ): boolean {
240
+ const existingCredentialName =
241
+ existing.credential && typeof existing.credential === 'object'
242
+ ? existing.credential.name
243
+ : null;
244
+ const importedCredentialName = importedCredential?.name ?? null;
245
+
246
+ return (
247
+ existing.transport === imported.transport &&
248
+ (existing.url ?? null) === (imported.url ?? null) &&
249
+ (existing.command ?? null) === (imported.command ?? null) &&
250
+ isDeepStrictEqual(existing.args ?? null, imported.args ?? null) &&
251
+ (existing.cwd ?? null) === (imported.cwd ?? null) &&
252
+ existingCredentialName === importedCredentialName
253
+ );
254
+ }
255
+ }