feat(permissions): voie projection CLI (LP0→LP3) + checkpoint Codex/input
Jalon vert regroupant deux chantiers entrelacés dans le working tree, indissociables au niveau fichier mais tous deux verts (cargo test --workspace + tests frontend permissions au vert). Permissions — voie « projection CLI » (advisory), complète : - LP0 domaine pur : modèle PermissionSet/EffectivePermissions, resolve deny-wins + postures Allow<Ask<Deny (crates/domain/src/permission.rs). - LP1 store : FsPermissionStore (.ideai/permissions.json). - LP2 use cases : Get/Update project, Update agent override, Resolve. - LP3 projecteurs Claude/Codex (settings.local.json / config.toml), câblage launch-path + PermissionProjectorRegistry, nettoyage des fichiers Replace orphelins au swap de profil (LP3-4), composition root + commandes Tauri, UI PermissionsPanel (projet + override agent). - ports.rs : PermissionStore + FileSystem::remove_file (cleanup au swap). Reste ouvert (hors scope, marqué dans le code) : LP4 enforcement OS airtight (Landlock fichiers) + résumé de permissions injecté. Inclut aussi le chantier Codex/input/sessions structurées en cours (McpConfigStrategy, StructuredAdapter, gestion d'input) partageant les mêmes fichiers (lifecycle.rs, commands.rs, dto.rs, state.rs). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@ -18,9 +18,10 @@ use application::{
|
||||
ListMemoriesInput, ListResumableAgentsInput, ListSkillsInput, LiveSessions, LoadLayoutInput,
|
||||
McpRuntime, MutateLayoutInput, OpenProjectInput, ReadAgentContextInput, ReadMemoryIndexInput,
|
||||
ReadProjectContextInput, RecallMemoryInput, ReconcileLayoutsInput, RenameLayoutInput,
|
||||
ResolveMemoryLinksInput, SetActiveLayoutInput, SnapshotRunningAgentsInput,
|
||||
SyncAgentWithTemplateInput, UnassignSkillFromAgentInput, UpdateAgentContextInput,
|
||||
UpdateMemoryInput, UpdateProjectContextInput, UpdateSkillInput,
|
||||
ResolveAgentPermissionsInput, ResolveMemoryLinksInput, SetActiveLayoutInput,
|
||||
SnapshotRunningAgentsInput, SyncAgentWithTemplateInput, UnassignSkillFromAgentInput,
|
||||
UpdateAgentContextInput, UpdateAgentPermissionsInput, UpdateMemoryInput,
|
||||
UpdateProjectContextInput, UpdateProjectPermissionsInput, UpdateSkillInput,
|
||||
};
|
||||
use domain::ports::PtyHandle;
|
||||
|
||||
@ -34,19 +35,22 @@ use crate::dto::{
|
||||
CreateLayoutResultDto, CreateMemoryRequestDto, CreateProjectRequestDto, CreateSkillRequestDto,
|
||||
CreateTemplateRequestDto, DeleteLayoutRequestDto, DeleteLayoutResultDto,
|
||||
DeliveredDelegationRequestDto, DetectProfilesRequestDto, DetectProfilesResponseDto,
|
||||
EmbedderEnginesDto, EmbedderProfileDto, EmbedderProfileListDto, ErrorDto, FirstRunStateDto,
|
||||
GitBranchesDto, GitCheckoutRequestDto, GitCommitDto, GitCommitListDto, GitCommitRequestDto,
|
||||
GitStageRequestDto, GitStatusListDto, GraphCommitListDto, HealthRequestDto, HealthResponseDto,
|
||||
InspectConversationRequestDto, InterruptAgentRequestDto, LaunchAgentRequestDto, LayoutDto,
|
||||
LayoutOperationDto, ListLayoutsDto, LiveAgentListDto, MemoryDto, MemoryIndexDto,
|
||||
MemoryLinksDto, MemoryListDto, OpenTerminalRequestDto, ProfileDto, ProfileListDto, ProjectDto,
|
||||
ProjectListDto, ReadAgentContextResponseDto, ReattachChatDto, ReattachResultDto,
|
||||
EffectivePermissionsDto, EmbedderEnginesDto, EmbedderProfileDto, EmbedderProfileListDto,
|
||||
ErrorDto, FirstRunStateDto, FrontAttachedRequestDto, GitBranchesDto, GitCheckoutRequestDto,
|
||||
GitCommitDto,
|
||||
GitCommitListDto, GitCommitRequestDto, GitStageRequestDto, GitStatusListDto,
|
||||
GraphCommitListDto, HealthRequestDto, HealthResponseDto, InspectConversationRequestDto,
|
||||
InterruptAgentRequestDto, LaunchAgentRequestDto, LayoutDto, LayoutOperationDto, ListLayoutsDto,
|
||||
LiveAgentListDto, MemoryDto, MemoryIndexDto, MemoryLinksDto, MemoryListDto,
|
||||
OpenTerminalRequestDto, ProfileDto, ProfileListDto, ProjectDto, ProjectListDto,
|
||||
ProjectPermissionsDto, ReadAgentContextResponseDto, ReattachChatDto, ReattachResultDto,
|
||||
RecallMemoryRequestDto, RenameLayoutRequestDto, ReplyChunk, ResizeTerminalRequestDto,
|
||||
ResumableAgentListDto, SaveEmbedderProfileRequestDto, SaveProfileRequestDto,
|
||||
SetActiveLayoutRequestDto, SkillDto, SkillListDto, SyncAgentWithTemplateRequestDto,
|
||||
SyncResultDto, TemplateDto, TemplateListDto, TerminalClosedDto, TerminalSessionDto,
|
||||
UnassignSkillRequestDto, UpdateAgentContextRequestDto, UpdateMemoryRequestDto,
|
||||
UpdateProjectContextRequestDto, UpdateSkillRequestDto, UpdateTemplateRequestDto,
|
||||
ResolveAgentPermissionsRequestDto, ResumableAgentListDto, SaveEmbedderProfileRequestDto,
|
||||
SaveProfileRequestDto, SetActiveLayoutRequestDto, SkillDto, SkillListDto,
|
||||
SyncAgentWithTemplateRequestDto, SyncResultDto, TemplateDto, TemplateListDto,
|
||||
TerminalClosedDto, TerminalSessionDto, UnassignSkillRequestDto, UpdateAgentContextRequestDto,
|
||||
UpdateAgentPermissionsRequestDto, UpdateMemoryRequestDto, UpdateProjectContextRequestDto,
|
||||
UpdateProjectPermissionsRequestDto, UpdateSkillRequestDto, UpdateTemplateRequestDto,
|
||||
WriteTerminalRequestDto,
|
||||
};
|
||||
use crate::pty::{PtyBridge, PtyChunk};
|
||||
@ -208,6 +212,87 @@ pub async fn update_project_context(
|
||||
.map_err(ErrorDto::from)
|
||||
}
|
||||
|
||||
/// `get_project_permissions` — read `.ideai/permissions.json`.
|
||||
///
|
||||
/// # Errors
|
||||
/// Returns an [`ErrorDto`] on invalid project id or store failure.
|
||||
#[tauri::command]
|
||||
pub async fn get_project_permissions(
|
||||
project_id: String,
|
||||
state: State<'_, AppState>,
|
||||
) -> Result<ProjectPermissionsDto, ErrorDto> {
|
||||
let project = resolve_project(&project_id, &state).await?;
|
||||
state
|
||||
.get_project_permissions
|
||||
.execute(application::GetProjectPermissionsInput { project })
|
||||
.await
|
||||
.map(|out| ProjectPermissionsDto(out.permissions))
|
||||
.map_err(ErrorDto::from)
|
||||
}
|
||||
|
||||
/// `update_project_permissions` — replace project default permissions.
|
||||
///
|
||||
/// # Errors
|
||||
/// Returns an [`ErrorDto`] on invalid project id or store failure.
|
||||
#[tauri::command]
|
||||
pub async fn update_project_permissions(
|
||||
request: UpdateProjectPermissionsRequestDto,
|
||||
state: State<'_, AppState>,
|
||||
) -> Result<ProjectPermissionsDto, ErrorDto> {
|
||||
let project = resolve_project(&request.project_id, &state).await?;
|
||||
state
|
||||
.update_project_permissions
|
||||
.execute(UpdateProjectPermissionsInput {
|
||||
project,
|
||||
permissions: request.permissions,
|
||||
})
|
||||
.await
|
||||
.map(|out| ProjectPermissionsDto(out.permissions))
|
||||
.map_err(ErrorDto::from)
|
||||
}
|
||||
|
||||
/// `update_agent_permissions` — replace or remove one agent override.
|
||||
///
|
||||
/// # Errors
|
||||
/// Returns an [`ErrorDto`] on invalid ids or store failure.
|
||||
#[tauri::command]
|
||||
pub async fn update_agent_permissions(
|
||||
request: UpdateAgentPermissionsRequestDto,
|
||||
state: State<'_, AppState>,
|
||||
) -> Result<ProjectPermissionsDto, ErrorDto> {
|
||||
let project = resolve_project(&request.project_id, &state).await?;
|
||||
let agent_id = parse_agent_id(&request.agent_id)?;
|
||||
state
|
||||
.update_agent_permissions
|
||||
.execute(UpdateAgentPermissionsInput {
|
||||
project,
|
||||
agent_id,
|
||||
permissions: request.permissions,
|
||||
})
|
||||
.await
|
||||
.map(|out| ProjectPermissionsDto(out.permissions))
|
||||
.map_err(ErrorDto::from)
|
||||
}
|
||||
|
||||
/// `resolve_agent_permissions` — resolve project defaults plus agent override.
|
||||
///
|
||||
/// # Errors
|
||||
/// Returns an [`ErrorDto`] on invalid ids or store failure.
|
||||
#[tauri::command]
|
||||
pub async fn resolve_agent_permissions(
|
||||
request: ResolveAgentPermissionsRequestDto,
|
||||
state: State<'_, AppState>,
|
||||
) -> Result<Option<EffectivePermissionsDto>, ErrorDto> {
|
||||
let project = resolve_project(&request.project_id, &state).await?;
|
||||
let agent_id = parse_agent_id(&request.agent_id)?;
|
||||
state
|
||||
.resolve_agent_permissions
|
||||
.execute(ResolveAgentPermissionsInput { project, agent_id })
|
||||
.await
|
||||
.map(|out| out.effective.map(EffectivePermissionsDto))
|
||||
.map_err(ErrorDto::from)
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Terminals (L3)
|
||||
// ---------------------------------------------------------------------------
|
||||
@ -1263,6 +1348,29 @@ pub async fn delegation_delivered(
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// `set_front_attached` — the write-portal reports whether a **frontend terminal cell**
|
||||
/// is mounted for an agent (mount ⇒ `true`, unmount ⇒ `false`).
|
||||
///
|
||||
/// Routes to [`OrchestratorService::set_agent_front_attached`]. This is what lets the
|
||||
/// mediator deliver a turn to a **headless** (background-delegated, cell-less) agent by
|
||||
/// writing its PTY itself: with no mounted cell nobody consumes `DelegationReady`, so
|
||||
/// the task would otherwise be lost (a delegated agent that never receives — and never
|
||||
/// answers — its task). An agent **with** a cell keeps the frontend write-portal path.
|
||||
///
|
||||
/// # Errors
|
||||
/// Returns an [`ErrorDto`] (`INVALID`) for a malformed agent id.
|
||||
#[tauri::command]
|
||||
pub async fn set_front_attached(
|
||||
request: FrontAttachedRequestDto,
|
||||
state: State<'_, AppState>,
|
||||
) -> Result<(), ErrorDto> {
|
||||
let agent_id = parse_agent_id(&request.agent_id)?;
|
||||
state
|
||||
.orchestrator_service
|
||||
.set_agent_front_attached(agent_id, request.attached);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// `reattach_agent_chat` — re-bind a view to a **still-living** structured session
|
||||
/// without re-sending or re-spawning it (ARCHITECTURE §17.6/§17.7).
|
||||
///
|
||||
|
||||
@ -1066,7 +1066,7 @@ use application::{
|
||||
ChangeAgentProfileOutput, CreateAgentOutput, InspectConversationOutput, LaunchAgentOutput,
|
||||
ListAgentsOutput, ReadAgentContextOutput,
|
||||
};
|
||||
use domain::{Agent, TerminalSession};
|
||||
use domain::{Agent, EffectivePermissions, PermissionSet, ProjectPermissions, TerminalSession};
|
||||
|
||||
/// An agent crossing the wire. [`Agent`] already serialises camelCase
|
||||
/// (`id`, `name`, `contextPath`, `profileId`, `origin` tagged, `synchronized`),
|
||||
@ -1135,6 +1135,52 @@ pub struct UpdateAgentContextRequestDto {
|
||||
pub content: String,
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Permissions (LP1)
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
/// Full project permission document crossing the wire.
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
#[serde(transparent)]
|
||||
pub struct ProjectPermissionsDto(pub ProjectPermissions);
|
||||
|
||||
/// Effective permissions crossing the wire.
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
#[serde(transparent)]
|
||||
pub struct EffectivePermissionsDto(pub EffectivePermissions);
|
||||
|
||||
/// Request DTO for updating project default permissions.
|
||||
#[derive(Debug, Clone, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct UpdateProjectPermissionsRequestDto {
|
||||
/// Id of the owning project.
|
||||
pub project_id: String,
|
||||
/// New project defaults. `null` removes defaults.
|
||||
pub permissions: Option<PermissionSet>,
|
||||
}
|
||||
|
||||
/// Request DTO for updating one agent permission override.
|
||||
#[derive(Debug, Clone, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct UpdateAgentPermissionsRequestDto {
|
||||
/// Id of the owning project.
|
||||
pub project_id: String,
|
||||
/// Target agent id.
|
||||
pub agent_id: String,
|
||||
/// New override. `null` removes the override.
|
||||
pub permissions: Option<PermissionSet>,
|
||||
}
|
||||
|
||||
/// Request DTO for resolving one agent's effective permissions.
|
||||
#[derive(Debug, Clone, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct ResolveAgentPermissionsRequestDto {
|
||||
/// Id of the owning project.
|
||||
pub project_id: String,
|
||||
/// Target agent id.
|
||||
pub agent_id: String,
|
||||
}
|
||||
|
||||
/// Request DTO for `update_project_context`.
|
||||
#[derive(Debug, Clone, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
@ -1224,6 +1270,21 @@ pub struct DeliveredDelegationRequestDto {
|
||||
pub ticket: String,
|
||||
}
|
||||
|
||||
/// Request DTO for `set_front_attached`: the write-portal of an agent cell reports
|
||||
/// whether a **frontend terminal cell is mounted** for `agentId` (`true` on mount,
|
||||
/// `false` on unmount). The mediator uses it to choose, at delivery time, between
|
||||
/// publishing `DelegationReady` (a cell will write it) and writing the turn into the
|
||||
/// PTY itself (headless/background-delegated agent with no cell). The frontend sends
|
||||
/// `{ request: { agentId, attached } }`.
|
||||
#[derive(Debug, Clone, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct FrontAttachedRequestDto {
|
||||
/// Id of the agent whose terminal cell mounted/unmounted.
|
||||
pub agent_id: String,
|
||||
/// `true` when the cell's write-portal is now active, `false` on teardown.
|
||||
pub attached: bool,
|
||||
}
|
||||
|
||||
/// Request DTO for `change_agent_profile` (§15.1): hot-swap an agent's runtime
|
||||
/// profile, optionally relaunching its live session in place.
|
||||
#[derive(Debug, Clone, Deserialize)]
|
||||
|
||||
@ -127,6 +127,10 @@ pub fn run() {
|
||||
commands::list_projects,
|
||||
commands::read_project_context,
|
||||
commands::update_project_context,
|
||||
commands::get_project_permissions,
|
||||
commands::update_project_permissions,
|
||||
commands::update_agent_permissions,
|
||||
commands::resolve_agent_permissions,
|
||||
commands::open_terminal,
|
||||
commands::write_terminal,
|
||||
commands::resize_terminal,
|
||||
@ -163,6 +167,7 @@ pub fn run() {
|
||||
commands::agent_send,
|
||||
commands::interrupt_agent,
|
||||
commands::delegation_delivered,
|
||||
commands::set_front_attached,
|
||||
commands::reattach_agent_chat,
|
||||
commands::close_agent_session,
|
||||
commands::list_resumable_agents,
|
||||
|
||||
@ -17,23 +17,25 @@ use application::{
|
||||
CreateLayout, CreateMemory, CreateProject, CreateSkill, CreateTemplate, DeleteAgent,
|
||||
DeleteEmbedderProfile, DeleteLayout, DeleteMemory, DeleteProfile, DeleteSkill, DeleteTemplate,
|
||||
DescribeEmbedderEngines, DetectAgentDrift, DetectProfiles, DismissEmbedderSuggestion,
|
||||
FirstRunState, GetMemory, GitBranches, GitCheckout, GitCommit, GitGraph, GitInit, GitLog,
|
||||
GitStage, GitStatus, GitUnstage, HealthUseCase, InspectConversation, LaunchAgent, ListAgents,
|
||||
ListAgentsInput, ListEmbedderProfiles, ListLayouts, ListMemories, ListProfiles, ListProjects,
|
||||
ListResumableAgents, ListSkills, ListTemplates, LiveAgentRegistry, LoadLayout, McpRuntime,
|
||||
MoveTabToNewWindow, MutateLayout, OnnxModelView, OpenProject, OpenTerminal,
|
||||
OrchestratorService, ReadAgentContext, ReadMemoryIndex, ReadProjectContext, RecallMemory,
|
||||
ReconcileLayouts, RecordTurn, RecordTurnProvider, ReferenceProfiles, RenameLayout,
|
||||
ResizeTerminal, ResolveMemoryLinks, SaveEmbedderProfile, SaveProfile, SetActiveLayout,
|
||||
SnapshotRunningAgents, StructuredSessions, SuggestedThisSession, SyncAgentWithTemplate,
|
||||
TerminalSessions, UnassignSkillFromAgent, UpdateAgentContext, UpdateMemory,
|
||||
UpdateProjectContext, UpdateSkill, UpdateTemplate, WriteToTerminal, AGENT_MEMORY_RECALL_BUDGET,
|
||||
FirstRunState, GetMemory, GetProjectPermissions, GitBranches, GitCheckout, GitCommit, GitGraph,
|
||||
GitInit, GitLog, GitStage, GitStatus, GitUnstage, HealthUseCase, InspectConversation,
|
||||
LaunchAgent, ListAgents, ListAgentsInput, ListEmbedderProfiles, ListLayouts, ListMemories,
|
||||
ListProfiles, ListProjects, ListResumableAgents, ListSkills, ListTemplates, LiveAgentRegistry,
|
||||
LoadLayout, McpRuntime, MoveTabToNewWindow, MutateLayout, OnnxModelView, OpenProject,
|
||||
PermissionProjectorRegistry,
|
||||
OpenTerminal, OrchestratorService, ReadAgentContext, ReadMemoryIndex, ReadProjectContext,
|
||||
RecallMemory, ReconcileLayouts, RecordTurn, RecordTurnProvider, ReferenceProfiles,
|
||||
RenameLayout, ResizeTerminal, ResolveAgentPermissions, ResolveMemoryLinks, SaveEmbedderProfile,
|
||||
SaveProfile, SetActiveLayout, SnapshotRunningAgents, StructuredSessions, SuggestedThisSession,
|
||||
SyncAgentWithTemplate, TerminalSessions, UnassignSkillFromAgent, UpdateAgentContext,
|
||||
UpdateAgentPermissions, UpdateMemory, UpdateProjectContext, UpdateProjectPermissions,
|
||||
UpdateSkill, UpdateTemplate, WriteToTerminal, AGENT_MEMORY_RECALL_BUDGET,
|
||||
};
|
||||
use domain::ports::{
|
||||
AgentContextStore, AgentRuntime, AgentSessionFactory, Clock, Embedder, EmbedderEnvInspector,
|
||||
EmbedderProfileStore, EmbedderPromptStore, EventBus, FileSystem, GitPort, IdGenerator,
|
||||
MemoryRecall, MemoryStore, ProcessSpawner, ProfileStore, ProjectStore, PtyPort, SkillStore,
|
||||
TemplateStore,
|
||||
MemoryRecall, MemoryStore, PermissionStore, ProcessSpawner, ProfileStore, ProjectStore,
|
||||
PtyPort, SkillStore, TemplateStore,
|
||||
};
|
||||
use domain::profile::{
|
||||
AgentProfile, ContextInjection, McpConfigStrategy, McpTransport, StructuredAdapter,
|
||||
@ -44,10 +46,11 @@ use serde_json::{json, Map, Value};
|
||||
use uuid::Uuid;
|
||||
|
||||
use infrastructure::{
|
||||
embedder_from_profile, AdaptiveMemoryRecall, ClaudeTranscriptInspector, CliAgentRuntime,
|
||||
embedder_from_profile, AdaptiveMemoryRecall, ClaudePermissionProjector,
|
||||
ClaudeTranscriptInspector, CliAgentRuntime, CodexPermissionProjector,
|
||||
EmbedderEnvProbe, FsConversationLog, FsEmbedderProfileStore, FsEmbedderPromptStore,
|
||||
FsHandoffStore, FsMemoryStore, FsOrchestratorWatcher, FsProfileStore, FsProjectStore,
|
||||
FsProviderSessionStore, FsSkillStore, FsTemplateStore, Git2Repository,
|
||||
FsHandoffStore, FsMemoryStore, FsOrchestratorWatcher, FsPermissionStore, FsProfileStore,
|
||||
FsProjectStore, FsProviderSessionStore, FsSkillStore, FsTemplateStore, Git2Repository,
|
||||
HeuristicHandoffSummarizer, IdeaiContextStore, InMemoryConversationRegistry, InMemoryMailbox,
|
||||
LocalFileSystem, LocalProcessSpawner, McpServer, MediatedInbox, NaiveMemoryRecall,
|
||||
OrchestratorWatchHandle, PortablePtyAdapter, StructuredSessionFactory, SystemClock,
|
||||
@ -230,6 +233,14 @@ pub struct AppState {
|
||||
pub inspect_conversation: Arc<InspectConversation>,
|
||||
/// Project registry — used by agent commands to resolve a `Project` from an id.
|
||||
pub project_store: Arc<dyn ProjectStore>,
|
||||
/// Read the project permission document.
|
||||
pub get_project_permissions: Arc<GetProjectPermissions>,
|
||||
/// Update project-level default permissions.
|
||||
pub update_project_permissions: Arc<UpdateProjectPermissions>,
|
||||
/// Update one agent permission override.
|
||||
pub update_agent_permissions: Arc<UpdateAgentPermissions>,
|
||||
/// Resolve effective permissions for one agent.
|
||||
pub resolve_agent_permissions: Arc<ResolveAgentPermissions>,
|
||||
// --- Windows (L10) ---
|
||||
/// Detach a tab into a new OS window (persists the workspace topology).
|
||||
pub move_tab: Arc<MoveTabToNewWindow>,
|
||||
@ -498,6 +509,10 @@ impl AppState {
|
||||
let contexts = Arc::new(IdeaiContextStore::new(Arc::clone(&fs_port)));
|
||||
let contexts_port = Arc::clone(&contexts) as Arc<dyn AgentContextStore>;
|
||||
|
||||
// --- Project permissions (LP1) ---
|
||||
let permission_store = Arc::new(FsPermissionStore::new(Arc::clone(&fs_port)));
|
||||
let permission_store_port = Arc::clone(&permission_store) as Arc<dyn PermissionStore>;
|
||||
|
||||
// --- Skill store (L12) ---
|
||||
// Global skills live in the machine-local app-data dir; project skills are
|
||||
// resolved per call from each project's `.ideai/` (so one store serves all
|
||||
@ -632,6 +647,20 @@ impl AppState {
|
||||
// PTH : tout profil (Claude/Codex inclus) ouvre une cellule terminal. Le code
|
||||
// `launch_structured` reste en place (mort-code retiré au lot de nettoyage B-6).
|
||||
let _session_factory = session_factory; // décâblé en B-2 (nettoyage B-6)
|
||||
|
||||
// --- Permission projectors (lot LP3-5) ---
|
||||
// UN seul registre, source unique de vérité, injecté à l'identique dans
|
||||
// `LaunchAgent` (projection au lancement) ET `ChangeAgentProfile` (nettoyage des
|
||||
// fichiers orphelins au swap). Les deux projecteurs concrets vivent dans
|
||||
// l'infrastructure, keyés par leur `ProjectorKey` via `with(...)`.
|
||||
let permission_projectors = Arc::new(
|
||||
PermissionProjectorRegistry::new()
|
||||
.with(Arc::new(ClaudePermissionProjector)
|
||||
as Arc<dyn domain::PermissionProjector>)
|
||||
.with(Arc::new(CodexPermissionProjector)
|
||||
as Arc<dyn domain::PermissionProjector>),
|
||||
);
|
||||
|
||||
let launch_agent = Arc::new(
|
||||
LaunchAgent::new(
|
||||
Arc::clone(&contexts_port),
|
||||
@ -646,6 +675,7 @@ impl AppState {
|
||||
Arc::clone(&memory_recall_port),
|
||||
Some(Arc::clone(&check_embedder_suggestion)),
|
||||
)
|
||||
.with_permission_store(Arc::clone(&permission_store_port))
|
||||
// Reprise conversationnelle (lot P7) : à chaque (re)lancement, si la cellule
|
||||
// porte une conversation et qu'un handoff existe (`<root>/.ideai/conversations/`),
|
||||
// son résumé est réinjecté dans le convention file. Best-effort, additif :
|
||||
@ -658,7 +688,11 @@ impl AppState {
|
||||
// `<root>/.ideai/conversations/providers.json`. Best-effort, additif : une
|
||||
// écriture en échec / pas d'id moteur ⇒ lancement normal, aucune écriture.
|
||||
.with_provider_session_provider(Arc::new(AppProviderSessionProvider)
|
||||
as Arc<dyn application::ProviderSessionProvider>),
|
||||
as Arc<dyn application::ProviderSessionProvider>)
|
||||
// Projection des permissions au (re)lancement (lot LP3-5) : avec le
|
||||
// permission store câblé ci-dessus, `resolve` a une source ⇒ le projecteur
|
||||
// du profil matérialise la config de permission de la CLI dans le run dir.
|
||||
.with_permission_projectors(Arc::clone(&permission_projectors)),
|
||||
);
|
||||
|
||||
// Hot-swap an agent's runtime profile (§15.1). Reuses the shared context/
|
||||
@ -676,7 +710,10 @@ impl AppState {
|
||||
Arc::clone(&launch_agent),
|
||||
Arc::clone(&events_port),
|
||||
)
|
||||
.with_structured(Arc::clone(&structured_sessions)),
|
||||
.with_structured(Arc::clone(&structured_sessions))
|
||||
// Même registre que `LaunchAgent` (lot LP3-5) : au swap cross-profile, on
|
||||
// nettoie les fichiers `Replace` orphelins de l'ancien profil avant relance.
|
||||
.with_permission_projectors(Arc::clone(&permission_projectors)),
|
||||
);
|
||||
|
||||
// Read-only inventory of resumable agent cells (§15.2). Reuses the shared
|
||||
@ -707,6 +744,18 @@ impl AppState {
|
||||
));
|
||||
|
||||
let project_store = Arc::clone(&store_port);
|
||||
let get_project_permissions = Arc::new(GetProjectPermissions::new(Arc::clone(
|
||||
&permission_store_port,
|
||||
)));
|
||||
let update_project_permissions = Arc::new(UpdateProjectPermissions::new(Arc::clone(
|
||||
&permission_store_port,
|
||||
)));
|
||||
let update_agent_permissions = Arc::new(UpdateAgentPermissions::new(Arc::clone(
|
||||
&permission_store_port,
|
||||
)));
|
||||
let resolve_agent_permissions = Arc::new(ResolveAgentPermissions::new(Arc::clone(
|
||||
&permission_store_port,
|
||||
)));
|
||||
|
||||
// --- Template store + use cases (L7) ---
|
||||
let template_store = Arc::new(FsTemplateStore::new(
|
||||
@ -856,8 +905,7 @@ impl AppState {
|
||||
}
|
||||
});
|
||||
}
|
||||
let input_mediator =
|
||||
Arc::clone(&mediated_inbox) as Arc<dyn domain::input::InputMediator>;
|
||||
let input_mediator = Arc::clone(&mediated_inbox) as Arc<dyn domain::input::InputMediator>;
|
||||
// Registre des conversations par paire (cadrage C3) : un fil par paire, session
|
||||
// vivante keyée par conversation (lève l'ambiguïté session/agent).
|
||||
let conversation_registry = Arc::new(InMemoryConversationRegistry::new())
|
||||
@ -957,6 +1005,10 @@ impl AppState {
|
||||
list_resumable_agents,
|
||||
inspect_conversation,
|
||||
project_store,
|
||||
get_project_permissions,
|
||||
update_project_permissions,
|
||||
update_agent_permissions,
|
||||
resolve_agent_permissions,
|
||||
create_template,
|
||||
update_template,
|
||||
list_templates,
|
||||
@ -1069,9 +1121,20 @@ impl AppState {
|
||||
// The project-id string the handshake guard compares against: the same
|
||||
// hyphen-free hex form the endpoint encodes, which M5d's `--project` reuses.
|
||||
let project_id = project.id.as_uuid().simple().to_string();
|
||||
// Readiness de démarrage : quand le pont MCP d'un agent se connecte (initialize),
|
||||
// libère son éventuel 1er tour différé (fix race cold-launch via signal MCP).
|
||||
// L'id arrive en hex (handshake `requester`) ⇒ on le parse en AgentId ici (la
|
||||
// composition root est la seule à connaître la frontière infra↔domaine).
|
||||
let service_for_ready = Arc::clone(&self.orchestrator_service);
|
||||
let ready_sink: Arc<dyn Fn(&str) + Send + Sync> = Arc::new(move |requester: &str| {
|
||||
if let Ok(uuid) = Uuid::parse_str(requester) {
|
||||
service_for_ready.release_agent_cold_start(AgentId::from_uuid(uuid));
|
||||
}
|
||||
});
|
||||
let handle = McpServerHandle::start(
|
||||
McpServer::new(Arc::clone(&self.orchestrator_service), project.clone())
|
||||
.with_events(events),
|
||||
.with_events(events)
|
||||
.with_ready_sink(ready_sink),
|
||||
endpoint,
|
||||
listener,
|
||||
project_id,
|
||||
@ -1355,7 +1418,10 @@ fn codex_mcp_config_target(profile: &AgentProfile) -> Option<(&str, &str)> {
|
||||
/// l'encodeur TOML partagé [`domain::McpServerWiring::to_config_toml`] (D2) — même
|
||||
/// source de wiring que la déclaration `.mcp.json` Claude, donc zéro dérive.
|
||||
fn mcp_server_entry_toml(profile: &AgentProfile, runtime: Option<&McpRuntime>) -> String {
|
||||
let transport = profile.mcp.as_ref().map_or(McpTransport::Stdio, |m| m.transport);
|
||||
let transport = profile
|
||||
.mcp
|
||||
.as_ref()
|
||||
.map_or(McpTransport::Stdio, |m| m.transport);
|
||||
let (command, args) = match runtime {
|
||||
Some(rt) => (
|
||||
rt.exe.clone(),
|
||||
|
||||
Reference in New Issue
Block a user