Spaces:
Running
Running
sync: 172 file da Baida98/AI@b3591b8e (2026-08-23 11:01 UTC) [deploy-all] (#59)
Browse files- sync: 172 file da Baida98/AI@b3591b8e (2026-08-23 11:01 UTC) [deploy-all] (0717e4f65d6433d30fcd9c3e2e05b03fb7f9e24f)
- api/public_status.py +23 -4
- api/terminal.py +49 -0
- benchmark-extended.mjs +20 -3
api/public_status.py
CHANGED
|
@@ -10,7 +10,7 @@ import asyncio
|
|
| 10 |
import logging
|
| 11 |
from typing import Any
|
| 12 |
|
| 13 |
-
from fastapi import APIRouter
|
| 14 |
|
| 15 |
from .state import sb
|
| 16 |
|
|
@@ -28,7 +28,7 @@ async def public_status() -> dict[str, Any]:
|
|
| 28 |
"""Restituisce esclusivamente lo snapshot deliberatamente pubblico."""
|
| 29 |
client = sb()
|
| 30 |
if client is None:
|
| 31 |
-
|
| 32 |
|
| 33 |
def operation():
|
| 34 |
return client.table("public_dashboard_snapshot").select(_PUBLIC_FIELDS).eq("singleton", True).limit(1).execute()
|
|
@@ -37,11 +37,11 @@ async def public_status() -> dict[str, Any]:
|
|
| 37 |
result = await asyncio.to_thread(operation)
|
| 38 |
except Exception as exc:
|
| 39 |
_logger.warning("public status snapshot unavailable: %s", type(exc).__name__)
|
| 40 |
-
|
| 41 |
|
| 42 |
row = (result.data or [None])[0]
|
| 43 |
if not row:
|
| 44 |
-
|
| 45 |
|
| 46 |
return {
|
| 47 |
"service_status": str(row.get("service_status") or "unknown"),
|
|
@@ -51,3 +51,22 @@ async def public_status() -> dict[str, Any]:
|
|
| 51 |
"app_version": row.get("app_version"),
|
| 52 |
"updated_at": row.get("updated_at"),
|
| 53 |
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 10 |
import logging
|
| 11 |
from typing import Any
|
| 12 |
|
| 13 |
+
from fastapi import APIRouter
|
| 14 |
|
| 15 |
from .state import sb
|
| 16 |
|
|
|
|
| 28 |
"""Restituisce esclusivamente lo snapshot deliberatamente pubblico."""
|
| 29 |
client = sb()
|
| 30 |
if client is None:
|
| 31 |
+
return _degraded_snapshot("database_unavailable")
|
| 32 |
|
| 33 |
def operation():
|
| 34 |
return client.table("public_dashboard_snapshot").select(_PUBLIC_FIELDS).eq("singleton", True).limit(1).execute()
|
|
|
|
| 37 |
result = await asyncio.to_thread(operation)
|
| 38 |
except Exception as exc:
|
| 39 |
_logger.warning("public status snapshot unavailable: %s", type(exc).__name__)
|
| 40 |
+
return _degraded_snapshot("snapshot_unavailable")
|
| 41 |
|
| 42 |
row = (result.data or [None])[0]
|
| 43 |
if not row:
|
| 44 |
+
return _degraded_snapshot("snapshot_not_initialized")
|
| 45 |
|
| 46 |
return {
|
| 47 |
"service_status": str(row.get("service_status") or "unknown"),
|
|
|
|
| 51 |
"app_version": row.get("app_version"),
|
| 52 |
"updated_at": row.get("updated_at"),
|
| 53 |
}
|
| 54 |
+
|
| 55 |
+
|
| 56 |
+
def _degraded_snapshot(reason: str) -> dict[str, Any]:
|
| 57 |
+
"""Safe public response while the operational snapshot is unavailable.
|
| 58 |
+
|
| 59 |
+
The public endpoint is used by lightweight status surfaces. Returning a
|
| 60 |
+
deliberate degraded state keeps those surfaces functional without
|
| 61 |
+
exposing database errors, internal topology, or operational records.
|
| 62 |
+
"""
|
| 63 |
+
return {
|
| 64 |
+
"service_status": "degraded",
|
| 65 |
+
"active_sessions": 0,
|
| 66 |
+
"queued_tasks": 0,
|
| 67 |
+
"in_progress_tasks": 0,
|
| 68 |
+
"app_version": None,
|
| 69 |
+
"updated_at": None,
|
| 70 |
+
"degraded": True,
|
| 71 |
+
"reason": reason,
|
| 72 |
+
}
|
api/terminal.py
CHANGED
|
@@ -1,5 +1,6 @@
|
|
| 1 |
"""backend/api/terminal.py β WebSocket PTY terminal (S354 + S754-B + S755)."""
|
| 2 |
import os, asyncio, pty, fcntl, struct, termios, json, shlex, time, logging
|
|
|
|
| 3 |
from pathlib import Path
|
| 4 |
from fastapi import APIRouter, WebSocket, WebSocketDisconnect
|
| 5 |
from fastapi import Depends
|
|
@@ -8,6 +9,31 @@ from .auth_guard import require_role, AuthRole
|
|
| 8 |
router = APIRouter()
|
| 9 |
_logger = logging.getLogger("terminal")
|
| 10 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 11 |
# ββ Startup script (S755) βββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
| 12 |
# Scritto in /data/.bashrc_agente e sourciate da bash via --rcfile.
|
| 13 |
# Configura venv Python + npm persistenti, Playwright, workspace, aliases, prompt.
|
|
@@ -239,6 +265,25 @@ async def terminal_packages(role: AuthRole = Depends(require_role(AuthRole.MACHI
|
|
| 239 |
'generated_at': int(time.time()),
|
| 240 |
}
|
| 241 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 242 |
@router.websocket('/ws/terminal')
|
| 243 |
async def terminal_ws(ws: WebSocket):
|
| 244 |
"""
|
|
@@ -263,6 +308,8 @@ async def terminal_ws(ws: WebSocket):
|
|
| 263 |
await ws.close(code=4403)
|
| 264 |
return
|
| 265 |
await ws.accept()
|
|
|
|
|
|
|
| 266 |
loop = asyncio.get_event_loop()
|
| 267 |
|
| 268 |
# S755: assicura che /data/.bashrc_agente esista e sia aggiornato
|
|
@@ -327,6 +374,7 @@ async def terminal_ws(ws: WebSocket):
|
|
| 327 |
try:
|
| 328 |
data = await loop.run_in_executor(None, lambda: os.read(master_fd, 4096))
|
| 329 |
if data:
|
|
|
|
| 330 |
await ws.send_bytes(data)
|
| 331 |
# S754-B: salvataggio periodico ogni 60s durante attivitΓ
|
| 332 |
_now = time.monotonic()
|
|
@@ -361,6 +409,7 @@ async def terminal_ws(ws: WebSocket):
|
|
| 361 |
try:
|
| 362 |
await asyncio.gather(_reader(), _writer())
|
| 363 |
finally:
|
|
|
|
| 364 |
closed.set()
|
| 365 |
# S754-B: salva lo stato prima di terminare il processo.
|
| 366 |
# La sessione tmux Γ¨ ancora viva qui (proc Γ¨ il CLIENT tmux, non il SERVER).
|
|
|
|
| 1 |
"""backend/api/terminal.py β WebSocket PTY terminal (S354 + S754-B + S755)."""
|
| 2 |
import os, asyncio, pty, fcntl, struct, termios, json, shlex, time, logging
|
| 3 |
+
from collections import defaultdict, deque
|
| 4 |
from pathlib import Path
|
| 5 |
from fastapi import APIRouter, WebSocket, WebSocketDisconnect
|
| 6 |
from fastapi import Depends
|
|
|
|
| 9 |
router = APIRouter()
|
| 10 |
_logger = logging.getLogger("terminal")
|
| 11 |
|
| 12 |
+
# Recent PTY output used by the authenticated auto-repair diagnostic.
|
| 13 |
+
# Buffers are intentionally process-local and bounded: they are diagnostics,
|
| 14 |
+
# not a second persistence channel for terminal sessions.
|
| 15 |
+
_BUFFER_MAX_CHUNKS = 200
|
| 16 |
+
_BUFFER_MAX_CHARS = 20_000
|
| 17 |
+
_terminal_buffers: dict[str, deque[str]] = defaultdict(
|
| 18 |
+
lambda: deque(maxlen=_BUFFER_MAX_CHUNKS)
|
| 19 |
+
)
|
| 20 |
+
_terminal_active: set[str] = set()
|
| 21 |
+
|
| 22 |
+
|
| 23 |
+
def _session_id(value: str | None) -> str:
|
| 24 |
+
"""Normalize the client-provided diagnostic key without trusting it."""
|
| 25 |
+
value = (value or "default").strip()
|
| 26 |
+
return value[:128] or "default"
|
| 27 |
+
|
| 28 |
+
|
| 29 |
+
def _append_buffer(session_id: str, data: bytes) -> None:
|
| 30 |
+
text = data.decode("utf-8", errors="replace")
|
| 31 |
+
if text:
|
| 32 |
+
_terminal_buffers[session_id].append(text)
|
| 33 |
+
# Keep the joined diagnostic bounded even when chunks are large.
|
| 34 |
+
while sum(len(chunk) for chunk in _terminal_buffers[session_id]) > _BUFFER_MAX_CHARS:
|
| 35 |
+
_terminal_buffers[session_id].popleft()
|
| 36 |
+
|
| 37 |
# ββ Startup script (S755) βββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
| 38 |
# Scritto in /data/.bashrc_agente e sourciate da bash via --rcfile.
|
| 39 |
# Configura venv Python + npm persistenti, Playwright, workspace, aliases, prompt.
|
|
|
|
| 265 |
'generated_at': int(time.time()),
|
| 266 |
}
|
| 267 |
|
| 268 |
+
|
| 269 |
+
@router.get('/api/terminal/buffer/{session_id}')
|
| 270 |
+
async def terminal_buffer(
|
| 271 |
+
session_id: str,
|
| 272 |
+
role: AuthRole = Depends(require_role(AuthRole.MACHINE)),
|
| 273 |
+
):
|
| 274 |
+
"""Return a bounded recent PTY diagnostic buffer.
|
| 275 |
+
|
| 276 |
+
The route is machine-authenticated because terminal output can contain
|
| 277 |
+
project data. It deliberately exposes no tmux metadata or environment.
|
| 278 |
+
"""
|
| 279 |
+
sid = _session_id(session_id)
|
| 280 |
+
return {
|
| 281 |
+
"buffer": "".join(_terminal_buffers.get(sid, ())),
|
| 282 |
+
"active": sid in _terminal_active,
|
| 283 |
+
"session_id": sid,
|
| 284 |
+
}
|
| 285 |
+
|
| 286 |
+
|
| 287 |
@router.websocket('/ws/terminal')
|
| 288 |
async def terminal_ws(ws: WebSocket):
|
| 289 |
"""
|
|
|
|
| 308 |
await ws.close(code=4403)
|
| 309 |
return
|
| 310 |
await ws.accept()
|
| 311 |
+
_sid = _session_id(ws.query_params.get("session_id"))
|
| 312 |
+
_terminal_active.add(_sid)
|
| 313 |
loop = asyncio.get_event_loop()
|
| 314 |
|
| 315 |
# S755: assicura che /data/.bashrc_agente esista e sia aggiornato
|
|
|
|
| 374 |
try:
|
| 375 |
data = await loop.run_in_executor(None, lambda: os.read(master_fd, 4096))
|
| 376 |
if data:
|
| 377 |
+
_append_buffer(_sid, data)
|
| 378 |
await ws.send_bytes(data)
|
| 379 |
# S754-B: salvataggio periodico ogni 60s durante attivitΓ
|
| 380 |
_now = time.monotonic()
|
|
|
|
| 409 |
try:
|
| 410 |
await asyncio.gather(_reader(), _writer())
|
| 411 |
finally:
|
| 412 |
+
_terminal_active.discard(_sid)
|
| 413 |
closed.set()
|
| 414 |
# S754-B: salva lo stato prima di terminare il processo.
|
| 415 |
# La sessione tmux Γ¨ ancora viva qui (proc Γ¨ il CLIENT tmux, non il SERVER).
|
benchmark-extended.mjs
CHANGED
|
@@ -858,7 +858,21 @@ async function fetchSciQ(seed){
|
|
| 858 |
source:"allenai/sciq"};
|
| 859 |
}
|
| 860 |
|
| 861 |
-
// ββ
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 862 |
async function callAgent(goal,timeoutMs=90000,options={}){
|
| 863 |
const t0=Date.now(); let out="",engine="?",provider="?",model="?",ttfa=null,toolCalls=0,done=false,failed=false,failureReason="",taskId="",lastEvent="",lastEventAt=null;
|
| 864 |
const telemetry=()=>({provider:provider||"?",model:model||engine||"?",ttfaMs:ttfa??9999,lastEvent:lastEvent||null,lastEventAt});
|
|
@@ -870,8 +884,10 @@ async function callAgent(goal,timeoutMs=90000,options={}){
|
|
| 870 |
try{
|
| 871 |
const created=await fetch(`${BASE_URL}/api/agent/tasks`,{
|
| 872 |
method:"POST",headers,
|
| 873 |
-
body:JSON.stringify({goal,context:
|
| 874 |
-
session_id:`bext5_${Date.now()}_${Math.random().toString(36).slice(2,5)}`
|
|
|
|
|
|
|
| 875 |
signal:ctrl.signal});
|
| 876 |
if(!created.ok)return{ok:false,output:"",engine,...telemetry(),toolCalls,durationMs:Date.now()-t0,failed:true,failureReason:`create task HTTP ${created.status}`};
|
| 877 |
const createdBody=await created.json();
|
|
@@ -2453,6 +2469,7 @@ async function runOneSeed(seed,opts={}){
|
|
| 2453 |
C_autonomia:"autonomy",D_memoria:"memory_context",E_recovery:"recovery",
|
| 2454 |
F_robustezza:"robustness",G_operativitΓ :"coding+data_analysis"},
|
| 2455 |
methodology:{
|
|
|
|
| 2456 |
canonical_seed:"1337 (stesse domande per tutti gli agenti β usa --rotate per seed diverso)",
|
| 2457 |
coding:"enterprise: Acc(35%)+Stab(20%)+Auto(15%)+Perf(10%)+Spd(10%)+Cost(5%)+Tool(5%)",
|
| 2458 |
nonCoding:"content: Acc(40%)+Struct(20%)+Comp(15%)+Prec(10%)+Auto(5%)+Spd(5%)+Cost(5%)",
|
|
|
|
| 858 |
source:"allenai/sciq"};
|
| 859 |
}
|
| 860 |
|
| 861 |
+
// ββ Reale contesto chat per benchmark ββββββββββββββββββββββββββββββββββββββββ
|
| 862 |
+
// Fixture deterministica: rende confrontabili le run e copre il percorso chat
|
| 863 |
+
// senza usare dati personali o stato persistente di una sessione reale.
|
| 864 |
+
const BENCHMARK_CONTEXT = Object.freeze([
|
| 865 |
+
{ role: "user", content: "Sto lavorando su un'app web TypeScript. Voglio risposte verificabili, compatibili con Safari iPhone e senza regressioni." },
|
| 866 |
+
{ role: "assistant", content: "Ricevuto. TerrΓ² conto del contesto, distinguerΓ² ciΓ² che Γ¨ verificato da ciΓ² che Γ¨ solo ipotizzato e preserverΓ² le funzionalitΓ esistenti." },
|
| 867 |
+
]);
|
| 868 |
+
const BENCHMARK_PERSONA = "architect";
|
| 869 |
+
const BENCHMARK_NEGATIVE_CONSTRAINTS = [
|
| 870 |
+
"Non dichiarare di aver eseguito comandi, test o verifiche che non sono stati realmente eseguiti.",
|
| 871 |
+
"Non inventare file, endpoint, credenziali, dati o risultati.",
|
| 872 |
+
"Non rimuovere funzionalitΓ esistenti senza motivazione e compatibilitΓ esplicite.",
|
| 873 |
+
].join("\n");
|
| 874 |
+
|
| 875 |
+
// ββ callAgent βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
| 876 |
async function callAgent(goal,timeoutMs=90000,options={}){
|
| 877 |
const t0=Date.now(); let out="",engine="?",provider="?",model="?",ttfa=null,toolCalls=0,done=false,failed=false,failureReason="",taskId="",lastEvent="",lastEventAt=null;
|
| 878 |
const telemetry=()=>({provider:provider||"?",model:model||engine||"?",ttfaMs:ttfa??9999,lastEvent:lastEvent||null,lastEventAt});
|
|
|
|
| 884 |
try{
|
| 885 |
const created=await fetch(`${BASE_URL}/api/agent/tasks`,{
|
| 886 |
method:"POST",headers,
|
| 887 |
+
body:JSON.stringify({goal,context:options.context??BENCHMARK_CONTEXT,max_steps:options.maxSteps??16,
|
| 888 |
+
session_id:`bext5_${Date.now()}_${Math.random().toString(36).slice(2,5)}`,
|
| 889 |
+
persona:options.persona??BENCHMARK_PERSONA,
|
| 890 |
+
negative_constraints:options.negativeConstraints??BENCHMARK_NEGATIVE_CONSTRAINTS}),
|
| 891 |
signal:ctrl.signal});
|
| 892 |
if(!created.ok)return{ok:false,output:"",engine,...telemetry(),toolCalls,durationMs:Date.now()-t0,failed:true,failureReason:`create task HTTP ${created.status}`};
|
| 893 |
const createdBody=await created.json();
|
|
|
|
| 2469 |
C_autonomia:"autonomy",D_memoria:"memory_context",E_recovery:"recovery",
|
| 2470 |
F_robustezza:"robustness",G_operativitΓ :"coding+data_analysis"},
|
| 2471 |
methodology:{
|
| 2472 |
+
runtime_input:{profile:"fixed-realistic-chat-v1",persona:BENCHMARK_PERSONA,negative_constraints:true,context_messages:BENCHMARK_CONTEXT.length},
|
| 2473 |
canonical_seed:"1337 (stesse domande per tutti gli agenti β usa --rotate per seed diverso)",
|
| 2474 |
coding:"enterprise: Acc(35%)+Stab(20%)+Auto(15%)+Perf(10%)+Spd(10%)+Cost(5%)+Tool(5%)",
|
| 2475 |
nonCoding:"content: Acc(40%)+Struct(20%)+Comp(15%)+Prec(10%)+Auto(5%)+Spd(5%)+Cost(5%)",
|