diff --git a/src/ClaudeDo.Worker/Planning/WindowsTerminalPlanningLauncher.cs b/src/ClaudeDo.Worker/Planning/WindowsTerminalPlanningLauncher.cs index a38948c..cb03995 100644 --- a/src/ClaudeDo.Worker/Planning/WindowsTerminalPlanningLauncher.cs +++ b/src/ClaudeDo.Worker/Planning/WindowsTerminalPlanningLauncher.cs @@ -3,6 +3,8 @@ // Allowed-tools: --allowedTools (camelCase), comma-separated tokens // System prompt: --append-system-prompt-file (file form) // Session ID: no pre-assign flag; resume with --resume +// Launch model: wt.exe directly spawns claude.exe via argv (UseShellExecute=false). +// No cmd /k shim — arbitrary initial-prompt content would be re-parsed by cmd.exe otherwise. using System.Diagnostics; @@ -38,29 +40,31 @@ public sealed class WindowsTerminalPlanningLauncher : IPlanningTerminalLauncher if (resolvedClaude is null) throw new PlanningLaunchException($"claude executable not found: {_claudePath}"); - var initialPrompt = File.Exists(ctx.Files.InitialPromptPath) - ? File.ReadAllText(ctx.Files.InitialPromptPath) - : string.Empty; - - // Build cmd line: set MAX_THINKING_TOKENS=20000 && claude "prompt" - // UseShellExecute=true means we cannot set psi.Environment, so we inject via cmd /k. - var claudeArgs = BuildStartArgs(ctx, initialPrompt, resolvedClaude); - var cmdLine = $"set MAX_THINKING_TOKENS=20000 && {claudeArgs}"; - var psi = new ProcessStartInfo { FileName = resolvedWt, - UseShellExecute = true, - WorkingDirectory = ctx.WorkingDir, + UseShellExecute = false, + CreateNoWindow = false, }; - // wt.exe -d cmd /k "" + + psi.Environment["MAX_THINKING_TOKENS"] = "20000"; + psi.ArgumentList.Add("-d"); psi.ArgumentList.Add(ctx.WorkingDir); - psi.ArgumentList.Add("cmd"); - psi.ArgumentList.Add("/k"); - psi.ArgumentList.Add(cmdLine); + psi.ArgumentList.Add(resolvedClaude); + psi.ArgumentList.Add("--model"); + psi.ArgumentList.Add(Model); + psi.ArgumentList.Add("--mcp-config"); + psi.ArgumentList.Add(ctx.Files.McpConfigPath); + psi.ArgumentList.Add("--append-system-prompt-file"); + psi.ArgumentList.Add(ctx.Files.SystemPromptPath); + psi.ArgumentList.Add("--allowedTools"); + psi.ArgumentList.Add(AllowedTools); + psi.ArgumentList.Add(File.ReadAllText(ctx.Files.InitialPromptPath)); + + var proc = Process.Start(psi) + ?? throw new PlanningLaunchException("Failed to start Windows Terminal process."); - Process.Start(psi); return Task.CompletedTask; } @@ -69,9 +73,6 @@ public sealed class WindowsTerminalPlanningLauncher : IPlanningTerminalLauncher if (!Directory.Exists(ctx.WorkingDir)) throw new PlanningLaunchException($"Working directory does not exist: {ctx.WorkingDir}"); - if (!File.Exists(ctx.McpConfigPath)) - throw new PlanningLaunchException($"MCP config file not found: {ctx.McpConfigPath}"); - var resolvedWt = Resolve(_wtPath); if (resolvedWt is null) throw new PlanningLaunchException($"Windows Terminal not found: {_wtPath}"); @@ -83,32 +84,24 @@ public sealed class WindowsTerminalPlanningLauncher : IPlanningTerminalLauncher var psi = new ProcessStartInfo { FileName = resolvedWt, - UseShellExecute = true, - WorkingDirectory = ctx.WorkingDir, + UseShellExecute = false, + CreateNoWindow = false, }; + psi.ArgumentList.Add("-d"); psi.ArgumentList.Add(ctx.WorkingDir); - psi.ArgumentList.Add("cmd"); - psi.ArgumentList.Add("/k"); - psi.ArgumentList.Add(BuildResumeArgs(ctx, resolvedClaude)); + psi.ArgumentList.Add(resolvedClaude); + psi.ArgumentList.Add("--resume"); + psi.ArgumentList.Add(ctx.ClaudeSessionId); + psi.ArgumentList.Add("--mcp-config"); + psi.ArgumentList.Add(ctx.McpConfigPath); + + var proc = Process.Start(psi) + ?? throw new PlanningLaunchException("Failed to start Windows Terminal process."); - Process.Start(psi); return Task.CompletedTask; } - private static string BuildStartArgs(PlanningSessionStartContext ctx, string initialPrompt, string claudePath) - { - // Build as a flat string for cmd /k; quote paths that may contain spaces. - return $"\"{claudePath}\" --mcp-config \"{ctx.Files.McpConfigPath}\" --append-system-prompt-file \"{ctx.Files.SystemPromptPath}\" --allowedTools \"{AllowedTools}\" --model {Model} \"{EscapeArg(initialPrompt)}\""; - } - - private static string BuildResumeArgs(PlanningSessionResumeContext ctx, string claudePath) - { - return $"\"{claudePath}\" --resume {ctx.ClaudeSessionId} --mcp-config \"{ctx.McpConfigPath}\""; - } - - private static string EscapeArg(string value) => value.Replace("\"", "\\\""); - private static string? Resolve(string pathOrName) { if (File.Exists(pathOrName))