diff --git a/.gitignore b/.gitignore index bcbeb2b0e..8dd8d997a 100644 --- a/.gitignore +++ b/.gitignore @@ -52,6 +52,9 @@ datasUFO # Ignore mkdocs build output documents/site/ +# Ignore frontend environment files with auto-generated content +galaxy/webui/frontend/.env.development.local + # Ignore config files with sensitive data (API keys) config/*/agents.yaml config/*/agent.yaml diff --git a/README.md b/README.md index 21ad7f9d6..486843469 100644 --- a/README.md +++ b/README.md @@ -22,7 +22,7 @@  [](https://opensource.org/licenses/MIT) [](https://microsoft.github.io/UFO/) -[](https://www.youtube.com/watch?v=QT_OhygMVXU) +[](https://www.youtube.com/watch?v=NGrVWGcJL8o) diff --git a/README_ZH.md b/README_ZH.md index 0db72437f..07b6969a1 100644 --- a/README_ZH.md +++ b/README_ZH.md @@ -21,7 +21,7 @@  [](https://opensource.org/licenses/MIT) [](https://microsoft.github.io/UFO/) -[](https://www.youtube.com/watch?v=QT_OhygMVXU) +[](https://www.youtube.com/watch?v=NGrVWGcJL8o)
diff --git a/documents/docs/aip/endpoints.md b/documents/docs/aip/endpoints.md
index 22238c043..7e9dfcd8e 100644
--- a/documents/docs/aip/endpoints.md
+++ b/documents/docs/aip/endpoints.md
@@ -1,7 +1,6 @@
# AIP Endpoints
-!!!quote "Complete Client/Server Implementations"
- Endpoints combine protocol, transport, and resilience components to provide production-ready AIP communication for servers, clients, and orchestrators.
+Endpoints combine protocol, transport, and resilience components to provide production-ready AIP communication for servers, clients, and orchestrators.
## Endpoint Types at a Glance
@@ -38,15 +37,14 @@ graph TB
The dashed arrows indicate capabilities that the base class provides to all subclasses. This inheritance design ensures consistent behavior across all endpoint types while allowing specialization for server, client, and orchestrator roles.
-!!!info "Base Endpoint Components"
- All endpoints inherit from `AIPEndpoint`, which provides:
-
- - **Protocol**: Message serialization and handling
- - **Reconnection Strategy**: Automatic reconnection with backoff
- - **Timeout Manager**: Operation timeout management
- - **Session Handlers**: Per-session state tracking
+**Base Endpoint Components:**
----
+All endpoints inherit from `AIPEndpoint`, which provides:
+
+- **Protocol**: Message serialization and handling
+- **Reconnection Strategy**: Automatic reconnection with backoff
+- **Timeout Manager**: Operation timeout management
+- **Session Handlers**: Per-session state tracking
## Base Endpoint: AIPEndpoint
@@ -83,8 +81,7 @@ await endpoint.stop()
## DeviceServerEndpoint
-!!!tip "Server-Side Device Management"
- Wraps UFO's server-side WebSocket handler with AIP protocol support for managing multiple device connections simultaneously.
+Wraps UFO's server-side WebSocket handler with AIP protocol support for managing multiple device connections simultaneously.
### Configuration
@@ -92,9 +89,9 @@ await endpoint.stop()
from aip.endpoints import DeviceServerEndpoint
endpoint = DeviceServerEndpoint(
- client_manager=client_manager, # WebSocket connection manager
- session_manager=session_manager, # Session state manager
- local=False # Local vs remote deployment
+ ws_manager=ws_manager, # WebSocket connection manager
+ session_manager=session_manager, # Session state manager
+ local=False # Local vs remote deployment
)
```
@@ -105,7 +102,7 @@ from fastapi import FastAPI, WebSocket
from aip.endpoints import DeviceServerEndpoint
app = FastAPI()
-endpoint = DeviceServerEndpoint(client_manager, session_manager)
+endpoint = DeviceServerEndpoint(ws_manager, session_manager)
@app.websocket("/ws")
async def websocket_route(websocket: WebSocket):
@@ -122,8 +119,9 @@ async def websocket_route(websocket: WebSocket):
| **Result Aggregation** | Collect and format execution results | Unified response handling |
| **Auto Task Cancellation** | Cancel tasks on disconnect | Prevent orphaned tasks |
-!!!success "Backward Compatibility"
- The Device Server Endpoint maintains full compatibility with UFO's existing WebSocket handler.
+**Backward Compatibility:**
+
+The Device Server Endpoint maintains full compatibility with UFO's existing WebSocket handler.
### Task Cancellation on Disconnection
@@ -139,8 +137,7 @@ await endpoint.cancel_device_tasks(
## DeviceClientEndpoint
-!!!tip "Client-Side Device Operations"
- Wraps UFO's client-side WebSocket client with AIP protocol support, automatic reconnection, and heartbeat management.
+Wraps UFO's client-side WebSocket client with AIP protocol support, automatic reconnection, and heartbeat management.
### Configuration
@@ -159,7 +156,7 @@ endpoint = DeviceClientEndpoint(
| Feature | Default Behavior | Configuration |
|---------|------------------|---------------|
-| **Heartbeat** | Starts on connection | 20s interval (configurable) |
+| **Heartbeat** | Starts on connection | 20s interval (fixed) |
| **Reconnection** | Exponential backoff | `max_retries=3`, `initial_backoff=2.0` |
| **Message Routing** | Auto-routes to UFO client | Handled internally |
| **Connection Management** | Auto-connect on start | Transparent to user |
@@ -199,8 +196,7 @@ endpoint = DeviceClientEndpoint(
## ConstellationEndpoint
-!!!tip "Orchestrator-Side Multi-Device Coordination"
- Enables the ConstellationClient to communicate with multiple devices simultaneously, managing connections, tasks, and queries.
+Enables the ConstellationClient to communicate with multiple devices simultaneously, managing connections, tasks, and queries.
### Configuration
@@ -234,6 +230,8 @@ connection = await endpoint.connect_to_device(
)
```
+Learn more about [AgentProfile configuration](../galaxy/client/device_manager.md) in the Galaxy documentation.
+
### Sending Tasks
```python
@@ -398,8 +396,8 @@ await endpoint.stop()
## Resilience Features
-!!!success "Built-In Resilience"
- All endpoints include automatic reconnection, timeout management, and heartbeat monitoring.
+!!!warning "Built-In Resilience"
+ All endpoints include automatic reconnection, timeout management, and heartbeat monitoring for production reliability.
### Resilience Configuration
@@ -484,12 +482,13 @@ class CustomEndpoint(DeviceClientEndpoint):
## Best Practices
-!!!tip "Endpoint Selection"
- | Use Case | Endpoint Type |
- |----------|---------------|
- | Device agent server | `DeviceServerEndpoint` |
- | Device agent client | `DeviceClientEndpoint` |
- | Multi-device orchestrator | `ConstellationEndpoint` |
+**Endpoint Selection:**
+
+| Use Case | Endpoint Type |
+|----------|---------------|
+| Device agent server | `DeviceServerEndpoint` |
+| Device agent client | `DeviceClientEndpoint` |
+| Multi-device orchestrator | `ConstellationEndpoint` |
!!!warning "Configuration Guidelines"
- **Set appropriate timeouts** based on deployment environment
@@ -539,4 +538,7 @@ from aip.endpoints import (
- [Resilience](./resilience.md) - Reconnection and heartbeat management
- [Messages](./messages.md) - Message types and validation
- [Overview](./overview.md) - System architecture and design
+- [Galaxy Client](../galaxy/client/overview.md) - Multi-device orchestration with ConstellationClient
+- [UFO Server](../server/websocket_handler.md) - WebSocket server implementation
+- [UFO Client](../client/websocket_client.md) - WebSocket client implementation
diff --git a/documents/docs/aip/messages.md b/documents/docs/aip/messages.md
index fc63bf735..5c5067615 100644
--- a/documents/docs/aip/messages.md
+++ b/documents/docs/aip/messages.md
@@ -1,7 +1,6 @@
# AIP Message Reference
-!!!quote "Strongly-Typed Communication"
- AIP uses **Pydantic-based messages** for automatic validation, serialization, and type safety. All messages transmit as JSON over WebSocket.
+AIP uses **Pydantic-based messages** for automatic validation, serialization, and type safety. All messages transmit as JSON over WebSocket.
## Message Overview
@@ -59,8 +58,7 @@ Unidirectional arrows indicate request-response patterns, while bidirectional ar
## Core Data Structures
-!!!info "Foundation Types"
- These Pydantic models form the building blocks for all AIP messages.
+These Pydantic models form the building blocks for all AIP messages.
### Essential Types Summary
@@ -105,8 +103,7 @@ control = ControlInfo(
)
```
- ConstellationEditor — Safe, interactive, and reversible constellation manipulation Task Constellation — The formal foundation of distributed workflow orchestration TaskConstellation — Orchestrating distributed workflows across the digital galaxy TaskStar — The atomic building block of distributed workflows TaskStarLine — Connecting tasks with intelligent dependency logic Add Device Modal - Register new device agents through the UI ➕ Add Device Modal - Register new device agents through the WebUI Loading... {if(w.indexOf(v)!==-1)return;const k=h.get(v);if(!k)return;const N=g.indexOf(v);let S=x;if(!S){const A=()=>{f.delete(v);const P=Array.from(h.keys()).filter(D=>!w.includes(D));if(P.forEach(D=>h.delete(D)),d.current=u.filter(D=>{const C=zr(D);return C===v||P.includes(C)}),!f.size){if(l.current===!1)return;a(),r&&r()}};S=T.createElement(Lc,{key:zr(k),isPresent:!1,onExitComplete:A,custom:t,presenceAffectsLayout:s,mode:o},k),f.set(v,S)}c.splice(N,0,S)}),c=c.map(x=>{const v=x.key;return f.has(v)?x:T.createElement(Lc,{key:zr(x),isPresent:!0,presenceAffectsLayout:s,mode:o},x)}),T.createElement(T.Fragment,null,f.size?c:c.map(x=>T.cloneElement(x)))},Ij=e=>{try{return new Intl.DateTimeFormat("en-US",{hour:"2-digit",minute:"2-digit",second:"2-digit"}).format(e)}catch{return""}},jj=e=>{if(!e)return"bg-slate-500/30 text-slate-200 border border-white/10";const t=e.toLowerCase();return["finish","completed","success","ready"].some(n=>t.includes(n))?"bg-emerald-500/20 text-emerald-200 border border-emerald-400/30":["fail","error"].some(n=>t.includes(n))?"bg-rose-500/20 text-rose-200 border border-rose-400/30":["continue","running","in_progress"].some(n=>t.includes(n))?"bg-amber-500/20 text-amber-100 border border-amber-400/30":"bg-slate-500/30 text-slate-200 border border-white/10"},Dr=({title:e,icon:t,children:n})=>y.jsxs("div",{className:"rounded-2xl border border-white/10 bg-white/5 p-4 text-sm text-slate-200",children:[y.jsxs("div",{className:"mb-2 flex items-center gap-2 text-[12px] uppercase tracking-[0.18em] text-slate-400",children:[y.jsx("span",{className:"inline-flex h-7 w-7 items-center justify-center rounded-full bg-white/10 text-slate-200 shadow-[0_0_12px_rgba(33,240,255,0.25)]",children:t}),e]}),y.jsx("div",{className:"space-y-2 whitespace-pre-wrap text-sm leading-relaxed text-slate-200",children:n})]}),Lj=e=>{const t=e==null?void 0:e.function,n=(e==null?void 0:e.arguments)||{};if(!t)return(e==null?void 0:e.action)||(e==null?void 0:e.command)||"Unknown Action";switch(t){case"add_task":{const r=n.task_id||"?",i=n.name||"";return i?`Add Task: '${r}' (${i})`:`Add Task: '${r}'`}case"remove_task":return`Remove Task: '${n.task_id||"?"}'`;case"update_task":{const r=n.task_id||"?",i=Object.keys(n).filter(o=>o!=="task_id"&&n[o]!==null&&n[o]!==void 0),s=i.length>0?i.join(", "):"fields";return`Update Task: '${r}' (${s})`}case"add_dependency":{const r=n.dependency_id||"?",i=n.from_task_id||"?",s=n.to_task_id||"?";return`Add Dependency (ID ${r}): ${i} → ${s}`}case"remove_dependency":return`Remove Dependency: '${n.dependency_id||"?"}'`;case"update_dependency":return`Update Dependency: '${n.dependency_id||"?"}'`;case"build_constellation":{const r=n.config||{};if(n.task_count!==void 0||n.dependency_count!==void 0){const i=n.task_count||0,s=n.dependency_count||0;return`Build Constellation (${i} tasks, ${s} dependencies)`}if(typeof r=="object"&&r!==null){const i=Array.isArray(r.tasks)?r.tasks.length:0,s=Array.isArray(r.dependencies)?r.dependencies.length:0;return`Build Constellation (${i} tasks, ${s} dependencies)`}return"Build Constellation"}case"clear_constellation":return"Clear Constellation (remove all tasks)";case"load_constellation":{const r=n.file_path||"?";return`Load Constellation from '${r.split(/[/\\]/).pop()||r}'`}case"save_constellation":{const r=n.file_path||"?";return`Save Constellation to '${r.split(/[/\\]/).pop()||r}'`}default:{const r=Object.entries(n).slice(0,2);if(r.length>0){const i=r.map(([s,o])=>`${s}=${o}`).join(", ");return`${t}(${i})`}return t}}},Rj=e=>{if(!e)return y.jsx(ic,{className:"h-3.5 w-3.5"});const t=e.toLowerCase();return["finish","completed","success","ready"].some(n=>t.includes(n))?y.jsx(Kr,{className:"h-3.5 w-3.5"}):["fail","error"].some(n=>t.includes(n))?y.jsx(Oo,{className:"h-3.5 w-3.5"}):["continue","running","in_progress"].some(n=>t.includes(n))?y.jsx(ic,{className:"h-3.5 w-3.5"}):y.jsx(ic,{className:"h-3.5 w-3.5"})},zj=({action:e,isLast:t,isExpanded:n,onToggle:r})=>{var u,c,f,d;const i=((u=e==null?void 0:e.result)==null?void 0:u.status)||(e==null?void 0:e.status)||((c=e==null?void 0:e.arguments)==null?void 0:c.status),s=((f=e==null?void 0:e.result)==null?void 0:f.error)||((d=e==null?void 0:e.result)==null?void 0:d.message),o=i&&String(i).toLowerCase()==="continue",a=Lj(e),l=()=>{if(!i)return"text-slate-400";const h=i.toLowerCase();return["finish","completed","success","ready"].some(m=>h.includes(m))?"text-emerald-400":["fail","error"].some(m=>h.includes(m))?"text-rose-400":["continue","running","in_progress"].some(m=>h.includes(m))?"text-amber-400":"text-slate-400"};return y.jsxs("div",{className:"relative",children:[y.jsxs("div",{className:"absolute left-0 top-0 flex h-full w-6",children:[y.jsx("div",{className:"w-px bg-white/10"}),!t&&y.jsx("div",{className:"absolute left-0 top-7 h-[calc(100%-1.75rem)] w-px bg-white/10"})]}),y.jsx("div",{className:"ml-6 pb-3",children:y.jsxs("div",{className:"flex items-start gap-2",children:[y.jsx("div",{className:"mt-3 h-px w-3 flex-shrink-0 bg-white/10"}),y.jsxs("div",{className:"flex-1 min-w-0",children:[y.jsxs("button",{onClick:r,className:"group flex w-full items-center gap-2 rounded-lg border border-white/5 bg-white/5 px-3 py-2 text-left text-sm transition hover:border-white/20 hover:bg-white/10",children:[y.jsx("span",{className:we("flex-shrink-0",l()),children:Rj(i)}),y.jsx("span",{className:"flex-1 truncate font-medium text-slate-200",children:a}),!o&&(e.arguments||s)&&y.jsx(Vf,{className:we("h-3.5 w-3.5 flex-shrink-0 text-slate-400 transition-transform",n&&"rotate-180")})]}),n&&!o&&y.jsxs("div",{className:"mt-2 space-y-2 rounded-lg border border-white/5 bg-black/20 p-3",children:[i&&y.jsxs("div",{children:[y.jsx("div",{className:"mb-1 text-[10px] uppercase tracking-wider text-slate-400",children:"Status"}),y.jsx("div",{className:we("text-sm font-medium",l()),children:String(i).toUpperCase()})]}),e.arguments&&y.jsxs("div",{children:[y.jsx("div",{className:"mb-1 text-[10px] uppercase tracking-wider text-slate-400",children:"Arguments"}),y.jsx("pre",{className:"whitespace-pre-wrap rounded-lg border border-white/5 bg-black/30 p-2 text-xs text-slate-300",children:JSON.stringify(e.arguments,null,2)})]}),y.jsxs("div",{children:[y.jsx("div",{className:"mb-1 text-[10px] uppercase tracking-wider text-slate-400",children:"Full Action Object (Debug)"}),y.jsx("pre",{className:"whitespace-pre-wrap rounded-lg border border-white/5 bg-black/30 p-2 text-xs text-slate-300",children:JSON.stringify(e,null,2)})]}),s&&y.jsxs("div",{className:"rounded-lg border border-rose-400/30 bg-rose-500/10 p-2",children:[y.jsx("div",{className:"mb-1 text-[10px] uppercase tracking-wider text-rose-300",children:"Error"}),y.jsx("div",{className:"text-xs text-rose-100",children:String(s)})]})]})]})]})})]})},Fj=({message:e,nextMessage:t,stepNumber:n})=>{const[r,i]=T.useState(!1),[s,o]=T.useState(!1),[a,l]=T.useState(new Set),u=e.role==="user",c=e.kind==="action",f=e.kind==="response"?e.payload:void 0,d=!!e.payload&&(c||e.kind==="system"),h=T.useMemo(()=>Ij(e.timestamp),[e.timestamp]),m=T.useMemo(()=>u?"You":e.agentName?e.agentName.toLowerCase().includes("constellation")?"UFO":e.agentName:"UFO",[u,e.agentName]),g=f==null?void 0:f.status,w=e.kind==="response"&&(t==null?void 0:t.kind)==="action",p=w?t==null?void 0:t.payload:void 0;if(c)return null;const x=()=>{e.payload&&sn().send({type:"replay_action",timestamp:Date.now(),payload:e.payload})};return y.jsxs("div",{className:we("flex w-full flex-col gap-2 transition-all",{"items-end":u,"items-start":!u}),children:[y.jsxs("div",{className:we("w-[88%] rounded-3xl border px-6 py-5 shadow-xl sm:w-[74%]",u?"rounded-br-xl border-galaxy-blue/50 bg-gradient-to-br from-galaxy-blue/25 via-galaxy-purple/25 to-galaxy-blue/15 text-slate-50 shadow-[0_0_30px_rgba(15,123,255,0.2),inset_0_1px_0_rgba(147,197,253,0.15)]":"rounded-bl-xl border-[rgba(10,186,181,0.35)] bg-gradient-to-br from-[rgba(10,186,181,0.12)] via-[rgba(12,50,65,0.8)] to-[rgba(11,30,45,0.85)] text-slate-100 shadow-[0_0_25px_rgba(10,186,181,0.18),inset_0_1px_0_rgba(10,186,181,0.12)]"),children:[!u&&y.jsxs("div",{className:"mb-4 flex items-center justify-between gap-3",children:[y.jsxs("div",{className:"flex items-center gap-3",children:[y.jsx("div",{className:"flex h-9 w-9 items-center justify-center rounded-xl bg-gradient-to-br from-cyan-500/20 to-blue-500/20 border border-cyan-400/30 shadow-lg",children:y.jsx(XC,{className:"h-5 w-5 text-cyan-300","aria-hidden":!0})}),y.jsxs("div",{className:"flex flex-col gap-0.5",children:[y.jsxs("div",{className:"flex items-center gap-2",children:[y.jsx("span",{className:"font-bold text-base text-slate-100",children:m}),n!==void 0&&y.jsxs("span",{className:"inline-flex items-center gap-1 rounded-full bg-gradient-to-r from-cyan-500/20 to-blue-500/20 border border-cyan-400/30 px-2 py-0.5 text-[10px] font-semibold text-cyan-300",children:[y.jsx("span",{className:"opacity-70",children:"STEP"}),y.jsx("span",{children:n})]})]}),y.jsx("span",{className:"text-[10px] text-slate-400",children:h})]})]}),y.jsxs("div",{className:"flex flex-wrap items-center gap-2",children:[y.jsxs("span",{className:"inline-flex items-center gap-1.5 rounded-lg border border-white/10 bg-white/5 px-2.5 py-1 text-[10px] font-medium uppercase tracking-wider text-slate-300",children:[y.jsx($f,{className:"h-3 w-3","aria-hidden":!0}),e.kind]}),g&&y.jsxs("span",{className:we("inline-flex items-center gap-1.5 rounded-lg px-2.5 py-1 text-[10px] font-medium uppercase tracking-wider",jj(g)),children:[y.jsx(Kr,{className:"h-3 w-3","aria-hidden":!0}),String(g).toUpperCase()]})]})]}),u&&y.jsx("div",{className:"mb-4 flex items-center justify-between gap-3",children:y.jsxs("div",{className:"flex items-center gap-3",children:[y.jsx("div",{className:"flex h-9 w-9 items-center justify-center rounded-xl bg-gradient-to-br from-purple-500/20 to-pink-500/20 border border-purple-400/30 shadow-lg",children:y.jsx(qC,{className:"h-5 w-5 text-purple-300","aria-hidden":!0})}),y.jsxs("div",{className:"flex flex-col gap-0.5",children:[y.jsx("span",{className:"font-bold text-base text-slate-100",children:m}),y.jsx("span",{className:"text-[10px] text-slate-400",children:h})]})]})}),e.kind==="response"&&f?y.jsxs("div",{className:"space-y-4",children:[f.thought&&y.jsx(Dr,{title:"Thought",icon:y.jsx(TC,{className:"h-3.5 w-3.5","aria-hidden":!0}),children:(()=>{const v=String(f.thought),k=100;if(!(v.length>k))return y.jsx("p",{children:v});let S=k;const A=[". ",`.
-`,"! ",`!
-`,"? ",`?
-`];for(const P of A){const D=v.lastIndexOf(P,k);if(D>k*.7){S=D+P.length;break}}return y.jsxs("div",{children:[y.jsx("p",{children:s?v:v.substring(0,S).trim()+"..."}),y.jsx("button",{onClick:()=>o(!s),className:"mt-2 inline-flex items-center gap-1 rounded-full border border-white/10 bg-white/5 px-3 py-1 text-xs text-slate-300 transition hover:border-white/30 hover:bg-white/10",children:s?y.jsxs(y.Fragment,{children:[y.jsx(Ym,{className:"h-3 w-3","aria-hidden":!0}),"Show less"]}):y.jsxs(y.Fragment,{children:[y.jsx(Vf,{className:"h-3 w-3","aria-hidden":!0}),"Show more (",v.length," chars)"]})})]})})()}),f.plan&&y.jsx(Dr,{title:"Plan",icon:y.jsx(RC,{className:"h-3.5 w-3.5","aria-hidden":!0}),children:Array.isArray(f.plan)?y.jsx("ul",{className:"space-y-1 text-sm",children:f.plan.map((v,k)=>y.jsxs("li",{className:"flex items-start gap-2 text-slate-200",children:[y.jsx("span",{className:"mt-[2px] h-2 w-2 rounded-full bg-galaxy-blue","aria-hidden":!0}),y.jsx("span",{children:v})]},k))}):y.jsx("p",{children:f.plan})}),f.decomposition_strategy&&y.jsx(Dr,{title:"Decomposition",icon:y.jsx(PC,{className:"h-3.5 w-3.5","aria-hidden":!0}),children:y.jsx("p",{children:f.decomposition_strategy})}),f.ask_details&&y.jsx(Dr,{title:"Ask Details",icon:y.jsx(uv,{className:"h-3.5 w-3.5","aria-hidden":!0}),children:y.jsx("pre",{className:"whitespace-pre-wrap text-xs text-slate-200/90",children:JSON.stringify(f.ask_details,null,2)})}),f.actions_summary&&y.jsx(Dr,{title:"Action Summary",icon:y.jsx($f,{className:"h-3.5 w-3.5","aria-hidden":!0}),children:y.jsx("p",{children:f.actions_summary})}),(f.response||f.final_response)&&y.jsx(Dr,{title:"Response",icon:y.jsx(Kr,{className:"h-3.5 w-3.5","aria-hidden":!0}),children:y.jsx("p",{children:f.final_response||f.response})}),f.validation&&y.jsx(Dr,{title:"Validation",icon:y.jsx(CC,{className:"h-3.5 w-3.5","aria-hidden":!0}),children:y.jsx("pre",{className:"whitespace-pre-wrap text-xs text-slate-200/90",children:JSON.stringify(f.validation,null,2)})}),!(f.thought||f.plan||f.actions_summary||f.response||f.final_response)&&y.jsx("div",{className:"prose prose-invert max-w-none text-sm leading-relaxed prose-headings:text-slate-100 prose-p:mb-3 prose-p:text-slate-200 prose-pre:bg-slate-900/80 prose-strong:text-slate-100",children:y.jsx($g,{remarkPlugins:[Gg],children:e.content})}),f.results&&g&&String(g).toLowerCase()!=="continue"&&y.jsxs("div",{className:we("mt-6 rounded-2xl border-2 p-6 shadow-xl",String(g).toLowerCase().includes("fail")||String(g).toLowerCase().includes("error")?"border-rose-500/50 bg-gradient-to-br from-rose-500/15 to-rose-600/8":"border-emerald-500/50 bg-gradient-to-br from-emerald-500/15 to-emerald-600/8"),children:[y.jsxs("div",{className:"mb-4 flex items-center gap-3",children:[y.jsx("div",{className:we("flex h-10 w-10 items-center justify-center rounded-xl shadow-lg",String(g).toLowerCase().includes("fail")||String(g).toLowerCase().includes("error")?"bg-gradient-to-br from-rose-500/35 to-rose-600/25 border border-rose-400/40":"bg-gradient-to-br from-emerald-500/35 to-emerald-600/25 border border-emerald-400/40"),children:String(g).toLowerCase().includes("fail")||String(g).toLowerCase().includes("error")?y.jsx(Oo,{className:"h-5 w-5 text-rose-300","aria-hidden":!0}):y.jsx(Kr,{className:"h-5 w-5 text-emerald-300","aria-hidden":!0})}),y.jsxs("div",{children:[y.jsx("h3",{className:we("text-base font-bold uppercase tracking-wider",String(g).toLowerCase().includes("fail")||String(g).toLowerCase().includes("error")?"text-rose-200":"text-emerald-200"),children:"Final Results"}),y.jsxs("p",{className:"text-xs text-slate-400 mt-0.5",children:["Status: ",String(g).toUpperCase()]})]})]}),y.jsx("div",{className:we("rounded-xl border p-4",String(g).toLowerCase().includes("fail")||String(g).toLowerCase().includes("error")?"border-rose-400/20 bg-rose-950/30":"border-emerald-400/20 bg-emerald-950/30"),children:typeof f.results=="string"?y.jsx("div",{className:we("whitespace-pre-wrap text-sm leading-relaxed",String(g).toLowerCase().includes("fail")||String(g).toLowerCase().includes("error")?"text-rose-100/90":"text-emerald-100/90"),children:f.results}):y.jsx("pre",{className:we("whitespace-pre-wrap text-sm leading-relaxed",String(g).toLowerCase().includes("fail")||String(g).toLowerCase().includes("error")?"text-rose-100/90":"text-emerald-100/90"),children:JSON.stringify(f.results,null,2)})})]})]}):y.jsx("div",{className:"prose prose-invert max-w-none text-sm leading-relaxed prose-headings:text-slate-100 prose-p:mb-3 prose-p:text-slate-200 prose-pre:bg-slate-900/80 prose-strong:text-slate-100",children:y.jsx($g,{remarkPlugins:[Gg],children:e.content})}),(d||c)&&y.jsxs("div",{className:"mt-5 flex items-center gap-3 text-xs text-slate-300",children:[c&&y.jsxs("button",{type:"button",onClick:x,className:"inline-flex items-center gap-2 rounded-full border border-white/10 bg-white/5 px-3 py-1 transition hover:border-white/30 hover:bg-white/10",children:[y.jsx(lv,{className:"h-3 w-3","aria-hidden":!0}),"Replay"]}),d&&y.jsxs("button",{type:"button",onClick:()=>i(v=>!v),className:"inline-flex items-center gap-1 rounded-full border border-white/10 bg-white/5 px-3 py-1 transition hover:border-white/30 hover:bg-white/10",children:[r?"Hide JSON":"View JSON",r?y.jsx(Ym,{className:"h-3 w-3","aria-hidden":!0}):y.jsx(Vf,{className:"h-3 w-3","aria-hidden":!0})]})]}),y.jsx(qk,{initial:!1,children:d&&r&&y.jsx(Wk.pre,{initial:{height:0,opacity:0},animate:{height:"auto",opacity:1},exit:{height:0,opacity:0},transition:{duration:.2},className:"mt-3 max-h-80 overflow-auto rounded-xl border border-white/10 bg-black/40 p-4 text-xs text-cyan-100",children:JSON.stringify(e.payload,null,2)})})]}),w&&p&&Array.isArray(p.actions)&&p.actions.length>0&&y.jsx("div",{className:"ml-12 w-[calc(88%-3rem)] sm:w-[calc(74%-3rem)]",children:p.actions.map((v,k)=>y.jsx(zj,{action:v,index:k,isLast:k===p.actions.length-1,isExpanded:a.has(k),onToggle:()=>{const N=new Set(a);N.has(k)?N.delete(k):N.add(k),l(N)}},k))})]})},Oj=[{label:"/reset",description:"Reset the current session state."},{label:"/replay",description:"Start next session and replay last request."}],Vj=()=>{const[e,t]=T.useState(""),[n,r]=T.useState(!1),{connected:i,session:s,ui:o,toggleComposerShortcuts:a,resetSessionState:l,messages:u,setTaskRunning:c,stopCurrentTask:f}=Ce(g=>({connected:g.connected,session:g.session,ui:g.ui,toggleComposerShortcuts:g.toggleComposerShortcuts,resetSessionState:g.resetSessionState,messages:g.messages,setTaskRunning:g.setTaskRunning,stopCurrentTask:g.stopCurrentTask})),d=T.useCallback(g=>{switch(g){case"/reset":return sn().sendReset(),l(),!0;case"/replay":{const w=[...u].reverse().find(p=>p.role==="user");return w?(sn().send({type:"next_session",timestamp:Date.now()}),l(),setTimeout(()=>{sn().sendRequest(w.content);const p=Ce.getState(),x=p.ensureSession(s.id,s.displayName),v=es();p.addMessage({id:v,sessionId:x,role:"user",kind:"user",author:"You",content:w.content,timestamp:Date.now(),status:"sent"})},500),!0):(console.warn("No previous user message to replay"),!0)}default:return!1}},[l,u,s.id,s.displayName]),h=T.useCallback(async()=>{const g=e.trim();if(!g||!i)return;if(g.startsWith("/")&&d(g.toLowerCase())){t("");return}const w=Ce.getState(),p=w.ensureSession(s.id,s.displayName),x=es();if(w.addMessage({id:x,sessionId:p,role:"user",kind:"user",author:"You",content:g,timestamp:Date.now(),status:"sent"}),Object.keys(w.constellations).length>0){const k=`temp-${Date.now()}`;w.upsertConstellation({id:k,name:"Loading...",status:"pending",description:"Waiting for constellation to be created...",taskIds:[],dag:{nodes:[],edges:[]},statistics:{total:0,pending:0,running:0,completed:0,failed:0},createdAt:Date.now()}),w.setActiveConstellation(k),console.log("📊 Created temporary constellation for new request")}r(!0),c(!0);try{sn().sendRequest(g)}catch(k){console.error("Failed to send request",k),w.updateMessage(x,{status:"error"}),c(!1)}finally{t(""),r(!1)}},[i,e,d,s.displayName,s.id,c]),m=g=>{if(o.isTaskRunning){g.key==="Enter"&&g.preventDefault();return}g.key==="Enter"&&!g.shiftKey&&(g.preventDefault(),h())};return y.jsx("div",{className:"relative rounded-[30px] border border-white/10 bg-gradient-to-br from-[rgba(11,24,44,0.82)] to-[rgba(8,15,28,0.75)] p-4 shadow-[0_8px_32px_rgba(0,0,0,0.4),0_2px_8px_rgba(15,123,255,0.12),inset_0_1px_1px_rgba(255,255,255,0.06)] ring-1 ring-inset ring-white/5",children:y.jsxs("div",{className:"relative",children:[y.jsx("textarea",{value:e,onChange:g=>t(g.target.value),onKeyDown:m,placeholder:i?"Ask Galaxy to orchestrate a new mission…":"Waiting for connection…",rows:3,className:"w-full resize-none rounded-3xl border border-white/5 bg-black/40 px-5 py-4 text-sm text-slate-100 placeholder:text-slate-500 shadow-[inset_0_2px_8px_rgba(0,0,0,0.3)] focus:border-white/15 focus:outline-none focus:ring-1 focus:ring-white/10 focus:shadow-[0_0_8px_rgba(15,123,255,0.08),inset_0_2px_8px_rgba(0,0,0,0.3)]",disabled:!i||n||o.isTaskRunning}),y.jsxs("div",{className:"mt-3 flex items-center justify-between gap-2 text-xs text-slate-400",children:[y.jsxs("div",{className:"flex items-center gap-2",children:[y.jsxs("button",{type:"button",onClick:()=>a(),className:"inline-flex items-center gap-2 rounded-full border border-white/10 px-3 py-1 hover:border-white/30",children:[y.jsx(KC,{className:"h-3 w-3","aria-hidden":!0}),"Shortcuts"]}),o.showComposerShortcuts&&y.jsx(y.Fragment,{children:Oj.map(g=>y.jsx("button",{type:"button",onClick:()=>{t(g.label),a()},title:g.description,className:"rounded-full border border-white/10 bg-black/30 px-3 py-1 text-xs font-medium text-slate-200 transition hover:border-white/30 hover:bg-black/40",children:g.label},g.label))})]}),y.jsx("button",{type:"button",onClick:o.isTaskRunning?f:h,disabled:!i||!o.isTaskRunning&&e.trim().length===0||n,className:we("inline-flex items-center gap-2 rounded-full px-4 py-2 text-sm font-semibold text-white transition-all duration-300",o.isTaskRunning?"bg-gradient-to-br from-[rgba(80,20,30,0.75)] via-[rgba(100,25,35,0.70)] to-[rgba(80,20,30,0.75)] hover:from-[rgba(100,25,35,0.85)] hover:via-[rgba(120,30,40,0.80)] hover:to-[rgba(100,25,35,0.85)] border border-rose-900/40 hover:border-rose-800/50 shadow-[0_0_16px_rgba(139,0,0,0.25),0_4px_12px_rgba(0,0,0,0.4),inset_0_1px_1px_rgba(255,255,255,0.08)]":"bg-gradient-to-br from-[rgba(6,182,212,0.85)] via-[rgba(147,51,234,0.80)] to-[rgba(236,72,153,0.85)] hover:from-[rgba(6,182,212,0.95)] hover:via-[rgba(147,51,234,0.90)] hover:to-[rgba(236,72,153,0.95)] border border-cyan-400/30 hover:border-purple-400/40 shadow-[0_0_20px_rgba(6,182,212,0.3),0_0_30px_rgba(147,51,234,0.2),0_4px_16px_rgba(0,0,0,0.3),inset_0_1px_2px_rgba(255,255,255,0.15),inset_0_-1px_2px_rgba(0,0,0,0.2)] active:scale-95 active:shadow-[0_0_15px_rgba(6,182,212,0.4),0_2px_8px_rgba(0,0,0,0.4)]",(!i||!o.isTaskRunning&&e.trim().length===0||n)&&"opacity-50 grayscale"),children:n?y.jsxs(y.Fragment,{children:[y.jsx(ou,{className:"h-4 w-4 animate-spin","aria-hidden":!0}),"Sending"]}):o.isTaskRunning?y.jsxs(y.Fragment,{children:[y.jsx(UC,{className:"h-4 w-4","aria-hidden":!0}),"Stop"]}):y.jsxs(y.Fragment,{children:[y.jsx(VC,{className:"h-4 w-4","aria-hidden":!0}),"Launch"]})})]})]})})},$j=(e,t,n)=>{const r=t.toLowerCase().trim();return e.filter(i=>n==="all"||i.kind===n?r?[i.content,i.agentName,i.role].filter(Boolean).map(a=>String(a).toLowerCase()).join(" ").includes(r):!0:!1)},Bj=()=>{const{messages:e,searchQuery:t,messageKind:n,isTaskStopped:r}=Ce(l=>({messages:l.messages,searchQuery:l.ui.searchQuery,messageKind:l.ui.messageKindFilter,isTaskStopped:l.ui.isTaskStopped}),Oe),i=T.useRef(null),s=T.useMemo(()=>$j(e,t,n),[e,n,t]),o=T.useMemo(()=>{const l=new Map;let u=0;return s.forEach(c=>{c.role==="user"?u=0:c.kind!=="action"&&(u++,l.set(c.id,u))}),l},[s]),a=T.useMemo(()=>{var u,c,f;if(e.length===0)return!1;const l=e[e.length-1];if(l.role==="user"||l.role==="assistant"&&l.kind==="action")return!0;if(l.role==="assistant"&&l.kind==="response"){const d=String(((u=l.payload)==null?void 0:u.status)||((f=(c=l.payload)==null?void 0:c.result)==null?void 0:f.status)||"").toLowerCase();if(d==="continue"||d==="running"||d==="pending"||d==="")return!0}return!1},[e]);return T.useEffect(()=>{i.current&&i.current.scrollTo({top:i.current.scrollHeight,behavior:"smooth"})},[s.length]),y.jsxs("div",{className:"flex h-full min-h-0 flex-col gap-4",children:[y.jsx(NE,{}),y.jsx("div",{ref:i,className:"flex-1 overflow-y-auto rounded-[28px] border border-white/10 bg-gradient-to-br from-[rgba(11,30,45,0.88)] via-[rgba(8,20,35,0.85)] to-[rgba(6,15,28,0.88)] p-6 shadow-[0_8px_32px_rgba(0,0,0,0.4),0_2px_8px_rgba(15,123,255,0.15),inset_0_1px_1px_rgba(255,255,255,0.08)] ring-1 ring-inset ring-white/5",children:y.jsx("div",{className:"flex flex-col gap-5",children:s.length===0?y.jsxs("div",{className:"flex h-full flex-col items-center justify-center gap-3 text-center text-slate-400",children:[y.jsx("span",{className:"text-3xl",children:"✨"}),y.jsx("p",{className:"max-w-sm text-sm",children:"Ready to launch. Describe a mission for the Galaxy Agent, or use quick commands below to explore diagnostics."})]}):y.jsxs(y.Fragment,{children:[s.map((l,u)=>y.jsx(Fj,{message:l,nextMessage:s[u+1],stepNumber:o.get(l.id)},l.id)),a&&!r&&y.jsxs("div",{className:"ml-14 flex items-center gap-2 rounded-xl border border-cyan-500/30 bg-gradient-to-r from-cyan-950/30 to-blue-950/20 px-4 py-2.5 shadow-[0_0_20px_rgba(6,182,212,0.15)]",children:[y.jsx(ou,{className:"h-3.5 w-3.5 animate-spin text-cyan-400"}),y.jsx("span",{className:"text-xs font-medium text-cyan-300/90",children:"UFO is thinking..."})]}),r&&y.jsxs("div",{className:"ml-14 flex items-center gap-2 rounded-xl border border-purple-400/20 bg-gradient-to-r from-purple-950/20 to-indigo-950/15 px-4 py-2.5 shadow-[0_0_16px_rgba(147,51,234,0.08)]",children:[y.jsx("div",{className:"h-2 w-2 rounded-full bg-purple-300/80 animate-pulse"}),y.jsx("span",{className:"text-xs font-medium text-purple-200/80",children:"Task stopped by user. Ready for new mission."})]})]})})}),y.jsx(Vj,{})]})},Hj=()=>{const{session:e,resetSessionState:t}=Ce(i=>({session:i.session,resetSessionState:i.resetSessionState})),n=()=>{sn().sendReset(),t()},r=()=>{sn().send({type:"next_session",timestamp:Date.now()}),t()};return y.jsxs("div",{className:"flex flex-col gap-4 rounded-[28px] border border-white/10 bg-gradient-to-br from-[rgba(11,30,45,0.88)] via-[rgba(8,20,35,0.85)] to-[rgba(6,15,28,0.88)] p-5 text-sm text-slate-100 shadow-[0_8px_32px_rgba(0,0,0,0.4),0_2px_8px_rgba(6,182,212,0.12),inset_0_1px_1px_rgba(255,255,255,0.08)] ring-1 ring-inset ring-white/5",children:[y.jsx("div",{className:"flex items-start justify-start",children:y.jsxs("div",{className:"flex items-center gap-2",children:[y.jsx($f,{className:"h-5 w-5 text-cyan-400 drop-shadow-[0_0_8px_rgba(6,182,212,0.5)]","aria-hidden":!0}),y.jsx("div",{className:"font-heading text-xl font-semibold tracking-tight text-white",children:e.displayName})]})}),y.jsxs("div",{className:"grid grid-cols-1 gap-3",children:[y.jsxs("button",{type:"button",onClick:n,className:"flex items-center gap-3 rounded-2xl border border-[rgba(10,186,181,0.4)] bg-gradient-to-r from-[rgba(10,186,181,0.15)] to-[rgba(6,182,212,0.15)] px-4 py-3 shadow-[0_4px_16px_rgba(0,0,0,0.25),0_0_15px_rgba(10,186,181,0.2),inset_0_1px_2px_rgba(255,255,255,0.1)] transition-all duration-200 hover:border-[rgba(10,186,181,0.6)] hover:from-[rgba(10,186,181,0.25)] hover:to-[rgba(6,182,212,0.25)] hover:shadow-[0_8px_24px_rgba(0,0,0,0.3),0_0_25px_rgba(10,186,181,0.3)]",children:[y.jsx(lv,{className:"h-4 w-4 text-[rgb(10,186,181)]","aria-hidden":!0}),y.jsxs("div",{className:"text-left",children:[y.jsx("div",{className:"text-sm font-medium text-white",children:"Reset Session"}),y.jsx("div",{className:"text-xs text-slate-400",children:"Clear chat, tasks, and devices"})]})]}),y.jsxs("button",{type:"button",onClick:r,className:"flex items-center gap-3 rounded-2xl border border-emerald-400/40 bg-gradient-to-r from-emerald-500/15 to-cyan-500/15 px-4 py-3 shadow-[0_4px_16px_rgba(0,0,0,0.25),0_0_15px_rgba(16,185,129,0.2),inset_0_1px_2px_rgba(255,255,255,0.1)] transition-all duration-200 hover:border-emerald-400/60 hover:from-emerald-500/25 hover:to-cyan-500/25 hover:shadow-[0_8px_24px_rgba(0,0,0,0.3),0_0_25px_rgba(16,185,129,0.3)]",children:[y.jsx(uv,{className:"h-4 w-4 text-emerald-300","aria-hidden":!0}),y.jsxs("div",{className:"text-left",children:[y.jsx("div",{className:"text-sm font-medium text-white",children:"Next Session"}),y.jsx("div",{className:"text-xs text-slate-400",children:"Launch with a fresh constellation"})]})]})]})]})},J0={connected:{label:"Connected",dot:"bg-emerald-400",text:"text-emerald-300"},idle:{label:"Idle",dot:"bg-cyan-400",text:"text-cyan-200"},busy:{label:"Busy",dot:"bg-amber-400",text:"text-amber-200"},connecting:{label:"Connecting",dot:"bg-blue-400",text:"text-blue-200"},failed:{label:"Failed",dot:"bg-rose-500",text:"text-rose-200"},disconnected:{label:"Disconnected",dot:"bg-slate-500",text:"text-slate-300"},offline:{label:"Offline",dot:"bg-slate-600",text:"text-slate-400"},unknown:{label:"Unknown",dot:"bg-slate-600",text:"text-slate-400"}},Uj=e=>{if(!e)return"No heartbeat yet";const t=Date.parse(e);if(Number.isNaN(t))return e;const n=Date.now()-t;if(n<6e4)return"Just now";const r=Math.round(n/6e4);return r<60?`${r} min ago`:`${Math.round(r/60)} hr ago`},Wj=({device:e})=>{const t=J0[e.status]||J0.unknown,n=e.highlightUntil&&e.highlightUntil>Date.now();return y.jsxs("div",{className:we("group rounded-2xl border bg-gradient-to-br p-4 text-xs transition-all duration-300","border-white/20 from-[rgba(25,40,60,0.75)] via-[rgba(20,35,52,0.7)] to-[rgba(15,28,45,0.75)]","shadow-[0_4px_16px_rgba(0,0,0,0.3),0_0_8px_rgba(15,123,255,0.1),inset_0_1px_2px_rgba(255,255,255,0.1),inset_0_0_20px_rgba(15,123,255,0.03)]","hover:border-white/35 hover:from-[rgba(28,45,65,0.85)] hover:via-[rgba(23,38,56,0.8)] hover:to-[rgba(18,30,48,0.85)]","hover:shadow-[0_8px_24px_rgba(0,0,0,0.35),0_0_20px_rgba(15,123,255,0.2),0_0_30px_rgba(6,182,212,0.15),inset_0_1px_2px_rgba(255,255,255,0.15),inset_0_0_30px_rgba(15,123,255,0.06)]","hover:translate-y-[-2px]",n&&"border-cyan-400/50 from-[rgba(6,182,212,0.2)] via-[rgba(15,123,255,0.15)] to-[rgba(15,28,45,0.8)] shadow-[0_0_30px_rgba(6,182,212,0.4),0_0_40px_rgba(6,182,212,0.25),0_4px_16px_rgba(0,0,0,0.3),inset_0_0_30px_rgba(6,182,212,0.1)]"),children:[y.jsxs("div",{className:"flex items-start justify-between gap-3",children:[y.jsxs("div",{children:[y.jsx("div",{className:"font-mono text-sm text-white drop-shadow-[0_1px_4px_rgba(0,0,0,0.5)]",children:e.name}),y.jsxs("div",{className:"mt-1 flex items-center gap-2",children:[y.jsx("span",{className:we("h-2 w-2 rounded-full shadow-[0_0_6px_currentColor]",t.dot),"aria-hidden":!0}),y.jsx("span",{className:we("text-[11px] uppercase tracking-[0.2em]",t.text),children:t.label}),e.os&&y.jsxs(y.Fragment,{children:[y.jsx("span",{className:"text-slate-600",children:"|"}),y.jsx("span",{className:"rounded-full border border-indigo-400/30 bg-indigo-500/20 px-2 py-0.5 text-[10px] font-medium uppercase tracking-[0.15em] text-indigo-300 shadow-[0_0_8px_rgba(99,102,241,0.2),inset_0_1px_1px_rgba(255,255,255,0.1)]",children:e.os})]})]})]}),y.jsx(MC,{className:"h-4 w-4 text-slate-400 transition-all group-hover:text-cyan-400 group-hover:drop-shadow-[0_0_6px_rgba(6,182,212,0.5)]","aria-hidden":!0})]}),y.jsxs("div",{className:"mt-3 grid gap-2 text-[11px] text-slate-300",children:[e.capabilities&&e.capabilities.length>0&&y.jsxs("div",{children:["Capabilities: ",e.capabilities.join(", ")]}),y.jsxs("div",{className:"flex items-center gap-2 text-slate-400",children:[y.jsx(su,{className:"h-3 w-3","aria-hidden":!0}),Uj(e.lastHeartbeat)]}),e.metadata&&e.metadata.region&&y.jsxs("div",{children:["Region: ",e.metadata.region]})]})]})},Yj=()=>{const{devices:e}=Ce(o=>({devices:o.devices}),Oe),[t,n]=T.useState(""),r=T.useMemo(()=>{const o=Object.values(e);if(!t)return o;const a=t.toLowerCase();return o.filter(l=>{var u;return[l.name,l.id,l.os,(u=l.metadata)==null?void 0:u.region].filter(Boolean).map(c=>String(c).toLowerCase()).some(c=>c.includes(a))})},[e,t]),i=r.length,s=r.filter(o=>o.status==="connected"||o.status==="idle"||o.status==="busy").length;return y.jsxs("div",{className:"flex h-full flex-col gap-4 rounded-[28px] border border-white/10 bg-gradient-to-br from-[rgba(11,30,45,0.88)] via-[rgba(8,20,35,0.85)] to-[rgba(6,15,28,0.88)] p-5 text-sm text-slate-100 shadow-[0_8px_32px_rgba(0,0,0,0.4),0_2px_8px_rgba(16,185,129,0.12),inset_0_1px_1px_rgba(255,255,255,0.08)] ring-1 ring-inset ring-white/5",children:[y.jsx("div",{className:"flex items-center justify-between",children:y.jsxs("div",{className:"flex items-center gap-3",children:[y.jsx(NC,{className:"h-5 w-5 text-emerald-400 drop-shadow-[0_0_8px_rgba(16,185,129,0.5)]","aria-hidden":!0}),y.jsx("div",{className:"font-heading text-xl font-semibold tracking-tight text-white",children:"Device Agent"}),y.jsxs("div",{className:"mt-0.5 rounded-lg border border-emerald-400/40 bg-gradient-to-r from-emerald-500/15 to-emerald-600/10 px-2.5 py-1 text-xs font-medium text-emerald-200 shadow-[0_0_15px_rgba(16,185,129,0.2),inset_0_1px_2px_rgba(255,255,255,0.1)]",children:[s,"/",i," online"]})]})}),y.jsxs("div",{className:"flex items-center gap-2 rounded-xl border border-white/5 bg-gradient-to-r from-black/30 to-black/20 px-3 py-2.5 text-xs text-slate-300 shadow-[inset_0_2px_8px_rgba(0,0,0,0.3)] focus-within:border-white/15 focus-within:shadow-[0_0_8px_rgba(16,185,129,0.08),inset_0_2px_8px_rgba(0,0,0,0.3)]",children:[y.jsx(cv,{className:"h-3.5 w-3.5","aria-hidden":!0}),y.jsx("input",{type:"search",value:t,onChange:o=>n(o.target.value),placeholder:"Filter by id, region, or OS",className:"w-full bg-transparent focus:outline-none"})]}),y.jsx("div",{className:"flex-1 space-y-3 overflow-y-auto",children:r.length===0?y.jsxs("div",{className:"flex flex-col items-center gap-2 rounded-2xl border border-dashed border-white/10 bg-white/5 p-6 text-center text-xs text-slate-400",children:[y.jsx(GC,{className:"h-5 w-5","aria-hidden":!0}),"No devices reported yet."]}):r.map(o=>y.jsx(Wj,{device:o},o.id))})]})},ey=()=>y.jsxs("div",{className:"flex h-full w-full flex-col gap-4 overflow-hidden",children:[y.jsx(Hj,{}),y.jsx("div",{className:"flex-1 overflow-y-auto space-y-4 pr-1",children:y.jsx(Yj,{})})]}),qj={info:{icon:y.jsx(jC,{className:"h-4 w-4","aria-hidden":!0}),className:"border-cyan-400/40 bg-cyan-500/20 text-cyan-100"},success:{icon:y.jsx(Kr,{className:"h-4 w-4","aria-hidden":!0}),className:"border-emerald-400/40 bg-emerald-500/20 text-emerald-100"},warning:{icon:y.jsx(Wm,{className:"h-4 w-4","aria-hidden":!0}),className:"border-amber-400/40 bg-amber-500/20 text-amber-100"},error:{icon:y.jsx(Wm,{className:"h-4 w-4","aria-hidden":!0}),className:"border-rose-400/40 bg-rose-500/20 text-rose-100"}},Kj=5e3,Gj=()=>{const{notifications:e,dismissNotification:t,markNotificationRead:n}=Ce(r=>({notifications:r.notifications,dismissNotification:r.dismissNotification,markNotificationRead:r.markNotificationRead}));return T.useEffect(()=>{const r=[];return e.forEach(i=>{const s=setTimeout(()=>{t(i.id)},Kj);r.push(s)}),()=>{r.forEach(i=>clearTimeout(i))}},[e,t]),y.jsx("div",{className:"pointer-events-none fixed bottom-6 left-6 z-50 flex w-80 flex-col gap-3",children:y.jsx(qk,{children:e.map(r=>{const i=qj[r.severity];return y.jsxs(Wk.div,{initial:{y:20,opacity:0},animate:{y:0,opacity:1},exit:{y:10,opacity:0},transition:{duration:.2},className:we("pointer-events-auto relative rounded-2xl border px-4 py-3 shadow-lg",i.className),onMouseEnter:()=>n(r.id),children:[y.jsx("button",{type:"button",className:"absolute right-2 top-2 rounded-full border border-white/20 p-1 text-slate-200 transition hover:bg-white/10",onClick:()=>t(r.id),children:y.jsx(Bf,{className:"h-3 w-3","aria-hidden":!0})}),y.jsxs("div",{className:"flex items-start gap-3 pr-6",children:[y.jsx("div",{className:"mt-1 flex-shrink-0",children:i.icon}),y.jsxs("div",{className:"flex-1 min-w-0 text-xs",children:[y.jsx("div",{className:"font-semibold text-white break-words",children:r.title}),r.description&&y.jsx("div",{className:"mt-1 text-[11px] text-slate-200/80 break-words",children:r.description}),y.jsxs("div",{className:"mt-2 flex items-center justify-between text-[10px] uppercase tracking-[0.18em] text-slate-300/70",children:[y.jsx("span",{className:"truncate",children:r.source||"system"}),y.jsx("span",{className:"flex-shrink-0 ml-2",children:new Date(r.timestamp).toLocaleTimeString()})]})]})]})]},r.id)})})})};function gt(e){if(typeof e=="string"||typeof e=="number")return""+e;let t="";if(Array.isArray(e))for(let n=0,r;n >8&15|t>>4&240,t>>4&15|t&240,(t&15)<<4|t&15,1):n===8?ba(t>>24&255,t>>16&255,t>>8&255,(t&255)/255):n===4?ba(t>>12&15|t>>8&240,t>>8&15|t>>4&240,t>>4&15|t&240,((t&15)<<4|t&15)/255):null):(t=HR.exec(e))?new kt(t[1],t[2],t[3],1):(t=UR.exec(e))?new kt(t[1]*255/100,t[2]*255/100,t[3]*255/100,1):(t=WR.exec(e))?ba(t[1],t[2],t[3],t[4]):(t=YR.exec(e))?ba(t[1]*255/100,t[2]*255/100,t[3]*255/100,t[4]):(t=qR.exec(e))?cy(t[1],t[2]/100,t[3]/100,1):(t=KR.exec(e))?cy(t[1],t[2]/100,t[3]/100,t[4]):iy.hasOwnProperty(e)?ay(iy[e]):e==="transparent"?new kt(NaN,NaN,NaN,0):null}function ay(e){return new kt(e>>16&255,e>>8&255,e&255,1)}function ba(e,t,n,r){return r<=0&&(e=t=n=NaN),new kt(e,t,n,r)}function QR(e){return e instanceof Ko||(e=Ao(e)),e?(e=e.rgb(),new kt(e.r,e.g,e.b,e.opacity)):new kt}function gd(e,t,n,r){return arguments.length===1?QR(e):new kt(e,t,n,r??1)}function kt(e,t,n,r){this.r=+e,this.g=+t,this.b=+n,this.opacity=+r}pp(kt,gd,u2(Ko,{brighter(e){return e=e==null?Ll:Math.pow(Ll,e),new kt(this.r*e,this.g*e,this.b*e,this.opacity)},darker(e){return e=e==null?No:Math.pow(No,e),new kt(this.r*e,this.g*e,this.b*e,this.opacity)},rgb(){return this},clamp(){return new kt(Xr(this.r),Xr(this.g),Xr(this.b),Rl(this.opacity))},displayable(){return-.5<=this.r&&this.r<255.5&&-.5<=this.g&&this.g<255.5&&-.5<=this.b&&this.b<255.5&&0<=this.opacity&&this.opacity<=1},hex:ly,formatHex:ly,formatHex8:ZR,formatRgb:uy,toString:uy}));function ly(){return`#${Ur(this.r)}${Ur(this.g)}${Ur(this.b)}`}function ZR(){return`#${Ur(this.r)}${Ur(this.g)}${Ur(this.b)}${Ur((isNaN(this.opacity)?1:this.opacity)*255)}`}function uy(){const e=Rl(this.opacity);return`${e===1?"rgb(":"rgba("}${Xr(this.r)}, ${Xr(this.g)}, ${Xr(this.b)}${e===1?")":`, ${e})`}`}function Rl(e){return isNaN(e)?1:Math.max(0,Math.min(1,e))}function Xr(e){return Math.max(0,Math.min(255,Math.round(e)||0))}function Ur(e){return e=Xr(e),(e<16?"0":"")+e.toString(16)}function cy(e,t,n,r){return r<=0?e=t=n=NaN:n<=0||n>=1?e=t=NaN:t<=0&&(e=NaN),new rn(e,t,n,r)}function c2(e){if(e instanceof rn)return new rn(e.h,e.s,e.l,e.opacity);if(e instanceof Ko||(e=Ao(e)),!e)return new rn;if(e instanceof rn)return e;e=e.rgb();var t=e.r/255,n=e.g/255,r=e.b/255,i=Math.min(t,n,r),s=Math.max(t,n,r),o=NaN,a=s-i,l=(s+i)/2;return a?(t===s?o=(n-r)/a+(nComplete Field List
+**Complete Field List:**
| Field | Type | Description |
|-------|------|-------------|
@@ -123,8 +120,6 @@ control = ControlInfo(
| `source` | str? | Data source identifier |
| `text_content` | str? | Text content |
-
Exclude from scheduling
Trigger auto-reconnect (G6) | N/A |
| **Reconnection succeeds** | Mark as `CONNECTED`
Resume scheduling | Session restored (G6) |
-| **Disconnect during task** | Mark tasks as `TASK_FAILED`
Propagate to ConstellationAgent
Trigger DAG edit | N/A |
+| **Disconnect during task** | Mark tasks as `FAILED`
Propagate to ConstellationAgent
Trigger DAG edit | N/A |
### ConstellationClient Disconnection
@@ -325,12 +300,9 @@ The `DISCONNECTED` state acts as a quarantine zone where the device is temporari
[→ See resilience implementation](./resilience.md)
----
-
## Extensibility Mechanisms
-!!!tip "Customization Points (G5)"
- AIP provides multiple extension points for domain-specific needs without modifying the core protocol, supporting composable extensibility.
+AIP provides multiple extension points for domain-specific needs without modifying the core protocol, supporting composable extensibility (G5).
### 1. Protocol Middleware
@@ -343,6 +315,10 @@ class AuditMiddleware(ProtocolMiddleware):
async def process_outgoing(self, msg):
log_to_audit_trail(msg)
return msg
+
+ async def process_incoming(self, msg):
+ log_to_audit_trail(msg)
+ return msg
```
### 2. Custom Message Handlers
@@ -359,13 +335,11 @@ Pluggable transport (default: WebSocket) (G5):
```python
from aip.transport import CustomTransport
-protocol.set_transport(CustomTransport(config))
+protocol.transport = CustomTransport(config)
```
[→ See extensibility guide](./protocols.md)
----
-
## Integration with UFO² Ecosystem
| Component | Integration Point | Benefit |
@@ -375,12 +349,14 @@ protocol.set_transport(CustomTransport(config))
| **Configuration System** | Agent endpoints, capabilities managed via UFO² config | Centralized management, type-safe validation |
| **Logging & Monitoring** | Comprehensive logging at all protocol layers | Debugging, performance monitoring, audit trails |
-!!!success "Seamless Ecosystem Integration"
- AIP abstracts network/device heterogeneity, allowing the orchestrator to treat all agents as **first-class citizens** in a single event-driven control plane.
-
----
+AIP abstracts network/device heterogeneity, allowing the orchestrator to treat all agents as **first-class citizens** in a single event-driven control plane.
+**Related Documentation:**
+- [TaskConstellation (DAG orchestrator)](../galaxy/constellation/task_constellation.md)
+- [ConstellationAgent (orchestration agent)](../galaxy/constellation_agent/overview.md)
+- [MCP Integration Guide](../mcp/overview.md)
+- [Configuration System](../configuration/system/system_config.md)
**Next Steps:**
- 📖 [Message Reference](./messages.md) - Complete message type documentation
@@ -389,12 +365,9 @@ protocol.set_transport(CustomTransport(config))
- 🔌 [Endpoints](./endpoints.md) - Endpoint setup and usage patterns
- 🛡️ [Resilience](./resilience.md) - Connection management and fault tolerance
----
-
## Summary
-!!!quote "AIP's Value Proposition"
- AIP transforms distributed workflow execution into a **coherent, safe, and adaptive system** where reasoning and execution converge seamlessly across diverse agents and environments.
+AIP transforms distributed workflow execution into a **coherent, safe, and adaptive system** where reasoning and execution converge seamlessly across diverse agents and environments.
**Key Takeaways:**
diff --git a/documents/docs/aip/protocols.md b/documents/docs/aip/protocols.md
index 22957d364..9ac3d4b77 100644
--- a/documents/docs/aip/protocols.md
+++ b/documents/docs/aip/protocols.md
@@ -1,13 +1,8 @@
# AIP Protocol Reference
-!!!quote "Specialized Protocol Layers"
- AIP's protocol stack provides specialized handlers for registration, task execution, commands, heartbeat, and device info—each optimized for its specific lifecycle phase.
-
## Protocol Stack Overview
-**Layered Protocol Architecture:**
-
-AIP uses a three-layer architecture where specialized protocols handle domain-specific concerns, the core protocol manages message processing, and the transport layer provides network communication:
+AIP uses a three-layer architecture where specialized protocols handle domain-specific concerns, the core protocol manages message processing, and the transport layer provides network communication.
```mermaid
graph TB
@@ -59,8 +54,7 @@ This layered design enables clean separation of concerns: specialized protocols
## Core Protocol: AIPProtocol
-!!!info "Foundation Layer"
- `AIPProtocol` provides transport-agnostic message handling with middleware support and automatic serialization.
+`AIPProtocol` provides transport-agnostic message handling with middleware support and automatic serialization.
### Quick Start
@@ -84,8 +78,7 @@ protocol = AIPProtocol(transport)
### Middleware Pipeline
-!!!tip "Extensible Processing"
- Add middleware for logging, authentication, metrics, or custom transformations.
+Add middleware for logging, authentication, metrics, or custom transformations.
```python
from aip.protocol.base import ProtocolMiddleware
@@ -126,13 +119,10 @@ await protocol.dispatch_message(server_msg)
## RegistrationProtocol {#registration-protocol}
-!!!success "Agent Onboarding"
- Handles initial registration and capability advertisement when agents join the constellation.
+Handles initial registration and capability advertisement when agents join the constellation.
### Registration Flow
-**Registration Handshake Sequence:**
-
The following diagram shows the two-way handshake for device registration, including validation and acknowledgment:
```mermaid
@@ -178,6 +168,8 @@ success = await reg_protocol.register_as_device(
- `timestamp`: Registration time (ISO 8601)
- `client_type`: Set to `ClientType.DEVICE`
+[→ See ClientType and ClientMessage in Message Reference](./messages.md)
+
### Constellation Registration
**Orchestrator Registration:**
@@ -207,13 +199,10 @@ success = await reg_protocol.register_as_constellation(
## TaskExecutionProtocol {#task-execution-protocol}
-!!!info "Multi-Turn Task Orchestration"
- Manages the complete task lifecycle: assignment → command execution → result reporting → completion.
+Manages the complete task lifecycle: assignment → command execution → result reporting → completion.
### Task Lifecycle
-**Task State Machine:**
-
This state diagram shows the complete task execution lifecycle, including the multi-turn command loop where agents can request additional commands before completion:
```mermaid
@@ -265,8 +254,7 @@ await task_protocol.send_task_assignment(
### Server → Client: Command Dispatch
-!!!tip "Batch Commands"
- Send multiple commands in one message to reduce network overhead.
+Send multiple commands in one message to reduce network overhead.
**Method 1: Using ServerMessage**
@@ -324,6 +312,8 @@ await task_protocol.send_command_results(
)
```
+[→ See Result and ResultStatus definitions in Message Reference](./messages.md)
+
### Task Completion
**Server → Client: Success**
@@ -353,8 +343,6 @@ await task_protocol.send_task_end(
### Complete Task Flow
-**End-to-End Multi-Turn Task Execution:**
-
This comprehensive sequence diagram shows the complete flow from task request to completion, including the multi-turn command loop where the agent iteratively executes commands and requests follow-up actions:
```mermaid
@@ -390,8 +378,7 @@ The loop in the middle represents iterative task execution where the agent can p
## CommandProtocol
-!!!info "Command Validation Layer"
- Provides validation utilities for commands and results before transmission.
+Provides validation utilities for commands and results before transmission.
### Validation Methods
@@ -424,20 +411,17 @@ if cmd_protocol.validate_results(results):
await task_protocol.send_command_results(results, ...)
```
-!!!warning "Always Validate"
- Validation catches protocol errors early, preventing runtime failures and debugging headaches.
+!!!warning "Validation Best Practice"
+ Always validate commands and results before transmission to catch protocol errors early and prevent runtime failures.
---
## HeartbeatProtocol {#heartbeat-protocol}
-!!!success "Connection Health Monitoring"
- Periodic keepalive messages detect broken connections and network issues.
+Periodic keepalive messages detect broken connections and network issues.
### Heartbeat Flow
-**Periodic Keepalive Mechanism:**
-
The heartbeat protocol uses a simple ping-pong pattern to verify connection health at regular intervals:
```mermaid
@@ -465,15 +449,14 @@ heartbeat_protocol = HeartbeatProtocol(transport)
await heartbeat_protocol.send_heartbeat(
client_id="windows_agent_001",
- session_id="session_123" # Optional
+ metadata={"custom_info": "value"} # Optional
)
```
### Server-Side Response
```python
-await heartbeat_protocol.send_heartbeat_response(
- session_id="session_123",
+await heartbeat_protocol.send_heartbeat_ack(
response_id="resp_hb_001"
)
```
@@ -487,13 +470,10 @@ await heartbeat_protocol.send_heartbeat_response(
## DeviceInfoProtocol
-!!!info "Telemetry Exchange"
- Request and report device hardware/software information for informed scheduling.
+Request and report device hardware/software information for informed scheduling.
### Info Request Flow
-**On-Demand Telemetry Collection:**
-
The server can request fresh device information at any time to make informed scheduling decisions:
```mermaid
@@ -508,7 +488,7 @@ sequenceDiagram
This pull-based telemetry model allows the orchestrator to query device capabilities on-demand (e.g., before assigning a GPU-intensive task) rather than relying on stale registration data.
-### Server → Client: Request Info
+### Constellation → Server: Request Info
```python
from aip.protocol import DeviceInfoProtocol
@@ -516,12 +496,15 @@ from aip.protocol import DeviceInfoProtocol
info_protocol = DeviceInfoProtocol(transport)
await info_protocol.request_device_info(
- device_id="windows_agent_001",
+ constellation_id="orchestrator_001",
+ target_device="windows_agent_001",
request_id="req_info_001"
)
```
-### Client → Server: Provide Info
+### Server → Client: Provide Info
+
+The server responds with device information (or an error if collection failed):
```python
device_info = {
@@ -537,11 +520,13 @@ device_info = {
await info_protocol.send_device_info_response(
device_info=device_info,
request_id="req_info_001",
- client_id="windows_agent_001"
+ error=None # Set to error message string if info collection failed
)
```
-!!!success "Use Cases"
+### Use Cases
+
+!!!success "Device-Aware Task Scheduling"
- **GPU-aware scheduling**: Check GPU availability before assigning vision tasks
- **Load balancing**: Distribute tasks based on CPU/RAM usage
- **Health monitoring**: Track device status over time
@@ -552,10 +537,7 @@ await info_protocol.send_device_info_response(
### Multi-Turn Conversations
-!!!tip "Correlation Chain"
- Use `prev_response_id` to maintain conversation context across multiple exchanges.
-
-**Request-Response Correlation Pattern:**
+Use `prev_response_id` to maintain conversation context across multiple exchanges.
This diagram shows how messages are chained together using `prev_response_id` to maintain conversation context:
@@ -587,8 +569,7 @@ await protocol.send_message(ClientMessage(
### Session-Based Communication
-!!!info "Session Grouping"
- All messages in a task share the same `session_id` for traceability.
+All messages in a task share the same `session_id` for traceability.
```python
SESSION_ID = "session_abc123"
@@ -627,31 +608,35 @@ await protocol.send_error(
## Best Practices
-!!!success "Protocol Selection"
- **Use specialized protocols** instead of manually constructing messages with `AIPProtocol`.
-
- | Task | Protocol |
- |------|----------|
- | Agent registration | `RegistrationProtocol` |
- | Task execution | `TaskExecutionProtocol` |
- | Command validation | `CommandProtocol` |
- | Keepalive | `HeartbeatProtocol` |
- | Device telemetry | `DeviceInfoProtocol` |
-
-!!!warning "Validation"
- - Always validate commands/results before transmission
- - Use `MessageValidator` for message integrity checks
- - Catch validation errors early
-
-!!!info "Session Management"
- - **Always set `session_id`** for task-related messages
- - Use **correlation IDs** (`prev_response_id`) for multi-turn conversations
- - **Generate unique IDs** with `uuid.uuid4()`
-
-!!!tip "Error Handling"
- - **Distinguish** protocol errors (connection) from application errors (task failure)
- - **Propagate errors** explicitly through error messages
- - **Leverage middleware** for cross-cutting concerns (logging, metrics, auth)
+### Protocol Selection
+
+Use specialized protocols instead of manually constructing messages with `AIPProtocol`.
+
+| Task | Protocol |
+|------|----------|
+| Agent registration | `RegistrationProtocol` |
+| Task execution | `TaskExecutionProtocol` |
+| Command validation | `CommandProtocol` |
+| Keepalive | `HeartbeatProtocol` |
+| Device telemetry | `DeviceInfoProtocol` |
+
+### Validation
+
+- Always validate commands/results before transmission
+- Use `MessageValidator` for message integrity checks
+- Catch validation errors early
+
+### Session Management
+
+- **Always set `session_id`** for task-related messages
+- Use **correlation IDs** (`prev_response_id`) for multi-turn conversations
+- **Generate unique IDs** with `uuid.uuid4()`
+
+### Error Handling
+
+- **Distinguish** protocol errors (connection) from application errors (task failure)
+- **Propagate errors** explicitly through error messages
+- **Leverage middleware** for cross-cutting concerns (logging, metrics, auth)
!!!danger "Resource Cleanup"
Always close protocols when done to release transport resources.
diff --git a/documents/docs/aip/resilience.md b/documents/docs/aip/resilience.md
index b393586f1..f38136cd1 100644
--- a/documents/docs/aip/resilience.md
+++ b/documents/docs/aip/resilience.md
@@ -1,7 +1,6 @@
# AIP Resilience
-!!!quote "Fault-Tolerant Communication"
- AIP's resilience layer ensures stable communication and consistent orchestration across distributed agent constellations through automatic reconnection, heartbeat monitoring, and timeout management.
+AIP's resilience layer ensures stable communication and consistent orchestration across distributed agent constellations through automatic reconnection, heartbeat monitoring, and timeout management.
## Resilience Components
@@ -16,13 +15,10 @@
## Resilient Connection Protocol
-!!!warning "Connection State Management"
- The Resilient Connection Protocol governs how connection disruptions are detected, handled, and recovered between ConstellationClient and Device Agents.
+The Resilient Connection Protocol governs how connection disruptions are detected, handled, and recovered between ConstellationClient and Device Agents.
### Connection State Diagram
-**Device Connection Lifecycle with Failure Handling:**
-
This state diagram shows how devices transition between connection states and the internal sub-states during disconnection recovery:
```mermaid
@@ -66,15 +62,12 @@ The nested states within `DISCONNECTED` show the cleanup and recovery sequence:
```python
# Automatically called on disconnection
-await device_server.cancel_all_tasks_for_client(client_id)
+await device_server.cancel_device_tasks(client_id, reason="device_disconnected")
```
### ConstellationClient Disconnection
-!!!success "Bidirectional Fault Handling"
- When ConstellationClient disconnects, Device Agent Servers proactively clean up to prevent orphaned tasks.
-
-**Orphaned Task Prevention:**
+When ConstellationClient disconnects, Device Agent Servers proactively clean up to prevent orphaned tasks.
This sequence diagram shows the proactive cleanup sequence when the orchestrator disconnects, ensuring all running tasks are properly aborted:
@@ -106,8 +99,7 @@ The `x` marker on the connection arrow indicates an abnormal termination. The se
## ReconnectionStrategy
-!!!info "Automatic Reconnection"
- Manages reconnection attempts with configurable backoff policies to handle transient network failures.
+Manages reconnection attempts with configurable backoff policies to handle transient network failures.
### Configuration
@@ -123,10 +115,11 @@ strategy = ReconnectionStrategy(
)
```
+[→ See how ReconnectionStrategy is used in endpoints](./endpoints.md)
+
### Backoff Policies
-!!!tip "Choose Based on Network Conditions"
- Select the policy that matches your deployment environment's network characteristics.
+Select the policy that matches your deployment environment's network characteristics.
| Policy | Backoff Pattern | Best For | Example Sequence |
|--------|----------------|----------|------------------|
@@ -140,8 +133,6 @@ strategy = ReconnectionStrategy(
### Reconnection Workflow
-**Automated Reconnection Decision Tree:**
-
This flowchart shows the complete reconnection logic from failure detection through recovery or permanent failure:
```mermaid
@@ -207,8 +198,7 @@ await strategy.handle_disconnection(
## HeartbeatManager {#heartbeat-manager}
-!!!success "Connection Health Monitoring"
- Sends periodic keepalive messages to detect broken connections before they cause failures.
+Sends periodic keepalive messages to detect broken connections before they cause failures.
### Configuration
@@ -223,6 +213,8 @@ heartbeat_manager = HeartbeatManager(
)
```
+[→ See HeartbeatProtocol reference](./protocols.md#heartbeat-protocol)
+
### Lifecycle Management
| Operation | Method | Description |
@@ -255,27 +247,33 @@ await heartbeat_manager.stop_all()
### Heartbeat Loop Internals
+The heartbeat manager automatically sends periodic heartbeats. If the protocol is not connected, it logs a warning and continues the loop:
+
```python
async def _heartbeat_loop(client_id: str, interval: float):
"""Internal heartbeat loop (automatic)"""
- while True:
- await asyncio.sleep(interval)
-
- if protocol.is_connected():
- await protocol.send_heartbeat(client_id)
- else:
- # Connection lost, exit loop
- break
+ try:
+ while True:
+ await asyncio.sleep(interval)
+
+ if protocol.is_connected():
+ try:
+ await protocol.send_heartbeat(client_id)
+ except Exception as e:
+ logger.error(f"Error sending heartbeat: {e}")
+ # Continue loop, connection manager handles disconnection
+ else:
+ logger.warning("Protocol not connected, skipping heartbeat")
+
+ except asyncio.CancelledError:
+ logger.debug("Heartbeat loop cancelled")
```
### Failure Detection
-!!!warning "Automatic Disconnection Trigger"
- If heartbeat fails to send (connection closed), the loop exits and triggers disconnection handling.
-
-**Heartbeat Failure Detection:**
+When the transport layer fails to send a heartbeat (connection closed), errors are logged but the loop continues running. The connection manager is responsible for detecting the disconnection through transport-level errors and triggering the reconnection strategy.
-This sequence diagram shows how the heartbeat loop detects connection failures and triggers disconnection handling:
+This sequence diagram shows how heartbeat errors are handled:
```mermaid
sequenceDiagram
@@ -291,31 +289,29 @@ sequenceDiagram
P-->>HM: Continue
else Connection dead
T-xP: ConnectionError
- P-xHM: Error
- HM->>HM: Exit loop
- HM->>HM: Trigger disconnection
+ P-xHM: Error (caught)
+ HM->>HM: Log error, continue loop
+ Note over HM: Connection manager
handles disconnection
at transport level
end
end
```
-The `x` markers indicate error paths. When the transport layer fails to send a heartbeat, the error propagates back to the manager, which exits the loop and initiates disconnection handling.
+The `x` markers indicate error paths. When the transport layer fails to send a heartbeat, the error is caught and logged. The heartbeat loop continues, while the connection manager detects the disconnection at the transport level and initiates recovery.
### Interval Guidelines
-!!!tip "Interval Selection"
- | Environment | Recommended Interval | Rationale |
- |-------------|---------------------|-----------|
- | **Local network** | 10-20s | Quick failure detection, low latency |
- | **Internet** | 30-60s | Balance overhead vs detection speed |
- | **Mobile/Unreliable** | 60-120s | Reduce battery/bandwidth usage |
- | **Critical systems** | 5-10s | Fastest failure detection |
+| Environment | Recommended Interval | Rationale |
+|-------------|---------------------|-----------|
+| **Local network** | 10-20s | Quick failure detection, low latency |
+| **Internet** | 30-60s | Balance overhead vs detection speed |
+| **Mobile/Unreliable** | 60-120s | Reduce battery/bandwidth usage |
+| **Critical systems** | 5-10s | Fastest failure detection |
---
## TimeoutManager
-!!!info "Operation Timeout Enforcement"
- Prevents operations from hanging indefinitely by enforcing configurable timeouts with automatic cancellation.
+Prevents operations from hanging indefinitely by enforcing configurable timeouts with automatic cancellation.
### Configuration
@@ -327,6 +323,8 @@ timeout_manager = TimeoutManager(
)
```
+[→ See how timeouts are used in protocol operations](./protocols.md)
+
### Usage Patterns
**Default Timeout:**
@@ -365,43 +363,39 @@ except TimeoutError:
### Recommended Timeouts
-!!!success "Timeout Guidelines by Operation"
- | Operation | Timeout | Rationale |
- |-----------|---------|-----------|
- | **Registration** | 10-30s | Simple message exchange |
- | **Task Dispatch** | 30-60s | May involve scheduling logic |
- | **Command Execution** | 60-300s | Depends on command complexity |
- | **Heartbeat** | 5-10s | Fast failure detection needed |
- | **Disconnection** | 5-15s | Clean shutdown |
- | **Device Info Query** | 15-30s | Telemetry collection |
+| Operation | Timeout | Rationale |
+|-----------|---------|-----------|
+| **Registration** | 10-30s | Simple message exchange |
+| **Task Dispatch** | 30-60s | May involve scheduling logic |
+| **Command Execution** | 60-300s | Depends on command complexity |
+| **Heartbeat** | 5-10s | Fast failure detection needed |
+| **Disconnection** | 5-15s | Clean shutdown |
+| **Device Info Query** | 15-30s | Telemetry collection |
---
## Integration with Endpoints
-!!!tip "Automatic Resilience"
- Endpoints automatically integrate all resilience components—no manual wiring needed.
+Endpoints automatically integrate all resilience components—no manual wiring needed.
### Example: DeviceClientEndpoint
```python
from aip.endpoints import DeviceClientEndpoint
-from aip.resilience import ReconnectionStrategy
endpoint = DeviceClientEndpoint(
ws_url="ws://localhost:8000/ws",
ufo_client=client,
- reconnection_strategy=ReconnectionStrategy(
- max_retries=3,
- initial_backoff=2.0,
- max_backoff=60.0
- )
+ max_retries=3, # Reconnection retries
+ timeout=120.0 # Connection timeout
)
# Resilience handled automatically on start
await endpoint.start()
```
+**Note**: The endpoint creates its own `ReconnectionStrategy` internally with the specified `max_retries`.
+
### Built-In Features
| Feature | Behavior | Configuration |
@@ -412,6 +406,7 @@ await endpoint.start()
| **Task Cancellation** | Auto-cancel on disconnect | Built-in to endpoint |
[→ See endpoint documentation](./endpoints.md)
+[→ See WebSocket transport details](./transport.md)
---
@@ -462,47 +457,43 @@ timeout_default = 180.0
### Scenario 1: Transient Network Failure
-!!!example "Brief Network Glitch"
- **Problem**: Network glitch disconnects client for 3 seconds.
-
- **Resolution**:
- 1. ✅ Disconnection detected via heartbeat timeout
- 2. ✅ Automatic reconnection triggered (1st attempt after 2s)
- 3. ✅ Connection restored successfully
- 4. ✅ Heartbeat resumes
- 5. ✅ Tasks continue
+**Problem**: Network glitch disconnects client for 3 seconds.
+
+**Resolution**:
+1. ✅ Disconnection detected via heartbeat timeout
+2. ✅ Automatic reconnection triggered (1st attempt after 2s)
+3. ✅ Connection restored successfully
+4. ✅ Heartbeat resumes
+5. ✅ Tasks continue
### Scenario 2: Prolonged Outage
-!!!failure "Extended Disconnection"
- **Problem**: Device offline for 10 minutes.
-
- **Resolution**:
- 1. ❌ Initial disconnection detected
- 2. ⏳ Multiple reconnection attempts (exponential backoff: 2s, 4s, 8s, 16s, 32s)
- 3. ❌ All attempts fail (max retries reached)
- 4. ⚠️ Tasks marked as FAILED
- 5. 📢 ConstellationAgent notified
- 6. ♻️ Tasks reassigned to other devices
+**Problem**: Device offline for 10 minutes.
+
+**Resolution**:
+1. ❌ Initial disconnection detected
+2. ⏳ Multiple reconnection attempts (exponential backoff: 2s, 4s, 8s, 16s, 32s)
+3. ❌ All attempts fail (max retries reached)
+4. ⚠️ Tasks marked as FAILED
+5. 📢 ConstellationAgent notified
+6. ♻️ Tasks reassigned to other devices
### Scenario 3: Server Restart
-!!!warning "All Clients Disconnect Simultaneously"
- **Problem**: Server restarts, causing all clients to disconnect at once.
-
- **Resolution**:
- 1. ⚠️ All clients detect disconnection
- 2. ⏳ Each client begins reconnection (with jitter to avoid thundering herd)
- 3. ✅ Server restarts and accepts connections
- 4. ✅ Clients reconnect and re-register
- 5. ✅ Task execution resumes
+**Problem**: Server restarts, causing all clients to disconnect at once.
+
+**Resolution**:
+1. ⚠️ All clients detect disconnection
+2. ⏳ Each client begins reconnection (with jitter to avoid thundering herd)
+3. ✅ Server restarts and accepts connections
+4. ✅ Clients reconnect and re-register
+5. ✅ Task execution resumes
### Scenario 4: Heartbeat Timeout
-!!!danger "Missing Heartbeat Response"
- **Problem**: Heartbeat not received within timeout period.
-
- **Resolution**:
+**Problem**: Heartbeat not received within timeout period.
+
+**Resolution**:
1. ⏰ HeartbeatManager detects missing pong
2. ⚠️ Connection marked as potentially dead
3. 🔄 Disconnection handling triggered
@@ -561,8 +552,7 @@ if not await strategy.attempt_reconnection(endpoint, device_id):
## Testing Resilience
-!!!tip "Simulate Failures"
- Test resilience by simulating network failures and verifying recovery.
+Test resilience by simulating network failures and verifying recovery.
```python
# Simulate disconnection
diff --git a/documents/docs/aip/transport.md b/documents/docs/aip/transport.md
index 54355b8e5..fc8715c48 100644
--- a/documents/docs/aip/transport.md
+++ b/documents/docs/aip/transport.md
@@ -1,12 +1,9 @@
# AIP Transport Layer
-!!!quote "Network Communication Foundation"
- The transport layer provides a pluggable abstraction for AIP's network communication, decoupling protocol logic from underlying network implementations through a unified Transport interface.
+The transport layer provides a pluggable abstraction for AIP's network communication, decoupling protocol logic from underlying network implementations through a unified Transport interface.
## Transport Architecture
-**Pluggable Transport Layer Design:**
-
AIP uses a transport abstraction pattern that allows different network protocols to be swapped without changing higher-level protocol logic. The current implementation focuses on WebSocket, with future support planned for HTTP/3 and gRPC:
```mermaid
@@ -39,8 +36,7 @@ The unified adapter bridges client and server WebSocket libraries, providing a c
## Transport Interface
-!!!info "Abstract Base Class"
- All transport implementations must implement the `Transport` interface for interoperability.
+All transport implementations must implement the `Transport` interface for interoperability.
### Core Operations
@@ -50,6 +46,7 @@ The unified adapter bridges client and server WebSocket libraries, providing a c
| `send(data)` | Send raw bytes | `None` |
| `receive()` | Receive raw bytes | `bytes` |
| `close()` | Close connection gracefully | `None` |
+| `wait_closed()` | Wait for connection to fully close | `None` |
| `is_connected` (property) | Check connection status | `bool` |
### Interface Definition
@@ -74,6 +71,10 @@ class Transport(ABC):
async def close(self) -> None:
"""Close connection"""
+ @abstractmethod
+ async def wait_closed(self) -> None:
+ """Wait for connection to fully close"""
+
@property
@abstractmethod
def is_connected(self) -> bool:
@@ -84,8 +85,7 @@ class Transport(ABC):
## WebSocket Transport
-!!!success "Primary Implementation"
- `WebSocketTransport` provides persistent, full-duplex, bidirectional communication over WebSocket protocol (RFC 6455).
+`WebSocketTransport` provides persistent, full-duplex, bidirectional communication over WebSocket protocol (RFC 6455).
### Quick Start
@@ -130,8 +130,9 @@ async def websocket_endpoint(websocket: WebSocket):
await transport.send(b"Response")
```
-!!!tip "Automatic Adapter Selection"
- WebSocketTransport automatically detects whether it's wrapping a FastAPI WebSocket or a client connection and selects the appropriate adapter.
+**Note**: WebSocketTransport automatically detects whether it's wrapping a FastAPI WebSocket or a client connection and selects the appropriate adapter.
+
+[→ See how endpoints use WebSocketTransport](./endpoints.md)
### Configuration Parameters
@@ -154,8 +155,6 @@ async def websocket_endpoint(websocket: WebSocket):
### Connection States
-**Transport State Machine:**
-
WebSocket connections transition through multiple states during their lifecycle. This diagram shows all possible states and transitions:
```mermaid
@@ -201,12 +200,7 @@ else:
### Ping/Pong Keepalive
-!!!info "Automatic Health Monitoring"
- WebSocket automatically sends ping frames at `ping_interval` to detect broken connections.
-
-**Keepalive Flow:**
-
-**WebSocket Ping/Pong Health Check:**
+WebSocket automatically sends ping frames at `ping_interval` to detect broken connections.
This sequence diagram shows the automatic ping/pong mechanism for detecting broken connections:
@@ -277,19 +271,17 @@ except Exception as e:
logger.error(f"Error during shutdown: {e}")
```
-!!!success "Close Frame Exchange"
- The transport sends a WebSocket close frame and waits for the peer's close frame within `close_timeout` before terminating the connection.
+**Note**: The transport sends a WebSocket close frame and waits for the peer's close frame within `close_timeout` before terminating the connection.
### Adapter Pattern
-!!!tip "Transparent Multi-Implementation Support"
- AIP uses adapters to provide a unified interface across different WebSocket libraries without exposing implementation details.
+AIP uses adapters to provide a unified interface across different WebSocket libraries without exposing implementation details.
**Supported WebSocket Implementations:**
| Implementation | Use Case | Adapter |
|----------------|----------|---------|
-| **websockets library** | Client-side connections | `WebSocketClientAdapter` |
+| **websockets library** | Client-side connections | `WebSocketsLibAdapter` |
| **FastAPI WebSocket** | Server-side endpoints | `FastAPIWebSocketAdapter` |
**Automatic Detection:**
@@ -298,7 +290,7 @@ except Exception as e:
# Server-side: Automatically uses FastAPIWebSocketAdapter
transport = WebSocketTransport(websocket=fastapi_websocket)
-# Client-side: Automatically uses WebSocketClientAdapter
+# Client-side: Automatically uses WebSocketsLibAdapter
transport = WebSocketTransport()
await transport.connect("ws://server:8000/ws")
```
@@ -314,13 +306,10 @@ await transport.connect("ws://server:8000/ws")
## Message Encoding
-!!!info "UTF-8 JSON Serialization"
- AIP uses UTF-8 encoded JSON for all messages, leveraging Pydantic for serialization/deserialization.
+AIP uses UTF-8 encoded JSON for all messages, leveraging Pydantic for serialization/deserialization.
### Encoding Flow
-**Message Serialization Pipeline:**
-
This diagram shows the transformation steps from Pydantic model to network bytes:
```mermaid
@@ -402,64 +391,70 @@ print(f"Task ID: {msg.task_id}")
### Optimization Strategies
-!!!success "Large Messages Strategy"
- For messages approaching `max_size`:
-
- **Option 1: Compression**
- ```python
+**Large Messages Strategy:**
+
+For messages approaching `max_size`:
+
+**Option 1: Compression**
+```python
import gzip
compressed = gzip.compress(large_data)
await transport.send(compressed)
```
-
- **Option 2: Chunking**
- ```python
+
+**Option 2: Chunking**
+```python
chunk_size = 1024 * 1024 # 1MB chunks
for i in range(0, len(large_data), chunk_size):
chunk = large_data[i:i+chunk_size]
await transport.send(chunk)
```
-
- **Option 3: Streaming Protocol**
- Consider implementing a custom streaming protocol for very large payloads.
-!!!tip "High Throughput Strategy"
- For high message rates:
-
- **Batch Messages:**
- ```python
+**Option 3: Streaming Protocol**
+
+Consider implementing a custom streaming protocol for very large payloads.
+
+[→ See message encoding details in Protocol Reference](./protocols.md)
+
+**High Throughput Strategy:**
+
+For high message rates:
+
+**Batch Messages:**
+```python
batch = [msg1, msg2, msg3, msg4]
batch_json = json.dumps([msg.model_dump() for msg in batch])
await transport.send(batch_json.encode('utf-8'))
```
-
- **Reduce Ping Frequency:**
- ```python
- transport = WebSocketTransport(
- ping_interval=60.0 # Less overhead
- )
- ```
-!!!info "Low Latency Strategy"
- For real-time applications:
-
- **Fast Failure Detection:**
- ```python
+**Reduce Ping Frequency:**
+```python
+transport = WebSocketTransport(
+ ping_interval=60.0 # Less overhead
+)
+```
+
+**Low Latency Strategy:**
+
+For real-time applications:
+
+**Fast Failure Detection:**
+```python
transport = WebSocketTransport(
ping_interval=10.0, # Quick detection
ping_timeout=30.0
)
```
-
- **Dedicated Connections:**
- ```python
- # One transport per device (no sharing)
- device_transports = {
- device_id: WebSocketTransport()
- for device_id in devices
- }
- ```
+
+**Dedicated Connections:**
+```python
+# One transport per device (no sharing)
+device_transports = {
+ device_id: WebSocketTransport()
+ for device_id in devices
+}
+```
---
@@ -500,8 +495,7 @@ print(f"Task ID: {msg.task_id}")
### Custom Transport Implementation
-!!!example "Extend Transport Interface"
- Implement custom transports for specialized protocols:
+Implement custom transports for specialized protocols:
```python
from aip.transport.base import Transport
@@ -527,24 +521,29 @@ class CustomTransport(Transport):
**Integration:**
+Custom transports can be used directly with protocols:
+
```python
-from aip.endpoints import DeviceClientEndpoint
+from aip.protocol import AIPProtocol
-# Use custom transport
-endpoint = DeviceClientEndpoint(
- transport=CustomTransport(),
- ufo_client=client
-)
+# Use custom transport with protocol
+transport = CustomTransport()
+await transport.connect("custom://server:port")
+
+protocol = AIPProtocol(transport)
+await protocol.send_message(message)
```
+[→ See Transport interface specification above](#transport-interface)
+[→ See Protocol usage examples](./protocols.md)
+
---
## Best Practices
### Environment-Specific Configuration
-!!!success "Network-Aware Configuration"
- Adapt transport settings to your deployment environment's characteristics.
+Adapt transport settings to your deployment environment's characteristics.
| Environment | ping_interval | ping_timeout | max_size | close_timeout |
|-------------|--------------|--------------|----------|---------------|
@@ -585,8 +584,7 @@ transport = WebSocketTransport(
### Connection Health Monitoring
-!!!tip "Proactive Health Checks"
- Always verify connection status before critical operations:
+Always verify connection status before critical operations:
```python
# Check before sending
@@ -600,8 +598,7 @@ await transport.send(data)
### Resilience Integration
-!!!info "Combine with Reconnection Strategy"
- Transport alone provides low-level communication. Combine with resilience components for production readiness:
+Transport alone provides low-level communication. Combine with resilience components for production readiness:
```python
from aip.resilience import ReconnectionStrategy
@@ -616,6 +613,7 @@ except ConnectionError:
```
[→ See Resilience documentation](./resilience.md)
+[→ See HeartbeatManager for connection health monitoring](./resilience.md#heartbeat-manager)
### Logging and Observability
diff --git a/documents/docs/client/computer.md b/documents/docs/client/computer.md
index 821dbbb03..16d98ac18 100644
--- a/documents/docs/client/computer.md
+++ b/documents/docs/client/computer.md
@@ -1,54 +1,39 @@
-# Computer - MCP Tool Execution Layer
+# Computer
-## Overview
+The **Computer** class is the core execution layer of the UFO client. It manages MCP (Model Context Protocol) tool execution, maintains tool registries, and provides thread-isolated execution for reliability. Each Computer instance represents a distinct execution context with its own namespace and resource management.
-The **Computer** class is the core execution layer that manages MCP (Model Context Protocol) servers and provides tool execution capabilities for UFO² agents. It acts as an abstraction layer between high-level commands and low-level MCP tool calls.
+## Architecture Overview
+The Computer layer provides the execution engine for MCP tools with three main components:
+
+```mermaid
+graph TB
+ CommandRouter["CommandRouter
Command Routing"]
+ ComputerManager["ComputerManager
Instance Management"]
+ Computer["Computer
Core Execution Layer"]
+ MCPServerManager["MCP Server Manager
Process Isolation"]
+
+ CommandRouter -->|Routes To| ComputerManager
+ ComputerManager -->|Creates & Manages| Computer
+ Computer -->|Data Collection| DataServers["Data Collection Servers
screenshot, ui_detection, etc."]
+ Computer -->|Actions| ActionServers["Action Servers
gui_automation, file_operations, etc."]
+ Computer -->|Uses| ToolsRegistry["Tools Registry
tool_type::tool_name → MCPToolCall"]
+ Computer -->|Provides| MetaTools["Meta Tools
list_tools built-in introspection"]
+ Computer -->|Delegates To| MCPServerManager
```
-┌─────────────────────────────────────────────────────┐
-│ CommandRouter │
-│ (Routes commands to computers) │
-└───────────────────┬─────────────────────────────────┘
- │
- ▼
-┌─────────────────────────────────────────────────────┐
-│ ComputerManager │
-│ (Creates & manages Computer instances) │
-└───────────────────┬─────────────────────────────────┘
- │
- ▼
-┌─────────────────────────────────────────────────────┐
-│ Computer │
-│ ┌─────────────────────────────────────────────┐ │
-│ │ Data Collection Servers (namespace1, ...) │ │
-│ │ - screenshot, ui_detection, etc. │ │
-│ └─────────────────────────────────────────────┘ │
-│ ┌─────────────────────────────────────────────┐ │
-│ │ Action Servers (namespace2, ...) │ │
-│ │ - gui_automation, file_operations, etc. │ │
-│ └─────────────────────────────────────────────┘ │
-│ ┌─────────────────────────────────────────────┐ │
-│ │ Tools Registry │ │
-│ │ - tool_type::tool_name → MCPToolCall │ │
-│ └─────────────────────────────────────────────┘ │
-│ ┌─────────────────────────────────────────────┐ │
-│ │ Meta Tools │ │
-│ │ - list_tools (built-in introspection) │ │
-│ └─────────────────────────────────────────────┘ │
-└────────────────────┬────────────────────────────────┘
- │
- ▼
- ┌─────────────────────┐
- │ MCP Server Manager │
- │ (Process isolation) │
- └─────────────────────┘
-```
-!!!info "Key Responsibilities"
- - **Tool Registration**: Register tools from multiple MCP servers with namespace isolation
- - **Command Routing**: Convert high-level commands to MCP tool calls
- - **Execution Management**: Execute tools in isolated thread pools with timeout protection
- - **Meta Tools**: Provide introspection capabilities (e.g., `list_tools`)
+**Computer** manages MCP tool execution with thread isolation and timeout control (6000-second timeout, 10-worker thread pool).
+**ComputerManager** handles multiple Computer instances with namespace-based routing.
+**CommandRouter** routes and executes commands across Computer instances with early-exit support.
+
+### Key Responsibilities
+
+- **Tool Registration**: Register tools from multiple MCP servers with namespace isolation
+- **Command Routing**: Convert high-level commands to MCP tool calls
+- **Execution Management**: Execute tools in isolated thread pools with timeout protection
+- **Meta Tools**: Provide introspection capabilities (e.g., `list_tools`)
+
+## Table of Contents
## Core Components
@@ -84,8 +69,7 @@ Computer supports two types of tool namespaces:
"action::type_text" # Type text
```
-!!!tip "Namespace Isolation"
- Different namespaces allow the same tool name to exist in both data collection and action contexts. For example, both `data_collection::get_file_info` and `action::get_file_info` can coexist.
+> **Note:** Different namespaces allow the same tool name to exist in both data collection and action contexts. For example, both `data_collection::get_file_info` and `action::get_file_info` can coexist.
### 2. ComputerManager Class
@@ -127,10 +111,11 @@ mcp:
reset: false
```
-!!!warning "Configuration Requirements"
- - Each agent must have at least a `default` root configuration
- - If `root_name` is not found, the manager falls back to `default`
- - Missing configurations will raise a `ValueError`
+**Configuration Requirements**
+
+- Each agent must have at least a `default` root configuration
+- If `root_name` is not found, the manager falls back to `default`
+- Missing configurations will raise a `ValueError`
### 3. CommandRouter Class
@@ -138,14 +123,13 @@ The `CommandRouter` executes commands on the appropriate `Computer` instance by
#### Execution Flow
-```
-Command → CommandRouter → ComputerManager → Computer → MCP Tool
- │
- └─ get_or_create(agent, process, root)
- │
- └─ computer.command2tool(command)
- │
- └─ computer.run_actions([tool_call])
+```mermaid
+graph LR
+ Command --> CommandRouter
+ CommandRouter --> ComputerManager
+ ComputerManager -->|get_or_create| Computer
+ Computer -->|command2tool| ToolCall[MCPToolCall]
+ ToolCall -->|run_actions| Result[MCP Tool Result]
```
## Initialization
@@ -184,8 +168,7 @@ computer = Computer(
await computer.async_init()
```
-!!!danger "Async Initialization Required"
- You **must** call `await computer.async_init()` after creating a `Computer` instance. This registers all MCP servers and their tools asynchronously.
+> **⚠️ Important:** You **must** call `await computer.async_init()` after creating a `Computer` instance. This registers all MCP servers and their tools asynchronously.
### ComputerManager Initialization
@@ -255,8 +238,7 @@ tool_call = computer.command2tool(command)
results = await computer.run_actions([tool_call])
```
-!!!tip "Automatic Tool Type Detection"
- If `tool_type` is not specified in the command, the `command2tool()` method will automatically detect whether the tool is registered as `data_collection` or `action`.
+If `tool_type` is not specified in the command, the `command2tool()` method will automatically detect whether the tool is registered as `data_collection` or `action`.
### Batch Tool Execution
@@ -310,14 +292,14 @@ result = await asyncio.wait_for(
)
```
-!!!warning "Timeout Handling"
- If a tool execution exceeds 6000 seconds, it will be cancelled and return a timeout error:
- ```python
- CallToolResult(
- is_error=True,
- content=[TextContent(text="Tool execution timed out after 6000s")]
- )
- ```
+If a tool execution exceeds 6000 seconds, it will be cancelled and return a timeout error:
+
+```python
+CallToolResult(
+ is_error=True,
+ content=[TextContent(text="Tool execution timed out after 6000s")]
+)
+```
## Meta Tools
@@ -354,19 +336,20 @@ result = await computer.run_actions([tool_call])
tools = result[0].data # List of available action tools
```
-!!!example "Meta Tool Example"
- ```python
- # List all tools in "screenshot" namespace
- result = await computer.run_actions([
- MCPToolCall(
- tool_key="data_collection::list_tools",
- tool_name="list_tools",
- parameters={"namespace": "screenshot", "remove_meta": True}
- )
- ])
-
- # Returns: [{"tool_name": "take_screenshot", "description": "...", ...}]
- ```
+**Example:**
+
+```python
+# List all tools in "screenshot" namespace
+result = await computer.run_actions([
+ MCPToolCall(
+ tool_key="data_collection::list_tools",
+ tool_name="list_tools",
+ parameters={"namespace": "screenshot", "remove_meta": True}
+ )
+])
+
+# Returns: [{"tool_name": "take_screenshot", "description": "...", ...}]
+```
## Dynamic Server Management
@@ -404,10 +387,11 @@ await computer.delete_server(
)
```
-!!!tip "Use Cases for Dynamic Server Management"
- - Add specialized tools for specific tasks
- - Remove servers to reduce memory footprint
- - Hot-reload MCP servers during development
+**Use cases for dynamic server management:**
+
+- Add specialized tools for specific tasks
+- Remove servers to reduce memory footprint
+- Hot-reload MCP servers during development
## Command Routing
@@ -463,8 +447,7 @@ results = await router.execute(
)
```
-!!!warning "Early Exit Behavior"
- When `early_exit=True`, if a command fails, subsequent commands will **not** be executed, and their results will be set to `ResultStatus.FAILED`.
+> **⚠️ Warning:** When `early_exit=True`, if a command fails, subsequent commands will **not** be executed, and their results will be set to `ResultStatus.SKIPPED`.
## Tool Registry
@@ -498,23 +481,27 @@ print(tool_info.mcp_server) # Reference to MCP server
## Best Practices
-!!!success "Configuration Best Practices"
- 1. **Use namespaces wisely**: Group related tools under meaningful namespaces
- 2. **Separate concerns**: Use `data_collection` for read-only operations, `action` for state changes
- 3. **Configure timeouts**: Adjust `_tool_timeout` for long-running operations
- 4. **Use default root**: Always provide a `default` root configuration as fallback
-
-!!!tip "Performance Optimization"
- 1. **Register servers in parallel**: The `async_init()` method already does this via `asyncio.gather()`
- 2. **Reuse Computer instances**: Let `ComputerManager` cache instances rather than creating new ones
- 3. **Limit concurrent tools**: The thread pool has 10 workers; excessive parallel tools may queue
- 4. **Reset servers carefully**: Setting `reset=True` in server config will restart the MCP server process
-
-!!!danger "Common Pitfalls"
- - **Forgetting `async_init()`**: Always call after creating a `Computer` instance
- - **Tool key collisions**: Ensure tool names are unique within each `tool_type`
- - **Timeout too short**: Some operations (e.g., file downloads) may need longer timeouts
- - **Blocking in meta tools**: Meta tools should be fast; avoid I/O operations
+### Configuration
+
+1. **Use namespaces wisely**: Group related tools under meaningful namespaces
+2. **Separate concerns**: Use `data_collection` for read-only operations, `action` for state changes
+3. **Configure timeouts**: Adjust `_tool_timeout` for long-running operations
+4. **Use default root**: Always provide a `default` root configuration as fallback
+
+### Performance Optimization
+
+1. **Register servers in parallel**: The `async_init()` method already does this via `asyncio.gather()`
+2. **Reuse Computer instances**: Let `ComputerManager` cache instances rather than creating new ones
+3. **Limit concurrent tools**: The thread pool has 10 workers; excessive parallel tools may queue
+4. **Reset servers carefully**: Setting `reset=True` in server config will restart the MCP server process
+
+### Common Pitfalls
+
+> **⚠️ Important:** Avoid these common mistakes:
+> - **Forgetting `async_init()`**: Always call after creating a `Computer` instance
+> - **Tool key collisions**: Ensure tool names are unique within each `tool_type`
+> - **Timeout too short**: Some operations (e.g., file downloads) may need longer timeouts
+> - **Blocking in meta tools**: Meta tools should be fast; avoid I/O operations
## Error Handling
diff --git a/documents/docs/client/computer_manager.md b/documents/docs/client/computer_manager.md
index 8e3b9e72d..4b8adc03f 100644
--- a/documents/docs/client/computer_manager.md
+++ b/documents/docs/client/computer_manager.md
@@ -1,17 +1,15 @@
-# 🖥️ Computer Manager & Computer
+# Computer Manager & Computer
-!!!quote "Multi-Namespace Tool Execution"
- The **Computer Manager** orchestrates multiple **Computer** instances, each representing an isolated execution namespace with dedicated MCP servers and tools. This enables context-specific tool routing and fine-grained control over data collection vs. action execution.
+The **Computer Manager** orchestrates multiple **Computer** instances, each representing an isolated execution namespace with dedicated MCP servers and tools. This enables context-specific tool routing and fine-grained control over data collection vs. action execution.
---
-## 📋 Overview
+## Overview
-!!!info "Two-Layer Architecture"
- The Computer layer consists of two components working together:
-
- - **ComputerManager**: High-level orchestrator managing multiple Computer instances
- - **Computer**: Individual execution namespace with its own MCP servers and tool registry
+The Computer layer consists of two components working together:
+
+- **ComputerManager**: High-level orchestrator managing multiple Computer instances
+- **Computer**: Individual execution namespace with its own MCP servers and tool registry
### Computer Manager Responsibilities
@@ -70,7 +68,7 @@ graph TB
---
-## 🏗️ Computer Manager Architecture
+## 🏗�?Computer Manager Architecture
### Computer Instance Management
@@ -106,12 +104,13 @@ graph LR
| **Data Collection** | Gathering information, non-invasive queries | Screenshots, UI element detection, app state |
| **Action** | Performing actions, invasive operations | GUI automation, file operations, app control |
-!!!success "Separation of Concerns"
- Data collection tools can't modify state, while action tools have full control. This prevents accidental state changes during information gathering.
+Data collection tools are designed for non-invasive information gathering, while action tools have full control for state-changing operations.
---
-## 🖥️ Computer (Instance) Architecture
+## Computer Manager Architecture
+
+## 🖥�?Computer (Instance) Architecture
### Internal Structure
@@ -131,9 +130,9 @@ graph TB
subgraph "Tool Registry"
Registry --> TR[_tools_registry Dict]
- TR -->|key: action.click| T1[MCPToolCall]
- TR -->|key: data.screenshot| T2[MCPToolCall]
- TR -->|key: action.list_tools| T3[Meta Tool]
+ TR -->|key: action::click| T1[MCPToolCall]
+ TR -->|key: data_collection::screenshot| T2[MCPToolCall]
+ TR -->|key: action::list_tools| T3[Meta Tool]
end
subgraph "Execution Engine"
@@ -158,56 +157,57 @@ graph TB
|-----------|------|---------|
| `_name` | `str` | Computer name (identifier) |
| `_process_name` | `str` | Associated process (e.g., "notepad.exe") |
-| `_data_collection_servers` | `Dict[str, BaseMCPServer]` | Namespace → MCP server mapping (data collection) |
-| `_action_servers` | `Dict[str, BaseMCPServer]` | Namespace → MCP server mapping (actions) |
-| `_tools_registry` | `Dict[str, MCPToolCall]` | Tool key → tool info mapping |
+| `_data_collection_servers` | `Dict[str, BaseMCPServer]` | Namespace �?MCP server mapping (data collection) |
+| `_action_servers` | `Dict[str, BaseMCPServer]` | Namespace �?MCP server mapping (actions) |
+| `_tools_registry` | `Dict[str, MCPToolCall]` | Tool key �?tool info mapping |
| `_meta_tools` | `Dict[str, Callable]` | Built-in meta tools |
| `_executor` | `ThreadPoolExecutor` | Thread pool for tool execution (10 workers) |
| `_tool_timeout` | `int` | Tool execution timeout: **6000 seconds (100 minutes)** |
-!!!warning "Tool Timeout: 100 Minutes"
- Based on source code: `self._tool_timeout = 6000` (6000 seconds = 100 minutes). This allows very long-running operations but prevents indefinite hangs.
+> **Note:** The tool execution timeout is 6000 seconds (100 minutes), allowing for very long-running operations while preventing indefinite hangs.
---
-## 🚀 Initialization
+## Initialization
### Computer Manager Initialization
-!!!example "Creating Computer Manager"
- ```python
- from ufo.client.computer import ComputerManager
- from ufo.client.mcp.mcp_server_manager import MCPServerManager
- from config.config_loader import get_ufo_config
-
- # 1. Get UFO configuration
- ufo_config = get_ufo_config()
-
- # 2. Initialize MCP server manager
- mcp_server_manager = MCPServerManager()
-
- # 3. Create computer manager
- computer_manager = ComputerManager(
- ufo_config.to_dict(),
- mcp_server_manager
- )
- ```
+**Creating Computer Manager:**
+
+```python
+from ufo.client.computer import ComputerManager
+from ufo.client.mcp.mcp_server_manager import MCPServerManager
+from config.config_loader import get_ufo_config
+
+# 1. Get UFO configuration
+ufo_config = get_ufo_config()
+
+# 2. Initialize MCP server manager
+mcp_server_manager = MCPServerManager()
+
+# 3. Create computer manager
+computer_manager = ComputerManager(
+ ufo_config.to_dict(),
+ mcp_server_manager
+)
+```
### Computer Instance Initialization
-!!!example "Computer Async Initialization"
- ```python
- computer = Computer(
- name="default_agent",
- process_name="explorer.exe",
- mcp_server_manager=mcp_server_manager,
- data_collection_servers_config=[...],
- action_servers_config=[...]
- )
-
- # Async initialization (required)
- await computer.async_init()
- ```
+**Computer Async Initialization:**
+
+```python
+computer = Computer(
+ name="default_agent",
+ process_name="explorer.exe",
+ mcp_server_manager=mcp_server_manager,
+ data_collection_servers_config=[...],
+ action_servers_config=[...]
+)
+
+# Async initialization (required)
+await computer.async_init()
+```
**Initialization Flow:**
@@ -266,8 +266,7 @@ action_servers:
### CommandRouter
-!!!info "Smart Command Resolution"
- The CommandRouter resolves which Computer instance should handle each command based on agent/process/root context.
+The CommandRouter resolves which Computer instance should handle each command based on agent/process/root context.
**Routing Signature:**
@@ -320,8 +319,7 @@ graph TD
### Tool Execution Pipeline
-!!!success "Thread Isolation for Blocking Operations"
- MCP tools are executed in isolated threads to prevent blocking operations (like `time.sleep`) from blocking the main event loop and causing WebSocket disconnections.
+MCP tools are executed in isolated threads to prevent blocking operations (like `time.sleep`) from blocking the main event loop and causing WebSocket disconnections.
**Execution Flow:**
@@ -398,23 +396,22 @@ result = await asyncio.wait_for(
---
-## 🛠️ Tool Registry
+## 🛠�?Tool Registry
### Tool Registration
-!!!info "Dynamic Tool Discovery"
- Tools are discovered from MCP servers during initialization and registered with unique keys.
+Tools are discovered from MCP servers during initialization and registered with unique keys.
**Tool Key Format:**
```
-
via WebSocket]
Client[UFO Client
Session Orchestration]
- ComputerMgr[Computer Manager
Multi-App Routing]
+ Router[Command Router
Command Execution]
Computer[Computer
MCP Tool Manager]
- MCPMgr[MCP Server Manager
Lifecycle & Registry]
+ MCPMgr[MCP Server Manager
Server Lifecycle]
- DataServers[Data Collection Servers
UICollector, ScreenCapture]
- ActionServers[Action Servers
UIExecutor, CLIExecutor]
+ DataServers[Data Collection Servers
UICollector, etc.]
+ ActionServers[Action Servers
UIExecutor, CommandLineExecutor]
Server -->|AIP Commands| Client
- Client -->|Execute Step| ComputerMgr
- ComputerMgr -->|Route to App| Computer
+ Client -->|Execute Actions| Router
+ Router -->|Route to Computer| Computer
Computer -->|Manage Servers| MCPMgr
- Computer -->|Auto-invoke| DataServers
- Computer -->|Execute Tool| ActionServers
+ Computer -->|Register & Execute| DataServers
+ Computer -->|Register & Execute| ActionServers
style Computer fill:#e1f5ff
style MCPMgr fill:#fff4e6
@@ -51,12 +42,13 @@ graph TB
**Key Components:**
-| Component | Role | Responsibility |
-|-----------|------|----------------|
-| **Computer** | MCP Tool Manager | Registers MCP servers, routes tool calls, executes in thread pool |
-| **MCP Server Manager** | Server Lifecycle | Creates/manages server instances, handles local/remote servers |
-| **Data Collection Servers** | Observation Layer | Auto-invoked to gather UI state, screenshots, system info |
-| **Action Servers** | Execution Layer | LLM-selected tools to perform actions (click, type, run) |
+| Component | Location | Responsibility |
+|-----------|----------|----------------|
+| **Computer** | `ufo.client.computer.Computer` | Manages MCP servers, routes tool calls, executes in thread pool |
+| **MCP Server Manager** | `ufo.client.mcp.mcp_server_manager.MCPServerManager` | Creates/manages server instances (local/http/stdio) |
+| **Command Router** | `ufo.client.computer.CommandRouter` | Routes commands to appropriate Computer instances |
+| **Data Collection Servers** | Various MCP servers | Tools for gathering system state (read-only) |
+| **Action Servers** | Various MCP servers | Tools for performing state changes |
---
@@ -68,41 +60,31 @@ graph TB
sequenceDiagram
participant Server as Agent Server
participant Client as UFO Client
+ participant Router as Command Router
participant Computer as Computer
participant MCP as MCP Server
- participant OS as Operating System
-
- Server->>Client: AIP Command
(tool_name, parameters)
- Client->>Computer: execute_step()
- alt Data Collection (Auto-Invoked)
- Computer->>MCP: Invoke all data_collection tools
- MCP->>OS: Take screenshot, detect UI
- OS-->>MCP: Screen image, UI elements
- MCP-->>Computer: Observation data
- end
-
- Computer->>Computer: command2tool()
Convert to MCPToolCall
-
- alt Action Execution (LLM-Selected)
- Computer->>MCP: call_tool(tool_name, parameters)
- MCP->>OS: Perform action (click, type, etc.)
- OS-->>MCP: Action result
- MCP-->>Computer: Success/Failure
- end
-
- Computer-->>Client: Result (status, outputs)
+ Server->>Client: AIP Command (tool_name, parameters)
+ Client->>Router: execute_actions(commands)
+ Router->>Computer: command2tool()
+ Computer->>Computer: Convert to MCPToolCall
+ Router->>Computer: run_actions([tool_call])
+ Computer->>MCP: call_tool(tool_name, parameters)
+ MCP-->>Computer: CallToolResult
+ Computer-->>Router: Results
+ Router-->>Client: List[Result]
Client-->>Server: AIP Result message
```
**Execution Stages:**
-| Stage | Description | Tool Type | Trigger |
-|-------|-------------|-----------|---------|
-| **1. Data Collection** | Gather system/UI state | Data Collection | Automatic (every step) |
-| **2. Command Conversion** | AIP Command → MCPToolCall | N/A | Client |
-| **3. Action Execution** | Execute LLM-selected tool | Action | Explicit command |
-| **4. Result Return** | Package result for server | N/A | Client |
+| Stage | Component | Description |
+|-------|-----------|-------------|
+| **1. Command Reception** | UFO Client | Receives AIP Command from server |
+| **2. Command Routing** | Command Router | Routes to appropriate Computer instance |
+| **3. Command Conversion** | Computer | AIP Command → MCPToolCall |
+| **4. Tool Execution** | Computer | Executes tool via MCP Server |
+| **5. Result Return** | UFO Client | Packages result for server |
---
@@ -110,8 +92,7 @@ sequenceDiagram
### Computer Class Overview
-!!!success "Central MCP Hub"
- The `Computer` class is the **client-side MCP manager**, handling server registration, tool discovery, and execution.
+The `Computer` class is the **client-side MCP manager**, handling server registration, tool discovery, and execution.
**Core Responsibilities:**
@@ -140,12 +121,12 @@ await computer.async_init()
| Step | Action | Result |
|------|--------|--------|
-| 1. Create MCPServerManager | Initialize server factory | Ready to create servers |
-| 2. Load data_collection servers | Register observation tools | UICollector, ScreenCapture ready |
-| 3. Load action servers | Register execution tools | UIExecutor, CLIExecutor ready |
-| 4. Discover tools | Query each server for tools | Tool registry populated |
+| 1. Create MCP Server Manager | Initialize server lifecycle manager | Ready to create servers |
+| 2. Initialize data_collection servers | Register observation tools | UICollector ready |
+| 3. Initialize action servers | Register execution tools | HostUIExecutor, CommandLineExecutor ready |
+| 4. Register MCP servers | Query each server for tools | Tool registry populated |
-See [Computer Manager](./computer_manager.md) for detailed architecture.
+See [Computer](./computer.md) for detailed class documentation.
---
@@ -153,8 +134,7 @@ See [Computer Manager](./computer_manager.md) for detailed architecture.
### Data Collection vs Action
-!!!info "Critical Distinction"
- Understanding the difference between server types is essential for proper MCP usage.
+Understanding the difference between server types is essential for proper MCP usage:
**Comparison:**
@@ -162,35 +142,41 @@ See [Computer Manager](./computer_manager.md) for detailed architecture.
|--------|------------------------|----------------|
| **Purpose** | Observe system state | Modify system state |
| **Examples** | `take_screenshot`, `detect_ui_elements` | `click`, `type_text`, `run_command` |
-| **Invocation** | **Automatic** (every step) | **Explicit** (LLM selects) |
+| **Invocation** | LLM-selected tools | LLM-selected tools |
| **Side Effects** | ❌ None (read-only) | ✅ Yes (state changes) |
| **Namespace** | `"data_collection"` | `"action"` |
-| **LLM Selectable?** | ❌ No | ✅ Yes |
+| **Tool Key Format** | `data_collection::tool_name` | `action::tool_name` |
**Data Collection Example:**
```python
-# Automatically invoked before each action
-# Client doesn't explicitly call - framework handles it
-screenshot = await data_collection_server.call_tool(
- "take_screenshot",
- region="active_window"
-)
+# Example: Take screenshot for UI analysis
+result = await computer.run_actions([
+ computer.command2tool(Command(
+ tool_name="take_screenshot",
+ tool_type="data_collection",
+ parameters={"region": "active_window"}
+ ))
+])
```
**Action Example:**
```python
-# LLM selects tool based on task
-# Client explicitly calls via AIP Command
-result = await action_server.call_tool(
- "click",
- control_text="Save",
- control_type="Button"
-)
+# Example: Click a button
+result = await computer.run_actions([
+ computer.command2tool(Command(
+ tool_name="click",
+ tool_type="action",
+ parameters={
+ "control_text": "Save",
+ "control_type": "Button"
+ }
+ ))
+])
```
-See [MCP Overview - Server Types](../mcp/overview.md#key-concepts) for detailed comparison.
+See [MCP Overview - Server Types](../mcp/overview.md#1-two-server-types) for detailed comparison.
---
@@ -213,7 +199,7 @@ HostAgent:
type: local
reset: false
- - namespace: CLIExecutor # Multiple servers allowed
+ - namespace: CommandLineExecutor # Multiple servers allowed
type: local
reset: false
```
@@ -239,25 +225,33 @@ HostAgent:
### Tool Discovery
+The Computer automatically discovers and registers tools from all configured MCP servers during initialization:
+
**Automatic Registration:**
```python
# During computer.async_init()
async def register_mcp_servers(self, servers, tool_type):
+ """Register tools from all MCP servers"""
for namespace, server in servers.items():
# Connect to MCP server
async with Client(server.server) as client:
# List available tools
tools = await client.list_tools()
- # Register each tool
+ # Register each tool with unique key
for tool in tools:
- self.tool_registry[tool.name] = MCPToolCall(
- name=tool.name,
+ tool_key = self.make_tool_key(tool_type, tool.name)
+ self._tools_registry[tool_key] = MCPToolCall(
+ tool_key=tool_key,
+ tool_name=tool.name,
+ title=tool.title,
+ namespace=namespace,
+ tool_type=tool_type,
description=tool.description,
- parameters=tool.inputSchema,
- mcp_server=server,
- namespace=namespace
+ input_schema=tool.inputSchema,
+ output_schema=tool.outputSchema,
+ mcp_server=server
)
```
@@ -265,13 +259,30 @@ async def register_mcp_servers(self, servers, tool_type):
| Field | Type | Description |
|-------|------|-------------|
-| `name` | `str` | Tool name (e.g., `"take_screenshot"`) |
-| `description` | `str` | Tool description from docstring |
-| `parameters` | `dict` | JSON schema of parameters |
+| `tool_key` | `str` | Unique key: `"tool_type::tool_name"` |
+| `tool_name` | `str` | Tool name (e.g., `"take_screenshot"`) |
+| `title` | `str` | Display title |
+| `namespace` | `str` | Server namespace (e.g., `"UICollector"`) |
+| `tool_type` | `str` | `"data_collection"` or `"action"` |
+| `description` | `str` | Tool description |
+| `input_schema` | `dict` | JSON schema for parameters |
+| `output_schema` | `dict` | JSON schema for results |
| `mcp_server` | `BaseMCPServer` | Server instance |
-| `namespace` | `str` | Server namespace |
-See [Computer Manager](./computer_manager.md) for details.
+### Tool Execution
+
+Tools execute in isolated threads with timeout protection (default: 6000 seconds = 100 minutes per tool):
+
+```python
+# Thread pool configuration
+self._executor = concurrent.futures.ThreadPoolExecutor(
+ max_workers=10,
+ thread_name_prefix="mcp_tool_"
+)
+self._tool_timeout = 6000 # 100 minutes
+```
+
+See [Computer](./computer.md) for execution details.
---
@@ -280,10 +291,18 @@ See [Computer Manager](./computer_manager.md) for details.
### Basic Usage
```python
-from ufo.client.computer_manager import ComputerManager
+from ufo.client.computer import ComputerManager, CommandRouter
+from ufo.client.mcp.mcp_server_manager import MCPServerManager
+from aip.messages import Command
+
+# Create MCP server manager
+mcp_server_manager = MCPServerManager()
+
+# Create computer manager (manages Computer instances)
+computer_manager = ComputerManager(config, mcp_server_manager)
-# Create computer manager (manages MCP servers internally)
-computer_manager = ComputerManager(config)
+# Create command router
+command_router = CommandRouter(computer_manager)
# Execute action through MCP
command = Command(
@@ -295,8 +314,13 @@ command = Command(
}
)
-# Computer routes to appropriate MCP server
-result = await computer_manager.execute_command(command)
+# Router creates Computer instance and executes
+results = await command_router.execute(
+ agent_name="HostAgent",
+ process_name="notepad.exe",
+ root_name="default",
+ commands=[command]
+)
```
### Custom MCP Server
@@ -319,8 +343,9 @@ async def custom_action(param: str) -> str:
# reset: false
```
-!!!example "🛠️ Build Custom Servers"
- See [Creating MCP Servers](../tutorials/creating_mcp_servers.md) for step-by-step guide to creating your own MCP servers.
+**For step-by-step instructions:**
+
+- [Creating MCP Servers](../tutorials/creating_mcp_servers.md) - Build your own MCP tools
---
@@ -330,19 +355,23 @@ async def custom_action(param: str) -> str:
**UFO Client:**
- Receives AIP Commands from server
-- Delegates to Computer Manager
+- Delegates to Command Router
- Returns AIP Results
-**Computer Manager:**
-- Routes commands to correct Computer instance (by app/process)
-- Manages multiple Computer instances
+**Command Router:**
+- Routes commands to appropriate Computer instance (by agent/process/root name)
+- Manages command execution with early-exit support
**Computer:**
- **MCP entry point**: Manages all MCP servers
- Executes tools via MCP Server Manager
-- Aggregates results
+- Maintains tool registry
+
+**MCP Server Manager:**
+- Creates and manages MCP server instances
+- Supports local, HTTP, and stdio deployment types
-See [UFO Client](./ufo_client.md) and [Computer Manager](./computer_manager.md) for integration details.
+See [UFO Client](./ufo_client.md) and [Computer](./computer.md) for integration details.
---
@@ -352,7 +381,7 @@ See [UFO Client](./ufo_client.md) and [Computer Manager](./computer_manager.md)
| Component | Description | Link |
|-----------|-------------|------|
-| **Computer Manager** | Multi-app MCP coordination | [Computer Manager](./computer_manager.md) |
+| **Computer** | Core MCP execution layer | [Computer](./computer.md) |
| **UFO Client** | Session orchestration | [UFO Client](./ufo_client.md) |
| **WebSocket Client** | Server communication | [WebSocket Client](./websocket_client.md) |
@@ -372,33 +401,33 @@ See [UFO Client](./ufo_client.md) and [Computer Manager](./computer_manager.md)
## 🎯 Key Takeaways
-!!!success "MCP in Client - Summary"
-
- **1. Computer is the MCP Manager**
- - Manages all MCP server instances
- - Routes tool calls to appropriate servers
- - Executes in thread pool for isolation
-
- **2. Two Server Types**
- - **Data Collection**: Auto-invoked, read-only, observation
- - **Action**: LLM-selected, state-changing, execution
-
- **3. Configuration-Driven**
- - Servers configured in `config/ufo/mcp.yaml`
- - Supports local, HTTP, and stdio deployment
-
- **4. Automatic Registration**
- - Tools auto-discovered during initialization
- - Tool registry built from server metadata
-
- **5. Detailed Docs Available**
- - Full MCP section at [../mcp/](../mcp/overview.md)
- - Custom server guides, examples, troubleshooting
+**MCP in Client - Summary**
+
+**1. Computer is the MCP Manager**
+- Manages all MCP server instances
+- Routes tool calls to appropriate servers
+- Executes in thread pool for isolation
+
+**2. Two Server Types**
+- **Data Collection**: Read-only, observation tools
+- **Action**: State-changing, execution tools
+
+**3. Configuration-Driven**
+- Servers configured in `config/ufo/mcp.yaml`
+- Supports local, HTTP, and stdio deployment
+
+**4. Automatic Registration**
+- Tools auto-discovered during initialization
+- Tool registry built from server metadata
+
+**5. Detailed Docs Available**
+- Full MCP section at [MCP Overview](../mcp/overview.md)
+- Custom server guides, examples, troubleshooting
---
## 🚀 Next Steps
-👉 [MCP Overview](../mcp/overview.md) - Understand MCP architecture in depth
-👉 [Computer Manager](./computer_manager.md) - See how MCP servers are managed
-👉 [Creating MCP Servers](../tutorials/creating_mcp_servers.md) - Build your own MCP tools
+- [MCP Overview](../mcp/overview.md) - Understand MCP architecture in depth
+- [Computer](./computer.md) - See how MCP servers are managed
+- [Creating MCP Servers](../tutorials/creating_mcp_servers.md) - Build your own MCP tools
diff --git a/documents/docs/client/overview.md b/documents/docs/client/overview.md
index 0483c93c0..816a5734f 100644
--- a/documents/docs/client/overview.md
+++ b/documents/docs/client/overview.md
@@ -1,14 +1,12 @@
-# Agent Client Overview
+# UFO Client Overview
-!!!quote "The Execution Engine"
- The **Agent Client** runs on target devices and serves as the **execution layer** of UFO's distributed agent system. It manages MCP (Model Context Protocol) servers, executes commands deterministically, and communicates with the Agent Server through the Agent Interaction Protocol (AIP).
+The **UFO Client** runs on target devices and serves as the **execution layer** of UFO's distributed agent system. It manages MCP (Model Context Protocol) servers, executes commands deterministically, and communicates with the Agent Server through the Agent Interaction Protocol (AIP).
-!!!tip "Quick Start"
- Ready to run a client? Jump to the [Quick Start Guide](./quick_start.md) to connect your device in minutes. Make sure the [Agent Server](../server/quick_start.md) is running first.
+**Quick Start:** Jump to the [Quick Start Guide](./quick_start.md) to connect your device. Make sure the [Agent Server](../server/quick_start.md) is running first.
---
-## 🎯 What is the Agent Client?
+## 🎯 What is the UFO Client?
```mermaid
graph LR
@@ -46,7 +44,7 @@ graph LR
style Tools fill:#fff9c4
```
-**The Agent Client is a stateless execution agent that:**
+**The UFO Client is a stateless execution agent that:**
| Capability | Description | Benefit |
|------------|-------------|---------|
@@ -56,64 +54,44 @@ graph LR
| **📡 Communicates via AIP** | Maintains persistent WebSocket connection | Real-time bidirectional communication |
| **🚫 Remains Stateless** | Executes directives without high-level reasoning | Independent updates, simple architecture |
-!!!info "Stateless Design Philosophy"
- The client focuses **purely on execution**. All reasoning and decision-making happens on the server, allowing:
-
- - ✅ Independent updates to server logic and client tools
- - ✅ Simple client architecture (easier to maintain)
- - ✅ Server can orchestrate multiple clients intelligently
- - ✅ Clients can be lightweight and resource-efficient
+**Stateless Design Philosophy:** The client focuses purely on execution. All reasoning and decision-making happens on the server, allowing independent updates to server logic and client tools, simple client architecture, intelligent orchestration of multiple clients, and resource-efficient operation.
-!!!tip "Server-Client Architecture"
- The Agent Client is part of UFO's distributed **server-client architecture**, where it handles command execution and resource access while the [Agent Server](../server/overview.md) handles orchestration and decision-making. See [Server-Client Architecture](../infrastructure/agents/server_client_architecture.md) for the complete design rationale, communication protocols, and deployment patterns.
+**Architecture:** The UFO Client is part of UFO's distributed **server-client architecture**, where it handles command execution and resource access while the [Agent Server](../server/overview.md) handles orchestration and decision-making. See [Server-Client Architecture](../infrastructure/agents/server_client_architecture.md) for the complete design rationale, communication protocols, and deployment patterns.
---
## 🏗️ Architecture
-!!!success "Layered Design"
- The client implements a **layered architecture** separating communication, execution, and tool management for maximum flexibility and maintainability.
+The client implements a **layered architecture** separating communication, execution, and tool management for maximum flexibility and maintainability.
```mermaid
graph TB
- subgraph "Communication Layer"
- WSC[WebSocket Client]
- AIP[AIP Protocol Handler]
+ subgraph "Communication"
+ WSC[WebSocket Client
AIP Protocol]
end
- subgraph "Orchestration Layer"
+ subgraph "Orchestration"
UFC[UFO Client]
CM[Computer Manager]
end
- subgraph "Execution Layer"
- COMP[Computer Instance]
- TR[Tool Registry]
+ subgraph "Execution"
+ COMP[Computer]
+ MCPM[MCP Manager]
end
- subgraph "Integration Layer"
- MCPM[MCP Server Manager]
+ subgraph "Tools"
LOCAL[Local MCP Servers]
REMOTE[Remote MCP Servers]
end
- subgraph "Support Layer"
- DI[Device Info Provider]
- CONFIG[Configuration Loader]
- end
-
- WSC --> AIP
- AIP --> UFC
+ WSC --> UFC
UFC --> CM
CM --> COMP
- COMP --> TR
- TR --> MCPM
+ COMP --> MCPM
MCPM --> LOCAL
MCPM --> REMOTE
- DI -.->|System Info| WSC
- CONFIG -.->|Settings| MCPM
-
style WSC fill:#bbdefb
style UFC fill:#c8e6c9
style COMP fill:#fff9c4
@@ -125,10 +103,10 @@ graph TB
| Component | Responsibility | Key Features | Documentation |
|-----------|---------------|--------------|---------------|
| **WebSocket Client** | AIP communication | • Connection management
• Registration
• Heartbeat monitoring
• Message routing | [Details →](./websocket_client.md) |
-| **UFO Client** | Execution orchestration | • Session tracking
• Command execution
• Result aggregation
• Error handling | [Details →](./ufo_client.md) |
+| **UFO Client** | Execution orchestration | • Command execution
• Result aggregation
• Error handling
• Session management | [Details →](./ufo_client.md) |
| **Computer Manager** | Multi-computer abstraction | • Computer instance management
• Namespace routing
• Resource isolation | [Details →](./computer_manager.md) |
| **Computer** | Tool management | • MCP server registration
• Tool registry
• Execution isolation
• Thread pool management | [Details →](./computer.md) |
-| **MCP Server Manager** | MCP lifecycle | • Server creation
• Configuration loading
• Connection pooling
• Health monitoring | [Details →](./mcp_integration.md) |
+| **MCP Server Manager** | MCP lifecycle | • Server creation
• Configuration loading
• Connection pooling
• Health monitoring | [MCP Documentation →](../mcp/overview.md) |
| **Device Info Provider** | System profiling | • Hardware detection
• Capability reporting
• Platform identification
• Feature enumeration | [Details →](./device_info.md) |
For detailed component documentation:
@@ -137,7 +115,7 @@ For detailed component documentation:
- [UFO Client](./ufo_client.md) - Execution orchestration
- [Computer Manager](./computer_manager.md) - Multi-computer management
- [Device Info Provider](./device_info.md) - System profiling
-- [MCP Integration](./mcp_integration.md) - MCP server management (brief overview)
+- [MCP Integration](../mcp/overview.md) - MCP server management (comprehensive documentation)
---
@@ -145,32 +123,22 @@ For detailed component documentation:
### 1. Deterministic Command Execution
-!!!info "Pure Execution - No Interpretation"
- The client executes commands **exactly as specified** without interpretation or reasoning, ensuring predictable behavior.
+The client executes commands **exactly as specified** without interpretation or reasoning, ensuring predictable behavior.
```mermaid
sequenceDiagram
participant Server
- participant WSClient as WebSocket Client
- participant UFOClient as UFO Client
- participant CompMgr as Computer Manager
+ participant Client as UFO Client
participant Computer
participant Tool as MCP Tool
- Server->>WSClient: COMMAND (AIP)
- WSClient->>UFOClient: Route Command
- UFOClient->>CompMgr: Execute Command
- CompMgr->>Computer: Route to Namespace
- Computer->>Computer: Lookup Tool in Registry
- Computer->>Tool: Execute Tool Call
-
- Note over Tool: Isolated Thread Pool
with Timeout
-
- Tool-->>Computer: Tool Result
- Computer-->>CompMgr: Aggregated Result
- CompMgr-->>UFOClient: Execution Result
- UFOClient-->>WSClient: Format Response
- WSClient-->>Server: COMMAND_RESULTS (AIP)
+ Server->>Client: COMMAND (AIP)
+ Client->>Computer: Execute Command
+ Computer->>Computer: Lookup Tool
+ Computer->>Tool: Execute with Timeout
+ Tool-->>Computer: Result
+ Computer-->>Client: Aggregated Result
+ Client-->>Server: COMMAND_RESULTS (AIP)
```
**Execution Flow:**
@@ -184,22 +152,20 @@ sequenceDiagram
| 5️⃣ **Aggregate** | Combine results from multiple tools | Structured response format |
| 6️⃣ **Return** | Send results back to server via AIP | Complete the execution loop |
-!!!success "Execution Guarantees"
- - ✅ **Isolation**: Each tool runs in separate thread pool
- - ✅ **Timeouts**: Configurable timeout (default: 100 minutes)
- - ✅ **Fault Tolerance**: One failed tool doesn't crash entire client
- - ✅ **Thread Safety**: Concurrent tool execution supported
- - ✅ **Error Reporting**: Structured errors returned to server
+**Execution Guarantees:**
+- **Isolation**: Each tool runs in separate thread pool
+- **Timeouts**: Configurable timeout (default: 6000 seconds/100 minutes)
+- **Fault Tolerance**: One failed tool doesn't crash entire client
+- **Thread Safety**: Concurrent tool execution supported
+- **Error Reporting**: Structured errors returned to server
### 2. MCP Server Management
-!!!tip "Extensible Tool Ecosystem"
- The client manages a collection of **MCP (Model Context Protocol) servers** to provide diverse tool access for automation tasks. The client is responsible for registering, managing, and executing these tools, while the [Agent Server](../server/overview.md) handles command orchestration. See [Server-Client Architecture](../infrastructure/agents/server_client_architecture.md#client-command-execution-and-resource-access) for how MCP integration fits into the overall architecture.
+The client manages a collection of **MCP (Model Context Protocol) servers** to provide diverse tool access for automation tasks. The client is responsible for registering, managing, and executing these tools, while the [Agent Server](../server/overview.md) handles command orchestration. See [Server-Client Architecture](../infrastructure/agents/server_client_architecture.md#client-command-execution-and-resource-access) for how MCP integration fits into the overall architecture.
**MCP Server Categories:**
-**Data Collection Servers:**
-**Purpose**: Gather information from the device
+**Data Collection Servers** gather information from the device:
| Server Type | Tools Provided | Use Cases |
|-------------|---------------|-----------|
@@ -208,14 +174,9 @@ sequenceDiagram
| **Screenshot** | Screen capture | Visual verification |
| **UI Element Detection** | Control trees, accessibility | UI automation |
-**Example Tools:**
-- `get_system_info()` - System specifications
-- `list_running_apps()` - Active applications
-- `capture_screenshot()` - Screen snapshot
-- `get_ui_tree()` - UI element hierarchy
+Example Tools: `get_system_info()`, `list_running_apps()`, `capture_screenshot()`, `get_ui_tree()`
-**Action Servers:**
-**Purpose**: Perform actions on the device
+**Action Servers** perform actions on the device:
| Server Type | Tools Provided | Use Cases |
|-------------|---------------|-----------|
@@ -224,11 +185,7 @@ sequenceDiagram
| **File System** | Read, write, delete | File operations |
| **Command Execution** | Shell commands | System automation |
-**Example Tools:**
-- `click_button(label)` - UI interaction
-- `type_text(text)` - Keyboard input
-- `open_application(name)` - Launch app
-- `execute_command(cmd)` - Shell execution
+Example Tools: `click_button(label)`, `type_text(text)`, `open_application(name)`, `execute_command(cmd)`
**Server Types:**
@@ -237,32 +194,32 @@ sequenceDiagram
| **Local MCP Servers** | Run in same process via FastMCP | Fast, no network overhead | Limited to local capabilities |
| **Remote MCP Servers** | Connect via HTTP/SSE | Scalable, shared services | Network latency, external dependency |
-!!!example "MCP Server Configuration"
- ```yaml
- mcp_servers:
- data_collection:
- - name: "system_info"
- type: "local"
- class: "SystemInfoServer"
- - name: "ui_detector"
- type: "local"
- class: "UIDetectionServer"
-
- action:
- - name: "gui_automation"
- type: "local"
- class: "GUIAutomationServer"
- - name: "file_ops"
- type: "remote"
- url: "http://localhost:8080/mcp"
- ```
-
-See [MCP Integration](./mcp_integration.md) for comprehensive MCP server documentation.
+**Example MCP Server Configuration:**
+
+```yaml
+mcp_servers:
+ data_collection:
+ - name: "system_info"
+ type: "local"
+ class: "SystemInfoServer"
+ - name: "ui_detector"
+ type: "local"
+ class: "UIDetectionServer"
+
+ action:
+ - name: "gui_automation"
+ type: "local"
+ class: "GUIAutomationServer"
+ - name: "file_ops"
+ type: "remote"
+ url: "http://localhost:8080/mcp"
+```
+
+See [MCP Integration](../mcp/overview.md) for comprehensive MCP server documentation.
### 3. Device Profiling
-!!!info "Intelligent Task Assignment"
- The client automatically collects and reports **device information** to enable the server to make intelligent task routing decisions.
+The client automatically collects and reports **device information** to enable the server to make intelligent task routing decisions.
**Device Profile Structure:**
@@ -301,37 +258,17 @@ See [MCP Integration](./mcp_integration.md) for comprehensive MCP server documen
```mermaid
graph LR
- subgraph "Client Side"
- Detect[Device Detection]
- Collect[Info Collection]
- Report[Profile Reporting]
- end
-
- subgraph "Server Side"
- Receive[Receive Profile]
- Store[Store in Registry]
- Route[Task Routing]
- end
+ Client[Client Detects
Device Info]
+ Server[Server Stores
Profile]
+ Route[Server Routes
Tasks]
- subgraph "Task Assignment"
- Match[Match Requirements]
- Select[Select Best Device]
- Dispatch[Dispatch Task]
- end
-
- Detect --> Collect
- Collect --> Report
- Report --> Receive
- Receive --> Store
- Store --> Route
-
- Route --> Match
- Match --> Select
- Select --> Dispatch
+ Client -->|Report Profile| Server
+ Server -->|Match Requirements| Route
+ Route -->|Dispatch Task| Client
- style Collect fill:#bbdefb
- style Store fill:#c8e6c9
- style Select fill:#fff9c4
+ style Client fill:#bbdefb
+ style Server fill:#c8e6c9
+ style Route fill:#fff9c4
```
**Server Uses Profile For:**
@@ -347,8 +284,7 @@ See [Device Info Provider](./device_info.md) for detailed profiling documentatio
### 4. Resilient Communication
-!!!success "Built on AIP (Agent Interaction Protocol)"
- Robust, fault-tolerant communication with the server using strongly-typed messages.
+Robust, fault-tolerant communication with the server using strongly-typed AIP messages.
**Connection Lifecycle:**
@@ -411,26 +347,19 @@ See [WebSocket Client](./websocket_client.md) and [AIP Protocol](../aip/overview
```mermaid
sequenceDiagram
participant Main as Client Main
- participant Config as Config Loader
participant MCP as MCP Manager
participant WSC as WebSocket Client
participant Server
- Note over Main: Client Startup
- Main->>Config: Load Configuration
- Config-->>Main: UFO Config
-
Main->>MCP: Initialize MCP Servers
- MCP->>MCP: Create Data Collection Servers
- MCP->>MCP: Create Action Servers
- MCP-->>Main: Server Registry
+ MCP-->>Main: Server Registry Ready
- Main->>WSC: Create WebSocket Client
- WSC->>Server: Connect to ws://server:5000/ws
+ Main->>WSC: Create Client & Connect
+ WSC->>Server: WebSocket Connect
Server-->>WSC: Connection Established
WSC->>WSC: Collect Device Info
- WSC->>Server: REGISTRATION
(device_id, platform, capabilities)
+ WSC->>Server: REGISTRATION
Server-->>WSC: REGISTRATION_ACK
WSC->>WSC: Start Heartbeat Loop
@@ -440,7 +369,7 @@ sequenceDiagram
Server-->>WSC: HEARTBEAT_ACK
end
- Note over WSC,Server: Ready to Receive Commands
+ Note over WSC,Server: Ready to Execute Commands
```
**Initialization Steps:**
@@ -461,32 +390,23 @@ sequenceDiagram
```mermaid
sequenceDiagram
participant Server
- participant WSC as WebSocket Client
- participant UFC as UFO Client
+ participant Client as UFO Client
participant Comp as Computer
participant Tool as MCP Tool
- Server->>WSC: COMMAND
{type: "click_button", args: {label: "Submit"}}
-
- WSC->>UFC: on_command_received()
- UFC->>Comp: execute_command(command)
-
- Note over Comp: Lookup Tool in Registry
+ Server->>Client: COMMAND
{type: "click_button", args: {...}}
+ Client->>Comp: execute_command()
Comp->>Comp: find_tool("click_button")
alt Tool Found
- Comp->>Tool: execute(label="Submit")
-
- Note over Tool: Execute in Thread Pool
(100 min timeout)
-
- Tool-->>Comp: Success: Button clicked
- Comp-->>UFC: Result: {status: "success", output: "Clicked"}
- UFC-->>WSC: format_result()
- WSC-->>Server: COMMAND_RESULTS
{status: "completed", result: {...}}
+ Comp->>Tool: execute(args)
+ Note over Tool: Thread Pool Execution
6000s timeout
+ Tool-->>Comp: Success
+ Comp-->>Client: Result
+ Client-->>Server: COMMAND_RESULTS
{status: "completed"}
else Tool Not Found
- Comp-->>UFC: Error: Tool not found
- UFC-->>WSC: format_error()
- WSC-->>Server: ERROR
{error: "Tool 'click_button' not found"}
+ Comp-->>Client: Error
+ Client-->>Server: ERROR
{error: "Tool not found"}
end
```
@@ -494,8 +414,7 @@ sequenceDiagram
## 🖥️ Platform Support
-!!!info "Multi-Platform Execution"
- The client supports multiple platforms with platform-specific tool implementations.
+The client supports multiple platforms with platform-specific tool implementations.
| Platform | Status | Features | Native Tools |
|----------|--------|----------|--------------|
@@ -510,26 +429,27 @@ sequenceDiagram
- **Override**: Use `--platform` flag to specify manually
- **Validation**: Server validates platform matches task requirements
-!!!example "Platform-Specific Example"
- **Windows:**
- ```python
- # Windows-specific tools
- tools = [
- "open_windows_app(name='Excel')",
- "execute_powershell(script='Get-Process')",
- "read_registry(key='HKLM\\Software')"
- ]
- ```
-
- **Linux:**
- ```python
- # Linux-specific tools
- tools = [
- "execute_bash(command='ls -la')",
- "install_package(name='vim')",
- "control_systemd(service='nginx', action='restart')"
- ]
- ```
+**Platform-Specific Example:**
+
+**Windows:**
+```python
+# Windows-specific tools
+tools = [
+ "open_windows_app(name='Excel')",
+ "execute_powershell(script='Get-Process')",
+ "read_registry(key='HKLM\\Software')"
+]
+```
+
+**Linux:**
+```python
+# Linux-specific tools
+tools = [
+ "execute_bash(command='ls -la')",
+ "install_package(name='vim')",
+ "control_systemd(service='nginx', action='restart')"
+]
+```
---
@@ -537,6 +457,8 @@ sequenceDiagram
### Command-Line Arguments
+Start the UFO client with:
+
```bash
python -m ufo.client.client [OPTIONS]
```
@@ -550,21 +472,22 @@ python -m ufo.client.client [OPTIONS]
| `--ws` | `flag` | `False` | **Enable WebSocket mode** (required) | `--ws` |
| `--max-retries` | `int` | `5` | Connection retry limit | `--max-retries 10` |
| `--platform` | `str` | Auto-detect | Platform override | `--platform windows` |
-| `--log-level` | `str` | `INFO` | Logging verbosity | `--log-level DEBUG` |
-
-!!!tip "Quick Start Command"
- ```bash
- # Minimal command (default server)
- python -m ufo.client.client --ws --client-id my_device
-
- # Production command (custom server)
- python -m ufo.client.client \
- --ws \
- --client-id device_production_01 \
- --ws-server ws://ufo-server.company.com:5000/ws \
- --max-retries 10 \
- --log-level INFO
- ```
+| `--log-level` | `str` | `WARNING` | Logging verbosity | `--log-level DEBUG` |
+
+**Quick Start Command:**
+
+```bash
+# Minimal command (default server)
+python -m ufo.client.client --ws --client-id my_device
+
+# Production command (custom server)
+python -m ufo.client.client \
+ --ws \
+ --client-id device_production_01 \
+ --ws-server ws://ufo-server.company.com:5000/ws \
+ --max-retries 10 \
+ --log-level INFO
+```
### UFO Configuration
@@ -579,31 +502,32 @@ The client inherits settings from `config_dev.yaml`:
| **Logging** | Log levels, formats, destinations | File logging, console output |
| **Platform Settings** | OS-specific configurations | Windows UI automation settings |
-!!!example "Sample Configuration"
- ```yaml
- client:
- heartbeat_interval: 30 # seconds
- command_timeout: 6000 # seconds (100 minutes)
- max_concurrent_tools: 10
-
- mcp_servers:
- data_collection:
- - name: system_info
- type: local
- enabled: true
- action:
- - name: gui_automation
- type: local
- enabled: true
- settings:
- click_delay: 0.5
- typing_speed: 100 # chars per minute
-
- logging:
- level: INFO
- format: "%(asctime)s - %(name)s - %(levelname)s - %(message)s"
- file: "logs/client.log"
- ```
+**Sample Configuration:**
+
+```yaml
+client:
+ heartbeat_interval: 30 # seconds
+ command_timeout: 6000 # seconds (100 minutes)
+ max_concurrent_tools: 10
+
+mcp_servers:
+ data_collection:
+ - name: system_info
+ type: local
+ enabled: true
+ action:
+ - name: gui_automation
+ type: local
+ enabled: true
+ settings:
+ click_delay: 0.5
+ typing_speed: 100 # chars per minute
+
+logging:
+ level: INFO
+ format: "%(asctime)s - %(name)s - %(levelname)s - %(message)s"
+ file: "logs/client.log"
+```
See [Configuration Guide](../configuration/system/overview.md) for comprehensive documentation.
@@ -611,8 +535,7 @@ See [Configuration Guide](../configuration/system/overview.md) for comprehensive
## ⚠️ Error Handling
-!!!danger "Robust Fault Tolerance"
- The client is designed to handle various failure scenarios gracefully without crashing.
+The client is designed to handle various failure scenarios gracefully without crashing.
### Connection Failures
@@ -653,21 +576,22 @@ stateDiagram-v2
| Mechanism | Purpose | Default Value |
|-----------|---------|---------------|
| **Thread Pool Isolation** | Prevent one tool from blocking others | Enabled |
-| **Execution Timeout** | Kill hung tools | 100 minutes |
+| **Execution Timeout** | Kill hung tools | 6000 seconds (100 minutes) |
| **Exception Catching** | Graceful error handling | All tools wrapped |
| **Error Reporting** | Notify server of failures | Structured error messages |
-!!!example "Error Handling Example"
- ```python
- # Client automatically handles tool errors
- try:
- result = tool.execute(args)
- return {"status": "success", "result": result}
- except TimeoutError:
- return {"status": "error", "error": "Tool execution timeout"}
- except Exception as e:
- return {"status": "error", "error": str(e)}
- ```
+**Error Handling Example:**
+
+```python
+# Client automatically handles tool errors
+try:
+ result = tool.execute(args)
+ return {"status": "success", "result": result}
+except TimeoutError:
+ return {"status": "error", "error": "Tool execution timeout"}
+except Exception as e:
+ return {"status": "error", "error": str(e)}
+```
### Server Disconnection
@@ -685,100 +609,102 @@ stateDiagram-v2
### Development Best Practices
-!!!tip "Development Tips"
-
- **1. Use Unique Client IDs**
- ```bash
- # Bad: Generic ID
- --client-id client_001
-
- # Good: Descriptive ID
- --client-id device_win_dev_john_laptop
- ```
-
- **2. Start with INFO Logging**
- ```bash
- # Development: INFO for normal operation
- --log-level INFO
-
- # Debugging: DEBUG for troubleshooting
- --log-level DEBUG
- ```
-
- **3. Test MCP Connectivity First**
- ```python
- # Verify MCP servers are accessible before running client
- from ufo.module.mcp_server import MCPServerManager
-
- manager = MCPServerManager()
- servers = manager.create_servers_from_config()
- print(f"Initialized {len(servers)} MCP servers")
- ```
+**1. Use Unique Client IDs**
+
+```bash
+# Bad: Generic ID
+--client-id client_001
+
+# Good: Descriptive ID
+--client-id device_win_dev_john_laptop
+```
+
+**2. Start with INFO Logging**
+
+```bash
+# Development: WARNING for normal operation (default)
+--log-level WARNING
+
+# Debugging: DEBUG for troubleshooting
+--log-level DEBUG
+```
+
+**3. Test MCP Connectivity First**
+
+```python
+# Verify MCP servers are accessible before running client
+from ufo.client.mcp.mcp_server_manager import MCPServerManager
+
+manager = MCPServerManager()
+# Test server creation from configuration
+```
### Production Best Practices
-!!!success "Production Deployment"
-
- **1. Use Descriptive Client IDs**
- ```bash
- # Include environment, location, purpose
- --client-id device_windows_production_office_01
- --client-id device_linux_staging_lab_02
- ```
-
- **2. Configure Automatic Restart**
-
- **systemd (Linux):**
- ```ini
- [Unit]
- Description=UFO Agent Client
- After=network.target
-
- [Service]
- Type=simple
- User=ufo
- WorkingDirectory=/opt/ufo
- ExecStart=/usr/bin/python3 -m ufo.client.client \
- --ws \
- --client-id device_linux_prod_01 \
- --ws-server ws://ufo-server.internal:5000/ws \
- --log-level INFO
- Restart=always
- RestartSec=10
-
- [Install]
- WantedBy=multi-user.target
- ```
-
- **PM2 (Cross-platform):**
- ```json
- {
- "apps": [{
- "name": "ufo-client",
- "script": "python",
- "args": [
- "-m", "ufo.client.client",
- "--ws",
- "--client-id", "device_win_prod_01",
- "--ws-server", "ws://ufo-server.internal:5000/ws",
- "--log-level", "INFO"
- ],
- "cwd": "C:\\ufo",
- "restart_delay": 5000,
- "max_restarts": 10
- }]
- }
- ```
-
- **3. Monitor Connection Health**
- ```python
- # Check logs for connection status
- tail -f logs/client.log | grep -E "Connected|Disconnected|ERROR"
- ```
+**1. Use Descriptive Client IDs**
+
+```bash
+# Include environment, location, purpose
+--client-id device_windows_production_office_01
+--client-id device_linux_staging_lab_02
+```
+
+**2. Configure Automatic Restart**
+
+**systemd (Linux):**
+
+```ini
+[Unit]
+Description=UFO Agent Client
+After=network.target
+
+[Service]
+Type=simple
+User=ufo
+WorkingDirectory=/opt/ufo
+ExecStart=/usr/bin/python3 -m ufo.client.client \
+ --ws \
+ --client-id device_linux_prod_01 \
+ --ws-server ws://ufo-server.internal:5000/ws \
+ --log-level INFO
+Restart=always
+RestartSec=10
+
+[Install]
+WantedBy=multi-user.target
+```
+
+**PM2 (Cross-platform):**
+
+```json
+{
+ "apps": [{
+ "name": "ufo-client",
+ "script": "python",
+ "args": [
+ "-m", "ufo.client.client",
+ "--ws",
+ "--client-id", "device_win_prod_01",
+ "--ws-server", "ws://ufo-server.internal:5000/ws",
+ "--log-level", "INFO"
+ ],
+ "cwd": "C:\\ufo",
+ "restart_delay": 5000,
+ "max_restarts": 10
+ }]
+}
+```
+
+**3. Monitor Connection Health**
+
+```bash
+# Check logs for connection status
+tail -f logs/client.log | grep -E "Connected|Disconnected|ERROR"
+```
### Security Best Practices
-!!!warning "Security Considerations"
+!!! warning "Security Considerations"
| Practice | Description | Implementation |
|----------|-------------|----------------|
@@ -822,8 +748,7 @@ stateDiagram-v2
## 🔄 Client vs. Server
-!!!quote "Separation of Concerns"
- Understanding the **clear division** between client and server responsibilities is crucial for effective system design.
+Understanding the **clear division** between client and server responsibilities is crucial for effective system design.
**Responsibility Matrix:**
@@ -875,53 +800,48 @@ graph TB
style C3 fill:#c8e6c9
```
-!!!info "Decoupled Architecture Benefits"
- - ✅ **Independent Updates**: Modify server logic without touching clients
- - ✅ **Flexible Deployment**: Run clients on any platform
- - ✅ **Scalability**: Add more clients without server changes
- - ✅ **Maintainability**: Simpler client code, easier debugging
- - ✅ **Testability**: Test client and server independently
+**Decoupled Architecture Benefits:**
+- Independent Updates: Modify server logic without touching clients
+- Flexible Deployment: Run clients on any platform
+- Scalability: Add more clients without server changes
+- Maintainability: Simpler client code, easier debugging
+- Testability: Test client and server independently
---
## 🚀 Next Steps
-!!!tip "Get Started with UFO Client"
-
- **1. Run Your First Client**
- ```bash
- # Follow the quick start guide
- python -m ufo.client.client \
- --ws \
- --client-id my_first_device \
- --ws-server ws://localhost:5000/ws
- ```
- 👉 [Quick Start Guide](./quick_start.md)
-
- **2. Understand Registration Process**
- - How clients register with the server
- - Device profile structure
- - Registration acknowledgment
-
- 👉 [Server Quick Start](../server/quick_start.md) - Start server and connect clients
-
- **3. Explore MCP Integration**
- - Learn about MCP servers
- - Configure custom tools
- - Create your own MCP servers
-
- 👉 [MCP Integration](./mcp_integration.md)
-
- **4. Configure for Your Environment**
- - Customize MCP servers
- - Adjust timeouts and retries
- - Platform-specific settings
-
- 👉 [Configuration Guide](../configuration/system/overview.md)
-
- **5. Master the Protocol**
- - Deep dive into AIP messages
- - Understand message flow
- - Error handling patterns
-
- 👉 [AIP Protocol](../aip/overview.md)
+**1. Run Your First Client**
+
+```bash
+# Follow the quick start guide
+python -m ufo.client.client \
+ --ws \
+ --client-id my_first_device \
+ --ws-server ws://localhost:5000/ws
+```
+👉 [Quick Start Guide](./quick_start.md)
+
+**2. Understand Registration Process**
+
+Learn how clients register with the server, device profile structure, and registration acknowledgment.
+
+👉 [Server Quick Start](../server/quick_start.md) - Start server and connect clients
+
+**3. Explore MCP Integration**
+
+Learn about MCP servers, configure custom tools, and create your own MCP servers.
+
+👉 [MCP Integration](../mcp/overview.md)
+
+**4. Configure for Your Environment**
+
+Customize MCP servers, adjust timeouts and retries, and configure platform-specific settings.
+
+👉 [Configuration Guide](../configuration/system/overview.md)
+
+**5. Master the Protocol**
+
+Deep dive into AIP messages, understand message flow, and error handling patterns.
+
+👉 [AIP Protocol](../aip/overview.md)
diff --git a/documents/docs/client/quick_start.md b/documents/docs/client/quick_start.md
index 7763ca38d..395bfb7a7 100644
--- a/documents/docs/client/quick_start.md
+++ b/documents/docs/client/quick_start.md
@@ -1,25 +1,21 @@
# ⚡ Quick Start
-!!!quote "Plug and Play Execution"
- Get your device connected to the UFO² Agent Server in under **5 minutes**. No complex setup—just run a single command and start executing tasks.
-
-Get your device connected to the UFO² Agent Server and executing tasks in minutes.
+Get your device connected to the UFO Agent Server and start executing tasks in minutes. No complex setup—just run a single command.
---
## 📋 Prerequisites
-!!!info "What You Need"
- Before connecting a client device, ensure these requirements are met.
+Before connecting a client device, ensure these requirements are met:
| Requirement | Version/Details | Verification Command |
|-------------|-----------------|----------------------|
| **Python** | 3.10 or higher | `python --version` |
-| **UFO² Installation** | Latest version with dependencies | `python -c "import ufo; print('✅ Installed')"` |
+| **UFO Installation** | Latest version with dependencies | `python -c "import ufo; print('✅ Installed')"` |
| **Running Server** | Agent server accessible on network | `curl http://server:5000/api/health` |
| **Network Access** | Client can reach server WebSocket endpoint | Test connectivity to server |
-!!!tip "Server First!"
+!!! tip "Server First!"
**Always start the Agent Server before connecting clients.** The server must be running and accessible for clients to register successfully.
👉 [Server Quick Start Guide](../server/quick_start.md)
@@ -50,8 +46,7 @@ wscat -c ws://localhost:5000/ws
### Minimal Command (Local Server)
-!!!example "Simplest Connection"
- Connect to a server running on the same machine with default settings:
+Connect to a server running on the same machine with default settings:
```bash
python -m ufo.client.client --ws --client-id my_device
@@ -69,8 +64,7 @@ python -m ufo.client.client --ws --client-id my_device
### Connect to Remote Server
-!!!example "Production Setup"
- Connect to a server running on a different machine in your network:
+Connect to a server running on a different machine in your network:
```bash
python -m ufo.client.client \
@@ -87,7 +81,7 @@ python -m ufo.client.client \
### Override Platform Detection
-!!!tip "When to Override"
+!!! tip "When to Override"
Normally, the client auto-detects the platform (`windows` or `linux`). Override when:
- Running in container/VM with mismatched OS
@@ -104,29 +98,29 @@ python -m ufo.client.client \
### Complete Command (All Options)
-!!!example "Production-Ready Configuration"
- ```bash
- python -m ufo.client.client \
- --ws \
- --ws-server ws://192.168.1.100:5000/ws \
- --client-id device_windows_prod_01 \
- --platform windows \
- --max-retries 10 \
- --log-level INFO
- ```
-
- **Enhancements:**
-
- - 🔁 **10 retries**: Resilient to temporary network issues
- - 📋 **INFO logging**: Balanced verbosity (not DEBUG spam)
- - 🏷️ **Descriptive ID**: `device_windows_prod_01` clearly identifies environment
+Production-ready configuration with all available options:
+
+```bash
+python -m ufo.client.client \
+ --ws \
+ --ws-server ws://192.168.1.100:5000/ws \
+ --client-id device_windows_prod_01 \
+ --platform windows \
+ --max-retries 10 \
+ --log-level WARNING
+```
+
+**Enhancements:**
+
+- 🔁 **10 retries**: Resilient to temporary network issues
+- 📋 **WARNING logging**: Default level (less verbose than INFO)
+- 🏷️ **Descriptive ID**: `device_windows_prod_01` clearly identifies environment
---
## 📝 Connection Parameters Reference
-!!!info "CLI Argument Details"
- All available command-line options for the UFO client.
+All available command-line options for the UFO client.
### Required Parameters
@@ -152,9 +146,9 @@ python -m ufo.client.client \
| Parameter | Type | Default | Description | Example |
|-----------|------|---------|-------------|---------|
-| `--log-level` | `str` | `INFO` | Logging verbosity: `DEBUG`, `INFO`, `WARNING`, `ERROR` | `--log-level DEBUG` |
+| `--log-level` | `str` | `WARNING` | Logging verbosity: `DEBUG`, `INFO`, `WARNING`, `ERROR`, `CRITICAL`, `OFF` | `--log-level DEBUG` |
-!!!warning "Unique Client IDs - Critical!"
+!!! warning "Unique Client IDs - Critical!"
**Each device MUST have a unique `--client-id`.** Duplicate IDs will cause:
- ❌ Connection conflicts (devices disconnecting each other)
@@ -175,8 +169,7 @@ python -m ufo.client.client \
### Client Logs
-!!!success "Connection Established"
- When the client connects successfully, you'll see this sequence:
+When the client connects successfully, you'll see this sequence:
```log
INFO - Platform detected/specified: windows
@@ -195,28 +188,21 @@ sequenceDiagram
participant C as Client
participant S as Server
- Note over C: 1. Startup
- C->>C: Load Config
- C->>C: Initialize MCP Servers
-
- Note over C,S: 2. Connection
+ C->>C: Load Config & Initialize MCP
C->>S: WebSocket Connect
S-->>C: Connection Ack
- Note over C,S: 3. Registration
C->>C: Collect Device Info
- C->>S: REGISTRATION
(client_id, platform, capabilities)
+ C->>S: REGISTRATION
(id, platform, capabilities)
S->>S: Validate & Store
S-->>C: REGISTRATION_ACK
- Note over C,S: 4. Keep-Alive
loop Every 30s
C->>S: HEARTBEAT
S-->>C: HEARTBEAT_ACK
end
- Note over C,S: 5. Ready
- S->>C: COMMAND (when task dispatched)
+ Note over C,S: Ready for Commands
```
### Server Logs
@@ -293,8 +279,7 @@ print(response.json())
### Monitor Heartbeats
-!!!info "Keep-Alive Mechanism"
- The client sends **heartbeat messages every 30 seconds** to prove it's still alive.
+The client sends **heartbeat messages every 30 seconds** to prove it's still alive.
**Client Logs (DEBUG level):**
@@ -314,8 +299,7 @@ DEBUG - [WS] Heartbeat acknowledged for device_windows_001
## 🎯 Running Your First Task
-!!!example "Test Task Execution"
- Once the client is connected, dispatch a simple task from the server to verify end-to-end functionality.
+Once the client is connected, dispatch a simple task from the server to verify end-to-end functionality.
### Dispatch Task via HTTP API
@@ -393,23 +377,16 @@ sequenceDiagram
participant API as HTTP API
participant Server
participant Client
- participant Notepad
+ participant App as Notepad
- API->>Server: POST /api/dispatch
{client_id, request}
+ API->>Server: POST /dispatch
Server->>Server: Create Session
Server-->>API: {session_id, status}
- Server->>Client: COMMAND
(open notepad, type text)
-
- Client->>Client: Parse Command
- Client->>Notepad: Launch Notepad
- Notepad-->>Client: Window Opened
-
- Client->>Notepad: Type "Hello from UFO"
- Notepad-->>Client: Text Entered
-
- Client->>Server: COMMAND_RESULTS
(success)
- Server->>Server: Update Session
+ Server->>Client: COMMAND
+ Client->>App: Launch & Type
+ App-->>Client: Done
+ Client->>Server: COMMAND_RESULTS
```
---
@@ -418,11 +395,11 @@ sequenceDiagram
### 1. Connection Refused
-!!!failure "Symptom"
- ```log
- ERROR - [WS] Unexpected error: [Errno 10061] Connect call failed
- ERROR - [WS] Max retries reached. Exiting.
- ```
+**Symptom:**
+```log
+ERROR - [WS] Unexpected error: [Errno 10061] Connect call failed
+ERROR - [WS] Max retries reached. Exiting.
+```
**Root Causes:**
@@ -462,11 +439,11 @@ netstat -tuln | grep :5000
### 2. Registration Failed
-!!!failure "Symptom"
- ```log
- ERROR - [WS] [AIP] ❌ Failed to register as device_windows_001
- RuntimeError: Registration failed for device_windows_001
- ```
+**Symptom:**
+```log
+ERROR - [WS] [AIP] ❌ Failed to register as device_windows_001
+RuntimeError: Registration failed for device_windows_001
+```
**Root Causes:**
@@ -497,11 +474,11 @@ python -m ufo.client.client --ws --client-id NEW_UNIQUE_ID
### 3. Platform Detection Issues
-!!!warning "Symptom"
- ```log
- WARNING - Platform not detected correctly
- WARNING - Defaulting to platform: unknown
- ```
+**Symptom:**
+```log
+WARNING - Platform not detected correctly
+WARNING - Defaulting to platform: unknown
+```
**Solution:**
@@ -524,11 +501,11 @@ python -m ufo.client.client \
### 4. Heartbeat Timeout
-!!!failure "Symptom"
- ```log
- ERROR - [WS] Connection closed: ConnectionClosedError
- INFO - [WS] Reconnecting... (attempt 2/5)
- ```
+**Symptom:**
+```log
+ERROR - [WS] Connection closed: ConnectionClosedError
+INFO - [WS] Reconnecting... (attempt 2/5)
+```
**Root Causes:**
@@ -568,8 +545,7 @@ curl http://server:5000/api/health
## 🌐 Multiple Devices
-!!!info "Multi-Device Deployment"
- Connect multiple devices to the same server for **fleet management** and **task distribution**.
+Connect multiple devices to the same server for **fleet management** and **task distribution**.
### Example Configuration
@@ -650,13 +626,12 @@ Examples:
## 🔧 Running as Background Service
-!!!tip "Production Deployment"
+!!! tip "Production Deployment"
For production use, run the client as a **system service** that starts automatically and restarts on failure.
### Linux (systemd)
-!!!example "Systemd Service Configuration"
- Create `/etc/systemd/system/ufo-client.service`:
+Create `/etc/systemd/system/ufo-client.service`:
```ini
[Unit]
@@ -733,8 +708,7 @@ sudo journalctl -u ufo-client -f
### Windows (NSSM)
-!!!example "Windows Service with NSSM"
- **NSSM** (Non-Sucking Service Manager) wraps any application as a Windows service.
+**NSSM** (Non-Sucking Service Manager) wraps any application as a Windows service.
**1. Download NSSM:**
@@ -798,8 +772,7 @@ Register-ScheduledTask -TaskName "UFOClient" `
### PM2 (Cross-Platform)
-!!!example "Node.js PM2 Process Manager"
- **PM2** is a cross-platform process manager with built-in load balancing, monitoring, and auto-restart.
+**PM2** is a cross-platform process manager with built-in load balancing, monitoring, and auto-restart.
**1. Install PM2:**
@@ -875,8 +848,7 @@ pm2 logs ufo-client
## 🏭 Production Deployment Best Practices
-!!!success "Enterprise-Grade Deployment"
- Follow these best practices for reliable production deployments.
+Follow these best practices for reliable production deployments.
### 1. Descriptive Client IDs
@@ -945,36 +917,37 @@ journalctl -u ufo-client -f --since "1 hour ago"
### 4. Health Monitoring
-!!!example "Monitoring Script"
- ```bash
- #!/bin/bash
- # check-ufo-client.sh
-
- CLIENT_ID="device_prod_01"
- SERVER_URL="http://192.168.1.100:5000"
-
- # Check if client is connected
- response=$(curl -s "${SERVER_URL}/api/clients" | grep -c "${CLIENT_ID}")
-
- if [ "$response" -eq "0" ]; then
- echo "ALERT: Client ${CLIENT_ID} is not connected!"
- # Send alert (email, Slack, PagerDuty, etc.)
- exit 1
- else
- echo "OK: Client ${CLIENT_ID} is connected"
- exit 0
- fi
- ```
-
- **Run via cron:**
- ```cron
- # Check every 5 minutes
- */5 * * * * /usr/local/bin/check-ufo-client.sh
- ```
+**Monitoring Script:**
+
+```bash
+#!/bin/bash
+# check-ufo-client.sh
+
+CLIENT_ID="device_prod_01"
+SERVER_URL="http://192.168.1.100:5000"
+
+# Check if client is connected
+response=$(curl -s "${SERVER_URL}/api/clients" | grep -c "${CLIENT_ID}")
+
+if [ "$response" -eq "0" ]; then
+ echo "ALERT: Client ${CLIENT_ID} is not connected!"
+ # Send alert (email, Slack, PagerDuty, etc.)
+ exit 1
+else
+ echo "OK: Client ${CLIENT_ID} is connected"
+ exit 0
+fi
+```
+
+**Run via cron:**
+```cron
+# Check every 5 minutes
+*/5 * * * * /usr/local/bin/check-ufo-client.sh
+```
### 5. Secure Communication
-!!!danger "Production Security"
+!!! danger "Production Security"
**Never expose clients to the internet without these security measures:**
**Use WSS (WebSocket Secure):**
@@ -1010,8 +983,7 @@ python -m ufo.server.app \
## 🔧 Troubleshooting Commands
-!!!tip "Diagnostic Tools"
- Use these commands to diagnose connection and execution issues.
+Use these commands to diagnose connection and execution issues.
### Test Server Connectivity
@@ -1091,14 +1063,14 @@ curl -X POST http://localhost:5000/api/dispatch \
## 🚀 Next Steps
-!!!tip "Continue Learning"
+!!! tip "Continue Learning"
Now that your client is connected and running tasks:
**1. Understand Registration Flow**
Learn how clients register with the server and exchange device profiles:
-👉 [Server Overview - Client Registration](../server/overview.md)
+👉 [UFO Client Overview](./overview.md)
**2. Explore Device Information**
@@ -1116,7 +1088,7 @@ Understand the AIP protocol and WebSocket message flow:
Learn how to add custom tools and configure MCP servers:
-👉 [MCP Integration](./mcp_integration.md)
+👉 [MCP Integration](../mcp/overview.md)
**5. Study the AIP Protocol**
diff --git a/documents/docs/client/ufo_client.md b/documents/docs/client/ufo_client.md
index 8989944b3..ff1c73236 100644
--- a/documents/docs/client/ufo_client.md
+++ b/documents/docs/client/ufo_client.md
@@ -1,16 +1,10 @@
# 🎯 UFO Client
-!!!quote "The Execution Orchestrator"
- The **UFO Client** is the pure execution engine that receives commands from the server, routes them to appropriate tools via the CommandRouter, and aggregates results—**no reasoning, just deterministic execution**.
-
-The UFO Client is the bridge between network communication and local tool execution.
-
----
+The **UFO Client** is the execution engine that receives commands from the server, routes them to appropriate tools via the CommandRouter, and aggregates results. It focuses on stateless command execution, delegating all decision-making to the server.
## 📋 Overview
-!!!info "Core Responsibilities"
- The UFO Client focuses exclusively on **stateless command execution**, delegating all decision-making to the server.
+The UFO Client bridges network communication and local tool execution.
**Key Capabilities:**
@@ -23,57 +17,53 @@ The UFO Client is the bridge between network communication and local tool execut
| **State Management** | Maintains agent, process, and root names | Property setters with validation |
| **Manager Coordination** | Orchestrates ComputerManager and MCPServerManager | `reset()` cascades to all managers |
-!!!success "Stateless Execution Philosophy"
- The UFO Client **never makes decisions**. It:
-
- - ✅ Executes commands sent by the server
- - ✅ Routes commands to the appropriate tools
- - ✅ Returns execution results
- - ❌ Does **not** decide which commands to run
- - ❌ Does **not** interpret user requests
- - ❌ Does **not** store long-term state
+The UFO Client follows a stateless execution philosophy:
+
+- Executes commands sent by the server
+- Routes commands to the appropriate tools
+- Returns execution results
+- Does **not** decide which commands to run
+- Does **not** interpret user requests
+- Does **not** store long-term state
**Architectural Position:**
```mermaid
graph LR
- subgraph "Server Side (Orchestration)"
- Server[Agent Server]
+ subgraph Server["Server Side (Orchestration)"]
+ SRV[Agent Server]
LLM[LLM Reasoning]
end
- subgraph "Network Layer"
+ subgraph Network["Network Layer"]
WSC[WebSocket Client]
end
- subgraph "Client Side (Execution)"
+ subgraph Client["Client Side (Execution)"]
UFC[UFO Client]
CR[Command Router]
Tools[MCP Tools]
end
- Server -->|Commands| WSC
+ SRV -->|Commands| WSC
WSC -->|execute_step| UFC
UFC -->|execute| CR
CR -->|tool calls| Tools
Tools -->|results| CR
CR -->|results| UFC
UFC -->|results| WSC
- WSC -->|results| Server
+ WSC -->|results| SRV
- LLM -->|planning| Server
+ LLM -->|planning| SRV
- style Server fill:#ffe0b2
+ style SRV fill:#ffe0b2
style UFC fill:#bbdefb
style Tools fill:#c8e6c9
```
----
-
## 🏗️ Architecture
-!!!success "Simple and Focused Design"
- The UFO Client has a minimal API surface—just initialization, execution, and reset.
+The UFO Client has a minimal API surface—just initialization, execution, and reset.
### Component Structure
@@ -138,18 +128,15 @@ graph TB
| `command_router` | `CommandRouter` | Routes commands to appropriate computers |
| `task_lock` | `asyncio.Lock` | Ensures thread-safe execution |
| `client_id` | `str` | Unique identifier for this client (default: `"client_001"`) |
-| `platform` | `str` | Platform type (`"windows"` or `"linux"`) |
+| `platform` | `str` | Platform type (`"windows"` or `"linux"`) - auto-detected if not provided |
| `session_id` | `Optional[str]` | Current session identifier |
| `agent_name` | `Optional[str]` | Active agent (e.g., `"HostAgent"`, `"AppAgent"`) |
| `process_name` | `Optional[str]` | Process context (e.g., `"notepad.exe"`) |
| `root_name` | `Optional[str]` | Root operation name |
----
-
## 🚀 Initialization
-!!!example "Creating a UFO Client"
- The UFO Client requires two manager instances: MCPServerManager and ComputerManager.
+Creating a UFO Client requires two manager instances: MCPServerManager and ComputerManager.
```python
from ufo.client.ufo_client import UFOClient
@@ -190,12 +177,9 @@ client = UFOClient(
2. Initializes `task_lock` (`asyncio.Lock()`)
3. Sets session state to `None` (session_id, agent_name, process_name, root_name)
----
-
## 📊 Session State Management
-!!!info "Metadata Tracking"
- The UFO Client maintains contextual metadata for the current execution session.
+The UFO Client maintains contextual metadata for the current execution session.
### Session ID
@@ -289,14 +273,11 @@ except ValueError as e:
| `process_name` | `str`, `None` | `ValueError` |
| `root_name` | `str`, `None` | `ValueError` |
----
-
## ⚙️ Command Execution
### Execute Step (Main Entry Point)
-!!!info "Single-Step Execution"
- `execute_step()` processes one complete server message, extracting metadata and executing all commands.
+`execute_step()` processes one complete server message, extracting metadata and executing all commands.
**Signature:**
@@ -372,8 +353,7 @@ for result in action_results:
### Execute Actions
-!!!info "Batch Command Execution"
- `execute_actions()` executes a list of commands via the CommandRouter.
+`execute_actions()` executes a list of commands via the CommandRouter.
**Signature:**
@@ -448,8 +428,6 @@ results = await client.execute_actions(commands)
See [Computer Manager](./computer_manager.md) for command routing details.
----
-
## 🔄 State Reset
!!!warning "Critical for Multi-Task Execution"
@@ -512,21 +490,17 @@ graph TD
| **On task failure** | Clean up failed state |
| **On server disconnection** | Reset to known good state |
-!!!tip "Automatic Reset"
- The WebSocket client automatically calls `reset()` before starting new tasks:
-
- ```python
- async with self.ufo_client.task_lock:
- self.ufo_client.reset() # Automatic
- await self.task_protocol.send_task_request(...)
- ```
+**Note:** The WebSocket client automatically calls `reset()` before starting new tasks:
----
+```python
+async with self.ufo_client.task_lock:
+ self.ufo_client.reset() # Automatic
+ await self.task_protocol.send_task_request(...)
+```
## 🔒 Thread Safety
-!!!success "Async-Safe Execution"
- The UFO Client uses `asyncio.Lock` to prevent concurrent state modifications.
+The UFO Client uses `asyncio.Lock` to prevent concurrent state modifications.
**Lock Implementation:**
@@ -555,8 +529,6 @@ async with client.task_lock:
!!!warning "Single Task Execution"
The lock ensures only **one task executes at a time**. Attempting concurrent execution will block until the lock is released.
----
-
## 📋 Complete Execution Pipeline
```mermaid
@@ -595,14 +567,11 @@ sequenceDiagram
WSC->>Server: COMMAND_RESULTS (via AIP)
```
----
-
## ⚠️ Error Handling
### Command Execution Errors
-!!!info "Error Capture in Results"
- Individual command failures are captured in `Result` objects, not thrown as exceptions.
+Individual command failures are captured in `Result` objects, not thrown as exceptions.
**Error Result Structure:**
@@ -653,12 +622,9 @@ except ValueError as e:
| Property validation error | Property setters | `ValueError` exception |
| Unexpected errors | Any component | Logged, may propagate |
----
-
## 📝 Logging
-!!!info "Execution Visibility"
- The UFO Client logs all major events for debugging and monitoring.
+The UFO Client logs all major events for debugging and monitoring.
**Log Examples:**
@@ -698,14 +664,11 @@ INFO - Client state has been reset.
| Production | `INFO` | Monitor without spam |
| Troubleshooting | `DEBUG` | Diagnose issues |
----
-
## 💡 Usage Example
### Complete Workflow
-!!!example "End-to-End Execution"
- This example shows how to use the UFO Client in a typical workflow.
+This example shows how to use the UFO Client in a typical workflow.
```python
import asyncio
@@ -754,692 +717,98 @@ async def main():
asyncio.run(main())
```
----
-
## ✅ Best Practices
### Development Best Practices
-!!!tip "Defensive Programming"
-
- **1. Always Reset Between Tasks**
- ```python
- async with client.task_lock:
- client.reset() # Clear previous state
- await client.execute_step(new_server_response)
- ```
-
- **2. Use Property Setters (Not Direct Assignment)**
- ```python
- # ✅ Good - validates input
- client.session_id = "session_123"
-
- # ❌ Bad - bypasses validation
- client._session_id = "session_123"
- ```
-
- **3. Log Execution Progress**
- ```python
- self.logger.info(f"Executing {len(commands)} actions for {self.agent_name}")
- ```
-
- **4. Handle Errors Gracefully**
- ```python
- try:
- results = await client.execute_actions(commands)
- except Exception as e:
- self.logger.error(f"Execution failed: {e}", exc_info=True)
- # Error is also captured in results
- ```
-
-### Production Best Practices
-
-!!!success "Reliability and Monitoring"
-
- **1. Use Thread Locks Consistently**
- ```python
- # Always use task_lock for state operations
- async with client.task_lock:
- client.reset()
- results = await client.execute_step(msg)
- ```
-
- **2. Monitor Execution Times**
- ```python
- import time
-
- start = time.time()
- results = await client.execute_actions(commands)
- duration = time.time() - start
-
- if duration > 60: # Alert if > 1 minute
- logger.warning(f"Slow execution: {duration}s for {len(commands)} commands")
- ```
-
- **3. Validate Results**
- ```python
- # Check for failures
- failed_actions = [r for r in results if r.status == ResultStatus.ERROR]
- if failed_actions:
- logger.error(f"{len(failed_actions)} actions failed")
- # Report to monitoring system
- ```
-
----
-
-## 🔗 Integration Points
-
-### WebSocket Client Integration
-
-!!!info "Network Communication Layer"
- The WebSocket client uses UFO Client for all command execution.
-
-**Integration:**
-
-```python
-# In WebSocket client
-action_results = await self.ufo_client.execute_step(server_response)
-```
-
-See [WebSocket Client](./websocket_client.md) for communication details.
-
-### Command Router Integration
-
-!!!info "Command Delegation"
- The UFO Client delegates all execution to the CommandRouter.
-
-**Integration:**
+**1. Always Reset Between Tasks**
```python
-action_results = await self.command_router.execute(
- agent_name=self.agent_name,
- process_name=self.process_name,
- root_name=self.root_name,
- commands=commands
-)
-```
-
-See [Computer Manager](./computer_manager.md) for routing details.
-
-### Computer Manager Integration
-
-!!!info "Computer Instance Management"
- The Computer Manager maintains computer instances for tool execution.
-
-**Integration:**
-
-```python
-# Reset cascades to computer manager
-self.computer_manager.reset()
-```
-
-See [Computer Manager](./computer_manager.md) for management details.
-
-### MCP Server Manager Integration
-
-!!!info "MCP Server Lifecycle"
- The MCP Server Manager handles MCP server creation and cleanup.
-
-**Integration:**
-
-```python
-# Reset cascades to MCP server manager
-self.mcp_server_manager.reset()
-```
-
-See [MCP Integration](./mcp_integration.md) for MCP details.
-
----
-
-## 🚀 Next Steps
-
-!!!tip "Continue Learning"
-
- **1. Understand Network Communication**
-
- Learn how the WebSocket client uses UFO Client:
-
- 👉 [WebSocket Client](./websocket_client.md)
-
- **2. Explore Command Routing**
-
- See how commands are routed to the right tools:
-
- 👉 [Computer Manager](./computer_manager.md)
-
- **3. Study Device Profiling**
-
- Understand device information collection:
-
- 👉 [Device Info Provider](./device_info.md)
-
- **4. Learn About MCP Integration**
-
- Deep dive into MCP server management:
-
- 👉 [MCP Integration](./mcp_integration.md)
-
- **5. Master AIP Messages**
-
- Understand message structures:
-
- 👉 [AIP Messages](../aip/messages.md)
-
-## Architecture
-
-```
-┌────────────────────────────────────────────────┐
-│ UFOClient │
-├────────────────────────────────────────────────┤
-│ Session State: │
-│ • session_id - Current session identifier │
-│ • agent_name - Active agent name │
-│ • process_name - Process context │
-│ • root_name - Root operation name │
-├────────────────────────────────────────────────┤
-│ Execution: │
-│ • execute_step() - Process server message │
-│ • execute_actions() - Run command list │
-│ • reset() - Clear session state │
-├────────────────────────────────────────────────┤
-│ Dependencies: │
-│ • CommandRouter - Route commands to tools │
-│ • ComputerManager - Manage computer instances │
-│ • MCPServerManager - Manage MCP servers │
-└────────────────────────────────────────────────┘
-```
-
-## Initialization
-
-```python
-from ufo.client.ufo_client import UFOClient
-from ufo.client.computer import ComputerManager
-from ufo.client.mcp.mcp_server_manager import MCPServerManager
-
-# Initialize managers
-mcp_server_manager = MCPServerManager()
-computer_manager = ComputerManager(
- ufo_config.to_dict(),
- mcp_server_manager
-)
-
-# Create UFO client
-client = UFOClient(
- mcp_server_manager=mcp_server_manager,
- computer_manager=computer_manager,
- client_id="device_windows_001",
- platform="windows"
-)
-```
-
-**Parameters:**
-
-| Parameter | Type | Description |
-|-----------|------|-------------|
-| `mcp_server_manager` | MCPServerManager | Manages MCP server lifecycle |
-| `computer_manager` | ComputerManager | Manages computer instances |
-| `client_id` | str (optional) | Unique client identifier (default: `"client_001"`) |
-| `platform` | str (optional) | Platform type: `"windows"` or `"linux"` |
-
-## Session State
-
-The client maintains state for the current session:
-
-### Session ID
-
-```python
-# Set session ID
-client.session_id = "session_20251104_143022_abc123"
-
-# Get session ID
-current_session = client.session_id
-
-# Clear session ID
-client.reset() # Sets session_id to None
-```
-
-!!!warning "Thread Safety"
- Session state is protected by `task_lock` to prevent concurrent modifications.
-
-### Agent Name
-
-Identifies the active agent (HostAgent, AppAgent, etc.):
-
-```python
-# Set agent name
-client.agent_name = "HostAgent"
-
-# Get agent name
-agent = client.agent_name
-```
-
-### Process Name
-
-Identifies the process context:
-
-```python
-# Set process name
-client.process_name = "notepad.exe"
-
-# Get process name
-process = client.process_name
-```
-
-### Root Name
-
-Identifies the root operation:
-
-```python
-# Set root name
-client.root_name = "open_application"
-
-# Get root name
-root = client.root_name
-```
-
-## Command Execution
-
-### Execute Step
-
-Processes a complete step from the server:
-
-```python
-from aip.messages import ServerMessage
-
-# Receive server message
-server_response = ServerMessage.model_validate_json(msg)
-
-# Execute step
-action_results = await client.execute_step(server_response)
-
-# action_results is a list of Result objects
-```
-
-**Workflow:**
-
-```
-Server Message → Extract Metadata → Execute Actions → Return Results
- │ │ │ │
- │ │ │ │
- Actions List agent_name CommandRouter Result[]
- process_name Execution
- root_name
-```
-
-**Implementation:**
-
-```python
-async def execute_step(self, response: ServerMessage) -> List[Result]:
- """Perform a single step execution."""
-
- # Extract metadata from server response
- self.agent_name = response.agent_name
- self.process_name = response.process_name
- self.root_name = response.root_name
-
- # Execute actions
- action_results = await self.execute_actions(response.actions)
-
- return action_results
-```
-
-### Execute Actions
-
-Executes a list of commands:
-
-```python
-from aip.messages import Command
-
-commands = [
- Command(
- action="click",
- parameters={"control_label": "Start", "x": 10, "y": 10}
- ),
- Command(
- action="type_text",
- parameters={"text": "notepad"}
- ),
- Command(
- action="press_key",
- parameters={"key": "enter"}
- )
-]
-
-# Execute all commands
-results = await client.execute_actions(commands)
-
-# results contains Result object for each command
-```
-
-**Delegation to Command Router:**
-
-```python
-async def execute_actions(self, commands: Optional[List[Command]]) -> List[Result]:
- """Execute the actions provided by the server."""
-
- action_results = []
-
- if commands:
- self.logger.info(f"Executing {len(commands)} actions in total")
-
- # Delegate to CommandRouter
- action_results = await self.command_router.execute(
- agent_name=self.agent_name,
- process_name=self.process_name,
- root_name=self.root_name,
- commands=commands
- )
-
- return action_results
-```
-
-See [Computer Manager](./computer_manager.md) for command routing details.
-
-## State Reset
-
-Resets all session state and dependent managers:
-
-```python
-def reset(self):
- """Reset session state and dependent managers."""
-
- # Clear session state
- self._session_id = None
- self._agent_name = None
- self._process_name = None
- self._root_name = None
-
- # Reset managers
- self.computer_manager.reset()
- self.mcp_server_manager.reset()
-
- self.logger.info("Client state has been reset.")
-```
-
-**When to Reset:**
-
-- Before starting a new task
-- On task completion
-- On task failure
-- On server disconnection
-
-!!!tip "Automatic Reset"
- The WebSocket client automatically calls `reset()` before starting new tasks.
-
-## Thread Safety
-
-The client uses an asyncio lock for thread-safe execution:
-
-```python
-# In WebSocket client
async with client.task_lock:
- client.reset()
- await client.execute_step(server_response)
+ client.reset() # Clear previous state
+ await client.execute_step(new_server_response)
```
-**Protected Operations:**
-
-- Session state modifications
-- Command execution
-- State reset
-
-## Property Validation
-
-All session properties validate their inputs:
+**2. Use Property Setters (Not Direct Assignment)**
```python
-# Valid assignment
-client.session_id = "session_123" # ✅
-
-# Invalid assignment
-client.session_id = 12345 # ❌ ValueError: Session ID must be a string or None
-
-# Valid clearing
-client.session_id = None # ✅
-
-# Similar validation for agent_name, process_name, root_name
-```
-
-**Error Example:**
+# ✅ Good - validates input
+client.session_id = "session_123"
-```python
-try:
- client.agent_name = 123 # Not a string
-except ValueError as e:
- print(e) # "Agent name must be a string or None."
+# ❌ Bad - bypasses validation
+client._session_id = "session_123"
```
-## Integration with Command Router
-
-The client delegates execution to the `CommandRouter`:
+**3. Log Execution Progress**
```python
-from ufo.client.computer import CommandRouter
-
-self.command_router = CommandRouter(
- computer_manager=self.computer_manager
-)
-
-# Execute commands
-results = await self.command_router.execute(
- agent_name="HostAgent",
- process_name="explorer.exe",
- root_name="open_folder",
- commands=[...]
-)
-```
-
-**Command Router Responsibilities:**
-
-- Route commands to appropriate computer instances
-- Execute commands via MCP tools
-- Handle tool failures and timeouts
-- Aggregate results
-
-See [Computer Manager](./computer_manager.md) for routing details.
-
-## Execution Flow
-
-### Complete Execution Pipeline
-
-```
-1. WebSocket receives COMMAND message
- │
- ▼
-2. WebSocket calls client.execute_step(server_response)
- │
- ▼
-3. Client extracts metadata (agent_name, process_name, root_name)
- │
- ▼
-4. Client calls execute_actions(commands)
- │
- ▼
-5. Client delegates to CommandRouter.execute()
- │
- ▼
-6. CommandRouter routes to Computer instances
- │
- ▼
-7. Computer executes via MCP tools
- │
- ▼
-8. Results bubble back up to client
- │
- ▼
-9. Client returns results to WebSocket
- │
- ▼
-10. WebSocket sends COMMAND_RESULTS via AIP
+self.logger.info(f"Executing {len(commands)} actions for {self.agent_name}")
```
-## Error Handling
-
-### Command Execution Errors
+**4. Handle Errors Gracefully**
```python
try:
results = await client.execute_actions(commands)
except Exception as e:
- logger.error(f"Command execution failed: {e}", exc_info=True)
- # Error will be included in Result objects
-```
-
-**Error Results:**
-
-Individual command failures are captured in Result objects:
-
-```python
-from aip.messages import Result, ResultStatus
-
-error_result = Result(
- action="click",
- status=ResultStatus.ERROR,
- error_message="Control not found",
- observation="Failed to locate control with label 'Start'"
-)
-```
-
-### Property Validation Errors
-
-```python
-try:
- client.session_id = 12345 # Invalid type
-except ValueError as e:
- logger.error(f"Invalid session ID: {e}")
-```
-
-## Logging
-
-The client logs execution progress:
-
-**Initialization:**
-
-```
-INFO - UFO Client initialized for platform: windows
-```
-
-**Session State Changes:**
-
-```
-INFO - Session ID set to: session_20251104_143022_abc123
-INFO - Agent name set to: HostAgent
-INFO - Process name set to: notepad.exe
-INFO - Root name set to: open_application
-```
-
-**Execution:**
-
-```
-INFO - Executing 5 actions in total
-```
-
-**Reset:**
-
-```
-INFO - Client state has been reset.
-```
-
-## Usage Example
-
-### Complete Workflow
-
-```python
-import asyncio
-from ufo.client.ufo_client import UFOClient
-from aip.messages import ServerMessage, Command
-
-async def main():
- # Initialize client
- client = UFOClient(
- mcp_server_manager=mcp_manager,
- computer_manager=computer_manager,
- client_id="device_windows_001",
- platform="windows"
- )
-
- # Simulate server message
- server_msg = ServerMessage(
- type=ServerMessageType.COMMAND,
- session_id="session_123",
- response_id="resp_456",
- agent_name="HostAgent",
- process_name="explorer.exe",
- root_name="navigate_folder",
- actions=[
- Command(action="click", parameters={"label": "File"}),
- Command(action="click", parameters={"label": "New Folder"})
- ],
- status=TaskStatus.PROCESSING
- )
-
- # Execute step
- results = await client.execute_step(server_msg)
-
- # Process results
- for result in results:
- print(f"Action: {result.action}")
- print(f"Status: {result.status}")
- print(f"Observation: {result.observation}")
-
- # Reset for next task
- client.reset()
-
-asyncio.run(main())
+ self.logger.error(f"Execution failed: {e}", exc_info=True)
+ # Error is also captured in results
```
-## Best Practices
+### Production Best Practices
-**Always Reset Between Tasks**
+**1. Use Thread Locks Consistently**
```python
+# Always use task_lock for state operations
async with client.task_lock:
client.reset()
- await client.execute_step(new_server_response)
+ results = await client.execute_step(msg)
```
-**Use Property Setters**
+**2. Monitor Execution Times**
```python
-# Good - validates input
-client.session_id = "session_123"
-
-# Bad - bypasses validation
-client._session_id = "session_123"
-```
+import time
-**Log Execution Progress**
+start = time.time()
+results = await client.execute_actions(commands)
+duration = time.time() - start
-```python
-self.logger.info(f"Executing {len(commands)} actions for {self.agent_name}")
+if duration > 60: # Alert if > 1 minute
+ logger.warning(f"Slow execution: {duration}s for {len(commands)} commands")
```
-**Handle Errors Gracefully**
+**3. Validate Results**
```python
-try:
- results = await client.execute_actions(commands)
-except Exception as e:
- # Log and report error
- self.logger.error(f"Execution failed: {e}", exc_info=True)
- # Error is captured in results
+# Check for failures
+failed_actions = [r for r in results if r.status == ResultStatus.ERROR]
+if failed_actions:
+ logger.error(f"{len(failed_actions)} actions failed")
+ # Report to monitoring system
```
-## Integration Points
+## 🔗 Integration Points
+
+### WebSocket Client Integration
-### WebSocket Client
+The WebSocket client uses UFO Client for all command execution.
-The WebSocket client uses UFO Client for execution:
+**Integration:**
```python
+# In WebSocket client
action_results = await self.ufo_client.execute_step(server_response)
```
See [WebSocket Client](./websocket_client.md) for communication details.
-### Command Router
+### Command Router Integration
-The UFO Client delegates to the CommandRouter:
+The UFO Client delegates all execution to the CommandRouter.
+
+**Integration:**
```python
action_results = await self.command_router.execute(
@@ -1452,30 +821,42 @@ action_results = await self.command_router.execute(
See [Computer Manager](./computer_manager.md) for routing details.
-### Computer Manager
+### Computer Manager Integration
+
+The Computer Manager maintains computer instances for tool execution.
-Manages computer instances that execute commands:
+**Integration:**
```python
-self.computer_manager.reset() # Called during client reset
+# Reset cascades to computer manager
+self.computer_manager.reset()
```
See [Computer Manager](./computer_manager.md) for management details.
-### MCP Server Manager
+### MCP Server Manager Integration
-Manages MCP server lifecycle:
+The MCP Server Manager handles MCP server creation and cleanup.
+
+**Integration:**
```python
-self.mcp_server_manager.reset() # Called during client reset
+# Reset cascades to MCP server manager
+self.mcp_server_manager.reset()
```
See [MCP Integration](./mcp_integration.md) for MCP details.
-## Next Steps
+## 🚀 Next Steps
+
+**Continue Learning**
+
+1. **Understand Network Communication** - Learn how the WebSocket client uses UFO Client: [WebSocket Client](./websocket_client.md)
+
+2. **Explore Command Routing** - See how commands are routed to the right tools: [Computer Manager](./computer_manager.md)
+
+3. **Study Device Profiling** - Understand device information collection: [Device Info Provider](./device_info.md)
+
+4. **Learn About MCP Integration** - Deep dive into MCP server management: [MCP Integration](./mcp_integration.md)
-- [WebSocket Client](./websocket_client.md) - Communication layer
-- [Computer Manager](./computer_manager.md) - Command routing
-- [Device Info Provider](./device_info.md) - System profiling
-- [MCP Integration](./mcp_integration.md) - Tool management
-- [AIP Messages](../aip/messages.md) - Message structures
+5. **Master AIP Messages** - Understand message structures: [AIP Messages](../aip/messages.md)
\ No newline at end of file
diff --git a/documents/docs/client/websocket_client.md b/documents/docs/client/websocket_client.md
index f2604041e..24abea8eb 100644
--- a/documents/docs/client/websocket_client.md
+++ b/documents/docs/client/websocket_client.md
@@ -1,16 +1,10 @@
# 🔌 WebSocket Client
-!!!quote "The Communication Backbone"
- The **WebSocket Client** implements the **AIP (Agent Interaction Protocol)** for reliable, bidirectional communication between device clients and the Agent Server. It's the transport layer that makes remote task execution possible.
-
-The WebSocket client provides the low-level communication infrastructure for UFO device clients.
-
----
+The **WebSocket Client** implements the **AIP (Agent Interaction Protocol)** for reliable, bidirectional communication between device clients and the Agent Server. It provides the low-level communication infrastructure for UFO device clients.
## 📋 Overview
-!!!info "Core Capabilities"
- The WebSocket client handles all network communication aspects, allowing the UFO Client to focus on task execution.
+The WebSocket client handles all network communication aspects, allowing the UFO Client to focus on task execution.
**Key Responsibilities:**
@@ -51,12 +45,9 @@ graph LR
style Server fill:#ffe0b2
```
----
-
## 🏗️ Architecture
-!!!success "Layered Design"
- The WebSocket client is organized into distinct layers for connection management, protocol handling, and message routing.
+The WebSocket client is organized into distinct layers for connection management, protocol handling, and message routing.
### Component Structure
@@ -169,30 +160,31 @@ sequenceDiagram
### Initialization Code
-!!!example "Creating a WebSocket Client"
- ```python
- from ufo.client.websocket import UFOWebSocketClient
- from ufo.client.ufo_client import UFOClient
-
- # Create UFO client (execution engine)
- ufo_client = UFOClient(
- mcp_server_manager=mcp_manager,
- computer_manager=computer_manager,
- client_id="device_windows_001",
- platform="windows"
- )
-
- # Create WebSocket client (communication layer)
- ws_client = UFOWebSocketClient(
- ws_url="ws://localhost:5000/ws",
- ufo_client=ufo_client,
- max_retries=3, # Default: 3 attempts
- timeout=120 # Heartbeat interval parameter (actual default: 30s)
- )
-
- # Connect and start listening (blocking call)
- await ws_client.connect_and_listen()
- ```
+Creating a WebSocket client:
+
+```python
+from ufo.client.websocket import UFOWebSocketClient
+from ufo.client.ufo_client import UFOClient
+
+# Create UFO client (execution engine)
+ufo_client = UFOClient(
+ mcp_server_manager=mcp_manager,
+computer_manager=computer_manager,
+ client_id="device_windows_001",
+ platform="windows"
+)
+
+# Create WebSocket client (communication layer)
+ws_client = UFOWebSocketClient(
+ ws_url="ws://localhost:5000/ws",
+ ufo_client=ufo_client,
+ max_retries=3, # Default: 3 attempts
+ timeout=120 # Heartbeat interval in seconds (default: 120)
+)
+
+# Connect and start listening (blocking call)
+await ws_client.connect_and_listen()
+```
**Constructor Parameters:**
@@ -201,15 +193,13 @@ sequenceDiagram
| `ws_url` | `str` | Required | WebSocket server URL (e.g., `ws://localhost:5000/ws`) |
| `ufo_client` | `UFOClient` | Required | UFO client instance for command execution |
| `max_retries` | `int` | `3` | Maximum connection retry attempts |
-| `timeout` | `float` | `120` | Parameter passed to heartbeat_loop (note: function default is 30s) |
+| `timeout` | `float` | `120` | Heartbeat interval in seconds (passed to `heartbeat_loop()`) |
-!!!warning "Timeout Parameter Clarification"
- The `timeout` parameter (default 120) is passed to `heartbeat_loop()`, but the function itself defaults to 30 seconds if not explicitly overridden in the call. See source code: `async def heartbeat_loop(self, interval: float = 30)`.
+**Note:** The `timeout` parameter is passed to `heartbeat_loop(interval)` to control heartbeat frequency. While `heartbeat_loop()` has a default of 30s in its signature, the client constructor uses 120s which is passed when calling the method.
### Connection Establishment Details
-!!!info "WebSocket Configuration"
- The client uses specific WebSocket parameters optimized for long-running task execution:
+The client uses specific WebSocket parameters optimized for long-running task execution:
**WebSocket Connection Parameters:**
@@ -233,17 +223,13 @@ async with websockets.connect(
| `close_timeout` | **10 seconds** | Quick cleanup on intentional disconnect |
| `max_size` | **100 MB** | Supports large screenshots, logs, file transfers |
-!!!success "Optimized for Long Operations"
- The 180-second `ping_timeout` ensures the connection stays alive during lengthy tool executions (up to 100 minutes per tool).
-
----
+**Note:** The 180-second `ping_timeout` ensures the connection stays alive during lengthy tool executions (up to 100 minutes per tool).
## 📝 Registration Flow
### Device Information Collection
-!!!info "Push Model"
- UFO uses a **push model** for device information: clients proactively send their profile during registration, rather than waiting for the server to request it. This reduces latency for constellation (multi-client) scenarios.
+UFO uses a **push model** for device information: clients proactively send their profile during registration, rather than waiting for the server to request it. This reduces latency for constellation (multi-client) scenarios.
**Device Info Collection:**
@@ -396,8 +382,7 @@ RuntimeError: Registration failed for device_windows_001
## 💓 Heartbeat Mechanism
-!!!success "Connection Health Monitoring"
- Heartbeats prove the client is still alive and responsive, allowing the server to detect disconnected clients quickly.
+Heartbeats prove the client is still alive and responsive, allowing the server to detect disconnected clients quickly.
### Heartbeat Loop Implementation
@@ -431,16 +416,17 @@ async def heartbeat_loop(self, interval: float = 30) -> None:
break # Exit loop if connection is closed
```
-!!!tip "Customizing Heartbeat Interval"
- Adjust the interval when calling the heartbeat loop:
-
- ```python
- # In handle_messages():
- await asyncio.gather(
- self.recv_loop(),
- self.heartbeat_loop(interval=60) # Custom 60-second interval
- )
- ```
+**Customizing Heartbeat Interval:**
+
+Adjust the interval when calling the heartbeat loop:
+
+```python
+# In handle_messages():
+await asyncio.gather(
+ self.recv_loop(),
+ self.heartbeat_loop(interval=60) # Custom 60-second interval
+)
+```
### Heartbeat Message Structure
@@ -493,8 +479,7 @@ stateDiagram-v2
### Message Router
-!!!info "Type-Based Dispatch"
- All incoming messages are validated against the AIP schema and routed based on their `type` field.
+All incoming messages are validated against the AIP schema and routed based on their `type` field.
**Message Dispatcher Code:**
@@ -620,8 +605,7 @@ async def start_task(self, request_text: str, task_name: str | None):
### Command Execution Handler
-!!!info "Server-Driven Execution"
- The server sends specific commands (tool calls) to execute, and the client returns results.
+The server sends specific commands (tool calls) to execute, and the client returns results.
**Command Execution Flow:**
@@ -689,8 +673,7 @@ async def handle_task_end(self, server_response: ServerMessage):
### Connection Error Recovery
-!!!success "Automatic Retry with Exponential Backoff"
- The client automatically retries failed connections using exponential backoff to avoid overwhelming the server.
+The client automatically retries failed connections using exponential backoff to avoid overwhelming the server.
**Retry Logic:**
@@ -743,16 +726,17 @@ async def _maybe_retry(self):
| 3rd retry | 8 seconds | 14s |
| **Max retries reached** | Exit | - |
-!!!note "Default Max Retries = 3"
- Based on source code: `max_retries: int = 3` in constructor. Increase for unreliable networks:
-
- ```python
- ws_client = UFOWebSocketClient(
- ws_url="ws://...",
- ufo_client=ufo_client,
- max_retries=10 # More resilient
- )
- ```
+**Default Max Retries = 3**
+
+Based on source code: `max_retries: int = 3` in constructor. Increase for unreliable networks:
+
+```python
+ws_client = UFOWebSocketClient(
+ ws_url="ws://...",
+ ufo_client=ufo_client,
+ max_retries=10 # More resilient
+)
+```
### Message Parsing Errors
@@ -767,8 +751,7 @@ except Exception as e:
# Message is dropped, client continues listening
```
-!!!info "Error Isolation"
- Message parsing errors don't crash the client—the error is logged and the receive loop continues.
+Message parsing errors don't crash the client—the error is logged and the receive loop continues.
### Registration Error Handling
@@ -786,15 +769,13 @@ except Exception as e:
}
```
-!!!tip "Graceful Degradation"
- If device info collection fails, registration still proceeds with minimal metadata (timestamp only).
+If device info collection fails, registration still proceeds with minimal metadata (timestamp only).
---
## 🔌 AIP Protocol Integration
-!!!info "Three Protocol Handlers"
- The WebSocket client uses three specialized AIP protocols for different communication patterns.
+The WebSocket client uses three specialized AIP protocols for different communication patterns.
### 1. Registration Protocol
@@ -885,8 +866,7 @@ See [AIP Task Execution Protocol](../aip/protocols.md#task-execution-protocol) f
### State Checking
-!!!info "Connection Status Verification"
- Use `is_connected()` to check if the client is ready to send messages.
+Use `is_connected()` to check if the client is ready to send messages.
**Implementation:**
@@ -911,8 +891,7 @@ else:
### Connected Event
-!!!tip "Async Coordination"
- The `connected_event` is an `asyncio.Event` that signals successful registration.
+The `connected_event` is an `asyncio.Event` that signals successful registration.
**Usage Pattern:**
@@ -933,89 +912,90 @@ await ws_client.start_task("Open Notepad", "task_notepad")
| Registered | **Set** | ✅ Ready to send/receive messages |
| Disconnected | Cleared | Connection lost, will retry |
----
-
## ✅ Best Practices
### Development Best Practices
-!!!tip "Debugging and Testing"
-
- **1. Enable DEBUG Logging**
- ```python
- import logging
- logging.basicConfig(level=logging.DEBUG)
- ```
-
- **Output:**
- ```log
- DEBUG - [WS] \[AIP] Heartbeat sent
- DEBUG - [WS] \[AIP] Heartbeat failed (connection closed): ...
- INFO - [WS] Received message: ServerMessage(type='COMMAND', ...)
- ```
-
- **2. Test Connection Before Full Integration**
- ```python
- # Test just connection and registration
- ws_client = UFOWebSocketClient(ws_url, ufo_client)
- await ws_client.connect_and_listen() # Should register successfully
- ```
-
- **3. Handle Connection Loss Gracefully**
- ```python
- try:
- await ws_client.connect_and_listen()
- except Exception as e:
- logger.error(f"WebSocket client error: {e}")
- # Implement recovery (e.g., alert, restart)
- ```
+**1. Enable DEBUG Logging**
+
+```python
+import logging
+logging.basicConfig(level=logging.DEBUG)
+```
+
+**Output:**
+```log
+DEBUG - [WS] [AIP] Heartbeat sent
+DEBUG - [WS] [AIP] Heartbeat failed (connection closed): ...
+INFO - [WS] Received message: ServerMessage(type='COMMAND', ...)
+```
+
+**2. Test Connection Before Full Integration**
+
+```python
+# Test just connection and registration
+ws_client = UFOWebSocketClient(ws_url, ufo_client)
+await ws_client.connect_and_listen() # Should register successfully
+```
+
+**3. Handle Connection Loss Gracefully**
+
+```python
+try:
+ await ws_client.connect_and_listen()
+except Exception as e:
+ logger.error(f"WebSocket client error: {e}")
+ # Implement recovery (e.g., alert, restart)
+```
### Production Best Practices
-!!!success "Reliability and Monitoring"
-
- **1. Use Appropriate Retry Limits**
-
- For production networks with occasional instability:
- ```python
- ws_client = UFOWebSocketClient(
- ws_url="wss://production-server.com/ws",
- ufo_client=ufo_client,
- max_retries=10 # More retries for resilience
- )
- ```
-
- **2. Monitor Connection Health**
-
- Log heartbeat success/failure for alerting:
- ```python
- # In heartbeat_loop (add custom monitoring):
- try:
- await self.heartbeat_protocol.send_heartbeat(...)
- self.logger.debug("[WS] ✅ Heartbeat sent successfully")
- # Update metrics: heartbeat_success_count++
- except Exception as e:
- self.logger.error(f"[WS] ❌ Heartbeat failed: {e}")
- # Trigger alert: connection_health_alert()
- ```
-
- **3. Use Secure WebSocket (WSS)**
- ```python
- # Production: Encrypted WebSocket
- ws_client = UFOWebSocketClient(
- ws_url="wss://ufo-server.company.com/ws", # WSS, not WS
- ufo_client=ufo_client
- )
- ```
-
- **4. Clean State on Reconnection**
-
- The client automatically resets state:
- ```python
- async with self.ufo_client.task_lock:
- self.ufo_client.reset() # Clears session state
- # Send new task request
- ```
+**1. Use Appropriate Retry Limits**
+
+For production networks with occasional instability:
+
+```python
+ws_client = UFOWebSocketClient(
+ ws_url="wss://production-server.com/ws",
+ ufo_client=ufo_client,
+ max_retries=10 # More retries for resilience
+)
+```
+
+**2. Monitor Connection Health**
+
+Log heartbeat success/failure for alerting:
+
+```python
+# In heartbeat_loop (add custom monitoring):
+try:
+ await self.heartbeat_protocol.send_heartbeat(...)
+ self.logger.debug("[WS] ✅ Heartbeat sent successfully")
+ # Update metrics: heartbeat_success_count++
+except Exception as e:
+ self.logger.error(f"[WS] ❌ Heartbeat failed: {e}")
+ # Trigger alert: connection_health_alert()
+```
+
+**3. Use Secure WebSocket (WSS)**
+
+```python
+# Production: Encrypted WebSocket
+ws_client = UFOWebSocketClient(
+ ws_url="wss://ufo-server.company.com/ws", # WSS, not WS
+ ufo_client=ufo_client
+)
+```
+
+**4. Clean State on Reconnection**
+
+The client automatically resets state:
+
+```python
+async with self.ufo_client.task_lock:
+ self.ufo_client.reset() # Clears session state
+ # Send new task request
+```
### Error Handling Best Practices
@@ -1054,8 +1034,7 @@ await ws_client.start_task("Open Notepad", "task_notepad")
### UFO Client Integration
-!!!info "Execution Delegation"
- The WebSocket client delegates all command execution to the UFO Client.
+The WebSocket client delegates all command execution to the UFO Client.
**Execution Flow:**
@@ -1077,8 +1056,7 @@ See [UFO Client](./ufo_client.md) for execution details.
### Device Info Provider Integration
-!!!info "System Profiling"
- Device information is collected once during registration.
+Device information is collected once during registration.
**Integration:**
@@ -1095,8 +1073,7 @@ See [Device Info Provider](./device_info.md) for profiling details.
### AIP Transport Integration
-!!!info "Low-Level Communication"
- All messages go through the WebSocket transport layer.
+All messages go through the WebSocket transport layer.
**Transport Creation:**
@@ -1113,444 +1090,16 @@ self.transport = WebSocketTransport(ws)
See [AIP Transport Layer](../aip/transport.md) for transport details.
----
-
## 🚀 Next Steps
-!!!tip "Continue Learning"
-
- **1. Connect Your Client**
-
- Follow the step-by-step guide to connect a device:
-
- 👉 [Quick Start Guide](./quick_start.md)
-
- **2. Understand Command Execution**
-
- Learn how the UFO Client executes commands:
-
- 👉 [UFO Client Documentation](./ufo_client.md)
-
- **3. Explore Device Profiling**
-
- See what device information is collected:
-
- 👉 [Device Info Provider](./device_info.md)
-
- **4. Master the AIP Protocol**
-
- Deep dive into message formats and protocol details:
-
- 👉 [AIP Protocol Guide](../aip/protocols.md)
-
- **5. Study Server-Side Registration**
-
- Understand how the server handles client registration:
-
- 👉 [Server Overview](../server/overview.md)
- )
- self.logger.debug("[WS] \[AIP] Heartbeat sent")
- except (ConnectionError, IOError) as e:
- self.logger.debug(f"[WS] \[AIP] Heartbeat failed: {e}")
- break # Exit loop if connection closed
-```
-
-**Default Interval:** 120 seconds
-
-**Heartbeat Message:**
-
-```json
-{
- "type": "HEARTBEAT",
- "client_id": "device_windows_001",
- "timestamp": "2025-11-04T14:30:22.123Z"
-}
-```
-
-!!!tip "Tuning Heartbeat"
- Adjust the interval in the `handle_messages()` call:
- ```python
- await self.heartbeat_loop(interval=60) # 60 second heartbeats
- ```
-
-## Message Handling
-
-### Message Dispatcher
-
-The client routes incoming messages by type:
-
-```python
-async def handle_message(self, msg: str):
- """Dispatch messages based on their type."""
- data = ServerMessage.model_validate_json(msg)
- msg_type = data.type
-
- if msg_type == ServerMessageType.TASK:
- await self.start_task(data.user_request, data.task_name)
- elif msg_type == ServerMessageType.HEARTBEAT:
- self.logger.info("[WS] Heartbeat received")
- elif msg_type == ServerMessageType.TASK_END:
- await self.handle_task_end(data)
- elif msg_type == ServerMessageType.ERROR:
- self.logger.error(f"[WS] Server error: {data.error}")
- elif msg_type == ServerMessageType.COMMAND:
- await self.handle_commands(data)
- else:
- self.logger.warning(f"[WS] Unknown message type: {msg_type}")
-```
-
-### Task Start
-
-When the server requests a task:
-
-```python
-async def start_task(self, request_text: str, task_name: str | None):
- """Start a new task."""
-
- # Check if another task is running
- if self.current_task is not None and not self.current_task.done():
- self.logger.warning("[WS] Task still running, ignoring new task")
- return
-
- # Reset client state
- async with self.ufo_client.task_lock:
- self.ufo_client.reset()
-
- # Send task request via AIP
- await self.task_protocol.send_task_request(
- request=request_text,
- task_name=task_name or str(uuid4()),
- session_id=self.ufo_client.session_id,
- client_id=self.ufo_client.client_id,
- metadata={"platform": self.ufo_client.platform}
- )
-```
-
-!!!warning "Single Task Execution"
- The client only executes one task at a time. New task requests while a task is running are ignored.
-
-### Command Execution
-
-When the server sends commands to execute:
-
-```python
-async def handle_commands(self, server_response: ServerMessage):
- """Handle commands from server."""
-
- response_id = server_response.response_id
- task_status = server_response.status
- self.session_id = server_response.session_id
-
- # Execute commands via UFO Client
- action_results = await self.ufo_client.execute_step(server_response)
-
- # Send results back via AIP
- await self.task_protocol.send_task_result(
- session_id=self.session_id,
- prev_response_id=response_id,
- action_results=action_results,
- status=task_status,
- client_id=self.ufo_client.client_id
- )
-
- # Check for task completion
- if task_status in [TaskStatus.COMPLETED, TaskStatus.FAILED]:
- await self.handle_task_end(server_response)
-```
-
-### Task Completion
-
-```python
-async def handle_task_end(self, server_response: ServerMessage):
- """Handle task end messages."""
-
- if server_response.status == TaskStatus.COMPLETED:
- self.logger.info(f"[WS] Task {self.session_id} completed")
- elif server_response.status == TaskStatus.FAILED:
- self.logger.error(f"[WS] Task {self.session_id} failed: {server_response.error}")
-```
-
-## Error Handling
-
-### Connection Errors
-
-**Automatic Retry with Exponential Backoff:**
-
-```python
-async def connect_and_listen(self):
- """Connect with automatic retry."""
- while self.retry_count < self.max_retries:
- try:
- async with websockets.connect(...) as ws:
- await self.register_client()
- self.retry_count = 0 # Reset on success
- await self.handle_messages()
- except (ConnectionClosedError, ConnectionClosedOK) as e:
- self.logger.error(f"[WS] Connection closed: {e}")
- self.retry_count += 1
- await self._maybe_retry()
- except Exception as e:
- self.logger.error(f"[WS] Unexpected error: {e}", exc_info=True)
- self.retry_count += 1
- await self._maybe_retry()
-
- self.logger.error("[WS] Max retries reached. Exiting.")
-```
-
-**Exponential Backoff:**
-
-```python
-async def _maybe_retry(self):
- """Exponential backoff before retry."""
- if self.retry_count < self.max_retries:
- wait_time = 2 ** self.retry_count # 1s, 2s, 4s, 8s, 16s...
- self.logger.info(f"[WS] Retrying in {wait_time}s...")
- await asyncio.sleep(wait_time)
-```
-
-### Message Parsing Errors
-
-```python
-try:
- data = ServerMessage.model_validate_json(msg)
- # Process message
-except Exception as e:
- self.logger.error(f"[WS] Error handling message: {e}", exc_info=True)
- # Send error report via AIP
- error_msg = ClientMessage(
- type=ClientMessageType.ERROR,
- error=str(e),
- client_id=self.ufo_client.client_id,
- timestamp=datetime.now(timezone.utc).isoformat()
- )
- await self.transport.send(error_msg.model_dump_json().encode())
-```
-
-### Registration Errors
-
-If device info collection fails:
-
-```python
-try:
- system_info = DeviceInfoProvider.collect_system_info(...)
- metadata = {"system_info": system_info.to_dict()}
-except Exception as e:
- self.logger.error(f"[WS] \[AIP] Error collecting device info: {e}")
- # Continue with minimal metadata
- metadata = {"registration_time": datetime.now(timezone.utc).isoformat()}
-```
-
-## AIP Protocol Integration
-
-The WebSocket client uses three AIP protocols:
-
-### Registration Protocol
-
-```python
-from aip.protocol.registration import RegistrationProtocol
-
-self.registration_protocol = RegistrationProtocol(self.transport)
-
-# Register as device
-success = await self.registration_protocol.register_as_device(
- device_id="device_windows_001",
- metadata={"system_info": {...}},
- platform="windows"
-)
-```
+**Continue Learning**
-See [AIP Registration Protocol](../aip/protocols.md#registration-protocol) for details.
+1. **Connect Your Client** - Follow the step-by-step guide: [Quick Start Guide](./quick_start.md)
-### Heartbeat Protocol
-
-```python
-from aip.protocol.heartbeat import HeartbeatProtocol
-
-self.heartbeat_protocol = HeartbeatProtocol(self.transport)
-
-# Send heartbeat
-await self.heartbeat_protocol.send_heartbeat("device_windows_001")
-```
-
-See [AIP Heartbeat Protocol](../aip/protocols.md#heartbeat-protocol) for details.
-
-### Task Execution Protocol
-
-```python
-from aip.protocol.task_execution import TaskExecutionProtocol
-
-self.task_protocol = TaskExecutionProtocol(self.transport)
-
-# Send task request
-await self.task_protocol.send_task_request(
- request="Open Notepad",
- task_name="task_001",
- session_id=None,
- client_id="device_windows_001"
-)
-
-# Send task result
-await self.task_protocol.send_task_result(
- session_id="session_123",
- prev_response_id="resp_456",
- action_results=[...],
- status=TaskStatus.COMPLETED,
- client_id="device_windows_001"
-)
-```
+2. **Understand Command Execution** - Learn how the UFO Client executes commands: [UFO Client Documentation](./ufo_client.md)
-See [AIP Task Execution Protocol](../aip/protocols.md#task-execution-protocol) for details.
+3. **Explore Device Profiling** - See what device information is collected: [Device Info Provider](./device_info.md)
-## Connection State
+4. **Master the AIP Protocol** - Deep dive into message formats: [AIP Protocol Guide](../aip/protocols.md)
-### State Checking
-
-```python
-def is_connected(self) -> bool:
- """Check if WebSocket is connected."""
- return (
- self.connected_event.is_set()
- and self._ws is not None
- and not self._ws.closed
- )
-```
-
-**Usage:**
-
-```python
-if ws_client.is_connected():
- await ws_client.start_task("Open Calculator", "task_calc")
-else:
- logger.error("Not connected to server")
-```
-
-### Connected Event
-
-The `connected_event` is an `asyncio.Event` that signals successful registration:
-
-```python
-# Wait for connection
-await ws_client.connected_event.wait()
-
-# Now safe to send task requests
-await ws_client.start_task("Open Notepad", "task_notepad")
-```
-
-## Best Practices
-
-**Handle Connection Loss Gracefully**
-
-```python
-try:
- await ws_client.connect_and_listen()
-except Exception as e:
- logger.error(f"WebSocket client error: {e}")
- # Implement your recovery strategy
-```
-
-**Use Appropriate Retry Limits**
-
-For unreliable networks, increase max retries:
-
-```python
-ws_client = UFOWebSocketClient(
- ws_url="ws://...",
- ufo_client=ufo_client,
- max_retries=10 # More retries for unstable connections
-)
-```
-
-**Monitor Connection Health**
-
-Log heartbeat success/failure:
-
-```python
-try:
- await self.heartbeat_protocol.send_heartbeat(...)
- self.logger.debug("[WS] ✅ Heartbeat sent successfully")
-except Exception as e:
- self.logger.error(f"[WS] ❌ Heartbeat failed: {e}")
-```
-
-**Clean State on Reconnection**
-
-The client automatically resets state on new tasks:
-
-```python
-async with self.ufo_client.task_lock:
- self.ufo_client.reset() # Clear session state
- # Send new task request
-```
-
-## Integration Points
-
-### UFO Client
-
-The WebSocket client delegates command execution to the UFO Client:
-
-```python
-action_results = await self.ufo_client.execute_step(server_response)
-```
-
-See [UFO Client](./ufo_client.md) for execution details.
-
-### Device Info Provider
-
-Collects device information for registration:
-
-```python
-system_info = DeviceInfoProvider.collect_system_info(
- client_id=self.ufo_client.client_id,
- custom_metadata=None
-)
-```
-
-See [Device Info Provider](./device_info.md) for profiling details.
-
-### AIP Transport
-
-All communication goes through the WebSocket transport:
-
-```python
-from aip.transport.websocket import WebSocketTransport
-
-self.transport = WebSocketTransport(ws)
-```
-
-See [AIP Transport Layer](../aip/transport.md) for transport details.
-
----
-
-## 🚀 Next Steps
-
-!!!tip "Continue Learning"
-
- **1. Connect Your Client**
-
- Follow the step-by-step guide to connect a device:
-
- 👉 [Quick Start Guide](./quick_start.md)
-
- **2. Understand Command Execution**
-
- Learn how the UFO Client executes commands:
-
- 👉 [UFO Client Documentation](./ufo_client.md)
-
- **3. Explore Device Profiling**
-
- See what device information is collected:
-
- 👉 [Device Info Provider](./device_info.md)
-
- **4. Master the AIP Protocol**
-
- Deep dive into message formats and protocol details:
-
- 👉 [AIP Protocol Guide](../aip/protocols.md)
-
- **5. Study Server-Side Registration**
-
- Understand how the server handles client registration:
-
- 👉 [Server Overview](../server/overview.md)
+5. **Study Server-Side Registration** - Understand how the server handles registration: [Server Overview](../server/overview.md)
diff --git a/documents/docs/configuration/models/azure_openai.md b/documents/docs/configuration/models/azure_openai.md
index b3f6a5e26..ee8381a44 100644
--- a/documents/docs/configuration/models/azure_openai.md
+++ b/documents/docs/configuration/models/azure_openai.md
@@ -1,31 +1,96 @@
# Azure OpenAI (AOAI)
-## Step 1
-To use the Azure OpenAI API, you need to create an account on the [Azure OpenAI website](https://azure.microsoft.com/en-us/products/ai-services/openai-service). After creating an account, you can deploy the AOAI API and access the API key.
+## Step 1: Create Azure OpenAI Resource
-## Step 2
-After obtaining the API key, you can configure the `HOST_AGENT` and `APP_AGENT` in the `config.yaml` file (rename the `config_template.yaml` file to `config.yaml`) to use the Azure OpenAI API. The following is an example configuration for the Azure OpenAI API:
+To use the Azure OpenAI API, create an account on the [Azure OpenAI website](https://azure.microsoft.com/en-us/products/ai-services/openai-service). After creating an account, deploy a model and obtain your API key and endpoint.
+
+## Step 2: Configure Agent Settings
+
+Configure the `HOST_AGENT` and `APP_AGENT` in the `config/ufo/agents.yaml` file to use the Azure OpenAI API.
+
+If the file doesn't exist, copy it from the template:
+
+```powershell
+Copy-Item config\ufo\agents.yaml.template config\ufo\agents.yaml
+```
+
+Edit `config/ufo/agents.yaml` with your Azure OpenAI configuration:
+
+### Option 1: API Key Authentication (Recommended for Development)
```yaml
-VISUAL_MODE: True, # Whether to use visual mode to understand screenshots and take actions
-API_TYPE: "aoai" , # The API type, "openai" for the OpenAI API, "aoai" for the AOAI API, 'azure_ad' for the ad authority of the AOAI API.
-API_BASE: "YOUR_ENDPOINT", # The AOAI API address. Format: https://{your-resource-name}.openai.azure.com
-API_KEY: "YOUR_KEY", # The aoai API key
-API_VERSION: "2024-02-15-preview", # The version of the API, "2024-02-15-preview" by default
-API_MODEL: "gpt-4-vision-preview", # The OpenAI model name, "gpt-4-vision-preview" by default. You may also use "gpt-4o" for using the GPT-4O model.
-API_DEPLOYMENT_ID: "YOUR_AOAI_DEPLOYMENT", # The deployment id for the AOAI API
+HOST_AGENT:
+ VISUAL_MODE: True # Enable visual mode to understand screenshots
+ REASONING_MODEL: False # Set to True for o-series models
+ API_TYPE: "aoai" # Use Azure OpenAI API
+ API_BASE: "https://YOUR_RESOURCE.openai.azure.com" # Your Azure endpoint
+ API_KEY: "YOUR_AOAI_KEY" # Your Azure OpenAI API key
+ API_VERSION: "2024-02-15-preview" # API version
+ API_MODEL: "gpt-4o" # Model name
+ API_DEPLOYMENT_ID: "YOUR_DEPLOYMENT_ID" # Your deployment name
+
+APP_AGENT:
+ VISUAL_MODE: True
+ REASONING_MODEL: False
+ API_TYPE: "aoai"
+ API_BASE: "https://YOUR_RESOURCE.openai.azure.com"
+ API_KEY: "YOUR_AOAI_KEY"
+ API_VERSION: "2024-02-15-preview"
+ API_MODEL: "gpt-4o-mini" # Use gpt-4o-mini for cost efficiency
+ API_DEPLOYMENT_ID: "YOUR_DEPLOYMENT_ID"
```
-If you want to use AAD for authentication, you should also set the following configuration:
+### Option 2: Azure AD Authentication (Recommended for Production)
+
+For Azure Active Directory authentication, use `API_TYPE: "azure_ad"`:
```yaml
- AAD_TENANT_ID: "YOUR_TENANT_ID", # Set the value to your tenant id for the llm model
- AAD_API_SCOPE: "YOUR_SCOPE", # Set the value to your scope for the llm model
- AAD_API_SCOPE_BASE: "YOUR_SCOPE_BASE" # Set the value to your scope base for the llm model, whose format is API://YOUR_SCOPE_BASE, and the only need is the YOUR_SCOPE_BASE
+HOST_AGENT:
+ VISUAL_MODE: True
+ REASONING_MODEL: False
+ API_TYPE: "azure_ad" # Use Azure AD authentication
+ API_BASE: "https://YOUR_RESOURCE.openai.azure.com" # Your Azure endpoint
+ API_VERSION: "2024-02-15-preview"
+ API_MODEL: "gpt-4o"
+ API_DEPLOYMENT_ID: "YOUR_DEPLOYMENT_ID"
+
+ # Azure AD Configuration
+ AAD_TENANT_ID: "YOUR_TENANT_ID" # Your Azure tenant ID
+ AAD_API_SCOPE: "YOUR_SCOPE" # Your API scope
+ AAD_API_SCOPE_BASE: "YOUR_SCOPE_BASE" # Scope base (without api:// prefix)
+
+APP_AGENT:
+ VISUAL_MODE: True
+ REASONING_MODEL: False
+ API_TYPE: "azure_ad"
+ API_BASE: "https://YOUR_RESOURCE.openai.azure.com"
+ API_VERSION: "2024-02-15-preview"
+ API_MODEL: "gpt-4o-mini"
+ API_DEPLOYMENT_ID: "YOUR_DEPLOYMENT_ID"
+ AAD_TENANT_ID: "YOUR_TENANT_ID"
+ AAD_API_SCOPE: "YOUR_SCOPE"
+ AAD_API_SCOPE_BASE: "YOUR_SCOPE_BASE"
```
-!!! tip
- If you set `VISUAL_MODE` to `True`, make sure the `API_DEPLOYMENT_ID` supports visual inputs.
+**Configuration Fields:**
+
+- **`VISUAL_MODE`**: Set to `True` to enable vision capabilities. Ensure your deployment supports visual inputs
+- **`API_TYPE`**: Use `"aoai"` for API key auth or `"azure_ad"` for Azure AD auth
+- **`API_BASE`**: Your Azure OpenAI endpoint URL (format: `https://{resource-name}.openai.azure.com`)
+- **`API_KEY`**: Your Azure OpenAI API key (not needed for Azure AD auth)
+- **`API_VERSION`**: Azure API version (e.g., `"2024-02-15-preview"`)
+- **`API_MODEL`**: Model identifier (e.g., `gpt-4o`, `gpt-4o-mini`)
+- **`API_DEPLOYMENT_ID`**: Your Azure deployment name (required for AOAI)
+- **`AAD_TENANT_ID`**: Azure tenant ID (required for Azure AD auth)
+- **`AAD_API_SCOPE`**: Azure AD API scope (required for Azure AD auth)
+- **`AAD_API_SCOPE_BASE`**: Scope base without `api://` prefix (required for Azure AD auth)
+
+**For detailed configuration options, see:**
+
+- [Agent Configuration Guide](../system/agents_config.md) - Complete agent settings reference
+- [Model Configuration Overview](overview.md) - Compare different LLM providers
+- [OpenAI](openai.md) - Standard OpenAI API setup
+
+## Step 3: Start Using UFO
-## Step 3
-After configuring the `HOST_AGENT` and `APP_AGENT` with the OpenAI API, you can start using UFO to interact with the AOAI API for various tasks on Windows OS. Please refer to the [Quick Start Guide](../../getting_started/quick_start_ufo2.md) for more details on how to get started with UFO.
\ No newline at end of file
+After configuration, you can start using UFO with the Azure OpenAI API. Refer to the [Quick Start Guide](../../getting_started/quick_start_ufo2.md) for detailed instructions on running your first tasks.
\ No newline at end of file
diff --git a/documents/docs/configuration/models/claude.md b/documents/docs/configuration/models/claude.md
index 0c2196ed0..162d0b35a 100644
--- a/documents/docs/configuration/models/claude.md
+++ b/documents/docs/configuration/models/claude.md
@@ -1,29 +1,69 @@
# Anthropic Claude
-## Step 1
-To use the Claude API, you need to create an account on the [Claude website](https://www.anthropic.com/) and access the API key.
+## Step 1: Obtain API Key
-## Step 2
-You may need to install additional dependencies to use the Claude API. You can install the dependencies using the following command:
+To use the Claude API, create an account on the [Anthropic Console](https://console.anthropic.com/) and access your API key from the API keys section.
+
+## Step 2: Install Dependencies
+
+Install the required Anthropic Python package:
```bash
pip install -U anthropic==0.37.1
```
-## Step 3
-Configure the `HOST_AGENT` and `APP_AGENT` in the `config.yaml` file (rename the `config_template.yaml` file to `config.yaml`) to use the Claude API. The following is an example configuration for the Claude API:
+## Step 3: Configure Agent Settings
+
+Configure the `HOST_AGENT` and `APP_AGENT` in the `config/ufo/agents.yaml` file to use the Claude API.
+
+If the file doesn't exist, copy it from the template:
+
+```powershell
+Copy-Item config\ufo\agents.yaml.template config\ufo\agents.yaml
+```
+
+Edit `config/ufo/agents.yaml` with your Claude configuration:
```yaml
-VISUAL_MODE: True, # Whether to use visual mode to understand screenshots and take actions
-API_TYPE: "Claude" ,
-API_KEY: "YOUR_KEY",
-API_MODEL: "YOUR_MODEL"
+HOST_AGENT:
+ VISUAL_MODE: True # Enable visual mode to understand screenshots
+ API_TYPE: "claude" # Use Claude API
+ API_BASE: "https://api.anthropic.com" # Claude API endpoint
+ API_KEY: "YOUR_CLAUDE_API_KEY" # Your Claude API key
+ API_MODEL: "claude-3-5-sonnet-20241022" # Model name
+ API_VERSION: "2023-06-01" # API version
+
+APP_AGENT:
+ VISUAL_MODE: True
+ API_TYPE: "claude"
+ API_BASE: "https://api.anthropic.com"
+ API_KEY: "YOUR_CLAUDE_API_KEY"
+ API_MODEL: "claude-3-5-sonnet-20241022"
+ API_VERSION: "2023-06-01"
```
-!!! tip
- If you set `VISUAL_MODE` to `True`, make sure the `API_MODEL` supports visual inputs.
-!!! tip
- `API_MODEL` is the model name of Claude LLM API. You can find the model name in the [Claude LLM model](https://www.anthropic.com/pricing#anthropic-api) list.
+**Configuration Fields:**
+
+- **`VISUAL_MODE`**: Set to `True` to enable vision capabilities. Most Claude 3+ models support visual inputs (see [Claude models](https://www.anthropic.com/pricing#anthropic-api))
+- **`API_TYPE`**: Use `"claude"` for Claude API (case-sensitive in code: lowercase)
+- **`API_BASE`**: Claude API endpoint - `https://api.anthropic.com`
+- **`API_KEY`**: Your Anthropic API key from the console
+- **`API_MODEL`**: Model identifier (e.g., `claude-3-5-sonnet-20241022`, `claude-3-opus-20240229`)
+- **`API_VERSION`**: API version identifier
+
+**Available Models:**
+
+- **Claude 3.5 Sonnet**: `claude-3-5-sonnet-20241022` - Best balance of intelligence and speed
+- **Claude 3 Opus**: `claude-3-opus-20240229` - Most capable model
+- **Claude 3 Sonnet**: `claude-3-sonnet-20240229` - Balanced performance
+- **Claude 3 Haiku**: `claude-3-haiku-20240307` - Fast and cost-effective
+
+**For detailed configuration options, see:**
+
+- [Agent Configuration Guide](../system/agents_config.md) - Complete agent settings reference
+- [Model Configuration Overview](overview.md) - Compare different LLM providers
+- [Anthropic Documentation](https://docs.anthropic.com/) - Official Claude API docs
+
+## Step 4: Start Using UFO
-## Step 4
-After configuring the `HOST_AGENT` and `APP_AGENT` with the Claude API, you can start using UFO to interact with the Claude API for various tasks on Windows OS. Please refer to the [Quick Start Guide](../../getting_started/quick_start_ufo2.md) for more details on how to get started with UFO.
\ No newline at end of file
+After configuration, you can start using UFO with the Claude API. Refer to the [Quick Start Guide](../../getting_started/quick_start_ufo2.md) for detailed instructions on running your first tasks.
\ No newline at end of file
diff --git a/documents/docs/configuration/models/custom_model.md b/documents/docs/configuration/models/custom_model.md
index b9a2ec4bd..3b893c624 100644
--- a/documents/docs/configuration/models/custom_model.md
+++ b/documents/docs/configuration/models/custom_model.md
@@ -1,49 +1,121 @@
# Customized LLM Models
-We support and welcome the integration of custom LLM models in UFO. If you have a custom LLM model that you would like to use with UFO, you can follow the steps below to configure the model in UFO.
+UFO supports and welcomes the integration of custom LLM models. If you have a custom LLM model that you would like to use with UFO, follow the steps below to configure it.
-## Step 1
- Create a custom LLM model and serve it on your local environment.
+## Step 1: Create and Serve Your Model
-## Step 2
- Create a python script under the `ufo/llm` directory, and implement your own LLM model class by inheriting the `BaseService` class in the `ufo/llm/base.py` file. We leave a `PlaceHolderService` class in the `ufo/llm/placeholder.py` file as an example. You must implement the `chat_completion` method in your LLM model class to accept a list of messages and return a list of completions for each message.
+Create a custom LLM model and serve it on your local or remote environment. Ensure your model has an accessible API endpoint.
+
+## Step 2: Implement Model Service Class
+
+Create a Python script under the `ufo/llm` directory and implement your own LLM model class by inheriting the `BaseService` class from `ufo/llm/base.py`.
+
+**Reference Example:** See `PlaceHolderService` in `ufo/llm/placeholder.py` as a template.
+
+You must implement the `chat_completion` method:
```python
def chat_completion(
self,
- messages,
- n,
+ messages: List[Dict[str, str]],
+ n: int = 1,
temperature: Optional[float] = None,
max_tokens: Optional[int] = None,
top_p: Optional[float] = None,
**kwargs: Any,
-):
+) -> Tuple[List[str], Optional[float]]:
"""
Generates completions for a given list of messages.
+
Args:
- messages (List[str]): The list of messages to generate completions for.
- n (int): The number of completions to generate for each message.
- temperature (float, optional): Controls the randomness of the generated completions. Higher values (e.g., 0.8) make the completions more random, while lower values (e.g., 0.2) make the completions more focused and deterministic. If not provided, the default value from the model configuration will be used.
- max_tokens (int, optional): The maximum number of tokens in the generated completions. If not provided, the default value from the model configuration will be used.
- top_p (float, optional): Controls the diversity of the generated completions. Higher values (e.g., 0.8) make the completions more diverse, while lower values (e.g., 0.2) make the completions more focused. If not provided, the default value from the model configuration will be used.
- **kwargs: Additional keyword arguments to be passed to the underlying completion method.
+ messages: The list of messages to generate completions for.
+ n: The number of completions to generate for each message.
+ temperature: Controls the randomness (higher = more random).
+ max_tokens: The maximum number of tokens in completions.
+ top_p: Controls diversity (higher = more diverse).
+ **kwargs: Additional keyword arguments.
+
Returns:
- List[str], None:A list of generated completions for each message and the cost set to be None.
+ Tuple[List[str], Optional[float]]:
+ - List of generated completions for each message
+ - Cost of the API call (None if not applicable)
+
Raises:
Exception: If an error occurs while making the API request.
"""
+ # Your implementation here
pass
```
-## Step 3
-After implementing the LLM model class, you can configure the `HOST_AGENT` and `APP_AGENT` in the `config.yaml` file (rename the `config_template.yaml` file to `config.yaml`) to use the custom LLM model. The following is an example configuration for the custom LLM model:
+**Key Implementation Points:**
+
+- Handle message formatting according to your model's API
+- Process visual inputs if `VISUAL_MODE` is enabled
+- Implement retry logic for failed requests
+- Calculate and return cost if applicable
+
+## Step 3: Configure Agent Settings
+
+Configure the `HOST_AGENT` and `APP_AGENT` in the `config/ufo/agents.yaml` file to use your custom model.
+
+If the file doesn't exist, copy it from the template:
+
+```powershell
+Copy-Item config\ufo\agents.yaml.template config\ufo\agents.yaml
+```
+
+Edit `config/ufo/agents.yaml` with your custom model configuration:
```yaml
-VISUAL_MODE: True, # Whether to use visual mode to understand screenshots and take actions
-API_TYPE: "custom_model" , # The API type, "openai" for the OpenAI API, "aoai" for the AOAI API, 'azure_ad' for the ad authority of the AOAI API.
-API_BASE: "YOUR_ENDPOINT", # The custom LLM API address.
-API_MODEL: "YOUR_MODEL", # The custom LLM model name.
+HOST_AGENT:
+ VISUAL_MODE: True # Set based on your model's capabilities
+ API_TYPE: "custom_model" # Use custom model type
+ API_BASE: "http://your-endpoint:port" # Your model's API endpoint
+ API_KEY: "YOUR_API_KEY" # Your API key (if required)
+ API_MODEL: "your-model-name" # Your model identifier
+
+APP_AGENT:
+ VISUAL_MODE: True
+ API_TYPE: "custom_model"
+ API_BASE: "http://your-endpoint:port"
+ API_KEY: "YOUR_API_KEY"
+ API_MODEL: "your-model-name"
```
-## Step 4
-After configuring the `HOST_AGENT` and `APP_AGENT` with the custom LLM model, you can start using UFO to interact with the custom LLM model for various tasks on Windows OS. Please refer to the [Quick Start Guide](../../getting_started/quick_start_ufo2.md) for more details on how to get started with UFO.
\ No newline at end of file
+**Configuration Fields:**
+
+- **`VISUAL_MODE`**: Set to `True` if your model supports visual inputs
+- **`API_TYPE`**: Use `"custom_model"` for custom implementations
+- **`API_BASE`**: Your custom model's API endpoint URL
+- **`API_KEY`**: Authentication key (if your model requires it)
+- **`API_MODEL`**: Model identifier or name
+
+**For detailed configuration options, see:**
+
+- [Agent Configuration Guide](../system/agents_config.md) - Complete agent settings reference
+- [Model Configuration Overview](overview.md) - Compare different LLM providers
+
+## Step 4: Register Your Model
+
+Update the model factory in `ufo/llm/__init__.py` to include your custom model class:
+
+```python
+from ufo.llm.your_model import YourModelService
+
+# Add to the model factory mapping
+MODEL_FACTORY = {
+ # ... existing models ...
+ "custom_model": YourModelService,
+}
+```
+
+## Step 5: Start Using UFO
+
+After configuration, you can start using UFO with your custom model. Refer to the [Quick Start Guide](../../getting_started/quick_start_ufo2.md) for detailed instructions on running your first tasks.
+
+**Testing Your Integration:**
+
+1. Test with simple requests first
+2. Verify visual mode works (if applicable)
+3. Check error handling and retry logic
+4. Monitor response quality and latency
\ No newline at end of file
diff --git a/documents/docs/configuration/models/deepseek.md b/documents/docs/configuration/models/deepseek.md
index 7611099b0..d76a4f3fa 100644
--- a/documents/docs/configuration/models/deepseek.md
+++ b/documents/docs/configuration/models/deepseek.md
@@ -1,20 +1,54 @@
# DeepSeek Model
-## Step 1
-DeepSeek is developed by Alibaba DAMO Academy. To use the DeepSeek models, Go to [DeepSeek](https://www.deepseek.com/) and register an account and get the API key.
+## Step 1: Obtain API Key
-## Step 2
-Configure the `HOST_AGENT` and `APP_AGENT` in the `config.yaml` file (rename the `config_template.yaml` file to `config.yaml`) to use the DeepSeek model. The following is an example configuration for the DeepSeek model:
+DeepSeek is developed by DeepSeek AI. To use DeepSeek models, go to [DeepSeek Platform](https://www.deepseek.com/), register an account, and obtain your API key from the API management console.
+
+## Step 2: Configure Agent Settings
+
+Configure the `HOST_AGENT` and `APP_AGENT` in the `config/ufo/agents.yaml` file to use the DeepSeek model.
+
+If the file doesn't exist, copy it from the template:
+
+```powershell
+Copy-Item config\ufo\agents.yaml.template config\ufo\agents.yaml
+```
+
+Edit `config/ufo/agents.yaml` with your DeepSeek configuration:
```yaml
- VISUAL_MODE: False, # Whether to use visual mode to understand screenshots and take actions
- API_TYPE: "deepseek" , # The API type, "deepseek" for the DeepSeek model.
- API_KEY: "YOUR_KEY", # The DeepSeek API key
- API_MODEL: "YOUR_MODEL" # The DeepSeek model name
+HOST_AGENT:
+ VISUAL_MODE: False # DeepSeek models typically don't support visual inputs
+ API_TYPE: "deepseek" # Use DeepSeek API
+ API_KEY: "YOUR_DEEPSEEK_API_KEY" # Your DeepSeek API key
+ API_MODEL: "deepseek-chat" # Model name
+
+APP_AGENT:
+ VISUAL_MODE: False
+ API_TYPE: "deepseek"
+ API_KEY: "YOUR_DEEPSEEK_API_KEY"
+ API_MODEL: "deepseek-chat"
```
-!!! tip
- Most DeepSeek models don't support visual inputs, rembmer to set `VISUAL_MODE` to `False`.
+**Configuration Fields:**
+
+- **`VISUAL_MODE`**: Set to `False` - Most DeepSeek models don't support visual inputs
+- **`API_TYPE`**: Use `"deepseek"` for DeepSeek API (case-sensitive in code: lowercase)
+- **`API_KEY`**: Your DeepSeek API key
+- **`API_MODEL`**: Model identifier (e.g., `deepseek-chat`, `deepseek-coder`)
+
+**Available Models:**
+
+- **DeepSeek-Chat**: `deepseek-chat` - General conversation model
+- **DeepSeek-Coder**: `deepseek-coder` - Code-specialized model
+
+**For detailed configuration options, see:**
+
+- [Agent Configuration Guide](../system/agents_config.md) - Complete agent settings reference
+- [Model Configuration Overview](overview.md) - Compare different LLM providers
+
+## Step 3: Start Using UFO
+
+After configuration, you can start using UFO with the DeepSeek model. Refer to the [Quick Start Guide](../../getting_started/quick_start_ufo2.md) for detailed instructions on running your first tasks.
-## Step 3
-After configuring the `HOST_AGENT` and `APP_AGENT` with the DeepSeek model, you can start using UFO to interact with the DeepSeek model for various tasks on Windows OS. Please refer to the [Quick Start Guide](../../getting_started/quick_start_ufo2.md) for more details on how to get started with UFO.
+**Note:** Since DeepSeek models don't support visual mode, UFO will operate in text-only mode, which may limit some UI automation capabilities that rely on screenshot understanding.
diff --git a/documents/docs/configuration/models/gemini.md b/documents/docs/configuration/models/gemini.md
index 0e7fc706a..e4d02ff35 100644
--- a/documents/docs/configuration/models/gemini.md
+++ b/documents/docs/configuration/models/gemini.md
@@ -1,30 +1,78 @@
# Google Gemini
-## Step 1
-To use the Google Gemini API, you need to create an account on the [Google Gemini website](https://ai.google.dev/) and access the API key.
+## Step 1: Obtain API Key
-## Step 2
-You may need to install additional dependencies to use the Google Gemini API. You can install the dependencies using the following command:
+To use the Google Gemini API, create an account on [Google AI Studio](https://ai.google.dev/) and generate your API key from the API keys section.
+
+## Step 2: Install Dependencies
+
+Install the required Google GenAI Python package:
```bash
pip install -U google-genai==1.12.1
```
-## Step 3
-Configure the `HOST_AGENT` and `APP_AGENT` in the `config.yaml` file (rename the `config_template.yaml` file to `config.yaml`) to use the Google Gemini API. The following is an example configuration for the Google Gemini API:
+## Step 3: Configure Agent Settings
+
+Configure the `HOST_AGENT` and `APP_AGENT` in the `config/ufo/agents.yaml` file to use the Google Gemini API.
+
+If the file doesn't exist, copy it from the template:
+
+```powershell
+Copy-Item config\ufo\agents.yaml.template config\ufo\agents.yaml
+```
+
+Edit `config/ufo/agents.yaml` with your Gemini configuration:
```yaml
-VISUAL_MODE: True, # Whether to use visual mode to understand screenshots and take actions
-JSON_SCHEMA: True, # Whether to use JSON schema for response completion
-API_TYPE: "gemini" ,
-API_KEY: "YOUR_KEY",
-API_MODEL: "YOUR_MODEL"
+HOST_AGENT:
+ VISUAL_MODE: True # Enable visual mode to understand screenshots
+ JSON_SCHEMA: True # Enable JSON schema for structured responses
+ API_TYPE: "gemini" # Use Gemini API
+ API_BASE: "https://generativelanguage.googleapis.com" # Gemini API endpoint
+ API_KEY: "YOUR_GEMINI_API_KEY" # Your Gemini API key
+ API_MODEL: "gemini-2.0-flash-exp" # Model name
+ API_VERSION: "v1beta" # API version
+
+APP_AGENT:
+ VISUAL_MODE: True
+ JSON_SCHEMA: True
+ API_TYPE: "gemini"
+ API_BASE: "https://generativelanguage.googleapis.com"
+ API_KEY: "YOUR_GEMINI_API_KEY"
+ API_MODEL: "gemini-2.0-flash-exp"
+ API_VERSION: "v1beta"
```
-!!! tip
- If you set `VISUAL_MODE` to `True`, make sure the `API_MODEL` supports visual inputs.
-!!! tip
- `API_MODEL` is the model name of Gemini LLM API. You can find the model name in the [Gemini LLM model](https://ai.google.dev/gemini-api) list. If you meet the `429` Resource has been exhausted (e.g. check quota)., it may because the rate limit of your Gemini API.
+**Configuration Fields:**
+
+- **`VISUAL_MODE`**: Set to `True` to enable vision capabilities. Most Gemini models support visual inputs (see [Gemini models](https://ai.google.dev/gemini-api/docs/models/gemini))
+- **`JSON_SCHEMA`**: Set to `True` to enable structured JSON output formatting
+- **`API_TYPE`**: Use `"gemini"` for Google Gemini API (case-sensitive in code: lowercase)
+- **`API_BASE`**: Gemini API endpoint - `https://generativelanguage.googleapis.com`
+- **`API_KEY`**: Your Google AI API key
+- **`API_MODEL`**: Model identifier (e.g., `gemini-2.0-flash-exp`, `gemini-1.5-pro`)
+- **`API_VERSION`**: API version (typically `v1beta`)
+
+**Available Models:**
+
+- **Gemini 2.0 Flash**: `gemini-2.0-flash-exp` - Latest experimental model with multimodal capabilities
+- **Gemini 1.5 Pro**: `gemini-1.5-pro` - Advanced reasoning and long context
+- **Gemini 1.5 Flash**: `gemini-1.5-flash` - Fast and efficient
+
+**Rate Limits:**
+
+If you encounter `429 Resource has been exhausted` errors, you've hit the rate limit of your Gemini API quota. Consider:
+- Reducing request frequency
+- Upgrading your API tier
+- Using exponential backoff for retries
+
+**For detailed configuration options, see:**
+
+- [Agent Configuration Guide](../system/agents_config.md) - Complete agent settings reference
+- [Model Configuration Overview](overview.md) - Compare different LLM providers
+- [Gemini API Documentation](https://ai.google.dev/gemini-api) - Official Gemini API docs
+
+## Step 4: Start Using UFO
-## Step 4
-After configuring the `HOST_AGENT` and `APP_AGENT` with the Gemini API, you can start using UFO to interact with the Gemini API for various tasks on Windows OS. Please refer to the [Quick Start Guide](../../getting_started/quick_start_ufo2.md) for more details on how to get started with UFO.
\ No newline at end of file
+After configuration, you can start using UFO with the Gemini API. Refer to the [Quick Start Guide](../../getting_started/quick_start_ufo2.md) for detailed instructions on running your first tasks.
\ No newline at end of file
diff --git a/documents/docs/configuration/models/ollama.md b/documents/docs/configuration/models/ollama.md
index 6b61b909f..27a58e367 100644
--- a/documents/docs/configuration/models/ollama.md
+++ b/documents/docs/configuration/models/ollama.md
@@ -1,48 +1,104 @@
# Ollama
-## Step 1
-If you want to use the Ollama model, Go to [Ollama](https://github.com/jmorganca/ollama) and follow the instructions to serve a LLM model on your local environment. We provide a short example to show how to configure the ollama in the following, which might change if ollama makes updates.
+## Step 1: Install and Start Ollama
+
+Go to [Ollama](https://github.com/jmorganca/ollama) and follow the installation instructions for your platform.
+
+**For Linux & WSL2:**
```bash
-## Install ollama on Linux & WSL2
+# Install Ollama
curl https://ollama.ai/install.sh | sh
-## Run the serving
+
+# Start the Ollama server
ollama serve
```
-## Step 2
-Open another terminal and run the following command to test the ollama model:
+**For Windows/Mac:** Download and install from the [Ollama website](https://ollama.ai/).
+
+## Step 2: Pull and Test a Model
+
+Open a new terminal and pull a model:
```bash
-ollama run YOUR_MODEL
+# Pull a model (e.g., llama2)
+ollama pull llama2
+
+# Test the model
+ollama run llama2
+```
+
+By default, Ollama starts a server at `http://localhost:11434`, which will be used as the API base in your configuration.
+
+## Step 3: Configure Agent Settings
+
+Configure the `HOST_AGENT` and `APP_AGENT` in the `config/ufo/agents.yaml` file to use Ollama.
+
+If the file doesn't exist, copy it from the template:
+
+```powershell
+Copy-Item config\ufo\agents.yaml.template config\ufo\agents.yaml
```
-!!!info
- When serving LLMs via Ollama, it will by default start a server at `http://localhost:11434`, which will later be used as the API base in `config.yaml`.
+Edit `config/ufo/agents.yaml` with your Ollama configuration:
+
+```yaml
+HOST_AGENT:
+ VISUAL_MODE: True # Enable if model supports vision (e.g., llava)
+ API_TYPE: "ollama" # Use Ollama API
+ API_BASE: "http://localhost:11434" # Ollama server endpoint
+ API_KEY: "ollama" # Placeholder (not used but required)
+ API_MODEL: "llama2" # Model name (must match pulled model)
+
+APP_AGENT:
+ VISUAL_MODE: True
+ API_TYPE: "ollama"
+ API_BASE: "http://localhost:11434"
+ API_KEY: "ollama"
+ API_MODEL: "llama2"
+```
+
+**Configuration Fields:**
+
+- **`VISUAL_MODE`**: Set to `True` only for vision-capable models like `llava`
+- **`API_TYPE`**: Use `"ollama"` for Ollama API (case-sensitive in code: lowercase)
+- **`API_BASE`**: Ollama server URL (default: `http://localhost:11434`)
+- **`API_KEY`**: Placeholder value (not used but required in config)
+- **`API_MODEL`**: Model name matching your pulled model
+
+**Important: Increase Context Length**
+
+UFO requires at least 20,000 tokens to function properly. Ollama's default context length is 2048 tokens, which is insufficient. You must create a custom model with increased context:
+
+1. Create a `Modelfile`:
+
+```text
+FROM llama2
+PARAMETER num_ctx 32768
+```
+
+2. Build the custom model:
+
+```bash
+ollama create llama2-max-ctx -f Modelfile
+```
-## Step 3
-After obtaining the API key, you can configure the `HOST_AGENT` and `APP_AGENT` in the `config.yaml` file (rename the `config_template.yaml` file to `config.yaml`) to use the Ollama API. The following is an example configuration for the Ollama API:
+3. Use the custom model in your config:
```yaml
-VISUAL_MODE: True, # Whether to use visual mode to understand screenshots and take actions
-API_TYPE: "ollama" ,
-API_BASE: "YOUR_ENDPOINT",
-API_KEY: "ollama", # not used but required
-API_MODEL: "YOUR_MODEL"
+API_MODEL: "llama2-max-ctx"
```
+For more details, see [Ollama's Modelfile documentation](https://github.com/ollama/ollama/blob/main/docs/modelfile.md).
-!!! tip
- `API_BASE` is the URL started in the Ollama LLM server and `API_MODEL` is the model name of Ollama LLM, it should be same as the one you served before. In addition, due to model token limitations, you can use lite version of prompt to have a taste on UFO which can be configured in `config_dev.yaml`.
+**For detailed configuration options, see:**
-!!! note
- To run UFO successfully with Ollama, you must increase the default token limit of 2048 tokens by creating a custom model with a modified Modelfile. Create a new Modelfile that specifies `PARAMETER num_ctx 32768` (or your model's maximum context length), then build your custom model with `ollama create [model]-max-ctx -f Modelfile`. UFO requires at least 20,000 tokens to function properly, so setting the `num_ctx` parameter to your model's maximum supported context length will ensure optimal performance. For more details on Modelfile configuration, refer to [Ollama's official documentation](https://github.com/ollama/ollama/blob/main/docs/modelfile.md).
+- [Agent Configuration Guide](../system/agents_config.md) - Complete agent settings reference
+- [Model Configuration Overview](overview.md) - Compare different LLM providers
-!!! tip
- If you set `VISUAL_MODE` to `True`, make sure the `API_MODEL` supports visual inputs.
+## Step 4: Start Using UFO
-## Step 4
-After configuring the `HOST_AGENT` and `APP_AGENT` with the Ollama API, you can start using UFO to interact with the Ollama API for various tasks on Windows OS. Please refer to the [Quick Start Guide](../../getting_started/quick_start_ufo2.md) for more details on how to get started with UFO.
+After configuration, you can start using UFO with Ollama. Refer to the [Quick Start Guide](../../getting_started/quick_start_ufo2.md) for detailed instructions on running your first tasks.
diff --git a/documents/docs/configuration/models/openai.md b/documents/docs/configuration/models/openai.md
index 5ca058974..2955218aa 100644
--- a/documents/docs/configuration/models/openai.md
+++ b/documents/docs/configuration/models/openai.md
@@ -1,28 +1,57 @@
# OpenAI
-## Step 1
+## Step 1: Obtain API Key
-To use the OpenAI API, you need to create an account on the [OpenAI website](https://platform.openai.com/signup). After creating an account, you can access the API key from the [API keys page](https://platform.openai.com/account/api-keys).
+To use the OpenAI API, create an account on the [OpenAI website](https://platform.openai.com/signup). After creating an account, you can access your API key from the [API keys page](https://platform.openai.com/account/api-keys).
-## Step 2
+## Step 2: Configure Agent Settings
-After obtaining the API key, you can configure the `HOST_AGENT` and `APP_AGENT` in the `config.yaml` file (rename the `config_template.yaml` file to `config.yaml`) to use the OpenAI API. The following is an example configuration for the OpenAI API:
+After obtaining the API key, configure the `HOST_AGENT` and `APP_AGENT` in the `config/ufo/agents.yaml` file to use the OpenAI API.
+
+If the file doesn't exist, copy it from the template:
+
+```powershell
+Copy-Item config\ufo\agents.yaml.template config\ufo\agents.yaml
+```
+
+Edit `config/ufo/agents.yaml` with your OpenAI configuration:
```yaml
-VISUAL_MODE: True, # Whether to use visual mode to understand screenshots and take actions
-REASONING_MODEL: False, # Set this to true if you are using o-series models such as o1, o3
-JSON_SCHEMA: True, # Whether to use JSON schema for response completion
-API_TYPE: "openai" , # The API type, "openai" for the OpenAI API, "aoai" for the AOAI API, 'azure_ad' for the ad authority of the AOAI API.
-API_BASE: "https://api.openai.com/v1/chat/completions", # The the OpenAI API endpoint, "https://api.openai.com/v1/chat/completions" for the OpenAI API.
-API_KEY: "sk-", # The OpenAI API key, begin with sk-
-API_VERSION: "2024-12-01-preview", # The version of the API, "2024-12-01-preview" by default
-API_MODEL: "gpt-4o", # The OpenAI model name, "gpt-4o" by default.
+HOST_AGENT:
+ VISUAL_MODE: True # Enable visual mode to understand screenshots
+ REASONING_MODEL: False # Set to True for o-series models (o1, o3, o3-mini)
+ API_TYPE: "openai" # Use OpenAI API
+ API_BASE: "https://api.openai.com/v1" # OpenAI API endpoint
+ API_KEY: "sk-YOUR_KEY_HERE" # Your OpenAI API key (starts with sk-)
+ API_VERSION: "2025-02-01-preview" # API version
+ API_MODEL: "gpt-4o" # Model name (gpt-4o, gpt-4o-mini, etc.)
+
+APP_AGENT:
+ VISUAL_MODE: True
+ REASONING_MODEL: False
+ API_TYPE: "openai"
+ API_BASE: "https://api.openai.com/v1"
+ API_KEY: "sk-YOUR_KEY_HERE"
+ API_VERSION: "2025-02-01-preview"
+ API_MODEL: "gpt-4o-mini" # Use gpt-4o-mini for cost efficiency
```
-!!! tip
- If you set `VISUAL_MODE` to `True`, make sure the `API_MODEL` supports visual inputs. You can find the list of models [here](https://platform.openai.com/docs/models).
+**Configuration Fields:**
+
+- **`VISUAL_MODE`**: Set to `True` to enable vision capabilities. Ensure your selected model supports visual inputs (see [OpenAI models](https://platform.openai.com/docs/models))
+- **`REASONING_MODEL`**: Set to `True` when using o-series models (o1, o3, o3-mini) which have different behavior
+- **`API_TYPE`**: Use `"openai"` for OpenAI API
+- **`API_BASE`**: OpenAI API base URL - `https://api.openai.com/v1`
+- **`API_KEY`**: Your OpenAI API key from the API keys page
+- **`API_VERSION`**: API version identifier
+- **`API_MODEL`**: Model identifier (e.g., `gpt-4o`, `gpt-4o-mini`, `gpt-4-turbo`)
+
+**For detailed configuration options, see:**
+- [Agent Configuration Guide](../system/agents_config.md) - Complete agent settings reference
+- [Model Configuration Overview](overview.md) - Compare different LLM providers
+- [Azure OpenAI](azure_openai.md) - Alternative Azure-hosted OpenAI setup
+## Step 3: Start Using UFO
-## Step 3
-After configuring the `HOST_AGENT` and `APP_AGENT` with the OpenAI API, you can start using UFO to interact with the OpenAI API for various tasks on Windows OS. Please refer to the [Quick Start Guide](../../getting_started/quick_start_ufo2.md) for more details on how to get started with UFO.
\ No newline at end of file
+After configuration, you can start using UFO with the OpenAI API. Refer to the [Quick Start Guide](../../getting_started/quick_start_ufo2.md) for detailed instructions on running your first tasks.
\ No newline at end of file
diff --git a/documents/docs/configuration/models/operator.md b/documents/docs/configuration/models/operator.md
index 726c1537f..c8bb9a6e6 100644
--- a/documents/docs/configuration/models/operator.md
+++ b/documents/docs/configuration/models/operator.md
@@ -1,37 +1,82 @@
# OpenAI CUA (Operator)
-The [Opeartor](https://openai.com/index/computer-using-agent/) is a specialized agentic model tailored for Computer-Using Agents (CUA). We now support calling via the Azure OpenAI API (AOAI). The following sections provide a comprehensive guide on how to set up and use the AOAI API with UFO. Note that now AOAI only supports the [Response API](https://learn.microsoft.com/en-us/azure/ai-services/openai/how-to/responses?tabs=python-secure) to invoke the model.
+The [Operator](https://openai.com/index/computer-using-agent/) is a specialized agentic model tailored for Computer-Using Agents (CUA). It's currently available via the Azure OpenAI API (AOAI) using the [Response API](https://learn.microsoft.com/en-us/azure/ai-services/openai/how-to/responses?tabs=python-secure).
+## Step 1: Create Azure OpenAI Resource
+To use the Operator model, create an account on the [Azure OpenAI website](https://azure.microsoft.com/en-us/products/ai-services/openai-service). After creating an account, deploy the Operator model and access your API key.
-## Step 1
-To use the Azure OpenAI API, you need to create an account on the [Azure OpenAI website](https://azure.microsoft.com/en-us/products/ai-services/openai-service). After creating an account, you can deploy the AOAI API and access the API key.
+## Step 2: Configure Operator Agent
-## Step 2
-After obtaining the API key, you can configure the `OPERATOR` in the `config.yaml` file (rename the `config_template.yaml` file to `config.yaml`) to use the Azure OpenAI API. The following is an example configuration for the Azure OpenAI API:
+Configure the `OPERATOR` in the `config/ufo/agents.yaml` file to use the Azure OpenAI Operator model.
+
+If the file doesn't exist, copy it from the template:
+
+```powershell
+Copy-Item config\ufo\agents.yaml.template config\ufo\agents.yaml
+```
+
+Edit `config/ufo/agents.yaml` with your Operator configuration:
```yaml
-OPERATOR: {
- SCALER: [1024, 768], # The scaler for the visual input in a list format, [width, height]
- API_TYPE: "azure_ad" , # The API type, "openai" for the OpenAI API, "aoai" for the AOAI API, 'azure_ad' for the ad authority of the AOAI API.
- API_MODEL: "computer-use-preview-20250311", #"gpt-4o-mini-20240718", #"gpt-4o-20240513", # The only OpenAI model by now that accepts visual input
- API_VERSION: "2025-03-01-preview", # "2024-02-15-preview" by default
- API_BASE: "
devices.yaml]
+ B[AIP Registration
Service Manifest]
+ C[Device Telemetry
DeviceInfoProvider]
- D[AgentProfile]
+ A -->|device_id, server_url
capabilities, metadata| D[AgentProfile]
+ B -->|platform, registration_time| D
+ C -->|system_info, features| D
style A fill:#e1f5ff
style B fill:#fff4e1
@@ -277,31 +257,23 @@ graph LR
sequenceDiagram
participant Config as devices.yaml
participant Manager as DeviceManager
- participant Registry as DeviceRegistry
participant Server as UFO Server
participant Telemetry as DeviceInfoProvider
- Note over Config,Telemetry: Phase 1: User Configuration
- Config->>Manager: Load config
- Manager->>Registry: register_device(device_id, server_url, capabilities, metadata)
- Registry->>Registry: Create AgentProfile (Source 1)
- Note over Registry: AgentProfile v1
Has: device_id, server_url, capabilities, user metadata
+ Note over Config,Telemetry: Phase 1: Initial Registration
+ Config->>Manager: Load device config
+ Manager->>Manager: Create AgentProfile
(device_id, server_url, capabilities)
Note over Config,Telemetry: Phase 2: Service Registration
Manager->>Server: WebSocket REGISTER
- Server->>Server: Process registration (platform, client_type)
- Server-->>Manager: Registration confirmed
- Registry->>Registry: Update metadata (Source 2)
- Note over Registry: AgentProfile v2
Added: platform, registration_time
+ Server-->>Manager: Add platform, registration_time
Note over Config,Telemetry: Phase 3: Telemetry Collection
Manager->>Server: request_device_info()
Server->>Telemetry: collect_system_info()
- Telemetry-->>Server: DeviceSystemInfo
+ Telemetry-->>Server: system_info
Server-->>Manager: system_info
- Manager->>Registry: update_device_system_info(system_info)
- Registry->>Registry: Merge system_info (Source 3)
- Note over Registry: AgentProfile v3 (Complete)
Added: system_info, supported_features
+ Manager->>Manager: Update AgentProfile
(merge system_info & features)
```
### Merging Strategy
@@ -433,28 +405,24 @@ AgentProfile(
)
```
-**Visual Representation:**
+### Profile Summary
-```
-╔══════════════════════════════════════════════════════════════════════╗
-║ AgentProfile: gpu_workstation_01 ║
-╠══════════════════════════════════════════════════════════════════════╣
-║ Status: IDLE Last Heartbeat: 10:45:30 ║
-╠══════════════════════════════════════════════════════════════════════╣
-║ SYSTEM │ PERFORMANCE ║
-║ OS: Windows 10.0.22631 │ GPU: 2× NVIDIA RTX 4090 ║
-║ CPU: 16 cores │ Memory: 64.0 GB ║
-║ Hostname: DESKTOP-GPU01 │ Network: 192.168.1.100 ║
-║ │ ║
-║ CAPABILITIES │ METADATA ║
-║ • web_browsing │ Location: office_desktop ║
-║ • office_applications │ Performance: very_high ║
-║ • gpu_computation │ Tags: production, gpu, ml ║
-║ • model_training │ ║
-║ • gui, cli, browser, file_system │ Engineer: ml-team@example.com ║
-╠══════════════════════════════════════════════════════════════════════╣
-║ Server: ws://192.168.1.100:5005/ws │ Registered: 2025-11-06 10:30 ║
-╚══════════════════════════════════════════════════════════════════════╝
+```mermaid
+graph TB
+ subgraph "AgentProfile: gpu_workstation_01"
+ A["Status: IDLE
Last Heartbeat: 10:45:30"]
+
+ B["System
━━━━━
OS: Windows 10.0.22631
CPU: 16 cores
Memory: 64.0 GB
Host: DESKTOP-GPU01
IP: 192.168.1.100"]
+
+ C["Capabilities
━━━━━
• web_browsing
• office_applications
• gpu_computation
• model_training
• gui, cli, browser
• file_system"]
+
+ D["Metadata
━━━━━
Location: office_desktop
Performance: very_high
Tags: production, gpu, ml
GPU: 2× NVIDIA RTX 4090"]
+ end
+
+ style A fill:#e3f2fd
+ style B fill:#f3e5f5
+ style C fill:#e8f5e9
+ style D fill:#fff3e0
```
### Example 2: Linux Server
@@ -620,6 +588,8 @@ print(f"Attempts: {profile.connection_attempts}") # 0
## 🎯 Usage Patterns
+The following patterns demonstrate how AgentProfile is used for intelligent task routing and device management. For more details on task constellation concepts, see [Constellation Overview](../constellation/overview.md).
+
### Task Assignment Decision
```python
@@ -769,51 +739,55 @@ print(f"Errors: {health['errors']}")
| **Galaxy Devices Config** | [Galaxy Devices Configuration](../../configuration/system/galaxy_devices.md) | YAML configuration reference |
| **Device Info** | [Device Info Provider](../../client/device_info.md) | Telemetry collection details |
| **AIP Protocol** | [AIP Overview](../../aip/overview.md) | Agent Interaction Protocol |
+| **Constellation System** | [Constellation Overview](../constellation/overview.md) | Multi-device coordination |
+| **WebSocket Client** | [Client AIP Integration](../client/aip_integration.md) | Client-side implementation |
---
## 💡 Best Practices
-!!!tip "AgentProfile Best Practices"
-
- **1. Meaningful Capabilities**
- ```python
- # ✅ Good: Specific, actionable capabilities
- capabilities = ["web_browsing", "office_excel", "file_management", "email_sending"]
-
- # ❌ Bad: Vague capabilities
- capabilities = ["desktop", "general"]
- ```
-
- **2. Rich Metadata**
- ```python
- # ✅ Good: Comprehensive metadata for smart routing
- metadata = {
- "location": "datacenter_us_west",
- "performance": "high",
- "description": "GPU workstation for ML training",
- "tags": ["production", "gpu", "ml"],
- "operation_engineer_email": "ml-team@example.com"
- }
- ```
-
- **3. Monitor Heartbeats**
- ```python
- # Regularly check heartbeat freshness
- if profile.last_heartbeat:
- age = datetime.now(timezone.utc) - profile.last_heartbeat
- if age > timedelta(minutes=5):
- logger.warning(f"Device {profile.device_id} heartbeat stale")
- ```
-
- **4. Use System Info for Resource-Aware Routing**
- ```python
- # Check if device has enough resources
- system_info = profile.metadata.get("system_info", {})
- if system_info.get("memory_total_gb", 0) >= 16:
- # Assign memory-intensive task
- pass
- ```
+### 1. Meaningful Capabilities
+
+```python
+# ✅ Good: Specific, actionable capabilities
+capabilities = ["web_browsing", "office_excel", "file_management", "email_sending"]
+
+# ❌ Bad: Vague capabilities
+capabilities = ["desktop", "general"]
+```
+
+### 2. Rich Metadata
+
+```python
+# ✅ Good: Comprehensive metadata for smart routing
+metadata = {
+ "location": "datacenter_us_west",
+ "performance": "high",
+ "description": "GPU workstation for ML training",
+ "tags": ["production", "gpu", "ml"],
+ "operation_engineer_email": "ml-team@example.com"
+}
+```
+
+### 3. Monitor Heartbeats
+
+```python
+# Regularly check heartbeat freshness
+if profile.last_heartbeat:
+ age = datetime.now(timezone.utc) - profile.last_heartbeat
+ if age > timedelta(minutes=5):
+ logger.warning(f"Device {profile.device_id} heartbeat stale")
+```
+
+### 4. Use System Info for Resource-Aware Routing
+
+```python
+# Check if device has enough resources
+system_info = profile.metadata.get("system_info", {})
+if system_info.get("memory_total_gb", 0) >= 16:
+ # Assign memory-intensive task
+ pass
+```
---
diff --git a/documents/docs/galaxy/agent_registration/device_registry.md b/documents/docs/galaxy/agent_registration/device_registry.md
index 8695b4910..04d7ab300 100644
--- a/documents/docs/galaxy/agent_registration/device_registry.md
+++ b/documents/docs/galaxy/agent_registration/device_registry.md
@@ -1,13 +1,10 @@
# 🗄️ DeviceRegistry - Device Data Management
-!!!quote "Single Responsibility: Device Information Storage"
- The **DeviceRegistry** is a focused component that manages device registration and information storage, providing a clean separation of concerns in the constellation architecture.
-
----
-
## 📋 Overview
-The **DeviceRegistry** is responsible for **device data management only**. It stores, retrieves, and updates AgentProfile instances without handling networking, task execution, or protocol logic.
+The **DeviceRegistry** is a focused component that manages device registration and information storage, providing a clean separation of concerns in the constellation architecture. It is responsible for **device data management only** - storing, retrieving, and updating AgentProfile instances without handling networking, task execution, or protocol logic.
+
+> For details on how devices connect and register using the AIP protocol, see [Registration Flow](./registration_flow.md).
**Core Responsibilities:**
@@ -19,14 +16,12 @@ The **DeviceRegistry** is responsible for **device data management only**. It st
| **Information Retrieval** | Provide device information to other components |
| **Task State Tracking** | Track which device is executing which task |
-**What DeviceRegistry Does NOT Do:**
+**Delegation to Other Components:**
-- ❌ Network communication (handled by WebSocketConnectionManager)
-- ❌ Message processing (handled by MessageProcessor)
-- ❌ Task execution (handled by TaskQueueManager)
-- ❌ Heartbeat monitoring (handled by HeartbeatManager)
-
----
+- Network communication → [`WebSocketConnectionManager`](../client/components.md#websocketconnectionmanager-network-communication-handler)
+- Message processing → [`MessageProcessor`](../client/components.md#messageprocessor-message-router-and-handler)
+- Task execution → [`TaskQueueManager`](../client/components.md#taskqueuemanager-task-scheduling-and-queuing)
+- Heartbeat monitoring → [`HeartbeatManager`](../client/components.md#heartbeatmanager-connection-health-monitor)
## 🏗️ Architecture
@@ -199,18 +194,7 @@ print(f"Registered: {profile.device_id}")
print(f"Status: {profile.status.value}") # "disconnected"
```
-**Characteristics:**
-
-- ✅ Creates defensive copy of capabilities list
-- ✅ Creates defensive copy of metadata dict
-- ✅ Sets initial status to `DISCONNECTED`
-- ✅ Logs registration action
-- ⚠️ Overwrites if device_id already exists (no duplicate check)
-
-!!!warning "Duplicate Registration"
- If a device_id already exists, it will be overwritten. Consider adding validation in production use.
-
----
+> **Note:** The `register_device()` method will overwrite an existing device if the same `device_id` is used. Consider adding validation if duplicate prevention is needed.
### 2. Device Retrieval
@@ -549,6 +533,8 @@ def update_device_system_info(
"""
```
+> **Note:** System information is collected from the device agent and retrieved via the server. See [Client Connection Manager](../../server/client_connection_manager.md) for server-side information management.
+
**Process:**
```mermaid
@@ -611,6 +597,10 @@ device_info.metadata.update({
if "custom_metadata" in system_info:
device_info.metadata["custom_metadata"] = system_info["custom_metadata"]
+# 5. Add tags if present
+if "tags" in system_info:
+ device_info.metadata["tags"] = system_info["tags"]
+
return True
```
@@ -691,8 +681,7 @@ def set_device_capabilities(
device_info.metadata.update(capabilities["metadata"])
```
-!!!info "Legacy Method"
- This method primarily exists for backwards compatibility. Modern code should use `update_device_system_info()` instead.
+> **Note:** This method is primarily for backwards compatibility. Modern code should use `update_device_system_info()` instead.
#### Method: `get_device_capabilities()`
@@ -869,6 +858,8 @@ def check_all_devices_health(registry: DeviceRegistry):
## 🔗 Integration with Other Components
+DeviceRegistry is used internally by other components in the constellation system. See [Components Overview](../client/components.md) for details on the component architecture.
+
### With ConstellationDeviceManager
```python
@@ -922,48 +913,50 @@ class TaskQueueManager:
| **AgentProfile** | [AgentProfile](./agent_profile.md) | Profile structure details |
| **Registration Flow** | [Registration Flow](./registration_flow.md) | Step-by-step registration |
| **Galaxy Devices Config** | [Galaxy Devices Configuration](../../configuration/system/galaxy_devices.md) | YAML config reference |
-| **Device Info** | [Device Info Provider](../../client/device_info.md) | Telemetry collection |
+| **Components** | [Client Components](../client/components.md) | Component architecture |
---
## 💡 Best Practices
-!!!tip "DeviceRegistry Best Practices"
-
- **1. Always Check Device Exists**
- ```python
+**1. Always Check Device Exists**
+
+```python
+profile = registry.get_device(device_id)
+if not profile:
+ logger.error(f"Device {device_id} not found")
+ return
+```
+
+**2. Use Defensive Copies for Lists/Dicts**
+
+```python
+# Registry already creates copies, but be aware
+capabilities = ["web", "office"]
+registry.register_device(..., capabilities=capabilities)
+# Modifying original list won't affect registry
+capabilities.append("new") # Safe
+```
+
+**3. Monitor Heartbeats Regularly**
+
+```python
+# Periodic check
+for device_id in registry.get_all_devices():
profile = registry.get_device(device_id)
- if not profile:
- logger.error(f"Device {device_id} not found")
- return
- ```
-
- **2. Use Defensive Copies for Lists/Dicts**
- ```python
- # Registry already creates copies, but be aware
- capabilities = ["web", "office"]
- registry.register_device(..., capabilities=capabilities)
- # Modifying original list won't affect registry
- capabilities.append("new") # Safe
- ```
-
- **3. Monitor Heartbeats Regularly**
- ```python
- # Periodic check
- for device_id in registry.get_all_devices():
- profile = registry.get_device(device_id)
- if profile.last_heartbeat:
- age = datetime.now(timezone.utc) - profile.last_heartbeat
- if age > timedelta(minutes=5):
- logger.warning(f"Stale heartbeat: {device_id}")
- ```
-
- **4. Clear Task State After Completion**
- ```python
- # Always set to IDLE after task completes
- registry.set_device_idle(device_id)
- # This automatically clears current_task_id
- ```
+ if profile.last_heartbeat:
+ age = datetime.now(timezone.utc) - profile.last_heartbeat
+ if age > timedelta(minutes=5):
+ logger.warning(f"Stale heartbeat: {device_id}")
+```
+
+**4. Clear Task State After Completion**
+
+```python
+# Always set to IDLE after task completes
+registry.set_device_idle(device_id)
+# This automatically clears current_task_id
+```
---
diff --git a/documents/docs/galaxy/agent_registration/overview.md b/documents/docs/galaxy/agent_registration/overview.md
index 5ada95b0c..fa4d1fda0 100644
--- a/documents/docs/galaxy/agent_registration/overview.md
+++ b/documents/docs/galaxy/agent_registration/overview.md
@@ -1,7 +1,6 @@
# 🌟 Agent Registration & Profiling - Overview
-!!!quote "The Foundation of Constellation"
- **Agent Registration** is the cornerstone of the AIP (Agent Interaction Protocol) initialization process. It enables dynamic discovery, capability advertisement, and intelligent task allocation across distributed constellation agents.
+**Agent Registration** is the cornerstone of the AIP (Agent Interaction Protocol) initialization process. It enables dynamic discovery, capability advertisement, and intelligent task allocation across distributed constellation agents.
---
@@ -12,8 +11,13 @@
At the core of AIP's initialization process is the **ConstellationClient** (implemented as `ConstellationDeviceManager`), which maintains a global registry of active agents. Any device agent service that exposes a WebSocket endpoint and implements the AIP task dispatch and result-return protocol can be seamlessly integrated into UFO, providing remarkable **extensibility**.
-!!!success "Key Innovation"
- The multi-source profiling pipeline enables **transparent capability discovery** and **safe adaptation** to environmental drift without direct administrator intervention.
+The multi-source profiling pipeline enables **transparent capability discovery** and **safe adaptation** to environmental drift without direct administrator intervention.
+
+For a complete understanding of the constellation system, see:
+
+- [Constellation Overview](../constellation/overview.md) - Multi-device coordination architecture
+- [Constellation Agent Overview](../constellation_agent/overview.md) - Agent behavior and patterns
+- [AIP Protocol Overview](../../aip/overview.md) - Message protocol details
---
@@ -32,39 +36,22 @@ The agent registry is a centralized store that tracks all active constellation a
### Multi-Source Profiling
-!!!info "Three-Source Architecture"
- Each **AgentProfile** consolidates information from **three distinct sources**, creating a comprehensive and dynamically updated view of each agent.
+Each **AgentProfile** consolidates information from **three distinct sources**, creating a comprehensive and dynamically updated view of each agent.
```mermaid
graph TB
- subgraph "Agent Profile Construction"
- AP[AgentProfile]
-
- subgraph "Source 1: User Configuration"
- UC[devices.yaml]
- UC --> |device_id, server_url| AP
- UC --> |capabilities| AP
- UC --> |metadata| AP
- end
-
- subgraph "Source 2: Service Manifest"
- SM[AIP Registration Protocol]
- SM --> |client_type| AP
- SM --> |platform| AP
- SM --> |registration_time| AP
- end
-
- subgraph "Source 3: Client Telemetry"
- CT[DeviceInfoProvider]
- CT --> |os_version, cpu_count| AP
- CT --> |memory_total_gb| AP
- CT --> |hostname, ip_address| AP
- CT --> |supported_features| AP
- end
+ subgraph Sources
+ UC[User Config
devices.yaml]
+ SM[AIP Registration
Service Manifest]
+ CT[Device Telemetry
DeviceInfoProvider]
end
+ UC -->|device_id, capabilities
metadata| AP[AgentProfile]
+ SM -->|platform, client_type
registration_time| AP
+ CT -->|system_info
supported_features| AP
+
AP --> CR[ConstellationDeviceManager]
- CR --> |Task Assignment| TA[Intelligent Routing]
+ CR --> TA[Intelligent Task Routing]
style UC fill:#e1f5ff
style SM fill:#fff4e1
@@ -80,6 +67,8 @@ graph TB
| **2. Service Manifest** | Device Agent Service (AIP) | Client type, platform, registration metadata | On registration |
| **3. Client Telemetry** | Device Client (DeviceInfoProvider) | Hardware specs, OS info, network status | On connection + periodic updates |
+**Note:** While constellation.yaml contains runtime settings like heartbeat intervals, the device-specific configuration is in devices.yaml.
+
---
## 🔄 Registration Flow
@@ -95,40 +84,30 @@ The registration process follows a well-defined sequence that ensures comprehens
sequenceDiagram
participant Admin as Administrator
participant CDM as ConstellationDeviceManager
- participant DR as DeviceRegistry
- participant WS as WebSocket Connection
participant Server as UFO Server
participant DIP as DeviceInfoProvider
Note over Admin,DIP: Phase 1: User Configuration
- Admin->>CDM: register_device(device_id, server_url, capabilities, metadata)
- CDM->>DR: register_device(...)
- DR->>DR: Create AgentProfile (Source 1)
- DR-->>CDM: AgentProfile created
+ Admin->>CDM: register_device(device_id, capabilities)
+ CDM->>CDM: Create AgentProfile
Note over Admin,DIP: Phase 2: WebSocket Connection
- CDM->>WS: connect_device(device_id)
- WS->>Server: WebSocket handshake
- Server-->>WS: Connection accepted
+ CDM->>Server: connect_device()
+ Server-->>CDM: Connection established
- Note over Admin,DIP: Phase 3: Service-Level Registration
- WS->>Server: REGISTER message (ClientType, platform)
- Server->>Server: Validate registration
- Server-->>WS: Registration confirmation
- CDM->>DR: update_device_status(CONNECTED)
+ Note over Admin,DIP: Phase 3: Service Registration
+ CDM->>Server: REGISTER message
+ Server-->>CDM: Registration confirmed
- Note over Admin,DIP: Phase 4: Client Telemetry Collection
- CDM->>Server: request_device_info(device_id)
+ Note over Admin,DIP: Phase 4: Telemetry Collection
+ CDM->>Server: request_device_info()
Server->>DIP: collect_system_info()
- DIP->>DIP: Detect hardware, OS, features
- DIP-->>Server: DeviceSystemInfo
+ DIP-->>Server: system_info
Server-->>CDM: system_info
- CDM->>DR: update_device_system_info(system_info)
- DR->>DR: Merge into AgentProfile (Source 3)
+ CDM->>CDM: Merge into AgentProfile
Note over Admin,DIP: Phase 5: Ready for Tasks
- CDM->>DR: set_device_idle(device_id)
- DR->>DR: Status = IDLE
+ CDM->>CDM: Set device to IDLE
```
**Registration Phases:**
@@ -141,13 +120,14 @@ sequenceDiagram
| **4. Telemetry Collection** | Retrieve runtime system information from device | DeviceInfoProvider, DeviceInfoProtocol | Hardware, OS, and feature data merged |
| **5. Activation** | Set device to IDLE state, ready for task assignment | DeviceRegistry | Agent ready for constellation tasks |
-!!!tip "Automatic vs Manual Connection"
- Devices can be registered with `auto_connect=True` to automatically establish connection, or `auto_connect=False` to require manual connection via `connect_device()`.
+Devices can be registered with `auto_connect=True` to automatically establish connection, or `auto_connect=False` to require manual connection via `connect_device()`.
---
## 📊 AgentProfile Structure
+The **AgentProfile** is the primary data structure representing a registered constellation agent. For detailed information about the AgentProfile and its lifecycle operations, see [Agent Profile Documentation](./agent_profile.md).
+
### Core Fields
The **AgentProfile** is the primary data structure representing a registered constellation agent:
@@ -222,8 +202,7 @@ metadata = {
}
```
-!!!example "Example AgentProfile"
- See the complete example in [Agent Profile Documentation](./agent_profile.md#example-profiles).
+For a complete example, see the [Agent Profile Documentation](./agent_profile.md#example-profiles).
---
@@ -232,6 +211,8 @@ metadata = {

*Lifecycle state transitions of the Constellation Agent.*
+The agent lifecycle is managed through a state machine that tracks connection, registration, and task execution states. For more details on agent behavior and state management, see [Constellation Agent State Management](../constellation_agent/state.md).
+
### State Definitions
```python
@@ -285,8 +266,7 @@ stateDiagram-v2
| Any | DISCONNECTED | Connection lost | Cleanup, schedule reconnection |
| FAILED | CONNECTING | Retry timer | Attempt reconnection (if under max_retries) |
-!!!warning "Automatic Reconnection"
- When a device disconnects or enters FAILED state, the system automatically schedules reconnection attempts up to `max_retries` times with `reconnect_delay` interval.
+**Important:** When a device disconnects or enters FAILED state, the system automatically schedules reconnection attempts up to `max_retries` times with `reconnect_delay` interval.
---
@@ -421,7 +401,7 @@ See [Device Info Provider Documentation](../../client/device_info.md) for teleme
**File:** `ufo/server/services/client_connection_manager.py`
-Server-side client connection tracking and management.
+Server-side client connection tracking and management. For detailed information about the server-side implementation, see [Client Connection Manager Documentation](../../server/client_connection_manager.md).
**Responsibilities:**
@@ -448,20 +428,19 @@ class ClientConnectionManager:
## 📝 Configuration
-!!!info "Configuration Files"
- Agent registration uses two configuration files:
-
- **1. `config/galaxy/devices.yaml`** - Device definitions:
- - Device endpoints and identities
- - User-specified capabilities and metadata
- - Connection parameters (max retries, auto-connect)
-
- **2. `config/galaxy/constellation.yaml`** - Runtime settings:
- - Constellation identification and logging
- - Heartbeat interval and reconnection delay
- - Task concurrency and step limits
-
- See [Galaxy Devices Configuration](../../configuration/system/galaxy_devices.md) and [Galaxy Constellation Configuration](../../configuration/system/galaxy_constellation.md) for details.
+Agent registration uses two configuration files:
+
+**1. `config/galaxy/devices.yaml`** - Device definitions:
+- Device endpoints and identities
+- User-specified capabilities and metadata
+- Connection parameters (max retries, auto-connect)
+
+**2. `config/galaxy/constellation.yaml`** - Runtime settings:
+- Constellation identification and logging
+- Heartbeat interval and reconnection delay
+- Task concurrency and step limits
+
+See [Galaxy Devices Configuration](../../configuration/system/galaxy_devices.md) and [Galaxy Constellation Configuration](../../configuration/system/galaxy_constellation.md) for details.
**Example Device Configuration (devices.yaml):**
@@ -541,7 +520,9 @@ print(f"Task Status: {result.status}")
print(f"Result: {result.result}")
```
-See [Registration Flow Documentation](./registration_flow.md) for detailed examples.
+For more details on task assignment and execution, see:
+- [Registration Flow Documentation](./registration_flow.md) - Detailed examples
+- [Constellation Task Distribution](../constellation/overview.md) - Task routing strategies
---
@@ -558,6 +539,8 @@ See [Registration Flow Documentation](./registration_flow.md) for detailed examp
| **Registration Flow** | [Registration Flow](./registration_flow.md) | Step-by-step registration process |
| **Galaxy Devices Config** | [Galaxy Devices Configuration](../../configuration/system/galaxy_devices.md) | YAML configuration reference |
| **Device Registry** | [Device Registry](./device_registry.md) | Registry component details |
+| **Constellation System** | [Constellation Overview](../constellation/overview.md) | Multi-device coordination |
+| **Client Connection Manager** | [Server Connection Manager](../../server/client_connection_manager.md) | Server-side connection tracking |
### Architecture Diagrams
@@ -569,31 +552,31 @@ See [Registration Flow Documentation](./registration_flow.md) for detailed examp
## 💡 Key Benefits
-!!!success "Multi-Source Profiling Advantages"
-
- **1. Improved Task Allocation Accuracy**
-
- - Administrators specify high-level capabilities
- - Service manifests advertise supported tools
- - Telemetry provides real-time hardware status
-
- **2. Transparent Capability Discovery**
-
- - No manual system info entry required
- - Automatic feature detection based on platform
- - Dynamic updates without configuration changes
-
- **3. Safe Adaptation to Environmental Drift**
-
- - System changes (upgrades, hardware additions) automatically reflected
- - No administrator intervention needed for routine updates
- - Consistent metadata across distributed agents
-
- **4. Reliable Scheduling Decisions**
-
- - Fresh and accurate information for task routing
- - Hardware-aware task assignment (CPU/memory requirements)
- - Platform-specific capability matching
+The multi-source profiling approach provides several advantages:
+
+**1. Improved Task Allocation Accuracy**
+
+- Administrators specify high-level capabilities
+- Service manifests advertise supported tools
+- Telemetry provides real-time hardware status
+
+**2. Transparent Capability Discovery**
+
+- No manual system info entry required
+- Automatic feature detection based on platform
+- Dynamic updates without configuration changes
+
+**3. Safe Adaptation to Environmental Drift**
+
+- System changes (upgrades, hardware additions) automatically reflected
+- No administrator intervention needed for routine updates
+- Consistent metadata across distributed agents
+
+**4. Reliable Scheduling Decisions**
+
+- Fresh and accurate information for task routing
+- Hardware-aware task assignment (CPU/memory requirements)
+- Platform-specific capability matching
---
@@ -614,5 +597,4 @@ See [Registration Flow Documentation](./registration_flow.md) for detailed examp
- **Device Info**: `ufo/client/device_info_provider.py`
- **Configuration**: `config/galaxy/devices.yaml`
-!!!tip "Best Practice"
- Always configure devices with meaningful metadata and capabilities to enable intelligent task routing. The system will automatically enhance this information with telemetry data.
+**Best Practice:** Always configure devices with meaningful metadata and capabilities to enable intelligent task routing. The system will automatically enhance this information with telemetry data.
diff --git a/documents/docs/galaxy/agent_registration/registration_flow.md b/documents/docs/galaxy/agent_registration/registration_flow.md
index b26048946..0352e8d0e 100644
--- a/documents/docs/galaxy/agent_registration/registration_flow.md
+++ b/documents/docs/galaxy/agent_registration/registration_flow.md
@@ -1,25 +1,20 @@
# 🔄 Registration Flow - Complete Process Guide
-!!!quote "From Configuration to Task-Ready"
- The **Registration Flow** transforms a device configuration entry into a fully profiled, connected, and task-ready constellation agent through a coordinated multi-phase process.
-
----
-
## 📋 Overview
-The registration flow is a **5-phase process** that:
+The registration flow transforms a device configuration entry into a fully profiled, connected, and task-ready constellation agent through a coordinated **5-phase process**:
1. **Loads user configuration** from YAML
-2. **Establishes WebSocket connection** to device agent server
+2. **Establishes WebSocket connection** to device agent server
3. **Performs AIP registration protocol** exchange
4. **Collects client telemetry** data
5. **Activates the agent** as task-ready
+See [Agent Registration Overview](./overview.md) for architecture context and [DeviceRegistry](./device_registry.md) for data management details.
+

*Multi-source AgentProfile construction and registration flow.*
----
-
## 🎯 Registration Phases
### Phase Overview
@@ -56,8 +51,6 @@ graph TB
| **4. Telemetry Collection** | 1-3s | No (graceful degradation) | No | System info merged |
| **5. Agent Activation** | < 1s | No | No | Status = IDLE |
----
-
## 📝 Phase 1: User Configuration
### Purpose
@@ -161,16 +154,15 @@ AgentProfile(
)
```
-!!!success "Phase 1 Complete"
- Device registered in local registry with user-specified configuration. Status: `DISCONNECTED`
-
----
+> **Phase 1 Complete:** Device registered in local registry with user-specified configuration. Status: `DISCONNECTED`
## 🌐 Phase 2: WebSocket Connection
### Purpose
-Establish a persistent WebSocket connection to the device agent's UFO server.
+Establish a persistent WebSocket connection to the device agent's UFO server. This connection is managed by the `WebSocketConnectionManager` component.
+
+See [Client Components](../client/components.md) for component architecture details.
### Process
@@ -289,8 +281,7 @@ graph TB
| `reconnect_delay` | 5.0 seconds | Delay between attempts |
| `retry_counter` | Per-device | Tracked in AgentProfile.connection_attempts |
-!!!warning "Connection Timeout"
- If a device fails to connect after `max_retries` attempts, it enters `FAILED` status and requires manual intervention (e.g., restarting the device agent server).
+> **Warning:** If a device fails to connect after `max_retries` attempts, it enters `FAILED` status and requires manual intervention (e.g., restarting the device agent server).
### Output
@@ -299,10 +290,7 @@ graph TB
- **Heartbeat monitoring** started
- **Status**: `CONNECTED`
-!!!success "Phase 2 Complete"
- WebSocket connection established. Message handler and heartbeat monitoring active.
-
----
+> **Phase 2 Complete:** WebSocket connection established. Message handler and heartbeat monitoring active.
## 📡 Phase 3: Service Registration (AIP)
@@ -314,6 +302,8 @@ Perform AIP registration protocol exchange to:
- Advertise platform information
- Validate registration with server
+See [AIP Protocol Documentation](../../aip/protocols.md#registration-protocol) for detailed protocol specifications.
+
### Process
```mermaid
@@ -448,9 +438,7 @@ ClientMessage(
)
```
-!!!info "Device vs Constellation"
- - **Device Client**: Actual device agent (Windows, Linux, etc.) that executes tasks
- - **Constellation Client**: Orchestrator that dispatches tasks to multiple device agents
+> **Note:** Device clients register as `ClientType.DEVICE`, while constellation orchestrators register as `ClientType.CONSTELLATION` with a `target_id` pointing to the device they want to control.
### Output
@@ -459,16 +447,15 @@ ClientMessage(
- Platform information stored
- Registration confirmation received
-!!!success "Phase 3 Complete"
- AIP registration protocol completed. Client type and platform recorded on server.
-
----
+> **Phase 3 Complete:** AIP registration protocol completed. Client type and platform recorded on server.
## 📊 Phase 4: Telemetry Collection
### Purpose
-Collect real-time system information from the device client and merge it into the AgentProfile.
+Collect real-time system information from the device client and merge it into the AgentProfile. The system information is collected by the device's `DeviceInfoProvider` during registration and sent to the server as part of the registration metadata.
+
+See [Device Info Provider](../../client/device_info.md) for details on telemetry collection.
### Process
@@ -555,7 +542,7 @@ await self.registration_protocol.register_as_device(
}
```
-See [Device Info Provider](../../client/device_info.md) for collection details.
+See [Device Info Provider](../../client/device_info.md) for telemetry collection details.
### Merging Logic
@@ -598,6 +585,10 @@ def update_device_system_info(
if "custom_metadata" in system_info:
device_info.metadata["custom_metadata"] = system_info["custom_metadata"]
+ # 5. Add tags if present
+ if "tags" in system_info:
+ device_info.metadata["tags"] = system_info["tags"]
+
return True
```
@@ -647,10 +638,7 @@ AgentProfile(
)
```
-!!!success "Phase 4 Complete"
- System information collected and merged into AgentProfile. Capabilities expanded with auto-detected features.
-
----
+> **Phase 4 Complete:** System information collected and merged into AgentProfile. Capabilities expanded with auto-detected features.
## ✅ Phase 5: Agent Activation
@@ -732,10 +720,7 @@ AgentProfile(
)
```
-!!!success "Phase 5 Complete"
- Agent fully registered, profiled, and activated. Status: `IDLE` - Ready to accept task assignments.
-
----
+> **Phase 5 Complete:** Agent fully registered, profiled, and activated. Status: `IDLE` - Ready to accept task assignments.
## 🎯 Complete End-to-End Example
@@ -898,56 +883,52 @@ except Exception as e:
| **Device Info** | [Device Info Provider](../../client/device_info.md) | Telemetry collection |
| **AIP Protocol** | [AIP Overview](../../aip/overview.md) | Protocol fundamentals |
----
-
## 💡 Best Practices
-!!!tip "Registration Best Practices"
-
- **1. Use auto_connect for Production**
- ```python
- await manager.register_device(..., auto_connect=True)
- # Automatically completes all 5 phases
- ```
-
- **2. Configure Appropriate max_retries**
- ```python
- # Critical devices: higher retries
- max_retries=10 # For production servers
-
- # Test devices: lower retries
- max_retries=3 # For development environments
- ```
-
- **3. Monitor Registration Status**
- ```python
- profile = manager.get_device_info(device_id)
- if profile.status == DeviceStatus.FAILED:
- logger.error(f"Device {device_id} failed to register")
- # Take corrective action
- ```
-
- **4. Provide Rich Metadata**
- ```python
- metadata={
- "location": "datacenter_us_west",
- "performance": "high",
- "tags": ["production", "critical"],
- "operation_engineer_email": "ops@example.com"
- }
- ```
+**1. Use auto_connect for Production**
----
+```python
+await manager.register_device(..., auto_connect=True)
+# Automatically completes all 5 phases
+```
+
+**2. Configure Appropriate max_retries**
+
+```python
+# Critical devices: higher retries
+max_retries=10 # For production servers
+
+# Test devices: lower retries
+max_retries=3 # For development environments
+```
+
+**3. Monitor Registration Status**
+
+```python
+profile = manager.get_device_info(device_id)
+if profile.status == DeviceStatus.FAILED:
+ logger.error(f"Device {device_id} failed to register")
+ # Take corrective action
+```
+
+**4. Provide Rich Metadata**
+
+```python
+metadata={
+ "location": "datacenter_us_west",
+ "performance": "high",
+ "tags": ["production", "critical"],
+ "operation_engineer_email": "ops@example.com"
+}
+```
## 🚀 Next Steps
1. **Configure Devices**: Read [Galaxy Devices Configuration](../../configuration/system/galaxy_devices.md)
2. **Understand DeviceRegistry**: Check [Device Registry](./device_registry.md)
-3. **Learn Task Assignment**: See [Task Execution Documentation]
+3. **Learn Task Assignment**: See [Task Execution Documentation](../constellation_orchestrator/overview.md)
4. **Study AIP Messages**: Read [AIP Messages](../../aip/messages.md)
----
-
## 📚 Source Code References
- **ConstellationDeviceManager**: `galaxy/client/device_manager.py`
diff --git a/documents/docs/galaxy/constellation/constellation_editor.md b/documents/docs/galaxy/constellation/constellation_editor.md
index d0bb68116..6fc4f5b00 100644
--- a/documents/docs/galaxy/constellation/constellation_editor.md
+++ b/documents/docs/galaxy/constellation/constellation_editor.md
@@ -1,16 +1,14 @@
# ConstellationEditor — Interactive DAG Editor
+---
+
## 📋 Overview
**ConstellationEditor** provides a high-level, command pattern-based interface for safe and comprehensive TaskConstellation manipulation. It offers undo/redo capabilities, batch operations, validation, and observer patterns for building, modifying, and managing complex workflow DAGs interactively.
-!!!info "Command Pattern Architecture"
- ConstellationEditor uses the **Command Pattern** to encapsulate all operations as reversible command objects, enabling:
-
- - **Undo/Redo**: Full command history with rollback capabilities
- - **Transactional Safety**: Atomic operations with validation
- - **Auditability**: Complete operation tracking
- - **Extensibility**: Easy addition of new command types
+The editor uses the **Command Pattern** to encapsulate all operations as reversible command objects, enabling undo/redo with full command history, transactional safety with atomic operations, complete operation tracking for auditability, and easy extensibility for new command types.
+
+**Usage in Galaxy**: The ConstellationEditor is primarily used by the [Constellation Agent](../constellation_agent/overview.md) to programmatically build task workflows, but can also be used directly for manual constellation creation and debugging.
---
@@ -94,8 +92,8 @@ added_task = editor.add_task(task_dict)
# Method 3: Create and add in one step
task = editor.create_and_add_task(
task_id="train_model",
- name="Model Training",
description="Train neural network on preprocessed data",
+ name="Model Training",
target_device_id="gpu_server",
priority="HIGH",
timeout=3600.0,
@@ -660,33 +658,38 @@ editor.save_constellation("ml_training_pipeline.json")
# Step 6: Execute (via orchestrator)
constellation = editor.constellation
-# ... pass to orchestrator for execution ...
+# Pass to ConstellationOrchestrator for distributed execution
+# See: ../constellation_orchestrator/overview.md for execution details
```
+For details on executing the built constellation, see the [Constellation Orchestrator documentation](../constellation_orchestrator/overview.md).
+
---
## 🎯 Best Practices
-!!!tip "Editor Usage"
- 1. **Enable history**: Always enable undo/redo for interactive editing
- 2. **Validate frequently**: Run `validate_constellation()` after major changes
- 3. **Use observers**: Add observers for logging, metrics, or UI updates
- 4. **Batch operations**: Use `batch_operations()` for multiple related changes
- 5. **Save incrementally**: Save constellation checkpoints during complex editing
+### Editor Usage Guidelines
-!!!example "Command Pattern Benefits"
- The command pattern provides:
-
- - **Undo/Redo**: Full operation history
- - **Audit trail**: Every change is recorded
- - **Transaction safety**: Operations are atomic
- - **Extensibility**: Easy to add new operation types
+1. **Enable history**: Always enable undo/redo for interactive editing sessions
+2. **Validate frequently**: Run `validate_constellation()` after major structural changes
+3. **Use observers**: Add observers for logging, metrics tracking, or UI updates
+4. **Batch operations**: Use `batch_operations()` for multiple related changes to improve efficiency
+5. **Save incrementally**: Create constellation checkpoints during complex editing workflows
+
+### Command Pattern Benefits
+
+The command pattern architecture provides several key advantages:
+
+- **Undo/Redo**: Full operation history with rollback capabilities
+- **Audit trail**: Every change is recorded and traceable
+- **Transaction safety**: Operations are atomic and validated
+- **Extensibility**: New operation types can be added easily
!!!warning "Common Pitfalls"
- - **Forgetting to validate**: Always validate before execution
- - **Clearing history prematurely**: Can't undo after `clear_history()`
- - **Modifying running constellations**: Editor operations fail if constellation is executing
- - **Ignoring observer errors**: Observers should handle their own exceptions
+ - **Forgetting to validate**: Always validate before passing to orchestrator for execution
+ - **Clearing history prematurely**: Cannot undo operations after calling `clear_history()`
+ - **Modifying running constellations**: Editor operations will fail if constellation is currently executing
+ - **Ignoring observer errors**: Observers should handle their own exceptions to avoid breaking the editor
---
@@ -728,10 +731,16 @@ result = editor.execute_command_by_name(
## 🔗 Related Components
-- **[TaskStar](task_star.md)** — Tasks that are edited
-- **[TaskStarLine](task_star_line.md)** — Dependencies that are managed
-- **[TaskConstellation](task_constellation.md)** — Constellation being edited
-- **[Overview](overview.md)** — Framework overview
+- **[TaskStar](task_star.md)** — Individual tasks that can be edited and managed
+- **[TaskStarLine](task_star_line.md)** — Dependencies between tasks that define execution order
+- **[TaskConstellation](task_constellation.md)** — The constellation DAG being edited
+- **[Overview](overview.md)** — Task Constellation framework overview
+
+### Related Documentation
+
+- **[Constellation Orchestrator](../constellation_orchestrator/overview.md)** — Learn how edited constellations are scheduled and executed
+- **[Constellation Agent](../constellation_agent/overview.md)** — Understand how agents use the editor to build constellations
+- **[Command Pattern](https://en.wikipedia.org/wiki/Command_pattern)** — More about the command design pattern
---
@@ -751,83 +760,83 @@ ConstellationEditor(
| Method | Description |
|--------|-------------|
-| `add_task(task)` | Add task (TaskStar or dict) |
-| `create_and_add_task(task_id, description, name, **kwargs)` | Create and add task |
-| `update_task(task_id, **updates)` | Update task properties |
-| `remove_task(task_id)` | Remove task |
-| `get_task(task_id)` | Get task by ID |
-| `list_tasks()` | Get all tasks |
+| `add_task(task)` | Add task (TaskStar or dict), returns TaskStar |
+| `create_and_add_task(task_id, description, name, **kwargs)` | Create and add new task, returns TaskStar |
+| `update_task(task_id, **updates)` | Update task properties, returns updated TaskStar |
+| `remove_task(task_id)` | Remove task and related dependencies, returns removed task ID (str) |
+| `get_task(task_id)` | Get task by ID, returns Optional[TaskStar] |
+| `list_tasks()` | Get all tasks, returns List[TaskStar] |
### Dependency Operations
| Method | Description |
|--------|-------------|
-| `add_dependency(dependency)` | Add dependency (TaskStarLine or dict) |
-| `create_and_add_dependency(from_id, to_id, type, **kwargs)` | Create and add dependency |
-| `update_dependency(dependency_id, **updates)` | Update dependency |
-| `remove_dependency(dependency_id)` | Remove dependency |
-| `get_dependency(dependency_id)` | Get dependency by ID |
-| `list_dependencies()` | Get all dependencies |
-| `get_task_dependencies(task_id)` | Get dependencies for task |
+| `add_dependency(dependency)` | Add dependency (TaskStarLine or dict), returns TaskStarLine |
+| `create_and_add_dependency(from_id, to_id, type, **kwargs)` | Create and add dependency, returns TaskStarLine |
+| `update_dependency(dependency_id, **updates)` | Update dependency properties, returns updated TaskStarLine |
+| `remove_dependency(dependency_id)` | Remove dependency, returns removed dependency ID (str) |
+| `get_dependency(dependency_id)` | Get dependency by ID, returns Optional[TaskStarLine] |
+| `list_dependencies()` | Get all dependencies, returns List[TaskStarLine] |
+| `get_task_dependencies(task_id)` | Get dependencies for specific task, returns List[TaskStarLine] |
### Bulk Operations
| Method | Description |
|--------|-------------|
-| `build_constellation(config, clear_existing)` | Build from schema |
-| `build_from_tasks_and_dependencies(tasks, deps, ...)` | Build from lists |
-| `clear_constellation()` | Remove all tasks and dependencies |
-| `batch_operations(operations)` | Execute multiple operations |
+| `build_constellation(config, clear_existing)` | Build constellation from TaskConstellationSchema |
+| `build_from_tasks_and_dependencies(tasks, deps, ...)` | Build constellation from task and dependency lists (returns TaskConstellation) |
+| `clear_constellation()` | Remove all tasks and dependencies from constellation |
+| `batch_operations(operations)` | Execute multiple operations in sequence, returning list of results |
### File Operations
| Method | Description |
|--------|-------------|
-| `save_constellation(file_path)` | Save to JSON file |
-| `load_constellation(file_path)` | Load from JSON file |
-| `load_from_dict(data)` | Load from dictionary |
-| `load_from_json_string(json_string)` | Load from JSON string |
+| `save_constellation(file_path)` | Save constellation to JSON file, returns file path |
+| `load_constellation(file_path)` | Load constellation from JSON file, returns TaskConstellation |
+| `load_from_dict(data)` | Load constellation from dictionary, returns TaskConstellation |
+| `load_from_json_string(json_string)` | Load constellation from JSON string, returns TaskConstellation |
### History Operations
| Method | Description |
|--------|-------------|
-| `undo()` | Undo last command |
-| `redo()` | Redo next command |
-| `can_undo()` | Check if undo available |
-| `can_redo()` | Check if redo available |
-| `get_undo_description()` | Get undo description |
-| `get_redo_description()` | Get redo description |
-| `clear_history()` | Clear command history |
-| `get_history()` | Get command history |
+| `undo()` | Undo last command, returns True if successful, False if no undo available |
+| `redo()` | Redo next command, returns True if successful, False if no redo available |
+| `can_undo()` | Check if undo is available (returns bool) |
+| `can_redo()` | Check if redo is available (returns bool) |
+| `get_undo_description()` | Get description of operation that would be undone (returns Optional[str]) |
+| `get_redo_description()` | Get description of operation that would be redone (returns Optional[str]) |
+| `clear_history()` | Clear command history (no return value) |
+| `get_history()` | Get list of command descriptions (returns List[str]) |
### Validation
| Method | Description |
|--------|-------------|
-| `validate_constellation()` | Validate DAG structure |
-| `has_cycles()` | Check for cycles |
-| `get_topological_order()` | Get topological ordering |
-| `get_ready_tasks()` | Get tasks ready to execute |
-| `get_statistics()` | Get constellation + editor stats |
+| `validate_constellation()` | Validate DAG structure, returns tuple of (is_valid: bool, errors: List[str]) |
+| `has_cycles()` | Check for cycles in the DAG, returns bool |
+| `get_topological_order()` | Get topological ordering of tasks, returns List[str] of task IDs |
+| `get_ready_tasks()` | Get tasks ready to execute (no pending dependencies), returns List[TaskStar] |
+| `get_statistics()` | Get comprehensive constellation and editor statistics, returns Dict[str, Any] |
### Observers
| Method | Description |
|--------|-------------|
-| `add_observer(observer)` | Add change observer |
-| `remove_observer(observer)` | Remove observer |
+| `add_observer(observer)` | Add change observer callable that receives (editor, command, result) |
+| `remove_observer(observer)` | Remove previously added observer |
### Advanced
| Method | Description |
|--------|-------------|
-| `create_subgraph(task_ids)` | Extract subgraph |
-| `merge_constellation(other_editor, prefix)` | Merge another constellation |
-| `display_constellation(mode)` | Display visualization |
+| `create_subgraph(task_ids)` | Extract subgraph with specific tasks |
+| `merge_constellation(other_editor, prefix)` | Merge another constellation with optional ID prefix |
+| `display_constellation(mode)` | Display visualization (modes: 'overview', 'topology', 'details', 'execution') |
+
+For interactive web-based visualization and editing, see the [Galaxy WebUI](../webui.md).
---
-
server.py]
+ B2[Routers
health/devices/websocket]
+ end
+
+ subgraph Business["Business Logic Layer"]
+ B3[Services
Config/Device/Galaxy]
+ B4[Handlers
WebSocket Message Handler]
+ end
+
+ subgraph Data["Data & Models Layer"]
+ B5[Models
Requests/Responses]
+ B6[Enums
MessageType/Status]
+ B7[Dependencies
AppState]
+ end
+
+ subgraph Events["Event Processing"]
+ B8[WebSocketObserver]
+ B9[EventSerializer]
+ end
end
subgraph Core["Galaxy Core"]
@@ -82,12 +126,23 @@ graph TB
C4[Event System]
end
- Frontend <-->|WebSocket| Backend
+ Frontend <-->|WebSocket| B2
+ B2 --> B4
+ B4 --> B3
+ B3 --> B7
+ B2 --> B5
+ B8 --> B9
+ B8 -->|Broadcast| Frontend
+ C4 -->|Publish Events| B8
+ B3 <-->|State Access| B7
Backend <-->|Event Bus| Core
end
style Frontend fill:#1a1a2e,stroke:#00d4ff,stroke-width:2px,color:#fff
- style Backend fill:#16213e,stroke:#7b2cbf,stroke-width:2px,color:#fff
+ style Presentation fill:#16213e,stroke:#7b2cbf,stroke-width:2px,color:#fff
+ style Business fill:#1a1a2e,stroke:#00d4ff,stroke-width:2px,color:#fff
+ style Data fill:#0f1419,stroke:#10b981,stroke-width:2px,color:#fff
+ style Events fill:#16213e,stroke:#ff006e,stroke-width:2px,color:#fff
style Core fill:#0a0e27,stroke:#ff006e,stroke-width:2px,color:#fff
```
@@ -95,11 +150,56 @@ graph TB
#### Backend Components
-| Component | File | Responsibility |
-|-----------|------|----------------|
-| **FastAPI Server** | `galaxy/webui/server.py` | HTTP server, WebSocket endpoint, static file serving |
-| **WebSocket Observer** | `galaxy/webui/websocket_observer.py` | Subscribes to Galaxy events, broadcasts to clients |
-| **Event Serializer** | Built into observer | Converts Python objects to JSON for WebSocket |
+The Galaxy WebUI backend follows a **modular architecture** with clear separation of concerns:
+
+| Component | File/Directory | Responsibility |
+|-----------|----------------|----------------|
+| **FastAPI Server** | `galaxy/webui/server.py` | Application initialization, middleware, router registration, lifespan management |
+| **Models** | `galaxy/webui/models/` | Pydantic models for requests/responses, enums for type safety |
+| **Services** | `galaxy/webui/services/` | Business logic layer (config, device, galaxy operations) |
+| **Handlers** | `galaxy/webui/handlers/` | WebSocket message processing and routing |
+| **Routers** | `galaxy/webui/routers/` | FastAPI endpoint definitions organized by feature |
+| **Dependencies** | `galaxy/webui/dependencies.py` | Dependency injection for state management (AppState) |
+| **WebSocket Observer** | `galaxy/webui/websocket_observer.py` | Event subscription and broadcasting to WebSocket clients |
+| **Event Serializer** | Built into observer | Converts Python objects to JSON-compatible format |
+
+**Detailed Backend Structure:**
+
+```
+galaxy/webui/
+├── server.py # Main FastAPI application
+├── dependencies.py # AppState and dependency injection
+├── websocket_observer.py # EventSerializer + WebSocketObserver
+├── models/
+│ ├── __init__.py # Export all models
+│ ├── enums.py # WebSocketMessageType, RequestStatus enums
+│ ├── requests.py # Pydantic request models
+│ └── responses.py # Pydantic response models
+├── services/
+│ ├── __init__.py
+│ ├── config_service.py # Configuration management
+│ ├── device_service.py # Device operations and snapshots
+│ └── galaxy_service.py # Galaxy client interactions
+├── handlers/
+│ ├── __init__.py
+│ └── websocket_handlers.py # WebSocket message handler
+├── routers/
+│ ├── __init__.py
+│ ├── health.py # Health check endpoint
+│ ├── devices.py # Device management endpoints
+│ └── websocket.py # WebSocket endpoint
+└── templates/
+ └── index.html # Fallback HTML page
+```
+
+**Architecture Benefits:**
+
+✅ **Maintainability**: Each module has a single, clear responsibility
+✅ **Testability**: Services and handlers can be unit tested independently
+✅ **Type Safety**: Pydantic models validate all inputs/outputs
+✅ **Extensibility**: Easy to add new endpoints, message types, or services
+✅ **Readability**: Clear module boundaries improve code comprehension
+✅ **Reusability**: Services can be shared across multiple endpoints
#### Frontend Components
@@ -108,7 +208,9 @@ graph TB
| **App** | `src/App.tsx` | Main layout, connection status, theme management |
| **ChatWindow** | `src/components/chat/ChatWindow.tsx` | Message display and input interface |
| **DagPreview** | `src/components/constellation/DagPreview.tsx` | Interactive constellation graph visualization |
-| **DeviceGrid** | `src/components/devices/DeviceGrid.tsx` | Device status cards and monitoring |
+| **DevicePanel** | `src/components/devices/DevicePanel.tsx` | Device status cards, search, and add button |
+| **DeviceCard** | `src/components/devices/DeviceCard.tsx` | Individual device status display |
+| **AddDeviceModal** | `src/components/devices/AddDeviceModal.tsx` | Modal dialog for adding new devices |
| **RightPanel** | `src/components/layout/RightPanel.tsx` | Tabbed panel for constellation, tasks, details |
| **EventLog** | `src/components/EventLog.tsx` | Real-time event stream display |
| **GalaxyStore** | `src/store/galaxyStore.ts` | Zustand state management |
@@ -118,6 +220,89 @@ graph TB
## 🔌 Communication Protocol
+### HTTP API Endpoints
+
+#### Health Check
+
+```http
+GET /health
+```
+
+**Response:**
+```json
+{
+ "status": "healthy",
+ "connections": 3,
+ "events_sent": 1247
+}
+```
+
+#### Add Device
+
+```http
+POST /api/devices
+Content-Type: application/json
+```
+
+**Request Body:**
+```json
+{
+ "device_id": "windows-laptop-1",
+ "server_url": "ws://192.168.1.100:8080",
+ "os": "Windows",
+ "capabilities": ["excel", "outlook", "browser"],
+ "metadata": {
+ "region": "us-west-2",
+ "owner": "data-team"
+ },
+ "auto_connect": true,
+ "max_retries": 5
+}
+```
+
+**Success Response (200):**
+```json
+{
+ "status": "success",
+ "message": "Device 'windows-laptop-1' added successfully",
+ "device": {
+ "device_id": "windows-laptop-1",
+ "server_url": "ws://192.168.1.100:8080",
+ "os": "Windows",
+ "capabilities": ["excel", "outlook", "browser"],
+ "auto_connect": true,
+ "max_retries": 5,
+ "metadata": {
+ "region": "us-west-2",
+ "owner": "data-team"
+ }
+ }
+}
+```
+
+**Error Responses:**
+
+- **404 Not Found**: `devices.yaml` configuration file not found
+ ```json
+ {
+ "detail": "devices.yaml not found"
+ }
+ ```
+
+- **409 Conflict**: Device ID already exists
+ ```json
+ {
+ "detail": "Device ID 'windows-laptop-1' already exists"
+ }
+ ```
+
+- **500 Internal Server Error**: Failed to add device
+ ```json
+ {
+ "detail": "Failed to add device:
+
on_event]
- C --> D[Event Serialization
Python → JSON]
- D --> E[WebSocket Broadcast
to all clients]
+ B --> C[WebSocketObserver
on_event]
+ C --> D[EventSerializer
serialize_event]
+ D --> D1[Type-specific
field extraction]
+ D --> D2[Recursive value
serialization]
+ D2 --> D3[Python → JSON]
+ D3 --> E[WebSocket Broadcast
to all clients]
E --> F[Frontend Clients
receive message]
F --> G[Store Update
Zustand]
G --> H[UI Re-render
React Components]
style A fill:#0a0e27,stroke:#ff006e,stroke-width:2px,color:#fff
style C fill:#16213e,stroke:#7b2cbf,stroke-width:2px,color:#fff
+ style D fill:#1a1a2e,stroke:#f59e0b,stroke-width:2px,color:#fff
style E fill:#1a1a2e,stroke:#00d4ff,stroke-width:2px,color:#fff
style G fill:#0f1419,stroke:#10b981,stroke-width:2px,color:#fff
style H fill:#1a1a2e,stroke:#f59e0b,stroke-width:2px,color:#fff
```
+### Event Serialization
+
+The `EventSerializer` class handles conversion of complex Python objects to JSON-compatible format:
+
+**Features:**
+- **Type Handler Registry**: Pre-registered handlers for Galaxy-specific types (TaskStarLine, TaskConstellation)
+- **Type Caching**: Cached imports to avoid repeated import attempts
+- **Recursive Serialization**: Handles nested structures (dicts, lists, dataclasses, Pydantic models)
+- **Polymorphic Event Handling**: Different serialization logic for TaskEvent, ConstellationEvent, AgentEvent, DeviceEvent
+- **Fallback Strategies**: Multiple serialization attempts with graceful fallback to string representation
+
+**Serialization Chain:**
+1. Handle primitives (str, int, float, bool, None)
+2. Handle datetime objects → ISO format
+3. Handle collections (dict, list, tuple) → recursive serialization
+4. Check registered type handlers (TaskStarLine, TaskConstellation)
+5. Try dataclass serialization (`asdict()`)
+6. Try Pydantic model serialization (`model_dump()`)
+7. Try generic `to_dict()` method
+8. Fallback to `str()` representation
+
### Event Types
The WebUI subscribes to all Galaxy event types:
@@ -693,6 +983,67 @@ The WebUI is designed to work on various screen sizes:
- Use Chrome/Edge for best performance
- Disable browser extensions
+### Device Addition Issues
+
+**Problem:** Cannot add device through UI
+
+**Solutions:**
+
+1. **Check `devices.yaml` exists:**
+ ```powershell
+ # Verify configuration file
+ Test-Path config/galaxy/devices.yaml
+ ```
+
+2. **Verify device ID uniqueness:**
+ - Device ID must be unique across all devices
+ - Check existing devices in the Device Panel
+
+3. **Validate server URL format:**
+ - Must start with `ws://` or `wss://`
+ - Example: `ws://192.168.1.100:8080` or `wss://device.example.com`
+ - Ensure device server is actually running at that URL
+
+4. **Check backend logs:**
+ ```powershell
+ # Look for error messages
+ python -m galaxy --webui --log-level DEBUG
+ ```
+
+**Problem:** Device added but not connecting
+
+**Solutions:**
+
+1. **Verify device server is running:**
+ - Check that the device agent is running at the specified URL
+ - Test connection: `curl ws://your-device-url/`
+
+2. **Check firewall/network:**
+ - Ensure WebSocket port is open
+ - Verify no proxy/firewall blocking connection
+
+3. **Check device logs:**
+ - Look at the device agent logs for connection errors
+ - Verify device can reach the Galaxy server
+
+4. **Manual connection:**
+ - If `auto_connect` failed, devices will retry automatically
+ - Check `connection_attempts` in device details
+ - Increase `max_retries` if needed
+
+**Problem:** Validation errors when adding device
+
+**Common Validation Issues:**
+
+| Error | Cause | Solution |
+|-------|-------|----------|
+| "Device ID is required" | Empty device_id field | Provide a unique identifier |
+| "Device ID already exists" | Duplicate device_id | Choose a different ID |
+| "Server URL is required" | Empty server_url | Provide WebSocket URL |
+| "Invalid WebSocket URL" | Wrong URL format | Use `ws://` or `wss://` prefix |
+| "OS is required" | No OS selected | Select or enter OS type |
+| "At least one capability required" | No capabilities added | Add at least one capability |
+
---
## 🧪 Development
@@ -725,10 +1076,31 @@ python -m galaxy --webui
```
galaxy/webui/
-├── server.py # FastAPI backend
-├── websocket_observer.py # Event broadcaster
+├── server.py # FastAPI application entry point
+├── dependencies.py # AppState and dependency injection
+├── websocket_observer.py # EventSerializer + WebSocketObserver
├── __init__.py
-└── frontend/
+├── models/ # Data models and validation
+│ ├── __init__.py # Export all models
+│ ├── enums.py # WebSocketMessageType, RequestStatus
+│ ├── requests.py # WebSocketMessage, DeviceAddRequest, etc.
+│ └── responses.py # WelcomeMessage, DeviceSnapshot, etc.
+├── services/ # Business logic layer
+│ ├── __init__.py
+│ ├── config_service.py # Configuration management
+│ ├── device_service.py # Device operations and snapshots
+│ └── galaxy_service.py # Galaxy client interaction
+├── handlers/ # Request/message processing
+│ ├── __init__.py
+│ └── websocket_handlers.py # WebSocketMessageHandler class
+├── routers/ # API endpoint definitions
+│ ├── __init__.py
+│ ├── health.py # GET /health
+│ ├── devices.py # POST /api/devices
+│ └── websocket.py # WebSocket /ws
+├── templates/ # HTML templates
+│ └── index.html # Fallback page when frontend not built
+└── frontend/ # React frontend application
├── src/
│ ├── main.tsx # Entry point
│ ├── App.tsx # Main layout
@@ -742,7 +1114,7 @@ galaxy/webui/
│ ├── services/ # WebSocket client
│ └── store/ # Zustand store
├── public/ # Static assets
- ├── dist/ # Build output
+ ├── dist/ # Build output (gitignored)
├── package.json # Dependencies
├── vite.config.ts # Vite configuration
├── tailwind.config.js # Tailwind CSS
@@ -760,6 +1132,8 @@ Output: `galaxy/webui/frontend/dist/`
### Code Quality
+**Frontend:**
+
```bash
# Lint
npm run lint
@@ -771,10 +1145,270 @@ npm run type-check
npm run format
```
+**Backend:**
+
+The modular architecture improves testability. Example unit tests:
+
+```python
+# tests/webui/test_event_serializer.py
+import pytest
+from galaxy.webui.websocket_observer import EventSerializer
+from galaxy.core.events import TaskEvent
+
+def test_serialize_task_event():
+ """Test serialization of TaskEvent."""
+ serializer = EventSerializer()
+
+ event = TaskEvent(
+ event_type=EventType.TASK_STARTED,
+ source_id="test",
+ timestamp=1234567890,
+ task_id="task_1",
+ status="running",
+ result=None,
+ error=None
+ )
+
+ result = serializer.serialize_event(event)
+
+ assert result["event_type"] == "task_started"
+ assert result["task_id"] == "task_1"
+ assert result["status"] == "running"
+
+def test_serialize_nested_dict():
+ """Test recursive serialization of nested structures."""
+ serializer = EventSerializer()
+
+ data = {
+ "level1": {
+ "level2": {
+ "value": 42
+ }
+ }
+ }
+
+ result = serializer.serialize_value(data)
+ assert result["level1"]["level2"]["value"] == 42
+```
+
+```python
+# tests/webui/test_services.py
+import pytest
+from galaxy.webui.services.device_service import DeviceService
+from galaxy.webui.dependencies import AppState
+
+def test_build_device_snapshot():
+ """Test device snapshot building."""
+ app_state = AppState()
+ # Setup mock galaxy_client with devices
+
+ service = DeviceService(app_state)
+ snapshot = service.build_device_snapshot()
+
+ assert "device_count" in snapshot
+ assert "all_devices" in snapshot
+```
+
+```python
+# tests/webui/test_handlers.py
+import pytest
+from unittest.mock import AsyncMock, MagicMock
+from galaxy.webui.handlers.websocket_handlers import WebSocketMessageHandler
+from galaxy.webui.models.enums import WebSocketMessageType
+
+@pytest.mark.asyncio
+async def test_handle_ping():
+ """Test ping message handling."""
+ websocket = AsyncMock()
+ app_state = MagicMock()
+
+ handler = WebSocketMessageHandler(websocket, app_state)
+
+ response = await handler.handle_message({
+ "type": WebSocketMessageType.PING,
+ "timestamp": 1234567890
+ })
+
+ assert response["type"] == "pong"
+```
+
---
## 🚀 Advanced Usage
+### Extending the Backend
+
+The modular architecture makes it easy to extend the Galaxy WebUI backend:
+
+#### Adding a New API Endpoint
+
+**1. Define Pydantic models:**
+
+```python
+# galaxy/webui/models/requests.py
+from pydantic import BaseModel, Field
+
+class TaskQueryRequest(BaseModel):
+ """Request to query task status."""
+ task_id: str = Field(..., description="The task ID to query")
+ include_history: bool = Field(default=False)
+```
+
+```python
+# galaxy/webui/models/responses.py
+from pydantic import BaseModel
+
+class TaskQueryResponse(BaseModel):
+ """Response with task details."""
+ task_id: str
+ status: str
+ result: dict | None = None
+```
+
+**2. Create a service method:**
+
+```python
+# galaxy/webui/services/task_service.py
+from typing import Dict, Any
+from galaxy.webui.dependencies import AppState
+
+class TaskService:
+ """Service for task-related operations."""
+
+ def __init__(self, app_state: AppState):
+ self.app_state = app_state
+
+ def get_task_details(self, task_id: str, include_history: bool) -> Dict[str, Any]:
+ """Get details for a specific task."""
+ galaxy_session = self.app_state.galaxy_session
+ if not galaxy_session:
+ raise ValueError("No active Galaxy session")
+
+ # Your business logic here
+ task = galaxy_session.get_task(task_id)
+ return {
+ "task_id": task.task_id,
+ "status": task.status.value,
+ "result": task.result if include_history else None
+ }
+```
+
+**3. Add a router endpoint:**
+
+```python
+# galaxy/webui/routers/tasks.py
+from fastapi import APIRouter, Depends
+from galaxy.webui.dependencies import get_app_state
+from galaxy.webui.models.requests import TaskQueryRequest
+from galaxy.webui.models.responses import TaskQueryResponse
+from galaxy.webui.services.task_service import TaskService
+
+router = APIRouter(prefix="/api/tasks", tags=["tasks"])
+
+@router.post("/query", response_model=TaskQueryResponse)
+async def query_task(
+ request: TaskQueryRequest,
+ app_state = Depends(get_app_state)
+):
+ """Query task status and details."""
+ service = TaskService(app_state)
+ result = service.get_task_details(request.task_id, request.include_history)
+ return TaskQueryResponse(**result)
+```
+
+**4. Register the router:**
+
+```python
+# galaxy/webui/server.py
+from galaxy.webui.routers import tasks_router
+
+app.include_router(tasks_router)
+```
+
+#### Adding a New WebSocket Message Type
+
+**1. Add enum value:**
+
+```python
+# galaxy/webui/models/enums.py
+class WebSocketMessageType(str, Enum):
+ """Types of messages exchanged via WebSocket."""
+ # ... existing types ...
+ CUSTOM_ACTION = "custom_action"
+```
+
+**2. Add request model:**
+
+```python
+# galaxy/webui/models/requests.py
+class CustomActionMessage(BaseModel):
+ """Custom action message."""
+ action_name: str
+ parameters: Dict[str, Any] = Field(default_factory=dict)
+```
+
+**3. Add handler method:**
+
+```python
+# galaxy/webui/handlers/websocket_handlers.py
+async def _handle_custom_action(self, data: Dict[str, Any]) -> Dict[str, Any]:
+ """Handle custom action messages."""
+ message = CustomActionMessage(**data)
+
+ # Your logic here
+ result = await self.service.perform_custom_action(
+ message.action_name,
+ message.parameters
+ )
+
+ return {
+ "type": "custom_action_completed",
+ "result": result
+ }
+```
+
+**4. Register handler:**
+
+```python
+# galaxy/webui/handlers/websocket_handlers.py
+def __init__(self, websocket: WebSocket, app_state: AppState):
+ # ... existing code ...
+ self._handlers[WebSocketMessageType.CUSTOM_ACTION] = self._handle_custom_action
+```
+
+#### Customizing Event Serialization
+
+Add custom serialization for new types:
+
+```python
+# galaxy/webui/websocket_observer.py
+
+class EventSerializer:
+ def _register_handlers(self) -> None:
+ """Register type-specific serialization handlers."""
+ # ... existing handlers ...
+
+ # Add custom type handler
+ try:
+ from your_module import CustomType
+ self._cached_types["CustomType"] = CustomType
+ self._type_handlers[CustomType] = self._serialize_custom_type
+ except ImportError:
+ self._cached_types["CustomType"] = None
+
+ def _serialize_custom_type(self, value: Any) -> Dict[str, Any]:
+ """Serialize a CustomType object."""
+ try:
+ return {
+ "id": value.id,
+ "data": self.serialize_value(value.data),
+ "metadata": value.get_metadata()
+ }
+ except Exception as e:
+ self.logger.warning(f"Failed to serialize CustomType: {e}")
+ return str(value)
+```
+
### Custom Event Handlers
You can extend the WebUI with custom event handlers:
@@ -791,6 +1425,123 @@ export function handleCustomEvent(event: GalaxyEvent) {
}
```
+### Programmatic Device Management
+
+Add devices programmatically using the API:
+
+```typescript
+// Add a device via API
+async function addDevice(deviceConfig: {
+ device_id: string;
+ server_url: string;
+ os: string;
+ capabilities: string[];
+ metadata?: Record
-
-
---
## 🎯 When to Use Which?
@@ -210,7 +203,7 @@ Understanding how UFO² concepts map to Galaxy:
| **Action** | **TaskStar** | Executable unit (but on specific device) |
| **Blackboard** | **Task Results** | Inter-task communication |
| **Config File** | `config/ufo/` → `config/galaxy/` | Configuration location |
-| **Execution Mode** | `--mode agent-server` | Device runs as server |
+| **Execution Mode** | `python -m ufo.server.app --port
-
-UFO v1
-UFO²
-UFO³ Galaxy
-
-
-
+#### UFO v1 Architecture
**Multi-Agent (GUI-Only)**
+
```
User Request
↓
@@ -95,15 +89,16 @@ Windows Apps (GUI)
```
**Capabilities:**
+
- Multi-app workflows
- Pure screenshot + click/type
- No API integration
- Single device
-
-
+#### UFO² Architecture
**Two-Tier Hierarchy (Hybrid)**
+
```
User Request
↓
@@ -115,16 +110,17 @@ Windows Apps (GUI + API)
```
**Capabilities:**
+
- Multi-app workflows
- Desktop orchestration
- Hybrid GUI–API execution
- Deep OS integration
- Single device
-
-
+#### UFO³ Galaxy Architecture
**Constellation Model (Distributed)**
+
```
User Request
↓
@@ -138,15 +134,12 @@ Cross-Platform Apps
```
**Capabilities:**
+
- Multi-device workflows
- Parallel execution
- Dynamic adaptation
- Heterogeneous platforms
-
-
+
(Selects Action Tool)"]
- # ❌ BAD: Poor metadata confuses LLM, leads to incorrect tool usage
- @mcp.tool()
- def click_input(control_id, button="left"):
- """Click something."""
- # Implementation...
- ```
+ Agent["Agent Decision
'Click OK Button'"]
- **Impact on LLM Performance:**
+ MCP["MCP Server
Action Server"]
- - **Good metadata** → LLM selects correct tool, provides valid parameters → High success rate
- - **Poor metadata** → LLM guesses tool usage, provides invalid parameters → High error rate, wasted API calls
+ subgraph Tools["Available Action Tools"]
+ Click["click()"]
+ Type["type_text()"]
+ Insert["insert_table()"]
+ Shell["run_shell()"]
+ end
- **Best Practices:**
+ System["System Modified
✅ Side Effects"]
- 1. ✅ Use `Annotated[type, "description"]` for all parameters
- 2. ✅ Write detailed docstrings explaining when and how to use the tool
- 3. ✅ Include examples in docstrings showing typical usage
- 4. ✅ Describe return values clearly
- 5. ✅ Specify constraints (e.g., valid ranges, formats, dependencies)
- 6. ❌ Don't leave parameters undocumented
- 7. ❌ Don't write vague descriptions like "some value" or "the thing"
+ LLM --> Agent
+ Agent --> MCP
+ MCP --> Tools
+ Tools --> System
- **See individual server documentation for examples of well-documented tools.**
-
-```
-┌─────────────────────────────────────────────────────┐
-│ Action Execution Flow (LLM-Driven) │
-└─────────────────────────────────────────────────────┘
- │
- ┌─────────────┴─────────────┐
- │ LLM Agent Decision │
- │ (Selects Action Tool) │
- └─────────────┬─────────────┘
- │
- ┌─────────────┴─────────────┐
- │ │
- ▼ ▼
-┌───────────────┐ ┌───────────────┐
-│ Agent │ │ MCP Server │
-│ Decision │──────────│ Action Server│
-│ "Click OK" │ Choose └───────────────┘
-└───────────────┘ Tool │
- ▼
- ┌───────────────────┐
- │ click() │
- │ type_text() │
- │ insert_table() │
- │ run_shell() │
- └───────────────────┘
- │
- ▼
- ┌───────────────────┐
- │ System Modified │
- │ ✅ Side Effects │
- └───────────────────┘
+ style LLM fill:#e3f2fd,stroke:#1976d2,stroke-width:2px
+ style Agent fill:#fff3e0,stroke:#f57c00,stroke-width:2px
+ style MCP fill:#e8f5e9,stroke:#388e3c,stroke-width:2px
+ style Tools fill:#f3e5f5,stroke:#7b1fa2,stroke-width:2px
+ style System fill:#ffebee,stroke:#c62828,stroke-width:2px
```
-!!!warning "Side Effects"
- - **✅ Modifies State**: Can change system, files, UI
- - **⚠️ Not Idempotent**: Same action may have different results
- - **🔒 Use with Caution**: Always verify before executing
- - **📝 Audit Trail**: Log all actions for debugging
- - **🤖 LLM-Controlled**: Agent decides when and which action to execute
+**Side Effects:**
+
+- **✅ Modifies State**: Can change system, files, UI
+- **⚠️ Not Idempotent**: Same action may have different results
+- **🔒 Use with Caution**: Always verify before executing
+- **📝 Audit Trail**: Log all actions for debugging
+- **🤖 LLM-Controlled**: Agent decides when and which action to execute
## Tool Type Identifier
@@ -170,14 +118,13 @@ UFO² provides several built-in action servers for different automation scenario
| **[ConstellationEditor](servers/constellation_editor.md)** | Multi-Device | Create and manage multi-device task workflows | [Full Details →](servers/constellation_editor.md) |
| **[HardwareExecutor](servers/hardware_executor.md)** | Hardware Control | Control Arduino, robot arms, test fixtures, mobile devices | [Full Details →](servers/hardware_executor.md) |
-!!!tip "Quick Reference"
- Each server documentation page includes:
-
- - 📋 **Complete tool reference** with all parameters and return values
- - 💡 **Code examples** showing actual usage patterns
- - ⚙️ **Configuration examples** for different scenarios
- - ✅ **Best practices** with do's and don'ts
- - 🎯 **Use cases** with complete workflows
+**Quick Reference:** Each server documentation page includes:
+
+- 📋 **Complete tool reference** with all parameters and return values
+- 💡 **Code examples** showing actual usage patterns
+- ⚙️ **Configuration examples** for different scenarios
+- ✅ **Best practices** with do's and don'ts
+- 🎯 **Use cases** with complete workflows
## Configuration Examples
@@ -396,7 +343,13 @@ after_screenshot = await computer.run_actions([
])
```
-For more details, see:
+For more details on agent execution patterns:
+
+- [HostAgent Commands](../ufo2/host_agent/commands.md) - HostAgent command patterns
+- [AppAgent Commands](../ufo2/app_agent/commands.md) - AppAgent action patterns
+- [Agent Overview](../ufo2/overview.md) - UFO² agent architecture
+
+For more details on data collection:
- [Data Collection Servers](data_collection.md) - Observation tools
- [UICollector Documentation](servers/ui_collector.md) - Complete data collection reference
@@ -410,11 +363,10 @@ For more details, see:
- [Computer](../client/computer.md) - Action execution layer
- [MCP Overview](overview.md) - High-level MCP architecture
-!!!danger "Safety Reminder"
- Action servers can **modify system state**. Always:
-
- 1. ✅ **Validate inputs** before execution
- 2. ✅ **Verify targets** exist and are accessible
- 3. ✅ **Log all actions** for audit trail
- 4. ✅ **Handle failures** gracefully with retries
- 5. ✅ **Test in safe environment** before production use
+**Safety Reminder:** Action servers can **modify system state**. Always:
+
+1. ✅ **Validate inputs** before execution
+2. ✅ **Verify targets** exist and are accessible
+3. ✅ **Log all actions** for audit trail
+4. ✅ **Handle failures** gracefully with retries
+5. ✅ **Test in safe environment** before production use
diff --git a/documents/docs/mcp/configuration.md b/documents/docs/mcp/configuration.md
index 28e91745b..5ed6000d7 100644
--- a/documents/docs/mcp/configuration.md
+++ b/documents/docs/mcp/configuration.md
@@ -8,8 +8,7 @@ MCP configuration in UFO² uses a **hierarchical YAML structure** that maps agen
config/ufo/mcp.yaml
```
-!!!info "Configuration Reference"
- For complete field documentation, see [MCP Reference](../configuration/system/mcp_reference.md).
+For complete field documentation, see [MCP Reference](../configuration/system/mcp_reference.md).
## Configuration Structure
@@ -46,8 +45,7 @@ AgentName
└─ ...
```
-!!!tip "Default Sub-Type"
- Always define a `default` sub-type as a fallback configuration. If a specific sub-type is not found, the agent will use `default`.
+**Default Sub-Type:** Always define a `default` sub-type as a fallback configuration. If a specific sub-type is not found, the agent will use `default`.
## Server Configuration Fields
@@ -202,8 +200,7 @@ AppAgent:
- **Data Collection**: Same as default
- **Actions**: App UI automation + Word COM API (insert_table, select_text, etc.)
-!!!warning "Reset Flag"
- Set `reset: true` for stateful tools (like COM executors) to prevent state leakage between contexts (e.g., different documents).
+**Reset Flag:** Set `reset: true` for stateful tools (like COM executors) to prevent state leakage between contexts (e.g., different documents).
#### Excel-Specific Configuration
@@ -303,8 +300,7 @@ HardwareAgent:
- **Data Collection**: CPU info, memory info, disk info
- **Actions**: Hardware control commands
-!!!tip "Remote Deployment"
- For remote servers, ensure the HTTP MCP server is running on the target machine. See [Remote Servers](remote_servers.md) for deployment guide.
+**Remote Deployment:** For remote servers, ensure the HTTP MCP server is running on the target machine. See [Remote Servers](remote_servers.md) for deployment guide.
### LinuxAgent
@@ -624,8 +620,7 @@ HostAgent:
type: local
```
-!!!info "Configuration Migration"
- See [Configuration Migration Guide](../configuration/system/migration.md) for detailed migration instructions.
+For detailed migration instructions, see [Configuration Migration Guide](../configuration/system/migration.md).
## Related Documentation
@@ -634,14 +629,17 @@ HostAgent:
- [Action Servers](action.md) - Action server configuration
- [Local Servers](local_servers.md) - Built-in local MCP servers
- [Remote Servers](remote_servers.md) - HTTP and Stdio deployment
-- **[Creating Custom MCP Servers Tutorial](../tutorials/creating_mcp_servers.md)** - Build your own servers
+- [Creating Custom MCP Servers Tutorial](../tutorials/creating_mcp_servers.md) - Build your own servers
- [MCP Reference](../configuration/system/mcp_reference.md) - Complete field reference
- [Configuration Guide](../configuration/system/overview.md) - General configuration guide
+- [HostAgent Overview](../ufo2/host_agent/overview.md) - HostAgent configuration examples
+- [AppAgent Overview](../ufo2/app_agent/overview.md) - AppAgent configuration examples
-!!!quote "Configuration Philosophy"
- MCP configuration follows the **convention over configuration** principle:
-
- - **Sensible defaults** - Minimal configuration required
- - **Explicit when needed** - Full control when customization is necessary
- - **Type-safe** - Validated on load to catch errors early
- - **Hierarchical** - Inherit from defaults, override as needed
+**Configuration Philosophy:**
+
+MCP configuration follows the **convention over configuration** principle:
+
+- **Sensible defaults** - Minimal configuration required
+- **Explicit when needed** - Full control when customization is necessary
+- **Type-safe** - Validated on load to catch errors early
+- **Hierarchical** - Inherit from defaults, override as needed
diff --git a/documents/docs/mcp/data_collection.md b/documents/docs/mcp/data_collection.md
index 7e404e06d..ed5e4512a 100644
--- a/documents/docs/mcp/data_collection.md
+++ b/documents/docs/mcp/data_collection.md
@@ -4,53 +4,50 @@
**Data Collection Servers** provide read-only tools that observe and retrieve system state without modifying it. These servers are essential for agents to understand the current environment before taking actions.
-!!!warning "Not LLM-Selectable"
- **Data Collection servers are automatically invoked by the UFO² framework** to gather context and build observation prompts for the LLM. The LLM agent **does not select these tools** - they run in the background to provide system state information.
+**Data Collection servers are automatically invoked by the UFO² framework** to gather context and build observation prompts for the LLM. The LLM agent **does not select these tools** - they run in the background to provide system state information.
+
+- **Framework-Driven**: Automatically called to collect screenshots, UI controls, system info
+- **Observation Purpose**: Build the prompt that the LLM uses for decision-making
+- **Not in Tool List**: These tools are NOT presented to the LLM as selectable actions
+
+**Only [Action Servers](./action.md) are LLM-selectable.**
+
+```mermaid
+graph TB
+ Framework["UFO² Framework
(Automatic Invocation)"]
- - **Framework-Driven**: Automatically called to collect screenshots, UI controls, system info
- - **Observation Purpose**: Build the prompt that the LLM uses for decision-making
- - **Not in Tool List**: These tools are NOT presented to the LLM as selectable actions
+ AgentStep["Agent Step
Observation & Prompt Build"]
- **Only [Action Servers](./action.md) are LLM-selectable.**
-
-```
-┌─────────────────────────────────────────────────────┐
-│ Data Collection Flow (Automatic) │
-└─────────────────────────────────────────────────────┘
- │
- ┌─────────────┴─────────────┐
- │ UFO² Framework │
- │ (Automatic Invocation) │
- └─────────────┬─────────────┘
- │
- ┌─────────────┴─────────────┐
- │ │
- ▼ ▼
-┌───────────────┐ ┌───────────────┐
-│ Agent Step │ │ MCP Server │
-│ Observation │◄─────────│ UICollector │
-│ Prompt Build │ └───────────────┘
-└───────────────┘ │
- ▼
- ┌───────────────────┐
- │ take_screenshot() │
- │ get_window_list() │
- │ get_control_info()│
- └───────────────────┘
- │
- ▼
- ┌───────────────────┐
- │ System State │
- │ → LLM Context │
- └───────────────────┘
+ MCP["MCP Server
UICollector"]
+
+ subgraph Tools["Data Collection Tools"]
+ Screenshot["take_screenshot()"]
+ WindowList["get_window_list()"]
+ ControlInfo["get_control_info()"]
+ end
+
+ SystemState["System State
→ LLM Context"]
+
+ Framework --> AgentStep
+ Framework --> MCP
+ MCP --> Tools
+ Tools --> SystemState
+ SystemState --> AgentStep
+
+ style Framework fill:#e3f2fd,stroke:#1976d2,stroke-width:2px
+ style AgentStep fill:#fff3e0,stroke:#f57c00,stroke-width:2px
+ style MCP fill:#e8f5e9,stroke:#388e3c,stroke-width:2px
+ style Tools fill:#f3e5f5,stroke:#7b1fa2,stroke-width:2px
+ style SystemState fill:#fce4ec,stroke:#c2185b,stroke-width:2px
```
-!!!info "Characteristics"
- - **❌ No Side Effects**: Cannot modify system state
- - **✅ Safe to Retry**: Can be called multiple times without risk
- - **✅ Idempotent**: Same input always produces same output
- - **📊 Observation Only**: Provides information for decision-making
- - **🤖 Framework-Invoked**: Not selectable by LLM agent
+**Characteristics:**
+
+- **❌ No Side Effects**: Cannot modify system state
+- **✅ Safe to Retry**: Can be called multiple times without risk
+- **✅ Idempotent**: Same input always produces same output
+- **📊 Observation Only**: Provides information for decision-making
+- **🤖 Framework-Invoked**: Not selectable by LLM agent
## Tool Type Identifier
@@ -233,6 +230,12 @@ See the [UICollector documentation](servers/ui_collector.md) for complete exampl
Data collection servers are typically used in the **observation phase** of agent execution. See the [UICollector documentation](servers/ui_collector.md) for complete integration patterns.
+For more details on agent architecture and execution flow:
+
+- [HostAgent Overview](../ufo2/host_agent/overview.md) - HostAgent architecture and workflow
+- [AppAgent Overview](../ufo2/app_agent/overview.md) - AppAgent architecture and workflow
+- [Agent Overview](../ufo2/overview.md) - UFO² agent system architecture
+
```python
# Agent execution loop
while not task_complete:
@@ -259,10 +262,11 @@ while not task_complete:
- [Computer](../client/computer.md) - Tool execution layer
- [MCP Overview](overview.md) - High-level MCP architecture
-!!!success "Key Takeaways"
- - Data collection servers are **read-only** and **safe to retry**
- - Always **observe before acting** to make informed decisions
- - **Cache results** when state hasn't changed to improve performance
- - Handle **errors gracefully** with retries and fallback logic
- - Use **appropriate regions** and **parallel collection** for performance
- - See the **[UICollector documentation](servers/ui_collector.md)** for complete details
+**Key Takeaways:**
+
+- Data collection servers are **read-only** and **safe to retry**
+- Always **observe before acting** to make informed decisions
+- **Cache results** when state hasn't changed to improve performance
+- Handle **errors gracefully** with retries and fallback logic
+- Use **appropriate regions** and **parallel collection** for performance
+- See the **[UICollector documentation](servers/ui_collector.md)** for complete details
diff --git a/documents/docs/mcp/local_servers.md b/documents/docs/mcp/local_servers.md
index b3d4d87b6..f38c58d9f 100644
--- a/documents/docs/mcp/local_servers.md
+++ b/documents/docs/mcp/local_servers.md
@@ -155,4 +155,7 @@ AppAgent:
- [Action Servers](./action.md) - Action server overview
- [MCP Configuration](./configuration.md) - Configuration guide
- [Remote Servers](./remote_servers.md) - HTTP/Stdio deployment
-- **[Creating Custom MCP Servers Tutorial](../tutorials/creating_mcp_servers.md)** - Learn to build your own servers
+- [Creating Custom MCP Servers Tutorial](../tutorials/creating_mcp_servers.md) - Learn to build your own servers
+- [HostAgent Overview](../ufo2/host_agent/overview.md) - HostAgent architecture
+- [AppAgent Overview](../ufo2/app_agent/overview.md) - AppAgent architecture
+- [Hybrid Actions](../ufo2/core_features/hybrid_actions.md) - GUI + API dual-mode automation
diff --git a/documents/docs/mcp/overview.md b/documents/docs/mcp/overview.md
index 12bcdb292..34ed61499 100644
--- a/documents/docs/mcp/overview.md
+++ b/documents/docs/mcp/overview.md
@@ -8,37 +8,38 @@
- **Execute actions** through action servers
- **Extend capabilities** through custom MCP servers
-```
-┌─────────────────────────────────────────────────────────┐
-│ UFO² Agent │
-│ ┌──────────────┐ ┌──────────────┐ │
-│ │ HostAgent │ │ AppAgent │ │
-│ └──────┬───────┘ └──────┬───────┘ │
-│ │ │ │
-│ ▼ ▼ │
-│ ┌──────────────────────────────────────────────┐ │
-│ │ Computer (MCP Tool Manager) │ │
-│ └──────────────────────────────────────────────┘ │
-└─────────────────┬───────────────────────────────────────┘
- │
- ┌─────────┴─────────┐
- │ │
- ▼ ▼
-┌───────────────┐ ┌──────────────┐
-│ Data Collection│ │ Action │
-│ Servers │ │ Servers │
-└───────────────┘ └──────────────┘
- │ │
- ▼ ▼
-┌───────────────┐ ┌──────────────┐
-│ UICollector │ │ UIExecutor │
-│ Hardware Info │ │ CLI Executor │
-│ Screen Capture│ │ COM Executor │
-└───────────────┘ └──────────────┘
+```mermaid
+graph TB
+ subgraph Agent["UFO² Agent"]
+ HostAgent[HostAgent]
+ AppAgent[AppAgent]
+ end
+
+ Computer["Computer
(MCP Tool Manager)"]
+
+ subgraph DataServers["Data Collection Servers"]
+ UICollector["UICollector
• Screenshots
• Window Info"]
+ HWInfo["Hardware Info
• CPU/Memory
• System State"]
+ end
+
+ subgraph ActionServers["Action Servers"]
+ UIExecutor["UIExecutor
• Click/Type
• UI Automation"]
+ CLIExecutor["CLI Executor
• Shell Commands"]
+ COMExecutor["COM Executor
• API Calls"]
+ end
+
+ HostAgent --> Computer
+ AppAgent --> Computer
+ Computer --> DataServers
+ Computer --> ActionServers
+
+ style Agent fill:#e3f2fd,stroke:#1976d2,stroke-width:2px
+ style Computer fill:#fff3e0,stroke:#f57c00,stroke-width:2px
+ style DataServers fill:#e8f5e9,stroke:#388e3c,stroke-width:2px
+ style ActionServers fill:#fce4ec,stroke:#c2185b,stroke-width:2px
```
-!!!info "MCP in UFO²"
- MCP serves as the **execution layer** in UFO²'s architecture. While agents make decisions about *what* to do, MCP servers handle *how* to do it by providing concrete tool implementations.
+MCP serves as the **execution layer** in UFO²'s architecture. While agents make decisions about *what* to do, MCP servers handle *how* to do it by providing concrete tool implementations.
## Key Concepts
@@ -51,51 +52,22 @@ MCP servers in UFO² are categorized into two types based on their purpose:
| **Data Collection** | Retrieve system state
Read-only operations | UI detection, Screenshot, System info | ❌ None | ❌ **No** - Auto-invoked |
| **Action** | Modify system state
State-changing operations | Click, Type text, Run command | ✅ Yes | ✅ **Yes** - LLM chooses |
-!!!tip "Server Selection Model"
- - **[Data Collection Servers](./data_collection.md)**: Automatically invoked by the framework to gather context and build observation prompts. **NOT selectable by LLM.**
- - **[Action Servers](./action.md)**: LLM agent actively selects which action tool to execute at each step based on the task. **Only action tools are LLM-selectable.**
-
- **How Action Tools Reach the LLM**: Each action tool's `Annotated` type hints and docstring are automatically extracted and converted into structured instructions that appear in the LLM's prompt. The LLM uses these instructions to understand what each tool does, what parameters it requires, and when to use it. **Therefore, developers should write clear, comprehensive docstrings and type annotations** - they directly impact the LLM's ability to use the tool correctly.
+**Server Selection Model:**
+
+- **[Data Collection Servers](./data_collection.md)**: Automatically invoked by the framework to gather context and build observation prompts. Not selectable by LLM.
+- **[Action Servers](./action.md)**: LLM agent actively selects which action tool to execute at each step based on the task. Only action tools are LLM-selectable.
+
+**How Action Tools Reach the LLM**: Each action tool's `Annotated` type hints and docstring are automatically extracted and converted into structured instructions that appear in the LLM's prompt. The LLM uses these instructions to understand what each tool does, what parameters it requires, and when to use it. Therefore, developers should write clear, comprehensive docstrings and type annotations - they directly impact the LLM's ability to use the tool correctly.
### 2. Server Deployment Models
UFO² supports three deployment models for MCP servers:
-```
-┌──────────────────────────────────────────────────────────┐
-│ Deployment Models │
-├──────────────────────────────────────────────────────────┤
-│ │
-│ 1. Local (In-Process) │
-│ ┌─────────────────────────────────────┐ │
-│ │ Agent Process │ │
-│ │ ├─ Agent Logic │ │
-│ │ └─ MCP Server (FastMCP) │ │
-│ └─────────────────────────────────────┘ │
-│ ✅ Fast (no IPC overhead) │
-│ ✅ Simple setup │
-│ ❌ Shares process resources │
-│ │
-│ 2. HTTP (Remote) │
-│ ┌──────────────┐ ┌──────────────┐ │
-│ │ Agent Process│────────>│ HTTP Server │ │
-│ │ │<────────│ (Port 8006) │ │
-│ └──────────────┘ REST └──────────────┘ │
-│ ✅ Process isolation │
-│ ✅ Language-agnostic │
-│ ❌ Network overhead │
-│ │
-│ 3. Stdio (Process) │
-│ ┌──────────────┐ ┌──────────────┐ │
-│ │ Agent Process│────────>│ Child Process│ │
-│ │ │<────────│ (stdin/out) │ │
-│ └──────────────┘ └──────────────┘ │
-│ ✅ Process isolation │
-│ ✅ Bidirectional streaming │
-│ ❌ Platform-specific │
-│ │
-└──────────────────────────────────────────────────────────┘
-```
+| Model | Description | Benefits | Trade-offs |
+|-------|-------------|----------|------------|
+| **Local (In-Process)** | Server runs in same process as agent | Fast (no IPC overhead), Simple setup | Shares process resources |
+| **HTTP (Remote)** | Server runs as HTTP service (e.g., Port 8006) | Process isolation, Language-agnostic | Network overhead |
+| **Stdio (Process)** | Server runs as child process using stdin/stdout | Process isolation, Bidirectional streaming | Platform-specific |
### 3. Namespace Isolation
@@ -117,28 +89,20 @@ HostAgent:
**Tool Key Format**: `{tool_type}::{tool_name}`
-```python
-# Examples:
-"data_collection::screenshot" # Screenshot tool in data_collection
-"action::click" # Click tool in action
-"action::run_shell" # Shell command in action
-```
+- Example: `data_collection::screenshot` - Screenshot tool in data_collection
+- Example: `action::click` - Click tool in action
+- Example: `action::run_shell` - Shell command in action
## Key Features
### 1. GUI + API Dual-Mode Automation
-!!!success "Hybrid Automation - UFO²'s Unique Strength"
- **UFO² supports both GUI automation and API-based automation simultaneously.** Each agent can register multiple action servers, combining:
-
- - **GUI Automation**: Windows UI Automation (UIA) - clicking, typing, scrolling when visual interaction is needed
- - **API Automation**: Direct COM API calls, shell commands, REST APIs for efficient, reliable operations
-
- **The LLM agent dynamically chooses the best action at each step** based on:
- - Task requirements (GUI needed for visual elements vs. API for batch operations)
- - Reliability (APIs are more stable than GUI clicks)
- - Speed (APIs are faster than GUI navigation)
- - Availability (fallback to GUI when API is unavailable)
+**UFO² supports both GUI automation and API-based automation simultaneously.** Each agent can register multiple action servers, combining:
+
+- **GUI Automation**: Windows UI Automation (UIA) - clicking, typing, scrolling when visual interaction is needed
+- **API Automation**: Direct COM API calls, shell commands, REST APIs for efficient, reliable operations
+
+**The LLM agent dynamically chooses the best action at each step** based on task requirements, reliability, speed, and availability.
**Example: Word Document Automation**
@@ -178,10 +142,11 @@ Step 5: Save as PDF
→ Reason: One API call vs. multiple GUI clicks (File → Save As → Format → PDF)
```
-!!!tip "Why Hybrid Automation Matters"
- - **APIs**: ~10x faster, deterministic, no visual dependency
- - **GUI**: Handles visual elements, fallback when API unavailable
- - **LLM Decision**: Chooses optimal approach per step, not locked into one mode
+**Why Hybrid Automation Matters:**
+
+- **APIs**: ~10x faster, deterministic, no visual dependency
+- **GUI**: Handles visual elements, fallback when API unavailable
+- **LLM Decision**: Chooses optimal approach per step, not locked into one mode
### 2. Multi-Server Per Agent
@@ -232,87 +197,69 @@ MCP servers can run:
### 4. Namespace Isolation
+Each MCP server has a unique namespace that groups related tools together, preventing naming conflicts and enabling modular organization. See [Namespace Isolation](#3-namespace-isolation) section above for details.
+
## Architecture
### MCP Server Lifecycle
-```
-┌─────────────────────────────────────────────────────────┐
-│ MCP Server Lifecycle │
-└─────────────────────────────────────────────────────────┘
- │
- ┌──────────────┴──────────────┐
- │ 1. Configuration Loading │
- │ (mcp.yaml) │
- └──────────────┬──────────────┘
- │
- ┌──────────────┴──────────────┐
- │ 2. MCPServerManager │
- │ Creates BaseMCPServer │
- └──────────────┬──────────────┘
- │
- ┌──────────────┴──────────────┐
- │ 3. Server.start() │
- │ - Local: Get from registry│
- │ - HTTP: Build URL │
- │ - Stdio: Spawn process │
- └──────────────┬──────────────┘
- │
- ┌──────────────┴──────────────┐
- │ 4. Computer Registration │
- │ - List tools from server │
- │ - Register in tool registry│
- └──────────────┬──────────────┘
- │
- ┌──────────────┴──────────────┐
- │ 5. Tool Execution │
- │ - Agent sends Command │
- │ - Computer routes to tool │
- │ - MCP server executes │
- └──────────────┬──────────────┘
- │
- ┌──────────────┴──────────────┐
- │ 6. Server.reset() (optional) │
- │ - Reset server state │
- └──────────────────────────────┘
+```mermaid
+graph TB
+ Start([MCP Server Lifecycle])
+
+ Config["1. Configuration Loading
(mcp.yaml)"]
+ Manager["2. MCPServerManager
Creates BaseMCPServer"]
+ ServerStart["3. Server.start()
• Local: Get from registry
• HTTP: Build URL
• Stdio: Spawn process"]
+ Register["4. Computer Registration
• List tools from server
• Register in tool registry"]
+ Execute["5. Tool Execution
• Agent sends Command
• Computer routes to tool
• MCP server executes"]
+ Reset["6. Server.reset() (optional)
Reset server state"]
+
+ Start --> Config
+ Config --> Manager
+ Manager --> ServerStart
+ ServerStart --> Register
+ Register --> Execute
+ Execute --> Reset
+
+ style Start fill:#f3e5f5,stroke:#7b1fa2,stroke-width:2px
+ style Config fill:#e1f5fe,stroke:#0277bd,stroke-width:2px
+ style Manager fill:#fff9c4,stroke:#f57f17,stroke-width:2px
+ style ServerStart fill:#e8f5e9,stroke:#2e7d32,stroke-width:2px
+ style Register fill:#fce4ec,stroke:#c2185b,stroke-width:2px
+ style Execute fill:#e0f2f1,stroke:#00695c,stroke-width:2px
+ style Reset fill:#f3e5f5,stroke:#7b1fa2,stroke-width:2px
```
### Component Relationships
-```
-┌────────────────────────────────────────────────────────────┐
-│ MCP Architecture │
-├────────────────────────────────────────────────────────────┤
-│ │
-│ ┌──────────────────────────────────────────────┐ │
-│ │ MCPRegistry (Singleton) │ │
-│ │ - Stores server factories │ │
-│ │ - Lazy initialization │ │
-│ └───────────────────┬──────────────────────────┘ │
-│ │ │
-│ ┌──────────────────┴──────────────────────────┐ │
-│ │ MCPServerManager (Singleton) │ │
-│ │ - Creates server instances │ │
-│ │ - Maps server types to classes │ │
-│ │ - Manages server lifecycle │ │
-│ └───────────────────┬──────────────────────────┘ │
-│ │ │
-│ ┌──────────────┼──────────────┐ │
-│ │ │ │ │
-│ ┌────▼────┐ ┌──────▼─────┐ ┌────▼────┐ │
-│ │ Local │ │ HTTP │ │ Stdio │ │
-│ │ MCP │ │ MCP │ │ MCP │ │
-│ │ Server │ │ Server │ │ Server │ │
-│ └─────────┘ └────────────┘ └─────────┘ │
-│ │
-│ ┌──────────────────────────────────────────────┐ │
-│ │ Computer (Per Agent) │ │
-│ │ - Manages multiple MCP servers │ │
-│ │ - Routes commands to tools │ │
-│ │ - Maintains tool registry │ │
-│ └──────────────────────────────────────────────┘ │
-│ │
-└────────────────────────────────────────────────────────────┘
+```mermaid
+graph TB
+ subgraph Architecture["MCP Architecture"]
+ Registry["MCPRegistry (Singleton)
• Stores server factories
• Lazy initialization"]
+
+ Manager["MCPServerManager (Singleton)
• Creates server instances
• Maps server types to classes
• Manages server lifecycle"]
+
+ subgraph ServerTypes["MCP Server Types"]
+ Local["Local MCP Server
(In-Process)"]
+ HTTP["HTTP MCP Server
(Remote)"]
+ Stdio["Stdio MCP Server
(Child Process)"]
+ end
+
+ Computer["Computer (Per Agent)
• Manages multiple MCP servers
• Routes commands to tools
• Maintains tool registry"]
+
+ Registry --> Manager
+ Manager --> ServerTypes
+ Manager --> Computer
+ end
+
+ style Architecture fill:#fafafa,stroke:#424242,stroke-width:2px
+ style Registry fill:#e1f5fe,stroke:#01579b,stroke-width:2px
+ style Manager fill:#fff9c4,stroke:#f57f17,stroke-width:2px
+ style ServerTypes fill:#e8f5e9,stroke:#1b5e20,stroke-width:2px
+ style Local fill:#c8e6c9,stroke:#2e7d32,stroke-width:1px
+ style HTTP fill:#c8e6c9,stroke:#2e7d32,stroke-width:1px
+ style Stdio fill:#c8e6c9,stroke:#2e7d32,stroke-width:1px
+ style Computer fill:#fce4ec,stroke:#880e4f,stroke-width:2px
```
## Built-in MCP Servers
@@ -404,13 +351,14 @@ HardwareAgent:
path: "/mcp"
```
-!!!tip "Configuration Hierarchy"
- Agent configurations follow this hierarchy:
-
- 1. **Agent Name** (e.g., `HostAgent`, `AppAgent`)
- 2. **Sub-type** (e.g., `default`, `WINWORD.EXE`)
- 3. **Tool Type** (e.g., `data_collection`, `action`)
- 4. **Server List** (array of server configurations)
+**Configuration Hierarchy:**
+
+Agent configurations follow this hierarchy:
+
+1. **Agent Name** (e.g., `HostAgent`, `AppAgent`)
+2. **Sub-type** (e.g., `default`, `WINWORD.EXE`)
+3. **Tool Type** (e.g., `data_collection`, `action`)
+4. **Server List** (array of server configurations)
## Key Features
@@ -424,10 +372,10 @@ AppAgent:
action:
- namespace: WordCOMExecutor
type: local
- reset: true # ✅ Reset COM state when switching documents
-```
+ reset: true
+
+**When to use reset:**
-When `reset: true`:
- Server state is cleared when switching contexts
- Prevents state leakage between tasks
- Useful for stateful tools (e.g., COM APIs)
@@ -447,8 +395,7 @@ self._tool_timeout = 6000 # 100 minutes
- Protects WebSocket connections from timeouts
- Enables concurrent tool execution
-!!!warning "Timeout Protection"
- If a tool takes longer than 6000 seconds, it will be **cancelled** and return a timeout error. Adjust `_tool_timeout` for long-running operations.
+**Timeout Protection:** If a tool takes longer than 6000 seconds, it will be cancelled and return a timeout error. Adjust `_tool_timeout` for long-running operations.
### 3. Dynamic Server Management
@@ -485,6 +432,8 @@ result = await computer.run_actions([tool_call])
# Returns: List of all available action tools
```
+For more details on introspection capabilities, see [Computer - Meta Tools](../client/computer.md#meta-tools).
+
## Configuration Files
MCP configuration is located at:
@@ -493,12 +442,11 @@ MCP configuration is located at:
config/ufo/mcp.yaml
```
-!!!info "Configuration Reference"
- For detailed configuration options, see:
-
- - [MCP Configuration Guide](configuration.md) - Complete configuration reference
- - [System Configuration](../configuration/system/system_config.md) - MCP-related system settings
- - [MCP Reference](../configuration/system/mcp_reference.md) - MCP-specific settings
+For detailed configuration options, see:
+
+- [MCP Configuration Guide](configuration.md) - Complete configuration reference
+- [System Configuration](../configuration/system/system_config.md) - MCP-related system settings
+- [MCP Reference](../configuration/system/mcp_reference.md) - MCP-specific settings
## Use Cases
@@ -553,11 +501,11 @@ HardwareAgent:
To start using MCP in UFO²:
-1. **Understand the two server types** - [Data Collection](data_collection.md) and [Action](action.md)
-2. **Configure your agents** - See [Configuration Guide](configuration.md)
-3. **Use built-in servers** - Explore [Local Servers](local_servers.md)
-4. **Create custom servers** - Follow [Creating Custom MCP Servers Tutorial](../tutorials/creating_mcp_servers.md)
-5. **Deploy remotely** - Set up [Remote Servers](remote_servers.md)
+1. **Understand the two server types** - Read about [Data Collection](data_collection.md) and [Action](action.md) servers
+2. **Configure your agents** - See [Configuration Guide](configuration.md) for setup details
+3. **Use built-in servers** - Explore available [Local Servers](local_servers.md)
+4. **Create custom servers** - Follow the [Creating Custom MCP Servers Tutorial](../tutorials/creating_mcp_servers.md)
+5. **Deploy remotely** - Learn about [Remote Servers](remote_servers.md) deployment
## Related Documentation
@@ -566,15 +514,17 @@ To start using MCP in UFO²:
- [Configuration Guide](configuration.md) - How to configure MCP for agents
- [Local Servers](local_servers.md) - Built-in MCP servers
- [Remote Servers](remote_servers.md) - HTTP and Stdio deployment
-- **[Creating Custom MCP Servers Tutorial](../tutorials/creating_mcp_servers.md)** - Step-by-step guide to building custom servers
+- [Creating Custom MCP Servers Tutorial](../tutorials/creating_mcp_servers.md) - Step-by-step guide to building custom servers
- [Computer](../client/computer.md) - MCP tool execution layer
- [Agent Client](../client/overview.md) - Client architecture overview
+- [Agent Overview](../ufo2/overview.md) - UFO² agent system architecture
-!!!quote "Design Philosophy"
- MCP in UFO² follows the **separation of concerns** principle:
-
- - **Agents** decide *what* to do (high-level planning)
- - **MCP servers** implement *how* to do it (low-level execution)
- - **Computer** manages the routing between them (middleware)
-
- This architecture enables flexibility, extensibility, and maintainability.
+**Design Philosophy:**
+
+MCP in UFO² follows the **separation of concerns** principle:
+
+- **Agents** decide *what* to do (high-level planning)
+- **MCP servers** implement *how* to do it (low-level execution)
+- **Computer** manages the routing between them (middleware)
+
+This architecture enables flexibility, extensibility, and maintainability.
diff --git a/documents/docs/mcp/remote_servers.md b/documents/docs/mcp/remote_servers.md
index 87f364bef..ccb7d0c8a 100644
--- a/documents/docs/mcp/remote_servers.md
+++ b/documents/docs/mcp/remote_servers.md
@@ -2,8 +2,7 @@
Remote MCP servers run as separate processes or on different machines, communicating with UFO² over HTTP or stdio. This enables **cross-platform automation**, process isolation, and distributed workflows.
-!!!tip "Cross-Platform Automation"
- Remote servers enable **Windows UFO² agents to control Linux systems, mobile devices, and hardware** through HTTP MCP servers running on those platforms.
+**Cross-Platform Automation:** Remote servers enable **Windows UFO² agents to control Linux systems, mobile devices, and hardware** through HTTP MCP servers running on those platforms.
## Deployment Models
@@ -154,17 +153,19 @@ CustomAgent:
## Best Practices
-!!!tip "DO - Recommended Practices"
- - ✅ **Use HTTP for cross-platform automation**
- - ✅ **Use stdio for process isolation**
- - ✅ **Validate remote server connectivity** before deployment
- - ✅ **Set appropriate timeouts** for long-running commands
- - ✅ **Use environment variables** for sensitive credentials
-
-!!!danger "DON'T - Anti-Patterns"
- - ❌ **Don't expose HTTP servers to public internet** without authentication
- - ❌ **Don't hardcode credentials** in configuration files
- - ❌ **Don't forget to start remote servers** before client connection
+**Recommended Practices:**
+
+- ✅ **Use HTTP for cross-platform automation**
+- ✅ **Use stdio for process isolation**
+- ✅ **Validate remote server connectivity** before deployment
+- ✅ **Set appropriate timeouts** for long-running commands
+- ✅ **Use environment variables** for sensitive credentials
+
+**Anti-Patterns to Avoid:**
+
+- ❌ **Don't expose HTTP servers to public internet** without authentication
+- ❌ **Don't hardcode credentials** in configuration files
+- ❌ **Don't forget to start remote servers** before client connection
---
diff --git a/documents/docs/mcp/servers/app_ui_executor.md b/documents/docs/mcp/servers/app_ui_executor.md
index cb79f69a5..af4f69bc3 100644
--- a/documents/docs/mcp/servers/app_ui_executor.md
+++ b/documents/docs/mcp/servers/app_ui_executor.md
@@ -4,11 +4,10 @@
**AppUIExecutor** is an action server that provides application-level UI automation for the AppAgent. It enables precise interaction with UI controls within the currently selected application window.
-!!!info "Server Type"
- **Type**: Action
- **Deployment**: Local (in-process)
- **Agent**: AppAgent
- **LLM-Selectable**: ✅ Yes
+**Server Type:** Action
+**Deployment:** Local (in-process)
+**Agent:** AppAgent
+**LLM-Selectable:** ✅ Yes
## Server Information
@@ -412,3 +411,4 @@ screenshot = await computer.run_actions([
- [HostUIExecutor](./host_ui_executor.md) - Window selection
- [UICollector](./ui_collector.md) - Control discovery
- [Action Servers](../action.md) - Action concepts
+- [AppAgent Overview](../../ufo2/app_agent/overview.md) - AppAgent architecture
diff --git a/documents/docs/mcp/servers/bash_executor.md b/documents/docs/mcp/servers/bash_executor.md
index b0a14810f..aec03574a 100644
--- a/documents/docs/mcp/servers/bash_executor.md
+++ b/documents/docs/mcp/servers/bash_executor.md
@@ -4,11 +4,10 @@
**BashExecutor** provides Linux shell command execution with output capture and system information retrieval via HTTP MCP server.
-!!!info "Server Type"
- **Type**: Action
- **Deployment**: HTTP (remote Linux server)
- **Default Port**: 8010
- **LLM-Selectable**: ✅ Yes
+**Server Type:** Action
+**Deployment:** HTTP (remote Linux server)
+**Default Port:** 8010
+**LLM-Selectable:** ✅ Yes
## Server Information
diff --git a/documents/docs/mcp/servers/command_line_executor.md b/documents/docs/mcp/servers/command_line_executor.md
index 86c1def60..f31e2ff04 100644
--- a/documents/docs/mcp/servers/command_line_executor.md
+++ b/documents/docs/mcp/servers/command_line_executor.md
@@ -4,11 +4,10 @@
**CommandLineExecutor** provides shell command execution capabilities for launching applications and running system commands.
-!!!info "Server Type"
- **Type**: Action
- **Deployment**: Local (in-process)
- **Agent**: HostAgent, AppAgent
- **LLM-Selectable**: ✅ Yes
+**Server Type:** Action
+**Deployment:** Local (in-process)
+**Agent:** HostAgent, AppAgent
+**LLM-Selectable:** ✅ Yes
## Server Information
@@ -292,8 +291,7 @@ await computer.run_actions([
- **Async execution**: No way to know when command completes
- **Security risk**: Arbitrary command execution
-!!!tip "Alternative for Linux"
- For Linux systems with output capture and better control, use **BashExecutor** server instead.
+**Tip:** For Linux systems with output capture and better control, use **BashExecutor** server instead.
## Related Documentation
diff --git a/documents/docs/mcp/servers/constellation_editor.md b/documents/docs/mcp/servers/constellation_editor.md
index 6d1a067c1..183ffcda2 100644
--- a/documents/docs/mcp/servers/constellation_editor.md
+++ b/documents/docs/mcp/servers/constellation_editor.md
@@ -4,11 +4,10 @@
**ConstellationEditor** provides multi-device task coordination and dependency management for distributed workflows in UFO².
-!!!info "Server Type"
- **Type**: Action
- **Deployment**: Local (in-process)
- **Agent**: GalaxyAgent
- **LLM-Selectable**: ✅ Yes
+**Server Type:** Action
+**Deployment:** Local (in-process)
+**Agent:** GalaxyAgent
+**LLM-Selectable:** ✅ Yes
## Server Information
@@ -117,8 +116,7 @@ Update specific fields of an existing task.
| `target_device_id` | `str` | No | `None` | New target device |
| `tips` | `List[str]` | No | `None` | New tips list |
-!!!note "Partial Updates"
- Only provided fields are updated; others remain unchanged.
+**Note:** Only provided fields are updated; others remain unchanged.
#### Returns
@@ -445,3 +443,5 @@ await computer.run_actions([
- [Action Servers](../action.md) - Action server concepts
- [MCP Overview](../overview.md) - MCP architecture
+- [Configuration Guide](../configuration.md) - Constellation setup
+- [Local Servers](../local_servers.md) - Local server deployment
diff --git a/documents/docs/mcp/servers/excel_com_executor.md b/documents/docs/mcp/servers/excel_com_executor.md
index 6df42a582..214a8dcde 100644
--- a/documents/docs/mcp/servers/excel_com_executor.md
+++ b/documents/docs/mcp/servers/excel_com_executor.md
@@ -4,12 +4,11 @@
**ExcelCOMExecutor** provides Microsoft Excel automation via COM API for efficient spreadsheet manipulation.
-!!!info "Server Type"
- **Type**: Action
- **Deployment**: Local (in-process)
- **Agent**: AppAgent
- **Target Application**: Microsoft Excel (`EXCEL.EXE`)
- **LLM-Selectable**: ✅ Yes
+**Server Type:** Action
+**Deployment:** Local (in-process)
+**Agent:** AppAgent
+**Target Application:** Microsoft Excel (`EXCEL.EXE`)
+**LLM-Selectable:** ✅ Yes
## Server Information
diff --git a/documents/docs/mcp/servers/hardware_executor.md b/documents/docs/mcp/servers/hardware_executor.md
index b9360ea9e..39a2acf53 100644
--- a/documents/docs/mcp/servers/hardware_executor.md
+++ b/documents/docs/mcp/servers/hardware_executor.md
@@ -4,11 +4,10 @@
**HardwareExecutor** provides hardware control capabilities including Arduino HID, BB-8 test fixture, robot arm, mouse control, and screenshot capture.
-!!!info "Server Type"
- **Type**: Action
- **Deployment**: HTTP (remote server)
- **Default Port**: 8006
- **LLM-Selectable**: ✅ Yes
+**Server Type:** Action
+**Deployment:** HTTP (remote server)
+**Default Port:** 8006
+**LLM-Selectable:** ✅ Yes
## Server Information
diff --git a/documents/docs/mcp/servers/host_ui_executor.md b/documents/docs/mcp/servers/host_ui_executor.md
index 93375ff53..9db8c1b74 100644
--- a/documents/docs/mcp/servers/host_ui_executor.md
+++ b/documents/docs/mcp/servers/host_ui_executor.md
@@ -4,11 +4,10 @@
**HostUIExecutor** is an action server that provides system-level UI automation capabilities for the HostAgent. It enables window management, window switching, and cross-application interactions at the desktop level.
-!!!info "Server Type"
- **Type**: Action
- **Deployment**: Local (in-process)
- **Agent**: HostAgent
- **LLM-Selectable**: ✅ Yes (LLM chooses when to execute)
+**Server Type:** Action
+**Deployment:** Local (in-process)
+**Agent:** HostAgent
+**LLM-Selectable:** ✅ Yes (LLM chooses when to execute)
## Server Information
diff --git a/documents/docs/mcp/servers/pdf_reader_executor.md b/documents/docs/mcp/servers/pdf_reader_executor.md
index c2b2168ad..40b7da94e 100644
--- a/documents/docs/mcp/servers/pdf_reader_executor.md
+++ b/documents/docs/mcp/servers/pdf_reader_executor.md
@@ -4,11 +4,10 @@
**PDFReaderExecutor** provides PDF text extraction with optional human simulation capabilities.
-!!!info "Server Type"
- **Type**: Action
- **Deployment**: Local (in-process)
- **Agent**: AppAgent, HostAgent
- **LLM-Selectable**: ✅ Yes
+**Server Type:** Action
+**Deployment:** Local (in-process)
+**Agent:** AppAgent, HostAgent
+**LLM-Selectable:** ✅ Yes
## Server Information
diff --git a/documents/docs/mcp/servers/ppt_com_executor.md b/documents/docs/mcp/servers/ppt_com_executor.md
index c7831331c..910428a6c 100644
--- a/documents/docs/mcp/servers/ppt_com_executor.md
+++ b/documents/docs/mcp/servers/ppt_com_executor.md
@@ -4,12 +4,11 @@
**PowerPointCOMExecutor** provides Microsoft PowerPoint automation via COM API for efficient presentation manipulation.
-!!!info "Server Type"
- **Type**: Action
- **Deployment**: Local (in-process)
- **Agent**: AppAgent
- **Target Application**: Microsoft PowerPoint (`POWERPNT.EXE`)
- **LLM-Selectable**: ✅ Yes
+**Server Type:** Action
+**Deployment:** Local (in-process)
+**Agent:** AppAgent
+**Target Application:** Microsoft PowerPoint (`POWERPNT.EXE`)
+**LLM-Selectable:** ✅ Yes
## Server Information
@@ -314,10 +313,9 @@ await computer.run_actions([
- **No content creation**: Cannot add text, shapes, or images via COM (use UI automation)
- **No slide management**: Cannot add/delete/reorder slides (use UI automation)
-!!!tip "Complementary Usage"
- Combine with **AppUIExecutor** for full PowerPoint automation:
- - **PowerPointCOMExecutor**: Background colors, export
- - **AppUIExecutor**: Add slides, insert text, shapes, animations
+**Tip:** Combine with **AppUIExecutor** for full PowerPoint automation:
+- **PowerPointCOMExecutor**: Background colors, export
+- **AppUIExecutor**: Add slides, insert text, shapes, animations
## Related Documentation
diff --git a/documents/docs/mcp/servers/ui_collector.md b/documents/docs/mcp/servers/ui_collector.md
index d1c71dc35..7f2ba8bd4 100644
--- a/documents/docs/mcp/servers/ui_collector.md
+++ b/documents/docs/mcp/servers/ui_collector.md
@@ -4,11 +4,10 @@
**UICollector** is a data collection MCP server that provides comprehensive UI observation and information retrieval capabilities for the UFO² framework. It automatically gathers screenshots, window lists, control information, and UI trees to build the observation context for LLM decision-making.
-!!!info "Server Type"
- **Type**: Data Collection
- **Deployment**: Local (in-process)
- **Agent**: HostAgent, AppAgent
- **LLM-Selectable**: ❌ No (automatically invoked by framework)
+**Server Type:** Data Collection
+**Deployment:** Local (in-process)
+**Agent:** HostAgent, AppAgent
+**LLM-Selectable:** ❌ No (automatically invoked by framework)
## Server Information
diff --git a/documents/docs/mcp/servers/word_com_executor.md b/documents/docs/mcp/servers/word_com_executor.md
index d26142a08..1743d04be 100644
--- a/documents/docs/mcp/servers/word_com_executor.md
+++ b/documents/docs/mcp/servers/word_com_executor.md
@@ -4,12 +4,11 @@
**WordCOMExecutor** provides Microsoft Word automation via COM API for efficient document manipulation beyond UI automation.
-!!!info "Server Type"
- **Type**: Action
- **Deployment**: Local (in-process)
- **Agent**: AppAgent
- **Target Application**: Microsoft Word (`WINWORD.EXE`)
- **LLM-Selectable**: ✅ Yes
+**Server Type:** Action
+**Deployment:** Local (in-process)
+**Agent:** AppAgent
+**Target Application:** Microsoft Word (`WINWORD.EXE`)
+**LLM-Selectable:** ✅ Yes
## Server Information
diff --git a/documents/docs/project_directory_structure.md b/documents/docs/project_directory_structure.md
index e1a3934ad..2b44bef6d 100644
--- a/documents/docs/project_directory_structure.md
+++ b/documents/docs/project_directory_structure.md
@@ -2,11 +2,14 @@
This repository implements **UFO³**, a multi-tier AgentOS architecture spanning from single-device automation (UFO²) to cross-device orchestration (Galaxy). This document provides an overview of the directory structure to help you understand the codebase organization.
-!!!tip "Architecture Overview"
- - **🌌 Galaxy**: Multi-device DAG-based orchestration framework that coordinates agents across different platforms
- - **🎯 UFO²**: Single-device Windows desktop agent system that can serve as Galaxy's sub-agent
- - **🔌 AIP**: Agent Integration Protocol for cross-device communication
- - **⚙️ Modular Configuration**: Type-safe configs in `config/galaxy/` and `config/ufo/`
+> **New to UFO³?** Start with the [Documentation Home](index.md) for an introduction and [Quick Start Guide](getting_started/quick_start_galaxy.md) to get up and running.
+
+**Architecture Overview:**
+
+- **🌌 Galaxy**: Multi-device DAG-based orchestration framework that coordinates agents across different platforms
+- **🎯 UFO²**: Single-device Windows desktop agent system that can serve as Galaxy's sub-agent
+- **🔌 AIP**: Agent Integration Protocol for cross-device communication
+- **⚙️ Modular Configuration**: Type-safe configs in `config/galaxy/` and `config/ufo/`
---
@@ -47,12 +50,12 @@ galaxy/
│ └── presenters/ # Response formatting
│
├── constellation/ # 🌟 Core DAG management system
-│ ├── constellation.py # TaskConstellation - DAG container
+│ ├── task_constellation.py # TaskConstellation - DAG container
│ ├── task_star.py # TaskStar - Task nodes
│ ├── task_star_line.py # TaskStarLine - Dependency edges
+│ ├── enums.py # Enums for constellation components
│ ├── editor/ # Interactive DAG editing with undo/redo
-│ ├── orchestrator/ # Event-driven execution coordination
-│ └── types/ # Type definitions (priority, dependency, device)
+│ └── orchestrator/ # Event-driven execution coordination
│
├── session/ # 📊 Session lifecycle management
│ ├── galaxy_session.py # GalaxySession implementation
@@ -60,16 +63,16 @@ galaxy/
│
├── client/ # 📡 Device management
│ ├── constellation_client.py # Device registration interface
-│ ├── constellation_device_manager.py # Device management coordinator
-│ ├── constellation_config.py # Configuration loading
+│ ├── device_manager.py # Device management coordinator
+│ ├── config_loader.py # Configuration loading
│ ├── components/ # Device registry, connection manager, etc.
-│ └── orchestration/ # Client orchestration
+│ └── support/ # Client support utilities
│
├── core/ # ⚡ Foundational components
-│ ├── types/ # Type system (protocols, dataclasses, enums)
-│ ├── interfaces/ # Interface definitions
-│ ├── di/ # Dependency injection container
-│ └── events/ # Event system
+│ ├── types.py # Type system (protocols, dataclasses, enums)
+│ ├── interfaces.py # Interface definitions
+│ ├── di_container.py # Dependency injection container
+│ └── events.py # Event system
│
├── visualization/ # 🎨 Rich console visualization
│ ├── dag_visualizer.py # DAG topology visualization
@@ -86,7 +89,7 @@ galaxy/
├── galaxy.py # Main Galaxy orchestrator
├── galaxy_client.py # Galaxy client interface
├── README.md # Galaxy overview
-└── README_UFO3.md # UFO³ detailed documentation
+└── README_ZH.md # Galaxy overview (Chinese)
```
### Key Components
@@ -99,12 +102,13 @@ galaxy/
| **DeviceManager** | Multi-device coordination and assignment | [Device Manager](galaxy/client/device_manager.md) |
| **Visualization** | Rich console DAG monitoring | [Galaxy Overview](galaxy/overview.md) |
-!!!info "Galaxy Documentation"
- - **[Galaxy Overview](galaxy/overview.md)** - Architecture and concepts
- - **[Quick Start](getting_started/quick_start_galaxy.md)** - Get started with Galaxy
- - **[Constellation Agent](galaxy/constellation_agent/overview.md)** - AI-powered task planning
- - **[Constellation Orchestrator](galaxy/constellation_orchestrator/overview.md)** - Event-driven coordination
- - **[Device Manager](galaxy/client/device_manager.md)** - Multi-device management
+**Galaxy Documentation:**
+
+- [Galaxy Overview](galaxy/overview.md) - Architecture and concepts
+- [Quick Start](getting_started/quick_start_galaxy.md) - Get started with Galaxy
+- [Constellation Agent](galaxy/constellation_agent/overview.md) - AI-powered task planning
+- [Constellation Orchestrator](galaxy/constellation_orchestrator/overview.md) - Event-driven coordination
+- [Device Manager](galaxy/client/device_manager.md) - Multi-device management
---
@@ -169,12 +173,14 @@ ufo/
| **Automator** | Hybrid GUI-API automation with fallback | [Core Features](ufo2/core_features/hybrid_actions.md) |
| **RAG** | Knowledge retrieval from multiple sources | [Knowledge Substrate](ufo2/core_features/knowledge_substrate/overview.md) |
-!!!info "UFO² Documentation"
- - **[UFO² Overview](ufo2/overview.md)** - Architecture and concepts
- - **[Quick Start](getting_started/quick_start_ufo2.md)** - Get started with UFO²
- - **[HostAgent States](ufo2/host_agent/state.md)** - Desktop orchestration states
- - **[AppAgent States](ufo2/app_agent/state.md)** - Application execution states
- - **[As Galaxy Device](ufo2/as_galaxy_device.md)** - Using UFO² as Galaxy sub-agent
+**UFO² Documentation:**
+
+- [UFO² Overview](ufo2/overview.md) - Architecture and concepts
+- [Quick Start](getting_started/quick_start_ufo2.md) - Get started with UFO²
+- [HostAgent States](ufo2/host_agent/state.md) - Desktop orchestration states
+- [AppAgent States](ufo2/app_agent/state.md) - Application execution states
+- [As Galaxy Device](ufo2/as_galaxy_device.md) - Using UFO² as Galaxy sub-agent
+- [Creating Custom Agents](tutorials/creating_app_agent/overview.md) - Build your own application agents
---
@@ -194,6 +200,8 @@ aip/
**Purpose**: Enables Galaxy to coordinate UFO² agents running on different devices and platforms through standardized messaging over HTTP/WebSocket.
+**Documentation**: See [AIP Overview](aip/overview.md) for protocol details and [Message Types](aip/messages.md) for message specifications.
+
---
## 🐧 Linux Agent
@@ -208,10 +216,11 @@ Lightweight CLI-based agent for Linux devices that integrates with Galaxy as a t
**Configuration**: Configured in `config/ufo/third_party.yaml` under `THIRD_PARTY_AGENT_CONFIG.LinuxAgent`
-!!!info "Linux Agent Documentation"
- - **[Linux Agent Overview](linux/overview.md)** - Architecture and capabilities
- - **[Quick Start](getting_started/quick_start_linux.md)** - Setup and deployment
- - **[As Galaxy Device](linux/as_galaxy_device.md)** - Integration with Galaxy
+**Linux Agent Documentation:**
+
+- [Linux Agent Overview](linux/overview.md) - Architecture and capabilities
+- [Quick Start](getting_started/quick_start_linux.md) - Setup and deployment
+- [As Galaxy Device](linux/as_galaxy_device.md) - Integration with Galaxy
---
@@ -241,22 +250,24 @@ config/
└── config_schemas.py # Pydantic validation schemas
```
-!!!warning "Configuration Files"
- - Template files (`.yaml.template`) should be copied to `.yaml` and edited
- - Active config files (`.yaml`) contain API keys and should NOT be committed
- - **Galaxy**: Uses `config/galaxy/agent.yaml` for ConstellationAgent LLM settings
- - **UFO²**: Uses `config/ufo/agents.yaml` for HostAgent/AppAgent LLM settings
- - **Third-Party**: Configure LinuxAgent and HardwareAgent in `config/ufo/third_party.yaml`
- - Use `python -m ufo.tools.convert_config` to migrate from legacy configs
-
-!!!info "Configuration Documentation"
- - **[Configuration Overview](configuration/system/overview.md)** - System architecture
- - **[Agents Configuration](configuration/system/agents_config.md)** - LLM and agent settings
- - **[System Configuration](configuration/system/system_config.md)** - Runtime and execution settings
- - **[RAG Configuration](configuration/system/rag_config.md)** - Knowledge retrieval
- - **[Third-Party Configuration](configuration/system/third_party_config.md)** - LinuxAgent and external agents
- - **[MCP Configuration](configuration/system/mcp_reference.md)** - MCP server setup
- - **[Model Configuration](configuration/models/overview.md)** - LLM provider setup
+**Configuration Files:**
+
+- Template files (`.yaml.template`) should be copied to `.yaml` and edited
+- Active config files (`.yaml`) contain API keys and should NOT be committed
+- **Galaxy**: Uses `config/galaxy/agent.yaml` for ConstellationAgent LLM settings
+- **UFO²**: Uses `config/ufo/agents.yaml` for HostAgent/AppAgent LLM settings
+- **Third-Party**: Configure LinuxAgent and HardwareAgent in `config/ufo/third_party.yaml`
+- Use `python -m ufo.tools.convert_config` to migrate from legacy configs
+
+**Configuration Documentation:**
+
+- [Configuration Overview](configuration/system/overview.md) - System architecture
+- [Agents Configuration](configuration/system/agents_config.md) - LLM and agent settings
+- [System Configuration](configuration/system/system_config.md) - Runtime and execution settings
+- [RAG Configuration](configuration/system/rag_config.md) - Knowledge retrieval
+- [Third-Party Configuration](configuration/system/third_party_config.md) - LinuxAgent and external agents
+- [MCP Configuration](configuration/system/mcp_reference.md) - MCP server setup
+- [Model Configuration](configuration/models/overview.md) - LLM provider setup
---
@@ -304,19 +315,19 @@ documents/
## 🗄️ Supporting Modules
### VectorDB (`vectordb/`)
-Vector database storage for RAG knowledge sources (help documents, execution traces, user demonstrations).
+Vector database storage for RAG knowledge sources (help documents, execution traces, user demonstrations). See [RAG Configuration](configuration/system/rag_config.md) for setup details.
### Learner (`learner/`)
-Tools for indexing help documents into vector database for RAG retrieval.
+Tools for indexing help documents into vector database for RAG retrieval. Integrates with the [Knowledge Substrate](ufo2/core_features/knowledge_substrate/overview.md) feature.
### Record Processor (`record_processor/`)
Parses human demonstrations from Windows Step Recorder for learning from user actions.
### Dataflow (`dataflow/`)
-Data collection pipeline for Large Action Model (LAM) training.
+Data collection pipeline for Large Action Model (LAM) training. See the [Dataflow](ufo2/dataflow/overview.md) documentation for workflow details.
### Model Worker (`model_worker/`)
-Custom LLM deployment tools for running local models.
+Custom LLM deployment tools for running local models. See [Model Configuration](configuration/models/overview.md) for supported providers.
### Logs (`logs/`)
Auto-generated execution logs organized by task and timestamp, including screenshots, UI trees, and agent actions.
@@ -335,11 +346,12 @@ Auto-generated execution logs organized by task and timestamp, including screens
| **Best For** | Cross-device collaboration | Windows desktop tasks | Linux server operations |
| **Integration** | Orchestrates all agents | Can be Galaxy device | Can be Galaxy device |
-!!!tip "Choosing the Right Framework"
- - **Use Galaxy** when: Tasks span multiple devices/platforms, complex workflows with dependencies
- - **Use UFO² Standalone** when: Single-device Windows automation, rapid prototyping
- - **Use Linux Agent** when: Linux server/CLI operations needed in Galaxy workflows
- - **Best Practice**: Galaxy orchestrates UFO² (Windows) + Linux Agent (Linux) for cross-platform tasks
+**Choosing the Right Framework:**
+
+- **Use Galaxy** when: Tasks span multiple devices/platforms, complex workflows with dependencies
+- **Use UFO² Standalone** when: Single-device Windows automation, rapid prototyping
+- **Use Linux Agent** when: Linux server/CLI operations needed in Galaxy workflows
+- **Best Practice**: Galaxy orchestrates UFO² (Windows) + Linux Agent (Linux) for cross-platform tasks
---
@@ -440,9 +452,9 @@ UFO³ follows **SOLID principles** and established software engineering patterns
---
-!!!success "Next Steps"
- 1. Start with **[Galaxy Quick Start](getting_started/quick_start_galaxy.md)** for multi-device orchestration
- 2. Or explore **[UFO² Quick Start](getting_started/quick_start_ufo2.md)** for single-device automation
- 3. Check **[FAQ](getting_started/faq.md)** for common questions
- 4. Join our community and contribute!
+**Next Steps:**
+1. Start with [Galaxy Quick Start](getting_started/quick_start_galaxy.md) for multi-device orchestration
+2. Or explore [UFO² Quick Start](getting_started/quick_start_ufo2.md) for single-device automation
+3. Check [FAQ](faq.md) for common questions
+4. Join our community and contribute!
diff --git a/documents/docs/server/api.md b/documents/docs/server/api.md
index 43b634983..5a443d611 100644
--- a/documents/docs/server/api.md
+++ b/documents/docs/server/api.md
@@ -1,7 +1,6 @@
# HTTP API Reference
-!!!quote "REST API for External Integration"
- The UFO Server provides a RESTful HTTP API for external systems to dispatch tasks, monitor client connections, retrieve results, and perform health checks. All endpoints are prefixed with `/api`.
+The UFO Server provides a RESTful HTTP API for external systems to dispatch tasks, monitor client connections, retrieve results, and perform health checks. All endpoints are prefixed with `/api`.
## 🎯 Overview
@@ -56,11 +55,12 @@ graph LR
| **Result Retrieval** | `GET /api/task_result/{task_name}` | Fetch task execution results |
| **Health Checks** | `GET /api/health` | Monitor server status and uptime |
-!!!info "Why Use the HTTP API?"
- - **External Integration**: Trigger UFO tasks from web apps, scripts, or CI/CD pipelines
- - **Stateless**: No WebSocket connection required
- - **RESTful**: Standard HTTP methods and JSON payloads
- - **Monitoring**: Health checks for load balancers and monitoring systems
+**Why Use the HTTP API?**
+
+- **External Integration**: Trigger UFO tasks from web apps, scripts, or CI/CD pipelines
+- **Stateless**: No WebSocket connection required
+- **RESTful**: Standard HTTP methods and JSON payloads
+- **Monitoring**: Health checks for load balancers and monitoring systems
---
@@ -68,8 +68,7 @@ graph LR
### POST /api/dispatch
-!!!success "Dispatch Tasks via HTTP"
- Send a task to a connected device without establishing a WebSocket connection. Ideal for external systems, web apps, and automation scripts.
+Send a task to a connected device without establishing a WebSocket connection. Ideal for external systems, web apps, and automation scripts.
#### Request Format
@@ -87,17 +86,14 @@ graph LR
| Field | Type | Required | Default | Description |
|-------|------|----------|---------|-------------|
-| `client_id` | `string` | ✅ **Yes** | - | Target client identifier (device or constellation) |
-| `request` | `string` | ✅ **Yes** | - | Natural language task description (user request) |
+| `client_id` | `string` | �?**Yes** | - | Target client identifier (device or constellation) |
+| `request` | `string` | �?**Yes** | - | Natural language task description (user request) |
| `task_name` | `string` | ⚠️ No | Auto-generated UUID | Human-readable task identifier |
-!!!warning "Parameter Names Changed"
- **Old documentation** used `device_id` and `task` - these are **incorrect**.
-
- **Actual parameters** (verified from source code):
- - `client_id` (not `device_id`)
- - `request` (not `task`)
- - `task_name` (optional identifier)
+**Important:** The correct parameter names (verified from source code) are:
+- `client_id` (not `device_id`)
+- `request` (not `task`)
+- `task_name` (optional identifier)
#### Success Response (200)
@@ -156,10 +152,11 @@ graph LR
#### Implementation Details
-!!!example "Actual Source Code"
- ```python
- @router.post("/api/dispatch")
- async def dispatch_task_api(data: Dict[str, Any]):
+**Source Code** (verified from `ufo/server/services/api.py`):
+
+```python
+@router.post("/api/dispatch")
+async def dispatch_task_api(data: Dict[str, Any]):
# Extract parameters
client_id = data.get("client_id")
user_request = data.get("request", "")
@@ -217,8 +214,7 @@ graph LR
}
```
-!!!tip "Use `session_id` to Track Results"
- Save the returned `session_id` to retrieve task results later via `GET /api/task_result/{task_name}`.
+**Tip:** Use the returned `session_id` to track results via `GET /api/task_result/{task_name}`.
#### Sequence Diagram
@@ -256,8 +252,7 @@ sequenceDiagram
### GET /api/clients
-!!!info "List Connected Clients"
- Query all currently connected clients (devices and constellations) to determine which targets are available for task dispatch.
+Query all currently connected clients (devices and constellations) to determine which targets are available for task dispatch.
#### Request
@@ -285,12 +280,23 @@ GET /api/clients
|-------|------|-------------|
| `online_clients` | `array🚀 UFO Server Dashboard
-
- Server Status
- Total Clients
- Devices
- Constellations
- Connected Clients
- UFO Server Dashboard
-
- Health Status
- Connected Clients
-
-
/api/*]
- WS[WebSocket Endpoint
/ws]
+ subgraph "Web Layer"
+ FastAPI[FastAPI App]
+ HTTP[HTTP API]
+ WS[WebSocket /ws]
end
subgraph "Service Layer"
- WSM[Client Connection Manager
Connection Registry]
- SM[Session Manager
Execution Lifecycle]
- WSH[WebSocket Handler
AIP Protocol]
+ WSM[Client Manager]
+ SM[Session Manager]
+ WSH[WebSocket Handler]
end
- subgraph "External Interfaces"
+ subgraph "Clients"
DC[Device Clients]
CC[Constellation Clients]
- EXT[External Systems]
end
FastAPI --> HTTP
@@ -71,11 +65,9 @@ graph TB
WSH --> WSM
WSH --> SM
- WSM --> SM
DC -->|WebSocket| WS
CC -->|WebSocket| WS
- EXT -->|HTTP| HTTP
style FastAPI fill:#e1f5ff
style WSM fill:#fff4e1
@@ -94,11 +86,11 @@ This layered design ensures each component has a single, well-defined responsibi
| **Session Manager** | Execution lifecycle | ✅ Platform-specific session creation
✅ Background async task execution
✅ Result callback delivery
✅ Session cancellation |
| **WebSocket Handler** | Protocol implementation | ✅ AIP message parsing/routing
✅ Client registration
✅ Heartbeat monitoring
✅ Task/command dispatch |
-!!!note "Component Documentation"
- - [Session Manager](./session_manager.md) - Session lifecycle and background execution
- - [Client Connection Manager](./client_connection_manager.md) - Connection registry and client tracking
- - [WebSocket Handler](./websocket_handler.md) - AIP protocol message handling
- - [HTTP API](./api.md) - REST endpoint specifications
+**Component Documentation:**
+- [Session Manager](./session_manager.md) - Session lifecycle and background execution
+- [Client Connection Manager](./client_connection_manager.md) - Connection registry and client tracking
+- [WebSocket Handler](./websocket_handler.md) - AIP protocol message handling
+- [HTTP API](./api.md) - REST endpoint specifications
---
@@ -106,8 +98,7 @@ This layered design ensures each component has a single, well-defined responsibi
### 1. Multi-Client Coordination
-!!!info "Dual Client Model"
- The server supports two distinct client types with different roles in the distributed architecture.
+The server supports two distinct client types with different roles in the distributed architecture.
**Client Type Comparison:**
@@ -134,10 +125,7 @@ See [Agent Client Overview](../client/overview.md) for detailed client architect
- Coordinate complex cross-device DAG execution
- Aggregate results from multiple devices
-!!!tip "Connection Flow"
- Both client types connect to `/ws` and register using the `REGISTER` message. The server differentiates behavior based on `client_type` field.
-
- For the complete server-client architecture and design rationale, see [Server-Client Architecture](../infrastructure/agents/server_client_architecture.md).
+Both client types connect to `/ws` and register using the `REGISTER` message. The server differentiates behavior based on `client_type` field. For the complete server-client architecture and design rationale, see [Server-Client Architecture](../infrastructure/agents/server_client_architecture.md).
See [Quick Start](./quick_start.md) for registration examples.
@@ -145,26 +133,23 @@ See [Quick Start](./quick_start.md) for registration examples.
### 2. Session Lifecycle Management
-!!!success "Stateful Execution Model"
- Unlike stateless HTTP servers, the Agent Server maintains **session state** throughout task execution, enabling multi-turn interactions and result callbacks.
+Unlike stateless HTTP servers, the Agent Server maintains **session state** throughout task execution, enabling multi-turn interactions and result callbacks.
**Session Lifecycle State Machine:**
```mermaid
stateDiagram-v2
[*] --> Created: create_session()
- Created --> Running: Start background execution
- Running --> Running: Multi-turn commands
- Running --> Completed: Task succeeds
- Running --> Failed: Task fails
- Running --> Cancelled: Client disconnect
- Completed --> [*]: Cleanup resources
- Failed --> [*]: Cleanup resources
- Cancelled --> [*]: Cleanup resources
+ Created --> Running: Start execution
+ Running --> Completed: Success
+ Running --> Failed: Error
+ Running --> Cancelled: Disconnect
+ Completed --> [*]
+ Failed --> [*]
+ Cancelled --> [*]
note right of Running
- Async execution in background
- Results via callback
+ Async background execution
Non-blocking server
end note
```
@@ -179,7 +164,7 @@ stateDiagram-v2
| **Failed** | `TASK_END` (error) | Error callback delivery | Error logged |
| **Cancelled** | Client disconnect | Cancel async task, cleanup | Session removed |
-!!!warning "Platform-Specific Implementations"
+!!!warning "Platform-Specific Sessions"
The SessionManager creates different session types based on the target platform:
- **Windows**: `WindowsSession` with UI automation support
- **Linux**: `LinuxSession` with bash automation
@@ -190,15 +175,14 @@ stateDiagram-v2
- ✅ **Platform abstraction**: Hides Windows/Linux differences
- ✅ **Background execution**: Non-blocking async task execution
- ✅ **Callback routing**: Delivers results via WebSocket
-- **Resource cleanup**: Cancels tasks on disconnect
-- **Result caching**: Stores results for HTTP retrieval
+- ✅ **Resource cleanup**: Cancels tasks on disconnect
+- ✅ **Result caching**: Stores results for HTTP retrieval
---
### 3. Resilient Communication
-!!!info "Built on AIP"
- The server implements the [Agent Interaction Protocol (AIP)](../aip/overview.md), providing structured, type-safe communication with automatic failure handling.
+The server implements the [Agent Interaction Protocol (AIP)](../aip/overview.md), providing structured, type-safe communication with automatic failure handling.
**Protocol Features:**
@@ -214,33 +198,26 @@ stateDiagram-v2
```mermaid
sequenceDiagram
- participant Client as Device/Constellation
- participant Server as Agent Server
+ participant Client
+ participant Server
participant SM as Session Manager
Client-xServer: Connection lost
- Server->>Server: Detect disconnection
- Server->>SM: Cancel sessions for client
- SM->>SM: Cancel background tasks
+ Server->>SM: Cancel sessions
SM->>SM: Cleanup resources
Server->>Server: Remove from registry
- alt Client is Device
- Server->>CC: Notify constellation of failure
- end
-
Note over Server: Client can reconnect
with same client_id
```
-!!!danger "Session Cancellation on Disconnect"
+!!!danger "Important: Session Cancellation on Disconnect"
When a client disconnects (device or constellation), **all associated sessions are immediately cancelled** to prevent orphaned tasks and resource leaks.
---
### 4. Dual API Interface
-!!!tip "Flexible Integration Options"
- The server provides two API styles to support different integration patterns: real-time WebSocket for agents and simple HTTP for external systems.
+The server provides two API styles to support different integration patterns: real-time WebSocket for agents and simple HTTP for external systems.
**WebSocket API (AIP-based)**
@@ -278,7 +255,7 @@ Purpose: Task dispatch and monitoring from external systems (HTTP clients, CI/CD
| Endpoint | Method | Purpose | Authentication |
|----------|--------|---------|----------------|
| `/api/dispatch` | POST | Dispatch task to device | Optional (if configured) |
-| `/api/task_result/{id}` | GET | Retrieve task results | Optional |
+| `/api/task_result/{task_name}` | GET | Retrieve task results | Optional |
| `/api/clients` | GET | List connected clients | Optional |
| `/api/health` | GET | Server health check | None |
@@ -288,14 +265,15 @@ Purpose: Task dispatch and monitoring from external systems (HTTP clients, CI/CD
curl -X POST http://localhost:5000/api/dispatch \
-H "Content-Type: application/json" \
-d '{
+ "client_id": "my_windows_device",
"request": "Open Notepad and type Hello World",
- "target_id": "windows_agent_001"
+ "task_name": "test_task_001"
}'
- # Response: {"session_id": "session_abc123"}
+ # Response: {"status": "dispatched", "session_id": "session_abc123", "task_name": "test_task_001"}
# Retrieve results
- curl http://localhost:5000/api/task_result/session_abc123
+ curl http://localhost:5000/api/task_result/test_task_001
```
See [HTTP API Reference](./api.md) for complete endpoint documentation.
@@ -316,11 +294,11 @@ sequenceDiagram
participant WSH as WebSocket Handler
participant DC as Device Client
- EXT->>HTTP: POST /api/dispatch
{request, target_id}
+ EXT->>HTTP: POST /api/dispatch
{client_id, request, task_name}
HTTP->>SM: create_session()
SM->>SM: Create platform session
SM-->>HTTP: session_id
- HTTP-->>EXT: 200 {session_id}
+ HTTP-->>EXT: 200 {session_id, task_name}
SM->>WSH: send_task(session_id, task)
WSH->>DC: TASK message (AIP)
@@ -387,14 +365,13 @@ sequenceDiagram
Server->>CC: TASK_END (both tasks)
```
-The server acts as a message router, forwarding tasks to target devices and routing results back to the constellation orchestrator.
+The server acts as a message router, forwarding tasks to target devices and routing results back to the constellation orchestrator. See [Constellation Documentation](../galaxy/overview.md) for more details on multi-device orchestration.
---
## Platform Support
-!!!info "Cross-Platform Compatibility"
- The server automatically detects client platforms and creates appropriate session implementations.
+The server automatically detects client platforms and creates appropriate session implementations.
**Supported Platforms:**
@@ -406,27 +383,27 @@ The server acts as a message router, forwarding tasks to target devices and rout
**Platform Auto-Detection:**
-```python
-# Server auto-detects platform from client registration
-# Or override globally with --platform flag
+The server automatically detects the client's platform during registration. You can override this globally with the `--platform` flag when needed for testing or specific deployment scenarios.
+```bash
python -m ufo.server.app --platform windows # Force Windows sessions
python -m ufo.server.app --platform linux # Force Linux sessions
python -m ufo.server.app # Auto-detect (default)
```
-!!!warning "Platform Override Use Cases"
+!!!warning "When to Use Platform Override"
Use `--platform` override when:
- Testing cross-platform sessions without actual devices
- Running server in container different from target platform
- Debugging platform-specific session behavior
+For more details on platform-specific implementations, see [Windows Agent](../linux/overview.md) and [Linux Agent](../linux/overview.md).
+
---
## Configuration
-!!!tip "Minimal Configuration Required"
- The server runs out-of-the-box with sensible defaults. Advanced configuration inherits from UFO's central config system.
+The server runs out-of-the-box with sensible defaults. Advanced configuration inherits from UFO's central config system.
### Command-Line Arguments
@@ -476,8 +453,7 @@ See [Configuration Guide](../configuration/system/overview.md) for comprehensive
### Health Monitoring
-!!!success "Built-in Health Checks"
- Monitor server status and performance using HTTP endpoints.
+Monitor server status and performance using HTTP endpoints.
**Health Check Endpoints:**
@@ -488,8 +464,7 @@ curl http://localhost:5000/api/health
# Response:
# {
# "status": "healthy",
-# "uptime_seconds": 3600,
-# "connected_clients": 5
+# "online_clients": [...]
# }
# Connected clients list
@@ -497,23 +472,15 @@ curl http://localhost:5000/api/clients
# Response:
# {
-# "clients": [
-# {"client_id": "windows_001", "type": "device", "connected_at": "2025-01-01T10:00:00Z"},
-# {"client_id": "constellation_main", "type": "constellation", "connected_at": "2025-01-01T10:05:00Z"}
-# ]
+# "online_clients": ["windows_001", "linux_002", ...]
# }
```
-See [Monitoring Guide](./monitoring.md) for comprehensive monitoring strategies including:
-- Performance metrics collection
-- Log aggregation patterns
-- Alert configuration
-- Dashboard setup
+For comprehensive monitoring strategies including performance metrics collection, log aggregation patterns, alert configuration, and dashboard setup, see [Monitoring Guide](./monitoring.md).
### Error Handling
-!!!danger "Failure Scenarios"
- The server handles common failure scenarios gracefully to maintain system stability.
+The server handles common failure scenarios gracefully to maintain system stability.
**Disconnection Handling Matrix:**
@@ -525,15 +492,8 @@ See [Monitoring Guide](./monitoring.md) for comprehensive monitoring strategies
| **Network Partition** | Heartbeat timeout | Mark disconnected, enable reconnection | Client reconnects with same ID |
| **Server Crash** | N/A | Clients detect via heartbeat | Clients reconnect to new instance |
-!!!note "Reconnection Behavior"
- Clients can reconnect with the same `client_id`. The server will:
- - Re-register the client
- - Restore heartbeat monitoring
- - **Not restore previous sessions** (sessions are ephemeral)
-
----
-
-## Best Practices
+!!!note "Reconnection Support"
+ Clients can reconnect with the same `client_id`. The server will re-register the client and restore heartbeat monitoring, but **will not restore previous sessions** (sessions are ephemeral).
---
@@ -541,8 +501,7 @@ See [Monitoring Guide](./monitoring.md) for comprehensive monitoring strategies
### Development Environment
-!!!tip "Local Development Setup"
- Optimize your development workflow with these recommended practices.
+Optimize your development workflow with these recommended practices.
**Recommended Development Configuration:**
@@ -575,13 +534,12 @@ python -m ufo.server.app \
# Terminal 3: Dispatch test task
curl -X POST http://127.0.0.1:5000/api/dispatch \
-H "Content-Type: application/json" \
- -d '{"request": "Open Notepad", "target_id": "windowsagent"}'
+ -d '{"client_id": "windowsagent", "request": "Open Notepad", "task_name": "test_001"}'
```
### Production Deployment
-!!!warning "Production Hardening Required"
- The default configuration is **not production-ready**. Implement these security and reliability measures.
+The default configuration is **not production-ready**. Implement these security and reliability measures.
**Production Architecture:**
@@ -631,63 +589,43 @@ graph LR
| **Logging** | Structured logging, log aggregation (ELK) | Centralized debugging and audit trails |
| **Resource Limits** | Set max connections, memory limits | Prevent resource exhaustion |
-!!!example "Nginx Reverse Proxy Configuration"
- ```nginx
- upstream ufo_server {
- server localhost:5000;
- server localhost:5001;
- server localhost:5002;
+**Example Nginx Configuration:**
+
+```nginx
+upstream ufo_server {
+ server localhost:5000;
+}
+
+server {
+ listen 443 ssl;
+ server_name ufo-server.example.com;
+
+ ssl_certificate /path/to/cert.pem;
+ ssl_certificate_key /path/to/key.pem;
+
+ # WebSocket endpoint
+ location /ws {
+ proxy_pass http://ufo_server;
+ proxy_http_version 1.1;
+ proxy_set_header Upgrade $http_upgrade;
+ proxy_set_header Connection "upgrade";
+ proxy_set_header Host $host;
+ proxy_read_timeout 3600s;
}
- server {
- listen 443 ssl;
- server_name ufo-server.example.com;
-
- ssl_certificate /path/to/cert.pem;
- ssl_certificate_key /path/to/key.pem;
-
- # WebSocket endpoint
- location /ws {
- proxy_pass http://ufo_server;
- proxy_http_version 1.1;
- proxy_set_header Upgrade $http_upgrade;
- proxy_set_header Connection "upgrade";
- proxy_set_header Host $host;
- proxy_read_timeout 3600s; # Long timeout for persistent connections
- }
-
- # HTTP API
- location /api/ {
- proxy_pass http://ufo_server;
- proxy_set_header Host $host;
- }
+ # HTTP API
+ location /api/ {
+ proxy_pass http://ufo_server;
+ proxy_set_header Host $host;
}
- ```
+}
+```
-!!!example "Systemd Service File"
- ```ini
- [Unit]
- Description=UFO Agent Server
- After=network.target
-
- [Service]
- Type=simple
- User=ufo
- WorkingDirectory=/opt/ufo
- ExecStart=/opt/ufo/venv/bin/python -m ufo.server.app --port 5000 --log-level INFO
- Restart=always
- RestartSec=10
- StandardOutput=append:/var/log/ufo/server.log
- StandardError=append:/var/log/ufo/server-error.log
-
- [Install]
- WantedBy=multi-user.target
- ```
+
### Scaling Strategies
-!!!info "Horizontal Scaling Considerations"
- The server can scale horizontally for high-load deployments, but requires careful session management.
+The server can scale horizontally for high-load deployments, but requires careful session management.
**Scaling Patterns:**
@@ -697,10 +635,10 @@ graph LR
| **Horizontal (Sticky Sessions)** | Multiple instances with session affinity | 100-1000 clients | Load balancer routes same client to same instance |
| **Horizontal (Shared State)** | Multiple instances with Redis | > 1000 clients | Requires session state externalization |
-!!!warning "Current Limitation: Sticky Sessions Required"
- The current implementation stores session state in-memory. For horizontal scaling, use **sticky sessions** (client affinity) in your load balancer to route clients to consistent server instances.
-
- **Future**: Shared state backend (Redis) for true stateless horizontal scaling.
+!!!warning "Current Limitation"
+ The current implementation stores session state in-memory. For horizontal scaling, use **sticky sessions** (client affinity) in your load balancer to route clients to consistent server instances. **Future**: Shared state backend (Redis) for true stateless horizontal scaling.
+
+
---
@@ -708,8 +646,6 @@ graph LR
### Common Issues
-!!!bug "Common Problems and Solutions"
-
**Issue: Clients Can't Connect**
```bash
@@ -739,7 +675,7 @@ python -m ufo.server.app --host 0.0.0.0 --port 5000
# Solution:
# Ensure client_id in request matches registered client
curl -X POST http://localhost:5000/api/dispatch \
- -d '{"request": "test", "target_id": "correct_client_id"}'
+ -d '{"client_id": "correct_client_id", "request": "test", "task_name": "test_001"}'
```
**Issue: Memory Leak / High Memory Usage**
@@ -776,6 +712,8 @@ curl -X POST http://localhost:5000/api/dispatch \
## Documentation Map
+Explore related documentation to deepen your understanding of the Agent Server ecosystem.
+
### Getting Started
| Document | Purpose |
@@ -803,16 +741,16 @@ curl -X POST http://localhost:5000/api/dispatch \
| Document | Purpose |
|----------|---------|
| [AIP Protocol](../aip/overview.md) | Communication protocol specification |
-| [Configuration](../configuration/system/overview.md) | UFO configuration system |
-| [Agents](../infrastructure/agents/overview.md) | Agent architecture and design |
+| [Agent Architecture](../infrastructure/agents/overview.md) | Agent design and FSM framework |
+| [Server-Client Architecture](../infrastructure/agents/server_client_architecture.md) | Distributed architecture rationale |
| [Client Overview](../client/overview.md) | Device client architecture |
+| [MCP Integration](../mcp/overview.md) | Model Context Protocol tool servers |
---
## Next Steps
-!!!quote "Learning Path"
- Follow this recommended sequence to master the Agent Server:
+Follow this recommended sequence to master the Agent Server:
**1. Run the Server** (5 minutes)
- Follow the [Quick Start Guide](./quick_start.md)
@@ -839,3 +777,4 @@ curl -X POST http://localhost:5000/api/dispatch \
- Implement monitoring
- Test failover scenarios
+
\ No newline at end of file
diff --git a/documents/docs/server/quick_start.md b/documents/docs/server/quick_start.md
index 2ce78de46..19392c6de 100644
--- a/documents/docs/server/quick_start.md
+++ b/documents/docs/server/quick_start.md
@@ -1,26 +1,24 @@
# Quick Start
-!!!quote "Get Up and Running in 5 Minutes"
- This hands-on guide walks you through starting the UFO Agent Server, connecting clients, and dispatching your first task. Perfect for first-time users.
+This hands-on guide walks you through starting the UFO Agent Server, connecting clients, and dispatching your first task. Perfect for first-time users.
---
## 📋 Prerequisites
-!!!info "Requirements Checklist"
- Before you begin, ensure you have:
-
- - **Python 3.10+** installed
- - **UFO² dependencies** installed (`pip install -r requirements.txt`)
- - **Network connectivity** for WebSocket connections
- - **Terminal access** (PowerShell, bash, or equivalent)
+Before you begin, ensure you have:
+
+- **Python 3.10+** installed
+- **UFO dependencies** installed (`pip install -r requirements.txt`)
+- **Network connectivity** for WebSocket connections
+- **Terminal access** (PowerShell, bash, or equivalent)
| Component | Minimum Version | Recommended |
|-----------|----------------|-------------|
| Python | 3.10 | 3.11+ |
| FastAPI | 0.104+ | Latest |
| Uvicorn | 0.24+ | Latest |
-| UFO² | - | Latest commit |
+| UFO | - | Latest commit |
---
@@ -28,79 +26,73 @@
### Basic Startup
-!!!example "Minimal Command"
- Start the server with default settings (port **5000**):
-
- ```bash
- python -m ufo.server.app
- ```
+Start the server with default settings (port **5000**):
+
+```bash
+python -m ufo.server.app
+```
**Expected Output:**
```console
2024-11-04 14:30:22 - ufo.server.app - INFO - Starting UFO Server on 0.0.0.0:5000
2024-11-04 14:30:22 - ufo.server.app - INFO - Platform: auto-detected
-2024-11-04 14:30:22 - ufo.server.app - INFO - Log level: INFO
+2024-11-04 14:30:22 - ufo.server.app - INFO - Log level: WARNING
INFO: Started server process [12345]
INFO: Waiting for application startup.
INFO: Application startup complete.
INFO: Uvicorn running on http://0.0.0.0:5000 (Press CTRL+C to quit)
```
-!!!success "Server Running"
- Once you see "Uvicorn running", the server is ready to accept WebSocket connections at `ws://0.0.0.0:5000/ws`
+Once you see "Uvicorn running", the server is ready to accept WebSocket connections at `ws://0.0.0.0:5000/ws`.
### Configuration Options
-!!!tip "CLI Arguments Reference"
- The server supports several command-line arguments for customization. All arguments are optional.
-
| Argument | Type | Default | Description | Example |
|----------|------|---------|-------------|---------|
| `--port` | int | `5000` | Server listening port | `--port 8080` |
| `--host` | str | `0.0.0.0` | Bind address (0.0.0.0 = all interfaces) | `--host 127.0.0.1` |
| `--platform` | str | `auto` | Platform override (`windows`, `linux`) | `--platform windows` |
-| `--log-level` | str | `INFO` | Logging verbosity | `--log-level DEBUG` |
+| `--log-level` | str | `WARNING` | Logging verbosity | `--log-level DEBUG` |
| `--local` | flag | `False` | Restrict to localhost connections only | `--local` |
-!!!example "Common Startup Configurations"
+**Common Startup Configurations:**
- **Development (Local Only):**
- ```bash
- python -m ufo.server.app --local --log-level DEBUG
- ```
- - Accepts connections only from `localhost`
- - Verbose debug logging
- - Default port 5000
-
- **Custom Port:**
- ```bash
- python -m ufo.server.app --port 8080
- ```
- - Useful if port 5000 is already in use
- - Accessible from network
-
- **Production (Linux):**
- ```bash
- python -m ufo.server.app --port 5000 --platform linux --log-level WARNING
- ```
- - Explicit platform specification
- - Reduced logging for performance
- - Production-ready configuration
-
- **Multi-Interface Binding:**
- ```bash
- python -m ufo.server.app --host 192.168.1.100 --port 5000
- ```
- - Binds to specific network interface
- - Useful for multi-homed servers
+**Development (Local Only):**
+```bash
+python -m ufo.server.app --local --log-level DEBUG
+```
+- Accepts connections only from `localhost`
+- Verbose debug logging
+- Default port 5000
+
+**Custom Port:**
+```bash
+python -m ufo.server.app --port 8080
+```
+- Useful if port 5000 is already in use
+- Accessible from network
+
+**Production (Linux):**
+```bash
+python -m ufo.server.app --port 5000 --platform linux --log-level WARNING
+```
+- Explicit platform specification
+- Reduced logging for performance
+- Production-ready configuration
+
+**Multi-Interface Binding:**
+```bash
+python -m ufo.server.app --host 192.168.1.100 --port 5000
+```
+- Binds to specific network interface
+- Useful for multi-homed servers
---
-## 🖥Connecting Device Clients
+## 🖥️ Connecting Device Clients
-!!!info "What is a Device Client?"
- A Device Client is an agent running on a physical or virtual machine that can execute tasks. Each device connects via WebSocket and registers with a unique `client_id`.
+A Device Client is an agent running on a physical or virtual machine that can execute tasks. Each device connects via WebSocket and registers with a unique `client_id`.
Once the server is running, connect device agents using the command line:
@@ -116,12 +108,10 @@ python -m ufo.client.client --ws --ws-server ws://127.0.0.1:5000/ws --client-id
python -m ufo.client.client --ws --ws-server ws://127.0.0.1:5000/ws --client-id my_linux_device --platform linux
```
-
-!!!success "Registration Success Indicator"
- When a client connects successfully, the server logs will display:
- ```console
- INFO: [WS] Registered device client: my_windows_device
- ```
+When a client connects successfully, the server logs will display:
+```console
+INFO: [WS] 📱 Device client my_windows_device connected
+```
### Client Connection Parameters
@@ -132,11 +122,11 @@ python -m ufo.client.client --ws --ws-server ws://127.0.0.1:5000/ws --client-id
| `--client-id` | Yes | string | Unique device identifier (must be unique across all clients) | `device_win_001` |
| `--platform` | ⚠️ Optional | string | Platform type: `windows`, `linux` | `--platform windows` |
-!!!warning "Client ID Uniqueness"
+!!!warning "Important: Client ID Uniqueness"
Each `client_id` must be globally unique. If a client connects with an existing ID, the old connection will be terminated.
!!!tip "Platform Auto-Detection"
- If you don't specify `--platform`, the client will auto-detect the operating system using Python's `platform.system()`. However, **explicit specification is recommended** for clarity and to avoid edge cases.
+ If you don't specify `--platform`, the client will auto-detect the operating system. However, **explicit specification is recommended** for clarity.
### Registration Protocol Flow
@@ -144,33 +134,24 @@ python -m ufo.client.client --ws --ws-server ws://127.0.0.1:5000/ws --client-id
sequenceDiagram
participant C as Device Client
participant S as Agent Server
- participant M as ClientConnectionManager
- Note over C,S: 1️⃣ WebSocket Connection
- C->>+S: WebSocket CONNECT to /ws
+ C->>S: WebSocket CONNECT /ws
S-->>C: Connection accepted
- Note over C,S: 2️⃣ AIP Registration Protocol
- C->>S: REGISTER message
{client_id, client_type, platform}
- S->>S: Validate client_id uniqueness
- S->>M: Register client in ClientConnectionManager
-
- Note over C,S: 3️⃣ Confirmation
- S-->>-C: REGISTER_CONFIRM
{status: "success"}
+ C->>S: REGISTER
{client_id, platform}
+ S->>S: Validate & register
+ S-->>C: REGISTER_CONFIRM
Note over C: Client Ready
- C->>C: Start task listening loop
```
-!!!note "AIP Protocol Integration"
- The registration process uses the **Agent Interaction Protocol (AIP)** for structured, reliable communication. See [AIP Documentation](../aip/overview.md) for protocol details.
+The registration process uses the **Agent Interaction Protocol (AIP)** for structured communication. See [AIP Documentation](../aip/overview.md) for details.
---
## 🌌 Connecting Constellation Clients
-!!!info "What is a Constellation Client?"
- A Constellation Client is an orchestrator that coordinates multi-device tasks. It connects to the server and can dispatch work across multiple registered device clients.
+A Constellation Client is an orchestrator that coordinates multi-device tasks. It connects to the server and can dispatch work across multiple registered device clients.
### Basic Constellation Connection
@@ -186,14 +167,10 @@ python -m galaxy.constellation.constellation --ws --ws-server ws://127.0.0.1:500
| `--ws-server` | Yes | Server WebSocket URL | `ws://127.0.0.1:5000/ws` |
| `--target-id` | ⚠️ Optional | Initial target device ID for tasks | `my_windows_device` |
-!!!danger "Target Device Must Be Online"
- If you specify `--target-id`, that device **must already be connected** to the server. Otherwise, the constellation registration will fail with:
- ```
- Target device 'my_windows_device' is not connected
- ```
+!!!danger "Important: Target Device Must Be Online"
+ If you specify `--target-id`, that device **must already be connected** to the server. Otherwise, registration will fail with: `Target device 'my_windows_device' is not connected`
-!!!tip "Multi-Device Orchestration"
- A constellation can dynamically dispatch tasks to different devices, not just the `target-id`. The `target-id` is primarily used for initial routing.
+A constellation can dynamically dispatch tasks to different devices, not just the `target-id`. For more on multi-device orchestration, see [Constellation Documentation](../galaxy/overview.md).
---
@@ -201,78 +178,58 @@ python -m galaxy.constellation.constellation --ws --ws-server ws://127.0.0.1:500
### Method 1: Check Connected Clients
-!!!example "List All Connected Clients"
- Use the HTTP API to verify connections:
-
- ```bash
- curl http://localhost:5000/api/clients
- ```
+Use the HTTP API to verify connections:
+
+```bash
+curl http://localhost:5000/api/clients
+```
**Expected Response:**
```json
{
- "online_clients": [
- {
- "client_id": "my_windows_device",
- "client_type": "device",
- "platform": "windows",
- "connected_at": "2024-11-04T14:30:45.123456",
- "uptime_seconds": 120
- }
- ]
+ "online_clients": ["my_windows_device", "my_linux_device"]
}
```
-!!!success "Client Connected"
- If you see your `client_id` in the `online_clients` list, the device is successfully connected and ready to receive tasks.
+If you see your `client_id` in the list, the device is successfully connected and ready to receive tasks.
### Method 2: Health Check
-!!!example "Server Health Endpoint"
- ```bash
- curl http://localhost:5000/api/health
- ```
+```bash
+curl http://localhost:5000/api/health
+```
**Expected Response:**
```json
{
"status": "healthy",
- "online_clients": [
- {
- "client_id": "my_windows_device",
- "client_type": "device",
- "platform": "windows"
- }
- ]
+ "online_clients": ["my_windows_device"]
}
```
-!!!tip "Monitoring Tip"
- The `/api/health` endpoint is useful for health checks in production monitoring systems (e.g., Prometheus, Nagios).
+The `/api/health` endpoint is useful for health checks in production monitoring systems.
---
## 🎯 Dispatching Your First Task
-!!!info "Task Dispatch via HTTP API"
- The easiest way to send a task to a connected device is through the HTTP `/api/dispatch` endpoint. This is ideal for external integrations, scripts, or manual testing.
+The easiest way to send a task to a connected device is through the HTTP `/api/dispatch` endpoint.
### Basic Task Dispatch
-!!!example "Send a Simple Task"
- Use the HTTP API to dispatch a task to a connected device:
-
- ```bash
- curl -X POST http://localhost:5000/api/dispatch \
- -H "Content-Type: application/json" \
- -d '{
- "client_id": "my_windows_device",
- "request": "Open Notepad and type Hello World",
- "task_name": "test_task_001"
- }'
- ```
+Use the HTTP API to dispatch a task to a connected device:
+
+```bash
+curl -X POST http://localhost:5000/api/dispatch \
+ -H "Content-Type: application/json" \
+ -d '{
+ "client_id": "my_windows_device",
+ "request": "Open Notepad and type Hello World",
+ "task_name": "test_task_001"
+ }'
+```
**Request Body Parameters:**
@@ -293,56 +250,36 @@ python -m galaxy.constellation.constellation --ws --ws-server ws://127.0.0.1:500
}
```
-!!!success "Task Dispatched"
- The `status: "dispatched"` indicates the task was successfully sent to the device via AIP protocol. The device will begin executing immediately.
+The `status: "dispatched"` indicates the task was successfully sent to the device. The device will begin executing immediately.
!!!warning "Client Must Be Online"
- If the target `client_id` is not connected, you'll receive:
- ```json
- {
- "detail": "Client not online"
- }
- ```
- Use `/api/clients` to verify the device is connected first.
+ If the target `client_id` is not connected, you'll receive `{"detail": "Client not online"}`. Use `/api/clients` to verify the device is connected first.
### Task Execution Flow
```mermaid
sequenceDiagram
participant API as HTTP Client
- participant S as Agent Server
- participant D as Device Client
- participant U as UFO Agent
-
- Note over API,S: 1️⃣ Task Submission
- API->>S: POST /api/dispatch
{client_id, request, task_name}
- S->>S: Validate client_id is online
- S->>S: Generate session_id & response_id
-
- Note over S,D: 2️⃣ AIP Task Assignment
- S->>D: TASK_ASSIGNMENT
{user_request, task_name, session_id}
- D->>U: Start UFO round execution
-
- Note over D,U: 3️⃣ Task Execution
- U->>U: Parse request Select actions Execute
- U-->>D: Execution result
-
- Note over D,S: 4️⃣ Result Reporting
- D->>S: TASK_RESULT
{status, result, session_id}
-
- Note over API,S: 5️⃣ Result Retrieval
- API->>S: GET /api/task_result/{task_name}
- S-->>API: {status: "done", result}
+ participant S as Server
+ participant D as Device
+
+ API->>S: POST /api/dispatch
+ S->>D: TASK (AIP)
+ D->>D: Execute task
+ D->>S: TASK_RESULT
+ API->>S: GET /task_result
+ S->>API: Results
```
+For detailed API specifications, see [HTTP API Reference](./api.md).
+
### Checking Task Results
-!!!example "Poll for Task Completion"
- Use the task name to retrieve results:
-
- ```bash
- curl http://localhost:5000/api/task_result/test_task_001
- ```
+Use the task name to retrieve results:
+
+```bash
+curl http://localhost:5000/api/task_result/test_task_001
+```
**While Task is Running:**
@@ -409,136 +346,106 @@ curl -X POST http://localhost:5000/api/dispatch \
### Issue 1: Port Already in Use
-!!!bug "Error: `Address already in use`"
- **Symptoms:**
- ```console
- ERROR: [Errno 98] Address already in use
- ```
-
- **Cause:** Another process is already using port 5000.
-
- **Solutions:**
-
- **Use Different Port:**
- ```bash
- python -m ufo.server.app --port 8080
- ```
+**Symptoms:**
+```console
+ERROR: [Errno 98] Address already in use
+```
+
+**Cause:** Another process is already using port 5000.
+
+**Solutions:**
+
+**Use Different Port:**
+```bash
+python -m ufo.server.app --port 8080
+```
+
+**Find & Kill Process (Linux/Mac):**
+```bash
+# Find process using port 5000
+lsof -i :5000
- **Find & Kill Process (Linux/Mac):**
- ```bash
- # Find process using port 5000
- lsof -i :5000
-
- # Kill the process
- kill -9
{status, result}
```
-!!!example "Device Client Self-Execution"
- When a **device** requests a task for itself:
-
- ```python
- async def handle_task_request(self, data: ClientMessage, websocket: WebSocket):
- client_id = data.client_id
- client_type = data.client_type
-
- if client_type == ClientType.DEVICE:
- # Device executing task on itself
- target_ws = websocket # Use requesting client's WebSocket
- platform = self.client_manager.get_client_info(client_id).platform
- target_device_id = client_id
- # ...
- ```
+**Device Client Self-Execution:**
-!!!example "Constellation Orchestrated Execution"
- When a **constellation** dispatches a task to a target device:
+When a **device** requests a task for itself:
+
+```python
+async def handle_task_request(self, data: ClientMessage, websocket: WebSocket):
+ client_id = data.client_id
+ client_type = data.client_type
+
+ if client_type == ClientType.DEVICE:
+ # Device executing task on itself
+ target_ws = websocket # Use requesting client's WebSocket
+ platform = self.client_manager.get_client_info(client_id).platform
+ target_device_id = client_id
+ # ...
+```
+
+**Constellation Orchestrated Execution:**
+
+When a **constellation** dispatches a task to a target device:
+
+```python
+async def handle_task_request(self, data: ClientMessage, websocket: WebSocket):
+ client_id = data.client_id
+ client_type = data.client_type
- ```python
- async def handle_task_request(self, data: ClientMessage, websocket: WebSocket):
- client_id = data.client_id
- client_type = data.client_type
+ if client_type == ClientType.CONSTELLATION:
+ # Constellation dispatching to target device
+ target_device_id = data.target_id
+ target_ws = self.client_manager.get_client(target_device_id)
+ platform = self.client_manager.get_client_info(target_device_id).platform
- if client_type == ClientType.CONSTELLATION:
- # Constellation dispatching to target device
- target_device_id = data.target_id
- target_ws = self.client_manager.get_client(target_device_id)
- platform = self.client_manager.get_client_info(target_device_id).platform
-
- # Validate target device exists
+ # Validate target device exists
if not target_ws:
raise ValueError(f"Target device '{target_device_id}' not connected")
@@ -512,7 +518,7 @@ async def send_result(sid: str, result_msg: ServerMessage):
if websocket.client_state == WebSocketState.CONNECTED:
await websocket.send_text(result_msg.model_dump_json())
-# Execute in background
+# Execute in background via SessionManager
await self.session_manager.execute_task_async(
session_id=session_id,
task_name=task_name,
@@ -526,14 +532,15 @@ await self.session_manager.execute_task_async(
await self.task_protocol.send_ack(session_id=session_id)
```
-!!!info "Why Immediate ACK?"
- The handler sends an **immediate ACK** after dispatching the task to the SessionManager. This confirms:
-
- - Task was received and validated
- - Session was created successfully
- - Task is now executing in background
-
- The actual task result is delivered later via the `send_result` callback.
+**Why Immediate ACK?**
+
+The handler sends an **immediate ACK** after dispatching the task to the [SessionManager](./session_manager.md). This confirms:
+
+- Task was received and validated
+- Session was created successfully
+- Task is now executing in background
+
+The actual task result is delivered later via the `send_result` callback.
**Session Tracking:**
@@ -547,8 +554,7 @@ await self.task_protocol.send_ack(session_id=session_id)
### Command Result Handling
-!!!info "Device Server Communication"
- When a device executes a command (e.g., "click button", "type text"), it sends results back to the server for processing by the session's command dispatcher.
+When a device executes a command (e.g., "click button", "type text"), it sends results back to the server for processing by the session's command dispatcher.
**Command Result Flow:**
@@ -606,15 +612,15 @@ async def handle_command_result(self, data: ClientMessage):
)
```
-!!!warning "Critical for Session Execution"
- Without proper command result handling, sessions would **hang indefinitely** waiting for device responses. The `set_result()` call is what unblocks the `await` in the command dispatcher.
+**Critical for Session Execution:**
+
+Without proper command result handling, sessions would **hang indefinitely** waiting for device responses. The `set_result()` call is what unblocks the `await` in the command dispatcher.
---
### Heartbeat Handling
-!!!success "Connection Health Monitoring"
- Heartbeats are lightweight ping/pong messages that ensure the WebSocket connection is alive and healthy.
+Heartbeats are lightweight ping/pong messages that ensure the WebSocket connection is alive and healthy.
```python
async def handle_heartbeat(self, data: ClientMessage, websocket: WebSocket) -> None:
@@ -650,18 +656,18 @@ async def handle_heartbeat(self, data: ClientMessage, websocket: WebSocket) -> N
}
```
-!!!tip "Heartbeat Best Practices"
- - **Frequency:** Clients should send heartbeats every **30-60 seconds**
- - **Timeout:** Server should consider connection dead after **2-3 missed heartbeats**
- - **Lightweight:** Heartbeat messages are small and processed quickly
- - **Non-blocking:** Heartbeat handling doesn't block task execution
+**Heartbeat Best Practices:**
+
+- **Frequency:** Clients should send heartbeats every **30-60 seconds**
+- **Timeout:** Server should consider connection dead after **2-3 missed heartbeats**
+- **Lightweight:** Heartbeat messages are small and processed quickly
+- **Non-blocking:** Heartbeat handling doesn't block task execution
---
### Device Info Handling
-!!!info "Capability Discovery"
- Constellations can query device capabilities (screen resolution, installed apps, OS version) to make intelligent task routing decisions.
+Constellations can query device capabilities (screen resolution, installed apps, OS version) to make intelligent task routing decisions.
**Device Info Request Flow:**
@@ -736,8 +742,9 @@ async def handle_device_info_request(
## 🔌 Client Disconnection
-!!!danger "Critical Cleanup Process"
- When a client disconnects (gracefully or abruptly), the handler must clean up sessions, remove registry entries, and prevent resource leaks.
+**Critical Cleanup Process:**
+
+When a client disconnects (gracefully or abruptly), the handler must clean up sessions, remove registry entries, and prevent resource leaks.
### Disconnection Detection
@@ -797,61 +804,63 @@ graph TD
style J fill:#c8e6c9
```
-!!!example "Device Client Cleanup"
- ```python
- async def disconnect(self, client_id: str) -> None:
- """Handle client disconnection and cleanup."""
- client_info = self.client_manager.get_client_info(client_id)
-
- if client_info and client_info.client_type == ClientType.DEVICE:
- # Get all sessions running on this device
- session_ids = self.client_manager.get_device_sessions(client_id)
-
- if session_ids:
- self.logger.info(
- f"[WS] 📱 Device {client_id} disconnected, "
- f"cancelling {len(session_ids)} active session(s)"
- )
-
- # Cancel all sessions
- for session_id in session_ids:
- try:
- await self.session_manager.cancel_task(
- session_id,
- reason="device_disconnected" # Send callback to constellation
- )
- except Exception as e:
- self.logger.error(f"Error cancelling session {session_id}: {e}")
-
- # Clean up mappings
- self.client_manager.remove_device_sessions(client_id)
- ```
+**Device Client Cleanup:**
-!!!example "Constellation Client Cleanup"
- ```python
- if client_info and client_info.client_type == ClientType.CONSTELLATION:
- # Get all sessions initiated by constellation
- session_ids = self.client_manager.get_constellation_sessions(client_id)
+```python
+async def disconnect(self, client_id: str) -> None:
+ """Handle client disconnection and cleanup."""
+ client_info = self.client_manager.get_client_info(client_id)
+
+ if client_info and client_info.client_type == ClientType.DEVICE:
+ # Get all sessions running on this device
+ session_ids = self.client_manager.get_device_sessions(client_id)
if session_ids:
self.logger.info(
- f"[WS] 🌟 Constellation {client_id} disconnected, "
+ f"[WS] 📱 Device {client_id} disconnected, "
f"cancelling {len(session_ids)} active session(s)"
)
- # Cancel all associated sessions
+ # Cancel all sessions
for session_id in session_ids:
try:
await self.session_manager.cancel_task(
session_id,
- reason="constellation_disconnected" # Don't send callback
+ reason="device_disconnected" # Send callback to constellation
)
except Exception as e:
self.logger.error(f"Error cancelling session {session_id}: {e}")
# Clean up mappings
- self.client_manager.remove_constellation_sessions(client_id)
- ```
+ self.client_manager.remove_device_sessions(client_id)
+```
+
+**Constellation Client Cleanup:**
+
+```python
+if client_info and client_info.client_type == ClientType.CONSTELLATION:
+ # Get all sessions initiated by constellation
+ session_ids = self.client_manager.get_constellation_sessions(client_id)
+
+ if session_ids:
+ self.logger.info(
+ f"[WS] 🌟 Constellation {client_id} disconnected, "
+ f"cancelling {len(session_ids)} active session(s)"
+ )
+
+ # Cancel all associated sessions
+ for session_id in session_ids:
+ try:
+ await self.session_manager.cancel_task(
+ session_id,
+ reason="constellation_disconnected" # Don't send callback
+ )
+ except Exception as e:
+ self.logger.error(f"Error cancelling session {session_id}: {e}")
+
+ # Clean up mappings
+ self.client_manager.remove_constellation_sessions(client_id)
+```
**Final Registry Cleanup:**
@@ -868,20 +877,20 @@ self.logger.info(f"[WS] {client_id} disconnected")
| **Device Disconnects** | `device_disconnected` | Yes Constellation | Notify orchestrator to reassign task |
| **Constellation Disconnects** | `constellation_disconnected` | No | Requester is gone, no one to notify |
-!!!warning "Proper Cleanup is Critical"
- Failing to clean up disconnected clients leads to:
-
- - **Orphaned sessions** consuming server memory
- - **Stale WebSocket references** causing errors
- - **Registry pollution** with non-existent clients
- - **Resource leaks** (file handles, memory)
+**Proper Cleanup is Critical:**
+
+Failing to clean up disconnected clients leads to:
+
+- **Orphaned sessions** consuming server memory
+- **Stale WebSocket references** causing errors
+- **Registry pollution** with non-existent clients
+- **Resource leaks** (file handles, memory)
---
## 🚨 Error Handling
-!!!info "Graceful Degradation"
- The handler implements comprehensive error handling to prevent failures from cascading and breaking the entire server.
+The handler implements comprehensive error handling to prevent failures from cascading and breaking the entire server.
### Error Categories
@@ -905,8 +914,9 @@ async def handle_heartbeat(self, data: ClientMessage, websocket: WebSocket):
# Don't raise - connection is already closed
```
-!!!tip "Why Catch and Ignore?"
- When a connection is abruptly closed, attempts to send messages will raise `ConnectionError`. Since the client is already gone, there's no point in propagating the error—just log it and continue cleanup.
+**Why Catch and Ignore?**
+
+When a connection is abruptly closed, attempts to send messages will raise `ConnectionError`. Since the client is already gone, there's no point in propagating the error—just log it and continue cleanup.
### Message Parsing Errors
@@ -961,7 +971,7 @@ async def handle_task_request(self, data: ClientMessage, websocket: WebSocket):
### Validation Errors with Connection Closure
```python
-async def _validate_constellation_client(self, reg_info, websocket):
+async def _validate_constellation_client(self, reg_info: ClientMessage) -> None:
"""Validate constellation's target device."""
claimed_device_id = reg_info.target_id
@@ -970,30 +980,29 @@ async def _validate_constellation_client(self, reg_info, websocket):
self.logger.warning(f"Constellation registration failed: {error_msg}")
# Send error via AIP protocol
- await self._send_error_response(websocket, error_msg)
+ await self._send_error_response(error_msg)
# Close connection immediately
- await websocket.close()
+ await self.transport.close()
# Raise to prevent further processing
raise ValueError(error_msg)
```
-!!!danger "When to Close Connections"
- Close connections immediately for:
-
- - **Invalid registration** (missing client_id, wrong message type)
- - **Authorization failures** (target device not connected for constellations)
- - **Protocol violations** (sending TASK before REGISTER)
-
- For other errors, log and send error messages but **keep connection alive**.
+**When to Close Connections:**
+
+Close connections immediately for:
+
+- **Invalid registration** (missing client_id, wrong message type)
+- **Authorization failures** (target device not connected for constellations)
+- **Protocol violations** (sending TASK before REGISTER)
+
+For other errors, log and send error messages but **keep connection alive**.
---
## Best Practices
-!!!tip "Production-Ready WebSocket Handling"
-
### 1. Validate Early and Thoroughly
```python
@@ -1077,21 +1086,21 @@ async def handler(self, websocket: WebSocket):
# Loop continues immediately, doesn't wait for handler to finish
```
-!!!warning "Why `asyncio.create_task`?"
- Without `create_task`, the handler would process messages **sequentially**, blocking new messages while handling the current one. This is problematic for:
-
- - Long-running task dispatches
- - Command result processing
- - Device info queries
-
- Background tasks allow **concurrent message processing** while keeping the receive loop responsive.
+**Why `asyncio.create_task`?**
+
+Without `create_task`, the handler would process messages **sequentially**, blocking new messages while handling the current one. This is problematic for:
+
+- Long-running task dispatches
+- Command result processing
+- Device info queries
+
+Background tasks allow **concurrent message processing** while keeping the receive loop responsive.
---
## 📚 Related Documentation
-!!!info "Learn More"
- Explore related components to understand the full server architecture:
+Explore related components to understand the full server architecture:
| Component | Purpose | Link |
|-----------|---------|------|
@@ -1106,21 +1115,21 @@ async def handler(self, websocket: WebSocket):
## 🎓 What You Learned
-!!!success "Key Takeaways"
- After reading this guide, you should understand:
-
- - **AIP Protocol Integration** - Four specialized protocols handle different communication aspects
- - **Registration Flow** - Validation Registration Confirmation
- - **Message Routing** - Central dispatcher routes messages to specialized handlers
- - **Dual Client Support** - Devices (self-execution) vs. Constellations (orchestration)
- - **Background Task Dispatch** - Immediate ACK + async execution
- - **Command Result Handling** - Unblocks command dispatcher waiting for device responses
- - **Heartbeat Monitoring** - Lightweight connection health checks
- - **Disconnection Cleanup** - Context-aware session cancellation and registry cleanup
- - **Error Handling** - Graceful degradation without cascading failures
-
-!!!tip "Next Steps"
- - Explore [Session Manager](./session_manager.md) to understand background execution internals
- - Learn about [Client Connection Manager](./client_connection_manager.md) for client registry management
- - Review [AIP Protocol Documentation](../aip/overview.md) for message format specifications
+After reading this guide, you should understand:
+
+- **AIP Protocol Integration** - Four specialized protocols handle different communication aspects
+- **Registration Flow** - Validation → Registration → Confirmation
+- **Message Routing** - Central dispatcher routes messages to specialized handlers
+- **Dual Client Support** - Devices (self-execution) vs. Constellations (orchestration)
+- **Background Task Dispatch** - Immediate ACK + async execution
+- **Command Result Handling** - Unblocks command dispatcher waiting for device responses
+- **Heartbeat Monitoring** - Lightweight connection health checks
+- **Disconnection Cleanup** - Context-aware session cancellation and registry cleanup
+- **Error Handling** - Graceful degradation without cascading failures
+
+**Next Steps:**
+
+- Explore [Session Manager](./session_manager.md) to understand background execution internals
+- Learn about [Client Connection Manager](./client_connection_manager.md) for client registry management
+- Review [AIP Protocol Documentation](../aip/overview.md) for message format specifications
diff --git a/documents/docs/ufo2/lts_support_policy.md b/documents/docs/ufo2/lts_support_policy.md
deleted file mode 100644
index 97271e7bc..000000000
--- a/documents/docs/ufo2/lts_support_policy.md
+++ /dev/null
@@ -1,24 +0,0 @@
-# Lts Support Policy
-
-!!!warning "Documentation In Progress"
- This page is a placeholder. Content will be added soon.
-
-## Overview
-
-TODO: Add overview content for Lts Support Policy.
-
-## Key Concepts
-
-TODO: Add key concepts.
-
-## Usage
-
-TODO: Add usage instructions.
-
-## Examples
-
-TODO: Add examples.
-
-## Related Documentation
-
-TODO: Add links to related pages.
diff --git a/documents/mkdocs.yml b/documents/mkdocs.yml
index 51b274000..2649a510a 100644
--- a/documents/mkdocs.yml
+++ b/documents/mkdocs.yml
@@ -11,7 +11,6 @@ nav:
- Quick Start (Linux Agent): getting_started/quick_start_linux.md
- Migration UFO² → UFO³: getting_started/migration_ufo2_to_galaxy.md
- More Guidance: getting_started/more_guidance.md
- - FAQ: getting_started/faq.md
- Configuration & Setup:
- Configuration System:
- Overview: configuration/system/overview.md
@@ -132,7 +131,6 @@ nav:
- Execution: ufo2/dataflow/execution.md
- Windows App Environment: ufo2/dataflow/windows_app_env.md
- Result: ufo2/dataflow/result.md
- - LTS Support Policy: ufo2/lts_support_policy.md
- Linux Agent:
- Overview: linux/overview.md
- Using as Galaxy Device: linux/as_galaxy_device.md
diff --git a/galaxy/agents/constellation_agent.py b/galaxy/agents/constellation_agent.py
index d935fd858..b1c4c4828 100644
--- a/galaxy/agents/constellation_agent.py
+++ b/galaxy/agents/constellation_agent.py
@@ -551,6 +551,17 @@ def print_response(
# Publish event asynchronously (non-blocking)
asyncio.create_task(get_event_bus().publish_event(event))
+ @property
+ def default_state(self):
+ """
+ Get the default state of the Constellation Agent.
+
+ :return: The default StartConstellationAgentState
+ """
+ from .constellation_agent_states import StartConstellationAgentState
+
+ return StartConstellationAgentState()
+
@property
def status_manager(self):
"""Get the status manager."""
diff --git a/galaxy/galaxy.py b/galaxy/galaxy.py
index 1093fcff5..d8dbc1ab4 100644
--- a/galaxy/galaxy.py
+++ b/galaxy/galaxy.py
@@ -345,6 +345,19 @@ def find_free_port(start_port=8000, max_attempts=10):
)
return
+ # Write port info to frontend config file for development mode
+ frontend_dir = Path(__file__).parent / "webui" / "frontend"
+ if frontend_dir.exists():
+ env_file = frontend_dir / ".env.development.local"
+ try:
+ with open(env_file, "w", encoding="utf-8") as f:
+ f.write(f"# Auto-generated by Galaxy backend\n")
+ f.write(f"# This file is updated each time the backend starts\n")
+ f.write(f"VITE_BACKEND_URL=http://localhost:{port}\n")
+ client.display.print_info(f"📝 Updated frontend config: {env_file}")
+ except Exception as e:
+ client.display.print_warning(f"⚠️ Could not write frontend config: {e}")
+
# Display banner
client.display.print_info("🌌 Galaxy WebUI Starting...")
client.display.print_info(f"📡 Server: http://localhost:{port}")
diff --git a/galaxy/session/galaxy_session.py b/galaxy/session/galaxy_session.py
index f5d79a145..48484a4bb 100644
--- a/galaxy/session/galaxy_session.py
+++ b/galaxy/session/galaxy_session.py
@@ -485,8 +485,14 @@ def reset(self) -> None:
# Save device info before clearing (should not be cleared on reset)
device_info = self._context.get(ContextNames.DEVICE_INFO)
- # Reset agent state
- self._agent.set_state(self._agent.default_state)
+ # Reset agent state to default if available
+ default_state = self._agent.default_state
+ if default_state is not None:
+ self._agent.set_state(default_state)
+ else:
+ self.logger.warning(
+ f"Agent {type(self._agent).__name__} has no default_state defined, skipping state reset"
+ )
# Clear rounds and results
self._rounds.clear()
diff --git a/galaxy/webui/dependencies.py b/galaxy/webui/dependencies.py
new file mode 100644
index 000000000..731da9821
--- /dev/null
+++ b/galaxy/webui/dependencies.py
@@ -0,0 +1,147 @@
+# Copyright (c) Microsoft Corporation.
+# Licensed under the MIT License.
+
+"""
+Dependency management for Galaxy Web UI.
+
+This module manages global state and provides dependency injection
+for FastAPI endpoints and WebSocket handlers.
+"""
+
+import logging
+from typing import TYPE_CHECKING, Optional
+
+from galaxy.webui.websocket_observer import WebSocketObserver
+
+if TYPE_CHECKING:
+ from galaxy.galaxy_client import GalaxyClient
+ from galaxy.session.galaxy_session import GalaxySession
+
+
+class AppState:
+ """
+ Application state container.
+
+ Manages global state for the Web UI server including:
+ - WebSocket observer for event broadcasting
+ - Galaxy session and client instances
+ - Request counter for tracking user requests
+
+ This class provides a centralized way to manage shared state
+ across the application instead of using global variables.
+ """
+
+ def __init__(self) -> None:
+ """Initialize the application state with default values."""
+ self.logger: logging.Logger = logging.getLogger(__name__)
+
+ # WebSocket observer for broadcasting events to clients
+ self._websocket_observer: Optional[WebSocketObserver] = None
+
+ # Galaxy session and client instances
+ self._galaxy_session: Optional["GalaxySession"] = None
+ self._galaxy_client: Optional["GalaxyClient"] = None
+
+ # Counter for generating unique task names in Web UI mode
+ self._request_counter: int = 0
+
+ @property
+ def websocket_observer(self) -> Optional[WebSocketObserver]:
+ """
+ Get the WebSocket observer instance.
+
+ :return: WebSocket observer or None if not initialized
+ """
+ return self._websocket_observer
+
+ @websocket_observer.setter
+ def websocket_observer(self, observer: WebSocketObserver) -> None:
+ """
+ Set the WebSocket observer instance.
+
+ :param observer: WebSocket observer to use for event broadcasting
+ """
+ self._websocket_observer = observer
+ self.logger.info(f"WebSocket observer set: {observer}")
+
+ @property
+ def galaxy_session(self) -> Optional["GalaxySession"]:
+ """
+ Get the current Galaxy session.
+
+ :return: Galaxy session or None if not initialized
+ """
+ return self._galaxy_session
+
+ @galaxy_session.setter
+ def galaxy_session(self, session: "GalaxySession") -> None:
+ """
+ Set the Galaxy session.
+
+ :param session: Galaxy session instance
+ """
+ self._galaxy_session = session
+ self.logger.info("Galaxy session set")
+
+ @property
+ def galaxy_client(self) -> Optional["GalaxyClient"]:
+ """
+ Get the current Galaxy client.
+
+ :return: Galaxy client or None if not initialized
+ """
+ return self._galaxy_client
+
+ @galaxy_client.setter
+ def galaxy_client(self, client: "GalaxyClient") -> None:
+ """
+ Set the Galaxy client.
+
+ :param client: Galaxy client instance
+ """
+ self._galaxy_client = client
+ self.logger.info("Galaxy client set")
+
+ @property
+ def request_counter(self) -> int:
+ """
+ Get the current request counter value.
+
+ :return: Current request counter
+ """
+ return self._request_counter
+
+ def increment_request_counter(self) -> int:
+ """
+ Increment and return the request counter.
+
+ :return: New counter value after increment
+ """
+ self._request_counter += 1
+ return self._request_counter
+
+ def reset_request_counter(self) -> None:
+ """
+ Reset the request counter to zero.
+
+ Called when session is reset or task is stopped.
+ """
+ self._request_counter = 0
+ self.logger.info("Request counter reset to 0")
+
+
+# Global application state instance
+# This is initialized once and shared across the application
+app_state = AppState()
+
+
+def get_app_state() -> AppState:
+ """
+ Get the application state instance.
+
+ This function can be used as a FastAPI dependency to inject
+ the application state into route handlers.
+
+ :return: Application state instance
+ """
+ return app_state
diff --git a/galaxy/webui/frontend/.env.example b/galaxy/webui/frontend/.env.example
new file mode 100644
index 000000000..425ecf763
--- /dev/null
+++ b/galaxy/webui/frontend/.env.example
@@ -0,0 +1,4 @@
+# Backend API URL for development
+# If your Galaxy backend is running on a different port, update this
+# Example: VITE_BACKEND_URL=http://localhost:8001
+VITE_BACKEND_URL=http://localhost:8000
diff --git a/galaxy/webui/frontend/README.md b/galaxy/webui/frontend/README.md
new file mode 100644
index 000000000..ab82737fc
--- /dev/null
+++ b/galaxy/webui/frontend/README.md
@@ -0,0 +1,100 @@
+# Galaxy WebUI Frontend
+
+React-based frontend for Galaxy Framework with real-time WebSocket updates.
+
+## Development Mode
+
+### Prerequisites
+- Node.js 16+ and npm
+- Galaxy backend running
+
+### Quick Start
+
+1. **Start the Galaxy backend** (in a separate terminal):
+ ```bash
+ cd UFO
+ python -m galaxy --webui
+ ```
+
+ The backend will:
+ - Find an available port (8000-8009)
+ - Auto-generate `.env.development.local` with the backend URL
+ - Display the backend URL (e.g., `http://localhost:8001`)
+
+2. **Start the frontend development server**:
+ ```bash
+ cd galaxy/webui/frontend
+ npm install # First time only
+ npm run dev
+ ```
+
+ The frontend will:
+ - Read the backend URL from `.env.development.local`
+ - Start on port 3000 (or 3001 if 3000 is busy)
+ - Connect to the backend automatically
+
+3. **Open your browser**:
+ - Frontend: `http://localhost:3000` (or 3001)
+ - The frontend will connect to backend automatically
+
+### Manual Port Configuration
+
+If you need to manually specify the backend port:
+
+1. Copy `.env.example` to `.env.development.local`:
+ ```bash
+ cp .env.example .env.development.local
+ ```
+
+2. Edit `.env.development.local`:
+ ```
+ VITE_BACKEND_URL=http://localhost:8001
+ ```
+
+3. Restart the frontend dev server
+
+## Production Mode
+
+In production, the backend serves the built frontend automatically:
+
+```bash
+# Build the frontend
+cd galaxy/webui/frontend
+npm run build
+
+# Start Galaxy with WebUI
+cd ../../..
+python -m galaxy --webui
+```
+
+Then open `http://localhost:8000` (or whatever port the backend chooses).
+
+## Architecture
+
+- **Development**: Frontend (Vite) runs separately, connects to backend via direct HTTP/WebSocket
+- **Production**: Backend (FastAPI) serves built frontend static files
+
+## Troubleshooting
+
+### Error: "Unexpected token '<', ":not([hidden])~:not([hidden]){--tw-space-y-reverse: 0;margin-top:calc(.25rem * calc(1 - var(--tw-space-y-reverse)));margin-bottom:calc(.25rem * var(--tw-space-y-reverse))}.space-y-2>:not([hidden])~:not([hidden]){--tw-space-y-reverse: 0;margin-top:calc(.5rem * calc(1 - var(--tw-space-y-reverse)));margin-bottom:calc(.5rem * var(--tw-space-y-reverse))}.space-y-3>:not([hidden])~:not([hidden]){--tw-space-y-reverse: 0;margin-top:calc(.75rem * calc(1 - var(--tw-space-y-reverse)));margin-bottom:calc(.75rem * var(--tw-space-y-reverse))}.space-y-4>:not([hidden])~:not([hidden]){--tw-space-y-reverse: 0;margin-top:calc(1rem * calc(1 - var(--tw-space-y-reverse)));margin-bottom:calc(1rem * var(--tw-space-y-reverse))}.overflow-auto{overflow:auto}.overflow-hidden{overflow:hidden}.overflow-y-auto{overflow-y:auto}.truncate{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.whitespace-pre-wrap{white-space:pre-wrap}.break-words{overflow-wrap:break-word}.rounded-2xl{border-radius:1rem}.rounded-3xl{border-radius:1.5rem}.rounded-\[24px\]{border-radius:24px}.rounded-\[28px\]{border-radius:28px}.rounded-\[30px\]{border-radius:30px}.rounded-full{border-radius:9999px}.rounded-lg{border-radius:.5rem}.rounded-xl{border-radius:.75rem}.rounded-bl-xl{border-bottom-left-radius:.75rem}.rounded-br-xl{border-bottom-right-radius:.75rem}.border{border-width:1px}.border-2{border-width:2px}.border-b{border-bottom-width:1px}.border-t{border-top-width:1px}.border-dashed{border-style:dashed}.border-\[rgba\(10\,186\,181\,0\.35\)\]{border-color:#0abab559}.border-\[rgba\(10\,186\,181\,0\.4\)\]{border-color:#0abab566}.border-amber-400\/30{border-color:#fbbf244d}.border-amber-400\/40{border-color:#fbbf2466}.border-cyan-400\/30{border-color:#22d3ee4d}.border-cyan-400\/40{border-color:#22d3ee66}.border-cyan-400\/50{border-color:#22d3ee80}.border-cyan-500\/30{border-color:#06b6d44d}.border-emerald-400\/20{border-color:#34d39933}.border-emerald-400\/30{border-color:#34d3994d}.border-emerald-400\/40{border-color:#34d39966}.border-emerald-500\/50{border-color:#10b98180}.border-galaxy-blue\/50{border-color:#0f7bff80}.border-galaxy-blue\/60{border-color:#0f7bff99}.border-indigo-400\/30{border-color:#818cf84d}.border-purple-400\/20{border-color:#c084fc33}.border-purple-400\/30{border-color:#c084fc4d}.border-rose-400\/20{border-color:#fb718533}.border-rose-400\/30{border-color:#fb71854d}.border-rose-400\/40{border-color:#fb718566}.border-rose-500\/50{border-color:#f43f5e80}.border-rose-900\/40{border-color:#88133766}.border-slate-400\/30{border-color:#94a3b84d}.border-white\/10{border-color:#ffffff1a}.border-white\/20{border-color:#fff3}.border-white\/5{border-color:#ffffff0d}.border-yellow-400\/30{border-color:#facc154d}.bg-\[\#0a0e1a\]{--tw-bg-opacity: 1;background-color:rgb(10 14 26 / var(--tw-bg-opacity, 1))}.bg-amber-400{--tw-bg-opacity: 1;background-color:rgb(251 191 36 / var(--tw-bg-opacity, 1))}.bg-amber-500\/20{background-color:#f59e0b33}.bg-black\/20{background-color:#0003}.bg-black\/30{background-color:#0000004d}.bg-black\/40{background-color:#0006}.bg-black\/60{background-color:#0009}.bg-blue-400{--tw-bg-opacity: 1;background-color:rgb(96 165 250 / var(--tw-bg-opacity, 1))}.bg-cyan-400{--tw-bg-opacity: 1;background-color:rgb(34 211 238 / var(--tw-bg-opacity, 1))}.bg-cyan-500\/20{background-color:#06b6d433}.bg-emerald-400{--tw-bg-opacity: 1;background-color:rgb(52 211 153 / var(--tw-bg-opacity, 1))}.bg-emerald-500\/20{background-color:#10b98133}.bg-emerald-950\/30{background-color:#022c224d}.bg-galaxy-blue{--tw-bg-opacity: 1;background-color:rgb(15 123 255 / var(--tw-bg-opacity, 1))}.bg-galaxy-blue\/15{background-color:#0f7bff26}.bg-indigo-500\/20{background-color:#6366f133}.bg-purple-300\/80{background-color:#d8b4fecc}.bg-rose-400{--tw-bg-opacity: 1;background-color:rgb(251 113 133 / var(--tw-bg-opacity, 1))}.bg-rose-500{--tw-bg-opacity: 1;background-color:rgb(244 63 94 / var(--tw-bg-opacity, 1))}.bg-rose-500\/10{background-color:#f43f5e1a}.bg-rose-500\/20{background-color:#f43f5e33}.bg-rose-950\/30{background-color:#4c05194d}.bg-slate-500{--tw-bg-opacity: 1;background-color:rgb(100 116 139 / var(--tw-bg-opacity, 1))}.bg-slate-500\/20{background-color:#64748b33}.bg-slate-500\/30{background-color:#64748b4d}.bg-slate-600{--tw-bg-opacity: 1;background-color:rgb(71 85 105 / var(--tw-bg-opacity, 1))}.bg-transparent{background-color:transparent}.bg-white\/10{background-color:#ffffff1a}.bg-white\/5{background-color:#ffffff0d}.bg-yellow-500\/10{background-color:#eab3081a}.bg-gradient-to-br{background-image:linear-gradient(to bottom right,var(--tw-gradient-stops))}.bg-gradient-to-r{background-image:linear-gradient(to right,var(--tw-gradient-stops))}.bg-starfield{background-image:radial-gradient(circle at 10% 20%,rgba(33,240,255,.18),transparent 45%),radial-gradient(circle at 80% 10%,rgba(147,51,234,.22),transparent 50%),radial-gradient(circle at 50% 80%,rgba(14,116,144,.3),transparent 55%)}.from-\[rgba\(10\,186\,181\,0\.12\)\]{--tw-gradient-from: rgba(10,186,181,.12) var(--tw-gradient-from-position);--tw-gradient-to: rgba(10, 186, 181, 0) var(--tw-gradient-to-position);--tw-gradient-stops: var(--tw-gradient-from), var(--tw-gradient-to)}.from-\[rgba\(10\,186\,181\,0\.15\)\]{--tw-gradient-from: rgba(10,186,181,.15) var(--tw-gradient-from-position);--tw-gradient-to: rgba(10, 186, 181, 0) var(--tw-gradient-to-position);--tw-gradient-stops: var(--tw-gradient-from), var(--tw-gradient-to)}.from-\[rgba\(11\,24\,44\,0\.82\)\]{--tw-gradient-from: rgba(11,24,44,.82) var(--tw-gradient-from-position);--tw-gradient-to: rgba(11, 24, 44, 0) var(--tw-gradient-to-position);--tw-gradient-stops: var(--tw-gradient-from), var(--tw-gradient-to)}.from-\[rgba\(11\,30\,45\,0\.85\)\]{--tw-gradient-from: rgba(11,30,45,.85) var(--tw-gradient-from-position);--tw-gradient-to: rgba(11, 30, 45, 0) var(--tw-gradient-to-position);--tw-gradient-stops: var(--tw-gradient-from), var(--tw-gradient-to)}.from-\[rgba\(11\,30\,45\,0\.88\)\]{--tw-gradient-from: rgba(11,30,45,.88) var(--tw-gradient-from-position);--tw-gradient-to: rgba(11, 30, 45, 0) var(--tw-gradient-to-position);--tw-gradient-stops: var(--tw-gradient-from), var(--tw-gradient-to)}.from-\[rgba\(25\,40\,60\,0\.75\)\]{--tw-gradient-from: rgba(25,40,60,.75) var(--tw-gradient-from-position);--tw-gradient-to: rgba(25, 40, 60, 0) var(--tw-gradient-to-position);--tw-gradient-stops: var(--tw-gradient-from), var(--tw-gradient-to)}.from-\[rgba\(6\,182\,212\,0\.2\)\]{--tw-gradient-from: rgba(6,182,212,.2) var(--tw-gradient-from-position);--tw-gradient-to: rgba(6, 182, 212, 0) var(--tw-gradient-to-position);--tw-gradient-stops: var(--tw-gradient-from), var(--tw-gradient-to)}.from-\[rgba\(6\,182\,212\,0\.85\)\]{--tw-gradient-from: rgba(6,182,212,.85) var(--tw-gradient-from-position);--tw-gradient-to: rgba(6, 182, 212, 0) var(--tw-gradient-to-position);--tw-gradient-stops: var(--tw-gradient-from), var(--tw-gradient-to)}.from-\[rgba\(80\,20\,30\,0\.75\)\]{--tw-gradient-from: rgba(80,20,30,.75) var(--tw-gradient-from-position);--tw-gradient-to: rgba(80, 20, 30, 0) var(--tw-gradient-to-position);--tw-gradient-stops: var(--tw-gradient-from), var(--tw-gradient-to)}.from-black\/30{--tw-gradient-from: rgb(0 0 0 / .3) var(--tw-gradient-from-position);--tw-gradient-to: rgb(0 0 0 / 0) var(--tw-gradient-to-position);--tw-gradient-stops: var(--tw-gradient-from), var(--tw-gradient-to)}.from-black\/50{--tw-gradient-from: rgb(0 0 0 / .5) var(--tw-gradient-from-position);--tw-gradient-to: rgb(0 0 0 / 0) var(--tw-gradient-to-position);--tw-gradient-stops: var(--tw-gradient-from), var(--tw-gradient-to)}.from-cyan-200{--tw-gradient-from: #a5f3fc var(--tw-gradient-from-position);--tw-gradient-to: rgb(165 243 252 / 0) var(--tw-gradient-to-position);--tw-gradient-stops: var(--tw-gradient-from), var(--tw-gradient-to)}.from-cyan-300{--tw-gradient-from: #67e8f9 var(--tw-gradient-from-position);--tw-gradient-to: rgb(103 232 249 / 0) var(--tw-gradient-to-position);--tw-gradient-stops: var(--tw-gradient-from), var(--tw-gradient-to)}.from-cyan-500\/20{--tw-gradient-from: rgb(6 182 212 / .2) var(--tw-gradient-from-position);--tw-gradient-to: rgb(6 182 212 / 0) var(--tw-gradient-to-position);--tw-gradient-stops: var(--tw-gradient-from), var(--tw-gradient-to)}.from-cyan-950\/30{--tw-gradient-from: rgb(8 51 68 / .3) var(--tw-gradient-from-position);--tw-gradient-to: rgb(8 51 68 / 0) var(--tw-gradient-to-position);--tw-gradient-stops: var(--tw-gradient-from), var(--tw-gradient-to)}.from-emerald-500\/10{--tw-gradient-from: rgb(16 185 129 / .1) var(--tw-gradient-from-position);--tw-gradient-to: rgb(16 185 129 / 0) var(--tw-gradient-to-position);--tw-gradient-stops: var(--tw-gradient-from), var(--tw-gradient-to)}.from-emerald-500\/15{--tw-gradient-from: rgb(16 185 129 / .15) var(--tw-gradient-from-position);--tw-gradient-to: rgb(16 185 129 / 0) var(--tw-gradient-to-position);--tw-gradient-stops: var(--tw-gradient-from), var(--tw-gradient-to)}.from-emerald-500\/35{--tw-gradient-from: rgb(16 185 129 / .35) var(--tw-gradient-from-position);--tw-gradient-to: rgb(16 185 129 / 0) var(--tw-gradient-to-position);--tw-gradient-stops: var(--tw-gradient-from), var(--tw-gradient-to)}.from-galaxy-blue{--tw-gradient-from: #0F7BFF var(--tw-gradient-from-position);--tw-gradient-to: rgb(15 123 255 / 0) var(--tw-gradient-to-position);--tw-gradient-stops: var(--tw-gradient-from), var(--tw-gradient-to)}.from-galaxy-blue\/25{--tw-gradient-from: rgb(15 123 255 / .25) var(--tw-gradient-from-position);--tw-gradient-to: rgb(15 123 255 / 0) var(--tw-gradient-to-position);--tw-gradient-stops: var(--tw-gradient-from), var(--tw-gradient-to)}.from-galaxy-blue\/40{--tw-gradient-from: rgb(15 123 255 / .4) var(--tw-gradient-from-position);--tw-gradient-to: rgb(15 123 255 / 0) var(--tw-gradient-to-position);--tw-gradient-stops: var(--tw-gradient-from), var(--tw-gradient-to)}.from-purple-500\/10{--tw-gradient-from: rgb(168 85 247 / .1) var(--tw-gradient-from-position);--tw-gradient-to: rgb(168 85 247 / 0) var(--tw-gradient-to-position);--tw-gradient-stops: var(--tw-gradient-from), var(--tw-gradient-to)}.from-purple-500\/20{--tw-gradient-from: rgb(168 85 247 / .2) var(--tw-gradient-from-position);--tw-gradient-to: rgb(168 85 247 / 0) var(--tw-gradient-to-position);--tw-gradient-stops: var(--tw-gradient-from), var(--tw-gradient-to)}.from-purple-950\/20{--tw-gradient-from: rgb(59 7 100 / .2) var(--tw-gradient-from-position);--tw-gradient-to: rgb(59 7 100 / 0) var(--tw-gradient-to-position);--tw-gradient-stops: var(--tw-gradient-from), var(--tw-gradient-to)}.from-rose-500\/15{--tw-gradient-from: rgb(244 63 94 / .15) var(--tw-gradient-from-position);--tw-gradient-to: rgb(244 63 94 / 0) var(--tw-gradient-to-position);--tw-gradient-stops: var(--tw-gradient-from), var(--tw-gradient-to)}.from-rose-500\/35{--tw-gradient-from: rgb(244 63 94 / .35) var(--tw-gradient-from-position);--tw-gradient-to: rgb(244 63 94 / 0) var(--tw-gradient-to-position);--tw-gradient-stops: var(--tw-gradient-from), var(--tw-gradient-to)}.via-\[rgba\(100\,25\,35\,0\.70\)\]{--tw-gradient-to: rgba(100, 25, 35, 0) var(--tw-gradient-to-position);--tw-gradient-stops: var(--tw-gradient-from), rgba(100,25,35,.7) var(--tw-gradient-via-position), var(--tw-gradient-to)}.via-\[rgba\(12\,50\,65\,0\.8\)\]{--tw-gradient-to: rgba(12, 50, 65, 0) var(--tw-gradient-to-position);--tw-gradient-stops: var(--tw-gradient-from), rgba(12,50,65,.8) var(--tw-gradient-via-position), var(--tw-gradient-to)}.via-\[rgba\(147\,51\,234\,0\.80\)\]{--tw-gradient-to: rgba(147, 51, 234, 0) var(--tw-gradient-to-position);--tw-gradient-stops: var(--tw-gradient-from), rgba(147,51,234,.8) var(--tw-gradient-via-position), var(--tw-gradient-to)}.via-\[rgba\(15\,123\,255\,0\.15\)\]{--tw-gradient-to: rgba(15, 123, 255, 0) var(--tw-gradient-to-position);--tw-gradient-stops: var(--tw-gradient-from), rgba(15,123,255,.15) var(--tw-gradient-via-position), var(--tw-gradient-to)}.via-\[rgba\(20\,35\,52\,0\.7\)\]{--tw-gradient-to: rgba(20, 35, 52, 0) var(--tw-gradient-to-position);--tw-gradient-stops: var(--tw-gradient-from), rgba(20,35,52,.7) var(--tw-gradient-via-position), var(--tw-gradient-to)}.via-\[rgba\(8\,20\,35\,0\.82\)\]{--tw-gradient-to: rgba(8, 20, 35, 0) var(--tw-gradient-to-position);--tw-gradient-stops: var(--tw-gradient-from), rgba(8,20,35,.82) var(--tw-gradient-via-position), var(--tw-gradient-to)}.via-\[rgba\(8\,20\,35\,0\.85\)\]{--tw-gradient-to: rgba(8, 20, 35, 0) var(--tw-gradient-to-position);--tw-gradient-stops: var(--tw-gradient-from), rgba(8,20,35,.85) var(--tw-gradient-via-position), var(--tw-gradient-to)}.via-galaxy-purple\/25{--tw-gradient-to: rgb(123 44 191 / 0) var(--tw-gradient-to-position);--tw-gradient-stops: var(--tw-gradient-from), rgb(123 44 191 / .25) var(--tw-gradient-via-position), var(--tw-gradient-to)}.via-purple-200{--tw-gradient-to: rgb(233 213 255 / 0) var(--tw-gradient-to-position);--tw-gradient-stops: var(--tw-gradient-from), #e9d5ff var(--tw-gradient-via-position), var(--tw-gradient-to)}.via-white{--tw-gradient-to: rgb(255 255 255 / 0) var(--tw-gradient-to-position);--tw-gradient-stops: var(--tw-gradient-from), #fff var(--tw-gradient-via-position), var(--tw-gradient-to)}.to-\[rgba\(11\,30\,45\,0\.85\)\]{--tw-gradient-to: rgba(11,30,45,.85) var(--tw-gradient-to-position)}.to-\[rgba\(15\,28\,45\,0\.75\)\]{--tw-gradient-to: rgba(15,28,45,.75) var(--tw-gradient-to-position)}.to-\[rgba\(15\,28\,45\,0\.8\)\]{--tw-gradient-to: rgba(15,28,45,.8) var(--tw-gradient-to-position)}.to-\[rgba\(236\,72\,153\,0\.85\)\]{--tw-gradient-to: rgba(236,72,153,.85) var(--tw-gradient-to-position)}.to-\[rgba\(6\,15\,28\,0\.85\)\]{--tw-gradient-to: rgba(6,15,28,.85) var(--tw-gradient-to-position)}.to-\[rgba\(6\,15\,28\,0\.88\)\]{--tw-gradient-to: rgba(6,15,28,.88) var(--tw-gradient-to-position)}.to-\[rgba\(6\,182\,212\,0\.15\)\]{--tw-gradient-to: rgba(6,182,212,.15) var(--tw-gradient-to-position)}.to-\[rgba\(8\,15\,28\,0\.75\)\]{--tw-gradient-to: rgba(8,15,28,.75) var(--tw-gradient-to-position)}.to-\[rgba\(8\,15\,28\,0\.85\)\]{--tw-gradient-to: rgba(8,15,28,.85) var(--tw-gradient-to-position)}.to-\[rgba\(80\,20\,30\,0\.75\)\]{--tw-gradient-to: rgba(80,20,30,.75) var(--tw-gradient-to-position)}.to-black\/20{--tw-gradient-to: rgb(0 0 0 / .2) var(--tw-gradient-to-position)}.to-black\/30{--tw-gradient-to: rgb(0 0 0 / .3) var(--tw-gradient-to-position)}.to-blue-500\/10{--tw-gradient-to: rgb(59 130 246 / .1) var(--tw-gradient-to-position)}.to-blue-500\/20{--tw-gradient-to: rgb(59 130 246 / .2) var(--tw-gradient-to-position)}.to-blue-950\/20{--tw-gradient-to: rgb(23 37 84 / .2) var(--tw-gradient-to-position)}.to-cyan-200{--tw-gradient-to: #a5f3fc var(--tw-gradient-to-position)}.to-cyan-500\/10{--tw-gradient-to: rgb(6 182 212 / .1) var(--tw-gradient-to-position)}.to-cyan-500\/15{--tw-gradient-to: rgb(6 182 212 / .15) var(--tw-gradient-to-position)}.to-emerald-600\/10{--tw-gradient-to: rgb(5 150 105 / .1) var(--tw-gradient-to-position)}.to-emerald-600\/25{--tw-gradient-to: rgb(5 150 105 / .25) var(--tw-gradient-to-position)}.to-galaxy-blue\/15{--tw-gradient-to: rgb(15 123 255 / .15) var(--tw-gradient-to-position)}.to-galaxy-purple{--tw-gradient-to: #7b2cbf var(--tw-gradient-to-position)}.to-galaxy-purple\/40{--tw-gradient-to: rgb(123 44 191 / .4) var(--tw-gradient-to-position)}.to-indigo-950\/15{--tw-gradient-to: rgb(30 27 75 / .15) var(--tw-gradient-to-position)}.to-pink-500\/20{--tw-gradient-to: rgb(236 72 153 / .2) var(--tw-gradient-to-position)}.to-purple-300{--tw-gradient-to: #d8b4fe var(--tw-gradient-to-position)}.to-purple-500\/20{--tw-gradient-to: rgb(168 85 247 / .2) var(--tw-gradient-to-position)}.to-rose-600\/10{--tw-gradient-to: rgb(225 29 72 / .1) var(--tw-gradient-to-position)}.to-rose-600\/25{--tw-gradient-to: rgb(225 29 72 / .25) var(--tw-gradient-to-position)}.to-white\/5{--tw-gradient-to: rgb(255 255 255 / .05) var(--tw-gradient-to-position)}.bg-clip-text{-webkit-background-clip:text;background-clip:text}.p-1{padding:.25rem}.p-1\.5{padding:.375rem}.p-2{padding:.5rem}.p-3{padding:.75rem}.p-4{padding:1rem}.p-5{padding:1.25rem}.p-6{padding:1.5rem}.p-8{padding:2rem}.px-2{padding-left:.5rem;padding-right:.5rem}.px-2\.5{padding-left:.625rem;padding-right:.625rem}.px-3{padding-left:.75rem;padding-right:.75rem}.px-4{padding-left:1rem;padding-right:1rem}.px-5{padding-left:1.25rem;padding-right:1.25rem}.px-6{padding-left:1.5rem;padding-right:1.5rem}.py-0\.5{padding-top:.125rem;padding-bottom:.125rem}.py-1{padding-top:.25rem;padding-bottom:.25rem}.py-1\.5{padding-top:.375rem;padding-bottom:.375rem}.py-2{padding-top:.5rem;padding-bottom:.5rem}.py-2\.5{padding-top:.625rem;padding-bottom:.625rem}.py-3{padding-top:.75rem;padding-bottom:.75rem}.py-4{padding-top:1rem;padding-bottom:1rem}.py-5{padding-top:1.25rem;padding-bottom:1.25rem}.pb-3{padding-bottom:.75rem}.pb-6{padding-bottom:1.5rem}.pr-1{padding-right:.25rem}.pr-6{padding-right:1.5rem}.pt-1{padding-top:.25rem}.pt-2{padding-top:.5rem}.text-left{text-align:left}.text-center{text-align:center}.font-heading{font-family:IBM Plex Sans,Inter,system-ui,sans-serif}.font-mono{font-family:JetBrains Mono,Menlo,monospace}.text-2xl{font-size:1.5rem;line-height:2rem}.text-3xl{font-size:1.875rem;line-height:2.25rem}.text-\[10px\]{font-size:10px}.text-\[11px\]{font-size:11px}.text-\[12px\]{font-size:12px}.text-\[9px\]{font-size:9px}.text-base{font-size:1rem;line-height:1.5rem}.text-lg{font-size:1.125rem;line-height:1.75rem}.text-sm{font-size:.875rem;line-height:1.25rem}.text-xl{font-size:1.25rem;line-height:1.75rem}.text-xs{font-size:.75rem;line-height:1rem}.font-bold{font-weight:700}.font-medium{font-weight:500}.font-normal{font-weight:400}.font-semibold{font-weight:600}.uppercase{text-transform:uppercase}.leading-relaxed{line-height:1.625}.leading-snug{line-height:1.375}.leading-tight{line-height:1.25}.tracking-\[0\.15em\]{letter-spacing:.15em}.tracking-\[0\.18em\]{letter-spacing:.18em}.tracking-\[0\.25em\]{letter-spacing:.25em}.tracking-\[0\.2em\]{letter-spacing:.2em}.tracking-tight{letter-spacing:-.025em}.tracking-tighter{letter-spacing:-.05em}.tracking-wide{letter-spacing:.025em}.tracking-wider{letter-spacing:.05em}.text-\[rgb\(10\,186\,181\)\]{--tw-text-opacity: 1;color:rgb(10 186 181 / var(--tw-text-opacity, 1))}.text-amber-100{--tw-text-opacity: 1;color:rgb(254 243 199 / var(--tw-text-opacity, 1))}.text-amber-200{--tw-text-opacity: 1;color:rgb(253 230 138 / var(--tw-text-opacity, 1))}.text-amber-300{--tw-text-opacity: 1;color:rgb(252 211 77 / var(--tw-text-opacity, 1))}.text-amber-400{--tw-text-opacity: 1;color:rgb(251 191 36 / var(--tw-text-opacity, 1))}.text-blue-200{--tw-text-opacity: 1;color:rgb(191 219 254 / var(--tw-text-opacity, 1))}.text-blue-300{--tw-text-opacity: 1;color:rgb(147 197 253 / var(--tw-text-opacity, 1))}.text-cyan-100{--tw-text-opacity: 1;color:rgb(207 250 254 / var(--tw-text-opacity, 1))}.text-cyan-200{--tw-text-opacity: 1;color:rgb(165 243 252 / var(--tw-text-opacity, 1))}.text-cyan-300{--tw-text-opacity: 1;color:rgb(103 232 249 / var(--tw-text-opacity, 1))}.text-cyan-300\/90{color:#67e8f9e6}.text-cyan-400{--tw-text-opacity: 1;color:rgb(34 211 238 / var(--tw-text-opacity, 1))}.text-emerald-100{--tw-text-opacity: 1;color:rgb(209 250 229 / var(--tw-text-opacity, 1))}.text-emerald-100\/90{color:#d1fae5e6}.text-emerald-200{--tw-text-opacity: 1;color:rgb(167 243 208 / var(--tw-text-opacity, 1))}.text-emerald-300{--tw-text-opacity: 1;color:rgb(110 231 183 / var(--tw-text-opacity, 1))}.text-emerald-400{--tw-text-opacity: 1;color:rgb(52 211 153 / var(--tw-text-opacity, 1))}.text-emerald-400\/40{color:#34d39966}.text-indigo-300{--tw-text-opacity: 1;color:rgb(165 180 252 / var(--tw-text-opacity, 1))}.text-purple-200\/80{color:#e9d5ffcc}.text-purple-300{--tw-text-opacity: 1;color:rgb(216 180 254 / var(--tw-text-opacity, 1))}.text-purple-400{--tw-text-opacity: 1;color:rgb(192 132 252 / var(--tw-text-opacity, 1))}.text-rose-100{--tw-text-opacity: 1;color:rgb(255 228 230 / var(--tw-text-opacity, 1))}.text-rose-100\/90{color:#ffe4e6e6}.text-rose-200{--tw-text-opacity: 1;color:rgb(254 205 211 / var(--tw-text-opacity, 1))}.text-rose-300{--tw-text-opacity: 1;color:rgb(253 164 175 / var(--tw-text-opacity, 1))}.text-rose-400{--tw-text-opacity: 1;color:rgb(251 113 133 / var(--tw-text-opacity, 1))}.text-slate-100{--tw-text-opacity: 1;color:rgb(241 245 249 / var(--tw-text-opacity, 1))}.text-slate-200{--tw-text-opacity: 1;color:rgb(226 232 240 / var(--tw-text-opacity, 1))}.text-slate-200\/80{color:#e2e8f0cc}.text-slate-200\/90{color:#e2e8f0e6}.text-slate-300{--tw-text-opacity: 1;color:rgb(203 213 225 / var(--tw-text-opacity, 1))}.text-slate-300\/70{color:#cbd5e1b3}.text-slate-400{--tw-text-opacity: 1;color:rgb(148 163 184 / var(--tw-text-opacity, 1))}.text-slate-400\/80{color:#94a3b8cc}.text-slate-50{--tw-text-opacity: 1;color:rgb(248 250 252 / var(--tw-text-opacity, 1))}.text-slate-500{--tw-text-opacity: 1;color:rgb(100 116 139 / var(--tw-text-opacity, 1))}.text-slate-600{--tw-text-opacity: 1;color:rgb(71 85 105 / var(--tw-text-opacity, 1))}.text-transparent{color:transparent}.text-white{--tw-text-opacity: 1;color:rgb(255 255 255 / var(--tw-text-opacity, 1))}.text-yellow-300{--tw-text-opacity: 1;color:rgb(253 224 71 / var(--tw-text-opacity, 1))}.opacity-30{opacity:.3}.opacity-50{opacity:.5}.opacity-70{opacity:.7}.shadow{--tw-shadow: 0 1px 3px 0 rgb(0 0 0 / .1), 0 1px 2px -1px rgb(0 0 0 / .1);--tw-shadow-colored: 0 1px 3px 0 var(--tw-shadow-color), 0 1px 2px -1px var(--tw-shadow-color);box-shadow:var(--tw-ring-offset-shadow, 0 0 #0000),var(--tw-ring-shadow, 0 0 #0000),var(--tw-shadow)}.shadow-2xl{--tw-shadow: 0 25px 50px -12px rgb(0 0 0 / .25);--tw-shadow-colored: 0 25px 50px -12px var(--tw-shadow-color);box-shadow:var(--tw-ring-offset-shadow, 0 0 #0000),var(--tw-ring-shadow, 0 0 #0000),var(--tw-shadow)}.shadow-\[0_0_12px_rgba\(33\,240\,255\,0\.25\)\]{--tw-shadow: 0 0 12px rgba(33,240,255,.25);--tw-shadow-colored: 0 0 12px var(--tw-shadow-color);box-shadow:var(--tw-ring-offset-shadow, 0 0 #0000),var(--tw-ring-shadow, 0 0 #0000),var(--tw-shadow)}.shadow-\[0_0_15px_rgba\(16\,185\,129\,0\.2\)\,inset_0_1px_2px_rgba\(255\,255\,255\,0\.1\)\]{--tw-shadow: 0 0 15px rgba(16,185,129,.2),inset 0 1px 2px rgba(255,255,255,.1);--tw-shadow-colored: 0 0 15px var(--tw-shadow-color), inset 0 1px 2px var(--tw-shadow-color);box-shadow:var(--tw-ring-offset-shadow, 0 0 #0000),var(--tw-ring-shadow, 0 0 #0000),var(--tw-shadow)}.shadow-\[0_0_16px_rgba\(139\,0\,0\,0\.25\)\,0_4px_12px_rgba\(0\,0\,0\,0\.4\)\,inset_0_1px_1px_rgba\(255\,255\,255\,0\.08\)\]{--tw-shadow: 0 0 16px rgba(139,0,0,.25),0 4px 12px rgba(0,0,0,.4),inset 0 1px 1px rgba(255,255,255,.08);--tw-shadow-colored: 0 0 16px var(--tw-shadow-color), 0 4px 12px var(--tw-shadow-color), inset 0 1px 1px var(--tw-shadow-color);box-shadow:var(--tw-ring-offset-shadow, 0 0 #0000),var(--tw-ring-shadow, 0 0 #0000),var(--tw-shadow)}.shadow-\[0_0_16px_rgba\(147\,51\,234\,0\.08\)\]{--tw-shadow: 0 0 16px rgba(147,51,234,.08);--tw-shadow-colored: 0 0 16px var(--tw-shadow-color);box-shadow:var(--tw-ring-offset-shadow, 0 0 #0000),var(--tw-ring-shadow, 0 0 #0000),var(--tw-shadow)}.shadow-\[0_0_20px_rgba\(15\,123\,255\,0\.4\)\,0_2px_8px_rgba\(123\,44\,191\,0\.3\)\]{--tw-shadow: 0 0 20px rgba(15,123,255,.4),0 2px 8px rgba(123,44,191,.3);--tw-shadow-colored: 0 0 20px var(--tw-shadow-color), 0 2px 8px var(--tw-shadow-color);box-shadow:var(--tw-ring-offset-shadow, 0 0 #0000),var(--tw-ring-shadow, 0 0 #0000),var(--tw-shadow)}.shadow-\[0_0_20px_rgba\(244\,63\,94\,0\.2\)\,inset_0_1px_2px_rgba\(255\,255\,255\,0\.1\)\]{--tw-shadow: 0 0 20px rgba(244,63,94,.2),inset 0 1px 2px rgba(255,255,255,.1);--tw-shadow-colored: 0 0 20px var(--tw-shadow-color), inset 0 1px 2px var(--tw-shadow-color);box-shadow:var(--tw-ring-offset-shadow, 0 0 #0000),var(--tw-ring-shadow, 0 0 #0000),var(--tw-shadow)}.shadow-\[0_0_20px_rgba\(6\,182\,212\,0\.15\)\]{--tw-shadow: 0 0 20px rgba(6,182,212,.15);--tw-shadow-colored: 0 0 20px var(--tw-shadow-color);box-shadow:var(--tw-ring-offset-shadow, 0 0 #0000),var(--tw-ring-shadow, 0 0 #0000),var(--tw-shadow)}.shadow-\[0_0_20px_rgba\(6\,182\,212\,0\.3\)\,0_0_30px_rgba\(147\,51\,234\,0\.2\)\,0_4px_16px_rgba\(0\,0\,0\,0\.3\)\,inset_0_1px_2px_rgba\(255\,255\,255\,0\.15\)\,inset_0_-1px_2px_rgba\(0\,0\,0\,0\.2\)\]{--tw-shadow: 0 0 20px rgba(6,182,212,.3),0 0 30px rgba(147,51,234,.2),0 4px 16px rgba(0,0,0,.3),inset 0 1px 2px rgba(255,255,255,.15),inset 0 -1px 2px rgba(0,0,0,.2);--tw-shadow-colored: 0 0 20px var(--tw-shadow-color), 0 0 30px var(--tw-shadow-color), 0 4px 16px var(--tw-shadow-color), inset 0 1px 2px var(--tw-shadow-color), inset 0 -1px 2px var(--tw-shadow-color);box-shadow:var(--tw-ring-offset-shadow, 0 0 #0000),var(--tw-ring-shadow, 0 0 #0000),var(--tw-shadow)}.shadow-\[0_0_25px_rgba\(10\,186\,181\,0\.18\)\,inset_0_1px_0_rgba\(10\,186\,181\,0\.12\)\]{--tw-shadow: 0 0 25px rgba(10,186,181,.18),inset 0 1px 0 rgba(10,186,181,.12);--tw-shadow-colored: 0 0 25px var(--tw-shadow-color), inset 0 1px 0 var(--tw-shadow-color);box-shadow:var(--tw-ring-offset-shadow, 0 0 #0000),var(--tw-ring-shadow, 0 0 #0000),var(--tw-shadow)}.shadow-\[0_0_30px_rgba\(15\,123\,255\,0\.2\)\,inset_0_1px_0_rgba\(147\,197\,253\,0\.15\)\]{--tw-shadow: 0 0 30px rgba(15,123,255,.2),inset 0 1px 0 rgba(147,197,253,.15);--tw-shadow-colored: 0 0 30px var(--tw-shadow-color), inset 0 1px 0 var(--tw-shadow-color);box-shadow:var(--tw-ring-offset-shadow, 0 0 #0000),var(--tw-ring-shadow, 0 0 #0000),var(--tw-shadow)}.shadow-\[0_0_30px_rgba\(6\,182\,212\,0\.4\)\,0_0_40px_rgba\(6\,182\,212\,0\.25\)\,0_4px_16px_rgba\(0\,0\,0\,0\.3\)\,inset_0_0_30px_rgba\(6\,182\,212\,0\.1\)\]{--tw-shadow: 0 0 30px rgba(6,182,212,.4),0 0 40px rgba(6,182,212,.25),0 4px 16px rgba(0,0,0,.3),inset 0 0 30px rgba(6,182,212,.1);--tw-shadow-colored: 0 0 30px var(--tw-shadow-color), 0 0 40px var(--tw-shadow-color), 0 4px 16px var(--tw-shadow-color), inset 0 0 30px var(--tw-shadow-color);box-shadow:var(--tw-ring-offset-shadow, 0 0 #0000),var(--tw-ring-shadow, 0 0 #0000),var(--tw-shadow)}.shadow-\[0_0_6px_currentColor\]{--tw-shadow: 0 0 6px currentColor;--tw-shadow-colored: 0 0 6px var(--tw-shadow-color);box-shadow:var(--tw-ring-offset-shadow, 0 0 #0000),var(--tw-ring-shadow, 0 0 #0000),var(--tw-shadow)}.shadow-\[0_0_8px_rgba\(99\,102\,241\,0\.2\)\,inset_0_1px_1px_rgba\(255\,255\,255\,0\.1\)\]{--tw-shadow: 0 0 8px rgba(99,102,241,.2),inset 0 1px 1px rgba(255,255,255,.1);--tw-shadow-colored: 0 0 8px var(--tw-shadow-color), inset 0 1px 1px var(--tw-shadow-color);box-shadow:var(--tw-ring-offset-shadow, 0 0 #0000),var(--tw-ring-shadow, 0 0 #0000),var(--tw-shadow)}.shadow-\[0_2px_8px_rgba\(0\,0\,0\,0\.2\)\,inset_0_1px_1px_rgba\(255\,255\,255\,0\.06\)\]{--tw-shadow: 0 2px 8px rgba(0,0,0,.2),inset 0 1px 1px rgba(255,255,255,.06);--tw-shadow-colored: 0 2px 8px var(--tw-shadow-color), inset 0 1px 1px var(--tw-shadow-color);box-shadow:var(--tw-ring-offset-shadow, 0 0 #0000),var(--tw-ring-shadow, 0 0 #0000),var(--tw-shadow)}.shadow-\[0_2px_8px_rgba\(0\,0\,0\,0\.2\)\,inset_0_1px_1px_rgba\(255\,255\,255\,0\.08\)\]{--tw-shadow: 0 2px 8px rgba(0,0,0,.2),inset 0 1px 1px rgba(255,255,255,.08);--tw-shadow-colored: 0 2px 8px var(--tw-shadow-color), inset 0 1px 1px var(--tw-shadow-color);box-shadow:var(--tw-ring-offset-shadow, 0 0 #0000),var(--tw-ring-shadow, 0 0 #0000),var(--tw-shadow)}.shadow-\[0_2px_8px_rgba\(0\,0\,0\,0\.2\)\,inset_0_1px_1px_rgba\(255\,255\,255\,0\.1\)\]{--tw-shadow: 0 2px 8px rgba(0,0,0,.2),inset 0 1px 1px rgba(255,255,255,.1);--tw-shadow-colored: 0 2px 8px var(--tw-shadow-color), inset 0 1px 1px var(--tw-shadow-color);box-shadow:var(--tw-ring-offset-shadow, 0 0 #0000),var(--tw-ring-shadow, 0 0 #0000),var(--tw-shadow)}.shadow-\[0_4px_16px_rgba\(0\,0\,0\,0\.25\)\,0_0_15px_rgba\(10\,186\,181\,0\.2\)\,inset_0_1px_2px_rgba\(255\,255\,255\,0\.1\)\]{--tw-shadow: 0 4px 16px rgba(0,0,0,.25),0 0 15px rgba(10,186,181,.2),inset 0 1px 2px rgba(255,255,255,.1);--tw-shadow-colored: 0 4px 16px var(--tw-shadow-color), 0 0 15px var(--tw-shadow-color), inset 0 1px 2px var(--tw-shadow-color);box-shadow:var(--tw-ring-offset-shadow, 0 0 #0000),var(--tw-ring-shadow, 0 0 #0000),var(--tw-shadow)}.shadow-\[0_4px_16px_rgba\(0\,0\,0\,0\.25\)\,0_0_15px_rgba\(16\,185\,129\,0\.2\)\,inset_0_1px_2px_rgba\(255\,255\,255\,0\.1\)\]{--tw-shadow: 0 4px 16px rgba(0,0,0,.25),0 0 15px rgba(16,185,129,.2),inset 0 1px 2px rgba(255,255,255,.1);--tw-shadow-colored: 0 4px 16px var(--tw-shadow-color), 0 0 15px var(--tw-shadow-color), inset 0 1px 2px var(--tw-shadow-color);box-shadow:var(--tw-ring-offset-shadow, 0 0 #0000),var(--tw-ring-shadow, 0 0 #0000),var(--tw-shadow)}.shadow-\[0_4px_16px_rgba\(0\,0\,0\,0\.3\)\,0_0_8px_rgba\(15\,123\,255\,0\.1\)\,inset_0_1px_2px_rgba\(255\,255\,255\,0\.1\)\,inset_0_0_20px_rgba\(15\,123\,255\,0\.03\)\]{--tw-shadow: 0 4px 16px rgba(0,0,0,.3),0 0 8px rgba(15,123,255,.1),inset 0 1px 2px rgba(255,255,255,.1),inset 0 0 20px rgba(15,123,255,.03);--tw-shadow-colored: 0 4px 16px var(--tw-shadow-color), 0 0 8px var(--tw-shadow-color), inset 0 1px 2px var(--tw-shadow-color), inset 0 0 20px var(--tw-shadow-color);box-shadow:var(--tw-ring-offset-shadow, 0 0 #0000),var(--tw-ring-shadow, 0 0 #0000),var(--tw-shadow)}.shadow-\[0_4px_16px_rgba\(0\,0\,0\,0\.3\)\,0_1px_4px_rgba\(15\,123\,255\,0\.1\)\,inset_0_1px_1px_rgba\(255\,255\,255\,0\.06\)\]{--tw-shadow: 0 4px 16px rgba(0,0,0,.3),0 1px 4px rgba(15,123,255,.1),inset 0 1px 1px rgba(255,255,255,.06);--tw-shadow-colored: 0 4px 16px var(--tw-shadow-color), 0 1px 4px var(--tw-shadow-color), inset 0 1px 1px var(--tw-shadow-color);box-shadow:var(--tw-ring-offset-shadow, 0 0 #0000),var(--tw-ring-shadow, 0 0 #0000),var(--tw-shadow)}.shadow-\[0_4px_16px_rgba\(0\,0\,0\,0\.3\)\,inset_0_1px_1px_rgba\(255\,255\,255\,0\.05\)\]{--tw-shadow: 0 4px 16px rgba(0,0,0,.3),inset 0 1px 1px rgba(255,255,255,.05);--tw-shadow-colored: 0 4px 16px var(--tw-shadow-color), inset 0 1px 1px var(--tw-shadow-color);box-shadow:var(--tw-ring-offset-shadow, 0 0 #0000),var(--tw-ring-shadow, 0 0 #0000),var(--tw-shadow)}.shadow-\[0_8px_32px_rgba\(0\,0\,0\,0\.35\)\,0_2px_8px_rgba\(15\,123\,255\,0\.1\)\,inset_0_1px_1px_rgba\(255\,255\,255\,0\.06\)\]{--tw-shadow: 0 8px 32px rgba(0,0,0,.35),0 2px 8px rgba(15,123,255,.1),inset 0 1px 1px rgba(255,255,255,.06);--tw-shadow-colored: 0 8px 32px var(--tw-shadow-color), 0 2px 8px var(--tw-shadow-color), inset 0 1px 1px var(--tw-shadow-color);box-shadow:var(--tw-ring-offset-shadow, 0 0 #0000),var(--tw-ring-shadow, 0 0 #0000),var(--tw-shadow)}.shadow-\[0_8px_32px_rgba\(0\,0\,0\,0\.4\)\,0_2px_8px_rgba\(147\,51\,234\,0\.12\)\,inset_0_1px_1px_rgba\(255\,255\,255\,0\.08\)\]{--tw-shadow: 0 8px 32px rgba(0,0,0,.4),0 2px 8px rgba(147,51,234,.12),inset 0 1px 1px rgba(255,255,255,.08);--tw-shadow-colored: 0 8px 32px var(--tw-shadow-color), 0 2px 8px var(--tw-shadow-color), inset 0 1px 1px var(--tw-shadow-color);box-shadow:var(--tw-ring-offset-shadow, 0 0 #0000),var(--tw-ring-shadow, 0 0 #0000),var(--tw-shadow)}.shadow-\[0_8px_32px_rgba\(0\,0\,0\,0\.4\)\,0_2px_8px_rgba\(15\,123\,255\,0\.12\)\,inset_0_1px_1px_rgba\(255\,255\,255\,0\.06\)\]{--tw-shadow: 0 8px 32px rgba(0,0,0,.4),0 2px 8px rgba(15,123,255,.12),inset 0 1px 1px rgba(255,255,255,.06);--tw-shadow-colored: 0 8px 32px var(--tw-shadow-color), 0 2px 8px var(--tw-shadow-color), inset 0 1px 1px var(--tw-shadow-color);box-shadow:var(--tw-ring-offset-shadow, 0 0 #0000),var(--tw-ring-shadow, 0 0 #0000),var(--tw-shadow)}.shadow-\[0_8px_32px_rgba\(0\,0\,0\,0\.4\)\,0_2px_8px_rgba\(15\,123\,255\,0\.15\)\,inset_0_1px_1px_rgba\(255\,255\,255\,0\.08\)\]{--tw-shadow: 0 8px 32px rgba(0,0,0,.4),0 2px 8px rgba(15,123,255,.15),inset 0 1px 1px rgba(255,255,255,.08);--tw-shadow-colored: 0 8px 32px var(--tw-shadow-color), 0 2px 8px var(--tw-shadow-color), inset 0 1px 1px var(--tw-shadow-color);box-shadow:var(--tw-ring-offset-shadow, 0 0 #0000),var(--tw-ring-shadow, 0 0 #0000),var(--tw-shadow)}.shadow-\[0_8px_32px_rgba\(0\,0\,0\,0\.4\)\,0_2px_8px_rgba\(16\,185\,129\,0\.12\)\,inset_0_1px_1px_rgba\(255\,255\,255\,0\.08\)\]{--tw-shadow: 0 8px 32px rgba(0,0,0,.4),0 2px 8px rgba(16,185,129,.12),inset 0 1px 1px rgba(255,255,255,.08);--tw-shadow-colored: 0 8px 32px var(--tw-shadow-color), 0 2px 8px var(--tw-shadow-color), inset 0 1px 1px var(--tw-shadow-color);box-shadow:var(--tw-ring-offset-shadow, 0 0 #0000),var(--tw-ring-shadow, 0 0 #0000),var(--tw-shadow)}.shadow-\[0_8px_32px_rgba\(0\,0\,0\,0\.4\)\,0_2px_8px_rgba\(6\,182\,212\,0\.12\)\,inset_0_1px_1px_rgba\(255\,255\,255\,0\.08\)\]{--tw-shadow: 0 8px 32px rgba(0,0,0,.4),0 2px 8px rgba(6,182,212,.12),inset 0 1px 1px rgba(255,255,255,.08);--tw-shadow-colored: 0 8px 32px var(--tw-shadow-color), 0 2px 8px var(--tw-shadow-color), inset 0 1px 1px var(--tw-shadow-color);box-shadow:var(--tw-ring-offset-shadow, 0 0 #0000),var(--tw-ring-shadow, 0 0 #0000),var(--tw-shadow)}.shadow-\[inset_0_1px_2px_rgba\(255\,255\,255\,0\.05\)\]{--tw-shadow: inset 0 1px 2px rgba(255,255,255,.05);--tw-shadow-colored: inset 0 1px 2px var(--tw-shadow-color);box-shadow:var(--tw-ring-offset-shadow, 0 0 #0000),var(--tw-ring-shadow, 0 0 #0000),var(--tw-shadow)}.shadow-\[inset_0_1px_2px_rgba\(255\,255\,255\,0\.1\)\]{--tw-shadow: inset 0 1px 2px rgba(255,255,255,.1);--tw-shadow-colored: inset 0 1px 2px var(--tw-shadow-color);box-shadow:var(--tw-ring-offset-shadow, 0 0 #0000),var(--tw-ring-shadow, 0 0 #0000),var(--tw-shadow)}.shadow-\[inset_0_2px_8px_rgba\(0\,0\,0\,0\.3\)\]{--tw-shadow: inset 0 2px 8px rgba(0,0,0,.3);--tw-shadow-colored: inset 0 2px 8px var(--tw-shadow-color);box-shadow:var(--tw-ring-offset-shadow, 0 0 #0000),var(--tw-ring-shadow, 0 0 #0000),var(--tw-shadow)}.shadow-glow{--tw-shadow: 0 0 25px rgba(33, 240, 255, .35);--tw-shadow-colored: 0 0 25px var(--tw-shadow-color);box-shadow:var(--tw-ring-offset-shadow, 0 0 #0000),var(--tw-ring-shadow, 0 0 #0000),var(--tw-shadow)}.shadow-lg{--tw-shadow: 0 10px 15px -3px rgb(0 0 0 / .1), 0 4px 6px -4px rgb(0 0 0 / .1);--tw-shadow-colored: 0 10px 15px -3px var(--tw-shadow-color), 0 4px 6px -4px var(--tw-shadow-color);box-shadow:var(--tw-ring-offset-shadow, 0 0 #0000),var(--tw-ring-shadow, 0 0 #0000),var(--tw-shadow)}.shadow-neon{--tw-shadow: 0 0 15px rgba(15, 123, 255, .45);--tw-shadow-colored: 0 0 15px var(--tw-shadow-color);box-shadow:var(--tw-ring-offset-shadow, 0 0 #0000),var(--tw-ring-shadow, 0 0 #0000),var(--tw-shadow)}.shadow-xl{--tw-shadow: 0 20px 25px -5px rgb(0 0 0 / .1), 0 8px 10px -6px rgb(0 0 0 / .1);--tw-shadow-colored: 0 20px 25px -5px var(--tw-shadow-color), 0 8px 10px -6px var(--tw-shadow-color);box-shadow:var(--tw-ring-offset-shadow, 0 0 #0000),var(--tw-ring-shadow, 0 0 #0000),var(--tw-shadow)}.ring-1{--tw-ring-offset-shadow: var(--tw-ring-inset) 0 0 0 var(--tw-ring-offset-width) var(--tw-ring-offset-color);--tw-ring-shadow: var(--tw-ring-inset) 0 0 0 calc(1px + var(--tw-ring-offset-width)) var(--tw-ring-color);box-shadow:var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow, 0 0 #0000)}.ring-inset{--tw-ring-inset: inset}.ring-white\/20{--tw-ring-color: rgb(255 255 255 / .2)}.ring-white\/5{--tw-ring-color: rgb(255 255 255 / .05)}.blur{--tw-blur: blur(8px);filter:var(--tw-blur) var(--tw-brightness) var(--tw-contrast) var(--tw-grayscale) var(--tw-hue-rotate) var(--tw-invert) var(--tw-saturate) var(--tw-sepia) var(--tw-drop-shadow)}.blur-xl{--tw-blur: blur(24px);filter:var(--tw-blur) var(--tw-brightness) var(--tw-contrast) var(--tw-grayscale) var(--tw-hue-rotate) var(--tw-invert) var(--tw-saturate) var(--tw-sepia) var(--tw-drop-shadow)}.drop-shadow{--tw-drop-shadow: drop-shadow(0 1px 2px rgb(0 0 0 / .1)) drop-shadow(0 1px 1px rgb(0 0 0 / .06));filter:var(--tw-blur) var(--tw-brightness) var(--tw-contrast) var(--tw-grayscale) var(--tw-hue-rotate) var(--tw-invert) var(--tw-saturate) var(--tw-sepia) var(--tw-drop-shadow)}.drop-shadow-\[0_0_20px_rgba\(6\,182\,212\,0\.3\)\]{--tw-drop-shadow: drop-shadow(0 0 20px rgba(6,182,212,.3));filter:var(--tw-blur) var(--tw-brightness) var(--tw-contrast) var(--tw-grayscale) var(--tw-hue-rotate) var(--tw-invert) var(--tw-saturate) var(--tw-sepia) var(--tw-drop-shadow)}.drop-shadow-\[0_0_8px_rgba\(147\,51\,234\,0\.5\)\]{--tw-drop-shadow: drop-shadow(0 0 8px rgba(147,51,234,.5));filter:var(--tw-blur) var(--tw-brightness) var(--tw-contrast) var(--tw-grayscale) var(--tw-hue-rotate) var(--tw-invert) var(--tw-saturate) var(--tw-sepia) var(--tw-drop-shadow)}.drop-shadow-\[0_0_8px_rgba\(16\,185\,129\,0\.5\)\]{--tw-drop-shadow: drop-shadow(0 0 8px rgba(16,185,129,.5));filter:var(--tw-blur) var(--tw-brightness) var(--tw-contrast) var(--tw-grayscale) var(--tw-hue-rotate) var(--tw-invert) var(--tw-saturate) var(--tw-sepia) var(--tw-drop-shadow)}.drop-shadow-\[0_0_8px_rgba\(255\,255\,255\,0\.3\)\]{--tw-drop-shadow: drop-shadow(0 0 8px rgba(255,255,255,.3));filter:var(--tw-blur) var(--tw-brightness) var(--tw-contrast) var(--tw-grayscale) var(--tw-hue-rotate) var(--tw-invert) var(--tw-saturate) var(--tw-sepia) var(--tw-drop-shadow)}.drop-shadow-\[0_0_8px_rgba\(6\,182\,212\,0\.5\)\]{--tw-drop-shadow: drop-shadow(0 0 8px rgba(6,182,212,.5));filter:var(--tw-blur) var(--tw-brightness) var(--tw-contrast) var(--tw-grayscale) var(--tw-hue-rotate) var(--tw-invert) var(--tw-saturate) var(--tw-sepia) var(--tw-drop-shadow)}.drop-shadow-\[0_1px_4px_rgba\(0\,0\,0\,0\.5\)\]{--tw-drop-shadow: drop-shadow(0 1px 4px rgba(0,0,0,.5));filter:var(--tw-blur) var(--tw-brightness) var(--tw-contrast) var(--tw-grayscale) var(--tw-hue-rotate) var(--tw-invert) var(--tw-saturate) var(--tw-sepia) var(--tw-drop-shadow)}.drop-shadow-\[0_2px_12px_rgba\(0\,0\,0\,0\.5\)\]{--tw-drop-shadow: drop-shadow(0 2px 12px rgba(0,0,0,.5));filter:var(--tw-blur) var(--tw-brightness) var(--tw-contrast) var(--tw-grayscale) var(--tw-hue-rotate) var(--tw-invert) var(--tw-saturate) var(--tw-sepia) var(--tw-drop-shadow)}.drop-shadow-\[0_2px_4px_rgba\(0\,0\,0\,0\.5\)\]{--tw-drop-shadow: drop-shadow(0 2px 4px rgba(0,0,0,.5));filter:var(--tw-blur) var(--tw-brightness) var(--tw-contrast) var(--tw-grayscale) var(--tw-hue-rotate) var(--tw-invert) var(--tw-saturate) var(--tw-sepia) var(--tw-drop-shadow)}.drop-shadow-\[0_2px_8px_rgba\(0\,0\,0\,0\.6\)\]{--tw-drop-shadow: drop-shadow(0 2px 8px rgba(0,0,0,.6));filter:var(--tw-blur) var(--tw-brightness) var(--tw-contrast) var(--tw-grayscale) var(--tw-hue-rotate) var(--tw-invert) var(--tw-saturate) var(--tw-sepia) var(--tw-drop-shadow)}.grayscale{--tw-grayscale: grayscale(100%);filter:var(--tw-blur) var(--tw-brightness) var(--tw-contrast) var(--tw-grayscale) var(--tw-hue-rotate) var(--tw-invert) var(--tw-saturate) var(--tw-sepia) var(--tw-drop-shadow)}.filter{filter:var(--tw-blur) var(--tw-brightness) var(--tw-contrast) var(--tw-grayscale) var(--tw-hue-rotate) var(--tw-invert) var(--tw-saturate) var(--tw-sepia) var(--tw-drop-shadow)}.backdrop-blur{--tw-backdrop-blur: blur(8px);-webkit-backdrop-filter:var(--tw-backdrop-blur) var(--tw-backdrop-brightness) var(--tw-backdrop-contrast) var(--tw-backdrop-grayscale) var(--tw-backdrop-hue-rotate) var(--tw-backdrop-invert) var(--tw-backdrop-opacity) var(--tw-backdrop-saturate) var(--tw-backdrop-sepia);backdrop-filter:var(--tw-backdrop-blur) var(--tw-backdrop-brightness) var(--tw-backdrop-contrast) var(--tw-backdrop-grayscale) var(--tw-backdrop-hue-rotate) var(--tw-backdrop-invert) var(--tw-backdrop-opacity) var(--tw-backdrop-saturate) var(--tw-backdrop-sepia)}.backdrop-blur-md{--tw-backdrop-blur: blur(12px);-webkit-backdrop-filter:var(--tw-backdrop-blur) var(--tw-backdrop-brightness) var(--tw-backdrop-contrast) var(--tw-backdrop-grayscale) var(--tw-backdrop-hue-rotate) var(--tw-backdrop-invert) var(--tw-backdrop-opacity) var(--tw-backdrop-saturate) var(--tw-backdrop-sepia);backdrop-filter:var(--tw-backdrop-blur) var(--tw-backdrop-brightness) var(--tw-backdrop-contrast) var(--tw-backdrop-grayscale) var(--tw-backdrop-hue-rotate) var(--tw-backdrop-invert) var(--tw-backdrop-opacity) var(--tw-backdrop-saturate) var(--tw-backdrop-sepia)}.backdrop-blur-sm{--tw-backdrop-blur: blur(4px);-webkit-backdrop-filter:var(--tw-backdrop-blur) var(--tw-backdrop-brightness) var(--tw-backdrop-contrast) var(--tw-backdrop-grayscale) var(--tw-backdrop-hue-rotate) var(--tw-backdrop-invert) var(--tw-backdrop-opacity) var(--tw-backdrop-saturate) var(--tw-backdrop-sepia);backdrop-filter:var(--tw-backdrop-blur) var(--tw-backdrop-brightness) var(--tw-backdrop-contrast) var(--tw-backdrop-grayscale) var(--tw-backdrop-hue-rotate) var(--tw-backdrop-invert) var(--tw-backdrop-opacity) var(--tw-backdrop-saturate) var(--tw-backdrop-sepia)}.backdrop-blur-xl{--tw-backdrop-blur: blur(24px);-webkit-backdrop-filter:var(--tw-backdrop-blur) var(--tw-backdrop-brightness) var(--tw-backdrop-contrast) var(--tw-backdrop-grayscale) var(--tw-backdrop-hue-rotate) var(--tw-backdrop-invert) var(--tw-backdrop-opacity) var(--tw-backdrop-saturate) var(--tw-backdrop-sepia);backdrop-filter:var(--tw-backdrop-blur) var(--tw-backdrop-brightness) var(--tw-backdrop-contrast) var(--tw-backdrop-grayscale) var(--tw-backdrop-hue-rotate) var(--tw-backdrop-invert) var(--tw-backdrop-opacity) var(--tw-backdrop-saturate) var(--tw-backdrop-sepia)}.transition{transition-property:color,background-color,border-color,text-decoration-color,fill,stroke,opacity,box-shadow,transform,filter,backdrop-filter;transition-timing-function:cubic-bezier(.4,0,.2,1);transition-duration:.15s}.transition-all{transition-property:all;transition-timing-function:cubic-bezier(.4,0,.2,1);transition-duration:.15s}.transition-transform{transition-property:transform;transition-timing-function:cubic-bezier(.4,0,.2,1);transition-duration:.15s}.duration-200{transition-duration:.2s}.duration-300{transition-duration:.3s}:root{font-family:Inter,system-ui,IBM Plex Sans,Helvetica,Arial,sans-serif;line-height:1.5;font-weight:400;color-scheme:dark;color:#f7faffeb;background-color:#050816;font-synthesis:none;text-rendering:optimizeLegibility;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale}body{margin:0;min-width:320px;min-height:100vh;background:radial-gradient(circle at 15% 20%,rgba(100,150,255,.15),transparent 35%),radial-gradient(circle at 85% 15%,rgba(150,100,255,.12),transparent 40%),radial-gradient(circle at 50% 90%,rgba(33,240,255,.08),transparent 45%),radial-gradient(ellipse at 70% 60%,rgba(80,120,200,.06),transparent 50%),linear-gradient(to bottom,#000814,#001a33,#000a1a);overflow:hidden}#root{width:100vw;height:100vh}.high-contrast body,body.high-contrast{background:#000;color:#fff}.high-contrast *,body.high-contrast *{outline-offset:2px}::-webkit-scrollbar{width:10px;height:10px}::-webkit-scrollbar-track{background:linear-gradient(to right,#0000004d,#0a142340);border-radius:6px;box-shadow:inset 0 0 6px #0006}::-webkit-scrollbar-thumb{background:linear-gradient(135deg,#06b6d440,#0f7bff38,#9333ea33);border-radius:6px;border:1px solid rgba(6,182,212,.15);box-shadow:0 0 4px #06b6d426,inset 0 1px 1px #ffffff14,inset 0 -1px 1px #0000004d}::-webkit-scrollbar-thumb:hover{background:linear-gradient(135deg,#06b6d466,#0f7bff59,#9333ea4d);box-shadow:0 0 8px #06b6d440,0 0 12px #0f7bff26,inset 0 1px 1px #ffffff1f,inset 0 -1px 1px #0000004d}::-webkit-scrollbar-thumb:active{background:linear-gradient(135deg,#06b6d480,#0f7bff73,#9333ea66);box-shadow:0 0 10px #06b6d44d,0 0 16px #0f7bff33,inset 0 1px 2px #0006}.galaxy-bg{position:relative;background:radial-gradient(ellipse at 30% 20%,rgba(70,120,200,.08),transparent 60%),radial-gradient(ellipse at 80% 70%,rgba(120,80,200,.06),transparent 55%),linear-gradient(135deg,#0a1628,#0f2847 45%,#152e52)}.galaxy-bg:before{content:"";position:absolute;top:0;left:0;right:0;bottom:0;background:radial-gradient(circle at 25% 35%,rgba(100,150,255,.03),transparent 45%),radial-gradient(circle at 75% 65%,rgba(200,100,255,.02),transparent 40%);pointer-events:none}.glow-text{text-shadow:0 0 10px rgba(0,212,255,.5),0 0 20px rgba(123,44,191,.3)}.glow-border{border:1px solid rgba(0,212,255,.3);box-shadow:0 0 10px #00d4ff33,inset 0 0 10px #00d4ff1a}.frosted-panel{background:linear-gradient(140deg,#0b182cd9,#121220e6);border:1px solid rgba(33,240,255,.08);box-shadow:0 12px 30px #03070f73,inset 0 0 0 1px #93c5fd05}.glass-card{background:linear-gradient(165deg,#08192db8,#0a0d1ec7);border:1px solid rgba(15,123,255,.12);box-shadow:0 10px 35px #020a1899}@keyframes fadeIn{0%{opacity:0;transform:translateY(10px)}to{opacity:1;transform:translateY(0)}}.animate-fade-in{animation:fadeIn .5s ease-out}@keyframes slideInLeft{0%{transform:translate(-100%)}to{transform:translate(0)}}@keyframes slideInRight{0%{transform:translate(100%)}to{transform:translate(0)}}.animate-slide-in-left{animation:slideInLeft .3s ease-out}.animate-slide-in-right{animation:slideInRight .3s ease-out}.star-static{position:absolute;border-radius:50%;will-change:transform;transform:translateZ(0)}.star-static[data-color=white]{background:radial-gradient(circle,rgba(240,245,255,1) 0%,rgba(200,220,255,.9) 20%,rgba(180,200,240,.4) 50%,transparent 100%);box-shadow:0 0 2px #f0f5ff,0 0 4px #c8dcffcc,0 0 8px #b4c8f080,0 0 12px #a0b4dc40}.star-static[data-color=blue]{background:radial-gradient(circle,rgba(220,235,255,1) 0%,rgba(180,210,255,.85) 20%,rgba(140,180,255,.4) 50%,transparent 100%);box-shadow:0 0 2px #dcebff,0 0 5px #b4d2ffb3,0 0 10px #8cb4ff66,0 0 15px #6496ff33}.star-static[data-color=yellow]{background:radial-gradient(circle,rgba(255,250,230,1) 0%,rgba(255,240,200,.9) 20%,rgba(255,220,150,.4) 50%,transparent 100%);box-shadow:0 0 2px #fffae6,0 0 4px #fff0c8cc,0 0 8px #ffdc9680,0 0 12px #ffc86440}.star-static[data-color=orange]{background:radial-gradient(circle,rgba(255,220,180,1) 0%,rgba(255,200,140,.9) 20%,rgba(255,180,100,.4) 50%,transparent 100%);box-shadow:0 0 2px #ffdcb4,0 0 4px #ffc88cbf,0 0 8px #ffb46473,0 0 12px #ffa05038}.star-static[data-color=red]{background:radial-gradient(circle,rgba(255,200,180,1) 0%,rgba(255,160,140,.9) 20%,rgba(255,120,100,.4) 50%,transparent 100%);box-shadow:0 0 2px #ffc8b4,0 0 4px #ffa08cb3,0 0 8px #ff786466,0 0 12px #ff503c33}.shooting-star-static{position:absolute;height:1.5px;background:linear-gradient(90deg,#dcebfff2,#c8dcffcc 25%,#b4c8ff66 60%,#c8dcff00);transform:rotate(15deg);box-shadow:0 0 8px #c8dcff99,0 0 4px #dcebff66}.noise-overlay:after{content:"";position:absolute;top:0;right:0;bottom:0;left:0;background-image:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='160' height='160' viewBox='0 0 160 160'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='1.2' stitchTiles='stitch'/%3E%3C/filter%3E%3Crect width='100%25' height='100%25' filter='url(%23n)' opacity='0.08'/%3E%3C/svg%3E");pointer-events:none;mix-blend-mode:screen}.compose-area-shadow{box-shadow:0 -12px 30px #050c1999}.react-flow__edge-path{stroke-linecap:round;stroke-linejoin:round;transition:stroke-width .3s ease,filter .3s ease}.react-flow__edge.animated .react-flow__edge-path{stroke-dasharray:8 4;animation:edgeFlow 1.5s linear infinite,edgePulse 2s ease-in-out infinite}@keyframes edgeFlow{0%{stroke-dashoffset:12}to{stroke-dashoffset:0}}@keyframes edgePulse{0%,to{opacity:.7}50%{opacity:1}}.react-flow__arrowhead polyline{stroke-linejoin:round;stroke-linecap:round}.react-flow__edge.selected .react-flow__edge-path{stroke-width:3px!important;filter:brightness(1.3) drop-shadow(0 0 6px currentColor)!important}.placeholder\:text-slate-500::-moz-placeholder{--tw-text-opacity: 1;color:rgb(100 116 139 / var(--tw-text-opacity, 1))}.placeholder\:text-slate-500::placeholder{--tw-text-opacity: 1;color:rgb(100 116 139 / var(--tw-text-opacity, 1))}.focus-within\:border-white\/15:focus-within{border-color:#ffffff26}.focus-within\:shadow-\[0_0_8px_rgba\(15\,123\,255\,0\.08\)\,inset_0_2px_8px_rgba\(0\,0\,0\,0\.3\)\]:focus-within{--tw-shadow: 0 0 8px rgba(15,123,255,.08),inset 0 2px 8px rgba(0,0,0,.3);--tw-shadow-colored: 0 0 8px var(--tw-shadow-color), inset 0 2px 8px var(--tw-shadow-color);box-shadow:var(--tw-ring-offset-shadow, 0 0 #0000),var(--tw-ring-shadow, 0 0 #0000),var(--tw-shadow)}.focus-within\:shadow-\[0_0_8px_rgba\(16\,185\,129\,0\.08\)\,inset_0_2px_8px_rgba\(0\,0\,0\,0\.3\)\]:focus-within{--tw-shadow: 0 0 8px rgba(16,185,129,.08),inset 0 2px 8px rgba(0,0,0,.3);--tw-shadow-colored: 0 0 8px var(--tw-shadow-color), inset 0 2px 8px var(--tw-shadow-color);box-shadow:var(--tw-ring-offset-shadow, 0 0 #0000),var(--tw-ring-shadow, 0 0 #0000),var(--tw-shadow)}.hover\:translate-y-\[-2px\]:hover{--tw-translate-y: -2px;transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skew(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y))}.hover\:scale-105:hover{--tw-scale-x: 1.05;--tw-scale-y: 1.05;transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skew(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y))}.hover\:border-\[rgba\(10\,186\,181\,0\.6\)\]:hover{border-color:#0abab599}.hover\:border-amber-400\/40:hover{border-color:#fbbf2466}.hover\:border-emerald-400\/40:hover{border-color:#34d39966}.hover\:border-emerald-400\/60:hover{border-color:#34d39999}.hover\:border-purple-400\/40:hover{border-color:#c084fc66}.hover\:border-rose-800\/50:hover{border-color:#9f123980}.hover\:border-white\/20:hover{border-color:#fff3}.hover\:border-white\/25:hover{border-color:#ffffff40}.hover\:border-white\/30:hover{border-color:#ffffff4d}.hover\:border-white\/35:hover{border-color:#ffffff59}.hover\:bg-black\/40:hover{background-color:#0006}.hover\:bg-white\/10:hover{background-color:#ffffff1a}.hover\:bg-white\/5:hover{background-color:#ffffff0d}.hover\:from-\[rgba\(10\,186\,181\,0\.25\)\]:hover{--tw-gradient-from: rgba(10,186,181,.25) var(--tw-gradient-from-position);--tw-gradient-to: rgba(10, 186, 181, 0) var(--tw-gradient-to-position);--tw-gradient-stops: var(--tw-gradient-from), var(--tw-gradient-to)}.hover\:from-\[rgba\(100\,25\,35\,0\.85\)\]:hover{--tw-gradient-from: rgba(100,25,35,.85) var(--tw-gradient-from-position);--tw-gradient-to: rgba(100, 25, 35, 0) var(--tw-gradient-to-position);--tw-gradient-stops: var(--tw-gradient-from), var(--tw-gradient-to)}.hover\:from-\[rgba\(28\,45\,65\,0\.85\)\]:hover{--tw-gradient-from: rgba(28,45,65,.85) var(--tw-gradient-from-position);--tw-gradient-to: rgba(28, 45, 65, 0) var(--tw-gradient-to-position);--tw-gradient-stops: var(--tw-gradient-from), var(--tw-gradient-to)}.hover\:from-\[rgba\(6\,182\,212\,0\.95\)\]:hover{--tw-gradient-from: rgba(6,182,212,.95) var(--tw-gradient-from-position);--tw-gradient-to: rgba(6, 182, 212, 0) var(--tw-gradient-to-position);--tw-gradient-stops: var(--tw-gradient-from), var(--tw-gradient-to)}.hover\:from-emerald-500\/25:hover{--tw-gradient-from: rgb(16 185 129 / .25) var(--tw-gradient-from-position);--tw-gradient-to: rgb(16 185 129 / 0) var(--tw-gradient-to-position);--tw-gradient-stops: var(--tw-gradient-from), var(--tw-gradient-to)}.hover\:via-\[rgba\(120\,30\,40\,0\.80\)\]:hover{--tw-gradient-to: rgba(120, 30, 40, 0) var(--tw-gradient-to-position);--tw-gradient-stops: var(--tw-gradient-from), rgba(120,30,40,.8) var(--tw-gradient-via-position), var(--tw-gradient-to)}.hover\:via-\[rgba\(147\,51\,234\,0\.90\)\]:hover{--tw-gradient-to: rgba(147, 51, 234, 0) var(--tw-gradient-to-position);--tw-gradient-stops: var(--tw-gradient-from), rgba(147,51,234,.9) var(--tw-gradient-via-position), var(--tw-gradient-to)}.hover\:via-\[rgba\(23\,38\,56\,0\.8\)\]:hover{--tw-gradient-to: rgba(23, 38, 56, 0) var(--tw-gradient-to-position);--tw-gradient-stops: var(--tw-gradient-from), rgba(23,38,56,.8) var(--tw-gradient-via-position), var(--tw-gradient-to)}.hover\:to-\[rgba\(100\,25\,35\,0\.85\)\]:hover{--tw-gradient-to: rgba(100,25,35,.85) var(--tw-gradient-to-position)}.hover\:to-\[rgba\(18\,30\,48\,0\.85\)\]:hover{--tw-gradient-to: rgba(18,30,48,.85) var(--tw-gradient-to-position)}.hover\:to-\[rgba\(236\,72\,153\,0\.95\)\]:hover{--tw-gradient-to: rgba(236,72,153,.95) var(--tw-gradient-to-position)}.hover\:to-\[rgba\(6\,182\,212\,0\.25\)\]:hover{--tw-gradient-to: rgba(6,182,212,.25) var(--tw-gradient-to-position)}.hover\:to-cyan-500\/25:hover{--tw-gradient-to: rgb(6 182 212 / .25) var(--tw-gradient-to-position)}.hover\:text-slate-200:hover{--tw-text-opacity: 1;color:rgb(226 232 240 / var(--tw-text-opacity, 1))}.hover\:text-white:hover{--tw-text-opacity: 1;color:rgb(255 255 255 / var(--tw-text-opacity, 1))}.hover\:shadow-\[0_0_10px_rgba\(15\,123\,255\,0\.15\)\]:hover{--tw-shadow: 0 0 10px rgba(15,123,255,.15);--tw-shadow-colored: 0 0 10px var(--tw-shadow-color);box-shadow:var(--tw-ring-offset-shadow, 0 0 #0000),var(--tw-ring-shadow, 0 0 #0000),var(--tw-shadow)}.hover\:shadow-\[0_4px_12px_rgba\(0\,0\,0\,0\.25\)\,0_0_15px_rgba\(15\,123\,255\,0\.15\)\]:hover{--tw-shadow: 0 4px 12px rgba(0,0,0,.25),0 0 15px rgba(15,123,255,.15);--tw-shadow-colored: 0 4px 12px var(--tw-shadow-color), 0 0 15px var(--tw-shadow-color);box-shadow:var(--tw-ring-offset-shadow, 0 0 #0000),var(--tw-ring-shadow, 0 0 #0000),var(--tw-shadow)}.hover\:shadow-\[0_4px_12px_rgba\(0\,0\,0\,0\.25\)\,0_0_15px_rgba\(16\,185\,129\,0\.2\)\]:hover{--tw-shadow: 0 4px 12px rgba(0,0,0,.25),0 0 15px rgba(16,185,129,.2);--tw-shadow-colored: 0 4px 12px var(--tw-shadow-color), 0 0 15px var(--tw-shadow-color);box-shadow:var(--tw-ring-offset-shadow, 0 0 #0000),var(--tw-ring-shadow, 0 0 #0000),var(--tw-shadow)}.hover\:shadow-\[0_4px_12px_rgba\(0\,0\,0\,0\.25\)\,0_0_15px_rgba\(245\,158\,11\,0\.2\)\]:hover{--tw-shadow: 0 4px 12px rgba(0,0,0,.25),0 0 15px rgba(245,158,11,.2);--tw-shadow-colored: 0 4px 12px var(--tw-shadow-color), 0 0 15px var(--tw-shadow-color);box-shadow:var(--tw-ring-offset-shadow, 0 0 #0000),var(--tw-ring-shadow, 0 0 #0000),var(--tw-shadow)}.hover\:shadow-\[0_8px_24px_rgba\(0\,0\,0\,0\.3\)\,0_0_25px_rgba\(10\,186\,181\,0\.3\)\]:hover{--tw-shadow: 0 8px 24px rgba(0,0,0,.3),0 0 25px rgba(10,186,181,.3);--tw-shadow-colored: 0 8px 24px var(--tw-shadow-color), 0 0 25px var(--tw-shadow-color);box-shadow:var(--tw-ring-offset-shadow, 0 0 #0000),var(--tw-ring-shadow, 0 0 #0000),var(--tw-shadow)}.hover\:shadow-\[0_8px_24px_rgba\(0\,0\,0\,0\.3\)\,0_0_25px_rgba\(16\,185\,129\,0\.3\)\]:hover{--tw-shadow: 0 8px 24px rgba(0,0,0,.3),0 0 25px rgba(16,185,129,.3);--tw-shadow-colored: 0 8px 24px var(--tw-shadow-color), 0 0 25px var(--tw-shadow-color);box-shadow:var(--tw-ring-offset-shadow, 0 0 #0000),var(--tw-ring-shadow, 0 0 #0000),var(--tw-shadow)}.hover\:shadow-\[0_8px_24px_rgba\(0\,0\,0\,0\.35\)\,0_0_20px_rgba\(15\,123\,255\,0\.2\)\,0_0_30px_rgba\(6\,182\,212\,0\.15\)\,inset_0_1px_2px_rgba\(255\,255\,255\,0\.15\)\,inset_0_0_30px_rgba\(15\,123\,255\,0\.06\)\]:hover{--tw-shadow: 0 8px 24px rgba(0,0,0,.35),0 0 20px rgba(15,123,255,.2),0 0 30px rgba(6,182,212,.15),inset 0 1px 2px rgba(255,255,255,.15),inset 0 0 30px rgba(15,123,255,.06);--tw-shadow-colored: 0 8px 24px var(--tw-shadow-color), 0 0 20px var(--tw-shadow-color), 0 0 30px var(--tw-shadow-color), inset 0 1px 2px var(--tw-shadow-color), inset 0 0 30px var(--tw-shadow-color);box-shadow:var(--tw-ring-offset-shadow, 0 0 #0000),var(--tw-ring-shadow, 0 0 #0000),var(--tw-shadow)}.focus\:border-white\/15:focus{border-color:#ffffff26}.focus\:shadow-\[0_0_8px_rgba\(15\,123\,255\,0\.08\)\,inset_0_2px_8px_rgba\(0\,0\,0\,0\.3\)\]:focus{--tw-shadow: 0 0 8px rgba(15,123,255,.08),inset 0 2px 8px rgba(0,0,0,.3);--tw-shadow-colored: 0 0 8px var(--tw-shadow-color), inset 0 2px 8px var(--tw-shadow-color);box-shadow:var(--tw-ring-offset-shadow, 0 0 #0000),var(--tw-ring-shadow, 0 0 #0000),var(--tw-shadow)}.focus\:outline-none:focus{outline:2px solid transparent;outline-offset:2px}.focus\:ring-1:focus{--tw-ring-offset-shadow: var(--tw-ring-inset) 0 0 0 var(--tw-ring-offset-width) var(--tw-ring-offset-color);--tw-ring-shadow: var(--tw-ring-inset) 0 0 0 calc(1px + var(--tw-ring-offset-width)) var(--tw-ring-color);box-shadow:var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow, 0 0 #0000)}.focus\:ring-white\/10:focus{--tw-ring-color: rgb(255 255 255 / .1)}.active\:scale-95:active{--tw-scale-x: .95;--tw-scale-y: .95;transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skew(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y))}.active\:shadow-\[0_0_15px_rgba\(6\,182\,212\,0\.4\)\,0_2px_8px_rgba\(0\,0\,0\,0\.4\)\]:active{--tw-shadow: 0 0 15px rgba(6,182,212,.4),0 2px 8px rgba(0,0,0,.4);--tw-shadow-colored: 0 0 15px var(--tw-shadow-color), 0 2px 8px var(--tw-shadow-color);box-shadow:var(--tw-ring-offset-shadow, 0 0 #0000),var(--tw-ring-shadow, 0 0 #0000),var(--tw-shadow)}.group:hover .group-hover\:text-cyan-400{--tw-text-opacity: 1;color:rgb(34 211 238 / var(--tw-text-opacity, 1))}.group:hover .group-hover\:drop-shadow-\[0_0_6px_rgba\(6\,182\,212\,0\.5\)\]{--tw-drop-shadow: drop-shadow(0 0 6px rgba(6,182,212,.5));filter:var(--tw-blur) var(--tw-brightness) var(--tw-contrast) var(--tw-grayscale) var(--tw-hue-rotate) var(--tw-invert) var(--tw-saturate) var(--tw-sepia) var(--tw-drop-shadow)}@media (min-width: 640px){.sm\:block{display:block}.sm\:h-16{height:4rem}.sm\:h-2\.5{height:.625rem}.sm\:w-16{width:4rem}.sm\:w-2\.5{width:.625rem}.sm\:w-\[74\%\]{width:74%}.sm\:w-\[calc\(74\%-3rem\)\]{width:calc(74% - 3rem)}.sm\:gap-4{gap:1rem}.sm\:px-5{padding-left:1.25rem;padding-right:1.25rem}.sm\:px-6{padding-left:1.5rem;padding-right:1.5rem}.sm\:py-2\.5{padding-top:.625rem;padding-bottom:.625rem}.sm\:text-2xl{font-size:1.5rem;line-height:2rem}.sm\:text-\[11px\]{font-size:11px}.sm\:text-base{font-size:1rem;line-height:1.5rem}.sm\:text-lg{font-size:1.125rem;line-height:1.75rem}.sm\:text-xs{font-size:.75rem;line-height:1rem}}@media (min-width: 768px){.md\:inline{display:inline}}@media (min-width: 1024px){.lg\:ml-3{margin-left:.75rem}.lg\:flex{display:flex}.lg\:hidden{display:none}.lg\:h-20{height:5rem}.lg\:w-20{width:5rem}.lg\:w-\[520px\]{width:520px}.lg\:px-8{padding-left:2rem;padding-right:2rem}.lg\:text-3xl{font-size:1.875rem;line-height:2.25rem}.lg\:text-lg{font-size:1.125rem;line-height:1.75rem}.lg\:text-xl{font-size:1.25rem;line-height:1.75rem}}@media (min-width: 1280px){.xl\:flex{display:flex}.xl\:w-72{width:18rem}.xl\:w-\[560px\]{width:560px}}@media (min-width: 1536px){.\32xl\:w-80{width:20rem}.\32xl\:w-\[640px\]{width:640px}}
diff --git a/galaxy/webui/frontend/dist/assets/index-BVxn_-SZ.js b/galaxy/webui/frontend/dist/assets/index-BVxn_-SZ.js
deleted file mode 100644
index ab77b0a89..000000000
--- a/galaxy/webui/frontend/dist/assets/index-BVxn_-SZ.js
+++ /dev/null
@@ -1,339 +0,0 @@
-var Gb=Object.defineProperty;var Xb=(e,t,n)=>t in e?Gb(e,t,{enumerable:!0,configurable:!0,writable:!0,value:n}):e[t]=n;var Cn=(e,t,n)=>Xb(e,typeof t!="symbol"?t+"":t,n);(function(){const t=document.createElement("link").relList;if(t&&t.supports&&t.supports("modulepreload"))return;for(const i of document.querySelectorAll('link[rel="modulepreload"]'))r(i);new MutationObserver(i=>{for(const s of i)if(s.type==="childList")for(const o of s.addedNodes)o.tagName==="LINK"&&o.rel==="modulepreload"&&r(o)}).observe(document,{childList:!0,subtree:!0});function n(i){const s={};return i.integrity&&(s.integrity=i.integrity),i.referrerPolicy&&(s.referrerPolicy=i.referrerPolicy),i.crossOrigin==="use-credentials"?s.credentials="include":i.crossOrigin==="anonymous"?s.credentials="omit":s.credentials="same-origin",s}function r(i){if(i.ep)return;i.ep=!0;const s=n(i);fetch(i.href,s)}})();var Qa=typeof globalThis<"u"?globalThis:typeof window<"u"?window:typeof global<"u"?global:typeof self<"u"?self:{};function Bl(e){return e&&e.__esModule&&Object.prototype.hasOwnProperty.call(e,"default")?e.default:e}var Gy={exports:{}},Hl={},Xy={exports:{}},ce={};/**
- * @license React
- * react.production.min.js
- *
- * Copyright (c) Facebook, Inc. and its affiliates.
- *
- * This source code is licensed under the MIT license found in the
- * LICENSE file in the root directory of this source tree.
- */var jo=Symbol.for("react.element"),Qb=Symbol.for("react.portal"),Zb=Symbol.for("react.fragment"),Jb=Symbol.for("react.strict_mode"),eS=Symbol.for("react.profiler"),tS=Symbol.for("react.provider"),nS=Symbol.for("react.context"),rS=Symbol.for("react.forward_ref"),iS=Symbol.for("react.suspense"),sS=Symbol.for("react.memo"),oS=Symbol.for("react.lazy"),Rp=Symbol.iterator;function aS(e){return e===null||typeof e!="object"?null:(e=Rp&&e[Rp]||e["@@iterator"],typeof e=="function"?e:null)}var Qy={isMounted:function(){return!1},enqueueForceUpdate:function(){},enqueueReplaceState:function(){},enqueueSetState:function(){}},Zy=Object.assign,Jy={};function ls(e,t,n){this.props=e,this.context=t,this.refs=Jy,this.updater=n||Qy}ls.prototype.isReactComponent={};ls.prototype.setState=function(e,t){if(typeof e!="object"&&typeof e!="function"&&e!=null)throw Error("setState(...): takes an object of state variables to update or a function which returns an object of state variables.");this.updater.enqueueSetState(this,e,t,"setState")};ls.prototype.forceUpdate=function(e){this.updater.enqueueForceUpdate(this,e,"forceUpdate")};function ex(){}ex.prototype=ls.prototype;function Md(e,t,n){this.props=e,this.context=t,this.refs=Jy,this.updater=n||Qy}var Dd=Md.prototype=new ex;Dd.constructor=Md;Zy(Dd,ls.prototype);Dd.isPureReactComponent=!0;var zp=Array.isArray,tx=Object.prototype.hasOwnProperty,Id={current:null},nx={key:!0,ref:!0,__self:!0,__source:!0};function rx(e,t,n){var r,i={},s=null,o=null;if(t!=null)for(r in t.ref!==void 0&&(o=t.ref),t.key!==void 0&&(s=""+t.key),t)tx.call(t,r)&&!nx.hasOwnProperty(r)&&(i[r]=t[r]);var a=arguments.length-2;if(a===1)i.children=n;else if(1>>1,B=R[F];if(0>>1;Ftypeof e=="number"?0:e;function VM(e){const t=ck(e);return fk(e)(t.map(OM))}const br={test:FM,parse:ck,createTransformer:fk,getAnimatableNone:VM},dk=(e,t)=>n=>`${n>0?t:e}`;function hk(e,t){return typeof e=="number"?n=>Pe(e,t,n):ct.test(e)?ok(e,t):e.startsWith("var(")?dk(e,t):mk(e,t)}const pk=(e,t)=>{const n=[...e],r=n.length,i=e.map((s,o)=>hk(s,t[o]));return s=>{for(let o=0;ot[0];e[0]>e[s-1]&&(e=[...e].reverse(),t=[...t].reverse());const o=HM(t,r,i),a=o.length,l=u=>{let c=0;if(a>1)for(;ctypeof e=="number"?0:e;function $j(e){const t=fk(e);return dk(e)(t.map(Vj))}const br={test:Oj,parse:fk,createTransformer:dk,getAnimatableNone:$j},hk=(e,t)=>n=>`${n>0?t:e}`;function pk(e,t){return typeof e=="number"?n=>Pe(e,t,n):ct.test(e)?ak(e,t):e.startsWith("var(")?hk(e,t):gk(e,t)}const mk=(e,t)=>{const n=[...e],r=n.length,i=e.map((s,o)=>pk(s,t[o]));return s=>{for(let o=0;ot[0];e[0]>e[s-1]&&(e=[...e].reverse(),t=[...t].reverse());const o=Uj(t,r,i),a=o.length,l=u=>{let c=0;if(a>1)for(;c