@tiledesk/tiledesk-server 2.28.2 → 3.0.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 (44) hide show
  1. package/CHANGELOG.md +20 -0
  2. package/app.js +6 -35
  3. package/config/native_mcp/native_mcp_servers.json +7 -0
  4. package/controllers/chatbot.controller.js +659 -0
  5. package/controllers/intents.controller.js +139 -0
  6. package/errorCodes.js +2 -1
  7. package/event/botEvent.js +43 -3
  8. package/middleware/errorHandler.js +41 -0
  9. package/models/chatbotTemplates.js +4 -0
  10. package/models/faq.js +10 -0
  11. package/models/faq_kb.js +49 -0
  12. package/package.json +2 -2
  13. package/pubmodules/aiGenerations/README.md +154 -0
  14. package/pubmodules/aiGenerations/index.js +7 -0
  15. package/pubmodules/aiGenerations/listeners.js +96 -0
  16. package/pubmodules/aiGenerations/models/faqKbRevision.js +51 -0
  17. package/pubmodules/aiGenerations/routes/aiGenerations.js +426 -0
  18. package/pubmodules/aiGenerations/services/agentWriter.js +286 -0
  19. package/pubmodules/aiGenerations/services/atomic.js +70 -0
  20. package/pubmodules/aiGenerations/services/references.js +78 -0
  21. package/pubmodules/aiGenerations/services/revisionService.js +169 -0
  22. package/pubmodules/aiGenerations/services/snapshot.js +109 -0
  23. package/pubmodules/aiGenerations/test/aiGenerationsRoute.js +503 -0
  24. package/pubmodules/pubModulesManager.js +17 -0
  25. package/routes/faq.js +10 -3
  26. package/routes/faq_kb.js +716 -411
  27. package/services/chatbotService.js +84 -5
  28. package/services/faqService.js +159 -45
  29. package/services/v3FlowService.js +416 -0
  30. package/template/chatbot/blank.js +21 -3
  31. package/template/chatbot/blank_subagent.js +139 -0
  32. package/template/chatbot/blank_voice.js +21 -4
  33. package/template/chatbot/blank_voice_twilio.js +21 -5
  34. package/template/chatbot/index.js +1 -0
  35. package/template/chatbot/subagent_notes.js +30 -0
  36. package/test/faqRoute.js +83 -0
  37. package/test/faqkbRoute.js +982 -523
  38. package/test/faqkbV3Route.js +436 -0
  39. package/test/fixtures/example-json-with-subagents.txt +1 -0
  40. package/test/nativemcp.test.js +6 -1
  41. package/test/v3FlowService.js +187 -0
  42. package/test/webhookRoute.js +2 -1
  43. package/utils/AppError.js +9 -0
  44. package/utils/asyncHandler.js +10 -0
@@ -1,5 +1,6 @@
1
1
  const axios = require("axios").default;
2
2
  const winston = require('../config/winston');
3
+ const botEvent = require('../event/botEvent');
3
4
  let Faq_kb = require("../models/faq_kb");
4
5
 
5
6
  class ChatbotService {
@@ -9,11 +10,20 @@ class ChatbotService {
9
10
  async fork(id_faq_kb, api_url, token, project_id, options) {
10
11
  winston.debug("(ChatbotService) fork");
11
12
 
13
+ // `true` is the older skip-subagents flag; publish passes `{ skipSubagents, forPublish }`.
14
+ const skipSubagents = options === true || (options && options.skipSubagents === true);
12
15
  const forPublish = options && options.forPublish === true;
13
- const forPublishQuery = forPublish ? '&for_publish=true' : '';
16
+
17
+ let url = api_url + '/' + project_id + '/faq_kb/fork/' + id_faq_kb + '?projectid=' + project_id + '&public=false&globals=true';
18
+ if (skipSubagents) {
19
+ url += '&skipSubagents=true';
20
+ }
21
+ if (forPublish) {
22
+ url += '&for_publish=true';
23
+ }
14
24
 
15
25
  return await axios({
16
- url: api_url + '/' + project_id + '/faq_kb/fork/' + id_faq_kb + '?projectid=' + project_id + '&public=false&globals=true' + forPublishQuery,
26
+ url: url,
17
27
  headers: {
18
28
  'Content-Type': 'application/json',
19
29
  'Authorization': token
@@ -45,8 +55,11 @@ class ChatbotService {
45
55
  },
46
56
  method: 'GET'
47
57
  }).then((resbody) => {
48
- winston.debug("(ChatbotService) forking private chatbot " + resbody.data.name)
49
58
  let chatbot = resbody.data;
59
+ if (typeof chatbot === 'string') {
60
+ chatbot = JSON.parse(chatbot);
61
+ }
62
+ winston.debug("(ChatbotService) forking private chatbot " + chatbot.name)
50
63
  return chatbot;
51
64
  }).catch((err) => {
52
65
  winston.error('(ChatbotService) FAQ_KB EXPORTJSON ERROR ' + err);
@@ -63,8 +76,11 @@ class ChatbotService {
63
76
  },
64
77
  method: 'GET'
65
78
  }).then((resbody) => {
66
- winston.debug("(ChatbotService) forking public chatbot " + resbody.data.name);
67
79
  let chatbot = resbody.data;
80
+ if (typeof chatbot === 'string') {
81
+ chatbot = JSON.parse(chatbot);
82
+ }
83
+ winston.debug("(ChatbotService) forking public chatbot " + chatbot.name);
68
84
  return chatbot
69
85
  }).catch((err) => {
70
86
  winston.error('(ChatbotService) FAQ_KB CHATBOT TEMPLATES ERROR ' + err);
@@ -100,8 +116,10 @@ class ChatbotService {
100
116
 
101
117
  winston.debug("(ChatbotService) importFaqs");
102
118
 
119
+ // Full import into the newly created parent: intents + nested subagents.
120
+ // Subagents are parent-scoped and get parent_id remapped by chatbotController.import.
103
121
  return await axios({
104
- url: api_url + '/' + project_id + '/faq_kb/importjson/' + id_faq_kb + "?intentsOnly=true",
122
+ url: api_url + '/' + project_id + '/faq_kb/importjson/' + id_faq_kb,
105
123
  headers: {
106
124
  'Content-Type': 'application/json',
107
125
  'Authorization': token
@@ -125,6 +143,67 @@ class ChatbotService {
125
143
  return Promise.reject(err);
126
144
  });
127
145
  }
146
+
147
+ _tilebotEndpoint() {
148
+ const port = process.env.PORT || '3000';
149
+ let endpoint = process.env.API_ENDPOINT
150
+ ? process.env.API_ENDPOINT + "/modules/tilebot/ext/"
151
+ : ("http://localhost:" + port + "/modules/tilebot/ext/");
152
+
153
+ if (process.env.TILEBOT_ENDPOINT) {
154
+ endpoint = process.env.TILEBOT_ENDPOINT + "/ext/";
155
+ }
156
+ return endpoint;
157
+ }
158
+
159
+ async publish({ id_faq_kb, chatbot_id, api_url, token, project_id, user_id, release_note, req }) {
160
+ const sourceId = chatbot_id || id_faq_kb;
161
+ winston.debug("(ChatbotService) publish id_faq_kb: " + id_faq_kb + " source: " + sourceId);
162
+
163
+ // Keep callsubagent.botId on the development subagent. Its url points at the published snapshot.
164
+ // for_publish skips FAQ_KB_CREATE on the trashed snapshot; FAQ_KB_PUBLISH is emitted below.
165
+ const forked = await this.fork(sourceId, api_url, token, project_id, { skipSubagents: true, forPublish: true });
166
+ const forkedChatBotId = forked.bot_id;
167
+ winston.debug("(ChatbotService) publish forkedChatBotId: " + forkedChatBotId);
168
+
169
+ const updatedForkedChatbot = await Faq_kb.findByIdAndUpdate(
170
+ forkedChatBotId,
171
+ {
172
+ $unset: { slug: 1 },
173
+ trashed: true,
174
+ root_id: id_faq_kb,
175
+ release_note: release_note,
176
+ publishedBy: user_id,
177
+ publishedAt: new Date().getTime()
178
+ },
179
+ { new: true, upsert: true }
180
+ ).exec();
181
+ winston.debug("(ChatbotService) publish updatedForkedChatbot: ", updatedForkedChatbot);
182
+ botEvent.emit('faqbot.update', updatedForkedChatbot);
183
+
184
+ const tilebotEndpoint = this._tilebotEndpoint();
185
+ winston.debug("(ChatbotService) TILEBOT_ENDPOINT: " + tilebotEndpoint);
186
+
187
+ const updatedOriginalChatbot = await Faq_kb.findByIdAndUpdate(
188
+ id_faq_kb,
189
+ { url: tilebotEndpoint + forkedChatBotId },
190
+ { new: true, upsert: true }
191
+ ).exec();
192
+ winston.debug("(ChatbotService) publish updatedOriginalChatbot: ", updatedOriginalChatbot);
193
+
194
+ await this.setModified(id_faq_kb, false);
195
+ botEvent.emit('faqbot.update', updatedOriginalChatbot);
196
+ botEvent.emit('faqbot.publish', {
197
+ req,
198
+ chatbot: updatedOriginalChatbot,
199
+ publishedBotId: forkedChatBotId,
200
+ release_note
201
+ });
202
+
203
+ return { bot_id: forkedChatBotId };
204
+ }
205
+
128
206
  }
129
207
 
208
+
130
209
  module.exports = { ChatbotService }
@@ -5,66 +5,179 @@ const botEvent = require('../event/botEvent');
5
5
  const chatbotTypes = require("../models/chatbotTypes");
6
6
  const chatbotTemplates = require("../models/chatbotTemplates");
7
7
  const templates = require('../template/chatbot');
8
+ const subagentNotes = require('../template/chatbot/subagent_notes');
9
+ const v3FlowService = require('./v3FlowService');
8
10
 
9
11
  class FaqService {
10
12
 
11
- create(id_project, id_user, data) {
13
+ // create(id_project, id_user, data) {
12
14
 
13
- return new Promise((resolve, reject) => {
15
+ // return new Promise((resolve, reject) => {
14
16
 
15
- var newFaq_kb = new Faq_kb({
16
- name: data.name,
17
- slug: data.slug,
18
- description: data.description,
19
- url: data.url,
20
- id_project: id_project,
21
- webhook_url: data.webhook_url,
22
- webhook_enabled: data.webhook_enabled,
23
- type: data.type,
24
- subtype: data.subtype,
25
- language: data.language,
26
- public: false,
27
- certified: false,
28
- mainCategory: data.mainCategory,
29
- intentsEngine: data.intentsEngine,
30
- trashed: false,
31
- createdBy: id_user,
32
- updatedBy: id_user,
33
- attributes: data.attributes
34
- });
17
+ // if (data.type === "subagent" && !data.parent_id) {
18
+ // return reject('Parent ID is required for subagent');
19
+ // }
35
20
 
36
- newFaq_kb.save((err, savedFaq_kb) => {
21
+ // var newFaq_kb = new Faq_kb({
22
+ // name: data.name,
23
+ // slug: data.slug,
24
+ // description: data.description,
25
+ // url: data.url,
26
+ // id_project: id_project,
27
+ // webhook_url: data.webhook_url,
28
+ // webhook_enabled: data.webhook_enabled,
29
+ // type: data.type,
30
+ // subtype: data.subtype,
31
+ // language: data.language,
32
+ // public: false,
33
+ // certified: false,
34
+ // mainCategory: data.mainCategory,
35
+ // intentsEngine: data.intentsEngine,
36
+ // trashed: false,
37
+ // createdBy: id_user,
38
+ // updatedBy: id_user,
39
+ // attributes: data.attributes,
40
+ // parent_id: data.parent_id
41
+ // });
37
42
 
38
- if (err) {
39
- winston.error('(FaqService) error saving new chatbot ', err)
40
- return reject('Error saving object.');
41
- }
43
+ // newFaq_kb.save((err, savedFaq_kb) => {
42
44
 
43
- winston.debug('(FaqService) saved chatbot ', savedFaq_kb.toObject())
44
- botEvent.emit('faqbot.create', savedFaq_kb);
45
+ // if (err) {
46
+ // winston.error('(FaqService) error saving new chatbot ', err)
47
+ // return reject('Error saving object.');
48
+ // }
45
49
 
46
- winston.debug('type ' + data.type)
50
+ // winston.debug('(FaqService) saved chatbot ', savedFaq_kb.toObject())
51
+ // botEvent.emit('faqbot.create', savedFaq_kb);
47
52
 
48
- if (data.type === "internal" || data.type === "tilebot") {
53
+ // winston.debug('type ' + data.type)
49
54
 
50
- let template = this.#resolveTemplate(data.subtype, data.template);
51
- winston.debug('template ' + template);
55
+ // if (data.type === "internal" || data.type === "tilebot") {
52
56
 
53
- let options = {};
54
- if (data.namespace_id) {
55
- options.namespace_id = data.namespace_id;
56
- }
57
+ // let template = this.#resolveTemplate(data.subtype, data.template);
58
+ // winston.debug('template ' + template);
57
59
 
58
- this.createGreetingsAndOperationalsFaqs(savedFaq_kb._id, savedFaq_kb.createdBy, savedFaq_kb.id_project, template, options);
60
+ // let options = {};
61
+ // if (data.namespace_id) {
62
+ // options.namespace_id = data.namespace_id;
63
+ // }
59
64
 
60
- } else {
61
- winston.debug('(FaqService) Chatbot type: external bot');
62
- }
65
+ // this.createGreetingsAndOperationalsFaqs(savedFaq_kb._id, savedFaq_kb.createdBy, savedFaq_kb.id_project, template, options);
63
66
 
64
- return resolve(savedFaq_kb);
65
- });
67
+ // } else {
68
+ // winston.debug('(FaqService) Chatbot type: external bot');
69
+ // }
70
+
71
+ // return resolve(savedFaq_kb);
72
+ // });
73
+
74
+ // });
75
+ // }
76
+
77
+ async create(id_project, id_user, data) {
78
+
79
+ if (!this.validateSubagent(data)) {
80
+ throw new Error("parent_id is required for subtype 'subagent'");
81
+ }
82
+
83
+ // A creation body written the old way (`attributes.dsVersion`, `attributes.connectors`) is promoted
84
+ // to the fields, as the import does: a client not yet updated still creates a proper V3 agent.
85
+ v3FlowService.promoteLegacyJson(data);
86
+
87
+ if (data.connectors !== undefined && data.connectors !== null) {
88
+ const errors = v3FlowService.validateConnectors(data.connectors);
89
+ if (errors.length > 0) {
90
+ winston.error('(FaqService) invalid connectors list: ' + errors.join('; '));
91
+ throw new Error('Invalid connectors list: ' + errors.join('; '));
92
+ }
93
+ }
94
+
95
+ let chatbot = this.createChatbotObject(id_project, id_user, data);
96
+
97
+ let template;
98
+ if (data.type === "internal" || data.type === "tilebot") {
99
+ template = this.#resolveTemplate(data.subtype, data.template);
100
+ if (template === 'blank_subagent') {
101
+ this.#applyBlankSubagentAttributes(chatbot);
102
+ }
103
+ }
66
104
 
105
+ let options = {};
106
+ if (data.namespace_id) {
107
+ options.namespace_id = data.namespace_id;
108
+ }
109
+
110
+ // A V3 agent is born in V3 form: the template's blocks lose their V2 destinations and the
111
+ // connectors list is saved with the bot, so nothing has to be repaired on first open.
112
+ // A list given by the caller (an import, a fork) wins: the template is not converted then.
113
+ let templateFaqs = null;
114
+ if (template && v3FlowService.isV3(chatbot) && (data.connectors === undefined || data.connectors === null)) {
115
+ const flow = v3FlowService.toV3Flow(templates[template](options));
116
+ chatbot.connectors = flow.connectors;
117
+ templateFaqs = flow.intents;
118
+ winston.debug('(FaqService) template ' + template + ' in V3 form: ' + flow.connectors.items.length + ' connectors');
119
+ }
120
+
121
+ let savedChatbot;
122
+ try {
123
+ savedChatbot = await chatbot.save();
124
+ winston.verbose('(FaqService) saved chatbot ', savedChatbot.toObject());
125
+ } catch (error) {
126
+ throw error;
127
+ }
128
+
129
+ if (template) {
130
+ this.createGreetingsAndOperationalsFaqs(savedChatbot._id, savedChatbot.createdBy, savedChatbot.id_project, template, options, templateFaqs);
131
+ }
132
+
133
+ botEvent.emit('faqbot.create', savedChatbot);
134
+ return savedChatbot;
135
+ }
136
+
137
+ #applyBlankSubagentAttributes(chatbot) {
138
+ const notes = JSON.parse(JSON.stringify(subagentNotes.notes)).map(note => {
139
+ note.id_faq_kb = chatbot._id.toString();
140
+ return note;
67
141
  });
142
+
143
+ chatbot.attributes = {
144
+ ...(chatbot.attributes || {}),
145
+ notes
146
+ };
147
+ }
148
+
149
+ createChatbotObject(id_project, id_user, data) {
150
+ return new Faq_kb({
151
+ name: data.name,
152
+ slug: data.slug,
153
+ description: data.description,
154
+ url: data.url,
155
+ id_project: id_project.toString(),
156
+ webhook_url: data.webhook_url,
157
+ webhook_enabled: data.webhook_enabled,
158
+ type: data.type,
159
+ subtype: data.subtype,
160
+ language: data.language,
161
+ public: false,
162
+ certified: false,
163
+ mainCategory: data.mainCategory,
164
+ intentsEngine: data.intentsEngine,
165
+ trashed: false,
166
+ createdBy: id_user,
167
+ updatedBy: id_user,
168
+ attributes: data.attributes,
169
+ parent_id: data.parent_id,
170
+ ds_version: v3FlowService.normalizeDsVersion(data.ds_version),
171
+ connectors: data.connectors
172
+ })
173
+ }
174
+
175
+ validateSubagent(data) {
176
+ if (data.subtype === "subagent" && !data.parent_id) {
177
+ winston.error('(FaqService) parent_id is required for subtype ' + data.subtype);
178
+ return false;
179
+ }
180
+ return true;
68
181
  }
69
182
 
70
183
  #resolveTemplate(subtype, template) {
@@ -76,13 +189,14 @@ class FaqService {
76
189
  return defaultTemplate;
77
190
  }
78
191
 
79
- createGreetingsAndOperationalsFaqs(faq_kb_id, created_by, projectid, template, options) {
192
+ createGreetingsAndOperationalsFaqs(faq_kb_id, created_by, projectid, template, options, faqs) {
80
193
 
81
194
  return new Promise( async (resolve, reject) => {
82
195
 
83
196
  winston.debug('(FaqService) create faqs from template: ' + template);
84
197
 
85
- let faqsArray = templates[template](options);
198
+ // `faqs`: the template's blocks already prepared by the caller (a V3 agent); otherwise the template as it is.
199
+ let faqsArray = faqs || templates[template](options);
86
200
 
87
201
  faqsArray.forEach(faq => {
88
202