plugin side confirmed

This commit is contained in:
2026-03-17 14:33:32 -04:00
parent 39a8ddef97
commit ecd78f7528
6 changed files with 336 additions and 100 deletions

View File

@@ -1,18 +1,24 @@
// Shared permission keys for agent permissions plugin
// Only the single real enforced permission is exposed here.
export const PERMISSION_KEYS = [
"canCreateAgents",
] as const;
import { PERMISSION_KEYS } from "@paperclipai/shared";
export { PERMISSION_KEYS };
export type PermissionKey = typeof PERMISSION_KEYS[number];
export const PERMISSION_LABELS: Record<PermissionKey, string> = {
"canCreateAgents": "Can Hire Agents",
"agents:create": "Can Hire Agents",
"users:invite": "Can Invite Users",
"users:manage_permissions": "Can Manage User Permissions",
"tasks:assign": "Can Assign Agents to Tasks",
"tasks:assign_scope": "Can Assign Agents (Scoped)",
"joins:approve": "Can Approve Join Requests",
};
export const PERMISSION_DESCRIPTIONS: Record<PermissionKey, string> = {
"canCreateAgents": "Allows this agent to create (hire) new agents",
"agents:create": "Create (hire) new agents in the company",
"users:invite": "Invite new users to join the company",
"users:manage_permissions": "Modify permission grants for other users",
"tasks:assign": "Assign agents to tasks and issues",
"tasks:assign_scope": "Assign agents within a specific scope",
"joins:approve": "Approve or reject pending join requests",
};
// Pagination constants

View File

@@ -11,6 +11,8 @@ const manifest: PaperclipPluginManifestV1 = {
capabilities: [
"agents.read",
"agents.update-permissions",
"agents.grants.read",
"agents.grants.write",
"ui.detailTab.register",
"ui.page.register",
"instance.settings.register"

View File

@@ -1,12 +1,13 @@
import { useState } from "react";
import { usePluginData, usePluginAction } from "@paperclipai/plugin-sdk/ui";
import type { PluginDetailTabProps } from "@paperclipai/plugin-sdk/ui";
import { PERMISSION_LABELS, PERMISSION_DESCRIPTIONS } from "../constants";
import { PERMISSION_KEYS, PERMISSION_LABELS, PERMISSION_DESCRIPTIONS, type PermissionKey } from "../constants";
interface AgentPermissionsData {
agentId: string;
agentName: string;
canCreateAgents: boolean;
grants: string[];
}
export function AgentPermissionsTab({ context }: PluginDetailTabProps) {
@@ -17,10 +18,47 @@ export function AgentPermissionsTab({ context }: PluginDetailTabProps) {
{ agentId, companyId }
);
const togglePermission = usePluginAction("toggle-agent-permission");
const [updating, setUpdating] = useState(false);
const toggleLegacy = usePluginAction("toggle-can-create-agents");
const toggleGrant = usePluginAction("toggle-permission");
const [updating, setUpdating] = useState<string | null>(null);
const [lastError, setLastError] = useState<Error | null>(null);
async function handleToggleLegacy(currentEnabled: boolean) {
if (!companyId) return;
setLastError(null);
setUpdating("legacy");
try {
await toggleLegacy({ agentId, companyId, enabled: !currentEnabled });
await refresh();
} catch (err) {
const e = err instanceof Error ? err : new Error(String(err));
setLastError(e);
console.error("Failed to toggle permission:", e);
} finally {
setUpdating(null);
}
}
async function handleToggleGrant(key: PermissionKey, currentEnabled: boolean) {
if (!companyId) return;
setLastError(null);
setUpdating(key);
try {
await toggleGrant({ agentId, companyId, permissionKey: key, enabled: !currentEnabled });
await refresh();
} catch (err) {
const e = err instanceof Error ? err : new Error(String(err));
setLastError(e);
console.error("Failed to toggle grant:", e);
} finally {
setUpdating(null);
}
}
function hasGrant(key: PermissionKey): boolean {
return data?.grants.includes(key) ?? false;
}
if (loading) return <div style={{ padding: "1rem" }}>Loading permissions...</div>;
if (error) return (
<div role="alert" style={{ padding: "1rem", color: "#c00" }}>
@@ -29,21 +67,6 @@ export function AgentPermissionsTab({ context }: PluginDetailTabProps) {
);
if (!data) return <div style={{ padding: "1rem" }}>No permissions data available</div>;
async function handleToggle(currentEnabled: boolean) {
setLastError(null);
setUpdating(true);
try {
await togglePermission({ agentId, companyId, enabled: !currentEnabled });
await refresh();
} catch (err) {
const e = err instanceof Error ? err : new Error(String(err));
setLastError(e);
console.error("Failed to toggle permission:", e);
} finally {
setUpdating(false);
}
}
return (
<div style={{ padding: "1rem", maxWidth: "600px" }}>
<h2 id="permissions-heading" style={{ marginBottom: "1.5rem", fontSize: "1.25rem", fontWeight: 600 }}>Agent Permissions</h2>
@@ -72,43 +95,78 @@ export function AgentPermissionsTab({ context }: PluginDetailTabProps) {
<legend style={{ display: "none" }}>Permission toggles</legend>
<div style={{ display: "flex", flexDirection: "column", gap: "0.75rem" }}>
<label
htmlFor="perm-canCreateAgents"
htmlFor="perm-legacy"
style={{
display: "flex",
alignItems: "center",
gap: "0.75rem",
cursor: updating ? "wait" : "pointer",
cursor: updating === "legacy" ? "wait" : "pointer",
padding: "0.5rem",
borderRadius: "4px",
backgroundColor: updating ? "#f5f5f5" : "transparent",
backgroundColor: updating === "legacy" ? "#f5f5f5" : "transparent",
transition: "background-color 0.2s"
}}
>
<input
id="perm-canCreateAgents"
id="perm-legacy"
type="checkbox"
checked={data.canCreateAgents}
onChange={() => handleToggle(data.canCreateAgents)}
disabled={updating}
style={{ width: "18px", height: "18px", cursor: updating ? "wait" : "pointer" }}
onChange={() => handleToggleLegacy(data.canCreateAgents)}
disabled={updating !== null}
style={{ width: "18px", height: "18px", cursor: updating === "legacy" ? "wait" : "pointer" }}
/>
<span>
<span style={{ fontWeight: 500, display: "block" }}>
{PERMISSION_LABELS["canCreateAgents"]}
</span>
<span style={{ fontWeight: 500, display: "block" }}>Can Hire Agents (legacy)</span>
<span style={{ color: "#666", fontSize: "0.875rem" }}>
{PERMISSION_DESCRIPTIONS["canCreateAgents"]}
Allows this agent to create (hire) new agents. Also grants task assignment rights.
</span>
</span>
{updating && (
<span
style={{ color: "#888", fontSize: "0.875rem" }}
role="status"
>
{updating === "legacy" && (
<span style={{ color: "#888", fontSize: "0.875rem" }} role="status">
updating...
</span>
)}
</label>
{PERMISSION_KEYS.map((key) => {
const enabled = hasGrant(key);
return (
<label
key={key}
htmlFor={`perm-${key}`}
style={{
display: "flex",
alignItems: "center",
gap: "0.75rem",
cursor: updating === key ? "wait" : "pointer",
padding: "0.5rem",
borderRadius: "4px",
backgroundColor: updating === key ? "#f5f5f5" : "transparent",
transition: "background-color 0.2s"
}}
>
<input
id={`perm-${key}`}
type="checkbox"
checked={enabled}
onChange={() => handleToggleGrant(key, enabled)}
disabled={updating !== null}
style={{ width: "18px", height: "18px", cursor: updating === key ? "wait" : "pointer" }}
/>
<span>
<span style={{ fontWeight: 500, display: "block" }}>{PERMISSION_LABELS[key]}</span>
<span style={{ color: "#666", fontSize: "0.875rem" }}>
{PERMISSION_DESCRIPTIONS[key]}
</span>
</span>
{updating === key && (
<span style={{ color: "#888", fontSize: "0.875rem" }} role="status">
updating...
</span>
)}
</label>
);
})}
</div>
</fieldset>
</div>

View File

@@ -1,12 +1,13 @@
import { useState } from "react";
import { usePluginData, usePluginAction, useHostContext } from "@paperclipai/plugin-sdk/ui";
import type { PluginPageProps } from "@paperclipai/plugin-sdk/ui";
import { PERMISSION_LABELS, PERMISSION_DESCRIPTIONS } from "../constants";
import { PERMISSION_KEYS, PERMISSION_LABELS, PERMISSION_DESCRIPTIONS, type PermissionKey } from "../constants";
interface AgentPermissionsSummary {
agentId: string;
agentName: string;
canCreateAgents: boolean;
grants: string[];
}
export function PermissionsPage(_props: PluginPageProps) {
@@ -17,21 +18,39 @@ export function PermissionsPage(_props: PluginPageProps) {
companyId ? { companyId, limit: 100 } : {}
);
const togglePermission = usePluginAction("toggle-agent-permission");
const toggleLegacy = usePluginAction("toggle-can-create-agents");
const toggleGrant = usePluginAction("toggle-permission");
const [updating, setUpdating] = useState<string | null>(null);
const [lastError, setLastError] = useState<Error | null>(null);
const [expandedAgents, setExpandedAgents] = useState<Set<string>>(new Set());
async function handleToggle(agentId: string, currentEnabled: boolean) {
async function handleToggleLegacy(agentId: string, currentEnabled: boolean) {
if (!companyId) return;
setLastError(null);
setUpdating(agentId);
setUpdating(`${agentId}:legacy`);
try {
await togglePermission({ agentId, companyId, enabled: !currentEnabled });
await toggleLegacy({ agentId, companyId, 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);
const e = err instanceof Error ? err : new Error(String(err));
setLastError(e);
console.error("Failed to toggle permission:", e);
} finally {
setUpdating(null);
}
}
async function handleToggleGrant(agentId: string, key: PermissionKey, currentEnabled: boolean) {
if (!companyId) return;
setLastError(null);
setUpdating(`${agentId}:${key}`);
try {
await toggleGrant({ agentId, companyId, permissionKey: key, enabled: !currentEnabled });
await refresh();
} catch (err) {
const e = err instanceof Error ? err : new Error(String(err));
setLastError(e);
console.error("Failed to toggle grant:", e);
} finally {
setUpdating(null);
}
@@ -49,6 +68,10 @@ export function PermissionsPage(_props: PluginPageProps) {
});
}
function hasGrant(agent: AgentPermissionsSummary, key: PermissionKey): boolean {
return agent.grants.includes(key);
}
if (loading) {
return (
<div style={{ padding: "2rem", textAlign: "center" }}>
@@ -105,7 +128,7 @@ export function PermissionsPage(_props: PluginPageProps) {
<div style={{ display: "flex", flexDirection: "column", gap: "1rem" }}>
{agentsData.map(agent => {
const isExpanded = expandedAgents.has(agent.agentId);
const isUpdating = updating === agent.agentId;
const hasAny = agent.canCreateAgents || agent.grants.length > 0;
return (
<div
@@ -146,7 +169,7 @@ export function PermissionsPage(_props: PluginPageProps) {
</span>
<span>{agent.agentName}</span>
</div>
{agent.canCreateAgents && (
{hasAny && (
<span style={{
fontSize: "0.75rem",
backgroundColor: "#e5e7eb",
@@ -154,7 +177,7 @@ export function PermissionsPage(_props: PluginPageProps) {
padding: "0.125rem 0.5rem",
borderRadius: "999px"
}}>
can hire
{agent.grants.length + (agent.canCreateAgents ? 1 : 0)} permissions
</span>
)}
</button>
@@ -163,17 +186,17 @@ export function PermissionsPage(_props: PluginPageProps) {
<div style={{ padding: "0 1.25rem 1.25rem" }}>
<fieldset style={{ border: "none", padding: 0, margin: 0 }}>
<legend style={{ display: "none" }}>Permission toggles for {agent.agentName}</legend>
<div style={{ display: "flex", flexDirection: "column", gap: "0.75rem", marginTop: "0.5rem" }}>
<div style={{ display: "flex", flexDirection: "column", gap: "0.5rem" }}>
<label
htmlFor={`perm-${agent.agentId}-canCreateAgents`}
style={{
display: "flex",
alignItems: "center",
gap: "0.75rem",
cursor: isUpdating ? "wait" : "pointer",
cursor: updating === `${agent.agentId}:legacy` ? "wait" : "pointer",
padding: "0.75rem",
borderRadius: "6px",
backgroundColor: isUpdating ? "#f9fafb" : "#fafafa",
backgroundColor: updating === `${agent.agentId}:legacy` ? "#f9fafb" : "#fafafa",
border: "1px solid #e5e7eb",
transition: "background-color 0.2s"
}}
@@ -182,22 +205,64 @@ export function PermissionsPage(_props: PluginPageProps) {
id={`perm-${agent.agentId}-canCreateAgents`}
type="checkbox"
checked={agent.canCreateAgents}
onChange={() => handleToggle(agent.agentId, agent.canCreateAgents)}
onChange={() => handleToggleLegacy(agent.agentId, agent.canCreateAgents)}
disabled={updating !== null}
style={{ width: "18px", height: "18px", cursor: isUpdating ? "wait" : "pointer" }}
style={{ width: "18px", height: "18px", cursor: updating === `${agent.agentId}:legacy` ? "wait" : "pointer" }}
/>
<div style={{ flex: 1 }}>
<div style={{ fontWeight: 500 }}>{PERMISSION_LABELS["canCreateAgents"]}</div>
<div style={{ fontWeight: 500 }}>Can Hire Agents (legacy)</div>
<div style={{ fontSize: "0.75rem", color: "#666" }}>
{PERMISSION_DESCRIPTIONS["canCreateAgents"]}
Allows this agent to create (hire) new agents. Also grants task assignment rights.
</div>
</div>
{isUpdating && (
{updating === `${agent.agentId}:legacy` && (
<span style={{ color: "#888", fontSize: "0.75rem" }} role="status">
updating...
</span>
)}
</label>
{PERMISSION_KEYS.map((key) => {
const enabled = hasGrant(agent, key);
const isUpdating = updating === `${agent.agentId}:${key}`;
return (
<label
key={key}
htmlFor={`perm-${agent.agentId}-${key}`}
style={{
display: "flex",
alignItems: "center",
gap: "0.75rem",
cursor: isUpdating ? "wait" : "pointer",
padding: "0.75rem",
borderRadius: "6px",
backgroundColor: isUpdating ? "#f9fafb" : "#fafafa",
border: "1px solid #e5e7eb",
transition: "background-color 0.2s"
}}
>
<input
id={`perm-${agent.agentId}-${key}`}
type="checkbox"
checked={enabled}
onChange={() => handleToggleGrant(agent.agentId, key, enabled)}
disabled={updating !== null}
style={{ width: "18px", height: "18px", cursor: isUpdating ? "wait" : "pointer" }}
/>
<div style={{ flex: 1 }}>
<div style={{ fontWeight: 500 }}>{PERMISSION_LABELS[key]}</div>
<div style={{ fontSize: "0.75rem", color: "#666" }}>
{PERMISSION_DESCRIPTIONS[key]}
</div>
</div>
{isUpdating && (
<span style={{ color: "#888", fontSize: "0.75rem" }} role="status">
updating...
</span>
)}
</label>
);
})}
</div>
</fieldset>
</div>

View File

@@ -1,12 +1,13 @@
import { useState } from "react";
import { usePluginData, usePluginAction, usePluginToast } from "@paperclipai/plugin-sdk/ui";
import type { PluginSettingsPageProps } from "@paperclipai/plugin-sdk/ui";
import { PERMISSION_LABELS, PERMISSION_DESCRIPTIONS } from "../constants";
import { PERMISSION_KEYS, PERMISSION_LABELS, PERMISSION_DESCRIPTIONS, type PermissionKey } from "../constants";
interface AgentPermissionsSummary {
agentId: string;
agentName: string;
canCreateAgents: boolean;
grants: string[];
}
const layoutStyle = {
@@ -78,7 +79,8 @@ export function PermissionsSettingsPage({ context }: PluginSettingsPageProps) {
companyId ? { companyId, limit: 100 } : {}
);
const togglePermission = usePluginAction("toggle-agent-permission");
const toggleLegacy = usePluginAction("toggle-can-create-agents");
const toggleGrant = usePluginAction("toggle-permission");
const toast = usePluginToast();
const [expandedAgents, setExpandedAgents] = useState<Set<string>>(new Set());
@@ -96,14 +98,15 @@ export function PermissionsSettingsPage({ context }: PluginSettingsPageProps) {
});
}
async function handleToggle(agentId: string, currentEnabled: boolean) {
setUpdating(agentId);
async function handleToggleLegacy(agentId: string, currentEnabled: boolean) {
if (!companyId) return;
setUpdating(`${agentId}:legacy`);
try {
await togglePermission({ agentId, companyId, enabled: !currentEnabled });
await toggleLegacy({ agentId, companyId, enabled: !currentEnabled });
await refresh();
toast({
title: "Permission updated",
body: `${PERMISSION_LABELS["canCreateAgents"]}: ${!currentEnabled ? "allowed" : "denied"}`,
body: `Can Hire Agents: ${!currentEnabled ? "allowed" : "denied"}`,
tone: "success",
});
} catch (err) {
@@ -118,6 +121,33 @@ export function PermissionsSettingsPage({ context }: PluginSettingsPageProps) {
}
}
async function handleToggleGrant(agentId: string, key: PermissionKey, currentEnabled: boolean) {
if (!companyId) return;
setUpdating(`${agentId}:${key}`);
try {
await toggleGrant({ agentId, companyId, permissionKey: key, enabled: !currentEnabled });
await refresh();
toast({
title: "Permission updated",
body: `${PERMISSION_LABELS[key]}: ${!currentEnabled ? "allowed" : "denied"}`,
tone: "success",
});
} catch (err) {
const message = err instanceof Error ? err.message : String(err);
toast({
title: "Failed to update permission",
body: message,
tone: "error",
});
} finally {
setUpdating(null);
}
}
function hasGrant(agent: AgentPermissionsSummary, key: PermissionKey): boolean {
return agent.grants.includes(key);
}
if (!companyId) {
return (
<div style={layoutStyle}>
@@ -162,14 +192,14 @@ export function PermissionsSettingsPage({ context }: PluginSettingsPageProps) {
<div style={layoutStyle}>
<div style={{ display: "grid", gap: "6px" }}>
<div style={mutedTextStyle}>
Toggle hiring permission to allow or deny each agent from creating new agents.
Manage permission grants for each agent. Toggle hiring permission and other capabilities.
</div>
</div>
<div style={{ display: "grid", gap: "10px" }}>
{agentsData.map((agent) => {
const isExpanded = expandedAgents.has(agent.agentId);
const isUpdating = updating === agent.agentId;
const hasAny = agent.canCreateAgents || agent.grants.length > 0;
return (
<div key={agent.agentId} style={cardStyle}>
@@ -178,9 +208,9 @@ export function PermissionsSettingsPage({ context }: PluginSettingsPageProps) {
<div style={{ display: "grid", gap: "2px", flex: 1 }}>
<strong style={{ fontSize: "13px" }}>{agent.agentName}</strong>
</div>
{agent.canCreateAgents && (
{hasAny && (
<span style={{ ...mutedTextStyle, fontSize: "11px" }}>
can hire
{agent.grants.length + (agent.canCreateAgents ? 1 : 0)} permissions
</span>
)}
</div>
@@ -190,27 +220,59 @@ export function PermissionsSettingsPage({ context }: PluginSettingsPageProps) {
<div style={{ display: "grid", gap: "8px" }}>
<div style={permissionRowStyle}>
<div style={{ display: "grid", gap: "2px", flex: 1 }}>
<div style={{ fontSize: "12px", fontWeight: 500 }}>{PERMISSION_LABELS["canCreateAgents"]}</div>
<div style={{ ...mutedTextStyle, fontSize: "11px" }}>{PERMISSION_DESCRIPTIONS["canCreateAgents"]}</div>
<div style={{ fontSize: "12px", fontWeight: 500 }}>Can Hire Agents (legacy)</div>
<div style={{ ...mutedTextStyle, fontSize: "11px" }}>
Allows this agent to create (hire) new agents. Also grants task assignment rights.
</div>
</div>
<label style={{ display: "flex", alignItems: "center", gap: "6px", cursor: isUpdating ? "wait" : "pointer" }}>
<label style={{ display: "flex", alignItems: "center", gap: "6px", cursor: updating === `${agent.agentId}:legacy` ? "wait" : "pointer" }}>
<input
type="checkbox"
checked={agent.canCreateAgents}
disabled={updating !== null}
onChange={() => handleToggle(agent.agentId, agent.canCreateAgents)}
onChange={() => handleToggleLegacy(agent.agentId, agent.canCreateAgents)}
style={{
width: "16px",
height: "16px",
accentColor: "var(--foreground)",
cursor: isUpdating ? "wait" : "pointer",
cursor: updating === `${agent.agentId}:legacy` ? "wait" : "pointer",
}}
/>
<span style={{ fontSize: "11px", opacity: 0.8 }}>
{isUpdating ? "saving…" : agent.canCreateAgents ? "Allowed" : "Denied"}
{updating === `${agent.agentId}:legacy` ? "saving…" : agent.canCreateAgents ? "Allowed" : "Denied"}
</span>
</label>
</div>
{PERMISSION_KEYS.map((key) => {
const enabled = hasGrant(agent, key);
const isKeyUpdating = updating === `${agent.agentId}:${key}`;
return (
<div key={key} style={permissionRowStyle}>
<div style={{ display: "grid", gap: "2px", flex: 1 }}>
<div style={{ fontSize: "12px", fontWeight: 500 }}>{PERMISSION_LABELS[key]}</div>
<div style={{ ...mutedTextStyle, fontSize: "11px" }}>{PERMISSION_DESCRIPTIONS[key]}</div>
</div>
<label style={{ display: "flex", alignItems: "center", gap: "6px", cursor: isKeyUpdating ? "wait" : "pointer" }}>
<input
type="checkbox"
checked={enabled}
disabled={updating !== null}
onChange={() => handleToggleGrant(agent.agentId, key, enabled)}
style={{
width: "16px",
height: "16px",
accentColor: "var(--foreground)",
cursor: isKeyUpdating ? "wait" : "pointer",
}}
/>
<span style={{ fontSize: "11px", opacity: 0.8 }}>
{isKeyUpdating ? "saving…" : enabled ? "Allowed" : "Denied"}
</span>
</label>
</div>
);
})}
</div>
</div>
) : null}

View File

@@ -5,32 +5,30 @@ interface AgentPermissionsSummary {
agentId: string;
agentName: string;
canCreateAgents: boolean;
grants: string[];
}
const plugin = definePlugin({
async setup(ctx) {
// Per-agent data for the detail tab
ctx.data.register("agent-permissions", async (params) => {
const agentId = typeof params.agentId === "string" ? params.agentId : "";
const companyId = typeof params.companyId === "string" ? params.companyId : "";
if (!agentId) return null;
// companyId may be empty when called from the detail tab; we still try
const agent = companyId
? await ctx.agents.get(agentId, companyId)
: null;
if (!agentId || !companyId) return null;
const agent = await ctx.agents.get(agentId, companyId);
if (!agent) return null;
const grants = await ctx.agents.getGrants(agentId, companyId);
return {
agentId: agent.id,
agentName: agent.name,
canCreateAgents: agent.permissions?.canCreateAgents ?? false,
grants,
};
});
// All-agents list for the permissions page and settings page
ctx.data.register("all-agents-permissions", async (params) => {
const companyId = typeof params.companyId === "string" ? params.companyId : "";
const limit = Math.min(
@@ -39,20 +37,65 @@ const plugin = definePlugin({
);
const offset = Math.max(0, Number(params.offset) || 0);
const agents = companyId
? await ctx.agents.list({ companyId, limit, offset })
: [];
if (!companyId) return [];
const result: AgentPermissionsSummary[] = agents.map(agent => ({
agentId: agent.id,
agentName: agent.name,
canCreateAgents: agent.permissions?.canCreateAgents ?? false,
}));
const agents = await ctx.agents.list({ companyId, limit, offset });
return result;
const results: AgentPermissionsSummary[] = await Promise.all(
agents.map(async (agent) => {
let grants: string[] = [];
try {
grants = await ctx.agents.getGrants(agent.id, companyId);
} catch {
grants = [];
}
return {
agentId: agent.id,
agentName: agent.name,
canCreateAgents: agent.permissions?.canCreateAgents ?? false,
grants,
};
})
);
return results;
});
ctx.actions.register("toggle-agent-permission", async (params) => {
ctx.actions.register("toggle-permission", async (params) => {
const { agentId, companyId, permissionKey, enabled } = params as {
agentId: string;
companyId: string;
permissionKey: string;
enabled: boolean;
};
if (!agentId || typeof agentId !== "string") {
throw new Error("Invalid agentId: must be a non-empty string");
}
if (!companyId || typeof companyId !== "string") {
throw new Error("Invalid companyId: must be a non-empty string");
}
if (!permissionKey || typeof permissionKey !== "string") {
throw new Error("Invalid permissionKey: must be a non-empty string");
}
if (typeof enabled !== "boolean") {
throw new Error("Invalid enabled: must be a boolean");
}
const currentGrants = await ctx.agents.getGrants(agentId, companyId);
const grantSet = new Set(currentGrants);
if (enabled) {
grantSet.add(permissionKey);
} else {
grantSet.delete(permissionKey);
}
await ctx.agents.setGrants(agentId, companyId, [...grantSet]);
return { success: true };
});
ctx.actions.register("toggle-can-create-agents", async (params) => {
const { agentId, companyId, enabled } = params as {
agentId: string;
companyId: string;