重构: 巨函数拆分 + 清理历史标记注释 + custom_prompt/停止按钮/tunnel 改进

This commit is contained in:
lxy
2026-07-31 21:36:12 +08:00
parent 365af554da
commit bd9031d35d
73 changed files with 1421 additions and 1064 deletions
+27 -25
View File
@@ -40,7 +40,7 @@ impl Node for HumanNode {
.and_then(|v| v.as_u64())
.unwrap_or(3600);
// F-260615-01: 解析 select_type(缺省 Single,向后兼容)。非 "multiple" 一律按 Single 处理。
// 解析 select_type(缺省 Single,向后兼容)。非 "multiple" 一律按 Single 处理。
let select_type = match config.get("select_type").and_then(|v| v.as_str()) {
Some("multiple") => SelectType::Multiple,
_ => SelectType::Single,
@@ -74,7 +74,7 @@ impl Node for HumanNode {
Ok(WorkflowEvent::HumanApprovalResponse {
execution_id, node_id, decision, decisions, comment,
}) if execution_id == ctx.execution_id && node_id == ctx.node_id => {
// F-260615-01: 归一化决策集合(优先用 decisions 数组,空则回退兼容 decision 单值)
// 归一化决策集合(优先用 decisions 数组,空则回退兼容 decision 单值)
// select_type=Single → 决策数必须 =1
// select_type=Multiple → 决策数必须 ≥1
// options 空 → 允许自由文本(仅受数量约束);
@@ -91,24 +91,26 @@ impl Node for HumanNode {
let each_valid = picked.iter().all(|d| !d.is_empty())
&& (options.is_empty() || picked.iter().all(|d| options.contains(d)));
if count_ok && each_valid {
// F-260616-06 阶段2: 拒绝语义化。
// 拒绝语义化。
// 审批拒绝此前与同意一样返 Ok —— 语义反转(审批被拒却报"成功"),
// 下游无法据 failed 触发退回/重做。
// 现:decision 命中拒绝关键字(见 REJECT_KEYWORDS)→ 返 Err
// "人工审批被拒绝(用户选择: <decision>)",executor Err 分支 set_failed
// → 工作流 failed 状态 → 阶段2 推进链可据 failed 触发退回。
// → 工作流 failed 状态 → 推进链可据 failed 触发退回。
// 行为变更:审批拒绝从 Ok → Err,标注(同步通知主代理)。
if contains_reject(&picked) {
let primary = picked.first().cloned().unwrap_or_default();
let comment_str = comment.unwrap_or_default();
// 意见后缀:空 comment 不拼接,非空才追加(避免空括号)
let suffix = if comment_str.is_empty() {
String::new()
} else {
format!(";意见: {}", comment_str)
};
return Err(anyhow::anyhow!(
"人工审批被拒绝(用户选择: {}){}",
primary,
if comment_str.is_empty() {
String::new()
} else {
format!(";意见: {}", comment_str)
}
suffix,
));
}
// 输出统一含 decisions 数组;保留 decision 取首项(向后兼容下游消费者)
@@ -225,7 +227,7 @@ mod tests {
}
/// 发一条审批响应到事件总线(模拟前端 approve_human_approval IPC 走完后的链路)。
/// F-260615-01: 单选调用方仅填 decision;多选调用方填 decisions。
/// 单选调用方仅填 decision;多选调用方填 decisions。
async fn send_response(
event_bus: &EventBus,
execution_id: &str,
@@ -244,7 +246,7 @@ mod tests {
.await;
}
/// F-260615-01: 多选响应发送助手(填 decisions 数组,decision 留空)
/// 多选响应发送助手(填 decisions 数组,decision 留空)
async fn send_response_multi(
event_bus: &EventBus,
execution_id: &str,
@@ -568,9 +570,9 @@ mod tests {
assert_eq!(sm.get(&"h".to_string()), NodeStatus::Cancelled);
}
// ===== F-260615-01: 多选审批覆盖 =====
// ===== 多选审批覆盖 =====
/// F-260615-01: select_type=multiple + 多 decisions(均∈options) → 返回 decisions 数组
/// select_type=multiple + 多 decisions(均∈options) → 返回 decisions 数组
#[tokio::test]
async fn multiple_select_returns_decisions_array() {
let bus = EventBus::new();
@@ -596,7 +598,7 @@ mod tests {
assert_eq!(out.data["comment"], json!("多选"));
}
/// F-260615-01: select_type=single 缺省 + decisions 多个 → 校验失败(count!=1)忽略后超时
/// select_type=single 缺省 + decisions 多个 → 校验失败(count!=1)忽略后超时
#[tokio::test]
async fn single_select_rejects_multiple_decisions_then_timeout() {
let bus = EventBus::new();
@@ -618,7 +620,7 @@ mod tests {
assert!(err.contains("超时"), "single 下多 decisions 应被忽略后超时, 实际: {}", err);
}
/// F-260615-01: select_type=multiple 但 decisions 含 ∉ options 的项 → 非法忽略后超时
/// select_type=multiple 但 decisions 含 ∉ options 的项 → 非法忽略后超时
#[tokio::test]
async fn multiple_select_invalid_option_ignored_then_timeout() {
let bus = EventBus::new();
@@ -644,7 +646,7 @@ mod tests {
assert!(err.contains("超时"), "含非法 option 应被忽略后超时, 实际: {}", err);
}
/// F-260615-01: 兼容旧调用方 —— 不填 select_type(缺省 single) + 只填 decision 单值,应正常通过
/// 兼容旧调用方 —— 不填 select_type(缺省 single) + 只填 decision 单值,应正常通过
/// (即所有未改造的现有 Request 均按 single 解析,零改动)
#[tokio::test]
async fn default_single_with_legacy_decision_single_value() {
@@ -664,10 +666,10 @@ mod tests {
assert_eq!(out.data["decisions"], json!(["同意"]), "兼容回退后 decisions 应含单值");
}
// ===== F-260616-06 阶段2: 审批拒绝语义化(行为变更: 拒绝从 Ok → Err) =====
// ===== 审批拒绝语义化(行为变更: 拒绝从 Ok → Err) =====
/// F-260616-06: 默认 options `["同意","拒绝"]` 下选「拒绝」→ Err(不再 Ok)。
/// 阶段2 推进链依赖工作流 failed 触发退回,故拒绝必须让节点返 Err → executor set_failed。
/// 默认 options `["同意","拒绝"]` 下选「拒绝」→ Err(不再 Ok)。
/// 推进链依赖工作流 failed 触发退回,故拒绝必须让节点返 Err → executor set_failed。
#[tokio::test]
async fn reject_decision_returns_error() {
let bus = EventBus::new();
@@ -697,7 +699,7 @@ mod tests {
);
}
/// F-260616-06: 同一 options 下选「同意」→ Ok(通过路径不回归)。
/// 同一 options 下选「同意」→ Ok(通过路径不回归)。
#[tokio::test]
async fn approve_decision_still_ok() {
let bus = EventBus::new();
@@ -718,7 +720,7 @@ mod tests {
assert_eq!(out.data["decision"], json!("同意"));
}
/// F-260616-06: 英文 reject 关键字同样识别为拒绝 → Err(归一化大小写/空白)。
/// 英文 reject 关键字同样识别为拒绝 → Err(归一化大小写/空白)。
/// 多关键字覆盖走 reject_keyword_detection_normalized 纯单元测试,此处仅验证端到端一条。
#[tokio::test]
async fn english_reject_keyword_returns_error() {
@@ -744,7 +746,7 @@ mod tests {
);
}
/// F-260616-06: 多选场景,picked 含一项拒绝 → 整单拒绝 → Err
/// 多选场景,picked 含一项拒绝 → 整单拒绝 → Err
/// (选了「驳回」即驳回,即便同时选了「同意」)。
#[tokio::test]
async fn multiple_select_with_one_reject_returns_error() {
@@ -776,7 +778,7 @@ mod tests {
assert!(err.contains("拒绝"), "多选含拒绝项应返 Err, 实际: {}", err);
}
/// F-260616-06: options 空的自由文本场景 —— 明确拒绝词("拒绝")仍返 Err,
/// options 空的自由文本场景 —— 明确拒绝词("拒绝")仍返 Err,
/// 其余自由文本(非拒绝词)仍按通过处理(向后兼容,不阻断自由反馈)。
#[tokio::test]
async fn empty_options_free_text_reject_keyword_still_errors() {
@@ -793,7 +795,7 @@ mod tests {
assert!(err.contains("拒绝"), "自由文本明确为拒绝词仍应 Err, 实际: {}", err);
}
/// F-260616-06: options 空的自由文本场景 —— 非拒绝词自由文本仍返 Ok(不误伤自由反馈)。
/// options 空的自由文本场景 —— 非拒绝词自由文本仍返 Ok(不误伤自由反馈)。
/// (empty_options_allows_free_text 已覆盖 "改成先做B方案" → Ok,此处补一条非拒绝中文短句。)
#[tokio::test]
async fn empty_options_non_reject_free_text_still_ok() {
@@ -810,7 +812,7 @@ mod tests {
assert_eq!(out.data["decision"], json!("再讨论一下"));
}
/// F-260616-06 单元: 关键字判定函数归一化(去空白+小写)与边界。
/// 单元: 关键字判定函数归一化(去空白+小写)与边界。
#[test]
fn reject_keyword_detection_normalized() {
assert!(is_reject_decision("拒绝"));