Tâches planifiées : l'IA travaille toute seule (repris de l'amont AJEAN)

Portage des v0.9.9 → v0.10.2 de l'amont, la dernière vraie fonctionnalité qui
nous manquait. Une consigne, une fréquence (« @every 2h », « tous les jours à
9h », ou une expression cron à 5 champs, dans le fuseau du navigateur), et l'IA
l'exécute seule en arrière-plan. Preset épinglé par tâche (bascule de modèle
avant l'exécution, attente du rechargement), accès mémoire et web réglables,
interrupteur maître pour tout suspendre, bouton « tester maintenant ».

Le planificateur est une goroutine, un tic par minute, dans le process qui
détient la conversation et parle au moteur. Une seule tâche part par tic : de
toute façon une seule inférence tourne à la fois, et étaler les départs évite
qu'une rafale monopolise le modèle. Occupé ou modèle en cours de chargement
n'est pas un échec — la tâche repasse au tic suivant.

Une tâche ne partage QU'UN point avec le chat : le verrou de génération. Ni
messages, ni journal d'affichage, ni epoch — elle construit son fil éphémère et
le jette, ne gardant que son texte final comme compte-rendu (borné à 4000
caractères, réinjecté au passage suivant pour la continuité).

Deux adaptations, parce que loki n'est pas l'amont :

— Dossier de travail. Ici il appartient à la DISCUSSION ouverte : une tâche y
  aurait déposé ses fichiers, et en aurait changé en cours de route si
  l'utilisateur changeait de discussion — pour disparaître avec elle à la
  suppression. Chaque tâche a donc le sien (workspace/tasks/<id>/), stable d'un
  passage à l'autre. La bascule ne touche que les points d'entrée des OUTILS
  (agentCwd) : le panneau Fichiers, les dépôts et les liens des messages
  continuent de suivre la discussion de l'utilisateur.

— Capacités. Mem est posé explicitement à MemOff quand l'agent est coupé : le
  zéro de MemMode est la chaîne vide, qu'EnabledTools ne reconnaît pas comme
  « coupée » — une tâche sans agent se serait vu offrir les outils mem_*. Le
  mode code reste off : rôles, critères et passe de vérification n'ont pas de
  sens sans personne en face.

Le refus d'un message pendant qu'une tâche tourne dit maintenant LAQUELLE occupe
le modèle : « génération en cours » sur un fil vide et immobile n'expliquait
rien.

Interface : section repliable dans les réglages (liste, état, prochain passage,
pastille), modale d'édition bâtie sur le gabarit de l'éditeur de preset, panneau
« dernier résultat » en markdown. Vérifié dans un vrai navigateur — création,
rendu, réouverture en édition, bascule intervalle/cron, interrupteur maître,
suppression — sans une seule erreur JS.
This commit is contained in:
Claude committed 2026-08-20 09:31:59 +00:00
1 parent 03ae361ade
commit 0f58ad7b49
25 files changed
+2441 -15

No files matched your search

+42
View File
@@ -2029,3 +2029,45 @@ html[data-files="1"] #files-btn{color:var(--accent)}
ne dépend plus du modèle qui pense — ou non — à la recopier dans sa réponse. */
.tool-shot{margin-top:8px}
.tool-shot img{max-width:100%;max-height:420px;border:1px solid var(--border);border-radius:4px;display:block;cursor:zoom-in}
.mcp-tag.mcp-tag-preset{flex:0 1 auto;min-width:2.5em}
.task-consigne{min-height:260px;resize:vertical;line-height:1.6;padding:12px}
/* Tâches planifiées (repris de l'amont AJEAN) : ligne en cours d'exécution + modale d'édition. */
.mcp-row.task-running{border-color:var(--accent);background:var(--accent-bg);cursor:default}
.task-dot-run{animation:taskpulse 1.1s ease-in-out infinite}
@keyframes taskpulse{0%,100%{opacity:1;transform:scale(1)}50%{opacity:.35;transform:scale(.7)}}
.task-run-badge{font-size:10px;font-weight:600;color:var(--accent);letter-spacing:.02em}
.task-stop-btn{flex:none;margin:0;padding:3px 10px;font-size:11px;border-radius:6px;background:var(--danger-bg);border:1px solid var(--err);color:var(--err)}
.task-stop-btn:hover{background:var(--danger-bg);border-color:var(--err);color:var(--err);opacity:.85}
/* La modale des tâches emprunte tout le design de l'éditeur de preset (.pe-*) :
même largeur, même mise en carte. */
#task-modal .modal-card{max-width:520px;overflow:hidden}
#task-modal-status:empty{display:none}
/* Panneau « Dernier résultat » : le compte-rendu complet de l'IA, lisible et
défilable — pas une mini-ligne tronquée. Posé dans une carte .pe-list-like. */
.task-report{font-size:13px;line-height:1.55;color:var(--text);background:var(--panel);border:1px solid var(--border);border-radius:12px;padding:11px 14px;max-height:240px;overflow:auto;white-space:pre-wrap;word-break:break-word}
.task-report.report-err{border-color:var(--err);color:var(--err)}
.task-report.report-run{color:var(--dim);font-style:italic}
.task-report.report-empty{color:var(--dim)}
/* Compte-rendu rendu en markdown : c'est du HTML de blocs, donc plus de pre-wrap
(sinon les sauts de ligne du HTML généré ajoutent des lignes vides). Styles
alignés sur ceux des bulles de chat (.msg .body). */
.task-report.md-report{white-space:normal}
.md-report>*{margin:.6em 0}
.md-report>*:first-child{margin-top:0}
.md-report>*:last-child{margin-bottom:0}
.md-report h1,.md-report h2,.md-report h3,.md-report h4{font-weight:600;line-height:1.25;margin:.9em 0 .4em}
.md-report h1{font-size:1.2em}.md-report h2{font-size:1.1em}.md-report h3{font-size:1em}.md-report h4{font-size:.95em}
.md-report ul,.md-report ol{padding-left:1.4em;margin:.5em 0}
.md-report li{margin:.15em 0}
.md-report li>p{margin:0}
.md-report code{background:var(--code-bg);padding:1px 5px;border-radius:3px;font-size:.9em;word-break:break-word}
.md-report pre{background:var(--code-bg);border:1px solid var(--border);border-radius:6px;padding:10px;overflow-x:auto;max-width:100%;margin:.5em 0}
.md-report pre code{background:transparent;padding:0;font-size:.85em;line-height:1.4}
.md-report blockquote{border-left:3px solid var(--border);padding:0 0 0 10px;margin:.4em 0;color:var(--dim)}
.md-report a{color:var(--accent);text-decoration:none}
.md-report a:hover{text-decoration:underline}
.md-report table{border-collapse:collapse;margin:.5em 0}
.md-report th,.md-report td{border:1px solid var(--border);padding:4px 8px}
.md-report hr{border:none;border-top:1px solid var(--border);margin:.6em 0}