feat(worker): resume a task's claude session in a terminal
This commit is contained in:
@@ -54,7 +54,7 @@ public sealed class PlanningHubTests : IDisposable
|
||||
private WorkerHub CreateHub()
|
||||
{
|
||||
var hub = new WorkerHub(
|
||||
null!, null!, null!, null!, null!, null!, null!, null!, null!,
|
||||
null!, null!, null!, null!, null!, _db.CreateFactory(), null!, null!, null!,
|
||||
_planning, _launcher, null!, null!, null!, null!, null!, null!, null!, null!,
|
||||
null!, new ClaudeDo.Worker.Online.OnlineInboxConfig(), new ClaudeDo.Worker.Online.OnlineTokenStore(),
|
||||
new ClaudeDo.Worker.Runner.PendingQuestionRegistry(), null!);
|
||||
@@ -85,6 +85,88 @@ public sealed class PlanningHubTests : IDisposable
|
||||
return (listId, task.Id);
|
||||
}
|
||||
|
||||
private async Task SeedRunAsync(string taskId, string? sessionId)
|
||||
{
|
||||
await new TaskRunRepository(_ctx).AddAsync(new TaskRunEntity
|
||||
{
|
||||
Id = Guid.NewGuid().ToString(),
|
||||
TaskId = taskId,
|
||||
RunNumber = 1,
|
||||
IsRetry = false,
|
||||
Prompt = "p",
|
||||
SessionId = sessionId,
|
||||
});
|
||||
}
|
||||
|
||||
private async Task<string> SeedWorktreeAsync(string taskId, WorktreeState state)
|
||||
{
|
||||
var path = Path.Combine(_rootDir, $"wt_{Guid.NewGuid():N}");
|
||||
Directory.CreateDirectory(path);
|
||||
await new WorktreeRepository(_ctx).AddAsync(new WorktreeEntity
|
||||
{
|
||||
TaskId = taskId,
|
||||
Path = path,
|
||||
BranchName = "claudedo/x",
|
||||
BaseCommit = "base",
|
||||
State = state,
|
||||
CreatedAt = DateTime.UtcNow,
|
||||
});
|
||||
return path;
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task ResumeTaskInTerminal_LaunchesWithWorktreePathAndSessionId()
|
||||
{
|
||||
var (_, taskId) = await SeedAsync();
|
||||
await SeedRunAsync(taskId, "sess-99");
|
||||
var wtPath = await SeedWorktreeAsync(taskId, WorktreeState.Active);
|
||||
var hub = CreateHub();
|
||||
|
||||
await hub.ResumeTaskInTerminal(taskId);
|
||||
|
||||
Assert.Equal(1, _launcher.LaunchTerminalResumeCalls);
|
||||
Assert.Equal(wtPath, _launcher.LastResumeWorkingDir);
|
||||
Assert.Equal("sess-99", _launcher.LastResumeSessionId);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task ResumeTaskInTerminal_NoSession_Throws()
|
||||
{
|
||||
var (_, taskId) = await SeedAsync();
|
||||
await SeedRunAsync(taskId, sessionId: null);
|
||||
await SeedWorktreeAsync(taskId, WorktreeState.Active);
|
||||
var hub = CreateHub();
|
||||
|
||||
await Assert.ThrowsAsync<HubException>(() => hub.ResumeTaskInTerminal(taskId));
|
||||
Assert.Equal(0, _launcher.LaunchTerminalResumeCalls);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task ResumeTaskInTerminal_RunningTask_Throws()
|
||||
{
|
||||
var (_, taskId) = await SeedAsync();
|
||||
await SeedRunAsync(taskId, "sess-1");
|
||||
await SeedWorktreeAsync(taskId, WorktreeState.Active);
|
||||
var task = await _tasks.GetByIdAsync(taskId);
|
||||
task!.Status = TaskStatus.Running;
|
||||
await _tasks.UpdateAsync(task);
|
||||
var hub = CreateHub();
|
||||
|
||||
await Assert.ThrowsAsync<HubException>(() => hub.ResumeTaskInTerminal(taskId));
|
||||
Assert.Equal(0, _launcher.LaunchTerminalResumeCalls);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task ResumeTaskInTerminal_NoWorktree_Throws()
|
||||
{
|
||||
var (_, taskId) = await SeedAsync();
|
||||
await SeedRunAsync(taskId, "sess-1");
|
||||
var hub = CreateHub();
|
||||
|
||||
await Assert.ThrowsAsync<HubException>(() => hub.ResumeTaskInTerminal(taskId));
|
||||
Assert.Equal(0, _launcher.LaunchTerminalResumeCalls);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task StartPlanningSessionAsync_ChangesStatusToPlanning_AndInvokesLauncher()
|
||||
{
|
||||
@@ -192,6 +274,19 @@ internal sealed class FakeTerminalLauncher : ITerminalLauncher
|
||||
LaunchResumeCalls++;
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
public int LaunchTerminalResumeCalls { get; private set; }
|
||||
public string? LastResumeWorkingDir { get; private set; }
|
||||
public string? LastResumeSessionId { get; private set; }
|
||||
|
||||
public Task LaunchResumeAsync(string workingDir, string claudeSessionId, CancellationToken cancellationToken)
|
||||
{
|
||||
if (ShouldThrow) throw new TerminalLaunchException("fake launch failure");
|
||||
LaunchTerminalResumeCalls++;
|
||||
LastResumeWorkingDir = workingDir;
|
||||
LastResumeSessionId = claudeSessionId;
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
}
|
||||
|
||||
internal sealed class RecordingClientProxy : IClientProxy
|
||||
|
||||
Reference in New Issue
Block a user