  var workspace = null;

  function updateWorkspaceStatusPanel(data) {
    var panel = document.getElementById('ws-status-panel');
    if (!panel) return;
    var agents = (data && data.agents) || [];
    if (!agents.length) {
      setHtml(panel, '<span style="font-size:0.75rem;color:var(--muted)">No agents in workspace.</span>');
      return;
    }
    var now = Date.now();
    function relTime(isoStr) {
      if (!isoStr) return '';
      var t = new Date(isoStr).getTime();
      if (isNaN(t)) return '';
      var diffS = Math.floor((now - t) / 1000);
      if (diffS < 5) return 'just now';
      if (diffS < 60) return diffS + 's ago';
      if (diffS < 3600) return Math.floor(diffS / 60) + 'm ago';
      if (diffS < 86400) return Math.floor(diffS / 3600) + 'h ago';
      return Math.floor(diffS / 86400) + 'd ago';
    }
    function stateColor(st) {
      var s = (st || '').toLowerCase();
      if (s === 'running' || s === 'working' || s === 'inference' || s === 'tooling') return 'var(--success)';
      if (s === 'failed' || s === 'error') return 'var(--error)';
      return 'var(--muted)';
    }
    function stateLabel(st) {
      var s = (st || 'idle').toLowerCase();
      if (s === 'inference' || s === 'tooling' || s === 'tool_execution') return 'running';
      return s;
    }
    var items = agents.map(function(a) {
      var rawState = a.task_state || a.activity || a.state || 'idle';
      var st = stateLabel(rawState);
      var color = stateColor(rawState);
      var rel = relTime(a.last_event_at || a.updated_at || '');
      var summary = a.active_task_summary || '';
      var pct = (a.active_task_percentage != null && a.active_task_percentage > 0)
        ? Math.round(a.active_task_percentage * 100) + '%' : '';
      return '<div style="display:inline-flex;align-items:center;gap:0.35rem;padding:0.2rem 0.55rem;background:var(--surface-2);border:1px solid var(--border-ghost);border-radius:9999px">'
        + '<span style="width:6px;height:6px;border-radius:50%;background:' + color + ';flex-shrink:0"></span>'
        + '<span style="font-size:0.6875rem;color:var(--text);font-weight:500">' + esc(a.name || a.id || '?') + '</span>'
        + '<span style="font-size:0.625rem;color:' + color + ';font-family:var(--font-mono)">' + esc(st) + '</span>'
        + (pct ? '<span style="font-size:0.5625rem;color:var(--accent);font-family:var(--font-mono)">' + esc(pct) + '</span>' : '')
        + (summary ? '<span style="font-size:0.5625rem;color:var(--muted);max-width:150px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap" title="' + esc(summary) + '">' + esc(summary) + '</span>' : '')
        + (rel ? '<span style="font-size:0.5625rem;color:var(--muted)">' + esc(rel) + '</span>' : '')
        + '</div>';
    }).join('');
    setHtml(panel, items);
  }

  function startWorkspaceEngine(data) { if (workspace) { workspace.stop(); workspace = null; } var canvas = document.getElementById('ws-canvas'); if (!canvas) return; workspace = new WorkspaceEngine(canvas, data); workspace.start(); updateWorkspaceStatusPanel(data); }

  function WorkspaceEngine(canvas, data) {
    this.canvas = canvas; this.ctx = canvas.getContext('2d');
    this.running = false; this.animId = 0; this.time = 0;
    this.bots = []; this.stations = []; this.tethers = []; this.interactions = []; this.dataStreams = [];
    this.selectedBotId = null;
    this.dpr = window.devicePixelRatio || 1;
    var self = this;
    this._onCanvasClick = function(e) { self.onCanvasClick(e); };
    this._onCanvasMove = function(e) { self.onCanvasMove(e); };
    this.canvas.addEventListener('click', this._onCanvasClick);
    this.canvas.addEventListener('mousemove', this._onCanvasMove);
    this._resizeObs = new ResizeObserver(function() { self.resize(); });
    this._resizeObs.observe(canvas.parentElement);
    this.initData(data);
  }

  WorkspaceEngine.prototype._stationById = function(id) {
    if (!id) return null;
    return this.stations.find(function(s) { return s.id === id; }) || null;
  };

  WorkspaceEngine.prototype._idleWarehouseStation = function() {
    return this.stations.find(function(s) {
      var k = (s.kind || '').toLowerCase();
      return s.id === 'shelter' || s.id === 'standby' || k === 'shelter' || k === 'standby';
    }) || null;
  };

  WorkspaceEngine.prototype._isRuntimeActive = function(activity) {
    var a = (activity || '').toLowerCase();
    return a === 'inference' || a === 'working' || a === 'tool_execution' || a === 'tooling' || a === 'moving' || a === 'walking' || a === 'talking';
  };

  WorkspaceEngine.prototype._buildBotFromAgent = function(a, idx, total) {
    var cx = 0.5, cy = 0.5;
    if (total > 1) {
      var angle = (idx / total) * Math.PI * 2 - Math.PI / 2;
      var radius = Math.min(0.22, 0.08 + total * 0.012);
      cx = 0.5 + Math.cos(angle) * radius;
      cy = 0.5 + Math.sin(angle) * radius;
    }
    return {
      id: a.id, name: a.name, role: a.role || 'subagent',
      model: a.model || '', state: (a.state || 'Idle').toString(), color: a.color || '#c180ff',
      activity: (a.activity || '').toString().toLowerCase(),
      subordinates: a.subordinates || [], supervisor: a.supervisor || null,
      skills: a.skills || [], activeSkill: a.active_skill || null, skillFade: 0,
      x: cx, y: cy,
      targetX: cx, targetY: cy, vx: 0, vy: 0,
      animState: (a.activity && a.activity !== 'idle') ? 'working' : 'idle', animFrame: 0, animTimer: 0,
      headAngle: 0, headTarget: 0, headTimer: 2 + idx * 0.15,
      lights: [0.6, 0.6, 0.8], lightTimer: 0.5,
      blinkTimer: 3 + idx * 0.2, blinking: false,
      workTimer: 0, currentStation: a.current_workstation || null,
      taskState: a.task_state || null,
      activeTaskSummary: a.active_task_summary || null,
      activeTaskPercentage: a.active_task_percentage || null
    };
  };

  WorkspaceEngine.prototype.initData = function(data) {
    var agents = data.agents || [];
    var systems = data.systems || data.workstations || [];
    this.stations = systems.map(function(s) { return { id: s.id, name: s.name, kind: s.kind, x: s.x, y: s.y }; });
    if (this.stations.length === 0) {
      this.stations = [
        { id: 'llm', name: 'LLM Inference', kind: 'Inference', x: 0.18, y: 0.22 },
        { id: 'memory', name: 'Memory', kind: 'Storage', x: 0.82, y: 0.22 },
        { id: 'exec', name: 'Code Execution', kind: 'Execution', x: 0.18, y: 0.78 },
        { id: 'blockchain', name: 'Blockchain', kind: 'Blockchain', x: 0.82, y: 0.78 },
        { id: 'web', name: 'Web / APIs', kind: 'Tool', x: 0.50, y: 0.12 },
        { id: 'files', name: 'File System', kind: 'Tool', x: 0.50, y: 0.88 },
        { id: 'tools_plugins', name: 'Tools / Plugins', kind: 'Plugin', x: 0.965, y: 0.50 },
        { id: 'shelter', name: 'Idle Agents', kind: 'Shelter', x: 0.035, y: 0.50 }
      ];
    }
    var self = this;
    this.bots = agents.map(function(a, idx) { return self._buildBotFromAgent(a, idx, agents.length); });
    this.bots.forEach(function(bot) {
      if (!bot.currentStation) return;
      var st = self.stations.find(function(s) { return s.id === bot.currentStation; });
      if (st) {
        bot.targetX = st.x;
        bot.targetY = st.y + 0.10;
      }
    });
    this.layoutStandby();
    this.tethers = [];
    for (var i = 0; i < this.bots.length; i++) {
      var bot = this.bots[i];
      if (bot.subordinates && bot.subordinates.length > 0) {
        for (var j = 0; j < bot.subordinates.length; j++) {
          var sub = this.bots.find(function(b) { return b.id === bot.subordinates[j]; });
          if (sub) this.tethers.push({ from: bot, to: sub, color: bot.color });
        }
      }
    }
  };

  WorkspaceEngine.prototype.applySnapshot = function(data) {
    if (!data) return;
    var agents = data.agents || [];
    var systems = data.systems || data.workstations || [];
    if (systems.length > 0) {
      this.stations = systems.map(function(s) { return { id: s.id, name: s.name, kind: s.kind, x: s.x, y: s.y }; });
    }
    var byId = {};
    this.bots.forEach(function(b) { byId[b.id] = b; });
    var nextBots = [];
    for (var i = 0; i < agents.length; i++) {
      var incoming = agents[i];
      var existing = byId[incoming.id];
      if (!existing) {
        existing = this._buildBotFromAgent(incoming, i, agents.length);
      } else {
        existing.name = incoming.name || existing.name;
        existing.role = incoming.role || existing.role;
        existing.model = incoming.model || existing.model;
        existing.state = (incoming.state || existing.state || 'Idle').toString();
        existing.color = incoming.color || existing.color;
        existing.activity = (incoming.activity || '').toString().toLowerCase();
        existing.subordinates = incoming.subordinates || existing.subordinates || [];
        existing.supervisor = incoming.supervisor || null;
        existing.skills = incoming.skills || existing.skills || [];
        existing.activeSkill = incoming.active_skill || null;
        existing.currentStation = incoming.current_workstation || existing.currentStation || null;
        existing.taskState = incoming.task_state || existing.taskState || null;
        existing.activeTaskSummary = incoming.active_task_summary || existing.activeTaskSummary || null;
        existing.activeTaskPercentage = incoming.active_task_percentage != null ? incoming.active_task_percentage : existing.activeTaskPercentage;
        var runtimeActive = this._isRuntimeActive(existing.activity);
        var stationedActive = !!existing.currentStation && existing.currentStation !== 'standby' && existing.currentStation !== 'shelter';
        if (existing.animState !== 'walking') existing.animState = (existing.activeSkill || runtimeActive || stationedActive) ? 'working' : 'idle';
      }
      var st = this._stationById(existing.currentStation);
      if (st) {
        existing.targetX = st.x;
        existing.targetY = st.y + 0.10;
      }
      nextBots.push(existing);
    }
    this.bots = nextBots;
    this.tethers = [];
    for (var t = 0; t < this.bots.length; t++) {
      var bot = this.bots[t];
      if (bot.subordinates && bot.subordinates.length > 0) {
        for (var j = 0; j < bot.subordinates.length; j++) {
          var sub = this.bots.find(function(b) { return b.id === bot.subordinates[j]; });
          if (sub) this.tethers.push({ from: bot, to: sub, color: bot.color });
        }
      }
    }
    this.layoutStandby();
  };

  WorkspaceEngine.prototype.layoutStandby = function() {
    var standby = this._idleWarehouseStation();
    if (!standby) return;
    var idleBots = this.bots.filter(function(b) {
      return !b.activeSkill && (b.animState === 'idle' || b.animState === 'walking');
    });
    // Move idle bots toward the shelter station. The bot animates back
    // via the standard lerp in tick() and drawBot() keeps drawing it
    // at reduced opacity until it arrives, then hides it.
    for (var i = 0; i < idleBots.length; i++) {
      var b = idleBots[i];
      b.targetX = standby.x;
      b.targetY = standby.y;
      b.currentStation = 'shelter';
    }
  };

  WorkspaceEngine.prototype.resize = function() {
    var parent = this.canvas.parentElement; if (!parent) return;
    var w = parent.clientWidth, h = parent.clientHeight; if (w < 1 || h < 1) return;
    this.canvas.width = w * this.dpr; this.canvas.height = h * this.dpr;
    this.canvas.style.width = w + 'px'; this.canvas.style.height = h + 'px';
    this.ctx.setTransform(this.dpr, 0, 0, this.dpr, 0, 0);
    this.W = w; this.H = h;
  };

  WorkspaceEngine.prototype.start = function() {
    this.running = true; this.resize(); this.lastTime = performance.now();
    var self = this;
    (function loop(ts) {
      if (!self.running) return;
      var dt = Math.min((ts - self.lastTime) / 1000, 0.05);
      self.lastTime = ts; self.time += dt;
      self.update(dt); self.render();
      self.animId = requestAnimationFrame(loop);
    })(performance.now());
  };

  WorkspaceEngine.prototype.stop = function() {
    this.running = false; cancelAnimationFrame(this.animId);
    if (this._onCanvasClick) this.canvas.removeEventListener('click', this._onCanvasClick);
    if (this._onCanvasMove) this.canvas.removeEventListener('mousemove', this._onCanvasMove);
    if (this._resizeObs) this._resizeObs.disconnect();
  };

  WorkspaceEngine.prototype._botAtScreenPoint = function(x, y) {
    var sorted = this.bots.slice().sort(function(a, b) { return b.y - a.y; });
    for (var i = 0; i < sorted.length; i++) {
      var bot = sorted[i];
      var isAgent = bot.role === 'agent';
      var radius = isAgent ? 34 : 28;
      var dx = x - (bot.x * this.W);
      var dy = y - (bot.y * this.H);
      if ((dx * dx + dy * dy) <= radius * radius) return bot;
    }
    return null;
  };

  WorkspaceEngine.prototype.onCanvasClick = function(evt) {
    if (!this.W || !this.H) return;
    var rect = this.canvas.getBoundingClientRect();
    var x = evt.clientX - rect.left;
    var y = evt.clientY - rect.top;
    var hit = this._botAtScreenPoint(x, y);
    this.selectedBotId = hit ? hit.id : null;
  };

  WorkspaceEngine.prototype.onCanvasMove = function(evt) {
    if (!this.W || !this.H) return;
    var rect = this.canvas.getBoundingClientRect();
    var x = evt.clientX - rect.left;
    var y = evt.clientY - rect.top;
    var hit = this._botAtScreenPoint(x, y);
    this.canvas.style.cursor = hit ? 'pointer' : 'default';
    this.canvas.title = hit && hit.name ? hit.name : '';
  };

  WorkspaceEngine.prototype.describeBotTask = function(bot) {
    if (!bot) return '';
    // Prefer explicit task metadata over animation-derived labels.
    if (bot.taskState === 'failed') {
      return bot.activeTaskSummary
        ? 'Failed: ' + bot.activeTaskSummary
        : 'Task failed.';
    }
    if (bot.activeTaskSummary) {
      var pct = (bot.activeTaskPercentage != null && bot.activeTaskPercentage > 0)
        ? ' (' + Math.round(bot.activeTaskPercentage * 100) + '%)' : '';
      return bot.activeTaskSummary + pct;
    }
    // Fall back to animation state when no task metadata is available.
    if (bot.animState === 'working' && bot.activeSkill) return 'Working on ' + bot.activeSkill + '.';
    if (bot.animState === 'working' && bot.currentStation) {
      var ws = this.stations.find(function(s) { return s.id === bot.currentStation; });
      return ws ? ('Working at ' + ws.name + '.') : 'Working on a task.';
    }
    if (bot.currentStation === 'standby' || bot.currentStation === 'shelter') return 'Idle.';
    if (bot.animState === 'walking') return 'Moving to next workstation.';
    return 'Idle.';
  };

  WorkspaceEngine.prototype.drawTaskBubble = function(ctx, bot, W, H) {
    var tc = this._themeColors || { surface: '#081329', text: '#dee5ff', aR: 193, aG: 128, aB: 255 };
    var text = this.describeBotTask(bot);
    if (!text) return;
    var px = bot.x * W;
    var py = bot.y * H - 56;
    ctx.save();
    ctx.font = '10px ' + getComputedStyle(document.body).fontFamily;
    var maxW = 240;
    var words = text.split(' ');
    var lines = [];
    var current = '';
    for (var i = 0; i < words.length; i++) {
      var testLine = current ? (current + ' ' + words[i]) : words[i];
      if (ctx.measureText(testLine).width > maxW && current) {
        lines.push(current);
        current = words[i];
      } else {
        current = testLine;
      }
    }
    if (current) lines.push(current);
    var bubbleW = 0;
    for (var li = 0; li < lines.length; li++) bubbleW = Math.max(bubbleW, ctx.measureText(lines[li]).width);
    bubbleW += 14;
    var lineH = 12;
    var bubbleH = lines.length * lineH + 10;
    var bx = Math.max(10, Math.min(W - bubbleW - 10, px - bubbleW / 2));
    var by = Math.max(8, py - bubbleH);
    ctx.fillStyle = tc.surface;
    ctx.globalAlpha = 0.96;
    ctx.beginPath(); ctx.roundRect(bx, by, bubbleW, bubbleH, 8); ctx.fill();
    ctx.strokeStyle = 'rgba(' + tc.aR + ',' + tc.aG + ',' + tc.aB + ',0.35)';
    ctx.lineWidth = 1;
    ctx.beginPath(); ctx.roundRect(bx, by, bubbleW, bubbleH, 8); ctx.stroke();
    ctx.beginPath();
    ctx.moveTo(px - 5, by + bubbleH);
    ctx.lineTo(px, by + bubbleH + 8);
    ctx.lineTo(px + 5, by + bubbleH);
    ctx.closePath();
    ctx.fillStyle = tc.surface;
    ctx.fill();
    ctx.stroke();
    ctx.fillStyle = tc.text;
    ctx.globalAlpha = 1;
    ctx.textAlign = 'left';
    for (var t = 0; t < lines.length; t++) ctx.fillText(lines[t], bx + 7, by + 14 + t * lineH);
    ctx.restore();
  };

  WorkspaceEngine.prototype.update = function(dt) {
    if (this.selectedBotId && !this.bots.some(function(b) { return b.id === this.selectedBotId; }, this)) {
      this.selectedBotId = null;
    }
    var self = this;
    this.bots.forEach(function(bot) {
      var dx = bot.targetX - bot.x, dy = bot.targetY - bot.y;
      var dist = Math.sqrt(dx * dx + dy * dy);
      if (dist > 0.005) {
        var step = Math.min(0.35 * dt, dist);
        bot.x += (dx / dist) * step; bot.y += (dy / dist) * step;
        bot.animState = 'walking';
      } else {
        bot.x = bot.targetX; bot.y = bot.targetY;
        if (bot.animState === 'walking') bot.animState = bot.activeSkill ? 'working' : 'idle';
      }
      bot.headTimer -= dt;
      if (bot.headTimer <= 0) { bot.headTarget = Math.sin(bot.animFrame * 0.9) * 0.35; bot.headTimer = 2.8; }
      bot.headAngle += (bot.headTarget - bot.headAngle) * 2 * dt;
      bot.lightTimer -= dt;
      if (bot.lightTimer <= 0) { bot.lights = bot.activeSkill ? [0.9, 0.6, 0.2] : [0.25, 0.45, 0.7]; bot.lightTimer = 0.5; }
      bot.blinkTimer -= dt;
      if (bot.blinkTimer <= 0) { bot.blinking = !bot.blinking; bot.blinkTimer = bot.blinking ? 0.12 : 3.2; }
      bot.animTimer += dt;
      if (bot.animTimer > 0.18) { bot.animFrame = (bot.animFrame + 1) % 4; bot.animTimer = 0; }

      if (bot.activeSkill) { bot.skillFade = Math.min(1, bot.skillFade + dt * 3); }
      else { bot.skillFade = Math.max(0, bot.skillFade - dt * 2); }
      if (bot.animState !== 'walking') {
        var runtimeActive = self._isRuntimeActive(bot.activity);
        var stationedActive = !!bot.currentStation && bot.currentStation !== 'standby' && bot.currentStation !== 'shelter';
        bot.animState = (bot.activeSkill || runtimeActive || stationedActive) ? 'working' : 'idle';
      }
    });

    for (var i = this.dataStreams.length - 1; i >= 0; i--) {
      var ds = this.dataStreams[i];
      ds.progress += dt * 1.5;
      ds.timer -= dt;
      if (ds.timer <= 0) this.dataStreams.splice(i, 1);
    }

    for (var i = this.interactions.length - 1; i >= 0; i--) {
      var ix = this.interactions[i];
      ix.timer -= dt; ix.phase += dt;
      ix.bubbleTimer += dt;
      if (ix.timer <= 0) {
        if (ix.botA._savedTarget) { ix.botA.targetX = ix.botA._savedTarget.x; ix.botA.targetY = ix.botA._savedTarget.y; delete ix.botA._savedTarget; }
        if (ix.botB._savedTarget) { ix.botB.targetX = ix.botB._savedTarget.x; ix.botB.targetY = ix.botB._savedTarget.y; delete ix.botB._savedTarget; }
        ix.botA.animState = 'idle'; ix.botB.animState = 'idle';
        this.interactions.splice(i, 1);
      }
    }
    this.layoutStandby();
  };

  function parseThemeColors() {
    var cs = getComputedStyle(document.documentElement);
    var bg = cs.getPropertyValue('--bg').trim() || '#060e20';
    var surface = cs.getPropertyValue('--surface').trim() || '#081329';
    var accent = cs.getPropertyValue('--accent').trim() || '#c180ff';
    var muted = cs.getPropertyValue('--muted').trim() || '#9baad6';
    var el = document.createElement('canvas'); el.width = el.height = 1;
    var c2 = el.getContext('2d'); c2.fillStyle = accent; c2.fillRect(0, 0, 1, 1);
    var d = c2.getImageData(0, 0, 1, 1).data;
    var text = cs.getPropertyValue('--text').trim() || '#dee5ff';
    return { bg: bg, surface: surface, accent: accent, muted: muted, text: text, aR: d[0], aG: d[1], aB: d[2] };
  }

  WorkspaceEngine.prototype.render = function() {
    var ctx = this.ctx, W = this.W, H = this.H;
    if (!W || !H) return;
    var tc = parseThemeColors(); var aR = tc.aR, aG = tc.aG, aB = tc.aB;
    this._themeColors = tc;
    ctx.clearRect(0, 0, W, H); ctx.fillStyle = tc.bg; ctx.fillRect(0, 0, W, H);

    ctx.fillStyle = 'rgba(' + aR + ',' + aG + ',' + aB + ',0.04)';
    for (var gx = 30; gx < W; gx += 40) for (var gy = 30; gy < H; gy += 40) {
      ctx.beginPath(); ctx.arc(gx, gy, 0.8, 0, Math.PI * 2); ctx.fill();
    }

    var self = this;
    this.stations.forEach(function(s) {
      var sx = s.x * W, sy = s.y * H;
      ctx.save(); ctx.translate(sx, sy);
      var pulse = 0.6 + 0.4 * Math.sin(self.time * 1.5 + sx * 0.01);
      var nearBot = self.bots.some(function(b) { return b.currentStation === s.id && (b.animState === 'working' || b.animState === 'walking'); });
      var glowAlpha = nearBot ? 0.08 : 0.03;
      var ringAlpha = nearBot ? (0.2 + 0.1 * pulse) : (0.08 + 0.04 * pulse);

      ctx.fillStyle = 'rgba(' + aR + ',' + aG + ',' + aB + ',' + glowAlpha + ')';
      ctx.beginPath(); ctx.arc(0, 8, 42, 0, Math.PI * 2); ctx.fill();
      ctx.strokeStyle = 'rgba(' + aR + ',' + aG + ',' + aB + ',' + ringAlpha + ')';
      ctx.lineWidth = nearBot ? 1.5 : 0.8;
      ctx.beginPath(); ctx.arc(0, 8, 42, 0, Math.PI * 2); ctx.stroke();

      var kind = (s.kind || '').toLowerCase();
      if (kind === 'inference') {
        ctx.strokeStyle = 'rgba(' + aR + ',' + aG + ',' + aB + ',0.3)'; ctx.lineWidth = 1;
        var nodes = [[-12,-18],[12,-18],[0,-6],[-18,-4],[18,-4],[-8,6],[8,6],[0,16]];
        var edges = [[0,2],[1,2],[0,3],[1,4],[2,5],[2,6],[3,5],[4,6],[5,7],[6,7]];
        edges.forEach(function(e) { ctx.beginPath(); ctx.moveTo(nodes[e[0]][0], nodes[e[0]][1]); ctx.lineTo(nodes[e[1]][0], nodes[e[1]][1]); ctx.stroke(); });
        nodes.forEach(function(n, i) { var r = i === 2 || i === 7 ? 4.5 : 3.5; ctx.fillStyle = tc.surface; ctx.beginPath(); ctx.arc(n[0], n[1], r, 0, Math.PI * 2); ctx.fill(); ctx.strokeStyle = 'rgba(' + aR + ',' + aG + ',' + aB + ',' + (0.5 + 0.3 * pulse) + ')'; ctx.lineWidth = 1.5; ctx.stroke(); ctx.fillStyle = 'rgba(' + aR + ',' + aG + ',' + aB + ',' + (0.3 + 0.4 * ((Math.sin(self.time * 3 + i * 1.2) + 1) / 2)) + ')'; ctx.beginPath(); ctx.arc(n[0], n[1], r - 1, 0, Math.PI * 2); ctx.fill(); });
      } else if (kind === 'storage') {
        for (var ci = 0; ci < 3; ci++) { var cy = -14 + ci * 11; ctx.fillStyle = tc.surface; ctx.beginPath(); ctx.ellipse(0, cy + 8, 18, 4, 0, 0, Math.PI * 2); ctx.fill(); ctx.fillRect(-18, cy, 36, 8); ctx.beginPath(); ctx.ellipse(0, cy, 18, 4, 0, 0, Math.PI * 2); ctx.fill(); ctx.strokeStyle = 'rgba(' + aR + ',' + aG + ',' + aB + ',' + (0.3 + 0.1 * ci) + ')'; ctx.lineWidth = 1.2; ctx.beginPath(); ctx.ellipse(0, cy, 18, 4, 0, 0, Math.PI * 2); ctx.stroke(); ctx.beginPath(); ctx.moveTo(-18, cy); ctx.lineTo(-18, cy + 8); ctx.stroke(); ctx.beginPath(); ctx.moveTo(18, cy); ctx.lineTo(18, cy + 8); ctx.stroke(); ctx.beginPath(); ctx.ellipse(0, cy + 8, 18, 4, 0, Math.PI, Math.PI * 2); ctx.stroke(); ctx.fillStyle = 'rgba(' + aR + ',' + aG + ',' + aB + ',' + (0.08 + 0.06 * Math.sin(self.time * 2 + ci)) + ')'; ctx.fillRect(-16, cy + 1, 32, 6); }
      } else if (kind === 'execution') {
        ctx.fillStyle = tc.surface; ctx.strokeStyle = 'rgba(' + aR + ',' + aG + ',' + aB + ',0.35)'; ctx.lineWidth = 1.5; ctx.beginPath(); ctx.roundRect(-22, -18, 44, 34, 4); ctx.fill(); ctx.stroke(); ctx.fillStyle = 'rgba(' + aR + ',' + aG + ',' + aB + ',0.12)'; ctx.fillRect(-22, -18, 44, 8); ctx.fillStyle = '#ef4444'; ctx.beginPath(); ctx.arc(-16, -14, 2, 0, Math.PI * 2); ctx.fill(); ctx.fillStyle = '#eab308'; ctx.beginPath(); ctx.arc(-10, -14, 2, 0, Math.PI * 2); ctx.fill(); ctx.fillStyle = '#22c55e'; ctx.beginPath(); ctx.arc(-4, -14, 2, 0, Math.PI * 2); ctx.fill(); ctx.fillStyle = 'rgba(' + aR + ',' + aG + ',' + aB + ',0.6)'; ctx.font = 'bold 8px monospace'; ctx.textAlign = 'left'; ctx.fillText('$_', -17, -2); ctx.fillStyle = 'rgba(' + aR + ',' + aG + ',' + aB + ',0.25)'; ctx.fillRect(-8, -6, 20 * pulse, 2); ctx.fillRect(-17, 3, 26, 1.5); ctx.fillRect(-17, 8, 18, 1.5); if (Math.sin(self.time * 4) > 0) { ctx.fillStyle = 'rgba(' + aR + ',' + aG + ',' + aB + ',0.8)'; ctx.fillRect(-10, -6, 6, 8); }
      } else if (kind === 'plugin') {
        ctx.fillStyle = tc.surface; ctx.strokeStyle = 'rgba(' + aR + ',' + aG + ',' + aB + ',0.35)'; ctx.lineWidth = 1.3;
        ctx.beginPath(); ctx.roundRect(-20, -16, 40, 32, 5); ctx.fill(); ctx.stroke();
        ctx.fillStyle = 'rgba(' + aR + ',' + aG + ',' + aB + ',0.14)'; ctx.fillRect(-18, -14, 36, 9);
        ctx.strokeStyle = 'rgba(' + aR + ',' + aG + ',' + aB + ',' + (0.45 + 0.2 * pulse) + ')'; ctx.lineWidth = 1.4;
        ctx.beginPath(); ctx.moveTo(-8, -2); ctx.lineTo(-8, 8); ctx.lineTo(8, 8); ctx.lineTo(8, -2); ctx.stroke();
        ctx.beginPath(); ctx.moveTo(-8, -2); ctx.lineTo(0, -10); ctx.lineTo(8, -2); ctx.stroke();
        ctx.fillStyle = 'rgba(' + aR + ',' + aG + ',' + aB + ',' + (0.35 + 0.25 * pulse) + ')';
        ctx.beginPath(); ctx.arc(-5, 3, 2.2, 0, Math.PI * 2); ctx.fill(); ctx.beginPath(); ctx.arc(5, 3, 2.2, 0, Math.PI * 2); ctx.fill();
      } else if (kind === 'blockchain') {
        ctx.save(); var hexR = 16; ctx.beginPath(); for (var hi = 0; hi < 6; hi++) { var angle = Math.PI / 6 + hi * Math.PI / 3; var hx = Math.cos(angle) * hexR, hy = Math.sin(angle) * hexR; if (hi === 0) ctx.moveTo(hx, hy); else ctx.lineTo(hx, hy); } ctx.closePath(); ctx.fillStyle = tc.surface; ctx.fill(); ctx.strokeStyle = 'rgba(' + aR + ',' + aG + ',' + aB + ',' + (0.35 + 0.15 * pulse) + ')'; ctx.lineWidth = 1.5; ctx.stroke(); var innerR = 10; ctx.beginPath(); for (var hi = 0; hi < 6; hi++) { var angle = Math.PI / 6 + hi * Math.PI / 3; ctx.lineTo(Math.cos(angle) * innerR, Math.sin(angle) * innerR); } ctx.closePath(); ctx.strokeStyle = 'rgba(' + aR + ',' + aG + ',' + aB + ',0.2)'; ctx.lineWidth = 1; ctx.stroke(); ctx.fillStyle = 'rgba(' + aR + ',' + aG + ',' + aB + ',' + (0.5 + 0.3 * pulse) + ')'; ctx.font = 'bold 14px monospace'; ctx.textAlign = 'center'; ctx.textBaseline = 'middle'; ctx.fillText('\u25c8', 0, 0); ctx.restore();
      } else if (kind === 'standby' || kind === 'shelter') {
        ctx.fillStyle = tc.surface; ctx.strokeStyle = 'rgba(' + aR + ',' + aG + ',' + aB + ',0.35)'; ctx.lineWidth = 1.2;
        ctx.beginPath(); ctx.roundRect(-24, -12, 48, 24, 6); ctx.fill(); ctx.stroke();
        ctx.fillStyle = 'rgba(' + aR + ',' + aG + ',' + aB + ',0.16)';
        ctx.fillRect(-22, -10, 44, 7);
        ctx.fillStyle = 'rgba(' + aR + ',' + aG + ',' + aB + ',0.45)';
        ctx.beginPath(); ctx.arc(-10, 2, 3, 0, Math.PI * 2); ctx.fill();
        ctx.beginPath(); ctx.arc(0, 2, 3, 0, Math.PI * 2); ctx.fill();
        ctx.beginPath(); ctx.arc(10, 2, 3, 0, Math.PI * 2); ctx.fill();
        var sheltered = self.bots.filter(function(b) {
          var idle = !b.activeSkill && (b.animState === 'idle' || b.animState === 'walking');
          return idle && (b.currentStation === 'shelter' || b.currentStation === 'standby');
        }).length;
        var badge = String(sheltered);
        ctx.font = 'bold 9px ' + getComputedStyle(document.body).fontFamily;
        var badgeW = Math.max(14, ctx.measureText(badge).width + 8);
        ctx.fillStyle = 'rgba(' + aR + ',' + aG + ',' + aB + ',0.22)';
        ctx.beginPath(); ctx.roundRect(14, -22, badgeW, 12, 6); ctx.fill();
        ctx.strokeStyle = 'rgba(' + aR + ',' + aG + ',' + aB + ',0.45)';
        ctx.lineWidth = 1;
        ctx.beginPath(); ctx.roundRect(14, -22, badgeW, 12, 6); ctx.stroke();
        ctx.fillStyle = tc.text;
        ctx.textAlign = 'center';
        ctx.textBaseline = 'middle';
        ctx.fillText(badge, 14 + badgeW / 2, -16);
      } else {
        ctx.fillStyle = tc.surface; ctx.strokeStyle = 'rgba(' + aR + ',' + aG + ',' + aB + ',0.3)'; ctx.lineWidth = 1.5; ctx.beginPath(); ctx.roundRect(-22, -18, 44, 34, 4); ctx.fill(); ctx.stroke(); ctx.fillStyle = 'rgba(' + aR + ',' + aG + ',' + aB + ',0.1)'; ctx.fillRect(-22, -18, 44, 9); ctx.fillStyle = 'rgba(' + aR + ',' + aG + ',' + aB + ',0.25)'; ctx.beginPath(); ctx.arc(-17, -13.5, 1.5, 0, Math.PI * 2); ctx.fill(); ctx.beginPath(); ctx.arc(-12, -13.5, 1.5, 0, Math.PI * 2); ctx.fill(); ctx.strokeStyle = 'rgba(' + aR + ',' + aG + ',' + aB + ',' + (0.3 + 0.15 * pulse) + ')'; ctx.lineWidth = 1; ctx.beginPath(); ctx.arc(0, 3, 9, 0, Math.PI * 2); ctx.stroke(); ctx.beginPath(); ctx.ellipse(0, 3, 4, 9, 0, 0, Math.PI * 2); ctx.stroke(); ctx.beginPath(); ctx.moveTo(-9, 3); ctx.lineTo(9, 3); ctx.stroke();
      }
      ctx.fillStyle = tc.muted; ctx.font = '10px ' + getComputedStyle(document.body).fontFamily;
      ctx.textAlign = 'center'; ctx.textBaseline = 'top'; ctx.fillText(s.name, 0, 32);

      if (s.kind === 'Inference') {
        var activeModels = [];
        self.bots.forEach(function(b) {
          if (b.currentStation === s.id && b.animState === 'working' && b.model) {
            if (activeModels.indexOf(b.model) === -1) activeModels.push(b.model);
          }
        });
        if (activeModels.length > 0) {
          ctx.font = '8px ' + getComputedStyle(document.body).fontFamily;
          activeModels.forEach(function(m, mi) {
            var my = 44 + mi * 12;
            var mw = ctx.measureText(m).width + 8;
            ctx.fillStyle = 'rgba(' + aR + ',' + aG + ',' + aB + ',0.1)';
            ctx.beginPath(); ctx.roundRect(-mw / 2, my - 5, mw, 11, 3); ctx.fill();
            ctx.strokeStyle = 'rgba(' + aR + ',' + aG + ',' + aB + ',0.2)'; ctx.lineWidth = 0.5;
            ctx.beginPath(); ctx.roundRect(-mw / 2, my - 5, mw, 11, 3); ctx.stroke();
            ctx.fillStyle = 'rgba(' + aR + ',' + aG + ',' + aB + ',0.7)';
            ctx.fillText(m, 0, my + 3);
          });
        }
      }
      ctx.restore();
    });

    this.dataStreams.forEach(function(ds) {
      var p = Math.min(1, ds.progress);
      var x1 = ds.fromX * W, y1 = ds.fromY * H, x2 = ds.toX * W, y2 = ds.toY * H;
      var cx = x1 + (x2 - x1) * p, cy = y1 + (y2 - y1) * p;
      var alpha = ds.timer > 0.3 ? 0.6 : ds.timer / 0.3 * 0.6;
      ctx.save();
      ctx.setLineDash([3, 6]); ctx.lineDashOffset = -self.time * 40;
      ctx.strokeStyle = ds.color + '30'; ctx.lineWidth = 1;
      ctx.beginPath(); ctx.moveTo(x1, y1); ctx.lineTo(x2, y2); ctx.stroke();
      ctx.setLineDash([]);
      ctx.fillStyle = ds.color; ctx.globalAlpha = alpha;
      ctx.beginPath(); ctx.arc(cx, cy, 3, 0, Math.PI * 2); ctx.fill();
      ctx.beginPath(); ctx.arc(cx, cy, 6, 0, Math.PI * 2);
      ctx.fillStyle = ds.color + '20'; ctx.fill();
      if (ds.label) {
        var midX = (x1 + x2) / 2, midY = (y1 + y2) / 2 - 10;
        ctx.globalAlpha = alpha * 0.95;
        ctx.font = 'bold 8px ' + getComputedStyle(document.body).fontFamily;
        ctx.textAlign = 'center';
        var lw = ctx.measureText(ds.label).width + 8;
        ctx.fillStyle = tc.surface; ctx.beginPath(); ctx.roundRect(midX - lw / 2, midY - 6, lw, 12, 4); ctx.fill();
        ctx.strokeStyle = ds.color + '55'; ctx.lineWidth = 0.5; ctx.stroke();
        ctx.fillStyle = ds.color; ctx.fillText(ds.label, midX, midY + 2.5);
      }
      ctx.globalAlpha = 1;
      ctx.restore();
    });

    this.tethers.forEach(function(t) {
      var subordinateActive = !!t.to && (
        !!t.to.activeSkill ||
        self._isRuntimeActive(t.to.activity) ||
        t.to.animState === 'working' ||
        t.to.animState === 'walking' ||
        !!(t.to.currentStation && t.to.currentStation !== 'standby' && t.to.currentStation !== 'shelter')
      );
      if (!subordinateActive) return;
      var fromX = t.from.x * W;
      var fromY = t.from.y * H;
      var toX = t.to.x * W;
      var toY = t.to.y * H;
      ctx.save(); ctx.setLineDash([4, 6]); ctx.lineDashOffset = -self.time * 15;
      ctx.strokeStyle = t.color + '25'; ctx.lineWidth = 1;
      ctx.beginPath(); ctx.moveTo(fromX, fromY); ctx.lineTo(toX, toY); ctx.stroke();
      ctx.setLineDash([]); ctx.restore();
    });

    this.interactions.forEach(function(ix) {
      var ax = ix.botA.x * W, ay = ix.botA.y * H;
      var bx = ix.botB.x * W, by = ix.botB.y * H;
      var midX = (ax + bx) / 2, midY = (ay + by) / 2;
      ctx.save();
      ctx.strokeStyle = 'rgba(' + aR + ',' + aG + ',' + aB + ',0.2)';
      ctx.lineWidth = 2; ctx.setLineDash([2, 4]); ctx.lineDashOffset = -self.time * 30;
      ctx.beginPath(); ctx.moveTo(ax, ay); ctx.lineTo(bx, by); ctx.stroke();
      ctx.setLineDash([]);
      var speakerA = Math.sin(ix.phase * 2.5) > 0;
      var spkX = speakerA ? ax : bx, spkY = (speakerA ? ay : by) - 45;
      var bubW = 28, bubH = 16;
      ctx.fillStyle = 'rgba(' + aR + ',' + aG + ',' + aB + ',0.12)';
      ctx.beginPath(); ctx.roundRect(spkX - bubW / 2, spkY - bubH / 2, bubW, bubH, 6); ctx.fill();
      ctx.strokeStyle = 'rgba(' + aR + ',' + aG + ',' + aB + ',0.25)'; ctx.lineWidth = 1;
      ctx.beginPath(); ctx.roundRect(spkX - bubW / 2, spkY - bubH / 2, bubW, bubH, 6); ctx.stroke();
      ctx.beginPath(); ctx.moveTo(spkX - 3, spkY + bubH / 2);
      ctx.lineTo(spkX, spkY + bubH / 2 + 5); ctx.lineTo(spkX + 3, spkY + bubH / 2);
      ctx.fillStyle = 'rgba(' + aR + ',' + aG + ',' + aB + ',0.12)'; ctx.fill();
      var dotPhase = ix.bubbleTimer * 4;
      for (var di = 0; di < 3; di++) {
        var dotA = 0.3 + 0.7 * Math.max(0, Math.sin(dotPhase - di * 0.8));
        ctx.fillStyle = 'rgba(' + aR + ',' + aG + ',' + aB + ',' + dotA + ')';
        ctx.beginPath(); ctx.arc(spkX - 6 + di * 6, spkY, 2, 0, Math.PI * 2); ctx.fill();
      }
      ctx.restore();
    });

    var sortedBots = this.bots.slice().sort(function(a, b) { return a.y - b.y; });
    sortedBots.forEach(function(bot) { self.drawBot(ctx, bot, W, H); });
    if (this.selectedBotId) {
      var selected = this.bots.find(function(b) { return b.id === self.selectedBotId; });
      if (selected) this.drawTaskBubble(ctx, selected, W, H);
    }
  };

  WorkspaceEngine.prototype.drawBot = function(ctx, bot, W, H) {
    // Sheltered bots are hidden once they arrive at the shelter position.
    // While en route (animating back), they remain visible so the transition
    // looks smooth instead of bots vanishing mid-canvas.
    var isSheltered = (bot.currentStation === 'shelter' || bot.currentStation === 'standby') && !bot.activeSkill;
    if (isSheltered) {
      var dx = Math.abs(bot.x - (bot.targetX || bot.x));
      var dy = Math.abs(bot.y - (bot.targetY || bot.y));
      var arrived = dx < 0.01 && dy < 0.01;
      if (arrived) return; // fully at shelter — hide and show badge count
      // still walking back — keep drawing at reduced opacity
    }
    var tc = this._themeColors || { surface: '#081329', muted: '#9baad6', text: '#dee5ff', aR: 193, aG: 128, aB: 255 };
    var px = bot.x * W, py = bot.y * H, t = this.time, frame = bot.animFrame;
    var isAgent = (bot.role === 'agent');
    var scale = isAgent ? 1.0 : 0.7;
    var bodyW = isAgent ? 20 : 14, bodyH = isAgent ? 24 : 18;
    var walkBob = (bot.animState === 'walking') ? Math.sin(t * 12) * 2 : 0;
    ctx.save(); ctx.translate(px, py + walkBob); ctx.scale(scale, scale);
    if (this.selectedBotId === bot.id) {
      ctx.strokeStyle = 'rgba(' + tc.aR + ',' + tc.aG + ',' + tc.aB + ',0.65)';
      ctx.lineWidth = 1.5;
      ctx.beginPath();
      ctx.ellipse(0, bodyH + 4, bodyW * 0.95, 6, 0, 0, Math.PI * 2);
      ctx.stroke();
    }

    ctx.fillStyle = 'rgba(0,0,0,0.25)';
    ctx.beginPath(); ctx.ellipse(0, bodyH + 4, bodyW * 0.6, 3.5, 0, 0, Math.PI * 2); ctx.fill();
    ctx.fillStyle = bot.color; ctx.strokeStyle = bot.color + 'cc'; ctx.lineWidth = 1.5;
    ctx.beginPath(); ctx.roundRect(-bodyW, -bodyH * 0.3, bodyW * 2, bodyH, isAgent ? 4 : 8); ctx.fill(); ctx.stroke();
    ctx.fillStyle = 'rgba(0,0,0,0.2)'; ctx.fillRect(-bodyW + 3, -bodyH * 0.3 + 3, bodyW * 2 - 6, bodyH * 0.35);
    var lightColors = ['#22c55e', '#eab308', '#ef4444'];
    for (var li = 0; li < 3; li++) { var lx = -bodyW + 6 + li * 7, ly = -bodyH * 0.3 + 7; ctx.fillStyle = lightColors[li]; ctx.globalAlpha = 0.3 + 0.7 * bot.lights[li]; ctx.beginPath(); ctx.arc(lx, ly, 2, 0, Math.PI * 2); ctx.fill(); }
    ctx.globalAlpha = 1;

    if (isAgent) {
      var legOffset = (bot.animState === 'walking') ? Math.sin(t * 10) * 4 : 0;
      ctx.fillStyle = bot.color + 'bb';
      ctx.fillRect(-bodyW + 4, bodyH * 0.7 - 2, 7, 10 + (legOffset > 0 ? legOffset : 0));
      ctx.fillRect(bodyW - 11, bodyH * 0.7 - 2, 7, 10 + (legOffset < 0 ? -legOffset : 0));
      ctx.fillStyle = tc.surface;
      ctx.fillRect(-bodyW + 2, bodyH * 0.7 + 7 + Math.max(0, legOffset), 11, 4);
      ctx.fillRect(bodyW - 13, bodyH * 0.7 + 7 + Math.max(0, -legOffset), 11, 4);
    } else {
      var treadOffset = (bot.animState === 'walking') ? (frame * 3) % 12 : 0;
      ctx.fillStyle = tc.surface; ctx.beginPath(); ctx.roundRect(-bodyW - 2, bodyH * 0.5, bodyW * 2 + 4, 8, 4); ctx.fill();
      ctx.strokeStyle = bot.color + '88'; ctx.lineWidth = 1; ctx.stroke();
      ctx.strokeStyle = bot.color + '44';
      for (var ti = 0; ti < 5; ti++) { var tx = -bodyW + 2 + (ti * 9 + treadOffset) % (bodyW * 2); ctx.beginPath(); ctx.moveTo(tx, bodyH * 0.5 + 1); ctx.lineTo(tx, bodyH * 0.5 + 7); ctx.stroke(); }
    }

    if (bot.animState === 'working') {
      var armAngle = Math.sin(t * 4) * 0.3;
      ctx.save(); ctx.translate(bodyW + 2, 0); ctx.rotate(-0.5 + armAngle);
      ctx.fillStyle = bot.color + 'cc'; ctx.fillRect(0, -2, 14, 4);
      ctx.fillStyle = '#eab308'; ctx.beginPath(); ctx.arc(14, 0, 3, 0, Math.PI * 2); ctx.fill();
      if (frame % 2 === 0) {
        ctx.fillStyle = '#eab308';
        ctx.globalAlpha = 0.7;
        for (var si = 0; si < 3; si++) {
          var sa = ((frame + si * 5) * 0.9) % (Math.PI * 2);
          ctx.beginPath();
          ctx.arc(14 + Math.cos(sa) * 5, Math.sin(sa) * 5, 1.5, 0, Math.PI * 2);
          ctx.fill();
        }
        ctx.globalAlpha = 1;
      }
      ctx.restore();
    }

    ctx.save(); ctx.translate(0, -bodyH * 0.3); ctx.rotate(bot.headAngle);
    var headW = isAgent ? 18 : 14, headH = isAgent ? 16 : 12;
    ctx.fillStyle = bot.color + 'aa'; ctx.fillRect(-3, -4, 6, 5);
    ctx.fillStyle = bot.color; ctx.strokeStyle = bot.color + 'cc'; ctx.lineWidth = 1.5;
    ctx.beginPath();
    if (isAgent) { ctx.roundRect(-headW, -headH - 4, headW * 2, headH, 5); }
    else { ctx.arc(0, -headH * 0.5 - 2, headW * 0.85, 0, Math.PI * 2); }
    ctx.fill(); ctx.stroke();

    if (isAgent) {
      ctx.strokeStyle = bot.color + 'cc'; ctx.lineWidth = 2;
      ctx.beginPath(); ctx.moveTo(0, -headH - 4); ctx.lineTo(0, -headH - 14); ctx.stroke();
      var antGlow = 0.5 + 0.5 * Math.sin(t * 3);
      ctx.fillStyle = bot.color; ctx.globalAlpha = 0.5 + 0.5 * antGlow;
      ctx.beginPath(); ctx.arc(0, -headH - 15, 3, 0, Math.PI * 2); ctx.fill(); ctx.globalAlpha = 1;
      var eyeY = -headH * 0.5 - 2;
      if (bot.blinking) { ctx.fillStyle = tc.surface; ctx.fillRect(-headW + 4, eyeY - 1, headW - 6, 2); ctx.fillRect(3, eyeY - 1, headW - 6, 2); }
      else { ctx.fillStyle = tc.surface; ctx.beginPath(); ctx.arc(-headW * 0.4, eyeY, 4, 0, Math.PI * 2); ctx.fill(); ctx.beginPath(); ctx.arc(headW * 0.4, eyeY, 4, 0, Math.PI * 2); ctx.fill(); ctx.fillStyle = tc.text; ctx.beginPath(); ctx.arc(-headW * 0.4 + bot.headAngle * 3, eyeY, 2, 0, Math.PI * 2); ctx.fill(); ctx.beginPath(); ctx.arc(headW * 0.4 + bot.headAngle * 3, eyeY, 2, 0, Math.PI * 2); ctx.fill(); }
    } else {
      var visorY = -headH * 0.5 - 2, visorGlow = 0.6 + 0.4 * Math.sin(t * 2.5 + px * 0.01);
      ctx.fillStyle = tc.surface; ctx.beginPath(); ctx.roundRect(-headW * 0.6, visorY - 3, headW * 1.2, 6, 3); ctx.fill();
      ctx.fillStyle = bot.color; ctx.globalAlpha = visorGlow; ctx.beginPath(); ctx.roundRect(-headW * 0.5, visorY - 2, headW, 4, 2); ctx.fill(); ctx.globalAlpha = 1;
      var scanX = Math.sin(t * 3 + px * 0.01) * headW * 0.35;
      ctx.fillStyle = '#fff'; ctx.globalAlpha = 0.6; ctx.beginPath(); ctx.arc(scanX, visorY, 1.5, 0, Math.PI * 2); ctx.fill(); ctx.globalAlpha = 1;
    }
    ctx.restore();

    if (isAgent && bot.subordinates && bot.subordinates.length > 0) {
      ctx.save(); ctx.translate(0, -bodyH * 0.3 - (isAgent ? 24 : 18));
      ctx.strokeStyle = bot.color; ctx.lineWidth = 2;
      ctx.beginPath(); ctx.moveTo(-6, -18); ctx.lineTo(0, -22); ctx.lineTo(6, -18); ctx.stroke();
      ctx.restore();
    }

    ctx.save();
    var tagY = -bodyH * 0.3 - (isAgent ? 30 : 20) - (isAgent && bot.subordinates && bot.subordinates.length > 0 ? 10 : 0);
    ctx.font = 'bold 10px ' + getComputedStyle(document.body).fontFamily; ctx.textAlign = 'center';
    var nameW = ctx.measureText(bot.name).width + 12;
    ctx.globalAlpha = 0.88; ctx.fillStyle = tc.surface;
    ctx.beginPath(); ctx.roundRect(-nameW / 2, tagY - 7, nameW, 14, 7); ctx.fill();
    ctx.globalAlpha = 1; ctx.strokeStyle = bot.color + '66'; ctx.lineWidth = 1; ctx.stroke();
    ctx.fillStyle = tc.text; ctx.fillText(bot.name, 0, tagY + 3);
    var dotColor = bot.state === 'Running' ? '#22c55e' : (bot.state === 'Error' ? '#ef4444' : '#eab308');
    ctx.fillStyle = dotColor; ctx.beginPath(); ctx.arc(nameW / 2 - 4, tagY, 3, 0, Math.PI * 2); ctx.fill();
    ctx.restore();

    if (bot.activeSkill && bot.skillFade > 0) {
      ctx.save();
      var skillY = bodyH + (isAgent ? 16 : 12);
      ctx.globalAlpha = bot.skillFade * 0.85;
      ctx.font = '8px ' + getComputedStyle(document.body).fontFamily; ctx.textAlign = 'center';
      var sw = ctx.measureText(bot.activeSkill).width + 10;
      ctx.fillStyle = bot.color + '20';
      ctx.beginPath(); ctx.roundRect(-sw / 2, skillY - 5, sw, 12, 4); ctx.fill();
      ctx.strokeStyle = bot.color + '50'; ctx.lineWidth = 0.5; ctx.stroke();
      ctx.fillStyle = bot.color; ctx.globalAlpha = bot.skillFade * 0.9;
      ctx.fillText(bot.activeSkill, 0, skillY + 3);
      ctx.globalAlpha = 1; ctx.restore();
    }

    ctx.restore();
  };

  WorkspaceEngine.prototype.handleEvent = function(ev) {
    if (!ev || !ev.type) return;
    var self = this;
    function primaryBot() {
      return self.bots.find(function(b) { return b.role === 'agent'; }) || self.bots[0] || null;
    }
    function llmStation() {
      return self.stations.find(function(s) { return s.id === 'llm'; }) || null;
    }
    if (ev.type === 'agent_started' || ev.type === 'agent_stopped' || ev.type === 'agent_error') {
      var bot = this.bots.find(function(b) { return b.id === ev.agent_id; });
      if (bot) { bot.state = ev.type === 'agent_started' ? 'Running' : (ev.type === 'agent_stopped' ? 'Stopped' : 'Error'); }
    }
    if (ev.type === 'agent_moved') {
      var bot = this.bots.find(function(b) { return b.id === ev.agent_id; });
      var station = this.stations.find(function(s) { return s.id === ev.workstation; });
      if (bot && station) { bot.targetX = station.x; bot.targetY = station.y + 0.10; bot.currentStation = station.id; }
    }
    if (ev.type === 'agent_working') {
      var bot = this.bots.find(function(b) { return b.id === ev.agent_id; });
      if (bot) {
        if (ev.workstation) {
          var st = this.stations.find(function(s) { return s.id === ev.workstation; });
          if (st) { bot.targetX = st.x; bot.targetY = st.y + 0.10; bot.currentStation = st.id; }
        }
        bot.animState = 'working';
        if (ev.skill) bot.activeSkill = ev.skill;
      }
    }
    if (ev.type === 'agent_idle') {
      var bot = this.bots.find(function(b) { return b.id === ev.agent_id; });
      if (bot) { bot.animState = 'idle'; bot.activeSkill = null; bot.currentStation = 'standby'; }
      this.layoutStandby();
    }
    if (ev.type === 'skill_activated') {
      var bot = this.bots.find(function(b) { return b.id === ev.agent_id; });
      if (bot) { bot.activeSkill = ev.skill || ev.skill_name; bot.animState = 'working'; }
    }
    if (ev.type === 'a2a_interaction') {
      var botA = this.bots.find(function(b) { return b.id === ev.agent_a; });
      var botB = this.bots.find(function(b) { return b.id === ev.agent_b; });
      if (botA && botB) {
        var meetX = (botA.x + botB.x) / 2, meetY = (botA.y + botB.y) / 2;
        botA._savedTarget = { x: botA.targetX, y: botA.targetY };
        botB._savedTarget = { x: botB.targetX, y: botB.targetY };
        botA.targetX = meetX - 0.03; botA.targetY = meetY;
        botB.targetX = meetX + 0.03; botB.targetY = meetY;
        botA.animState = 'talking'; botB.animState = 'talking';
        self.interactions.push({ botA: botA, botB: botB, timer: ev.duration || 4, phase: 0, bubbleTimer: 0 });
      }
    }
    if (ev.type === 'stream_start') {
      var bot = primaryBot();
      var st = llmStation();
      if (bot) {
        if (st) { bot.targetX = st.x; bot.targetY = st.y + 0.10; bot.currentStation = st.id; }
        bot.animState = 'working';
        bot.activeSkill = ev.model || 'inference';
      }
    }
    if (ev.type === 'stream_chunk') {
      var bot = primaryBot();
      var st = llmStation();
      if (bot && st) {
        self.dataStreams.push({
          fromX: bot.x, fromY: bot.y, toX: st.x, toY: st.y,
          color: bot.color, timer: 0.8, progress: 0, label: ''
        });
      }
      if (ev.done) {
        var b = primaryBot();
        if (b) { b.animState = 'idle'; b.activeSkill = null; b.currentStation = 'standby'; }
        this.layoutStandby();
      }
    }
    if (ev.type === 'stream_end') {
      var bot = primaryBot();
      if (bot) { bot.animState = 'idle'; bot.activeSkill = null; bot.currentStation = 'standby'; }
      this.layoutStandby();
    }
  };

  // BUG-002: WebSocket connection with exponential backoff reconnection.
  // S-HIGH-2: Uses short-lived tickets instead of persistent API key in URL.
  App._wsReconnectDelay = 1000;
  App._wsReconnectTimer = null;
  App._bindWsEvents = function(ws) {
    ws.onopen = function() {
      App._wsReconnectDelay = 1000;
      var d = document.getElementById('ws-dot'); if (d) d.classList.remove('off');
      var l = document.getElementById('ws-label'); if (l) l.textContent = 'Connected';
      refreshSidebarIdentity();
    };
    ws.onclose = function() {
      var d = document.getElementById('ws-dot'); if (d) d.classList.add('off');
      var l = document.getElementById('ws-label'); if (l) l.textContent = 'Reconnecting...';
      clearTimeout(App._wsReconnectTimer);
      App._wsReconnectTimer = setTimeout(function() { App._connectWs(); }, App._wsReconnectDelay);
      App._wsReconnectDelay = Math.min(App._wsReconnectDelay * 2, 30000);
    };
    ws.onerror = function() {
      var d = document.getElementById('ws-dot'); if (d) d.classList.add('off');
    };
    ws.onmessage = function(ev) {
      try {
        var event = JSON.parse(ev.data);
        if (workspace && workspace.handleEvent) workspace.handleEvent(event);
        if (event && event.type === 'stream_start') {
          App._liveStreamTurn = {
            turn_id: event.turn_id || '',
            session_id: event.session_id || '',
            model: event.model || ''
          };
          if (App.page === 'context') {
            App.navigate('context');
          }
        } else if (event && event.type === 'stream_end') {
          if (!event.turn_id || (App._liveStreamTurn && App._liveStreamTurn.turn_id === event.turn_id)) {
            App._liveStreamTurn = null;
            if (App.page === 'context') {
              App.navigate('context');
            }
          }
        }
        if (event && (event.type === 'model_selection' || event.type === 'model_shift')) {
          if (App.page === 'efficiency') {
            App.navigate('efficiency');
          } else if (App.page === 'context' && App._ctxActiveTurn && event.turn_id === App._ctxActiveTurn.id) {
            App.navigate('context');
          }
        }
      } catch(e) {}
    };
  };
  App._connectWs = function() {
    try {
      if (App.ws && (App.ws.readyState === WebSocket.CONNECTING || App.ws.readyState === WebSocket.OPEN)) return;
      var proto = window.location.protocol === 'https:' ? 'wss:' : 'ws:';
      var wsUrl = proto + '//' + window.location.host + '/ws';
      if (!API_KEY) {
        // No auth configured — connect directly
        App.ws = new WebSocket(wsUrl);
        App._bindWsEvents(App.ws);
        return;
      }
      // Fetch a short-lived ticket, then connect with it
      fetch(BASE + '/api/ws-ticket', {
        method: 'POST',
        headers: authHeaders({ 'Accept': 'application/json' })
      }).then(function(r) {
        if (!r.ok) throw new Error('ticket fetch failed: ' + r.status);
        return r.json();
      }).then(function(data) {
        App.ws = new WebSocket(wsUrl + '?ticket=' + encodeURIComponent(data.ticket));
        App._bindWsEvents(App.ws);
      }).catch(function() {
        var d = document.getElementById('ws-dot'); if (d) d.classList.add('off');
        var l = document.getElementById('ws-label'); if (l) l.textContent = 'Reconnecting...';
        clearTimeout(App._wsReconnectTimer);
        App._wsReconnectTimer = setTimeout(function() { App._connectWs(); }, App._wsReconnectDelay);
        App._wsReconnectDelay = Math.min(App._wsReconnectDelay * 2, 30000);
      });
    } catch (err) { var d = document.getElementById('ws-dot'); if (d) d.classList.add('off'); var l = document.getElementById('ws-label'); if (l) l.textContent = 'No WebSocket'; }
  };
  App._connectWs();
  setInterval(refreshSidebarIdentity, 30000);

  startModelsBackgroundRefresh(App);
  App._loadAvailableModels({ nonBlocking: true, validationLevel: 'zero' }).catch(function() {});
  // BUG-FIX: call hint preference prompt only once on startup, not on every navigation
  ensureAuth().then(function() { maybePromptHintPreference(); }).catch(function() {});
  onHash();
