4569657923
## What & why This is the system behind the Dashboard Agent — an assistant that answers questions about a project's runs, errors, queues, deploys and health, and can investigate failures end to end. The agent runs as a chat.agent task in its own Trigger project. It has no access to the main database or ClickHouse; all platform data is read through the public API using a delegated, read-only user token. Everything here is behind `canAccessDashboardAgent` and inert with the flag off. The UI that mounts the panel lands in #4529. ## Stack `#4418` (this, base) ← `#4529` UI ← `#4525` Watch ← `#4516` storybook gallery. The scenario/contract reference for the whole stack is `internal-packages/dashboard-agent/GUIDEBOOK.md` (it lands on the Watch branch): it states, per feature, what makes each thing happen and where that is decided. ## What's inside **Agent runtime and tools** — `internal-packages/dashboard-agent`: prompt, tool set (API reads, TRQL query, docs, navigation, evidence/investigations, repo source), conversation compaction, a prompt-prefix token budget pinned by snapshot test, and sampled LLM-judged turn evals. The package cannot import webapp server code, which is what makes the "no DB access" claim structural rather than a convention. **Contracts** — `internal-packages/dashboard-agent-contracts`: `trigger://` URIs, intents, and the block envelope every rendered card travels in. **Conversation store** — `internal-packages/dashboard-agent-db`: drizzle over postgres-js in its own `trigger_dashboard_agent` Postgres schema, plus one additive migration. **Auth boundary** — the user-actor token gains an optional environment claim; one guard (`userActorEnvironment.server.ts`) enforces it so routes don't each re-derive the rule. Token minting, cap ceiling, and the RBAC fallback path for self-hosted. **Transport** — webapp resource routes that mint the token and proxy each turn, and SDK-side mid-turn reconnect. **Public API the agent reads through** — orgs, projects, environments, runs, queue metrics, workers, a run's commit metadata, repo snapshot, reports, and `POST /api/v1/query`. **Reports** — the health report's layout is declared once and shared by the card, the markdown surface and the JSON/MCP surface, so the same report reads the same in the dashboard, the terminal and an editor. **Block renderers** — the report and investigation cards the flows above already emit (`app/components/dashboard-agent/`). The panel that hosts them, and the rest of the chat UI, is #4529. **Query safety and CSP** — see below. ## Key decisions - **The agent is a separate Trigger project, not webapp code.** It reads platform data over the public API with a delegated user-actor token whose `cap` ceilings it to read scopes. No Prisma, no ClickHouse, no webapp imports. - **The PAT-only auth helper now refuses user-actor tokens.** This is an intentional behavioral change: its callers consume only a bare userId and do not enforce delegated-token capabilities. Actor-aware routes continue through the scoped route builders instead. - **RBAC fallback builds a delegated token's ability from its own cap**, never the blanket ability a PAT gets (read-only when the token declares none). Without this, the agent's read-only cap would buy a write JWT on self-hosted. - **Org creation checks RBAC only for user-actor tokens, and only after the env gate**, so an install with `ORG_CREATION_API_ENABLED` off returns 404 rather than 403, and an ordinary PAT never consults an ability the route has no org to scope. Both orderings are pinned by test. - **The query path is read-only in depth.** TRQL rejects write statements at the grammar level (they don't parse, rather than being filtered), ClickHouse runs with `readonly=1`, and the org/project/env filters are injected server-side from the credential — the request body cannot widen scope. An unparseable query denies instead of falling through to the permissive resource. - **Document-wide img-src CSP.** Remote images are an outbound-request/exfiltration surface, so the policy permits only own-origin/data/blob, the required SSO avatar hosts, and the favicon endpoint. Operators can add exact origins through CSP_IMG_SRC_ALLOWLIST; wildcard hosts and bare schemes are intentionally not allowed. - **The chat transport reconnects on a mid-turn EOF** (`@trigger.dev/sdk`). A body that ends without a turn-complete is terminal only when the server says `X-Session-Settled: true`; otherwise the transport resubscribes from `lastEventId` with bounded backoff, and any record re-earns the budget. Previously a closed long-poll window or a proxy restart left the reply stuck as if still generating. - **Conversations live in their own datastore**, schema-scoped and foreign-key-free (it references `organizationId`/`userId` by id, because in cloud it is a different database). It is a display read-model for the History tab and transport resume; `chat.agent`'s object-store snapshot remains the model's source of truth. - **Deterministic first.** Reports and health checks contain no LLM — they are computed from the same data the dashboard shows, and the model only narrates and links them. That is what makes a number in an answer auditable. ## Testing - 63 new test files, run with `pnpm run test --filter webapp` and per-package vitest. Heaviest coverage on the auth boundary (`userActorPatOnlyBoundary`, `userActorTokenClaimsAndScopes`, `contextlessPatRoutes`, `rbacFallbackBranch`), TRQL read-only, the report layout, and the SDK reconnect. - The agent package has a separate eval lane (`pnpm run test:evals`, `vitest.eval.config.ts`) that hits the real model, so it never runs in `pnpm test`. - Live-tested against a local stack scenario by scenario; the GUIDEBOOK lists the condition each behaviour is expected under, which is what those runs were checked against. ## Changelog `.server-changes/dashboard-agent.md`, plus changesets for `@trigger.dev/core` (report schemas), `@trigger.dev/sdk` (chat reconnect) and the CLI's `mint-token` help text.
284 lines
10 KiB
JSON
284 lines
10 KiB
JSON
{
|
|
"private": true,
|
|
"name": "webapp",
|
|
"version": "1.0.0",
|
|
"sideEffects": false,
|
|
"scripts": {
|
|
"build": "run-s build:** && pnpm run upload:sourcemaps",
|
|
"build:remix": "cross-env NODE_OPTIONS=\"--max-old-space-size=8192\" remix vite:build",
|
|
"build:server": "esbuild --platform=node --format=cjs ./server.ts --outdir=build --sourcemap",
|
|
"build:otlpworker": "esbuild --platform=node --format=cjs --bundle ./app/v3/otlpTransformWorker.ts --outfile=build/otlpTransformWorker.cjs --sourcemap",
|
|
"build:sentry": "esbuild --platform=node --format=cjs --outbase=. ./sentry.server.ts ./app/utils/sentryTraceContext.server.ts --outdir=build --sourcemap",
|
|
"dev": "cross-env NODE_ENV=development PORT=3030 tsx ./server.ts",
|
|
"dev:worker": "cross-env NODE_PATH=../../node_modules/.pnpm/node_modules node ./build/server.js",
|
|
"format": "oxfmt .",
|
|
"lint": "oxlint -c ../../.oxlintrc.json",
|
|
"start": "cross-env NODE_ENV=production node --max-old-space-size=8192 ./build/server.js",
|
|
"start:local": "cross-env node --max-old-space-size=8192 ./build/server.js",
|
|
"typecheck": "cross-env NODE_OPTIONS=\"--max-old-space-size=8192\" tsc --noEmit -p ./tsconfig.check.json",
|
|
"guard:runops-legacy": "cross-env NODE_OPTIONS=\"--max-old-space-size=8192\" tsx ./scripts/runOpsLegacyGuard.ts",
|
|
"db:seed": "tsx seed.ts",
|
|
"db:seed:ai-spans": "tsx seed-ai-spans.mts",
|
|
"db:seed:queue-metrics": "tsx seed-queue-metrics.mts",
|
|
"upload:sourcemaps": "bash ./upload-sourcemaps.sh",
|
|
"test": "vitest --no-file-parallelism",
|
|
"eval:dev": "evalite watch"
|
|
},
|
|
"dependencies": {
|
|
"@ai-sdk/anthropic": "^3.0.0",
|
|
"@ai-sdk/openai": "^3.0.0",
|
|
"@ai-sdk/react": "^3.0.0",
|
|
"@ariakit/react": "^0.4.6",
|
|
"@ariakit/react-core": "^0.4.6",
|
|
"@aws-sdk/client-ecr": "^3.931.0",
|
|
"@aws-sdk/client-s3": "^3.936.0",
|
|
"@aws-sdk/client-sts": "^3.840.0",
|
|
"@aws-sdk/s3-presigned-post": "^3.936.0",
|
|
"@aws-sdk/s3-request-presigner": "^3.936.0",
|
|
"@better-auth/utils": "^0.2.6",
|
|
"@codemirror/autocomplete": "6.4.0",
|
|
"@codemirror/commands": "6.1.3",
|
|
"@codemirror/lang-json": "6.0.1",
|
|
"@codemirror/lang-sql": "6.5.5",
|
|
"@codemirror/language": "6.3.2",
|
|
"@codemirror/lint": "6.4.2",
|
|
"@codemirror/search": "6.2.3",
|
|
"@codemirror/state": "6.2.0",
|
|
"@codemirror/view": "6.7.2",
|
|
"@conform-to/react": "^1.2.2",
|
|
"@conform-to/zod": "^1.2.2",
|
|
"@depot/cli": "0.0.1-cli.2.80.0",
|
|
"@depot/sdk-node": "^1.0.0",
|
|
"@headlessui/react": "^1.7.8",
|
|
"@heroicons/react": "^2.0.12",
|
|
"@internal/cache": "workspace:*",
|
|
"@internal/compute": "workspace:*",
|
|
"@internal/dashboard-agent": "workspace:*",
|
|
"@internal/dashboard-agent-contracts": "workspace:*",
|
|
"@internal/dashboard-agent-db": "workspace:*",
|
|
"@internal/llm-model-catalog": "workspace:*",
|
|
"@internal/metrics-pipeline": "workspace:*",
|
|
"@internal/redis": "workspace:*",
|
|
"@internal/run-engine": "workspace:*",
|
|
"@internal/run-ops-database": "workspace:*",
|
|
"@internal/run-store": "workspace:*",
|
|
"@internal/schedule-engine": "workspace:*",
|
|
"@internal/tracing": "workspace:*",
|
|
"@internal/tsql": "workspace:*",
|
|
"@internationalized/date": "^3.5.1",
|
|
"@jsonhero/schema-infer": "^0.1.5",
|
|
"@kapaai/react-sdk": "^0.1.3",
|
|
"@lezer/highlight": "^1.1.6",
|
|
"@opentelemetry/api": "1.9.1",
|
|
"@opentelemetry/api-logs": "0.218.0",
|
|
"@opentelemetry/core": "2.7.1",
|
|
"@opentelemetry/exporter-logs-otlp-http": "0.218.0",
|
|
"@opentelemetry/exporter-metrics-otlp-proto": "0.218.0",
|
|
"@opentelemetry/exporter-trace-otlp-http": "0.218.0",
|
|
"@opentelemetry/host-metrics": "^0.38.3",
|
|
"@opentelemetry/instrumentation": "0.218.0",
|
|
"@opentelemetry/instrumentation-aws-sdk": "^0.69.0",
|
|
"@opentelemetry/instrumentation-express": "^0.62.0",
|
|
"@opentelemetry/instrumentation-http": "0.218.0",
|
|
"@opentelemetry/resource-detector-aws": "^2.14.0",
|
|
"@opentelemetry/resources": "2.7.1",
|
|
"@opentelemetry/sdk-logs": "0.218.0",
|
|
"@opentelemetry/sdk-metrics": "2.7.1",
|
|
"@opentelemetry/sdk-trace-base": "2.7.1",
|
|
"@opentelemetry/sdk-trace-node": "2.7.1",
|
|
"@opentelemetry/semantic-conventions": "1.41.1",
|
|
"@popperjs/core": "^2.11.8",
|
|
"@prisma/instrumentation": "^6.14.0",
|
|
"@radix-ui/react-accordion": "^1.2.11",
|
|
"@radix-ui/react-alert-dialog": "^1.0.4",
|
|
"@radix-ui/react-dialog": "^1.0.3",
|
|
"@radix-ui/react-popover": "^1.0.5",
|
|
"@radix-ui/react-radio-group": "^1.1.3",
|
|
"@radix-ui/react-slider": "^1.1.2",
|
|
"@radix-ui/react-switch": "^1.0.3",
|
|
"@radix-ui/react-tabs": "^1.0.3",
|
|
"@radix-ui/react-tooltip": "^1.0.5",
|
|
"@react-aria/datepicker": "^3.9.1",
|
|
"@react-stately/datepicker": "^3.9.1",
|
|
"@react-types/datepicker": "^3.7.1",
|
|
"@remix-run/express": "2.17.5",
|
|
"@remix-run/node": "2.17.5",
|
|
"@remix-run/react": "2.17.5",
|
|
"@remix-run/router": "^1.23.3",
|
|
"@remix-run/server-runtime": "2.17.5",
|
|
"@s2-dev/streamstore": "^0.25.0",
|
|
"@sentry/remix": "9.46.0",
|
|
"@slack/web-api": "7.16.0",
|
|
"@socket.io/redis-adapter": "^8.3.0",
|
|
"@streamdown/code": "^1.1.1",
|
|
"@tabler/icons-react": "^3.36.1",
|
|
"@tanstack/match-sorter-utils": "^8.19.4",
|
|
"@tanstack/react-table": "^8.21.3",
|
|
"@tanstack/react-virtual": "^3.0.4",
|
|
"@team-plain/graphql": "^1.3.0",
|
|
"@team-plain/ui-components": "^5.0.0",
|
|
"@trigger.dev/companyicons": "^1.5.35",
|
|
"@trigger.dev/core": "workspace:*",
|
|
"@trigger.dev/database": "workspace:*",
|
|
"@trigger.dev/otlp-importer": "workspace:*",
|
|
"@trigger.dev/platform": "1.3.0",
|
|
"@trigger.dev/plugins": "workspace:*",
|
|
"@trigger.dev/rbac": "workspace:*",
|
|
"@trigger.dev/redis-worker": "workspace:*",
|
|
"@trigger.dev/sdk": "workspace:*",
|
|
"@trigger.dev/sso": "workspace:*",
|
|
"@uiw/react-codemirror": "^4.19.5",
|
|
"@unkey/cache": "^1.5.0",
|
|
"@unkey/error": "^0.2.0",
|
|
"@upstash/ratelimit": "^1.1.3",
|
|
"@vercel/sdk": "^1.19.1",
|
|
"@window-splitter/react": "1.1.3",
|
|
"ai": "^6.0.116",
|
|
"assert": "^2.1.0",
|
|
"assert-never": "^1.2.1",
|
|
"aws4fetch": "^1.0.18",
|
|
"class-variance-authority": "^0.5.2",
|
|
"clsx": "^1.2.1",
|
|
"compression": "^1.7.4",
|
|
"cookie": "^0.6.0",
|
|
"cron-parser": "^4.9.0",
|
|
"cronstrue": "^2.21.0",
|
|
"cross-env": "^7.0.3",
|
|
"cuid": "^2.1.8",
|
|
"date-fns": "^4.1.0",
|
|
"dompurify": "^3.4.1",
|
|
"dotenv": "^16.4.5",
|
|
"effect": "^3.21.2",
|
|
"emails": "workspace:*",
|
|
"evt": "^2.4.13",
|
|
"express": "4.20.0",
|
|
"framer-motion": "^10.12.11",
|
|
"input-otp": "^1.4.2",
|
|
"intl-parse-accept-language": "^1.0.0",
|
|
"ioredis": "~5.6.0",
|
|
"isbot": "^3.6.5",
|
|
"jose": "^5.4.0",
|
|
"json-stable-stringify": "^1.3.0",
|
|
"jsonpointer": "^5.0.1",
|
|
"lodash.omit": "^4.5.0",
|
|
"lru-cache": "^11.2.4",
|
|
"lucide-react": "^0.229.0",
|
|
"marked": "^4.0.18",
|
|
"match-sorter": "^6.3.4",
|
|
"morgan": "^1.10.0",
|
|
"nanoid": "3.3.8",
|
|
"neverthrow": "^8.2.0",
|
|
"non.geist": "^1.0.2",
|
|
"octokit": "^3.2.1",
|
|
"openai": "^4.33.1",
|
|
"p-limit": "^6.2.0",
|
|
"p-map": "^6.0.0",
|
|
"p-retry": "^4.6.1",
|
|
"parse-duration": "^2.1.0",
|
|
"pg": "8.15.6",
|
|
"@prisma/adapter-pg": "6.14.0",
|
|
"posthog-js": "^1.93.3",
|
|
"posthog-node": "5.35.6",
|
|
"prism-react-renderer": "^2.3.1",
|
|
"prismjs": "^1.30.0",
|
|
"prom-client": "^15.1.0",
|
|
"prop-types": "^15.8.1",
|
|
"qrcode.react": "^4.2.0",
|
|
"random-words": "^2.0.0",
|
|
"react": "^18.2.0",
|
|
"react-day-picker": "^9.13.0",
|
|
"react-dom": "^18.2.0",
|
|
"react-grid-layout": "^2.2.2",
|
|
"react-hotkeys-hook": "^4.4.1",
|
|
"react-markdown": "^10.1.0",
|
|
"react-popper": "^2.3.0",
|
|
"react-resizable": "^3.1.3",
|
|
"react-use": "17.5.1",
|
|
"recharts": "^2.15.2",
|
|
"regression": "^2.0.1",
|
|
"remix-auth": "^3.6.0",
|
|
"remix-auth-email-link": "2.0.2",
|
|
"remix-auth-github": "^1.6.0",
|
|
"remix-auth-google": "^2.0.0",
|
|
"remix-typedjson": "0.3.1",
|
|
"remix-utils": "^7.7.0",
|
|
"semver": "^7.5.0",
|
|
"simplur": "^3.0.1",
|
|
"slug": "^6.0.0",
|
|
"socket.io": "4.7.4",
|
|
"socket.io-adapter": "^2.5.4",
|
|
"sonner": "^1.0.3",
|
|
"sql-formatter": "^15.4.10",
|
|
"streamdown": "^2.5.0",
|
|
"superjson": "^2.2.1",
|
|
"tailwind-merge": "^3.6.0",
|
|
"tailwind-scrollbar-hide": "^4.0.0",
|
|
"tiny-invariant": "^1.2.0",
|
|
"tw-animate-css": "^1.4.0",
|
|
"util": "^0.12.5",
|
|
"uuid": "^14.0.0",
|
|
"ws": "^8.11.0",
|
|
"zod": "3.25.76",
|
|
"zod-error": "1.5.0",
|
|
"zod-validation-error": "^1.5.0"
|
|
},
|
|
"devDependencies": {
|
|
"@internal/clickhouse": "workspace:*",
|
|
"@internal/replication": "workspace:*",
|
|
"@internal/testcontainers": "workspace:*",
|
|
"@playwright/test": "^1.36.2",
|
|
"@remix-run/dev": "2.17.5",
|
|
"@remix-run/testing": "^2.17.5",
|
|
"@sentry/cli": "2.50.2",
|
|
"@swc/core": "^1.3.4",
|
|
"@swc/helpers": "^0.4.11",
|
|
"@tailwindcss/forms": "^0.5.11",
|
|
"@tailwindcss/postcss": "^4.3.1",
|
|
"@tailwindcss/typography": "^0.5.20",
|
|
"@testcontainers/postgresql": "^11.14.0",
|
|
"@total-typescript/ts-reset": "^0.4.2",
|
|
"@types/bcryptjs": "^2.4.2",
|
|
"@types/compression": "^1.7.2",
|
|
"@types/cookie": "^0.6.0",
|
|
"@types/express": "^4.17.13",
|
|
"@types/json-query": "^2.2.3",
|
|
"@types/marked": "^4.0.3",
|
|
"@types/morgan": "^1.9.3",
|
|
"@types/node-fetch": "^2.6.2",
|
|
"@types/pg": "^8.11.10",
|
|
"@types/prismjs": "^1.26.0",
|
|
"@types/qs": "^6.9.7",
|
|
"@types/react": "18.2.69",
|
|
"@types/react-dom": "18.2.7",
|
|
"@types/regression": "^2.0.6",
|
|
"@types/semver": "^7.5.0",
|
|
"@types/slug": "^5.0.3",
|
|
"@types/supertest": "^6.0.2",
|
|
"@types/tar": "^6.1.4",
|
|
"@types/ws": "^8.5.3",
|
|
"autoevals": "^0.0.130",
|
|
"css-loader": "^6.10.0",
|
|
"datepicker": "link:@types/@react-aria/datepicker",
|
|
"engine.io": "^6.5.4",
|
|
"esbuild": "^0.15.10",
|
|
"evalite": "1.0.0-beta.16",
|
|
"npm-run-all": "^4.1.5",
|
|
"postcss-import": "^16.0.1",
|
|
"postcss-loader": "^8.1.1",
|
|
"rimraf": "^6.0.1",
|
|
"style-loader": "^3.3.4",
|
|
"supertest": "^7.0.0",
|
|
"tailwind-scrollbar": "^4.0.2",
|
|
"tailwindcss": "^4.3.1",
|
|
"tsconfig-paths": "^3.14.1",
|
|
"tsx": "^4.20.6",
|
|
"typescript": "catalog:",
|
|
"typescript-legacy-api": "npm:typescript@6.0.3",
|
|
"vite-tsconfig-paths": "^5.1.4",
|
|
"vite": "^6.4.2"
|
|
},
|
|
"engines": {
|
|
"node": ">=18.19.0 || >=20.6.0"
|
|
}
|
|
}
|