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:
Claude committed 2026-10-16 03:51:09 +00:00
1 parent 869bd18bff
commit e94b625931
14 files changed
+7946 -18

No files matched your search

+58
View File
@@ -495,3 +495,61 @@ async fn amorce_au_demarrage() {
assert_eq!(next("eteinte"), 0);
assert_eq!(next("posee"), 42);
}
fn zero_clock() -> i64 {
0
}
/// Lot 37 : les outils task_* de l'agent, rejoués contre le journal du Go
/// (`internal/loki/parity_tasks_tools_test.go` → `parity_tasks/tools.json`) :
/// chaque appel, son résultat tel que le modèle le reçoit, les tâches en base
/// après coup.
#[tokio::test(flavor = "multi_thread", worker_threads = 4)]
async fn parite_des_outils() {
use loki_rs::tasks::tools::{ToolEnv, exec, is_tool};
let fx: Value = serde_json::from_str(include_str!("parity_tasks/tools.json")).expect("journal");
let mut bn = bench(zero_clock).await;
let scripts = bn.home.join("scripts");
let mut now = 0;
for st in fx.as_array().unwrap() {
let name = s(&st["name"]);
if let Some(n) = st["now"].as_str() {
now = rfc3339_ns(n);
}
let files: serde_json::Map<String, Value> = st["files"]
.as_object()
.into_iter()
.flatten()
.map(|(k, v)| (k.clone(), Value::String(bn.unmask(s(v)))))
.collect();
apply_files(&bn.home, &Value::Object(files));
for (k, v) in st["state"].as_object().into_iter().flatten() {
bn.go.set("state", k, s(v));
}
if let Some(t) = st["put_task"].as_str() {
let raw = bn.unmask(t);
let v: Value = serde_json::from_str(&raw).unwrap();
bn.go.set("tasks", s(&v["id"]), &raw);
bn.mask(&raw);
}
let tool = s(&st["tool"]);
assert!(is_tool(tool), "{name}");
let args: serde_json::Map<String, Value> =
serde_json::from_str(&bn.unmask(&st["args"].to_string())).unwrap();
let env = ToolEnv {
store: &bn.store,
scripts_dir: &scripts,
now_ns: now,
};
let out = exec(&env, tool, &args).await.expect("outil task_*");
assert_eq!(bn.mask(&out), s(&st["out"]), "{name}");
assert_eq!(bn.snapshot().await, st["tasks"], "{name}");
}
let env = ToolEnv {
store: &bn.store,
scripts_dir: &scripts,
now_ns: now,
};
assert_eq!(exec(&env, "bash", &serde_json::Map::new()).await, None);
assert!(!is_tool("task_run"));
}