Implement Phase 4: tools, God Mode, and missing features
Backend: - Add Tavily web search tool wrapper (tools/web_search.py) - Add PDF reader + ChromaDB vector store tool (tools/pdf_reader.py) - Bind tools to LLM calls via .bind_tools() in dynamic_graph_builder - Implement God Mode using LangGraph interrupt_before + MemorySaver - Add approve/reject/modify API endpoints for God Mode - Add PDF upload endpoint with ingestion pipeline - Add persistent run history (CouncilRun model + run_service + API) - Add Alembic migration for council_runs table - Enhance WebSocket to emit run_paused and run_resumed events - Add tests for tools, God Mode, and run history Frontend: - Add God Mode approval UI (GodModePanel component) - Add Auto-Pilot / God Mode toggle in Konferenzzimmer - Add functional PDF upload handler - Add Conditional Edge editor (EdgeSettingsPanel component) - Add edge click selection in ArchitectCanvas - Update Zustand store with edge selection and update actions - Update types for God Mode, execution modes, and WS events - Update API client with God Mode, PDF upload, and blueprint run endpoints - Update WebSocket hook for paused/resumed events - Add Vitest config and frontend tests (store, parser, types, API) https://claude.ai/code/session_017U6idFgaqnYTXzPxA7mxMv
This commit is contained in:
parent
c6d0c4a636
commit
001649a364
31 changed files with 2502 additions and 81 deletions
|
|
@ -2,20 +2,22 @@
|
|||
WebSocket endpoint for real-time agent status updates.
|
||||
|
||||
Clients connect to /ws/council/{run_id} and receive JSON events whenever
|
||||
an agent node becomes active. This powers the live diagram pulsing in the frontend.
|
||||
an agent node becomes active or the run status changes.
|
||||
|
||||
Event format:
|
||||
{"event": "node_start", "run_id": "...", "node": "master_agent", "iteration": 2}
|
||||
{"event": "node_complete", "run_id": "...", "node": "critic_agent", "score": 6.5}
|
||||
{"event": "run_complete", "run_id": "...", "final_draft": "..."}
|
||||
{"event": "node_active", "run_id": "...", "node": "master_agent", "iteration": 2}
|
||||
{"event": "run_paused", "run_id": "...", "next_nodes": ["critic_agent"], "current_draft": "..."}
|
||||
{"event": "run_complete", "run_id": "...", "final_draft": "...", "critic_score": 8.5}
|
||||
{"event": "run_failed", "run_id": "...", "error": "..."}
|
||||
"""
|
||||
|
||||
import asyncio
|
||||
import json
|
||||
|
||||
from fastapi import APIRouter, WebSocket, WebSocketDisconnect
|
||||
|
||||
from api.run_store import run_store
|
||||
from services.dynamic_graph_builder import get_god_mode_state
|
||||
|
||||
|
||||
ws_router = APIRouter()
|
||||
|
|
@ -53,6 +55,7 @@ async def council_websocket(websocket: WebSocket, run_id: str):
|
|||
|
||||
On connect: sends the current run status immediately.
|
||||
While running: polls the run store and pushes status changes.
|
||||
On paused: sends a god mode pause event with next_nodes.
|
||||
On complete/failed: sends a final event and closes the connection.
|
||||
"""
|
||||
await websocket.accept()
|
||||
|
|
@ -77,13 +80,17 @@ async def council_websocket(websocket: WebSocket, run_id: str):
|
|||
|
||||
# Poll for status changes and push updates
|
||||
last_node = None
|
||||
last_status = run["status"]
|
||||
while True:
|
||||
run = run_store.get(run_id)
|
||||
if run is None:
|
||||
break
|
||||
|
||||
current_node = run.get("active_node")
|
||||
if current_node and current_node != last_node:
|
||||
current_status = run["status"]
|
||||
|
||||
# Emit node_active when the active agent changes
|
||||
if current_node and current_node != last_node and current_node != "done":
|
||||
await websocket.send_text(
|
||||
json.dumps({
|
||||
"event": "node_active",
|
||||
|
|
@ -94,7 +101,41 @@ async def council_websocket(websocket: WebSocket, run_id: str):
|
|||
)
|
||||
last_node = current_node
|
||||
|
||||
if run["status"] == "completed":
|
||||
# God Mode: emit pause event
|
||||
if current_status == "paused" and last_status != "paused":
|
||||
god_state = get_god_mode_state(run_id)
|
||||
await websocket.send_text(
|
||||
json.dumps({
|
||||
"event": "run_paused",
|
||||
"run_id": run_id,
|
||||
"next_nodes": god_state["next_nodes"] if god_state else [],
|
||||
"current_draft": (
|
||||
god_state["current_state"].get("current_draft", "")
|
||||
if god_state else ""
|
||||
),
|
||||
"critic_score": (
|
||||
god_state["current_state"].get("critic_score")
|
||||
if god_state else None
|
||||
),
|
||||
"iteration_count": (
|
||||
god_state["current_state"].get("iteration_count")
|
||||
if god_state else None
|
||||
),
|
||||
})
|
||||
)
|
||||
last_status = current_status
|
||||
|
||||
# Status changed from paused back to running (user approved)
|
||||
if current_status == "running" and last_status == "paused":
|
||||
await websocket.send_text(
|
||||
json.dumps({
|
||||
"event": "run_resumed",
|
||||
"run_id": run_id,
|
||||
})
|
||||
)
|
||||
last_status = current_status
|
||||
|
||||
if current_status == "completed":
|
||||
await websocket.send_text(
|
||||
json.dumps({
|
||||
"event": "run_complete",
|
||||
|
|
@ -106,7 +147,7 @@ async def council_websocket(websocket: WebSocket, run_id: str):
|
|||
)
|
||||
break
|
||||
|
||||
if run["status"] == "failed":
|
||||
if current_status == "failed":
|
||||
await websocket.send_text(
|
||||
json.dumps({
|
||||
"event": "run_failed",
|
||||
|
|
@ -116,6 +157,7 @@ async def council_websocket(websocket: WebSocket, run_id: str):
|
|||
)
|
||||
break
|
||||
|
||||
last_status = current_status
|
||||
await asyncio.sleep(0.5) # 500ms polling interval
|
||||
|
||||
except WebSocketDisconnect:
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue