Backup automatico script del 2026-09-27 07:00
This commit is contained in:
1 parent
f347badf3b
commit
09dcbbdadb
24 files changed
+1164
-193
No files matched your search
@@ -7,7 +7,7 @@ from typing import Any, Optional
|
||||
from .. import audit
|
||||
from ..context import store as context_store
|
||||
from ..jwt_utils import has_scope
|
||||
from ..knowledge import apps_indexer, gitea, gitea_indexer, indexer, paperless
|
||||
from ..knowledge import apps_indexer, gitea, gitea_indexer, indexer, paperless, qdrant_store
|
||||
from ..integrations import casa, homeassistant, irrigazione, turni
|
||||
|
||||
|
||||
@@ -25,7 +25,10 @@ def tool_definitions() -> list[dict]:
|
||||
},
|
||||
{
|
||||
"name": "list_projects",
|
||||
"description": "Elenca i progetti dell'utente corrente",
|
||||
"description": (
|
||||
"Elenca i progetti personali dell'utente e i contesti condivisi con tutta la famiglia "
|
||||
"(campo shared=true, con owner e updated_by). I condivisi sono letti e aggiornati da tutti."
|
||||
),
|
||||
"inputSchema": {
|
||||
"type": "object",
|
||||
"properties": {"include_archived": {"type": "boolean", "default": False}},
|
||||
@@ -39,6 +42,11 @@ def tool_definitions() -> list[dict]:
|
||||
"required": ["title"],
|
||||
"properties": {
|
||||
"title": {"type": "string"},
|
||||
"shared": {
|
||||
"type": "boolean",
|
||||
"default": False,
|
||||
"description": "Contesto condiviso con tutta la famiglia (tutti leggono e aggiornano)",
|
||||
},
|
||||
"tags": {"type": "array", "items": {"type": "string"}},
|
||||
"gitea_repo": {
|
||||
"type": "string",
|
||||
@@ -91,7 +99,13 @@ def tool_definitions() -> list[dict]:
|
||||
},
|
||||
{
|
||||
"name": "save_context",
|
||||
"description": "Salva o appende memoria persistente in un progetto",
|
||||
"description": (
|
||||
"Salva o appende memoria persistente in un progetto. Nei contesti condivisi (shared=true) "
|
||||
"la modifica vale per tutta la famiglia: quando cambia un dato (acquisti, inventario, "
|
||||
"prezzi, regole) aggiorna la sezione esistente con mode=replace del testo completo invece "
|
||||
"di appendere informazioni che la contraddicono, e passa if_revision (meta.revision letto "
|
||||
"con get_project_context) per non sovrascrivere modifiche fatte nel frattempo da altri."
|
||||
),
|
||||
"inputSchema": {
|
||||
"type": "object",
|
||||
"required": ["project_id", "content"],
|
||||
@@ -99,12 +113,32 @@ def tool_definitions() -> list[dict]:
|
||||
"project_id": {"type": "string"},
|
||||
"content": {"type": "string"},
|
||||
"mode": {"type": "string", "enum": ["append", "replace"], "default": "append"},
|
||||
"if_revision": {
|
||||
"type": "integer",
|
||||
"description": "meta.revision letto prima di modificare: se è cambiato, errore di conflitto",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
"name": "share_project",
|
||||
"description": (
|
||||
"Condivide con tutta la famiglia un tuo progetto personale (shared=true): tutti potranno "
|
||||
"leggerlo e aggiornarlo, storia inclusa. shared=false lo riporta personale del proprietario "
|
||||
"(solo proprietario o admin)."
|
||||
),
|
||||
"inputSchema": {
|
||||
"type": "object",
|
||||
"required": ["project_id"],
|
||||
"properties": {
|
||||
"project_id": {"type": "string"},
|
||||
"shared": {"type": "boolean", "default": True},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
"name": "archive_project",
|
||||
"description": "Archivia o ripristina un progetto",
|
||||
"description": "Archivia o ripristina un progetto (se condiviso: solo proprietario o admin)",
|
||||
"inputSchema": {
|
||||
"type": "object",
|
||||
"required": ["project_id"],
|
||||
@@ -116,7 +150,7 @@ def tool_definitions() -> list[dict]:
|
||||
},
|
||||
{
|
||||
"name": "search_context",
|
||||
"description": "Ricerca semantica nei contesti salvati dell'utente",
|
||||
"description": "Ricerca semantica nei contesti salvati dell'utente e in quelli condivisi con la famiglia",
|
||||
"inputSchema": {
|
||||
"type": "object",
|
||||
"required": ["query"],
|
||||
@@ -163,7 +197,7 @@ def tool_definitions() -> list[dict]:
|
||||
},
|
||||
{
|
||||
"name": "reindex_gitea_repo",
|
||||
"description": "Re-indicizza i file testo di un repository Gitea (admin o owner repo)",
|
||||
"description": "Re-indicizza i file testo di un repository Gitea (admin o owner repo). Asincrono: risponde subito, l'indicizzazione prosegue in background",
|
||||
"inputSchema": {
|
||||
"type": "object",
|
||||
"required": ["repo"],
|
||||
@@ -195,7 +229,7 @@ def tool_definitions() -> list[dict]:
|
||||
},
|
||||
{
|
||||
"name": "reindex_document",
|
||||
"description": "Re-indicizza un documento Paperless (admin)",
|
||||
"description": "Re-indicizza un documento Paperless (admin). Asincrono: risponde subito",
|
||||
"inputSchema": {
|
||||
"type": "object",
|
||||
"required": ["doc_id"],
|
||||
@@ -312,7 +346,7 @@ def tool_definitions() -> list[dict]:
|
||||
"reindex": {
|
||||
"type": "boolean",
|
||||
"default": True,
|
||||
"description": "Aggiorna subito il vector store RAG per questo file",
|
||||
"description": "Aggiorna il vector store RAG per questo file (in background)",
|
||||
},
|
||||
},
|
||||
},
|
||||
@@ -447,7 +481,7 @@ def tool_definitions() -> list[dict]:
|
||||
},
|
||||
{
|
||||
"name": "reindex_apps",
|
||||
"description": "Forza re-indicizzazione RAG da Irrigazione e/o Turni (admin)",
|
||||
"description": "Forza re-indicizzazione RAG da Irrigazione e/o Turni (admin). Asincrono: risponde subito",
|
||||
"inputSchema": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
@@ -498,12 +532,32 @@ def call_tool(name: str, arguments: dict, claims: dict) -> dict:
|
||||
arguments.get("tags"),
|
||||
gitea_repo=gitea_repo,
|
||||
seed_from_gitea=bool(arguments.get("seed_from_gitea")),
|
||||
shared=bool(arguments.get("shared")),
|
||||
)
|
||||
except RuntimeError as exc:
|
||||
raise PermissionError(str(exc)) from exc
|
||||
except ValueError as exc:
|
||||
raise ValueError(str(exc)) from exc
|
||||
audit.log_tool(username, name, meta["id"])
|
||||
audit.log_tool(username, name, meta["id"], detail={"shared": bool(meta.get("shared"))})
|
||||
return _text_result(meta)
|
||||
|
||||
if name == "share_project":
|
||||
if not has_scope(claims, "context:write"):
|
||||
raise PermissionError("Scope context:write richiesto")
|
||||
project_id = arguments["project_id"]
|
||||
shared = bool(arguments.get("shared", True))
|
||||
before = context_store.get_project_context(username, project_id, session_limit=0, include_gitea=False)
|
||||
owner = before["meta"].get("owner") or username
|
||||
was_shared = bool(before["meta"].get("shared"))
|
||||
meta = context_store.share_project(username, project_id, shared=shared, is_admin=is_admin)
|
||||
if was_shared != bool(meta.get("shared")):
|
||||
# Sposta i vettori: via dalla collezione di partenza, reindicizza nella nuova
|
||||
source = qdrant_store.ctx_collection(owner) if shared else qdrant_store.CTX_SHARED_COLLECTION
|
||||
indexer.submit_background(
|
||||
f"share {project_id} shared={shared}",
|
||||
_move_project_vectors, source, owner, project_id,
|
||||
)
|
||||
audit.log_tool(username, name, project_id, detail={"shared": shared})
|
||||
return _text_result(meta)
|
||||
|
||||
if name == "link_project_repo":
|
||||
@@ -558,13 +612,13 @@ def call_tool(name: str, arguments: dict, claims: dict) -> dict:
|
||||
arguments["project_id"],
|
||||
arguments["content"],
|
||||
arguments.get("mode", "append"),
|
||||
if_revision=arguments.get("if_revision"),
|
||||
)
|
||||
indexer.submit_background(
|
||||
f"context {username}/{arguments['project_id']}",
|
||||
indexer.index_project_context,
|
||||
username, arguments["project_id"],
|
||||
)
|
||||
try:
|
||||
indexer.index_context_snippet(
|
||||
username, arguments["project_id"], arguments["content"], meta["title"]
|
||||
)
|
||||
except Exception:
|
||||
pass
|
||||
audit.log_tool(username, name, arguments["project_id"])
|
||||
return _text_result(meta)
|
||||
|
||||
@@ -572,7 +626,7 @@ def call_tool(name: str, arguments: dict, claims: dict) -> dict:
|
||||
if not has_scope(claims, "context:write"):
|
||||
raise PermissionError("Scope context:write richiesto")
|
||||
meta = context_store.archive_project(
|
||||
username, arguments["project_id"], arguments.get("archived", True)
|
||||
username, arguments["project_id"], arguments.get("archived", True), is_admin=is_admin
|
||||
)
|
||||
audit.log_tool(username, name, arguments["project_id"])
|
||||
return _text_result(meta)
|
||||
@@ -623,17 +677,16 @@ def call_tool(name: str, arguments: dict, claims: dict) -> dict:
|
||||
owner = repo.split("/", 1)[0].lower()
|
||||
if not is_admin and owner != username:
|
||||
raise PermissionError("Puoi re-indicizzare solo repository di cui sei owner")
|
||||
try:
|
||||
result = gitea_indexer.index_repo(
|
||||
repo,
|
||||
username=username,
|
||||
force=True,
|
||||
max_files=arguments.get("max_files"),
|
||||
)
|
||||
except Exception as exc:
|
||||
raise PermissionError(f"Re-indicizzazione Gitea fallita: {exc}") from exc
|
||||
indexer.submit_background(
|
||||
f"gitea repo {repo}",
|
||||
gitea_indexer.index_repo,
|
||||
repo,
|
||||
username=username,
|
||||
force=True,
|
||||
max_files=arguments.get("max_files"),
|
||||
)
|
||||
audit.log_tool(username, name, detail={"repo": repo})
|
||||
return _text_result(result)
|
||||
return _text_result({"queued": True, "repo": repo})
|
||||
|
||||
if name == "get_document":
|
||||
if not has_scope(claims, "knowledge:read"):
|
||||
@@ -655,9 +708,10 @@ def call_tool(name: str, arguments: dict, claims: dict) -> dict:
|
||||
if name == "reindex_document":
|
||||
if not is_admin:
|
||||
raise PermissionError("Scope admin richiesto")
|
||||
result = indexer.index_document(int(arguments["doc_id"]), force=True)
|
||||
audit.log_tool(username, name, str(arguments["doc_id"]))
|
||||
return _text_result(result)
|
||||
doc_id = int(arguments["doc_id"])
|
||||
indexer.submit_background(f"paperless doc {doc_id}", indexer.index_document, doc_id, force=True)
|
||||
audit.log_tool(username, name, str(doc_id))
|
||||
return _text_result({"queued": True, "doc_id": doc_id})
|
||||
|
||||
if name in (
|
||||
"list_repos", "get_file", "search_code", "list_issues", "get_issue", "create_issue",
|
||||
@@ -792,22 +846,14 @@ def call_tool(name: str, arguments: dict, claims: dict) -> dict:
|
||||
username=username,
|
||||
)
|
||||
if arguments.get("reindex", True):
|
||||
try:
|
||||
owner, repo_name = gitea.parse_repo(repo)
|
||||
meta = gitea._request(
|
||||
"GET", f"/repos/{owner}/{repo_name}", username=username
|
||||
)
|
||||
private = bool(meta.get("private"))
|
||||
idx = gitea_indexer.index_file(
|
||||
repo,
|
||||
arguments["path"],
|
||||
username=username,
|
||||
private=private,
|
||||
force=True,
|
||||
)
|
||||
result["reindex"] = idx
|
||||
except Exception:
|
||||
result["reindex"] = {"skipped": True}
|
||||
indexer.submit_background(
|
||||
f"gitea file {repo}:{arguments['path']}",
|
||||
_reindex_gitea_file,
|
||||
repo,
|
||||
arguments["path"],
|
||||
username,
|
||||
)
|
||||
result["reindex"] = {"queued": True}
|
||||
except PermissionError:
|
||||
raise
|
||||
except Exception as exc:
|
||||
@@ -941,17 +987,31 @@ def call_tool(name: str, arguments: dict, claims: dict) -> dict:
|
||||
raise PermissionError("Scope admin richiesto")
|
||||
src = arguments.get("source", "all")
|
||||
if src == "irrigazione":
|
||||
result = apps_indexer.index_irrigazione()
|
||||
job = apps_indexer.index_irrigazione
|
||||
elif src == "turni":
|
||||
result = apps_indexer.index_turni()
|
||||
job = apps_indexer.index_turni
|
||||
else:
|
||||
result = apps_indexer.index_all()
|
||||
job = apps_indexer.index_all
|
||||
indexer.submit_background(f"apps {src}", job)
|
||||
audit.log_tool(username, name, detail={"source": src})
|
||||
return _text_result(result)
|
||||
return _text_result({"queued": True, "source": src})
|
||||
|
||||
raise ValueError(f"Tool sconosciuto: {name}")
|
||||
|
||||
|
||||
def _move_project_vectors(source_collection: str, owner: str, project_id: str) -> None:
|
||||
indexer.drop_project_context(source_collection, project_id)
|
||||
indexer.index_project_context(owner, project_id)
|
||||
|
||||
|
||||
def _reindex_gitea_file(repo: str, path: str, username: str) -> None:
|
||||
owner, repo_name = gitea.parse_repo(repo)
|
||||
meta = gitea._request("GET", f"/repos/{owner}/{repo_name}", username=username)
|
||||
gitea_indexer.index_file(
|
||||
repo, path, username=username, private=bool(meta.get("private")), force=True
|
||||
)
|
||||
|
||||
|
||||
def list_resources(claims: dict) -> list:
|
||||
username = claims["sub"]
|
||||
return context_store.list_resources(username)
|
||||
|
||||