Jalon vert regroupant deux chantiers entrelacés dans le working tree, indissociables au niveau fichier mais tous deux verts (cargo test --workspace + tests frontend permissions au vert). Permissions — voie « projection CLI » (advisory), complète : - LP0 domaine pur : modèle PermissionSet/EffectivePermissions, resolve deny-wins + postures Allow<Ask<Deny (crates/domain/src/permission.rs). - LP1 store : FsPermissionStore (.ideai/permissions.json). - LP2 use cases : Get/Update project, Update agent override, Resolve. - LP3 projecteurs Claude/Codex (settings.local.json / config.toml), câblage launch-path + PermissionProjectorRegistry, nettoyage des fichiers Replace orphelins au swap de profil (LP3-4), composition root + commandes Tauri, UI PermissionsPanel (projet + override agent). - ports.rs : PermissionStore + FileSystem::remove_file (cleanup au swap). Reste ouvert (hors scope, marqué dans le code) : LP4 enforcement OS airtight (Landlock fichiers) + résumé de permissions injecté. Inclut aussi le chantier Codex/input/sessions structurées en cours (McpConfigStrategy, StructuredAdapter, gestion d'input) partageant les mêmes fichiers (lifecycle.rs, commands.rs, dto.rs, state.rs). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
210 lines
8.7 KiB
Rust
210 lines
8.7 KiB
Rust
//! # IdeA — `app-tauri` (presentation / driving adapter + composition root)
|
|
//!
|
|
//! This crate is the **only** place that knows every other crate. It:
|
|
//! - builds the concrete adapters and injects them into use cases
|
|
//! ([`state::AppState`], the composition root),
|
|
//! - exposes `#[tauri::command]` handlers ([`commands`]) mapping DTOs ↔ use cases,
|
|
//! - relays domain events to the frontend ([`events::TauriEventRelay`]),
|
|
//! - hosts the generic PTY↔Channel bridge ([`pty::PtyBridge`]) for L3 and its
|
|
//! structured-chat twin ([`chat::ChatBridge`]) for §17.
|
|
//!
|
|
//! The wiring lives in the library (testable) and `main.rs` is a thin shim.
|
|
|
|
#![forbid(unsafe_code)]
|
|
#![warn(missing_docs)]
|
|
|
|
pub mod chat;
|
|
pub mod commands;
|
|
pub mod dto;
|
|
pub mod events;
|
|
pub mod mcp_bridge;
|
|
pub mod mcp_endpoint;
|
|
pub mod pty;
|
|
pub mod state;
|
|
|
|
use std::process::ExitCode;
|
|
|
|
use tauri::Manager;
|
|
|
|
use state::AppState;
|
|
|
|
/// The `argv[1]` subcommand token that switches the binary into the headless
|
|
/// `mcp-server` **bridge** mode (cadrage v5 §1.3) instead of launching Tauri.
|
|
pub const MCP_SERVER_SUBCOMMAND: &str = "mcp-server";
|
|
|
|
/// Process entry point: routes `argv` **before** anything Tauri/WebKit is touched.
|
|
///
|
|
/// When invoked as `<exe> mcp-server …` (an MCP CLI spawned us from the injected
|
|
/// `.mcp.json` declaration), we run the **stdio↔loopback bridge** headless and
|
|
/// **never** initialise the webview — see [`mcp_bridge::run_mcp_bridge`]. Any other
|
|
/// invocation is the normal IDE launch: [`run`] (which blocks until the window
|
|
/// closes and then exits the process itself).
|
|
///
|
|
/// Returns the [`ExitCode`] for the bridge path; the normal path does not return.
|
|
#[must_use]
|
|
pub fn dispatch() -> ExitCode {
|
|
let mut args = std::env::args_os().skip(1);
|
|
if args.next().is_some_and(|a| a == *MCP_SERVER_SUBCOMMAND) {
|
|
// Headless bridge: bypass Tauri entirely. Forward the remaining args
|
|
// (`--endpoint`, `--project`, `--requester`) to the bridge parser.
|
|
let rest: Vec<String> = args.map(|a| a.to_string_lossy().into_owned()).collect();
|
|
return mcp_bridge::run_mcp_bridge(rest);
|
|
}
|
|
|
|
run();
|
|
ExitCode::SUCCESS
|
|
}
|
|
|
|
/// Builds and runs the Tauri application.
|
|
///
|
|
/// Sets up the composition root (resolving the app-data directory via the Tauri
|
|
/// path API), registers commands, spawns the event relay, and starts the main
|
|
/// window.
|
|
///
|
|
/// # Panics
|
|
/// Panics if the Tauri application fails to build or run (no window/webview), or
|
|
/// if the app-data directory cannot be resolved.
|
|
pub fn run() {
|
|
tauri::Builder::default()
|
|
.plugin(tauri_plugin_dialog::init())
|
|
.setup(|app| {
|
|
// Resolve the machine-local IDE data directory (ARCHITECTURE §9.2)
|
|
// and build the composition root once the app handle exists, so the
|
|
// stores receive a concrete path without ever touching Tauri.
|
|
let app_data_dir = app
|
|
.path()
|
|
.app_data_dir()
|
|
.expect("failed to resolve the app data directory");
|
|
let app_state = AppState::build(app_data_dir);
|
|
|
|
// Wire the domain event bus → Tauri events relay.
|
|
events::spawn_relay(app.handle().clone(), &app_state.event_bus);
|
|
|
|
app.manage(app_state);
|
|
|
|
// Kill all live PTYs cleanly when the main window is closing. This is
|
|
// independent of the per-view (navigation/layout) lifecycle — those
|
|
// must NEVER kill a PTY — and only fires on a genuine app shutdown.
|
|
// A brutal crash is best-effort and out of scope.
|
|
if let Some(window) = app.get_webview_window("main") {
|
|
let handle = app.handle().clone();
|
|
window.on_window_event(move |event| {
|
|
if let tauri::WindowEvent::CloseRequested { .. } = event {
|
|
if let Some(state) = handle.try_state::<AppState>() {
|
|
let pty = std::sync::Arc::clone(&state.pty_port);
|
|
// ORDER IS CRITICAL: freeze `agent_was_running` on every
|
|
// agent leaf of every open project FIRST, reading the live
|
|
// PTY registry as it stands now; only THEN kill the PTYs.
|
|
// If we killed first, the registry would be empty and every
|
|
// agent would be persisted as "closed".
|
|
let snapshot = std::sync::Arc::clone(&state.snapshot_running_agents);
|
|
let open_projects = state.open_project_ids();
|
|
let handles = state.terminal_sessions.handles();
|
|
tauri::async_runtime::block_on(async move {
|
|
for project_id in open_projects {
|
|
let _ = snapshot
|
|
.execute(application::SnapshotRunningAgentsInput {
|
|
project_id,
|
|
})
|
|
.await;
|
|
}
|
|
for h in handles {
|
|
let _ = pty.kill(&h).await;
|
|
}
|
|
});
|
|
}
|
|
}
|
|
});
|
|
}
|
|
|
|
Ok(())
|
|
})
|
|
.invoke_handler(tauri::generate_handler![
|
|
commands::health,
|
|
commands::create_project,
|
|
commands::open_project,
|
|
commands::close_project,
|
|
commands::list_projects,
|
|
commands::read_project_context,
|
|
commands::update_project_context,
|
|
commands::get_project_permissions,
|
|
commands::update_project_permissions,
|
|
commands::update_agent_permissions,
|
|
commands::resolve_agent_permissions,
|
|
commands::open_terminal,
|
|
commands::write_terminal,
|
|
commands::resize_terminal,
|
|
commands::close_terminal,
|
|
commands::reattach_terminal,
|
|
commands::load_layout,
|
|
commands::mutate_layout,
|
|
commands::list_layouts,
|
|
commands::create_layout,
|
|
commands::rename_layout,
|
|
commands::delete_layout,
|
|
commands::set_active_layout,
|
|
commands::first_run_state,
|
|
commands::reference_profiles,
|
|
commands::detect_profiles,
|
|
commands::list_profiles,
|
|
commands::save_profile,
|
|
commands::delete_profile,
|
|
commands::configure_profiles,
|
|
commands::list_embedder_profiles,
|
|
commands::save_embedder_profile,
|
|
commands::delete_embedder_profile,
|
|
commands::describe_embedder_engines,
|
|
commands::dismiss_embedder_suggestion,
|
|
commands::create_agent,
|
|
commands::list_agents,
|
|
commands::list_live_agents,
|
|
commands::attach_live_agent,
|
|
commands::read_agent_context,
|
|
commands::update_agent_context,
|
|
commands::delete_agent,
|
|
commands::launch_agent,
|
|
commands::change_agent_profile,
|
|
commands::agent_send,
|
|
commands::interrupt_agent,
|
|
commands::delegation_delivered,
|
|
commands::set_front_attached,
|
|
commands::reattach_agent_chat,
|
|
commands::close_agent_session,
|
|
commands::list_resumable_agents,
|
|
commands::inspect_conversation,
|
|
commands::create_template,
|
|
commands::update_template,
|
|
commands::list_templates,
|
|
commands::delete_template,
|
|
commands::create_agent_from_template,
|
|
commands::detect_agent_drift,
|
|
commands::sync_agent_with_template,
|
|
commands::git_status,
|
|
commands::git_stage,
|
|
commands::git_unstage,
|
|
commands::git_commit,
|
|
commands::git_branches,
|
|
commands::git_checkout,
|
|
commands::git_log,
|
|
commands::git_init,
|
|
commands::git_graph,
|
|
commands::create_skill,
|
|
commands::update_skill,
|
|
commands::list_skills,
|
|
commands::delete_skill,
|
|
commands::assign_skill_to_agent,
|
|
commands::unassign_skill_from_agent,
|
|
commands::create_memory,
|
|
commands::update_memory,
|
|
commands::list_memories,
|
|
commands::get_memory,
|
|
commands::delete_memory,
|
|
commands::read_memory_index,
|
|
commands::recall_memory,
|
|
commands::resolve_memory_links,
|
|
commands::move_tab_to_new_window,
|
|
])
|
|
.run(tauri::generate_context!())
|
|
.expect("error while running IdeA Tauri application");
|
|
}
|