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_ASR` turn completion, including a synthetic STT seam for buffered-audio replay
- `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
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
- `CONTEXT` capture and follow-up turn state
- `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
- 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;
}
if (reply.DelayMs > 0)
{
await Task.Delay(reply.DelayMs, context.RequestAborted);
}
var payload = Encoding.UTF8.GetBytes(reply.Text);
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 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 skill = plan.Actions.OfType<InvokeNativeSkillAction>().FirstOrDefault();
@@ -15,9 +15,9 @@ public sealed class ResponsePlanToSocketMessagesMapper
: session.LastTransId ?? string.Empty;
var transcript = turn.NormalizedTranscript ?? turn.RawTranscript ?? string.Empty;
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",
transID = transId,
@@ -43,9 +43,9 @@ public sealed class ResponsePlanToSocketMessagesMapper
score = 0.95
}
}
}));
})));
messages.Add(JsonSerializer.Serialize(new
messages.Add(new SocketReplyPlan(JsonSerializer.Serialize(new
{
type = "EOS",
data = new
@@ -53,21 +53,23 @@ public sealed class ResponsePlanToSocketMessagesMapper
sessionId = plan.SessionId,
transID = transId
}
}));
})));
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;
}
public IReadOnlyList<string> MapFallback(CloudSession session, string transId, IReadOnlyList<string> rules)
public IReadOnlyList<SocketReplyPlan> MapFallback(CloudSession session, string transId, IReadOnlyList<string> rules)
{
return
[
JsonSerializer.Serialize(new
new SocketReplyPlan(JsonSerializer.Serialize(new
{
type = "LISTEN",
transID = transId,
@@ -93,8 +95,8 @@ public sealed class ResponsePlanToSocketMessagesMapper
score = 0.95
}
}
}),
JsonSerializer.Serialize(new
})),
new SocketReplyPlan(JsonSerializer.Serialize(new
{
type = "EOS",
data = new
@@ -102,8 +104,8 @@ public sealed class ResponsePlanToSocketMessagesMapper
sessionId = session.SessionId,
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("'", "&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.LastListenType = "fallback";
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();
ResetBufferedAudio(session);
return fallbackReplies;
@@ -308,9 +308,10 @@ public sealed class WebSocketTurnFinalizationService(
turnState.AwaitingTurnCompletion = false;
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();
ResetBufferedAudio(session);

View File

@@ -3,5 +3,6 @@ namespace Jibo.Cloud.Domain.Models;
public sealed class WebSocketReply
{
public string? Text { get; init; }
public int DelayMs { 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("EOS", ReadReplyType(replies[1]));
Assert.Equal("SKILL_ACTION", ReadReplyType(replies[2]));
Assert.Equal(75, replies[2].DelayMs);
using var listenPayload = JsonDocument.Parse(replies[0].Text!);
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("EOS", ReadReplyType(replies[1]));
Assert.Equal("SKILL_ACTION", ReadReplyType(replies[2]));
Assert.Equal(75, replies[2].DelayMs);
using var listenPayload = JsonDocument.Parse(replies[0].Text!);
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("EOS", ReadReplyType(replies[1]));
Assert.Equal("SKILL_ACTION", ReadReplyType(replies[2]));
Assert.Equal(75, replies[2].DelayMs);
using var listenPayload = JsonDocument.Parse(replies[0].Text!);
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("EOS", ReadReplyType(finalizeReplies[1]));
Assert.Equal("SKILL_ACTION", ReadReplyType(finalizeReplies[2]));
Assert.Equal(75, finalizeReplies[2].DelayMs);
using var listenPayload = JsonDocument.Parse(finalizeReplies[0].Text!);
Assert.Equal("tell me a joke", listenPayload.RootElement.GetProperty("data").GetProperty("asr").GetProperty("text").GetString());