using ModelContextProtocol; namespace ClaudeDo.Worker.Lifecycle; /// /// Reports MCP/hub progress for a long-running operation so a waiting caller doesn't hit an /// idle-silence abort (Claude Code's MCP client aborts an MCP call after ~300s of silence). /// internal static class ProgressReporter { /// /// Awaits while reporting progress every . /// rides the same cadence for non-MCP callers (e.g. the Hub, which /// turns it into an OperationProgress broadcast). No-op passthrough when both are null. /// public static async Task RunAsync( Task work, TimeSpan interval, IProgress? progress, string message, Action? onTick = null) { if (progress is null && onTick is null) return await work; var sw = System.Diagnostics.Stopwatch.StartNew(); while (true) { var finished = await Task.WhenAny(work, Task.Delay(interval)); if (finished == work) return await work; progress?.Report(new ProgressNotificationValue { Progress = 0, Message = $"{message} ({sw.Elapsed:mm\\:ss})" }); onTick?.Invoke(sw.Elapsed); } } /// /// Reports progress for one processed item out of a known total, e.g. "cleaning worktrees /// (3/12)". For callers iterating a batch (cleanup_task_worktree, get_task_diff, /// preview_merge_set) instead of awaiting one long-running task. /// public static void ReportItem( IProgress? progress, string action, int current, int total) { progress?.Report(new ProgressNotificationValue { Progress = current, Total = total, Message = $"{action} ({current}/{total})", }); } }