> Premium standalone component (part of no design system, works in any React + Tailwind app) from AI Canvas. Index of every component and install setup: https://aicanvas.me/llms.txt

# Agent Status

A status pill that says what the agent is doing, whose move it is, time and cost, and opens to the step history and token bill.

- Category: AI Agent UI
- Built with: Motion, Tailwind CSS
- License: Premium (proprietary, not in the public repo)
- Page: https://aicanvas.me/components/agent-status

## Install

```bash
npx shadcn@latest add @aicanvas/agent-status
```

Requires Premium. Set your AI Canvas token as AICANVAS_TOKEN and register the @aicanvas registry with it, as described in the Install section of https://aicanvas.me/llms.txt. Without a Premium token the command writes a placeholder file instead of the component.

AI agents can also get its install command from the read-only AI Canvas MCP server (`npx -y @aicanvas/mcp`) and run it. Setup: https://aicanvas.me/mcp

Peer dependencies:

```bash
npm install framer-motion
```

## Props

### StatusStep

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `kind` | `StepKind` |  | Selects the activity icon and waiting treatment for the step. |
| `label` | `string` |  | Step action text shown in the run details. |
| `target?` | `string` |  | File, command or other target shown after the label; left out, no target is shown. |
| `outcome?` | `string` |  | Result text shown at the end of the row; left out, no result text is shown. |
| `diff?` | `{added: number, removed: number}` |  | Added and removed counts shown instead of outcome; left out, outcome is used when present. |
| `failed?` | `boolean` |  | Colors the outcome as a failure when true; left out, the outcome uses the muted treatment. |
| `durationMs?` | `number` |  | Step duration shown at the row end; left out, no duration is shown. |
| `status?` | `'done'\|'live'\|'canceled'` |  | Sets the row state; left out, the row is treated as done. |

### TokenUse

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `input` | `number` |  | Input token count shown in the cost breakdown. |
| `output` | `number` |  | Output token count shown in the cost breakdown. |
| `inputUsd?` | `number` |  | Input cost used in the breakdown and calculated total; left out, no input price is shown. |
| `outputUsd?` | `number` |  | Output cost used in the breakdown and calculated total; left out, no output price is shown. |

### AgentStatus

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `state?` | `AgentState` |  | Sets the agent state and its pill treatment; left out, it is ready in host mode, while passing it turns the demo off. |
| `phrase?` | `string` |  | Replaces the state phrase in the pill; left out, a phrase is chosen from the state, while passing it turns the demo off. |
| `target?` | `string` |  | Adds a file, command or other target after the phrase; left out, no target is shown, while passing it turns the demo off. |
| `elapsedMs?` | `number` |  | Shows elapsed or terminal duration in milliseconds; left out, no elapsed time is shown, while passing it turns the demo off. |
| `waitingMs?` | `number` |  | Shows how long the waiting state has lasted; left out, the waiting pill has no clock, while passing it turns the demo off. |
| `quietMs?` | `number` |  | Marks working or thinking as stalled after the tuning threshold; left out, no quiet-time stall is inferred, while passing it turns the demo off. |
| `costUsd?` | `number` |  | Shows the total run cost in US dollars; left out, a total is calculated only when token prices are supplied, while passing it turns the demo off. |
| `steps?` | `StatusStep[]` |  | Supplies rows for the expandable run details; left out, the details contain no steps in host mode, while passing it turns the demo off. |
| `tokens?` | `TokenUse` |  | Supplies input and output usage for the cost breakdown; left out, token lines are not shown, while passing it turns the demo off. |
| `defaultOpen?` | `boolean` | `false` | Sets whether run details start expanded. |
| `onStop?` | `() => void` |  | Handles stopping an active run and makes the stop control available outside the demo; left out, host runs have no stop control. |
| `onReview?` | `() => void` |  | Handles the review action and makes that control available while waiting; left out, host waiting states have no review control. |
| `className?` | `string` |  | Adds classes to the outermost root; left out, only the built-in classes are used. |
| `tuning?` | `Partial<Tuning>` |  | Overrides status measurements, timing and motion values; left out, every value in TUNING is used. |

## About

Agent Status is one line that always says what the agent is doing: idle, thinking, running a named tool, waiting for the user, done or failed, with elapsed time and cost that only updates when a turn completes. It reads like a sentence with the object in mono, hovering expands a short history with durations, the waiting state pulses and adds a needs you chip that scrolls to the decision, and it ships as a 28px pill for headers or a 40px bar with the history inline. State changes are announced once to screen readers.

Use cases: AI coding agents, Chat assistants, App headers.

## Source

The source is Premium and is not published. Preview it at https://aicanvas.me/components/agent-status.

## Remix prompt

The public part of the remix prompt: 1. Setup and 2. Constants. The rest of the build spec (state, markup, how it works) is part of Premium.

```markdown
# Agent Status
A live status pill for AI agents that expands into run steps and cost.

## 1. Setup
// npm install framer-motion
File: components-workspace/agent-status/index.tsx  ·  'use client'  ·  export default function AgentStatus()
import { useEffect, useId, useLayoutEffect, useRef, useState, useSyncExternalStore } from 'react'
import type * as React from 'react'
import { animate, AnimatePresence, motion, useIsPresent, useMotionValue, useTransform } from 'framer-motion'
import type { MotionValue } from 'framer-motion'
import { RotateCcw } from '@/app/_icons/icons/rotate-ccw'
import { ChevronDown } from '@/app/_icons/icons/chevron-down'
import { Check } from '@/app/_icons/icons/check'
import { LoaderCircle } from '@/app/_icons/icons/loader-circle'
import { CircleAlertDuotone } from '@/app/_icons/icons/circle-alert-duotone'
import { CircleCheckDuotone } from '@/app/_icons/icons/circle-check-duotone'
import { CircleDashedDuotone } from '@/app/_icons/icons/circle-dashed-duotone'
import { CircleMinusDuotone } from '@/app/_icons/icons/circle-minus-duotone'
import { CircleStopDuotone } from '@/app/_icons/icons/circle-stop-duotone'
import { CircleXDuotone } from '@/app/_icons/icons/circle-x-duotone'
import { FilePlusDuotone } from '@/app/_icons/icons/file-plus-duotone'
import { FileTextDuotone } from '@/app/_icons/icons/file-text-duotone'
import { GlobeDuotone } from '@/app/_icons/icons/globe-duotone'
import { MessageCircleDuotone } from '@/app/_icons/icons/message-circle-duotone'
import { Pen2Duotone } from '@/app/_icons/icons/pen-2-duotone'
import { SearchDuotone } from '@/app/_icons/icons/search-duotone'
import { SparkleDuotone } from '@/app/_icons/icons/sparkle-duotone'
import { TerminalDuotone } from '@/app/_icons/icons/terminal-duotone'
import { TrashDuotone } from '@/app/_icons/icons/trash-duotone'

Styling is Tailwind CSS v4 (the bg-(--var) and text-(color:--var) syntax, pointer-coarse: and class-based dark:). The @/app/_icons imports are the AI Canvas icon set; without it, use any icons that accept the same size, weight and className props.

## 2. Constants
export type Tuning = {
  pillH: number
  pillMinW: number
  pillMaxW: number
  panelW: number
  panelRadius: number
  panelGap: number
  glyphPx: number
  stopPx: number
  widthSpringS: number
  phraseOutMs: number
  phraseInMs: number
  phraseRisePx: number
  hoverMs: number
  stateMs: number
  panelMs: number
  countMs: number
  lightMs: number
  pillLightH: number
  panelLightH: number
  rimFlashMs: number
  dotPitch: number
  dotFieldH: number
  replayDelayMs: number
  shimmerS: number
  spinS: number
  stallAfterMs: number
  stopHoldMs: number
  tickMs: number
  demoSpeed: number
}

export const TUNING: Tuning = {
  pillH: 40,
  pillMinW: 200,
  pillMaxW: 480,
  panelW: 480,
  panelRadius: 24,
  panelGap: 8,
  glyphPx: 16,
  stopPx: 32,
  widthSpringS: 0.35,
  phraseOutMs: 120,
  phraseInMs: 160,
  phraseRisePx: 4,
  hoverMs: 150,
  stateMs: 200,
  panelMs: 280,
  countMs: 400,
  lightMs: 600,
  pillLightH: 72,
  panelLightH: 160,
  rimFlashMs: 600,
  dotPitch: 6,
  dotFieldH: 64,
  replayDelayMs: 500,
  shimmerS: 2.6,
  spinS: 0.9,
  stallAfterMs: 15000,
  stopHoldMs: 8000,
  tickMs: 100,
  demoSpeed: 1,
}

const EASE = [0.23, 1, 0.32, 1] as const

// Keeps a number and its unit on one line ("212 lines").
const NBSP = String.fromCharCode(160)

const MINUS = String.fromCharCode(8722)

export type AgentState = 'ready' | 'thinking' | 'working' | 'waiting' | 'stalled' | 'done' | 'stopped' | 'failed'

export type StepKind = 'think' | 'read' | 'search' | 'edit' | 'create' | 'run' | 'fetch' | 'delete' | 'wait'

export type StatusStep = {
  kind: StepKind
  label: string
  target?: string
  outcome?: string
  diff?: { added: number; removed: number }
  failed?: boolean
  durationMs?: number
  status?: 'done' | 'live' | 'canceled'
}

export type TokenUse = { input: number; output: number; inputUsd?: number; outputUsd?: number }

type AgentStatusProps = {
  state?: AgentState
  phrase?: string
  target?: string
  elapsedMs?: number
  waitingMs?: number
  quietMs?: number
  costUsd?: number
  steps?: StatusStep[]
  tokens?: TokenUse
  defaultOpen?: boolean
  onStop?: () => void
  onReview?: () => void
  className?: string
  tuning?: Partial<Tuning>
}

const KIND_ICON: Record<StepKind, typeof FileTextDuotone> = {
  think: SparkleDuotone,
  read: FileTextDuotone,
  search: SearchDuotone,
  edit: Pen2Duotone,
  create: FilePlusDuotone,
  run: TerminalDuotone,
  fetch: GlobeDuotone,
  delete: TrashDuotone,
  wait: MessageCircleDuotone,
}

type Beat = {
  at: number
  end: number
  kind: StepKind
  say: string
  sayTarget?: string
  now: string
  past: string
  target?: string
  outcome?: string
  diff?: { added: number; removed: number }
  failed?: boolean
  usd: number
  agent: boolean
}

const STORY: Beat[] = [
  { at: 800, end: 2600, kind: 'think', say: 'Thinking', now: 'Thinking', past: 'Thought', usd: 0.01, agent: true },
  {
    at: 2600,
    end: 4200,
    kind: 'read',
    say: 'Reading',
    sayTarget: 'src/auth.ts',
    now: 'Reading',
    past: 'Read',
    target: 'src/auth.ts',
    outcome: `212${NBSP}lines`,
    usd: 0.01,
    agent: true,
  },
  {
    at: 4200,
    end: 6400,
    kind: 'run',
    say: 'Running tests',
    now: 'Running',
    past: 'Ran',
    target: 'npm test',
    outcome: `2${NBSP}failed`,
    failed: true,
    usd: 0.02,
    agent: true,
  },
  {
    at: 6400,
    end: 8000,
    kind: 'edit',
    say: 'Editing',
    sayTarget: 'src/auth.ts',
    now: 'Editing',
    past: 'Edited',
    target: 'src/auth.ts',
    diff: { added: 8, removed: 2 },
    usd: 0.03,
    agent: true,
  },
  {
    at: 8000,
    end: 12000,
    kind: 'wait',
    say: 'Waiting for you: approve deleting',
    sayTarget: 'build/',
    now: 'Asking to delete',
    past: 'Asked to delete',
    target: 'build/',
    outcome: 'approved',
    usd: 0,
    agent: false,
  },
  {
    at: 12000,
    end: 13600,
    kind: 'delete',
    say: 'Deleting',
    sayTarget: 'build/',
    now: 'Deleting',
    past: 'Deleted',
    target: 'build/',
    outcome: `38${NBSP}files`,
    usd: 0.01,
    agent: true,
  },
  {
    at: 13600,
    end: 15400,
    kind: 'run',
    say: 'Running tests',
    now: 'Running',
    past: 'Ran',
    target: 'npm test',
    outcome: `58${NBSP}passed`,
    usd: 0.04,
    agent: true,
  },
]

const STORY_DONE = 15400

const APPROVED_FROM = 12000

const APPROVED_TO = 12800

const STORY_AGENT_MS = 65000

const STORY_ACTIVE_MS = STORY.reduce((n, b) => n + (b.agent ? b.end - b.at : 0), 0)

const STORY_RATE = STORY_AGENT_MS / STORY_ACTIVE_MS

const STORY_USD = STORY.reduce((n, b) => n + b.usd, 0)

const STORY_TOKENS = { input: 32100, output: 4200, inputUsd: 0.08, outputUsd: 0.04 }

const span = (e: number, a: number, b: number) => Math.min(Math.max(e, a), b) - a

type Model = {
  state: AgentState
  approved: boolean
  phrase?: string
  target?: string
  elapsedMs?: number
  waitingMs?: number
  quietMs?: number
  costUsd?: number
  steps: StatusStep[]
  tokens?: TokenUse
}

function storyAt(e: number, stopped: boolean): Model {
  const agentMs = STORY.reduce((n, b) => n + (b.agent ? span(e, b.at, b.end) * STORY_RATE : 0), 0)
  const cur = STORY.find((b) => e >= b.at && e < b.end)
  const cost = STORY.reduce((n, b) => n + (e >= b.end ? b.usd : 0), 0)
  const share = cost / STORY_USD
  const steps: StatusStep[] = STORY.filter((b) => e >= b.at).map((b): StatusStep => {
    const real = b.end - b.at
    const ms = b.agent ? real * STORY_RATE : real
    if (e >= b.end) {
      return b.kind === 'think'
        ? { kind: b.kind, label: `${b.past} for ${fmtSpan(ms)}`, status: 'done' }
        : {
            kind: b.kind,
            label: b.past,
            target: b.target,
            outcome: b.outcome,
            diff: b.diff,
            failed: b.failed,
            durationMs: ms,
            status: 'done',
          }
    }
    const live = (e - b.at) * (b.agent ? STORY_RATE : 1)
    return stopped
      ? { kind: b.kind, label: b.now, target: b.target, outcome: 'stopped', status: 'canceled' }
      : { kind: b.kind, label: b.now, target: b.target, durationMs: live, status: 'live' }
  })
  const state: AgentState = stopped
    ? 'stopped'
    : e < STORY[0].at
      ? 'ready'
      : e >= STORY_DONE
        ? 'done'
        : cur?.kind === 'wait'
          ? 'waiting'
          : cur?.kind === 'think'
            ? 'thinking'
            : 'working'
  return {
    state,
    approved: !stopped && e >= APPROVED_FROM && e < APPROVED_TO,
    phrase: stopped ? undefined : cur?.say,
    target: stopped ? undefined : cur?.sayTarget,
    elapsedMs: e >= STORY[0].at ? agentMs : undefined,
    waitingMs: cur?.kind === 'wait' ? e - cur.at : undefined,
    costUsd: e >= STORY[0].at ? cost : undefined,
    steps,
    tokens:
      cost > 0
        ? {
            input: STORY_TOKENS.input * share,
            output: STORY_TOKENS.output * share,
            inputUsd: STORY_TOKENS.inputUsd * share,
            outputUsd: STORY_TOKENS.outputUsd * share,
          }
        : undefined,
  }
}

// Reduced motion as an external store: server and hydrating render both read false.
const REDUCED_QUERY = '(prefers-reduced-motion: reduce)'

function subscribeReduced(onChange: () => void) {
  if (typeof window === 'undefined' || !window.matchMedia) return () => {}
  const mq = window.matchMedia(REDUCED_QUERY)
  mq.addEventListener('change', onChange)
  return () => mq.removeEventListener('change', onChange)
}

function getReduced() {
  return typeof window !== 'undefined' && !!window.matchMedia && window.matchMedia(REDUCED_QUERY).matches
}

function getReducedServer() {
  return false
}

function fmtClock(ms: number) {
  const s = Math.floor(ms / 1000)
  return `${Math.floor(s / 60)}:${String(s % 60).padStart(2, '0')}`
}

function fmtSpan(ms: number) {
  const s = Math.max(1, Math.round(ms / 1000))
  if (s < 60) return `${s}s`
  return `${Math.floor(s / 60)}m${NBSP}${String(s % 60).padStart(2, '0')}s`
}

function spokenSpan(ms: number) {
  const s = Math.max(1, Math.round(ms / 1000))
  const m = Math.floor(s / 60)
  const r = s % 60
  const sec = `${r} second${r === 1 ? '' : 's'}`
  return m === 0 ? sec : `${m} minute${m === 1 ? '' : 's'}${r ? ` ${sec}` : ''}`
}

function fmtDuration(ms: number) {
  if (ms < 60000) return `${(ms / 1000).toFixed(1)}s`
  return fmtSpan(ms)
}

const fmtUsd = (v: number) => `$${v.toFixed(2)}`

const fmtTokens = (n: number) => `${n >= 1000 ? `${(n / 1000).toFixed(1)}k` : Math.round(n)}${NBSP}tokens`

const RUNNING: AgentState[] = ['thinking', 'working', 'stalled']

const THEME_VARS = [
  '[--as-page:#EEEEF3] dark:[--as-page:#0E0E0F]',
  '[--as-panel:#F4F4FA] dark:[--as-panel:#1B1B1C]',
  '[--as-well:#EEEEF3] dark:[--as-well:#0E0E0F]',
  '[--as-raised:#DFDFE3] dark:[--as-raised:#2D2D2E]',
  '[--as-raised-hover:#CACACD] dark:[--as-raised-hover:#373738]',
  '[--as-border:#DFDFE3] dark:[--as-border:#2D2D2E]',
  '[--as-border-strong:#CACACD] dark:[--as-border-strong:#373738]',
  '[--as-hairline:#DFDFE3] dark:[--as-hairline:#2D2D2E]',
  '[--as-rim:#CACACD] dark:[--as-rim:#F4F4FA2E]',
  '[--as-rim-lit:#9B9B9E] dark:[--as-rim-lit:#F4F4FA4D]',
  '[--as-text:#1B1B1C] dark:[--as-text:#F4F4FA]',
  '[--as-body:#373738] dark:[--as-body:#CACACD]',
  '[--as-muted:#575759] dark:[--as-muted:#9B9B9E]',
  '[--as-faint:#7B7B7D]',
  '[--as-selection:#A0A0A233] dark:[--as-selection:#5E5E6333]',
  '[--as-success:#047857] dark:[--as-success:#34D399]',
  '[--as-ai:#0369A1] dark:[--as-ai:#7DD3FC]',
  '[--as-attention:#B45309] dark:[--as-attention:#FFB574]',
  '[--as-red:#C8262C] dark:[--as-red:#E5484D]',
  '[--as-primary:#1B1B1C] dark:[--as-primary:#F4F4FA]',
  '[--as-primary-hover:#373738] dark:[--as-primary-hover:#FFFFFF]',
  '[--as-on-primary:#F4F4FA] dark:[--as-on-primary:#0E0E0F]',
  '[--as-focus:#1B1B1C99] dark:[--as-focus:#F4F4FA99]',
  '[--as-focus-gap:#F4F4FA] dark:[--as-focus-gap:#1B1B1C]',
  '[--as-dot:#1B1B1C] dark:[--as-dot:#F4F4FA]',
  '[--as-elev-card:0_2px_6px_rgba(0,0,0,0.06),0_28px_56px_-16px_rgba(0,0,0,0.25)] dark:[--as-elev-card:0_2px_6px_rgba(0,0,0,0.5),0_36px_72px_-18px_rgba(0,0,0,0.9)]',
  '[--as-elev-1:0_1px_2px_rgba(0,0,0,0.08)] dark:[--as-elev-1:0_1px_2px_rgba(0,0,0,0.5)]',
].join(' ')

const TITLE = 'min-w-0 truncate text-[15px] leading-6 font-semibold text-(color:--as-text)'

const META = 'shrink-0 text-[12px] leading-4 tabular-nums text-(color:--as-muted)'

const GROUP = 'font-mono text-[11px] leading-4 text-(color:--as-muted)'

const MONO = 'font-mono text-[12px] leading-[18px]'

const FOCUS =
  'outline-none focus-visible:ring-2 focus-visible:ring-(color:--as-focus) focus-visible:ring-offset-2 focus-visible:ring-offset-(color:--as-focus-gap)'

const FOCUS_PAGE =
  'outline-none focus-visible:ring-2 focus-visible:ring-(color:--as-focus) focus-visible:ring-offset-2 focus-visible:ring-offset-(color:--as-page)'

// Touch hit areas: the visible box stays, a pseudo-element grows the target to 44px.
const HIT = "relative pointer-coarse:after:absolute pointer-coarse:after:content-['']"

const HIT_ICON = `${HIT} pointer-coarse:after:-inset-1.5`

const HIT_REVIEW = `${HIT} pointer-coarse:after:-inset-y-1.5 pointer-coarse:after:-inset-x-1`

const BTN =
  'inline-flex shrink-0 items-center justify-center gap-1.5 rounded-full text-[12px] leading-4 font-semibold transition-[background-color,color,border-color] duration-150'

const BTN_GHOST = `${BTN} h-8 px-4 text-(color:--as-muted) hover:bg-(--as-raised) hover:text-(color:--as-text)`

const SHIMMER = 'linear-gradient(90deg, var(--as-muted) 0%, var(--as-text) 50%, var(--as-muted) 100%)'

type Tone = 'neutral' | 'ai' | 'attention' | 'success' | 'red'

const TONE_VAR: Record<Tone, string> = {
  neutral: 'var(--as-dot)',
  ai: 'var(--as-ai)',
  attention: 'var(--as-attention)',
  success: 'var(--as-success)',
  red: 'var(--as-red)',
}

const TONE_STRENGTH: Record<Tone, number> = { neutral: 6, ai: 14, attention: 14, success: 10, red: 12 }

/**
 * @typedef {object} StatusStep
 * @property {StepKind} kind Selects the activity icon and waiting treatment for the step.
 * @property {string} label Step action text shown in the run details.
 * @property {string} [target] File, command or other target shown after the label; left out, no target is shown.
 * @property {string} [outcome] Result text shown at the end of the row; left out, no result text is shown.
 * @property {{added: number, removed: number}} [diff] Added and removed counts shown instead of outcome; left out, outcome is used when present.
 * @property {boolean} [failed] Colors the outcome as a failure when true; left out, the outcome uses the muted treatment.
 * @property {number} [durationMs] Step duration shown at the row end; left out, no duration is shown.
 * @property {'done'|'live'|'canceled'} [status] Sets the row state; left out, the row is treated as done.
 */

/**
 * @typedef {object} TokenUse
 * @property {number} input Input token count shown in the cost breakdown.
 * @property {number} output Output token count shown in the cost breakdown.
 * @property {number} [inputUsd] Input cost used in the breakdown and calculated total; left out, no input price is shown.
 * @property {number} [outputUsd] Output cost used in the breakdown and calculated total; left out, no output price is shown.
 */

/**
 * @typedef {object} AgentStatusProps
 * @property {AgentState} [state] Sets the agent state and its pill treatment; left out, it is ready in host mode, while passing it turns the demo off.
 * @property {string} [phrase] Replaces the state phrase in the pill; left out, a phrase is chosen from the state, while passing it turns the demo off.
 * @property {string} [target] Adds a file, command or other target after the phrase; left out, no target is shown, while passing it turns the demo off.
 * @property {number} [elapsedMs] Shows elapsed or terminal duration in milliseconds; left out, no elapsed time is shown, while passing it turns the demo off.
 * @property {number} [waitingMs] Shows how long the waiting state has lasted; left out, the waiting pill has no clock, while passing it turns the demo off.
 * @property {number} [quietMs] Marks working or thinking as stalled after the tuning threshold; left out, no quiet-time stall is inferred, while passing it turns the demo off.
 * @property {number} [costUsd] Shows the total run cost in US dollars; left out, a total is calculated only when token prices are supplied, while passing it turns the demo off.
 * @property {StatusStep[]} [steps] Supplies rows for the expandable run details; left out, the details contain no steps in host mode, while passing it turns the demo off.
 * @property {TokenUse} [tokens] Supplies input and output usage for the cost breakdown; left out, token lines are not shown, while passing it turns the demo off.
 * @property {boolean} [defaultOpen=false] Sets whether run details start expanded.
 * @property {() => void} [onStop] Handles stopping an active run and makes the stop control available outside the demo; left out, host runs have no stop control.
 * @property {() => void} [onReview] Handles the review action and makes that control available while waiting; left out, host waiting states have no review control.
 * @property {string} [className] Adds classes to the outermost root; left out, only the built-in classes are used.
 * @property {Partial<Tuning>} [tuning] Overrides status measurements, timing and motion values; left out, every value in TUNING is used.
 */
```

## Related

- [Agent Composer](https://aicanvas.me/components/agent-composer.md): The message input an agent sits behind: a textarea that grows to six rows, inline slash commands, @file mentions as removable chips, a drag-and-drop attachment row, a model picker with each provider's logo, and a round send button whose glyph becomes a pulsing stop square while the agent works.
- [Thinking Trace](https://aicanvas.me/components/reasoning-trace.md): A single steady line shows what an agent is thinking right now, then settles into how long it thought, with every step one click away.
- [Checkpoint](https://aicanvas.me/components/agent-checkpoint.md): An agent checkpoint card that pauses for a decision on a question, command or plan, then collapses into a one-line receipt with undo, edit-before-allow and a risk grade.
- [Permission Request](https://aicanvas.me/components/permission-scope.md): A permission card where an agent asks for access and you allow it once, for this chat or always for a pattern, then revoke it from the receipt.
- [Code Block](https://aicanvas.me/components/code-panel.md): Code an agent just wrote streams in line by line, then numbered markers tie its notes to the exact lines, with Copy and Apply.
