-
Notifications
You must be signed in to change notification settings - Fork 8
Clean up classes #15
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: master
Are you sure you want to change the base?
Clean up classes #15
Conversation
- Move `SubAgentContext`, `ReviewerContext`, `ReviewDecision`, `AgentTask`, `AgentTaskResult`, `AgentStatusInfo` to `Agents/MultiAgent/Objects` - Move `AgentStatus`, `TaskStatus`, `ReviewStatus` to `Agents/MultiAgent` - This improves project structure and organization. Commit generated by The Saturn Pipeline
- Move `SubAgentConfiguration` to `Config/Objects` - This improves project structure and organization. Commit generated by The Saturn Pipeline
- Move `PersistedAgentConfiguration` to `Configuration/Objects` - This improves project structure and organization. Commit generated by The Saturn Pipeline
- Remove windows specific git add command - The previous implementation caused errors. Commit generated by The Saturn Pipeline
- Remove unused comment in CreateAgentTool.cs Commit generated by The Saturn Pipeline
- Introduce new objects for tool results - These objects will be used to standardize tool outputs Commit generated by The Saturn Pipeline
- Remove internal classes from tools - Use the new objects instead Commit generated by The Saturn Pipeline
- Add StreamingToolCall object - This object will be used to store streaming tool calls Commit generated by The Saturn Pipeline
- Remove StreamingToolCall class from AgentBase - Use the new StreamingToolCall object instead Commit generated by The Saturn Pipeline
- Add FormattedText object - This object will be used to store formatted text Commit generated by The Saturn Pipeline
- Remove FormattedText class from MarkdownRenderer - Use the new FormattedText object instead Commit generated by The Saturn Pipeline
WalkthroughRelocates many DTOs, enums, and helper classes into dedicated Objects namespaces across Agents, Tools, Config, Configuration, and UI. Files add corresponding using directives and remove in-file type definitions. GitManager replaces platform-specific add with a single Changes
Sequence Diagram(s)(omitted — changes are structural/type relocations without new control-flow interactions) Estimated code review effort🎯 4 (Complex) | ⏱️ ~45 minutes Poem
Tip 🔌 Remote MCP (Model Context Protocol) integration is now available!Pro plan users can now connect to remote MCP servers from the Integrations page. Connect with popular remote MCPs such as Notion and Linear to add more context to your reviews and chats. 📜 Recent review detailsConfiguration used: CodeRabbit UI Review profile: CHILL Plan: Pro 💡 Knowledge Base configuration:
You can enable these sources in your CodeRabbit configuration. 📒 Files selected for processing (1)
🚧 Files skipped from review as they are similar to previous changes (1)
✨ Finishing Touches
🧪 Generate unit tests
🪧 TipsChatThere are 3 ways to chat with CodeRabbit:
SupportNeed help? Create a ticket on our support page for assistance with any issues or questions. CodeRabbit Commands (Invoked using PR/Issue comments)Type Other keywords and placeholders
CodeRabbit Configuration File (
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 9
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (19)
Tools/WebFetchTool.cs (1)
161-169
: Harden cache key and avoid serving header-authenticated content across callsThe cache key omits includeMetadata and maxLength, so callers can receive mismatched content (metadata present/absent, different truncation). Also, caching responses when custom headers are supplied (e.g., Authorization, Cookie) risks leaking header-scoped content across unrelated calls. Lastly, returning the same ToolResult instance invites cross-call mutation.
Apply:
- var cacheKey = $"{url}|{extractionMode}|{selector}"; - if (useCache && _cache.TryGetValue(cacheKey, out var cached)) + var hasCustomHeaders = headers != null && headers.Count > 0; + var canCache = useCache && !hasCustomHeaders; + var cacheKey = $"{url}|{extractionMode}|{selector}|meta={includeMetadata}|max={maxLength}"; + if (canCache && _cache.TryGetValue(cacheKey, out var cached)) { - if (DateTime.UtcNow - cached.CachedAt < CacheDuration) + if (DateTime.UtcNow - cached.CachedAt < CacheDuration) { - return cached.Result; + var r = cached.Result; // defensive copy to avoid shared mutable state + return new ToolResult + { + Success = r.Success, + FormattedOutput = r.FormattedOutput, + RawData = r.RawData, + Error = r.Error + }; } - _cache.TryRemove(cacheKey, out _); + _cache.TryRemove(cacheKey, out _); }And when storing:
- if (useCache) + if (canCache) { _cache[cacheKey] = new CachedContent { Result = result, CachedAt = DateTime.UtcNow }; }Tools/GlobTool.cs (2)
37-39
: Pattern parsing contradicts examples; comma-splitting breaks{json,xml,config}
and yields invalid includesThe example shows brace expansion:
**/*.{json,xml,config}
, but the implementation naïvely splits the entire pattern string on commas, producing invalid fragments like**/*.{json
. Microsoft’s globbing does not support brace expansion; multiple patterns should be passed separately (e.g., with;
). This likely confuses users and produces wrong results.Update the parsing to split only on
;
and fix the example:- var patterns = pattern.Split(new[] { ',', ';' }, StringSplitOptions.RemoveEmptyEntries) - .Select(p => p.Trim()) - .Where(p => !string.IsNullOrEmpty(p)); + var patterns = pattern.Split(new[] { ';' }, StringSplitOptions.RemoveEmptyEntries) + .Select(p => p.Trim()) + .Where(p => !string.IsNullOrEmpty(p));-// - Find config files: pattern='**/*.{json,xml,config}' +// - Find config files: pattern='**/*.json;**/*.xml;**/*.config'Optionally, expand the parameter help text to explicitly instruct semicolon-separated multiple patterns.
I can update parameter docs/examples across the repo for consistency if you want.
Also applies to: 165-168
347-361
: Harden path containment check to avoid prefix-bypass (StartsWith is unsafe for paths)
StartsWith(currentDirectory, OrdinalIgnoreCase)
allows escapes likeC:\work2
whencurrentDirectory
isC:\work
. UsePath.GetRelativePath
and validate it doesn’t escape upward or cross volumes.Apply:
- var fullPath = Path.GetFullPath(path); - var currentDirectory = Path.GetFullPath(Directory.GetCurrentDirectory()); - - if (!fullPath.StartsWith(currentDirectory, StringComparison.OrdinalIgnoreCase)) - { - throw new SecurityException($"Access denied: Path '{path}' is outside the working directory."); - } + var fullPath = Path.GetFullPath(path); + var currentDirectory = Path.GetFullPath(Directory.GetCurrentDirectory()); + var rel = Path.GetRelativePath(currentDirectory, fullPath); + // Outside if it climbs up (..), or if RelativePath returns rooted (different drive/volume) + if (Path.IsPathRooted(rel) || rel == ".." || rel.StartsWith(".." + Path.DirectorySeparatorChar, StringComparison.Ordinal)) + { + throw new SecurityException($"Access denied: Path '{path}' is outside the working directory."); + }Tools/DeleteFileTool.cs (2)
31-34
: Advertised safety invariant isn’t enforced: allowlisting working directoryThe description promises “Prevents deletion outside working directory”, but there’s no check after
GetFullPath
. Add a guard to block paths outside the allowed root.Apply this diff near path resolution:
- var fullPath = Path.GetFullPath(path); + var fullPath = Path.GetFullPath(path); + var root = Directory.GetCurrentDirectory(); + if (!IsSubPathOf(fullPath, root)) + { + return CreateErrorResult($"Refusing to delete outside working directory: {fullPath}"); + }Add helper in the class:
private static bool IsSubPathOf(string childPath, string parentPath) { var child = Path.GetFullPath(childPath) .TrimEnd(Path.DirectorySeparatorChar, Path.AltDirectorySeparatorChar); var parent = Path.GetFullPath(parentPath) .TrimEnd(Path.DirectorySeparatorChar, Path.AltDirectorySeparatorChar); if (child.Equals(parent, StringComparison.OrdinalIgnoreCase)) return true; return child.StartsWith(parent + Path.DirectorySeparatorChar, StringComparison.OrdinalIgnoreCase); }Also applies to: 100-113
171-173
: Pattern + recursive can attempt to delete non-empty directories → IOExceptionWith
recursive=true
and a restrictivepattern
, you schedule all directories for deletion regardless of whether non-matching files remain.Directory.Delete(dir, false)
then throws for non-empty dirs and aborts the whole operation.Minimally fix in
PerformDeletion
by skipping non-empty directories and respectingforce
for read-only dirs:- foreach (var item in items.Where(i => i.Type == "directory").OrderByDescending(i => i.Path.Length)) + foreach (var item in items.Where(i => i.Type == "directory").OrderByDescending(i => i.Path.Length)) { - if (Directory.Exists(item.Path)) - { - Directory.Delete(item.Path, false); - deleted.Add(item); - } + if (!Directory.Exists(item.Path)) + continue; + + // Clear read-only attributes on directories when forced + if (force) + { + var attrs = File.GetAttributes(item.Path); + if ((attrs & FileAttributes.ReadOnly) == FileAttributes.ReadOnly) + { + File.SetAttributes(item.Path, attrs & ~FileAttributes.ReadOnly); + } + } + + // Only delete if empty (patterned deletions may leave residual files) + if (!Directory.EnumerateFileSystemEntries(item.Path).Any()) + { + Directory.Delete(item.Path, false); + deleted.Add(item); + } }Optionally, avoid adding directories at all when a pattern is provided and
recursive=true
, and instead decide directory cleanup post-file deletions. I can provide that refactor if preferred.Also applies to: 204-238, 255-262
Tools/SearchAndReplaceTool.cs (5)
259-263
: Fix compile error: Matches type should be ListCI failure shows a type mismatch: List vs List. FileResult.Matches is List, so initialize accordingly.
var result = new FileResult { Path = filePath, - Matches = new List<MatchInfo>() + Matches = new List<SearchMatchInfo>() };
341-354
: Align GeneratePreview signature with SearchMatchInfo and highlight the exact occurrence
- Signature still uses List while callers pass List.
- Replacing via string.Replace highlights all occurrences; use Column + MatchedText length to bracket only the matched span.
-private string GeneratePreview(string original, string modified, List<MatchInfo> matches) +private string GeneratePreview(string original, string modified, List<SearchMatchInfo> matches) { var preview = new StringBuilder(); var maxMatches = Math.Min(matches.Count, 3); for (int i = 0; i < maxMatches; i++) { var match = matches[i]; preview.AppendLine($" Line {match.Line}:"); preview.AppendLine($" - {match.LineContent}"); - - var modifiedLine = match.LineContent.Replace(match.MatchedText, $"[{match.MatchedText}]"); - preview.AppendLine($" + {modifiedLine}"); + + // Bracket exactly the matched occurrence using 1-based Column + var col0 = Math.Max(0, match.Column - 1); + var len = Math.Max(0, match.MatchedText?.Length ?? 0); + col0 = Math.Min(col0, match.LineContent.Length); + var safeLen = Math.Min(len, Math.Max(0, match.LineContent.Length - col0)); + var before = match.LineContent.Substring(0, col0); + var after = match.LineContent.Substring(col0 + safeLen); + var highlighted = $"{before}[{match.MatchedText}]{after}"; + preview.AppendLine($" + {highlighted}"); } if (matches.Count > 3) { preview.AppendLine($" ... and {matches.Count - 3} more matches"); } return preview.ToString(); }
327-331
: Guard against index==0: LastIndexOf with negative startIndex throwsWhen a match starts at index 0, LastIndexOf('\n', index - 1) passes -1 and throws ArgumentOutOfRangeException. Guard and handle “no prior newline”.
private int GetColumnNumber(string content, int index) { - var lastNewline = content.LastIndexOf('\n', index - 1); - return index - lastNewline; + if (index <= 0) return 1; + var lastNewline = content.LastIndexOf('\n', index - 1); + if (lastNewline < 0) return index + 1; + return index - lastNewline; }
333-339
: Avoid exception on index==0 and strip CR from CRLF lines in previewsSame negative startIndex issue here; also trim trailing '\r' so previews don’t show stray carriage returns.
private string GetLineContent(string content, int index) { - var start = content.LastIndexOf('\n', index - 1) + 1; + var prevNewline = index > 0 ? content.LastIndexOf('\n', index - 1) : -1; + var start = prevNewline + 1; var end = content.IndexOf('\n', index); if (end == -1) end = content.Length; - return content.Substring(start, end - start); + return content.Substring(start, end - start).TrimEnd('\r'); }
424-438
: Harden path containment check to prevent prefix-bypassStartsWith(currentDirectory) allows siblings like C:\repo2 when currentDirectory is C:\repo. Normalize with trailing separators or use a relative-path check.
private void ValidatePathSecurity(string path) { if (string.IsNullOrWhiteSpace(path)) { throw new ArgumentException("Path cannot be null or empty"); } - var fullPath = Path.GetFullPath(path); - var currentDirectory = Path.GetFullPath(Directory.GetCurrentDirectory()); - - if (!fullPath.StartsWith(currentDirectory, StringComparison.OrdinalIgnoreCase)) + var fullPath = Path.GetFullPath(path); + var currentDirectory = Path.GetFullPath(Directory.GetCurrentDirectory()); + var currentWithSep = currentDirectory.TrimEnd(Path.DirectorySeparatorChar, Path.AltDirectorySeparatorChar) + + Path.DirectorySeparatorChar; + var fullWithSep = fullPath.TrimEnd(Path.DirectorySeparatorChar, Path.AltDirectorySeparatorChar) + + Path.DirectorySeparatorChar; + if (!fullWithSep.StartsWith(currentWithSep, StringComparison.OrdinalIgnoreCase)) { throw new SecurityException($"Access denied: Path '{path}' is outside the working directory"); } }Tools/GrepTool.cs (4)
29-31
: Documentation parameter mismatch: 'caseSensitive' vs 'ignoreCase'.The How to use text says 'caseSensitive', while the actual parameter is 'ignoreCase'. This is user-facing and can cause incorrect usage.
Apply:
- - Set 'caseSensitive' to false for case-insensitive search + - Set 'ignoreCase' to true for case-insensitive searchAlso applies to: 67-71
116-117
: Add a regex timeout (and optionally compile) to prevent catastrophic backtracking (ReDoS).Patterns are user-provided; unbounded regex evaluation can hang the process.
Apply:
- var regexOptions = ignoreCase ? RegexOptions.IgnoreCase : RegexOptions.None; - var regex = new Regex(pattern, regexOptions); + var regexOptions = (ignoreCase ? RegexOptions.IgnoreCase : RegexOptions.None) | RegexOptions.Compiled; + var regex = new Regex(pattern, regexOptions, TimeSpan.FromSeconds(2));
148-176
: Global maxResults enforcement is broken inside SearchFile.You pass the remaining budget (maxResults - results.Count) into SearchFile, but the loop condition compares the global results.Count to that remaining value. If results.Count is already > remaining (which it always is), the loop never executes for subsequent files. This silently under-reports results.
Minimal fix: compute a per-call target and compare against it.
- private void SearchFile(string filePath, Regex regex, List<GrepResult> results, int maxResults) + private void SearchFile(string filePath, Regex regex, List<GrepResult> results, int maxResults) { try { - var lines = File.ReadAllLines(filePath); - for (int i = 0; i < lines.Length && results.Count < maxResults; i++) + var lines = File.ReadAllLines(filePath); + // Honor the remaining allowance passed in by caller + var targetCount = results.Count + maxResults; + for (int i = 0; i < lines.Length && results.Count < targetCount; i++) { var matches = regex.Matches(lines[i]); if (matches.Count > 0) { results.Add(new GrepResult { FilePath = filePath, LineNumber = i + 1, Line = lines[i], Matches = matches.Cast<Match>().Select(m => new MatchInfo { Value = m.Value, Index = m.Index, Length = m.Length }).ToList() }); } } } catch (Exception ex) { - + System.Diagnostics.Debug.WriteLine($"[GrepTool] Failed reading '{filePath}': {ex.Message}"); } }If you’d like, I can propose a streaming version using File.ReadLines to further reduce memory.
173-176
: Don’t swallow exceptions silently.Currently, failures to read a file disappear. At minimum, log the error; ideally, propagate as a warning in the ToolResult.
If you prefer not to change the method above, at least:
- catch (Exception ex) - { - - } + catch (Exception ex) + { + System.Diagnostics.Debug.WriteLine($"[GrepTool] Failed reading '{filePath}': {ex.Message}"); + }Configuration/ConfigurationManager.cs (3)
28-46
: Don’t swallow exceptions; fix CS0168 by logging or omitting the variableCI warns: Line 47 CS0168 unused variable ex. More importantly, the exception is swallowed, making troubleshooting difficult.
Apply:
public static async Task<PersistedAgentConfiguration?> LoadConfigurationAsync() { try { if (!File.Exists(ConfigFilePath)) { return null; } var json = await File.ReadAllTextAsync(ConfigFilePath); var config = JsonSerializer.Deserialize<PersistedAgentConfiguration>(json, JsonOptions); - if (config != null && config.RequireCommandApproval == null) + if (config != null && config.RequireCommandApproval == null) { config.RequireCommandApproval = true; } return config; } - catch (Exception ex) + catch (Exception ex) { - - return null; + Console.Error.WriteLine($"[LoadConfigurationAsync] Failed to load {ConfigFilePath}: {ex}"); + return null; } }
54-70
: Same as above for Save; fix CS0168 and add loggingThe exception is swallowed and ex is unused (CI warning at Line 66). Log the error.
Apply:
public static async Task SaveConfigurationAsync(PersistedAgentConfiguration config) { try { if (!Directory.Exists(AppDataPath)) { Directory.CreateDirectory(AppDataPath); } var json = JsonSerializer.Serialize(config, JsonOptions); await File.WriteAllTextAsync(ConfigFilePath, json); } - catch (Exception ex) + catch (Exception ex) { - + Console.Error.WriteLine($"[SaveConfigurationAsync] Failed to save {ConfigFilePath}: {ex}"); } }
90-103
: Only override booleans when explicitly set in persisted configPairs with making booleans nullable in PersistedAgentConfiguration to avoid accidental feature toggles on upgrade.
Apply:
public static void ApplyToAgentConfiguration(AgentConfiguration target, PersistedAgentConfiguration source) { target.Name = source.Name ?? target.Name; target.Model = source.Model ?? target.Model; target.Temperature = source.Temperature ?? target.Temperature; target.MaxTokens = source.MaxTokens ?? target.MaxTokens; target.TopP = source.TopP ?? target.TopP; - target.EnableStreaming = source.EnableStreaming; - target.MaintainHistory = source.MaintainHistory; + if (source.EnableStreaming.HasValue) target.EnableStreaming = source.EnableStreaming.Value; + if (source.MaintainHistory.HasValue) target.MaintainHistory = source.MaintainHistory.Value; target.MaxHistoryMessages = source.MaxHistoryMessages ?? target.MaxHistoryMessages; - target.EnableTools = source.EnableTools; + if (source.EnableTools.HasValue) target.EnableTools = source.EnableTools.Value; target.ToolNames = source.ToolNames ?? target.ToolNames; target.RequireCommandApproval = source.RequireCommandApproval ?? true; }Tools/ApplyDiffTool.cs (2)
598-619
: Path containment check is vulnerable to prefix-matching bypass; enforce safe containmentUsing StartsWith on raw full paths can be bypassed with similarly prefixed directories (e.g., C:\repo vs C:\repo-bak) or different roots. Replace the prefix check with a robust containment check.
Apply this diff to harden the validation:
- try - { - var fullPath = Path.GetFullPath(filePath); - var currentDirectory = Path.GetFullPath(Directory.GetCurrentDirectory()); - - if (!fullPath.StartsWith(currentDirectory, StringComparison.OrdinalIgnoreCase)) - { - throw new SecurityException($"Access denied: Path '{filePath}' is outside the working directory."); - } - } + try + { + var fullPath = Path.GetFullPath(filePath); + var basePath = Path.GetFullPath(Directory.GetCurrentDirectory()); + // Ensure base path ends with a separator for strict containment + if (!basePath.EndsWith(Path.DirectorySeparatorChar) && !basePath.EndsWith(Path.AltDirectorySeparatorChar)) + { + basePath += Path.DirectorySeparatorChar; + } + // Reject if not under the working directory + if (!fullPath.StartsWith(basePath, StringComparison.OrdinalIgnoreCase)) + { + throw new SecurityException($"Access denied: Path '{filePath}' is outside the working directory."); + } + }
490-496
: Validate Remove operations against file content to prevent accidental deletionsCurrently, Remove blindly deletes the line at currentIndex without confirming it matches the expected content. This can corrupt files if the file diverged slightly from the expected state.
- else if (change.Type == ChangeType.Remove) - { - if (currentIndex < lines.Count) - { - lines.RemoveAt(currentIndex); - } - } + else if (change.Type == ChangeType.Remove) + { + if (currentIndex >= lines.Count) + { + throw new InvalidOperationException($"Remove out of range at context '{hunk.ContextLine}'."); + } + var expected = change.Content ?? string.Empty; + var actual = lines[currentIndex]; + if (!string.Equals(actual, expected, StringComparison.Ordinal)) + { + throw new InvalidOperationException($"Remove mismatch at context '{hunk.ContextLine}'. Expected '{expected}', found '{actual}'."); + } + lines.RemoveAt(currentIndex); + }
using Saturn.OpenRouter.Services; | ||
|
||
namespace Saturn.Agents.Core.Objects | ||
{ | ||
internal class StreamingToolCall | ||
{ | ||
public string Id { get; set; } = string.Empty; | ||
public string Name { get; set; } = string.Empty; | ||
public JsonStreamAccumulator ArgumentsAccumulator { get; } = new JsonStreamAccumulator(); | ||
public bool IsComplete => ArgumentsAccumulator.IsComplete; | ||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
💡 Verification agent
🧩 Analysis chain
Class is not referenced; either wire it up or remove to keep the cleanup focused.
The new internal class StreamingToolCall
isn’t used anywhere (AgentBase accumulates deltas directly into ToolCall
). Keeping dead code contradicts the “Clean up classes” objective.
Run this to confirm usage:
Options:
- If unused, delete the file.
- If intended, integrate it into the streaming path to accumulate JSON arguments robustly (via
JsonStreamAccumulator
) instead of raw string concatenation.
Also consider sealing and documenting the type:
- internal class StreamingToolCall
+ internal sealed class StreamingToolCall
🏁 Script executed:
#!/bin/bash
# Verify whether StreamingToolCall is referenced anywhere
rg -nP '\bStreamingToolCall\b' -g '!**/bin/**' -g '!**/obj/**' -C2
Length of output: 420
Remove unused StreamingToolCall
class
The StreamingToolCall
class in Agents/Core/Objects/StreamingToolCall.cs
is not referenced anywhere outside its own definition and therefore constitutes dead code. Please remove it to keep the “Clean up classes” objective focused:
• Delete the file Agents/Core/Objects/StreamingToolCall.cs
• Remove its entry from the corresponding project file (e.g., Agents.Core.csproj
)
• If you intended to add streaming support, consider integrating JsonStreamAccumulator
into the existing ToolCall
workflow instead of introducing an unused class.
🤖 Prompt for AI Agents
In Agents/Core/Objects/StreamingToolCall.cs lines 1-12 the StreamingToolCall
class is unused; delete the file, remove its <Compile
Include="Agents\Core\Objects\StreamingToolCall.cs" /> entry from
Agents.Core.csproj (or equivalent project file), and if streaming support is
required instead integrate JsonStreamAccumulator into the existing ToolCall
implementation rather than keeping this separate unused class.
using System; | ||
using System.Collections.Generic; | ||
using System.Linq; | ||
using System.Text; | ||
using System.Threading.Tasks; | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
💡 Verification agent
🧩 Analysis chain
Repo-wide sanity check for TaskStatus usage
There may be other sites where TaskStatus
is referenced unqualified and could be impacted by the BCL collision. Run this quick audit and tidy up if needed (either fully-qualify your enum or add a using
alias).
🏁 Script executed:
#!/bin/bash
set -euo pipefail
echo "Scanning for potentially ambiguous TaskStatus usages…"
# 1) Find unqualified TaskStatus references in C# files (excluding obvious comments)
rg -nP --type cs -C1 '\bTaskStatus\b' | sed -n '1,200p'
echo
echo "Occurrences of using System.Threading.Tasks in Objects/ (these are likely unnecessary):"
rg -nP --type cs '^using\s+System\.Threading\.Tasks\s*;' Agents/MultiAgent/Objects -n
cat << 'TIP'
Tip:
- Prefer fully-qualifying your enum as Saturn.Agents.MultiAgent.TaskStatus in DTOs, or
- Add an alias at the top of files that need both Task (BCL) and your enum:
using AgentTaskStatus = Saturn.Agents.MultiAgent.TaskStatus;
// then: public AgentTaskStatus Status { get; set; }
TIP
Length of output: 2237
Audit and Resolve TaskStatus
Enum Collisions
We scanned the repo for unqualified TaskStatus
references and BCL imports; here’s what needs attention:
• Remove unnecessary using System.Threading.Tasks;
from DTO/object files that only reference your local enum. This avoids pulling in System.Threading.Tasks.TaskStatus
by accident. Specifically, delete or comment out that directive in:
- Agents/MultiAgent/Objects/SubAgentContext.cs
- Agents/MultiAgent/Objects/ReviewerContext.cs
- Agents/MultiAgent/Objects/ReviewDecision.cs
- Agents/MultiAgent/Objects/AgentTaskResult.cs
- Agents/MultiAgent/Objects/AgentTaskEnums.cs
- Agents/MultiAgent/Objects/AgentStatusInfo.cs
- Agents/MultiAgent/Objects/AgentTask.cs
Example diff for AgentTask.cs
:
@@ -5,7 +5,6 @@
using System;
using System.Collections.Generic;
using System.Linq;
-using System.Threading.Tasks; // remove to avoid TaskStatus collision
using System.Text;
using System.Threading.Tasks;
• In Agents/MultiAgent/AgentManager.cs
, unqualified usages like:
Status = TaskStatus.Running;
…
agentContext.CurrentTask!.Status = success
? TaskStatus.Completed
: TaskStatus.Failed;
resolve to the BCL enum when using System.Threading.Tasks;
is in scope. To ensure you’re using your local enum (Saturn.Agents.MultiAgent.Objects.TaskStatus
), choose one of:
- Add a using-alias at the top:
And update references:
using AgentTaskStatus = Saturn.Agents.MultiAgent.Objects.TaskStatus;
- Status = TaskStatus.Running; + Status = AgentTaskStatus.Running;
- Fully qualify each enum member:
- Status = TaskStatus.Running; + Status = Saturn.Agents.MultiAgent.Objects.TaskStatus.Running;
Please apply these changes to prevent accidental BCL collisions and ensure all TaskStatus
references point to your intended enum.
🤖 Prompt for AI Agents
In Agents/MultiAgent/Objects/AgentTask.cs around lines 1 to 6, remove the
unnecessary "using System.Threading.Tasks" directive so the file does not
inadvertently import the BCL TaskStatus; ensure any TaskStatus references in
this file resolve to the local enum by either adding a using-alias for
Saturn.Agents.MultiAgent.Objects.TaskStatus at the top or by fully qualifying
the enum where used, and apply the same removal of the System.Threading.Tasks
using to the other DTO/object files listed in the comment to prevent BCL
collisions.
using System.Collections.Generic; | ||
using System.Linq; | ||
using System.Text; | ||
using System.Threading.Tasks; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🛠️ Refactor suggestion
Remove unnecessary using that triggers the ambiguity
using System.Threading.Tasks;
isn’t used in this DTO and is the root cause of the TaskStatus
collision here.
Apply:
-using System.Threading.Tasks;
+// (removed) Avoids name clash with Saturn.Agents.MultiAgent.TaskStatus
🤖 Prompt for AI Agents
In Agents/MultiAgent/Objects/AgentTask.cs around line 5, remove the unused using
directive "using System.Threading.Tasks;" which causes an ambiguity with
TaskStatus; delete that using so the DTO no longer imports the conflicting
namespace and the correct TaskStatus (presumably from your domain or another
namespace) is used.
public string Description { get; set; } = ""; | ||
public Dictionary<string, object>? Context { get; set; } | ||
public DateTime StartedAt { get; set; } | ||
public TaskStatus Status { get; set; } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Fix ambiguous reference to TaskStatus
TaskStatus
here is ambiguous between System.Threading.Tasks.TaskStatus
and your enum in Saturn.Agents.MultiAgent
. This will fail to compile on most setups.
Apply this minimal diff to disambiguate and prevent future surprises:
- public TaskStatus Status { get; set; }
+ public Saturn.Agents.MultiAgent.TaskStatus Status { get; set; }
📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
public TaskStatus Status { get; set; } | |
public Saturn.Agents.MultiAgent.TaskStatus Status { get; set; } |
🤖 Prompt for AI Agents
In Agents/MultiAgent/Objects/AgentTask.cs around line 15, the property type
TaskStatus is ambiguous with System.Threading.Tasks.TaskStatus; update the
declaration to reference your enum explicitly (e.g.
Saturn.Agents.MultiAgent.TaskStatus Status { get; set; }) or add a using alias
for your enum and use that alias for the property to remove ambiguity and ensure
it compiles.
public enum TaskStatus | ||
{ | ||
Pending, | ||
Running, | ||
Completed, | ||
Failed | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Name collision: TaskStatus conflicts with System.Threading.Tasks.TaskStatus
Defining TaskStatus
here collides with the BCL enum System.Threading.Tasks.TaskStatus
. This already causes an ambiguity in Agents/MultiAgent/Objects/AgentTask.cs where using System.Threading.Tasks;
is present and TaskStatus
is referenced unqualified.
Two safe fixes:
- Minimal: Fully-qualify usages at call sites (e.g.,
Saturn.Agents.MultiAgent.TaskStatus
) and avoidusing System.Threading.Tasks;
in DTOs that don’t need it. - Structural: Rename this enum to
AgentTaskStatus
to permanently avoid collisions.
I’ve proposed a targeted fix in AgentTask.cs; consider the structural rename as a follow-up to prevent future regressions.
🤖 Prompt for AI Agents
In Agents/MultiAgent/Objects/AgentTaskEnums.cs around lines 19-25, the enum
TaskStatus collides with System.Threading.Tasks.TaskStatus; rename the enum to
AgentTaskStatus and update its declaration and XML comments, then update all
call sites (e.g., Agents/MultiAgent/AgentTask.cs and any DTOs) to use
AgentTaskStatus instead of TaskStatus, remove or keep using directives as
appropriate, and run a build to ensure no remaining ambiguities or missing
references.
using System; | ||
using System.Collections.Generic; | ||
using System.Linq; | ||
using System.Text; | ||
using System.Threading.Tasks; | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Add missing namespace import (compile error).
Agent
is defined in Saturn.Agents
; add the using directive.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
+using Saturn.Agents;
📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
using System; | |
using System.Collections.Generic; | |
using System.Linq; | |
using System.Text; | |
using System.Threading.Tasks; | |
using System; | |
using System.Collections.Generic; | |
using System.Linq; | |
using System.Text; | |
using System.Threading.Tasks; | |
using Saturn.Agents; |
🤖 Prompt for AI Agents
In Agents/MultiAgent/Objects/ReviewerContext.cs around lines 1 to 6, the file is
missing the namespace import for the Agent type which lives in Saturn.Agents;
add a using Saturn.Agents; directive to the top of the file alongside the other
using statements, then save and rebuild to resolve the compile error.
using System; | ||
using System.Collections.Generic; | ||
using System.Linq; | ||
using System.Text; | ||
using System.Threading.Tasks; | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Add missing namespace imports (compile error).
Agent
lives in Saturn.Agents
and AgentStatus
is in Saturn.Agents.MultiAgent
. Without these usings this file won’t compile.
Apply this diff:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
+using Saturn.Agents;
+using Saturn.Agents.MultiAgent;
📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
using System; | |
using System.Collections.Generic; | |
using System.Linq; | |
using System.Text; | |
using System.Threading.Tasks; | |
using System; | |
using System.Collections.Generic; | |
using System.Linq; | |
using System.Text; | |
using System.Threading.Tasks; | |
using Saturn.Agents; | |
using Saturn.Agents.MultiAgent; |
🤖 Prompt for AI Agents
In Agents/MultiAgent/Objects/SubAgentContext.cs around lines 1 to 6, the file is
missing required namespace imports so it won't compile because Agent is in
Saturn.Agents and AgentStatus is in Saturn.Agents.MultiAgent; add using
Saturn.Agents; and using Saturn.Agents.MultiAgent; at the top of the file
(alongside existing System usings) so the types resolve.
public bool EnableStreaming { get; set; } = true; | ||
public bool MaintainHistory { get; set; } = true; | ||
public int? MaxHistoryMessages { get; set; } | ||
public bool EnableTools { get; set; } = true; | ||
public List<string>? ToolNames { get; set; } | ||
public bool? RequireCommandApproval { get; set; } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🛠️ Refactor suggestion
Avoid silently enabling features on legacy configs (make booleans nullable and apply conditionally)
EnableStreaming/MaintainHistory/EnableTools default to true here. When older JSON lacks these fields, System.Text.Json keeps the initializer values, potentially turning features on unexpectedly when applying to AgentConfiguration.
Apply (DTO changes):
- public bool EnableStreaming { get; set; } = true;
- public bool MaintainHistory { get; set; } = true;
+ public bool? EnableStreaming { get; set; }
+ public bool? MaintainHistory { get; set; }
public int? MaxHistoryMessages { get; set; }
- public bool EnableTools { get; set; } = true;
+ public bool? EnableTools { get; set; }
And update ApplyToAgentConfiguration (see suggested diff in ConfigurationManager below) to only override when HasValue.
This preserves backward compatibility and prevents unintended behavior shifts for users upgrading.
📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
public bool EnableStreaming { get; set; } = true; | |
public bool MaintainHistory { get; set; } = true; | |
public int? MaxHistoryMessages { get; set; } | |
public bool EnableTools { get; set; } = true; | |
public List<string>? ToolNames { get; set; } | |
public bool? RequireCommandApproval { get; set; } | |
public bool? EnableStreaming { get; set; } | |
public bool? MaintainHistory { get; set; } | |
public int? MaxHistoryMessages { get; set; } | |
public bool? EnableTools { get; set; } | |
public List<string>? ToolNames { get; set; } | |
public bool? RequireCommandApproval { get; set; } |
🤖 Prompt for AI Agents
In Configuration/Objects/PersistedAgentConfiguration.cs around lines 16–21,
change the boolean DTO fields that currently default to true (EnableStreaming,
MaintainHistory, EnableTools) to nullable booleans (bool?) with no initializer
so legacy JSON doesn't silently enable features; leave RequireCommandApproval
as-is if already nullable and keep MaxHistoryMessages/ToolNames nullable. Then
update ApplyToAgentConfiguration to only assign each corresponding
AgentConfiguration property when the persisted field HasValue (for bool? fields)
or is not null (for ToolNames/MaxHistoryMessages), so existing configs aren’t
overridden unless the persisted DTO explicitly specifies a value.
{ | ||
public class CommandExecutorConfig | ||
{ | ||
public SecurityMode SecurityMode { get; set; } = SecurityMode.Unrestricted; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
💡 Verification agent
🧩 Analysis chain
Defaulting to Unrestricted execution is risky; consider safer-by-default.
If “Unrestricted” allows executing arbitrary commands, defaulting to it can create a footgun. If preserving behavior is required for this PR, at least track a follow-up to switch the default to the most restrictive mode in a subsequent release, or require callers to opt-in explicitly.
Please verify current call sites rely on Unrestricted as a default before changing it. To check usage and available enum values:
🏁 Script executed:
#!/bin/bash
# Find where the config is instantiated and what SecurityMode values exist.
rg -nP -C2 $'new\\s+CommandExecutorConfig\\s*\\('
rg -nP -C1 $'SecurityMode\\.(\\w+)'
Length of output: 1363
Security Risk: Defaulting to Unrestricted Execution
We’ve verified that by default SecurityMode is set to Unrestricted in:
- Tools/Objects/CommandExecutorConfig.cs (line 7)
- Tools/ExecuteCommandTool.cs constructors (lines 23 and 29), which both do
new CommandExecutorConfig()
when no config is provided
As a result, all existing call sites run in Unrestricted mode by default, allowing arbitrary commands without approval.
Recommendations
- If you need to preserve backward‐compatibility in this PR, file a follow-up issue to switch the default SecurityMode to the most restrictive option (e.g.,
DenyAll
or an allowlist) in the next release. - For stronger guarantees, consider requiring callers to explicitly specify
SecurityMode
(makingUnrestricted
an opt-in only) or removing the parameterless constructor altogether.
🤖 Prompt for AI Agents
In Tools/Objects/CommandExecutorConfig.cs around line 7, the SecurityMode
property is defaulting to SecurityMode.Unrestricted which causes callers that
use new CommandExecutorConfig() (e.g., Tools/ExecuteCommandTool.cs constructors
at lines 23 and 29) to run in Unrestricted mode by default; change the default
to a safer setting (e.g., SecurityMode.DenyAll or SecurityMode.Allowlist) or
remove the parameterless default by making the property required via constructor
injection so callers must explicitly pass a SecurityMode, and if preserving
backward compatibility in this PR, create a follow-up issue to flip the default
to the most restrictive option in the next release and/or require explicit
opt-in for Unrestricted.
- Rename `MatchInfo` to `SearchMatchInfo` for clarity. - This change improves code readability and maintainability. Commit generated by The Saturn Pipeline
Summary by CodeRabbit