Spaces:
Sleeping
Sleeping
Claude Code commited on
Commit ·
93d76e5
1
Parent(s): c8b024d
Claude Code: Investigate the file to identify the cause of the "unknown" run
Browse files- .openclaw/agents/__pycache__/brain_minimal.cpython-311.pyc +0 -0
- .openclaw/agents/__pycache__/rbac.cpython-311.pyc +0 -0
- .openclaw/agents/brain_minimal.py +3 -1
- .openclaw/agents/cain_status.json +1 -1
- .openclaw/agents/logs/session-archive.jsonl +3 -0
- __pycache__/app.cpython-311.pyc +0 -0
- app.py +131 -15
.openclaw/agents/__pycache__/brain_minimal.cpython-311.pyc
CHANGED
|
Binary files a/.openclaw/agents/__pycache__/brain_minimal.cpython-311.pyc and b/.openclaw/agents/__pycache__/brain_minimal.cpython-311.pyc differ
|
|
|
.openclaw/agents/__pycache__/rbac.cpython-311.pyc
CHANGED
|
Binary files a/.openclaw/agents/__pycache__/rbac.cpython-311.pyc and b/.openclaw/agents/__pycache__/rbac.cpython-311.pyc differ
|
|
|
.openclaw/agents/brain_minimal.py
CHANGED
|
@@ -666,10 +666,12 @@ class BrainMinimal:
|
|
| 666 |
return {
|
| 667 |
"success": True,
|
| 668 |
"status": "healthy",
|
|
|
|
| 669 |
"state": self.brain_state.value,
|
| 670 |
"agent": self.agent_name,
|
| 671 |
"role": self.current_role.value if self.current_role else "unknown",
|
| 672 |
-
"tools_count": len(self.get_allowed_tools())
|
|
|
|
| 673 |
}
|
| 674 |
|
| 675 |
def _get_status(self) -> Dict[str, Any]:
|
|
|
|
| 666 |
return {
|
| 667 |
"success": True,
|
| 668 |
"status": "healthy",
|
| 669 |
+
"stage": "RUNNING",
|
| 670 |
"state": self.brain_state.value,
|
| 671 |
"agent": self.agent_name,
|
| 672 |
"role": self.current_role.value if self.current_role else "unknown",
|
| 673 |
+
"tools_count": len(self.get_allowed_tools()),
|
| 674 |
+
"error": None
|
| 675 |
}
|
| 676 |
|
| 677 |
def _get_status(self) -> Dict[str, Any]:
|
.openclaw/agents/cain_status.json
CHANGED
|
@@ -1,5 +1,5 @@
|
|
| 1 |
{
|
| 2 |
"current_state": "idle",
|
| 3 |
-
"last_updated": "2026-03-14T19:
|
| 4 |
"agent": "cain"
|
| 5 |
}
|
|
|
|
| 1 |
{
|
| 2 |
"current_state": "idle",
|
| 3 |
+
"last_updated": "2026-03-14T19:17:42.455943+00:00",
|
| 4 |
"agent": "cain"
|
| 5 |
}
|
.openclaw/agents/logs/session-archive.jsonl
CHANGED
|
@@ -19,3 +19,6 @@
|
|
| 19 |
{"type": "state_change", "state": "processing", "timestamp": "2026-03-14T19:10:02.927551+00:00", "agent": "cain"}
|
| 20 |
{"type": "state_change", "state": "success", "timestamp": "2026-03-14T19:10:02.928572+00:00", "agent": "cain"}
|
| 21 |
{"type": "state_change", "state": "idle", "timestamp": "2026-03-14T19:10:02.929525+00:00", "agent": "cain"}
|
|
|
|
|
|
|
|
|
|
|
|
| 19 |
{"type": "state_change", "state": "processing", "timestamp": "2026-03-14T19:10:02.927551+00:00", "agent": "cain"}
|
| 20 |
{"type": "state_change", "state": "success", "timestamp": "2026-03-14T19:10:02.928572+00:00", "agent": "cain"}
|
| 21 |
{"type": "state_change", "state": "idle", "timestamp": "2026-03-14T19:10:02.929525+00:00", "agent": "cain"}
|
| 22 |
+
{"type": "state_change", "state": "processing", "timestamp": "2026-03-14T19:17:35.121684+00:00", "agent": "cain"}
|
| 23 |
+
{"type": "state_change", "state": "success", "timestamp": "2026-03-14T19:17:35.122941+00:00", "agent": "cain"}
|
| 24 |
+
{"type": "state_change", "state": "idle", "timestamp": "2026-03-14T19:17:35.124108+00:00", "agent": "cain"}
|
__pycache__/app.cpython-311.pyc
CHANGED
|
Binary files a/__pycache__/app.cpython-311.pyc and b/__pycache__/app.cpython-311.pyc differ
|
|
|
app.py
CHANGED
|
@@ -9,6 +9,8 @@ import os
|
|
| 9 |
import sys
|
| 10 |
import json
|
| 11 |
import asyncio
|
|
|
|
|
|
|
| 12 |
from typing import Dict, Any, List, Optional
|
| 13 |
from datetime import datetime
|
| 14 |
from pathlib import Path
|
|
@@ -18,6 +20,21 @@ from fastapi.responses import JSONResponse
|
|
| 18 |
from pydantic import BaseModel, Field
|
| 19 |
import uvicorn
|
| 20 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 21 |
# Paths
|
| 22 |
WORKSPACE_DIR = Path("/tmp/claude-workspace")
|
| 23 |
OPENCLAW_HOME = Path.home() / ".openclaw"
|
|
@@ -36,16 +53,20 @@ sys.path.insert(0, str(BASE_DIR))
|
|
| 36 |
try:
|
| 37 |
from brain_minimal import get_brain, BrainState
|
| 38 |
BRAIN_AVAILABLE = True
|
| 39 |
-
|
|
|
|
| 40 |
BRAIN_AVAILABLE = False
|
| 41 |
-
|
|
|
|
| 42 |
|
| 43 |
try:
|
| 44 |
from health_monitor import get_health_monitor, LogLevel, HealthStatus
|
| 45 |
HEALTH_MONITOR_AVAILABLE = True
|
| 46 |
-
|
|
|
|
| 47 |
HEALTH_MONITOR_AVAILABLE = False
|
| 48 |
-
|
|
|
|
| 49 |
|
| 50 |
# FastAPI app
|
| 51 |
app = FastAPI(
|
|
@@ -54,6 +75,41 @@ app = FastAPI(
|
|
| 54 |
version="1.0.0"
|
| 55 |
)
|
| 56 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 57 |
# ========== Request/Response Models ==========
|
| 58 |
|
| 59 |
class ChatMessage(BaseModel):
|
|
@@ -101,6 +157,36 @@ class HealthResponse(BaseModel):
|
|
| 101 |
_brain_instance = None
|
| 102 |
_start_time = datetime.utcnow()
|
| 103 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 104 |
def get_brain_instance():
|
| 105 |
"""Get or create brain instance"""
|
| 106 |
global _brain_instance
|
|
@@ -158,7 +244,12 @@ async def get_agents_status():
|
|
| 158 |
brain = get_brain_instance()
|
| 159 |
|
| 160 |
if brain is None:
|
| 161 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 162 |
|
| 163 |
try:
|
| 164 |
info = brain.get_info()
|
|
@@ -178,7 +269,12 @@ async def get_agents_status():
|
|
| 178 |
timestamp=datetime.utcnow().isoformat() + "Z"
|
| 179 |
)
|
| 180 |
except Exception as e:
|
| 181 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 182 |
|
| 183 |
@app.post("/api/chat", response_model=ChatResponse, tags=["Chat"])
|
| 184 |
async def chat(message: ChatMessage, background_tasks: BackgroundTasks):
|
|
@@ -240,9 +336,10 @@ async def get_health():
|
|
| 240 |
health_result = brain.execute_tool("health_check")
|
| 241 |
components["brain"] = health_result
|
| 242 |
except Exception as e:
|
| 243 |
-
|
|
|
|
| 244 |
else:
|
| 245 |
-
components["brain"] = {"status": "unavailable"}
|
| 246 |
|
| 247 |
# Health monitor components
|
| 248 |
if monitor:
|
|
@@ -260,18 +357,34 @@ async def get_health():
|
|
| 260 |
}
|
| 261 |
}
|
| 262 |
except Exception as e:
|
| 263 |
-
|
|
|
|
|
|
|
|
|
|
| 264 |
|
| 265 |
# Determine overall status
|
| 266 |
if brain:
|
| 267 |
-
|
| 268 |
-
|
| 269 |
-
|
| 270 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 271 |
else:
|
| 272 |
overall_status = "degraded"
|
| 273 |
stage = "BRAIN_UNAVAILABLE"
|
| 274 |
agent = "cain"
|
|
|
|
| 275 |
|
| 276 |
return HealthResponse(
|
| 277 |
status=overall_status,
|
|
@@ -517,10 +630,13 @@ try:
|
|
| 517 |
app = gr.mount_gradio_app(app, gradio_app, path="/gradio")
|
| 518 |
|
| 519 |
print("[API] Gradio dashboard mounted at /gradio")
|
| 520 |
-
|
| 521 |
-
|
|
|
|
|
|
|
| 522 |
except Exception as e:
|
| 523 |
print(f"[API] Warning: Could not mount Gradio dashboard: {e}")
|
|
|
|
| 524 |
|
| 525 |
# ========== Main Entry Point ==========
|
| 526 |
|
|
|
|
| 9 |
import sys
|
| 10 |
import json
|
| 11 |
import asyncio
|
| 12 |
+
import traceback
|
| 13 |
+
import logging
|
| 14 |
from typing import Dict, Any, List, Optional
|
| 15 |
from datetime import datetime
|
| 16 |
from pathlib import Path
|
|
|
|
| 20 |
from pydantic import BaseModel, Field
|
| 21 |
import uvicorn
|
| 22 |
|
| 23 |
+
# Configure logging
|
| 24 |
+
LOG_DIR = Path.home() / ".openclaw" / "logs"
|
| 25 |
+
LOG_DIR.mkdir(parents=True, exist_ok=True)
|
| 26 |
+
LOG_FILE = LOG_DIR / "api_errors.log"
|
| 27 |
+
|
| 28 |
+
logging.basicConfig(
|
| 29 |
+
level=logging.INFO,
|
| 30 |
+
format='%(asctime)s - %(name)s - %(levelname)s - %(message)s',
|
| 31 |
+
handlers=[
|
| 32 |
+
logging.FileHandler(LOG_FILE),
|
| 33 |
+
logging.StreamHandler(sys.stdout)
|
| 34 |
+
]
|
| 35 |
+
)
|
| 36 |
+
logger = logging.getLogger("cain_api")
|
| 37 |
+
|
| 38 |
# Paths
|
| 39 |
WORKSPACE_DIR = Path("/tmp/claude-workspace")
|
| 40 |
OPENCLAW_HOME = Path.home() / ".openclaw"
|
|
|
|
| 53 |
try:
|
| 54 |
from brain_minimal import get_brain, BrainState
|
| 55 |
BRAIN_AVAILABLE = True
|
| 56 |
+
logger.info("brain_minimal module loaded successfully")
|
| 57 |
+
except ImportError as e:
|
| 58 |
BRAIN_AVAILABLE = False
|
| 59 |
+
logger.error(f"Failed to import brain_minimal: {e}\n{traceback.format_exc()}")
|
| 60 |
+
print(f"[API] Warning: brain_minimal not available - {e}")
|
| 61 |
|
| 62 |
try:
|
| 63 |
from health_monitor import get_health_monitor, LogLevel, HealthStatus
|
| 64 |
HEALTH_MONITOR_AVAILABLE = True
|
| 65 |
+
logger.info("health_monitor module loaded successfully")
|
| 66 |
+
except ImportError as e:
|
| 67 |
HEALTH_MONITOR_AVAILABLE = False
|
| 68 |
+
logger.error(f"Failed to import health_monitor: {e}\n{traceback.format_exc()}")
|
| 69 |
+
print(f"[API] Warning: health_monitor not available - {e}")
|
| 70 |
|
| 71 |
# FastAPI app
|
| 72 |
app = FastAPI(
|
|
|
|
| 75 |
version="1.0.0"
|
| 76 |
)
|
| 77 |
|
| 78 |
+
|
| 79 |
+
# ========== Global Exception Handler ==========
|
| 80 |
+
|
| 81 |
+
@app.exception_handler(Exception)
|
| 82 |
+
async def global_exception_handler(request, exc):
|
| 83 |
+
"""
|
| 84 |
+
Global exception handler that logs full stack traces for all errors.
|
| 85 |
+
This ensures no error is silently swallowed as "unknown".
|
| 86 |
+
"""
|
| 87 |
+
# Get full traceback
|
| 88 |
+
tb_str = traceback.format_exc()
|
| 89 |
+
error_type = type(exc).__name__
|
| 90 |
+
error_msg = str(exc)
|
| 91 |
+
|
| 92 |
+
# Log the full error with traceback
|
| 93 |
+
logger.error(
|
| 94 |
+
f"Unhandled exception: {error_type}: {error_msg}\n"
|
| 95 |
+
f"Path: {request.url.path}\n"
|
| 96 |
+
f"Method: {request.method}\n"
|
| 97 |
+
f"Traceback:\n{tb_str}"
|
| 98 |
+
)
|
| 99 |
+
|
| 100 |
+
# Return detailed error response
|
| 101 |
+
return JSONResponse(
|
| 102 |
+
status_code=500,
|
| 103 |
+
content={
|
| 104 |
+
"error": error_msg,
|
| 105 |
+
"error_type": error_type,
|
| 106 |
+
"traceback": tb_str,
|
| 107 |
+
"path": str(request.url.path),
|
| 108 |
+
"method": request.method,
|
| 109 |
+
"timestamp": datetime.utcnow().isoformat() + "Z"
|
| 110 |
+
}
|
| 111 |
+
)
|
| 112 |
+
|
| 113 |
# ========== Request/Response Models ==========
|
| 114 |
|
| 115 |
class ChatMessage(BaseModel):
|
|
|
|
| 157 |
_brain_instance = None
|
| 158 |
_start_time = datetime.utcnow()
|
| 159 |
|
| 160 |
+
# ========== Startup Event ==========
|
| 161 |
+
|
| 162 |
+
@app.on_event("startup")
|
| 163 |
+
async def startup_event():
|
| 164 |
+
"""Log startup information and verify components"""
|
| 165 |
+
logger.info("=" * 50)
|
| 166 |
+
logger.info("Cain API Starting")
|
| 167 |
+
logger.info(f"Start time: {_start_time.isoformat()}")
|
| 168 |
+
logger.info(f"Brain available: {BRAIN_AVAILABLE}")
|
| 169 |
+
logger.info(f"Health monitor available: {HEALTH_MONITOR_AVAILABLE}")
|
| 170 |
+
logger.info(f"Working directory: {os.getcwd()}")
|
| 171 |
+
logger.info(f"Python version: {sys.version}")
|
| 172 |
+
logger.info(f"API log file: {LOG_FILE}")
|
| 173 |
+
logger.info("=" * 50)
|
| 174 |
+
|
| 175 |
+
# Test brain initialization
|
| 176 |
+
if BRAIN_AVAILABLE:
|
| 177 |
+
try:
|
| 178 |
+
brain = get_brain_instance()
|
| 179 |
+
if brain:
|
| 180 |
+
logger.info(f"Brain initialized: agent={brain.agent_name}, role={brain.current_role}")
|
| 181 |
+
health = brain.execute_tool("health_check")
|
| 182 |
+
logger.info(f"Initial health check: {health}")
|
| 183 |
+
else:
|
| 184 |
+
logger.warning("Brain instance is None after initialization")
|
| 185 |
+
except Exception as e:
|
| 186 |
+
logger.error(f"Brain initialization test failed: {e}\n{traceback.format_exc()}")
|
| 187 |
+
else:
|
| 188 |
+
logger.warning("BRAIN_AVAILABLE=False - brain features will be limited")
|
| 189 |
+
|
| 190 |
def get_brain_instance():
|
| 191 |
"""Get or create brain instance"""
|
| 192 |
global _brain_instance
|
|
|
|
| 244 |
brain = get_brain_instance()
|
| 245 |
|
| 246 |
if brain is None:
|
| 247 |
+
logger.error("Brain not available for /api/agents")
|
| 248 |
+
raise HTTPException(
|
| 249 |
+
status_code=503,
|
| 250 |
+
detail="Brain not available",
|
| 251 |
+
headers={"X-Error-Timestamp": datetime.utcnow().isoformat() + "Z"}
|
| 252 |
+
)
|
| 253 |
|
| 254 |
try:
|
| 255 |
info = brain.get_info()
|
|
|
|
| 269 |
timestamp=datetime.utcnow().isoformat() + "Z"
|
| 270 |
)
|
| 271 |
except Exception as e:
|
| 272 |
+
logger.error(f"Error getting agent status: {e}\n{traceback.format_exc()}")
|
| 273 |
+
raise HTTPException(
|
| 274 |
+
status_code=500,
|
| 275 |
+
detail=f"Error getting agent status: {str(e)}",
|
| 276 |
+
headers={"X-Error-Type": type(e).__name__}
|
| 277 |
+
)
|
| 278 |
|
| 279 |
@app.post("/api/chat", response_model=ChatResponse, tags=["Chat"])
|
| 280 |
async def chat(message: ChatMessage, background_tasks: BackgroundTasks):
|
|
|
|
| 336 |
health_result = brain.execute_tool("health_check")
|
| 337 |
components["brain"] = health_result
|
| 338 |
except Exception as e:
|
| 339 |
+
logger.error(f"Brain health check failed: {e}\n{traceback.format_exc()}")
|
| 340 |
+
components["brain"] = {"status": "error", "error": str(e), "traceback": traceback.format_exc()}
|
| 341 |
else:
|
| 342 |
+
components["brain"] = {"status": "unavailable", "error": "Brain not initialized"}
|
| 343 |
|
| 344 |
# Health monitor components
|
| 345 |
if monitor:
|
|
|
|
| 357 |
}
|
| 358 |
}
|
| 359 |
except Exception as e:
|
| 360 |
+
logger.error(f"Health monitor check failed: {e}\n{traceback.format_exc()}")
|
| 361 |
+
components["health_monitor"] = {"status": "error", "error": str(e), "traceback": traceback.format_exc()}
|
| 362 |
+
else:
|
| 363 |
+
components["health_monitor"] = {"status": "unavailable", "error": "Health monitor not initialized"}
|
| 364 |
|
| 365 |
# Determine overall status
|
| 366 |
if brain:
|
| 367 |
+
try:
|
| 368 |
+
brain_health = brain.execute_tool("health_check")
|
| 369 |
+
overall_status = brain_health.get("status", "unknown")
|
| 370 |
+
stage = brain_health.get("stage", "UNKNOWN")
|
| 371 |
+
agent = brain_health.get("agent", "cain")
|
| 372 |
+
error = brain_health.get("error", None)
|
| 373 |
+
|
| 374 |
+
# Log if there's an error but status is healthy
|
| 375 |
+
if error:
|
| 376 |
+
logger.warning(f"Health check returned with error: {error}")
|
| 377 |
+
except Exception as e:
|
| 378 |
+
logger.error(f"Failed to get brain health: {e}\n{traceback.format_exc()}")
|
| 379 |
+
overall_status = "error"
|
| 380 |
+
stage = "ERROR"
|
| 381 |
+
agent = "cain"
|
| 382 |
+
error = str(e)
|
| 383 |
else:
|
| 384 |
overall_status = "degraded"
|
| 385 |
stage = "BRAIN_UNAVAILABLE"
|
| 386 |
agent = "cain"
|
| 387 |
+
error = "Brain not initialized"
|
| 388 |
|
| 389 |
return HealthResponse(
|
| 390 |
status=overall_status,
|
|
|
|
| 630 |
app = gr.mount_gradio_app(app, gradio_app, path="/gradio")
|
| 631 |
|
| 632 |
print("[API] Gradio dashboard mounted at /gradio")
|
| 633 |
+
logger.info("Gradio dashboard mounted successfully at /gradio")
|
| 634 |
+
except ImportError as e:
|
| 635 |
+
print(f"[API] Warning: Gradio not available, dashboard not mounted - {e}")
|
| 636 |
+
logger.error(f"Failed to import gradio: {e}\n{traceback.format_exc()}")
|
| 637 |
except Exception as e:
|
| 638 |
print(f"[API] Warning: Could not mount Gradio dashboard: {e}")
|
| 639 |
+
logger.error(f"Failed to mount Gradio dashboard: {e}\n{traceback.format_exc()}")
|
| 640 |
|
| 641 |
# ========== Main Entry Point ==========
|
| 642 |
|