# UI Team Handoff - Execution Engine Integration
**Date**: 2025-12-03
**Component**: v2-execution-engine-rust
## 🚨 Critical Architecture Change
**The Execution Engine is now a SINGLETON with strict SERIAL execution.**
To ensure stability and prevent race conditions during the UI integration phase, we have enforced a strict **Serial Execution Mode**. This means only **one command executes at a time**.
### Initialization (Must do this!)
You **must** use `ExecutionEngine::init_global(config)` instead of `ExecutionEngine::new()`.
```rust
// src-tauri/src/main.rs
use execution_engine::{ExecutionEngine, ExecutionConfig};
fn main() {
let config = ExecutionConfig::default();
// ⚠️ THIS IS REQUIRED ⚠️
// This initializes the global singleton and forces max_concurrent_executions = 1
ExecutionEngine::init_global(config).expect("Failed to initialize execution engine");
// Access the engine instance
let engine = ExecutionEngine::global();
tauri::Builder::default()
// Note: You might not need to manage state manually if using the global accessor in commands
// but keeping it managed is fine for dependency injection consistency.
.manage(engine)
// ...
}
```
### Usage in Commands
You can now access the engine securely from anywhere using the global accessor:
```rust
#[tauri::command]
async fn execute_command(request: ExecutionRequest) -> Result<String, String> {
// Access the singleton directly
let engine = ExecutionEngine::global();
let id = engine.execute(request).await.map_err(|e| e.to_string())?;
Ok(id.to_string())
}
```
---
## 🛠️ Testing without UI
We have created a **TypeScript Integration Bridge** to allow you to test the execution engine logic without needing the full Tauri UI stack.
**Location**: `v2-execution-engine-rust/examples/node-caller.ts`
### How to use
1. Go to the execution engine directory:
```bash
cd v2-execution-engine-rust
```
2. Run the bridge:
```bash
npx ts-node examples/node-caller.ts
```
3. What it does:
- Spawns the Rust engine in a separate process.
- Sends commands via JSON over stdin.
- Receives results via JSON over stdout.
- **Verifies that the Rust/TS types match.**
This is perfect for testing your `ExecutionRequest` payloads before wiring them up in the React frontend.
---
## 📦 Data Types
Ensure your frontend types match the Rust structs in `src/types.rs`.
**ExecutionRequest**:
```typescript
interface ExecutionRequest {
id: string; // UUID
command:
| { type: 'shell'; command: string; shell: string } // usually this
| { type: 'exec'; program: string; args: string[] };
env: Record<string, string>;
working_dir: string | null;
timeout_ms: number | null;
}
```
**ExecutionResult**:
```typescript
interface ExecutionResult {
id: string;
exit_code: number;
stdout: string;
stderr: string;
error?: string;
}
```
---
## 📡 Real-Time Streaming (Optional)
If you need real-time output (e.g., for a terminal view), you must register an `EventHandler` that forwards events to the Tauri frontend.
### 1. Implement the Handler
```rust
use execution_engine::{EventHandler, ExecutionEvent};
use tauri::{AppHandle, Manager};
use async_trait::async_trait;
struct TauriEventHandler {
app_handle: AppHandle,
}
#[async_trait]
impl EventHandler for TauriEventHandler {
async fn on_event(&self, event: ExecutionEvent) {
// Emit event to frontend
// Event payload matches ExecutionEvent struct (serde serialized)
let _ = self.app_handle.emit_all("execution:event", event);
}
}
```
### 2. Register during Initialization
```rust
use std::sync::Arc;
fn main() {
let config = ExecutionConfig::default();
// Initialize engine
let engine = ExecutionEngine::init_global(config).expect("Failed to init");
tauri::Builder::default()
.setup(|app| {
let handle = app.handle();
// Attach handler to the global engine instance
// Note: This API might require slight adjustment based on exact Engine API for mutable access
// or if we need to do this *before* init_global.
//
// Since init_global consumes config, you might need to wrap the handler
// if the engine supports dynamic handler attachment, or pass it during init
// (check engine.rs specific signatures).
Ok(())
})
// ...
}
```
**Frontend Event Listener**:
```typescript
import { listen } from '@tauri-apps/api/event';
type ExecutionEvent =
| { event_type: 'started', execution_id: string, ... }
| { event_type: 'stdout', execution_id: string, line: string, ... }
| { event_type: 'completed', result: ExecutionResult, ... };
await listen<ExecutionEvent>('execution:event', (event) => {
const payload = event.payload;
if (payload.event_type === 'stdout') {
console.log(payload.line);
}
});
```