@voicethere/agent 0.7.13 → 0.8.1

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.
Files changed (64) hide show
  1. package/README.md +14 -3
  2. package/dist/index.d.ts +1 -1
  3. package/dist/index.d.ts.map +1 -1
  4. package/dist/index.js +1 -1
  5. package/dist/index.js.map +1 -1
  6. package/dist/runtime.d.ts +10 -0
  7. package/dist/runtime.d.ts.map +1 -1
  8. package/dist/runtime.js +28 -2
  9. package/dist/runtime.js.map +1 -1
  10. package/dist/templates/echo/agent.js +16 -5
  11. package/dist/templates/echo-dc/agent.js +1 -1
  12. package/dist/templates/game-sync/agent.js +162 -125
  13. package/dist/templates/recording-consent/agent.js +12 -12
  14. package/dist/templates/registry.d.ts +1 -1
  15. package/dist/templates/registry.d.ts.map +1 -1
  16. package/dist/templates/registry.js +42 -26
  17. package/dist/templates/registry.js.map +1 -1
  18. package/dist/templates/voice-showcase/agent.js +64 -25
  19. package/dist/templates/voice-starter/agent.js +1 -1
  20. package/dist/templates/webhooks/agent.js +2 -2
  21. package/dist/templates/webhooks-redis/agent.js +11 -4
  22. package/dist/templates/world-sync/agent.js +829 -0
  23. package/dist/templates/world-sync-binary/agent.js +1031 -0
  24. package/package.json +3 -2
  25. package/templates/README.md +41 -69
  26. package/templates/crash/README.md +13 -0
  27. package/templates/echo/README.md +13 -0
  28. package/templates/{echo.ts → echo/agent.ts} +4 -6
  29. package/templates/echo-dc/README.md +13 -0
  30. package/templates/{echo-dc.ts → echo-dc/agent.ts} +1 -1
  31. package/templates/echo-smoke/README.md +13 -0
  32. package/templates/game-sync/README.md +21 -0
  33. package/templates/{game-sync.ts → game-sync/agent.ts} +126 -94
  34. package/templates/{game-sync-redis.ts → game-sync/redis.ts} +4 -13
  35. package/templates/{game-sync-sim.ts → game-sync/sim.ts} +14 -7
  36. package/templates/{game-sync-world-layout.ts → game-sync/world-layout.ts} +72 -30
  37. package/templates/game-sync-smoke/README.md +13 -0
  38. package/templates/mix-smoke/README.md +13 -0
  39. package/templates/positional-tts/README.md +13 -0
  40. package/templates/recording-consent/README.md +13 -0
  41. package/templates/redis-sync/README.md +23 -0
  42. package/templates/redis-sync/agent.ts +72 -95
  43. package/templates/redis-sync/world-layout.ts +103 -26
  44. package/templates/spatial-showcase/README.md +13 -0
  45. package/templates/voice-showcase/README.md +13 -0
  46. package/templates/voice-showcase/agent.ts +3 -1
  47. package/templates/voice-showcase/conversation.ts +9 -5
  48. package/templates/voice-showcase/delivery.ts +40 -10
  49. package/templates/voice-starter/README.md +13 -0
  50. package/templates/webhooks/README.md +13 -0
  51. package/templates/{webhooks.ts → webhooks/agent.ts} +1 -1
  52. package/templates/webhooks-redis/README.md +13 -0
  53. package/templates/{webhooks-redis.ts → webhooks-redis/agent.ts} +9 -9
  54. package/templates/world-sync/README.md +20 -0
  55. package/templates/world-sync/agent.ts +62 -0
  56. package/templates/world-sync-binary/README.md +21 -0
  57. package/templates/world-sync-binary/agent.ts +107 -0
  58. package/templates/world-sync-binary/protocol.ts +281 -0
  59. /package/templates/{crash.ts → crash/agent.ts} +0 -0
  60. /package/templates/{echo-smoke.ts → echo-smoke/agent.ts} +0 -0
  61. /package/templates/{game-sync-protocol.ts → game-sync/protocol.ts} +0 -0
  62. /package/templates/{game-sync-smoke.ts → game-sync-smoke/agent.ts} +0 -0
  63. /package/templates/{mix-smoke.ts → mix-smoke/agent.ts} +0 -0
  64. /package/templates/{agent.ts → voice-starter/agent.ts} +0 -0
@@ -10753,7 +10753,7 @@ var require_built3 = __commonJS({
10753
10753
  }
10754
10754
  });
10755
10755
 
10756
- // templates/game-sync.ts
10756
+ // templates/game-sync/agent.ts
10757
10757
  var import_ioredis = __toESM(require_built3(), 1);
10758
10758
 
10759
10759
  // dist/runtime.js
@@ -11477,14 +11477,19 @@ function buildIdleEnv(sessionId) {
11477
11477
  function sendToClient(sessionId, payload) {
11478
11478
  sendParentMessage({ type: "send_to_client", sessionId, payload });
11479
11479
  }
11480
- function sendBinaryToClient(sessionId, data, channel = "sync") {
11481
- const buffer = Buffer.isBuffer(data) ? data : Buffer.from(data);
11482
- sendParentMessage({
11483
- type: "send_binary_to_client",
11484
- sessionId,
11485
- data: buffer,
11486
- channel
11487
- });
11480
+ function asOutboundBuffer(data) {
11481
+ return Buffer.isBuffer(data) ? data : Buffer.from(data.buffer, data.byteOffset, data.byteLength);
11482
+ }
11483
+ function broadCastBinaryToClients(data, sessionIds, channel = "sync") {
11484
+ const buffer = asOutboundBuffer(data);
11485
+ for (const sessionId of sessionIds) {
11486
+ sendParentMessage({
11487
+ type: "send_binary_to_client",
11488
+ sessionId,
11489
+ data: buffer,
11490
+ channel
11491
+ });
11492
+ }
11488
11493
  }
11489
11494
  var AGENT_LOG_MESSAGE_MAX_CHARS = 2048;
11490
11495
  var AGENT_LOG_FIELDS_MAX_CHARS = 8192;
@@ -11542,7 +11547,7 @@ function agentLog(level, message, fieldsOrSessionId, sessionId) {
11542
11547
  sendParentMessage(buildAgentLogPayload(level, message, fields, resolvedSessionId));
11543
11548
  }
11544
11549
 
11545
- // templates/game-sync-protocol.ts
11550
+ // templates/game-sync/protocol.ts
11546
11551
  var MAX_LIVE_OBJECTS = 25;
11547
11552
  var REGISTER_NACK_REASON_WORLD_FULL = "world_full";
11548
11553
  var UNREGISTER_NACK_REASON_NOT_FOUND = "not_found";
@@ -11586,7 +11591,7 @@ function parseChatCommand(message) {
11586
11591
  return { text };
11587
11592
  }
11588
11593
 
11589
- // templates/game-sync-world-layout.ts
11594
+ // templates/game-sync/world-layout.ts
11590
11595
  var OBJECT_STRIDE = 9;
11591
11596
  var WORLD_FLOAT_COUNT = MAX_LIVE_OBJECTS * OBJECT_STRIDE;
11592
11597
  var WORLD_BYTE_LENGTH = WORLD_FLOAT_COUNT * 4;
@@ -11629,6 +11634,12 @@ function markSlotFree(world, slot) {
11629
11634
  world[start + i] = 0;
11630
11635
  }
11631
11636
  }
11637
+ var OBJECT_SLOT_SCRATCH = new Float32Array(OBJECT_STRIDE);
11638
+ var OBJECT_SLOT_SCRATCH_BUF = Buffer.from(
11639
+ OBJECT_SLOT_SCRATCH.buffer,
11640
+ OBJECT_SLOT_SCRATCH.byteOffset,
11641
+ OBJECT_SLOT_BYTE_LENGTH
11642
+ );
11632
11643
  function writeObjectSlot(world, slot, objectId, posX, posY, posZ, posW, dirX, dirY, dirZ, dirW) {
11633
11644
  const start = slot * OBJECT_STRIDE;
11634
11645
  world[start] = objectId;
@@ -11651,6 +11662,18 @@ function collectActiveObjectIds(world) {
11651
11662
  }
11652
11663
  return ids;
11653
11664
  }
11665
+ function collectActiveObjectIdsInto(world, dest) {
11666
+ let count = 0;
11667
+ const limit = Math.min(MAX_LIVE_OBJECTS, dest.length);
11668
+ for (let slot = 0; slot < MAX_LIVE_OBJECTS && count < limit; slot += 1) {
11669
+ const objectId = readSlotObjectId(world, slot);
11670
+ if (objectId !== 0) {
11671
+ dest[count] = objectId;
11672
+ count += 1;
11673
+ }
11674
+ }
11675
+ return count;
11676
+ }
11654
11677
  function liveWorldSnapshot(world, owners) {
11655
11678
  const ids = collectActiveObjectIds(world);
11656
11679
  ids.sort((a, b) => a - b);
@@ -11677,39 +11700,37 @@ function clampSimulationDtSec(elapsedMs, fallbackHz) {
11677
11700
  const elapsedSec = elapsedMs / 1e3;
11678
11701
  return Math.min(maxDt, Math.max(minDt, elapsedSec));
11679
11702
  }
11680
- function normalizeWorldBuffer(raw) {
11703
+ function normalizeWorldBuffer(raw, dest = createEmptyWorldBuffer()) {
11704
+ const destBytes = new Uint8Array(
11705
+ dest.buffer,
11706
+ dest.byteOffset,
11707
+ dest.byteLength
11708
+ );
11681
11709
  if (!raw || raw.byteLength === 0) {
11682
- return createEmptyWorldBuffer();
11710
+ destBytes.fill(0);
11711
+ return dest;
11683
11712
  }
11684
- const bytes = Math.floor(raw.byteLength / 4) * 4;
11685
- if (bytes === 0) {
11686
- return createEmptyWorldBuffer();
11713
+ const byteCount = Math.min(
11714
+ Math.floor(raw.byteLength / 4) * 4,
11715
+ destBytes.byteLength
11716
+ );
11717
+ if (byteCount > 0) {
11718
+ destBytes.set(raw.subarray(0, byteCount));
11687
11719
  }
11688
- const aligned = raw.buffer.slice(raw.byteOffset, raw.byteOffset + bytes);
11689
- const decoded = new Float32Array(aligned);
11690
- if (decoded.length === WORLD_FLOAT_COUNT) {
11691
- return decoded;
11720
+ if (byteCount < destBytes.byteLength) {
11721
+ destBytes.fill(0, byteCount);
11692
11722
  }
11693
- const normalized = createEmptyWorldBuffer();
11694
- normalized.set(
11695
- decoded.subarray(0, Math.min(decoded.length, WORLD_FLOAT_COUNT))
11696
- );
11697
- return normalized;
11698
- }
11699
- function objectIdHeaderBytes(objectId) {
11700
- const header = new Float32Array([objectId]);
11701
- return Buffer.from(header.buffer, header.byteOffset, 4);
11723
+ return dest;
11702
11724
  }
11703
-
11704
- // templates/game-sync-redis.ts
11705
- function buildObjectIdHeadersBlob() {
11706
- const parts = [];
11725
+ var OBJECT_ID_HEADERS_BLOB = (() => {
11726
+ const blob = Buffer.alloc(MAX_LIVE_OBJECTS * 4);
11707
11727
  for (let objectId = 1; objectId <= MAX_LIVE_OBJECTS; objectId += 1) {
11708
- parts.push(objectIdHeaderBytes(objectId));
11728
+ blob.writeFloatLE(objectId, (objectId - 1) * 4);
11709
11729
  }
11710
- return Buffer.concat(parts);
11711
- }
11712
- var OBJECT_ID_HEADERS_BLOB = buildObjectIdHeadersBlob();
11730
+ return blob;
11731
+ })();
11732
+
11733
+ // templates/game-sync/redis.ts
11713
11734
  var LUA_ALLOCATE_OBJECT = `
11714
11735
  local key = KEYS[1]
11715
11736
  local size = tonumber(ARGV[1])
@@ -11797,14 +11818,15 @@ var REDIS_EVAL_KEYS = {
11797
11818
  headers: OBJECT_ID_HEADERS_BLOB
11798
11819
  };
11799
11820
 
11800
- // templates/game-sync-sim.ts
11821
+ // templates/game-sync/sim.ts
11801
11822
  var BOARD_WIDTH = 1280;
11802
11823
  var BOARD_HEIGHT = 720;
11803
11824
  var OBJECT_RADIUS = 25;
11804
11825
  var COLLISION_RESTITUTION = 1;
11805
11826
  var OBJECT_STRIDE2 = 9;
11806
- function simulateWorldStep(worldState2, dtSec, activeObjectIds) {
11807
- for (const objectId of activeObjectIds) {
11827
+ function simulateWorldStep(worldState2, dtSec, activeObjectIds2, count = activeObjectIds2.length) {
11828
+ for (let i = 0; i < count; i += 1) {
11829
+ const objectId = activeObjectIds2[i] ?? 0;
11808
11830
  const slot = objectIdToSlot(objectId);
11809
11831
  const start = slot * OBJECT_STRIDE2;
11810
11832
  if (start < 0 || start + OBJECT_STRIDE2 > worldState2.length) continue;
@@ -11827,13 +11849,13 @@ function simulateWorldStep(worldState2, dtSec, activeObjectIds) {
11827
11849
  worldState2[start + 5] = vx;
11828
11850
  worldState2[start + 6] = vy;
11829
11851
  }
11830
- for (let i = 0; i < activeObjectIds.length; i += 1) {
11831
- const aId = activeObjectIds[i];
11852
+ for (let i = 0; i < count; i += 1) {
11853
+ const aId = activeObjectIds2[i] ?? 0;
11832
11854
  const aSlot = objectIdToSlot(aId);
11833
11855
  const aStart = aSlot * OBJECT_STRIDE2;
11834
11856
  if (aStart < 0 || aStart + OBJECT_STRIDE2 > worldState2.length) continue;
11835
- for (let j = i + 1; j < activeObjectIds.length; j += 1) {
11836
- const bId = activeObjectIds[j];
11857
+ for (let j = i + 1; j < count; j += 1) {
11858
+ const bId = activeObjectIds2[j] ?? 0;
11837
11859
  const bSlot = objectIdToSlot(bId);
11838
11860
  const bStart = bSlot * OBJECT_STRIDE2;
11839
11861
  if (bStart < 0 || bStart + OBJECT_STRIDE2 > worldState2.length) continue;
@@ -11900,7 +11922,7 @@ function simulateWorldStep(worldState2, dtSec, activeObjectIds) {
11900
11922
  }
11901
11923
  }
11902
11924
 
11903
- // templates/game-sync.ts
11925
+ // templates/game-sync/agent.ts
11904
11926
  var BROADCAST_HZ = 60;
11905
11927
  var BROADCAST_INTERVAL_MS = Math.floor(1e3 / BROADCAST_HZ);
11906
11928
  var SIM_LOCK_TTL_MS = BROADCAST_INTERVAL_MS * 2;
@@ -11908,10 +11930,16 @@ var SIM_LOCK_RETRY_MS = 5;
11908
11930
  var MIN_SPEED = 90;
11909
11931
  var MAX_SPEED = 180;
11910
11932
  var connectedSessions = /* @__PURE__ */ new Set();
11933
+ var connectedSessionList = [];
11911
11934
  var objectOwners = /* @__PURE__ */ new Map();
11912
11935
  var sessionObjects = /* @__PURE__ */ new Map();
11913
11936
  var freeSlots = [];
11914
11937
  var worldState = createEmptyWorldBuffer();
11938
+ var worldStateBytes = Buffer.from(
11939
+ worldState.buffer,
11940
+ worldState.byteOffset,
11941
+ worldState.byteLength
11942
+ );
11915
11943
  var redis = null;
11916
11944
  var broadcastTimer = null;
11917
11945
  var worldMutationChain = Promise.resolve();
@@ -11961,18 +11989,22 @@ function rand(min, max) {
11961
11989
  function randomVelocity() {
11962
11990
  return (Math.random() < 0.5 ? -1 : 1) * rand(MIN_SPEED, MAX_SPEED);
11963
11991
  }
11992
+ var INITIAL_TAIL = new Float32Array(8);
11993
+ var INITIAL_TAIL_BUF = Buffer.from(
11994
+ INITIAL_TAIL.buffer,
11995
+ INITIAL_TAIL.byteOffset,
11996
+ INITIAL_TAIL.byteLength
11997
+ );
11964
11998
  function randomInitialTail() {
11965
- const floats = new Float32Array([
11966
- rand(OBJECT_RADIUS, BOARD_WIDTH - OBJECT_RADIUS),
11967
- rand(OBJECT_RADIUS, BOARD_HEIGHT - OBJECT_RADIUS),
11968
- 0,
11969
- 1,
11970
- randomVelocity(),
11971
- randomVelocity(),
11972
- 0,
11973
- 0
11974
- ]);
11975
- return Buffer.from(floats.buffer, floats.byteOffset, floats.byteLength);
11999
+ INITIAL_TAIL[0] = rand(OBJECT_RADIUS, BOARD_WIDTH - OBJECT_RADIUS);
12000
+ INITIAL_TAIL[1] = rand(OBJECT_RADIUS, BOARD_HEIGHT - OBJECT_RADIUS);
12001
+ INITIAL_TAIL[2] = 0;
12002
+ INITIAL_TAIL[3] = 1;
12003
+ INITIAL_TAIL[4] = randomVelocity();
12004
+ INITIAL_TAIL[5] = randomVelocity();
12005
+ INITIAL_TAIL[6] = 0;
12006
+ INITIAL_TAIL[7] = 0;
12007
+ return INITIAL_TAIL_BUF;
11976
12008
  }
11977
12009
  function attachObjectToSession(sessionId, objectId) {
11978
12010
  let owned = sessionObjects.get(sessionId);
@@ -12054,7 +12086,7 @@ async function registerObjectInRedis(sessionId) {
12054
12086
  return null;
12055
12087
  }
12056
12088
  attachObjectToSession(sessionId, objectId);
12057
- worldState = await loadWorldFromRedis();
12089
+ await loadWorldFromRedis();
12058
12090
  return objectId;
12059
12091
  }
12060
12092
  async function releaseObjectInRedis(objectId) {
@@ -12074,7 +12106,7 @@ async function releaseObjectInRedis(objectId) {
12074
12106
  REDIS_EVAL_KEYS.headers
12075
12107
  );
12076
12108
  if (Number(released) === 1) {
12077
- worldState = await loadWorldFromRedis();
12109
+ await loadWorldFromRedis();
12078
12110
  }
12079
12111
  return Number(released) === 1;
12080
12112
  }
@@ -12126,83 +12158,84 @@ function notifyObjectReleased(objectId, ownerSessionId) {
12126
12158
  });
12127
12159
  }
12128
12160
  }
12129
- function copyWorldBuffer(world) {
12130
- return Buffer.from(world.buffer, world.byteOffset, world.byteLength);
12131
- }
12132
- function broadcastWorldBuffer(world) {
12133
- if (connectedSessions.size === 0) return;
12134
- const payload = copyWorldBuffer(world);
12135
- for (const sessionId of connectedSessions) {
12136
- sendBinaryToClient(sessionId, payload, "sync");
12137
- }
12161
+ function broadcastWorldBuffer() {
12162
+ if (connectedSessionList.length === 0) return;
12163
+ broadCastBinaryToClients(worldStateBytes, connectedSessionList, "sync");
12138
12164
  }
12139
12165
  async function loadWorldFromRedis() {
12140
- if (!redis) {
12141
- return new Float32Array(worldState);
12142
- }
12166
+ if (!redis) return;
12143
12167
  const raw = await redis.getBuffer(REDIS_WORLD_KEY);
12144
- return normalizeWorldBuffer(raw);
12168
+ normalizeWorldBuffer(raw, worldState);
12145
12169
  }
12146
- async function saveWorldToRedis(world) {
12170
+ async function saveWorldToRedis() {
12147
12171
  if (!redis) return;
12148
- await redis.set(REDIS_WORLD_KEY, copyWorldBuffer(world));
12172
+ await redis.set(REDIS_WORLD_KEY, worldStateBytes);
12149
12173
  }
12150
- async function runSimulationTick() {
12174
+ var activeObjectIds = new Int32Array(MAX_LIVE_OBJECTS);
12175
+ var SIM_LOCK_TRY_ONCE = { retryUntilAcquired: false };
12176
+ var SIM_TICK_PLAN_INPUT = { lockAcquired: false, connectedSessions };
12177
+ function takeTickDtSec() {
12151
12178
  const now = Date.now();
12152
12179
  const dt = clampSimulationDtSec(
12153
12180
  lastTickTimeMs === 0 ? 0 : now - lastTickTimeMs,
12154
12181
  BROADCAST_HZ
12155
12182
  );
12156
12183
  lastTickTimeMs = now;
12184
+ return dt;
12185
+ }
12186
+ function stepWorldInPlace() {
12187
+ const dt = takeTickDtSec();
12188
+ const count = collectActiveObjectIdsInto(worldState, activeObjectIds);
12189
+ simulateWorldStep(worldState, dt, activeObjectIds, count);
12190
+ }
12191
+ async function stepWorldInMemory() {
12192
+ stepWorldInPlace();
12193
+ }
12194
+ async function stepWorldWithRedis() {
12195
+ await loadWorldFromRedis();
12196
+ stepWorldInPlace();
12197
+ await saveWorldToRedis();
12198
+ broadcastWorldBuffer();
12199
+ return true;
12200
+ }
12201
+ async function runRedisTick() {
12202
+ const lockResult = await withRedisSimLock(
12203
+ stepWorldWithRedis,
12204
+ SIM_LOCK_TRY_ONCE
12205
+ );
12206
+ if (lockResult !== null) return;
12207
+ takeTickDtSec();
12208
+ if (planRedisSimTick(SIM_TICK_PLAN_INPUT) === "relay") {
12209
+ await loadWorldFromRedis();
12210
+ broadcastWorldBuffer();
12211
+ }
12212
+ }
12213
+ async function runSimulationTick() {
12157
12214
  if (!redis) {
12158
- await withWorldMutation(async () => {
12159
- simulateWorldStep(worldState, dt, collectActiveObjectIds(worldState));
12160
- });
12161
- broadcastWorldBuffer(worldState);
12215
+ await withWorldMutation(stepWorldInMemory);
12216
+ broadcastWorldBuffer();
12162
12217
  return;
12163
12218
  }
12164
- await withWorldMutation(async () => {
12165
- const lockResult = await withRedisSimLock(
12166
- async () => {
12167
- const world = await loadWorldFromRedis();
12168
- const activeObjectIds = collectActiveObjectIds(world);
12169
- simulateWorldStep(world, dt, activeObjectIds);
12170
- await saveWorldToRedis(world);
12171
- worldState = world;
12172
- broadcastWorldBuffer(world);
12173
- return true;
12174
- },
12175
- { retryUntilAcquired: false }
12176
- );
12177
- if (lockResult === null) {
12178
- const plan = planRedisSimTick({
12179
- lockAcquired: false,
12180
- connectedSessions
12181
- });
12182
- if (plan === "relay") {
12183
- const world = await loadWorldFromRedis();
12184
- worldState = world;
12185
- broadcastWorldBuffer(world);
12186
- }
12219
+ await withWorldMutation(runRedisTick);
12220
+ }
12221
+ function logTickError(error) {
12222
+ const detail = error instanceof Error ? error.message : String(error);
12223
+ agentLog("error", `world tick failed: ${detail}`);
12224
+ }
12225
+ function onBroadcastTick() {
12226
+ if (!redis && connectedSessions.size < 1) {
12227
+ if (broadcastTimer) {
12228
+ clearInterval(broadcastTimer);
12229
+ broadcastTimer = null;
12187
12230
  }
12188
- });
12231
+ return;
12232
+ }
12233
+ void runSimulationTick().catch(logTickError);
12189
12234
  }
12190
12235
  function startBroadcastLoopIfNeeded() {
12191
12236
  if (broadcastTimer) return;
12192
12237
  if (!redis && connectedSessions.size < 1) return;
12193
- broadcastTimer = setInterval(() => {
12194
- if (!redis && connectedSessions.size < 1) {
12195
- if (broadcastTimer) {
12196
- clearInterval(broadcastTimer);
12197
- broadcastTimer = null;
12198
- }
12199
- return;
12200
- }
12201
- void runSimulationTick().catch((error) => {
12202
- const detail = error instanceof Error ? error.message : String(error);
12203
- agentLog("error", `world tick failed: ${detail}`);
12204
- });
12205
- }, BROADCAST_INTERVAL_MS);
12238
+ broadcastTimer = setInterval(onBroadcastTick, BROADCAST_INTERVAL_MS);
12206
12239
  agentLog("info", `world loop started (${BROADCAST_HZ}Hz)`);
12207
12240
  }
12208
12241
  function stopBroadcastLoopIfNeeded() {
@@ -12217,8 +12250,7 @@ async function ensureRedisWorldInitialized() {
12217
12250
  if (!redis) return;
12218
12251
  const existing = await redis.getBuffer(REDIS_WORLD_KEY);
12219
12252
  if (!existing || existing.byteLength === 0) {
12220
- const empty = createEmptyWorldBuffer();
12221
- await redis.set(REDIS_WORLD_KEY, copyWorldBuffer(empty));
12253
+ await redis.set(REDIS_WORLD_KEY, worldStateBytes);
12222
12254
  }
12223
12255
  }
12224
12256
  defineAgent({
@@ -12229,25 +12261,25 @@ defineAgent({
12229
12261
  "warn",
12230
12262
  "AGENT_REDIS_URL unset \u2014 game-sync uses per-worker in-memory world only"
12231
12263
  );
12232
- worldState = createEmptyWorldBuffer();
12233
12264
  return;
12234
12265
  }
12235
- redis = new import_ioredis.default(redisUrl, {
12266
+ redis = new import_ioredis.Redis(redisUrl, {
12236
12267
  maxRetriesPerRequest: 3,
12237
12268
  lazyConnect: true
12238
12269
  });
12239
12270
  await redis.connect();
12240
12271
  await ensureRedisWorldInitialized();
12241
- worldState = await loadWorldFromRedis();
12272
+ await loadWorldFromRedis();
12242
12273
  startBroadcastLoopIfNeeded();
12243
12274
  agentLog("info", "game-sync agent connected to project Redis world buffer");
12244
12275
  },
12245
12276
  async onClientJoin({ sessionId }) {
12246
- connectedSessions.add(sessionId);
12247
- startBroadcastLoopIfNeeded();
12248
- if (redis) {
12249
- worldState = await loadWorldFromRedis();
12277
+ if (!connectedSessions.has(sessionId)) {
12278
+ connectedSessions.add(sessionId);
12279
+ connectedSessionList.push(sessionId);
12250
12280
  }
12281
+ startBroadcastLoopIfNeeded();
12282
+ await loadWorldFromRedis();
12251
12283
  sendToClient(sessionId, {
12252
12284
  type: "world_snapshot",
12253
12285
  objects: liveWorldSnapshot(worldState, objectOwners)
@@ -12255,7 +12287,12 @@ defineAgent({
12255
12287
  agentLog("info", `join ${sessionId} connected=${connectedSessions.size}`);
12256
12288
  },
12257
12289
  async onClientLeave({ sessionId }) {
12258
- connectedSessions.delete(sessionId);
12290
+ if (connectedSessions.delete(sessionId)) {
12291
+ const index = connectedSessionList.indexOf(sessionId);
12292
+ if (index !== -1) {
12293
+ connectedSessionList.splice(index, 1);
12294
+ }
12295
+ }
12259
12296
  for (const [objectId, ownerSessionId] of objectOwners) {
12260
12297
  if (ownerSessionId === sessionId) {
12261
12298
  objectOwners.delete(objectId);
@@ -849,7 +849,7 @@ function createInitialState(recordingAvailable) {
849
849
  consentSkipped: true
850
850
  };
851
851
  }
852
- function speakAndChat(text) {
852
+ function speakAndChat2(text) {
853
853
  return {
854
854
  speakLines: [text],
855
855
  messages: [{ type: "chat_reply", text }]
@@ -858,7 +858,7 @@ function speakAndChat(text) {
858
858
  function beginSession(recordingAvailable) {
859
859
  const state = createInitialState(recordingAvailable);
860
860
  if (recordingAvailable) {
861
- const prompt = speakAndChat(CONSENT_PROMPT);
861
+ const prompt = speakAndChat2(CONSENT_PROMPT);
862
862
  return {
863
863
  state,
864
864
  speakLines: prompt.speakLines,
@@ -866,8 +866,8 @@ function beginSession(recordingAvailable) {
866
866
  recordingAction: null
867
867
  };
868
868
  }
869
- const skip = speakAndChat(RECORDING_DISABLED_SKIP_MESSAGE);
870
- const name = speakAndChat(NAME_PROMPT);
869
+ const skip = speakAndChat2(RECORDING_DISABLED_SKIP_MESSAGE);
870
+ const name = speakAndChat2(NAME_PROMPT);
871
871
  return {
872
872
  state,
873
873
  speakLines: [...skip.speakLines, ...name.speakLines],
@@ -951,7 +951,7 @@ function sanitizeToken(raw, maxLen) {
951
951
  return value;
952
952
  }
953
953
  function askNameAgain(state) {
954
- const prompt = speakAndChat(
954
+ const prompt = speakAndChat2(
955
955
  "Sorry, I didn't catch your name. May I have your name please?"
956
956
  );
957
957
  return {
@@ -962,7 +962,7 @@ function askNameAgain(state) {
962
962
  };
963
963
  }
964
964
  function askBirthdateAgain(state) {
965
- const prompt = speakAndChat(
965
+ const prompt = speakAndChat2(
966
966
  "Sorry, I didn't catch your date of birth. Could you repeat it?"
967
967
  );
968
968
  return {
@@ -974,7 +974,7 @@ function askBirthdateAgain(state) {
974
974
  }
975
975
  function finishAfterPii(state) {
976
976
  const next = { ...state, phase: "complete" };
977
- const thankYou = speakAndChat(
977
+ const thankYou = speakAndChat2(
978
978
  `Thank you, ${state.name}. We have your date of birth on file.`
979
979
  );
980
980
  let recordingAction = null;
@@ -997,7 +997,7 @@ function handleUtterance(state, utterance) {
997
997
  phase: "awaitingName",
998
998
  consent: false
999
999
  };
1000
- const name = speakAndChat(NAME_PROMPT);
1000
+ const name = speakAndChat2(NAME_PROMPT);
1001
1001
  return {
1002
1002
  state: next,
1003
1003
  speakLines: name.speakLines,
@@ -1011,7 +1011,7 @@ function handleUtterance(state, utterance) {
1011
1011
  phase: "awaitingName",
1012
1012
  consent: true
1013
1013
  };
1014
- const name = speakAndChat(NAME_PROMPT);
1014
+ const name = speakAndChat2(NAME_PROMPT);
1015
1015
  return {
1016
1016
  state: next,
1017
1017
  speakLines: name.speakLines,
@@ -1019,7 +1019,7 @@ function handleUtterance(state, utterance) {
1019
1019
  recordingAction: "pause"
1020
1020
  };
1021
1021
  }
1022
- const retry = speakAndChat(
1022
+ const retry = speakAndChat2(
1023
1023
  "Please say yes or no \u2014 may we record this conversation?"
1024
1024
  );
1025
1025
  return {
@@ -1039,7 +1039,7 @@ function handleUtterance(state, utterance) {
1039
1039
  phase: "awaitingBirthdate",
1040
1040
  name
1041
1041
  };
1042
- const birthdate = speakAndChat(BIRTHDATE_PROMPT);
1042
+ const birthdate = speakAndChat2(BIRTHDATE_PROMPT);
1043
1043
  return {
1044
1044
  state: next,
1045
1045
  speakLines: birthdate.speakLines,
@@ -1055,7 +1055,7 @@ function handleUtterance(state, utterance) {
1055
1055
  return finishAfterPii({ ...state, birthdate });
1056
1056
  }
1057
1057
  case "complete": {
1058
- const done = speakAndChat("We are all set. How can I help you today?");
1058
+ const done = speakAndChat2("We are all set. How can I help you today?");
1059
1059
  return {
1060
1060
  state,
1061
1061
  speakLines: done.speakLines,
@@ -1,7 +1,7 @@
1
1
  export type TemplateKind = "product" | "e2e";
2
2
  export interface AgentTemplateDefinition {
3
3
  id: string;
4
- /** Path relative to `templates/` (e.g. `echo.ts`, `redis-sync/agent.ts`). */
4
+ /** Path relative to `templates/` (e.g. `echo/agent.ts`, `redis-sync/agent.ts`). */
5
5
  entry: string;
6
6
  kind: TemplateKind;
7
7
  /** When true, a prebuilt bundle is published at `dist/templates/<id>/agent.js`. */
@@ -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;IACtB,oFAAoF;IACpF,eAAe,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;CAC1C;AAED,eAAO,MAAM,eAAe,EAAE,SAAS,uBAAuB,EAmKpD,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"}
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,mFAAmF;IACnF,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;IACtB,oFAAoF;IACpF,eAAe,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;CAC1C;AAED,eAAO,MAAM,eAAe,EAAE,SAAS,uBAAuB,EAqLpD,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"}