mirror of
https://github.com/R0m1k3/Loki.git
synced 2026-10-24 07:28:13 +02:00
Lot 37 : outils task_* de l'agent exécutés par la façade
Quand la façade tient les tâches (LOKI_TASKS=rust), les quatre outils par lesquels le modèle gère ses tâches planifiées — task_list, task_create, task_update, task_delete — s'exécutent dans ses tours (src/tasks/tools.rs, port de tasks_tools.go), sans passer par le Go : mêmes messages, projet actif, fuseau de l'utilisateur, tâche unique « dans N minutes » ou « à HH:MM », fréquence validée, script vérifié, compte-rendu coupé comme le Go. ChatShared porte la même base de tâches que les routes. Côté Go, les outils prennent l'heure des tâches (tasksNow) au lieu de time.Now : rien ne change en service, le journal de parité la fixe. Parité : journal de 44 appels tiré du Go (parity_tasks_tools_test.go → tests/parity_tasks/tools.json), rejoué par tests/tasks.rs ; un tour de la façade qui crée une tâche (tests/lot6.rs). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_016tcGfJWefW5kUGoC2wL8cv
This commit is contained in:
14 files changed
+7946
-18
No files matched your search
@@ -668,6 +668,7 @@ async fn tour_complet_par_le_go() {
|
||||
tpl: Arc::new(loki_rs::chat::tplprobe::TplProbe::new()),
|
||||
prewarm: Default::default(),
|
||||
mcp: None,
|
||||
tasks: None,
|
||||
go_version: Default::default(),
|
||||
});
|
||||
let cfg = std::collections::HashMap::new();
|
||||
@@ -851,13 +852,13 @@ async fn service(
|
||||
tokio::sync::oneshot::Sender<()>,
|
||||
tempfile::TempDir,
|
||||
) {
|
||||
service_with(engine_url, None).await
|
||||
service_with(engine_url, |_| {}).await
|
||||
}
|
||||
|
||||
/// [`service`], avec le pool MCP de la façade.
|
||||
/// [`service`], l'état partagé réglé par `f` (pool MCP, tâches…).
|
||||
async fn service_with(
|
||||
engine_url: Option<&str>,
|
||||
mcp: Option<Arc<loki_rs::mcp::Mcp>>,
|
||||
f: impl FnOnce(&mut ChatShared),
|
||||
) -> (
|
||||
Arc<ChatService>,
|
||||
Arc<fakego::FakeGo>,
|
||||
@@ -874,7 +875,7 @@ async fn service_with(
|
||||
let sessions = Sessions::new(st.clone(), Conversation::new(), GoSessionHooks(st.clone()));
|
||||
sessions.load().await.unwrap();
|
||||
let mut shared = ChatShared::for_sessions(&sessions, st, t.path().join("chatimg"));
|
||||
shared.mcp = mcp;
|
||||
f(&mut shared);
|
||||
(
|
||||
ChatService::new(Arc::new(sessions), Arc::new(shared)),
|
||||
go,
|
||||
@@ -2443,7 +2444,7 @@ done
|
||||
std::fs::set_permissions(&bin, std::fs::Permissions::from_mode(0o755)).unwrap();
|
||||
let trace = dir.path().join("trace");
|
||||
let pool = Arc::new(loki_rs::mcp::Mcp::new(dir.path().to_path_buf()));
|
||||
let (svc, go, _stop, _t) = service_with(Some(&url), Some(pool.clone())).await;
|
||||
let (svc, go, _stop, _t) = service_with(Some(&url), |s| s.mcp = Some(pool.clone())).await;
|
||||
let mut servers = loki_rs::mcp::config::Servers::new();
|
||||
servers.insert(
|
||||
"sh".into(),
|
||||
@@ -2493,3 +2494,60 @@ done
|
||||
assert!(inits[1].contains(r#""version":"0.15.0""#), "{inits:?}");
|
||||
pool.close_all().await;
|
||||
}
|
||||
|
||||
/// Lot 37 : un outil task_* appelé par un tour de la façade s'exécute ici
|
||||
/// quand elle tient les tâches — la tâche rangée dans le projet actif, au
|
||||
/// fuseau de l'utilisateur, sans passer par le Go.
|
||||
#[tokio::test]
|
||||
async fn outil_task_par_la_facade() {
|
||||
let call = sse(&[
|
||||
serde_json::json!({"choices":[{"delta":{"tool_calls":[{"index":0,"id":"t1","type":"function","function":{"name":"task_create","arguments":"{\"name\":\"Rappel\",\"prompt\":\"Bois\",\"schedule\":\"@every 2h\"}"}}]}}]}),
|
||||
serde_json::json!({"choices":[{"delta":{},"finish_reason":"tool_calls"}]}),
|
||||
]);
|
||||
let (url, seen) = fake_engine(vec![call, answer("c'est noté")]).await;
|
||||
let dir = tempfile::tempdir().unwrap();
|
||||
// 2026-03-28 12:00:00 UTC.
|
||||
fn clock() -> i64 {
|
||||
1_774_699_200 * 1_000_000_000
|
||||
}
|
||||
let tasks = Arc::new(loki_rs::tasks::Tasks::with(
|
||||
dir.path().to_path_buf(),
|
||||
std::time::Duration::from_secs(3600),
|
||||
clock,
|
||||
));
|
||||
let (svc, go, _stop, _t) = service_with(Some(&url), |s| s.tasks = Some(tasks.clone())).await;
|
||||
go.set("state", "user_tz", "Europe/Paris");
|
||||
go.set("state", "active_project", "generale");
|
||||
svc.send(serde_json::json!({"message": "rappelle-moi de boire", "cid": "t"}))
|
||||
.await
|
||||
.unwrap();
|
||||
until_done(&svc, 1).await;
|
||||
assert!(go.tool_log.lock().unwrap().is_empty(), "{:?}", go.tool_log);
|
||||
let list = loki_rs::tasks::list_tasks(&svc.shared.store).await;
|
||||
assert_eq!(list.len(), 1, "{list:?}");
|
||||
let t = &list[0];
|
||||
assert_eq!(
|
||||
(
|
||||
t.name.as_str(),
|
||||
t.schedule.as_str(),
|
||||
t.tz.as_str(),
|
||||
t.project.as_str()
|
||||
),
|
||||
("Rappel", "@every 2h", "Europe/Paris", "generale")
|
||||
);
|
||||
let seen = seen.lock().unwrap().clone();
|
||||
let tool = seen[1].1["messages"]
|
||||
.as_array()
|
||||
.unwrap()
|
||||
.iter()
|
||||
.rfind(|m| m["role"] == "tool")
|
||||
.unwrap()["content"]
|
||||
.clone();
|
||||
assert_eq!(
|
||||
tool,
|
||||
serde_json::json!(format!(
|
||||
"[ok] tâche 'Rappel' créée (id {}), prochaine exécution 2026-03-28 15:00 (Europe/Paris)",
|
||||
t.id
|
||||
))
|
||||
);
|
||||
}
|
||||
Reference in new issue
Block a user