Initial commit to new repository

This commit is contained in:
2026-04-03 18:22:19 +09:00
commit 4458bb0f52
7672 changed files with 452440 additions and 0 deletions

View File

@@ -0,0 +1,129 @@
namespace AxCopilot.Services;
/// <summary>
/// 실행 중/최근 task 상태를 관리하는 전용 저장소.
/// AppStateService와 UI가 같은 로직을 공유할 수 있게 분리합니다.
/// </summary>
public sealed class TaskRunStore
{
public sealed class TaskRun
{
public string Id { get; init; } = "";
public string Kind { get; set; } = "";
public string Title { get; set; } = "";
public string Status { get; set; } = "running";
public string Summary { get; set; } = "";
public DateTime StartedAt { get; set; } = DateTime.Now;
public DateTime UpdatedAt { get; set; } = DateTime.Now;
public string? FilePath { get; set; }
}
private readonly List<TaskRun> _active = new();
private readonly List<TaskRun> _recent = new();
public IReadOnlyList<TaskRun> ActiveTasks => _active;
public IReadOnlyList<TaskRun> RecentTasks => _recent;
public event Action? Changed;
public void Upsert(string id, string kind, string title, string summary, string status = "running", string? filePath = null)
{
var existing = _active.FirstOrDefault(t => string.Equals(t.Id, id, StringComparison.OrdinalIgnoreCase));
if (existing == null)
{
_active.Add(new TaskRun
{
Id = id,
Kind = kind,
Title = title,
Summary = summary,
Status = status,
FilePath = filePath,
StartedAt = DateTime.Now,
UpdatedAt = DateTime.Now,
});
Changed?.Invoke();
return;
}
existing.Kind = kind;
existing.Title = title;
existing.Summary = summary;
existing.Status = status;
existing.FilePath = filePath;
existing.UpdatedAt = DateTime.Now;
Changed?.Invoke();
}
public void Complete(string id, string? summary = null, string status = "completed")
{
var existing = _active.FirstOrDefault(t => string.Equals(t.Id, id, StringComparison.OrdinalIgnoreCase));
if (existing == null)
return;
_active.Remove(existing);
existing.Status = status;
if (!string.IsNullOrWhiteSpace(summary))
existing.Summary = summary;
existing.UpdatedAt = DateTime.Now;
_recent.Insert(0, existing);
TrimRecent();
Changed?.Invoke();
}
public void CompleteByPrefix(string prefix, string? summary = null, string status = "completed")
{
var matches = _active
.Where(t => t.Id.StartsWith(prefix, StringComparison.OrdinalIgnoreCase))
.ToList();
foreach (var match in matches)
{
_active.Remove(match);
match.Status = status;
if (!string.IsNullOrWhiteSpace(summary))
match.Summary = summary;
match.UpdatedAt = DateTime.Now;
_recent.Insert(0, match);
}
if (matches.Count == 0)
return;
TrimRecent();
Changed?.Invoke();
}
public void RestoreRecent(IEnumerable<TaskRun>? recent)
{
RestoreState(null, recent);
}
public void RestoreState(IEnumerable<TaskRun>? active, IEnumerable<TaskRun>? recent)
{
_active.Clear();
_recent.Clear();
if (active != null)
{
_active.AddRange(active
.OrderByDescending(t => t.UpdatedAt)
.Take(25));
}
if (recent != null)
{
_recent.AddRange(recent
.OrderByDescending(t => t.UpdatedAt)
.Take(25));
}
Changed?.Invoke();
}
private void TrimRecent()
{
if (_recent.Count > 25)
_recent.RemoveRange(25, _recent.Count - 25);
}
}