agent-zero/plugins/_agent_editor/api/agent_editor.py
Alessandro 027466c873 Add project-scoped Agent Editor profiles
Reuse the existing project/profile layers so Manage agents can switch between Global and project scope while sparse reads, writes, resets, and policy configs stay bound to the selected layer.

Make deletion scope-owned, preserve inherited profiles, and open Save & test chats in the exact selected scope.
2026-08-12 14:34:15 +02:00

82 lines
3.5 KiB
Python

from __future__ import annotations
from pathlib import Path
from typing import Any
from agent import AgentContext
from helpers import projects
from helpers.api import ApiHandler, Request, Response
from plugins._agent_editor.helpers import editor
class AgentEditor(ApiHandler):
async def process(self, input: dict, request: Request) -> dict | Response:
try:
action = str(input.get("action") or "list").strip().lower()
context = _context(input)
if action == "list":
return {"ok": True, "profiles": editor.list_profiles(context)}
if action == "load":
profile_id = editor.validate_profile_id(input.get("profile_id"))
return {
"ok": True,
"state": editor.build_editor_state(profile_id, context),
}
if action in {"plan", "save"}:
patch = input.get("patch")
plan = editor.build_change_plan(patch, context)
if action == "plan":
return {"ok": True, **plan.response()}
receipt = editor.apply_change_plan(plan)
profile_id = editor.validate_profile_id(patch.get("profile_id"))
return {
"ok": True,
**receipt,
"effective_profile": editor.build_profile_state(profile_id, context),
}
if action in {"plan_remove_changes", "remove_changes"}:
profile_id = editor.validate_profile_id(input.get("profile_id"))
plan = editor.plan_remove_changes(
profile_id,
context,
destructive=bool(input.get("destructive")),
)
if action == "plan_remove_changes":
return {"ok": True, **plan.response()}
return {"ok": True, **editor.apply_change_plan(plan)}
if action == "delete_impact":
return {
"ok": True,
"impact": editor.delete_impact(input.get("profile_id"), context),
}
if action in {"plan_delete", "delete"}:
profile_id = editor.validate_profile_id(input.get("profile_id"))
plan = editor.plan_delete_custom(profile_id, context)
if action == "plan_delete":
return {
"ok": True,
**plan.response(),
"impact": editor.delete_impact(profile_id, context),
}
if input.get("confirm") is not True:
raise ValueError("Deleting a custom agent requires confirmation.")
return {"ok": True, **editor.apply_change_plan(plan)}
raise ValueError(f"Unknown Agent Editor action: {action}")
except ValueError as exc:
return Response(status=400, response=str(exc), mimetype="text/plain")
def _context(input: dict[str, Any]) -> Any:
context_id = str(input.get("context_id") or "").strip()
if context_id:
context = AgentContext.get(context_id)
if not context:
raise ValueError("Chat context not found.")
return context
project_name = str(input.get("project_name") or "").strip()
if project_name:
project_name = projects.validate_project_name(project_name)
if not Path(projects.get_project_folder(project_name)).is_dir():
raise ValueError("Project not found.")
return editor._EditorContext(project_name)