// Claude CLI flags (verified 2026-04-23 via Context7): // Thinking budget: env var MAX_THINKING_TOKENS=20000 (no CLI flag exists) // 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; namespace ClaudeDo.Worker.Planning; public sealed class WindowsTerminalPlanningLauncher : IPlanningTerminalLauncher { private const string AllowedTools = "mcp__claudedo__*,Read,Grep,Glob,WebFetch,WebSearch,Skill"; private const string Model = "claude-opus-4-7"; private readonly string _wtPath; private readonly string _claudePath; public WindowsTerminalPlanningLauncher(string wtPath, string claudePath) { _wtPath = wtPath; _claudePath = claudePath; } public Task LaunchStartAsync(PlanningSessionStartContext ctx, CancellationToken cancellationToken) { if (!Directory.Exists(ctx.WorkingDir)) throw new PlanningLaunchException($"Working directory does not exist: {ctx.WorkingDir}"); if (!File.Exists(ctx.Files.SystemPromptPath)) throw new PlanningLaunchException($"System prompt file not found: {ctx.Files.SystemPromptPath}"); if (!File.Exists(ctx.Files.InitialPromptPath)) throw new PlanningLaunchException($"Initial prompt file not found: {ctx.Files.InitialPromptPath}"); var resolvedWt = Resolve(_wtPath); if (resolvedWt is null) throw new PlanningLaunchException($"Windows Terminal not found: {_wtPath}"); var resolvedClaude = Resolve(_claudePath); if (resolvedClaude is null) throw new PlanningLaunchException($"claude executable not found: {_claudePath}"); var psi = new ProcessStartInfo { FileName = resolvedWt, UseShellExecute = false, CreateNoWindow = false, }; psi.Environment["MAX_THINKING_TOKENS"] = "20000"; psi.Environment["CLAUDEDO_PLANNING_TOKEN"] = ctx.Token; // Arg order: --allowedTools is variadic (space-separated). The positional // prompt must follow a single-value flag, or it will be swallowed. // --append-system-prompt-file serves as that buffer. psi.ArgumentList.Add("-d"); psi.ArgumentList.Add(ctx.WorkingDir); psi.ArgumentList.Add(resolvedClaude); psi.ArgumentList.Add("--model"); psi.ArgumentList.Add(Model); psi.ArgumentList.Add("--permission-mode"); psi.ArgumentList.Add("plan"); psi.ArgumentList.Add("--allowedTools"); psi.ArgumentList.Add(AllowedTools); psi.ArgumentList.Add("--append-system-prompt-file"); psi.ArgumentList.Add(ctx.Files.SystemPromptPath); psi.ArgumentList.Add(File.ReadAllText(ctx.Files.InitialPromptPath)); var proc = Process.Start(psi) ?? throw new PlanningLaunchException("Failed to start Windows Terminal process."); return Task.CompletedTask; } public Task LaunchInteractiveAsync(InteractiveLaunchContext ctx, CancellationToken cancellationToken) { if (!Directory.Exists(ctx.WorkingDir)) throw new PlanningLaunchException($"Working directory does not exist: {ctx.WorkingDir}"); var resolvedWt = Resolve(_wtPath) ?? throw new PlanningLaunchException($"Windows Terminal not found: {_wtPath}"); var resolvedClaude = Resolve(_claudePath) ?? throw new PlanningLaunchException($"claude executable not found: {_claudePath}"); var psi = new ProcessStartInfo { FileName = resolvedWt, UseShellExecute = false, CreateNoWindow = false, }; psi.Environment["MAX_THINKING_TOKENS"] = "20000"; psi.ArgumentList.Add("-d"); psi.ArgumentList.Add(ctx.WorkingDir); psi.ArgumentList.Add(resolvedClaude); psi.ArgumentList.Add("--model"); psi.ArgumentList.Add(Model); psi.ArgumentList.Add("--permission-mode"); psi.ArgumentList.Add("auto"); psi.ArgumentList.Add(ctx.InitialPrompt); var proc = Process.Start(psi) ?? throw new PlanningLaunchException("Failed to start Windows Terminal process."); return Task.CompletedTask; } public Task LaunchResumeAsync(PlanningSessionResumeContext ctx, CancellationToken cancellationToken) { if (!Directory.Exists(ctx.WorkingDir)) throw new PlanningLaunchException($"Working directory does not exist: {ctx.WorkingDir}"); var resolvedWt = Resolve(_wtPath); if (resolvedWt is null) throw new PlanningLaunchException($"Windows Terminal not found: {_wtPath}"); var resolvedClaude = Resolve(_claudePath); if (resolvedClaude is null) throw new PlanningLaunchException($"claude executable not found: {_claudePath}"); var psi = new ProcessStartInfo { FileName = resolvedWt, UseShellExecute = false, CreateNoWindow = false, }; psi.Environment["CLAUDEDO_PLANNING_TOKEN"] = ctx.Token; psi.ArgumentList.Add("-d"); psi.ArgumentList.Add(ctx.WorkingDir); psi.ArgumentList.Add(resolvedClaude); psi.ArgumentList.Add("--permission-mode"); psi.ArgumentList.Add("plan"); psi.ArgumentList.Add("--resume"); psi.ArgumentList.Add(ctx.ClaudeSessionId); var proc = Process.Start(psi) ?? throw new PlanningLaunchException("Failed to start Windows Terminal process."); return Task.CompletedTask; } private static string? Resolve(string pathOrName) { if (File.Exists(pathOrName)) return pathOrName; // Try PATH resolution var envPath = Environment.GetEnvironmentVariable("PATH") ?? string.Empty; var extensions = new[] { "", ".exe", ".cmd", ".bat" }; foreach (var dir in envPath.Split(Path.PathSeparator)) { foreach (var ext in extensions) { var candidate = Path.Combine(dir, pathOrName + ext); if (File.Exists(candidate)) return candidate; } } return null; } }