diff --git a/docs/swarm/README.md b/docs/swarm/README.md index 8dbd3f25..9c233eb6 100644 --- a/docs/swarm/README.md +++ b/docs/swarm/README.md @@ -456,6 +456,18 @@ Swarm-side, `GET /api/agents//state/stream` relays that subject as SSE, resolving the agent's hive at request time exactly as the terminal stream does. The payload passes through opaquely — the controller never parses a header. +The third thing an agent publishes is its **icon**, the same SVG its own +`GET /icon` serves. It goes into the `agent-icons` KV bucket under the key +``, with no hive in it, so the swarm can show the icon of an agent that's +stopped or has moved hives. Only an agent connected with its own queue +credential publishes it: the queue grants that credential +`$KV.agent-icons.` and no other key, and grants the hive's shared client +none of the bucket. The harness writes once per start, because a config change +reaches an agent by restarting its container. An agent with no icon deletes its +key. Swarm-side, `GET /api/agents//icon` serves the stored bytes, and 404 +means the agent has no icon. swarm-ui's agent cards load it as an `` and show the +dimmed hyperhive mark for an agent without one, as the hive dashboard does. + ### Swarm-wide forge objects The controller also keeps the forge objects that are one per swarm, not diff --git a/frontend/packages/swarm-ui/src/pages/agents/AgentCard.css b/frontend/packages/swarm-ui/src/pages/agents/AgentCard.css index 90a81e33..e548aa9f 100644 --- a/frontend/packages/swarm-ui/src/pages/agents/AgentCard.css +++ b/frontend/packages/swarm-ui/src/pages/agents/AgentCard.css @@ -22,3 +22,37 @@ white-space: normal; word-break: break-word; } +/* Icon beside the name/message, sized and dimmed like the hive + dashboard's `.container-icon` (dashboard.css). */ +.ui-agent-card-row { + display: flex; + align-items: flex-start; + gap: 0.7em; +} +.ui-agent-card-body { + display: flex; + flex-direction: column; + gap: 0.35em; + flex: 1; + min-width: 0; +} +.ui-agent-card-icon { + position: relative; + overflow: hidden; + flex: none; + width: 5em; + aspect-ratio: 1; + border-radius: 6px; + background-color: color-mix(in srgb, var(--crust) 60%, transparent); +} +.ui-agent-card-icon > img { + position: absolute; + inset: 0; + width: 100%; + height: 100%; + object-fit: contain; +} +.ui-agent-card-icon-none { + filter: grayscale(1); + opacity: 0.4; +} diff --git a/frontend/packages/swarm-ui/src/pages/agents/AgentCard.tsx b/frontend/packages/swarm-ui/src/pages/agents/AgentCard.tsx index e1ea8803..894b7c44 100644 --- a/frontend/packages/swarm-ui/src/pages/agents/AgentCard.tsx +++ b/frontend/packages/swarm-ui/src/pages/agents/AgentCard.tsx @@ -12,6 +12,7 @@ // `WantedMenu` below keeps its own clicks (mouse or keyboard-synthesized, // `WantedMenu` hosts real `