fix(worker): keep interactive & planning prompts intact past Windows Terminal
wt.exe treats ';' as a command/tab delimiter in every argument, with no escape that survives quoting (microsoft/terminal#13264), so a task description containing ';' spawned extra terminals on "Run interactively" and planning start. Route the launch as wt -> powershell -> claude and pass the free-text prompt via $env:CLAUDEDO_LAUNCH_PROMPT so it never reaches the wt command line; PowerShell binds the variable as a single argument (embedded quotes escaped for PS 5.1). Also clarify the launcher, which serves interactive runs too (not just planning): IPlanningTerminalLauncher -> ITerminalLauncher, WindowsTerminalPlanningLauncher -> WindowsTerminalLauncher, LaunchStart/Resume -> LaunchPlanning{Start,Resume}Async.
This commit is contained in:
@@ -0,0 +1,48 @@
|
||||
using ClaudeDo.Worker.Planning;
|
||||
|
||||
namespace ClaudeDo.Worker.Tests.Planning;
|
||||
|
||||
public sealed class WindowsTerminalLauncherTests
|
||||
{
|
||||
private static PlanningSessionStartContext MakeStartCtx(string? wd = null)
|
||||
{
|
||||
var workingDir = wd ?? Path.GetTempPath();
|
||||
var dir = Path.Combine(Path.GetTempPath(), Guid.NewGuid().ToString());
|
||||
Directory.CreateDirectory(dir);
|
||||
return new PlanningSessionStartContext(
|
||||
ParentTaskId: "task-1",
|
||||
WorkingDir: workingDir,
|
||||
Token: "test-token",
|
||||
WorktreePath: workingDir,
|
||||
BranchName: "claudedo/planning/task1",
|
||||
Files: new PlanningSessionFiles(
|
||||
SessionDirectory: dir,
|
||||
SystemPromptPath: Path.Combine(dir, "system-prompt.md"),
|
||||
InitialPromptPath: Path.Combine(dir, "initial-prompt.txt")));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task LaunchPlanningStartAsync_WorkingDirMissing_Throws()
|
||||
{
|
||||
var ctx = MakeStartCtx(wd: Path.Combine(Path.GetTempPath(), "nonexistent_" + Guid.NewGuid()));
|
||||
var sut = new WindowsTerminalLauncher(wtPath: "wt", claudePath: "claude");
|
||||
var ex = await Assert.ThrowsAsync<TerminalLaunchException>(() =>
|
||||
sut.LaunchPlanningStartAsync(ctx, CancellationToken.None));
|
||||
Assert.Contains("Working directory", ex.Message);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task LaunchPlanningStartAsync_WtMissing_Throws()
|
||||
{
|
||||
var ctx = MakeStartCtx();
|
||||
File.WriteAllText(ctx.Files.SystemPromptPath, "sp");
|
||||
File.WriteAllText(ctx.Files.InitialPromptPath, "ip");
|
||||
|
||||
var sut = new WindowsTerminalLauncher(
|
||||
wtPath: "C:/no/such/wt.exe",
|
||||
claudePath: "claude");
|
||||
var ex = await Assert.ThrowsAsync<TerminalLaunchException>(() =>
|
||||
sut.LaunchPlanningStartAsync(ctx, CancellationToken.None));
|
||||
Assert.Contains("Windows Terminal", ex.Message);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user