fixes for version 13 bugs

This commit is contained in:
Jacob Dubin
2026-04-22 07:48:36 -05:00
parent 6c62e48495
commit 425d8c1a9b
15 changed files with 14003 additions and 21 deletions

View File

@@ -184,6 +184,8 @@ Latest clock discovery findings:
- The `jibo test 15` bundle shows stock OS 1.9 rejecting our older top-level `timerValue` launch with `found no matching transition`, so the safer cloud contract is a stock-style `start` intent with the timer/alarm entities attached. - The `jibo test 15` bundle shows stock OS 1.9 rejecting our older top-level `timerValue` launch with `found no matching transition`, so the safer cloud contract is a stock-style `start` intent with the timer/alarm entities attached.
- The same bundle also shows local follow-up rules like `clock/timer_set_value`, so bare replies such as `five minutes` or `ten twenty five` need to be parsed when the robot is already collecting a timer/alarm value. - The same bundle also shows local follow-up rules like `clock/timer_set_value`, so bare replies such as `five minutes` or `ten twenty five` need to be parsed when the robot is already collecting a timer/alarm value.
- The newest `.NET` pass now routes `open the clock` into the direct `askForTime` clock-view path, moves plain time/date/day questions onto stock-shaped local `@be/clock` handoffs, and keeps malformed timer/alarm requests on a clarification reply path instead of generic chat echo. - The newest `.NET` pass now routes `open the clock` into the direct `askForTime` clock-view path, moves plain time/date/day questions onto stock-shaped local `@be/clock` handoffs, and keeps malformed timer/alarm requests on a clarification reply path instead of generic chat echo.
- The `jibo test 17` bundle shows two remaining clock realities on stock OS 1.9: some alarm misses are genuine STT loss before the cloud ever sees the minutes, and empty cleanup turns like `clock/alarm_timer_okay` must stay local instead of degrading into `heyJibo`/Nimbus.
- When the robot context includes a usable local `runtime.location.iso`, ambiguous alarm times now prefer the next real local occurrence rather than defaulting blindly.
Latest photo discovery findings: Latest photo discovery findings:
@@ -191,11 +193,13 @@ Latest photo discovery findings:
- `snapshot` and `photobooth` are not gallery submodes; stock main-menu logic remaps them into `@be/create` with `createOnePhoto` and `createSomePhotos`. - `snapshot` and `photobooth` are not gallery submodes; stock main-menu logic remaps them into `@be/create` with `createOnePhoto` and `createSomePhotos`.
- The newest `.NET` pass keeps that routing, adds local-file persistence for media metadata, and serves stored media URLs back through `/media/{path}` as a first hosted-gallery slice. - The newest `.NET` pass keeps that routing, adds local-file persistence for media metadata, and serves stored media URLs back through `/media/{path}` as a first hosted-gallery slice.
- The remaining gap is binary fidelity: the current HTTP capture path stores request bodies as text, which is enough to preserve metadata and a placeholder payload, but may still be too lossy for perfect thumbnails/original fetches. - The remaining gap is binary fidelity: the current HTTP capture path stores request bodies as text, which is enough to preserve metadata and a placeholder payload, but may still be too lossy for perfect thumbnails/original fetches.
- The `jibo test 17` gallery blue-ring report is at least partly tangled up with the gallery-empty path: stock `@be/gallery` says `there's nothing in the gallery yet. want to take a picture now?`, so lingering mic state there is not purely a launch-routing issue.
Latest update and state findings: Latest update and state findings:
- unstaged update queries should not fabricate placeholder no-op manifests, because stock settings logic can treat any returned object like a pending update - unstaged update queries should not fabricate placeholder no-op manifests, because stock settings logic can treat any returned object like a pending update
- the hosted `.NET` cloud now persists update/media/backups state to a local state file by default, which is a better bridge toward Azure SQL / Blob storage than the old process-memory-only behavior - the hosted `.NET` cloud now persists update/media/backups state to a local state file by default, which is a better bridge toward Azure SQL / Blob storage than the old process-memory-only behavior
- The `jibo test 17` session also includes a real on-robot backup announcement and temporary settings connectivity turbulence, so not all sluggishness from that run should be attributed to the newer cloud protocol changes.
## Speech, Animation, And ESML ## Speech, Animation, And ESML

View File

@@ -6,7 +6,7 @@
This is the production-oriented path for restoring device connectivity and creating a foundation for future runtime, AI, and OTA work. This is the production-oriented path for restoring device connectivity and creating a foundation for future runtime, AI, and OTA work.
Current spoken cloud version: `Open Jibo Cloud version 1.0.10.` Current spoken cloud version: `Open Jibo Cloud version 1.0.14.`
Release hygiene reminder: Release hygiene reminder:

View File

@@ -14,6 +14,7 @@ public sealed class JiboInteractionService(
var catalog = await contentCache.GetCatalogAsync(cancellationToken); var catalog = await contentCache.GetCatalogAsync(cancellationToken);
var transcript = (turn.NormalizedTranscript ?? turn.RawTranscript ?? string.Empty).Trim(); var transcript = (turn.NormalizedTranscript ?? turn.RawTranscript ?? string.Empty).Trim();
var lowered = transcript.ToLowerInvariant(); var lowered = transcript.ToLowerInvariant();
var referenceLocalTime = TryResolveReferenceLocalTime(turn);
var clientIntent = turn.Attributes.TryGetValue("clientIntent", out var rawClientIntent) var clientIntent = turn.Attributes.TryGetValue("clientIntent", out var rawClientIntent)
? rawClientIntent?.ToString() ? rawClientIntent?.ToString()
: null; : null;
@@ -27,6 +28,7 @@ public sealed class JiboInteractionService(
var isAlarmValueTurn = IsClockAlarmValueTurn(clientRules, listenRules); var isAlarmValueTurn = IsClockAlarmValueTurn(clientRules, listenRules);
var semanticIntent = ResolveSemanticIntent( var semanticIntent = ResolveSemanticIntent(
lowered, lowered,
referenceLocalTime,
clientIntent, clientIntent,
clientRules, clientRules,
listenRules, listenRules,
@@ -38,7 +40,7 @@ public sealed class JiboInteractionService(
{ {
"joke" => BuildJokeDecision(catalog), "joke" => BuildJokeDecision(catalog),
"dance" => BuildRandomDanceDecision(catalog), "dance" => BuildRandomDanceDecision(catalog),
"twerk" => BuildDanceDecision("rom-twerk", "Watch me twerk."), "twerk" => BuildDanceDecision("twerk", "rom-twerk", "Watch me twerk."),
"time" => BuildClockLaunchDecision("time", "clock", "askForTime", "Showing the time."), "time" => BuildClockLaunchDecision("time", "clock", "askForTime", "Showing the time."),
"date" => BuildClockLaunchDecision("date", "clock", "askForDate", "Showing the date."), "date" => BuildClockLaunchDecision("date", "clock", "askForDate", "Showing the date."),
"day" => BuildClockLaunchDecision("day", "clock", "askForDay", "Showing the day."), "day" => BuildClockLaunchDecision("day", "clock", "askForDay", "Showing the day."),
@@ -50,7 +52,7 @@ public sealed class JiboInteractionService(
"timer_menu" => BuildClockLaunchDecision("timer", "Opening the timer."), "timer_menu" => BuildClockLaunchDecision("timer", "Opening the timer."),
"alarm_menu" => BuildClockLaunchDecision("alarm", "Opening the alarm."), "alarm_menu" => BuildClockLaunchDecision("alarm", "Opening the alarm."),
"timer_value" => BuildTimerValueDecision(lowered, isTimerValueTurn), "timer_value" => BuildTimerValueDecision(lowered, isTimerValueTurn),
"alarm_value" => BuildAlarmValueDecision(lowered, isAlarmValueTurn), "alarm_value" => BuildAlarmValueDecision(lowered, isAlarmValueTurn, referenceLocalTime),
"timer_clarify" => new JiboInteractionDecision("timer_clarify", "How long should I set the timer for?"), "timer_clarify" => new JiboInteractionDecision("timer_clarify", "How long should I set the timer for?"),
"alarm_clarify" => new JiboInteractionDecision("alarm_clarify", "What time should I set the alarm for?"), "alarm_clarify" => new JiboInteractionDecision("alarm_clarify", "What time should I set the alarm for?"),
"photo_gallery" => BuildPhotoGalleryLaunchDecision(), "photo_gallery" => BuildPhotoGalleryLaunchDecision(),
@@ -89,13 +91,13 @@ public sealed class JiboInteractionService(
{ {
var dance = randomizer.Choose(catalog.DanceAnimations); var dance = randomizer.Choose(catalog.DanceAnimations);
var replyText = randomizer.Choose(catalog.DanceReplies); var replyText = randomizer.Choose(catalog.DanceReplies);
return BuildDanceDecision(dance, replyText); return BuildDanceDecision("dance", dance, replyText);
} }
private static JiboInteractionDecision BuildDanceDecision(string dance, string replyText) private static JiboInteractionDecision BuildDanceDecision(string intentName, string dance, string replyText)
{ {
return new JiboInteractionDecision( return new JiboInteractionDecision(
"dance", intentName,
replyText, replyText,
"chitchat-skill", "chitchat-skill",
new Dictionary<string, object?> new Dictionary<string, object?>
@@ -147,6 +149,7 @@ public sealed class JiboInteractionService(
private static string ResolveSemanticIntent( private static string ResolveSemanticIntent(
string loweredTranscript, string loweredTranscript,
DateTimeOffset? referenceLocalTime,
string? clientIntent, string? clientIntent,
IReadOnlyList<string> clientRules, IReadOnlyList<string> clientRules,
IReadOnlyList<string> listenRules, IReadOnlyList<string> listenRules,
@@ -285,7 +288,7 @@ public sealed class JiboInteractionService(
return "alarm_menu"; return "alarm_menu";
} }
if (TryParseAlarmValue(loweredTranscript, isAlarmValueTurn) is not null) if (TryParseAlarmValue(loweredTranscript, isAlarmValueTurn, referenceLocalTime) is not null)
{ {
return "alarm_value"; return "alarm_value";
} }
@@ -342,16 +345,16 @@ public sealed class JiboInteractionService(
return "photo_gallery"; return "photo_gallery";
} }
if (MatchesAny(loweredTranscript, "dance", "boogie"))
{
return "dance";
}
if (MatchesAny(loweredTranscript, "twerk")) if (MatchesAny(loweredTranscript, "twerk"))
{ {
return "twerk"; return "twerk";
} }
if (MatchesAny(loweredTranscript, "dance", "boogie"))
{
return "dance";
}
if (MatchesAny(loweredTranscript, "surprise", "surprise me", "show me something fun")) if (MatchesAny(loweredTranscript, "surprise", "surprise me", "show me something fun"))
{ {
return "surprise"; return "surprise";
@@ -512,9 +515,12 @@ public sealed class JiboInteractionService(
}); });
} }
private static JiboInteractionDecision BuildAlarmValueDecision(string loweredTranscript, bool allowImplicit) private static JiboInteractionDecision BuildAlarmValueDecision(
string loweredTranscript,
bool allowImplicit,
DateTimeOffset? referenceLocalTime)
{ {
var alarm = TryParseAlarmValue(loweredTranscript, allowImplicit) ?? new ClockAlarmValue("7:00", "am"); var alarm = TryParseAlarmValue(loweredTranscript, allowImplicit, referenceLocalTime) ?? new ClockAlarmValue("7:00", "am");
return new JiboInteractionDecision( return new JiboInteractionDecision(
"alarm_value", "alarm_value",
@@ -724,6 +730,43 @@ public sealed class JiboInteractionService(
}; };
} }
private static DateTimeOffset? TryResolveReferenceLocalTime(TurnContext turn)
{
if (!turn.Attributes.TryGetValue("context", out var value) || value is null)
{
return null;
}
try
{
var contextJson = value.ToString();
if (string.IsNullOrWhiteSpace(contextJson))
{
return null;
}
using var document = JsonDocument.Parse(contextJson);
if (!document.RootElement.TryGetProperty("runtime", out var runtime) ||
runtime.ValueKind != JsonValueKind.Object ||
!runtime.TryGetProperty("location", out var location) ||
location.ValueKind != JsonValueKind.Object ||
!location.TryGetProperty("iso", out var iso) ||
iso.ValueKind != JsonValueKind.String)
{
return null;
}
var isoValue = iso.GetString();
return DateTimeOffset.TryParse(isoValue, out var parsed)
? parsed
: null;
}
catch
{
return null;
}
}
private static bool MatchesAny(string loweredTranscript, params string[] candidates) private static bool MatchesAny(string loweredTranscript, params string[] candidates)
{ {
return candidates.Any(candidate => loweredTranscript.Contains(candidate, StringComparison.Ordinal)); return candidates.Any(candidate => loweredTranscript.Contains(candidate, StringComparison.Ordinal));
@@ -780,7 +823,10 @@ public sealed class JiboInteractionService(
seconds is null ? "null" : seconds.Value.ToString()); seconds is null ? "null" : seconds.Value.ToString());
} }
private static ClockAlarmValue? TryParseAlarmValue(string loweredTranscript, bool allowImplicit = false) private static ClockAlarmValue? TryParseAlarmValue(
string loweredTranscript,
bool allowImplicit = false,
DateTimeOffset? referenceLocalTime = null)
{ {
if (!allowImplicit && !loweredTranscript.Contains("alarm", StringComparison.Ordinal)) if (!allowImplicit && !loweredTranscript.Contains("alarm", StringComparison.Ordinal))
{ {
@@ -807,7 +853,7 @@ public sealed class JiboInteractionService(
}; };
if (compactHour is >= 1 and <= 12 && compactMinute is >= 0 and <= 59) if (compactHour is >= 1 and <= 12 && compactMinute is >= 0 and <= 59)
{ {
var compactAmPm = ResolveAmPm(compactMatch.Groups["ampm"].Value); var compactAmPm = ResolveAmPm(compactMatch.Groups["ampm"].Value, compactHour, compactMinute, referenceLocalTime);
return new ClockAlarmValue($"{compactHour}:{compactMinute:00}", compactAmPm); return new ClockAlarmValue($"{compactHour}:{compactMinute:00}", compactAmPm);
} }
} }
@@ -833,15 +879,59 @@ public sealed class JiboInteractionService(
return null; return null;
} }
var ampm = ResolveAmPm(match.Groups["ampm"].Value); var ampm = ResolveAmPm(match.Groups["ampm"].Value, hour.Value, minute.Value, referenceLocalTime);
return new ClockAlarmValue($"{hour}:{minute:00}", ampm); return new ClockAlarmValue($"{hour}:{minute:00}", ampm);
} }
private static string ResolveAmPm(string token) private static string ResolveAmPm(string token, int hour, int minute, DateTimeOffset? referenceLocalTime)
{ {
var normalized = token.Replace(" ", string.Empty, StringComparison.Ordinal) var normalized = token.Replace(" ", string.Empty, StringComparison.Ordinal)
.Replace(".", string.Empty, StringComparison.Ordinal); .Replace(".", string.Empty, StringComparison.Ordinal);
return normalized.StartsWith("p", StringComparison.OrdinalIgnoreCase) ? "pm" : "am"; if (normalized.StartsWith("p", StringComparison.OrdinalIgnoreCase))
{
return "pm";
}
if (normalized.StartsWith("a", StringComparison.OrdinalIgnoreCase))
{
return "am";
}
return referenceLocalTime.HasValue
? ResolveNextOccurrenceAmPm(hour, minute, referenceLocalTime.Value)
: "am";
}
private static string ResolveNextOccurrenceAmPm(int hour, int minute, DateTimeOffset referenceLocalTime)
{
var amCandidate = BuildAlarmCandidate(referenceLocalTime, hour, minute, isPm: false);
var pmCandidate = BuildAlarmCandidate(referenceLocalTime, hour, minute, isPm: true);
return amCandidate <= pmCandidate ? "am" : "pm";
}
private static DateTimeOffset BuildAlarmCandidate(DateTimeOffset referenceLocalTime, int hour, int minute, bool isPm)
{
var hour24 = hour % 12;
if (isPm)
{
hour24 += 12;
}
var candidate = new DateTimeOffset(
referenceLocalTime.Year,
referenceLocalTime.Month,
referenceLocalTime.Day,
hour24,
minute,
0,
referenceLocalTime.Offset);
if (candidate <= referenceLocalTime)
{
candidate = candidate.AddDays(1);
}
return candidate;
} }
private static bool IsTimerRequest(string loweredTranscript) private static bool IsTimerRequest(string loweredTranscript)

View File

@@ -2,7 +2,7 @@ namespace Jibo.Cloud.Application.Services;
public static class OpenJiboCloudBuildInfo public static class OpenJiboCloudBuildInfo
{ {
public const string Version = "1.0.13"; public const string Version = "1.0.14";
public static string VersionWords => Version.Replace(".", " dot "); public static string VersionWords => Version.Replace(".", " dot ");

View File

@@ -469,6 +469,24 @@ public sealed class WebSocketTurnFinalizationService(
.ToArray(); .ToArray();
} }
if (ShouldHandleAsLocalNoInput(finalizedTurn))
{
turnState.AwaitingTurnCompletion = false;
session.LastTranscript = string.Empty;
session.LastIntent = null;
session.LastListenType = "no-input";
var localRule = ReadPrimaryNoInputRule(finalizedTurn);
var noInputReplies = ResponsePlanToSocketMessagesMapper.MapNoInput(
turnState.TransId ?? session.LastTransId ?? string.Empty,
string.IsNullOrWhiteSpace(localRule) ? turnState.ListenRules : [localRule])
.Select(map => new WebSocketReply { Text = map.Text, DelayMs = map.DelayMs })
.ToArray();
ResetBufferedAudio(session);
turnState.SawListen = false;
turnState.SawContext = false;
return noInputReplies;
}
if (ShouldIgnoreInitialEmptyHotphraseTurn(finalizedTurn, turnState)) if (ShouldIgnoreInitialEmptyHotphraseTurn(finalizedTurn, turnState))
{ {
turnState.HotphraseEmptyTurnCount += 1; turnState.HotphraseEmptyTurnCount += 1;
@@ -639,7 +657,7 @@ public sealed class WebSocketTurnFinalizationService(
private static bool ShouldIgnorePassiveLocalSkillContext(CloudSession session, string? text) private static bool ShouldIgnorePassiveLocalSkillContext(CloudSession session, string? text)
{ {
if (session.FollowUpOpen || session.TurnState.SawListen) if (session.FollowUpOpen)
{ {
return false; return false;
} }
@@ -805,6 +823,31 @@ public sealed class WebSocketTurnFinalizationService(
string.Equals(rule, "surprises-ota/want_to_download_now", StringComparison.OrdinalIgnoreCase)); string.Equals(rule, "surprises-ota/want_to_download_now", StringComparison.OrdinalIgnoreCase));
} }
private static bool ShouldHandleAsLocalNoInput(TurnContext turn)
{
if (!string.IsNullOrWhiteSpace(turn.NormalizedTranscript) || !string.IsNullOrWhiteSpace(turn.RawTranscript))
{
return false;
}
return ReadRules(turn, "listenRules")
.Concat(ReadRules(turn, "clientRules"))
.Any(static rule =>
string.Equals(rule, "clock/alarm_timer_okay", StringComparison.OrdinalIgnoreCase));
}
private static string? ReadPrimaryNoInputRule(TurnContext turn)
{
return ReadRules(turn, "listenRules")
.Concat(ReadRules(turn, "clientRules"))
.FirstOrDefault(static rule =>
string.Equals(rule, "clock/alarm_timer_okay", StringComparison.OrdinalIgnoreCase) ||
string.Equals(rule, "create/is_it_a_keeper", StringComparison.OrdinalIgnoreCase) ||
string.Equals(rule, "settings/download_now_later", StringComparison.OrdinalIgnoreCase) ||
string.Equals(rule, "surprises-date/offer_date_fact", StringComparison.OrdinalIgnoreCase) ||
string.Equals(rule, "surprises-ota/want_to_download_now", StringComparison.OrdinalIgnoreCase));
}
private static string? ReadPrimaryYesNoRule(TurnContext turn) private static string? ReadPrimaryYesNoRule(TurnContext turn)
{ {
return ReadRules(turn, "listenRules") return ReadRules(turn, "listenRules")

View File

@@ -41,6 +41,21 @@ public sealed class JiboInteractionServiceTests
Assert.Equal("<speak>Okay.<break size='0.2'/> Watch this.<anim cat='dance' filter='music, rom-upbeat' /></speak>", decision.SkillPayload!["esml"]); Assert.Equal("<speak>Okay.<break size='0.2'/> Watch this.<anim cat='dance' filter='music, rom-upbeat' /></speak>", decision.SkillPayload!["esml"]);
} }
[Fact]
public async Task BuildDecisionAsync_TwerkQuestion_PrefersSpecificTwerkIntent()
{
var service = CreateService();
var decision = await service.BuildDecisionAsync(new TurnContext
{
RawTranscript = "can you twerk",
NormalizedTranscript = "can you twerk"
});
Assert.Equal("twerk", decision.IntentName);
Assert.Equal("chitchat-skill", decision.SkillName);
}
[Fact] [Fact]
public async Task BuildDecisionAsync_ClientNluAskForDate_MapsToDateIntent() public async Task BuildDecisionAsync_ClientNluAskForDate_MapsToDateIntent()
{ {
@@ -368,6 +383,46 @@ public sealed class JiboInteractionServiceTests
Assert.Equal("pm", decision.SkillPayload["ampm"]); Assert.Equal("pm", decision.SkillPayload["ampm"]);
} }
[Fact]
public async Task BuildDecisionAsync_SetAlarmForSevenEighteen_UsesNextOccurrenceFromContext()
{
var service = CreateService();
var decision = await service.BuildDecisionAsync(new TurnContext
{
RawTranscript = "set an alarm for 7:18",
NormalizedTranscript = "set an alarm for 7:18",
Attributes = new Dictionary<string, object?>
{
["context"] = """{"runtime":{"location":{"iso":"2026-04-22T07:15:00-05:00"}}}"""
}
});
Assert.Equal("alarm_value", decision.IntentName);
Assert.Equal("7:18", decision.SkillPayload!["time"]);
Assert.Equal("am", decision.SkillPayload["ampm"]);
}
[Fact]
public async Task BuildDecisionAsync_SetAlarmForSevenTen_UsesNextOccurrenceFromContext()
{
var service = CreateService();
var decision = await service.BuildDecisionAsync(new TurnContext
{
RawTranscript = "set an alarm for 7:10",
NormalizedTranscript = "set an alarm for 7:10",
Attributes = new Dictionary<string, object?>
{
["context"] = """{"runtime":{"location":{"iso":"2026-04-22T07:15:00-05:00"}}}"""
}
});
Assert.Equal("alarm_value", decision.IntentName);
Assert.Equal("7:10", decision.SkillPayload!["time"]);
Assert.Equal("pm", decision.SkillPayload["ampm"]);
}
[Fact] [Fact]
public async Task BuildDecisionAsync_TimerValueFollowUp_ParsesBareDuration() public async Task BuildDecisionAsync_TimerValueFollowUp_ParsesBareDuration()
{ {

View File

@@ -561,6 +561,41 @@ public sealed class JiboWebSocketServiceTests
Assert.Equal("pm", listenPayload.RootElement.GetProperty("data").GetProperty("nlu").GetProperty("entities").GetProperty("ampm").GetString()); Assert.Equal("pm", listenPayload.RootElement.GetProperty("data").GetProperty("nlu").GetProperty("entities").GetProperty("ampm").GetString());
} }
[Fact]
public async Task ClientAsr_SetAlarmForSevenTen_UsesNextOccurrenceFromContext()
{
await _service.HandleMessageAsync(new WebSocketMessageEnvelope
{
HostName = "neo-hub.jibo.com",
Path = "/listen",
Kind = "neo-hub-listen",
Token = "hub-clock-next-occurrence-token",
Text = """{"type":"LISTEN","transID":"trans-clock-next-occurrence","data":{"rules":["globals/global_commands_launch"]}}"""
});
await _service.HandleMessageAsync(new WebSocketMessageEnvelope
{
HostName = "neo-hub.jibo.com",
Path = "/listen",
Kind = "neo-hub-listen",
Token = "hub-clock-next-occurrence-token",
Text = """{"type":"CONTEXT","transID":"trans-clock-next-occurrence","data":{"runtime":{"location":{"iso":"2026-04-22T07:15:00-05:00"}}}}"""
});
var replies = await _service.HandleMessageAsync(new WebSocketMessageEnvelope
{
HostName = "neo-hub.jibo.com",
Path = "/listen",
Kind = "neo-hub-listen",
Token = "hub-clock-next-occurrence-token",
Text = """{"type":"CLIENT_ASR","transID":"trans-clock-next-occurrence","data":{"text":"set an alarm for 7:10"}}"""
});
using var listenPayload = JsonDocument.Parse(replies[0].Text!);
Assert.Equal("7:10", listenPayload.RootElement.GetProperty("data").GetProperty("nlu").GetProperty("entities").GetProperty("time").GetString());
Assert.Equal("pm", listenPayload.RootElement.GetProperty("data").GetProperty("nlu").GetProperty("entities").GetProperty("ampm").GetString());
}
[Fact] [Fact]
public async Task ClientAsr_TimerValueFollowUp_ParsesBareDurationIntoClockStartIntent() public async Task ClientAsr_TimerValueFollowUp_ParsesBareDurationIntoClockStartIntent()
{ {
@@ -739,6 +774,36 @@ public sealed class JiboWebSocketServiceTests
Assert.Equal(0, session.TurnState.BufferedAudioChunkCount); Assert.Equal(0, session.TurnState.BufferedAudioChunkCount);
} }
[Fact]
public async Task ClientAsr_AlarmTimerOkayEmptyReply_MapsToLocalNoInputInsteadOfFallback()
{
await _service.HandleMessageAsync(new WebSocketMessageEnvelope
{
HostName = "neo-hub.jibo.com",
Path = "/listen",
Kind = "neo-hub-listen",
Token = "hub-clock-alarm-okay-token",
Text = """{"type":"LISTEN","transID":"trans-clock-alarm-okay","data":{"rules":["clock/alarm_timer_okay","globals/gui_nav","globals/mim_repeat","globals/global_commands_launch"]}}"""
});
var replies = await _service.HandleMessageAsync(new WebSocketMessageEnvelope
{
HostName = "neo-hub.jibo.com",
Path = "/listen",
Kind = "neo-hub-listen",
Token = "hub-clock-alarm-okay-token",
Text = """{"type":"CLIENT_ASR","transID":"trans-clock-alarm-okay","data":{}}"""
});
Assert.Equal(2, replies.Count);
Assert.Equal("LISTEN", ReadReplyType(replies[0]));
Assert.Equal("EOS", ReadReplyType(replies[1]));
using var listenPayload = JsonDocument.Parse(replies[0].Text!);
Assert.Equal(string.Empty, listenPayload.RootElement.GetProperty("data").GetProperty("nlu").GetProperty("intent").GetString());
Assert.Equal("clock/alarm_timer_okay", listenPayload.RootElement.GetProperty("data").GetProperty("nlu").GetProperty("rules")[0].GetString());
}
[Fact] [Fact]
public async Task ClientAsr_SnapAPicture_RedirectsIntoCreateSkill() public async Task ClientAsr_SnapAPicture_RedirectsIntoCreateSkill()
{ {

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,4 @@
{
"exception": "System.InvalidOperationException: whisper.cpp returned no transcript for the buffered audio turn.\n at Jibo.Cloud.Infrastructure.Audio.LocalWhisperCppBufferedAudioSttStrategy.TranscribeAsync(TurnContext turn, CancellationToken cancellationToken) in /home/jake-dubin/JiboExperiments/OpenJibo/src/Jibo.Cloud/dotnet/src/Jibo.Cloud.Infrastructure/Audio/LocalWhisperCppBufferedAudioSttStrategy.cs:line 58\n at Jibo.Cloud.Application.Services.WebSocketTurnFinalizationService.ResolveTranscriptAsync(TurnContext turn, CloudSession session, CancellationToken cancellationToken) in /home/jake-dubin/JiboExperiments/OpenJibo/src/Jibo.Cloud/dotnet/src/Jibo.Cloud.Application/Services/WebSocketTurnFinalizationService.cs:line 219",
"message": "Error during STT processing"
}

View File

@@ -0,0 +1,4 @@
{
"exception": "System.InvalidOperationException: whisper.cpp returned no transcript for the buffered audio turn.\n at Jibo.Cloud.Infrastructure.Audio.LocalWhisperCppBufferedAudioSttStrategy.TranscribeAsync(TurnContext turn, CancellationToken cancellationToken) in /home/jake-dubin/JiboExperiments/OpenJibo/src/Jibo.Cloud/dotnet/src/Jibo.Cloud.Infrastructure/Audio/LocalWhisperCppBufferedAudioSttStrategy.cs:line 58\n at Jibo.Cloud.Application.Services.WebSocketTurnFinalizationService.ResolveTranscriptAsync(TurnContext turn, CloudSession session, CancellationToken cancellationToken) in /home/jake-dubin/JiboExperiments/OpenJibo/src/Jibo.Cloud/dotnet/src/Jibo.Cloud.Application/Services/WebSocketTurnFinalizationService.cs:line 242",
"message": "Error during STT processing"
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,127 @@
{
"name": "neohubjibocom-neohublisten-tid52b6f46e3b6a11f191515cf821ea55ae",
"session": {
"hostName": "neo-hub.jibo.com",
"path": "/v1/listen",
"kind": "neo-hub-listen",
"token": "hub-usr_openjibo_owner-1776545569192"
},
"steps": [
{
"text": {
"type": "LISTEN",
"ts": 1776546327052,
"msgID": "mid-5303b5f6-3b6a-11f1-adc4-5cf821ea55ae",
"transID": "tid-52b6f46e-3b6a-11f1-9151-5cf821ea55ae",
"data": {
"lang": "en-US",
"hotphrase": true,
"rules": [
"launch",
"globals/global_commands_launch"
],
"mode": "CLIENT_ASR",
"asr": {
"hints": [],
"earlyEOS": [],
"encoding": "OGG_OPUS",
"sampleRate": 16000,
"sosTimeout": 7000,
"maxSpeechTimeout": 20000
}
}
},
"binary": null,
"expectedReplyTypes": [
"OPENJIBO_TURN_PENDING"
]
},
{
"text": {
"type": "CLIENT_ASR",
"ts": 1776546327052,
"msgID": "mid-5303bd76-3b6a-11f1-81d4-5cf821ea55ae",
"transID": "tid-52b6f46e-3b6a-11f1-9151-5cf821ea55ae",
"data": {
"text": "tell me about the news"
}
},
"binary": null,
"expectedReplyTypes": [
"LISTEN",
"EOS",
"SKILL_ACTION"
]
},
{
"text": {
"type": "CONTEXT",
"ts": 1776546327177,
"msgID": "mid-5316cf2e-3b6a-11f1-8aa7-5cf821ea55ae",
"transID": "tid-52b6f46e-3b6a-11f1-9151-5cf821ea55ae",
"data": {
"runtime": {
"character": {
"emotion": {
"name": "NEUTRAL",
"valence": 0.45,
"confidence": 0.2
},
"motivation": {
"social": 1,
"playful": 1
}
},
"perception": {
"speaker": null,
"peoplePresent": []
},
"location": {
"city": "Pleasant Hill",
"state": "Missouri",
"stateAbbr": "MO",
"country": "United States",
"countryCode": "US",
"lat": 38.8358494,
"lng": -94.1427229,
"iso": "2026-04-18T16:05:27.073-05:00"
},
"loop": {
"loopId": "5c0b221fdf9d450019c5e253",
"users": [
{
"firstName": "Erin",
"lastName": "Picone",
"phoneticName": "Erin",
"gender": "female",
"birthdate": 649209600000,
"id": "5c0b221fdf9d450019c5e255",
"accountId": "5c0b20547c46170019235759"
}
],
"jibo": {
"color": "WHITE",
"birthdate": 1544234645598,
"id": "5c0b221fdf9d450019c5e254"
},
"owner": "5c0b221fdf9d450019c5e255"
},
"dialog": {
"referent": null
}
},
"skill": {
"id": null
},
"general": {
"release": "1.9.2"
}
}
},
"binary": null,
"expectedReplyTypes": [
"OPENJIBO_CONTEXT_ACK"
]
}
]
}

View File

@@ -0,0 +1,174 @@
{
"name": "neohubjibocom-neohublisten-tida8165b823b9411f195545cf821ea55ae",
"session": {
"hostName": "neo-hub.jibo.com",
"path": "/v1/listen",
"kind": "neo-hub-listen",
"token": "hub-usr_openjibo_owner-1776563295273"
},
"steps": [
{
"text": {
"type": "LISTEN",
"ts": 1776564508900,
"msgID": "mid-a83d9076-3b94-11f1-978d-5cf821ea55ae",
"transID": "tid-a8165b82-3b94-11f1-9554-5cf821ea55ae",
"data": {
"lang": "en-US",
"hotphrase": true,
"rules": [
"launch",
"globals/global_commands_launch"
],
"mode": "",
"asr": {
"hints": [],
"earlyEOS": [],
"encoding": "OGG_OPUS",
"sampleRate": 16000,
"sosTimeout": 7000,
"maxSpeechTimeout": 20000
}
}
},
"binary": null,
"expectedReplyTypes": [
"LISTEN",
"EOS",
"SKILL_ACTION"
]
},
{
"text": null,
"binary": [
79,
103,
103,
83,
0,
2,
0,
0,
0,
0,
0,
0,
0,
0,
62,
200,
6,
48,
0,
0,
0,
0,
146,
142,
146,
101,
1,
19,
79,
112,
117,
115,
72,
101,
97,
100,
1,
1,
0,
0,
128,
62,
0,
0,
0,
0,
0
],
"expectedReplyTypes": [
"OPENJIBO_AUDIO_RECEIVED"
]
},
{
"text": {
"type": "CONTEXT",
"ts": 1776564510009,
"msgID": "mid-a8e6d640-3b94-11f1-b2b0-5cf821ea55ae",
"transID": "tid-a8165b82-3b94-11f1-9554-5cf821ea55ae",
"data": {
"runtime": {
"character": {
"emotion": {
"name": "NEUTRAL",
"valence": 0.45,
"confidence": 0.2
},
"motivation": {
"social": 1,
"playful": 1
}
},
"perception": {
"speaker": null,
"peoplePresent": [
{
"id": "NOT_TRAINED",
"entityId": 1282,
"type": "fused",
"confidence": 0.25
}
]
},
"location": {
"city": "Pleasant Hill",
"state": "Missouri",
"stateAbbr": "MO",
"country": "United States",
"countryCode": "US",
"lat": 38.8358494,
"lng": -94.1427229,
"iso": "2026-04-18T21:08:29.921-05:00"
},
"loop": {
"loopId": "5c0b221fdf9d450019c5e253",
"users": [
{
"firstName": "Erin",
"lastName": "Picone",
"phoneticName": "Erin",
"gender": "female",
"birthdate": 649209600000,
"id": "5c0b221fdf9d450019c5e255",
"accountId": "5c0b20547c46170019235759"
}
],
"jibo": {
"color": "WHITE",
"birthdate": 1544234645598,
"id": "5c0b221fdf9d450019c5e254"
},
"owner": "5c0b221fdf9d450019c5e255"
},
"dialog": {
"referent": null
}
},
"skill": {
"id": null
},
"general": {
"release": "1.9.2"
}
}
},
"binary": null,
"expectedReplyTypes": [
"OPENJIBO_CONTEXT_ACK"
]
}
]
}

View File

@@ -0,0 +1,95 @@
{
"name": "neohubjibocom-neohublisten-tidd2b591403b6811f1a5735cf821ea55ae",
"session": {
"hostName": "neo-hub.jibo.com",
"path": "/v1/listen",
"kind": "neo-hub-listen",
"token": "hub-usr_openjibo_owner-1776545569192"
},
"steps": [
{
"text": {
"type": "LISTEN",
"ts": 1776545682509,
"msgID": "mid-d2d657ea-3b68-11f1-985b-5cf821ea55ae",
"transID": "tid-d2b59140-3b68-11f1-a573-5cf821ea55ae",
"data": {
"lang": "en-US",
"hotphrase": true,
"rules": [
"launch",
"globals/global_commands_launch"
],
"mode": "",
"asr": {
"hints": [],
"earlyEOS": [],
"encoding": "OGG_OPUS",
"sampleRate": 16000,
"sosTimeout": 7000,
"maxSpeechTimeout": 20000
}
}
},
"binary": null,
"expectedReplyTypes": [
"OPENJIBO_TURN_PENDING"
]
},
{
"text": null,
"binary": [
79,
103,
103,
83,
0,
2,
0,
0,
0,
0,
0,
0,
0,
0,
94,
132,
99,
103,
0,
0,
0,
0,
234,
141,
12,
246,
1,
19,
79,
112,
117,
115,
72,
101,
97,
100,
1,
1,
0,
0,
128,
62,
0,
0,
0,
0,
0
],
"expectedReplyTypes": [
"OPENJIBO_AUDIO_RECEIVED"
]
}
]
}

File diff suppressed because it is too large Load Diff