fix: plan compact persiste et recharge a l'identique (stabilite entre sessions)
This commit is contained in:
+85
-21
@@ -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", "")
|
||||
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 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
|
||||
summary = proj.journal_summary()
|
||||
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"
|
||||
"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 += (
|
||||
"=== JOURNAL DES DÉCISIONS (historique du projet) ===\n\n"
|
||||
f"{summary}\n\n"
|
||||
)
|
||||
reprime += f"=== JOURNAL DES DÉCISIONS ===\n\n{summary}\n\n"
|
||||
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)."
|
||||
"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:)."
|
||||
)
|
||||
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)
|
||||
|
||||
|
||||
Reference in New Issue
Block a user