refactor: rename wait_for_approval to ask_user
More general-purpose user intervention tool — not just approve/reject, but any question or input request. Renames across Rust backend, Vue frontend, prompts, and status strings. Tool: wait_for_approval → ask_user (param: reason → question) Status: WaitingApproval → WaitingUser, waiting_approval → waiting_user Enum: NeedsApproval → NeedsInput
This commit is contained in:
56
src/agent.rs
56
src/agent.rs
@@ -56,7 +56,7 @@ pub fn plan_infos_from_state(state: &AgentState) -> Vec<PlanStepInfo> {
|
||||
let status = match s.status {
|
||||
StepStatus::Pending => "pending",
|
||||
StepStatus::Running => "running",
|
||||
StepStatus::WaitingApproval => "waiting_approval",
|
||||
StepStatus::WaitingUser => "waiting_user",
|
||||
StepStatus::Done => "done",
|
||||
StepStatus::Failed => "failed",
|
||||
};
|
||||
@@ -428,18 +428,18 @@ async fn agent_loop(
|
||||
.and_then(|json| serde_json::from_str::<AgentState>(&json).ok())
|
||||
.unwrap_or_else(AgentState::new);
|
||||
|
||||
// Resume directly if: workflow is failed/done/waiting_approval,
|
||||
// OR if state snapshot has a WaitingApproval step (e.g. after pod restart)
|
||||
let has_waiting_step = state.steps.iter().any(|s| matches!(s.status, StepStatus::WaitingApproval));
|
||||
// Resume directly if: workflow is failed/done/waiting_user,
|
||||
// OR if state snapshot has a WaitingUser step (e.g. after pod restart)
|
||||
let has_waiting_step = state.steps.iter().any(|s| matches!(s.status, StepStatus::WaitingUser));
|
||||
let is_resuming = wf.status == "failed" || wf.status == "done"
|
||||
|| wf.status == "waiting_approval" || has_waiting_step;
|
||||
|| wf.status == "waiting_user" || has_waiting_step;
|
||||
if is_resuming {
|
||||
// Reset Failed/WaitingApproval steps so they get re-executed
|
||||
// Reset Failed/WaitingUser steps so they get re-executed
|
||||
for step in &mut state.steps {
|
||||
if matches!(step.status, StepStatus::Failed) {
|
||||
step.status = StepStatus::Pending;
|
||||
}
|
||||
if matches!(step.status, StepStatus::WaitingApproval) {
|
||||
if matches!(step.status, StepStatus::WaitingUser) {
|
||||
// Mark as Running so it continues (not re-plans)
|
||||
step.status = StepStatus::Running;
|
||||
}
|
||||
@@ -483,7 +483,7 @@ async fn agent_loop(
|
||||
}
|
||||
state.phase = AgentPhase::Executing { step: next };
|
||||
// Only clear chat history when advancing to a new step;
|
||||
// keep it when resuming the same step after wait_for_approval
|
||||
// keep it when resuming the same step after ask_user
|
||||
if !was_same_step {
|
||||
state.current_step_chat_history.clear();
|
||||
}
|
||||
@@ -722,12 +722,12 @@ fn build_step_tools() -> Vec<Tool> {
|
||||
},
|
||||
"required": ["content"]
|
||||
})),
|
||||
make_tool("wait_for_approval", "暂停执行,等待用户确认后继续。用于关键决策点。", serde_json::json!({
|
||||
make_tool("ask_user", "向用户提问,暂停执行等待用户回复。用于需要用户输入、确认或决策的场景。", serde_json::json!({
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"reason": { "type": "string", "description": "说明为什么需要用户确认" }
|
||||
"question": { "type": "string", "description": "要向用户提出的问题或需要确认的内容" }
|
||||
},
|
||||
"required": ["reason"]
|
||||
"required": ["question"]
|
||||
})),
|
||||
make_tool("step_done", "完成当前步骤。必须提供摘要和产出物列表(无产出物时传空数组)。", serde_json::json!({
|
||||
"type": "object",
|
||||
@@ -827,7 +827,7 @@ fn build_feedback_prompt(project_id: &str, state: &AgentState, feedback: &str) -
|
||||
let status = match s.status {
|
||||
StepStatus::Done => " [done]",
|
||||
StepStatus::Running => " [running]",
|
||||
StepStatus::WaitingApproval => " [waiting]",
|
||||
StepStatus::WaitingUser => " [waiting]",
|
||||
StepStatus::Failed => " [FAILED]",
|
||||
StepStatus::Pending => "",
|
||||
};
|
||||
@@ -1326,8 +1326,8 @@ async fn run_step_loop(
|
||||
}
|
||||
}
|
||||
|
||||
"wait_for_approval" => {
|
||||
let reason = args["reason"].as_str().unwrap_or("等待确认");
|
||||
"ask_user" => {
|
||||
let reason = args["question"].as_str().unwrap_or("等待确认");
|
||||
let _ = broadcast_tx.send(WsMessage::ActivityUpdate {
|
||||
workflow_id: workflow_id.to_string(),
|
||||
activity: format!("步骤 {} — 等待用户确认: {}", step_order, reason),
|
||||
@@ -1336,18 +1336,18 @@ async fn run_step_loop(
|
||||
// Broadcast waiting status
|
||||
let _ = broadcast_tx.send(WsMessage::PlanUpdate {
|
||||
workflow_id: workflow_id.to_string(),
|
||||
steps: plan_infos_from_state_with_override(step_order, "waiting_approval",
|
||||
steps: plan_infos_from_state_with_override(step_order, "waiting_user",
|
||||
pool, workflow_id).await,
|
||||
});
|
||||
let _ = broadcast_tx.send(WsMessage::WorkflowStatusUpdate {
|
||||
workflow_id: workflow_id.to_string(),
|
||||
status: "waiting_approval".into(),
|
||||
status: "waiting_user".into(),
|
||||
});
|
||||
let _ = sqlx::query("UPDATE workflows SET status = 'waiting_approval' WHERE id = ?")
|
||||
let _ = sqlx::query("UPDATE workflows SET status = 'waiting_user' WHERE id = ?")
|
||||
.bind(workflow_id)
|
||||
.execute(pool)
|
||||
.await;
|
||||
log_execution(pool, broadcast_tx, workflow_id, step_order, "wait_for_approval", reason, reason, "waiting").await;
|
||||
log_execution(pool, broadcast_tx, workflow_id, step_order, "ask_user", reason, reason, "waiting").await;
|
||||
|
||||
tracing::info!("[workflow {}] Step {} waiting for approval: {}", workflow_id, step_order, reason);
|
||||
|
||||
@@ -1370,7 +1370,7 @@ async fn run_step_loop(
|
||||
|
||||
if approval_content.starts_with("rejected:") {
|
||||
let reason = approval_content.strip_prefix("rejected:").unwrap_or("").trim();
|
||||
log_execution(pool, broadcast_tx, workflow_id, step_order, "wait_for_approval", "rejected", reason, "failed").await;
|
||||
log_execution(pool, broadcast_tx, workflow_id, step_order, "ask_user", "rejected", reason, "failed").await;
|
||||
step_chat_history.push(ChatMessage::tool_result(&tc.id, &format!("用户拒绝: {}", reason)));
|
||||
step_done_result = Some(StepResult {
|
||||
status: StepResultStatus::Failed { error: format!("用户终止: {}", reason) },
|
||||
@@ -1582,7 +1582,7 @@ async fn run_step_loop(
|
||||
}
|
||||
|
||||
/// Helper to get plan step infos with a status override for a specific step.
|
||||
/// Used during wait_for_approval in the step sub-loop where we don't have
|
||||
/// Used during ask_user in the step sub-loop where we don't have
|
||||
/// mutable access to the AgentState.
|
||||
async fn plan_infos_from_state_with_override(
|
||||
step_order: i32,
|
||||
@@ -1609,7 +1609,7 @@ async fn plan_infos_from_state_with_override(
|
||||
match s.status {
|
||||
StepStatus::Pending => "pending",
|
||||
StepStatus::Running => "running",
|
||||
StepStatus::WaitingApproval => "waiting_approval",
|
||||
StepStatus::WaitingUser => "waiting_user",
|
||||
StepStatus::Done => "done",
|
||||
StepStatus::Failed => "failed",
|
||||
}.to_string()
|
||||
@@ -1731,9 +1731,9 @@ async fn run_agent_loop(
|
||||
});
|
||||
let _ = broadcast_tx.send(WsMessage::WorkflowStatusUpdate {
|
||||
workflow_id: workflow_id.to_string(),
|
||||
status: "waiting_approval".into(),
|
||||
status: "waiting_user".into(),
|
||||
});
|
||||
let _ = sqlx::query("UPDATE workflows SET status = 'waiting_approval' WHERE id = ?")
|
||||
let _ = sqlx::query("UPDATE workflows SET status = 'waiting_user' WHERE id = ?")
|
||||
.bind(workflow_id)
|
||||
.execute(pool)
|
||||
.await;
|
||||
@@ -1896,11 +1896,11 @@ async fn run_agent_loop(
|
||||
save_state_snapshot(pool, workflow_id, step_order, &state).await;
|
||||
return Err(anyhow::anyhow!("Step {} failed: {}", step_order, error));
|
||||
}
|
||||
StepResultStatus::NeedsApproval { message: _ } => {
|
||||
// This shouldn't normally happen since wait_for_approval is handled inside
|
||||
StepResultStatus::NeedsInput { message: _ } => {
|
||||
// This shouldn't normally happen since ask_user is handled inside
|
||||
// run_step_loop, but handle gracefully
|
||||
if let Some(s) = state.steps.iter_mut().find(|s| s.order == step_order) {
|
||||
s.status = StepStatus::WaitingApproval;
|
||||
s.status = StepStatus::WaitingUser;
|
||||
}
|
||||
save_state_snapshot(pool, workflow_id, step_order, &state).await;
|
||||
continue;
|
||||
@@ -1934,7 +1934,7 @@ async fn run_agent_loop(
|
||||
let marker = match s.status {
|
||||
StepStatus::Done => " [done]",
|
||||
StepStatus::Running => " [running]",
|
||||
StepStatus::WaitingApproval => " [waiting]",
|
||||
StepStatus::WaitingUser => " [waiting]",
|
||||
StepStatus::Failed => " [FAILED]",
|
||||
StepStatus::Pending => "",
|
||||
};
|
||||
@@ -2238,7 +2238,7 @@ mod tests {
|
||||
let names: Vec<&str> = tools.iter().map(|t| t.function.name.as_str()).collect();
|
||||
for expected in &["execute", "read_file", "write_file", "list_files",
|
||||
"start_service", "stop_service", "update_scratchpad",
|
||||
"wait_for_approval", "kb_search", "kb_read"] {
|
||||
"ask_user", "kb_search", "kb_read"] {
|
||||
assert!(names.contains(expected), "{} must be in step tools", expected);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user