// Page 3 — Team
function TeamPage({ alerts, pushToast }) {
const [expanded, setExpanded] = useState(() => new Set(["kostya"]));
const [confirmRestart, setConfirmRestart] = useState(null);
const alertsByAgent = useMemo(() => {
const m = {};
alerts.forEach(a => {
if (a.status === "active" && a.agent) (m[a.agent] = m[a.agent] || []).push(a);
});
return m;
}, [alerts]);
const toggle = (id) => {
setExpanded((prev) => {
const n = new Set(prev);
n.has(id) ? n.delete(id) : n.add(id);
return n;
});
};
return (
Команда
{TEAM.length} агентов · {TEAM.filter(t => t.watcherOnline).length} watcher'ов онлайн
0 ? "1fr" : "1fr 1fr", gap: 16 }}>
{/* Render rows; expanded ones go full width */}
setConfirmRestart(a)}
/>
setConfirmRestart(null)}
onConfirm={() => {
pushToast(`Watcher ${confirmRestart.name} перезапускается…`, "warn");
setConfirmRestart(null);
}}
/>
);
}
function TeamGrid({ team, expanded, toggle, alertsByAgent, onRestart }) {
return (
{team.map((a) => (
expanded.has(a.id) ? (
toggle(a.id)}
onRestart={() => onRestart(a)}
/>
) : (
toggle(a.id)} />
)
))}
);
}
function AgentRow({ agent, alerts, onExpand }) {
const health = agent.health;
const bar =
health === "crit" ? "var(--crit)" :
health === "warn" ? "var(--warn)" : "var(--ok)";
return (
{agent.name}
{agent.roleLabel}
{agent.currentTask ? (
PB-{agent.currentTask.seq}
{agent.currentTask.title}
) : (
— свободен —
)}
{agent.modulesCurrent.map(m => )}
context
= 70 ? "var(--ok)" : agent.contextPct >= 30 ? "var(--warn)" : "var(--crit)" }}>{agent.contextPct}%
watcher
{agent.watcherOnline ? {Math.floor(agent.watcherUptimeMin/60)}ч {agent.watcherUptimeMin%60}м : offline}
{alerts.length > 0 && ⚠ {alerts.length} алерт{alerts.length === 1 ? "" : "а"}}
›
);
}
function AgentBigCard({ agent, alerts, onCollapse, onRestart }) {
const health = agent.health;
const accent =
health === "crit" ? "var(--crit)" :
health === "warn" ? "var(--warn)" : "var(--ok)";
// Mock comments
const comments = [
{ ts: "2026-05-17T14:28:04Z", text: "progress: правлю Footer.tsx, осталось header" },
{ ts: "2026-05-17T14:18:30Z", text: "взял задачу, начинаю с поиска вхождений Print-X" },
{ ts: "2026-05-17T13:45:11Z", text: "вернулся к PB-47, всё ещё тыкаю интеграцию FastAPI" },
];
return (
{/* Header bar with health stripe */}
{agent.name}
{agent.roleLabel}
{agent.email}
watcher {agent.watcherOnline ? "online" : "offline"}
{agent.watcherOnline && (
<>
PID: {agent.watcherPid}
uptime: {Math.floor(agent.watcherUptimeMin/60)}ч {agent.watcherUptimeMin%60}м
heartbeat: {agent.lastHeartbeatMin.toFixed(1)}с назад
>
)}
{!agent.watcherOnline && (
последний heartbeat: {Math.round(agent.lastHeartbeatMin)} мин назад
)}
{/* Body — 3 columns */}
{/* Column 1 — current task & comments */}
Сейчас занимается
{agent.currentTask ? (
<>
PB-{agent.currentTask.seq}
{agent.currentTask.title}
Последние комментарии в Plane
{comments.slice(0, 3).map((c, i) => (
))}
>
) : (
— свободен — ждёт следующей задачи
)}
{alerts.length > 0 && (
Активные алерты
{alerts.map(al => (
))}
)}
{/* Column 2 — context + tool calls */}
Контекст
За день
= 70 ? "var(--ok)" : agent.contextPct >= 30 ? "var(--warn)" : "var(--crit)"} />
с {agent.contextHistory[0]}% до {agent.contextHistory[agent.contextHistory.length - 1]}%
Среднее tool-calls на задачу
{agent.avgToolCalls}
{agent.avgToolCalls < 15 ? "лёгкие задачи" : agent.avgToolCalls < 25 ? "средняя сложность" : "тяжёлые задачи / debugging"}
{/* Column 3 — expertise */}
Сейчас в работе
{agent.modulesCurrent.map(m => )}
Историческая экспертиза
{agent.modulesHistorical.map((h) => (
{h.lastSeen}
))}
);
}
window.TeamPage = TeamPage;