diff --git a/apps/web/app/(dashboard)/agents/page.tsx b/apps/web/app/(dashboard)/agents/page.tsx index 7fb11efd..902cbcf4 100644 --- a/apps/web/app/(dashboard)/agents/page.tsx +++ b/apps/web/app/(dashboard)/agents/page.tsx @@ -25,10 +25,13 @@ import { MoreHorizontal, Play, ChevronDown, + Globe, + Lock, } from "lucide-react"; import type { Agent, AgentStatus, + AgentVisibility, AgentTool, AgentTrigger, AgentTriggerType, @@ -126,6 +129,7 @@ function CreateAgentDialog({ const [name, setName] = useState(""); const [description, setDescription] = useState(""); const [selectedRuntimeId, setSelectedRuntimeId] = useState(runtimes[0]?.id ?? ""); + const [visibility, setVisibility] = useState("private"); const [creating, setCreating] = useState(false); const [runtimeOpen, setRuntimeOpen] = useState(false); @@ -145,6 +149,7 @@ function CreateAgentDialog({ name: name.trim(), description: description.trim(), runtime_id: selectedRuntime.id, + visibility, triggers: [{ id: generateId(), type: "on_assign", enabled: true, config: {} }], }); onClose(); @@ -189,6 +194,42 @@ function CreateAgentDialog({ /> +
+ +
+ + +
+
+
diff --git a/apps/web/features/issues/components/pickers/assignee-picker.tsx b/apps/web/features/issues/components/pickers/assignee-picker.tsx index a41ba9cc..5ffd0d2c 100644 --- a/apps/web/features/issues/components/pickers/assignee-picker.tsx +++ b/apps/web/features/issues/components/pickers/assignee-picker.tsx @@ -1,8 +1,9 @@ "use client"; import { useState } from "react"; -import { Bot, UserMinus } from "lucide-react"; -import type { IssueAssigneeType, UpdateIssueRequest } from "@/shared/types"; +import { Bot, Lock, UserMinus } from "lucide-react"; +import type { Agent, IssueAssigneeType, UpdateIssueRequest } from "@/shared/types"; +import { useAuthStore } from "@/features/auth"; import { useWorkspaceStore, useActorName } from "@/features/workspace"; import { PropertyPicker, @@ -11,6 +12,13 @@ import { PickerEmpty, } from "./property-picker"; +function canAssignAgent(agent: Agent, userId: string | undefined, memberRole: string | undefined): boolean { + if (agent.visibility !== "private") return true; + if (agent.owner_id === userId) return true; + if (memberRole === "owner" || memberRole === "admin") return true; + return false; +} + export function AssigneePicker({ assigneeType, assigneeId, @@ -24,10 +32,14 @@ export function AssigneePicker({ }) { const [open, setOpen] = useState(false); const [filter, setFilter] = useState(""); + const user = useAuthStore((s) => s.user); const members = useWorkspaceStore((s) => s.members); const agents = useWorkspaceStore((s) => s.agents); const { getActorName, getActorInitials } = useActorName(); + const currentMember = members.find((m) => m.user_id === user?.id); + const memberRole = currentMember?.role; + const query = filter.toLowerCase(); const filteredMembers = members.filter((m) => m.name.toLowerCase().includes(query), @@ -117,24 +129,32 @@ export function AssigneePicker({ {/* Agents */} {filteredAgents.length > 0 && ( - {filteredAgents.map((a) => ( - { - onUpdate({ - assignee_type: "agent", - assignee_id: a.id, - }); - setOpen(false); - }} - > -
- -
- {a.name} -
- ))} + {filteredAgents.map((a) => { + const allowed = canAssignAgent(a, user?.id, memberRole); + return ( + { + if (!allowed) return; + onUpdate({ + assignee_type: "agent", + assignee_id: a.id, + }); + setOpen(false); + }} + > +
+ +
+ {a.name} + {a.visibility === "private" && ( + + )} +
+ ); + })}
)} diff --git a/apps/web/features/issues/components/pickers/property-picker.tsx b/apps/web/features/issues/components/pickers/property-picker.tsx index 2aa2048d..1329fa4d 100644 --- a/apps/web/features/issues/components/pickers/property-picker.tsx +++ b/apps/web/features/issues/components/pickers/property-picker.tsx @@ -79,11 +79,13 @@ export function PropertyPicker({ export function PickerItem({ selected, + disabled, onClick, hoverClassName, children, }: { selected: boolean; + disabled?: boolean; onClick: () => void; hoverClassName?: string; children: React.ReactNode; @@ -91,8 +93,9 @@ export function PickerItem({ return (