From 3e87980bc78e12af390ae92bc09dc85dfb6f5ae9 Mon Sep 17 00:00:00 2001 From: Nitay Rabinovich Date: Wed, 13 May 2026 14:38:15 +0200 Subject: [PATCH] add visible topology agent linking --- apps/web/src/components/NarrowFallback.tsx | 48 ++++++++++- apps/web/src/components/NodeCard.tsx | 35 +++++++- apps/web/src/components/TopologyCanvas.tsx | 99 ++++++++++++++++------ apps/web/src/styles.css | 36 +++++++- apps/web/test/app.test.tsx | 92 +++++++++++++++++++- 5 files changed, 274 insertions(+), 36 deletions(-) diff --git a/apps/web/src/components/NarrowFallback.tsx b/apps/web/src/components/NarrowFallback.tsx index eb40f22..f9cb64f 100644 --- a/apps/web/src/components/NarrowFallback.tsx +++ b/apps/web/src/components/NarrowFallback.tsx @@ -1,18 +1,43 @@ +import { useMemo, useState } from "react"; import type { TopologySnapshot } from "@amesh/protocol"; +import { ArrowRight } from "lucide-react"; +import { createTriggerRule } from "../api.js"; import { relativeTime } from "../lib/time.js"; import { NodeSettingsButton } from "./NodeSettingsButton.js"; type Props = { topology: TopologySnapshot }; export function NarrowFallback({ topology }: Props) { - const agentsById = new Map(topology.agents.map((agent) => [agent.id, agent])); + const [connectionSourceAgentId, setConnectionSourceAgentId] = useState(null); + const agentsById = useMemo( + () => new Map(topology.agents.map((agent) => [agent.id, agent])), + [topology.agents] + ); + const connectionSourceAgentName = + connectionSourceAgentId ? agentsById.get(connectionSourceAgentId)?.name ?? null : null; + + async function pickConnectionEndpoint(agentId: string) { + if (!connectionSourceAgentId) { + setConnectionSourceAgentId(agentId); + return; + } + if (connectionSourceAgentId === agentId) { + setConnectionSourceAgentId(null); + return; + } + await createTriggerRule({ + sourceAgentId: connectionSourceAgentId, + targetAgentId: agentId, + mode: "allow" + }); + setConnectionSourceAgentId(null); + } return (
- Drag-to-connect needs a wider screen. Open amesh on a desktop (≥1024px) to add or remove - rules on the canvas. This view is read-only. + Compact topology view. Use the arrow controls on online agents to create allow rules.
{topology.nodes.length === 0 ? ( @@ -49,6 +74,23 @@ export function NarrowFallback({ topology }: Props) { {agents.map((agent) => (
  • {agent.name} ({agent.status}) + {" "} + {typeof agent.capabilities.cwd === "string" ? ( <> {" "} diff --git a/apps/web/src/components/NodeCard.tsx b/apps/web/src/components/NodeCard.tsx index a72642f..f8953f5 100644 --- a/apps/web/src/components/NodeCard.tsx +++ b/apps/web/src/components/NodeCard.tsx @@ -1,5 +1,6 @@ import { Handle, Position } from "@xyflow/react"; import { useNavigate } from "@tanstack/react-router"; +import { ArrowRight } from "lucide-react"; import type { AgentRecord, AgentStatus, NodeRecord, NodeStatus } from "@amesh/protocol"; import { relativeTime } from "../lib/time.js"; @@ -8,6 +9,9 @@ import { NodeSettingsButton } from "./NodeSettingsButton.js"; export type NodeCardData = { node: NodeRecord; agents: AgentRecord[]; + connectionSourceAgentId: string | null; + connectionSourceAgentName: string | null; + onConnectionPick: (agent: AgentRecord) => void; }; type NodeCardProps = { @@ -29,7 +33,13 @@ function agentStatusLabel(status: AgentStatus) { } export function NodeCard({ data }: NodeCardProps) { - const { node, agents } = data.data; + const { + node, + agents, + connectionSourceAgentId, + connectionSourceAgentName, + onConnectionPick + } = data.data; const isOffline = node.status === "offline"; const navigate = useNavigate(); @@ -62,6 +72,13 @@ export function NodeCard({ data }: NodeCardProps) {
      {agents.map((agent) => { const chatDisabled = isOffline || agent.status !== "online"; + const connectionSelected = connectionSourceAgentId === agent.id; + const connectionDisabled = isOffline || agent.status !== "online"; + const connectionLabel = connectionSourceAgentId + ? connectionSelected + ? `Cancel connection from ${agent.name}` + : `Connect ${connectionSourceAgentName ?? "selected agent"} to ${agent.name}` + : `Start connection from ${agent.name}`; return (
    • @@ -72,6 +89,22 @@ export function NodeCard({ data }: NodeCardProps) { ) : null}
      +