feat: menu + — Connecteurs (socle) — phase 5/8 (v7.55.0)
FlowDeck CI / lint (push) Successful in 2m11s
FlowDeck CI / test (push) Failing after 15m16s
FlowDeck CI / docker (push) Skipped

- Table agent_connectors (migration 32) : colonnes plates name, url,
  secret_encrypted, enabled, status, detail — config_json remis (YAGNI, les
  scopes OAuth des phases 6-7 ajouteront le leur).
- app/services/connectors.py : 1 fichier au lieu du package connectors/ —
  3 natifs (gitea, github, web) servis à la volée avec statut sans réseau,
  CRUD des personnels (URL validée par _validate_url = garde SSRF, clé chiffrée
  Fernet et jamais renvoyée — seul has_secret), probe() qui persiste
  status/detail, connector_fetch() borné à 20 000 car.
- API /api/agent/connectors : GET, POST (400 URL privée), PATCH, DELETE,
  POST /connectors/probe — 401 sans session, CSRF global. OpenAPI 519 chemins.
- Outil LLM connector_fetch (26e outil) : un seul outil qui dispatche vers
  Gitea/GitHub/web/personnalisé (id ou nom ou kind + path + query) au lieu d'un
  outil par connecteur ; désactivé/inconnu = erreur outil, jamais de run cassé.
- Menu + : section « Connecteurs » (catalogue avec badge ✓/✗/⚠/?), fiche par
  connecteur (Tester, Activer/Désactiver, Supprimer — masqués pour les natifs),
  formulaire « Ajouter un connecteur personnalisé » (clé en type=password).
- Tests : tests/test_v755_connectors.py (13) — 3 natifs, roundtrip Fernet de la
  clé, 5 URLs refusées (localhost, 127.0.0.1, metadata cloud, ftp:, file:),
  toggle persisté, 401, probe OK/erreur, outil complet, câblage menu ;
  test_v751/v753/v754 adaptés (1 seule section « bientôt » = plugins).
  Suite complète 1306 verts (-n auto), ruff 0, eslint 0 problème.
This commit is contained in:
2026-10-06 23:26:19 -04:00
parent 64c85caffc
commit 18c72d77fe
16 changed files with 974 additions and 29 deletions
+43 -1
View File
@@ -1072,6 +1072,48 @@ class SearchCode(Tool):
# ══════════════════════════ Registry ══════════════════════════
class ConnectorFetch(Tool):
name = "connector_fetch"
description = (
"Lit un connecteur configuré dans FlowDeck : API Gitea / GitHub (natives), "
"recherche web (natif « web »), ou un connecteur personnalisé (URL + clé). "
"`path` est relatif à l'URL du connecteur (ex. « /api/v1/repos ») ; "
"`query` sert au connecteur web. Les hôtes privés sont refusés."
)
parameters = {
"type": "object",
"properties": {
"connector": {"type": "string",
"description": "id, nom ou kind (gitea / github / web)"},
"path": {"type": "string", "description": "chemin relatif (API natives)"},
"query": {"type": "string", "description": "terme de recherche (web)"},
},
"required": ["connector"],
}
async def execute(self, args, *, user_id=None) -> ToolResult:
from app.services import connectors
try:
res = await connectors.connector_fetch(
str(args.get("connector") or ""),
str(args.get("path") or ""),
str(args.get("query") or ""),
)
except ValueError as exc:
return ToolResult(status="error", tool=self.name, message=str(exc))
except Exception as exc: # noqa: BLE001 — réseau : erreur outil, pas run
logger.warning("connector_fetch failed: %s", exc)
return ToolResult(status="error", tool=self.name,
message=f"Connecteur indisponible: {exc}")
if res.get("status") != "ok":
return ToolResult(status="error", tool=self.name,
message=(res.get("text") or "erreur")[:500])
text = res.get("text") or ""
return ToolResult(status="success", tool=self.name, target_type="connector",
message=text[:800], data={"text": text})
TOOL_CLASSES = [
SearchWorkspace, ReadCollection, ReadPage, ReadWorkspaces, ReadDocument,
CreateCollection, CreateView, AddProperty, CreatePage, CreateDocument,
@@ -1080,7 +1122,7 @@ TOOL_CLASSES = [
DeletePage, DeleteCollection,
ReadGiteaIssues, SyncGitea, CreateGiteaIssue,
DeleteDocument,
WebSearch, FetchUrl, SearchCode,
WebSearch, FetchUrl, SearchCode, ConnectorFetch,
]