feat(worker): AskUser MCP tool so a running task can ask the user mid-run
A running task can call mcp__claudedo_run__AskUser(question) to block (up to 3 min) on a human answer. PendingQuestionRegistry holds the pending question + TaskCompletionSource; the tool broadcasts TaskQuestionAsked, awaits the answer (WorkerHub.AnswerTaskQuestion resolves it), and returns it as the tool result — or a 'proceed on your judgment' fallback on timeout. The run stays Running throughout (no status/schema change). ClaudeProcess raises MCP_TOOL_TIMEOUT so the 60s HTTP-MCP cap doesn't kill the wait; the run MCP is now wired for every task, not just standalone ones. System prompt updated to reconcile 'unattended'.
This commit is contained in:
@@ -26,6 +26,12 @@ public sealed class HubBroadcaster : IPrimeBroadcaster, IRefineBroadcaster
|
||||
public Task TaskUpdated(string taskId) =>
|
||||
_hub.Clients.All.SendAsync("TaskUpdated", taskId);
|
||||
|
||||
public Task TaskQuestionAsked(string taskId, string questionId, string question) =>
|
||||
_hub.Clients.All.SendAsync("TaskQuestionAsked", taskId, questionId, question);
|
||||
|
||||
public Task TaskQuestionResolved(string taskId, string questionId) =>
|
||||
_hub.Clients.All.SendAsync("TaskQuestionResolved", taskId, questionId);
|
||||
|
||||
public Task ListUpdated(string listId) =>
|
||||
_hub.Clients.All.SendAsync("ListUpdated", listId);
|
||||
|
||||
|
||||
@@ -56,6 +56,7 @@ public record WorktreeOverviewDto(
|
||||
bool PathExistsOnDisk);
|
||||
|
||||
public record ForceRemoveResultDto(bool Removed, string? Reason);
|
||||
public record PendingQuestionDto(string TaskId, string QuestionId, string Question);
|
||||
public record MergeResultDto(string Status, IReadOnlyList<string> ConflictFiles, string? ErrorMessage);
|
||||
public record MergePreviewDto(string Status, IReadOnlyList<string> ConflictFiles, int ChangedFileCount);
|
||||
public record MergeTargetsDto(string DefaultBranch, IReadOnlyList<string> LocalBranches);
|
||||
@@ -114,6 +115,7 @@ public sealed class WorkerHub : Microsoft.AspNetCore.SignalR.Hub
|
||||
private readonly WorkerConfig _cfg;
|
||||
private readonly OnlineInboxConfig _onlineInboxConfig;
|
||||
private readonly OnlineTokenStore _onlineTokenStore;
|
||||
private readonly Runner.PendingQuestionRegistry _pendingQuestions;
|
||||
private readonly LogRingBuffer? _logBuffer;
|
||||
|
||||
public WorkerHub(
|
||||
@@ -139,6 +141,7 @@ public sealed class WorkerHub : Microsoft.AspNetCore.SignalR.Hub
|
||||
WorkerConfig cfg,
|
||||
OnlineInboxConfig onlineInboxConfig,
|
||||
OnlineTokenStore onlineTokenStore,
|
||||
Runner.PendingQuestionRegistry pendingQuestions,
|
||||
LogRingBuffer? logBuffer = null)
|
||||
{
|
||||
_queue = queue;
|
||||
@@ -163,9 +166,22 @@ public sealed class WorkerHub : Microsoft.AspNetCore.SignalR.Hub
|
||||
_cfg = cfg;
|
||||
_onlineInboxConfig = onlineInboxConfig;
|
||||
_onlineTokenStore = onlineTokenStore;
|
||||
_pendingQuestions = pendingQuestions;
|
||||
_logBuffer = logBuffer;
|
||||
}
|
||||
|
||||
/// <summary>Deliver the user's answer to a question a running task raised via AskUser.
|
||||
/// Returns false if no matching question is still pending (already answered or timed out).</summary>
|
||||
public bool AnswerTaskQuestion(string taskId, string questionId, string answer) =>
|
||||
_pendingQuestions.TryAnswer(taskId, questionId, answer ?? string.Empty);
|
||||
|
||||
/// <summary>The question a running task is currently blocked on, if any (for UI re-attach).</summary>
|
||||
public PendingQuestionDto? GetPendingQuestion(string taskId)
|
||||
{
|
||||
var q = _pendingQuestions.Get(taskId);
|
||||
return q is null ? null : new PendingQuestionDto(q.TaskId, q.QuestionId, q.Question);
|
||||
}
|
||||
|
||||
/// <summary>Recent worker log records (last 30 min, all levels) for the Log Visualizer overlay.</summary>
|
||||
public IReadOnlyList<WorkerLogRecord> GetRecentLogs() =>
|
||||
_logBuffer?.Snapshot() ?? Array.Empty<WorkerLogRecord>();
|
||||
|
||||
Reference in New Issue
Block a user