Baida07 commited on
Commit
89c94a0
Β·
1 Parent(s): ed6f314

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)

Files changed (3) hide show
  1. api/public_status.py +23 -4
  2. api/terminal.py +49 -0
  3. 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, HTTPException
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
- raise HTTPException(status_code=503, detail="Public status non configurato")
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
- raise HTTPException(status_code=503, detail="Public status temporaneamente non disponibile") from exc
41
 
42
  row = (result.data or [None])[0]
43
  if not row:
44
- raise HTTPException(status_code=503, detail="Public status snapshot non inizializzato")
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
- // ── callAgent ─────────────────────────────────────────────────────────────────
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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:[],max_steps:options.maxSteps??16,
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%)",