From 31a394e694dd48180f3d8f1cb637629af55379ce Mon Sep 17 00:00:00 2001 From: mika kuns Date: Sat, 25 Apr 2026 11:28:47 +0200 Subject: [PATCH] feat(mcp/external): add DeleteTask Co-Authored-By: Claude Sonnet 4.6 --- .../External/ExternalMcpService.cs | 12 +++++++ .../External/ExternalMcpServiceTests.cs | 36 +++++++++++++++++++ 2 files changed, 48 insertions(+) diff --git a/src/ClaudeDo.Worker/External/ExternalMcpService.cs b/src/ClaudeDo.Worker/External/ExternalMcpService.cs index f1687d9..3f6c955 100644 --- a/src/ClaudeDo.Worker/External/ExternalMcpService.cs +++ b/src/ClaudeDo.Worker/External/ExternalMcpService.cs @@ -219,6 +219,18 @@ public sealed class ExternalMcpService return cancelled; } + [McpServerTool, Description("Delete a task. Refuses if the task is currently Running — cancel it first.")] + public async Task DeleteTask(string taskId, 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 delete a running task. Cancel it first."); + + await _tasks.DeleteAsync(taskId, cancellationToken); + await _broadcaster.TaskUpdated(taskId); + } + [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> ListTags(CancellationToken cancellationToken) { diff --git a/tests/ClaudeDo.Worker.Tests/External/ExternalMcpServiceTests.cs b/tests/ClaudeDo.Worker.Tests/External/ExternalMcpServiceTests.cs index 9a4b6d4..2f40785 100644 --- a/tests/ClaudeDo.Worker.Tests/External/ExternalMcpServiceTests.cs +++ b/tests/ClaudeDo.Worker.Tests/External/ExternalMcpServiceTests.cs @@ -225,4 +225,40 @@ public sealed class ExternalMcpServiceTests : IDisposable await Assert.ThrowsAsync(() => sut.UpdateTask("does-not-exist", "x", null, null, null, CancellationToken.None)); } + + [Fact] + public async Task DeleteTask_RemovesTaskAndTagJoins() + { + var listId = await SeedListAsync(); + var task = await SeedTaskAsync(listId); + await _tasks.SetTagsAsync(task.Id, new[] { "agent" }); + var queue = CreateQueue(); + var sut = BuildSut(queue); + + await sut.DeleteTask(task.Id, CancellationToken.None); + + Assert.Null(await _tasks.GetByIdAsync(task.Id)); + } + + [Fact] + public async Task DeleteTask_OnRunning_Throws() + { + var listId = await SeedListAsync(); + var task = await SeedTaskAsync(listId, status: TaskStatus.Running); + var queue = CreateQueue(); + var sut = BuildSut(queue); + + await Assert.ThrowsAsync(() => + sut.DeleteTask(task.Id, CancellationToken.None)); + } + + [Fact] + public async Task DeleteTask_NotFound_Throws() + { + var queue = CreateQueue(); + var sut = BuildSut(queue); + + await Assert.ThrowsAsync(() => + sut.DeleteTask("does-not-exist", CancellationToken.None)); + } }