feat(settings): claude_bin im Dateien-Tab setzbar, mit Auflösungs-Check

Bisher nur per Hand in worker.config.json. Neuer Abschnitt CLAUDE CLI mit
eigenem Save-Button (die Datei gehoert dem Worker, nicht app_settings) und einer
Zeile, wohin der Wert per ExecutableResolver tatsaechlich aufloest — 'nicht im
PATH gefunden' war bisher erst am fehlgeschlagenen Run zu sehen. WorkerConfig ist
DI-Singleton und ClaudeProcess loest pro Spawn auf, also greift die Aenderung ab
dem naechsten Run ohne Neustart.

SaveOnlineInbox/SaveClaudeBin teilen jetzt ein SaveKey(), damit beide dieselbe
read-modify-write-Semantik haben (alle anderen Keys bleiben unberuehrt).
Die zugehoerigen Locale-Keys sind im vorigen Commit mitgelaufen.
This commit is contained in:
mika kuns
2026-08-27 10:39:25 +02:00
parent 31d53b0033
commit 13b81cac68
9 changed files with 124 additions and 1 deletions
@@ -0,0 +1,46 @@
using ClaudeDo.Worker.Config;
using Xunit;
namespace ClaudeDo.Worker.Tests.Config;
/// SaveClaudeBin/SaveOnlineInbox are read-modify-write on a file the user also hand-edits, so the
/// one thing that must hold is: every other key survives.
public sealed class WorkerConfigSaveTests : IDisposable
{
private readonly string _path = Path.Combine(
Path.GetTempPath(), $"cd_workercfg_{Guid.NewGuid():N}.json");
public void Dispose() { try { File.Delete(_path); } catch { } }
[Fact]
public void SaveClaudeBin_keeps_every_other_key()
{
File.WriteAllText(_path, """
{
"claude_bin": "claude",
"signalr_port": 12345,
"external_mcp_api_key": "secret",
"online_inbox": { "enabled": true }
}
""");
var cfg = WorkerConfig.Load(_path);
cfg.ClaudeBin = @"C:\tools\claude.cmd";
cfg.SaveClaudeBin(_path);
var reloaded = WorkerConfig.Load(_path);
Assert.Equal(@"C:\tools\claude.cmd", reloaded.ClaudeBin);
Assert.Equal(12345, reloaded.SignalRPort);
Assert.Equal("secret", reloaded.ExternalMcpApiKey);
Assert.True(reloaded.OnlineInbox.Enabled);
}
[Fact]
public void SaveClaudeBin_creates_the_file_when_missing()
{
var cfg = new WorkerConfig { ClaudeBin = "claude-next" };
cfg.SaveClaudeBin(_path);
Assert.Equal("claude-next", WorkerConfig.Load(_path).ClaudeBin);
}
}
@@ -159,6 +159,8 @@ sealed class FakeWorkerClient : IWorkerClient
public Task<string> GetLastPrepLogAsync() => Task.FromResult(string.Empty);
public Task<WorkerBuildInfoDto?> GetWorkerBuildInfoAsync() => Task.FromResult<WorkerBuildInfoDto?>(null);
public Task RefineTaskAsync(string taskId) => Task.CompletedTask;
public Task<ClaudeBinDto?> GetClaudeBinAsync() => Task.FromResult<ClaudeBinDto?>(null);
public Task<ClaudeBinDto?> SetClaudeBinAsync(string? value) => Task.FromResult<ClaudeBinDto?>(null);
public Task<OnlineInboxStateDto?> GetOnlineInboxStateAsync() => Task.FromResult<OnlineInboxStateDto?>(null);
public Task SetOnlineInboxConfigAsync(OnlineInboxConfigInputDto input) => Task.CompletedTask;
public Task SetOnlineInboxAuthAsync(string refreshToken) => Task.CompletedTask;