File size: 12,274 Bytes
a10e62e
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
"""
Agent Control Routes - REST API for agent-to-agent Atom OS control.

Allows any agent (OpenClaw, Claude, custom) to programmatically control Atom OS:
- Start Atom as background service
- Stop Atom service
- Check status
- Execute commands

Usage:
    import requests

    # Start Atom
    response = requests.post("http://localhost:8000/api/agent/start",
                            json={"port": 8000})

    # Check status
    response = requests.get("http://localhost:8000/api/agent/status")

    # Stop Atom
    response = requests.post("http://localhost:8000/api/agent/stop")
"""

from fastapi import APIRouter, Depends, HTTPException
from pydantic import BaseModel, Field
from typing import Optional, List, Dict, Any
from sqlalchemy.orm import Session

# Import daemon manager
import sys
from pathlib import Path
sys.path.insert(0, str(Path(__file__).parent.parent))
from cli.daemon import DaemonManager

# Import authentication and authorization
from core.admin_endpoints import get_super_admin
from core.models import User, DelegationChain
from core.database import get_db

router = APIRouter(prefix="/api/agent", tags=["agent-control"])


# Request/Response Models
class StartAgentRequest(BaseModel):
    """Request model for starting Atom OS service."""

    port: int = Field(default=8000, ge=1, le=65535, description="Port for web server")
    host: str = Field(default="0.0.0.0", description="Host to bind to")
    workers: int = Field(default=1, ge=1, le=16, description="Number of worker processes")
    host_mount: bool = Field(default=False, description="Enable host filesystem mount")
    dev: bool = Field(default=False, description="Enable development mode")


class StartAgentResponse(BaseModel):
    """Response model for start endpoint."""

    success: bool
    pid: Optional[int] = None
    status: str
    dashboard_url: Optional[str] = None
    message: str
    error: Optional[str] = None


class StopAgentResponse(BaseModel):
    """Response model for stop endpoint."""

    success: bool
    status: str
    message: str
    error: Optional[str] = None


class RestartAgentResponse(BaseModel):
    """Response model for restart endpoint."""

    success: bool
    pid: Optional[int] = None
    status: str
    dashboard_url: Optional[str] = None
    was_running: bool
    message: str
    error: Optional[str] = None


class AgentStatusResponse(BaseModel):
    """Response model for status endpoint."""

    success: bool
    status: dict
    message: Optional[str] = None


class ExecuteCommandRequest(BaseModel):
    """Request model for execute endpoint."""

    command: str = Field(..., description="Atom command to execute")
    timeout: int = Field(default=30, ge=1, le=300, description="Timeout in seconds")


class ExecuteCommandResponse(BaseModel):
    """Response model for execute endpoint."""

    success: bool
    result: Optional[str] = None
    error: Optional[str] = None
    note: Optional[str] = None


# API Endpoints
@router.post("/start", response_model=StartAgentResponse)
async def start_atom(
    request: StartAgentRequest,
    current_user: User = Depends(get_super_admin)
):
    """Start Atom OS as background service (super_admin only).

    **SECURITY**: Requires super_admin authentication to prevent unauthorized
    daemon control. Use this endpoint only from trusted sources.

    Called by external agents (Claude, OpenClaw, custom agents) to
    programmatically start Atom as a background service.

    **Example:**
        ```python
        import requests

        response = requests.post(
            "http://localhost:8000/api/agent/start",
            json={"port": 8000, "host": "0.0.0.0"}
        )
        print(response.json())
        ```

    **Returns:**
        - success: True if started successfully
        - pid: Process ID of daemon
        - status: "started"
        - dashboard_url: URL to web dashboard
        - message: Success message

    **Raises:**
        - 400: If Atom is already running
        - 500: If daemon fails to start
    """
    try:
        if DaemonManager.is_running():
            current_pid = DaemonManager.get_pid()
            raise HTTPException(
                status_code=400,
                detail=f"Atom OS is already running (PID: {current_pid})"
            )

        pid = DaemonManager.start_daemon(
            port=request.port,
            host=request.host,
            workers=request.workers,
            host_mount=request.host_mount,
            dev=request.dev
        )

        return StartAgentResponse(
            success=True,
            pid=pid,
            status="started",
            dashboard_url=f"http://{request.host}:{request.port}",
            message="Atom OS started successfully"
        )

    except RuntimeError as e:
        raise HTTPException(status_code=500, detail=str(e))
    except IOError as e:
        raise HTTPException(status_code=500, detail=str(e))


@router.post("/stop", response_model=StopAgentResponse)
async def stop_atom(current_user: User = Depends(get_super_admin)):
    """Stop Atom OS background service (super_admin only).

    **SECURITY**: Requires super_admin authentication to prevent unauthorized
    daemon control.

    Gracefully shuts down Atom daemon service.

    **Example:**
        ```python
        import requests

        response = requests.post("http://localhost:8000/api/agent/stop")
        print(response.json())
        ```

    **Returns:**
        - success: True if stopped
        - status: "stopped"
        - message: Success message

    **Raises:**
        - 400: If Atom is not running
        - 500: If stop fails
    """
    try:
        if not DaemonManager.is_running():
            raise HTTPException(
                status_code=400,
                detail="Atom OS is not running"
            )

        DaemonManager.stop_daemon()

        return StopAgentResponse(
            success=True,
            status="stopped",
            message="Atom OS stopped successfully"
        )

    except HTTPException:
        raise
    except Exception as e:
        raise HTTPException(status_code=500, detail=str(e))


@router.post("/restart", response_model=RestartAgentResponse)
async def restart_atom(
    request: StartAgentRequest,
    current_user: User = Depends(get_super_admin)
):
    """Restart Atom OS background service (super_admin only).

    **SECURITY**: Requires super_admin authentication to prevent unauthorized
    daemon control.

    Stops Atom if running, then starts again with new configuration.

    **Example:**
        ```python
        import requests

        response = requests.post(
            "http://localhost:8000/api/agent/restart",
            json={"port": 8000}
        )
        print(response.json())
        ```

    **Returns:**
        - success: True if restarted
        - pid: New process ID
        - status: "restarted"
        - dashboard_url: URL to web dashboard
        - was_running: Whether Atom was running before restart
        - message: Success message

    **Raises:**
        - 500: If restart fails
    """
    try:
        was_running = DaemonManager.is_running()

        if was_running:
            DaemonManager.stop_daemon()

        # Wait for clean shutdown
        import time
        time.sleep(2)

        pid = DaemonManager.start_daemon(
            port=request.port,
            host=request.host,
            workers=request.workers,
            host_mount=request.host_mount,
            dev=request.dev
        )

        return RestartAgentResponse(
            success=True,
            pid=pid,
            status="restarted",
            dashboard_url=f"http://{request.host}:{request.port}",
            was_running=was_running,
            message="Atom OS restarted successfully"
        )

    except Exception as e:
        raise HTTPException(status_code=500, detail=str(e))


@router.get("/status", response_model=AgentStatusResponse)
async def get_status():
    """Get Atom OS status and running info.

    Returns current status, PID, uptime, memory usage, and CPU.

    **Example:**
        ```python
        import requests

        response = requests.get("http://localhost:8000/api/agent/status")
        print(response.json())
        ```

    **Returns:**
        - success: True
        - status: Dict with running status, pid, uptime_seconds, memory_mb, cpu_percent

    **Example Response:**
        ```json
        {
            "success": true,
            "status": {
                "running": true,
                "pid": 12345,
                "uptime_seconds": 3600,
                "memory_mb": 256.5,
                "cpu_percent": 5.2,
                "status": "running"
            }
        }
        ```
    """
    try:
        status_info = DaemonManager.get_status()

        return AgentStatusResponse(
            success=True,
            status=status_info
        )

    except Exception as e:
        raise HTTPException(status_code=500, detail=str(e))


@router.post("/execute", response_model=ExecuteCommandResponse)
async def execute_atom_command(
    request: ExecuteCommandRequest,
    current_user: User = Depends(get_super_admin)
):
    """Execute single Atom command and return result (super_admin only).

    **SECURITY**: Requires super_admin authentication to prevent unauthorized
    command execution. This endpoint executes arbitrary Atom commands.

    Useful for one-off tasks from other agents. Starts Atom temporarily,
    executes command, and shuts down.

    **Note:** Command routing not yet fully implemented.
    Use POST /api/agent/start to run Atom as service instead.

    **Example:**
        ```python
        import requests

        response = requests.post(
            "http://localhost:8000/api/agent/execute",
            json={"command": "agent.chat('Hello, create a report')"}
        )
        print(response.json())
        ```

    **Returns:**
        - success: True
        - result: Command execution result (when implemented)
        - note: Implementation status message

    **Note:**
        This endpoint is currently a placeholder. Use daemon mode for
        full Atom functionality:

        ```bash
        # Start as service
        atom-os daemon

        # Or via API
        curl -X POST http://localhost:8000/api/agent/start
        ```
    """
    return ExecuteCommandResponse(
        success=True,
        result="Command execution not yet implemented",
        note="Use POST /api/agent/start to run Atom as service instead"
    )


@router.get("/{chain_id}/bottlenecks")
async def analyze_chain_bottlenecks(
    chain_id: str,
    db: Session = Depends(get_db),
    # For Upstream, we restrict this to admins as it reveals internal telemetry
    current_user: User = Depends(get_super_admin)
):
    """
    Perform diagnostic analysis to identify bottlenecks in the delegation chain.
    
    RESTRICTED: Super Admin only.
    """
    # 1. Verify chain existence
    chain = db.query(DelegationChain).filter(DelegationChain.id == chain_id).first()
    if not chain:
        raise HTTPException(
            status_code=404,
            detail="Delegation chain not found"
        )

    # 2. Run analysis
    from analytics.fleet_optimization_service import FleetOptimizationService
    service = FleetOptimizationService(db)
    report = service.analyze_bottlenecks(chain_id)

    return {
        "chain_id": chain_id,
        "report": report,
        "summary": {
            "total_issues": len(report),
            "critical_issues": len([r for r in report if r["severity"] == "critical"]),
            "warnings": len([r for r in report if r["severity"] == "warning"])
        }
    }

@router.get("/fleet/health")
async def get_fleet_health_summary(
    db: Session = Depends(get_db),
    current_user: User = Depends(get_super_admin)
):
    """
    Get fleet-wide health metrics for the supervisor dashboard.
    
    RESTRICTED: Super Admin only.
    """
    from analytics.fleet_optimization_service import FleetOptimizationService
    service = FleetOptimizationService(db)
    # Scoped to the current admin's tenant if applicable
    tenant_id = getattr(current_user, "tenant_id", None)
    return service.get_fleet_health_summary(tenant_id)