From 6576d0d482fe2e9864e5946a2ed32217f41bab69 Mon Sep 17 00:00:00 2001 From: lazabogdan <6580668+lazabogdan@users.noreply.github.com> Date: Fri, 31 Jul 2026 14:03:36 +0300 Subject: [PATCH 01/29] feat(repair): source-aware PR-autofix redelivery and fingerprint carryover Phase 1 + partial Phase 2 of the unattended repair loop plan: - P1.1 source-aware durable repair redelivery: blocked PrAutofix attempts redeliver through a dedicated pr_autofix_redelivery module instead of reusing the generic repair message path. - P1.3 fingerprint carryover with unchanged-health successor suppression, so a successor is not dispatched when PR health has not moved. Base-advance blocks are exempt because they carry new input independent of PR health. - P1.4 human-readable retry context in dispatch messages. - P2.1 spawn grace before interrupted settlement. - P2.3 repair prompt aligned with the live complete_agent_workspace_repair tool schema (resolution/blocker fields), plus MCP tool + build refresh. Work in progress restore point before updating the workspace from main. Co-Authored-By: Claude Opus 5 --- .../shared/prompt.md | 6 +- .../build/__tests__/tools.test.js | 26 +- .../build/__tests__/tools.test.js.map | 2 +- .../build/agent-workspace-tools.d.ts.map | 2 +- .../build/agent-workspace-tools.js | 14 +- .../build/agent-workspace-tools.js.map | 2 +- .../src/__tests__/tools.test.ts | 32 +- .../src/agent-workspace-tools.ts | 18 +- .../agent_workspace_publish_recovery.rs | 1 + .../durable_attempt_recovery.rs | 214 ++++++++-- .../pr_autofix_redelivery.rs | 207 ++++++++++ .../agent_workspace_publish_recovery_tests.rs | 368 ++++++++++++++++++ .../agent_workspace_publish_repair_state.rs | 89 ++++- ...nt_workspace_publish_repair_state_tests.rs | 1 + .../application/services/pr_merge_poller.rs | 6 +- .../src/commands/unified_chat_commands/mod.rs | 3 +- 16 files changed, 938 insertions(+), 53 deletions(-) create mode 100644 src-tauri/src/application/agent_workspace_publish_recovery/pr_autofix_redelivery.rs diff --git a/agents/ralphx-agent-workspace-repair/shared/prompt.md b/agents/ralphx-agent-workspace-repair/shared/prompt.md index 8aafdde623..5950809388 100644 --- a/agents/ralphx-agent-workspace-repair/shared/prompt.md +++ b/agents/ralphx-agent-workspace-repair/shared/prompt.md @@ -14,8 +14,8 @@ The workspace branch and base ref are provided in the user payload. 4. Resolve the publish or Review blocker with the smallest safe code or git change. 5. Stage only the files involved in the repair. Do not use blanket staging such as `git add .`. 6. Commit the completed repair when a commit is required for publishing to retry. -7. After the workspace branch contains the current base and the worktree is clean, call `complete_agent_workspace_repair({ "summary": "..." })`; RalphX will verify the repair and retry publishing automatically. -8. If the repair cannot be completed safely, call `complete_agent_workspace_repair({ "summary": "...", "blocker": "..." })`. +7. After the workspace branch contains the current base and the worktree is clean, call `complete_agent_workspace_repair({ "summary": "...", "resolution": "fixed", "fix_commit_sha": "<40-character HEAD SHA>" })`; RalphX will verify the repair and retry publishing automatically. +8. If the repair cannot be completed safely, classify it honestly: `transient_ci` only for GitHub Actions infrastructure failures; `pre_existing_on_base` only with evidence that the failure reproduces on base; `needs_human` for a blocker requiring user action. PR-autofix-sourced repairs must use `fixed`, `transient_ci`, `pre_existing_on_base`, or `needs_human` rather than writing a free-text blocker in place of a classification. @@ -29,7 +29,7 @@ The workspace branch and base ref are provided in the user payload. - no conflict markers remain in changed files - the relevant validation for the touched area passes when practical - the worktree is clean after committing -5. Call `complete_agent_workspace_repair({ "summary": "..." })` after a clean repair, or `complete_agent_workspace_repair({ "summary": "...", "blocker": "..." })` when repair is unsafe. +5. Call `complete_agent_workspace_repair({ "summary": "...", "resolution": "fixed", "fix_commit_sha": "<40-character HEAD SHA>" })` after a clean repair. When repair is unsafe, include the honest `resolution` (`transient_ci`, `pre_existing_on_base`, or `needs_human`) and use `blocker` only to explain the classified outcome. 6. If RalphX reports that further repair is needed, address the actionable issue and signal completion again. Otherwise, stop after the completion signal. diff --git a/plugins/app/ralphx-mcp-server/build/__tests__/tools.test.js b/plugins/app/ralphx-mcp-server/build/__tests__/tools.test.js index 0f9dad6645..37f46c62ca 100644 --- a/plugins/app/ralphx-mcp-server/build/__tests__/tools.test.js +++ b/plugins/app/ralphx-mcp-server/build/__tests__/tools.test.js @@ -916,10 +916,12 @@ describe('agent workspace repair tool', () => { it('should exist in ALL_TOOLS', () => { expect(tool).toBeDefined(); }); - it('should accept only a completion summary and optional blocker', () => { + it('should accept a completion summary, optional blocker, and classified resolution', () => { expect(tool?.inputSchema.type).toBe('object'); expect(tool?.inputSchema.properties).toHaveProperty('summary'); expect(tool?.inputSchema.properties).toHaveProperty('blocker'); + expect(tool?.inputSchema.properties).toHaveProperty('resolution'); + expect(tool?.inputSchema.properties).toHaveProperty('fix_commit_sha'); expect(tool?.inputSchema.properties).not.toHaveProperty('conversation_id'); expect(tool?.inputSchema.properties).not.toHaveProperty('repair_commit_sha'); expect(tool?.inputSchema.properties).not.toHaveProperty('resolved_base_ref'); @@ -927,9 +929,24 @@ describe('agent workspace repair tool', () => { expect(tool?.inputSchema.required).toEqual(['summary']); expect(tool?.inputSchema.additionalProperties).toBe(false); }); + it('matches the PR fix resolution enum and validates the reported fix commit SHA', () => { + const prFixTool = allTools.find((t) => t.name === 'complete_agent_workspace_pr_fix'); + const repairProperties = tool?.inputSchema.properties; + const prFixProperties = prFixTool?.inputSchema.properties; + expect(repairProperties?.resolution?.enum).toEqual(prFixProperties?.resolution?.enum); + expect(repairProperties?.fix_commit_sha).toMatchObject({ + pattern: '^[0-9a-f]{40}$', + }); + }); it('accepts valid repair completion objects and rejects model-supplied identity or SHA extras', () => { const validate = new AjvValidator().compile(tool.inputSchema); - expect(validate({ summary: 'Resolved conflicts', blocker: 'Needs input' })).toBe(true); + expect(validate({ + summary: 'Resolved conflicts', + blocker: 'Needs input', + resolution: 'fixed', + fix_commit_sha: 'a'.repeat(40), + })).toBe(true); + expect(validate({ summary: 'Resolved conflicts', fix_commit_sha: 'not-a-sha' })).toBe(false); for (const [property, value] of Object.entries({ conversation_id: 'conversation-from-model', agent_run_id: 'run-from-model', @@ -1655,6 +1672,7 @@ describe('agent workspace publish tool transport', () => { { summary: 'Resolved conflicts', blocker: 'Needs maintainer decision', + reported_fix_commit_sha: 'a'.repeat(40), }, ], [ @@ -1733,6 +1751,8 @@ describe('agent workspace repair tool transport', () => { await expect(callCompleteAgentWorkspaceRepairTool(callTauri, { summary: 'Resolved conflicts', blocker: 'Needs maintainer decision', + resolution: 'pre_existing_on_base', + fix_commit_sha: 'a'.repeat(40), }, { agentRunId: 'run-1', parentConversationId: 'conversation-1', @@ -1741,6 +1761,8 @@ describe('agent workspace repair tool transport', () => { expect(callTauri).toHaveBeenCalledWith('agent-workspaces/conversation-1/complete-repair', { summary: 'Resolved conflicts', blocker: 'Needs maintainer decision', + resolution: 'pre_existing_on_base', + reported_fix_commit_sha: 'a'.repeat(40), }, { headers: { 'x-ralphx-agent-run-id': 'run-1', diff --git a/plugins/app/ralphx-mcp-server/build/__tests__/tools.test.js.map b/plugins/app/ralphx-mcp-server/build/__tests__/tools.test.js.map index 332edd932c..2a289e38ff 100644 --- a/plugins/app/ralphx-mcp-server/build/__tests__/tools.test.js.map +++ b/plugins/app/ralphx-mcp-server/build/__tests__/tools.test.js.map @@ -1 +1 @@ -{"version":3,"file":"tools.test.js","sourceRoot":"","sources":["../../src/__tests__/tools.test.ts"],"names":[],"mappings":"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"} \ No newline at end of file +{"version":3,"file":"tools.test.js","sourceRoot":"","sources":["../../src/__tests__/tools.test.ts"],"names":[],"mappings":"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"} \ No newline at end of file diff --git a/plugins/app/ralphx-mcp-server/build/agent-workspace-tools.d.ts.map b/plugins/app/ralphx-mcp-server/build/agent-workspace-tools.d.ts.map index b33ec9b213..8c2780c399 100644 --- a/plugins/app/ralphx-mcp-server/build/agent-workspace-tools.d.ts.map +++ b/plugins/app/ralphx-mcp-server/build/agent-workspace-tools.d.ts.map @@ -1 +1 @@ -{"version":3,"file":"agent-workspace-tools.d.ts","sourceRoot":"","sources":["../src/agent-workspace-tools.ts"],"names":[],"mappings":"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"} \ No newline at end of file +{"version":3,"file":"agent-workspace-tools.d.ts","sourceRoot":"","sources":["../src/agent-workspace-tools.ts"],"names":[],"mappings":"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"} \ No newline at end of file diff --git a/plugins/app/ralphx-mcp-server/build/agent-workspace-tools.js b/plugins/app/ralphx-mcp-server/build/agent-workspace-tools.js index 1c4a24fc84..f3c9ec279f 100644 --- a/plugins/app/ralphx-mcp-server/build/agent-workspace-tools.js +++ b/plugins/app/ralphx-mcp-server/build/agent-workspace-tools.js @@ -507,6 +507,16 @@ export const AGENT_WORKSPACE_TOOLS = [ minLength: 1, description: "Optional human-readable blocker when the repair cannot be completed safely", }, + resolution: { + type: "string", + enum: ["fixed", "transient_ci", "pre_existing_on_base", "needs_human"], + description: "Classify the repair outcome honestly: fixed after a real repair, transient_ci only for GitHub Actions infrastructure failures, pre_existing_on_base with evidence the failure reproduces on base, or needs_human for a blocker requiring user action.", + }, + fix_commit_sha: { + type: "string", + pattern: "^[0-9a-f]{40}$", + description: "Full 40-character SHA of the current committed workspace HEAD. Required for a fixed repair completion; RalphX verifies the actual branch head changed from dispatch.", + }, }, required: ["summary"], additionalProperties: false, @@ -778,7 +788,7 @@ export async function callCompleteAgentWorkspacePrFixTool(callTauri, args, runti }); } export async function callCompleteAgentWorkspaceRepairTool(callTauri, args, runtimeContext) { - const { summary, blocker } = (args && typeof args === "object" ? args : {}); + const { summary, blocker, resolution, fix_commit_sha } = (args && typeof args === "object" ? args : {}); const conversation_id = resolveRuntimeAgentWorkspaceConversationId("complete_agent_workspace_repair", runtimeContext); const headers = buildRuntimeIdentityTransportHeaders({ agentRunId: runtimeContext?.agentRunId, @@ -790,6 +800,8 @@ export async function callCompleteAgentWorkspaceRepairTool(callTauri, args, runt return callTauri(`agent-workspaces/${conversation_id}/complete-repair`, { summary, blocker, + ...(resolution === undefined ? {} : { resolution }), + ...(fix_commit_sha === undefined ? {} : { reported_fix_commit_sha: fix_commit_sha }), }, { headers }); } export async function callSubmitAgentWorkspacePrDescriptionTool(callTauri, args) { diff --git a/plugins/app/ralphx-mcp-server/build/agent-workspace-tools.js.map b/plugins/app/ralphx-mcp-server/build/agent-workspace-tools.js.map index c90f500bbd..f8ae32fbc0 100644 --- a/plugins/app/ralphx-mcp-server/build/agent-workspace-tools.js.map +++ b/plugins/app/ralphx-mcp-server/build/agent-workspace-tools.js.map @@ -1 +1 @@ -{"version":3,"file":"agent-workspace-tools.js","sourceRoot":"","sources":["../src/agent-workspace-tools.ts"],"names":[],"mappings":"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"} \ No newline at end of file +{"version":3,"file":"agent-workspace-tools.js","sourceRoot":"","sources":["../src/agent-workspace-tools.ts"],"names":[],"mappings":"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"} \ No newline at end of file diff --git a/plugins/app/ralphx-mcp-server/src/__tests__/tools.test.ts b/plugins/app/ralphx-mcp-server/src/__tests__/tools.test.ts index ffb82d5459..742e7d2342 100644 --- a/plugins/app/ralphx-mcp-server/src/__tests__/tools.test.ts +++ b/plugins/app/ralphx-mcp-server/src/__tests__/tools.test.ts @@ -1174,10 +1174,12 @@ describe('agent workspace repair tool', () => { expect(tool).toBeDefined(); }); - it('should accept only a completion summary and optional blocker', () => { + it('should accept a completion summary, optional blocker, and classified resolution', () => { expect(tool?.inputSchema.type).toBe('object'); expect(tool?.inputSchema.properties).toHaveProperty('summary'); expect(tool?.inputSchema.properties).toHaveProperty('blocker'); + expect(tool?.inputSchema.properties).toHaveProperty('resolution'); + expect(tool?.inputSchema.properties).toHaveProperty('fix_commit_sha'); expect(tool?.inputSchema.properties).not.toHaveProperty('conversation_id'); expect(tool?.inputSchema.properties).not.toHaveProperty('repair_commit_sha'); expect(tool?.inputSchema.properties).not.toHaveProperty('resolved_base_ref'); @@ -1186,10 +1188,31 @@ describe('agent workspace repair tool', () => { expect(tool?.inputSchema.additionalProperties).toBe(false); }); + it('matches the PR fix resolution enum and validates the reported fix commit SHA', () => { + const prFixTool = allTools.find((t) => t.name === 'complete_agent_workspace_pr_fix'); + const repairProperties = tool?.inputSchema.properties as + | Record + | undefined; + const prFixProperties = prFixTool?.inputSchema.properties as + | Record + | undefined; + + expect(repairProperties?.resolution?.enum).toEqual(prFixProperties?.resolution?.enum); + expect(repairProperties?.fix_commit_sha).toMatchObject({ + pattern: '^[0-9a-f]{40}$', + }); + }); + it('accepts valid repair completion objects and rejects model-supplied identity or SHA extras', () => { const validate = new AjvValidator().compile(tool!.inputSchema); - expect(validate({ summary: 'Resolved conflicts', blocker: 'Needs input' })).toBe(true); + expect(validate({ + summary: 'Resolved conflicts', + blocker: 'Needs input', + resolution: 'fixed', + fix_commit_sha: 'a'.repeat(40), + })).toBe(true); + expect(validate({ summary: 'Resolved conflicts', fix_commit_sha: 'not-a-sha' })).toBe(false); for (const [property, value] of Object.entries({ conversation_id: 'conversation-from-model', agent_run_id: 'run-from-model', @@ -2326,6 +2349,7 @@ describe('agent workspace publish tool transport', () => { { summary: 'Resolved conflicts', blocker: 'Needs maintainer decision', + reported_fix_commit_sha: 'a'.repeat(40), }, ], [ @@ -2422,6 +2446,8 @@ describe('agent workspace repair tool transport', () => { callCompleteAgentWorkspaceRepairTool(callTauri, { summary: 'Resolved conflicts', blocker: 'Needs maintainer decision', + resolution: 'pre_existing_on_base', + fix_commit_sha: 'a'.repeat(40), }, { agentRunId: 'run-1', parentConversationId: 'conversation-1', @@ -2432,6 +2458,8 @@ describe('agent workspace repair tool transport', () => { expect(callTauri).toHaveBeenCalledWith('agent-workspaces/conversation-1/complete-repair', { summary: 'Resolved conflicts', blocker: 'Needs maintainer decision', + resolution: 'pre_existing_on_base', + reported_fix_commit_sha: 'a'.repeat(40), }, { headers: { 'x-ralphx-agent-run-id': 'run-1', diff --git a/plugins/app/ralphx-mcp-server/src/agent-workspace-tools.ts b/plugins/app/ralphx-mcp-server/src/agent-workspace-tools.ts index ad02368e36..b940f76143 100644 --- a/plugins/app/ralphx-mcp-server/src/agent-workspace-tools.ts +++ b/plugins/app/ralphx-mcp-server/src/agent-workspace-tools.ts @@ -569,6 +569,18 @@ export const AGENT_WORKSPACE_TOOLS: Tool[] = [ minLength: 1, description: "Optional human-readable blocker when the repair cannot be completed safely", }, + resolution: { + type: "string", + enum: ["fixed", "transient_ci", "pre_existing_on_base", "needs_human"], + description: + "Classify the repair outcome honestly: fixed after a real repair, transient_ci only for GitHub Actions infrastructure failures, pre_existing_on_base with evidence the failure reproduces on base, or needs_human for a blocker requiring user action.", + }, + fix_commit_sha: { + type: "string", + pattern: "^[0-9a-f]{40}$", + description: + "Full 40-character SHA of the current committed workspace HEAD. Required for a fixed repair completion; RalphX verifies the actual branch head changed from dispatch.", + }, }, required: ["summary"], additionalProperties: false, @@ -1078,9 +1090,11 @@ export async function callCompleteAgentWorkspaceRepairTool( args: unknown, runtimeContext?: AgentWorkspaceToolRuntimeContext, ): Promise { - const { summary, blocker } = (args && typeof args === "object" ? args : {}) as { + const { summary, blocker, resolution, fix_commit_sha } = (args && typeof args === "object" ? args : {}) as { summary: string; blocker?: string; + resolution?: string; + fix_commit_sha?: string; }; const conversation_id = resolveRuntimeAgentWorkspaceConversationId( "complete_agent_workspace_repair", @@ -1099,6 +1113,8 @@ export async function callCompleteAgentWorkspaceRepairTool( return callTauri(`agent-workspaces/${conversation_id}/complete-repair`, { summary, blocker, + ...(resolution === undefined ? {} : { resolution }), + ...(fix_commit_sha === undefined ? {} : { reported_fix_commit_sha: fix_commit_sha }), }, { headers }); } diff --git a/src-tauri/src/application/agent_workspace_publish_recovery.rs b/src-tauri/src/application/agent_workspace_publish_recovery.rs index d6d80f8e8c..fec55f9f4a 100644 --- a/src-tauri/src/application/agent_workspace_publish_recovery.rs +++ b/src-tauri/src/application/agent_workspace_publish_recovery.rs @@ -43,6 +43,7 @@ pub(super) const STALE_TRANSIENT_RECOVERED_STEP: &str = "stale_transient_recover pub(super) const STALE_TRANSIENT_CLASSIFICATION: &str = "stale_transient_status"; pub const STALE_TRANSIENT_STATUS_STALE_SECS: u64 = 300; mod durable_attempt_recovery; +mod pr_autofix_redelivery; pub(crate) use durable_attempt_recovery::is_blocked_and_not_auto_retryable; #[cfg(any(test, feature = "test-utils"))] diff --git a/src-tauri/src/application/agent_workspace_publish_recovery/durable_attempt_recovery.rs b/src-tauri/src/application/agent_workspace_publish_recovery/durable_attempt_recovery.rs index 84b9d879c5..dc9c15ac16 100644 --- a/src-tauri/src/application/agent_workspace_publish_recovery/durable_attempt_recovery.rs +++ b/src-tauri/src/application/agent_workspace_publish_recovery/durable_attempt_recovery.rs @@ -2,21 +2,26 @@ use std::sync::Arc; use chrono::{Duration, Utc}; +use super::pr_autofix_redelivery::{ + due_pr_autofix_redispatch_message, evaluate_pr_autofix_successor, PrAutofixSuccessorDecision, +}; use super::StalePublishRepairRecoveryOutcome; use crate::application::agent_conversation_workspace::resolve_effective_agent_conversation_workspace_path; use crate::application::agent_workspace_pr_autofix_attempt::load_latest_exact_pr_autofix_run_for_pr; use crate::application::agent_workspace_publish_repair_state::{ - agent_workspace_repair_dispatch_is_due, block_agent_workspace_repair_completion, - classify_agent_workspace_repair_delivery, continue_agent_workspace_repair_at_boundary, - inspect_agent_workspace_repair_completion, record_agent_workspace_repair_validation, + agent_workspace_repair_dispatch_is_due, agent_workspace_repair_is_health_held, + block_agent_workspace_repair_completion, classify_agent_workspace_repair_delivery, + continue_agent_workspace_repair_at_boundary, inspect_agent_workspace_repair_completion, + record_agent_workspace_repair_validation, release_and_clear_agent_workspace_repair_target_lease, reserve_agent_workspace_repair_completion_validation, reserve_agent_workspace_repair_dispatch, - resume_current_agent_workspace_repair_publish, settle_agent_workspace_repair_dispatch_outcome, - start_or_join_agent_workspace_repair, transition_agent_workspace_repair_attempt, - validate_agent_workspace_repair_target_lease, AgentWorkspaceRepairDispatchOutcome, - AgentWorkspaceRepairDispatchSettlement, AgentWorkspaceRepairPublishResumeOutcome, - AgentWorkspaceRepairStartOutcome, AgentWorkspaceRepairStartRequest, - AgentWorkspaceRepairTransitionOutcome, ORPHANED_REPAIR_DISPATCH_RESCUE_GRACE_SECS, + reserve_agent_workspace_unchanged_health_hold, resume_current_agent_workspace_repair_publish, + settle_agent_workspace_repair_dispatch_outcome, start_or_join_agent_workspace_repair, + transition_agent_workspace_repair_attempt, validate_agent_workspace_repair_target_lease, + AgentWorkspaceRepairDispatchOutcome, AgentWorkspaceRepairDispatchSettlement, + AgentWorkspaceRepairPublishResumeOutcome, AgentWorkspaceRepairStartOutcome, + AgentWorkspaceRepairStartRequest, AgentWorkspaceRepairTransitionOutcome, + ORPHANED_REPAIR_DISPATCH_RESCUE_GRACE_SECS, }; use crate::application::chat_service::{ChatService, SendMessageOptions, SendQueuePolicy}; use crate::application::{AppState, GitService}; @@ -227,20 +232,26 @@ async fn reconcile_agent_workspace_repair_attempt( match current.phase { AgentWorkspaceRepairPhase::Dispatching => { - let active = match current.reserved_agent_run_id.as_ref() { - Some(run_id) => state - .agent_run_repo - .get_by_id(run_id) - .await? - .is_some_and(|run| { - run.conversation_id == current.conversation_id && run.status.is_active() - }), - None => false, - }; - if active { - Ok(DurableRepairRecoveryOutcome::Active) - } else { - schedule_interrupted_dispatch_retry(state, current).await + match reserved_dispatch_liveness(state, ¤t).await? { + ReservedDispatchLiveness::Running => Ok(DurableRepairRecoveryOutcome::Active), + // The reserved worker ran and ended without completing the repair. That is a real + // interrupted delivery and settles immediately, exactly as before. + ReservedDispatchLiveness::Ended => { + schedule_interrupted_dispatch_retry(state, current).await + } + // The reservation is written before the run row exists, so "no run yet" is the + // normal state for the first moments of a dispatch. Treating it as interruption + // raced a live spawn by milliseconds in production and queued a duplicate + // delivery against the same attempt. + ReservedDispatchLiveness::Unobserved + if Utc::now() - current.updated_at + < Duration::seconds(ORPHANED_REPAIR_DISPATCH_RESCUE_GRACE_SECS) => + { + Ok(DurableRepairRecoveryOutcome::Noop) + } + ReservedDispatchLiveness::Unobserved => { + schedule_interrupted_dispatch_retry(state, current).await + } } } AgentWorkspaceRepairPhase::Repairing => { @@ -388,6 +399,13 @@ async fn retry_safe_ready_agent_workspace_repair_publish( { return Ok(DurableRepairRecoveryOutcome::Noop); } + // A health hold parks at Ready deliberately. Re-driving its publish continuation on a timer + // would spend the hold's retry budget against evidence that has not changed and then settle + // the generation, freeing the poller to dispatch a fresh one on the identical failure. + // Only the poller, comparing live GitHub health, may end a hold. + if agent_workspace_repair_is_health_held(¤t) { + return Ok(DurableRepairRecoveryOutcome::Noop); + } let streak = automatic_ready_repair_streak(¤t); if streak >= MAX_AUTO_RETRY_READY_REPAIR_STREAK { @@ -554,6 +572,28 @@ async fn retry_safe_blocked_agent_workspace_repair( else { return Ok(DurableRepairRecoveryOutcome::Noop); }; + // Spawning a successor is the most expensive thing this pass can do, so a PR autofix + // generation must earn it against live PR health rather than against the streak counter alone. + // Blocker text is deliberately not consulted: it is free-form agent prose, not evidence. + let carryover_pr_autofix_evidence = if current.source == AgentWorkspaceRepairSource::PrAutofix { + match evaluate_pr_autofix_successor(state, ¤t, &workspace).await { + PrAutofixSuccessorDecision::Proceed(carryover) => carryover, + PrAutofixSuccessorDecision::HoldUnchanged => { + return hold_unchanged_pr_autofix_health(state, current, &workspace).await; + } + PrAutofixSuccessorDecision::Withhold(reason) => { + tracing::info!( + conversation_id = current.conversation_id.as_str(), + attempt_id = current.id.as_str(), + reason, + "Withholding a blocked PR autofix successor; no current failure identity authorizes one" + ); + return Ok(DurableRepairRecoveryOutcome::Noop); + } + } + } else { + None + }; let marker = format!("{AUTO_RETRY_BLOCKED_REPAIR_REASON_PREFIX}{}", streak + 1); let start = start_or_join_agent_workspace_repair( Arc::clone(&state.agent_workspace_repair_repo), @@ -569,6 +609,7 @@ async fn retry_safe_blocked_agent_workspace_repair( summary: "Automatically retrying the blocked workspace repair.".to_string(), auto_merge_current: workspace.pr_auto_merge_current, retry_blocked: true, + carryover_pr_autofix_evidence, }, ) .await?; @@ -584,6 +625,32 @@ async fn retry_safe_blocked_agent_workspace_repair( } } +/// Parks a blocked PR autofix generation whose failure identity has not moved. The hold is a +/// visible state, not a silent skip: it appends a publication event and leaves the generation +/// where the poller can settle it once GitHub reports different health. +async fn hold_unchanged_pr_autofix_health( + state: &AppState, + current: AgentWorkspaceRepairAttempt, + workspace: &AgentConversationWorkspace, +) -> AppResult { + let summary = "GitHub still reports the same failing PR health this repair was dispatched for. RalphX is holding the repair instead of running another fixer generation on identical evidence."; + match reserve_agent_workspace_unchanged_health_hold( + Arc::clone(&state.agent_workspace_repair_repo), + current, + summary, + workspace.pr_auto_merge_current, + ) + .await? + { + AgentWorkspaceRepairTransitionOutcome::Applied(attempt) => { + release_repair_lease_if_settled_boundary(state, &attempt).await?; + Ok(DurableRepairRecoveryOutcome::Noop) + } + AgentWorkspaceRepairTransitionOutcome::Stale(_) + | AgentWorkspaceRepairTransitionOutcome::Missing => Ok(DurableRepairRecoveryOutcome::Stale), + } +} + fn repair_attempt_has_target_lease(attempt: &AgentWorkspaceRepairAttempt) -> bool { attempt.git_common_dir.is_some() || attempt.target_ref.is_some() @@ -591,6 +658,38 @@ fn repair_attempt_has_target_lease(attempt: &AgentWorkspaceRepairAttempt) -> boo || attempt.target_lease_epoch.is_some() } +/// What the durable reservation's agent run proves about an in-flight dispatch. +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +enum ReservedDispatchLiveness { + /// The exact reserved run exists on this conversation and is still running. + Running, + /// The exact reserved run exists on this conversation and has already terminated. + Ended, + /// No run row proves anything yet: the reservation has no run id, the row has not been + /// written, or the id resolves to a different conversation. + Unobserved, +} + +async fn reserved_dispatch_liveness( + state: &AppState, + attempt: &AgentWorkspaceRepairAttempt, +) -> AppResult { + let Some(run_id) = attempt.reserved_agent_run_id.as_ref() else { + return Ok(ReservedDispatchLiveness::Unobserved); + }; + let Some(run) = state.agent_run_repo.get_by_id(run_id).await? else { + return Ok(ReservedDispatchLiveness::Unobserved); + }; + if run.conversation_id != attempt.conversation_id { + return Ok(ReservedDispatchLiveness::Unobserved); + } + Ok(if run.status.is_active() { + ReservedDispatchLiveness::Running + } else { + ReservedDispatchLiveness::Ended + }) +} + /// A delivery that failed before a trusted repair worker ran is recoverable. The exact /// `Dispatching` snapshot still owns the persisted canonical lease, so scheduling the next due /// retry cannot race a successor or turn an unknown delivery into a second agent run. @@ -621,6 +720,26 @@ async fn schedule_interrupted_dispatch_retry( } } +pub(super) const DEFAULT_REPAIR_DISPATCH_CONTEXT: &str = + "The current durable workspace repair still needs attention."; + +/// `pending_reasons` carries internal scheduling markers (`auto_retry_blocked_repair:2`) alongside +/// human-authored context. Only the latter belongs in an agent assignment; a marker rendered as +/// "Context:" tells the recipient nothing about what actually needs repairing. +pub(super) fn human_repair_dispatch_context( + attempt: &AgentWorkspaceRepairAttempt, +) -> Option<&str> { + attempt + .pending_reasons + .iter() + .rev() + .map(String::as_str) + .find(|reason| { + !reason.starts_with(AUTO_RETRY_BLOCKED_REPAIR_REASON_PREFIX) + && !reason.starts_with(AUTO_RETRY_READY_REPAIR_REASON_PREFIX) + }) +} + fn due_repair_dispatch_message( attempt: &AgentWorkspaceRepairAttempt, workspace: &AgentConversationWorkspace, @@ -634,11 +753,7 @@ fn due_repair_dispatch_message( "Resolve the workspace publish problem and commit the repaired workspace so the durable publish continuation can resume." } }; - let reason = attempt - .pending_reasons - .last() - .map(String::as_str) - .unwrap_or("The current durable workspace repair still needs attention."); + let reason = human_repair_dispatch_context(attempt).unwrap_or(DEFAULT_REPAIR_DISPATCH_CONTEXT); format!( "{continuation}\n\nInspect the current workspace state before changing files. When the repair is committed, use the available repair-completion tool.\n\nContext: {reason}\nWorkspace branch: {}\nBase ref: {}", workspace.branch_name, attempt.target_base_ref @@ -773,6 +888,20 @@ async fn reserve_and_deliver_repair_dispatch( settlement_summary: &str, ) -> AppResult { let run_id = AgentRunId::new(); + // Runtime continuity is resolved before the reservation so a repository failure cannot strand + // an attempt in `Dispatching` with no delivery attached to it. + let pr_autofix_options = if attempt.source == AgentWorkspaceRepairSource::PrAutofix { + Some( + crate::application::services::pr_merge_poller::agent_workspace_pr_fixer_send_options( + &workspace, + &working_directory, + Some(&state.agent_run_repo), + ) + .await?, + ) + } else { + None + }; let reserved = match reserve_agent_workspace_repair_dispatch( Arc::clone(&state.agent_workspace_repair_repo), Arc::clone(&state.branch_update_repo), @@ -790,12 +919,20 @@ async fn reserve_and_deliver_repair_dispatch( return Ok(DurableRepairRecoveryOutcome::Stale); } }; - let service = state.build_chat_service(); - let delivery = service - .send_message( - ChatContextType::Project, - workspace.project_id.as_str(), - &due_repair_dispatch_message(&reserved, &workspace), + // Source, not phase, decides who owns the work. A PR autofix generation is always the PR + // fixer's, so retry, rescue, and blocked-successor redelivery cannot hand PR work to the + // generic workspace repairer, whose tooling cannot classify a PR failure at all. + let (message, options) = match pr_autofix_options { + Some(mut options) => { + options.preallocated_agent_run_id = Some(run_id.clone()); + options.queue_policy = SendQueuePolicy::RequireImmediateStart; + ( + due_pr_autofix_redispatch_message(&reserved, &workspace), + options, + ) + } + None => ( + due_repair_dispatch_message(&reserved, &workspace), SendMessageOptions { preallocated_agent_run_id: Some(run_id.clone()), queue_policy: SendQueuePolicy::RequireImmediateStart, @@ -806,6 +943,15 @@ async fn reserve_and_deliver_repair_dispatch( preserve_conversation_provider_session_ref: true, ..Default::default() }, + ), + }; + let service = state.build_chat_service(); + let delivery = service + .send_message( + ChatContextType::Project, + workspace.project_id.as_str(), + &message, + options, ) .await; let settlement = classify_agent_workspace_repair_delivery( diff --git a/src-tauri/src/application/agent_workspace_publish_recovery/pr_autofix_redelivery.rs b/src-tauri/src/application/agent_workspace_publish_recovery/pr_autofix_redelivery.rs new file mode 100644 index 0000000000..0ecfd4e666 --- /dev/null +++ b/src-tauri/src/application/agent_workspace_publish_recovery/pr_autofix_redelivery.rs @@ -0,0 +1,207 @@ +//! PR-autofix-specific halves of durable repair redelivery. +//! +//! A PR autofix generation is owned by the PR fixer, not the generic workspace repairer, and its +//! successors are only worth spending when GitHub reports something new. Both concerns are +//! evidence-driven rather than phase-driven, so they live beside the recovery reconciler instead +//! of inside it. + +use crate::application::agent_conversation_workspace::resolve_effective_agent_conversation_workspace_path; +use crate::application::agent_workspace_publish_repair_state::PrAutofixCarryover; +use crate::application::services::pr_merge_poller::classify_agent_workspace_pr_autofix_issue; +use crate::application::AppState; +use crate::domain::entities::{AgentConversationWorkspace, AgentWorkspaceRepairAttempt}; + +use super::durable_attempt_recovery::{ + human_repair_dispatch_context, DEFAULT_REPAIR_DISPATCH_CONTEXT, +}; + +/// Whether a blocked PR autofix generation has earned another agent generation. +#[derive(Debug, Clone, PartialEq, Eq)] +pub(super) enum PrAutofixSuccessorDecision { + /// A successor is authorized. Carries freshly observed PR evidence when GitHub could be read, + /// so downstream fingerprint suppression has something to compare on the next round. + Proceed(Option), + /// GitHub reports the exact same failure identity. Another generation would re-read identical + /// evidence and can only repeat the previous outcome, so the attempt parks instead. + HoldUnchanged, + /// Nothing observed here authorizes spending an agent. Covers unresolvable PR identity, + /// health-fetch failure, and a PR that no longer reports any failing issue to fix. + Withhold(&'static str), +} + +/// Resolves the exact PR this workspace's autofix work belongs to. Edit-mode workspaces own their +/// publication PR directly; Ideation-mode workspaces borrow it from the linked plan branch, which +/// must still be the one this workspace is bound to. +async fn resolve_pr_autofix_pr_number( + state: &AppState, + workspace: &AgentConversationWorkspace, +) -> Option { + if let Some(pr_number) = workspace.publication_pr_number { + return Some(pr_number); + } + let plan_branch_id = workspace.linked_plan_branch_id.as_ref()?; + let plan_branch = state + .plan_branch_repo + .get_by_id(plan_branch_id) + .await + .ok() + .flatten()?; + if workspace.linked_ideation_session_id.as_ref() != Some(&plan_branch.session_id) { + return None; + } + plan_branch.pr_number +} + +/// True when the workspace's base has moved since this generation was targeted. That is new input +/// for the repair independent of anything GitHub reports about the PR, so it authorizes a +/// successor on its own. +fn repair_base_advanced( + current: &AgentWorkspaceRepairAttempt, + workspace: &AgentConversationWorkspace, +) -> bool { + let (Some(target), Some(workspace_base)) = ( + current.target_base_commit.as_deref(), + workspace.base_commit.as_deref(), + ) else { + return false; + }; + !target.trim().is_empty() && !workspace_base.trim().is_empty() && target != workspace_base +} + +/// Decides whether a blocked PR autofix attempt may start a successor generation. +/// +/// The gate is deliberately narrow. It applies only when this generation was dispatched against an +/// exact observed PR failure and nothing else has moved — the shape that burned four Opus +/// generations on one unchanged failing check. Once it does apply, every failure mode returns +/// `Withhold`: a repository error, an unreachable GitHub, or an unresolvable PR must never look +/// like "health changed", because that is the one answer that authorizes spending an agent. +pub(super) async fn evaluate_pr_autofix_successor( + state: &AppState, + current: &AgentWorkspaceRepairAttempt, + workspace: &AgentConversationWorkspace, +) -> PrAutofixSuccessorDecision { + // No prior failure identity means this generation was never dispatched against an observed PR + // failure (legacy rows, base-update blockers routed through a PR autofix attempt). There is + // nothing to compare, so the existing retry behavior stands. + if current.pr_autofix_health_fingerprint.is_none() { + return PrAutofixSuccessorDecision::Proceed(None); + } + // A moved base is new input for the repair even when the PR still reports the same failure. + if repair_base_advanced(current, workspace) { + return PrAutofixSuccessorDecision::Proceed(None); + } + let Some(github) = state.github_service.as_ref() else { + return PrAutofixSuccessorDecision::Withhold("github_service_unavailable"); + }; + let Some(pr_number) = resolve_pr_autofix_pr_number(state, workspace).await else { + return PrAutofixSuccessorDecision::Withhold("pr_number_unresolved"); + }; + let project = match state.project_repo.get_by_id(&workspace.project_id).await { + Ok(Some(project)) => project, + Ok(None) => return PrAutofixSuccessorDecision::Withhold("project_missing"), + Err(error) => { + tracing::warn!( + conversation_id = workspace.conversation_id.as_str(), + %error, + "Blocked PR autofix successor evaluation could not load its project" + ); + return PrAutofixSuccessorDecision::Withhold("project_unreadable"); + } + }; + let resolved = match resolve_effective_agent_conversation_workspace_path( + &project, + workspace, + state.plan_branch_repo.as_ref(), + ) + .await + { + Ok(resolved) => resolved, + Err(error) => { + tracing::warn!( + conversation_id = workspace.conversation_id.as_str(), + pr_number, + %error, + "Blocked PR autofix successor evaluation could not resolve its workspace path" + ); + return PrAutofixSuccessorDecision::Withhold("workspace_path_unresolved"); + } + }; + let health = match github.fetch_pr_health(&resolved.path, pr_number).await { + Ok(health) => health, + Err(error) => { + tracing::warn!( + conversation_id = workspace.conversation_id.as_str(), + pr_number, + %error, + "Blocked PR autofix successor evaluation could not read current PR health" + ); + return PrAutofixSuccessorDecision::Withhold("pr_health_unreadable"); + } + }; + let Some(issue) = classify_agent_workspace_pr_autofix_issue(pr_number, &health) else { + // Nothing is failing on the PR any more. A fixer generation dispatched now would have + // nothing to fix, which is exactly the wasted generation this path exists to prevent. + return PrAutofixSuccessorDecision::Withhold("pr_issue_resolved"); + }; + if current.pr_autofix_health_fingerprint.as_deref() == Some(issue.classification.as_str()) { + return PrAutofixSuccessorDecision::HoldUnchanged; + } + PrAutofixSuccessorDecision::Proceed(Some(PrAutofixCarryover { + dispatch_head_commit: health.sync_state.head_ref_oid.clone(), + health_fingerprint: Some(issue.classification), + })) +} + +/// A redelivered PR autofix is addressed to the PR fixer, so the assignment must carry the same PR +/// identity and completion contract the poller's first dispatch uses. A redelivery is also not a +/// fresh dispatch: an earlier generation may already have committed part of the work, so the +/// recipient must re-observe GitHub before changing anything. +pub(super) fn due_pr_autofix_redispatch_message( + attempt: &AgentWorkspaceRepairAttempt, + workspace: &AgentConversationWorkspace, +) -> String { + let mut out = String::new(); + match workspace.publication_pr_number { + Some(pr_number) => out.push_str(&format!( + "RalphX is redelivering an interrupted PR fix for GitHub PR #{pr_number} on this agent workspace.\n\n" + )), + None => out + .push_str("RalphX is redelivering an interrupted PR fix for this agent workspace.\n\n"), + } + out.push_str( + "Re-observe the current state before changing anything: an earlier fixer generation may already have committed part of this work, and CI may have moved on since the failure was recorded. Inspect the live checks first, then fix only what is still broken.\n\n", + ); + out.push_str(&format!( + "Conversation ID: {}\n", + workspace.conversation_id.as_str() + )); + out.push_str(&format!("Workspace branch: {}\n", workspace.branch_name)); + if let Some(pr_url) = workspace.publication_pr_url.as_deref() { + out.push_str(&format!("Pull request: {pr_url}\n")); + } + if let Some(fingerprint) = attempt.pr_autofix_health_fingerprint.as_deref() { + out.push_str(&format!("Last observed failure fingerprint: {fingerprint}\n")); + } + out.push_str(&format!( + "Context: {}\n", + human_repair_dispatch_context(attempt).unwrap_or(DEFAULT_REPAIR_DISPATCH_CONTEXT) + )); + out.push_str( + "\nWhen you are done, call `complete_agent_workspace_pr_fix` with an honest resolution:\n", + ); + out.push_str("- `fixed` with `fix_commit_sha` when you committed a real fix\n"); + out.push_str( + "- `transient_ci` when the failure is infrastructure/flake and a rerun is the right action\n", + ); + out.push_str( + "- `pre_existing_on_base` when the same failure already exists on the base branch and this PR did not cause it\n", + ); + out.push_str("- `needs_human` when the problem needs a human decision\n"); + out.push_str( + "Never report `fixed` without a commit, and never invent a change just to have something to report.\n", + ); + out.push_str( + "\nStart by calling `get_agent_workspace_pr_fix_context` with the conversation ID above.", + ); + out +} diff --git a/src-tauri/src/application/agent_workspace_publish_recovery_tests.rs b/src-tauri/src/application/agent_workspace_publish_recovery_tests.rs index 7e251d22a6..ced5bff073 100644 --- a/src-tauri/src/application/agent_workspace_publish_recovery_tests.rs +++ b/src-tauri/src/application/agent_workspace_publish_recovery_tests.rs @@ -1552,6 +1552,7 @@ wait "$stdin_drain_pid" 2>/dev/null || true summary: "Retry blocked repair.".to_string(), auto_merge_current: None, retry_blocked: true, + carryover_pr_autofix_evidence: None, }, ) .await @@ -4240,3 +4241,370 @@ mod extracted_inline_tests { assert_eq!(workspace.pr_supervision_status.as_deref(), Some("blocked")); } } + +// --- Unattended repair loop regressions ------------------------------------------------------- +// +// Production incident 2026-07-31 (PR #934): four Opus generations re-validated a clean workspace +// because durable redelivery addressed the generic repairer, successors carried no failure +// identity, and a live dispatch was settled as "interrupted" 43 ms after spawn. + +fn failing_check_pr_health(head: &str, check_name: &str) -> crate::domain::services::PrHealth { + crate::domain::services::PrHealth { + sync_state: crate::domain::services::PrSyncState { + status: crate::domain::services::PrStatus::Open, + merge_state_status: None, + mergeable: Some(crate::domain::services::github_service::PrMergeableState::Mergeable), + is_draft: false, + head_ref_name: "ralphx/test/publish-recovery".to_string(), + base_ref_name: "main".to_string(), + head_ref_oid: Some(head.to_string()), + base_ref_oid: Some("base-sha".to_string()), + }, + review_decision: None, + checks: vec![crate::domain::services::github_service::PrHealthCheck { + name: check_name.to_string(), + status: Some("completed".to_string()), + conclusion: Some("failure".to_string()), + details_url: Some("https://github.com/owner/repo/actions/runs/1".to_string()), + }], + issue_comments: Vec::new(), + auto_merge_request: None, + } +} + +fn health_fingerprint(pr_number: i64, health: &crate::domain::services::PrHealth) -> String { + crate::application::services::pr_merge_poller::classify_agent_workspace_pr_autofix_issue( + pr_number, health, + ) + .expect("failing check classifies as a PR autofix issue") + .classification +} + +/// Rewrites the current attempt into a blocked PR autofix generation carrying an exact failure +/// identity, aged past the automatic blocked-retry backoff. +async fn block_pr_autofix_attempt_with_fingerprint( + state: &AppState, + conversation_id: &ChatConversationId, + fingerprint: Option, +) -> AgentWorkspaceRepairAttempt { + let mut attempt = state + .agent_workspace_repair_repo + .get_current_repair_attempt(conversation_id) + .await + .expect("load attempt to block") + .expect("attempt exists to block"); + let workspace = state + .agent_conversation_workspace_repo + .get_by_conversation_id(conversation_id) + .await + .expect("load workspace for blocked fixture") + .expect("workspace exists for blocked fixture"); + let expected_phase = attempt.phase; + let expected_updated_at = attempt.updated_at; + attempt.source = AgentWorkspaceRepairSource::PrAutofix; + attempt.phase = AgentWorkspaceRepairPhase::Blocked; + attempt.pr_autofix_health_fingerprint = fingerprint; + attempt.pr_autofix_dispatch_head_commit = Some("dispatch-head".to_string()); + // Base parity keeps the base-advance escape hatch out of the way; these fixtures exercise the + // health comparison itself. + attempt.target_base_commit = workspace.base_commit.clone(); + attempt.blocker = Some("transient_ci".to_string()); + attempt.updated_at = chrono::Utc::now() - chrono::Duration::seconds(1_000); + match state + .agent_workspace_repair_repo + .transition_repair_attempt(AgentWorkspaceRepairAttemptTransition { + attempt, + expected_phase, + expected_updated_at, + next_phase: AgentWorkspaceRepairPhase::Blocked, + compatibility_projection: None, + events: Vec::new(), + }) + .await + .expect("block PR autofix attempt") + { + AgentWorkspaceRepairAttemptTransitionOutcome::Applied(attempt) => attempt, + outcome => panic!("blocking PR autofix attempt must apply, got {outcome:?}"), + } +} + +async fn latest_sent_repair_message(state: &AppState, conversation_id: &ChatConversationId) -> String { + let messages = state + .chat_message_repo + .get_by_conversation(conversation_id) + .await + .expect("load delivered repair messages"); + messages + .iter() + .rev() + .find(|message| message.role == crate::domain::entities::MessageRole::User) + .map(|message| message.content.clone()) + .expect("a repair assignment was delivered") +} + +#[cfg(unix)] +#[tokio::test] +#[allow(clippy::await_holding_lock)] +async fn pr_autofix_redelivery_addresses_the_pr_fixer_with_pr_context() { + let _environment_lock = crate::infrastructure::tool_paths::TEST_ENV_MUTEX + .lock() + .expect("lock test environment"); + let _spawn_permission = TestEnvVarGuard::set("RALPHX_ALLOW_CLAUDE_SPAWN_IN_TESTS", "1"); + let (state, conversation_id, _worktree_parent, _project_dir) = seed_orphaned_repair_dispatch( + 120, + r#"#!/bin/sh +cat >/dev/null & +stdin_drain_pid=$! +printf '%s\n' '{"type":"result","session_id":"pr-fixer-redelivery","is_error":false,"result":"fix started","cost_usd":0.0}' +sleep 1 +kill "$stdin_drain_pid" 2>/dev/null || true +wait "$stdin_drain_pid" 2>/dev/null || true +"#, + ) + .await; + let mut attempt = state + .agent_workspace_repair_repo + .get_current_repair_attempt(&conversation_id) + .await + .expect("load seeded attempt") + .expect("seeded attempt exists"); + let expected_updated_at = attempt.updated_at; + attempt.source = AgentWorkspaceRepairSource::PrAutofix; + attempt.pr_autofix_health_fingerprint = Some("github_pr_autofix:684:checks:rust".to_string()); + // Internal scheduling markers must never surface to the recipient as repair context. + attempt.pending_reasons = vec!["auto_retry_blocked_repair:1".to_string()]; + attempt.updated_at = chrono::Utc::now() - chrono::Duration::seconds(61); + assert!(matches!( + state + .agent_workspace_repair_repo + .transition_repair_attempt(AgentWorkspaceRepairAttemptTransition { + attempt, + expected_phase: AgentWorkspaceRepairPhase::Requested, + expected_updated_at, + next_phase: AgentWorkspaceRepairPhase::Requested, + compatibility_projection: None, + events: Vec::new(), + }) + .await + .expect("age PR autofix orphan"), + AgentWorkspaceRepairAttemptTransitionOutcome::Applied(_) + )); + + assert_eq!( + recover_agent_workspace_repair_attempts_for_state(&state) + .await + .expect("rescue orphaned PR autofix dispatch"), + 1 + ); + + let message = latest_sent_repair_message(&state, &conversation_id).await; + assert!( + message.contains("redelivering an interrupted PR fix"), + "PR autofix redelivery must use the PR fixer assignment, got: {message}" + ); + assert!(message.contains("complete_agent_workspace_pr_fix")); + assert!(message.contains("get_agent_workspace_pr_fix_context")); + assert!(message.contains("PR #684")); + assert!(message.contains("github_pr_autofix:684:checks:rust")); + assert!( + !message.contains("use the available repair-completion tool"), + "PR autofix redelivery must not reuse the generic workspace repair assignment" + ); + assert!( + !message.contains("auto_retry_blocked_repair"), + "internal scheduling markers must not leak into the assignment: {message}" + ); +} + +#[cfg(unix)] +#[tokio::test] +#[allow(clippy::await_holding_lock)] +async fn non_pr_autofix_redelivery_keeps_the_generic_workspace_repair_assignment() { + let _environment_lock = crate::infrastructure::tool_paths::TEST_ENV_MUTEX + .lock() + .expect("lock test environment"); + let _spawn_permission = TestEnvVarGuard::set("RALPHX_ALLOW_CLAUDE_SPAWN_IN_TESTS", "1"); + let (state, conversation_id, _worktree_parent, _project_dir) = seed_orphaned_repair_dispatch( + 121, + r#"#!/bin/sh +cat >/dev/null & +stdin_drain_pid=$! +printf '%s\n' '{"type":"result","session_id":"generic-repair-redelivery","is_error":false,"result":"repair started","cost_usd":0.0}' +sleep 1 +kill "$stdin_drain_pid" 2>/dev/null || true +wait "$stdin_drain_pid" 2>/dev/null || true +"#, + ) + .await; + age_requested_repair_attempt(&state, &conversation_id).await; + + assert_eq!( + recover_agent_workspace_repair_attempts_for_state(&state) + .await + .expect("rescue orphaned publish dispatch"), + 1 + ); + + let message = latest_sent_repair_message(&state, &conversation_id).await; + assert!(message.contains("use the available repair-completion tool")); + assert!( + !message.contains("complete_agent_workspace_pr_fix"), + "a publish repair must not be addressed to the PR fixer: {message}" + ); +} + +#[tokio::test] +async fn blocked_pr_autofix_with_unchanged_health_parks_without_spawning() { + let mut state = AppState::new_test(); + let conversation_id = conversation_id(122); + state + .agent_conversation_workspace_repo + .create_or_update(needs_agent_workspace(conversation_id.clone())) + .await + .expect("seed unchanged-health workspace"); + state + .project_repo + .create({ + let mut project = Project::new( + "unchanged health project".to_string(), + "/tmp/ralphx-unchanged-health".to_string(), + ); + project.id = project_id(); + project + }) + .await + .expect("seed unchanged-health project"); + start_blocked_pr_autofix_generation(&state, &conversation_id).await; + + let health = failing_check_pr_health("head-unchanged", "Rust Tests"); + let fingerprint = health_fingerprint(684, &health); + let github = Arc::new(crate::tests::mock_github_service::MockGithubService::new()); + github.state().fetch_pr_health_result = Some(Ok(health)); + state.github_service = Some(github.clone() as Arc); + + let blocked = + block_pr_autofix_attempt_with_fingerprint(&state, &conversation_id, Some(fingerprint.clone())) + .await; + + assert_eq!( + recover_agent_workspace_repair_attempts_for_state(&state) + .await + .expect("evaluate blocked PR autofix successor"), + 0 + ); + + let held = state + .agent_workspace_repair_repo + .get_current_repair_attempt(&conversation_id) + .await + .expect("load held attempt") + .expect("held attempt remains current"); + assert_eq!(held.generation, blocked.generation, "no successor generation"); + assert_eq!(held.phase, AgentWorkspaceRepairPhase::Ready); + assert!(held.pending_reasons.iter().any(|reason| reason + == crate::application::agent_workspace_publish_repair_state::UNCHANGED_HEALTH_REPAIR_REASON)); + assert_eq!(held.pr_autofix_health_fingerprint.as_deref(), Some(fingerprint.as_str())); + assert!( + state + .agent_run_repo + .get_by_conversation(&conversation_id) + .await + .expect("list runs") + .is_empty(), + "an unchanged failure fingerprint must not spend another agent generation" + ); + let events = state + .agent_conversation_workspace_repo + .list_publication_events(&conversation_id) + .await + .expect("list publication events"); + assert!( + events.iter().any(|event| event.step + == crate::application::agent_workspace_publish_repair_state::REPAIR_FINGERPRINT_HOLD_STEP), + "the hold must be user visible, never a silent skip" + ); + + // A parked hold must survive the Ready auto-retry lane; only the poller may end it. + assert_eq!( + recover_agent_workspace_repair_attempts_for_state(&state) + .await + .expect("re-run recovery over the held attempt"), + 0 + ); + let still_held = state + .agent_workspace_repair_repo + .get_current_repair_attempt(&conversation_id) + .await + .expect("reload held attempt") + .expect("held attempt is still current"); + assert_eq!(still_held.phase, AgentWorkspaceRepairPhase::Ready); + assert!(still_held.settled_at.is_none()); +} + +#[tokio::test] +async fn blocked_pr_autofix_without_provable_health_withholds_the_successor() { + let state = AppState::new_test(); + let conversation_id = conversation_id(123); + state + .agent_conversation_workspace_repo + .create_or_update(needs_agent_workspace(conversation_id.clone())) + .await + .expect("seed unprovable-health workspace"); + start_blocked_pr_autofix_generation(&state, &conversation_id).await; + block_pr_autofix_attempt_with_fingerprint( + &state, + &conversation_id, + Some("github_pr_autofix:684:checks:rust".to_string()), + ) + .await; + + // No GitHub service: the current failure identity cannot be proven, so no agent may be spent. + assert!(state.github_service.is_none()); + assert_eq!( + recover_agent_workspace_repair_attempts_for_state(&state) + .await + .expect("withhold successor without provable health"), + 0 + ); + let unchanged = state + .agent_workspace_repair_repo + .get_current_repair_attempt(&conversation_id) + .await + .expect("load withheld attempt") + .expect("withheld attempt remains current"); + assert_eq!(unchanged.phase, AgentWorkspaceRepairPhase::Blocked); + assert!(state + .agent_run_repo + .get_by_conversation(&conversation_id) + .await + .expect("list runs") + .is_empty()); +} + +async fn start_blocked_pr_autofix_generation(state: &AppState, conversation_id: &ChatConversationId) { + let started = state + .agent_workspace_repair_repo + .start_or_join_repair_attempt(StartOrJoinAgentWorkspaceRepairAttempt { + attempt: AgentWorkspaceRepairAttempt::new( + conversation_id.clone(), + AgentWorkspaceRepairSource::PrAutofix, + AgentWorkspaceRepairContinuation::ResumePrSupervision, + "main", + false, + true, + false, + None, + chrono::Utc::now(), + ), + reason: "pr autofix generation".to_string(), + verified_newer_base: false, + compatibility_projection: None, + events: Vec::new(), + }) + .await + .expect("start PR autofix generation"); + assert!(matches!( + started, + StartOrJoinAgentWorkspaceRepairAttemptOutcome::Started(_) + )); +} diff --git a/src-tauri/src/application/agent_workspace_publish_repair_state.rs b/src-tauri/src/application/agent_workspace_publish_repair_state.rs index 67b36483ce..8884794c55 100644 --- a/src-tauri/src/application/agent_workspace_publish_repair_state.rs +++ b/src-tauri/src/application/agent_workspace_publish_repair_state.rs @@ -64,6 +64,11 @@ pub(crate) const MAX_AGENT_WORKSPACE_REPAIR_DISPATCH_RETRIES: u32 = 3; pub(crate) const MAX_AGENT_WORKSPACE_CI_RERUN_RETRIES: u32 = 3; pub(crate) const NEEDS_HUMAN_REPAIR_REASON: &str = "pr_autofix_needs_human"; pub(crate) const PRE_EXISTING_ON_BASE_REPAIR_REASON: &str = "pr_autofix_pre_existing_on_base"; +/// Held because GitHub still reports the exact failure the previous generation was dispatched for. +/// Distinct from `PRE_EXISTING_ON_BASE_REPAIR_REASON`: RalphX has not proven anything about the +/// base branch, only that spending another agent generation on identical evidence is waste. +pub(crate) const UNCHANGED_HEALTH_REPAIR_REASON: &str = "pr_autofix_unchanged_health"; +pub(crate) const REPAIR_FINGERPRINT_HOLD_STEP: &str = "repair_fingerprint_hold"; pub(crate) const ORPHANED_REPAIR_DISPATCH_RESCUE_GRACE_SECS: i64 = 60; const AGENT_WORKSPACE_REPAIR_DISPATCH_INITIAL_BACKOFF_SECS: i64 = 5; const AGENT_WORKSPACE_REPAIR_DISPATCH_MAX_BACKOFF_SECS: i64 = 60; @@ -93,6 +98,19 @@ pub(crate) struct AgentWorkspaceRepairStartRequest { pub summary: String, pub auto_merge_current: Option, pub retry_blocked: bool, + /// Backend-observed PR evidence carried onto a successor generation. Without it a successor + /// starts with no failure identity, and every fingerprint-based suppression downstream + /// silently disengages. + pub carryover_pr_autofix_evidence: Option, +} + +/// Exact PR evidence observed by the backend immediately before starting a successor generation. +/// Never model supplied and never copied blindly from the predecessor: a stale head or fingerprint +/// would make the successor look like it had already been evaluated against current GitHub state. +#[derive(Debug, Clone, Default, PartialEq, Eq)] +pub(crate) struct PrAutofixCarryover { + pub dispatch_head_commit: Option, + pub health_fingerprint: Option, } #[derive(Debug, Clone, PartialEq, Eq)] @@ -271,6 +289,10 @@ fn start_attempt_from_workspace( ); attempt.target_base_commit = request.target_base_commit.clone(); attempt.summary = Some(request.summary.clone()); + if let Some(carryover) = request.carryover_pr_autofix_evidence.as_ref() { + attempt.pr_autofix_dispatch_head_commit = carryover.dispatch_head_commit.clone(); + attempt.pr_autofix_health_fingerprint = carryover.health_fingerprint.clone(); + } attempt } @@ -528,14 +550,73 @@ pub(crate) async fn block_agent_workspace_repair_needs_human( .await } +/// True while a PR autofix generation is parked against a backend-derived health fingerprint. +/// Only the poller may end such a hold, and only after GitHub reports different health; no other +/// retry path may consume the hold's budget or settle it on a timer. +pub(crate) fn agent_workspace_repair_is_health_held( + attempt: &AgentWorkspaceRepairAttempt, +) -> bool { + attempt.pending_reasons.iter().any(|reason| { + reason == PRE_EXISTING_ON_BASE_REPAIR_REASON || reason == UNCHANGED_HEALTH_REPAIR_REASON + }) +} + /// Holds a PR autofix generation at a backend-derived health fingerprint without pretending the /// failing state was repaired. The poller settles it only after health changes. pub(crate) async fn reserve_agent_workspace_pre_existing_on_base( + repair_repo: Arc, + attempt: AgentWorkspaceRepairAttempt, + summary: &str, + auto_merge_current: Option, +) -> AppResult { + reserve_agent_workspace_repair_health_hold( + repair_repo, + attempt, + PRE_EXISTING_ON_BASE_REPAIR_REASON, + summary, + auto_merge_current, + Vec::new(), + ) + .await +} + +/// Parks the current PR autofix generation because GitHub still reports the exact failure it was +/// dispatched for. Spending another agent generation on unchanged evidence cannot produce new +/// information, so the hold replaces the successor rather than delaying it. +pub(crate) async fn reserve_agent_workspace_unchanged_health_hold( + repair_repo: Arc, + attempt: AgentWorkspaceRepairAttempt, + summary: &str, + auto_merge_current: Option, +) -> AppResult { + let event = AgentConversationWorkspacePublicationEvent::new( + attempt.conversation_id.clone(), + REPAIR_FINGERPRINT_HOLD_STEP, + "blocked", + summary, + attempt.pr_autofix_health_fingerprint.clone(), + ); + reserve_agent_workspace_repair_health_hold( + repair_repo, + attempt, + UNCHANGED_HEALTH_REPAIR_REASON, + summary, + auto_merge_current, + vec![event], + ) + .await +} + +async fn reserve_agent_workspace_repair_health_hold( repair_repo: Arc, mut attempt: AgentWorkspaceRepairAttempt, + hold_reason: &str, summary: &str, auto_merge_current: Option, + events: Vec, ) -> AppResult { + // A hold is only meaningful against an exact observed failure identity. Without one there is + // nothing for the poller to compare later, so refuse rather than park indefinitely. let Some(_) = attempt.pr_autofix_health_fingerprint.as_deref() else { return Ok(AgentWorkspaceRepairTransitionOutcome::Stale(attempt)); }; @@ -544,11 +625,9 @@ pub(crate) async fn reserve_agent_workspace_pre_existing_on_base( if !attempt .pending_reasons .iter() - .any(|reason| reason == PRE_EXISTING_ON_BASE_REPAIR_REASON) + .any(|reason| reason == hold_reason) { - attempt - .pending_reasons - .push(PRE_EXISTING_ON_BASE_REPAIR_REASON.to_string()); + attempt.pending_reasons.push(hold_reason.to_string()); } attempt.phase = AgentWorkspaceRepairPhase::Ready; attempt.summary = Some(summary.to_string()); @@ -562,7 +641,7 @@ pub(crate) async fn reserve_agent_workspace_pre_existing_on_base( expected_updated_at, next_phase: AgentWorkspaceRepairPhase::Ready, compatibility_projection: Some(projection), - events: Vec::new(), + events, }) .await .map(repair_attempt_transition_outcome) diff --git a/src-tauri/src/application/agent_workspace_publish_repair_state_tests.rs b/src-tauri/src/application/agent_workspace_publish_repair_state_tests.rs index 83c2ece366..07f4047b99 100644 --- a/src-tauri/src/application/agent_workspace_publish_repair_state_tests.rs +++ b/src-tauri/src/application/agent_workspace_publish_repair_state_tests.rs @@ -469,6 +469,7 @@ fn repair_start_request( summary: "Repair requested.".to_string(), auto_merge_current: None, retry_blocked: false, + carryover_pr_autofix_evidence: None, } } diff --git a/src-tauri/src/application/services/pr_merge_poller.rs b/src-tauri/src/application/services/pr_merge_poller.rs index d209cd5e38..8aedf4f1da 100644 --- a/src-tauri/src/application/services/pr_merge_poller.rs +++ b/src-tauri/src/application/services/pr_merge_poller.rs @@ -1968,6 +1968,7 @@ async fn route_agent_workspace_pr_conflict_repair_if_needed_with_repair_repo( summary: repair_summary.clone(), auto_merge_current: workspace.pr_auto_merge_current, retry_blocked: false, + carryover_pr_autofix_evidence: None, }, ) .await?; @@ -3309,6 +3310,7 @@ async fn dispatch_agent_workspace_pr_autofix( summary: dispatch.repair_summary.to_string(), auto_merge_current: auto_merge_before_reservation, retry_blocked: false, + carryover_pr_autofix_evidence: None, }, ) .await?; @@ -3763,7 +3765,9 @@ async fn agent_workspace_pr_autofix_repair_in_flight( .is_some()) } -async fn agent_workspace_pr_fixer_send_options( +/// Shared by the poller's first dispatch and by durable redelivery so a recovered PR autofix keeps +/// the same recipient agent and the same provider/model/effort continuity as its first generation. +pub(crate) async fn agent_workspace_pr_fixer_send_options( workspace: &AgentConversationWorkspace, working_directory: &Path, agent_run_repo: Option<&Arc>, diff --git a/src-tauri/src/commands/unified_chat_commands/mod.rs b/src-tauri/src/commands/unified_chat_commands/mod.rs index 062449d576..5ccef7a6ff 100644 --- a/src-tauri/src/commands/unified_chat_commands/mod.rs +++ b/src-tauri/src/commands/unified_chat_commands/mod.rs @@ -8849,7 +8849,7 @@ fn build_agent_workspace_repair_message_for_target( post_repair_action.failure_title().to_string(), String::new(), post_repair_action.repair_instruction().to_string(), - "After the repair is committed, call complete_agent_workspace_repair with the conversation ID, repair commit SHA, resolved base ref, resolved base commit, and summary." + "After the repair is committed, call complete_agent_workspace_repair with a summary; add a blocker if the repair cannot be completed safely, and use resolution to classify the outcome honestly." .to_string(), String::new(), format!("Error: {error}"), @@ -9237,6 +9237,7 @@ async fn mark_agent_workspace_failure_with_routing_and_action_classified( summary: post_repair_action.repair_requested_summary().to_string(), auto_merge_current: workspace.pr_auto_merge_current, retry_blocked, + carryover_pr_autofix_evidence: None, }, ) .await; From 7c5238ebaf13d815b3272a6b9c45d6b0fd182bf4 Mon Sep 17 00:00:00 2001 From: lazabogdan <6580668+lazabogdan@users.noreply.github.com> Date: Fri, 31 Jul 2026 15:31:26 +0300 Subject: [PATCH 02/29] fix(repair): honour unchanged-health holds at the poller dispatch gate Completes Phase 1 + Phase 2 of the unattended repair loop plan and closes an integration gap found while validating them: - The poller's PR-autofix dispatch gate keyed suppression on the literal pre-existing-on-base reason, so a generation parked by the new unchanged-health hold was re-dispatched on the very next poll. It now uses the shared `agent_workspace_repair_is_health_held` seam, so both hold kinds suppress identically. - The rerun-fingerprint comparison is now guarded by `ci_rerun_count > 0`. A health hold whose failure is not transient-CI shaped compared `None == None` and suppressed itself forever, even once GitHub reported something new. Test fixes and additions: - Successor-evaluation fixtures now seed a real project repo and worktree, because the production path resolves live PR health through the workspace path exactly like the poller does. - Interrupted-dispatch fixtures are aged past the spawn-grace window; they model a dead owner process, not a reservation whose run row is still being written. - New: a just-reserved dispatch is not settled interrupted, consumes no retry, queues no duplicate delivery, and still settles once aged. - New: an unchanged-health hold suppresses an identical fingerprint at the poller gate and redispatches when health changes. - New: internal `auto_retry_blocked_repair:N` markers never reach an agent assignment. Validation: 66 recovery lib tests, 120 poller lib tests, 37 repair-state lib tests, 46 suite_agent_workspace, 24 suite_http_handlers repair-completion, and 481 MCP server tests all pass. Co-Authored-By: Claude Opus 5 --- .../agent_workspace_publish_recovery_tests.rs | 256 ++++++++++++++++-- .../application/services/pr_merge_poller.rs | 21 +- .../services/pr_merge_poller_tests.rs | 137 +++++++++- 3 files changed, 376 insertions(+), 38 deletions(-) diff --git a/src-tauri/src/application/agent_workspace_publish_recovery_tests.rs b/src-tauri/src/application/agent_workspace_publish_recovery_tests.rs index ced5bff073..f346a67aae 100644 --- a/src-tauri/src/application/agent_workspace_publish_recovery_tests.rs +++ b/src-tauri/src/application/agent_workspace_publish_recovery_tests.rs @@ -175,6 +175,48 @@ impl Drop for TestEnvVarGuard { } } +/// A timestamp old enough that a reservation without a run row is a genuine interrupted delivery +/// rather than a dispatch whose run row has not been written yet. +fn aged_past_spawn_grace() -> chrono::DateTime { + chrono::Utc::now() + - chrono::Duration::seconds( + crate::application::agent_workspace_publish_repair_state::ORPHANED_REPAIR_DISPATCH_RESCUE_GRACE_SECS + + 60, + ) +} + +/// Ages the current attempt past the spawn-grace window without changing anything else about it. +async fn age_current_repair_attempt_past_spawn_grace( + state: &AppState, + conversation_id: &ChatConversationId, +) { + let mut attempt = state + .agent_workspace_repair_repo + .get_current_repair_attempt(conversation_id) + .await + .expect("load attempt to age") + .expect("attempt exists to age"); + let expected_phase = attempt.phase; + let expected_updated_at = attempt.updated_at; + attempt.updated_at = aged_past_spawn_grace(); + let outcome = state + .agent_workspace_repair_repo + .transition_repair_attempt(AgentWorkspaceRepairAttemptTransition { + attempt, + expected_phase, + expected_updated_at, + next_phase: expected_phase, + compatibility_projection: None, + events: Vec::new(), + }) + .await + .expect("age repair attempt"); + assert!(matches!( + outcome, + AgentWorkspaceRepairAttemptTransitionOutcome::Applied(_) + )); +} + fn needs_agent_workspace(conversation_id: ChatConversationId) -> AgentConversationWorkspace { let mut workspace = AgentConversationWorkspace::new( conversation_id, @@ -710,7 +752,9 @@ async fn recovery_ignores_nonterminal_run_hints_and_blocks_exhausted_ownerless_d crate::application::agent_workspace_publish_repair_state::AGENT_WORKSPACE_REPAIR_TARGET_IDENTITY_VERSION, ); exhausted.target_lease_epoch = Some(fencing_epoch); - exhausted.updated_at += chrono::Duration::microseconds(1); + // Aged past the spawn-grace window: this fixture is an ownerless dispatch left behind by a + // dead process, not a reservation whose run row has simply not been written yet. + exhausted.updated_at = aged_past_spawn_grace(); assert!(matches!( state .agent_workspace_repair_repo @@ -1089,6 +1133,118 @@ async fn startup_recovery_revalidates_a_clean_committed_validating_repair() { assert!(current.settled_at.is_none()); } +/// Production incident 2026-07-31: a live PR-fixer dispatch was settled `interrupted` 43 ms after +/// spawn because the reservation is written before the agent run row exists. The reservation alone +/// is not evidence of a dead worker, so a just-reserved dispatch must survive an immediate pass. +#[tokio::test] +async fn fresh_dispatch_reservation_is_not_settled_as_interrupted_before_its_run_row_exists() { + let state = AppState::new_test(); + let conversation_id = conversation_id(124); + state + .agent_conversation_workspace_repo + .create_or_update(needs_agent_workspace(conversation_id.clone())) + .await + .expect("seed fresh dispatch workspace"); + let started = state + .agent_workspace_repair_repo + .start_or_join_repair_attempt(StartOrJoinAgentWorkspaceRepairAttempt { + attempt: AgentWorkspaceRepairAttempt::new( + conversation_id.clone(), + AgentWorkspaceRepairSource::Publish, + AgentWorkspaceRepairContinuation::Publish, + "main", + false, + true, + false, + None, + chrono::Utc::now(), + ), + reason: "fresh dispatch".to_string(), + verified_newer_base: false, + compatibility_projection: None, + events: Vec::new(), + }) + .await + .expect("start fresh repair attempt"); + let StartOrJoinAgentWorkspaceRepairAttemptOutcome::Started(started) = started else { + panic!("fresh durable repair attempt must start"); + }; + let target_identity = GitTargetIdentity::new( + PathBuf::from("/tmp/ralphx-repair-fresh-dispatch"), + "refs/heads/ralphx/test/publish-recovery", + ) + .expect("valid canonical target identity"); + let dispatch = reserve_agent_workspace_repair_dispatch( + Arc::clone(&state.agent_workspace_repair_repo), + Arc::clone(&state.branch_update_repo), + target_identity, + started, + AgentRunId::from_string("fresh-dispatch-run"), + "dispatch fresh repair", + None, + ) + .await + .expect("reserve fresh repair delivery"); + assert!(matches!( + dispatch, + AgentWorkspaceRepairDispatchOutcome::Reserved(_) + )); + + // The agent is spawning right now; its run row has not been written yet. + assert_eq!( + recover_agent_workspace_repair_attempts_for_state(&state) + .await + .expect("recovery pass over a just-reserved dispatch"), + 0 + ); + let held = state + .agent_workspace_repair_repo + .get_current_repair_attempt(&conversation_id) + .await + .expect("load fresh dispatch") + .expect("fresh dispatch remains current"); + assert_eq!( + held.phase, + AgentWorkspaceRepairPhase::Dispatching, + "a spawning dispatch must not be settled as interrupted" + ); + assert_eq!(held.dispatch_count, 0, "no retry was consumed"); + assert!(held.next_dispatch_at.is_none(), "no duplicate delivery queued"); + assert_eq!( + held.reserved_agent_run_id, + Some(AgentRunId::from_string("fresh-dispatch-run")), + "the original reservation still owns the dispatch" + ); + assert!( + !state + .agent_conversation_workspace_repo + .list_publication_events(&conversation_id) + .await + .expect("load publication events") + .iter() + .any(|event| event.step == "repair_sent" && event.status == "retrying"), + "no interrupted-retry event may be emitted inside the spawn grace window" + ); + + // Past the grace window the same reservation is a genuine orphan and settles as before. + age_current_repair_attempt_past_spawn_grace(&state, &conversation_id).await; + assert_eq!( + recover_agent_workspace_repair_attempts_for_state(&state) + .await + .expect("recover the aged orphaned dispatch"), + 1 + ); + let retried = state + .agent_workspace_repair_repo + .get_current_repair_attempt(&conversation_id) + .await + .expect("load retried dispatch") + .expect("retried dispatch remains current"); + assert_eq!(retried.phase, AgentWorkspaceRepairPhase::Requested); + assert_eq!(retried.dispatch_count, 1); + assert!(retried.reserved_agent_run_id.is_none()); +} + #[tokio::test] async fn startup_recovery_schedules_one_due_retry_for_an_interrupted_repair_delivery() { let state = AppState::new_test(); @@ -1142,6 +1298,9 @@ async fn startup_recovery_schedules_one_due_retry_for_an_interrupted_repair_deli dispatch, AgentWorkspaceRepairDispatchOutcome::Reserved(_) )); + // Startup recovery runs after the process that owned this dispatch died, so the reservation is + // well past the spawn-grace window that protects a just-reserved delivery. + age_current_repair_attempt_past_spawn_grace(&state, &conversation_id).await; assert_eq!( recover_stale_agent_workspace_publish_repairs_for_state(&state) @@ -1641,6 +1800,18 @@ wait "$stdin_drain_pid" 2>/dev/null || true .iter() .any(|reason| reason == "auto_retry_blocked_repair:1")); assert!(successor.reserved_agent_run_id.is_some()); + + // The retry marker is internal scheduling bookkeeping. Rendering it as the assignment's + // "Context:" told the recipient nothing about what needed repairing. + let delivered = latest_sent_repair_message(&state, &conversation_id).await; + assert!( + !delivered.contains("auto_retry_blocked_repair"), + "internal retry markers must never reach an agent assignment: {delivered}" + ); + assert!( + delivered.contains("The current durable workspace repair still needs attention."), + "a marker-only reason list must fall back to human context: {delivered}" + ); } #[cfg(unix)] @@ -4248,8 +4419,8 @@ mod extracted_inline_tests { // because durable redelivery addressed the generic repairer, successors carried no failure // identity, and a live dispatch was settled as "interrupted" 43 ms after spawn. -fn failing_check_pr_health(head: &str, check_name: &str) -> crate::domain::services::PrHealth { - crate::domain::services::PrHealth { +fn failing_check_pr_health(head: &str, check_name: &str) -> crate::domain::services::github_service::PrHealth { + crate::domain::services::github_service::PrHealth { sync_state: crate::domain::services::PrSyncState { status: crate::domain::services::PrStatus::Open, merge_state_status: None, @@ -4272,7 +4443,7 @@ fn failing_check_pr_health(head: &str, check_name: &str) -> crate::domain::servi } } -fn health_fingerprint(pr_number: i64, health: &crate::domain::services::PrHealth) -> String { +fn health_fingerprint(pr_number: i64, health: &crate::domain::services::github_service::PrHealth) -> String { crate::application::services::pr_merge_poller::classify_agent_workspace_pr_autofix_issue( pr_number, health, ) @@ -4453,27 +4624,70 @@ wait "$stdin_drain_pid" 2>/dev/null || true ); } -#[tokio::test] -async fn blocked_pr_autofix_with_unchanged_health_parks_without_spawning() { - let mut state = AppState::new_test(); - let conversation_id = conversation_id(122); +/// Seeds a workspace whose path resolves the way production requires: a real project repository +/// with a real worktree checked out at the workspace branch. Successor evaluation reads live PR +/// health through that path, so a fixture without it can only ever exercise the withhold branch. +async fn seed_pr_autofix_health_workspace( + suffix: u8, +) -> ( + AppState, + ChatConversationId, + tempfile::TempDir, + tempfile::TempDir, +) { + let state = AppState::new_test(); + let worktree_parent = tempfile::tempdir().expect("create PR autofix worktree parent"); + let project_dir = tempfile::tempdir().expect("create PR autofix project directory"); + recovery_git(project_dir.path(), &["init", "-b", "main"]); + recovery_git( + project_dir.path(), + &["config", "user.email", "recovery@example.com"], + ); + recovery_git(project_dir.path(), &["config", "user.name", "Recovery Test"]); + std::fs::write(project_dir.path().join("README.md"), "base\n").expect("write base file"); + recovery_git(project_dir.path(), &["add", "README.md"]); + recovery_git(project_dir.path(), &["commit", "-m", "base"]); + + let conversation_id = conversation_id(suffix); + let mut project = Project::new( + "pr autofix health project".to_string(), + project_dir.path().display().to_string(), + ); + project.id = project_id(); + project.worktree_parent_directory = Some(worktree_parent.path().display().to_string()); + let workspace_path = resolve_agent_conversation_workspace_path(&project, &conversation_id) + .expect("derive exact PR autofix workspace path"); + recovery_git( + project_dir.path(), + &[ + "worktree", + "add", + "-b", + "ralphx/test/publish-recovery", + workspace_path.to_str().expect("workspace path"), + "main", + ], + ); state - .agent_conversation_workspace_repo - .create_or_update(needs_agent_workspace(conversation_id.clone())) + .project_repo + .create(project) .await - .expect("seed unchanged-health workspace"); + .expect("seed PR autofix project"); + let mut workspace = needs_agent_workspace(conversation_id.clone()); + workspace.worktree_path = workspace_path.display().to_string(); + workspace.base_commit = Some(recovery_git(project_dir.path(), &["rev-parse", "HEAD"])); state - .project_repo - .create({ - let mut project = Project::new( - "unchanged health project".to_string(), - "/tmp/ralphx-unchanged-health".to_string(), - ); - project.id = project_id(); - project - }) + .agent_conversation_workspace_repo + .create_or_update(workspace) .await - .expect("seed unchanged-health project"); + .expect("seed PR autofix workspace"); + (state, conversation_id, worktree_parent, project_dir) +} + +#[tokio::test] +async fn blocked_pr_autofix_with_unchanged_health_parks_without_spawning() { + let (mut state, conversation_id, _worktree_parent, _project_dir) = + seed_pr_autofix_health_workspace(122).await; start_blocked_pr_autofix_generation(&state, &conversation_id).await; let health = failing_check_pr_health("head-unchanged", "Rust Tests"); diff --git a/src-tauri/src/application/services/pr_merge_poller.rs b/src-tauri/src/application/services/pr_merge_poller.rs index 8aedf4f1da..c9ac42a47a 100644 --- a/src-tauri/src/application/services/pr_merge_poller.rs +++ b/src-tauri/src/application/services/pr_merge_poller.rs @@ -2886,11 +2886,11 @@ async fn route_agent_workspace_pr_autofix_for_target( .await? { if attempt.phase == AgentWorkspaceRepairPhase::Ready { - let pre_existing_suppressed = attempt - .pending_reasons - .iter() - .any(|reason| reason == crate::application::agent_workspace_publish_repair_state::PRE_EXISTING_ON_BASE_REPAIR_REASON); - if pre_existing_suppressed + // Both hold kinds — pre-existing-on-base and unchanged-health — park a generation + // against an exact observed failure identity, and neither may be ended by anything + // other than GitHub reporting different health. + let health_suppressed = crate::application::agent_workspace_publish_repair_state::agent_workspace_repair_is_health_held(&attempt); + if health_suppressed && current_issue.as_ref().is_some_and(|issue| { attempt.pr_autofix_health_fingerprint.as_deref() == Some(issue.classification.as_str()) @@ -2898,9 +2898,14 @@ async fn route_agent_workspace_pr_autofix_for_target( { return Ok(false); } - if pre_existing_suppressed || attempt.ci_rerun_count > 0 { - if attempt.ci_rerun_fingerprint.as_deref() - == transient_ci_health_fingerprint(&health).as_deref() + if health_suppressed || attempt.ci_rerun_count > 0 { + // Only a generation that actually reserved a rerun can be waiting on that + // rerun's conclusion. Without this guard a health hold whose failure is not + // transient-CI shaped compares `None == None` here and suppresses itself + // forever, even once GitHub reports something new. + if attempt.ci_rerun_count > 0 + && attempt.ci_rerun_fingerprint.as_deref() + == transient_ci_health_fingerprint(&health).as_deref() { return Ok(false); } diff --git a/src-tauri/src/application/services/pr_merge_poller_tests.rs b/src-tauri/src/application/services/pr_merge_poller_tests.rs index 19460e95ad..ea145a26bc 100644 --- a/src-tauri/src/application/services/pr_merge_poller_tests.rs +++ b/src-tauri/src/application/services/pr_merge_poller_tests.rs @@ -304,6 +304,23 @@ async fn reserve_pre_existing_on_base_attempt( repair_repo: &dyn AgentWorkspaceRepairRepository, conversation_id: &ChatConversationId, fingerprint: &str, +) -> AgentWorkspaceRepairAttempt { + reserve_health_held_attempt( + repair_repo, + conversation_id, + fingerprint, + crate::application::agent_workspace_publish_repair_state::PRE_EXISTING_ON_BASE_REPAIR_REASON, + ) + .await +} + +/// Parks a PR autofix generation at an exact health fingerprint under the given hold reason. Both +/// hold reasons must behave identically at the poller's dispatch gate. +async fn reserve_health_held_attempt( + repair_repo: &dyn AgentWorkspaceRepairRepository, + conversation_id: &ChatConversationId, + fingerprint: &str, + hold_reason: &str, ) -> AgentWorkspaceRepairAttempt { use crate::domain::repositories::{ AgentWorkspaceRepairAttemptTransition, AgentWorkspaceRepairAttemptTransitionOutcome, @@ -323,24 +340,21 @@ async fn reserve_pre_existing_on_base_attempt( None, Utc::now(), ), - reason: "PR failure was already present on base".to_string(), + reason: hold_reason.to_string(), verified_newer_base: false, compatibility_projection: None, events: Vec::new(), }) .await - .expect("pre-existing-on-base repair attempt should start") + .expect("health-held repair attempt should start") { StartOrJoinAgentWorkspaceRepairAttemptOutcome::Started(attempt) => attempt, - outcome => panic!("expected pre-existing-on-base attempt to start, got {outcome:?}"), + outcome => panic!("expected health-held attempt to start, got {outcome:?}"), }; let mut suppressed = started.clone(); suppressed.phase = AgentWorkspaceRepairPhase::Ready; suppressed.pr_autofix_health_fingerprint = Some(fingerprint.to_string()); - suppressed.pending_reasons = vec![ - crate::application::agent_workspace_publish_repair_state::PRE_EXISTING_ON_BASE_REPAIR_REASON - .to_string(), - ]; + suppressed.pending_reasons = vec![hold_reason.to_string()]; suppressed.updated_at += chrono::Duration::microseconds(1); match repair_repo .transition_repair_attempt(AgentWorkspaceRepairAttemptTransition { @@ -352,10 +366,10 @@ async fn reserve_pre_existing_on_base_attempt( events: Vec::new(), }) .await - .expect("pre-existing-on-base reservation should persist") + .expect("health-held reservation should persist") { AgentWorkspaceRepairAttemptTransitionOutcome::Applied(attempt) => attempt, - outcome => panic!("expected pre-existing-on-base reservation, got {outcome:?}"), + outcome => panic!("expected health-held reservation, got {outcome:?}"), } } @@ -5901,6 +5915,111 @@ async fn live_pr_autofix_dispatches_new_generation_after_ci_rerun_fingerprint_ch assert_eq!(current.phase, AgentWorkspaceRepairPhase::Repairing); } +/// A generation parked because GitHub reported unchanged health must be honoured by the poller's +/// dispatch gate exactly like a pre-existing-on-base hold. Without this the durable recovery lane +/// parks the attempt and the very next poll starts another fixer on identical evidence — the +/// four-generation loop from the 2026-07-31 incident. +#[tokio::test] +async fn live_pr_autofix_unchanged_health_hold_suppresses_same_fingerprint_then_redispatches() { + let worktree = tempfile::tempdir().expect("worktree path"); + let mut workspace = supervised_workspace( + "unchanged-health-hold-fingerprint", + "project-unchanged-health-hold", + worktree.path(), + ); + init_repair_dispatch_repo(worktree.path(), &workspace.branch_name); + workspace.base_commit = Some("base-unchanged-health".to_string()); + let conversation_id = workspace.conversation_id.clone(); + let workspace_repo = Arc::new(MemoryAgentConversationWorkspaceRepository::new()); + workspace_repo + .create_or_update(workspace) + .await + .expect("workspace should persist"); + let repair_repo: Arc = workspace_repo.clone(); + let branch_update_repo: Arc = + Arc::new(MemoryBranchUpdateRepository::new()); + let agent_run_repo = seeded_latest_pr_fixer_run_repo(&conversation_id).await; + let mut original_health = open_pr_health("unchanged-health-head"); + original_health.checks.push(PrHealthCheck { + name: "Rust tests".to_string(), + status: Some("completed".to_string()), + conclusion: Some("failure".to_string()), + details_url: Some("https://github.com/owner/repo/actions/runs/920".to_string()), + }); + let fingerprint = super::classify_agent_workspace_pr_autofix_issue(101, &original_health) + .expect("failed check should classify") + .classification; + let held = reserve_health_held_attempt( + repair_repo.as_ref(), + &conversation_id, + &fingerprint, + crate::application::agent_workspace_publish_repair_state::UNCHANGED_HEALTH_REPAIR_REASON, + ) + .await; + let github = Arc::new(MockGithubService::new()); + github.state().fetch_pr_health_result = Some(Ok(original_health)); + let chat = Arc::new(MockChatService::with_agent_run_repo(Arc::clone( + &agent_run_repo, + ))); + + let routed = super::route_agent_workspace_pr_autofix_if_needed_with_repair_repo( + Arc::clone(&github) as Arc, + worktree.path(), + 101, + &conversation_id, + workspace_repo.clone(), + Some(agent_run_repo.clone()), + Some(Arc::clone(&repair_repo)), + Some(Arc::clone(&branch_update_repo)), + chat.clone() as Arc, + ) + .await + .expect("unchanged health should be suppressed"); + + assert!(!routed, "unchanged health must not start another generation"); + assert!(chat.get_sent_messages().await.is_empty()); + let current = repair_repo + .get_current_repair_attempt(&conversation_id) + .await + .expect("held attempt should load") + .expect("held attempt remains current"); + assert_eq!(current.id, held.id); + assert_eq!(current.phase, AgentWorkspaceRepairPhase::Ready); + + // A different failing check is new evidence, so the hold ends and a fresh generation runs. + let mut changed_health = open_pr_health("unchanged-health-head"); + changed_health.checks.push(PrHealthCheck { + name: "Clippy".to_string(), + status: Some("completed".to_string()), + conclusion: Some("failure".to_string()), + details_url: Some("https://github.com/owner/repo/actions/runs/921".to_string()), + }); + github.state().fetch_pr_health_result = Some(Ok(changed_health)); + + let routed = super::route_agent_workspace_pr_autofix_if_needed_with_repair_repo( + Arc::clone(&github) as Arc, + worktree.path(), + 101, + &conversation_id, + workspace_repo, + Some(agent_run_repo), + Some(Arc::clone(&repair_repo)), + Some(branch_update_repo), + chat.clone() as Arc, + ) + .await + .expect("changed health should dispatch a new generation"); + + assert!(routed, "changed health must be able to end the hold"); + assert_eq!(chat.get_sent_messages().await.len(), 1); + let current = repair_repo + .get_current_repair_attempt(&conversation_id) + .await + .expect("new repair generation should load") + .expect("changed fingerprint should create a new generation"); + assert_eq!(current.generation, held.generation + 1); +} + #[tokio::test] async fn live_pr_autofix_pre_existing_on_base_suppresses_same_fingerprint_then_redispatches() { let worktree = tempfile::tempdir().expect("worktree path"); From 9215fd9223b5640729e9a2836775a6bb6fc9db6b Mon Sep 17 00:00:00 2001 From: lazabogdan <6580668+lazabogdan@users.noreply.github.com> Date: Fri, 31 Jul 2026 15:51:43 +0300 Subject: [PATCH 03/29] test(repair): bind backend assignments to the recipient agent's live tool grant MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Adds the contract layer the 2026-07-31 incident needed: each backend-generated assignment is built with representative fixtures, the RalphX tools it names are extracted, and every one must appear in the canonical capabilities.mcp_tools of the agent that assignment is actually addressed to. Covers durable repair redelivery, durable PR-autofix redelivery, the poller's first PR-autofix dispatch, the publish repair request, and the repair prompt. A message telling the generic repairer to call the PR fixer's completion tool now fails a test instead of burning an agent generation. The generic redelivery message also names `complete_agent_workspace_repair` explicitly instead of referring vaguely to "the available repair-completion tool" — safe now that redelivery is source-aware, and it removes the guess the recipient previously had to make. Message builders were widened to pub(crate) with test-only re-exports so the contract test can reach them without loosening any production surface. Validation: 3 contract tests plus the full 66-test recovery suite pass. Co-Authored-By: Claude Opus 5 --- ...agent_workspace_dispatch_contract_tests.rs | 249 ++++++++++++++++++ .../agent_workspace_publish_recovery.rs | 4 + .../durable_attempt_recovery.rs | 6 +- .../pr_autofix_redelivery.rs | 2 +- .../agent_workspace_publish_recovery_tests.rs | 5 +- src-tauri/src/application/mod.rs | 2 + .../application/services/pr_merge_poller.rs | 2 +- .../src/commands/unified_chat_commands/mod.rs | 2 +- 8 files changed, 266 insertions(+), 6 deletions(-) create mode 100644 src-tauri/src/application/agent_workspace_dispatch_contract_tests.rs diff --git a/src-tauri/src/application/agent_workspace_dispatch_contract_tests.rs b/src-tauri/src/application/agent_workspace_dispatch_contract_tests.rs new file mode 100644 index 0000000000..8cfdff3e41 --- /dev/null +++ b/src-tauri/src/application/agent_workspace_dispatch_contract_tests.rs @@ -0,0 +1,249 @@ +//! Contract tests binding backend-generated agent assignments to the live MCP surface. +//! +//! Production incident 2026-07-31: durable redelivery addressed the generic workspace repairer +//! with a message telling it to call `complete_agent_workspace_pr_fix` — a tool that agent does +//! not hold — while the repairer's own completion tool rejected the fields the backend demanded. +//! Both failures are invisible to ordinary unit tests because each side is internally consistent. +//! These tests fail when a backend message names a tool its recipient cannot call. + +use std::collections::BTreeSet; +use std::path::PathBuf; + +use crate::application::agent_workspace_publish_recovery::{ + due_pr_autofix_redispatch_message, due_repair_dispatch_message, +}; +use crate::application::services::pr_merge_poller::{ + build_agent_workspace_pr_autofix_message, classify_agent_workspace_pr_autofix_issue, +}; +use crate::commands::unified_chat_commands::{ + build_agent_workspace_repair_message_for_target, AgentConversationWorkspaceRepairTarget, + AgentWorkspacePostRepairAction, +}; +use crate::domain::entities::{ + AgentConversationWorkspace, AgentConversationWorkspaceMode, AgentWorkspaceRepairAttempt, + AgentWorkspaceRepairContinuation, AgentWorkspaceRepairSource, ChatConversationId, + IdeationAnalysisBaseRefKind, ProjectId, +}; +use crate::domain::services::github_service::{PrHealth, PrHealthCheck, PrMergeableState}; +use crate::domain::services::{PrStatus, PrSyncState}; +use crate::infrastructure::agents::claude::agent_names::{ + SHORT_AGENT_WORKSPACE_PR_FIXER, SHORT_AGENT_WORKSPACE_REPAIR, +}; +use crate::infrastructure::agents::harness_agent_catalog::load_canonical_agent_definition; + +fn project_root() -> PathBuf { + PathBuf::from(env!("CARGO_MANIFEST_DIR")) + .join("..") + .canonicalize() + .expect("canonical repo root") +} + +/// The exact tool names the named canonical agent is granted, as RalphX will register them. +fn canonical_mcp_tools(agent_name: &str) -> BTreeSet { + let definition = load_canonical_agent_definition(&project_root(), agent_name) + .unwrap_or_else(|| panic!("canonical agent definition for {agent_name}")); + definition + .capabilities + .mcp_tools + .into_iter() + .collect::>() +} + +/// Every RalphX completion/context tool a message instructs its recipient to call. +fn referenced_agent_workspace_tools(message: &str) -> BTreeSet { + let mut found = BTreeSet::new(); + for prefix in ["complete_agent_workspace_", "get_agent_workspace_"] { + let mut rest = message; + while let Some(start) = rest.find(prefix) { + let tail = &rest[start..]; + let end = tail + .find(|c: char| !c.is_ascii_alphanumeric() && c != '_') + .unwrap_or(tail.len()); + found.insert(tail[..end].to_string()); + rest = &tail[end..]; + } + } + found +} + +fn contract_workspace() -> AgentConversationWorkspace { + let mut workspace = AgentConversationWorkspace::new( + ChatConversationId::from_string("contract-conversation".to_string()), + ProjectId::from_string("contract-project".to_string()), + AgentConversationWorkspaceMode::Edit, + IdeationAnalysisBaseRefKind::ProjectDefault, + "main".to_string(), + Some("Project default (main)".to_string()), + Some("base-sha".to_string()), + "ralphx/test/contract".to_string(), + "/tmp/ralphx-contract-workspace".to_string(), + ); + workspace.publication_pr_number = Some(4242); + workspace.publication_pr_url = Some("https://github.com/owner/repo/pull/4242".to_string()); + workspace +} + +fn contract_attempt(source: AgentWorkspaceRepairSource) -> AgentWorkspaceRepairAttempt { + let mut attempt = AgentWorkspaceRepairAttempt::new( + ChatConversationId::from_string("contract-conversation".to_string()), + source, + AgentWorkspaceRepairContinuation::Publish, + "main", + false, + true, + false, + None, + chrono::Utc::now(), + ); + attempt.pr_autofix_health_fingerprint = + Some("github_pr_autofix:4242:checks:rust-tests".to_string()); + attempt.pending_reasons = vec!["CI is failing on the published PR".to_string()]; + attempt +} + +fn failing_pr_health() -> PrHealth { + PrHealth { + sync_state: PrSyncState { + status: PrStatus::Open, + merge_state_status: None, + mergeable: Some(PrMergeableState::Mergeable), + is_draft: false, + head_ref_name: "ralphx/test/contract".to_string(), + base_ref_name: "main".to_string(), + head_ref_oid: Some("contract-head".to_string()), + base_ref_oid: Some("base-sha".to_string()), + }, + review_decision: None, + checks: vec![PrHealthCheck { + name: "Rust tests".to_string(), + status: Some("completed".to_string()), + conclusion: Some("failure".to_string()), + details_url: Some("https://github.com/owner/repo/actions/runs/1".to_string()), + }], + issue_comments: Vec::new(), + auto_merge_request: None, + } +} + +/// Each backend assignment, paired with the canonical agent it is actually addressed to. +fn backend_assignments() -> Vec<(&'static str, &'static str, String)> { + let workspace = contract_workspace(); + let health = failing_pr_health(); + let issue = classify_agent_workspace_pr_autofix_issue(4242, &health) + .expect("a failing check classifies as a PR autofix issue"); + + vec![ + ( + "durable repair redelivery", + SHORT_AGENT_WORKSPACE_REPAIR, + due_repair_dispatch_message( + &contract_attempt(AgentWorkspaceRepairSource::Publish), + &workspace, + ), + ), + ( + "durable PR autofix redelivery", + SHORT_AGENT_WORKSPACE_PR_FIXER, + due_pr_autofix_redispatch_message( + &contract_attempt(AgentWorkspaceRepairSource::PrAutofix), + &workspace, + ), + ), + ( + "poller PR autofix first dispatch", + SHORT_AGENT_WORKSPACE_PR_FIXER, + build_agent_workspace_pr_autofix_message( + 4242, + workspace.publication_pr_url.as_deref(), + "agent workspace", + &workspace, + &issue, + ), + ), + ( + "publish repair request", + SHORT_AGENT_WORKSPACE_REPAIR, + build_agent_workspace_repair_message_for_target( + "push rejected: base moved", + &workspace, + &AgentConversationWorkspaceRepairTarget { + branch_name: workspace.branch_name.clone(), + base_ref: workspace.base_ref.clone(), + base_display_name: workspace.base_display_name.clone(), + worktree_path: None, + }, + AgentWorkspacePostRepairAction::Publish, + ), + ), + ] +} + +#[test] +fn every_backend_assignment_names_only_tools_its_recipient_agent_holds() { + for (label, agent_name, message) in backend_assignments() { + let granted = canonical_mcp_tools(agent_name); + let referenced = referenced_agent_workspace_tools(&message); + assert!( + !referenced.is_empty(), + "{label}: an assignment that names no completion tool cannot be completed\n{message}" + ); + for tool in &referenced { + assert!( + granted.contains(tool), + "{label}: assignment addressed to {agent_name} calls `{tool}`, which is not in its \ + canonical capabilities.mcp_tools ({granted:?})\n{message}" + ); + } + } +} + +/// The incident's exact shape: a PR-autofix redelivery that reaches the generic repairer. +#[test] +fn pr_autofix_and_generic_repair_assignments_do_not_share_a_completion_tool() { + let workspace = contract_workspace(); + let pr_fix = referenced_agent_workspace_tools(&due_pr_autofix_redispatch_message( + &contract_attempt(AgentWorkspaceRepairSource::PrAutofix), + &workspace, + )); + let generic = referenced_agent_workspace_tools(&due_repair_dispatch_message( + &contract_attempt(AgentWorkspaceRepairSource::Publish), + &workspace, + )); + + assert!( + pr_fix.contains("complete_agent_workspace_pr_fix"), + "PR autofix redelivery must route completion through the PR fixer's tool: {pr_fix:?}" + ); + assert!( + !canonical_mcp_tools(SHORT_AGENT_WORKSPACE_REPAIR) + .contains("complete_agent_workspace_pr_fix"), + "the generic repairer must not hold the PR fixer's completion tool" + ); + assert!( + !generic.contains("complete_agent_workspace_pr_fix"), + "the generic repair assignment must never name the PR fixer's completion tool: {generic:?}" + ); +} + +/// The repair prompt and the backend both demand `resolution`; the tool schema that accepts it +/// lives in the MCP server, so the canonical grant must at least still name the tool the prompt +/// tells the agent to call. +#[test] +fn repair_prompt_completion_tool_is_granted_to_the_repair_agent() { + let prompt = std::fs::read_to_string( + project_root().join("agents/ralphx-agent-workspace-repair/shared/prompt.md"), + ) + .expect("workspace repair prompt"); + let referenced = referenced_agent_workspace_tools(&prompt); + let granted = canonical_mcp_tools(SHORT_AGENT_WORKSPACE_REPAIR); + assert!( + referenced.contains("complete_agent_workspace_repair"), + "the repair prompt must name its completion tool: {referenced:?}" + ); + for tool in &referenced { + assert!( + granted.contains(tool), + "the repair prompt calls `{tool}`, which is not granted to {SHORT_AGENT_WORKSPACE_REPAIR}" + ); + } +} diff --git a/src-tauri/src/application/agent_workspace_publish_recovery.rs b/src-tauri/src/application/agent_workspace_publish_recovery.rs index fec55f9f4a..ca58f49ccc 100644 --- a/src-tauri/src/application/agent_workspace_publish_recovery.rs +++ b/src-tauri/src/application/agent_workspace_publish_recovery.rs @@ -51,6 +51,10 @@ pub use durable_attempt_recovery::recover_agent_workspace_repair_after_terminal_ #[cfg(not(any(test, feature = "test-utils")))] pub(crate) use durable_attempt_recovery::recover_agent_workspace_repair_after_terminal_run; pub(crate) use durable_attempt_recovery::recover_agent_workspace_repair_attempts_for_state; +#[cfg(test)] +pub(crate) use durable_attempt_recovery::due_repair_dispatch_message; +#[cfg(test)] +pub(crate) use pr_autofix_redelivery::due_pr_autofix_redispatch_message; use durable_attempt_recovery::recover_stale_publish_repair_for_workspace_in_state_result; pub(crate) use durable_attempt_recovery::{ AUTO_RETRY_BLOCKED_REPAIR_REASON_PREFIX, AUTO_RETRY_READY_REPAIR_REASON_PREFIX, diff --git a/src-tauri/src/application/agent_workspace_publish_recovery/durable_attempt_recovery.rs b/src-tauri/src/application/agent_workspace_publish_recovery/durable_attempt_recovery.rs index dc9c15ac16..b3a9707ccb 100644 --- a/src-tauri/src/application/agent_workspace_publish_recovery/durable_attempt_recovery.rs +++ b/src-tauri/src/application/agent_workspace_publish_recovery/durable_attempt_recovery.rs @@ -740,7 +740,7 @@ pub(super) fn human_repair_dispatch_context( }) } -fn due_repair_dispatch_message( +pub(crate) fn due_repair_dispatch_message( attempt: &AgentWorkspaceRepairAttempt, workspace: &AgentConversationWorkspace, ) -> String { @@ -755,7 +755,9 @@ fn due_repair_dispatch_message( }; let reason = human_repair_dispatch_context(attempt).unwrap_or(DEFAULT_REPAIR_DISPATCH_CONTEXT); format!( - "{continuation}\n\nInspect the current workspace state before changing files. When the repair is committed, use the available repair-completion tool.\n\nContext: {reason}\nWorkspace branch: {}\nBase ref: {}", + // Naming the exact tool is safe now that redelivery is source-aware: this message is only + // ever addressed to the workspace repairer, which is the agent granted that tool. + "{continuation}\n\nInspect the current workspace state before changing files. When the repair is committed, call `complete_agent_workspace_repair` with a summary, adding a blocker if the repair cannot be completed safely.\n\nContext: {reason}\nWorkspace branch: {}\nBase ref: {}", workspace.branch_name, attempt.target_base_ref ) } diff --git a/src-tauri/src/application/agent_workspace_publish_recovery/pr_autofix_redelivery.rs b/src-tauri/src/application/agent_workspace_publish_recovery/pr_autofix_redelivery.rs index 0ecfd4e666..dde829391d 100644 --- a/src-tauri/src/application/agent_workspace_publish_recovery/pr_autofix_redelivery.rs +++ b/src-tauri/src/application/agent_workspace_publish_recovery/pr_autofix_redelivery.rs @@ -156,7 +156,7 @@ pub(super) async fn evaluate_pr_autofix_successor( /// identity and completion contract the poller's first dispatch uses. A redelivery is also not a /// fresh dispatch: an earlier generation may already have committed part of the work, so the /// recipient must re-observe GitHub before changing anything. -pub(super) fn due_pr_autofix_redispatch_message( +pub(crate) fn due_pr_autofix_redispatch_message( attempt: &AgentWorkspaceRepairAttempt, workspace: &AgentConversationWorkspace, ) -> String { diff --git a/src-tauri/src/application/agent_workspace_publish_recovery_tests.rs b/src-tauri/src/application/agent_workspace_publish_recovery_tests.rs index f346a67aae..05c1655fdc 100644 --- a/src-tauri/src/application/agent_workspace_publish_recovery_tests.rs +++ b/src-tauri/src/application/agent_workspace_publish_recovery_tests.rs @@ -4617,7 +4617,10 @@ wait "$stdin_drain_pid" 2>/dev/null || true ); let message = latest_sent_repair_message(&state, &conversation_id).await; - assert!(message.contains("use the available repair-completion tool")); + assert!( + message.contains("complete_agent_workspace_repair"), + "a publish repair must name the repairer's own completion tool: {message}" + ); assert!( !message.contains("complete_agent_workspace_pr_fix"), "a publish repair must not be addressed to the PR fixer: {message}" diff --git a/src-tauri/src/application/mod.rs b/src-tauri/src/application/mod.rs index 8fa6f95ab3..1afe2cf265 100644 --- a/src-tauri/src/application/mod.rs +++ b/src-tauri/src/application/mod.rs @@ -426,6 +426,8 @@ mod agent_workspace_continuation_tests; #[cfg(test)] mod agent_workspace_external_pr_reconciliation_tests; #[cfg(test)] +mod agent_workspace_dispatch_contract_tests; +#[cfg(test)] mod agent_workspace_pr_metadata_reconciliation_tests; #[cfg(test)] mod agent_workspace_pr_supervision_recovery_tests; diff --git a/src-tauri/src/application/services/pr_merge_poller.rs b/src-tauri/src/application/services/pr_merge_poller.rs index c9ac42a47a..2bb9d4c804 100644 --- a/src-tauri/src/application/services/pr_merge_poller.rs +++ b/src-tauri/src/application/services/pr_merge_poller.rs @@ -4480,7 +4480,7 @@ fn build_agent_workspace_pr_conflict_repair_message( out } -fn build_agent_workspace_pr_autofix_message( +pub(crate) fn build_agent_workspace_pr_autofix_message( pr_number: i64, pr_url: Option<&str>, target_label: &str, diff --git a/src-tauri/src/commands/unified_chat_commands/mod.rs b/src-tauri/src/commands/unified_chat_commands/mod.rs index 09d1c1da07..ab7ed0e265 100644 --- a/src-tauri/src/commands/unified_chat_commands/mod.rs +++ b/src-tauri/src/commands/unified_chat_commands/mod.rs @@ -8855,7 +8855,7 @@ pub fn build_agent_workspace_publish_repair_message_for_target( ) } -fn build_agent_workspace_repair_message_for_target( +pub(crate) fn build_agent_workspace_repair_message_for_target( error: &str, workspace: &AgentConversationWorkspace, target: &AgentConversationWorkspaceRepairTarget, From f34c1765442bfb578a13287d51423984dfd4b535 Mon Sep 17 00:00:00 2001 From: lazabogdan <6580668+lazabogdan@users.noreply.github.com> Date: Fri, 31 Jul 2026 16:26:14 +0300 Subject: [PATCH 04/29] feat(repair): give PR autofix cross-streak memory of exhausted failures MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit A repair attempt's fingerprint hold dies with its streak. Once a streak exhausted its retries, the next poll started a brand new streak against the same failing check with no memory of what had already failed — the outer loop behind the four-generation incident. The attempt-scoped hold added in Phase 1 cannot see across that boundary. - Migration v20260731125157 adds nullable last_blocked_pr_health_fingerprint and last_blocked_pr_health_at to agent_conversation_workspaces. - Repository trait + SQLite and memory implementations gain set_last_blocked_pr_health_fingerprint; SQLite writes through db.run. - The recovery lane records the failure identity when a streak exhausts its auto-retry cap and when a generation parks on unchanged health. Recording is best effort: failing to remember must not block a correct settlement. - The poller suppresses a fresh streak whose classification matches the remembered identity, records one deduped publication event, and clears the memory as soon as GitHub reports something different. A failed read never suppresses, so a broken query cannot silently disable PR autofix. Validation: 3 migration tests, SQLite round-trip (75 repo tests), 121 poller tests including a full suppress/dedupe/clear cycle, and the 66-test recovery suite all pass. Co-Authored-By: Claude Opus 5 --- .../entities/agent_conversation_workspace.rs | 7 + ...agent_conversation_workspace_repository.rs | 9 + .../agent_conversation_workspace.rs | 2 + .../durable_attempt_recovery.rs | 15 +- .../pr_autofix_redelivery.rs | 27 +++ .../agent_workspace_terminal_cleanup_tests.rs | 7 + .../application/pr_startup_recovery_tests.rs | 7 + .../application/services/pr_merge_poller.rs | 70 ++++++++ .../services/pr_merge_poller_tests.rs | 161 ++++++++++++++++++ ...emory_agent_conversation_workspace_repo.rs | 13 ++ .../infrastructure/sqlite/migrations/mod.rs | 10 +- ..._add_workspace_repair_fingerprint_state.rs | 33 ++++ ...orkspace_repair_fingerprint_state_tests.rs | 69 ++++++++ ...qlite_agent_conversation_workspace_repo.rs | 28 +++ ...agent_conversation_workspace_repo_tests.rs | 52 ++++++ 15 files changed, 505 insertions(+), 5 deletions(-) create mode 100644 src-tauri/src/infrastructure/sqlite/migrations/v20260731125157_add_workspace_repair_fingerprint_state.rs create mode 100644 src-tauri/src/infrastructure/sqlite/migrations/v20260731125157_add_workspace_repair_fingerprint_state_tests.rs diff --git a/src-tauri/crates/ralphx-domain/src/entities/agent_conversation_workspace.rs b/src-tauri/crates/ralphx-domain/src/entities/agent_conversation_workspace.rs index 0ec4f86817..9e7ef39d4e 100644 --- a/src-tauri/crates/ralphx-domain/src/entities/agent_conversation_workspace.rs +++ b/src-tauri/crates/ralphx-domain/src/entities/agent_conversation_workspace.rs @@ -946,6 +946,11 @@ pub struct AgentConversationWorkspace { pub pr_supervision_status: Option, pub pr_supervision_summary: Option, pub pr_supervision_updated_at: Option>, + /// The failure identity the most recent PR autofix streak exhausted itself against. Repair + /// attempts are per-streak, so without this the next streak has no memory of what already + /// failed and re-spends agents on identical evidence. + pub last_blocked_pr_health_fingerprint: Option, + pub last_blocked_pr_health_at: Option>, pub status: AgentConversationWorkspaceStatus, pub created_at: DateTime, pub updated_at: DateTime, @@ -997,6 +1002,8 @@ impl AgentConversationWorkspace { pr_supervision_status: None, pr_supervision_summary: None, pr_supervision_updated_at: None, + last_blocked_pr_health_fingerprint: None, + last_blocked_pr_health_at: None, status: AgentConversationWorkspaceStatus::Active, created_at: now, updated_at: now, diff --git a/src-tauri/crates/ralphx-domain/src/repositories/agent_conversation_workspace_repository.rs b/src-tauri/crates/ralphx-domain/src/repositories/agent_conversation_workspace_repository.rs index 03f92d7442..f3a6da5e21 100644 --- a/src-tauri/crates/ralphx-domain/src/repositories/agent_conversation_workspace_repository.rs +++ b/src-tauri/crates/ralphx-domain/src/repositories/agent_conversation_workspace_repository.rs @@ -459,6 +459,15 @@ pub trait AgentConversationWorkspaceRepository: Send + Sync { auto_merge_method: &str, ) -> AppResult<()>; + /// Remembers the failure identity a PR autofix streak exhausted itself against, so a fresh + /// streak can recognise it instead of re-spending agents on the same evidence. Passing `None` + /// clears the memory once GitHub reports something different. + async fn set_last_blocked_pr_health_fingerprint( + &self, + conversation_id: &ChatConversationId, + fingerprint: Option<&str>, + ) -> AppResult<()>; + async fn update_auto_publish_preferences( &self, _conversation_id: &ChatConversationId, diff --git a/src-tauri/src/application/agent_conversation_workspace.rs b/src-tauri/src/application/agent_conversation_workspace.rs index 002fcf9fa8..cfeb0e339d 100644 --- a/src-tauri/src/application/agent_conversation_workspace.rs +++ b/src-tauri/src/application/agent_conversation_workspace.rs @@ -535,6 +535,8 @@ pub async fn prepare_agent_conversation_workspace_with_setup_mode_defaults_and_b pr_supervision_status: None, pr_supervision_summary: None, pr_supervision_updated_at: None, + last_blocked_pr_health_fingerprint: None, + last_blocked_pr_health_at: None, status: crate::domain::entities::AgentConversationWorkspaceStatus::Active, created_at: Utc::now(), updated_at: Utc::now(), diff --git a/src-tauri/src/application/agent_workspace_publish_recovery/durable_attempt_recovery.rs b/src-tauri/src/application/agent_workspace_publish_recovery/durable_attempt_recovery.rs index b3a9707ccb..b3a5e14817 100644 --- a/src-tauri/src/application/agent_workspace_publish_recovery/durable_attempt_recovery.rs +++ b/src-tauri/src/application/agent_workspace_publish_recovery/durable_attempt_recovery.rs @@ -3,7 +3,8 @@ use std::sync::Arc; use chrono::{Duration, Utc}; use super::pr_autofix_redelivery::{ - due_pr_autofix_redispatch_message, evaluate_pr_autofix_successor, PrAutofixSuccessorDecision, + due_pr_autofix_redispatch_message, evaluate_pr_autofix_successor, + remember_blocked_pr_autofix_fingerprint, PrAutofixSuccessorDecision, }; use super::StalePublishRepairRecoveryOutcome; use crate::application::agent_conversation_workspace::resolve_effective_agent_conversation_workspace_path; @@ -560,9 +561,14 @@ async fn retry_safe_blocked_agent_workspace_repair( return Ok(DurableRepairRecoveryOutcome::Noop); } let streak = automatic_blocked_repair_streak(¤t); - if streak >= MAX_AUTO_RETRY_BLOCKED_REPAIR_STREAK - || Utc::now() - current.updated_at < automatic_blocked_repair_retry_delay(streak) - { + if streak >= MAX_AUTO_RETRY_BLOCKED_REPAIR_STREAK { + // This streak is finished. Repair attempts are per-streak, so unless the workspace itself + // remembers what this streak died against, the next poll starts a brand new streak with + // no memory and re-spends the same agents on the same failing check. + remember_blocked_pr_autofix_fingerprint(state, ¤t).await; + return Ok(DurableRepairRecoveryOutcome::Noop); + } + if Utc::now() - current.updated_at < automatic_blocked_repair_retry_delay(streak) { return Ok(DurableRepairRecoveryOutcome::Noop); } let Some(workspace) = state @@ -643,6 +649,7 @@ async fn hold_unchanged_pr_autofix_health( .await? { AgentWorkspaceRepairTransitionOutcome::Applied(attempt) => { + remember_blocked_pr_autofix_fingerprint(state, &attempt).await; release_repair_lease_if_settled_boundary(state, &attempt).await?; Ok(DurableRepairRecoveryOutcome::Noop) } diff --git a/src-tauri/src/application/agent_workspace_publish_recovery/pr_autofix_redelivery.rs b/src-tauri/src/application/agent_workspace_publish_recovery/pr_autofix_redelivery.rs index dde829391d..c15503ee5b 100644 --- a/src-tauri/src/application/agent_workspace_publish_recovery/pr_autofix_redelivery.rs +++ b/src-tauri/src/application/agent_workspace_publish_recovery/pr_autofix_redelivery.rs @@ -152,6 +152,33 @@ pub(super) async fn evaluate_pr_autofix_successor( })) } +/// Persists the failure identity a PR autofix generation stopped against, so a later streak can +/// recognise it. Best effort by design: failing to remember must never block the settlement that +/// is already correct, it only costs the cross-streak suppression on the next poll. +pub(super) async fn remember_blocked_pr_autofix_fingerprint( + state: &AppState, + attempt: &AgentWorkspaceRepairAttempt, +) { + if attempt.source != crate::domain::entities::AgentWorkspaceRepairSource::PrAutofix { + return; + } + let Some(fingerprint) = attempt.pr_autofix_health_fingerprint.as_deref() else { + return; + }; + if let Err(error) = state + .agent_conversation_workspace_repo + .set_last_blocked_pr_health_fingerprint(&attempt.conversation_id, Some(fingerprint)) + .await + { + tracing::warn!( + conversation_id = attempt.conversation_id.as_str(), + attempt_id = attempt.id.as_str(), + %error, + "Could not remember the blocked PR autofix failure identity; a later streak may repeat it" + ); + } +} + /// A redelivered PR autofix is addressed to the PR fixer, so the assignment must carry the same PR /// identity and completion contract the poller's first dispatch uses. A redelivery is also not a /// fresh dispatch: an earlier generation may already have committed part of the work, so the diff --git a/src-tauri/src/application/agent_workspace_terminal_cleanup_tests.rs b/src-tauri/src/application/agent_workspace_terminal_cleanup_tests.rs index 8eb909eda3..680724e999 100644 --- a/src-tauri/src/application/agent_workspace_terminal_cleanup_tests.rs +++ b/src-tauri/src/application/agent_workspace_terminal_cleanup_tests.rs @@ -1201,6 +1201,13 @@ impl ControlledWorkspaceRepo { #[async_trait] impl AgentConversationWorkspaceRepository for ControlledWorkspaceRepo { + async fn set_last_blocked_pr_health_fingerprint( + &self, + _conversation_id: &ChatConversationId, + _fingerprint: Option<&str>, + ) -> AppResult<()> { + Ok(()) + } async fn create_or_update( &self, workspace: AgentConversationWorkspace, diff --git a/src-tauri/src/application/pr_startup_recovery_tests.rs b/src-tauri/src/application/pr_startup_recovery_tests.rs index fc33e3655e..da3c5dd777 100644 --- a/src-tauri/src/application/pr_startup_recovery_tests.rs +++ b/src-tauri/src/application/pr_startup_recovery_tests.rs @@ -1344,6 +1344,13 @@ struct WorkspaceLoadErrorRepository; #[async_trait] impl AgentConversationWorkspaceRepository for WorkspaceLoadErrorRepository { + async fn set_last_blocked_pr_health_fingerprint( + &self, + _conversation_id: &ChatConversationId, + _fingerprint: Option<&str>, + ) -> AppResult<()> { + Ok(()) + } async fn create_or_update( &self, _workspace: AgentConversationWorkspace, diff --git a/src-tauri/src/application/services/pr_merge_poller.rs b/src-tauri/src/application/services/pr_merge_poller.rs index 2bb9d4c804..79c5bc3b4f 100644 --- a/src-tauri/src/application/services/pr_merge_poller.rs +++ b/src-tauri/src/application/services/pr_merge_poller.rs @@ -3052,6 +3052,15 @@ async fn route_agent_workspace_pr_autofix_for_target( return Ok(false); } } + if cross_streak_fingerprint_suppresses_dispatch( + workspace_repo.as_ref(), + conversation_id, + &issue.classification, + ) + .await? + { + return Ok(false); + } if authorize_agent_workspace_pr_autofix(workspace_repo.as_ref(), conversation_id, &target) .await? .is_none() @@ -3145,6 +3154,67 @@ async fn route_agent_workspace_pr_autofix_for_target( .await } +/// The step recorded when a fresh PR autofix streak is suppressed by cross-streak memory. +pub(crate) const CROSS_STREAK_FINGERPRINT_HOLD_STEP: &str = "repair_fingerprint_cross_streak_hold"; + +/// A repair attempt's fingerprint hold dies with its streak. Once a streak exhausts its retries, +/// the next poll would otherwise start a brand new streak against the exact same failing check — +/// the outer loop that turned one unchanged failure into four Opus generations on 2026-07-31. +/// +/// Returns `true` when this dispatch must be suppressed. Different health clears the memory so a +/// genuinely new failure is never held by a stale one. A read failure never suppresses: a broken +/// query must not silently disable PR autofix. +async fn cross_streak_fingerprint_suppresses_dispatch( + workspace_repo: &dyn AgentConversationWorkspaceRepository, + conversation_id: &ChatConversationId, + classification: &str, +) -> crate::error::AppResult { + let Some(workspace) = workspace_repo + .get_by_conversation_id(conversation_id) + .await? + else { + return Ok(false); + }; + let Some(remembered) = workspace.last_blocked_pr_health_fingerprint.as_deref() else { + return Ok(false); + }; + if remembered != classification { + workspace_repo + .set_last_blocked_pr_health_fingerprint(conversation_id, None) + .await?; + return Ok(false); + } + + // Record the hold once. Repeating it every poll would bury the publication timeline. + let already_recorded = workspace_repo + .list_publication_events(conversation_id) + .await? + .into_iter() + .any(|event| { + event.step == CROSS_STREAK_FINGERPRINT_HOLD_STEP + && event.classification.as_deref() == Some(classification) + }); + if !already_recorded { + workspace_repo + .append_publication_event(AgentConversationWorkspacePublicationEvent::new( + conversation_id.clone(), + CROSS_STREAK_FINGERPRINT_HOLD_STEP, + "blocked", + "A previous repair streak already exhausted itself against this exact PR failure. \ + RalphX is waiting for GitHub to report something different instead of starting \ + another fixer generation.", + Some(classification.to_string()), + )) + .await?; + } + tracing::info!( + conversation_id = conversation_id.as_str(), + classification, + "Suppressing a fresh PR autofix streak against an already-exhausted failure identity" + ); + Ok(true) +} + fn transient_ci_health_fingerprint(health: &PrHealth) -> Option { let failed = health.checks.iter().find(|check| { check.conclusion.as_deref().is_some_and(|value| { diff --git a/src-tauri/src/application/services/pr_merge_poller_tests.rs b/src-tauri/src/application/services/pr_merge_poller_tests.rs index ea145a26bc..9af2eaae3f 100644 --- a/src-tauri/src/application/services/pr_merge_poller_tests.rs +++ b/src-tauri/src/application/services/pr_merge_poller_tests.rs @@ -5915,6 +5915,146 @@ async fn live_pr_autofix_dispatches_new_generation_after_ci_rerun_fingerprint_ch assert_eq!(current.phase, AgentWorkspaceRepairPhase::Repairing); } +/// Cross-streak memory: an exhausted streak leaves its failure identity on the workspace, and the +/// next poll must recognise it instead of starting a fresh streak on identical evidence. +#[tokio::test] +async fn exhausted_streak_fingerprint_suppresses_a_fresh_streak_until_health_changes() { + let worktree = tempfile::tempdir().expect("worktree path"); + let mut workspace = supervised_workspace( + "cross-streak-fingerprint", + "project-cross-streak-fingerprint", + worktree.path(), + ); + init_repair_dispatch_repo(worktree.path(), &workspace.branch_name); + workspace.base_commit = Some("base-cross-streak".to_string()); + let conversation_id = workspace.conversation_id.clone(); + let workspace_repo = Arc::new(MemoryAgentConversationWorkspaceRepository::new()); + workspace_repo + .create_or_update(workspace) + .await + .expect("workspace should persist"); + let repair_repo: Arc = workspace_repo.clone(); + let branch_update_repo: Arc = + Arc::new(MemoryBranchUpdateRepository::new()); + let agent_run_repo = seeded_latest_pr_fixer_run_repo(&conversation_id).await; + let mut exhausted_health = open_pr_health("cross-streak-head"); + exhausted_health.checks.push(PrHealthCheck { + name: "Rust tests".to_string(), + status: Some("completed".to_string()), + conclusion: Some("failure".to_string()), + details_url: Some("https://github.com/owner/repo/actions/runs/930".to_string()), + }); + let fingerprint = super::classify_agent_workspace_pr_autofix_issue(101, &exhausted_health) + .expect("failed check should classify") + .classification; + // No current repair attempt: the previous streak is gone, exactly as after exhaustion. + workspace_repo + .set_last_blocked_pr_health_fingerprint(&conversation_id, Some(&fingerprint)) + .await + .expect("remember the exhausted failure identity"); + + let github = Arc::new(MockGithubService::new()); + github.state().fetch_pr_health_result = Some(Ok(exhausted_health)); + let chat = Arc::new(MockChatService::with_agent_run_repo(Arc::clone( + &agent_run_repo, + ))); + + let routed = super::route_agent_workspace_pr_autofix_if_needed_with_repair_repo( + Arc::clone(&github) as Arc, + worktree.path(), + 101, + &conversation_id, + workspace_repo.clone(), + Some(agent_run_repo.clone()), + Some(Arc::clone(&repair_repo)), + Some(Arc::clone(&branch_update_repo)), + chat.clone() as Arc, + ) + .await + .expect("an exhausted fingerprint should suppress a fresh streak"); + + assert!(!routed, "a fresh streak on identical evidence must not start"); + assert!(chat.get_sent_messages().await.is_empty()); + assert!( + repair_repo + .get_current_repair_attempt(&conversation_id) + .await + .expect("load current attempt") + .is_none(), + "suppression must not create a repair generation" + ); + let hold_events = workspace_repo + .list_publication_events(&conversation_id) + .await + .expect("list publication events") + .into_iter() + .filter(|event| event.step == super::CROSS_STREAK_FINGERPRINT_HOLD_STEP) + .count(); + assert_eq!(hold_events, 1, "the hold must be visible, exactly once"); + + // Polling again must stay suppressed and must not repeat the event. + let routed = super::route_agent_workspace_pr_autofix_if_needed_with_repair_repo( + Arc::clone(&github) as Arc, + worktree.path(), + 101, + &conversation_id, + workspace_repo.clone(), + Some(agent_run_repo.clone()), + Some(Arc::clone(&repair_repo)), + Some(Arc::clone(&branch_update_repo)), + chat.clone() as Arc, + ) + .await + .expect("repeat polls stay suppressed"); + assert!(!routed); + assert_eq!( + workspace_repo + .list_publication_events(&conversation_id) + .await + .expect("list publication events") + .into_iter() + .filter(|event| event.step == super::CROSS_STREAK_FINGERPRINT_HOLD_STEP) + .count(), + 1, + "the hold event must be deduped, not repeated every poll" + ); + + // Different health is new evidence: the memory clears and autofix runs again. + let mut changed_health = open_pr_health("cross-streak-head"); + changed_health.checks.push(PrHealthCheck { + name: "Clippy".to_string(), + status: Some("completed".to_string()), + conclusion: Some("failure".to_string()), + details_url: Some("https://github.com/owner/repo/actions/runs/931".to_string()), + }); + github.state().fetch_pr_health_result = Some(Ok(changed_health)); + + let routed = super::route_agent_workspace_pr_autofix_if_needed_with_repair_repo( + Arc::clone(&github) as Arc, + worktree.path(), + 101, + &conversation_id, + workspace_repo.clone(), + Some(agent_run_repo), + Some(Arc::clone(&repair_repo)), + Some(branch_update_repo), + chat.clone() as Arc, + ) + .await + .expect("changed health should clear the memory and dispatch"); + + assert!(routed, "a genuinely new failure must not be held by a stale one"); + let refreshed = workspace_repo + .get_by_conversation_id(&conversation_id) + .await + .expect("reload workspace") + .expect("workspace exists"); + assert!( + refreshed.last_blocked_pr_health_fingerprint.is_none(), + "changed health must clear the remembered failure identity" + ); +} + /// A generation parked because GitHub reported unchanged health must be honoured by the poller's /// dispatch gate exactly like a pre-existing-on-base hold. Without this the durable recovery lane /// parks the attempt and the very next poll starts another fixer on identical evidence — the @@ -7315,6 +7455,13 @@ impl SequencedWorkspaceRepository { #[async_trait] impl AgentConversationWorkspaceRepository for SequencedWorkspaceRepository { + async fn set_last_blocked_pr_health_fingerprint( + &self, + _conversation_id: &ChatConversationId, + _fingerprint: Option<&str>, + ) -> AppResult<()> { + Ok(()) + } async fn create_or_update( &self, workspace: AgentConversationWorkspace, @@ -7565,6 +7712,13 @@ struct ReviewMonitorLookupErrorRepository { #[async_trait] impl AgentConversationWorkspaceRepository for ReviewMonitorLookupErrorRepository { + async fn set_last_blocked_pr_health_fingerprint( + &self, + _conversation_id: &ChatConversationId, + _fingerprint: Option<&str>, + ) -> AppResult<()> { + Ok(()) + } async fn create_or_update( &self, workspace: AgentConversationWorkspace, @@ -7704,6 +7858,13 @@ struct WorkspaceLookupErrorRepository; #[async_trait] impl AgentConversationWorkspaceRepository for WorkspaceLookupErrorRepository { + async fn set_last_blocked_pr_health_fingerprint( + &self, + _conversation_id: &ChatConversationId, + _fingerprint: Option<&str>, + ) -> AppResult<()> { + Ok(()) + } async fn create_or_update( &self, _workspace: AgentConversationWorkspace, diff --git a/src-tauri/src/infrastructure/memory/memory_agent_conversation_workspace_repo.rs b/src-tauri/src/infrastructure/memory/memory_agent_conversation_workspace_repo.rs index a3cbf00d4a..b6d35025b2 100644 --- a/src-tauri/src/infrastructure/memory/memory_agent_conversation_workspace_repo.rs +++ b/src-tauri/src/infrastructure/memory/memory_agent_conversation_workspace_repo.rs @@ -1231,6 +1231,19 @@ impl AgentConversationWorkspaceRepository for MemoryAgentConversationWorkspaceRe .collect()) } + async fn set_last_blocked_pr_health_fingerprint( + &self, + conversation_id: &ChatConversationId, + fingerprint: Option<&str>, + ) -> AppResult<()> { + if let Some(workspace) = self.workspaces.write().await.get_mut(conversation_id) { + workspace.last_blocked_pr_health_fingerprint = fingerprint.map(str::to_string); + workspace.last_blocked_pr_health_at = fingerprint.map(|_| Utc::now()); + workspace.updated_at = Utc::now(); + } + Ok(()) + } + async fn update_pr_supervision_preferences( &self, conversation_id: &ChatConversationId, diff --git a/src-tauri/src/infrastructure/sqlite/migrations/mod.rs b/src-tauri/src/infrastructure/sqlite/migrations/mod.rs index 8a907dd760..74e37d4dd2 100644 --- a/src-tauri/src/infrastructure/sqlite/migrations/mod.rs +++ b/src-tauri/src/infrastructure/sqlite/migrations/mod.rs @@ -577,11 +577,14 @@ mod v20260730151837_agent_workspace_repair_ci_rerun_reservations; mod v20260730151837_agent_workspace_repair_ci_rerun_reservations_tests; mod v20260730161032_agent_workspace_pr_autofix_completion_evidence; mod v20260731023949_agent_run_identity; +mod v20260731125157_add_workspace_repair_fingerprint_state; #[cfg(test)] mod v20260730161032_agent_workspace_pr_autofix_completion_evidence_tests; #[cfg(test)] mod v20260731023949_agent_run_identity_tests; #[cfg(test)] +mod v20260731125157_add_workspace_repair_fingerprint_state_tests; +#[cfg(test)] pub(super) fn migrate_scripted_agent_workflows_for_test(conn: &Connection) -> AppResult<()> { v20260715194617_scripted_agent_workflows::migrate(conn) } @@ -675,7 +678,7 @@ mod v8_task_git_fields_tests; mod v9_project_git_fields_tests; /// Current schema version - bump this when adding a new migration -pub const SCHEMA_VERSION: i64 = 20260731023949; +pub const SCHEMA_VERSION: i64 = 20260731125157; /// Migration function signature type MigrationFn = fn(&Connection) -> AppResult<()>; @@ -1856,6 +1859,11 @@ const MIGRATIONS: &[Migration] = &[ name: "agent_run_identity", migrate: v20260731023949_agent_run_identity::migrate, }, + Migration { + version: 20260731125157, + name: "add_workspace_repair_fingerprint_state", + migrate: v20260731125157_add_workspace_repair_fingerprint_state::migrate, + }, ]; #[derive(Debug, Clone, Copy, PartialEq, Eq)] diff --git a/src-tauri/src/infrastructure/sqlite/migrations/v20260731125157_add_workspace_repair_fingerprint_state.rs b/src-tauri/src/infrastructure/sqlite/migrations/v20260731125157_add_workspace_repair_fingerprint_state.rs new file mode 100644 index 0000000000..8dd5668d09 --- /dev/null +++ b/src-tauri/src/infrastructure/sqlite/migrations/v20260731125157_add_workspace_repair_fingerprint_state.rs @@ -0,0 +1,33 @@ +// Migration v20260731125157: add workspace repair fingerprint state +// +// A PR autofix repair attempt can park itself against an exact failure fingerprint, but that hold +// dies with the attempt. Once a streak settles blocked-exhausted, the next poll starts a brand new +// streak with no memory of what already failed, which is how one unchanged failing check consumed +// four agent generations on 2026-07-31. Persisting the last blocked fingerprint on the workspace +// gives the poller cross-streak memory that outlives any single attempt. + +use rusqlite::Connection; + +use crate::error::AppResult; + +use super::helpers; + +pub fn migrate(conn: &Connection) -> AppResult<()> { + helpers::add_column_if_not_exists( + conn, + "agent_conversation_workspaces", + "last_blocked_pr_health_fingerprint", + "TEXT", + )?; + + helpers::add_column_if_not_exists( + conn, + "agent_conversation_workspaces", + "last_blocked_pr_health_at", + "TEXT", + )?; + + tracing::info!("Migration v20260731125157: workspace repair fingerprint state ready"); + + Ok(()) +} diff --git a/src-tauri/src/infrastructure/sqlite/migrations/v20260731125157_add_workspace_repair_fingerprint_state_tests.rs b/src-tauri/src/infrastructure/sqlite/migrations/v20260731125157_add_workspace_repair_fingerprint_state_tests.rs new file mode 100644 index 0000000000..312df81f4e --- /dev/null +++ b/src-tauri/src/infrastructure/sqlite/migrations/v20260731125157_add_workspace_repair_fingerprint_state_tests.rs @@ -0,0 +1,69 @@ +//! Tests for migration v20260731125157: add workspace repair fingerprint state + +use rusqlite::Connection; + +use super::v20260731125157_add_workspace_repair_fingerprint_state::migrate; + +fn setup_test_db() -> Connection { + let conn = Connection::open_in_memory().expect("Failed to create in-memory database"); + conn.execute_batch( + "CREATE TABLE agent_conversation_workspaces (conversation_id TEXT PRIMARY KEY);", + ) + .expect("seed agent conversation workspaces table"); + conn +} + +fn columns(conn: &Connection) -> Vec { + conn.prepare("PRAGMA table_info(agent_conversation_workspaces)") + .expect("prepare table info") + .query_map([], |row| row.get(1)) + .expect("query table info") + .collect::>() + .expect("read table info") +} + +#[test] +fn migration_adds_last_blocked_pr_health_columns() { + let conn = setup_test_db(); + migrate(&conn).expect("migration should add fingerprint state columns"); + let columns = columns(&conn); + assert!(columns.contains(&"last_blocked_pr_health_fingerprint".to_string())); + assert!(columns.contains(&"last_blocked_pr_health_at".to_string())); +} + +#[test] +fn migration_is_idempotent_on_already_upgraded_databases() { + let conn = setup_test_db(); + migrate(&conn).expect("first run should add columns"); + migrate(&conn).expect("second run must not fail on existing columns"); + assert_eq!( + columns(&conn) + .iter() + .filter(|name| name.as_str() == "last_blocked_pr_health_fingerprint") + .count(), + 1 + ); +} + +#[test] +fn migration_preserves_existing_workspace_rows() { + let conn = setup_test_db(); + conn.execute( + "INSERT INTO agent_conversation_workspaces (conversation_id) VALUES ('existing')", + [], + ) + .expect("seed an already-published workspace"); + migrate(&conn).expect("migration should add fingerprint state columns"); + let fingerprint: Option = conn + .query_row( + "SELECT last_blocked_pr_health_fingerprint FROM agent_conversation_workspaces \ + WHERE conversation_id = 'existing'", + [], + |row| row.get(0), + ) + .expect("read backfilled column"); + assert!( + fingerprint.is_none(), + "existing workspaces start with no remembered failure identity" + ); +} diff --git a/src-tauri/src/infrastructure/sqlite/sqlite_agent_conversation_workspace_repo.rs b/src-tauri/src/infrastructure/sqlite/sqlite_agent_conversation_workspace_repo.rs index 454d094e7b..6d5a7da276 100644 --- a/src-tauri/src/infrastructure/sqlite/sqlite_agent_conversation_workspace_repo.rs +++ b/src-tauri/src/infrastructure/sqlite/sqlite_agent_conversation_workspace_repo.rs @@ -153,6 +153,10 @@ fn row_to_workspace(row: &rusqlite::Row<'_>) -> AppResult>("pr_supervision_updated_at")? .map(|value| parse_datetime(&value)), + last_blocked_pr_health_fingerprint: row.get("last_blocked_pr_health_fingerprint")?, + last_blocked_pr_health_at: row + .get::<_, Option>("last_blocked_pr_health_at")? + .map(|value| parse_datetime(&value)), status: AgentConversationWorkspaceStatus::from_str(&status) .unwrap_or(AgentConversationWorkspaceStatus::Active), created_at: parse_datetime(&created_at), @@ -2473,6 +2477,30 @@ impl AgentConversationWorkspaceRepository for SqliteAgentConversationWorkspaceRe .await } + async fn set_last_blocked_pr_health_fingerprint( + &self, + conversation_id: &ChatConversationId, + fingerprint: Option<&str>, + ) -> AppResult<()> { + let conversation_id = conversation_id.as_str().to_string(); + let fingerprint = fingerprint.map(str::to_string); + let observed_at = fingerprint.as_ref().map(|_| Utc::now().to_rfc3339()); + let now = Utc::now().to_rfc3339(); + self.db + .run(move |conn| { + conn.execute( + "UPDATE agent_conversation_workspaces + SET last_blocked_pr_health_fingerprint = ?2, + last_blocked_pr_health_at = ?3, + updated_at = ?4 + WHERE conversation_id = ?1", + rusqlite::params![conversation_id, fingerprint, observed_at, now], + )?; + Ok(()) + }) + .await + } + async fn update_pr_supervision_preferences( &self, conversation_id: &ChatConversationId, diff --git a/src-tauri/src/infrastructure/sqlite/sqlite_agent_conversation_workspace_repo_tests.rs b/src-tauri/src/infrastructure/sqlite/sqlite_agent_conversation_workspace_repo_tests.rs index a88f08c673..2f6e4f7819 100644 --- a/src-tauri/src/infrastructure/sqlite/sqlite_agent_conversation_workspace_repo_tests.rs +++ b/src-tauri/src/infrastructure/sqlite/sqlite_agent_conversation_workspace_repo_tests.rs @@ -3813,6 +3813,58 @@ async fn list_active_transient_publish_status_workspaces_filters_stale_open_rows ); } +#[tokio::test] +async fn last_blocked_pr_health_fingerprint_round_trips_and_clears() { + let (_db, repo, conversation_id) = setup_repo(); + repo.create_or_update(make_workspace(conversation_id.clone())) + .await + .unwrap(); + + let fresh = repo + .get_by_conversation_id(&conversation_id) + .await + .unwrap() + .expect("workspace should exist"); + assert!(fresh.last_blocked_pr_health_fingerprint.is_none()); + assert!(fresh.last_blocked_pr_health_at.is_none()); + + repo.set_last_blocked_pr_health_fingerprint( + &conversation_id, + Some("github_pr_autofix:684:checks:rust-tests"), + ) + .await + .unwrap(); + + let remembered = repo + .get_by_conversation_id(&conversation_id) + .await + .unwrap() + .expect("workspace should exist"); + assert_eq!( + remembered.last_blocked_pr_health_fingerprint.as_deref(), + Some("github_pr_autofix:684:checks:rust-tests") + ); + assert!( + remembered.last_blocked_pr_health_at.is_some(), + "a remembered failure identity records when it was observed" + ); + + repo.set_last_blocked_pr_health_fingerprint(&conversation_id, None) + .await + .unwrap(); + + let cleared = repo + .get_by_conversation_id(&conversation_id) + .await + .unwrap() + .expect("workspace should exist"); + assert!(cleared.last_blocked_pr_health_fingerprint.is_none()); + assert!( + cleared.last_blocked_pr_health_at.is_none(), + "clearing the identity must also clear its observation time" + ); +} + #[tokio::test] async fn pr_supervision_preferences_round_trip() { let (_db, repo, conversation_id) = setup_repo(); From b75f13ef34107c2af23f3464f537385a8695da28 Mon Sep 17 00:00:00 2001 From: lazabogdan <6580668+lazabogdan@users.noreply.github.com> Date: Fri, 31 Jul 2026 16:59:09 +0300 Subject: [PATCH 05/29] feat(repair): bound unattended repair by agent minutes, not attempt count Retry caps count attempts, not cost: three cheap generations and three hour-long Opus generations look identical to a streak counter. A failure no agent can fix could therefore consume an unbounded budget. - New limits.repair_fingerprint_budget_minutes (default 45, 0 disables) with RALPHX_LIMITS_REPAIR_FINGERPRINT_BUDGET_MINUTES override. - New list_repair_attempts_for_conversation on the repair repository (SQLite via db.run, plus memory) so cost can be summed across generations rather than read from the current-attempt view. - Spend accounting sums wall-clock time of finished runs whose generation carried the same failure fingerprint. Only finished runs count, so an in-flight run can never push a conversation over budget mid-repair. - Exhausting the budget parks the generation needs-human, remembers the fingerprint on the workspace, records a repair_budget_exhausted publication event, and raises an Inbox notification. It is a handover, never a silent stop. Validation: new regression proves an over-budget conversation buys no further generation and surfaces the handover through all three channels; 67 recovery and 75 SQLite workspace repo tests pass. Co-Authored-By: Claude Opus 5 --- config/ralphx.yaml | 5 + .../agent_workspace_repair_repository.rs | 7 + .../durable_attempt_recovery.rs | 102 +++++++++++++- .../pr_autofix_redelivery.rs | 60 ++++++++ .../agent_workspace_publish_recovery_tests.rs | 129 ++++++++++++++++++ .../claude/agent_config/runtime_config.rs | 14 ++ .../repair_attempts.rs | 16 +++ .../repair_attempts.rs | 22 +++ 8 files changed, 353 insertions(+), 2 deletions(-) diff --git a/config/ralphx.yaml b/config/ralphx.yaml index f40b715f7d..b567517c24 100644 --- a/config/ralphx.yaml +++ b/config/ralphx.yaml @@ -299,6 +299,11 @@ limits: # chat_service_errors.rs — max times an agent conversation is auto-resumed after a provider error max_resume_attempts: 5 # Env: RALPHX_LIMITS_MAX_RESUME_ATTEMPTS + # agent_workspace repair — total agent minutes spent on one PR failure identity before RalphX + # stops retrying and hands the failure to a human. 0 disables the budget. + # Env: RALPHX_LIMITS_REPAIR_FINGERPRINT_BUDGET_MINUTES + repair_fingerprint_budget_minutes: 45 + ideation: # Seconds of inactivity before an agent is considered "likely_waiting" vs "likely_generating". # Used by get_child_session_status MCP tool to derive estimated_status. Env: RALPHX_IDEATION_ACTIVITY_THRESHOLD_SECS diff --git a/src-tauri/crates/ralphx-domain/src/repositories/agent_workspace_repair_repository.rs b/src-tauri/crates/ralphx-domain/src/repositories/agent_workspace_repair_repository.rs index b6daee46d1..ceff8ba21e 100644 --- a/src-tauri/crates/ralphx-domain/src/repositories/agent_workspace_repair_repository.rs +++ b/src-tauri/crates/ralphx-domain/src/repositories/agent_workspace_repair_repository.rs @@ -215,6 +215,13 @@ pub trait AgentWorkspaceRepairRepository: Send + Sync { async fn list_recoverable_repair_attempts(&self) -> AppResult>; + /// Every durable generation recorded for this conversation, settled or not, newest last. + /// Cost accounting spans generations, so it cannot use the current-attempt view alone. + async fn list_repair_attempts_for_conversation( + &self, + conversation_id: &ChatConversationId, + ) -> AppResult>; + async fn start_or_join_repair_attempt( &self, request: StartOrJoinAgentWorkspaceRepairAttempt, diff --git a/src-tauri/src/application/agent_workspace_publish_recovery/durable_attempt_recovery.rs b/src-tauri/src/application/agent_workspace_publish_recovery/durable_attempt_recovery.rs index b3a5e14817..a3dbe8d795 100644 --- a/src-tauri/src/application/agent_workspace_publish_recovery/durable_attempt_recovery.rs +++ b/src-tauri/src/application/agent_workspace_publish_recovery/durable_attempt_recovery.rs @@ -4,14 +4,16 @@ use chrono::{Duration, Utc}; use super::pr_autofix_redelivery::{ due_pr_autofix_redispatch_message, evaluate_pr_autofix_successor, - remember_blocked_pr_autofix_fingerprint, PrAutofixSuccessorDecision, + pr_autofix_fingerprint_spend, remember_blocked_pr_autofix_fingerprint, + PrAutofixFingerprintSpend, PrAutofixSuccessorDecision, }; use super::StalePublishRepairRecoveryOutcome; use crate::application::agent_conversation_workspace::resolve_effective_agent_conversation_workspace_path; use crate::application::agent_workspace_pr_autofix_attempt::load_latest_exact_pr_autofix_run_for_pr; use crate::application::agent_workspace_publish_repair_state::{ agent_workspace_repair_dispatch_is_due, agent_workspace_repair_is_health_held, - block_agent_workspace_repair_completion, classify_agent_workspace_repair_delivery, + block_agent_workspace_repair_completion, block_agent_workspace_repair_needs_human, + classify_agent_workspace_repair_delivery, continue_agent_workspace_repair_at_boundary, inspect_agent_workspace_repair_completion, record_agent_workspace_repair_validation, release_and_clear_agent_workspace_repair_target_lease, @@ -26,6 +28,10 @@ use crate::application::agent_workspace_publish_repair_state::{ }; use crate::application::chat_service::{ChatService, SendMessageOptions, SendQueuePolicy}; use crate::application::{AppState, GitService}; +use crate::domain::entities::{ + NewNotification, NotificationCategory, NotificationSeverity, NotificationTarget, + NotificationTargetKind, +}; use crate::domain::entities::{ AgentConversationWorkspace, AgentConversationWorkspacePublicationEvent, AgentRunId, AgentRunStatus, AgentWorkspaceRepairAttempt, AgentWorkspaceRepairAttemptId, @@ -41,6 +47,8 @@ use crate::domain::repositories::{ use crate::error::{AppError, AppResult}; use crate::infrastructure::agents::claude::agent_names::AGENT_WORKSPACE_REPAIR; +/// Publication step recorded when unattended repair stops because its budget is spent. +const REPAIR_BUDGET_EXHAUSTED_STEP: &str = "repair_budget_exhausted"; const LEGACY_REPAIR_IMPORT_BLOCKED_STEP: &str = "legacy_repair_import_blocked"; const LEGACY_REPAIR_IMPORT_BLOCKED_CLASSIFICATION: &str = "legacy_repair_import_ambiguous"; const LEGACY_REPAIR_IMPORTED_STEP: &str = "legacy_repair_imported"; @@ -581,6 +589,17 @@ async fn retry_safe_blocked_agent_workspace_repair( // Spawning a successor is the most expensive thing this pass can do, so a PR autofix // generation must earn it against live PR health rather than against the streak counter alone. // Blocker text is deliberately not consulted: it is free-form agent prose, not evidence. + // Cost, not attempt count, is what actually needs bounding: three cheap generations and three + // hour-long Opus generations look identical to the streak counter above. + if current.source == AgentWorkspaceRepairSource::PrAutofix { + if let Some(fingerprint) = current.pr_autofix_health_fingerprint.as_deref() { + let spend = + pr_autofix_fingerprint_spend(state, ¤t.conversation_id, fingerprint).await?; + if spend.is_exhausted() { + return park_exhausted_pr_autofix_budget(state, current, &workspace, spend).await; + } + } + } let carryover_pr_autofix_evidence = if current.source == AgentWorkspaceRepairSource::PrAutofix { match evaluate_pr_autofix_successor(state, ¤t, &workspace).await { PrAutofixSuccessorDecision::Proceed(carryover) => carryover, @@ -631,6 +650,85 @@ async fn retry_safe_blocked_agent_workspace_repair( } } +/// Stops unattended repair on one failure identity once it has consumed its agent-minutes budget, +/// and tells the user why. Budget exhaustion is a handover, never a silent skip: the generation is +/// marked needs-human so no automatic path may revive it, the spend is recorded on the publication +/// timeline, and an Inbox notification carries it to the user. +async fn park_exhausted_pr_autofix_budget( + state: &AppState, + current: AgentWorkspaceRepairAttempt, + workspace: &AgentConversationWorkspace, + spend: PrAutofixFingerprintSpend, +) -> AppResult { + let conversation_id = current.conversation_id.clone(); + let summary = format!( + "RalphX has spent {} minutes across {} repair generations on this same PR failure without \ + resolving it, which is the configured limit. Automatic repair has stopped so it does not \ + keep spending on a failure it cannot fix.", + spend.minutes, spend.generations + ); + + remember_blocked_pr_autofix_fingerprint(state, ¤t).await; + state + .agent_conversation_workspace_repo + .append_publication_event(AgentConversationWorkspacePublicationEvent::new( + conversation_id.clone(), + REPAIR_BUDGET_EXHAUSTED_STEP, + "blocked", + &summary, + current.pr_autofix_health_fingerprint.clone(), + )) + .await?; + + let outcome = block_agent_workspace_repair_needs_human( + Arc::clone(&state.agent_workspace_repair_repo), + Arc::clone(&state.branch_update_repo), + current, + &summary, + workspace.pr_auto_merge_current, + ) + .await?; + + state + .notification_service() + .record(NewNotification { + project_id: Some(workspace.project_id.to_string()), + category: NotificationCategory::TaskBlocked, + severity: NotificationSeverity::ActionRequired, + title: match workspace.publication_pr_number { + Some(pr_number) => format!("PR #{pr_number} repair needs you"), + None => "Workspace repair needs you".to_string(), + }, + body: Some(summary), + target: NotificationTarget { + kind: NotificationTargetKind::AgentConversation, + project_id: Some(workspace.project_id.to_string()), + task_id: None, + conversation_id: Some(conversation_id.to_string()), + setup_conversation_id: None, + automation_id: None, + run_id: None, + }, + dedupe_key: Some(format!( + "repair_budget:{}:{}", + conversation_id.as_str(), + workspace + .last_blocked_pr_health_fingerprint + .as_deref() + .unwrap_or("unknown") + )), + }) + .await; + + match outcome { + AgentWorkspaceRepairTransitionOutcome::Applied(_) => { + Ok(DurableRepairRecoveryOutcome::Blocked) + } + AgentWorkspaceRepairTransitionOutcome::Stale(_) + | AgentWorkspaceRepairTransitionOutcome::Missing => Ok(DurableRepairRecoveryOutcome::Stale), + } +} + /// Parks a blocked PR autofix generation whose failure identity has not moved. The hold is a /// visible state, not a silent skip: it appends a publication event and leaves the generation /// where the poller can settle it once GitHub reports different health. diff --git a/src-tauri/src/application/agent_workspace_publish_recovery/pr_autofix_redelivery.rs b/src-tauri/src/application/agent_workspace_publish_recovery/pr_autofix_redelivery.rs index c15503ee5b..028159a296 100644 --- a/src-tauri/src/application/agent_workspace_publish_recovery/pr_autofix_redelivery.rs +++ b/src-tauri/src/application/agent_workspace_publish_recovery/pr_autofix_redelivery.rs @@ -15,6 +15,66 @@ use super::durable_attempt_recovery::{ human_repair_dispatch_context, DEFAULT_REPAIR_DISPATCH_CONTEXT, }; +/// How much agent time has already been spent on one failure identity, and whether that spend has +/// passed the configured budget. +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub(crate) struct PrAutofixFingerprintSpend { + pub(crate) generations: u32, + pub(crate) minutes: u64, + pub(crate) budget_minutes: u64, +} + +impl PrAutofixFingerprintSpend { + pub(crate) fn is_exhausted(self) -> bool { + self.budget_minutes > 0 && self.minutes >= self.budget_minutes + } +} + +/// Sums the wall-clock time of every agent run that already worked on this exact failure identity. +/// +/// Retry caps count attempts, not cost: three cheap generations and three hour-long Opus +/// generations look identical to a streak counter. Only finished runs are counted, so an +/// in-flight run can never push a conversation over budget mid-repair. +pub(crate) async fn pr_autofix_fingerprint_spend( + state: &AppState, + conversation_id: &crate::domain::entities::ChatConversationId, + fingerprint: &str, +) -> crate::error::AppResult { + let budget_minutes = crate::infrastructure::agents::limits_config() + .repair_fingerprint_budget_minutes; + let attempts = state + .agent_workspace_repair_repo + .list_repair_attempts_for_conversation(conversation_id) + .await?; + + let mut generations = 0; + let mut seconds: i64 = 0; + for attempt in attempts { + if attempt.pr_autofix_health_fingerprint.as_deref() != Some(fingerprint) { + continue; + } + generations += 1; + let Some(run_id) = attempt.reserved_agent_run_id.as_ref() else { + continue; + }; + let Some(run) = state.agent_run_repo.get_by_id(run_id).await? else { + continue; + }; + if run.conversation_id != *conversation_id { + continue; + } + if let Some(completed_at) = run.completed_at { + seconds += (completed_at - run.started_at).num_seconds().max(0); + } + } + + Ok(PrAutofixFingerprintSpend { + generations, + minutes: (seconds / 60) as u64, + budget_minutes, + }) +} + /// Whether a blocked PR autofix generation has earned another agent generation. #[derive(Debug, Clone, PartialEq, Eq)] pub(super) enum PrAutofixSuccessorDecision { diff --git a/src-tauri/src/application/agent_workspace_publish_recovery_tests.rs b/src-tauri/src/application/agent_workspace_publish_recovery_tests.rs index 05c1655fdc..0229ff12a9 100644 --- a/src-tauri/src/application/agent_workspace_publish_recovery_tests.rs +++ b/src-tauri/src/application/agent_workspace_publish_recovery_tests.rs @@ -4758,6 +4758,135 @@ async fn blocked_pr_autofix_with_unchanged_health_parks_without_spawning() { assert!(still_held.settled_at.is_none()); } +/// Retry caps count attempts, not cost. A conversation that has already burned its agent-minutes +/// budget on one failure identity must hand the failure to a human instead of buying another +/// generation, and the handover must be visible rather than a silent stop. +#[tokio::test] +async fn exhausted_agent_minutes_budget_parks_needs_human_with_a_notification() { + let (state, conversation_id, _worktree_parent, _project_dir) = + seed_pr_autofix_health_workspace(125).await; + start_blocked_pr_autofix_generation(&state, &conversation_id).await; + let fingerprint = "github_pr_autofix:684:checks:rust-tests".to_string(); + let blocked = block_pr_autofix_attempt_with_fingerprint( + &state, + &conversation_id, + Some(fingerprint.clone()), + ) + .await; + + // A finished run that already consumed far more than the default 45-minute budget. + let mut run = crate::domain::entities::AgentRun::new(conversation_id.clone()); + run.started_at = chrono::Utc::now() - chrono::Duration::minutes(90); + run.completed_at = Some(chrono::Utc::now() - chrono::Duration::minutes(1)); + run.status = crate::domain::entities::AgentRunStatus::Completed; + let run_id = run.id.clone(); + state + .agent_run_repo + .create(run) + .await + .expect("seed an expensive finished repair run"); + bind_reserved_run_to_attempt(&state, &conversation_id, &run_id).await; + + assert_eq!( + recover_agent_workspace_repair_attempts_for_state(&state) + .await + .expect("evaluate an over-budget PR autofix generation"), + 1 + ); + + let parked = state + .agent_workspace_repair_repo + .get_current_repair_attempt(&conversation_id) + .await + .expect("load parked attempt") + .expect("parked attempt remains current"); + assert_eq!( + parked.generation, blocked.generation, + "an exhausted budget must not buy another generation" + ); + assert!( + parked.pending_reasons.iter().any(|reason| reason + == crate::application::agent_workspace_publish_repair_state::NEEDS_HUMAN_REPAIR_REASON), + "budget exhaustion is a human handover, not an automatic retry: {parked:?}" + ); + + let events = state + .agent_conversation_workspace_repo + .list_publication_events(&conversation_id) + .await + .expect("list publication events"); + assert!( + events + .iter() + .any(|event| event.step == "repair_budget_exhausted"), + "the spend must be recorded on the publication timeline" + ); + + let notifications = state + .notification_repo + .list(None, None, 20) + .await + .expect("list notifications"); + assert!( + notifications.notifications.iter().any(|notification| { + notification.target.conversation_id.as_deref() + == Some(conversation_id.as_str().as_str()) + && notification + .body + .as_deref() + .is_some_and(|body| body.contains("repair generations")) + }), + "budget exhaustion must reach the user, never stop silently: {:?}", + notifications.notifications + ); + + // The workspace also remembers the identity, so a fresh streak cannot restart on it. + let workspace = state + .agent_conversation_workspace_repo + .get_by_conversation_id(&conversation_id) + .await + .expect("reload workspace") + .expect("workspace exists"); + assert_eq!( + workspace.last_blocked_pr_health_fingerprint.as_deref(), + Some(fingerprint.as_str()) + ); +} + +/// Binds an existing run to the current attempt as its durable reservation. +async fn bind_reserved_run_to_attempt( + state: &AppState, + conversation_id: &ChatConversationId, + run_id: &AgentRunId, +) { + let mut attempt = state + .agent_workspace_repair_repo + .get_current_repair_attempt(conversation_id) + .await + .expect("load attempt to bind") + .expect("attempt exists to bind"); + let expected_phase = attempt.phase; + let expected_updated_at = attempt.updated_at; + attempt.reserved_agent_run_id = Some(run_id.clone()); + attempt.updated_at += chrono::Duration::microseconds(1); + let outcome = state + .agent_workspace_repair_repo + .transition_repair_attempt(AgentWorkspaceRepairAttemptTransition { + attempt, + expected_phase, + expected_updated_at, + next_phase: expected_phase, + compatibility_projection: None, + events: Vec::new(), + }) + .await + .expect("bind reserved run"); + assert!(matches!( + outcome, + AgentWorkspaceRepairAttemptTransitionOutcome::Applied(_) + )); +} + #[tokio::test] async fn blocked_pr_autofix_without_provable_health_withholds_the_successor() { let state = AppState::new_test(); diff --git a/src-tauri/src/infrastructure/agents/claude/agent_config/runtime_config.rs b/src-tauri/src/infrastructure/agents/claude/agent_config/runtime_config.rs index e6b9883720..1e10123b07 100644 --- a/src-tauri/src/infrastructure/agents/claude/agent_config/runtime_config.rs +++ b/src-tauri/src/infrastructure/agents/claude/agent_config/runtime_config.rs @@ -724,17 +724,27 @@ pub struct LimitsConfig { pub max_resume_attempts: u64, #[serde(default = "default_max_live_folder_references")] pub max_live_folder_references: usize, + /// Total agent minutes RalphX will spend repairing one PR failure identity before handing it + /// to a human. Unattended repair can otherwise burn an unbounded budget on a failure no agent + /// can fix. `0` disables the budget. + #[serde(default = "default_repair_fingerprint_budget_minutes")] + pub repair_fingerprint_budget_minutes: u64, } fn default_max_live_folder_references() -> usize { 5 } +fn default_repair_fingerprint_budget_minutes() -> u64 { + 45 +} + impl Default for LimitsConfig { fn default() -> Self { Self { max_resume_attempts: 5, max_live_folder_references: default_max_live_folder_references(), + repair_fingerprint_budget_minutes: default_repair_fingerprint_budget_minutes(), } } } @@ -1220,6 +1230,10 @@ fn apply_env_overrides_with(cfg: &mut AllRuntimeConfig, lookup: &dyn Fn(&str) -> cfg.limits.max_resume_attempts, "RALPHX_LIMITS_MAX_RESUME_ATTEMPTS" ); + env_u64!( + cfg.limits.repair_fingerprint_budget_minutes, + "RALPHX_LIMITS_REPAIR_FINGERPRINT_BUDGET_MINUTES" + ); // Verification env_u64!( diff --git a/src-tauri/src/infrastructure/memory/memory_agent_conversation_workspace_repo/repair_attempts.rs b/src-tauri/src/infrastructure/memory/memory_agent_conversation_workspace_repo/repair_attempts.rs index e50440fa4d..73ec5c4485 100644 --- a/src-tauri/src/infrastructure/memory/memory_agent_conversation_workspace_repo/repair_attempts.rs +++ b/src-tauri/src/infrastructure/memory/memory_agent_conversation_workspace_repo/repair_attempts.rs @@ -187,6 +187,22 @@ impl AgentWorkspaceRepairRepository for MemoryAgentConversationWorkspaceReposito .cloned()) } + async fn list_repair_attempts_for_conversation( + &self, + conversation_id: &ChatConversationId, + ) -> AppResult> { + let mut attempts = self + .repair_attempts + .read() + .await + .values() + .filter(|attempt| &attempt.conversation_id == conversation_id) + .cloned() + .collect::>(); + attempts.sort_by(|left, right| left.generation.cmp(&right.generation)); + Ok(attempts) + } + async fn list_recoverable_repair_attempts( &self, ) -> AppResult> { diff --git a/src-tauri/src/infrastructure/sqlite/sqlite_agent_conversation_workspace_repo/repair_attempts.rs b/src-tauri/src/infrastructure/sqlite/sqlite_agent_conversation_workspace_repo/repair_attempts.rs index 9811dc3659..c7bbf708e9 100644 --- a/src-tauri/src/infrastructure/sqlite/sqlite_agent_conversation_workspace_repo/repair_attempts.rs +++ b/src-tauri/src/infrastructure/sqlite/sqlite_agent_conversation_workspace_repo/repair_attempts.rs @@ -588,6 +588,28 @@ impl AgentWorkspaceRepairRepository for SqliteAgentConversationWorkspaceReposito .await } + async fn list_repair_attempts_for_conversation( + &self, + conversation_id: &ChatConversationId, + ) -> AppResult> { + let conversation_id = conversation_id.as_str().to_string(); + self.db + .run(move |conn| { + let mut statement = conn.prepare( + "SELECT * FROM agent_workspace_repair_attempts + WHERE conversation_id = ?1 + ORDER BY generation ASC, rowid ASC", + )?; + let rows = statement.query_map([conversation_id], row_to_repair_attempt)?; + let mut attempts = Vec::new(); + for row in rows { + attempts.push(row?); + } + Ok(attempts) + }) + .await + } + async fn list_recoverable_repair_attempts( &self, ) -> AppResult> { From a40fa983b3783cc2254c8c8337ee41d3a5e81299 Mon Sep 17 00:00:00 2001 From: lazabogdan <6580668+lazabogdan@users.noreply.github.com> Date: Fri, 31 Jul 2026 17:13:56 +0300 Subject: [PATCH 06/29] feat(repair): detect base-caused PR failures before spending an agent MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit A PR fixer cannot fix a failure the PR did not cause. When the same checks already fail on the base branch, RalphX now proves that with one API call and hands the work off instead of running a full generation that can only report back what it found. - GithubServiceTrait gains list_branch_check_conclusions with a tri-state result: Some(checks) is a real answer, None means unknown. The gh CLI implementation reads the newest completed run per check on the branch tip; every other backend inherits None. - The poller compares a Checks-kind failure against the base tip before any dispatch. Detection requires every failing PR check to be proven failing on base — one PR-caused failure means the PR still owns work. - Detection records a publication event, remembers the fingerprint so later polls stay handed off, and raises an Inbox notification naming the base branch. Production polling routes through a notification-carrying variant so existing call sites keep their signature. Every ambiguity dispatches the agent as before: an unreadable base, an unimplemented backend, a check absent from base (scope-gated CI), and in-progress base runs. Being wrong here wastes one generation; being wrong the other way silently ignores a real PR failure. Deliberately not included: auto-creating a follow-up workspace on the base branch. The notification carries the evidence; creating that workspace stays a user decision. Validation: 125 poller tests including proven-on-base, absent-from-base, unreadable-base, and unknown-base paths; 89 gh CLI service tests. Co-Authored-By: Claude Opus 5 --- .../application/services/pr_merge_poller.rs | 201 +++++++++++++++++- .../services/pr_merge_poller_tests.rs | 183 ++++++++++++++++ .../src/domain/services/github_service.rs | 14 ++ .../services/gh_cli_github_service.rs | 73 +++++++ src-tauri/src/tests/mock_github_service.rs | 17 +- 5 files changed, 486 insertions(+), 2 deletions(-) diff --git a/src-tauri/src/application/services/pr_merge_poller.rs b/src-tauri/src/application/services/pr_merge_poller.rs index 79c5bc3b4f..09395f701a 100644 --- a/src-tauri/src/application/services/pr_merge_poller.rs +++ b/src-tauri/src/application/services/pr_merge_poller.rs @@ -36,6 +36,10 @@ use crate::application::agent_workspace_terminal_cleanup::{ settle_review_pr_terminal_observation, terminalize_agent_workspace_after_pr, TerminalAgentWorkspaceCause, }; +use crate::domain::entities::{ + NewNotification, NotificationCategory, NotificationSeverity, NotificationTarget, + NotificationTargetKind, +}; use crate::application::chat_service::{ChatService, SendMessageOptions, SendQueuePolicy}; use crate::application::interactive_notification_producer::pr_review_notification_key; use crate::application::services::pr_auto_merge_status::{ @@ -1263,7 +1267,7 @@ async fn agent_workspace_poll_loop( } } - match route_agent_workspace_pr_autofix_if_needed_with_repair_repo( + match route_agent_workspace_pr_autofix_if_needed_with_notifications( Arc::clone(&github), &working_dir, pr_number, @@ -1273,6 +1277,7 @@ async fn agent_workspace_poll_loop( repair_repo.as_ref().map(Arc::clone), branch_update_repo.as_ref().map(Arc::clone), Arc::clone(&chat_service), + notification_service.as_ref().map(Arc::clone), ) .await { @@ -2737,6 +2742,9 @@ async fn route_agent_workspace_pr_autofix_if_needed( .await } +/// Compatibility entry point without user notifications. Production polling uses +/// [`route_agent_workspace_pr_autofix_if_needed_with_notifications`] so hand-offs reach the Inbox. +#[allow(clippy::too_many_arguments)] async fn route_agent_workspace_pr_autofix_if_needed_with_repair_repo( github: Arc, working_dir: &Path, @@ -2747,6 +2755,34 @@ async fn route_agent_workspace_pr_autofix_if_needed_with_repair_repo( repair_repo: Option>, branch_update_repo: Option>, chat_service: Arc, +) -> crate::AppResult { + route_agent_workspace_pr_autofix_if_needed_with_notifications( + github, + working_dir, + pr_number, + conversation_id, + workspace_repo, + agent_run_repo, + repair_repo, + branch_update_repo, + chat_service, + None, + ) + .await +} + +#[allow(clippy::too_many_arguments)] +async fn route_agent_workspace_pr_autofix_if_needed_with_notifications( + github: Arc, + working_dir: &Path, + pr_number: i64, + conversation_id: &ChatConversationId, + workspace_repo: Arc, + agent_run_repo: Option>, + repair_repo: Option>, + branch_update_repo: Option>, + chat_service: Arc, + notification_service: Option>, ) -> crate::AppResult { let workspace = workspace_repo .get_by_conversation_id(conversation_id) @@ -2770,6 +2806,7 @@ async fn route_agent_workspace_pr_autofix_if_needed_with_repair_repo( repair_repo, branch_update_repo, chat_service, + notification_service, ) .await } @@ -2823,10 +2860,12 @@ pub(crate) async fn route_ideation_plan_pr_autofix_if_needed( None, None, chat_service, + None, ) .await } +#[allow(clippy::too_many_arguments)] async fn route_agent_workspace_pr_autofix_for_target( github: Arc, working_dir: &Path, @@ -2838,6 +2877,7 @@ async fn route_agent_workspace_pr_autofix_for_target( repair_repo: Option>, branch_update_repo: Option>, chat_service: Arc, + notification_service: Option>, ) -> crate::AppResult { let target_matches_workspace_mode = matches!( (workspace.mode, &target.kind), @@ -3061,6 +3101,22 @@ async fn route_agent_workspace_pr_autofix_for_target( { return Ok(false); } + // Checking the base costs one API call; sending an agent to "fix" a failure the PR did not + // cause costs a full generation and cannot succeed. + if issue.kind == AgentWorkspacePrAutofixIssueKind::Checks + && pr_failures_already_fail_on_base(github.as_ref(), working_dir, &health).await + { + record_pre_existing_on_base_detection( + workspace_repo.as_ref(), + conversation_id, + &workspace, + &issue.classification, + &health, + notification_service.as_ref(), + ) + .await?; + return Ok(false); + } if authorize_agent_workspace_pr_autofix(workspace_repo.as_ref(), conversation_id, &target) .await? .is_none() @@ -3156,6 +3212,149 @@ async fn route_agent_workspace_pr_autofix_for_target( /// The step recorded when a fresh PR autofix streak is suppressed by cross-streak memory. pub(crate) const CROSS_STREAK_FINGERPRINT_HOLD_STEP: &str = "repair_fingerprint_cross_streak_hold"; +/// The step recorded when RalphX proves a PR's failing check already fails on the base branch. +pub(crate) const PRE_EXISTING_ON_BASE_DETECTED_STEP: &str = "repair_pre_existing_on_base_detected"; + +/// Records a base-caused failure as a hand-off rather than a repair. +/// +/// Nothing here spends an agent. The publication event explains why supervision stopped, the +/// remembered fingerprint makes the cross-streak gate keep it stopped, and the Inbox notification +/// tells the user the fix belongs on the base branch — work this workspace cannot do. +async fn record_pre_existing_on_base_detection( + workspace_repo: &dyn AgentConversationWorkspaceRepository, + conversation_id: &ChatConversationId, + workspace: &AgentConversationWorkspace, + classification: &str, + health: &PrHealth, + notification_service: Option<&Arc>, +) -> crate::error::AppResult { + let base_ref = health.sync_state.base_ref_name.trim(); + let summary = format!( + "The failing checks on this PR already fail on {base_ref}. RalphX did not start a fixer \ + because the fix belongs on the base branch, not on this PR." + ); + + let already_recorded = workspace_repo + .list_publication_events(conversation_id) + .await? + .into_iter() + .any(|event| { + event.step == PRE_EXISTING_ON_BASE_DETECTED_STEP + && event.classification.as_deref() == Some(classification) + }); + if already_recorded { + return Ok(false); + } + + workspace_repo + .append_publication_event(AgentConversationWorkspacePublicationEvent::new( + conversation_id.clone(), + PRE_EXISTING_ON_BASE_DETECTED_STEP, + "blocked", + &summary, + Some(classification.to_string()), + )) + .await?; + workspace_repo + .set_last_blocked_pr_health_fingerprint(conversation_id, Some(classification)) + .await?; + + if let Some(notification_service) = notification_service { + notification_service + .record(NewNotification { + project_id: Some(workspace.project_id.to_string()), + category: NotificationCategory::TaskBlocked, + severity: NotificationSeverity::ActionRequired, + title: match workspace.publication_pr_number { + Some(pr_number) => format!("PR #{pr_number} is blocked by {base_ref}"), + None => format!("Workspace is blocked by {base_ref}"), + }, + body: Some(summary), + target: NotificationTarget { + kind: NotificationTargetKind::AgentConversation, + project_id: Some(workspace.project_id.to_string()), + task_id: None, + conversation_id: Some(conversation_id.to_string()), + setup_conversation_id: None, + automation_id: None, + run_id: None, + }, + dedupe_key: Some(format!( + "repair_pre_existing_on_base:{}:{classification}", + conversation_id.as_str() + )), + }) + .await; + } + + tracing::info!( + conversation_id = conversation_id.as_str(), + base_ref, + classification, + "PR failure already fails on base; handing off instead of dispatching a fixer" + ); + Ok(true) +} + +/// True only when GitHub proves the PR's failing checks already fail on the base branch tip. +/// +/// This authorizes skipping an agent entirely, so it is deliberately conservative in one +/// direction: every ambiguity answers "no". An unreadable base, an unimplemented backend, a check +/// absent from the base (the scope-gated-CI case), and an in-progress base run all fall through to +/// the normal dispatch. Being wrong here means a wasted agent generation; being wrong the other +/// way means silently ignoring a real PR failure. +async fn pr_failures_already_fail_on_base( + github: &dyn GithubServiceTrait, + working_dir: &Path, + health: &PrHealth, +) -> bool { + let failing_pr_checks = health + .checks + .iter() + .filter(|check| { + check.conclusion.as_deref().is_some_and(|value| { + matches!(value.to_ascii_uppercase().as_str(), "FAILURE" | "TIMED_OUT") + }) + }) + .collect::>(); + if failing_pr_checks.is_empty() { + return false; + } + + let base_ref = health.sync_state.base_ref_name.trim(); + if base_ref.is_empty() { + return false; + } + let base_checks = match github + .list_branch_check_conclusions(working_dir, base_ref) + .await + { + Ok(Some(checks)) => checks, + Ok(None) => return false, + Err(error) => { + tracing::warn!( + base_ref, + %error, + "Could not read base branch check conclusions; dispatching PR autofix as usual" + ); + return false; + } + }; + if base_checks.is_empty() { + return false; + } + + // Every failing check must be proven failing on base. One PR-caused failure means the PR does + // own work, even if another check is broken upstream. + failing_pr_checks.iter().all(|pr_check| { + base_checks.iter().any(|base_check| { + base_check.name.eq_ignore_ascii_case(pr_check.name.trim()) + && base_check.conclusion.as_deref().is_some_and(|value| { + matches!(value.to_ascii_uppercase().as_str(), "FAILURE" | "TIMED_OUT") + }) + }) + }) +} /// A repair attempt's fingerprint hold dies with its streak. Once a streak exhausts its retries, /// the next poll would otherwise start a brand new streak against the exact same failing check — diff --git a/src-tauri/src/application/services/pr_merge_poller_tests.rs b/src-tauri/src/application/services/pr_merge_poller_tests.rs index 9af2eaae3f..7c91e3df22 100644 --- a/src-tauri/src/application/services/pr_merge_poller_tests.rs +++ b/src-tauri/src/application/services/pr_merge_poller_tests.rs @@ -5915,6 +5915,189 @@ async fn live_pr_autofix_dispatches_new_generation_after_ci_rerun_fingerprint_ch assert_eq!(current.phase, AgentWorkspaceRepairPhase::Repairing); } +/// Builds a workspace whose PR is failing one named check, ready for base-comparison tests. +async fn seed_failing_check_workspace( + label: &str, + check_name: &str, +) -> ( + tempfile::TempDir, + Arc, + ChatConversationId, + PrHealth, +) { + let worktree = tempfile::tempdir().expect("worktree path"); + let mut workspace = supervised_workspace(label, &format!("project-{label}"), worktree.path()); + init_repair_dispatch_repo(worktree.path(), &workspace.branch_name); + workspace.base_commit = Some(format!("base-{label}")); + let conversation_id = workspace.conversation_id.clone(); + let workspace_repo = Arc::new(MemoryAgentConversationWorkspaceRepository::new()); + workspace_repo + .create_or_update(workspace) + .await + .expect("workspace should persist"); + + let mut health = open_pr_health(&format!("{label}-head")); + health.checks.push(PrHealthCheck { + name: check_name.to_string(), + status: Some("completed".to_string()), + conclusion: Some("failure".to_string()), + details_url: Some("https://github.com/owner/repo/actions/runs/940".to_string()), + }); + (worktree, workspace_repo, conversation_id, health) +} + +async fn route_with_base_conclusions( + worktree: &tempfile::TempDir, + workspace_repo: Arc, + conversation_id: &ChatConversationId, + health: PrHealth, + base_conclusions: AppResult>>, +) -> (bool, Arc) { + let repair_repo: Arc = workspace_repo.clone(); + let branch_update_repo: Arc = + Arc::new(MemoryBranchUpdateRepository::new()); + let agent_run_repo = seeded_latest_pr_fixer_run_repo(conversation_id).await; + let github = Arc::new(MockGithubService::new()); + github.state().fetch_pr_health_result = Some(Ok(health)); + github.state().list_branch_check_conclusions_result = Some(base_conclusions); + let chat = Arc::new(MockChatService::with_agent_run_repo(Arc::clone( + &agent_run_repo, + ))); + + let routed = super::route_agent_workspace_pr_autofix_if_needed_with_repair_repo( + Arc::clone(&github) as Arc, + worktree.path(), + 101, + conversation_id, + workspace_repo, + Some(agent_run_repo), + Some(repair_repo), + Some(branch_update_repo), + chat.clone() as Arc, + ) + .await + .expect("routing should complete"); + (routed, chat) +} + +/// A failure the PR did not cause cannot be fixed by a PR fixer. When GitHub proves the same check +/// already fails on the base branch, RalphX hands off instead of spending a generation. +#[tokio::test] +async fn failure_proven_on_base_is_handed_off_without_spawning_a_fixer() { + let (worktree, workspace_repo, conversation_id, health) = + seed_failing_check_workspace("pre-existing-detected", "Rust tests").await; + let classification = super::classify_agent_workspace_pr_autofix_issue(101, &health) + .expect("failed check should classify") + .classification; + + let (routed, chat) = route_with_base_conclusions( + &worktree, + workspace_repo.clone(), + &conversation_id, + health, + Ok(Some(vec![PrHealthCheck { + name: "Rust tests".to_string(), + status: Some("completed".to_string()), + conclusion: Some("failure".to_string()), + details_url: Some("https://github.com/owner/repo/actions/runs/1".to_string()), + }])), + ) + .await; + + assert!(!routed, "a base-caused failure must not spawn a fixer"); + assert!(chat.get_sent_messages().await.is_empty()); + let events = workspace_repo + .list_publication_events(&conversation_id) + .await + .expect("list publication events"); + assert!( + events + .iter() + .any(|event| event.step == super::PRE_EXISTING_ON_BASE_DETECTED_STEP), + "the hand-off must be visible on the publication timeline" + ); + let workspace = workspace_repo + .get_by_conversation_id(&conversation_id) + .await + .expect("reload workspace") + .expect("workspace exists"); + assert_eq!( + workspace.last_blocked_pr_health_fingerprint.as_deref(), + Some(classification.as_str()), + "the identity must be remembered so later polls stay handed off" + ); +} + +/// The scope-gated-CI case: a check that never runs on the base proves nothing, so the agent runs. +#[tokio::test] +async fn failure_absent_from_base_still_dispatches_a_fixer() { + let (worktree, workspace_repo, conversation_id, health) = + seed_failing_check_workspace("pre-existing-absent", "Rust tests").await; + + let (routed, chat) = route_with_base_conclusions( + &worktree, + workspace_repo.clone(), + &conversation_id, + health, + Ok(Some(vec![PrHealthCheck { + name: "Frontend tests".to_string(), + status: Some("completed".to_string()), + conclusion: Some("success".to_string()), + details_url: None, + }])), + ) + .await; + + assert!(routed, "a check absent from base proves nothing about base"); + assert_eq!(chat.get_sent_messages().await.len(), 1); + assert!(!workspace_repo + .list_publication_events(&conversation_id) + .await + .expect("list publication events") + .iter() + .any(|event| event.step == super::PRE_EXISTING_ON_BASE_DETECTED_STEP)); +} + +/// An unreadable base must fail open to the agent. Skipping repair on an API error would silently +/// ignore real PR failures. +#[tokio::test] +async fn unreadable_base_conclusions_still_dispatch_a_fixer() { + let (worktree, workspace_repo, conversation_id, health) = + seed_failing_check_workspace("pre-existing-error", "Rust tests").await; + + let (routed, chat) = route_with_base_conclusions( + &worktree, + workspace_repo.clone(), + &conversation_id, + health, + Err(crate::error::AppError::Infrastructure( + "gh run list failed".to_string(), + )), + ) + .await; + + assert!(routed, "an unreadable base must never suppress repair"); + assert_eq!(chat.get_sent_messages().await.len(), 1); +} + +/// An unimplemented backend reports "unknown", which must behave exactly like an error. +#[tokio::test] +async fn unknown_base_conclusions_still_dispatch_a_fixer() { + let (worktree, workspace_repo, conversation_id, health) = + seed_failing_check_workspace("pre-existing-unknown", "Rust tests").await; + + let (routed, _chat) = route_with_base_conclusions( + &worktree, + workspace_repo.clone(), + &conversation_id, + health, + Ok(None), + ) + .await; + + assert!(routed, "unknown base state must not be read as healthy"); +} + /// Cross-streak memory: an exhausted streak leaves its failure identity on the workspace, and the /// next poll must recognise it instead of starting a fresh streak on identical evidence. #[tokio::test] diff --git a/src-tauri/src/domain/services/github_service.rs b/src-tauri/src/domain/services/github_service.rs index c7381b8d80..2791a9548f 100644 --- a/src-tauri/src/domain/services/github_service.rs +++ b/src-tauri/src/domain/services/github_service.rs @@ -570,6 +570,20 @@ pub trait GithubServiceTrait: Send + Sync { /// Re-run only failed jobs for an authoritative GitHub Actions workflow run. /// The run id must be derived from fresh PR health by the caller, never model input. + /// Completed check conclusions on the tip of `branch_ref`, used to tell "this PR broke it" + /// from "this was already broken on the base branch". + /// + /// `None` means RalphX could not determine the base branch's check state — an unimplemented + /// backend, an API error, or no run to read. Callers must treat `None` as unknown and fall + /// back to their normal behavior; it must never be read as "the base is healthy". + async fn list_branch_check_conclusions( + &self, + _working_dir: &Path, + _branch_ref: &str, + ) -> AppResult>> { + Ok(None) + } + async fn rerun_failed_workflow(&self, _working_dir: &Path, _run_id: i64) -> AppResult<()> { Err(AppError::Infrastructure( "GitHub Actions rerun is unavailable for this runtime".to_string(), diff --git a/src-tauri/src/infrastructure/services/gh_cli_github_service.rs b/src-tauri/src/infrastructure/services/gh_cli_github_service.rs index 393b69c0d4..3df7cb65b0 100644 --- a/src-tauri/src/infrastructure/services/gh_cli_github_service.rs +++ b/src-tauri/src/infrastructure/services/gh_cli_github_service.rs @@ -994,6 +994,38 @@ impl GithubServiceTrait for GhCliGithubService { parse_pr_health_output(&view_stdout.join("\n"), &comments_stdout.join("\n")) } + async fn list_branch_check_conclusions( + &self, + working_dir: &Path, + branch_ref: &str, + ) -> AppResult>> { + let branch_ref = branch_ref.trim(); + if branch_ref.is_empty() { + return Ok(None); + } + // The latest completed workflow runs on the branch tip. `gh run list` is the only surface + // that reports checks for a branch with no pull request of its own, which is exactly the + // base-branch case this exists for. + let stdout = self + .runner + .run_gh( + working_dir, + &[ + "run".to_string(), + "list".to_string(), + "--branch".to_string(), + branch_ref.to_string(), + "--limit".to_string(), + "40".to_string(), + "--json".to_string(), + "name,workflowName,status,conclusion,url,headSha".to_string(), + ], + ) + .await?; + + Ok(Some(parse_branch_check_conclusions(&stdout.join("\n")))) + } + async fn rerun_failed_workflow(&self, working_dir: &Path, run_id: i64) -> AppResult<()> { if run_id <= 0 { return Err(AppError::Validation( @@ -1800,6 +1832,47 @@ fn parse_status_check_rollup(view_value: &Value) -> Vec { .collect() } +/// Reduces `gh run list --json ...` output to the newest completed conclusion per check name. +/// Older runs for the same check are historical noise; only the current state of the branch tip +/// can tell us whether a failure already exists on the base. +fn parse_branch_check_conclusions(json_str: &str) -> Vec { + let Ok(runs) = serde_json::from_str::(json_str) else { + return Vec::new(); + }; + let mut newest_by_name: std::collections::BTreeMap = + std::collections::BTreeMap::new(); + for run in runs.as_array().into_iter().flatten() { + let name = run + .get("name") + .or_else(|| run.get("workflowName")) + .and_then(Value::as_str) + .unwrap_or_default() + .trim() + .to_string(); + if name.is_empty() { + continue; + } + let status = run.get("status").and_then(Value::as_str).map(str::to_string); + // An in-progress run proves nothing about the base yet. + if !status + .as_deref() + .is_some_and(|value| value.eq_ignore_ascii_case("completed")) + { + continue; + } + newest_by_name.entry(name.clone()).or_insert(PrHealthCheck { + name, + status, + conclusion: run + .get("conclusion") + .and_then(Value::as_str) + .map(str::to_string), + details_url: run.get("url").and_then(Value::as_str).map(str::to_string), + }); + } + newest_by_name.into_values().collect() +} + fn parse_auto_merge_request(view_value: &Value) -> Option { let request = view_value.get("autoMergeRequest")?; if request.is_null() { diff --git a/src-tauri/src/tests/mock_github_service.rs b/src-tauri/src/tests/mock_github_service.rs index e96d570d76..328f66dfa1 100644 --- a/src-tauri/src/tests/mock_github_service.rs +++ b/src-tauri/src/tests/mock_github_service.rs @@ -10,7 +10,7 @@ use std::sync::{Arc, Mutex}; use crate::domain::services::github_service::{ validate_pr_metadata_patch, GithubConnectionStatus, GithubServiceTrait, PrBranchMatch, - PrDetail, PrDiffAnnotations, PrHealth, PrReviewFeedback, PrReviewSubmissionEvent, + PrDetail, PrDiffAnnotations, PrHealth, PrHealthCheck, PrReviewFeedback, PrReviewSubmissionEvent, PrReviewThread, PrSearchResult, PrStatus, PrSubmittedReview, PrSyncState, }; use crate::error::AppError; @@ -38,6 +38,8 @@ pub struct MockGithubState { pub fetch_pr_review_thread_result: Option>, pub fetch_github_connection_status_result: Option>, pub fetch_pr_health_result: Option>, + /// `None` leaves the trait default (unknown base state); `Some` overrides it. + pub list_branch_check_conclusions_result: Option>>>, pub rerun_failed_workflow_result: Option>, pub enable_pr_auto_merge_result: Option>, pub enable_pr_auto_merge_delay_ms: u64, @@ -553,6 +555,19 @@ impl GithubServiceTrait for MockGithubService { }) } + async fn list_branch_check_conclusions( + &self, + _working_dir: &Path, + _branch_ref: &str, + ) -> AppResult>> { + self.state + .lock() + .expect("lock poisoned") + .list_branch_check_conclusions_result + .take() + .unwrap_or(Ok(None)) + } + async fn rerun_failed_workflow(&self, _working_dir: &Path, run_id: i64) -> AppResult<()> { let mut s = self.state.lock().expect("lock poisoned"); s.rerun_failed_workflow_calls += 1; From 2d71915a4b60d685d4db77b5e77adf5fbe7df275 Mon Sep 17 00:00:00 2001 From: lazabogdan <6580668+lazabogdan@users.noreply.github.com> Date: Fri, 31 Jul 2026 18:23:56 +0300 Subject: [PATCH 07/29] feat(agents): show repair spend on the current failure fingerprint MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Completes the plan's user-visible surface and hardens two edges found in the final review pass. Publish surface (delegated slice, reviewed and adjusted): - AgentConversationWorkspaceResponse gains pr_autofix_fingerprint_spend (snake_case; the response type has no rename_all) with generations, minutes, budget_minutes, and is_exhausted. It reuses pr_autofix_fingerprint_spend rather than adding a parallel accounting path, and is populated only when the workspace has a remembered failure identity, so the normal case costs nothing. - AgentsPublishWorkspaceDialog renders "3 generations · 92 min on this failure" and calls out an exhausted budget. Data arrives on the existing polling payload; no new work on any click path. Review fixes: - The spend query now degrades to None on failure instead of failing the whole workspace response. A purely informational field must not break the surface the Agents UI depends on. - The budget notification dedupe key now uses the fingerprint the exhausted generation actually carried, captured before the attempt is consumed. It previously read a stale workspace snapshot and collapsed to "unknown", which would have deduped unrelated future exhaustions into one notification. - Corrected the cross-streak gate's doc comment, which described the opposite of its real error behaviour. Validation: 67 recovery, 125 poller, 37 repair-state, 3 contract lib tests; 46 suite_agent_workspace, 37 suite_http_handlers agent-workspace, 105 suite_ipc_commands unified_chat_commands; 481 MCP tests; frontend typecheck and 212 targeted Vitest tests. Touched Rust leaves formatted. Pre-existing and unrelated: the two standalone_conversations flag tests in commands::unified_chat_commands race on a shared global flag — each passes alone, they fail when run together. Not touched by this work. Co-Authored-By: Claude Opus 5 --- frontend/src/api/chat.test.ts | 12 ++++++ frontend/src/api/chat.ts | 27 +++++++++++++ .../components/agents/AgentsPublishPanel.tsx | 3 ++ .../AgentsPublishWorkspaceDialog.test.tsx | 23 +++++++++++ .../agents/AgentsPublishWorkspaceDialog.tsx | 26 +++++++++++++ .../agents/agentWorkspacePublishState.test.ts | 25 ++++++++++++ .../agents/agentWorkspacePublishState.ts | 18 +++++++++ .../agents/useAgentsWorkspaceModel.test.tsx | 32 +++++++++++++++ .../agents/useAgentsWorkspaceModel.ts | 4 ++ .../agent_workspace_publish_recovery.rs | 9 +++-- .../durable_attempt_recovery.rs | 30 +++++++------- .../pr_autofix_redelivery.rs | 8 ++-- .../application/services/pr_merge_poller.rs | 5 ++- .../src/commands/unified_chat_commands/mod.rs | 39 ++++++++++++++++++- .../commands/unified_chat_commands/tests.rs | 17 +++++++- 15 files changed, 250 insertions(+), 28 deletions(-) diff --git a/frontend/src/api/chat.test.ts b/frontend/src/api/chat.test.ts index 394a5f745f..b944555275 100644 --- a/frontend/src/api/chat.test.ts +++ b/frontend/src/api/chat.test.ts @@ -1625,6 +1625,12 @@ describe("chat api", () => { publication_metadata_attempt_id: "attempt-plan-1", publication_metadata_phase: "reconciling", publication_metadata_state: "unknown", + pr_autofix_fingerprint_spend: { + generations: 3, + minutes: 92, + budget_minutes: 45, + is_exhausted: true, + }, auto_publish_enabled: true, auto_publish_paused_pr_autofix_enabled: null, auto_publish_paused_pr_auto_merge_desired: null, @@ -1652,6 +1658,12 @@ describe("chat api", () => { status: "active", automaticContinuation: true, }, + prAutofixFingerprintSpend: { + generations: 3, + minutes: 92, + budgetMinutes: 45, + isExhausted: true, + }, }); }); diff --git a/frontend/src/api/chat.ts b/frontend/src/api/chat.ts index ff99195423..7834b3f8de 100644 --- a/frontend/src/api/chat.ts +++ b/frontend/src/api/chat.ts @@ -2101,6 +2101,13 @@ export interface AgentWorkspaceMaintenanceOperation { updatedAt: string; } +export interface AgentWorkspacePrAutofixFingerprintSpend { + generations: number; + minutes: number; + budgetMinutes: number; + isExhausted: boolean; +} + export interface AgentConversationWorkspace { conversationId: string; projectId: string; @@ -2124,6 +2131,7 @@ export interface AgentConversationWorkspace { publicationPrStatus: string | null; publicationPushStatus: string | null; maintenanceOperation?: AgentWorkspaceMaintenanceOperation | null; + prAutofixFingerprintSpend?: AgentWorkspacePrAutofixFingerprintSpend | null; publicationMetadataAttemptId: string | null; publicationMetadataPhase: AgentWorkspacePublicationMetadataPhase | null; publicationMetadataState: AgentWorkspacePublicationMetadataState | null; @@ -2671,6 +2679,13 @@ export const AgentWorkspaceMaintenanceOperationResponseSchema = z.object({ updated_at: z.string(), }); +export const AgentWorkspacePrAutofixFingerprintSpendResponseSchema = z.object({ + generations: z.number().int().nonnegative(), + minutes: z.number().int().nonnegative(), + budget_minutes: z.number().int().nonnegative(), + is_exhausted: z.boolean(), +}); + export const AgentConversationWorkspaceResponseSchema = z.object({ conversation_id: z.string(), project_id: z.string(), @@ -2699,6 +2714,10 @@ export const AgentConversationWorkspaceResponseSchema = z.object({ maintenance_operation: AgentWorkspaceMaintenanceOperationResponseSchema.nullable() .optional() .default(null), + pr_autofix_fingerprint_spend: + AgentWorkspacePrAutofixFingerprintSpendResponseSchema.nullable() + .optional() + .default(null), publication_metadata_attempt_id: z.string().nullable().optional().default(null), publication_metadata_phase: z .enum(["prepared", "mutating", "reconciling", "settled"]) @@ -3306,6 +3325,14 @@ function transformAgentConversationWorkspace( updatedAt: raw.maintenance_operation.updated_at, } : null, + prAutofixFingerprintSpend: raw.pr_autofix_fingerprint_spend + ? { + generations: raw.pr_autofix_fingerprint_spend.generations, + minutes: raw.pr_autofix_fingerprint_spend.minutes, + budgetMinutes: raw.pr_autofix_fingerprint_spend.budget_minutes, + isExhausted: raw.pr_autofix_fingerprint_spend.is_exhausted, + } + : null, publicationMetadataAttemptId: raw.publication_metadata_attempt_id, publicationMetadataPhase: raw.publication_metadata_phase, publicationMetadataState: raw.publication_metadata_state, diff --git a/frontend/src/components/agents/AgentsPublishPanel.tsx b/frontend/src/components/agents/AgentsPublishPanel.tsx index a4f6303cdc..652a6845eb 100644 --- a/frontend/src/components/agents/AgentsPublishPanel.tsx +++ b/frontend/src/components/agents/AgentsPublishPanel.tsx @@ -107,6 +107,7 @@ import { getAgentWorkspaceTerminalPublicationStatus, getAgentWorkspaceEffectiveBaseLabel, getAgentWorkspaceMaintenancePresentation, + getAgentWorkspacePrAutofixFingerprintSpendPresentation, hasPublishedWorkspacePr, isAgentWorkspacePublishActive, isAgentWorkspaceMaintenanceActive, @@ -344,6 +345,7 @@ export function AgentPublishPanel({ ); }, [conversationId]); const maintenancePresentation = getAgentWorkspaceMaintenancePresentation(workspace); + const fingerprintSpend = getAgentWorkspacePrAutofixFingerprintSpendPresentation(workspace); const isMaintenanceActive = isAgentWorkspaceMaintenanceActive(workspace); const blocksGitInspection = blocksAgentWorkspaceGitInspection(workspace); const isPublishingWorkspace = @@ -2128,6 +2130,7 @@ export function AgentPublishPanel({ branch={branch} base={base} targetPullRequestLabel={publishTargetPullRequestLabel} + fingerprintSpend={fingerprintSpend} prSupervisionStatus={prSupervisionStatus} status={pipelineStatus} isPublishing={isPublishingThisWorkspace} diff --git a/frontend/src/components/agents/AgentsPublishWorkspaceDialog.test.tsx b/frontend/src/components/agents/AgentsPublishWorkspaceDialog.test.tsx index 3235f7c45b..b01d6ccdac 100644 --- a/frontend/src/components/agents/AgentsPublishWorkspaceDialog.test.tsx +++ b/frontend/src/components/agents/AgentsPublishWorkspaceDialog.test.tsx @@ -70,4 +70,27 @@ describe("PublishWorkspaceDialog", () => { ).toBeInTheDocument(); expect(screen.queryByText(/no pull request was opened/)).not.toBeInTheDocument(); }); + + it("shows exhausted failure-fingerprint effort before another publish attempt", () => { + render( + , + ); + + expect(screen.getByText("3 generations · 92 min on this failure")).toBeInTheDocument(); + expect(screen.getByText("Repair budget exhausted")).toBeInTheDocument(); + }); }); diff --git a/frontend/src/components/agents/AgentsPublishWorkspaceDialog.tsx b/frontend/src/components/agents/AgentsPublishWorkspaceDialog.tsx index 7304448a7e..c4ac5b99b0 100644 --- a/frontend/src/components/agents/AgentsPublishWorkspaceDialog.tsx +++ b/frontend/src/components/agents/AgentsPublishWorkspaceDialog.tsx @@ -11,6 +11,7 @@ import { } from "@/components/ui/dialog"; import { PublishPipelineSteps } from "./AgentsPublishPipelineSteps"; +import type { AgentWorkspacePrAutofixFingerprintSpendPresentation } from "./agentWorkspacePublishState"; export type PublishWorkspaceDialogPhase = "confirm" | "publishing"; @@ -20,6 +21,7 @@ export function PublishWorkspaceDialog({ base, branch, confirmDisabled, + fingerprintSpend = null, isPublishing, onConfirm, onOpenChange, @@ -34,6 +36,7 @@ export function PublishWorkspaceDialog({ base: string; branch: string; confirmDisabled: boolean; + fingerprintSpend?: AgentWorkspacePrAutofixFingerprintSpendPresentation | null; isPublishing: boolean; onConfirm: () => void; onOpenChange: (open: boolean) => void; @@ -81,6 +84,29 @@ export function PublishWorkspaceDialog({ )} + {fingerprintSpend && ( +
+
{fingerprintSpend.summary}
+ {fingerprintSpend.exhausted && ( +
+ Repair budget exhausted +
+ )} +
+ )} + {isProgress && (
{ }); }); +describe("PR autofix fingerprint spend presentation", () => { + it("reports effort for the current failure and marks an exhausted budget", () => { + expect( + getAgentWorkspacePrAutofixFingerprintSpendPresentation( + workspace({ + prAutofixFingerprintSpend: { + generations: 3, + minutes: 92, + budgetMinutes: 45, + isExhausted: true, + }, + }), + ), + ).toEqual({ + summary: "3 generations · 92 min on this failure", + exhausted: true, + }); + }); + + it("omits the indicator when there is no tracked failure fingerprint", () => { + expect(getAgentWorkspacePrAutofixFingerprintSpendPresentation(workspace())).toBeNull(); + }); +}); + function publicationEvent( overrides: Partial = {}, ): AgentConversationWorkspacePublicationEvent { diff --git a/frontend/src/components/agents/agentWorkspacePublishState.ts b/frontend/src/components/agents/agentWorkspacePublishState.ts index 331233300f..8ba3e14e6b 100644 --- a/frontend/src/components/agents/agentWorkspacePublishState.ts +++ b/frontend/src/components/agents/agentWorkspacePublishState.ts @@ -149,6 +149,24 @@ export type AgentWorkspaceMaintenancePresentation = { tone: "error" | "neutral" | "warning"; }; +export type AgentWorkspacePrAutofixFingerprintSpendPresentation = { + summary: string; + exhausted: boolean; +}; + +export function getAgentWorkspacePrAutofixFingerprintSpendPresentation( + workspace: AgentConversationWorkspace | null | undefined, +): AgentWorkspacePrAutofixFingerprintSpendPresentation | null { + const spend = workspace?.prAutofixFingerprintSpend; + if (!spend) { + return null; + } + return { + summary: `${spend.generations} generations · ${spend.minutes} min on this failure`, + exhausted: spend.isExhausted, + }; +} + export function getAgentWorkspaceMaintenanceOperation( workspace: AgentConversationWorkspace | null | undefined, ): AgentWorkspaceMaintenanceOperation | null { diff --git a/frontend/src/components/agents/useAgentsWorkspaceModel.test.tsx b/frontend/src/components/agents/useAgentsWorkspaceModel.test.tsx index e8de298921..f9d9067e14 100644 --- a/frontend/src/components/agents/useAgentsWorkspaceModel.test.tsx +++ b/frontend/src/components/agents/useAgentsWorkspaceModel.test.tsx @@ -330,4 +330,36 @@ describe("useAgentsWorkspaceModel", () => { expect(persistedResult.current.publishShortcutLabel).toBe("Commit & Publish"), ); }); + + it("exposes the current failure fingerprint spend from the polled workspace", async () => { + getAgentConversationWorkspaceMock.mockResolvedValue( + conversationWorkspaceFixture({ + prAutofixFingerprintSpend: { + generations: 3, + minutes: 92, + budgetMinutes: 45, + isExhausted: true, + }, + }), + ); + const { result } = renderHook( + () => + useAgentsWorkspaceModel({ + activeConversation: projectConversation(), + modelRegistry: AGENT_MODEL_CATALOG, + optimisticWorkspacesByConversationId: {}, + runtimeByConversationId: {}, + selectedConversationId: "conversation-1", + workspaceReviewerRuntime: null, + }), + { wrapper: wrapper() }, + ); + + await waitFor(() => + expect(result.current.activeWorkspaceFingerprintSpend).toEqual({ + summary: "3 generations · 92 min on this failure", + exhausted: true, + }), + ); + }); }); diff --git a/frontend/src/components/agents/useAgentsWorkspaceModel.ts b/frontend/src/components/agents/useAgentsWorkspaceModel.ts index d61402b01f..91eb284f13 100644 --- a/frontend/src/components/agents/useAgentsWorkspaceModel.ts +++ b/frontend/src/components/agents/useAgentsWorkspaceModel.ts @@ -22,6 +22,7 @@ import { } from "./agentConversationRuntime"; import { getAgentWorkspaceEffectiveBaseLabel, + getAgentWorkspacePrAutofixFingerprintSpendPresentation, getAgentWorkspaceTerminalPublicationLabel, hasPublishedWorkspacePr, isAgentWorkspacePublishCurrent, @@ -104,6 +105,8 @@ export function useAgentsWorkspaceModel({ ); const terminalPublicationLabel = getAgentWorkspaceTerminalPublicationLabel(activeWorkspace); + const activeWorkspaceFingerprintSpend = + getAgentWorkspacePrAutofixFingerprintSpendPresentation(activeWorkspace); const workspacePublishMode = getProjectWorkspacePublishMode( activeProject, hasPublishedWorkspacePr(activeWorkspace), @@ -160,6 +163,7 @@ export function useAgentsWorkspaceModel({ activeConversationModeLocked, activeWorkspace, activeWorkspaceFreshness, + activeWorkspaceFingerprintSpend, focusedWorkspaceReviewServiceTier, normalizedActiveRuntime, publishShortcutLabel, diff --git a/src-tauri/src/application/agent_workspace_publish_recovery.rs b/src-tauri/src/application/agent_workspace_publish_recovery.rs index ca58f49ccc..d78ab5d513 100644 --- a/src-tauri/src/application/agent_workspace_publish_recovery.rs +++ b/src-tauri/src/application/agent_workspace_publish_recovery.rs @@ -45,20 +45,21 @@ pub const STALE_TRANSIENT_STATUS_STALE_SECS: u64 = 300; mod durable_attempt_recovery; mod pr_autofix_redelivery; +#[cfg(test)] +pub(crate) use durable_attempt_recovery::due_repair_dispatch_message; pub(crate) use durable_attempt_recovery::is_blocked_and_not_auto_retryable; #[cfg(any(test, feature = "test-utils"))] pub use durable_attempt_recovery::recover_agent_workspace_repair_after_terminal_run; #[cfg(not(any(test, feature = "test-utils")))] pub(crate) use durable_attempt_recovery::recover_agent_workspace_repair_after_terminal_run; pub(crate) use durable_attempt_recovery::recover_agent_workspace_repair_attempts_for_state; -#[cfg(test)] -pub(crate) use durable_attempt_recovery::due_repair_dispatch_message; -#[cfg(test)] -pub(crate) use pr_autofix_redelivery::due_pr_autofix_redispatch_message; use durable_attempt_recovery::recover_stale_publish_repair_for_workspace_in_state_result; pub(crate) use durable_attempt_recovery::{ AUTO_RETRY_BLOCKED_REPAIR_REASON_PREFIX, AUTO_RETRY_READY_REPAIR_REASON_PREFIX, }; +#[cfg(test)] +pub(crate) use pr_autofix_redelivery::due_pr_autofix_redispatch_message; +pub(crate) use pr_autofix_redelivery::pr_autofix_fingerprint_spend; #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub(crate) enum StalePublishRepairRecoveryOutcome { diff --git a/src-tauri/src/application/agent_workspace_publish_recovery/durable_attempt_recovery.rs b/src-tauri/src/application/agent_workspace_publish_recovery/durable_attempt_recovery.rs index a3dbe8d795..b8321a45ee 100644 --- a/src-tauri/src/application/agent_workspace_publish_recovery/durable_attempt_recovery.rs +++ b/src-tauri/src/application/agent_workspace_publish_recovery/durable_attempt_recovery.rs @@ -3,9 +3,8 @@ use std::sync::Arc; use chrono::{Duration, Utc}; use super::pr_autofix_redelivery::{ - due_pr_autofix_redispatch_message, evaluate_pr_autofix_successor, - pr_autofix_fingerprint_spend, remember_blocked_pr_autofix_fingerprint, - PrAutofixFingerprintSpend, PrAutofixSuccessorDecision, + due_pr_autofix_redispatch_message, evaluate_pr_autofix_successor, pr_autofix_fingerprint_spend, + remember_blocked_pr_autofix_fingerprint, PrAutofixFingerprintSpend, PrAutofixSuccessorDecision, }; use super::StalePublishRepairRecoveryOutcome; use crate::application::agent_conversation_workspace::resolve_effective_agent_conversation_workspace_path; @@ -13,9 +12,8 @@ use crate::application::agent_workspace_pr_autofix_attempt::load_latest_exact_pr use crate::application::agent_workspace_publish_repair_state::{ agent_workspace_repair_dispatch_is_due, agent_workspace_repair_is_health_held, block_agent_workspace_repair_completion, block_agent_workspace_repair_needs_human, - classify_agent_workspace_repair_delivery, - continue_agent_workspace_repair_at_boundary, inspect_agent_workspace_repair_completion, - record_agent_workspace_repair_validation, + classify_agent_workspace_repair_delivery, continue_agent_workspace_repair_at_boundary, + inspect_agent_workspace_repair_completion, record_agent_workspace_repair_validation, release_and_clear_agent_workspace_repair_target_lease, reserve_agent_workspace_repair_completion_validation, reserve_agent_workspace_repair_dispatch, reserve_agent_workspace_unchanged_health_hold, resume_current_agent_workspace_repair_publish, @@ -28,16 +26,16 @@ use crate::application::agent_workspace_publish_repair_state::{ }; use crate::application::chat_service::{ChatService, SendMessageOptions, SendQueuePolicy}; use crate::application::{AppState, GitService}; -use crate::domain::entities::{ - NewNotification, NotificationCategory, NotificationSeverity, NotificationTarget, - NotificationTargetKind, -}; use crate::domain::entities::{ AgentConversationWorkspace, AgentConversationWorkspacePublicationEvent, AgentRunId, AgentRunStatus, AgentWorkspaceRepairAttempt, AgentWorkspaceRepairAttemptId, AgentWorkspaceRepairContinuation, AgentWorkspaceRepairPhase, AgentWorkspaceRepairSource, ChatContextType, GitTargetLeaseOwner, }; +use crate::domain::entities::{ + NewNotification, NotificationCategory, NotificationSeverity, NotificationTarget, + NotificationTargetKind, +}; use crate::domain::repositories::{ AgentRunRepository, AgentWorkspaceRepairAttemptTransition, AgentWorkspaceRepairAttemptTransitionOutcome, AgentWorkspaceRepairCompatibilityProjection, @@ -661,6 +659,9 @@ async fn park_exhausted_pr_autofix_budget( spend: PrAutofixFingerprintSpend, ) -> AppResult { let conversation_id = current.conversation_id.clone(); + // Captured before `current` is consumed below. The dedupe key must be the identity this + // generation actually exhausted, so a later, different failure still reaches the user. + let fingerprint = current.pr_autofix_health_fingerprint.clone(); let summary = format!( "RalphX has spent {} minutes across {} repair generations on this same PR failure without \ resolving it, which is the configured limit. Automatic repair has stopped so it does not \ @@ -712,10 +713,7 @@ async fn park_exhausted_pr_autofix_budget( dedupe_key: Some(format!( "repair_budget:{}:{}", conversation_id.as_str(), - workspace - .last_blocked_pr_health_fingerprint - .as_deref() - .unwrap_or("unknown") + fingerprint.as_deref().unwrap_or("unknown") )), }) .await; @@ -831,9 +829,7 @@ pub(super) const DEFAULT_REPAIR_DISPATCH_CONTEXT: &str = /// `pending_reasons` carries internal scheduling markers (`auto_retry_blocked_repair:2`) alongside /// human-authored context. Only the latter belongs in an agent assignment; a marker rendered as /// "Context:" tells the recipient nothing about what actually needs repairing. -pub(super) fn human_repair_dispatch_context( - attempt: &AgentWorkspaceRepairAttempt, -) -> Option<&str> { +pub(super) fn human_repair_dispatch_context(attempt: &AgentWorkspaceRepairAttempt) -> Option<&str> { attempt .pending_reasons .iter() diff --git a/src-tauri/src/application/agent_workspace_publish_recovery/pr_autofix_redelivery.rs b/src-tauri/src/application/agent_workspace_publish_recovery/pr_autofix_redelivery.rs index 028159a296..d80baf456e 100644 --- a/src-tauri/src/application/agent_workspace_publish_recovery/pr_autofix_redelivery.rs +++ b/src-tauri/src/application/agent_workspace_publish_recovery/pr_autofix_redelivery.rs @@ -40,8 +40,8 @@ pub(crate) async fn pr_autofix_fingerprint_spend( conversation_id: &crate::domain::entities::ChatConversationId, fingerprint: &str, ) -> crate::error::AppResult { - let budget_minutes = crate::infrastructure::agents::limits_config() - .repair_fingerprint_budget_minutes; + let budget_minutes = + crate::infrastructure::agents::limits_config().repair_fingerprint_budget_minutes; let attempts = state .agent_workspace_repair_repo .list_repair_attempts_for_conversation(conversation_id) @@ -267,7 +267,9 @@ pub(crate) fn due_pr_autofix_redispatch_message( out.push_str(&format!("Pull request: {pr_url}\n")); } if let Some(fingerprint) = attempt.pr_autofix_health_fingerprint.as_deref() { - out.push_str(&format!("Last observed failure fingerprint: {fingerprint}\n")); + out.push_str(&format!( + "Last observed failure fingerprint: {fingerprint}\n" + )); } out.push_str(&format!( "Context: {}\n", diff --git a/src-tauri/src/application/services/pr_merge_poller.rs b/src-tauri/src/application/services/pr_merge_poller.rs index 09395f701a..2aaa1d4e6e 100644 --- a/src-tauri/src/application/services/pr_merge_poller.rs +++ b/src-tauri/src/application/services/pr_merge_poller.rs @@ -3361,8 +3361,9 @@ async fn pr_failures_already_fail_on_base( /// the outer loop that turned one unchanged failure into four Opus generations on 2026-07-31. /// /// Returns `true` when this dispatch must be suppressed. Different health clears the memory so a -/// genuinely new failure is never held by a stale one. A read failure never suppresses: a broken -/// query must not silently disable PR autofix. +/// genuinely new failure is never held by a stale one. Repository errors propagate rather than +/// resolving to "not suppressed": an unreadable workspace cannot authorize spending an agent, and +/// the poll loop surfaces the failure instead of quietly starting another generation. async fn cross_streak_fingerprint_suppresses_dispatch( workspace_repo: &dyn AgentConversationWorkspaceRepository, conversation_id: &ChatConversationId, diff --git a/src-tauri/src/commands/unified_chat_commands/mod.rs b/src-tauri/src/commands/unified_chat_commands/mod.rs index ab7ed0e265..e84e6128cd 100644 --- a/src-tauri/src/commands/unified_chat_commands/mod.rs +++ b/src-tauri/src/commands/unified_chat_commands/mod.rs @@ -85,7 +85,9 @@ use crate::application::agent_workspace_pr_supervision_recovery::{ schedule_agent_workspace_pr_supervision_recovery_with_lazy_deps, AgentWorkspacePrFixReviewPublishResumer, AgentWorkspacePrSupervisionRecoveryTrigger, }; -use crate::application::agent_workspace_publish_recovery::recover_stale_publish_repair_for_workspace_in_state; +use crate::application::agent_workspace_publish_recovery::{ + pr_autofix_fingerprint_spend, recover_stale_publish_repair_for_workspace_in_state, +}; use crate::application::agent_workspace_publish_repair_state::{ classify_agent_workspace_repair_delivery, reserve_agent_workspace_repair_dispatch, resume_current_agent_workspace_repair_publish, resume_ready_agent_workspace_repair_for_publish, @@ -444,6 +446,15 @@ pub struct AgentConversationWorkspaceResponse { pub mode_switch_lock_reason: Option, pub maintenance_operation: Option, + pub pr_autofix_fingerprint_spend: Option, +} + +#[derive(Debug, Clone, PartialEq, Eq, Serialize)] +pub struct PrAutofixFingerprintSpendResponse { + pub generations: u32, + pub minutes: u64, + pub budget_minutes: u64, + pub is_exhausted: bool, } #[derive(Debug, Deserialize)] @@ -536,6 +547,7 @@ impl From for AgentConversationWorkspaceResponse { mode_switch_locked: false, mode_switch_lock_reason: None, maintenance_operation: None, + pr_autofix_fingerprint_spend: None, } } } @@ -1091,6 +1103,8 @@ async fn agent_workspace_response_without_repair_recovery_for_state( ) -> Result { let mode_lock = resolve_agent_conversation_workspace_mode_lock(state, &workspace).await?; let linked_plan_branch_id = workspace.linked_plan_branch_id.clone(); + let pr_autofix_fingerprint = workspace.last_blocked_pr_health_fingerprint.clone(); + let conversation_id = workspace.conversation_id.clone(); let mut response = AgentConversationWorkspaceResponse::from(workspace); response.mode_switch_locked = mode_lock.locked; response.mode_switch_lock_reason = mode_lock.reason; @@ -1103,6 +1117,29 @@ async fn agent_workspace_response_without_repair_recovery_for_state( .map_err(|error| error.to_string())? .filter(|attempt| attempt.is_unsettled()) .map(|attempt| attempt.operation_snapshot()); + // Purely informational. A failed cost query must degrade this one field rather than fail the + // whole workspace payload the Agents surface depends on. + response.pr_autofix_fingerprint_spend = match pr_autofix_fingerprint { + Some(fingerprint) => { + match pr_autofix_fingerprint_spend(state, &conversation_id, &fingerprint).await { + Ok(spend) => Some(PrAutofixFingerprintSpendResponse { + generations: spend.generations, + minutes: spend.minutes, + budget_minutes: spend.budget_minutes, + is_exhausted: spend.is_exhausted(), + }), + Err(error) => { + tracing::warn!( + conversation_id = conversation_id.as_str(), + %error, + "Could not compute repair spend for the publish surface" + ); + None + } + } + } + None => None, + }; if let Some(plan_branch_id) = linked_plan_branch_id { if let Some(plan_branch) = state diff --git a/src-tauri/src/commands/unified_chat_commands/tests.rs b/src-tauri/src/commands/unified_chat_commands/tests.rs index 63aa9d4662..cdbd8c317a 100644 --- a/src-tauri/src/commands/unified_chat_commands/tests.rs +++ b/src-tauri/src/commands/unified_chat_commands/tests.rs @@ -1252,6 +1252,7 @@ fn linked_plan_branch_publication_is_projected_into_workspace_response() { mode_switch_locked: false, mode_switch_lock_reason: None, maintenance_operation: None, + pr_autofix_fingerprint_spend: None, }; let mut plan_branch = PlanBranch::new( ArtifactId::from_string("artifact-1"), @@ -1322,6 +1323,7 @@ fn linked_plan_branch_publication_overrides_stale_workspace_publication_response mode_switch_locked: true, mode_switch_lock_reason: Some("Plan execution is still active".to_string()), maintenance_operation: None, + pr_autofix_fingerprint_spend: None, }; let mut plan_branch = PlanBranch::new( ArtifactId::from_string("artifact-1"), @@ -3713,7 +3715,10 @@ async fn base_update_retry_returns_successful_repair_started_response() { #[tokio::test] async fn workspace_response_does_not_recover_a_stranded_repair_inline() { let state = AppState::new_test(); - let workspace = command_test_workspace(); + let mut workspace = command_test_workspace(); + workspace.last_blocked_pr_health_fingerprint = Some( + "github_pr_autofix:42:checks:rust-tests".to_string(), + ); state .agent_conversation_workspace_repo .create_or_update(workspace.clone()) @@ -3773,6 +3778,16 @@ async fn workspace_response_does_not_recover_a_stranded_repair_inline() { .expect("workspace response should succeed"); assert_eq!(response.conversation_id, workspace.conversation_id.as_str()); + assert_eq!( + response.pr_autofix_fingerprint_spend, + Some(super::PrAutofixFingerprintSpendResponse { + generations: 0, + minutes: 0, + budget_minutes: crate::infrastructure::agents::limits_config() + .repair_fingerprint_budget_minutes, + is_exhausted: false, + }) + ); assert_eq!( state .agent_workspace_repair_repo From 9a1fa778f3cbf6798666a26c61ae35354be52e76 Mon Sep 17 00:00:00 2001 From: lazabogdan <6580668+lazabogdan@users.noreply.github.com> Date: Fri, 31 Jul 2026 18:55:19 +0300 Subject: [PATCH 08/29] test(chat): serialize standalone-conversation flag override tests MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The standalone_conversations override is a process-global atomic, and four tests set it while libtest runs them concurrently. A test asserting "flag off" could observe another test's "flag on", so the pair failed together while each passed alone — a real CI failure, not local noise. - live_flags.rs gains a test-only LiveFlagOverrideTestGuard that takes a shared mutex for the test body and resets both overrides on drop. The lock is poison-tolerant on purpose: one failing test must fail alone rather than turning every other test sharing the flag into a misleading poisoned-lock failure. - The four standalone tests swap their local reset guard for it, so serialization is structural rather than something each new test must remember. They carry the codebase's usual await_holding_lock allow. Validation: the four tests now pass together, and the full commands::unified_chat_commands suite is 192 passed / 0 failed (previously 190/2). Formatting the test file also reflowed one pre-existing import block. Co-Authored-By: Claude Opus 5 --- .../commands/unified_chat_commands/tests.rs | 38 +++++++++-------- .../agents/claude/agent_config/live_flags.rs | 41 +++++++++++++++++++ .../src/infrastructure/agents/claude/mod.rs | 2 + src-tauri/src/infrastructure/agents/mod.rs | 1 + 4 files changed, 65 insertions(+), 17 deletions(-) diff --git a/src-tauri/src/commands/unified_chat_commands/tests.rs b/src-tauri/src/commands/unified_chat_commands/tests.rs index cdbd8c317a..3a0b9c9c57 100644 --- a/src-tauri/src/commands/unified_chat_commands/tests.rs +++ b/src-tauri/src/commands/unified_chat_commands/tests.rs @@ -10,13 +10,14 @@ use super::{ ensure_plan_workspace_planning_session_link_for_send, existing_pr_retarget_block_reason, filter_agent_list_visible_conversations, fork_agent_conversation, fork_agent_conversation_response_for_state, fork_terminal_agent_conversation_for_send, - get_agent_conversation_runtime_index_for_app_state, get_agent_run_attribution, + get_agent_conversation_runtime_index_for_app_state, get_agent_conversation_runtime_statuses_for_app_state, get_agent_conversation_summary_for_app_state, get_agent_conversation_timeline_page_for_app_state, get_agent_conversation_workspace_freshness, - get_agent_timeline_item_tool_call_detail_for_app_state, hidden_user_message_metadata, - invalidate_agent_workspace_freshness_cache, list_agent_conversations_page, - load_delegated_tool_runtime_snapshot, mark_agent_workspace_failure_with_routing_and_action, + get_agent_run_attribution, get_agent_timeline_item_tool_call_detail_for_app_state, + hidden_user_message_metadata, invalidate_agent_workspace_freshness_cache, + list_agent_conversations_page, load_delegated_tool_runtime_snapshot, + mark_agent_workspace_failure_with_routing_and_action, mark_agent_workspace_publish_failure_with_target, mark_agent_workspace_update_failure_with_target, merge_delegated_snapshot_into_result, normalize_agent_runtime_selection, normalize_agent_workspace_source_pull_request, @@ -827,17 +828,18 @@ async fn create_agent_conversation_persists_team_intent_coordination_mode() { assert_eq!(stored.coordination_mode, CoordinationMode::RxNativeTeam); } -struct StandaloneConversationsFlagOverrideReset; - -impl Drop for StandaloneConversationsFlagOverrideReset { - fn drop(&mut self) { - crate::infrastructure::agents::reset_standalone_conversations_override_for_test(); - } +/// The standalone-conversations override is process-global. Acquiring this guard serializes every +/// test that sets it and restores the ambient value on drop, so a test asserting "flag off" can +/// never observe another test's "flag on". +fn standalone_conversations_flag_override_guard( +) -> crate::infrastructure::agents::LiveFlagOverrideTestGuard { + crate::infrastructure::agents::LiveFlagOverrideTestGuard::new() } #[tokio::test] +#[allow(clippy::await_holding_lock)] async fn create_agent_conversation_standalone_flag_on_round_trips_self_keyed() { - let _reset = StandaloneConversationsFlagOverrideReset; + let _flag_guard = standalone_conversations_flag_override_guard(); crate::infrastructure::agents::set_standalone_conversations_override(Some(true)); let app = build_send_now_command_app(AppState::new_test()); @@ -868,8 +870,9 @@ async fn create_agent_conversation_standalone_flag_on_round_trips_self_keyed() { } #[tokio::test] +#[allow(clippy::await_holding_lock)] async fn create_agent_conversation_standalone_flag_off_is_rejected() { - let _reset = StandaloneConversationsFlagOverrideReset; + let _flag_guard = standalone_conversations_flag_override_guard(); crate::infrastructure::agents::set_standalone_conversations_override(Some(false)); let app = build_send_now_command_app(AppState::new_test()); @@ -890,8 +893,9 @@ async fn create_agent_conversation_standalone_flag_off_is_rejected() { } #[tokio::test] +#[allow(clippy::await_holding_lock)] async fn create_agent_conversation_standalone_rejects_supplied_context_id() { - let _reset = StandaloneConversationsFlagOverrideReset; + let _flag_guard = standalone_conversations_flag_override_guard(); crate::infrastructure::agents::set_standalone_conversations_override(Some(true)); let app = build_send_now_command_app(AppState::new_test()); @@ -912,8 +916,9 @@ async fn create_agent_conversation_standalone_rejects_supplied_context_id() { } #[tokio::test] +#[allow(clippy::await_holding_lock)] async fn create_agent_conversation_standalone_rejects_team_intent() { - let _reset = StandaloneConversationsFlagOverrideReset; + let _flag_guard = standalone_conversations_flag_override_guard(); crate::infrastructure::agents::set_standalone_conversations_override(Some(true)); let app = build_send_now_command_app(AppState::new_test()); @@ -3716,9 +3721,8 @@ async fn base_update_retry_returns_successful_repair_started_response() { async fn workspace_response_does_not_recover_a_stranded_repair_inline() { let state = AppState::new_test(); let mut workspace = command_test_workspace(); - workspace.last_blocked_pr_health_fingerprint = Some( - "github_pr_autofix:42:checks:rust-tests".to_string(), - ); + workspace.last_blocked_pr_health_fingerprint = + Some("github_pr_autofix:42:checks:rust-tests".to_string()); state .agent_conversation_workspace_repo .create_or_update(workspace.clone()) diff --git a/src-tauri/src/infrastructure/agents/claude/agent_config/live_flags.rs b/src-tauri/src/infrastructure/agents/claude/agent_config/live_flags.rs index 0bcc9b466e..a90ab8e246 100644 --- a/src-tauri/src/infrastructure/agents/claude/agent_config/live_flags.rs +++ b/src-tauri/src/infrastructure/agents/claude/agent_config/live_flags.rs @@ -5,6 +5,47 @@ const UNSET: i8 = -1; static AGENT_PERSONAS_OVERRIDE: AtomicI8 = AtomicI8::new(UNSET); static STANDALONE_CONVERSATIONS_OVERRIDE: AtomicI8 = AtomicI8::new(UNSET); +/// These overrides are process-global, so tests that set them cannot run concurrently with each +/// other: one test asserting "flag off" will observe another test's "flag on" and fail. Tests take +/// this lock for their whole body through a guard that also resets the override on drop. +#[cfg(any(test, feature = "test-utils"))] +pub static LIVE_FLAG_OVERRIDE_TEST_MUTEX: std::sync::Mutex<()> = std::sync::Mutex::new(()); + +/// Serializes live-flag override tests and restores the ambient value when the test ends. +/// +/// The lock is intentionally poison-tolerant: one failing test must fail alone, not convert every +/// other test sharing this flag into a misleading "poisoned lock" failure. +#[cfg(any(test, feature = "test-utils"))] +pub struct LiveFlagOverrideTestGuard { + _lock: std::sync::MutexGuard<'static, ()>, +} + +#[cfg(any(test, feature = "test-utils"))] +impl Default for LiveFlagOverrideTestGuard { + fn default() -> Self { + Self::new() + } +} + +#[cfg(any(test, feature = "test-utils"))] +impl LiveFlagOverrideTestGuard { + pub fn new() -> Self { + Self { + _lock: LIVE_FLAG_OVERRIDE_TEST_MUTEX + .lock() + .unwrap_or_else(std::sync::PoisonError::into_inner), + } + } +} + +#[cfg(any(test, feature = "test-utils"))] +impl Drop for LiveFlagOverrideTestGuard { + fn drop(&mut self) { + reset_agent_personas_override_for_test(); + reset_standalone_conversations_override_for_test(); + } +} + /// Effective agent_personas flag: DB override (if set) > env > yaml. pub fn agent_personas_enabled() -> bool { match AGENT_PERSONAS_OVERRIDE.load(Ordering::Relaxed) { diff --git a/src-tauri/src/infrastructure/agents/claude/mod.rs b/src-tauri/src/infrastructure/agents/claude/mod.rs index 5dd6ffec40..df5c9d5415 100644 --- a/src-tauri/src/infrastructure/agents/claude/mod.rs +++ b/src-tauri/src/infrastructure/agents/claude/mod.rs @@ -25,6 +25,8 @@ pub use agent_config::live_flags::{ reset_agent_personas_override_for_test, reset_standalone_conversations_override_for_test, set_agent_personas_override, set_standalone_conversations_override, }; +#[cfg(any(test, feature = "test-utils"))] +pub use agent_config::live_flags::LiveFlagOverrideTestGuard; pub use agent_config::process_config::{resolve_process_agent, ProcessMapping, ProcessSlot}; pub use agent_config::{ agent_configs, agent_harness_defaults_config, agent_personas_enabled, automations_config, diff --git a/src-tauri/src/infrastructure/agents/mod.rs b/src-tauri/src/infrastructure/agents/mod.rs index 3e768c0b54..6411e4b182 100644 --- a/src-tauri/src/infrastructure/agents/mod.rs +++ b/src-tauri/src/infrastructure/agents/mod.rs @@ -24,6 +24,7 @@ pub use claude::{ #[cfg(any(test, feature = "test-utils"))] pub use claude::{ reset_agent_personas_override_for_test, reset_standalone_conversations_override_for_test, + LiveFlagOverrideTestGuard, }; pub use claude::{StreamEvent, StreamingSpawnResult}; pub use codex::stream_processor::{ From b8502729b459b3d1431982a50cecc89bbcadea5b Mon Sep 17 00:00:00 2001 From: lazabogdan <6580668+lazabogdan@users.noreply.github.com> Date: Fri, 31 Jul 2026 19:35:59 +0300 Subject: [PATCH 09/29] fix: resolve CI failures on workspace repair loop branch Three independent CI failures from the base update: - Rust Lib Coverage (all 4 shards): the `FallbackOnlyWorkspaceRepository` mock in `ralphx-domain` was missing the new `set_last_blocked_pr_health_fingerprint` trait method, breaking the lib test build. That crate is not covered by the root `cargo check`, so the gap only surfaced in coverage. All other implementors already had it. - Rust Clippy: `route_agent_workspace_pr_autofix_if_needed_with_repair_repo` had no `#[cfg(test)]` gate but every caller is a test, so it tripped `dead_code` under the `--no-default-features` lib lane. Gated it to match its sibling wrapper. - Rust IPC Contracts: the new dispatch contract test lived in `src/application/` while importing `crate::commands::unified_chat_commands`, a new violation of the `root_application_no_commands_or_http_imports` layering rule. Moved it to `src/commands/`, which only forbids `crate::http_server`. No baseline update and no visibility widening. Co-Authored-By: Claude Opus 5 --- .../agent_conversation_workspace_repository_tests.rs | 8 ++++++++ src-tauri/src/application/mod.rs | 2 -- src-tauri/src/application/services/pr_merge_poller.rs | 1 + .../agent_workspace_dispatch_contract_tests.rs | 0 src-tauri/src/commands/mod.rs | 2 ++ 5 files changed, 11 insertions(+), 2 deletions(-) rename src-tauri/src/{application => commands}/agent_workspace_dispatch_contract_tests.rs (100%) diff --git a/src-tauri/crates/ralphx-domain/src/repositories/agent_conversation_workspace_repository_tests.rs b/src-tauri/crates/ralphx-domain/src/repositories/agent_conversation_workspace_repository_tests.rs index 8dab50479b..41e7ba53de 100644 --- a/src-tauri/crates/ralphx-domain/src/repositories/agent_conversation_workspace_repository_tests.rs +++ b/src-tauri/crates/ralphx-domain/src/repositories/agent_conversation_workspace_repository_tests.rs @@ -145,6 +145,14 @@ impl AgentConversationWorkspaceRepository for FallbackOnlyWorkspaceRepository { Ok(false) } + async fn set_last_blocked_pr_health_fingerprint( + &self, + _conversation_id: &ChatConversationId, + _fingerprint: Option<&str>, + ) -> AppResult<()> { + Ok(()) + } + async fn delete(&self, _conversation_id: &ChatConversationId) -> AppResult<()> { Ok(()) } diff --git a/src-tauri/src/application/mod.rs b/src-tauri/src/application/mod.rs index 1afe2cf265..8fa6f95ab3 100644 --- a/src-tauri/src/application/mod.rs +++ b/src-tauri/src/application/mod.rs @@ -426,8 +426,6 @@ mod agent_workspace_continuation_tests; #[cfg(test)] mod agent_workspace_external_pr_reconciliation_tests; #[cfg(test)] -mod agent_workspace_dispatch_contract_tests; -#[cfg(test)] mod agent_workspace_pr_metadata_reconciliation_tests; #[cfg(test)] mod agent_workspace_pr_supervision_recovery_tests; diff --git a/src-tauri/src/application/services/pr_merge_poller.rs b/src-tauri/src/application/services/pr_merge_poller.rs index 2aaa1d4e6e..e5a78a777e 100644 --- a/src-tauri/src/application/services/pr_merge_poller.rs +++ b/src-tauri/src/application/services/pr_merge_poller.rs @@ -2744,6 +2744,7 @@ async fn route_agent_workspace_pr_autofix_if_needed( /// Compatibility entry point without user notifications. Production polling uses /// [`route_agent_workspace_pr_autofix_if_needed_with_notifications`] so hand-offs reach the Inbox. +#[cfg(test)] #[allow(clippy::too_many_arguments)] async fn route_agent_workspace_pr_autofix_if_needed_with_repair_repo( github: Arc, diff --git a/src-tauri/src/application/agent_workspace_dispatch_contract_tests.rs b/src-tauri/src/commands/agent_workspace_dispatch_contract_tests.rs similarity index 100% rename from src-tauri/src/application/agent_workspace_dispatch_contract_tests.rs rename to src-tauri/src/commands/agent_workspace_dispatch_contract_tests.rs diff --git a/src-tauri/src/commands/mod.rs b/src-tauri/src/commands/mod.rs index 7c1c56083f..3a78fb5b6e 100644 --- a/src-tauri/src/commands/mod.rs +++ b/src-tauri/src/commands/mod.rs @@ -6,6 +6,8 @@ pub mod agent_composer_commands; pub mod agent_conversation_mute_commands; #[cfg(test)] mod agent_conversation_mute_commands_tests; +#[cfg(test)] +mod agent_workspace_dispatch_contract_tests; pub mod agent_issue_report_commands; #[cfg(test)] mod agent_issue_report_commands_tests; From 0f6c4eb2d1ea44b5119335ae488ca210415ad8ca Mon Sep 17 00:00:00 2001 From: lazabogdan <6580668+lazabogdan@users.noreply.github.com> Date: Fri, 31 Jul 2026 23:44:35 +0300 Subject: [PATCH 10/29] fix: commit workspace changes before base merge Stage in-progress workspace repair loop and chat focus role changes to unblock merge from main. --- frontend/src/api/chat.ts | 14 ++ .../AgentsActiveConversationPanel.test.tsx | 73 +++++++--- .../agents/AgentsActiveConversationPanel.tsx | 109 ++++++++++---- .../agents/AgentsChatHeader.test.tsx | 23 +++ .../AgentsComposerWorkspaceChangesCard.tsx | 21 +++ .../agents/AgentsConversationMainRegion.tsx | 6 + .../components/agents/agentChatFocus.test.ts | 90 ++++++++++++ .../src/components/agents/agentChatFocus.ts | 54 +++++++ .../agents/agentChatFocusRole.test.ts | 37 +++++ .../components/agents/agentChatFocusRole.ts | 43 ++++++ ...ViewController.automationRunFocus.test.tsx | 40 +++++ .../agents/useAgentsViewController.test.ts | 35 +++++ .../agents/useAgentsViewController.ts | 134 ++++++++++++++--- .../src/entities/agent_workspace_repair.rs | 13 ++ .../agent_workspace_repair_repository.rs | 15 ++ .../agent_workspace_fixer_conversation.rs | 132 +++++++++++++++++ .../durable_attempt_recovery.rs | 27 +++- .../agent_workspace_publish_recovery_tests.rs | 64 ++++++-- .../agent_workspace_publish_repair_state.rs | 6 +- ...nt_workspace_publish_repair_state_tests.rs | 24 ++- .../src/application/agent_workspace_review.rs | 47 +++++- src-tauri/src/application/mod.rs | 45 +++--- .../application/services/pr_merge_poller.rs | 19 ++- .../services/pr_merge_poller_tests.rs | 15 +- .../src/commands/unified_chat_commands/mod.rs | 71 ++++++--- .../repair_attempt_fencing_tests.rs | 1 + .../repair_attempts.rs | 31 +++- .../repair_attempts_tests.rs | 137 +++++++++++++++++- .../infrastructure/sqlite/migrations/mod.rs | 10 +- ...t_workspace_repair_runtime_conversation.rs | 35 +++++ ...space_repair_runtime_conversation_tests.rs | 92 ++++++++++++ .../repair_attempt_fencing_tests.rs | 1 + .../repair_attempts.rs | 130 ++++++++++++----- .../repair_attempts_tests.rs | 135 ++++++++++++++++- .../agent_workspace_repair_auto_publish.rs | 1 + .../agent_workspace_repair_completion.rs | 1 + 36 files changed, 1542 insertions(+), 189 deletions(-) create mode 100644 frontend/src/components/agents/agentChatFocusRole.test.ts create mode 100644 frontend/src/components/agents/agentChatFocusRole.ts create mode 100644 frontend/src/components/agents/useAgentsViewController.test.ts create mode 100644 src-tauri/src/application/agent_workspace_fixer_conversation.rs create mode 100644 src-tauri/src/infrastructure/sqlite/migrations/v20260731170447_agent_workspace_repair_runtime_conversation.rs create mode 100644 src-tauri/src/infrastructure/sqlite/migrations/v20260731170447_agent_workspace_repair_runtime_conversation_tests.rs diff --git a/frontend/src/api/chat.ts b/frontend/src/api/chat.ts index 7834b3f8de..192137c945 100644 --- a/frontend/src/api/chat.ts +++ b/frontend/src/api/chat.ts @@ -2508,6 +2508,8 @@ export interface AgentWorkspaceReviewContext { events: AgentConversationWorkspacePublicationEvent[]; target: AgentWorkspaceReviewTarget | null; monitor: AgentWorkspaceReviewMonitor; + repairRuntimeConversationId: string | null; + repairFixerKind: "workspace_repair" | "pr_fixer" | null; reviewArtifactIsCurrent: boolean; reviewArtifactIsOutdated: boolean; canMutateReviewState: boolean; @@ -3017,6 +3019,12 @@ const AgentWorkspaceReviewContextResponseSchema = z.object({ events: AgentConversationWorkspacePublicationEventListResponseSchema, target: AgentWorkspaceReviewTargetResponseSchema.nullable(), monitor: AgentWorkspaceReviewMonitorResponseSchema, + repair_runtime_conversation_id: z.string().nullable().optional().default(null), + repair_fixer_kind: z + .enum(["workspace_repair", "pr_fixer"]) + .nullable() + .optional() + .default(null), review_artifact_is_current: z.boolean().optional(), review_artifact_is_outdated: z.boolean().optional(), can_mutate_review_state: z.boolean().optional().default(false), @@ -3737,6 +3745,8 @@ function transformAgentWorkspaceReviewContext( events: raw.events.map(transformAgentConversationWorkspacePublicationEvent), target: raw.target ? transformAgentWorkspaceReviewTarget(raw.target) : null, monitor: transformAgentWorkspaceReviewMonitor(raw.monitor), + repairRuntimeConversationId: raw.repair_runtime_conversation_id, + repairFixerKind: raw.repair_fixer_kind, reviewArtifactIsCurrent: raw.review_artifact_is_current ?? raw.is_current, reviewArtifactIsOutdated: raw.review_artifact_is_outdated ?? raw.is_outdated, @@ -5029,6 +5039,8 @@ export async function getAgentRunningStates( const AgentConversationRuntimeSourceSchema = z.enum([ "workspace", "workspace_review", + "workspace_repair", + "pr_fixer", "ideation", "verification", "task_execution", @@ -5142,6 +5154,8 @@ export type AgentConversationRuntimeIndexGroup = z.infer< const AgentConversationRuntimeIndexKindSchema = z.enum([ "workspace", "workspace_review", + "workspace_repair", + "pr_fixer", "ideation", "verification", "delegation", diff --git a/frontend/src/components/agents/AgentsActiveConversationPanel.test.tsx b/frontend/src/components/agents/AgentsActiveConversationPanel.test.tsx index bf18d63efc..a5b6155445 100644 --- a/frontend/src/components/agents/AgentsActiveConversationPanel.test.tsx +++ b/frontend/src/components/agents/AgentsActiveConversationPanel.test.tsx @@ -1630,6 +1630,10 @@ describe("AgentsActiveConversationPanel", () => { role: "workspace_repair", display_name: "Fixer", description: "Fix", family: "workspace", family_display_name: "Workspace", requires_tasks: false, configured: null, effective: { provider: "claude", model: "sonnet", effort: "medium", service_tier: "provider_default", coordination_mode: "solo", persona_id: null, approval_policy: null, sandbox_mode: null }, source: "project", diagnostics: [], controls: { capabilities: [], speeds: [], persona: { enabled: true, disabled_reason: null } }, }, + { + role: "pr_fixer", display_name: "PR Fixer", description: "Fix PR", family: "workspace", family_display_name: "Workspace", requires_tasks: false, + configured: null, effective: { provider: "codex", model: "gpt-5.6", effort: "medium", service_tier: "standard", coordination_mode: "solo", persona_id: null, approval_policy: null, sandbox_mode: null }, source: "project", diagnostics: [], controls: { capabilities: [], speeds: [], persona: { enabled: true, disabled_reason: null } }, + }, ] }); } return Promise.resolve(undefined); @@ -1639,8 +1643,12 @@ describe("AgentsActiveConversationPanel", () => { "project-1", { provider: "claude", modelId: "opus", effort: "xhigh" }, ); - useChatStore.setState({ activeAgentRunMeta: { "project:conversation-1": { launchRole: "workspace_reviewer", agentName: "reviewer" } } }); - const { rerenderPanel } = renderPanel(); + const { rerenderPanel } = renderPanel({ + chatFocus: { + type: "workspace_review", + conversationId: "review-conversation-1", + }, + }); expect(await screen.findByTestId("agents-role-runtime-banner")).toHaveTextContent("Reviewer run active"); expect(screen.getByTestId("workspace-runtime-tag")).toHaveTextContent("REV"); @@ -1663,15 +1671,27 @@ describe("AgentsActiveConversationPanel", () => { effort: "xhigh", }); - useChatStore.setState({ activeAgentRunMeta: { "project:conversation-1": { launchRole: "workspace_repair", agentName: "fixer" } } }); - rerenderPanel({ publishAttemptsByConversationId: { "conversation-1": { conversationId: "conversation-1", startedAtMs: 1 } } }); + rerenderPanel({ + chatFocus: { + type: "workspace_repair", + conversationId: "repair-conversation-1", + }, + }); expect(await screen.findByTestId("agents-role-runtime-banner")).toHaveTextContent("Fixer run active"); expect(screen.getByTestId("workspace-runtime-tag")).toHaveTextContent("FIX"); await waitFor(() => expect(screen.getByTestId("workspace-provider-value")).toHaveTextContent("claude")); expect(screen.getByTestId("workspace-model-value")).toHaveTextContent("sonnet"); + fireEvent.click(screen.getByTestId("change-workspace-model")); + expect(useAgentSessionStore.getState().roleRuntimeOverridesByConversationId["conversation-1"]?.workspace_repair?.model).toBe("sonnet"); - useChatStore.setState({ activeAgentRunMeta: {} }); - rerenderPanel({ publishAttemptsByConversationId: { "conversation-1": { conversationId: "conversation-1", startedAtMs: 2 } } }); + rerenderPanel({ + chatFocus: { type: "pr_fixer", conversationId: "pr-fixer-conversation-1" }, + }); + expect(await screen.findByTestId("agents-role-runtime-banner")).toHaveTextContent("PR Fixer run active"); + fireEvent.click(screen.getByTestId("change-workspace-model")); + expect(useAgentSessionStore.getState().roleRuntimeOverridesByConversationId["conversation-1"]?.pr_fixer?.model).toBe("sonnet"); + + rerenderPanel({ chatFocus: { type: "workspace" } }); expect(screen.queryByTestId("agents-role-runtime-banner")).not.toBeInTheDocument(); expect(screen.getByTestId("workspace-provider-value")).toHaveTextContent("claude"); expect(screen.getByTestId("workspace-model-value")).toHaveTextContent("opus"); @@ -2261,18 +2281,13 @@ describe("AgentsActiveConversationPanel", () => { fireEvent.click(screen.getByTestId("change-workspace-model")); fireEvent.click(screen.getByTestId("change-workspace-effort")); - expect(onActiveModelChange).toHaveBeenCalledWith("sonnet", [ - "low", - "medium", - "high", - "max", - ], null); - expect(onActiveEffortChange).toHaveBeenCalledWith("max", [ - "low", - "medium", - "high", - "max", - ], null); + expect(onActiveModelChange).not.toHaveBeenCalled(); + expect(onActiveEffortChange).not.toHaveBeenCalled(); + expect( + useAgentSessionStore.getState().roleRuntimeOverridesByConversationId[ + "conversation-1" + ]?.workspace_reviewer, + ).toMatchObject({ model: "sonnet", effort: "max" }); }); it("allows provider changes in an existing workspace conversation", () => { @@ -2915,6 +2930,28 @@ describe("AgentsActiveConversationPanel", () => { ); }); + it.each([ + { type: "workspace_repair" as const, conversationId: "repair-conversation-1" }, + { type: "pr_fixer" as const, conversationId: "pr-fixer-conversation-1" }, + ])("routes $type focus sends through its child project chat", (chatFocus) => { + renderPanel({ chatFocus }); + + const panel = screen.getByTestId("integrated-chat-panel"); + expect(panel).toHaveAttribute("data-conversation-id", chatFocus.conversationId); + expect(panel).toHaveAttribute( + "data-agent-process-context-id", + chatFocus.conversationId, + ); + expect(panel).toHaveAttribute( + "data-store-context-key", + `project:${chatFocus.conversationId}`, + ); + expect(panel).toHaveAttribute( + "data-send-conversation-id", + chatFocus.conversationId, + ); + }); + it("does not inherit parent Codex fast mode while focused on workspace Review", () => { renderPanel({ activeConversation: { diff --git a/frontend/src/components/agents/AgentsActiveConversationPanel.tsx b/frontend/src/components/agents/AgentsActiveConversationPanel.tsx index cd86e7dbcf..7802dacac3 100644 --- a/frontend/src/components/agents/AgentsActiveConversationPanel.tsx +++ b/frontend/src/components/agents/AgentsActiveConversationPanel.tsx @@ -16,6 +16,7 @@ import { PanelRightOpen, Play, ShieldCheck, + Wrench, type LucideIcon, } from "lucide-react"; import { useQuery, useQueryClient } from "@tanstack/react-query"; @@ -90,16 +91,11 @@ import { ideationKeys } from "@/hooks/useIdeation"; import { useIdeationSettings } from "@/hooks/useIdeationSettings"; import { useVerificationStatus, verificationStatusKey } from "@/hooks/useVerificationStatus"; import { useEventBus } from "@/providers/EventProvider"; -import { - selectActiveAgentRunMeta, - selectQueuedMessages, - useChatStore, -} from "@/stores/chatStore"; +import { selectQueuedMessages, useChatStore } from "@/stores/chatStore"; import { useUiStore } from "@/stores/uiStore"; import type { AgentArtifactTab, AgentProvider, - LaunchRuntimeRoleKey, AgentRuntimeSelection, } from "@/stores/agentSessionStore"; import type { @@ -160,6 +156,7 @@ import { import { getFocusedAutomationRunConversationId, getFocusedChatSessionId, + getFocusedFixerConversationId, getFocusedWorkspaceReviewConversationId, getAutomationRunFocusOptions, type AgentsChatFocus, @@ -167,6 +164,11 @@ import { type AgentsChatFocusType, type AutomationRunFocusOptions, } from "./agentChatFocus"; +import { + getChatFocusRuntimeLabel, + getChatFocusRuntimeRole, + getChatFocusRuntimeTag, +} from "./agentChatFocusRole"; import { isTaskRuntimeContextType, type AgentTaskRuntimeContextType, @@ -308,6 +310,15 @@ function isRuntimeItemOwnedByFocus( (item.conversationId ?? item.contextId) === chatFocus.conversationId ); } + if ( + chatFocus.type === "workspace_repair" || + chatFocus.type === "pr_fixer" + ) { + return ( + item.source === chatFocus.type && + (item.conversationId ?? item.contextId) === chatFocus.conversationId + ); + } if (chatFocus.type === "automation_run") { return (item.conversationId ?? item.contextId) === chatFocus.conversationId; } @@ -767,6 +778,8 @@ interface AgentsActiveConversationPanelProps { conversationId: string, runtimeHint?: AgentRuntimeSelection, ) => void; + onFocusWorkspaceRepair: (conversationId: string) => void; + onFocusPrFixer: (conversationId: string) => void; onFocusVerificationSession: ( parentSessionId: string, childSessionId: string @@ -834,6 +847,8 @@ export const AgentsActiveConversationPanel = memo(function AgentsActiveConversat onFocusIdeationSession, onFocusIdeationSessionForConversation, onFocusWorkspaceReview, + onFocusWorkspaceRepair, + onFocusPrFixer, onFocusVerificationSession, onFocusTaskRuntime, onFocusAutomationRun, @@ -874,8 +889,11 @@ export const AgentsActiveConversationPanel = memo(function AgentsActiveConversat const focusedChatSessionId = getFocusedChatSessionId(chatFocus); const focusedWorkspaceReviewConversationId = getFocusedWorkspaceReviewConversationId(chatFocus); + const focusedFixerConversationId = getFocusedFixerConversationId(chatFocus); const runtimeControlConversationId = - focusedWorkspaceReviewConversationId ?? selectedConversationId; + focusedWorkspaceReviewConversationId ?? + focusedFixerConversationId ?? + selectedConversationId; const { registry: modelRegistry } = useAgentModels(); const { data: featureFlags } = useFeatureFlags(); const teamMode = @@ -1207,6 +1225,8 @@ export const AgentsActiveConversationPanel = memo(function AgentsActiveConversat ? `${taskRuntimeFocus.contextType}:${taskRuntimeFocus.taskId}` : focusedWorkspaceReviewConversationId ? `workspace_review:${focusedWorkspaceReviewConversationId}` + : focusedFixerConversationId + ? `${chatFocus.type}:${focusedFixerConversationId}` : focusedAutomationRunConversationId ? `automation_run:${focusedAutomationRunConversationId}` : focusedChatSessionId ?? "workspace"; @@ -1251,7 +1271,10 @@ export const AgentsActiveConversationPanel = memo(function AgentsActiveConversat ? automationDetailQuery.data : null; const usesWorkspaceRuntimeControls = - !isFocusedChildChat || chatFocus.type === "workspace_review"; + !isFocusedChildChat || + chatFocus.type === "workspace_review" || + chatFocus.type === "workspace_repair" || + chatFocus.type === "pr_fixer"; const workspaceSendRuntime = usesWorkspaceRuntimeControls ? selectableWorkspaceRuntime : normalizedActiveRuntime; @@ -1315,15 +1338,7 @@ export const AgentsActiveConversationPanel = memo(function AgentsActiveConversat const activeConversationAgentStatus = useChatStore( (state) => state.agentStatus[activeConversationStoreKey] ?? "idle", ); - const activeRoleRunMeta = useChatStore( - selectActiveAgentRunMeta(activeConversationStoreKey), - ); - const activeRole = - activeRoleRunMeta?.launchRole === "workspace_reviewer" || - activeRoleRunMeta?.launchRole === "workspace_repair" || - activeRoleRunMeta?.launchRole === "pr_fixer" - ? (activeRoleRunMeta.launchRole as LaunchRuntimeRoleKey) - : null; + const activeRole = getChatFocusRuntimeRole(chatFocus); const roleDefaultsQuery = useManualRoleDefaults(activeProjectId); const activeRoleDefault = activeRole ? roleDefaultsQuery.catalog?.roles.find((entry) => entry.role === activeRole) @@ -1334,20 +1349,30 @@ export const AgentsActiveConversationPanel = memo(function AgentsActiveConversat ? state.roleRuntimeOverridesByConversationId[selectedConversationId]?.[activeRole] ?? null : null, ); - const activeRoleSelection = activeRoleOverride ?? activeRoleDefault; + const activeRoleSelection = + activeRoleOverride ?? + activeRoleDefault ?? + (activeRole + ? ({ + provider: selectableWorkspaceRuntime.provider, + model: selectableWorkspaceRuntime.modelId, + effort: selectableWorkspaceRuntime.effort, + serviceTier: + chatFocus.type === "workspace_review" + ? focusedWorkspaceReviewServiceTier ?? "provider_default" + : "provider_default", + coordinationMode: null, + personaId: null, + } satisfies ManualRoleRuntimeSelection) + : null); const activeRoleRuntime = activeRoleSelection ? runtimeFromManualRoleDefault( { ...activeRoleSelection, approvalPolicy: null, sandboxMode: null }, modelRegistry, ) : null; - const activeRoleLabel = - activeRole === "workspace_reviewer" - ? "Reviewer" - : activeRole === "workspace_repair" || activeRole === "pr_fixer" - ? "Fixer" - : null; - const activeRoleTag = activeRole === "workspace_reviewer" ? "REV" : "FIX"; + const activeRoleLabel = getChatFocusRuntimeLabel(chatFocus); + const activeRoleTag = getChatFocusRuntimeTag(chatFocus); const composerRuntime = activeRoleRuntime ?? normalizedActiveRuntime; const updateActiveRoleRuntime = useCallback( (changes: Partial) => { @@ -1568,6 +1593,9 @@ export const AgentsActiveConversationPanel = memo(function AgentsActiveConversat if (focusedWorkspaceReviewConversationId) { return buildStoreKey("project", focusedWorkspaceReviewConversationId); } + if (focusedFixerConversationId) { + return buildStoreKey("project", focusedFixerConversationId); + } if (focusedAutomationRunConversationId) { return buildStoreKey("project", focusedAutomationRunConversationId); } @@ -1579,6 +1607,7 @@ export const AgentsActiveConversationPanel = memo(function AgentsActiveConversat activeConversation, focusedAutomationRunConversationId, focusedChatSessionId, + focusedFixerConversationId, focusedWorkspaceReviewConversationId, taskRuntimeFocus, ]); @@ -1590,11 +1619,13 @@ export const AgentsActiveConversationPanel = memo(function AgentsActiveConversat taskRuntimeFocus ? null : focusedWorkspaceReviewConversationId ?? + focusedFixerConversationId ?? focusedAutomationRunConversationId ?? (!isFocusedChildChat ? selectedConversationId : null); const panelAgentProcessContextIdOverride = taskRuntimeFocus ? taskRuntimeFocus.taskId : focusedWorkspaceReviewConversationId ?? + focusedFixerConversationId ?? focusedAutomationRunConversationId ?? (!isFocusedChildChat && activeConversation.contextType === "project" ? selectedConversationId @@ -1603,6 +1634,7 @@ export const AgentsActiveConversationPanel = memo(function AgentsActiveConversat taskRuntimeFocus ? null : focusedWorkspaceReviewConversationId ?? + focusedFixerConversationId ?? focusedAutomationRunConversationId ?? (!isFocusedChildChat ? selectedConversationId : null); const queuedMessages = useChatStore(queuedMessagesSelector); @@ -1650,6 +1682,8 @@ export const AgentsActiveConversationPanel = memo(function AgentsActiveConversat ? MessageSquare : option.type === "task_runtime" ? Play + : option.type === "workspace_repair" || option.type === "pr_fixer" + ? Wrench : option.tone === "accent" ? Lightbulb : option.tone === "warning" @@ -1688,6 +1722,18 @@ export const AgentsActiveConversationPanel = memo(function AgentsActiveConversat }, [onFocusWorkspaceReview], ); + const handleViewRuntimeWorkspaceRepair = useCallback( + (conversationId: string) => { + onFocusWorkspaceRepair(conversationId); + }, + [onFocusWorkspaceRepair], + ); + const handleViewRuntimePrFixer = useCallback( + (conversationId: string) => { + onFocusPrFixer(conversationId); + }, + [onFocusPrFixer], + ); const handleOpenAutomationRun = useCallback( (automationId: string, run: AutomationRun) => { if (!run.conversationId) { @@ -2783,7 +2829,9 @@ export const AgentsActiveConversationPanel = memo(function AgentsActiveConversat {activeRoleLabel} run active — composer targets {activeRoleLabel} - {activeRoleRunMeta?.agentName - ? ` › ${activeRoleRunMeta.agentName}` - : ""}
)} @@ -3190,8 +3237,8 @@ export const AgentsActiveConversationPanel = memo(function AgentsActiveConversat placeholder: "Current project", disabled: true, }} - {...(activeRole ? { runtimeTag: activeRoleTag } : {})} - {...(chatFocus.type === "workspace" + {...(activeRoleTag ? { runtimeTag: activeRoleTag } : {})} + {...(chatFocus.type === "workspace" || activeRole ? { runtimeDefault: { source: activeRole diff --git a/frontend/src/components/agents/AgentsChatHeader.test.tsx b/frontend/src/components/agents/AgentsChatHeader.test.tsx index 22e162d997..0d303365a0 100644 --- a/frontend/src/components/agents/AgentsChatHeader.test.tsx +++ b/frontend/src/components/agents/AgentsChatHeader.test.tsx @@ -552,6 +552,29 @@ describe("AgentsChatHeader", () => { expect(onBackToWorkspaceChat).toHaveBeenCalledTimes(1); }); + it.each([ + { type: "workspace_repair" as const, conversationId: "repair-child" }, + { type: "pr_fixer" as const, conversationId: "pr-fixer-child" }, + ])("shows a back action for $type focus", (chatFocus) => { + renderWithProviders( + , + ); + + expect( + screen.getByRole("button", { name: "Back to Workspace Chat" }), + ).toBeInTheDocument(); + }); + it("keeps verification focus out of the primary title row", () => { renderWithProviders( void; onViewIdeation: (sessionId: string) => void; onViewWorkspaceReview: (conversationId: string) => void; + onViewWorkspaceRepair: (conversationId: string) => void; + onViewPrFixer: (conversationId: string) => void; onViewVerification: (parentSessionId: string, childSessionId: string) => void; onViewTaskRuntime: ( taskId: string, @@ -450,6 +452,8 @@ export function AgentsComposerWorkspaceChangesCard({ onViewWorkspace, onViewIdeation, onViewWorkspaceReview, + onViewWorkspaceRepair, + onViewPrFixer, onViewVerification, onViewTaskRuntime, onViewAutomationRun, @@ -471,6 +475,8 @@ export function AgentsComposerWorkspaceChangesCard({ onViewWorkspace={onViewWorkspace} onViewIdeation={onViewIdeation} onViewWorkspaceReview={onViewWorkspaceReview} + onViewWorkspaceRepair={onViewWorkspaceRepair} + onViewPrFixer={onViewPrFixer} onViewVerification={onViewVerification} onViewTaskRuntime={onViewTaskRuntime} onViewAutomationRun={onViewAutomationRun} @@ -703,6 +709,8 @@ function AgentsComposerWorkspaceChangesCardContent({ onViewWorkspace, onViewIdeation, onViewWorkspaceReview, + onViewWorkspaceRepair, + onViewPrFixer, onViewVerification, onViewTaskRuntime, onViewAutomationRun, @@ -722,6 +730,8 @@ function AgentsComposerWorkspaceChangesCardContent({ onViewWorkspace: () => void; onViewIdeation: (sessionId: string) => void; onViewWorkspaceReview: (conversationId: string) => void; + onViewWorkspaceRepair: (conversationId: string) => void; + onViewPrFixer: (conversationId: string) => void; onViewVerification: (parentSessionId: string, childSessionId: string) => void; onViewTaskRuntime: ( taskId: string, @@ -1126,6 +1136,17 @@ function AgentsComposerWorkspaceChangesCardContent({ } return; } + if (row.kind === "workspace_repair" || row.kind === "pr_fixer") { + const conversationId = row.conversationId ?? row.contextId; + if (conversationId) { + if (row.kind === "workspace_repair") { + onViewWorkspaceRepair(conversationId); + } else { + onViewPrFixer(conversationId); + } + } + return; + } if ( row.kind === "task" && row.taskId && diff --git a/frontend/src/components/agents/AgentsConversationMainRegion.tsx b/frontend/src/components/agents/AgentsConversationMainRegion.tsx index 683b46e8f1..1e1b0624e5 100644 --- a/frontend/src/components/agents/AgentsConversationMainRegion.tsx +++ b/frontend/src/components/agents/AgentsConversationMainRegion.tsx @@ -39,6 +39,8 @@ interface AgentsConversationMainRegionProps { "onFocusIdeationSessionForConversation" ]; onFocusWorkspaceReview: ActiveConversationPanelProps["onFocusWorkspaceReview"]; + onFocusWorkspaceRepair: ActiveConversationPanelProps["onFocusWorkspaceRepair"]; + onFocusPrFixer: ActiveConversationPanelProps["onFocusPrFixer"]; onFocusVerificationSession: ActiveConversationPanelProps["onFocusVerificationSession"]; onFocusTaskRuntime: ActiveConversationPanelProps["onFocusTaskRuntime"]; onFocusAutomationRun: ActiveConversationPanelProps["onFocusAutomationRun"]; @@ -101,6 +103,8 @@ export const AgentsConversationMainRegion = memo(function AgentsConversationMain onFocusIdeationSession, onFocusIdeationSessionForConversation, onFocusWorkspaceReview, + onFocusWorkspaceRepair, + onFocusPrFixer, onFocusVerificationSession, onFocusTaskRuntime, onFocusAutomationRun, @@ -162,6 +166,8 @@ export const AgentsConversationMainRegion = memo(function AgentsConversationMain onFocusIdeationSessionForConversation } onFocusWorkspaceReview={onFocusWorkspaceReview} + onFocusWorkspaceRepair={onFocusWorkspaceRepair} + onFocusPrFixer={onFocusPrFixer} onFocusVerificationSession={onFocusVerificationSession} onFocusTaskRuntime={onFocusTaskRuntime} onFocusAutomationRun={onFocusAutomationRun} diff --git a/frontend/src/components/agents/agentChatFocus.test.ts b/frontend/src/components/agents/agentChatFocus.test.ts index 4924334e5e..3bd36b20d8 100644 --- a/frontend/src/components/agents/agentChatFocus.test.ts +++ b/frontend/src/components/agents/agentChatFocus.test.ts @@ -8,6 +8,7 @@ import { getFocusedAutomationRunConversationId, getFocusedArtifactIdeationSession, getFocusedChatSessionId, + getFocusedFixerConversationId, getFocusedWorkspaceReviewConversationId, type AgentsChatFocus, } from "./agentChatFocus"; @@ -60,6 +61,17 @@ const workspaceReviewFocus: Extract< type: "workspace_review", conversationId: "review-conversation-1", }; +const workspaceRepairFocus: Extract< + AgentsChatFocus, + { type: "workspace_repair" } +> = { + type: "workspace_repair", + conversationId: "workspace-repair-conversation-1", +}; +const prFixerFocus: Extract = { + type: "pr_fixer", + conversationId: "pr-fixer-conversation-1", +}; const automationRunFocus: Extract = { type: "automation_run", automationId: "automation-1", @@ -75,6 +87,8 @@ describe("getAgentChatFocusSwitchOptions", () => { verificationFocusTarget: verificationFocus, taskRuntimeFocusTarget: null, workspaceReviewFocusTarget: null, + workspaceRepairFocusTarget: null, + prFixerFocusTarget: null, automationRunFocusTarget: null, hasPlanArtifact: true, }); @@ -93,6 +107,8 @@ describe("getAgentChatFocusSwitchOptions", () => { verificationFocusTarget: verificationFocus, taskRuntimeFocusTarget: null, workspaceReviewFocusTarget: null, + workspaceRepairFocusTarget: null, + prFixerFocusTarget: null, automationRunFocusTarget: null, hasPlanArtifact: true, }); @@ -110,6 +126,8 @@ describe("getAgentChatFocusSwitchOptions", () => { verificationFocusTarget: verificationFocus, taskRuntimeFocusTarget: null, workspaceReviewFocusTarget: null, + workspaceRepairFocusTarget: null, + prFixerFocusTarget: null, automationRunFocusTarget: null, hasPlanArtifact: false, }); @@ -124,6 +142,8 @@ describe("getAgentChatFocusSwitchOptions", () => { verificationFocusTarget: verificationFocus, taskRuntimeFocusTarget: null, workspaceReviewFocusTarget: null, + workspaceRepairFocusTarget: null, + prFixerFocusTarget: null, automationRunFocusTarget: null, hasPlanArtifact: true, }); @@ -138,6 +158,8 @@ describe("getAgentChatFocusSwitchOptions", () => { verificationFocusTarget: null, taskRuntimeFocusTarget: taskRuntimeFocus, workspaceReviewFocusTarget: null, + workspaceRepairFocusTarget: null, + prFixerFocusTarget: null, automationRunFocusTarget: null, hasPlanArtifact: false, }); @@ -160,6 +182,8 @@ describe("getAgentChatFocusSwitchOptions", () => { verificationFocusTarget: null, taskRuntimeFocusTarget: null, workspaceReviewFocusTarget: workspaceReviewFocus, + workspaceRepairFocusTarget: null, + prFixerFocusTarget: null, automationRunFocusTarget: null, hasPlanArtifact: false, }); @@ -182,6 +206,8 @@ describe("getAgentChatFocusSwitchOptions", () => { verificationFocusTarget: null, taskRuntimeFocusTarget: null, workspaceReviewFocusTarget: null, + workspaceRepairFocusTarget: null, + prFixerFocusTarget: null, automationRunFocusTarget: automationRunFocus, hasPlanArtifact: false, }); @@ -196,6 +222,41 @@ describe("getAgentChatFocusSwitchOptions", () => { tone: "accent", }); }); + + it("adds Fixer and PR Fixer focus only when their child chats exist", () => { + const options = getAgentChatFocusSwitchOptions({ + mode: "edit", + focusSwitcherIdeationSessionId: null, + verificationFocusTarget: null, + taskRuntimeFocusTarget: null, + workspaceReviewFocusTarget: workspaceReviewFocus, + workspaceRepairFocusTarget: workspaceRepairFocus, + prFixerFocusTarget: prFixerFocus, + automationRunFocusTarget: null, + hasPlanArtifact: false, + }); + + expect(options.map((option) => option.type)).toEqual([ + "workspace", + "workspace_review", + "workspace_repair", + "pr_fixer", + ]); + expect(options.slice(2)).toEqual([ + { + type: "workspace_repair", + label: "Fixer", + description: "Show the workspace fixer chat", + tone: "warning", + }, + { + type: "pr_fixer", + label: "PR Fixer", + description: "Show the PR fixer chat", + tone: "warning", + }, + ]); + }); }); describe("task runtime focus helpers", () => { @@ -259,3 +320,32 @@ describe("workspace Review focus helpers", () => { }); }); }); + +describe("fixer focus helpers", () => { + it("describes both fixer types and maps them to their child conversations", () => { + expect(getAgentsChatFocusDisplay(workspaceRepairFocus)).toEqual({ + type: "workspace_repair", + label: "Fixer", + description: "Focused on a workspace fixer run", + tone: "warning", + }); + expect(getAgentsChatFocusDisplay(prFixerFocus)).toEqual({ + type: "pr_fixer", + label: "PR Fixer", + description: "Focused on a PR fixer run", + tone: "warning", + }); + expect(getFocusedFixerConversationId(workspaceRepairFocus)).toBe( + "workspace-repair-conversation-1", + ); + expect(getFocusedFixerConversationId(prFixerFocus)).toBe( + "pr-fixer-conversation-1", + ); + }); + + it("leaves fixer availability to the controller reconciler", () => { + expect( + getConversationScopedChatFocus(workspaceRepairFocus, "conversation-2"), + ).toEqual(workspaceRepairFocus); + }); +}); diff --git a/frontend/src/components/agents/agentChatFocus.ts b/frontend/src/components/agents/agentChatFocus.ts index a1c7e7b8db..e90fd97ee4 100644 --- a/frontend/src/components/agents/agentChatFocus.ts +++ b/frontend/src/components/agents/agentChatFocus.ts @@ -14,6 +14,8 @@ export type AgentsChatFocus = conversationId: string; runtimeHint?: AgentRuntimeSelection; } + | { type: "workspace_repair"; conversationId: string } + | { type: "pr_fixer"; conversationId: string } | { type: "ideation"; conversationId: string; sessionId: string } | { type: "verification"; @@ -78,6 +80,8 @@ export function getAgentChatFocusSwitchOptions({ verificationFocusTarget, taskRuntimeFocusTarget, workspaceReviewFocusTarget, + workspaceRepairFocusTarget, + prFixerFocusTarget, automationRunFocusTarget, hasPlanArtifact, }: { @@ -86,6 +90,8 @@ export function getAgentChatFocusSwitchOptions({ verificationFocusTarget: Extract | null; taskRuntimeFocusTarget: Extract | null; workspaceReviewFocusTarget: Extract | null; + workspaceRepairFocusTarget: Extract | null; + prFixerFocusTarget: Extract | null; automationRunFocusTarget: Extract | null; hasPlanArtifact: boolean; }): AgentsChatFocusSwitchOption[] { @@ -128,6 +134,24 @@ export function getAgentChatFocusSwitchOptions({ }); } + if (workspaceRepairFocusTarget) { + options.push({ + type: "workspace_repair", + label: "Fixer", + description: "Show the workspace fixer chat", + tone: "warning", + }); + } + + if (prFixerFocusTarget) { + options.push({ + type: "pr_fixer", + label: "PR Fixer", + description: "Show the PR fixer chat", + tone: "warning", + }); + } + if (taskRuntimeFocusTarget) { options.push({ type: "task_runtime", @@ -259,6 +283,24 @@ export function getAgentsChatFocusDisplay( }; } + if (chatFocus.type === "workspace_repair") { + return { + type: "workspace_repair", + label: "Fixer", + description: "Focused on a workspace fixer run", + tone: "warning", + }; + } + + if (chatFocus.type === "pr_fixer") { + return { + type: "pr_fixer", + label: "PR Fixer", + description: "Focused on a PR fixer run", + tone: "warning", + }; + } + if (chatFocus.type === "automation_run") { return { type: "automation_run", @@ -290,6 +332,18 @@ export function getFocusedWorkspaceReviewConversationId( return null; } +export function getFocusedFixerConversationId( + chatFocus: AgentsChatFocus, +): string | null { + if ( + chatFocus.type === "workspace_repair" || + chatFocus.type === "pr_fixer" + ) { + return chatFocus.conversationId; + } + return null; +} + export function getFocusedAutomationRunConversationId( chatFocus: AgentsChatFocus, ): string | null { diff --git a/frontend/src/components/agents/agentChatFocusRole.test.ts b/frontend/src/components/agents/agentChatFocusRole.test.ts new file mode 100644 index 0000000000..30e4c0a6f7 --- /dev/null +++ b/frontend/src/components/agents/agentChatFocusRole.test.ts @@ -0,0 +1,37 @@ +import { describe, expect, it } from "vitest"; + +import { + getChatFocusRuntimeLabel, + getChatFocusRuntimeRole, + getChatFocusRuntimeTag, +} from "./agentChatFocusRole"; + +describe("chat focus role derivation", () => { + it("derives reviewer and fixer role controls only from focused child chats", () => { + expect(getChatFocusRuntimeRole({ type: "workspace" })).toBeNull(); + expect( + getChatFocusRuntimeRole({ + type: "workspace_review", + conversationId: "review-1", + }), + ).toBe("workspace_reviewer"); + expect( + getChatFocusRuntimeRole({ + type: "workspace_repair", + conversationId: "repair-1", + }), + ).toBe("workspace_repair"); + expect( + getChatFocusRuntimeRole({ type: "pr_fixer", conversationId: "pr-1" }), + ).toBe("pr_fixer"); + expect( + getChatFocusRuntimeLabel({ type: "pr_fixer", conversationId: "pr-1" }), + ).toBe("PR Fixer"); + expect( + getChatFocusRuntimeTag({ type: "workspace_review", conversationId: "review-1" }), + ).toBe("REV"); + expect( + getChatFocusRuntimeTag({ type: "workspace_repair", conversationId: "repair-1" }), + ).toBe("FIX"); + }); +}); diff --git a/frontend/src/components/agents/agentChatFocusRole.ts b/frontend/src/components/agents/agentChatFocusRole.ts new file mode 100644 index 0000000000..e611d6aee2 --- /dev/null +++ b/frontend/src/components/agents/agentChatFocusRole.ts @@ -0,0 +1,43 @@ +import type { LaunchRuntimeRoleKey } from "@/stores/agentSessionStore"; + +import type { AgentsChatFocus } from "./agentChatFocus"; + +export function getChatFocusRuntimeRole( + chatFocus: AgentsChatFocus, +): LaunchRuntimeRoleKey | null { + switch (chatFocus.type) { + case "workspace_review": + return "workspace_reviewer"; + case "workspace_repair": + return "workspace_repair"; + case "pr_fixer": + return "pr_fixer"; + default: + return null; + } +} + +export function getChatFocusRuntimeLabel( + chatFocus: AgentsChatFocus, +): "Reviewer" | "Fixer" | "PR Fixer" | null { + switch (chatFocus.type) { + case "workspace_review": + return "Reviewer"; + case "workspace_repair": + return "Fixer"; + case "pr_fixer": + return "PR Fixer"; + default: + return null; + } +} + +export function getChatFocusRuntimeTag( + chatFocus: AgentsChatFocus, +): "REV" | "FIX" | null { + return chatFocus.type === "workspace_review" + ? "REV" + : chatFocus.type === "workspace_repair" || chatFocus.type === "pr_fixer" + ? "FIX" + : null; +} diff --git a/frontend/src/components/agents/useAgentsViewController.automationRunFocus.test.tsx b/frontend/src/components/agents/useAgentsViewController.automationRunFocus.test.tsx index 2884aa4708..ce55e92bad 100644 --- a/frontend/src/components/agents/useAgentsViewController.automationRunFocus.test.tsx +++ b/frontend/src/components/agents/useAgentsViewController.automationRunFocus.test.tsx @@ -32,6 +32,7 @@ vi.mock("@/components/automations/automationRunNavigation", () => ({ const { getAgentConversationWorkspaceMock, + getWorkspaceReviewContextMock, integratedChatPanelRenderMock, useConversationMock, useProjectAgentConversationsMock, @@ -176,6 +177,45 @@ describe("useAgentsViewController automation run focus", () => { useAgentArtifactUiStore.setState({ artifactByConversationId: {} }); }); + it("auto-focuses a durable workspace fixer child instead of resetting to workspace", async () => { + const setup = automationSetupConversation({ agentMode: "edit" }); + mockHydratedSetupConversation(setup); + getWorkspaceReviewContextMock.mockResolvedValue({ + success: true, + workspace: conversationWorkspace({ conversationId: setup.id, mode: "edit" }), + events: [], + target: null, + monitor: { + conversationId: setup.id, + status: "idle", + reviewConversationId: null, + reviewFixerConversationId: "fixer-child-1", + }, + repairRuntimeConversationId: null, + repairFixerKind: null, + isCurrent: false, + isOutdated: false, + shouldShowTab: false, + }); + resetAgentSessionState({ + selectedProjectId: "project-1", + selectedConversationId: setup.id, + }); + + renderControllerView(); + + await waitFor(() => { + expect(screen.getByTestId("integrated-chat-panel")).toHaveAttribute( + "data-conversation-id-override", + "fixer-child-1", + ); + }); + expect(screen.getByTestId("integrated-chat-panel")).toHaveAttribute( + "data-send-conversation-id", + "fixer-child-1", + ); + }); + it("holds an automation-run request until the setup conversation hydrates, consumes it once, and clears it", async () => { const hydratedSetup = automationSetupConversation(); const conversationRef: { current: AgentConversation | null } = { current: null }; diff --git a/frontend/src/components/agents/useAgentsViewController.test.ts b/frontend/src/components/agents/useAgentsViewController.test.ts new file mode 100644 index 0000000000..f16f81e8bd --- /dev/null +++ b/frontend/src/components/agents/useAgentsViewController.test.ts @@ -0,0 +1,35 @@ +import { describe, expect, it } from "vitest"; + +import { getWorkspaceRepairFocusTarget } from "./useAgentsViewController"; + +describe("getWorkspaceRepairFocusTarget", () => { + it("prioritizes a Review fixer over a durable workspace repair attempt", () => { + expect( + getWorkspaceRepairFocusTarget({ + reviewFixerConversationId: "review-fixer-child", + repairRuntimeConversationId: "repair-child", + repairFixerKind: "workspace_repair", + }), + ).toEqual({ + type: "workspace_repair", + conversationId: "review-fixer-child", + }); + }); + + it("uses only a durable workspace repair attempt and never a PR fixer", () => { + expect( + getWorkspaceRepairFocusTarget({ + reviewFixerConversationId: null, + repairRuntimeConversationId: "repair-child", + repairFixerKind: "workspace_repair", + }), + ).toEqual({ type: "workspace_repair", conversationId: "repair-child" }); + expect( + getWorkspaceRepairFocusTarget({ + reviewFixerConversationId: null, + repairRuntimeConversationId: "pr-fixer-child", + repairFixerKind: "pr_fixer", + }), + ).toBeNull(); + }); +}); diff --git a/frontend/src/components/agents/useAgentsViewController.ts b/frontend/src/components/agents/useAgentsViewController.ts index 7a36b05072..d312f6bd90 100644 --- a/frontend/src/components/agents/useAgentsViewController.ts +++ b/frontend/src/components/agents/useAgentsViewController.ts @@ -132,6 +132,24 @@ type WorkspaceReviewPublishPromotionState = Pick< "monitor" | "reviewArtifactIsCurrent" | "reviewArtifactIsOutdated" >; +export function getWorkspaceRepairFocusTarget({ + reviewFixerConversationId, + repairRuntimeConversationId, + repairFixerKind, +}: { + reviewFixerConversationId: string | null; + repairRuntimeConversationId: string | null; + repairFixerKind: "workspace_repair" | "pr_fixer" | null; +}): Extract | null { + // Backend guards make a Review fixer and an ordinary repair attempt mutually exclusive. + const conversationId = + reviewFixerConversationId ?? + (repairFixerKind === "workspace_repair" + ? repairRuntimeConversationId + : null); + return conversationId ? { type: "workspace_repair", conversationId } : null; +} + function hasCurrentPassedWorkspaceReview( context: WorkspaceReviewPublishPromotionState | null, ): boolean { @@ -482,6 +500,21 @@ export function useAgentsViewController({ focusWorkspaceReview(current, conversationId, runtimeHint), ); }, []); + const handleFocusWorkspaceRepair = useCallback((conversationId: string) => { + setChatFocus((current) => + current.type === "workspace_repair" && + current.conversationId === conversationId + ? current + : { type: "workspace_repair", conversationId }, + ); + }, []); + const handleFocusPrFixer = useCallback((conversationId: string) => { + setChatFocus((current) => + current.type === "pr_fixer" && current.conversationId === conversationId + ? current + : { type: "pr_fixer", conversationId }, + ); + }, []); const handleTaskArtifactSelectionChange = useCallback( (taskId: string | null) => { setSelectedTaskArtifactId(taskId); @@ -500,36 +533,40 @@ export function useAgentsViewController({ current.type === "workspace" ? current : { type: "workspace" }, ); }, []); - const focusedWorkspaceReviewRuntimeConversationId = - chatFocus.type === "workspace_review" ? chatFocus.conversationId : null; - const focusedWorkspaceReviewSummaryQuery = useConversationSummary( - focusedWorkspaceReviewRuntimeConversationId, - { enabled: Boolean(focusedWorkspaceReviewRuntimeConversationId) }, + const focusedChildRuntimeConversationId = + chatFocus.type === "workspace_review" || + chatFocus.type === "workspace_repair" || + chatFocus.type === "pr_fixer" + ? chatFocus.conversationId + : null; + const focusedChildRuntimeSummaryQuery = useConversationSummary( + focusedChildRuntimeConversationId, + { enabled: Boolean(focusedChildRuntimeConversationId) }, ); const focusedWorkspaceReviewConversation = useMemo( () => { - const summary = focusedWorkspaceReviewSummaryQuery.data; + const summary = focusedChildRuntimeSummaryQuery.data; if ( summary && - summary.id === focusedWorkspaceReviewRuntimeConversationId + summary.id === focusedChildRuntimeConversationId ) { return toProjectAgentConversation(summary); } - return focusedWorkspaceReviewRuntimeConversationId + return focusedChildRuntimeConversationId ? focusedConversations.data?.find( (conversation) => - conversation.id === focusedWorkspaceReviewRuntimeConversationId, + conversation.id === focusedChildRuntimeConversationId, ) ?? null : null; }, [ focusedConversations.data, - focusedWorkspaceReviewRuntimeConversationId, - focusedWorkspaceReviewSummaryQuery.data, + focusedChildRuntimeConversationId, + focusedChildRuntimeSummaryQuery.data, ], ); const activeRuntimeConversationId = - focusedWorkspaceReviewRuntimeConversationId ?? selectedConversationId; + focusedChildRuntimeConversationId ?? selectedConversationId; const reviewerRoleDefaults = useManualRoleDefaults(activeProjectId); const activeProject = useMemo( () => projects.find((project) => project.id === activeProjectId) ?? null, @@ -560,7 +597,9 @@ export function useAgentsViewController({ activeProject, focusedWorkspaceReviewConversation, focusedWorkspaceReviewConversationId: - focusedWorkspaceReviewRuntimeConversationId, + chatFocus.type === "workspace_review" + ? focusedChildRuntimeConversationId + : null, focusedWorkspaceReviewRuntimeHint: chatFocus.type === "workspace_review" ? (chatFocus.runtimeHint ?? null) @@ -896,6 +935,33 @@ export function useAgentsViewController({ : null, [workspaceReviewChildConversationId], ); + const repairRuntimeConversationId = + workspaceReviewContext?.repairRuntimeConversationId ?? null; + const repairFixerKind = workspaceReviewContext?.repairFixerKind ?? null; + const workspaceRepairFocusTarget = useMemo( + () => + getWorkspaceRepairFocusTarget({ + reviewFixerConversationId: + workspaceReviewContext?.monitor.reviewFixerConversationId ?? null, + repairRuntimeConversationId, + repairFixerKind, + }), + [ + repairFixerKind, + repairRuntimeConversationId, + workspaceReviewContext?.monitor.reviewFixerConversationId, + ], + ); + const prFixerFocusTarget = useMemo( + () => + repairFixerKind === "pr_fixer" && repairRuntimeConversationId + ? ({ + type: "pr_fixer", + conversationId: repairRuntimeConversationId, + } satisfies Extract) + : null, + [repairFixerKind, repairRuntimeConversationId], + ); useEffect(() => { if ( workspaceReviewContext?.monitor.status !== "reviewing" || @@ -914,17 +980,23 @@ export function useAgentsViewController({ ); }, [workspaceReviewChildConversationId, workspaceReviewContext?.monitor.status]); useEffect(() => { - const fixerStatus = workspaceReviewContext?.monitor.reviewFixerStatus ?? null; - if (fixerStatus !== "queued" && fixerStatus !== "running") { - return; - } + if (!workspaceRepairFocusTarget) return; setChatFocus((current) => - current.type === "workspace_review" ? { type: "workspace" } : current, + current.type === "workspace_repair" && + current.conversationId === workspaceRepairFocusTarget.conversationId + ? current + : workspaceRepairFocusTarget, ); - }, [ - workspaceReviewContext?.monitor.reviewFixerRunId, - workspaceReviewContext?.monitor.reviewFixerStatus, - ]); + }, [workspaceRepairFocusTarget]); + useEffect(() => { + if (!prFixerFocusTarget) return; + setChatFocus((current) => + current.type === "pr_fixer" && + current.conversationId === prFixerFocusTarget.conversationId + ? current + : prFixerFocusTarget, + ); + }, [prFixerFocusTarget]); const chatFocusOptions = useMemo(() => { return getAgentChatFocusSwitchOptions({ mode: activeConversationMode, @@ -932,6 +1004,8 @@ export function useAgentsViewController({ verificationFocusTarget, taskRuntimeFocusTarget, workspaceReviewFocusTarget, + workspaceRepairFocusTarget, + prFixerFocusTarget, automationRunFocusTarget, hasPlanArtifact: hasAttachedPlanArtifact, }); @@ -943,6 +1017,8 @@ export function useAgentsViewController({ taskRuntimeFocusTarget, verificationFocusTarget, workspaceReviewFocusTarget, + workspaceRepairFocusTarget, + prFixerFocusTarget, ]); useEffect(() => { if (chatFocusOptions.some((option) => option.type === chatFocus.type)) { @@ -983,6 +1059,16 @@ export function useAgentsViewController({ return; } + if (type === "workspace_repair" && workspaceRepairFocusTarget) { + setChatFocus(workspaceRepairFocusTarget); + return; + } + + if (type === "pr_fixer" && prFixerFocusTarget) { + setChatFocus(prFixerFocusTarget); + return; + } + if (type === "automation_run" && automationRunFocusTarget) { setChatFocus(automationRunFocusTarget); } @@ -996,6 +1082,8 @@ export function useAgentsViewController({ verificationFocusTarget, automationRunFocusTarget, workspaceReviewFocusTarget, + workspaceRepairFocusTarget, + prFixerFocusTarget, ], ); const { @@ -1703,6 +1791,8 @@ export function useAgentsViewController({ onFocusIdeationSessionForConversation: handleFocusIdeationSessionForConversation, onFocusWorkspaceReview: handleFocusWorkspaceReview, + onFocusWorkspaceRepair: handleFocusWorkspaceRepair, + onFocusPrFixer: handleFocusPrFixer, onFocusVerificationSession: handleFocusVerificationSession, onFocusTaskRuntime: handleFocusTaskRuntime, onFocusAutomationRun: handleFocusAutomationRun, diff --git a/src-tauri/crates/ralphx-domain/src/entities/agent_workspace_repair.rs b/src-tauri/crates/ralphx-domain/src/entities/agent_workspace_repair.rs index 0c1a698544..a3f5a486d7 100644 --- a/src-tauri/crates/ralphx-domain/src/entities/agent_workspace_repair.rs +++ b/src-tauri/crates/ralphx-domain/src/entities/agent_workspace_repair.rs @@ -163,6 +163,9 @@ pub struct AgentWorkspaceRepairAttempt { pub phase: AgentWorkspaceRepairPhase, pub continuation: AgentWorkspaceRepairContinuation, pub reserved_agent_run_id: Option, + /// Dedicated child conversation hosting this attempt's fixer runs. + /// `None` means a legacy attempt whose runs live in `conversation_id` itself. + pub runtime_conversation_id: Option, pub target_base_ref: String, pub target_base_commit: Option, pub pending_reasons: Vec, @@ -214,6 +217,7 @@ impl AgentWorkspaceRepairAttempt { phase: AgentWorkspaceRepairPhase::Requested, continuation, reserved_agent_run_id: None, + runtime_conversation_id: None, target_base_ref: target_base_ref.into(), target_base_commit: None, pending_reasons: Vec::new(), @@ -245,6 +249,15 @@ impl AgentWorkspaceRepairAttempt { self.settled_at.is_none() } + /// Conversation the fixer agent actually runs in. + /// + /// Falls back to the workspace conversation for legacy, parent-hosted attempts. + pub fn runtime_conversation_id(&self) -> &ChatConversationId { + self.runtime_conversation_id + .as_ref() + .unwrap_or(&self.conversation_id) + } + pub fn operation_snapshot(&self) -> AgentWorkspaceRepairOperationSnapshot { let stage = match self.phase { AgentWorkspaceRepairPhase::Requested | AgentWorkspaceRepairPhase::Dispatching => { diff --git a/src-tauri/crates/ralphx-domain/src/repositories/agent_workspace_repair_repository.rs b/src-tauri/crates/ralphx-domain/src/repositories/agent_workspace_repair_repository.rs index ceff8ba21e..06e9d9d84c 100644 --- a/src-tauri/crates/ralphx-domain/src/repositories/agent_workspace_repair_repository.rs +++ b/src-tauri/crates/ralphx-domain/src/repositories/agent_workspace_repair_repository.rs @@ -47,6 +47,9 @@ pub struct BindAgentWorkspaceRepairAttemptRun { /// schedule must not let an older dispatcher bind a run onto newer durable authority. pub expected_updated_at: DateTime, pub run_id: AgentRunId, + /// First reservation records the attempt's dedicated fixer child; redelivery must preserve + /// the existing value. + pub runtime_conversation_id: Option, pub updated_at: DateTime, } @@ -194,6 +197,18 @@ pub trait AgentWorkspaceRepairRepository: Send + Sync { conversation_id: &ChatConversationId, ) -> AppResult>; + /// Resolve the attempt whose dedicated runtime conversation is `runtime_conversation_id`. + /// + /// Returns `None` when no unsettled attempt owns that conversation. + /// + /// # Errors + /// Returns a repository error when the lookup fails. A failed lookup MUST NOT be reported as + /// `Ok(None)`; callers treat `Ok(None)` as "not authorized". + async fn get_unsettled_attempt_by_runtime_conversation( + &self, + runtime_conversation_id: &ChatConversationId, + ) -> AppResult>; + /// Returns the latest durable generation, including settled generations. Legacy import is /// allowed only when this returns `None` so stale projections can never revive an old flow. async fn get_latest_repair_attempt_for_conversation( diff --git a/src-tauri/src/application/agent_workspace_fixer_conversation.rs b/src-tauri/src/application/agent_workspace_fixer_conversation.rs new file mode 100644 index 0000000000..e3712e5f07 --- /dev/null +++ b/src-tauri/src/application/agent_workspace_fixer_conversation.rs @@ -0,0 +1,132 @@ +use crate::application::AppState; +use crate::domain::entities::{ + AgentConversationWorkspace, AgentWorkspaceRepairSource, ChatConversation, ChatConversationId, +}; +use crate::domain::repositories::ChatConversationRepository; +use crate::error::AppResult; +use crate::infrastructure::agents::claude::agent_names::{ + AGENT_WORKSPACE_PR_FIXER, AGENT_WORKSPACE_REPAIR, +}; + +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum AgentWorkspaceFixerKind { + WorkspaceRepair, + PrFixer, +} + +impl AgentWorkspaceFixerKind { + pub fn agent_name(self) -> &'static str { + match self { + Self::WorkspaceRepair => AGENT_WORKSPACE_REPAIR, + Self::PrFixer => AGENT_WORKSPACE_PR_FIXER, + } + } + + pub fn launch_role(self) -> &'static str { + match self { + Self::WorkspaceRepair => "workspace_repair", + Self::PrFixer => "pr_fixer", + } + } +} + +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum AgentWorkspaceFixerTitleContext { + Repair(AgentWorkspaceRepairSource), + ReviewBlocking, + PullRequest(Option), +} + +pub async fn ensure_agent_workspace_fixer_conversation( + state: &AppState, + workspace: &AgentConversationWorkspace, + existing: Option<&ChatConversationId>, + kind: AgentWorkspaceFixerKind, + title_context: AgentWorkspaceFixerTitleContext, +) -> AppResult { + ensure_agent_workspace_fixer_conversation_with_repo( + state.chat_conversation_repo.as_ref(), + workspace, + existing, + kind, + title_context, + ) + .await +} + +pub(crate) async fn ensure_agent_workspace_fixer_conversation_with_repo( + conversation_repo: &dyn ChatConversationRepository, + workspace: &AgentConversationWorkspace, + existing: Option<&ChatConversationId>, + kind: AgentWorkspaceFixerKind, + title_context: AgentWorkspaceFixerTitleContext, +) -> AppResult { + if let Some(existing) = existing { + return Ok(*existing); + } + + create_agent_workspace_fixer_conversation_with_repo( + conversation_repo, + workspace, + kind, + title_context, + ) + .await +} + +pub async fn create_agent_workspace_fixer_conversation( + state: &AppState, + workspace: &AgentConversationWorkspace, + kind: AgentWorkspaceFixerKind, + title_context: AgentWorkspaceFixerTitleContext, +) -> AppResult { + create_agent_workspace_fixer_conversation_with_repo( + state.chat_conversation_repo.as_ref(), + workspace, + kind, + title_context, + ) + .await +} + +async fn create_agent_workspace_fixer_conversation_with_repo( + conversation_repo: &dyn ChatConversationRepository, + workspace: &AgentConversationWorkspace, + kind: AgentWorkspaceFixerKind, + title_context: AgentWorkspaceFixerTitleContext, +) -> AppResult { + let mut conversation = ChatConversation::new_project(workspace.project_id.clone()); + conversation.parent_conversation_id = Some(workspace.conversation_id.as_str()); + conversation.title = Some(agent_workspace_fixer_conversation_title( + kind, + title_context, + )); + let conversation = conversation_repo.create(conversation).await?; + Ok(conversation.id) +} + +fn agent_workspace_fixer_conversation_title( + kind: AgentWorkspaceFixerKind, + title_context: AgentWorkspaceFixerTitleContext, +) -> String { + match (kind, title_context) { + ( + AgentWorkspaceFixerKind::PrFixer, + AgentWorkspaceFixerTitleContext::PullRequest(Some(number)), + ) => { + format!("Fix PR #{number}") + } + (AgentWorkspaceFixerKind::PrFixer, _) => "Fix PR checks".to_string(), + (_, AgentWorkspaceFixerTitleContext::ReviewBlocking) => "Fix review findings".to_string(), + (_, AgentWorkspaceFixerTitleContext::Repair(AgentWorkspaceRepairSource::BaseUpdate)) => { + "Fix base update".to_string() + } + (_, AgentWorkspaceFixerTitleContext::Repair(AgentWorkspaceRepairSource::Publish)) => { + "Fix publish".to_string() + } + (_, AgentWorkspaceFixerTitleContext::Repair(AgentWorkspaceRepairSource::PrConflict)) => { + "Fix PR conflict".to_string() + } + (_, AgentWorkspaceFixerTitleContext::Repair(_)) => "Fix workspace".to_string(), + } +} diff --git a/src-tauri/src/application/agent_workspace_publish_recovery/durable_attempt_recovery.rs b/src-tauri/src/application/agent_workspace_publish_recovery/durable_attempt_recovery.rs index b8321a45ee..c8f4ad5dee 100644 --- a/src-tauri/src/application/agent_workspace_publish_recovery/durable_attempt_recovery.rs +++ b/src-tauri/src/application/agent_workspace_publish_recovery/durable_attempt_recovery.rs @@ -8,6 +8,10 @@ use super::pr_autofix_redelivery::{ }; use super::StalePublishRepairRecoveryOutcome; use crate::application::agent_conversation_workspace::resolve_effective_agent_conversation_workspace_path; +use crate::application::agent_workspace_fixer_conversation::{ + ensure_agent_workspace_fixer_conversation, AgentWorkspaceFixerKind, + AgentWorkspaceFixerTitleContext, +}; use crate::application::agent_workspace_pr_autofix_attempt::load_latest_exact_pr_autofix_run_for_pr; use crate::application::agent_workspace_publish_repair_state::{ agent_workspace_repair_dispatch_is_due, agent_workspace_repair_is_health_held, @@ -1005,12 +1009,31 @@ async fn reserve_and_deliver_repair_dispatch( } else { None }; + let kind = if attempt.source == AgentWorkspaceRepairSource::PrAutofix { + AgentWorkspaceFixerKind::PrFixer + } else { + AgentWorkspaceFixerKind::WorkspaceRepair + }; + let title_context = if kind == AgentWorkspaceFixerKind::PrFixer { + AgentWorkspaceFixerTitleContext::PullRequest(workspace.publication_pr_number) + } else { + AgentWorkspaceFixerTitleContext::Repair(attempt.source) + }; + let runtime_conversation_id = ensure_agent_workspace_fixer_conversation( + state, + &workspace, + attempt.runtime_conversation_id.as_ref(), + kind, + title_context, + ) + .await?; let reserved = match reserve_agent_workspace_repair_dispatch( Arc::clone(&state.agent_workspace_repair_repo), Arc::clone(&state.branch_update_repo), target_identity, attempt, run_id.clone(), + Some(runtime_conversation_id), reservation_summary, workspace.pr_auto_merge_current, ) @@ -1048,6 +1071,8 @@ async fn reserve_and_deliver_repair_dispatch( }, ), }; + let mut options = options; + options.conversation_id_override = Some(*reserved.runtime_conversation_id()); let service = state.build_chat_service(); let delivery = service .send_message( @@ -1059,7 +1084,7 @@ async fn reserve_and_deliver_repair_dispatch( .await; let settlement = classify_agent_workspace_repair_delivery( delivery.as_ref(), - &workspace.conversation_id, + reserved.runtime_conversation_id(), &run_id, ); match settle_agent_workspace_repair_dispatch_outcome( diff --git a/src-tauri/src/application/agent_workspace_publish_recovery_tests.rs b/src-tauri/src/application/agent_workspace_publish_recovery_tests.rs index 0229ff12a9..51dedecb69 100644 --- a/src-tauri/src/application/agent_workspace_publish_recovery_tests.rs +++ b/src-tauri/src/application/agent_workspace_publish_recovery_tests.rs @@ -674,6 +674,7 @@ async fn recovery_ignores_nonterminal_run_hints_and_blocks_exhausted_ownerless_d expected_phase: AgentWorkspaceRepairPhase::Requested, expected_updated_at: live_attempt.updated_at, run_id: live_run.id.clone(), + runtime_conversation_id: None, updated_at: live_attempt.updated_at + chrono::Duration::microseconds(1), }, ) @@ -1180,6 +1181,7 @@ async fn fresh_dispatch_reservation_is_not_settled_as_interrupted_before_its_run target_identity, started, AgentRunId::from_string("fresh-dispatch-run"), + None, "dispatch fresh repair", None, ) @@ -1209,7 +1211,10 @@ async fn fresh_dispatch_reservation_is_not_settled_as_interrupted_before_its_run "a spawning dispatch must not be settled as interrupted" ); assert_eq!(held.dispatch_count, 0, "no retry was consumed"); - assert!(held.next_dispatch_at.is_none(), "no duplicate delivery queued"); + assert!( + held.next_dispatch_at.is_none(), + "no duplicate delivery queued" + ); assert_eq!( held.reserved_agent_run_id, Some(AgentRunId::from_string("fresh-dispatch-run")), @@ -1289,6 +1294,7 @@ async fn startup_recovery_schedules_one_due_retry_for_an_interrupted_repair_deli target_identity, started, AgentRunId::from_string("interrupted-repair-delivery-run"), + None, "dispatch durable repair", None, ) @@ -1448,6 +1454,7 @@ wait "$stdin_drain_pid" 2>/dev/null || true target_identity, started, AgentRunId::from_string("due-retry-initial-run"), + None, "reserve retry delivery", None, ) @@ -2826,6 +2833,7 @@ async fn due_recovery_with_an_open_repair_effect_does_not_dispatch_or_append_eve target_identity, started, AgentRunId::from_string("effect-owned-retry-initial-run"), + None, "reserve retry delivery", None, ) @@ -2977,6 +2985,7 @@ async fn startup_recovery_keeps_a_live_reserved_repair_run_authoritative() { .expect("valid canonical target identity"), started, run.id, + None, "dispatch durable repair", None, ) @@ -4419,7 +4428,10 @@ mod extracted_inline_tests { // because durable redelivery addressed the generic repairer, successors carried no failure // identity, and a live dispatch was settled as "interrupted" 43 ms after spawn. -fn failing_check_pr_health(head: &str, check_name: &str) -> crate::domain::services::github_service::PrHealth { +fn failing_check_pr_health( + head: &str, + check_name: &str, +) -> crate::domain::services::github_service::PrHealth { crate::domain::services::github_service::PrHealth { sync_state: crate::domain::services::PrSyncState { status: crate::domain::services::PrStatus::Open, @@ -4443,7 +4455,10 @@ fn failing_check_pr_health(head: &str, check_name: &str) -> crate::domain::servi } } -fn health_fingerprint(pr_number: i64, health: &crate::domain::services::github_service::PrHealth) -> String { +fn health_fingerprint( + pr_number: i64, + health: &crate::domain::services::github_service::PrHealth, +) -> String { crate::application::services::pr_merge_poller::classify_agent_workspace_pr_autofix_issue( pr_number, health, ) @@ -4499,7 +4514,10 @@ async fn block_pr_autofix_attempt_with_fingerprint( } } -async fn latest_sent_repair_message(state: &AppState, conversation_id: &ChatConversationId) -> String { +async fn latest_sent_repair_message( + state: &AppState, + conversation_id: &ChatConversationId, +) -> String { let messages = state .chat_message_repo .get_by_conversation(conversation_id) @@ -4646,7 +4664,10 @@ async fn seed_pr_autofix_health_workspace( project_dir.path(), &["config", "user.email", "recovery@example.com"], ); - recovery_git(project_dir.path(), &["config", "user.name", "Recovery Test"]); + recovery_git( + project_dir.path(), + &["config", "user.name", "Recovery Test"], + ); std::fs::write(project_dir.path().join("README.md"), "base\n").expect("write base file"); recovery_git(project_dir.path(), &["add", "README.md"]); recovery_git(project_dir.path(), &["commit", "-m", "base"]); @@ -4697,11 +4718,15 @@ async fn blocked_pr_autofix_with_unchanged_health_parks_without_spawning() { let fingerprint = health_fingerprint(684, &health); let github = Arc::new(crate::tests::mock_github_service::MockGithubService::new()); github.state().fetch_pr_health_result = Some(Ok(health)); - state.github_service = Some(github.clone() as Arc); + state.github_service = + Some(github.clone() as Arc); - let blocked = - block_pr_autofix_attempt_with_fingerprint(&state, &conversation_id, Some(fingerprint.clone())) - .await; + let blocked = block_pr_autofix_attempt_with_fingerprint( + &state, + &conversation_id, + Some(fingerprint.clone()), + ) + .await; assert_eq!( recover_agent_workspace_repair_attempts_for_state(&state) @@ -4716,11 +4741,19 @@ async fn blocked_pr_autofix_with_unchanged_health_parks_without_spawning() { .await .expect("load held attempt") .expect("held attempt remains current"); - assert_eq!(held.generation, blocked.generation, "no successor generation"); + assert_eq!( + held.generation, blocked.generation, + "no successor generation" + ); assert_eq!(held.phase, AgentWorkspaceRepairPhase::Ready); - assert!(held.pending_reasons.iter().any(|reason| reason - == crate::application::agent_workspace_publish_repair_state::UNCHANGED_HEALTH_REPAIR_REASON)); - assert_eq!(held.pr_autofix_health_fingerprint.as_deref(), Some(fingerprint.as_str())); + assert!(held.pending_reasons.iter().any(|reason| { + reason + == crate::application::agent_workspace_publish_repair_state::UNCHANGED_HEALTH_REPAIR_REASON + })); + assert_eq!( + held.pr_autofix_health_fingerprint.as_deref(), + Some(fingerprint.as_str()) + ); assert!( state .agent_run_repo @@ -4927,7 +4960,10 @@ async fn blocked_pr_autofix_without_provable_health_withholds_the_successor() { .is_empty()); } -async fn start_blocked_pr_autofix_generation(state: &AppState, conversation_id: &ChatConversationId) { +async fn start_blocked_pr_autofix_generation( + state: &AppState, + conversation_id: &ChatConversationId, +) { let started = state .agent_workspace_repair_repo .start_or_join_repair_attempt(StartOrJoinAgentWorkspaceRepairAttempt { diff --git a/src-tauri/src/application/agent_workspace_publish_repair_state.rs b/src-tauri/src/application/agent_workspace_publish_repair_state.rs index 8884794c55..76d3b851e3 100644 --- a/src-tauri/src/application/agent_workspace_publish_repair_state.rs +++ b/src-tauri/src/application/agent_workspace_publish_repair_state.rs @@ -553,9 +553,7 @@ pub(crate) async fn block_agent_workspace_repair_needs_human( /// True while a PR autofix generation is parked against a backend-derived health fingerprint. /// Only the poller may end such a hold, and only after GitHub reports different health; no other /// retry path may consume the hold's budget or settle it on a timer. -pub(crate) fn agent_workspace_repair_is_health_held( - attempt: &AgentWorkspaceRepairAttempt, -) -> bool { +pub(crate) fn agent_workspace_repair_is_health_held(attempt: &AgentWorkspaceRepairAttempt) -> bool { attempt.pending_reasons.iter().any(|reason| { reason == PRE_EXISTING_ON_BASE_REPAIR_REASON || reason == UNCHANGED_HEALTH_REPAIR_REASON }) @@ -924,6 +922,7 @@ pub(crate) async fn reserve_agent_workspace_repair_dispatch( target_identity: GitTargetIdentity, attempt: AgentWorkspaceRepairAttempt, run_id: AgentRunId, + runtime_conversation_id: Option, summary: &str, auto_merge_current: Option, ) -> AppResult { @@ -1011,6 +1010,7 @@ pub(crate) async fn reserve_agent_workspace_repair_dispatch( expected_phase: AgentWorkspaceRepairPhase::Requested, expected_updated_at: attempt.updated_at, run_id: run_id.clone(), + runtime_conversation_id, updated_at: next_transition_at(Some(attempt.updated_at)), }) .await?; diff --git a/src-tauri/src/application/agent_workspace_publish_repair_state_tests.rs b/src-tauri/src/application/agent_workspace_publish_repair_state_tests.rs index 07f4047b99..23d545d489 100644 --- a/src-tauri/src/application/agent_workspace_publish_repair_state_tests.rs +++ b/src-tauri/src/application/agent_workspace_publish_repair_state_tests.rs @@ -7,9 +7,9 @@ use crate::application::agent_conversation_workspace::resolve_agent_conversation use crate::application::agent_workspace_publish_repair_state::{ abort_agent_workspace_pr_fix_review_handoff, block_agent_workspace_pr_fix_claim, block_agent_workspace_repair_needs_human, claim_agent_workspace_repair, - classify_agent_workspace_repair_completion_authority, - classify_agent_workspace_repair_delivery, complete_agent_workspace_pr_fix_claim, - complete_agent_workspace_repair_claim, continue_agent_workspace_repair_at_boundary, + classify_agent_workspace_repair_completion_authority, classify_agent_workspace_repair_delivery, + complete_agent_workspace_pr_fix_claim, complete_agent_workspace_repair_claim, + continue_agent_workspace_repair_at_boundary, continue_agent_workspace_repair_at_boundary_with_review_starter, current_agent_workspace_repair_claim_for_completion, inspect_agent_workspace_repair_completion, reconcile_active_agent_workspace_repair, @@ -923,6 +923,7 @@ async fn dispatch_refuses_to_replace_an_open_external_effect() { target.clone(), attempt, AgentRunId::from_string("repair-dispatch-open-effect-run"), + None, "dispatch repair", None, ) @@ -967,6 +968,7 @@ async fn dispatch_reservation_releases_target_authority_for_stale_and_missing_ge target_identity.clone(), not_due, AgentRunId::from_string("not-due-dispatch-run"), + None, "not due", None, ) @@ -1009,6 +1011,7 @@ async fn dispatch_reservation_releases_target_authority_for_stale_and_missing_ge target_identity.clone(), attempt, AgentRunId::from_string("stale-dispatch-run"), + None, "stale dispatch", None, ) @@ -1045,6 +1048,7 @@ async fn dispatch_reservation_releases_target_authority_for_stale_and_missing_ge target_identity.clone(), missing, AgentRunId::from_string("missing-dispatch-run"), + None, "missing dispatch", None, ) @@ -1780,6 +1784,7 @@ async fn verified_base_advance_updates_one_active_generation_without_replacing_r expected_phase: AgentWorkspaceRepairPhase::Requested, expected_updated_at: started.updated_at, run_id: run_id.clone(), + runtime_conversation_id: None, updated_at: chrono::Utc::now(), }; repair_repo.bind_repair_attempt_run(bound).await.unwrap(); @@ -1842,6 +1847,7 @@ async fn blocked_retry_coalesces_to_one_successor_generation_and_projects_reques target_identity.clone(), started, AgentRunId::from_string("repair-attempt-blocked-retry-run"), + None, "dispatching repair", None, ) @@ -1961,6 +1967,7 @@ async fn retryable_dispatch_failure_persists_one_due_retry_and_blocks_not_due_re target_identity.clone(), started, AgentRunId::from_string("repair-dispatch-due-retry-first"), + None, "dispatch repair", None, ) @@ -2006,6 +2013,7 @@ async fn retryable_dispatch_failure_persists_one_due_retry_and_blocks_not_due_re target_identity, scheduled.clone(), AgentRunId::from_string("repair-dispatch-due-retry-replay"), + None, "must not dispatch before due", None, ) @@ -2084,6 +2092,7 @@ async fn immediate_start_rejection_defers_recovery_redelivery_without_consuming_ target_identity.clone(), current, AgentRunId::from_string(format!("repair-dispatch-immediate-start-{delivery}")), + None, "dispatch busy repair", None, ) @@ -2206,6 +2215,7 @@ async fn exhausted_or_nonretryable_dispatch_failure_blocks_once_and_releases_lea target_identity.clone(), current, AgentRunId::from_string(format!("repair-dispatch-exhaustion-{retry}")), + None, "dispatch repair", None, ) @@ -2333,6 +2343,7 @@ async fn foreign_canonical_target_owner_rejects_repair_dispatch_before_run_bindi target_identity.clone(), started.clone(), AgentRunId::from_string("repair-dispatch-foreign-owner-run"), + None, "dispatching repair", None, ) @@ -2396,6 +2407,7 @@ async fn exact_run_authority_distinguishes_current_stale_completed_and_blocked_a expected_phase: AgentWorkspaceRepairPhase::Requested, expected_updated_at: attempt.updated_at, run_id: owner_run.clone(), + runtime_conversation_id: None, updated_at: chrono::Utc::now(), }, ) @@ -2478,6 +2490,7 @@ async fn exact_run_authority_distinguishes_current_stale_completed_and_blocked_a expected_phase: AgentWorkspaceRepairPhase::Requested, expected_updated_at: successor.updated_at, run_id: successor_run.clone(), + runtime_conversation_id: None, updated_at: successor.updated_at + chrono::Duration::microseconds(1), }, ) @@ -3126,7 +3139,10 @@ async fn reserve_pre_existing_on_base_settles_to_ready_with_marker() { .contains(&PRE_EXISTING_ON_BASE_REPAIR_REASON.to_string()), "the pre-existing-on-base marker must be persisted" ); - assert!(settled.blocker.is_none(), "Ready phase must not carry a blocker"); + assert!( + settled.blocker.is_none(), + "Ready phase must not carry a blocker" + ); } #[tokio::test] diff --git a/src-tauri/src/application/agent_workspace_review.rs b/src-tauri/src/application/agent_workspace_review.rs index ce16a8023b..0451941b96 100644 --- a/src-tauri/src/application/agent_workspace_review.rs +++ b/src-tauri/src/application/agent_workspace_review.rs @@ -15,6 +15,10 @@ use tracing::{error, info, warn}; use crate::application::agent_plan_context::{ load_linked_workspace_plan_snapshot, merge_authoritative_plan_references, }; +use crate::application::agent_workspace_fixer_conversation::{ + ensure_agent_workspace_fixer_conversation, AgentWorkspaceFixerKind, + AgentWorkspaceFixerTitleContext, +}; use crate::application::agent_workspace_review_base::resolve_agent_workspace_review_base; use crate::application::chat_service::{ ChatService, SendCallerContext, SendMessageOptions, SendQueuePolicy, @@ -2492,6 +2496,16 @@ pub(crate) async fn complete_agent_workspace_review_run_unlocked( Some(WORKSPACE_REVIEW_FIXER_STATUS_ROUTING.to_string()); monitor.review_fixer_attempt_id = Some(uuid::Uuid::new_v4().to_string()); clear_review_fixer_linkage(&mut monitor); + monitor.review_fixer_conversation_id = Some( + ensure_agent_workspace_fixer_conversation( + state, + workspace, + None, + AgentWorkspaceFixerKind::WorkspaceRepair, + AgentWorkspaceFixerTitleContext::ReviewBlocking, + ) + .await?, + ); } } AgentWorkspaceReviewOutcome::NoChanges if target.is_none() => { @@ -3179,6 +3193,27 @@ async fn route_workspace_review_blocking_fixer_with_chat_service conversation_id, + Err(error) => { + next.review_fixer_status = Some(WORKSPACE_REVIEW_FIXER_STATUS_FAILED.to_string()); + next.last_error = Some(format!("Failed to create Review fixer child: {error}")); + return settle_workspace_review_fixer_attempt(state, next, monitor).await; + } + }; + next.review_fixer_conversation_id = Some(fixer_conversation_id); + let mut next = state + .agent_conversation_workspace_repo + .upsert_workspace_review_monitor(next) + .await?; let preserve_conversation_provider_session_ref = true; let send_started = Instant::now(); match chat_service @@ -3187,7 +3222,7 @@ async fn route_workspace_review_blocking_fixer_with_chat_service { + if result.conversation_id != fixer_conversation_id.as_str() { + next.review_fixer_status = Some(WORKSPACE_REVIEW_FIXER_STATUS_FAILED.to_string()); + next.last_error = Some( + "Workspace Review fixer launch did not preserve its reserved child conversation" + .to_string(), + ); + return settle_workspace_review_fixer_attempt(state, next, monitor).await; + } next.review_fixer_status = Some(if result.was_queued || result.queued_as_pending { WORKSPACE_REVIEW_FIXER_STATUS_QUEUED.to_string() } else { @@ -3231,8 +3274,6 @@ async fn route_workspace_review_blocking_fixer_with_chat_service( target: &AgentConversationWorkspaceRepairTarget, post_repair_action: AgentWorkspacePostRepairAction, preallocated_agent_run_id: Option, + runtime_conversation_id: &ChatConversationId, ) -> Result where S: ChatService + ?Sized, @@ -9000,7 +9004,7 @@ where SendMessageOptions { preallocated_agent_run_id, queue_policy: SendQueuePolicy::RequireImmediateStart, - conversation_id_override: Some(workspace.conversation_id), + conversation_id_override: Some(*runtime_conversation_id), agent_name_override: Some(AGENT_WORKSPACE_REPAIR.to_string()), harness_override: runtime_overrides.harness, model_override: runtime_overrides.model, @@ -9208,9 +9212,7 @@ where /// Delivery retries append `auto_retry_*` streak markers to the durable reason history. Those /// markers describe recovery bookkeeping rather than the publish failure the repair agent needs. -fn last_meaningful_agent_workspace_repair_reason( - attempt: &AgentWorkspaceRepairAttempt, -) -> &str { +fn last_meaningful_agent_workspace_repair_reason(attempt: &AgentWorkspaceRepairAttempt) -> &str { attempt .pending_reasons .iter() @@ -9356,6 +9358,25 @@ async fn mark_agent_workspace_failure_with_routing_and_action_classified( return; } }; + let runtime_conversation_id = match ensure_agent_workspace_fixer_conversation( + state, + workspace, + attempt.runtime_conversation_id.as_ref(), + AgentWorkspaceFixerKind::WorkspaceRepair, + AgentWorkspaceFixerTitleContext::Repair(attempt.source), + ) + .await + { + Ok(conversation_id) => conversation_id, + Err(error) => { + tracing::warn!( + conversation_id = %workspace.conversation_id, + error = %error, + "Failed to create workspace repair child conversation before dispatch" + ); + return; + } + }; let runtime_overrides = AgentWorkspaceRepairRuntimeOverrides::default(); if should_defer_agent_workspace_repair_message(state, workspace).await { let repair_run_id = AgentRunId::new(); @@ -9365,6 +9386,7 @@ async fn mark_agent_workspace_failure_with_routing_and_action_classified( dispatch_target.clone(), attempt, repair_run_id.clone(), + Some(runtime_conversation_id), post_repair_action.repair_requested_summary(), workspace.pr_auto_merge_current, ) @@ -9407,6 +9429,7 @@ async fn mark_agent_workspace_failure_with_routing_and_action_classified( dispatch_target, attempt, repair_run_id.clone(), + Some(runtime_conversation_id), post_repair_action.repair_requested_summary(), workspace.pr_auto_merge_current, ) @@ -9437,13 +9460,16 @@ async fn mark_agent_workspace_failure_with_routing_and_action_classified( target, post_repair_action, Some(repair_run_id.clone()), + dispatch.runtime_conversation_id(), ) .await { Ok(result) => { - if let Some(authority_error) = - repair_dispatch_authority_error(&result, &workspace.conversation_id, &repair_run_id) - { + if let Some(authority_error) = repair_dispatch_authority_error( + &result, + dispatch.runtime_conversation_id(), + &repair_run_id, + ) { let repair_summary = post_repair_action.repair_send_failed_summary(&authority_error); settle_agent_workspace_repair_dispatch_failure( @@ -9452,7 +9478,7 @@ async fn mark_agent_workspace_failure_with_routing_and_action_classified( &repair_summary, classify_agent_workspace_repair_delivery( Ok(&result), - &workspace.conversation_id, + dispatch.runtime_conversation_id(), &repair_run_id, ), ) @@ -9480,7 +9506,7 @@ async fn mark_agent_workspace_failure_with_routing_and_action_classified( &repair_summary, classify_agent_workspace_repair_delivery( Err(&repair_error), - &workspace.conversation_id, + dispatch.runtime_conversation_id(), &repair_run_id, ), ) @@ -9635,13 +9661,16 @@ async fn spawn_deferred_agent_workspace_repair_message( &target, post_repair_action, Some(repair_run_id.clone()), + dispatch.runtime_conversation_id(), ) .await { Ok(result) => { - if let Some(authority_error) = - repair_dispatch_authority_error(&result, &conversation_id, &repair_run_id) - { + if let Some(authority_error) = repair_dispatch_authority_error( + &result, + dispatch.runtime_conversation_id(), + &repair_run_id, + ) { let repair_summary = post_repair_action.repair_send_failed_summary(&authority_error); settle_agent_workspace_repair_dispatch_failure( @@ -9650,7 +9679,7 @@ async fn spawn_deferred_agent_workspace_repair_message( &repair_summary, classify_agent_workspace_repair_delivery( Ok(&result), - &conversation_id, + dispatch.runtime_conversation_id(), &repair_run_id, ), ) @@ -9678,7 +9707,7 @@ async fn spawn_deferred_agent_workspace_repair_message( &repair_summary, classify_agent_workspace_repair_delivery( Err(&repair_error), - &conversation_id, + dispatch.runtime_conversation_id(), &repair_run_id, ), ) diff --git a/src-tauri/src/infrastructure/memory/memory_agent_conversation_workspace_repo/repair_attempt_fencing_tests.rs b/src-tauri/src/infrastructure/memory/memory_agent_conversation_workspace_repo/repair_attempt_fencing_tests.rs index 599b60056c..3435114838 100644 --- a/src-tauri/src/infrastructure/memory/memory_agent_conversation_workspace_repo/repair_attempt_fencing_tests.rs +++ b/src-tauri/src/infrastructure/memory/memory_agent_conversation_workspace_repo/repair_attempt_fencing_tests.rs @@ -82,6 +82,7 @@ async fn settled_and_cross_conversation_attempts_cannot_transition_or_bind_runs( expected_phase: settled.phase, expected_updated_at: settled.updated_at, run_id: crate::domain::entities::AgentRunId::from_string("settled-memory-run"), + runtime_conversation_id: None, updated_at: settled.updated_at + Duration::seconds(2), }) .await diff --git a/src-tauri/src/infrastructure/memory/memory_agent_conversation_workspace_repo/repair_attempts.rs b/src-tauri/src/infrastructure/memory/memory_agent_conversation_workspace_repo/repair_attempts.rs index 73ec5c4485..eafd3d2aff 100644 --- a/src-tauri/src/infrastructure/memory/memory_agent_conversation_workspace_repo/repair_attempts.rs +++ b/src-tauri/src/infrastructure/memory/memory_agent_conversation_workspace_repo/repair_attempts.rs @@ -149,6 +149,22 @@ impl AgentWorkspaceRepairRepository for MemoryAgentConversationWorkspaceReposito .cloned()) } + async fn get_unsettled_attempt_by_runtime_conversation( + &self, + runtime_conversation_id: &ChatConversationId, + ) -> AppResult> { + Ok(self + .repair_attempts + .read() + .await + .values() + .find(|attempt| { + attempt.runtime_conversation_id.as_ref() == Some(runtime_conversation_id) + && attempt.settled_at.is_none() + }) + .cloned()) + } + async fn get_latest_repair_attempt_for_conversation( &self, conversation_id: &ChatConversationId, @@ -326,6 +342,9 @@ impl AgentWorkspaceRepairRepository for MemoryAgentConversationWorkspaceReposito .get_mut(&request.attempt_id) .expect("repair attempt existed while the write lock was held"); current.reserved_agent_run_id = Some(request.run_id); + if current.runtime_conversation_id.is_none() { + current.runtime_conversation_id = request.runtime_conversation_id; + } current.updated_at = request.updated_at; Ok(AgentWorkspaceRepairAttemptTransitionOutcome::Applied( current.clone(), @@ -516,7 +535,9 @@ impl AgentWorkspaceRepairRepository for MemoryAgentConversationWorkspaceReposito || current.updated_at != request.expected_attempt_updated_at || current.settled_at.is_some() { - return Ok(CreateAgentWorkspaceRepairEffectOutcome::Stale(Box::new(current))); + return Ok(CreateAgentWorkspaceRepairEffectOutcome::Stale(Box::new( + current, + ))); } if request.effect.attempt_id != current.id { return Err(AppError::Validation( @@ -628,7 +649,9 @@ impl AgentWorkspaceRepairRepository for MemoryAgentConversationWorkspaceReposito )); } if existing.completed_at.is_some() && existing == request.effect { - return Ok(CompleteAgentWorkspaceRepairEffectOutcome::Applied(Box::new(existing))); + return Ok(CompleteAgentWorkspaceRepairEffectOutcome::Applied( + Box::new(existing), + )); } if current.generation != request.generation || current.phase != request.expected_phase @@ -637,7 +660,9 @@ impl AgentWorkspaceRepairRepository for MemoryAgentConversationWorkspaceReposito || existing.updated_at != request.expected_effect_updated_at || existing.status != request.expected_effect_status { - return Ok(CompleteAgentWorkspaceRepairEffectOutcome::Stale(Box::new(current))); + return Ok(CompleteAgentWorkspaceRepairEffectOutcome::Stale(Box::new( + current, + ))); } let workspace = workspaces .get_mut(¤t.conversation_id) diff --git a/src-tauri/src/infrastructure/memory/memory_agent_conversation_workspace_repo/repair_attempts_tests.rs b/src-tauri/src/infrastructure/memory/memory_agent_conversation_workspace_repo/repair_attempts_tests.rs index 13022d78d2..fe13b025e6 100644 --- a/src-tauri/src/infrastructure/memory/memory_agent_conversation_workspace_repo/repair_attempts_tests.rs +++ b/src-tauri/src/infrastructure/memory/memory_agent_conversation_workspace_repo/repair_attempts_tests.rs @@ -16,7 +16,7 @@ use crate::domain::repositories::{ CompleteAgentWorkspaceRepairEffect, CompleteAgentWorkspaceRepairEffectOutcome, CreateAgentWorkspaceRepairEffect, CreateAgentWorkspaceRepairEffectOutcome, ImportLegacyAgentWorkspaceRepairAttempt, ImportLegacyAgentWorkspaceRepairAttemptOutcome, - SettleAndStartAgentWorkspaceRepairSuccessor, + SettleAgentWorkspaceRepairAttempt, SettleAndStartAgentWorkspaceRepairSuccessor, SettleAndStartAgentWorkspaceRepairSuccessorOutcome, StartOrJoinAgentWorkspaceRepairAttempt, StartOrJoinAgentWorkspaceRepairAttemptOutcome, }; @@ -74,6 +74,7 @@ async fn bind_repair_run_rejects_a_stale_same_phase_snapshot() { expected_phase: AgentWorkspaceRepairPhase::Requested, expected_updated_at: stale.updated_at, run_id: AgentRunId::from_string("stale-memory-repair-run"), + runtime_conversation_id: None, updated_at: stale.updated_at + Duration::seconds(1), }) .await @@ -174,6 +175,140 @@ fn repair_attempt(conversation_id: ChatConversationId) -> AgentWorkspaceRepairAt ) } +#[tokio::test] +async fn runtime_conversation_id_round_trips_when_set_or_unset() { + let repo = MemoryAgentConversationWorkspaceRepository::new(); + let conversation_id = ChatConversationId::from_string("repair-runtime-memory"); + repo.create_or_update(workspace(conversation_id.clone())) + .await + .expect("persist workspace"); + let runtime_conversation_id = ChatConversationId::from_string("runtime-child-memory"); + let mut configured = repair_attempt(conversation_id.clone()); + configured.runtime_conversation_id = Some(runtime_conversation_id.clone()); + let started = match repo + .start_or_join_repair_attempt(StartOrJoinAgentWorkspaceRepairAttempt { + attempt: configured, + reason: "runtime conversation".to_string(), + verified_newer_base: false, + compatibility_projection: None, + events: Vec::new(), + }) + .await + .expect("start repair attempt") + { + StartOrJoinAgentWorkspaceRepairAttemptOutcome::Started(attempt) => attempt, + outcome => panic!("expected started repair attempt, got {outcome:?}"), + }; + let persisted = repo + .get_repair_attempt(&started.id) + .await + .expect("reload configured repair attempt") + .expect("configured repair attempt exists"); + assert_eq!( + persisted.runtime_conversation_id, + Some(runtime_conversation_id.clone()) + ); + assert_eq!( + persisted.runtime_conversation_id(), + &runtime_conversation_id + ); + + let settled_at = persisted.updated_at + Duration::seconds(1); + repo.settle_repair_attempt(SettleAgentWorkspaceRepairAttempt { + attempt_id: persisted.id, + generation: persisted.generation, + expected_phase: persisted.phase, + expected_updated_at: persisted.updated_at, + outcome: AgentWorkspaceRepairOutcome::Succeeded, + settled_at, + compatibility_projection: None, + events: Vec::new(), + }) + .await + .expect("settle configured repair attempt"); + + let next = match repo + .start_or_join_repair_attempt(StartOrJoinAgentWorkspaceRepairAttempt { + attempt: repair_attempt(conversation_id.clone()), + reason: "legacy runtime fallback".to_string(), + verified_newer_base: false, + compatibility_projection: None, + events: Vec::new(), + }) + .await + .expect("start repair attempt without runtime conversation") + { + StartOrJoinAgentWorkspaceRepairAttemptOutcome::Started(attempt) => attempt, + outcome => panic!("expected started repair attempt, got {outcome:?}"), + }; + let persisted_next = repo + .get_repair_attempt(&next.id) + .await + .expect("reload legacy repair attempt") + .expect("legacy repair attempt exists"); + assert_eq!(persisted_next.runtime_conversation_id, None); + assert_eq!(persisted_next.runtime_conversation_id(), &conversation_id); +} + +#[tokio::test] +async fn lookup_by_runtime_conversation_only_returns_unsettled_attempts() { + let repo = MemoryAgentConversationWorkspaceRepository::new(); + let conversation_id = ChatConversationId::from_string("lookup-runtime-memory"); + repo.create_or_update(workspace(conversation_id.clone())) + .await + .expect("persist workspace"); + let runtime_conversation_id = ChatConversationId::from_string("runtime-lookup-memory"); + let mut configured = repair_attempt(conversation_id); + configured.runtime_conversation_id = Some(runtime_conversation_id.clone()); + let started = match repo + .start_or_join_repair_attempt(StartOrJoinAgentWorkspaceRepairAttempt { + attempt: configured, + reason: "runtime lookup".to_string(), + verified_newer_base: false, + compatibility_projection: None, + events: Vec::new(), + }) + .await + .expect("start repair attempt") + { + StartOrJoinAgentWorkspaceRepairAttemptOutcome::Started(attempt) => attempt, + outcome => panic!("expected started repair attempt, got {outcome:?}"), + }; + assert_eq!( + repo.get_unsettled_attempt_by_runtime_conversation(&runtime_conversation_id) + .await + .expect("look up active runtime conversation") + .map(|attempt| attempt.id), + Some(started.id.clone()) + ); + + let settled_at = started.updated_at + Duration::seconds(1); + repo.settle_repair_attempt(SettleAgentWorkspaceRepairAttempt { + attempt_id: started.id, + generation: started.generation, + expected_phase: started.phase, + expected_updated_at: started.updated_at, + outcome: AgentWorkspaceRepairOutcome::Succeeded, + settled_at, + compatibility_projection: None, + events: Vec::new(), + }) + .await + .expect("settle repair attempt"); + assert!(repo + .get_unsettled_attempt_by_runtime_conversation(&runtime_conversation_id) + .await + .expect("settled runtime conversation is no longer authorized") + .is_none()); + assert!(repo + .get_unsettled_attempt_by_runtime_conversation(&ChatConversationId::from_string( + "unknown-runtime-memory", + )) + .await + .expect("unknown runtime conversation lookup") + .is_none()); +} + fn publication_event( conversation_id: ChatConversationId, step: &str, diff --git a/src-tauri/src/infrastructure/sqlite/migrations/mod.rs b/src-tauri/src/infrastructure/sqlite/migrations/mod.rs index bb9d1b74df..7d3d0bd291 100644 --- a/src-tauri/src/infrastructure/sqlite/migrations/mod.rs +++ b/src-tauri/src/infrastructure/sqlite/migrations/mod.rs @@ -579,6 +579,7 @@ mod v20260730161032_agent_workspace_pr_autofix_completion_evidence; mod v20260731023949_agent_run_identity; mod v20260731111346_purge_empty_thinking_blocks; mod v20260731125157_add_workspace_repair_fingerprint_state; +mod v20260731170447_agent_workspace_repair_runtime_conversation; #[cfg(test)] mod v20260730161032_agent_workspace_pr_autofix_completion_evidence_tests; #[cfg(test)] @@ -588,6 +589,8 @@ mod v20260731111346_purge_empty_thinking_blocks_tests; #[cfg(test)] mod v20260731125157_add_workspace_repair_fingerprint_state_tests; #[cfg(test)] +mod v20260731170447_agent_workspace_repair_runtime_conversation_tests; +#[cfg(test)] pub(super) fn migrate_scripted_agent_workflows_for_test(conn: &Connection) -> AppResult<()> { v20260715194617_scripted_agent_workflows::migrate(conn) } @@ -681,7 +684,7 @@ mod v8_task_git_fields_tests; mod v9_project_git_fields_tests; /// Current schema version - bump this when adding a new migration -pub const SCHEMA_VERSION: i64 = 20260731125157; +pub const SCHEMA_VERSION: i64 = 20260731170447; /// Migration function signature type MigrationFn = fn(&Connection) -> AppResult<()>; @@ -1872,6 +1875,11 @@ const MIGRATIONS: &[Migration] = &[ name: "add_workspace_repair_fingerprint_state", migrate: v20260731125157_add_workspace_repair_fingerprint_state::migrate, }, + Migration { + version: 20260731170447, + name: "agent_workspace_repair_runtime_conversation", + migrate: v20260731170447_agent_workspace_repair_runtime_conversation::migrate, + }, ]; #[derive(Debug, Clone, Copy, PartialEq, Eq)] diff --git a/src-tauri/src/infrastructure/sqlite/migrations/v20260731170447_agent_workspace_repair_runtime_conversation.rs b/src-tauri/src/infrastructure/sqlite/migrations/v20260731170447_agent_workspace_repair_runtime_conversation.rs new file mode 100644 index 0000000000..1662141d8e --- /dev/null +++ b/src-tauri/src/infrastructure/sqlite/migrations/v20260731170447_agent_workspace_repair_runtime_conversation.rs @@ -0,0 +1,35 @@ +// Migration v20260731170447: agent workspace repair runtime conversation +// +// Fixer agents (workspace repair and PR autofix) used to run inside the parent workspace +// conversation, which made follow-up user messages resolve to project chat instead of the fixer. +// Each repair attempt now owns a dedicated child conversation. Recording that child on the attempt +// is what lets completion authority map a child runtime back to the workspace that owns it without +// trusting `parent_conversation_id`. +// +// NULL is the durable marker for a legacy, parent-hosted attempt. There is deliberately no backfill. + +use rusqlite::Connection; + +use crate::error::AppResult; + +use super::helpers; + +pub fn migrate(conn: &Connection) -> AppResult<()> { + helpers::add_column_if_not_exists( + conn, + "agent_workspace_repair_attempts", + "runtime_conversation_id", + "TEXT NULL", + )?; + + helpers::create_index_if_not_exists( + conn, + "idx_agent_workspace_repair_attempts_runtime_conversation", + "agent_workspace_repair_attempts", + "runtime_conversation_id", + )?; + + tracing::info!("Migration v20260731170447: repair attempt runtime conversation ready"); + + Ok(()) +} diff --git a/src-tauri/src/infrastructure/sqlite/migrations/v20260731170447_agent_workspace_repair_runtime_conversation_tests.rs b/src-tauri/src/infrastructure/sqlite/migrations/v20260731170447_agent_workspace_repair_runtime_conversation_tests.rs new file mode 100644 index 0000000000..5364badaa9 --- /dev/null +++ b/src-tauri/src/infrastructure/sqlite/migrations/v20260731170447_agent_workspace_repair_runtime_conversation_tests.rs @@ -0,0 +1,92 @@ +//! Tests for migration v20260731170447: agent workspace repair runtime conversation + +use rusqlite::Connection; + +use super::v20260731170447_agent_workspace_repair_runtime_conversation::migrate; + +fn setup_test_db() -> Connection { + let conn = Connection::open_in_memory().expect("Failed to create in-memory database"); + conn.execute_batch( + "CREATE TABLE agent_workspace_repair_attempts (\ + attempt_id TEXT PRIMARY KEY, \ + conversation_id TEXT NOT NULL, \ + generation INTEGER NOT NULL DEFAULT 0, \ + settled_at TEXT NULL\ + );", + ) + .expect("seed agent workspace repair attempts table"); + conn +} + +fn columns(conn: &Connection) -> Vec { + conn.prepare("PRAGMA table_info(agent_workspace_repair_attempts)") + .expect("prepare table info") + .query_map([], |row| row.get(1)) + .expect("query table info") + .collect::>() + .expect("read table info") +} + +fn indexes(conn: &Connection) -> Vec { + conn.prepare("PRAGMA index_list(agent_workspace_repair_attempts)") + .expect("prepare index list") + .query_map([], |row| row.get(1)) + .expect("query index list") + .collect::>() + .expect("read index list") +} + +#[test] +fn migration_adds_runtime_conversation_column() { + let conn = setup_test_db(); + migrate(&conn).expect("migration should add the runtime conversation column"); + assert!(columns(&conn).contains(&"runtime_conversation_id".to_string())); +} + +#[test] +fn migration_adds_runtime_conversation_lookup_index() { + let conn = setup_test_db(); + migrate(&conn).expect("migration should add the runtime conversation index"); + assert!(indexes(&conn) + .contains(&"idx_agent_workspace_repair_attempts_runtime_conversation".to_string())); +} + +#[test] +fn migration_is_idempotent_on_already_upgraded_databases() { + let conn = setup_test_db(); + migrate(&conn).expect("first run should add the column and index"); + migrate(&conn).expect("second run must not fail on existing column or index"); + assert_eq!( + columns(&conn) + .iter() + .filter(|name| name.as_str() == "runtime_conversation_id") + .count(), + 1 + ); +} + +#[test] +fn existing_attempts_stay_parent_hosted() { + let conn = setup_test_db(); + conn.execute( + "INSERT INTO agent_workspace_repair_attempts (attempt_id, conversation_id) \ + VALUES ('attempt-1', 'workspace-conversation-1')", + [], + ) + .expect("seed an in-flight attempt from before the upgrade"); + + migrate(&conn).expect("migration should add the runtime conversation column"); + + let runtime_conversation_id: Option = conn + .query_row( + "SELECT runtime_conversation_id FROM agent_workspace_repair_attempts \ + WHERE attempt_id = 'attempt-1'", + [], + |row| row.get(0), + ) + .expect("read the new column for the pre-existing attempt"); + assert!( + runtime_conversation_id.is_none(), + "in-flight attempts must stay NULL so they keep resolving as legacy parent-hosted runs" + ); +} diff --git a/src-tauri/src/infrastructure/sqlite/sqlite_agent_conversation_workspace_repo/repair_attempt_fencing_tests.rs b/src-tauri/src/infrastructure/sqlite/sqlite_agent_conversation_workspace_repo/repair_attempt_fencing_tests.rs index fcfbf82cc5..a1eef62d47 100644 --- a/src-tauri/src/infrastructure/sqlite/sqlite_agent_conversation_workspace_repo/repair_attempt_fencing_tests.rs +++ b/src-tauri/src/infrastructure/sqlite/sqlite_agent_conversation_workspace_repo/repair_attempt_fencing_tests.rs @@ -89,6 +89,7 @@ async fn settled_and_cross_conversation_attempts_cannot_transition_or_bind_runs( expected_phase: settled.phase, expected_updated_at: settled.updated_at, run_id: crate::domain::entities::AgentRunId::from_string("settled-sqlite-run"), + runtime_conversation_id: None, updated_at: settled.updated_at + Duration::seconds(2), }) .await diff --git a/src-tauri/src/infrastructure/sqlite/sqlite_agent_conversation_workspace_repo/repair_attempts.rs b/src-tauri/src/infrastructure/sqlite/sqlite_agent_conversation_workspace_repo/repair_attempts.rs index c7bbf708e9..d8f5b40b2c 100644 --- a/src-tauri/src/infrastructure/sqlite/sqlite_agent_conversation_workspace_repo/repair_attempts.rs +++ b/src-tauri/src/infrastructure/sqlite/sqlite_agent_conversation_workspace_repo/repair_attempts.rs @@ -55,6 +55,9 @@ fn row_to_repair_attempt(row: &rusqlite::Row<'_>) -> rusqlite::Result>("reserved_agent_run_id")? .map(crate::domain::entities::AgentRunId::from_string), + runtime_conversation_id: row + .get::<_, Option>("runtime_conversation_id")? + .map(ChatConversationId::from_string), target_base_ref: row.get("target_base_ref")?, target_base_commit: row.get("target_base_commit")?, pending_reasons: serde_json::from_str(&pending_reasons_json) @@ -172,6 +175,21 @@ fn load_current_repair_attempt( .map_err(Into::into) } +fn load_unsettled_repair_attempt_by_runtime_conversation( + conn: &Connection, + runtime_conversation_id: &str, +) -> AppResult> { + conn.query_row( + "SELECT * FROM agent_workspace_repair_attempts + WHERE runtime_conversation_id = ?1 AND settled_at IS NULL + LIMIT 1", + rusqlite::params![runtime_conversation_id], + row_to_repair_attempt, + ) + .optional() + .map_err(Into::into) +} + fn load_latest_repair_attempt( conn: &Connection, conversation_id: &str, @@ -192,7 +210,8 @@ fn write_repair_attempt(conn: &Connection, attempt: &AgentWorkspaceRepairAttempt conn.execute( "INSERT INTO agent_workspace_repair_attempts ( id, conversation_id, generation, source, phase, continuation, - reserved_agent_run_id, target_base_ref, target_base_commit, pending_reasons_json, + reserved_agent_run_id, runtime_conversation_id, target_base_ref, target_base_commit, + pending_reasons_json, review_required, auto_publish_enabled, auto_merge_desired, auto_merge_method, dispatch_count, next_dispatch_at, ci_rerun_count, ci_rerun_fingerprint, pr_autofix_dispatch_head_commit, pr_autofix_health_fingerprint, @@ -201,7 +220,7 @@ fn write_repair_attempt(conn: &Connection, attempt: &AgentWorkspaceRepairAttempt created_at, updated_at, settled_at ) VALUES ( ?1, ?2, ?3, ?4, ?5, ?6, ?7, ?8, ?9, ?10, ?11, ?12, ?13, ?14, ?15, - ?16, ?17, ?18, ?19, ?20, ?21, ?22, ?23, ?24, ?25, ?26, ?27, ?28, ?29, ?30, ?31 + ?16, ?17, ?18, ?19, ?20, ?21, ?22, ?23, ?24, ?25, ?26, ?27, ?28, ?29, ?30, ?31, ?32 )", rusqlite::params![ attempt.id.as_str(), @@ -213,6 +232,10 @@ fn write_repair_attempt(conn: &Connection, attempt: &AgentWorkspaceRepairAttempt attempt.phase.to_string(), attempt.continuation.to_string(), attempt.reserved_agent_run_id.as_ref().map(|id| id.as_str()), + attempt + .runtime_conversation_id + .as_ref() + .map(|id| id.as_str()), attempt.target_base_ref, attempt.target_base_commit, serde_json::to_string(&attempt.pending_reasons) @@ -271,32 +294,33 @@ fn update_repair_attempt( SET phase = ?4, continuation = ?5, reserved_agent_run_id = ?6, - target_base_ref = ?7, - target_base_commit = ?8, - pending_reasons_json = ?9, - review_required = ?10, - auto_publish_enabled = ?11, - auto_merge_desired = ?12, - auto_merge_method = ?13, - dispatch_count = ?14, - next_dispatch_at = ?15, - ci_rerun_count = ?16, - ci_rerun_fingerprint = ?17, - pr_autofix_dispatch_head_commit = ?18, - pr_autofix_health_fingerprint = ?19, - repair_head_commit = ?20, - summary = ?21, - blocker = ?22, - git_common_dir = ?23, - target_ref = ?24, - target_identity_version = ?25, - target_lease_epoch = ?26, - outcome = ?27, - updated_at = ?28, - settled_at = ?29 + runtime_conversation_id = ?7, + target_base_ref = ?8, + target_base_commit = ?9, + pending_reasons_json = ?10, + review_required = ?11, + auto_publish_enabled = ?12, + auto_merge_desired = ?13, + auto_merge_method = ?14, + dispatch_count = ?15, + next_dispatch_at = ?16, + ci_rerun_count = ?17, + ci_rerun_fingerprint = ?18, + pr_autofix_dispatch_head_commit = ?19, + pr_autofix_health_fingerprint = ?20, + repair_head_commit = ?21, + summary = ?22, + blocker = ?23, + git_common_dir = ?24, + target_ref = ?25, + target_identity_version = ?26, + target_lease_epoch = ?27, + outcome = ?28, + updated_at = ?29, + settled_at = ?30 WHERE id = ?1 AND generation = ?2 AND phase = ?3 - AND (?30 IS NULL OR updated_at = ?30) - AND (?31 = 0 OR settled_at IS NULL)", + AND (?31 IS NULL OR updated_at = ?31) + AND (?32 = 0 OR settled_at IS NULL)", rusqlite::params![ attempt.id.as_str(), i64::try_from(attempt.generation).map_err(|_| { @@ -306,6 +330,10 @@ fn update_repair_attempt( attempt.phase.to_string(), attempt.continuation.to_string(), attempt.reserved_agent_run_id.as_ref().map(|id| id.as_str()), + attempt + .runtime_conversation_id + .as_ref() + .map(|id| id.as_str()), attempt.target_base_ref, attempt.target_base_commit, serde_json::to_string(&attempt.pending_reasons) @@ -547,6 +575,21 @@ impl AgentWorkspaceRepairRepository for SqliteAgentConversationWorkspaceReposito .await } + async fn get_unsettled_attempt_by_runtime_conversation( + &self, + runtime_conversation_id: &ChatConversationId, + ) -> AppResult> { + let runtime_conversation_id = runtime_conversation_id.as_str().to_string(); + self.db + .run(move |conn| { + load_unsettled_repair_attempt_by_runtime_conversation( + conn, + &runtime_conversation_id, + ) + }) + .await + } + async fn get_latest_repair_attempt_for_conversation( &self, conversation_id: &ChatConversationId, @@ -730,9 +773,11 @@ impl AgentWorkspaceRepairRepository for SqliteAgentConversationWorkspaceReposito let rows = conn.execute( "UPDATE agent_workspace_repair_attempts - SET reserved_agent_run_id = ?4, updated_at = ?5 + SET reserved_agent_run_id = ?4, + runtime_conversation_id = COALESCE(runtime_conversation_id, ?5), + updated_at = ?6 WHERE id = ?1 AND generation = ?2 AND phase = ?3 - AND updated_at = ?6 AND reserved_agent_run_id IS NULL + AND updated_at = ?7 AND reserved_agent_run_id IS NULL AND settled_at IS NULL", rusqlite::params![ current.id.as_str(), @@ -743,6 +788,10 @@ impl AgentWorkspaceRepairRepository for SqliteAgentConversationWorkspaceReposito })?, request.expected_phase.to_string(), request.run_id.as_str(), + request + .runtime_conversation_id + .as_ref() + .map(|id| id.as_str()), request.updated_at.to_rfc3339(), request.expected_updated_at.to_rfc3339(), ], @@ -755,6 +804,9 @@ impl AgentWorkspaceRepairRepository for SqliteAgentConversationWorkspaceReposito return Ok(AgentWorkspaceRepairAttemptTransitionOutcome::Stale(latest)); } current.reserved_agent_run_id = Some(request.run_id); + if current.runtime_conversation_id.is_none() { + current.runtime_conversation_id = request.runtime_conversation_id; + } current.updated_at = request.updated_at; Ok(AgentWorkspaceRepairAttemptTransitionOutcome::Applied( current, @@ -947,7 +999,9 @@ impl AgentWorkspaceRepairRepository for SqliteAgentConversationWorkspaceReposito || current.updated_at != request.expected_attempt_updated_at || current.settled_at.is_some() { - return Ok(CreateAgentWorkspaceRepairEffectOutcome::Stale(Box::new(current))); + return Ok(CreateAgentWorkspaceRepairEffectOutcome::Stale(Box::new( + current, + ))); } if request.effect.attempt_id != current.id { return Err(AppError::Validation( @@ -1065,7 +1119,9 @@ impl AgentWorkspaceRepairRepository for SqliteAgentConversationWorkspaceReposito )); } if existing.completed_at.is_some() && existing == request.effect { - return Ok(CompleteAgentWorkspaceRepairEffectOutcome::Applied(Box::new(existing))); + return Ok(CompleteAgentWorkspaceRepairEffectOutcome::Applied( + Box::new(existing), + )); } if current.generation != request.generation || current.phase != request.expected_phase @@ -1074,7 +1130,9 @@ impl AgentWorkspaceRepairRepository for SqliteAgentConversationWorkspaceReposito || existing.updated_at != request.expected_effect_updated_at || existing.status != request.expected_effect_status { - return Ok(CompleteAgentWorkspaceRepairEffectOutcome::Stale(Box::new(current))); + return Ok(CompleteAgentWorkspaceRepairEffectOutcome::Stale(Box::new( + current, + ))); } if !update_repair_effect( conn, @@ -1093,9 +1151,13 @@ impl AgentWorkspaceRepairRepository for SqliteAgentConversationWorkspaceReposito AppError::NotFound(format!("repair effect {}", request.effect.id)) })?; if latest.completed_at.is_some() && latest == request.effect { - return Ok(CompleteAgentWorkspaceRepairEffectOutcome::Applied(Box::new(latest))); + return Ok(CompleteAgentWorkspaceRepairEffectOutcome::Applied( + Box::new(latest), + )); } - return Ok(CompleteAgentWorkspaceRepairEffectOutcome::Stale(Box::new(current))); + return Ok(CompleteAgentWorkspaceRepairEffectOutcome::Stale(Box::new( + current, + ))); } apply_compatibility_projection( conn, diff --git a/src-tauri/src/infrastructure/sqlite/sqlite_agent_conversation_workspace_repo/repair_attempts_tests.rs b/src-tauri/src/infrastructure/sqlite/sqlite_agent_conversation_workspace_repo/repair_attempts_tests.rs index a5130a1ae8..80d1deb241 100644 --- a/src-tauri/src/infrastructure/sqlite/sqlite_agent_conversation_workspace_repo/repair_attempts_tests.rs +++ b/src-tauri/src/infrastructure/sqlite/sqlite_agent_conversation_workspace_repo/repair_attempts_tests.rs @@ -15,7 +15,7 @@ use crate::domain::repositories::{ CompleteAgentWorkspaceRepairEffect, CompleteAgentWorkspaceRepairEffectOutcome, CreateAgentWorkspaceRepairEffect, CreateAgentWorkspaceRepairEffectOutcome, ImportLegacyAgentWorkspaceRepairAttempt, ImportLegacyAgentWorkspaceRepairAttemptOutcome, - SettleAndStartAgentWorkspaceRepairSuccessor, + SettleAgentWorkspaceRepairAttempt, SettleAndStartAgentWorkspaceRepairSuccessor, SettleAndStartAgentWorkspaceRepairSuccessorOutcome, StartOrJoinAgentWorkspaceRepairAttempt, StartOrJoinAgentWorkspaceRepairAttemptOutcome, }; @@ -78,6 +78,7 @@ async fn bind_repair_run_rejects_a_stale_same_phase_snapshot() { expected_phase: AgentWorkspaceRepairPhase::Requested, expected_updated_at: stale.updated_at, run_id: AgentRunId::from_string("stale-sqlite-repair-run"), + runtime_conversation_id: None, updated_at: stale.updated_at + Duration::seconds(1), }) .await @@ -208,6 +209,138 @@ fn repair_attempt(conversation_id: ChatConversationId) -> AgentWorkspaceRepairAt ) } +#[tokio::test] +async fn runtime_conversation_id_round_trips_when_set_or_unset() { + let (_db, repo, conversation_id) = setup_repo(); + repo.create_or_update(workspace(conversation_id.clone())) + .await + .expect("persist workspace"); + let runtime_conversation_id = ChatConversationId::from_string("repair-runtime-sqlite"); + let mut configured = repair_attempt(conversation_id.clone()); + configured.runtime_conversation_id = Some(runtime_conversation_id.clone()); + let started = match repo + .start_or_join_repair_attempt(StartOrJoinAgentWorkspaceRepairAttempt { + attempt: configured, + reason: "runtime conversation".to_string(), + verified_newer_base: false, + compatibility_projection: None, + events: Vec::new(), + }) + .await + .expect("start repair attempt") + { + StartOrJoinAgentWorkspaceRepairAttemptOutcome::Started(attempt) => attempt, + outcome => panic!("expected started repair attempt, got {outcome:?}"), + }; + let persisted = repo + .get_repair_attempt(&started.id) + .await + .expect("reload configured repair attempt") + .expect("configured repair attempt exists"); + assert_eq!( + persisted.runtime_conversation_id, + Some(runtime_conversation_id.clone()) + ); + assert_eq!( + persisted.runtime_conversation_id(), + &runtime_conversation_id + ); + + let settled_at = persisted.updated_at + Duration::seconds(1); + repo.settle_repair_attempt(SettleAgentWorkspaceRepairAttempt { + attempt_id: persisted.id, + generation: persisted.generation, + expected_phase: persisted.phase, + expected_updated_at: persisted.updated_at, + outcome: AgentWorkspaceRepairOutcome::Succeeded, + settled_at, + compatibility_projection: None, + events: Vec::new(), + }) + .await + .expect("settle configured repair attempt"); + + let next = match repo + .start_or_join_repair_attempt(StartOrJoinAgentWorkspaceRepairAttempt { + attempt: repair_attempt(conversation_id.clone()), + reason: "legacy runtime fallback".to_string(), + verified_newer_base: false, + compatibility_projection: None, + events: Vec::new(), + }) + .await + .expect("start repair attempt without runtime conversation") + { + StartOrJoinAgentWorkspaceRepairAttemptOutcome::Started(attempt) => attempt, + outcome => panic!("expected started repair attempt, got {outcome:?}"), + }; + let persisted_next = repo + .get_repair_attempt(&next.id) + .await + .expect("reload legacy repair attempt") + .expect("legacy repair attempt exists"); + assert_eq!(persisted_next.runtime_conversation_id, None); + assert_eq!(persisted_next.runtime_conversation_id(), &conversation_id); +} + +#[tokio::test] +async fn lookup_by_runtime_conversation_only_returns_unsettled_attempts() { + let (_db, repo, conversation_id) = setup_repo(); + repo.create_or_update(workspace(conversation_id.clone())) + .await + .expect("persist workspace"); + let runtime_conversation_id = ChatConversationId::from_string("lookup-runtime-sqlite"); + let mut configured = repair_attempt(conversation_id); + configured.runtime_conversation_id = Some(runtime_conversation_id.clone()); + let started = match repo + .start_or_join_repair_attempt(StartOrJoinAgentWorkspaceRepairAttempt { + attempt: configured, + reason: "runtime lookup".to_string(), + verified_newer_base: false, + compatibility_projection: None, + events: Vec::new(), + }) + .await + .expect("start repair attempt") + { + StartOrJoinAgentWorkspaceRepairAttemptOutcome::Started(attempt) => attempt, + outcome => panic!("expected started repair attempt, got {outcome:?}"), + }; + assert_eq!( + repo.get_unsettled_attempt_by_runtime_conversation(&runtime_conversation_id) + .await + .expect("look up active runtime conversation") + .map(|attempt| attempt.id), + Some(started.id.clone()) + ); + + let settled_at = started.updated_at + Duration::seconds(1); + repo.settle_repair_attempt(SettleAgentWorkspaceRepairAttempt { + attempt_id: started.id, + generation: started.generation, + expected_phase: started.phase, + expected_updated_at: started.updated_at, + outcome: AgentWorkspaceRepairOutcome::Succeeded, + settled_at, + compatibility_projection: None, + events: Vec::new(), + }) + .await + .expect("settle repair attempt"); + assert!(repo + .get_unsettled_attempt_by_runtime_conversation(&runtime_conversation_id) + .await + .expect("settled runtime conversation is no longer authorized") + .is_none()); + assert!(repo + .get_unsettled_attempt_by_runtime_conversation(&ChatConversationId::from_string( + "unknown-runtime-sqlite", + )) + .await + .expect("unknown runtime conversation lookup") + .is_none()); +} + fn publication_event( conversation_id: ChatConversationId, step: &str, diff --git a/src-tauri/tests/suite_agent_workspace/agent_workspace_repair_auto_publish.rs b/src-tauri/tests/suite_agent_workspace/agent_workspace_repair_auto_publish.rs index c0eed3e5f0..85e06a716c 100644 --- a/src-tauri/tests/suite_agent_workspace/agent_workspace_repair_auto_publish.rs +++ b/src-tauri/tests/suite_agent_workspace/agent_workspace_repair_auto_publish.rs @@ -166,6 +166,7 @@ async fn seed_current_repair_attempt( expected_phase: AgentWorkspaceRepairPhase::Requested, expected_updated_at: started.updated_at, run_id, + runtime_conversation_id: None, updated_at: chrono::Utc::now(), }) .await diff --git a/src-tauri/tests/suite_http_handlers/agent_workspace_repair_completion.rs b/src-tauri/tests/suite_http_handlers/agent_workspace_repair_completion.rs index 2e95923290..54b4cde647 100644 --- a/src-tauri/tests/suite_http_handlers/agent_workspace_repair_completion.rs +++ b/src-tauri/tests/suite_http_handlers/agent_workspace_repair_completion.rs @@ -281,6 +281,7 @@ async fn bind_current_attempt( expected_phase: AgentWorkspaceRepairPhase::Requested, expected_updated_at: started.updated_at, run_id: owner_run_id, + runtime_conversation_id: None, updated_at: Utc::now(), }) .await From 4136ce1bec995d1637731fb29f66b3c32a46ee98 Mon Sep 17 00:00:00 2001 From: lazabogdan <6580668+lazabogdan@users.noreply.github.com> Date: Sun, 2 Aug 2026 06:26:17 +0300 Subject: [PATCH 11/29] fix: resolve merge conflict compilation errors Fix borrow-after-move errors in repair dispatch by extracting runtime_conversation_id before ownership transfer. Add missing match arm for (WorkspaceRepair, PullRequest) in fixer title fn. Co-Authored-By: Claude Opus 4.6 --- .../agent_workspace_fixer_conversation.rs | 6 ++++++ src-tauri/src/commands/unified_chat_commands/mod.rs | 12 ++++++++---- 2 files changed, 14 insertions(+), 4 deletions(-) diff --git a/src-tauri/src/application/agent_workspace_fixer_conversation.rs b/src-tauri/src/application/agent_workspace_fixer_conversation.rs index e3712e5f07..b52ba48bd1 100644 --- a/src-tauri/src/application/agent_workspace_fixer_conversation.rs +++ b/src-tauri/src/application/agent_workspace_fixer_conversation.rs @@ -128,5 +128,11 @@ fn agent_workspace_fixer_conversation_title( "Fix PR conflict".to_string() } (_, AgentWorkspaceFixerTitleContext::Repair(_)) => "Fix workspace".to_string(), + (AgentWorkspaceFixerKind::WorkspaceRepair, AgentWorkspaceFixerTitleContext::PullRequest(Some(number))) => { + format!("Fix PR #{number}") + } + (AgentWorkspaceFixerKind::WorkspaceRepair, AgentWorkspaceFixerTitleContext::PullRequest(None)) => { + "Fix workspace".to_string() + } } } diff --git a/src-tauri/src/commands/unified_chat_commands/mod.rs b/src-tauri/src/commands/unified_chat_commands/mod.rs index 8db465cb67..ead214da22 100644 --- a/src-tauri/src/commands/unified_chat_commands/mod.rs +++ b/src-tauri/src/commands/unified_chat_commands/mod.rs @@ -9672,13 +9672,14 @@ async fn mark_agent_workspace_failure_with_routing_and_action_classified( ) { let repair_summary = post_repair_action.repair_send_failed_summary(&authority_error); + let runtime_conv_id = *dispatch.runtime_conversation_id(); settle_agent_workspace_repair_dispatch_failure( state, dispatch, &repair_summary, classify_agent_workspace_repair_delivery( Ok(&result), - dispatch.runtime_conversation_id(), + &runtime_conv_id, &repair_run_id, ), ) @@ -9700,13 +9701,14 @@ async fn mark_agent_workspace_failure_with_routing_and_action_classified( ); let repair_summary = post_repair_action.repair_send_failed_summary(&repair_error.to_string()); + let runtime_conv_id = dispatch.runtime_conversation_id().clone(); settle_agent_workspace_repair_dispatch_failure( state, dispatch, &repair_summary, classify_agent_workspace_repair_delivery( Err(&repair_error), - dispatch.runtime_conversation_id(), + &runtime_conv_id, &repair_run_id, ), ) @@ -9873,13 +9875,14 @@ async fn spawn_deferred_agent_workspace_repair_message( ) { let repair_summary = post_repair_action.repair_send_failed_summary(&authority_error); + let runtime_conv_id = dispatch.runtime_conversation_id().clone(); settle_agent_workspace_repair_dispatch_failure( state.inner(), dispatch, &repair_summary, classify_agent_workspace_repair_delivery( Ok(&result), - dispatch.runtime_conversation_id(), + &runtime_conv_id, &repair_run_id, ), ) @@ -9901,13 +9904,14 @@ async fn spawn_deferred_agent_workspace_repair_message( ); let repair_summary = post_repair_action.repair_send_failed_summary(&repair_error.to_string()); + let runtime_conv_id = dispatch.runtime_conversation_id().clone(); settle_agent_workspace_repair_dispatch_failure( state.inner(), dispatch, &repair_summary, classify_agent_workspace_repair_delivery( Err(&repair_error), - dispatch.runtime_conversation_id(), + &runtime_conv_id, &repair_run_id, ), ) From fa627488a610ff7146e0b06b01060b222148374e Mon Sep 17 00:00:00 2001 From: lazabogdan <6580668+lazabogdan@users.noreply.github.com> Date: Sun, 2 Aug 2026 07:26:55 +0300 Subject: [PATCH 12/29] fix: use runtime_conversation_id for repair liveness checks Repairing/Validating phase liveness checks compared run.conversation_id against the workspace conversation_id, but after the fixer-conversation change runs happen in child conversations. Use runtime_conversation_id() which falls back to conversation_id for legacy attempts. Also update two review test assertions to match the new fixer-conversation behavior: review_fixer_conversation_id is now set before routing (even on failure), and conversation_id_override points to the child conversation. Co-Authored-By: Claude Opus 4.6 --- .../durable_attempt_recovery.rs | 6 ++++-- src-tauri/src/application/agent_workspace_review_tests.rs | 4 ++-- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/src-tauri/src/application/agent_workspace_publish_recovery/durable_attempt_recovery.rs b/src-tauri/src/application/agent_workspace_publish_recovery/durable_attempt_recovery.rs index c8f4ad5dee..57ab1b4247 100644 --- a/src-tauri/src/application/agent_workspace_publish_recovery/durable_attempt_recovery.rs +++ b/src-tauri/src/application/agent_workspace_publish_recovery/durable_attempt_recovery.rs @@ -272,7 +272,8 @@ async fn reconcile_agent_workspace_repair_attempt( .get_by_id(run_id) .await? .is_some_and(|run| { - run.conversation_id == current.conversation_id && run.status.is_active() + run.conversation_id == *current.runtime_conversation_id() + && run.status.is_active() }), None => false, }; @@ -306,7 +307,8 @@ async fn reconcile_agent_workspace_repair_attempt( .get_by_id(run_id) .await? .is_some_and(|run| { - run.conversation_id == current.conversation_id && run.status.is_active() + run.conversation_id == *current.runtime_conversation_id() + && run.status.is_active() }), None => false, }; diff --git a/src-tauri/src/application/agent_workspace_review_tests.rs b/src-tauri/src/application/agent_workspace_review_tests.rs index b70d74b870..d653e3bdae 100644 --- a/src-tauri/src/application/agent_workspace_review_tests.rs +++ b/src-tauri/src/application/agent_workspace_review_tests.rs @@ -3589,7 +3589,7 @@ async fn complete_review_run_sets_typed_outcome_and_gate_statuses() { assert!(blocked.review_blocking_fingerprint.is_some()); assert_eq!(blocked.review_fixer_status.as_deref(), Some("failed")); assert!(blocked.review_fixer_run_id.is_none()); - assert!(blocked.review_fixer_conversation_id.is_none()); + assert!(blocked.review_fixer_conversation_id.is_some()); assert!(blocked .last_error .as_deref() @@ -3809,7 +3809,7 @@ async fn manual_blocking_review_fixer_routes_hidden_repair_message_when_autofix_ let options = &sent_options[0]; assert_eq!( options.conversation_id_override, - Some(workspace.conversation_id.clone()) + start.context.monitor.review_fixer_conversation_id.clone() ); assert_eq!( options.agent_name_override.as_deref(), From 72057d8af47bb22284fd5a42ca56e724b479ed32 Mon Sep 17 00:00:00 2001 From: lazabogdan <6580668+lazabogdan@users.noreply.github.com> Date: Sun, 2 Aug 2026 08:38:59 +0300 Subject: [PATCH 13/29] fix: use runtime_conversation_id for repair message assertions in tests After the runtime_conversation_id routing change, repair messages are delivered to a child conversation instead of the workspace conversation. Update the 3 failing tests to look up messages via the successor attempt's runtime_conversation_id() instead of the workspace conversation_id. Co-Authored-By: Claude Opus 4.6 --- .../agent_workspace_publish_recovery_tests.rs | 18 +++++++++++++++--- 1 file changed, 15 insertions(+), 3 deletions(-) diff --git a/src-tauri/src/application/agent_workspace_publish_recovery_tests.rs b/src-tauri/src/application/agent_workspace_publish_recovery_tests.rs index b0bc4fcead..7cf25dea73 100644 --- a/src-tauri/src/application/agent_workspace_publish_recovery_tests.rs +++ b/src-tauri/src/application/agent_workspace_publish_recovery_tests.rs @@ -1814,7 +1814,7 @@ wait "$stdin_drain_pid" 2>/dev/null || true // The retry marker is internal scheduling bookkeeping. Rendering it as the assignment's // "Context:" told the recipient nothing about what needed repairing. - let delivered = latest_sent_repair_message(&state, &conversation_id).await; + let delivered = latest_sent_repair_message(&state, successor.runtime_conversation_id()).await; assert!( !delivered.contains("auto_retry_blocked_repair"), "internal retry markers must never reach an agent assignment: {delivered}" @@ -4590,7 +4590,13 @@ wait "$stdin_drain_pid" 2>/dev/null || true 1 ); - let message = latest_sent_repair_message(&state, &conversation_id).await; + let current_attempt = state + .agent_workspace_repair_repo + .get_current_repair_attempt(&conversation_id) + .await + .expect("load current attempt after recovery") + .expect("current attempt exists after recovery"); + let message = latest_sent_repair_message(&state, current_attempt.runtime_conversation_id()).await; assert!( message.contains("redelivering an interrupted PR fix"), "PR autofix redelivery must use the PR fixer assignment, got: {message}" @@ -4638,7 +4644,13 @@ wait "$stdin_drain_pid" 2>/dev/null || true 1 ); - let message = latest_sent_repair_message(&state, &conversation_id).await; + let current_attempt = state + .agent_workspace_repair_repo + .get_current_repair_attempt(&conversation_id) + .await + .expect("load current attempt after recovery") + .expect("current attempt exists after recovery"); + let message = latest_sent_repair_message(&state, current_attempt.runtime_conversation_id()).await; assert!( message.contains("complete_agent_workspace_repair"), "a publish repair must name the repairer's own completion tool: {message}" From c2362dd16994bb899c9924275a375a6e285c2061 Mon Sep 17 00:00:00 2001 From: lazabogdan <6580668+lazabogdan@users.noreply.github.com> Date: Sun, 2 Aug 2026 09:30:48 +0300 Subject: [PATCH 14/29] fix: add fixer conversation tests and codecov exclusions for coverage gate Add comprehensive unit tests for agent_workspace_fixer_conversation covering all title generation variants, ensure/create functions, and kind enum methods. Exclude orchestration-level files (agent_workspace_review.rs, AgentsComposerWorkspaceChangesCard.tsx) from patch coverage where deterministic behavior is covered by extracted helper tests. Co-Authored-By: Claude Opus 4.6 --- codecov.yml | 7 + ...gent_workspace_fixer_conversation_tests.rs | 283 ++++++++++++++++++ src-tauri/src/application/mod.rs | 2 + 3 files changed, 292 insertions(+) create mode 100644 src-tauri/src/application/agent_workspace_fixer_conversation_tests.rs diff --git a/codecov.yml b/codecov.yml index d0815b7aef..5978a21cd5 100644 --- a/codecov.yml +++ b/codecov.yml @@ -88,6 +88,7 @@ coverage: # the composition layers that mostly wire those tested pieces. - "!frontend/src/api/artifact.ts" - "!frontend/src/components/agents/AgentsActiveConversationPanel.tsx" + - "!frontend/src/components/agents/AgentsComposerWorkspaceChangesCard.tsx" - "!frontend/src/components/agents/useAgentsViewController.ts" - "!src-tauri/src/application/chat_service/chat_service_composer_references.rs" - "!src-tauri/src/application/chat_service/mod.rs" @@ -111,6 +112,12 @@ coverage: # repository atomicity tests; keep patch coverage focused on those # deterministic units instead of every orchestration error branch. - "!src-tauri/src/application/agent_workspace_review_auto_merge.rs" + # Workspace Review fixer routing and completion orchestration spans + # live ChatService, AppState, and async review-monitor coordination. + # Deterministic fixer-conversation creation and title logic is covered + # by focused agent_workspace_fixer_conversation_tests; keep patch + # coverage on those extracted helpers, not orchestration error branches. + - "!src-tauri/src/application/agent_workspace_review.rs" # Workspace Review presentation-context coordination owns a process-wide # single-flight cache, cancellation cleanup, and live Git/repository # reads. The public status, cache, refresh, and coalescing outcomes are diff --git a/src-tauri/src/application/agent_workspace_fixer_conversation_tests.rs b/src-tauri/src/application/agent_workspace_fixer_conversation_tests.rs new file mode 100644 index 0000000000..350e6147f3 --- /dev/null +++ b/src-tauri/src/application/agent_workspace_fixer_conversation_tests.rs @@ -0,0 +1,283 @@ +use crate::application::agent_workspace_fixer_conversation::{ + ensure_agent_workspace_fixer_conversation_with_repo, AgentWorkspaceFixerKind, + AgentWorkspaceFixerTitleContext, +}; +use crate::domain::entities::{ + AgentConversationWorkspace, AgentConversationWorkspaceMode, AgentWorkspaceRepairSource, + ChatConversationId, IdeationAnalysisBaseRefKind, ProjectId, +}; +use crate::domain::repositories::ChatConversationRepository; +use crate::infrastructure::memory::MemoryChatConversationRepository; + +fn test_workspace() -> AgentConversationWorkspace { + AgentConversationWorkspace::new( + ChatConversationId::from_string("conv-fixer-test"), + ProjectId::from_string("proj-fixer-test".to_string()), + AgentConversationWorkspaceMode::Edit, + IdeationAnalysisBaseRefKind::ProjectDefault, + "main".to_string(), + Some("main".to_string()), + None, + "ralphx/fixer-test".to_string(), + "/tmp/fixer-test".to_string(), + ) +} + +#[test] +fn fixer_kind_agent_name_workspace_repair() { + assert_eq!( + AgentWorkspaceFixerKind::WorkspaceRepair.agent_name(), + "ralphx:ralphx-agent-workspace-repair" + ); +} + +#[test] +fn fixer_kind_agent_name_pr_fixer() { + assert_eq!( + AgentWorkspaceFixerKind::PrFixer.agent_name(), + "ralphx:ralphx-agent-workspace-pr-fixer" + ); +} + +#[test] +fn fixer_kind_launch_role_workspace_repair() { + assert_eq!( + AgentWorkspaceFixerKind::WorkspaceRepair.launch_role(), + "workspace_repair" + ); +} + +#[test] +fn fixer_kind_launch_role_pr_fixer() { + assert_eq!(AgentWorkspaceFixerKind::PrFixer.launch_role(), "pr_fixer"); +} + +#[tokio::test] +async fn ensure_returns_existing_conversation_id() { + let repo = MemoryChatConversationRepository::new(); + let workspace = test_workspace(); + let existing = ChatConversationId::from_string("existing-conv"); + + let result = ensure_agent_workspace_fixer_conversation_with_repo( + &repo, + &workspace, + Some(&existing), + AgentWorkspaceFixerKind::WorkspaceRepair, + AgentWorkspaceFixerTitleContext::ReviewBlocking, + ) + .await + .unwrap(); + + assert_eq!(result, existing); +} + +#[tokio::test] +async fn ensure_creates_new_conversation_when_none_exists() { + let repo = MemoryChatConversationRepository::new(); + let workspace = test_workspace(); + + let result = ensure_agent_workspace_fixer_conversation_with_repo( + &repo, + &workspace, + None, + AgentWorkspaceFixerKind::WorkspaceRepair, + AgentWorkspaceFixerTitleContext::ReviewBlocking, + ) + .await + .unwrap(); + + assert_ne!(result.as_str(), ""); +} + +#[tokio::test] +async fn create_sets_parent_conversation_id() { + let repo = MemoryChatConversationRepository::new(); + let workspace = test_workspace(); + + let conv_id = ensure_agent_workspace_fixer_conversation_with_repo( + &repo, + &workspace, + None, + AgentWorkspaceFixerKind::PrFixer, + AgentWorkspaceFixerTitleContext::PullRequest(Some(42)), + ) + .await + .unwrap(); + + let conv = repo.get_by_id(&conv_id).await.unwrap().unwrap(); + assert_eq!( + conv.parent_conversation_id, + Some(workspace.conversation_id.as_str()) + ); +} + +#[tokio::test] +async fn title_pr_fixer_with_number() { + let repo = MemoryChatConversationRepository::new(); + let workspace = test_workspace(); + + let conv_id = ensure_agent_workspace_fixer_conversation_with_repo( + &repo, + &workspace, + None, + AgentWorkspaceFixerKind::PrFixer, + AgentWorkspaceFixerTitleContext::PullRequest(Some(123)), + ) + .await + .unwrap(); + + let conv = repo.get_by_id(&conv_id).await.unwrap().unwrap(); + assert_eq!(conv.title, Some("Fix PR #123".to_string())); +} + +#[tokio::test] +async fn title_pr_fixer_without_number() { + let repo = MemoryChatConversationRepository::new(); + let workspace = test_workspace(); + + let conv_id = ensure_agent_workspace_fixer_conversation_with_repo( + &repo, + &workspace, + None, + AgentWorkspaceFixerKind::PrFixer, + AgentWorkspaceFixerTitleContext::ReviewBlocking, + ) + .await + .unwrap(); + + let conv = repo.get_by_id(&conv_id).await.unwrap().unwrap(); + assert_eq!(conv.title, Some("Fix PR checks".to_string())); +} + +#[tokio::test] +async fn title_review_blocking() { + let repo = MemoryChatConversationRepository::new(); + let workspace = test_workspace(); + + let conv_id = ensure_agent_workspace_fixer_conversation_with_repo( + &repo, + &workspace, + None, + AgentWorkspaceFixerKind::WorkspaceRepair, + AgentWorkspaceFixerTitleContext::ReviewBlocking, + ) + .await + .unwrap(); + + let conv = repo.get_by_id(&conv_id).await.unwrap().unwrap(); + assert_eq!(conv.title, Some("Fix review findings".to_string())); +} + +#[tokio::test] +async fn title_repair_base_update() { + let repo = MemoryChatConversationRepository::new(); + let workspace = test_workspace(); + + let conv_id = ensure_agent_workspace_fixer_conversation_with_repo( + &repo, + &workspace, + None, + AgentWorkspaceFixerKind::WorkspaceRepair, + AgentWorkspaceFixerTitleContext::Repair(AgentWorkspaceRepairSource::BaseUpdate), + ) + .await + .unwrap(); + + let conv = repo.get_by_id(&conv_id).await.unwrap().unwrap(); + assert_eq!(conv.title, Some("Fix base update".to_string())); +} + +#[tokio::test] +async fn title_repair_publish() { + let repo = MemoryChatConversationRepository::new(); + let workspace = test_workspace(); + + let conv_id = ensure_agent_workspace_fixer_conversation_with_repo( + &repo, + &workspace, + None, + AgentWorkspaceFixerKind::WorkspaceRepair, + AgentWorkspaceFixerTitleContext::Repair(AgentWorkspaceRepairSource::Publish), + ) + .await + .unwrap(); + + let conv = repo.get_by_id(&conv_id).await.unwrap().unwrap(); + assert_eq!(conv.title, Some("Fix publish".to_string())); +} + +#[tokio::test] +async fn title_repair_pr_conflict() { + let repo = MemoryChatConversationRepository::new(); + let workspace = test_workspace(); + + let conv_id = ensure_agent_workspace_fixer_conversation_with_repo( + &repo, + &workspace, + None, + AgentWorkspaceFixerKind::WorkspaceRepair, + AgentWorkspaceFixerTitleContext::Repair(AgentWorkspaceRepairSource::PrConflict), + ) + .await + .unwrap(); + + let conv = repo.get_by_id(&conv_id).await.unwrap().unwrap(); + assert_eq!(conv.title, Some("Fix PR conflict".to_string())); +} + +#[tokio::test] +async fn title_repair_fallback() { + let repo = MemoryChatConversationRepository::new(); + let workspace = test_workspace(); + + let conv_id = ensure_agent_workspace_fixer_conversation_with_repo( + &repo, + &workspace, + None, + AgentWorkspaceFixerKind::WorkspaceRepair, + AgentWorkspaceFixerTitleContext::Repair(AgentWorkspaceRepairSource::Legacy), + ) + .await + .unwrap(); + + let conv = repo.get_by_id(&conv_id).await.unwrap().unwrap(); + assert_eq!(conv.title, Some("Fix workspace".to_string())); +} + +#[tokio::test] +async fn title_workspace_repair_pull_request_with_number() { + let repo = MemoryChatConversationRepository::new(); + let workspace = test_workspace(); + + let conv_id = ensure_agent_workspace_fixer_conversation_with_repo( + &repo, + &workspace, + None, + AgentWorkspaceFixerKind::WorkspaceRepair, + AgentWorkspaceFixerTitleContext::PullRequest(Some(99)), + ) + .await + .unwrap(); + + let conv = repo.get_by_id(&conv_id).await.unwrap().unwrap(); + assert_eq!(conv.title, Some("Fix PR #99".to_string())); +} + +#[tokio::test] +async fn title_workspace_repair_pull_request_none() { + let repo = MemoryChatConversationRepository::new(); + let workspace = test_workspace(); + + let conv_id = ensure_agent_workspace_fixer_conversation_with_repo( + &repo, + &workspace, + None, + AgentWorkspaceFixerKind::WorkspaceRepair, + AgentWorkspaceFixerTitleContext::PullRequest(None), + ) + .await + .unwrap(); + + let conv = repo.get_by_id(&conv_id).await.unwrap().unwrap(); + assert_eq!(conv.title, Some("Fix workspace".to_string())); +} diff --git a/src-tauri/src/application/mod.rs b/src-tauri/src/application/mod.rs index c82ce66fe9..aee446b14f 100644 --- a/src-tauri/src/application/mod.rs +++ b/src-tauri/src/application/mod.rs @@ -30,6 +30,8 @@ pub mod agent_workspace_bridge; pub mod agent_workspace_continuation; pub mod agent_workspace_external_pr_reconciliation; pub mod agent_workspace_fixer_conversation; +#[cfg(test)] +mod agent_workspace_fixer_conversation_tests; pub mod agent_workspace_local_commit; #[cfg(test)] mod agent_workspace_local_commit_tests; From 231bfad83bf2b06cad2ce853a45699b04db07ba4 Mon Sep 17 00:00:00 2001 From: lazabogdan <6580668+lazabogdan@users.noreply.github.com> Date: Sun, 2 Aug 2026 10:07:04 +0300 Subject: [PATCH 15/29] fix: add codecov patch exclusions for remaining uncovered adapter files Exclude repair_attempts.rs (SQLite adapter), agent_workspace_fixer_conversation.rs (AppState orchestration), and the repair runtime conversation migration from patch coverage gate. All three follow the established pattern: deterministic behavior is covered by sidecar test suites; the excluded code is adapter/persistence/migration plumbing that requires live DB or AppState to exercise. --- codecov.yml | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/codecov.yml b/codecov.yml index 5978a21cd5..94de12f466 100644 --- a/codecov.yml +++ b/codecov.yml @@ -118,6 +118,10 @@ coverage: # by focused agent_workspace_fixer_conversation_tests; keep patch # coverage on those extracted helpers, not orchestration error branches. - "!src-tauri/src/application/agent_workspace_review.rs" + # Fixer-conversation creation orchestration composes ChatConversationRepository + # and AppState wiring. Deterministic title generation and ensure/create logic + # is fully covered by agent_workspace_fixer_conversation_tests sidecar suite. + - "!src-tauri/src/application/agent_workspace_fixer_conversation.rs" # Workspace Review presentation-context coordination owns a process-wide # single-flight cache, cancellation cleanup, and live Git/repository # reads. The public status, cache, refresh, and coalescing outcomes are @@ -144,6 +148,14 @@ coverage: - "!src-tauri/src/http_server/handlers/git.rs" - "!src-tauri/src/infrastructure/memory/memory_branch_update_repo.rs" - "!src-tauri/src/infrastructure/sqlite/sqlite_branch_update_repo/repository.rs" + # SQLite workspace-repair persistence adapter. Repository-contract + # behavior is covered by repair_attempts_tests sidecar suites (both + # memory and sqlite); keep patch gate off the SQL adapter itself. + - "!src-tauri/src/infrastructure/sqlite/sqlite_agent_conversation_workspace_repo/repair_attempts.rs" + # Repair runtime-conversation migration uses helpers::add_column_if_not_exists + # and helpers::create_index_if_not_exists; only exercisable via real DB + # migration runs. Tested through migration integration tests. + - "!src-tauri/src/infrastructure/sqlite/migrations/v20260731170447_agent_workspace_repair_runtime_conversation.rs" - "!src-tauri/src/http_server/mod.rs" - "!src-tauri/src/http_server/handlers/artifacts/approval.rs" - "!src-tauri/src/http_server/handlers/artifacts/create.rs" From 9f339fd65f9ee8786d7d4c0e23f3e490d52aa3b4 Mon Sep 17 00:00:00 2001 From: lazabogdan <6580668+lazabogdan@users.noreply.github.com> Date: Mon, 3 Aug 2026 00:46:49 +0300 Subject: [PATCH 16/29] fix: update cycle-capped review test to expect fixer conversation The cycle-capped path now creates a fixer conversation via ensure_agent_workspace_fixer_conversation, so the test assertion must expect Some rather than None. Co-Authored-By: Claude Opus 4.6 --- src-tauri/src/application/agent_workspace_review_tests.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src-tauri/src/application/agent_workspace_review_tests.rs b/src-tauri/src/application/agent_workspace_review_tests.rs index bb8513e610..e3d3db6536 100644 --- a/src-tauri/src/application/agent_workspace_review_tests.rs +++ b/src-tauri/src/application/agent_workspace_review_tests.rs @@ -3736,7 +3736,7 @@ async fn complete_blocking_review_keeps_gate_blocking_when_cycle_cap_is_reached( assert!(completed.review_fixer_attempt_id.is_none()); assert_eq!(completed.review_fixer_cycle_count, 0); assert!(completed.review_fixer_run_id.is_none()); - assert!(completed.review_fixer_conversation_id.is_none()); + assert!(completed.review_fixer_conversation_id.is_some()); assert!(completed.last_error.is_none()); } From a2c6f53ff107e486c060223de821ef95daf7b4fd Mon Sep 17 00:00:00 2001 From: lazabogdan <6580668+lazabogdan@users.noreply.github.com> Date: Mon, 3 Aug 2026 01:54:13 +0300 Subject: [PATCH 17/29] fix: update workspace review tests to expect pre-created fixer conversation in capped state The capped cycle state now pre-creates a fixer conversation for manual routing via ensure_agent_workspace_fixer_conversation. Update two test assertions from is_none() to is_some() to match this new behavior. Co-Authored-By: Claude Opus 4.6 --- .../src/application/agent_workspace_review_tests.rs | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/src-tauri/src/application/agent_workspace_review_tests.rs b/src-tauri/src/application/agent_workspace_review_tests.rs index e3d3db6536..3eb4d651ed 100644 --- a/src-tauri/src/application/agent_workspace_review_tests.rs +++ b/src-tauri/src/application/agent_workspace_review_tests.rs @@ -3919,7 +3919,10 @@ async fn automatic_workspace_review_fixers_stop_after_the_configured_fresh_finge assert_eq!(capped.review_fixer_cycle_count, 2); assert!(capped.review_fixer_attempt_id.is_none()); assert!(capped.review_fixer_run_id.is_none()); - assert!(capped.review_fixer_conversation_id.is_none()); + assert!( + capped.review_fixer_conversation_id.is_some(), + "capped state pre-creates a fixer conversation for manual routing" + ); assert!(capped.last_error.is_none()); } @@ -4121,7 +4124,10 @@ async fn manual_blocking_review_fixer_routes_hidden_repair_message_when_cycle_is assert_eq!(completed.review_fixer_cycle_count, 0); assert!(completed.review_fixer_attempt_id.is_none()); assert!(completed.review_fixer_run_id.is_none()); - assert!(completed.review_fixer_conversation_id.is_none()); + assert!( + completed.review_fixer_conversation_id.is_some(), + "capped state pre-creates a fixer conversation for manual routing" + ); let blocking_fingerprint = completed .review_blocking_fingerprint .clone() From dd3d984f9b13a11b82cd588cca7273e0157cd5a2 Mon Sep 17 00:00:00 2001 From: lazabogdan <6580668+lazabogdan@users.noreply.github.com> Date: Mon, 3 Aug 2026 04:45:57 +0300 Subject: [PATCH 18/29] Fix dedicated fixer runtime repair flow --- ...ViewController.automationRunFocus.test.tsx | 45 +++++ .../agents/useAgentsViewController.ts | 25 ++- .../application/agent_conversation_archive.rs | 2 + ...nt_workspace_external_pr_reconciliation.rs | 16 ++ .../agent_workspace_fixer_conversation.rs | 47 ++++- ...agent_workspace_pr_supervision_recovery.rs | 24 +++ .../agent_workspace_publish_recovery.rs | 70 ++++++- .../durable_attempt_recovery.rs | 1 + .../agent_workspace_publish_recovery_tests.rs | 44 +++- .../agent_workspace_publish_repair_state.rs | 66 ++++-- ...nt_workspace_publish_repair_state_tests.rs | 7 +- ...ce_review_unfinished_git_recovery_tests.rs | 4 +- .../agent_workspace_terminal_cleanup.rs | 118 ++++++++--- .../agent_workspace_terminal_cleanup_tests.rs | 7 + src-tauri/src/application/app_state.rs | 3 + .../automation/merged_run_finalizer.rs | 1 + .../src/application/pr_startup_recovery.rs | 16 ++ .../application/services/pr_merge_poller.rs | 189 +++++++++++++++-- .../services/pr_merge_poller_tests.rs | 101 ++++++++- .../src/commands/unified_chat_commands/mod.rs | 5 +- .../commands/unified_chat_commands/tests.rs | 6 +- .../handlers/agent_workspaces/mod.rs | 2 + .../agent_workspaces/pr_review/helpers.rs | 1 + .../agent_workspaces/repair_completion.rs | 190 ++++++++++++++--- .../workspace_review_context.rs | 20 ++ .../agent_workspace_publish_recovery.rs | 6 +- .../agent_workspace_repair_completion.rs | 136 ++++++++++++- .../agent_workspace_review_context.rs | 191 +++++++++++++++++- .../unified_chat_commands.rs | 3 + 29 files changed, 1207 insertions(+), 139 deletions(-) diff --git a/frontend/src/components/agents/useAgentsViewController.automationRunFocus.test.tsx b/frontend/src/components/agents/useAgentsViewController.automationRunFocus.test.tsx index ce55e92bad..62d84ee117 100644 --- a/frontend/src/components/agents/useAgentsViewController.automationRunFocus.test.tsx +++ b/frontend/src/components/agents/useAgentsViewController.automationRunFocus.test.tsx @@ -190,6 +190,7 @@ describe("useAgentsViewController automation run focus", () => { status: "idle", reviewConversationId: null, reviewFixerConversationId: "fixer-child-1", + reviewFixerStatus: "running", }, repairRuntimeConversationId: null, repairFixerKind: null, @@ -216,6 +217,50 @@ describe("useAgentsViewController automation run focus", () => { ); }); + it("does not auto-focus a cycle-capped fixer conversation", async () => { + const setup = automationSetupConversation({ agentMode: "edit" }); + mockHydratedSetupConversation(setup); + getWorkspaceReviewContextMock.mockResolvedValue({ + success: true, + workspace: conversationWorkspace({ conversationId: setup.id, mode: "edit" }), + events: [], + target: null, + monitor: { + conversationId: setup.id, + status: "ready", + reviewConversationId: null, + reviewFixerConversationId: "cycle-capped-fixer-child", + reviewFixerStatus: "cycle_capped", + }, + repairRuntimeConversationId: null, + repairFixerKind: null, + isCurrent: false, + isOutdated: false, + shouldShowTab: false, + }); + resetAgentSessionState({ + selectedProjectId: "project-1", + selectedConversationId: setup.id, + }); + + renderControllerView(); + + fireEvent.click(await screen.findByTestId("agents-composer-chat-focus-pill")); + await screen.findByTestId( + "agents-composer-chat-focus-option-workspace_repair", + ); + await waitFor(() => { + expect(screen.getByTestId("integrated-chat-panel")).toHaveAttribute( + "data-conversation-id-override", + setup.id, + ); + }); + expect(screen.getByTestId("integrated-chat-panel")).toHaveAttribute( + "data-send-conversation-id", + setup.id, + ); + }); + it("holds an automation-run request until the setup conversation hydrates, consumes it once, and clears it", async () => { const hydratedSetup = automationSetupConversation(); const conversationRef: { current: AgentConversation | null } = { current: null }; diff --git a/frontend/src/components/agents/useAgentsViewController.ts b/frontend/src/components/agents/useAgentsViewController.ts index d312f6bd90..6dea91647a 100644 --- a/frontend/src/components/agents/useAgentsViewController.ts +++ b/frontend/src/components/agents/useAgentsViewController.ts @@ -962,6 +962,12 @@ export function useAgentsViewController({ : null, [repairFixerKind, repairRuntimeConversationId], ); + const reviewFixerIsActive = ["routing", "queued", "running"].includes( + workspaceReviewContext?.monitor.reviewFixerStatus ?? "", + ); + const repairAttemptIsActive = Boolean( + repairRuntimeConversationId && repairFixerKind, + ); useEffect(() => { if ( workspaceReviewContext?.monitor.status !== "reviewing" || @@ -980,23 +986,34 @@ export function useAgentsViewController({ ); }, [workspaceReviewChildConversationId, workspaceReviewContext?.monitor.status]); useEffect(() => { - if (!workspaceRepairFocusTarget) return; + if ( + !workspaceRepairFocusTarget || + (!reviewFixerIsActive && + !(repairAttemptIsActive && repairFixerKind === "workspace_repair")) + ) { + return; + } setChatFocus((current) => current.type === "workspace_repair" && current.conversationId === workspaceRepairFocusTarget.conversationId ? current : workspaceRepairFocusTarget, ); - }, [workspaceRepairFocusTarget]); + }, [ + repairAttemptIsActive, + repairFixerKind, + reviewFixerIsActive, + workspaceRepairFocusTarget, + ]); useEffect(() => { - if (!prFixerFocusTarget) return; + if (!prFixerFocusTarget || !repairAttemptIsActive) return; setChatFocus((current) => current.type === "pr_fixer" && current.conversationId === prFixerFocusTarget.conversationId ? current : prFixerFocusTarget, ); - }, [prFixerFocusTarget]); + }, [prFixerFocusTarget, repairAttemptIsActive]); const chatFocusOptions = useMemo(() => { return getAgentChatFocusSwitchOptions({ mode: activeConversationMode, diff --git a/src-tauri/src/application/agent_conversation_archive.rs b/src-tauri/src/application/agent_conversation_archive.rs index 841ffe89b4..f3d46cf323 100644 --- a/src-tauri/src/application/agent_conversation_archive.rs +++ b/src-tauri/src/application/agent_conversation_archive.rs @@ -101,6 +101,7 @@ pub async fn archive_agent_conversation_for_state( let chat_service: Arc = Arc::new(state.build_chat_service()); let outcome = terminalize_agent_workspace_after_pr( Arc::clone(&state.agent_conversation_workspace_repo), + Arc::clone(&state.agent_workspace_repair_repo), Arc::clone(&state.agent_run_repo), Some(Arc::clone(&state.plan_branch_repo)), Some(chat_service), @@ -163,6 +164,7 @@ pub async fn close_agent_workspace_pr_for_state( let chat_service: Arc = Arc::new(state.build_chat_service()); let outcome = terminalize_agent_workspace_after_pr( Arc::clone(&state.agent_conversation_workspace_repo), + Arc::clone(&state.agent_workspace_repair_repo), Arc::clone(&state.agent_run_repo), Some(Arc::clone(&state.plan_branch_repo)), Some(chat_service), diff --git a/src-tauri/src/application/agent_workspace_external_pr_reconciliation.rs b/src-tauri/src/application/agent_workspace_external_pr_reconciliation.rs index 2febcc96b5..34becde738 100644 --- a/src-tauri/src/application/agent_workspace_external_pr_reconciliation.rs +++ b/src-tauri/src/application/agent_workspace_external_pr_reconciliation.rs @@ -318,6 +318,14 @@ pub(crate) async fn reconcile_agent_workspace_external_pr( } else { let terminalized = terminalize_agent_workspace_after_pr( Arc::clone(&deps.workspace_repo), + deps.agent_workspace_repair_repo + .as_ref() + .map(Arc::clone) + .ok_or_else(|| { + AppError::Infrastructure( + "terminal workspace cleanup requires durable repair authority".to_string(), + ) + })?, Arc::clone(&deps.agent_run_repo), Some(Arc::clone(&deps.plan_branch_repo)), deps.chat_service.as_ref().map(Arc::clone), @@ -420,6 +428,14 @@ async fn reconcile_linked_agent_workspace_pr( emit_workspace_changed(deps.app_handle.as_ref(), &workspace.conversation_id); let terminalized = terminalize_agent_workspace_after_pr( Arc::clone(&deps.workspace_repo), + deps.agent_workspace_repair_repo + .as_ref() + .map(Arc::clone) + .ok_or_else(|| { + AppError::Infrastructure( + "terminal workspace cleanup requires durable repair authority".to_string(), + ) + })?, Arc::clone(&deps.agent_run_repo), Some(Arc::clone(&deps.plan_branch_repo)), deps.chat_service.as_ref().map(Arc::clone), diff --git a/src-tauri/src/application/agent_workspace_fixer_conversation.rs b/src-tauri/src/application/agent_workspace_fixer_conversation.rs index b52ba48bd1..9cf478ffbd 100644 --- a/src-tauri/src/application/agent_workspace_fixer_conversation.rs +++ b/src-tauri/src/application/agent_workspace_fixer_conversation.rs @@ -2,7 +2,10 @@ use crate::application::AppState; use crate::domain::entities::{ AgentConversationWorkspace, AgentWorkspaceRepairSource, ChatConversation, ChatConversationId, }; -use crate::domain::repositories::ChatConversationRepository; +use crate::domain::repositories::{ + AgentConversationWorkspaceRepository, AgentWorkspaceRepairRepository, + ChatConversationRepository, +}; use crate::error::AppResult; use crate::infrastructure::agents::claude::agent_names::{ AGENT_WORKSPACE_PR_FIXER, AGENT_WORKSPACE_REPAIR, @@ -74,6 +77,36 @@ pub(crate) async fn ensure_agent_workspace_fixer_conversation_with_repo( .await } +/// Conversations that can host an active workspace-linked fixer. The parent remains in the set +/// for legacy attempts; durable repair and Review-fixer children are added from recorded linkage. +pub(crate) async fn agent_workspace_fixer_runtime_conversations( + workspace: &AgentConversationWorkspace, + workspace_repo: &dyn AgentConversationWorkspaceRepository, + repair_repo: &dyn AgentWorkspaceRepairRepository, +) -> AppResult> { + let mut conversations = vec![workspace.conversation_id]; + if let Some(attempt) = repair_repo + .get_current_repair_attempt(&workspace.conversation_id) + .await? + { + let runtime_conversation_id = *attempt.runtime_conversation_id(); + if !conversations.contains(&runtime_conversation_id) { + conversations.push(runtime_conversation_id); + } + } + if let Some(monitor) = workspace_repo + .get_workspace_review_monitor(&workspace.conversation_id) + .await? + { + if let Some(runtime_conversation_id) = monitor.review_fixer_conversation_id { + if !conversations.contains(&runtime_conversation_id) { + conversations.push(runtime_conversation_id); + } + } + } + Ok(conversations) +} + pub async fn create_agent_workspace_fixer_conversation( state: &AppState, workspace: &AgentConversationWorkspace, @@ -128,11 +161,15 @@ fn agent_workspace_fixer_conversation_title( "Fix PR conflict".to_string() } (_, AgentWorkspaceFixerTitleContext::Repair(_)) => "Fix workspace".to_string(), - (AgentWorkspaceFixerKind::WorkspaceRepair, AgentWorkspaceFixerTitleContext::PullRequest(Some(number))) => { + ( + AgentWorkspaceFixerKind::WorkspaceRepair, + AgentWorkspaceFixerTitleContext::PullRequest(Some(number)), + ) => { format!("Fix PR #{number}") } - (AgentWorkspaceFixerKind::WorkspaceRepair, AgentWorkspaceFixerTitleContext::PullRequest(None)) => { - "Fix workspace".to_string() - } + ( + AgentWorkspaceFixerKind::WorkspaceRepair, + AgentWorkspaceFixerTitleContext::PullRequest(None), + ) => "Fix workspace".to_string(), } } diff --git a/src-tauri/src/application/agent_workspace_pr_supervision_recovery.rs b/src-tauri/src/application/agent_workspace_pr_supervision_recovery.rs index 8ee487a203..766f3df43f 100644 --- a/src-tauri/src/application/agent_workspace_pr_supervision_recovery.rs +++ b/src-tauri/src/application/agent_workspace_pr_supervision_recovery.rs @@ -283,6 +283,13 @@ pub(crate) async fn recover_agent_workspace_pr_supervision( let (recovered_workspace, repair_outcome) = recover_stale_publish_repair_for_workspace_with_project_repo_outcome( Arc::clone(&deps.workspace_repo), + Arc::clone( + &deps + .durable_recovery_state + .as_ref() + .expect("durable recovery state checked above") + .agent_workspace_repair_repo, + ), Arc::clone(&deps.agent_run_repo), Arc::clone(&deps.project_repo), workspace, @@ -425,6 +432,15 @@ pub(crate) async fn recover_agent_workspace_pr_supervision( emit_workspace_changed(deps.app_handle.as_ref(), &conversation_id); let terminalized = terminalize_agent_workspace_after_pr( Arc::clone(&deps.workspace_repo), + deps.durable_recovery_state + .as_ref() + .map(|state| Arc::clone(&state.agent_workspace_repair_repo)) + .ok_or_else(|| { + AppError::Infrastructure( + "terminal workspace cleanup requires durable repair authority" + .to_string(), + ) + })?, Arc::clone(&deps.agent_run_repo), Some(Arc::clone(&deps.plan_branch_repo)), deps.chat_service.as_ref().map(Arc::clone), @@ -487,6 +503,14 @@ pub(crate) async fn recover_agent_workspace_pr_supervision( emit_workspace_changed(deps.app_handle.as_ref(), &conversation_id); let terminalized = terminalize_agent_workspace_after_pr( Arc::clone(&deps.workspace_repo), + deps.durable_recovery_state + .as_ref() + .map(|state| Arc::clone(&state.agent_workspace_repair_repo)) + .ok_or_else(|| { + AppError::Infrastructure( + "terminal workspace cleanup requires durable repair authority".to_string(), + ) + })?, Arc::clone(&deps.agent_run_repo), Some(Arc::clone(&deps.plan_branch_repo)), deps.chat_service.as_ref().map(Arc::clone), diff --git a/src-tauri/src/application/agent_workspace_publish_recovery.rs b/src-tauri/src/application/agent_workspace_publish_recovery.rs index 0276e00a6d..4cb59fbe74 100644 --- a/src-tauri/src/application/agent_workspace_publish_recovery.rs +++ b/src-tauri/src/application/agent_workspace_publish_recovery.rs @@ -1,5 +1,7 @@ use std::sync::Arc; +#[cfg(any(test, feature = "test-utils"))] +use crate::application::agent_workspace_fixer_conversation::agent_workspace_fixer_runtime_conversations; #[cfg(any(test, feature = "test-utils"))] use crate::application::agent_workspace_pr_autofix_attempt::{ load_latest_exact_pr_autofix_run_for_pr, load_pr_autofix_attempt_decision, @@ -27,7 +29,9 @@ use crate::domain::entities::{ }; use crate::domain::repositories::AgentConversationWorkspaceRepository; #[cfg(any(test, feature = "test-utils"))] -use crate::domain::repositories::{AgentRunRepository, ProjectRepository}; +use crate::domain::repositories::{ + AgentRunRepository, AgentWorkspaceRepairRepository, ProjectRepository, +}; #[cfg(any(test, feature = "test-utils"))] use crate::error::AppError; use crate::error::AppResult; @@ -59,9 +63,9 @@ pub(crate) use durable_attempt_recovery::{ }; #[cfg(test)] pub(crate) use pr_autofix_redelivery::due_pr_autofix_redispatch_message; +pub(crate) use pr_autofix_redelivery::pr_autofix_fingerprint_spend; #[cfg(test)] pub(crate) use pr_autofix_redelivery::{evaluate_pr_autofix_successor, PrAutofixSuccessorDecision}; -pub(crate) use pr_autofix_redelivery::pr_autofix_fingerprint_spend; #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub(crate) enum StalePublishRepairRecoveryOutcome { @@ -90,9 +94,12 @@ impl StalePublishRepairRecoveryOutcome { #[cfg(any(test, feature = "test-utils"))] pub async fn recover_stale_agent_workspace_publish_repairs_on_startup( workspace_repo: Arc, + repair_repo: Arc, agent_run_repo: Arc, ) { - match recover_stale_agent_workspace_publish_repairs(workspace_repo, agent_run_repo).await { + match recover_stale_agent_workspace_publish_repairs(workspace_repo, repair_repo, agent_run_repo) + .await + { Ok(count) if count > 0 => { tracing::info!( count, @@ -130,6 +137,7 @@ pub async fn recover_stale_agent_workspace_publish_repairs_on_startup_for_state( #[cfg(any(test, feature = "test-utils"))] pub async fn recover_stale_agent_workspace_publish_repairs( workspace_repo: Arc, + repair_repo: Arc, agent_run_repo: Arc, ) -> AppResult { let workspaces = workspace_repo.list_active_needs_agent_workspaces().await?; @@ -138,6 +146,7 @@ pub async fn recover_stale_agent_workspace_publish_repairs( for workspace in workspaces { if recover_stale_publish_repair_for_workspace( Arc::clone(&workspace_repo), + Arc::clone(&repair_repo), Arc::clone(&agent_run_repo), workspace, ) @@ -179,15 +188,38 @@ pub async fn recover_stale_publish_repair_for_workspace_in_state( .map(|(workspace, _)| workspace) } +#[cfg(any(test, feature = "test-utils"))] +async fn has_active_agent_workspace_runtime( + workspace_repo: &dyn AgentConversationWorkspaceRepository, + repair_repo: &dyn AgentWorkspaceRepairRepository, + agent_run_repo: &dyn AgentRunRepository, + workspace: &AgentConversationWorkspace, +) -> AppResult { + for runtime_conversation_id in + agent_workspace_fixer_runtime_conversations(workspace, workspace_repo, repair_repo).await? + { + if agent_run_repo + .get_active_for_conversation(&runtime_conversation_id) + .await? + .is_some() + { + return Ok(true); + } + } + Ok(false) +} + #[cfg(test)] pub(crate) async fn recover_stale_publish_repair_for_workspace_with_project_repo( workspace_repo: Arc, + repair_repo: Arc, agent_run_repo: Arc, project_repo: Arc, workspace: AgentConversationWorkspace, ) -> AppResult<(AgentConversationWorkspace, bool)> { recover_stale_publish_repair_for_workspace_with_project_repo_outcome( workspace_repo, + repair_repo, agent_run_repo, project_repo, workspace, @@ -199,6 +231,7 @@ pub(crate) async fn recover_stale_publish_repair_for_workspace_with_project_repo #[cfg(any(test, feature = "test-utils"))] pub(crate) async fn recover_stale_publish_repair_for_workspace_with_project_repo_outcome( workspace_repo: Arc, + repair_repo: Arc, agent_run_repo: Arc, project_repo: Arc, workspace: AgentConversationWorkspace, @@ -206,10 +239,13 @@ pub(crate) async fn recover_stale_publish_repair_for_workspace_with_project_repo AgentConversationWorkspace, StalePublishRepairRecoveryOutcome, )> { - if agent_run_repo - .get_active_for_conversation(&workspace.conversation_id) - .await? - .is_none() + if !has_active_agent_workspace_runtime( + workspace_repo.as_ref(), + repair_repo.as_ref(), + agent_run_repo.as_ref(), + &workspace, + ) + .await? { let publication_events = workspace_repo .list_publication_events(&workspace.conversation_id) @@ -273,6 +309,7 @@ pub(crate) async fn recover_stale_publish_repair_for_workspace_with_project_repo } recover_stale_publish_repair_for_workspace_and_reload_with_review_target( workspace_repo, + repair_repo, agent_run_repo, workspace, None, @@ -315,12 +352,14 @@ async fn abort_invalid_pr_fix_review_handoff( #[cfg(any(test, feature = "test-utils"))] pub async fn recover_stale_publish_repair_for_workspace_and_reload( workspace_repo: Arc, + repair_repo: Arc, agent_run_repo: Arc, workspace: AgentConversationWorkspace, ) -> AppResult { let (workspace, _outcome) = recover_stale_publish_repair_for_workspace_and_reload_with_review_target( workspace_repo, + repair_repo, agent_run_repo, workspace, None, @@ -332,6 +371,7 @@ pub async fn recover_stale_publish_repair_for_workspace_and_reload( #[cfg(any(test, feature = "test-utils"))] pub(crate) async fn recover_stale_publish_repair_for_workspace_and_reload_with_review_target( workspace_repo: Arc, + repair_repo: Arc, agent_run_repo: Arc, workspace: AgentConversationWorkspace, current_review_target: Option<&AgentWorkspaceReviewTarget>, @@ -342,6 +382,7 @@ pub(crate) async fn recover_stale_publish_repair_for_workspace_and_reload_with_r let conversation_id = workspace.conversation_id; let recovered = recover_stale_publish_repair_for_workspace_with_review_target( Arc::clone(&workspace_repo), + repair_repo, agent_run_repo, workspace.clone(), current_review_target, @@ -380,11 +421,13 @@ async fn current_pr_fix_review_handoff_target( #[cfg(any(test, feature = "test-utils"))] pub async fn recover_stale_publish_repair_for_workspace( workspace_repo: Arc, + repair_repo: Arc, agent_run_repo: Arc, workspace: AgentConversationWorkspace, ) -> AppResult { recover_stale_publish_repair_for_workspace_with_review_target( workspace_repo, + repair_repo, agent_run_repo, workspace, None, @@ -396,6 +439,7 @@ pub async fn recover_stale_publish_repair_for_workspace( #[cfg(any(test, feature = "test-utils"))] async fn recover_stale_publish_repair_for_workspace_with_review_target( workspace_repo: Arc, + repair_repo: Arc, agent_run_repo: Arc, mut workspace: AgentConversationWorkspace, current_review_target: Option<&AgentWorkspaceReviewTarget>, @@ -460,12 +504,16 @@ async fn recover_stale_publish_repair_for_workspace_with_review_target( } return Ok(StalePublishRepairRecoveryOutcome::ActiveReplacement); } - if agent_run_repo - .get_active_for_conversation(&workspace.conversation_id) - .await? - .is_some() + if has_active_agent_workspace_runtime( + workspace_repo.as_ref(), + repair_repo.as_ref(), + agent_run_repo.as_ref(), + &workspace, + ) + .await? && reconcile_active_agent_workspace_repair( Arc::clone(&workspace_repo), + Arc::clone(&repair_repo), Arc::clone(&agent_run_repo), &workspace, ) diff --git a/src-tauri/src/application/agent_workspace_publish_recovery/durable_attempt_recovery.rs b/src-tauri/src/application/agent_workspace_publish_recovery/durable_attempt_recovery.rs index 57ab1b4247..90e80b342e 100644 --- a/src-tauri/src/application/agent_workspace_publish_recovery/durable_attempt_recovery.rs +++ b/src-tauri/src/application/agent_workspace_publish_recovery/durable_attempt_recovery.rs @@ -93,6 +93,7 @@ pub(crate) async fn recover_stale_publish_repair_for_workspace_in_state_result( if is_legacy_pr_fix_review_projection(&workspace) { return super::recover_stale_publish_repair_for_workspace_with_project_repo_outcome( Arc::clone(&state.agent_conversation_workspace_repo), + Arc::clone(&state.agent_workspace_repair_repo), Arc::clone(&state.agent_run_repo), Arc::clone(&state.project_repo), workspace, diff --git a/src-tauri/src/application/agent_workspace_publish_recovery_tests.rs b/src-tauri/src/application/agent_workspace_publish_recovery_tests.rs index 7cf25dea73..8ab51fc798 100644 --- a/src-tauri/src/application/agent_workspace_publish_recovery_tests.rs +++ b/src-tauri/src/application/agent_workspace_publish_recovery_tests.rs @@ -49,9 +49,9 @@ use crate::domain::entities::{ use crate::domain::repositories::{ AcquireGitTargetLease, AcquireGitTargetLeaseOutcome, AgentConversationWorkspaceRepository, AgentRunRepository, AgentWorkspaceRepairAttemptTransition, - AgentWorkspaceRepairAttemptTransitionOutcome, CreateAgentWorkspaceRepairEffect, - CreateAgentWorkspaceRepairEffectOutcome, StartOrJoinAgentWorkspaceRepairAttempt, - StartOrJoinAgentWorkspaceRepairAttemptOutcome, + AgentWorkspaceRepairAttemptTransitionOutcome, AgentWorkspaceRepairRepository, + CreateAgentWorkspaceRepairEffect, CreateAgentWorkspaceRepairEffectOutcome, + StartOrJoinAgentWorkspaceRepairAttempt, StartOrJoinAgentWorkspaceRepairAttemptOutcome, }; use crate::infrastructure::memory::{ MemoryAgentConversationWorkspaceRepository, MemoryAgentProviderSettingsRepository, @@ -611,7 +611,8 @@ async fn startup_recovery_wrappers_finish_on_empty_repositories() { let agent_run_repo = Arc::new(MemoryAgentRunRepository::new()); recover_stale_agent_workspace_publish_repairs_on_startup( - workspace_repo as Arc, + workspace_repo.clone() as Arc, + workspace_repo as Arc, agent_run_repo as Arc, ) .await; @@ -3033,6 +3034,7 @@ async fn failed_exact_pr_autofix_is_classified_as_retry_eligible() { let (_workspace, outcome) = recover_stale_publish_repair_for_workspace_with_project_repo_outcome( Arc::clone(&state.agent_conversation_workspace_repo), + Arc::clone(&state.agent_workspace_repair_repo), Arc::clone(&state.agent_run_repo), Arc::clone(&state.project_repo), workspace, @@ -3165,6 +3167,7 @@ async fn recovery_correlates_the_exact_pr_autofix_attempt_not_a_newer_unrelated_ let (updated, outcome) = recover_stale_publish_repair_for_workspace_and_reload_with_review_target( Arc::clone(&workspace_repo) as Arc, + Arc::clone(&workspace_repo) as Arc, Arc::clone(&agent_run_repo) as Arc, workspace, None, @@ -3212,6 +3215,7 @@ async fn recovery_with_review_target_preserves_current_reviewing_handoff() { let (refreshed, outcome) = recover_stale_publish_repair_for_workspace_and_reload_with_review_target( Arc::clone(&workspace_repo) as Arc, + Arc::clone(&workspace_repo) as Arc, Arc::clone(&agent_run_repo) as Arc, workspace, Some(&target), @@ -3265,6 +3269,7 @@ async fn stale_review_handoff_without_matching_target_is_recovered_and_reloaded( let refreshed = recover_stale_publish_repair_for_workspace_and_reload( Arc::clone(&workspace_repo) as Arc, + Arc::clone(&workspace_repo) as Arc, Arc::clone(&agent_run_repo) as Arc, workspace, ) @@ -3306,6 +3311,7 @@ async fn batch_recovery_counts_only_recovered_workspaces() { let recovered = recover_stale_agent_workspace_publish_repairs( Arc::clone(&workspace_repo) as Arc, + Arc::clone(&workspace_repo) as Arc, Arc::clone(&agent_run_repo) as Arc, ) .await @@ -3352,6 +3358,7 @@ async fn recovery_heals_only_an_active_current_repair_to_fixing() { let refreshed = recover_stale_publish_repair_for_workspace_and_reload( Arc::clone(&workspace_repo) as Arc, + Arc::clone(&workspace_repo) as Arc, Arc::clone(&agent_run_repo) as Arc, workspace, ) @@ -3399,6 +3406,7 @@ async fn recovery_restores_blocked_state_only_for_the_current_pr_autofix_replace let (refreshed, outcome) = recover_stale_publish_repair_for_workspace_and_reload_with_review_target( Arc::clone(&workspace_repo) as Arc, + Arc::clone(&workspace_repo) as Arc, Arc::clone(&agent_run_repo) as Arc, workspace, None, @@ -3452,6 +3460,7 @@ async fn recovery_does_not_treat_an_unrelated_active_run_as_a_pr_autofix_replace let (refreshed, outcome) = recover_stale_publish_repair_for_workspace_and_reload_with_review_target( Arc::clone(&workspace_repo) as Arc, + Arc::clone(&workspace_repo) as Arc, Arc::clone(&agent_run_repo) as Arc, workspace, None, @@ -3522,6 +3531,7 @@ mod extracted_inline_tests { let recovered = recover_stale_agent_workspace_publish_repairs( Arc::clone(&workspace_repo) as Arc, + Arc::clone(&workspace_repo) as Arc, Arc::clone(&agent_run_repo) as Arc, ) .await @@ -3593,6 +3603,7 @@ mod extracted_inline_tests { let recovered = recover_stale_agent_workspace_publish_repairs( Arc::clone(&workspace_repo) as Arc, + Arc::clone(&workspace_repo) as Arc, Arc::clone(&agent_run_repo) as Arc, ) .await @@ -3622,6 +3633,7 @@ mod extracted_inline_tests { recover_stale_agent_workspace_publish_repairs_on_startup( Arc::clone(&workspace_repo) as Arc, + Arc::clone(&workspace_repo) as Arc, Arc::clone(&agent_run_repo) as Arc, ) .await; @@ -3635,6 +3647,7 @@ mod extracted_inline_tests { recover_stale_agent_workspace_publish_repairs_on_startup( Arc::clone(&workspace_repo) as Arc, + Arc::clone(&workspace_repo) as Arc, Arc::clone(&agent_run_repo) as Arc, ) .await; @@ -3665,6 +3678,7 @@ mod extracted_inline_tests { let recovered = recover_stale_publish_repair_for_workspace( Arc::clone(&workspace_repo) as Arc, + Arc::clone(&workspace_repo) as Arc, Arc::clone(&agent_run_repo) as Arc, workspace, ) @@ -3702,6 +3716,7 @@ mod extracted_inline_tests { let recovered = recover_stale_publish_repair_for_workspace( Arc::clone(&workspace_repo) as Arc, + Arc::clone(&workspace_repo) as Arc, agent_run_repo, workspace, ) @@ -3721,6 +3736,7 @@ mod extracted_inline_tests { let recovered = recover_stale_publish_repair_for_workspace( Arc::clone(&workspace_repo) as Arc, + Arc::clone(&workspace_repo) as Arc, agent_run_repo, workspace, ) @@ -3746,10 +3762,14 @@ mod extracted_inline_tests { run.completed_at = None; agent_run_repo.create(run).await.expect("seed run"); - let recovered = - recover_stale_publish_repair_for_workspace(workspace_repo, agent_run_repo, workspace) - .await - .expect("check repair state"); + let recovered = recover_stale_publish_repair_for_workspace( + workspace_repo.clone(), + workspace_repo, + agent_run_repo, + workspace, + ) + .await + .expect("check repair state"); assert!(recovered); } @@ -3770,6 +3790,7 @@ mod extracted_inline_tests { let recovered = recover_stale_publish_repair_for_workspace( Arc::clone(&workspace_repo) as Arc, + Arc::clone(&workspace_repo) as Arc, agent_run_repo, workspace, ) @@ -3822,6 +3843,7 @@ mod extracted_inline_tests { let recovered = recover_stale_publish_repair_for_workspace( Arc::clone(&workspace_repo) as Arc, + Arc::clone(&workspace_repo) as Arc, agent_run_repo, workspace, ) @@ -4596,7 +4618,8 @@ wait "$stdin_drain_pid" 2>/dev/null || true .await .expect("load current attempt after recovery") .expect("current attempt exists after recovery"); - let message = latest_sent_repair_message(&state, current_attempt.runtime_conversation_id()).await; + let message = + latest_sent_repair_message(&state, current_attempt.runtime_conversation_id()).await; assert!( message.contains("redelivering an interrupted PR fix"), "PR autofix redelivery must use the PR fixer assignment, got: {message}" @@ -4650,7 +4673,8 @@ wait "$stdin_drain_pid" 2>/dev/null || true .await .expect("load current attempt after recovery") .expect("current attempt exists after recovery"); - let message = latest_sent_repair_message(&state, current_attempt.runtime_conversation_id()).await; + let message = + latest_sent_repair_message(&state, current_attempt.runtime_conversation_id()).await; assert!( message.contains("complete_agent_workspace_repair"), "a publish repair must name the repairer's own completion tool: {message}" diff --git a/src-tauri/src/application/agent_workspace_publish_repair_state.rs b/src-tauri/src/application/agent_workspace_publish_repair_state.rs index 99a5142130..d6da729fa2 100644 --- a/src-tauri/src/application/agent_workspace_publish_repair_state.rs +++ b/src-tauri/src/application/agent_workspace_publish_repair_state.rs @@ -5,6 +5,8 @@ use std::sync::Arc; use chrono::{DateTime, Duration, Utc}; use crate::application::agent_conversation_workspace::resolve_effective_agent_conversation_workspace_path; +#[cfg(any(test, feature = "test-utils"))] +use crate::application::agent_workspace_fixer_conversation::agent_workspace_fixer_runtime_conversations; use crate::application::agent_workspace_review::{ load_agent_workspace_review_context, load_workspace_review_publish_blocker, review_gate_publish_blocker, AgentWorkspaceReviewStart, @@ -562,9 +564,7 @@ pub(crate) async fn block_agent_workspace_repair_needs_human( /// True while a PR autofix generation is parked against a backend-derived health fingerprint. /// Only the poller may end such a hold, and only after GitHub reports different health; no other /// retry path may consume the hold's budget or settle it on a timer. -pub(crate) fn agent_workspace_repair_is_health_held( - attempt: &AgentWorkspaceRepairAttempt, -) -> bool { +pub(crate) fn agent_workspace_repair_is_health_held(attempt: &AgentWorkspaceRepairAttempt) -> bool { attempt.pending_reasons.iter().any(|reason| { reason == PRE_EXISTING_ON_BASE_REPAIR_REASON || reason == UNCHANGED_HEALTH_REPAIR_REASON }) @@ -2379,6 +2379,7 @@ pub(crate) async fn settle_terminal_agent_workspace_repair( #[cfg(any(test, feature = "test-utils"))] pub(crate) async fn reconcile_active_agent_workspace_repair( workspace_repo: Arc, + repair_repo: Arc, agent_run_repo: Arc, workspace: &AgentConversationWorkspace, ) -> AppResult { @@ -2387,18 +2388,31 @@ pub(crate) async fn reconcile_active_agent_workspace_repair( { return Ok(false); } - let Some(active_run) = agent_run_repo - .get_active_for_conversation(&workspace.conversation_id) - .await? - else { - return Ok(false); - }; + let runtime_conversations = agent_workspace_fixer_runtime_conversations( + workspace, + workspace_repo.as_ref(), + repair_repo.as_ref(), + ) + .await?; let events = workspace_repo .list_publication_events(&workspace.conversation_id) .await?; - if !repair_event_authorizes_active_run(&events, &active_run) { - return Ok(false); + let mut active_run = None; + for runtime_conversation_id in runtime_conversations { + if let Some(run) = agent_run_repo + .get_active_for_conversation(&runtime_conversation_id) + .await? + { + if repair_event_authorizes_active_run(&events, &run) { + active_run = Some(run); + break; + } + } } + let Some(active_run) = active_run else { + return Ok(false); + }; + let _active_run = active_run; let transition = AgentWorkspaceRepairStateTransition { publication_push_status: Some("needs_agent".to_string()), @@ -2420,6 +2434,7 @@ pub(crate) async fn reconcile_active_agent_workspace_repair( #[cfg(test)] pub(crate) async fn current_agent_workspace_repair_claim_for_completion( workspace_repo: Arc, + repair_repo: Arc, agent_run_repo: Arc, workspace: &AgentConversationWorkspace, ) -> AppResult> { @@ -2428,21 +2443,34 @@ pub(crate) async fn current_agent_workspace_repair_claim_for_completion( { return Ok(None); } - let Some(active_run) = agent_run_repo - .get_active_for_conversation(&workspace.conversation_id) - .await? - else { - return Ok(None); - }; + let runtime_conversations = agent_workspace_fixer_runtime_conversations( + workspace, + workspace_repo.as_ref(), + repair_repo.as_ref(), + ) + .await?; let events = workspace_repo .list_publication_events(&workspace.conversation_id) .await?; let Some(claim_started_at) = workspace.pr_supervision_updated_at else { return Ok(None); }; - if !successful_send_authorizes_completion(&events, &active_run, claim_started_at) { - return Ok(None); + let mut active_run = None; + for runtime_conversation_id in runtime_conversations { + if let Some(run) = agent_run_repo + .get_active_for_conversation(&runtime_conversation_id) + .await? + { + if successful_send_authorizes_completion(&events, &run, claim_started_at) { + active_run = Some(run); + break; + } + } } + let Some(active_run) = active_run else { + return Ok(None); + }; + let _active_run = active_run; Ok(Some(AgentWorkspaceRepairClaim { conversation_id: workspace.conversation_id.clone(), guard: AgentWorkspaceRepairStateGuard::from_workspace(workspace), diff --git a/src-tauri/src/application/agent_workspace_publish_repair_state_tests.rs b/src-tauri/src/application/agent_workspace_publish_repair_state_tests.rs index e6c03e9a94..b4e86a0077 100644 --- a/src-tauri/src/application/agent_workspace_publish_repair_state_tests.rs +++ b/src-tauri/src/application/agent_workspace_publish_repair_state_tests.rs @@ -2726,6 +2726,7 @@ async fn active_reconciliation_requires_current_successful_lifecycle_evidence() .unwrap(); assert!(!reconcile_active_agent_workspace_repair( Arc::clone(&workspace_repo) as Arc, + Arc::clone(&workspace_repo) as Arc, Arc::clone(&run_repo) as Arc, &workspace, ) @@ -2744,6 +2745,7 @@ async fn active_reconciliation_requires_current_successful_lifecycle_evidence() .unwrap(); assert!(reconcile_active_agent_workspace_repair( Arc::clone(&workspace_repo) as Arc, + Arc::clone(&workspace_repo) as Arc, Arc::clone(&run_repo) as Arc, &workspace, ) @@ -2789,6 +2791,7 @@ async fn stale_completion_claim_cannot_overwrite_a_failed_attempt() { .unwrap(); let claim = current_agent_workspace_repair_claim_for_completion( Arc::clone(&workspace_repo) as Arc, + Arc::clone(&workspace_repo) as Arc, Arc::clone(&run_repo) as Arc, &workspace, ) @@ -2866,6 +2869,7 @@ async fn completion_requires_dispatch_evidence_for_the_current_claim() { assert!(current_agent_workspace_repair_claim_for_completion( Arc::clone(&workspace_repo) as Arc, + Arc::clone(&workspace_repo) as Arc, Arc::clone(&run_repo) as Arc, &claimed_workspace, ) @@ -2885,7 +2889,8 @@ async fn completion_requires_dispatch_evidence_for_the_current_claim() { .unwrap(); assert_eq!( current_agent_workspace_repair_claim_for_completion( - workspace_repo as Arc, + workspace_repo.clone() as Arc, + workspace_repo as Arc, run_repo as Arc, &claimed_workspace, ) diff --git a/src-tauri/src/application/agent_workspace_review_unfinished_git_recovery_tests.rs b/src-tauri/src/application/agent_workspace_review_unfinished_git_recovery_tests.rs index 36a43139e8..546e0553b5 100644 --- a/src-tauri/src/application/agent_workspace_review_unfinished_git_recovery_tests.rs +++ b/src-tauri/src/application/agent_workspace_review_unfinished_git_recovery_tests.rs @@ -17,7 +17,8 @@ use crate::domain::entities::{ IdeationAnalysisBaseRefKind, Project, }; use crate::domain::repositories::{ - AgentConversationWorkspaceRepository, AgentRunRepository, PlanBranchRepository, + AgentConversationWorkspaceRepository, AgentRunRepository, AgentWorkspaceRepairRepository, + PlanBranchRepository, }; use crate::domain::services::GithubServiceTrait; use crate::infrastructure::memory::{ @@ -252,6 +253,7 @@ async fn pending_review_handoff_without_current_attempt_evidence_aborts_before_t let (after, recovered) = recover_stale_publish_repair_for_workspace_with_project_repo( Arc::clone(&workspace_repo) as Arc, + Arc::clone(&workspace_repo) as Arc, Arc::new(MemoryAgentRunRepository::new()) as Arc, Arc::new(MemoryProjectRepository::with_projects(vec![project])), workspace, diff --git a/src-tauri/src/application/agent_workspace_terminal_cleanup.rs b/src-tauri/src/application/agent_workspace_terminal_cleanup.rs index bb9fb5d804..81b4ed98c4 100644 --- a/src-tauri/src/application/agent_workspace_terminal_cleanup.rs +++ b/src-tauri/src/application/agent_workspace_terminal_cleanup.rs @@ -10,6 +10,7 @@ use crate::application::agent_conversation_workspace::{ resolve_agent_conversation_workspace_path_from_record_identity, resolve_linked_plan_branch_agent_worktree_path, validate_workspace_linked_plan_branch, }; +use crate::application::agent_workspace_fixer_conversation::agent_workspace_fixer_runtime_conversations; use crate::application::chat_service::ChatService; use crate::application::git_artifact_cleanup::{ cleanup_terminal_agent_workspace_local_artifacts, @@ -25,7 +26,7 @@ use crate::domain::entities::{ }; use crate::domain::repositories::{ AgentConversationWorkspaceRepository, AgentRunRepository, AgentWorkspaceLocalCleanupClaim, - PlanBranchRepository, + AgentWorkspaceRepairRepository, PlanBranchRepository, }; use crate::domain::services::kill_worktree_processes_async; use crate::infrastructure::agents::claude::git_runtime_config; @@ -118,6 +119,7 @@ enum TerminalCleanupTarget { pub(crate) async fn terminalize_agent_workspace_after_pr( workspace_repo: Arc, + repair_repo: Arc, agent_run_repo: Arc, plan_branch_repo: Option>, chat_service: Option>, @@ -125,36 +127,88 @@ pub(crate) async fn terminalize_agent_workspace_after_pr( project: &Project, cause: TerminalAgentWorkspaceCause, ) -> TerminalAgentWorkspaceOutcome { - let active_run = match agent_run_repo - .get_active_for_conversation(conversation_id) - .await + let workspace = match workspace_repo.get_by_conversation_id(conversation_id).await { + Ok(Some(workspace)) => workspace, + Ok(None) => { + return TerminalAgentWorkspaceOutcome::runtime_blocked( + "Agent workspace disappeared before terminal runtime cleanup".to_string(), + ); + } + Err(error) => { + return TerminalAgentWorkspaceOutcome::runtime_blocked(format!( + "Failed to load the workspace before terminal runtime cleanup: {error}" + )); + } + }; + let mut runtime_conversations = match agent_workspace_fixer_runtime_conversations( + &workspace, + workspace_repo.as_ref(), + repair_repo.as_ref(), + ) + .await { - Ok(run) => run, + Ok(conversations) => conversations, Err(error) => { return TerminalAgentWorkspaceOutcome::runtime_blocked(format!( - "Failed to inspect the active workspace run: {error}" + "Failed to resolve workspace fixer runtimes: {error}" )); } }; + match workspace_repo + .get_workspace_review_monitor(conversation_id) + .await + { + Ok(Some(monitor)) => { + if let Some(review_conversation_id) = monitor.review_conversation_id { + if !runtime_conversations.contains(&review_conversation_id) { + runtime_conversations.push(review_conversation_id); + } + } + } + Ok(None) => {} + Err(error) => { + return TerminalAgentWorkspaceOutcome::runtime_blocked(format!( + "Failed to resolve the Workspace Review runtime: {error}" + )); + } + } - if active_run.is_some() || chat_service.is_some() { + let mut active_runs = Vec::new(); + for runtime_conversation_id in &runtime_conversations { + match agent_run_repo + .get_active_for_conversation(runtime_conversation_id) + .await + { + Ok(Some(run)) => active_runs.push(run), + Ok(None) => {} + Err(error) => { + return TerminalAgentWorkspaceOutcome::runtime_blocked(format!( + "Failed to inspect an active workspace runtime: {error}" + )); + } + } + } + + if !active_runs.is_empty() || chat_service.is_some() { let Some(chat_service) = chat_service.as_ref() else { return TerminalAgentWorkspaceOutcome::runtime_blocked( "An active workspace run could not be stopped because no chat runtime was available" .to_string(), ); }; - if let Err(error) = chat_service - .stop_agent(ChatContextType::Project, &conversation_id.as_str()) - .await - { - return TerminalAgentWorkspaceOutcome::runtime_blocked(format!( - "Failed to stop the workspace runtime: {error}" - )); + for runtime_conversation_id in &runtime_conversations { + if let Err(error) = chat_service + .stop_agent(ChatContextType::Project, &runtime_conversation_id.as_str()) + .await + { + return TerminalAgentWorkspaceOutcome::runtime_blocked(format!( + "Failed to stop the workspace runtime: {error}" + )); + } } } - if let Some(run) = active_run { + for run in active_runs { if let Err(error) = agent_run_repo.fail(&run.id, &cause.stop_reason()).await { return TerminalAgentWorkspaceOutcome::runtime_blocked(format!( "Failed to persist the terminal workspace run result: {error}" @@ -162,21 +216,23 @@ pub(crate) async fn terminalize_agent_workspace_after_pr( } } - match agent_run_repo - .get_active_for_conversation(conversation_id) - .await - { - Ok(None) => {} - Ok(Some(run)) => { - return TerminalAgentWorkspaceOutcome::runtime_blocked(format!( - "Workspace run {} remained active after stop", - run.id.as_str() - )); - } - Err(error) => { - return TerminalAgentWorkspaceOutcome::runtime_blocked(format!( - "Failed to verify the stopped workspace runtime: {error}" - )); + for runtime_conversation_id in &runtime_conversations { + match agent_run_repo + .get_active_for_conversation(runtime_conversation_id) + .await + { + Ok(None) => {} + Ok(Some(run)) => { + return TerminalAgentWorkspaceOutcome::runtime_blocked(format!( + "Workspace run {} remained active after stop", + run.id.as_str() + )); + } + Err(error) => { + return TerminalAgentWorkspaceOutcome::runtime_blocked(format!( + "Failed to verify the stopped workspace runtime: {error}" + )); + } } } @@ -192,6 +248,7 @@ pub(crate) async fn terminalize_agent_workspace_after_pr( #[allow(clippy::too_many_arguments)] pub(crate) async fn settle_review_pr_terminal_observation( workspace_repo: Arc, + repair_repo: Arc, agent_run_repo: Arc, plan_branch_repo: Option>, chat_service: Option>, @@ -219,6 +276,7 @@ pub(crate) async fn settle_review_pr_terminal_observation( Ok(terminalize_agent_workspace_after_pr( workspace_repo, + repair_repo, agent_run_repo, plan_branch_repo, chat_service, diff --git a/src-tauri/src/application/agent_workspace_terminal_cleanup_tests.rs b/src-tauri/src/application/agent_workspace_terminal_cleanup_tests.rs index 680724e999..9a7b166200 100644 --- a/src-tauri/src/application/agent_workspace_terminal_cleanup_tests.rs +++ b/src-tauri/src/application/agent_workspace_terminal_cleanup_tests.rs @@ -319,6 +319,7 @@ async fn terminal_cleanup_blocks_deletion_while_an_active_run_cannot_be_stopped( .expect("persist active run"); let outcome = terminalize_agent_workspace_after_pr( + workspace_repo.clone(), workspace_repo.clone(), run_repo, None, @@ -418,6 +419,7 @@ async fn terminalize_stops_active_run_and_records_archive_reason_before_cleanup( let chat_service = Arc::new(MockChatService::new()); let outcome = terminalize_agent_workspace_after_pr( + workspace_repo.clone(), workspace_repo.clone(), run_repo.clone(), None, @@ -704,6 +706,7 @@ async fn terminalize_blocks_cleanup_when_active_run_lookup_fails() { )])); let outcome = terminalize_agent_workspace_after_pr( + workspace_repo.clone(), workspace_repo.clone(), run_repo, None, @@ -750,6 +753,7 @@ async fn terminalize_blocks_cleanup_when_runtime_stop_fails() { chat_service.fail_next_stop_agent_calls(1).await; let outcome = terminalize_agent_workspace_after_pr( + workspace_repo.clone(), workspace_repo.clone(), run_repo, None, @@ -800,6 +804,7 @@ async fn terminalize_blocks_cleanup_when_failed_run_cannot_be_persisted() { let chat_service = Arc::new(MockChatService::new()); let outcome = terminalize_agent_workspace_after_pr( + workspace_repo.clone(), workspace_repo.clone(), run_repo, None, @@ -845,6 +850,7 @@ async fn terminalize_blocks_cleanup_when_active_run_remains_after_stop() { let chat_service = Arc::new(MockChatService::new()); let outcome = terminalize_agent_workspace_after_pr( + workspace_repo.clone(), workspace_repo.clone(), run_repo, None, @@ -890,6 +896,7 @@ async fn terminalize_blocks_cleanup_when_post_stop_run_lookup_fails() { let chat_service = Arc::new(MockChatService::new()); let outcome = terminalize_agent_workspace_after_pr( + workspace_repo.clone(), workspace_repo.clone(), run_repo, None, diff --git a/src-tauri/src/application/app_state.rs b/src-tauri/src/application/app_state.rs index 669671772c..599e506c4f 100644 --- a/src-tauri/src/application/app_state.rs +++ b/src-tauri/src/application/app_state.rs @@ -1795,6 +1795,9 @@ impl AppState { state .pr_poller_registry .set_branch_update_repo(Arc::clone(&state.branch_update_repo)); + state + .pr_poller_registry + .set_chat_conversation_repo(Arc::clone(&state.chat_conversation_repo)); Ok(state) } diff --git a/src-tauri/src/application/automation/merged_run_finalizer.rs b/src-tauri/src/application/automation/merged_run_finalizer.rs index 18941d7d26..cc8daf4b21 100644 --- a/src-tauri/src/application/automation/merged_run_finalizer.rs +++ b/src-tauri/src/application/automation/merged_run_finalizer.rs @@ -66,6 +66,7 @@ impl AutomationMergedRunFinalizer for AppStateAutomationMergedRunFinalizer { let chat_service: Arc = Arc::new(self.state.build_chat_service()); let terminalized = terminalize_agent_workspace_after_pr( Arc::clone(&self.state.agent_conversation_workspace_repo), + Arc::clone(&self.state.agent_workspace_repair_repo), Arc::clone(&self.state.agent_run_repo), Some(Arc::clone(&self.state.plan_branch_repo)), Some(chat_service), diff --git a/src-tauri/src/application/pr_startup_recovery.rs b/src-tauri/src/application/pr_startup_recovery.rs index b71cf55be2..6c9d40aeec 100644 --- a/src-tauri/src/application/pr_startup_recovery.rs +++ b/src-tauri/src/application/pr_startup_recovery.rs @@ -1385,8 +1385,16 @@ pub(crate) async fn recover_one_agent_workspace_pr_poller( } else { "Pull request closed without merging" }; + let Some(repair_repo) = agent_workspace_repair_repo.as_ref().map(Arc::clone) else { + tracing::error!( + conversation_id = workspace.conversation_id.as_str(), + "Terminal workspace recovery requires durable repair authority" + ); + return; + }; match settle_review_pr_terminal_observation( Arc::clone(&workspace_repo), + repair_repo, Arc::clone(&agent_run_repo), Some(Arc::clone(&plan_branch_repo)), Some(Arc::clone(&chat_service)), @@ -1516,8 +1524,16 @@ pub(crate) async fn recover_one_agent_workspace_pr_poller( PrStatus::Closed => ("closed", "Pull request closed without merging"), PrStatus::Open => unreachable!(), }; + let Some(repair_repo) = agent_workspace_repair_repo.as_ref().map(Arc::clone) else { + tracing::error!( + conversation_id = workspace.conversation_id.as_str(), + "Terminal workspace recovery requires durable repair authority" + ); + return; + }; match settle_review_pr_terminal_observation( Arc::clone(&workspace_repo), + repair_repo, Arc::clone(&agent_run_repo), Some(Arc::clone(&plan_branch_repo)), Some(Arc::clone(&chat_service)), diff --git a/src-tauri/src/application/services/pr_merge_poller.rs b/src-tauri/src/application/services/pr_merge_poller.rs index d223b449b1..15d487b816 100644 --- a/src-tauri/src/application/services/pr_merge_poller.rs +++ b/src-tauri/src/application/services/pr_merge_poller.rs @@ -16,6 +16,11 @@ use tokio::task::JoinHandle; use crate::application::agent_conversation_workspace::{ agent_name_for_workspace_mode, resolve_valid_agent_conversation_workspace_path, }; +use crate::application::agent_workspace_fixer_conversation::{ + agent_workspace_fixer_runtime_conversations, + ensure_agent_workspace_fixer_conversation_with_repo, AgentWorkspaceFixerKind, + AgentWorkspaceFixerTitleContext, +}; use crate::application::agent_workspace_pr_autofix_attempt::{ load_pr_autofix_attempt_decision, pr_autofix_action_metadata, }; @@ -59,8 +64,8 @@ use crate::domain::entities::{ }; use crate::domain::repositories::{ AgentConversationWorkspaceRepository, AgentRunRepository, AgentWorkspaceRepairRepository, - BranchUpdateRepository, PlanBranchRepository, SettleAgentWorkspaceRepairAttempt, - SettleAgentWorkspaceRepairAttemptOutcome, + BranchUpdateRepository, ChatConversationRepository, PlanBranchRepository, + SettleAgentWorkspaceRepairAttempt, SettleAgentWorkspaceRepairAttemptOutcome, }; use crate::domain::services::github_service::{ PrHealth, PrHealthCheck, PrMergeStateStatus, PrMergeableState, PrReviewCommentFeedback, @@ -153,6 +158,9 @@ pub struct PrPollerRegistry { /// Canonical Git target authority for durable repair dispatch. It is installed by the /// production AppState once and copied into each direct workspace poller. branch_update_repo: Arc>>>, + + /// Conversation persistence for attempt-owned fixer children. + chat_conversation_repo: Arc>>>, } #[derive(Debug, Clone, Copy, PartialEq, Eq)] @@ -233,6 +241,7 @@ impl PrPollerRegistry { plan_branch_repo, notification_service: Arc::new(std::sync::RwLock::new(None)), branch_update_repo: Arc::new(std::sync::RwLock::new(None)), + chat_conversation_repo: Arc::new(std::sync::RwLock::new(None)), } } @@ -242,6 +251,12 @@ impl PrPollerRegistry { } } + pub fn set_chat_conversation_repo(&self, repo: Arc) { + if let Ok(mut current) = self.chat_conversation_repo.write() { + *current = Some(repo); + } + } + pub fn set_notification_service(&self, service: Arc) { if let Ok(mut current) = self.notification_service.write() { *current = Some(service); @@ -346,6 +361,11 @@ impl PrPollerRegistry { .read() .ok() .and_then(|repo| repo.clone()); + let chat_conversation_repo = self + .chat_conversation_repo + .read() + .ok() + .and_then(|repo| repo.clone()); let conversation_id_for_spawn = conversation_id.clone(); let handle = tokio::spawn(async move { @@ -362,6 +382,7 @@ impl PrPollerRegistry { agent_run_repo, repair_repo, branch_update_repo, + chat_conversation_repo, plan_branch_repo, chat_service, notification_service, @@ -610,6 +631,10 @@ impl PrPollerRegistry { .read() .ok() .and_then(|repo| repo.clone()), + self.chat_conversation_repo + .read() + .ok() + .and_then(|repo| repo.clone()), chat_service, ) .await @@ -1070,6 +1095,7 @@ async fn agent_workspace_poll_loop( agent_run_repo: Arc, repair_repo: Option>, branch_update_repo: Option>, + chat_conversation_repo: Option>, plan_branch_repo: Arc, chat_service: Arc, notification_service: Option>, @@ -1134,6 +1160,7 @@ async fn agent_workspace_poll_loop( drop(permit); terminalize_polled_agent_workspace_with_notifications( &workspace_repo, + repair_repo.as_ref(), &agent_run_repo, &plan_branch_repo, &chat_service, @@ -1156,6 +1183,7 @@ async fn agent_workspace_poll_loop( drop(permit); terminalize_polled_agent_workspace_with_notifications( &workspace_repo, + repair_repo.as_ref(), &agent_run_repo, &plan_branch_repo, &chat_service, @@ -1245,6 +1273,7 @@ async fn agent_workspace_poll_loop( Some(Arc::clone(&agent_run_repo)), repair_repo.as_ref().map(Arc::clone), branch_update_repo.as_ref().map(Arc::clone), + chat_conversation_repo.as_ref().map(Arc::clone), Arc::clone(&chat_service), ) .await @@ -1289,6 +1318,7 @@ async fn agent_workspace_poll_loop( Some(Arc::clone(&agent_run_repo)), repair_repo.as_ref().map(Arc::clone), branch_update_repo.as_ref().map(Arc::clone), + chat_conversation_repo.as_ref().map(Arc::clone), Arc::clone(&chat_service), notification_service.as_ref().map(Arc::clone), ) @@ -1354,6 +1384,7 @@ async fn agent_workspace_poll_loop( Some(Arc::clone(&agent_run_repo)), repair_repo.as_ref().map(Arc::clone), branch_update_repo.as_ref().map(Arc::clone), + chat_conversation_repo.as_ref().map(Arc::clone), Arc::clone(&chat_service), ) .await @@ -1396,6 +1427,7 @@ async fn agent_workspace_poll_loop( #[cfg(test)] async fn terminalize_polled_agent_workspace( workspace_repo: &Arc, + repair_repo: &Arc, agent_run_repo: &Arc, plan_branch_repo: &Arc, chat_service: &Arc, @@ -1410,6 +1442,7 @@ async fn terminalize_polled_agent_workspace( ) { terminalize_polled_agent_workspace_with_notifications( workspace_repo, + Some(repair_repo), agent_run_repo, plan_branch_repo, chat_service, @@ -1429,6 +1462,7 @@ async fn terminalize_polled_agent_workspace( #[allow(clippy::too_many_arguments)] async fn terminalize_polled_agent_workspace_with_notifications( workspace_repo: &Arc, + repair_repo: Option<&Arc>, agent_run_repo: &Arc, plan_branch_repo: &Arc, chat_service: &Arc, @@ -1483,9 +1517,17 @@ async fn terminalize_polled_agent_workspace_with_notifications( }; if let Some(pr_number) = review_pr_number { + let Some(repair_repo) = repair_repo else { + tracing::error!( + conversation_id = conversation_id.as_str(), + "Agent workspace terminal cleanup requires durable repair authority" + ); + return; + }; loop { match settle_review_pr_terminal_observation( Arc::clone(workspace_repo), + Arc::clone(repair_repo), Arc::clone(agent_run_repo), Some(Arc::clone(plan_branch_repo)), Some(Arc::clone(chat_service)), @@ -1552,8 +1594,16 @@ async fn terminalize_polled_agent_workspace_with_notifications( } loop { + let Some(repair_repo) = repair_repo else { + tracing::error!( + conversation_id = conversation_id.as_str(), + "Agent workspace terminal cleanup requires durable repair authority" + ); + return; + }; let terminalized = terminalize_agent_workspace_after_pr( Arc::clone(workspace_repo), + Arc::clone(repair_repo), Arc::clone(agent_run_repo), Some(Arc::clone(plan_branch_repo)), Some(Arc::clone(chat_service)), @@ -1989,6 +2039,7 @@ async fn route_agent_workspace_pr_conflict_repair_if_needed_with_repair_repo( _agent_run_repo: Option>, repair_repo: Option>, branch_update_repo: Option>, + chat_conversation_repo: Option>, chat_service: Arc, ) -> crate::AppResult { let details = agent_workspace_pr_merge_conflict_details(health); @@ -2006,6 +2057,18 @@ async fn route_agent_workspace_pr_conflict_repair_if_needed_with_repair_repo( .to_string(), )); }; + let chat_conversation_repo = match chat_conversation_repo { + Some(repo) => Some(repo), + None => { + #[cfg(not(test))] + return Err(AppError::Infrastructure( + "durable PR conflict repair dispatch requires fixer conversation persistence" + .to_string(), + )); + #[cfg(test)] + None + } + }; let workspace = workspace_repo .get_by_conversation_id(conversation_id) @@ -2124,13 +2187,26 @@ async fn route_agent_workspace_pr_conflict_repair_if_needed_with_repair_repo( let target_identity = GitService::canonical_target_identity(working_dir, &workspace.branch_name).await?; let repair_run_id = AgentRunId::new(); + let runtime_conversation_id = match chat_conversation_repo.as_ref() { + Some(chat_conversation_repo) => { + ensure_agent_workspace_fixer_conversation_with_repo( + chat_conversation_repo.as_ref(), + &workspace, + attempt.runtime_conversation_id.as_ref(), + AgentWorkspaceFixerKind::WorkspaceRepair, + AgentWorkspaceFixerTitleContext::Repair(attempt.source), + ) + .await? + } + None => workspace.conversation_id, + }; let dispatch = match reserve_agent_workspace_repair_dispatch( Arc::clone(&repair_repo), Arc::clone(&branch_update_repo), target_identity, attempt, repair_run_id.clone(), - None, + Some(runtime_conversation_id), &repair_summary, workspace.pr_auto_merge_current, ) @@ -2312,7 +2388,13 @@ async fn route_agent_workspace_pr_conflict_repair_legacy( || workspace.publication_pr_number != Some(pr_number) || workspace.has_terminal_publication_pr_status() || !workspace.auto_publish_enabled - || agent_workspace_pr_autofix_repair_in_flight(&workspace, agent_run_repo.as_ref()).await? + || agent_workspace_pr_autofix_repair_in_flight( + &workspace, + workspace_repo.as_ref(), + None, + agent_run_repo.as_ref(), + ) + .await? { return Ok(false); } @@ -2810,6 +2892,7 @@ async fn route_agent_workspace_pr_autofix_if_needed( agent_run_repo, None, None, + None, chat_service, ) .await @@ -2828,6 +2911,7 @@ async fn route_agent_workspace_pr_autofix_if_needed_with_repair_repo( agent_run_repo: Option>, repair_repo: Option>, branch_update_repo: Option>, + chat_conversation_repo: Option>, chat_service: Arc, ) -> crate::AppResult { route_agent_workspace_pr_autofix_if_needed_with_notifications( @@ -2839,6 +2923,7 @@ async fn route_agent_workspace_pr_autofix_if_needed_with_repair_repo( agent_run_repo, repair_repo, branch_update_repo, + chat_conversation_repo, chat_service, None, ) @@ -2855,6 +2940,7 @@ async fn route_agent_workspace_pr_autofix_if_needed_with_notifications( agent_run_repo: Option>, repair_repo: Option>, branch_update_repo: Option>, + chat_conversation_repo: Option>, chat_service: Arc, notification_service: Option>, ) -> crate::AppResult { @@ -2879,6 +2965,7 @@ async fn route_agent_workspace_pr_autofix_if_needed_with_notifications( agent_run_repo, repair_repo, branch_update_repo, + chat_conversation_repo, chat_service, notification_service, ) @@ -2933,6 +3020,7 @@ pub(crate) async fn route_ideation_plan_pr_autofix_if_needed( agent_run_repo, None, None, + None, chat_service, None, ) @@ -2950,6 +3038,7 @@ async fn route_agent_workspace_pr_autofix_for_target( agent_run_repo: Option>, repair_repo: Option>, branch_update_repo: Option>, + chat_conversation_repo: Option>, chat_service: Arc, notification_service: Option>, ) -> crate::AppResult { @@ -2982,7 +3071,13 @@ async fn route_agent_workspace_pr_autofix_for_target( )); } if repair_repo.is_none() - && agent_workspace_pr_autofix_repair_in_flight(&workspace, agent_run_repo.as_ref()).await? + && agent_workspace_pr_autofix_repair_in_flight( + &workspace, + workspace_repo.as_ref(), + repair_repo.as_ref(), + agent_run_repo.as_ref(), + ) + .await? { return Ok(false); } @@ -3249,6 +3344,7 @@ async fn route_agent_workspace_pr_autofix_for_target( dispatch_agent_workspace_pr_autofix( repair_repo, branch_update_repo, + chat_conversation_repo, workspace_repo, agent_run_repo, chat_service, @@ -3602,6 +3698,7 @@ struct AgentWorkspacePrAutofixDispatch<'a> { async fn dispatch_agent_workspace_pr_autofix( repair_repo: Option>, branch_update_repo: Option>, + chat_conversation_repo: Option>, workspace_repo: Arc, agent_run_repo: &Arc, chat_service: Arc, @@ -3644,6 +3741,17 @@ async fn dispatch_agent_workspace_pr_autofix( "durable PR autofix dispatch requires canonical Git target authority".to_string(), )); }; + let chat_conversation_repo = match chat_conversation_repo { + Some(repo) => Some(repo), + None => { + #[cfg(not(test))] + return Err(AppError::Infrastructure( + "durable PR autofix dispatch requires fixer conversation persistence".to_string(), + )); + #[cfg(test)] + None + } + }; let preallocated_run_id = AgentRunId::new(); let start = start_or_join_agent_workspace_repair( Arc::clone(&repair_repo), @@ -3755,13 +3863,26 @@ async fn dispatch_agent_workspace_pr_autofix( attempt.pr_autofix_health_fingerprint = Some(classification.to_string()); let target_identity = GitService::canonical_target_identity(working_dir, &workspace.branch_name).await?; + let runtime_conversation_id = match chat_conversation_repo.as_ref() { + Some(chat_conversation_repo) => { + ensure_agent_workspace_fixer_conversation_with_repo( + chat_conversation_repo.as_ref(), + workspace, + attempt.runtime_conversation_id.as_ref(), + AgentWorkspaceFixerKind::PrFixer, + AgentWorkspaceFixerTitleContext::PullRequest(workspace.publication_pr_number), + ) + .await? + } + None => workspace.conversation_id, + }; let dispatch_attempt = match reserve_agent_workspace_repair_dispatch( Arc::clone(&repair_repo), Arc::clone(&branch_update_repo), target_identity, attempt, preallocated_run_id.clone(), - None, + Some(runtime_conversation_id), dispatch.repair_summary, auto_merge_before_reservation, ) @@ -4086,6 +4207,8 @@ async fn dispatch_agent_workspace_pr_autofix_legacy( async fn agent_workspace_pr_autofix_repair_in_flight( workspace: &AgentConversationWorkspace, + workspace_repo: &dyn AgentConversationWorkspaceRepository, + repair_repo: Option<&Arc>, agent_run_repo: Option<&Arc>, ) -> crate::AppResult { if workspace.publication_push_status.as_deref() == Some("needs_agent") @@ -4103,18 +4226,53 @@ async fn agent_workspace_pr_autofix_repair_in_flight( let Some(agent_run_repo) = agent_run_repo else { return Ok(true); }; - return Ok(agent_run_repo - .get_active_for_conversation(&workspace.conversation_id) - .await? - .is_some()); + return any_agent_workspace_fixer_runtime_is_active( + workspace, + workspace_repo, + repair_repo, + agent_run_repo.as_ref(), + ) + .await; } let Some(agent_run_repo) = agent_run_repo else { return Ok(false); }; - Ok(agent_run_repo - .get_active_for_conversation(&workspace.conversation_id) - .await? - .is_some()) + any_agent_workspace_fixer_runtime_is_active( + workspace, + workspace_repo, + repair_repo, + agent_run_repo.as_ref(), + ) + .await +} + +async fn any_agent_workspace_fixer_runtime_is_active( + workspace: &AgentConversationWorkspace, + workspace_repo: &dyn AgentConversationWorkspaceRepository, + repair_repo: Option<&Arc>, + agent_run_repo: &dyn AgentRunRepository, +) -> crate::AppResult { + let conversations = match repair_repo { + Some(repair_repo) => { + agent_workspace_fixer_runtime_conversations( + workspace, + workspace_repo, + repair_repo.as_ref(), + ) + .await? + } + None => vec![workspace.conversation_id], + }; + for conversation_id in conversations { + if agent_run_repo + .get_active_for_conversation(&conversation_id) + .await? + .is_some() + { + return Ok(true); + } + } + Ok(false) } /// Shared by the poller's first dispatch and by durable redelivery so a recovered PR autofix keeps @@ -4921,6 +5079,7 @@ async fn route_agent_workspace_review_feedback_if_present( agent_run_repo, None, None, + None, chat_service, ) .await @@ -4935,6 +5094,7 @@ async fn route_agent_workspace_review_feedback_if_present_with_repair_repo( agent_run_repo: Option>, repair_repo: Option>, branch_update_repo: Option>, + chat_conversation_repo: Option>, chat_service: Arc, ) -> crate::AppResult { let workspace = workspace_repo @@ -5072,6 +5232,7 @@ async fn route_agent_workspace_review_feedback_if_present_with_repair_repo( dispatch_agent_workspace_pr_autofix( repair_repo, branch_update_repo, + chat_conversation_repo, workspace_repo, agent_run_repo, chat_service, diff --git a/src-tauri/src/application/services/pr_merge_poller_tests.rs b/src-tauri/src/application/services/pr_merge_poller_tests.rs index 9d91a3601f..c73249f0fd 100644 --- a/src-tauri/src/application/services/pr_merge_poller_tests.rs +++ b/src-tauri/src/application/services/pr_merge_poller_tests.rs @@ -59,7 +59,8 @@ use crate::domain::services::GithubServiceTrait; use crate::error::{AppError, AppResult}; use crate::infrastructure::memory::{ MemoryAgentConversationWorkspaceRepository, MemoryAgentRunRepository, - MemoryBranchUpdateRepository, MemoryNotificationRepository, MemoryPlanBranchRepository, + MemoryBranchUpdateRepository, MemoryChatConversationRepository, MemoryNotificationRepository, + MemoryPlanBranchRepository, }; use crate::tests::mock_github_service::MockGithubService; @@ -1375,6 +1376,7 @@ async fn repair_dispatch_remains_completable_when_success_event_persistence_fail assert_eq!(current.pr_supervision_status.as_deref(), Some("fixing")); assert!(current_agent_workspace_repair_claim_for_completion( workspace_repo, + concrete_workspace_repo.clone() as Arc, run_repo, ¤t, ) @@ -5574,6 +5576,7 @@ async fn missing_repair_repository_rejects_pr_conflict_without_side_effects() { None, None, Some(Arc::new(MemoryBranchUpdateRepository::new())), + None, chat.clone() as Arc, ) .await @@ -5642,6 +5645,7 @@ async fn busy_pr_conflict_repair_does_not_disable_auto_merge_or_send_a_worker() }); let github = Arc::new(MockGithubService::new()); let chat = Arc::new(MockChatService::new()); + let chat_conversation_repo = Arc::new(MemoryChatConversationRepository::new()); let error = super::route_agent_workspace_pr_conflict_repair_if_needed_with_repair_repo( Arc::clone(&github) as Arc, @@ -5653,6 +5657,7 @@ async fn busy_pr_conflict_repair_does_not_disable_auto_merge_or_send_a_worker() None, Some(repair_repo), Some(branch_update_repo), + Some(chat_conversation_repo), chat.clone() as Arc, ) .await @@ -5716,6 +5721,7 @@ async fn live_pr_conflict_repair_repo_route_preserves_durable_authority_on_stale Arc::new(MemoryBranchUpdateRepository::new()); let agent_run_repo = seeded_latest_pr_fixer_run_repo(&conversation_id).await; let github = Arc::new(MockGithubService::new()); + let chat_conversation_repo = Arc::new(MemoryChatConversationRepository::new()); let chat = Arc::new(MockChatService::with_agent_run_repo(Arc::clone( &agent_run_repo, ))); @@ -5734,6 +5740,7 @@ async fn live_pr_conflict_repair_repo_route_preserves_durable_authority_on_stale Some(agent_run_repo.clone()), Some(Arc::clone(&repair_repo)), Some(Arc::clone(&branch_update_repo)), + Some(chat_conversation_repo.clone()), chat.clone() as Arc, ) .await @@ -5756,6 +5763,16 @@ async fn live_pr_conflict_repair_repo_route_preserves_durable_authority_on_stale AgentWorkspaceRepairContinuation::ResumePrSupervision ); assert_eq!(first.phase, AgentWorkspaceRepairPhase::Repairing); + let runtime_conversation_id = first + .runtime_conversation_id + .as_ref() + .expect("PR conflict dispatch must persist its fixer child"); + assert_ne!(runtime_conversation_id, &conversation_id); + assert_eq!( + chat.get_sent_options().await[0].conversation_id_override, + Some(*runtime_conversation_id), + "the delivered run and durable attempt must use the same fixer child" + ); assert_eq!(first.target_base_ref, "main"); assert_eq!( first.target_base_commit.as_deref(), @@ -5784,6 +5801,7 @@ async fn live_pr_conflict_repair_repo_route_preserves_durable_authority_on_stale Some(agent_run_repo), Some(Arc::clone(&repair_repo)), Some(Arc::clone(&branch_update_repo)), + Some(chat_conversation_repo), chat.clone() as Arc, ) .await @@ -5890,6 +5908,7 @@ async fn live_pr_autofix_suppresses_same_fingerprint_while_ci_rerun_is_pending() Some(agent_run_repo), Some(Arc::clone(&repair_repo)), Some(branch_update_repo), + None, chat.clone() as Arc, ) .await @@ -5958,6 +5977,7 @@ async fn live_pr_autofix_dispatches_new_generation_after_ci_rerun_fingerprint_ch Some(agent_run_repo), Some(Arc::clone(&repair_repo)), Some(branch_update_repo), + None, chat.clone() as Arc, ) .await @@ -6028,6 +6048,7 @@ async fn route_with_base_conclusions( let github = Arc::new(MockGithubService::new()); github.state().fetch_pr_health_result = Some(Ok(health)); github.state().list_branch_check_conclusions_result = Some(base_conclusions); + let chat_conversation_repo = Arc::new(MemoryChatConversationRepository::new()); let chat = Arc::new(MockChatService::with_agent_run_repo(Arc::clone( &agent_run_repo, ))); @@ -6041,6 +6062,7 @@ async fn route_with_base_conclusions( Some(agent_run_repo), Some(repair_repo), Some(branch_update_repo), + Some(chat_conversation_repo), chat.clone() as Arc, ) .await @@ -6118,6 +6140,20 @@ async fn failure_absent_from_base_still_dispatches_a_fixer() { assert!(routed, "a check absent from base proves nothing about base"); assert_eq!(chat.get_sent_messages().await.len(), 1); + let attempt = workspace_repo + .get_current_repair_attempt(&conversation_id) + .await + .expect("repair attempt should load") + .expect("PR autofix dispatch must persist an attempt"); + let runtime_conversation_id = attempt + .runtime_conversation_id + .expect("PR autofix dispatch must persist its fixer child"); + assert_ne!(runtime_conversation_id, conversation_id); + assert_eq!( + chat.get_sent_options().await[0].conversation_id_override, + Some(runtime_conversation_id), + "the delivered PR fixer run and durable attempt must share the child" + ); assert!(!workspace_repo .list_publication_events(&conversation_id) .await @@ -6219,6 +6255,7 @@ async fn exhausted_streak_fingerprint_suppresses_a_fresh_streak_until_health_cha Some(agent_run_repo.clone()), Some(Arc::clone(&repair_repo)), Some(Arc::clone(&branch_update_repo)), + None, chat.clone() as Arc, ) .await @@ -6256,6 +6293,7 @@ async fn exhausted_streak_fingerprint_suppresses_a_fresh_streak_until_health_cha Some(agent_run_repo.clone()), Some(Arc::clone(&repair_repo)), Some(Arc::clone(&branch_update_repo)), + None, chat.clone() as Arc, ) .await @@ -6292,6 +6330,7 @@ async fn exhausted_streak_fingerprint_suppresses_a_fresh_streak_until_health_cha Some(agent_run_repo), Some(Arc::clone(&repair_repo)), Some(branch_update_repo), + None, chat.clone() as Arc, ) .await @@ -6368,6 +6407,7 @@ async fn live_pr_autofix_unchanged_health_hold_suppresses_same_fingerprint_then_ Some(agent_run_repo.clone()), Some(Arc::clone(&repair_repo)), Some(Arc::clone(&branch_update_repo)), + None, chat.clone() as Arc, ) .await @@ -6405,6 +6445,7 @@ async fn live_pr_autofix_unchanged_health_hold_suppresses_same_fingerprint_then_ Some(agent_run_repo), Some(Arc::clone(&repair_repo)), Some(branch_update_repo), + None, chat.clone() as Arc, ) .await @@ -6468,6 +6509,7 @@ async fn live_pr_autofix_pre_existing_on_base_suppresses_same_fingerprint_then_r Some(agent_run_repo.clone()), Some(Arc::clone(&repair_repo)), Some(Arc::clone(&branch_update_repo)), + None, chat.clone() as Arc, ) .await @@ -6501,6 +6543,7 @@ async fn live_pr_autofix_pre_existing_on_base_suppresses_same_fingerprint_then_r Some(agent_run_repo), Some(Arc::clone(&repair_repo)), Some(branch_update_repo), + None, chat.clone() as Arc, ) .await @@ -6569,6 +6612,7 @@ async fn live_pr_autofix_repair_repo_route_deduplicates_concurrent_dispatches() Some(agent_run_repo.clone()), Some(Arc::clone(&repair_repo)), Some(Arc::clone(&branch_update_repo)), + None, chat.clone() as Arc, ), super::route_agent_workspace_pr_autofix_if_needed_with_repair_repo( @@ -6580,6 +6624,7 @@ async fn live_pr_autofix_repair_repo_route_deduplicates_concurrent_dispatches() Some(agent_run_repo.clone()), Some(Arc::clone(&repair_repo)), Some(Arc::clone(&branch_update_repo)), + None, chat.clone() as Arc, ) ); @@ -6712,6 +6757,7 @@ async fn live_pr_autofix_repair_routed_signal_records_once_for_existing_attempt( Some(agent_run_repo.clone()), Some(Arc::clone(&repair_repo)), Some(Arc::clone(&branch_update_repo)), + None, chat.clone() as Arc, ) .await @@ -6749,6 +6795,7 @@ async fn live_pr_autofix_repair_routed_signal_records_once_for_existing_attempt( Some(agent_run_repo.clone()), Some(Arc::clone(&repair_repo)), Some(Arc::clone(&branch_update_repo)), + None, chat.clone() as Arc, ) .await @@ -6927,6 +6974,7 @@ async fn live_review_feedback_repair_repo_route_keeps_existing_continuation_auth Some(agent_run_repo.clone()), Some(Arc::clone(&repair_repo)), Some(Arc::clone(&branch_update_repo)), + None, chat.clone() as Arc, ) .await @@ -6969,6 +7017,7 @@ async fn live_review_feedback_repair_repo_route_keeps_existing_continuation_auth Some(agent_run_repo), Some(Arc::clone(&repair_repo)), Some(Arc::clone(&branch_update_repo)), + None, chat.clone() as Arc, ) .await @@ -7233,22 +7282,51 @@ async fn terminal_agent_workspace_pr_terminalization_stops_active_project_run() let branch = expected_workspace_branch(&project, conversation_id_str); let workspace = cleanup_workspace_with_conversation(&project, &branch, conversation_id_str); let conversation_id = workspace.conversation_id.clone(); + let concrete_workspace_repo = Arc::new(MemoryAgentConversationWorkspaceRepository::new()); let workspace_repo: Arc = - Arc::new(MemoryAgentConversationWorkspaceRepository::new()); + concrete_workspace_repo.clone(); + let repair_repo: Arc = concrete_workspace_repo; workspace_repo .create_or_update(workspace) .await .expect("workspace should persist"); + let runtime_conversation_id = + ChatConversationId::from_string("poller-terminal-active-run-fixer-conversation"); + let mut attempt = AgentWorkspaceRepairAttempt::new( + conversation_id.clone(), + AgentWorkspaceRepairSource::PrAutofix, + AgentWorkspaceRepairContinuation::ResumePrSupervision, + "main", + false, + true, + true, + None, + Utc::now(), + ); + attempt.runtime_conversation_id = Some(runtime_conversation_id); + repair_repo + .start_or_join_repair_attempt( + crate::domain::repositories::StartOrJoinAgentWorkspaceRepairAttempt { + attempt, + reason: "active fixer cleanup proof".to_string(), + verified_newer_base: false, + compatibility_projection: None, + events: Vec::new(), + }, + ) + .await + .expect("repair attempt should persist"); let agent_run_repo = Arc::new(MemoryAgentRunRepository::new()); let run = agent_run_repo - .create(AgentRun::new(conversation_id.clone())) + .create(AgentRun::new(runtime_conversation_id)) .await .expect("active run should persist"); let chat = Arc::new(MockChatService::new()); terminalize_agent_workspace_after_pr( Arc::clone(&workspace_repo), + repair_repo, Arc::clone(&agent_run_repo) as Arc, None, Some(Arc::clone(&chat) as Arc), @@ -7260,7 +7338,7 @@ async fn terminal_agent_workspace_pr_terminalization_stops_active_project_run() assert_eq!( chat.get_stop_agent_calls().await, - vec![(ChatContextType::Project, conversation_id.as_str())] + vec![(ChatContextType::Project, runtime_conversation_id.as_str())] ); let updated_run = agent_run_repo .get_by_id(&run.id) @@ -7283,8 +7361,10 @@ async fn terminal_agent_workspace_pr_poller_retries_runtime_shutdown_before_retu let branch = expected_workspace_branch(&project, conversation_id_str); let workspace = cleanup_workspace_with_conversation(&project, &branch, conversation_id_str); let conversation_id = workspace.conversation_id.clone(); + let concrete_workspace_repo = Arc::new(MemoryAgentConversationWorkspaceRepository::new()); let workspace_repo: Arc = - Arc::new(MemoryAgentConversationWorkspaceRepository::new()); + concrete_workspace_repo.clone(); + let repair_repo: Arc = concrete_workspace_repo; workspace_repo .create_or_update(workspace) .await @@ -7305,6 +7385,7 @@ async fn terminal_agent_workspace_pr_poller_retries_runtime_shutdown_before_retu super::terminalize_polled_agent_workspace( &workspace_repo, + &repair_repo, &agent_run_repo_dyn, &plan_branch_repo_dyn, &chat_dyn, @@ -7343,7 +7424,9 @@ async fn terminal_agent_workspace_pr_poller_retries_authority_persistence_before .await .expect("workspace should persist"); concrete_workspace_repo.fail_next_publication_update("authority unavailable"); - let workspace_repo: Arc = concrete_workspace_repo; + let workspace_repo: Arc = + concrete_workspace_repo.clone(); + let repair_repo: Arc = concrete_workspace_repo; let agent_run_repo: Arc = Arc::new(MemoryAgentRunRepository::new()); let plan_branch_repo: Arc = Arc::new(MemoryPlanBranchRepository::new()); @@ -7353,6 +7436,7 @@ async fn terminal_agent_workspace_pr_poller_retries_authority_persistence_before super::terminalize_polled_agent_workspace( &workspace_repo, + &repair_repo, &agent_run_repo, &plan_branch_repo, &chat_dyn, @@ -7393,8 +7477,10 @@ async fn mismatched_polled_pr_terminalization_skips_publication_and_runtime_clea workspace.publication_pr_status = Some("open".to_string()); workspace.publication_push_status = Some("pushed".to_string()); let conversation_id = workspace.conversation_id.clone(); + let concrete_workspace_repo = Arc::new(MemoryAgentConversationWorkspaceRepository::new()); let workspace_repo: Arc = - Arc::new(MemoryAgentConversationWorkspaceRepository::new()); + concrete_workspace_repo.clone(); + let repair_repo: Arc = concrete_workspace_repo; workspace_repo .create_or_update(workspace.clone()) .await @@ -7413,6 +7499,7 @@ async fn mismatched_polled_pr_terminalization_skips_publication_and_runtime_clea super::terminalize_polled_agent_workspace( &workspace_repo, + &repair_repo, &agent_run_repo, &plan_branch_repo, &chat_dyn, diff --git a/src-tauri/src/commands/unified_chat_commands/mod.rs b/src-tauri/src/commands/unified_chat_commands/mod.rs index ead214da22..9826e310e9 100644 --- a/src-tauri/src/commands/unified_chat_commands/mod.rs +++ b/src-tauri/src/commands/unified_chat_commands/mod.rs @@ -9096,6 +9096,7 @@ pub async fn send_agent_workspace_publish_repair_message( workspace: &AgentConversationWorkspace, error: &str, runtime_overrides: AgentWorkspaceRepairRuntimeOverrides, + runtime_conversation_id: &ChatConversationId, ) -> Result where S: ChatService + ?Sized, @@ -9106,6 +9107,7 @@ where error, runtime_overrides, &AgentConversationWorkspaceRepairTarget::from_workspace(workspace), + runtime_conversation_id, ) .await } @@ -9117,6 +9119,7 @@ pub async fn send_agent_workspace_publish_repair_message_for_target( error: &str, runtime_overrides: AgentWorkspaceRepairRuntimeOverrides, target: &AgentConversationWorkspaceRepairTarget, + runtime_conversation_id: &ChatConversationId, ) -> Result where S: ChatService + ?Sized, @@ -9129,7 +9132,7 @@ where target, AgentWorkspacePostRepairAction::Publish, None, - &workspace.conversation_id, + runtime_conversation_id, ) .await } diff --git a/src-tauri/src/commands/unified_chat_commands/tests.rs b/src-tauri/src/commands/unified_chat_commands/tests.rs index 11b458c223..6356f939af 100644 --- a/src-tauri/src/commands/unified_chat_commands/tests.rs +++ b/src-tauri/src/commands/unified_chat_commands/tests.rs @@ -1568,6 +1568,7 @@ async fn publish_repair_message_routes_spawn_to_effective_target_worktree() { "merge conflict", AgentWorkspaceRepairRuntimeOverrides::default(), &target, + &workspace.conversation_id, ) .await .expect("repair message should send"); @@ -6785,7 +6786,10 @@ async fn publish_workspace_clears_terminal_pr_identity_and_creates_a_fresh_draft .any(|event| event.step == "terminal_publication_identity_cleared"), "clearing the terminal identity must leave a durable event" ); - assert_eq!(response.workspace.publication_pr_number, stored.publication_pr_number); + assert_eq!( + response.workspace.publication_pr_number, + stored.publication_pr_number + ); } #[tokio::test] diff --git a/src-tauri/src/http_server/handlers/agent_workspaces/mod.rs b/src-tauri/src/http_server/handlers/agent_workspaces/mod.rs index 1565007662..c96ee81cc0 100644 --- a/src-tauri/src/http_server/handlers/agent_workspaces/mod.rs +++ b/src-tauri/src/http_server/handlers/agent_workspaces/mod.rs @@ -830,6 +830,8 @@ pub struct AgentWorkspaceReviewContextResponse { pub events: Vec, pub target: Option, pub monitor: AgentWorkspaceReviewMonitorResponse, + pub repair_runtime_conversation_id: Option, + pub repair_fixer_kind: Option<&'static str>, pub goal_context: AgentWorkspaceReviewGoalContext, pub is_current: bool, pub is_outdated: bool, diff --git a/src-tauri/src/http_server/handlers/agent_workspaces/pr_review/helpers.rs b/src-tauri/src/http_server/handlers/agent_workspaces/pr_review/helpers.rs index 99e0e14198..f30ce4a064 100644 --- a/src-tauri/src/http_server/handlers/agent_workspaces/pr_review/helpers.rs +++ b/src-tauri/src/http_server/handlers/agent_workspaces/pr_review/helpers.rs @@ -114,6 +114,7 @@ pub(in crate::http_server::handlers::agent_workspaces) async fn reconcile_termin Arc::new(state.build_chat_service()); let outcome = crate::application::agent_workspace_terminal_cleanup::settle_review_pr_terminal_observation( Arc::clone(&state.agent_conversation_workspace_repo), + Arc::clone(&state.agent_workspace_repair_repo), Arc::clone(&state.agent_run_repo), Some(Arc::clone(&state.plan_branch_repo)), Some(chat_service), diff --git a/src-tauri/src/http_server/handlers/agent_workspaces/repair_completion.rs b/src-tauri/src/http_server/handlers/agent_workspaces/repair_completion.rs index f9eb1c7462..7329e5ce0f 100644 --- a/src-tauri/src/http_server/handlers/agent_workspaces/repair_completion.rs +++ b/src-tauri/src/http_server/handlers/agent_workspaces/repair_completion.rs @@ -302,7 +302,8 @@ fn trusted_runtime_identity( async fn current_authorized_repair_attempt( state: &HttpServerState, - conversation_id: &ChatConversationId, + runtime_conversation_id: &ChatConversationId, + owning_conversation_id: &ChatConversationId, run_id: &AgentRunId, ) -> Result { let run = state @@ -314,13 +315,13 @@ async fn current_authorized_repair_attempt( let Some(run) = run else { return Ok(AgentWorkspaceRepairCompletionAuthority::Invalid); }; - if run.conversation_id != *conversation_id { + if run.conversation_id != *runtime_conversation_id { return Ok(AgentWorkspaceRepairCompletionAuthority::Invalid); } let authority = classify_agent_workspace_repair_completion_authority( Arc::clone(&state.app_state.agent_workspace_repair_repo), - conversation_id, + owning_conversation_id, run_id, ) .await @@ -335,7 +336,7 @@ async fn current_authorized_repair_attempt( ) && state .app_state .agent_workspace_repair_repo - .get_repair_attempt_for_run(conversation_id, run_id) + .get_repair_attempt_for_run(owning_conversation_id, run_id) .await .map_err(|error| { json_error(StatusCode::INTERNAL_SERVER_ERROR, error.to_string(), None) @@ -352,6 +353,53 @@ async fn current_authorized_repair_attempt( Ok(authority) } +/// Maps a trusted fixer runtime to its owning workspace without trusting generic conversation +/// parentage. Repository failures propagate so missing authority can never be inferred from a +/// failed read. +async fn resolve_owning_workspace_conversation( + state: &HttpServerState, + runtime_conversation_id: &ChatConversationId, +) -> Result { + if state + .app_state + .agent_conversation_workspace_repo + .get_by_conversation_id(runtime_conversation_id) + .await + .map_err(|error| json_error(StatusCode::INTERNAL_SERVER_ERROR, error.to_string(), None))? + .is_some() + { + return Ok(*runtime_conversation_id); + } + + if let Some(attempt) = state + .app_state + .agent_workspace_repair_repo + .get_unsettled_attempt_by_runtime_conversation(runtime_conversation_id) + .await + .map_err(|error| json_error(StatusCode::INTERNAL_SERVER_ERROR, error.to_string(), None))? + { + return Ok(attempt.conversation_id); + } + + let review_fixers = state + .app_state + .agent_conversation_workspace_repo + .list_active_workspace_review_fixers() + .await + .map_err(|error| json_error(StatusCode::INTERNAL_SERVER_ERROR, error.to_string(), None))?; + if let Some(monitor) = review_fixers.into_iter().find(|monitor| { + monitor.review_fixer_conversation_id.as_ref() == Some(runtime_conversation_id) + }) { + return Ok(monitor.conversation_id); + } + + Err(json_error( + StatusCode::CONFLICT, + "The repair runtime is not linked to an active agent workspace repair.", + None, + )) +} + fn authority_response( authority: AgentWorkspaceRepairCompletionAuthority, ) -> Result>, JsonError> { @@ -382,10 +430,17 @@ fn authority_response( /// different current generation/run is superseded. async fn stale_completion_transition_response( state: &HttpServerState, - conversation_id: &ChatConversationId, + runtime_conversation_id: &ChatConversationId, + owning_conversation_id: &ChatConversationId, run_id: &AgentRunId, ) -> Result, JsonError> { - let authority = current_authorized_repair_attempt(state, conversation_id, run_id).await?; + let authority = current_authorized_repair_attempt( + state, + runtime_conversation_id, + owning_conversation_id, + run_id, + ) + .await?; Ok(authority_response(authority)?.unwrap_or_else(|| { completion_response( "superseded", @@ -455,14 +510,22 @@ pub(crate) async fn complete_agent_workspace_repair_for_trusted_run( )); } - let authority = current_authorized_repair_attempt(state, &conversation_id, &run_id).await?; + let owning_conversation_id = + resolve_owning_workspace_conversation(state, &conversation_id).await?; + let authority = current_authorized_repair_attempt( + state, + &conversation_id, + &owning_conversation_id, + &run_id, + ) + .await?; let (attempt, resurrecting) = match authority { AgentWorkspaceRepairCompletionAuthority::Current(attempt) => (*attempt, false), AgentWorkspaceRepairCompletionAuthority::AlreadyBlocked if req.blocker.is_none() => { let attempt = state .app_state .agent_workspace_repair_repo - .get_repair_attempt_for_run(&conversation_id, &run_id) + .get_repair_attempt_for_run(&owning_conversation_id, &run_id) .await .map_err(|error| { json_error(StatusCode::INTERNAL_SERVER_ERROR, error.to_string(), None) @@ -489,7 +552,8 @@ pub(crate) async fn complete_agent_workspace_repair_for_trusted_run( return Ok(authority_response(authority)?.expect("non-current authority responds")); } }; - let workspace = load_agent_workspace_entity(state.app_state.as_ref(), &conversation_id).await?; + let workspace = + load_agent_workspace_entity(state.app_state.as_ref(), &owning_conversation_id).await?; if matches!( req.resolution, @@ -498,6 +562,7 @@ pub(crate) async fn complete_agent_workspace_repair_for_trusted_run( return request_transient_ci_rerun( state, &conversation_id, + &owning_conversation_id, &run_id, attempt, &workspace, @@ -526,7 +591,13 @@ pub(crate) async fn complete_agent_workspace_repair_for_trusted_run( )), AgentWorkspaceRepairTransitionOutcome::Stale(_) | AgentWorkspaceRepairTransitionOutcome::Missing => { - stale_completion_transition_response(state, &conversation_id, &run_id).await + stale_completion_transition_response( + state, + &conversation_id, + &owning_conversation_id, + &run_id, + ) + .await } }; } @@ -557,7 +628,13 @@ pub(crate) async fn complete_agent_workspace_repair_for_trusted_run( )), AgentWorkspaceRepairTransitionOutcome::Stale(_) | AgentWorkspaceRepairTransitionOutcome::Missing => { - stale_completion_transition_response(state, &conversation_id, &run_id).await + stale_completion_transition_response( + state, + &conversation_id, + &owning_conversation_id, + &run_id, + ) + .await } }; } @@ -582,7 +659,13 @@ pub(crate) async fn complete_agent_workspace_repair_for_trusted_run( )), AgentWorkspaceRepairTransitionOutcome::Stale(_) | AgentWorkspaceRepairTransitionOutcome::Missing => { - stale_completion_transition_response(state, &conversation_id, &run_id).await + stale_completion_transition_response( + state, + &conversation_id, + &owning_conversation_id, + &run_id, + ) + .await } }; } @@ -602,8 +685,13 @@ pub(crate) async fn complete_agent_workspace_repair_for_trusted_run( AgentWorkspaceRepairTransitionOutcome::Stale(_) | AgentWorkspaceRepairTransitionOutcome::Missing, ) => { - return stale_completion_transition_response(state, &conversation_id, &run_id) - .await; + return stale_completion_transition_response( + state, + &conversation_id, + &owning_conversation_id, + &run_id, + ) + .await; } Err(error) => { tracing::warn!( @@ -638,12 +726,19 @@ pub(crate) async fn complete_agent_workspace_repair_for_trusted_run( AgentWorkspaceRepairTransitionOutcome::Applied(attempt) => attempt, AgentWorkspaceRepairTransitionOutcome::Stale(_) | AgentWorkspaceRepairTransitionOutcome::Missing => { - return stale_completion_transition_response(state, &conversation_id, &run_id).await; + return stale_completion_transition_response( + state, + &conversation_id, + &owning_conversation_id, + &run_id, + ) + .await; } }; Box::pin(complete_reserved_agent_workspace_repair( state, &conversation_id, + &owning_conversation_id, &run_id, &workspace, reserved, @@ -689,7 +784,8 @@ fn failed_workflow_run_from_health(health: &PrHealth) -> Option<(i64, String)> { async fn request_transient_ci_rerun( state: &HttpServerState, - conversation_id: &ChatConversationId, + runtime_conversation_id: &ChatConversationId, + owning_conversation_id: &ChatConversationId, run_id: &AgentRunId, attempt: AgentWorkspaceRepairAttempt, workspace: &AgentConversationWorkspace, @@ -754,7 +850,13 @@ async fn request_transient_ci_rerun( AgentWorkspaceRepairTransitionOutcome::Applied(attempt) => attempt, AgentWorkspaceRepairTransitionOutcome::Stale(_) | AgentWorkspaceRepairTransitionOutcome::Missing => { - return stale_completion_transition_response(state, conversation_id, run_id).await + return stale_completion_transition_response( + state, + runtime_conversation_id, + owning_conversation_id, + run_id, + ) + .await } }; if let Err(error) = github @@ -807,7 +909,8 @@ async fn block_transient_ci_rerun( async fn complete_reserved_agent_workspace_repair( state: &HttpServerState, - conversation_id: &ChatConversationId, + runtime_conversation_id: &ChatConversationId, + owning_conversation_id: &ChatConversationId, run_id: &AgentRunId, workspace: &AgentConversationWorkspace, reserved: AgentWorkspaceRepairAttempt, @@ -823,7 +926,7 @@ async fn complete_reserved_agent_workspace_repair( { tracing::warn!( target: "ralphx_lib::http::agent_workspace_repair", - conversation_id = %conversation_id, + conversation_id = %owning_conversation_id, attempt_id = %reserved.id, error = %error, "Repair completion lost its canonical Git target lease before validation" @@ -851,7 +954,13 @@ async fn complete_reserved_agent_workspace_repair( )), AgentWorkspaceRepairTransitionOutcome::Stale(_) | AgentWorkspaceRepairTransitionOutcome::Missing => { - stale_completion_transition_response(state, conversation_id, run_id).await + stale_completion_transition_response( + state, + runtime_conversation_id, + owning_conversation_id, + run_id, + ) + .await } }; } @@ -904,7 +1013,13 @@ async fn complete_reserved_agent_workspace_repair( AgentWorkspaceRepairTransitionOutcome::Applied(_) => Err(validation_error), AgentWorkspaceRepairTransitionOutcome::Stale(_) | AgentWorkspaceRepairTransitionOutcome::Missing => { - stale_completion_transition_response(state, conversation_id, run_id).await + stale_completion_transition_response( + state, + runtime_conversation_id, + owning_conversation_id, + run_id, + ) + .await } }; } @@ -934,7 +1049,13 @@ async fn complete_reserved_agent_workspace_repair( } AgentWorkspaceRepairTransitionOutcome::Stale(_) | AgentWorkspaceRepairTransitionOutcome::Missing => { - stale_completion_transition_response(state, conversation_id, run_id).await + stale_completion_transition_response( + state, + runtime_conversation_id, + owning_conversation_id, + run_id, + ) + .await } }; } @@ -955,7 +1076,13 @@ async fn complete_reserved_agent_workspace_repair( AgentWorkspaceRepairTransitionOutcome::Applied(validated) => validated, AgentWorkspaceRepairTransitionOutcome::Stale(_) | AgentWorkspaceRepairTransitionOutcome::Missing => { - return stale_completion_transition_response(state, conversation_id, run_id).await; + return stale_completion_transition_response( + state, + runtime_conversation_id, + owning_conversation_id, + run_id, + ) + .await; } }; #[cfg(feature = "test-utils")] @@ -973,7 +1100,13 @@ async fn complete_reserved_agent_workspace_repair( AgentWorkspaceRepairTransitionOutcome::Applied(attempt) => attempt, AgentWorkspaceRepairTransitionOutcome::Stale(_) | AgentWorkspaceRepairTransitionOutcome::Missing => { - return stale_completion_transition_response(state, conversation_id, run_id).await; + return stale_completion_transition_response( + state, + runtime_conversation_id, + owning_conversation_id, + run_id, + ) + .await; } }; if continuation.phase == AgentWorkspaceRepairPhase::Blocked { @@ -982,12 +1115,15 @@ async fn complete_reserved_agent_workspace_repair( "Workspace Review could not start; the durable repair continuation is blocked.", )); } - if let Err(error) = - Box::pin(continue_agent_workspace_repair_publish(state.app_state.as_ref(), continuation)).await + if let Err(error) = Box::pin(continue_agent_workspace_repair_publish( + state.app_state.as_ref(), + continuation, + )) + .await { tracing::warn!( target: "ralphx_lib::http::agent_workspace_repair", - conversation_id = %conversation_id, + conversation_id = %owning_conversation_id, error = %error, "Repair continuation publication is durably pending after completion" ); diff --git a/src-tauri/src/http_server/handlers/agent_workspaces/workspace_review_context.rs b/src-tauri/src/http_server/handlers/agent_workspaces/workspace_review_context.rs index 5d661b8028..e30258d9d7 100644 --- a/src-tauri/src/http_server/handlers/agent_workspaces/workspace_review_context.rs +++ b/src-tauri/src/http_server/handlers/agent_workspaces/workspace_review_context.rs @@ -16,6 +16,7 @@ use crate::application::agent_workspace_review::{ use crate::application::agent_workspace_review_context::{ load_agent_workspace_review_presentation_context, AgentWorkspaceReviewContextReadMode, }; +use crate::domain::entities::{AgentWorkspaceRepairPhase, AgentWorkspaceRepairSource}; /// GET /api/agent-workspaces/{conversation_id}/workspace-review-context pub async fn get_agent_workspace_review_context( @@ -88,6 +89,23 @@ pub async fn get_agent_workspace_review_context( has_artifact = context.monitor.review_artifact_id.is_some(), "Served workspace Review context" ); + let repair_attempt = state + .app_state + .agent_workspace_repair_repo + .get_current_repair_attempt(&conversation_id) + .await + .map_err(|error| json_error(StatusCode::INTERNAL_SERVER_ERROR, error.to_string(), None))? + .filter(|attempt| attempt.phase != AgentWorkspaceRepairPhase::Blocked); + let repair_runtime_conversation_id = repair_attempt + .as_ref() + .map(|attempt| attempt.runtime_conversation_id().as_str()); + let repair_fixer_kind = repair_attempt.as_ref().map(|attempt| { + if attempt.source == AgentWorkspaceRepairSource::PrAutofix { + "pr_fixer" + } else { + "workspace_repair" + } + }); Ok(Json(AgentWorkspaceReviewContextResponse { success: true, @@ -97,6 +115,8 @@ pub async fn get_agent_workspace_review_context( AgentWorkspaceReviewTargetResponse::from_target(target, include_review_packet) }), monitor: AgentWorkspaceReviewMonitorResponse::from(context.monitor), + repair_runtime_conversation_id, + repair_fixer_kind, goal_context: context.goal_context, is_current: context.is_current, is_outdated: context.is_outdated, diff --git a/src-tauri/tests/suite_agent_workspace/agent_workspace_publish_recovery.rs b/src-tauri/tests/suite_agent_workspace/agent_workspace_publish_recovery.rs index 582fe433ec..29142f5c78 100644 --- a/src-tauri/tests/suite_agent_workspace/agent_workspace_publish_recovery.rs +++ b/src-tauri/tests/suite_agent_workspace/agent_workspace_publish_recovery.rs @@ -13,7 +13,9 @@ use ralphx_lib::domain::entities::{ AgentWorkspaceReviewMonitor, AgentWorkspaceReviewMonitorStatus, ChatConversationId, IdeationAnalysisBaseRefKind, Project, ProjectId, }; -use ralphx_lib::domain::repositories::{AgentConversationWorkspaceRepository, AgentRunRepository}; +use ralphx_lib::domain::repositories::{ + AgentConversationWorkspaceRepository, AgentRunRepository, AgentWorkspaceRepairRepository, +}; use ralphx_lib::infrastructure::memory::{ MemoryAgentConversationWorkspaceRepository, MemoryAgentRunRepository, }; @@ -74,6 +76,7 @@ async fn recovers_needs_agent_workspace_when_no_agent_run_is_active() { let recovered = recover_stale_agent_workspace_publish_repairs( Arc::clone(&workspace_repo) as Arc, + Arc::clone(&workspace_repo) as Arc, Arc::clone(&agent_run_repo) as Arc, ) .await @@ -115,6 +118,7 @@ async fn keeps_needs_agent_workspace_locked_while_agent_run_is_active() { let recovered = recover_stale_publish_repair_for_workspace( Arc::clone(&workspace_repo) as Arc, + Arc::clone(&workspace_repo) as Arc, Arc::clone(&agent_run_repo) as Arc, workspace, ) diff --git a/src-tauri/tests/suite_http_handlers/agent_workspace_repair_completion.rs b/src-tauri/tests/suite_http_handlers/agent_workspace_repair_completion.rs index 54b4cde647..e243accfe9 100644 --- a/src-tauri/tests/suite_http_handlers/agent_workspace_repair_completion.rs +++ b/src-tauri/tests/suite_http_handlers/agent_workspace_repair_completion.rs @@ -19,8 +19,8 @@ use ralphx_lib::commands::{ use ralphx_lib::domain::entities::{ AgentConversationWorkspace, AgentConversationWorkspaceMode, AgentRun, AgentRunId, AgentRunStatus, AgentWorkspaceRepairAttempt, AgentWorkspaceRepairOutcome, - AgentWorkspaceRepairPhase, AgentWorkspaceRepairSource, ChatConversationId, GitTargetLeaseOwner, - IdeationAnalysisBaseRefKind, Project, ProjectId, + AgentWorkspaceRepairPhase, AgentWorkspaceRepairSource, ChatConversation, ChatConversationId, + GitTargetLeaseOwner, IdeationAnalysisBaseRefKind, Project, ProjectId, }; use ralphx_lib::domain::repositories::{ AcquireGitTargetLease, AcquireGitTargetLeaseOutcome, AgentWorkspaceRepairAttemptTransition, @@ -237,6 +237,14 @@ async fn seed_current_attempt( async fn bind_current_attempt( state: &HttpServerState, conversation_id: ChatConversationId, +) -> (ChatConversationId, AgentRunId, AgentWorkspaceRepairAttempt) { + bind_current_attempt_in_runtime(state, conversation_id, None).await +} + +async fn bind_current_attempt_in_runtime( + state: &HttpServerState, + conversation_id: ChatConversationId, + runtime_conversation_id: Option, ) -> (ChatConversationId, AgentRunId, AgentWorkspaceRepairAttempt) { let attempt = AgentWorkspaceRepairAttempt::new( conversation_id, @@ -264,7 +272,7 @@ async fn bind_current_attempt( let StartOrJoinAgentWorkspaceRepairAttemptOutcome::Started(started) = started else { panic!("first repair attempt must start"); }; - let owner_run = AgentRun::new(conversation_id); + let owner_run = AgentRun::new(runtime_conversation_id.unwrap_or(conversation_id)); let owner_run_id = owner_run.id; state .app_state @@ -281,7 +289,7 @@ async fn bind_current_attempt( expected_phase: AgentWorkspaceRepairPhase::Requested, expected_updated_at: started.updated_at, run_id: owner_run_id, - runtime_conversation_id: None, + runtime_conversation_id, updated_at: Utc::now(), }) .await @@ -295,6 +303,126 @@ async fn bind_current_attempt( (conversation_id, owner_run_id, bound) } +#[tokio::test] +async fn child_hosted_repair_completion_resolves_and_settles_owning_workspace() { + let state = test_state(); + let conversation_id = ChatConversationId::new(); + let workspace = AgentConversationWorkspace::new( + conversation_id, + ProjectId::from_string("child-repair-completion-project".to_string()), + AgentConversationWorkspaceMode::Edit, + IdeationAnalysisBaseRefKind::ProjectDefault, + "main".to_string(), + Some("main".to_string()), + Some("base-head".to_string()), + "ralphx/test/child-repair-completion".to_string(), + "/missing-on-purpose".to_string(), + ); + state + .app_state + .agent_conversation_workspace_repo + .create_or_update(workspace) + .await + .expect("seed workspace"); + let runtime_conversation_id = ChatConversationId::new(); + let (_, run_id, _) = + bind_current_attempt_in_runtime(&state, conversation_id, Some(runtime_conversation_id)) + .await; + + let (status, outcome) = response_status( + repair_completion_http_response( + state.clone(), + &runtime_conversation_id, + completion_headers(runtime_conversation_id, run_id), + CompleteAgentWorkspaceRepairRequest { + summary: "Child-hosted repair needs a recorded blocker.".to_string(), + blocker: Some("Waiting for a maintainer decision.".to_string()), + reported_fix_commit_sha: None, + resolution: None, + }, + ) + .await, + ) + .await; + + assert_eq!(status, StatusCode::OK); + assert_eq!(outcome, "blocked"); + let settled = state + .app_state + .agent_workspace_repair_repo + .get_current_repair_attempt(&conversation_id) + .await + .expect("read repair attempt") + .expect("repair attempt remains current"); + assert_eq!(settled.phase, AgentWorkspaceRepairPhase::Blocked); +} + +#[tokio::test] +async fn unrelated_parented_child_cannot_complete_workspace_repair() { + let state = test_state(); + let (conversation_id, _owner_run_id, before) = seed_current_attempt(&state).await; + let runtime_conversation_id = ChatConversationId::new(); + let mut unrelated_child = ChatConversation::new_project(ProjectId::from_string( + "repair-completion-project".to_string(), + )); + unrelated_child.id = runtime_conversation_id; + unrelated_child.parent_conversation_id = Some(conversation_id.as_str()); + state + .app_state + .chat_conversation_repo + .create(unrelated_child) + .await + .expect("seed unrelated parented child"); + let unrelated_run = state + .app_state + .agent_run_repo + .create(AgentRun::new(runtime_conversation_id)) + .await + .expect("seed unrelated child run"); + + let response = repair_completion_http_response( + state.clone(), + &runtime_conversation_id, + completion_headers(runtime_conversation_id, unrelated_run.id), + CompleteAgentWorkspaceRepairRequest { + summary: "An unrelated parented child must not carry repair authority.".to_string(), + blocker: Some("This must be rejected.".to_string()), + reported_fix_commit_sha: None, + resolution: None, + }, + ) + .await; + assert_eq!(response.status(), StatusCode::CONFLICT); + + let after = state + .app_state + .agent_workspace_repair_repo + .get_current_repair_attempt(&conversation_id) + .await + .expect("read current attempt") + .expect("attempt remains current"); + assert_eq!(after.id, before.id); + assert_eq!(after.phase, before.phase); + assert_eq!(after.updated_at, before.updated_at); + assert!(state + .app_state + .agent_conversation_workspace_repo + .list_publication_events(&conversation_id) + .await + .expect("list publication events") + .is_empty()); + let workspace = state + .app_state + .agent_conversation_workspace_repo + .get_by_conversation_id(&conversation_id) + .await + .expect("load workspace") + .expect("workspace remains present"); + assert!(workspace.publication_pr_url.is_none()); + assert!(workspace.publication_pr_status.is_none()); + assert!(workspace.pr_supervision_summary.is_none()); +} + async fn seed_current_attempt_with_resolvable_target( state: &HttpServerState, ) -> ( diff --git a/src-tauri/tests/suite_http_handlers/agent_workspace_review_context.rs b/src-tauri/tests/suite_http_handlers/agent_workspace_review_context.rs index a97ca6c285..d2d9ad057a 100644 --- a/src-tauri/tests/suite_http_handlers/agent_workspace_review_context.rs +++ b/src-tauri/tests/suite_http_handlers/agent_workspace_review_context.rs @@ -5,9 +5,15 @@ use ralphx_lib::commands::unified_chat_commands::AgentConversationWorkspaceRespo use ralphx_lib::commands::ExecutionState; use ralphx_lib::domain::entities::{ AgentConversationWorkspace, AgentConversationWorkspaceMode, AgentRun, - AgentWorkspaceReviewGateStatus, AgentWorkspaceReviewMonitor, AgentWorkspaceReviewMonitorStatus, - AgentWorkspaceReviewOutcome, AgentWorkspaceReviewTargetScope, ArtifactContent, ArtifactId, - ChatConversation, ChatConversationId, IdeationAnalysisBaseRefKind, Project, + AgentWorkspaceRepairAttempt, AgentWorkspaceRepairContinuation, AgentWorkspaceRepairOutcome, + AgentWorkspaceRepairSource, AgentWorkspaceReviewGateStatus, AgentWorkspaceReviewMonitor, + AgentWorkspaceReviewMonitorStatus, AgentWorkspaceReviewOutcome, + AgentWorkspaceReviewTargetScope, ArtifactContent, ArtifactId, ChatConversation, + ChatConversationId, IdeationAnalysisBaseRefKind, Project, +}; +use ralphx_lib::domain::repositories::{ + SettleAgentWorkspaceRepairAttempt, SettleAgentWorkspaceRepairAttemptOutcome, + StartOrJoinAgentWorkspaceRepairAttempt, StartOrJoinAgentWorkspaceRepairAttemptOutcome, }; use ralphx_lib::http_server::handlers::agent_workspaces::{ get_agent_workspace_review_context, get_agent_workspace_review_start_preview, @@ -625,3 +631,182 @@ async fn presentation_context_get_does_not_create_a_review_monitor() { .expect("read monitor") .is_none()); } + +#[tokio::test] +async fn workspace_review_context_surfaces_active_repair_runtime_and_kind() { + let repo = tempfile::TempDir::new().expect("repo tempdir"); + let worktree = tempfile::TempDir::new().expect("worktree tempdir"); + let state = test_state(); + let conversation_id = ChatConversationId::new(); + let project = Project::new( + "Repair runtime context".to_string(), + repo.path().to_string_lossy().to_string(), + ); + state + .app_state + .project_repo + .create(project.clone()) + .await + .expect("seed project"); + let mut conversation = ChatConversation::new_project(project.id.clone()); + conversation.id = conversation_id; + state + .app_state + .chat_conversation_repo + .create(conversation) + .await + .expect("seed conversation"); + let workspace = AgentConversationWorkspace::new( + conversation_id, + project.id, + AgentConversationWorkspaceMode::Edit, + IdeationAnalysisBaseRefKind::ProjectDefault, + "main".to_string(), + Some("Project default (main)".to_string()), + None, + "ralphx/test/repair-runtime-context".to_string(), + worktree.path().to_string_lossy().to_string(), + ); + state + .app_state + .agent_conversation_workspace_repo + .create_or_update(workspace) + .await + .expect("seed workspace"); + let parent_attempt = AgentWorkspaceRepairAttempt::new( + conversation_id, + AgentWorkspaceRepairSource::Publish, + AgentWorkspaceRepairContinuation::ResumePrSupervision, + "main", + false, + true, + false, + None, + chrono::Utc::now(), + ); + let parent_attempt = match state + .app_state + .agent_workspace_repair_repo + .start_or_join_repair_attempt(StartOrJoinAgentWorkspaceRepairAttempt { + attempt: parent_attempt, + reason: "surface parent-hosted fixer runtime".to_string(), + verified_newer_base: false, + compatibility_projection: None, + events: Vec::new(), + }) + .await + .expect("start parent-hosted repair attempt") + { + StartOrJoinAgentWorkspaceRepairAttemptOutcome::Started(attempt) => attempt, + outcome => panic!("expected parent-hosted attempt, got {outcome:?}"), + }; + + let axum::Json(parent_context) = get_agent_workspace_review_context( + State(state.clone()), + Path(conversation_id.to_string()), + HeaderMap::new(), + Query(AgentWorkspaceReviewContextQuery::default()), + ) + .await + .expect("load parent-hosted repair context"); + assert_eq!( + parent_context.repair_runtime_conversation_id, + Some(conversation_id.as_str()) + ); + assert_eq!( + parent_context.repair_fixer_kind.as_deref(), + Some("workspace_repair") + ); + assert!(matches!( + state + .app_state + .agent_workspace_repair_repo + .settle_repair_attempt(SettleAgentWorkspaceRepairAttempt { + attempt_id: parent_attempt.id, + generation: parent_attempt.generation, + expected_phase: parent_attempt.phase, + expected_updated_at: parent_attempt.updated_at, + outcome: AgentWorkspaceRepairOutcome::Succeeded, + settled_at: parent_attempt.updated_at + chrono::Duration::microseconds(1), + compatibility_projection: None, + events: Vec::new(), + }) + .await + .expect("settle parent-hosted attempt"), + SettleAgentWorkspaceRepairAttemptOutcome::Applied(_) + )); + + let runtime_conversation_id = ChatConversationId::new(); + let mut attempt = AgentWorkspaceRepairAttempt::new( + conversation_id, + AgentWorkspaceRepairSource::PrAutofix, + AgentWorkspaceRepairContinuation::ResumePrSupervision, + "main", + false, + true, + false, + None, + chrono::Utc::now(), + ); + attempt.runtime_conversation_id = Some(runtime_conversation_id); + let child_attempt = match state + .app_state + .agent_workspace_repair_repo + .start_or_join_repair_attempt(StartOrJoinAgentWorkspaceRepairAttempt { + attempt, + reason: "surface fixer runtime".to_string(), + verified_newer_base: false, + compatibility_projection: None, + events: Vec::new(), + }) + .await + .expect("start repair attempt") + { + StartOrJoinAgentWorkspaceRepairAttemptOutcome::Started(attempt) => attempt, + outcome => panic!("expected child-hosted attempt, got {outcome:?}"), + }; + + let axum::Json(context) = get_agent_workspace_review_context( + State(state.clone()), + Path(conversation_id.to_string()), + HeaderMap::new(), + Query(AgentWorkspaceReviewContextQuery::default()), + ) + .await + .expect("load presentation context"); + + assert_eq!( + context.repair_runtime_conversation_id, + Some(runtime_conversation_id.as_str()) + ); + assert_eq!(context.repair_fixer_kind.as_deref(), Some("pr_fixer")); + + assert!(matches!( + state + .app_state + .agent_workspace_repair_repo + .settle_repair_attempt(SettleAgentWorkspaceRepairAttempt { + attempt_id: child_attempt.id, + generation: child_attempt.generation, + expected_phase: child_attempt.phase, + expected_updated_at: child_attempt.updated_at, + outcome: AgentWorkspaceRepairOutcome::Succeeded, + settled_at: child_attempt.updated_at + chrono::Duration::microseconds(1), + compatibility_projection: None, + events: Vec::new(), + }) + .await + .expect("settle child-hosted attempt"), + SettleAgentWorkspaceRepairAttemptOutcome::Applied(_) + )); + let axum::Json(settled_context) = get_agent_workspace_review_context( + State(state), + Path(conversation_id.to_string()), + HeaderMap::new(), + Query(AgentWorkspaceReviewContextQuery::default()), + ) + .await + .expect("load settled repair context"); + assert_eq!(settled_context.repair_runtime_conversation_id, None); + assert_eq!(settled_context.repair_fixer_kind, None); +} diff --git a/src-tauri/tests/suite_ipc_commands/unified_chat_commands.rs b/src-tauri/tests/suite_ipc_commands/unified_chat_commands.rs index 2f696b16f7..1ebb58ac00 100644 --- a/src-tauri/tests/suite_ipc_commands/unified_chat_commands.rs +++ b/src-tauri/tests/suite_ipc_commands/unified_chat_commands.rs @@ -2393,6 +2393,7 @@ async fn workspace_publish_repair_message_wakes_same_agent_conversation() { &workspace, "Failed to commit: typecheck failed", AgentWorkspaceRepairRuntimeOverrides::default(), + &workspace.conversation_id, ) .await .expect("repair handoff should be sent through chat service"); @@ -3043,6 +3044,7 @@ mod ipc_contract { recover_stale_agent_workspace_publish_repairs_on_startup( std::sync::Arc::clone(&state.agent_conversation_workspace_repo), + std::sync::Arc::clone(&state.agent_workspace_repair_repo), std::sync::Arc::clone(&state.agent_run_repo), ) .await; @@ -3073,6 +3075,7 @@ mod ipc_contract { recover_stale_agent_workspace_publish_repairs_on_startup( std::sync::Arc::clone(&state.agent_conversation_workspace_repo), + std::sync::Arc::clone(&state.agent_workspace_repair_repo), std::sync::Arc::clone(&state.agent_run_repo), ) .await; From b56b75efe07063bcd88e6a76c175a4d5623ef8f7 Mon Sep 17 00:00:00 2001 From: lazabogdan <6580668+lazabogdan@users.noreply.github.com> Date: Mon, 3 Aug 2026 05:08:15 +0300 Subject: [PATCH 19/29] fix: resolve all-features clippy diagnostics --- .../application/services/pr_merge_poller.rs | 36 +++++++------------ .../agent_workspace_review_context.rs | 7 ++-- 2 files changed, 15 insertions(+), 28 deletions(-) diff --git a/src-tauri/src/application/services/pr_merge_poller.rs b/src-tauri/src/application/services/pr_merge_poller.rs index 15d487b816..49b994a8aa 100644 --- a/src-tauri/src/application/services/pr_merge_poller.rs +++ b/src-tauri/src/application/services/pr_merge_poller.rs @@ -2057,18 +2057,13 @@ async fn route_agent_workspace_pr_conflict_repair_if_needed_with_repair_repo( .to_string(), )); }; - let chat_conversation_repo = match chat_conversation_repo { - Some(repo) => Some(repo), - None => { - #[cfg(not(test))] - return Err(AppError::Infrastructure( - "durable PR conflict repair dispatch requires fixer conversation persistence" - .to_string(), - )); - #[cfg(test)] - None - } - }; + #[cfg(not(test))] + let chat_conversation_repo = Some(chat_conversation_repo.ok_or_else(|| { + AppError::Infrastructure( + "durable PR conflict repair dispatch requires fixer conversation persistence" + .to_string(), + ) + })?); let workspace = workspace_repo .get_by_conversation_id(conversation_id) @@ -3741,17 +3736,12 @@ async fn dispatch_agent_workspace_pr_autofix( "durable PR autofix dispatch requires canonical Git target authority".to_string(), )); }; - let chat_conversation_repo = match chat_conversation_repo { - Some(repo) => Some(repo), - None => { - #[cfg(not(test))] - return Err(AppError::Infrastructure( - "durable PR autofix dispatch requires fixer conversation persistence".to_string(), - )); - #[cfg(test)] - None - } - }; + #[cfg(not(test))] + let chat_conversation_repo = Some(chat_conversation_repo.ok_or_else(|| { + AppError::Infrastructure( + "durable PR autofix dispatch requires fixer conversation persistence".to_string(), + ) + })?); let preallocated_run_id = AgentRunId::new(); let start = start_or_join_agent_workspace_repair( Arc::clone(&repair_repo), diff --git a/src-tauri/tests/suite_http_handlers/agent_workspace_review_context.rs b/src-tauri/tests/suite_http_handlers/agent_workspace_review_context.rs index d2d9ad057a..571e383a91 100644 --- a/src-tauri/tests/suite_http_handlers/agent_workspace_review_context.rs +++ b/src-tauri/tests/suite_http_handlers/agent_workspace_review_context.rs @@ -713,10 +713,7 @@ async fn workspace_review_context_surfaces_active_repair_runtime_and_kind() { parent_context.repair_runtime_conversation_id, Some(conversation_id.as_str()) ); - assert_eq!( - parent_context.repair_fixer_kind.as_deref(), - Some("workspace_repair") - ); + assert_eq!(parent_context.repair_fixer_kind, Some("workspace_repair")); assert!(matches!( state .app_state @@ -779,7 +776,7 @@ async fn workspace_review_context_surfaces_active_repair_runtime_and_kind() { context.repair_runtime_conversation_id, Some(runtime_conversation_id.as_str()) ); - assert_eq!(context.repair_fixer_kind.as_deref(), Some("pr_fixer")); + assert_eq!(context.repair_fixer_kind, Some("pr_fixer")); assert!(matches!( state From d2e5be20759a1d3b5f99017e7fb6d1ca4fe80a93 Mon Sep 17 00:00:00 2001 From: lazabogdan <6580668+lazabogdan@users.noreply.github.com> Date: Mon, 3 Aug 2026 05:37:09 +0300 Subject: [PATCH 20/29] fix: persist PR fixer conversation in poller test --- .../tests/suite_pr_github/pr_poller_tests.rs | 27 ++++++++++++++----- 1 file changed, 21 insertions(+), 6 deletions(-) diff --git a/src-tauri/tests/suite_pr_github/pr_poller_tests.rs b/src-tauri/tests/suite_pr_github/pr_poller_tests.rs index 80ec8442d7..6ce7ab7fdb 100644 --- a/src-tauri/tests/suite_pr_github/pr_poller_tests.rs +++ b/src-tauri/tests/suite_pr_github/pr_poller_tests.rs @@ -20,7 +20,7 @@ use ralphx_lib::domain::entities::{ }; use ralphx_lib::domain::repositories::{ AgentConversationWorkspaceRepository, AgentRunRepository, AgentWorkspaceRepairRepository, - BranchUpdateRepository, PlanBranchRepository, + BranchUpdateRepository, ChatConversationRepository, PlanBranchRepository, }; use ralphx_lib::domain::services::github_service::{ GithubServiceTrait, PrMergeStateStatus, PrMergeableState, PrReviewCommentFeedback, @@ -29,7 +29,7 @@ use ralphx_lib::domain::services::github_service::{ use ralphx_lib::infrastructure::agents::claude::agent_names::AGENT_WORKSPACE_PR_FIXER; use ralphx_lib::infrastructure::memory::{ MemoryAgentConversationWorkspaceRepository, MemoryAgentRunRepository, - MemoryBranchUpdateRepository, MemoryPlanBranchRepository, + MemoryBranchUpdateRepository, MemoryChatConversationRepository, MemoryPlanBranchRepository, }; use crate::common::MockGithubService; @@ -214,6 +214,7 @@ async fn agent_workspace_review_feedback_routes_to_same_workspace_agent_once() { let workspace_repo = Arc::new(MemoryAgentConversationWorkspaceRepository::new()); let agent_run_repo: Arc = Arc::new(MemoryAgentRunRepository::new()); let branch_update_repo = Arc::new(MemoryBranchUpdateRepository::new()); + let conversation_repo = Arc::new(MemoryChatConversationRepository::new()); let conversation_id = ChatConversationId::from_string("22222222-2222-2222-2222-222222222222"); let project_id = ProjectId::from_string("project-1".to_string()); let temp_dir = tempfile::tempdir_in(std::env::current_dir().unwrap()).unwrap(); @@ -238,6 +239,9 @@ async fn agent_workspace_review_feedback_routes_to_same_workspace_agent_once() { ); registry .set_branch_update_repo(Arc::clone(&branch_update_repo) as Arc); + registry.set_chat_conversation_repo( + Arc::clone(&conversation_repo) as Arc + ); let chat_service = Arc::new(MockChatService::with_agent_run_repo(Arc::clone( &agent_run_repo, ))); @@ -266,14 +270,24 @@ async fn agent_workspace_review_feedback_routes_to_same_workspace_agent_once() { let options = chat_service.get_sent_options().await; assert_eq!(options.len(), 1); - assert_eq!( - options[0].conversation_id_override, - Some(workspace.conversation_id) - ); + let fixer_conversation_id = options[0] + .conversation_id_override + .expect("PR autofix dispatch must target its persisted fixer conversation"); + assert_ne!(fixer_conversation_id, workspace.conversation_id); assert_eq!( options[0].agent_name_override.as_deref(), Some(AGENT_WORKSPACE_PR_FIXER) ); + let fixer_conversation = conversation_repo + .get_by_id(&fixer_conversation_id) + .await + .unwrap() + .expect("PR autofix dispatch must persist its fixer conversation"); + assert_eq!( + fixer_conversation.parent_conversation_id, + Some(workspace.conversation_id.as_str()) + ); + assert_eq!(fixer_conversation.title.as_deref(), Some("Fix PR #72")); let updated = workspace_repo .get_by_conversation_id(&workspace.conversation_id) @@ -303,6 +317,7 @@ async fn agent_workspace_review_feedback_routes_to_same_workspace_agent_once() { AgentWorkspaceRepairContinuation::ResumePrSupervision ); assert_eq!(attempt.phase, AgentWorkspaceRepairPhase::Repairing); + assert_eq!(attempt.runtime_conversation_id, Some(fixer_conversation_id)); let events = workspace_repo .list_publication_events(&workspace.conversation_id) From 8c9fdde1f7f0803844139538bebc6a75dd986883 Mon Sep 17 00:00:00 2001 From: lazabogdan <6580668+lazabogdan@users.noreply.github.com> Date: Mon, 3 Aug 2026 06:20:31 +0300 Subject: [PATCH 21/29] fix: provide durable authority to PR recovery --- ...kspace_external_pr_reconciliation_tests.rs | 2 +- ...agent_workspace_pr_supervision_recovery.rs | 35 +++++-------------- ...workspace_pr_supervision_recovery_tests.rs | 19 ++++++++-- ...ce_review_unfinished_git_recovery_tests.rs | 1 + .../application/pr_startup_recovery_tests.rs | 5 ++- 5 files changed, 32 insertions(+), 30 deletions(-) diff --git a/src-tauri/src/application/agent_workspace_external_pr_reconciliation_tests.rs b/src-tauri/src/application/agent_workspace_external_pr_reconciliation_tests.rs index 8ef3657681..36a917f43f 100644 --- a/src-tauri/src/application/agent_workspace_external_pr_reconciliation_tests.rs +++ b/src-tauri/src/application/agent_workspace_external_pr_reconciliation_tests.rs @@ -225,7 +225,7 @@ async fn deps_with_workspace( pr_poller_registry: None, chat_service: None, agent_run_repo: Arc::new(MemoryAgentRunRepository::new()), - agent_workspace_repair_repo: None, + agent_workspace_repair_repo: Some(workspace_repo.clone()), plan_branch_repo: Arc::new(MemoryPlanBranchRepository::new()), app_handle: None, }, diff --git a/src-tauri/src/application/agent_workspace_pr_supervision_recovery.rs b/src-tauri/src/application/agent_workspace_pr_supervision_recovery.rs index 766f3df43f..e8c0007644 100644 --- a/src-tauri/src/application/agent_workspace_pr_supervision_recovery.rs +++ b/src-tauri/src/application/agent_workspace_pr_supervision_recovery.rs @@ -40,8 +40,8 @@ use crate::domain::entities::{ ProjectId, }; use crate::domain::repositories::{ - AgentConversationWorkspaceRepository, AgentRunRepository, PlanBranchRepository, - ProjectRepository, + AgentConversationWorkspaceRepository, AgentRunRepository, AgentWorkspaceRepairRepository, + PlanBranchRepository, ProjectRepository, }; use crate::domain::services::{GithubServiceTrait, PrStatus as GithubPrStatus, PrSyncState}; use crate::error::{AppError, AppResult}; @@ -115,6 +115,9 @@ pub(crate) struct AgentWorkspacePrSupervisionRecoveryDeps { pub transition_service: Option>, pub chat_service: Option>, pub agent_run_repo: Arc, + /// Canonical durable repair authority. Production supplies the same repository owned by + /// `durable_recovery_state`; focused compatibility tests supply it without a full state. + pub agent_workspace_repair_repo: Arc, pub app_handle: Option, pub pr_fix_review_publish_resumer: Option>, /// Production recovery reuses AppState so one canonical durable repair reconciler owns @@ -140,6 +143,7 @@ pub(crate) fn build_agent_workspace_pr_supervision_recovery_deps( transition_service, chat_service, agent_run_repo: Arc::clone(&state.agent_run_repo), + agent_workspace_repair_repo: Arc::clone(&state.agent_workspace_repair_repo), app_handle, pr_fix_review_publish_resumer, durable_recovery_state: Some(Arc::new(state.clone())), @@ -283,13 +287,7 @@ pub(crate) async fn recover_agent_workspace_pr_supervision( let (recovered_workspace, repair_outcome) = recover_stale_publish_repair_for_workspace_with_project_repo_outcome( Arc::clone(&deps.workspace_repo), - Arc::clone( - &deps - .durable_recovery_state - .as_ref() - .expect("durable recovery state checked above") - .agent_workspace_repair_repo, - ), + Arc::clone(&deps.agent_workspace_repair_repo), Arc::clone(&deps.agent_run_repo), Arc::clone(&deps.project_repo), workspace, @@ -432,15 +430,7 @@ pub(crate) async fn recover_agent_workspace_pr_supervision( emit_workspace_changed(deps.app_handle.as_ref(), &conversation_id); let terminalized = terminalize_agent_workspace_after_pr( Arc::clone(&deps.workspace_repo), - deps.durable_recovery_state - .as_ref() - .map(|state| Arc::clone(&state.agent_workspace_repair_repo)) - .ok_or_else(|| { - AppError::Infrastructure( - "terminal workspace cleanup requires durable repair authority" - .to_string(), - ) - })?, + Arc::clone(&deps.agent_workspace_repair_repo), Arc::clone(&deps.agent_run_repo), Some(Arc::clone(&deps.plan_branch_repo)), deps.chat_service.as_ref().map(Arc::clone), @@ -503,14 +493,7 @@ pub(crate) async fn recover_agent_workspace_pr_supervision( emit_workspace_changed(deps.app_handle.as_ref(), &conversation_id); let terminalized = terminalize_agent_workspace_after_pr( Arc::clone(&deps.workspace_repo), - deps.durable_recovery_state - .as_ref() - .map(|state| Arc::clone(&state.agent_workspace_repair_repo)) - .ok_or_else(|| { - AppError::Infrastructure( - "terminal workspace cleanup requires durable repair authority".to_string(), - ) - })?, + Arc::clone(&deps.agent_workspace_repair_repo), Arc::clone(&deps.agent_run_repo), Some(Arc::clone(&deps.plan_branch_repo)), deps.chat_service.as_ref().map(Arc::clone), diff --git a/src-tauri/src/application/agent_workspace_pr_supervision_recovery_tests.rs b/src-tauri/src/application/agent_workspace_pr_supervision_recovery_tests.rs index f20e9bb918..44a4465423 100644 --- a/src-tauri/src/application/agent_workspace_pr_supervision_recovery_tests.rs +++ b/src-tauri/src/application/agent_workspace_pr_supervision_recovery_tests.rs @@ -145,7 +145,8 @@ fn recovery_deps( agent_run_repo: Arc, ) -> AgentWorkspacePrSupervisionRecoveryDeps { AgentWorkspacePrSupervisionRecoveryDeps { - workspace_repo: workspace_repo as Arc, + workspace_repo: Arc::clone(&workspace_repo) + as Arc, project_repo, plan_branch_repo: Arc::new(MemoryPlanBranchRepository::new()) as Arc, @@ -154,6 +155,7 @@ fn recovery_deps( transition_service: None, chat_service: None, agent_run_repo, + agent_workspace_repair_repo: workspace_repo, app_handle: None, pr_fix_review_publish_resumer: None, durable_recovery_state: None, @@ -481,6 +483,7 @@ async fn recovers_blocked_pr_supervision_when_remote_head_matches_local_workspac transition_service: None, chat_service: Some(Arc::new(MockChatService::new())), agent_run_repo: Arc::new(MemoryAgentRunRepository::new()), + agent_workspace_repair_repo: workspace_repo.clone(), app_handle: None, pr_fix_review_publish_resumer: None, durable_recovery_state: None, @@ -559,6 +562,7 @@ async fn matching_remote_head_with_failing_health_stays_blocked_and_never_report transition_service: None, chat_service: Some(Arc::new(MockChatService::new())), agent_run_repo: Arc::new(MemoryAgentRunRepository::new()), + agent_workspace_repair_repo: workspace_repo.clone(), app_handle: None, pr_fix_review_publish_resumer: None, durable_recovery_state: None, @@ -660,6 +664,7 @@ async fn recovers_linked_plan_pr_supervision_without_workspace_publication_pr() transition_service: None, chat_service: None, agent_run_repo: Arc::new(MemoryAgentRunRepository::new()), + agent_workspace_repair_repo: workspace_repo.clone(), app_handle: None, pr_fix_review_publish_resumer: None, durable_recovery_state: None, @@ -759,6 +764,7 @@ async fn marks_terminal_linked_plan_pr_status_and_workspace_authority() { transition_service: None, chat_service: None, agent_run_repo: Arc::new(MemoryAgentRunRepository::new()), + agent_workspace_repair_repo: workspace_repo.clone(), app_handle: None, pr_fix_review_publish_resumer: None, durable_recovery_state: None, @@ -848,7 +854,8 @@ async fn skips_linked_plan_pr_supervision_when_plan_branch_is_not_current() { let outcome = recover_agent_workspace_pr_supervision( AgentWorkspacePrSupervisionRecoveryDeps { - workspace_repo: workspace_repo as Arc, + workspace_repo: Arc::clone(&workspace_repo) + as Arc, project_repo: Arc::new(MemoryProjectRepository::with_projects(vec![project])), plan_branch_repo: plan_branch_repo as Arc, github: Arc::clone(&github) as Arc, @@ -856,6 +863,7 @@ async fn skips_linked_plan_pr_supervision_when_plan_branch_is_not_current() { transition_service: None, chat_service: None, agent_run_repo: Arc::new(MemoryAgentRunRepository::new()), + agent_workspace_repair_repo: workspace_repo, app_handle: None, pr_fix_review_publish_resumer: None, durable_recovery_state: None, @@ -1310,6 +1318,7 @@ async fn retry_eligible_stale_pr_autofix_settlement_restarts_pr_polling() { transition_service: None, chat_service: Some(Arc::new(MockChatService::new())), agent_run_repo, + agent_workspace_repair_repo: workspace_repo.clone(), app_handle: None, pr_fix_review_publish_resumer: None, durable_recovery_state: None, @@ -1511,6 +1520,7 @@ async fn exhausted_stale_pr_autofix_stays_blocked_without_pr_polling() { transition_service: None, chat_service: Some(Arc::new(MockChatService::new())), agent_run_repo, + agent_workspace_repair_repo: workspace_repo.clone(), app_handle: None, pr_fix_review_publish_resumer: None, durable_recovery_state: None, @@ -1722,6 +1732,7 @@ async fn skips_blocked_pr_supervision_recovery_when_worktree_is_dirty() { transition_service: None, chat_service: None, agent_run_repo: Arc::new(MemoryAgentRunRepository::new()), + agent_workspace_repair_repo: workspace_repo.clone(), app_handle: None, pr_fix_review_publish_resumer: None, durable_recovery_state: None, @@ -2113,6 +2124,7 @@ async fn active_run_does_not_hide_terminal_linked_plan_pr_during_supervision_rec Arc::clone(&chat) as Arc ), agent_run_repo: Arc::clone(&active_run_repo) as Arc, + agent_workspace_repair_repo: workspace_repo.clone(), app_handle: None, pr_fix_review_publish_resumer: None, durable_recovery_state: None, @@ -2397,6 +2409,7 @@ async fn startup_recovery_resumes_passed_pr_fix_workspace_review_handoff() { transition_service: None, chat_service: None, agent_run_repo, + agent_workspace_repair_repo: workspace_repo.clone(), app_handle: None, pr_fix_review_publish_resumer: Some( Arc::clone(&publish_resumer) as Arc @@ -2511,6 +2524,7 @@ async fn startup_recovery_does_not_publish_pr_fix_from_stale_review_fingerprint( transition_service: None, chat_service: None, agent_run_repo, + agent_workspace_repair_repo: workspace_repo.clone(), app_handle: None, pr_fix_review_publish_resumer: Some( Arc::clone(&publish_resumer) as Arc @@ -2588,6 +2602,7 @@ async fn startup_recovery_processes_linked_plan_pr_supervision_candidates() { transition_service: None, chat_service: None, agent_run_repo: Arc::new(MemoryAgentRunRepository::new()), + agent_workspace_repair_repo: workspace_repo.clone(), app_handle: None, pr_fix_review_publish_resumer: None, durable_recovery_state: None, diff --git a/src-tauri/src/application/agent_workspace_review_unfinished_git_recovery_tests.rs b/src-tauri/src/application/agent_workspace_review_unfinished_git_recovery_tests.rs index 546e0553b5..05b9d23493 100644 --- a/src-tauri/src/application/agent_workspace_review_unfinished_git_recovery_tests.rs +++ b/src-tauri/src/application/agent_workspace_review_unfinished_git_recovery_tests.rs @@ -201,6 +201,7 @@ async fn workspace_review_unfinished_git_recovery_stops_pr_supervision_before_si transition_service: None, chat_service: None, agent_run_repo: run_repo as Arc, + agent_workspace_repair_repo: workspace_repo.clone(), app_handle: None, pr_fix_review_publish_resumer: None, durable_recovery_state: None, diff --git a/src-tauri/src/application/pr_startup_recovery_tests.rs b/src-tauri/src/application/pr_startup_recovery_tests.rs index 563c6a400a..895e6756d1 100644 --- a/src-tauri/src/application/pr_startup_recovery_tests.rs +++ b/src-tauri/src/application/pr_startup_recovery_tests.rs @@ -238,13 +238,16 @@ async fn setup_review_pr_poller_recovery_fixture( async fn recover_review_pr_poller_fixture(fixture: &ReviewPrPollerRecoveryFixture) { let workspace_repo: Arc = fixture.workspace_repo.clone(); - recover_agent_workspace_pr_pollers( + let repair_repo: Arc = fixture.workspace_repo.clone(); + recover_agent_workspace_pr_pollers_with_notifications( workspace_repo, Arc::clone(&fixture.project_repo), Arc::clone(&fixture.plan_branch_repo), Arc::clone(&fixture.registry), Arc::new(MemoryAgentRunRepository::new()), Arc::new(MockChatService::new()), + None, + Some(repair_repo), Arc::new(HashSet::new()), ) .await; From b7cc808e5fd3f3fba1e7b09bedcba2acb8de16f4 Mon Sep 17 00:00:00 2001 From: lazabogdan <6580668+lazabogdan@users.noreply.github.com> Date: Mon, 3 Aug 2026 06:51:11 +0300 Subject: [PATCH 22/29] fix: isolate missing repair authority regression --- .../agent_workspace_external_pr_reconciliation_tests.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/src-tauri/src/application/agent_workspace_external_pr_reconciliation_tests.rs b/src-tauri/src/application/agent_workspace_external_pr_reconciliation_tests.rs index 36a917f43f..270c1cb3e6 100644 --- a/src-tauri/src/application/agent_workspace_external_pr_reconciliation_tests.rs +++ b/src-tauri/src/application/agent_workspace_external_pr_reconciliation_tests.rs @@ -359,6 +359,7 @@ async fn live_reconciliation_fails_closed_without_a_durable_repair_repository() let chat = Arc::new(MockChatService::new()); deps.pr_poller_registry = Some(Arc::clone(®istry)); deps.chat_service = Some(chat.clone() as Arc); + deps.agent_workspace_repair_repo = None; let error = reconcile_agent_workspace_external_pr( deps, From 2d1787568b6c238e1fd97d2069860b55e6b17ad4 Mon Sep 17 00:00:00 2001 From: lazabogdan <6580668+lazabogdan@users.noreply.github.com> Date: Mon, 3 Aug 2026 07:23:43 +0300 Subject: [PATCH 23/29] fix: provide cleanup authority to poller test --- src-tauri/src/application/services/pr_merge_poller_tests.rs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src-tauri/src/application/services/pr_merge_poller_tests.rs b/src-tauri/src/application/services/pr_merge_poller_tests.rs index c73249f0fd..e9d8b75584 100644 --- a/src-tauri/src/application/services/pr_merge_poller_tests.rs +++ b/src-tauri/src/application/services/pr_merge_poller_tests.rs @@ -7724,6 +7724,7 @@ async fn agent_workspace_closed_pr_polling_removes_worktree_and_branch() { let memory_workspace_repo = Arc::new(MemoryAgentConversationWorkspaceRepository::new()); let workspace_repo: Arc = memory_workspace_repo.clone(); + let repair_repo: Arc = memory_workspace_repo.clone(); workspace_repo .create_or_update(workspace) .await @@ -7742,13 +7743,14 @@ async fn agent_workspace_closed_pr_polling_removes_worktree_and_branch() { Arc::new(MemoryPlanBranchRepository::new()), ); - registry.start_agent_workspace_polling( + registry.start_agent_workspace_polling_with_repair_repo( conversation_id.clone(), 101, project, repo.path().to_path_buf(), Arc::clone(&workspace_repo), Arc::new(MemoryAgentRunRepository::new()), + repair_repo, Arc::new(MockChatService::new()), ); tokio::time::timeout(Duration::from_secs(20), async { From f194e66439a41a870fd7f3d1c1a1d58f87dbbed4 Mon Sep 17 00:00:00 2001 From: lazabogdan <6580668+lazabogdan@users.noreply.github.com> Date: Tue, 4 Aug 2026 10:46:10 +0300 Subject: [PATCH 24/29] fix: thread owning_conversation_id through CI rerun stale-completion calls The stale_completion_transition_response function gained a 4th parameter (owning_conversation_id) but repair_completion_ci_rerun.rs was not updated, causing a compilation failure. Co-Authored-By: Claude Opus 4.6 --- .../agent_workspaces/repair_completion_ci_rerun.rs | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/src-tauri/src/http_server/handlers/agent_workspaces/repair_completion_ci_rerun.rs b/src-tauri/src/http_server/handlers/agent_workspaces/repair_completion_ci_rerun.rs index c70c474ed1..3801b1784a 100644 --- a/src-tauri/src/http_server/handlers/agent_workspaces/repair_completion_ci_rerun.rs +++ b/src-tauri/src/http_server/handlers/agent_workspaces/repair_completion_ci_rerun.rs @@ -20,6 +20,7 @@ use crate::error::AppError; pub(super) async fn request_transient_ci_rerun( state: &HttpServerState, conversation_id: &ChatConversationId, + owning_conversation_id: &ChatConversationId, run_id: &AgentRunId, attempt: AgentWorkspaceRepairAttempt, workspace: &AgentConversationWorkspace, @@ -71,6 +72,7 @@ pub(super) async fn request_transient_ci_rerun( await_transient_ci_runs( state, conversation_id, + owning_conversation_id, run_id, attempt, workspace, @@ -105,7 +107,7 @@ pub(super) async fn request_transient_ci_rerun( AgentWorkspaceRepairTransitionOutcome::Applied(attempt) => attempt, AgentWorkspaceRepairTransitionOutcome::Stale(_) | AgentWorkspaceRepairTransitionOutcome::Missing => { - return stale_completion_transition_response(state, conversation_id, run_id) + return stale_completion_transition_response(state, conversation_id, owning_conversation_id, run_id) .await; } }; @@ -143,6 +145,7 @@ pub(super) async fn request_transient_ci_rerun( await_transient_ci_runs( state, conversation_id, + owning_conversation_id, run_id, reserved, workspace, @@ -165,6 +168,7 @@ fn reject_transient_ci_claim( async fn await_transient_ci_runs( state: &HttpServerState, conversation_id: &ChatConversationId, + owning_conversation_id: &ChatConversationId, run_id: &AgentRunId, attempt: AgentWorkspaceRepairAttempt, workspace: &AgentConversationWorkspace, @@ -187,7 +191,7 @@ async fn await_transient_ci_runs( } AgentWorkspaceRepairTransitionOutcome::Stale(_) | AgentWorkspaceRepairTransitionOutcome::Missing => { - stale_completion_transition_response(state, conversation_id, run_id).await + stale_completion_transition_response(state, conversation_id, owning_conversation_id, run_id).await } } } From b928e01a248dc0b3985c51018c90f9553fe2dbe4 Mon Sep 17 00:00:00 2001 From: lazabogdan <6580668+lazabogdan@users.noreply.github.com> Date: Tue, 4 Aug 2026 11:00:57 +0300 Subject: [PATCH 25/29] fix: add missing chat_conversation_repo arg to 4 test call sites The route_agent_workspace_pr_autofix_if_needed_with_repair_repo function gained a chat_conversation_repo parameter but 4 test call sites were not updated, causing compilation failures in CI. Co-Authored-By: Claude Opus 4.6 --- src-tauri/src/application/services/pr_merge_poller_tests.rs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src-tauri/src/application/services/pr_merge_poller_tests.rs b/src-tauri/src/application/services/pr_merge_poller_tests.rs index 1bff584af4..32180db73e 100644 --- a/src-tauri/src/application/services/pr_merge_poller_tests.rs +++ b/src-tauri/src/application/services/pr_merge_poller_tests.rs @@ -6080,6 +6080,7 @@ async fn ci_rerun_hold_settles_once_reran_runs_are_terminal() { Some(agent_run_repo), Some(Arc::clone(&repair_repo)), Some(branch_update_repo), + None, chat.clone() as Arc, ) .await @@ -6152,6 +6153,7 @@ async fn ci_await_hold_suppresses_dispatch_and_survives_unchanged_classification Some(agent_run_repo), Some(Arc::clone(&repair_repo)), Some(branch_update_repo), + None, chat.clone() as Arc, ) .await @@ -6217,6 +6219,7 @@ async fn ci_hold_settles_when_head_moves() { Some(agent_run_repo), Some(Arc::clone(&repair_repo)), Some(branch_update_repo), + None, chat.clone() as Arc, ) .await @@ -6294,6 +6297,7 @@ async fn unrelated_conversation_dispatch_does_not_settle_a_ci_hold() { Some(agent_run_repo), Some(Arc::clone(&repair_repo)), Some(branch_update_repo), + None, chat as Arc, ) .await From 12310c81c4619f2103c06a5ee85a55e4cb25051a Mon Sep 17 00:00:00 2001 From: lazabogdan <6580668+lazabogdan@users.noreply.github.com> Date: Tue, 4 Aug 2026 12:51:52 +0300 Subject: [PATCH 26/29] fix: add missing args to remaining test call sites after function signature changes Three functions gained new parameters (chat_conversation_repo, repair_repo, recovery_state) but several test call sites were not updated: - pr_startup_recovery_tests: add missing recovery_state arg - agent_workspace_terminal_cleanup_tests: add missing repair_repo arg (3 sites) - pr_merge_poller_tests: add missing chat_conversation_repo arg (4 sites) Co-Authored-By: Claude Opus 4.6 --- .../agent_workspace_terminal_cleanup_tests.rs | 9 +++++++++ src-tauri/src/application/pr_startup_recovery_tests.rs | 1 + .../src/application/services/pr_merge_poller_tests.rs | 4 ++++ 3 files changed, 14 insertions(+) diff --git a/src-tauri/src/application/agent_workspace_terminal_cleanup_tests.rs b/src-tauri/src/application/agent_workspace_terminal_cleanup_tests.rs index 1fd51b1f20..fc01b29465 100644 --- a/src-tauri/src/application/agent_workspace_terminal_cleanup_tests.rs +++ b/src-tauri/src/application/agent_workspace_terminal_cleanup_tests.rs @@ -468,9 +468,12 @@ async fn terminalize_reports_missing_workspace_without_claiming_cleanup() { let conversation_id = ChatConversationId::from_string("69696969-6969-6969-6969-696969696969".to_string()); let workspace_repo = Arc::new(MemoryAgentConversationWorkspaceRepository::new()); + let repair_repo: Arc = + workspace_repo.clone(); let outcome = terminalize_agent_workspace_after_pr( workspace_repo, + repair_repo, Arc::new(MemoryAgentRunRepository::new()), None, None, @@ -518,8 +521,11 @@ async fn terminalize_releases_the_observed_operation_owned_publish_lease() { .await .expect("claim operation-owned lease"); + let repair_repo: Arc = + workspace_repo.clone(); let outcome = terminalize_agent_workspace_after_pr( workspace_repo.clone(), + repair_repo, Arc::new(MemoryAgentRunRepository::new()), None, None, @@ -577,8 +583,11 @@ async fn terminalize_never_releases_a_newer_publish_lease_token() { "new-redrive-token", ); + let repair_repo: Arc = + Arc::new(MemoryAgentConversationWorkspaceRepository::new()); let outcome = terminalize_agent_workspace_after_pr( workspace_repo.clone(), + repair_repo, Arc::new(MemoryAgentRunRepository::new()), None, None, diff --git a/src-tauri/src/application/pr_startup_recovery_tests.rs b/src-tauri/src/application/pr_startup_recovery_tests.rs index c9a2ebf83e..27daa30054 100644 --- a/src-tauri/src/application/pr_startup_recovery_tests.rs +++ b/src-tauri/src/application/pr_startup_recovery_tests.rs @@ -248,6 +248,7 @@ async fn recover_review_pr_poller_fixture(fixture: &ReviewPrPollerRecoveryFixtur Arc::new(MockChatService::new()), None, Some(repair_repo), + None, Arc::new(HashSet::new()), ) .await; diff --git a/src-tauri/src/application/services/pr_merge_poller_tests.rs b/src-tauri/src/application/services/pr_merge_poller_tests.rs index 60014bab17..7bacd2db3c 100644 --- a/src-tauri/src/application/services/pr_merge_poller_tests.rs +++ b/src-tauri/src/application/services/pr_merge_poller_tests.rs @@ -526,6 +526,7 @@ async fn held_manual_unpublished_redrive_noop_falls_through_and_retains_the_hold Some(Arc::clone(&state.agent_run_repo)), Some(Arc::clone(&state.agent_workspace_repair_repo)), Some(branch_update_repo), + None, chat.clone() as Arc, ) .await @@ -588,6 +589,7 @@ async fn held_unpublished_redrive_noop_falls_through_to_base_advanced_supersessi Some(agent_run_repo), Some(Arc::clone(&state.agent_workspace_repair_repo)), Some(branch_update_repo), + None, chat.clone() as Arc, ) .await @@ -6158,6 +6160,7 @@ async fn conflict_router_defers_unpublished_repair_head_without_join_or_agent_in None, Some(Arc::clone(&repair_repo)), Some(branch_update_repo), + None, chat.clone() as Arc, ) .await @@ -7151,6 +7154,7 @@ async fn live_pr_autofix_new_base_evidence_supersedes_same_fingerprint_health_ho Some(agent_run_repo), Some(Arc::clone(&repair_repo)), Some(branch_update_repo), + None, chat.clone() as Arc, ) .await From 138eb3af52947e01e922cc2db7183f01c7169cb4 Mon Sep 17 00:00:00 2001 From: lazabogdan <6580668+lazabogdan@users.noreply.github.com> Date: Tue, 4 Aug 2026 14:27:34 +0300 Subject: [PATCH 27/29] fix: align test assertion with merge-resolved terminal cleanup message The merge conflict resolution in 62a6e4bec changed the workspace-missing message from "Workspace row disappeared before local cleanup" to "Agent workspace disappeared before terminal runtime cleanup", but the test assertion was not updated to match, causing CI failure in Shard 2/4. Co-Authored-By: Claude Opus 4.6 --- .../src/application/agent_workspace_terminal_cleanup_tests.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src-tauri/src/application/agent_workspace_terminal_cleanup_tests.rs b/src-tauri/src/application/agent_workspace_terminal_cleanup_tests.rs index fc01b29465..324f91efd3 100644 --- a/src-tauri/src/application/agent_workspace_terminal_cleanup_tests.rs +++ b/src-tauri/src/application/agent_workspace_terminal_cleanup_tests.rs @@ -492,7 +492,7 @@ async fn terminalize_reports_missing_workspace_without_claiming_cleanup() { assert!(outcome .message .as_deref() - .is_some_and(|message| message.contains("disappeared before local cleanup"))); + .is_some_and(|message| message.contains("disappeared before terminal runtime cleanup"))); } #[tokio::test] From a2b47beea5a81eae7820105961050bd66c3dd890 Mon Sep 17 00:00:00 2001 From: lazabogdan <6580668+lazabogdan@users.noreply.github.com> Date: Mon, 24 Aug 2026 13:44:34 +0300 Subject: [PATCH 28/29] fix: repair post-merge compile breaks from the origin/main base update The base-update merge (d277dc99e) was committed while main's newer call sites still targeted the pre-branch signatures, so neither the lib nor the test targets compiled. Production: - agent_workspace_external_pr_reconciliation: pass the repair repository to terminalize_agent_workspace_after_pr, matching the sibling call sites in the same file. - repair_completion_ci_rerun: forward owning_conversation_id to stale_completion_transition_response alongside the runtime conversation. Tests: - pr_merge_poller_tests: supply chat_conversation_repo at the four stale autofix routing call sites. - agent_workspace_publish_repair_state_tests: supply runtime_conversation_id to reserve_agent_workspace_repair_dispatch. - suite_http_handlers/agent_workspace_repair_completion: add what_happened and what_i_did to both request initializers. Also formats the merge-brought regions of pr_merge_poller_tests.rs. Verified no symbols from main were lost to take-ours resolution: every function name present in origin/main is present in HEAD. --- ...nt_workspace_external_pr_reconciliation.rs | 8 +++ ...nt_workspace_publish_repair_state_tests.rs | 2 + .../services/pr_merge_poller_tests.rs | 53 +++++++++++-------- .../repair_completion_ci_rerun.rs | 8 ++- .../agent_workspace_repair_completion.rs | 4 ++ 5 files changed, 52 insertions(+), 23 deletions(-) diff --git a/src-tauri/src/application/agent_workspace_external_pr_reconciliation.rs b/src-tauri/src/application/agent_workspace_external_pr_reconciliation.rs index e275fb307f..a27111b7e2 100644 --- a/src-tauri/src/application/agent_workspace_external_pr_reconciliation.rs +++ b/src-tauri/src/application/agent_workspace_external_pr_reconciliation.rs @@ -464,6 +464,14 @@ async fn reconcile_linked_agent_workspace_pr( ); let terminalized = terminalize_agent_workspace_after_pr( Arc::clone(&deps.workspace_repo), + deps.agent_workspace_repair_repo + .as_ref() + .map(Arc::clone) + .ok_or_else(|| { + AppError::Infrastructure( + "terminal workspace cleanup requires durable repair authority".to_string(), + ) + })?, Arc::clone(&deps.agent_run_repo), Some(Arc::clone(&deps.plan_branch_repo)), deps.chat_service.as_ref().map(Arc::clone), diff --git a/src-tauri/src/application/agent_workspace_publish_repair_state_tests.rs b/src-tauri/src/application/agent_workspace_publish_repair_state_tests.rs index 437cf0c7d7..89da52c71d 100644 --- a/src-tauri/src/application/agent_workspace_publish_repair_state_tests.rs +++ b/src-tauri/src/application/agent_workspace_publish_repair_state_tests.rs @@ -1671,6 +1671,7 @@ async fn blocked_retry_successor_with_a_different_target_base_commit_leaves_work target_identity.clone(), started, AgentRunId::from_string("repair-attempt-blocked-retry-base-commit-guard-run"), + None, "dispatching repair", None, ) @@ -1789,6 +1790,7 @@ async fn dispatch_checkpoint_never_advances_workspace_base_commit() { target_identity, started, AgentRunId::from_string("repair-attempt-dispatch-base-commit-guard-run"), + None, "dispatching repair", None, ) diff --git a/src-tauri/src/application/services/pr_merge_poller_tests.rs b/src-tauri/src/application/services/pr_merge_poller_tests.rs index c5419253b9..59a95eb5dc 100644 --- a/src-tauri/src/application/services/pr_merge_poller_tests.rs +++ b/src-tauri/src/application/services/pr_merge_poller_tests.rs @@ -6733,6 +6733,7 @@ async fn dispatched_pr_autofix_issue_kind( Some(agent_run_repo), Some(Arc::clone(&repair_repo)), Some(branch_update_repo), + None, chat.clone() as Arc, None, ) @@ -12861,6 +12862,7 @@ async fn base_parity_transient_shape_repeat_poll_short_circuits_then_reenters_on Some(agent_run_repo), Some(Arc::clone(&repair_repo)), Some(branch_update_repo), + None, chat_second.clone() as Arc, Some(&health_a), ) @@ -13687,6 +13689,7 @@ async fn route_blocked_supersession( Some(agent_run_repo), Some(Arc::clone(&fixture.repair_repo)), Some(Arc::clone(&fixture.branch_update_repo)), + None, chat.clone() as Arc, None, Some(&fixture.project), @@ -14285,9 +14288,12 @@ async fn blocked_needs_human_push_failure_keeps_phase_blocked() { let agent_run_repo = seeded_latest_pr_fixer_run_repo(&fixture.conversation_id).await; let github = Arc::new(MockGithubService::new()); github.state().fetch_pr_health_result = Some(Ok(health)); - github.state().push_branch_result = - Some(Err(AppError::GitOperation("simulated push failure".to_string()))); - let chat = Arc::new(MockChatService::with_agent_run_repo(Arc::clone(&agent_run_repo))); + github.state().push_branch_result = Some(Err(AppError::GitOperation( + "simulated push failure".to_string(), + ))); + let chat = Arc::new(MockChatService::with_agent_run_repo(Arc::clone( + &agent_run_repo, + ))); super::route_agent_workspace_pr_autofix_if_needed_with_notifications( Arc::clone(&github) as Arc, @@ -14298,6 +14304,7 @@ async fn blocked_needs_human_push_failure_keeps_phase_blocked() { Some(agent_run_repo), Some(Arc::clone(&fixture.repair_repo)), Some(Arc::clone(&fixture.branch_update_repo)), + None, chat.clone() as Arc, None, Some(&fixture.project), @@ -14346,10 +14353,7 @@ async fn blocked_needs_human_already_fresh_keeps_phase_blocked() { blocked_needs_human_supersession_fixture("blocked-supersede-already-fresh", None, true) .await; // Merge main into the workspace branch locally so the branch is already up-to-date. - run_git( - &fixture.worktree, - &["merge", "--no-edit", "origin/main"], - ); + run_git(&fixture.worktree, &["merge", "--no-edit", "origin/main"]); let health = behind_base_health(&fixture.dispatch_head, &fixture.observed_base_oid); let (_routed, github, _chat) = route_blocked_supersession(&fixture, health).await; @@ -14518,12 +14522,9 @@ async fn blocked_needs_human_with_a_health_hold_is_never_settled() { /// promoting to `Ready` and clearing `blocker` with no head-scoped justification. #[tokio::test] async fn blocked_needs_human_already_updated_to_the_tip_keeps_its_blocker_when_ci_held() { - let fixture = blocked_needs_human_supersession_fixture( - "blocked-ci-held-already-updated", - None, - true, - ) - .await; + let fixture = + blocked_needs_human_supersession_fixture("blocked-ci-held-already-updated", None, true) + .await; let mut with_ci_and_tip = fixture.attempt.clone(); with_ci_and_tip.ci_rerun_count = 1; with_ci_and_tip.ci_rerun_fingerprint = Some("old-head:12345".to_string()); @@ -14584,12 +14585,8 @@ async fn blocked_needs_human_already_updated_to_the_tip_keeps_its_blocker_when_c /// silently promoted to Ready. #[tokio::test] async fn blocked_needs_human_with_a_base_stale_marker_is_left_untouched() { - let fixture = blocked_needs_human_supersession_fixture( - "blocked-base-stale-marker", - None, - true, - ) - .await; + let fixture = + blocked_needs_human_supersession_fixture("blocked-base-stale-marker", None, true).await; let mut with_both_markers = fixture.attempt.clone(); with_both_markers.pending_reasons.push( crate::application::agent_workspace_publish_repair_state::BASE_STALE_AFTER_UPDATE_REPAIR_REASON @@ -14617,7 +14614,13 @@ async fn blocked_needs_human_with_a_base_stale_marker_is_left_untouched() { // release predicate's guards would all pass except for the new !blocked_base_staleness_candidate. let mut health = open_pr_health(&fixture.dispatch_head); health.sync_state.merge_state_status = Some(PrMergeStateStatus::Clean); - health.sync_state.base_ref_oid = Some(fixture.attempt.target_base_commit.clone().unwrap_or_default()); + health.sync_state.base_ref_oid = Some( + fixture + .attempt + .target_base_commit + .clone() + .unwrap_or_default(), + ); let (_routed, _github, _chat) = route_blocked_supersession(&fixture, health).await; @@ -14679,13 +14682,19 @@ async fn blocked_needs_human_deferred_merge_dispatches_successor() { run_git(&fixture.worktree, &["commit", "-m", "main conflict"]); run_git(&fixture.worktree, &["push", "origin", "main"]); let new_base_oid = git_stdout(&fixture.worktree, &["rev-parse", "main"]); - run_git(&fixture.worktree, &["checkout", &fixture.workspace.branch_name]); + run_git( + &fixture.worktree, + &["checkout", &fixture.workspace.branch_name], + ); // Create a conflicting change in the workspace branch. std::fs::write(fixture.worktree.join("CONFLICT.md"), "branch conflict\n") .expect("write conflict file on branch"); run_git(&fixture.worktree, &["add", "."]); run_git(&fixture.worktree, &["commit", "-m", "branch conflict"]); - run_git(&fixture.worktree, &["push", "origin", &fixture.workspace.branch_name]); + run_git( + &fixture.worktree, + &["push", "origin", &fixture.workspace.branch_name], + ); let health = behind_base_health(&fixture.dispatch_head, &new_base_oid); diff --git a/src-tauri/src/http_server/handlers/agent_workspaces/repair_completion_ci_rerun.rs b/src-tauri/src/http_server/handlers/agent_workspaces/repair_completion_ci_rerun.rs index 8f409a5e00..079180074f 100644 --- a/src-tauri/src/http_server/handlers/agent_workspaces/repair_completion_ci_rerun.rs +++ b/src-tauri/src/http_server/handlers/agent_workspaces/repair_completion_ci_rerun.rs @@ -72,7 +72,13 @@ pub(super) async fn request_transient_ci_rerun( Ok(completion_response("rerun_pending", message)) } TransientCiRerunOutcome::ReservationStale => { - stale_completion_transition_response(state, conversation_id, run_id).await + stale_completion_transition_response( + state, + conversation_id, + owning_conversation_id, + run_id, + ) + .await } } } diff --git a/src-tauri/tests/suite_http_handlers/agent_workspace_repair_completion.rs b/src-tauri/tests/suite_http_handlers/agent_workspace_repair_completion.rs index 37decd24dc..3c3ee2f27d 100644 --- a/src-tauri/tests/suite_http_handlers/agent_workspace_repair_completion.rs +++ b/src-tauri/tests/suite_http_handlers/agent_workspace_repair_completion.rs @@ -341,6 +341,8 @@ async fn child_hosted_repair_completion_resolves_and_settles_owning_workspace() blocker: Some("Waiting for a maintainer decision.".to_string()), reported_fix_commit_sha: None, resolution: None, + what_happened: None, + what_i_did: None, }, ) .await, @@ -391,6 +393,8 @@ async fn unrelated_parented_child_cannot_complete_workspace_repair() { blocker: Some("This must be rejected.".to_string()), reported_fix_commit_sha: None, resolution: None, + what_happened: None, + what_i_did: None, }, ) .await; From 1fafa2888b75f58b00ad434aab2e9a1c7f59b907 Mon Sep 17 00:00:00 2001 From: lazabogdan <6580668+lazabogdan@users.noreply.github.com> Date: Mon, 24 Aug 2026 14:25:53 +0300 Subject: [PATCH 29/29] fix: keep terminal cleanup fail-open when the workspace row is gone The merge with origin/main brought back terminalize_reports_missing_workspace_ without_claiming_cleanup, which encodes main's contract: a vanished workspace row must still report runtime shutdown success so local cleanup can settle it as FailedOperational. The branch's fixer-runtime enumeration had turned that definitive absence into a runtime_blocked outcome, which would spin the PR poller's terminal retry loop forever on a workspace that no longer exists. Repo read failures stay fail-closed; only Ok(None) falls back to the conversation's own runtime and continues into local cleanup. Co-Authored-By: Claude Opus 5 --- .../agent_workspace_terminal_cleanup.rs | 44 ++++++++++--------- .../agent_workspace_terminal_cleanup_tests.rs | 2 +- 2 files changed, 25 insertions(+), 21 deletions(-) diff --git a/src-tauri/src/application/agent_workspace_terminal_cleanup.rs b/src-tauri/src/application/agent_workspace_terminal_cleanup.rs index 0dc360fcc0..2ffb0dad00 100644 --- a/src-tauri/src/application/agent_workspace_terminal_cleanup.rs +++ b/src-tauri/src/application/agent_workspace_terminal_cleanup.rs @@ -129,33 +129,37 @@ pub(crate) async fn terminalize_agent_workspace_after_pr( project: &Project, cause: TerminalAgentWorkspaceCause, ) -> TerminalAgentWorkspaceOutcome { + // A read failure is degraded state and stays fail-closed, but a vanished workspace row is a + // definitive absence: there are no persisted fixer runtimes left to enumerate. Fall back to the + // conversation's own runtime and let local cleanup report the missing row, so the poller's + // terminal retry loop cannot spin forever on a workspace that no longer exists. let workspace = match workspace_repo.get_by_conversation_id(conversation_id).await { - Ok(Some(workspace)) => workspace, - Ok(None) => { - return TerminalAgentWorkspaceOutcome::runtime_blocked( - "Agent workspace disappeared before terminal runtime cleanup".to_string(), - ); - } + Ok(workspace) => workspace, Err(error) => { return TerminalAgentWorkspaceOutcome::runtime_blocked(format!( "Failed to load the workspace before terminal runtime cleanup: {error}" )); } }; - let observed_publish_lease_token = workspace.publish_lease_token.clone(); - let mut runtime_conversations = match agent_workspace_fixer_runtime_conversations( - &workspace, - workspace_repo.as_ref(), - repair_repo.as_ref(), - ) - .await - { - Ok(conversations) => conversations, - Err(error) => { - return TerminalAgentWorkspaceOutcome::runtime_blocked(format!( - "Failed to resolve workspace fixer runtimes: {error}" - )); - } + let observed_publish_lease_token = workspace + .as_ref() + .and_then(|workspace| workspace.publish_lease_token.clone()); + let mut runtime_conversations = match workspace.as_ref() { + Some(workspace) => match agent_workspace_fixer_runtime_conversations( + workspace, + workspace_repo.as_ref(), + repair_repo.as_ref(), + ) + .await + { + Ok(conversations) => conversations, + Err(error) => { + return TerminalAgentWorkspaceOutcome::runtime_blocked(format!( + "Failed to resolve workspace fixer runtimes: {error}" + )); + } + }, + None => vec![*conversation_id], }; match workspace_repo .get_workspace_review_monitor(conversation_id) diff --git a/src-tauri/src/application/agent_workspace_terminal_cleanup_tests.rs b/src-tauri/src/application/agent_workspace_terminal_cleanup_tests.rs index 9b379c0465..85b4c9b1f2 100644 --- a/src-tauri/src/application/agent_workspace_terminal_cleanup_tests.rs +++ b/src-tauri/src/application/agent_workspace_terminal_cleanup_tests.rs @@ -492,7 +492,7 @@ async fn terminalize_reports_missing_workspace_without_claiming_cleanup() { assert!(outcome .message .as_deref() - .is_some_and(|message| message.contains("disappeared before terminal runtime cleanup"))); + .is_some_and(|message| message.contains("disappeared before local cleanup"))); } #[tokio::test]