Terminal verboso com eventos de tool, turno, sistema e stderr + cards com botões na base
- Executor envia 5 tipos de evento: chunk, tool, turn, system, stderr - Frontend renderiza cada tipo com cor e formatação distintas no terminal - Cards de agentes e pipelines com flex-column e botões alinhados na base - CSS para novos tipos de linha do terminal (tool amarelo, turn accent, stderr muted)
This commit is contained in:
@@ -1058,6 +1058,24 @@ textarea {
|
||||
font-style: italic;
|
||||
}
|
||||
|
||||
.terminal-line.tool .content {
|
||||
color: var(--warning);
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
.terminal-line.turn .content {
|
||||
color: var(--accent);
|
||||
font-weight: 600;
|
||||
font-size: 12px;
|
||||
letter-spacing: 0.5px;
|
||||
}
|
||||
|
||||
.terminal-line.stderr .content {
|
||||
color: var(--text-muted);
|
||||
font-size: 11px;
|
||||
opacity: 0.7;
|
||||
}
|
||||
|
||||
.terminal-cursor {
|
||||
display: inline-block;
|
||||
width: 8px;
|
||||
|
||||
@@ -171,8 +171,18 @@ const App = {
|
||||
|
||||
case 'execution_output': {
|
||||
Terminal.stopProcessing();
|
||||
const evtType = data.data?.type || 'chunk';
|
||||
const content = data.data?.content || '';
|
||||
if (content) {
|
||||
if (!content) break;
|
||||
if (evtType === 'tool') {
|
||||
Terminal.addLine(`▸ ${content}`, 'tool', data.executionId);
|
||||
} else if (evtType === 'turn') {
|
||||
Terminal.addLine(`── ${content} ──`, 'turn', data.executionId);
|
||||
} else if (evtType === 'system') {
|
||||
Terminal.addLine(content, 'system', data.executionId);
|
||||
} else if (evtType === 'stderr') {
|
||||
Terminal.addLine(content, 'stderr', data.executionId);
|
||||
} else {
|
||||
Terminal.addLine(content, 'default', data.executionId);
|
||||
}
|
||||
App._updateActiveBadge();
|
||||
@@ -233,8 +243,18 @@ const App = {
|
||||
|
||||
case 'pipeline_step_output': {
|
||||
Terminal.stopProcessing();
|
||||
const stepEvtType = data.data?.type || 'chunk';
|
||||
const stepContent = data.data?.content || '';
|
||||
if (stepContent) {
|
||||
if (!stepContent) break;
|
||||
if (stepEvtType === 'tool') {
|
||||
Terminal.addLine(`▸ ${stepContent}`, 'tool', data.executionId);
|
||||
} else if (stepEvtType === 'turn') {
|
||||
Terminal.addLine(`── ${stepContent} ──`, 'turn', data.executionId);
|
||||
} else if (stepEvtType === 'system') {
|
||||
Terminal.addLine(stepContent, 'system', data.executionId);
|
||||
} else if (stepEvtType === 'stderr') {
|
||||
Terminal.addLine(stepContent, 'stderr', data.executionId);
|
||||
} else {
|
||||
Terminal.addLine(stepContent, 'default', data.executionId);
|
||||
}
|
||||
break;
|
||||
|
||||
@@ -111,6 +111,56 @@ function extractText(event) {
|
||||
return null;
|
||||
}
|
||||
|
||||
function extractToolInfo(event) {
|
||||
if (!event) return null;
|
||||
|
||||
if (event.type === 'assistant' && event.message?.content) {
|
||||
const toolBlocks = event.message.content.filter((b) => b.type === 'tool_use');
|
||||
if (toolBlocks.length > 0) {
|
||||
return toolBlocks.map((b) => {
|
||||
const name = b.name || 'unknown';
|
||||
const input = b.input || {};
|
||||
let detail = '';
|
||||
if (input.command) detail = input.command.slice(0, 120);
|
||||
else if (input.file_path) detail = input.file_path;
|
||||
else if (input.pattern) detail = input.pattern;
|
||||
else if (input.query) detail = input.query;
|
||||
else if (input.path) detail = input.path;
|
||||
else if (input.prompt) detail = input.prompt.slice(0, 80);
|
||||
else if (input.description) detail = input.description.slice(0, 80);
|
||||
return { name, detail };
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
if (event.type === 'content_block_start' && event.content_block?.type === 'tool_use') {
|
||||
return [{ name: event.content_block.name || 'tool', detail: '' }];
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
function extractSystemInfo(event) {
|
||||
if (!event) return null;
|
||||
|
||||
if (event.type === 'system' && event.message) return event.message;
|
||||
if (event.type === 'error') return event.error?.message || event.message || 'Erro desconhecido';
|
||||
|
||||
if (event.type === 'result') {
|
||||
const parts = [];
|
||||
if (event.num_turns) parts.push(`${event.num_turns} turnos`);
|
||||
if (event.cost_usd) parts.push(`custo: $${event.cost_usd.toFixed(4)}`);
|
||||
if (event.duration_ms) {
|
||||
const s = (event.duration_ms / 1000).toFixed(1);
|
||||
parts.push(`duração: ${s}s`);
|
||||
}
|
||||
if (event.session_id) parts.push(`sessão: ${event.session_id.slice(0, 8)}...`);
|
||||
return parts.length > 0 ? `Resultado: ${parts.join(' | ')}` : null;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
export function execute(agentConfig, task, callbacks = {}) {
|
||||
if (activeExecutions.size >= maxConcurrent) {
|
||||
const err = new Error(`Limite de ${maxConcurrent} execuções simultâneas atingido`);
|
||||
@@ -156,13 +206,35 @@ export function execute(agentConfig, task, callbacks = {}) {
|
||||
let fullText = '';
|
||||
let resultMeta = null;
|
||||
|
||||
let turnCount = 0;
|
||||
|
||||
function processEvent(parsed) {
|
||||
if (!parsed) return;
|
||||
|
||||
const tools = extractToolInfo(parsed);
|
||||
if (tools) {
|
||||
for (const t of tools) {
|
||||
const msg = t.detail ? `${t.name}: ${t.detail}` : t.name;
|
||||
if (onData) onData({ type: 'tool', content: msg, toolName: t.name }, executionId);
|
||||
}
|
||||
}
|
||||
|
||||
const text = extractText(parsed);
|
||||
if (text) {
|
||||
fullText += text;
|
||||
if (onData) onData({ type: 'chunk', content: text }, executionId);
|
||||
}
|
||||
|
||||
const sysInfo = extractSystemInfo(parsed);
|
||||
if (sysInfo) {
|
||||
if (onData) onData({ type: 'system', content: sysInfo }, executionId);
|
||||
}
|
||||
|
||||
if (parsed.type === 'assistant') {
|
||||
turnCount++;
|
||||
if (onData) onData({ type: 'turn', content: `Turno ${turnCount}`, turn: turnCount }, executionId);
|
||||
}
|
||||
|
||||
if (parsed.type === 'result') {
|
||||
resultMeta = {
|
||||
costUsd: parsed.cost_usd || 0,
|
||||
@@ -182,7 +254,12 @@ export function execute(agentConfig, task, callbacks = {}) {
|
||||
});
|
||||
|
||||
child.stderr.on('data', (chunk) => {
|
||||
errorBuffer += chunk.toString();
|
||||
const str = chunk.toString();
|
||||
errorBuffer += str;
|
||||
const lines = str.split('\n').filter(l => l.trim());
|
||||
for (const line of lines) {
|
||||
if (onData) onData({ type: 'stderr', content: line.trim() }, executionId);
|
||||
}
|
||||
});
|
||||
|
||||
child.on('error', (err) => {
|
||||
@@ -267,14 +344,35 @@ export function resume(agentConfig, sessionId, message, callbacks = {}) {
|
||||
let errorBuffer = '';
|
||||
let fullText = '';
|
||||
let resultMeta = null;
|
||||
let turnCount = 0;
|
||||
|
||||
function processEvent(parsed) {
|
||||
if (!parsed) return;
|
||||
|
||||
const tools = extractToolInfo(parsed);
|
||||
if (tools) {
|
||||
for (const t of tools) {
|
||||
const msg = t.detail ? `${t.name}: ${t.detail}` : t.name;
|
||||
if (onData) onData({ type: 'tool', content: msg, toolName: t.name }, executionId);
|
||||
}
|
||||
}
|
||||
|
||||
const text = extractText(parsed);
|
||||
if (text) {
|
||||
fullText += text;
|
||||
if (onData) onData({ type: 'chunk', content: text }, executionId);
|
||||
}
|
||||
|
||||
const sysInfo = extractSystemInfo(parsed);
|
||||
if (sysInfo) {
|
||||
if (onData) onData({ type: 'system', content: sysInfo }, executionId);
|
||||
}
|
||||
|
||||
if (parsed.type === 'assistant') {
|
||||
turnCount++;
|
||||
if (onData) onData({ type: 'turn', content: `Turno ${turnCount}`, turn: turnCount }, executionId);
|
||||
}
|
||||
|
||||
if (parsed.type === 'result') {
|
||||
resultMeta = {
|
||||
costUsd: parsed.cost_usd || 0,
|
||||
@@ -294,7 +392,12 @@ export function resume(agentConfig, sessionId, message, callbacks = {}) {
|
||||
});
|
||||
|
||||
child.stderr.on('data', (chunk) => {
|
||||
errorBuffer += chunk.toString();
|
||||
const str = chunk.toString();
|
||||
errorBuffer += str;
|
||||
const lines = str.split('\n').filter(l => l.trim());
|
||||
for (const line of lines) {
|
||||
if (onData) onData({ type: 'stderr', content: line.trim() }, executionId);
|
||||
}
|
||||
});
|
||||
|
||||
child.on('error', (err) => {
|
||||
|
||||
Reference in New Issue
Block a user