Download MD no histórico, relatórios externos e service systemd

- Botão de download .md no modal de detalhe do histórico (agente e pipeline)
- Relatórios de execução gravados também em ~/agent_reports/ (configurável via AGENT_REPORTS_DIR)
- Service systemd (user) para iniciar o orchestrator no boot com auto-restart
This commit is contained in:
Frederico Castro
2026-02-27 04:19:10 -03:00
parent 9b66a415ff
commit a2a1aa2c7a
12 changed files with 499 additions and 12 deletions

View File

@@ -335,6 +335,8 @@ const AgentsUI = {
const instructionsEl = document.getElementById('execute-instructions');
if (instructionsEl) instructionsEl.value = '';
if (App._executeDropzone) App._executeDropzone.reset();
AgentsUI._loadSavedTasks();
Modal.open('execute-modal-overlay');

View File

@@ -188,6 +188,10 @@ const HistoryUI = {
Modal.open('execution-detail-modal-overlay');
Utils.refreshIcons(content);
content.querySelector('[data-action="download-result-md"]')?.addEventListener('click', () => {
HistoryUI._downloadResultMd(exec);
});
content.querySelectorAll('.pipeline-step-prompt-toggle').forEach((btn) => {
btn.addEventListener('click', () => {
const stepCard = btn.closest('.pipeline-step-detail');
@@ -217,6 +221,12 @@ const HistoryUI = {
: '';
return `
${exec.result ? `
<div class="report-actions">
<button class="btn btn-ghost btn-sm" data-action="download-result-md" type="button">
<i data-lucide="download"></i> Download .md
</button>
</div>` : ''}
<div class="execution-detail-meta">
<div class="execution-detail-row">
<span class="execution-detail-label">Agente</span>
@@ -326,7 +336,14 @@ const HistoryUI = {
`;
}).join('');
const hasResults = steps.some(s => s.result);
return `
${hasResults ? `
<div class="report-actions">
<button class="btn btn-ghost btn-sm" data-action="download-result-md" type="button">
<i data-lucide="download"></i> Download .md
</button>
</div>` : ''}
<div class="execution-detail-meta">
<div class="execution-detail-row">
<span class="execution-detail-label">Pipeline</span>
@@ -374,6 +391,36 @@ const HistoryUI = {
`;
},
_downloadResultMd(exec) {
let md = '';
const name = exec.type === 'pipeline'
? (exec.pipelineName || 'Pipeline')
: (exec.agentName || 'Agente');
if (exec.type === 'pipeline') {
md += `# ${name}\n\n`;
const steps = Array.isArray(exec.steps) ? exec.steps : [];
steps.forEach((step, i) => {
md += `## Passo ${i + 1}${step.agentName || 'Agente'}\n\n`;
if (step.result) md += `${step.result}\n\n`;
});
} else {
md += exec.result || '';
}
const slug = name.toLowerCase().replace(/[^a-z0-9]+/g, '-').replace(/(^-|-$)/g, '');
const filename = `${slug}-${new Date(exec.startedAt || Date.now()).toISOString().slice(0, 10)}.md`;
const blob = new Blob([md], { type: 'text/markdown' });
const url = URL.createObjectURL(blob);
const a = document.createElement('a');
a.href = url;
a.download = filename;
a.click();
URL.revokeObjectURL(url);
Toast.success('Download iniciado');
},
async retryExecution(id) {
try {
await API.executions.retry(id);

View File

@@ -370,6 +370,8 @@ const PipelinesUI = {
const workdirEl = document.getElementById('pipeline-execute-workdir');
if (workdirEl) workdirEl.value = '';
if (App._pipelineDropzone) App._pipelineDropzone.reset();
Modal.open('pipeline-execute-modal-overlay');
},
@@ -384,7 +386,16 @@ const PipelinesUI = {
}
try {
await API.pipelines.execute(pipelineId, input, workingDirectory);
let contextFiles = null;
const dropzone = App._pipelineDropzone;
if (dropzone && dropzone.getFiles().length > 0) {
Toast.info('Fazendo upload dos arquivos...');
const uploadResult = await API.uploads.send(dropzone.getFiles());
contextFiles = uploadResult.files;
}
await API.pipelines.execute(pipelineId, input, workingDirectory, contextFiles);
if (dropzone) dropzone.reset();
Modal.close('pipeline-execute-modal-overlay');
App.navigateTo('terminal');
Toast.info('Pipeline iniciado');