From f4912109ddbaf26c767ed7d256944c2404c82fd0 Mon Sep 17 00:00:00 2001 From: Michael Freno Date: Tue, 17 Mar 2026 00:32:16 -0400 Subject: [PATCH] FRE-366: Add dedicated permissions page with toggle controls - Add page slot with /permissions route for dedicated permissions management - Add ui.page.register capability - Create PermissionsPage component with agent list and permission toggles - Each agent can be selected to view and toggle their permissions - Maintains existing sidebar and detail tab functionality Co-Authored-By: Paperclip --- plugin-agent-permissions/src/manifest.ts | 10 +- .../src/ui/PermissionsPage.tsx | 243 ++++++++++++++++++ plugin-agent-permissions/src/ui/index.tsx | 1 + 3 files changed, 253 insertions(+), 1 deletion(-) create mode 100644 plugin-agent-permissions/src/ui/PermissionsPage.tsx diff --git a/plugin-agent-permissions/src/manifest.ts b/plugin-agent-permissions/src/manifest.ts index f2d807d..377ee2e 100644 --- a/plugin-agent-permissions/src/manifest.ts +++ b/plugin-agent-permissions/src/manifest.ts @@ -13,7 +13,8 @@ const manifest: PaperclipPluginManifestV1 = { "plugin.state.read", "plugin.state.write", "ui.detailTab.register", - "ui.sidebar.register" + "ui.sidebar.register", + "ui.page.register" ], entrypoints: { worker: "./dist/worker.js", @@ -21,6 +22,13 @@ const manifest: PaperclipPluginManifestV1 = { }, ui: { slots: [ + { + type: "page", + id: "permissions-page", + displayName: "Permissions", + exportName: "PermissionsPage", + routePath: "/permissions" + }, { type: "detailTab", id: "permissions", diff --git a/plugin-agent-permissions/src/ui/PermissionsPage.tsx b/plugin-agent-permissions/src/ui/PermissionsPage.tsx new file mode 100644 index 0000000..5c46702 --- /dev/null +++ b/plugin-agent-permissions/src/ui/PermissionsPage.tsx @@ -0,0 +1,243 @@ +import { useState } from "react"; +import { usePluginData, usePluginAction, useHostContext } from "@paperclipai/plugin-sdk/ui"; +import type { PluginPageProps } from "@paperclipai/plugin-sdk/ui"; +import { PERMISSION_KEYS, PERMISSION_LABELS, type PermissionKey } from "../constants"; + +interface AgentPermissionsSummary { + agentId: string; + agentName: string; + permissions: Record; +} + +export function PermissionsPage(_props: PluginPageProps) { + const { companyId } = useHostContext(); + + const { data: agentsData, loading, error, refresh } = usePluginData( + "all-agents-permissions", + companyId ? { companyId, limit: 100 } : {} + ); + + 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); + + async function handleToggle(agentId: string, permissionKey: PermissionKey, currentEnabled: boolean) { + setLastError(null); + setUpdating({ agentId, permissionKey }); + try { + await togglePermission({ agentId, permissionKey, enabled: !currentEnabled }); + await refresh(); + } catch (err) { + const error = err instanceof Error ? err : new Error(String(err)); + setLastError(error); + console.error("Failed to toggle permission:", error); + } finally { + setUpdating(null); + } + } + + if (loading) { + return ( +
+
Loading agents and permissions...
+
+ ); + } + + if (error) { + return ( +
+ Error: {error.message} +
+ ); + } + + if (!agentsData || agentsData.length === 0) { + return ( +
+

Agent Permissions

+

No agents found in this company.

+
+ ); + } + + const selectedAgent = selectedAgentId + ? agentsData.find(a => a.agentId === selectedAgentId) + : null; + + return ( +
+
+

+ Agent Permissions +

+

+ Manage what actions each agent can perform within Paperclip. +

+
+ + {lastError && ( +
+ Failed to update: {lastError.message} +
+ )} + +
+ + +
+ {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 ( + + ); + })} +
+
+
+ ) : ( +
+ Select an agent from the list to manage their permissions +
+ )} +
+
+
+ ); +} + +function getPermissionDescription(key: PermissionKey): string { + const descriptions: Record = { + "agents:create": "Allows this agent to create new agents (agent hiring)", + "users:invite": "Allows inviting board users to the company", + "users:manage_permissions": "Allows managing user permissions", + "tasks:assign": "Allows assigning tasks to agents", + "tasks:assign_scope": "Scope for task assignment (limits which agents can be assigned)", + "joins:approve": "Allows approving join requests" + }; + return descriptions[key]; +} diff --git a/plugin-agent-permissions/src/ui/index.tsx b/plugin-agent-permissions/src/ui/index.tsx index ff54f99..e9cd334 100644 --- a/plugin-agent-permissions/src/ui/index.tsx +++ b/plugin-agent-permissions/src/ui/index.tsx @@ -1,2 +1,3 @@ export { AgentPermissionsTab } from './AgentPermissionsTab'; export { PermissionsNav } from './PermissionsNav'; +export { PermissionsPage } from './PermissionsPage';