docs-live
5 Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
480bede0ad |
feat(webapp,sdk): dashboard agent plan enforcement, component gallery — and fixes (#4516)
Plan enforcement for the dashboard agent — message quota and watch limits — plus the component gallery, fixes and test hardening from the same stack (#4548, #4549, #4550, #4552, #4556 merged here). ## Plan enforcement ([TRI-12863](https://linear.app/triggerdotdev/issue/TRI-12863)) **Agent message quota.** The Free-plan allowance becomes a real server-side limit with a durable counter. New `agent_message_usage` table keyed `(organization_id, period)` — deliberately not joined to chats, so deleting a chat can't free quota within the period. Both send paths count one user message (wakes never count) and refuse at the cap with `403 message_quota_reached`, which the client renders as an upgrade panel, never a silent drop. The refusal code is a single shared constant on both sides. **Watch limits.** A watch whose window exceeds the plan's `agentWatchMaxHours`, or that would push the org past its `agentWatchers` count, is refused with `watch_limit_reached` (409 on the API, an upgrade hint on the card). Plan limits only tighten the existing code ceilings (`min(plan, 24h)`, per-chat cap of 3 still applies). A plan limit of zero means zero, not unlimited. Questions answerable instantly are answered before any plan refusal — a one-shot consumes no slot and never sees an upgrade nag. **Fails open by design.** Cloud ships the actual per-plan numbers separately (TRI-12863 P0). Until then absent limits resolve to the unlimited sentinel and the upgrade UI is gated on billing presence — self-hosted sees no cap, no upsell, with tests proving the fallback. Both quotas are nudges, not security boundaries: a failing limit read never blocks a send. ## Component gallery An admin-only gallery of every agent card state: five `storybook.agent-*` pages (chat UI, view blocks, report, investigation, watch) with their shared shell and manifest, demo fixtures, two demo-only cards, toast examples, and the screenshot script. No LLM and no data — every state renders from fixtures under `dashboard-agent/demo/`, never reachable from a production path. Designers and reviewers can look at every state, including the report states, without seeding anything. ## And fixes **SDK: watch-mode chat subscriptions survive quiet windows** (TRI-13065, TRI-13070) — watch mode keeps reconnecting across empty long-poll windows and only stops on abort or a settled session; a passive subscriber can no longer stop a turn it doesn't own (`stopOnAbort` is explicit, default off). Review findings fixed alongside: a superseded stream's async teardown no longer removes the live successor's abort controller or multi-tab claim, and stopping a generation hands the chat back to the user's other tabs. **Query boundary pinned end-to-end** ([TRI-11165](https://linear.app/triggerdotdev/issue/TRI-11165)) — a route-level test drives `api.v1.query` with a real signed environment JWT (writes refused before ClickHouse, a read passes); `readonly=1` made non-overridable; a per-turn cap stops the model burning a turn rewriting a query it can't fix (deterministic SQL errors only — busy/transport rejections don't count). **chat.agent durability regression suite** ([TRI-11166](https://linear.app/triggerdotdev/issue/TRI-11166)) — testcontainers-backed coverage of the two audit criticals (cross-tenant isolation, no duplicate mid-stream turn, both control-broken) plus crash-resume, cursor-based refresh, clean rollback of a mid-write turn failure (torn by a real constraint violation), and OOM-restart replay. **Investigation sweep backoff** — stale investigations get an attempt counter and backoff so a poison row can't pin the sweep queue head (migration `0005`: `sweep_attempts`, `last_sweep_attempt_at`). ## Screenshots <img width="1440" height="791" alt="Screenshot 2026-08-06 at 00 36 19" src="https://github.com/user-attachments/assets/6a68cd42-8580-469d-afe7-e28d1eef18e1" /> 🤖 Generated with [Claude Code](https://claude.com/claude-code) |
||
|
|
fbd86b6ee9 |
feat(sdk): onEvent observability callback on the chat transport (#4187)
## Summary
`sendMessage` from `useChat` gives no feedback about whether a message
actually reached the backend, and the `fetch` override is wire-level: it
requires knowing endpoint semantics, cannot attribute requests to
messages, and misses the headStart first-turn POST entirely. This adds a
typed `onEvent` observability callback to `TriggerChatTransport` /
`useTriggerChatTransport` so send-success metrics, time-to-first-token,
and "sent but never answered" watchdogs become a few lines of client
code.
## Example
```ts
const transport = useTriggerChatTransport({
task: "my-chat",
accessToken: ({ chatId }) => mintChatAccessToken(chatId),
onEvent: (event) => {
switch (event.type) {
case "message-sent":
// Durably acknowledged by the session's input stream, not just "request accepted".
metrics.increment("chat.message_sent", { source: event.source });
metrics.timing("chat.send_duration_ms", event.durationMs);
break;
case "message-send-failed":
metrics.increment("chat.message_send_failed", { status: event.status });
break;
case "first-chunk":
metrics.timing("chat.ttft_ms", event.sinceSendMs ?? 0);
break;
case "turn-completed":
metrics.timing("chat.turn_duration_ms", event.sinceSendMs ?? 0);
break;
}
},
});
```
## Design
One callback, one discriminated union (`ChatTransportEvent`):
- `message-sent` / `message-send-failed`: terminal send outcomes with
`messageId`, a `source` discriminator (submit, regenerate, steer,
action, stop, head-start), `durationMs`, `bodyBytes`, the append's
idempotency key (`partId`, also stored on the server-side record), and
error + HTTP status on failure. `message-sent` means the append was
durably acknowledged, after any internal token-refresh retries.
- `stream-connected` (with a `resumed` flag and the cursor it connected
from), `first-chunk` (chunk type plus `sinceSendMs` for
time-to-first-token), `turn-completed` (`sinceSendMs` full-turn latency
and the agent's committed input cursor), and `stream-error` follow the
response side, so a send can be paired with the answer that should
follow it. `messageId` on response events is client-side attribution
from the last turn-producing send on that chat.
Emissions sit at the transport's existing choke points, covering every
send path uniformly (including steering and headStart, which the fetch
override cannot observe). Exceptions thrown by the callback are
swallowed: observability can never break the chat. The React hook keeps
the callback live across renders instead of freezing the first-render
closure.
## Verification
Unit tests drive the transport directly with the `fetch` override as the
network stub (send success/failure per source, stream lifecycle, resumed
flag, field enrichment, callback exceptions swallowed). Verified
end-to-end against a realistic metrics setup in the ai-chat reference
app (counters, send-duration and TTFT histograms, and both watchdogs
built purely on these events): a healthy two-turn chat produces exactly
the expected event sequence and TTFT values; an oversized append records
`message_send_failed` with status 413; and killing the worker after a
durable send fires both `sent_but_no_stream` and `sent_but_unanswered`,
reproducing and detecting the "message disappeared" failure mode that
motivated this feature.
|
||
|
|
4536eded9e |
docs: drop release-candidate framing ahead of the 4.5 GA (#4100)
Removes the release-candidate framing from the docs for the 4.5 GA: the `@rc` install caveats on the `skills` CLI command, and the AI Agents and Prompts release-candidate banner (a shared snippet used across the ai-chat and prompts pages) plus the `>=4.5.0-rc.0` compatibility pin in the AI reference. |
||
|
|
c0b9fdfce9 |
docs(ai-chat): clarify lastEventId is sessionId-keyed across run boundaries (#3700)
## Summary Two docs edits that close a footgun customers persisting transport state can hit. Clearing `lastEventId` on `chat.endRun()` looks intuitive — the Run ended, the cursor must be stale — but the cursor is sessionId-keyed, not runId-keyed. Clearing it forces the next `sendMessages` to subscribe from `seq_num=0`, which may hit the prior turn's still-durable `turn-complete` record and close the SSE empty before the new Run's chunks arrive. Spells out the invariant in the frontend transport persistence table and adds a Warning in the `chat.endRun()` reference. ## Test plan - [x] Mintlify preview renders - [x] No callout stacking |
||
|
|
80bb600cd5 |
docs(ai-chat): AI Agents documentation for v4.5 (#3226)
## Summary Lands the full AI Agents documentation surface alongside the v4.5 release candidate of `@trigger.dev/sdk`. Covers `chat.agent` end to end — defining agents, lifecycle hooks, the frontend transport, sub-agents, recovery from cancel/crash/OOM, AI Prompts integration — and the Sessions primitive that backs it. ## Coverage - **Conceptual**: Overview, Quick Start, How it works. - **Building agents**: Backend (`chat.agent` / `chat.createSession` / raw primitives), Lifecycle hooks, Frontend transport, Server-side `AgentChat`, Sessions reference, `chat.local` state primitive, TypeScript types. - **Features**: AI Prompts integration, Fast starts (Preload + Head Start), Compaction, Pending Messages (steering), Background Injection (`chat.inject` + `chat.defer`), Actions (undo / regenerate / edit), Error handling. - **Patterns (13)**: Sub-agents, Branching conversations, Code sandbox, Database persistence, Persistence and replay, HITL, Tool result auditing, Large payloads, Agent skills, OOM resilience, Recovery boot, Trusted edge signals, Version upgrades. - **Reference**: API Reference, Client Protocol (wire format), Testing harness (`mockChatAgent`), MCP server tools, Upgrade guide, Changelog. ## Structure changes - Top-level nav: AI → **Agents**, with sub-groups for *Building agents / Features / Patterns / Reference*. - New RC banner snippet on every page links to the supported AI SDK versions table on the API Reference. - All examples use Anthropic with `stopWhen: stepCountIs(15)`. --------- Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com> |