Initial commit to new repository
This commit is contained in:
616
src/AxCopilot/Services/TaskRunService.cs
Normal file
616
src/AxCopilot/Services/TaskRunService.cs
Normal file
@@ -0,0 +1,616 @@
|
||||
namespace AxCopilot.Services;
|
||||
|
||||
/// <summary>
|
||||
/// Executes task-state tracking behind a single service boundary.
|
||||
/// AppStateService and UI layers should prefer this service over direct store access.
|
||||
/// </summary>
|
||||
public sealed class TaskRunService
|
||||
{
|
||||
public sealed class TaskSummaryState
|
||||
{
|
||||
public int ActiveCount { get; init; }
|
||||
public int PendingPermissionCount { get; init; }
|
||||
public TaskRunStore.TaskRun? LatestRecentTask { get; init; }
|
||||
public bool HasActiveTasks => ActiveCount > 0;
|
||||
}
|
||||
|
||||
private readonly TaskRunStore _store;
|
||||
|
||||
public TaskRunService()
|
||||
: this(new TaskRunStore())
|
||||
{
|
||||
}
|
||||
|
||||
public TaskRunService(TaskRunStore store)
|
||||
{
|
||||
_store = store;
|
||||
_store.Changed += () => Changed?.Invoke();
|
||||
}
|
||||
|
||||
public TaskRunStore Store => _store;
|
||||
public IReadOnlyList<TaskRunStore.TaskRun> ActiveTasks => _store.ActiveTasks;
|
||||
public IReadOnlyList<TaskRunStore.TaskRun> RecentTasks => _store.RecentTasks;
|
||||
|
||||
public event Action? Changed;
|
||||
|
||||
public void StartOrUpdate(string id, string kind, string title, string summary, string status = "running", string? filePath = null)
|
||||
{
|
||||
_store.Upsert(id, kind, title, summary, status, filePath);
|
||||
}
|
||||
|
||||
public void Complete(string id, string? summary = null, string status = "completed")
|
||||
{
|
||||
_store.Complete(id, summary, status);
|
||||
}
|
||||
|
||||
public void CompleteByPrefix(string prefix, string? summary = null, string status = "completed")
|
||||
{
|
||||
_store.CompleteByPrefix(prefix, summary, status);
|
||||
}
|
||||
|
||||
public void RestoreRecent(IEnumerable<TaskRunStore.TaskRun>? recent)
|
||||
{
|
||||
_store.RestoreRecent(recent);
|
||||
}
|
||||
|
||||
public void StartAgentRun(string runId, string summary)
|
||||
{
|
||||
var evt = new Agent.AgentEvent { RunId = runId };
|
||||
StartOrUpdate(GetAgentTaskKey(evt), "agent", "main", summary, "running");
|
||||
}
|
||||
|
||||
public void CompleteAgentRun(string runId, string summary, bool success)
|
||||
{
|
||||
var evt = new Agent.AgentEvent { RunId = runId };
|
||||
Complete(GetAgentTaskKey(evt), summary, success ? "completed" : "failed");
|
||||
}
|
||||
|
||||
public void StartToolRun(string runId, string toolName, string summary, string? filePath = null, bool skill = false)
|
||||
{
|
||||
var evt = new Agent.AgentEvent { RunId = runId, ToolName = toolName };
|
||||
StartOrUpdate(
|
||||
GetToolTaskKey(evt),
|
||||
skill ? "skill" : "tool",
|
||||
string.IsNullOrWhiteSpace(toolName) ? (skill ? "skill" : "tool") : toolName,
|
||||
summary,
|
||||
"running",
|
||||
filePath);
|
||||
}
|
||||
|
||||
public void CompleteToolRun(string runId, string toolName, string summary, bool success)
|
||||
{
|
||||
var evt = new Agent.AgentEvent { RunId = runId, ToolName = toolName };
|
||||
Complete(GetToolTaskKey(evt), summary, success ? "completed" : "failed");
|
||||
}
|
||||
|
||||
public void StartPermissionRequest(string runId, string toolName, string summary, string? filePath = null)
|
||||
{
|
||||
var evt = new Agent.AgentEvent { RunId = runId, ToolName = toolName };
|
||||
StartOrUpdate(
|
||||
GetPermissionTaskKey(evt),
|
||||
"permission",
|
||||
string.IsNullOrWhiteSpace(toolName) ? "권한 요청" : $"{toolName} 권한",
|
||||
summary,
|
||||
"waiting",
|
||||
filePath);
|
||||
}
|
||||
|
||||
public void CompletePermissionRequest(string runId, string toolName, string summary, bool granted)
|
||||
{
|
||||
var evt = new Agent.AgentEvent { RunId = runId, ToolName = toolName };
|
||||
Complete(GetPermissionTaskKey(evt), summary, granted ? "completed" : "failed");
|
||||
}
|
||||
|
||||
public void RecordHookResult(string runId, string toolName, string summary, bool success, string? filePath = null)
|
||||
{
|
||||
var evt = new Agent.AgentEvent { RunId = runId, ToolName = toolName };
|
||||
StartOrUpdate(
|
||||
GetHookTaskKey(evt),
|
||||
"hook",
|
||||
string.IsNullOrWhiteSpace(toolName) ? "hook" : $"{toolName} hook",
|
||||
summary,
|
||||
"running",
|
||||
filePath);
|
||||
Complete(GetHookTaskKey(evt), summary, success ? "completed" : "failed");
|
||||
}
|
||||
|
||||
public void StartBackgroundRun(string id, string title, string summary)
|
||||
{
|
||||
var taskId = BuildBackgroundTaskId(id);
|
||||
StartOrUpdate(taskId, "background", string.IsNullOrWhiteSpace(title) ? "background" : title, summary, "running");
|
||||
}
|
||||
|
||||
public void CompleteBackgroundRun(string id, string title, string summary, bool success)
|
||||
{
|
||||
var taskId = BuildBackgroundTaskId(id);
|
||||
if (!ActiveTasks.Any(x => string.Equals(x.Id, taskId, StringComparison.OrdinalIgnoreCase)))
|
||||
{
|
||||
StartOrUpdate(taskId, "background", string.IsNullOrWhiteSpace(title) ? "background" : title, summary, "running");
|
||||
}
|
||||
|
||||
Complete(taskId, summary, success ? "completed" : "failed");
|
||||
}
|
||||
|
||||
public void StartQueueRun(string tab, string id, string summary)
|
||||
{
|
||||
var taskId = BuildQueueTaskId(tab, id);
|
||||
var title = string.IsNullOrWhiteSpace(tab) ? "queue" : $"{tab} queue";
|
||||
StartOrUpdate(taskId, "queue", title, summary, "running");
|
||||
}
|
||||
|
||||
public void CompleteQueueRun(string tab, string id, string summary, string status)
|
||||
{
|
||||
var taskId = BuildQueueTaskId(tab, id);
|
||||
if (!ActiveTasks.Any(x => string.Equals(x.Id, taskId, StringComparison.OrdinalIgnoreCase)))
|
||||
{
|
||||
var title = string.IsNullOrWhiteSpace(tab) ? "queue" : $"{tab} queue";
|
||||
StartOrUpdate(taskId, "queue", title, summary, "running");
|
||||
}
|
||||
|
||||
Complete(taskId, summary, status);
|
||||
}
|
||||
|
||||
public TaskSummaryState GetSummary()
|
||||
{
|
||||
var active = ActiveTasks;
|
||||
return new TaskSummaryState
|
||||
{
|
||||
ActiveCount = active.Count,
|
||||
PendingPermissionCount = active.Count(task =>
|
||||
string.Equals(task.Kind, "permission", StringComparison.OrdinalIgnoreCase) ||
|
||||
string.Equals(task.Status, "waiting", StringComparison.OrdinalIgnoreCase)),
|
||||
LatestRecentTask = RecentTasks.FirstOrDefault(),
|
||||
};
|
||||
}
|
||||
|
||||
public void RestoreRecentFromExecutionEvents(IEnumerable<Models.ChatExecutionEvent>? history)
|
||||
{
|
||||
var restored = new List<TaskRunStore.TaskRun>();
|
||||
var active = new Dictionary<string, TaskRunStore.TaskRun>(StringComparer.OrdinalIgnoreCase);
|
||||
var seen = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
|
||||
|
||||
if (history != null)
|
||||
{
|
||||
foreach (var item in history
|
||||
.OrderBy(x => x.Timestamp)
|
||||
.ThenBy(x => x.Iteration)
|
||||
.ThenBy(x => x.ElapsedMs))
|
||||
{
|
||||
var scopedId = TryGetScopedId(item);
|
||||
if (string.IsNullOrWhiteSpace(scopedId))
|
||||
continue;
|
||||
|
||||
if (TryCreateActiveTaskFromExecutionEvent(item, out var activeTask))
|
||||
{
|
||||
active[scopedId] = activeTask;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (IsTerminalExecutionEvent(item))
|
||||
{
|
||||
active.Remove(scopedId);
|
||||
RemoveRunScopedActiveTasks(active, item);
|
||||
}
|
||||
}
|
||||
|
||||
foreach (var item in history
|
||||
.OrderByDescending(x => x.Timestamp)
|
||||
.ThenByDescending(GetReplayPriority)
|
||||
.ThenByDescending(x => x.Iteration)
|
||||
.ThenByDescending(x => x.ElapsedMs))
|
||||
{
|
||||
var task = TryCreateTaskFromExecutionEvent(item);
|
||||
if (task == null || !seen.Add(task.Id))
|
||||
continue;
|
||||
|
||||
restored.Add(task);
|
||||
if (restored.Count >= 25)
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
_store.RestoreState(active.Values, restored);
|
||||
}
|
||||
|
||||
private static int GetReplayPriority(Models.ChatExecutionEvent evt)
|
||||
{
|
||||
if (string.Equals(evt.Type, nameof(Agent.AgentEventType.Error), StringComparison.OrdinalIgnoreCase))
|
||||
return 100;
|
||||
if (string.Equals(evt.Type, nameof(Agent.AgentEventType.Complete), StringComparison.OrdinalIgnoreCase))
|
||||
return 90;
|
||||
if (string.Equals(evt.Type, nameof(Agent.AgentEventType.PermissionDenied), StringComparison.OrdinalIgnoreCase))
|
||||
return 80;
|
||||
if (string.Equals(evt.Type, nameof(Agent.AgentEventType.PermissionGranted), StringComparison.OrdinalIgnoreCase))
|
||||
return 70;
|
||||
if (string.Equals(evt.Type, nameof(Agent.AgentEventType.HookResult), StringComparison.OrdinalIgnoreCase))
|
||||
return evt.Success ? 60 : 65;
|
||||
if (string.Equals(evt.Type, nameof(Agent.AgentEventType.ToolResult), StringComparison.OrdinalIgnoreCase))
|
||||
return evt.Success ? 50 : 55;
|
||||
if (string.Equals(evt.Type, nameof(Agent.AgentEventType.ToolCall), StringComparison.OrdinalIgnoreCase))
|
||||
return 40;
|
||||
if (string.Equals(evt.Type, nameof(Agent.AgentEventType.PermissionRequest), StringComparison.OrdinalIgnoreCase))
|
||||
return 30;
|
||||
return 10;
|
||||
}
|
||||
|
||||
public void ApplyAgentEvent(Agent.AgentEvent evt)
|
||||
{
|
||||
switch (evt.Type)
|
||||
{
|
||||
case Agent.AgentEventType.PermissionRequest:
|
||||
StartPermissionRequest(
|
||||
evt.RunId,
|
||||
evt.ToolName,
|
||||
string.IsNullOrWhiteSpace(evt.Summary) ? "권한 확인 대기" : evt.Summary,
|
||||
evt.FilePath);
|
||||
break;
|
||||
case Agent.AgentEventType.PermissionGranted:
|
||||
CompletePermissionRequest(
|
||||
evt.RunId,
|
||||
evt.ToolName,
|
||||
string.IsNullOrWhiteSpace(evt.Summary) ? "권한 확인됨" : evt.Summary,
|
||||
true);
|
||||
break;
|
||||
case Agent.AgentEventType.PermissionDenied:
|
||||
CompletePermissionRequest(
|
||||
evt.RunId,
|
||||
evt.ToolName,
|
||||
string.IsNullOrWhiteSpace(evt.Summary) ? "권한 거부됨" : evt.Summary,
|
||||
false);
|
||||
break;
|
||||
case Agent.AgentEventType.ToolCall:
|
||||
StartToolRun(
|
||||
evt.RunId,
|
||||
evt.ToolName,
|
||||
string.IsNullOrWhiteSpace(evt.Summary) ? $"{evt.ToolName} 실행 중" : evt.Summary,
|
||||
evt.FilePath);
|
||||
break;
|
||||
case Agent.AgentEventType.ToolResult:
|
||||
CompleteToolRun(
|
||||
evt.RunId,
|
||||
evt.ToolName,
|
||||
string.IsNullOrWhiteSpace(evt.Summary) ? evt.ToolName : evt.Summary,
|
||||
evt.Success);
|
||||
break;
|
||||
case Agent.AgentEventType.SkillCall:
|
||||
StartToolRun(
|
||||
evt.RunId,
|
||||
evt.ToolName,
|
||||
string.IsNullOrWhiteSpace(evt.Summary) ? $"{evt.ToolName} 실행 중" : evt.Summary,
|
||||
evt.FilePath,
|
||||
skill: true);
|
||||
break;
|
||||
case Agent.AgentEventType.HookResult:
|
||||
RecordHookResult(
|
||||
evt.RunId,
|
||||
evt.ToolName,
|
||||
string.IsNullOrWhiteSpace(evt.Summary) ? "hook 실행" : evt.Summary,
|
||||
evt.Success,
|
||||
evt.FilePath);
|
||||
break;
|
||||
case Agent.AgentEventType.Thinking:
|
||||
case Agent.AgentEventType.Planning:
|
||||
StartAgentRun(evt.RunId, evt.Summary);
|
||||
break;
|
||||
case Agent.AgentEventType.Complete:
|
||||
CompleteAgentRun(
|
||||
evt.RunId,
|
||||
string.IsNullOrWhiteSpace(evt.Summary) ? "작업 완료" : evt.Summary,
|
||||
true);
|
||||
CompleteByPrefix(
|
||||
string.IsNullOrWhiteSpace(evt.RunId) ? "tool:" : $"tool:{evt.RunId}:",
|
||||
string.IsNullOrWhiteSpace(evt.Summary) ? "작업 완료" : evt.Summary,
|
||||
"completed");
|
||||
CompleteByPrefix(
|
||||
string.IsNullOrWhiteSpace(evt.RunId) ? "permission:" : $"permission:{evt.RunId}:",
|
||||
string.IsNullOrWhiteSpace(evt.Summary) ? "작업 완료" : evt.Summary,
|
||||
"completed");
|
||||
CompleteByPrefix(
|
||||
string.IsNullOrWhiteSpace(evt.RunId) ? "hook:" : $"hook:{evt.RunId}:",
|
||||
string.IsNullOrWhiteSpace(evt.Summary) ? "작업 완료" : evt.Summary,
|
||||
"completed");
|
||||
break;
|
||||
case Agent.AgentEventType.Error:
|
||||
if (!string.IsNullOrWhiteSpace(evt.ToolName))
|
||||
{
|
||||
CompleteToolRun(
|
||||
evt.RunId,
|
||||
evt.ToolName,
|
||||
string.IsNullOrWhiteSpace(evt.Summary) ? evt.ToolName : evt.Summary,
|
||||
false);
|
||||
}
|
||||
else
|
||||
{
|
||||
CompleteAgentRun(
|
||||
evt.RunId,
|
||||
string.IsNullOrWhiteSpace(evt.Summary) ? "오류 발생" : evt.Summary,
|
||||
false);
|
||||
CompleteByPrefix(
|
||||
string.IsNullOrWhiteSpace(evt.RunId) ? "permission:" : $"permission:{evt.RunId}:",
|
||||
string.IsNullOrWhiteSpace(evt.Summary) ? "오류 발생" : evt.Summary,
|
||||
"failed");
|
||||
CompleteByPrefix(
|
||||
string.IsNullOrWhiteSpace(evt.RunId) ? "hook:" : $"hook:{evt.RunId}:",
|
||||
string.IsNullOrWhiteSpace(evt.Summary) ? "오류 발생" : evt.Summary,
|
||||
"failed");
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
public void ApplySubAgentStatus(Agent.SubAgentStatusEvent evt)
|
||||
{
|
||||
var taskId = string.IsNullOrWhiteSpace(evt.Id) ? "subagent:unknown" : $"subagent:{evt.Id}";
|
||||
var title = string.IsNullOrWhiteSpace(evt.Id) ? "sub-agent" : evt.Id;
|
||||
var summary = string.IsNullOrWhiteSpace(evt.Summary) ? evt.Task : evt.Summary;
|
||||
|
||||
switch (evt.Status)
|
||||
{
|
||||
case Agent.SubAgentRunStatus.Started:
|
||||
StartOrUpdate(taskId, "subagent", title, summary, "running");
|
||||
StartBackgroundRun(evt.Id, title, summary);
|
||||
break;
|
||||
case Agent.SubAgentRunStatus.Completed:
|
||||
Complete(taskId, summary, "completed");
|
||||
CompleteBackgroundRun(evt.Id, title, summary, true);
|
||||
break;
|
||||
case Agent.SubAgentRunStatus.Failed:
|
||||
Complete(taskId, summary, "failed");
|
||||
CompleteBackgroundRun(evt.Id, title, summary, false);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
private static string GetAgentTaskKey(Agent.AgentEvent evt)
|
||||
=> string.IsNullOrWhiteSpace(evt.RunId) ? "agent:main" : $"agent:{evt.RunId}";
|
||||
|
||||
private static string GetToolTaskKey(Agent.AgentEvent evt)
|
||||
{
|
||||
var suffix = string.IsNullOrWhiteSpace(evt.ToolName) ? "unknown" : evt.ToolName;
|
||||
return string.IsNullOrWhiteSpace(evt.RunId)
|
||||
? $"tool:{suffix}"
|
||||
: $"tool:{evt.RunId}:{suffix}";
|
||||
}
|
||||
|
||||
private static string GetPermissionTaskKey(Agent.AgentEvent evt)
|
||||
{
|
||||
var suffix = string.IsNullOrWhiteSpace(evt.ToolName) ? "unknown" : evt.ToolName;
|
||||
return string.IsNullOrWhiteSpace(evt.RunId)
|
||||
? $"permission:{suffix}"
|
||||
: $"permission:{evt.RunId}:{suffix}";
|
||||
}
|
||||
|
||||
private static string GetHookTaskKey(Agent.AgentEvent evt)
|
||||
{
|
||||
var suffix = string.IsNullOrWhiteSpace(evt.ToolName) ? "unknown" : evt.ToolName;
|
||||
return string.IsNullOrWhiteSpace(evt.RunId)
|
||||
? $"hook:{suffix}"
|
||||
: $"hook:{evt.RunId}:{suffix}";
|
||||
}
|
||||
|
||||
private static string BuildBackgroundTaskId(string id)
|
||||
=> string.IsNullOrWhiteSpace(id) ? "background:unknown" : $"background:{id}";
|
||||
|
||||
private static string BuildQueueTaskId(string tab, string id)
|
||||
{
|
||||
var scope = string.IsNullOrWhiteSpace(tab) ? "default" : tab;
|
||||
var suffix = string.IsNullOrWhiteSpace(id) ? "unknown" : id;
|
||||
return $"queue:{scope}:{suffix}";
|
||||
}
|
||||
|
||||
private static TaskRunStore.TaskRun? TryCreateTaskFromExecutionEvent(Models.ChatExecutionEvent item)
|
||||
{
|
||||
static string BuildScopedId(string prefix, Models.ChatExecutionEvent evt)
|
||||
{
|
||||
var suffix = string.IsNullOrWhiteSpace(evt.ToolName) ? "main" : evt.ToolName;
|
||||
return string.IsNullOrWhiteSpace(evt.RunId)
|
||||
? $"{prefix}:{suffix}"
|
||||
: $"{prefix}:{evt.RunId}:{suffix}";
|
||||
}
|
||||
|
||||
if (string.Equals(item.Type, nameof(Agent.AgentEventType.PermissionGranted), StringComparison.OrdinalIgnoreCase) ||
|
||||
string.Equals(item.Type, nameof(Agent.AgentEventType.PermissionDenied), StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
return new TaskRunStore.TaskRun
|
||||
{
|
||||
Id = BuildScopedId("permission", item),
|
||||
Kind = "permission",
|
||||
Title = string.IsNullOrWhiteSpace(item.ToolName) ? "권한" : $"{item.ToolName} 권한",
|
||||
Summary = item.Summary,
|
||||
Status = string.Equals(item.Type, nameof(Agent.AgentEventType.PermissionDenied), StringComparison.OrdinalIgnoreCase) ? "failed" : "completed",
|
||||
StartedAt = item.Timestamp,
|
||||
UpdatedAt = item.Timestamp,
|
||||
FilePath = item.FilePath,
|
||||
};
|
||||
}
|
||||
|
||||
if (string.Equals(item.Type, nameof(Agent.AgentEventType.ToolResult), StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
return new TaskRunStore.TaskRun
|
||||
{
|
||||
Id = BuildScopedId("tool", item),
|
||||
Kind = "tool",
|
||||
Title = string.IsNullOrWhiteSpace(item.ToolName) ? "tool" : item.ToolName,
|
||||
Summary = item.Summary,
|
||||
Status = item.Success ? "completed" : "failed",
|
||||
StartedAt = item.Timestamp,
|
||||
UpdatedAt = item.Timestamp,
|
||||
FilePath = item.FilePath,
|
||||
};
|
||||
}
|
||||
|
||||
if (string.Equals(item.Type, nameof(Agent.AgentEventType.HookResult), StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
return new TaskRunStore.TaskRun
|
||||
{
|
||||
Id = BuildScopedId("hook", item),
|
||||
Kind = "hook",
|
||||
Title = string.IsNullOrWhiteSpace(item.ToolName) ? "hook" : $"{item.ToolName} hook",
|
||||
Summary = item.Summary,
|
||||
Status = item.Success ? "completed" : "failed",
|
||||
StartedAt = item.Timestamp,
|
||||
UpdatedAt = item.Timestamp,
|
||||
FilePath = item.FilePath,
|
||||
};
|
||||
}
|
||||
|
||||
if (string.Equals(item.Type, nameof(Agent.AgentEventType.Complete), StringComparison.OrdinalIgnoreCase) ||
|
||||
string.Equals(item.Type, nameof(Agent.AgentEventType.Error), StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
return new TaskRunStore.TaskRun
|
||||
{
|
||||
Id = BuildScopedId("agent", item),
|
||||
Kind = "agent",
|
||||
Title = "main",
|
||||
Summary = item.Summary,
|
||||
Status = string.Equals(item.Type, nameof(Agent.AgentEventType.Error), StringComparison.OrdinalIgnoreCase) ? "failed" : "completed",
|
||||
StartedAt = item.Timestamp,
|
||||
UpdatedAt = item.Timestamp,
|
||||
FilePath = item.FilePath,
|
||||
};
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private static bool TryCreateActiveTaskFromExecutionEvent(
|
||||
Models.ChatExecutionEvent item,
|
||||
out TaskRunStore.TaskRun task)
|
||||
{
|
||||
task = new TaskRunStore.TaskRun();
|
||||
|
||||
if (string.Equals(item.Type, nameof(Agent.AgentEventType.PermissionRequest), StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
task = new TaskRunStore.TaskRun
|
||||
{
|
||||
Id = BuildScopedId("permission", item),
|
||||
Kind = "permission",
|
||||
Title = string.IsNullOrWhiteSpace(item.ToolName) ? "권한 요청" : $"{item.ToolName} 권한",
|
||||
Summary = item.Summary,
|
||||
Status = "waiting",
|
||||
StartedAt = item.Timestamp,
|
||||
UpdatedAt = item.Timestamp,
|
||||
FilePath = item.FilePath,
|
||||
};
|
||||
return true;
|
||||
}
|
||||
|
||||
if (string.Equals(item.Type, nameof(Agent.AgentEventType.ToolCall), StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
task = new TaskRunStore.TaskRun
|
||||
{
|
||||
Id = BuildScopedId("tool", item),
|
||||
Kind = "tool",
|
||||
Title = string.IsNullOrWhiteSpace(item.ToolName) ? "tool" : item.ToolName,
|
||||
Summary = item.Summary,
|
||||
Status = "running",
|
||||
StartedAt = item.Timestamp,
|
||||
UpdatedAt = item.Timestamp,
|
||||
FilePath = item.FilePath,
|
||||
};
|
||||
return true;
|
||||
}
|
||||
|
||||
if (string.Equals(item.Type, nameof(Agent.AgentEventType.Thinking), StringComparison.OrdinalIgnoreCase) ||
|
||||
string.Equals(item.Type, nameof(Agent.AgentEventType.Planning), StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
task = new TaskRunStore.TaskRun
|
||||
{
|
||||
Id = BuildScopedId("agent", item),
|
||||
Kind = "agent",
|
||||
Title = "main",
|
||||
Summary = item.Summary,
|
||||
Status = "running",
|
||||
StartedAt = item.Timestamp,
|
||||
UpdatedAt = item.Timestamp,
|
||||
FilePath = item.FilePath,
|
||||
};
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private static bool IsTerminalExecutionEvent(Models.ChatExecutionEvent item)
|
||||
{
|
||||
if (string.Equals(item.Type, nameof(Agent.AgentEventType.PermissionGranted), StringComparison.OrdinalIgnoreCase) ||
|
||||
string.Equals(item.Type, nameof(Agent.AgentEventType.PermissionDenied), StringComparison.OrdinalIgnoreCase))
|
||||
return true;
|
||||
|
||||
if (string.Equals(item.Type, nameof(Agent.AgentEventType.ToolResult), StringComparison.OrdinalIgnoreCase))
|
||||
return true;
|
||||
|
||||
if (string.Equals(item.Type, nameof(Agent.AgentEventType.Complete), StringComparison.OrdinalIgnoreCase))
|
||||
return true;
|
||||
|
||||
if (string.Equals(item.Type, nameof(Agent.AgentEventType.Error), StringComparison.OrdinalIgnoreCase))
|
||||
return true;
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private static void RemoveRunScopedActiveTasks(
|
||||
Dictionary<string, TaskRunStore.TaskRun> active,
|
||||
Models.ChatExecutionEvent item)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(item.RunId))
|
||||
return;
|
||||
|
||||
var shouldClearAllByRun =
|
||||
string.Equals(item.Type, nameof(Agent.AgentEventType.Complete), StringComparison.OrdinalIgnoreCase) ||
|
||||
(string.Equals(item.Type, nameof(Agent.AgentEventType.Error), StringComparison.OrdinalIgnoreCase)
|
||||
&& string.IsNullOrWhiteSpace(item.ToolName));
|
||||
|
||||
if (!shouldClearAllByRun)
|
||||
return;
|
||||
|
||||
var prefixes = new[]
|
||||
{
|
||||
$"agent:{item.RunId}:",
|
||||
$"tool:{item.RunId}:",
|
||||
$"permission:{item.RunId}:",
|
||||
$"hook:{item.RunId}:",
|
||||
};
|
||||
|
||||
var keys = active.Keys
|
||||
.Where(key => prefixes.Any(prefix => key.StartsWith(prefix, StringComparison.OrdinalIgnoreCase)))
|
||||
.ToList();
|
||||
|
||||
foreach (var key in keys)
|
||||
active.Remove(key);
|
||||
}
|
||||
|
||||
private static string? TryGetScopedId(Models.ChatExecutionEvent item)
|
||||
{
|
||||
if (string.Equals(item.Type, nameof(Agent.AgentEventType.PermissionRequest), StringComparison.OrdinalIgnoreCase) ||
|
||||
string.Equals(item.Type, nameof(Agent.AgentEventType.PermissionGranted), StringComparison.OrdinalIgnoreCase) ||
|
||||
string.Equals(item.Type, nameof(Agent.AgentEventType.PermissionDenied), StringComparison.OrdinalIgnoreCase))
|
||||
return BuildScopedId("permission", item);
|
||||
|
||||
if (string.Equals(item.Type, nameof(Agent.AgentEventType.ToolCall), StringComparison.OrdinalIgnoreCase) ||
|
||||
string.Equals(item.Type, nameof(Agent.AgentEventType.ToolResult), StringComparison.OrdinalIgnoreCase))
|
||||
return BuildScopedId("tool", item);
|
||||
|
||||
if (string.Equals(item.Type, nameof(Agent.AgentEventType.Thinking), StringComparison.OrdinalIgnoreCase) ||
|
||||
string.Equals(item.Type, nameof(Agent.AgentEventType.Planning), StringComparison.OrdinalIgnoreCase) ||
|
||||
string.Equals(item.Type, nameof(Agent.AgentEventType.Complete), StringComparison.OrdinalIgnoreCase))
|
||||
return BuildScopedId("agent", item);
|
||||
|
||||
if (string.Equals(item.Type, nameof(Agent.AgentEventType.Error), StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(item.ToolName))
|
||||
return BuildScopedId("agent", item);
|
||||
return BuildScopedId("tool", item);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private static string BuildScopedId(string prefix, Models.ChatExecutionEvent evt)
|
||||
{
|
||||
var suffix = string.IsNullOrWhiteSpace(evt.ToolName) ? "main" : evt.ToolName;
|
||||
return string.IsNullOrWhiteSpace(evt.RunId)
|
||||
? $"{prefix}:{suffix}"
|
||||
: $"{prefix}:{evt.RunId}:{suffix}";
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user