diff --git a/src/ClaudeDo.Data/Wire.cs b/src/ClaudeDo.Data/Wire.cs
new file mode 100644
index 00000000..653373ef
--- /dev/null
+++ b/src/ClaudeDo.Data/Wire.cs
@@ -0,0 +1,200 @@
+using ClaudeDo.Data.Models;
+using TaskStatus = ClaudeDo.Data.Models.TaskStatus;
+
+namespace ClaudeDo.Data.Wire;
+
+// The SignalR contract between WorkerHub (server) and WorkerClient (client). Both used to
+// declare their own copy of every record in this file; they drifted (MergePreviewDto lost its
+// verify fields on the client side, two records disagreed on their name). One definition, so
+// a hub signature change is a compile error instead of a silently dropped JSON field.
+//
+// Imported project-wide via a item in the Ui, Worker
+// and their test projects — that's why nothing here needs a per-file using.
+
+public record ActiveTaskDto(string Slot, string TaskId, DateTime StartedAt);
+
+public record WorkerBuildInfoDto(string? BuildSha);
+
+public record AppSettingsDto(
+ string DefaultClaudeInstructions,
+ string DefaultModel,
+ int DefaultMaxTurns,
+ string DefaultPermissionMode,
+ int MaxParallelExecutions,
+ string WorktreeStrategy,
+ string? CentralWorktreeRoot,
+ bool WorktreeAutoCleanupEnabled,
+ int WorktreeAutoCleanupDays,
+ string? ReportExcludedPaths,
+ int StandupWeekday,
+ int DailyPrepMaxTasks,
+ List? SessionSkills = null,
+ List? ModelPresets = null,
+ int UsageGateFiveHourPct = 80,
+ int UsageGateSevenDayPct = 90,
+ int MaxTurnsCeiling = 80,
+ // Throttle stages per bucket — dragged on the usage-monitor gauges, not typed in Settings.
+ int UsageThrottleFiveHourSoftPct = 50,
+ int UsageThrottleFiveHourHardPct = 65,
+ int UsageThrottleSevenDaySoftPct = 50,
+ int UsageThrottleSevenDayHardPct = 65,
+ bool AutoContinueOnUsageLimit = false);
+
+public record ModelPresetDto(string Model, string Effort, int MaxTurns);
+
+public record SessionSkillDto(
+ string Name,
+ string Description,
+ string SourceUrl,
+ string PinnedRef,
+ DateTimeOffset AddedAt);
+
+public record WorktreeCleanupDto(int Removed);
+
+public record WorktreeResetDto(int Removed, int TasksAffected, bool Blocked, int RunningTasks);
+
+public record WorktreeOverviewDto(
+ string TaskId,
+ string TaskTitle,
+ TaskStatus TaskStatus,
+ string ListId,
+ string ListName,
+ string Path,
+ string BranchName,
+ string BaseCommit,
+ WorktreeState State,
+ string? DiffStat,
+ DateTime CreatedAt,
+ bool PathExistsOnDisk);
+
+public record ForceRemoveResultDto(bool Removed, string? Reason);
+
+public record PlanningMergeConflictStateDto(string PlanningTaskId, string SubtaskId);
+
+public record PendingQuestionDto(string TaskId, string QuestionId, string Question);
+
+public record MergeResultDto(string Status, IReadOnlyList ConflictFiles, string? ErrorMessage);
+
+public record BaseDirtyWarningDto(int ModifiedCount, int UntrackedCount);
+
+public record SetTaskStatusResultDto(BaseDirtyWarningDto? BaseDirty);
+
+public record MergePreviewDto(
+ string Status, IReadOnlyList ConflictFiles, int ChangedFileCount,
+ int? VerifyExitCode = null, long? VerifyDurationMs = null, string? VerifyOutputTail = null);
+
+public record MergeTargetsDto(
+ string DefaultBranch, IReadOnlyList LocalBranches, string DefaultCommitMessage);
+
+public record MergeConflictDocumentsDto(string TaskId, IReadOnlyList Files);
+
+public record ConflictDocumentDto(string Path, bool IsBinary, IReadOnlyList Segments);
+
+public record MergeSegmentDto(bool IsConflict, string Text, string Ours, string? Base, string Theirs);
+
+public record UpdateListDto(string Id, string Name, string? WorkingDir, string DefaultCommitType, bool IsManual = false, bool FindingsTracked = false);
+
+public record UpdateListConfigDto(string ListId, string? Model, string? SystemPrompt, string? AgentPath, int? MaxTurns = null, List? SessionSkills = null, string? VerifyCommand = null);
+
+public record UpdateTaskAgentSettingsDto(string TaskId, string? Model, string? SystemPrompt, string? AgentPath, int? MaxTurns = null, List? SessionSkills = null);
+
+public record ListConfigDto(string? Model, string? SystemPrompt, string? AgentPath, int? MaxTurns = null, List? SessionSkills = null, string? VerifyCommand = null);
+
+public record SeedResultDto(int Copied, int Skipped);
+
+public record OnlineInboxStateDto(
+ bool Enabled,
+ string ApiBaseUrl,
+ string Authority,
+ string ClientId,
+ string Scopes,
+ string RedirectUri,
+ bool SignedIn,
+ int PollIntervalSeconds);
+
+public record OnlineInboxConfigInputDto(
+ bool Enabled,
+ string ApiBaseUrl,
+ int PollIntervalSeconds,
+ string Authority,
+ string ClientId,
+ string Scopes,
+ string RedirectUri);
+
+public record UsageLimitDto(
+ string Kind,
+ string Group,
+ double Percent,
+ string Severity,
+ DateTimeOffset? ResetsAt,
+ string? ScopeModelDisplayName,
+ bool IsActive);
+
+public record UsageSnapshotDto(
+ double? FiveHourPercent,
+ DateTimeOffset? FiveHourResetsAt,
+ double? SevenDayPercent,
+ DateTimeOffset? SevenDayResetsAt,
+ IReadOnlyList Limits,
+ int FiveHourThresholdPct,
+ int SevenDayThresholdPct,
+ bool IsGateBlocked,
+ string? GateReason,
+ DateTime? FetchedAtUtc,
+ bool IsStale,
+ string? LastError,
+ int ConfiguredSlots,
+ int EffectiveSlots,
+ string? ThrottleBucket,
+ // Throttle stages per bucket, drawn (and dragged) on the usage-monitor gauges. Defaults match
+ // the DB defaults so an older worker that doesn't send them yet still yields sane markers.
+ int ThrottleFiveHourSoftPct = 50,
+ int ThrottleFiveHourHardPct = 65,
+ int ThrottleSevenDaySoftPct = 50,
+ int ThrottleSevenDayHardPct = 65);
+
+public record ModelUsageRowDto(
+ DateOnly Date,
+ string Model,
+ string Scope,
+ long InputTokens,
+ long OutputTokens,
+ long CacheReadTokens,
+ long CacheCreationTokens,
+ int Messages,
+ double? CostUsd = null);
+
+public record TaskUsageRowDto(
+ string TaskId,
+ string TaskTitle,
+ string ListId,
+ string ListName,
+ string? Model,
+ int Runs,
+ long TokensIn,
+ long TokensOut,
+ double? CostUsd = null,
+ int? Retries = null,
+ bool? Productive = null,
+ bool? OneShot = null);
+
+public record TokenTrackerDashboardDto(bool Ok, string? Url, string? Error);
+
+public record TokenTrackerStatusDto(
+ bool Installed,
+ string? Version,
+ bool NodeOk,
+ string? NodeVersion,
+ DateTime? LastFetchedUtc,
+ string? LastError,
+ int? FormatVersion,
+ int SessionCount);
+
+// What an embedded ConPTY terminal (UI process) needs to start a real `claude` process for
+// a task's worktree, with the same setup as an autonomous run (session-skills seeded onto
+// disk, the same run environment variables) plus the --resume-vs-fresh-start choice.
+public sealed record LaunchSpec(
+ string Cwd,
+ string Exe,
+ IReadOnlyList Args,
+ IReadOnlyDictionary Env);
diff --git a/src/ClaudeDo.Ui/CLAUDE.md b/src/ClaudeDo.Ui/CLAUDE.md
index 41d06265..48ec46fe 100644
--- a/src/ClaudeDo.Ui/CLAUDE.md
+++ b/src/ClaudeDo.Ui/CLAUDE.md
@@ -84,7 +84,7 @@ new editor boilerplate there rather than copying it a third time.
## Services
-- **WorkerClient / IWorkerClient** — SignalR client on `http://127.0.0.1:47821/hub`, auto-reconnect with exponential backoff. The surface **tracks `WorkerHub`** — treat `src/ClaudeDo.Worker/Hub/WorkerHub.cs` as the canonical method list rather than duplicating it here. Events mirror `HubBroadcaster`. Lifecycle (`StartAsync`/`StopAsync`) and a few admin methods live only on the concrete `WorkerClient`.
+- **WorkerClient / IWorkerClient** — SignalR client on `http://127.0.0.1:47821/hub`, auto-reconnect with exponential backoff. The surface **tracks `WorkerHub`** — treat `src/ClaudeDo.Worker/Hub/WorkerHub.cs` as the canonical method list rather than duplicating it here. The wire DTOs are **not** declared here: they live once in `ClaudeDo.Data/Wire.cs` and reach both sides via a `` item in the csproj (Ui, Worker, and both test projects), so a hub signature change is a compile error instead of a silently dropped JSON field. Add new ones there, never a local copy. Events mirror `HubBroadcaster`. Lifecycle (`StartAsync`/`StopAsync`) and a few admin methods live only on the concrete `WorkerClient`.
- **INotesApi / WorkerNotesApi** — daily-note CRUD; UI DTO `DailyNoteDto(Id, Date, Text, SortOrder)`.
- **IPrimeScheduleApi** — prime-schedule CRUD.
- **UpdateCheckService** — polls releases; `LastCheckStatus`/`LatestVersion`/`CheckNowAsync` feed the shell's update banner.
@@ -95,8 +95,11 @@ new editor boilerplate there rather than copying it a third time.
## Converters
In `Converters/` — grep rather than list: log-level brush, dot brush,
-status equality, icon key, strike/italic/opacity toggles, null→bool,
-uppercase.
+status equality, icon key, `KeepLastNumber`. The one-expression ones
+(strike/italic/opacity toggles, uppercase, not-a-header-row) share
+`SimpleConverters.cs`; put new trivial ones there instead of a new file.
+There is no null→bool converter — bind
+`Converter={x:Static conv:ObjectConverters.IsNotNull}` (Avalonia ships it).
## Dialog Pattern
diff --git a/src/ClaudeDo.Ui/ClaudeDo.Ui.csproj b/src/ClaudeDo.Ui/ClaudeDo.Ui.csproj
index 57689421..5492f80a 100644
--- a/src/ClaudeDo.Ui/ClaudeDo.Ui.csproj
+++ b/src/ClaudeDo.Ui/ClaudeDo.Ui.csproj
@@ -1,4 +1,4 @@
-
+
@@ -31,6 +31,10 @@
true
+
+
+
+
diff --git a/src/ClaudeDo.Ui/Services/Interfaces/IWorkerClient.cs b/src/ClaudeDo.Ui/Services/Interfaces/IWorkerClient.cs
index 62b9cce8..bfbb19c8 100644
--- a/src/ClaudeDo.Ui/Services/Interfaces/IWorkerClient.cs
+++ b/src/ClaudeDo.Ui/Services/Interfaces/IWorkerClient.cs
@@ -63,7 +63,7 @@ public interface IWorkerClient : INotifyPropertyChanged
string? LastApproveTarget { get; }
- IReadOnlyList GetActiveTasks();
+ IReadOnlyList GetActiveTasks();
Task WakeQueueAsync();
Task RunNowAsync(string taskId);
diff --git a/src/ClaudeDo.Ui/Services/WorkerClient.cs b/src/ClaudeDo.Ui/Services/WorkerClient.cs
index c0a3ab0d..d83b5413 100644
--- a/src/ClaudeDo.Ui/Services/WorkerClient.cs
+++ b/src/ClaudeDo.Ui/Services/WorkerClient.cs
@@ -10,7 +10,6 @@ using Microsoft.Extensions.DependencyInjection;
namespace ClaudeDo.Ui.Services;
-public record ActiveTask(string Slot, string TaskId, DateTime StartedAt);
public sealed record WorkerLogEntry(string Message, WorkerLogLevel Level, DateTime TimestampUtc);
sealed class IndefiniteRetryPolicy : IRetryPolicy
@@ -41,7 +40,7 @@ public partial class WorkerClient : ObservableObject, IAsyncDisposable, IWorkerC
[ObservableProperty]
private bool _isReconnecting;
- public ObservableCollection ActiveTasks { get; } = new();
+ public ObservableCollection ActiveTasks { get; } = new();
public event Action? TaskStartedEvent;
public event Action? TaskFinishedEvent;
@@ -77,7 +76,7 @@ public partial class WorkerClient : ObservableObject, IAsyncDisposable, IWorkerC
public string? LastApproveTarget { get; private set; }
- public IReadOnlyList GetActiveTasks() => ActiveTasks.ToList();
+ public IReadOnlyList GetActiveTasks() => ActiveTasks.ToList();
public WorkerClient(string signalRUrl)
{
@@ -118,7 +117,7 @@ public partial class WorkerClient : ObservableObject, IAsyncDisposable, IWorkerC
{
Dispatcher.UIThread.Post(() =>
{
- ActiveTasks.Add(new ActiveTask(slot, taskId, startedAt));
+ ActiveTasks.Add(new ActiveTaskDto(slot, taskId, startedAt));
TaskStartedEvent?.Invoke(slot, taskId, startedAt);
});
});
@@ -417,7 +416,7 @@ public partial class WorkerClient : ObservableObject, IAsyncDisposable, IWorkerC
{
ActiveTasks.Clear();
foreach (var a in active)
- ActiveTasks.Add(new ActiveTask(a.Slot, a.TaskId, a.StartedAt));
+ ActiveTasks.Add(new ActiveTaskDto(a.Slot, a.TaskId, a.StartedAt));
});
}
catch (HubException)
@@ -712,177 +711,5 @@ public partial class WorkerClient : ObservableObject, IAsyncDisposable, IWorkerC
=> await FinalizePlanningSessionAsync(taskId, queueAgentTasks, ct);
async Task IWorkerClient.GetPendingDraftCountAsync(string taskId, CancellationToken ct)
=> await GetPendingDraftCountAsync(taskId, ct);
-
- // DTOs for deserializing hub responses
- private sealed class ActiveTaskDto
- {
- public string Slot { get; set; } = "";
- public string TaskId { get; set; } = "";
- public DateTime StartedAt { get; set; }
- }
}
-public sealed record AppSettingsDto(
- string DefaultClaudeInstructions,
- string DefaultModel,
- int DefaultMaxTurns,
- string DefaultPermissionMode,
- int MaxParallelExecutions,
- string WorktreeStrategy,
- string? CentralWorktreeRoot,
- bool WorktreeAutoCleanupEnabled,
- int WorktreeAutoCleanupDays,
- string? ReportExcludedPaths,
- int StandupWeekday,
- int DailyPrepMaxTasks,
- List? SessionSkills = null,
- List? ModelPresets = null,
- int UsageGateFiveHourPct = 80,
- int UsageGateSevenDayPct = 90,
- int MaxTurnsCeiling = 80,
- // Throttle stages per bucket — dragged on the usage-monitor gauges, not typed in Settings.
- int UsageThrottleFiveHourSoftPct = 50,
- int UsageThrottleFiveHourHardPct = 65,
- int UsageThrottleSevenDaySoftPct = 50,
- int UsageThrottleSevenDayHardPct = 65,
- bool AutoContinueOnUsageLimit = false);
-
-// Per-model run defaults (effort + turn budget) edited in Settings → General.
-public sealed record ModelPresetDto(string Model, string Effort, int MaxTurns);
-
-public sealed record SessionSkillDto(
- string Name,
- string Description,
- string SourceUrl,
- string PinnedRef,
- DateTimeOffset AddedAt);
-
-public sealed record WorktreeCleanupDto(int Removed);
-public sealed record WorktreeResetDto(int Removed, int TasksAffected, bool Blocked, int RunningTasks);
-public record MergeResultDto(string Status, IReadOnlyList ConflictFiles, string? ErrorMessage);
-public record BaseDirtyWarningDto(int ModifiedCount, int UntrackedCount);
-public record SetTaskStatusResultDto(BaseDirtyWarningDto? BaseDirty);
-public record MergePreviewDto(string Status, IReadOnlyList ConflictFiles, int ChangedFileCount);
-public record MergeTargetsDto(
- string DefaultBranch, IReadOnlyList LocalBranches, string DefaultCommitMessage);
-public record MergeConflictDocumentsDto(string TaskId, IReadOnlyList Files);
-public record ConflictDocumentDto(string Path, bool IsBinary, IReadOnlyList Segments);
-public record MergeSegmentDto(bool IsConflict, string Text, string Ours, string? Base, string Theirs);
-public sealed record UpdateListDto(string Id, string Name, string? WorkingDir, string DefaultCommitType, bool IsManual = false, bool FindingsTracked = false);
-public sealed record UpdateListConfigDto(string ListId, string? Model, string? SystemPrompt, string? AgentPath, int? MaxTurns = null, List? SessionSkills = null, string? VerifyCommand = null);
-public sealed record UpdateTaskAgentSettingsDto(string TaskId, string? Model, string? SystemPrompt, string? AgentPath, int? MaxTurns = null, List? SessionSkills = null);
-public sealed record ListConfigDto(string? Model, string? SystemPrompt, string? AgentPath, int? MaxTurns = null, List? SessionSkills = null, string? VerifyCommand = null);
-public sealed record SeedResultDto(int Copied, int Skipped);
-
-public sealed record WorktreeOverviewDto(
- string TaskId,
- string TaskTitle,
- ClaudeDo.Data.Models.TaskStatus TaskStatus,
- string ListId,
- string ListName,
- string Path,
- string BranchName,
- string BaseCommit,
- WorktreeState State,
- string? DiffStat,
- DateTime CreatedAt,
- bool PathExistsOnDisk);
-
-public sealed record LaunchSpec(
- string Cwd,
- string Exe,
- IReadOnlyList Args,
- IReadOnlyDictionary Env);
-
-public sealed record ForceRemoveResultDto(bool Removed, string? Reason);
-public sealed record PlanningMergeConflictStateDto(string PlanningTaskId, string SubtaskId);
-public sealed record PendingQuestionDto(string TaskId, string QuestionId, string Question);
-public sealed record WorkerBuildInfoDto(string? BuildSha);
-
-public sealed record OnlineInboxStateDto(
- bool Enabled,
- string ApiBaseUrl,
- string Authority,
- string ClientId,
- string Scopes,
- string RedirectUri,
- bool SignedIn,
- int PollIntervalSeconds);
-
-public sealed record OnlineInboxConfigInputDto(
- bool Enabled,
- string ApiBaseUrl,
- int PollIntervalSeconds,
- string Authority,
- string ClientId,
- string Scopes,
- string RedirectUri);
-
-public sealed record UsageLimitDto(
- string Kind,
- string Group,
- double Percent,
- string Severity,
- DateTimeOffset? ResetsAt,
- string? ScopeModelDisplayName,
- bool IsActive);
-
-public sealed record UsageSnapshotDto(
- double? FiveHourPercent,
- DateTimeOffset? FiveHourResetsAt,
- double? SevenDayPercent,
- DateTimeOffset? SevenDayResetsAt,
- IReadOnlyList Limits,
- int FiveHourThresholdPct,
- int SevenDayThresholdPct,
- bool IsGateBlocked,
- string? GateReason,
- DateTime? FetchedAtUtc,
- bool IsStale,
- string? LastError,
- int ConfiguredSlots,
- int EffectiveSlots,
- string? ThrottleBucket,
- // Throttle stages per bucket, drawn (and dragged) on the usage-monitor gauges. Defaults match
- // the DB defaults so an older worker that doesn't send them yet still yields sane markers.
- int ThrottleFiveHourSoftPct = 50,
- int ThrottleFiveHourHardPct = 65,
- int ThrottleSevenDaySoftPct = 50,
- int ThrottleSevenDayHardPct = 65);
-
-public sealed record ModelUsageRowDto(
- DateOnly Date,
- string Model,
- string Scope,
- long InputTokens,
- long OutputTokens,
- long CacheReadTokens,
- long CacheCreationTokens,
- int Messages,
- double? CostUsd = null);
-
-public sealed record TaskUsageRowDto(
- string TaskId,
- string TaskTitle,
- string ListId,
- string ListName,
- string? Model,
- int Runs,
- long TokensIn,
- long TokensOut,
- double? CostUsd = null,
- int? Retries = null,
- bool? Productive = null,
- bool? OneShot = null);
-
-public sealed record TokenTrackerDashboardDto(bool Ok, string? Url, string? Error);
-
-public sealed record TokenTrackerStatusDto(
- bool Installed,
- string? Version,
- bool NodeOk,
- string? NodeVersion,
- DateTime? LastFetchedUtc,
- string? LastError,
- int? FormatVersion,
- int SessionCount);
diff --git a/src/ClaudeDo.Worker/CLAUDE.md b/src/ClaudeDo.Worker/CLAUDE.md
index 5b5036e2..2fa0ef39 100644
--- a/src/ClaudeDo.Worker/CLAUDE.md
+++ b/src/ClaudeDo.Worker/CLAUDE.md
@@ -177,7 +177,10 @@ Claude then calls `get_daily_prep_candidates`, picks an effort-aware subset capp
## SignalR Hub
-`WorkerHub` is the canonical method list — grep it rather than trusting a doc inventory.
+`WorkerHub` is the canonical method list — grep it rather than trusting a doc inventory. Its wire
+DTOs live in `ClaudeDo.Data/Wire.cs` (shared with `IWorkerClient` via a `` item), **not** in
+`WorkerHub.cs` — don't re-declare one locally, that's how `MergePreviewDto` lost its verify fields
+on the client side.
Groups: execution · review/merge · conflict resolver · planning sessions · interactive ConPTY
launch specs · worktrees · agents/settings/lists · reports/notes/prep · diagnostics · usage.
`IWorkerClient` in `ClaudeDo.Ui` mirrors it.
diff --git a/src/ClaudeDo.Worker/ClaudeDo.Worker.csproj b/src/ClaudeDo.Worker/ClaudeDo.Worker.csproj
index 9c7497d6..c95498cf 100644
--- a/src/ClaudeDo.Worker/ClaudeDo.Worker.csproj
+++ b/src/ClaudeDo.Worker/ClaudeDo.Worker.csproj
@@ -27,6 +27,10 @@
ClaudeTaskWorker.ico
+
+
+
+
diff --git a/src/ClaudeDo.Worker/Hub/WorkerHub.cs b/src/ClaudeDo.Worker/Hub/WorkerHub.cs
index c1bb25ba..d10bc8cb 100644
--- a/src/ClaudeDo.Worker/Hub/WorkerHub.cs
+++ b/src/ClaudeDo.Worker/Hub/WorkerHub.cs
@@ -28,174 +28,6 @@ using Microsoft.EntityFrameworkCore;
namespace ClaudeDo.Worker.Hub;
-public record ActiveTaskDto(string Slot, string TaskId, DateTime StartedAt);
-
-/// Git SHA the running worker was built from. Null when the build wasn't stamped
-/// (e.g. a local dev build without the SourceRevisionId target).
-public record WorkerBuildInfoDto(string? BuildSha);
-
-public record AppSettingsDto(
- string DefaultClaudeInstructions,
- string DefaultModel,
- int DefaultMaxTurns,
- string DefaultPermissionMode,
- int MaxParallelExecutions,
- string WorktreeStrategy,
- string? CentralWorktreeRoot,
- bool WorktreeAutoCleanupEnabled,
- int WorktreeAutoCleanupDays,
- string? ReportExcludedPaths,
- int StandupWeekday,
- int DailyPrepMaxTasks,
- List? SessionSkills = null,
- List? ModelPresets = null,
- int UsageGateFiveHourPct = 80,
- int UsageGateSevenDayPct = 90,
- int MaxTurnsCeiling = 80,
- // Throttle stages per bucket — dragged on the usage-monitor gauges, not typed in Settings.
- int UsageThrottleFiveHourSoftPct = 50,
- int UsageThrottleFiveHourHardPct = 65,
- int UsageThrottleSevenDaySoftPct = 50,
- int UsageThrottleSevenDayHardPct = 65,
- bool AutoContinueOnUsageLimit = false);
-
-// Per-model run defaults (effort + turn budget) edited in Settings -> General.
-public record ModelPresetDto(string Model, string Effort, int MaxTurns);
-
-public record SessionSkillDto(
- string Name,
- string Description,
- string SourceUrl,
- string PinnedRef,
- DateTimeOffset AddedAt);
-
-public record WorktreeCleanupDto(int Removed);
-public record WorktreeResetDto(int Removed, int TasksAffected, bool Blocked, int RunningTasks);
-
-public record WorktreeOverviewDto(
- string TaskId,
- string TaskTitle,
- ClaudeDo.Data.Models.TaskStatus TaskStatus,
- string ListId,
- string ListName,
- string Path,
- string BranchName,
- string BaseCommit,
- WorktreeState State,
- string? DiffStat,
- DateTime CreatedAt,
- bool PathExistsOnDisk);
-
-public record ForceRemoveResultDto(bool Removed, string? Reason);
-public record PlanningMergeConflictStateDto(string PlanningTaskId, string SubtaskId);
-public record PendingQuestionDto(string TaskId, string QuestionId, string Question);
-public record MergeResultDto(string Status, IReadOnlyList ConflictFiles, string? ErrorMessage);
-public record BaseDirtyWarningDto(int ModifiedCount, int UntrackedCount);
-public record SetTaskStatusResultDto(BaseDirtyWarningDto? BaseDirty);
-// Verify* fields are always null on this path today -- the UI's live mergeability indicator never
-// requests a verify run (that would mean an unrequested build on every preview poll); they exist so
-// MergePreviewDto matches TaskMergeService.MergePreviewResult should a caller opt in later.
-public record MergePreviewDto(
- string Status, IReadOnlyList ConflictFiles, int ChangedFileCount,
- int? VerifyExitCode = null, long? VerifyDurationMs = null, string? VerifyOutputTail = null);
-public record MergeTargetsDto(
- string DefaultBranch, IReadOnlyList LocalBranches, string DefaultCommitMessage);
-public record MergeConflictDocumentsDto(string TaskId, IReadOnlyList Files);
-public record ConflictDocumentDto(string Path, bool IsBinary, IReadOnlyList Segments);
-public record MergeSegmentDto(bool IsConflict, string Text, string Ours, string? Base, string Theirs);
-public record UpdateListDto(string Id, string Name, string? WorkingDir, string DefaultCommitType, bool IsManual = false, bool FindingsTracked = false);
-public record UpdateListConfigDto(string ListId, string? Model, string? SystemPrompt, string? AgentPath, int? MaxTurns = null, List? SessionSkills = null, string? VerifyCommand = null);
-public record UpdateTaskAgentSettingsDto(string TaskId, string? Model, string? SystemPrompt, string? AgentPath, int? MaxTurns = null, List? SessionSkills = null);
-public record ListConfigDto(string? Model, string? SystemPrompt, string? AgentPath, int? MaxTurns = null, List? SessionSkills = null, string? VerifyCommand = null);
-public record SeedResultDto(int Copied, int Skipped);
-
-public record OnlineInboxStateDto(
- bool Enabled,
- string ApiBaseUrl,
- string Authority,
- string ClientId,
- string Scopes,
- string RedirectUri,
- bool SignedIn,
- int PollIntervalSeconds);
-
-public record OnlineInboxConfigInput(
- bool Enabled,
- string ApiBaseUrl,
- int PollIntervalSeconds,
- string Authority,
- string ClientId,
- string Scopes,
- string RedirectUri);
-
-public record UsageLimitDto(
- string Kind,
- string Group,
- double Percent,
- string Severity,
- DateTimeOffset? ResetsAt,
- string? ScopeModelDisplayName,
- bool IsActive);
-
-public record UsageSnapshotDto(
- double? FiveHourPercent,
- DateTimeOffset? FiveHourResetsAt,
- double? SevenDayPercent,
- DateTimeOffset? SevenDayResetsAt,
- IReadOnlyList Limits,
- int FiveHourThresholdPct,
- int SevenDayThresholdPct,
- bool IsGateBlocked,
- string? GateReason,
- DateTime? FetchedAtUtc,
- bool IsStale,
- string? LastError,
- int ConfiguredSlots,
- int EffectiveSlots,
- string? ThrottleBucket,
- // Throttle stages per bucket, so the usage monitor can draw (and drag) them on each gauge.
- int ThrottleFiveHourSoftPct,
- int ThrottleFiveHourHardPct,
- int ThrottleSevenDaySoftPct,
- int ThrottleSevenDayHardPct);
-
-public record ModelUsageRowDto(
- DateOnly Date,
- string Model,
- string Scope,
- long InputTokens,
- long OutputTokens,
- long CacheReadTokens,
- long CacheCreationTokens,
- int Messages,
- double? CostUsd = null);
-
-public record TaskUsageRowDto(
- string TaskId,
- string TaskTitle,
- string ListId,
- string ListName,
- string? Model,
- int Runs,
- long TokensIn,
- long TokensOut,
- double? CostUsd = null,
- int? Retries = null,
- bool? Productive = null,
- bool? OneShot = null);
-
-public record TokenTrackerDashboardDto(bool Ok, string? Url, string? Error);
-
-public record TokenTrackerStatusDto(
- bool Installed,
- string? Version,
- bool NodeOk,
- string? NodeVersion,
- DateTime? LastFetchedUtc,
- string? LastError,
- int? FormatVersion,
- int SessionCount);
-
public sealed class WorkerHub : Microsoft.AspNetCore.SignalR.Hub
{
private static readonly string Version =
@@ -240,7 +72,7 @@ public sealed class WorkerHub : Microsoft.AspNetCore.SignalR.Hub
private readonly Runner.PendingQuestionRegistry _pendingQuestions;
private readonly LogRingBuffer? _logBuffer;
private readonly ISessionSkillRegistry _skillRegistry;
- private readonly IInteractiveLaunchSpecService? _interactiveLaunchSpec;
+ private readonly InteractiveLaunchSpecService? _interactiveLaunchSpec;
private readonly WorktreeManager? _worktreeManager;
private readonly Data.Git.GitService? _git;
private readonly UsageSnapshotBuilder? _usageSnapshotBuilder;
@@ -275,7 +107,7 @@ public sealed class WorkerHub : Microsoft.AspNetCore.SignalR.Hub
Runner.PendingQuestionRegistry pendingQuestions,
ISessionSkillRegistry skillRegistry,
LogRingBuffer? logBuffer = null,
- IInteractiveLaunchSpecService? interactiveLaunchSpec = null,
+ InteractiveLaunchSpecService? interactiveLaunchSpec = null,
WorktreeManager? worktreeManager = null,
Data.Git.GitService? git = null,
UsageSnapshotBuilder? usageSnapshotBuilder = null,
@@ -1162,7 +994,7 @@ public sealed class WorkerHub : Microsoft.AspNetCore.SignalR.Hub
_onlineInboxConfig.PollIntervalSeconds);
}
- public void SetOnlineInboxConfig(OnlineInboxConfigInput input)
+ public void SetOnlineInboxConfig(OnlineInboxConfigInputDto input)
{
_onlineInboxConfig.Enabled = input.Enabled;
_onlineInboxConfig.ApiBaseUrl = input.ApiBaseUrl ?? "";
diff --git a/tests/ClaudeDo.Ui.Tests/ClaudeDo.Ui.Tests.csproj b/tests/ClaudeDo.Ui.Tests/ClaudeDo.Ui.Tests.csproj
index 3cfb7ab3..bfe36982 100644
--- a/tests/ClaudeDo.Ui.Tests/ClaudeDo.Ui.Tests.csproj
+++ b/tests/ClaudeDo.Ui.Tests/ClaudeDo.Ui.Tests.csproj
@@ -8,6 +8,7 @@
+
diff --git a/tests/ClaudeDo.Ui.Tests/StubWorkerClient.cs b/tests/ClaudeDo.Ui.Tests/StubWorkerClient.cs
index e13c30ea..69ff19a1 100644
--- a/tests/ClaudeDo.Ui.Tests/StubWorkerClient.cs
+++ b/tests/ClaudeDo.Ui.Tests/StubWorkerClient.cs
@@ -45,7 +45,7 @@ public abstract class StubWorkerClient : IWorkerClient
public int ClearMyDayCalls { get; private set; }
public int RunDailyPrepNowCalls { get; private set; }
- public virtual IReadOnlyList GetActiveTasks() => System.Array.Empty();
+ public virtual IReadOnlyList GetActiveTasks() => System.Array.Empty();
public void RaiseTaskStarted(string slot, string taskId, DateTime startedAt) => TaskStartedEvent?.Invoke(slot, taskId, startedAt);
public void RaiseTaskFinished(string slot, string taskId, string status, DateTime finishedAt) => TaskFinishedEvent?.Invoke(slot, taskId, status, finishedAt);
diff --git a/tests/ClaudeDo.Ui.Tests/ViewModels/MissionControlViewModelTests.cs b/tests/ClaudeDo.Ui.Tests/ViewModels/MissionControlViewModelTests.cs
index 70435e0b..6eb06163 100644
--- a/tests/ClaudeDo.Ui.Tests/ViewModels/MissionControlViewModelTests.cs
+++ b/tests/ClaudeDo.Ui.Tests/ViewModels/MissionControlViewModelTests.cs
@@ -94,8 +94,8 @@ public class MissionControlViewModelTests : IDisposable
private sealed class SeededFakeWorker : StubWorkerClient
{
- public override IReadOnlyList GetActiveTasks()
- => new[] { new ActiveTask("slot-1", "seed1", DateTime.UtcNow) };
+ public override IReadOnlyList GetActiveTasks()
+ => new[] { new ActiveTaskDto("slot-1", "seed1", DateTime.UtcNow) };
}
// ── acceptance criterion (b): RefreshQueueAsync returns Running rows first ─
diff --git a/tests/ClaudeDo.Worker.Tests/ClaudeDo.Worker.Tests.csproj b/tests/ClaudeDo.Worker.Tests/ClaudeDo.Worker.Tests.csproj
index bfa4f500..c7d07e4d 100644
--- a/tests/ClaudeDo.Worker.Tests/ClaudeDo.Worker.Tests.csproj
+++ b/tests/ClaudeDo.Worker.Tests/ClaudeDo.Worker.Tests.csproj
@@ -19,6 +19,7 @@
+
diff --git a/tests/ClaudeDo.Worker.Tests/UiVm/TasksIslandViewModelPlanningTests.cs b/tests/ClaudeDo.Worker.Tests/UiVm/TasksIslandViewModelPlanningTests.cs
index 025b4f99..6bd31701 100644
--- a/tests/ClaudeDo.Worker.Tests/UiVm/TasksIslandViewModelPlanningTests.cs
+++ b/tests/ClaudeDo.Worker.Tests/UiVm/TasksIslandViewModelPlanningTests.cs
@@ -163,7 +163,7 @@ sealed class FakeWorkerClient : IWorkerClient
public Task SetOnlineInboxConfigAsync(OnlineInboxConfigInputDto input) => Task.CompletedTask;
public Task SetOnlineInboxAuthAsync(string refreshToken) => Task.CompletedTask;
public Task ClearOnlineInboxAuthAsync() => Task.CompletedTask;
- public IReadOnlyList GetActiveTasks() => System.Array.Empty();
+ public IReadOnlyList GetActiveTasks() => System.Array.Empty();
public Task GetUsageSnapshotAsync() => Task.FromResult(null);
public Task RefreshUsageAsync() => Task.FromResult(null);