Files
AX-Copilot-Codex/src/AxCopilot/Services/Agent/AgentLoopTransitions.Documents.cs
lacvet 7e774a9387 코드 탭은 컨텍스트만 claude-code식으로 두고 품질 게이트를 AX 기준으로 복원한다
- 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
2026-04-12 22:49:33 +09:00

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;
}
}