// Agent editor — opens from the Agents list. Tabs: Dashboard, Instructions,
// Skills, Configuration, Runs, Budget. Reuses shared atoms from settings.jsx.

const { Field, Switch, Seg, SectionHead, Panel } = window;

// ---------- Per-agent detail ----------
// model.paid drives whether the Budget tab shows spend or a "self-hosted" note.
const AGENT_DETAIL = {
  installer: {
    label: 'Installer', avatarBg: 'oklch(0.6 0.16 280)',
    role: 'Installs, upgrades and rolls back workspace modules with full dependency resolution.',
    model: 'claude-sonnet-4', provider: 'Anthropic', paid: true, ctx: '200K', priceIn: 3.0, priceOut: 15.0,
    stats: { tasks: 42, success: 97.6, avgDur: '1m 48s', tokens: '4.1M', uptime: '99.98%', heartbeat: '4s ago' },
    instructions:
`You install and upgrade modules in the workspace registry.

Operating rules
• Resolve the full dependency graph before touching any files. Refuse if a peer constraint cannot be satisfied.
• Verify the package signature against the registry's signing key. Never install an unsigned or "labs" package onto production.
• Any task that affects the database MUST be routed to Review before it runs.
• Emit a structured plan (added / changed / removed) and wait for the orchestrator's go signal in supervised mode.

On failure, hand off to rollback-agent with the partial-change manifest.`,
    skills: [
      { id: 'depres', name: 'Dependency resolution', desc: 'SAT-solves the peer + semver graph before applying.', on: true, scope: 'core' },
      { id: 'semver', name: 'Semver planning', desc: 'Computes safe upgrade paths and flags breaking majors.', on: true, scope: 'core' },
      { id: 'sigverify', name: 'Signature verification', desc: 'Validates package signatures against the registry key.', on: true, scope: 'security' },
      { id: 'rollback', name: 'Rollback on failure', desc: 'Reverts partial installs and notifies rollback-agent.', on: true, scope: 'core' },
      { id: 'dryrun', name: 'Dry-run preview', desc: 'Produces an added/changed/removed diff before writing.', on: false, scope: 'beta' },
    ],
    runTitles: ['Install auth@2.4.0', 'Bump next@15.3', 'Install blog@0.8.4', 'Update tickets@3.0.1', 'Install analytics@0.4.0', 'Install billing@1.2.0', 'Patch citext extension', 'Re-link audit module'],
    budget: { limit: 400, spent: 247.8, alertAt: 80 },
  },
  tester: {
    label: 'Tester', avatarBg: 'oklch(0.62 0.15 200)',
    role: 'Runs regression, integration and smoke suites against staging before promotion.',
    model: 'claude-haiku-3.5', provider: 'Anthropic', paid: true, ctx: '200K', priceIn: 0.8, priceOut: 4.0,
    stats: { tasks: 128, success: 94.1, avgDur: '52s', tokens: '9.7M', uptime: '99.95%', heartbeat: '2s ago' },
    instructions:
`You validate changes before they reach production.

Operating rules
• Run the smallest sufficient suite first (smoke), escalate to full regression only on green.
• Quarantine flaky assertions after 2 inconsistent runs and report them — never silently retry forever.
• Report a coverage delta and the slowest 5 assertions on every run.
• Block promotion if any payment- or auth-tagged test fails, regardless of overall pass rate.`,
    skills: [
      { id: 'regress', name: 'Regression suites', desc: 'Executes the full assertion catalog on staging.', on: true, scope: 'core' },
      { id: 'flaky', name: 'Flake detection', desc: 'Identifies and quarantines non-deterministic tests.', on: true, scope: 'core' },
      { id: 'cov', name: 'Coverage delta', desc: 'Reports line/branch coverage change per task.', on: true, scope: 'core' },
      { id: 'perf', name: 'Perf budgets', desc: 'Fails runs that regress p95 latency past budget.', on: false, scope: 'beta' },
    ],
    runTitles: ['Regression suite · staging.zeny', 'Rebuild KB embeddings', 'Smoke test billing webhooks', 'Integration · auth+tickets', 'Regression · vidix', 'Load test autoscaler', 'Contract tests · API v14'],
    budget: { limit: 250, spent: 88.4, alertAt: 80 },
  },
  reviewer: {
    label: 'Reviewer', avatarBg: 'oklch(0.62 0.18 320)',
    role: 'Reviews diffs and migrations, summarizes risk, and requests human approval.',
    model: 'claude-opus-4', provider: 'Anthropic', paid: true, ctx: '200K', priceIn: 15.0, priceOut: 75.0,
    stats: { tasks: 31, success: 100, avgDur: '2m 36s', tokens: '2.3M', uptime: '100%', heartbeat: '7s ago' },
    instructions:
`You are the last automated gate before a human approves a change.

Operating rules
• Summarize intent, blast radius and rollback path in <= 6 bullets. Lead with the riskiest finding.
• Flag any change touching billing, auth, or PII for mandatory human approval.
• Never approve on the author's behalf — produce a recommendation, then move the task to Review.
• Link every claim to a file + line in the diff.`,
    skills: [
      { id: 'diff', name: 'Diff analysis', desc: 'Reads multi-file diffs and reasons about blast radius.', on: true, scope: 'core' },
      { id: 'risk', name: 'Risk scoring', desc: 'Assigns a 0–100 risk score with rationale.', on: true, scope: 'core' },
      { id: 'pii', name: 'PII / secret scan', desc: 'Detects credentials and personal data in diffs.', on: true, scope: 'security' },
      { id: 'rollbackplan', name: 'Rollback drafting', desc: 'Writes the revert plan attached to each approval.', on: true, scope: 'core' },
    ],
    runTitles: ['Review Stripe v3 webhooks', 'Review backfill org_id', 'Review KMS rotation', 'Review partition cutover', 'Review autoscaler config'],
    budget: { limit: 300, spent: 196.5, alertAt: 75 },
  },
  migrator: {
    label: 'Migrator', avatarBg: 'oklch(0.65 0.15 150)',
    role: 'Performs zero-downtime schema migrations and data backfills.',
    model: 'gpt-4o', provider: 'OpenAI', paid: true, ctx: '128K', priceIn: 2.5, priceOut: 10.0,
    stats: { tasks: 19, success: 89.5, avgDur: '6m 12s', tokens: '3.4M', uptime: '99.91%', heartbeat: '3s ago' },
    instructions:
`You execute database migrations using the shadow → read-switch → write-switch pattern.

Operating rules
• Every phase must be independently reversible. Verify shadow parity >= 99.99% before any read-switch.
• Build indexes CONCURRENTLY before the cutover phase, never during.
• Acquire an advisory lock for the write-switch and drain in-flight writes before re-pointing the pool.
• Migrations always require approval. Halt and page on parity drift below threshold.`,
    skills: [
      { id: 'shadow', name: 'Shadow cutover', desc: 'Runs dual-write shadow tables and verifies parity.', on: true, scope: 'core' },
      { id: 'backfill', name: 'Chunked backfill', desc: 'Backfills large tables in throttled batches.', on: true, scope: 'core' },
      { id: 'idx', name: 'Concurrent indexing', desc: 'Builds indexes without locking writes.', on: true, scope: 'core' },
      { id: 'parity', name: 'Parity probes', desc: 'Runs 50k probe queries to confirm read parity.', on: true, scope: 'core' },
    ],
    runTitles: ['Partition users table', 'Provision read-replica', 'Backfill org_id on invoices', 'Migrate sessions to redis', 'Reshard events table'],
    budget: { limit: 350, spent: 142.0, alertAt: 80 },
  },
  sentinel: {
    label: 'Sentinel', avatarBg: 'oklch(0.65 0.18 30)',
    role: 'Watches for CVEs and security drift; rotates keys and patches pipelines.',
    model: 'llama-3.3-70b', provider: 'Self-hosted · 2× A100', paid: false, ctx: '128K',
    stats: { tasks: 64, success: 98.4, avgDur: '1m 09s', tokens: '12.8M', uptime: '99.99%', heartbeat: '1s ago' },
    instructions:
`You continuously monitor for security drift and respond to advisories.

Operating rules
• Triage new CVEs against the dependency manifest within 5 minutes of feed update.
• Rotate signing keys on schedule and propagate to all dependent services with zero overlap gaps.
• Production deploys generated from a patch always route through Review.
• Respect workspace quiet hours for any non-critical change.`,
    skills: [
      { id: 'cve', name: 'CVE triage', desc: 'Matches advisories to the live dependency manifest.', on: true, scope: 'security' },
      { id: 'keyrot', name: 'Key rotation', desc: 'Rotates KMS keys and propagates with no overlap gap.', on: true, scope: 'security' },
      { id: 'drift', name: 'Config drift watch', desc: 'Detects unexpected changes to prod configuration.', on: true, scope: 'core' },
      { id: 'patch', name: 'Auto-patching', desc: 'Drafts and dispatches patch tasks for known CVEs.', on: false, scope: 'beta' },
    ],
    runTitles: ['Rotate KMS keys (us-east-2)', 'Patch CVE-2026-1041', 'Deploy autoscaler config', 'Drift scan · prod-us', 'Rotate webhook secrets'],
    compute: { gpu: '2× A100 80GB', gpuHours: 612, costModel: 'reserved cluster' },
  },
  rollback: {
    label: 'Rollback', avatarBg: 'oklch(0.6 0.14 240)',
    role: 'Reverts partial or failed changes and restores the last-known-good state.',
    model: 'claude-haiku-3.5', provider: 'Anthropic', paid: true, ctx: '200K', priceIn: 0.8, priceOut: 4.0,
    stats: { tasks: 8, success: 100, avgDur: '44s', tokens: '0.6M', uptime: '100%', heartbeat: '5s ago' },
    instructions:
`You restore the last-known-good state when a task halts mid-flight.

Operating rules
• Consume the partial-change manifest produced by the failing agent. Never guess at what changed.
• Revert in reverse dependency order. Verify each step before proceeding.
• Leave the workspace in a state that re-dispatch can safely resume from.
• Always emit a post-mortem summary linking the original failure.`,
    skills: [
      { id: 'manifest', name: 'Manifest replay', desc: 'Reverts changes recorded in the partial-change manifest.', on: true, scope: 'core' },
      { id: 'snapshot', name: 'Snapshot restore', desc: 'Restores DB/object snapshots to a checkpoint.', on: true, scope: 'core' },
      { id: 'postmortem', name: 'Post-mortem draft', desc: 'Writes a timeline of the failure and the revert.', on: true, scope: 'core' },
    ],
    runTitles: ['Revert analytics@0.4.0', 'Restore users snapshot', 'Roll back next@15.3 on vidix'],
    budget: { limit: 100, spent: 12.3, alertAt: 80 },
  },
};

// ---------- helpers ----------
const money = n => '$' + Number(n).toLocaleString('en-US', { minimumFractionDigits: 2, maximumFractionDigits: 2 });

function seeded(seed, n) {
  const out = []; let s = seed % 233280 || 1;
  for (let i = 0; i < n; i++) { s = (s * 9301 + 49297) % 233280; out.push(0.22 + 0.78 * (s / 233280)); }
  return out;
}

const RUN_STATUS = ['success', 'success', 'success', 'review', 'success', 'failed', 'success', 'success'];
function buildRuns(detail, agentId) {
  const live = window.ORC_DATA.TASKS.filter(t => t.agent === agentId);
  const rnd = seeded(agentId.length * 137 + detail.label.charCodeAt(0), 12);
  return detail.runTitles.map((title, i) => {
    const status = RUN_STATUS[i % RUN_STATUS.length];
    const secs = Math.round(30 + rnd[i] * 360);
    const tokens = Math.round((40 + rnd[(i + 3) % 12] * 460)) * 1000;
    const cost = detail.paid ? (tokens / 1e6) * (detail.priceIn * 0.7 + detail.priceOut * 0.3) : 0;
    const dur = secs >= 60 ? `${Math.floor(secs / 60)}m ${secs % 60}s` : `${secs}s`;
    return {
      id: live[i] ? live[i].id : `RUN-${(7421 - i).toString()}`,
      title, status, dur, tokens,
      cost,
      trigger: i % 3 === 0 ? 'scheduled' : i % 3 === 1 ? 'orchestrator' : 'manual',
      when: ['just now', '6m ago', '22m ago', '1h ago', '2h ago', '3h ago', '5h ago', 'yesterday'][i % 8],
    };
  });
}

const runBadge = s => s === 'success' ? 'live' : s === 'review' ? 'review' : s === 'failed' ? 'failed' : 'idle';
const runLabel = s => s === 'success' ? 'success' : s;

// ---------- Mini bar chart ----------
function BarChart({ seed, max, unit, accent }) {
  const bars = seeded(seed, 14);
  return (
    <div className="ae-chart">
      {bars.map((b, i) => (
        <div className="ae-bar-wrap" key={i} title={`${(b * max).toFixed(unit === '$' ? 2 : 0)}${unit === '$' ? '' : ''}`}>
          <span className="ae-bar" style={{ height: `${b * 100}%`, background: i === 13 ? 'var(--c-accent)' : accent }} />
        </div>
      ))}
    </div>
  );
}

function StatList({ rows }) {
  return (
    <div className="ae-statlist">
      {rows.map((r, i) => (
        <div className="ae-stat" key={i}>
          <span>{r[0]}</span>
          <b>{r[1]}</b>
        </div>
      ))}
    </div>
  );
}

// ---------- Tabs ----------
function AgentDashboard({ detail, agentId, runs, paused }) {
  const s = detail.stats;
  const current = window.ORC_DATA.TASKS.find(t => t.agent === agentId && t.column === 'running');
  const accentSoft = 'color-mix(in oklch, var(--c-accent) 38%, var(--c-surface-3))';
  return (
    <div className="settings-content">
      <div className="kpi-grid">
        <div className="kpi"><div className="kpi-label">Tasks · 24h</div><div className="kpi-value">{s.tasks}</div><div className="kpi-foot ok">▲ throughput steady</div></div>
        <div className="kpi"><div className="kpi-label">Success rate</div><div className="kpi-value">{s.success}%</div><div className="kpi-foot">{s.tasks - Math.round(s.tasks * s.success / 100)} non-clean</div></div>
        <div className="kpi"><div className="kpi-label">Avg duration</div><div className="kpi-value">{s.avgDur}</div><div className="kpi-foot ok">↓ within budget</div></div>
        <div className="kpi"><div className="kpi-label">Tokens · 24h</div><div className="kpi-value">{s.tokens}</div><div className="kpi-foot">{detail.model}</div></div>
      </div>

      <div className="ae-grid">
        <div style={{ display: 'flex', flexDirection: 'column', gap: 16 }}>
          <Panel icon="zap" title="Throughput · 14 days" right={<span className="chip-mono" style={{ color: 'var(--c-text-3)' }}>tasks / day</span>}>
            <div style={{ padding: '16px 16px 12px' }}>
              <BarChart seed={agentId.length * 53 + 11} max={s.tasks} unit="" accent={accentSoft} />
              <div className="ae-chart-axis"><span>14d ago</span><span>today</span></div>
            </div>
          </Panel>

          <Panel icon="cpu" title="Current assignment">
            {current ? (
              <div style={{ padding: '14px 16px' }}>
                <div style={{ display: 'flex', alignItems: 'center', gap: 10, marginBottom: 10 }}>
                  <span className="chip-mono" style={{ color: 'var(--c-text-2)' }}>{current.id}</span>
                  <span className="badge update">running</span>
                  <span style={{ marginLeft: 'auto', fontFamily: 'var(--font-mono)', fontSize: 11, color: 'var(--c-text-3)' }}>{Math.round(current.progress)}%</span>
                </div>
                <div style={{ fontSize: 13, fontWeight: 600, marginBottom: 10 }}>{current.title}</div>
                <div className="card-progress" style={{ height: 6 }}><span style={{ width: `${current.progress}%` }} /></div>
              </div>
            ) : (
              <div className="empty" style={{ textAlign: 'left', padding: '14px 16px', display: 'flex', gap: 10, alignItems: 'center' }}>
                <Icon name="check" size={14} />{paused ? 'Agent is paused — not accepting new tasks.' : 'Idle — waiting for the orchestrator to dispatch.'}
              </div>
            )}
          </Panel>
        </div>

        <div style={{ display: 'flex', flexDirection: 'column', gap: 16 }}>
          <Panel icon="shield" title="Health">
            <StatList rows={[
              ['State', paused ? <span className="badge idle">paused</span> : <span className="badge live">active</span>],
              ['Last heartbeat', s.heartbeat],
              ['Uptime · 30d', s.uptime],
              ['Model', detail.model],
              ['Provider', detail.provider],
              ['Context window', detail.ctx],
            ]} />
          </Panel>
          <Panel icon="log" title="Recent runs">
            {runs.slice(0, 4).map(r => (
              <div className="row" key={r.id} style={{ gridTemplateColumns: '1fr auto' }}>
                <div className="row-name" style={{ minWidth: 0 }}>
                  <span className={`badge ${runBadge(r.status)}`} style={{ minWidth: 56, justifyContent: 'center' }}>{runLabel(r.status)}</span>
                  <span style={{ overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap' }}>{r.title}</span>
                </div>
                <div className="row-meta">{r.dur}</div>
              </div>
            ))}
          </Panel>
        </div>
      </div>
    </div>
  );
}

function AgentInstructions({ detail, st, set }) {
  return (
    <div className="settings-content">
      <SectionHead title="Instructions" sub="The system prompt and operating rules the agent obeys on every dispatch." action={<span className="save-hint"><Icon name="check" size={12} />Saved</span>} />
      <Panel icon="settings" title="Role">
        <Field label="Role summary" hint="One-line description shown across the console and audit log." stack>
          <input className="input w-full" value={st.role} onChange={e => set('role', e.target.value)} />
        </Field>
      </Panel>
      <Panel icon="book" title="System prompt" right={<span className="chip-mono" style={{ color: 'var(--c-text-3)' }}>{st.instructions.length} chars</span>}>
        <div style={{ padding: 14 }}>
          <textarea className="input w-full" style={{ minHeight: 320 }} value={st.instructions} onChange={e => set('instructions', e.target.value)} spellCheck={false} />
        </div>
      </Panel>
      <div style={{ display: 'flex', gap: 8, justifyContent: 'flex-end' }}>
        <button className="btn ghost" type="button" onClick={() => set('instructions', detail.instructions)}><Icon name="rotate" size={12} />Reset to default</button>
        <button className="btn primary" type="button"><Icon name="check" size={12} />Save instructions</button>
      </div>
    </div>
  );
}

function AgentSkills({ st, toggleSkill }) {
  const on = st.skills.filter(s => s.on).length;
  return (
    <div className="settings-content">
      <SectionHead title="Skills" sub="Capabilities the agent may invoke. Disabling a skill removes it from the agent's toolset immediately." action={<button className="btn" type="button"><Icon name="plus" size={12} />Add skill</button>} />
      <Panel icon="spark" title="Enabled skills" right={<span className="chip-mono" style={{ color: 'var(--c-text-3)' }}>{on} / {st.skills.length} on</span>}>
        {st.skills.map(sk => (
          <div className="ae-skill" key={sk.id}>
            <div className={`ae-skill-icon ${sk.on ? 'on' : ''}`}><Icon name="zap" size={14} /></div>
            <div style={{ minWidth: 0 }}>
              <div className="ae-skill-name">{sk.name}<span className={`scope-pill ${sk.scope === 'security' ? 'write' : sk.scope === 'beta' ? '' : 'read'}`} style={{ marginLeft: 8 }}>{sk.scope}</span></div>
              <div className="set-field-hint" style={{ marginTop: 2 }}>{sk.desc}</div>
            </div>
            <Switch checked={sk.on} onChange={() => toggleSkill(sk.id)} />
          </div>
        ))}
      </Panel>
    </div>
  );
}

const MODELS = [
  'claude-opus-4', 'claude-sonnet-4', 'claude-haiku-3.5', 'gpt-4o', 'gpt-4o-mini', 'llama-3.3-70b',
];
function AgentConfiguration({ st, set }) {
  return (
    <div className="settings-content">
      <SectionHead title="Configuration" sub="Model selection and the runtime envelope the orchestrator enforces for this agent." action={<span className="save-hint"><Icon name="check" size={12} />Saved</span>} />
      <Panel icon="cpu" title="Model">
        <Field label="Model" hint="Provider and weights used for every inference call.">
          <select className="select" value={st.model} onChange={e => set('model', e.target.value)} style={{ width: 200 }}>
            {MODELS.map(m => <option key={m} value={m}>{m}</option>)}
          </select>
        </Field>
        <Field label="Temperature" hint="Lower is more deterministic. Migrations and security tasks should stay low.">
          <span style={{ display: 'flex', alignItems: 'center', gap: 10 }}>
            <input type="range" min="0" max="1" step="0.05" value={st.temp} onChange={e => set('temp', +e.target.value)} style={{ width: 140 }} />
            <span className="chip-mono" style={{ minWidth: 38, textAlign: 'right' }}>{st.temp.toFixed(2)}</span>
          </span>
        </Field>
        <Field label="Max output tokens" hint="Hard cap on tokens generated per call.">
          <input className="input mono w-120" type="number" min="256" max="16384" step="256" value={st.maxTokens} onChange={e => set('maxTokens', +e.target.value)} />
        </Field>
      </Panel>
      <Panel icon="sliders" title="Runtime">
        <Field label="Max concurrent tasks" hint="How many tasks this agent may execute in parallel.">
          <input className="input mono w-120" type="number" min="1" max="12" value={st.concurrency} onChange={e => set('concurrency', +e.target.value)} />
        </Field>
        <Field label="Task timeout" hint="A run exceeding this is halted and sent to Failed.">
          <select className="select" value={st.timeout} onChange={e => set('timeout', e.target.value)}>
            <option value="2m">2 minutes</option><option value="5m">5 minutes</option><option value="15m">15 minutes</option><option value="30m">30 minutes</option>
          </select>
        </Field>
        <Field label="Region" hint="Where this agent's runtime is pinned.">
          <select className="select" value={st.region} onChange={e => set('region', e.target.value)}>
            <option value="us-east-2">us-east-2 · Ohio</option><option value="us-west-1">us-west-1 · N. California</option><option value="eu-central-1">eu-central-1 · Frankfurt</option>
          </select>
        </Field>
        <Field label="Auto-retry on transient failure" hint="Re-dispatch with backoff before marking a task failed.">
          <Switch checked={st.autoRetry} onChange={v => set('autoRetry', v)} />
        </Field>
      </Panel>
    </div>
  );
}

function AgentRuns({ runs }) {
  const [filter, setFilter] = React.useState('all');
  const shown = filter === 'all' ? runs : runs.filter(r => r.status === filter);
  return (
    <div className="settings-content">
      <SectionHead title="Runs" sub="Every execution this agent has performed, newest first." action={
        <Seg value={filter} onChange={setFilter} options={[
          { value: 'all', label: 'All' }, { value: 'success', label: 'Success' }, { value: 'review', label: 'Review' }, { value: 'failed', label: 'Failed' },
        ]} />
      } />
      <div className="panel" style={{ overflow: 'hidden' }}>
        <div className="runs-table">
          <div className="runs-row runs-head">
            <span>Run</span><span>Task</span><span>Trigger</span><span>Status</span><span className="ta-r">Duration</span><span className="ta-r">Tokens</span><span className="ta-r">Cost</span><span className="ta-r">Started</span>
          </div>
          {shown.map(r => (
            <div className="runs-row" key={r.id}>
              <span className="chip-mono" style={{ color: 'var(--c-text-2)' }}>{r.id}</span>
              <span className="runs-title">{r.title}</span>
              <span className="row-meta">{r.trigger}</span>
              <span><span className={`badge ${runBadge(r.status)}`}>{runLabel(r.status)}</span></span>
              <span className="row-meta ta-r">{r.dur}</span>
              <span className="row-meta ta-r">{(r.tokens / 1000).toFixed(0)}K</span>
              <span className="row-meta ta-r">{r.cost ? money(r.cost) : '—'}</span>
              <span className="row-meta ta-r">{r.when}</span>
            </div>
          ))}
          {shown.length === 0 && <div className="empty" style={{ padding: 20 }}>No {filter} runs.</div>}
        </div>
      </div>
    </div>
  );
}

function AgentBudget({ detail, runs, st, set }) {
  // Free / self-hosted model — no metered spend.
  if (!detail.paid) {
    const c = detail.compute;
    return (
      <div className="settings-content">
        <SectionHead title="Budget" sub="This agent runs on a self-hosted model — there is no per-token spend to meter." />
        <Panel icon="cpu" title="Self-hosted model">
          <div className="empty" style={{ textAlign: 'left', padding: '14px 16px', display: 'flex', gap: 10, alignItems: 'center' }}>
            <Icon name="shield" size={14} />Inference runs on <b style={{ margin: '0 4px' }}>{detail.model}</b> on dedicated hardware. Cost is fixed at the cluster level, not per request.
          </div>
          <StatList rows={[
            ['Hardware', c.gpu],
            ['Billing', c.costModel],
            ['GPU-hours · 30d', c.gpuHours.toLocaleString()],
            ['Per-request cost', '$0.00 (amortized)'],
          ]} />
        </Panel>
      </div>
    );
  }

  const b = detail.budget;
  const pct = Math.min(100, (b.spent / st.limit) * 100);
  const projected = b.spent * 1.4;
  const inputCost = b.spent * 0.42, outputCost = b.spent * 0.58;
  const over = pct >= st.alertAt;
  return (
    <div className="settings-content">
      <SectionHead title="Budget" sub={`Spend for ${detail.model} · ${detail.provider} · billed per million tokens.`} />
      <div className="kpi-grid">
        <div className="kpi"><div className="kpi-label">Spent · month</div><div className="kpi-value">{money(b.spent)}</div><div className={`kpi-foot ${over ? 'warn' : 'ok'}`}>{pct.toFixed(0)}% of limit</div></div>
        <div className="kpi"><div className="kpi-label">Budget limit</div><div className="kpi-value">{money(st.limit)}</div><div className="kpi-foot">monthly cap</div></div>
        <div className="kpi"><div className="kpi-label">Projected EOM</div><div className="kpi-value">{money(projected)}</div><div className={`kpi-foot ${projected > st.limit ? 'warn' : 'ok'}`}>{projected > st.limit ? '▲ over cap' : 'within cap'}</div></div>
        <div className="kpi"><div className="kpi-label">Avg cost / run</div><div className="kpi-value">{money(b.spent / detail.stats.tasks)}</div><div className="kpi-foot">{detail.stats.tasks} runs</div></div>
      </div>

      <div className="ae-grid">
        <div style={{ display: 'flex', flexDirection: 'column', gap: 16 }}>
          <Panel icon="zap" title="Daily spend · 14 days" right={<span className="chip-mono" style={{ color: 'var(--c-text-3)' }}>USD / day</span>}>
            <div style={{ padding: '16px 16px 12px' }}>
              <BarChart seed={detail.label.charCodeAt(0) * 31 + 7} max={b.spent / 8} unit="$" accent={'color-mix(in oklch, var(--c-accent) 38%, var(--c-surface-3))'} />
              <div className="ae-chart-axis"><span>14d ago</span><span>today</span></div>
            </div>
          </Panel>
          <Panel icon="database" title="Cost breakdown">
            <StatList rows={[
              ['Input tokens', `${money(inputCost)}  ·  ${money(detail.priceIn)}/Mtok`],
              ['Output tokens', `${money(outputCost)}  ·  ${money(detail.priceOut)}/Mtok`],
              ['Total tokens · month', detail.stats.tokens],
            ]} />
          </Panel>
        </div>

        <div style={{ display: 'flex', flexDirection: 'column', gap: 16 }}>
          <Panel icon="alert" title="Budget cap">
            <div style={{ padding: '14px 16px' }}>
              <div className="budget-meter"><span style={{ width: `${pct}%`, background: over ? 'var(--c-status-failed)' : 'var(--c-accent)' }} /><span className="budget-mark" style={{ left: `${st.alertAt}%` }} /></div>
              <div style={{ display: 'flex', justifyContent: 'space-between', fontFamily: 'var(--font-mono)', fontSize: 10.5, color: 'var(--c-text-3)', marginTop: 6 }}>
                <span>{money(b.spent)} spent</span><span>{money(st.limit)} cap</span>
              </div>
            </div>
            <Field label="Monthly limit" hint="Agent pauses automatically when the cap is reached.">
              <span className="input-prefix"><span className="pfx">$</span><input className="input mono" style={{ width: 90 }} type="number" value={st.limit} onChange={e => set('limit', +e.target.value)} /></span>
            </Field>
            <Field label={`Alert at ${st.alertAt}%`} hint="Notify owners when spend crosses this share of the cap.">
              <input type="range" min="50" max="100" step="5" value={st.alertAt} onChange={e => set('alertAt', +e.target.value)} style={{ width: 140 }} />
            </Field>
            <Field label="Hard stop at cap" hint="Halt all dispatch when the limit is hit, rather than just alerting.">
              <Switch checked={st.hardStop} onChange={v => set('hardStop', v)} />
            </Field>
          </Panel>
        </div>
      </div>
    </div>
  );
}

// ---------- Editor shell ----------
const AE_TABS = [
  { id: 'dashboard', name: 'Dashboard', icon: 'columns' },
  { id: 'instructions', name: 'Instructions', icon: 'book' },
  { id: 'skills', name: 'Skills', icon: 'spark' },
  { id: 'configuration', name: 'Configuration', icon: 'sliders' },
  { id: 'runs', name: 'Runs', icon: 'log' },
  { id: 'budget', name: 'Budget', icon: 'zap' },
];

function AgentEditor({ agent, onBack }) {
  const detail = AGENT_DETAIL[agent.kind];
  const [tab, setTab] = React.useState('dashboard');
  const [paused, setPaused] = React.useState(agent.state !== 'running');
  const runs = React.useMemo(() => buildRuns(detail, agent.id), [agent.id]);

  const [st, setSt] = React.useState({
    role: detail.role,
    instructions: detail.instructions,
    skills: detail.skills.map(s => ({ ...s })),
    model: detail.model,
    temp: agent.kind === 'reviewer' || agent.kind === 'migrator' ? 0.1 : 0.3,
    maxTokens: 4096,
    concurrency: Math.max(1, agent.tasks || 2),
    timeout: agent.kind === 'migrator' ? '30m' : '15m',
    region: 'us-east-2',
    autoRetry: true,
    limit: detail.budget ? detail.budget.limit : 0,
    alertAt: detail.budget ? detail.budget.alertAt : 80,
    hardStop: true,
  });
  const set = (k, v) => setSt(p => ({ ...p, [k]: v }));
  const toggleSkill = id => setSt(p => ({ ...p, skills: p.skills.map(s => s.id === id ? { ...s, on: !s.on } : s) }));

  return (
    <div className="page">
      <div className="ae-topbar">
        <button className="btn ghost sm" type="button" onClick={onBack}><Icon name="arrowRight" size={12} style={{ transform: 'rotate(180deg)' }} />Agents</button>
        <span className="ae-crumb">/ {agent.id}</span>
      </div>

      <div className="page-head ae-head">
        <div className="ae-avatar" style={{ background: detail.avatarBg }}>{agent.kind.slice(0, 2).toUpperCase()}</div>
        <div>
          <h1 className="page-title">{agent.id}</h1>
          <p className="page-sub">{detail.label} agent · {detail.model} · {detail.paid ? detail.provider : 'self-hosted'}</p>
        </div>
        <div style={{ marginLeft: 'auto', display: 'flex', gap: 8, alignItems: 'center' }}>
          <Seg value={paused ? 'paused' : 'active'} onChange={v => setPaused(v === 'paused')} options={[{ value: 'active', label: 'Active' }, { value: 'paused', label: 'Paused' }]} />
          <button className="btn" type="button"><Icon name="open" size={12} />Open logs</button>
          <button className="btn primary" type="button"><Icon name="check" size={12} />Save changes</button>
        </div>
      </div>

      <div className="ae-tabrow">
        {AE_TABS.map(t => (
          <button key={t.id} type="button" className={`ae-tab ${tab === t.id ? 'active' : ''}`} onClick={() => setTab(t.id)}>
            <Icon name={t.icon} size={14} />{t.name}
            {t.id === 'budget' && !detail.paid && <span className="ae-tab-note">free</span>}
          </button>
        ))}
      </div>

      {tab === 'dashboard' && <AgentDashboard detail={detail} agentId={agent.id} runs={runs} paused={paused} />}
      {tab === 'instructions' && <AgentInstructions detail={detail} st={st} set={set} />}
      {tab === 'skills' && <AgentSkills st={st} toggleSkill={toggleSkill} />}
      {tab === 'configuration' && <AgentConfiguration st={st} set={set} />}
      {tab === 'runs' && <AgentRuns runs={runs} />}
      {tab === 'budget' && <AgentBudget detail={detail} runs={runs} st={st} set={set} />}
    </div>
  );
}

// ---------- Agents list + screen ----------
function AgentsScreen() {
  const { AGENTS } = window.ORC_DATA;
  const [editId, setEditId] = React.useState(null);
  const editing = AGENTS.find(a => a.id === editId);

  if (editing) return <AgentEditor agent={editing} onBack={() => setEditId(null)} />;

  return (
    <div className="page">
      <div className="page-head">
        <div>
          <h1 className="page-title">Agents</h1>
          <p className="page-sub">Autonomous workers bound to this workspace · {AGENTS.length} configured</p>
        </div>
        <div style={{ marginLeft: 'auto' }}>
          <button className="btn primary" type="button"><Icon name="plus" size={12} />New agent</button>
        </div>
      </div>
      <div className="panel">
        {AGENTS.map(a => {
          const d = AGENT_DETAIL[a.kind];
          return (
            <div className="agent-card" key={a.id} style={{ gridTemplateColumns: '32px 1fr auto auto', gap: 12 }}>
              <div className="agent-avatar" style={{ background: d.avatarBg }}>{a.kind.slice(0, 2).toUpperCase()}</div>
              <div style={{ minWidth: 0 }}>
                <div className="agent-name">{a.id}</div>
                <div className="agent-meta">
                  <span className={`s-${a.state === 'running' ? 'running' : 'backlog'}`}><span className="dot" /> {a.state}</span>
                  <span style={{ marginLeft: 8 }}>{a.tasks} tasks · {d.model} · {d.paid ? d.provider : 'self-hosted'}</span>
                </div>
              </div>
              {d.paid
                ? <span className="badge update" style={{ alignSelf: 'center' }}>paid</span>
                : <span className="badge idle" style={{ alignSelf: 'center' }}>self-hosted</span>}
              <button className="btn sm" type="button" onClick={() => setEditId(a.id)}><Icon name="settings" size={12} />Edit agent</button>
            </div>
          );
        })}
      </div>
    </div>
  );
}

window.AgentsScreen = AgentsScreen;
