let-them-talk 4.2.0 → 5.2.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.
Files changed (39) hide show
  1. package/CHANGELOG.md +640 -540
  2. package/README.md +592 -415
  3. package/cli.js +1089 -589
  4. package/conversation-templates/autonomous-feature.json +22 -0
  5. package/conversation-templates/code-review.json +21 -11
  6. package/conversation-templates/debug-squad.json +21 -11
  7. package/conversation-templates/feature-build.json +21 -11
  8. package/conversation-templates/research-write.json +21 -11
  9. package/dashboard.html +9250 -7771
  10. package/dashboard.js +1232 -29
  11. package/office/agents.js +148 -4
  12. package/office/animation.js +68 -0
  13. package/office/assets.js +431 -0
  14. package/office/builder.js +355 -0
  15. package/office/building-interior.js +261 -0
  16. package/office/campus-env.js +119 -23
  17. package/office/car-hud.js +368 -0
  18. package/office/daynight.js +221 -0
  19. package/office/economy-hud.js +432 -0
  20. package/office/economy-ui.js +238 -0
  21. package/office/environment.js +818 -808
  22. package/office/face.js +65 -0
  23. package/office/fast-travel.js +215 -0
  24. package/office/hq-building.js +295 -0
  25. package/office/index.js +1095 -423
  26. package/office/instancing.js +160 -0
  27. package/office/lod-manager.js +165 -0
  28. package/office/multiplayer-hud.js +428 -0
  29. package/office/net-client.js +299 -0
  30. package/office/particles.js +172 -0
  31. package/office/player.js +658 -436
  32. package/office/post-processing.js +82 -0
  33. package/office/sky.js +319 -0
  34. package/office/street-furniture.js +308 -0
  35. package/office/vehicle.js +455 -0
  36. package/office/world-save.js +91 -0
  37. package/package.json +59 -59
  38. package/server.js +7190 -4685
  39. package/conversation-templates/managed-team.json +0 -12
package/office/agents.js CHANGED
@@ -1,9 +1,9 @@
1
1
  import { S } from './state.js';
2
- import { DESK_POSITIONS, SPAWN_POS } from './constants.js';
2
+ import { DESK_POSITIONS, SPAWN_POS, REST_AREA_POS, REST_AREA_ENTRANCE } from './constants.js';
3
3
  import { createCharacter } from './character.js';
4
4
  import { resolveAppearance } from './appearance.js';
5
5
  import { buildHair } from './hair.js';
6
- import { buildFaceSprite } from './face.js';
6
+ import { buildFaceSprite, setEmotion } from './face.js';
7
7
  import { buildOutfit, removeOutfit } from './outfits.js';
8
8
  import { getNavigationPath } from './navigation.js';
9
9
 
@@ -291,6 +291,36 @@ export function syncAgents() {
291
291
  }
292
292
  }
293
293
 
294
+ // --- Autonomous behaviors: sleeping → rest area, waking → back to desk ---
295
+ if (newState === 'sleeping' && oldState === 'active' && existing.location === 'desk' && existing.registered && !existing.dying) {
296
+ // Agent fell asleep — walk to rest area after a short delay
297
+ existing.location = 'walking';
298
+ (function(a) {
299
+ setTimeout(function() {
300
+ showBubble(a, 'Need a break...');
301
+ a.isSitting = false;
302
+ navigateTo(a, REST_AREA_ENTRANCE.x, REST_AREA_ENTRANCE.z, function() {
303
+ navigateTo(a, REST_AREA_POS.x, REST_AREA_POS.z, function() {
304
+ a.location = 'rest';
305
+ a.state = 'sleeping';
306
+ showBubble(a, 'zzz...');
307
+ });
308
+ });
309
+ }, 1000 + Math.random() * 2000);
310
+ })(existing);
311
+ }
312
+ if (newState === 'active' && (oldState === 'sleeping' || existing.location === 'rest') && existing.location !== 'desk' && existing.registered && !existing.dying) {
313
+ // Agent woke up — walk back to desk
314
+ existing.location = 'walking';
315
+ existing.state = 'active';
316
+ (function(a) {
317
+ showBubble(a, 'Back to work!');
318
+ navigateTo(a, a.deskPos.x, a.deskPos.z + 0.7, function() {
319
+ a.location = 'desk';
320
+ });
321
+ })(existing);
322
+ }
323
+
294
324
  existing.displayName = info.display_name || name;
295
325
  var wasListening = existing.isListening;
296
326
  existing.isListening = !!(info.is_listening);
@@ -313,11 +343,21 @@ export function syncAgents() {
313
343
  if (prevTask && prevTask.status !== 'done' && task.status === 'done') {
314
344
  existing.taskCelebration = 2;
315
345
  existing.celebrateTimer = 1.5;
346
+ setEmotion(existing, 'happy', 6);
347
+ }
348
+ // Blocked task → frustrated face
349
+ if (task.status === 'blocked' && (!prevTask || prevTask.status !== 'blocked')) {
350
+ setEmotion(existing, 'frustrated', 8);
316
351
  }
317
352
  } else {
318
353
  existing.currentTask = null;
319
354
  }
320
355
 
356
+ // Listening agents look focused
357
+ if (existing.isListening && !wasListening) {
358
+ setEmotion(existing, 'focused', 10);
359
+ }
360
+
321
361
  var newApp = info.appearance || {};
322
362
  if (JSON.stringify(newApp) !== JSON.stringify(existing.appearance)) {
323
363
  existing.appearance = newApp;
@@ -329,6 +369,66 @@ export function syncAgents() {
329
369
  }
330
370
  }
331
371
 
372
+ // --- Random social behavior: idle agents occasionally stretch or look around ---
373
+ // Limit concurrent social walks to prevent traffic jams (max 2 walking at once)
374
+ var walkingCount = 0;
375
+ for (var wn in S.agents3d) { if (S.agents3d[wn].location === 'walking') walkingCount++; }
376
+
377
+ for (var sn in S.agents3d) {
378
+ var sa = S.agents3d[sn];
379
+ if (!sa.registered || sa.state !== 'active' || sa.location !== 'desk' || sa.target) continue;
380
+ if (!sa._socialTimer) sa._socialTimer = 30 + Math.random() * 60;
381
+ sa._socialTimer -= 2; // syncAgents runs every ~2s
382
+ if (sa._socialTimer <= 0) {
383
+ sa._socialTimer = 40 + Math.random() * 80; // next social event in 40-120s
384
+ // Pick a random behavior: stretch, look around, or visit another agent
385
+ var roll = Math.random();
386
+ if (roll < 0.4) {
387
+ // Stretch at desk
388
+ sa.stretchTimer = 2;
389
+ } else if (roll < 0.7) {
390
+ // Look around curiously
391
+ sa.thinkTimer = 1.5;
392
+ } else if (walkingCount < 2) {
393
+ // Walk to a random nearby agent's desk to "chat" then return (max 2 concurrent)
394
+ var others = [];
395
+ for (var on in S.agents3d) {
396
+ if (on !== sn && S.agents3d[on].registered && S.agents3d[on].state === 'active' && S.agents3d[on].location === 'desk') {
397
+ others.push(S.agents3d[on]);
398
+ }
399
+ }
400
+ if (others.length > 0) {
401
+ var buddy = others[Math.floor(Math.random() * others.length)];
402
+ (function(a, b) {
403
+ a.location = 'walking';
404
+ a.isSitting = false;
405
+ showBubble(a, 'Hey ' + b.displayName + '!');
406
+ setEmotion(a, 'playful', 6);
407
+ var stopX = b.deskPos.x + 1.5;
408
+ var stopZ = b.deskPos.z + 0.7;
409
+ navigateTo(a, stopX, stopZ, function() {
410
+ // Face buddy
411
+ var dx = b.pos.x - a.pos.x;
412
+ var dz = b.pos.z - a.pos.z;
413
+ a.facingTarget = Math.atan2(dx, dz);
414
+ a.waveTimer = 0.8;
415
+ // Buddy turns toward visitor
416
+ b.facingTarget = Math.atan2(-dx, -dz);
417
+ setTimeout(function() {
418
+ showBubble(a, 'Back to it!');
419
+ navigateTo(a, a.deskPos.x, a.deskPos.z + 0.7, function() {
420
+ a.location = 'desk';
421
+ });
422
+ // Buddy turns back to desk
423
+ setTimeout(function() { b.facingTarget = Math.PI; }, 1500);
424
+ }, 3000 + Math.random() * 2000);
425
+ });
426
+ })(sa, buddy);
427
+ }
428
+ }
429
+ }
430
+ }
431
+
332
432
  for (var n in S.agents3d) {
333
433
  if (!window.cachedAgents[n]) {
334
434
  var deadAgent = S.agents3d[n];
@@ -345,8 +445,11 @@ export function processMessages() {
345
445
  var history = window.cachedHistory;
346
446
  if (!history || history.length === 0) return;
347
447
 
348
- var newMsgs = history.slice(S.lastProcessedMsg);
349
- S.lastProcessedMsg = history.length;
448
+ // Use window-level counter so it persists across 3D stop/start cycles (tab switches)
449
+ // This prevents message replay when user switches from Messages tab back to 3D Hub
450
+ if (typeof window._lastProcessedMsg === 'undefined') window._lastProcessedMsg = 0;
451
+ var newMsgs = history.slice(window._lastProcessedMsg);
452
+ window._lastProcessedMsg = history.length;
350
453
 
351
454
  for (var i = 0; i < newMsgs.length; i++) {
352
455
  var msg = newMsgs[i];
@@ -357,6 +460,37 @@ export function processMessages() {
357
460
  from.lastMessageTime = Date.now();
358
461
  flashDeskScreen(from.deskIdx);
359
462
 
463
+ // Instant preview bubble — show short text immediately before walk animation
464
+ // Gives users instant visual feedback that the agent is about to speak
465
+ var preview = text.length > 30 ? text.substring(0, 27) + '...' : text;
466
+ showBubble(from, preview);
467
+
468
+ // Auto-celebrate on task completion events
469
+ if (text.indexOf('[EVENT] Task') >= 0 && text.indexOf('completed') >= 0) {
470
+ from.celebrateTimer = 1.5;
471
+ from.taskCelebration = 2;
472
+ }
473
+
474
+ // Emotion detection from message content
475
+ var textLower = text.toLowerCase();
476
+ if (textLower.indexOf('done') >= 0 || textLower.indexOf('pass') >= 0 || textLower.indexOf('success') >= 0 || textLower.indexOf('great') >= 0 || textLower.indexOf('shipped') >= 0) {
477
+ setEmotion(from, 'happy', 5);
478
+ } else if (textLower.indexOf('error') >= 0 || textLower.indexOf('fail') >= 0 || textLower.indexOf('bug') >= 0 || textLower.indexOf('broken') >= 0) {
479
+ setEmotion(from, 'frustrated', 5);
480
+ } else if (textLower.indexOf('?') >= 0 && (textLower.indexOf('how') >= 0 || textLower.indexOf('why') >= 0 || textLower.indexOf('what if') >= 0)) {
481
+ setEmotion(from, 'thinking', 4);
482
+ } else if (textLower.indexOf('!') >= 0 && (textLower.indexOf('wow') >= 0 || textLower.indexOf('amazing') >= 0 || textLower.indexOf('awesome') >= 0)) {
483
+ setEmotion(from, 'excited', 4);
484
+ }
485
+
486
+ // Target agent gets surprised when directly addressed
487
+ if (msg.to && msg.to !== 'all' && S.agents3d[msg.to]) {
488
+ var targetAgent = S.agents3d[msg.to];
489
+ if (targetAgent.registered && targetAgent.isSitting) {
490
+ setEmotion(targetAgent, 'surprised', 2);
491
+ }
492
+ }
493
+
360
494
  // Contextual gesture based on message type
361
495
  var isBC = !msg.to || msg.to === 'all';
362
496
  if (isBC) {
@@ -365,6 +499,16 @@ export function processMessages() {
365
499
  from.pointTimer = 0.6;
366
500
  }
367
501
 
502
+ // Glance reaction — nearby sitting agents glance toward the speaker
503
+ for (var gn in S.agents3d) {
504
+ var ga = S.agents3d[gn];
505
+ if (gn === msg.from || gn === msg.to || !ga.registered || ga.state !== 'active' || !ga.isSitting) continue;
506
+ var gdx = from.pos.x - ga.pos.x;
507
+ ga._glanceTarget = from.name;
508
+ ga._glanceDirection = gdx > 0 ? 1 : -1; // left or right glance
509
+ ga._glanceTimer = 0;
510
+ }
511
+
368
512
  if (msg.to && msg.to !== 'all' && S.agents3d[msg.to]) {
369
513
  var target = S.agents3d[msg.to];
370
514
  (function(f, t, txt) {
@@ -120,6 +120,56 @@ export function updateAgent(agent, dt, time) {
120
120
  agent.parts.head.rotation.x = -stPhase * 0.2;
121
121
  }
122
122
 
123
+ // Active typing animation — subtle hand/finger movement when working at desk
124
+ if (agent.isSitting && agent.state === 'active' && !agent.isListening && !isWalking && !isSleeping) {
125
+ var typeSpeed = 8 + Math.sin(time * 0.7 + agent.name.length) * 2; // vary per agent
126
+ var typeL = Math.sin(time * typeSpeed) * 0.06;
127
+ var typeR = Math.sin(time * typeSpeed + 1.5) * 0.06; // offset for alternating hands
128
+ agent.parts.leftForearm.rotation.x += typeL;
129
+ agent.parts.rightForearm.rotation.x += typeR;
130
+ // Subtle head bob while typing (looking at screen)
131
+ agent.parts.head.rotation.x += Math.sin(time * 1.2) * 0.015;
132
+ }
133
+
134
+ // Frustrated gesture — when task is blocked, head in hands periodically
135
+ if (agent.currentTask && agent.currentTask.status === 'blocked' && agent.isSitting && !isWalking) {
136
+ if (!agent.frustratedTimer) agent.frustratedTimer = 3 + Math.random() * 5;
137
+ agent.frustratedTimer -= dt;
138
+ if (agent.frustratedTimer <= 0 && agent.frustratedTimer > -2.5) {
139
+ // Head drops, arms come up to cradle head
140
+ var fT = Math.min(1, (-agent.frustratedTimer) / 0.5);
141
+ agent.parts.head.rotation.x += fT * 0.3;
142
+ agent.parts.leftArm.rotation.x = -fT * 1.2;
143
+ agent.parts.rightArm.rotation.x = -fT * 1.2;
144
+ agent.parts.leftForearm.rotation.x = -fT * 1.0;
145
+ agent.parts.rightForearm.rotation.x = -fT * 1.0;
146
+ }
147
+ if (agent.frustratedTimer < -2.5) {
148
+ agent.frustratedTimer = 8 + Math.random() * 10; // reset
149
+ }
150
+ } else {
151
+ agent.frustratedTimer = 0;
152
+ }
153
+
154
+ // Listening lean-forward — body leans toward screen when in listen mode
155
+ if (agent.isListening && agent.isSitting && !isWalking && !isSleeping) {
156
+ agent.parts.body.rotation.x += -0.08; // slight forward lean
157
+ agent.parts.head.rotation.x += -0.05; // looking up at screen
158
+ }
159
+
160
+ // Head nod when being talked to (another agent is visiting)
161
+ if (agent._listeningTo && !isWalking && !isSleeping) {
162
+ if (!agent._nodTimer) agent._nodTimer = 0;
163
+ agent._nodTimer += dt;
164
+ // Periodic nod: quick down-up every ~2s
165
+ var nodCycle = agent._nodTimer % 2.2;
166
+ if (nodCycle < 0.3) {
167
+ agent.parts.head.rotation.x += Math.sin(nodCycle / 0.3 * Math.PI) * 0.12;
168
+ }
169
+ } else {
170
+ agent._nodTimer = 0;
171
+ }
172
+
123
173
  // Idle gesture system — random gestures when sitting and idle
124
174
  if (!agent.idleGestureTimer) agent.idleGestureTimer = 5 + Math.random() * 10;
125
175
  if (agent.isSitting && agent.state === 'active' && !isWalking && !agent.isListening) {
@@ -205,6 +255,24 @@ export function updateAgent(agent, dt, time) {
205
255
  agent.parts.rightForearm.rotation.x *= 0.9;
206
256
  }
207
257
 
258
+ // Glance at nearby speaking agent — head turns slightly toward speaker
259
+ if (agent._glanceTarget && agent.isSitting && !isWalking && !isSleeping) {
260
+ if (!agent._glanceTimer) agent._glanceTimer = 0;
261
+ agent._glanceTimer += dt;
262
+ if (agent._glanceTimer < 2.5) {
263
+ var glanceT = Math.min(1, agent._glanceTimer / 0.4);
264
+ agent.parts.head.rotation.y = agent._glanceDirection * 0.25 * glanceT;
265
+ } else {
266
+ // Fade back
267
+ agent.parts.head.rotation.y *= 0.9;
268
+ if (Math.abs(agent.parts.head.rotation.y) < 0.01) {
269
+ agent._glanceTarget = null;
270
+ agent._glanceTimer = 0;
271
+ agent.parts.head.rotation.y = 0;
272
+ }
273
+ }
274
+ }
275
+
208
276
  // Idle breathing
209
277
  if (!isWalking && !isSleeping) {
210
278
  var breatheSpeed = S.conversationVelocity === -1 ? 1.2 : 2;