using AxCopilot.Models; namespace AxCopilot.Services.Agent; public partial class AgentLoopService { private void ApplyDocumentPlanSuccessTransitions( ContentBlock call, ToolResult result, List messages, ref bool documentPlanCalled, ref string? documentPlanPath, ref string? documentPlanTitle, ref string? documentPlanScaffold) { if (!string.Equals(call.ToolName, "document_plan", StringComparison.OrdinalIgnoreCase)) return; documentPlanCalled = true; var po = result.Output ?? string.Empty; var pm = System.Text.RegularExpressions.Regex.Match(po, @"path:\s*""([^""]+)"""); if (pm.Success) documentPlanPath = pm.Groups[1].Value; var tm = System.Text.RegularExpressions.Regex.Match(po, @"title:\s*""([^""]+)"""); if (tm.Success) documentPlanTitle = tm.Groups[1].Value; documentPlanScaffold = ExtractDocumentPlanScaffold(po); if (!ContainsDocumentPlanFollowUpInstruction(po)) return; var toolHint = ResolveDocumentPlanFollowUpTool(po); EmitEvent(AgentEventType.Thinking, "", $"Document structure ready · next creation candidate {toolHint}"); } private static string? ExtractDocumentPlanScaffold(string output) { if (string.IsNullOrWhiteSpace(output)) return null; var markers = new (string Start, string End)[] { ("--- body start ---", "--- body end ---"), ("", ""), }; foreach (var (startMarker, endMarker) in markers) { var start = output.IndexOf(startMarker, StringComparison.OrdinalIgnoreCase); if (start < 0) continue; var contentStart = start + startMarker.Length; var end = output.IndexOf(endMarker, contentStart, StringComparison.OrdinalIgnoreCase); if (end <= contentStart) continue; var scaffold = output[contentStart..end].Trim(); if (!string.IsNullOrWhiteSpace(scaffold)) return scaffold; } return null; } private static bool ContainsDocumentPlanFollowUpInstruction(string output) { if (string.IsNullOrWhiteSpace(output)) return false; return output.Contains("immediate next step", StringComparison.OrdinalIgnoreCase) || output.Contains("call html_create", StringComparison.OrdinalIgnoreCase) || output.Contains("call document_assemble", StringComparison.OrdinalIgnoreCase) || output.Contains("call docx_create", StringComparison.OrdinalIgnoreCase); } private static string ResolveDocumentPlanFollowUpTool(string output) { if (output.Contains("document_assemble", StringComparison.OrdinalIgnoreCase)) return "document_assemble"; if (output.Contains("docx_create", StringComparison.OrdinalIgnoreCase)) return "docx_create"; if (output.Contains("markdown_create", StringComparison.OrdinalIgnoreCase)) return "markdown_create"; if (output.Contains("file_write", StringComparison.OrdinalIgnoreCase)) return "file_write"; return "html_create"; } private async Task<(bool Completed, bool ConsumedExtraIteration)> TryHandleTerminalDocumentCompletionTransitionAsync( ContentBlock call, ToolResult result, List toolCalls, List messages, Models.LlmSettings llm, ModelExecutionProfileCatalog.ExecutionPolicy executionPolicy, AgentContext context, CancellationToken ct, bool documentPlanWasCalled = false) { if (!result.Success || !IsTerminalDocumentTool(call.ToolName) || toolCalls.Count != 1) return (false, false); if (!string.Equals(ActiveTab, "Code", StringComparison.OrdinalIgnoreCase)) { EmitEvent(AgentEventType.Complete, "", "에이전트 작업 완료"); return (true, false); } var verificationEnabled = executionPolicy.EnablePostToolVerification && AgentTabSettingsResolver.IsPostToolVerificationEnabled(ActiveTab, llm); var shouldVerify = ShouldRunPostToolVerification( ActiveTab, call.ToolName, result.Success, verificationEnabled, verificationEnabled); var consumedExtraIteration = false; if (shouldVerify) { await RunPostToolVerificationAsync(messages, call.ToolName, result, context, ct); consumedExtraIteration = true; } EmitEvent(AgentEventType.Complete, "", "에이전트 작업 완료"); return (true, consumedExtraIteration); } private async Task TryApplyPostToolVerificationTransitionAsync( ContentBlock call, ToolResult result, List messages, Models.LlmSettings llm, ModelExecutionProfileCatalog.ExecutionPolicy executionPolicy, AgentContext context, CancellationToken ct) { if (!result.Success) return false; var verificationEnabled = executionPolicy.EnablePostToolVerification && AgentTabSettingsResolver.IsPostToolVerificationEnabled(ActiveTab, llm); var shouldVerify = ShouldRunPostToolVerification( ActiveTab, call.ToolName, result.Success, verificationEnabled, verificationEnabled); if (!shouldVerify) return false; if (!string.Equals(ActiveTab, "Code", StringComparison.OrdinalIgnoreCase)) return false; var highImpactCodeChange = IsHighImpactCodeModification(ActiveTab ?? "", call.ToolName, result); var hasDiffEvidence = HasDiffEvidenceAfterLastModification(messages); var hasRecentBuildOrTestEvidence = HasBuildOrTestEvidenceAfterLastModification(messages); var isCodeModification = call.ToolName is "file_edit" or "file_write" or "file_manage" or "script_create"; if (!isCodeModification) return false; if (highImpactCodeChange && hasDiffEvidence && hasRecentBuildOrTestEvidence) return false; if (!highImpactCodeChange && (hasDiffEvidence || hasRecentBuildOrTestEvidence)) return false; await RunPostToolVerificationAsync(messages, call.ToolName, result, context, ct); return true; } }