Upgraded Agent Node

This commit is contained in:
Nicole Rappe 2025-05-30 05:20:06 -06:00
parent a999dae19c
commit 94d6359f91

View File

@ -2,6 +2,7 @@
import React, { useEffect, useState, useCallback, useMemo, useRef } from "react"; import React, { useEffect, useState, useCallback, useMemo, useRef } from "react";
import { Handle, Position, useReactFlow, useStore } from "reactflow"; import { Handle, Position, useReactFlow, useStore } from "reactflow";
// Modern Node: Borealis Agent (Sidebar Config Enabled)
const BorealisAgentNode = ({ id, data }) => { const BorealisAgentNode = ({ id, data }) => {
const { getNodes, setNodes } = useReactFlow(); const { getNodes, setNodes } = useReactFlow();
const edges = useStore((state) => state.edges); const edges = useStore((state) => state.edges);
@ -10,7 +11,7 @@ const BorealisAgentNode = ({ id, data }) => {
const [isConnected, setIsConnected] = useState(false); const [isConnected, setIsConnected] = useState(false);
const prevRolesRef = useRef([]); const prevRolesRef = useRef([]);
// ---------------- Agent List & Sorting ---------------- // Agent List Sorted (Online First)
const agentList = useMemo(() => { const agentList = useMemo(() => {
if (!agents || typeof agents !== "object") return []; if (!agents || typeof agents !== "object") return [];
return Object.entries(agents) return Object.entries(agents)
@ -23,7 +24,7 @@ const BorealisAgentNode = ({ id, data }) => {
.sort((a, b) => b.last_seen - a.last_seen); .sort((a, b) => b.last_seen - a.last_seen);
}, [agents]); }, [agents]);
// ---------------- Periodic Agent Fetching ---------------- // Fetch Agents Periodically
useEffect(() => { useEffect(() => {
const fetchAgents = () => { const fetchAgents = () => {
fetch("/api/agents") fetch("/api/agents")
@ -36,7 +37,7 @@ const BorealisAgentNode = ({ id, data }) => {
return () => clearInterval(interval); return () => clearInterval(interval);
}, []); }, []);
// ---------------- Node Data Sync ---------------- // Sync node data with sidebar changes
useEffect(() => { useEffect(() => {
setNodes((nds) => setNodes((nds) =>
nds.map((n) => nds.map((n) =>
@ -44,9 +45,9 @@ const BorealisAgentNode = ({ id, data }) => {
) )
); );
setIsConnected(false); setIsConnected(false);
}, [selectedAgent]); }, [selectedAgent, setNodes, id]);
// ---------------- Attached Role Collection ---------------- // Attached Roles logic
const attachedRoleIds = useMemo( const attachedRoleIds = useMemo(
() => () =>
edges edges
@ -54,7 +55,6 @@ const BorealisAgentNode = ({ id, data }) => {
.map((e) => e.target), .map((e) => e.target),
[edges, id] [edges, id]
); );
const getAttachedRoles = useCallback(() => { const getAttachedRoles = useCallback(() => {
const allNodes = getNodes(); const allNodes = getNodes();
return attachedRoleIds return attachedRoleIds
@ -65,9 +65,9 @@ const BorealisAgentNode = ({ id, data }) => {
.filter((r) => r); .filter((r) => r);
}, [attachedRoleIds, getNodes]); }, [attachedRoleIds, getNodes]);
// ---------------- Provision Role Logic ---------------- // Provision Roles to Agent
const provisionRoles = useCallback((roles) => { const provisionRoles = useCallback((roles) => {
if (!selectedAgent) return; // Allow empty roles but require agent if (!selectedAgent) return;
fetch("/api/agent/provision", { fetch("/api/agent/provision", {
method: "POST", method: "POST",
headers: { "Content-Type": "application/json" }, headers: { "Content-Type": "application/json" },
@ -79,12 +79,10 @@ const BorealisAgentNode = ({ id, data }) => {
}) })
.catch(() => {}); .catch(() => {});
}, [selectedAgent]); }, [selectedAgent]);
const handleConnect = useCallback(() => { const handleConnect = useCallback(() => {
const roles = getAttachedRoles(); const roles = getAttachedRoles();
provisionRoles(roles); // Always call even with empty roles provisionRoles(roles);
}, [getAttachedRoles, provisionRoles]); }, [getAttachedRoles, provisionRoles]);
const handleDisconnect = useCallback(() => { const handleDisconnect = useCallback(() => {
if (!selectedAgent) return; if (!selectedAgent) return;
fetch("/api/agent/provision", { fetch("/api/agent/provision", {
@ -99,7 +97,7 @@ const BorealisAgentNode = ({ id, data }) => {
.catch(() => {}); .catch(() => {});
}, [selectedAgent]); }, [selectedAgent]);
// ---------------- Auto-Provision When Roles Change ---------------- // Auto-provision on role change
useEffect(() => { useEffect(() => {
const newRoles = getAttachedRoles(); const newRoles = getAttachedRoles();
const prevSerialized = JSON.stringify(prevRolesRef.current || []); const prevSerialized = JSON.stringify(prevRolesRef.current || []);
@ -109,7 +107,7 @@ const BorealisAgentNode = ({ id, data }) => {
} }
}, [attachedRoleIds, isConnected, getAttachedRoles, provisionRoles]); }, [attachedRoleIds, isConnected, getAttachedRoles, provisionRoles]);
// ---------------- Status Label ---------------- // Status Label
const selectedAgentStatus = useMemo(() => { const selectedAgentStatus = useMemo(() => {
if (!selectedAgent) return "Unassigned"; if (!selectedAgent) return "Unassigned";
const agent = agents[selectedAgent]; const agent = agents[selectedAgent];
@ -117,7 +115,7 @@ const BorealisAgentNode = ({ id, data }) => {
return agent.status === "provisioned" ? "Connected" : "Available"; return agent.status === "provisioned" ? "Connected" : "Available";
}, [agents, selectedAgent]); }, [agents, selectedAgent]);
// ---------------- Render ---------------- // Render (Sidebar handles config)
return ( return (
<div className="borealis-node"> <div className="borealis-node">
<Handle <Handle
@ -173,16 +171,51 @@ const BorealisAgentNode = ({ id, data }) => {
); );
}; };
// Node Registration Object with sidebar config and docs
export default { export default {
type: "Borealis_Agent", type: "Borealis_Agent",
label: "Borealis Agent", label: "Borealis Agent",
description: ` description: `
Main Agent Node Select and connect to a remote Borealis Agent.
- Assign roles to agent dynamically by connecting "Agent Role" nodes.
- Selects an available agent - Auto-provisions agent as role assignments change.
- Connect/disconnects via button - See live agent status and re-connect/disconnect easily.
- Auto-updates roles when attached roles change
`.trim(), `.trim(),
content: "Select and manage an Agent with dynamic roles", content: "Select and manage an Agent with dynamic roles",
component: BorealisAgentNode, component: BorealisAgentNode,
config: [
{
key: "agent_id",
label: "Agent",
type: "text", // NOTE: UI populates via agent fetch, but config drives default for sidebar.
defaultValue: ""
}
],
usage_documentation: `
### Borealis Agent Node
This node represents an available Borealis Agent (Python client) you can control from your workflow.
#### Features
- **Select** an agent from the list of online agents.
- **Connect/Disconnect** from the agent at any time.
- **Attach roles** (by connecting "Agent Role" nodes to this node's output handle) to assign behaviors dynamically.
- **Live status** shows if the agent is available, connected, or offline.
#### How to Use
1. **Drag in a Borealis Agent node.**
2. **Pick an agent** from the dropdown list (auto-populates from backend).
3. **Click "Connect to Agent"** to provision it for the workflow.
4. **Attach Agent Role Nodes** (e.g., Screenshot, Macro Keypress) to the "provisioner" output handle to define what the agent should do.
5. Agent will automatically update its roles as you change connected Role Nodes.
#### Output Handle
- "provisioner" (bottom): Connect Agent Role nodes here.
#### Good to Know
- If an agent disconnects or goes offline, its status will show "Reconnecting..." until it returns.
- Node config can be edited in the right sidebar.
- **Roles update LIVE**: Any time you change attached roles, the agent gets updated instantly.
`.trim()
}; };