Files
AX-Copilot-Codex/src/AxCopilot/Services/DraftQueueService.cs
lacvet 72a8c0d541
Some checks failed
Release Gate / gate (push) Has been cancelled
빌드 부산물 추적 해제와 AX Agent 대기열·composer UI 정리
- .gitignore에 bin/obj/publish 및 IDE/OS/비밀정보 패턴 추가
- Git 인덱스에서 publish 및 src 하위 bin/obj 빌드 부산물 추적을 해제하여 저장소 노이즈를 정리
- DraftQueue를 실행 대기/최근 결과 섹션과 상태 요약 pill 구조로 재정리
- composer 상단 모델/컨텍스트/프리셋 줄과 하단 작업 위치 칩 UI를 더 평평한 시각 언어로 통일
- 워크스페이스·브랜치·워크트리 패널에 공통 row 및 요약 strip을 적용해 panel UX를 정돈
- README.md와 docs/DEVELOPMENT.md, docs/AGENT_ROADMAP.md, AGENTS.md 이력을 갱신

검증
- dotnet build src/AxCopilot/AxCopilot.csproj -c Release -v minimal -p:OutputPath=bin\\verify\\ -p:IntermediateOutputPath=obj\\verify\\
- 경고 0개, 오류 0개
2026-04-04 23:03:42 +09:00

159 lines
5.0 KiB
C#

using AxCopilot.Models;
namespace AxCopilot.Services;
/// <summary>
/// 드래프트 큐 항목의 정렬과 상태 전이를 담당한다.
/// 이후 command queue 실행기와 직접 연결될 수 있도록 queue policy를 분리한다.
/// </summary>
public sealed class DraftQueueService
{
public sealed class DraftQueueSummary
{
public int TotalCount { get; init; }
public int QueuedCount { get; init; }
public int RunningCount { get; init; }
public int BlockedCount { get; init; }
public int FailedCount { get; init; }
public int CompletedCount { get; init; }
public DraftQueueItem? NextItem { get; init; }
public DateTime? NextReadyAt { get; init; }
}
public DraftQueueItem CreateItem(string text, string priority = "next", string kind = "message")
{
return new DraftQueueItem
{
Text = text.Trim(),
Priority = NormalizePriority(priority),
Kind = string.IsNullOrWhiteSpace(kind) ? "message" : kind.Trim().ToLowerInvariant(),
State = "queued",
CreatedAt = DateTime.Now,
};
}
public DraftQueueItem? GetNextQueuedItem(IEnumerable<DraftQueueItem>? items, DateTime? now = null)
{
var at = now ?? DateTime.Now;
return items?
.Where(x => CanRunNow(x, at))
.OrderBy(GetPriorityRank)
.ThenBy(x => x.CreatedAt)
.FirstOrDefault();
}
public DraftQueueSummary GetSummary(IEnumerable<DraftQueueItem>? items, DateTime? now = null)
{
var at = now ?? DateTime.Now;
var snapshot = items?.ToList() ?? [];
var nextReadyAt = snapshot
.Where(IsBlocked)
.Select(x => x.NextRetryAt)
.Where(x => x.HasValue)
.OrderBy(x => x)
.FirstOrDefault();
return new DraftQueueSummary
{
TotalCount = snapshot.Count,
QueuedCount = snapshot.Count(x => CanRunNow(x, at)),
RunningCount = snapshot.Count(x => string.Equals(x.State, "running", StringComparison.OrdinalIgnoreCase)),
BlockedCount = snapshot.Count(IsBlocked),
FailedCount = snapshot.Count(x => string.Equals(x.State, "failed", StringComparison.OrdinalIgnoreCase)),
CompletedCount = snapshot.Count(x => string.Equals(x.State, "completed", StringComparison.OrdinalIgnoreCase)),
NextItem = GetNextQueuedItem(snapshot, at),
NextReadyAt = nextReadyAt,
};
}
public bool MarkRunning(DraftQueueItem? item)
{
if (item == null)
return false;
item.State = "running";
item.LastError = null;
item.NextRetryAt = null;
item.AttemptCount++;
return true;
}
public bool MarkCompleted(DraftQueueItem? item)
{
if (item == null)
return false;
item.State = "completed";
item.LastError = null;
item.NextRetryAt = null;
return true;
}
public bool MarkFailed(DraftQueueItem? item, string? error)
{
if (item == null)
return false;
item.State = "failed";
item.LastError = string.IsNullOrWhiteSpace(error) ? null : error.Trim();
item.NextRetryAt = null;
return true;
}
public bool ScheduleRetry(DraftQueueItem? item, string? error, TimeSpan? delay = null)
{
if (item == null)
return false;
item.State = "queued";
item.LastError = string.IsNullOrWhiteSpace(error) ? null : error.Trim();
item.NextRetryAt = DateTime.Now.Add(delay ?? GetRetryDelay(item));
return true;
}
public bool ResetToQueued(DraftQueueItem? item)
{
if (item == null)
return false;
item.State = "queued";
item.LastError = null;
item.NextRetryAt = null;
return true;
}
public TimeSpan GetRetryDelay(DraftQueueItem item)
{
var seconds = Math.Min(300, 15 * Math.Max(1, (int)Math.Pow(2, Math.Max(0, item.AttemptCount - 1))));
return TimeSpan.FromSeconds(seconds);
}
public bool CanRunNow(DraftQueueItem item, DateTime? now = null)
{
var at = now ?? DateTime.Now;
return IsQueued(item) && (!item.NextRetryAt.HasValue || item.NextRetryAt.Value <= at);
}
private static bool IsQueued(DraftQueueItem item)
=> string.Equals(item.State, "queued", StringComparison.OrdinalIgnoreCase);
private static bool IsBlocked(DraftQueueItem item)
=> IsQueued(item) && item.NextRetryAt.HasValue && item.NextRetryAt.Value > DateTime.Now;
private static string NormalizePriority(string? priority)
=> priority switch
{
"now" => "now",
"later" => "later",
_ => "next",
};
private static int GetPriorityRank(DraftQueueItem item)
=> NormalizePriority(item.Priority) switch
{
"now" => 0,
"next" => 1,
_ => 2,
};
}