feat(wave): #119/#122/#131/#132 verts + sprint plugins ESM/persistance #135/#136/#139

État d'intégration confiné à la branche batch. Les tickets #119 (skills →
capacités agent découvrables), #122 (override permissions par défaut), #131
(effort par agent/presets) et #132 (outil MCP d'édition du contexte projet)
sont verts en périmètre. Le sprint plugins multi-fichiers ESM / persistance
plugin-owned (#135/#136/#139) est co-implémenté dans les MÊMES fichiers de
câblage (frontend/src/ports/index.ts, backend/src/lib.rs, domain/ports.rs,
backend/dto.rs), inséparable sans staging interactif (indisponible ici).

Commit unique volontaire : préserve l'état vert QA sans découpe hunk risquée.
NON mergé vers develop tant que #137 (QA e2e plugins) n'est pas vert.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
2026-08-03 11:06:23 +02:00
parent 22c6bd803d
commit 171c6c923c
59 changed files with 3654 additions and 291 deletions

View File

@ -8,8 +8,10 @@ import type {
PermissionPosture,
PermissionRule,
PermissionSet,
PermissionShadowReport,
ProjectPermissions,
ProjectSystemPermissions,
ResolvedAgentPermissions,
ResolvedAgentSystemPermissions,
SystemPermissionSet,
} from "@/domain";
@ -27,6 +29,7 @@ export interface PolicyDraft {
export interface AgentPermissionRow {
agent: Agent;
override: PermissionSet | null;
shadowed: PermissionShadowReport | null;
systemOverride: SystemPermissionSet | null;
resolvedSystem: ResolvedAgentSystemPermissions | null;
}
@ -132,6 +135,9 @@ export function usePermissions(projectId: string): PermissionsViewModel {
const [resolvedSystemByAgent, setResolvedSystemByAgent] = useState<
Record<string, ResolvedAgentSystemPermissions>
>({});
const [resolvedPermissionsByAgent, setResolvedPermissionsByAgent] = useState<
Record<string, ResolvedAgentPermissions>
>({});
const [busy, setBusy] = useState(false);
const [error, setError] = useState<string | null>(null);
@ -156,9 +162,29 @@ export function usePermissions(projectId: string): PermissionsViewModel {
}
}),
);
const resolvedPermissionPairs = await Promise.all(
agentList.map(async (candidate) => {
try {
return [
candidate.id,
await permission.resolveAgentPermissions(projectId, candidate.id),
] as const;
} catch {
return [candidate.id, null] as const;
}
}),
);
setAgents(agentList);
setDocument(permissionDoc);
setSystemDocument(systemPermissionDoc);
setResolvedPermissionsByAgent(
Object.fromEntries(
resolvedPermissionPairs.filter(
(pair): pair is readonly [string, ResolvedAgentPermissions] =>
pair[1] !== null,
),
),
);
setResolvedSystemByAgent(
Object.fromEntries(
resolvedPairs.filter(
@ -191,10 +217,17 @@ export function usePermissions(projectId: string): PermissionsViewModel {
return agents.map((candidate) => ({
agent: candidate,
override: overrides.get(candidate.id) ?? null,
shadowed: resolvedPermissionsByAgent[candidate.id]?.shadowed ?? null,
systemOverride: systemOverrides.get(candidate.id) ?? null,
resolvedSystem: resolvedSystemByAgent[candidate.id] ?? null,
}));
}, [agents, document, systemDocument, resolvedSystemByAgent]);
}, [
agents,
document,
systemDocument,
resolvedPermissionsByAgent,
resolvedSystemByAgent,
]);
const projectDraft = useMemo(
() => draftFromSet(document?.projectDefaults ?? null),