@liriraid/agentflow-ai 1.0.13 → 1.0.15
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/bin/agentflow.mjs +103 -3
- package/orchestrator.js +393 -113
- package/package.json +1 -1
- package/scripts/auto-trigger.js +114 -0
- package/scripts/monitor-check.js +172 -0
- package/src/ink/app.mjs +22 -14
- package/src/ink/index.mjs +23 -2
- package/templates/en/ORCHESTRATOR.md +53 -28
- package/templates/en/orchestrator.config.json +3 -3
- package/templates/es/ORCHESTRATOR.md +61 -22
- package/templates/es/orchestrator.config.json +5 -5
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@liriraid/agentflow-ai",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.15",
|
|
4
4
|
"description": "Multi-agent workspace orchestrator with TUI. Coordinates AI coding agents over your real frontend and backend projects.",
|
|
5
5
|
"author": "LiriRaid",
|
|
6
6
|
"homepage": "https://github.com/LiriRaid/agentflow-ai#readme",
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// ============================================================================
|
|
3
|
+
// Auto-trigger script - Ejecutar cada 60 segundos desde Windows Task Scheduler
|
|
4
|
+
// Detecta nuevo contenido en INBOX.md y dispara Claude headless para procesarlo
|
|
5
|
+
// ============================================================================
|
|
6
|
+
|
|
7
|
+
const fs = require('fs');
|
|
8
|
+
const path = require('path');
|
|
9
|
+
const { spawn } = require('child_process');
|
|
10
|
+
|
|
11
|
+
const WORKSPACE = process.env.ORCHESTRATOR_WORKSPACE || process.cwd();
|
|
12
|
+
const INBOX_FILE = path.join(WORKSPACE, 'INBOX.md');
|
|
13
|
+
const QUEUE_FILE = path.join(WORKSPACE, 'QUEUE.md');
|
|
14
|
+
const LAST_CHECK_FILE = path.join(WORKSPACE, 'logs', 'last-auto-check.json');
|
|
15
|
+
|
|
16
|
+
function timestamp() {
|
|
17
|
+
return new Date().toISOString().replace('T', ' ').slice(0, 19);
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
function detectLanguage() {
|
|
21
|
+
if (!fs.existsSync(QUEUE_FILE)) return 'en';
|
|
22
|
+
try {
|
|
23
|
+
const content = fs.readFileSync(QUEUE_FILE, 'utf-8');
|
|
24
|
+
return (content.includes('## Pendientes') || content.includes('## Completadas')) ? 'es' : 'en';
|
|
25
|
+
} catch { return 'en'; }
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
const lang = detectLanguage();
|
|
29
|
+
|
|
30
|
+
// Leer último hash guardado
|
|
31
|
+
let lastCheck = { time: 0, inboxHash: '' };
|
|
32
|
+
try {
|
|
33
|
+
if (fs.existsSync(LAST_CHECK_FILE)) {
|
|
34
|
+
lastCheck = JSON.parse(fs.readFileSync(LAST_CHECK_FILE, 'utf-8'));
|
|
35
|
+
}
|
|
36
|
+
} catch {}
|
|
37
|
+
|
|
38
|
+
// Leer INBOX actual
|
|
39
|
+
let inboxContent = '';
|
|
40
|
+
let currentHash = '';
|
|
41
|
+
try {
|
|
42
|
+
if (fs.existsSync(INBOX_FILE)) {
|
|
43
|
+
inboxContent = fs.readFileSync(INBOX_FILE, 'utf-8');
|
|
44
|
+
currentHash = inboxContent.slice(0, 500);
|
|
45
|
+
}
|
|
46
|
+
} catch {}
|
|
47
|
+
|
|
48
|
+
// Si no hay contenido o no cambió, salir
|
|
49
|
+
if (!inboxContent.trim() || currentHash === lastCheck.inboxHash) {
|
|
50
|
+
console.log(`[${timestamp()}] Sin cambios en INBOX. Nada que procesar.`);
|
|
51
|
+
process.exit(0);
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
console.log(`[${timestamp()}] Nuevo contenido en INBOX detectado — disparando Claude...`);
|
|
55
|
+
|
|
56
|
+
// Guardar hash para no relanzar en el próximo ciclo de 60s
|
|
57
|
+
lastCheck = { time: Date.now(), inboxHash: currentHash };
|
|
58
|
+
fs.mkdirSync(path.dirname(LAST_CHECK_FILE), { recursive: true });
|
|
59
|
+
fs.writeFileSync(LAST_CHECK_FILE, JSON.stringify(lastCheck), 'utf-8');
|
|
60
|
+
|
|
61
|
+
// Prompt para Claude headless — lee INBOX y crea la task de implementación si aplica
|
|
62
|
+
const prompt = lang === 'es'
|
|
63
|
+
? `Eres el orquestador de este workspace. Tu única misión ahora es procesar el INBOX.
|
|
64
|
+
|
|
65
|
+
Pasos:
|
|
66
|
+
1. Lee INBOX.md en ${WORKSPACE}
|
|
67
|
+
2. Lee QUEUE.md en ${WORKSPACE} para ver las tareas existentes (secciones Pendientes, En progreso, Completadas)
|
|
68
|
+
|
|
69
|
+
Si en INBOX.md hay análisis completados de un agente (especialmente OpenCode) que aún NO tienen su tarea de implementación en la sección ## Pendientes de QUEUE.md:
|
|
70
|
+
- Determina el siguiente TASK ID disponible leyendo QUEUE.md
|
|
71
|
+
- Crea la nueva TASK en QUEUE.md con el formato exacto:
|
|
72
|
+
TASK-NNN | título corto | Codex | P1 | repo | descripción basada en el análisis
|
|
73
|
+
|
|
74
|
+
Si ya existe la tarea correspondiente, o el análisis no está completo, responde solo: "Sin acción necesaria."
|
|
75
|
+
|
|
76
|
+
Reglas: No hagas commit ni push. No analices código del proyecto. Solo lee INBOX.md y QUEUE.md, y edita QUEUE.md si hace falta.`
|
|
77
|
+
: `You are the orchestrator for this workspace. Your only mission now is to process the INBOX.
|
|
78
|
+
|
|
79
|
+
Steps:
|
|
80
|
+
1. Read INBOX.md in ${WORKSPACE}
|
|
81
|
+
2. Read QUEUE.md in ${WORKSPACE} to see existing tasks (sections Pending, In Progress, Completed)
|
|
82
|
+
|
|
83
|
+
If INBOX.md contains completed analyses from an agent (especially OpenCode) that do NOT yet have a corresponding implementation task in the ## Pending section of QUEUE.md:
|
|
84
|
+
- Determine the next available TASK ID by reading QUEUE.md
|
|
85
|
+
- Create the new TASK in QUEUE.md with the exact format:
|
|
86
|
+
TASK-NNN | short title | Codex | P1 | repo | description based on the analysis
|
|
87
|
+
|
|
88
|
+
If the corresponding task already exists, or the analysis is not complete, reply only: "No action needed."
|
|
89
|
+
|
|
90
|
+
Rules: Do not commit or push. Do not analyze project code. Only read INBOX.md and QUEUE.md, and edit QUEUE.md if necessary.`;
|
|
91
|
+
|
|
92
|
+
const claude = spawn('claude', [
|
|
93
|
+
'-p', prompt,
|
|
94
|
+
'--add-dir', WORKSPACE,
|
|
95
|
+
'--dangerously-skip-permissions'
|
|
96
|
+
], {
|
|
97
|
+
cwd: WORKSPACE,
|
|
98
|
+
stdio: ['ignore', 'pipe', 'pipe'],
|
|
99
|
+
shell: true
|
|
100
|
+
});
|
|
101
|
+
|
|
102
|
+
let output = '';
|
|
103
|
+
claude.stdout.on('data', d => { output += d.toString(); });
|
|
104
|
+
claude.stderr.on('data', d => { process.stderr.write(d); });
|
|
105
|
+
|
|
106
|
+
claude.on('close', code => {
|
|
107
|
+
const result = output.trim().slice(0, 300);
|
|
108
|
+
console.log(`[${timestamp()}] Claude completó (exit ${code}): ${result}`);
|
|
109
|
+
});
|
|
110
|
+
|
|
111
|
+
claude.on('error', err => {
|
|
112
|
+
console.error(`[${timestamp()}] Error al lanzar Claude: ${err.message}`);
|
|
113
|
+
process.exit(1);
|
|
114
|
+
});
|
|
@@ -0,0 +1,172 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// ============================================================================
|
|
3
|
+
// Monitor script - Ejecutar cada 5 minutos desde Windows Task Scheduler
|
|
4
|
+
// SOLO corre cuando Modo Ausencia está activado (.away-mode existe)
|
|
5
|
+
// En cada revisión manda un prompt a Claude para que monitoree y tome decisiones
|
|
6
|
+
// ============================================================================
|
|
7
|
+
|
|
8
|
+
const fs = require('fs');
|
|
9
|
+
const path = require('path');
|
|
10
|
+
const { spawn } = require('child_process');
|
|
11
|
+
|
|
12
|
+
const WORKSPACE = process.env.ORCHESTRATOR_WORKSPACE || process.cwd();
|
|
13
|
+
const QUEUE_FILE = path.join(WORKSPACE, 'QUEUE.md');
|
|
14
|
+
const INBOX_FILE = path.join(WORKSPACE, 'INBOX.md');
|
|
15
|
+
const STATE_FILE = path.join(WORKSPACE, 'logs', 'orchestrator-state.json');
|
|
16
|
+
const ACTIONS_FILE = path.join(WORKSPACE, 'ACTIONS.md');
|
|
17
|
+
const AWAY_MODE_FILE = path.join(WORKSPACE, '.away-mode');
|
|
18
|
+
|
|
19
|
+
// Si Away Mode no está activado, salir inmediatamente
|
|
20
|
+
if (!fs.existsSync(AWAY_MODE_FILE)) {
|
|
21
|
+
console.log('Monitor: Away Mode no activado. Saltando.');
|
|
22
|
+
process.exit(0);
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
function timestamp() {
|
|
26
|
+
return new Date().toISOString().replace('T', ' ').slice(0, 19);
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
function detectLanguage() {
|
|
30
|
+
if (!fs.existsSync(QUEUE_FILE)) return 'en';
|
|
31
|
+
try {
|
|
32
|
+
const content = fs.readFileSync(QUEUE_FILE, 'utf-8');
|
|
33
|
+
return (content.includes('## Pendientes') || content.includes('## Completadas')) ? 'es' : 'en';
|
|
34
|
+
} catch { return 'en'; }
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
const lang = detectLanguage();
|
|
38
|
+
|
|
39
|
+
function readQueue() {
|
|
40
|
+
if (!fs.existsSync(QUEUE_FILE)) return { pending: [], inProgress: [], completed: [] };
|
|
41
|
+
const result = { pending: [], inProgress: [], completed: [] };
|
|
42
|
+
let section = '';
|
|
43
|
+
for (const line of fs.readFileSync(QUEUE_FILE, 'utf-8').split('\n')) {
|
|
44
|
+
const trimmed = line.trim();
|
|
45
|
+
if (trimmed.startsWith('## Pending') || trimmed.startsWith('## Pendientes')) { section = 'pending'; continue; }
|
|
46
|
+
if (trimmed.startsWith('## In Progress') || trimmed.startsWith('## En progreso')) { section = 'inProgress'; continue; }
|
|
47
|
+
if (trimmed.startsWith('## Completed') || trimmed.startsWith('## Completadas')) { section = 'completed'; continue; }
|
|
48
|
+
if (!trimmed || trimmed.startsWith('#') || trimmed.startsWith('>')) continue;
|
|
49
|
+
if (section && trimmed.includes('|')) result[section].push(trimmed);
|
|
50
|
+
}
|
|
51
|
+
return result;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
function readState() {
|
|
55
|
+
if (!fs.existsSync(STATE_FILE)) return null;
|
|
56
|
+
try { return JSON.parse(fs.readFileSync(STATE_FILE, 'utf-8')); }
|
|
57
|
+
catch { return null; }
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
function launchClaude(prompt) {
|
|
61
|
+
const claude = spawn('claude', [
|
|
62
|
+
'-p', prompt,
|
|
63
|
+
'--add-dir', WORKSPACE,
|
|
64
|
+
'--dangerously-skip-permissions'
|
|
65
|
+
], {
|
|
66
|
+
cwd: WORKSPACE,
|
|
67
|
+
stdio: 'inherit',
|
|
68
|
+
shell: true
|
|
69
|
+
});
|
|
70
|
+
claude.on('error', err => console.error(`[${timestamp()}] Error lanzando Claude: ${err.message}`));
|
|
71
|
+
return claude;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
// ============================================================================
|
|
75
|
+
// RECOPILAR ESTADO ACTUAL
|
|
76
|
+
// ============================================================================
|
|
77
|
+
const queue = readQueue();
|
|
78
|
+
const state = readState();
|
|
79
|
+
|
|
80
|
+
const busyAgents = Object.values(state?.agents || {}).filter(a => a.status === 'busy').length;
|
|
81
|
+
const failedAgents = Object.entries(state?.agents || {})
|
|
82
|
+
.filter(([, ag]) => (ag.lastLine || '').startsWith('FALLÓ:') || (ag.lastLine || '').startsWith('FAILED:'))
|
|
83
|
+
.map(([name, ag]) => `${name}: ${ag.lastLine}`);
|
|
84
|
+
const retryingAgents = Object.entries(state?.agents || {})
|
|
85
|
+
.filter(([, ag]) => {
|
|
86
|
+
const ll = ag.lastLine || '';
|
|
87
|
+
return ll.startsWith('REINTENTO:') || ll.startsWith('LÍMITE:') || ll.startsWith('RETRY:');
|
|
88
|
+
})
|
|
89
|
+
.map(([name, ag]) => `${name}: ${ag.lastLine}`);
|
|
90
|
+
|
|
91
|
+
const hasWork = queue.pending.length > 0 || (state?.inProgress?.length || 0) > 0 || busyAgents > 0;
|
|
92
|
+
|
|
93
|
+
console.log(`[${timestamp()}] Monitor Modo Ausencia:`);
|
|
94
|
+
console.log(` Pendientes: ${queue.pending.length} | En progreso: ${state?.inProgress?.length || 0} | Completadas: ${queue.completed.length}`);
|
|
95
|
+
console.log(` Agentes ocupados: ${busyAgents} | Fallidos: ${failedAgents.length} | Reintentando: ${retryingAgents.length}`);
|
|
96
|
+
|
|
97
|
+
// ============================================================================
|
|
98
|
+
// SI NO HAY TRABAJO → DESACTIVAR Y DAR RESUMEN FINAL
|
|
99
|
+
// ============================================================================
|
|
100
|
+
if (!hasWork && queue.completed.length > 0) {
|
|
101
|
+
console.log(` -> Sin trabajo pendiente. Desactivando Modo Ausencia.`);
|
|
102
|
+
|
|
103
|
+
try { fs.unlinkSync(AWAY_MODE_FILE); } catch {}
|
|
104
|
+
try { if (fs.existsSync(ACTIONS_FILE)) fs.unlinkSync(ACTIONS_FILE); } catch {}
|
|
105
|
+
|
|
106
|
+
const donePrompt = lang === 'es'
|
|
107
|
+
? `Modo Ausencia terminado. Todas las tareas se completaron mientras estabas ausente.
|
|
108
|
+
|
|
109
|
+
Lee QUEUE.md en ${WORKSPACE} y dame un resumen de todo lo que se logró durante la sesión.
|
|
110
|
+
Luego dime si hay algo que podamos continuar o integrar a partir de lo que ya se hizo, o pregúntame qué quiero priorizar a continuación.`
|
|
111
|
+
: `Away Mode ended. All tasks were completed while you were away.
|
|
112
|
+
|
|
113
|
+
Read QUEUE.md in ${WORKSPACE} and give me a summary of everything accomplished during the session.
|
|
114
|
+
Then tell me if there is anything we can continue or integrate from what was done, or ask me what I want to prioritize next.`;
|
|
115
|
+
|
|
116
|
+
launchClaude(donePrompt);
|
|
117
|
+
process.exit(0);
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
// ============================================================================
|
|
121
|
+
// HAY TRABAJO → MANDAR PROMPT A CLAUDE PARA QUE MONITOREE Y TOME DECISIONES
|
|
122
|
+
// ============================================================================
|
|
123
|
+
|
|
124
|
+
// Construir contexto de estado para incluir en el prompt
|
|
125
|
+
const stateLines = [];
|
|
126
|
+
if (queue.pending.length > 0) {
|
|
127
|
+
stateLines.push(`Tareas pendientes en cola: ${queue.pending.length}`);
|
|
128
|
+
queue.pending.slice(0, 5).forEach(t => stateLines.push(` - ${t.split('|').slice(0, 3).join('|').trim()}`));
|
|
129
|
+
}
|
|
130
|
+
if (state?.inProgress?.length > 0) {
|
|
131
|
+
stateLines.push(`Tareas en progreso: ${state.inProgress.map(t => `${t.id} (${t.agent})`).join(', ')}`);
|
|
132
|
+
}
|
|
133
|
+
if (failedAgents.length > 0) {
|
|
134
|
+
stateLines.push(`Agentes con fallo: ${failedAgents.join(' | ')}`);
|
|
135
|
+
}
|
|
136
|
+
if (retryingAgents.length > 0) {
|
|
137
|
+
stateLines.push(`Agentes reintentando: ${retryingAgents.join(' | ')}`);
|
|
138
|
+
}
|
|
139
|
+
if (queue.completed.length > 0) {
|
|
140
|
+
stateLines.push(`Tareas completadas hasta ahora: ${queue.completed.length}`);
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
const stateContext = stateLines.join('\n');
|
|
144
|
+
|
|
145
|
+
const monitorPrompt = lang === 'es'
|
|
146
|
+
? `Modo Ausencia activo — revisión automática cada 5 minutos.
|
|
147
|
+
|
|
148
|
+
Estado actual del orquestador:
|
|
149
|
+
${stateContext}
|
|
150
|
+
|
|
151
|
+
Instrucciones:
|
|
152
|
+
1. Lee INBOX.md en ${WORKSPACE} — si hay análisis completados de agentes que aún no tienen su tarea de implementación en QUEUE.md, crea la TASK correspondiente
|
|
153
|
+
2. Lee QUEUE.md en ${WORKSPACE} — si hay tareas fallidas que la TUI no pudo reasignar automáticamente (marcadas como failed), reasígnalas manualmente al siguiente agente disponible
|
|
154
|
+
3. Si hay agentes idle y tareas pendientes que no se están procesando, revisa si hay un problema de dependencias o bloqueo y resuélvelo
|
|
155
|
+
4. Si detectas que el trabajo avanza normalmente, no hagas nada y responde brevemente el estado
|
|
156
|
+
|
|
157
|
+
No hagas commit ni push. No inventes tareas nuevas fuera del alcance actual.`
|
|
158
|
+
: `Away Mode active — automatic check every 5 minutes.
|
|
159
|
+
|
|
160
|
+
Current orchestrator state:
|
|
161
|
+
${stateContext}
|
|
162
|
+
|
|
163
|
+
Instructions:
|
|
164
|
+
1. Read INBOX.md in ${WORKSPACE} — if there are completed agent analyses without a corresponding implementation task in QUEUE.md, create the TASK
|
|
165
|
+
2. Read QUEUE.md in ${WORKSPACE} — if there are failed tasks that the TUI could not auto-reassign (marked as failed), manually reassign to the next available agent
|
|
166
|
+
3. If there are idle agents and pending tasks not being processed, check for dependency or blocking issues and resolve them
|
|
167
|
+
4. If work is progressing normally, do nothing and briefly report the status
|
|
168
|
+
|
|
169
|
+
Do not commit or push. Do not invent new tasks outside the current scope.`;
|
|
170
|
+
|
|
171
|
+
console.log(` -> Disparando prompt a Claude para monitoreo...`);
|
|
172
|
+
launchClaude(monitorPrompt);
|
package/src/ink/app.mjs
CHANGED
|
@@ -19,6 +19,8 @@ const TEXT = {
|
|
|
19
19
|
es: {
|
|
20
20
|
busy: 'Ocupado',
|
|
21
21
|
idle: 'En espera',
|
|
22
|
+
failed: 'Falló',
|
|
23
|
+
retrying: 'Reintentando',
|
|
22
24
|
noTask: 'Sin tarea activa',
|
|
23
25
|
ready: 'Listo para trabajar',
|
|
24
26
|
project: 'Proyecto',
|
|
@@ -31,9 +33,7 @@ const TEXT = {
|
|
|
31
33
|
paused: 'Pausado',
|
|
32
34
|
preview: 'Explorando Ink',
|
|
33
35
|
shortcuts: 'Atajos',
|
|
34
|
-
start: 'iniciar/reanudar',
|
|
35
36
|
pause: 'pausar',
|
|
36
|
-
reload: 'recargar QUEUE.md',
|
|
37
37
|
quit: 'salir y matar agentes',
|
|
38
38
|
summary: 'Resumen',
|
|
39
39
|
activeQueue: 'Cola activa',
|
|
@@ -43,6 +43,8 @@ const TEXT = {
|
|
|
43
43
|
en: {
|
|
44
44
|
busy: 'Busy',
|
|
45
45
|
idle: 'Idle',
|
|
46
|
+
failed: 'Failed',
|
|
47
|
+
retrying: 'Retrying',
|
|
46
48
|
noTask: 'No active task',
|
|
47
49
|
ready: 'Ready to work',
|
|
48
50
|
project: 'Project',
|
|
@@ -55,9 +57,7 @@ const TEXT = {
|
|
|
55
57
|
paused: 'Paused',
|
|
56
58
|
preview: 'Exploring Ink',
|
|
57
59
|
shortcuts: 'Shortcuts',
|
|
58
|
-
start: 'start/resume',
|
|
59
60
|
pause: 'pause',
|
|
60
|
-
reload: 'reload QUEUE.md',
|
|
61
61
|
quit: 'quit and stop agents',
|
|
62
62
|
summary: 'Summary',
|
|
63
63
|
activeQueue: 'Active Queue',
|
|
@@ -82,7 +82,20 @@ const Panel = ({title, width, children}) =>
|
|
|
82
82
|
);
|
|
83
83
|
|
|
84
84
|
const AgentCard = ({agent, text}) => {
|
|
85
|
-
const statusColor =
|
|
85
|
+
const statusColor =
|
|
86
|
+
agent.status === 'busy' ? COLORS.success :
|
|
87
|
+
agent.status === 'failed' ? 'red' :
|
|
88
|
+
agent.status === 'retrying' ? COLORS.warning :
|
|
89
|
+
COLORS.muted;
|
|
90
|
+
|
|
91
|
+
const statusLabel =
|
|
92
|
+
agent.status === 'busy' ? text.busy :
|
|
93
|
+
agent.status === 'failed' ? text.failed :
|
|
94
|
+
agent.status === 'retrying' ? text.retrying :
|
|
95
|
+
text.idle;
|
|
96
|
+
|
|
97
|
+
const costLine = agent.totalCost > 0 ? `${text.cost}: $${agent.totalCost.toFixed(2)}` : null;
|
|
98
|
+
|
|
86
99
|
return h(
|
|
87
100
|
Box,
|
|
88
101
|
{
|
|
@@ -94,13 +107,14 @@ const AgentCard = ({agent, text}) => {
|
|
|
94
107
|
flexDirection: 'column'
|
|
95
108
|
},
|
|
96
109
|
h(Text, {bold: true, wrap: 'wrap'}, agent.name),
|
|
97
|
-
h(Text, {color: statusColor
|
|
110
|
+
h(Text, {color: statusColor, bold: agent.status === 'failed' || agent.status === 'retrying'}, statusLabel),
|
|
98
111
|
h(
|
|
99
112
|
Text,
|
|
100
113
|
{color: COLORS.muted, wrap: 'wrap'},
|
|
101
114
|
agent.task || text.noTask
|
|
102
115
|
),
|
|
103
|
-
h(Text, {color: COLORS.muted, wrap: 'wrap'}, agent.detail || text.ready)
|
|
116
|
+
h(Text, {color: COLORS.muted, wrap: 'wrap'}, agent.detail || text.ready),
|
|
117
|
+
...(costLine ? [h(Text, {color: COLORS.success, wrap: 'wrap'}, costLine)] : [])
|
|
104
118
|
);
|
|
105
119
|
};
|
|
106
120
|
|
|
@@ -123,8 +137,6 @@ export function App({snapshot, paused = false, onAction}) {
|
|
|
123
137
|
}
|
|
124
138
|
|
|
125
139
|
const normalized = input.toLowerCase();
|
|
126
|
-
if (normalized === 'r') onAction?.('reload');
|
|
127
|
-
if (normalized === 's') onAction?.('start');
|
|
128
140
|
if (normalized === 'p') onAction?.('pause');
|
|
129
141
|
if (normalized === 'q') onAction?.('quit');
|
|
130
142
|
});
|
|
@@ -173,14 +185,10 @@ export function App({snapshot, paused = false, onAction}) {
|
|
|
173
185
|
Box,
|
|
174
186
|
{marginBottom: 1},
|
|
175
187
|
h(Text, {color: COLORS.warning}, `${text.shortcuts}: `),
|
|
176
|
-
h(Text, {bold: true}, 'S'),
|
|
177
|
-
h(Text, {color: COLORS.muted}, truncate(` ${text.start} `, Math.max(0, shortcutWidth - 40))),
|
|
178
188
|
h(Text, {bold: true}, 'P'),
|
|
179
189
|
h(Text, {color: COLORS.muted}, truncate(` ${text.pause} `, Math.max(0, shortcutWidth - 55))),
|
|
180
|
-
h(Text, {bold: true}, 'R'),
|
|
181
|
-
h(Text, {color: COLORS.muted}, truncate(` ${text.reload} `, Math.max(0, shortcutWidth - 70))),
|
|
182
190
|
h(Text, {bold: true}, 'Q'),
|
|
183
|
-
h(Text, {color: COLORS.muted}, truncate(` ${text.quit}`, Math.max(0, shortcutWidth -
|
|
191
|
+
h(Text, {color: COLORS.muted}, truncate(` ${text.quit}`, Math.max(0, shortcutWidth - 70)))
|
|
184
192
|
),
|
|
185
193
|
h(
|
|
186
194
|
Box,
|
package/src/ink/index.mjs
CHANGED
|
@@ -20,6 +20,7 @@ const CONTROL_FILE = path.join(ROOT, 'logs', 'orchestrator-control.json');
|
|
|
20
20
|
|
|
21
21
|
const argv = process.argv.slice(2);
|
|
22
22
|
const startPaused = argv.includes('--paused');
|
|
23
|
+
const startYolo = argv.includes('--yolo');
|
|
23
24
|
const TEXT = {
|
|
24
25
|
es: {
|
|
25
26
|
configMissing: root =>
|
|
@@ -218,14 +219,33 @@ function buildSnapshot() {
|
|
|
218
219
|
|
|
219
220
|
const agents = Object.entries(config.agents || {}).map(([name]) => {
|
|
220
221
|
const agent = engineState.agents?.[name];
|
|
222
|
+
const lastLine = agent?.lastLine || '';
|
|
223
|
+
|
|
224
|
+
let status;
|
|
225
|
+
if (agent?.status === 'busy') {
|
|
226
|
+
status = 'busy';
|
|
227
|
+
} else if (lastLine.startsWith('FALLÓ:') || lastLine.startsWith('FAILED:')) {
|
|
228
|
+
status = 'failed';
|
|
229
|
+
} else if (
|
|
230
|
+
lastLine.startsWith('REINTENTO:') ||
|
|
231
|
+
lastLine.startsWith('LÍMITE:') ||
|
|
232
|
+
lastLine.startsWith('RETRY:') ||
|
|
233
|
+
lastLine.startsWith('LIMIT:')
|
|
234
|
+
) {
|
|
235
|
+
status = 'retrying';
|
|
236
|
+
} else {
|
|
237
|
+
status = 'idle';
|
|
238
|
+
}
|
|
239
|
+
|
|
221
240
|
return {
|
|
222
241
|
name,
|
|
223
|
-
status
|
|
242
|
+
status,
|
|
224
243
|
task: agent?.task ? `${agent.task.id} · ${agent.task.title}` : null,
|
|
225
244
|
detail:
|
|
226
245
|
agent?.status === 'busy'
|
|
227
246
|
? `${agent.task?.priority || 'P?'} · ${agent.task?.repo || 'repo'}`
|
|
228
|
-
:
|
|
247
|
+
: lastLine || text.ready,
|
|
248
|
+
totalCost: agent?.totalCost || 0
|
|
229
249
|
};
|
|
230
250
|
});
|
|
231
251
|
|
|
@@ -266,6 +286,7 @@ function ensureEngine() {
|
|
|
266
286
|
|
|
267
287
|
const childArgs = [ENGINE_FILE, '--headless'];
|
|
268
288
|
if (startPaused) childArgs.push('--paused');
|
|
289
|
+
if (startYolo) childArgs.push('--yolo');
|
|
269
290
|
|
|
270
291
|
pushLocalEvent(
|
|
271
292
|
startPaused ? text.startPaused : text.startRunning
|
|
@@ -64,44 +64,63 @@ When the user says something like `Read ORCHESTRATOR.md and start`, do this:
|
|
|
64
64
|
|
|
65
65
|
1. Read this file completely.
|
|
66
66
|
2. Read `orchestrator.config.json` — identify the real project paths in `repos` (frontend, backend). Those are the paths where the worker agents operate.
|
|
67
|
-
3.
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
67
|
+
3. **Verify script automation:** check if `logs/schedule-configured.json` exists.
|
|
68
|
+
- If it does **NOT exist**: run `agentflow schedule` in the workspace directory to register `auto-trigger.js` (every 1 min) and `monitor-check.js` (every 5 min) in the task scheduler. Then create `logs/schedule-configured.json` with `{"configuredAt": "<date>"}`. Inform the user that automation is ready.
|
|
69
|
+
- If it **already exists**: continue without doing anything.
|
|
70
|
+
4. Read `<projectName>-plan.md`, `PLAN.md`, or `plan.md` if present.
|
|
71
|
+
5. Read the newest `handoffs/HANDOFF-*.md` if the folder exists.
|
|
72
|
+
6. **Read `INBOX.md` if it exists** — it contains automatic TUI notifications of completed tasks that require your attention (creating next TASKs, reading agent reports, etc.).
|
|
73
|
+
7. Read `QUEUE.md` to understand pending, active, and completed work.
|
|
74
|
+
8. Read all `progress/PROGRESS-*.md` files if present.
|
|
75
|
+
9. Read `ENGRAM.md` and follow the memory rules.
|
|
76
|
+
10. Use `openspec/` for large or multi-phase changes.
|
|
77
|
+
11. Tell the user the orchestrator is ready and ask what to prioritize.
|
|
75
78
|
|
|
76
79
|
**INBOX rule:** At the start of EACH response, if `INBOX.md` has new entries since your last read, check it first. This is how you know when an agent finished and what to create next — without Away Mode active.
|
|
77
80
|
|
|
81
|
+
**STATUS rule:** Also read `STATUS.md` at the start of each response to get current context of agents and queue. This file updates automatically every 60 seconds.
|
|
82
|
+
|
|
83
|
+
**ACTIONS rule:** If `ACTIONS.md` exists, read it too — it contains automatic monitoring actions (completed tasks needing follow-up, failed tasks, stuck tasks, etc).
|
|
84
|
+
|
|
78
85
|
Startup is context loading only. Do not create project code changes during startup.
|
|
79
86
|
|
|
80
87
|
## Away Mode
|
|
81
88
|
|
|
82
89
|
If the user says something like:
|
|
83
90
|
|
|
91
|
+
- `I will be away for 1 hour`
|
|
84
92
|
- `I will be away for 2 hours`
|
|
85
|
-
- `
|
|
86
|
-
- `
|
|
87
|
-
- `continue while I am away`
|
|
88
|
-
|
|
89
|
-
enter **Away Mode** for that session.
|
|
90
|
-
|
|
91
|
-
In Away Mode:
|
|
92
|
-
|
|
93
|
-
1. Check work state every 5 minutes.
|
|
94
|
-
2. Read `QUEUE.md`, completed tasks, active tasks, idle agents, progress files, and blocked tasks.
|
|
95
|
-
3. Assign new useful TASKs when agents become idle, as long as the work stays within the user's stated goal.
|
|
96
|
-
4. Update `QUEUE.md` and `TASKS.md` when work needs splitting, dependency cleanup, or a next batch.
|
|
97
|
-
5. Keep progress moving without inventing new product scope.
|
|
93
|
+
- `going out for a while`
|
|
94
|
+
- `activate monitoring`
|
|
98
95
|
|
|
99
|
-
Away Mode
|
|
96
|
+
**Activate Away Mode:**
|
|
97
|
+
```bash
|
|
98
|
+
echo away > .away-mode
|
|
99
|
+
```
|
|
100
100
|
|
|
101
|
-
-
|
|
102
|
-
-
|
|
103
|
-
-
|
|
104
|
-
-
|
|
101
|
+
**The monitor-check.js script will run every 5 minutes** and check:
|
|
102
|
+
- Completed tasks without follow-up
|
|
103
|
+
- Failed tasks
|
|
104
|
+
- Stuck tasks (>10 min)
|
|
105
|
+
- And write to ACTIONS.md
|
|
106
|
+
|
|
107
|
+
**Auto-deactivate:**
|
|
108
|
+
When there are NO pending tasks AND NO agents working AND all tasks are completed:
|
|
109
|
+
- The script removes .away-mode automatically
|
|
110
|
+
- Away Mode deactivates by itself
|
|
111
|
+
- When you return and say "I'm back" → Clayde responds normally
|
|
112
|
+
|
|
113
|
+
**The monitor-check.js script will run every 5 minutes** and check:
|
|
114
|
+
- Completed tasks without follow-up
|
|
115
|
+
- Failed tasks
|
|
116
|
+
- Stuck tasks (>10 min)
|
|
117
|
+
- And write to ACTIONS.md so when you return, Claude reads it automatically
|
|
118
|
+
|
|
119
|
+
**Deactivate Away Mode:**
|
|
120
|
+
```bash
|
|
121
|
+
# Delete indicator file
|
|
122
|
+
del .away-mode
|
|
123
|
+
```
|
|
105
124
|
|
|
106
125
|
## Fallback Policy
|
|
107
126
|
|
|
@@ -142,7 +161,13 @@ Default agent summary:
|
|
|
142
161
|
|
|
143
162
|
## How To Assign Work
|
|
144
163
|
|
|
145
|
-
|
|
164
|
+
1. **When the user asks for a change or new task** → **NEVER analyze directly yourself**
|
|
165
|
+
- **First**: Create a TASK in `QUEUE.md` assigned to **OpenCode** to analyze the context
|
|
166
|
+
- **Second**: Wait for OpenCode to finish its analysis (check INBOX.md or progress/)
|
|
167
|
+
- **Third**: You receive the analysis → create new TASK to implement (Codex or OpenCode)
|
|
168
|
+
- **Never analyze the project code yourself** - that's OpenCode's job
|
|
169
|
+
|
|
170
|
+
2. Write TASKs in `QUEUE.md` with this format:
|
|
146
171
|
|
|
147
172
|
```text
|
|
148
173
|
TASK-NNN | short title | Agent | P1 | repo | detailed description
|
|
@@ -155,7 +180,7 @@ Rules:
|
|
|
155
180
|
3. Add `> after:TASK-NNN` at the end of the description for dependencies.
|
|
156
181
|
4. Use `TASKS.md` under `### TASK-NNN` for longer task specs when needed.
|
|
157
182
|
5. Use `briefs/TASK-NNN-BRIEF.md` for very detailed briefs when needed.
|
|
158
|
-
6.
|
|
183
|
+
6. **The TUI starts automatically** - you don't need to press R or S. The TUI detects new tasks and launches them.
|
|
159
184
|
|
|
160
185
|
Routing preferences:
|
|
161
186
|
|
|
@@ -71,14 +71,14 @@
|
|
|
71
71
|
"cli": "gemini",
|
|
72
72
|
"profile": "gemini",
|
|
73
73
|
"defaultRepo": "frontend",
|
|
74
|
-
"model": "
|
|
74
|
+
"model": "auto",
|
|
75
75
|
"instructionsFile": "agents/GEMINI.md"
|
|
76
76
|
},
|
|
77
77
|
"OpenCode": {
|
|
78
78
|
"cli": "opencode",
|
|
79
79
|
"profile": "opencode",
|
|
80
80
|
"defaultRepo": "frontend",
|
|
81
|
-
"model": "
|
|
81
|
+
"model": "auto",
|
|
82
82
|
"instructionsFile": "agents/OPENCODE.md"
|
|
83
83
|
},
|
|
84
84
|
"Cursor": {
|
|
@@ -92,7 +92,7 @@
|
|
|
92
92
|
"cli": "abacusai",
|
|
93
93
|
"profile": "abacusai",
|
|
94
94
|
"defaultRepo": "backend",
|
|
95
|
-
"model": "
|
|
95
|
+
"model": "auto",
|
|
96
96
|
"instructionsFile": "agents/ABACUS.md"
|
|
97
97
|
}
|
|
98
98
|
}
|