fix: plan compact persiste et recharge a l'identique (stabilite entre sessions)

This commit is contained in:
2026-06-22 11:55:23 +02:00
parent f864c97f2a
commit caf655b708
+85 -21
View File
@@ -228,6 +228,9 @@ class Project:
"nom": self.name,
"cree_le": datetime.now().isoformat(timespec="seconds"),
"dernier_markdown": "",
"dernier_plan_compact": "",
"plan_modifie_le": "", # horodatage dernière modif du plan
"formalise_le": "", # horodatage dernière formalisation
"dernier_yaml": "",
"dernier_pptx": "",
"nb_sessions": 0,
@@ -239,10 +242,34 @@ class Project:
self.state_file.write_text(
json.dumps(state, ensure_ascii=False, indent=2), encoding="utf-8")
def has_state(self) -> bool:
"""Vrai si le projet a déjà produit au moins un narratif formalisé."""
def save_plan_compact(self, plan: str):
"""Sauvegarde continue du plan compact (source de vérité de la structure)."""
state = self.load_state()
state["dernier_plan_compact"] = plan
state["plan_modifie_le"] = datetime.now().isoformat(timespec="seconds")
self.save_state(state)
def mark_formalised(self, markdown: str):
"""Enregistre le Markdown formalisé + l'horodatage de formalisation."""
state = self.load_state()
state["dernier_markdown"] = markdown
state["formalise_le"] = datetime.now().isoformat(timespec="seconds")
self.save_state(state)
def plan_diverge(self) -> bool:
"""Vrai si le plan compact a été modifié APRÈS la dernière formalisation."""
st = self.load_state()
return bool(st.get("dernier_markdown"))
pm, fm = st.get("plan_modifie_le", ""), st.get("formalise_le", "")
if not pm:
return False
if not fm:
return bool(st.get("dernier_plan_compact"))
return pm > fm
def has_state(self) -> bool:
"""Vrai si le projet a déjà un plan compact OU un narratif formalisé."""
st = self.load_state()
return bool(st.get("dernier_plan_compact") or st.get("dernier_markdown"))
# ── Journal de décisions (Solution D) ───────────────────────────────────
def log(self, entry: str):
@@ -531,34 +558,60 @@ class AgentSession:
def reprime_narrator(session: AgentSession, proj: Project) -> None:
"""
Réamorce une session Narrator neuve avec le contexte d'un projet
déjà travaillé : dernier Markdown formalisé + journal des décisions.
(Solution C — on ne restaure pas la conversation exacte, on réinforme.)
Réamorce une session Narrator avec le contexte d'un projet déjà travaillé.
Priorité au PLAN COMPACT persisté (source de vérité) : on le RECHARGE
à l'identique et on le réinjecte comme état de départ — l'agent ne
réinvente rien. Repli sur la reconstruction depuis le Markdown
uniquement si aucun plan compact n'a été sauvegardé (projets antérieurs
à la persistance du plan).
"""
state = proj.load_state()
plan = state.get("dernier_plan_compact", "")
last_md = state.get("dernier_markdown", "")
if not last_md:
return
summary = proj.journal_summary()
if plan:
# RECHARGEMENT EXACT — pas de régénération
reprime = (
"Nous reprenons un projet de présentation déjà travaillé. "
"Voici l'état actuel pour te remettre en contexte.\n\n"
"=== DERNIER NARRATIF FORMALISÉ (état actuel du deck) ===\n\n"
"Voici le PLAN COMPACT EXACT sur lequel nous nous étions arrêtés. "
"C'est l'état de référence : tu le conserves tel quel et tu "
"travailles dessus. Ne le reformule pas, ne le réinvente pas, "
"ne renumérotes que si je te demande une modification de flux.\n\n"
"=== PLAN COMPACT ACTUEL ===\n\n"
f"{plan}\n\n"
)
if summary:
reprime += f"=== JOURNAL DES DÉCISIONS ===\n\n{summary}\n\n"
reprime += (
"Confirme en réaffichant ce plan compact À L'IDENTIQUE, puis "
"attends mes instructions (modes f: flux / d: deep dive). "
"N'ajoute aucun commentaire."
)
info("Réamorçage — plan compact rechargé à l'identique.")
out = session.start(reprime)
display_narrator(out)
return
# Repli : pas de plan compact persisté → reconstruction depuis le Markdown
if not last_md:
return
reprime = (
"Nous reprenons un projet déjà travaillé, mais seul le narratif "
"formalisé est disponible (pas de plan compact sauvegardé).\n\n"
"=== DERNIER NARRATIF FORMALISÉ ===\n\n"
f"{last_md}\n\n"
)
if summary:
reprime += f"=== JOURNAL DES DÉCISIONS ===\n\n{summary}\n\n"
reprime += (
"=== JOURNAL DES DÉCISIONS (historique du projet) ===\n\n"
f"{summary}\n\n"
"Reconstruis le PLAN COMPACT à partir de ce narratif (chapitres, "
"slides numérotées en continu, tags core/OPT-XXX et audience). "
"Affiche uniquement ce plan compact. On repartira de lui (modes f:/d:)."
)
reprime += (
"Reconstruis à partir de ce narratif le PLAN COMPACT de la présentation "
"(format hiérarchique : chapitres, slides numérotées en continu, tags "
"core/OPT-XXX et audience). Affiche uniquement ce plan compact, sans "
"autre commentaire. On repartira de ce plan pour la révision, avec les "
"mêmes modes f: (flux) et d: (deep dive)."
)
info("Réamorçage du Narrator — reconstruction du plan compact...")
warn("Aucun plan compact sauvegardé — reconstruction depuis le narratif "
"(peut différer légèrement).")
out = session.start(reprime)
display_narrator(out)
@@ -585,12 +638,20 @@ def run_narrator(session: AgentSession, proj: Project) -> Optional[str]:
print("\n Documents présents dans inputs/ — /lire pour les injecter.")
loaded_docs: set = set()
last_plan: str = "" # dernier plan compact (mode flux)
# Précharge le plan compact persisté (reprise) — source de vérité exacte
last_plan: str = proj.load_state().get("dernier_plan_compact", "")
last_response: str = "" # dernière réponse quelconque
formalized_md: Optional[str] = None
pending_context: str = ""
mode: Optional[str] = None # MODE_FLUX / MODE_DEEP / None
if last_plan and not session.started:
info("Plan compact rechargé depuis la session précédente :")
print("\n" + textwrap.indent(last_plan, " "))
if proj.plan_diverge():
warn("Le plan a été modifié après la dernière formalisation — "
"pense à /formalise avant de générer.")
def send_with_mode(msg: str, current_mode: Optional[str]) -> str:
"""Préfixe le message d'une consigne de mode pour l'agent."""
if current_mode == MODE_FLUX:
@@ -646,6 +707,7 @@ def run_narrator(session: AgentSession, proj: Project) -> Optional[str]:
last_response = session.send(send_with_mode(pasted, mode))
if mode == MODE_FLUX or mode is None:
last_plan = last_response
proj.save_plan_compact(last_plan)
display_narrator(last_response)
continue
@@ -666,6 +728,7 @@ def run_narrator(session: AgentSession, proj: Project) -> Optional[str]:
"en développant le contenu de chaque slide. Inclus les "
"slides tiroir en les marquant [OPT-XXX].")
formalized_md = last_response
proj.mark_formalised(formalized_md)
display_narrator(last_response)
ok("Formalisé. /valider pour passer au Designer, ou continue à ajuster.")
continue
@@ -716,6 +779,7 @@ def run_narrator(session: AgentSession, proj: Project) -> Optional[str]:
# Mémoriser le plan si on est en flux (ou tout premier plan)
if mode == MODE_FLUX or (mode is None and not last_plan):
last_plan = last_response
proj.save_plan_compact(last_plan)
display_narrator(last_response)