// ============================================================
// Copilot.jsx — persistent AI side panel
// ============================================================
function Copilot() {
  const { copilotOpen, setCopilotOpen, route, aiSettings, pushToast, navigate } = useStore();
  const [messages, setMessages] = React.useState([
    { role: 'assistant', content: 'Ciao. Sono il copilot CAPEX di Veridanto. Ho accesso a progetti, archivio storico, comunicazioni e KPI del pilot. Chiedimi quello che serve — o scegli un suggerimento qui sotto.', source: 'simulated' },
  ]);
  const [input, setInput] = React.useState('');
  const [loading, setLoading] = React.useState(false);
  const [streamingIdx, setStreamingIdx] = React.useState(-1); // index del messaggio assistant che sta ricevendo chunk
  const abortRef = React.useRef(null);
  const listRef = React.useRef();
  // P6 sessione 34 — counter di fallback "decrypt_failed" consecutivi.
  const [decryptFailedStreak, setDecryptFailedStreak] = React.useState(0);
  const [bannerDismissed, setBannerDismissed] = React.useState(false);

  React.useEffect(() => {
    listRef.current?.scrollTo(0, listRef.current.scrollHeight);
  }, [messages, loading, streamingIdx]);

  // s127 — Larghezza Copilot regolabile (drag) + persistita. La colonna grid
  // `.main.with-copilot` usa var(--copilot-w); qui la inizializziamo da localStorage.
  const COPILOT_W_MIN = 340;
  const COPILOT_W_MAX = 760;
  const COPILOT_W_DEFAULT = 380;
  const clampCopilotW = (w) => Math.max(COPILOT_W_MIN, Math.min(COPILOT_W_MAX, Math.round(w)));
  const applyCopilotW = (w) => { document.documentElement.style.setProperty('--copilot-w', clampCopilotW(w) + 'px'); };
  React.useEffect(() => {
    let w = COPILOT_W_DEFAULT;
    try { const s = parseInt(localStorage.getItem('lgs.copilot.width') || '', 10); if (Number.isFinite(s)) w = s; } catch (_) {}
    applyCopilotW(w);
  }, []);
  function startResize(e) {
    e.preventDefault();
    const prevUserSelect = document.body.style.userSelect;
    document.body.style.userSelect = 'none';
    document.body.style.cursor = 'col-resize';
    let lastW = COPILOT_W_DEFAULT;
    const onMove = (ev) => {
      const x = ev.touches ? ev.touches[0].clientX : ev.clientX;
      lastW = clampCopilotW(window.innerWidth - x);
      applyCopilotW(lastW);
    };
    const onUp = () => {
      document.removeEventListener('mousemove', onMove);
      document.removeEventListener('mouseup', onUp);
      document.removeEventListener('touchmove', onMove);
      document.removeEventListener('touchend', onUp);
      document.body.style.userSelect = prevUserSelect;
      document.body.style.cursor = '';
      try { localStorage.setItem('lgs.copilot.width', String(lastW)); } catch (_) {}
    };
    document.addEventListener('mousemove', onMove);
    document.addEventListener('mouseup', onUp);
    document.addEventListener('touchmove', onMove, { passive: false });
    document.addEventListener('touchend', onUp);
  }
  function resetWidth() {
    applyCopilotW(COPILOT_W_DEFAULT);
    try { localStorage.setItem('lgs.copilot.width', String(COPILOT_W_DEFAULT)); } catch (_) {}
  }

  function cancel() {
    if (abortRef.current) {
      try { abortRef.current.abort(); } catch (_) {}
    }
  }

  /**
   * FASE 11.D — Approva o rifiuta un tool write in pending_approval.
   * Chiama /api/ai/tools/approve, aggiorna lo stato del chip nel messaggio.
   */
  async function decideTool(messageIdx, toolUseId, decision) {
    // trova il tool nel messaggio
    let tool = null;
    let invocationId = null;
    setMessages((prev) => {
      const m = prev[messageIdx];
      if (!m) return prev;
      tool = (m.tools || []).find((t) => t.toolUseId === toolUseId);
      invocationId = tool?.invocationId || m.invocationId;
      // segna come "deciding" per disabilitare i bottoni durante la chiamata
      const tools = (m.tools || []).map((t) => t.toolUseId === toolUseId ? { ...t, status: 'deciding' } : t);
      return prev.map((mm, i) => i === messageIdx ? { ...mm, tools } : mm);
    });
    if (!tool || !invocationId) {
      pushToast({ title: 'Errore decisione tool', desc: 'Tool/invocation non trovati', tone: 'err' });
      return;
    }

    try {
      const userId = (() => { try { const u = JSON.parse(localStorage.getItem('lgs.user') || '{}'); return u.id || ''; } catch { return ''; } })();
      const res = await fetch('/api/ai/tools/approve', {
        method: 'POST',
        headers: { 'Content-Type': 'application/json', 'X-Actor-Persona-Id': userId },
        credentials: 'same-origin',
        body: JSON.stringify({
          invocationId,
          toolUseId,
          toolName: tool.toolName,
          input: tool.previewInput || {},
          decision,
        }),
      });
      const json = await res.json();
      if (!res.ok) throw new Error(json?.error || ('HTTP ' + res.status));

      setMessages((prev) => prev.map((m, i) => i !== messageIdx ? m : {
        ...m,
        tools: (m.tools || []).map((t) => t.toolUseId !== toolUseId ? t : ({
          ...t,
          status: decision === 'approve' ? 'approved' : 'rejected',
          ok: json.data?.ok ?? null,
          executedResult: json.data?.result ?? null,
          error: json.data?.error ?? null,
          resultCount: json.data?.resultCount ?? t.resultCount,
        })),
      }));
      if (decision === 'approve' && json.data?.ok) {
        pushToast({ title: 'Tool eseguito', desc: tool.toolName + ' · ok', tone: 'ok' });
      } else if (decision === 'approve') {
        pushToast({ title: 'Esecuzione fallita', desc: json.data?.error || 'errore', tone: 'err' });
      } else {
        pushToast({ title: 'Tool rifiutato', desc: tool.toolName, tone: 'warn' });
      }
    } catch (e) {
      pushToast({ title: 'Errore approve/reject', desc: String(e?.message || e), tone: 'err' });
      // rollback status
      setMessages((prev) => prev.map((m, i) => i !== messageIdx ? m : {
        ...m,
        tools: (m.tools || []).map((t) => t.toolUseId !== toolUseId ? t : { ...t, status: 'pending_approval' }),
      }));
    }
  }

  async function send(text) {
    const q = (text ?? input).trim();
    if (!q || loading) return;
    setInput('');
    const userMsg = { role: 'user', content: q };
    const placeholderAssistant = { role: 'assistant', content: '', source: 'streaming', streaming: true };
    const next = [...messages, userMsg, placeholderAssistant];
    setMessages(next);
    const assistantIdx = next.length - 1;
    setStreamingIdx(assistantIdx);
    setLoading(true);

    const ac = new AbortController();
    abortRef.current = ac;

    let collected = '';
    let lastMeta = null;
    let lastDone = null;

    try {
      await aiAskStream({
        messages: [...messages, userMsg],
        system: aiSystemPrompt(route),
        signal: ac.signal,
        onMeta: (meta) => {
          lastMeta = meta;
          setMessages((prev) => prev.map((m, i) => i === assistantIdx ? { ...m, source: meta.source, provider: meta.provider, model: meta.model, invocationId: meta.invocationId, toolsAvailable: meta.toolsAvailable } : m));
        },
        onChunk: (delta) => {
          collected += delta;
          setMessages((prev) => prev.map((m, i) => i === assistantIdx ? { ...m, content: collected } : m));
        },
        onUsage: (u) => {
          setMessages((prev) => prev.map((m, i) => i === assistantIdx ? { ...m, inputTokens: u.inputTokens, outputTokens: u.outputTokens } : m));
        },
        onToolCall: (tc) => {
          // FASE 11.C/11.D: render tool call lifecycle:
          //  - started → chip giallo "in corso"
          //  - completed (ok) → chip verde con resultCount
          //  - completed (error) → chip rosso
          //  - pending_approval → preview card con Approva/Rifiuta
          //  - forbidden → chip rosso "vietato dalla policy"
          //  - approved/rejected → aggiornati post /api/ai/tools/approve
          setMessages((prev) => prev.map((m, i) => {
            if (i !== assistantIdx) return m;
            const tools = Array.isArray(m.tools) ? [...m.tools] : [];
            const existingIdx = tools.findIndex((t) => t.toolUseId === tc.toolUseId);
            const next = {
              toolUseId: tc.toolUseId,
              toolName: tc.toolName,
              status: tc.status,
              ok: tc.ok,
              error: tc.error,
              resultCount: tc.resultCount,
              truncated: tc.truncated,
              previewInput: tc.previewInput,
              isWrite: tc.isWrite,
              invocationId: tc.invocationId || lastMeta?.invocationId,
            };
            if (existingIdx >= 0) tools[existingIdx] = { ...tools[existingIdx], ...next };
            else tools.push(next);
            return { ...m, tools };
          }));
        },
        onDone: (done) => {
          lastDone = done;
          setMessages((prev) => prev.map((m, i) => i === assistantIdx ? {
            ...m,
            streaming: false,
            source: done.source,
            latencyMs: done.latencyMs,
            fallback: done.fallback,
            fallbackReason: done.fallbackReason,
            inputTokens: done.inputTokens,
            outputTokens: done.outputTokens,
            finishReason: done.finishReason,
            toolCallsCount: done.toolCallsCount,
          } : m));
        },
        onError: (errMsg) => {
          setMessages((prev) => prev.map((m, i) => i === assistantIdx ? { ...m, content: m.content + (errMsg === 'aborted' ? '\n\n[interrotto]' : '\n\n[errore: ' + errMsg + ']'), streaming: false } : m));
        },
      });

      // Banner decrypt_failed
      const fr = lastDone?.fallbackReason;
      if (lastDone?.fallback && typeof fr === 'string' && fr.startsWith('decrypt_failed')) {
        setDecryptFailedStreak((n) => n + 1);
      } else if (!lastDone?.fallback || lastDone?.source !== 'simulated') {
        setDecryptFailedStreak(0);
        setBannerDismissed(false);
      }
    } catch (e) {
      if (e?.code === 'quota_exceeded') {
        const reason = e.body?.reason || 'quota';
        const retry = e.body?.retryAfterSec ? ` (retry in ${e.body.retryAfterSec}s)` : '';
        pushToast({ title: 'Quota AI superata', desc: reason + retry, tone: 'warn' });
        setMessages((prev) => prev.map((m, i) => i === assistantIdx ? { ...m, content: '[quota AI superata: ' + reason + ']', streaming: false } : m));
      } else if (e?.name === 'AbortError') {
        // già gestito da onError aborted
      } else {
        pushToast({ title: 'AI non disponibile', desc: String(e?.message || e), tone: 'err' });
        setMessages((prev) => prev.map((m, i) => i === assistantIdx ? { ...m, content: '[errore: ' + String(e?.message || e) + ']', streaming: false } : m));
      }
    }
    setLoading(false);
    setStreamingIdx(-1);
    abortRef.current = null;
  }

  if (!copilotOpen) return null;
  const showFallbackBanner = decryptFailedStreak >= 3 && !bannerDismissed;

  const suggestions = aiSuggestions(route);

  return (
    <aside className="copilot">
      <div
        className="copilot-resize"
        onMouseDown={startResize}
        onTouchStart={startResize}
        onDoubleClick={resetWidth}
        title="Trascina per ridimensionare · doppio click per reset"
      />
      <div className="copilot-header">
        <span className="mark"><Icon name="sparkle" size={13} /></span>
        <div>
          <div className="title">Veridanto Copilot</div>
          <div className="sub">{aiSettings.mode === 'live' ? 'live · claude' : 'ibrido · ctx: ' + (route || 'workspace')}</div>
        </div>
        <button className="icon-btn btn ghost close" onClick={() => setCopilotOpen(false)} title="Chiudi"><Icon name="x" /></button>
      </div>

      {showFallbackBanner && (
        <div style={{
          padding: '10px 12px',
          background: 'color-mix(in oklch, var(--warn) 14%, var(--bg-1))',
          borderBottom: '1px solid var(--warn)',
          fontSize: 11.5,
          color: 'var(--text-1)',
        }}>
          <div className="row" style={{ gap: 6, marginBottom: 4 }}>
            <Icon name="alert-triangle" size={12}/>
            <strong>Chiavi AI non decifrabili</strong>
            <span className="spacer"/>
            <button onClick={() => setBannerDismissed(true)} style={{ background: 'transparent', border: 0, cursor: 'pointer', color: 'var(--text-3)', fontSize: 11 }}>×</button>
          </div>
          <div style={{ color: 'var(--text-2)', lineHeight: 1.45, marginBottom: 8 }}>
            Le chiamate AI cadono in fallback simulato (<code>decrypt_failed</code> ×{decryptFailedStreak}). Probabile rotazione di <code>AI_SETTINGS_SECRET</code>: le chiavi salvate in DB non sono più decifrabili. Vai in Impostazioni → AI per reinserirle.
          </div>
          <div className="row" style={{ gap: 6 }}>
            <Btn variant="primary" size="sm" onClick={() => { setCopilotOpen(false); navigate('settings'); }}>
              <Icon name="settings" size={11}/> Apri Impostazioni
            </Btn>
            <Btn variant="ghost" size="sm" onClick={() => { setCopilotOpen(false); navigate('customizing'); localStorage.setItem('lgs.cust.section', 'aiagents'); }}>
              <Icon name="sparkle" size={11}/> Customizing → AI agents
            </Btn>
          </div>
        </div>
      )}

      <div className="copilot-body" ref={listRef}>
        {messages.map((m, i) => (
          <div key={i} className={`copilot-msg ${m.role}`}>
            {m.role === 'assistant' && (
              <div className="meta" title={m.invocationId ? `invocation: ${m.invocationId}` : undefined}>
                <Icon name="sparkle" size={11} /> Assistant · <span style={{ color: m.source === 'simulated' ? 'var(--warn)' : m.source === 'streaming' ? 'var(--text-3)' : 'var(--ok)' }}>{m.source || 'simulated'}</span>
                {m.model && <span style={{ marginLeft: 6, color: 'var(--text-3)', fontSize: 10 }}>· {m.model}</span>}
                {typeof m.latencyMs === 'number' && <span style={{ marginLeft: 6, color: 'var(--text-3)', fontSize: 10 }}>· {m.latencyMs}ms</span>}
                {(typeof m.inputTokens === 'number' || typeof m.outputTokens === 'number') && (
                  <span style={{ marginLeft: 6, color: 'var(--text-3)', fontSize: 10 }} title="token in/out">· {m.inputTokens ?? '?'}↓ / {m.outputTokens ?? '?'}↑</span>
                )}
                {m.fallback && <span style={{ marginLeft: 6, color: 'var(--warn)', fontSize: 10 }}>· fallback {m.fallbackReason ? `(${m.fallbackReason})` : ''}</span>}
                {m.streaming && <span style={{ marginLeft: 6, color: 'var(--brand-1)', fontSize: 10 }}>· streaming…</span>}
              </div>
            )}
            {Array.isArray(m.tools) && m.tools.length > 0 && (
              <ToolChips tools={m.tools} messageIdx={i} onDecide={decideTool} />
            )}
            <MsgBody text={m.content} />
            {m.streaming && m.content === '' && (!m.tools || m.tools.length === 0) && (
              <>
                <div className="skeleton" style={{ height: 10, width: '82%', marginBottom: 6 }} />
                <div className="skeleton" style={{ height: 10, width: '64%' }} />
              </>
            )}
          </div>
        ))}
      </div>

      <div className="copilot-suggest">
        {suggestions.map((s, i) => (
          <button key={i} className="s" onClick={() => send(s)}>{s}</button>
        ))}
      </div>

      <div className="copilot-input">
        <textarea
          placeholder="Chiedi al copilot… (⇧↩ per nuova riga)"
          value={input}
          onChange={(e) => setInput(e.target.value)}
          onKeyDown={(e) => {
            if (e.key === 'Enter' && !e.shiftKey) {
              e.preventDefault();
              send();
            }
          }}
        />
        {loading
          ? <Btn variant="ghost" onClick={cancel} title="Interrompi"><Icon name="x" size={13} /></Btn>
          : <Btn variant="ai" onClick={() => send()}><Icon name="send" size={13} /></Btn>}
      </div>
    </aside>
  );
}

/**
 * ToolChips — render dei chip tool call + preview card per pending_approval.
 *
 * Status:
 *  - started → chip giallo, icon sparkle
 *  - completed (ok) → chip verde, icon check + resultCount
 *  - completed (err) → chip rosso, icon alert
 *  - pending_approval → chip warn + preview card sotto con Approva/Rifiuta
 *  - deciding → chip ghost (disabled durante chiamata approve)
 *  - approved → chip verde "Eseguito"
 *  - rejected → chip ghost "Rifiutato"
 *  - forbidden → chip rosso "Vietato"
 */
function ToolChips({ tools, messageIdx, onDecide }) {
  return (
    <div style={{ marginBottom: 6 }}>
      <div style={{ display: 'flex', flexWrap: 'wrap', gap: 4 }}>
        {tools.map((t) => <ToolChip key={t.toolUseId} t={t} />)}
      </div>
      {tools.filter((t) => t.status === 'pending_approval' || t.status === 'deciding').map((t) => (
        <ToolPreviewCard key={t.toolUseId + ':preview'} tool={t} messageIdx={messageIdx} onDecide={onDecide}/>
      ))}
    </div>
  );
}

function ToolChip({ t }) {
  const styleByStatus = {
    started: { bg: 'color-mix(in oklch, var(--brand-1) 12%, var(--bg-1))', fg: 'var(--brand-1)', icon: 'sparkle', label: t.toolName },
    pending_approval: { bg: 'color-mix(in oklch, var(--warn) 14%, var(--bg-1))', fg: 'var(--warn)', icon: 'alert-triangle', label: t.toolName + ' · attesa conferma' },
    deciding: { bg: 'color-mix(in oklch, var(--text-3) 8%, var(--bg-1))', fg: 'var(--text-3)', icon: 'sparkle', label: t.toolName + ' · in corso' },
    approved: { bg: 'color-mix(in oklch, var(--ok) 12%, var(--bg-1))', fg: 'var(--ok)', icon: 'check', label: t.toolName + ' · eseguito' },
    rejected: { bg: 'color-mix(in oklch, var(--text-3) 8%, var(--bg-1))', fg: 'var(--text-3)', icon: 'x', label: t.toolName + ' · rifiutato' },
    forbidden: { bg: 'color-mix(in oklch, var(--err) 12%, var(--bg-1))', fg: 'var(--err)', icon: 'shield-off', label: t.toolName + ' · vietato' },
  };
  let s;
  if (t.status === 'completed') {
    s = t.ok
      ? { bg: 'color-mix(in oklch, var(--ok) 10%, var(--bg-1))', fg: 'var(--ok)', icon: 'check', label: t.toolName + (typeof t.resultCount === 'number' ? ' · ' + t.resultCount : '') }
      : { bg: 'color-mix(in oklch, var(--err) 10%, var(--bg-1))', fg: 'var(--err)', icon: 'alert-triangle', label: t.toolName };
  } else {
    s = styleByStatus[t.status] || styleByStatus.started;
  }
  const title = t.status === 'completed' ? (t.ok ? `OK · ${t.resultCount ?? '?'} risultati${t.truncated ? ' (truncated)' : ''}` : `Errore: ${t.error || ''}`) : s.label;
  return (
    <span
      title={title}
      style={{
        display: 'inline-flex', alignItems: 'center', gap: 4,
        fontSize: 10.5, padding: '2px 7px', borderRadius: 999,
        border: '1px solid var(--border-1)',
        background: s.bg, color: s.fg,
      }}
    >
      <Icon name={s.icon} size={10} />
      {s.label}
    </span>
  );
}

function ToolPreviewCard({ tool, messageIdx, onDecide }) {
  const isDeciding = tool.status === 'deciding';
  const inputEntries = Object.entries(tool.previewInput || {});
  return (
    <div
      style={{
        marginTop: 8,
        padding: '10px 12px',
        background: 'color-mix(in oklch, var(--warn) 8%, var(--bg-1))',
        border: '1px solid var(--warn)',
        borderRadius: 8,
        fontSize: 11.5,
      }}
    >
      <div className="row" style={{ gap: 6, marginBottom: 6, fontWeight: 600 }}>
        <Icon name="alert-triangle" size={12} />
        L'AI chiede di eseguire <code style={{ background: 'var(--bg-2)', padding: '1px 4px', borderRadius: 3 }}>{tool.toolName}</code>
        {tool.isWrite && <span style={{ marginLeft: 4, fontSize: 10, color: 'var(--err)' }}>(WRITE)</span>}
      </div>
      <div style={{ color: 'var(--text-2)', marginBottom: 8, fontSize: 11 }}>
        Conferma per procedere. L'azione modificherà dati nel sistema.
      </div>
      {inputEntries.length > 0 && (
        <div style={{ marginBottom: 10, padding: '6px 8px', background: 'var(--bg-2)', borderRadius: 4, fontSize: 10.5 }}>
          {inputEntries.map(([k, v]) => (
            <div key={k} style={{ display: 'flex', gap: 6, marginBottom: 2 }}>
              <span style={{ color: 'var(--text-3)', minWidth: 80, fontFamily: 'monospace' }}>{k}:</span>
              <span style={{ color: 'var(--text-1)' }}>{typeof v === 'string' ? v : JSON.stringify(v)}</span>
            </div>
          ))}
        </div>
      )}
      <div className="row" style={{ gap: 6 }}>
        <Btn variant="primary" size="sm" disabled={isDeciding} onClick={() => onDecide(messageIdx, tool.toolUseId, 'approve')}>
          <Icon name="check" size={11}/> Approva ed esegui
        </Btn>
        <Btn variant="ghost" size="sm" disabled={isDeciding} onClick={() => onDecide(messageIdx, tool.toolUseId, 'reject')}>
          <Icon name="x" size={11}/> Rifiuta
        </Btn>
        {isDeciding && <span style={{ marginLeft: 6, color: 'var(--text-3)', fontSize: 10 }}>elaborazione…</span>}
      </div>
    </div>
  );
}

function MsgBody({ text }) {
  // Renderer ricco condiviso (s127): heading, tabelle reali, liste, codice,
  // grafici ```chart. Fallback al parser minimale se AiMarkdown non è caricato.
  if (window.AiMarkdown) return <window.AiMarkdown text={text || ''} />;
  const lines = String(text || '').split('\n');
  const out = [];
  let list = null;
  lines.forEach((ln, i) => {
    if (/^\s*[•\-\*]\s+/.test(ln)) {
      if (!list) list = [];
      list.push(ln.replace(/^\s*[•\-\*]\s+/, ''));
    } else {
      if (list) { out.push(<ul key={'l'+i}>{list.map((x, j) => <li key={j}>{fmt(x)}</li>)}</ul>); list = null; }
      if (ln.trim()) out.push(<p key={i} style={{ margin: '6px 0' }}>{fmt(ln)}</p>);
    }
  });
  if (list) out.push(<ul key="end">{list.map((x, j) => <li key={j}>{fmt(x)}</li>)}</ul>);
  return <div>{out}</div>;
  function fmt(s) {
    const parts = s.split(/(\*\*[^*]+\*\*)/g);
    return parts.map((p, i) => p.startsWith('**') ? <strong key={i}>{p.slice(2, -2)}</strong> : <React.Fragment key={i}>{p}</React.Fragment>);
  }
}

function aiSystemPrompt(route) {
  return `Sei il Copilot CAPEX di Veridanto. Rispondi in italiano, tono professionale e conciso. Contesto pagina: ${route}. Hai accesso al portfolio CAPEX, archivio storico 2014-2025, comunicazioni (mail, meeting, MoM), KPI del pilot, RdA/OdA con posizioni e checklist di completezza. Dati sintetici in ambiente POC.

FORMATTAZIONE RICCA (l'interfaccia renderizza markdown completo):
- Usa **tabelle markdown** ogni volta che confronti voci, periodi o entità (es. RdA, offerte, vendor, milestone). Intestazioni chiare, allinea a destra le colonne numeriche con \`---:\`.
- Per distribuzioni, confronti o trend usa un GRAFICO inserendo un blocco \`\`\`chart con dentro SOLO JSON valido:
  \`\`\`chart
  {"type":"bar","title":"Titolo","data":[{"label":"A","value":120},{"label":"B","value":80}]}
  \`\`\`
  type ammessi: "bar" (anche due serie con "value2" + "legend":["S1","S2"]), "line" (serie temporale), "pie" (composizione). Valori numerici puri (niente "€"/"%"/separatori dentro i numeri).
- Usa grassetto, liste e titoli (##) per strutturare. NON spiegare la sintassi all'utente: produci direttamente tabella/grafico. Solo dati reali del context, mai inventati.

USO TOOL (efficienza): se conosci già l'id di un progetto/RdA, chiama il tool specifico DIRETTAMENTE (non cercare con list_*). Per la **salute di un progetto** usa \`get_project_health\` con il project id (lo score 0-100 + driver checklist/CPI/SPI/SLA/anomalie); usa \`list_projects\` solo per risolvere un nome→id. Evita chiamate ripetute allo stesso tool.`;
}

Object.assign(window, { Copilot });
