| 1 | using CascadeIDE.Features.Agent.Environment; |
| 2 | |
| 3 | namespace CascadeIDE.Features.Chat; |
| 4 | |
| 5 | public readonly record struct ChatSlashAgentResult(bool Success, string Message); |
| 6 | |
| 7 | public static class ChatSlashAgentActions |
| 8 | { |
| 9 | public static bool TryExecute( |
| 10 | string slashPath, |
| 11 | string? argsTail, |
| 12 | IAgentEnvironmentService agentEnvironment, |
| 13 | Func<string?> getSolutionPath, |
| 14 | out ChatSlashAgentResult result) |
| 15 | { |
| 16 | result = default; |
| 17 | if (!IntentSlashCatalog.TryGetRoute(slashPath, out var route) |
| 18 | || route.ExecutionKind != ChatSlashCommandExecutionKind.LocalAgent) |
| 19 | { |
| 20 | return false; |
| 21 | } |
| 22 | |
| 23 | var normalized = slashPath.Trim().ToLowerInvariant(); |
| 24 | return normalized switch |
| 25 | { |
| 26 | "/agent verify" => RunVerify(argsTail, agentEnvironment, getSolutionPath, out result), |
| 27 | "/agent cancel" => RunCancel(agentEnvironment, out result), |
| 28 | "/agent status" => RunStatus(agentEnvironment, out result), |
| 29 | "/agent last" => RunLast(agentEnvironment, out result), |
| 30 | "/agent sandbox" => RunSandbox(argsTail, agentEnvironment, getSolutionPath, out result), |
| 31 | _ => Unknown(normalized, out result), |
| 32 | }; |
| 33 | } |
| 34 | |
| 35 | private static bool RunVerify( |
| 36 | string? argsTail, |
| 37 | IAgentEnvironmentService agentEnvironment, |
| 38 | Func<string?> getSolutionPath, |
| 39 | out ChatSlashAgentResult result) |
| 40 | { |
| 41 | var solution = getSolutionPath(); |
| 42 | if (string.IsNullOrWhiteSpace(solution)) |
| 43 | { |
| 44 | result = new(false, "Открой solution (.sln/.slnx) перед /agent verify."); |
| 45 | return true; |
| 46 | } |
| 47 | |
| 48 | if (!AgentVerifyPolicyParser.TryParse(argsTail, out var policy)) |
| 49 | { |
| 50 | result = new(false, "Политика: minimal | standard | strict | ci_parity (по умолчанию standard)."); |
| 51 | return true; |
| 52 | } |
| 53 | |
| 54 | var start = agentEnvironment.StartVerify(solution, policy); |
| 55 | if (!start.Accepted) |
| 56 | { |
| 57 | result = new(false, start.Error ?? "Verify не запущен."); |
| 58 | return true; |
| 59 | } |
| 60 | |
| 61 | result = new( |
| 62 | true, |
| 63 | $"Verify запущен (run {start.RunId![..8]}…, snapshot {start.VerifySnapshotId}). Смотри /agent status и /agent last."); |
| 64 | return true; |
| 65 | } |
| 66 | |
| 67 | private static bool RunCancel(IAgentEnvironmentService agentEnvironment, out ChatSlashAgentResult result) |
| 68 | { |
| 69 | var cancelled = agentEnvironment.CancelActive(); |
| 70 | result = new(true, cancelled ? "Активный verify отменён." : "Нет активного verify."); |
| 71 | return true; |
| 72 | } |
| 73 | |
| 74 | private static bool RunStatus(IAgentEnvironmentService agentEnvironment, out ChatSlashAgentResult result) |
| 75 | { |
| 76 | var status = agentEnvironment.GetStatus(); |
| 77 | if (!status.IsActive) |
| 78 | { |
| 79 | result = new(true, "AEE: нет активного verify. Последний результат — /agent last."); |
| 80 | return true; |
| 81 | } |
| 82 | |
| 83 | var tail = status.WritesInvalidatedVerifyEpoch ? " · правки во время verify — перезапусти verify." : ""; |
| 84 | result = new( |
| 85 | true, |
| 86 | $"AEE: verify {status.RunId![..8]}… · policy {status.Policy} · snapshot {status.VerifySnapshotId}" |
| 87 | + (!string.IsNullOrEmpty(status.SandboxRunDirectory) |
| 88 | ? $" · sandbox {status.SandboxRunDirectory}" |
| 89 | : "") |
| 90 | + $"{tail}"); |
| 91 | return true; |
| 92 | } |
| 93 | |
| 94 | private static bool RunLast(IAgentEnvironmentService agentEnvironment, out ChatSlashAgentResult result) |
| 95 | { |
| 96 | var last = agentEnvironment.GetLastRun(); |
| 97 | if (last is null) |
| 98 | { |
| 99 | result = new(true, "Пока нет завершённых verify runs в этой сессии."); |
| 100 | return true; |
| 101 | } |
| 102 | |
| 103 | result = new(true, last.FormatChatTrace()); |
| 104 | return true; |
| 105 | } |
| 106 | |
| 107 | private static bool RunSandbox( |
| 108 | string? argsTail, |
| 109 | IAgentEnvironmentService agentEnvironment, |
| 110 | Func<string?> getWorkspaceRoot, |
| 111 | out ChatSlashAgentResult result) |
| 112 | { |
| 113 | if (!AgentSandboxProfileParser.TryParse(argsTail, out var profile)) |
| 114 | { |
| 115 | result = new(false, "Профиль: agent_ephemeral | agent_worktree | in_place"); |
| 116 | return true; |
| 117 | } |
| 118 | |
| 119 | var prep = agentEnvironment.PrepareSandbox(profile, getWorkspaceRoot()); |
| 120 | result = prep.Success |
| 121 | ? new(true, $"Sandbox {profile}: {prep.Path}\n{prep.Detail}") |
| 122 | : new(false, prep.Detail ?? "Sandbox prepare failed."); |
| 123 | return true; |
| 124 | } |
| 125 | |
| 126 | private static bool Unknown(string path, out ChatSlashAgentResult result) |
| 127 | { |
| 128 | result = new(false, $"Неизвестная agent-команда: {path}. Доступно: /agent verify|cancel|status|last|sandbox"); |
| 129 | return true; |
| 130 | } |
| 131 | } |
| 132 | |