StreamingClaudeSession drives claude --input-format stream-json over a kept- open stdin: sends user messages, interrupts the in-flight turn via the verified control_request protocol, and tracks turn state from result events (treating an interrupt-aborted error_during_execution result as turn-ended). IClaudeStreamTransport abstracts the process I/O so it is unit-tested with a fake (no real claude). LiveSessionRegistry maps taskId -> live session for the hub to route into. Backs the upcoming in-app interactive sessions; autonomous task execution untouched.
44 lines
1.3 KiB
C#
44 lines
1.3 KiB
C#
using System.Collections.Concurrent;
|
|
using ClaudeDo.Worker.Runner.Interfaces;
|
|
|
|
namespace ClaudeDo.Worker.Runner;
|
|
|
|
// Singleton in-memory registry of active live streaming sessions.
|
|
// A session's lifetime matches its associated task run; dead entries are removed by the runner.
|
|
public sealed class LiveSessionRegistry
|
|
{
|
|
private readonly ConcurrentDictionary<string, ILiveSession> _sessions = new();
|
|
|
|
public void Register(string taskId, ILiveSession session)
|
|
{
|
|
if (_sessions.TryRemove(taskId, out var existing))
|
|
{
|
|
// Best-effort stop of the replaced session; don't await to avoid deadlock risk.
|
|
_ = existing.StopAsync().ContinueWith(t =>
|
|
{
|
|
if (t.IsFaulted) { /* swallow — old session is already orphaned */ }
|
|
}, TaskScheduler.Default);
|
|
}
|
|
_sessions[taskId] = session;
|
|
}
|
|
|
|
public bool TryGet(string taskId, out ILiveSession session)
|
|
{
|
|
if (_sessions.TryGetValue(taskId, out var s))
|
|
{
|
|
session = s;
|
|
return true;
|
|
}
|
|
session = null!;
|
|
return false;
|
|
}
|
|
|
|
public void Unregister(string taskId) => _sessions.TryRemove(taskId, out _);
|
|
|
|
public async Task StopAsync(string taskId)
|
|
{
|
|
if (_sessions.TryRemove(taskId, out var session))
|
|
await session.StopAsync();
|
|
}
|
|
}
|