feat(backend,frontend): tâches planifiées projet — tool MCP idea_scheduled_task_* et menu utilisateur #305, fix transcript lié indisponible après reboot #314 (QA verte)
#305: nouvel agrégat durable ScheduledProjectTask dans domain (scheduled_task.rs, ids, ports ScheduledProjectTaskStore), source de vérité <app_data_dir>/scheduled-project-tasks/<projectId>.json, schedules bornés V1 interval/daily/weekly avec fuseau, cycle À approuver/Active/Suspendue avec approbation obligatoire des tâches demandées par agent; use cases application/scheduled_task (list/create/update/delete/run_now/reconcile/fire) réutilisant BackgroundTask pour les runs avec distinction planifié/manuel; store infrastructure dédié + scheduler reconcile au boot sans rattrapage en rafale, cwd confiné au projet; 7 tools MCP idea_scheduled_task_* (list/read lecture seule, create/update/delete/run_now/set_enabled write-actions soumises aux permissions natives); commandes Tauri + routes HTTP aux contrats DTO identiques (camelCase nextRunAtMs/lastRunAtMs/lastTaskId/createdBy/updatedBy); frontend: gateways Tauri/HTTP/mock + types ports/domain, surface projet 'Tâches planifiées' (liste utilisateur+agent unifiée, création/édition avec récapitulatif avant activation, approbation/refus, exécuter maintenant, suspendre/reprendre, suppression), catalogage surface web (webSurfaceCatalog/viewPlacement/ViewPanelBody/WebWorkspace); docs/scheduled-project-tasks.md et sdk docs/services.md (047c38b: pas un service plugin V1, pas d'émulation via cron système). #314: correction de 'Conversation liée — transcript indisponible' après reboot: restauration du transcript lié côté application (orchestrator service, lifecycle, session_limit, background sink, terminal registry) et côté frontend (adapters agent/http frames/stream/requestResponse, workStateNormalization, CustomAgentChatView avec état et action utile), tests dto_chat/agent_lifecycle/structured_registry_d1/background_completion_sink étendus. QA verte rapportée par QA sur feature/305-scheduled-project-tasks-mcp, sans bloquant, pour #305 et #314. Hors périmètre worktree: tickets non liés, attachments/, scripts/; aucun push.
This commit is contained in:
@ -3719,11 +3719,12 @@ pub fn reattach_agent_chat(
|
||||
// this live channel — generation supersede, no double emission).
|
||||
let _gen = state.chat_bridge.register(sid, on_reply);
|
||||
|
||||
Ok(ReattachChatDto {
|
||||
session_id: sid.to_string(),
|
||||
Ok(ReattachChatDto::from_scrollback(
|
||||
sid.to_string(),
|
||||
conversation_id,
|
||||
scrollback,
|
||||
})
|
||||
state.structured_sessions.transcript_restore_expected(&sid),
|
||||
))
|
||||
}
|
||||
|
||||
/// `detach_agent_chat` — detach only the custom chat view transport from a live
|
||||
@ -5201,6 +5202,7 @@ pub async fn spawn_background_command(
|
||||
rendezvous: None,
|
||||
parent_task_id: None,
|
||||
deadline_ms: request.deadline_ms,
|
||||
origin: domain::BackgroundTaskOrigin::Manual,
|
||||
})
|
||||
.await
|
||||
.map(|out| BackgroundTaskDto::from(out.task))
|
||||
@ -5346,3 +5348,124 @@ pub async fn list_background_tasks(
|
||||
tasks.sort_by_key(|task| (task.created_at_ms, task.id));
|
||||
Ok(tasks.into_iter().map(BackgroundTaskDto::from).collect())
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Scheduled project tasks (#305)
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
/// Lists durable scheduled definitions for one project.
|
||||
#[tauri::command]
|
||||
pub async fn list_scheduled_project_tasks(
|
||||
project_id: String,
|
||||
state: State<'_, AppState>,
|
||||
) -> Result<Vec<domain::ScheduledProjectTask>, ErrorDto> {
|
||||
state
|
||||
.scheduled_project_tasks
|
||||
.list(parse_project_id(&project_id)?)
|
||||
.await
|
||||
.map_err(ErrorDto::from)
|
||||
}
|
||||
|
||||
/// Creates a human-approved scheduled project task definition.
|
||||
#[tauri::command]
|
||||
pub async fn create_scheduled_project_task(
|
||||
request: crate::dto::CreateScheduledProjectTaskRequestDto,
|
||||
state: State<'_, AppState>,
|
||||
) -> Result<domain::ScheduledProjectTask, ErrorDto> {
|
||||
state
|
||||
.scheduled_project_tasks
|
||||
.create(application::CreateScheduledProjectTaskInput {
|
||||
project_id: parse_project_id(&request.project_id)?,
|
||||
name: request.name,
|
||||
description: request.description,
|
||||
command: request.command,
|
||||
schedule: request.schedule,
|
||||
timezone: request.timezone,
|
||||
enabled: request.enabled,
|
||||
timeout_ms: request.timeout_ms,
|
||||
actor: domain::ScheduledTaskActor::Human,
|
||||
})
|
||||
.await
|
||||
.map_err(ErrorDto::from)
|
||||
}
|
||||
|
||||
/// Replaces a scheduled project task definition under revision control.
|
||||
#[tauri::command]
|
||||
pub async fn update_scheduled_project_task(
|
||||
request: crate::dto::UpdateScheduledProjectTaskRequestDto,
|
||||
state: State<'_, AppState>,
|
||||
) -> Result<domain::ScheduledProjectTask, ErrorDto> {
|
||||
state
|
||||
.scheduled_project_tasks
|
||||
.update(application::UpdateScheduledProjectTaskInput {
|
||||
project_id: parse_project_id(&request.project_id)?,
|
||||
scheduled_task_id: crate::dto::parse_scheduled_task_id(&request.scheduled_task_id)?,
|
||||
name: request.name,
|
||||
description: request.description,
|
||||
command: request.command,
|
||||
schedule: request.schedule,
|
||||
timezone: request.timezone,
|
||||
enabled: request.enabled,
|
||||
timeout_ms: request.timeout_ms,
|
||||
audit_revision: request.audit_revision,
|
||||
actor: domain::ScheduledTaskActor::Human,
|
||||
})
|
||||
.await
|
||||
.map_err(ErrorDto::from)
|
||||
}
|
||||
|
||||
/// Deletes a definition without cancelling a run already in progress.
|
||||
#[tauri::command]
|
||||
pub async fn delete_scheduled_project_task(
|
||||
project_id: String,
|
||||
scheduled_task_id: String,
|
||||
state: State<'_, AppState>,
|
||||
) -> Result<(), ErrorDto> {
|
||||
state
|
||||
.scheduled_project_tasks
|
||||
.delete(
|
||||
parse_project_id(&project_id)?,
|
||||
crate::dto::parse_scheduled_task_id(&scheduled_task_id)?,
|
||||
domain::ScheduledTaskActor::Human,
|
||||
)
|
||||
.await
|
||||
.map_err(ErrorDto::from)
|
||||
}
|
||||
|
||||
/// Starts an immediate background run for an approved definition.
|
||||
#[tauri::command]
|
||||
pub async fn run_scheduled_project_task_now(
|
||||
project_id: String,
|
||||
scheduled_task_id: String,
|
||||
state: State<'_, AppState>,
|
||||
) -> Result<domain::ScheduledProjectTask, ErrorDto> {
|
||||
state
|
||||
.scheduled_project_tasks
|
||||
.run_now(
|
||||
parse_project_id(&project_id)?,
|
||||
crate::dto::parse_scheduled_task_id(&scheduled_task_id)?,
|
||||
domain::ScheduledTaskActor::Human,
|
||||
)
|
||||
.await
|
||||
.map_err(ErrorDto::from)
|
||||
}
|
||||
|
||||
/// Enables/approves or disables a scheduled definition.
|
||||
#[tauri::command]
|
||||
pub async fn set_scheduled_project_task_enabled(
|
||||
project_id: String,
|
||||
scheduled_task_id: String,
|
||||
enabled: bool,
|
||||
state: State<'_, AppState>,
|
||||
) -> Result<domain::ScheduledProjectTask, ErrorDto> {
|
||||
state
|
||||
.scheduled_project_tasks
|
||||
.set_enabled(
|
||||
parse_project_id(&project_id)?,
|
||||
crate::dto::parse_scheduled_task_id(&scheduled_task_id)?,
|
||||
enabled,
|
||||
domain::ScheduledTaskActor::Human,
|
||||
)
|
||||
.await
|
||||
.map_err(ErrorDto::from)
|
||||
}
|
||||
|
||||
@ -418,6 +418,12 @@ pub fn run() {
|
||||
commands::retry_background_task,
|
||||
commands::attach_background_task,
|
||||
commands::list_background_tasks,
|
||||
commands::list_scheduled_project_tasks,
|
||||
commands::create_scheduled_project_task,
|
||||
commands::update_scheduled_project_task,
|
||||
commands::delete_scheduled_project_task,
|
||||
commands::run_scheduled_project_task_now,
|
||||
commands::set_scheduled_project_task_enabled,
|
||||
plugins::plugin_list_plugins,
|
||||
plugins::plugin_list_project_settings,
|
||||
plugins::plugin_set_project_enabled,
|
||||
|
||||
Reference in New Issue
Block a user