diff --git a/public/css/styles.css b/public/css/styles.css index 3f29115..cdcdf3e 100644 --- a/public/css/styles.css +++ b/public/css/styles.css @@ -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; diff --git a/public/js/app.js b/public/js/app.js index 176897c..3830243 100644 --- a/public/js/app.js +++ b/public/js/app.js @@ -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; diff --git a/src/agents/executor.js b/src/agents/executor.js index 5b73597..c1f61a0 100644 --- a/src/agents/executor.js +++ b/src/agents/executor.js @@ -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) => {