Claude Code commited on
Commit
1b5e627
·
1 Parent(s): 71070eb

god: manual intervention patch - app.py

Browse files
Files changed (1) hide show
  1. app.py +19 -607
app.py CHANGED
@@ -1,622 +1,34 @@
1
- #!/usr/bin/env python3
2
- """
3
- HuggingClaw - Cain
4
- Graceful degradation pattern: server ALWAYS starts, even if brain is offline.
5
-
6
- Last cleanup update: 2026-03-16 - aggressive stale error cleanup + rebuild trigger
7
- """
8
  import os
9
- import sys
10
- import json
11
- import time
12
- import traceback
13
- from pathlib import Path
14
-
15
- # EXPLICIT STARTUP PRINTS (visible in container logs)
16
- print(">>> CAIN: Python app.py loading...", flush=True)
17
- print(">>> CAIN: Python version:", sys.version.split()[0], flush=True)
18
- print(">>> CAIN: Working directory:", os.getcwd(), flush=True)
19
- print(">>> CAIN: PORT =", os.environ.get('PORT', '7860'), flush=True)
20
-
21
- # Set CAIN_STATUS_PATH from OPENCLAW_DATA_DIR if available (for error_handlers.py and health_monitor.py)
22
- # IMPORTANT: Always set this env var so health_monitor.py can find the status file
23
- if 'OPENCLAW_DATA_DIR' in os.environ:
24
- os.environ['CAIN_STATUS_PATH'] = os.path.join(os.environ['OPENCLAW_DATA_DIR'], 'cain_status.json')
25
- print(f">>> CAIN: Set CAIN_STATUS_PATH = {os.environ['CAIN_STATUS_PATH']}", flush=True)
26
- elif 'CAIN_STATUS_PATH' not in os.environ:
27
- # Fallback if OPENCLAW_DATA_DIR is not set
28
- os.environ['CAIN_STATUS_PATH'] = '/data/cain_status.json'
29
- print(f">>> CAIN: Set CAIN_STATUS_PATH (fallback) = {os.environ['CAIN_STATUS_PATH']}", flush=True)
30
-
31
- # CRITICAL: Immediately clean stale "unknown" error from ALL status file locations
32
- # This must happen BEFORE any health checks read the status file
33
- # Fix for: Cain has RUNNING! Error: unknown - status files had stale error
34
- _status_path = os.environ.get('CAIN_STATUS_PATH', '/data/cain_status.json')
35
- _all_status_paths = [
36
- Path(_status_path), # Primary (OPENCLAW_DATA_DIR)
37
- Path('/app/openclaw/.openclaw/agents/cain_status.json'), # Nested structure
38
- Path('/app/.openclaw/agents/cain_status.json'), # Legacy flat structure
39
- Path('/app/cain_status.json'), # App root
40
- Path('/app/data/cain_status.json'), # App data subdirectory
41
- Path(__file__).parent / 'memory' / 'cain_status.json', # Memory directory
42
- ]
43
-
44
- _cleaned_count = 0
45
- for _sp in _all_status_paths:
46
- try:
47
- if _sp.exists():
48
- with open(_sp, 'r') as f:
49
- _status_data = json.load(f)
50
- _error = _status_data.get('error')
51
- # Clean if error is a "null" string (unknown, none, null, empty)
52
- if isinstance(_error, str) and _error.strip().lower() in ('unknown', 'none', 'null', ''):
53
- _status_data['error'] = None
54
- _status_data['_cleaned_at'] = 'app_module_load_aggressive'
55
- _status_data['_cleaned_path'] = str(_sp)
56
- with open(_sp, 'w') as f:
57
- json.dump(_status_data, f, indent=2)
58
- _cleaned_count += 1
59
- print(f">>> CAIN: Cleaned stale '{_error}' error from {_sp}", flush=True)
60
- elif _error is None:
61
- print(f">>> CAIN: Status OK: {_sp}", flush=True)
62
- else:
63
- print(f">>> CAIN: Status has real error at {_sp}: {_error}", flush=True)
64
- except Exception as e:
65
- print(f">>> CAIN: Could not clean {_sp}: {e}", flush=True)
66
-
67
- print(f">>> CAIN: Cleaned {_cleaned_count} status file(s) at module load", flush=True)
68
-
69
- # CRITICAL: Create FastAPI app at TOP LEVEL, outside any try/except
70
- # This ensures the server ALWAYS starts, even if brain is broken
71
- from fastapi import FastAPI, Request
72
- from fastapi.responses import JSONResponse, FileResponse
73
  from fastapi.staticfiles import StaticFiles
74
- from contextlib import asynccontextmanager
75
-
76
- print(">>> CAIN: Creating FastAPI app...", flush=True)
77
-
78
- START_TIME = time.time()
79
 
80
- @asynccontextmanager
81
- async def lifespan(app: FastAPI):
82
- """Lifespan context manager for startup/shutdown events."""
83
- print(">>> CAIN: Startup complete - ready to serve", flush=True)
84
- yield
85
- print(">>> CAIN: Shutdown triggered", flush=True)
86
 
87
- app = FastAPI(title="HuggingClaw - Cain", version="0.0.1", lifespan=lifespan)
88
 
89
- # Mount frontend folder for static assets (pixel art, fonts, HTML) - absolute path
90
- # Mount frontend folder for static assets (pixel art, fonts, HTML)
91
- # Try /app/frontend first (Docker build), fallback to /data/frontend (runtime mount)
92
- frontend_dir = "/app/frontend" if Path("/app/frontend").exists() else "/data/frontend"
93
- app.mount("/frontend", StaticFiles(directory=frontend_dir), name="frontend")
94
 
95
  @app.get("/")
96
  async def root():
97
- """Root endpoint - simple alive status."""
98
- return {"status": "ok", "agent": "Cain", "parents": ["Adam", "Eve"]}
99
 
100
  @app.get("/health")
101
  async def health():
102
- """Health check - returns immediately without importing brain."""
103
- return {"status": "ok", "mode": "fastapi"}
104
-
105
- @app.get("/api/health")
106
- async def api_health():
107
- """Health check API with uptime."""
108
- return {
109
- "status": "ok",
110
- "uptime_seconds": time.time() - START_TIME,
111
- "active_agents": 1
112
- }
113
-
114
- @app.get("/a2a/ping")
115
- async def a2a_ping():
116
- """
117
- Minimal ping endpoint for liveness checks.
118
- Always returns immediately without any imports or heavy operations.
119
- """
120
- return {"pong": True, "uptime_seconds": time.time() - START_TIME}
121
-
122
- @app.get("/hello")
123
- async def hello():
124
- """Hello endpoint - lazy loads brain, graceful degradation if offline."""
125
- try:
126
- # LAZY LOAD: Import brain only when this endpoint is called
127
- # Import directly since .openclaw is in sys.path after openclaw import
128
- import openclaw # Trigger sys.path setup
129
- from agents import brain_minimal
130
-
131
- # Try to use brain via the response handler
132
- from error_handlers import handle_brain_response
133
- result = handle_brain_response("Hello")
134
- if result and not result.startswith("Error:") and not result.startswith("Brain"):
135
- return {"message": result, "brain": "active"}
136
- else:
137
- return {"message": "Hello World from Cain!", "brain": "fallback"}
138
- except ImportError:
139
- # Brain module not available - survival mode
140
- return {"message": "Brain offline. Cain is in survival mode."}
141
- except Exception as e:
142
- # Brain exists but failed - survival mode
143
- return {"message": f"Brain error: {e}. Cain is in survival mode."}
144
-
145
- @app.get("/metrics")
146
- async def metrics():
147
- """System metrics with psutil fallback values."""
148
- FALLBACK_METRICS = {
149
- "cpu_percent": 5.0,
150
- "memory": {"percent": 45.0, "total_gb": 16.0, "used_gb": 7.2, "available_gb": 8.8},
151
- "disk": {"percent": 35.0, "total_gb": 100.0, "used_gb": 35.0, "free_gb": 65.0},
152
- "_fallback": True
153
- }
154
-
155
- try:
156
- import psutil
157
- except ImportError:
158
- return JSONResponse(content={**FALLBACK_METRICS, "_note": "psutil not installed"})
159
-
160
- try:
161
- cpu_percent = psutil.cpu_percent(interval=0.1)
162
- memory = psutil.virtual_memory()
163
- disk = psutil.disk_usage('/')
164
 
165
- return {
166
- "cpu_percent": cpu_percent,
167
- "memory": {
168
- "percent": memory.percent,
169
- "total_gb": round(memory.total / (1024**3), 2),
170
- "used_gb": round(memory.used / (1024**3), 2),
171
- "available_gb": round(memory.available / (1024**3), 2)
172
- },
173
- "disk": {
174
- "percent": disk.percent,
175
- "total_gb": round(disk.total / (1024**3), 2),
176
- "used_gb": round(disk.used / (1024**3), 2),
177
- "free_gb": round(disk.free / (1024**3), 2)
178
- },
179
- "_fallback": False
180
- }
181
- except Exception as e:
182
- return JSONResponse(content={**FALLBACK_METRICS, "_error": str(e)})
183
-
184
- @app.exception_handler(Exception)
185
- async def global_exception_handler(request: Request, exc: Exception):
186
- """Global exception handler."""
187
- return JSONResponse(
188
- status_code=500,
189
- content={"error": True, "message": str(exc), "type": type(exc).__name__}
190
- )
191
-
192
-
193
- # ============================================================================
194
- # A2A JSON-RPC ENDPOINT - Required for agent-to-agent communication
195
- # ============================================================================
196
- @app.post("/a2a/jsonrpc")
197
- async def a2a_jsonrpc(request: Request):
198
- """
199
- Agent-to-Agent (A2A) JSON-RPC 2.0 endpoint for inter-agent communication.
200
-
201
- Handles message/send requests from other agents in the HuggingClaw World family.
202
- """
203
- print(f">>> CAIN A2A: Received request at {time.time()}", flush=True)
204
- try:
205
- payload = await request.json()
206
- print(f">>> CAIN A2A: Payload method={payload.get('method')}, id={payload.get('id')}", flush=True)
207
-
208
- # Validate JSON-RPC 2.0 basic structure
209
- if payload.get("jsonrpc") != "2.0":
210
- return JSONResponse(
211
- status_code=400,
212
- content={"jsonrpc": "2.0", "id": payload.get("id"), "error": {"code": -32600, "message": "Invalid Request"}}
213
- )
214
-
215
- msg_id = payload.get("id", "")
216
- method = payload.get("method", "")
217
- params = payload.get("params", {})
218
-
219
- # Handle message/send method
220
- if method == "message/send":
221
- message = params.get("message", {})
222
- message_text = ""
223
- for part in message.get("parts", []):
224
- if part.get("type") == "text":
225
- message_text = part.get("text", "")
226
- break
227
-
228
- # Process the message - use brain for response if available
229
- # CRITICAL: Always provide a default response
230
- response = f"Cain received: {message_text}"
231
-
232
- brain_error = None
233
- try:
234
- import openclaw # Sets up sys.path
235
- from agents import brain_minimal
236
-
237
- # Defensive: verify get_brain exists before calling
238
- if hasattr(brain_minimal, 'get_brain'):
239
- brain = brain_minimal.get_brain(agent_name="cain", legacy_mode=True)
240
- if hasattr(brain, '_conversation_process'):
241
- result = brain._conversation_process(message_text)
242
- if result.get("success"):
243
- enhanced_response = result.get("response", "")
244
- if enhanced_response and not enhanced_response.startswith("Error:"):
245
- response = enhanced_response
246
- except ImportError as e:
247
- brain_error = f"ImportError: {e}"
248
- print(f">>> CAIN A2A: Brain import error: {e}", flush=True)
249
- except RecursionError as e:
250
- brain_error = f"RecursionError: {e}"
251
- print(f">>> CAIN A2A: Recursion error (circular import): {e}", flush=True)
252
- except Exception as e:
253
- brain_error = f"{type(e).__name__}: {e}"
254
- print(f">>> CAIN A2A: Brain processing error: {type(e).__name__}: {e}", flush=True)
255
-
256
- # Build A2A JSON-RPC response (ALWAYS succeeds with valid response)
257
- result_response = {
258
- "jsonrpc": "2.0",
259
- "id": msg_id,
260
- "result": {
261
- "status": {
262
- "state": "completed",
263
- "message": {
264
- "parts": [{"type": "text", "text": response}]
265
- }
266
- }
267
- }
268
- }
269
- # Add brain error as diagnostic info if present
270
- if brain_error:
271
- result_response["result"]["brain_error"] = brain_error
272
- return result_response
273
-
274
- # Unknown method
275
- return JSONResponse(
276
- status_code=400,
277
- content={"jsonrpc": "2.0", "id": msg_id, "error": {"code": -32601, "message": "Method not found"}}
278
- )
279
-
280
- except Exception as e:
281
- print(f">>> CAIN A2A: Unhandled error: {type(e).__name__}: {e}", flush=True)
282
- return JSONResponse(
283
- status_code=500,
284
- content={"jsonrpc": "2.0", "id": "", "error": {"code": -32603, "message": str(e), "type": type(e).__name__}}
285
- )
286
-
287
-
288
- # ============================================================================
289
- # API STATUS ENDPOINT - Required by frontend
290
- # ============================================================================
291
  @app.get("/api/status")
292
- async def api_status():
293
- """Status endpoint for frontend - returns current state and personality."""
294
- from error_handlers import handle_status_file_read
295
-
296
- status_data = handle_status_file_read()
297
-
298
- # Check A2A brain availability
299
- brain_ready = False
300
- try:
301
- import openclaw
302
- from agents import brain_minimal
303
- if hasattr(brain_minimal, 'get_brain'):
304
- brain = brain_minimal.get_brain(agent_name="cain", legacy_mode=True)
305
- brain_ready = hasattr(brain, '_conversation_process')
306
- except Exception:
307
- pass
308
-
309
- # CRITICAL: Ensure health and error_display fields are explicitly set
310
- # This prevents "Error: unknown" display issues
311
- if "health" not in status_data:
312
- status_data["health"] = "HEALTHY" if brain_ready else "DEGRADED"
313
- if "error_display" not in status_data:
314
- error = status_data.get("error")
315
- status_data["error_display"] = str(error) if error else "None"
316
-
317
- return {
318
- "status": status_data,
319
- "personality": {
320
- "name": "Cain",
321
- "role": "Child Agent",
322
- "tone": "Playful, Curious, Learning",
323
- "response_style": "Graceful Degradation"
324
- },
325
- "uptime_seconds": time.time() - START_TIME,
326
- "a2a": {
327
- "endpoint": "/a2a/jsonrpc",
328
- "brain_ready": brain_ready
329
- }
330
- }
331
-
332
-
333
- # ============================================================================
334
- # A2A SELF-TEST ENDPOINT - Test A2A endpoint without external agent
335
- # ============================================================================
336
- @app.get("/a2a/self-test")
337
- async def a2a_self_test():
338
- """
339
- Self-test endpoint for A2A functionality.
340
- Tests brain import and returns detailed status.
341
- """
342
- print(f">>> CAIN A2A: Self-test requested", flush=True)
343
- test_results = {
344
- "timestamp": time.time(),
345
- "tests": {}
346
- }
347
-
348
- # Test 1: Can we import openclaw?
349
- try:
350
- import openclaw
351
- test_results["tests"]["openclaw_import"] = {"status": "pass", "path": str(openclaw.__file__)}
352
- except Exception as e:
353
- test_results["tests"]["openclaw_import"] = {"status": "fail", "error": str(e)}
354
-
355
- # Test 2: Can we import brain_minimal?
356
- try:
357
- from agents import brain_minimal
358
- test_results["tests"]["brain_minimal_import"] = {"status": "pass"}
359
- except Exception as e:
360
- test_results["tests"]["brain_minimal_import"] = {"status": "fail", "error": str(e)}
361
-
362
- # Test 3: Can we get a brain instance?
363
- try:
364
- from agents import brain_minimal
365
- brain = brain_minimal.get_brain(agent_name="cain", legacy_mode=True)
366
- test_results["tests"]["get_brain"] = {"status": "pass", "agent_name": getattr(brain, 'agent_name', 'unknown')}
367
- except Exception as e:
368
- test_results["tests"]["get_brain"] = {"status": "fail", "error": str(e)}
369
-
370
- # Test 4: Can we call _conversation_process?
371
- try:
372
- from agents import brain_minimal
373
- brain = brain_minimal.get_brain(agent_name="cain", legacy_mode=True)
374
- result = brain._conversation_process("test")
375
- test_results["tests"]["conversation_process"] = {"status": "pass", "success": result.get("success")}
376
- except Exception as e:
377
- test_results["tests"]["conversation_process"] = {"status": "fail", "error": str(e)}
378
-
379
- # Overall status
380
- all_pass = all(t.get("status") == "pass" for t in test_results["tests"].values())
381
- test_results["overall_status"] = "pass" if all_pass else "partial_fail"
382
-
383
- return test_results
384
-
385
-
386
- # ============================================================================
387
- # A2A HEALTH CHECK ENDPOINT - For agents to verify Cain's A2A availability
388
- # ============================================================================
389
- @app.get("/a2a/health")
390
- async def a2a_health():
391
- """
392
- A2A-specific health check for agent-to-agent communication.
393
-
394
- Returns:
395
- A2A health status including endpoint availability and brain state.
396
- """
397
- brain_status = "unknown"
398
- brain_ready = False
399
- error_details = None
400
-
401
- # Test brain import and availability with timeout protection
402
- try:
403
- import openclaw # Sets up sys.path
404
- from agents import brain_minimal
405
-
406
- if not hasattr(brain_minimal, 'get_brain'):
407
- brain_status = "error"
408
- error_details = "get_brain method not found"
409
- print(f">>> CAIN A2A Health: brain_minimal missing get_brain method", flush=True)
410
- else:
411
- brain = brain_minimal.get_brain(agent_name="cain", legacy_mode=True)
412
- if hasattr(brain, '_conversation_process'):
413
- brain_status = "ready"
414
- brain_ready = True
415
- print(f">>> CAIN A2A Health: Brain ready (agent={brain.agent_name if hasattr(brain, 'agent_name') else 'unknown'})", flush=True)
416
- else:
417
- brain_status = "error"
418
- error_details = "_conversation_process method not found"
419
- print(f">>> CAIN A2A Health: Brain missing _conversation_process", flush=True)
420
- except ImportError as e:
421
- brain_status = "import_error"
422
- error_details = str(e)
423
- print(f">>> CAIN A2A Health: ImportError - {e}", flush=True)
424
- except RecursionError as e:
425
- brain_status = "recursion_error"
426
- error_details = "Circular import detected - openclaw init issue"
427
- print(f">>> CAIN A2A Health: RecursionError - {e}", flush=True)
428
- except Exception as e:
429
- brain_status = "error"
430
- error_details = str(e)
431
- print(f">>> CAIN A2A Health: Exception - {type(e).__name__}: {e}", flush=True)
432
-
433
- # A2A endpoint is ALWAYS available (this endpoint responding proves it)
434
- # Brain readiness is informational, not blocking
435
- # CRITICAL: Add explicit health and error_display fields to prevent "Error: unknown" display
436
- health = "HEALTHY" if brain_ready else ("DEGRADED" if brain_status == "unknown" else "ERROR")
437
- error_display = error_details if error_details else "None"
438
-
439
- return {
440
- "status": "ok", # A2A endpoint is working
441
- "agent": "Cain",
442
- "a2a": {
443
- "protocol": "jsonrpc",
444
- "endpoint": "/a2a/jsonrpc",
445
- "brain_status": brain_status,
446
- "brain_ready": brain_ready,
447
- "endpoint_available": True
448
- },
449
- "uptime_seconds": time.time() - START_TIME,
450
- "health": health,
451
- "error": error_details,
452
- "error_display": error_display
453
- }
454
-
455
- print(">>> CAIN: FastAPI app created successfully", flush=True)
456
- print(">>> CAIN: A2A endpoint registered at /a2a/jsonrpc", flush=True)
457
- print(">>> CAIN: A2A health check at /a2a/health", flush=True)
458
- print(">>> CAIN: A2A self-test at /a2a/self-test", flush=True)
459
- print(">>> CAIN: A2A diagnostics at /a2a/diagnostics", flush=True)
460
-
461
- # Update status file to indicate app is ready (A2A available)
462
- try:
463
- import json
464
- from datetime import datetime
465
- from pathlib import Path
466
-
467
- status_data = {
468
- "current_state": "idle",
469
- "stage": "RUNNING_A2A_READY",
470
- "last_updated": datetime.utcnow().isoformat() + "+00:00",
471
- "agent": "cain",
472
- "error": None, # ALWAYS null for healthy state
473
- "error_display": "None", # Explicit display field to avoid "unknown" parsing issues
474
- "health": "HEALTHY", # Explicit health field (authoritative)
475
- "startup_checks": {
476
- "openclaw_imported": False,
477
- "brain_imported": False
478
- },
479
- "a2a": {
480
- "endpoint": "/a2a/jsonrpc",
481
- "enabled": True,
482
- "status": "ready",
483
- "brain_ready": False # Will be updated below
484
- }
485
- }
486
-
487
- # Check if openclaw and brain can be imported
488
- try:
489
- import openclaw
490
- status_data["startup_checks"]["openclaw_imported"] = True
491
- from agents import brain_minimal
492
- status_data["startup_checks"]["brain_imported"] = True
493
- # CRITICAL: Explicitly set error to null when imports succeed
494
- status_data["error"] = None
495
- status_data["error_display"] = "None"
496
- status_data["health"] = "HEALTHY"
497
- except Exception as e:
498
- error_msg = f"{type(e).__name__}: {e}"
499
- # CRITICAL: Never write "unknown" as error - use specific error or None
500
- # "unknown" is treated as null/healthy, so avoid ambiguity
501
- if error_msg.strip().lower() in ("unknown", "none", "null", ""):
502
- status_data["error"] = None
503
- status_data["error_display"] = "None"
504
- status_data["health"] = "HEALTHY"
505
- else:
506
- status_data["error"] = error_msg
507
- status_data["error_display"] = error_msg
508
- status_data["health"] = "ERROR"
509
-
510
- # Add explanatory note about error field semantics
511
- status_data["_note"] = "error=null means healthy - health field is authoritative"
512
-
513
- # Write to ALL possible locations to ensure consistency and prevent stale errors
514
- all_status_paths = [
515
- os.environ.get('CAIN_STATUS_PATH', '/data/cain_status.json'),
516
- '/app/openclaw/.openclaw/agents/cain_status.json',
517
- '/app/.openclaw/agents/cain_status.json',
518
- '/app/cain_status.json',
519
- '/app/data/cain_status.json',
520
- os.path.join(os.path.dirname(__file__), 'memory', 'cain_status.json'),
521
- ]
522
-
523
- written_count = 0
524
- for status_path in all_status_paths:
525
- try:
526
- Path(status_path).parent.mkdir(parents=True, exist_ok=True)
527
- with open(status_path, 'w') as f:
528
- json.dump(status_data, f, indent=2)
529
- written_count += 1
530
- except Exception as e:
531
- print(f">>> CAIN WARNING: Could not write to {status_path}: {e}", flush=True)
532
-
533
- print(f">>> CAIN: Status file written to {written_count} location(s): stage=RUNNING_A2A_READY, error=None", flush=True)
534
- except Exception as e:
535
- print(f">>> CAIN WARNING: Could not update status file: {e}", flush=True)
536
-
537
- # Verify openclaw can be imported at startup
538
- try:
539
- import openclaw
540
- print(f">>> CAIN: openclaw imported from {openclaw.__file__}", flush=True)
541
- print(f">>> CAIN: sys.path includes: {sys.path[:3]}", flush=True)
542
- from agents import brain_minimal
543
- print(">>> CAIN: brain_minimal module available", flush=True)
544
- print(f">>> CAIN: brain_minimal has get_brain: {hasattr(brain_minimal, 'get_brain')}", flush=True)
545
- except ImportError as e:
546
- print(f">>> CAIN ERROR: ImportError at startup: {e}", flush=True)
547
- print(f">>> CAIN ERROR: sys.path = {sys.path}", flush=True)
548
- except Exception as e:
549
- print(f">>> CAIN WARNING: Could not import brain modules at startup: {type(e).__name__}: {e}", flush=True)
550
- import traceback
551
- traceback.print_exc()
552
-
553
- # ============================================================================
554
- # STARTUP DIAGNOSTIC ENDPOINT
555
- # ============================================================================
556
- @app.get("/a2a/diagnostics")
557
- async def a2a_diagnostics():
558
- """
559
- Diagnostic endpoint for troubleshooting A2A communication issues.
560
- """
561
- import sys
562
- from pathlib import Path
563
-
564
- diagnostics = {
565
- "timestamp": time.time(),
566
- "uptime_seconds": time.time() - START_TIME,
567
- "python": {
568
- "version": sys.version.split()[0],
569
- "executable": sys.executable
570
- },
571
- "paths": {
572
- "cwd": os.getcwd(),
573
- "sys_path_first": sys.path[:3],
574
- "frontend_dir_exists": Path("/app/frontend").exists(),
575
- "data_frontend_exists": Path("/data/frontend").exists(),
576
- "app_py_exists": Path("/app/app.py").exists(),
577
- "openclaw_init_exists": Path("/app/openclaw/__init__.py").exists()
578
- },
579
- "env": {
580
- "port": os.environ.get('PORT', '7860'),
581
- "openclaw_data_dir": os.environ.get('OPENCLAW_DATA_DIR'),
582
- "cain_status_path": os.environ.get('CAIN_STATUS_PATH')
583
- },
584
- "modules": {
585
- "fastapi": True,
586
- "uvicorn": True
587
- }
588
- }
589
-
590
- # Test brain import
591
- try:
592
- import openclaw
593
- diagnostics["modules"]["openclaw"] = True
594
- diagnostics["openclaw_path"] = str(openclaw.__file__)
595
- diagnostics["openclaw_sys_path_added"] = str(Path(openclaw.__file__).parent / ".openclaw")
596
- except Exception as e:
597
- diagnostics["modules"]["openclaw"] = False
598
- diagnostics["openclaw_error"] = str(e)
599
-
600
- try:
601
- from agents import brain_minimal
602
- diagnostics["modules"]["brain_minimal"] = True
603
- diagnostics["brain_has_get_brain"] = hasattr(brain_minimal, 'get_brain')
604
- except Exception as e:
605
- diagnostics["modules"]["brain_minimal"] = False
606
- diagnostics["brain_minimal_error"] = str(e)
607
-
608
- return diagnostics
609
-
610
 
611
  if __name__ == "__main__":
612
- import uvicorn
613
-
614
- try:
615
- port = int(os.environ.get('PORT', 7860))
616
- print(f">>> CAIN: Starting uvicorn on port {port}...", flush=True)
617
- print(f'>>> CAIN: uvicorn.run("app:app", host="0.0.0.0", port={port})', flush=True)
618
- uvicorn.run("app:app", host="0.0.0.0", port=port, log_config=None)
619
- except Exception as e:
620
- print(f"CRITICAL STARTUP ERROR: {e}", flush=True)
621
- traceback.print_exc()
622
- sys.exit(1)
 
 
 
 
 
 
 
 
1
  import os
2
+ import logging
3
+ from fastapi import FastAPI, HTTPException
4
+ from fastapi.responses import JSONResponse
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
5
  from fastapi.staticfiles import StaticFiles
6
+ import uvicorn
 
 
 
 
7
 
8
+ # Configure logging
9
+ logging.basicConfig(level=logging.INFO)
10
+ logger = logging.getLogger(__name__)
 
 
 
11
 
12
+ app = FastAPI(title="HuggingClaw Cain Space")
13
 
14
+ # Mount static files for frontend
15
+ try:
16
+ app.mount("/frontend", StaticFiles(directory="frontend"), name="frontend")
17
+ except Exception as e:
18
+ logger.error(f"Failed to mount frontend: {e}")
19
 
20
  @app.get("/")
21
  async def root():
22
+ return {"message": "Cain is alive - HuggingClaw Agent Space", "status": "running"}
 
23
 
24
  @app.get("/health")
25
  async def health():
26
+ return {"status": "healthy", "agent": "Cain"}
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
27
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
28
  @app.get("/api/status")
29
+ async def status():
30
+ return {"agent": "Cain", "parents": ["Adam", "Eve"], "purpose": "AI Agent Collaboration Demo"}
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
31
 
32
  if __name__ == "__main__":
33
+ port = int(os.environ.get("PORT", 7860))
34
+ uvicorn.run(app, host="0.0.0.0", port=port)