feat(ops): פאנל "מתאמי-סוכנים" ב-/operations — מעבר-אדפטר בכפתור (any→any)
All checks were successful
G12 Leak-Guard / leak-guard (pull_request) Successful in 3s

שלב-ה-UI של מנגנון מעבר-האדפטר (PR #247). הכפתור ב-/operations מריץ את
scripts/migrate_agent_adapter.py בהוסט דרך גשר-court-fetch (הקונטיינר לא יכול
לבצע — צריך FS-הוסט + DB-המובנה), בדיוק כמו כפתורי-pm2.

- court_fetch_service/server.py: endpoint /adapter-migration מאומת (Bearer,
  COURT_FETCH_SHARED_SECRET) שמריץ את הסקריפט עם allowlist-פעולות ו-args אטומיים
  (exec, ללא shell; הסקריפט מאמת). symbol-light לשמירת G12 בשכבת mcp-server.
- web/app.py: proxy POST /api/operations/agents/migrate-adapter → הגשר.
- web-ui: useMigrateAdapter (operations.ts) + AgentAdaptersPanel לפי המוקאפ
  המאושר 02d-operations-adapters.html: roster per-role (מתאם נוכחי+מודל+בורר-יעד),
  סרגל-חירום גלובלי (הכל→Gemini/החזר→Claude), preflight בדיאלוג + toggle שחרור-כלים,
  דגלי מועבר/א-סימטרי. מחזר usePaperclipAgents לתצוגה.

עיצוב אושר ע"י חיים בשער Claude Design (פרויקט IA Redesign X17).
נבדק: tsc --noEmit נקי, eslint נקי.

Invariants: G12 (גשר symbol-light; הסקריפט בשכבת scripts/ הוא שמדבר Paperclip),
INV-MC1 (שתי החברות יחד), INV-IA "מקום אחד" (/operations). המשך FU-8a.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
2026-06-13 12:06:40 +00:00
parent b9e4c1fde4
commit 64612240d5
5 changed files with 428 additions and 0 deletions

View File

@@ -6900,6 +6900,51 @@ async def operations_agent_reset_session(agent_id: str):
return {"ok": True, "agent_id": agent_id, "result": result}
class AdapterMigrationRequest(BaseModel):
action: str # check | apply | revert | verify
agent: str | None = None # agent display-name, or "all"
to: str | None = None # target adapter (for check/apply)
model: str | None = None # optional model override
relax_tools: bool = False # free conflicting write tools from the global gemini excludeTools
@app.post("/api/operations/agents/migrate-adapter")
async def operations_agent_migrate_adapter(req: AdapterMigrationRequest):
"""Migrate an agent (or 'all') to a target adapter, in both companies.
The migration is host-side (it needs the host filesystem — generated
instruction copies, the gemini settings file — and the embedded board DB),
so this proxies scripts/migrate_agent_adapter.py through the court-fetch host
bridge, Bearer-authenticated exactly like the pm2 controls. The script's exit
code + stdout/stderr are relayed verbatim so the dashboard can show preflight
warnings (a non-zero --check is a refusal to render, not a transport error)."""
if req.action not in {"check", "apply", "revert", "verify"}:
raise HTTPException(400, "action חייב להיות check/apply/revert/verify")
secret = os.environ.get("COURT_FETCH_SHARED_SECRET", "").strip()
headers = {"Authorization": f"Bearer {secret}"} if secret else {}
body = {
"action": req.action,
"agent": req.agent or "",
"to": req.to or "",
"model": req.model or "",
"relax_tools": req.relax_tools,
}
try:
async with httpx.AsyncClient(timeout=190.0) as client:
r = await client.post(
f"{_COURT_FETCH_SERVICE_URL}/adapter-migration", json=body, headers=headers,
)
except Exception as e: # host bridge down / unreachable
raise HTTPException(502, f"לא ניתן להגיע לשירות-המארח: {e}") from e
try:
payload = r.json()
except Exception:
payload = {"ok": False, "error": r.text[:300]}
if r.status_code >= 400:
raise HTTPException(r.status_code, payload.get("error", "migration bridge failed"))
return payload
@app.get("/api/digests/{digest_id}")
async def digest_get(digest_id: str):
try: