- balanced와 reasoning_first 프로필에서 post-tool verification을 다시 활성화해 일반 코드 수정에도 후속 검증이 붙도록 조정 - bugfix/feature/refactor 작업에 구조화된 최종 보고 게이트를 다시 적용하고 Code 시스템 프롬프트의 VERIFY/REPORT 기준을 더 강하게 복원 - README와 DEVELOPMENT 문서 이력을 2026-04-13 00:08 KST 기준으로 갱신 - 검증: dotnet build src/AxCopilot/AxCopilot.csproj -c Release -v minimal -p:OutputPath=bin\\verify\\ -p:IntermediateOutputPath=obj\\verify\\ 경고 0 / 오류 0
171 lines
6.4 KiB
C#
171 lines
6.4 KiB
C#
using AxCopilot.Models;
|
|
|
|
namespace AxCopilot.Services.Agent;
|
|
|
|
public partial class AgentLoopService
|
|
{
|
|
private void ApplyDocumentPlanSuccessTransitions(
|
|
ContentBlock call,
|
|
ToolResult result,
|
|
List<ChatMessage> 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 ---"),
|
|
("<!-- body start marker -->", "<!-- body end marker -->"),
|
|
};
|
|
|
|
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<ContentBlock> toolCalls,
|
|
List<ChatMessage> 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<bool> TryApplyPostToolVerificationTransitionAsync(
|
|
ContentBlock call,
|
|
ToolResult result,
|
|
List<ChatMessage> 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;
|
|
}
|
|
}
|