Compare commits

...
6 Commits
Author SHA1 Message Date
mika kuns 40d2537486 Merge remote-tracking branch 'origin/main'
Changelog / changelog (push) Successful in 2s
Release / release (push) Successful in 42s
2026-09-03 10:17:58 +02:00
mika kuns f70503d038 fix(worker): interaktive Session ohne Arbeitsverzeichnis im User-Verzeichnis starten
Eine manuelle Liste (Erinnerungen) hat kein WorkingDir. BuildForTaskAsync
warf dafuer bisher "no working directory configured", die interaktive
Session liess sich also nicht oeffnen. Jetzt faellt der Pfad auf das
User-Verzeichnis zurueck; Session-Skills werden dort bewusst nicht
geseedet, weil das Ziel das echte ~/.claude/skills waere.
2026-09-03 10:17:06 +02:00
ClaudeDo CI 9360cd2fe6 docs(changelog): update for v2.13.3 2026-09-03 05:48:10 +00:00
mika kuns ee502883f8 chore(claudedo): update CHANGELOG for v2.13.3
Changelog / changelog (push) Successful in 2s
Release / release (push) Successful in 40s
2026-09-03 07:47:43 +02:00
mika kuns e64d570554 fix(worker): raise SignalR MaximumParallelInvocationsPerClient
Default of 1 serializes every hub call on a connection - a slow one
(GetTicketProjects hitting an unreachable ticket system, 10s timeout)
blocked unrelated calls like UpdateList/UpdateListConfig behind it for
the same duration, making list-settings save appear to hang.
2026-09-03 07:47:26 +02:00
ClaudeDo CI fc25ee45d1 docs(changelog): update for v2.13.2 2026-09-03 05:23:15 +00:00
4 changed files with 80 additions and 16 deletions
+34
View File
@@ -1,5 +1,39 @@
# Changelog
## v2.13.3 — 2026-09-03
### Fixes
- raise SignalR MaximumParallelInvocationsPerClient (e64d5705)
### Documentation
- update for v2.13.2 (fc25ee45)
## v2.13.2 — 2026-09-03
### Features
- Handler-Run per Knopf abbrechen (bd5aa265)
### Fixes
- strip CLAUDE_CODE_CHILD_SESSION/CLAUDECODE before every ConPTY launch (51ee5050)
- Dequeue-X auf blockierten Queue-Zeilen anzeigen und Kette konsistent halten (df3e7ba0)
- report and gate ListSettingsModal load failures instead of swallowing them (a1b20d9f)
- move ScrollViewer padding to inner content in modal bodies (e0c6b670)
- Mission Control oeffnen, wenn ein Handler-Run per MCP startet oder ein Handoff eintrifft (b05b6fba)
### Performance
- show list settings modal before its seven-round-trip load finishes (f24654df)
### Documentation
- remove stale session-skills empty-state entry (2bd48ea1)
- update for v2.13.1 (eb3aee41)
- update for v2.13.0 (2b873322)
## v2.13.1 — 2026-09-02
### Fixes
+8 -4
View File
@@ -78,10 +78,14 @@ builder.Services.AddHostedService<AttachmentOrphanRecovery>();
builder.Services.AddHostedService<PromptFileRecovery>();
builder.Services.AddHostedService<LegacyWorktreeFolderRecovery>();
builder.Services.AddHostedService<UntrackedWorktreeRecovery>();
builder.Services.AddSignalR().AddJsonProtocol(options =>
{
options.PayloadSerializerOptions.Converters.Add(new System.Text.Json.Serialization.JsonStringEnumConverter());
});
// Default MaximumParallelInvocationsPerClient=1 serializes every hub call on one connection —
// a slow one (e.g. GetTicketProjects hitting an unreachable ticket system) blocks unrelated
// calls like UpdateList/UpdateListConfig behind it for the same 10s.
builder.Services.AddSignalR(options => options.MaximumParallelInvocationsPerClient = 10)
.AddJsonProtocol(options =>
{
options.PayloadSerializerOptions.Converters.Add(new System.Text.Json.Serialization.JsonStringEnumConverter());
});
// Runner stack.
builder.Services.AddSingleton<IClaudeProcess, ClaudeProcess>();
@@ -75,7 +75,8 @@ public sealed class InteractiveLaunchSpecService
/// Throws KeyNotFoundException if the task doesn't exist, InvalidOperationException
/// if it's Running/Queued. If the task has no usable worktree yet, one is created on
/// demand (same mechanism as an autonomous run) provided the task's list has a working
/// directory pointing at a git repo -- otherwise throws InvalidOperationException. Resumes
/// directory pointing at a git repo; a non-repo directory is opened directly, and a list with
/// no working directory at all (manual/reminder list) opens in the user's home dir. Resumes
/// (--resume) this task's own last interactive session (TaskEntity.InteractiveSessionId) if
/// it has one, else the latest autonomous run's session; a task that has never run either
/// way, or whose worktree was just created fresh, gets a fresh-start spec instead -- pre-
@@ -94,6 +95,7 @@ public sealed class InteractiveLaunchSpecService
var isFreshWorktree = false;
string sessionDir;
bool sessionIsWorktree;
var seedSkills = true;
if (worktree is null || worktree.State is not (WorktreeState.Active or WorktreeState.Kept))
{
@@ -105,10 +107,17 @@ public sealed class InteractiveLaunchSpecService
// existing shape in the codebase (a list-handler task also has no worktree and commits
// straight to the list's working dir), so no WorktreeEntity is created here either.
var list = await new ListRepository(ctx).GetByIdAsync(task.ListId, ct);
if (list?.WorkingDir is null)
throw new InvalidOperationException("This task has no working directory configured -- can't create a worktree.");
if (await _git.IsGitRepoAsync(list.WorkingDir, ct))
if (list is null || string.IsNullOrEmpty(list.WorkingDir))
{
// A manual/reminder list has no repo at all -- open the session in the user's
// home directory instead of refusing it. Skills are deliberately NOT seeded
// there: the destination would be the user's REAL ~/.claude/skills, not a
// throwaway session copy.
sessionDir = Environment.GetFolderPath(Environment.SpecialFolder.UserProfile);
sessionIsWorktree = false;
seedSkills = false;
}
else if (await _git.IsGitRepoAsync(list.WorkingDir, ct))
{
// A stale (Merged/Discarded) row is already tracked in `ctx`'s identity map from
// the read above -- CreateAsync replaces it via its OWN context, so re-querying on
@@ -146,9 +155,12 @@ public sealed class InteractiveLaunchSpecService
// history the task accumulated before its previous worktree went away.
var run = isFreshWorktree ? null : await new TaskRunRepository(ctx).GetLatestByTaskIdAsync(taskId, ct);
var requestedSkills = TaskRunner.UnionSkillNames(task.SessionSkills, listConfig?.SessionSkills, globalSettings.SessionSkills);
var skillNames = await FilterToInstalledSkillsAsync(requestedSkills, ct);
await _skillSeeder.SeedAsync(sessionDir, skillNames, isWorktree: sessionIsWorktree, ct);
if (seedSkills)
{
var requestedSkills = TaskRunner.UnionSkillNames(task.SessionSkills, listConfig?.SessionSkills, globalSettings.SessionSkills);
var skillNames = await FilterToInstalledSkillsAsync(requestedSkills, ct);
await _skillSeeder.SeedAsync(sessionDir, skillNames, isWorktree: sessionIsWorktree, ct);
}
var resolvedClaude = WindowsTerminalLauncher.Resolve(_claudePath)
?? throw new InvalidOperationException($"claude executable not found: {_claudePath}");
@@ -220,8 +220,15 @@ public sealed class InteractiveLaunchSpecServiceTests : IDisposable
}
[Fact]
public async Task BuildForTaskAsync_NoWorktreeRow_NoWorkingDirConfigured_ThrowsInvalidOperation()
public async Task BuildForTaskAsync_NoWorktreeRow_NoWorkingDirConfigured_OpensInUserHomeWithoutSeedingSkills()
{
_registry.Installed.Add(new SessionSkillEntity
{
Name = "installed-skill", SourceUrl = "https://example.com/x.git",
PinnedRef = "abc", Subpath = "skills/installed-skill", Description = "d",
AddedAt = DateTimeOffset.UtcNow,
});
var listId = await SeedListAsync();
using (var ctx = _db.CreateContext())
{
@@ -230,11 +237,18 @@ public sealed class InteractiveLaunchSpecServiceTests : IDisposable
await ctx.SaveChangesAsync();
}
var taskId = Guid.NewGuid().ToString();
await SeedTaskAsync(taskId, listId, TaskStatus.Idle);
await SeedTaskAsync(taskId, listId, TaskStatus.Idle,
sessionSkillsJson: "[\"installed-skill\"]");
var svc = BuildService();
await Assert.ThrowsAsync<InvalidOperationException>(
() => svc.BuildForTaskAsync(taskId, CancellationToken.None));
var spec = await svc.BuildForTaskAsync(taskId, CancellationToken.None);
Assert.Equal(Environment.GetFolderPath(Environment.SpecialFolder.UserProfile), spec.Cwd);
// Seeding here would copy into the user's REAL ~/.claude/skills.
Assert.Empty(_seeder.Calls);
using var readCtx = _db.CreateContext();
Assert.Null(await new WorktreeRepository(readCtx).GetByTaskIdAsync(taskId));
}
[Fact]