AkurAI Build
Menu

popagent

public

Latest change c900eb46afe65e3e2c2b205bb0d05e6587af4306 - feat: present agents as a portfolio by AkurAI Build

import { useCallback, useEffect, useRef, useState } from "react";
import { Activity, Bot, Brain, Building2, Clock3, Globe, Hash, KeyRound, Mail, Moon, Palette, Plus, Settings2, SlidersHorizontal, Sparkles, Sun, Trash2, X } from "lucide-react";
import { apiFetch } from "./api";
import { useFocusTrap } from "./use-focus-trap";
import {
  AGENT_TOOL_NAMES,
  MAX_AGENT_INSTRUCTIONS_CHARACTERS,
  runtimeModelId,
  type AgentListResponse,
  type AgentBrowserAccess,
  type AgentRuntimeSettings,
  type ModelSource,
  type AgentWorkspace,
  type AgentSchedule,
  type AgentSettings,
  type AgentSkill,
  type AgentSkillListResponse,
  type AgentToolName,
  type AgentTaskSource,
  type BrowserProfile,
  type BrowserSession,
  type BrowserSettings,
  type ModelCatalogResponse,
  type ScheduleListResponse,
  type AgentWorkspaceAccess,
} from "../api-types";
import { describeSchedule, scheduleCron, WEEKDAYS, type ScheduleFrequency } from "./schedules";
import { APPEARANCE_THEMES, preferredTheme, saveTheme, type AppearanceTheme } from "./appearance";
import type { SettingsSection } from "./routes";
import { ObservabilityPage } from "./ObservabilityPage";
import { ChannelSettingsPanel } from "./ChannelSettingsPanel";
import { MemorySettingsPanel } from "./MemorySettingsPanel";
import { SecretSettingsPanel } from "./SecretSettingsPanel";
import { AutonomySettingsPanel } from "./AutonomySettingsPanel";
import { CompanyRosterPanel } from "./CompanyRosterPanel";
import { executionAgentPeople } from "../company-roster";

function RuntimeNumberField({
  label,
  value,
  min,
  max,
  step = 1,
  onChange,
}: {
  label: string;
  value: number;
  min: number;
  max: number;
  step?: number;
  onChange: (value: number) => void;
}) {
  return <label className="text-sm font-medium">{label}<input aria-label={label} type="number" min={min} max={max} step={step} value={value} onChange={(event) => onChange(event.currentTarget.valueAsNumber)} className="mt-2 min-h-11 w-full rounded border border-line bg-bg px-3 text-ink outline-none focus:border-accent/50" /></label>;
}
export function RuntimeModelSource({
  value,
  onChange,
}: {
  value: ModelSource;
  onChange: (value: ModelSource) => void;
}) {
  return <label className="text-sm font-medium">Model source
    <select aria-label="Model source" value={value} onChange={(event) => onChange(event.currentTarget.value as ModelSource)} className="mt-2 min-h-11 w-full rounded border border-line bg-bg px-3 text-ink outline-none focus:border-accent/50">
      <option value="configured">Configured model</option>
      <option value="local">Titan local 9B</option>
      <option value="openrouter-free">OpenRouter free router</option>
    </select>
  </label>;
}

export function AgentWorkSource({ source }: { source: AgentTaskSource }) {
  const label = source === "build-maintenance"
    ? "build-maintenance"
    : source === "self-update"
      ? "self-update"
      : "user";
  return <span> · source {label}</span>;
}

export function AgentPortfolioCard({
  agent,
  selected,
  onChoose,
}: {
  agent: AgentSettings;
  selected: boolean;
  onChoose: () => void;
}) {
  const person = executionAgentPeople.get(agent.id);
  const status = agent.id === "orchistrator"
    ? "Orchestration lead"
    : agent.delegationEnabled ? "Available" : "Unavailable";
  return <button
    type="button"
    aria-pressed={selected}
    onClick={onChoose}
    className={`agent-flow-node ${selected ? "is-selected" : ""} ${agent.id !== "orchistrator" && !agent.delegationEnabled ? "is-disabled" : ""}`}
  >
    {person ? <img src={person.avatarUrl} alt="" className="size-24 shrink-0 rounded-full border border-line object-cover" /> : <span className="flex size-24 shrink-0 items-center justify-center rounded-full border border-line bg-panel-2"><Bot className="size-8 text-ink-dim" /></span>}
    <span className="min-w-0 flex-1 text-left">
      <span className="agent-flow-kicker">{person?.department ?? "Agent"}</span>
      <span className="agent-flow-name">{person?.name ?? agent.name}</span>
      <span className="agent-flow-title">{person?.title ?? agent.name}</span>
      <span className="agent-flow-description">{agent.description}</span>
    </span>
    <span className="agent-flow-status">{status}</span>
  </button>;
}

export function AgentIdentityCard({
  agentId,
  model,
  delegationEnabled,
}: {
  agentId: string;
  model: string | null;
  delegationEnabled: boolean;
}) {
  const person = executionAgentPeople.get(agentId);
  if (!person) return null;
  return <div className="mb-5 grid gap-5 border-b border-line pb-5 sm:grid-cols-[8rem_1fr_auto] sm:items-center">
    <img src={person.avatarUrl} alt="" className="size-28 rounded-full border border-line object-cover sm:size-32" />
    <div className="min-w-0">
      <p className="text-2xl font-semibold tracking-tight">{person.name}</p>
      <p className="mt-1 text-base text-ink-muted">{person.title}</p>
      <div className="mt-3 flex flex-wrap gap-x-4 gap-y-1 text-xs text-ink-dim">
        <span>{person.department}</span>
        <a href={`mailto:${person.email}`} className="inline-flex items-center gap-1 text-accent hover:underline"><Mail className="size-3" />{person.email}</a>
      </div>
    </div>
    <div className="grid gap-1 text-left text-xs sm:text-right">
      <span className="text-ink-dim">Execution profile</span>
      <span className="max-w-64 truncate font-mono text-ink-muted">{model ?? "Request-selected model"}</span>
      <span className={delegationEnabled || agentId === "orchistrator" ? "text-emerald-500" : "text-ink-dim"}>
        {agentId === "orchistrator" ? "Orchestration lead" : delegationEnabled ? "Available for delegation" : "Delegation disabled"}
      </span>
    </div>
  </div>;
}

export function AgentSettingsPage({
  open,
  section,
  onSectionChange,
  onClose,
  workspaces,
  workspaceId,
}: {
  open: boolean;
  section: SettingsSection;
  onSectionChange: (section: SettingsSection) => void;
  onClose: () => void;
  workspaces: AgentWorkspace[];
  workspaceId?: string;
}) {
  const [theme, setTheme] = useState<AppearanceTheme>(preferredTheme);
  const [agents, setAgents] = useState<AgentSettings[]>([]);
  const [selectedId, setSelectedId] = useState<string>();
  const [agentName, setAgentName] = useState("");
  const [agentDescription, setAgentDescription] = useState("");
  const [instructions, setInstructions] = useState("");
  const [agentModel, setAgentModel] = useState<string | null>(null);
  const [agentWorkspaceAccess, setAgentWorkspaceAccess] = useState<AgentWorkspaceAccess>("none");
  const [agentBrowserAccess, setAgentBrowserAccess] = useState<AgentBrowserAccess>("none");
  const [agentDelegationEnabled, setAgentDelegationEnabled] = useState(false);
  const [agentTools, setAgentTools] = useState<AgentToolName[]>([]);
  const [agentPanel, setAgentPanel] = useState<"instructions" | "skills">("instructions");
  const [skills, setSkills] = useState<AgentSkill[]>([]);
  const [editingSkill, setEditingSkill] = useState<AgentSkill>();
  const [skillName, setSkillName] = useState("");
  const [skillDescription, setSkillDescription] = useState("");
  const [skillInstructions, setSkillInstructions] = useState("");
  const [skillReferences, setSkillReferences] = useState("{}");
  const [skillEnabled, setSkillEnabled] = useState(true);
  const [skillUserInvocable, setSkillUserInvocable] = useState(true);
  const [skillError, setSkillError] = useState("");
  const [browserSettings, setBrowserSettings] = useState<BrowserSettings>();
  const [browserSessions, setBrowserSessions] = useState<BrowserSession[]>([]);
  const [browserProfiles, setBrowserProfiles] = useState<BrowserProfile[]>([]);
  const [browserHealth, setBrowserHealth] = useState<{ provider: string; healthy: boolean; headless: boolean; activeSessions: number }>();
  const [browserRecordings, setBrowserRecordings] = useState<Array<{ name: string; size: number; createdAt: string }>>([]);
  const [profileName, setProfileName] = useState("");
  const [profileState, setProfileState] = useState("{\n  \"cookies\": [],\n  \"origins\": []\n}");
  const [profileError, setProfileError] = useState("");
  const [testingBrowser, setTestingBrowser] = useState(false);
  const [runtimeSettings, setRuntimeSettings] = useState<AgentRuntimeSettings>();
  const [runtimeModels, setRuntimeModels] = useState<string[]>([]);
  const [schedules, setSchedules] = useState<AgentSchedule[]>([]);
  const [scheduleWorkspaceId, setScheduleWorkspaceId] = useState(workspaceId ?? workspaces[0]?.id ?? "default");
  const [scheduleName, setScheduleName] = useState("");
  const [schedulePrompt, setSchedulePrompt] = useState("");
  const [scheduleFrequency, setScheduleFrequency] = useState<ScheduleFrequency>("daily");
  const [scheduleTime, setScheduleTime] = useState("09:00");
  const [scheduleWeekday, setScheduleWeekday] = useState("1");
  const [saving, setSaving] = useState(false);
  const [saved, setSaved] = useState(false);
  const closeRef = useRef<HTMLButtonElement>(null);
  const pageRef = useRef<HTMLDivElement>(null);
  const loadSkills = useCallback(async (agentId: string) => {
    try {
      const response = await apiFetch(`/api/agents/${agentId}/skills`);
      if (response.ok) setSkills(((await response.json()) as AgentSkillListResponse).skills);
    } catch {}
  }, []);
  const loadSchedules = useCallback(async (selectedWorkspaceId: string) => {
    try {
      const query = `?workspaceId=${encodeURIComponent(selectedWorkspaceId)}`;
      const response = await apiFetch(`/api/schedules${query}`);
      if (response.ok) setSchedules(((await response.json()) as ScheduleListResponse).schedules);
    } catch {}
  }, []);
  useEffect(() => {
    if (!open) return;
    const selectedWorkspaceId = workspaceId ?? workspaces[0]?.id ?? "default";
    setScheduleWorkspaceId(selectedWorkspaceId);
    void loadSchedules(selectedWorkspaceId);
  }, [loadSchedules, open, workspaceId, workspaces]);
  useEffect(() => {
    if (!open) return;
    void Promise.all([
      apiFetch("/api/agents"),
      apiFetch("/api/settings/browser"),
      apiFetch("/api/settings/agent-runtime"),
      apiFetch("/api/models"),
      apiFetch("/api/browser/sessions"),
      apiFetch("/api/browser/profiles"),
      apiFetch("/api/browser/recordings"),
      apiFetch("/api/browser/health"),
    ]).then(async ([agentResponse, browserResponse, runtimeResponse, modelResponse, browserSessionsResponse, browserProfilesResponse, browserRecordingsResponse, browserHealthResponse]) => {
      if (agentResponse.ok) {
        const loaded = ((await agentResponse.json()) as AgentListResponse).agents;
        const flowOrder: Record<string, number> = { orchistrator: 0, popagent: 0, researcher: 1, implementer: 2, reviewer: 3 };
        const ordered = [...loaded].sort((a, b) => (flowOrder[a.id] ?? 99) - (flowOrder[b.id] ?? 99));
        setAgents(ordered);
        if (ordered[0]) {
          setSelectedId(ordered[0].id);
          setAgentName(ordered[0].name);
          setAgentDescription(ordered[0].description);
          setInstructions(ordered[0].instructions);
          setAgentModel(ordered[0].model);
          setAgentWorkspaceAccess(ordered[0].workspaceAccess);
          setAgentBrowserAccess(ordered[0].browserAccess);
          setAgentDelegationEnabled(ordered[0].delegationEnabled);
          setAgentTools(ordered[0].tools);
          void loadSkills(ordered[0].id);
        }
      }
      if (browserResponse.ok) setBrowserSettings(await browserResponse.json() as BrowserSettings);
      if (browserSessionsResponse.ok) setBrowserSessions(((await browserSessionsResponse.json()) as { sessions: BrowserSession[] }).sessions);
      if (browserProfilesResponse.ok) setBrowserProfiles(((await browserProfilesResponse.json()) as { profiles: BrowserProfile[] }).profiles);
      if (browserRecordingsResponse.ok) setBrowserRecordings(((await browserRecordingsResponse.json()) as { recordings: Array<{ name: string; size: number; createdAt: string }> }).recordings);
      if (browserHealthResponse.ok) setBrowserHealth(await browserHealthResponse.json() as { provider: string; healthy: boolean; headless: boolean; activeSessions: number });
      if (runtimeResponse.ok) setRuntimeSettings(await runtimeResponse.json() as AgentRuntimeSettings);
      if (modelResponse.ok) setRuntimeModels(((await modelResponse.json()) as ModelCatalogResponse).models);
    }).catch(() => {});
  }, [loadSkills, open]);
  const selected = agents.find((item) => item.id === selectedId);
  const choose = (item: AgentSettings) => {
    setSelectedId(item.id);
    setAgentName(item.name);
    setAgentDescription(item.description);
    setInstructions(item.instructions);
    setAgentModel(item.model);
    setAgentWorkspaceAccess(item.workspaceAccess);
    setAgentBrowserAccess(item.browserAccess);
    setAgentDelegationEnabled(item.delegationEnabled);
    setAgentTools(item.tools);
    setSaved(false);
    void loadSkills(item.id);
  };
  const saveInstructions = async () => {
    if (!selected) return;
    setSaving(true);
    const response = await apiFetch(`/api/agents/${selected.id}`, {
      method: "PATCH",
      headers: { "content-type": "application/json" },
      body: JSON.stringify({
        name: agentName,
        description: agentDescription,
        instructions,
        model: agentModel,
        workspaceAccess: agentWorkspaceAccess,
        browserAccess: agentBrowserAccess,
        delegationEnabled: agentDelegationEnabled,
        tools: agentTools,
      }),
    });
    if (response.ok) {
      const updated = (await response.json()) as AgentSettings;
      setAgents((current) => current.map((item) => item.id === updated.id ? updated : item));
      setAgentName(updated.name);
      setAgentDescription(updated.description);
      setAgentWorkspaceAccess(updated.workspaceAccess);
      setAgentBrowserAccess(updated.browserAccess);
      setAgentDelegationEnabled(updated.delegationEnabled);
      setAgentTools(updated.tools);
      setSaved(true);
    }
    setSaving(false);
  };
  const toggleAgentTool = (tool: AgentToolName) => {
    setAgentTools((current) => current.includes(tool)
      ? current.filter((candidate) => candidate !== tool)
      : [...current, tool]);
    setSaved(false);
  };
  const editSkill = (skill?: AgentSkill) => {
    setEditingSkill(skill);
    setSkillName(skill?.name ?? "");
    setSkillDescription(skill?.description ?? "");
    setSkillInstructions(skill?.instructions ?? "");
    setSkillReferences(JSON.stringify(skill?.references ?? {}, null, 2));
    setSkillEnabled(skill?.enabled ?? true);
    setSkillUserInvocable(skill?.userInvocable ?? true);
    setSkillError("");
    setSaved(false);
  };
  const saveSkill = async () => {
    if (!selected) return;
    let references: Record<string, string>;
    try {
      const parsed: unknown = JSON.parse(skillReferences);
      if (!parsed || typeof parsed !== "object" || Array.isArray(parsed) || Object.values(parsed).some((value) => typeof value !== "string")) throw new Error();
      references = Object.fromEntries(Object.entries(parsed).map(([key, value]) => [key, String(value)]));
    } catch {
      setSkillError("References must be a JSON object whose values are strings.");
      return;
    }
    setSaving(true);
    setSaved(false);
    const response = await apiFetch(editingSkill ? `/api/agents/${selected.id}/skills/${editingSkill.id}` : `/api/agents/${selected.id}/skills`, {
      method: editingSkill ? "PATCH" : "POST",
      headers: { "content-type": "application/json" },
      body: JSON.stringify({ name: skillName, description: skillDescription, instructions: skillInstructions, references, enabled: skillEnabled, userInvocable: skillUserInvocable }),
    });
    if (response.ok) {
      await loadSkills(selected.id);
      setEditingSkill(await response.json() as AgentSkill);
      setSaved(true);
    } else {
      setSkillError("Unable to save skill. Names must be unique lowercase words separated by hyphens.");
    }
    setSaving(false);
  };
  const deleteSkill = async () => {
    if (!selected || !editingSkill || !confirm(`Delete the “${editingSkill.name}” skill?`)) return;
    const response = await apiFetch(`/api/agents/${selected.id}/skills/${editingSkill.id}`, { method: "DELETE" });
    if (response.ok) {
      await loadSkills(selected.id);
      editSkill();
    }
  };
  const saveBrowser = async () => {
    if (!browserSettings) return;
    setSaving(true);
    const { updatedAt, ...input } = browserSettings;
    void updatedAt;
    const response = await apiFetch("/api/settings/browser", {
      method: "PATCH",
      headers: { "content-type": "application/json" },
      body: JSON.stringify(input),
    });
    if (response.ok) {
      setBrowserSettings(await response.json() as BrowserSettings);
      setSaved(true);
    }
    setSaving(false);
  };
  const updateBrowser = <K extends keyof BrowserSettings>(key: K, value: BrowserSettings[K]) => {
    setBrowserSettings((current) => current ? { ...current, [key]: value } : current);
    setSaved(false);
  };
  const createBrowserProfile = async () => {
    setProfileError("");
    let state: unknown;
    try { state = JSON.parse(profileState); } catch { setProfileError("Storage state must be valid JSON."); return; }
    const response = await apiFetch("/api/browser/profiles", {
      method: "POST", headers: { "content-type": "application/json" },
      body: JSON.stringify({ name: profileName, enabled: browserProfiles.length === 0, state }),
    });
    if (!response.ok) { setProfileError("Unable to save profile. Use a Playwright storage-state object."); return; }
    const profile = await response.json() as BrowserProfile;
    setBrowserProfiles((current) => [profile, ...current]);
    setProfileName("");
    setSaved(true);
  };
  const downloadBrowserRecording = async (name: string) => {
    const response = await apiFetch(`/api/browser/recordings/${encodeURIComponent(name)}`);
    if (!response.ok) return;
    const url = URL.createObjectURL(await response.blob());
    const anchor = document.createElement("a");
    anchor.href = url;
    anchor.download = name;
    anchor.click();
    URL.revokeObjectURL(url);
  };
  const setActiveBrowserProfile = async (profile: BrowserProfile, enabled: boolean) => {
    const response = await apiFetch(`/api/browser/profiles/${profile.id}`, {
      method: "PATCH", headers: { "content-type": "application/json" },
      body: JSON.stringify({ name: profile.name, enabled }),
    });
    if (response.ok) {
      const updated = await response.json() as BrowserProfile;
      setBrowserProfiles((current) => current.map((item) => item.id === updated.id ? updated : { ...item, enabled: false }));
    }
  };
  const createSchedule = async () => {
    if (!scheduleName.trim() || !schedulePrompt.trim()) return;
    const cron = scheduleCron(scheduleFrequency, scheduleTime, scheduleWeekday);
    const response = await apiFetch("/api/schedules", { method: "POST", headers: { "content-type": "application/json" }, body: JSON.stringify({ name: scheduleName, prompt: schedulePrompt, cron, enabled: true, workspaceId: scheduleWorkspaceId }) });
    if (response.ok) { setScheduleName(""); setSchedulePrompt(""); await loadSchedules(scheduleWorkspaceId); }
  };
  const saveRuntime = async () => {
    if (!runtimeSettings) return;
    setSaving(true);
    const { updatedAt, ...input } = runtimeSettings;
    void updatedAt;
    const response = await apiFetch("/api/settings/agent-runtime", {
      method: "PATCH",
      headers: { "content-type": "application/json" },
      body: JSON.stringify(input),
    });
    if (response.ok) {
      setRuntimeSettings(await response.json() as AgentRuntimeSettings);
      setSaved(true);
    }
    setSaving(false);
  };
  const changeRuntime = <Key extends keyof AgentRuntimeSettings>(
    key: Key,
    value: AgentRuntimeSettings[Key],
  ) => {
    setRuntimeSettings((current) => current ? { ...current, [key]: value } : current);
    setSaved(false);
  };
  const changeModelSource = (source: ModelSource) => {
    setRuntimeSettings((current) => current
      ? {
        ...current,
        modelSource: source,
        defaultModel: runtimeModelId(current.defaultModel, source),
      }
      : current);
    setSaved(false);
  };
  const updateSchedule = async (schedule: AgentSchedule, changes: Pick<AgentSchedule, "cron" | "enabled">) => {
    try {
      await apiFetch(`/api/schedules/${schedule.id}`, {
        method: "PATCH",
        headers: { "content-type": "application/json" },
        body: JSON.stringify({ ...schedule, ...changes }),
      });
      await loadSchedules(scheduleWorkspaceId);
    } catch {}
  };
  const toggleSchedule = (schedule: AgentSchedule) =>
    updateSchedule(schedule, { cron: schedule.cron, enabled: !schedule.enabled });
  const deleteSchedule = async (id: string) => {
    try {
      await apiFetch(`/api/schedules/${id}`, { method: "DELETE" });
      await loadSchedules(scheduleWorkspaceId);
    } catch {}
  };
  useEffect(() => {
    if (!open || section !== "schedules") return;
    const timer = setInterval(() => void loadSchedules(scheduleWorkspaceId), 2000);
    return () => clearInterval(timer);
  }, [loadSchedules, open, scheduleWorkspaceId, section]);
  useEffect(() => {
    if (!open) return;
    closeRef.current?.focus();
    const close = (event: KeyboardEvent) => {
      if (event.key === "Escape" && !pageRef.current?.querySelector('[role="alertdialog"]')) onClose();
    };
    document.addEventListener("keydown", close);
    return () => document.removeEventListener("keydown", close);
  }, [onClose, open]);
  useFocusTrap(pageRef, open);
  if (!open) return null;
  const navigationClass = (target: SettingsSection) => `flex min-h-11 w-auto shrink-0 items-center justify-center gap-2 rounded px-3 text-sm md:w-full md:justify-start ${section === target ? "bg-accent-soft font-medium text-ink" : "text-ink-muted hover:bg-panel-2 hover:text-ink"}`;
  return (
    <div ref={pageRef} role="dialog" aria-modal="true" aria-labelledby="agent-settings-title" className="fixed inset-0 z-50 flex flex-col bg-bg">
      <header className="flex min-h-14 items-center border-b border-line bg-panel px-4">
        <div id="agent-settings-title" className="flex items-center gap-2 font-semibold"><Settings2 className="size-4 text-accent" />Settings</div>
        <button ref={closeRef} type="button" aria-label="Close settings" onClick={onClose} className="ml-auto flex size-11 items-center justify-center rounded hover:bg-panel-2"><X className="size-4" /></button>
      </header>
      <div className="flex min-h-0 flex-1 flex-col md:flex-row">
        <nav aria-label="Settings sections" className="flex shrink-0 gap-1 overflow-x-auto border-b border-line bg-panel p-2 md:block md:w-56 md:border-b-0 md:border-r md:p-3">
          <button type="button" onClick={() => { onSectionChange("appearance"); setSaved(false); }} className={navigationClass("appearance")}><Palette className="size-4 text-accent" />Appearance</button>
          <button type="button" onClick={() => { onSectionChange("agents"); setSaved(false); }} className={navigationClass("agents")}><Bot className="size-4 text-accent" />Agents</button>
          <button type="button" onClick={() => { onSectionChange("company"); setSaved(false); }} className={navigationClass("company")}><Building2 className="size-4 text-accent" />Company</button>
          <button type="button" onClick={() => { onSectionChange("channels"); setSaved(false); }} className={navigationClass("channels")}><Hash className="size-4 text-accent" />Channels</button>
          <button type="button" onClick={() => { onSectionChange("browser"); setSaved(false); }} className={navigationClass("browser")}><Globe className="size-4 text-accent" />Browser</button>
          <button type="button" onClick={() => { onSectionChange("secrets"); setSaved(false); }} className={navigationClass("secrets")}><KeyRound className="size-4 text-accent" />Secrets</button>
          <button type="button" onClick={() => { onSectionChange("memory"); setSaved(false); }} className={navigationClass("memory")}><Brain className="size-4 text-accent" />Memory</button>
          <button type="button" onClick={() => { onSectionChange("runtime"); setSaved(false); }} className={navigationClass("runtime")}><SlidersHorizontal className="size-4 text-accent" />Runtime</button>
          <button type="button" onClick={() => { onSectionChange("autonomy"); setSaved(false); }} className={navigationClass("autonomy")}><Sparkles className="size-4 text-accent" />Autonomy</button>
          <button type="button" onClick={() => { onSectionChange("schedules"); setSaved(false); }} className={navigationClass("schedules")}><Clock3 className="size-4 text-accent" />Schedules</button>
          <button type="button" onClick={() => { onSectionChange("observability"); setSaved(false); }} className={navigationClass("observability")}><Activity className="size-4 text-accent" />Observe</button>
        </nav>
        <main className="min-w-0 flex-1 overflow-y-auto px-4 py-5 md:px-6 md:py-6">
          {section === "appearance" ? <div className="w-full max-w-3xl">
            <h1 className="text-xl font-semibold">Appearance</h1>
            <p className="mt-1 text-sm text-ink-dim">Choose how Popagent looks on this device. Your selection is saved in this browser.</p>
            <section className="mt-6 border border-line bg-panel p-5">
              <h2 className="font-medium">Theme</h2>
              <div className="mt-4 grid gap-4 sm:grid-cols-2 lg:grid-cols-3" role="radiogroup" aria-label="Color theme">
                {APPEARANCE_THEMES.map((option) => {
                  const selected = theme === option.id;
                  const Icon = option.id === "light" || option.id === "solarized-light" ? Sun : option.id === "dark" ? Moon : Palette;
                  return <button key={option.id} type="button" role="radio" aria-checked={selected} aria-label={`${option.label} theme`} onClick={() => { setTheme(option.id); saveTheme(option.id); }} className={`theme-choice theme-choice--${option.id} ${selected ? "is-selected" : ""}`}>
                    <span className="theme-preview" aria-hidden="true"><span className="theme-preview-sidebar" /><span className="theme-preview-content"><span /><span /><span /></span></span>
                    <span className="flex items-center gap-2 px-3 py-3 text-left text-sm font-medium"><Icon className="size-4" /><span>{option.label}</span><span className="theme-check ml-auto" /></span>
                  </button>;
                })}
              </div>
            </section>
          </div> : section === "agents" ? <div className="w-full">
            <div className="flex flex-wrap items-end justify-between gap-2">
              <div><p className="text-xs font-medium uppercase tracking-[0.18em] text-accent">Agent portfolio</p><h1 className="mt-1 text-2xl font-semibold tracking-tight">Your operating team</h1><p className="mt-1 max-w-2xl text-sm text-ink-dim">Review each agent’s identity, remit, tools, and working instructions.</p></div>
              <span className="text-xs text-ink-dim">Changes apply on the next run</span>
            </div>
            {agents.length ? <section className="agent-flow mt-5" aria-label="Agent portfolio">
              {agents.map((item) => <AgentPortfolioCard
                key={item.id}
                agent={item}
                selected={item.id === selectedId}
                onChoose={() => choose(item)}
              />)}
            </section> : null}
            <div className="mt-4">
              {selected ? <section className="border border-line bg-panel p-3 sm:p-4">
                <AgentIdentityCard agentId={selected.id} model={selected.model} delegationEnabled={selected.delegationEnabled} />
                <div className="mb-4 flex gap-1 border-b border-line pb-2">
                  <button type="button" onClick={() => { setAgentPanel("instructions"); setSaved(false); }} className={`min-h-10 rounded px-3 text-sm ${agentPanel === "instructions" ? "bg-accent-soft text-ink" : "text-ink-muted"}`}>Instructions</button>
                  <button type="button" onClick={() => { setAgentPanel("skills"); setSaved(false); }} className={`min-h-10 rounded px-3 text-sm ${agentPanel === "skills" ? "bg-accent-soft text-ink" : "text-ink-muted"}`}>Skills</button>
                </div>
                {agentPanel === "instructions" ? <>
                  <div className="grid gap-3 sm:grid-cols-2">
                    <label className="text-xs font-medium text-ink-muted">Display name<input aria-label="Agent display name" maxLength={120} value={agentName} onChange={(event) => { setAgentName(event.currentTarget.value); setSaved(false); }} className="mt-1 min-h-10 w-full rounded border border-line bg-bg px-3 text-sm text-ink outline-none focus:border-accent/50" /></label>
                    <label className="text-xs font-medium text-ink-muted">Delegation description<input aria-label="Agent delegation description" maxLength={1024} value={agentDescription} onChange={(event) => { setAgentDescription(event.currentTarget.value); setSaved(false); }} className="mt-1 min-h-10 w-full rounded border border-line bg-bg px-3 text-sm text-ink outline-none focus:border-accent/50" /></label>
                  </div>
                  <label className="mt-3 block text-xs font-medium text-ink-muted">Model<select aria-label="Agent model" value={agentModel ?? ""} onChange={(event) => { setAgentModel(event.currentTarget.value || null); setSaved(false); }} className="mt-1 min-h-10 w-full rounded border border-line bg-bg px-3 text-sm text-ink outline-none"><option value="">Use selected request model</option>{runtimeModels.map((model) => <option key={model} value={model}>{model}</option>)}</select></label>
                  <div className="mt-4 border-y border-line py-4">
                    <div className="flex flex-wrap items-baseline justify-between gap-2"><h3 className="text-sm font-medium">Capabilities</h3><p className="text-xs text-ink-dim">Writable workspace, interactive browser, and secret tools grant broader access.</p></div>
                    <div className="mt-3 grid gap-3 sm:grid-cols-2">
                      <label className="text-xs font-medium text-ink-dim">Workspace access<select aria-label="Agent workspace access" value={agentWorkspaceAccess} onChange={(event) => { setAgentWorkspaceAccess(event.currentTarget.value as AgentWorkspaceAccess); setSaved(false); }} className="mt-1 min-h-10 w-full rounded border border-line bg-bg px-3 text-sm text-ink outline-none"><option value="none">None</option><option value="read-only">Read only</option><option value="read-write">Read and write</option></select></label>
                      <label className="text-xs font-medium text-ink-dim">Browser access<select aria-label="Agent browser access" value={agentBrowserAccess} onChange={(event) => { setAgentBrowserAccess(event.currentTarget.value as AgentBrowserAccess); setSaved(false); }} className="mt-1 min-h-10 w-full rounded border border-line bg-bg px-3 text-sm text-ink outline-none"><option value="none">None</option><option value="read-only">Read only</option><option value="interactive">Interactive</option></select></label>
                    </div>
                    {selected.id === "orchistrator" ? null : <label className="mt-3 flex items-center gap-2 text-sm"><input aria-label="Enable agent delegation" type="checkbox" checked={agentDelegationEnabled} onChange={(event) => { setAgentDelegationEnabled(event.currentTarget.checked); setSaved(false); }} className="size-4 accent-accent" />Available for delegation</label>}
                    <fieldset className="mt-3"><legend className="text-xs font-medium text-ink-dim">Tools</legend><div className="mt-2 flex flex-wrap gap-2">{AGENT_TOOL_NAMES.map((tool) => <label key={tool} className="flex min-h-9 items-center gap-2 rounded border border-line bg-bg px-3 text-xs"><input type="checkbox" checked={agentTools.includes(tool)} onChange={() => toggleAgentTool(tool)} className="size-4 accent-accent" />{tool}</label>)}</div></fieldset>
                  </div>
                  <div className="mt-4 flex flex-wrap items-baseline justify-between gap-1"><label htmlFor="agent-instructions" className="text-sm font-medium">Instructions</label><span className="text-xs text-ink-dim">{instructions.length.toLocaleString()} / {MAX_AGENT_INSTRUCTIONS_CHARACTERS.toLocaleString()}</span></div>
                  {selected.sourceUrls.length ? <p className="mt-1 text-xs text-ink-dim">Adapted from {selected.sourceUrls.map((url, index) => <span key={url}>{index ? ", " : ""}<a href={url} target="_blank" rel="noreferrer" className="text-accent hover:underline">source {index + 1}</a></span>)}.</p> : null}
                  <textarea id="agent-instructions" maxLength={MAX_AGENT_INSTRUCTIONS_CHARACTERS} value={instructions} onChange={(event) => { setInstructions(event.currentTarget.value); setSaved(false); }} rows={14} className="mt-2 w-full resize-y rounded border border-line bg-bg p-3 text-sm leading-relaxed outline-none focus:border-accent/50" />
                  <div className="mt-3 flex items-center justify-end gap-3">{saved ? <span role="status" className="text-xs text-emerald-500">Saved</span> : null}<button type="button" disabled={saving || !agentName.trim() || !agentDescription.trim() || !instructions.trim()} onClick={() => void saveInstructions()} className="min-h-10 rounded bg-accent px-4 text-sm font-medium text-white disabled:opacity-60">{saving ? "Saving…" : "Save agent"}</button></div>
                </> : <div className="grid gap-4 lg:grid-cols-[13rem_1fr]">
                  <div><button type="button" onClick={() => editSkill()} className="mb-2 min-h-10 w-full rounded border border-line bg-panel-2 px-3 text-left text-sm"><Plus className="mr-2 inline size-4 text-accent" />New skill</button>{skills.map((skill) => <button key={skill.id} type="button" onClick={() => editSkill(skill)} className={`mb-2 w-full rounded border px-3 py-2 text-left ${editingSkill?.id === skill.id ? "border-accent/40 bg-accent-soft" : "border-line bg-bg"}`}><span className="block truncate text-sm font-medium">{skill.name}</span><span className="text-xs text-ink-dim">{skill.enabled ? "Enabled" : "Disabled"}</span></button>)}</div>
                  <div>
                    {editingSkill?.sourceUrls.length ? <p className="mb-3 text-xs text-ink-dim">Adapted from {editingSkill.sourceUrls.map((url, index) => <span key={url}>{index ? ", " : ""}<a href={url} target="_blank" rel="noreferrer" className="text-accent hover:underline">source {index + 1}</a></span>)}.</p> : null}
                    <label className="text-xs font-medium text-ink-dim">Name<input aria-label="Skill name" value={skillName} onChange={(event) => setSkillName(event.currentTarget.value)} placeholder="release-checklist" className="mt-1 block w-full rounded border border-line bg-bg px-3 py-2 text-sm text-ink outline-none" /></label>
                    <label className="mt-3 block text-xs font-medium text-ink-dim">Description<input aria-label="Skill description" value={skillDescription} onChange={(event) => setSkillDescription(event.currentTarget.value)} placeholder="When this skill should be used" className="mt-1 block w-full rounded border border-line bg-bg px-3 py-2 text-sm text-ink outline-none" /></label>
                    <label className="mt-3 block text-xs font-medium text-ink-dim">Instructions<textarea aria-label="Skill instructions" value={skillInstructions} onChange={(event) => setSkillInstructions(event.currentTarget.value)} rows={10} className="mt-1 block w-full resize-y rounded border border-line bg-bg p-3 text-sm text-ink outline-none" /></label>
                    <label className="mt-3 block text-xs font-medium text-ink-dim">References JSON<textarea aria-label="Skill references" value={skillReferences} onChange={(event) => setSkillReferences(event.currentTarget.value)} rows={4} className="mt-1 block w-full resize-y rounded border border-line bg-bg p-3 font-mono text-xs text-ink outline-none" /></label>
                    <div className="mt-3 flex flex-wrap gap-5 text-sm"><label className="flex items-center gap-2"><input type="checkbox" checked={skillEnabled} onChange={(event) => setSkillEnabled(event.currentTarget.checked)} className="size-4 accent-accent" />Enabled</label><label className="flex items-center gap-2"><input type="checkbox" checked={skillUserInvocable} onChange={(event) => setSkillUserInvocable(event.currentTarget.checked)} className="size-4 accent-accent" />User invocable</label></div>
                    {skillError ? <p role="alert" className="mt-3 text-sm text-red-400">{skillError}</p> : null}
                    <div className="mt-4 flex items-center justify-end gap-2">{editingSkill ? <button type="button" onClick={() => void deleteSkill()} className="min-h-10 rounded border border-red-500/30 px-4 text-sm text-red-400">Delete</button> : null}{saved ? <span role="status" className="text-xs text-emerald-500">Saved</span> : null}<button type="button" disabled={saving || !skillName || !skillDescription || !skillInstructions} onClick={() => void saveSkill()} className="min-h-10 rounded bg-accent px-4 text-sm font-medium text-white disabled:opacity-50">{saving ? "Saving…" : editingSkill ? "Save skill" : "Create skill"}</button></div>
                  </div>
                </div>}
              </section> : <div className="text-sm text-ink-dim">Loading agents…</div>}
            </div>
          </div> : section === "company" ? <CompanyRosterPanel /> : section === "channels" ? <ChannelSettingsPanel /> : section === "browser" ? <div className="w-full max-w-5xl">
            <h1 className="text-xl font-semibold">Browser</h1>
            <p className="mt-1 text-sm text-ink-dim">Configure isolated Mastra AgentBrowser sessions and the live chat workspace.</p>
            {!browserSettings ? <p className="mt-6 text-sm text-ink-dim">Loading browser settings…</p> : <div className="mt-6 space-y-4">
              <section className="rounded border border-line bg-panel p-5">
                <label className="flex items-start justify-between gap-4"><span><span className="block font-medium">Enable browser tools</span><span className="mt-1 block text-sm leading-relaxed text-ink-dim">Agents can navigate rendered websites. Active sessions appear automatically in a live panel beside chat.</span></span><input aria-label="Enable browser tools" type="checkbox" checked={browserSettings.enabled} onChange={(event) => updateBrowser("enabled", event.currentTarget.checked)} className="mt-1 size-5 accent-accent" /></label>
                {browserHealth ? <div className="mt-4 flex flex-wrap items-end gap-4 border-t border-line pt-4 text-xs"><span className="min-w-28"><span className="block text-ink-dim">Provider</span>{browserHealth.provider}</span><span className="min-w-28"><span className="block text-ink-dim">Status</span>{browserHealth.healthy ? "Ready" : "Unavailable"}</span><span className="min-w-28"><span className="block text-ink-dim">Display</span>{browserHealth.headless ? "Headless" : "Visible"}</span><span className="min-w-20"><span className="block text-ink-dim">Sessions</span>{browserHealth.activeSessions}</span><button type="button" disabled={testingBrowser || !browserSettings.enabled} onClick={async () => { setTestingBrowser(true); const response = await apiFetch("/api/browser/test", { method: "POST" }); setTestingBrowser(false); setBrowserHealth((current) => current ? { ...current, healthy: response.ok } : current); }} className="ml-auto min-h-10 border border-line px-3 text-xs disabled:opacity-50">{testingBrowser ? "Testing…" : "Test browser"}</button></div> : null}
              </section>
              <section className="rounded border border-line bg-panel p-5"><h2 className="font-medium">Runtime</h2><p className="mt-1 text-xs text-ink-dim">Local headless Chromium. Thread scope is recommended for isolation.</p><div className="mt-4 grid gap-4 sm:grid-cols-2 lg:grid-cols-3">
                <label className="text-sm font-medium">Session isolation<select aria-label="Browser session isolation" value={browserSettings.scope} onChange={(event) => updateBrowser("scope", event.currentTarget.value as BrowserSettings["scope"])} className="mt-2 min-h-11 w-full border border-line bg-bg px-3"><option value="thread">One browser per chat</option><option value="shared">Shared browser</option></select></label>
                <RuntimeNumberField label="Viewport width" value={browserSettings.viewportWidth} min={320} max={3840} onChange={(value) => updateBrowser("viewportWidth", value)} />
                <RuntimeNumberField label="Viewport height" value={browserSettings.viewportHeight} min={240} max={2160} onChange={(value) => updateBrowser("viewportHeight", value)} />
                <RuntimeNumberField label="Operation timeout (ms)" value={browserSettings.timeoutMs} min={1000} max={120000} step={1000} onChange={(value) => updateBrowser("timeoutMs", value)} />
                <RuntimeNumberField label="Maximum sessions" value={browserSettings.maxSessions} min={1} max={16} onChange={(value) => updateBrowser("maxSessions", value)} />
                <RuntimeNumberField label="Idle timeout (ms)" value={browserSettings.idleTimeoutMs} min={60000} max={86400000} step={60000} onChange={(value) => updateBrowser("idleTimeoutMs", value)} />
              </div></section>
              <section className="rounded border border-line bg-panel p-5"><h2 className="font-medium">Capabilities</h2><div className="mt-4 grid gap-2 sm:grid-cols-2 lg:grid-cols-3">{([
                ["screencastEnabled", "Live browser panel"], ["screenshotsEnabled", "Screenshots"], ["multiTabEnabled", "Multiple tabs"], ["formsEnabled", "Forms and keyboard"], ["dialogsEnabled", "Browser dialogs"], ["dragEnabled", "Drag and drop"], ["evaluateEnabled", "Page JavaScript"], ["recordingEnabled", "Session recording (beta)"],
              ] as const).map(([key, label]) => <label key={key} className="flex min-h-11 items-center gap-3 border border-line bg-bg px-3 text-sm"><input type="checkbox" checked={browserSettings[key]} onChange={(event) => updateBrowser(key, event.currentTarget.checked)} className="size-4 accent-accent" />{label}</label>)}</div><p className="mt-3 text-xs text-ink-dim">Read-only agents never receive form, click, dialog, drag, JavaScript, close, tab-management, recording, or admin-input capabilities.</p></section>
              <section className="rounded border border-line bg-panel p-5"><h2 className="font-medium">Network policy</h2><p className="mt-1 text-xs text-ink-dim">Private and local addresses remain blocked unless explicitly allowed. One hostname per line; <code>*.example.com</code> is supported.</p><div className="mt-4 grid gap-4 sm:grid-cols-2"><label className="text-sm font-medium">Allowed hosts<textarea aria-label="Allowed browser hosts" rows={4} value={browserSettings.allowHosts.join("\n")} onChange={(event) => updateBrowser("allowHosts", event.currentTarget.value.split(/\s+/).filter(Boolean))} className="mt-2 w-full border border-line bg-bg p-3 font-mono text-xs" /></label><label className="text-sm font-medium">Denied hosts<textarea aria-label="Denied browser hosts" rows={4} value={browserSettings.denyHosts.join("\n")} onChange={(event) => updateBrowser("denyHosts", event.currentTarget.value.split(/\s+/).filter(Boolean))} className="mt-2 w-full border border-line bg-bg p-3 font-mono text-xs" /></label></div></section>
              <section className="rounded border border-line bg-panel p-5"><h2 className="font-medium">Recordings</h2><div className={`mt-4 grid gap-4 sm:grid-cols-2 ${browserSettings.recordingEnabled ? "" : "opacity-50"}`}><RuntimeNumberField label="Retention (days)" value={browserSettings.recordingRetentionDays} min={1} max={90} onChange={(value) => updateBrowser("recordingRetentionDays", value)} /><RuntimeNumberField label="Maximum files" value={browserSettings.recordingMaxFiles} min={1} max={100} onChange={(value) => updateBrowser("recordingMaxFiles", value)} /></div><p className="mt-3 text-xs text-ink-dim">Recordings are stored under the contained Popagent data directory and may include sensitive page content.</p><div className="mt-3 space-y-2">{browserRecordings.map((recording) => <div key={recording.name} className="flex items-center gap-3 border border-line bg-bg p-3 text-xs"><div className="min-w-0 flex-1"><div className="truncate font-mono">{recording.name}</div><div className="text-ink-dim">{Math.ceil(recording.size / 1024)} KB · {new Date(recording.createdAt).toLocaleString()}</div></div><button type="button" onClick={() => void downloadBrowserRecording(recording.name)} className="min-h-10 border border-line px-3">Download</button><button type="button" onClick={async () => { await apiFetch(`/api/browser/recordings/${encodeURIComponent(recording.name)}`, { method: "DELETE" }); setBrowserRecordings((current) => current.filter((item) => item.name !== recording.name)); }} className="min-h-10 border border-line px-3 text-red-400">Delete</button></div>)}</div></section>
              <section className="rounded border border-line bg-panel p-5"><h2 className="font-medium">Authenticated profiles</h2><p className="mt-1 text-xs text-ink-dim">Upload Playwright storage state. Cookies and local storage are encrypted and never returned by the API.</p><div className="mt-4 grid gap-3 md:grid-cols-[minmax(0,1fr)_minmax(0,2fr)]"><label className="text-sm font-medium">Profile name<input aria-label="Browser profile name" value={profileName} onChange={(event) => setProfileName(event.currentTarget.value)} className="mt-2 min-h-11 w-full border border-line bg-bg px-3" /></label><label className="text-sm font-medium">Storage-state JSON<textarea aria-label="Browser profile storage state" value={profileState} onChange={(event) => setProfileState(event.currentTarget.value)} rows={5} className="mt-2 w-full border border-line bg-bg p-3 font-mono text-xs" /></label></div>{profileError ? <p role="alert" className="mt-2 text-xs text-red-400">{profileError}</p> : null}<div className="mt-3 flex justify-end"><button type="button" disabled={!profileName.trim()} onClick={() => void createBrowserProfile()} className="min-h-11 bg-accent px-4 text-sm font-medium text-white disabled:opacity-50">Add encrypted profile</button></div><div className="mt-3 space-y-2">{browserProfiles.map((profile) => <div key={profile.id} className="flex items-center gap-3 border border-line bg-bg p-3 text-sm"><label className="flex min-w-0 flex-1 items-center gap-3"><input type="radio" name="active-browser-profile" checked={profile.enabled} onChange={() => void setActiveBrowserProfile(profile, true)} className="size-4 accent-accent" /><span className="truncate">{profile.name}</span></label><button type="button" onClick={async () => { await apiFetch(`/api/browser/profiles/${profile.id}`, { method: "DELETE" }); setBrowserProfiles((current) => current.filter((item) => item.id !== profile.id)); }} className="min-h-10 border border-line px-3 text-xs text-red-400">Delete</button></div>)}</div></section>
              <section className="rounded border border-line bg-panel p-5"><div className="flex items-center justify-between"><div><h2 className="font-medium">Active sessions</h2><p className="mt-1 text-xs text-ink-dim">{browserSessions.length} currently tracked</p></div></div><div className="mt-3 space-y-2">{browserSessions.length ? browserSessions.map((session) => <div key={session.id} className="flex items-center gap-3 border border-line bg-bg p-3 text-sm"><Globe className="size-4 text-accent" /><div className="min-w-0 flex-1"><div className="truncate">{session.currentUrl ?? "Starting…"}</div><div className="text-xs text-ink-dim">{session.access} · {session.tabs.length} tabs · {session.threadId}</div></div><button type="button" onClick={async () => { await apiFetch(`/api/browser/sessions/${encodeURIComponent(session.id)}/close`, { method: "POST" }); setBrowserSessions((current) => current.filter((item) => item.id !== session.id)); }} className="min-h-10 border border-line px-3 text-xs">Close</button></div>) : <p className="text-sm text-ink-dim">No active browser sessions.</p>}</div></section>
              <div className="flex items-center justify-end gap-3">{saved ? <span role="status" className="text-xs text-emerald-500">Saved</span> : null}<button type="button" disabled={saving} onClick={() => void saveBrowser()} className="min-h-11 bg-accent px-4 text-sm font-medium text-white disabled:opacity-60">{saving ? "Saving…" : "Save browser settings"}</button></div>
            </div>}
          </div> : section === "secrets" ? <SecretSettingsPanel /> : section === "memory" ? <MemorySettingsPanel /> : section === "runtime" ? <div className="w-full max-w-4xl">
            <h1 className="text-xl font-semibold">Runtime</h1>
            <p className="mt-1 text-sm text-ink-dim">Control model execution, delegation bounds, and background task scheduling. Changes apply to new turns and tasks.</p>
            {runtimeSettings ? <div className="mt-6 space-y-5">
              <section className="rounded border border-line bg-panel p-5">
                <h2 className="font-medium">Model and execution</h2>
                <div className="mt-4 grid gap-4 sm:grid-cols-2">
                  <RuntimeModelSource value={runtimeSettings.modelSource} onChange={changeModelSource} />
                  <label className="text-sm font-medium">Default model
                    <select aria-label="Default model" disabled={runtimeSettings.modelSource !== "configured"} value={runtimeSettings.defaultModel} onChange={(event) => changeRuntime("defaultModel", event.currentTarget.value)} className="mt-2 min-h-11 w-full rounded border border-line bg-bg px-3 text-ink outline-none focus:border-accent/50 disabled:opacity-60">
                      {!runtimeModels.includes(runtimeSettings.defaultModel) ? <option value={runtimeSettings.defaultModel}>{runtimeSettings.defaultModel}</option> : null}
                      {runtimeModels.map((model) => <option key={model} value={model}>{model}</option>)}
                    </select>
                  </label>
                  <p className="self-end text-xs leading-relaxed text-ink-dim sm:col-span-2">The free route uses 9Router’s <code>auto/coding:free</code> route, which delegates to currently available OpenRouter free coding models. Availability and model behavior may vary.</p>
                  <RuntimeNumberField label="Supervisor max steps" value={runtimeSettings.supervisorMaxSteps} min={1} max={128} onChange={(value) => changeRuntime("supervisorMaxSteps", value)} />
                  <RuntimeNumberField label="Specialist max steps" value={runtimeSettings.specialistMaxSteps} min={1} max={128} onChange={(value) => changeRuntime("specialistMaxSteps", value)} />
                  <RuntimeNumberField label="Concurrent tool calls" value={runtimeSettings.toolConcurrency} min={1} max={16} onChange={(value) => changeRuntime("toolConcurrency", value)} />
                  <RuntimeNumberField label="Processor retries" value={runtimeSettings.maxProcessorRetries} min={0} max={10} onChange={(value) => changeRuntime("maxProcessorRetries", value)} />
                </div>
                <label className="mt-4 block text-sm font-medium">Final-response feedback<textarea aria-label="Final-response feedback" maxLength={4000} rows={3} value={runtimeSettings.finalResponseFeedback} onChange={(event) => changeRuntime("finalResponseFeedback", event.currentTarget.value)} className="mt-2 w-full resize-y rounded border border-line bg-bg p-3 text-sm outline-none focus:border-accent/50" /></label>
              </section>
              <section className="rounded border border-line bg-panel p-5">
                <h2 className="font-medium">Delegation</h2>
                <div className="mt-4 grid gap-4 sm:grid-cols-2">
                  <RuntimeNumberField label="Context messages" value={runtimeSettings.delegationContextMessages} min={1} max={100} onChange={(value) => changeRuntime("delegationContextMessages", value)} />
                  <RuntimeNumberField label="Result character limit" value={runtimeSettings.delegationResultCharacters} min={1000} max={100000} step={1000} onChange={(value) => changeRuntime("delegationResultCharacters", value)} />
                </div>
                <label className="mt-4 block text-sm font-medium">Failure feedback<textarea aria-label="Delegation failure feedback" maxLength={4000} rows={3} value={runtimeSettings.delegationFailureFeedback} onChange={(event) => changeRuntime("delegationFailureFeedback", event.currentTarget.value)} className="mt-2 w-full resize-y rounded border border-line bg-bg p-3 text-sm outline-none focus:border-accent/50" /></label>
                <label className="mt-4 block text-sm font-medium">Result truncation marker<textarea aria-label="Delegation result truncation marker" maxLength={500} rows={2} value={runtimeSettings.delegationResultTruncationMarker} onChange={(event) => changeRuntime("delegationResultTruncationMarker", event.currentTarget.value)} className="mt-2 w-full resize-y rounded border border-line bg-bg p-3 font-mono text-sm outline-none focus:border-accent/50" /></label>
              </section>
              <section className="rounded border border-line bg-panel p-5">
                <h2 className="font-medium">Background tasks</h2>
                <div className="mt-4 grid gap-4 sm:grid-cols-2">
                  <RuntimeNumberField label="Concurrent tasks" value={runtimeSettings.taskConcurrency} min={1} max={16} onChange={(value) => changeRuntime("taskConcurrency", value)} />
                  <RuntimeNumberField label="Poll interval (ms)" value={runtimeSettings.taskPollIntervalMs} min={250} max={300000} step={250} onChange={(value) => changeRuntime("taskPollIntervalMs", value)} />
                  <RuntimeNumberField label="Task timeout (ms)" value={runtimeSettings.taskTimeoutMs} min={1000} max={86400000} step={1000} onChange={(value) => changeRuntime("taskTimeoutMs", value)} />
                  <RuntimeNumberField label="Restart stale threshold (ms)" value={runtimeSettings.taskStaleAfterMs} min={60000} max={604800000} step={60000} onChange={(value) => changeRuntime("taskStaleAfterMs", value)} />
                </div>
              </section>
              <div className="flex items-center justify-end gap-3">{saved ? <span role="status" className="text-xs text-emerald-500">Saved</span> : null}<button type="button" disabled={saving} onClick={() => void saveRuntime()} className="min-h-11 rounded bg-accent px-4 text-sm font-medium text-white disabled:opacity-60">{saving ? "Saving…" : "Save runtime settings"}</button></div>
            </div> : <p className="mt-6 text-sm text-ink-dim">Loading runtime settings…</p>}
          </div> : section === "autonomy" ? <AutonomySettingsPanel /> : section === "observability" ? <ObservabilityPage /> : <div className="w-full max-w-4xl">
            <h1 className="text-xl font-semibold">Schedules</h1>
            <p className="mt-1 text-sm text-ink-dim">Create recurring work. Live tasks and progress are on the workspace overview.</p>
            <label className="mt-4 block max-w-sm text-xs font-medium text-ink-dim">Workspace
              <select aria-label="Schedule workspace" value={scheduleWorkspaceId} onChange={(event) => { const id = event.currentTarget.value; setScheduleWorkspaceId(id); void loadSchedules(id); }} className="mt-1 min-h-11 w-full rounded border border-line bg-bg px-3 text-sm text-ink outline-none">
                {workspaces.map((workspace) => <option key={workspace.id} value={workspace.id}>{workspace.name}</option>)}
              </select>
            </label>
            <section className="mt-6 rounded border border-line bg-panel p-5">
              <h2 className="font-medium">New schedule</h2>
              <input aria-label="Schedule name" value={scheduleName} onChange={(event) => setScheduleName(event.currentTarget.value)} placeholder="Daily summary" className="mt-3 w-full rounded border border-line bg-bg px-3 py-2 text-sm outline-none" />
              <textarea aria-label="Schedule prompt" value={schedulePrompt} onChange={(event) => setSchedulePrompt(event.currentTarget.value)} rows={3} placeholder="Task prompt" className="mt-2 w-full rounded border border-line bg-bg p-3 text-sm outline-none" />
              <div className="mt-2 grid gap-2 sm:grid-cols-2">
                <label className="text-xs text-ink-dim">Repeat<select aria-label="Schedule frequency" value={scheduleFrequency} onChange={(event) => setScheduleFrequency(event.currentTarget.value as ScheduleFrequency)} className="mt-1 min-h-11 w-full rounded border border-line bg-bg px-3 text-sm text-ink outline-none"><option value="daily">Every day</option><option value="weekdays">Weekdays</option><option value="weekly">Every week</option></select></label>
                <label className="text-xs text-ink-dim">Time<input type="time" aria-label="Schedule time" value={scheduleTime} onChange={(event) => setScheduleTime(event.currentTarget.value)} className="mt-1 min-h-11 w-full rounded border border-line bg-bg px-3 text-sm text-ink outline-none" /></label>
              </div>
              {scheduleFrequency === "weekly" ? <label className="mt-2 block text-xs text-ink-dim">Day<select aria-label="Schedule weekday" value={scheduleWeekday} onChange={(event) => setScheduleWeekday(event.currentTarget.value)} className="mt-1 min-h-11 w-full rounded border border-line bg-bg px-3 text-sm text-ink outline-none">{WEEKDAYS.map((day) => <option key={day.value} value={day.value}>{day.label}</option>)}</select></label> : null}
              <button type="button" disabled={!scheduleName.trim() || !schedulePrompt.trim() || !scheduleTime} onClick={() => void createSchedule()} className="mt-3 min-h-11 rounded bg-accent px-4 text-sm font-medium text-white disabled:opacity-50">Create schedule</button>
            </section>
            <section className="mt-5 rounded border border-line bg-panel p-5"><h2 className="font-medium">Schedules</h2><div className="mt-3 space-y-2">{schedules.length ? schedules.map((schedule) => <article key={schedule.id} className="rounded border border-line bg-bg p-3"><div className="flex flex-wrap items-center gap-3"><div className="min-w-0 flex-1"><div className="font-medium">{schedule.name}</div><div className="truncate text-xs text-ink-dim">{describeSchedule(schedule.cron)}{schedule.nextRunAt ? ` · next ${new Date(schedule.nextRunAt).toLocaleString()}` : ""}<AgentWorkSource source={schedule.source} /></div>{schedule.latestTaskStatus ? <div className="mt-1 text-xs text-ink-muted">Latest: {schedule.latestTaskStatus}{schedule.latestHeartbeatAt ? ` · heartbeat ${new Date(schedule.latestHeartbeatAt).toLocaleString()}` : ""}</div> : null}</div><button type="button" onClick={() => void toggleSchedule(schedule)} className="min-h-11 rounded border border-line px-3 text-xs">{schedule.enabled ? "Pause" : "Resume"}</button>{schedule.source === "user" ? <button type="button" aria-label={`Delete schedule ${schedule.name}`} onClick={() => void deleteSchedule(schedule.id)} className="flex size-11 items-center justify-center rounded text-red-400"><Trash2 className="size-4" /></button> : null}</div><form className="mt-3 flex flex-wrap items-end gap-2" onSubmit={(event) => { event.preventDefault(); const cron = new FormData(event.currentTarget).get("cron"); if (typeof cron === "string") void updateSchedule(schedule, { cron, enabled: schedule.enabled }); }}><label className="min-w-52 flex-1 text-xs text-ink-dim">Cron<input name="cron" aria-label={`Cron for ${schedule.name}`} defaultValue={schedule.cron} maxLength={100} className="mt-1 min-h-10 w-full rounded border border-line bg-panel px-3 font-mono text-sm text-ink outline-none focus:border-accent/50" /></label><button type="submit" className="min-h-10 rounded border border-line px-3 text-xs">Save timing</button></form></article>) : <p className="text-sm text-ink-dim">No schedules.</p>}</div></section>
          </div>}
        </main>
      </div>
    </div>
  );
}