mirror of
https://github.com/R0m1k3/Loki.git
synced 2026-10-18 20:57:05 +02:00
Tâches : scripts planifiés sans modèle, et l'IA planifie la sienne
Deux reprises d'AJEAN autour des tâches planifiées. Dossier de scripts (/data/scripts) : un dossier durable À CÔTÉ de memory, hors du workspace. Le workspace est jetable — supprimer une discussion emporte ses fichiers — donc un script qu'on veut garder n'y avait pas sa place. Le briefing machine l'annonce à l'IA, qui y écrit et y lance ses scripts normalement. Tâche « script seul » (Task.Kind/Script) : le planificateur lance le script sans charger le modèle ni consommer un token, sa sortie devient le compte-rendu, et l'UI l'affiche comme n'importe quelle tâche. Elle tourne hors du verrou de génération — d'où un registre à part pour l'afficher « en cours » et l'arrêter (/api/tasks/stop), et un « tester » qui n'attend ni le verrou ni le moteur. Sélecteur Consigne IA / Script seul dans la modale. Outils task_list/create/update/delete : l'IA se donne elle-même rappels et veilles récurrentes, cloisonnés par projet (dans un projet, elle ne voit et ne pilote que ses tâches). Le budget du préambule passe de 8600 à 11000 caractères, avec le palier documenté dans le test. Dossier mémoire réservé à ses outils : bash, write et edit ne le touchent plus (guardToolOnly*). Un `cat memory/…` contournait l'index MEMORY.md, et c'est la condition d'un chiffrement de la mémoire à venir. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01K6CAgoLJzufeA8rZTSpSpY
This commit is contained in:
17 files changed
+1043
-26
No files matched your search
@@ -926,11 +926,34 @@ document.documentElement.setAttribute('data-side',localStorage.getItem('loki-sid
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Type de tâche : une consigne exécutée par l'IA, ou un script du dossier
|
||||
protégé lancé SEUL — sans charger le modèle ni consommer de tokens.
|
||||
Une sauvegarde, une synchro, un nettoyage n'ont rien à demander à un
|
||||
LLM ; les faire passer par lui coûtait un chargement de modèle. -->
|
||||
<div class="pe-group">
|
||||
<div class="pe-gh">Type</div>
|
||||
<div class="pe-seg">
|
||||
<label><input type="radio" name="task-kind" value="agent" onchange="taskKindUI('agent')"><span>Consigne IA</span></label>
|
||||
<label><input type="radio" name="task-kind" value="script" onchange="taskKindUI('script')"><span>Script seul</span></label>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="pe-group" id="task-prompt-group">
|
||||
<div class="pe-gh">Consigne</div>
|
||||
<textarea id="task-prompt" class="pe-inp task-consigne" spellcheck="false" placeholder="Ce que l'IA doit faire, et comment livrer le résultat. Tu peux détailler librement : contexte, étapes, format du compte-rendu attendu…"></textarea>
|
||||
</div>
|
||||
|
||||
<div class="pe-group" id="task-script-group" style="display:none">
|
||||
<div class="pe-gh">Script</div>
|
||||
<div class="pe-list">
|
||||
<div class="pe-row">
|
||||
<span class="pe-row-l">À exécuter</span>
|
||||
<span class="pe-row-c"><span class="pe-selc"><select id="task-script"></select></span></span>
|
||||
</div>
|
||||
<div class="pe-row stack plain"><div id="task-script-hint" class="muted" style="font-size:11px"></div></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="pe-group">
|
||||
<div class="pe-gh">Preset</div>
|
||||
<div class="pe-list">
|
||||
|
||||
@@ -12,6 +12,8 @@ let taskEditing = null; // id en cours d'édition ('' = nouvelle, null = fermé)
|
||||
let TASK_RUNNING = ''; // id de la tâche en cours d'exécution ('' = aucune)
|
||||
let TASK_PRESETS = []; // presets disponibles (pour le sélecteur de la modale)
|
||||
let TASK_PROJECTS = []; // projets disponibles (sélecteur « projet visé »)
|
||||
let TASK_SCRIPTS = []; // scripts durables disponibles (tâche « script seul »)
|
||||
let TASK_SCRIPTS_DIR = ''; // chemin du dossier de scripts, pour l'aide affichée
|
||||
let TASK_MEM_ON = true; // état mémoire global (défaut d'une nouvelle tâche)
|
||||
let TASK_WEB_ON = true; // état web global (défaut d'une nouvelle tâche)
|
||||
let tasksPollTimer = null;
|
||||
@@ -35,6 +37,8 @@ function renderTasks(r){
|
||||
const agentOn = !!(r && r.agent);
|
||||
TASK_PRESETS = (r && r.presets) || [];
|
||||
TASK_PROJECTS = (r && r.projects) || [];
|
||||
TASK_SCRIPTS = (r && r.scripts) || [];
|
||||
TASK_SCRIPTS_DIR = (r && r.scripts_dir) || 'le dossier de scripts';
|
||||
TASK_MEM_ON = !r || r.mem_on !== false;
|
||||
TASK_WEB_ON = !r || r.web_on !== false;
|
||||
TASK_RUNNING = (r && r.running_id) || '';
|
||||
@@ -194,6 +198,9 @@ function openTask(id){
|
||||
// stockés en négation) ; sur une nouvelle on suit l'état global de la machine.
|
||||
document.getElementById('task-mem').checked = t ? !t.no_mem : TASK_MEM_ON;
|
||||
document.getElementById('task-web').checked = t ? !t.no_web : TASK_WEB_ON;
|
||||
// Type : IA par défaut. Une tâche script n'a ni consigne ni preset utile.
|
||||
setTaskKind(t && t.kind === 'script' ? 'script' : 'agent');
|
||||
fillScriptSelect(t ? (t.script||'') : '');
|
||||
fillPresetSelect(t ? (t.preset||'') : '');
|
||||
// Nouvelle tâche : le projet ACTIF est proposé — on crée presque toujours une
|
||||
// tâche pour le chantier qu'on a sous les yeux.
|
||||
@@ -284,6 +291,51 @@ function fillTaskProjectSelect(selected){
|
||||
if(selected && [...sel.options].some(o=>o.value===selected)) sel.value = selected;
|
||||
}
|
||||
|
||||
// fillScriptSelect peuple le sélecteur de script avec les fichiers du dossier
|
||||
// protégé (LOKI_HOME/scripts). Dossier vide = on le dit et on laisse le
|
||||
// sélecteur vide : l'enregistrement sera refusé côté serveur, autant l'annoncer.
|
||||
function fillScriptSelect(selected){
|
||||
const sel = document.getElementById('task-script');
|
||||
if(!sel) return;
|
||||
sel.textContent = '';
|
||||
TASK_SCRIPTS.forEach(sc=>{
|
||||
const o = document.createElement('option'); o.value = sc.name;
|
||||
o.textContent = sc.name + (sc.size ? ' · ' + fmtSize(sc.size) : '');
|
||||
sel.appendChild(o);
|
||||
});
|
||||
if(selected && [...sel.options].some(o=>o.value===selected)) sel.value = selected;
|
||||
const hint = document.getElementById('task-script-hint');
|
||||
if(hint){
|
||||
hint.textContent = TASK_SCRIPTS.length
|
||||
? 'Scripts de ' + TASK_SCRIPTS_DIR + ' — l\'IA peut y écrire les siens.'
|
||||
: 'Aucun script dans ' + TASK_SCRIPTS_DIR + ' : demande à l\'IA d\'y en écrire un, ou dépose-le toi-même.';
|
||||
}
|
||||
}
|
||||
|
||||
// setTaskKind bascule le formulaire entre « consigne IA » et « script seul ».
|
||||
function setTaskKind(kind){
|
||||
const r = document.querySelector('input[name="task-kind"][value="'+kind+'"]');
|
||||
if(r) r.checked = true;
|
||||
taskKindUI(kind);
|
||||
}
|
||||
|
||||
// taskKindUI n'affiche que les champs du type choisi : une tâche script n'a pas
|
||||
// de consigne, et le preset ne la concerne pas (aucun modèle n'est chargé).
|
||||
function taskKindUI(kind){
|
||||
const script = kind === 'script';
|
||||
const pg = document.getElementById('task-prompt-group');
|
||||
const sg = document.getElementById('task-script-group');
|
||||
if(pg) pg.style.display = script ? 'none' : '';
|
||||
if(sg) sg.style.display = script ? '' : 'none';
|
||||
const pr = document.getElementById('task-preset');
|
||||
if(pr) pr.closest('.pe-row').style.display = script ? 'none' : '';
|
||||
}
|
||||
|
||||
function taskKind(){
|
||||
const r = document.querySelector('input[name="task-kind"]:checked');
|
||||
return (r && r.value === 'script') ? 'script' : 'agent';
|
||||
}
|
||||
|
||||
function setTaskFreqMode(mode){
|
||||
const r = document.querySelector('input[name="task-freq-mode"][value="'+mode+'"]');
|
||||
if(r) r.checked = true;
|
||||
@@ -319,6 +371,8 @@ function buildSchedule(){
|
||||
|
||||
async function saveTask(){
|
||||
const name = document.getElementById('task-name').value.trim();
|
||||
const kind = taskKind();
|
||||
const script = (document.getElementById('task-script')||{}).value || '';
|
||||
const prompt = document.getElementById('task-prompt').value.trim();
|
||||
const schedule = buildSchedule();
|
||||
const enabled = document.getElementById('task-enabled').checked;
|
||||
@@ -328,9 +382,13 @@ async function saveTask(){
|
||||
const no_mem = !document.getElementById('task-mem').checked;
|
||||
const no_web = !document.getElementById('task-web').checked;
|
||||
const st = document.getElementById('task-modal-status');
|
||||
if(!name || !prompt){ st.textContent = 'nom et consigne obligatoires'; st.style.color = 'var(--err)'; return; }
|
||||
if(!name){ st.textContent = 'nom obligatoire'; st.style.color = 'var(--err)'; return; }
|
||||
if(kind === 'script' ? !script : !prompt){
|
||||
st.textContent = kind === 'script' ? 'choisis un script' : 'consigne obligatoire';
|
||||
st.style.color = 'var(--err)'; return;
|
||||
}
|
||||
if(!schedule){ st.textContent = 'fréquence invalide'; st.style.color = 'var(--err)'; return; }
|
||||
const r = await jpost('/api/tasks/save', {id: taskEditing||'', name, prompt, schedule, enabled, preset, project, tz, no_mem, no_web});
|
||||
const r = await jpost('/api/tasks/save', {id: taskEditing||'', name, kind, script, prompt, schedule, enabled, preset, project, tz, no_mem, no_web});
|
||||
if(!r || !r.ok){ st.textContent = (r && r.error) || 'échec'; st.style.color = 'var(--err)'; return; }
|
||||
closeTask();
|
||||
await loadTasks();
|
||||
|
||||
Reference in new issue
Block a user