@voicethere/agent 0.5.6 → 0.7.0
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 +63 -2
- package/dist/index.d.ts +2 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -2
- package/dist/index.js.map +1 -1
- package/dist/protocol.d.ts +99 -2
- package/dist/protocol.d.ts.map +1 -1
- package/dist/protocol.js +4 -0
- package/dist/protocol.js.map +1 -1
- package/dist/runtime.d.ts +45 -1
- package/dist/runtime.d.ts.map +1 -1
- package/dist/runtime.js +230 -0
- package/dist/runtime.js.map +1 -1
- package/dist/templates/echo/agent.js +61 -2
- package/dist/templates/echo-dc/agent.js +61 -2
- package/dist/templates/game-sync/agent.js +125 -33
- package/dist/templates/index.d.ts +2 -0
- package/dist/templates/index.d.ts.map +1 -1
- package/dist/templates/index.js +14 -0
- package/dist/templates/index.js.map +1 -1
- package/dist/templates/positional-tts/agent.js +810 -0
- package/dist/templates/recording-consent/agent.js +61 -2
- package/dist/templates/registry.d.ts.map +1 -1
- package/dist/templates/registry.js +17 -0
- package/dist/templates/registry.js.map +1 -1
- package/dist/templates/voice-showcase/agent.js +102 -21
- package/dist/templates/voice-starter/agent.js +61 -2
- package/dist/templates/webhooks/agent.js +61 -2
- package/dist/templates/webhooks-redis/agent.js +61 -2
- package/package.json +10 -4
- package/templates/README.md +12 -6
- package/templates/game-sync-world-layout.ts +45 -0
- package/templates/game-sync.ts +39 -24
- package/templates/mix-smoke.ts +224 -0
- package/templates/positional-tts/agent.ts +115 -0
- package/templates/positional-tts/orbit.ts +11 -0
- package/templates/voice-showcase/agent.ts +21 -22
- package/templates/voice-showcase/delivery.ts +57 -0
|
@@ -136,6 +136,18 @@ function isRecordingControlAckMessage(value) {
|
|
|
136
136
|
const msg = value;
|
|
137
137
|
return msg.type === "recording_control_ack" && typeof msg.requestId === "string";
|
|
138
138
|
}
|
|
139
|
+
function isMixControlAckMessage(value) {
|
|
140
|
+
if (!value || typeof value !== "object")
|
|
141
|
+
return false;
|
|
142
|
+
const msg = value;
|
|
143
|
+
return msg.type === "mix_control_ack" && typeof msg.requestId === "string";
|
|
144
|
+
}
|
|
145
|
+
function isSttControlAckMessage(value) {
|
|
146
|
+
if (!value || typeof value !== "object")
|
|
147
|
+
return false;
|
|
148
|
+
const msg = value;
|
|
149
|
+
return msg.type === "stt_control_ack" && typeof msg.requestId === "string";
|
|
150
|
+
}
|
|
139
151
|
function isWebhookMessage(value) {
|
|
140
152
|
if (!value || typeof value !== "object")
|
|
141
153
|
return false;
|
|
@@ -178,7 +190,7 @@ function isParentMessage(value) {
|
|
|
178
190
|
if (!value || typeof value !== "object")
|
|
179
191
|
return false;
|
|
180
192
|
const msg = value;
|
|
181
|
-
return msg.type === "session_start" || msg.type === "speech_event" || msg.type === "session_end" || msg.type === "data_channel_message" || msg.type === "data_channel_binary" || msg.type === "idle_timeout" || msg.type === "recording_control_ack" || msg.type === "webhook";
|
|
193
|
+
return msg.type === "session_start" || msg.type === "speech_event" || msg.type === "session_end" || msg.type === "data_channel_message" || msg.type === "data_channel_binary" || msg.type === "idle_timeout" || msg.type === "recording_control_ack" || msg.type === "mix_control_ack" || msg.type === "stt_control_ack" || msg.type === "webhook";
|
|
182
194
|
}
|
|
183
195
|
function isSessionScopedParentMessage(value) {
|
|
184
196
|
return isParentMessage(value) && !isWebhookMessage(value);
|
|
@@ -192,8 +204,12 @@ function parseDataChannelPayload(raw) {
|
|
|
192
204
|
}
|
|
193
205
|
var peerEnvBySessionId = /* @__PURE__ */ new Map();
|
|
194
206
|
var recordingAvailableBySessionId = /* @__PURE__ */ new Map();
|
|
207
|
+
var mixAvailableBySessionId = /* @__PURE__ */ new Map();
|
|
208
|
+
var ttsPoseAvailableBySessionId = /* @__PURE__ */ new Map();
|
|
195
209
|
var endedSessionIds = /* @__PURE__ */ new Set();
|
|
196
210
|
var pendingRecordingAcks = /* @__PURE__ */ new Map();
|
|
211
|
+
var pendingMixAcks = /* @__PURE__ */ new Map();
|
|
212
|
+
var pendingSttAcks = /* @__PURE__ */ new Map();
|
|
197
213
|
function handleRecordingControlAck(message) {
|
|
198
214
|
const pending = pendingRecordingAcks.get(message.requestId);
|
|
199
215
|
if (!pending)
|
|
@@ -206,6 +222,30 @@ function handleRecordingControlAck(message) {
|
|
|
206
222
|
requestId: message.requestId
|
|
207
223
|
});
|
|
208
224
|
}
|
|
225
|
+
function handleMixControlAck(message) {
|
|
226
|
+
const pending = pendingMixAcks.get(message.requestId);
|
|
227
|
+
if (!pending)
|
|
228
|
+
return;
|
|
229
|
+
clearTimeout(pending.timer);
|
|
230
|
+
pendingMixAcks.delete(message.requestId);
|
|
231
|
+
pending.resolve({
|
|
232
|
+
ok: message.ok,
|
|
233
|
+
reason: message.reason,
|
|
234
|
+
requestId: message.requestId
|
|
235
|
+
});
|
|
236
|
+
}
|
|
237
|
+
function handleSttControlAck(message) {
|
|
238
|
+
const pending = pendingSttAcks.get(message.requestId);
|
|
239
|
+
if (!pending)
|
|
240
|
+
return;
|
|
241
|
+
clearTimeout(pending.timer);
|
|
242
|
+
pendingSttAcks.delete(message.requestId);
|
|
243
|
+
pending.resolve({
|
|
244
|
+
ok: message.ok,
|
|
245
|
+
reason: message.reason,
|
|
246
|
+
requestId: message.requestId
|
|
247
|
+
});
|
|
248
|
+
}
|
|
209
249
|
function clearPendingRecordingAcksForSession(sessionId, reason) {
|
|
210
250
|
for (const [requestId, pending] of pendingRecordingAcks) {
|
|
211
251
|
if (pending.sessionId !== sessionId)
|
|
@@ -261,6 +301,11 @@ function allowOutboundForSession(sessionId) {
|
|
|
261
301
|
return queue.isLive(sessionId);
|
|
262
302
|
}
|
|
263
303
|
function sendParentMessage(message) {
|
|
304
|
+
const msgType = message && typeof message === "object" && "type" in message ? message.type : void 0;
|
|
305
|
+
if (msgType === "mix_control" || msgType === "stt_control") {
|
|
306
|
+
process.send?.(message);
|
|
307
|
+
return;
|
|
308
|
+
}
|
|
264
309
|
const sessionId = message && typeof message === "object" && "sessionId" in message && typeof message.sessionId === "string" ? message.sessionId : void 0;
|
|
265
310
|
if (!allowOutboundForSession(sessionId))
|
|
266
311
|
return;
|
|
@@ -342,6 +387,8 @@ async function handleParentMessage(message, handlers) {
|
|
|
342
387
|
endedSessionIds.delete(message.sessionId);
|
|
343
388
|
peerEnvBySessionId.set(message.sessionId, message.env);
|
|
344
389
|
recordingAvailableBySessionId.set(message.sessionId, message.recordingAvailable ?? false);
|
|
390
|
+
mixAvailableBySessionId.set(message.sessionId, message.mixAvailable ?? false);
|
|
391
|
+
ttsPoseAvailableBySessionId.set(message.sessionId, message.ttsPoseAvailable ?? false);
|
|
345
392
|
const sessionStartInitDelayMs = resolveSessionStartInitDelayMs();
|
|
346
393
|
if (sessionStartInitDelayMs > 0) {
|
|
347
394
|
await new Promise((resolve) => setTimeout(resolve, sessionStartInitDelayMs));
|
|
@@ -349,7 +396,9 @@ async function handleParentMessage(message, handlers) {
|
|
|
349
396
|
await (handlers.onClientJoin ?? handlers.onSessionStart)?.({
|
|
350
397
|
sessionId: message.sessionId,
|
|
351
398
|
env: message.env,
|
|
352
|
-
recordingAvailable: message.recordingAvailable ?? false
|
|
399
|
+
recordingAvailable: message.recordingAvailable ?? false,
|
|
400
|
+
mixAvailable: message.mixAvailable ?? false,
|
|
401
|
+
ttsPoseAvailable: message.ttsPoseAvailable ?? false
|
|
353
402
|
});
|
|
354
403
|
sendParentMessage({
|
|
355
404
|
type: "session_start_ack",
|
|
@@ -387,6 +436,8 @@ async function handleParentMessage(message, handlers) {
|
|
|
387
436
|
clearPendingRecordingAcksForSession(message.sessionId, "session_ended");
|
|
388
437
|
peerEnvBySessionId.delete(message.sessionId);
|
|
389
438
|
recordingAvailableBySessionId.delete(message.sessionId);
|
|
439
|
+
mixAvailableBySessionId.delete(message.sessionId);
|
|
440
|
+
ttsPoseAvailableBySessionId.delete(message.sessionId);
|
|
390
441
|
await (handlers.onClientLeave ?? handlers.onSessionEnd)?.({
|
|
391
442
|
sessionId: message.sessionId
|
|
392
443
|
});
|
|
@@ -406,6 +457,14 @@ function defineAgent(handlers) {
|
|
|
406
457
|
handleRecordingControlAck(message);
|
|
407
458
|
return;
|
|
408
459
|
}
|
|
460
|
+
if (isMixControlAckMessage(message)) {
|
|
461
|
+
handleMixControlAck(message);
|
|
462
|
+
return;
|
|
463
|
+
}
|
|
464
|
+
if (isSttControlAckMessage(message)) {
|
|
465
|
+
handleSttControlAck(message);
|
|
466
|
+
return;
|
|
467
|
+
}
|
|
409
468
|
if (isWebhookMessage(message)) {
|
|
410
469
|
void agentStartReady.then(() => handleWebhookMessage(message, handlers));
|
|
411
470
|
return;
|
|
@@ -10890,6 +10890,18 @@ function isRecordingControlAckMessage(value) {
|
|
|
10890
10890
|
const msg = value;
|
|
10891
10891
|
return msg.type === "recording_control_ack" && typeof msg.requestId === "string";
|
|
10892
10892
|
}
|
|
10893
|
+
function isMixControlAckMessage(value) {
|
|
10894
|
+
if (!value || typeof value !== "object")
|
|
10895
|
+
return false;
|
|
10896
|
+
const msg = value;
|
|
10897
|
+
return msg.type === "mix_control_ack" && typeof msg.requestId === "string";
|
|
10898
|
+
}
|
|
10899
|
+
function isSttControlAckMessage(value) {
|
|
10900
|
+
if (!value || typeof value !== "object")
|
|
10901
|
+
return false;
|
|
10902
|
+
const msg = value;
|
|
10903
|
+
return msg.type === "stt_control_ack" && typeof msg.requestId === "string";
|
|
10904
|
+
}
|
|
10893
10905
|
function isWebhookMessage(value) {
|
|
10894
10906
|
if (!value || typeof value !== "object")
|
|
10895
10907
|
return false;
|
|
@@ -10932,7 +10944,7 @@ function isParentMessage(value) {
|
|
|
10932
10944
|
if (!value || typeof value !== "object")
|
|
10933
10945
|
return false;
|
|
10934
10946
|
const msg = value;
|
|
10935
|
-
return msg.type === "session_start" || msg.type === "speech_event" || msg.type === "session_end" || msg.type === "data_channel_message" || msg.type === "data_channel_binary" || msg.type === "idle_timeout" || msg.type === "recording_control_ack" || msg.type === "webhook";
|
|
10947
|
+
return msg.type === "session_start" || msg.type === "speech_event" || msg.type === "session_end" || msg.type === "data_channel_message" || msg.type === "data_channel_binary" || msg.type === "idle_timeout" || msg.type === "recording_control_ack" || msg.type === "mix_control_ack" || msg.type === "stt_control_ack" || msg.type === "webhook";
|
|
10936
10948
|
}
|
|
10937
10949
|
function isSessionScopedParentMessage(value) {
|
|
10938
10950
|
return isParentMessage(value) && !isWebhookMessage(value);
|
|
@@ -10946,8 +10958,12 @@ function parseDataChannelPayload(raw) {
|
|
|
10946
10958
|
}
|
|
10947
10959
|
var peerEnvBySessionId = /* @__PURE__ */ new Map();
|
|
10948
10960
|
var recordingAvailableBySessionId = /* @__PURE__ */ new Map();
|
|
10961
|
+
var mixAvailableBySessionId = /* @__PURE__ */ new Map();
|
|
10962
|
+
var ttsPoseAvailableBySessionId = /* @__PURE__ */ new Map();
|
|
10949
10963
|
var endedSessionIds = /* @__PURE__ */ new Set();
|
|
10950
10964
|
var pendingRecordingAcks = /* @__PURE__ */ new Map();
|
|
10965
|
+
var pendingMixAcks = /* @__PURE__ */ new Map();
|
|
10966
|
+
var pendingSttAcks = /* @__PURE__ */ new Map();
|
|
10951
10967
|
function handleRecordingControlAck(message) {
|
|
10952
10968
|
const pending = pendingRecordingAcks.get(message.requestId);
|
|
10953
10969
|
if (!pending)
|
|
@@ -10960,6 +10976,30 @@ function handleRecordingControlAck(message) {
|
|
|
10960
10976
|
requestId: message.requestId
|
|
10961
10977
|
});
|
|
10962
10978
|
}
|
|
10979
|
+
function handleMixControlAck(message) {
|
|
10980
|
+
const pending = pendingMixAcks.get(message.requestId);
|
|
10981
|
+
if (!pending)
|
|
10982
|
+
return;
|
|
10983
|
+
clearTimeout(pending.timer);
|
|
10984
|
+
pendingMixAcks.delete(message.requestId);
|
|
10985
|
+
pending.resolve({
|
|
10986
|
+
ok: message.ok,
|
|
10987
|
+
reason: message.reason,
|
|
10988
|
+
requestId: message.requestId
|
|
10989
|
+
});
|
|
10990
|
+
}
|
|
10991
|
+
function handleSttControlAck(message) {
|
|
10992
|
+
const pending = pendingSttAcks.get(message.requestId);
|
|
10993
|
+
if (!pending)
|
|
10994
|
+
return;
|
|
10995
|
+
clearTimeout(pending.timer);
|
|
10996
|
+
pendingSttAcks.delete(message.requestId);
|
|
10997
|
+
pending.resolve({
|
|
10998
|
+
ok: message.ok,
|
|
10999
|
+
reason: message.reason,
|
|
11000
|
+
requestId: message.requestId
|
|
11001
|
+
});
|
|
11002
|
+
}
|
|
10963
11003
|
function clearPendingRecordingAcksForSession(sessionId, reason) {
|
|
10964
11004
|
for (const [requestId, pending] of pendingRecordingAcks) {
|
|
10965
11005
|
if (pending.sessionId !== sessionId)
|
|
@@ -11015,6 +11055,11 @@ function allowOutboundForSession(sessionId) {
|
|
|
11015
11055
|
return queue.isLive(sessionId);
|
|
11016
11056
|
}
|
|
11017
11057
|
function sendParentMessage(message) {
|
|
11058
|
+
const msgType = message && typeof message === "object" && "type" in message ? message.type : void 0;
|
|
11059
|
+
if (msgType === "mix_control" || msgType === "stt_control") {
|
|
11060
|
+
process.send?.(message);
|
|
11061
|
+
return;
|
|
11062
|
+
}
|
|
11018
11063
|
const sessionId = message && typeof message === "object" && "sessionId" in message && typeof message.sessionId === "string" ? message.sessionId : void 0;
|
|
11019
11064
|
if (!allowOutboundForSession(sessionId))
|
|
11020
11065
|
return;
|
|
@@ -11096,6 +11141,8 @@ async function handleParentMessage(message, handlers) {
|
|
|
11096
11141
|
endedSessionIds.delete(message.sessionId);
|
|
11097
11142
|
peerEnvBySessionId.set(message.sessionId, message.env);
|
|
11098
11143
|
recordingAvailableBySessionId.set(message.sessionId, message.recordingAvailable ?? false);
|
|
11144
|
+
mixAvailableBySessionId.set(message.sessionId, message.mixAvailable ?? false);
|
|
11145
|
+
ttsPoseAvailableBySessionId.set(message.sessionId, message.ttsPoseAvailable ?? false);
|
|
11099
11146
|
const sessionStartInitDelayMs = resolveSessionStartInitDelayMs();
|
|
11100
11147
|
if (sessionStartInitDelayMs > 0) {
|
|
11101
11148
|
await new Promise((resolve) => setTimeout(resolve, sessionStartInitDelayMs));
|
|
@@ -11103,7 +11150,9 @@ async function handleParentMessage(message, handlers) {
|
|
|
11103
11150
|
await (handlers.onClientJoin ?? handlers.onSessionStart)?.({
|
|
11104
11151
|
sessionId: message.sessionId,
|
|
11105
11152
|
env: message.env,
|
|
11106
|
-
recordingAvailable: message.recordingAvailable ?? false
|
|
11153
|
+
recordingAvailable: message.recordingAvailable ?? false,
|
|
11154
|
+
mixAvailable: message.mixAvailable ?? false,
|
|
11155
|
+
ttsPoseAvailable: message.ttsPoseAvailable ?? false
|
|
11107
11156
|
});
|
|
11108
11157
|
sendParentMessage({
|
|
11109
11158
|
type: "session_start_ack",
|
|
@@ -11141,6 +11190,8 @@ async function handleParentMessage(message, handlers) {
|
|
|
11141
11190
|
clearPendingRecordingAcksForSession(message.sessionId, "session_ended");
|
|
11142
11191
|
peerEnvBySessionId.delete(message.sessionId);
|
|
11143
11192
|
recordingAvailableBySessionId.delete(message.sessionId);
|
|
11193
|
+
mixAvailableBySessionId.delete(message.sessionId);
|
|
11194
|
+
ttsPoseAvailableBySessionId.delete(message.sessionId);
|
|
11144
11195
|
await (handlers.onClientLeave ?? handlers.onSessionEnd)?.({
|
|
11145
11196
|
sessionId: message.sessionId
|
|
11146
11197
|
});
|
|
@@ -11160,6 +11211,14 @@ function defineAgent(handlers) {
|
|
|
11160
11211
|
handleRecordingControlAck(message);
|
|
11161
11212
|
return;
|
|
11162
11213
|
}
|
|
11214
|
+
if (isMixControlAckMessage(message)) {
|
|
11215
|
+
handleMixControlAck(message);
|
|
11216
|
+
return;
|
|
11217
|
+
}
|
|
11218
|
+
if (isSttControlAckMessage(message)) {
|
|
11219
|
+
handleSttControlAck(message);
|
|
11220
|
+
return;
|
|
11221
|
+
}
|
|
11163
11222
|
if (isWebhookMessage(message)) {
|
|
11164
11223
|
void agentStartReady.then(() => handleWebhookMessage(message, handlers));
|
|
11165
11224
|
return;
|
|
@@ -11460,16 +11519,6 @@ function writeObjectSlot(world, slot, objectId, posX, posY, posZ, posW, dirX, di
|
|
|
11460
11519
|
world[start + 7] = dirZ;
|
|
11461
11520
|
world[start + 8] = dirW;
|
|
11462
11521
|
}
|
|
11463
|
-
function preserveEmptySlots(simulated, authoritative) {
|
|
11464
|
-
for (let slot = 0; slot < MAX_LIVE_OBJECTS; slot += 1) {
|
|
11465
|
-
if (readSlotObjectId(authoritative, slot) === 0) {
|
|
11466
|
-
markSlotFree(simulated, slot);
|
|
11467
|
-
}
|
|
11468
|
-
}
|
|
11469
|
-
}
|
|
11470
|
-
function commitSimulatedWorld(simulated, latestRedis) {
|
|
11471
|
-
preserveEmptySlots(simulated, latestRedis);
|
|
11472
|
-
}
|
|
11473
11522
|
function collectActiveObjectIds(world) {
|
|
11474
11523
|
const ids = [];
|
|
11475
11524
|
for (let slot = 0; slot < MAX_LIVE_OBJECTS; slot += 1) {
|
|
@@ -11480,6 +11529,32 @@ function collectActiveObjectIds(world) {
|
|
|
11480
11529
|
}
|
|
11481
11530
|
return ids;
|
|
11482
11531
|
}
|
|
11532
|
+
function liveWorldSnapshot(world, owners) {
|
|
11533
|
+
const ids = collectActiveObjectIds(world);
|
|
11534
|
+
ids.sort((a, b) => a - b);
|
|
11535
|
+
return ids.map((objectId) => ({
|
|
11536
|
+
objectId,
|
|
11537
|
+
ownerSessionId: owners.get(objectId) ?? ""
|
|
11538
|
+
}));
|
|
11539
|
+
}
|
|
11540
|
+
function planRedisSimTick(options) {
|
|
11541
|
+
if (options.lockAcquired) {
|
|
11542
|
+
return "simulate";
|
|
11543
|
+
}
|
|
11544
|
+
if (options.connectedSessions.size > 0) {
|
|
11545
|
+
return "relay";
|
|
11546
|
+
}
|
|
11547
|
+
return "noop";
|
|
11548
|
+
}
|
|
11549
|
+
function clampSimulationDtSec(elapsedMs, fallbackHz) {
|
|
11550
|
+
const minDt = 1 / fallbackHz;
|
|
11551
|
+
const maxDt = 0.05;
|
|
11552
|
+
if (!Number.isFinite(elapsedMs) || elapsedMs <= 0) {
|
|
11553
|
+
return minDt;
|
|
11554
|
+
}
|
|
11555
|
+
const elapsedSec = elapsedMs / 1e3;
|
|
11556
|
+
return Math.min(maxDt, Math.max(minDt, elapsedSec));
|
|
11557
|
+
}
|
|
11483
11558
|
function normalizeWorldBuffer(raw) {
|
|
11484
11559
|
if (!raw || raw.byteLength === 0) {
|
|
11485
11560
|
return createEmptyWorldBuffer();
|
|
@@ -11718,6 +11793,7 @@ var worldState = createEmptyWorldBuffer();
|
|
|
11718
11793
|
var redis = null;
|
|
11719
11794
|
var broadcastTimer = null;
|
|
11720
11795
|
var worldMutationChain = Promise.resolve();
|
|
11796
|
+
var lastTickTimeMs = 0;
|
|
11721
11797
|
function withWorldMutation(fn) {
|
|
11722
11798
|
const run = worldMutationChain.then(fn);
|
|
11723
11799
|
worldMutationChain = run.then(
|
|
@@ -11902,13 +11978,11 @@ async function unregisterObject(sessionId, objectId) {
|
|
|
11902
11978
|
}
|
|
11903
11979
|
return { ok: true, objectId: target.objectId };
|
|
11904
11980
|
}
|
|
11905
|
-
function
|
|
11906
|
-
const objects =
|
|
11907
|
-
for (const
|
|
11908
|
-
|
|
11981
|
+
function broadcastWorldSnapshot() {
|
|
11982
|
+
const objects = liveWorldSnapshot(worldState, objectOwners);
|
|
11983
|
+
for (const sessionId of connectedSessions) {
|
|
11984
|
+
sendToClient(sessionId, { type: "world_snapshot", objects });
|
|
11909
11985
|
}
|
|
11910
|
-
objects.sort((a, b) => a.objectId - b.objectId);
|
|
11911
|
-
return objects;
|
|
11912
11986
|
}
|
|
11913
11987
|
function notifyObjectRegistered(objectId, ownerSessionId) {
|
|
11914
11988
|
for (const sessionId of connectedSessions) {
|
|
@@ -11952,31 +12026,44 @@ async function saveWorldToRedis(world) {
|
|
|
11952
12026
|
await redis.setBuffer(REDIS_WORLD_KEY, copyWorldBuffer(world));
|
|
11953
12027
|
}
|
|
11954
12028
|
async function runSimulationTick() {
|
|
12029
|
+
const now = Date.now();
|
|
12030
|
+
const dt = clampSimulationDtSec(
|
|
12031
|
+
lastTickTimeMs === 0 ? 0 : now - lastTickTimeMs,
|
|
12032
|
+
BROADCAST_HZ
|
|
12033
|
+
);
|
|
12034
|
+
lastTickTimeMs = now;
|
|
11955
12035
|
if (!redis) {
|
|
11956
12036
|
await withWorldMutation(async () => {
|
|
11957
|
-
|
|
11958
|
-
simulateWorldStep(worldState, 1 / BROADCAST_HZ, activeObjectIds);
|
|
12037
|
+
simulateWorldStep(worldState, dt, collectActiveObjectIds(worldState));
|
|
11959
12038
|
});
|
|
11960
12039
|
broadcastWorldBuffer(worldState);
|
|
11961
12040
|
return;
|
|
11962
12041
|
}
|
|
11963
12042
|
await withWorldMutation(async () => {
|
|
11964
|
-
await withRedisSimLock(
|
|
12043
|
+
const lockResult = await withRedisSimLock(
|
|
11965
12044
|
async () => {
|
|
11966
|
-
const
|
|
11967
|
-
const activeObjectIds = collectActiveObjectIds(
|
|
11968
|
-
simulateWorldStep(
|
|
11969
|
-
|
|
11970
|
-
|
|
11971
|
-
|
|
11972
|
-
|
|
12045
|
+
const world = await loadWorldFromRedis();
|
|
12046
|
+
const activeObjectIds = collectActiveObjectIds(world);
|
|
12047
|
+
simulateWorldStep(world, dt, activeObjectIds);
|
|
12048
|
+
await saveWorldToRedis(world);
|
|
12049
|
+
worldState = world;
|
|
12050
|
+
broadcastWorldBuffer(world);
|
|
12051
|
+
return true;
|
|
11973
12052
|
},
|
|
11974
12053
|
{ retryUntilAcquired: false }
|
|
11975
12054
|
);
|
|
12055
|
+
if (lockResult === null) {
|
|
12056
|
+
const plan = planRedisSimTick({
|
|
12057
|
+
lockAcquired: false,
|
|
12058
|
+
connectedSessions
|
|
12059
|
+
});
|
|
12060
|
+
if (plan === "relay") {
|
|
12061
|
+
const world = await loadWorldFromRedis();
|
|
12062
|
+
worldState = world;
|
|
12063
|
+
broadcastWorldBuffer(world);
|
|
12064
|
+
}
|
|
12065
|
+
}
|
|
11976
12066
|
});
|
|
11977
|
-
const world = await loadWorldFromRedis();
|
|
11978
|
-
worldState = world;
|
|
11979
|
-
broadcastWorldBuffer(world);
|
|
11980
12067
|
}
|
|
11981
12068
|
function startBroadcastLoopIfNeeded() {
|
|
11982
12069
|
if (broadcastTimer) return;
|
|
@@ -12033,12 +12120,15 @@ defineAgent({
|
|
|
12033
12120
|
startBroadcastLoopIfNeeded();
|
|
12034
12121
|
agentLog("info", "game-sync agent connected to project Redis world buffer");
|
|
12035
12122
|
},
|
|
12036
|
-
onClientJoin({ sessionId }) {
|
|
12123
|
+
async onClientJoin({ sessionId }) {
|
|
12037
12124
|
connectedSessions.add(sessionId);
|
|
12038
12125
|
startBroadcastLoopIfNeeded();
|
|
12126
|
+
if (redis) {
|
|
12127
|
+
worldState = await loadWorldFromRedis();
|
|
12128
|
+
}
|
|
12039
12129
|
sendToClient(sessionId, {
|
|
12040
12130
|
type: "world_snapshot",
|
|
12041
|
-
objects:
|
|
12131
|
+
objects: liveWorldSnapshot(worldState, objectOwners)
|
|
12042
12132
|
});
|
|
12043
12133
|
agentLog("info", `join ${sessionId} connected=${connectedSessions.size}`);
|
|
12044
12134
|
},
|
|
@@ -12081,6 +12171,7 @@ defineAgent({
|
|
|
12081
12171
|
}
|
|
12082
12172
|
sendToClient(ctx.sessionId, { type: "register_ack", objectId });
|
|
12083
12173
|
notifyObjectRegistered(objectId, ctx.sessionId);
|
|
12174
|
+
broadcastWorldSnapshot();
|
|
12084
12175
|
agentLog(
|
|
12085
12176
|
"info",
|
|
12086
12177
|
`register session=${ctx.sessionId} objectId=${objectId}`
|
|
@@ -12105,6 +12196,7 @@ defineAgent({
|
|
|
12105
12196
|
type: "unregister_ack",
|
|
12106
12197
|
objectId: result.objectId
|
|
12107
12198
|
});
|
|
12199
|
+
broadcastWorldSnapshot();
|
|
12108
12200
|
agentLog(
|
|
12109
12201
|
"info",
|
|
12110
12202
|
`unregister_ack session=${ctx.sessionId} objectId=${result.objectId}`
|
|
@@ -13,6 +13,8 @@ export declare function listTemplates(filter?: ListTemplatesFilter): AgentTempla
|
|
|
13
13
|
export declare function getTemplate(id: string): AgentTemplateDefinition;
|
|
14
14
|
export declare function resolveTemplateEntryPath(id: string): string;
|
|
15
15
|
export declare function loadTemplateSources(id: string): TemplateSourceFile[];
|
|
16
|
+
/** Like {@link loadTemplateSources}, but paths match registry `sourceFiles` (e.g. `voice-showcase/agent.ts`). */
|
|
17
|
+
export declare function loadTemplateWorkspaceSources(id: string): TemplateSourceFile[];
|
|
16
18
|
export declare function hasSeedBundle(id: string): boolean;
|
|
17
19
|
export declare function loadTemplateBundle(id: string): Buffer;
|
|
18
20
|
export declare function buildSeedTemplateBundles(): Promise<void>;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/templates/index.ts"],"names":[],"mappings":"AAKA,OAAO,EAEL,KAAK,uBAAuB,EAC5B,KAAK,YAAY,EAGlB,MAAM,eAAe,CAAC;AAEvB,YAAY,EACV,uBAAuB,EACvB,eAAe,EACf,YAAY,GACb,MAAM,eAAe,CAAC;AACvB,OAAO,EACL,eAAe,EACf,eAAe,EACf,iBAAiB,EACjB,yBAAyB,GAC1B,MAAM,eAAe,CAAC;AAEvB,MAAM,WAAW,kBAAkB;IACjC,2FAA2F;IAC3F,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,MAAM,CAAC;CACjB;AAED,MAAM,WAAW,mBAAmB;IAClC,IAAI,CAAC,EAAE,YAAY,CAAC;CACrB;AAaD,wBAAgB,aAAa,CAC3B,MAAM,CAAC,EAAE,mBAAmB,GAC3B,uBAAuB,EAAE,CAK3B;AAED,wBAAgB,WAAW,CAAC,EAAE,EAAE,MAAM,GAAG,uBAAuB,CAE/D;AAED,wBAAgB,wBAAwB,CAAC,EAAE,EAAE,MAAM,GAAG,MAAM,CAO3D;AAED,wBAAgB,mBAAmB,CAAC,EAAE,EAAE,MAAM,GAAG,kBAAkB,EAAE,CAYpE;AAMD,wBAAgB,aAAa,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO,CAMjD;AAED,wBAAgB,kBAAkB,CAAC,EAAE,EAAE,MAAM,GAAG,MAAM,CAYrD;AAED,wBAAsB,wBAAwB,IAAI,OAAO,CAAC,IAAI,CAAC,CAS9D"}
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/templates/index.ts"],"names":[],"mappings":"AAKA,OAAO,EAEL,KAAK,uBAAuB,EAC5B,KAAK,YAAY,EAGlB,MAAM,eAAe,CAAC;AAEvB,YAAY,EACV,uBAAuB,EACvB,eAAe,EACf,YAAY,GACb,MAAM,eAAe,CAAC;AACvB,OAAO,EACL,eAAe,EACf,eAAe,EACf,iBAAiB,EACjB,yBAAyB,GAC1B,MAAM,eAAe,CAAC;AAEvB,MAAM,WAAW,kBAAkB;IACjC,2FAA2F;IAC3F,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,MAAM,CAAC;CACjB;AAED,MAAM,WAAW,mBAAmB;IAClC,IAAI,CAAC,EAAE,YAAY,CAAC;CACrB;AAaD,wBAAgB,aAAa,CAC3B,MAAM,CAAC,EAAE,mBAAmB,GAC3B,uBAAuB,EAAE,CAK3B;AAED,wBAAgB,WAAW,CAAC,EAAE,EAAE,MAAM,GAAG,uBAAuB,CAE/D;AAED,wBAAgB,wBAAwB,CAAC,EAAE,EAAE,MAAM,GAAG,MAAM,CAO3D;AAED,wBAAgB,mBAAmB,CAAC,EAAE,EAAE,MAAM,GAAG,kBAAkB,EAAE,CAYpE;AAED,iHAAiH;AACjH,wBAAgB,4BAA4B,CAAC,EAAE,EAAE,MAAM,GAAG,kBAAkB,EAAE,CAY7E;AAMD,wBAAgB,aAAa,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO,CAMjD;AAED,wBAAgB,kBAAkB,CAAC,EAAE,EAAE,MAAM,GAAG,MAAM,CAYrD;AAED,wBAAsB,wBAAwB,IAAI,OAAO,CAAC,IAAI,CAAC,CAS9D"}
|
package/dist/templates/index.js
CHANGED
|
@@ -43,6 +43,20 @@ export function loadTemplateSources(id) {
|
|
|
43
43
|
};
|
|
44
44
|
});
|
|
45
45
|
}
|
|
46
|
+
/** Like {@link loadTemplateSources}, but paths match registry `sourceFiles` (e.g. `voice-showcase/agent.ts`). */
|
|
47
|
+
export function loadTemplateWorkspaceSources(id) {
|
|
48
|
+
const template = getTemplateById(id);
|
|
49
|
+
return template.sourceFiles.map((sourceFile) => {
|
|
50
|
+
const absolutePath = join(TEMPLATES_DIR, sourceFile);
|
|
51
|
+
if (!existsSync(absolutePath)) {
|
|
52
|
+
throw new Error(`Template source not found for ${id}: ${absolutePath}`);
|
|
53
|
+
}
|
|
54
|
+
return {
|
|
55
|
+
path: sourceFile,
|
|
56
|
+
content: readFileSync(absolutePath, "utf8"),
|
|
57
|
+
};
|
|
58
|
+
});
|
|
59
|
+
}
|
|
46
60
|
function seedBundlePath(id) {
|
|
47
61
|
return join(PACKAGE_ROOT, "dist", "templates", id, "agent.js");
|
|
48
62
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/templates/index.ts"],"names":[],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/templates/index.ts"],"names":[],"mappings":"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"}
|