Some checks failed
Release Gate / gate (push) Has been cancelled
- claude-code 선택적 탐색 흐름을 참고해 Cowork/Code 시스템 프롬프트에서 folder_map 상시 선행 지시를 완화하고 glob/grep 기반 좁은 탐색을 우선하도록 조정함 - FolderMapTool 기본 depth를 2로, include_files 기본값을 false로 낮추고 MultiReadTool 최대 파일 수를 8개로 줄여 초기 과탐색 폭을 보수적으로 조정함 - AgentLoopExplorationPolicy partial을 추가해 탐색 범위 분류, broad-scan corrective hint, exploration_breadth 성능 로그를 연결함 - AgentLoopService에 탐색 범위 가이드 주입과 실행 중 탐색 폭 추적을 추가하고, 좁은 질문에서 반복적인 folder_map/대량 multi_read를 교정하도록 정리함 - DocxToHtmlConverter nullable 경고를 수정해 Release 빌드 경고 0 / 오류 0 기준을 다시 충족함 - README와 docs/DEVELOPMENT.md에 2026-04-09 10:36 (KST) 기준 개발 이력을 반영함
147 lines
5.7 KiB
C#
147 lines
5.7 KiB
C#
using AxCopilot.Models;
|
|
|
|
namespace AxCopilot.Services;
|
|
|
|
/// <summary>
|
|
/// 드래프트 큐 항목의 실행 전이와 재시도 정책을 담당합니다.
|
|
/// ChatWindow가 직접 상태 전이를 조합하지 않도록 별도 서비스로 분리합니다.
|
|
/// </summary>
|
|
public sealed class DraftQueueProcessorService
|
|
{
|
|
public bool CanStartNext(ChatSessionStateService? session, string tab)
|
|
=> session?.GetNextQueuedDraft(tab) != null;
|
|
|
|
public DraftQueueItem? TryStartNext(ChatSessionStateService? session, string tab, ChatStorageService? storage = null, string? preferredDraftId = null, TaskRunService? taskRuns = null)
|
|
{
|
|
if (session == null)
|
|
return null;
|
|
|
|
DraftQueueItem? next = null;
|
|
|
|
if (!string.IsNullOrWhiteSpace(preferredDraftId))
|
|
{
|
|
next = session.GetDraftQueueItems(tab)
|
|
.FirstOrDefault(x => string.Equals(x.Id, preferredDraftId, StringComparison.OrdinalIgnoreCase));
|
|
|
|
if (next != null &&
|
|
!string.Equals(next.State, "queued", StringComparison.OrdinalIgnoreCase))
|
|
{
|
|
session.ResetDraftToQueued(tab, next.Id, storage);
|
|
next = session.GetDraftQueueItems(tab)
|
|
.FirstOrDefault(x => string.Equals(x.Id, preferredDraftId, StringComparison.OrdinalIgnoreCase));
|
|
}
|
|
}
|
|
|
|
next ??= session.GetNextQueuedDraft(tab);
|
|
if (next == null)
|
|
return null;
|
|
|
|
if (!session.MarkDraftRunning(tab, next.Id, storage))
|
|
return null;
|
|
|
|
taskRuns?.StartQueueRun(tab, next.Id, next.Text);
|
|
|
|
return session.GetDraftQueueItems(tab)
|
|
.FirstOrDefault(x => string.Equals(x.Id, next.Id, StringComparison.OrdinalIgnoreCase))
|
|
?? next;
|
|
}
|
|
|
|
public bool Complete(ChatSessionStateService? session, string tab, string draftId, ChatStorageService? storage = null, TaskRunService? taskRuns = null)
|
|
{
|
|
var completed = session?.MarkDraftCompleted(tab, draftId, storage) ?? false;
|
|
if (completed)
|
|
taskRuns?.CompleteQueueRun(tab, draftId, "대기열 작업 완료", "completed");
|
|
return completed;
|
|
}
|
|
|
|
public bool HandleFailure(ChatSessionStateService? session, string tab, string draftId, string? error, bool cancelled = false, int maxAutoRetries = 3, ChatStorageService? storage = null, TaskRunService? taskRuns = null)
|
|
{
|
|
if (session == null)
|
|
return false;
|
|
|
|
if (cancelled)
|
|
{
|
|
var reset = session.ResetDraftToQueued(tab, draftId, storage);
|
|
if (reset)
|
|
taskRuns?.CompleteQueueRun(tab, draftId, string.IsNullOrWhiteSpace(error) ? "대기열 작업 중단" : error, "cancelled");
|
|
return reset;
|
|
}
|
|
|
|
var handled = session.ScheduleDraftRetry(tab, draftId, error, maxAutoRetries, storage);
|
|
if (handled)
|
|
{
|
|
var item = session.GetDraftQueueItems(tab)
|
|
.FirstOrDefault(x => string.Equals(x.Id, draftId, StringComparison.OrdinalIgnoreCase));
|
|
var blocked = item?.NextRetryAt.HasValue == true;
|
|
taskRuns?.CompleteQueueRun(
|
|
tab,
|
|
draftId,
|
|
string.IsNullOrWhiteSpace(error) ? (blocked ? "재시도 대기" : "대기열 작업 실패") : error,
|
|
blocked ? "blocked" : "failed");
|
|
}
|
|
|
|
return handled;
|
|
}
|
|
|
|
public int PromoteReadyBlockedItems(ChatSessionStateService? session, string tab, ChatStorageService? storage = null)
|
|
{
|
|
if (session == null)
|
|
return 0;
|
|
|
|
var promoted = 0;
|
|
foreach (var item in session.GetDraftQueueItems(tab)
|
|
.Where(x => string.Equals(x.State, "queued", StringComparison.OrdinalIgnoreCase)
|
|
&& x.NextRetryAt.HasValue
|
|
&& x.NextRetryAt.Value <= DateTime.Now)
|
|
.ToList())
|
|
{
|
|
if (session.ResetDraftToQueued(tab, item.Id, storage))
|
|
promoted++;
|
|
}
|
|
|
|
return promoted;
|
|
}
|
|
|
|
public int ClearCompleted(ChatSessionStateService? session, string tab, ChatStorageService? storage = null)
|
|
=> ClearByState(session, tab, "completed", storage);
|
|
|
|
public int ClearFailed(ChatSessionStateService? session, string tab, ChatStorageService? storage = null)
|
|
=> ClearByState(session, tab, "failed", storage);
|
|
|
|
/// <summary>대기 중인 항목을 모두 제거합니다 (중지 시 사용).</summary>
|
|
public int ClearQueued(ChatSessionStateService? session, string tab, ChatStorageService? storage = null)
|
|
=> ClearByState(session, tab, "queued", storage);
|
|
|
|
/// <summary>실행 중인 항목을 실패로 전환합니다 (중지 시 사용).</summary>
|
|
public int CancelRunning(ChatSessionStateService? session, string tab, ChatStorageService? storage = null)
|
|
{
|
|
if (session == null) return 0;
|
|
int count = 0;
|
|
foreach (var item in session.GetDraftQueueItems(tab)
|
|
.Where(x => string.Equals(x.State, "running", StringComparison.OrdinalIgnoreCase))
|
|
.ToList())
|
|
{
|
|
if (session.RemoveDraft(tab, item.Id, storage))
|
|
count++;
|
|
}
|
|
return count;
|
|
}
|
|
|
|
private static int ClearByState(ChatSessionStateService? session, string tab, string state, ChatStorageService? storage)
|
|
{
|
|
if (session == null)
|
|
return 0;
|
|
|
|
var removed = 0;
|
|
foreach (var item in session.GetDraftQueueItems(tab)
|
|
.Where(x => string.Equals(x.State, state, StringComparison.OrdinalIgnoreCase))
|
|
.ToList())
|
|
{
|
|
if (session.RemoveDraft(tab, item.Id, storage))
|
|
removed++;
|
|
}
|
|
|
|
return removed;
|
|
}
|
|
}
|