let-them-talk 3.6.1 → 3.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/CHANGELOG.md CHANGED
@@ -1,5 +1,51 @@
1
1
  # Changelog
2
2
 
3
+ ## [3.7.0] - 2026-03-16
4
+
5
+ ### Added — Agent Ecosystem (20 new tools, 52 total)
6
+
7
+ **Tier 1 — Critical Infrastructure:**
8
+ - **`get_briefing()`** — full project onboarding in one call: agents, tasks, decisions, KB, locked files, progress, project file tree
9
+ - **`lock_file(path)` / `unlock_file(path?)`** — exclusive file editing with auto-release on agent death
10
+ - **`log_decision(decision, reasoning?, topic?)` / `get_decisions(topic?)`** — persistent decision log, prevents re-debating
11
+ - **Agent recovery on rejoin** — `register()` returns active tasks, workspace keys, recent messages for returning agents
12
+
13
+ **Tier 2 — Quality of Life:**
14
+ - **`kb_write(key, content)` / `kb_read(key?)` / `kb_list()`** — shared team knowledge base (any agent reads/writes)
15
+ - **Event hooks** — auto-fires system messages on `agent_join`, `task_complete`, `all_tasks_done`, `dependency_met`
16
+ - **`update_progress(feature, percent, notes)` / `get_progress()`** — feature-level progress tracking with overall %
17
+ - **`get_compressed_history()`** — auto-compresses old messages into summary segments, keeps recent verbatim
18
+ - **`listen_group()` now blocks indefinitely** — no more timeout, agents never drop out
19
+
20
+ **Tier 3 — Advanced Collaboration:**
21
+ - **`call_vote(question, options)` / `cast_vote(vote_id, choice)` / `vote_status(vote_id?)`** — team voting with auto-resolve when all vote
22
+ - **`request_review(file, desc)` / `submit_review(review_id, status, feedback)`** — code review pipeline with approve/changes_requested
23
+ - **`declare_dependency(task_id, depends_on)` / `check_dependencies(task_id?)`** — task dependency tracking with auto-notify on resolve
24
+ - **`get_reputation(agent?)` / `suggest_task()`** — agent reputation tracking (auto-detects strengths), task suggestions based on skills
25
+ - **Auto-reputation tracking** — global hook tracks every action (messages, tasks, reviews, decisions, KB writes) without manual calls
26
+
27
+ ### Fixed
28
+ - **Monitor screens stay red** when agent stops listening — persistent color state instead of 300ms flash
29
+ - **"NOT LISTENING" warning** shown prominently on desk monitor canvas
30
+ - **Status color logic** — green = listening, red = active but not listening, yellow = sleeping, dim = dead
31
+
32
+ ## [3.6.2] - 2026-03-16
33
+
34
+ ### Added — Message Awareness System
35
+ - **Sender gets busy status** — `send_message` and `broadcast` tell you when recipients are working (not listening) so you know messages are queued
36
+ - **Pending message nudge** — every non-listen tool call checks for unread messages and tells the agent to call `listen_group()` soon
37
+ - **Message age tracking** — `listen_group` shows `age_seconds` per message and `delayed: true` flag for messages older than 30s
38
+ - **Agent status in batch** — `listen_group` returns `agents_status` map showing who is `listening` vs `working`
39
+ - **listen_group retry** — timeout now returns `retry: true` with explicit instruction to call again immediately
40
+ - **next_action field** — successful `listen_group` response tells agent to call `listen_group()` again after responding
41
+ - **Ctrl key removed from camera** — no longer moves camera down (Q/E only)
42
+
43
+ ### Added — 3D World: Campus Environment & Navigation
44
+ - **Campus environment** — new outdoor environment option with buildings, paths, green spaces
45
+ - **Navigation system** — pathfinding for agents to walk around obstacles instead of through walls
46
+ - **Door animations** — manager office door slides open when agents approach, closes when they leave
47
+ - **Roof visibility** — roof hides when camera is above ceiling height
48
+
3
49
  ## [3.6.1] - 2026-03-16
4
50
 
5
51
  ### Fixed
package/README.md CHANGED
@@ -86,7 +86,7 @@ Each terminal spawns its own MCP server process. All processes share a `.agent-b
86
86
 
87
87
  - **3D virtual office** — chibi characters at desks, spectator camera (WASD+mouse), 11 hairstyles, 6 outfits, gestures, furniture, TV dashboard
88
88
  - **Managed conversation mode** — structured turn-taking with floor control for 3+ agents, prevents broadcast storms
89
- - **32 MCP tools** — messaging, tasks, workflows, profiles, workspaces, branching, managed mode
89
+ - **52 MCP tools** — messaging, tasks, workflows, profiles, workspaces, branching, managed mode, briefing, file locking, decisions, KB, voting, reviews, dependencies, reputation
90
90
  - **8-tab dashboard** — 3D Hub (default), messages, tasks, workspaces, workflows, launch, stats, docs
91
91
  - **Group conversation mode** — free multi-agent collaboration with auto-broadcast and cooldown
92
92
  - **5 agent templates** — pair, team, review, debate, managed — with ready-to-paste prompts
@@ -175,7 +175,7 @@ The dashboard's default view is a **real-time 3D virtual office** (the "3D Hub")
175
175
 
176
176
  **Animations:** walk, sit, type, raise hand, sleep (ZZZ), wave, think, point, celebrate, stretch, idle gestures. Agents turn toward speakers during conversations.
177
177
 
178
- ## MCP Tools (32)
178
+ ## MCP Tools (52)
179
179
 
180
180
  <details>
181
181
  <summary><strong>Messaging (13 tools)</strong></summary>
@@ -249,6 +249,90 @@ The dashboard's default view is a **real-time 3D virtual office** (the "3D Hub")
249
249
 
250
250
  </details>
251
251
 
252
+ <details>
253
+ <summary><strong>Briefing & Recovery (1 tool)</strong></summary>
254
+
255
+ | Tool | Description |
256
+ |------|-------------|
257
+ | `get_briefing` | Full project onboarding — agents, tasks, decisions, KB, locks, progress, files |
258
+
259
+ </details>
260
+
261
+ <details>
262
+ <summary><strong>File Locking (2 tools)</strong></summary>
263
+
264
+ | Tool | Description |
265
+ |------|-------------|
266
+ | `lock_file` | Lock a file for exclusive editing. Auto-releases on death |
267
+ | `unlock_file` | Unlock a file or all your locked files |
268
+
269
+ </details>
270
+
271
+ <details>
272
+ <summary><strong>Decision Log (2 tools)</strong></summary>
273
+
274
+ | Tool | Description |
275
+ |------|-------------|
276
+ | `log_decision` | Log a team decision with reasoning and topic |
277
+ | `get_decisions` | Get all decisions, optionally filtered by topic |
278
+
279
+ </details>
280
+
281
+ <details>
282
+ <summary><strong>Knowledge Base (3 tools)</strong></summary>
283
+
284
+ | Tool | Description |
285
+ |------|-------------|
286
+ | `kb_write` | Write to shared team knowledge base |
287
+ | `kb_read` | Read KB entries (one or all) |
288
+ | `kb_list` | List all KB keys with metadata |
289
+
290
+ </details>
291
+
292
+ <details>
293
+ <summary><strong>Progress & Compression (3 tools)</strong></summary>
294
+
295
+ | Tool | Description |
296
+ |------|-------------|
297
+ | `update_progress` | Update feature-level completion percentage |
298
+ | `get_progress` | Get all feature progress with overall % |
299
+ | `get_compressed_history` | Compressed old messages + recent verbatim |
300
+
301
+ </details>
302
+
303
+ <details>
304
+ <summary><strong>Voting (3 tools)</strong></summary>
305
+
306
+ | Tool | Description |
307
+ |------|-------------|
308
+ | `call_vote` | Start a team vote with options |
309
+ | `cast_vote` | Cast your vote (auto-resolves when all vote) |
310
+ | `vote_status` | Check vote results |
311
+
312
+ </details>
313
+
314
+ <details>
315
+ <summary><strong>Code Review (2 tools)</strong></summary>
316
+
317
+ | Tool | Description |
318
+ |------|-------------|
319
+ | `request_review` | Request a code review from the team |
320
+ | `submit_review` | Approve or request changes with feedback |
321
+
322
+ </details>
323
+
324
+ <details>
325
+ <summary><strong>Dependencies & Reputation (4 tools)</strong></summary>
326
+
327
+ | Tool | Description |
328
+ |------|-------------|
329
+ | `declare_dependency` | Declare task dependency (auto-notifies on resolve) |
330
+ | `check_dependencies` | Check blocked/resolved dependencies |
331
+ | `get_reputation` | Agent leaderboard with strengths |
332
+ | `suggest_task` | Get next task suggestion based on your skills |
333
+
334
+ </details>
335
+
252
336
  ## CLI Reference
253
337
 
254
338
  ```bash
package/cli.js CHANGED
@@ -9,7 +9,7 @@ const command = process.argv[2];
9
9
 
10
10
  function printUsage() {
11
11
  console.log(`
12
- Let Them Talk — Agent Bridge v3.6.1
12
+ Let Them Talk — Agent Bridge v3.7.0
13
13
  MCP message broker for inter-agent communication
14
14
  Supports: Claude Code, Gemini CLI, Codex CLI, Ollama
15
15
 
package/dashboard.html CHANGED
@@ -2558,6 +2558,15 @@
2558
2558
  opacity: 0;
2559
2559
  transition: none;
2560
2560
  }
2561
+ .office3d-listen-lost {
2562
+ pointer-events: none;
2563
+ font-family: Inter, sans-serif;
2564
+ white-space: nowrap;
2565
+ }
2566
+ @keyframes office3d-pulse {
2567
+ 0%, 100% { opacity: 1; transform: scale(1); }
2568
+ 50% { opacity: 0.5; transform: scale(1.1); }
2569
+ }
2561
2570
 
2562
2571
  /* 3D task indicator */
2563
2572
  .office3d-task-indicator {
@@ -3300,6 +3309,7 @@
3300
3309
  <div class="office-toolbar">
3301
3310
  <label>Environment:</label>
3302
3311
  <select id="office-env-select" onchange="officeSetEnvironment(this.value)">
3312
+ <option value="campus" selected>Tech Campus</option>
3303
3313
  <option value="modern">Modern Office</option>
3304
3314
  <option value="startup">Startup Garage</option>
3305
3315
  </select>
@@ -6848,9 +6858,9 @@ function renderDocs() {
6848
6858
  '</div>' +
6849
6859
  '</div>' +
6850
6860
 
6851
- // All 32 Tools
6861
+ // All 52 Tools
6852
6862
  '<div class="docs-section">' +
6853
- '<h3>All 32 MCP Tools</h3>' +
6863
+ '<h3>All 52 MCP Tools</h3>' +
6854
6864
  '<h4>Core Messaging</h4>' +
6855
6865
  '<div class="docs-tool-grid">' +
6856
6866
  '<div class="docs-tool-item"><code>register(name, provider?)</code><div class="desc">Register your agent identity. Must be called first.</div></div>' +
package/office/agents.js CHANGED
@@ -5,6 +5,34 @@ import { resolveAppearance } from './appearance.js';
5
5
  import { buildHair } from './hair.js';
6
6
  import { buildFaceSprite } from './face.js';
7
7
  import { buildOutfit, removeOutfit } from './outfits.js';
8
+ import { getNavigationPath } from './navigation.js';
9
+
10
+ // Navigate agent using waypoint pathfinding (campus) or direct walk (other envs)
11
+ export function navigateTo(agent, tx, tz, callback) {
12
+ var path = getNavigationPath(agent.pos.x, agent.pos.z, tx, tz);
13
+ if (!path || path.length === 0) {
14
+ walkTo(agent, tx, tz, callback);
15
+ return;
16
+ }
17
+ // Queue all waypoints, put callback on the last one
18
+ agent.walkQueue = [];
19
+ for (var i = 1; i < path.length; i++) {
20
+ agent.walkQueue.push({ x: path[i].x, z: path[i].z, cb: null, triggerDoor: path[i].triggerDoor });
21
+ }
22
+ // Attach callback to last queued point (or first walk if only 1 point)
23
+ if (agent.walkQueue.length > 0) {
24
+ agent.walkQueue[agent.walkQueue.length - 1].cb = callback;
25
+ }
26
+ // Start walking to first point
27
+ var first = path[0];
28
+ walkTo(agent, first.x, first.z, first.triggerDoor ? function() { triggerManagerDoor(true); } : (path.length === 1 ? callback : null));
29
+ }
30
+
31
+ function triggerManagerDoor(open) {
32
+ if (S._managerDoor) {
33
+ S._managerDoorOpen = open ? 1 : 0;
34
+ }
35
+ }
8
36
 
9
37
  export function walkTo(agent, tx, tz, callback) {
10
38
  var dx = tx - agent.pos.x;
@@ -25,13 +53,39 @@ export function showBubble(agent, text) {
25
53
  agent.bubbleText = display;
26
54
  }
27
55
 
56
+ function getDeskPositions() {
57
+ return S._campusDeskPositions || DESK_POSITIONS;
58
+ }
59
+
28
60
  function assignDesk(agentName) {
61
+ var desks = getDeskPositions();
29
62
  var used = {};
30
63
  for (var n in S.agents3d) used[S.agents3d[n].deskIdx] = true;
31
- for (var i = 0; i < DESK_POSITIONS.length; i++) {
32
- if (!used[i]) return i;
64
+
65
+ // If campus mode, check if agent has "Manager" role or name — assign last desk (manager office)
66
+ if (S.currentEnv === 'campus' && desks.length > 0) {
67
+ var info = (window.cachedAgents || {})[agentName] || {};
68
+ var role = (info.role || '').toLowerCase();
69
+ var dname = (info.display_name || agentName).toLowerCase();
70
+ var regName = agentName.toLowerCase();
71
+ var isManager = role === 'manager' || role === 'project lead' || role === 'ceo' || role === 'director' ||
72
+ role.indexOf('project manager') >= 0 || role.indexOf('team lead') >= 0 ||
73
+ dname === 'manager' || regName === 'manager';
74
+ var managerIdx = desks.length - 1; // last desk is manager office
75
+ if (isManager && !used[managerIdx]) {
76
+ return managerIdx;
77
+ }
78
+ // Non-manager agents skip the manager desk
79
+ for (var i = 0; i < desks.length - 1; i++) {
80
+ if (!used[i]) return i;
81
+ }
82
+ return Object.keys(S.agents3d).length % (desks.length - 1);
83
+ }
84
+
85
+ for (var j = 0; j < desks.length; j++) {
86
+ if (!used[j]) return j;
33
87
  }
34
- return Object.keys(S.agents3d).length % DESK_POSITIONS.length;
88
+ return Object.keys(S.agents3d).length % desks.length;
35
89
  }
36
90
 
37
91
  function fetchTasks() {
@@ -75,13 +129,19 @@ function updateLabel(agent) {
75
129
  }
76
130
  }
77
131
 
78
- function updateDeskScreen(deskIdx, status) {
132
+ function updateDeskScreen(deskIdx, status, isListening) {
79
133
  var desk = S.deskMeshes[deskIdx];
80
134
  if (!desk) return;
81
- if (status === 'active') {
82
- desk.screenMat.emissive.setHex(0x58a6ff);
135
+ if (status === 'active' && isListening) {
136
+ // Listening — green screen
137
+ desk.screenMat.emissive.setHex(0x22c55e);
83
138
  desk.screenMat.emissiveIntensity = 0.5;
84
- desk.screenMat.color.setHex(0x58a6ff);
139
+ desk.screenMat.color.setHex(0x22c55e);
140
+ } else if (status === 'active' && !isListening) {
141
+ // Active but NOT listening — red screen
142
+ desk.screenMat.emissive.setHex(0xef4444);
143
+ desk.screenMat.emissiveIntensity = 0.6;
144
+ desk.screenMat.color.setHex(0xef4444);
85
145
  } else if (status === 'sleeping') {
86
146
  desk.screenMat.emissive.setHex(0x1a2744);
87
147
  desk.screenMat.emissiveIntensity = 0.15;
@@ -96,11 +156,14 @@ function updateDeskScreen(deskIdx, status) {
96
156
  function flashDeskScreen(deskIdx) {
97
157
  var desk = S.deskMeshes[deskIdx];
98
158
  if (!desk) return;
159
+ // Flash white briefly — the next syncAgents call (every 2s) will set the correct persistent color via updateDeskScreen
99
160
  desk.screenMat.emissive.setHex(0xffffff);
100
161
  desk.screenMat.emissiveIntensity = 1.5;
101
162
  setTimeout(function() {
102
- desk.screenMat.emissive.setHex(0x58a6ff);
103
- 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);
104
167
  }, 300);
105
168
  }
106
169
 
@@ -151,7 +214,8 @@ export function syncAgents() {
151
214
  var info = window.cachedAgents[name];
152
215
  if (!S.agents3d[name]) {
153
216
  var deskIdx = assignDesk(name);
154
- var deskPos = DESK_POSITIONS[deskIdx] || DESK_POSITIONS[0];
217
+ var allDesks = getDeskPositions();
218
+ var deskPos = allDesks[deskIdx] || allDesks[0];
155
219
  var parts = createCharacter(info.display_name || name, info.appearance || {});
156
220
  var agent = {
157
221
  name: name,
@@ -189,6 +253,7 @@ export function syncAgents() {
189
253
  celebrateTimer: 0,
190
254
  stretchTimer: 0,
191
255
  idleGestureTimer: 5 + Math.random() * 10,
256
+ listenLostTimer: 0,
192
257
  lastMessageTime: 0,
193
258
  monitorTimer: 0,
194
259
  location: 'desk', // 'desk', 'dressing_room', 'rest', 'walking'
@@ -203,10 +268,10 @@ export function syncAgents() {
203
268
  showBubble(agent, 'Checking in...');
204
269
  (function(a) {
205
270
  setTimeout(function() {
206
- walkTo(a, a.deskPos.x, a.deskPos.z + 0.7, function() {
271
+ navigateTo(a, a.deskPos.x, a.deskPos.z + 0.7, function() {
207
272
  a.registered = true;
208
273
  showBubble(a, 'Ready to work!');
209
- updateDeskScreen(a.deskIdx, a.state);
274
+ updateDeskScreen(a.deskIdx, a.state, a.isListening);
210
275
  });
211
276
  }, 800);
212
277
  })(agent);
@@ -227,8 +292,20 @@ export function syncAgents() {
227
292
  }
228
293
 
229
294
  existing.displayName = info.display_name || name;
295
+ var wasListening = existing.isListening;
230
296
  existing.isListening = !!(info.is_listening);
231
297
 
298
+ // Detect listen mode change — update screen color persistently
299
+ if (wasListening && !existing.isListening) {
300
+ // Left listen mode — flash then stay red until next sync sets updateDeskScreen
301
+ existing.listenLostTimer = 3;
302
+ flashDeskScreen(existing.deskIdx);
303
+ }
304
+ if (!wasListening && existing.isListening) {
305
+ // Entered listen mode — next updateDeskScreen will set green
306
+ existing.listenLostTimer = 0;
307
+ }
308
+
232
309
  var task = getAgentTask(name);
233
310
  if (task) {
234
311
  var prevTask = existing.currentTask;
@@ -248,7 +325,7 @@ export function syncAgents() {
248
325
  }
249
326
 
250
327
  updateLabel(existing);
251
- if (existing.registered) updateDeskScreen(existing.deskIdx, existing.state);
328
+ if (existing.registered) updateDeskScreen(existing.deskIdx, existing.state, existing.isListening);
252
329
  }
253
330
  }
254
331
 
@@ -309,7 +386,7 @@ export function processMessages() {
309
386
  stopX = tx + 1.5;
310
387
  stopZ = tz;
311
388
  }
312
- walkTo(f, stopX, stopZ, function() {
389
+ navigateTo(f, stopX, stopZ, function() {
313
390
  // Sender faces target
314
391
  var dx2 = t.pos.x - f.pos.x;
315
392
  var dz2 = t.pos.z - f.pos.z;
@@ -325,7 +402,7 @@ export function processMessages() {
325
402
 
326
403
  setTimeout(function() {
327
404
  // Sender walks back to desk
328
- walkTo(f, f.deskPos.x, f.deskPos.z + 0.7);
405
+ navigateTo(f, f.deskPos.x, f.deskPos.z + 0.7);
329
406
  // Target turns back to desk after a short delay
330
407
  setTimeout(function() {
331
408
  if (t._listeningTo === f.name) {
@@ -342,7 +419,7 @@ export function processMessages() {
342
419
  (function(f, txt) {
343
420
  setTimeout(function() {
344
421
  f.walkQueue = [];
345
- walkTo(f, 0, 0, function() {
422
+ navigateTo(f, 0, 0, function() {
346
423
  showBubble(f, txt);
347
424
  // All nearby agents turn toward the broadcaster
348
425
  for (var an in S.agents3d) {
@@ -355,7 +432,7 @@ export function processMessages() {
355
432
  a._listeningTo = f.name;
356
433
  }
357
434
  setTimeout(function() {
358
- walkTo(f, f.deskPos.x, f.deskPos.z + 0.7);
435
+ navigateTo(f, f.deskPos.x, f.deskPos.z + 0.7);
359
436
  // All listeners turn back
360
437
  setTimeout(function() {
361
438
  for (var an2 in S.agents3d) {
@@ -1,4 +1,6 @@
1
1
  import { S } from './state.js';
2
+ import * as THREE from 'three';
3
+ import { CSS2DObject } from 'three/addons/renderers/CSS2DRenderer.js';
2
4
  import { updateMonitorScreen, setMonitorDim } from './monitors.js';
3
5
 
4
6
  export function easeInOutQuad(t) {
@@ -35,6 +37,10 @@ export function updateAgent(agent, dt, time) {
35
37
  agent.walkProgress = 0;
36
38
  if (agent.walkQueue && agent.walkQueue.length > 0) {
37
39
  var next = agent.walkQueue.shift();
40
+ // Trigger door animation if waypoint requires it
41
+ if (next.triggerDoor && S._managerDoor) {
42
+ S._managerDoorOpen = 1;
43
+ }
38
44
  walkTo(agent, next.x, next.z, next.cb);
39
45
  } else if (cb) {
40
46
  cb();
@@ -148,7 +154,7 @@ export function updateAgent(agent, dt, time) {
148
154
  var sittingTarget = agent.isSitting ? 1 : 0;
149
155
  agent.sittingLerp += (sittingTarget - agent.sittingLerp) * Math.min(1, dt * 5);
150
156
 
151
- agent.parts.group.position.y = agent.sittingLerp * 0.06;
157
+ agent.parts.group.position.y = agent.sittingLerp * 0.14;
152
158
  var sitHip = -1.5 * agent.sittingLerp;
153
159
  agent.parts.leftLeg.rotation.x = agent.parts.leftLeg.rotation.x * (1 - agent.sittingLerp) + sitHip * agent.sittingLerp;
154
160
  agent.parts.rightLeg.rotation.x = agent.parts.rightLeg.rotation.x * (1 - agent.sittingLerp) + sitHip * agent.sittingLerp;
@@ -251,6 +257,34 @@ export function updateAgent(agent, dt, time) {
251
257
  agent.parts.head.rotation.z = Math.sin(time * 1.5) * 0.08;
252
258
  }
253
259
 
260
+ // Listen-lost alert (head shake + warning indicator)
261
+ if (agent.listenLostTimer > 0) {
262
+ agent.listenLostTimer -= dt;
263
+ // Head shake animation (rapid left-right)
264
+ var shakeT = agent.listenLostTimer;
265
+ if (shakeT > 1.5) {
266
+ agent.parts.head.rotation.y = Math.sin(time * 20) * 0.15;
267
+ }
268
+ // Show warning indicator above head
269
+ if (!agent._listenLostDiv) {
270
+ agent._listenLostDiv = document.createElement('div');
271
+ agent._listenLostDiv.className = 'office3d-listen-lost';
272
+ agent._listenLostDiv.innerHTML = '<span style="color:#ef4444;font-size:14px;font-weight:bold;text-shadow:0 0 6px rgba(239,68,68,0.6);animation:office3d-pulse 0.5s infinite">&#x26A0; NOT LISTENING</span>';
273
+ agent._listenLostLabel = new CSS2DObject(agent._listenLostDiv);
274
+ agent._listenLostLabel.position.set(0, 1.9, 0);
275
+ agent.parts.group.add(agent._listenLostLabel);
276
+ }
277
+ agent._listenLostDiv.style.display = 'block';
278
+ agent._listenLostDiv.style.opacity = String(Math.min(1, agent.listenLostTimer));
279
+ if (agent.listenLostTimer <= 0) {
280
+ agent._listenLostDiv.style.display = 'none';
281
+ agent.listenLostTimer = 0;
282
+ agent.parts.head.rotation.y = 0;
283
+ }
284
+ } else if (agent._listenLostDiv) {
285
+ agent._listenLostDiv.style.display = 'none';
286
+ }
287
+
254
288
  // Typing dots
255
289
  var showTyping = agent.state === 'active' && !agent.isListening && !isWalking && !isSleeping && agent.registered && agent.isSitting;
256
290
  agent.parts.typingLabel.visible = showTyping;