mirror of
https://github.com/R0m1k3/Loki.git
synced 2026-10-11 17:26:57 +02:00
Plan d'architecture pour le code + garde-fou shell tolérant aux URLs
- make_plan(code=True) : pour une tâche d'application, produit un plan d'IMPLÉMENTATION concret (étape 1 = architecture/fichiers, puis une fonctionnalité testable à la fois), réaliste et sans dépendance externe — au lieu d'une liste d'objectifs produit (« moteur IA »…) que les petits modèles n'arrivent pas à livrer. - run_shell : le garde-fou de confinement ne bloque plus les URLs (http://localhost:8080 était rejeté à cause du « // »), et autorise la base du workspace même quand la session cible un projet (ls /workspace). Les évasions réelles (/etc, /config, ~, ../..) restent bloquées. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01SVay7z3y7q2gEe54ByAE6N
This commit is contained in:
3 files changed
+31
-4
No files matched your search
+22
-2
@@ -22,6 +22,20 @@ _PLAN_PROMPT = (
|
|||||||
"UNIQUEMENT les étapes, en français."
|
"UNIQUEMENT les étapes, en français."
|
||||||
)
|
)
|
||||||
|
|
||||||
|
_PLAN_CODE_PROMPT = (
|
||||||
|
"Tu es architecte logiciel. Établis un plan d'IMPLÉMENTATION concret pour "
|
||||||
|
"cette application, en 3 à 5 étapes courtes (une par ligne, numérotées "
|
||||||
|
"« 1. », « 2. »…).\n"
|
||||||
|
"- Étape 1 = ARCHITECTURE : quels fichiers créer et leur rôle (ex. un seul "
|
||||||
|
"index.html avec HTML+CSS+JS, ou séparer index.html / style.css / app.js).\n"
|
||||||
|
"- Étapes suivantes = construire UNE fonctionnalité concrète et testable à "
|
||||||
|
"la fois (structure/affichage, puis interactions, puis logique).\n"
|
||||||
|
"Reste RÉALISTE et réalisable en une passe : pas de dépendance externe, pas "
|
||||||
|
"de bibliothèque à installer, pas de « moteur IA » complexe si ce n'est pas "
|
||||||
|
"explicitement demandé — une logique simple en JavaScript suffit. "
|
||||||
|
"Pas d'introduction ni de conclusion, UNIQUEMENT les étapes, en français."
|
||||||
|
)
|
||||||
|
|
||||||
_CRITIQUE_PROMPT = (
|
_CRITIQUE_PROMPT = (
|
||||||
"Tu es un relecteur exigeant. Voici une demande et la réponse d'un "
|
"Tu es un relecteur exigeant. Voici une demande et la réponse d'un "
|
||||||
"assistant. Si la réponse est correcte et complète, réponds exactement "
|
"assistant. Si la réponse est correcte et complète, réponds exactement "
|
||||||
@@ -90,13 +104,19 @@ async def make_plan(
|
|||||||
model: str,
|
model: str,
|
||||||
message: str,
|
message: str,
|
||||||
*,
|
*,
|
||||||
|
code: bool = False,
|
||||||
options: dict | None = None,
|
options: dict | None = None,
|
||||||
keep_alive: str | None = None,
|
keep_alive: str | None = None,
|
||||||
) -> list[str]:
|
) -> list[str]:
|
||||||
"""Renvoie la liste des étapes (vide si échec — jamais bloquant)."""
|
"""Renvoie la liste des étapes (vide si échec — jamais bloquant).
|
||||||
|
|
||||||
|
``code=True`` bascule sur un plan d'ARCHITECTURE/implémentation concret
|
||||||
|
(fichiers, structure, fonctionnalités) plutôt qu'une liste d'objectifs.
|
||||||
|
"""
|
||||||
try:
|
try:
|
||||||
raw = await _ask(
|
raw = await _ask(
|
||||||
model, _PLAN_PROMPT, message[:1200], num_predict=220,
|
model, _PLAN_CODE_PROMPT if code else _PLAN_PROMPT,
|
||||||
|
message[:1200], num_predict=260,
|
||||||
options=options, keep_alive=keep_alive,
|
options=options, keep_alive=keep_alive,
|
||||||
)
|
)
|
||||||
except (httpx.HTTPError, OSError) as exc:
|
except (httpx.HTTPError, OSError) as exc:
|
||||||
|
|||||||
@@ -349,7 +349,8 @@ async def chat(req: ChatRequest) -> StreamingResponse:
|
|||||||
prep = asyncio.ensure_future(asyncio.gather(
|
prep = asyncio.ensure_future(asyncio.gather(
|
||||||
rag.recall(req.session_id, req.content, embed_model=cfg.get("embed_model"))
|
rag.recall(req.session_id, req.content, embed_model=cfg.get("embed_model"))
|
||||||
if want_rag else asyncio.sleep(0, result=[]),
|
if want_rag else asyncio.sleep(0, result=[]),
|
||||||
enhance.make_plan(model, req.content, options=run_opts, keep_alive=keep)
|
enhance.make_plan(model, req.content, code=use_code,
|
||||||
|
options=run_opts, keep_alive=keep)
|
||||||
if want_plan else asyncio.sleep(0, result=[]),
|
if want_plan else asyncio.sleep(0, result=[]),
|
||||||
coder.pick_code_model(model, cfg.get("code_model"))
|
coder.pick_code_model(model, cfg.get("code_model"))
|
||||||
if use_code else asyncio.sleep(0, result=model),
|
if use_code else asyncio.sleep(0, result=model),
|
||||||
|
|||||||
@@ -512,11 +512,17 @@ def _guard_shell(command: str) -> None:
|
|||||||
sortent du workspace. Combiné à la validation utilisateur, ça empêche le
|
sortent du workspace. Combiné à la validation utilisateur, ça empêche le
|
||||||
modèle d'écrire ailleurs que dans son workspace.
|
modèle d'écrire ailleurs que dans son workspace.
|
||||||
"""
|
"""
|
||||||
root = _workspace_root()
|
# Racine autorisée = base du workspace (pas le sous-dossier projet) : ainsi
|
||||||
|
# `ls /workspace` reste permis même quand la session cible un projet.
|
||||||
|
root = os.path.abspath(settings.workspace_dir)
|
||||||
for token in _PATH_TOKEN.findall(command):
|
for token in _PATH_TOKEN.findall(command):
|
||||||
token = token.strip()
|
token = token.strip()
|
||||||
if not token:
|
if not token:
|
||||||
continue
|
continue
|
||||||
|
# Jeton commençant par `//` = autorité d'URL (http://, ws://…) ou chemin
|
||||||
|
# réseau, jamais une évasion du système de fichiers : on l'ignore.
|
||||||
|
if token.startswith("//"):
|
||||||
|
continue
|
||||||
if token.startswith("~"):
|
if token.startswith("~"):
|
||||||
raise ToolError(
|
raise ToolError(
|
||||||
f"chemin hors du workspace refusé : {token}. "
|
f"chemin hors du workspace refusé : {token}. "
|
||||||
|
|||||||
Reference in new issue
Block a user