let-them-talk 3.7.0 → 3.9.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/dashboard.js CHANGED
@@ -284,17 +284,22 @@ function apiStats(query) {
284
284
  const history = readJsonl(filePath('history.jsonl', projectPath));
285
285
  const agents = readJson(filePath('agents.json', projectPath));
286
286
 
287
- // Per-agent stats
287
+ // Per-agent stats — only count messages from agents still in agents.json
288
288
  const perAgent = {};
289
- let totalMessages = history.length;
289
+ const knownAgentNames = new Set(Object.keys(agents));
290
+ knownAgentNames.add('__system__');
291
+ knownAgentNames.add('Dashboard');
292
+ let totalMessages = 0;
290
293
  const hourBuckets = new Array(24).fill(0);
291
294
 
292
295
  for (let i = 0; i < history.length; i++) {
293
296
  const m = history[i];
294
297
  const from = m.from || 'unknown';
298
+ if (!knownAgentNames.has(from)) continue; // skip removed agents
295
299
  if (!perAgent[from]) {
296
300
  perAgent[from] = { messages: 0, responseTimes: [], hours: new Array(24).fill(0) };
297
301
  }
302
+ totalMessages++;
298
303
  perAgent[from].messages++;
299
304
  const ts = new Date(m.timestamp);
300
305
  const hour = ts.getHours();
@@ -313,7 +318,7 @@ function apiStats(query) {
313
318
  }
314
319
  }
315
320
 
316
- // Build per-agent summary
321
+ // Build per-agent summary — only include agents currently in agents.json
317
322
  const agentStats = {};
318
323
  let busiestAgent = null;
319
324
  let busiestCount = 0;
@@ -368,4 +368,5 @@ function disposeAgent(agent) {
368
368
  if (agent.parts.zzzObjects) {
369
369
  agent.parts.zzzObjects.forEach(function(z) { if (z.div.parentElement) z.div.remove(); });
370
370
  }
371
+ if (agent._listenLostDiv && agent._listenLostDiv.parentElement) agent._listenLostDiv.remove();
371
372
  }
@@ -468,7 +468,7 @@ function buildLobby(marbleMat, chromeMat, goldMat, walnutMat) {
468
468
  tvFrame.castShadow = true;
469
469
  group.add(tvFrame);
470
470
  // Animated canvas
471
- var tvW = 480, tvH = 300;
471
+ var tvW = 960, tvH = 600;
472
472
  var tvCvs = document.createElement('canvas');
473
473
  tvCvs.width = tvW; tvCvs.height = tvH;
474
474
  var tvTex = new THREE.CanvasTexture(tvCvs);
@@ -425,94 +425,87 @@ export function updateTVScreen(time) {
425
425
  var cvs = tv.canvas, ctx = cvs.getContext('2d');
426
426
  var W = cvs.width, H = cvs.height;
427
427
 
428
- // Background gradient
428
+ // Clear
429
429
  ctx.fillStyle = '#0a0e1a';
430
430
  ctx.fillRect(0, 0, W, H);
431
431
 
432
432
  // Top bar
433
433
  ctx.fillStyle = '#111830';
434
- ctx.fillRect(0, 0, W, 24);
434
+ ctx.fillRect(0, 0, W, 40);
435
435
  ctx.fillStyle = '#58a6ff';
436
- ctx.font = 'bold 11px monospace';
437
- ctx.fillText('OFFICE DASHBOARD', 8, 16);
438
- // Clock
436
+ ctx.font = 'bold 20px monospace';
437
+ ctx.fillText('OFFICE DASHBOARD', 16, 27);
439
438
  var now = new Date();
440
439
  var timeStr = now.getHours().toString().padStart(2, '0') + ':' + now.getMinutes().toString().padStart(2, '0') + ':' + now.getSeconds().toString().padStart(2, '0');
441
440
  ctx.fillStyle = '#7ee787';
442
441
  ctx.textAlign = 'right';
443
- ctx.fillText(timeStr, W - 8, 16);
442
+ ctx.fillText(timeStr, W - 16, 27);
444
443
  ctx.textAlign = 'left';
445
444
 
446
- // Agent count + message stats
445
+ // Data
447
446
  var agents = window.cachedAgents || {};
448
447
  var history = window.cachedHistory || [];
449
448
  var agentNames = Object.keys(agents);
450
449
  var activeCount = 0, sleepCount = 0;
451
450
  agentNames.forEach(function(n) {
452
- var st = agents[n].status || 'dead';
453
- if (st === 'active') activeCount++;
454
- else if (st === 'sleeping') sleepCount++;
451
+ if (agents[n].status === 'active') activeCount++;
452
+ else if (agents[n].status === 'sleeping') sleepCount++;
455
453
  });
456
454
 
457
- ctx.font = '10px monospace';
458
- var y = 42;
459
-
460
- // Stats section
461
- ctx.fillStyle = '#546178';
462
- ctx.fillText('AGENTS', 8, y);
463
- ctx.fillStyle = '#d2a8ff';
464
- ctx.fillText(String(agentNames.length), 70, y);
465
- ctx.fillStyle = '#4ade80';
466
- ctx.fillText(activeCount + ' active', 90, y);
467
- ctx.fillStyle = '#facc15';
468
- ctx.fillText(sleepCount + ' idle', 160, y);
469
- y += 18;
470
-
471
- ctx.fillStyle = '#546178';
472
- ctx.fillText('MESSAGES', 8, y);
473
- ctx.fillStyle = '#79c0ff';
474
- ctx.fillText(String(history.length), 80, y);
475
- y += 18;
476
-
477
- // Separator line
478
- ctx.strokeStyle = '#1a2744';
479
- ctx.beginPath(); ctx.moveTo(8, y); ctx.lineTo(W - 8, y); ctx.stroke();
480
- y += 14;
481
-
482
- // Recent activity feed (last 5 messages)
483
- ctx.fillStyle = '#546178';
484
- ctx.font = '9px monospace';
485
- ctx.fillText('RECENT ACTIVITY', 8, y);
486
- y += 14;
487
-
488
- var recentMsgs = history.slice(-5);
455
+ var y = 68;
456
+ ctx.font = '16px monospace';
457
+
458
+ // Stats row 1
459
+ ctx.fillStyle = '#546178'; ctx.fillText('AGENTS', 16, y);
460
+ ctx.fillStyle = '#d2a8ff'; ctx.fillText(String(agentNames.length), 110, y);
461
+ ctx.fillStyle = '#4ade80'; ctx.fillText(activeCount + ' active', 140, y);
462
+ ctx.fillStyle = '#facc15'; ctx.fillText(sleepCount + ' idle', 280, y);
463
+ y += 26;
464
+
465
+ // Stats row 2
466
+ ctx.fillStyle = '#546178'; ctx.fillText('MESSAGES', 16, y);
467
+ ctx.fillStyle = '#79c0ff'; ctx.fillText(String(history.length), 130, y);
468
+ y += 26;
469
+
470
+ // Separator
471
+ ctx.strokeStyle = '#1a2744'; ctx.lineWidth = 1;
472
+ ctx.beginPath(); ctx.moveTo(16, y); ctx.lineTo(W - 16, y); ctx.stroke();
473
+ y += 20;
474
+
475
+ // Activity header
476
+ ctx.fillStyle = '#546178'; ctx.font = '14px monospace';
477
+ ctx.fillText('RECENT ACTIVITY', 16, y);
478
+ y += 22;
479
+
480
+ // Messages
481
+ ctx.font = '13px monospace';
482
+ var recentMsgs = history.slice(-7);
489
483
  for (var i = 0; i < recentMsgs.length; i++) {
484
+ if (y > H - 50) break;
490
485
  var msg = recentMsgs[i];
491
486
  var from = msg.from || '?';
492
- var to = msg.to || 'all';
493
- var snippet = (msg.content || msg.message || '').substring(0, 30);
494
- if ((msg.content || msg.message || '').length > 30) snippet += '..';
495
- ctx.fillStyle = '#7ee787';
496
- ctx.fillText(from, 8, y);
497
- ctx.fillStyle = '#546178';
498
- ctx.fillText(' > ', 8 + from.length * 5.5, y);
499
- ctx.fillStyle = '#d2a8ff';
500
- ctx.fillText(to, 8 + from.length * 5.5 + 18, y);
501
- y += 12;
502
- ctx.fillStyle = '#8892b0';
503
- ctx.fillText(' ' + snippet, 8, y);
504
- y += 14;
505
- if (y > H - 30) break;
487
+ var to2 = msg.to || 'all';
488
+ var content = msg.content || msg.message || '';
489
+ var maxLen = Math.floor((W - 40) / 7.5);
490
+ var snippet = content.length > maxLen ? content.substring(0, maxLen - 2) + '..' : content;
491
+
492
+ // From > To
493
+ ctx.fillStyle = '#7ee787'; ctx.fillText(from, 16, y);
494
+ var fromW = ctx.measureText(from).width;
495
+ ctx.fillStyle = '#546178'; ctx.fillText(' > ', 16 + fromW, y);
496
+ ctx.fillStyle = '#d2a8ff'; ctx.fillText(to2, 16 + fromW + ctx.measureText(' > ').width, y);
497
+ y += 18;
498
+ // Snippet
499
+ ctx.fillStyle = '#8892b0'; ctx.fillText(' ' + snippet, 16, y);
500
+ y += 22;
506
501
  }
507
502
  if (recentMsgs.length === 0) {
508
- ctx.fillStyle = '#3d4663';
509
- ctx.fillText(' Waiting for messages...', 8, y);
503
+ ctx.fillStyle = '#3d4663'; ctx.fillText(' Waiting for messages...', 16, y);
510
504
  }
511
505
 
512
- // Bottom ticker bar
506
+ // Bottom ticker
513
507
  ctx.fillStyle = '#111830';
514
- ctx.fillRect(0, H - 20, W, 20);
515
- // Scrolling ticker
508
+ ctx.fillRect(0, H - 32, W, 32);
516
509
  var tickerParts = [];
517
510
  agentNames.forEach(function(n) {
518
511
  var info = agents[n];
@@ -520,13 +513,13 @@ export function updateTVScreen(time) {
520
513
  tickerParts.push(st + ' ' + (info.display_name || n));
521
514
  });
522
515
  var tickerText = tickerParts.length > 0 ? tickerParts.join(' \u2022 ') + ' \u2022 ' : 'No agents online';
523
- tv.tickerOffset = (tv.tickerOffset + 1) % (tickerText.length * 6);
516
+ ctx.font = '15px monospace';
517
+ var charW = 9;
518
+ tv.tickerOffset = (tv.tickerOffset + 1.5) % (tickerText.length * charW);
524
519
  ctx.fillStyle = '#58a6ff';
525
- ctx.font = '10px monospace';
526
- // Draw twice for seamless loop
527
- var fullW = tickerText.length * 6;
528
- ctx.fillText(tickerText, -tv.tickerOffset, H - 6);
529
- ctx.fillText(tickerText, -tv.tickerOffset + fullW, H - 6);
520
+ var fullTW = tickerText.length * charW;
521
+ ctx.fillText(tickerText, -tv.tickerOffset, H - 10);
522
+ ctx.fillText(tickerText, -tv.tickerOffset + fullTW, H - 10);
530
523
 
531
524
  // Scanline overlay
532
525
  ctx.fillStyle = 'rgba(0,0,0,0.04)';
package/office/index.js CHANGED
@@ -8,6 +8,7 @@ import { updateAgent } from './animation.js';
8
8
  import { syncAgents, processMessages, walkTo, navigateTo, showBubble } from './agents.js';
9
9
  // Side-effect: registers window.officeGetAppearance
10
10
  import './appearance.js';
11
+ import { spawnPlayer, despawnPlayer, isPlayerMode, updatePlayer, savePlayerAppearance, getPlayerAppearance, getPlayer, invalidateColliders } from './player.js';
11
12
 
12
13
  // Expose createCharacter + resolveAppearance for the character designer (Phase 3)
13
14
  export { createCharacter } from './character.js';
@@ -226,6 +227,11 @@ function animate() {
226
227
  updateAgent(S.agents3d[name], dt, time);
227
228
  }
228
229
 
230
+ // Player avatar mode
231
+ if (isPlayerMode() && S.controls && S.controls.keys) {
232
+ updatePlayer(dt, time, S.controls.keys);
233
+ }
234
+
229
235
  // Hide roof when camera is above ceiling height
230
236
  if (S._roofGroup) {
231
237
  S._roofGroup.visible = S.camera.position.y < 6.5;
@@ -244,6 +250,12 @@ function animate() {
244
250
  if (Math.sqrt(adx * adx + adz * adz) < 3) { shouldOpen = true; break; }
245
251
  }
246
252
  }
253
+ // Also open for player
254
+ if (!shouldOpen && S._player) {
255
+ var pdx = S._player.pos.x - doorX;
256
+ var pdz = S._player.pos.z - doorZ;
257
+ if (Math.sqrt(pdx * pdx + pdz * pdz) < 3) shouldOpen = true;
258
+ }
247
259
  S._managerDoorOpen = shouldOpen ? 1 : 0;
248
260
  S._managerDoorLerp += (S._managerDoorOpen - S._managerDoorLerp) * Math.min(1, dt * 4);
249
261
  S._managerDoor.position.x = S._managerDoorLerp * 1.3; // slide open to the right
@@ -345,7 +357,15 @@ window.office3dSetEnvironment = function(env) {
345
357
  });
346
358
  }
347
359
  S.agents3d = {};
360
+ S._tvScreen = null;
361
+ S._roofGroup = null;
362
+ S._managerDoor = null;
363
+ S._managerDoorOpen = 0;
364
+ S._managerDoorLerp = 0;
365
+ S._managerOfficePos = null;
366
+ S._campusDeskPositions = null;
348
367
  S.lastProcessedMsg = 0;
368
+ invalidateColliders();
349
369
  buildEnvironment();
350
370
  // syncAgents will recreate all agents with correct desk assignments
351
371
  syncAgents();
@@ -357,6 +377,36 @@ window.office3dSetCamSpeed = function(speed) {
357
377
  if (S.controls) S.controls.moveSpeed = speed;
358
378
  };
359
379
 
380
+ // Player avatar API
381
+ window.office3dEnterWorld = function() {
382
+ spawnPlayer();
383
+ };
384
+ window.office3dExitWorld = function() {
385
+ despawnPlayer();
386
+ };
387
+ window.office3dIsPlayerMode = function() {
388
+ return isPlayerMode();
389
+ };
390
+ window.office3dSavePlayerAppearance = function(app) {
391
+ savePlayerAppearance(app);
392
+ };
393
+ window.office3dGetPlayerAppearance = function() {
394
+ return getPlayerAppearance();
395
+ };
396
+ window.office3dRebuildPlayer = function(appearance) {
397
+ if (!S._player) return;
398
+ savePlayerAppearance(appearance);
399
+ // Rebuild: despawn and respawn with new appearance
400
+ var pos = { x: S._player.pos.x, z: S._player.pos.z };
401
+ var facing = S._player.facing;
402
+ despawnPlayer();
403
+ var p = spawnPlayer();
404
+ p.pos.x = pos.x;
405
+ p.pos.z = pos.z;
406
+ p.facing = facing;
407
+ p.parts.group.position.set(pos.x, 0, pos.z);
408
+ };
409
+
360
410
  // Handle visibility change for 3D mode
361
411
  document.addEventListener('visibilitychange', function() {
362
412
  if (document.hidden && S.running) {
@@ -3,7 +3,7 @@ import { S } from './state.js';
3
3
 
4
4
  export function updateMonitorScreen(deskIdx, agentName, time) {
5
5
  var desk = S.deskMeshes[deskIdx];
6
- if (!desk) return;
6
+ if (!desk || !desk.screen) return;
7
7
  var W = 256, H = 160;
8
8
  if (!S.monitorCanvases[deskIdx]) {
9
9
  var cvs = document.createElement('canvas');
@@ -109,7 +109,7 @@ export function updateMonitorScreen(deskIdx, agentName, time) {
109
109
 
110
110
  export function setMonitorDim(deskIdx) {
111
111
  var desk = S.deskMeshes[deskIdx];
112
- if (!desk) return;
112
+ if (!desk || !desk.screen) return;
113
113
  if (S.monitorCanvases[deskIdx]) {
114
114
  S.monitorCanvases[deskIdx].texture.dispose();
115
115
  if (desk.screen.material !== desk.screenMat) desk.screen.material.dispose();