@intranefr/superbackend 1.6.6 → 1.6.7

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 (51) hide show
  1. package/.env.example +4 -0
  2. package/README.md +18 -0
  3. package/package.json +6 -1
  4. package/public/js/admin-superdemos.js +396 -0
  5. package/public/sdk/superdemos.iife.js +614 -0
  6. package/public/superdemos-qa.html +324 -0
  7. package/sdk/superdemos/browser/src/index.js +719 -0
  8. package/src/cli/agent-chat.js +369 -0
  9. package/src/cli/agent-list.js +42 -0
  10. package/src/controllers/adminAgentsChat.controller.js +172 -0
  11. package/src/controllers/adminSuperDemos.controller.js +382 -0
  12. package/src/controllers/superDemosPublic.controller.js +126 -0
  13. package/src/middleware.js +102 -19
  14. package/src/models/BlogAutomationLock.js +4 -4
  15. package/src/models/BlogPost.js +16 -16
  16. package/src/models/CacheEntry.js +17 -6
  17. package/src/models/JsonConfig.js +2 -4
  18. package/src/models/RateLimitMetricBucket.js +10 -5
  19. package/src/models/SuperDemo.js +38 -0
  20. package/src/models/SuperDemoProject.js +32 -0
  21. package/src/models/SuperDemoStep.js +27 -0
  22. package/src/routes/adminAgents.routes.js +10 -0
  23. package/src/routes/adminMarkdowns.routes.js +3 -0
  24. package/src/routes/adminSuperDemos.routes.js +31 -0
  25. package/src/routes/superDemos.routes.js +9 -0
  26. package/src/services/auditLogger.js +75 -37
  27. package/src/services/email.service.js +18 -3
  28. package/src/services/superDemosAuthoringSessions.service.js +132 -0
  29. package/src/services/superDemosWs.service.js +164 -0
  30. package/src/services/terminalsWs.service.js +35 -3
  31. package/src/utils/rbac/rightsRegistry.js +2 -0
  32. package/views/admin-agents.ejs +261 -11
  33. package/views/admin-dashboard.ejs +78 -8
  34. package/views/admin-superdemos.ejs +335 -0
  35. package/views/admin-terminals.ejs +462 -34
  36. package/views/partials/admin/agents-chat.ejs +80 -0
  37. package/views/partials/dashboard/nav-items.ejs +1 -0
  38. package/views/partials/dashboard/tab-bar.ejs +6 -0
  39. package/cookies.txt +0 -6
  40. package/cookies1.txt +0 -6
  41. package/cookies2.txt +0 -6
  42. package/cookies3.txt +0 -6
  43. package/cookies4.txt +0 -5
  44. package/cookies_old.txt +0 -5
  45. package/cookies_old_test.txt +0 -6
  46. package/cookies_super.txt +0 -5
  47. package/cookies_super_test.txt +0 -6
  48. package/cookies_test.txt +0 -6
  49. package/test-access.js +0 -63
  50. package/test-iframe-fix.html +0 -63
  51. package/test-iframe.html +0 -14
@@ -0,0 +1,369 @@
1
+ #!/usr/bin/env node
2
+
3
+ require('dotenv').config(process.env.MODE ? { path: `.env.${process.env.MODE}` } : {});
4
+ process.env.TUI_MODE = 'true';
5
+ const { ScriptBase } = require('../helpers/scriptBase');
6
+ const agentService = require('../services/agent.service');
7
+ const llmService = require('../services/llm.service');
8
+ const Agent = require('../models/Agent');
9
+ const JsonConfig = require('../models/JsonConfig');
10
+ const term = require('terminal-kit').terminal;
11
+
12
+ class AgentChatTUI extends ScriptBase {
13
+ constructor() {
14
+ super({
15
+ name: 'AgentChatTUI',
16
+ autoDisconnect: true,
17
+ timeout: 3600000
18
+ });
19
+ this.chatId = `tui-${Date.now()}`;
20
+ this.escCount = 0;
21
+ this.escTimer = null;
22
+ this.abortController = null;
23
+ this.isProcessing = false;
24
+ }
25
+
26
+ getRelativeTime(date) {
27
+ const now = new Date();
28
+ const diff = now - new Date(date);
29
+ const seconds = Math.floor(diff / 1000);
30
+ const minutes = Math.floor(seconds / 60);
31
+ const hours = Math.floor(minutes / 60);
32
+ const days = Math.floor(hours / 24);
33
+
34
+ if (seconds < 60) return 'just now';
35
+ if (minutes < 60) return `${minutes}m ago`;
36
+ if (hours < 24) return `${hours}h ago`;
37
+ if (days < 7) return `${days}d ago`;
38
+ return new Date(date).toLocaleDateString();
39
+ }
40
+
41
+ async execute(context) {
42
+ term.clear();
43
+ term.bold.cyan('--- SuperBackend AI Agent TUI ---\n\n');
44
+
45
+ const agents = await Agent.find().lean();
46
+ if (agents.length === 0) {
47
+ term.red('❌ No agents found. Please create an agent in the admin UI first.\n');
48
+ return;
49
+ }
50
+
51
+ term.white('Available Agents:\n');
52
+ agents.forEach((a, i) => {
53
+ term.white(`${i + 1}. `).cyan(`${a.name} `).gray(`(${a.model})\n`);
54
+ });
55
+ term.white(`${agents.length + 1}. `).red('Exit\n');
56
+
57
+ term.white('\nSelect an agent (number): ');
58
+ const choice = await term.inputField().promise;
59
+ const index = parseInt(choice, 10) - 1;
60
+
61
+ if (isNaN(index) || index < 0 || index >= agents.length) {
62
+ if (index === agents.length) {
63
+ term.green('\nGoodbye!\n');
64
+ return;
65
+ }
66
+ term.red('\nInvalid selection.\n');
67
+ return;
68
+ }
69
+
70
+ const selectedAgent = agents[index];
71
+ this.selectedAgent = selectedAgent;
72
+ const senderId = 'cli-user';
73
+
74
+ term.clear();
75
+ term.bold.cyan(`\n--- Chatting with: ${selectedAgent.name} ---\n\n`);
76
+
77
+ term.on('key', (name) => {
78
+ if (name === 'ESCAPE') {
79
+ if (!this.isProcessing) return;
80
+
81
+ this.escCount++;
82
+ if (this.escCount === 1) {
83
+ this.drawStatusBar(' Press ESC again to stop operation ', 'bgRed');
84
+
85
+ this.escTimer = setTimeout(() => {
86
+ this.escCount = 0;
87
+ this.drawStatusBar();
88
+ }, 2000);
89
+ } else if (this.escCount >= 2) {
90
+ if (this.abortController) {
91
+ this.abortController.abort();
92
+ this.drawStatusBar(' 🛑 Aborting... ', 'bgYellow', 'black');
93
+ setTimeout(() => this.drawStatusBar(), 1000);
94
+ }
95
+ this.escCount = 0;
96
+ if (this.escTimer) clearTimeout(this.escTimer);
97
+ }
98
+ } else if (name === 'CTRL_C') {
99
+ term.grabInput(false);
100
+ process.exit();
101
+ }
102
+ });
103
+
104
+ term.grabInput(true);
105
+
106
+ term.scrollingRegion(1, term.height - 2);
107
+
108
+ this.drawStatusBar();
109
+
110
+ term.on('resize', (width, height) => {
111
+ term.scrollingRegion(1, height - 2);
112
+ this.drawStatusBar();
113
+ });
114
+
115
+ while (true) {
116
+ term.bold.cyan(`\n[${this.chatId.slice(-8)}] You: `);
117
+ const input = await term.inputField().promise;
118
+ term('\n');
119
+
120
+ const cmd = input.toLowerCase().trim();
121
+ if (['exit', 'quit', '\\q'].includes(cmd)) {
122
+ term.bold.green('\n👋 Ending session...\n');
123
+ break;
124
+ }
125
+
126
+ if (cmd === '/compact') {
127
+ term.bold.yellow('✨ Compacting session... ');
128
+ try {
129
+ const result = await agentService.compactSession(selectedAgent._id, this.chatId);
130
+ if (result.success) {
131
+ term.bold.green(`Done! Created snapshot: ${result.snapshotId}\n`);
132
+ } else {
133
+ term.bold.red(`Failed: ${result.message}\n`);
134
+ }
135
+ } catch (err) {
136
+ term.bold.red(`Error: ${err.message}\n`);
137
+ }
138
+ continue;
139
+ }
140
+
141
+ if (cmd === '/new') {
142
+ this.chatId = `tui-${Date.now()}`;
143
+ this.drawStatusBar();
144
+ term.bold.green(`\n🆕 Started new session: ${this.chatId}\n`);
145
+ continue;
146
+ }
147
+
148
+ if (cmd === '/sessions') {
149
+ const sessionConfigs = await JsonConfig.find({
150
+ alias: { $regex: /^agent-session-tui-/ }
151
+ })
152
+ .sort({ updatedAt: -1 })
153
+ .limit(10)
154
+ .lean();
155
+
156
+ term.bold.white('\n--- Recent TUI Sessions ---\n');
157
+ if (sessionConfigs.length === 0) {
158
+ term.gray('No recent sessions found.\n');
159
+ } else {
160
+ sessionConfigs.forEach((c, i) => {
161
+ const data = JSON.parse(c.jsonRaw);
162
+ const labelDisplay = data.label ? `^c[${data.label}] ` : '';
163
+ const timeAgo = this.getRelativeTime(c.updatedAt);
164
+ term.white(`${i + 1}. `).cyan(`${labelDisplay}`).bold(`${data.id} `).gray(`(${timeAgo})\n`);
165
+ term.gray(` Tokens: ${data.totalTokens || 0} | Snapshot: ${data.lastSnapshotId || 'None'}\n`);
166
+ });
167
+ }
168
+
169
+ term.bold.white('\nSelect session number (or Enter to cancel): ');
170
+ const sessionChoice = await term.inputField().promise;
171
+ if (sessionChoice.trim()) {
172
+ const sIndex = parseInt(sessionChoice, 10) - 1;
173
+ if (!isNaN(sIndex) && sIndex >= 0 && sIndex < sessionConfigs.length) {
174
+ const selectedSession = JSON.parse(sessionConfigs[sIndex].jsonRaw);
175
+ this.chatId = selectedSession.id;
176
+ this.drawStatusBar();
177
+ const labelDisplay = selectedSession.label ? ` (${selectedSession.label})` : '';
178
+ term.bold.green(`\n🔄 Switched to session: ${this.chatId}${labelDisplay}\n`);
179
+ } else {
180
+ term.bold.red('\nInvalid selection.\n');
181
+ }
182
+ } else {
183
+ term('\n');
184
+ }
185
+ continue;
186
+ }
187
+
188
+ if (cmd.startsWith('/rename')) {
189
+ const newLabel = input.replace('/rename', '').trim();
190
+ if (!newLabel) {
191
+ term.bold.red('❌ Please provide a label: /rename My Session Label\n');
192
+ continue;
193
+ }
194
+
195
+ term.bold.yellow('✨ Renaming session... ');
196
+ try {
197
+ const result = await agentService.renameSession(this.chatId, newLabel);
198
+ if (result.success) {
199
+ term.bold.green(`Done! Session renamed to: ${result.label}\n`);
200
+ } else {
201
+ term.bold.red(`Failed: ${result.message}\n`);
202
+ }
203
+ } catch (err) {
204
+ term.bold.red(`Error: ${err.message}\n`);
205
+ }
206
+ continue;
207
+ }
208
+
209
+ if (!input.trim()) continue;
210
+
211
+ this.isProcessing = true;
212
+ this.abortController = new AbortController();
213
+
214
+ let thinkingSpinner = null;
215
+ let hasErasedInitialThinking = false;
216
+
217
+ try {
218
+ let hasStartedContent = false;
219
+ let hasStartedReasoning = false;
220
+
221
+ this.drawStatusBar(` ⏳ Starting Loop... `, 'bgYellow', 'black');
222
+
223
+ const response = await agentService.processMessage(selectedAgent._id, {
224
+ content: input,
225
+ senderId,
226
+ chatId: this.chatId
227
+ }, {
228
+ abortSignal: this.abortController.signal,
229
+ onProgress: async (p) => {
230
+ if (p.status === 'reasoning') {
231
+ if (!hasStartedReasoning) {
232
+ hasStartedReasoning = true;
233
+ this.drawStatusBar(` 🧠 Loop ${p.iteration || '?'} | thinking... `);
234
+ term.column(1).eraseLine();
235
+ hasErasedInitialThinking = true;
236
+ term.bold.magenta(`${selectedAgent.name} (thinking): `).gray.italic('\n');
237
+ }
238
+ term.gray.italic(p.token);
239
+ } else if (p.status === 'streaming_content') {
240
+ if (thinkingSpinner) {
241
+ thinkingSpinner.animate(false);
242
+ thinkingSpinner = null;
243
+ }
244
+ if (!hasStartedContent) {
245
+ hasStartedContent = true;
246
+ this.drawStatusBar(` ✍️ Loop ${p.iteration || '?'} | responding... `);
247
+ if (hasStartedReasoning) {
248
+ term('\n\n');
249
+ } else {
250
+ term.column(1).eraseLine();
251
+ hasErasedInitialThinking = true;
252
+ }
253
+ term.bold.magenta(`${selectedAgent.name}: `);
254
+ }
255
+ term.white(p.token);
256
+ } else if (p.status === 'thinking') {
257
+ hasStartedContent = false;
258
+ hasStartedReasoning = false;
259
+ this.drawStatusBar(` ⏳ Loop ${p.iteration}/${p.maxIterations} | thinking... `, 'bgYellow', 'black');
260
+
261
+ if (!hasErasedInitialThinking && !thinkingSpinner) {
262
+ term.bold.magenta(`${selectedAgent.name}: `);
263
+ thinkingSpinner = await term.spinner('dots');
264
+ }
265
+ } else if (p.status === 'executing_tools') {
266
+ this.drawStatusBar(` 🛠️ Loop ${p.iteration || '?'} | preparing tools... `, 'bgCyan', 'black');
267
+ } else if (p.status === 'executing_tool') {
268
+ this.drawStatusBar(` ⚙️ Loop ${p.iteration || '?'} | ${p.tool}... `, 'bgCyan', 'black');
269
+ } else if (p.status === 'initializing') {
270
+ this.drawStatusBar(` 🚀 ${p.message} `, 'bgBlue', 'white');
271
+ }
272
+ }
273
+ });
274
+
275
+ const { text, usage, chatId: sessionChatId } = response;
276
+ this.chatId = sessionChatId;
277
+
278
+ if (thinkingSpinner) thinkingSpinner.animate(false);
279
+ this.drawStatusBar();
280
+
281
+ if (this.abortController.signal.aborted) {
282
+ throw new Error('Operation aborted');
283
+ }
284
+
285
+ if (!hasStartedContent) {
286
+ if (!hasErasedInitialThinking) term.column(1).eraseLine();
287
+ term.bold.magenta(`${selectedAgent.name}: `).white(text + '\n');
288
+ } else {
289
+ term('\n');
290
+ }
291
+
292
+ if (usage) {
293
+ const contextLength = await llmService.getModelContextLength(selectedAgent.model, selectedAgent.providerKey);
294
+ const currentTokens = usage.total_tokens || (usage.prompt_tokens + usage.completion_tokens);
295
+ this.drawStatusBar(null, null, null, { tokens: currentTokens, max: contextLength });
296
+ }
297
+ } catch (err) {
298
+ if (thinkingSpinner) thinkingSpinner.animate(false);
299
+ term.column(1).eraseLine();
300
+ if (err.message === 'Operation aborted' || err.message.includes('aborted')) {
301
+ term.bold.yellow('⚠️ Operation cancelled by user.\n');
302
+ } else {
303
+ term.bold.red(`❌ Error: ${err.message}\n`);
304
+ }
305
+ } finally {
306
+ this.isProcessing = false;
307
+ this.abortController = null;
308
+ this.escCount = 0;
309
+ }
310
+ }
311
+ }
312
+
313
+ drawStatusBar(message = null, bgColor = 'bgBlue', textColor = 'white', meta = {}) {
314
+ term.saveCursor();
315
+
316
+ term.moveTo(1, term.height - 1);
317
+ term.eraseLine();
318
+ term.gray(" Commands: '/new', '/sessions', '/compact', '/rename [label]', 'exit'");
319
+
320
+ term.moveTo(1, term.height);
321
+ term.eraseLine();
322
+
323
+ if (message) {
324
+ term[bgColor][textColor](` ${message.trim()} `);
325
+ } else {
326
+ const id = this.chatId.slice(-8);
327
+ const agent = this.selectedAgent ? this.selectedAgent.name : 'No Agent';
328
+ const model = this.selectedAgent ? this.selectedAgent.model : 'No Model';
329
+
330
+ term.bgCyan.black(` 🆔 ${id} `);
331
+ term.bgBlue.white(` 🤖 ${agent} `);
332
+ term.bgBlack.gray(` 🧠 ${model} `);
333
+
334
+ if (meta.tokens || this.lastTokens) {
335
+ const tokens = meta.tokens || this.lastTokens;
336
+ const max = meta.max || this.lastMax;
337
+ this.lastTokens = tokens;
338
+ this.lastMax = max;
339
+
340
+ const formatNum = (num) => num >= 1000 ? (num / 1000).toFixed(1) + 'k' : num;
341
+ const formattedTokens = formatNum(tokens);
342
+ const formattedMax = formatNum(max);
343
+ const perc = max > 0 ? ((tokens / max) * 100).toFixed(1) : 0;
344
+ const color = perc > 80 ? 'bgRed' : perc > 50 ? 'bgYellow' : 'bgGreen';
345
+ term[color].black(` 📊 ${formattedTokens}/${formattedMax} (${perc}%) `);
346
+ }
347
+ }
348
+
349
+ term.restoreCursor();
350
+ }
351
+
352
+ async cleanup() {
353
+ term.grabInput(false);
354
+ term.scrollingRegion(1, term.height);
355
+ term.moveTo(1, term.height).eraseLine();
356
+ await new Promise(r => setTimeout(r, 100));
357
+ }
358
+ }
359
+
360
+ // Handle direct execution (not as a script)
361
+ if (require.main === module) {
362
+ const tui = new AgentChatTUI();
363
+ tui.run().catch(err => {
364
+ console.error('Fatal error:', err);
365
+ process.exit(1);
366
+ });
367
+ }
368
+
369
+ module.exports = AgentChatTUI;
@@ -0,0 +1,42 @@
1
+ #!/usr/bin/env node
2
+
3
+ require('dotenv').config(process.env.MODE ? { path: `.env.${process.env.MODE}` } : {});
4
+ const Agent = require('../models/Agent');
5
+
6
+ async function listAgents() {
7
+ try {
8
+ const agents = await Agent.find().lean();
9
+
10
+ if (agents.length === 0) {
11
+ console.log('❌ No agents found. Please create an agent in the admin UI first.');
12
+ return;
13
+ }
14
+
15
+ console.log('\n🤖 Available Agents:\n');
16
+ agents.forEach((agent, index) => {
17
+ console.log(`${index + 1}. ${agent.name} (${agent.model})`);
18
+ if (agent.systemPrompt) {
19
+ const prompt = agent.systemPrompt.substring(0, 100);
20
+ console.log(` "${prompt}${agent.systemPrompt.length > 100 ? '...' : ''}"`);
21
+ }
22
+ if (agent.tools && agent.tools.length > 0) {
23
+ console.log(` Tools: ${agent.tools.join(', ')}`);
24
+ }
25
+ console.log('');
26
+ });
27
+
28
+ console.log(`Total: ${agents.length} agent(s)`);
29
+ console.log('\n💡 To start chatting: npx @intranefr/superbackend agent-chat');
30
+
31
+ } catch (error) {
32
+ console.error('❌ Error:', error.message);
33
+ process.exit(1);
34
+ }
35
+ }
36
+
37
+ // Handle direct execution
38
+ if (require.main === module) {
39
+ listAgents();
40
+ }
41
+
42
+ module.exports = { listAgents };
@@ -0,0 +1,172 @@
1
+ const crypto = require('crypto');
2
+ const Agent = require('../models/Agent');
3
+ const JsonConfig = require('../models/JsonConfig');
4
+ const agentService = require('../services/agent.service');
5
+
6
+ function safeParse(raw, fallback = null) {
7
+ try {
8
+ return JSON.parse(raw);
9
+ } catch (_) {
10
+ return fallback;
11
+ }
12
+ }
13
+
14
+ exports.newSession = async (req, res) => {
15
+ try {
16
+ const { agentId } = req.body || {};
17
+ if (!agentId) return res.status(400).json({ error: 'agentId is required' });
18
+
19
+ const agent = await Agent.findById(agentId).select('_id').lean();
20
+ if (!agent) return res.status(404).json({ error: 'Agent not found' });
21
+
22
+ return res.json({ chatId: `web-${Date.now()}-${crypto.randomUUID().slice(0, 8)}` });
23
+ } catch (error) {
24
+ return res.status(500).json({ error: error.message });
25
+ }
26
+ };
27
+
28
+ exports.listSessions = async (req, res) => {
29
+ try {
30
+ const { agentId } = req.query;
31
+ const docs = await JsonConfig.find({ alias: { $regex: /^agent-session-/ } })
32
+ .sort({ updatedAt: -1 })
33
+ .limit(30)
34
+ .lean();
35
+
36
+ const items = docs
37
+ .map((doc) => {
38
+ const parsed = safeParse(doc.jsonRaw, {});
39
+ return {
40
+ id: parsed.id,
41
+ agentId: parsed.agentId,
42
+ label: parsed.label || null,
43
+ totalTokens: parsed.totalTokens || 0,
44
+ lastSnapshotId: parsed.lastSnapshotId || null,
45
+ updatedAt: doc.updatedAt,
46
+ };
47
+ })
48
+ .filter((x) => x.id)
49
+ .filter((x) => !agentId || String(x.agentId) === String(agentId));
50
+
51
+ return res.json({ items });
52
+ } catch (error) {
53
+ return res.status(500).json({ error: error.message });
54
+ }
55
+ };
56
+
57
+ exports.renameSession = async (req, res) => {
58
+ try {
59
+ const { chatId, label } = req.body || {};
60
+ if (!chatId || !label) return res.status(400).json({ error: 'chatId and label are required' });
61
+ const result = await agentService.renameSession(chatId, label);
62
+ if (!result.success) return res.status(400).json({ error: result.message || 'Failed to rename session' });
63
+ return res.json(result);
64
+ } catch (error) {
65
+ return res.status(500).json({ error: error.message });
66
+ }
67
+ };
68
+
69
+ exports.compactSession = async (req, res) => {
70
+ try {
71
+ const { agentId, chatId } = req.body || {};
72
+ if (!agentId || !chatId) return res.status(400).json({ error: 'agentId and chatId are required' });
73
+ const result = await agentService.compactSession(agentId, chatId);
74
+ if (!result.success) return res.status(400).json({ error: result.message || 'Failed to compact session' });
75
+ return res.json(result);
76
+ } catch (error) {
77
+ return res.status(500).json({ error: error.message });
78
+ }
79
+ };
80
+
81
+ exports.sendMessage = async (req, res) => {
82
+ try {
83
+ const { agentId, chatId, content } = req.body || {};
84
+ if (!agentId || !content) return res.status(400).json({ error: 'agentId and content are required' });
85
+
86
+ const senderId = String(req.session?.adminUser?._id || req.session?.user?._id || 'admin-web');
87
+ const response = await agentService.processMessage(
88
+ agentId,
89
+ { content, senderId, chatId },
90
+ {},
91
+ );
92
+
93
+ return res.json(response);
94
+ } catch (error) {
95
+ return res.status(500).json({ error: error.message });
96
+ }
97
+ };
98
+
99
+ exports.streamMessage = async (req, res) => {
100
+ try {
101
+ const { agentId, chatId, content } = req.body || {};
102
+ if (!agentId || !content) return res.status(400).json({ error: 'agentId and content are required' });
103
+
104
+ const senderId = String(req.session?.adminUser?._id || req.session?.user?._id || 'admin-web');
105
+
106
+ res.setHeader('Content-Type', 'text/event-stream');
107
+ res.setHeader('Cache-Control', 'no-cache');
108
+ res.setHeader('Connection', 'keep-alive');
109
+ if (res.flushHeaders) res.flushHeaders();
110
+
111
+ const writeEvent = (event, payload) => {
112
+ res.write(`event: ${event}\n`);
113
+ res.write(`data: ${JSON.stringify(payload)}\n\n`);
114
+ };
115
+
116
+ writeEvent('status', { status: 'starting' });
117
+
118
+ const response = await agentService.processMessage(
119
+ agentId,
120
+ { content, senderId, chatId },
121
+ {
122
+ onProgress: (p) => writeEvent('progress', p),
123
+ },
124
+ );
125
+
126
+ writeEvent('done', response);
127
+ res.end();
128
+ } catch (error) {
129
+ res.write(`event: error\n`);
130
+ res.write(`data: ${JSON.stringify({ error: error.message })}\n\n`);
131
+ res.end();
132
+ }
133
+ };
134
+
135
+ exports.loadSessionMessages = async (req, res) => {
136
+ try {
137
+ const { chatId } = req.params;
138
+ console.log('[loadSessionMessages] Loading messages for chatId:', chatId);
139
+ if (!chatId) return res.status(400).json({ error: 'chatId is required' });
140
+
141
+ const doc = await JsonConfig.findOne({
142
+ 'jsonRaw.id': chatId,
143
+ alias: { $regex: /^agent-session-/ }
144
+ }).lean();
145
+
146
+ console.log('[loadSessionMessages] Found document:', !!doc);
147
+
148
+ if (!doc) return res.status(404).json({ error: 'Session not found' });
149
+
150
+ const parsed = safeParse(doc.jsonRaw, {});
151
+ const messages = parsed.messages || [];
152
+ console.log('[loadSessionMessages] Messages count:', messages.length);
153
+
154
+ return res.json({
155
+ chatId: parsed.id,
156
+ agentId: parsed.agentId,
157
+ label: parsed.label,
158
+ messages,
159
+ totalTokens: parsed.totalTokens || 0,
160
+ updatedAt: doc.updatedAt
161
+ });
162
+ } catch (error) {
163
+ console.error('[loadSessionMessages] Error:', error.message);
164
+ return res.status(500).json({ error: error.message });
165
+ }
166
+ };
167
+
168
+ exports.chatHealth = async (_req, res) => {
169
+ const uri = process.env.MONGODB_URI || '';
170
+ const dbName = uri.split('/').pop()?.split('?')[0] || 'unknown';
171
+ return res.json({ dbName });
172
+ };