feat(worker): accept #123 task numbers as MCP tool input
TaskIdResolver resolves a #123/bare-123 taskId parameter to its GUID before any lookup, across every External/ MCP tool that takes a task id, including the batch tools' id arrays (via delegation to the already-resolving single-entity methods) and update_task's dependsOnTaskId (empty string still passes through unchanged as the clear-link sentinel). An unknown number throws a clear error instead of a silent null. McpToolDocs.TaskNumberHint tells the agent to refer to tasks as #<number> when reporting to the user, added to the description of get_task, list_tasks, add_task, update_task_status and review_task.
This commit is contained in:
+46
@@ -0,0 +1,46 @@
|
||||
using System.Globalization;
|
||||
using ClaudeDo.Data.Repositories;
|
||||
|
||||
namespace ClaudeDo.Worker.External;
|
||||
|
||||
/// <summary>
|
||||
/// Resolves a task-id MCP parameter that may be a display number (<c>#123</c> or bare <c>123</c>)
|
||||
/// into the task's GUID. A GUID is never all-digits, so the two forms never collide. Anything
|
||||
/// else — including an empty string, which <c>update_task</c>'s dependsOnTaskId uses as a
|
||||
/// "clear the link" sentinel — passes through untouched.
|
||||
/// </summary>
|
||||
internal static class TaskIdResolver
|
||||
{
|
||||
public static async Task<string> ResolveAsync(TaskRepository tasks, string idOrNumber, CancellationToken ct = default)
|
||||
{
|
||||
return (await ResolveCoreAsync(tasks, idOrNumber, ct))!;
|
||||
}
|
||||
|
||||
public static async Task<string?> ResolveOptionalAsync(TaskRepository tasks, string? idOrNumber, CancellationToken ct = default)
|
||||
{
|
||||
return idOrNumber is null ? null : await ResolveCoreAsync(tasks, idOrNumber, ct);
|
||||
}
|
||||
|
||||
public static async Task<IReadOnlyList<string>> ResolveManyAsync(
|
||||
TaskRepository tasks, IReadOnlyList<string> idsOrNumbers, CancellationToken ct = default)
|
||||
{
|
||||
var resolved = new string[idsOrNumbers.Count];
|
||||
for (var i = 0; i < idsOrNumbers.Count; i++)
|
||||
resolved[i] = await ResolveAsync(tasks, idsOrNumbers[i], ct);
|
||||
return resolved;
|
||||
}
|
||||
|
||||
private static async Task<string?> ResolveCoreAsync(TaskRepository tasks, string idOrNumber, CancellationToken ct)
|
||||
{
|
||||
if (idOrNumber.Length == 0)
|
||||
return idOrNumber;
|
||||
|
||||
var candidate = idOrNumber[0] == '#' ? idOrNumber[1..] : idOrNumber;
|
||||
if (!int.TryParse(candidate, NumberStyles.None, CultureInfo.InvariantCulture, out var number))
|
||||
return idOrNumber;
|
||||
|
||||
var task = await tasks.GetByNumberAsync(number, ct)
|
||||
?? throw new InvalidOperationException($"no task with number {number}");
|
||||
return task.Id;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user