feat: add api key auth, devii agent, openai gateway, and admin service management

This commit introduces a comprehensive set of new features including API key authentication with CLI management commands (get, reset, backfill), a Devii agentic assistant with WebSocket terminal and session bootstrap, an OpenAI-compatible LLM gateway service, and an admin service management panel. It also adds Playwright browser automation for bot support, configures internal gateway URLs, refactors content editing/deletion to support JSON API responses, and updates documentation across AGENTS.md, README.md, and the developer docs site.
This commit is contained in:
2026-06-08 15:38:33 +00:00
parent 921e382cbc
commit e0535bb7c5
270 changed files with 54408 additions and 541 deletions
@@ -0,0 +1,195 @@
# retoor <retoor@molodetz.nl>
from __future__ import annotations
import json
import logging
from typing import Any, Optional
from ..config import Settings
from ..errors import ToolInputError
from .lessons import LessonStore
from .loop import TraceCallback, react_loop
from .state import AgentState, get_state
logger = logging.getLogger("devii.agentic.controller")
SUB_AGENT_SYSTEM_PROMPT = (
"You are a focused sub-agent spawned to complete a single scoped task on this "
"DevPlace platform. Begin with a plan() call. Investigate, then act with the most specific "
"tools available. If you change anything, confirm it and call verify() before returning. "
"Return a concise factual result string under 1500 characters."
)
NO_DELEGATE = "delegate"
class AgenticController:
def __init__(self, lessons: LessonStore, settings: Settings) -> None:
self._lessons = lessons
self._settings = settings
self._llm: Any = None
self._dispatcher: Any = None
self._tools: list[dict[str, Any]] = []
self._on_trace: Optional[TraceCallback] = None
self._cost_tracker: Any = None
self._chunk_store: Any = None
def bind(
self,
llm: Any,
dispatcher: Any,
tools: list[dict[str, Any]],
on_trace: Optional[TraceCallback],
cost_tracker: Any = None,
chunk_store: Any = None,
) -> None:
self._llm = llm
self._dispatcher = dispatcher
self._tools = tools
self._on_trace = on_trace
self._cost_tracker = cost_tracker
self._chunk_store = chunk_store
async def dispatch(self, name: str, arguments: dict[str, Any]) -> str:
handlers = {
"plan": self._plan,
"reflect": self._reflect,
"recall": self._recall,
"forget_lessons": self._forget,
"verify": self._verify,
"delegate": self._delegate,
}
handler = handlers.get(name)
if handler is None:
raise ToolInputError(f"Unknown agentic tool: {name}")
return await handler(arguments)
async def _plan(self, arguments: dict[str, Any]) -> str:
goal = str(arguments.get("goal", "")).strip()
steps = arguments.get("steps") or []
if not goal:
raise ToolInputError("plan requires a goal.")
if not isinstance(steps, list) or not steps:
raise ToolInputError("plan requires a non-empty steps list.")
confidence = float(arguments.get("confidence", 0.8) or 0.8)
state = get_state()
if state is not None:
state.plan = {
"goal": goal,
"steps": steps,
"success_criteria": arguments.get("success_criteria", ""),
"confidence": confidence,
}
advice = ""
if confidence < 0.6:
advice = "Confidence is below 0.6 - gather more context or recall() past lessons before executing."
return json.dumps(
{"status": "success", "plan_recorded": True, "step_count": len(steps), "advice": advice},
ensure_ascii=False,
)
async def _reflect(self, arguments: dict[str, Any]) -> str:
observation = str(arguments.get("observation", "")).strip()
conclusion = str(arguments.get("conclusion", "")).strip()
next_action = str(arguments.get("next_action", "")).strip()
if not (observation and conclusion and next_action):
raise ToolInputError("reflect requires observation, conclusion, and next_action.")
tags = str(arguments.get("tags", "") or "").strip()
record = self._lessons.add(observation, conclusion, next_action, tags)
state = get_state()
if state is not None:
state.reflections.append(
{"observation": observation, "conclusion": conclusion, "next_action": next_action}
)
return json.dumps(
{
"status": "success",
"lesson_uid": record["uid"],
"total_lessons": self._lessons.count(),
},
ensure_ascii=False,
)
async def _recall(self, arguments: dict[str, Any]) -> str:
query = str(arguments.get("query", "")).strip()
if not query:
raise ToolInputError("recall requires a query.")
k = int(arguments.get("k", self._settings.recall_top_k) or self._settings.recall_top_k)
hits = self._lessons.search(query, k=k)
return json.dumps({"status": "success", "count": len(hits), "lessons": hits}, ensure_ascii=False)
async def _forget(self, arguments: dict[str, Any]) -> str:
query = str(arguments.get("query", "") or "").strip()
if query:
removed = 0
for hit in self._lessons.search(query, k=50):
uid = hit.get("uid")
if uid and self._lessons.delete(uid):
removed += 1
else:
removed = self._lessons.clear()
return json.dumps(
{"status": "success", "forgotten": removed, "remaining": self._lessons.count()},
ensure_ascii=False,
)
async def _verify(self, arguments: dict[str, Any]) -> str:
summary = str(arguments.get("summary", "")).strip()
if not summary:
raise ToolInputError("verify requires a summary of what was confirmed.")
confirmed = arguments.get("confirmed", True)
if isinstance(confirmed, str):
confirmed = confirmed.strip().lower() not in ("", "false", "no", "0")
state = get_state()
if state is not None and confirmed:
state.verified = True
return json.dumps(
{"status": "success", "verified": bool(confirmed), "summary": summary}, ensure_ascii=False
)
async def _delegate(self, arguments: dict[str, Any]) -> str:
task = str(arguments.get("task", "")).strip()
if not task:
raise ToolInputError("delegate requires a task description.")
if self._llm is None or self._dispatcher is None:
raise ToolInputError("Delegation is not available in this context.")
allowed = arguments.get("allowed_tools")
if allowed:
allowed_set = {str(name) for name in allowed}
tools = [
tool
for tool in self._tools
if tool["function"]["name"] in allowed_set and tool["function"]["name"] != NO_DELEGATE
]
else:
tools = [tool for tool in self._tools if tool["function"]["name"] != NO_DELEGATE]
messages = [
{"role": "system", "content": SUB_AGENT_SYSTEM_PROMPT},
{"role": "user", "content": task},
]
sub_state = AgentState()
result = await react_loop(
llm=self._llm,
dispatcher=self._dispatcher,
messages=messages,
tools=tools,
state=sub_state,
settings=self._settings,
max_iterations=self._settings.delegate_max_iterations,
plan_required=self._settings.plan_required,
verify_required=self._settings.verify_required,
on_trace=self._on_trace,
cost_tracker=self._cost_tracker,
chunk_store=self._chunk_store,
)
return json.dumps(
{
"status": "success",
"iterations": sub_state.iteration,
"verified": sub_state.verified,
"reflections": len(sub_state.reflections),
"result": result,
},
ensure_ascii=False,
)