diff --git a/ui/web/src/pages/agents/agent-detail/agent-permissions-tab.tsx b/ui/web/src/pages/agents/agent-detail/agent-permissions-tab.tsx index 3e03b355..58d2f901 100644 --- a/ui/web/src/pages/agents/agent-detail/agent-permissions-tab.tsx +++ b/ui/web/src/pages/agents/agent-detail/agent-permissions-tab.tsx @@ -1,4 +1,4 @@ -import { useState, useEffect, useMemo } from "react"; +import { useState, useEffect, useMemo, useCallback } from "react"; import { Plus, Trash2, Loader2, Shield, FolderOpen, RefreshCw } from "lucide-react"; import { useTranslation } from "react-i18next"; import { Button } from "@/components/ui/button"; @@ -9,6 +9,9 @@ import { import { Combobox, type ComboboxOption } from "@/components/ui/combobox"; import { useConfigPermissions, type ConfigPermission } from "../hooks/use-config-permissions"; import { UserPickerCombobox } from "@/components/shared/user-picker-combobox"; +import { useWs } from "@/hooks/use-ws"; +import { Methods } from "@/api/protocol"; +import type { DeliveryTarget } from "../hooks/use-agent-heartbeat"; const CONFIG_TYPES = [ { value: "file_writer", label: "File Writer", descKey: "permissions.types.file_writer_desc" }, @@ -18,18 +21,26 @@ const CONFIG_TYPES = [ { value: "*", label: "All (*)", descKey: "permissions.types.all_desc" }, ]; -function getScopeOptions(configType: string, existingScopes: string[]): ComboboxOption[] { +function getScopeOptions(configType: string, targets: DeliveryTarget[]): ComboboxOption[] { + // Only groups — topic-level permissions are not supported by the backend scope check + const groupOptions = targets + .filter((t) => t.kind === "group") + .map((t) => ({ + value: `group:${t.channel}:${t.chatId}`, + label: t.title ? `${t.title} (${t.chatId})` : t.chatId, + })); + if (configType === "file_writer") { - const dynamic = existingScopes.map((s) => ({ value: s, label: s })); return [ { value: "group:*", label: "All Groups" }, - ...dynamic, + ...groupOptions, { value: "*", label: "Global (*)" }, ]; } return [ { value: "agent", label: "Agent (DM)" }, { value: "group:*", label: "All Groups" }, + ...groupOptions, { value: "*", label: "Global (*)" }, ]; } @@ -40,6 +51,7 @@ interface AgentPermissionsTabProps { export function AgentPermissionsTab({ agentId }: AgentPermissionsTabProps) { const { t } = useTranslation("agents"); + const ws = useWs(); const { permissions, loading, load, grant, revoke } = useConfigPermissions(agentId); const [userId, setUserId] = useState(""); @@ -47,22 +59,39 @@ export function AgentPermissionsTab({ agentId }: AgentPermissionsTabProps) { const [scope, setScope] = useState("group:*"); const [permission, setPermission] = useState("allow"); const [adding, setAdding] = useState(false); + const [targets, setTargets] = useState([]); - // Collect existing file_writer scopes for dynamic scope options - const existingFileWriterScopes = useMemo(() => - [...new Set( - permissions - .filter((p) => p.configType === "file_writer") - .map((p) => p.scope) - )], - [permissions], - ); + // Fetch delivery targets (groups/topics) from channel_contacts + const loadTargets = useCallback(async () => { + if (!agentId || !ws.isConnected) return; + try { + const res = await ws.call<{ targets: DeliveryTarget[] }>( + Methods.HEARTBEAT_TARGETS, { agentId }, + ); + setTargets(res.targets ?? []); + } catch { /* ignore — targets are optional enhancement */ } + }, [ws, agentId]); + + useEffect(() => { loadTargets(); }, [loadTargets]); const scopeOptions = useMemo( - () => getScopeOptions(configType, existingFileWriterScopes), - [configType, existingFileWriterScopes], + () => getScopeOptions(configType, targets), + [configType, targets], ); + // Build scope → display label lookup from targets + const scopeLabels = useMemo(() => { + const map = new Map(); + map.set("group:*", t("permissions.scopes.group_all")); + map.set("*", t("permissions.scopes.global")); + map.set("agent", t("permissions.scopes.agent")); + for (const tgt of targets) { + const key = `group:${tgt.channel}:${tgt.chatId}`; + map.set(key, tgt.title ? `${tgt.title} (${tgt.chatId})` : tgt.chatId); + } + return map; + }, [targets, t]); + // Reset scope when configType changes useEffect(() => { if (configType === "file_writer") { @@ -196,7 +225,7 @@ export function AgentPermissionsTab({ agentId }: AgentPermissionsTabProps) {
- {scopeKey} + {scopeLabels.get(scopeKey) ?? scopeKey}
{writers.map((p) => { const displayName = p.metadata?.displayName || p.userId; @@ -251,7 +280,7 @@ export function AgentPermissionsTab({ agentId }: AgentPermissionsTabProps) { {p.userId} {p.configType} - @ {p.scope} + @ {scopeLabels.get(p.scope) ?? p.scope}