From 06319c5a47e343bb0d77ceee81cc3bf5bd76f331 Mon Sep 17 00:00:00 2001 From: Neonforge <48338160+Neonforge98@users.noreply.github.com> Date: Fri, 14 Aug 2026 17:10:59 -0700 Subject: [PATCH 1/7] refactor(orchestrator): retire the automated review loop backend Pre-commit hook ran. Total eslint: 0, total circular: 0 --- .../tool_infra/project/execution.rs | 187 ++---------------- .../src/orchestrator_notify/handlers.rs | 176 ----------------- .../agent-core/src/orchestrator_notify/mod.rs | 162 ++------------- .../src/tests/orchestrator_notify_tests.rs | 180 ----------------- .../src/orchestrator/branch_health.rs | 90 --------- .../src/orchestrator/commands.rs | 152 +------------- .../src/orchestrator/follow_up.rs | 82 -------- .../src/orchestrator/mod.rs | 2 - .../src/orchestrator/state_machine.rs | 98 +-------- .../orchestrator/tests/state_machine_tests.rs | 100 +--------- src-tauri/src/commands/handler_list.inc | 4 - 11 files changed, 41 insertions(+), 1192 deletions(-) delete mode 100644 src-tauri/crates/agent-core/src/tests/orchestrator_notify_tests.rs delete mode 100644 src-tauri/crates/project-management/src/orchestrator/branch_health.rs delete mode 100644 src-tauri/crates/project-management/src/orchestrator/follow_up.rs diff --git a/src-tauri/crates/agent-core/src/foundation/tool_infra/project/execution.rs b/src-tauri/crates/agent-core/src/foundation/tool_infra/project/execution.rs index 93d24f5964..9604dc6eac 100644 --- a/src-tauri/crates/agent-core/src/foundation/tool_infra/project/execution.rs +++ b/src-tauri/crates/agent-core/src/foundation/tool_infra/project/execution.rs @@ -68,109 +68,6 @@ pub(crate) fn build_agent_prompt( parts.join("\n") } -/// Build a review task prompt (Rust port of the frontend's -/// `buildReviewTaskPrompt` — the backend launches review sessions now). -pub(crate) fn build_review_prompt( - short_id: &str, - frontmatter: &WorkItemFrontmatter, - body: &str, -) -> String { - let mut parts = Vec::new(); - parts.push(format!( - "Review the code changes for work item: {}", - short_id - )); - parts.push(format!("\n## Work Item Title\n{}", frontmatter.title)); - if !body.is_empty() { - parts.push(format!("\n## Work Item Description\n{}", body)); - } - if !frontmatter.todos.is_empty() { - parts.push("\n## Acceptance Criteria / Todos".to_string()); - for todo in &frontmatter.todos { - let check = if todo.status == super::helpers::TODO_STATUS_COMPLETED { - "[x]" - } else { - "[ ]" - }; - parts.push(format!(" {} {}", check, todo.content)); - } - } - - let branch = frontmatter - .proof_of_work - .as_ref() - .and_then(|pow| pow.branch.clone()); - parts.push("\n## Branch Information".to_string()); - if let Some(ref branch_name) = branch { - parts.push(format!("- Work item branch: `{}`", branch_name)); - } - parts.push("- Base branch: `main`".to_string()); - parts.push(format!( - "- Run: `git diff main..{}` to see all changes", - branch.as_deref().unwrap_or("HEAD") - )); - - if let Some(feedback) = frontmatter - .proof_of_work - .as_ref() - .and_then(|pow| pow.review_feedback.as_ref()) - .filter(|fb| !matches!(fb.outcome, ReviewOutcome::Approved)) - { - parts.push("\n## Previous Review Feedback (from the last review round)".to_string()); - parts.push(format!("Outcome: {:?}", feedback.outcome)); - parts.push(format!("Summary: {}", feedback.summary)); - let round = frontmatter - .proof_of_work - .as_ref() - .map(|pow| pow.review_history.len()) - .unwrap_or(0) - + 1; - parts.push(format!( - "\nThis is review round {}. Check if the above issues from the previous round \ - have been addressed in the current diff.", - round - )); - } - - parts.join("\n") -} - -/// Append review feedback the coding agent must address (fix rounds). -pub(crate) fn append_fix_feedback(prompt: &mut String, frontmatter: &WorkItemFrontmatter) { - let Some(feedback) = frontmatter - .proof_of_work - .as_ref() - .and_then(|pow| pow.review_feedback.as_ref()) - .filter(|fb| !matches!(fb.outcome, ReviewOutcome::Approved)) - else { - return; - }; - - prompt.push_str("\n\n## Review Feedback To Address\n"); - prompt.push_str(&format!("Outcome: {:?}\n", feedback.outcome)); - prompt.push_str(&format!("Summary: {}\n", feedback.summary)); - if !feedback.comments.is_empty() { - prompt.push_str("Address all of the following items before finishing:\n"); - for (idx, comment) in feedback.comments.iter().enumerate() { - let loc = match (&comment.file_path, comment.line) { - (Some(path), Some(line)) => format!("{}:{} — ", path, line), - (Some(path), None) => format!("{} — ", path), - _ => String::new(), - }; - prompt.push_str(&format!( - " {}. [{:?}] {}{}\n", - idx + 1, - comment.severity, - loc, - comment.message - )); - } - } - prompt.push_str( - "\nFix the issues above explicitly and verify they are resolved before you finish.\n", - ); -} - fn parse_agent_defs_for_execution( content: &str, path: &std::path::Path, @@ -756,90 +653,38 @@ pub async fn start_work_item_session_with_reason( /// Which post-transition session the orchestrator needs launched. #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub enum PhaseLaunch { - /// State machine moved to Review and already queued a pending review - /// linked session — launch the reviewer. - Review, - /// Review requested changes; state machine moved back to Coding and - /// queued a pending coding linked session — launch the fix round. - Fix, /// Coding failed and auto-retry is on; phase is already back at Coding — /// relaunch the owner agent. Retry, } -/// Launch the session demanded by an orchestrator transition -/// (LaunchReview / LaunchFix / RetryAgent). Unlike [`start_work_item`] this -/// does NOT run `snapshot_config` or phase validation: the state machine has -/// already performed the transition; we only materialize the session. -/// -/// Review reuses the work item's account; the review config may override -/// account/model. +/// Launch the session demanded by an orchestrator transition (RetryAgent). +/// Unlike [`start_work_item`] this does NOT run `snapshot_config` or phase +/// validation: the state machine has already performed the transition; we +/// only materialize the session. pub async fn launch_phase_session( project_slug: &str, short_id: &str, app: &tauri::AppHandle, phase: PhaseLaunch, ) -> Result { - let (review_account, review_model) = if phase == PhaseLaunch::Review { - let ctx_data = run_blocking("read_review_config", { - let slug = project_slug.to_string(); - let sid = short_id.to_string(); - move || io::read_work_item(&slug, &sid) - }) - .await?; - let review_config = ctx_data - .frontmatter - .orchestrator_config - .as_ref() - .and_then(|c| c.effective_review_config()); - ( - review_config.as_ref().and_then(|rc| rc.account_id.clone()), - review_config.as_ref().and_then(|rc| rc.model_id.clone()), - ) - } else { - (None, None) - }; - - let ctx = resolve_launch_context( - project_slug, - short_id, - review_account.as_deref(), - review_model.as_deref(), - None, - ) - .await?; + let PhaseLaunch::Retry = phase; + let ctx = resolve_launch_context(project_slug, short_id, None, None, None).await?; - let (agent_role, mut prompt) = match phase { - PhaseLaunch::Review => ( - "review".to_string(), - build_review_prompt(short_id, &ctx.data.frontmatter, &ctx.data.body), - ), - PhaseLaunch::Fix | PhaseLaunch::Retry => { - let mut prompt = if ctx.agent_def.is_some() { - build_agent_prompt(short_id, &ctx.data.frontmatter, &ctx.data.body) - } else { - build_project_prompt(short_id, &ctx.data.frontmatter, &ctx.data.body) - }; - if phase == PhaseLaunch::Fix { - append_fix_feedback(&mut prompt, &ctx.data.frontmatter); - } - let role = ctx - .agent_def - .as_ref() - .map(|d| d.name.clone()) - .unwrap_or_else(|| "sde".to_string()); - (role, prompt) - } + let mut prompt = if ctx.agent_def.is_some() { + build_agent_prompt(short_id, &ctx.data.frontmatter, &ctx.data.body) + } else { + build_project_prompt(short_id, &ctx.data.frontmatter, &ctx.data.body) }; + let agent_role = ctx + .agent_def + .as_ref() + .map(|d| d.name.clone()) + .unwrap_or_else(|| "sde".to_string()); append_workspace_section(&mut prompt, &ctx); - // Review sessions run the plain SDE harness (no custom agent definition) - // so the reviewer judges with fresh eyes. - let agent_definition_id = match phase { - PhaseLaunch::Review => None, - _ => ctx.agent_def_id.clone(), - }; + let agent_definition_id = ctx.agent_def_id.clone(); let session_id = crate::session::launch::launch_agent_session( app, diff --git a/src-tauri/crates/agent-core/src/orchestrator_notify/handlers.rs b/src-tauri/crates/agent-core/src/orchestrator_notify/handlers.rs index d000f47af4..3e01a7b2ea 100644 --- a/src-tauri/crates/agent-core/src/orchestrator_notify/handlers.rs +++ b/src-tauri/crates/agent-core/src/orchestrator_notify/handlers.rs @@ -7,182 +7,6 @@ //! of the session log and shapes it into a typed payload — the dispatch //! site lives in `orchestrator_notify::mod`. -pub(super) fn extract_review_feedback( - session_id: &str, -) -> Option { - use core_types::workflow::{ReviewFeedback, ReviewOutcome}; - - let messages = crate::session::persistence::load_messages(session_id).ok()?; - - let last_assistant = messages - .iter() - .rev() - .find(|msg| msg.role == "assistant" && !msg.content.is_empty())?; - - let content = &last_assistant.content; - - if let Some(feedback) = parse_structured_review_block(content, session_id) { - return Some(feedback); - } - - tracing::debug!( - "[review] No structured block found for session {}, falling back to keyword heuristics", - session_id - ); - let content_lower = content.to_lowercase(); - let outcome = if content_lower.contains("approved") - && !content_lower.contains("not approved") - && !content_lower.contains("changes needed") - && !content_lower.contains("changes requested") - { - ReviewOutcome::Approved - } else { - ReviewOutcome::ChangesRequested - }; - - let summary = extract_first_sentence(content); - - Some(ReviewFeedback { - outcome, - summary, - comments: Vec::new(), - session_id: session_id.to_string(), - reviewed_at: chrono::Utc::now().to_rfc3339(), - resolved_from_previous: Vec::new(), - }) -} - -pub(crate) fn parse_structured_review_block( - content: &str, - session_id: &str, -) -> Option { - use core_types::workflow::{ReviewComment, ReviewFeedback, ReviewOutcome}; - - let start_marker = "---REVIEW_START---"; - let end_marker = "---REVIEW_END---"; - - let start_idx = content.find(start_marker)?; - let block_start = start_idx + start_marker.len(); - let end_idx = content[block_start..].find(end_marker)?; - let block = &content[block_start..block_start + end_idx]; - - let mut outcome: Option = None; - let mut summary = String::new(); - let mut comments: Vec = Vec::new(); - let mut in_issues = false; - - for line in block.lines() { - let trimmed = line.trim(); - if trimmed.is_empty() { - continue; - } - - if let Some(rest) = trimmed.strip_prefix("VERDICT:") { - let verdict = rest.trim().to_uppercase(); - outcome = Some(match verdict.as_str() { - "APPROVED" => ReviewOutcome::Approved, - "CHANGES_REQUESTED" => ReviewOutcome::ChangesRequested, - _ => ReviewOutcome::ChangesRequested, - }); - in_issues = false; - } else if let Some(rest) = trimmed.strip_prefix("SUMMARY:") { - summary = rest.trim().to_string(); - in_issues = false; - } else if trimmed == "ISSUES:" { - in_issues = true; - } else if in_issues && trimmed.starts_with("- [") { - if let Some(comment) = parse_issue_line(trimmed) { - comments.push(comment); - } - } - } - - let outcome = outcome?; - - Some(ReviewFeedback { - outcome, - summary, - comments, - session_id: session_id.to_string(), - reviewed_at: chrono::Utc::now().to_rfc3339(), - resolved_from_previous: Vec::new(), - }) -} - -pub(crate) fn parse_issue_line(line: &str) -> Option { - use core_types::workflow::{ReviewComment, ReviewCommentSeverity}; - - let rest = line.strip_prefix("- ")?; - - let (severity, after_tag) = if let Some(r) = rest.strip_prefix("[ERROR]") { - (ReviewCommentSeverity::Error, r) - } else if let Some(r) = rest.strip_prefix("[WARNING]") { - (ReviewCommentSeverity::Warning, r) - } else if let Some(r) = rest.strip_prefix("[SUGGESTION]") { - (ReviewCommentSeverity::Suggestion, r) - } else { - let r = rest.strip_prefix("[PRAISE]")?; - (ReviewCommentSeverity::Praise, r) - }; - - let after_tag = after_tag.trim(); - - let (file_path, line_num, message) = - if let Some(dash_pos) = after_tag.find(" — ").or_else(|| after_tag.find(" - ")) { - let location = &after_tag[..dash_pos].trim(); - let dash_len = if after_tag[dash_pos..].starts_with(" — ") { - " — ".len() - } else { - " - ".len() - }; - let msg = after_tag[dash_pos + dash_len..].trim().to_string(); - - let (fp, ln) = parse_file_location(location); - (fp, ln, msg) - } else { - (None, None, after_tag.to_string()) - }; - - Some(ReviewComment { - file_path, - line: line_num, - severity, - message, - }) -} - -pub(crate) fn parse_file_location(location: &str) -> (Option, Option) { - if location.is_empty() { - return (None, None); - } - if let Some(colon_pos) = location.rfind(':') { - let path_part = &location[..colon_pos]; - let line_part = &location[colon_pos + 1..]; - if let Ok(line_num) = line_part.parse::() { - return (Some(path_part.to_string()), Some(line_num)); - } - } - (Some(location.to_string()), None) -} - -pub(crate) fn extract_first_sentence(content: &str) -> String { - let trimmed = content.trim(); - for (idx, ch) in trimmed.char_indices() { - if (ch == '.' || ch == '!' || ch == '\n') && idx > 10 { - let sentence = trimmed[..=idx].trim(); - if sentence.len() <= 500 { - return sentence.to_string(); - } - } - } - let truncated: String = crate::utils::safe_truncate_chars_to_string(&trimmed, 300); - if truncated.len() < trimmed.len() { - format!("{}…", truncated.trim_end()) - } else { - truncated - } -} - /// Proof-of-work facts gathered from git, decoupled from the frontmatter /// mutation so the subprocess I/O can run OUTSIDE the work item's /// `BEGIN IMMEDIATE` transaction. Running git inside the transaction diff --git a/src-tauri/crates/agent-core/src/orchestrator_notify/mod.rs b/src-tauri/crates/agent-core/src/orchestrator_notify/mod.rs index 9b7d79d2e1..e21b974a6c 100644 --- a/src-tauri/crates/agent-core/src/orchestrator_notify/mod.rs +++ b/src-tauri/crates/agent-core/src/orchestrator_notify/mod.rs @@ -464,75 +464,23 @@ pub async fn notify_orchestrator_session_terminal( total_tokens, ); - use core_types::workflow::{AgentRole, OrchestratorPhase}; - let effective_role = agent_role.or_else(|| { - let phase = frontmatter - .orchestrator_state - .as_ref() - .map(|s| &s.current_phase); - match phase { - Some(OrchestratorPhase::Review) => { - tracing::debug!( - "[orchestrator] Session {} not in linked_sessions, inferring Review from phase", - session_id_owned - ); - Some(AgentRole::Review) + let _ = agent_role; + match status { + AgentSessionStatus::Completed => { + if let Some(ref collected) = collected_proof { + apply_proof_of_work(frontmatter, collected); } - _ => None, + state_machine::on_session_complete(frontmatter) + } + AgentSessionStatus::Failed => state_machine::on_session_failed( + frontmatter, + &session_id_owned, + "Session failed", + ), + _ => { + state_machine::cancel(frontmatter); + state_machine::TransitionResult::Completed } - }); - - match effective_role { - Some(AgentRole::Review) => match status { - AgentSessionStatus::Completed => { - let review_result = extract_review_feedback( - &session_id_owned, - ); - let outcome = review_result - .as_ref() - .map(|rf| rf.outcome.clone()) - .unwrap_or(project_management::projects::types::ReviewOutcome::Approved); - - if let Some(feedback) = review_result { - project_management::orchestrator::proof_of_work::set_review_feedback( - frontmatter, - feedback, - ); - } - - state_machine::on_review_complete(frontmatter, &outcome) - } - AgentSessionStatus::Failed => { - state_machine::on_review_failed( - frontmatter, - &session_id_owned, - "Review session failed", - ) - } - _ => { - state_machine::cancel(frontmatter); - state_machine::TransitionResult::Completed - } - }, - _ => match status { - AgentSessionStatus::Completed => { - if let Some(ref collected) = collected_proof { - apply_proof_of_work(frontmatter, collected); - } - state_machine::on_session_complete(frontmatter) - } - AgentSessionStatus::Failed => { - state_machine::on_session_failed( - frontmatter, - &session_id_owned, - "Session failed", - ) - } - _ => { - state_machine::cancel(frontmatter); - state_machine::TransitionResult::Completed - } - }, } }, ) @@ -614,22 +562,6 @@ pub async fn notify_orchestrator_session_terminal( ) .await; } - TransitionResult::LaunchReview => { - spawn_phase_launch( - handle, - transition_slug, - &work_item_id_for_launch, - crate::tool_infra::PhaseLaunch::Review, - ); - } - TransitionResult::LaunchFix => { - spawn_phase_launch( - handle, - transition_slug, - &work_item_id_for_launch, - crate::tool_infra::PhaseLaunch::Fix, - ); - } TransitionResult::RetryAgent => { spawn_phase_launch( handle, @@ -638,19 +570,6 @@ pub async fn notify_orchestrator_session_terminal( crate::tool_infra::PhaseLaunch::Retry, ); } - TransitionResult::CreateFollowUp => { - // Dead enum value today — the state machine never returns - // it. Kept as an explicit no-op so a future producer - // fails loudly in review rather than silently here. - tracing::warn!( - "[orchestrator] CreateFollowUp transition for session {} has no producer", - session_id - ); - } - TransitionResult::AwaitingUser => { - tracing::debug!("[orchestrator] Session {} awaiting user action", session_id); - notify_inbox_awaiting_user(&work_item_id_for_launch); - } TransitionResult::Ignored => { // Stale terminal from a session that lost the claim — // already logged inside the mutator; no follow-on. @@ -798,55 +717,6 @@ async fn notify_routine_fire_work_item_terminal( } } -/// Write an inbox notification when a work item needs the user's decision -/// (AwaitingUser) so unattended runs surface instead of silently stalling. -fn notify_inbox_awaiting_user(work_item_id: &str) { - let now = chrono::Utc::now().to_rfc3339(); - let msg = inbox::persistence::InboxMessage { - id: format!( - "orchestrator-awaiting-{}-{}", - work_item_id, - chrono::Utc::now().timestamp() - ), - title: format!( - "[Action Needed] Work item {} awaits your review", - work_item_id - ), - preview: "Orchestration paused: review outcome needs a human decision".to_string(), - content: format!( - "Work item {} reached the **awaiting user** state.\n\n\ - The automated review loop could not resolve on its own \ - (changes requested beyond max rounds, or an inconclusive review).\n\n\ - **Action needed:** open the work item and approve, retry, or close it.", - work_item_id - ), - category: "workitems".to_string(), - priority: "high".to_string(), - status: "unread".to_string(), - sender_name: Some("Orchestrator".to_string()), - metadata: "{}".to_string(), - labels: serde_json::to_string(&["awaiting-user"]) - .expect("serializing a static [&str] is infallible"), - created_at: now.clone(), - updated_at: now, - }; - if let Err(err) = inbox::persistence::upsert_message(&msg) { - tracing::warn!( - "[orchestrator] Failed to write awaiting-user inbox notification for {}: {}", - work_item_id, - err - ); - } -} - mod handlers; -use handlers::{apply_proof_of_work, collect_proof_of_work_data_bounded, extract_review_feedback}; - -#[cfg(test)] -pub(crate) use handlers::{ - extract_first_sentence, parse_file_location, parse_issue_line, parse_structured_review_block, -}; +use handlers::{apply_proof_of_work, collect_proof_of_work_data_bounded}; -#[cfg(test)] -#[path = "../tests/orchestrator_notify_tests.rs"] -mod tests; diff --git a/src-tauri/crates/agent-core/src/tests/orchestrator_notify_tests.rs b/src-tauri/crates/agent-core/src/tests/orchestrator_notify_tests.rs deleted file mode 100644 index b7baf0616c..0000000000 --- a/src-tauri/crates/agent-core/src/tests/orchestrator_notify_tests.rs +++ /dev/null @@ -1,180 +0,0 @@ -use core_types::workflow::ReviewCommentSeverity; - -use crate::orchestrator_notify::{ - extract_first_sentence, parse_file_location, parse_issue_line, parse_structured_review_block, -}; - -// -- parse_file_location -- - -#[test] -fn parse_file_location_with_line() { - let (path, line) = parse_file_location("src/foo.ts:42"); - assert_eq!(path, Some("src/foo.ts".to_string())); - assert_eq!(line, Some(42)); -} - -#[test] -fn parse_file_location_no_line() { - let (path, line) = parse_file_location("src/foo.ts"); - assert_eq!(path, Some("src/foo.ts".to_string())); - assert_eq!(line, None); -} - -#[test] -fn parse_file_location_empty() { - let (path, line) = parse_file_location(""); - assert!(path.is_none()); - assert!(line.is_none()); -} - -#[test] -fn parse_file_location_non_numeric_after_colon() { - let (path, line) = parse_file_location("src/foo.ts:bar"); - assert_eq!(path, Some("src/foo.ts:bar".to_string())); - assert_eq!(line, None); -} - -#[test] -fn parse_file_location_windows_path_with_drive() { - let (path, line) = parse_file_location("C:\\src\\foo.ts:10"); - assert_eq!(path, Some("C:\\src\\foo.ts".to_string())); - assert_eq!(line, Some(10)); -} - -// -- parse_issue_line -- - -#[test] -fn parse_issue_line_error_with_location() { - let comment = parse_issue_line("- [ERROR] src/foo.ts:42 — missing null check").unwrap(); - assert_eq!(comment.severity, ReviewCommentSeverity::Error); - assert_eq!(comment.file_path, Some("src/foo.ts".to_string())); - assert_eq!(comment.line, Some(42)); - assert_eq!(comment.message, "missing null check"); -} - -#[test] -fn parse_issue_line_warning_em_dash() { - let comment = parse_issue_line("- [WARNING] src/bar.rs — potential race condition").unwrap(); - assert_eq!(comment.severity, ReviewCommentSeverity::Warning); - assert_eq!(comment.file_path, Some("src/bar.rs".to_string())); - assert!(comment.line.is_none()); -} - -#[test] -fn parse_issue_line_suggestion_hyphen_dash() { - let comment = parse_issue_line("- [SUGGESTION] src/lib.rs - consider using iterators").unwrap(); - assert_eq!(comment.severity, ReviewCommentSeverity::Suggestion); - assert_eq!(comment.message, "consider using iterators"); -} - -#[test] -fn parse_issue_line_praise() { - let comment = parse_issue_line("- [PRAISE] src/test.rs — excellent test coverage").unwrap(); - assert_eq!(comment.severity, ReviewCommentSeverity::Praise); -} - -#[test] -fn parse_issue_line_unknown_severity() { - assert!(parse_issue_line("- [INFO] something").is_none()); -} - -#[test] -fn parse_issue_line_no_dash_prefix() { - assert!(parse_issue_line("[ERROR] missing dash prefix").is_none()); -} - -// -- extract_first_sentence -- - -#[test] -fn extract_first_sentence_period() { - let result = extract_first_sentence("This is a sentence. And another one."); - assert_eq!(result, "This is a sentence."); -} - -#[test] -fn extract_first_sentence_newline() { - let result = extract_first_sentence("First line here\nSecond line"); - assert!(result.contains("First line here")); -} - -#[test] -fn extract_first_sentence_short_content() { - let result = extract_first_sentence("Short."); - assert_eq!(result, "Short."); -} - -#[test] -fn extract_first_sentence_long_no_period() { - let long = "a".repeat(500); - let result = extract_first_sentence(&long); - assert!(result.len() < 500, "should truncate long content"); -} - -#[test] -fn extract_first_sentence_empty() { - let result = extract_first_sentence(""); - assert!(result.is_empty()); -} - -// -- parse_structured_review_block -- - -#[test] -fn parse_review_block_approved() { - let content = "\ -Some analysis here. - ----REVIEW_START--- -VERDICT: APPROVED -SUMMARY: Everything looks good ----REVIEW_END--- -"; - let feedback = parse_structured_review_block(content, "sess-1").unwrap(); - assert_eq!( - feedback.outcome, - project_management::projects::types::ReviewOutcome::Approved - ); - assert_eq!(feedback.summary, "Everything looks good"); - assert!(feedback.comments.is_empty()); - assert_eq!(feedback.session_id, "sess-1"); -} - -#[test] -fn parse_review_block_changes_requested_with_issues() { - let content = "\ ----REVIEW_START--- -VERDICT: CHANGES_REQUESTED -SUMMARY: Several issues found -ISSUES: -- [ERROR] src/main.rs:10 — null pointer -- [WARNING] src/lib.rs — performance concern -- [PRAISE] src/test.rs — good coverage ----REVIEW_END--- -"; - let feedback = parse_structured_review_block(content, "sess-2").unwrap(); - assert_eq!( - feedback.outcome, - project_management::projects::types::ReviewOutcome::ChangesRequested - ); - assert_eq!(feedback.comments.len(), 3); - assert_eq!(feedback.comments[0].severity, ReviewCommentSeverity::Error); - assert_eq!( - feedback.comments[1].severity, - ReviewCommentSeverity::Warning - ); - assert_eq!(feedback.comments[2].severity, ReviewCommentSeverity::Praise); -} - -#[test] -fn parse_review_block_missing_markers() { - assert!(parse_structured_review_block("no markers here", "sess").is_none()); -} - -#[test] -fn parse_review_block_no_verdict() { - let content = "\ ----REVIEW_START--- -SUMMARY: Missing verdict line ----REVIEW_END--- -"; - assert!(parse_structured_review_block(content, "sess").is_none()); -} diff --git a/src-tauri/crates/project-management/src/orchestrator/branch_health.rs b/src-tauri/crates/project-management/src/orchestrator/branch_health.rs deleted file mode 100644 index 133e02f4f9..0000000000 --- a/src-tauri/crates/project-management/src/orchestrator/branch_health.rs +++ /dev/null @@ -1,90 +0,0 @@ -//! Branch health checks — validate Git branch state before retry/resume. - -use git::git_command; -use std::path::Path; - -/// Result of a branch health check. -#[derive(Debug, Clone, serde::Serialize)] -#[serde(rename_all = "camelCase")] -pub struct BranchHealthResult { - pub branch_exists: bool, - pub is_clean: bool, - pub has_external_modifications: bool, - pub has_merge_conflicts: bool, - pub details: String, -} - -impl BranchHealthResult { - pub fn is_healthy(&self) -> bool { - self.branch_exists && !self.has_merge_conflicts - } -} - -/// Check the health of a Git branch before retrying or resuming a workflow. -pub fn check_branch_health(repo_path: &str, branch: &str) -> BranchHealthResult { - let repo = Path::new(repo_path); - - let branch_exists = git_branch_exists(repo, branch); - if !branch_exists { - return BranchHealthResult { - branch_exists: false, - is_clean: false, - has_external_modifications: false, - has_merge_conflicts: false, - details: format!("Branch '{}' does not exist", branch), - }; - } - - let is_clean = git_is_clean(repo); - let has_merge_conflicts = git_has_merge_conflicts(repo); - - BranchHealthResult { - branch_exists: true, - is_clean, - has_external_modifications: false, - has_merge_conflicts, - details: if has_merge_conflicts { - "Branch has unresolved merge conflicts".to_string() - } else if !is_clean { - "Branch has uncommitted changes".to_string() - } else { - "Branch is healthy".to_string() - }, - } -} - -fn git_branch_exists(repo: &Path, branch: &str) -> bool { - let Ok(mut command) = git_command() else { - return false; - }; - command - .args(["rev-parse", "--verify", branch]) - .current_dir(repo) - .output() - .map(|output| output.status.success()) - .unwrap_or(false) -} - -fn git_is_clean(repo: &Path) -> bool { - let Ok(mut command) = git_command() else { - return false; - }; - command - .args(["status", "--porcelain"]) - .current_dir(repo) - .output() - .map(|output| output.stdout.is_empty()) - .unwrap_or(false) -} - -fn git_has_merge_conflicts(repo: &Path) -> bool { - let Ok(mut command) = git_command() else { - return false; - }; - command - .args(["diff", "--name-only", "--diff-filter=U"]) - .current_dir(repo) - .output() - .map(|output| !output.stdout.is_empty()) - .unwrap_or(false) -} diff --git a/src-tauri/crates/project-management/src/orchestrator/commands.rs b/src-tauri/crates/project-management/src/orchestrator/commands.rs index 2c873fb964..809a6b2432 100644 --- a/src-tauri/crates/project-management/src/orchestrator/commands.rs +++ b/src-tauri/crates/project-management/src/orchestrator/commands.rs @@ -1,20 +1,12 @@ //! Tauri commands for the work item orchestrator. -use serde::Serialize; use tauri::Emitter; use crate::projects::events::DATA_CHANGED_EVENT; use crate::projects::io as projects_io; -use crate::projects::io::repo_resolver; -use crate::projects::types::{ - AgentRole, LinkedSessionType, OrchestratorPhase, PrStatus, -}; +use crate::projects::types::PrStatus; -use super::branch_health; use super::proof_of_work; -use super::state_machine; -use crate::projects::io::orchestrator_view; -use core_types::session::PENDING_SESSION_PLACEHOLDER; fn emit_data_changed(app: &tauri::AppHandle, project_slug: &str, work_item_id: &str) { let _ = app.emit( @@ -27,148 +19,6 @@ fn emit_data_changed(app: &tauri::AppHandle, project_slug: &str, work_item_id: & ); } -#[derive(Debug, Clone, Serialize)] -#[serde(rename_all = "camelCase")] -pub struct OrchestratorStatus { - pub current_phase: String, - pub retry_count: u32, - pub interrupted: bool, - pub has_active_config: bool, -} - -/// Cancel the active orchestrator workflow. -#[tauri::command] -pub async fn orchestrator_cancel( - project_slug: String, - work_item_id: String, - app: tauri::AppHandle, -) -> Result<(), String> { - let event_project_slug = project_slug.clone(); - let event_work_item_id = work_item_id.clone(); - tokio::task::spawn_blocking(move || -> Result<(), String> { - state_machine::mutate_work_item(&project_slug, &work_item_id, |frontmatter| { - state_machine::cancel(frontmatter); - state_machine::TransitionResult::Completed - })?; - Ok(()) - }) - .await - .map_err(|err| err.to_string())??; - - emit_data_changed(&app, &event_project_slug, &event_work_item_id); - Ok(()) -} - -/// Retry the SDE phase after a failure. Performs branch health check first. -#[tauri::command] -pub async fn orchestrator_retry( - project_slug: String, - work_item_id: String, - app: tauri::AppHandle, -) -> Result<(), String> { - let event_project_slug = project_slug.clone(); - let event_work_item_id = work_item_id.clone(); - tokio::task::spawn_blocking(move || { - let state = orchestrator_view::read_orchestrator_state(&project_slug, &work_item_id)? - .ok_or("No orchestrator state")?; - - if !matches!( - state.current_phase, - OrchestratorPhase::Failed | OrchestratorPhase::AwaitingUser - ) { - return Err(format!( - "Cannot retry: orchestrator is in phase '{:?}', expected failed or awaiting_user", - state.current_phase - )); - } - - // Branch health check still needs the on-disk repo (proof_of_work - // records the branch name; we run `git` against the bound checkout). - let data = projects_io::read_work_item(&project_slug, &work_item_id)?; - if let Some(ref pow) = data.frontmatter.proof_of_work { - if let Some(ref branch) = pow.branch { - let repo_path = - repo_resolver::resolve_repo_for_work_item(&project_slug, &work_item_id)?; - let health = branch_health::check_branch_health(&repo_path, branch); - if !health.is_healthy() { - return Err(format!("Branch health check failed: {}", health.details)); - } - } - } - - state_machine::mutate_work_item(&project_slug, &work_item_id, |fm| { - state_machine::snapshot_config(fm); - state_machine::add_linked_session( - fm, - PENDING_SESSION_PLACEHOLDER, - AgentRole::Coding, - LinkedSessionType::Native, - ); - state_machine::TransitionResult::RetryAgent - })?; - - Ok(()) - }) - .await - .map_err(|err| err.to_string())??; - - emit_data_changed(&app, &event_project_slug, &event_work_item_id); - Ok(()) -} - -/// Get the current orchestrator status for a work item. -/// -/// Reads `orchestrator_state` out of `workitem_extras.extras_json` — -/// the same blob `update_work_item_atomic` writes during every -/// transition. No parallel mirror; this is the single source of -/// truth. -#[tauri::command] -pub async fn orchestrator_get_status( - project_slug: String, - work_item_id: String, -) -> Result { - tokio::task::spawn_blocking(move || { - let state = orchestrator_view::read_orchestrator_state(&project_slug, &work_item_id)?; - - Ok(match state { - Some(state) => OrchestratorStatus { - current_phase: format!("{:?}", state.current_phase).to_lowercase(), - retry_count: state.retry_count, - interrupted: state.interrupted, - has_active_config: state.active_config.is_some(), - }, - None => OrchestratorStatus { - current_phase: "idle".to_string(), - retry_count: 0, - interrupted: false, - has_active_config: false, - }, - }) - }) - .await - .map_err(|err| err.to_string())? -} - -/// Create a follow-up work item from review feedback. -/// Returns the new work item's short ID. -#[tauri::command] -pub async fn orchestrator_create_follow_up( - project_slug: String, - parent_short_id: String, - review_feedback: String, - app: tauri::AppHandle, -) -> Result { - let event_project_slug = project_slug.clone(); - let event_parent_short_id = parent_short_id.clone(); - let result = tokio::task::spawn_blocking(move || { - super::follow_up::create_follow_up(&project_slug, &parent_short_id, &review_feedback) - }) - .await - .map_err(|err| err.to_string())??; - - emit_data_changed(&app, &event_project_slug, &event_parent_short_id); - Ok(result) -} /// Get cumulative diff stats between a base branch and a work item branch. /// diff --git a/src-tauri/crates/project-management/src/orchestrator/follow_up.rs b/src-tauri/crates/project-management/src/orchestrator/follow_up.rs deleted file mode 100644 index 6744d511ca..0000000000 --- a/src-tauri/crates/project-management/src/orchestrator/follow_up.rs +++ /dev/null @@ -1,82 +0,0 @@ -//! Follow-up work item creation when review requests changes. - -use crate::projects::io; -use crate::projects::types::{FollowUpRef, WorkItemFrontmatter}; - -/// Create a follow-up work item from review feedback. -/// -/// 1. Allocates a new short ID -/// 2. Creates a new work item with parent = original short_id -/// 3. Copies orchestrator_config from parent -/// 4. Adds FollowUpRef to the parent's follow_up_items -/// -/// Returns the new short ID. -pub fn create_follow_up( - project_slug: &str, - parent_short_id: &str, - review_feedback: &str, -) -> Result { - let parent_data = io::read_work_item(project_slug, parent_short_id)?; - let parent = &parent_data.frontmatter; - - let new_short_id = io::allocate_short_id(project_slug)?; - let now = chrono::Utc::now().to_rfc3339(); - - let title = format!("Follow-up: {} — address review feedback", parent.title); - - let new_frontmatter = WorkItemFrontmatter { - id: new_short_id.clone(), - short_id: new_short_id.clone(), - title, - project: parent.project.clone(), - status: "backlog".to_string(), - priority: parent.priority.clone(), - assignee: parent.assignee.clone(), - assignee_type: parent.assignee_type.clone(), - labels: parent.labels.clone(), - milestone: parent.milestone.clone(), - parent: Some(parent_short_id.to_string()), - stage: None, - start_date: None, - target_date: parent.target_date.clone(), - created_by: Some("orchestrator".to_string()), - origin_session: None, - created_at: now.clone(), - updated_at: now, - deleted_at: None, - starred: false, - todos: vec![], - comments: vec![], - history: vec![], - delegations: vec![], - handoff: None, - linked_sessions: vec![], - proof_of_work: None, - orchestrator_config: parent.orchestrator_config.clone(), - orchestrator_state: None, - follow_up_items: vec![], - schedule: None, - routine_source: parent.routine_source.clone(), - execution_lock: None, - close_out: None, - work_products: vec![], - }; - - let body = format!( - "## Review Feedback\n\n{}\n\n---\n\nParent work item: {}\n", - review_feedback, parent_short_id - ); - - io::write_work_item(project_slug, &new_short_id, &new_frontmatter, &body)?; - - // Add follow-up reference to the parent - let mut parent_fm = parent_data.frontmatter.clone(); - parent_fm.follow_up_items.push(FollowUpRef { - short_id: new_short_id.clone(), - reason: Some("Review requested changes".to_string()), - }); - parent_fm.updated_at = chrono::Utc::now().to_rfc3339(); - io::write_work_item(project_slug, parent_short_id, &parent_fm, &parent_data.body)?; - - Ok(new_short_id) -} diff --git a/src-tauri/crates/project-management/src/orchestrator/mod.rs b/src-tauri/crates/project-management/src/orchestrator/mod.rs index dece5750d6..eefe86dd23 100644 --- a/src-tauri/crates/project-management/src/orchestrator/mod.rs +++ b/src-tauri/crates/project-management/src/orchestrator/mod.rs @@ -3,10 +3,8 @@ //! NOT an LLM agent. This module coordinates SDE → Review → Follow-up pipelines //! by reading/writing work item frontmatter and launching coding agent sessions. -pub mod branch_health; pub mod commands; pub mod diff_stats; -pub mod follow_up; pub mod proof_of_work; pub mod state_machine; diff --git a/src-tauri/crates/project-management/src/orchestrator/state_machine.rs b/src-tauri/crates/project-management/src/orchestrator/state_machine.rs index 013706ddb5..bfad854197 100644 --- a/src-tauri/crates/project-management/src/orchestrator/state_machine.rs +++ b/src-tauri/crates/project-management/src/orchestrator/state_machine.rs @@ -8,7 +8,7 @@ use crate::projects::io; use crate::projects::types::{ AgentRole, LastFailure, LinkedSession, LinkedSessionStatus, LinkedSessionType, - OrchestratorConfig, OrchestratorPhase, OrchestratorState, ReviewOutcome, WorkItemFrontmatter, + OrchestratorConfig, OrchestratorPhase, OrchestratorState, WorkItemFrontmatter, }; use core_types::session::PENDING_SESSION_PLACEHOLDER; @@ -58,27 +58,14 @@ pub fn effective_config(frontmatter: &WorkItemFrontmatter) -> OrchestratorConfig /// Transition after agent session completes successfully. pub fn on_session_complete(frontmatter: &mut WorkItemFrontmatter) -> TransitionResult { - let config = effective_config(frontmatter); let state = frontmatter .orchestrator_state .get_or_insert_with(OrchestratorState::default); - if config.effective_review_config().is_some() { - state.current_phase = OrchestratorPhase::Review; - auto_transition_status(frontmatter, &OrchestratorPhase::Review); - add_linked_session( - frontmatter, - "pending", - AgentRole::Review, - LinkedSessionType::Native, - ); - TransitionResult::LaunchReview - } else { - state.current_phase = OrchestratorPhase::Completed; - state.active_config = None; - auto_transition_status(frontmatter, &OrchestratorPhase::Completed); - TransitionResult::Completed - } + state.current_phase = OrchestratorPhase::Completed; + state.active_config = None; + auto_transition_status(frontmatter, &OrchestratorPhase::Completed); + TransitionResult::Completed } /// Transition after agent session fails. @@ -108,76 +95,6 @@ pub fn on_session_failed( } } -/// Transition after review completes (agent or human). -/// -/// - `Approved` → Completed -/// - `ChangesRequested` → back to Coding (fix round) if under max_rounds, else AwaitingUser -/// - `Inconclusive` → AwaitingUser -pub fn on_review_complete( - frontmatter: &mut WorkItemFrontmatter, - outcome: &ReviewOutcome, -) -> TransitionResult { - let config = effective_config(frontmatter); - let review_config = config.effective_review_config(); - let max_rounds = review_config.as_ref().map_or(3, |rc| rc.max_rounds); - - let state = frontmatter - .orchestrator_state - .get_or_insert_with(OrchestratorState::default); - - match outcome { - ReviewOutcome::Approved => { - state.current_phase = OrchestratorPhase::Completed; - state.active_config = None; - auto_transition_status(frontmatter, &OrchestratorPhase::Completed); - TransitionResult::Completed - } - ReviewOutcome::ChangesRequested => { - if state.review_round < max_rounds { - state.review_round += 1; - state.current_phase = OrchestratorPhase::Coding; - auto_transition_status(frontmatter, &OrchestratorPhase::Coding); - add_linked_session( - frontmatter, - "pending", - AgentRole::Coding, - LinkedSessionType::Native, - ); - TransitionResult::LaunchFix - } else { - state.current_phase = OrchestratorPhase::AwaitingUser; - auto_transition_status(frontmatter, &OrchestratorPhase::AwaitingUser); - TransitionResult::AwaitingUser - } - } - ReviewOutcome::Inconclusive => { - state.current_phase = OrchestratorPhase::AwaitingUser; - auto_transition_status(frontmatter, &OrchestratorPhase::AwaitingUser); - TransitionResult::AwaitingUser - } - } -} - -/// Transition after review agent fails (e.g. LLM error, timeout). -pub fn on_review_failed( - frontmatter: &mut WorkItemFrontmatter, - session_id: &str, - reason: &str, -) -> TransitionResult { - let state = frontmatter - .orchestrator_state - .get_or_insert_with(OrchestratorState::default); - - state.last_failure = Some(LastFailure { - session_id: Some(session_id.to_string()), - reason: Some(reason.to_string()), - timestamp: Some(chrono::Utc::now().to_rfc3339()), - }); - state.current_phase = OrchestratorPhase::AwaitingUser; - - TransitionResult::AwaitingUser -} - /// Mark the workflow as interrupted (graceful shutdown). pub fn mark_interrupted(frontmatter: &mut WorkItemFrontmatter) { let state = frontmatter @@ -324,14 +241,9 @@ pub fn mutate_work_item( /// What action the orchestrator should take after a transition. #[derive(Debug, Clone, PartialEq)] pub enum TransitionResult { - LaunchReview, - /// Review gave ChangesRequested — re-launch owner agent with review feedback. - LaunchFix, RetryAgent, Completed, Failed, - CreateFollowUp, - AwaitingUser, /// Stale terminal signal from a session that no longer owns the /// item's execution claim — the mutation was skipped entirely. Ignored, diff --git a/src-tauri/crates/project-management/src/orchestrator/tests/state_machine_tests.rs b/src-tauri/crates/project-management/src/orchestrator/tests/state_machine_tests.rs index c9cb87140c..6c3066f5a0 100644 --- a/src-tauri/crates/project-management/src/orchestrator/tests/state_machine_tests.rs +++ b/src-tauri/crates/project-management/src/orchestrator/tests/state_machine_tests.rs @@ -118,7 +118,7 @@ fn on_session_complete_without_review_does_not_complete_work_item() { } #[test] -fn on_session_complete_with_review_launches_review() { +fn on_session_complete_ignores_review_config() { let mut fm = make_frontmatter(); fm.orchestrator_config = Some(OrchestratorConfig { review_enabled: true, @@ -126,11 +126,9 @@ fn on_session_complete_with_review_launches_review() { }); snapshot_config(&mut fm); let result = on_session_complete(&mut fm); - assert_eq!(result, TransitionResult::LaunchReview); + assert_eq!(result, TransitionResult::Completed); let state = fm.orchestrator_state.as_ref().unwrap(); - assert_eq!(state.current_phase, OrchestratorPhase::Review); - assert_eq!(fm.status, "in_review"); - assert!(!fm.linked_sessions.is_empty()); + assert_eq!(state.current_phase, OrchestratorPhase::Completed); } // ========== on_session_failed ========== @@ -177,98 +175,6 @@ fn on_session_failed_fails_immediately_without_retry() { assert_eq!(result, TransitionResult::Failed); } -// ========== on_review_complete ========== - -#[test] -fn on_review_complete_approved_does_not_complete_work_item() { - let mut fm = make_frontmatter(); - fm.orchestrator_config = Some(OrchestratorConfig { - review_enabled: true, - ..OrchestratorConfig::default() - }); - snapshot_config(&mut fm); - on_session_complete(&mut fm); - let result = on_review_complete(&mut fm, &ReviewOutcome::Approved); - assert_eq!(result, TransitionResult::Completed); - let state = fm.orchestrator_state.as_ref().unwrap(); - assert_eq!(state.current_phase, OrchestratorPhase::Completed); - assert_eq!(fm.status, "in_review"); -} - -#[test] -fn on_review_complete_changes_requested_launches_fix() { - let mut fm = make_frontmatter(); - fm.orchestrator_config = Some(OrchestratorConfig { - review_enabled: true, - ..OrchestratorConfig::default() - }); - snapshot_config(&mut fm); - on_session_complete(&mut fm); - let result = on_review_complete(&mut fm, &ReviewOutcome::ChangesRequested); - assert_eq!(result, TransitionResult::LaunchFix); - let state = fm.orchestrator_state.as_ref().unwrap(); - assert_eq!(state.current_phase, OrchestratorPhase::Coding); - assert_eq!(state.review_round, 1); - assert_eq!(fm.status, "in_progress"); -} - -#[test] -fn on_review_complete_changes_requested_awaits_user_at_max_rounds() { - let mut fm = make_frontmatter(); - fm.orchestrator_config = Some(OrchestratorConfig { - review_enabled: true, - review_config: Some(ReviewConfig { - max_rounds: 1, - ..ReviewConfig::default() - }), - ..OrchestratorConfig::default() - }); - snapshot_config(&mut fm); - on_session_complete(&mut fm); - // Round 0 < max_rounds(1): increments to 1, returns LaunchFix - let result1 = on_review_complete(&mut fm, &ReviewOutcome::ChangesRequested); - assert_eq!(result1, TransitionResult::LaunchFix); - // Fix completes, back to review - on_session_complete(&mut fm); - // Round 1 >= max_rounds(1): returns AwaitingUser - let result2 = on_review_complete(&mut fm, &ReviewOutcome::ChangesRequested); - assert_eq!(result2, TransitionResult::AwaitingUser); -} - -#[test] -fn on_review_complete_inconclusive_awaits_user() { - let mut fm = make_frontmatter(); - fm.orchestrator_config = Some(OrchestratorConfig { - review_enabled: true, - ..OrchestratorConfig::default() - }); - snapshot_config(&mut fm); - on_session_complete(&mut fm); - let result = on_review_complete(&mut fm, &ReviewOutcome::Inconclusive); - assert_eq!(result, TransitionResult::AwaitingUser); - let state = fm.orchestrator_state.as_ref().unwrap(); - assert_eq!(state.current_phase, OrchestratorPhase::AwaitingUser); - assert_eq!(fm.status, "in_review"); -} - -// ========== on_review_failed ========== - -#[test] -fn on_review_failed_goes_to_awaiting_user() { - let mut fm = make_frontmatter(); - fm.orchestrator_config = Some(OrchestratorConfig { - review_enabled: true, - ..OrchestratorConfig::default() - }); - snapshot_config(&mut fm); - on_session_complete(&mut fm); - let result = on_review_failed(&mut fm, "rev-1", "LLM timeout"); - assert_eq!(result, TransitionResult::AwaitingUser); - let state = fm.orchestrator_state.as_ref().unwrap(); - assert_eq!(state.current_phase, OrchestratorPhase::AwaitingUser); - assert!(state.last_failure.is_some()); -} - // ========== mark_interrupted ========== #[test] diff --git a/src-tauri/src/commands/handler_list.inc b/src-tauri/src/commands/handler_list.inc index 77dd9b52dd..1b5c8bc863 100644 --- a/src-tauri/src/commands/handler_list.inc +++ b/src-tauri/src/commands/handler_list.inc @@ -991,10 +991,6 @@ agent_core::specialization::policies::policies_set_scope, agent_core::specialization::external_import::external_import_detect, agent_core::specialization::external_import::external_import_apply, // Orchestrator commands (work item multi-agent lifecycle) -project_management::orchestrator::commands::orchestrator_cancel, -project_management::orchestrator::commands::orchestrator_retry, -project_management::orchestrator::commands::orchestrator_get_status, -project_management::orchestrator::commands::orchestrator_create_follow_up, project_management::orchestrator::commands::orchestrator_get_diff_stats, project_management::orchestrator::commands::orchestrator_set_pr, // MCP commands From 25e949b6dd3719918de6808c7c70d0be18e27966 Mon Sep 17 00:00:00 2001 From: Neonforge <48338160+Neonforge98@users.noreply.github.com> Date: Fri, 14 Aug 2026 17:18:31 -0700 Subject: [PATCH 2/7] refactor(work-items): drop review panel actions and orchestrator hook Pre-commit hook ran. Total eslint: 0, total circular: 0 --- .../CollapsibleSummary.tsx | 41 -- .../ReviewFeedbackPanel/IterationHistory.tsx | 119 ----- .../ReviewFeedbackPanel/SeverityPills.tsx | 45 -- .../ReviewFeedbackPanel/index.tsx | 215 -------- .../components/WorkItemContent/OutputTab.tsx | 27 - .../components/WorkItemContent/index.tsx | 8 - .../components/WorkItemContent/types.ts | 8 - .../WorkItemDetail/WorkItemDetailBody.tsx | 12 - .../components/WorkItemDetail/index.tsx | 22 +- .../ProjectManager/WorkItems/hooks/index.ts | 2 +- .../hooks/useWorkItemActiveSession.ts | 65 +++ .../hooks/useWorkItemOrchestrator/index.ts | 2 - .../useWorkItemOrchestrator/useAutoReview.ts | 169 ------ .../useStaleSessionDetection.ts | 240 --------- .../useWorkItemCollabLock.test.ts | 223 -------- .../useWorkItemCollabLock.ts | 427 --------------- .../useWorkItemOrchestrator.ts | 503 ------------------ .../workItemOrchestratorOwnership.test.ts | 152 ------ .../workItemOrchestratorOwnership.ts | 116 ---- 19 files changed, 68 insertions(+), 2328 deletions(-) delete mode 100644 src/modules/ProjectManager/WorkItems/components/AgentWorkflow/ReviewFeedbackPanel/CollapsibleSummary.tsx delete mode 100644 src/modules/ProjectManager/WorkItems/components/AgentWorkflow/ReviewFeedbackPanel/IterationHistory.tsx delete mode 100644 src/modules/ProjectManager/WorkItems/components/AgentWorkflow/ReviewFeedbackPanel/SeverityPills.tsx delete mode 100644 src/modules/ProjectManager/WorkItems/components/AgentWorkflow/ReviewFeedbackPanel/index.tsx create mode 100644 src/modules/ProjectManager/WorkItems/hooks/useWorkItemActiveSession.ts delete mode 100644 src/modules/ProjectManager/WorkItems/hooks/useWorkItemOrchestrator/index.ts delete mode 100644 src/modules/ProjectManager/WorkItems/hooks/useWorkItemOrchestrator/useAutoReview.ts delete mode 100644 src/modules/ProjectManager/WorkItems/hooks/useWorkItemOrchestrator/useStaleSessionDetection.ts delete mode 100644 src/modules/ProjectManager/WorkItems/hooks/useWorkItemOrchestrator/useWorkItemCollabLock.test.ts delete mode 100644 src/modules/ProjectManager/WorkItems/hooks/useWorkItemOrchestrator/useWorkItemCollabLock.ts delete mode 100644 src/modules/ProjectManager/WorkItems/hooks/useWorkItemOrchestrator/useWorkItemOrchestrator.ts delete mode 100644 src/modules/ProjectManager/WorkItems/hooks/useWorkItemOrchestrator/workItemOrchestratorOwnership.test.ts delete mode 100644 src/modules/ProjectManager/WorkItems/hooks/useWorkItemOrchestrator/workItemOrchestratorOwnership.ts diff --git a/src/modules/ProjectManager/WorkItems/components/AgentWorkflow/ReviewFeedbackPanel/CollapsibleSummary.tsx b/src/modules/ProjectManager/WorkItems/components/AgentWorkflow/ReviewFeedbackPanel/CollapsibleSummary.tsx deleted file mode 100644 index d0da7ee908..0000000000 --- a/src/modules/ProjectManager/WorkItems/components/AgentWorkflow/ReviewFeedbackPanel/CollapsibleSummary.tsx +++ /dev/null @@ -1,41 +0,0 @@ -import React, { useState } from "react"; -import { useTranslation } from "react-i18next"; - -import Button from "@src/components/Button"; - -interface CollapsibleSummaryProps { - content: string; -} - -const CollapsibleSummary: React.FC = ({ content }) => { - const { t } = useTranslation("projects"); - const [expanded, setExpanded] = useState(false); - const firstLine = content.split("\n")[0]; - const hasMore = content.includes("\n") || content.length > 150; - - return ( -
-
- {expanded ? ( -
{content}
- ) : ( - {firstLine} - )} -
- {hasMore && ( - - )} -
- ); -}; - -export default CollapsibleSummary; diff --git a/src/modules/ProjectManager/WorkItems/components/AgentWorkflow/ReviewFeedbackPanel/IterationHistory.tsx b/src/modules/ProjectManager/WorkItems/components/AgentWorkflow/ReviewFeedbackPanel/IterationHistory.tsx deleted file mode 100644 index 057aa61fc2..0000000000 --- a/src/modules/ProjectManager/WorkItems/components/AgentWorkflow/ReviewFeedbackPanel/IterationHistory.tsx +++ /dev/null @@ -1,119 +0,0 @@ -import { ChevronDown, ChevronRight, SquareArrowOutUpRight } from "lucide-react"; -import React, { useMemo, useState } from "react"; -import { useTranslation } from "react-i18next"; - -import type { ReviewFeedback } from "@src/api/http/project"; -import Button from "@src/components/Button"; -import { CommentRow } from "@src/components/CodeReviewBlocks"; - -interface IterationHistoryProps { - history: ReviewFeedback[]; - latestResolutions: { - round: number; - comment_index: number; - status: string; - }[]; - onOpenSession?: (sessionId: string) => void; - onOpenFileAtLine?: (filePath: string, line?: number) => void; -} - -const IterationHistory: React.FC = ({ - history, - latestResolutions, - onOpenSession, - onOpenFileAtLine, -}) => { - const { t } = useTranslation("projects"); - const [expandedRound, setExpandedRound] = useState(null); - - const resolutionMap = useMemo(() => { - const resMap = new Map(); - for (const resolution of latestResolutions) { - const key = `${resolution.round}-${resolution.comment_index}`; - resMap.set(key, resolution.status.toUpperCase()); - } - return resMap; - }, [latestResolutions]); - - return ( -
-
- {t("workItems.reviewFeedback.iterationHistory", { - count: history.length, - })} -
- {history.map((round, idx) => { - const roundNumber = idx + 1; - const isExpanded = expandedRound === roundNumber; - const roundComments = round.comments ?? []; - const issueCount = roundComments.filter( - (comment) => - comment.severity === "error" || comment.severity === "warning" - ).length; - const fixedCount = roundComments.filter((_comment, commentIdx) => - resolutionMap.has(`${roundNumber}-${commentIdx}`) - ).length; - - return ( -
- - - {isExpanded && ( -
-

- {t("workItems.reviewFeedback.archived")} -

- {(round.comments ?? []).map((comment, commentIdx) => { - const resKey = `${roundNumber}-${commentIdx}`; - const resStatus = resolutionMap.get(resKey); - return ( - - ); - })} -
- )} -
- ); - })} -
- ); -}; - -export default IterationHistory; diff --git a/src/modules/ProjectManager/WorkItems/components/AgentWorkflow/ReviewFeedbackPanel/SeverityPills.tsx b/src/modules/ProjectManager/WorkItems/components/AgentWorkflow/ReviewFeedbackPanel/SeverityPills.tsx deleted file mode 100644 index 234ec7322e..0000000000 --- a/src/modules/ProjectManager/WorkItems/components/AgentWorkflow/ReviewFeedbackPanel/SeverityPills.tsx +++ /dev/null @@ -1,45 +0,0 @@ -import React from "react"; - -import type { ReviewCommentSeverity } from "@src/api/http/project"; -import { SeverityIcon } from "@src/components/CodeReviewBlocks"; - -const SEVERITY_ORDER: ReviewCommentSeverity[] = [ - "error", - "warning", - "suggestion", - "praise", -]; - -const PILL_STYLES: Record = { - error: "bg-danger-6/15 text-danger-6", - warning: "bg-warning-6/15 text-warning-6", - suggestion: "bg-primary-6/15 text-primary-6", - praise: "bg-success-6/15 text-success-6", -}; - -interface SeverityPillsProps { - counts: Partial>; -} - -const SeverityPills: React.FC = ({ counts }) => { - const pills = SEVERITY_ORDER.filter( - (severity) => (counts[severity] ?? 0) > 0 - ); - if (pills.length === 0) return null; - - return ( -
- {pills.map((severity) => ( - - - {counts[severity]} - - ))} -
- ); -}; - -export default SeverityPills; diff --git a/src/modules/ProjectManager/WorkItems/components/AgentWorkflow/ReviewFeedbackPanel/index.tsx b/src/modules/ProjectManager/WorkItems/components/AgentWorkflow/ReviewFeedbackPanel/index.tsx deleted file mode 100644 index 490e38beb2..0000000000 --- a/src/modules/ProjectManager/WorkItems/components/AgentWorkflow/ReviewFeedbackPanel/index.tsx +++ /dev/null @@ -1,215 +0,0 @@ -import { - AlertTriangle, - CheckCircle2, - SquareArrowOutUpRight, -} from "lucide-react"; -import React, { useMemo } from "react"; -import { useTranslation } from "react-i18next"; - -import type { - OrchestratorPhase, - ReviewCommentSeverity, - ReviewFeedback, -} from "@src/api/http/project"; -import Button from "@src/components/Button"; -import { CommentRow } from "@src/components/CodeReviewBlocks"; - -import CollapsibleSummary from "./CollapsibleSummary"; -import IterationHistory from "./IterationHistory"; -import SeverityPills from "./SeverityPills"; - -interface ReviewFeedbackPanelProps { - latestReview?: ReviewFeedback; - reviewHistory?: ReviewFeedback[]; - phase: OrchestratorPhase; - compact?: boolean; - onOpenSession?: (sessionId: string) => void; - onOpenFileAtLine?: (filePath: string, line?: number) => void; - onRetry?: () => void; - onAcceptAsIs?: () => void; - onCreateFollowUp?: () => void; - onCancel?: () => void; -} - -const OUTCOME_STYLES = { - approved: { - headerIcon: , - headerText: "text-text-1", - }, - changes_requested: { - headerIcon: , - headerText: "text-text-1", - }, - inconclusive: { - headerIcon: , - headerText: "text-text-1", - }, -} as const; - -const SEVERITY_ORDER: ReviewCommentSeverity[] = [ - "error", - "warning", - "suggestion", - "praise", -]; - -const ReviewFeedbackPanel: React.FC = ({ - latestReview, - reviewHistory = [], - phase, - compact = false, - onOpenSession, - onOpenFileAtLine, - onRetry, - onAcceptAsIs, - onCreateFollowUp, - onCancel, -}) => { - const { t } = useTranslation("projects"); - - const severityCounts = useMemo(() => { - const counts: Partial> = {}; - for (const comment of latestReview?.comments ?? []) { - counts[comment.severity] = (counts[comment.severity] ?? 0) + 1; - } - return counts; - }, [latestReview?.comments]); - - const sortedComments = useMemo(() => { - const comments = latestReview?.comments ?? []; - if (comments.length === 0) return []; - return [...comments].sort((commentA, commentB) => { - const orderA = SEVERITY_ORDER.indexOf(commentA.severity); - const orderB = SEVERITY_ORDER.indexOf(commentB.severity); - return orderA - orderB; - }); - }, [latestReview?.comments]); - - if (!latestReview) return null; - - const outcome = latestReview.outcome ?? "inconclusive"; - const isDuringSdeRerun = phase === "sde" && reviewHistory.length > 0; - const isAwaitingUser = phase === "awaiting_user"; - - const headerLabel = isDuringSdeRerun - ? t("workItems.reviewFeedback.previousReview") - : outcome === "approved" - ? t("workItems.reviewFeedback.approved") - : outcome === "inconclusive" - ? t("workItems.reviewFeedback.inconclusive") - : t("workItems.reviewFeedback.changesRequested"); - - const style = - OUTCOME_STYLES[outcome as keyof typeof OUTCOME_STYLES] ?? - OUTCOME_STYLES.inconclusive; - - if (compact) { - return ( -
- {style.headerIcon} - - {headerLabel} - - -
- ); - } - - return ( -
-
-
- {style.headerIcon} - - {headerLabel} - - -
- {onOpenSession && latestReview.session_id && ( -
- - {latestReview.summary && ( -
- -
- )} - - {sortedComments.length > 0 && ( -
- {sortedComments.map((comment, idx) => ( - - ))} -
- )} - - {isDuringSdeRerun && ( -
- {t("workItems.reviewFeedback.sdeAddressing")} -
- )} - - {isAwaitingUser && ( -
-

- {t("workItems.agentWorkflow.whatNext")} -

-
- {onRetry && ( - - )} - {onAcceptAsIs && ( - - )} - {onCreateFollowUp && ( - - )} - {onCancel && ( - - )} -
-
- )} - - {reviewHistory.length > 0 && !isDuringSdeRerun && ( - - )} -
- ); -}; - -export default ReviewFeedbackPanel; diff --git a/src/modules/ProjectManager/WorkItems/components/WorkItemContent/OutputTab.tsx b/src/modules/ProjectManager/WorkItems/components/WorkItemContent/OutputTab.tsx index 57a0df2142..5b0d1173cf 100644 --- a/src/modules/ProjectManager/WorkItems/components/WorkItemContent/OutputTab.tsx +++ b/src/modules/ProjectManager/WorkItems/components/WorkItemContent/OutputTab.tsx @@ -8,7 +8,6 @@ import { } from "@src/api/http/project"; import { useProjectDataChanged } from "@src/hooks/project"; import ChangedFilesList from "@src/modules/ProjectManager/WorkItems/components/AgentWorkflow/ChangedFilesList"; -import ReviewFeedbackPanel from "@src/modules/ProjectManager/WorkItems/components/AgentWorkflow/ReviewFeedbackPanel"; import { CollapsibleSection } from "@src/modules/shared/layouts/blocks"; import PrSection from "./PrSection"; @@ -25,10 +24,6 @@ const OutputTab: React.FC = ({ onOpenFileAtLine, onReviewAllFiles, onOpenSession, - onRetry, - onAcceptAsIs, - onCreateFollowUp, - onCancel, onCreatePr, }) => { const { t } = useTranslation("projects"); @@ -135,7 +130,6 @@ const OutputTab: React.FC = ({ {hasChangedFiles ? ( = ({ )} - {proofOfWork?.review_feedback && ( - - - - )} - {(displayedUsage.costUsd > 0 || displayedUsage.totalTokens > 0) && ( = ({ githubIssueInteraction, orgId, onOpenSubItem, - onCancelAgent, - onRetry, - onAcceptAsIs, - onCreateFollowUp, onOpenSession, onOpenFileDiff, onOpenFileAtLine, @@ -809,10 +805,6 @@ const WorkItemContent: React.FC = ({ onOpenFileAtLine={onOpenFileAtLine} onReviewAllFiles={onReviewAllFiles} onOpenSession={onOpenSession} - onRetry={onRetry} - onAcceptAsIs={onAcceptAsIs} - onCreateFollowUp={onCreateFollowUp} - onCancel={onCancelAgent} onCreatePr={onCreatePr} /> ); diff --git a/src/modules/ProjectManager/WorkItems/components/WorkItemContent/types.ts b/src/modules/ProjectManager/WorkItems/components/WorkItemContent/types.ts index 06f195d919..70d84ce7ed 100644 --- a/src/modules/ProjectManager/WorkItems/components/WorkItemContent/types.ts +++ b/src/modules/ProjectManager/WorkItems/components/WorkItemContent/types.ts @@ -58,10 +58,6 @@ export interface WorkItemContentProps { }; /** Inline GitHub-native body, comment, and status actions for thread surfaces. */ githubIssueInteraction?: GitHubIssueInteractionConfig; - onCancelAgent?: () => void; - onRetry?: () => void; - onAcceptAsIs?: () => void; - onCreateFollowUp?: () => void; onOpenSession?: (sessionId: string, title?: string) => void; onOpenFileDiff?: (filePath: string) => void; onOpenFileAtLine?: (filePath: string, line?: number) => void; @@ -119,10 +115,6 @@ export interface OutputTabContentProps { onOpenFileAtLine?: (filePath: string, line?: number) => void; onReviewAllFiles?: (filePaths: string[]) => void; onOpenSession?: (sessionId: string, title?: string) => void; - onRetry?: () => void; - onAcceptAsIs?: () => void; - onCreateFollowUp?: () => void; - onCancel?: () => void; onCreatePr?: () => Promise<{ url?: string; error?: string }>; } diff --git a/src/modules/ProjectManager/WorkItems/components/WorkItemDetail/WorkItemDetailBody.tsx b/src/modules/ProjectManager/WorkItems/components/WorkItemDetail/WorkItemDetailBody.tsx index 7cf837fd7e..f6080e2389 100644 --- a/src/modules/ProjectManager/WorkItems/components/WorkItemDetail/WorkItemDetailBody.tsx +++ b/src/modules/ProjectManager/WorkItems/components/WorkItemDetail/WorkItemDetailBody.tsx @@ -44,10 +44,6 @@ interface WorkItemDetailBodyProps { onOpenSubItem?: (item: WorkItemDataPayload) => void; onUpdateWorkItem: (updates: Partial) => void; onUpdateWorkItemImmediate: (updates: Partial) => void; - onCancelAgent: () => void; - onRetry: (instructions?: string) => void; - onAcceptAsIs: () => void; - onCreateFollowUp: () => void; onOpenSession: (sessionId: string, title?: string) => void; onOpenFileDiff: (filePath: string) => void; onOpenFileAtLine: (filePath: string, line?: number) => void; @@ -77,10 +73,6 @@ export function WorkItemDetailBody({ onOpenSubItem, onUpdateWorkItem, onUpdateWorkItemImmediate, - onCancelAgent, - onRetry, - onAcceptAsIs, - onCreateFollowUp, onOpenSession, onOpenFileDiff, onOpenFileAtLine, @@ -128,10 +120,6 @@ export function WorkItemDetailBody({ projectSlug={projectSlug} orgId={orgId} shortId={shortId} - onCancelAgent={onCancelAgent} - onRetry={onRetry} - onAcceptAsIs={onAcceptAsIs} - onCreateFollowUp={onCreateFollowUp} onOpenSession={onOpenSession} onOpenFileDiff={onOpenFileDiff} onOpenFileAtLine={onOpenFileAtLine} diff --git a/src/modules/ProjectManager/WorkItems/components/WorkItemDetail/index.tsx b/src/modules/ProjectManager/WorkItems/components/WorkItemDetail/index.tsx index 2ebe41af4f..a68318e47c 100644 --- a/src/modules/ProjectManager/WorkItems/components/WorkItemDetail/index.tsx +++ b/src/modules/ProjectManager/WorkItems/components/WorkItemDetail/index.tsx @@ -21,7 +21,7 @@ import { } from "@src/types/core/workItem"; import { getContextMenuItems } from "../../config"; -import { useWorkItemOrchestrator } from "../../hooks/useWorkItemOrchestrator"; +import { useWorkItemActiveSession } from "../../hooks/useWorkItemActiveSession"; import { formatWorkItemShortId } from "../../workItemIdentity"; import WorkItemContextMenu from "../WorkItemContextMenu"; import { WorkItemDetailBody } from "./WorkItemDetailBody"; @@ -128,23 +128,9 @@ const WorkItemDetail: React.FC = ({ const { activeAgentSessionId, activeAgentRole, - handleRetry, - handleCancelAgent, - handleAcceptAsIs, - handleCreateFollowUp, worktreePath, projectRepoPath, - } = useWorkItemOrchestrator({ - workItem, - displayWorkItem, - repoPath, - projectSlug, - shortId, - onRefreshWorkItem, - onUpdateWorkItem, - hasPendingChanges, - handleSave, - }); + } = useWorkItemActiveSession(workItem, repoPath); const { handleOpenFileDiff, handleOpenFileAtLine, handleReviewAllFiles } = useWorkItemFileActions(repoPath); @@ -416,10 +402,6 @@ const WorkItemDetail: React.FC = ({ onOpenSubItem={handleOpenSubItem} onUpdateWorkItem={handleLocalUpdate} onUpdateWorkItemImmediate={handleImmediateUpdate} - onCancelAgent={handleCancelAgent} - onRetry={handleRetry} - onAcceptAsIs={handleAcceptAsIs} - onCreateFollowUp={handleCreateFollowUp} onOpenSession={handleOpenSessionWithContext} onOpenFileDiff={handleOpenFileDiff} onOpenFileAtLine={handleOpenFileAtLine} diff --git a/src/modules/ProjectManager/WorkItems/hooks/index.ts b/src/modules/ProjectManager/WorkItems/hooks/index.ts index 2b25a250c4..1f00e2eb97 100644 --- a/src/modules/ProjectManager/WorkItems/hooks/index.ts +++ b/src/modules/ProjectManager/WorkItems/hooks/index.ts @@ -10,7 +10,7 @@ export { useWorkItems } from "./useWorkItems"; // Agent session orchestration (SDE/Review agent lifecycle) -export { useWorkItemOrchestrator } from "./useWorkItemOrchestrator"; +export { useWorkItemActiveSession } from "./useWorkItemActiveSession"; // Work item CRUD actions export { default as useWorkItemActions } from "./useWorkItemActions"; diff --git a/src/modules/ProjectManager/WorkItems/hooks/useWorkItemActiveSession.ts b/src/modules/ProjectManager/WorkItems/hooks/useWorkItemActiveSession.ts new file mode 100644 index 0000000000..de0b8bb58b --- /dev/null +++ b/src/modules/ProjectManager/WorkItems/hooks/useWorkItemActiveSession.ts @@ -0,0 +1,65 @@ +import { useAtomValue } from "jotai"; +import { useMemo } from "react"; + +import { activeWorkspaceRootPathAtom } from "@src/store/workspace"; +import type { WorkItem as WorkItemExtended } from "@src/types/core/workItem"; + +import { AGENT_ROLE, type AgentRole, toAgentRole } from "../constants"; + +const RUNNING_LINKED_SESSION_STATUS = "running" as const; +const COMPLETED_WORK_ITEM_STATUS = "completed" as const; + +/** + * Read-only projection of the work item's active execution: which linked + * session (if any) is currently running, and the repo paths PR creation + * needs. Purely derived from persisted data — the execution lock and + * linked-session rows the backend maintains. + */ +export function useWorkItemActiveSession( + workItem: WorkItemExtended, + repoPath?: string | null +): { + activeAgentSessionId: string | null; + activeAgentRole: AgentRole | null; + worktreePath: string | null; + projectRepoPath: string | null; +} { + const worktreePath = useAtomValue(activeWorkspaceRootPathAtom) || null; + const projectRepoPath = repoPath ?? null; + + const { activeAgentSessionId, activeAgentRole } = useMemo(() => { + const runningLinkedSession = + workItem.linkedSessions?.find( + (session) => session.status === RUNNING_LINKED_SESSION_STATUS + ) ?? null; + const isCompletedWorkItem = + workItem.workItemStatus === COMPLETED_WORK_ITEM_STATUS || + workItem.status === COMPLETED_WORK_ITEM_STATUS; + const hasTerminalOnlyLinkedSessions = + (workItem.linkedSessions?.length ?? 0) > 0 && !runningLinkedSession; + const activeExecutionLockSessionId = + isCompletedWorkItem || hasTerminalOnlyLinkedSessions + ? null + : (workItem.executionLock?.activeSessionId ?? null); + const sessionId = + activeExecutionLockSessionId ?? runningLinkedSession?.session_id ?? null; + return { + activeAgentSessionId: sessionId, + activeAgentRole: sessionId + ? (toAgentRole(runningLinkedSession?.agent_role) ?? AGENT_ROLE.Sde) + : null, + }; + }, [ + workItem.executionLock?.activeSessionId, + workItem.linkedSessions, + workItem.status, + workItem.workItemStatus, + ]); + + return { + activeAgentSessionId, + activeAgentRole, + worktreePath, + projectRepoPath, + }; +} diff --git a/src/modules/ProjectManager/WorkItems/hooks/useWorkItemOrchestrator/index.ts b/src/modules/ProjectManager/WorkItems/hooks/useWorkItemOrchestrator/index.ts deleted file mode 100644 index 2858294738..0000000000 --- a/src/modules/ProjectManager/WorkItems/hooks/useWorkItemOrchestrator/index.ts +++ /dev/null @@ -1,2 +0,0 @@ -export { useWorkItemOrchestrator } from "./useWorkItemOrchestrator"; -export type { UseWorkItemOrchestratorOptions } from "./useWorkItemOrchestrator"; diff --git a/src/modules/ProjectManager/WorkItems/hooks/useWorkItemOrchestrator/useAutoReview.ts b/src/modules/ProjectManager/WorkItems/hooks/useWorkItemOrchestrator/useAutoReview.ts deleted file mode 100644 index 34ed5273b5..0000000000 --- a/src/modules/ProjectManager/WorkItems/hooks/useWorkItemOrchestrator/useAutoReview.ts +++ /dev/null @@ -1,169 +0,0 @@ -import { useEffect, useRef } from "react"; - -import Message from "@src/components/Message"; -import { SessionService } from "@src/engines/SessionCore/services/SessionService"; -import { createLogger } from "@src/hooks/logger"; -import type { WorkItem as WorkItemExtended } from "@src/types/core/workItem"; - -import { buildReviewTaskPrompt } from "../../components/WorkItemDetail/promptBuilder"; -import { - AGENT_ROLE, - ORCHESTRATOR_PHASE, - PENDING_SESSION_ID, - SESSION_STATUS, - formatOrchestratorError, -} from "../../constants"; -import type { AgentRole } from "../../constants"; - -const logger = createLogger("useAutoReview"); - -interface UseAutoReviewOptions { - enabled: boolean; - claimLaunch: () => boolean; - releaseLaunchClaim: () => void; - workItem: WorkItemExtended; - projectRepoPath: string | null; - accountId: string | null; - modelId?: string; - shortId: string | null; - projectSlug: string | null; - resolveSessionRepoPath: () => string; - onRefreshWorkItem?: () => void; - isStartingAgent: boolean; - setIsStartingAgent: (value: boolean) => void; - setActiveAgentSessionId: (value: string | null) => void; - setActiveAgentRole: (value: AgentRole | null) => void; -} - -/** - * Auto-launch review session when orchestrator transitions to "review" phase. - * - * IMPORTANT: `isStartingAgent` is tracked via ref so it does NOT appear in the - * effect dependency array — including it would cause the effect to re-fire - * during the SDE→review transition while the SDE session is still tearing down. - */ -export function useAutoReview(options: UseAutoReviewOptions): void { - const { - enabled, - claimLaunch, - releaseLaunchClaim, - workItem, - projectRepoPath, - accountId, - modelId, - shortId, - projectSlug, - resolveSessionRepoPath, - onRefreshWorkItem, - isStartingAgent, - setIsStartingAgent, - setActiveAgentSessionId, - setActiveAgentRole, - } = options; - - const reviewLaunchRef = useRef(false); - const isStartingAgentRef = useRef(isStartingAgent); - useEffect(() => { - isStartingAgentRef.current = isStartingAgent; - }, [isStartingAgent]); - - useEffect(() => { - const phase = workItem.orchestratorState?.current_phase; - if (!enabled) { - reviewLaunchRef.current = false; - return; - } - if (phase !== ORCHESTRATOR_PHASE.Review) { - reviewLaunchRef.current = false; - releaseLaunchClaim(); - return; - } - if (reviewLaunchRef.current || isStartingAgentRef.current) return; - - const hasActiveReview = workItem.linkedSessions?.some( - (ls) => - ls.agent_role === AGENT_ROLE.Review && - ls.status === SESSION_STATUS.Running && - ls.session_id !== PENDING_SESSION_ID - ); - if (hasActiveReview) return; - - // Only lock the ref AFTER validating params — if params are temporarily - // null (still loading), we leave the ref unlocked so the effect can - // retry on the next render when params become available. - if (!projectRepoPath || !accountId || !shortId) return; - - if (!claimLaunch()) return; - reviewLaunchRef.current = true; - - let cancelled = false; - const launchReview = async () => { - setIsStartingAgent(true); - try { - const reviewPrompt = buildReviewTaskPrompt( - workItem, - shortId, - workItem.proofOfWork?.branch ?? undefined, - undefined - ); - - const { sessionId: createdSessionId } = await SessionService.create({ - task: reviewPrompt, - repoPath: projectRepoPath, - projectRepoPath, - worktreePath: - resolveSessionRepoPath() !== projectRepoPath - ? resolveSessionRepoPath() - : undefined, - accountId, - model: modelId, - workItemId: shortId, - agentRole: AGENT_ROLE.Review, - mode: AGENT_ROLE.Review, - projectSlug: projectSlug ?? undefined, - }); - - if (!cancelled) { - setActiveAgentSessionId(createdSessionId); - setActiveAgentRole(AGENT_ROLE.Review); - logger.info( - `Started Review agent for ${shortId}, sessionId=${createdSessionId}` - ); - onRefreshWorkItem?.(); - } - } catch (error) { - const msg = formatOrchestratorError(error); - logger.error(`Failed to start review agent: ${msg}`); - if (!cancelled) { - Message.error(msg); - onRefreshWorkItem?.(); - } - } finally { - if (!cancelled) setIsStartingAgent(false); - } - }; - launchReview(); - return () => { - cancelled = true; - }; - // eslint-disable-next-line react-hooks/exhaustive-deps - }, [ - enabled, - claimLaunch, - releaseLaunchClaim, - workItem.orchestratorState?.current_phase, - workItem.linkedSessions, - workItem.name, - workItem.spec, - workItem.todos, - workItem.proofOfWork?.branch, - workItem.proofOfWork?.review_feedback, - workItem.proofOfWork?.review_history, - accountId, - modelId, - projectRepoPath, - shortId, - resolveSessionRepoPath, - onRefreshWorkItem, - ]); -} diff --git a/src/modules/ProjectManager/WorkItems/hooks/useWorkItemOrchestrator/useStaleSessionDetection.ts b/src/modules/ProjectManager/WorkItems/hooks/useWorkItemOrchestrator/useStaleSessionDetection.ts deleted file mode 100644 index 68cdc03b0c..0000000000 --- a/src/modules/ProjectManager/WorkItems/hooks/useWorkItemOrchestrator/useStaleSessionDetection.ts +++ /dev/null @@ -1,240 +0,0 @@ -import { useCallback, useEffect, useRef } from "react"; - -import { getSession } from "@src/api/tauri/agent"; -import { createLogger } from "@src/hooks/logger"; -import type { WorkItem as WorkItemExtended } from "@src/types/core/workItem"; -import { invokeTauri } from "@src/util/platform/tauri/init"; - -import { - ACTIVE_PHASES, - ORCHESTRATOR_COMMAND, - PENDING_SESSION_ID, - SESSION_STATUS, -} from "../../constants"; - -const logger = createLogger("useStaleSessionDetection"); - -const STALE_GRACE_PERIOD_MS = 5000; - -interface UseStaleSessionDetectionOptions { - enabled: boolean; - workItem: WorkItemExtended; - projectRepoPath: string | null; - projectSlug: string | null; - shortId: string | null; - isStartingAgent: boolean; - handleCancelAgent: () => Promise; - onRefreshWorkItem?: () => void; -} - -/** - * Two effects that detect stale orchestrator state: - * - * 1. On mount / session change: compare frontend phase with backend phase - * and refresh if they diverge. - * - * 2. When phase is sde/review: verify that linked sessions are still alive. - * If they are terminal or missing, wait a grace period then cancel. - */ -export function useStaleSessionDetection( - options: UseStaleSessionDetectionOptions -): void { - const { - enabled, - workItem, - projectRepoPath, - projectSlug, - shortId, - isStartingAgent, - handleCancelAgent, - onRefreshWorkItem, - } = options; - - const syncCheckDoneRef = useRef(null); - useEffect(() => { - if (!enabled) { - syncCheckDoneRef.current = null; - return; - } - if (!projectRepoPath || !projectSlug || !shortId) return; - if (syncCheckDoneRef.current === workItem.session_id) return; - syncCheckDoneRef.current = workItem.session_id; - - let cancelled = false; - const syncState = async () => { - try { - const status = await invokeTauri<{ - currentPhase: string; - retryCount: number; - interrupted: boolean; - hasActiveConfig: boolean; - }>(ORCHESTRATOR_COMMAND.GetStatus, { - projectSlug, - workItemId: shortId, - }); - - const frontendPhase = - workItem.orchestratorState?.current_phase ?? "idle"; - const backendPhase = status.currentPhase; - - if (frontendPhase !== backendPhase && !cancelled) { - logger.info( - `Orchestrator state mismatch: frontend=${frontendPhase}, backend=${backendPhase}. Refreshing.` - ); - onRefreshWorkItem?.(); - } - } catch { - // orchestrator_get_status may fail before the project store is initialized - } - }; - syncState(); - return () => { - cancelled = true; - }; - }, [ - enabled, - workItem.session_id, - workItem.orchestratorState?.current_phase, - projectRepoPath, - projectSlug, - shortId, - onRefreshWorkItem, - ]); - - const graceCheckAndCancel = useCallback( - async ( - phase: string, - cancelledRef: { current: boolean }, - reason: string - ): Promise => { - if (!projectRepoPath || !projectSlug || !shortId) { - await handleCancelAgent(); - return; - } - await new Promise((resolve) => - setTimeout(resolve, STALE_GRACE_PERIOD_MS) - ); - if (cancelledRef.current) return; - try { - const freshStatus = await invokeTauri<{ currentPhase: string }>( - ORCHESTRATOR_COMMAND.GetStatus, - { projectSlug, workItemId: shortId } - ); - if (cancelledRef.current) return; - if (freshStatus.currentPhase !== phase) { - onRefreshWorkItem?.(); - return; - } - } catch { - // Network or init error — still cancel after grace period - if (cancelledRef.current) return; - } - if (!cancelledRef.current) { - logger.info(`${reason}, cancelling orchestrator after grace period`); - await handleCancelAgent(); - } - }, - [ - projectRepoPath, - projectSlug, - shortId, - handleCancelAgent, - onRefreshWorkItem, - ] - ); - - // Key the stale check on phase + the serialized linked session IDs so that - // when sessions change (e.g. after a retry spawns a new session), we run - // a fresh stale check instead of being permanently locked out. - const linkedSessionKey = workItem.linkedSessions - ?.map((ls) => `${ls.session_id}:${ls.status}`) - .join(","); - - const staleCheckDoneRef = useRef(null); - useEffect(() => { - if (!enabled) { - staleCheckDoneRef.current = null; - return; - } - if (isStartingAgent) return; - - const phase = workItem.orchestratorState?.current_phase; - if (!phase || !ACTIVE_PHASES.has(phase)) { - staleCheckDoneRef.current = null; - return; - } - - const checkKey = `${phase}::${linkedSessionKey}`; - if (staleCheckDoneRef.current === checkKey) return; - staleCheckDoneRef.current = checkKey; - - const runningLinked = workItem.linkedSessions?.filter( - (ls) => - ls.status === SESSION_STATUS.Running && - ls.session_id !== PENDING_SESSION_ID && - ls.session_id.length > 0 - ); - if (!runningLinked || runningLinked.length === 0) { - const hasPendingPlaceholders = workItem.linkedSessions?.some( - (ls) => ls.session_id === PENDING_SESSION_ID - ); - if (hasPendingPlaceholders) { - staleCheckDoneRef.current = null; - return; - } - - const cancelledRef = { current: false }; - graceCheckAndCancel(phase, cancelledRef, "No running sessions"); - return () => { - cancelledRef.current = true; - }; - } - - const cancelledRef = { current: false }; - const checkSessions = async () => { - for (const ls of runningLinked) { - if (cancelledRef.current) return; - try { - const session = (await getSession(ls.session_id)) as unknown as { - session_id: string; - status: string; - } | null; - const status = session?.status; - if ( - !session || - status === SESSION_STATUS.Completed || - status === SESSION_STATUS.Failed || - status === SESSION_STATUS.Cancelled - ) { - await graceCheckAndCancel( - phase, - cancelledRef, - `Stale session ${ls.session_id} (status=${status ?? "missing"})` - ); - return; - } - } catch { - if (!cancelledRef.current) { - logger.info( - `Session ${ls.session_id} not found, cancelling orchestrator` - ); - await handleCancelAgent(); - } - return; - } - } - }; - checkSessions(); - return () => { - cancelledRef.current = true; - }; - }, [ - enabled, - workItem.orchestratorState?.current_phase, - workItem.linkedSessions, - linkedSessionKey, - handleCancelAgent, - isStartingAgent, - graceCheckAndCancel, - ]); -} diff --git a/src/modules/ProjectManager/WorkItems/hooks/useWorkItemOrchestrator/useWorkItemCollabLock.test.ts b/src/modules/ProjectManager/WorkItems/hooks/useWorkItemOrchestrator/useWorkItemCollabLock.test.ts deleted file mode 100644 index 2d892c6917..0000000000 --- a/src/modules/ProjectManager/WorkItems/hooks/useWorkItemOrchestrator/useWorkItemCollabLock.test.ts +++ /dev/null @@ -1,223 +0,0 @@ -/** - * Regression coverage for the cloud-alias resolution of - * `useWorkItemCollabLock` (cloud-parity Phase B). - * - * The bug this pins down: the hook used to probe - * `resolveCloudOrgForProjectOrg` exactly once per project. That resolver - * gates on membership in `org2CloudOrgsAtom`, which is in-memory only and - * populated ASYNCHRONOUSLY after sign-in / app start — so a probe that ran - * before `listMyOrgs` landed cached `cloudOrgId: null` forever, and a - * genuinely cloud-synced work item silently lost server lock arbitration - * (two members could double-start it). `watchCollabOrgResolution` — the - * framework-free core the hook's effect binds to (the node vitest env has - * no hook-render harness) — must re-probe on every atom change. - */ -import { afterEach, beforeEach, describe, expect, it, vi } from "vitest"; - -import { projectApi } from "@src/api/http/project"; -import type { ProjectData } from "@src/api/http/project"; -import { org2CloudOrgsAtom } from "@src/features/Org2Cloud/org2CloudOrgsAtom"; -import { - createInstrumentedStore, - getInstrumentedStore, - isStoreInitialized, -} from "@src/util/core/state/instrumentedStore"; - -import type { ResolvedCollabOrgResolution } from "./useWorkItemCollabLock"; -import { - getSharedCollabResolutionWatchCount, - resolveLockHolder, - watchCollabOrgResolution, -} from "./useWorkItemCollabLock"; - -vi.mock("@src/api/http/project", () => ({ - projectApi: { - readProject: vi.fn(), - readOrgs: vi.fn(), - }, -})); - -// Keep the import graph fetch-free: the lock RPC helpers are exercised by -// their own suite (cloudWorkItemLock tests). -vi.mock("@src/features/Org2Cloud/cloudWorkItemLock", () => ({ - acquireCloudWorkItemLock: vi.fn(), - releaseCloudWorkItemLock: vi.fn(), -})); - -const projectApiMock = vi.mocked(projectApi); - -const PROJECT: ProjectData = { - meta: { org_id: "porg-1" }, -} as ProjectData; - -/** The durable cloud alias row `resolveCloudOrgForProjectOrg` reads. */ -const CLOUD_ALIAS = { - id: "porg-1", - name: "Cloud Team", - slug: "cloud-team", - org_key: "cloud-team", - source: "local", - sync_provider: "orgii_collab", - external_org_id: "corg-1", - created_at: "2026-07-01T00:00:00.000Z", - updated_at: "2026-07-01T00:00:00.000Z", -}; - -/** Drain the watcher's probe (two awaits ⇒ a couple of macrotask turns). */ -async function settle(): Promise { - for (let i = 0; i < 4; i += 1) { - await new Promise((resolve) => setTimeout(resolve, 0)); - } -} - -beforeEach(() => { - if (!isStoreInitialized()) createInstrumentedStore(); - getInstrumentedStore().set(org2CloudOrgsAtom, []); - projectApiMock.readProject.mockResolvedValue(PROJECT); - projectApiMock.readOrgs.mockResolvedValue([CLOUD_ALIAS]); -}); - -afterEach(() => { - getInstrumentedStore().set(org2CloudOrgsAtom, []); - vi.clearAllMocks(); -}); - -describe("resolveLockHolder", () => { - it("returns no holder when the lock is free", () => { - expect(resolveLockHolder(null, "me")).toEqual({ - heldByOther: false, - holderName: null, - }); - }); - - it("prefers the roster display name for a teammate holder", () => { - expect(resolveLockHolder("user-2", "user-1", "Ada Lovelace")).toEqual({ - heldByOther: true, - holderName: "Ada Lovelace", - }); - }); - - it("falls back to the raw id when the member left the roster", () => { - expect(resolveLockHolder("user-2", "user-1", null)).toEqual({ - heldByOther: true, - holderName: "user-2", - }); - }); - - it("treats our own lock as not held by other", () => { - expect(resolveLockHolder("user-1", "user-1", "Me").heldByOther).toBe(false); - }); -}); - -describe("watchCollabOrgResolution", () => { - it("shares one project probe across concurrent detail surfaces", async () => { - const first: ResolvedCollabOrgResolution[] = []; - const second: ResolvedCollabOrgResolution[] = []; - const releaseFirst = watchCollabOrgResolution("proj-shared", (resolution) => - first.push(resolution) - ); - const releaseSecond = watchCollabOrgResolution( - "proj-shared", - (resolution) => second.push(resolution) - ); - - await settle(); - expect(projectApiMock.readProject).toHaveBeenCalledOnce(); - expect(first.at(-1)).toEqual(second.at(-1)); - expect(getSharedCollabResolutionWatchCount()).toBe(1); - - releaseFirst(); - expect(getSharedCollabResolutionWatchCount()).toBe(1); - releaseSecond(); - expect(getSharedCollabResolutionWatchCount()).toBe(0); - }); - - it("re-resolves the cloud alias when org2CloudOrgsAtom hydrates LATE", async () => { - const resolutions: ResolvedCollabOrgResolution[] = []; - const unwatch = watchCollabOrgResolution("proj-1", (resolution) => - resolutions.push(resolution) - ); - - // App-start race: the local IPC reads win against the in-flight - // listMyOrgs fetch, so the first probe sees an EMPTY cloud orgs atom. - await settle(); - expect(resolutions.at(-1)).toEqual({ - status: "resolved", - projectOrgId: "porg-1", - cloudOrgId: null, - }); - - // Late hydration lands. Pre-fix this changed nothing (the null was - // frozen for the lifetime of the view); now the watcher re-probes and - // the SAME subscriber receives the corrected resolution. - getInstrumentedStore().set(org2CloudOrgsAtom, [ - { orgId: "corg-1", name: "Cloud Team", role: "member" }, - ]); - await settle(); - expect(resolutions.at(-1)).toEqual({ - status: "resolved", - projectOrgId: "porg-1", - cloudOrgId: "corg-1", - }); - - unwatch(); - }); - - it("drops the alias again when the atom clears (sign-out)", async () => { - getInstrumentedStore().set(org2CloudOrgsAtom, [ - { orgId: "corg-1", name: "Cloud Team", role: "member" }, - ]); - const resolutions: ResolvedCollabOrgResolution[] = []; - const unwatch = watchCollabOrgResolution("proj-1", (resolution) => - resolutions.push(resolution) - ); - await settle(); - expect(resolutions.at(-1)?.cloudOrgId).toBe("corg-1"); - - getInstrumentedStore().set(org2CloudOrgsAtom, []); - await settle(); - // Signed out ⇒ the missing-credential residual: proceed-locally. - expect(resolutions.at(-1)?.cloudOrgId).toBeNull(); - unwatch(); - }); - - it("keeps the caller's state on probe failure, then recovers on the next atom change", async () => { - projectApiMock.readProject.mockRejectedValueOnce(new Error("ipc down")); - const resolutions: ResolvedCollabOrgResolution[] = []; - const unwatch = watchCollabOrgResolution("proj-1", (resolution) => - resolutions.push(resolution) - ); - await settle(); - // No emission: the hook stays "unresolved" and acquireLock's inline - // retry / CollabMembershipUnresolvedError discipline blocks the start. - expect(resolutions).toEqual([]); - - // Hydration triggers a re-probe with the read healthy again. - getInstrumentedStore().set(org2CloudOrgsAtom, [ - { orgId: "corg-1", name: "Cloud Team", role: "member" }, - ]); - await settle(); - expect(resolutions.at(-1)).toEqual({ - status: "resolved", - projectOrgId: "porg-1", - cloudOrgId: "corg-1", - }); - unwatch(); - }); - - it("stops emitting after unsubscribe", async () => { - const resolutions: ResolvedCollabOrgResolution[] = []; - const unwatch = watchCollabOrgResolution("proj-1", (resolution) => - resolutions.push(resolution) - ); - await settle(); - const emitted = resolutions.length; - - unwatch(); - getInstrumentedStore().set(org2CloudOrgsAtom, [ - { orgId: "corg-1", name: "Cloud Team", role: "member" }, - ]); - await settle(); - expect(resolutions.length).toBe(emitted); - }); -}); diff --git a/src/modules/ProjectManager/WorkItems/hooks/useWorkItemOrchestrator/useWorkItemCollabLock.ts b/src/modules/ProjectManager/WorkItems/hooks/useWorkItemOrchestrator/useWorkItemCollabLock.ts deleted file mode 100644 index c76679ebaf..0000000000 --- a/src/modules/ProjectManager/WorkItems/hooks/useWorkItemOrchestrator/useWorkItemCollabLock.ts +++ /dev/null @@ -1,427 +0,0 @@ -/** - * Cloud execution-lock awareness for the work item orchestrator - * (design §16.6 / §16.9, cloud-parity Phase B/E). - * - * A shared work item is a native local row under a CLOUD-aliased project org - * (`external_org_id` alias). This hook resolves whether the CURRENT work - * item belongs to such an org and, if so, who holds the server-arbitrated - * execution lock so the "start agent" affordance can disable instead of - * double-starting. - * - * The lock itself is arbitrated by the server RPCs (cloudWorkItemLock.ts); - * the holder (`executionLock.lockedByMemberId` — a cloud userId) syncs down - * inside the work-item payload, so read paths stay purely local. - */ -import { useAtomValue } from "jotai"; -import { useEffect, useMemo, useState } from "react"; - -import { projectApi } from "@src/api/http/project"; -import type { WorkItemExecutionLock } from "@src/api/http/project"; -import { - acquireCloudWorkItemLock, - releaseCloudWorkItemLock, -} from "@src/features/Org2Cloud/cloudWorkItemLock"; -import { - org2CloudAuthAtom, - org2CloudAuthIdentityKey, -} from "@src/features/Org2Cloud/org2CloudAuthAtom"; -import { - ensureCloudMemberNames, - org2CloudMemberNamesAtom, - resolveCloudMemberName, -} from "@src/features/Org2Cloud/org2CloudMemberNamesAtom"; -import { - org2CloudOrgsAtom, - org2CloudRosterVersionAtom, -} from "@src/features/Org2Cloud/org2CloudOrgsAtom"; -import { - isCloudOrgMembershipPending, - resolveCloudOrgForProjectOrg, -} from "@src/features/Org2Cloud/org2CloudProjectOrgAlias"; -import { createLogger } from "@src/hooks/logger"; -import { getInstrumentedStore } from "@src/util/core/state/instrumentedStore"; - -const logger = createLogger("useWorkItemCollabLock"); - -/** - * Raised by `acquireLock` when the work item's collab membership could not - * be resolved (readProject failed both in the resolve effect and in the - * acquire-time retry). The caller must NOT start an agent on it: proceeding - * would run WITHOUT server arbitration on a work item that may well be - * cloud-synced, silently double-starting against a teammate. - */ -export class CollabMembershipUnresolvedError extends Error { - constructor(projectSlug: string) { - super(`collab membership unresolved for project ${projectSlug}`); - this.name = "CollabMembershipUnresolvedError"; - } -} - -export function isCollabMembershipUnresolvedError( - error: unknown -): error is CollabMembershipUnresolvedError { - return error instanceof CollabMembershipUnresolvedError; -} - -/** - * Result of resolving the work item's owning project org. "unresolved" is a - * first-class state (readProject pending or failed): it must never be - * conflated with "resolved: not collab", or a transient read failure lets an - * agent start without server arbitration. - */ -export interface ResolvedCollabOrgResolution { - status: "resolved"; - projectOrgId: string | null; - /** Cloud org id when the project org is aliased to a managed-cloud org. */ - cloudOrgId: string | null; -} - -type CollabOrgResolution = - | { status: "unresolved" } - | ResolvedCollabOrgResolution; - -const UNRESOLVED: CollabOrgResolution = { status: "unresolved" }; - -/** - * Resolve a project's owning org + cloud alias and KEEP the answer fresh - * against `org2CloudOrgsAtom`. That atom is in-memory only and populated - * asynchronously after sign-in / app start (`useOrg2CloudOrgs`), so a probe - * that runs before `listMyOrgs` lands sees an empty atom and reports - * `cloudOrgId: null` for a genuinely cloud-aliased org. Freezing that answer - * would silently skip cloud lock arbitration for the lifetime of the mounted - * view — so every change to the atom re-runs the probe and pushes a fresh - * resolution. - * - * `onResolution` fires only with RESOLVED states; a failed probe keeps the - * caller's current state so `acquireLock`'s unresolved-blocking discipline - * holds. Concurrent triggers coalesce into one trailing re-probe. Returns an - * unsubscribe function. Exported for the node vitest env (the repo has no - * hook-render harness) — the hook's effect is a thin binding over this. - */ -function startCollabOrgResolutionWatch( - projectSlug: string, - onResolution: (resolution: ResolvedCollabOrgResolution) => void -): () => void { - let stopped = false; - let probing = false; - let reprobeQueued = false; - - const probe = async (): Promise => { - if (probing) { - reprobeQueued = true; - return; - } - probing = true; - try { - const project = await projectApi.readProject(projectSlug); - // Managed-cloud alias probe (cloud-parity Phase B): a failure here - // keeps the current state exactly like a readProject failure — we - // must never degrade to "resolved: not collab" on an error. - const cloudOrgId = await resolveCloudOrgForProjectOrg( - project.meta.org_id - ); - // A null cloudOrgId during the cloud-orgs roster's first-load window is - // indistinguishable from a genuinely non-cloud org. Emitting - // "resolved: cloudOrgId null" here would let acquireLock start WITHOUT - // server arbitration on a possibly cloud-synced item (the empty-atom - // app-start / flaky-network race). Treat that pending state like a probe - // failure — keep the caller "unresolved"; the atom-change re-probe - // corrects it once the roster lands. - if ( - cloudOrgId === null && - (await isCloudOrgMembershipPending(project.meta.org_id)) - ) { - return; - } - if (!stopped) { - onResolution({ - status: "resolved", - projectOrgId: project.meta.org_id, - cloudOrgId, - }); - } - } catch (error) { - logger.warn("failed to resolve project org for collab lock", error); - // Keep the caller's state: acquireLock retries inline and blocks when - // it cannot prove the work item is not collab-synced. - } finally { - probing = false; - if (reprobeQueued && !stopped) { - reprobeQueued = false; - void probe(); - } - } - }; - - const unsubscribe = getInstrumentedStore().sub(org2CloudOrgsAtom, () => { - void probe(); - }); - void probe(); - - return () => { - stopped = true; - unsubscribe(); - }; -} - -interface SharedCollabResolutionWatch { - listeners: Set<(resolution: ResolvedCollabOrgResolution) => void>; - lastResolution: ResolvedCollabOrgResolution | null; - stop: () => void; -} - -const sharedCollabResolutionWatches = new Map< - string, - SharedCollabResolutionWatch ->(); - -/** - * Multicast one project-org probe across every rendered presentation of the - * same work item. Entries exist only while at least one consumer is mounted, - * so repeated tab open/close cycles cannot retain project state. - */ -export function watchCollabOrgResolution( - projectSlug: string, - onResolution: (resolution: ResolvedCollabOrgResolution) => void -): () => void { - let shared = sharedCollabResolutionWatches.get(projectSlug); - if (!shared) { - shared = { - listeners: new Set(), - lastResolution: null, - stop: () => undefined, - }; - sharedCollabResolutionWatches.set(projectSlug, shared); - const ownedShared = shared; - shared.stop = startCollabOrgResolutionWatch(projectSlug, (resolution) => { - ownedShared.lastResolution = resolution; - for (const listener of ownedShared.listeners) listener(resolution); - }); - } - - shared.listeners.add(onResolution); - if (shared.lastResolution) { - const currentResolution = shared.lastResolution; - queueMicrotask(() => { - if (shared?.listeners.has(onResolution)) onResolution(currentResolution); - }); - } - - let released = false; - return () => { - if (released) return; - released = true; - const current = sharedCollabResolutionWatches.get(projectSlug); - if (!current) return; - current.listeners.delete(onResolution); - if (current.listeners.size === 0) { - current.stop(); - sharedCollabResolutionWatches.delete(projectSlug); - } - }; -} - -export function getSharedCollabResolutionWatchCount(): number { - return sharedCollabResolutionWatches.size; -} - -interface LockHolderDisplay { - heldByOther: boolean; - holderName: string | null; -} - -/** - * Cloud lock holders are cloud userIds (our own id comes from the auth - * atom); teammate display names resolve through the cached org member - * roster, falling back to the raw id only when the member is unknown - * (e.g. left the org). - */ -export function resolveLockHolder( - lockedByMemberId: string | undefined | null, - currentMemberId: string | undefined | null, - displayName: string | null = null -): LockHolderDisplay { - if (!lockedByMemberId) { - return { heldByOther: false, holderName: null }; - } - return { - heldByOther: lockedByMemberId !== currentMemberId, - holderName: displayName ?? lockedByMemberId, - }; -} - -export interface UseWorkItemCollabLockOptions { - projectSlug?: string | null; - shortId?: string | null; - /** Global work item id (== `orgii_work_items.id`), used as the lock key. */ - workItemId?: string | null; - executionLock?: WorkItemExecutionLock | null; -} - -export interface WorkItemCollabLock { - /** True when the lock is held by a different org member than us. */ - isLockedByOther: boolean; - /** Display name of the other holder (falls back to the raw id). */ - lockHolderName: string | null; - /** Whether the work item is under a cloud-synced org at all. */ - isCollabWorkItem: boolean; - /** - * Acquire the server lock before starting. Resolves `false` for non-cloud - * work items (proceed locally). Rejects with `ORG2_CONFLICT` when a - * teammate holds the lock, and with `CollabMembershipUnresolvedError` when - * collab membership cannot be resolved (the caller must block the start — - * see the error's doc). - */ - acquireLock: () => Promise; - /** Release the server lock when the session terminates (best-effort). */ - releaseLock: () => Promise; -} - -export function useWorkItemCollabLock( - options: UseWorkItemCollabLockOptions -): WorkItemCollabLock { - const { projectSlug, shortId, workItemId, executionLock } = options; - const cloudAuth = useAtomValue(org2CloudAuthAtom); - const [resolution, setResolution] = useState(UNRESOLVED); - - // Resolve the project's org id per project — and keep it fresh: the - // watcher re-probes whenever `org2CloudOrgsAtom` changes, so a resolution - // computed against the not-yet-hydrated (or since-refetched) cloud orgs - // atom is corrected instead of frozen for the lifetime of the view (a - // frozen `cloudOrgId: null` would silently skip cloud lock arbitration). - // Only sets state after an await / through a microtask, so the effect - // never mutates state synchronously (react-hooks/set-state-in-effect). A - // readProject FAILURE keeps the "unresolved" state — it must not degrade - // to "resolved: not collab", or acquireLock would skip server arbitration - // on a transient error; acquireLock retries the read instead. - useEffect(() => { - let cancelled = false; - if (!projectSlug) { - // No project ⇒ standalone work item ⇒ provably not cloud-synced. - queueMicrotask(() => { - if (!cancelled) { - setResolution({ - status: "resolved", - projectOrgId: null, - cloudOrgId: null, - }); - } - }); - return () => { - cancelled = true; - }; - } - queueMicrotask(() => { - if (!cancelled) setResolution(UNRESOLVED); - }); - const unwatch = watchCollabOrgResolution(projectSlug, (resolved) => { - if (!cancelled) setResolution(resolved); - }); - return () => { - cancelled = true; - unwatch(); - }; - }, [projectSlug]); - - const cloudOrgId = - resolution.status === "resolved" ? resolution.cloudOrgId : null; - - const localMemberId = cloudOrgId ? cloudAuth?.userId : undefined; - - const memberNames = useAtomValue(org2CloudMemberNamesAtom); - const rosterVersionByOrg = useAtomValue(org2CloudRosterVersionAtom); - const rosterVersion = cloudOrgId ? (rosterVersionByOrg[cloudOrgId] ?? 0) : 0; - const lockedByMemberId = executionLock?.lockedByMemberId; - - useEffect(() => { - if (!cloudOrgId || !lockedByMemberId) return; - if (lockedByMemberId === localMemberId) return; - void ensureCloudMemberNames(cloudOrgId); - }, [cloudOrgId, lockedByMemberId, localMemberId, rosterVersion]); - - const holderDisplayName = - cloudOrgId && lockedByMemberId - ? resolveCloudMemberName( - memberNames, - cloudOrgId, - lockedByMemberId, - cloudAuth ? org2CloudAuthIdentityKey(cloudAuth) : undefined - ) - : null; - - const holder = useMemo( - () => resolveLockHolder(lockedByMemberId, localMemberId, holderDisplayName), - [lockedByMemberId, localMemberId, holderDisplayName] - ); - - const acquireLock = useMemo( - () => async (): Promise => { - if (!projectSlug || !workItemId) return false; - if (resolution.status !== "resolved") { - // The resolve effect failed (or has not finished): retry inline. If - // the project org STILL cannot be read we must block the start — - // only a provably-not-cloud work item may run without arbitration. - // The cloud-alias probe is part of the same resolution (its failure - // equally blocks the start). - try { - const project = await projectApi.readProject(projectSlug); - const effectiveCloudOrgId = await resolveCloudOrgForProjectOrg( - project.meta.org_id - ); - if ( - effectiveCloudOrgId === null && - (await isCloudOrgMembershipPending(project.meta.org_id)) - ) { - // Signed in with the cloud-orgs roster still loading: we cannot - // prove this work item is NOT cloud-synced, so block rather than - // start without server arbitration (same discipline as a - // readProject failure). The roster lands within ~1s of app start. - throw new CollabMembershipUnresolvedError(projectSlug); - } - setResolution({ - status: "resolved", - projectOrgId: project.meta.org_id, - cloudOrgId: effectiveCloudOrgId, - }); - } catch (error) { - if (isCollabMembershipUnresolvedError(error)) throw error; - logger.warn( - "collab membership still unresolved at acquire time", - error - ); - throw new CollabMembershipUnresolvedError(projectSlug); - } - } - // acquireCloudWorkItemLock resolves the cloud alias itself and returns - // false for non-cloud work items (proceed without a server lock). - return acquireCloudWorkItemLock(projectSlug, workItemId, { - activeShortId: shortId ?? undefined, - }); - }, - [projectSlug, resolution, workItemId, shortId] - ); - - const releaseLock = useMemo( - () => async (): Promise => { - // Best-effort by design: a non-cloud work item is a no-op, and any - // failure is swallowed (the row still syncs, and the server-side lock - // is idempotently overwritten by the next acquirer). - if (!projectSlug || !workItemId) return; - try { - await releaseCloudWorkItemLock(projectSlug, workItemId); - } catch { - // Offline / already released / signed out: the payload sync path - // still reconciles the lock; nothing actionable to surface here. - } - }, - [projectSlug, workItemId] - ); - - return { - isLockedByOther: holder.heldByOther, - lockHolderName: holder.holderName, - isCollabWorkItem: Boolean(cloudOrgId), - acquireLock, - releaseLock, - }; -} diff --git a/src/modules/ProjectManager/WorkItems/hooks/useWorkItemOrchestrator/useWorkItemOrchestrator.ts b/src/modules/ProjectManager/WorkItems/hooks/useWorkItemOrchestrator/useWorkItemOrchestrator.ts deleted file mode 100644 index d1f5f93c13..0000000000 --- a/src/modules/ProjectManager/WorkItems/hooks/useWorkItemOrchestrator/useWorkItemOrchestrator.ts +++ /dev/null @@ -1,503 +0,0 @@ -import { useAtomValue, useStore } from "jotai"; -import { useCallback, useEffect, useMemo, useRef, useState } from "react"; -import { useTranslation } from "react-i18next"; - -import Message from "@src/components/Message"; -import { SessionService } from "@src/engines/SessionCore/services/SessionService"; -import type { AgentExecMode } from "@src/features/SessionCreator/config"; -import { isCollabConflictError } from "@src/features/TeamCollaboration/engine/collabSyncEngineHelpers"; -import { createLogger } from "@src/hooks/logger"; -import { activeWorkspaceRootPathAtom } from "@src/store/workspace"; -import type { WorkItem as WorkItemExtended } from "@src/types/core/workItem"; -import { invokeTauri } from "@src/util/platform/tauri/init"; - -import { buildSdeTaskPrompt } from "../../components/WorkItemDetail/promptBuilder"; -import { - AGENT_ROLE, - ORCHESTRATOR_COMMAND, - TERMINAL_PHASES, - formatOrchestratorError, - toAgentRole, -} from "../../constants"; -import type { AgentRole, OrchestratorPhase } from "../../constants"; -import { useAutoReview } from "./useAutoReview"; -import { useStaleSessionDetection } from "./useStaleSessionDetection"; -import { - isCollabMembershipUnresolvedError, - useWorkItemCollabLock, -} from "./useWorkItemCollabLock"; -import { - claimWorkItemOrchestratorAction, - releaseWorkItemOrchestratorAction, - useWorkItemOrchestratorOwnership, -} from "./workItemOrchestratorOwnership"; - -const logger = createLogger("useWorkItemOrchestrator"); -const RUNNING_LINKED_SESSION_STATUS = "running" as const; -const COMPLETED_WORK_ITEM_STATUS = "completed" as const; - -const VALID_EXEC_MODES = new Set([ - "build", - "ask", - "plan", - "debug", - "review", -]); - -function normalizeExecMode(raw: string | undefined): AgentExecMode | undefined { - if (!raw) return undefined; - if (raw === "explore") return "ask"; - if (VALID_EXEC_MODES.has(raw)) return raw as AgentExecMode; - return undefined; -} - -export interface UseWorkItemOrchestratorOptions { - workItem: WorkItemExtended; - /** Effective work item with pending edits overlaid */ - displayWorkItem: WorkItemExtended; - repoPath?: string | null; - projectSlug?: string | null; - shortId?: string | null; - onRefreshWorkItem?: () => void; - onUpdateWorkItem?: (updates: Partial) => void; - /** When true, save pending edits before starting an agent */ - hasPendingChanges: boolean; - handleSave: () => Promise; -} - -export function useWorkItemOrchestrator( - options: UseWorkItemOrchestratorOptions -) { - const { - workItem, - displayWorkItem, - repoPath, - projectSlug, - shortId, - onRefreshWorkItem, - onUpdateWorkItem, - hasPendingChanges, - handleSave, - } = options; - - const { t } = useTranslation("projects"); - const store = useStore(); - const ownershipKey = - projectSlug && shortId - ? `${projectSlug}:${shortId}:${workItem.session_id}` - : null; - const ownsAutomaticSideEffects = useWorkItemOrchestratorOwnership( - store, - ownershipKey - ); - const claimAutoReviewLaunch = useCallback( - () => - Boolean( - ownershipKey && - ownsAutomaticSideEffects && - claimWorkItemOrchestratorAction( - store, - ownershipKey, - "auto-review-launch" - ) - ), - [ownershipKey, ownsAutomaticSideEffects, store] - ); - const releaseAutoReviewLaunchClaim = useCallback(() => { - if (ownershipKey) { - releaseWorkItemOrchestratorAction( - store, - ownershipKey, - "auto-review-launch" - ); - } - }, [ownershipKey, store]); - - const [isStartingAgent, setIsStartingAgent] = useState(false); - const [activeAgentSessionId, setActiveAgentSessionId] = useState< - string | null - >(null); - const [activeAgentRole, setActiveAgentRole] = useState( - null - ); - - const worktreePath = useAtomValue(activeWorkspaceRootPathAtom) || null; - - // Collab execution lock (design §16.6): resolves whether this work item is - // under a collab-synced org and, if so, whether a teammate holds the lock. - const collabLock = useWorkItemCollabLock({ - projectSlug, - shortId, - workItemId: workItem.session_id, - executionLock: displayWorkItem.executionLock, - }); - - const projectRepoPath = repoPath ?? null; - const accountId = - displayWorkItem.orchestratorConfig?.selected_account_id ?? null; - const modelId = - displayWorkItem.orchestratorConfig?.selected_model_id ?? undefined; - const rawMode = displayWorkItem.orchestratorConfig?.agent_mode; - const agentMode: AgentExecMode | undefined = normalizeExecMode(rawMode); - - const resolveSessionRepoPath = useCallback( - () => worktreePath ?? projectRepoPath ?? "", - [projectRepoPath, worktreePath] - ); - - const validateOrchestratorParams = useCallback((): boolean => { - if (!projectRepoPath || !projectSlug || !shortId) { - logger.error( - `Missing orchestrator params: projectRepoPath=${projectRepoPath}, projectSlug=${projectSlug}, shortId=${shortId}` - ); - return false; - } - return true; - }, [projectRepoPath, projectSlug, shortId]); - - const launchSdeSession = useCallback( - async ( - orchestratorCommand: typeof ORCHESTRATOR_COMMAND.Retry, - additionalInstructions?: string - ) => { - if (hasPendingChanges) { - await handleSave(); - } - - if (!validateOrchestratorParams() || !accountId) { - if (!accountId) { - Message.error(t("workItems.agentSettings.noCodeAccountError")); - } - return; - } - - const displayWorkItemCompleted = - displayWorkItem.workItemStatus === COMPLETED_WORK_ITEM_STATUS || - displayWorkItem.status === COMPLETED_WORK_ITEM_STATUS; - if ( - !displayWorkItemCompleted && - displayWorkItem.executionLock?.activeSessionId - ) { - Message.warning(t("workItems.agentWorkflow.running")); - onRefreshWorkItem?.(); - return; - } - - // Collab lock held by a teammate (design §16.6): the synced payload - // already tells us; refuse before spending a start. - if (collabLock.isLockedByOther) { - Message.warning( - t("workItems.agentWorkflow.collabLockHeld", { - name: collabLock.lockHolderName ?? "", - }) - ); - onRefreshWorkItem?.(); - return; - } - - // Acquire the server-arbitrated lock first (design §16.6). A no-op for - // non-collab work items; ORGII_CONFLICT means a teammate won the race. - let collabLockAcquired = false; - try { - collabLockAcquired = await collabLock.acquireLock(); - } catch (lockError) { - if (isCollabConflictError(lockError)) { - Message.warning(t("workItems.agentWorkflow.collabLockConflict")); - onRefreshWorkItem?.(); - return; - } - if (isCollabMembershipUnresolvedError(lockError)) { - // We cannot prove this work item is NOT collab-synced, so starting - // would skip server arbitration entirely — block instead. - Message.warning(t("workItems.agentWorkflow.collabLockUnresolved")); - return; - } - logger.warn( - `Failed to acquire collab lock for ${shortId}: ${formatOrchestratorError(lockError)}` - ); - // Non-conflict acquire failures on a KNOWN-collab work item (offline - // etc.) fall through: the local execution lock still guards - // single-machine safety. - } - - setIsStartingAgent(true); - try { - await invokeTauri(orchestratorCommand, { - projectSlug, - workItemId: shortId, - }); - - const sdePrompt = buildSdeTaskPrompt( - workItem, - shortId!, - additionalInstructions, - agentMode - ); - - const { sessionId: createdSessionId } = await SessionService.create({ - task: sdePrompt, - repoPath: projectRepoPath!, - projectRepoPath: projectRepoPath!, - worktreePath: - resolveSessionRepoPath() !== projectRepoPath - ? resolveSessionRepoPath() - : undefined, - accountId, - model: modelId, - workItemId: shortId!, - agentRole: AGENT_ROLE.Sde, - projectSlug: projectSlug ?? undefined, - mode: agentMode, - }); - - setActiveAgentSessionId(createdSessionId); - setActiveAgentRole(AGENT_ROLE.Sde); - logger.info( - `${orchestratorCommand} SDE for ${shortId}, sessionId=${createdSessionId}` - ); - onRefreshWorkItem?.(); - } catch (error) { - setActiveAgentSessionId(null); - setActiveAgentRole(null); - // The start failed AFTER the server lock was acquired: release it, - // or the holder-less run deadlocks this work item for every member - // (including us — the row would show OUR id as a live holder until - // the stale-TTL takeover). Best-effort: releaseLock swallows errors. - if (collabLockAcquired) { - void collabLock.releaseLock(); - } - try { - await invokeTauri(ORCHESTRATOR_COMMAND.Cancel, { - projectSlug, - workItemId: shortId, - }); - } catch (cancelError) { - logger.warn( - `Failed to roll back ${orchestratorCommand} for ${shortId}: ${formatOrchestratorError(cancelError)}` - ); - } - const msg = formatOrchestratorError(error); - logger.error(`Failed ${orchestratorCommand} for ${shortId}: ${msg}`); - Message.error(msg); - onRefreshWorkItem?.(); - } finally { - setIsStartingAgent(false); - } - }, - [ - hasPendingChanges, - handleSave, - validateOrchestratorParams, - accountId, - collabLock, - displayWorkItem.executionLock?.activeSessionId, - displayWorkItem.status, - displayWorkItem.workItemStatus, - projectRepoPath, - projectSlug, - shortId, - workItem, - modelId, - agentMode, - resolveSessionRepoPath, - onRefreshWorkItem, - t, - ] - ); - - const handleRetry = useCallback( - (instructions?: string) => - launchSdeSession(ORCHESTRATOR_COMMAND.Retry, instructions), - [launchSdeSession] - ); - - const handleCancelAgent = useCallback(async () => { - if (!validateOrchestratorParams()) return; - - try { - await invokeTauri(ORCHESTRATOR_COMMAND.Cancel, { - projectSlug, - workItemId: shortId, - }); - logger.info(`Cancelled orchestrator for ${shortId}`); - onRefreshWorkItem?.(); - } catch (error) { - const msg = formatOrchestratorError(error); - logger.error(`Failed to cancel orchestrator: ${msg}`); - Message.error(msg); - } - }, [validateOrchestratorParams, projectSlug, shortId, onRefreshWorkItem]); - - const handleAcceptAsIs = useCallback(async () => { - if (!validateOrchestratorParams()) return; - - try { - await invokeTauri(ORCHESTRATOR_COMMAND.Cancel, { - projectSlug, - workItemId: shortId, - }); - onUpdateWorkItem?.({ workItemStatus: "completed" }); - logger.info(`Accepted work item ${shortId} as-is`); - onRefreshWorkItem?.(); - } catch (error) { - const msg = formatOrchestratorError(error); - logger.error(`Failed to accept as-is: ${msg}`); - Message.error(msg); - } - }, [ - validateOrchestratorParams, - projectSlug, - shortId, - onUpdateWorkItem, - onRefreshWorkItem, - ]); - - const handleCreateFollowUp = useCallback(async () => { - if (!validateOrchestratorParams()) return; - - const feedbackSummary = - workItem.proofOfWork?.review_feedback?.summary ?? - t("workItems.agentWorkflow.reviewRequestedChanges"); - - try { - const newShortId = await invokeTauri( - ORCHESTRATOR_COMMAND.CreateFollowUp, - { - projectSlug, - parentShortId: shortId, - reviewFeedback: feedbackSummary, - } - ); - logger.info(`Created follow-up ${newShortId} from ${shortId}`); - - await invokeTauri(ORCHESTRATOR_COMMAND.Cancel, { - projectSlug, - workItemId: shortId, - }); - - onRefreshWorkItem?.(); - Message.success( - t("workItems.agentWorkflow.followUpCreated", { shortId: newShortId }) - ); - } catch (error) { - const msg = formatOrchestratorError(error); - logger.error(`Failed to create follow-up: ${msg}`); - Message.error(msg); - } - }, [ - validateOrchestratorParams, - projectSlug, - shortId, - workItem.proofOfWork?.review_feedback?.summary, - onRefreshWorkItem, - t, - ]); - - const runningLinkedSession = useMemo( - () => - workItem.linkedSessions?.find( - (session) => session.status === RUNNING_LINKED_SESSION_STATUS - ) ?? null, - [workItem.linkedSessions] - ); - const isCompletedWorkItem = - workItem.workItemStatus === COMPLETED_WORK_ITEM_STATUS || - workItem.status === COMPLETED_WORK_ITEM_STATUS; - const hasTerminalOnlyLinkedSessions = - (workItem.linkedSessions?.length ?? 0) > 0 && !runningLinkedSession; - const activeExecutionLockSessionId = - isCompletedWorkItem || hasTerminalOnlyLinkedSessions - ? null - : (workItem.executionLock?.activeSessionId ?? null); - const persistedActiveSessionId = - activeExecutionLockSessionId ?? runningLinkedSession?.session_id ?? null; - const canUseLocalActiveSession = - !isCompletedWorkItem && !hasTerminalOnlyLinkedSessions; - const effectiveActiveAgentSessionId = - persistedActiveSessionId ?? - (canUseLocalActiveSession ? activeAgentSessionId : null); - const effectiveActiveAgentRole = effectiveActiveAgentSessionId - ? (toAgentRole(runningLinkedSession?.agent_role) ?? - activeAgentRole ?? - AGENT_ROLE.Sde) - : null; - - useEffect(() => { - if (!canUseLocalActiveSession && activeAgentSessionId) { - setActiveAgentSessionId(null); - setActiveAgentRole(null); - } - }, [activeAgentSessionId, canUseLocalActiveSession]); - - const prevPhaseRef = useRef( - (workItem.orchestratorState?.current_phase as OrchestratorPhase) ?? "idle" - ); - useEffect(() => { - const phase = - (workItem.orchestratorState?.current_phase as OrchestratorPhase) ?? - "idle"; - if (prevPhaseRef.current !== phase) { - const wasActive = !TERMINAL_PHASES.has(prevPhaseRef.current); - prevPhaseRef.current = phase; - if (TERMINAL_PHASES.has(phase)) { - if (activeAgentSessionId && !persistedActiveSessionId) { - setActiveAgentSessionId(null); - setActiveAgentRole(null); - } - // Release the server lock on the run→terminal transition (design - // §16.6). The Rust side clears the LOCAL execution_lock; this drops - // the collab holder so teammates can start next (best-effort — the - // synced payload also reconciles it). - if (wasActive && ownsAutomaticSideEffects) { - void collabLock.releaseLock(); - } - } - } - }, [ - workItem.orchestratorState?.current_phase, - activeAgentSessionId, - persistedActiveSessionId, - collabLock, - ownsAutomaticSideEffects, - ]); - - useAutoReview({ - enabled: ownsAutomaticSideEffects, - claimLaunch: claimAutoReviewLaunch, - releaseLaunchClaim: releaseAutoReviewLaunchClaim, - workItem, - projectRepoPath, - accountId, - modelId, - shortId: shortId ?? null, - projectSlug: projectSlug ?? null, - resolveSessionRepoPath, - onRefreshWorkItem, - isStartingAgent, - setIsStartingAgent, - setActiveAgentSessionId, - setActiveAgentRole, - }); - - useStaleSessionDetection({ - enabled: ownsAutomaticSideEffects, - workItem, - projectRepoPath, - projectSlug: projectSlug ?? null, - shortId: shortId ?? null, - isStartingAgent, - handleCancelAgent, - onRefreshWorkItem, - }); - - return { - activeAgentSessionId: effectiveActiveAgentSessionId, - activeAgentRole: effectiveActiveAgentRole, - handleRetry, - handleCancelAgent, - handleAcceptAsIs, - handleCreateFollowUp, - worktreePath, - projectRepoPath, - }; -} diff --git a/src/modules/ProjectManager/WorkItems/hooks/useWorkItemOrchestrator/workItemOrchestratorOwnership.test.ts b/src/modules/ProjectManager/WorkItems/hooks/useWorkItemOrchestrator/workItemOrchestratorOwnership.test.ts deleted file mode 100644 index 57d87500c5..0000000000 --- a/src/modules/ProjectManager/WorkItems/hooks/useWorkItemOrchestrator/workItemOrchestratorOwnership.test.ts +++ /dev/null @@ -1,152 +0,0 @@ -// @vitest-environment jsdom -import { Provider, createStore } from "jotai"; -import { act, createElement } from "react"; -import { createRoot } from "react-dom/client"; -import { - afterAll, - beforeAll, - beforeEach, - describe, - expect, - it, - vi, -} from "vitest"; - -import { - claimWorkItemOrchestratorAction, - getWorkItemOrchestratorOwnershipCount, - isWorkItemOrchestratorOwner, - releaseWorkItemOrchestratorAction, - resetWorkItemOrchestratorOwnership, - retainWorkItemOrchestratorOwnership, - useWorkItemOrchestratorOwnership, -} from "./workItemOrchestratorOwnership"; - -function OwnershipProbe({ - label, - ownershipKey, - store, -}: { - label: string; - ownershipKey: string; - store: ReturnType; -}) { - const ownsSideEffects = useWorkItemOrchestratorOwnership(store, ownershipKey); - return createElement("div", { - "data-testid": label, - "data-owns-side-effects": String(ownsSideEffects), - }); -} - -describe("workItemOrchestratorOwnership", () => { - const actEnvironment = globalThis as typeof globalThis & { - IS_REACT_ACT_ENVIRONMENT?: boolean; - }; - - beforeAll(() => { - actEnvironment.IS_REACT_ACT_ENVIRONMENT = true; - }); - - beforeEach(() => resetWorkItemOrchestratorOwnership()); - - afterAll(() => { - Reflect.deleteProperty(actEnvironment, "IS_REACT_ACT_ENVIRONMENT"); - }); - - it("elects one owner, hands off on release, and drops the final entry", () => { - const store = createStore(); - const first = Symbol("first"); - const second = Symbol("second"); - const firstListener = vi.fn(); - const secondListener = vi.fn(); - - const releaseFirst = retainWorkItemOrchestratorOwnership( - store, - "project:item", - first, - firstListener - ); - const releaseSecond = retainWorkItemOrchestratorOwnership( - store, - "project:item", - second, - secondListener - ); - - expect(isWorkItemOrchestratorOwner(store, "project:item", first)).toBe( - true - ); - expect(isWorkItemOrchestratorOwner(store, "project:item", second)).toBe( - false - ); - expect(getWorkItemOrchestratorOwnershipCount(store)).toBe(1); - expect( - claimWorkItemOrchestratorAction(store, "project:item", "auto-review") - ).toBe(true); - expect( - claimWorkItemOrchestratorAction(store, "project:item", "auto-review") - ).toBe(false); - - releaseFirst(); - expect(isWorkItemOrchestratorOwner(store, "project:item", second)).toBe( - true - ); - expect(secondListener).toHaveBeenCalledOnce(); - expect( - claimWorkItemOrchestratorAction(store, "project:item", "auto-review") - ).toBe(false); - releaseWorkItemOrchestratorAction(store, "project:item", "auto-review"); - expect( - claimWorkItemOrchestratorAction(store, "project:item", "auto-review") - ).toBe(true); - - releaseSecond(); - expect(getWorkItemOrchestratorOwnershipCount(store)).toBe(0); - }); - - it("hands hook ownership to the remaining rendered surface", async () => { - const store = createStore(); - const container = document.createElement("div"); - document.body.appendChild(container); - const root = createRoot(container); - const ownershipKey = "project:item:session"; - const probe = (label: string) => - createElement(OwnershipProbe, { - key: label, - label, - ownershipKey, - store, - }); - - await act(async () => { - root.render( - createElement(Provider, { store }, probe("first"), probe("second")) - ); - }); - expect( - container - .querySelector("[data-testid='first']") - ?.getAttribute("data-owns-side-effects") - ).toBe("true"); - expect( - container - .querySelector("[data-testid='second']") - ?.getAttribute("data-owns-side-effects") - ).toBe("false"); - - await act(async () => { - root.render(createElement(Provider, { store }, probe("second"))); - }); - await vi.waitFor(() => { - expect( - container - .querySelector("[data-testid='second']") - ?.getAttribute("data-owns-side-effects") - ).toBe("true"); - }); - - act(() => root.unmount()); - container.remove(); - expect(getWorkItemOrchestratorOwnershipCount(store)).toBe(0); - }); -}); diff --git a/src/modules/ProjectManager/WorkItems/hooks/useWorkItemOrchestrator/workItemOrchestratorOwnership.ts b/src/modules/ProjectManager/WorkItems/hooks/useWorkItemOrchestrator/workItemOrchestratorOwnership.ts deleted file mode 100644 index a9ef69b8ef..0000000000 --- a/src/modules/ProjectManager/WorkItems/hooks/useWorkItemOrchestrator/workItemOrchestratorOwnership.ts +++ /dev/null @@ -1,116 +0,0 @@ -import type { Store } from "jotai/vanilla/store"; -import { useCallback, useRef, useSyncExternalStore } from "react"; - -interface OwnershipEntry { - claims: Set; - owners: symbol[]; - listeners: Map void>; -} - -let entriesByStore = new WeakMap>(); - -function entriesFor(store: Store): Map { - let entries = entriesByStore.get(store); - if (!entries) { - entries = new Map(); - entriesByStore.set(store, entries); - } - return entries; -} - -function notify(entry: OwnershipEntry): void { - for (const listener of entry.listeners.values()) listener(); -} - -export function retainWorkItemOrchestratorOwnership( - store: Store, - key: string, - owner: symbol, - listener: () => void -): () => void { - const entries = entriesFor(store); - let entry = entries.get(key); - if (!entry) { - entry = { claims: new Set(), owners: [], listeners: new Map() }; - entries.set(key, entry); - } - if (!entry.owners.includes(owner)) entry.owners.push(owner); - entry.listeners.set(owner, listener); - - let released = false; - return () => { - if (released) return; - released = true; - const current = entries.get(key); - if (!current) return; - current.owners = current.owners.filter((candidate) => candidate !== owner); - current.listeners.delete(owner); - if (current.owners.length === 0) { - entries.delete(key); - if (entries.size === 0) entriesByStore.delete(store); - return; - } - notify(current); - }; -} - -export function isWorkItemOrchestratorOwner( - store: Store, - key: string, - owner: symbol -): boolean { - return entriesByStore.get(store)?.get(key)?.owners[0] === owner; -} - -export function getWorkItemOrchestratorOwnershipCount(store: Store): number { - return entriesByStore.get(store)?.size ?? 0; -} - -export function claimWorkItemOrchestratorAction( - store: Store, - key: string, - action: string -): boolean { - const entry = entriesByStore.get(store)?.get(key); - if (!entry || entry.claims.has(action)) return false; - entry.claims.add(action); - return true; -} - -export function releaseWorkItemOrchestratorAction( - store: Store, - key: string, - action: string -): void { - entriesByStore.get(store)?.get(key)?.claims.delete(action); -} - -export function resetWorkItemOrchestratorOwnership(): void { - entriesByStore = new WeakMap>(); -} - -export function useWorkItemOrchestratorOwnership( - store: Store, - key: string | null -): boolean { - const ownerRef = useRef(Symbol("work-item-orchestrator-owner")); - const subscribe = useCallback( - (listener: () => void) => { - if (!key) return () => undefined; - return retainWorkItemOrchestratorOwnership( - store, - key, - ownerRef.current, - listener - ); - }, - [key, store] - ); - const getSnapshot = useCallback( - () => - key ? isWorkItemOrchestratorOwner(store, key, ownerRef.current) : false, - [key, store] - ); - - return useSyncExternalStore(subscribe, getSnapshot, () => false); -} From eec1646e5bc18ef2902942d8e998ccee2c7f6cac Mon Sep 17 00:00:00 2001 From: Neonforge <48338160+Neonforge98@users.noreply.github.com> Date: Fri, 14 Aug 2026 17:22:27 -0700 Subject: [PATCH 3/7] refactor(work-items): remove review settings surface Pre-commit hook ran. Total eslint: 0, total circular: 0 --- .../components/AgentSettings/index.tsx | 27 ---- .../ReviewerConfigSection/index.tsx | 142 ------------------ .../ProjectManager/WorkItems/constants.ts | 14 -- 3 files changed, 183 deletions(-) delete mode 100644 src/modules/ProjectManager/WorkItems/components/ReviewerConfigSection/index.tsx diff --git a/src/modules/ProjectManager/WorkItems/components/AgentSettings/index.tsx b/src/modules/ProjectManager/WorkItems/components/AgentSettings/index.tsx index 465d1448c9..ab5cee3315 100644 --- a/src/modules/ProjectManager/WorkItems/components/AgentSettings/index.tsx +++ b/src/modules/ProjectManager/WorkItems/components/AgentSettings/index.tsx @@ -96,7 +96,6 @@ const AgentSettings: React.FC = ({ ); const effectiveConfig = { ...DEFAULT_ORCHESTRATOR_CONFIG, ...config }; - const followUpDisabled = !effectiveConfig.review_enabled; const maxRetryDisabled = !effectiveConfig.auto_retry_on_failure; const selectedAccount = useMemo( @@ -300,32 +299,6 @@ const AgentSettings: React.FC = ({ - - handleToggle("review_enabled", checked)} - /> - - - - handleToggle("follow_up_enabled", checked)} - disabled={followUpDisabled} - /> - - = { - self_review: "workItems.agentSettings.reviewerSelfReview", - agent: "workItems.agentSettings.reviewerAgent", - human: "workItems.agentSettings.reviewerHuman", - org: "workItems.agentSettings.reviewerAgent", -}; - -interface ReviewerConfigSectionProps { - config: OrchestratorConfig; - onUpdateConfig: (updates: Partial) => void; - availableAgents: AgentDefinition[]; - t: (key: string) => string; -} - -const ReviewerConfigSection: React.FC = ({ - config, - onUpdateConfig, - availableAgents, - t, -}) => { - const builtInAgents = useAtomValue(builtInAgentsAtom); - - const reviewConfig: ReviewConfig = config.review_config ?? { - reviewer: { type: "self_review" }, - max_rounds: 3, - }; - - const handleReviewerTypeChange = ( - value: string | number | (string | number)[] - ) => { - const newType = value as ReviewerRefType; - onUpdateConfig({ - review_config: { - ...reviewConfig, - reviewer: { type: newType, id: undefined }, - }, - }); - }; - - const handleAgentSelect = (value: string | number | (string | number)[]) => { - const id = (value as string) || undefined; - onUpdateConfig({ - review_config: { - ...reviewConfig, - reviewer: { type: "agent", id }, - }, - }); - }; - - const handleMaxRoundsChange = (value: string) => { - const rounds = Math.max(1, Math.min(10, Number(value) || 1)); - onUpdateConfig({ - review_config: { ...reviewConfig, max_rounds: rounds }, - }); - }; - - const allAgents = [ - ...builtInAgents.map((agent) => ({ - id: agent.id, - name: agent.name, - })), - ...availableAgents.map((agent) => ({ - id: agent.id, - name: agent.name, - })), - ]; - - return ( -
-
- - {t("workItems.agentSettings.reviewerType")} - - ({ - value: agent.id, - label: agent.name, - })), - ]} - onChange={handleAgentSelect} - size="mini" - className="max-w-[140px]" - dropdownWidthMode="match" - /> -
- )} -
- - {t("workItems.agentSettings.maxReviewRounds")} - - -
-
- ); -}; - -export default ReviewerConfigSection; diff --git a/src/modules/ProjectManager/WorkItems/constants.ts b/src/modules/ProjectManager/WorkItems/constants.ts index 6a1aa22e12..c9fa08d331 100644 --- a/src/modules/ProjectManager/WorkItems/constants.ts +++ b/src/modules/ProjectManager/WorkItems/constants.ts @@ -66,20 +66,6 @@ export const SESSION_STATUS = { export const PENDING_SESSION_ID = "pending"; -export const ORCHESTRATOR_COMMAND = { - Retry: "orchestrator_retry", - Cancel: "orchestrator_cancel", - GetStatus: "orchestrator_get_status", - CreateFollowUp: "orchestrator_create_follow_up", -} as const; - -export type OrchestratorCommand = - (typeof ORCHESTRATOR_COMMAND)[keyof typeof ORCHESTRATOR_COMMAND]; - -export function formatOrchestratorError(error: unknown): string { - return error instanceof Error ? error.message : String(error); -} - /** * Default OrchestratorConfig — single source of truth. * Import this instead of duplicating inline defaults. From d01c18f917a036c243981a5f6cfc89aa5b763c07 Mon Sep 17 00:00:00 2001 From: Neonforge <48338160+Neonforge98@users.noreply.github.com> Date: Fri, 14 Aug 2026 17:30:00 -0700 Subject: [PATCH 4/7] chore(i18n): drop review-loop locale keys Pre-commit hook ran. Total eslint: 4, total circular: 0 --- src/i18n/locales/de/projects.json | 44 ++------------------------ src/i18n/locales/en/projects.json | 44 ++------------------------ src/i18n/locales/es/projects.json | 44 ++------------------------ src/i18n/locales/fr/projects.json | 44 ++------------------------ src/i18n/locales/ja/projects.json | 44 ++------------------------ src/i18n/locales/ko/projects.json | 44 ++------------------------ src/i18n/locales/pl/projects.json | 44 ++------------------------ src/i18n/locales/pt/projects.json | 44 ++------------------------ src/i18n/locales/ru/projects.json | 44 ++------------------------ src/i18n/locales/tr/projects.json | 44 ++------------------------ src/i18n/locales/vi/projects.json | 44 ++------------------------ src/i18n/locales/zh-Hant/projects.json | 44 ++------------------------ src/i18n/locales/zh/projects.json | 44 ++------------------------ 13 files changed, 26 insertions(+), 546 deletions(-) diff --git a/src/i18n/locales/de/projects.json b/src/i18n/locales/de/projects.json index 789d950d31..f1731d8720 100644 --- a/src/i18n/locales/de/projects.json +++ b/src/i18n/locales/de/projects.json @@ -360,8 +360,7 @@ "membersGroup": "Mitglieder", "agentsGroup": "Agents", "orgsGroup": "Organisationen", - "noReviewer": "Kein Reviewer", - "searchReviewer": "Reviewer suchen..." + "noReviewer": "Kein Reviewer" }, "history": { "noHistory": "Noch keine Historie" @@ -463,17 +462,12 @@ "title": "Agent Einstellungen", "providerTitle": "Anbieter", "behaviorTitle": "Verhalten", - "reviewEnabled": "Code Review", - "reviewEnabledDesc": "AI Agent überprüft Code-Änderungen vor Abschluss", - "followUpEnabled": "Folgemaßnahmen", - "followUpEnabledDesc": "Automatisch Folgemaßnahme erstellen wenn Review Änderungen verlangt", "autoRetryOnFailure": "Automatischer Neuversuch", "autoRetryOnFailureDesc": "Automatisch wiederholen bei Sitzungsfehler", "maxRetryCount": "Max. Neuversuche", "autoCreatePr": "PR automatisch erstellen", "autoCreatePrDesc": "Automatisch Pull Request bei Abschluss erstellen", "pendingChanges": "Änderungen werden beim nächsten Durchlauf angewendet", - "disabledRequiresReview": "Erfordert aktiviertes Code Review", "disabledRequiresAutoRetry": "Erfordert aktivierten automatischen Neuversuch", "codeAccount": "Code-Konto", "codeAccountDesc": "Lokales Konto zur Ausführung des Agent auswählen", @@ -491,19 +485,15 @@ "addSubAgent": "Sub-Agent hinzufügen...", "noSubAgents": "Keine Sub-Agents konfiguriert.", "noCodeAccountError": "Bitte wählen Sie in den Agent-Einstellungen ein Code-Konto aus, bevor Sie starten.", - "reviewerType": "Reviewer-Typ", "reviewerSelfReview": "Selbst-Review", "reviewerAgent": "Agent", - "reviewerHuman": "Manuelles Review", - "maxReviewRounds": "Max. Review-Runden", - "maxReviewRoundsDesc": "Maximale Anzahl der Review-Iterationen" + "reviewerHuman": "Manuelles Review" }, "agentWorkflow": { "title": "Agent Workflow", "running": "Läuft...", "completed": "Abgeschlossen", "failed": "Fehlgeschlagen", - "awaitingUser": "Aktion erforderlich", "interrupted": "Unterbrochen", "retry": "Wiederholen", "resume": "Fortsetzen", @@ -513,8 +503,6 @@ "noWorkflowRun": "Noch kein Agent Workflow ausgeführt", "confirmStart": "SDE Agent für dieses Arbeitselement starten?", "sdePhase": "SDE Entwicklung", - "reviewPhase": "Code Review", - "followUpPhase": "Nachverfolgung", "totalCost": "Gesamtkosten", "totalTokens": "Gesamt Token", "proofOfWork": "Arbeitsergebnis", @@ -523,7 +511,6 @@ "filesChanged": "Geänderte Dateien", "linesAdded": "Hinzugefügte Zeilen", "linesRemoved": "Entfernte Zeilen", - "reviewOutcome": "Review-Ergebnis", "approved": "Genehmigt", "changesRequested": "Änderungen angefordert", "inconclusive": "Unbestimmt", @@ -538,7 +525,6 @@ "whatNext": "Was möchten Sie tun?", "acceptAsIs": "So Akzeptieren", "fixAndRerun": "Beheben und Erneut Ausführen", - "createFollowUp": "Nachfolgeaufgabe Erstellen", "roleSde": "SDE", "roleReview": "Review", "roleFollowUp": "Follow-up", @@ -548,7 +534,6 @@ "statusCompleted": "Abgeschlossen", "statusFailed": "Fehlgeschlagen", "statusCancelled": "Abgebrochen", - "followUpCreated": "Follow-up {{shortId}} erstellt", "sessionFiles": "Dateien", "noFilesModified": "Keine Dateien geändert", "subAgents": "Sub-Agents", @@ -558,7 +543,6 @@ "tokens": "Token", "subAgentDefault": "Sub-Agent", "toolResult": "{{tool}} Ergebnis", - "reviewRequestedChanges": "Review hat Änderungen angefordert", "collabLockRunning": "{{name}} is running", "collabLockHeld": "{{name}}'s agent is already working on this item", "collabLockConflict": "Another member just started an agent on this item", @@ -570,30 +554,6 @@ "agentWorking": "Agent arbeitet noch...", "reviewInEditor": "Alle im Editor überprüfen" }, - "reviewFeedback": { - "approved": "Review: Genehmigt", - "changesRequested": "Review: Änderungen Angefordert", - "inconclusive": "Review: Unbestimmt", - "previousReview": "Vorheriges Review", - "sdeAddressing": "SDE bearbeitet diese Probleme...", - "iterationHistory": "Iterationsverlauf ({{count}} vorherige)", - "reviewRound": "Review #{{round}}", - "archived": "Dieses Review wurde durch ein späteres Review archiviert.", - "outcomeApproved": "Genehmigt", - "outcomeChangesRequested": "Änderungen angefordert", - "issueCount_one": "{{count}} Problem", - "issueCount_other": "{{count}} Probleme", - "fixedCount": "{{count}} behoben", - "errors_one": "{{count}} Fehler", - "errors_other": "{{count}} Fehler", - "warnings_one": "{{count}} Warnung", - "warnings_other": "{{count}} Warnungen", - "suggestions_one": "{{count}} Vorschlag", - "suggestions_other": "{{count}} Vorschläge", - "praises_one": "{{count}} Lob", - "praises_other": "{{count}} Lob", - "showFullReview": "Vollständiges Review anzeigen" - }, "errors": { "noBranch": "Kein Branch im Arbeitsnachweis gefunden", "notAuthenticated": "Nicht authentifiziert. Bitte melden Sie sich beim Marketplace an.", diff --git a/src/i18n/locales/en/projects.json b/src/i18n/locales/en/projects.json index 1560f3c1e6..40e7dd68b3 100644 --- a/src/i18n/locales/en/projects.json +++ b/src/i18n/locales/en/projects.json @@ -359,8 +359,7 @@ "membersGroup": "Members", "agentsGroup": "Agents", "orgsGroup": "Organizations", - "noReviewer": "No reviewer", - "searchReviewer": "Search reviewer..." + "noReviewer": "No reviewer" }, "history": { "noHistory": "No history yet" @@ -478,17 +477,12 @@ "title": "Agent Settings", "providerTitle": "Provider", "behaviorTitle": "Behavior", - "reviewEnabled": "Code Review", - "reviewEnabledDesc": "AI agent reviews code changes before completion", - "followUpEnabled": "Follow-up Items", - "followUpEnabledDesc": "Auto-create follow-up when review requests changes", "autoRetryOnFailure": "Auto Retry", "autoRetryOnFailureDesc": "Automatically retry when the session fails", "maxRetryCount": "Max Retries", "autoCreatePr": "Auto Create PR", "autoCreatePrDesc": "Automatically create a pull request on completion", "pendingChanges": "Changes will apply on next run", - "disabledRequiresReview": "Requires review to be enabled", "disabledRequiresAutoRetry": "Requires auto retry to be enabled", "codeAccount": "Code Account", "codeAccountDesc": "Select the local account to run the agent", @@ -506,19 +500,15 @@ "addSubAgent": "Add Sub-Agent...", "noSubAgents": "No sub-agents configured.", "noCodeAccountError": "Please select a code account in Agent Settings before starting.", - "reviewerType": "Reviewer Type", "reviewerSelfReview": "Self Review", "reviewerAgent": "Agent", - "reviewerHuman": "Human", - "maxReviewRounds": "Max Review Rounds", - "maxReviewRoundsDesc": "Maximum number of review iterations" + "reviewerHuman": "Human" }, "agentWorkflow": { "title": "Agent Workflow", "running": "Running...", "completed": "Completed", "failed": "Failed", - "awaitingUser": "Awaiting Action", "interrupted": "Interrupted", "retry": "Retry", "resume": "Resume", @@ -528,8 +518,6 @@ "noWorkflowRun": "No agent workflow has been run yet", "confirmStart": "Start the SDE agent on this work item?", "sdePhase": "SDE Development", - "reviewPhase": "Code Review", - "followUpPhase": "Follow-up", "totalCost": "Total Cost", "totalTokens": "Total Tokens", "proofOfWork": "Proof of Work", @@ -538,7 +526,6 @@ "filesChanged": "Files Changed", "linesAdded": "Lines Added", "linesRemoved": "Lines Removed", - "reviewOutcome": "Review Outcome", "approved": "Approved", "changesRequested": "Changes Requested", "inconclusive": "Inconclusive", @@ -553,7 +540,6 @@ "whatNext": "What would you like to do?", "acceptAsIs": "Accept As-Is", "fixAndRerun": "Fix & Re-run", - "createFollowUp": "Create Follow-up", "roleSde": "SDE", "roleReview": "Review", "roleFollowUp": "Follow-up", @@ -563,7 +549,6 @@ "statusCompleted": "Completed", "statusFailed": "Failed", "statusCancelled": "Cancelled", - "followUpCreated": "Follow-up {{shortId}} created", "sessionFiles": "Files", "noFilesModified": "No files modified", "subAgents": "Sub-Agents", @@ -573,7 +558,6 @@ "tokens": "tokens", "subAgentDefault": "Sub-Agent", "toolResult": "{{tool}} result", - "reviewRequestedChanges": "Review requested changes", "collabLockRunning": "{{name}} is running", "collabLockHeld": "{{name}}'s agent is already working on this item", "collabLockConflict": "Another member just started an agent on this item", @@ -585,30 +569,6 @@ "agentWorking": "Agent is still working...", "reviewInEditor": "Review all in editor" }, - "reviewFeedback": { - "approved": "Review: Approved", - "changesRequested": "Review: Changes Requested", - "inconclusive": "Review: Inconclusive", - "previousReview": "Previous Review", - "sdeAddressing": "SDE is addressing these issues...", - "iterationHistory": "Iteration History ({{count}} previous)", - "reviewRound": "Review #{{round}}", - "archived": "This review is archived by a later review.", - "outcomeApproved": "Approved", - "outcomeChangesRequested": "Changes Requested", - "issueCount_one": "{{count}} issue", - "issueCount_other": "{{count}} issues", - "fixedCount": "{{count}} fixed", - "errors_one": "{{count}} Error", - "errors_other": "{{count}} Errors", - "warnings_one": "{{count}} Warning", - "warnings_other": "{{count}} Warnings", - "suggestions_one": "{{count}} Suggestion", - "suggestions_other": "{{count}} Suggestions", - "praises_one": "{{count}} Praise", - "praises_other": "{{count}} Praises", - "showFullReview": "Show full review" - }, "errors": { "noBranch": "No branch found in proof of work", "notAuthenticated": "Not authenticated. Please log in to the marketplace.", diff --git a/src/i18n/locales/es/projects.json b/src/i18n/locales/es/projects.json index cf5035e9e9..7cef45078e 100644 --- a/src/i18n/locales/es/projects.json +++ b/src/i18n/locales/es/projects.json @@ -360,8 +360,7 @@ "membersGroup": "Miembros", "agentsGroup": "Agents", "orgsGroup": "Organizaciones", - "noReviewer": "Sin revisor", - "searchReviewer": "Buscar revisor..." + "noReviewer": "Sin revisor" }, "history": { "noHistory": "Sin historial aún" @@ -463,17 +462,12 @@ "title": "Configuración de Agent", "providerTitle": "Proveedor", "behaviorTitle": "Comportamiento", - "reviewEnabled": "Revisión de Código", - "reviewEnabledDesc": "El AI Agent revisa los cambios de código antes de completar", - "followUpEnabled": "Elementos de Seguimiento", - "followUpEnabledDesc": "Crear seguimiento automático cuando la revisión solicita cambios", "autoRetryOnFailure": "Reintento Automático", "autoRetryOnFailureDesc": "Reintentar automáticamente cuando la sesión falla", "maxRetryCount": "Máximo de Reintentos", "autoCreatePr": "Crear PR Automáticamente", "autoCreatePrDesc": "Crear automáticamente un Pull Request al completar", "pendingChanges": "Los cambios se aplicarán en la próxima ejecución", - "disabledRequiresReview": "Requiere que la revisión esté habilitada", "disabledRequiresAutoRetry": "Requiere que el reintento automático esté habilitado", "codeAccount": "Cuenta de Código", "codeAccountDesc": "Seleccionar la cuenta local para ejecutar el Agent", @@ -491,19 +485,15 @@ "addSubAgent": "Agregar Sub-Agent...", "noSubAgents": "No hay sub-agents configurados.", "noCodeAccountError": "Seleccione una cuenta de código en la configuración del Agent antes de iniciar.", - "reviewerType": "Tipo de revisor", "reviewerSelfReview": "Auto revisión", "reviewerAgent": "Agent", - "reviewerHuman": "Revisión humana", - "maxReviewRounds": "Máx. rondas de revisión", - "maxReviewRoundsDesc": "Número máximo de iteraciones de revisión" + "reviewerHuman": "Revisión humana" }, "agentWorkflow": { "title": "Flujo de Trabajo de Agent", "running": "Ejecutando...", "completed": "Completado", "failed": "Fallido", - "awaitingUser": "Esperando Acción", "interrupted": "Interrumpido", "retry": "Reintentar", "resume": "Reanudar", @@ -513,8 +503,6 @@ "noWorkflowRun": "Aún no se ha ejecutado ningún flujo de trabajo de Agent", "confirmStart": "¿Iniciar el SDE Agent en este elemento de trabajo?", "sdePhase": "Desarrollo SDE", - "reviewPhase": "Revisión de Código", - "followUpPhase": "Seguimiento", "totalCost": "Costo Total", "totalTokens": "Total de Token", "proofOfWork": "Prueba de Trabajo", @@ -523,7 +511,6 @@ "filesChanged": "Archivos Cambiados", "linesAdded": "Líneas Añadidas", "linesRemoved": "Líneas Eliminadas", - "reviewOutcome": "Resultado de Revisión", "approved": "Aprobado", "changesRequested": "Cambios Solicitados", "inconclusive": "Inconcluso", @@ -538,7 +525,6 @@ "whatNext": "¿Qué le gustaría hacer?", "acceptAsIs": "Aceptar Tal Cual", "fixAndRerun": "Corregir y Re-ejecutar", - "createFollowUp": "Crear Seguimiento", "roleSde": "SDE", "roleReview": "Review", "roleFollowUp": "Follow-up", @@ -548,7 +534,6 @@ "statusCompleted": "Completado", "statusFailed": "Fallido", "statusCancelled": "Cancelado", - "followUpCreated": "Follow-up {{shortId}} creado", "sessionFiles": "Archivos", "noFilesModified": "Sin archivos modificados", "subAgents": "Sub-Agents", @@ -558,7 +543,6 @@ "tokens": "tokens", "subAgentDefault": "Sub-Agent", "toolResult": "resultado de {{tool}}", - "reviewRequestedChanges": "Review solicitó cambios", "collabLockRunning": "{{name}} is running", "collabLockHeld": "{{name}}'s agent is already working on this item", "collabLockConflict": "Another member just started an agent on this item", @@ -570,30 +554,6 @@ "agentWorking": "El Agent aún está trabajando...", "reviewInEditor": "Revisar todo en el editor" }, - "reviewFeedback": { - "approved": "Review: Aprobado", - "changesRequested": "Review: Cambios Solicitados", - "inconclusive": "Review: No Concluyente", - "previousReview": "Review Anterior", - "sdeAddressing": "SDE está abordando estos problemas...", - "iterationHistory": "Historial de Iteraciones ({{count}} anteriores)", - "reviewRound": "Review #{{round}}", - "archived": "Esta review ha sido archivada por una review posterior.", - "outcomeApproved": "Aprobado", - "outcomeChangesRequested": "Cambios solicitados", - "issueCount_one": "{{count}} problema", - "issueCount_other": "{{count}} problemas", - "fixedCount": "{{count}} corregido(s)", - "errors_one": "{{count}} Error", - "errors_other": "{{count}} Errores", - "warnings_one": "{{count}} Advertencia", - "warnings_other": "{{count}} Advertencias", - "suggestions_one": "{{count}} Sugerencia", - "suggestions_other": "{{count}} Sugerencias", - "praises_one": "{{count}} Elogio", - "praises_other": "{{count}} Elogios", - "showFullReview": "Mostrar review completa" - }, "errors": { "noBranch": "No se encontró rama en la prueba de trabajo", "notAuthenticated": "No autenticado. Inicie sesión en el marketplace.", diff --git a/src/i18n/locales/fr/projects.json b/src/i18n/locales/fr/projects.json index 7b8d068be4..060a0269b2 100644 --- a/src/i18n/locales/fr/projects.json +++ b/src/i18n/locales/fr/projects.json @@ -360,8 +360,7 @@ "membersGroup": "Membres", "agentsGroup": "Agents", "orgsGroup": "Organisations", - "noReviewer": "Aucun réviseur", - "searchReviewer": "Rechercher un réviseur..." + "noReviewer": "Aucun réviseur" }, "history": { "noHistory": "Pas encore d'historique" @@ -463,17 +462,12 @@ "title": "Paramètres Agent", "providerTitle": "Fournisseur", "behaviorTitle": "Comportement", - "reviewEnabled": "Revue de Code", - "reviewEnabledDesc": "L'AI Agent vérifie les modifications avant finalisation", - "followUpEnabled": "Éléments de Suivi", - "followUpEnabledDesc": "Créer automatiquement un suivi quand la revue demande des changements", "autoRetryOnFailure": "Réessai Automatique", "autoRetryOnFailureDesc": "Réessayer automatiquement en cas d'échec de session", "maxRetryCount": "Nombre Max de Réessais", "autoCreatePr": "Créer PR Automatiquement", "autoCreatePrDesc": "Créer automatiquement un Pull Request à la fin", "pendingChanges": "Les modifications seront appliquées à la prochaine exécution", - "disabledRequiresReview": "Nécessite l'activation de la revue de code", "disabledRequiresAutoRetry": "Nécessite l'activation du réessai automatique", "codeAccount": "Compte de Code", "codeAccountDesc": "Sélectionner le compte local pour exécuter l'Agent", @@ -491,19 +485,15 @@ "addSubAgent": "Ajouter un Sub-Agent...", "noSubAgents": "Aucun sub-agent configuré.", "noCodeAccountError": "Veuillez sélectionner un compte de code dans les paramètres de l'Agent avant de commencer.", - "reviewerType": "Type de réviseur", "reviewerSelfReview": "Auto-révision", "reviewerAgent": "Agent", - "reviewerHuman": "Révision humaine", - "maxReviewRounds": "Rondes de révision max.", - "maxReviewRoundsDesc": "Nombre maximum d'itérations de révision" + "reviewerHuman": "Révision humaine" }, "agentWorkflow": { "title": "Flux de Travail Agent", "running": "En cours...", "completed": "Terminé", "failed": "Échoué", - "awaitingUser": "En Attente d'Action", "interrupted": "Interrompu", "retry": "Réessayer", "resume": "Reprendre", @@ -513,8 +503,6 @@ "noWorkflowRun": "Aucun flux de travail Agent n'a encore été exécuté", "confirmStart": "Démarrer l'Agent SDE sur cet élément de travail ?", "sdePhase": "Développement SDE", - "reviewPhase": "Revue de Code", - "followUpPhase": "Suivi", "totalCost": "Coût Total", "totalTokens": "Total Token", "proofOfWork": "Preuve de Travail", @@ -523,7 +511,6 @@ "filesChanged": "Fichiers Modifiés", "linesAdded": "Lignes Ajoutées", "linesRemoved": "Lignes Supprimées", - "reviewOutcome": "Résultat de Revue", "approved": "Approuvé", "changesRequested": "Modifications Demandées", "inconclusive": "Non Concluant", @@ -538,7 +525,6 @@ "whatNext": "Que souhaitez-vous faire ?", "acceptAsIs": "Accepter Tel Quel", "fixAndRerun": "Corriger et Relancer", - "createFollowUp": "Créer un Suivi", "roleSde": "SDE", "roleReview": "Review", "roleFollowUp": "Follow-up", @@ -548,7 +534,6 @@ "statusCompleted": "Terminé", "statusFailed": "Échoué", "statusCancelled": "Annulé", - "followUpCreated": "Follow-up {{shortId}} créé", "sessionFiles": "Fichiers", "noFilesModified": "Aucun fichier modifié", "subAgents": "Sub-Agents", @@ -558,7 +543,6 @@ "tokens": "tokens", "subAgentDefault": "Sub-Agent", "toolResult": "résultat de {{tool}}", - "reviewRequestedChanges": "Review a demandé des modifications", "collabLockRunning": "{{name}} is running", "collabLockHeld": "{{name}}'s agent is already working on this item", "collabLockConflict": "Another member just started an agent on this item", @@ -570,30 +554,6 @@ "agentWorking": "L'Agent travaille encore...", "reviewInEditor": "Tout réviser dans l'éditeur" }, - "reviewFeedback": { - "approved": "Review : Approuvé", - "changesRequested": "Review : Modifications Demandées", - "inconclusive": "Review : Non Concluant", - "previousReview": "Review Précédente", - "sdeAddressing": "SDE traite ces problèmes...", - "iterationHistory": "Historique des itérations ({{count}} précédentes)", - "reviewRound": "Review #{{round}}", - "archived": "Cette review est archivée par une review ultérieure.", - "outcomeApproved": "Approuvé", - "outcomeChangesRequested": "Modifications demandées", - "issueCount_one": "{{count}} problème", - "issueCount_other": "{{count}} problèmes", - "fixedCount": "{{count}} corrigé(s)", - "errors_one": "{{count}} Erreur", - "errors_other": "{{count}} Erreurs", - "warnings_one": "{{count}} Avertissement", - "warnings_other": "{{count}} Avertissements", - "suggestions_one": "{{count}} Suggestion", - "suggestions_other": "{{count}} Suggestions", - "praises_one": "{{count}} Éloge", - "praises_other": "{{count}} Éloges", - "showFullReview": "Afficher la review complète" - }, "errors": { "noBranch": "Aucune branche trouvée dans la preuve de travail", "notAuthenticated": "Non authentifié. Veuillez vous connecter au marketplace.", diff --git a/src/i18n/locales/ja/projects.json b/src/i18n/locales/ja/projects.json index 6cb9d77d2c..3f0698229d 100644 --- a/src/i18n/locales/ja/projects.json +++ b/src/i18n/locales/ja/projects.json @@ -360,8 +360,7 @@ "membersGroup": "メンバー", "agentsGroup": "Agents", "orgsGroup": "組織", - "noReviewer": "レビューアーなし", - "searchReviewer": "レビューアーを検索..." + "noReviewer": "レビューアーなし" }, "history": { "noHistory": "履歴はまだありません" @@ -463,17 +462,12 @@ "title": "Agent 設定", "providerTitle": "プロバイダー", "behaviorTitle": "動作", - "reviewEnabled": "コードレビュー", - "reviewEnabledDesc": "AI Agent が完了前にコード変更をレビュー", - "followUpEnabled": "フォローアップ項目", - "followUpEnabledDesc": "レビューで修正が求められた場合に自動でフォローアップを作成", "autoRetryOnFailure": "自動リトライ", "autoRetryOnFailureDesc": "セッション失敗時に自動でリトライ", "maxRetryCount": "最大リトライ回数", "autoCreatePr": "PR 自動作成", "autoCreatePrDesc": "完了時に自動で Pull Request を作成", "pendingChanges": "変更は次回実行時に適用されます", - "disabledRequiresReview": "コードレビューの有効化が必要です", "disabledRequiresAutoRetry": "自動リトライの有効化が必要です", "codeAccount": "コードアカウント", "codeAccountDesc": "Agent を実行するローカルアカウントを選択", @@ -491,19 +485,15 @@ "addSubAgent": "Sub-Agent を追加...", "noSubAgents": "Sub-Agent は設定されていません。", "noCodeAccountError": "開始前に Agent 設定でコードアカウントを選択してください。", - "reviewerType": "レビュータイプ", "reviewerSelfReview": "セルフレビュー", "reviewerAgent": "Agent", - "reviewerHuman": "人的レビュー", - "maxReviewRounds": "最大レビュー回数", - "maxReviewRoundsDesc": "レビューイテレーションの最大回数" + "reviewerHuman": "人的レビュー" }, "agentWorkflow": { "title": "Agent ワークフロー", "running": "実行中...", "completed": "完了", "failed": "失敗", - "awaitingUser": "操作待ち", "interrupted": "中断済み", "retry": "リトライ", "resume": "再開", @@ -513,8 +503,6 @@ "noWorkflowRun": "Agent ワークフローはまだ実行されていません", "confirmStart": "この作業アイテムで SDE Agent を開始しますか?", "sdePhase": "SDE 開発", - "reviewPhase": "コードレビュー", - "followUpPhase": "フォローアップ", "totalCost": "合計コスト", "totalTokens": "合計 Token", "proofOfWork": "作業成果", @@ -523,7 +511,6 @@ "filesChanged": "変更ファイル数", "linesAdded": "追加行数", "linesRemoved": "削除行数", - "reviewOutcome": "レビュー結果", "approved": "承認済み", "changesRequested": "修正依頼", "inconclusive": "未確定", @@ -538,7 +525,6 @@ "whatNext": "次に何をしますか?", "acceptAsIs": "そのまま承認", "fixAndRerun": "修正して再実行", - "createFollowUp": "フォローアップを作成", "roleSde": "SDE", "roleReview": "Review", "roleFollowUp": "Follow-up", @@ -548,7 +534,6 @@ "statusCompleted": "完了", "statusFailed": "失敗", "statusCancelled": "キャンセル済み", - "followUpCreated": "Follow-up {{shortId}} を作成しました", "sessionFiles": "ファイル", "noFilesModified": "変更されたファイルはありません", "subAgents": "Sub-Agents", @@ -558,7 +543,6 @@ "tokens": "Token", "subAgentDefault": "Sub-Agent", "toolResult": "{{tool}} 結果", - "reviewRequestedChanges": "Review 変更リクエスト", "collabLockRunning": "{{name}} is running", "collabLockHeld": "{{name}}'s agent is already working on this item", "collabLockConflict": "Another member just started an agent on this item", @@ -570,30 +554,6 @@ "agentWorking": "Agent がまだ作業中...", "reviewInEditor": "エディターですべてレビュー" }, - "reviewFeedback": { - "approved": "Review: 承認", - "changesRequested": "Review: 修正依頼", - "inconclusive": "Review: 未確定", - "previousReview": "前回の Review", - "sdeAddressing": "SDE がこれらの問題に対応中...", - "iterationHistory": "イテレーション履歴 ({{count}} 件の過去)", - "reviewRound": "Review #{{round}}", - "archived": "この Review は後続の Review によりアーカイブされました。", - "outcomeApproved": "承認済み", - "outcomeChangesRequested": "変更リクエスト", - "issueCount_one": "{{count}} 件の問題", - "issueCount_other": "{{count}} 件の問題", - "fixedCount": "{{count}} 件修正済み", - "errors_one": "{{count}} 件のエラー", - "errors_other": "{{count}} 件のエラー", - "warnings_one": "{{count}} 件の警告", - "warnings_other": "{{count}} 件の警告", - "suggestions_one": "{{count}} 件の提案", - "suggestions_other": "{{count}} 件の提案", - "praises_one": "{{count}} 件の賞賛", - "praises_other": "{{count}} 件の賞賛", - "showFullReview": "Review 全文を表示" - }, "errors": { "noBranch": "作業証明にブランチが見つかりません", "notAuthenticated": "認証されていません。Marketplace にログインしてください。", diff --git a/src/i18n/locales/ko/projects.json b/src/i18n/locales/ko/projects.json index 08564150bb..167a19e0df 100644 --- a/src/i18n/locales/ko/projects.json +++ b/src/i18n/locales/ko/projects.json @@ -360,8 +360,7 @@ "membersGroup": "멤버", "agentsGroup": "Agents", "orgsGroup": "조직", - "noReviewer": "리뷰어 없음", - "searchReviewer": "리뷰어 검색..." + "noReviewer": "리뷰어 없음" }, "history": { "noHistory": "아직 기록이 없습니다" @@ -463,17 +462,12 @@ "title": "Agent 설정", "providerTitle": "제공자", "behaviorTitle": "동작", - "reviewEnabled": "코드 리뷰", - "reviewEnabledDesc": "AI Agent가 완료 전 코드 변경 사항을 리뷰", - "followUpEnabled": "후속 항목", - "followUpEnabledDesc": "리뷰에서 수정 요청 시 자동으로 후속 항목 생성", "autoRetryOnFailure": "자동 재시도", "autoRetryOnFailureDesc": "세션 실패 시 자동 재시도", "maxRetryCount": "최대 재시도 횟수", "autoCreatePr": "PR 자동 생성", "autoCreatePrDesc": "완료 시 자동으로 Pull Request 생성", "pendingChanges": "변경 사항은 다음 실행 시 적용됩니다", - "disabledRequiresReview": "코드 리뷰 활성화 필요", "disabledRequiresAutoRetry": "자동 재시도 활성화 필요", "codeAccount": "코드 계정", "codeAccountDesc": "Agent를 실행할 로컬 계정 선택", @@ -491,19 +485,15 @@ "addSubAgent": "Sub-Agent 추가...", "noSubAgents": "구성된 Sub-Agent가 없습니다.", "noCodeAccountError": "시작하기 전에 Agent 설정에서 코드 계정을 선택하세요.", - "reviewerType": "리뷰어 유형", "reviewerSelfReview": "셀프 리뷰", "reviewerAgent": "Agent", - "reviewerHuman": "수동 리뷰", - "maxReviewRounds": "최대 리뷰 라운드", - "maxReviewRoundsDesc": "리뷰 반복의 최대 횟수" + "reviewerHuman": "수동 리뷰" }, "agentWorkflow": { "title": "Agent 워크플로", "running": "실행 중...", "completed": "완료", "failed": "실패", - "awaitingUser": "작업 대기", "interrupted": "중단됨", "retry": "재시도", "resume": "재개", @@ -513,8 +503,6 @@ "noWorkflowRun": "아직 Agent 워크플로가 실행되지 않았습니다", "confirmStart": "이 작업 항목에서 SDE Agent를 시작하시겠습니까?", "sdePhase": "SDE 개발", - "reviewPhase": "코드 리뷰", - "followUpPhase": "후속 처리", "totalCost": "총 비용", "totalTokens": "총 Token", "proofOfWork": "작업 결과", @@ -523,7 +511,6 @@ "filesChanged": "변경된 파일", "linesAdded": "추가된 줄", "linesRemoved": "삭제된 줄", - "reviewOutcome": "리뷰 결과", "approved": "승인됨", "changesRequested": "수정 요청", "inconclusive": "미결정", @@ -538,7 +525,6 @@ "whatNext": "다음에 무엇을 하시겠습니까?", "acceptAsIs": "현재 상태로 수락", "fixAndRerun": "수정 후 재실행", - "createFollowUp": "후속 작업 생성", "roleSde": "SDE", "roleReview": "Review", "roleFollowUp": "Follow-up", @@ -548,7 +534,6 @@ "statusCompleted": "완료", "statusFailed": "실패", "statusCancelled": "취소됨", - "followUpCreated": "Follow-up {{shortId}} 생성됨", "sessionFiles": "파일", "noFilesModified": "수정된 파일 없음", "subAgents": "Sub-Agents", @@ -558,7 +543,6 @@ "tokens": "Token", "subAgentDefault": "Sub-Agent", "toolResult": "{{tool}} 결과", - "reviewRequestedChanges": "Review 변경 요청", "collabLockRunning": "{{name}} is running", "collabLockHeld": "{{name}}'s agent is already working on this item", "collabLockConflict": "Another member just started an agent on this item", @@ -570,30 +554,6 @@ "agentWorking": "Agent가 아직 작업 중...", "reviewInEditor": "에디터에서 모두 리뷰" }, - "reviewFeedback": { - "approved": "Review: 승인됨", - "changesRequested": "Review: 변경 요청됨", - "inconclusive": "Review: 미결정", - "previousReview": "이전 Review", - "sdeAddressing": "SDE가 이 문제들을 처리 중...", - "iterationHistory": "반복 기록 (이전 {{count}}건)", - "reviewRound": "Review #{{round}}", - "archived": "이 Review는 이후 Review로 보관 처리되었습니다.", - "outcomeApproved": "승인됨", - "outcomeChangesRequested": "변경 요청됨", - "issueCount_one": "{{count}}개 문제", - "issueCount_other": "{{count}}개 문제", - "fixedCount": "{{count}}개 수정됨", - "errors_one": "오류 {{count}}건", - "errors_other": "오류 {{count}}건", - "warnings_one": "경고 {{count}}건", - "warnings_other": "경고 {{count}}건", - "suggestions_one": "제안 {{count}}건", - "suggestions_other": "제안 {{count}}건", - "praises_one": "칭찬 {{count}}건", - "praises_other": "칭찬 {{count}}건", - "showFullReview": "전체 Review 보기" - }, "errors": { "noBranch": "작업 증명에서 브랜치를 찾을 수 없습니다", "notAuthenticated": "인증되지 않았습니다. Marketplace에 로그인하세요.", diff --git a/src/i18n/locales/pl/projects.json b/src/i18n/locales/pl/projects.json index 48799a4e3c..e27f4c1075 100644 --- a/src/i18n/locales/pl/projects.json +++ b/src/i18n/locales/pl/projects.json @@ -360,8 +360,7 @@ "membersGroup": "Członkowie", "agentsGroup": "Agenci", "orgsGroup": "Organizacje", - "noReviewer": "Brak recenzenta", - "searchReviewer": "Szukaj recenzenta..." + "noReviewer": "Brak recenzenta" }, "history": { "noHistory": "Brak historii" @@ -463,17 +462,12 @@ "title": "Ustawienia Agenta", "providerTitle": "Dostawca", "behaviorTitle": "Zachowanie", - "reviewEnabled": "Recenzja kodu", - "reviewEnabledDesc": "Agent AI sprawdza zmiany w kodzie przed ukończeniem", - "followUpEnabled": "Elementy uzupełniające", - "followUpEnabledDesc": "Automatyczne tworzenie elementu uzupełniającego, gdy recenzja wymaga zmian", "autoRetryOnFailure": "Automatyczne ponawianie", "autoRetryOnFailureDesc": "Automatycznie ponów próbę, gdy sesja zakończy się niepowodzeniem", "maxRetryCount": "Maks. liczba ponowień", "autoCreatePr": "Automatyczne tworzenie PR", "autoCreatePrDesc": "Automatycznie utwórz pull request po ukończeniu", "pendingChanges": "Zmiany zostaną zastosowane przy następnym uruchomieniu", - "disabledRequiresReview": "Wymaga włączenia recenzji", "disabledRequiresAutoRetry": "Wymaga włączenia automatycznego ponawiania", "codeAccount": "Konto kodu", "codeAccountDesc": "Wybierz lokalne konto do uruchomienia Agenta", @@ -491,19 +485,15 @@ "addSubAgent": "Dodaj Pod-Agenta...", "noSubAgents": "Brak skonfigurowanych Pod-Agentów.", "noCodeAccountError": "Wybierz konto kodu w Ustawieniach Agenta przed rozpoczęciem.", - "reviewerType": "Typ recenzenta", "reviewerSelfReview": "Samoocena", "reviewerAgent": "Agent", - "reviewerHuman": "Człowiek", - "maxReviewRounds": "Maks. liczba rund recenzji", - "maxReviewRoundsDesc": "Maksymalna liczba iteracji recenzji" + "reviewerHuman": "Człowiek" }, "agentWorkflow": { "title": "Workflow Agenta", "running": "Uruchomiony...", "completed": "Ukończone", "failed": "Niepowodzenie", - "awaitingUser": "Oczekuje na działanie", "interrupted": "Przerwane", "retry": "Ponów", "resume": "Wznów", @@ -513,8 +503,6 @@ "noWorkflowRun": "Żaden workflow Agenta nie został jeszcze uruchomiony", "confirmStart": "Uruchomić Agenta SDE dla tego zadania?", "sdePhase": "Rozwój SDE", - "reviewPhase": "Recenzja kodu", - "followUpPhase": "Uzupełnienie", "totalCost": "Łączny koszt", "totalTokens": "Łącznie Token", "proofOfWork": "Dowód pracy", @@ -523,7 +511,6 @@ "filesChanged": "Zmienione pliki", "linesAdded": "Dodane linie", "linesRemoved": "Usunięte linie", - "reviewOutcome": "Wynik recenzji", "approved": "Zatwierdzone", "changesRequested": "Wymagane zmiany", "inconclusive": "Nierozstrzygnięte", @@ -538,7 +525,6 @@ "whatNext": "Co chcesz zrobić?", "acceptAsIs": "Zaakceptuj bez zmian", "fixAndRerun": "Napraw i uruchom ponownie", - "createFollowUp": "Utwórz uzupełnienie", "roleSde": "SDE", "roleReview": "Recenzja", "roleFollowUp": "Uzupełnienie", @@ -548,7 +534,6 @@ "statusCompleted": "Ukończone", "statusFailed": "Niepowodzenie", "statusCancelled": "Anulowane", - "followUpCreated": "Utworzono uzupełnienie {{shortId}}", "sessionFiles": "Pliki", "noFilesModified": "Brak zmodyfikowanych plików", "subAgents": "Pod-Agenci", @@ -558,7 +543,6 @@ "tokens": "Token", "subAgentDefault": "Pod-Agent", "toolResult": "Wynik {{tool}}", - "reviewRequestedChanges": "Recenzja wymagała zmian", "collabLockRunning": "{{name}} is running", "collabLockHeld": "{{name}}'s agent is already working on this item", "collabLockConflict": "Another member just started an agent on this item", @@ -570,30 +554,6 @@ "agentWorking": "Agent nadal pracuje...", "reviewInEditor": "Sprawdź wszystkie w edytorze" }, - "reviewFeedback": { - "approved": "Recenzja: Zatwierdzone", - "changesRequested": "Recenzja: Wymagane zmiany", - "inconclusive": "Recenzja: Nierozstrzygnięte", - "previousReview": "Poprzednia recenzja", - "sdeAddressing": "SDE zajmuje się tymi problemami...", - "iterationHistory": "Historia iteracji ({{count}} poprzednich)", - "reviewRound": "Recenzja #{{round}}", - "archived": "Ta recenzja została zarchiwizowana przez nowszą.", - "outcomeApproved": "Zatwierdzone", - "outcomeChangesRequested": "Wymagane zmiany", - "issueCount_one": "{{count}} problem", - "issueCount_other": "{{count}} problemów", - "fixedCount": "{{count}} naprawionych", - "errors_one": "{{count}} błąd", - "errors_other": "{{count}} błędów", - "warnings_one": "{{count}} ostrzeżenie", - "warnings_other": "{{count}} ostrzeżeń", - "suggestions_one": "{{count}} sugestia", - "suggestions_other": "{{count}} sugestii", - "praises_one": "{{count}} pochwała", - "praises_other": "{{count}} pochwał", - "showFullReview": "Pokaż pełną recenzję" - }, "errors": { "noBranch": "Nie znaleziono gałęzi w dowodzie pracy", "notAuthenticated": "Nie uwierzytelniono. Zaloguj się do marketplace.", diff --git a/src/i18n/locales/pt/projects.json b/src/i18n/locales/pt/projects.json index 07f7714503..da63c8513f 100644 --- a/src/i18n/locales/pt/projects.json +++ b/src/i18n/locales/pt/projects.json @@ -360,8 +360,7 @@ "membersGroup": "Membros", "agentsGroup": "Agents", "orgsGroup": "Organizações", - "noReviewer": "Sem revisor", - "searchReviewer": "Buscar revisor..." + "noReviewer": "Sem revisor" }, "history": { "noHistory": "Nenhum histórico ainda" @@ -463,17 +462,12 @@ "title": "Configurações do Agent", "providerTitle": "Provedor", "behaviorTitle": "Comportamento", - "reviewEnabled": "Revisão de Código", - "reviewEnabledDesc": "O Agent de IA revisa as alterações de código antes da conclusão", - "followUpEnabled": "Itens de Follow-up", - "followUpEnabledDesc": "Cria automaticamente um follow-up quando a revisão solicita alterações", "autoRetryOnFailure": "Tentar Novamente Automaticamente", "autoRetryOnFailureDesc": "Tenta novamente automaticamente quando a sessão falha", "maxRetryCount": "Máximo de Tentativas", "autoCreatePr": "Criar PR Automaticamente", "autoCreatePrDesc": "Cria automaticamente um pull request ao concluir", "pendingChanges": "As alterações serão aplicadas na próxima execução", - "disabledRequiresReview": "Requer que a revisão esteja ativada", "disabledRequiresAutoRetry": "Requer que a repetição automática esteja ativada", "codeAccount": "Conta de Código", "codeAccountDesc": "Selecione a conta local para executar o Agent", @@ -491,19 +485,15 @@ "addSubAgent": "Adicionar Sub-Agent...", "noSubAgents": "Nenhum Sub-Agent configurado.", "noCodeAccountError": "Selecione uma conta de código em Configurações do Agent antes de iniciar.", - "reviewerType": "Tipo de Revisor", "reviewerSelfReview": "Auto-revisão", "reviewerAgent": "Agent", - "reviewerHuman": "Humano", - "maxReviewRounds": "Máximo de Rodadas de Revisão", - "maxReviewRoundsDesc": "Número máximo de iterações de revisão" + "reviewerHuman": "Humano" }, "agentWorkflow": { "title": "Workflow do Agent", "running": "Executando...", "completed": "Concluído", "failed": "Falhou", - "awaitingUser": "Aguardando Ação", "interrupted": "Interrompido", "retry": "Tentar novamente", "resume": "Retomar", @@ -513,8 +503,6 @@ "noWorkflowRun": "Nenhum workflow de Agent foi executado ainda", "confirmStart": "Iniciar o Agent SDE neste item de trabalho?", "sdePhase": "Desenvolvimento SDE", - "reviewPhase": "Revisão de Código", - "followUpPhase": "Follow-up", "totalCost": "Custo Total", "totalTokens": "Total de Tokens", "proofOfWork": "Prova de Trabalho", @@ -523,7 +511,6 @@ "filesChanged": "Arquivos Alterados", "linesAdded": "Linhas Adicionadas", "linesRemoved": "Linhas Removidas", - "reviewOutcome": "Resultado da Revisão", "approved": "Aprovado", "changesRequested": "Alterações Solicitadas", "inconclusive": "Inconclusivo", @@ -538,7 +525,6 @@ "whatNext": "O que você gostaria de fazer?", "acceptAsIs": "Aceitar como Está", "fixAndRerun": "Corrigir e Executar Novamente", - "createFollowUp": "Criar Follow-up", "roleSde": "SDE", "roleReview": "Revisão", "roleFollowUp": "Follow-up", @@ -548,7 +534,6 @@ "statusCompleted": "Concluído", "statusFailed": "Falhou", "statusCancelled": "Cancelado", - "followUpCreated": "Follow-up {{shortId}} criado", "sessionFiles": "Arquivos", "noFilesModified": "Nenhum arquivo modificado", "subAgents": "Sub-Agents", @@ -558,7 +543,6 @@ "tokens": "tokens", "subAgentDefault": "Sub-Agent", "toolResult": "Resultado de {{tool}}", - "reviewRequestedChanges": "Revisão solicitou alterações", "collabLockRunning": "{{name}} is running", "collabLockHeld": "{{name}}'s agent is already working on this item", "collabLockConflict": "Another member just started an agent on this item", @@ -570,30 +554,6 @@ "agentWorking": "O Agent ainda está trabalhando...", "reviewInEditor": "Revisar tudo no editor" }, - "reviewFeedback": { - "approved": "Revisão: Aprovado", - "changesRequested": "Revisão: Alterações Solicitadas", - "inconclusive": "Revisão: Inconclusiva", - "previousReview": "Revisão Anterior", - "sdeAddressing": "O SDE está tratando destes problemas...", - "iterationHistory": "Histórico de Iterações ({{count}} anteriores)", - "reviewRound": "Revisão #{{round}}", - "archived": "Esta revisão foi arquivada por uma revisão posterior.", - "outcomeApproved": "Aprovado", - "outcomeChangesRequested": "Alterações Solicitadas", - "issueCount_one": "{{count}} problema", - "issueCount_other": "{{count}} problemas", - "fixedCount": "{{count}} corrigidos", - "errors_one": "{{count}} Erro", - "errors_other": "{{count}} Erros", - "warnings_one": "{{count}} Aviso", - "warnings_other": "{{count}} Avisos", - "suggestions_one": "{{count}} Sugestão", - "suggestions_other": "{{count}} Sugestões", - "praises_one": "{{count}} Elogio", - "praises_other": "{{count}} Elogios", - "showFullReview": "Mostrar revisão completa" - }, "errors": { "noBranch": "Nenhuma branch encontrada na prova de trabalho", "notAuthenticated": "Não autenticado. Faça login no marketplace.", diff --git a/src/i18n/locales/ru/projects.json b/src/i18n/locales/ru/projects.json index 8d5a4aa1d7..d282e2b4cf 100644 --- a/src/i18n/locales/ru/projects.json +++ b/src/i18n/locales/ru/projects.json @@ -360,8 +360,7 @@ "membersGroup": "Участники", "agentsGroup": "Agents", "orgsGroup": "Организации", - "noReviewer": "Нет ревьюера", - "searchReviewer": "Поиск ревьюера..." + "noReviewer": "Нет ревьюера" }, "history": { "noHistory": "История пока пуста" @@ -463,17 +462,12 @@ "title": "Настройки Agent", "providerTitle": "Провайдер", "behaviorTitle": "Поведение", - "reviewEnabled": "Code Review", - "reviewEnabledDesc": "AI Agent проверяет изменения кода перед завершением", - "followUpEnabled": "Дополнительные задачи", - "followUpEnabledDesc": "Автоматически создавать задачу когда ревью запрашивает изменения", "autoRetryOnFailure": "Автоматический повтор", "autoRetryOnFailureDesc": "Автоматически повторять при сбое сессии", "maxRetryCount": "Макс. повторов", "autoCreatePr": "Автосоздание PR", "autoCreatePrDesc": "Автоматически создавать Pull Request при завершении", "pendingChanges": "Изменения будут применены при следующем запуске", - "disabledRequiresReview": "Требуется включение ревью кода", "disabledRequiresAutoRetry": "Требуется включение автоповтора", "codeAccount": "Аккаунт кода", "codeAccountDesc": "Выберите локальный аккаунт для запуска Agent", @@ -491,19 +485,15 @@ "addSubAgent": "Добавить Sub-Agent...", "noSubAgents": "Sub-Agents не настроены.", "noCodeAccountError": "Пожалуйста, выберите учётную запись кода в настройках Agent перед началом.", - "reviewerType": "Тип ревьюера", "reviewerSelfReview": "Самопроверка", "reviewerAgent": "Agent", - "reviewerHuman": "Ручная проверка", - "maxReviewRounds": "Макс. раундов проверки", - "maxReviewRoundsDesc": "Максимальное количество итераций проверки" + "reviewerHuman": "Ручная проверка" }, "agentWorkflow": { "title": "Agent Workflow", "running": "Выполняется...", "completed": "Завершено", "failed": "Ошибка", - "awaitingUser": "Ожидание действия", "interrupted": "Прервано", "retry": "Повторить", "resume": "Возобновить", @@ -513,8 +503,6 @@ "noWorkflowRun": "Agent Workflow ещё не запускался", "confirmStart": "Запустить SDE Agent для этой задачи?", "sdePhase": "SDE Разработка", - "reviewPhase": "Code Review", - "followUpPhase": "Доработка", "totalCost": "Общая стоимость", "totalTokens": "Всего Token", "proofOfWork": "Результат работы", @@ -523,7 +511,6 @@ "filesChanged": "Изменённые файлы", "linesAdded": "Добавленные строки", "linesRemoved": "Удалённые строки", - "reviewOutcome": "Результат ревью", "approved": "Одобрено", "changesRequested": "Запрошены изменения", "inconclusive": "Неопределённо", @@ -538,7 +525,6 @@ "whatNext": "Что вы хотите сделать?", "acceptAsIs": "Принять Как Есть", "fixAndRerun": "Исправить и Перезапустить", - "createFollowUp": "Создать Продолжение", "roleSde": "SDE", "roleReview": "Review", "roleFollowUp": "Follow-up", @@ -548,7 +534,6 @@ "statusCompleted": "Завершено", "statusFailed": "Ошибка", "statusCancelled": "Отменено", - "followUpCreated": "Follow-up {{shortId}} создан", "sessionFiles": "Файлы", "noFilesModified": "Нет изменённых файлов", "subAgents": "Sub-Agents", @@ -558,7 +543,6 @@ "tokens": "Token", "subAgentDefault": "Sub-Agent", "toolResult": "результат {{tool}}", - "reviewRequestedChanges": "Review запросил изменения", "collabLockRunning": "{{name}} is running", "collabLockHeld": "{{name}}'s agent is already working on this item", "collabLockConflict": "Another member just started an agent on this item", @@ -570,30 +554,6 @@ "agentWorking": "Agent ещё работает...", "reviewInEditor": "Просмотреть всё в редакторе" }, - "reviewFeedback": { - "approved": "Review: Одобрено", - "changesRequested": "Review: Требуются Изменения", - "inconclusive": "Review: Не Определено", - "previousReview": "Предыдущее Review", - "sdeAddressing": "SDE решает эти проблемы...", - "iterationHistory": "История итераций ({{count}} предыдущих)", - "reviewRound": "Review #{{round}}", - "archived": "Это Review было архивировано более поздним Review.", - "outcomeApproved": "Одобрено", - "outcomeChangesRequested": "Требуются изменения", - "issueCount_one": "{{count}} проблема", - "issueCount_other": "{{count}} проблем", - "fixedCount": "{{count}} исправлено", - "errors_one": "{{count}} ошибка", - "errors_other": "{{count}} ошибок", - "warnings_one": "{{count}} предупреждение", - "warnings_other": "{{count}} предупреждений", - "suggestions_one": "{{count}} предложение", - "suggestions_other": "{{count}} предложений", - "praises_one": "{{count}} похвала", - "praises_other": "{{count}} похвал", - "showFullReview": "Показать полный Review" - }, "errors": { "noBranch": "Ветка не найдена в доказательстве работы", "notAuthenticated": "Не авторизован. Пожалуйста, войдите на маркетплейс.", diff --git a/src/i18n/locales/tr/projects.json b/src/i18n/locales/tr/projects.json index 49c8f6f410..b2074e81a6 100644 --- a/src/i18n/locales/tr/projects.json +++ b/src/i18n/locales/tr/projects.json @@ -360,8 +360,7 @@ "membersGroup": "Üyeler", "agentsGroup": "Agents", "orgsGroup": "Organizasyonlar", - "noReviewer": "Gözden geçiren yok", - "searchReviewer": "Gözden geçiren ara..." + "noReviewer": "Gözden geçiren yok" }, "history": { "noHistory": "Henüz geçmiş yok" @@ -463,17 +462,12 @@ "title": "Agent Ayarları", "providerTitle": "Sağlayıcı", "behaviorTitle": "Davranış", - "reviewEnabled": "Kod İnceleme", - "reviewEnabledDesc": "AI Agent tamamlamadan önce kod değişikliklerini inceler", - "followUpEnabled": "Takip Öğeleri", - "followUpEnabledDesc": "İnceleme değişiklik istediğinde otomatik takip oluştur", "autoRetryOnFailure": "Otomatik Yeniden Deneme", "autoRetryOnFailureDesc": "Oturum başarısız olduğunda otomatik yeniden dene", "maxRetryCount": "Maks. Yeniden Deneme", "autoCreatePr": "Otomatik PR Oluştur", "autoCreatePrDesc": "Tamamlandığında otomatik Pull Request oluştur", "pendingChanges": "Değişiklikler bir sonraki çalıştırmada uygulanacak", - "disabledRequiresReview": "Kod incelemenin etkin olması gerekir", "disabledRequiresAutoRetry": "Otomatik yeniden denemenin etkin olması gerekir", "codeAccount": "Kod Hesabı", "codeAccountDesc": "Agent'ı çalıştırmak için yerel hesap seçin", @@ -491,19 +485,15 @@ "addSubAgent": "Sub-Agent Ekle...", "noSubAgents": "Yapılandırılmış sub-agent yok.", "noCodeAccountError": "Başlamadan önce Agent Ayarlarında bir kod hesabı seçin.", - "reviewerType": "İnceleyici türü", "reviewerSelfReview": "Öz inceleme", "reviewerAgent": "Agent", - "reviewerHuman": "Manuel inceleme", - "maxReviewRounds": "Maks. inceleme turu", - "maxReviewRoundsDesc": "Maksimum inceleme yineleme sayısı" + "reviewerHuman": "Manuel inceleme" }, "agentWorkflow": { "title": "Agent İş Akışı", "running": "Çalışıyor...", "completed": "Tamamlandı", "failed": "Başarısız", - "awaitingUser": "İşlem Bekleniyor", "interrupted": "Kesintiye Uğradı", "retry": "Yeniden Dene", "resume": "Devam Et", @@ -513,8 +503,6 @@ "noWorkflowRun": "Henüz Agent iş akışı çalıştırılmadı", "confirmStart": "Bu iş öğesinde SDE Agent başlatılsın mı?", "sdePhase": "SDE Geliştirme", - "reviewPhase": "Kod İnceleme", - "followUpPhase": "Takip", "totalCost": "Toplam Maliyet", "totalTokens": "Toplam Token", "proofOfWork": "Çalışma Kanıtı", @@ -523,7 +511,6 @@ "filesChanged": "Değişen Dosyalar", "linesAdded": "Eklenen Satırlar", "linesRemoved": "Silinen Satırlar", - "reviewOutcome": "İnceleme Sonucu", "approved": "Onaylandı", "changesRequested": "Değişiklik İstendi", "inconclusive": "Belirsiz", @@ -538,7 +525,6 @@ "whatNext": "Ne yapmak istersiniz?", "acceptAsIs": "Olduğu Gibi Kabul Et", "fixAndRerun": "Düzelt ve Yeniden Çalıştır", - "createFollowUp": "Takip Oluştur", "roleSde": "SDE", "roleReview": "Review", "roleFollowUp": "Follow-up", @@ -548,7 +534,6 @@ "statusCompleted": "Tamamlandı", "statusFailed": "Başarısız", "statusCancelled": "İptal Edildi", - "followUpCreated": "Follow-up {{shortId}} oluşturuldu", "sessionFiles": "Dosyalar", "noFilesModified": "Değiştirilen dosya yok", "subAgents": "Sub-Agents", @@ -558,7 +543,6 @@ "tokens": "Token", "subAgentDefault": "Sub-Agent", "toolResult": "{{tool}} sonucu", - "reviewRequestedChanges": "Review değişiklik istedi", "collabLockRunning": "{{name}} is running", "collabLockHeld": "{{name}}'s agent is already working on this item", "collabLockConflict": "Another member just started an agent on this item", @@ -570,30 +554,6 @@ "agentWorking": "Agent hala çalışıyor...", "reviewInEditor": "Tümünü editörde incele" }, - "reviewFeedback": { - "approved": "Review: Onaylandı", - "changesRequested": "Review: Değişiklik İstendi", - "inconclusive": "Review: Belirsiz", - "previousReview": "Önceki Review", - "sdeAddressing": "SDE bu sorunları çözüyor...", - "iterationHistory": "İterasyon Geçmişi ({{count}} önceki)", - "reviewRound": "Review #{{round}}", - "archived": "Bu review daha sonraki bir review tarafından arşivlendi.", - "outcomeApproved": "Onaylandı", - "outcomeChangesRequested": "Değişiklik istendi", - "issueCount_one": "{{count}} sorun", - "issueCount_other": "{{count}} sorun", - "fixedCount": "{{count}} düzeltildi", - "errors_one": "{{count}} Hata", - "errors_other": "{{count}} Hata", - "warnings_one": "{{count}} Uyarı", - "warnings_other": "{{count}} Uyarı", - "suggestions_one": "{{count}} Öneri", - "suggestions_other": "{{count}} Öneri", - "praises_one": "{{count}} Övgü", - "praises_other": "{{count}} Övgü", - "showFullReview": "Tam Review'ı göster" - }, "errors": { "noBranch": "Çalışma kanıtında dal bulunamadı", "notAuthenticated": "Kimlik doğrulanmadı. Lütfen marketplace'e giriş yapın.", diff --git a/src/i18n/locales/vi/projects.json b/src/i18n/locales/vi/projects.json index 946af828b2..b21bf576c4 100644 --- a/src/i18n/locales/vi/projects.json +++ b/src/i18n/locales/vi/projects.json @@ -360,8 +360,7 @@ "membersGroup": "Thành viên", "agentsGroup": "Agents", "orgsGroup": "Tổ chức", - "noReviewer": "Không có người đánh giá", - "searchReviewer": "Tìm người đánh giá..." + "noReviewer": "Không có người đánh giá" }, "history": { "noHistory": "Chưa có lịch sử" @@ -463,17 +462,12 @@ "title": "Cài đặt Agent", "providerTitle": "Nhà cung cấp", "behaviorTitle": "Hành vi", - "reviewEnabled": "Code Review", - "reviewEnabledDesc": "AI Agent đánh giá các thay đổi code trước khi hoàn tất", - "followUpEnabled": "Mục theo dõi", - "followUpEnabledDesc": "Tự động tạo mục theo dõi khi đánh giá yêu cầu sửa đổi", "autoRetryOnFailure": "Tự động thử lại", "autoRetryOnFailureDesc": "Tự động thử lại khi phiên thất bại", "maxRetryCount": "Số lần thử lại tối đa", "autoCreatePr": "Tự động tạo PR", "autoCreatePrDesc": "Tự động tạo Pull Request khi hoàn tất", "pendingChanges": "Thay đổi sẽ được áp dụng trong lần chạy tiếp theo", - "disabledRequiresReview": "Cần bật code review", "disabledRequiresAutoRetry": "Cần bật tự động thử lại", "codeAccount": "Tài khoản Code", "codeAccountDesc": "Chọn tài khoản cục bộ để chạy Agent", @@ -491,19 +485,15 @@ "addSubAgent": "Thêm Sub-Agent...", "noSubAgents": "Chưa cấu hình sub-agent.", "noCodeAccountError": "Vui lòng chọn tài khoản mã trong cài đặt Agent trước khi bắt đầu.", - "reviewerType": "Loại người đánh giá", "reviewerSelfReview": "Tự đánh giá", "reviewerAgent": "Agent", - "reviewerHuman": "Đánh giá thủ công", - "maxReviewRounds": "Số vòng đánh giá tối đa", - "maxReviewRoundsDesc": "Số vòng lặp đánh giá tối đa" + "reviewerHuman": "Đánh giá thủ công" }, "agentWorkflow": { "title": "Agent Workflow", "running": "Đang chạy...", "completed": "Hoàn tất", "failed": "Thất bại", - "awaitingUser": "Chờ thao tác", "interrupted": "Đã gián đoạn", "retry": "Thử lại", "resume": "Tiếp tục", @@ -513,8 +503,6 @@ "noWorkflowRun": "Chưa có Agent workflow nào được chạy", "confirmStart": "Bắt đầu SDE Agent cho mục công việc này?", "sdePhase": "Phát triển SDE", - "reviewPhase": "Code Review", - "followUpPhase": "Theo dõi", "totalCost": "Tổng chi phí", "totalTokens": "Tổng Token", "proofOfWork": "Kết quả công việc", @@ -523,7 +511,6 @@ "filesChanged": "Tệp thay đổi", "linesAdded": "Dòng thêm", "linesRemoved": "Dòng xóa", - "reviewOutcome": "Kết quả đánh giá", "approved": "Đã phê duyệt", "changesRequested": "Yêu cầu sửa đổi", "inconclusive": "Chưa xác định", @@ -538,7 +525,6 @@ "whatNext": "Bạn muốn làm gì tiếp theo?", "acceptAsIs": "Chấp Nhận Như Hiện Tại", "fixAndRerun": "Sửa và Chạy Lại", - "createFollowUp": "Tạo Công Việc Tiếp Theo", "roleSde": "SDE", "roleReview": "Review", "roleFollowUp": "Follow-up", @@ -548,7 +534,6 @@ "statusCompleted": "Hoàn thành", "statusFailed": "Thất bại", "statusCancelled": "Đã hủy", - "followUpCreated": "Follow-up {{shortId}} đã tạo", "sessionFiles": "Tệp", "noFilesModified": "Không có tệp nào được sửa đổi", "subAgents": "Sub-Agents", @@ -558,7 +543,6 @@ "tokens": "Token", "subAgentDefault": "Sub-Agent", "toolResult": "kết quả {{tool}}", - "reviewRequestedChanges": "Review yêu cầu thay đổi", "collabLockRunning": "{{name}} is running", "collabLockHeld": "{{name}}'s agent is already working on this item", "collabLockConflict": "Another member just started an agent on this item", @@ -570,30 +554,6 @@ "agentWorking": "Agent vẫn đang làm việc...", "reviewInEditor": "Xem lại tất cả trong trình soạn thảo" }, - "reviewFeedback": { - "approved": "Review: Đã Duyệt", - "changesRequested": "Review: Yêu Cầu Thay Đổi", - "inconclusive": "Review: Chưa Xác Định", - "previousReview": "Review Trước", - "sdeAddressing": "SDE đang xử lý các vấn đề này...", - "iterationHistory": "Lịch Sử Lặp ({{count}} lần trước)", - "reviewRound": "Review #{{round}}", - "archived": "Review này đã được lưu trữ bởi review sau đó.", - "outcomeApproved": "Đã phê duyệt", - "outcomeChangesRequested": "Yêu cầu thay đổi", - "issueCount_one": "{{count}} vấn đề", - "issueCount_other": "{{count}} vấn đề", - "fixedCount": "{{count}} đã sửa", - "errors_one": "{{count}} Lỗi", - "errors_other": "{{count}} Lỗi", - "warnings_one": "{{count}} Cảnh báo", - "warnings_other": "{{count}} Cảnh báo", - "suggestions_one": "{{count}} Gợi ý", - "suggestions_other": "{{count}} Gợi ý", - "praises_one": "{{count}} Khen ngợi", - "praises_other": "{{count}} Khen ngợi", - "showFullReview": "Hiển thị Review đầy đủ" - }, "errors": { "noBranch": "Không tìm thấy nhánh trong bằng chứng công việc", "notAuthenticated": "Chưa xác thực. Vui lòng đăng nhập Marketplace.", diff --git a/src/i18n/locales/zh-Hant/projects.json b/src/i18n/locales/zh-Hant/projects.json index f36b3479e9..a014384c19 100644 --- a/src/i18n/locales/zh-Hant/projects.json +++ b/src/i18n/locales/zh-Hant/projects.json @@ -360,8 +360,7 @@ "membersGroup": "成員", "agentsGroup": "Agents", "orgsGroup": "組織", - "noReviewer": "無審查人", - "searchReviewer": "搜索審查人..." + "noReviewer": "無審查人" }, "history": { "noHistory": "暫無歷史記錄" @@ -463,17 +462,12 @@ "title": "Agent 設置", "providerTitle": "提供商", "behaviorTitle": "行爲", - "reviewEnabled": "代碼審查", - "reviewEnabledDesc": "AI Agent 在完成前審查代碼變更", - "followUpEnabled": "後續項", - "followUpEnabledDesc": "審查請求修改時自動創建後續項", "autoRetryOnFailure": "自動重試", "autoRetryOnFailureDesc": "會話失敗時自動重試", "maxRetryCount": "最大重試次數", "autoCreatePr": "自動創建 PR", "autoCreatePrDesc": "完成後自動創建 Pull Request", "pendingChanges": "更改將在下次運行時生效", - "disabledRequiresReview": "需要啓用代碼審查", "disabledRequiresAutoRetry": "需要啓用自動重試", "codeAccount": "代碼賬戶", "codeAccountDesc": "選擇運行 Agent 的本地賬戶", @@ -491,19 +485,15 @@ "addSubAgent": "添加 Sub-Agent...", "noSubAgents": "未配置 Sub-Agent。", "noCodeAccountError": "請在 Agent 設置中選擇代碼賬戶後再啓動。", - "reviewerType": "審查方式", "reviewerSelfReview": "自我審查", "reviewerAgent": "Agent", - "reviewerHuman": "人工審查", - "maxReviewRounds": "最大審查輪次", - "maxReviewRoundsDesc": "審查迭代的最大次數" + "reviewerHuman": "人工審查" }, "agentWorkflow": { "title": "Agent 工作流", "running": "運行中...", "completed": "已完成", "failed": "失敗", - "awaitingUser": "等待操作", "interrupted": "已中斷", "retry": "重試", "resume": "恢復", @@ -513,8 +503,6 @@ "noWorkflowRun": "尚未運行 Agent 工作流", "confirmStart": "在此工作項上啓動 SDE Agent?", "sdePhase": "SDE 開發", - "reviewPhase": "代碼審查", - "followUpPhase": "後續處理", "totalCost": "總費用", "totalTokens": "總 Token", "proofOfWork": "工作成果", @@ -523,7 +511,6 @@ "filesChanged": "變更文件數", "linesAdded": "新增行數", "linesRemoved": "刪除行數", - "reviewOutcome": "審查結果", "approved": "已批准", "changesRequested": "請求修改", "inconclusive": "未確定", @@ -538,7 +525,6 @@ "whatNext": "下一步做什麼?", "acceptAsIs": "直接接受", "fixAndRerun": "修復並重新運行", - "createFollowUp": "創建後續任務", "roleSde": "SDE", "roleReview": "Review", "roleFollowUp": "Follow-up", @@ -548,7 +534,6 @@ "statusCompleted": "已完成", "statusFailed": "失敗", "statusCancelled": "已取消", - "followUpCreated": "Follow-up {{shortId}} 已創建", "sessionFiles": "文件", "noFilesModified": "沒有修改文件", "subAgents": "Sub-Agents", @@ -558,7 +543,6 @@ "tokens": "Token", "subAgentDefault": "Sub-Agent", "toolResult": "{{tool}} 結果", - "reviewRequestedChanges": "Review 請求修改", "collabLockRunning": "{{name}} is running", "collabLockHeld": "{{name}}'s agent is already working on this item", "collabLockConflict": "Another member just started an agent on this item", @@ -570,30 +554,6 @@ "agentWorking": "Agent 仍在工作中...", "reviewInEditor": "在編輯器中審閱全部" }, - "reviewFeedback": { - "approved": "Review: 已通過", - "changesRequested": "Review: 需要修改", - "inconclusive": "Review: 未確定", - "previousReview": "上一輪 Review", - "sdeAddressing": "SDE 正在處理這些問題...", - "iterationHistory": "迭代歷史 ({{count}} 輪歷史)", - "reviewRound": "Review #{{round}}", - "archived": "此 Review 已被後續 Review 封存。", - "outcomeApproved": "已批准", - "outcomeChangesRequested": "需要修改", - "issueCount_one": "{{count}} 個問題", - "issueCount_other": "{{count}} 個問題", - "fixedCount": "{{count}} 項已修復", - "errors_one": "{{count}} 個錯誤", - "errors_other": "{{count}} 個錯誤", - "warnings_one": "{{count}} 個警告", - "warnings_other": "{{count}} 個警告", - "suggestions_one": "{{count}} 個建議", - "suggestions_other": "{{count}} 個建議", - "praises_one": "{{count}} 個贊", - "praises_other": "{{count}} 個贊", - "showFullReview": "顯示完整 Review" - }, "errors": { "noBranch": "工作證明中未找到分支", "notAuthenticated": "未認證。請登錄 Marketplace。", diff --git a/src/i18n/locales/zh/projects.json b/src/i18n/locales/zh/projects.json index a506faa7f9..b12f6a9ad6 100644 --- a/src/i18n/locales/zh/projects.json +++ b/src/i18n/locales/zh/projects.json @@ -360,8 +360,7 @@ "membersGroup": "成员", "agentsGroup": "Agents", "orgsGroup": "组织", - "noReviewer": "无审查人", - "searchReviewer": "搜索审查人..." + "noReviewer": "无审查人" }, "history": { "noHistory": "暂无历史记录" @@ -479,17 +478,12 @@ "title": "Agent 设置", "providerTitle": "提供商", "behaviorTitle": "行为", - "reviewEnabled": "代码审查", - "reviewEnabledDesc": "AI Agent 在完成前审查代码变更", - "followUpEnabled": "后续项", - "followUpEnabledDesc": "审查请求修改时自动创建后续项", "autoRetryOnFailure": "自动重试", "autoRetryOnFailureDesc": "会话失败时自动重试", "maxRetryCount": "最大重试次数", "autoCreatePr": "自动创建 PR", "autoCreatePrDesc": "完成后自动创建 Pull Request", "pendingChanges": "更改将在下次运行时生效", - "disabledRequiresReview": "需要启用代码审查", "disabledRequiresAutoRetry": "需要启用自动重试", "codeAccount": "代码账户", "codeAccountDesc": "选择运行 Agent 的本地账户", @@ -507,19 +501,15 @@ "addSubAgent": "添加 Sub-Agent...", "noSubAgents": "未配置 Sub-Agent。", "noCodeAccountError": "请在 Agent 设置中选择代码账户后再启动。", - "reviewerType": "审查方式", "reviewerSelfReview": "自我审查", "reviewerAgent": "Agent", - "reviewerHuman": "人工审查", - "maxReviewRounds": "最大审查轮次", - "maxReviewRoundsDesc": "审查迭代的最大次数" + "reviewerHuman": "人工审查" }, "agentWorkflow": { "title": "Agent 工作流", "running": "运行中...", "completed": "已完成", "failed": "失败", - "awaitingUser": "等待操作", "interrupted": "已中断", "retry": "重试", "resume": "恢复", @@ -529,8 +519,6 @@ "noWorkflowRun": "尚未运行 Agent 工作流", "confirmStart": "在此工作项上启动 SDE Agent?", "sdePhase": "SDE 开发", - "reviewPhase": "代码审查", - "followUpPhase": "后续处理", "totalCost": "总费用", "totalTokens": "总 Token", "proofOfWork": "工作成果", @@ -539,7 +527,6 @@ "filesChanged": "变更文件数", "linesAdded": "新增行数", "linesRemoved": "删除行数", - "reviewOutcome": "审查结果", "approved": "已批准", "changesRequested": "请求修改", "inconclusive": "未确定", @@ -554,7 +541,6 @@ "whatNext": "下一步做什么?", "acceptAsIs": "直接接受", "fixAndRerun": "修复并重新运行", - "createFollowUp": "创建后续任务", "roleSde": "SDE", "roleReview": "Review", "roleFollowUp": "Follow-up", @@ -564,7 +550,6 @@ "statusCompleted": "已完成", "statusFailed": "失败", "statusCancelled": "已取消", - "followUpCreated": "Follow-up {{shortId}} 已创建", "sessionFiles": "文件", "noFilesModified": "没有修改文件", "subAgents": "Sub-Agents", @@ -574,7 +559,6 @@ "tokens": "Token", "subAgentDefault": "Sub-Agent", "toolResult": "{{tool}} 结果", - "reviewRequestedChanges": "Review 请求修改", "collabLockRunning": "{{name}} 正在处理", "collabLockHeld": "{{name}} 的 agent 正在处理该工作项", "collabLockConflict": "另一位成员刚刚在该工作项上启动了 agent", @@ -586,30 +570,6 @@ "agentWorking": "Agent 仍在工作中...", "reviewInEditor": "在编辑器中审阅全部" }, - "reviewFeedback": { - "approved": "Review: 已通过", - "changesRequested": "Review: 需要修改", - "inconclusive": "Review: 未确定", - "previousReview": "上一轮 Review", - "sdeAddressing": "SDE 正在处理这些问题...", - "iterationHistory": "迭代历史 ({{count}} 轮历史)", - "reviewRound": "Review #{{round}}", - "archived": "此 Review 已被后续 Review 归档。", - "outcomeApproved": "已批准", - "outcomeChangesRequested": "需要修改", - "issueCount_one": "{{count}} 个问题", - "issueCount_other": "{{count}} 个问题", - "fixedCount": "{{count}} 项已修复", - "errors_one": "{{count}} 个错误", - "errors_other": "{{count}} 个错误", - "warnings_one": "{{count}} 个警告", - "warnings_other": "{{count}} 个警告", - "suggestions_one": "{{count}} 个建议", - "suggestions_other": "{{count}} 个建议", - "praises_one": "{{count}} 个赞", - "praises_other": "{{count}} 个赞", - "showFullReview": "显示完整 Review" - }, "errors": { "noBranch": "工作证明中未找到分支", "notAuthenticated": "未认证。请登录 Marketplace。", From 508078d679880001c90802c1cdb153a7cf9eb118 Mon Sep 17 00:00:00 2001 From: Neonforge <48338160+Neonforge98@users.noreply.github.com> Date: Fri, 14 Aug 2026 17:57:38 -0700 Subject: [PATCH 5/7] refactor(work-items): drop reviewer property field Pre-commit hook ran. Total eslint: 4, total circular: 0 --- .../InlineCreateWorkItemFields.tsx | 1 - .../WorkItemProperties/PeopleSection.tsx | 46 +----- .../WorkItemProperties/ReviewerDropdown.tsx | 135 ------------------ .../components/WorkItemProperties/index.tsx | 2 - .../components/WorkItemProperties/types.ts | 9 +- .../useWorkItemPropertyHandlers.ts | 69 --------- 6 files changed, 3 insertions(+), 259 deletions(-) delete mode 100644 src/modules/ProjectManager/WorkItems/components/WorkItemProperties/ReviewerDropdown.tsx diff --git a/src/modules/ProjectManager/WorkItems/components/CreateWorkItemView/InlineCreateWorkItemFields.tsx b/src/modules/ProjectManager/WorkItems/components/CreateWorkItemView/InlineCreateWorkItemFields.tsx index e43d5b2ea7..721c9d6101 100644 --- a/src/modules/ProjectManager/WorkItems/components/CreateWorkItemView/InlineCreateWorkItemFields.tsx +++ b/src/modules/ProjectManager/WorkItems/components/CreateWorkItemView/InlineCreateWorkItemFields.tsx @@ -62,7 +62,6 @@ export const CREATE_WORK_ITEM_VISIBLE_FIELDS: WorkItemPropertyFieldKey[] = [ "status", "priority", "assignee", - "reviewer", "milestone", "startDate", "date", diff --git a/src/modules/ProjectManager/WorkItems/components/WorkItemProperties/PeopleSection.tsx b/src/modules/ProjectManager/WorkItems/components/WorkItemProperties/PeopleSection.tsx index 1be4cd74cc..1094fbbceb 100644 --- a/src/modules/ProjectManager/WorkItems/components/WorkItemProperties/PeopleSection.tsx +++ b/src/modules/ProjectManager/WorkItems/components/WorkItemProperties/PeopleSection.tsx @@ -1,10 +1,4 @@ -import { ScanEye } from "lucide-react"; - -import { DROPDOWN_ITEM } from "@src/components/Dropdown/tokens"; -import { - FieldRow, - type FieldRowVariant, -} from "@src/components/PropertyField/PropertyFieldEditable"; +import type { FieldRowVariant } from "@src/components/PropertyField/PropertyFieldEditable"; import type { AgentDefinition, OrgMember, @@ -13,7 +7,6 @@ import type { Person } from "@src/types/core/shared"; import type { WorkItem as WorkItemExtended } from "@src/types/core/workItem"; import { AssigneePropertyField } from "./AssigneePropertyField"; -import { ReviewerDropdown } from "./ReviewerDropdown"; import type { WorkItemExternalAssigneeConfig, WorkItemPropertyFieldKey, @@ -52,8 +45,7 @@ export function PeopleSection({ externalAssigneeConfig, }: PeopleSectionProps) { const showAssignee = !visibleFields || visibleFields.has("assignee"); - const showReviewer = !visibleFields || visibleFields.has("reviewer"); - if (!showAssignee && !showReviewer) return null; + if (!showAssignee) return null; return ( <> @@ -74,40 +66,6 @@ export function PeopleSection({ externalConfig={externalAssigneeConfig} /> )} - - {showReviewer && ( -
- } - value={handlers.getReviewerDisplay()} - isSelected={!!handlers.currentReviewer} - isActive={openPicker === "reviewer"} - variant={fieldVariant} - onClear={() => handlers.handleReviewerChange(null)} - onClick={() => togglePicker("reviewer")} - /> - {openPicker === "reviewer" && ( - - )} -
- )} ); } diff --git a/src/modules/ProjectManager/WorkItems/components/WorkItemProperties/ReviewerDropdown.tsx b/src/modules/ProjectManager/WorkItems/components/WorkItemProperties/ReviewerDropdown.tsx deleted file mode 100644 index 693230e4d0..0000000000 --- a/src/modules/ProjectManager/WorkItems/components/WorkItemProperties/ReviewerDropdown.tsx +++ /dev/null @@ -1,135 +0,0 @@ -import { AtSign, ScanEye, User } from "lucide-react"; -import React from "react"; - -import type { ReviewerRefType } from "@src/api/http/project"; -import Avatar from "@src/components/Avatar"; -import { - DROPDOWN_CLASSES, - DROPDOWN_ITEM, -} from "@src/components/Dropdown/tokens"; -import { - type FieldRowVariant, - Option, - SearchableDropdown, -} from "@src/components/PropertyField/PropertyFieldEditable"; -import type { Person } from "@src/types/core/shared"; - -interface ReviewerRef { - type?: string; - id?: string; -} - -interface ReviewerDropdownProps { - allAgentList: { id: string; name: string }[]; - availableMembers: Person[]; - currentReviewer: ReviewerRef | undefined; - onReviewerChange: ( - reviewerType: ReviewerRefType | null, - reviewerId?: string - ) => void; - t: (key: string) => string; - fieldVariant?: FieldRowVariant; -} - -export const ReviewerDropdown: React.FC = ({ - allAgentList, - availableMembers, - currentReviewer, - onReviewerChange, - t, - fieldVariant = "row", -}) => ( - - {(searchQuery) => { - const query = searchQuery?.toLowerCase() ?? ""; - const filteredAgents = query - ? allAgentList.filter((agent) => - agent.name.toLowerCase().includes(query) - ) - : allAgentList; - const filteredMembers = query - ? availableMembers.filter((person) => - person.name.toLowerCase().includes(query) - ) - : availableMembers; - - return ( - <> - - ); - })} - {filteredMembers.length > 0 && ( -
- {t("workItems.properties.membersGroup")} -
- )} - {filteredMembers.map((person) => { - const isSelected = - currentReviewer?.type === "human" && - currentReviewer?.id === person.id; - return ( - - ); - })} - - ); - }} -
-); diff --git a/src/modules/ProjectManager/WorkItems/components/WorkItemProperties/index.tsx b/src/modules/ProjectManager/WorkItems/components/WorkItemProperties/index.tsx index b14270827b..92b2df7776 100644 --- a/src/modules/ProjectManager/WorkItems/components/WorkItemProperties/index.tsx +++ b/src/modules/ProjectManager/WorkItems/components/WorkItemProperties/index.tsx @@ -95,7 +95,6 @@ export const WORK_ITEM_THREAD_PROPERTY_FIELDS: WorkItemPropertyFieldKey[] = [ "status", "priority", "assignee", - "reviewer", "date", ]; @@ -104,7 +103,6 @@ const DEFAULT_VISIBLE_FIELDS: WorkItemPropertyFieldKey[] = [ "status", "priority", "assignee", - "reviewer", "milestone", "startDate", "date", diff --git a/src/modules/ProjectManager/WorkItems/components/WorkItemProperties/types.ts b/src/modules/ProjectManager/WorkItems/components/WorkItemProperties/types.ts index 72150c07fb..99536e4604 100644 --- a/src/modules/ProjectManager/WorkItems/components/WorkItemProperties/types.ts +++ b/src/modules/ProjectManager/WorkItems/components/WorkItemProperties/types.ts @@ -1,4 +1,4 @@ -import type { ReviewerRefType, WorkItemSchedule } from "@src/api/http/project"; +import type { WorkItemSchedule } from "@src/api/http/project"; import type { FieldRowVariant } from "@src/components/PropertyField/PropertyFieldEditable"; import type { AgentDefinition, @@ -18,7 +18,6 @@ export type WorkItemPropertyPicker = | "status" | "priority" | "assignee" - | "reviewer" | "project" | "milestone" | "startDate" @@ -96,15 +95,9 @@ export interface WorkItemPropertiesProps { export interface WorkItemPropertyHandlers { allAgentList: { id: string; name: string }[]; - currentReviewer: unknown; handleStatusChange: (value: WorkItemStatus) => void; handlePriorityChange: (value: WorkItemPriority) => void; handleAssigneeChange: (person: Person | null, assigneeType?: string) => void; - handleReviewerChange: ( - reviewerType: ReviewerRefType | null, - reviewerId?: string - ) => void; - getReviewerDisplay: () => string; handleScheduleChange: (schedule: WorkItemSchedule | null) => void; handleLabelToggle: (label: WorkItemLabel) => void; handleLabelsClear: () => void; diff --git a/src/modules/ProjectManager/WorkItems/components/WorkItemProperties/useWorkItemPropertyHandlers.ts b/src/modules/ProjectManager/WorkItems/components/WorkItemProperties/useWorkItemPropertyHandlers.ts index e18daec78a..72a7ab1f0a 100644 --- a/src/modules/ProjectManager/WorkItems/components/WorkItemProperties/useWorkItemPropertyHandlers.ts +++ b/src/modules/ProjectManager/WorkItems/components/WorkItemProperties/useWorkItemPropertyHandlers.ts @@ -3,7 +3,6 @@ import { useCallback, useMemo } from "react"; import type { OrchestratorConfig, - ReviewerRefType, WorkItemSchedule, } from "@src/api/http/project"; import { builtInAgentsAtom } from "@src/modules/MainApp/AgentOrgs/store/builtInAgentsAtom"; @@ -48,9 +47,6 @@ export function useWorkItemPropertyHandlers({ }: UseWorkItemPropertyHandlersParams) { const builtInAgents = useAtomValue(builtInAgentsAtom); - // Memoized so downstream `useCallback` deps (notably the reviewer-display - // callback) stay stable across renders — eslint react-hooks/exhaustive-deps - // flagged the inline array literal as a re-rendering trigger. const allAgentList = useMemo( () => [ ...builtInAgents.map((agent) => ({ id: agent.id, name: agent.name })), @@ -121,68 +117,6 @@ export function useWorkItemPropertyHandlers({ [workItem.orchestratorConfig, availableOrgs, onUpdate, closePicker] ); - const reviewConfig = workItem.orchestratorConfig?.review_config; - const currentReviewer = reviewConfig?.reviewer; - - const handleReviewerChange = useCallback( - (reviewerType: ReviewerRefType | null, reviewerId?: string) => { - const existingConfig: OrchestratorConfig = { - ...DEFAULT_ORCHESTRATOR_CONFIG, - ...workItem.orchestratorConfig, - }; - if (reviewerType === null) { - onUpdate({ - orchestratorConfig: { - ...existingConfig, - review_enabled: false, - review_config: undefined, - }, - }); - } else { - onUpdate({ - orchestratorConfig: { - ...existingConfig, - review_enabled: true, - review_config: { - reviewer: { type: reviewerType, id: reviewerId }, - max_rounds: reviewConfig?.max_rounds ?? 3, - }, - }, - }); - } - closePicker(); - }, - [workItem.orchestratorConfig, reviewConfig, onUpdate, closePicker] - ); - - const getReviewerDisplay = useCallback((): string => { - if (!currentReviewer) return t("workItems.properties.noReviewer"); - switch (currentReviewer.type) { - case "self_review": - return t("workItems.agentSettings.reviewerSelfReview"); - case "agent": { - if (currentReviewer.id) { - const found = allAgentList.find( - (agent) => agent.id === currentReviewer.id - ); - return found?.name ?? currentReviewer.id; - } - return t("workItems.agentSettings.reviewerAgent"); - } - case "human": { - if (currentReviewer.id) { - const found = availableMembers.find( - (person) => person.id === currentReviewer.id - ); - return found?.name ?? currentReviewer.id; - } - return t("workItems.agentSettings.reviewerHuman"); - } - default: - return t("workItems.properties.noReviewer"); - } - }, [currentReviewer, allAgentList, availableMembers, t]); - const handleScheduleChange = useCallback( (schedule: WorkItemSchedule | null) => { onUpdate({ schedule }); @@ -307,12 +241,9 @@ export function useWorkItemPropertyHandlers({ return { builtInAgents, allAgentList, - currentReviewer, handleStatusChange, handlePriorityChange, handleAssigneeChange, - handleReviewerChange, - getReviewerDisplay, handleScheduleChange, handleLabelToggle, handleLabelsClear, From 26e8f0e5b0d88de6dc89149009dd33c472ad7863 Mon Sep 17 00:00:00 2001 From: Neonforge <48338160+Neonforge98@users.noreply.github.com> Date: Fri, 14 Aug 2026 18:03:52 -0700 Subject: [PATCH 6/7] refactor(work-items): remove orphaned agent settings panel Pre-commit hook ran. Total eslint: 4, total circular: 0 --- src/i18n/locales/de/projects.json | 34 +- src/i18n/locales/en/projects.json | 34 +- src/i18n/locales/es/projects.json | 34 +- src/i18n/locales/fr/projects.json | 34 +- src/i18n/locales/ja/projects.json | 34 +- src/i18n/locales/ko/projects.json | 34 +- src/i18n/locales/pl/projects.json | 34 +- src/i18n/locales/pt/projects.json | 34 +- src/i18n/locales/ru/projects.json | 34 +- src/i18n/locales/tr/projects.json | 34 +- src/i18n/locales/vi/projects.json | 34 +- src/i18n/locales/zh-Hant/projects.json | 34 +- src/i18n/locales/zh/projects.json | 34 +- .../components/AgentSettings/index.tsx | 348 ------------------ 14 files changed, 13 insertions(+), 777 deletions(-) delete mode 100644 src/modules/ProjectManager/WorkItems/components/AgentSettings/index.tsx diff --git a/src/i18n/locales/de/projects.json b/src/i18n/locales/de/projects.json index f1731d8720..122823ebbf 100644 --- a/src/i18n/locales/de/projects.json +++ b/src/i18n/locales/de/projects.json @@ -359,8 +359,7 @@ "inHours": "in {{count}} Stunden", "membersGroup": "Mitglieder", "agentsGroup": "Agents", - "orgsGroup": "Organisationen", - "noReviewer": "Kein Reviewer" + "orgsGroup": "Organisationen" }, "history": { "noHistory": "Noch keine Historie" @@ -458,37 +457,6 @@ "dueDate": "Fälligkeitsdatum" }, "addWorkItem": "Arbeitselement hinzufügen", - "agentSettings": { - "title": "Agent Einstellungen", - "providerTitle": "Anbieter", - "behaviorTitle": "Verhalten", - "autoRetryOnFailure": "Automatischer Neuversuch", - "autoRetryOnFailureDesc": "Automatisch wiederholen bei Sitzungsfehler", - "maxRetryCount": "Max. Neuversuche", - "autoCreatePr": "PR automatisch erstellen", - "autoCreatePrDesc": "Automatisch Pull Request bei Abschluss erstellen", - "pendingChanges": "Änderungen werden beim nächsten Durchlauf angewendet", - "disabledRequiresAutoRetry": "Erfordert aktivierten automatischen Neuversuch", - "codeAccount": "Code-Konto", - "codeAccountDesc": "Lokales Konto zur Ausführung des Agent auswählen", - "selectAccount": "Konto auswählen...", - "noAccountsReady": "Keine lokalen Konten verfügbar", - "model": "Modell", - "modelDesc": "Modell für den Agent auswählen", - "selectModel": "Modell auswählen...", - "manageAccounts": "Konten verwalten", - "selectAccountFirst": "Wählen Sie zuerst ein Code-Konto", - "noModelsAvailable": "Keine Modelle verfügbar", - "validationError": "Bitte wählen Sie ein Code-Konto und Modell in den Agent Einstellungen vor dem Start", - "subAgents": "Sub-Agents", - "subAgentsDesc": "Agents, die während der Ausführung delegiert werden können", - "addSubAgent": "Sub-Agent hinzufügen...", - "noSubAgents": "Keine Sub-Agents konfiguriert.", - "noCodeAccountError": "Bitte wählen Sie in den Agent-Einstellungen ein Code-Konto aus, bevor Sie starten.", - "reviewerSelfReview": "Selbst-Review", - "reviewerAgent": "Agent", - "reviewerHuman": "Manuelles Review" - }, "agentWorkflow": { "title": "Agent Workflow", "running": "Läuft...", diff --git a/src/i18n/locales/en/projects.json b/src/i18n/locales/en/projects.json index 40e7dd68b3..a54f3097da 100644 --- a/src/i18n/locales/en/projects.json +++ b/src/i18n/locales/en/projects.json @@ -358,8 +358,7 @@ "inHours": "in {{count}} hours", "membersGroup": "Members", "agentsGroup": "Agents", - "orgsGroup": "Organizations", - "noReviewer": "No reviewer" + "orgsGroup": "Organizations" }, "history": { "noHistory": "No history yet" @@ -473,37 +472,6 @@ "dueDate": "Due Date" }, "addWorkItem": "Add Work Item", - "agentSettings": { - "title": "Agent Settings", - "providerTitle": "Provider", - "behaviorTitle": "Behavior", - "autoRetryOnFailure": "Auto Retry", - "autoRetryOnFailureDesc": "Automatically retry when the session fails", - "maxRetryCount": "Max Retries", - "autoCreatePr": "Auto Create PR", - "autoCreatePrDesc": "Automatically create a pull request on completion", - "pendingChanges": "Changes will apply on next run", - "disabledRequiresAutoRetry": "Requires auto retry to be enabled", - "codeAccount": "Code Account", - "codeAccountDesc": "Select the local account to run the agent", - "selectAccount": "Select account...", - "noAccountsReady": "No local accounts available", - "model": "Model", - "modelDesc": "Select the model for the agent", - "selectModel": "Select model...", - "manageAccounts": "Manage Accounts", - "selectAccountFirst": "Select a code account first", - "noModelsAvailable": "No models available", - "validationError": "Please select a code account and model in Agent Settings before starting", - "subAgents": "Sub-Agents", - "subAgentsDesc": "Agents available for delegation during execution", - "addSubAgent": "Add Sub-Agent...", - "noSubAgents": "No sub-agents configured.", - "noCodeAccountError": "Please select a code account in Agent Settings before starting.", - "reviewerSelfReview": "Self Review", - "reviewerAgent": "Agent", - "reviewerHuman": "Human" - }, "agentWorkflow": { "title": "Agent Workflow", "running": "Running...", diff --git a/src/i18n/locales/es/projects.json b/src/i18n/locales/es/projects.json index 7cef45078e..9148614b99 100644 --- a/src/i18n/locales/es/projects.json +++ b/src/i18n/locales/es/projects.json @@ -359,8 +359,7 @@ "inHours": "en {{count}} horas", "membersGroup": "Miembros", "agentsGroup": "Agents", - "orgsGroup": "Organizaciones", - "noReviewer": "Sin revisor" + "orgsGroup": "Organizaciones" }, "history": { "noHistory": "Sin historial aún" @@ -458,37 +457,6 @@ "dueDate": "Fecha límite" }, "addWorkItem": "Agregar elemento de trabajo", - "agentSettings": { - "title": "Configuración de Agent", - "providerTitle": "Proveedor", - "behaviorTitle": "Comportamiento", - "autoRetryOnFailure": "Reintento Automático", - "autoRetryOnFailureDesc": "Reintentar automáticamente cuando la sesión falla", - "maxRetryCount": "Máximo de Reintentos", - "autoCreatePr": "Crear PR Automáticamente", - "autoCreatePrDesc": "Crear automáticamente un Pull Request al completar", - "pendingChanges": "Los cambios se aplicarán en la próxima ejecución", - "disabledRequiresAutoRetry": "Requiere que el reintento automático esté habilitado", - "codeAccount": "Cuenta de Código", - "codeAccountDesc": "Seleccionar la cuenta local para ejecutar el Agent", - "selectAccount": "Seleccionar cuenta...", - "noAccountsReady": "No hay cuentas locales disponibles", - "model": "Modelo", - "modelDesc": "Seleccionar el modelo para el Agent", - "selectModel": "Seleccionar modelo...", - "manageAccounts": "Administrar cuentas", - "selectAccountFirst": "Seleccione primero una cuenta de código", - "noModelsAvailable": "No hay modelos disponibles", - "validationError": "Seleccione una cuenta de código y un modelo en Configuración de Agent antes de iniciar", - "subAgents": "Sub-Agents", - "subAgentsDesc": "Agents disponibles para delegación durante la ejecución", - "addSubAgent": "Agregar Sub-Agent...", - "noSubAgents": "No hay sub-agents configurados.", - "noCodeAccountError": "Seleccione una cuenta de código en la configuración del Agent antes de iniciar.", - "reviewerSelfReview": "Auto revisión", - "reviewerAgent": "Agent", - "reviewerHuman": "Revisión humana" - }, "agentWorkflow": { "title": "Flujo de Trabajo de Agent", "running": "Ejecutando...", diff --git a/src/i18n/locales/fr/projects.json b/src/i18n/locales/fr/projects.json index 060a0269b2..84de89116d 100644 --- a/src/i18n/locales/fr/projects.json +++ b/src/i18n/locales/fr/projects.json @@ -359,8 +359,7 @@ "inHours": "dans {{count}} heures", "membersGroup": "Membres", "agentsGroup": "Agents", - "orgsGroup": "Organisations", - "noReviewer": "Aucun réviseur" + "orgsGroup": "Organisations" }, "history": { "noHistory": "Pas encore d'historique" @@ -458,37 +457,6 @@ "dueDate": "Date d'échéance" }, "addWorkItem": "Ajouter un élément de travail", - "agentSettings": { - "title": "Paramètres Agent", - "providerTitle": "Fournisseur", - "behaviorTitle": "Comportement", - "autoRetryOnFailure": "Réessai Automatique", - "autoRetryOnFailureDesc": "Réessayer automatiquement en cas d'échec de session", - "maxRetryCount": "Nombre Max de Réessais", - "autoCreatePr": "Créer PR Automatiquement", - "autoCreatePrDesc": "Créer automatiquement un Pull Request à la fin", - "pendingChanges": "Les modifications seront appliquées à la prochaine exécution", - "disabledRequiresAutoRetry": "Nécessite l'activation du réessai automatique", - "codeAccount": "Compte de Code", - "codeAccountDesc": "Sélectionner le compte local pour exécuter l'Agent", - "selectAccount": "Sélectionner un compte...", - "noAccountsReady": "Aucun compte local disponible", - "model": "Modèle", - "modelDesc": "Sélectionner le modèle pour l'Agent", - "selectModel": "Sélectionner un modèle...", - "manageAccounts": "Gérer les comptes", - "selectAccountFirst": "Sélectionnez d'abord un compte de code", - "noModelsAvailable": "Aucun modèle disponible", - "validationError": "Veuillez sélectionner un compte de code et un modèle dans les Paramètres Agent avant de démarrer", - "subAgents": "Sub-Agents", - "subAgentsDesc": "Agents disponibles pour délégation pendant l'exécution", - "addSubAgent": "Ajouter un Sub-Agent...", - "noSubAgents": "Aucun sub-agent configuré.", - "noCodeAccountError": "Veuillez sélectionner un compte de code dans les paramètres de l'Agent avant de commencer.", - "reviewerSelfReview": "Auto-révision", - "reviewerAgent": "Agent", - "reviewerHuman": "Révision humaine" - }, "agentWorkflow": { "title": "Flux de Travail Agent", "running": "En cours...", diff --git a/src/i18n/locales/ja/projects.json b/src/i18n/locales/ja/projects.json index 3f0698229d..7092665562 100644 --- a/src/i18n/locales/ja/projects.json +++ b/src/i18n/locales/ja/projects.json @@ -359,8 +359,7 @@ "inHours": "{{count}} 時間後", "membersGroup": "メンバー", "agentsGroup": "Agents", - "orgsGroup": "組織", - "noReviewer": "レビューアーなし" + "orgsGroup": "組織" }, "history": { "noHistory": "履歴はまだありません" @@ -458,37 +457,6 @@ "dueDate": "期限" }, "addWorkItem": "ワークアイテムを追加", - "agentSettings": { - "title": "Agent 設定", - "providerTitle": "プロバイダー", - "behaviorTitle": "動作", - "autoRetryOnFailure": "自動リトライ", - "autoRetryOnFailureDesc": "セッション失敗時に自動でリトライ", - "maxRetryCount": "最大リトライ回数", - "autoCreatePr": "PR 自動作成", - "autoCreatePrDesc": "完了時に自動で Pull Request を作成", - "pendingChanges": "変更は次回実行時に適用されます", - "disabledRequiresAutoRetry": "自動リトライの有効化が必要です", - "codeAccount": "コードアカウント", - "codeAccountDesc": "Agent を実行するローカルアカウントを選択", - "selectAccount": "アカウントを選択...", - "noAccountsReady": "利用可能なローカルアカウントがありません", - "model": "モデル", - "modelDesc": "Agent のモデルを選択", - "selectModel": "モデルを選択...", - "manageAccounts": "アカウント管理", - "selectAccountFirst": "先にコードアカウントを選択してください", - "noModelsAvailable": "利用可能なモデルがありません", - "validationError": "開始する前に Agent 設定でコードアカウントとモデルを選択してください", - "subAgents": "Sub-Agents", - "subAgentsDesc": "実行中に委任可能な Agent", - "addSubAgent": "Sub-Agent を追加...", - "noSubAgents": "Sub-Agent は設定されていません。", - "noCodeAccountError": "開始前に Agent 設定でコードアカウントを選択してください。", - "reviewerSelfReview": "セルフレビュー", - "reviewerAgent": "Agent", - "reviewerHuman": "人的レビュー" - }, "agentWorkflow": { "title": "Agent ワークフロー", "running": "実行中...", diff --git a/src/i18n/locales/ko/projects.json b/src/i18n/locales/ko/projects.json index 167a19e0df..54a260b48c 100644 --- a/src/i18n/locales/ko/projects.json +++ b/src/i18n/locales/ko/projects.json @@ -359,8 +359,7 @@ "inHours": "{{count}}시간 후", "membersGroup": "멤버", "agentsGroup": "Agents", - "orgsGroup": "조직", - "noReviewer": "리뷰어 없음" + "orgsGroup": "조직" }, "history": { "noHistory": "아직 기록이 없습니다" @@ -458,37 +457,6 @@ "dueDate": "마감일" }, "addWorkItem": "작업 항목 추가", - "agentSettings": { - "title": "Agent 설정", - "providerTitle": "제공자", - "behaviorTitle": "동작", - "autoRetryOnFailure": "자동 재시도", - "autoRetryOnFailureDesc": "세션 실패 시 자동 재시도", - "maxRetryCount": "최대 재시도 횟수", - "autoCreatePr": "PR 자동 생성", - "autoCreatePrDesc": "완료 시 자동으로 Pull Request 생성", - "pendingChanges": "변경 사항은 다음 실행 시 적용됩니다", - "disabledRequiresAutoRetry": "자동 재시도 활성화 필요", - "codeAccount": "코드 계정", - "codeAccountDesc": "Agent를 실행할 로컬 계정 선택", - "selectAccount": "계정 선택...", - "noAccountsReady": "사용 가능한 로컬 계정이 없습니다", - "model": "모델", - "modelDesc": "Agent에 사용할 모델 선택", - "selectModel": "모델 선택...", - "manageAccounts": "계정 관리", - "selectAccountFirst": "먼저 코드 계정을 선택하세요", - "noModelsAvailable": "사용 가능한 모델이 없습니다", - "validationError": "시작하기 전에 Agent 설정에서 코드 계정과 모델을 선택하세요", - "subAgents": "Sub-Agents", - "subAgentsDesc": "실행 중 위임에 사용할 수 있는 Agent", - "addSubAgent": "Sub-Agent 추가...", - "noSubAgents": "구성된 Sub-Agent가 없습니다.", - "noCodeAccountError": "시작하기 전에 Agent 설정에서 코드 계정을 선택하세요.", - "reviewerSelfReview": "셀프 리뷰", - "reviewerAgent": "Agent", - "reviewerHuman": "수동 리뷰" - }, "agentWorkflow": { "title": "Agent 워크플로", "running": "실행 중...", diff --git a/src/i18n/locales/pl/projects.json b/src/i18n/locales/pl/projects.json index e27f4c1075..daed7ea761 100644 --- a/src/i18n/locales/pl/projects.json +++ b/src/i18n/locales/pl/projects.json @@ -359,8 +359,7 @@ "inHours": "za {{count}} godzin", "membersGroup": "Członkowie", "agentsGroup": "Agenci", - "orgsGroup": "Organizacje", - "noReviewer": "Brak recenzenta" + "orgsGroup": "Organizacje" }, "history": { "noHistory": "Brak historii" @@ -458,37 +457,6 @@ "dueDate": "Termin" }, "addWorkItem": "Dodaj zadanie", - "agentSettings": { - "title": "Ustawienia Agenta", - "providerTitle": "Dostawca", - "behaviorTitle": "Zachowanie", - "autoRetryOnFailure": "Automatyczne ponawianie", - "autoRetryOnFailureDesc": "Automatycznie ponów próbę, gdy sesja zakończy się niepowodzeniem", - "maxRetryCount": "Maks. liczba ponowień", - "autoCreatePr": "Automatyczne tworzenie PR", - "autoCreatePrDesc": "Automatycznie utwórz pull request po ukończeniu", - "pendingChanges": "Zmiany zostaną zastosowane przy następnym uruchomieniu", - "disabledRequiresAutoRetry": "Wymaga włączenia automatycznego ponawiania", - "codeAccount": "Konto kodu", - "codeAccountDesc": "Wybierz lokalne konto do uruchomienia Agenta", - "selectAccount": "Wybierz konto...", - "noAccountsReady": "Brak dostępnych lokalnych kont", - "model": "Model", - "modelDesc": "Wybierz model dla Agenta", - "selectModel": "Wybierz model...", - "manageAccounts": "Zarządzaj kontami", - "selectAccountFirst": "Najpierw wybierz konto kodu", - "noModelsAvailable": "Brak dostępnych modeli", - "validationError": "Wybierz konto kodu i model w Ustawieniach Agenta przed rozpoczęciem", - "subAgents": "Pod-Agenci", - "subAgentsDesc": "Agenci dostępni do delegacji podczas wykonywania", - "addSubAgent": "Dodaj Pod-Agenta...", - "noSubAgents": "Brak skonfigurowanych Pod-Agentów.", - "noCodeAccountError": "Wybierz konto kodu w Ustawieniach Agenta przed rozpoczęciem.", - "reviewerSelfReview": "Samoocena", - "reviewerAgent": "Agent", - "reviewerHuman": "Człowiek" - }, "agentWorkflow": { "title": "Workflow Agenta", "running": "Uruchomiony...", diff --git a/src/i18n/locales/pt/projects.json b/src/i18n/locales/pt/projects.json index da63c8513f..8613418b38 100644 --- a/src/i18n/locales/pt/projects.json +++ b/src/i18n/locales/pt/projects.json @@ -359,8 +359,7 @@ "inHours": "em {{count}} horas", "membersGroup": "Membros", "agentsGroup": "Agents", - "orgsGroup": "Organizações", - "noReviewer": "Sem revisor" + "orgsGroup": "Organizações" }, "history": { "noHistory": "Nenhum histórico ainda" @@ -458,37 +457,6 @@ "dueDate": "Prazo" }, "addWorkItem": "Adicionar Item de Trabalho", - "agentSettings": { - "title": "Configurações do Agent", - "providerTitle": "Provedor", - "behaviorTitle": "Comportamento", - "autoRetryOnFailure": "Tentar Novamente Automaticamente", - "autoRetryOnFailureDesc": "Tenta novamente automaticamente quando a sessão falha", - "maxRetryCount": "Máximo de Tentativas", - "autoCreatePr": "Criar PR Automaticamente", - "autoCreatePrDesc": "Cria automaticamente um pull request ao concluir", - "pendingChanges": "As alterações serão aplicadas na próxima execução", - "disabledRequiresAutoRetry": "Requer que a repetição automática esteja ativada", - "codeAccount": "Conta de Código", - "codeAccountDesc": "Selecione a conta local para executar o Agent", - "selectAccount": "Selecionar conta...", - "noAccountsReady": "Nenhuma conta local disponível", - "model": "Modelo", - "modelDesc": "Selecione o modelo para o Agent", - "selectModel": "Selecionar modelo...", - "manageAccounts": "Gerenciar Contas", - "selectAccountFirst": "Selecione uma conta de código primeiro", - "noModelsAvailable": "Nenhum modelo disponível", - "validationError": "Selecione uma conta de código e um modelo em Configurações do Agent antes de iniciar", - "subAgents": "Sub-Agents", - "subAgentsDesc": "Agents disponíveis para delegação durante a execução", - "addSubAgent": "Adicionar Sub-Agent...", - "noSubAgents": "Nenhum Sub-Agent configurado.", - "noCodeAccountError": "Selecione uma conta de código em Configurações do Agent antes de iniciar.", - "reviewerSelfReview": "Auto-revisão", - "reviewerAgent": "Agent", - "reviewerHuman": "Humano" - }, "agentWorkflow": { "title": "Workflow do Agent", "running": "Executando...", diff --git a/src/i18n/locales/ru/projects.json b/src/i18n/locales/ru/projects.json index d282e2b4cf..51487f19da 100644 --- a/src/i18n/locales/ru/projects.json +++ b/src/i18n/locales/ru/projects.json @@ -359,8 +359,7 @@ "inHours": "через {{count}} часов", "membersGroup": "Участники", "agentsGroup": "Agents", - "orgsGroup": "Организации", - "noReviewer": "Нет ревьюера" + "orgsGroup": "Организации" }, "history": { "noHistory": "История пока пуста" @@ -458,37 +457,6 @@ "dueDate": "Срок" }, "addWorkItem": "Добавить рабочий элемент", - "agentSettings": { - "title": "Настройки Agent", - "providerTitle": "Провайдер", - "behaviorTitle": "Поведение", - "autoRetryOnFailure": "Автоматический повтор", - "autoRetryOnFailureDesc": "Автоматически повторять при сбое сессии", - "maxRetryCount": "Макс. повторов", - "autoCreatePr": "Автосоздание PR", - "autoCreatePrDesc": "Автоматически создавать Pull Request при завершении", - "pendingChanges": "Изменения будут применены при следующем запуске", - "disabledRequiresAutoRetry": "Требуется включение автоповтора", - "codeAccount": "Аккаунт кода", - "codeAccountDesc": "Выберите локальный аккаунт для запуска Agent", - "selectAccount": "Выберите аккаунт...", - "noAccountsReady": "Нет доступных локальных аккаунтов", - "model": "Модель", - "modelDesc": "Выберите модель для Agent", - "selectModel": "Выберите модель...", - "manageAccounts": "Управление аккаунтами", - "selectAccountFirst": "Сначала выберите аккаунт кода", - "noModelsAvailable": "Нет доступных моделей", - "validationError": "Выберите аккаунт кода и модель в настройках Agent перед запуском", - "subAgents": "Sub-Agents", - "subAgentsDesc": "Agents, доступные для делегирования при выполнении", - "addSubAgent": "Добавить Sub-Agent...", - "noSubAgents": "Sub-Agents не настроены.", - "noCodeAccountError": "Пожалуйста, выберите учётную запись кода в настройках Agent перед началом.", - "reviewerSelfReview": "Самопроверка", - "reviewerAgent": "Agent", - "reviewerHuman": "Ручная проверка" - }, "agentWorkflow": { "title": "Agent Workflow", "running": "Выполняется...", diff --git a/src/i18n/locales/tr/projects.json b/src/i18n/locales/tr/projects.json index b2074e81a6..b0673107ce 100644 --- a/src/i18n/locales/tr/projects.json +++ b/src/i18n/locales/tr/projects.json @@ -359,8 +359,7 @@ "inHours": "{{count}} saat içinde", "membersGroup": "Üyeler", "agentsGroup": "Agents", - "orgsGroup": "Organizasyonlar", - "noReviewer": "Gözden geçiren yok" + "orgsGroup": "Organizasyonlar" }, "history": { "noHistory": "Henüz geçmiş yok" @@ -458,37 +457,6 @@ "dueDate": "Son Tarih" }, "addWorkItem": "İş Öğesi Ekle", - "agentSettings": { - "title": "Agent Ayarları", - "providerTitle": "Sağlayıcı", - "behaviorTitle": "Davranış", - "autoRetryOnFailure": "Otomatik Yeniden Deneme", - "autoRetryOnFailureDesc": "Oturum başarısız olduğunda otomatik yeniden dene", - "maxRetryCount": "Maks. Yeniden Deneme", - "autoCreatePr": "Otomatik PR Oluştur", - "autoCreatePrDesc": "Tamamlandığında otomatik Pull Request oluştur", - "pendingChanges": "Değişiklikler bir sonraki çalıştırmada uygulanacak", - "disabledRequiresAutoRetry": "Otomatik yeniden denemenin etkin olması gerekir", - "codeAccount": "Kod Hesabı", - "codeAccountDesc": "Agent'ı çalıştırmak için yerel hesap seçin", - "selectAccount": "Hesap seçin...", - "noAccountsReady": "Kullanılabilir yerel hesap yok", - "model": "Model", - "modelDesc": "Agent için model seçin", - "selectModel": "Model seçin...", - "manageAccounts": "Hesapları yönet", - "selectAccountFirst": "Önce bir kod hesabı seçin", - "noModelsAvailable": "Kullanılabilir model yok", - "validationError": "Başlatmadan önce Agent Ayarlarından bir kod hesabı ve model seçin", - "subAgents": "Sub-Agents", - "subAgentsDesc": "Yürütme sırasında devredilmek üzere kullanılabilir agents", - "addSubAgent": "Sub-Agent Ekle...", - "noSubAgents": "Yapılandırılmış sub-agent yok.", - "noCodeAccountError": "Başlamadan önce Agent Ayarlarında bir kod hesabı seçin.", - "reviewerSelfReview": "Öz inceleme", - "reviewerAgent": "Agent", - "reviewerHuman": "Manuel inceleme" - }, "agentWorkflow": { "title": "Agent İş Akışı", "running": "Çalışıyor...", diff --git a/src/i18n/locales/vi/projects.json b/src/i18n/locales/vi/projects.json index b21bf576c4..682ed6317a 100644 --- a/src/i18n/locales/vi/projects.json +++ b/src/i18n/locales/vi/projects.json @@ -359,8 +359,7 @@ "inHours": "trong {{count}} giờ", "membersGroup": "Thành viên", "agentsGroup": "Agents", - "orgsGroup": "Tổ chức", - "noReviewer": "Không có người đánh giá" + "orgsGroup": "Tổ chức" }, "history": { "noHistory": "Chưa có lịch sử" @@ -458,37 +457,6 @@ "dueDate": "Hạn chót" }, "addWorkItem": "Thêm mục công việc", - "agentSettings": { - "title": "Cài đặt Agent", - "providerTitle": "Nhà cung cấp", - "behaviorTitle": "Hành vi", - "autoRetryOnFailure": "Tự động thử lại", - "autoRetryOnFailureDesc": "Tự động thử lại khi phiên thất bại", - "maxRetryCount": "Số lần thử lại tối đa", - "autoCreatePr": "Tự động tạo PR", - "autoCreatePrDesc": "Tự động tạo Pull Request khi hoàn tất", - "pendingChanges": "Thay đổi sẽ được áp dụng trong lần chạy tiếp theo", - "disabledRequiresAutoRetry": "Cần bật tự động thử lại", - "codeAccount": "Tài khoản Code", - "codeAccountDesc": "Chọn tài khoản cục bộ để chạy Agent", - "selectAccount": "Chọn tài khoản...", - "noAccountsReady": "Không có tài khoản cục bộ khả dụng", - "model": "Mô hình", - "modelDesc": "Chọn mô hình cho Agent", - "selectModel": "Chọn mô hình...", - "manageAccounts": "Quản lý tài khoản", - "selectAccountFirst": "Vui lòng chọn tài khoản code trước", - "noModelsAvailable": "Không có mô hình khả dụng", - "validationError": "Vui lòng chọn tài khoản code và mô hình trong Cài đặt Agent trước khi bắt đầu", - "subAgents": "Sub-Agents", - "subAgentsDesc": "Agents có sẵn để ủy quyền trong quá trình thực thi", - "addSubAgent": "Thêm Sub-Agent...", - "noSubAgents": "Chưa cấu hình sub-agent.", - "noCodeAccountError": "Vui lòng chọn tài khoản mã trong cài đặt Agent trước khi bắt đầu.", - "reviewerSelfReview": "Tự đánh giá", - "reviewerAgent": "Agent", - "reviewerHuman": "Đánh giá thủ công" - }, "agentWorkflow": { "title": "Agent Workflow", "running": "Đang chạy...", diff --git a/src/i18n/locales/zh-Hant/projects.json b/src/i18n/locales/zh-Hant/projects.json index a014384c19..ec0a02001f 100644 --- a/src/i18n/locales/zh-Hant/projects.json +++ b/src/i18n/locales/zh-Hant/projects.json @@ -359,8 +359,7 @@ "inHours": "{{count}} 小時後", "membersGroup": "成員", "agentsGroup": "Agents", - "orgsGroup": "組織", - "noReviewer": "無審查人" + "orgsGroup": "組織" }, "history": { "noHistory": "暫無歷史記錄" @@ -458,37 +457,6 @@ "dueDate": "截止日期" }, "addWorkItem": "添加工作項", - "agentSettings": { - "title": "Agent 設置", - "providerTitle": "提供商", - "behaviorTitle": "行爲", - "autoRetryOnFailure": "自動重試", - "autoRetryOnFailureDesc": "會話失敗時自動重試", - "maxRetryCount": "最大重試次數", - "autoCreatePr": "自動創建 PR", - "autoCreatePrDesc": "完成後自動創建 Pull Request", - "pendingChanges": "更改將在下次運行時生效", - "disabledRequiresAutoRetry": "需要啓用自動重試", - "codeAccount": "代碼賬戶", - "codeAccountDesc": "選擇運行 Agent 的本地賬戶", - "selectAccount": "選擇賬戶...", - "noAccountsReady": "沒有可用的本地賬戶", - "model": "模型", - "modelDesc": "選擇 Agent 使用的模型", - "selectModel": "選擇模型...", - "manageAccounts": "管理賬戶", - "selectAccountFirst": "請先選擇代碼賬戶", - "noModelsAvailable": "沒有可用的模型", - "validationError": "請在 Agent 設置中選擇代碼賬戶和模型後再啓動", - "subAgents": "Sub-Agents", - "subAgentsDesc": "執行期間可用於委派的 Agent", - "addSubAgent": "添加 Sub-Agent...", - "noSubAgents": "未配置 Sub-Agent。", - "noCodeAccountError": "請在 Agent 設置中選擇代碼賬戶後再啓動。", - "reviewerSelfReview": "自我審查", - "reviewerAgent": "Agent", - "reviewerHuman": "人工審查" - }, "agentWorkflow": { "title": "Agent 工作流", "running": "運行中...", diff --git a/src/i18n/locales/zh/projects.json b/src/i18n/locales/zh/projects.json index b12f6a9ad6..faccb540b2 100644 --- a/src/i18n/locales/zh/projects.json +++ b/src/i18n/locales/zh/projects.json @@ -359,8 +359,7 @@ "inHours": "{{count}} 小时后", "membersGroup": "成员", "agentsGroup": "Agents", - "orgsGroup": "组织", - "noReviewer": "无审查人" + "orgsGroup": "组织" }, "history": { "noHistory": "暂无历史记录" @@ -474,37 +473,6 @@ "dueDate": "截止日期" }, "addWorkItem": "添加工作项", - "agentSettings": { - "title": "Agent 设置", - "providerTitle": "提供商", - "behaviorTitle": "行为", - "autoRetryOnFailure": "自动重试", - "autoRetryOnFailureDesc": "会话失败时自动重试", - "maxRetryCount": "最大重试次数", - "autoCreatePr": "自动创建 PR", - "autoCreatePrDesc": "完成后自动创建 Pull Request", - "pendingChanges": "更改将在下次运行时生效", - "disabledRequiresAutoRetry": "需要启用自动重试", - "codeAccount": "代码账户", - "codeAccountDesc": "选择运行 Agent 的本地账户", - "selectAccount": "选择账户...", - "noAccountsReady": "没有可用的本地账户", - "model": "模型", - "modelDesc": "选择 Agent 使用的模型", - "selectModel": "选择模型...", - "manageAccounts": "管理账户", - "selectAccountFirst": "请先选择代码账户", - "noModelsAvailable": "没有可用的模型", - "validationError": "请在 Agent 设置中选择代码账户和模型后再启动", - "subAgents": "Sub-Agents", - "subAgentsDesc": "执行期间可用于委派的 Agent", - "addSubAgent": "添加 Sub-Agent...", - "noSubAgents": "未配置 Sub-Agent。", - "noCodeAccountError": "请在 Agent 设置中选择代码账户后再启动。", - "reviewerSelfReview": "自我审查", - "reviewerAgent": "Agent", - "reviewerHuman": "人工审查" - }, "agentWorkflow": { "title": "Agent 工作流", "running": "运行中...", diff --git a/src/modules/ProjectManager/WorkItems/components/AgentSettings/index.tsx b/src/modules/ProjectManager/WorkItems/components/AgentSettings/index.tsx deleted file mode 100644 index ab5cee3315..0000000000 --- a/src/modules/ProjectManager/WorkItems/components/AgentSettings/index.tsx +++ /dev/null @@ -1,348 +0,0 @@ -import { useAtomValue } from "jotai"; -import { Plus, SquareArrowOutUpRight, X } from "lucide-react"; -import React, { useCallback, useMemo } from "react"; -import { useTranslation } from "react-i18next"; -import { useNavigate } from "react-router-dom"; - -import type { OrchestratorConfig } from "@src/api/http/project"; -import DropdownFooter from "@src/components/Dropdown/DropdownFooter"; -import { - DROPDOWN_CLASSES, - DROPDOWN_ITEM, -} from "@src/components/Dropdown/tokens"; -import InlineAlert from "@src/components/InlineAlert"; -import NumberInput from "@src/components/NumberInput"; -import Select from "@src/components/Select"; -import Switch from "@src/components/Switch"; -import { buildIntegrationsPath } from "@src/config/mainAppPaths"; -import { useKeyVault } from "@src/hooks/keyVault/useKeyVault"; -import { builtInAgentsAtom } from "@src/modules/MainApp/AgentOrgs/store/builtInAgentsAtom"; -import type { AgentDefinition } from "@src/modules/MainApp/AgentOrgs/types"; -import { - SECTION_CONTROL_STYLE, - SECTION_GAP_CLASSES, - SectionContainer, - SectionRow, -} from "@src/modules/shared/layouts/SectionLayout"; -import { CollapsibleSection } from "@src/modules/shared/layouts/blocks"; - -import { DEFAULT_ORCHESTRATOR_CONFIG } from "../../constants"; - -interface AgentSettingsProps { - config: OrchestratorConfig; - isWorkflowActive: boolean; - onUpdateConfig: (updates: Partial) => void; - /** Custom agents from Agent Teams (for sub-agent selection) */ - availableAgents?: AgentDefinition[]; -} - -const AgentSettings: React.FC = ({ - config, - isWorkflowActive, - onUpdateConfig, - availableAgents = [], -}) => { - const { t } = useTranslation("projects"); - const navigate = useNavigate(); - const builtInAgents = useAtomValue(builtInAgentsAtom); - - const { localAccounts, loading: accountsLoading } = useKeyVault({ - autoLoad: true, - }); - - const readyAccounts = useMemo( - () => localAccounts.filter((account) => account.status === "ready"), - [localAccounts] - ); - - const accountOptions = useMemo( - () => - readyAccounts.map((account) => ({ - value: account.id, - label: account.name, - })), - [readyAccounts] - ); - - const handleToggle = useCallback( - (field: keyof OrchestratorConfig, value: boolean) => { - onUpdateConfig({ [field]: value }); - }, - [onUpdateConfig] - ); - - const handleMaxRetryChange = useCallback( - (value: number | undefined) => { - if (value !== undefined) onUpdateConfig({ max_retry_count: value }); - }, - [onUpdateConfig] - ); - - const handleSelectAccount = useCallback( - (value: string | number | (string | number)[]) => { - onUpdateConfig({ - selected_account_id: String(value), - selected_model_id: undefined, - }); - }, - [onUpdateConfig] - ); - - const handleSelectModel = useCallback( - (value: string | number | (string | number)[]) => { - onUpdateConfig({ selected_model_id: String(value) }); - }, - [onUpdateConfig] - ); - - const effectiveConfig = { ...DEFAULT_ORCHESTRATOR_CONFIG, ...config }; - const maxRetryDisabled = !effectiveConfig.auto_retry_on_failure; - - const selectedAccount = useMemo( - () => - readyAccounts.find( - (acc) => acc.id === effectiveConfig.selected_account_id - ), - [readyAccounts, effectiveConfig.selected_account_id] - ); - - const modelOptions = useMemo( - () => - (selectedAccount?.availableModels ?? []).map((modelId) => ({ - value: modelId, - label: modelId, - })), - [selectedAccount] - ); - - const noAccountSelected = !effectiveConfig.selected_account_id; - - // Sub-agent multi-select - const subAgentIds = useMemo( - () => effectiveConfig.sub_agent_ids ?? [], - [effectiveConfig.sub_agent_ids] - ); - const addedIdSet = useMemo(() => new Set(subAgentIds), [subAgentIds]); - - const allAgents = useMemo( - () => [...availableAgents, ...builtInAgents], - [availableAgents, builtInAgents] - ); - - const addableAgentOptions = useMemo( - () => - allAgents - .filter((agent) => !addedIdSet.has(agent.id)) - .map((agent) => ({ - value: agent.id, - label: agent.name, - })), - [allAgents, addedIdSet] - ); - - const handleAddSubAgent = useCallback( - (value: string | number | (string | number)[]) => { - const agentId = String(value); - if (!agentId || addedIdSet.has(agentId)) return; - onUpdateConfig({ sub_agent_ids: [...subAgentIds, agentId] }); - }, - [subAgentIds, addedIdSet, onUpdateConfig] - ); - - const handleRemoveSubAgent = useCallback( - (agentId: string) => { - onUpdateConfig({ - sub_agent_ids: subAgentIds.filter((id) => id !== agentId), - }); - }, - [subAgentIds, onUpdateConfig] - ); - - const resolveAgentName = useCallback( - (agentId: string) => - allAgents.find((agent) => agent.id === agentId)?.name ?? agentId, - [allAgents] - ); - - const handleOpenIntegrations = useCallback(() => { - const path = buildIntegrationsPath({ category: "models" }); - navigate(`${path}?modelsTab=my-accounts`); - }, [navigate]); - - const accountDropdownRender = useCallback( - (menu: React.ReactNode) => ( -
- {menu} - - - -
- ), - [handleOpenIntegrations, t] - ); - - return ( - -
- {isWorkflowActive && ( - - {t("workItems.agentSettings.pendingChanges")} - - )} - - - - - - - - {/* Sub-Agents — multi-select add/remove list */} - -
- {t("workItems.agentSettings.subAgents")} -
-
- {t("workItems.agentSettings.subAgentsDesc")} -
- - {subAgentIds.length === 0 && ( -
- {t("workItems.agentSettings.noSubAgents")} -
- )} - - {subAgentIds.map((agentId) => ( -
- - {resolveAgentName(agentId)} - - -
- ))} - - {addableAgentOptions.length > 0 && ( -
-