@voicethere/agent 0.5.4 → 0.5.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/agent.js +6 -0
- package/dist/runtime.d.ts.map +1 -1
- package/dist/runtime.js +13 -1
- package/dist/runtime.js.map +1 -1
- package/dist/templates/echo/agent.js +3 -0
- package/dist/templates/echo-dc/agent.js +3 -0
- package/dist/templates/game-sync/agent.js +119 -49
- package/dist/templates/recording-consent/agent.js +1003 -0
- package/dist/templates/registry.d.ts.map +1 -1
- package/dist/templates/registry.js +11 -0
- package/dist/templates/registry.js.map +1 -1
- package/dist/templates/voice-showcase/agent.js +168 -10
- package/dist/templates/voice-starter/agent.js +3 -0
- package/dist/templates/webhooks/agent.js +3 -0
- package/dist/templates/webhooks-redis/agent.js +3 -0
- package/package.json +1 -1
- package/templates/README.md +10 -4
- package/templates/game-sync-protocol.ts +13 -0
- package/templates/game-sync-world-layout.ts +27 -0
- package/templates/game-sync.ts +114 -60
- package/templates/recording-consent/agent.ts +162 -0
- package/templates/recording-consent/conversation.ts +300 -0
- package/templates/voice-showcase/conversation.ts +17 -6
- package/templates/voice-showcase/weather.ts +184 -5
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"registry.d.ts","sourceRoot":"","sources":["../../src/templates/registry.ts"],"names":[],"mappings":"AAAA,MAAM,MAAM,YAAY,GAAG,SAAS,GAAG,KAAK,CAAC;AAE7C,MAAM,WAAW,uBAAuB;IACtC,EAAE,EAAE,MAAM,CAAC;IACX,6EAA6E;IAC7E,KAAK,EAAE,MAAM,CAAC;IACd,IAAI,EAAE,YAAY,CAAC;IACnB,mFAAmF;IACnF,YAAY,EAAE,OAAO,CAAC;IACtB,WAAW,EAAE,MAAM,CAAC;IACpB,yEAAyE;IACzE,WAAW,EAAE,MAAM,EAAE,CAAC;CACvB;AAED,eAAO,MAAM,eAAe,EAAE,SAAS,uBAAuB,
|
|
1
|
+
{"version":3,"file":"registry.d.ts","sourceRoot":"","sources":["../../src/templates/registry.ts"],"names":[],"mappings":"AAAA,MAAM,MAAM,YAAY,GAAG,SAAS,GAAG,KAAK,CAAC;AAE7C,MAAM,WAAW,uBAAuB;IACtC,EAAE,EAAE,MAAM,CAAC;IACX,6EAA6E;IAC7E,KAAK,EAAE,MAAM,CAAC;IACd,IAAI,EAAE,YAAY,CAAC;IACnB,mFAAmF;IACnF,YAAY,EAAE,OAAO,CAAC;IACtB,WAAW,EAAE,MAAM,CAAC;IACpB,yEAAyE;IACzE,WAAW,EAAE,MAAM,EAAE,CAAC;CACvB;AAED,eAAO,MAAM,eAAe,EAAE,SAAS,uBAAuB,EA4HpD,CAAC;AAEX,MAAM,MAAM,eAAe,GAAG,CAAC,OAAO,eAAe,CAAC,CAAC,MAAM,CAAC,CAAC,IAAI,CAAC,CAAC;AAMrE,wBAAgB,iBAAiB,CAAC,EAAE,EAAE,MAAM,GAAG,EAAE,IAAI,eAAe,CAEnE;AAED,wBAAgB,eAAe,CAAC,EAAE,EAAE,MAAM,GAAG,uBAAuB,CAMnE;AAED,wBAAgB,yBAAyB,IAAI,uBAAuB,EAAE,CAErE"}
|
|
@@ -51,6 +51,17 @@ export const AGENT_TEMPLATES = [
|
|
|
51
51
|
"voice-showcase/fun-facts.ts",
|
|
52
52
|
],
|
|
53
53
|
},
|
|
54
|
+
{
|
|
55
|
+
id: "recording-consent",
|
|
56
|
+
entry: "recording-consent/agent.ts",
|
|
57
|
+
kind: "product",
|
|
58
|
+
seedOnCreate: true,
|
|
59
|
+
description: "Recording consent flow — ask consent, pause capture for PII, resume when allowed.",
|
|
60
|
+
sourceFiles: [
|
|
61
|
+
"recording-consent/agent.ts",
|
|
62
|
+
"recording-consent/conversation.ts",
|
|
63
|
+
],
|
|
64
|
+
},
|
|
54
65
|
{
|
|
55
66
|
id: "webhooks",
|
|
56
67
|
entry: "webhooks.ts",
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"registry.js","sourceRoot":"","sources":["../../src/templates/registry.ts"],"names":[],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"registry.js","sourceRoot":"","sources":["../../src/templates/registry.ts"],"names":[],"mappings":"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"}
|
|
@@ -191,6 +191,7 @@ function parseDataChannelPayload(raw) {
|
|
|
191
191
|
}
|
|
192
192
|
}
|
|
193
193
|
var peerEnvBySessionId = /* @__PURE__ */ new Map();
|
|
194
|
+
var recordingAvailableBySessionId = /* @__PURE__ */ new Map();
|
|
194
195
|
var endedSessionIds = /* @__PURE__ */ new Set();
|
|
195
196
|
var pendingRecordingAcks = /* @__PURE__ */ new Map();
|
|
196
197
|
function handleRecordingControlAck(message) {
|
|
@@ -340,6 +341,7 @@ async function handleParentMessage(message, handlers) {
|
|
|
340
341
|
case "session_start":
|
|
341
342
|
endedSessionIds.delete(message.sessionId);
|
|
342
343
|
peerEnvBySessionId.set(message.sessionId, message.env);
|
|
344
|
+
recordingAvailableBySessionId.set(message.sessionId, message.recordingAvailable ?? false);
|
|
343
345
|
const sessionStartInitDelayMs = resolveSessionStartInitDelayMs();
|
|
344
346
|
if (sessionStartInitDelayMs > 0) {
|
|
345
347
|
await new Promise((resolve) => setTimeout(resolve, sessionStartInitDelayMs));
|
|
@@ -384,6 +386,7 @@ async function handleParentMessage(message, handlers) {
|
|
|
384
386
|
case "session_end":
|
|
385
387
|
clearPendingRecordingAcksForSession(message.sessionId, "session_ended");
|
|
386
388
|
peerEnvBySessionId.delete(message.sessionId);
|
|
389
|
+
recordingAvailableBySessionId.delete(message.sessionId);
|
|
387
390
|
await (handlers.onClientLeave ?? handlers.onSessionEnd)?.({
|
|
388
391
|
sessionId: message.sessionId
|
|
389
392
|
});
|
|
@@ -669,6 +672,136 @@ function pickFunFact() {
|
|
|
669
672
|
var GEOCODE_URL = "https://geocoding-api.open-meteo.com/v1/search";
|
|
670
673
|
var FORECAST_URL = "https://api.open-meteo.com/v1/forecast";
|
|
671
674
|
var FETCH_TIMEOUT_MS = 8e3;
|
|
675
|
+
var DIGIT_WORDS = {
|
|
676
|
+
zero: "0",
|
|
677
|
+
oh: "0",
|
|
678
|
+
o: "0",
|
|
679
|
+
one: "1",
|
|
680
|
+
two: "2",
|
|
681
|
+
three: "3",
|
|
682
|
+
four: "4",
|
|
683
|
+
five: "5",
|
|
684
|
+
six: "6",
|
|
685
|
+
seven: "7",
|
|
686
|
+
eight: "8",
|
|
687
|
+
nine: "9"
|
|
688
|
+
};
|
|
689
|
+
var COUNTRY_ALIASES = {
|
|
690
|
+
thailand: "Thailand",
|
|
691
|
+
us: "United States",
|
|
692
|
+
usa: "United States",
|
|
693
|
+
america: "United States",
|
|
694
|
+
"united states": "United States",
|
|
695
|
+
"united states of america": "United States",
|
|
696
|
+
uk: "United Kingdom",
|
|
697
|
+
britain: "United Kingdom",
|
|
698
|
+
england: "United Kingdom",
|
|
699
|
+
"united kingdom": "United Kingdom",
|
|
700
|
+
"great britain": "United Kingdom",
|
|
701
|
+
germany: "Germany",
|
|
702
|
+
france: "France",
|
|
703
|
+
spain: "Spain",
|
|
704
|
+
italy: "Italy",
|
|
705
|
+
japan: "Japan",
|
|
706
|
+
china: "China",
|
|
707
|
+
india: "India",
|
|
708
|
+
australia: "Australia",
|
|
709
|
+
canada: "Canada",
|
|
710
|
+
brazil: "Brazil",
|
|
711
|
+
mexico: "Mexico",
|
|
712
|
+
netherlands: "Netherlands",
|
|
713
|
+
holland: "Netherlands",
|
|
714
|
+
"the netherlands": "Netherlands",
|
|
715
|
+
belgium: "Belgium",
|
|
716
|
+
switzerland: "Switzerland",
|
|
717
|
+
sweden: "Sweden",
|
|
718
|
+
norway: "Norway",
|
|
719
|
+
denmark: "Denmark",
|
|
720
|
+
finland: "Finland",
|
|
721
|
+
poland: "Poland",
|
|
722
|
+
portugal: "Portugal",
|
|
723
|
+
greece: "Greece",
|
|
724
|
+
turkey: "Turkey",
|
|
725
|
+
egypt: "Egypt",
|
|
726
|
+
"south africa": "South Africa",
|
|
727
|
+
"new zealand": "New Zealand",
|
|
728
|
+
ireland: "Ireland",
|
|
729
|
+
singapore: "Singapore",
|
|
730
|
+
malaysia: "Malaysia",
|
|
731
|
+
indonesia: "Indonesia",
|
|
732
|
+
vietnam: "Vietnam",
|
|
733
|
+
philippines: "Philippines",
|
|
734
|
+
"south korea": "South Korea",
|
|
735
|
+
korea: "South Korea",
|
|
736
|
+
taiwan: "Taiwan",
|
|
737
|
+
"hong kong": "Hong Kong",
|
|
738
|
+
israel: "Israel",
|
|
739
|
+
uae: "United Arab Emirates",
|
|
740
|
+
"united arab emirates": "United Arab Emirates",
|
|
741
|
+
"saudi arabia": "Saudi Arabia",
|
|
742
|
+
pakistan: "Pakistan",
|
|
743
|
+
bangladesh: "Bangladesh",
|
|
744
|
+
nigeria: "Nigeria",
|
|
745
|
+
kenya: "Kenya",
|
|
746
|
+
argentina: "Argentina",
|
|
747
|
+
chile: "Chile",
|
|
748
|
+
colombia: "Colombia",
|
|
749
|
+
peru: "Peru",
|
|
750
|
+
austria: "Austria",
|
|
751
|
+
"czech republic": "Czechia",
|
|
752
|
+
czechia: "Czechia",
|
|
753
|
+
romania: "Romania",
|
|
754
|
+
hungary: "Hungary",
|
|
755
|
+
ukraine: "Ukraine"
|
|
756
|
+
};
|
|
757
|
+
function matchCountryName(text) {
|
|
758
|
+
const key = text.trim().toLowerCase().replace(/[.,!?]+$/g, "").replace(/\s+/g, " ");
|
|
759
|
+
if (!key) return null;
|
|
760
|
+
return COUNTRY_ALIASES[key] ?? null;
|
|
761
|
+
}
|
|
762
|
+
function spokenDigitsToPostal(text) {
|
|
763
|
+
const tokens = text.toLowerCase().split(/[^a-z0-9]+/).filter(Boolean);
|
|
764
|
+
const digits = [];
|
|
765
|
+
for (const token of tokens) {
|
|
766
|
+
if (/^\d$/.test(token)) {
|
|
767
|
+
digits.push(token);
|
|
768
|
+
continue;
|
|
769
|
+
}
|
|
770
|
+
const mapped = DIGIT_WORDS[token];
|
|
771
|
+
if (mapped) {
|
|
772
|
+
digits.push(mapped);
|
|
773
|
+
}
|
|
774
|
+
}
|
|
775
|
+
if (digits.length >= 4 && digits.length <= 6) {
|
|
776
|
+
return digits.join("");
|
|
777
|
+
}
|
|
778
|
+
return null;
|
|
779
|
+
}
|
|
780
|
+
function splitTrailingCountry(text) {
|
|
781
|
+
const words = text.trim().split(/\s+/).filter(Boolean);
|
|
782
|
+
if (words.length < 2) return null;
|
|
783
|
+
for (let n = Math.min(3, words.length - 1); n >= 1; n -= 1) {
|
|
784
|
+
const tail = words.slice(-n).join(" ");
|
|
785
|
+
const country = matchCountryName(tail);
|
|
786
|
+
if (country) {
|
|
787
|
+
return { rest: words.slice(0, -n).join(" "), country };
|
|
788
|
+
}
|
|
789
|
+
}
|
|
790
|
+
return null;
|
|
791
|
+
}
|
|
792
|
+
function cityFromRemainder(rest) {
|
|
793
|
+
const trimmed = rest.trim().replace(/[.,!?;:]+$/g, "").replace(/\s+(?:in the|in|of)$/i, "").trim();
|
|
794
|
+
if (!trimmed) return null;
|
|
795
|
+
if (/^\d{4,6}(-\d{4})?$/.test(trimmed)) {
|
|
796
|
+
return trimmed;
|
|
797
|
+
}
|
|
798
|
+
const spoken = spokenDigitsToPostal(trimmed);
|
|
799
|
+
if (spoken) return spoken;
|
|
800
|
+
if (trimmed.length >= 2 && trimmed.length <= 60) {
|
|
801
|
+
return trimmed;
|
|
802
|
+
}
|
|
803
|
+
return null;
|
|
804
|
+
}
|
|
672
805
|
function wmoCodeToPhrase(code) {
|
|
673
806
|
if (code === 0) return "clear sky";
|
|
674
807
|
if (code <= 3) return "partly cloudy";
|
|
@@ -689,29 +822,48 @@ function formatWeatherSpeech(result) {
|
|
|
689
822
|
function parseLocationUtterance(utterance) {
|
|
690
823
|
const text = utterance.trim();
|
|
691
824
|
if (!text) return null;
|
|
825
|
+
const countryOnly = matchCountryName(text);
|
|
826
|
+
if (countryOnly) {
|
|
827
|
+
return { country: countryOnly };
|
|
828
|
+
}
|
|
829
|
+
const trailing = splitTrailingCountry(text);
|
|
830
|
+
if (trailing) {
|
|
831
|
+
const city = cityFromRemainder(trailing.rest);
|
|
832
|
+
if (city) {
|
|
833
|
+
return { city, country: trailing.country };
|
|
834
|
+
}
|
|
835
|
+
return { country: trailing.country };
|
|
836
|
+
}
|
|
692
837
|
const inMatch = text.match(
|
|
693
838
|
/^(?:in\s+)?(.+?)\s+in\s+([a-zA-Z][\w\s.-]{1,40})$/i
|
|
694
839
|
);
|
|
695
840
|
if (inMatch) {
|
|
696
|
-
|
|
841
|
+
const country = matchCountryName(inMatch[2]) ?? inMatch[2].trim();
|
|
842
|
+
return { city: inMatch[1].trim(), country };
|
|
697
843
|
}
|
|
698
844
|
const commaMatch = text.match(/^(.+?),\s*([a-zA-Z][\w\s.-]{1,40})$/);
|
|
699
845
|
if (commaMatch) {
|
|
700
|
-
|
|
846
|
+
const country = matchCountryName(commaMatch[2]) ?? commaMatch[2].trim();
|
|
847
|
+
return { city: commaMatch[1].trim(), country };
|
|
701
848
|
}
|
|
702
849
|
const countryMatch = text.match(
|
|
703
850
|
/^(.+?)\s+(?:country\s+)?([a-zA-Z][\w\s.-]{2,40})$/i
|
|
704
851
|
);
|
|
705
852
|
if (countryMatch && countryMatch[2].split(/\s+/).length <= 3) {
|
|
706
853
|
const city = countryMatch[1].trim();
|
|
707
|
-
const
|
|
854
|
+
const countryRaw = countryMatch[2].trim();
|
|
855
|
+
const country = matchCountryName(countryRaw) ?? countryRaw;
|
|
708
856
|
if (city.length >= 2 && country.length >= 2) {
|
|
709
857
|
return { city, country };
|
|
710
858
|
}
|
|
711
859
|
}
|
|
712
|
-
if (/^\d{
|
|
860
|
+
if (/^\d{4,6}(-\d{4})?$/.test(text)) {
|
|
713
861
|
return { city: text };
|
|
714
862
|
}
|
|
863
|
+
const spokenPostal = spokenDigitsToPostal(text);
|
|
864
|
+
if (spokenPostal) {
|
|
865
|
+
return { city: spokenPostal };
|
|
866
|
+
}
|
|
715
867
|
if (text.length >= 2 && text.length <= 60) {
|
|
716
868
|
return { city: text };
|
|
717
869
|
}
|
|
@@ -1059,8 +1211,15 @@ function handleUtterance(state, utterance) {
|
|
|
1059
1211
|
}
|
|
1060
1212
|
case "weatherAwaitingLocation": {
|
|
1061
1213
|
const parsed = parseLocationUtterance(text);
|
|
1062
|
-
|
|
1063
|
-
|
|
1214
|
+
let city = parsed?.city || state.weatherCity;
|
|
1215
|
+
let country = parsed?.country || state.weatherCountry;
|
|
1216
|
+
if (state.weatherCity && !country) {
|
|
1217
|
+
const followUp = matchCountryName(text) ?? (parsed?.city ? matchCountryName(parsed.city) : null);
|
|
1218
|
+
if (followUp) {
|
|
1219
|
+
city = state.weatherCity;
|
|
1220
|
+
country = followUp;
|
|
1221
|
+
}
|
|
1222
|
+
}
|
|
1064
1223
|
if (!city) {
|
|
1065
1224
|
const ask = speakAndChat(
|
|
1066
1225
|
"Please tell me a city or ZIP code and the country."
|
|
@@ -1071,7 +1230,7 @@ function handleUtterance(state, utterance) {
|
|
|
1071
1230
|
messages: ask.messages
|
|
1072
1231
|
};
|
|
1073
1232
|
}
|
|
1074
|
-
if (!country
|
|
1233
|
+
if (!country) {
|
|
1075
1234
|
return {
|
|
1076
1235
|
state: {
|
|
1077
1236
|
...state,
|
|
@@ -1084,12 +1243,11 @@ function handleUtterance(state, utterance) {
|
|
|
1084
1243
|
]
|
|
1085
1244
|
};
|
|
1086
1245
|
}
|
|
1087
|
-
const resolvedCountry = country ?? state.weatherCountry;
|
|
1088
1246
|
return {
|
|
1089
|
-
state: { ...state, weatherCity: city, weatherCountry:
|
|
1247
|
+
state: { ...state, weatherCity: city, weatherCountry: country },
|
|
1090
1248
|
speakLines: [],
|
|
1091
1249
|
messages: [],
|
|
1092
|
-
pendingWeather: { city, country
|
|
1250
|
+
pendingWeather: { city, country }
|
|
1093
1251
|
};
|
|
1094
1252
|
}
|
|
1095
1253
|
case "countAwaitingNumber": {
|
|
@@ -191,6 +191,7 @@ function parseDataChannelPayload(raw) {
|
|
|
191
191
|
}
|
|
192
192
|
}
|
|
193
193
|
var peerEnvBySessionId = /* @__PURE__ */ new Map();
|
|
194
|
+
var recordingAvailableBySessionId = /* @__PURE__ */ new Map();
|
|
194
195
|
var endedSessionIds = /* @__PURE__ */ new Set();
|
|
195
196
|
var pendingRecordingAcks = /* @__PURE__ */ new Map();
|
|
196
197
|
function handleRecordingControlAck(message) {
|
|
@@ -340,6 +341,7 @@ async function handleParentMessage(message, handlers) {
|
|
|
340
341
|
case "session_start":
|
|
341
342
|
endedSessionIds.delete(message.sessionId);
|
|
342
343
|
peerEnvBySessionId.set(message.sessionId, message.env);
|
|
344
|
+
recordingAvailableBySessionId.set(message.sessionId, message.recordingAvailable ?? false);
|
|
343
345
|
const sessionStartInitDelayMs = resolveSessionStartInitDelayMs();
|
|
344
346
|
if (sessionStartInitDelayMs > 0) {
|
|
345
347
|
await new Promise((resolve) => setTimeout(resolve, sessionStartInitDelayMs));
|
|
@@ -384,6 +386,7 @@ async function handleParentMessage(message, handlers) {
|
|
|
384
386
|
case "session_end":
|
|
385
387
|
clearPendingRecordingAcksForSession(message.sessionId, "session_ended");
|
|
386
388
|
peerEnvBySessionId.delete(message.sessionId);
|
|
389
|
+
recordingAvailableBySessionId.delete(message.sessionId);
|
|
387
390
|
await (handlers.onClientLeave ?? handlers.onSessionEnd)?.({
|
|
388
391
|
sessionId: message.sessionId
|
|
389
392
|
});
|
|
@@ -194,6 +194,7 @@ function parseDataChannelPayload(raw) {
|
|
|
194
194
|
}
|
|
195
195
|
}
|
|
196
196
|
var peerEnvBySessionId = /* @__PURE__ */ new Map();
|
|
197
|
+
var recordingAvailableBySessionId = /* @__PURE__ */ new Map();
|
|
197
198
|
var endedSessionIds = /* @__PURE__ */ new Set();
|
|
198
199
|
var pendingRecordingAcks = /* @__PURE__ */ new Map();
|
|
199
200
|
function handleRecordingControlAck(message) {
|
|
@@ -343,6 +344,7 @@ async function handleParentMessage(message, handlers) {
|
|
|
343
344
|
case "session_start":
|
|
344
345
|
endedSessionIds.delete(message.sessionId);
|
|
345
346
|
peerEnvBySessionId.set(message.sessionId, message.env);
|
|
347
|
+
recordingAvailableBySessionId.set(message.sessionId, message.recordingAvailable ?? false);
|
|
346
348
|
const sessionStartInitDelayMs = resolveSessionStartInitDelayMs();
|
|
347
349
|
if (sessionStartInitDelayMs > 0) {
|
|
348
350
|
await new Promise((resolve) => setTimeout(resolve, sessionStartInitDelayMs));
|
|
@@ -387,6 +389,7 @@ async function handleParentMessage(message, handlers) {
|
|
|
387
389
|
case "session_end":
|
|
388
390
|
clearPendingRecordingAcksForSession(message.sessionId, "session_ended");
|
|
389
391
|
peerEnvBySessionId.delete(message.sessionId);
|
|
392
|
+
recordingAvailableBySessionId.delete(message.sessionId);
|
|
390
393
|
await (handlers.onClientLeave ?? handlers.onSessionEnd)?.({
|
|
391
394
|
sessionId: message.sessionId
|
|
392
395
|
});
|
|
@@ -10946,6 +10946,7 @@ function parseDataChannelPayload(raw) {
|
|
|
10946
10946
|
}
|
|
10947
10947
|
}
|
|
10948
10948
|
var peerEnvBySessionId = /* @__PURE__ */ new Map();
|
|
10949
|
+
var recordingAvailableBySessionId = /* @__PURE__ */ new Map();
|
|
10949
10950
|
var endedSessionIds = /* @__PURE__ */ new Set();
|
|
10950
10951
|
var pendingRecordingAcks = /* @__PURE__ */ new Map();
|
|
10951
10952
|
function handleRecordingControlAck(message) {
|
|
@@ -11095,6 +11096,7 @@ async function handleParentMessage(message, handlers) {
|
|
|
11095
11096
|
case "session_start":
|
|
11096
11097
|
endedSessionIds.delete(message.sessionId);
|
|
11097
11098
|
peerEnvBySessionId.set(message.sessionId, message.env);
|
|
11099
|
+
recordingAvailableBySessionId.set(message.sessionId, message.recordingAvailable ?? false);
|
|
11098
11100
|
const sessionStartInitDelayMs = resolveSessionStartInitDelayMs();
|
|
11099
11101
|
if (sessionStartInitDelayMs > 0) {
|
|
11100
11102
|
await new Promise((resolve) => setTimeout(resolve, sessionStartInitDelayMs));
|
|
@@ -11139,6 +11141,7 @@ async function handleParentMessage(message, handlers) {
|
|
|
11139
11141
|
case "session_end":
|
|
11140
11142
|
clearPendingRecordingAcksForSession(message.sessionId, "session_ended");
|
|
11141
11143
|
peerEnvBySessionId.delete(message.sessionId);
|
|
11144
|
+
recordingAvailableBySessionId.delete(message.sessionId);
|
|
11142
11145
|
await (handlers.onClientLeave ?? handlers.onSessionEnd)?.({
|
|
11143
11146
|
sessionId: message.sessionId
|
|
11144
11147
|
});
|
package/package.json
CHANGED
package/templates/README.md
CHANGED
|
@@ -15,10 +15,10 @@ import {
|
|
|
15
15
|
|
|
16
16
|
## Product vs e2e
|
|
17
17
|
|
|
18
|
-
| Kind | Dashboard create
|
|
19
|
-
| ----------- |
|
|
20
|
-
| **product** | Yes (`echo`, `echo-dc`, `voice-starter`, `game-sync`, `voice-showcase`, `webhooks`, `webhooks-redis`) | Yes — `dist/templates/<id>/agent.js` | Platform project create |
|
|
21
|
-
| **e2e** | No
|
|
18
|
+
| Kind | Dashboard create | Prebuilt seed bundle | Typical consumer |
|
|
19
|
+
| ----------- | -------------------------------------------------------------------------------------------------------------------------- | ------------------------------------ | ----------------------- |
|
|
20
|
+
| **product** | Yes (`echo`, `echo-dc`, `voice-starter`, `game-sync`, `voice-showcase`, `recording-consent`, `webhooks`, `webhooks-redis`) | Yes — `dist/templates/<id>/agent.js` | Platform project create |
|
|
21
|
+
| **e2e** | No | No — build from sources at test time | `voicethere/e2e` smokes |
|
|
22
22
|
|
|
23
23
|
Product templates always set `seedOnCreate: true`. CI fails if a product template is missing its prebuilt bundle after `npm run build`.
|
|
24
24
|
|
|
@@ -67,6 +67,12 @@ Conversational voice demo for landing and dashboard previews — greets the user
|
|
|
67
67
|
|
|
68
68
|
Sources: `voice-showcase/agent.ts` (defineAgent wiring), `conversation.ts` (pure state machine), `weather.ts`, `recipes.ts`, `fun-facts.ts`.
|
|
69
69
|
|
|
70
|
+
### `recording-consent/` (`recording-consent`)
|
|
71
|
+
|
|
72
|
+
Demonstrates conversation recording consent on connect: asks whether recording is OK when the project has recording enabled, pauses capture while collecting name and date of birth, then resumes only if the customer consented. When project recording is off, logs a warning and never calls `startRecording`. Voice finals and typed chat share the same handler.
|
|
73
|
+
|
|
74
|
+
Sources: `recording-consent/agent.ts` (defineAgent wiring), `conversation.ts` (pure state machine).
|
|
75
|
+
|
|
70
76
|
### `webhooks.ts` (`webhooks`)
|
|
71
77
|
|
|
72
78
|
Inbound HTTP webhook sample — verifies `x-agent-webhook-signature` HMAC on the **raw body** with `AGENT_WEBHOOK_SIGNING_SECRET`, then `JSON.parse` and fans out to connected sessions via DataChannel + `speak`.
|
|
@@ -19,6 +19,19 @@ export interface UnregisterCommand {
|
|
|
19
19
|
objectId?: number;
|
|
20
20
|
}
|
|
21
21
|
|
|
22
|
+
export function resolveRemoveTarget(
|
|
23
|
+
objectId: number | undefined,
|
|
24
|
+
ownedObjectIds: ReadonlySet<number> | undefined | null,
|
|
25
|
+
): { ok: true; objectId: number } | { ok: false; reason: string } {
|
|
26
|
+
if (objectId !== undefined) {
|
|
27
|
+
return { ok: true, objectId };
|
|
28
|
+
}
|
|
29
|
+
if (!ownedObjectIds || ownedObjectIds.size === 0) {
|
|
30
|
+
return { ok: false, reason: UNREGISTER_NACK_REASON_NOT_FOUND };
|
|
31
|
+
}
|
|
32
|
+
return { ok: true, objectId: Math.max(...ownedObjectIds) };
|
|
33
|
+
}
|
|
34
|
+
|
|
22
35
|
export function parseUnregisterCommand(
|
|
23
36
|
message: unknown,
|
|
24
37
|
): UnregisterCommand | null {
|
|
@@ -124,6 +124,33 @@ export function writeObjectSlot(
|
|
|
124
124
|
world[start + 8] = dirW;
|
|
125
125
|
}
|
|
126
126
|
|
|
127
|
+
/**
|
|
128
|
+
* After simulating from a Redis load, keep slots empty that a concurrent release
|
|
129
|
+
* zeroed in Redis while the tick was in flight — prevents saveWorldToRedis from
|
|
130
|
+
* restoring an object Lua just cleared (sim GET → simulate → SET race).
|
|
131
|
+
*
|
|
132
|
+
* `authoritative` must be a fresh Redis GET after simulate (never stale in-memory
|
|
133
|
+
* worldState — an empty buffer would wipe every live slot).
|
|
134
|
+
*/
|
|
135
|
+
export function preserveEmptySlots(
|
|
136
|
+
simulated: Float32Array,
|
|
137
|
+
authoritative: Float32Array,
|
|
138
|
+
): void {
|
|
139
|
+
for (let slot = 0; slot < MAX_LIVE_OBJECTS; slot += 1) {
|
|
140
|
+
if (readSlotObjectId(authoritative, slot) === 0) {
|
|
141
|
+
markSlotFree(simulated, slot);
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
/** Merge a simulated world with the latest Redis snapshot before SET. */
|
|
147
|
+
export function commitSimulatedWorld(
|
|
148
|
+
simulated: Float32Array,
|
|
149
|
+
latestRedis: Float32Array,
|
|
150
|
+
): void {
|
|
151
|
+
preserveEmptySlots(simulated, latestRedis);
|
|
152
|
+
}
|
|
153
|
+
|
|
127
154
|
export function collectActiveObjectIds(world: Float32Array): number[] {
|
|
128
155
|
const ids: number[] = [];
|
|
129
156
|
for (let slot = 0; slot < MAX_LIVE_OBJECTS; slot += 1) {
|