Compare commits
6
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
40d2537486 | ||
|
|
f70503d038 | ||
|
|
9360cd2fe6 | ||
|
|
ee502883f8 | ||
|
|
e64d570554 | ||
|
|
fc25ee45d1 |
@@ -1,5 +1,39 @@
|
|||||||
# Changelog
|
# 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
|
## v2.13.1 — 2026-09-02
|
||||||
|
|
||||||
### Fixes
|
### Fixes
|
||||||
|
|||||||
@@ -78,10 +78,14 @@ builder.Services.AddHostedService<AttachmentOrphanRecovery>();
|
|||||||
builder.Services.AddHostedService<PromptFileRecovery>();
|
builder.Services.AddHostedService<PromptFileRecovery>();
|
||||||
builder.Services.AddHostedService<LegacyWorktreeFolderRecovery>();
|
builder.Services.AddHostedService<LegacyWorktreeFolderRecovery>();
|
||||||
builder.Services.AddHostedService<UntrackedWorktreeRecovery>();
|
builder.Services.AddHostedService<UntrackedWorktreeRecovery>();
|
||||||
builder.Services.AddSignalR().AddJsonProtocol(options =>
|
// 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());
|
options.PayloadSerializerOptions.Converters.Add(new System.Text.Json.Serialization.JsonStringEnumConverter());
|
||||||
});
|
});
|
||||||
|
|
||||||
// Runner stack.
|
// Runner stack.
|
||||||
builder.Services.AddSingleton<IClaudeProcess, ClaudeProcess>();
|
builder.Services.AddSingleton<IClaudeProcess, ClaudeProcess>();
|
||||||
|
|||||||
@@ -75,7 +75,8 @@ public sealed class InteractiveLaunchSpecService
|
|||||||
/// Throws KeyNotFoundException if the task doesn't exist, InvalidOperationException
|
/// 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
|
/// 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
|
/// 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
|
/// (--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
|
/// 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-
|
/// 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;
|
var isFreshWorktree = false;
|
||||||
string sessionDir;
|
string sessionDir;
|
||||||
bool sessionIsWorktree;
|
bool sessionIsWorktree;
|
||||||
|
var seedSkills = true;
|
||||||
|
|
||||||
if (worktree is null || worktree.State is not (WorktreeState.Active or WorktreeState.Kept))
|
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
|
// 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.
|
// straight to the list's working dir), so no WorktreeEntity is created here either.
|
||||||
var list = await new ListRepository(ctx).GetByIdAsync(task.ListId, ct);
|
var list = await new ListRepository(ctx).GetByIdAsync(task.ListId, ct);
|
||||||
if (list?.WorkingDir is null)
|
if (list is null || string.IsNullOrEmpty(list.WorkingDir))
|
||||||
throw new InvalidOperationException("This task has no working directory configured -- can't create a worktree.");
|
{
|
||||||
|
// A manual/reminder list has no repo at all -- open the session in the user's
|
||||||
if (await _git.IsGitRepoAsync(list.WorkingDir, ct))
|
// 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
|
// 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
|
// 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.
|
// history the task accumulated before its previous worktree went away.
|
||||||
var run = isFreshWorktree ? null : await new TaskRunRepository(ctx).GetLatestByTaskIdAsync(taskId, ct);
|
var run = isFreshWorktree ? null : await new TaskRunRepository(ctx).GetLatestByTaskIdAsync(taskId, ct);
|
||||||
|
|
||||||
|
if (seedSkills)
|
||||||
|
{
|
||||||
var requestedSkills = TaskRunner.UnionSkillNames(task.SessionSkills, listConfig?.SessionSkills, globalSettings.SessionSkills);
|
var requestedSkills = TaskRunner.UnionSkillNames(task.SessionSkills, listConfig?.SessionSkills, globalSettings.SessionSkills);
|
||||||
var skillNames = await FilterToInstalledSkillsAsync(requestedSkills, ct);
|
var skillNames = await FilterToInstalledSkillsAsync(requestedSkills, ct);
|
||||||
await _skillSeeder.SeedAsync(sessionDir, skillNames, isWorktree: sessionIsWorktree, ct);
|
await _skillSeeder.SeedAsync(sessionDir, skillNames, isWorktree: sessionIsWorktree, ct);
|
||||||
|
}
|
||||||
|
|
||||||
var resolvedClaude = WindowsTerminalLauncher.Resolve(_claudePath)
|
var resolvedClaude = WindowsTerminalLauncher.Resolve(_claudePath)
|
||||||
?? throw new InvalidOperationException($"claude executable not found: {_claudePath}");
|
?? throw new InvalidOperationException($"claude executable not found: {_claudePath}");
|
||||||
|
|||||||
@@ -220,8 +220,15 @@ public sealed class InteractiveLaunchSpecServiceTests : IDisposable
|
|||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
[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();
|
var listId = await SeedListAsync();
|
||||||
using (var ctx = _db.CreateContext())
|
using (var ctx = _db.CreateContext())
|
||||||
{
|
{
|
||||||
@@ -230,11 +237,18 @@ public sealed class InteractiveLaunchSpecServiceTests : IDisposable
|
|||||||
await ctx.SaveChangesAsync();
|
await ctx.SaveChangesAsync();
|
||||||
}
|
}
|
||||||
var taskId = Guid.NewGuid().ToString();
|
var taskId = Guid.NewGuid().ToString();
|
||||||
await SeedTaskAsync(taskId, listId, TaskStatus.Idle);
|
await SeedTaskAsync(taskId, listId, TaskStatus.Idle,
|
||||||
|
sessionSkillsJson: "[\"installed-skill\"]");
|
||||||
|
|
||||||
var svc = BuildService();
|
var svc = BuildService();
|
||||||
await Assert.ThrowsAsync<InvalidOperationException>(
|
var spec = await svc.BuildForTaskAsync(taskId, CancellationToken.None);
|
||||||
() => 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]
|
[Fact]
|
||||||
|
|||||||
Reference in New Issue
Block a user