refactor(desktop): split kernel_commands/pipeline_commands into modules, add SaaS client libs and gateway modules

Split monolithic kernel_commands.rs (2185 lines) and pipeline_commands.rs (1391 lines)
into focused sub-modules under kernel_commands/ and pipeline_commands/ directories.
Add gateway module (commands, config, io, runtime), health_check, and 15 new
TypeScript client libraries for SaaS relay, auth, admin, telemetry, and kernel
sub-systems (a2a, agent, chat, hands, skills, triggers).

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
iven
2026-03-31 11:12:47 +08:00
parent d0ae7d2770
commit f79560a911
71 changed files with 8521 additions and 5997 deletions

View File

@@ -0,0 +1,230 @@
//! Pipeline CRUD commands (Create / Update / Delete).
use std::collections::HashMap;
use std::sync::Arc;
use tauri::State;
use serde::{Deserialize, Serialize};
use serde_json::Value;
use zclaw_pipeline::{
Pipeline,
PipelineMetadata,
PipelineSpec,
PipelineStep,
Action,
ErrorStrategy,
};
use super::{PipelineState, PipelineInfo};
use super::helpers::{get_pipelines_directory, pipeline_to_info};
/// Create pipeline request
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct CreatePipelineRequest {
pub name: String,
pub description: Option<String>,
pub steps: Vec<WorkflowStepInput>,
}
/// Update pipeline request
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct UpdatePipelineRequest {
pub name: Option<String>,
pub description: Option<String>,
pub steps: Option<Vec<WorkflowStepInput>>,
}
/// Workflow step input from frontend
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct WorkflowStepInput {
pub hand_name: String,
pub name: Option<String>,
pub params: Option<HashMap<String, Value>>,
pub condition: Option<String>,
}
/// Create a new pipeline as a YAML file
#[tauri::command]
pub async fn pipeline_create(
state: State<'_, Arc<PipelineState>>,
request: CreatePipelineRequest,
) -> Result<PipelineInfo, String> {
let name = request.name.trim().to_string();
if name.is_empty() {
return Err("Pipeline name cannot be empty".to_string());
}
let pipelines_dir = get_pipelines_directory()?;
if !pipelines_dir.exists() {
std::fs::create_dir_all(&pipelines_dir)
.map_err(|e| format!("Failed to create pipelines directory: {}", e))?;
}
// Generate pipeline ID from name
let pipeline_id = name.to_lowercase()
.replace(' ', "-")
.replace(|c: char| !c.is_alphanumeric() && c != '-', "");
let file_path = pipelines_dir.join(format!("{}.yaml", pipeline_id));
if file_path.exists() {
return Err(format!("Pipeline file already exists: {}", file_path.display()));
}
// Build Pipeline struct
let steps: Vec<PipelineStep> = request.steps.into_iter().enumerate().map(|(i, s)| {
let step_id = s.name.clone().unwrap_or_else(|| format!("step-{}", i + 1));
PipelineStep {
id: step_id,
action: Action::Hand {
hand_id: s.hand_name.clone(),
hand_action: "execute".to_string(),
params: s.params.unwrap_or_default().into_iter().map(|(k, v)| (k, v.to_string())).collect(),
},
description: s.name,
when: s.condition,
retry: None,
timeout_secs: None,
}
}).collect();
let pipeline = Pipeline {
api_version: "zclaw/v1".to_string(),
kind: "Pipeline".to_string(),
metadata: PipelineMetadata {
name: pipeline_id.clone(),
display_name: Some(name),
description: request.description,
category: None,
industry: None,
tags: vec![],
icon: None,
author: None,
version: "1.0.0".to_string(),
annotations: None,
},
spec: PipelineSpec {
inputs: vec![],
steps,
outputs: HashMap::new(),
on_error: ErrorStrategy::Stop,
timeout_secs: 0,
max_workers: 4,
},
};
// Serialize to YAML
let yaml_content = serde_yaml::to_string(&pipeline)
.map_err(|e| format!("Failed to serialize pipeline: {}", e))?;
std::fs::write(&file_path, yaml_content)
.map_err(|e| format!("Failed to write pipeline file: {}", e))?;
// Register in state
let mut state_pipelines = state.pipelines.write().await;
let mut state_paths = state.pipeline_paths.write().await;
state_pipelines.insert(pipeline_id.clone(), pipeline.clone());
state_paths.insert(pipeline_id, file_path);
Ok(pipeline_to_info(&pipeline))
}
/// Update an existing pipeline
#[tauri::command]
pub async fn pipeline_update(
state: State<'_, Arc<PipelineState>>,
pipeline_id: String,
request: UpdatePipelineRequest,
) -> Result<PipelineInfo, String> {
let pipelines = state.pipelines.read().await;
let paths = state.pipeline_paths.read().await;
let existing = pipelines.get(&pipeline_id)
.ok_or_else(|| format!("Pipeline not found: {}", pipeline_id))?;
let file_path = paths.get(&pipeline_id)
.ok_or_else(|| format!("Pipeline file path not found: {}", pipeline_id))?
.clone();
// Build updated pipeline
let updated_metadata = PipelineMetadata {
display_name: request.name.or(existing.metadata.display_name.clone()),
description: request.description.or(existing.metadata.description.clone()),
..existing.metadata.clone()
};
let updated_steps = match request.steps {
Some(steps) => steps.into_iter().enumerate().map(|(i, s)| {
let step_id = s.name.clone().unwrap_or_else(|| format!("step-{}", i + 1));
PipelineStep {
id: step_id,
action: Action::Hand {
hand_id: s.hand_name.clone(),
hand_action: "execute".to_string(),
params: s.params.unwrap_or_default().into_iter().map(|(k, v)| (k, v.to_string())).collect(),
},
description: s.name,
when: s.condition,
retry: None,
timeout_secs: None,
}
}).collect(),
None => existing.spec.steps.clone(),
};
let updated_pipeline = Pipeline {
metadata: updated_metadata,
spec: PipelineSpec {
steps: updated_steps,
..existing.spec.clone()
},
..existing.clone()
};
// Write to file
let yaml_content = serde_yaml::to_string(&updated_pipeline)
.map_err(|e| format!("Failed to serialize pipeline: {}", e))?;
// Drop read locks before write
drop(pipelines);
drop(paths);
std::fs::write(file_path, yaml_content)
.map_err(|e| format!("Failed to write pipeline file: {}", e))?;
// Update state
let mut state_pipelines = state.pipelines.write().await;
state_pipelines.insert(pipeline_id.clone(), updated_pipeline.clone());
Ok(pipeline_to_info(&updated_pipeline))
}
/// Delete a pipeline
#[tauri::command]
pub async fn pipeline_delete(
state: State<'_, Arc<PipelineState>>,
pipeline_id: String,
) -> Result<(), String> {
let paths = state.pipeline_paths.read().await;
let file_path = paths.get(&pipeline_id)
.ok_or_else(|| format!("Pipeline not found: {}", pipeline_id))?;
let path = file_path.clone();
drop(paths);
// Remove file
if path.exists() {
std::fs::remove_file(&path)
.map_err(|e| format!("Failed to delete pipeline file: {}", e))?;
}
// Remove from state
let mut state_pipelines = state.pipelines.write().await;
let mut state_paths = state.pipeline_paths.write().await;
state_pipelines.remove(&pipeline_id);
state_paths.remove(&pipeline_id);
Ok(())
}