Compare commits
3 Commits
a1d3ce707c
...
46f999c676
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
46f999c676 | ||
|
|
39f0902a0f | ||
|
|
fd3c2dc69a |
@@ -4,7 +4,8 @@ COPY package*.json ./
|
|||||||
RUN npm ci --omit=dev
|
RUN npm ci --omit=dev
|
||||||
RUN npm install -g @anthropic-ai/claude-code
|
RUN npm install -g @anthropic-ai/claude-code
|
||||||
COPY . .
|
COPY . .
|
||||||
RUN mkdir -p data
|
RUN mkdir -p data && chown -R node:node /app
|
||||||
|
USER node
|
||||||
ENV HOST=0.0.0.0
|
ENV HOST=0.0.0.0
|
||||||
ENV PORT=3000
|
ENV PORT=3000
|
||||||
EXPOSE 3000
|
EXPOSE 3000
|
||||||
|
|||||||
1386
public/app.html
Normal file
1386
public/app.html
Normal file
File diff suppressed because it is too large
Load Diff
1842
public/index.html
1842
public/index.html
File diff suppressed because it is too large
Load Diff
@@ -118,6 +118,7 @@ const API = {
|
|||||||
status() { return API.request('GET', '/system/status'); },
|
status() { return API.request('GET', '/system/status'); },
|
||||||
info() { return API.request('GET', '/system/info'); },
|
info() { return API.request('GET', '/system/info'); },
|
||||||
activeExecutions() { return API.request('GET', '/executions/active'); },
|
activeExecutions() { return API.request('GET', '/executions/active'); },
|
||||||
|
cancelAll() { return API.request('POST', '/executions/cancel-all'); },
|
||||||
},
|
},
|
||||||
|
|
||||||
settings: {
|
settings: {
|
||||||
|
|||||||
@@ -555,6 +555,18 @@ const App = {
|
|||||||
|
|
||||||
on('pipeline-execute-submit', 'click', () => PipelinesUI._executeFromModal());
|
on('pipeline-execute-submit', 'click', () => PipelinesUI._executeFromModal());
|
||||||
|
|
||||||
|
on('terminal-stop-btn', 'click', async () => {
|
||||||
|
try {
|
||||||
|
await API.system.cancelAll();
|
||||||
|
Terminal.stopProcessing();
|
||||||
|
Terminal.addLine('Todas as execuções foram interrompidas.', 'error');
|
||||||
|
Toast.warning('Execuções interrompidas');
|
||||||
|
App._updateActiveBadge();
|
||||||
|
} catch (err) {
|
||||||
|
Toast.error(`Erro ao interromper: ${err.message}`);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
on('terminal-clear-btn', 'click', () => {
|
on('terminal-clear-btn', 'click', () => {
|
||||||
Terminal.clear();
|
Terminal.clear();
|
||||||
Terminal.disableChat();
|
Terminal.disableChat();
|
||||||
@@ -960,6 +972,9 @@ const App = {
|
|||||||
if (countEl) countEl.textContent = count;
|
if (countEl) countEl.textContent = count;
|
||||||
if (badge) badge.style.display = count > 0 ? 'flex' : 'none';
|
if (badge) badge.style.display = count > 0 ? 'flex' : 'none';
|
||||||
|
|
||||||
|
const stopBtn = document.getElementById('terminal-stop-btn');
|
||||||
|
if (stopBtn) stopBtn.hidden = count === 0;
|
||||||
|
|
||||||
const terminalSelect = document.getElementById('terminal-execution-select');
|
const terminalSelect = document.getElementById('terminal-execution-select');
|
||||||
if (terminalSelect && Array.isArray(active)) {
|
if (terminalSelect && Array.isArray(active)) {
|
||||||
const existing = new Set(
|
const existing = new Set(
|
||||||
|
|||||||
@@ -102,7 +102,7 @@ const HistoryUI = {
|
|||||||
<i data-lucide="eye"></i>
|
<i data-lucide="eye"></i>
|
||||||
Ver detalhes
|
Ver detalhes
|
||||||
</button>
|
</button>
|
||||||
${(exec.status === 'error' && exec.type === 'pipeline' && exec.failedAtStep !== undefined) ? `
|
${(exec.status === 'error' && exec.type === 'pipeline') ? `
|
||||||
<button class="btn btn-ghost btn-sm" data-action="resume-pipeline" data-id="${exec.id}" type="button" title="Retomar do passo ${(exec.failedAtStep || 0) + 1}">
|
<button class="btn btn-ghost btn-sm" data-action="resume-pipeline" data-id="${exec.id}" type="button" title="Retomar do passo ${(exec.failedAtStep || 0) + 1}">
|
||||||
<i data-lucide="play"></i>
|
<i data-lucide="play"></i>
|
||||||
Retomar
|
Retomar
|
||||||
|
|||||||
@@ -362,6 +362,7 @@ export function resume(agentConfig, sessionId, message, callbacks = {}) {
|
|||||||
const model = agentConfig.model || 'claude-sonnet-4-6';
|
const model = agentConfig.model || 'claude-sonnet-4-6';
|
||||||
const args = [
|
const args = [
|
||||||
'--resume', sessionId,
|
'--resume', sessionId,
|
||||||
|
'-p', sanitizeText(message),
|
||||||
'--output-format', 'stream-json',
|
'--output-format', 'stream-json',
|
||||||
'--verbose',
|
'--verbose',
|
||||||
'--model', model,
|
'--model', model,
|
||||||
@@ -378,7 +379,7 @@ export function resume(agentConfig, sessionId, message, callbacks = {}) {
|
|||||||
|
|
||||||
const spawnOptions = {
|
const spawnOptions = {
|
||||||
env: cleanEnv(),
|
env: cleanEnv(),
|
||||||
stdio: ['pipe', 'pipe', 'pipe'],
|
stdio: ['ignore', 'pipe', 'pipe'],
|
||||||
};
|
};
|
||||||
|
|
||||||
if (agentConfig.workingDirectory && agentConfig.workingDirectory.trim()) {
|
if (agentConfig.workingDirectory && agentConfig.workingDirectory.trim()) {
|
||||||
@@ -388,8 +389,6 @@ export function resume(agentConfig, sessionId, message, callbacks = {}) {
|
|||||||
console.log(`[executor] Resumindo sessão: ${sessionId} | Execução: ${executionId}`);
|
console.log(`[executor] Resumindo sessão: ${sessionId} | Execução: ${executionId}`);
|
||||||
|
|
||||||
const child = spawn(CLAUDE_BIN, args, spawnOptions);
|
const child = spawn(CLAUDE_BIN, args, spawnOptions);
|
||||||
child.stdin.write(sanitizeText(message));
|
|
||||||
child.stdin.end();
|
|
||||||
|
|
||||||
activeExecutions.set(executionId, {
|
activeExecutions.set(executionId, {
|
||||||
process: child,
|
process: child,
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ import * as manager from '../agents/manager.js';
|
|||||||
import { tasksStore, settingsStore, executionsStore, webhooksStore, notificationsStore, secretsStore, agentVersionsStore } from '../store/db.js';
|
import { tasksStore, settingsStore, executionsStore, webhooksStore, notificationsStore, secretsStore, agentVersionsStore } from '../store/db.js';
|
||||||
import * as scheduler from '../agents/scheduler.js';
|
import * as scheduler from '../agents/scheduler.js';
|
||||||
import * as pipeline from '../agents/pipeline.js';
|
import * as pipeline from '../agents/pipeline.js';
|
||||||
import { getBinPath, updateMaxConcurrent } from '../agents/executor.js';
|
import { getBinPath, updateMaxConcurrent, cancelAllExecutions, getActiveExecutions } from '../agents/executor.js';
|
||||||
import { invalidateAgentMapCache } from '../agents/pipeline.js';
|
import { invalidateAgentMapCache } from '../agents/pipeline.js';
|
||||||
import { cached } from '../cache/index.js';
|
import { cached } from '../cache/index.js';
|
||||||
import { readdirSync, readFileSync, unlinkSync, existsSync, mkdirSync } from 'fs';
|
import { readdirSync, readFileSync, unlinkSync, existsSync, mkdirSync } from 'fs';
|
||||||
@@ -838,6 +838,23 @@ router.get('/executions/active', (req, res) => {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
router.post('/executions/cancel-all', (req, res) => {
|
||||||
|
try {
|
||||||
|
const activePipelines = pipeline.getActivePipelines();
|
||||||
|
for (const p of activePipelines) {
|
||||||
|
pipeline.cancelPipeline(p.pipelineId);
|
||||||
|
}
|
||||||
|
cancelAllExecutions();
|
||||||
|
const running = executionsStore.getAll().filter(e => e.status === 'running' || e.status === 'awaiting_approval');
|
||||||
|
for (const e of running) {
|
||||||
|
executionsStore.update(e.id, { status: 'canceled', endedAt: new Date().toISOString() });
|
||||||
|
}
|
||||||
|
res.json({ cancelled: true });
|
||||||
|
} catch (err) {
|
||||||
|
res.status(500).json({ error: err.message });
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
router.get('/executions/recent', (req, res) => {
|
router.get('/executions/recent', (req, res) => {
|
||||||
try {
|
try {
|
||||||
const limit = parseInt(req.query.limit) || 20;
|
const limit = parseInt(req.query.limit) || 20;
|
||||||
|
|||||||
Reference in New Issue
Block a user