let-them-talk 3.6.2 → 3.8.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;
package/office/agents.js CHANGED
@@ -156,11 +156,14 @@ function updateDeskScreen(deskIdx, status, isListening) {
156
156
  function flashDeskScreen(deskIdx) {
157
157
  var desk = S.deskMeshes[deskIdx];
158
158
  if (!desk) return;
159
+ // Flash white briefly — the next syncAgents call (every 2s) will set the correct persistent color via updateDeskScreen
159
160
  desk.screenMat.emissive.setHex(0xffffff);
160
161
  desk.screenMat.emissiveIntensity = 1.5;
161
162
  setTimeout(function() {
162
- desk.screenMat.emissive.setHex(0x58a6ff);
163
- desk.screenMat.emissiveIntensity = 0.5;
163
+ // Force immediate red until next sync corrects it
164
+ desk.screenMat.emissive.setHex(0xef4444);
165
+ desk.screenMat.emissiveIntensity = 0.6;
166
+ desk.screenMat.color.setHex(0xef4444);
164
167
  }, 300);
165
168
  }
166
169
 
@@ -292,14 +295,14 @@ export function syncAgents() {
292
295
  var wasListening = existing.isListening;
293
296
  existing.isListening = !!(info.is_listening);
294
297
 
295
- // Detect listen mode change
298
+ // Detect listen mode change — update screen color persistently
296
299
  if (wasListening && !existing.isListening) {
297
- // Left listen mode — flash alert
300
+ // Left listen mode — flash then stay red until next sync sets updateDeskScreen
298
301
  existing.listenLostTimer = 3;
299
302
  flashDeskScreen(existing.deskIdx);
300
303
  }
301
304
  if (!wasListening && existing.isListening) {
302
- // Entered listen mode
305
+ // Entered listen mode — next updateDeskScreen will set green
303
306
  existing.listenLostTimer = 0;
304
307
  }
305
308
 
@@ -154,7 +154,7 @@ export function updateAgent(agent, dt, time) {
154
154
  var sittingTarget = agent.isSitting ? 1 : 0;
155
155
  agent.sittingLerp += (sittingTarget - agent.sittingLerp) * Math.min(1, dt * 5);
156
156
 
157
- agent.parts.group.position.y = agent.sittingLerp * 0.06;
157
+ agent.parts.group.position.y = agent.sittingLerp * 0.14;
158
158
  var sitHip = -1.5 * agent.sittingLerp;
159
159
  agent.parts.leftLeg.rotation.x = agent.parts.leftLeg.rotation.x * (1 - agent.sittingLerp) + sitHip * agent.sittingLerp;
160
160
  agent.parts.rightLeg.rotation.x = agent.parts.rightLeg.rotation.x * (1 - agent.sittingLerp) + sitHip * agent.sittingLerp;
@@ -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
  }
@@ -446,23 +446,45 @@ function buildLobby(marbleMat, chromeMat, goldMat, walnutMat) {
446
446
  kb.position.set(-0.8, 1.2, lz - 0.4);
447
447
  group.add(kb);
448
448
 
449
- // --- Company logo wall (behind reception) ---
449
+ // --- Feature wall with big TV monitor (behind reception) ---
450
450
  var logoWallMat = new THREE.MeshStandardMaterial({ color: 0x15181f, roughness: 0.7 });
451
- var logoWall = new THREE.Mesh(new THREE.BoxGeometry(6, 3, 0.15), logoWallMat);
452
- logoWall.position.set(0, 2, lz + 1.5);
451
+ var logoWall = new THREE.Mesh(new THREE.BoxGeometry(6, 4, 0.15), logoWallMat);
452
+ logoWall.position.set(0, 2.5, lz + 1.5);
453
453
  logoWall.castShadow = true;
454
454
  group.add(logoWall);
455
- // Backlit logo text
455
+
456
+ // "LET THEM TALK" logo text above the TV
456
457
  var logoDiv = document.createElement('div');
457
458
  logoDiv.textContent = 'LET THEM TALK';
458
- logoDiv.style.cssText = 'color:#ffffff;font-size:16px;font-weight:900;font-family:Inter,sans-serif;letter-spacing:6px;text-shadow:0 0 20px rgba(88,166,255,0.6),0 0 40px rgba(88,166,255,0.3);';
459
+ logoDiv.style.cssText = 'color:#ffffff;font-size:14px;font-weight:900;font-family:Inter,sans-serif;letter-spacing:6px;text-shadow:0 0 20px rgba(88,166,255,0.6),0 0 40px rgba(88,166,255,0.3);';
459
460
  var logoLabel = new CSS2DObject(logoDiv);
460
- logoLabel.position.set(0, 2.8, lz + 1.6);
461
+ logoLabel.position.set(0, 4.3, lz + 1.6);
461
462
  group.add(logoLabel);
462
- // Accent light behind logo wall
463
- var logoLight = new THREE.RectAreaLight ? null : null; // not available without addon
463
+
464
+ // Big TV screen (dynamic canvas dashboard) facing INTO the room (-z)
465
+ var tvFrame = new THREE.Mesh(new THREE.BoxGeometry(5, 2.8, 0.06),
466
+ new THREE.MeshStandardMaterial({ color: 0x0a0a0a, roughness: 0.2 }));
467
+ tvFrame.position.set(0, 2.2, lz + 1.4);
468
+ tvFrame.castShadow = true;
469
+ group.add(tvFrame);
470
+ // Animated canvas
471
+ var tvW = 960, tvH = 600;
472
+ var tvCvs = document.createElement('canvas');
473
+ tvCvs.width = tvW; tvCvs.height = tvH;
474
+ var tvTex = new THREE.CanvasTexture(tvCvs);
475
+ tvTex.minFilter = THREE.LinearFilter;
476
+ var tvScreenMat = new THREE.MeshStandardMaterial({
477
+ map: tvTex, emissive: 0x58a6ff, emissiveIntensity: 0.2, roughness: 0.1
478
+ });
479
+ var tvScreen = new THREE.Mesh(new THREE.PlaneGeometry(4.6, 2.5), tvScreenMat);
480
+ tvScreen.position.set(0, 2.2, lz + 1.36);
481
+ tvScreen.rotation.y = Math.PI;
482
+ group.add(tvScreen);
483
+ S._tvScreen = { canvas: tvCvs, texture: tvTex, tickerOffset: 0 };
484
+
485
+ // Accent light on the wall
464
486
  var logoSpot = new THREE.PointLight(0x58a6ff, 0.5, 6);
465
- logoSpot.position.set(0, 3.5, lz + 1);
487
+ logoSpot.position.set(0, 4.2, lz + 1);
466
488
  group.add(logoSpot);
467
489
 
468
490
  // --- Water feature (low rectangular pool) ---
@@ -1241,16 +1263,16 @@ function buildRecCenter(x, z, walnutMat, chromeMat, carpetMat) {
1241
1263
  S.furnitureGroup.add(bot);
1242
1264
  });
1243
1265
 
1244
- // Big TV on the back
1245
- var tvMat = new THREE.MeshStandardMaterial({ color: 0x0a0a0a, roughness: 0.2 });
1246
- var tv = new THREE.Mesh(new THREE.BoxGeometry(3, 1.8, 0.08), tvMat);
1247
- tv.position.set(x, 2.5, z - 3.8);
1248
- tv.castShadow = true;
1249
- S.furnitureGroup.add(tv);
1250
- var tvScreen = new THREE.Mesh(new THREE.PlaneGeometry(2.8, 1.6),
1251
- new THREE.MeshStandardMaterial({ color: 0x1a2a4a, emissive: 0x1a2a4a, emissiveIntensity: 0.3, roughness: 0.1 }));
1252
- tvScreen.position.set(x, 2.5, z - 3.75);
1253
- S.furnitureGroup.add(tvScreen);
1266
+ // Static decorative TV (smaller, no dashboard — main TV is at reception)
1267
+ var tvMat2 = new THREE.MeshStandardMaterial({ color: 0x0a0a0a, roughness: 0.2 });
1268
+ var tvBody = new THREE.Mesh(new THREE.BoxGeometry(2.5, 1.5, 0.08), tvMat2);
1269
+ tvBody.position.set(x, 2.3, z - 3.8);
1270
+ tvBody.castShadow = true;
1271
+ S.furnitureGroup.add(tvBody);
1272
+ var tvScr = new THREE.Mesh(new THREE.PlaneGeometry(2.3, 1.3),
1273
+ new THREE.MeshStandardMaterial({ color: 0x0a1520, emissive: 0x22c55e, emissiveIntensity: 0.15, roughness: 0.1 }));
1274
+ tvScr.position.set(x, 2.3, z - 3.75);
1275
+ S.furnitureGroup.add(tvScr);
1254
1276
 
1255
1277
  // "REC ZONE" sign
1256
1278
  var signDiv = document.createElement('div');
@@ -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');
@@ -37,7 +37,16 @@ export function updateMonitorScreen(deskIdx, agentName, time) {
37
37
  var agentInfo = (window.cachedAgents || {})[agentName] || {};
38
38
  var lines = [];
39
39
 
40
- var statusColor = agentInfo.status === 'active' ? '#28c840' : '#ffbd2e';
40
+ // Prominent warning when agent is NOT listening
41
+ if (agentInfo.status === 'active' && !agentInfo.is_listening) {
42
+ ctx.fillStyle = '#1a0808';
43
+ ctx.fillRect(0, 14, W, 14);
44
+ ctx.fillStyle = '#ef4444';
45
+ ctx.font = 'bold 10px monospace';
46
+ ctx.fillText('\u26A0 NOT LISTENING', 6, 25);
47
+ }
48
+
49
+ var statusColor = agentInfo.is_listening ? '#28c840' : agentInfo.status === 'active' ? '#ef4444' : '#ffbd2e';
41
50
  lines.push({ color: '#546178', text: '$ agent status' });
42
51
  lines.push({ color: statusColor, text: ' ' + (agentInfo.status || 'unknown').toUpperCase() + (agentInfo.is_listening ? ' (listening)' : ' (working)') });
43
52
  lines.push({ color: '#546178', text: '' });
@@ -100,7 +109,7 @@ export function updateMonitorScreen(deskIdx, agentName, time) {
100
109
 
101
110
  export function setMonitorDim(deskIdx) {
102
111
  var desk = S.deskMeshes[deskIdx];
103
- if (!desk) return;
112
+ if (!desk || !desk.screen) return;
104
113
  if (S.monitorCanvases[deskIdx]) {
105
114
  S.monitorCanvases[deskIdx].texture.dispose();
106
115
  if (desk.screen.material !== desk.screenMat) desk.screen.material.dispose();