feat: on-demand auto-triage from web UI
Adds an "Auto-process queue" button to /admin/triage that lets the
user kick off a full LLM triage pass without SSH. Bridges the gap
between web UI (in container) and claude CLI (host-only).
Architecture:
- UI button POSTs to /admin/triage/request-drain
- Endpoint writes atocore/config/auto_triage_requested_at flag
- Host-side watcher cron (every 2 min) checks for the flag
- When found: clears flag, acquires lock, runs auto_triage.py,
records progress via atocore/status/* entries
- UI polls /admin/triage/drain-status every 10s to show progress,
auto-reloads when done
Safety:
- Lock file prevents concurrent runs on host
- Flag cleared before run so duplicate clicks queue at most one re-run
- Fail-open: watcher errors just log, don't break anything
- Status endpoint stays read-only
Installation on host (one-time):
*/2 * * * * /srv/storage/atocore/app/deploy/dalidou/auto-triage-watcher.sh \
>> /home/papa/atocore-logs/auto-triage-watcher.log 2>&1
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -130,6 +130,57 @@ def admin_triage(limit: int = 100) -> HTMLResponse:
|
||||
return HTMLResponse(content=render_triage_page(limit=limit))
|
||||
|
||||
|
||||
@router.post("/admin/triage/request-drain")
|
||||
def admin_triage_request_drain() -> dict:
|
||||
"""Request a host-side auto-triage run.
|
||||
|
||||
Writes a flag in project state. A host cron watcher picks it up
|
||||
within ~2min and runs auto_triage.py, then clears the flag.
|
||||
This is the bridge between "user clicked button in web UI" and
|
||||
"claude CLI (on host, not in container) runs".
|
||||
"""
|
||||
from datetime import datetime as _dt, timezone as _tz
|
||||
from atocore.context.project_state import set_state
|
||||
|
||||
now = _dt.now(_tz.utc).strftime("%Y-%m-%dT%H:%M:%SZ")
|
||||
set_state(
|
||||
project_name="atocore",
|
||||
category="config",
|
||||
key="auto_triage_requested_at",
|
||||
value=now,
|
||||
source="admin ui",
|
||||
)
|
||||
return {"requested_at": now, "note": "Host watcher will pick this up within 2 minutes."}
|
||||
|
||||
|
||||
@router.get("/admin/triage/drain-status")
|
||||
def admin_triage_drain_status() -> dict:
|
||||
"""Current state of the auto-triage pipeline (for UI polling)."""
|
||||
from atocore.context.project_state import get_state
|
||||
out = {
|
||||
"requested_at": None,
|
||||
"last_started_at": None,
|
||||
"last_finished_at": None,
|
||||
"last_result": None,
|
||||
"is_running": False,
|
||||
}
|
||||
try:
|
||||
for e in get_state("atocore"):
|
||||
if e.category == "config" and e.key == "auto_triage_requested_at":
|
||||
out["requested_at"] = e.value
|
||||
elif e.category == "status" and e.key == "auto_triage_last_started_at":
|
||||
out["last_started_at"] = e.value
|
||||
elif e.category == "status" and e.key == "auto_triage_last_finished_at":
|
||||
out["last_finished_at"] = e.value
|
||||
elif e.category == "status" and e.key == "auto_triage_last_result":
|
||||
out["last_result"] = e.value
|
||||
elif e.category == "status" and e.key == "auto_triage_running":
|
||||
out["is_running"] = (e.value == "1")
|
||||
except Exception:
|
||||
pass
|
||||
return out
|
||||
|
||||
|
||||
# --- Request/Response models ---
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user