@runflow-ai/cli 0.2.11 โ 0.2.12
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.
- package/CLI-DOCS.md +89 -0
- package/QUICK-TEST-GUIDE.md +273 -0
- package/dist/commands/test/test.command.d.ts +19 -12
- package/dist/commands/test/test.command.js +716 -212
- package/dist/commands/test/test.command.js.map +1 -1
- package/package.json +3 -2
- package/static/dist-test/assets/favico.avif +0 -0
- package/static/dist-test/assets/logo_runflow_positive.png +0 -0
- package/static/dist-test/assets/main-ClrC9fUE.css +1 -0
- package/static/dist-test/assets/main-rM2NnEnW.js +53 -0
- package/static/dist-test/assets/runflow-logo.png +0 -0
- package/static/dist-test/index-test.html +16 -0
- package/static/dist-test/widget/runflow-widget.js +221 -0
- package/static/app.js +0 -1381
- package/static/frontend-server-template.js +0 -24
- package/static/index.html +0 -340
- package/static/style.css +0 -1354
- package/static/test-server-template.js +0 -641
|
@@ -50,70 +50,74 @@ const nest_commander_1 = require("nest-commander");
|
|
|
50
50
|
const common_1 = require("@nestjs/common");
|
|
51
51
|
const fs = __importStar(require("fs"));
|
|
52
52
|
const path = __importStar(require("path"));
|
|
53
|
-
const child_process_1 = require("child_process");
|
|
54
53
|
const chalk_1 = __importDefault(require("chalk"));
|
|
55
|
-
const
|
|
56
|
-
const
|
|
54
|
+
const express_1 = __importDefault(require("express"));
|
|
55
|
+
const cors_1 = __importDefault(require("cors"));
|
|
56
|
+
const crypto_1 = require("crypto");
|
|
57
57
|
let TestCommand = class TestCommand extends nest_commander_1.CommandRunner {
|
|
58
58
|
constructor() {
|
|
59
59
|
super(...arguments);
|
|
60
60
|
this.server = null;
|
|
61
|
-
this.
|
|
61
|
+
this.app = null;
|
|
62
|
+
this.storageDir = '';
|
|
62
63
|
}
|
|
63
64
|
async run(passedParam, options) {
|
|
64
65
|
try {
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
const
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
console.log(chalk_1.default.cyan.bold('๐งช Runflow Test Server'));
|
|
72
|
-
console.log(chalk_1.default.gray('Starting local development environment...\n'));
|
|
73
|
-
const validation = (0, validator_1.validateForTest)();
|
|
74
|
-
if (!validation.isValid) {
|
|
75
|
-
console.error(chalk_1.default.red('\nโ Project validation failed!'));
|
|
76
|
-
validation.errors.forEach((error) => {
|
|
77
|
-
console.error(chalk_1.default.red(` โข ${error}`));
|
|
78
|
-
});
|
|
79
|
-
console.error(chalk_1.default.gray('\n๐ Documentation: https://docs.runflow.ai\n'));
|
|
66
|
+
console.log(chalk_1.default.cyan.bold('\n๐งช Runflow Test Server'));
|
|
67
|
+
console.log(chalk_1.default.gray('Starting local observability server...\n'));
|
|
68
|
+
const config = this.loadRunflowConfig();
|
|
69
|
+
if (!config || !config.agentId) {
|
|
70
|
+
console.error(chalk_1.default.red('โ No agentId found in .runflow/rf.json'));
|
|
71
|
+
console.error(chalk_1.default.gray(' Run "rf clone <agentId>" first\n'));
|
|
80
72
|
process.exit(1);
|
|
81
73
|
}
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
74
|
+
console.log(chalk_1.default.green('โ Found agent config'));
|
|
75
|
+
console.log(chalk_1.default.gray(` Agent: ${config.agentName || config.agentId}\n`));
|
|
76
|
+
this.storageDir = path.join(process.cwd(), '.runflow', 'traces');
|
|
77
|
+
this.ensureStorageDir();
|
|
78
|
+
const basePort = options?.port || this.getRandomPort();
|
|
79
|
+
let port = null;
|
|
80
|
+
let attempts = 0;
|
|
81
|
+
const maxAttempts = 3;
|
|
82
|
+
while (attempts < maxAttempts && !port) {
|
|
83
|
+
const tryPort = basePort + attempts;
|
|
84
|
+
try {
|
|
85
|
+
await this.startObservabilityServer(tryPort);
|
|
86
|
+
port = tryPort;
|
|
87
|
+
console.log(chalk_1.default.green(`โ Local server running on port ${port}\n`));
|
|
93
88
|
}
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
console.log(chalk_1.default.gray('Press Ctrl+C to stop'));
|
|
100
|
-
if (shouldOpen) {
|
|
101
|
-
this.openBrowser(`http://${host}:${port + 1000}`);
|
|
89
|
+
catch (error) {
|
|
90
|
+
attempts++;
|
|
91
|
+
if (attempts < maxAttempts) {
|
|
92
|
+
console.log(chalk_1.default.yellow(`โ ๏ธ Port ${tryPort} in use, trying ${tryPort + 1}...`));
|
|
93
|
+
}
|
|
102
94
|
}
|
|
103
|
-
this.setupCleanupHandlers();
|
|
104
|
-
await this.keepAlive();
|
|
105
95
|
}
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
}
|
|
110
|
-
console.error(chalk_1.default.red(`โ Error: ${error instanceof Error ? error.message : 'Unknown error'}`));
|
|
111
|
-
await this.cleanup();
|
|
96
|
+
if (!port) {
|
|
97
|
+
console.error(chalk_1.default.red(`โ Failed to start server after ${maxAttempts} attempts`));
|
|
98
|
+
console.error(chalk_1.default.gray(' All ports are busy. Try again later.\n'));
|
|
112
99
|
process.exit(1);
|
|
113
100
|
}
|
|
101
|
+
let portalUrl = `http://localhost:${port}/agents/${config.agentId}/test-monitor?apiUrl=http://localhost:${port}`;
|
|
102
|
+
if (config.agentName) {
|
|
103
|
+
portalUrl += `&agentName=${encodeURIComponent(config.agentName)}`;
|
|
104
|
+
}
|
|
105
|
+
if (options?.noBrowser !== true) {
|
|
106
|
+
console.log(chalk_1.default.green('โ Opening browser...\n'));
|
|
107
|
+
this.openBrowser(portalUrl);
|
|
108
|
+
}
|
|
109
|
+
console.log(chalk_1.default.cyan.bold('๐ Test Portal Ready!'));
|
|
110
|
+
console.log('');
|
|
111
|
+
console.log(chalk_1.default.cyan(` ๐ Portal: ${portalUrl}`));
|
|
112
|
+
console.log(chalk_1.default.gray(` ๐ก API: http://localhost:${port}`));
|
|
113
|
+
console.log(chalk_1.default.gray(` ๐ Execute: POST http://localhost:${port}/api/v1/agents/execute`));
|
|
114
|
+
console.log(chalk_1.default.gray(` ๐พ Storage: ${this.storageDir}`));
|
|
115
|
+
console.log('');
|
|
116
|
+
console.log(chalk_1.default.gray('Press Ctrl+C to stop\n'));
|
|
117
|
+
this.setupCleanupHandlers();
|
|
118
|
+
await this.keepAlive();
|
|
114
119
|
}
|
|
115
120
|
catch (error) {
|
|
116
|
-
console.log('deu erro aqui');
|
|
117
121
|
console.error(chalk_1.default.red(`โ Error: ${error instanceof Error ? error.message : 'Unknown error'}`));
|
|
118
122
|
await this.cleanup();
|
|
119
123
|
process.exit(1);
|
|
@@ -126,14 +130,7 @@ let TestCommand = class TestCommand extends nest_commander_1.CommandRunner {
|
|
|
126
130
|
}
|
|
127
131
|
return port;
|
|
128
132
|
}
|
|
129
|
-
|
|
130
|
-
return val;
|
|
131
|
-
}
|
|
132
|
-
parseOpen() {
|
|
133
|
-
return false;
|
|
134
|
-
}
|
|
135
|
-
parseVerbose(val) {
|
|
136
|
-
console.log('๐ [PARSER] parseVerbose called with:', val);
|
|
133
|
+
parseNoBrowser() {
|
|
137
134
|
return true;
|
|
138
135
|
}
|
|
139
136
|
loadRunflowConfig() {
|
|
@@ -148,197 +145,722 @@ let TestCommand = class TestCommand extends nest_commander_1.CommandRunner {
|
|
|
148
145
|
}
|
|
149
146
|
return null;
|
|
150
147
|
}
|
|
151
|
-
async
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
148
|
+
async executeAgent(message, threadId, sessionId, userId) {
|
|
149
|
+
try {
|
|
150
|
+
const possibleFiles = [
|
|
151
|
+
path.join(process.cwd(), 'agent.ts'),
|
|
152
|
+
path.join(process.cwd(), 'agent.js'),
|
|
153
|
+
path.join(process.cwd(), 'src', 'agent.ts'),
|
|
154
|
+
path.join(process.cwd(), 'src', 'agent.js'),
|
|
155
|
+
path.join(process.cwd(), 'main.ts'),
|
|
156
|
+
path.join(process.cwd(), 'main.js'),
|
|
157
|
+
];
|
|
158
|
+
let agentFile = null;
|
|
159
|
+
for (const file of possibleFiles) {
|
|
160
|
+
if (fs.existsSync(file)) {
|
|
161
|
+
agentFile = file;
|
|
162
|
+
break;
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
if (!agentFile) {
|
|
166
|
+
throw new Error('Agent file not found. Expected agent.ts, agent.js, main.ts, or main.js in project root or src/');
|
|
167
|
+
}
|
|
168
|
+
process.env.RUNFLOW_LOCAL_TRACES = 'true';
|
|
169
|
+
process.env.RUNFLOW_ENV = 'development';
|
|
170
|
+
process.env.RUNFLOW_VERBOSE_TRACING = 'true';
|
|
171
|
+
let agent;
|
|
172
|
+
if (agentFile.endsWith('.ts')) {
|
|
173
|
+
require('tsx/cjs');
|
|
174
|
+
const agentModule = require(agentFile);
|
|
175
|
+
agent = agentModule.default || agentModule.agent || agentModule;
|
|
176
|
+
if (typeof agent === 'function') {
|
|
177
|
+
agent = agent();
|
|
178
|
+
}
|
|
179
|
+
if (!agent || typeof agent.process !== 'function') {
|
|
180
|
+
const exportedKeys = Object.keys(agentModule);
|
|
181
|
+
for (const key of exportedKeys) {
|
|
182
|
+
const exported = agentModule[key];
|
|
183
|
+
if (typeof exported === 'function') {
|
|
184
|
+
const result = exported();
|
|
185
|
+
if (result && typeof result.process === 'function') {
|
|
186
|
+
agent = result;
|
|
187
|
+
break;
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
}
|
|
166
192
|
}
|
|
167
193
|
else {
|
|
168
|
-
|
|
194
|
+
const agentModule = require(agentFile);
|
|
195
|
+
agent = agentModule.default || agentModule.agent || agentModule;
|
|
196
|
+
if (typeof agent === 'function') {
|
|
197
|
+
agent = agent();
|
|
198
|
+
}
|
|
199
|
+
if (!agent || typeof agent.process !== 'function') {
|
|
200
|
+
const exportedKeys = Object.keys(agentModule);
|
|
201
|
+
for (const key of exportedKeys) {
|
|
202
|
+
const exported = agentModule[key];
|
|
203
|
+
if (typeof exported === 'function') {
|
|
204
|
+
const result = exported();
|
|
205
|
+
if (result && typeof result.process === 'function') {
|
|
206
|
+
agent = result;
|
|
207
|
+
break;
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
}
|
|
169
212
|
}
|
|
170
|
-
|
|
213
|
+
if (!agent || typeof agent.process !== 'function') {
|
|
214
|
+
throw new Error('Agent must export an agent instance with a process() method');
|
|
215
|
+
}
|
|
216
|
+
const config = this.loadRunflowConfig();
|
|
217
|
+
const input = {
|
|
218
|
+
message,
|
|
219
|
+
sessionId,
|
|
220
|
+
userId,
|
|
221
|
+
channel: 'local-test',
|
|
222
|
+
threadId,
|
|
223
|
+
metadata: {
|
|
224
|
+
timestamp: new Date().toISOString(),
|
|
225
|
+
source: 'local-cli',
|
|
226
|
+
agentId: config?.agentId,
|
|
227
|
+
}
|
|
228
|
+
};
|
|
229
|
+
const result = await agent.process(input);
|
|
230
|
+
await new Promise((resolve) => setTimeout(resolve, 3000));
|
|
231
|
+
const tracesFile = path.join(process.cwd(), '.runflow', 'traces.json');
|
|
232
|
+
if (fs.existsSync(tracesFile)) {
|
|
233
|
+
try {
|
|
234
|
+
const content = fs.readFileSync(tracesFile, 'utf-8');
|
|
235
|
+
const data = JSON.parse(content);
|
|
236
|
+
const execId = result.executionId || `exec-${Date.now()}`;
|
|
237
|
+
const execution = data.executions?.[execId];
|
|
238
|
+
if (execution) {
|
|
239
|
+
console.log(chalk_1.default.gray(` Traces saved: ${execution.traces?.length || 0}`));
|
|
240
|
+
const traceTypes = execution.traces?.map((t) => t.type) || [];
|
|
241
|
+
console.log(chalk_1.default.gray(` Types: ${[...new Set(traceTypes)].join(', ')}`));
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
catch (error) {
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
return {
|
|
248
|
+
executionId: result.executionId || `exec-${Date.now()}`,
|
|
249
|
+
message: result.output || result.message || result,
|
|
250
|
+
metadata: result.metadata || {},
|
|
251
|
+
};
|
|
252
|
+
}
|
|
253
|
+
catch (error) {
|
|
254
|
+
throw new Error(`Failed to execute agent: ${error.message}`);
|
|
171
255
|
}
|
|
172
256
|
}
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
257
|
+
getRandomPort() {
|
|
258
|
+
return Math.floor(Math.random() * 1000) + 3000;
|
|
259
|
+
}
|
|
260
|
+
generateSessionId(req, bodySessionId) {
|
|
261
|
+
if (bodySessionId) {
|
|
262
|
+
return bodySessionId;
|
|
176
263
|
}
|
|
177
|
-
|
|
178
|
-
|
|
264
|
+
const headerSessionId = req.headers['x-session-id'];
|
|
265
|
+
if (headerSessionId) {
|
|
266
|
+
return headerSessionId;
|
|
179
267
|
}
|
|
180
|
-
const
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
268
|
+
const cookieHeader = req.headers['cookie'];
|
|
269
|
+
if (cookieHeader) {
|
|
270
|
+
const cookies = cookieHeader.split(';').reduce((acc, cookie) => {
|
|
271
|
+
const [key, value] = cookie.trim().split('=');
|
|
272
|
+
acc[key] = value;
|
|
273
|
+
return acc;
|
|
274
|
+
}, {});
|
|
275
|
+
if (cookies['runflow_session']) {
|
|
276
|
+
return cookies['runflow_session'];
|
|
277
|
+
}
|
|
184
278
|
}
|
|
185
|
-
const
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
},
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
279
|
+
const fingerprint = this.createFingerprint(req);
|
|
280
|
+
return `auto_${fingerprint}_${Date.now()}`;
|
|
281
|
+
}
|
|
282
|
+
generateUserId(req, sessionId) {
|
|
283
|
+
const cookieHeader = req.headers['cookie'];
|
|
284
|
+
if (cookieHeader) {
|
|
285
|
+
const cookies = cookieHeader.split(';').reduce((acc, cookie) => {
|
|
286
|
+
const [key, value] = cookie.trim().split('=');
|
|
287
|
+
acc[key] = value;
|
|
288
|
+
return acc;
|
|
289
|
+
}, {});
|
|
290
|
+
if (cookies['runflow_user']) {
|
|
291
|
+
return cookies['runflow_user'];
|
|
292
|
+
}
|
|
199
293
|
}
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
294
|
+
return sessionId;
|
|
295
|
+
}
|
|
296
|
+
createFingerprint(req) {
|
|
297
|
+
const components = [
|
|
298
|
+
req.headers['user-agent'] || '',
|
|
299
|
+
req.headers['accept-language'] || '',
|
|
300
|
+
req.ip || req.socket.remoteAddress || '',
|
|
301
|
+
].join('|');
|
|
302
|
+
return (0, crypto_1.createHash)('md5').update(components).digest('hex').substring(0, 12);
|
|
303
|
+
}
|
|
304
|
+
ensureStorageDir() {
|
|
305
|
+
if (!fs.existsSync(this.storageDir)) {
|
|
306
|
+
fs.mkdirSync(this.storageDir, { recursive: true });
|
|
307
|
+
}
|
|
308
|
+
}
|
|
309
|
+
async startObservabilityServer(port) {
|
|
310
|
+
return new Promise((resolve, reject) => {
|
|
311
|
+
this.app = (0, express_1.default)();
|
|
312
|
+
this.app.use((0, cors_1.default)({
|
|
313
|
+
origin: true,
|
|
314
|
+
credentials: true,
|
|
315
|
+
methods: ['GET', 'POST', 'PUT', 'DELETE', 'OPTIONS', 'PATCH'],
|
|
316
|
+
allowedHeaders: ['Content-Type', 'X-Thread-Id', 'X-Session-Id', 'Authorization', 'Cookie'],
|
|
317
|
+
exposedHeaders: ['Content-Length', 'X-Request-Id', 'X-Session-ID', 'X-User-ID', 'X-Timestamp'],
|
|
318
|
+
maxAge: 86400,
|
|
319
|
+
}));
|
|
320
|
+
this.app.use(express_1.default.json());
|
|
321
|
+
const distPath = path.join(__dirname, '..', '..', '..', 'static', 'dist-test');
|
|
322
|
+
this.app.use('/assets', express_1.default.static(path.join(distPath, 'assets')));
|
|
323
|
+
this.app.use('/widget', express_1.default.static(path.join(distPath, 'widget')));
|
|
324
|
+
this.app.get('/health', (req, res) => {
|
|
325
|
+
res.status(200).json({ status: 'ok', mode: 'local', version: '1.0.0' });
|
|
326
|
+
});
|
|
327
|
+
this.setupObservabilityEndpoints();
|
|
328
|
+
this.app.get('*', (req, res) => {
|
|
329
|
+
const indexPath = path.join(distPath, 'index-test.html');
|
|
330
|
+
res.sendFile(indexPath);
|
|
331
|
+
});
|
|
332
|
+
this.server = this.app.listen(port, () => {
|
|
333
|
+
resolve();
|
|
334
|
+
});
|
|
335
|
+
this.server.on('error', (error) => {
|
|
336
|
+
if (error.code === 'EADDRINUSE') {
|
|
337
|
+
reject(new Error(`Port ${port} is already in use`));
|
|
338
|
+
}
|
|
339
|
+
else {
|
|
340
|
+
reject(error);
|
|
341
|
+
}
|
|
342
|
+
});
|
|
343
|
+
});
|
|
344
|
+
}
|
|
345
|
+
setupObservabilityEndpoints() {
|
|
346
|
+
if (!this.app)
|
|
347
|
+
return;
|
|
348
|
+
this.app.post('/api/v1/agents/execute', async (req, res) => {
|
|
349
|
+
try {
|
|
350
|
+
const { message, threadId, sessionId: bodySessionId } = req.body;
|
|
351
|
+
if (!message) {
|
|
352
|
+
return res.status(400).json({
|
|
353
|
+
success: false,
|
|
354
|
+
error: 'message is required'
|
|
355
|
+
});
|
|
356
|
+
}
|
|
357
|
+
const normalizedMessage = message.toLowerCase().trim();
|
|
358
|
+
if (normalizedMessage === 'ping' || normalizedMessage === 'pong') {
|
|
359
|
+
return res.json({
|
|
360
|
+
success: true,
|
|
361
|
+
executionId: `ping-${Date.now()}`,
|
|
362
|
+
message: 'pong',
|
|
363
|
+
sessionId: 'ping-session',
|
|
364
|
+
userId: 'ping-user',
|
|
365
|
+
metadata: { isPing: true },
|
|
366
|
+
});
|
|
367
|
+
}
|
|
368
|
+
const sessionId = this.generateSessionId(req, bodySessionId);
|
|
369
|
+
const userId = this.generateUserId(req, sessionId);
|
|
370
|
+
console.log(chalk_1.default.gray(`๐จ [${new Date().toISOString()}] Executing agent...`));
|
|
371
|
+
console.log(chalk_1.default.gray(` Message: ${message.substring(0, 50)}${message.length > 50 ? '...' : ''}`));
|
|
372
|
+
if (threadId) {
|
|
373
|
+
console.log(chalk_1.default.gray(` Thread: ${threadId}`));
|
|
374
|
+
}
|
|
375
|
+
console.log(chalk_1.default.gray(` Session: ${sessionId}`));
|
|
376
|
+
const result = await this.executeAgent(message, threadId || sessionId, sessionId, userId);
|
|
377
|
+
console.log(chalk_1.default.green(`โ [${new Date().toISOString()}] Agent executed successfully`));
|
|
378
|
+
console.log(chalk_1.default.gray(` Execution ID: ${result.executionId}`));
|
|
379
|
+
console.log('');
|
|
380
|
+
res.setHeader('X-Session-ID', sessionId);
|
|
381
|
+
res.setHeader('X-User-ID', userId);
|
|
382
|
+
res.setHeader('X-Timestamp', new Date().toISOString());
|
|
383
|
+
res.setHeader('Set-Cookie', [
|
|
384
|
+
`runflow_session=${sessionId}; Path=/; HttpOnly; SameSite=Lax`,
|
|
385
|
+
`runflow_user=${userId}; Path=/; HttpOnly; SameSite=Lax`,
|
|
386
|
+
]);
|
|
387
|
+
res.json({
|
|
388
|
+
success: true,
|
|
389
|
+
executionId: result.executionId,
|
|
390
|
+
message: result.message,
|
|
391
|
+
sessionId: sessionId,
|
|
392
|
+
userId: userId,
|
|
393
|
+
metadata: result.metadata,
|
|
394
|
+
});
|
|
395
|
+
}
|
|
396
|
+
catch (error) {
|
|
397
|
+
console.error(chalk_1.default.red(`โ [${new Date().toISOString()}] Agent execution failed:`));
|
|
398
|
+
console.error(chalk_1.default.red(` ${error.message}`));
|
|
399
|
+
console.log('');
|
|
400
|
+
res.status(500).json({
|
|
401
|
+
success: false,
|
|
402
|
+
error: error.message
|
|
403
|
+
});
|
|
204
404
|
}
|
|
205
405
|
});
|
|
206
|
-
this.
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
406
|
+
this.app.get('/api/v1/observability/threads', async (req, res) => {
|
|
407
|
+
try {
|
|
408
|
+
const { agentId, threadId, limit = 50 } = req.query;
|
|
409
|
+
if (!agentId) {
|
|
410
|
+
return res.status(400).json({ error: 'agentId is required' });
|
|
411
|
+
}
|
|
412
|
+
const threads = await this.getThreads(agentId, threadId, parseInt(limit));
|
|
413
|
+
res.json({ threads, total: threads.length });
|
|
210
414
|
}
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
415
|
+
catch (error) {
|
|
416
|
+
console.error(chalk_1.default.red(`โ Error fetching threads: ${error.message}`));
|
|
417
|
+
res.status(500).json({ error: error.message });
|
|
418
|
+
}
|
|
419
|
+
});
|
|
420
|
+
this.app.get('/api/v1/observability/threads/:threadId/executions', async (req, res) => {
|
|
421
|
+
try {
|
|
422
|
+
const { threadId } = req.params;
|
|
423
|
+
const { agentId } = req.query;
|
|
424
|
+
if (!agentId) {
|
|
425
|
+
return res.status(400).json({ error: 'agentId is required' });
|
|
215
426
|
}
|
|
427
|
+
const result = await this.getThreadExecutions(agentId, threadId);
|
|
428
|
+
res.json(result);
|
|
429
|
+
}
|
|
430
|
+
catch (error) {
|
|
431
|
+
console.error(chalk_1.default.red(`โ Error fetching executions: ${error.message}`));
|
|
432
|
+
res.status(500).json({ error: error.message });
|
|
216
433
|
}
|
|
217
434
|
});
|
|
218
|
-
this.
|
|
219
|
-
|
|
435
|
+
this.app.get('/api/v1/observability/executions/:executionId', async (req, res) => {
|
|
436
|
+
try {
|
|
437
|
+
const { executionId } = req.params;
|
|
438
|
+
const { agentId } = req.query;
|
|
439
|
+
if (!agentId) {
|
|
440
|
+
return res.status(400).json({ error: 'agentId is required' });
|
|
441
|
+
}
|
|
442
|
+
const result = await this.getExecutionTraces(agentId, executionId);
|
|
443
|
+
if (!result) {
|
|
444
|
+
return res.status(404).json({ error: 'Execution not found' });
|
|
445
|
+
}
|
|
446
|
+
res.json(result);
|
|
447
|
+
}
|
|
448
|
+
catch (error) {
|
|
449
|
+
console.error(chalk_1.default.red(`โ Error fetching execution: ${error.message}`));
|
|
450
|
+
res.status(500).json({ error: error.message });
|
|
451
|
+
}
|
|
220
452
|
});
|
|
221
|
-
await this.waitForServer(port, 10000);
|
|
222
453
|
}
|
|
223
|
-
async
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
454
|
+
async getThreads(agentId, threadId, limit = 50) {
|
|
455
|
+
const threads = [];
|
|
456
|
+
const files = this.getAllTraceFiles();
|
|
457
|
+
for (const filePath of files) {
|
|
458
|
+
try {
|
|
459
|
+
const content = fs.readFileSync(filePath, 'utf-8');
|
|
460
|
+
const data = JSON.parse(content);
|
|
461
|
+
if (data.agentId !== agentId)
|
|
462
|
+
continue;
|
|
463
|
+
for (const [tid, threadData] of Object.entries(data.threads || {})) {
|
|
464
|
+
if (threadId && tid !== threadId)
|
|
465
|
+
continue;
|
|
466
|
+
const td = threadData;
|
|
467
|
+
const executions = td.executions || [];
|
|
468
|
+
const lastExecution = executions[executions.length - 1];
|
|
469
|
+
threads.push({
|
|
470
|
+
id: `thread-${tid}`,
|
|
471
|
+
executionId: lastExecution?.executionId,
|
|
472
|
+
threadId: tid,
|
|
473
|
+
tenantId: 'local',
|
|
474
|
+
agentId: data.agentId,
|
|
475
|
+
entityType: td.entityType || 'email',
|
|
476
|
+
entityValue: td.entityValue || tid,
|
|
477
|
+
userId: td.userId || null,
|
|
478
|
+
sessionId: data.sessionId,
|
|
479
|
+
channel: td.channel || 'sdk',
|
|
480
|
+
source: td.source || 'local',
|
|
481
|
+
inputMessage: lastExecution?.inputMessage,
|
|
482
|
+
outputMessage: lastExecution?.outputMessage,
|
|
483
|
+
totalTraces: executions.reduce((sum, e) => sum + (e.traces?.length || 0), 0),
|
|
484
|
+
totalDurationMs: executions.reduce((sum, e) => sum + (e.totalDurationMs || 0), 0),
|
|
485
|
+
totalTokens: executions.reduce((sum, e) => sum + (e.totalTokens || 0), 0),
|
|
486
|
+
totalCostUsd: executions.reduce((sum, e) => sum + (e.totalCostUsd || 0), 0),
|
|
487
|
+
status: lastExecution?.status || 'pending',
|
|
488
|
+
error: lastExecution?.error || null,
|
|
489
|
+
startedAt: executions[0]?.startedAt,
|
|
490
|
+
completedAt: lastExecution?.completedAt,
|
|
491
|
+
createdAt: executions[0]?.startedAt,
|
|
492
|
+
});
|
|
493
|
+
}
|
|
494
|
+
}
|
|
495
|
+
catch (error) {
|
|
496
|
+
}
|
|
229
497
|
}
|
|
230
|
-
const
|
|
231
|
-
const
|
|
232
|
-
|
|
233
|
-
|
|
498
|
+
const sdkThreads = this.getSDKTraces(agentId);
|
|
499
|
+
const filteredSDKThreads = threadId
|
|
500
|
+
? sdkThreads.filter(t => t.threadId === threadId)
|
|
501
|
+
: sdkThreads;
|
|
502
|
+
threads.push(...filteredSDKThreads);
|
|
503
|
+
return threads
|
|
504
|
+
.sort((a, b) => new Date(b.startedAt).getTime() - new Date(a.startedAt).getTime())
|
|
505
|
+
.slice(0, limit);
|
|
506
|
+
}
|
|
507
|
+
async getThreadExecutions(agentId, threadId) {
|
|
508
|
+
const executions = [];
|
|
509
|
+
const files = this.getAllTraceFiles();
|
|
510
|
+
for (const filePath of files) {
|
|
511
|
+
try {
|
|
512
|
+
const content = fs.readFileSync(filePath, 'utf-8');
|
|
513
|
+
const data = JSON.parse(content);
|
|
514
|
+
if (data.agentId !== agentId)
|
|
515
|
+
continue;
|
|
516
|
+
const threadData = data.threads?.[threadId];
|
|
517
|
+
if (threadData?.executions) {
|
|
518
|
+
const formattedExecutions = threadData.executions.map((e) => ({
|
|
519
|
+
id: e.executionId,
|
|
520
|
+
executionId: e.executionId,
|
|
521
|
+
threadId: threadId,
|
|
522
|
+
tenantId: 'local',
|
|
523
|
+
agentId: data.agentId,
|
|
524
|
+
entityType: threadData.entityType || 'email',
|
|
525
|
+
entityValue: threadData.entityValue || threadId,
|
|
526
|
+
userId: threadData.userId || null,
|
|
527
|
+
sessionId: data.sessionId,
|
|
528
|
+
channel: threadData.channel || 'sdk',
|
|
529
|
+
source: threadData.source || 'local',
|
|
530
|
+
inputMessage: e.inputMessage,
|
|
531
|
+
outputMessage: e.outputMessage,
|
|
532
|
+
totalTraces: e.traces?.length || 0,
|
|
533
|
+
totalDurationMs: e.totalDurationMs || 0,
|
|
534
|
+
totalTokens: e.totalTokens || 0,
|
|
535
|
+
totalCostUsd: e.totalCostUsd || 0,
|
|
536
|
+
status: e.status || 'pending',
|
|
537
|
+
error: e.error || null,
|
|
538
|
+
startedAt: e.startedAt,
|
|
539
|
+
completedAt: e.completedAt,
|
|
540
|
+
createdAt: e.startedAt,
|
|
541
|
+
}));
|
|
542
|
+
executions.push(...formattedExecutions);
|
|
543
|
+
}
|
|
544
|
+
}
|
|
545
|
+
catch (error) {
|
|
546
|
+
}
|
|
234
547
|
}
|
|
235
|
-
const
|
|
236
|
-
const
|
|
237
|
-
if (
|
|
238
|
-
|
|
548
|
+
const sdkThreads = this.getSDKTraces(agentId);
|
|
549
|
+
const sdkThread = sdkThreads.find(t => t.threadId === threadId);
|
|
550
|
+
if (sdkThread && sdkThread._sdkExecutions) {
|
|
551
|
+
const formattedExecutions = sdkThread._sdkExecutions.map((e) => ({
|
|
552
|
+
id: e.executionId,
|
|
553
|
+
executionId: e.executionId,
|
|
554
|
+
threadId: threadId,
|
|
555
|
+
tenantId: 'local',
|
|
556
|
+
agentId: agentId,
|
|
557
|
+
entityType: sdkThread.entityType,
|
|
558
|
+
entityValue: sdkThread.entityValue,
|
|
559
|
+
userId: sdkThread.userId,
|
|
560
|
+
sessionId: e.executionId,
|
|
561
|
+
channel: 'sdk',
|
|
562
|
+
source: 'local',
|
|
563
|
+
inputMessage: e.inputMessage,
|
|
564
|
+
outputMessage: e.outputMessage,
|
|
565
|
+
totalTraces: e.traces?.length || 0,
|
|
566
|
+
totalDurationMs: e.totalDurationMs,
|
|
567
|
+
totalTokens: e.totalTokens,
|
|
568
|
+
totalCostUsd: e.totalCostUsd,
|
|
569
|
+
status: e.status,
|
|
570
|
+
error: e.error || null,
|
|
571
|
+
startedAt: e.startedAt,
|
|
572
|
+
completedAt: e.completedAt,
|
|
573
|
+
createdAt: e.startedAt,
|
|
574
|
+
}));
|
|
575
|
+
executions.push(...formattedExecutions);
|
|
239
576
|
}
|
|
240
|
-
const
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
577
|
+
const sorted = executions.sort((a, b) => new Date(b.startedAt).getTime() - new Date(a.startedAt).getTime());
|
|
578
|
+
return {
|
|
579
|
+
threadId,
|
|
580
|
+
executions: sorted,
|
|
581
|
+
total: sorted.length,
|
|
582
|
+
};
|
|
583
|
+
}
|
|
584
|
+
async getExecutionTraces(agentId, executionId) {
|
|
585
|
+
const files = this.getAllTraceFiles();
|
|
586
|
+
for (const filePath of files) {
|
|
587
|
+
try {
|
|
588
|
+
const content = fs.readFileSync(filePath, 'utf-8');
|
|
589
|
+
const data = JSON.parse(content);
|
|
590
|
+
if (data.agentId !== agentId)
|
|
591
|
+
continue;
|
|
592
|
+
for (const [tid, threadData] of Object.entries(data.threads || {})) {
|
|
593
|
+
const td = threadData;
|
|
594
|
+
const execution = td.executions?.find((e) => e.executionId === executionId);
|
|
595
|
+
if (execution) {
|
|
596
|
+
const formattedExecution = {
|
|
597
|
+
id: execution.executionId,
|
|
598
|
+
executionId: execution.executionId,
|
|
599
|
+
threadId: tid,
|
|
600
|
+
tenantId: 'local',
|
|
601
|
+
agentId: data.agentId,
|
|
602
|
+
entityType: td.entityType || 'email',
|
|
603
|
+
entityValue: td.entityValue || tid,
|
|
604
|
+
userId: td.userId || null,
|
|
605
|
+
sessionId: data.sessionId,
|
|
606
|
+
channel: td.channel || 'sdk',
|
|
607
|
+
source: td.source || 'local',
|
|
608
|
+
inputMessage: execution.inputMessage,
|
|
609
|
+
outputMessage: execution.outputMessage,
|
|
610
|
+
totalTraces: execution.traces?.length || 0,
|
|
611
|
+
totalDurationMs: execution.totalDurationMs || 0,
|
|
612
|
+
totalTokens: execution.totalTokens || 0,
|
|
613
|
+
totalCostUsd: execution.totalCostUsd || 0,
|
|
614
|
+
status: execution.status || 'pending',
|
|
615
|
+
error: execution.error || null,
|
|
616
|
+
startedAt: execution.startedAt,
|
|
617
|
+
completedAt: execution.completedAt,
|
|
618
|
+
createdAt: execution.startedAt,
|
|
619
|
+
};
|
|
620
|
+
const traces = execution.traces || [];
|
|
621
|
+
const formattedTraces = traces.map((t) => ({
|
|
622
|
+
id: t.id || t.traceId,
|
|
623
|
+
traceId: t.id || t.traceId,
|
|
624
|
+
parentTraceId: t.parentId || null,
|
|
625
|
+
executionId: execution.executionId,
|
|
626
|
+
tenantId: 'local',
|
|
627
|
+
agentId: data.agentId,
|
|
628
|
+
threadId: tid,
|
|
629
|
+
userId: td.userId || null,
|
|
630
|
+
sessionId: data.sessionId,
|
|
631
|
+
type: t.type,
|
|
632
|
+
operation: t.name || t.type,
|
|
633
|
+
status: t.status,
|
|
634
|
+
error: t.error || null,
|
|
635
|
+
input: t.input || null,
|
|
636
|
+
output: t.output || null,
|
|
637
|
+
metadata: t.metadata || {},
|
|
638
|
+
startedAt: t.startedAt,
|
|
639
|
+
completedAt: t.completedAt,
|
|
640
|
+
durationMs: t.durationMs,
|
|
641
|
+
tokensTotal: t.metadata?.totalTokens || t.metadata?.tokens || null,
|
|
642
|
+
costUsd: t.metadata?.cost || null,
|
|
643
|
+
createdAt: t.startedAt,
|
|
644
|
+
children: t.children || [],
|
|
645
|
+
}));
|
|
646
|
+
return {
|
|
647
|
+
execution: formattedExecution,
|
|
648
|
+
traces: formattedTraces,
|
|
649
|
+
};
|
|
650
|
+
}
|
|
651
|
+
}
|
|
652
|
+
}
|
|
653
|
+
catch (error) {
|
|
246
654
|
}
|
|
247
655
|
}
|
|
248
|
-
const
|
|
249
|
-
|
|
250
|
-
|
|
656
|
+
const sdkThreads = this.getSDKTraces(agentId);
|
|
657
|
+
for (const thread of sdkThreads) {
|
|
658
|
+
if (thread._sdkExecutions) {
|
|
659
|
+
const execution = thread._sdkExecutions.find((e) => e.executionId === executionId);
|
|
660
|
+
if (execution) {
|
|
661
|
+
const formattedExecution = {
|
|
662
|
+
id: execution.executionId,
|
|
663
|
+
executionId: execution.executionId,
|
|
664
|
+
threadId: thread.threadId,
|
|
665
|
+
tenantId: 'local',
|
|
666
|
+
agentId: agentId,
|
|
667
|
+
entityType: thread.entityType,
|
|
668
|
+
entityValue: thread.entityValue,
|
|
669
|
+
userId: thread.userId,
|
|
670
|
+
sessionId: execution.executionId,
|
|
671
|
+
channel: 'sdk',
|
|
672
|
+
source: 'local',
|
|
673
|
+
inputMessage: execution.inputMessage,
|
|
674
|
+
outputMessage: execution.outputMessage,
|
|
675
|
+
totalTraces: this.countAllTraces(execution.traces),
|
|
676
|
+
totalDurationMs: execution.totalDurationMs || 0,
|
|
677
|
+
totalTokens: execution.totalTokens || 0,
|
|
678
|
+
totalCostUsd: execution.totalCostUsd || 0,
|
|
679
|
+
status: execution.status || 'success',
|
|
680
|
+
error: execution.error || null,
|
|
681
|
+
startedAt: execution.startedAt,
|
|
682
|
+
completedAt: execution.completedAt,
|
|
683
|
+
createdAt: execution.startedAt,
|
|
684
|
+
};
|
|
685
|
+
const formattedTraces = execution.traces || [];
|
|
686
|
+
return {
|
|
687
|
+
execution: formattedExecution,
|
|
688
|
+
traces: formattedTraces,
|
|
689
|
+
};
|
|
690
|
+
}
|
|
691
|
+
}
|
|
251
692
|
}
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
},
|
|
260
|
-
});
|
|
261
|
-
this.frontend.stdout?.on('data', (data) => {
|
|
262
|
-
if (verbose) {
|
|
263
|
-
process.stdout.write(data.toString());
|
|
693
|
+
return null;
|
|
694
|
+
}
|
|
695
|
+
countAllTraces(traces) {
|
|
696
|
+
let count = traces.length;
|
|
697
|
+
traces.forEach((trace) => {
|
|
698
|
+
if (trace.children && trace.children.length > 0) {
|
|
699
|
+
count += this.countAllTraces(trace.children);
|
|
264
700
|
}
|
|
265
701
|
});
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
702
|
+
return count;
|
|
703
|
+
}
|
|
704
|
+
getAllTraceFiles() {
|
|
705
|
+
const files = [];
|
|
706
|
+
if (!fs.existsSync(this.storageDir)) {
|
|
707
|
+
return files;
|
|
708
|
+
}
|
|
709
|
+
const dateDirs = fs.readdirSync(this.storageDir);
|
|
710
|
+
for (const dateDir of dateDirs) {
|
|
711
|
+
const datePath = path.join(this.storageDir, dateDir);
|
|
712
|
+
if (!fs.statSync(datePath).isDirectory())
|
|
713
|
+
continue;
|
|
714
|
+
const traceFiles = fs.readdirSync(datePath).filter((f) => f.endsWith('.json'));
|
|
715
|
+
for (const file of traceFiles) {
|
|
716
|
+
files.push(path.join(datePath, file));
|
|
269
717
|
}
|
|
270
|
-
}
|
|
271
|
-
|
|
272
|
-
console.error(chalk_1.default.red(`Failed to start frontend: ${error.message}`));
|
|
273
|
-
});
|
|
274
|
-
await this.waitForServer(frontendPort, 5000);
|
|
718
|
+
}
|
|
719
|
+
return files;
|
|
275
720
|
}
|
|
276
|
-
|
|
277
|
-
const
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
721
|
+
getSDKTraces(agentId) {
|
|
722
|
+
const sdkTracesFile = path.join(process.cwd(), '.runflow', 'traces.json');
|
|
723
|
+
if (!fs.existsSync(sdkTracesFile)) {
|
|
724
|
+
return [];
|
|
725
|
+
}
|
|
726
|
+
try {
|
|
727
|
+
const content = fs.readFileSync(sdkTracesFile, 'utf-8');
|
|
728
|
+
const data = JSON.parse(content);
|
|
729
|
+
const threads = [];
|
|
730
|
+
const executionsMap = data.executions || {};
|
|
731
|
+
const threadGroups = {};
|
|
732
|
+
for (const [execId, execution] of Object.entries(executionsMap)) {
|
|
733
|
+
const exec = execution;
|
|
734
|
+
const threadId = exec.threadId || 'unknown';
|
|
735
|
+
if (!threadGroups[threadId]) {
|
|
736
|
+
threadGroups[threadId] = {
|
|
737
|
+
threadId: threadId,
|
|
738
|
+
entityType: exec.entityType || 'session',
|
|
739
|
+
entityValue: exec.entityValue || null,
|
|
740
|
+
userId: exec.userId || null,
|
|
741
|
+
channel: 'sdk',
|
|
742
|
+
source: 'local',
|
|
743
|
+
executions: [],
|
|
744
|
+
};
|
|
283
745
|
}
|
|
746
|
+
const traces = exec.traces || [];
|
|
747
|
+
const agentTrace = traces.find((t) => t.type === 'agent_execution' &&
|
|
748
|
+
!t.parentTraceId &&
|
|
749
|
+
t.operation === 'agent_execution') || traces.find((t) => t.type === 'agent_execution' &&
|
|
750
|
+
!t.parentTraceId) || traces.find((t) => t.type === 'agent_execution');
|
|
751
|
+
const tracesWithChildren = this.buildTraceHierarchy(traces);
|
|
752
|
+
const executionData = {
|
|
753
|
+
executionId: exec.executionId,
|
|
754
|
+
inputMessage: agentTrace?.input?.message || 'N/A',
|
|
755
|
+
outputMessage: agentTrace?.output?.message || 'N/A',
|
|
756
|
+
status: exec.status || 'success',
|
|
757
|
+
totalDurationMs: traces.reduce((sum, t) => sum + (t.duration || 0), 0),
|
|
758
|
+
totalTokens: traces.reduce((sum, t) => sum + (t.metadata?.totalTokens || 0), 0),
|
|
759
|
+
totalCostUsd: 0,
|
|
760
|
+
startedAt: exec.startedAt || agentTrace?.startTime,
|
|
761
|
+
completedAt: agentTrace?.endTime || exec.completedAt,
|
|
762
|
+
traces: tracesWithChildren,
|
|
763
|
+
};
|
|
764
|
+
threadGroups[threadId].executions.push(executionData);
|
|
284
765
|
}
|
|
285
|
-
|
|
766
|
+
for (const [threadId, threadData] of Object.entries(threadGroups)) {
|
|
767
|
+
const td = threadData;
|
|
768
|
+
const lastExecution = td.executions[td.executions.length - 1];
|
|
769
|
+
threads.push({
|
|
770
|
+
id: `thread-${threadId}`,
|
|
771
|
+
executionId: lastExecution?.executionId,
|
|
772
|
+
threadId: threadId,
|
|
773
|
+
tenantId: 'local',
|
|
774
|
+
agentId: agentId,
|
|
775
|
+
entityType: td.entityType,
|
|
776
|
+
entityValue: td.entityValue,
|
|
777
|
+
userId: td.userId,
|
|
778
|
+
sessionId: lastExecution?.executionId,
|
|
779
|
+
channel: td.channel,
|
|
780
|
+
source: td.source,
|
|
781
|
+
inputMessage: lastExecution?.inputMessage,
|
|
782
|
+
outputMessage: lastExecution?.outputMessage,
|
|
783
|
+
totalTraces: td.executions.reduce((sum, e) => sum + (e.traces?.length || 0), 0),
|
|
784
|
+
totalDurationMs: td.executions.reduce((sum, e) => sum + (e.totalDurationMs || 0), 0),
|
|
785
|
+
totalTokens: td.executions.reduce((sum, e) => sum + (e.totalTokens || 0), 0),
|
|
786
|
+
totalCostUsd: td.executions.reduce((sum, e) => sum + (e.totalCostUsd || 0), 0),
|
|
787
|
+
status: lastExecution?.status || 'success',
|
|
788
|
+
error: lastExecution?.error || null,
|
|
789
|
+
startedAt: td.executions[0]?.startedAt,
|
|
790
|
+
completedAt: lastExecution?.completedAt,
|
|
791
|
+
createdAt: td.executions[0]?.startedAt,
|
|
792
|
+
_sdkExecutions: td.executions,
|
|
793
|
+
});
|
|
286
794
|
}
|
|
287
|
-
|
|
795
|
+
return threads;
|
|
796
|
+
}
|
|
797
|
+
catch (error) {
|
|
798
|
+
console.error(chalk_1.default.yellow('โ ๏ธ Failed to read SDK traces.json:'), error.message);
|
|
799
|
+
return [];
|
|
288
800
|
}
|
|
289
|
-
throw new Error(`Server on port ${port} failed to start`);
|
|
290
801
|
}
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
802
|
+
buildTraceHierarchy(traces) {
|
|
803
|
+
const traceMap = new Map();
|
|
804
|
+
traces.forEach((t) => {
|
|
805
|
+
traceMap.set(t.traceId, {
|
|
806
|
+
id: t.traceId,
|
|
807
|
+
traceId: t.traceId,
|
|
808
|
+
parentTraceId: t.parentTraceId || null,
|
|
809
|
+
type: t.type,
|
|
810
|
+
operation: t.operation,
|
|
811
|
+
status: t.status,
|
|
812
|
+
startedAt: t.startTime,
|
|
813
|
+
completedAt: t.endTime,
|
|
814
|
+
durationMs: t.duration,
|
|
815
|
+
input: t.input,
|
|
816
|
+
output: t.output,
|
|
817
|
+
metadata: t.metadata,
|
|
818
|
+
children: [],
|
|
819
|
+
});
|
|
820
|
+
});
|
|
821
|
+
const rootTraces = [];
|
|
822
|
+
traces.forEach((t) => {
|
|
823
|
+
const node = traceMap.get(t.traceId);
|
|
824
|
+
if (t.parentTraceId) {
|
|
825
|
+
const parent = traceMap.get(t.parentTraceId);
|
|
826
|
+
if (parent) {
|
|
827
|
+
parent.children.push(node);
|
|
297
828
|
}
|
|
298
829
|
else {
|
|
299
|
-
|
|
830
|
+
rootTraces.push(node);
|
|
300
831
|
}
|
|
301
|
-
}
|
|
302
|
-
|
|
832
|
+
}
|
|
833
|
+
else {
|
|
834
|
+
rootTraces.push(node);
|
|
835
|
+
}
|
|
303
836
|
});
|
|
837
|
+
return rootTraces;
|
|
304
838
|
}
|
|
305
839
|
openBrowser(url) {
|
|
840
|
+
const { spawn } = require('child_process');
|
|
306
841
|
const start = process.platform === 'darwin' ? 'open' : process.platform === 'win32' ? 'start' : 'xdg-open';
|
|
307
842
|
const spawnOptions = { detached: true, stdio: 'ignore' };
|
|
308
843
|
if (process.platform === 'win32') {
|
|
309
844
|
spawnOptions.shell = true;
|
|
310
845
|
}
|
|
311
|
-
|
|
846
|
+
spawn(start, [url], spawnOptions);
|
|
312
847
|
}
|
|
313
848
|
setupCleanupHandlers() {
|
|
314
849
|
const cleanup = async () => {
|
|
315
|
-
console.log(chalk_1.default.yellow('\n๐ Shutting down
|
|
850
|
+
console.log(chalk_1.default.yellow('\n๐ Shutting down server...'));
|
|
316
851
|
await this.cleanup();
|
|
317
852
|
process.exit(0);
|
|
318
853
|
};
|
|
319
854
|
process.on('SIGINT', cleanup);
|
|
320
855
|
process.on('SIGTERM', cleanup);
|
|
321
|
-
process.on('exit', () => this.cleanup());
|
|
322
856
|
}
|
|
323
857
|
async cleanup() {
|
|
324
858
|
if (this.server) {
|
|
325
|
-
this.server.
|
|
859
|
+
this.server.close(() => {
|
|
860
|
+
console.log(chalk_1.default.gray('โ Server stopped'));
|
|
861
|
+
});
|
|
326
862
|
this.server = null;
|
|
327
863
|
}
|
|
328
|
-
if (this.frontend) {
|
|
329
|
-
this.frontend.kill('SIGTERM');
|
|
330
|
-
this.frontend = null;
|
|
331
|
-
}
|
|
332
|
-
const runflowDir = path.join(process.cwd(), '.runflow');
|
|
333
|
-
const frontendDir = path.join(runflowDir, 'test-frontend');
|
|
334
|
-
if (fs.existsSync(frontendDir)) {
|
|
335
|
-
try {
|
|
336
|
-
fs.rmSync(frontendDir, { recursive: true, force: true });
|
|
337
|
-
console.log('๐งน Cleaned up temporary files');
|
|
338
|
-
}
|
|
339
|
-
catch (error) {
|
|
340
|
-
}
|
|
341
|
-
}
|
|
342
864
|
}
|
|
343
865
|
async keepAlive() {
|
|
344
866
|
return new Promise(() => {
|
|
@@ -349,7 +871,7 @@ exports.TestCommand = TestCommand;
|
|
|
349
871
|
__decorate([
|
|
350
872
|
(0, nest_commander_1.Option)({
|
|
351
873
|
flags: '-p, --port <port>',
|
|
352
|
-
description: '
|
|
874
|
+
description: 'Base port for the server (default: random between 3000-4000)',
|
|
353
875
|
}),
|
|
354
876
|
__metadata("design:type", Function),
|
|
355
877
|
__metadata("design:paramtypes", [String]),
|
|
@@ -357,36 +879,18 @@ __decorate([
|
|
|
357
879
|
], TestCommand.prototype, "parsePort", null);
|
|
358
880
|
__decorate([
|
|
359
881
|
(0, nest_commander_1.Option)({
|
|
360
|
-
flags: '-
|
|
361
|
-
description: 'Host for the server (default: localhost)',
|
|
362
|
-
}),
|
|
363
|
-
__metadata("design:type", Function),
|
|
364
|
-
__metadata("design:paramtypes", [String]),
|
|
365
|
-
__metadata("design:returntype", String)
|
|
366
|
-
], TestCommand.prototype, "parseHost", null);
|
|
367
|
-
__decorate([
|
|
368
|
-
(0, nest_commander_1.Option)({
|
|
369
|
-
flags: '--no-open',
|
|
882
|
+
flags: '--no-browser',
|
|
370
883
|
description: "Don't open browser automatically",
|
|
371
884
|
}),
|
|
372
885
|
__metadata("design:type", Function),
|
|
373
886
|
__metadata("design:paramtypes", []),
|
|
374
887
|
__metadata("design:returntype", Boolean)
|
|
375
|
-
], TestCommand.prototype, "
|
|
376
|
-
__decorate([
|
|
377
|
-
(0, nest_commander_1.Option)({
|
|
378
|
-
flags: '--verbose',
|
|
379
|
-
description: 'Show detailed logs',
|
|
380
|
-
}),
|
|
381
|
-
__metadata("design:type", Function),
|
|
382
|
-
__metadata("design:paramtypes", [String]),
|
|
383
|
-
__metadata("design:returntype", Boolean)
|
|
384
|
-
], TestCommand.prototype, "parseVerbose", null);
|
|
888
|
+
], TestCommand.prototype, "parseNoBrowser", null);
|
|
385
889
|
exports.TestCommand = TestCommand = __decorate([
|
|
386
890
|
(0, common_1.Injectable)(),
|
|
387
891
|
(0, nest_commander_1.Command)({
|
|
388
892
|
name: 'test',
|
|
389
|
-
description: '๐งช Start local
|
|
893
|
+
description: '๐งช Start local observability server and open test portal',
|
|
390
894
|
})
|
|
391
895
|
], TestCommand);
|
|
392
896
|
//# sourceMappingURL=test.command.js.map
|