Files
ClaudeDo/src/ClaudeDo.Worker/Runner/LiveSessionRegistry.cs
Mika Kuns d8a043fae7 feat(worker): persistent streaming Claude session + live session registry
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.
2026-06-26 16:11:52 +02:00

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();
}
}