feat(mcp/external): add SetTaskTags
This commit is contained in:
@@ -231,6 +231,23 @@ public sealed class ExternalMcpService
|
|||||||
await _broadcaster.TaskUpdated(taskId);
|
await _broadcaster.TaskUpdated(taskId);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[McpServerTool, Description("Replace the full tag set on an existing task. Missing tag names auto-create. Refuses if the task is Running.")]
|
||||||
|
public async Task<TaskDto> SetTaskTags(
|
||||||
|
string taskId,
|
||||||
|
IReadOnlyList<string> tags,
|
||||||
|
CancellationToken cancellationToken)
|
||||||
|
{
|
||||||
|
var task = await _tasks.GetByIdAsync(taskId, cancellationToken)
|
||||||
|
?? throw new InvalidOperationException($"Task {taskId} not found.");
|
||||||
|
if (task.Status == TaskStatus.Running)
|
||||||
|
throw new InvalidOperationException("Cannot retag a running task. Cancel it first.");
|
||||||
|
|
||||||
|
await _tasks.SetTagsAsync(taskId, tags, cancellationToken);
|
||||||
|
var reload = (await _tasks.GetByIdAsync(taskId, cancellationToken))!;
|
||||||
|
await _broadcaster.TaskUpdated(taskId);
|
||||||
|
return ToDto(reload);
|
||||||
|
}
|
||||||
|
|
||||||
[McpServerTool, Description("List all known tags. Useful for discovering existing tag names (including 'agent' which marks tasks for auto-execution) before tagging.")]
|
[McpServerTool, Description("List all known tags. Useful for discovering existing tag names (including 'agent' which marks tasks for auto-execution) before tagging.")]
|
||||||
public async Task<IReadOnlyList<TagDto>> ListTags(CancellationToken cancellationToken)
|
public async Task<IReadOnlyList<TagDto>> ListTags(CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -261,4 +261,34 @@ public sealed class ExternalMcpServiceTests : IDisposable
|
|||||||
await Assert.ThrowsAsync<InvalidOperationException>(() =>
|
await Assert.ThrowsAsync<InvalidOperationException>(() =>
|
||||||
sut.DeleteTask("does-not-exist", CancellationToken.None));
|
sut.DeleteTask("does-not-exist", CancellationToken.None));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task SetTaskTags_ReplacesTagSetAndBroadcasts()
|
||||||
|
{
|
||||||
|
var listId = await SeedListAsync();
|
||||||
|
var task = await SeedTaskAsync(listId);
|
||||||
|
await _tasks.SetTagsAsync(task.Id, new[] { "agent" });
|
||||||
|
var queue = CreateQueue();
|
||||||
|
var sut = BuildSut(queue);
|
||||||
|
|
||||||
|
var dto = await sut.SetTaskTags(task.Id, new[] { "manual" }, CancellationToken.None);
|
||||||
|
|
||||||
|
var tags = await _tasks.GetTagsAsync(task.Id);
|
||||||
|
Assert.Single(tags);
|
||||||
|
Assert.Equal("manual", tags[0].Name);
|
||||||
|
Assert.Contains(_hub.RecordingClients.Proxy.Calls,
|
||||||
|
c => c.Method == "TaskUpdated" && (string)c.Args[0]! == task.Id);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task SetTaskTags_OnRunning_Throws()
|
||||||
|
{
|
||||||
|
var listId = await SeedListAsync();
|
||||||
|
var task = await SeedTaskAsync(listId, status: TaskStatus.Running);
|
||||||
|
var queue = CreateQueue();
|
||||||
|
var sut = BuildSut(queue);
|
||||||
|
|
||||||
|
await Assert.ThrowsAsync<InvalidOperationException>(() =>
|
||||||
|
sut.SetTaskTags(task.Id, new[] { "manual" }, CancellationToken.None));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user