feat(cross-project): surface UI des permissions contactables — PermissionsPanel et TemplateEditor exposent contactable/default_contactable, alignement httpInvoker — #260 (typecheck + 48 tests verts, QA en attente)
This commit is contained in:
@ -54,6 +54,19 @@ const NETWORK_LABELS: Record<NetworkPolicy, string> = {
|
||||
ask: "Demande",
|
||||
};
|
||||
|
||||
const CROSS_PROJECT_HELP =
|
||||
"Autorise les agents d’autres projets IdeA à solliciter cet agent via l’orchestration inter-projets. L’agent reste soumis à ses permissions et à son contexte.";
|
||||
|
||||
function systemPermissionsFromDraft(
|
||||
network: NetworkPolicy | "",
|
||||
crossProjectContactable: boolean,
|
||||
): SystemPermissionSet | null {
|
||||
const permissions: SystemPermissionSet = {};
|
||||
if (network) permissions.network = network;
|
||||
if (crossProjectContactable) permissions.crossProjectContactable = true;
|
||||
return Object.keys(permissions).length > 0 ? permissions : null;
|
||||
}
|
||||
|
||||
export function PermissionsPanel({ projectId }: PermissionsPanelProps) {
|
||||
const vm = usePermissions(projectId);
|
||||
const [target, setTarget] = useState<EditorTarget>({ type: "project" });
|
||||
@ -247,6 +260,7 @@ export function PermissionsPanel({ projectId }: PermissionsPanelProps) {
|
||||
? vm.systemDocument?.projectDefault ?? null
|
||||
: null
|
||||
}
|
||||
showCrossProjectContactable={target.type === "agent"}
|
||||
busy={vm.busy}
|
||||
onSave={(permissions) => void handleSaveNetwork(permissions)}
|
||||
/>
|
||||
@ -320,6 +334,7 @@ interface NetworkPermissionEditorProps {
|
||||
source: SystemPermissionSet | null;
|
||||
inherited: SystemPermissionSet | null;
|
||||
resolved: ResolvedAgentSystemPermissions | null;
|
||||
showCrossProjectContactable: boolean;
|
||||
busy: boolean;
|
||||
onSave: (permissions: SystemPermissionSet | null) => void;
|
||||
}
|
||||
@ -329,18 +344,27 @@ function NetworkPermissionEditor({
|
||||
source,
|
||||
inherited,
|
||||
resolved,
|
||||
showCrossProjectContactable,
|
||||
busy,
|
||||
onSave,
|
||||
}: NetworkPermissionEditorProps) {
|
||||
const current = source?.network ?? "";
|
||||
const currentCrossProjectContactable =
|
||||
source?.crossProjectContactable ?? false;
|
||||
const [local, setLocal] = useState<NetworkPolicy | "">(current);
|
||||
const [localCrossProjectContactable, setLocalCrossProjectContactable] =
|
||||
useState(currentCrossProjectContactable);
|
||||
|
||||
useEffect(() => {
|
||||
setLocal(current);
|
||||
}, [current]);
|
||||
setLocalCrossProjectContactable(currentCrossProjectContactable);
|
||||
}, [current, currentCrossProjectContactable]);
|
||||
|
||||
const readOnly = resolved?.control.mode === "readOnly";
|
||||
const changed = local !== current;
|
||||
const changed =
|
||||
local !== current ||
|
||||
(showCrossProjectContactable &&
|
||||
localCrossProjectContactable !== currentCrossProjectContactable);
|
||||
const wanted = resolved?.wanted ?? source?.network ?? inherited?.network ?? null;
|
||||
const effective = resolved?.effective ?? null;
|
||||
const runtimeLocked = resolved?.runtimeLock.state === "locked";
|
||||
@ -432,12 +456,36 @@ function NetworkPermissionEditor({
|
||||
</p>
|
||||
)}
|
||||
|
||||
{showCrossProjectContactable && (
|
||||
<label className="flex items-start gap-2 rounded-md border border-border/70 bg-raised px-3 py-2">
|
||||
<input
|
||||
type="checkbox"
|
||||
aria-label="Contactable depuis d’autres projets"
|
||||
checked={localCrossProjectContactable}
|
||||
onChange={(e) => setLocalCrossProjectContactable(e.target.checked)}
|
||||
disabled={busy || readOnly}
|
||||
className="mt-0.5 h-4 w-4 shrink-0 accent-primary disabled:cursor-not-allowed disabled:opacity-50"
|
||||
/>
|
||||
<span className="flex min-w-0 flex-col gap-1">
|
||||
<span className="text-sm font-medium text-content">
|
||||
Contactable depuis d’autres projets
|
||||
</span>
|
||||
<span className="text-xs text-muted">{CROSS_PROJECT_HELP}</span>
|
||||
</span>
|
||||
</label>
|
||||
)}
|
||||
|
||||
<div className="flex flex-wrap items-center justify-end gap-2 pt-1">
|
||||
<Button
|
||||
size="sm"
|
||||
variant="ghost"
|
||||
aria-label="Clear network"
|
||||
disabled={busy || readOnly || !source?.network}
|
||||
disabled={
|
||||
busy ||
|
||||
readOnly ||
|
||||
(!source?.network &&
|
||||
!(showCrossProjectContactable && source?.crossProjectContactable))
|
||||
}
|
||||
onClick={() => onSave(null)}
|
||||
>
|
||||
Clear
|
||||
@ -448,7 +496,14 @@ function NetworkPermissionEditor({
|
||||
aria-label="Save network"
|
||||
disabled={busy || readOnly || !changed}
|
||||
loading={busy && changed}
|
||||
onClick={() => onSave(local ? { network: local } : null)}
|
||||
onClick={() =>
|
||||
onSave(
|
||||
systemPermissionsFromDraft(
|
||||
local,
|
||||
showCrossProjectContactable && localCrossProjectContactable,
|
||||
),
|
||||
)
|
||||
}
|
||||
>
|
||||
Save
|
||||
</Button>
|
||||
|
||||
Reference in New Issue
Block a user