let-them-talk 5.3.0 → 5.4.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 (166) hide show
  1. package/CHANGELOG.md +3 -1
  2. package/README.md +158 -592
  3. package/SECURITY.md +3 -3
  4. package/USAGE.md +151 -0
  5. package/agent-contracts.js +447 -0
  6. package/api-agents.js +760 -0
  7. package/autonomy/decision-v2.js +380 -0
  8. package/autonomy/watchdog-policy.js +572 -0
  9. package/cli.js +454 -298
  10. package/conversation-templates/autonomous-feature.json +83 -22
  11. package/conversation-templates/code-review.json +69 -21
  12. package/conversation-templates/debug-squad.json +69 -21
  13. package/conversation-templates/feature-build.json +69 -21
  14. package/conversation-templates/research-write.json +69 -21
  15. package/dashboard.html +3148 -174
  16. package/dashboard.js +823 -786
  17. package/data-dir.js +58 -0
  18. package/docs/architecture/branch-semantics.md +157 -0
  19. package/docs/architecture/canonical-event-schema.md +88 -0
  20. package/docs/architecture/markdown-workspace.md +183 -0
  21. package/docs/architecture/runtime-contract.md +459 -0
  22. package/docs/architecture/runtime-migration-hardening.md +64 -0
  23. package/events/hooks.js +154 -0
  24. package/events/log.js +457 -0
  25. package/events/replay.js +33 -0
  26. package/events/schema.js +432 -0
  27. package/managed-team-integration.js +261 -0
  28. package/office/agents.js +704 -597
  29. package/office/animation.js +1 -1
  30. package/office/assets/arcade-cabinet.js +141 -0
  31. package/office/assets/archway.js +77 -0
  32. package/office/assets/bar-counter.js +91 -0
  33. package/office/assets/bar-stool.js +71 -0
  34. package/office/assets/beanbag.js +64 -0
  35. package/office/assets/bench.js +99 -0
  36. package/office/assets/bollard.js +87 -0
  37. package/office/assets/cactus.js +100 -0
  38. package/office/assets/carpet-tile.js +46 -0
  39. package/office/assets/chair.js +123 -0
  40. package/office/assets/chandelier.js +107 -0
  41. package/office/assets/coffee-machine.js +95 -0
  42. package/office/assets/coffee-table.js +81 -0
  43. package/office/assets/column.js +95 -0
  44. package/office/assets/desk-lamp.js +102 -0
  45. package/office/assets/desk.js +76 -0
  46. package/office/assets/dining-table.js +105 -0
  47. package/office/assets/door.js +70 -0
  48. package/office/assets/dual-monitor.js +72 -0
  49. package/office/assets/fence.js +76 -0
  50. package/office/assets/filing-cabinet.js +111 -0
  51. package/office/assets/floor-lamp.js +69 -0
  52. package/office/assets/floor-tile.js +54 -0
  53. package/office/assets/flower-pot.js +76 -0
  54. package/office/assets/foosball.js +95 -0
  55. package/office/assets/fridge.js +99 -0
  56. package/office/assets/gaming-chair.js +154 -0
  57. package/office/assets/gaming-desk.js +105 -0
  58. package/office/assets/glass-door.js +72 -0
  59. package/office/assets/glass-wall.js +64 -0
  60. package/office/assets/half-wall.js +49 -0
  61. package/office/assets/hanging-plant.js +112 -0
  62. package/office/assets/index.js +151 -0
  63. package/office/assets/indoor-tree.js +90 -0
  64. package/office/assets/l-sofa.js +153 -0
  65. package/office/assets/marble-floor.js +64 -0
  66. package/office/assets/materials.js +40 -0
  67. package/office/assets/meeting-table.js +88 -0
  68. package/office/assets/microwave.js +94 -0
  69. package/office/assets/monitor.js +67 -0
  70. package/office/assets/neon-strip.js +73 -0
  71. package/office/assets/painting.js +84 -0
  72. package/office/assets/palm-tree.js +108 -0
  73. package/office/assets/pc-tower.js +91 -0
  74. package/office/assets/pendant-light.js +67 -0
  75. package/office/assets/ping-pong.js +114 -0
  76. package/office/assets/plant.js +72 -0
  77. package/office/assets/planter-box.js +95 -0
  78. package/office/assets/pool-table.js +94 -0
  79. package/office/assets/printer.js +113 -0
  80. package/office/assets/reception-desk.js +133 -0
  81. package/office/assets/rug.js +78 -0
  82. package/office/assets/sculpture.js +85 -0
  83. package/office/assets/server-rack.js +98 -0
  84. package/office/assets/sink.js +109 -0
  85. package/office/assets/sofa.js +106 -0
  86. package/office/assets/speaker.js +83 -0
  87. package/office/assets/spotlight.js +83 -0
  88. package/office/assets/street-lamp.js +97 -0
  89. package/office/assets/trash-can.js +83 -0
  90. package/office/assets/treadmill.js +126 -0
  91. package/office/assets/trophy.js +89 -0
  92. package/office/assets/tv-screen.js +79 -0
  93. package/office/assets/vase.js +84 -0
  94. package/office/assets/wall-clock.js +84 -0
  95. package/office/assets/wall.js +53 -0
  96. package/office/assets/water-cooler.js +146 -0
  97. package/office/assets/whiteboard.js +115 -0
  98. package/office/assets.js +3 -431
  99. package/office/builder.js +791 -355
  100. package/office/campus-env.js +1012 -1119
  101. package/office/environment.js +2 -0
  102. package/office/gallery.js +997 -0
  103. package/office/index.js +165 -61
  104. package/office/navigation.js +173 -152
  105. package/office/player.js +178 -68
  106. package/office/robot-character.js +272 -0
  107. package/office/spectator-camera.js +33 -10
  108. package/office/state.js +2 -0
  109. package/office/world-save.js +35 -4
  110. package/package.json +57 -3
  111. package/providers/comfyui.js +383 -0
  112. package/providers/dalle.js +79 -0
  113. package/providers/gemini.js +181 -0
  114. package/providers/ollama.js +184 -0
  115. package/providers/replicate.js +115 -0
  116. package/providers/zai.js +183 -0
  117. package/runtime-descriptor.js +270 -0
  118. package/scripts/check-agent-contract-advisory.js +132 -0
  119. package/scripts/check-api-agent-parity.js +277 -0
  120. package/scripts/check-autonomy-v2-decision.js +207 -0
  121. package/scripts/check-autonomy-v2-execution.js +588 -0
  122. package/scripts/check-autonomy-v2-watchdog.js +224 -0
  123. package/scripts/check-branch-fork-snapshot.js +337 -0
  124. package/scripts/check-branch-isolation.js +787 -0
  125. package/scripts/check-branch-semantics.js +139 -0
  126. package/scripts/check-dashboard-control-plane.js +1304 -0
  127. package/scripts/check-docs-onboarding.js +490 -0
  128. package/scripts/check-event-schema.js +276 -0
  129. package/scripts/check-evidence-completion.js +239 -0
  130. package/scripts/check-invariants.js +992 -0
  131. package/scripts/check-lifecycle-hooks.js +525 -0
  132. package/scripts/check-managed-team-integration.js +166 -0
  133. package/scripts/check-markdown-workspace-export.js +548 -0
  134. package/scripts/check-markdown-workspace-safety.js +347 -0
  135. package/scripts/check-markdown-workspace.js +136 -0
  136. package/scripts/check-message-replay.js +429 -0
  137. package/scripts/check-migration-hardening.js +300 -0
  138. package/scripts/check-performance-indexing.js +272 -0
  139. package/scripts/check-provider-capabilities.js +316 -0
  140. package/scripts/check-runtime-contract.js +109 -0
  141. package/scripts/check-session-aware-context.js +172 -0
  142. package/scripts/check-session-lifecycle.js +210 -0
  143. package/scripts/export-markdown-workspace.js +84 -0
  144. package/scripts/fixtures/message-replay/clean.jsonl +2 -0
  145. package/scripts/fixtures/message-replay/corrupt-correction-payload.jsonl +1 -0
  146. package/scripts/fixtures/message-replay/corrupt-jsonl.jsonl +1 -0
  147. package/scripts/fixtures/message-replay/corrupt-payload.jsonl +1 -0
  148. package/scripts/fixtures/message-replay/out-of-order.jsonl +2 -0
  149. package/scripts/migrate-legacy-to-canonical.js +201 -0
  150. package/scripts/run-verification-suite.js +242 -0
  151. package/scripts/sync-packaged-docs.js +69 -0
  152. package/server.js +9546 -7216
  153. package/state/agents.js +161 -0
  154. package/state/canonical.js +3068 -0
  155. package/state/dashboard-queries.js +441 -0
  156. package/state/evidence.js +56 -0
  157. package/state/io.js +69 -0
  158. package/state/markdown-workspace.js +951 -0
  159. package/state/messages.js +669 -0
  160. package/state/sessions.js +683 -0
  161. package/state/tasks-workflows.js +92 -0
  162. package/templates/debate.json +2 -2
  163. package/templates/managed.json +4 -4
  164. package/templates/pair.json +2 -2
  165. package/templates/review.json +2 -2
  166. package/templates/team.json +3 -3
package/office/index.js CHANGED
@@ -6,6 +6,8 @@ import { initScene } from './scene.js';
6
6
  import { buildEnvironment, updateTVScreen } from './environment.js';
7
7
  import { updateAgent } from './animation.js';
8
8
  import { syncAgents, processMessages, walkTo, navigateTo, showBubble } from './agents.js';
9
+ import { tickGallery, updateGalleryScreens } from './gallery.js';
10
+ import { updateRobotAnimation } from './robot-character.js';
9
11
  // Side-effect: registers window.officeGetAppearance
10
12
  import './appearance.js';
11
13
  import { spawnPlayer, despawnPlayer, isPlayerMode, updatePlayer, savePlayerAppearance, getPlayerAppearance, getPlayer, invalidateColliders } from './player.js';
@@ -26,8 +28,13 @@ function getCityMods() {
26
28
  }).catch(function(e) { console.warn('City modules failed:', e); });
27
29
  return _cityMods;
28
30
  }
29
- function isDriving() { return _cityMods && _cityMods.vehicle && _cityMods.vehicle.isDriving(); }
30
- function isConnected() { return false; }
31
+ function isDriving() { return _cityMods && _cityMods.vehicle && _cityMods.vehicle.isDriving(); }
32
+ function isConnected() { return false; }
33
+
34
+ function scopedOfficeApiUrl(path, options) {
35
+ if (typeof window.scopedApiUrl === 'function') return window.scopedApiUrl(path, null, options);
36
+ return path;
37
+ }
31
38
 
32
39
  // Expose createCharacter + resolveAppearance for the character designer (Phase 3)
33
40
  export { createCharacter } from './character.js';
@@ -312,29 +319,29 @@ function executeCommand(agentName, action) {
312
319
  });
313
320
  break;
314
321
 
315
- case 'send_message':
316
- showInputOverlay('Send message to ' + agentName + ':', 'Type your message...', function(msg) {
317
- if (msg && msg.trim()) {
318
- showBubble(agent, 'Message incoming...');
319
- fetch('/api/inject' + (window.activeProject ? '?project=' + encodeURIComponent(window.activeProject) : ''), {
320
- method: 'POST',
321
- headers: { 'Content-Type': 'application/json', 'X-LTT-Request': '1' },
322
- body: JSON.stringify({ to: agentName, content: msg.trim() })
323
- }).then(function() { showBubble(agent, 'Got it!'); });
324
- }
322
+ case 'send_message':
323
+ showInputOverlay('Send message to ' + agentName + ':', 'Type your message...', function(msg) {
324
+ if (msg && msg.trim()) {
325
+ showBubble(agent, 'Message incoming...');
326
+ fetch(scopedOfficeApiUrl('/api/inject'), {
327
+ method: 'POST',
328
+ headers: { 'Content-Type': 'application/json', 'X-LTT-Request': '1' },
329
+ body: JSON.stringify({ to: agentName, content: msg.trim() })
330
+ }).then(function() { showBubble(agent, 'Got it!'); });
331
+ }
325
332
  });
326
333
  break;
327
334
 
328
- case 'assign_task':
329
- showInputOverlay('New task for ' + agentName + ':', 'Task title...', function(title) {
330
- if (title && title.trim()) {
331
- showBubble(agent, 'New task assigned!');
332
- fetch('/api/tasks' + (window.activeProject ? '?project=' + encodeURIComponent(window.activeProject) : ''), {
333
- method: 'POST',
334
- headers: { 'Content-Type': 'application/json', 'X-LTT-Request': '1' },
335
- body: JSON.stringify({ title: title.trim(), assignee: agentName, status: 'pending' })
336
- });
337
- }
335
+ case 'assign_task':
336
+ showInputOverlay('New task for ' + agentName + ':', 'Task title...', function(title) {
337
+ if (title && title.trim()) {
338
+ showBubble(agent, 'New task assigned!');
339
+ fetch(scopedOfficeApiUrl('/api/tasks'), {
340
+ method: 'POST',
341
+ headers: { 'Content-Type': 'application/json', 'X-LTT-Request': '1' },
342
+ body: JSON.stringify({ title: title.trim(), assignee: agentName, status: 'pending' })
343
+ });
344
+ }
338
345
  });
339
346
  break;
340
347
 
@@ -348,13 +355,13 @@ function executeCommand(agentName, action) {
348
355
  }
349
356
  break;
350
357
 
351
- case 'nudge':
352
- showBubble(agent, 'Hey! Wake up!');
353
- fetch('/api/inject' + (window.activeProject ? '?project=' + encodeURIComponent(window.activeProject) : ''), {
354
- method: 'POST',
355
- headers: { 'Content-Type': 'application/json', 'X-LTT-Request': '1' },
356
- body: JSON.stringify({ to: agentName, content: 'Hey ' + agentName + ', the user is waiting for you. Please check for new messages and continue your work.' })
357
- });
358
+ case 'nudge':
359
+ showBubble(agent, 'Hey! Wake up!');
360
+ fetch(scopedOfficeApiUrl('/api/inject'), {
361
+ method: 'POST',
362
+ headers: { 'Content-Type': 'application/json', 'X-LTT-Request': '1' },
363
+ body: JSON.stringify({ to: agentName, content: 'Hey ' + agentName + ', the user is waiting for you. Please check for new messages and continue your work.' })
364
+ });
358
365
  break;
359
366
 
360
367
  case 'edit_profile':
@@ -398,9 +405,15 @@ function animate() {
398
405
  var time = S.clock.getElapsedTime();
399
406
 
400
407
  for (var name in S.agents3d) {
401
- updateAgent(S.agents3d[name], dt, time);
408
+ var ag = S.agents3d[name];
409
+ if (!ag) continue;
410
+ updateAgent(ag, dt, time);
411
+ if (ag.isApiAgent && S.agents3d[name]) updateRobotAnimation(ag, dt, time);
402
412
  }
403
413
 
414
+ // Gallery slideshow tick
415
+ if (S.currentEnv === 'campus') tickGallery(dt);
416
+
404
417
  // Player avatar mode — skip when driving (vehicle takes over)
405
418
  if (isPlayerMode() && S.controls && S.controls.keys && !isDriving()) {
406
419
  updatePlayer(dt, time, S.controls.keys);
@@ -757,6 +770,13 @@ window.office3dStart = function() {
757
770
  syncAgents();
758
771
  processMessages();
759
772
  updateTVScreen(S.clock.getElapsedTime());
773
+ // Fetch media for gallery screens
774
+ if (S.galleryScreens) {
775
+ var pq = window.currentProjectPath ? '?project=' + encodeURIComponent(window.currentProjectPath) : '';
776
+ fetch('/api/media' + pq).then(function(r) { return r.json(); }).then(function(media) {
777
+ if (Array.isArray(media) && media.length > 0) updateGalleryScreens(media);
778
+ }).catch(function() {});
779
+ }
760
780
  }
761
781
  }, 2000);
762
782
  };
@@ -800,9 +820,19 @@ window.office3dSetEnvironment = function(env) {
800
820
  // Load city modules on demand
801
821
  if (env === 'city') getCityMods();
802
822
  if (S.scene) {
803
- // Remove all existing agents so they get recreated with proper desk assignments
823
+ // Remove all existing agents including CSS2D label DOM elements
804
824
  for (var name in S.agents3d) {
805
825
  var agent = S.agents3d[name];
826
+ // Remove CSS2D label DOM elements explicitly
827
+ if (agent.parts.labelDiv && agent.parts.labelDiv.parentElement) agent.parts.labelDiv.remove();
828
+ if (agent.parts.bubbleDiv && agent.parts.bubbleDiv.parentElement) agent.parts.bubbleDiv.remove();
829
+ if (agent.parts.taskDiv && agent.parts.taskDiv.parentElement) agent.parts.taskDiv.remove();
830
+ // Remove all CSS2DObject DOM nodes from the group tree
831
+ agent.parts.group.traverse(function(child) {
832
+ if (child.isCSS2DObject && child.element && child.element.parentElement) {
833
+ child.element.remove();
834
+ }
835
+ });
806
836
  S.scene.remove(agent.parts.group);
807
837
  agent.parts.group.traverse(function(child) {
808
838
  if (child.geometry) child.geometry.dispose();
@@ -813,6 +843,8 @@ window.office3dSetEnvironment = function(env) {
813
843
  });
814
844
  }
815
845
  S.agents3d = {};
846
+ // Also release any claimed gallery seats
847
+ window._gallerySeatsReset = true;
816
848
  S._tvScreen = null;
817
849
  S._roofGroup = null;
818
850
  S._managerDoor = null;
@@ -821,6 +853,7 @@ window.office3dSetEnvironment = function(env) {
821
853
  S._managerOfficePos = null;
822
854
  S._campusDeskPositions = null;
823
855
  S.lastProcessedMsg = 0;
856
+ window._lastProcessedMsg = 0;
824
857
  invalidateColliders();
825
858
  buildEnvironment();
826
859
  // syncAgents will recreate all agents with correct desk assignments
@@ -885,49 +918,120 @@ if (window.activeView === 'office') {
885
918
  window.office3dStart();
886
919
  }
887
920
 
888
- // ===================== INTERACTIVE IFRAME MONITOR (Phase 2) =====================
889
- var activeIframe = null;
921
+ // ===================== INTERACTIVE MONITOR Dashboard + ComfyUI tabs =====================
922
+ var _monitorOverlay = null;
923
+ var _monitorActiveTab = 'dashboard';
890
924
 
891
925
  window.onPlayerSit = function(deskIdx) {
892
- if (activeIframe) return;
893
- var container = document.getElementById('office-3d-container') || document.getElementById('office-area');
926
+ if (_monitorOverlay) return;
927
+
928
+ // Release pointer lock so mouse works in iframe
929
+ if (document.pointerLockElement) document.exitPointerLock();
930
+
931
+ // Use office-area as parent (it has proper positioning)
932
+ var container = document.getElementById('office-area');
933
+ if (!container) container = document.getElementById('office-3d-container');
894
934
  if (!container) return;
895
935
 
896
- // Create iframe overlay positioned over the 3D canvas
936
+ // Overlay
897
937
  var overlay = document.createElement('div');
898
- overlay.id = 'office-iframe-overlay';
899
- overlay.style.cssText = 'position:absolute;top:5%;left:10%;width:80%;height:85%;z-index:200;background:#000;border-radius:8px;box-shadow:0 0 40px rgba(88,166,255,0.3);overflow:hidden;display:flex;flex-direction:column';
938
+ overlay.id = 'office-monitor-overlay';
939
+ overlay.style.cssText = 'position:fixed;top:3%;left:5%;width:90%;height:92%;z-index:99999;background:#0a0c14;border-radius:10px;box-shadow:0 0 60px rgba(88,166,255,0.3),0 0 0 1px #30363d;overflow:hidden;display:flex;flex-direction:column;';
900
940
 
901
- // Header bar (mimics monitor bezel)
941
+ // Header bar with tabs + close
902
942
  var header = document.createElement('div');
903
- header.style.cssText = 'background:#1a1f36;padding:6px 12px;display:flex;align-items:center;justify-content:space-between;flex-shrink:0';
904
- header.innerHTML = '<div style="display:flex;gap:6px"><span style="width:10px;height:10px;border-radius:50%;background:#ff5f57"></span><span style="width:10px;height:10px;border-radius:50%;background:#ffbd2e"></span><span style="width:10px;height:10px;border-radius:50%;background:#28c840"></span></div><span style="color:#8892b0;font-size:11px;font-family:monospace">Let Them Talk Dashboard</span><button id="office-leave-btn" style="background:#ff5f57;color:#fff;border:none;border-radius:4px;padding:3px 12px;font-size:11px;font-weight:bold;cursor:pointer;font-family:monospace">LEAVE</button>';
905
- header.querySelector('#office-leave-btn').addEventListener('click', function() {
906
- if (typeof window.onPlayerStand === 'function') window.onPlayerStand();
907
- // Also trigger player stand-up in player.js
908
- if (typeof window.playerForceStand === 'function') window.playerForceStand();
909
- });
943
+ header.style.cssText = 'background:#141824;padding:0 12px;display:flex;align-items:center;justify-content:space-between;flex-shrink:0;height:36px;border-bottom:1px solid #30363d;';
944
+
945
+ // Tab buttons
946
+ var tabsHtml = '<div style="display:flex;gap:0;">';
947
+ tabsHtml += '<button id="mon-tab-dashboard" style="padding:8px 16px;font-size:11px;font-family:monospace;border:none;cursor:pointer;border-bottom:2px solid #58a6ff;background:transparent;color:#58a6ff;">Dashboard</button>';
948
+ tabsHtml += '<button id="mon-tab-comfyui" style="padding:8px 16px;font-size:11px;font-family:monospace;border:none;cursor:pointer;border-bottom:2px solid transparent;background:transparent;color:#8892b0;">ComfyUI</button>';
949
+ tabsHtml += '</div>';
950
+
951
+ var closeHtml = '<button id="mon-close-btn" style="background:#ff5f57;color:#fff;border:none;border-radius:4px;padding:3px 14px;font-size:11px;font-weight:bold;cursor:pointer;font-family:monospace;">ESC to Leave</button>';
952
+
953
+ header.innerHTML = tabsHtml + closeHtml;
910
954
  overlay.appendChild(header);
911
955
 
956
+ // Iframe container
957
+ var iframeWrap = document.createElement('div');
958
+ iframeWrap.style.cssText = 'flex:1;position:relative;';
959
+
912
960
  // Dashboard iframe
913
- var iframe = document.createElement('iframe');
914
- iframe.src = window.location.origin || 'http://localhost:3000';
915
- iframe.style.cssText = 'flex:1;border:none;width:100%;background:#0d1117';
916
- iframe.allow = 'clipboard-read; clipboard-write';
917
- overlay.appendChild(iframe);
918
-
919
- container.style.position = 'relative';
920
- container.appendChild(overlay);
921
- activeIframe = overlay;
922
-
923
- // Focus iframe for keyboard input
924
- iframe.addEventListener('load', function() { iframe.focus(); });
961
+ var dashIframe = document.createElement('iframe');
962
+ dashIframe.id = 'mon-iframe-dashboard';
963
+ dashIframe.src = window.location.origin || 'http://localhost:3000';
964
+ dashIframe.style.cssText = 'position:absolute;top:0;left:0;width:100%;height:100%;border:none;background:#0d1117;';
965
+ dashIframe.allow = 'clipboard-read; clipboard-write';
966
+ iframeWrap.appendChild(dashIframe);
967
+
968
+ // ComfyUI iframe (hidden initially)
969
+ var comfyIframe = document.createElement('iframe');
970
+ comfyIframe.id = 'mon-iframe-comfyui';
971
+ comfyIframe.style.cssText = 'position:absolute;top:0;left:0;width:100%;height:100%;border:none;background:#1a1a2e;display:none;';
972
+ comfyIframe.allow = 'clipboard-read; clipboard-write';
973
+ // Don't load ComfyUI until tab is clicked (saves resources)
974
+ iframeWrap.appendChild(comfyIframe);
975
+
976
+ overlay.appendChild(iframeWrap);
977
+ document.body.appendChild(overlay);
978
+ _monitorOverlay = overlay;
979
+ _monitorActiveTab = 'dashboard';
980
+
981
+ // Tab switching
982
+ function switchTab(tab) {
983
+ _monitorActiveTab = tab;
984
+ var dIframe = document.getElementById('mon-iframe-dashboard');
985
+ var cIframe = document.getElementById('mon-iframe-comfyui');
986
+ var dTab = document.getElementById('mon-tab-dashboard');
987
+ var cTab = document.getElementById('mon-tab-comfyui');
988
+
989
+ if (tab === 'dashboard') {
990
+ if (dIframe) dIframe.style.display = 'block';
991
+ if (cIframe) cIframe.style.display = 'none';
992
+ if (dTab) { dTab.style.color = '#58a6ff'; dTab.style.borderBottomColor = '#58a6ff'; }
993
+ if (cTab) { cTab.style.color = '#8892b0'; cTab.style.borderBottomColor = 'transparent'; }
994
+ } else {
995
+ if (dIframe) dIframe.style.display = 'none';
996
+ if (cIframe) {
997
+ cIframe.style.display = 'block';
998
+ // Lazy-load ComfyUI on first switch
999
+ if (!cIframe.src || cIframe.src === 'about:blank' || cIframe.src === '') {
1000
+ cIframe.src = 'http://127.0.0.1:8188';
1001
+ }
1002
+ }
1003
+ if (dTab) { dTab.style.color = '#8892b0'; dTab.style.borderBottomColor = 'transparent'; }
1004
+ if (cTab) { cTab.style.color = '#ff6b35'; cTab.style.borderBottomColor = '#ff6b35'; }
1005
+ }
1006
+ }
1007
+
1008
+ overlay.querySelector('#mon-tab-dashboard').addEventListener('click', function() { switchTab('dashboard'); });
1009
+ overlay.querySelector('#mon-tab-comfyui').addEventListener('click', function() { switchTab('comfyui'); });
1010
+
1011
+ // Close button
1012
+ overlay.querySelector('#mon-close-btn').addEventListener('click', function() {
1013
+ if (typeof window.playerForceStand === 'function') window.playerForceStand();
1014
+ window.onPlayerStand();
1015
+ });
1016
+
1017
+ // ESC key closes overlay
1018
+ overlay._escHandler = function(e) {
1019
+ if (e.code === 'Escape' && _monitorOverlay) {
1020
+ if (typeof window.playerForceStand === 'function') window.playerForceStand();
1021
+ window.onPlayerStand();
1022
+ }
1023
+ };
1024
+ document.addEventListener('keydown', overlay._escHandler);
1025
+
1026
+ // Focus dashboard iframe
1027
+ dashIframe.addEventListener('load', function() { dashIframe.focus(); });
925
1028
  };
926
1029
 
927
1030
  window.onPlayerStand = function() {
928
- if (activeIframe) {
929
- activeIframe.remove();
930
- activeIframe = null;
1031
+ if (_monitorOverlay) {
1032
+ if (_monitorOverlay._escHandler) document.removeEventListener('keydown', _monitorOverlay._escHandler);
1033
+ _monitorOverlay.remove();
1034
+ _monitorOverlay = null;
931
1035
  }
932
1036
  };
933
1037