agent-master-devsecops 0.0.0-stage → 5.1.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 +204 -2
- package/agents/AGENT_DEVSECOPS.md +32 -0
- package/agents/AGENT_INFRA.md +31 -0
- package/agents/AGENT_ORCHESTRATOR.md +39 -0
- package/agents/AGENT_QA.md +58 -0
- package/agents/AGENT_SCRUM.md +23 -0
- package/agents/AGENT_SRE.md +22 -0
- package/agents/AGENT_UIUX.md +26 -0
- package/docs/ARCHITECTURE.md +152 -0
- package/docs/CALCULATIONS.md +17 -0
- package/docs/CHANGELOG.md +112 -0
- package/docs/ENGINEERING_RULES.md +56 -0
- package/docs/IDEAS.md +17 -0
- package/docs/LOCAL_BACKLOG.md +20 -0
- package/docs/STATE.md +111 -0
- package/docs/playbooks/PLAYBOOK_CIRCUIT_BREAKER_LLM.md +58 -0
- package/docs/playbooks/PLAYBOOK_OPENTELEMETRY_TRACING.md +58 -0
- package/package.json +57 -4
- package/scripts/auditors/a11y_auditor.js +462 -0
- package/scripts/auditors/code_quality_auditor.js +444 -0
- package/scripts/auditors/contract_test_validator.js +396 -0
- package/scripts/auditors/eval_harness.js +213 -0
- package/scripts/auditors/git_hygiene_linter.js +391 -0
- package/scripts/auditors/pr_reviewer.js +627 -0
- package/scripts/auditors/sast_scanner.js +442 -0
- package/scripts/auditors/sca_dependency_auditor.js +673 -0
- package/scripts/bin/bootstrap_cli.js +597 -0
- package/scripts/bin/domain_idea_miner.js +658 -0
- package/scripts/bin/smart_onboarding.js +905 -0
- package/scripts/bin/web_inspector.js +578 -0
- package/scripts/core/architecture_advisor.js +378 -0
- package/scripts/core/ast_skeletonizer.js +413 -0
- package/scripts/core/autonomous_remediation_engine.js +305 -0
- package/scripts/core/circuit_breaker.js +546 -0
- package/scripts/core/context_compressor.js +237 -0
- package/scripts/core/fast_feature_scaffold.js +840 -0
- package/scripts/core/hot_state_cache.js +238 -0
- package/scripts/core/knowledge_gateway.js +888 -0
- package/scripts/core/magic_prompt_generator.js +250 -0
- package/scripts/core/multi_agent_orchestrator.js +421 -0
- package/scripts/core/otel_tracer.js +335 -0
- package/scripts/core/perf_benchmark.js +212 -0
- package/scripts/core/sarif_engine.js +299 -0
- package/scripts/core/sre_remediation_engine.js +236 -0
- package/scripts/core/universal_packager.js +357 -0
- package/scripts/integrations/atlassian_client.js +600 -0
- package/scripts/integrations/atlassian_sync_action.js +576 -0
- package/scripts/integrations/confluence_sync.js +684 -0
- package/scripts/integrations/credentials_guard.js +500 -0
- package/scripts/integrations/jira_manager.js +722 -0
- package/scripts/mcp/handlers/atlassian_handlers.js +300 -0
- package/scripts/mcp/handlers/core_handlers.js +419 -0
- package/scripts/mcp/handlers/qa_handlers.js +502 -0
- package/scripts/mcp/handlers/rules_handlers.js +379 -0
- package/scripts/mcp/handlers/scaffold_handlers.js +334 -0
- package/scripts/mcp/handlers/sre_handlers.js +443 -0
- package/scripts/mcp/resources_registry.js +453 -0
- package/scripts/mcp/server.js +408 -0
- package/scripts/mcp/tool_handlers.js +444 -0
- package/scripts/mcp/tools_registry.js +1123 -0
- package/scripts/mcp_server.js +236 -0
|
@@ -0,0 +1,578 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* scripts/bin/web_inspector.js
|
|
4
|
+
*
|
|
5
|
+
* Web Dashboard & Playground Interativo Local (v5.1 Enterprise - AGDEV-28 / I-14).
|
|
6
|
+
* Servidor HTTP leve em Node.js puro (sem frameworks pesados) para inspecionar
|
|
7
|
+
* ferramentas MCP, executar chamadas interativas, copiar prompts, rodar o harness
|
|
8
|
+
* e auditar telemetria de runtime com conformidade industrial OASIS SARIF v2.1.0.
|
|
9
|
+
*/
|
|
10
|
+
'use strict';
|
|
11
|
+
|
|
12
|
+
const http = require('node:http');
|
|
13
|
+
const fs = require('node:fs');
|
|
14
|
+
const path = require('node:path');
|
|
15
|
+
const os = require('node:os');
|
|
16
|
+
|
|
17
|
+
const ROOT_DIR = path.resolve(__dirname, '../..');
|
|
18
|
+
const { version: PKG_VERSION } = require('../../package.json');
|
|
19
|
+
const { processMessage, TOOLS } = require('../mcp_server.js');
|
|
20
|
+
const { MagicPromptGenerator } = require('../core/magic_prompt_generator.js');
|
|
21
|
+
const { evaluateResponse } = require('../auditors/eval_harness.js');
|
|
22
|
+
const { createSarifReport } = require('../core/sarif_engine.js');
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Coleta telemetria em tempo real do processo Node.js e do ecossistema de ferramentas MCP.
|
|
26
|
+
* @param {object} [options={}]
|
|
27
|
+
* @returns {object}
|
|
28
|
+
*/
|
|
29
|
+
function getTelemetry(options = {}) {
|
|
30
|
+
const mem = process.memoryUsage();
|
|
31
|
+
const heapUsed = (mem.heapUsed / 1024 / 1024).toFixed(2);
|
|
32
|
+
const heapTotal = (mem.heapTotal / 1024 / 1024).toFixed(2);
|
|
33
|
+
const rss = (mem.rss / 1024 / 1024).toFixed(2);
|
|
34
|
+
const toolsCount = Array.isArray(TOOLS) ? TOOLS.length : 0;
|
|
35
|
+
|
|
36
|
+
return Object.freeze({
|
|
37
|
+
nodeVersion: process.version,
|
|
38
|
+
platform: process.platform,
|
|
39
|
+
arch: process.arch,
|
|
40
|
+
uptimeSeconds: Math.floor(process.uptime()),
|
|
41
|
+
heapUsedMB: heapUsed,
|
|
42
|
+
heapTotalMB: heapTotal,
|
|
43
|
+
rssMB: rss,
|
|
44
|
+
toolsCount,
|
|
45
|
+
status: 'UP'
|
|
46
|
+
});
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* Formata relatório executivo em Markdown da telemetria e servidor web.
|
|
51
|
+
* @param {object} [telemetry]
|
|
52
|
+
* @returns {string}
|
|
53
|
+
*/
|
|
54
|
+
function formatReport(telemetry = getTelemetry()) {
|
|
55
|
+
const t = (telemetry && typeof telemetry === 'object') ? telemetry : getTelemetry();
|
|
56
|
+
let output = '# 🌐 Agent-Master DevSecOps — Web Inspector Report\n\n';
|
|
57
|
+
output += `**Status:** \`${t.status || 'UP'}\`\n`;
|
|
58
|
+
output += `**Versão do Pacote:** \`v${PKG_VERSION}\`\n`;
|
|
59
|
+
output += `**Runtime:** \`Node.js ${t.nodeVersion || process.version}\` (${t.platform || process.platform} ${t.arch || process.arch})\n`;
|
|
60
|
+
output += `**Tempo de Atividade (Uptime):** \`${t.uptimeSeconds || 0}s\`\n`;
|
|
61
|
+
output += `**Ferramentas MCP Ativas:** \`${t.toolsCount || 0}\`\n\n`;
|
|
62
|
+
|
|
63
|
+
output += '### 📊 Consumo de Recursos de Memória\n';
|
|
64
|
+
output += `- **Heap V8 Usada:** \`${t.heapUsedMB || 0} MB\`\n`;
|
|
65
|
+
output += `- **Heap V8 Total:** \`${t.heapTotalMB || 0} MB\`\n`;
|
|
66
|
+
output += `- **Memória RSS Alocada:** \`${t.rssMB || 0} MB\`\n\n`;
|
|
67
|
+
|
|
68
|
+
output += '### 🔌 Endpoints Oficiais do Servidor Local\n';
|
|
69
|
+
output += '- `GET /` — Web Dashboard & Playground Interativo\n';
|
|
70
|
+
output += '- `GET /api/status` — Telemetria de Runtime & Catálogo de Ferramentas\n';
|
|
71
|
+
output += '- `POST /api/mcp` — Proxy JSON-RPC 2.0 para Ferramentas MCP\n';
|
|
72
|
+
output += '- `POST /api/eval` — Benchmark Empírico contra os 10 Princípios Canônicos\n';
|
|
73
|
+
output += '- `GET /api/magic-prompt` — Gerador de Magic Prompt & Bookmarklet\n';
|
|
74
|
+
|
|
75
|
+
return output;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
/**
|
|
79
|
+
* Exporta a auditoria de telemetria de runtime no padrão oficial OASIS SARIF v2.1.0.
|
|
80
|
+
* @param {object} [telemetry]
|
|
81
|
+
* @returns {object} SARIF v2.1.0 report
|
|
82
|
+
*/
|
|
83
|
+
function auditTelemetryToSarif(telemetry = getTelemetry()) {
|
|
84
|
+
const t = (telemetry && typeof telemetry === 'object') ? telemetry : getTelemetry();
|
|
85
|
+
const findings = [];
|
|
86
|
+
|
|
87
|
+
const heapNum = parseFloat(t.heapUsedMB) || 0;
|
|
88
|
+
if (heapNum > 500) {
|
|
89
|
+
findings.push({
|
|
90
|
+
ruleId: 'AGDEV-PERF-HIGH-HEAP',
|
|
91
|
+
name: 'Uso de Memória Heap Elevado',
|
|
92
|
+
severity: 'MEDIUM',
|
|
93
|
+
message: `Heap V8 consumida (${heapNum} MB) acima do limite recomendado para microsserviços locais (500 MB).`,
|
|
94
|
+
filename: 'process.memoryUsage',
|
|
95
|
+
line: 1,
|
|
96
|
+
column: 1
|
|
97
|
+
});
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
const rules = [
|
|
101
|
+
{
|
|
102
|
+
id: 'AGDEV-PERF-HIGH-HEAP',
|
|
103
|
+
name: 'Uso de Memória Heap Elevado',
|
|
104
|
+
severity: 'MEDIUM',
|
|
105
|
+
description: 'O consumo de memória da VM do Node.js está acima do recomendado.',
|
|
106
|
+
remediation: 'Execute coleta de lixo ou reinicie o servidor local para liberar memória retida.'
|
|
107
|
+
}
|
|
108
|
+
];
|
|
109
|
+
|
|
110
|
+
return createSarifReport({
|
|
111
|
+
toolName: 'agent-master-web-inspector',
|
|
112
|
+
toolVersion: '5.1.0',
|
|
113
|
+
rules,
|
|
114
|
+
findings,
|
|
115
|
+
properties: {
|
|
116
|
+
category: 'Runtime Telemetry & Performance',
|
|
117
|
+
heapUsedMB: t.heapUsedMB,
|
|
118
|
+
toolsCount: t.toolsCount
|
|
119
|
+
}
|
|
120
|
+
});
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
/**
|
|
124
|
+
* Renderiza o HTML do Dashboard interativo v5.1.
|
|
125
|
+
* @returns {string}
|
|
126
|
+
*/
|
|
127
|
+
function renderHtmlDashboard() {
|
|
128
|
+
const telemetry = getTelemetry();
|
|
129
|
+
const magicGen = new MagicPromptGenerator(ROOT_DIR);
|
|
130
|
+
const bookmarklet = magicGen.generateBookmarklet();
|
|
131
|
+
const magicPrompt = magicGen.generateMagicPrompt();
|
|
132
|
+
|
|
133
|
+
return `<!DOCTYPE html>
|
|
134
|
+
<html lang="pt-BR">
|
|
135
|
+
<head>
|
|
136
|
+
<meta charset="UTF-8">
|
|
137
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
138
|
+
<title>Agent Master DevSecOps — Web Dashboard & Playground</title>
|
|
139
|
+
<style>
|
|
140
|
+
:root {
|
|
141
|
+
--bg: #090d16;
|
|
142
|
+
--card-bg: #131c2e;
|
|
143
|
+
--border: #23324d;
|
|
144
|
+
--text: #f1f5f9;
|
|
145
|
+
--muted: #94a3b8;
|
|
146
|
+
--primary: #38bdf8;
|
|
147
|
+
--primary-hover: #0284c7;
|
|
148
|
+
--success: #10b981;
|
|
149
|
+
--warning: #f59e0b;
|
|
150
|
+
--danger: #ef4444;
|
|
151
|
+
--code-bg: #05080f;
|
|
152
|
+
}
|
|
153
|
+
* { box-sizing: border-box; margin: 0; padding: 0; }
|
|
154
|
+
body { font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif; background: var(--bg); color: var(--text); padding: 24px; line-height: 1.5; }
|
|
155
|
+
header { display: flex; justify-content: space-between; align-items: center; margin-bottom: 24px; padding-bottom: 16px; border-bottom: 1px solid var(--border); }
|
|
156
|
+
h1 { font-size: 20px; color: var(--primary); display: flex; align-items: center; gap: 8px; }
|
|
157
|
+
.badge { background: #0c4a6e; color: var(--primary); padding: 4px 10px; border-radius: 999px; font-size: 12px; font-weight: 600; }
|
|
158
|
+
.grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(320px, 1fr)); gap: 20px; }
|
|
159
|
+
.card { background: var(--card-bg); border: 1px solid var(--border); border-radius: 12px; padding: 20px; box-shadow: 0 4px 12px rgba(0,0,0,0.3); }
|
|
160
|
+
.card h2 { font-size: 16px; color: var(--text); margin-bottom: 14px; display: flex; justify-content: space-between; align-items: center; }
|
|
161
|
+
.telemetry-row { display: flex; justify-content: space-between; padding: 8px 0; border-bottom: 1px solid #1a273f; font-size: 13px; }
|
|
162
|
+
.telemetry-row span:last-child { font-weight: bold; color: var(--primary); }
|
|
163
|
+
select, input, textarea, button { width: 100%; padding: 10px 12px; border-radius: 6px; border: 1px solid var(--border); background: var(--code-bg); color: var(--text); font-size: 13px; font-family: inherit; margin-bottom: 12px; }
|
|
164
|
+
textarea { font-family: monospace; min-height: 90px; resize: vertical; }
|
|
165
|
+
button { background: var(--primary); color: #05080f; font-weight: 600; cursor: pointer; border: none; transition: background 0.2s; }
|
|
166
|
+
button:hover { background: var(--primary-hover); }
|
|
167
|
+
pre { background: var(--code-bg); border: 1px solid var(--border); border-radius: 6px; padding: 12px; font-size: 12px; color: #a5f3fc; overflow-x: auto; max-height: 220px; }
|
|
168
|
+
.btn-bm { display: inline-block; text-align: center; background: #0284c7; color: white; padding: 10px 16px; border-radius: 6px; text-decoration: none; font-weight: bold; font-size: 13px; margin-bottom: 12px; }
|
|
169
|
+
.btn-bm:hover { background: #0369a1; }
|
|
170
|
+
.copy-btn { background: #1e293b; color: var(--primary); border: 1px solid var(--border); margin-top: 4px; padding: 6px 12px; font-size: 12px; }
|
|
171
|
+
.copy-btn:hover { background: #334155; }
|
|
172
|
+
</style>
|
|
173
|
+
</head>
|
|
174
|
+
<body>
|
|
175
|
+
<header>
|
|
176
|
+
<h1>⚡ Agent Master DevSecOps <span class="badge">v${PKG_VERSION}</span></h1>
|
|
177
|
+
<div style="font-size: 13px; color: var(--muted);">Local Web Dashboard & Playground (v5.1 Enterprise)</div>
|
|
178
|
+
</header>
|
|
179
|
+
|
|
180
|
+
<div class="grid">
|
|
181
|
+
<!-- Card 1: Telemetria & Status -->
|
|
182
|
+
<div class="card">
|
|
183
|
+
<h2>📊 Telemetria de Runtime</h2>
|
|
184
|
+
<div class="telemetry-row"><span>Node.js</span><span>${telemetry.nodeVersion}</span></div>
|
|
185
|
+
<div class="telemetry-row"><span>Plataforma</span><span>${telemetry.platform} (${telemetry.arch})</span></div>
|
|
186
|
+
<div class="telemetry-row"><span>V8 Heap Usada</span><span>${telemetry.heapUsedMB} MB</span></div>
|
|
187
|
+
<div class="telemetry-row"><span>Memória RSS</span><span>${telemetry.rssMB} MB</span></div>
|
|
188
|
+
<div class="telemetry-row"><span>Uptime</span><span>${telemetry.uptimeSeconds}s</span></div>
|
|
189
|
+
<div class="telemetry-row"><span>Ferramentas MCP Ativas</span><span>${telemetry.toolsCount}</span></div>
|
|
190
|
+
</div>
|
|
191
|
+
|
|
192
|
+
<!-- Card 2: 1-Click Magic Prompt & Bookmarklet -->
|
|
193
|
+
<div class="card">
|
|
194
|
+
<h2>🚀 Ativação Web Instantânea</h2>
|
|
195
|
+
<p style="font-size: 13px; color: var(--muted); margin-bottom: 12px;">
|
|
196
|
+
Arraste para a barra de favoritos para ativar o Agent Master em 1 clique em qualquer chat de IA:
|
|
197
|
+
</p>
|
|
198
|
+
<a class="btn-bm" href="${bookmarklet}">⚡ Ativar Agent Master DevSecOps</a>
|
|
199
|
+
<button class="copy-btn" onclick="copyPrompt()">📋 Copiar Magic Prompt (< 900 tokens)</button>
|
|
200
|
+
</div>
|
|
201
|
+
|
|
202
|
+
<!-- Card 3: MCP Tool Playground -->
|
|
203
|
+
<div class="card" style="grid-column: span 2;">
|
|
204
|
+
<h2>🛠️ MCP Tools Playground (JSON-RPC 2.0)</h2>
|
|
205
|
+
<label for="toolSelect" style="font-size: 12px; color: var(--muted); display: block; margin-bottom: 4px;">Selecione a Ferramenta MCP:</label>
|
|
206
|
+
<select id="toolSelect" aria-label="Selecione a Ferramenta MCP" onchange="loadToolTemplate()">
|
|
207
|
+
${TOOLS.map(t => `<option value="${t.name}">${t.name} — ${t.description.slice(0, 70)}...</option>`).join('')}
|
|
208
|
+
</select>
|
|
209
|
+
|
|
210
|
+
<label for="toolArgs" style="font-size: 12px; color: var(--muted); display: block; margin-bottom: 4px;">Argumentos JSON:</label>
|
|
211
|
+
<textarea id="toolArgs" aria-label="Argumentos JSON da Ferramenta">{}</textarea>
|
|
212
|
+
|
|
213
|
+
<button onclick="runTool()">▶️ Executar Tool via MCP</button>
|
|
214
|
+
|
|
215
|
+
<label style="font-size: 12px; color: var(--muted); display: block; margin-top: 8px; margin-bottom: 4px;">Resposta JSON-RPC:</label>
|
|
216
|
+
<pre id="toolOutput">// Clique em Executar para ver a resposta</pre>
|
|
217
|
+
</div>
|
|
218
|
+
|
|
219
|
+
<!-- Card 4: Evaluation Harness Testbed -->
|
|
220
|
+
<div class="card" style="grid-column: span 2;">
|
|
221
|
+
<h2>🎯 LLM Evaluation Harness Testbed (Regra 14 & Normas)</h2>
|
|
222
|
+
<p style="font-size: 13px; color: var(--muted); margin-bottom: 12px;">
|
|
223
|
+
Avalie empiricamente a resposta de qualquer LLM contra os 10 Princípios Canônicos de Engenharia de Software:
|
|
224
|
+
</p>
|
|
225
|
+
<label for="evalModel" style="font-size: 12px; color: var(--muted); display: block; margin-bottom: 4px;">Nome do Modelo:</label>
|
|
226
|
+
<input type="text" id="evalModel" aria-label="Nome do Modelo" value="Claude 3.5 Sonnet / GPT-4o" placeholder="Nome do modelo">
|
|
227
|
+
<label for="evalResponse" style="font-size: 12px; color: var(--muted); display: block; margin-bottom: 4px;">Resposta gerada pela LLM:</label>
|
|
228
|
+
<textarea id="evalResponse" aria-label="Resposta gerada pela LLM" placeholder="Cole a resposta gerada pela LLM aqui para avaliação empírica..."></textarea>
|
|
229
|
+
<button onclick="runEval()">⚖️ Avaliar Conformidade com os 10 Princípios Canônicos</button>
|
|
230
|
+
<pre id="evalOutput">// Resultado do scoring será exibido aqui</pre>
|
|
231
|
+
</div>
|
|
232
|
+
</div>
|
|
233
|
+
|
|
234
|
+
<script>
|
|
235
|
+
const MAGIC_PROMPT = ${JSON.stringify(magicPrompt)};
|
|
236
|
+
|
|
237
|
+
function copyPrompt() {
|
|
238
|
+
navigator.clipboard.writeText(MAGIC_PROMPT).then(() => {
|
|
239
|
+
alert("✅ Magic Prompt copiado para a área de transferência!");
|
|
240
|
+
});
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
const TOOL_TEMPLATES = {
|
|
244
|
+
agent_validate_idea: JSON.stringify({
|
|
245
|
+
ideaTitle: "Otimização de buffer de cache",
|
|
246
|
+
ideaDescription: "Poda de registros expirados para evitar memory leak",
|
|
247
|
+
projectStack: "Node.js, Express, SQLite",
|
|
248
|
+
projectDomain: "API de pagamentos e checkout"
|
|
249
|
+
}, null, 2),
|
|
250
|
+
agent_sast_scan: JSON.stringify({
|
|
251
|
+
code: "const q = 'SEL' + 'ECT * FROM users WHERE id = ' + req.body.id;"
|
|
252
|
+
}, null, 2),
|
|
253
|
+
agent_audit_quality: JSON.stringify({
|
|
254
|
+
code: "function test(a) { if (a) { return 1; } else { return 0; } }"
|
|
255
|
+
}, null, 2),
|
|
256
|
+
agent_sre_telemetry: JSON.stringify({}, null, 2),
|
|
257
|
+
agent_bootstrap_environment: JSON.stringify({ dryRun: true }, null, 2),
|
|
258
|
+
agent_generate_magic_prompt: JSON.stringify({ dryRun: true }, null, 2)
|
|
259
|
+
};
|
|
260
|
+
|
|
261
|
+
function loadToolTemplate() {
|
|
262
|
+
const tool = document.getElementById('toolSelect').value;
|
|
263
|
+
document.getElementById('toolArgs').value = TOOL_TEMPLATES[tool] || '{}';
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
async function runTool() {
|
|
267
|
+
const tool = document.getElementById('toolSelect').value;
|
|
268
|
+
let args = {};
|
|
269
|
+
try {
|
|
270
|
+
args = JSON.parse(document.getElementById('toolArgs').value);
|
|
271
|
+
} catch (e) {
|
|
272
|
+
alert("JSON inválido nos argumentos!");
|
|
273
|
+
return;
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
document.getElementById('toolOutput').innerText = "Processando...";
|
|
277
|
+
const res = await fetch('/api/mcp', {
|
|
278
|
+
method: 'POST',
|
|
279
|
+
headers: { 'Content-Type': 'application/json' },
|
|
280
|
+
body: JSON.stringify({ name: tool, arguments: args })
|
|
281
|
+
});
|
|
282
|
+
const data = await res.json();
|
|
283
|
+
document.getElementById('toolOutput').innerText = JSON.stringify(data, null, 2);
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
async function runEval() {
|
|
287
|
+
const modelName = document.getElementById('evalModel').value;
|
|
288
|
+
const responseReceived = document.getElementById('evalResponse').value;
|
|
289
|
+
|
|
290
|
+
if (!responseReceived.trim()) {
|
|
291
|
+
alert("Por favor cole a resposta da LLM.");
|
|
292
|
+
return;
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
document.getElementById('evalOutput').innerText = "Avaliando resposta...";
|
|
296
|
+
const res = await fetch('/api/eval', {
|
|
297
|
+
method: 'POST',
|
|
298
|
+
headers: { 'Content-Type': 'application/json' },
|
|
299
|
+
body: JSON.stringify({ modelName, responseReceived })
|
|
300
|
+
});
|
|
301
|
+
const data = await res.json();
|
|
302
|
+
document.getElementById('evalOutput').innerText = JSON.stringify(data, null, 2);
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
loadToolTemplate();
|
|
306
|
+
</script>
|
|
307
|
+
</body>
|
|
308
|
+
</html>`;
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
/**
|
|
312
|
+
* Envia resposta JSON com cabeçalhos de segurança defensivos (DRY).
|
|
313
|
+
* @param {object} res
|
|
314
|
+
* @param {number} statusCode
|
|
315
|
+
* @param {object} data
|
|
316
|
+
* @param {object} [headers={}]
|
|
317
|
+
*/
|
|
318
|
+
function sendJson(res, statusCode, data, headers = {}) {
|
|
319
|
+
const payload = JSON.stringify(data, null, 2);
|
|
320
|
+
res.writeHead(statusCode, {
|
|
321
|
+
'Content-Type': 'application/json; charset=utf-8',
|
|
322
|
+
'X-Content-Type-Options': 'nosniff',
|
|
323
|
+
'X-Frame-Options': 'DENY',
|
|
324
|
+
...headers
|
|
325
|
+
});
|
|
326
|
+
res.end(payload);
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
/**
|
|
330
|
+
* Envia resposta de erro padronizada (DRY).
|
|
331
|
+
* @param {object} res
|
|
332
|
+
* @param {Error|object} err
|
|
333
|
+
*/
|
|
334
|
+
function sendError(res, err) {
|
|
335
|
+
const statusCode = err.statusCode || 400;
|
|
336
|
+
const headers = {};
|
|
337
|
+
if (statusCode === 413) headers['Connection'] = 'close';
|
|
338
|
+
sendJson(res, statusCode, { error: err.message || String(err) }, headers);
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
/**
|
|
342
|
+
* Faz parsing seguro de JSON do corpo da requisição com proteção DoS/OOM (1MB max).
|
|
343
|
+
* @param {object} req
|
|
344
|
+
* @param {number} [maxBytes=1048576]
|
|
345
|
+
* @returns {Promise<object>}
|
|
346
|
+
*/
|
|
347
|
+
function parseJsonBody(req, maxBytes = 1048576) {
|
|
348
|
+
return new Promise((resolve, reject) => {
|
|
349
|
+
let body = '';
|
|
350
|
+
let bytesReceived = 0;
|
|
351
|
+
let exceeded = false;
|
|
352
|
+
|
|
353
|
+
req.on('data', chunk => {
|
|
354
|
+
if (exceeded) return;
|
|
355
|
+
bytesReceived += typeof chunk === 'string' ? Buffer.byteLength(chunk) : chunk.length;
|
|
356
|
+
if (bytesReceived > maxBytes) {
|
|
357
|
+
exceeded = true;
|
|
358
|
+
if (typeof req.resume === 'function') req.resume();
|
|
359
|
+
const err = new Error('Payload Too Large: corpo da requisição excedeu o limite máximo seguro (1MB)');
|
|
360
|
+
err.statusCode = 413;
|
|
361
|
+
reject(err);
|
|
362
|
+
return;
|
|
363
|
+
}
|
|
364
|
+
body += chunk;
|
|
365
|
+
});
|
|
366
|
+
|
|
367
|
+
req.on('end', () => {
|
|
368
|
+
if (exceeded) return;
|
|
369
|
+
try {
|
|
370
|
+
resolve(body ? JSON.parse(body) : {});
|
|
371
|
+
} catch (err) {
|
|
372
|
+
reject(err);
|
|
373
|
+
}
|
|
374
|
+
});
|
|
375
|
+
|
|
376
|
+
req.on('error', reject);
|
|
377
|
+
});
|
|
378
|
+
}
|
|
379
|
+
|
|
380
|
+
/**
|
|
381
|
+
* Cria a instância do servidor HTTP local com endpoints REST e segurança defensiva.
|
|
382
|
+
* @param {object} [options={}]
|
|
383
|
+
* @returns {object} { server, port, host, listen, close }
|
|
384
|
+
*/
|
|
385
|
+
function createInspectorServer(options = {}) {
|
|
386
|
+
const safeOptions = (options && typeof options === 'object') ? options : {};
|
|
387
|
+
const port = safeOptions.port !== undefined ? safeOptions.port : 4040;
|
|
388
|
+
const host = safeOptions.host || '127.0.0.1';
|
|
389
|
+
|
|
390
|
+
const server = http.createServer(async (req, res) => {
|
|
391
|
+
// CORS seguro restrito a localhost / 127.0.0.1
|
|
392
|
+
const origin = req.headers.origin || '';
|
|
393
|
+
if (!origin || /^https?:\/\/(localhost|127\.0\.0\.1)(:\d+)?$/.test(origin)) {
|
|
394
|
+
res.setHeader('Access-Control-Allow-Origin', origin || `http://localhost:${port}`);
|
|
395
|
+
}
|
|
396
|
+
res.setHeader('Access-Control-Allow-Methods', 'GET, POST, OPTIONS');
|
|
397
|
+
res.setHeader('Access-Control-Allow-Headers', 'Content-Type');
|
|
398
|
+
|
|
399
|
+
if (req.method === 'OPTIONS') {
|
|
400
|
+
res.writeHead(204);
|
|
401
|
+
res.end();
|
|
402
|
+
return;
|
|
403
|
+
}
|
|
404
|
+
|
|
405
|
+
let parsedUrl;
|
|
406
|
+
try {
|
|
407
|
+
parsedUrl = new URL(req.url, `http://${req.headers.host || 'localhost'}`);
|
|
408
|
+
} catch (_) {
|
|
409
|
+
sendError(res, new Error('URL inválida na requisição'));
|
|
410
|
+
return;
|
|
411
|
+
}
|
|
412
|
+
|
|
413
|
+
const pathname = parsedUrl.pathname;
|
|
414
|
+
|
|
415
|
+
// 1. Dashboard UI
|
|
416
|
+
if (pathname === '/' && req.method === 'GET') {
|
|
417
|
+
res.writeHead(200, {
|
|
418
|
+
'Content-Type': 'text/html; charset=utf-8',
|
|
419
|
+
'X-Content-Type-Options': 'nosniff',
|
|
420
|
+
'X-Frame-Options': 'DENY'
|
|
421
|
+
});
|
|
422
|
+
res.end(renderHtmlDashboard());
|
|
423
|
+
return;
|
|
424
|
+
}
|
|
425
|
+
|
|
426
|
+
// 2. Telemetry & Status API
|
|
427
|
+
if (pathname === '/api/status' && req.method === 'GET') {
|
|
428
|
+
const data = {
|
|
429
|
+
status: 'UP',
|
|
430
|
+
telemetry: getTelemetry(),
|
|
431
|
+
tools: TOOLS.map(t => ({ name: t.name, description: t.description }))
|
|
432
|
+
};
|
|
433
|
+
sendJson(res, 200, data);
|
|
434
|
+
return;
|
|
435
|
+
}
|
|
436
|
+
|
|
437
|
+
// 3. MCP Tool Proxy
|
|
438
|
+
if (pathname === '/api/mcp' && req.method === 'POST') {
|
|
439
|
+
try {
|
|
440
|
+
const body = await parseJsonBody(req);
|
|
441
|
+
const rpcRequest = {
|
|
442
|
+
jsonrpc: '2.0',
|
|
443
|
+
id: Date.now(),
|
|
444
|
+
method: 'tools/call',
|
|
445
|
+
params: {
|
|
446
|
+
name: body.name,
|
|
447
|
+
arguments: (body.arguments && typeof body.arguments === 'object') ? body.arguments : {}
|
|
448
|
+
}
|
|
449
|
+
};
|
|
450
|
+
const rpcResponse = processMessage(rpcRequest);
|
|
451
|
+
sendJson(res, 200, rpcResponse);
|
|
452
|
+
} catch (err) {
|
|
453
|
+
sendError(res, err);
|
|
454
|
+
}
|
|
455
|
+
return;
|
|
456
|
+
}
|
|
457
|
+
|
|
458
|
+
// 4. Eval Harness API
|
|
459
|
+
if (pathname === '/api/eval' && req.method === 'POST') {
|
|
460
|
+
try {
|
|
461
|
+
const body = await parseJsonBody(req);
|
|
462
|
+
const evalResult = evaluateResponse(body.modelName || 'Custom Model', body.promptSent || '', body.responseReceived || '');
|
|
463
|
+
sendJson(res, 200, evalResult);
|
|
464
|
+
} catch (err) {
|
|
465
|
+
sendError(res, err);
|
|
466
|
+
}
|
|
467
|
+
return;
|
|
468
|
+
}
|
|
469
|
+
|
|
470
|
+
// 5. Magic Prompt API
|
|
471
|
+
if (pathname === '/api/magic-prompt' && req.method === 'GET') {
|
|
472
|
+
const gen = new MagicPromptGenerator(ROOT_DIR);
|
|
473
|
+
const prompt = gen.generateMagicPrompt();
|
|
474
|
+
const bookmarklet = gen.generateBookmarklet();
|
|
475
|
+
const tokens = gen.estimateTokens(prompt);
|
|
476
|
+
sendJson(res, 200, { prompt, bookmarklet, tokens });
|
|
477
|
+
return;
|
|
478
|
+
}
|
|
479
|
+
|
|
480
|
+
// 404 Fallback
|
|
481
|
+
sendJson(res, 404, { error: 'Endpoint não encontrado', pathname });
|
|
482
|
+
});
|
|
483
|
+
|
|
484
|
+
return {
|
|
485
|
+
server,
|
|
486
|
+
port,
|
|
487
|
+
host,
|
|
488
|
+
listen: (cb) => server.listen(port, host, cb),
|
|
489
|
+
close: () => new Promise(resolve => server.close(resolve))
|
|
490
|
+
};
|
|
491
|
+
}
|
|
492
|
+
|
|
493
|
+
/**
|
|
494
|
+
* Classe Canônica WebInspector
|
|
495
|
+
*/
|
|
496
|
+
class WebInspector {
|
|
497
|
+
static get DEFAULT_PORT() {
|
|
498
|
+
return 4040;
|
|
499
|
+
}
|
|
500
|
+
|
|
501
|
+
static get DEFAULT_HOST() {
|
|
502
|
+
return '127.0.0.1';
|
|
503
|
+
}
|
|
504
|
+
|
|
505
|
+
constructor(options = {}) {
|
|
506
|
+
const safeOptions = (options && typeof options === 'object') ? options : {};
|
|
507
|
+
this.port = safeOptions.port !== undefined ? safeOptions.port : WebInspector.DEFAULT_PORT;
|
|
508
|
+
this.host = safeOptions.host || WebInspector.DEFAULT_HOST;
|
|
509
|
+
this.serverInstance = null;
|
|
510
|
+
}
|
|
511
|
+
|
|
512
|
+
getTelemetry() {
|
|
513
|
+
return getTelemetry();
|
|
514
|
+
}
|
|
515
|
+
|
|
516
|
+
renderDashboard() {
|
|
517
|
+
return renderHtmlDashboard();
|
|
518
|
+
}
|
|
519
|
+
|
|
520
|
+
formatReport(telemetry) {
|
|
521
|
+
return formatReport(telemetry || this.getTelemetry());
|
|
522
|
+
}
|
|
523
|
+
|
|
524
|
+
auditToSarif(telemetry) {
|
|
525
|
+
return auditTelemetryToSarif(telemetry || this.getTelemetry());
|
|
526
|
+
}
|
|
527
|
+
|
|
528
|
+
createServer() {
|
|
529
|
+
this.serverInstance = createInspectorServer({ port: this.port, host: this.host });
|
|
530
|
+
return this.serverInstance;
|
|
531
|
+
}
|
|
532
|
+
|
|
533
|
+
async start() {
|
|
534
|
+
if (!this.serverInstance) {
|
|
535
|
+
this.createServer();
|
|
536
|
+
}
|
|
537
|
+
await new Promise(resolve => this.serverInstance.listen(resolve));
|
|
538
|
+
return this.serverInstance;
|
|
539
|
+
}
|
|
540
|
+
|
|
541
|
+
async stop() {
|
|
542
|
+
if (this.serverInstance && typeof this.serverInstance.close === 'function') {
|
|
543
|
+
await this.serverInstance.close();
|
|
544
|
+
this.serverInstance = null;
|
|
545
|
+
}
|
|
546
|
+
}
|
|
547
|
+
}
|
|
548
|
+
|
|
549
|
+
if (require.main === module) {
|
|
550
|
+
const args = process.argv.slice(2);
|
|
551
|
+
let port = 4040;
|
|
552
|
+
const portIdx = args.findIndex(a => a === '--port' || a === '-p');
|
|
553
|
+
if (portIdx !== -1 && args[portIdx + 1]) {
|
|
554
|
+
port = parseInt(args[portIdx + 1], 10);
|
|
555
|
+
}
|
|
556
|
+
|
|
557
|
+
const inspector = createInspectorServer({ port });
|
|
558
|
+
inspector.listen(() => {
|
|
559
|
+
console.log('\n============================================================');
|
|
560
|
+
console.log('⚡ Agent Master DevSecOps — Web Dashboard & Inspector');
|
|
561
|
+
console.log('============================================================\n');
|
|
562
|
+
console.log(`🌐 Dashboard disponível em: http://localhost:${port}`);
|
|
563
|
+
console.log(`📊 Endpoint de Métricas & Status: http://localhost:${port}/api/status`);
|
|
564
|
+
console.log(`🛠️ Playground MCP: http://localhost:${port}/api/mcp`);
|
|
565
|
+
console.log('\nPressione Ctrl+C para encerrar o servidor.\n');
|
|
566
|
+
});
|
|
567
|
+
}
|
|
568
|
+
|
|
569
|
+
module.exports = {
|
|
570
|
+
WebInspector,
|
|
571
|
+
createInspectorServer,
|
|
572
|
+
getTelemetry,
|
|
573
|
+
renderHtmlDashboard,
|
|
574
|
+
formatReport,
|
|
575
|
+
auditTelemetryToSarif,
|
|
576
|
+
sendJson,
|
|
577
|
+
sendError
|
|
578
|
+
};
|