feat(assistant): implement tool registry and expand assistant capabilities

Introduce a centralized tool registry system for the assistant to manage
executable operations, permissions, and tool catalogs. This refactors
the assistant dispatch logic from a monolithic approach to a modular,
decorator-based registry.

Key changes:
- Implement `AssistantToolRegistry` to handle tool registration,
  permission checks, and safe operation identification.
- Add a suite of new assistant tools: backup, commit, branch creation,
  deployment, health summary, environment listing, LLM operations,
  maintenance, migration, and dashboard searching.
- Refactor `_dispatch.py` and `_llm_planner.py` to utilize the new
  registry for intent resolution and tool catalog building.
- Enhance the LLM planner to support more descriptive clarification
  responses in Russian when intents are ambiguous.
- Update the frontend to support the new tool-based workflow, including
  improved i18n labels for assistant operations and a new step-by-step
  wizard for the migration page.
- Add ADR-0008 to document the architectural decision for the tool
  registry.
This commit is contained in:
2026-05-27 18:05:28 +03:00
parent 5c40c9efce
commit bb1cea8fd2
33 changed files with 2892 additions and 850 deletions

View File

@@ -0,0 +1,79 @@
# #region AssistantToolTaskStatus [C:3] [TYPE Module] [SEMANTICS assistant, tool, status, task]
# @BRIEF Handler for the "get_task_status" tool — retrieve task status by id or latest user task.
# @LAYER API
# @RELATION DEPENDS_ON -> [AssistantToolRegistry]
# @RELATION DEPENDS_ON -> [TaskManager]
from __future__ import annotations
from typing import Any
from fastapi import HTTPException
from sqlalchemy.orm import Session
from src.core.config_manager import ConfigManager
from src.core.logger import belief_scope, logger
from src.core.task_manager import TaskManager
from src.schemas.auth import User
from ._resolvers import _build_task_observability_summary, _extract_result_deep_links
from ._schemas import AssistantAction
from ._tool_registry import _check_any_permission, assistant_tool
# #region handle_get_task_status [C:3] [TYPE Function]
@assistant_tool(
operation="get_task_status",
domain="status",
description="Get task status by task_id or latest user task",
optional_entities=["task_id"],
risk_level="safe",
requires_confirmation=False,
permission_checks=[("tasks", "READ")],
)
@belief_scope("get_task_status")
async def handle_get_task_status(
intent: dict[str, Any],
current_user: User,
task_manager: TaskManager,
config_manager: ConfigManager,
db: Session,
) -> tuple[str, str | None, list[AssistantAction]]:
"""Get task status by task_id or latest user task."""
_check_any_permission(current_user, [("tasks", "READ")])
entities = intent.get("entities", {})
task_id = entities.get("task_id")
if not task_id:
recent = [
t
for t in task_manager.get_tasks(limit=20, offset=0)
if t.user_id == current_user.id
]
if not recent:
return ("У вас пока нет задач в истории.", None, [])
task = recent[0]
actions = [AssistantAction(type="open_task", label="Open Task", target=task.id)]
if str(task.status).upper() in {"SUCCESS", "FAILED"}:
actions.extend(_extract_result_deep_links(task, config_manager))
summary_line = _build_task_observability_summary(task, config_manager)
text = (
f"Последняя задача: {task.id}, статус: {task.status}."
+ (f"\n{summary_line}" if summary_line else "")
)
return (text, task.id, actions)
task = task_manager.get_task(task_id)
if not task:
raise HTTPException(status_code=404, detail=f"Task {task_id} not found")
actions = [AssistantAction(type="open_task", label="Open Task", target=task.id)]
if str(task.status).upper() in {"SUCCESS", "FAILED"}:
actions.extend(_extract_result_deep_links(task, config_manager))
summary_line = _build_task_observability_summary(task, config_manager)
text = (
f"Статус задачи {task.id}: {task.status}."
+ (f"\n{summary_line}" if summary_line else "")
)
return (text, task.id, actions)
# #endregion handle_get_task_status
# #endregion AssistantToolTaskStatus