refactor(worker): simplify ClaudeProcess to accept pre-built args and use StreamAnalyzer
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -16,17 +16,16 @@ public sealed class ClaudeProcess : IClaudeProcess
|
||||
}
|
||||
|
||||
public async Task<RunResult> RunAsync(
|
||||
string arguments,
|
||||
string prompt,
|
||||
string workingDirectory,
|
||||
string logPath,
|
||||
string taskId,
|
||||
Func<string, Task> onStdoutLine,
|
||||
CancellationToken ct)
|
||||
{
|
||||
var psi = new ProcessStartInfo
|
||||
{
|
||||
FileName = _cfg.ClaudeBin,
|
||||
Arguments = "-p --output-format stream-json --verbose --dangerously-skip-permissions",
|
||||
Arguments = arguments,
|
||||
WorkingDirectory = workingDirectory,
|
||||
RedirectStandardInput = true,
|
||||
RedirectStandardOutput = true,
|
||||
@@ -40,30 +39,25 @@ public sealed class ClaudeProcess : IClaudeProcess
|
||||
using var process = new Process { StartInfo = psi };
|
||||
process.Start();
|
||||
|
||||
// Write prompt to stdin, then close.
|
||||
await process.StandardInput.WriteAsync(prompt);
|
||||
process.StandardInput.Close();
|
||||
|
||||
string? resultMarkdown = null;
|
||||
var analyzer = new StreamAnalyzer();
|
||||
var lastStderr = new StringBuilder();
|
||||
|
||||
// Register cancellation to kill the process tree.
|
||||
await using var ctr = ct.Register(() =>
|
||||
{
|
||||
try { process.Kill(entireProcessTree: true); }
|
||||
catch { /* already exited */ }
|
||||
});
|
||||
|
||||
// Read stdout and stderr concurrently.
|
||||
var stdoutTask = Task.Run(async () =>
|
||||
{
|
||||
while (await process.StandardOutput.ReadLineAsync(ct) is { } line)
|
||||
{
|
||||
if (string.IsNullOrEmpty(line)) continue;
|
||||
await onStdoutLine(line);
|
||||
|
||||
if (MessageParser.TryExtractResult(line, out var res))
|
||||
resultMarkdown = res;
|
||||
analyzer.ProcessLine(line);
|
||||
}
|
||||
}, ct);
|
||||
|
||||
@@ -81,16 +75,34 @@ public sealed class ClaudeProcess : IClaudeProcess
|
||||
await process.WaitForExitAsync(ct);
|
||||
|
||||
var exitCode = process.ExitCode;
|
||||
var streamResult = analyzer.GetResult();
|
||||
|
||||
if (exitCode == 0 && resultMarkdown is not null)
|
||||
if (exitCode == 0 && streamResult.ResultMarkdown is not null)
|
||||
{
|
||||
return new RunResult { ExitCode = exitCode, ResultMarkdown = resultMarkdown };
|
||||
return new RunResult
|
||||
{
|
||||
ExitCode = exitCode,
|
||||
ResultMarkdown = streamResult.ResultMarkdown,
|
||||
StructuredOutputJson = streamResult.StructuredOutputJson,
|
||||
SessionId = streamResult.SessionId,
|
||||
TurnCount = streamResult.TurnCount,
|
||||
TokensIn = streamResult.TokensIn,
|
||||
TokensOut = streamResult.TokensOut,
|
||||
};
|
||||
}
|
||||
|
||||
var error = lastStderr.Length > 0
|
||||
? lastStderr.ToString().Trim()
|
||||
: $"Claude exited with code {exitCode} and no result.";
|
||||
|
||||
return new RunResult { ExitCode = exitCode, ErrorMarkdown = error };
|
||||
return new RunResult
|
||||
{
|
||||
ExitCode = exitCode,
|
||||
ErrorMarkdown = error,
|
||||
SessionId = streamResult.SessionId,
|
||||
TurnCount = streamResult.TurnCount,
|
||||
TokensIn = streamResult.TokensIn,
|
||||
TokensOut = streamResult.TokensOut,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,10 +3,9 @@ namespace ClaudeDo.Worker.Runner;
|
||||
public interface IClaudeProcess
|
||||
{
|
||||
Task<RunResult> RunAsync(
|
||||
string arguments,
|
||||
string prompt,
|
||||
string workingDirectory,
|
||||
string logPath,
|
||||
string taskId,
|
||||
Func<string, Task> onStdoutLine,
|
||||
CancellationToken ct);
|
||||
}
|
||||
|
||||
@@ -75,18 +75,23 @@ public sealed class TaskRunner
|
||||
await _taskRepo.MarkRunningAsync(task.Id, now, ct);
|
||||
await _broadcaster.TaskStarted(slot, task.Id, now);
|
||||
|
||||
// Build prompt.
|
||||
// Build prompt and arguments.
|
||||
var prompt = string.IsNullOrWhiteSpace(task.Description)
|
||||
? task.Title
|
||||
: $"{task.Title}\n\n{task.Description.Trim()}";
|
||||
|
||||
var arguments = new ClaudeArgsBuilder().Build(new ClaudeRunConfig(
|
||||
Model: null,
|
||||
SystemPrompt: null,
|
||||
AgentPath: null,
|
||||
ResumeSessionId: null));
|
||||
|
||||
await using var logWriter = new LogWriter(logPath);
|
||||
|
||||
var result = await _claude.RunAsync(
|
||||
arguments,
|
||||
prompt,
|
||||
runDir,
|
||||
logPath,
|
||||
task.Id,
|
||||
async line =>
|
||||
{
|
||||
await logWriter.WriteLineAsync(line, ct);
|
||||
|
||||
Reference in New Issue
Block a user