diff --git a/plugin-agent-permissions/src/manifest.ts b/plugin-agent-permissions/src/manifest.ts index 377ee2e..d2fe3b1 100644 --- a/plugin-agent-permissions/src/manifest.ts +++ b/plugin-agent-permissions/src/manifest.ts @@ -13,8 +13,8 @@ const manifest: PaperclipPluginManifestV1 = { "plugin.state.read", "plugin.state.write", "ui.detailTab.register", - "ui.sidebar.register", - "ui.page.register" + "ui.page.register", + "instance.settings.register" ], entrypoints: { worker: "./dist/worker.js", @@ -29,18 +29,18 @@ const manifest: PaperclipPluginManifestV1 = { exportName: "PermissionsPage", routePath: "/permissions" }, + { + type: "settingsPage", + id: "permissions-settings", + displayName: "Agent Permissions", + exportName: "PermissionsSettingsPage" + }, { type: "detailTab", id: "permissions", displayName: "Permissions", exportName: "AgentPermissionsTab", entityTypes: ["agent"] - }, - { - type: "sidebar", - id: "permissions-nav", - displayName: "Permissions", - exportName: "PermissionsNav" } ] } diff --git a/plugin-agent-permissions/src/ui/PermissionsNav.tsx b/plugin-agent-permissions/src/ui/PermissionsNav.tsx deleted file mode 100644 index 7197daf..0000000 --- a/plugin-agent-permissions/src/ui/PermissionsNav.tsx +++ /dev/null @@ -1,70 +0,0 @@ -import { usePluginData, useHostContext } from "@paperclipai/plugin-sdk/ui"; -import type { PluginSidebarProps } from "@paperclipai/plugin-sdk/ui"; -import { SIDEBAR_PREVIEW_LIMIT, type PermissionKey } from "../constants"; - -interface AgentPermissionsSummary { - agentId: string; - agentName: string; - permissions: Record; -} - -export function PermissionsNav(_props: PluginSidebarProps) { - const { companyId } = useHostContext(); - const { data: agentsData, loading, error } = usePluginData( - "all-agents-permissions", - companyId ? { companyId } : undefined - ); - - if (loading) return ( -
- Loading permissions... -
- ); - if (error) return ( -
- Error: {error.message} -
- ); - if (!agentsData || agentsData.length === 0) { - return ( -
-

Permissions

-

No agents found

-
- ); - } - - const agentsWithPermissions = agentsData.filter(a => - Object.values(a.permissions).some(v => v) - ); - - return ( -
-

Permissions

-

- {agentsWithPermissions.length} agent(s) with custom permissions -

-
    - {agentsData.slice(0, SIDEBAR_PREVIEW_LIMIT).map(agent => { - const permCount = Object.values(agent.permissions).filter(Boolean).length; - return ( -
  • -
    {agent.agentName}
    -
    - {permCount} permission(s) granted -
    -
  • - ); - })} -
-
- ); -} diff --git a/plugin-agent-permissions/src/ui/PermissionsPage.tsx b/plugin-agent-permissions/src/ui/PermissionsPage.tsx index 5c46702..15cee55 100644 --- a/plugin-agent-permissions/src/ui/PermissionsPage.tsx +++ b/plugin-agent-permissions/src/ui/PermissionsPage.tsx @@ -20,7 +20,7 @@ export function PermissionsPage(_props: PluginPageProps) { const togglePermission = usePluginAction("toggle-agent-permission"); const [updating, setUpdating] = useState<{ agentId: string; permissionKey: PermissionKey } | null>(null); const [lastError, setLastError] = useState(null); - const [selectedAgentId, setSelectedAgentId] = useState(null); + const [expandedAgents, setExpandedAgents] = useState>(new Set()); async function handleToggle(agentId: string, permissionKey: PermissionKey, currentEnabled: boolean) { setLastError(null); @@ -37,6 +37,18 @@ export function PermissionsPage(_props: PluginPageProps) { } } + function handleToggleAgent(agentId: string) { + setExpandedAgents((prev) => { + const next = new Set(prev); + if (next.has(agentId)) { + next.delete(agentId); + } else { + next.add(agentId); + } + return next; + }); + } + if (loading) { return (
@@ -62,12 +74,8 @@ export function PermissionsPage(_props: PluginPageProps) { ); } - const selectedAgent = selectedAgentId - ? agentsData.find(a => a.agentId === selectedAgentId) - : null; - return ( -
+

Agent Permissions @@ -94,137 +102,122 @@ export function PermissionsPage(_props: PluginPageProps) {

)} -
- - -
- {selectedAgent ? ( -
-
-

- {selectedAgent.agentName} -

-

Manage permissions for this agent

-
- -
- Permission toggles for {selectedAgent.agentName} -
- {PERMISSION_KEYS.map((key) => { - const enabled = selectedAgent.permissions[key] ?? false; - const isUpdating = updating?.agentId === selectedAgent.agentId && updating?.permissionKey === key; - - return ( - - ); - })} + {agent.agentName}
-
+ {permCount > 0 && ( + + {permCount} permission(s) + + )} + + + {isExpanded && ( +
+
+ Permission toggles for {agent.agentName} +
+ {PERMISSION_KEYS.map((key) => { + const enabled = agent.permissions[key] ?? false; + const isUpdating = updating?.agentId === agent.agentId && updating?.permissionKey === key; + + return ( + + ); + })} +
+
+
+ )}
- ) : ( -
- Select an agent from the list to manage their permissions -
- )} -
+ ); + })}
); diff --git a/plugin-agent-permissions/src/ui/PermissionsSettingsPage.tsx b/plugin-agent-permissions/src/ui/PermissionsSettingsPage.tsx new file mode 100644 index 0000000..5b980f0 --- /dev/null +++ b/plugin-agent-permissions/src/ui/PermissionsSettingsPage.tsx @@ -0,0 +1,259 @@ +import { useState } from "react"; +import { useHostContext, usePluginData, usePluginAction, usePluginToast, type PluginSettingsPageProps } from "@paperclipai/plugin-sdk/ui"; + +type Permission = { + capability: string; + allowed: boolean; +}; + +type AgentRecord = { + id: string; + name: string; + status: string; + description?: string | null; +}; + +const layoutStyle = { + display: "grid", + gap: "16px", +}; + +const cardStyle = { + border: "1px solid var(--border)", + borderRadius: "12px", + overflow: "hidden", + background: "var(--card, transparent)", +};; + +const headerStyle = { + display: "flex", + alignItems: "center", + gap: "12px", + padding: "14px 16px", + cursor: "pointer", + background: "color-mix(in srgb, var(--muted, #888) 6%, transparent)", + userSelect: "none", +}; + +const contentStyle = { + padding: "14px 16px 16px", + display: "grid", + gap: "12px", +};; + +const permissionRowStyle = { + display: "flex", + alignItems: "center", + justifyContent: "space-between", + gap: "12px", + padding: "8px 10px", + borderRadius: "8px", + border: "1px solid color-mix(in srgb, var(--border) 60%, transparent)", +};; + +const mutedTextStyle = { + fontSize: "12px", + opacity: 0.72, + lineHeight: 1.45, +};; + +function ChevronIcon({ expanded }: { expanded: boolean }) { + return ( + + + + ); +} + +function StatusBadge({ status }: { status: string }) { + const isActive = status === "active"; + return ( + + {status} + + ); +} + +export function PermissionsSettingsPage({ context }: PluginSettingsPageProps) { + const companyId = context.companyId; + + const agentsQuery = usePluginData("agents", companyId ? { companyId } : {}); + const permissionsQuery = usePluginData("agent-permissions", companyId ? { companyId } : {}); + const updatePermissions = usePluginAction("update-agent-permissions"); + const toast = usePluginToast(); + + const [expandedAgents, setExpandedAgents] = useState>(new Set()); + + const agents = agentsQuery.data ?? []; + const allPermissions = permissionsQuery.data ?? []; + + function getAgentPermissions(agentId: string): Permission[] { + return allPermissions.filter((p) => p.capability.startsWith(`${agentId}:`)); + } + + function handleToggleAgent(agentId: string) { + setExpandedAgents((prev) => { + const next = new Set(prev); + if (next.has(agentId)) { + next.delete(agentId); + } else { + next.add(agentId); + } + return next; + }); + } + + async function handleTogglePermission(agentId: string, capability: string, newValue: boolean) { + if (!companyId) return; + + try { + await updatePermissions({ + companyId, + agentId, + capability, + allowed: newValue, + }); + permissionsQuery.refresh(); + toast({ + title: "Permission updated", + body: `${capability}: ${newValue ? "allowed" : "denied"}`, + tone: "success", + }); + } catch (error) { + const message = error instanceof Error ? error.message : String(error); + toast({ + title: "Failed to update permission", + body: message, + tone: "error", + }); + } + } + + if (!companyId) { + return ( +
+
+ Select a company to manage agent permissions. +
+
+ ); + } + + if (agentsQuery.loading || permissionsQuery.loading) { + return ( +
+
+ Loading agents and permissions… +
+
+ ); + } + + if (agents.length === 0) { + return ( +
+
+ No agents found for this company. +
+
+ ); + } + + return ( +
+
+ Agent Permissions +
+ Manage permissions for each agent in your company. Toggle capabilities to allow or deny specific actions. +
+
+ +
+ {agents.map((agent) => { + const isExpanded = expandedAgents.has(agent.id); + const agentPermissions = getAgentPermissions(agent.id); + + return ( +
+
handleToggleAgent(agent.id)}> + +
+
+ {agent.name} + +
+ {agent.description ? ( +
{agent.description}
+ ) : null} +
+ + {agentPermissions.length} permissions + +
+ + {isExpanded ? ( +
+ {agentPermissions.length === 0 ? ( +
+ No custom permissions configured for this agent. +
+ ) : ( +
+ {agentPermissions.map((permission) => { + const capabilityName = permission.capability.replace(`${agent.id}:`, ""); + + return ( +
+
+
{capabilityName}
+
+ {permission.capability} +
+
+ +
+ ); + })} +
+ )} +
+ ) : null} +
+ ); + })} +
+
+ ); +} diff --git a/plugin-agent-permissions/src/ui/index.tsx b/plugin-agent-permissions/src/ui/index.tsx index e9cd334..25ac36b 100644 --- a/plugin-agent-permissions/src/ui/index.tsx +++ b/plugin-agent-permissions/src/ui/index.tsx @@ -1,3 +1,3 @@ export { AgentPermissionsTab } from './AgentPermissionsTab'; -export { PermissionsNav } from './PermissionsNav'; export { PermissionsPage } from './PermissionsPage'; +export { PermissionsSettingsPage } from './PermissionsSettingsPage';