feat(scheduler,rotation,envelope,api,ui): pgqueuer jobs and deferred injects, master rotation with handout, vault envelope, jobs page

This commit is contained in:
hh
2026-08-29 02:29:05 +02:00
parent 0479d7cdda
commit 662bdcdfda
27 changed files with 2123 additions and 120 deletions
+43 -13
View File
@@ -60,6 +60,7 @@ if TYPE_CHECKING:
from pathlib import Path
from beaver_gateway.core.conversations import Conversations
from beaver_gateway.core.scheduler import Scheduler
from beaver_gateway.frontends.base import GatewayRuntime
_log = logging.getLogger("beaver_gateway.frontends.api")
@@ -525,19 +526,48 @@ def build_app(runtime: GatewayRuntime, *, memory_root: Path | None = None) -> Fa
await require_token(request, runtime, scope=SCOPE)
raw = request.query_params.get("conversation")
conv = await conv_of(raw) if raw else None
return {
"schedules": [
{
"id": s.id,
"conversation_row": s.conversation_id,
"execute_at": _iso(s.execute_at),
"text": s.text,
"created_at": _iso(s.created_at),
"delivered_at": _iso(s.delivered_at),
}
for s in await conversations.schedules(conv)
]
}
return {"schedules": await conversations.schedules(conv)}
def scheduler_of() -> Scheduler:
if runtime.scheduler is None:
raise HTTPException(status.HTTP_503_SERVICE_UNAVAILABLE, "no scheduler")
return cast("Scheduler", runtime.scheduler)
@app.get("/jobs")
async def jobs(request: Request) -> dict[str, Any]:
await require_token(request, runtime, scope=SCOPE)
return await scheduler_of().snapshot()
@app.post("/jobs/{name}/run", status_code=status.HTTP_202_ACCEPTED)
async def run_job(name: str, request: Request) -> dict[str, Any]:
await require_token(request, runtime, scope=SCOPE)
scheduler = scheduler_of()
job = scheduler.job(name)
if job is None:
raise HTTPException(status.HTTP_404_NOT_FOUND, f"no job {name!r}")
return {"job": await scheduler.trigger(job, await body_of(request))}
@app.delete("/jobs/queue/{job_id}")
async def cancel_job(job_id: int, request: Request) -> dict[str, Any]:
await require_token(request, runtime, scope=SCOPE)
if not await scheduler_of().cancel(job_id):
raise HTTPException(status.HTTP_404_NOT_FOUND, f"no queued job {job_id}")
return {"cancelled": job_id}
@app.post(
"/conversations/{public_id}/schedule", status_code=status.HTTP_201_CREATED
)
async def post_schedule(public_id: str, request: Request) -> dict[str, Any]:
await require_token(request, runtime, scope=SCOPE)
conv = await conv_of(public_id)
data = await body_of(request)
try:
job_id, when = await conversations.schedule(
conv, text_of(data, "at"), text_of(data, "text")
)
except (RuntimeError, ValueError) as exc:
raise HTTPException(status.HTTP_400_BAD_REQUEST, str(exc)) from exc
return {"job": job_id, "execute_at": _iso(when)}
@app.get("/usage")
async def usage(request: Request) -> dict[str, Any]: