<!-- GENERATED by scripts/build-llms.mjs from llms/agent-tools.md — do not edit this file. -->

# `lr-tool-timeline`

- **Import** `import '@aceshooting/lyra-ui/components/lr-tool-timeline.js';` (stable tag alias; registers the tag)
- **Class** `LyraToolTimeline`, also available unregistered from `@aceshooting/lyra-ui/components/agent-tools/tool-timeline/tool-timeline.class.js`
- **Family** `components/agent-tools/` — see `llms/index.md` for its siblings
- **Status** `stable` since `4.1.0` — see the maturity and deprecation policy in `llms/shared.md`
- **Release history** [CHANGELOG.md](../../CHANGELOG.md); family-wide breaking-change summaries: [llms-full.txt](../../llms-full.txt)
- **Deprecations** none
- **Optional peers** none
- **Themeable via** 17 parts, 13 custom properties — see this component's own `@csspart`/`@cssprop` list below
- **Library-wide behavior** (events, form association, `locale`/`strings`, tokens, TS types): `llms/shared.md`

---

## `lr-tool-timeline`

Chronological list of agent tool/function calls composed from tool-call, result, and approval
primitives, with retry counts and sensitive-field redaction.

**Properties:**

- `entries: readonly ToolTimelineEntry[] = []` (attribute: false) — `ToolTimelineEntry` **extends
  `ToolInvocation` from `@aceshooting/lyra-ui/ai`** (`{ id: string; name: string; args:
Record<string, unknown>; status: ToolCallStatus; result?: unknown; error?: string }`, where
  `ToolCallStatus = 'pending' | 'running' | 'success' | 'error' | 'denied'`) with `{ startedAt?:
number; endedAt?: number; retryCount?: number; redactedFields?: readonly string[]; needsApproval?: boolean;
approved?: boolean; sourceKey?: string; icon?: string }`. `sourceKey` identifies the owning run or
  source generation when invocation ids can be reused; every expansion, activation, renderer error,
  and approval draft is correlated by `(sourceKey, id)`. Entries with empty/blank invocation ids
  are omitted; a blank optional `sourceKey` is treated as absent. Duplicate occurrences of the same
  pair are normalized before any lookup with a deterministic first-occurrence-wins policy. `icon` is a
  literal hint forwarded to the composed tool-call chip. A foreign runtime `status` normalizes once
  to `pending` before both the timeline row and its composed chip render.
  Timestamps are epoch milliseconds; entries sort ascending by `startedAt`,
  and an entry with none sorts after every timed entry (keeping its relative position among other
  untimed ones) and renders no visible timestamp. `startedAt`+`endedAt` derive the `durationMs`
  handed to the per-entry `lr-tool-call-chip`. `retryCount: 2` means the call reached its current
  state on its third try; `0`/omitted renders no retry indicator. `redactedFields` are dotted paths
  within `args`/`result`/`error` to mask in the rendered detail view (e.g.
  `['args.apiKey', 'result.rows.0.ssn']`, or a bare `'args'`/`'result'`/`'error'` to mask a whole
  branch); an unmatched path is a no-op, never an error — and redaction is **never** applied to the
  copy of `args` handed to the approval dialog. While `needsApproval` is `true` and `approved` is
  still `undefined`, activating the entry's chip opens the shared approval dialog instead of merely
  firing the chip's own selection event
- `approvalEditable: boolean = true` (attribute `approval-editable`, reflected) — forwarded to the
  shared approval dialog's `editable`: whether a reviewer may edit an entry's arguments before
  approving. Uses a `true`-defaulting string converter, so plain-HTML `approval-editable="false"`
  works
- `formatTimestamp?: (date: Date) => string` (attribute: false) — overrides the default
  `hour:minute` rendering of each entry's `startedAt`
- `pendingApproval: ToolTimelineApprovalPending = null` (read-only) — `'approve'` or `'deny'` while
  a listener has vetoed `lr-tool-approval-decide` and the timeline is holding the shared dialog for
  host persistence; otherwise `null`

**Methods:** `finalizePendingApproval(): void` closes a held dialog after the host has persisted and
applied its controlled `entries` update (it never mutates `entries` itself). `revertPendingApproval():
void` releases a held dialog after persistence fails, retaining the reviewer’s current argument edit
so they can retry. Both are no-ops when no decision is held.

At most 500 unique entries mount. Open disclosures and the entry under approval review reserve
positions before later ordinary history is omitted; a localized `[part="limit"]` note exposes the
bounded projection. Redaction is deferred until a disclosure opens and memoized while the entry's
payload and path list are unchanged. It is bounded to 100 paths, 64 levels, 10,000 visited nodes,
and 4,096 characters per path; crossing a ceiling masks the affected branch instead of exposing
data or exhausting the page.

**Events:** `lr-tool-activate` (`detail: { invocationId: string; sourceKey?: string }`) for a
non-approval entry activation and `lr-tool-render-error` (`detail: { invocationId: string;
sourceKey?: string; toolName: string; error: unknown }`) for a contained nested renderer failure.
The raw child chip-selection, renderer-error, details, and dialog events do not leak across the
timeline boundary. `lr-tool-approval-decide` (`detail: ToolTimelineApprovalDetail` =
`ToolApprovalEventDetail & { args?: unknown; sourceKey?: string }` = `{ invocationId: string;
approved: boolean; args?: unknown; sourceKey?: string }`, extending the shared detail from
`@aceshooting/lyra-ui/ai`). `args` is present only when
`approved` is `true`, and may differ from what the entry originally proposed — the dialog's inline
edit step can hand back different arguments. A listener that only needs `{ invocationId, approved }`
can ignore it; one actually executing the tool needs it. This is a cancelable veto point:
`preventDefault()` preserves the pending approval dialog and its current inline argument edits
instead of closing/resetting them, sets `pendingApproval`, and requires the host to call
`finalizePendingApproval()` after persistence succeeds or `revertPendingApproval()` after it fails.
A host may instead resolve the decision synchronously by reassigning `entries` (with the entry's
`approved` field set) from within that same listener, without ever calling
`finalizePendingApproval()`: the entry's live state is re-checked immediately after dispatch, so
`pendingApproval` — and the shared dialog's pending presentation — is never set or left set for
an entry that no longer needs a decision.

```ts
timeline.addEventListener("lr-tool-approval-decide", async (event) => {
  event.preventDefault();
  try {
    await persistDecision(event.detail);
    timeline.entries = applyDecision(timeline.entries, event.detail);
    timeline.finalizePendingApproval();
  } catch {
    timeline.revertPendingApproval();
  }
});
```

**CSS parts:** `base`,
`entry`, `entry-marker`, `entry-header`, `entry-timestamp`, `entry-body`, `entry-details`,
`entry-result`, `entry-error`, `entry-retries`, `entry-retries-count`, `entry-retries-label`,
`entry-redacted-indicator`, `entry-approval-status`, `approval-dialog`, `empty`, `limit`.
Each entry's `lr-details` disclosure has a localized contextual summary naming that tool call, not a
repeated bare "Details" label.

**Themeable custom properties:** `--lr-tool-timeline-gap` (default `var(--lr-space-l)`) — vertical
gap between entries; `--lr-tool-timeline-marker-size` (default `var(--lr-size-0-625rem)`) — the
per-entry timeline marker dot's size, which also sets the entry grid's leading column width;
`--lr-tool-timeline-denied-marker-color` (default `var(--lr-color-warning)`) — rail-dot color for a
`status="denied"` entry, decoupled from the pending-approval border color below so either can be
retinted independently; `--lr-tool-timeline-pending-marker-color` (default
`var(--lr-color-text-quiet)`) — rail-dot color for a `status="pending"` entry;
`--lr-tool-timeline-pending-approval-border-color` (default
`var(--lr-color-warning)`) — color of the entry body's leading border while that entry's
`data-pending-approval` is `"true"`.

**Additional API surface:**

- `--lr-tool-timeline-running-marker-color` — Running rail dot. Default: `var(--lr-color-brand)`.
- `--lr-tool-timeline-success-marker-color` — Success rail dot. Default: `var(--lr-color-success)`.
- `--lr-tool-timeline-error-marker-color` — Error rail dot. Default: `var(--lr-color-danger)`.
- `--lr-tool-timeline-approved-bg` — Approved badge background. Default: `var(--lr-color-success-quiet)`.
- `--lr-tool-timeline-approved-color` — Approved badge foreground. Default: `var(--lr-color-success)`.
- `--lr-tool-timeline-denied-bg` — Denied badge background. Default: `var(--lr-color-danger-quiet)`.
- `--lr-tool-timeline-denied-color` — Denied badge foreground. Default: `var(--lr-color-danger)`.
- `--lr-tool-timeline-error-color` — Expanded error text. Default: `var(--lr-color-danger)`.
