feat(worker): session skills SignalR surface + per-level persistence
This commit is contained in:
+1
-1
@@ -58,7 +58,7 @@ public sealed class ConfigMcpTools
|
||||
_ = await _tasks.GetByIdAsync(taskId, cancellationToken)
|
||||
?? throw new InvalidOperationException($"Task {taskId} not found.");
|
||||
|
||||
await _tasks.UpdateAgentSettingsAsync(taskId, model.NullIfBlank(), systemPrompt.NullIfBlank(), agentPath.NullIfBlank(), maxTurns, cancellationToken);
|
||||
await _tasks.UpdateAgentSettingsAsync(taskId, model.NullIfBlank(), systemPrompt.NullIfBlank(), agentPath.NullIfBlank(), maxTurns, ct: cancellationToken);
|
||||
await _broadcaster.TaskUpdated(taskId);
|
||||
}
|
||||
|
||||
|
||||
@@ -15,8 +15,10 @@ using ClaudeDo.Worker.Queue;
|
||||
using ClaudeDo.Worker.Refine;
|
||||
using ClaudeDo.Worker.Report;
|
||||
using ClaudeDo.Worker.Report.Interfaces;
|
||||
using ClaudeDo.Worker.Skills;
|
||||
using ClaudeDo.Worker.State;
|
||||
using ClaudeDo.Worker.Worktrees;
|
||||
using System.Text.Json;
|
||||
using TaskStatus = ClaudeDo.Data.Models.TaskStatus;
|
||||
using Microsoft.AspNetCore.SignalR;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
@@ -37,7 +39,15 @@ public record AppSettingsDto(
|
||||
int WorktreeAutoCleanupDays,
|
||||
string? ReportExcludedPaths,
|
||||
int StandupWeekday,
|
||||
int DailyPrepMaxTasks);
|
||||
int DailyPrepMaxTasks,
|
||||
List<string>? SessionSkills = null);
|
||||
|
||||
public record SessionSkillDto(
|
||||
string Name,
|
||||
string Description,
|
||||
string SourceUrl,
|
||||
string PinnedRef,
|
||||
DateTimeOffset AddedAt);
|
||||
|
||||
public record WorktreeCleanupDto(int Removed);
|
||||
public record WorktreeResetDto(int Removed, int TasksAffected, bool Blocked, int RunningTasks);
|
||||
@@ -65,9 +75,9 @@ public record MergeConflictDocumentsDto(string TaskId, IReadOnlyList<ConflictDoc
|
||||
public record ConflictDocumentDto(string Path, bool IsBinary, IReadOnlyList<MergeSegmentDto> Segments);
|
||||
public record MergeSegmentDto(bool IsConflict, string Text, string Ours, string? Base, string Theirs);
|
||||
public record UpdateListDto(string Id, string Name, string? WorkingDir, string DefaultCommitType);
|
||||
public record UpdateListConfigDto(string ListId, string? Model, string? SystemPrompt, string? AgentPath, int? MaxTurns = null);
|
||||
public record UpdateTaskAgentSettingsDto(string TaskId, string? Model, string? SystemPrompt, string? AgentPath, int? MaxTurns = null);
|
||||
public record ListConfigDto(string? Model, string? SystemPrompt, string? AgentPath, int? MaxTurns = null);
|
||||
public record UpdateListConfigDto(string ListId, string? Model, string? SystemPrompt, string? AgentPath, int? MaxTurns = null, List<string>? SessionSkills = null);
|
||||
public record UpdateTaskAgentSettingsDto(string TaskId, string? Model, string? SystemPrompt, string? AgentPath, int? MaxTurns = null, List<string>? SessionSkills = null);
|
||||
public record ListConfigDto(string? Model, string? SystemPrompt, string? AgentPath, int? MaxTurns = null, List<string>? SessionSkills = null);
|
||||
public record SeedResultDto(int Copied, int Skipped);
|
||||
|
||||
public record OnlineInboxStateDto(
|
||||
@@ -119,6 +129,7 @@ public sealed class WorkerHub : Microsoft.AspNetCore.SignalR.Hub
|
||||
private readonly Runner.PendingQuestionRegistry _pendingQuestions;
|
||||
private readonly InteractiveSessionService _interactive;
|
||||
private readonly LogRingBuffer? _logBuffer;
|
||||
private readonly ISessionSkillRegistry _skillRegistry;
|
||||
|
||||
public WorkerHub(
|
||||
QueueService queue,
|
||||
@@ -145,6 +156,7 @@ public sealed class WorkerHub : Microsoft.AspNetCore.SignalR.Hub
|
||||
OnlineTokenStore onlineTokenStore,
|
||||
Runner.PendingQuestionRegistry pendingQuestions,
|
||||
InteractiveSessionService interactive,
|
||||
ISessionSkillRegistry skillRegistry,
|
||||
LogRingBuffer? logBuffer = null)
|
||||
{
|
||||
_queue = queue;
|
||||
@@ -171,9 +183,23 @@ public sealed class WorkerHub : Microsoft.AspNetCore.SignalR.Hub
|
||||
_onlineTokenStore = onlineTokenStore;
|
||||
_pendingQuestions = pendingQuestions;
|
||||
_interactive = interactive;
|
||||
_skillRegistry = skillRegistry;
|
||||
_logBuffer = logBuffer;
|
||||
}
|
||||
|
||||
// Persistence boundary for the session_skills JSON-array columns (task/list/global).
|
||||
// A null/empty selection persists as null so "inherit / none" stays clean, and the
|
||||
// shape matches TaskRunner.UnionSkillNames's expected JSON string array.
|
||||
private static string? SkillsToJson(List<string>? names) =>
|
||||
names is null or { Count: 0 } ? null : JsonSerializer.Serialize(names);
|
||||
|
||||
private static List<string>? SkillsFromJson(string? json)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(json)) return null;
|
||||
try { return JsonSerializer.Deserialize<List<string>>(json); }
|
||||
catch (JsonException) { return null; }
|
||||
}
|
||||
|
||||
/// <summary>Deliver the user's answer to a question a running task raised via AskUser.
|
||||
/// Returns false if no matching question is still pending (already answered or timed out).</summary>
|
||||
public bool AnswerTaskQuestion(string taskId, string questionId, string answer) =>
|
||||
@@ -288,7 +314,8 @@ public sealed class WorkerHub : Microsoft.AspNetCore.SignalR.Hub
|
||||
row.WorktreeAutoCleanupDays,
|
||||
row.ReportExcludedPaths,
|
||||
row.StandupWeekday,
|
||||
row.DailyPrepMaxTasks);
|
||||
row.DailyPrepMaxTasks,
|
||||
SkillsFromJson(row.SessionSkills));
|
||||
}
|
||||
|
||||
public async Task UpdateAppSettings(AppSettingsDto dto)
|
||||
@@ -310,9 +337,28 @@ public sealed class WorkerHub : Microsoft.AspNetCore.SignalR.Hub
|
||||
ReportExcludedPaths = dto.ReportExcludedPaths,
|
||||
StandupWeekday = dto.StandupWeekday is >= 0 and <= 6 ? dto.StandupWeekday : (int)DayOfWeek.Wednesday,
|
||||
DailyPrepMaxTasks = dto.DailyPrepMaxTasks,
|
||||
SessionSkills = SkillsToJson(dto.SessionSkills),
|
||||
});
|
||||
}
|
||||
|
||||
public async Task<List<SessionSkillDto>> GetSessionSkills()
|
||||
{
|
||||
var rows = await _skillRegistry.ListAsync(Context.ConnectionAborted);
|
||||
return rows.Select(r => new SessionSkillDto(r.Name, r.Description, r.SourceUrl, r.PinnedRef, r.AddedAt)).ToList();
|
||||
}
|
||||
|
||||
public Task<List<string>> InstallSessionSkill(string url) => HubGuard(async () =>
|
||||
{
|
||||
var installed = await _skillRegistry.InstallAsync(url, Context.ConnectionAborted);
|
||||
return installed.ToList();
|
||||
});
|
||||
|
||||
public Task UpdateSessionSkill(string sourceUrl) => HubGuard(
|
||||
() => _skillRegistry.UpdateAsync(sourceUrl, Context.ConnectionAborted));
|
||||
|
||||
public Task RemoveSessionSkill(string sourceUrl) => HubGuard(
|
||||
() => _skillRegistry.RemoveAsync(sourceUrl, Context.ConnectionAborted));
|
||||
|
||||
public async Task<WorktreeCleanupDto> CleanupFinishedWorktrees(string? listId = null)
|
||||
{
|
||||
var result = await _wtMaintenance.CleanupFinishedAsync(listId, Context.ConnectionAborted);
|
||||
@@ -456,8 +502,9 @@ public sealed class WorkerHub : Microsoft.AspNetCore.SignalR.Hub
|
||||
var model = dto.Model.NullIfBlank();
|
||||
var systemPrompt = dto.SystemPrompt.NullIfBlank();
|
||||
var agentPath = dto.AgentPath.NullIfBlank();
|
||||
var sessionSkills = SkillsToJson(dto.SessionSkills);
|
||||
|
||||
if (model is null && systemPrompt is null && agentPath is null && dto.MaxTurns is null)
|
||||
if (model is null && systemPrompt is null && agentPath is null && dto.MaxTurns is null && sessionSkills is null)
|
||||
{
|
||||
await repo.DeleteConfigAsync(dto.ListId);
|
||||
}
|
||||
@@ -470,6 +517,7 @@ public sealed class WorkerHub : Microsoft.AspNetCore.SignalR.Hub
|
||||
SystemPrompt = systemPrompt,
|
||||
AgentPath = agentPath,
|
||||
MaxTurns = dto.MaxTurns,
|
||||
SessionSkills = sessionSkills,
|
||||
});
|
||||
}
|
||||
|
||||
@@ -482,7 +530,7 @@ public sealed class WorkerHub : Microsoft.AspNetCore.SignalR.Hub
|
||||
var repo = new ListRepository(ctx);
|
||||
var config = await repo.GetConfigAsync(listId);
|
||||
if (config is null) return null;
|
||||
return new ListConfigDto(config.Model, config.SystemPrompt, config.AgentPath, config.MaxTurns);
|
||||
return new ListConfigDto(config.Model, config.SystemPrompt, config.AgentPath, config.MaxTurns, SkillsFromJson(config.SessionSkills));
|
||||
}
|
||||
|
||||
public async Task SetTaskStatus(string taskId, string status)
|
||||
@@ -543,7 +591,8 @@ public sealed class WorkerHub : Microsoft.AspNetCore.SignalR.Hub
|
||||
dto.Model.NullIfBlank(),
|
||||
dto.SystemPrompt.NullIfBlank(),
|
||||
dto.AgentPath.NullIfBlank(),
|
||||
dto.MaxTurns);
|
||||
dto.MaxTurns,
|
||||
SkillsToJson(dto.SessionSkills));
|
||||
|
||||
await _broadcaster.TaskUpdated(dto.TaskId);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user