Spawns Windows Terminal in the list working directory running `claude --permission-mode auto` with the task title and description prefilled as the initial prompt. Reuses the planning launcher infrastructure but skips worktree, system prompt, and MCP setup. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
168 lines
6.4 KiB
C#
168 lines
6.4 KiB
C#
// 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 <path> (file form)
|
|
// Session ID: no pre-assign flag; resume with --resume <id>
|
|
// 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;
|
|
}
|
|
}
|