Continuação de conversa no terminal, histórico de agendamentos, webhooks e melhorias gerais
- Terminal com input de chat: após execução, permite continuar conversa com o agente via --resume do CLI, mantendo contexto da sessão (sessionId persistido) - Nova rota POST /api/agents/:id/continue para retomar sessões - Executor com função resume() para spawnar claude com --resume <sessionId> - Histórico de agendamentos agora busca do executionsStore (persistente) com dados completos: agente, tarefa, status, duração, custo e link para detalhes no modal - Execuções de agendamento tagueadas com source:'schedule' e scheduleId - Correção da expressão cron duplicada na UI de agendamentos - cronToHuman trata expressões com minuto específico (ex: 37 3 * * * → Todo dia às 03:37) - Botão "Copiar cURL" nos cards de webhook com payload de exemplo contextual - Webhooks component (webhooks.js) adicionado ao repositório
This commit is contained in:
@@ -105,7 +105,7 @@ export function deleteAgent(id) {
|
||||
return agentsStore.delete(id);
|
||||
}
|
||||
|
||||
export function executeTask(agentId, task, instructions, wsCallback) {
|
||||
export function executeTask(agentId, task, instructions, wsCallback, metadata = {}) {
|
||||
const agent = agentsStore.getById(agentId);
|
||||
if (!agent) throw new Error(`Agente ${agentId} não encontrado`);
|
||||
if (agent.status !== 'active') throw new Error(`Agente ${agentId} está inativo`);
|
||||
@@ -116,6 +116,7 @@ export function executeTask(agentId, task, instructions, wsCallback) {
|
||||
|
||||
const historyRecord = executionsStore.create({
|
||||
type: 'agent',
|
||||
...metadata,
|
||||
agentId,
|
||||
agentName: agent.agent_name,
|
||||
task: taskText,
|
||||
@@ -154,6 +155,11 @@ export function executeTask(agentId, task, instructions, wsCallback) {
|
||||
result: result.result || '',
|
||||
exitCode: result.exitCode,
|
||||
endedAt,
|
||||
costUsd: result.costUsd || 0,
|
||||
totalCostUsd: result.totalCostUsd || 0,
|
||||
durationMs: result.durationMs || 0,
|
||||
numTurns: result.numTurns || 0,
|
||||
sessionId: result.sessionId || '',
|
||||
});
|
||||
if (cb) cb({ type: 'execution_complete', executionId: execId, agentId, data: result });
|
||||
},
|
||||
@@ -216,7 +222,7 @@ export function scheduleTask(agentId, taskDescription, cronExpression, wsCallbac
|
||||
schedulesStore.save(items);
|
||||
|
||||
scheduler.schedule(scheduleId, cronExpression, () => {
|
||||
executeTask(agentId, taskDescription, null, null);
|
||||
executeTask(agentId, taskDescription, null, null, { source: 'schedule', scheduleId });
|
||||
}, false);
|
||||
|
||||
return { scheduleId, agentId, agentName: agent.agent_name, taskDescription, cronExpression };
|
||||
@@ -234,13 +240,71 @@ export function updateScheduleTask(scheduleId, data, wsCallback) {
|
||||
const cronExpression = data.cronExpression || stored.cronExpression;
|
||||
|
||||
scheduler.updateSchedule(scheduleId, cronExpression, () => {
|
||||
executeTask(agentId, taskDescription, null, null);
|
||||
executeTask(agentId, taskDescription, null, null, { source: 'schedule', scheduleId });
|
||||
});
|
||||
|
||||
schedulesStore.update(scheduleId, { agentId, agentName: agent.agent_name, taskDescription, cronExpression });
|
||||
return schedulesStore.getById(scheduleId);
|
||||
}
|
||||
|
||||
export function continueConversation(agentId, sessionId, message, wsCallback) {
|
||||
const agent = agentsStore.getById(agentId);
|
||||
if (!agent) throw new Error(`Agente ${agentId} não encontrado`);
|
||||
|
||||
const cb = getWsCallback(wsCallback);
|
||||
const startedAt = new Date().toISOString();
|
||||
|
||||
const historyRecord = executionsStore.create({
|
||||
type: 'agent',
|
||||
agentId,
|
||||
agentName: agent.agent_name,
|
||||
task: message,
|
||||
status: 'running',
|
||||
startedAt,
|
||||
parentSessionId: sessionId,
|
||||
});
|
||||
|
||||
const executionId = executor.resume(
|
||||
agent.config,
|
||||
sessionId,
|
||||
message,
|
||||
{
|
||||
onData: (parsed, execId) => {
|
||||
if (cb) cb({ type: 'execution_output', executionId: execId, agentId, data: parsed });
|
||||
},
|
||||
onError: (err, execId) => {
|
||||
const endedAt = new Date().toISOString();
|
||||
executionsStore.update(historyRecord.id, { status: 'error', error: err.message, endedAt });
|
||||
if (cb) cb({ type: 'execution_error', executionId: execId, agentId, data: { error: err.message } });
|
||||
},
|
||||
onComplete: (result, execId) => {
|
||||
const endedAt = new Date().toISOString();
|
||||
executionsStore.update(historyRecord.id, {
|
||||
status: 'completed',
|
||||
result: result.result || '',
|
||||
exitCode: result.exitCode,
|
||||
endedAt,
|
||||
costUsd: result.costUsd || 0,
|
||||
totalCostUsd: result.totalCostUsd || 0,
|
||||
durationMs: result.durationMs || 0,
|
||||
numTurns: result.numTurns || 0,
|
||||
sessionId: result.sessionId || sessionId,
|
||||
});
|
||||
if (cb) cb({ type: 'execution_complete', executionId: execId, agentId, data: result });
|
||||
},
|
||||
}
|
||||
);
|
||||
|
||||
if (!executionId) {
|
||||
executionsStore.update(historyRecord.id, { status: 'error', error: 'Limite de execuções simultâneas atingido', endedAt: new Date().toISOString() });
|
||||
throw new Error('Limite de execuções simultâneas atingido');
|
||||
}
|
||||
|
||||
executionsStore.update(historyRecord.id, { executionId });
|
||||
incrementDailyCount();
|
||||
return executionId;
|
||||
}
|
||||
|
||||
export function cancelExecution(executionId) {
|
||||
return executor.cancel(executionId);
|
||||
}
|
||||
@@ -278,9 +342,9 @@ export function importAgent(data) {
|
||||
}
|
||||
|
||||
export function restoreSchedules() {
|
||||
scheduler.restoreSchedules((agentId, taskDescription) => {
|
||||
scheduler.restoreSchedules((agentId, taskDescription, scheduleId) => {
|
||||
try {
|
||||
executeTask(agentId, taskDescription, null, null);
|
||||
executeTask(agentId, taskDescription, null, null, { source: 'schedule', scheduleId });
|
||||
} catch (err) {
|
||||
console.log(`[manager] Erro ao executar tarefa agendada: ${err.message}`);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user