Skip to content

Conversation

@yileicn
Copy link
Member

@yileicn yileicn commented Dec 29, 2025

PR Type

Enhancement


Description

  • Comprehensive conversion of synchronous methods to async/await pattern across the entire codebase

  • Converted all repository implementations (FileRepository and MongoRepository) to use async methods for database and file I/O operations

  • Replaced synchronous MongoDB operations with async equivalents (UpdateOneAsync, InsertManyAsync, DeleteManyAsync, etc.)

  • Replaced synchronous file I/O operations with async versions (File.WriteAllTextAsync, File.ReadAllTextAsync, etc.)

  • Replaced Thread.Sleep() calls with await Task.Delay() for non-blocking delays

  • Replaced lock statements with SemaphoreSlim for thread-safe async operations in conversation management

  • Updated all interface signatures in IBotSharpRepository and IConversationService to return Task, Task<T>, or Task<bool>

  • Updated service layer methods across ConversationService, AgentService, RoleService, and other services to properly await async repository calls

  • Updated controller methods to use await for async service operations

  • Updated plugin and hook implementations to properly handle async method calls

  • Removed unnecessary .ConfigureAwait(false).GetAwaiter().GetResult() calls and replaced with proper await syntax


Diagram Walkthrough

flowchart LR
  A["Synchronous Methods<br/>File I/O & MongoDB"] -->|Convert to async| B["Async Methods<br/>with Task return types"]
  C["Thread.Sleep()"] -->|Replace with| D["Task.Delay()"]
  E["lock statements"] -->|Replace with| F["SemaphoreSlim"]
  G["Sync Repository Calls"] -->|Add await| H["Async Repository Calls"]
  B --> I["Updated Interfaces<br/>IBotSharpRepository<br/>IConversationService"]
  H --> I
  I --> J["Service Layer<br/>Properly awaits async calls"]
  J --> K["Controller Layer<br/>Properly awaits async calls"]
Loading

File Walkthrough

Relevant files
Enhancement
51 files
FileRepository.Agent.cs
Convert FileRepository Agent methods to async operations 

src/Infrastructure/BotSharp.Core/Repository/FileRepository/FileRepository.Agent.cs

  • Converted all synchronous methods to async Task methods with await
    keywords
  • Replaced synchronous file I/O operations (File.WriteAllText,
    File.ReadAllText, File.WriteAllLines) with async equivalents
    (File.WriteAllTextAsync, File.ReadAllTextAsync,
    File.WriteAllLinesAsync)
  • Replaced Thread.Sleep() calls with await Task.Delay() for non-blocking
    delays
  • Updated method signatures to return Task, Task, or Task instead of
    void or direct return types
+107/-107
MongoRepository.Agent.cs
Convert MongoRepository Agent methods to async operations

src/Plugins/BotSharp.Plugin.MongoStorage/Repository/MongoRepository.Agent.cs

  • Converted all synchronous MongoDB operations to async equivalents
    (e.g., UpdateOne to UpdateOneAsync, InsertMany to InsertManyAsync,
    DeleteMany to DeleteManyAsync)
  • Updated method signatures to return Task, Task, or Task instead of
    void or direct return types
  • Added await keywords for all async MongoDB driver calls
  • Replaced LINQ queries with async-compatible MongoDB filter builders
+107/-105
FileRepository.Conversation.cs
Convert FileRepository Conversation methods to async operations

src/Infrastructure/BotSharp.Core/Repository/FileRepository/FileRepository.Conversation.cs

  • Converted synchronous file I/O operations to async equivalents
    throughout conversation management methods
  • Replaced lock statements with SemaphoreSlim for thread-safe async
    operations on _dialogLock and _stateLock
  • Updated all method signatures to return Task, Task, or Task instead of
    void or direct return types
  • Replaced Thread.Sleep() with await Task.Delay() for non-blocking
    delays
+93/-67 
MongoRepository.Conversation.cs
Convert MongoRepository Conversation methods to async operations

src/Plugins/BotSharp.Plugin.MongoStorage/Repository/MongoRepository.Conversation.cs

  • Converted all synchronous MongoDB operations to async equivalents with
    await keywords
  • Updated method signatures to return Task, Task, or Task instead of
    void or direct return types
  • Replaced LINQ queries with async-compatible MongoDB filter builders
    and aggregation pipelines
  • Refactored GetIdleConversations to use MongoDB filter builders instead
    of LINQ for better async support
+104/-70
IBotSharpRepository.cs
Update IBotSharpRepository interface signatures to async 

src/Infrastructure/BotSharp.Abstraction/Repositories/IBotSharpRepository.cs

  • Updated interface method signatures to return Task, Task, or Task
    instead of void or direct return types
  • Affected methods include agent operations, conversation management,
    statistics, translations, and knowledge base operations
  • Maintains backward compatibility with default NotImplementedException
    implementations
+60/-60 
StreamingLogHook.cs
Convert StreamingLogHook methods to async operations         

src/Plugins/BotSharp.Plugin.ChatHub/Hooks/StreamingLogHook.cs

  • Converted BuildContentLog and BuildStateLog methods to async Task
    methods
  • Updated all calls to these methods with await keyword
  • Changed SaveConversationContentLog and SaveConversationStateLog calls
    to use await for async repository operations
+22/-22 
ConversationService.cs
Update ConversationService to use async repository methods

src/Infrastructure/BotSharp.Core/Conversations/Services/ConversationService.cs

  • Updated method calls to use await for async repository operations
    (DeleteConversations, UpdateConversationTitle, UpdateConversationTags,
    etc.)
  • Converted GetDialogHistory and SetConversationId to async methods with
    await keywords
  • Updated GetAgentMessageCount and SaveStates to async methods
  • Added await for async state loading and dialog retrieval operations
+19/-19 
FileRepository.KnowledgeBase.cs
Convert FileRepository KnowledgeBase methods to async operations

src/Infrastructure/BotSharp.Core/Repository/FileRepository/FileRepository.KnowledgeBase.cs

  • Converted synchronous file I/O operations to async equivalents
    (File.WriteAllTextAsync, File.ReadAllTextAsync)
  • Updated method signatures to return Task instead of bool
  • Added await keywords for all async file operations
+12/-12 
MongoRepository.Role.cs
MongoDB Role repository methods converted to async             

src/Plugins/BotSharp.Plugin.MongoStorage/Repository/MongoRepository.Role.cs

  • Converted RefreshRoles, GetRoles, GetRoleDetails, and UpdateRole
    methods to async by adding async Task return types
  • Replaced synchronous MongoDB operations (DeleteMany, InsertMany,
    Find().ToList(), UpdateOne) with their async counterparts
    (DeleteManyAsync, InsertManyAsync, ToListAsync, UpdateOneAsync)
  • Added await keywords for all async MongoDB calls
+16/-16 
FileRepository.Log.cs
File repository log methods converted to async                     

src/Infrastructure/BotSharp.Core/Repository/FileRepository/FileRepository.Log.cs

  • Converted log-related methods (SaveLlmCompletionLog,
    SaveConversationContentLog, GetConversationContentLogs,
    SaveConversationStateLog, GetConversationStateLogs) to async
  • Replaced synchronous file I/O operations (File.WriteAllText,
    File.ReadAllText) with async versions (File.WriteAllTextAsync,
    File.ReadAllTextAsync)
  • Added await keywords for all async file operations
+10/-10 
FileRepository.Role.cs
File repository role methods converted to async                   

src/Infrastructure/BotSharp.Core/Repository/FileRepository/FileRepository.Role.cs

  • Converted role management methods to async with Task return types
  • Replaced Thread.Sleep(50) with await Task.Delay(50) for async delays
  • Changed synchronous file operations to async (File.WriteAllTextAsync)
  • Wrapped synchronous results with Task.FromResult() for consistency
+11/-11 
PluginLoader.cs
Plugin loader methods converted to async                                 

src/Infrastructure/BotSharp.Core/Plugins/PluginLoader.cs

  • Converted GetPlugins, GetPagedPlugins, and UpdatePluginStatus methods
    to async
  • Replaced synchronous repository calls with async equivalents using
    await
  • Simplified async/await patterns by removing
    .ConfigureAwait(false).GetAwaiter().GetResult() calls and using proper
    await
+14/-14 
MongoRepository.Log.cs
MongoDB log repository methods converted to async               

src/Plugins/BotSharp.Plugin.MongoStorage/Repository/MongoRepository.Log.cs

  • Converted log-related methods to async with Task return types
  • Replaced synchronous MongoDB operations with async counterparts
    (InsertOneAsync, FirstOrDefaultAsync, ToListAsync)
  • Added await keywords for all async MongoDB calls
+12/-12 
ConversationStateService.cs
Conversation state service methods converted to async       

src/Infrastructure/BotSharp.Core/Conversations/Services/ConversationStateService.cs

  • Converted Load and Save methods to async with Task return types
  • Replaced synchronous repository calls with async equivalents using
    await
  • Changed .Wait() calls to proper await syntax for async operations
  • Updated Dispose method to use
    .ConfigureAwait(false).GetAwaiter().GetResult() for async cleanup
+9/-9     
MongoRepository.KnowledgeBase.cs
MongoDB knowledge base repository methods converted to async

src/Plugins/BotSharp.Plugin.MongoStorage/Repository/MongoRepository.KnowledgeBase.cs

  • Converted knowledge base configuration and document methods to async
  • Replaced synchronous MongoDB operations with async counterparts
    (DeleteManyAsync, InsertManyAsync, ReplaceOneAsync, ToListAsync)
  • Added await keywords for all async database calls
+12/-12 
MongoRepository.AgentTask.cs
MongoDB agent task repository methods converted to async 

src/Plugins/BotSharp.Plugin.MongoStorage/Repository/MongoRepository.AgentTask.cs

  • Converted agent task methods to async with Task return types
  • Replaced synchronous MongoDB queries with async equivalents
    (FirstOrDefaultAsync, InsertOneAsync, InsertManyAsync,
    ReplaceOneAsync, DeleteManyAsync)
  • Updated method signatures to use async Task instead of void for async
    operations
+15/-13 
FileRepository.AgentTask.cs
File repository agent task methods converted to async       

src/Infrastructure/BotSharp.Core/Repository/FileRepository/FileRepository.AgentTask.cs

  • Converted agent task methods to async with Task return types
  • Replaced synchronous file operations with async versions
    (File.WriteAllTextAsync)
  • Implemented BulkInsertAgentTasks method with proper async iteration
  • Wrapped synchronous results with Task.FromResult() for consistency
+19/-10 
AgentService.Coding.cs
Agent service coding methods updated for async calls         

src/Infrastructure/BotSharp.Core/Agents/Services/AgentService.Coding.cs

  • Updated method calls to use await for async database operations
  • Simplified async/await patterns by removing unnecessary
    Task.FromResult() wrappers
  • Ensured proper async chaining for code script operations
+6/-6     
FileRepository.Stats.cs
File repository stats methods converted to async                 

src/Infrastructure/BotSharp.Core/Repository/FileRepository/FileRepository.Stats.cs

  • Converted stats methods to async with Task return types
  • Replaced synchronous file I/O operations with async versions
    (File.ReadAllTextAsync, File.WriteAllTextAsync)
  • Added await keywords for all async file operations
+6/-6     
EvaluatingService.Evaluate.cs
Evaluation service methods converted to async                       

src/Infrastructure/BotSharp.Core/Evaluations/Services/EvaluatingService.Evaluate.cs

  • Converted GetInitialStates method to async with Task return type
  • Updated method calls to use await for async repository operations
  • Changed synchronous storage calls to async equivalents
+5/-5     
FileRepository.Crontab.cs
File repository crontab methods converted to async             

src/Infrastructure/BotSharp.Core/Repository/FileRepository/FileRepository.Crontab.cs

  • Converted crontab item methods to async with Task return types
  • Replaced synchronous file operations with async versions
    (File.WriteAllTextAsync, File.ReadAllTextAsync)
  • Wrapped synchronous results with Task.FromResult() for consistency
+5/-5     
ConversationStorage.cs
Conversation storage methods converted to async with semaphore

src/Infrastructure/BotSharp.Core/Conversations/Services/ConversationStorage.cs

  • Converted Append and GetDialogs methods to async with Task return
    types
  • Replaced synchronous lock mechanism with SemaphoreSlim for async-safe
    synchronization
  • Updated method calls to use await for async repository operations
+15/-10 
BotSharpConversationSideCar.cs
Conversation sidecar methods converted to async                   

src/Infrastructure/BotSharp.Core.SideCar/Services/BotSharpConversationSideCar.cs

  • Converted all conversation sidecar methods to async with Task return
    types
  • Added await Task.CompletedTask for methods that don't perform actual
    async operations
  • Updated method signatures to return Task or Task instead of void
+15/-5   
MongoRepository.Translation.cs
MongoDB translation repository methods converted to async

src/Plugins/BotSharp.Plugin.MongoStorage/Repository/MongoRepository.Translation.cs

  • Converted translation memory methods to async with Task return types
  • Replaced synchronous MongoDB operations with async counterparts
    (ToListAsync, InsertManyAsync, ReplaceOneAsync)
  • Replaced Thread.Sleep(50) with await Task.Delay(50) for async delays
+7/-7     
MongoRepository.AgentCodeScript.cs
MongoDB agent code script repository methods converted to async

src/Plugins/BotSharp.Plugin.MongoStorage/Repository/MongoRepository.AgentCodeScript.cs

  • Converted code script methods to async with Task return types
  • Replaced synchronous MongoDB operations with async counterparts
    (BulkWriteAsync, InsertManyAsync, DeleteManyAsync)
  • Added await keywords for all async database calls
+7/-7     
ConversationController.cs
Conversation controller methods updated for async calls   

src/Infrastructure/BotSharp.OpenAPI/Controllers/Conversation/ConversationController.cs

  • Updated conversation controller methods to use await for async service
    calls
  • Changed SetConversationId and GetDialogHistory calls to async with
    await
  • Ensured proper async chaining throughout the controller
+6/-6     
FileRepository.Translation.cs
File repository translation methods converted to async     

src/Infrastructure/BotSharp.Core/Repository/FileRepository/FileRepository.Translation.cs

  • Converted translation memory methods to async with Task return types
  • Replaced synchronous file I/O operations with async versions
    (File.ReadAllTextAsync, File.WriteAllTextAsync)
  • Added await keywords for all async file operations
+5/-5     
TwilioMessageQueueService.cs
Twilio message queue service methods converted to async   

src/Plugins/BotSharp.Plugin.Twilio/Services/TwilioMessageQueueService.cs

  • Converted helper methods to async with Task return types
  • Updated method calls to use await for async service operations
  • Changed synchronous calls to GetDialogHistory to async equivalents
+6/-6     
FileRepository.AgentCodeScript.cs
File repository agent code script methods converted to async

src/Infrastructure/BotSharp.Core/Repository/FileRepository/FileRepository.AgentCodeScript.cs

  • Converted code script methods to async with Task return types
  • Replaced synchronous file operations with async versions
    (File.WriteAllTextAsync)
  • Wrapped synchronous results with Task.FromResult() for consistency
+6/-6     
RealtimeHub.cs
Realtime hub methods updated for async calls                         

src/Infrastructure/BotSharp.Core.Realtime/Services/RealtimeHub.cs

  • Updated method calls to use await for async service operations
  • Changed SetConversationId, GetDialogHistory, and Append calls to async
    with await
  • Ensured proper async chaining throughout the realtime hub
+4/-4     
RoleService.cs
Role service methods updated for async calls                         

src/Infrastructure/BotSharp.Core/Roles/Services/RoleService.cs

  • Updated method calls to use await for async repository operations
  • Changed GetRoleOptions to return Task> instead of async Task
  • Ensured proper async chaining for role operations
+10/-9   
ConversationService.Migration.cs
Conversation migration service methods updated for async calls

src/Infrastructure/BotSharp.Core/Conversations/Services/ConversationService.Migration.cs

  • Updated method calls to use await for async repository operations
  • Changed synchronous database calls to async equivalents
  • Ensured proper async chaining for migration operations
+3/-3     
AgentService.GetAgents.cs
Agent service GetAgents methods updated for async calls   

src/Infrastructure/BotSharp.Core/Agents/Services/AgentService.GetAgents.cs

  • Updated method calls to use await for async repository and service
    operations
  • Changed GetAgents and GetPlugin calls to async with await
  • Ensured proper async chaining throughout the service
+5/-5     
IConversationService.cs
Conversation service interface methods converted to async

src/Infrastructure/BotSharp.Abstraction/Conversations/IConversationService.cs

  • Updated interface method signatures to return Task for async
    operations
  • Changed SetConversationId, GetDialogHistory, and SaveStates to async
  • Ensured interface consistency with async implementations
+3/-3     
PlotChartFn.cs
Chart handler function methods converted to async               

src/Plugins/BotSharp.Plugin.ChartHandler/Functions/PlotChartFn.cs

  • Converted helper method to async with Task return type
  • Updated method calls to use await for async repository and service
    operations
  • Changed GetDialogHistory and GetAgentTemplate calls to async with
    await
+5/-5     
PyProgrammerFn.cs
Python interpreter function methods converted to async     

src/Plugins/BotSharp.Plugin.PythonInterpreter/Functions/PyProgrammerFn.cs

  • Converted helper method to async with Task return type
  • Updated method calls to use await for async repository and service
    operations
  • Changed GetDialogHistory and GetAgentTemplate calls to async with
    await
+5/-5     
AgentTaskService.cs
Agent task service methods updated for async calls             

src/Infrastructure/BotSharp.Core/Tasks/Services/AgentTaskService.cs

  • Updated method calls to use await for async repository operations
  • Simplified async/await patterns by removing unnecessary
    Task.FromResult() wrappers
  • Ensured proper async chaining for task operations
+6/-8     
PluginController.cs
Plugin controller methods updated for async calls               

src/Infrastructure/BotSharp.OpenAPI/Controllers/Setting/PluginController.cs

  • Updated controller methods to use await for async plugin loader
    operations
  • Changed GetPagedPlugins and GetPlugins calls to async with await
  • Ensured proper async chaining throughout the controller
+7/-6     
SqlDriverController.cs
SQL driver controller methods updated for async calls       

src/Plugins/BotSharp.Plugin.SqlDriver/Controllers/SqlDriverController.cs

  • Updated controller methods to use await for async service operations
  • Changed SetConversationId and Append calls to async with await
  • Ensured proper async chaining throughout the controller
+4/-4     
MongoRepository.Crontab.cs
MongoDB crontab repository methods converted to async       

src/Plugins/BotSharp.Plugin.MongoStorage/Repository/MongoRepository.Crontab.cs

  • Converted crontab item methods to async with Task return types
  • Replaced synchronous MongoDB operations with async counterparts
    (ReplaceOneAsync, DeleteManyAsync)
  • Added await keywords for all async database calls
+4/-4     
TokenStatsConversationHook.cs
Convert AddToken to async method call                                       

src/Infrastructure/BotSharp.Logger/Hooks/TokenStatsConversationHook.cs

  • Changed AddToken method call to use await keyword, making it properly
    asynchronous
  • Removed unnecessary await Task.CompletedTask statement
  • Simplified the async method to directly await the token addition
    operation
+1/-2     
ReadImageFn.cs
Add await to GetDialogHistory async call                                 

src/Plugins/BotSharp.Plugin.ImageHandler/Functions/ReadImageFn.cs

  • Added await keyword to GetDialogHistory() method call
  • Ensures proper asynchronous handling of dialog history retrieval
+1/-1     
AgentService.cs
Add await to GetUserAgents database call                                 

src/Infrastructure/BotSharp.Core/Agents/Services/AgentService.cs

  • Added await keyword to _db.GetUserAgents() method call
  • Properly awaits the asynchronous database operation
+1/-1     
ReadPdfFn.cs
Add await to GetDialogHistory async call                                 

src/Plugins/BotSharp.Plugin.FileHandler/Functions/ReadPdfFn.cs

  • Added await keyword to GetDialogHistory() method call
  • Ensures proper asynchronous handling of dialog history retrieval
+1/-1     
ReadAudioFn.cs
Add await to GetDialogHistory async call                                 

src/Plugins/BotSharp.Plugin.AudioHandler/Functions/ReadAudioFn.cs

  • Added await keyword to GetDialogHistory() method call
  • Ensures proper asynchronous handling of dialog history retrieval
+1/-1     
ReadExcelFn.cs
Add await to GetDialogHistory async call                                 

src/Plugins/BotSharp.Plugin.ExcelHandler/Functions/ReadExcelFn.cs

  • Added await keyword to GetDialogHistory() method call
  • Ensures proper asynchronous handling of dialog history retrieval
+1/-1     
WelcomeHook.cs
Add await to storage Append async call                                     

src/Plugins/BotSharp.Plugin.ChatHub/Hooks/WelcomeHook.cs

  • Added await keyword to _storage.Append() method call
  • Properly awaits the asynchronous storage operation
+1/-1     
WebIntelligentSearchFn.cs
Add await to GetDialogHistory async call                                 

src/Infrastructure/BotSharp.Core/WebSearch/Functions/WebIntelligentSearchFn.cs

  • Added await keyword to GetDialogHistory() method call
  • Ensures proper asynchronous handling of dialog history retrieval
+1/-1     
IBotSharpStatsService.cs
Convert UpdateStats interface to async method                       

src/Infrastructure/BotSharp.Abstraction/Statistics/Services/IBotSharpStatsService.cs

  • Changed UpdateStats method signature to return Task instead of bool
  • Converts the method to be asynchronous
+1/-1     
ISettingService.cs
Convert GetDetail interface to async method                           

src/Infrastructure/BotSharp.Abstraction/Settings/ISettingService.cs

  • Changed GetDetail method signature to return Task instead of object
  • Converts the method to be asynchronous
+1/-1     
TwilioConversationHook.cs
Add await to states Save async call                                           

src/Plugins/BotSharp.Plugin.Twilio/Hooks/TwilioConversationHook.cs

  • Added await keyword to states.Save() method call
  • Properly awaits the asynchronous save operation
+1/-1     
Additional files
61 files
IAgentService.cs +1/-1     
IConversationStateService.cs +2/-2     
IConversationStorage.cs +3/-3     
ITokenStatistics.cs +1/-1     
IConversationSideCar.cs +5/-5     
ScheduleTaskFn.cs +2/-2     
RuleEngine.cs +2/-2     
AgentService.CreateAgent.cs +3/-3     
AgentService.DeleteAgent.cs +2/-2     
AgentService.GetPlugin.cs +2/-2     
AgentService.RefreshAgents.cs +4/-4     
AgentService.UpdateAgent.cs +2/-2     
ConversationService.SendMessage.cs +2/-2     
ConversationService.Summary.cs +1/-1     
ConversationService.TruncateMessage.cs +1/-1     
ConversationService.UpdateBreakpoint.cs +1/-1     
TokenStatistics.cs +2/-2     
EvaluatingService.cs +1/-1     
FileInstructService.SelectFile.cs +2/-2     
SettingService.cs +2/-2     
InstructService.Instruct.cs +3/-3     
LoggerService.Conversation.cs +4/-4     
ConversationObserver.cs +1/-1     
FileRepository.Plugin.cs +4/-4     
FileRepository.User.cs +4/-4     
RouteToAgentFn.cs +2/-2     
HFReasoner.cs +1/-1     
RoutingService.InstructLoop.cs +1/-1     
RoutingService.cs +3/-3     
BotSharpStatsService.cs +2/-2     
ResponseTemplateService.cs +2/-2     
TranslationService.cs +2/-2     
UserService.Token.cs +2/-2     
UserService.cs +4/-2     
CommonContentGeneratingHook.cs +3/-4     
ConversationController.File.cs +1/-1     
SettingController.cs +4/-4     
ChatStreamMiddleware.cs +2/-2     
ChatbotUiController.cs +1/-1     
HandleEmailReaderFn.cs +1/-1     
KnowledgeService.Common.cs +2/-2     
KnowledgeService.Document.cs +2/-2     
KnowledgeService.Vector.cs +3/-3     
MessageHandleService.cs +1/-1     
MongoRepository.Plugin.cs +4/-4     
MongoRepository.Stats.cs +4/-4     
MongoRepository.User.cs +1/-1     
SqlGenerationFn.cs +1/-1     
SqlPrimaryStageFn.cs +1/-1     
SqlSecondaryStageFn.cs +1/-1     
PrimaryStagePlanFn.cs +1/-1     
SecondaryStagePlanFn.cs +1/-1     
SummaryPlanFn.cs +1/-1     
SqlDriverCrontabHook.cs +1/-1     
SqlChartProcessor.cs +1/-1     
TwilioInboundController.cs +3/-3     
TwilioRecordController.cs +2/-2     
TwilioTranscribeController.cs +2/-2     
OutboundPhoneCallFn.cs +2/-2     
TwilioStreamMiddleware.cs +4/-4     
WeChatBackgroundService.cs +1/-1     

@qodo-code-review
Copy link

qodo-code-review bot commented Dec 29, 2025

PR Compliance Guide 🔍

Below is a summary of compliance checks for this PR:

Security Compliance
Path traversal

Description: Multiple new/modified async code paths build file system paths using potentially
user-controlled identifiers (e.g., agentId, templateName, ci.Channel, func.Name) in
Path.Combine(...) and then read/write/delete via File.*Async (e.g., writing
instruction/template/response files and reading templates/responses), which could permit
path traversal or arbitrary file overwrite/read if those inputs are not strictly
validated/sanitized elsewhere (e.g., values containing .., directory separators, or
absolute paths). FileRepository.Agent.cs [365-792]

Referred Code
    // Save default instructions
    var instructionFile = Path.Combine(instructionDir, $"{AGENT_INSTRUCTION_FILE}.{_agentSettings.TemplateFormat}");
    await File.WriteAllTextAsync(instructionFile, instruction ?? string.Empty);
    await Task.Delay(50);

    // Save channel instructions
    foreach (var ci in channelInstructions)
    {
        if (string.IsNullOrWhiteSpace(ci.Channel))
        {
            continue;
        }

        var file = Path.Combine(instructionDir, $"{AGENT_INSTRUCTION_FILE}.{ci.Channel}.{_agentSettings.TemplateFormat}");
        await File.WriteAllTextAsync(file, ci.Instruction ?? string.Empty);
        await Task.Delay(50);
    }
}

private async Task UpdateAgentFunctions(string agentId, List<FunctionDef> inputFunctions)


 ... (clipped 407 lines)
Ticket Compliance
🎫 No ticket provided
  • Create ticket/issue
Codebase Duplication Compliance
Codebase context is not defined

Follow the guide to enable codebase context checks.

Custom Compliance
🟢
Generic: Secure Error Handling

Objective: To prevent the leakage of sensitive system information through error messages while
providing sufficient detail for internal debugging.

Status: Passed

Learn more about managing compliance generic rules or creating your own custom rules

Generic: Secure Logging Practices

Objective: To ensure logs are useful for debugging and auditing without exposing sensitive
information like PII, PHI, or cardholder data.

Status: Passed

Learn more about managing compliance generic rules or creating your own custom rules

🔴
Generic: Meaningful Naming and Self-Documenting Code

Objective: Ensure all identifiers clearly express their purpose and intent, making code
self-documenting

Status:
Misspelled method name: The newly async method name MigrateConvsersationLatestStates contains a spelling error,
reducing readability and self-documentation.

Referred Code
public async Task<bool> MigrateConvsersationLatestStates(string conversationId)
{
    if (string.IsNullOrEmpty(conversationId))
    {
        return false;
    }

    var convDir = FindConversationDirectory(conversationId);
    if (string.IsNullOrEmpty(convDir))
    {
        return false;
    }

    var stateFile = Path.Combine(convDir, STATE_FILE);
    var states = await CollectConversationStates(stateFile);
    var latestStates = BuildLatestStates(states);

    var latestStateFile = Path.Combine(convDir, CONV_LATEST_STATE_FILE);
    var stateStr = JsonSerializer.Serialize(latestStates, _options);
    await File.WriteAllTextAsync(latestStateFile, stateStr);
    return true;


 ... (clipped 1 lines)

Learn more about managing compliance generic rules or creating your own custom rules

Generic: Robust Error Handling and Edge Case Management

Objective: Ensure comprehensive error handling that provides meaningful context and graceful
degradation

Status:
Unhandled file I/O: New async file reads/writes (e.g., reading convFile directly) are performed without
existence checks or exception handling, which can throw and fail without actionable
context.

Referred Code
public async Task UpdateConversationTitle(string conversationId, string title)
{
    var convDir = FindConversationDirectory(conversationId);
    if (!string.IsNullOrEmpty(convDir))
    {
        var convFile = Path.Combine(convDir, CONVERSATION_FILE);
        var content = await File.ReadAllTextAsync(convFile);
        var record = JsonSerializer.Deserialize<Conversation>(content, _options);
        if (record != null)
        {
            record.Title = title;
            record.UpdatedTime = DateTime.UtcNow;
            await File.WriteAllTextAsync(convFile, JsonSerializer.Serialize(record, _options));
        }
    }
}
public async Task UpdateConversationTitleAlias(string conversationId, string titleAlias)
{
    var convDir = FindConversationDirectory(conversationId);
    if (!string.IsNullOrEmpty(convDir))
    {


 ... (clipped 41 lines)

Learn more about managing compliance generic rules or creating your own custom rules

Generic: Comprehensive Audit Trails

Objective: To create a detailed and reliable record of critical system actions for security analysis
and compliance.

Status:
Missing audit logs: Critical destructive operations (bulk delete of agent-related collections and per-agent
deletes) are executed without any visible audit logging of actor, action, and outcome in
the changed code.

Referred Code
public async Task<bool> DeleteAgents()
{
    try
    {
        await _dc.UserAgents.DeleteManyAsync(Builders<UserAgentDocument>.Filter.Empty);
        await _dc.RoleAgents.DeleteManyAsync(Builders<RoleAgentDocument>.Filter.Empty);
        await _dc.AgentTasks.DeleteManyAsync(Builders<AgentTaskDocument>.Filter.Empty);
        await _dc.AgentCodeScripts.DeleteManyAsync(Builders<AgentCodeScriptDocument>.Filter.Empty);
        await _dc.Agents.DeleteManyAsync(Builders<AgentDocument>.Filter.Empty);
        return true;
    }
    catch



 ... (clipped 25 lines)

Learn more about managing compliance generic rules or creating your own custom rules

Generic: Security-First Input Validation and Data Handling

Objective: Ensure all data inputs are validated, sanitized, and handled securely to prevent
vulnerabilities

Status:
Path input hardening: The changed async code continues to build filesystem paths directly from external
identifiers (e.g., agentId) without visible normalization/validation, which may require
verification against upstream constraints to prevent path traversal risks.

Referred Code
    // Save default instructions
    var instructionFile = Path.Combine(instructionDir, $"{AGENT_INSTRUCTION_FILE}.{_agentSettings.TemplateFormat}");
    await File.WriteAllTextAsync(instructionFile, instruction ?? string.Empty);
    await Task.Delay(50);

    // Save channel instructions
    foreach (var ci in channelInstructions)
    {
        if (string.IsNullOrWhiteSpace(ci.Channel))
        {
            continue;
        }

        var file = Path.Combine(instructionDir, $"{AGENT_INSTRUCTION_FILE}.{ci.Channel}.{_agentSettings.TemplateFormat}");
        await File.WriteAllTextAsync(file, ci.Instruction ?? string.Empty);
        await Task.Delay(50);
    }
}

private async Task UpdateAgentFunctions(string agentId, List<FunctionDef> inputFunctions)


 ... (clipped 157 lines)

Learn more about managing compliance generic rules or creating your own custom rules

  • Update
Compliance status legend 🟢 - Fully Compliant
🟡 - Partial Compliant
🔴 - Not Compliant
⚪ - Requires Further Human Verification
🏷️ - Compliance label

@qodo-code-review
Copy link

qodo-code-review bot commented Dec 29, 2025

PR Code Suggestions ✨

Explore these optional code suggestions:

CategorySuggestion                                                                                                                                    Impact
Possible issue
Fix list initializer syntax

Fix a compilation error by replacing the JavaScript-style empty array literal []
with the correct C# syntax, new List().

src/Infrastructure/BotSharp.Core/Conversations/Services/ConversationService.cs [245]

-filter.UserIds = !isAdmin && user?.Id != null ? [user.Id] : [];
+filter.UserIds = !isAdmin && user?.Id != null
+    ? new List<string> { user.Id }
+    : new List<string>();
  • Apply / Chat
Suggestion importance[1-10]: 10

__

Why: The suggestion correctly identifies and fixes invalid C# syntax ([] for an empty list) that was introduced in the PR and would cause a compilation error.

High
Avoid blocking on async in Dispose

Avoid blocking on an async method in Dispose by removing the call to Save().
This prevents a "sync over async" anti-pattern that can cause deadlocks.

src/Infrastructure/BotSharp.Core/Conversations/Services/ConversationStateService.cs [367-370]

 public void Dispose()
 {
-    Save().ConfigureAwait(false).GetAwaiter().GetResult();
+    // Avoid calling async methods from Dispose.
+    // The Save() method should be called and awaited
+    // by the consumer of this service before disposal.
 }
  • Apply / Chat
Suggestion importance[1-10]: 9

__

Why: The suggestion correctly identifies a "sync over async" anti-pattern in the Dispose method, which was introduced in the PR and can cause application deadlocks.

High
Avoid blocking on async database calls

Fix a "sync over async" anti-pattern by converting GetRoutingRecords and
GetRoutableAgents to async methods and using await for the db.GetAgents call to
prevent deadlocks.

src/Infrastructure/BotSharp.Core/Routing/RoutingService.cs [81]

-var agents = db.GetAgents(filter).ConfigureAwait(false).GetAwaiter().GetResult();
+var agents = await db.GetAgents(filter);

[To ensure code accuracy, apply this suggestion manually]

Suggestion importance[1-10]: 9

__

Why: The suggestion correctly identifies a critical "sync over async" anti-pattern introduced in the PR that can cause deadlocks, and it provides the correct fix.

High
Use await instead of blocking calls

Replace the blocking .ConfigureAwait(false).GetAwaiter().GetResult() call with
await inside the SetState and RemoveState methods to prevent potential
deadlocks.

src/Infrastructure/BotSharp.Core/Conversations/Services/ConversationStateService.cs [119]

 // inside SetState<T> and RemoveState
-... 
-    }).ConfigureAwait(false).GetAwaiter().GetResult();
+...
+    await hook.OnStateAdded(leafNode.DataType, leafNode.Source, leafNode.Readonly);

[To ensure code accuracy, apply this suggestion manually]

Suggestion importance[1-10]: 9

__

Why: The suggestion correctly identifies a critical "sync over async" anti-pattern introduced in the PR that can cause deadlocks, and it provides the correct fix.

High
Avoid blocking calls in observer

Replace the blocking .GetAwaiter().GetResult() call in the OnNext method with
Task.Run to execute the asynchronous storage.Append operation in the background,
avoiding potential deadlocks.

src/Infrastructure/BotSharp.Core/MessageHub/Observers/ConversationObserver.cs [53]

-storage.Append(conv.ConversationId, value.Data).ConfigureAwait(false).GetAwaiter().GetResult();
+_ = Task.Run(() => storage.Append(conv.ConversationId, value.Data));
  • Apply / Chat
Suggestion importance[1-10]: 9

__

Why: The suggestion correctly identifies a "sync over async" call (.GetAwaiter().GetResult()) introduced in the PR, which can cause deadlocks, and provides a valid solution using Task.Run to prevent blocking the observer thread.

High
Use aggregation pipeline for efficient joins

Replace the inefficient client-side LINQ join in GetUserAgents with a more
performant server-side MongoDB aggregation pipeline using $lookup.

src/Plugins/BotSharp.Plugin.MongoStorage/Repository/MongoRepository.Agent.cs [505-541]

 public async Task<List<UserAgent>> GetUserAgents(string userId)
 {
-    var found = await (from ua in _dc.UserAgents.AsQueryable()
-                 join u in _dc.Users.AsQueryable() on ua.UserId equals u.Id
-                 where ua.UserId == userId || u.ExternalId == userId
-                 select ua).ToListAsync();
+    var userAgentCollection = _dc.GetCollection<UserAgentDocument>("UserAgent");
+    var userCollection = _dc.GetCollection<UserDocument>("User");
+
+    var found = await userAgentCollection.Aggregate()
+        .Lookup(
+            foreignCollection: userCollection,
+            localField: "UserId",
+            foreignField: "_id",
+            @as: "Users"
+        )
+        .Match(new BsonDocument
+        {
+            { "$or", new BsonArray
+                {
+                    new BsonDocument("UserId", userId),
+                    new BsonDocument("Users.ExternalId", userId)
+                }
+            }
+        })
+        .As<UserAgentDocument>()
+        .ToListAsync();
 
     if (found.IsNullOrEmpty())
     {
         return new List<UserAgent>();
     }
 
     var res = found.Select(ua => new UserAgent
     {
         Id = ua.Id,
         UserId = ua.UserId,
         AgentId = ua.AgentId,
         CreatedTime = ua.CreatedTime,
         UpdatedTime = ua.UpdatedTime
     }).ToList();
 
     var agentIds = found.Select(x => x.AgentId).Distinct().ToList();
     var agents = await GetAgents(new AgentFilter { AgentIds = agentIds });
     foreach (var item in res)
     {
         var agent = agents.FirstOrDefault(x => x.Id == item.AgentId);
         if (agent == null) continue;
 
         item.Editable = agent.IsPublic;
         item.Agent = agent;
     }
 
     return res;
 }

[To ensure code accuracy, apply this suggestion manually]

Suggestion importance[1-10]: 8

__

Why: The suggestion correctly points out a significant performance issue with client-side joins and proposes using a server-side aggregation pipeline ($lookup), which is the standard and much more efficient way to perform joins in MongoDB.

Medium
General
Add semaphore to GetDialogs

To prevent race conditions, wrap the logic inside the GetDialogs method with the
same SemaphoreSlim used in the Append method, ensuring thread-safe read/write
access.

src/Infrastructure/BotSharp.Core/Conversations/Services/ConversationStorage.cs [53-104]

 public async Task<List<RoleDialogModel>> GetDialogs(string conversationId)
 {
-    var db = _services.GetRequiredService<IBotSharpRepository>();
-    var dialogs = await db.GetConversationDialogs(conversationId);
-    ...
-    return results;
+    await _semaphore.WaitAsync();
+    try
+    {
+        var db = _services.GetRequiredService<IBotSharpRepository>();
+        var dialogs = await db.GetConversationDialogs(conversationId);
+        ...
+        return results;
+    }
+    finally
+    {
+        _semaphore.Release();
+    }
 }

[To ensure code accuracy, apply this suggestion manually]

Suggestion importance[1-10]: 8

__

Why: The suggestion correctly points out a potential race condition where GetDialogs could read data while Append is writing, and proposes using the existing semaphore to ensure thread safety.

Medium
Simplify logic by removing unnecessary loop
Suggestion Impact:The commit refactored/simplified the query logic inside the existing loop by removing the verbose MongoDB FilterBuilder/orFilters construction and replacing it with a single LINQ predicate. However, it did not implement the core part of the suggestion: the while loop and pagination (Skip/Take) remain.

code diff:

@@ -560,45 +561,14 @@
         while (true)
         {
             var skip = (page - 1) * batchSize;
-            var builder = Builders<ConversationDocument>.Filter;
-            var filters = new List<FilterDefinition<ConversationDocument>>();
-
-            // Build the OR condition: (!excludeAgentIds.Contains(AgentId) && DialogCount <= messageLimit) 
-            //                        || (excludeAgentIds.Contains(AgentId) && DialogCount == 0)
-            var orFilters = new List<FilterDefinition<ConversationDocument>>();
-            
-            // First condition: !excludeAgentIds.Contains(AgentId) && DialogCount <= messageLimit
-            if (excludeAgentIdsList.Any())
-            {
-                orFilters.Add(builder.And(
-                    builder.Nin(x => x.AgentId, excludeAgentIdsList),
-                    builder.Lte(x => x.DialogCount, messageLimit)
-                ));
-            }
-            else
-            {
-                // If excludeAgentIds is empty, all agents match the first condition
-                orFilters.Add(builder.Lte(x => x.DialogCount, messageLimit));
-            }
-
-            // Second condition: excludeAgentIds.Contains(AgentId) && DialogCount == 0
-            if (excludeAgentIdsList.Any())
-            {
-                orFilters.Add(builder.And(
-                    builder.In(x => x.AgentId, excludeAgentIdsList),
-                    builder.Eq(x => x.DialogCount, 0)
-                ));
-            }
-
-            filters.Add(builder.Or(orFilters));
-            filters.Add(builder.Lte(x => x.UpdatedTime, utcNow.AddHours(-bufferHours)));
-
-            var filter = builder.And(filters);
-            var candidates = await _dc.Conversations.Find(filter)
-                                                    .Skip(skip)
-                                                    .Limit(batchSize)
-                                                    .Project(x => x.Id)
-                                                    .ToListAsync();
+            var candidates = await _dc.Conversations.AsQueryable()
+                                              .Where(x => ((!excludeAgentIds.Contains(x.AgentId) && x.DialogCount <= messageLimit)
+                                                       || (excludeAgentIds.Contains(x.AgentId) && x.DialogCount == 0))
+                                                        && x.UpdatedTime <= utcNow.AddHours(-bufferHours))
+                                              .Skip(skip)
+                                              .Take(batchSize)
+                                              .Select(x => x.Id)
+                                              .ToListAsync();

Simplify the GetIdleConversations method by removing the unnecessary while loop
and pagination logic, using a single database query with Limit instead.

src/Plugins/BotSharp.Plugin.MongoStorage/Repository/MongoRepository.Conversation.cs [547-618]

 public async Task<List<string>> GetIdleConversations(int batchSize, int messageLimit, int bufferHours, IEnumerable<string> excludeAgentIds)
 {
-    var page = 1;
     var batchLimit = 100;
     var utcNow = DateTime.UtcNow;
-    var conversationIds = new List<string>();
     var excludeAgentIdsList = excludeAgentIds?.ToList() ?? new List<string>();
 
     if (batchSize <= 0 || batchSize > batchLimit)
     {
         batchSize = batchLimit;
     }
 
-    while (true)
+    var builder = Builders<ConversationDocument>.Filter;
+    var filters = new List<FilterDefinition<ConversationDocument>>();
+
+    // Build the OR condition: (!excludeAgentIds.Contains(AgentId) && DialogCount <= messageLimit) 
+    //                        || (excludeAgentIds.Contains(AgentId) && DialogCount == 0)
+    var orFilters = new List<FilterDefinition<ConversationDocument>>();
+    
+    // First condition: !excludeAgentIds.Contains(AgentId) && DialogCount <= messageLimit
+    if (excludeAgentIdsList.Any())
     {
-        var skip = (page - 1) * batchSize;
-        var builder = Builders<ConversationDocument>.Filter;
-        var filters = new List<FilterDefinition<ConversationDocument>>();
-
-        // Build the OR condition: (!excludeAgentIds.Contains(AgentId) && DialogCount <= messageLimit) 
-        //                        || (excludeAgentIds.Contains(AgentId) && DialogCount == 0)
-        var orFilters = new List<FilterDefinition<ConversationDocument>>();
-        
-        // First condition: !excludeAgentIds.Contains(AgentId) && DialogCount <= messageLimit
-        if (excludeAgentIdsList.Any())
-        {
-            orFilters.Add(builder.And(
-                builder.Nin(x => x.AgentId, excludeAgentIdsList),
-                builder.Lte(x => x.DialogCount, messageLimit)
-            ));
-        }
-        else
-        {
-            // If excludeAgentIds is empty, all agents match the first condition
-            orFilters.Add(builder.Lte(x => x.DialogCount, messageLimit));
-        }
-
-        // Second condition: excludeAgentIds.Contains(AgentId) && DialogCount == 0
-        if (excludeAgentIdsList.Any())
-        {
-            orFilters.Add(builder.And(
-                builder.In(x => x.AgentId, excludeAgentIdsList),
-                builder.Eq(x => x.DialogCount, 0)
-            ));
-        }
-
-        filters.Add(builder.Or(orFilters));
-        filters.Add(builder.Lte(x => x.UpdatedTime, utcNow.AddHours(-bufferHours)));
-
-        var filter = builder.And(filters);
-        var candidates = await _dc.Conversations.Find(filter)
-                                                .Skip(skip)
-                                                .Limit(batchSize)
-                                                .Project(x => x.Id)
-                                                .ToListAsync();
-
-        if (candidates.IsNullOrEmpty())
-        {
-            break;
-        }
-
-        conversationIds.AddRange(candidates);
-        if (conversationIds.Count >= batchSize)
-        {
-            break;
-        }
-        page++;
+        orFilters.Add(builder.And(
+            builder.Nin(x => x.AgentId, excludeAgentIdsList),
+            builder.Lte(x => x.DialogCount, messageLimit)
+        ));
+    }
+    else
+    {
+        // If excludeAgentIds is empty, all agents match the first condition
+        orFilters.Add(builder.Lte(x => x.DialogCount, messageLimit));
     }
 
-    return conversationIds.Take(batchSize).ToList();
+    // Second condition: excludeAgentIds.Contains(AgentId) && DialogCount == 0
+    if (excludeAgentIdsList.Any())
+    {
+        orFilters.Add(builder.And(
+            builder.In(x => x.AgentId, excludeAgentIdsList),
+            builder.Eq(x => x.DialogCount, 0)
+        ));
+    }
+
+    filters.Add(builder.Or(orFilters));
+    filters.Add(builder.Lte(x => x.UpdatedTime, utcNow.AddHours(-bufferHours)));
+
+    var filter = builder.And(filters);
+    var conversationIds = await _dc.Conversations.Find(filter)
+                                            .Limit(batchSize)
+                                            .Project(x => x.Id)
+                                            .ToListAsync();
+
+    return conversationIds;
 }

[To ensure code accuracy, apply this suggestion manually]

Suggestion importance[1-10]: 7

__

Why: The suggestion correctly identifies that the while loop is unnecessary and inefficient, replacing multiple potential database queries with a single, simpler query that achieves the same result.

Medium
Avoid capturing context

Add .ConfigureAwait(false) to all asynchronous file and I/O operations to avoid
capturing the synchronization context and reduce the risk of deadlocks.

src/Infrastructure/BotSharp.Core/Repository/FileRepository/FileRepository.Conversation.cs [135]

-await File.WriteAllTextAsync(dialogFile, JsonSerializer.Serialize(elements, _options));
+await File.WriteAllTextAsync(dialogFile, JsonSerializer.Serialize(elements, _options))
+      .ConfigureAwait(false);
  • Apply / Chat
Suggestion importance[1-10]: 6

__

Why: The suggestion provides a best practice for library code to prevent potential deadlocks in consuming applications by using .ConfigureAwait(false) on async I/O operations.

Low
Execute independent async tasks concurrently

Improve performance by executing independent OnStateLoaded hook calls
concurrently using Task.WhenAll instead of awaiting them sequentially in a loop.

src/Infrastructure/BotSharp.Core/Conversations/Services/ConversationStateService.cs [224-228]

 var hooks = _services.GetHooks<IConversationHook>(_routingContext.GetCurrentAgentId());
-foreach (var hook in hooks)
-{
-    await hook.OnStateLoaded(_curStates);
-}
+var tasks = hooks.Select(hook => hook.OnStateLoaded(_curStates));
+await Task.WhenAll(tasks);
  • Apply / Chat
Suggestion importance[1-10]: 6

__

Why: The suggestion proposes a valid performance optimization by running independent hook operations concurrently using Task.WhenAll, which is a good practice for improving asynchronous code.

Low
Learned
best practice
Guard semaphore release with flag

Guard _dialogLock.Release() with an acquired flag so the semaphore is released
only if WaitAsync() succeeded, preventing incorrect releases on exceptions
before acquisition.

src/Infrastructure/BotSharp.Core/Repository/FileRepository/FileRepository.Conversation.cs [85-106]

-await _dialogLock.WaitAsync();
+var acquired = false;
 try
 {
+    await _dialogLock.WaitAsync();
+    acquired = true;
+
     if (!File.Exists(dialogDir))
     {
         return dialogs;
     }
 
     var texts = await File.ReadAllTextAsync(dialogDir);
     try
     {
         dialogs = JsonSerializer.Deserialize<List<DialogElement>>(texts, _options) ?? new List<DialogElement>();
     }
     catch
     {
         dialogs = new List<DialogElement>();
     }
 }
 finally
 {
-    _dialogLock.Release();
+    if (acquired) _dialogLock.Release();
 }
  • Apply / Chat
Suggestion importance[1-10]: 6

__

Why:
Relevant best practice - Ensure semaphores are only released when successfully acquired (use an acquired flag in a try/finally) to avoid double-release or releasing without acquisition on exceptions.

Low
  • Update

@Oceania2018 Oceania2018 requested a review from iceljc December 29, 2025 14:07
@adenchen123
Copy link
Contributor

Reviewed

@yileicn yileicn merged commit 8d9360b into SciSharp:master Dec 30, 2025
1 of 4 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants