fixes for test paths

This commit is contained in:
Jacob Dubin
2026-04-15 18:24:18 -05:00
parent fe187f9e1a
commit 88b309aa76
7 changed files with 33 additions and 16 deletions

View File

@@ -72,6 +72,7 @@ The current .NET pass covers only a narrow, explicitly synthetic subset of obser
- `CLIENT_NLU` turn completion using remembered listen/session metadata - `CLIENT_NLU` turn completion using remembered listen/session metadata
- `CLIENT_ASR` turn completion, including a synthetic STT seam for buffered-audio replay - `CLIENT_ASR` turn completion, including a synthetic STT seam for buffered-audio replay
- `EOS` emission after completed turns - `EOS` emission after completed turns
- delayed `SKILL_ACTION` emission after `EOS` on completed turn flows to better match the Node oracle timing
- first richer vertical slice for joke/chat `SKILL_ACTION` playback - first richer vertical slice for joke/chat `SKILL_ACTION` playback
This does not yet mean parity for: This does not yet mean parity for:

View File

@@ -76,6 +76,7 @@ Current websocket scope is still intentionally narrow:
- structured websocket telemetry and live-run fixture export - structured websocket telemetry and live-run fixture export
- `CONTEXT` capture and follow-up turn state - `CONTEXT` capture and follow-up turn state
- `EOS` completion - `EOS` completion
- delayed `SKILL_ACTION` emission after `EOS` to preserve the current Node-observed turn sequence
- first skill vertical for joke/chat `SKILL_ACTION` playback - first skill vertical for joke/chat `SKILL_ACTION` playback
- repo-root live-run capture support for both `captures/http/` and `captures/websocket/` - repo-root live-run capture support for both `captures/http/` and `captures/websocket/`

View File

@@ -86,6 +86,11 @@ app.Use(async (context, next) =>
continue; continue;
} }
if (reply.DelayMs > 0)
{
await Task.Delay(reply.DelayMs, context.RequestAborted);
}
var payload = Encoding.UTF8.GetBytes(reply.Text); var payload = Encoding.UTF8.GetBytes(reply.Text);
await socket.SendAsync(payload, WebSocketMessageType.Text, true, context.RequestAborted); await socket.SendAsync(payload, WebSocketMessageType.Text, true, context.RequestAborted);
} }

View File

@@ -6,7 +6,7 @@ namespace Jibo.Cloud.Application.Services;
public sealed class ResponsePlanToSocketMessagesMapper public sealed class ResponsePlanToSocketMessagesMapper
{ {
public IReadOnlyList<string> Map(ResponsePlan plan, TurnContext turn, CloudSession session, bool emitSkillActions) public IReadOnlyList<SocketReplyPlan> Map(ResponsePlan plan, TurnContext turn, CloudSession session, bool emitSkillActions)
{ {
var speak = plan.Actions.OfType<SpeakAction>().FirstOrDefault(); var speak = plan.Actions.OfType<SpeakAction>().FirstOrDefault();
var skill = plan.Actions.OfType<InvokeNativeSkillAction>().FirstOrDefault(); var skill = plan.Actions.OfType<InvokeNativeSkillAction>().FirstOrDefault();
@@ -15,9 +15,9 @@ public sealed class ResponsePlanToSocketMessagesMapper
: session.LastTransId ?? string.Empty; : session.LastTransId ?? string.Empty;
var transcript = turn.NormalizedTranscript ?? turn.RawTranscript ?? string.Empty; var transcript = turn.NormalizedTranscript ?? turn.RawTranscript ?? string.Empty;
var rules = ReadRules(turn); var rules = ReadRules(turn);
var messages = new List<string>(); var messages = new List<SocketReplyPlan>();
messages.Add(JsonSerializer.Serialize(new messages.Add(new SocketReplyPlan(JsonSerializer.Serialize(new
{ {
type = "LISTEN", type = "LISTEN",
transID = transId, transID = transId,
@@ -43,9 +43,9 @@ public sealed class ResponsePlanToSocketMessagesMapper
score = 0.95 score = 0.95
} }
} }
})); })));
messages.Add(JsonSerializer.Serialize(new messages.Add(new SocketReplyPlan(JsonSerializer.Serialize(new
{ {
type = "EOS", type = "EOS",
data = new data = new
@@ -53,21 +53,23 @@ public sealed class ResponsePlanToSocketMessagesMapper
sessionId = plan.SessionId, sessionId = plan.SessionId,
transID = transId transID = transId
} }
})); })));
if (emitSkillActions && speak is not null) if (emitSkillActions && speak is not null)
{ {
messages.Add(JsonSerializer.Serialize(BuildSkillPayload(plan, turn, transId, speak, skill))); messages.Add(new SocketReplyPlan(
JsonSerializer.Serialize(BuildSkillPayload(plan, turn, transId, speak, skill)),
DelayMs: 75));
} }
return messages; return messages;
} }
public IReadOnlyList<string> MapFallback(CloudSession session, string transId, IReadOnlyList<string> rules) public IReadOnlyList<SocketReplyPlan> MapFallback(CloudSession session, string transId, IReadOnlyList<string> rules)
{ {
return return
[ [
JsonSerializer.Serialize(new new SocketReplyPlan(JsonSerializer.Serialize(new
{ {
type = "LISTEN", type = "LISTEN",
transID = transId, transID = transId,
@@ -93,8 +95,8 @@ public sealed class ResponsePlanToSocketMessagesMapper
score = 0.95 score = 0.95
} }
} }
}), })),
JsonSerializer.Serialize(new new SocketReplyPlan(JsonSerializer.Serialize(new
{ {
type = "EOS", type = "EOS",
data = new data = new
@@ -102,8 +104,8 @@ public sealed class ResponsePlanToSocketMessagesMapper
sessionId = session.SessionId, sessionId = session.SessionId,
transID = transId transID = transId
} }
}), })),
JsonSerializer.Serialize(BuildGenericFallbackSkillPayload(transId)) new SocketReplyPlan(JsonSerializer.Serialize(BuildGenericFallbackSkillPayload(transId)), DelayMs: 75)
]; ];
} }
@@ -231,4 +233,6 @@ public sealed class ResponsePlanToSocketMessagesMapper
.Replace("\"", "&quot;", StringComparison.Ordinal) .Replace("\"", "&quot;", StringComparison.Ordinal)
.Replace("'", "&apos;", StringComparison.Ordinal); .Replace("'", "&apos;", StringComparison.Ordinal);
} }
public sealed record SocketReplyPlan(string Text, int DelayMs = 0);
} }

View File

@@ -269,7 +269,7 @@ public sealed class WebSocketTurnFinalizationService(
session.LastIntent = "heyJibo"; session.LastIntent = "heyJibo";
session.LastListenType = "fallback"; session.LastListenType = "fallback";
var fallbackReplies = replyMapper.MapFallback(session, turnState.TransId ?? session.LastTransId ?? string.Empty, turnState.ListenRules) var fallbackReplies = replyMapper.MapFallback(session, turnState.TransId ?? session.LastTransId ?? string.Empty, turnState.ListenRules)
.Select(text => new WebSocketReply { Text = text }) .Select(map => new WebSocketReply { Text = map.Text, DelayMs = map.DelayMs })
.ToArray(); .ToArray();
ResetBufferedAudio(session); ResetBufferedAudio(session);
return fallbackReplies; return fallbackReplies;
@@ -308,9 +308,10 @@ public sealed class WebSocketTurnFinalizationService(
turnState.AwaitingTurnCompletion = false; turnState.AwaitingTurnCompletion = false;
var emitSkillActions = messageType != "CLIENT_NLU"; var emitSkillActions = messageType != "CLIENT_NLU";
var replies = replyMapper.Map(plan, finalizedTurn, session, emitSkillActions).Select(text => new WebSocketReply var replies = replyMapper.Map(plan, finalizedTurn, session, emitSkillActions).Select(map => new WebSocketReply
{ {
Text = text Text = map.Text,
DelayMs = map.DelayMs
}).ToArray(); }).ToArray();
ResetBufferedAudio(session); ResetBufferedAudio(session);

View File

@@ -3,5 +3,6 @@ namespace Jibo.Cloud.Domain.Models;
public sealed class WebSocketReply public sealed class WebSocketReply
{ {
public string? Text { get; init; } public string? Text { get; init; }
public int DelayMs { get; init; }
public bool Close { get; init; } public bool Close { get; init; }
} }

View File

@@ -49,6 +49,7 @@ public sealed class JiboWebSocketServiceTests
Assert.Equal("LISTEN", ReadReplyType(replies[0])); Assert.Equal("LISTEN", ReadReplyType(replies[0]));
Assert.Equal("EOS", ReadReplyType(replies[1])); Assert.Equal("EOS", ReadReplyType(replies[1]));
Assert.Equal("SKILL_ACTION", ReadReplyType(replies[2])); Assert.Equal("SKILL_ACTION", ReadReplyType(replies[2]));
Assert.Equal(75, replies[2].DelayMs);
using var listenPayload = JsonDocument.Parse(replies[0].Text!); using var listenPayload = JsonDocument.Parse(replies[0].Text!);
Assert.Equal("hello jibo", listenPayload.RootElement.GetProperty("data").GetProperty("asr").GetProperty("text").GetString()); Assert.Equal("hello jibo", listenPayload.RootElement.GetProperty("data").GetProperty("asr").GetProperty("text").GetString());
@@ -124,6 +125,7 @@ public sealed class JiboWebSocketServiceTests
Assert.Equal("LISTEN", ReadReplyType(replies[0])); Assert.Equal("LISTEN", ReadReplyType(replies[0]));
Assert.Equal("EOS", ReadReplyType(replies[1])); Assert.Equal("EOS", ReadReplyType(replies[1]));
Assert.Equal("SKILL_ACTION", ReadReplyType(replies[2])); Assert.Equal("SKILL_ACTION", ReadReplyType(replies[2]));
Assert.Equal(75, replies[2].DelayMs);
using var listenPayload = JsonDocument.Parse(replies[0].Text!); using var listenPayload = JsonDocument.Parse(replies[0].Text!);
Assert.Equal("tell me a joke", listenPayload.RootElement.GetProperty("data").GetProperty("asr").GetProperty("text").GetString()); Assert.Equal("tell me a joke", listenPayload.RootElement.GetProperty("data").GetProperty("asr").GetProperty("text").GetString());
@@ -180,6 +182,7 @@ public sealed class JiboWebSocketServiceTests
Assert.Equal("LISTEN", ReadReplyType(replies[0])); Assert.Equal("LISTEN", ReadReplyType(replies[0]));
Assert.Equal("EOS", ReadReplyType(replies[1])); Assert.Equal("EOS", ReadReplyType(replies[1]));
Assert.Equal("SKILL_ACTION", ReadReplyType(replies[2])); Assert.Equal("SKILL_ACTION", ReadReplyType(replies[2]));
Assert.Equal(75, replies[2].DelayMs);
using var listenPayload = JsonDocument.Parse(replies[0].Text!); using var listenPayload = JsonDocument.Parse(replies[0].Text!);
Assert.Equal("heyJibo", listenPayload.RootElement.GetProperty("data").GetProperty("nlu").GetProperty("intent").GetString()); Assert.Equal("heyJibo", listenPayload.RootElement.GetProperty("data").GetProperty("nlu").GetProperty("intent").GetString());
@@ -324,6 +327,7 @@ public sealed class JiboWebSocketServiceTests
Assert.Equal("LISTEN", ReadReplyType(finalizeReplies[0])); Assert.Equal("LISTEN", ReadReplyType(finalizeReplies[0]));
Assert.Equal("EOS", ReadReplyType(finalizeReplies[1])); Assert.Equal("EOS", ReadReplyType(finalizeReplies[1]));
Assert.Equal("SKILL_ACTION", ReadReplyType(finalizeReplies[2])); Assert.Equal("SKILL_ACTION", ReadReplyType(finalizeReplies[2]));
Assert.Equal(75, finalizeReplies[2].DelayMs);
using var listenPayload = JsonDocument.Parse(finalizeReplies[0].Text!); using var listenPayload = JsonDocument.Parse(finalizeReplies[0].Text!);
Assert.Equal("tell me a joke", listenPayload.RootElement.GetProperty("data").GetProperty("asr").GetProperty("text").GetString()); Assert.Equal("tell me a joke", listenPayload.RootElement.GetProperty("data").GetProperty("asr").GetProperty("text").GetString());