Remote deploy console: stream agent compose up to the browser (0.23.0)
Extends the live deploy console to remote/agent stacks. New agent WS endpoint
`/agent/ws/deploy/{stack_id}` runs `compose up -d` and streams its output; the
central app proxies it through `/ws/agent-deploy/{agent_id}/{stack_id}` (same
pattern + token URL-encoding as the agent-logs proxy) and records an
`agent.stack.start` audit entry. The editor's remote Deploy path now opens the
DeployConsole (agentId) instead of the blocking `agentsApi.action(start)`.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 4.8
parent
7592085ce9
commit
d46a6c3576
+33
-1
@@ -62,7 +62,7 @@ def _map_docker(exc: DockerError):
|
||||
raise HTTPException(status_code=code, detail=exc.detail or exc.error)
|
||||
raise exc # falls through to the global 502 DockerError handler
|
||||
|
||||
AGENT_VERSION = "0.22.0"
|
||||
AGENT_VERSION = "0.23.0"
|
||||
|
||||
|
||||
# --------------------------------------------------------------------------- #
|
||||
@@ -640,6 +640,38 @@ async def ws_logs(websocket: WebSocket, stack_id: str, token: str | None = Query
|
||||
pass
|
||||
|
||||
|
||||
@app.websocket("/agent/ws/deploy/{stack_id}")
|
||||
async def ws_deploy(websocket: WebSocket, stack_id: str, token: str | None = Query(default=None)):
|
||||
"""Run `docker compose up -d` and stream its output to the central app so the
|
||||
browser sees deploy progress live (token via query param)."""
|
||||
await websocket.accept()
|
||||
expected = settings.AGENT_TOKEN
|
||||
if not expected or token != expected:
|
||||
await websocket.close(code=4401)
|
||||
return
|
||||
if not os.path.isdir(compose_service.stack_dir(stack_id)):
|
||||
await websocket.send_text(json.dumps({"type": "error", "detail": "stack not found"}))
|
||||
await websocket.close()
|
||||
return
|
||||
compose_service.mark_busy(stack_id)
|
||||
try:
|
||||
async for kind, payload in compose_service.stream_up(stack_id):
|
||||
if kind == "log":
|
||||
await websocket.send_text(json.dumps({"type": "log", "line": payload}))
|
||||
else:
|
||||
await websocket.send_text(json.dumps({"type": "done", "returncode": payload}))
|
||||
except WebSocketDisconnect:
|
||||
# Browser navigated away; the compose subprocess keeps running.
|
||||
pass
|
||||
except Exception as exc: # noqa: BLE001
|
||||
try:
|
||||
await websocket.send_text(json.dumps({"type": "error", "detail": str(exc)}))
|
||||
except Exception: # noqa: BLE001
|
||||
pass
|
||||
finally:
|
||||
compose_service.clear_busy(stack_id)
|
||||
|
||||
|
||||
@app.get("/agent/health")
|
||||
def health() -> dict:
|
||||
return {"status": "ok"}
|
||||
|
||||
Reference in New Issue
Block a user