@runflow-ai/cli 0.2.11 โ 0.2.13
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 +703 -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,709 @@ 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 agentModule;
|
|
172
|
+
if (agentFile.endsWith('.ts')) {
|
|
173
|
+
require('tsx/cjs');
|
|
174
|
+
agentModule = require(agentFile);
|
|
166
175
|
}
|
|
167
176
|
else {
|
|
168
|
-
|
|
177
|
+
agentModule = require(agentFile);
|
|
178
|
+
}
|
|
179
|
+
const config = this.loadRunflowConfig();
|
|
180
|
+
const input = {
|
|
181
|
+
message,
|
|
182
|
+
sessionId,
|
|
183
|
+
userId,
|
|
184
|
+
channel: 'local-test',
|
|
185
|
+
threadId,
|
|
186
|
+
metadata: {
|
|
187
|
+
timestamp: new Date().toISOString(),
|
|
188
|
+
source: 'local-cli',
|
|
189
|
+
agentId: config?.agentId,
|
|
190
|
+
}
|
|
191
|
+
};
|
|
192
|
+
let result;
|
|
193
|
+
if (typeof agentModule.main === 'function') {
|
|
194
|
+
result = await agentModule.main(input);
|
|
169
195
|
}
|
|
170
|
-
|
|
196
|
+
else if (typeof agentModule.default === 'function') {
|
|
197
|
+
result = await agentModule.default(input);
|
|
198
|
+
}
|
|
199
|
+
else {
|
|
200
|
+
const agent = agentModule.agent || agentModule.default || agentModule;
|
|
201
|
+
if (typeof agent === 'function') {
|
|
202
|
+
const agentInstance = agent();
|
|
203
|
+
if (agentInstance && typeof agentInstance.process === 'function') {
|
|
204
|
+
result = await agentInstance.process(input);
|
|
205
|
+
}
|
|
206
|
+
else {
|
|
207
|
+
throw new Error('No valid export found. Expected: export async function main(input), export default function, or agent with process() method');
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
else if (agent && typeof agent.process === 'function') {
|
|
211
|
+
result = await agent.process(input);
|
|
212
|
+
}
|
|
213
|
+
else {
|
|
214
|
+
throw new Error('No valid export found. Expected: export async function main(input), export default function, or agent with process() method');
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
await new Promise((resolve) => setTimeout(resolve, 3000));
|
|
218
|
+
const tracesFile = path.join(process.cwd(), '.runflow', 'traces.json');
|
|
219
|
+
if (fs.existsSync(tracesFile)) {
|
|
220
|
+
try {
|
|
221
|
+
const content = fs.readFileSync(tracesFile, 'utf-8');
|
|
222
|
+
const data = JSON.parse(content);
|
|
223
|
+
const execId = result.executionId || result.metadata?.executionId || `exec-${Date.now()}`;
|
|
224
|
+
const execution = data.executions?.[execId];
|
|
225
|
+
if (execution) {
|
|
226
|
+
console.log(chalk_1.default.gray(` Traces saved: ${execution.traces?.length || 0}`));
|
|
227
|
+
const traceTypes = execution.traces?.map((t) => t.type) || [];
|
|
228
|
+
console.log(chalk_1.default.gray(` Types: ${[...new Set(traceTypes)].join(', ')}`));
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
catch (error) {
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
return {
|
|
235
|
+
executionId: result.executionId || result.metadata?.executionId || `exec-${Date.now()}`,
|
|
236
|
+
message: result.message || result.output || result,
|
|
237
|
+
metadata: result.metadata || {},
|
|
238
|
+
};
|
|
239
|
+
}
|
|
240
|
+
catch (error) {
|
|
241
|
+
throw new Error(`Failed to execute agent: ${error.message}`);
|
|
171
242
|
}
|
|
172
243
|
}
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
244
|
+
getRandomPort() {
|
|
245
|
+
return Math.floor(Math.random() * 1000) + 3000;
|
|
246
|
+
}
|
|
247
|
+
generateSessionId(req, bodySessionId) {
|
|
248
|
+
if (bodySessionId) {
|
|
249
|
+
return bodySessionId;
|
|
250
|
+
}
|
|
251
|
+
const headerSessionId = req.headers['x-session-id'];
|
|
252
|
+
if (headerSessionId) {
|
|
253
|
+
return headerSessionId;
|
|
176
254
|
}
|
|
177
|
-
|
|
178
|
-
|
|
255
|
+
const cookieHeader = req.headers['cookie'];
|
|
256
|
+
if (cookieHeader) {
|
|
257
|
+
const cookies = cookieHeader.split(';').reduce((acc, cookie) => {
|
|
258
|
+
const [key, value] = cookie.trim().split('=');
|
|
259
|
+
acc[key] = value;
|
|
260
|
+
return acc;
|
|
261
|
+
}, {});
|
|
262
|
+
if (cookies['runflow_session']) {
|
|
263
|
+
return cookies['runflow_session'];
|
|
264
|
+
}
|
|
179
265
|
}
|
|
180
|
-
const
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
266
|
+
const fingerprint = this.createFingerprint(req);
|
|
267
|
+
return `auto_${fingerprint}_${Date.now()}`;
|
|
268
|
+
}
|
|
269
|
+
generateUserId(req, sessionId) {
|
|
270
|
+
const cookieHeader = req.headers['cookie'];
|
|
271
|
+
if (cookieHeader) {
|
|
272
|
+
const cookies = cookieHeader.split(';').reduce((acc, cookie) => {
|
|
273
|
+
const [key, value] = cookie.trim().split('=');
|
|
274
|
+
acc[key] = value;
|
|
275
|
+
return acc;
|
|
276
|
+
}, {});
|
|
277
|
+
if (cookies['runflow_user']) {
|
|
278
|
+
return cookies['runflow_user'];
|
|
279
|
+
}
|
|
184
280
|
}
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
console.log(chalk_1.default.gray('[DEBUG] Verbose mode enabled - attaching log listeners'));
|
|
281
|
+
return sessionId;
|
|
282
|
+
}
|
|
283
|
+
createFingerprint(req) {
|
|
284
|
+
const components = [
|
|
285
|
+
req.headers['user-agent'] || '',
|
|
286
|
+
req.headers['accept-language'] || '',
|
|
287
|
+
req.ip || req.socket.remoteAddress || '',
|
|
288
|
+
].join('|');
|
|
289
|
+
return (0, crypto_1.createHash)('md5').update(components).digest('hex').substring(0, 12);
|
|
290
|
+
}
|
|
291
|
+
ensureStorageDir() {
|
|
292
|
+
if (!fs.existsSync(this.storageDir)) {
|
|
293
|
+
fs.mkdirSync(this.storageDir, { recursive: true });
|
|
199
294
|
}
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
295
|
+
}
|
|
296
|
+
async startObservabilityServer(port) {
|
|
297
|
+
return new Promise((resolve, reject) => {
|
|
298
|
+
this.app = (0, express_1.default)();
|
|
299
|
+
this.app.use((0, cors_1.default)({
|
|
300
|
+
origin: true,
|
|
301
|
+
credentials: true,
|
|
302
|
+
methods: ['GET', 'POST', 'PUT', 'DELETE', 'OPTIONS', 'PATCH'],
|
|
303
|
+
allowedHeaders: ['Content-Type', 'X-Thread-Id', 'X-Session-Id', 'Authorization', 'Cookie'],
|
|
304
|
+
exposedHeaders: ['Content-Length', 'X-Request-Id', 'X-Session-ID', 'X-User-ID', 'X-Timestamp'],
|
|
305
|
+
maxAge: 86400,
|
|
306
|
+
}));
|
|
307
|
+
this.app.use(express_1.default.json());
|
|
308
|
+
const distPath = path.join(__dirname, '..', '..', '..', 'static', 'dist-test');
|
|
309
|
+
this.app.use('/assets', express_1.default.static(path.join(distPath, 'assets')));
|
|
310
|
+
this.app.use('/widget', express_1.default.static(path.join(distPath, 'widget')));
|
|
311
|
+
this.app.get('/health', (req, res) => {
|
|
312
|
+
res.status(200).json({ status: 'ok', mode: 'local', version: '1.0.0' });
|
|
313
|
+
});
|
|
314
|
+
this.setupObservabilityEndpoints();
|
|
315
|
+
this.app.get('*', (req, res) => {
|
|
316
|
+
const indexPath = path.join(distPath, 'index-test.html');
|
|
317
|
+
res.sendFile(indexPath);
|
|
318
|
+
});
|
|
319
|
+
this.server = this.app.listen(port, () => {
|
|
320
|
+
resolve();
|
|
321
|
+
});
|
|
322
|
+
this.server.on('error', (error) => {
|
|
323
|
+
if (error.code === 'EADDRINUSE') {
|
|
324
|
+
reject(new Error(`Port ${port} is already in use`));
|
|
325
|
+
}
|
|
326
|
+
else {
|
|
327
|
+
reject(error);
|
|
328
|
+
}
|
|
329
|
+
});
|
|
330
|
+
});
|
|
331
|
+
}
|
|
332
|
+
setupObservabilityEndpoints() {
|
|
333
|
+
if (!this.app)
|
|
334
|
+
return;
|
|
335
|
+
this.app.post('/api/v1/agents/execute', async (req, res) => {
|
|
336
|
+
try {
|
|
337
|
+
const { message, threadId, sessionId: bodySessionId } = req.body;
|
|
338
|
+
if (!message) {
|
|
339
|
+
return res.status(400).json({
|
|
340
|
+
success: false,
|
|
341
|
+
error: 'message is required'
|
|
342
|
+
});
|
|
343
|
+
}
|
|
344
|
+
const normalizedMessage = message.toLowerCase().trim();
|
|
345
|
+
if (normalizedMessage === 'ping' || normalizedMessage === 'pong') {
|
|
346
|
+
return res.json({
|
|
347
|
+
success: true,
|
|
348
|
+
executionId: `ping-${Date.now()}`,
|
|
349
|
+
message: 'pong',
|
|
350
|
+
sessionId: 'ping-session',
|
|
351
|
+
userId: 'ping-user',
|
|
352
|
+
metadata: { isPing: true },
|
|
353
|
+
});
|
|
354
|
+
}
|
|
355
|
+
const sessionId = this.generateSessionId(req, bodySessionId);
|
|
356
|
+
const userId = this.generateUserId(req, sessionId);
|
|
357
|
+
console.log(chalk_1.default.gray(`๐จ [${new Date().toISOString()}] Executing agent...`));
|
|
358
|
+
console.log(chalk_1.default.gray(` Message: ${message.substring(0, 50)}${message.length > 50 ? '...' : ''}`));
|
|
359
|
+
if (threadId) {
|
|
360
|
+
console.log(chalk_1.default.gray(` Thread: ${threadId}`));
|
|
361
|
+
}
|
|
362
|
+
console.log(chalk_1.default.gray(` Session: ${sessionId}`));
|
|
363
|
+
const result = await this.executeAgent(message, threadId || sessionId, sessionId, userId);
|
|
364
|
+
console.log(chalk_1.default.green(`โ [${new Date().toISOString()}] Agent executed successfully`));
|
|
365
|
+
console.log(chalk_1.default.gray(` Execution ID: ${result.executionId}`));
|
|
366
|
+
console.log('');
|
|
367
|
+
res.setHeader('X-Session-ID', sessionId);
|
|
368
|
+
res.setHeader('X-User-ID', userId);
|
|
369
|
+
res.setHeader('X-Timestamp', new Date().toISOString());
|
|
370
|
+
res.setHeader('Set-Cookie', [
|
|
371
|
+
`runflow_session=${sessionId}; Path=/; HttpOnly; SameSite=Lax`,
|
|
372
|
+
`runflow_user=${userId}; Path=/; HttpOnly; SameSite=Lax`,
|
|
373
|
+
]);
|
|
374
|
+
res.json({
|
|
375
|
+
success: true,
|
|
376
|
+
executionId: result.executionId,
|
|
377
|
+
message: result.message,
|
|
378
|
+
sessionId: sessionId,
|
|
379
|
+
userId: userId,
|
|
380
|
+
metadata: result.metadata,
|
|
381
|
+
});
|
|
382
|
+
}
|
|
383
|
+
catch (error) {
|
|
384
|
+
console.error(chalk_1.default.red(`โ [${new Date().toISOString()}] Agent execution failed:`));
|
|
385
|
+
console.error(chalk_1.default.red(` ${error.message}`));
|
|
386
|
+
console.log('');
|
|
387
|
+
res.status(500).json({
|
|
388
|
+
success: false,
|
|
389
|
+
error: error.message
|
|
390
|
+
});
|
|
204
391
|
}
|
|
205
392
|
});
|
|
206
|
-
this.
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
393
|
+
this.app.get('/api/v1/observability/threads', async (req, res) => {
|
|
394
|
+
try {
|
|
395
|
+
const { agentId, threadId, limit = 50 } = req.query;
|
|
396
|
+
if (!agentId) {
|
|
397
|
+
return res.status(400).json({ error: 'agentId is required' });
|
|
398
|
+
}
|
|
399
|
+
const threads = await this.getThreads(agentId, threadId, parseInt(limit));
|
|
400
|
+
res.json({ threads, total: threads.length });
|
|
210
401
|
}
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
402
|
+
catch (error) {
|
|
403
|
+
console.error(chalk_1.default.red(`โ Error fetching threads: ${error.message}`));
|
|
404
|
+
res.status(500).json({ error: error.message });
|
|
405
|
+
}
|
|
406
|
+
});
|
|
407
|
+
this.app.get('/api/v1/observability/threads/:threadId/executions', async (req, res) => {
|
|
408
|
+
try {
|
|
409
|
+
const { threadId } = req.params;
|
|
410
|
+
const { agentId } = req.query;
|
|
411
|
+
if (!agentId) {
|
|
412
|
+
return res.status(400).json({ error: 'agentId is required' });
|
|
215
413
|
}
|
|
414
|
+
const result = await this.getThreadExecutions(agentId, threadId);
|
|
415
|
+
res.json(result);
|
|
416
|
+
}
|
|
417
|
+
catch (error) {
|
|
418
|
+
console.error(chalk_1.default.red(`โ Error fetching executions: ${error.message}`));
|
|
419
|
+
res.status(500).json({ error: error.message });
|
|
216
420
|
}
|
|
217
421
|
});
|
|
218
|
-
this.
|
|
219
|
-
|
|
422
|
+
this.app.get('/api/v1/observability/executions/:executionId', async (req, res) => {
|
|
423
|
+
try {
|
|
424
|
+
const { executionId } = req.params;
|
|
425
|
+
const { agentId } = req.query;
|
|
426
|
+
if (!agentId) {
|
|
427
|
+
return res.status(400).json({ error: 'agentId is required' });
|
|
428
|
+
}
|
|
429
|
+
const result = await this.getExecutionTraces(agentId, executionId);
|
|
430
|
+
if (!result) {
|
|
431
|
+
return res.status(404).json({ error: 'Execution not found' });
|
|
432
|
+
}
|
|
433
|
+
res.json(result);
|
|
434
|
+
}
|
|
435
|
+
catch (error) {
|
|
436
|
+
console.error(chalk_1.default.red(`โ Error fetching execution: ${error.message}`));
|
|
437
|
+
res.status(500).json({ error: error.message });
|
|
438
|
+
}
|
|
220
439
|
});
|
|
221
|
-
await this.waitForServer(port, 10000);
|
|
222
440
|
}
|
|
223
|
-
async
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
441
|
+
async getThreads(agentId, threadId, limit = 50) {
|
|
442
|
+
const threads = [];
|
|
443
|
+
const files = this.getAllTraceFiles();
|
|
444
|
+
for (const filePath of files) {
|
|
445
|
+
try {
|
|
446
|
+
const content = fs.readFileSync(filePath, 'utf-8');
|
|
447
|
+
const data = JSON.parse(content);
|
|
448
|
+
if (data.agentId !== agentId)
|
|
449
|
+
continue;
|
|
450
|
+
for (const [tid, threadData] of Object.entries(data.threads || {})) {
|
|
451
|
+
if (threadId && tid !== threadId)
|
|
452
|
+
continue;
|
|
453
|
+
const td = threadData;
|
|
454
|
+
const executions = td.executions || [];
|
|
455
|
+
const lastExecution = executions[executions.length - 1];
|
|
456
|
+
threads.push({
|
|
457
|
+
id: `thread-${tid}`,
|
|
458
|
+
executionId: lastExecution?.executionId,
|
|
459
|
+
threadId: tid,
|
|
460
|
+
tenantId: 'local',
|
|
461
|
+
agentId: data.agentId,
|
|
462
|
+
entityType: td.entityType || 'email',
|
|
463
|
+
entityValue: td.entityValue || tid,
|
|
464
|
+
userId: td.userId || null,
|
|
465
|
+
sessionId: data.sessionId,
|
|
466
|
+
channel: td.channel || 'sdk',
|
|
467
|
+
source: td.source || 'local',
|
|
468
|
+
inputMessage: lastExecution?.inputMessage,
|
|
469
|
+
outputMessage: lastExecution?.outputMessage,
|
|
470
|
+
totalTraces: executions.reduce((sum, e) => sum + (e.traces?.length || 0), 0),
|
|
471
|
+
totalDurationMs: executions.reduce((sum, e) => sum + (e.totalDurationMs || 0), 0),
|
|
472
|
+
totalTokens: executions.reduce((sum, e) => sum + (e.totalTokens || 0), 0),
|
|
473
|
+
totalCostUsd: executions.reduce((sum, e) => sum + (e.totalCostUsd || 0), 0),
|
|
474
|
+
status: lastExecution?.status || 'pending',
|
|
475
|
+
error: lastExecution?.error || null,
|
|
476
|
+
startedAt: executions[0]?.startedAt,
|
|
477
|
+
completedAt: lastExecution?.completedAt,
|
|
478
|
+
createdAt: executions[0]?.startedAt,
|
|
479
|
+
});
|
|
480
|
+
}
|
|
481
|
+
}
|
|
482
|
+
catch (error) {
|
|
483
|
+
}
|
|
229
484
|
}
|
|
230
|
-
const
|
|
231
|
-
const
|
|
232
|
-
|
|
233
|
-
|
|
485
|
+
const sdkThreads = this.getSDKTraces(agentId);
|
|
486
|
+
const filteredSDKThreads = threadId
|
|
487
|
+
? sdkThreads.filter(t => t.threadId === threadId)
|
|
488
|
+
: sdkThreads;
|
|
489
|
+
threads.push(...filteredSDKThreads);
|
|
490
|
+
return threads
|
|
491
|
+
.sort((a, b) => new Date(b.startedAt).getTime() - new Date(a.startedAt).getTime())
|
|
492
|
+
.slice(0, limit);
|
|
493
|
+
}
|
|
494
|
+
async getThreadExecutions(agentId, threadId) {
|
|
495
|
+
const executions = [];
|
|
496
|
+
const files = this.getAllTraceFiles();
|
|
497
|
+
for (const filePath of files) {
|
|
498
|
+
try {
|
|
499
|
+
const content = fs.readFileSync(filePath, 'utf-8');
|
|
500
|
+
const data = JSON.parse(content);
|
|
501
|
+
if (data.agentId !== agentId)
|
|
502
|
+
continue;
|
|
503
|
+
const threadData = data.threads?.[threadId];
|
|
504
|
+
if (threadData?.executions) {
|
|
505
|
+
const formattedExecutions = threadData.executions.map((e) => ({
|
|
506
|
+
id: e.executionId,
|
|
507
|
+
executionId: e.executionId,
|
|
508
|
+
threadId: threadId,
|
|
509
|
+
tenantId: 'local',
|
|
510
|
+
agentId: data.agentId,
|
|
511
|
+
entityType: threadData.entityType || 'email',
|
|
512
|
+
entityValue: threadData.entityValue || threadId,
|
|
513
|
+
userId: threadData.userId || null,
|
|
514
|
+
sessionId: data.sessionId,
|
|
515
|
+
channel: threadData.channel || 'sdk',
|
|
516
|
+
source: threadData.source || 'local',
|
|
517
|
+
inputMessage: e.inputMessage,
|
|
518
|
+
outputMessage: e.outputMessage,
|
|
519
|
+
totalTraces: e.traces?.length || 0,
|
|
520
|
+
totalDurationMs: e.totalDurationMs || 0,
|
|
521
|
+
totalTokens: e.totalTokens || 0,
|
|
522
|
+
totalCostUsd: e.totalCostUsd || 0,
|
|
523
|
+
status: e.status || 'pending',
|
|
524
|
+
error: e.error || null,
|
|
525
|
+
startedAt: e.startedAt,
|
|
526
|
+
completedAt: e.completedAt,
|
|
527
|
+
createdAt: e.startedAt,
|
|
528
|
+
}));
|
|
529
|
+
executions.push(...formattedExecutions);
|
|
530
|
+
}
|
|
531
|
+
}
|
|
532
|
+
catch (error) {
|
|
533
|
+
}
|
|
234
534
|
}
|
|
235
|
-
const
|
|
236
|
-
const
|
|
237
|
-
if (
|
|
238
|
-
|
|
535
|
+
const sdkThreads = this.getSDKTraces(agentId);
|
|
536
|
+
const sdkThread = sdkThreads.find(t => t.threadId === threadId);
|
|
537
|
+
if (sdkThread && sdkThread._sdkExecutions) {
|
|
538
|
+
const formattedExecutions = sdkThread._sdkExecutions.map((e) => ({
|
|
539
|
+
id: e.executionId,
|
|
540
|
+
executionId: e.executionId,
|
|
541
|
+
threadId: threadId,
|
|
542
|
+
tenantId: 'local',
|
|
543
|
+
agentId: agentId,
|
|
544
|
+
entityType: sdkThread.entityType,
|
|
545
|
+
entityValue: sdkThread.entityValue,
|
|
546
|
+
userId: sdkThread.userId,
|
|
547
|
+
sessionId: e.executionId,
|
|
548
|
+
channel: 'sdk',
|
|
549
|
+
source: 'local',
|
|
550
|
+
inputMessage: e.inputMessage,
|
|
551
|
+
outputMessage: e.outputMessage,
|
|
552
|
+
totalTraces: e.traces?.length || 0,
|
|
553
|
+
totalDurationMs: e.totalDurationMs,
|
|
554
|
+
totalTokens: e.totalTokens,
|
|
555
|
+
totalCostUsd: e.totalCostUsd,
|
|
556
|
+
status: e.status,
|
|
557
|
+
error: e.error || null,
|
|
558
|
+
startedAt: e.startedAt,
|
|
559
|
+
completedAt: e.completedAt,
|
|
560
|
+
createdAt: e.startedAt,
|
|
561
|
+
}));
|
|
562
|
+
executions.push(...formattedExecutions);
|
|
239
563
|
}
|
|
240
|
-
const
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
564
|
+
const sorted = executions.sort((a, b) => new Date(b.startedAt).getTime() - new Date(a.startedAt).getTime());
|
|
565
|
+
return {
|
|
566
|
+
threadId,
|
|
567
|
+
executions: sorted,
|
|
568
|
+
total: sorted.length,
|
|
569
|
+
};
|
|
570
|
+
}
|
|
571
|
+
async getExecutionTraces(agentId, executionId) {
|
|
572
|
+
const files = this.getAllTraceFiles();
|
|
573
|
+
for (const filePath of files) {
|
|
574
|
+
try {
|
|
575
|
+
const content = fs.readFileSync(filePath, 'utf-8');
|
|
576
|
+
const data = JSON.parse(content);
|
|
577
|
+
if (data.agentId !== agentId)
|
|
578
|
+
continue;
|
|
579
|
+
for (const [tid, threadData] of Object.entries(data.threads || {})) {
|
|
580
|
+
const td = threadData;
|
|
581
|
+
const execution = td.executions?.find((e) => e.executionId === executionId);
|
|
582
|
+
if (execution) {
|
|
583
|
+
const formattedExecution = {
|
|
584
|
+
id: execution.executionId,
|
|
585
|
+
executionId: execution.executionId,
|
|
586
|
+
threadId: tid,
|
|
587
|
+
tenantId: 'local',
|
|
588
|
+
agentId: data.agentId,
|
|
589
|
+
entityType: td.entityType || 'email',
|
|
590
|
+
entityValue: td.entityValue || tid,
|
|
591
|
+
userId: td.userId || null,
|
|
592
|
+
sessionId: data.sessionId,
|
|
593
|
+
channel: td.channel || 'sdk',
|
|
594
|
+
source: td.source || 'local',
|
|
595
|
+
inputMessage: execution.inputMessage,
|
|
596
|
+
outputMessage: execution.outputMessage,
|
|
597
|
+
totalTraces: execution.traces?.length || 0,
|
|
598
|
+
totalDurationMs: execution.totalDurationMs || 0,
|
|
599
|
+
totalTokens: execution.totalTokens || 0,
|
|
600
|
+
totalCostUsd: execution.totalCostUsd || 0,
|
|
601
|
+
status: execution.status || 'pending',
|
|
602
|
+
error: execution.error || null,
|
|
603
|
+
startedAt: execution.startedAt,
|
|
604
|
+
completedAt: execution.completedAt,
|
|
605
|
+
createdAt: execution.startedAt,
|
|
606
|
+
};
|
|
607
|
+
const traces = execution.traces || [];
|
|
608
|
+
const formattedTraces = traces.map((t) => ({
|
|
609
|
+
id: t.id || t.traceId,
|
|
610
|
+
traceId: t.id || t.traceId,
|
|
611
|
+
parentTraceId: t.parentId || null,
|
|
612
|
+
executionId: execution.executionId,
|
|
613
|
+
tenantId: 'local',
|
|
614
|
+
agentId: data.agentId,
|
|
615
|
+
threadId: tid,
|
|
616
|
+
userId: td.userId || null,
|
|
617
|
+
sessionId: data.sessionId,
|
|
618
|
+
type: t.type,
|
|
619
|
+
operation: t.name || t.type,
|
|
620
|
+
status: t.status,
|
|
621
|
+
error: t.error || null,
|
|
622
|
+
input: t.input || null,
|
|
623
|
+
output: t.output || null,
|
|
624
|
+
metadata: t.metadata || {},
|
|
625
|
+
startedAt: t.startedAt,
|
|
626
|
+
completedAt: t.completedAt,
|
|
627
|
+
durationMs: t.durationMs,
|
|
628
|
+
tokensTotal: t.metadata?.totalTokens || t.metadata?.tokens || null,
|
|
629
|
+
costUsd: t.metadata?.cost || null,
|
|
630
|
+
createdAt: t.startedAt,
|
|
631
|
+
children: t.children || [],
|
|
632
|
+
}));
|
|
633
|
+
return {
|
|
634
|
+
execution: formattedExecution,
|
|
635
|
+
traces: formattedTraces,
|
|
636
|
+
};
|
|
637
|
+
}
|
|
638
|
+
}
|
|
639
|
+
}
|
|
640
|
+
catch (error) {
|
|
246
641
|
}
|
|
247
642
|
}
|
|
248
|
-
const
|
|
249
|
-
|
|
250
|
-
|
|
643
|
+
const sdkThreads = this.getSDKTraces(agentId);
|
|
644
|
+
for (const thread of sdkThreads) {
|
|
645
|
+
if (thread._sdkExecutions) {
|
|
646
|
+
const execution = thread._sdkExecutions.find((e) => e.executionId === executionId);
|
|
647
|
+
if (execution) {
|
|
648
|
+
const formattedExecution = {
|
|
649
|
+
id: execution.executionId,
|
|
650
|
+
executionId: execution.executionId,
|
|
651
|
+
threadId: thread.threadId,
|
|
652
|
+
tenantId: 'local',
|
|
653
|
+
agentId: agentId,
|
|
654
|
+
entityType: thread.entityType,
|
|
655
|
+
entityValue: thread.entityValue,
|
|
656
|
+
userId: thread.userId,
|
|
657
|
+
sessionId: execution.executionId,
|
|
658
|
+
channel: 'sdk',
|
|
659
|
+
source: 'local',
|
|
660
|
+
inputMessage: execution.inputMessage,
|
|
661
|
+
outputMessage: execution.outputMessage,
|
|
662
|
+
totalTraces: this.countAllTraces(execution.traces),
|
|
663
|
+
totalDurationMs: execution.totalDurationMs || 0,
|
|
664
|
+
totalTokens: execution.totalTokens || 0,
|
|
665
|
+
totalCostUsd: execution.totalCostUsd || 0,
|
|
666
|
+
status: execution.status || 'success',
|
|
667
|
+
error: execution.error || null,
|
|
668
|
+
startedAt: execution.startedAt,
|
|
669
|
+
completedAt: execution.completedAt,
|
|
670
|
+
createdAt: execution.startedAt,
|
|
671
|
+
};
|
|
672
|
+
const formattedTraces = execution.traces || [];
|
|
673
|
+
return {
|
|
674
|
+
execution: formattedExecution,
|
|
675
|
+
traces: formattedTraces,
|
|
676
|
+
};
|
|
677
|
+
}
|
|
678
|
+
}
|
|
251
679
|
}
|
|
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());
|
|
680
|
+
return null;
|
|
681
|
+
}
|
|
682
|
+
countAllTraces(traces) {
|
|
683
|
+
let count = traces.length;
|
|
684
|
+
traces.forEach((trace) => {
|
|
685
|
+
if (trace.children && trace.children.length > 0) {
|
|
686
|
+
count += this.countAllTraces(trace.children);
|
|
264
687
|
}
|
|
265
688
|
});
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
689
|
+
return count;
|
|
690
|
+
}
|
|
691
|
+
getAllTraceFiles() {
|
|
692
|
+
const files = [];
|
|
693
|
+
if (!fs.existsSync(this.storageDir)) {
|
|
694
|
+
return files;
|
|
695
|
+
}
|
|
696
|
+
const dateDirs = fs.readdirSync(this.storageDir);
|
|
697
|
+
for (const dateDir of dateDirs) {
|
|
698
|
+
const datePath = path.join(this.storageDir, dateDir);
|
|
699
|
+
if (!fs.statSync(datePath).isDirectory())
|
|
700
|
+
continue;
|
|
701
|
+
const traceFiles = fs.readdirSync(datePath).filter((f) => f.endsWith('.json'));
|
|
702
|
+
for (const file of traceFiles) {
|
|
703
|
+
files.push(path.join(datePath, file));
|
|
269
704
|
}
|
|
270
|
-
}
|
|
271
|
-
|
|
272
|
-
console.error(chalk_1.default.red(`Failed to start frontend: ${error.message}`));
|
|
273
|
-
});
|
|
274
|
-
await this.waitForServer(frontendPort, 5000);
|
|
705
|
+
}
|
|
706
|
+
return files;
|
|
275
707
|
}
|
|
276
|
-
|
|
277
|
-
const
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
708
|
+
getSDKTraces(agentId) {
|
|
709
|
+
const sdkTracesFile = path.join(process.cwd(), '.runflow', 'traces.json');
|
|
710
|
+
if (!fs.existsSync(sdkTracesFile)) {
|
|
711
|
+
return [];
|
|
712
|
+
}
|
|
713
|
+
try {
|
|
714
|
+
const content = fs.readFileSync(sdkTracesFile, 'utf-8');
|
|
715
|
+
const data = JSON.parse(content);
|
|
716
|
+
const threads = [];
|
|
717
|
+
const executionsMap = data.executions || {};
|
|
718
|
+
const threadGroups = {};
|
|
719
|
+
for (const [execId, execution] of Object.entries(executionsMap)) {
|
|
720
|
+
const exec = execution;
|
|
721
|
+
const threadId = exec.threadId || 'unknown';
|
|
722
|
+
if (!threadGroups[threadId]) {
|
|
723
|
+
threadGroups[threadId] = {
|
|
724
|
+
threadId: threadId,
|
|
725
|
+
entityType: exec.entityType || 'session',
|
|
726
|
+
entityValue: exec.entityValue || null,
|
|
727
|
+
userId: exec.userId || null,
|
|
728
|
+
channel: 'sdk',
|
|
729
|
+
source: 'local',
|
|
730
|
+
executions: [],
|
|
731
|
+
};
|
|
283
732
|
}
|
|
733
|
+
const traces = exec.traces || [];
|
|
734
|
+
const agentTrace = traces.find((t) => t.type === 'agent_execution' &&
|
|
735
|
+
!t.parentTraceId &&
|
|
736
|
+
t.operation === 'agent_execution') || traces.find((t) => t.type === 'agent_execution' &&
|
|
737
|
+
!t.parentTraceId) || traces.find((t) => t.type === 'agent_execution');
|
|
738
|
+
const tracesWithChildren = this.buildTraceHierarchy(traces);
|
|
739
|
+
const executionData = {
|
|
740
|
+
executionId: exec.executionId,
|
|
741
|
+
inputMessage: agentTrace?.input?.message || 'N/A',
|
|
742
|
+
outputMessage: agentTrace?.output?.message || 'N/A',
|
|
743
|
+
status: exec.status || 'success',
|
|
744
|
+
totalDurationMs: traces.reduce((sum, t) => sum + (t.duration || 0), 0),
|
|
745
|
+
totalTokens: traces.reduce((sum, t) => sum + (t.metadata?.totalTokens || 0), 0),
|
|
746
|
+
totalCostUsd: 0,
|
|
747
|
+
startedAt: exec.startedAt || agentTrace?.startTime,
|
|
748
|
+
completedAt: agentTrace?.endTime || exec.completedAt,
|
|
749
|
+
traces: tracesWithChildren,
|
|
750
|
+
};
|
|
751
|
+
threadGroups[threadId].executions.push(executionData);
|
|
284
752
|
}
|
|
285
|
-
|
|
753
|
+
for (const [threadId, threadData] of Object.entries(threadGroups)) {
|
|
754
|
+
const td = threadData;
|
|
755
|
+
const lastExecution = td.executions[td.executions.length - 1];
|
|
756
|
+
threads.push({
|
|
757
|
+
id: `thread-${threadId}`,
|
|
758
|
+
executionId: lastExecution?.executionId,
|
|
759
|
+
threadId: threadId,
|
|
760
|
+
tenantId: 'local',
|
|
761
|
+
agentId: agentId,
|
|
762
|
+
entityType: td.entityType,
|
|
763
|
+
entityValue: td.entityValue,
|
|
764
|
+
userId: td.userId,
|
|
765
|
+
sessionId: lastExecution?.executionId,
|
|
766
|
+
channel: td.channel,
|
|
767
|
+
source: td.source,
|
|
768
|
+
inputMessage: lastExecution?.inputMessage,
|
|
769
|
+
outputMessage: lastExecution?.outputMessage,
|
|
770
|
+
totalTraces: td.executions.reduce((sum, e) => sum + (e.traces?.length || 0), 0),
|
|
771
|
+
totalDurationMs: td.executions.reduce((sum, e) => sum + (e.totalDurationMs || 0), 0),
|
|
772
|
+
totalTokens: td.executions.reduce((sum, e) => sum + (e.totalTokens || 0), 0),
|
|
773
|
+
totalCostUsd: td.executions.reduce((sum, e) => sum + (e.totalCostUsd || 0), 0),
|
|
774
|
+
status: lastExecution?.status || 'success',
|
|
775
|
+
error: lastExecution?.error || null,
|
|
776
|
+
startedAt: td.executions[0]?.startedAt,
|
|
777
|
+
completedAt: lastExecution?.completedAt,
|
|
778
|
+
createdAt: td.executions[0]?.startedAt,
|
|
779
|
+
_sdkExecutions: td.executions,
|
|
780
|
+
});
|
|
286
781
|
}
|
|
287
|
-
|
|
782
|
+
return threads;
|
|
783
|
+
}
|
|
784
|
+
catch (error) {
|
|
785
|
+
console.error(chalk_1.default.yellow('โ ๏ธ Failed to read SDK traces.json:'), error.message);
|
|
786
|
+
return [];
|
|
288
787
|
}
|
|
289
|
-
throw new Error(`Server on port ${port} failed to start`);
|
|
290
788
|
}
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
789
|
+
buildTraceHierarchy(traces) {
|
|
790
|
+
const traceMap = new Map();
|
|
791
|
+
traces.forEach((t) => {
|
|
792
|
+
traceMap.set(t.traceId, {
|
|
793
|
+
id: t.traceId,
|
|
794
|
+
traceId: t.traceId,
|
|
795
|
+
parentTraceId: t.parentTraceId || null,
|
|
796
|
+
type: t.type,
|
|
797
|
+
operation: t.operation,
|
|
798
|
+
status: t.status,
|
|
799
|
+
startedAt: t.startTime,
|
|
800
|
+
completedAt: t.endTime,
|
|
801
|
+
durationMs: t.duration,
|
|
802
|
+
input: t.input,
|
|
803
|
+
output: t.output,
|
|
804
|
+
metadata: t.metadata,
|
|
805
|
+
children: [],
|
|
806
|
+
});
|
|
807
|
+
});
|
|
808
|
+
const rootTraces = [];
|
|
809
|
+
traces.forEach((t) => {
|
|
810
|
+
const node = traceMap.get(t.traceId);
|
|
811
|
+
if (t.parentTraceId) {
|
|
812
|
+
const parent = traceMap.get(t.parentTraceId);
|
|
813
|
+
if (parent) {
|
|
814
|
+
parent.children.push(node);
|
|
297
815
|
}
|
|
298
816
|
else {
|
|
299
|
-
|
|
817
|
+
rootTraces.push(node);
|
|
300
818
|
}
|
|
301
|
-
}
|
|
302
|
-
|
|
819
|
+
}
|
|
820
|
+
else {
|
|
821
|
+
rootTraces.push(node);
|
|
822
|
+
}
|
|
303
823
|
});
|
|
824
|
+
return rootTraces;
|
|
304
825
|
}
|
|
305
826
|
openBrowser(url) {
|
|
827
|
+
const { spawn } = require('child_process');
|
|
306
828
|
const start = process.platform === 'darwin' ? 'open' : process.platform === 'win32' ? 'start' : 'xdg-open';
|
|
307
829
|
const spawnOptions = { detached: true, stdio: 'ignore' };
|
|
308
830
|
if (process.platform === 'win32') {
|
|
309
831
|
spawnOptions.shell = true;
|
|
310
832
|
}
|
|
311
|
-
|
|
833
|
+
spawn(start, [url], spawnOptions);
|
|
312
834
|
}
|
|
313
835
|
setupCleanupHandlers() {
|
|
314
836
|
const cleanup = async () => {
|
|
315
|
-
console.log(chalk_1.default.yellow('\n๐ Shutting down
|
|
837
|
+
console.log(chalk_1.default.yellow('\n๐ Shutting down server...'));
|
|
316
838
|
await this.cleanup();
|
|
317
839
|
process.exit(0);
|
|
318
840
|
};
|
|
319
841
|
process.on('SIGINT', cleanup);
|
|
320
842
|
process.on('SIGTERM', cleanup);
|
|
321
|
-
process.on('exit', () => this.cleanup());
|
|
322
843
|
}
|
|
323
844
|
async cleanup() {
|
|
324
845
|
if (this.server) {
|
|
325
|
-
this.server.
|
|
846
|
+
this.server.close(() => {
|
|
847
|
+
console.log(chalk_1.default.gray('โ Server stopped'));
|
|
848
|
+
});
|
|
326
849
|
this.server = null;
|
|
327
850
|
}
|
|
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
851
|
}
|
|
343
852
|
async keepAlive() {
|
|
344
853
|
return new Promise(() => {
|
|
@@ -349,7 +858,7 @@ exports.TestCommand = TestCommand;
|
|
|
349
858
|
__decorate([
|
|
350
859
|
(0, nest_commander_1.Option)({
|
|
351
860
|
flags: '-p, --port <port>',
|
|
352
|
-
description: '
|
|
861
|
+
description: 'Base port for the server (default: random between 3000-4000)',
|
|
353
862
|
}),
|
|
354
863
|
__metadata("design:type", Function),
|
|
355
864
|
__metadata("design:paramtypes", [String]),
|
|
@@ -357,36 +866,18 @@ __decorate([
|
|
|
357
866
|
], TestCommand.prototype, "parsePort", null);
|
|
358
867
|
__decorate([
|
|
359
868
|
(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',
|
|
869
|
+
flags: '--no-browser',
|
|
370
870
|
description: "Don't open browser automatically",
|
|
371
871
|
}),
|
|
372
872
|
__metadata("design:type", Function),
|
|
373
873
|
__metadata("design:paramtypes", []),
|
|
374
874
|
__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);
|
|
875
|
+
], TestCommand.prototype, "parseNoBrowser", null);
|
|
385
876
|
exports.TestCommand = TestCommand = __decorate([
|
|
386
877
|
(0, common_1.Injectable)(),
|
|
387
878
|
(0, nest_commander_1.Command)({
|
|
388
879
|
name: 'test',
|
|
389
|
-
description: '๐งช Start local
|
|
880
|
+
description: '๐งช Start local observability server and open test portal',
|
|
390
881
|
})
|
|
391
882
|
], TestCommand);
|
|
392
883
|
//# sourceMappingURL=test.command.js.map
|