Some checks failed
Release Gate / gate (push) Has been cancelled
- .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개
159 lines
5.0 KiB
C#
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,
|
|
};
|
|
}
|