feat(claude-do): Expose HandoffRoundTracker budget via an MCP tool (e.g. get_
The handoff-budget task (round/remaining/final now surfaced in the handoff.md brief via HandoffRoundTracker.GetStatus, wired through InteractiveLaunchSpecService.BuildForMergeHelperHandoffAsync) only reaches the session at the start of a handoff round. There is no way to query the current round/remaining budget for a handler task mid-session or from outside (e.g. the UI, or a script). Consider add ClaudeDo-Task: 0a185f8e-823d-42fc-9a5b-97c5eb91c616
This commit is contained in:
+22
@@ -11,6 +11,8 @@ namespace ClaudeDo.Worker.External;
|
||||
// reflects the effective (post-coercion) value in that case.
|
||||
public sealed record HandoffListHandlerResult(bool Requested, string TaskId, int Number, int SurvivingCount, string NextPhase, bool Coerced = false);
|
||||
|
||||
public sealed record HandoffStatusResult(string TaskId, int Number, int Round, int Remaining, bool Final);
|
||||
|
||||
[McpServerToolType]
|
||||
public sealed class HandoffMcpTools
|
||||
{
|
||||
@@ -66,4 +68,24 @@ public sealed class HandoffMcpTools
|
||||
await _broadcaster.HandoffRequested(taskId, survivingTaskIds, round.EffectivePhase);
|
||||
return new HandoffListHandlerResult(true, taskId, task.Number, survivingTaskIds.Count, round.EffectivePhase, round.Coerced);
|
||||
}
|
||||
|
||||
[McpServerTool, Description(
|
||||
"Read-only lookup of a handler task's handoff-round budget (round already used, rounds " +
|
||||
"remaining before handoff_list_handler starts coercing to a _final phase, and whether the " +
|
||||
"chain is already sealed by a completed merge_final round) -- for checking the budget mid-" +
|
||||
"session or from outside a handoff, instead of only seeing it once at the start of a round. " +
|
||||
"A task that never called handoff_list_handler reports round 0, its full remaining budget, " +
|
||||
"and final=false. In-memory only, same as the tracker itself: a worker restart resets it." +
|
||||
McpToolDocs.TaskNumberHint)]
|
||||
public async Task<HandoffStatusResult> GetHandoffStatus(
|
||||
[Description("The handler task's own id.")] string taskId,
|
||||
CancellationToken cancellationToken = default)
|
||||
{
|
||||
taskId = await TaskIdResolver.ResolveAsync(_tasks, taskId, cancellationToken);
|
||||
var task = await _tasks.GetByIdAsync(taskId, cancellationToken)
|
||||
?? throw new InvalidOperationException($"Task {taskId} not found.");
|
||||
|
||||
var status = _rounds.GetStatus(taskId);
|
||||
return new HandoffStatusResult(taskId, task.Number, status.Round, status.Remaining, status.Final);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -26,6 +26,20 @@ public sealed class HandoffRoundTracker
|
||||
|
||||
public sealed record Result(bool Rejected, bool Coerced, string EffectivePhase, string? Error);
|
||||
|
||||
// Read-only budget lookup for a handler task that hasn't (yet) called HandoffListHandler --
|
||||
// Round/Remaining/Final all report their untouched defaults instead of throwing, since "no
|
||||
// handoff yet" is a normal state, not an error.
|
||||
public sealed record Status(int Round, int Remaining, bool Final);
|
||||
|
||||
public Status GetStatus(string taskId)
|
||||
{
|
||||
if (!_states.TryGetValue(taskId, out var state))
|
||||
return new Status(0, MaxHandoffs, false);
|
||||
|
||||
lock (state)
|
||||
return new Status(state.Count, Math.Max(0, MaxHandoffs - state.Count), state.Sealed);
|
||||
}
|
||||
|
||||
public Result Register(string taskId, string nextPhase)
|
||||
{
|
||||
var state = _states.GetOrAdd(taskId, _ => new TaskState());
|
||||
|
||||
Reference in New Issue
Block a user