Persistir output do terminal no servidor e corrigir cursor do flow editor
- Buffer server-side no executor para manter até 1000 linhas por execução ativa - Terminal restaura output do servidor ao recarregar a página (F5) - Fechar overlay do flow editor ao navegar para outra seção - Garantir SHELL e HOME no ambiente dos processos filhos
This commit is contained in:
@@ -90,6 +90,8 @@ const App = {
|
|||||||
history.pushState(null, '', `#${section}`);
|
history.pushState(null, '', `#${section}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (typeof FlowEditor !== 'undefined') FlowEditor._teardown();
|
||||||
|
|
||||||
document.querySelectorAll('.section').forEach((el) => {
|
document.querySelectorAll('.section').forEach((el) => {
|
||||||
const isActive = el.id === section;
|
const isActive = el.id === section;
|
||||||
el.classList.toggle('active', isActive);
|
el.classList.toggle('active', isActive);
|
||||||
|
|||||||
@@ -737,8 +737,12 @@ const FlowEditor = {
|
|||||||
if (!leave) return;
|
if (!leave) return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
FlowEditor._teardown();
|
||||||
|
},
|
||||||
|
|
||||||
|
_teardown() {
|
||||||
const overlay = FlowEditor._overlay;
|
const overlay = FlowEditor._overlay;
|
||||||
if (!overlay) return;
|
if (!overlay || overlay.hidden) return;
|
||||||
|
|
||||||
overlay.classList.remove('active');
|
overlay.classList.remove('active');
|
||||||
setTimeout(() => { overlay.hidden = true; }, 200);
|
setTimeout(() => { overlay.hidden = true; }, 200);
|
||||||
@@ -755,6 +759,7 @@ const FlowEditor = {
|
|||||||
FlowEditor._selectedNode = null;
|
FlowEditor._selectedNode = null;
|
||||||
FlowEditor._dragState = null;
|
FlowEditor._dragState = null;
|
||||||
FlowEditor._panStart = null;
|
FlowEditor._panStart = null;
|
||||||
|
FlowEditor._dirty = false;
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -43,16 +43,35 @@ const Terminal = {
|
|||||||
try {
|
try {
|
||||||
const active = await API.system.activeExecutions();
|
const active = await API.system.activeExecutions();
|
||||||
const hasActive = Array.isArray(active) && active.length > 0;
|
const hasActive = Array.isArray(active) && active.length > 0;
|
||||||
if (hasActive && Terminal._restoreFromStorage()) {
|
if (hasActive) {
|
||||||
|
const exec = active[0];
|
||||||
|
const serverBuffer = Array.isArray(exec.outputBuffer) ? exec.outputBuffer : [];
|
||||||
|
|
||||||
|
if (serverBuffer.length > 0) {
|
||||||
|
Terminal.lines = serverBuffer.map((item) => {
|
||||||
|
const time = new Date();
|
||||||
|
return {
|
||||||
|
content: item.content || '',
|
||||||
|
type: item.type || 'default',
|
||||||
|
timestamp: time.toTimeString().slice(0, 8),
|
||||||
|
executionId: exec.executionId,
|
||||||
|
};
|
||||||
|
});
|
||||||
|
Terminal._saveToStorage();
|
||||||
|
} else {
|
||||||
|
Terminal._restoreFromStorage();
|
||||||
|
}
|
||||||
|
|
||||||
Terminal.render();
|
Terminal.render();
|
||||||
|
const startedAt = exec.startedAt ? new Date(exec.startedAt).getTime() : null;
|
||||||
const savedStart = sessionStorage.getItem(Terminal._timerStorageKey);
|
const savedStart = sessionStorage.getItem(Terminal._timerStorageKey);
|
||||||
Terminal._startTimer(savedStart ? Number(savedStart) : null);
|
Terminal._startTimer(savedStart ? Number(savedStart) : startedAt);
|
||||||
Terminal.startProcessing(active[0].agentConfig?.agent_name || 'Agente');
|
Terminal.startProcessing(exec.agentConfig?.agent_name || 'Agente');
|
||||||
try {
|
try {
|
||||||
const chatData = sessionStorage.getItem(Terminal._chatStorageKey);
|
const chatData = sessionStorage.getItem(Terminal._chatStorageKey);
|
||||||
if (chatData) Terminal._chatSession = JSON.parse(chatData);
|
if (chatData) Terminal._chatSession = JSON.parse(chatData);
|
||||||
} catch {}
|
} catch {}
|
||||||
} else if (!hasActive) {
|
} else {
|
||||||
Terminal._clearStorage();
|
Terminal._clearStorage();
|
||||||
Terminal._hideTimer();
|
Terminal._hideTimer();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,8 +9,10 @@ const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|||||||
const AGENT_SETTINGS = path.resolve(__dirname, '..', '..', 'data', 'agent-settings.json');
|
const AGENT_SETTINGS = path.resolve(__dirname, '..', '..', 'data', 'agent-settings.json');
|
||||||
const CLAUDE_BIN = resolveBin();
|
const CLAUDE_BIN = resolveBin();
|
||||||
const activeExecutions = new Map();
|
const activeExecutions = new Map();
|
||||||
|
const executionOutputBuffers = new Map();
|
||||||
const MAX_OUTPUT_SIZE = 512 * 1024;
|
const MAX_OUTPUT_SIZE = 512 * 1024;
|
||||||
const MAX_ERROR_SIZE = 100 * 1024;
|
const MAX_ERROR_SIZE = 100 * 1024;
|
||||||
|
const MAX_BUFFER_LINES = 1000;
|
||||||
const ALLOWED_DIRECTORIES = (process.env.ALLOWED_DIRECTORIES || '').split(',').map(d => d.trim()).filter(Boolean);
|
const ALLOWED_DIRECTORIES = (process.env.ALLOWED_DIRECTORIES || '').split(',').map(d => d.trim()).filter(Boolean);
|
||||||
|
|
||||||
let maxConcurrent = settingsStore.get().maxConcurrent || 5;
|
let maxConcurrent = settingsStore.get().maxConcurrent || 5;
|
||||||
@@ -52,6 +54,8 @@ function cleanEnv(agentSecrets) {
|
|||||||
delete env.CLAUDECODE;
|
delete env.CLAUDECODE;
|
||||||
delete env.ANTHROPIC_API_KEY;
|
delete env.ANTHROPIC_API_KEY;
|
||||||
env.CLAUDE_CODE_MAX_OUTPUT_TOKENS = '128000';
|
env.CLAUDE_CODE_MAX_OUTPUT_TOKENS = '128000';
|
||||||
|
if (!env.SHELL) env.SHELL = '/bin/bash';
|
||||||
|
if (!env.HOME) env.HOME = process.env.HOME || '/root';
|
||||||
if (agentSecrets && typeof agentSecrets === 'object') {
|
if (agentSecrets && typeof agentSecrets === 'object') {
|
||||||
Object.assign(env, agentSecrets);
|
Object.assign(env, agentSecrets);
|
||||||
}
|
}
|
||||||
@@ -179,6 +183,16 @@ function extractSystemInfo(event) {
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function bufferLine(executionId, data) {
|
||||||
|
let buf = executionOutputBuffers.get(executionId);
|
||||||
|
if (!buf) {
|
||||||
|
buf = [];
|
||||||
|
executionOutputBuffers.set(executionId, buf);
|
||||||
|
}
|
||||||
|
buf.push(data);
|
||||||
|
if (buf.length > MAX_BUFFER_LINES) buf.shift();
|
||||||
|
}
|
||||||
|
|
||||||
function processChildOutput(child, executionId, callbacks, options = {}) {
|
function processChildOutput(child, executionId, callbacks, options = {}) {
|
||||||
const { onData, onError, onComplete } = callbacks;
|
const { onData, onError, onComplete } = callbacks;
|
||||||
const timeoutMs = options.timeout || 1800000;
|
const timeoutMs = options.timeout || 1800000;
|
||||||
@@ -202,7 +216,9 @@ function processChildOutput(child, executionId, callbacks, options = {}) {
|
|||||||
if (tools) {
|
if (tools) {
|
||||||
for (const t of tools) {
|
for (const t of tools) {
|
||||||
const msg = t.detail ? `${t.name}: ${t.detail}` : t.name;
|
const msg = t.detail ? `${t.name}: ${t.detail}` : t.name;
|
||||||
if (onData) onData({ type: 'tool', content: msg, toolName: t.name }, executionId);
|
const data = { type: 'tool', content: msg, toolName: t.name };
|
||||||
|
bufferLine(executionId, data);
|
||||||
|
if (onData) onData(data, executionId);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -211,17 +227,23 @@ function processChildOutput(child, executionId, callbacks, options = {}) {
|
|||||||
if (fullText.length < MAX_OUTPUT_SIZE) {
|
if (fullText.length < MAX_OUTPUT_SIZE) {
|
||||||
fullText += text;
|
fullText += text;
|
||||||
}
|
}
|
||||||
if (onData) onData({ type: 'chunk', content: text }, executionId);
|
const data = { type: 'chunk', content: text };
|
||||||
|
bufferLine(executionId, data);
|
||||||
|
if (onData) onData(data, executionId);
|
||||||
}
|
}
|
||||||
|
|
||||||
const sysInfo = extractSystemInfo(parsed);
|
const sysInfo = extractSystemInfo(parsed);
|
||||||
if (sysInfo) {
|
if (sysInfo) {
|
||||||
if (onData) onData({ type: 'system', content: sysInfo }, executionId);
|
const data = { type: 'system', content: sysInfo };
|
||||||
|
bufferLine(executionId, data);
|
||||||
|
if (onData) onData(data, executionId);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (parsed.type === 'assistant') {
|
if (parsed.type === 'assistant') {
|
||||||
turnCount++;
|
turnCount++;
|
||||||
if (onData) onData({ type: 'turn', content: `Turno ${turnCount}`, turn: turnCount }, executionId);
|
const data = { type: 'turn', content: `Turno ${turnCount}`, turn: turnCount };
|
||||||
|
bufferLine(executionId, data);
|
||||||
|
if (onData) onData(data, executionId);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (parsed.type === 'result') {
|
if (parsed.type === 'result') {
|
||||||
@@ -251,7 +273,9 @@ function processChildOutput(child, executionId, callbacks, options = {}) {
|
|||||||
}
|
}
|
||||||
const lines = str.split('\n').filter(l => l.trim());
|
const lines = str.split('\n').filter(l => l.trim());
|
||||||
for (const line of lines) {
|
for (const line of lines) {
|
||||||
if (onData) onData({ type: 'stderr', content: line.trim() }, executionId);
|
const data = { type: 'stderr', content: line.trim() };
|
||||||
|
bufferLine(executionId, data);
|
||||||
|
if (onData) onData(data, executionId);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -260,6 +284,7 @@ function processChildOutput(child, executionId, callbacks, options = {}) {
|
|||||||
console.log(`[executor][error] ${err.message}`);
|
console.log(`[executor][error] ${err.message}`);
|
||||||
hadError = true;
|
hadError = true;
|
||||||
activeExecutions.delete(executionId);
|
activeExecutions.delete(executionId);
|
||||||
|
executionOutputBuffers.delete(executionId);
|
||||||
if (onError) onError(err, executionId);
|
if (onError) onError(err, executionId);
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -267,6 +292,7 @@ function processChildOutput(child, executionId, callbacks, options = {}) {
|
|||||||
clearTimeout(timeout);
|
clearTimeout(timeout);
|
||||||
const wasCanceled = activeExecutions.get(executionId)?.canceled || false;
|
const wasCanceled = activeExecutions.get(executionId)?.canceled || false;
|
||||||
activeExecutions.delete(executionId);
|
activeExecutions.delete(executionId);
|
||||||
|
executionOutputBuffers.delete(executionId);
|
||||||
if (hadError) return;
|
if (hadError) return;
|
||||||
if (outputBuffer.trim()) processEvent(parseStreamLine(outputBuffer));
|
if (outputBuffer.trim()) processEvent(parseStreamLine(outputBuffer));
|
||||||
|
|
||||||
@@ -426,6 +452,7 @@ export function cancel(executionId) {
|
|||||||
export function cancelAllExecutions() {
|
export function cancelAllExecutions() {
|
||||||
for (const [, exec] of activeExecutions) exec.process.kill('SIGTERM');
|
for (const [, exec] of activeExecutions) exec.process.kill('SIGTERM');
|
||||||
activeExecutions.clear();
|
activeExecutions.clear();
|
||||||
|
executionOutputBuffers.clear();
|
||||||
}
|
}
|
||||||
|
|
||||||
export function getActiveExecutions() {
|
export function getActiveExecutions() {
|
||||||
@@ -433,6 +460,7 @@ export function getActiveExecutions() {
|
|||||||
executionId: exec.executionId,
|
executionId: exec.executionId,
|
||||||
startedAt: exec.startedAt,
|
startedAt: exec.startedAt,
|
||||||
agentConfig: exec.agentConfig,
|
agentConfig: exec.agentConfig,
|
||||||
|
outputBuffer: executionOutputBuffers.get(exec.executionId) || [],
|
||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user