480bede0ad
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)
38 lines
1.6 KiB
TypeScript
38 lines
1.6 KiB
TypeScript
import { defineConfig } from "vitest/config";
|
|
import { DurationShardingSequencer } from "@internal/testcontainers/sequencer";
|
|
import tsconfigPaths from "vite-tsconfig-paths";
|
|
|
|
export default defineConfig({
|
|
test: {
|
|
sequence: { sequencer: DurationShardingSequencer },
|
|
// Webapp tests live under test/**; the run-ops migration family
|
|
// colocates its *.server.test.ts next to source under app/v3/runOpsMigration/.
|
|
// The run-store seam test colocates next to its source at app/v3/runStore.server.test.ts.
|
|
// Pure unit tests for runEngine concerns colocate next to their source file.
|
|
include: [
|
|
"test/**/*.test.ts",
|
|
"app/v3/runOpsMigration/**/*.test.ts",
|
|
"app/v3/runStore.server.test.ts",
|
|
"app/v3/utils/**/*.test.ts",
|
|
"app/v3/services/bulk/**/*.test.ts",
|
|
"app/runEngine/concerns/**/*.test.ts",
|
|
"app/runEngine/services/**/*.test.ts",
|
|
"app/utils/**/*.test.ts",
|
|
"app/components/code/**/*.test.ts",
|
|
"app/components/dashboard-agent/**/*.test.ts",
|
|
"app/components/queues/**/*.test.ts",
|
|
"app/routes/storybook.agent-ui/*.test.ts",
|
|
"app/presenters/v3/reports/**/*.test.ts",
|
|
],
|
|
// *.e2e.test.ts: smoke matrix, run via vitest.e2e.config.ts.
|
|
// *.e2e.full.test.ts: full auth suite, runs via vitest.e2e.full.config.ts
|
|
// (needs a globalSetup-spawned webapp + Postgres container).
|
|
exclude: ["test/**/*.e2e.test.ts", "test/**/*.e2e.full.test.ts"],
|
|
globals: true,
|
|
pool: "forks",
|
|
setupFiles: ["./test/setup.ts"], // load apps/webapp/.env
|
|
},
|
|
// @ts-ignore
|
|
plugins: [tsconfigPaths({ projects: ["./tsconfig.json"] })],
|
|
});
|