feat(wave): #119/#122/#131/#132 verts + sprint plugins ESM/persistance #135/#136/#139

État d'intégration confiné à la branche batch. Les tickets #119 (skills →
capacités agent découvrables), #122 (override permissions par défaut), #131
(effort par agent/presets) et #132 (outil MCP d'édition du contexte projet)
sont verts en périmètre. Le sprint plugins multi-fichiers ESM / persistance
plugin-owned (#135/#136/#139) est co-implémenté dans les MÊMES fichiers de
câblage (frontend/src/ports/index.ts, backend/src/lib.rs, domain/ports.rs,
backend/dto.rs), inséparable sans staging interactif (indisponible ici).

Commit unique volontaire : préserve l'état vert QA sans découpe hunk risquée.
NON mergé vers develop tant que #137 (QA e2e plugins) n'est pas vert.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
2026-08-03 11:06:23 +02:00
parent 22c6bd803d
commit 171c6c923c
59 changed files with 3654 additions and 291 deletions

View File

@ -257,6 +257,7 @@ pub fn run() {
commands::get_project_permissions,
commands::update_project_permissions,
commands::update_agent_permissions,
commands::update_agent_effort,
commands::resolve_agent_permissions,
commands::get_project_system_permissions,
commands::update_project_system_permissions,
@ -401,6 +402,9 @@ pub fn run() {
plugins::plugin_workspace_read_binary,
plugins::plugin_workspace_write_text,
plugins::plugin_workspace_write_binary,
plugins::plugin_storage_get,
plugins::plugin_storage_set,
plugins::plugin_storage_delete,
plugins::plugin_workspace_list_dir,
plugins::plugin_workspace_stat,
plugins::plugin_query_project_structure,
@ -438,6 +442,9 @@ fn plugin_workspace_invoke_handler<R: tauri::Runtime>(
plugins::plugin_workspace_read_binary,
plugins::plugin_workspace_write_text,
plugins::plugin_workspace_write_binary,
plugins::plugin_storage_get,
plugins::plugin_storage_set,
plugins::plugin_storage_delete,
plugins::plugin_workspace_list_dir,
plugins::plugin_workspace_stat,
plugins::plugin_query_project_structure,
@ -1030,6 +1037,61 @@ mod tests {
std::fs::remove_dir_all(app_data).ok();
}
#[test]
fn dto_plugins_storage_commands_are_registered_in_tauri_invoke_handler() {
let app_data = test_app_data_dir("plugin-storage-commands");
let app = mock_builder()
.manage(crate::state::AppState::build(app_data.clone()))
.invoke_handler(plugin_workspace_invoke_handler())
.build(mock_context(noop_assets()))
.expect("mock app builds");
let webview = tauri::WebviewWindowBuilder::new(&app, "main", Default::default())
.build()
.expect("mock webview builds");
let get_err = invoke_plugin_command(
&webview,
"plugin_storage_get",
json!({
"input": {
"pluginId": "dev.acme.missing",
"key": "helloPlugin.launches"
}
}),
)
.expect_err("missing plugin must surface through the registered command");
assert_eq!(get_err["code"], "NOT_FOUND");
let set_err = invoke_plugin_command(
&webview,
"plugin_storage_set",
json!({
"input": {
"pluginId": "dev.acme.missing",
"key": "helloPlugin.launches",
"value": 1
}
}),
)
.expect_err("missing plugin must surface through the registered command");
assert_eq!(set_err["code"], "NOT_FOUND");
let delete_err = invoke_plugin_command(
&webview,
"plugin_storage_delete",
json!({
"input": {
"pluginId": "dev.acme.missing",
"key": "helloPlugin.launches"
}
}),
)
.expect_err("missing plugin must surface through the registered command");
assert_eq!(delete_err["code"], "NOT_FOUND");
std::fs::remove_dir_all(app_data).ok();
}
#[test]
fn dto_plugins_command_task_commands_are_registered_in_tauri_invoke_handler() {
let app_data = test_app_data_dir("plugin-task-commands");