fix(agent_v1): assainissement PII des logs client a la source (push-log-DGX, brique 4)
Remplace dans les logs/print le contenu utilisateur brut par un equivalent PII-safe via core/log_safe : titres de fenetre -> _title_hash, reponses VLM -> [len,has_target], metadonnees -> _sanitize_metadata, chemins -> _path_ext, workflow_name -> _title_hash. 8 fichiers (executor, recovery, captor, streamer, main, capture_server, activity_panel, window_info_crossplatform). Audit Qwen complete : ~17 fuites de titre multi-lignes + 2e fuite VLM (print) non listees ont ete traitees ; localisation par contenu (refs Qwen derivees). Preserve volontairement : prompts de grounding VLM (vlm_description) ou le titre est load-bearing (resolution 100% vision) -> ne PAS hasher. Differe : window_focus_change (verdict apprentissage). En attente arbitrage Dom : button_text (~11 captions), patterns, champs detail. py_compile 8/8 OK, imports OK, helper 6/6 vert. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -32,6 +32,7 @@ from pynput.keyboard import Key, KeyCode
|
||||
# Importation relative pour rester dans le module v1
|
||||
from ..vision.capturer import VisionCapturer
|
||||
from ..vision.system_info import get_screen_metadata
|
||||
from .log_safe import _sanitize_metadata
|
||||
# from ..monitoring.system import SystemMonitor
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
@@ -676,7 +677,7 @@ class EventCaptorV1:
|
||||
metadata = get_screen_metadata()
|
||||
with self._screen_metadata_lock:
|
||||
self._screen_metadata = metadata
|
||||
logger.debug(f"Métadonnées système rafraîchies : {metadata}")
|
||||
logger.debug(f"Métadonnées système rafraîchies : {_sanitize_metadata(metadata)}")
|
||||
except Exception as e:
|
||||
logger.error(f"Erreur refresh métadonnées système : {e}")
|
||||
|
||||
|
||||
@@ -26,6 +26,7 @@ from typing import Any, Dict, Optional
|
||||
# DPI awareness est configure (SetProcessDpiAwareness(2) sur Windows).
|
||||
# Sans cela, pynput et mss utilisent des coordonnees logiques (virtualisees).
|
||||
from ..config import MACHINE_ID as _ # noqa: F401 — side-effect import
|
||||
from .log_safe import _title_hash
|
||||
|
||||
import mss
|
||||
from pynput.mouse import Button, Controller as MouseController
|
||||
@@ -862,7 +863,7 @@ class ActionExecutorV1:
|
||||
)
|
||||
if handled:
|
||||
logger.info(
|
||||
f"[RUNTIME-DIALOG] '{current_title}' gere via serveur "
|
||||
f"[RUNTIME-DIALOG] [title_hash={_title_hash(current_title)}] gere via serveur "
|
||||
f"fenetre -> bouton '{button_text}' "
|
||||
f"[{resolved.get('method', 'server')}]"
|
||||
)
|
||||
@@ -890,7 +891,7 @@ class ActionExecutorV1:
|
||||
)
|
||||
if handled:
|
||||
logger.info(
|
||||
f"[RUNTIME-DIALOG] '{current_title}' gere localement "
|
||||
f"[RUNTIME-DIALOG] [title_hash={_title_hash(current_title)}] gere localement "
|
||||
f"fenetre -> bouton '{button_text}' [dialog_window_text_template]"
|
||||
)
|
||||
return handled
|
||||
@@ -917,7 +918,7 @@ class ActionExecutorV1:
|
||||
)
|
||||
if handled:
|
||||
logger.info(
|
||||
f"[RUNTIME-DIALOG] '{current_title}' gere par geometrie "
|
||||
f"[RUNTIME-DIALOG] [title_hash={_title_hash(current_title)}] gere par geometrie "
|
||||
f"fenetre -> bouton '{button_text}'"
|
||||
)
|
||||
return handled
|
||||
@@ -967,7 +968,7 @@ class ActionExecutorV1:
|
||||
if not handled:
|
||||
continue
|
||||
logger.info(
|
||||
f"[RUNTIME-DIALOG] '{current_title}' gere via serveur "
|
||||
f"[RUNTIME-DIALOG] [title_hash={_title_hash(current_title)}] gere via serveur "
|
||||
f"-> bouton '{button_text}' [{resolved.get('method', 'server')}]"
|
||||
)
|
||||
return handled
|
||||
@@ -992,13 +993,13 @@ class ActionExecutorV1:
|
||||
if not handled:
|
||||
continue
|
||||
logger.info(
|
||||
f"[RUNTIME-DIALOG] '{current_title}' gere localement "
|
||||
f"[RUNTIME-DIALOG] [title_hash={_title_hash(current_title)}] gere localement "
|
||||
f"-> bouton '{button_text}' [dialog_text_template]"
|
||||
)
|
||||
return handled
|
||||
|
||||
logger.info(
|
||||
f"[RUNTIME-DIALOG] Aucun bouton resolu pour '{current_title}'"
|
||||
f"[RUNTIME-DIALOG] Aucun bouton resolu pour [title_hash={_title_hash(current_title)}]"
|
||||
)
|
||||
return None
|
||||
|
||||
@@ -1258,7 +1259,7 @@ class ActionExecutorV1:
|
||||
|
||||
if dialog_spec.get("skip_current_action_after_handle", False):
|
||||
logger.info(
|
||||
f"[RUNTIME-DIALOG] Dialogue '{current_title}' gere -> "
|
||||
f"[RUNTIME-DIALOG] Dialogue [title_hash={_title_hash(current_title)}] gere -> "
|
||||
f"action {action.get('action_id', 'unknown')} skippée"
|
||||
)
|
||||
return {
|
||||
@@ -1587,7 +1588,7 @@ class ActionExecutorV1:
|
||||
]
|
||||
for pattern in popup_patterns:
|
||||
if pattern in current_title:
|
||||
logger.info(f"Observer : popup détectée par titre — '{current_title}'")
|
||||
logger.info(f"Observer : popup détectée par titre — [title_hash={_title_hash(current_title)}]")
|
||||
# On ne peut pas résoudre les coords juste par le titre
|
||||
# → retourner popup sans coords, le caller fera handle_popup_vlm()
|
||||
return {
|
||||
@@ -1874,8 +1875,8 @@ class ActionExecutorV1:
|
||||
)
|
||||
else:
|
||||
logger.warning(
|
||||
f"[LEA] Fenêtre incorrecte : attendu '{expected_title}', "
|
||||
f"actuel '{current_title}'"
|
||||
f"[LEA] Fenêtre incorrecte : attendu [title_hash={_title_hash(expected_title)}], "
|
||||
f"actuel [title_hash={_title_hash(current_title)}]"
|
||||
)
|
||||
auto_result = self._maybe_handle_runtime_dialog_before_pause(
|
||||
action=action,
|
||||
@@ -1888,8 +1889,8 @@ class ActionExecutorV1:
|
||||
if auto_result is not None:
|
||||
return auto_result
|
||||
print(
|
||||
f" [PRÉ-VÉRIF] Fenêtre '{current_title}' ≠ "
|
||||
f"attendu '{expected_title}' → mode apprentissage"
|
||||
f" [PRÉ-VÉRIF] Fenêtre [title_hash={_title_hash(current_title)}] ≠ "
|
||||
f"attendu [title_hash={_title_hash(expected_title)}] → mode apprentissage"
|
||||
)
|
||||
try:
|
||||
self.notifier.replay_learning_mode(
|
||||
@@ -1936,8 +1937,8 @@ class ActionExecutorV1:
|
||||
# des coordonnées devenues invalides.
|
||||
result["success"] = False
|
||||
result["error"] = (
|
||||
f"Fenêtre incorrecte : attendu '{expected_title}', "
|
||||
f"actuel '{current_title}'"
|
||||
f"Fenêtre incorrecte : attendu [title_hash={_title_hash(expected_title)}], "
|
||||
f"actuel [title_hash={_title_hash(current_title)}]"
|
||||
)
|
||||
result["warning"] = "wrong_window"
|
||||
result["target_description"] = expected_title
|
||||
@@ -1945,11 +1946,11 @@ class ActionExecutorV1:
|
||||
result["screenshot"] = self._capture_screenshot_b64()
|
||||
logger.warning(
|
||||
f"[LEA] Wrong window sans correction → pause "
|
||||
f"(attendu '{expected_title}', actuel '{current_title}')"
|
||||
f"(attendu [title_hash={_title_hash(expected_title)}], actuel [title_hash={_title_hash(current_title)}])"
|
||||
)
|
||||
return result
|
||||
else:
|
||||
logger.info(f"[LEA] Pré-vérif OK : '{current_title}'")
|
||||
logger.info(f"[LEA] Pré-vérif OK : [title_hash={_title_hash(current_title)}]")
|
||||
|
||||
# ── OBSERVER : pré-analyse écran avant résolution ──
|
||||
# Détecte popups, dialogues, états inattendus AVANT de chercher la cible.
|
||||
@@ -1964,8 +1965,8 @@ class ActionExecutorV1:
|
||||
# Popup détectée AVANT la résolution — la fermer
|
||||
popup_label = observation.get("popup_label", "popup")
|
||||
popup_coords = observation.get("popup_coords")
|
||||
print(f" [OBSERVER] Popup détectée : '{popup_label}' — fermeture")
|
||||
logger.info(f"Observer : popup '{popup_label}' détectée avant résolution")
|
||||
print(f" [OBSERVER] Popup détectée : [title_hash={_title_hash(popup_label)}] — fermeture")
|
||||
logger.info(f"Observer : popup [title_hash={_title_hash(popup_label)}] détectée avant résolution")
|
||||
|
||||
# ── SÉCURITÉ : refuser de cliquer sur un dialogue système ──
|
||||
# Avant de suivre les coordonnées du serveur (VLM-based,
|
||||
@@ -2365,8 +2366,8 @@ class ActionExecutorV1:
|
||||
recheck_title = recheck_info.get("title", "")
|
||||
if not _matches_expected_window(recheck_title):
|
||||
logger.warning(
|
||||
f"P0.9 transition instable : matched '{post_title}' "
|
||||
f"puis '{recheck_title}' à T+0.5s ≠ '{expected_after}'"
|
||||
f"P0.9 transition instable : matched [title_hash={_title_hash(post_title)}] "
|
||||
f"puis [title_hash={_title_hash(recheck_title)}] à T+0.5s ≠ [title_hash={_title_hash(expected_after)}]"
|
||||
)
|
||||
matched = False
|
||||
post_title = recheck_title
|
||||
@@ -2376,19 +2377,19 @@ class ActionExecutorV1:
|
||||
result["runtime_dialog"] = runtime_dialog_handled
|
||||
print(
|
||||
f" [POST-VÉRIF] Dialogue runtime géré "
|
||||
f"→ retour '{post_title}'"
|
||||
f"→ retour [title_hash={_title_hash(post_title)}]"
|
||||
)
|
||||
logger.info(
|
||||
"POST-VÉRIF runtime dialog géré : '%s' -> '%s'",
|
||||
runtime_dialog_handled.get("dialog_title", ""),
|
||||
post_title,
|
||||
"POST-VÉRIF runtime dialog géré : [title_hash=%s] -> [title_hash=%s]",
|
||||
_title_hash(runtime_dialog_handled.get("dialog_title", "")),
|
||||
_title_hash(post_title),
|
||||
)
|
||||
else:
|
||||
print(f" [POST-VÉRIF] OK en {elapsed_wait:.1f}s — '{post_title}'")
|
||||
logger.info(f"POST-VÉRIF OK en {elapsed_wait:.1f}s : '{post_title}'")
|
||||
print(f" [POST-VÉRIF] OK en {elapsed_wait:.1f}s — [title_hash={_title_hash(post_title)}]")
|
||||
logger.info(f"POST-VÉRIF OK en {elapsed_wait:.1f}s : [title_hash={_title_hash(post_title)}]")
|
||||
else:
|
||||
print(f" [POST-VÉRIF] TIMEOUT {max_wait}s — '{post_title}' ≠ '{expected_after}'")
|
||||
logger.warning(f"POST-VÉRIF TIMEOUT : '{post_title}' ≠ '{expected_after}'")
|
||||
print(f" [POST-VÉRIF] TIMEOUT {max_wait}s — [title_hash={_title_hash(post_title)}] ≠ [title_hash={_title_hash(expected_after)}]")
|
||||
logger.warning(f"POST-VÉRIF TIMEOUT : [title_hash={_title_hash(post_title)}] ≠ [title_hash={_title_hash(expected_after)}]")
|
||||
if runtime_dialog_handled:
|
||||
result["warning"] = (
|
||||
f"runtime_dialog_handled_post_verify:{post_title}"
|
||||
@@ -2396,9 +2397,9 @@ class ActionExecutorV1:
|
||||
result["runtime_dialog"] = runtime_dialog_handled
|
||||
logger.warning(
|
||||
"POST-VÉRIF runtime dialog géré mais "
|
||||
"fenêtre finale inattendue : '%s' ≠ '%s'",
|
||||
post_title,
|
||||
expected_after,
|
||||
"fenêtre finale inattendue : [title_hash=%s] ≠ [title_hash=%s]",
|
||||
_title_hash(post_title),
|
||||
_title_hash(expected_after),
|
||||
)
|
||||
# Contrôle strict : si success_strict, on STOP.
|
||||
# On durcit aussi les vrais changements de fenêtre
|
||||
@@ -2416,8 +2417,8 @@ class ActionExecutorV1:
|
||||
if bool(action.get("success_strict")) or requires_transition:
|
||||
result["success"] = False
|
||||
result["error"] = (
|
||||
f"Post-vérif échouée : fenêtre '{post_title}' "
|
||||
f"au lieu de '{expected_after}'"
|
||||
f"Post-vérif échouée : fenêtre [title_hash={_title_hash(post_title)}] "
|
||||
f"au lieu de [title_hash={_title_hash(expected_after)}]"
|
||||
)
|
||||
result["warning"] = "wrong_window"
|
||||
result["needs_human"] = True
|
||||
@@ -2458,7 +2459,7 @@ class ActionExecutorV1:
|
||||
# paste=True (opt-in via action.paste) → clipboard + Ctrl+V (non-Citrix)
|
||||
self._type_text(text, paste=bool(action.get("paste", False)))
|
||||
print(f" [TYPE] Termine.")
|
||||
logger.info(f"Replay type : '{text[:30]}...' ({len(text)} chars, raw_keys={'oui' if raw_keys else 'non'})")
|
||||
logger.info(f"Replay type : [{len(text)} chars] (raw_keys={'oui' if raw_keys else 'non'})")
|
||||
|
||||
elif action_type == "key_combo":
|
||||
keys = action.get("keys", [])
|
||||
@@ -2524,12 +2525,12 @@ class ActionExecutorV1:
|
||||
if not self._window_title_matches_any(current_title, patterns):
|
||||
logger.warning(
|
||||
"[LEA] verify_screen garde KO : attendu un titre "
|
||||
"contenant %s, actuel '%s'",
|
||||
patterns, current_title,
|
||||
"contenant %s, actuel [title_hash=%s]",
|
||||
patterns, _title_hash(current_title),
|
||||
)
|
||||
print(
|
||||
f" [VERIFY] Garde titre KO "
|
||||
f"(patterns={patterns}, actuel='{current_title}') "
|
||||
f"(patterns={patterns}, actuel=[title_hash={_title_hash(current_title)}]) "
|
||||
"→ apprentissage humain"
|
||||
)
|
||||
try:
|
||||
@@ -2557,15 +2558,15 @@ class ActionExecutorV1:
|
||||
result["error"] = (
|
||||
f"verify_screen titre fenêtre KO : attendu "
|
||||
f"un titre contenant {patterns}, "
|
||||
f"actuel '{current_title}'"
|
||||
f"actuel [title_hash={_title_hash(current_title)}]"
|
||||
)
|
||||
result["warning"] = "setup_guard_window_mismatch"
|
||||
result["needs_human"] = True
|
||||
result["screenshot"] = self._capture_screenshot_b64()
|
||||
return result
|
||||
logger.info(
|
||||
"[LEA] verify_screen garde OK : '%s' matche %s",
|
||||
current_title, patterns,
|
||||
"[LEA] verify_screen garde OK : [title_hash=%s] matche %s",
|
||||
_title_hash(current_title), patterns,
|
||||
)
|
||||
|
||||
print(f" [VERIFY] Termine (verification deferred au serveur).")
|
||||
@@ -3736,8 +3737,8 @@ Example: x_pct=0.50, y_pct=0.30"""
|
||||
real_x = int(x_pct * sw)
|
||||
real_y = int(y_pct * sh)
|
||||
label = server_result.get("matched_element", {}).get("label", "popup")
|
||||
print(f" [POPUP-SERVER] Popup détectée ! Clic sur '{label}' → ({real_x}, {real_y})")
|
||||
logger.info(f"[POPUP-SERVER] Clic popup '{label}' à ({real_x}, {real_y})")
|
||||
print(f" [POPUP-SERVER] Popup détectée ! Clic sur [title_hash={_title_hash(label)}] → ({real_x}, {real_y})")
|
||||
logger.info(f"[POPUP-SERVER] Clic popup [title_hash={_title_hash(label)}] à ({real_x}, {real_y})")
|
||||
self._click((real_x, real_y), "left")
|
||||
time.sleep(1.0)
|
||||
return True
|
||||
@@ -3856,8 +3857,8 @@ Example: x_pct=0.50, y_pct=0.30"""
|
||||
|
||||
raw_content = resp.json().get("message", {}).get("content", "")
|
||||
full_response = prefill + raw_content
|
||||
print(f" [POPUP-VLM] Réponse en {elapsed:.1f}s : {full_response.strip()}")
|
||||
logger.info(f"[POPUP-VLM] Réponse VLM ({elapsed:.1f}s) : {full_response.strip()}")
|
||||
print(f" [POPUP-VLM] Réponse en {elapsed:.1f}s : [len={len(full_response)}, has_target={'target' in full_response}]")
|
||||
logger.info(f"[POPUP-VLM] Réponse VLM ({elapsed:.1f}s) : [len={len(full_response)}, has_target={'target' in full_response}]")
|
||||
|
||||
# Extraire le texte du bouton depuis la réponse
|
||||
button_text = raw_content.strip().strip('"').strip("'").strip(".")
|
||||
@@ -4172,7 +4173,7 @@ Example: x_pct=0.50, y_pct=0.30"""
|
||||
try:
|
||||
self.keyboard.type(char)
|
||||
except Exception as e:
|
||||
logger.debug(f"Impossible de taper '{char}': {e}")
|
||||
logger.debug(f"Impossible de taper [1 char typed]: {e}")
|
||||
# Délai humain entre les frappes (40-120ms)
|
||||
time.sleep(random.uniform(0.04, 0.12))
|
||||
|
||||
|
||||
@@ -24,6 +24,8 @@ from dataclasses import dataclass
|
||||
from enum import Enum
|
||||
from typing import Any, Dict, List, Optional
|
||||
|
||||
from .log_safe import _title_hash
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
@@ -168,8 +170,8 @@ class RecoveryEngine:
|
||||
from ..window_info_crossplatform import get_active_window_info
|
||||
active = get_active_window_info()
|
||||
active_title = active.get("title", "")
|
||||
logger.info(f"Recovery : Alt+F4 sur '{active_title}'")
|
||||
print(f" [RECOVERY] Alt+F4 — fermeture de '{active_title}'")
|
||||
logger.info(f"Recovery : Alt+F4 sur [title_hash={_title_hash(active_title)}]")
|
||||
print(f" [RECOVERY] Alt+F4 — fermeture de [title_hash={_title_hash(active_title)}]")
|
||||
except Exception:
|
||||
logger.info("Recovery : Alt+F4 (fenêtre active inconnue)")
|
||||
print(" [RECOVERY] Alt+F4 — fermeture fenêtre indésirable")
|
||||
@@ -182,7 +184,7 @@ class RecoveryEngine:
|
||||
return RecoveryResult(
|
||||
action_taken=RecoveryAction.CLOSE_WINDOW,
|
||||
success=True,
|
||||
detail=f"Alt+F4 exécuté sur '{active_title if 'active_title' in dir() else '?'}'",
|
||||
detail=f"Alt+F4 exécuté sur [title_hash={_title_hash(active_title) if 'active_title' in dir() else '?'}]",
|
||||
)
|
||||
|
||||
elif strategy == RecoveryAction.CLICK_AWAY:
|
||||
|
||||
@@ -29,6 +29,7 @@ from .ui.capture_server import CaptureServer
|
||||
from .session.storage import SessionStorage
|
||||
from .vision.capturer import VisionCapturer
|
||||
from .finalize_contract import dispatch_finalize_result
|
||||
from .core.log_safe import _title_hash
|
||||
|
||||
# Import optionnel du client serveur (pour le chat et les workflows)
|
||||
# Deux chemins : relatif (depuis agent_v0.agent_v1) ou absolu (depuis C:\rpa_vision\agent_v1)
|
||||
@@ -261,7 +262,7 @@ class AgentV1:
|
||||
# Ne PAS en relancer une ici — deux threads poll simultanés causent
|
||||
# une race condition où les actions sont consommées mais pas exécutées.
|
||||
|
||||
logger.info(f"Session {self.session_id} ({workflow_name}) sur machine {self.machine_id} en cours...")
|
||||
logger.info(f"Session {self.session_id} [wf_hash={_title_hash(workflow_name)}] sur machine {self.machine_id} en cours...")
|
||||
|
||||
def _command_watchdog_loop(self):
|
||||
"""Surveille un fichier de commande pour executer des ordres visuels (legacy)."""
|
||||
|
||||
@@ -36,6 +36,7 @@ import requests
|
||||
from PIL import Image
|
||||
|
||||
from ..config import API_TOKEN, BASE_DIR, STREAMING_ENDPOINT
|
||||
from ..core.log_safe import _title_hash
|
||||
from .persistent_buffer import MAX_ATTEMPTS, PersistentBuffer
|
||||
|
||||
|
||||
@@ -138,7 +139,7 @@ class TraceStreamer:
|
||||
target=self._buffer_drain_loop, daemon=True
|
||||
)
|
||||
self._drain_thread.start()
|
||||
logger.info(f"Streamer pour {self.session_id} démarré")
|
||||
logger.info(f"Streamer démarré")
|
||||
|
||||
def stop(self):
|
||||
"""Arrêter le streaming et finaliser la session côté serveur.
|
||||
@@ -166,7 +167,7 @@ class TraceStreamer:
|
||||
self._drain_thread.join(timeout=2.0)
|
||||
|
||||
self._finalize_session()
|
||||
logger.info(f"Streamer pour {self.session_id} arrêté")
|
||||
logger.info(f"Streamer arrêté")
|
||||
|
||||
def push_event(self, event_data: dict):
|
||||
"""Enfile un événement pour envoi immédiat.
|
||||
@@ -632,7 +633,7 @@ class TraceStreamer:
|
||||
self._check_redirect(resp, url)
|
||||
if resp.ok:
|
||||
result = resp.json()
|
||||
logger.info(f"Session finalisée: {result}")
|
||||
logger.info(f"Session finalisée [status={result.get('status')}, wf_hash={_title_hash(result.get('workflow_name',''))}]")
|
||||
if self._on_finalize_result is not None:
|
||||
try:
|
||||
self._on_finalize_result(result)
|
||||
|
||||
@@ -29,6 +29,8 @@ from dataclasses import dataclass, field
|
||||
from enum import Enum
|
||||
from typing import Optional
|
||||
|
||||
from ..core.log_safe import _title_hash
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
@@ -132,7 +134,7 @@ class ActivityPanel:
|
||||
)
|
||||
self._notifier_changement()
|
||||
self._rafraichir_ui()
|
||||
logger.info(f"[ACTIVITY] Workflow démarré : {nom} ({nb_etapes} étapes)")
|
||||
logger.info(f"[ACTIVITY] Workflow démarré : [wf_hash={_title_hash(nom)}] ({nb_etapes} étapes)")
|
||||
|
||||
def mettre_a_jour(
|
||||
self,
|
||||
|
||||
@@ -27,6 +27,8 @@ import os
|
||||
import time
|
||||
from http.server import HTTPServer, BaseHTTPRequestHandler
|
||||
|
||||
from ..core.log_safe import _path_ext
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
CAPTURE_PORT = int(os.environ.get("RPA_CAPTURE_PORT", "5006"))
|
||||
@@ -312,7 +314,7 @@ class _FileActionHandlerLocal:
|
||||
})
|
||||
extensions[ext] = extensions.get(ext, 0) + 1
|
||||
|
||||
logger.info(f"Liste dossier '{path_str}' : {len(files)} fichiers")
|
||||
logger.info(f"Liste dossier [ext={_path_ext(path_str)}] : {len(files)} fichiers")
|
||||
return {"files": files, "count": len(files), "extensions": extensions, "path": path_str}
|
||||
|
||||
def _create_dir(self, params: dict) -> dict:
|
||||
@@ -328,7 +330,7 @@ class _FileActionHandlerLocal:
|
||||
target = _Path(path_str)
|
||||
existed = target.exists()
|
||||
target.mkdir(parents=True, exist_ok=True)
|
||||
logger.info(f"Dossier '{path_str}' {'existait deja' if existed else 'cree'}")
|
||||
logger.info(f"Dossier [ext={_path_ext(path_str)}] {'existait deja' if existed else 'cree'}")
|
||||
return {"created": not existed, "path": path_str, "already_existed": existed}
|
||||
|
||||
def _move_file(self, params: dict) -> dict:
|
||||
@@ -350,7 +352,7 @@ class _FileActionHandlerLocal:
|
||||
|
||||
_Path(dst).parent.mkdir(parents=True, exist_ok=True)
|
||||
_shutil.move(src, dst)
|
||||
logger.info(f"Fichier deplace : '{src}' -> '{dst}'")
|
||||
logger.info(f"Fichier deplace : [ext={_path_ext(src)}] -> [ext={_path_ext(dst)}]")
|
||||
return {"moved": True, "source": src, "destination": dst}
|
||||
|
||||
def _copy_file(self, params: dict) -> dict:
|
||||
@@ -376,7 +378,7 @@ class _FileActionHandlerLocal:
|
||||
_shutil.copytree(src, dst)
|
||||
else:
|
||||
_shutil.copy2(src, dst)
|
||||
logger.info(f"Fichier copie : '{src}' -> '{dst}'")
|
||||
logger.info(f"Fichier copie : [ext={_path_ext(src)}] -> [ext={_path_ext(dst)}]")
|
||||
return {"copied": True, "source": src, "destination": dst}
|
||||
|
||||
def _sort_by_extension(self, params: dict) -> dict:
|
||||
@@ -425,7 +427,7 @@ class _FileActionHandlerLocal:
|
||||
extensions[ext] = extensions.get(ext, 0) + 1
|
||||
|
||||
logger.info(
|
||||
f"Classement par extension '{source_dir_str}' : {len(moved)} fichiers"
|
||||
f"Classement par extension [ext={_path_ext(source_dir_str)}] : {len(moved)} fichiers"
|
||||
)
|
||||
return {
|
||||
"moved": moved,
|
||||
|
||||
@@ -19,6 +19,8 @@ import platform
|
||||
import subprocess
|
||||
from typing import Any, Dict, Optional
|
||||
|
||||
from .core.log_safe import _title_hash
|
||||
|
||||
|
||||
def _run_cmd(cmd: list[str]) -> Optional[str]:
|
||||
"""Exécute une commande et renvoie la sortie texte (strippée), ou None en cas d'erreur."""
|
||||
@@ -372,7 +374,7 @@ if __name__ == "__main__":
|
||||
for i in range(5):
|
||||
info = get_active_window_info()
|
||||
rect = get_active_window_rect()
|
||||
print(f"[{i+1}] App: {info['app_name']:20s} | Title: {info['title']}")
|
||||
print(f"[{i+1}] App: {info['app_name']:20s} | Title: [title_hash={_title_hash(info['title'])}]")
|
||||
if rect:
|
||||
print(f" Rect: {rect['rect']} | Size: {rect['size']}")
|
||||
else:
|
||||
|
||||
Reference in New Issue
Block a user