fix: resolve 17 P2 defects and 5 P3 defects from pre-launch audit
Some checks failed
CI / Lint & TypeCheck (push) Has been cancelled
CI / Unit Tests (push) Has been cancelled
CI / Build Frontend (push) Has been cancelled
CI / Rust Check (push) Has been cancelled
CI / Security Scan (push) Has been cancelled
CI / E2E Tests (push) Has been cancelled

Batch fix covering multiple modules:
- P2-01: HandRegistry Semaphore-based max_concurrent enforcement
- P2-03: Populate toolCount/metricCount from Hand trait methods
- P2-06: heartbeat_update_config minimum interval validation
- P2-07: ReflectionResult used_fallback marker for rule-based fallback
- P2-08/09: identity_propose_change parameter naming consistency
- P2-10: ClassroomMetadata is_placeholder flag for LLM failure
- P2-11: classroomStore userDidCloseDuringGeneration intent tracking
- P2-12: workflowStore pipeline_create sends actionType
- P2-13/14: PipelineInfo step_count + PipelineStepInfo for proper step mapping
- P2-15: Pipe transform support in context.resolve (8 transforms)
- P2-16: Mustache {{...}} → \${...} auto-normalization
- P2-17: SaaSLogin password placeholder 6→8
- P2-19: serialize_skill_md + update_skill preserve tools field
- P2-22: ToolOutputGuard sensitive patterns from warn→block
- P2-23: Mutex::unwrap() → unwrap_or_else in relay/service.rs
- P3-01/03/07/08/09: Various P3 fixes
- DEFECT_LIST.md: comprehensive status sync (43/51 fixed, 8 remaining)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
iven
2026-04-06 00:49:16 +08:00
parent f9e1ce1d6e
commit 26a833d1c8
25 changed files with 408 additions and 143 deletions

View File

@@ -40,10 +40,18 @@ pub struct UpdatePipelineRequest {
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct WorkflowStepInput {
/// Action type discriminator (P2-12: enables non-Hand action types)
pub action_type: Option<String>,
pub hand_name: String,
pub name: Option<String>,
pub params: Option<HashMap<String, Value>>,
pub condition: Option<String>,
/// LLM generation template (for action_type = "llm_generate")
pub template: Option<String>,
/// Parallel collection path (for action_type = "parallel")
pub each: Option<String>,
/// Condition branches (for action_type = "condition")
pub branches: Option<HashMap<String, Value>>,
}
/// Create a new pipeline as a YAML file
@@ -74,18 +82,57 @@ pub async fn pipeline_create(
return Err(format!("Pipeline file already exists: {}", file_path.display()));
}
// Build Pipeline struct
// P2-12: Build PipelineSteps with proper action type from WorkflowStepInput
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 {
let params_map: HashMap<String, String> = s.params
.unwrap_or_default()
.into_iter()
.map(|(k, v)| (k, v.to_string()))
.collect();
let action = match s.action_type.as_deref().unwrap_or("hand") {
"llm_generate" => Action::LlmGenerate {
template: s.template.unwrap_or_default(),
input: params_map,
model: None,
temperature: None,
max_tokens: None,
json_mode: false,
},
"parallel" => Action::Parallel {
each: s.each.unwrap_or_else(|| "item".to_string()),
step: Box::new(PipelineStep {
id: format!("{}-body", step_id),
action: Action::Hand {
hand_id: s.hand_name.clone(),
hand_action: "execute".to_string(),
params: params_map,
},
description: None,
when: None,
retry: None,
timeout_secs: None,
}),
max_workers: None,
},
"condition" => Action::Condition {
condition: s.condition.unwrap_or_default(),
branches: vec![],
default: None,
},
_ => 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(),
params: params_map,
},
};
PipelineStep {
id: step_id,
action,
description: s.name,
when: s.condition,
when: None,
retry: None,
timeout_secs: None,
}
@@ -156,18 +203,58 @@ pub async fn pipeline_update(
..existing.metadata.clone()
};
// P2-12: Build PipelineSteps with proper action type (mirrors pipeline_create logic)
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 {
let params_map: HashMap<String, String> = s.params
.unwrap_or_default()
.into_iter()
.map(|(k, v)| (k, v.to_string()))
.collect();
let action = match s.action_type.as_deref().unwrap_or("hand") {
"llm_generate" => Action::LlmGenerate {
template: s.template.unwrap_or_default(),
input: params_map,
model: None,
temperature: None,
max_tokens: None,
json_mode: false,
},
"parallel" => Action::Parallel {
each: s.each.unwrap_or_else(|| "item".to_string()),
step: Box::new(PipelineStep {
id: format!("{}-body", step_id),
action: Action::Hand {
hand_id: s.hand_name.clone(),
hand_action: "execute".to_string(),
params: params_map,
},
description: None,
when: None,
retry: None,
timeout_secs: None,
}),
max_workers: None,
},
"condition" => Action::Condition {
condition: s.condition.unwrap_or_default(),
branches: vec![],
default: None,
},
_ => 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(),
params: params_map,
},
};
PipelineStep {
id: step_id,
action,
description: s.name,
when: s.condition,
when: None,
retry: None,
timeout_secs: None,
}

View File

@@ -6,7 +6,6 @@ use tauri::{AppHandle, Emitter, State};
use zclaw_pipeline::{
RunStatus,
parse_pipeline_yaml,
parse_pipeline_v2_yaml,
PipelineExecutor,
ActionRegistry,
LlmActionDriver,
@@ -16,7 +15,7 @@ use zclaw_pipeline::{
use super::{PipelineState, PipelineInfo, PipelineRunResponse, RunPipelineResponse, RunPipelineRequest};
use super::adapters::{RuntimeLlmAdapter, PipelineSkillDriver, PipelineHandDriver};
use super::helpers::{get_pipelines_directory, scan_pipelines_with_paths, scan_pipelines_full_sync, pipeline_to_info, pipeline_v2_to_info};
use super::helpers::{get_pipelines_directory, scan_pipelines_with_paths, scan_pipelines_full_sync, pipeline_to_info};
use crate::kernel_commands::KernelState;

View File

@@ -9,7 +9,7 @@ use zclaw_pipeline::{
PipelineV2,
};
use super::types::{PipelineInfo, PipelineInputInfo};
use super::types::{PipelineInfo, PipelineInputInfo, PipelineStepInfo};
pub(crate) fn get_pipelines_directory() -> Result<PathBuf, String> {
// Try to find pipelines directory
@@ -169,6 +169,32 @@ pub(crate) fn pipeline_to_info(pipeline: &Pipeline) -> PipelineInfo {
icon: pipeline.metadata.icon.clone().unwrap_or_else(|| "📦".to_string()),
version: pipeline.metadata.version.clone(),
author: pipeline.metadata.author.clone().unwrap_or_default(),
// P2-13: Expose step count from actual pipeline spec
step_count: pipeline.spec.steps.len(),
// P2-14: Expose actual pipeline steps
steps: pipeline.spec.steps.iter().map(|step| {
use zclaw_pipeline::Action;
let (action_type, hand_name) = match &step.action {
Action::LlmGenerate { .. } => ("llm_generate".to_string(), None),
Action::Parallel { .. } => ("parallel".to_string(), None),
Action::Condition { .. } => ("condition".to_string(), None),
Action::Hand { hand_id, .. } => ("hand".to_string(), Some(hand_id.clone())),
Action::Skill { skill_id, .. } => ("skill".to_string(), Some(skill_id.clone())),
Action::ClassroomRender { .. } => ("classroom_render".to_string(), None),
Action::Sequential { .. } => ("sequential".to_string(), None),
Action::FileExport { .. } => ("file_export".to_string(), None),
Action::HttpRequest { .. } => ("http_request".to_string(), None),
Action::SetVar { .. } => ("set_var".to_string(), None),
Action::Delay { .. } => ("delay".to_string(), None),
Action::SkillOrchestration { .. } => ("skill_orchestration".to_string(), None),
};
PipelineStepInfo {
name: step.id.clone(),
action_type,
hand_name,
condition: step.when.clone(),
}
}).collect(),
inputs: pipeline.spec.inputs.iter().map(|input| {
PipelineInputInfo {
name: input.name.clone(),
@@ -225,5 +251,8 @@ pub(crate) fn pipeline_v2_to_info(v2: &PipelineV2) -> PipelineInfo {
options: param.options.clone(),
}
}).collect(),
// V2 pipelines don't have steps in the same format
step_count: 0,
steps: vec![],
}
}

View File

@@ -28,6 +28,22 @@ pub struct PipelineInfo {
pub author: String,
/// Input parameters
pub inputs: Vec<PipelineInputInfo>,
/// P2-13: Step count (was missing, causing frontend to show 0)
#[serde(default)]
pub step_count: usize,
/// P2-14: Actual pipeline steps (populated in pipeline_get detail view)
#[serde(default)]
pub steps: Vec<PipelineStepInfo>,
}
/// P2-14: Pipeline step info for detail view
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct PipelineStepInfo {
pub name: String,
pub action_type: String,
pub hand_name: Option<String>,
pub condition: Option<String>,
}
/// Pipeline input parameter info