diff --git a/OpenJibo/src/Jibo.Cloud/dotnet/src/Jibo.Cloud.Application/Services/JiboInteractionService.cs b/OpenJibo/src/Jibo.Cloud/dotnet/src/Jibo.Cloud.Application/Services/JiboInteractionService.cs
index 4479122..25076cd 100644
--- a/OpenJibo/src/Jibo.Cloud/dotnet/src/Jibo.Cloud.Application/Services/JiboInteractionService.cs
+++ b/OpenJibo/src/Jibo.Cloud/dotnet/src/Jibo.Cloud.Application/Services/JiboInteractionService.cs
@@ -608,18 +608,64 @@ public sealed class JiboInteractionService(
"I can check weather once my weather service is connected.");
}
+ var locationQuery = TryResolveWeatherLocationQuery(transcript);
+ var weatherCoordinates = string.IsNullOrWhiteSpace(locationQuery)
+ ? TryResolveWeatherCoordinates(turn)
+ : null;
+ var useCelsius = ShouldUseCelsius(turn, transcript);
+ var isNextWeekForecast = IsNextWeekForecastRequest(normalizedTranscript);
+
+ if (isNextWeekForecast)
+ {
+ var weeklySnapshots = new List<(int DayOffset, WeatherReportSnapshot Snapshot)>();
+ for (var offset = 1; offset <= MaxWeatherForecastDayOffset; offset += 1)
+ {
+ WeatherReportSnapshot? weeklySnapshot;
+ try
+ {
+ weeklySnapshot = await weatherReportProvider.GetReportAsync(
+ new WeatherReportRequest(
+ locationQuery,
+ weatherCoordinates?.Latitude,
+ weatherCoordinates?.Longitude,
+ IsTomorrow: offset == 1,
+ useCelsius,
+ ForecastDayOffset: offset),
+ cancellationToken);
+ }
+ catch (Exception) when (!cancellationToken.IsCancellationRequested)
+ {
+ weeklySnapshot = null;
+ }
+
+ if (weeklySnapshot is not null)
+ {
+ weeklySnapshots.Add((offset, weeklySnapshot));
+ }
+ }
+
+ if (weeklySnapshots.Count == 0)
+ {
+ return new JiboInteractionDecision(
+ "weather",
+ "I couldn't fetch the weather right now. Please try again.");
+ }
+
+ var weeklySpokenReply = BuildNextWeekForecastSpokenReply(weeklySnapshots, referenceLocalTime);
+ var weeklyWeatherPayload = BuildWeatherSkillPayload(weeklySpokenReply, weeklySnapshots[0].Snapshot, referenceLocalTime);
+ return new JiboInteractionDecision(
+ "weather",
+ weeklySpokenReply,
+ "chitchat-skill",
+ SkillPayload: weeklyWeatherPayload);
+ }
+
if (weatherDate.ForecastDayOffset > MaxWeatherForecastDayOffset)
{
return new JiboInteractionDecision(
"weather",
$"I can forecast up to {MaxWeatherForecastDayOffset} days ahead. Try tomorrow or another day this week.");
}
-
- var locationQuery = TryResolveWeatherLocationQuery(transcript);
- var weatherCoordinates = string.IsNullOrWhiteSpace(locationQuery)
- ? TryResolveWeatherCoordinates(turn)
- : null;
- var useCelsius = ShouldUseCelsius(turn, transcript);
WeatherReportSnapshot? snapshot;
try
{
@@ -688,6 +734,52 @@ public sealed class JiboInteractionService(
return $"Right now in {location}, it is {summary} and {snapshot.Temperature} degrees {unit}.";
}
+ private static string BuildNextWeekForecastSpokenReply(
+ IReadOnlyList<(int DayOffset, WeatherReportSnapshot Snapshot)> snapshots,
+ DateTimeOffset? referenceLocalTime)
+ {
+ if (snapshots.Count == 0)
+ {
+ return "I couldn't build a forecast right now.";
+ }
+
+ var location = snapshots[0].Snapshot.LocationName;
+ if (string.IsNullOrWhiteSpace(location))
+ {
+ location = "your area";
+ }
+
+ var unit = snapshots[0].Snapshot.UseCelsius ? "Celsius" : "Fahrenheit";
+ var referenceDate = (referenceLocalTime ?? DateTimeOffset.UtcNow).Date;
+ var segments = snapshots
+ .OrderBy(static item => item.DayOffset)
+ .Take(MaxWeatherForecastDayOffset)
+ .Select(item =>
+ {
+ var dayName = referenceDate.AddDays(item.DayOffset).ToString("dddd", CultureInfo.InvariantCulture);
+ var summary = string.IsNullOrWhiteSpace(item.Snapshot.Summary)
+ ? "partly cloudy"
+ : item.Snapshot.Summary.Trim().TrimEnd('.');
+ var high = item.Snapshot.HighTemperature ?? item.Snapshot.Temperature;
+ var low = item.Snapshot.LowTemperature ?? item.Snapshot.Temperature;
+ return $"{dayName}: {summary}, high {high}, low {low}.";
+ });
+
+ return $"I can share the next five-day forecast in {location}. {string.Join(" ", segments)} Temperatures are in {unit}.";
+ }
+
+ private static bool IsNextWeekForecastRequest(string normalizedTranscript)
+ {
+ if (string.IsNullOrWhiteSpace(normalizedTranscript) ||
+ !normalizedTranscript.Contains("next week", StringComparison.Ordinal))
+ {
+ return false;
+ }
+
+ return normalizedTranscript.Contains("forecast", StringComparison.Ordinal) ||
+ normalizedTranscript.Contains("weather", StringComparison.Ordinal);
+ }
+
private static bool ShouldDefaultForecastToTomorrow(string normalizedTranscript, WeatherDateEntity weatherDate)
{
if (weatherDate.ForecastDayOffset > 0 ||
@@ -946,7 +1038,11 @@ public sealed class JiboInteractionService(
["skillId"] = "news",
["cloudSkill"] = "news",
["mim_id"] = "runtime-news",
- ["mim_type"] = "announcement"
+ ["mim_type"] = "announcement",
+ ["prompt_id"] = "NewsHeadline_AN_01",
+ ["prompt_sub_category"] = "AN",
+ ["esml"] =
+ $"{EscapeForEsml(spokenBriefing)}"
};
if (!string.IsNullOrWhiteSpace(sourceName))
@@ -4100,6 +4196,10 @@ public sealed class JiboInteractionService(
("technology", "technology"),
("tech", "technology"),
("ai", "technology"),
+ ("a i", "technology"),
+ ("a eye", "technology"),
+ ("aye eye", "technology"),
+ ("artificial intelligence", "technology"),
("science", "science"),
("business", "business"),
("finance", "business"),
diff --git a/OpenJibo/src/Jibo.Cloud/dotnet/src/Jibo.Cloud.Application/Services/ResponsePlanToSocketMessagesMapper.cs b/OpenJibo/src/Jibo.Cloud/dotnet/src/Jibo.Cloud.Application/Services/ResponsePlanToSocketMessagesMapper.cs
index 073a37e..98b39b1 100644
--- a/OpenJibo/src/Jibo.Cloud/dotnet/src/Jibo.Cloud.Application/Services/ResponsePlanToSocketMessagesMapper.cs
+++ b/OpenJibo/src/Jibo.Cloud/dotnet/src/Jibo.Cloud.Application/Services/ResponsePlanToSocketMessagesMapper.cs
@@ -844,9 +844,10 @@ public sealed class ResponsePlanToSocketMessagesMapper
["view"] = resolvedGuiConfig
};
- playConfig["gui"] = resolvedGuiConfig;
- playConfig["no_matches_for_gui"] = 0;
- playConfig["no_inputs_for_gui"] = 0;
+ jcpConfig["timeout"] = 6;
+ jcpConfig["barge_in"] = true;
+ jcpConfig["no_matches_for_gui"] = 0;
+ jcpConfig["no_inputs_for_gui"] = 0;
var weatherViews = new Dictionary(StringComparer.OrdinalIgnoreCase)
{
diff --git a/OpenJibo/src/Jibo.Cloud/dotnet/src/Jibo.Cloud.Application/Services/WebSocketTurnFinalizationService.cs b/OpenJibo/src/Jibo.Cloud/dotnet/src/Jibo.Cloud.Application/Services/WebSocketTurnFinalizationService.cs
index fe4ff92..e92ffc0 100644
--- a/OpenJibo/src/Jibo.Cloud/dotnet/src/Jibo.Cloud.Application/Services/WebSocketTurnFinalizationService.cs
+++ b/OpenJibo/src/Jibo.Cloud/dotnet/src/Jibo.Cloud.Application/Services/WebSocketTurnFinalizationService.cs
@@ -668,6 +668,22 @@ public sealed partial class WebSocketTurnFinalizationService(
UpdatePendingProactivityOffer(session, plan.IntentName);
await ApplyContextUpdatesAsync(session, plan.ContextUpdates, envelope, plan.IntentName, cancellationToken);
+ var invokedSkillAction = plan.Actions.OfType().FirstOrDefault();
+ if ((string.Equals(plan.IntentName, "weather", StringComparison.OrdinalIgnoreCase) ||
+ string.Equals(plan.IntentName, "news", StringComparison.OrdinalIgnoreCase)) &&
+ invokedSkillAction is not null)
+ {
+ await sink.RecordTurnDiagnosticAsync(
+ "skill_payload_summary",
+ BuildTurnDiagnosticSnapshot(session, envelope, new Dictionary
+ {
+ ["intent"] = plan.IntentName,
+ ["skillName"] = invokedSkillAction.SkillName,
+ ["payload"] = invokedSkillAction.Payload
+ }),
+ cancellationToken);
+ }
+
session.FollowUpExpiresUtc = plan.FollowUp.KeepMicOpen
? DateTimeOffset.UtcNow.Add(plan.FollowUp.Timeout)
: null;
diff --git a/OpenJibo/tests/Jibo.Cloud.Tests/WebSockets/JiboInteractionServiceTests.cs b/OpenJibo/tests/Jibo.Cloud.Tests/WebSockets/JiboInteractionServiceTests.cs
index b9ff01b..2b86951 100644
--- a/OpenJibo/tests/Jibo.Cloud.Tests/WebSockets/JiboInteractionServiceTests.cs
+++ b/OpenJibo/tests/Jibo.Cloud.Tests/WebSockets/JiboInteractionServiceTests.cs
@@ -1761,7 +1761,7 @@ public sealed class JiboInteractionServiceTests
}
[Fact]
- public async Task BuildDecisionAsync_WeatherNextWeek_WithContext_ReturnsGuardrailMessage()
+ public async Task BuildDecisionAsync_WeatherNextWeek_WithContext_ReturnsFiveDaySummary()
{
var provider = new CapturingWeatherReportProvider
{
@@ -1780,8 +1780,12 @@ public sealed class JiboInteractionServiceTests
});
Assert.Equal("weather", decision.IntentName);
- Assert.Equal("I can forecast up to 5 days ahead. Try tomorrow or another day this week.", decision.ReplyText);
- Assert.Null(provider.LastRequest);
+ Assert.Contains("next five-day forecast", decision.ReplyText, StringComparison.OrdinalIgnoreCase);
+ Assert.Contains("Seattle, US", decision.ReplyText, StringComparison.OrdinalIgnoreCase);
+ Assert.Contains("Temperatures are in Fahrenheit.", decision.ReplyText, StringComparison.OrdinalIgnoreCase);
+ Assert.NotNull(provider.LastRequest);
+ Assert.Equal("Seattle", provider.LastRequest!.LocationQuery);
+ Assert.Equal(5, provider.LastRequest.ForecastDayOffset);
}
[Fact]
@@ -2690,6 +2694,7 @@ public sealed class JiboInteractionServiceTests
Assert.Equal("news", decision.SkillPayload!["skillId"]);
Assert.Equal("news", decision.SkillPayload["cloudSkill"]);
Assert.Equal("runtime-news", decision.SkillPayload["mim_id"]);
+ Assert.Contains("news-stinger", decision.SkillPayload["esml"]?.ToString(), StringComparison.OrdinalIgnoreCase);
Assert.Equal("NewsAPI", decision.SkillPayload["news_source"]);
Assert.Equal(2, decision.SkillPayload["news_headline_count"]);
Assert.Contains("Local robotics team unveils weather-ready helper", decision.ReplyText, StringComparison.OrdinalIgnoreCase);
@@ -2697,6 +2702,30 @@ public sealed class JiboInteractionServiceTests
Assert.Equal(3, provider.LastRequest!.MaxHeadlines);
}
+ [Fact]
+ public async Task BuildDecisionAsync_TellMeTheNews_WithAIAlias_UsesTechnologyCategory()
+ {
+ var provider = new CapturingNewsBriefingProvider
+ {
+ Snapshot = new NewsBriefingSnapshot(
+ [
+ new NewsHeadline("AI labs unveil new home companion behaviors")
+ ],
+ "NewsAPI")
+ };
+ var service = CreateService(newsBriefingProvider: provider);
+
+ var decision = await service.BuildDecisionAsync(new TurnContext
+ {
+ RawTranscript = "tell me the a i news",
+ NormalizedTranscript = "tell me the a i news"
+ });
+
+ Assert.Equal("news", decision.IntentName);
+ Assert.NotNull(provider.LastRequest);
+ Assert.Contains("technology", provider.LastRequest!.PreferredCategories, StringComparer.OrdinalIgnoreCase);
+ }
+
[Fact]
public async Task BuildDecisionAsync_TellMeTheNews_WithMemoryPreference_UsesCategoryHints()
{
diff --git a/OpenJibo/tests/Jibo.Cloud.Tests/WebSockets/JiboWebSocketServiceTests.cs b/OpenJibo/tests/Jibo.Cloud.Tests/WebSockets/JiboWebSocketServiceTests.cs
index ba4ff11..ade8d55 100644
--- a/OpenJibo/tests/Jibo.Cloud.Tests/WebSockets/JiboWebSocketServiceTests.cs
+++ b/OpenJibo/tests/Jibo.Cloud.Tests/WebSockets/JiboWebSocketServiceTests.cs
@@ -2155,13 +2155,9 @@ public sealed class JiboWebSocketServiceTests
Assert.Equal("views.weatherHiLo", gui.GetProperty("data").GetString());
Assert.True(gui.GetProperty("pause").GetBoolean());
- var play = jcpConfig.GetProperty("play");
- Assert.True(play.TryGetProperty("gui", out var playGui));
- Assert.Equal("Javascript", playGui.GetProperty("type").GetString());
- Assert.True(playGui.GetProperty("pause").GetBoolean());
- Assert.Equal("weatherTempView", playGui.GetProperty("data").GetProperty("viewConfig").GetProperty("id").GetString());
- Assert.Equal(0, play.GetProperty("no_matches_for_gui").GetInt32());
- Assert.Equal(0, play.GetProperty("no_inputs_for_gui").GetInt32());
+ Assert.Equal(6, jcpConfig.GetProperty("timeout").GetInt32());
+ Assert.Equal(0, jcpConfig.GetProperty("no_matches_for_gui").GetInt32());
+ Assert.Equal(0, jcpConfig.GetProperty("no_inputs_for_gui").GetInt32());
Assert.True(jcpConfig.TryGetProperty("display", out var display));
Assert.Equal(