Phase 7 : outils web_search et run_shell avec validation

Backend :
- tools.py : web_search (DuckDuckGo HTML sans clé, ou SearxNG via SEARX_URL) et
  run_shell (confiné au workspace, sortie bornée, timeout)
- agent.py : run_shell sensible -> émet tool_confirm et ne s'exécute pas tant que
  l'utilisateur n'a pas validé (confirm_shell)
- agent_config.py : web_search/run_shell désactivés par défaut, flag confirm_shell
- routes/shell.py : POST /api/shell/run exécute une commande validée
- routes/chat.py : relaie l'événement tool_confirm, passe confirm_shell

Frontend :
- streamChat : événement tool_confirm
- store : pendingShell + approveShell/rejectShell (réinjecte le résultat à l'agent)
- ChatPanel : carte de validation de commande (Approuver / Refuser)
- SettingsView : toggles web_search/run_shell, marqueur sensible, switch confirm_shell
- ToolCard : glyphes web_search/run_shell, statut 'à valider', aperçu d'argument

Tests : run_shell confiné, gate de confirmation (commande dangereuse non exécutée),
route shell, parser DuckDuckGo.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SVay7z3y7q2gEe54ByAE6N
This commit is contained in:
MichaelandClaude Opus 4.8 committed 2026-06-29 21:09:27 +00:00
1 parent 2db6beea5c
commit de2befe31e
12 files changed
+398 -26

No files matched your search

+28 -3
View File
@@ -8,7 +8,10 @@ const TOOL_DESC: Record<string, string> = {
read_file: "Lire un fichier du projet",
write_file: "Créer / modifier un fichier",
list_dir: "Lister un répertoire",
web_search: "Recherche web",
run_shell: "Exécuter une commande",
};
const SENSITIVE = new Set(["run_shell"]);
/** Vue Configuration (Frame 2) — Modèle & génération, outils, invite système. */
export function SettingsView() {
@@ -279,6 +282,9 @@ export function SettingsView() {
</span>
<span className="flex-1 text-xs text-muted-2">
{TOOL_DESC[name] ?? ""}
{SENSITIVE.has(name) && (
<span className="text-warn"> · sensible</span>
)}
</span>
<button
onClick={() =>
@@ -298,9 +304,28 @@ export function SettingsView() {
</div>
);
})}
<div className="mt-1 border-t border-line-soft pt-2.5 font-mono text-[11px] text-muted-3">
web_search · run_shell — arrivent bientôt (sensibles)
</div>
{draft.tools.run_shell && (
<div className="mt-2 flex items-center gap-3 rounded-[10px] border border-line-soft bg-base px-3 py-2.5">
<span className="flex-1 text-xs text-ink-3">
Demander une validation avant chaque commande shell
</span>
<button
onClick={() => set("confirm_shell", !draft.confirm_shell)}
className="relative h-5 w-[34px] rounded-full transition-colors"
style={{
background: draft.confirm_shell ? "#f0a15c" : "#3a342c",
}}
>
<span
className="absolute top-0.5 h-4 w-4 rounded-full transition-all"
style={{
background: draft.confirm_shell ? "#1f1b16" : "#6f675c",
left: draft.confirm_shell ? 16 : 2,
}}
/>
</button>
</div>
)}
</div>
</div>
</div>