diff --git a/packages/views/autopilots/components/autopilot-access-manager.tsx b/packages/views/autopilots/components/autopilot-access-manager.tsx
new file mode 100644
index 000000000..4074fb9de
--- /dev/null
+++ b/packages/views/autopilots/components/autopilot-access-manager.tsx
@@ -0,0 +1,162 @@
+"use client";
+
+import { useMemo, useState } from "react";
+import { Plus, X } from "lucide-react";
+import { useQuery } from "@tanstack/react-query";
+import { useWorkspaceId } from "@multica/core/hooks";
+import { memberListOptions } from "@multica/core/workspace/queries";
+import { useActorName } from "@multica/core/workspace/hooks";
+import {
+ useGrantAutopilotAccess,
+ useRevokeAutopilotAccess,
+} from "@multica/core/autopilots/mutations";
+import type { AutopilotCollaborator } from "@multica/core/types";
+import { toast } from "sonner";
+import { ActorAvatar } from "../../common/actor-avatar";
+import {
+ PropertyPicker,
+ PickerItem,
+ PickerEmpty,
+} from "../../issues/components/pickers/property-picker";
+import { matchesPinyin } from "../../editor/extensions/pinyin-match";
+import { useT } from "../../i18n";
+
+// Grant / revoke explicit write access to an autopilot. Members-only, mirroring
+// the subscriber picker. Creators and workspace admins always have access and
+// are not listed here — this manages the additional, explicitly-granted set.
+// Rendered inside the edit dialog's "Manage access" popover; access changes
+// commit immediately via their own mutations and are independent of the form's
+// Save action.
+export function AutopilotAccessManager({
+ autopilotId,
+ collaborators,
+}: {
+ autopilotId: string;
+ collaborators: AutopilotCollaborator[];
+}) {
+ const { t } = useT("autopilots");
+ const wsId = useWorkspaceId();
+ const { data: members = [] } = useQuery(memberListOptions(wsId));
+ const { getActorName } = useActorName();
+ const grant = useGrantAutopilotAccess();
+ const revoke = useRevokeAutopilotAccess();
+ const [pickerOpen, setPickerOpen] = useState(false);
+ const [filter, setFilter] = useState("");
+
+ const grantedIds = useMemo(
+ () => new Set(collaborators.map((c) => c.user_id)),
+ [collaborators],
+ );
+
+ const query = filter.trim().toLowerCase();
+ const candidates = useMemo(
+ () =>
+ members.filter(
+ (m) =>
+ !grantedIds.has(m.user_id) &&
+ (query === "" ||
+ m.name.toLowerCase().includes(query) ||
+ matchesPinyin(m.name, query)),
+ ),
+ [members, grantedIds, query],
+ );
+
+ const handleGrant = async (userId: string) => {
+ try {
+ await grant.mutateAsync({ autopilotId, userId });
+ toast.success(t(($) => $.access.toast_granted));
+ } catch (e: any) {
+ toast.error(e?.message || t(($) => $.access.toast_failed));
+ }
+ };
+
+ const handleRevoke = async (userId: string) => {
+ try {
+ await revoke.mutateAsync({ autopilotId, userId });
+ toast.success(t(($) => $.access.toast_revoked));
+ } catch (e: any) {
+ toast.error(e?.message || t(($) => $.access.toast_failed));
+ }
+ };
+
+ return (
+
+
+
+ {t(($) => $.access.current_label)}
+
+
{
+ setPickerOpen(v);
+ if (!v) setFilter("");
+ }}
+ width="w-64"
+ align="end"
+ searchable
+ searchPlaceholder={t(($) => $.access.search_placeholder)}
+ onSearchChange={setFilter}
+ trigger={
+
+
+ {t(($) => $.access.add)}
+
+ }
+ >
+ {candidates.length === 0 ? (
+
+ ) : (
+ candidates.map((m) => (
+ {
+ void handleGrant(m.user_id);
+ setPickerOpen(false);
+ }}
+ >
+
+ {m.name}
+
+ ))
+ )}
+
+
+
+ {collaborators.length === 0 ? (
+
+ {t(($) => $.access.empty)}
+
+ ) : (
+
+ {collaborators.map((c) => (
+ -
+
+
+
+ {getActorName("member", c.user_id)}
+
+
+
+
+ ))}
+
+ )}
+
+
+ {t(($) => $.access.owner_note)}
+
+
+ );
+}
diff --git a/packages/views/autopilots/components/autopilot-detail-page.tsx b/packages/views/autopilots/components/autopilot-detail-page.tsx
index bcd2dbce9..e65c8ba99 100644
--- a/packages/views/autopilots/components/autopilot-detail-page.tsx
+++ b/packages/views/autopilots/components/autopilot-detail-page.tsx
@@ -4,7 +4,7 @@ import { useState } from "react";
import {
Zap, Play, Clock, Plus, Trash2, CheckCircle2, XCircle, Loader2, Pencil,
Ban, ChevronDown, ChevronRight,
- Webhook, Copy, Check, RotateCw, Users,
+ Webhook, Copy, Check, RotateCw,
} from "lucide-react";
import { useQuery } from "@tanstack/react-query";
import { autopilotDetailOptions, autopilotRunsOptions, autopilotRunOptions } from "@multica/core/autopilots/queries";
@@ -62,7 +62,6 @@ import type { AgentTask } from "@multica/core/types/agent";
import { ReadonlyContent } from "../../editor";
import { TranscriptButton } from "../../common/task-transcript";
import { AutopilotDialog } from "./autopilot-dialog";
-import { ManageAccessDialog } from "./manage-access-dialog";
import { WebhookPayloadPreview } from "./webhook-payload-preview";
import { WebhookDeliveriesSection } from "./webhook-deliveries-section";
import { ProjectIcon } from "../../projects/components/project-icon";
@@ -635,7 +634,6 @@ export function AutopilotDetailPage({ autopilotId }: { autopilotId: string }) {
const [triggerDialogOpen, setTriggerDialogOpen] = useState(false);
const [editDialogOpen, setEditDialogOpen] = useState(false);
- const [accessDialogOpen, setAccessDialogOpen] = useState(false);
const [deleteConfirmOpen, setDeleteConfirmOpen] = useState(false);
const [deleting, setDeleting] = useState(false);
@@ -760,12 +758,6 @@ export function AutopilotDetailPage({ autopilotId }: { autopilotId: string }) {
actions={
canWrite ? (
<>
- {canManageAccess && (
-
- )}