diff --git a/src/Infrastructure/BotSharp.Abstraction/Crontab/ICrontabHook.cs b/src/Infrastructure/BotSharp.Abstraction/Crontab/ICrontabHook.cs
index 94284150f..33c64c8c5 100644
--- a/src/Infrastructure/BotSharp.Abstraction/Crontab/ICrontabHook.cs
+++ b/src/Infrastructure/BotSharp.Abstraction/Crontab/ICrontabHook.cs
@@ -7,6 +7,10 @@ public interface ICrontabHook : IHookBase
string[]? Triggers
=> null;
+ ///
+ /// Establishes the identity for the cron event; invoked synchronously for all hooks before any OnCronTriggered runs, so the identity flows down into the execution phase.
+ ///
+ ///
void OnAuthenticate(CrontabItem item) { }
Task OnCronTriggered(CrontabItem item)
diff --git a/src/Infrastructure/BotSharp.Abstraction/Models/Gpt4xModelConstants.cs b/src/Infrastructure/BotSharp.Abstraction/Models/Gpt4xModelConstants.cs
index a812ee02d..8919b743c 100644
--- a/src/Infrastructure/BotSharp.Abstraction/Models/Gpt4xModelConstants.cs
+++ b/src/Infrastructure/BotSharp.Abstraction/Models/Gpt4xModelConstants.cs
@@ -4,7 +4,6 @@ public static class Gpt4xModelConstants
{
public const string GPT_4o = "gpt-4o";
public const string GPT_4o_Mini = "gpt-4o-mini";
- public const string GPT_4o_Mini_Realtime_Preview = "gpt-4o-mini-realtime-preview";
public const string GPT_4o_Mini_Search_Preview = "gpt-4o-mini-search-preview";
public const string GPT_4o_Mini_Transcribe = "gpt-4o-mini-transcribe";
public const string GPT_4o_Transcribe = "gpt-4o-transcribe";
diff --git a/src/Plugins/BotSharp.Plugin.OpenAI/Providers/Realtime/RealTimeCompletionProvider.cs b/src/Plugins/BotSharp.Plugin.OpenAI/Providers/Realtime/RealTimeCompletionProvider.cs
index 30119236b..40d9a0633 100644
--- a/src/Plugins/BotSharp.Plugin.OpenAI/Providers/Realtime/RealTimeCompletionProvider.cs
+++ b/src/Plugins/BotSharp.Plugin.OpenAI/Providers/Realtime/RealTimeCompletionProvider.cs
@@ -41,8 +41,6 @@ public RealTimeCompletionProvider(
_logger = logger;
_services = services;
_botsharpOptions = botsharpOptions;
-
- var settingService = _services.GetRequiredService();
}
public async Task Connect(
diff --git a/src/Plugins/BotSharp.Plugin.Twilio/Controllers/TwilioInboundController.cs b/src/Plugins/BotSharp.Plugin.Twilio/Controllers/TwilioInboundController.cs
index 40ba7d80d..5006a26f0 100644
--- a/src/Plugins/BotSharp.Plugin.Twilio/Controllers/TwilioInboundController.cs
+++ b/src/Plugins/BotSharp.Plugin.Twilio/Controllers/TwilioInboundController.cs
@@ -53,6 +53,9 @@ public async Task InitiateStreamConversation(ConversationalVoiceReq
instruction.SpeechPaths.Add(request.InitAudioFile);
}
+ // Authenticate (synchronous, so identity writes survive into the async session hooks)
+ HookEmitter.Emit(_services, hook => hook.OnAuthenticate(request), request.AgentId);
+
// Before creating session
await HookEmitter.Emit(_services, async hook =>
{
@@ -76,7 +79,9 @@ await HookEmitter.Emit(_services, async hook =>
if (twilio.MachineDetected(request))
{
response = new VoiceResponse();
- await HookEmitter.Emit(_services,
+ // Authenticate (synchronous, so identity writes survive into the async status hook)
+ HookEmitter.Emit(_services, hook => hook.OnAuthenticate(request), request.AgentId);
+ await HookEmitter.Emit(_services,
async hook => await hook.OnVoicemailStarting(request), request.AgentId);
var url = twilio.GetSpeechPath(request.ConversationId, "voicemail.mp3");
diff --git a/src/Plugins/BotSharp.Plugin.Twilio/Controllers/TwilioOutboundController.cs b/src/Plugins/BotSharp.Plugin.Twilio/Controllers/TwilioOutboundController.cs
index 5543af38a..f355d77ce 100644
--- a/src/Plugins/BotSharp.Plugin.Twilio/Controllers/TwilioOutboundController.cs
+++ b/src/Plugins/BotSharp.Plugin.Twilio/Controllers/TwilioOutboundController.cs
@@ -33,7 +33,9 @@ public async Task InitiateOutboundCall(ConversationalVoiceRequest r
if (twilio.MachineDetected(request))
{
response = new VoiceResponse();
- await HookEmitter.Emit(_services,
+ // Authenticate (synchronous, so identity writes survive into the async status hook)
+ HookEmitter.Emit(_services, hook => hook.OnAuthenticate(request), request.AgentId);
+ await HookEmitter.Emit(_services,
async hook => await hook.OnVoicemailStarting(request), request.AgentId);
var url = twilio.GetSpeechPath(request.ConversationId, "voicemail.mp3");
diff --git a/src/Plugins/BotSharp.Plugin.Twilio/Controllers/TwilioRecordController.cs b/src/Plugins/BotSharp.Plugin.Twilio/Controllers/TwilioRecordController.cs
index c6690d84c..bb96d9d8e 100644
--- a/src/Plugins/BotSharp.Plugin.Twilio/Controllers/TwilioRecordController.cs
+++ b/src/Plugins/BotSharp.Plugin.Twilio/Controllers/TwilioRecordController.cs
@@ -36,6 +36,7 @@ public async Task PhoneRecordingStatus(ConversationalVoiceRequest
});
await convService.SaveStates();
+ HookEmitter.Emit(_services, x => x.OnAuthenticate(request), request.AgentId);
// recording completed
await HookEmitter.Emit(_services, x => x.OnRecordingCompleted(request), request.AgentId);
}
diff --git a/src/Plugins/BotSharp.Plugin.Twilio/Controllers/TwilioVoiceController.cs b/src/Plugins/BotSharp.Plugin.Twilio/Controllers/TwilioVoiceController.cs
index 786f116cd..5dc53b375 100644
--- a/src/Plugins/BotSharp.Plugin.Twilio/Controllers/TwilioVoiceController.cs
+++ b/src/Plugins/BotSharp.Plugin.Twilio/Controllers/TwilioVoiceController.cs
@@ -62,6 +62,9 @@ public async Task InitiateConversation(ConversationalVoiceRequest r
ActionOnEmptyResult = true
};
+ // Authenticate (synchronous, so identity writes survive into the async session hooks)
+ HookEmitter.Emit(_services, hook => hook.OnAuthenticate(request), request.AgentId);
+
await HookEmitter.Emit(_services, async hook =>
{
await hook.OnSessionCreating(request, instruction);
@@ -112,6 +115,9 @@ await HookEmitter.Emit(_services, async hook =>
[HttpPost("twilio/voice/receive/{seqNum}")]
public async Task ReceiveCallerMessage(ConversationalVoiceRequest request)
{
+ // Authenticate (synchronous, so identity writes survive into the async session hooks)
+ HookEmitter.Emit(_services, hook => hook.OnAuthenticate(request), request.AgentId);
+
var twilio = _services.GetRequiredService();
var messageQueue = _services.GetRequiredService();
var sessionManager = _services.GetRequiredService();
@@ -203,6 +209,9 @@ await HookEmitter.Emit(_services, async hook =>
[HttpPost("twilio/voice/reply/{seqNum}")]
public async Task ReplyCallerMessage(ConversationalVoiceRequest request)
{
+ // Authenticate (synchronous, so identity writes survive into the async session hooks)
+ HookEmitter.Emit(_services, hook => hook.OnAuthenticate(request), request.AgentId);
+
var nextSeqNum = request.SeqNum + 1;
var sessionManager = _services.GetRequiredService();
var twilio = _services.GetRequiredService();
@@ -343,6 +352,9 @@ public async Task PhoneCallStatus(ConversationalVoiceRequest reque
{
var twilio = _services.GetRequiredService();
+ // Authenticate (synchronous, so identity writes survive into the async status hooks)
+ HookEmitter.Emit(_services, hook => hook.OnAuthenticate(request), request.AgentId);
+
switch (request.CallStatus)
{
case "completed":
diff --git a/src/Plugins/BotSharp.Plugin.Twilio/Hooks/TwilioConversationHook.cs b/src/Plugins/BotSharp.Plugin.Twilio/Hooks/TwilioConversationHook.cs
index 3ccc4f4d6..8762c5649 100644
--- a/src/Plugins/BotSharp.Plugin.Twilio/Hooks/TwilioConversationHook.cs
+++ b/src/Plugins/BotSharp.Plugin.Twilio/Hooks/TwilioConversationHook.cs
@@ -41,6 +41,7 @@ public override async Task OnFunctionExecuted(RoleDialogModel message, InvokeFun
CallSid = sid,
};
+ // No OnAuthenticate before these hooks — identity is established only at the HTTP entry points.
foreach (var hook in hooks)
{
if (await hook.ShouldReconnect(request, message))
diff --git a/src/Plugins/BotSharp.Plugin.Twilio/Interfaces/ITwilioCallStatusHook.cs b/src/Plugins/BotSharp.Plugin.Twilio/Interfaces/ITwilioCallStatusHook.cs
index e7ce09325..3c73b5c95 100644
--- a/src/Plugins/BotSharp.Plugin.Twilio/Interfaces/ITwilioCallStatusHook.cs
+++ b/src/Plugins/BotSharp.Plugin.Twilio/Interfaces/ITwilioCallStatusHook.cs
@@ -6,6 +6,11 @@ namespace BotSharp.Plugin.Twilio.Interfaces;
public interface ITwilioCallStatusHook : IHookBase
{
+ ///
+ /// Establishes the operator identity synchronously at the call-status webhook entry, before any status hook method runs.
+ ///
+ ///
+ void OnAuthenticate(ConversationalVoiceRequest request);
Task OnVoicemailLeft(ConversationalVoiceRequest request) => Task.CompletedTask;
Task OnUserDisconnected(ConversationalVoiceRequest request) => Task.CompletedTask;
Task OnRecordingCompleted(ConversationalVoiceRequest request) => Task.CompletedTask;
diff --git a/src/Plugins/BotSharp.Plugin.Twilio/Interfaces/ITwilioSessionHook.cs b/src/Plugins/BotSharp.Plugin.Twilio/Interfaces/ITwilioSessionHook.cs
index 00837b6a4..8632e72e4 100644
--- a/src/Plugins/BotSharp.Plugin.Twilio/Interfaces/ITwilioSessionHook.cs
+++ b/src/Plugins/BotSharp.Plugin.Twilio/Interfaces/ITwilioSessionHook.cs
@@ -7,6 +7,12 @@ namespace BotSharp.Plugin.Twilio.Interfaces;
public interface ITwilioSessionHook : IHookBase
{
+ ///
+ /// Establishes the operator identity synchronously at the HTTP entry points only; the WebSocket and background flows manage identity separately.
+ ///
+ ///
+ void OnAuthenticate(ConversationalVoiceRequest request);
+
///
/// Before session creating
///
diff --git a/src/Plugins/BotSharp.Plugin.Twilio/Services/TwilioService.cs b/src/Plugins/BotSharp.Plugin.Twilio/Services/TwilioService.cs
index e0618aaf6..1720d95d5 100644
--- a/src/Plugins/BotSharp.Plugin.Twilio/Services/TwilioService.cs
+++ b/src/Plugins/BotSharp.Plugin.Twilio/Services/TwilioService.cs
@@ -324,7 +324,8 @@ public async Task WaitingForAiResponse(ConversationalVoiceRequest
ActionOnEmptyResult = true
};
- await HookEmitter.Emit(_services, async hook => await hook.OnWaitingAgentResponse(request, instruction),
+ // No OnAuthenticate before these hooks — identity is established only at the HTTP entry points.
+ await HookEmitter.Emit(_services, async hook => await hook.OnWaitingAgentResponse(request, instruction),
request.AgentId);
response = ReturnInstructions(instruction);
diff --git a/src/Plugins/BotSharp.Plugin.Twilio/TwilioStreamMiddleware.cs b/src/Plugins/BotSharp.Plugin.Twilio/TwilioStreamMiddleware.cs
index d653a3417..0f49ea07b 100644
--- a/src/Plugins/BotSharp.Plugin.Twilio/TwilioStreamMiddleware.cs
+++ b/src/Plugins/BotSharp.Plugin.Twilio/TwilioStreamMiddleware.cs
@@ -79,6 +79,7 @@ private async Task HandleWebSocket(IServiceProvider services, string agentId, st
var convService = services.GetRequiredService();
await convService.SetConversationId(conversationId, []);
+ // No OnAuthenticate before these hooks — identity is established only at the HTTP entry points.
var hooks = services.GetHooks(agentId);
foreach (var hook in hooks)
{