Bc-AI commited on
Commit
712d1a7
Β·
verified Β·
1 Parent(s): c49b1dd

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +88 -235
app.py CHANGED
@@ -1,14 +1,12 @@
1
  """
2
- SharePUTERβ„’ v2 Worker Node
3
- Sandboxed execution, library management, GPU/CPU/RAM support,
4
- gradient capture for ML workflows.
5
  """
6
 
7
  from fastapi import FastAPI
8
  from fastapi.responses import HTMLResponse, JSONResponse
9
  from fastapi.middleware.cors import CORSMiddleware
10
  import httpx, asyncio, uuid, os, sys, io, json, time, traceback
11
- import subprocess, multiprocessing, signal, resource
12
  from datetime import datetime
13
  from contextlib import redirect_stdout, redirect_stderr
14
  from typing import Optional
@@ -16,15 +14,14 @@ from typing import Optional
16
  app = FastAPI(title="SharePUTERβ„’ v2 Worker")
17
  app.add_middleware(CORSMiddleware, allow_origins=["*"], allow_methods=["*"], allow_headers=["*"])
18
 
19
- HEAD_URL = os.environ.get("SACCP_HEAD_URL", "https://bc-ai-saccp-head.hf.space")
20
  NODE_TYPE = os.environ.get("SACCP_NODE_TYPE", "CPU").upper()
21
  NODE_OWNER = os.environ.get("SACCP_NODE_OWNER", "anonymous")
22
  AUTO_REG = os.environ.get("SACCP_AUTO_REGISTER", "true").lower() == "true"
23
- SANDBOX = os.environ.get("SACCP_SANDBOX", "true").lower() == "true"
24
  NODE_ID: Optional[str] = None
25
  REGISTERED = False
26
 
27
- # ─── System Detection ──────────────────────────────────────────────────────
28
  def detect_specs():
29
  import psutil
30
  s = {
@@ -43,348 +40,204 @@ def detect_specs():
43
  s["gpu_available"] = True
44
  s["gpu_name"] = torch.cuda.get_device_name(0)
45
  s["gpu_memory_gb"] = round(torch.cuda.get_device_properties(0).total_mem / (1024**3), 2)
46
- except ImportError: pass
 
47
  return s
48
 
 
49
  def detect_libs():
50
- """Detect installed Python libraries."""
51
- common = ["numpy", "pandas", "scipy", "scikit-learn", "sklearn",
52
- "torch", "torchvision", "torchaudio", "tensorflow", "keras",
53
- "transformers", "datasets", "tokenizers", "accelerate",
54
- "matplotlib", "seaborn", "plotly", "pillow", "PIL",
55
- "opencv-python", "cv2", "requests", "httpx", "flask", "fastapi",
56
- "sqlalchemy", "redis", "celery", "beautifulsoup4", "bs4",
57
- "nltk", "spacy", "gensim", "xgboost", "lightgbm", "catboost",
58
- "jax", "flax", "optax", "huggingface_hub", "diffusers",
59
- "langchain", "openai", "anthropic", "gradio", "streamlit",
60
- "pydantic", "sympy", "networkx", "statsmodels",]
61
  installed = []
62
  for lib in common:
63
  try:
64
  __import__(lib.replace("-", "_"))
65
  installed.append(lib)
66
- except ImportError: pass
 
67
  return installed
68
 
 
69
  import psutil
70
  SPECS = detect_specs()
71
  INSTALLED_LIBS = detect_libs()
72
 
73
- # ─── Sandboxed Execution ───────────────────────────────────────────────────
74
- BLOCKED_MODULES = {"shutil", "subprocess", "ctypes", "socket"}
75
- BLOCKED_BUILTINS = {"exec", "eval", "compile", "__import__"} if SANDBOX else set()
76
-
77
- def install_libs(libs: list) -> dict:
78
- """Install required libraries that are missing."""
79
- results = {}
80
- for lib in libs:
81
- lib_clean = lib.strip().lower()
82
- if not lib_clean: continue
83
- # Security: only allow known-safe library names
84
- if not all(c.isalnum() or c in "-_." for c in lib_clean):
85
- results[lib_clean] = "blocked (invalid characters)"
86
- continue
87
- try:
88
- __import__(lib_clean.replace("-", "_").split("==")[0].split(">=")[0])
89
- results[lib_clean] = "already installed"
90
- except ImportError:
91
- try:
92
- proc = subprocess.run(
93
- [sys.executable, "-m", "pip", "install", "--quiet", lib_clean],
94
- capture_output=True, text=True, timeout=120
95
- )
96
- if proc.returncode == 0:
97
- results[lib_clean] = "installed"
98
- INSTALLED_LIBS.append(lib_clean)
99
- else:
100
- results[lib_clean] = f"failed: {proc.stderr[:200]}"
101
- except subprocess.TimeoutExpired:
102
- results[lib_clean] = "timeout"
103
- except Exception as e:
104
- results[lib_clean] = f"error: {str(e)}"
105
- return results
106
-
107
 
108
- def safe_execute(code: str, timeout: int = 600, sandbox: bool = True) -> dict:
109
- """Execute code with resource limits and isolation."""
110
  result = {
111
- "status": "completed", "result": None, "stdout": "", "stderr": "",
112
- "error": None, "execution_time": 0, "resource_usage": {}
 
 
 
 
113
  }
114
 
115
  stdout_cap = io.StringIO()
116
  stderr_cap = io.StringIO()
117
-
118
- # Build namespace
119
- safe_builtins = {k: v for k, v in __builtins__.__dict__.items()
120
- if k not in BLOCKED_BUILTINS} if sandbox and hasattr(__builtins__, '__dict__') else __builtins__
121
- namespace = {"__builtins__": safe_builtins}
122
 
123
  start = time.time()
124
  start_mem = psutil.Process().memory_info().rss
125
 
126
  try:
127
  with redirect_stdout(stdout_cap), redirect_stderr(stderr_cap):
128
- exec(compile(code, "<saccp_fragment>", "exec"), namespace)
129
 
130
  result["execution_time"] = round(time.time() - start, 3)
131
  result["stdout"] = stdout_cap.getvalue()
132
- result["stderr"] = stderr_cap.getvalue()
133
 
134
  end_mem = psutil.Process().memory_info().rss
135
  result["resource_usage"] = {
136
  "time_seconds": result["execution_time"],
137
  "memory_delta_mb": round((end_mem - start_mem) / (1024**2), 2),
138
- "peak_memory_mb": round(end_mem / (1024**2), 2),
139
  }
140
 
141
- # Capture result
142
- for var in ["result", "__saccp_result__", "output", "__fragment_results__", "answer"]:
143
  if var in namespace and namespace[var] is not None:
144
  val = namespace[var]
145
  try:
146
  json.dumps(val)
147
  result["result"] = val
148
  except (TypeError, ValueError):
149
- result["result"] = str(val)
150
  break
151
 
152
  if result["result"] is None and result["stdout"]:
153
- result["result"] = result["stdout"].strip()[-10000:]
154
 
155
- except MemoryError:
156
- result["status"] = "failed"
157
- result["error"] = "MemoryError: Fragment exceeded available memory"
158
  except Exception as e:
159
  result["status"] = "failed"
160
- result["error"] = f"{type(e).__name__}: {str(e)}\n{traceback.format_exc()[-2000:]}"
161
  result["execution_time"] = round(time.time() - start, 3)
162
  result["stdout"] = stdout_cap.getvalue()
163
 
164
  return result
165
 
166
 
167
- # ─── Homepage ───────────────────────────────────────────────────────────────
168
  @app.get("/", response_class=HTMLResponse)
169
  async def homepage():
170
- tc = {"RAM": "#ff6b6b", "CPU": "#00aaff", "GPU": "#aa66ff"}.get(NODE_TYPE, "#00ff88")
171
  ram_avail = round(psutil.virtual_memory().available / (1024**3), 2)
172
- cpu_pct = psutil.cpu_percent(interval=0.3)
173
- libs_html = "".join(f'<span class="lib">{l}</span>' for l in INSTALLED_LIBS[:30])
174
-
175
- html = f"""<!DOCTYPE html>
176
- <html><head><meta charset="UTF-8"><meta name="viewport" content="width=device-width,initial-scale=1">
177
- <title>SharePUTERβ„’ v2 Worker</title>
178
- <style>
179
- @import url('https://fonts.googleapis.com/css2?family=JetBrains+Mono:wght@300;400;700&family=Inter:wght@300;400;600;900&display=swap');
180
- *{{margin:0;padding:0;box-sizing:border-box}}
181
- body{{font-family:'Inter',sans-serif;background:#0a0a0f;color:#e0e0e0;min-height:100vh;
182
- display:flex;align-items:center;justify-content:center}}
183
- .bg{{position:fixed;top:0;left:0;right:0;bottom:0;
184
- background:radial-gradient(circle at 30% 30%,{tc}11,transparent 50%),
185
- radial-gradient(circle at 70% 70%,{tc}08,transparent 50%)}}
186
- .card{{position:relative;z-index:1;background:rgba(255,255,255,.02);border:1px solid rgba(255,255,255,.06);
187
- border-radius:24px;padding:40px;max-width:600px;width:92%}}
188
- .badge{{display:inline-block;padding:5px 16px;border-radius:20px;font-family:'JetBrains Mono';
189
- font-size:.8rem;font-weight:700;letter-spacing:2px;background:{tc}22;color:{tc};
190
- border:1px solid {tc}44;margin-bottom:16px}}
191
- h1{{font-size:1.8rem;font-weight:900;margin-bottom:4px;
192
- background:linear-gradient(135deg,#fff,{tc});-webkit-background-clip:text;-webkit-text-fill-color:transparent}}
193
- .sub{{color:#888;font-size:.85rem;margin-bottom:24px}}
194
- .pulse{{display:inline-block;width:8px;height:8px;border-radius:50%;background:#00ff88;
195
- animation:p 2s infinite;margin-right:6px}}
196
- @keyframes p{{0%,100%{{box-shadow:0 0 0 0 rgba(0,255,136,.4)}}50%{{box-shadow:0 0 0 6px rgba(0,255,136,0)}}}}
197
- .specs{{background:rgba(255,255,255,.02);border:1px solid rgba(255,255,255,.04);border-radius:12px;
198
- padding:16px;margin:16px 0}}
199
- .specs h3{{font-size:.7rem;text-transform:uppercase;letter-spacing:2px;color:#888;margin-bottom:12px}}
200
- .row{{display:flex;justify-content:space-between;padding:6px 0;border-bottom:1px solid rgba(255,255,255,.03)}}
201
- .row:last-child{{border:none}}
202
- .lbl{{color:#888;font-size:.85rem}}
203
- .val{{color:{tc};font-family:'JetBrains Mono';font-weight:600;font-size:.85rem}}
204
- .libs{{margin:16px 0}}
205
- .lib{{display:inline-block;padding:2px 8px;margin:2px;border-radius:4px;font-size:.7rem;
206
- font-family:'JetBrains Mono';background:rgba(0,255,136,.06);color:#00ff88;
207
- border:1px solid rgba(0,255,136,.15)}}
208
- .cfg{{background:rgba(255,255,255,.02);border:1px solid rgba(255,255,255,.04);border-radius:12px;
209
- padding:16px;margin-top:16px}}
210
- .cfg label{{display:block;color:#aaa;font-size:.8rem;margin-bottom:3px}}
211
- .cfg input,.cfg select{{width:100%;padding:8px 12px;border-radius:8px;border:1px solid rgba(255,255,255,.1);
212
- background:rgba(255,255,255,.04);color:#fff;font-family:'JetBrains Mono';font-size:.85rem;
213
- margin-bottom:10px;outline:none}}
214
- .btn{{width:100%;padding:12px;border:none;border-radius:10px;
215
- background:linear-gradient(135deg,{tc},{tc}aa);color:#000;font-weight:700;cursor:pointer;
216
- font-family:'Inter';font-size:.95rem;transition:.2s}}
217
- .btn:hover{{transform:translateY(-2px);box-shadow:0 4px 20px {tc}44}}
218
- #res{{margin-top:10px;padding:10px;border-radius:8px;font-family:'JetBrains Mono';font-size:.8rem;display:none}}
219
- .ft{{text-align:center;margin-top:16px;color:#555;font-size:.7rem}}
220
- </style></head><body>
221
- <div class="bg"></div>
222
- <div class="card">
223
- <div class="badge">{NODE_TYPE} NODE</div>
224
- <h1>SharePUTERβ„’ v2 Worker</h1>
225
- <p class="sub">SACCP Network Compute Node</p>
226
- <div style="font-size:.85rem;margin-bottom:16px">
227
- <span class="pulse"></span>
228
- <span style="color:#00ff88">{'REGISTERED & WORKING' if REGISTERED else 'ONLINE β€” AWAITING REGISTRATION'}</span>
229
- </div>
230
- <div class="specs"><h3>System</h3>
231
- <div class="row"><span class="lbl">Node ID</span><span class="val">{NODE_ID or 'Not registered'}</span></div>
232
- <div class="row"><span class="lbl">CPU Cores</span><span class="val">{SPECS['cpu_count']}</span></div>
233
- <div class="row"><span class="lbl">CPU Usage</span><span class="val">{cpu_pct}%</span></div>
234
- <div class="row"><span class="lbl">Total RAM</span><span class="val">{SPECS['ram_total_gb']} GB</span></div>
235
- <div class="row"><span class="lbl">Available RAM</span><span class="val">{ram_avail} GB</span></div>
236
- <div class="row"><span class="lbl">GPU</span><span class="val">{SPECS['gpu_name'] or 'None'} {f"({SPECS['gpu_memory_gb']}GB)" if SPECS['gpu_available'] else ''}</span></div>
237
- <div class="row"><span class="lbl">Sandbox</span><span class="val">{'Enabled βœ“' if SANDBOX else 'Disabled'}</span></div>
238
- </div>
239
- <div class="libs"><span style="color:#888;font-size:.7rem;text-transform:uppercase;letter-spacing:2px">Installed Libraries</span><br>{libs_html}</div>
240
- <div class="cfg"><h3 style="font-size:.7rem;text-transform:uppercase;letter-spacing:2px;color:#888;margin-bottom:12px">Configure</h3>
241
- <label>Head Node URL</label><input id="url" value="{HEAD_URL}"/>
242
- <label>Node Type</label>
243
- <select id="type"><option value="RAM" {"selected" if NODE_TYPE=="RAM" else ""}>RAM</option>
244
- <option value="CPU" {"selected" if NODE_TYPE=="CPU" else ""}>CPU</option>
245
- <option value="GPU" {"selected" if NODE_TYPE=="GPU" else ""}>GPU</option></select>
246
- <label>Owner Username</label><input id="own" value="{NODE_OWNER}"/>
247
- <button class="btn" onclick="go()">{'Reconfigure' if REGISTERED else 'Register & Start'}</button>
248
- <div id="res"></div>
249
- </div>
250
- <div class="ft">SharePUTERβ„’ SACCP v2.0</div>
251
- </div>
252
- <script>
253
- async function go(){{const r=document.getElementById('res');r.style.display='block';
254
- r.style.background='rgba(0,136,255,.1)';r.style.color='#00aaff';r.innerText='Registering...';
255
- try{{const resp=await fetch('/configure',{{method:'POST',headers:{{'Content-Type':'application/json'}},
256
- body:JSON.stringify({{head_url:document.getElementById('url').value,
257
- node_type:document.getElementById('type').value,
258
- owner:document.getElementById('own').value}})}});
259
- const d=await resp.json();if(resp.ok){{r.style.background='rgba(0,255,136,.1)';r.style.color='#00ff88';
260
- r.innerText='βœ“ '+d.message+'\\nNode: '+d.node_id;setTimeout(()=>location.reload(),1500)}}
261
- else{{r.style.background='rgba(255,68,68,.1)';r.style.color='#ff4444';r.innerText='βœ— '+(d.detail||d.error)}}
262
- }}catch(e){{r.style.background='rgba(255,68,68,.1)';r.style.color='#ff4444';r.innerText='βœ— '+e.message}}}}
263
- </script></body></html>"""
264
- return HTMLResponse(html)
265
-
266
-
267
- @app.post("/configure")
268
- async def configure(data: dict):
269
- global HEAD_URL, NODE_TYPE, NODE_OWNER, NODE_ID, REGISTERED
270
- HEAD_URL = data.get("head_url", HEAD_URL)
271
- NODE_TYPE = data.get("node_type", NODE_TYPE).upper()
272
- NODE_OWNER = data.get("owner", NODE_OWNER)
273
-
274
- try:
275
- async with httpx.AsyncClient(timeout=15) as c:
276
- r = await c.post(f"{HEAD_URL}/api/register_node", json={
277
- "node_type": NODE_TYPE, "node_url": os.environ.get("SPACE_HOST", "http://localhost:7861"),
278
- "owner": NODE_OWNER, "specs": SPECS, "installed_libs": INSTALLED_LIBS,
279
- "sandbox_enabled": SANDBOX,
280
- })
281
- if r.status_code == 200:
282
- NODE_ID = r.json().get("node_id")
283
- REGISTERED = True
284
- return {"message": "Registered!", "node_id": NODE_ID, "pay_rate": r.json().get("pay_rate")}
285
- return JSONResponse({"error": r.text}, 500)
286
- except Exception as e:
287
- return JSONResponse({"error": str(e)}, 500)
288
 
289
 
290
  @app.get("/health")
291
  async def health():
292
- return {"status": "online", "node_id": NODE_ID, "node_type": NODE_TYPE,
293
- "registered": REGISTERED, "version": "2.0.0"}
294
 
295
 
296
  async def worker_loop():
297
  global NODE_ID, REGISTERED
 
 
298
  while not REGISTERED:
299
- await asyncio.sleep(3)
300
  if AUTO_REG:
301
  try:
302
  async with httpx.AsyncClient(timeout=15) as c:
303
  r = await c.post(f"{HEAD_URL}/api/register_node", json={
304
  "node_type": NODE_TYPE,
305
  "node_url": os.environ.get("SPACE_HOST", "http://localhost:7861"),
306
- "owner": NODE_OWNER, "specs": SPECS,
307
- "installed_libs": INSTALLED_LIBS, "sandbox_enabled": SANDBOX,
 
308
  })
309
  if r.status_code == 200:
310
  NODE_ID = r.json().get("node_id")
311
  REGISTERED = True
312
- except: pass
 
 
313
 
314
  print(f"[SACCP] Worker active: {NODE_ID} ({NODE_TYPE})")
315
  last_hb = 0
316
 
317
  while True:
318
  try:
 
319
  if time.time() - last_hb > 25:
320
  try:
321
  async with httpx.AsyncClient(timeout=10) as c:
322
  await c.post(f"{HEAD_URL}/api/node_heartbeat", json={
323
- "node_id": NODE_ID, "status": "online",
 
324
  "installed_libs": INSTALLED_LIBS,
325
- "current_load": {
326
- "cpu_pct": psutil.cpu_percent(),
327
- "ram_avail_gb": round(psutil.virtual_memory().available/(1024**3), 2),
328
- }
329
  })
330
  last_hb = time.time()
331
- except: pass
 
332
 
 
333
  async with httpx.AsyncClient(timeout=15) as c:
334
  libs_str = ",".join(INSTALLED_LIBS)
335
  r = await c.get(
336
- f"{HEAD_URL}/api/get_work?node_id={NODE_ID}&node_type={NODE_TYPE}"
337
- f"&has_gpu={SPECS['gpu_available']}&ram_gb={SPECS['ram_available_gb']}"
338
- f"&libs={libs_str}"
 
 
 
 
 
339
  )
 
340
  if r.status_code != 200:
341
- await asyncio.sleep(3); continue
 
 
342
  work = r.json().get("work")
343
  if not work:
344
- await asyncio.sleep(2); continue
 
345
 
346
- fid = work["fragment_id"]
347
  code = work.get("code", "")
348
  ftype = work.get("fragment_type", "compute")
349
- req_libs = work.get("required_libs", [])
350
  timeout = work.get("timeout_seconds", 600)
351
 
352
- print(f"[SACCP] Running {fid} ({ftype})")
353
 
354
- # Install required libraries
355
- if req_libs:
356
- install_results = install_libs(req_libs)
357
- print(f"[SACCP] Lib install: {install_results}")
358
-
359
- # Execute
360
- if ftype in ("setup", "aggregate", "gradient_aggregate"):
361
- exec_r = safe_execute(code, timeout=120, sandbox=SANDBOX)
362
- else:
363
- exec_r = safe_execute(code, timeout=timeout, sandbox=SANDBOX)
364
 
365
  # Submit result
366
- payload = {
367
- "fragment_id": fid, "node_id": NODE_ID,
368
- "status": exec_r["status"], "result": exec_r.get("result"),
369
- "error": exec_r.get("error"), "stdout": exec_r.get("stdout", ""),
370
- "resource_usage": exec_r.get("resource_usage", {}),
371
- }
372
-
373
- # Check for gradient data
374
- if isinstance(exec_r.get("result"), dict) and "gradients" in exec_r["result"]:
375
- payload["gradients"] = exec_r["result"]
376
-
377
- await c.post(f"{HEAD_URL}/api/submit_result", json=payload)
378
- print(f"[SACCP] {fid} β†’ {exec_r['status']} ({exec_r['execution_time']}s)")
379
 
380
  except Exception as e:
381
  print(f"[SACCP] Error: {e}")
382
  await asyncio.sleep(3)
383
 
 
384
  @app.on_event("startup")
385
  async def startup():
386
  asyncio.create_task(worker_loop())
387
 
 
388
  if __name__ == "__main__":
389
  import uvicorn
390
  uvicorn.run(app, host="0.0.0.0", port=7861)
 
1
  """
2
+ SharePUTERβ„’ v2 Worker Node β€” FIXED
 
 
3
  """
4
 
5
  from fastapi import FastAPI
6
  from fastapi.responses import HTMLResponse, JSONResponse
7
  from fastapi.middleware.cors import CORSMiddleware
8
  import httpx, asyncio, uuid, os, sys, io, json, time, traceback
9
+ import subprocess, multiprocessing
10
  from datetime import datetime
11
  from contextlib import redirect_stdout, redirect_stderr
12
  from typing import Optional
 
14
  app = FastAPI(title="SharePUTERβ„’ v2 Worker")
15
  app.add_middleware(CORSMiddleware, allow_origins=["*"], allow_methods=["*"], allow_headers=["*"])
16
 
17
+ HEAD_URL = os.environ.get("SACCP_HEAD_URL", "http://localhost:7860")
18
  NODE_TYPE = os.environ.get("SACCP_NODE_TYPE", "CPU").upper()
19
  NODE_OWNER = os.environ.get("SACCP_NODE_OWNER", "anonymous")
20
  AUTO_REG = os.environ.get("SACCP_AUTO_REGISTER", "true").lower() == "true"
 
21
  NODE_ID: Optional[str] = None
22
  REGISTERED = False
23
 
24
+
25
  def detect_specs():
26
  import psutil
27
  s = {
 
40
  s["gpu_available"] = True
41
  s["gpu_name"] = torch.cuda.get_device_name(0)
42
  s["gpu_memory_gb"] = round(torch.cuda.get_device_properties(0).total_mem / (1024**3), 2)
43
+ except ImportError:
44
+ pass
45
  return s
46
 
47
+
48
  def detect_libs():
49
+ common = ["numpy", "pandas", "scipy", "scikit-learn", "torch", "transformers",
50
+ "tensorflow", "keras", "pillow", "requests", "httpx", "fastapi", "pydantic"]
 
 
 
 
 
 
 
 
 
51
  installed = []
52
  for lib in common:
53
  try:
54
  __import__(lib.replace("-", "_"))
55
  installed.append(lib)
56
+ except ImportError:
57
+ pass
58
  return installed
59
 
60
+
61
  import psutil
62
  SPECS = detect_specs()
63
  INSTALLED_LIBS = detect_libs()
64
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
65
 
66
+ def safe_execute(code: str, timeout: int = 600) -> dict:
67
+ """Execute Python code safely."""
68
  result = {
69
+ "status": "completed",
70
+ "result": None,
71
+ "stdout": "",
72
+ "error": None,
73
+ "execution_time": 0,
74
+ "resource_usage": {}
75
  }
76
 
77
  stdout_cap = io.StringIO()
78
  stderr_cap = io.StringIO()
79
+ namespace = {}
 
 
 
 
80
 
81
  start = time.time()
82
  start_mem = psutil.Process().memory_info().rss
83
 
84
  try:
85
  with redirect_stdout(stdout_cap), redirect_stderr(stderr_cap):
86
+ exec(compile(code, "<fragment>", "exec"), namespace)
87
 
88
  result["execution_time"] = round(time.time() - start, 3)
89
  result["stdout"] = stdout_cap.getvalue()
 
90
 
91
  end_mem = psutil.Process().memory_info().rss
92
  result["resource_usage"] = {
93
  "time_seconds": result["execution_time"],
94
  "memory_delta_mb": round((end_mem - start_mem) / (1024**2), 2),
 
95
  }
96
 
97
+ # Capture result variable
98
+ for var in ["result", "__saccp_result__", "__saccp_results__", "output"]:
99
  if var in namespace and namespace[var] is not None:
100
  val = namespace[var]
101
  try:
102
  json.dumps(val)
103
  result["result"] = val
104
  except (TypeError, ValueError):
105
+ result["result"] = str(val)[:5000]
106
  break
107
 
108
  if result["result"] is None and result["stdout"]:
109
+ result["result"] = result["stdout"].strip()[-5000:]
110
 
 
 
 
111
  except Exception as e:
112
  result["status"] = "failed"
113
+ result["error"] = f"{type(e).__name__}: {str(e)}\n{traceback.format_exc()[-1500:]}"
114
  result["execution_time"] = round(time.time() - start, 3)
115
  result["stdout"] = stdout_cap.getvalue()
116
 
117
  return result
118
 
119
 
 
120
  @app.get("/", response_class=HTMLResponse)
121
  async def homepage():
122
+ import psutil
123
  ram_avail = round(psutil.virtual_memory().available / (1024**3), 2)
124
+ return HTMLResponse(f"""<!DOCTYPE html>
125
+ <html><head><title>SharePUTER Worker</title>
126
+ <style>body{{font-family:system-ui;background:#0a0a0f;color:#ddd;padding:40px;max-width:600px;margin:0 auto}}
127
+ h1{{color:#00ff88}}.stat{{background:#111;padding:12px;margin:8px 0;border-radius:8px}}</style></head>
128
+ <body><h1>πŸ€– SharePUTER Worker</h1>
129
+ <p>Node ID: <strong>{NODE_ID or 'Not registered'}</strong></p>
130
+ <p>Type: <strong>{NODE_TYPE}</strong></p>
131
+ <p>Status: <strong style="color:#00ff88">{'ACTIVE' if REGISTERED else 'WAITING'}</strong></p>
132
+ <div class="stat">CPUs: {SPECS['cpu_count']}</div>
133
+ <div class="stat">RAM: {ram_avail} / {SPECS['ram_total_gb']} GB</div>
134
+ <div class="stat">GPU: {SPECS['gpu_name'] or 'None'}</div>
135
+ <div class="stat">Libs: {', '.join(INSTALLED_LIBS[:10])}</div>
136
+ </body></html>""")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
137
 
138
 
139
  @app.get("/health")
140
  async def health():
141
+ return {"status": "online", "node_id": NODE_ID, "registered": REGISTERED}
 
142
 
143
 
144
  async def worker_loop():
145
  global NODE_ID, REGISTERED
146
+
147
+ # Wait for registration
148
  while not REGISTERED:
149
+ await asyncio.sleep(2)
150
  if AUTO_REG:
151
  try:
152
  async with httpx.AsyncClient(timeout=15) as c:
153
  r = await c.post(f"{HEAD_URL}/api/register_node", json={
154
  "node_type": NODE_TYPE,
155
  "node_url": os.environ.get("SPACE_HOST", "http://localhost:7861"),
156
+ "owner": NODE_OWNER,
157
+ "specs": SPECS,
158
+ "installed_libs": INSTALLED_LIBS,
159
  })
160
  if r.status_code == 200:
161
  NODE_ID = r.json().get("node_id")
162
  REGISTERED = True
163
+ print(f"[SACCP] Registered as {NODE_ID}")
164
+ except Exception as e:
165
+ print(f"[SACCP] Registration failed: {e}")
166
 
167
  print(f"[SACCP] Worker active: {NODE_ID} ({NODE_TYPE})")
168
  last_hb = 0
169
 
170
  while True:
171
  try:
172
+ # Heartbeat every 25 seconds
173
  if time.time() - last_hb > 25:
174
  try:
175
  async with httpx.AsyncClient(timeout=10) as c:
176
  await c.post(f"{HEAD_URL}/api/node_heartbeat", json={
177
+ "node_id": NODE_ID,
178
+ "status": "online",
179
  "installed_libs": INSTALLED_LIBS,
 
 
 
 
180
  })
181
  last_hb = time.time()
182
+ except:
183
+ pass
184
 
185
+ # Get work
186
  async with httpx.AsyncClient(timeout=15) as c:
187
  libs_str = ",".join(INSTALLED_LIBS)
188
  r = await c.get(
189
+ f"{HEAD_URL}/api/get_work",
190
+ params={
191
+ "node_id": NODE_ID,
192
+ "node_type": NODE_TYPE,
193
+ "has_gpu": str(SPECS['gpu_available']).lower(),
194
+ "ram_gb": SPECS['ram_available_gb'],
195
+ "libs": libs_str,
196
+ }
197
  )
198
+
199
  if r.status_code != 200:
200
+ await asyncio.sleep(2)
201
+ continue
202
+
203
  work = r.json().get("work")
204
  if not work:
205
+ await asyncio.sleep(1)
206
+ continue
207
 
208
+ frag_id = work["fragment_id"]
209
  code = work.get("code", "")
210
  ftype = work.get("fragment_type", "compute")
 
211
  timeout = work.get("timeout_seconds", 600)
212
 
213
+ print(f"[SACCP] Running {frag_id} ({ftype})")
214
 
215
+ # Execute the code
216
+ exec_result = safe_execute(code, timeout=timeout)
 
 
 
 
 
 
 
 
217
 
218
  # Submit result
219
+ await c.post(f"{HEAD_URL}/api/submit_result", json={
220
+ "fragment_id": frag_id,
221
+ "node_id": NODE_ID,
222
+ "status": exec_result["status"],
223
+ "result": exec_result.get("result"),
224
+ "error": exec_result.get("error"),
225
+ "stdout": exec_result.get("stdout", ""),
226
+ "resource_usage": exec_result.get("resource_usage", {}),
227
+ })
228
+
229
+ print(f"[SACCP] {frag_id} β†’ {exec_result['status']} ({exec_result['execution_time']}s)")
 
 
230
 
231
  except Exception as e:
232
  print(f"[SACCP] Error: {e}")
233
  await asyncio.sleep(3)
234
 
235
+
236
  @app.on_event("startup")
237
  async def startup():
238
  asyncio.create_task(worker_loop())
239
 
240
+
241
  if __name__ == "__main__":
242
  import uvicorn
243
  uvicorn.run(app, host="0.0.0.0", port=7861)