@arvoretech/runtime-lens-mcp 1.0.0 → 1.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +10 -3
- package/agent/index.ts +281 -212
- package/dist/agent/index.js +282 -0
- package/dist/agent/index.js.map +7 -0
- package/dist/agent-bridge.d.ts +20 -0
- package/dist/agent-bridge.d.ts.map +1 -0
- package/dist/agent-bridge.js +185 -0
- package/dist/agent-bridge.js.map +1 -0
- package/dist/extension/extension.js +433 -0
- package/dist/extension/extension.js.map +7 -0
- package/dist/server.d.ts +1 -0
- package/dist/server.d.ts.map +1 -1
- package/dist/server.js +115 -100
- package/dist/server.js.map +1 -1
- package/package.json +2 -2
- package/src/agent-bridge.ts +218 -0
- package/src/server.ts +405 -358
- package/.vscodeignore +0 -21
- package/agent/tsconfig.json +0 -17
- package/eslint.config.js +0 -41
- package/extension/decorator.ts +0 -144
- package/extension/extension.ts +0 -98
- package/extension/runtime-bridge.ts +0 -206
- package/extension/tsconfig.json +0 -17
- package/tsconfig.json +0 -20
- package/vitest.config.ts +0 -13
package/dist/server.js
CHANGED
|
@@ -1,22 +1,25 @@
|
|
|
1
1
|
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
2
2
|
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
3
|
+
import { AgentBridge } from "./agent-bridge.js";
|
|
3
4
|
import { LogCollector } from "./log-collector.js";
|
|
4
5
|
import { ProcessInspector } from "./process-inspector.js";
|
|
5
6
|
import { RuntimeInterceptor } from "./runtime-interceptor.js";
|
|
6
|
-
import {
|
|
7
|
+
import { GetErrorsParamsSchema, GetPerformanceParamsSchema, InspectRequestParamsSchema, SearchLogsParamsSchema, TailLogsParamsSchema, } from "./types.js";
|
|
7
8
|
export class RuntimeLensMCPServer {
|
|
8
9
|
server;
|
|
9
10
|
collector;
|
|
10
11
|
inspector;
|
|
11
12
|
interceptor;
|
|
13
|
+
agentBridge;
|
|
12
14
|
constructor(projectRoot, logPaths) {
|
|
13
15
|
this.server = new McpServer({
|
|
14
16
|
name: "runtime-lens-mcp",
|
|
15
|
-
version: "1.
|
|
17
|
+
version: "1.1.0",
|
|
16
18
|
});
|
|
17
19
|
this.collector = new LogCollector(projectRoot, logPaths);
|
|
18
20
|
this.inspector = new ProcessInspector(projectRoot);
|
|
19
21
|
this.interceptor = new RuntimeInterceptor(this.collector);
|
|
22
|
+
this.agentBridge = new AgentBridge(this.collector);
|
|
20
23
|
this.setupTools();
|
|
21
24
|
}
|
|
22
25
|
static fromEnvironment() {
|
|
@@ -50,15 +53,14 @@ export class RuntimeLensMCPServer {
|
|
|
50
53
|
framework: TailLogsParamsSchema.shape.framework,
|
|
51
54
|
source: TailLogsParamsSchema.shape.source,
|
|
52
55
|
},
|
|
53
|
-
}, async (params) => {
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
});
|
|
56
|
+
}, async (params) => ({
|
|
57
|
+
content: [
|
|
58
|
+
{
|
|
59
|
+
type: "text",
|
|
60
|
+
text: JSON.stringify(this.collector.getLogs(params), null, 2),
|
|
61
|
+
},
|
|
62
|
+
],
|
|
63
|
+
}));
|
|
62
64
|
}
|
|
63
65
|
registerSearchLogs() {
|
|
64
66
|
this.server.registerTool("search_logs", {
|
|
@@ -71,15 +73,14 @@ export class RuntimeLensMCPServer {
|
|
|
71
73
|
limit: SearchLogsParamsSchema.shape.limit,
|
|
72
74
|
since: SearchLogsParamsSchema.shape.since,
|
|
73
75
|
},
|
|
74
|
-
}, async (params) => {
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
});
|
|
76
|
+
}, async (params) => ({
|
|
77
|
+
content: [
|
|
78
|
+
{
|
|
79
|
+
type: "text",
|
|
80
|
+
text: JSON.stringify(this.collector.searchLogs(params), null, 2),
|
|
81
|
+
},
|
|
82
|
+
],
|
|
83
|
+
}));
|
|
83
84
|
}
|
|
84
85
|
registerGetErrors() {
|
|
85
86
|
this.server.registerTool("get_errors", {
|
|
@@ -90,15 +91,14 @@ export class RuntimeLensMCPServer {
|
|
|
90
91
|
framework: GetErrorsParamsSchema.shape.framework,
|
|
91
92
|
grouped: GetErrorsParamsSchema.shape.grouped,
|
|
92
93
|
},
|
|
93
|
-
}, async (params) => {
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
});
|
|
94
|
+
}, async (params) => ({
|
|
95
|
+
content: [
|
|
96
|
+
{
|
|
97
|
+
type: "text",
|
|
98
|
+
text: JSON.stringify(this.collector.getErrors(params), null, 2),
|
|
99
|
+
},
|
|
100
|
+
],
|
|
101
|
+
}));
|
|
102
102
|
}
|
|
103
103
|
registerInspectRequests() {
|
|
104
104
|
this.server.registerTool("inspect_requests", {
|
|
@@ -111,15 +111,14 @@ export class RuntimeLensMCPServer {
|
|
|
111
111
|
statusCode: InspectRequestParamsSchema.shape.statusCode,
|
|
112
112
|
limit: InspectRequestParamsSchema.shape.limit,
|
|
113
113
|
},
|
|
114
|
-
}, async (params) => {
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
});
|
|
114
|
+
}, async (params) => ({
|
|
115
|
+
content: [
|
|
116
|
+
{
|
|
117
|
+
type: "text",
|
|
118
|
+
text: JSON.stringify(this.collector.getRequests(params), null, 2),
|
|
119
|
+
},
|
|
120
|
+
],
|
|
121
|
+
}));
|
|
123
122
|
}
|
|
124
123
|
registerGetPerformance() {
|
|
125
124
|
this.server.registerTool("get_performance", {
|
|
@@ -132,12 +131,13 @@ export class RuntimeLensMCPServer {
|
|
|
132
131
|
},
|
|
133
132
|
}, async (params) => {
|
|
134
133
|
await this.collector.collectFromProcess();
|
|
135
|
-
const metrics = this.collector.getMetrics(params);
|
|
136
134
|
return {
|
|
137
|
-
content: [
|
|
135
|
+
content: [
|
|
136
|
+
{
|
|
138
137
|
type: "text",
|
|
139
|
-
text: JSON.stringify(
|
|
140
|
-
}
|
|
138
|
+
text: JSON.stringify(this.collector.getMetrics(params), null, 2),
|
|
139
|
+
},
|
|
140
|
+
],
|
|
141
141
|
};
|
|
142
142
|
});
|
|
143
143
|
}
|
|
@@ -146,15 +146,14 @@ export class RuntimeLensMCPServer {
|
|
|
146
146
|
title: "Get Environment Info",
|
|
147
147
|
description: "Get comprehensive environment information including running Node.js processes, listening ports, project framework detection, and system resources.",
|
|
148
148
|
inputSchema: {},
|
|
149
|
-
}, async () => {
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
});
|
|
149
|
+
}, async () => ({
|
|
150
|
+
content: [
|
|
151
|
+
{
|
|
152
|
+
type: "text",
|
|
153
|
+
text: JSON.stringify(await this.inspector.getEnvironmentInfo(), null, 2),
|
|
154
|
+
},
|
|
155
|
+
],
|
|
156
|
+
}));
|
|
158
157
|
}
|
|
159
158
|
registerClearLogs() {
|
|
160
159
|
this.server.registerTool("clear_logs", {
|
|
@@ -164,10 +163,14 @@ export class RuntimeLensMCPServer {
|
|
|
164
163
|
}, async () => {
|
|
165
164
|
const result = this.collector.clearLogs();
|
|
166
165
|
return {
|
|
167
|
-
content: [
|
|
166
|
+
content: [
|
|
167
|
+
{
|
|
168
168
|
type: "text",
|
|
169
|
-
text: JSON.stringify({
|
|
170
|
-
|
|
169
|
+
text: JSON.stringify({
|
|
170
|
+
message: `Cleared ${result.cleared} entries`,
|
|
171
|
+
}),
|
|
172
|
+
},
|
|
173
|
+
],
|
|
171
174
|
};
|
|
172
175
|
});
|
|
173
176
|
}
|
|
@@ -176,15 +179,14 @@ export class RuntimeLensMCPServer {
|
|
|
176
179
|
title: "Get Log Statistics",
|
|
177
180
|
description: "Get statistics about collected logs including counts by level and framework.",
|
|
178
181
|
inputSchema: {},
|
|
179
|
-
}, async () => {
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
});
|
|
182
|
+
}, async () => ({
|
|
183
|
+
content: [
|
|
184
|
+
{
|
|
185
|
+
type: "text",
|
|
186
|
+
text: JSON.stringify(this.collector.getStats(), null, 2),
|
|
187
|
+
},
|
|
188
|
+
],
|
|
189
|
+
}));
|
|
188
190
|
}
|
|
189
191
|
registerStartInterceptor() {
|
|
190
192
|
this.server.registerTool("start_interceptor", {
|
|
@@ -194,10 +196,15 @@ export class RuntimeLensMCPServer {
|
|
|
194
196
|
}, async () => {
|
|
195
197
|
this.interceptor.startIntercepting();
|
|
196
198
|
return {
|
|
197
|
-
content: [
|
|
199
|
+
content: [
|
|
200
|
+
{
|
|
198
201
|
type: "text",
|
|
199
|
-
text: JSON.stringify({
|
|
200
|
-
|
|
202
|
+
text: JSON.stringify({
|
|
203
|
+
message: "Console interceptor started",
|
|
204
|
+
active: true,
|
|
205
|
+
}),
|
|
206
|
+
},
|
|
207
|
+
],
|
|
201
208
|
};
|
|
202
209
|
});
|
|
203
210
|
}
|
|
@@ -209,10 +216,15 @@ export class RuntimeLensMCPServer {
|
|
|
209
216
|
}, async () => {
|
|
210
217
|
this.interceptor.stopIntercepting();
|
|
211
218
|
return {
|
|
212
|
-
content: [
|
|
219
|
+
content: [
|
|
220
|
+
{
|
|
213
221
|
type: "text",
|
|
214
|
-
text: JSON.stringify({
|
|
215
|
-
|
|
222
|
+
text: JSON.stringify({
|
|
223
|
+
message: "Console interceptor stopped",
|
|
224
|
+
active: false,
|
|
225
|
+
}),
|
|
226
|
+
},
|
|
227
|
+
],
|
|
216
228
|
};
|
|
217
229
|
});
|
|
218
230
|
}
|
|
@@ -223,12 +235,16 @@ export class RuntimeLensMCPServer {
|
|
|
223
235
|
inputSchema: {},
|
|
224
236
|
}, async () => {
|
|
225
237
|
await this.collector.collectFromFiles();
|
|
226
|
-
const stats = this.collector.getStats();
|
|
227
238
|
return {
|
|
228
|
-
content: [
|
|
239
|
+
content: [
|
|
240
|
+
{
|
|
229
241
|
type: "text",
|
|
230
|
-
text: JSON.stringify({
|
|
231
|
-
|
|
242
|
+
text: JSON.stringify({
|
|
243
|
+
message: "Log collection complete",
|
|
244
|
+
stats: this.collector.getStats(),
|
|
245
|
+
}),
|
|
246
|
+
},
|
|
247
|
+
],
|
|
232
248
|
};
|
|
233
249
|
});
|
|
234
250
|
}
|
|
@@ -237,48 +253,46 @@ export class RuntimeLensMCPServer {
|
|
|
237
253
|
title: "Scan Project Structure",
|
|
238
254
|
description: "Analyze the project to detect framework (React/Next.js/NestJS), find log files, and list configuration files.",
|
|
239
255
|
inputSchema: {},
|
|
240
|
-
}, async () => {
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
});
|
|
256
|
+
}, async () => ({
|
|
257
|
+
content: [
|
|
258
|
+
{
|
|
259
|
+
type: "text",
|
|
260
|
+
text: JSON.stringify(await this.collector.scanProjectStructure(), null, 2),
|
|
261
|
+
},
|
|
262
|
+
],
|
|
263
|
+
}));
|
|
249
264
|
}
|
|
250
265
|
registerFindProcesses() {
|
|
251
266
|
this.server.registerTool("find_processes", {
|
|
252
267
|
title: "Find Running Node Processes",
|
|
253
268
|
description: "Find running Node.js processes related to React, Next.js, or NestJS applications.",
|
|
254
269
|
inputSchema: {},
|
|
255
|
-
}, async () => {
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
});
|
|
270
|
+
}, async () => ({
|
|
271
|
+
content: [
|
|
272
|
+
{
|
|
273
|
+
type: "text",
|
|
274
|
+
text: JSON.stringify(await this.inspector.findRunningProcesses(), null, 2),
|
|
275
|
+
},
|
|
276
|
+
],
|
|
277
|
+
}));
|
|
264
278
|
}
|
|
265
279
|
registerGetPorts() {
|
|
266
280
|
this.server.registerTool("get_listening_ports", {
|
|
267
281
|
title: "Get Listening Ports",
|
|
268
282
|
description: "List all TCP ports currently being listened on by Node.js processes.",
|
|
269
283
|
inputSchema: {},
|
|
270
|
-
}, async () => {
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
});
|
|
284
|
+
}, async () => ({
|
|
285
|
+
content: [
|
|
286
|
+
{
|
|
287
|
+
type: "text",
|
|
288
|
+
text: JSON.stringify(await this.inspector.getPortListeners(), null, 2),
|
|
289
|
+
},
|
|
290
|
+
],
|
|
291
|
+
}));
|
|
279
292
|
}
|
|
280
293
|
async start() {
|
|
281
294
|
try {
|
|
295
|
+
this.agentBridge.connect();
|
|
282
296
|
const transport = new StdioServerTransport();
|
|
283
297
|
await this.server.connect(transport);
|
|
284
298
|
console.error("Runtime Lens MCP Server started successfully");
|
|
@@ -291,6 +305,7 @@ export class RuntimeLensMCPServer {
|
|
|
291
305
|
setupGracefulShutdown() {
|
|
292
306
|
const shutdown = async (signal) => {
|
|
293
307
|
console.error(`Received ${signal}, shutting down gracefully...`);
|
|
308
|
+
this.agentBridge.disconnect();
|
|
294
309
|
this.interceptor.stopIntercepting();
|
|
295
310
|
process.exit(0);
|
|
296
311
|
};
|
package/dist/server.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"server.js","sourceRoot":"","sources":["../src/server.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,yCAAyC,CAAC;AACpE,OAAO,EAAE,oBAAoB,EAAE,MAAM,2CAA2C,CAAC;AACjF,OAAO,EAAE,YAAY,EAAE,MAAM,oBAAoB,CAAC;AAClD,OAAO,EAAE,gBAAgB,EAAE,MAAM,wBAAwB,CAAC;AAC1D,OAAO,EAAE,kBAAkB,EAAE,MAAM,0BAA0B,CAAC;AAC9D,OAAO,
|
|
1
|
+
{"version":3,"file":"server.js","sourceRoot":"","sources":["../src/server.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,yCAAyC,CAAC;AACpE,OAAO,EAAE,oBAAoB,EAAE,MAAM,2CAA2C,CAAC;AACjF,OAAO,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAC;AAChD,OAAO,EAAE,YAAY,EAAE,MAAM,oBAAoB,CAAC;AAClD,OAAO,EAAE,gBAAgB,EAAE,MAAM,wBAAwB,CAAC;AAC1D,OAAO,EAAE,kBAAkB,EAAE,MAAM,0BAA0B,CAAC;AAC9D,OAAO,EACN,qBAAqB,EACrB,0BAA0B,EAC1B,0BAA0B,EAC1B,sBAAsB,EACtB,oBAAoB,GACpB,MAAM,YAAY,CAAC;AAEpB,MAAM,OAAO,oBAAoB;IACf,MAAM,CAAY;IAClB,SAAS,CAAe;IACxB,SAAS,CAAmB;IAC5B,WAAW,CAAqB;IAChC,WAAW,CAAc;IAE1C,YAAY,WAAoB,EAAE,QAAmB;QACpD,IAAI,CAAC,MAAM,GAAG,IAAI,SAAS,CAAC;YAC3B,IAAI,EAAE,kBAAkB;YACxB,OAAO,EAAE,OAAO;SAChB,CAAC,CAAC;QACH,IAAI,CAAC,SAAS,GAAG,IAAI,YAAY,CAAC,WAAW,EAAE,QAAQ,CAAC,CAAC;QACzD,IAAI,CAAC,SAAS,GAAG,IAAI,gBAAgB,CAAC,WAAW,CAAC,CAAC;QACnD,IAAI,CAAC,WAAW,GAAG,IAAI,kBAAkB,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC;QAC1D,IAAI,CAAC,WAAW,GAAG,IAAI,WAAW,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC;QACnD,IAAI,CAAC,UAAU,EAAE,CAAC;IACnB,CAAC;IAED,MAAM,CAAC,eAAe;QACrB,MAAM,WAAW,GAAG,OAAO,CAAC,GAAG,CAAC,yBAAyB,IAAI,OAAO,CAAC,GAAG,EAAE,CAAC;QAC3E,MAAM,QAAQ,GACb,OAAO,CAAC,GAAG,CAAC,sBAAsB,EAAE,KAAK,CAAC,GAAG,CAAC,CAAC,MAAM,CAAC,OAAO,CAAC,IAAI,EAAE,CAAC;QACtE,OAAO,IAAI,oBAAoB,CAAC,WAAW,EAAE,QAAQ,CAAC,CAAC;IACxD,CAAC;IAEO,UAAU;QACjB,IAAI,CAAC,gBAAgB,EAAE,CAAC;QACxB,IAAI,CAAC,kBAAkB,EAAE,CAAC;QAC1B,IAAI,CAAC,iBAAiB,EAAE,CAAC;QACzB,IAAI,CAAC,uBAAuB,EAAE,CAAC;QAC/B,IAAI,CAAC,sBAAsB,EAAE,CAAC;QAC9B,IAAI,CAAC,kBAAkB,EAAE,CAAC;QAC1B,IAAI,CAAC,iBAAiB,EAAE,CAAC;QACzB,IAAI,CAAC,gBAAgB,EAAE,CAAC;QACxB,IAAI,CAAC,wBAAwB,EAAE,CAAC;QAChC,IAAI,CAAC,uBAAuB,EAAE,CAAC;QAC/B,IAAI,CAAC,mBAAmB,EAAE,CAAC;QAC3B,IAAI,CAAC,mBAAmB,EAAE,CAAC;QAC3B,IAAI,CAAC,qBAAqB,EAAE,CAAC;QAC7B,IAAI,CAAC,gBAAgB,EAAE,CAAC;IACzB,CAAC;IAEO,gBAAgB;QACvB,IAAI,CAAC,MAAM,CAAC,YAAY,CACvB,WAAW,EACX;YACC,KAAK,EAAE,WAAW;YAClB,WAAW,EACV,8GAA8G;YAC/G,WAAW,EAAE;gBACZ,KAAK,EAAE,oBAAoB,CAAC,KAAK,CAAC,KAAK;gBACvC,KAAK,EAAE,oBAAoB,CAAC,KAAK,CAAC,KAAK;gBACvC,SAAS,EAAE,oBAAoB,CAAC,KAAK,CAAC,SAAS;gBAC/C,MAAM,EAAE,oBAAoB,CAAC,KAAK,CAAC,MAAM;aACzC;SACD,EACD,KAAK,EAAE,MAAM,EAAE,EAAE,CAAC,CAAC;YAClB,OAAO,EAAE;gBACR;oBACC,IAAI,EAAE,MAAe;oBACrB,IAAI,EAAE,IAAI,CAAC,SAAS,CAAC,IAAI,CAAC,SAAS,CAAC,OAAO,CAAC,MAAM,CAAC,EAAE,IAAI,EAAE,CAAC,CAAC;iBAC7D;aACD;SACD,CAAC,CACF,CAAC;IACH,CAAC;IAEO,kBAAkB;QACzB,IAAI,CAAC,MAAM,CAAC,YAAY,CACvB,aAAa,EACb;YACC,KAAK,EAAE,aAAa;YACpB,WAAW,EACV,iGAAiG;YAClG,WAAW,EAAE;gBACZ,KAAK,EAAE,sBAAsB,CAAC,KAAK,CAAC,KAAK;gBACzC,KAAK,EAAE,sBAAsB,CAAC,KAAK,CAAC,KAAK;gBACzC,SAAS,EAAE,sBAAsB,CAAC,KAAK,CAAC,SAAS;gBACjD,KAAK,EAAE,sBAAsB,CAAC,KAAK,CAAC,KAAK;gBACzC,KAAK,EAAE,sBAAsB,CAAC,KAAK,CAAC,KAAK;aACzC;SACD,EACD,KAAK,EAAE,MAAM,EAAE,EAAE,CAAC,CAAC;YAClB,OAAO,EAAE;gBACR;oBACC,IAAI,EAAE,MAAe;oBACrB,IAAI,EAAE,IAAI,CAAC,SAAS,CACnB,IAAI,CAAC,SAAS,CAAC,UAAU,CAAC,MAAa,CAAC,EACxC,IAAI,EACJ,CAAC,CACD;iBACD;aACD;SACD,CAAC,CACF,CAAC;IACH,CAAC;IAEO,iBAAiB;QACxB,IAAI,CAAC,MAAM,CAAC,YAAY,CACvB,YAAY,EACZ;YACC,KAAK,EAAE,YAAY;YACnB,WAAW,EACV,0GAA0G;YAC3G,WAAW,EAAE;gBACZ,KAAK,EAAE,qBAAqB,CAAC,KAAK,CAAC,KAAK;gBACxC,SAAS,EAAE,qBAAqB,CAAC,KAAK,CAAC,SAAS;gBAChD,OAAO,EAAE,qBAAqB,CAAC,KAAK,CAAC,OAAO;aAC5C;SACD,EACD,KAAK,EAAE,MAAM,EAAE,EAAE,CAAC,CAAC;YAClB,OAAO,EAAE;gBACR;oBACC,IAAI,EAAE,MAAe;oBACrB,IAAI,EAAE,IAAI,CAAC,SAAS,CAAC,IAAI,CAAC,SAAS,CAAC,SAAS,CAAC,MAAM,CAAC,EAAE,IAAI,EAAE,CAAC,CAAC;iBAC/D;aACD;SACD,CAAC,CACF,CAAC;IACH,CAAC;IAEO,uBAAuB;QAC9B,IAAI,CAAC,MAAM,CAAC,YAAY,CACvB,kBAAkB,EAClB;YACC,KAAK,EAAE,uBAAuB;YAC9B,WAAW,EACV,mHAAmH;YACpH,WAAW,EAAE;gBACZ,EAAE,EAAE,0BAA0B,CAAC,KAAK,CAAC,EAAE;gBACvC,MAAM,EAAE,0BAA0B,CAAC,KAAK,CAAC,MAAM;gBAC/C,UAAU,EAAE,0BAA0B,CAAC,KAAK,CAAC,UAAU;gBACvD,UAAU,EAAE,0BAA0B,CAAC,KAAK,CAAC,UAAU;gBACvD,KAAK,EAAE,0BAA0B,CAAC,KAAK,CAAC,KAAK;aAC7C;SACD,EACD,KAAK,EAAE,MAAM,EAAE,EAAE,CAAC,CAAC;YAClB,OAAO,EAAE;gBACR;oBACC,IAAI,EAAE,MAAe;oBACrB,IAAI,EAAE,IAAI,CAAC,SAAS,CAAC,IAAI,CAAC,SAAS,CAAC,WAAW,CAAC,MAAM,CAAC,EAAE,IAAI,EAAE,CAAC,CAAC;iBACjE;aACD;SACD,CAAC,CACF,CAAC;IACH,CAAC;IAEO,sBAAsB;QAC7B,IAAI,CAAC,MAAM,CAAC,YAAY,CACvB,iBAAiB,EACjB;YACC,KAAK,EAAE,yBAAyB;YAChC,WAAW,EACV,oGAAoG;YACrG,WAAW,EAAE;gBACZ,MAAM,EAAE,0BAA0B,CAAC,KAAK,CAAC,MAAM;gBAC/C,KAAK,EAAE,0BAA0B,CAAC,KAAK,CAAC,KAAK;gBAC7C,KAAK,EAAE,0BAA0B,CAAC,KAAK,CAAC,KAAK;aAC7C;SACD,EACD,KAAK,EAAE,MAAM,EAAE,EAAE;YAChB,MAAM,IAAI,CAAC,SAAS,CAAC,kBAAkB,EAAE,CAAC;YAC1C,OAAO;gBACN,OAAO,EAAE;oBACR;wBACC,IAAI,EAAE,MAAe;wBACrB,IAAI,EAAE,IAAI,CAAC,SAAS,CAAC,IAAI,CAAC,SAAS,CAAC,UAAU,CAAC,MAAM,CAAC,EAAE,IAAI,EAAE,CAAC,CAAC;qBAChE;iBACD;aACD,CAAC;QACH,CAAC,CACD,CAAC;IACH,CAAC;IAEO,kBAAkB;QACzB,IAAI,CAAC,MAAM,CAAC,YAAY,CACvB,cAAc,EACd;YACC,KAAK,EAAE,sBAAsB;YAC7B,WAAW,EACV,oJAAoJ;YACrJ,WAAW,EAAE,EAAE;SACf,EACD,KAAK,IAAI,EAAE,CAAC,CAAC;YACZ,OAAO,EAAE;gBACR;oBACC,IAAI,EAAE,MAAe;oBACrB,IAAI,EAAE,IAAI,CAAC,SAAS,CACnB,MAAM,IAAI,CAAC,SAAS,CAAC,kBAAkB,EAAE,EACzC,IAAI,EACJ,CAAC,CACD;iBACD;aACD;SACD,CAAC,CACF,CAAC;IACH,CAAC;IAEO,iBAAiB;QACxB,IAAI,CAAC,MAAM,CAAC,YAAY,CACvB,YAAY,EACZ;YACC,KAAK,EAAE,kBAAkB;YACzB,WAAW,EACV,kEAAkE;YACnE,WAAW,EAAE,EAAE;SACf,EACD,KAAK,IAAI,EAAE;YACV,MAAM,MAAM,GAAG,IAAI,CAAC,SAAS,CAAC,SAAS,EAAE,CAAC;YAC1C,OAAO;gBACN,OAAO,EAAE;oBACR;wBACC,IAAI,EAAE,MAAe;wBACrB,IAAI,EAAE,IAAI,CAAC,SAAS,CAAC;4BACpB,OAAO,EAAE,WAAW,MAAM,CAAC,OAAO,UAAU;yBAC5C,CAAC;qBACF;iBACD;aACD,CAAC;QACH,CAAC,CACD,CAAC;IACH,CAAC;IAEO,gBAAgB;QACvB,IAAI,CAAC,MAAM,CAAC,YAAY,CACvB,WAAW,EACX;YACC,KAAK,EAAE,oBAAoB;YAC3B,WAAW,EACV,8EAA8E;YAC/E,WAAW,EAAE,EAAE;SACf,EACD,KAAK,IAAI,EAAE,CAAC,CAAC;YACZ,OAAO,EAAE;gBACR;oBACC,IAAI,EAAE,MAAe;oBACrB,IAAI,EAAE,IAAI,CAAC,SAAS,CAAC,IAAI,CAAC,SAAS,CAAC,QAAQ,EAAE,EAAE,IAAI,EAAE,CAAC,CAAC;iBACxD;aACD;SACD,CAAC,CACF,CAAC;IACH,CAAC;IAEO,wBAAwB;QAC/B,IAAI,CAAC,MAAM,CAAC,YAAY,CACvB,mBAAmB,EACnB;YACC,KAAK,EAAE,2BAA2B;YAClC,WAAW,EACV,oIAAoI;YACrI,WAAW,EAAE,EAAE;SACf,EACD,KAAK,IAAI,EAAE;YACV,IAAI,CAAC,WAAW,CAAC,iBAAiB,EAAE,CAAC;YACrC,OAAO;gBACN,OAAO,EAAE;oBACR;wBACC,IAAI,EAAE,MAAe;wBACrB,IAAI,EAAE,IAAI,CAAC,SAAS,CAAC;4BACpB,OAAO,EAAE,6BAA6B;4BACtC,MAAM,EAAE,IAAI;yBACZ,CAAC;qBACF;iBACD;aACD,CAAC;QACH,CAAC,CACD,CAAC;IACH,CAAC;IAEO,uBAAuB;QAC9B,IAAI,CAAC,MAAM,CAAC,YAAY,CACvB,kBAAkB,EAClB;YACC,KAAK,EAAE,0BAA0B;YACjC,WAAW,EACV,kFAAkF;YACnF,WAAW,EAAE,EAAE;SACf,EACD,KAAK,IAAI,EAAE;YACV,IAAI,CAAC,WAAW,CAAC,gBAAgB,EAAE,CAAC;YACpC,OAAO;gBACN,OAAO,EAAE;oBACR;wBACC,IAAI,EAAE,MAAe;wBACrB,IAAI,EAAE,IAAI,CAAC,SAAS,CAAC;4BACpB,OAAO,EAAE,6BAA6B;4BACtC,MAAM,EAAE,KAAK;yBACb,CAAC;qBACF;iBACD;aACD,CAAC;QACH,CAAC,CACD,CAAC;IACH,CAAC;IAEO,mBAAmB;QAC1B,IAAI,CAAC,MAAM,CAAC,YAAY,CACvB,oBAAoB,EACpB;YACC,KAAK,EAAE,yBAAyB;YAChC,WAAW,EACV,wIAAwI;YACzI,WAAW,EAAE,EAAE;SACf,EACD,KAAK,IAAI,EAAE;YACV,MAAM,IAAI,CAAC,SAAS,CAAC,gBAAgB,EAAE,CAAC;YACxC,OAAO;gBACN,OAAO,EAAE;oBACR;wBACC,IAAI,EAAE,MAAe;wBACrB,IAAI,EAAE,IAAI,CAAC,SAAS,CAAC;4BACpB,OAAO,EAAE,yBAAyB;4BAClC,KAAK,EAAE,IAAI,CAAC,SAAS,CAAC,QAAQ,EAAE;yBAChC,CAAC;qBACF;iBACD;aACD,CAAC;QACH,CAAC,CACD,CAAC;IACH,CAAC;IAEO,mBAAmB;QAC1B,IAAI,CAAC,MAAM,CAAC,YAAY,CACvB,cAAc,EACd;YACC,KAAK,EAAE,wBAAwB;YAC/B,WAAW,EACV,+GAA+G;YAChH,WAAW,EAAE,EAAE;SACf,EACD,KAAK,IAAI,EAAE,CAAC,CAAC;YACZ,OAAO,EAAE;gBACR;oBACC,IAAI,EAAE,MAAe;oBACrB,IAAI,EAAE,IAAI,CAAC,SAAS,CACnB,MAAM,IAAI,CAAC,SAAS,CAAC,oBAAoB,EAAE,EAC3C,IAAI,EACJ,CAAC,CACD;iBACD;aACD;SACD,CAAC,CACF,CAAC;IACH,CAAC;IAEO,qBAAqB;QAC5B,IAAI,CAAC,MAAM,CAAC,YAAY,CACvB,gBAAgB,EAChB;YACC,KAAK,EAAE,6BAA6B;YACpC,WAAW,EACV,mFAAmF;YACpF,WAAW,EAAE,EAAE;SACf,EACD,KAAK,IAAI,EAAE,CAAC,CAAC;YACZ,OAAO,EAAE;gBACR;oBACC,IAAI,EAAE,MAAe;oBACrB,IAAI,EAAE,IAAI,CAAC,SAAS,CACnB,MAAM,IAAI,CAAC,SAAS,CAAC,oBAAoB,EAAE,EAC3C,IAAI,EACJ,CAAC,CACD;iBACD;aACD;SACD,CAAC,CACF,CAAC;IACH,CAAC;IAEO,gBAAgB;QACvB,IAAI,CAAC,MAAM,CAAC,YAAY,CACvB,qBAAqB,EACrB;YACC,KAAK,EAAE,qBAAqB;YAC5B,WAAW,EACV,sEAAsE;YACvE,WAAW,EAAE,EAAE;SACf,EACD,KAAK,IAAI,EAAE,CAAC,CAAC;YACZ,OAAO,EAAE;gBACR;oBACC,IAAI,EAAE,MAAe;oBACrB,IAAI,EAAE,IAAI,CAAC,SAAS,CACnB,MAAM,IAAI,CAAC,SAAS,CAAC,gBAAgB,EAAE,EACvC,IAAI,EACJ,CAAC,CACD;iBACD;aACD;SACD,CAAC,CACF,CAAC;IACH,CAAC;IAED,KAAK,CAAC,KAAK;QACV,IAAI,CAAC;YACJ,IAAI,CAAC,WAAW,CAAC,OAAO,EAAE,CAAC;YAC3B,MAAM,SAAS,GAAG,IAAI,oBAAoB,EAAE,CAAC;YAC7C,MAAM,IAAI,CAAC,MAAM,CAAC,OAAO,CAAC,SAAS,CAAC,CAAC;YACrC,OAAO,CAAC,KAAK,CAAC,8CAA8C,CAAC,CAAC;QAC/D,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YAChB,OAAO,CAAC,KAAK,CACZ,0CAA0C,EAC1C,KAAK,YAAY,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,KAAK,CAC9C,CAAC;YACF,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;QACjB,CAAC;IACF,CAAC;IAED,qBAAqB;QACpB,MAAM,QAAQ,GAAG,KAAK,EAAE,MAAc,EAAiB,EAAE;YACxD,OAAO,CAAC,KAAK,CAAC,YAAY,MAAM,+BAA+B,CAAC,CAAC;YACjE,IAAI,CAAC,WAAW,CAAC,UAAU,EAAE,CAAC;YAC9B,IAAI,CAAC,WAAW,CAAC,gBAAgB,EAAE,CAAC;YACpC,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;QACjB,CAAC,CAAC;QACF,OAAO,CAAC,EAAE,CAAC,QAAQ,EAAE,GAAG,EAAE,CAAC,QAAQ,CAAC,QAAQ,CAAC,CAAC,CAAC;QAC/C,OAAO,CAAC,EAAE,CAAC,SAAS,EAAE,GAAG,EAAE,CAAC,QAAQ,CAAC,SAAS,CAAC,CAAC,CAAC;IAClD,CAAC;CACD"}
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@arvoretech/runtime-lens-mcp",
|
|
3
3
|
"displayName": "Runtime Lens",
|
|
4
|
-
"version": "1.
|
|
4
|
+
"version": "1.2.0",
|
|
5
5
|
"description": "Runtime Lens - Runtime inspection with inline values for React, NestJS and Next.js",
|
|
6
6
|
"publisher": "arvore",
|
|
7
7
|
"main": "dist/extension/extension.js",
|
|
@@ -29,7 +29,7 @@
|
|
|
29
29
|
"dev": "tsx src/index.ts",
|
|
30
30
|
"start": "node dist/index.js",
|
|
31
31
|
"package": "pnpm build:all && vsce package --no-dependencies",
|
|
32
|
-
"prepublishOnly": "node -e \"const p=require('./package.json');p.name='@arvoretech/runtime-lens-mcp';require('fs').writeFileSync('package.json',JSON.stringify(p,null,2)+'\\n')\"",
|
|
32
|
+
"prepublishOnly": "pnpm build:all && node -e \"const p=require('./package.json');p.name='@arvoretech/runtime-lens-mcp';require('fs').writeFileSync('package.json',JSON.stringify(p,null,2)+'\\n')\"",
|
|
33
33
|
"postpublish": "node -e \"const p=require('./package.json');p.name='runtime-lens';require('fs').writeFileSync('package.json',JSON.stringify(p,null,2)+'\\n')\"",
|
|
34
34
|
"test": "vitest",
|
|
35
35
|
"test:cov": "vitest --coverage",
|
|
@@ -0,0 +1,218 @@
|
|
|
1
|
+
import { randomBytes } from "node:crypto";
|
|
2
|
+
import { request } from "node:http";
|
|
3
|
+
import type { Socket } from "node:net";
|
|
4
|
+
import type { LogCollector } from "./log-collector.js";
|
|
5
|
+
|
|
6
|
+
interface AgentMessage {
|
|
7
|
+
type: "log" | "error" | "warn" | "info" | "debug" | "result" | "buffer";
|
|
8
|
+
file: string;
|
|
9
|
+
line: number;
|
|
10
|
+
column: number;
|
|
11
|
+
values: string[];
|
|
12
|
+
timestamp: number;
|
|
13
|
+
expression?: string;
|
|
14
|
+
logs?: AgentMessage[];
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
const LEVEL_MAP: Record<string, "debug" | "info" | "warn" | "error" | "fatal"> =
|
|
18
|
+
{
|
|
19
|
+
log: "info",
|
|
20
|
+
info: "info",
|
|
21
|
+
warn: "warn",
|
|
22
|
+
error: "error",
|
|
23
|
+
debug: "debug",
|
|
24
|
+
result: "info",
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
export class AgentBridge {
|
|
28
|
+
private socket: Socket | null = null;
|
|
29
|
+
private reconnectTimer: ReturnType<typeof setTimeout> | null = null;
|
|
30
|
+
private connected = false;
|
|
31
|
+
private readonly port: number;
|
|
32
|
+
private readonly collector: LogCollector;
|
|
33
|
+
private dataBuffer: Buffer = Buffer.alloc(0);
|
|
34
|
+
private stopped = false;
|
|
35
|
+
|
|
36
|
+
constructor(collector: LogCollector, port = 9500) {
|
|
37
|
+
this.collector = collector;
|
|
38
|
+
this.port = parseInt(process.env.RUNTIME_LENS_PORT || String(port), 10);
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
connect(): void {
|
|
42
|
+
if (this.socket || this.stopped) return;
|
|
43
|
+
|
|
44
|
+
const key = randomBytes(16).toString("base64");
|
|
45
|
+
|
|
46
|
+
const req = request({
|
|
47
|
+
hostname: "localhost",
|
|
48
|
+
port: this.port,
|
|
49
|
+
path: "/",
|
|
50
|
+
method: "GET",
|
|
51
|
+
timeout: 3000,
|
|
52
|
+
headers: {
|
|
53
|
+
Upgrade: "websocket",
|
|
54
|
+
Connection: "Upgrade",
|
|
55
|
+
"Sec-WebSocket-Key": key,
|
|
56
|
+
"Sec-WebSocket-Version": "13",
|
|
57
|
+
},
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
req.on("upgrade", (_res, socket) => {
|
|
61
|
+
this.socket = socket;
|
|
62
|
+
this.connected = true;
|
|
63
|
+
this.dataBuffer = Buffer.alloc(0);
|
|
64
|
+
console.error("[runtime-lens-mcp] Connected to agent on port", this.port);
|
|
65
|
+
|
|
66
|
+
// Request buffered logs
|
|
67
|
+
this.sendFrame(JSON.stringify({ type: "get_buffer" }));
|
|
68
|
+
|
|
69
|
+
socket.on("data", (data: Buffer) => {
|
|
70
|
+
this.dataBuffer = Buffer.concat([this.dataBuffer, data]);
|
|
71
|
+
this.processFrames();
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
socket.on("close", () => {
|
|
75
|
+
this.connected = false;
|
|
76
|
+
this.socket = null;
|
|
77
|
+
this.scheduleReconnect();
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
socket.on("error", () => {
|
|
81
|
+
this.connected = false;
|
|
82
|
+
this.socket = null;
|
|
83
|
+
});
|
|
84
|
+
});
|
|
85
|
+
|
|
86
|
+
req.on("error", () => {
|
|
87
|
+
this.scheduleReconnect();
|
|
88
|
+
});
|
|
89
|
+
|
|
90
|
+
req.on("timeout", () => {
|
|
91
|
+
req.destroy();
|
|
92
|
+
this.scheduleReconnect();
|
|
93
|
+
});
|
|
94
|
+
|
|
95
|
+
req.end();
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
disconnect(): void {
|
|
99
|
+
this.stopped = true;
|
|
100
|
+
if (this.reconnectTimer) {
|
|
101
|
+
clearTimeout(this.reconnectTimer);
|
|
102
|
+
this.reconnectTimer = null;
|
|
103
|
+
}
|
|
104
|
+
if (this.socket) {
|
|
105
|
+
this.socket.end();
|
|
106
|
+
this.socket = null;
|
|
107
|
+
}
|
|
108
|
+
this.connected = false;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
isConnected(): boolean {
|
|
112
|
+
return this.connected;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
private processFrames(): void {
|
|
116
|
+
while (this.dataBuffer.length >= 2) {
|
|
117
|
+
const firstByte = this.dataBuffer[0];
|
|
118
|
+
const secondByte = this.dataBuffer[1];
|
|
119
|
+
const isFin = (firstByte & 0x80) !== 0;
|
|
120
|
+
const opcode = firstByte & 0x0f;
|
|
121
|
+
|
|
122
|
+
if (opcode === 0x08) {
|
|
123
|
+
this.socket?.end();
|
|
124
|
+
return;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
let payloadLen = secondByte & 0x7f;
|
|
128
|
+
let headerLen = 2;
|
|
129
|
+
|
|
130
|
+
if (payloadLen === 126) {
|
|
131
|
+
if (this.dataBuffer.length < 4) return;
|
|
132
|
+
payloadLen = this.dataBuffer.readUInt16BE(2);
|
|
133
|
+
headerLen = 4;
|
|
134
|
+
} else if (payloadLen === 127) {
|
|
135
|
+
if (this.dataBuffer.length < 10) return;
|
|
136
|
+
payloadLen = Number(this.dataBuffer.readBigUInt64BE(2));
|
|
137
|
+
headerLen = 10;
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
const totalLen = headerLen + payloadLen;
|
|
141
|
+
if (this.dataBuffer.length < totalLen) return;
|
|
142
|
+
|
|
143
|
+
const payload = this.dataBuffer.subarray(headerLen, totalLen);
|
|
144
|
+
this.dataBuffer = this.dataBuffer.subarray(totalLen);
|
|
145
|
+
|
|
146
|
+
if (isFin && (opcode === 0x01 || opcode === 0x02)) {
|
|
147
|
+
const text = payload.toString("utf-8");
|
|
148
|
+
try {
|
|
149
|
+
const msg: AgentMessage = JSON.parse(text);
|
|
150
|
+
this.handleMessage(msg);
|
|
151
|
+
} catch {
|
|
152
|
+
// invalid JSON
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
private handleMessage(msg: AgentMessage): void {
|
|
159
|
+
if (msg.type === "buffer" && msg.logs) {
|
|
160
|
+
for (const log of msg.logs) {
|
|
161
|
+
this.ingestLog(log);
|
|
162
|
+
}
|
|
163
|
+
return;
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
this.ingestLog(msg);
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
private ingestLog(msg: AgentMessage): void {
|
|
170
|
+
const level = LEVEL_MAP[msg.type] || "info";
|
|
171
|
+
this.collector.addLog({
|
|
172
|
+
level,
|
|
173
|
+
message: msg.values.join(", "),
|
|
174
|
+
source: msg.file || "agent",
|
|
175
|
+
framework: "unknown",
|
|
176
|
+
metadata: {
|
|
177
|
+
line: msg.line,
|
|
178
|
+
column: msg.column,
|
|
179
|
+
agentType: msg.type,
|
|
180
|
+
expression: msg.expression,
|
|
181
|
+
originalTimestamp: msg.timestamp,
|
|
182
|
+
},
|
|
183
|
+
});
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
private sendFrame(text: string): void {
|
|
187
|
+
if (!this.socket) return;
|
|
188
|
+
const data = Buffer.from(text, "utf-8");
|
|
189
|
+
const len = data.length;
|
|
190
|
+
let header: Buffer;
|
|
191
|
+
|
|
192
|
+
if (len < 126) {
|
|
193
|
+
header = Buffer.alloc(2);
|
|
194
|
+
header[0] = 0x81;
|
|
195
|
+
header[1] = len;
|
|
196
|
+
} else if (len < 65536) {
|
|
197
|
+
header = Buffer.alloc(4);
|
|
198
|
+
header[0] = 0x81;
|
|
199
|
+
header[1] = 126;
|
|
200
|
+
header.writeUInt16BE(len, 2);
|
|
201
|
+
} else {
|
|
202
|
+
header = Buffer.alloc(10);
|
|
203
|
+
header[0] = 0x81;
|
|
204
|
+
header[1] = 127;
|
|
205
|
+
header.writeBigUInt64BE(BigInt(len), 2);
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
this.socket.write(Buffer.concat([header, data]));
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
private scheduleReconnect(): void {
|
|
212
|
+
if (this.reconnectTimer || this.stopped) return;
|
|
213
|
+
this.reconnectTimer = setTimeout(() => {
|
|
214
|
+
this.reconnectTimer = null;
|
|
215
|
+
this.connect();
|
|
216
|
+
}, 5000);
|
|
217
|
+
}
|
|
218
|
+
}
|