Spaces:
Sleeping
Sleeping
Boot priority: resume the user's active profile instead of the env DB; redact db_url passwords on all schema endpoints
Browse files- Agent/worker.py +46 -6
Agent/worker.py
CHANGED
|
@@ -18,15 +18,25 @@ app = FastAPI(title="Agent Worker")
|
|
| 18 |
# Schema profile registry (dynamic multi-DB support)
|
| 19 |
# ---------------------------------------------------------------------------
|
| 20 |
try:
|
| 21 |
-
from .schema_profile import ProfileRegistry
|
| 22 |
except Exception:
|
| 23 |
import Agent.schema_profile as _sp
|
| 24 |
ProfileRegistry = _sp.ProfileRegistry
|
|
|
|
| 25 |
|
| 26 |
SCHEMAS_DIR = os.path.join("uploads", "schemas")
|
| 27 |
PROFILE_STORE_PATH = os.path.join("chat_store", "schema_profiles.json")
|
| 28 |
profile_registry = ProfileRegistry(PROFILE_STORE_PATH)
|
| 29 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 30 |
SUPPORTED_DB_SUFFIXES = {".sqlite", ".db", ".sqlite3", ".csv"}
|
| 31 |
|
| 32 |
|
|
@@ -119,7 +129,19 @@ async def startup_event():
|
|
| 119 |
# ------------------------------------------------------------------
|
| 120 |
db_url = os.environ.get("DB_CONNECTION_URL")
|
| 121 |
active = profile_registry.active()
|
| 122 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 123 |
|
| 124 |
if target_uri:
|
| 125 |
try:
|
|
@@ -171,6 +193,23 @@ async def startup_event():
|
|
| 171 |
except Exception as e:
|
| 172 |
# Log but continue; agentic_workflow has fallback behavior
|
| 173 |
print(f"Worker: initialize_database failed: {e}")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 174 |
else:
|
| 175 |
print("Worker: no database available (no env DB, no profiles, no demo bundle)")
|
| 176 |
# Optional Groq Vision health check in cloud mode
|
|
@@ -370,7 +409,8 @@ async def upload_image(file: UploadFile = File(...)):
|
|
| 370 |
|
| 371 |
@app.get("/schemas")
|
| 372 |
async def schemas_list():
|
| 373 |
-
return {"profiles":
|
|
|
|
| 374 |
|
| 375 |
|
| 376 |
@app.post("/schemas/add")
|
|
@@ -393,7 +433,7 @@ async def schemas_add_json(req: AddSchemaJsonReq):
|
|
| 393 |
except Exception as e:
|
| 394 |
raise HTTPException(status_code=400, detail=f"Profile saved, but DB init failed: {e}")
|
| 395 |
# Return full schema digest preview
|
| 396 |
-
return {"profile": entry, "schema": agent.ACTIVE_SCHEMA_PROFILE.summarize() if agent else None}
|
| 397 |
|
| 398 |
|
| 399 |
@app.post("/schemas/add-file")
|
|
@@ -462,7 +502,7 @@ async def schemas_add_file(file: UploadFile = File(...), name: Optional[str] = F
|
|
| 462 |
await agent.initialize_database(uri=db_url, schema_notes=notes)
|
| 463 |
except Exception as e:
|
| 464 |
raise HTTPException(status_code=400, detail=f"Profile saved, but DB init failed: {e}")
|
| 465 |
-
return {"profile": entry, "schema": agent.ACTIVE_SCHEMA_PROFILE.summarize() if agent else None}
|
| 466 |
|
| 467 |
|
| 468 |
@app.post("/schemas/switch/{profile_id}")
|
|
@@ -484,7 +524,7 @@ async def schemas_switch(profile_id: str):
|
|
| 484 |
await agent.initialize_database(uri=entry["db_url"], schema_notes=entry.get("notes"))
|
| 485 |
except Exception as e:
|
| 486 |
raise HTTPException(status_code=400, detail=f"Switch failed: {e}")
|
| 487 |
-
return {"profile": entry, "schema": agent.ACTIVE_SCHEMA_PROFILE.summarize()}
|
| 488 |
|
| 489 |
|
| 490 |
class SetSchemaNotesReq(BaseModel):
|
|
|
|
| 18 |
# Schema profile registry (dynamic multi-DB support)
|
| 19 |
# ---------------------------------------------------------------------------
|
| 20 |
try:
|
| 21 |
+
from .schema_profile import ProfileRegistry, _redact_db_url
|
| 22 |
except Exception:
|
| 23 |
import Agent.schema_profile as _sp
|
| 24 |
ProfileRegistry = _sp.ProfileRegistry
|
| 25 |
+
_redact_db_url = _sp._redact_db_url
|
| 26 |
|
| 27 |
SCHEMAS_DIR = os.path.join("uploads", "schemas")
|
| 28 |
PROFILE_STORE_PATH = os.path.join("chat_store", "schema_profiles.json")
|
| 29 |
profile_registry = ProfileRegistry(PROFILE_STORE_PATH)
|
| 30 |
|
| 31 |
+
|
| 32 |
+
def _safe_profile(entry):
|
| 33 |
+
"""Copy of a registry entry safe for public API responses (password masked)."""
|
| 34 |
+
if not isinstance(entry, dict):
|
| 35 |
+
return entry
|
| 36 |
+
safe = dict(entry)
|
| 37 |
+
safe["db_url"] = _redact_db_url(safe.get("db_url"))
|
| 38 |
+
return safe
|
| 39 |
+
|
| 40 |
SUPPORTED_DB_SUFFIXES = {".sqlite", ".db", ".sqlite3", ".csv"}
|
| 41 |
|
| 42 |
|
|
|
|
| 129 |
# ------------------------------------------------------------------
|
| 130 |
db_url = os.environ.get("DB_CONNECTION_URL")
|
| 131 |
active = profile_registry.active()
|
| 132 |
+
# Boot priority: 1) the user's last ACTIVATED profile (persisted across
|
| 133 |
+
# restarts; never the auto-registered demo placeholder), 2) the env
|
| 134 |
+
# DB_CONNECTION_URL, 3) the bundled demo file. Previously the env secret
|
| 135 |
+
# ALWAYS won, so every restart silently yanked the engine back to the
|
| 136 |
+
# owner's default database even when the registry said the user was
|
| 137 |
+
# working in (e.g.) their car database — engine and registry disagreed
|
| 138 |
+
# and queries ran against the wrong database.
|
| 139 |
+
_active_kind = (active or {}).get("kind")
|
| 140 |
+
target_uri = (
|
| 141 |
+
(active or {}).get("db_url")
|
| 142 |
+
if active and _active_kind != "demo"
|
| 143 |
+
else None
|
| 144 |
+
) or db_url or demo_db_url
|
| 145 |
|
| 146 |
if target_uri:
|
| 147 |
try:
|
|
|
|
| 193 |
except Exception as e:
|
| 194 |
# Log but continue; agentic_workflow has fallback behavior
|
| 195 |
print(f"Worker: initialize_database failed: {e}")
|
| 196 |
+
# If the persisted profile's DB became unreachable (rotated
|
| 197 |
+
# credentials, deleted file), retry once with the env DB so the
|
| 198 |
+
# worker still boots with a usable database.
|
| 199 |
+
if db_url and target_uri != db_url:
|
| 200 |
+
try:
|
| 201 |
+
print("[WORKER] Falling back to DB_CONNECTION_URL after failed profile boot")
|
| 202 |
+
await agent.initialize_database(uri=db_url)
|
| 203 |
+
_env_match = next(
|
| 204 |
+
(p for p in profile_registry.list()
|
| 205 |
+
if ((p.get("db_url") or "").replace("\\", "/") == db_url.replace("\\", "/"))),
|
| 206 |
+
None,
|
| 207 |
+
)
|
| 208 |
+
if _env_match and profile_registry.active_id != _env_match["id"]:
|
| 209 |
+
profile_registry.switch(_env_match["id"])
|
| 210 |
+
print(f"[WORKER] Active profile switched to '{_env_match['name']}' (env fallback)")
|
| 211 |
+
except Exception as e2:
|
| 212 |
+
print(f"Worker: env fallback failed: {e2}")
|
| 213 |
else:
|
| 214 |
print("Worker: no database available (no env DB, no profiles, no demo bundle)")
|
| 215 |
# Optional Groq Vision health check in cloud mode
|
|
|
|
| 409 |
|
| 410 |
@app.get("/schemas")
|
| 411 |
async def schemas_list():
|
| 412 |
+
return {"profiles": [_safe_profile(p) for p in profile_registry.list()],
|
| 413 |
+
"active_id": profile_registry.active_id}
|
| 414 |
|
| 415 |
|
| 416 |
@app.post("/schemas/add")
|
|
|
|
| 433 |
except Exception as e:
|
| 434 |
raise HTTPException(status_code=400, detail=f"Profile saved, but DB init failed: {e}")
|
| 435 |
# Return full schema digest preview
|
| 436 |
+
return {"profile": _safe_profile(entry), "schema": agent.ACTIVE_SCHEMA_PROFILE.summarize() if agent else None}
|
| 437 |
|
| 438 |
|
| 439 |
@app.post("/schemas/add-file")
|
|
|
|
| 502 |
await agent.initialize_database(uri=db_url, schema_notes=notes)
|
| 503 |
except Exception as e:
|
| 504 |
raise HTTPException(status_code=400, detail=f"Profile saved, but DB init failed: {e}")
|
| 505 |
+
return {"profile": _safe_profile(entry), "schema": agent.ACTIVE_SCHEMA_PROFILE.summarize() if agent else None}
|
| 506 |
|
| 507 |
|
| 508 |
@app.post("/schemas/switch/{profile_id}")
|
|
|
|
| 524 |
await agent.initialize_database(uri=entry["db_url"], schema_notes=entry.get("notes"))
|
| 525 |
except Exception as e:
|
| 526 |
raise HTTPException(status_code=400, detail=f"Switch failed: {e}")
|
| 527 |
+
return {"profile": _safe_profile(entry), "schema": agent.ACTIVE_SCHEMA_PROFILE.summarize()}
|
| 528 |
|
| 529 |
|
| 530 |
class SetSchemaNotesReq(BaseModel):
|