feat(domain,application,infrastructure,backend,app-tauri,web-server,frontend): restaurer les projets ouverts au redémarrage dans l'ordre mémorisé — #303 (état versionné OpenProjectRestoreState persisté sous <app_data_dir>/open-projects.json via port dédié OpenProjectRestoreStore, distinct du registre projects.json et sans recycler workspace.json; sept use cases cadrés: read/record opened/record closed/save order/restore/retry/remove, mutations sérialisées par mutex applicatif contre les lost updates, tombstones conservés à leur position avec lastFailure et codes stables notFound/inaccessible/store/invalid; prévalidation root par metadata+listing sans interrompre les autres restaurations; activation centralisée BackendCore::activate_project réutilisée par ouverture Tauri/HTTP, création, restore et retry: reconciliation layouts/live-state/background tasks, watcher orchestrateur + serveur MCP, réparation run dirs Claude; cinq commandes exposées Tauri et HTTP avec contrats DTO identiques: restore_open_projects, retry_open_project_restore, remove_project_from_restore, save_open_project_restore_order, read_open_project_restore_state; frontend: DTO de reprise + ProjectGateway étendu, useProjects initialise par listProjects+restoreOpenProjects et reconstruit openTabs dans l'ordre opened sans notification quand tout réussit, notification unique singulier/pluriel avec Voir les détails/Réessayer, détail par projet avec cause française et Retirer de la reprise, retry réussi réinséré à la position mémorisée, onglet conservé si closeProject échoué; saveOpenProjectRestoreOrder exposé non appelé car l'UI ne permet pas encore de réordonnancer; QA verte reconfirmée par Git: rustup run stable cargo fmt --all -- --check, git diff --check, cargo test -p backend open_project_restore_tests 1/1 couvrant absence initiale/ordre/fermeture/inaccessible/retry/notFound/remove/watcher+MCP, cargo test -p web-server authorized_open_project_restore_commands_share_the_tauri_contract 1/1, cargo test -p domain 311+ suites vertes, cargo check -p application -p infrastructure -p backend -p app-tauri -p web-server OK, npm run typecheck OK, npm test ciblé 3 fichiers/37 tests OK, npm run build OK; réserves hors périmètre documentées au carnet: application tests/layout_usecases.rs 2 échecs fixtures plugin package NotFound, infrastructure 1 timing test cancellation process, npm test large 32 échecs web shell/LS7/tickets web — préexistants sans lien avec #303; restant: rebuild AppImage demandé après validation puis validation live fermeture/relance; fichiers tickets/296/*, tickets/299-303/*, counter/index, attachments/ et scripts/ hors périmètre; aucun push)
This commit is contained in:
@ -22,16 +22,15 @@ use application::{
|
||||
GitInitInput, GitLogInput, GitStagePathInput, GitStatusInput, InspectConversationInput,
|
||||
LaunchAgentInput, ListAgentsInput, ListDevicesInput, ListLayoutsInput, ListMemoriesInput,
|
||||
ListResumableAgentsInput, ListSkillsInput, ListSlashCommandsInput, LiveSessions,
|
||||
LoadLayoutInput, McpRuntime, MutateLayoutInput, OpenPluginLayoutWindowInput, OpenProjectInput,
|
||||
LoadLayoutInput, McpRuntime, MutateLayoutInput, OpenPluginLayoutWindowInput,
|
||||
ReadAgentContextInput, ReadConversationPageInput, ReadMcpToolPermissionsInput,
|
||||
ReadMemoryIndexInput, ReadProjectContextInput, RecallMemoryInput, ReconcileLayoutsInput,
|
||||
ReconcileLiveStateInput, RenameDeviceInput, RenameLayoutInput, ResolveAgentPermissionsInput,
|
||||
ResolveAgentSystemPermissionsInput, ResolveMemoryLinksInput, RevokeDeviceInput,
|
||||
RotateConversationLogInput, SaveChatDraftInput, SetActiveLayoutInput,
|
||||
SetAgentMcpToolAllowedInput, SlashCommandEffect, SnapshotRunningAgentsInput,
|
||||
StopLiveAgentInput, SyncAgentWithTemplateInput, UnassignSkillFromAgentInput,
|
||||
UpdateAgentContactableInput, UpdateAgentContextInput, UpdateAgentEffortInput,
|
||||
UpdateAgentMcpToolPermissionsInput, UpdateAgentPermissionsInput,
|
||||
ReadMemoryIndexInput, ReadProjectContextInput, RecallMemoryInput, RenameDeviceInput,
|
||||
RenameLayoutInput, ResolveAgentPermissionsInput, ResolveAgentSystemPermissionsInput,
|
||||
ResolveMemoryLinksInput, RevokeDeviceInput, RotateConversationLogInput, SaveChatDraftInput,
|
||||
SetActiveLayoutInput, SetAgentMcpToolAllowedInput, SlashCommandEffect,
|
||||
SnapshotRunningAgentsInput, StopLiveAgentInput, SyncAgentWithTemplateInput,
|
||||
UnassignSkillFromAgentInput, UpdateAgentContactableInput, UpdateAgentContextInput,
|
||||
UpdateAgentEffortInput, UpdateAgentMcpToolPermissionsInput, UpdateAgentPermissionsInput,
|
||||
UpdateAgentSystemPermissionsInput, UpdateMemoryInput, UpdateProjectContextInput,
|
||||
UpdateProjectMcpToolPermissionsInput, UpdateProjectOrchestratorInput,
|
||||
UpdateProjectPermissionsInput, UpdateProjectSystemPermissionsInput, UpdateSkillInput,
|
||||
@ -65,20 +64,22 @@ use crate::dto::{
|
||||
LayoutOperationDto, ListLayoutsDto, LiveAgentListDto, ManagedTempGcResponseDto,
|
||||
ManagedTempHandleRequestDto, ManagedTempLeaseDto, ManagedTempLeaseResponseDto, MemoryDto,
|
||||
MemoryIndexDto, MemoryLinksDto, MemoryListDto, ModelServerConfigDto, ModelServerConfigListDto,
|
||||
OpenCodeProviderListDto, OpenTerminalRequestDto, PreviewModelServerCommandDto, ProfileDto,
|
||||
ProfileListDto, ProfileModelCatalogDto, ProjectDto, ProjectListDto,
|
||||
ProjectMcpToolPermissionsDto, ProjectPermissionsDto, ProjectSystemPermissionsDto,
|
||||
ProjectWorkStateDto, ReadAgentContextResponseDto, ReadChatDraftResponseDto,
|
||||
ReadConversationPageRequestDto, ReattachChatDto, ReattachResultDto, RecallMemoryRequestDto,
|
||||
RenameLayoutRequestDto, ReplyChunk, ResizeTerminalRequestDto,
|
||||
ResolveAgentPermissionsRequestDto, ResolveAgentPermissionsResponseDto,
|
||||
ResolveAgentSystemPermissionsRequestDto, ResolvedAgentSystemPermissionsDto,
|
||||
ResumableAgentListDto, SaveChatDraftRequestDto, SaveEmbedderProfileRequestDto,
|
||||
SaveModelServerRequestDto, SaveOpenCodeProviderProfileRequestDto, SaveProfileRequestDto,
|
||||
SetActiveLayoutRequestDto, SetActiveLayoutResultDto, SetAgentMcpToolAllowedRequestDto,
|
||||
SkillDto, SkillListDto, SlashCommandEffectDto, SlashCommandListDto, StopLiveAgentRequestDto,
|
||||
StopLiveAgentResponseDto, SyncAgentWithTemplateRequestDto, SyncResultDto, TemplateDto,
|
||||
TemplateListDto, TerminalClosedDto, TerminalSessionDto, TurnPageDto, UnassignSkillRequestDto,
|
||||
OpenCodeProviderListDto, OpenProjectRestoreStateDto, OpenTerminalRequestDto,
|
||||
PreviewModelServerCommandDto, ProfileDto, ProfileListDto, ProfileModelCatalogDto, ProjectDto,
|
||||
ProjectListDto, ProjectMcpToolPermissionsDto, ProjectPermissionsDto,
|
||||
ProjectSystemPermissionsDto, ProjectWorkStateDto, ReadAgentContextResponseDto,
|
||||
ReadChatDraftResponseDto, ReadConversationPageRequestDto, ReattachChatDto, ReattachResultDto,
|
||||
RecallMemoryRequestDto, RemoveOpenProjectRestoreEntryRequestDto, RenameLayoutRequestDto,
|
||||
ReplyChunk, ResizeTerminalRequestDto, ResolveAgentPermissionsRequestDto,
|
||||
ResolveAgentPermissionsResponseDto, ResolveAgentSystemPermissionsRequestDto,
|
||||
ResolvedAgentSystemPermissionsDto, RestoreOpenProjectsOutputDto, ResumableAgentListDto,
|
||||
RetryOpenProjectRestoreRequestDto, SaveChatDraftRequestDto, SaveEmbedderProfileRequestDto,
|
||||
SaveModelServerRequestDto, SaveOpenCodeProviderProfileRequestDto,
|
||||
SaveOpenProjectRestoreOrderRequestDto, SaveProfileRequestDto, SetActiveLayoutRequestDto,
|
||||
SetActiveLayoutResultDto, SetAgentMcpToolAllowedRequestDto, SkillDto, SkillListDto,
|
||||
SlashCommandEffectDto, SlashCommandListDto, StopLiveAgentRequestDto, StopLiveAgentResponseDto,
|
||||
SyncAgentWithTemplateRequestDto, SyncResultDto, TemplateDto, TemplateListDto,
|
||||
TerminalClosedDto, TerminalSessionDto, TurnPageDto, UnassignSkillRequestDto,
|
||||
UpdateAgentContactableRequestDto, UpdateAgentContextRequestDto, UpdateAgentEffortRequestDto,
|
||||
UpdateAgentMcpToolPermissionsRequestDto, UpdateAgentPermissionsRequestDto,
|
||||
UpdateAgentSystemPermissionsRequestDto, UpdateMemoryRequestDto, UpdateProjectContextRequestDto,
|
||||
@ -438,9 +439,12 @@ pub async fn create_project(
|
||||
.execute(request.into())
|
||||
.await
|
||||
.map_err(ErrorDto::from)?;
|
||||
// Start tailing this project's `.ideai/requests/` tree so an orchestrator
|
||||
// agent can delegate agent/skill creation to IdeA (§14.3).
|
||||
state.ensure_orchestrator_watch(&output.project);
|
||||
state
|
||||
.record_project_opened_for_restore
|
||||
.execute(output.project.clone())
|
||||
.await
|
||||
.map_err(ErrorDto::from)?;
|
||||
state.activate_project(&output.project).await;
|
||||
Ok(ProjectDto::from(output))
|
||||
}
|
||||
|
||||
@ -467,36 +471,9 @@ pub(crate) async fn open_project_for_adapter(
|
||||
) -> Result<ProjectDto, ErrorDto> {
|
||||
let id = parse_project_id(&project_id)?;
|
||||
let output = state
|
||||
.open_project
|
||||
.execute(OpenProjectInput { project_id: id })
|
||||
.open_project_and_activate(id)
|
||||
.await
|
||||
.map_err(ErrorDto::from)?;
|
||||
// R0c (§3.4 « Trou C ») : dé-doublonne les `layouts.json` portant plusieurs
|
||||
// feuilles sur le même agent AVANT toute reprise (`list_resumable_agents`
|
||||
// relit la version persistée), de sorte qu'on ne propose / ne relance qu'une
|
||||
// session par agent. Idempotent (no-op sans doublon) et best-effort : un échec
|
||||
// ne doit pas bloquer l'ouverture.
|
||||
let _ = state
|
||||
.reconcile_layouts
|
||||
.execute(ReconcileLayoutsInput { project_id: id })
|
||||
.await;
|
||||
// Réconcilie les lignes de live-state fantômes : un crash ne déclenche jamais
|
||||
// `close_project`, donc l'ouverture est le seul filet fiable pour repasser en
|
||||
// `idle` les agents `working`/`waiting`/`blocked` dont la session est morte.
|
||||
// Acte système (appelle le port live-state directement, jamais via la surface
|
||||
// MCP self-only). Best-effort : un échec ne bloque pas l'ouverture. Tourne
|
||||
// AVANT toute relance d'agent (toute reprise réécrit ensuite en LWW).
|
||||
let _ = state
|
||||
.reconcile_live_state
|
||||
.execute(ReconcileLiveStateInput { project_id: id })
|
||||
.await;
|
||||
// Réconcilie les tâches de fond persistées au même point de boot que le
|
||||
// live-state : les Running sans handle vivant deviennent terminales, les
|
||||
// complétions WakeOwner non livrées repassent dans l'inbox unifiée.
|
||||
let _ = state.reconcile_background_tasks.execute(id).await;
|
||||
// (Re)start the orchestrator watcher for this project (idempotent, §14.3).
|
||||
state.ensure_orchestrator_watch(&output.project);
|
||||
state.reconcile_claude_run_dirs(&output.project).await;
|
||||
Ok(ProjectDto::from(output))
|
||||
}
|
||||
|
||||
@ -633,10 +610,92 @@ pub async fn close_project(project_id: String, state: State<'_, AppState>) -> Re
|
||||
workspace: None,
|
||||
})
|
||||
.await
|
||||
.map_err(ErrorDto::from)?;
|
||||
state
|
||||
.record_project_closed_for_restore
|
||||
.execute(id)
|
||||
.await
|
||||
.map(|_| ())
|
||||
.map_err(ErrorDto::from)
|
||||
}
|
||||
|
||||
/// Reads the ordered open-project restore state without opening projects.
|
||||
#[tauri::command]
|
||||
pub async fn read_open_project_restore_state(
|
||||
state: State<'_, AppState>,
|
||||
) -> Result<OpenProjectRestoreStateDto, ErrorDto> {
|
||||
state
|
||||
.read_open_project_restore_state
|
||||
.execute()
|
||||
.await
|
||||
.map(Into::into)
|
||||
.map_err(ErrorDto::from)
|
||||
}
|
||||
|
||||
/// Restores all retained projects and applies normal-open runtime side effects.
|
||||
#[tauri::command]
|
||||
pub async fn restore_open_projects(
|
||||
state: State<'_, AppState>,
|
||||
) -> Result<RestoreOpenProjectsOutputDto, ErrorDto> {
|
||||
state
|
||||
.restore_open_projects_and_activate()
|
||||
.await
|
||||
.map(Into::into)
|
||||
.map_err(ErrorDto::from)
|
||||
}
|
||||
|
||||
/// Retries one retained entry, or all entries carrying a restore failure.
|
||||
#[tauri::command]
|
||||
pub async fn retry_open_project_restore(
|
||||
request: RetryOpenProjectRestoreRequestDto,
|
||||
state: State<'_, AppState>,
|
||||
) -> Result<RestoreOpenProjectsOutputDto, ErrorDto> {
|
||||
let project_id = request
|
||||
.project_id
|
||||
.as_deref()
|
||||
.map(parse_project_id)
|
||||
.transpose()?;
|
||||
state
|
||||
.retry_open_project_restore_and_activate(project_id)
|
||||
.await
|
||||
.map(Into::into)
|
||||
.map_err(ErrorDto::from)
|
||||
}
|
||||
|
||||
/// Removes one retained entry from future restoration.
|
||||
#[tauri::command]
|
||||
pub async fn remove_project_from_restore(
|
||||
request: RemoveOpenProjectRestoreEntryRequestDto,
|
||||
state: State<'_, AppState>,
|
||||
) -> Result<OpenProjectRestoreStateDto, ErrorDto> {
|
||||
let project_id = parse_project_id(&request.project_id)?;
|
||||
state
|
||||
.remove_project_from_restore
|
||||
.execute(project_id)
|
||||
.await
|
||||
.map(Into::into)
|
||||
.map_err(ErrorDto::from)
|
||||
}
|
||||
|
||||
/// Persists a new visible order; omitted retained entries keep their prior order.
|
||||
#[tauri::command]
|
||||
pub async fn save_open_project_restore_order(
|
||||
request: SaveOpenProjectRestoreOrderRequestDto,
|
||||
state: State<'_, AppState>,
|
||||
) -> Result<OpenProjectRestoreStateDto, ErrorDto> {
|
||||
let ordered_project_ids = request
|
||||
.ordered_project_ids
|
||||
.iter()
|
||||
.map(|id| parse_project_id(id))
|
||||
.collect::<Result<Vec<_>, _>>()?;
|
||||
state
|
||||
.save_open_project_restore_order
|
||||
.execute(ordered_project_ids)
|
||||
.await
|
||||
.map(Into::into)
|
||||
.map_err(ErrorDto::from)
|
||||
}
|
||||
|
||||
/// `list_projects` — list the projects known to the registry.
|
||||
///
|
||||
/// # Errors
|
||||
|
||||
@ -254,6 +254,11 @@ pub fn run() {
|
||||
commands::create_project,
|
||||
commands::open_project,
|
||||
commands::close_project,
|
||||
commands::read_open_project_restore_state,
|
||||
commands::restore_open_projects,
|
||||
commands::retry_open_project_restore,
|
||||
commands::remove_project_from_restore,
|
||||
commands::save_open_project_restore_order,
|
||||
commands::list_projects,
|
||||
commands::read_project_context,
|
||||
commands::update_project_context,
|
||||
|
||||
Reference in New Issue
Block a user