From 45696579233527dade718676f6d9622d69409f9b Mon Sep 17 00:00:00 2001 From: Katia Bulatova Date: Tue, 11 Aug 2026 18:56:14 +0200 Subject: [PATCH] =?UTF-8?q?feat(webapp):=20dashboard=20agent=20=E2=80=94?= =?UTF-8?q?=20chat,=20reports,=20investigate=20(#4418)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## 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. --- .changeset/chat-stream-mid-turn-reconnect.md | 7 + .claude/skills/errors-api-e2e/SKILL.md | 2 +- .gitattributes | 5 + .gitignore | 2 + .server-changes/dashboard-agent.md | 8 + apps/webapp/.gitignore | 5 +- apps/webapp/app/components/AskAI.tsx | 9 + .../code/StreamdownRenderer.test.ts | 80 + .../components/code/StreamdownRenderer.tsx | 30 +- .../components/code/tsql/tsqlLinter.test.ts | 7 +- .../dashboard-agent/message-limits.test.ts | 63 + .../dashboard-agent/message-limits.ts | 51 + .../dashboard-agent/resolve-uris.test.ts | 32 + .../dashboard-agent/resolve-uris.ts | 25 + .../app/components/metrics/MiniLineChart.tsx | 12 +- .../components/navigation/SideMenuItem.tsx | 17 +- .../components/primitives/AgentDotMatrix.tsx | 17 + .../app/components/primitives/Buttons.tsx | 40 +- .../app/components/primitives/Popover.tsx | 7 +- .../app/components/primitives/Spinner.tsx | 14 + .../app/components/primitives/TextLink.tsx | 4 + .../app/components/primitives/Toast.tsx | 20 +- .../app/components/queues/queue-thresholds.ts | 2 + .../runs/v3/agent/AgentMessageView.tsx | 6 +- apps/webapp/app/entry.server.tsx | 31 + apps/webapp/app/env.server.ts | 5 +- apps/webapp/app/hooks/useThemeMode.ts | 24 + .../v3/reports/ReportPresenter.server.ts | 67 +- .../presenters/v3/reports/health/execution.ts | 10 +- .../app/presenters/v3/reports/health/flow.ts | 93 +- .../v3/reports/health/health-core.ts | 172 +- .../v3/reports/health/health-data.ts | 429 ++-- .../v3/reports/health/health-messages.ts | 51 +- .../presenters/v3/reports/health/health.ts | 123 +- .../presenters/v3/reports/health/liveness.ts | 5 +- .../presenters/v3/reports/renderMarkdown.ts | 534 ++-- .../v3/reports/report-layout.test.ts | 101 + .../presenters/v3/reports/report-layout.ts | 668 +++++ .../v3/reports/report-message-catalogs.ts | 5 - .../presenters/v3/reports/report-messages.ts | 16 +- .../presenters/v3/reports/report-registry.ts | 31 +- .../v3/reports/report-view-model.ts | 220 +- .../v3/reports/reportsApi.server.ts | 36 + .../v3/reports/reportsApiAuth.server.ts | 14 + apps/webapp/app/root.tsx | 10 +- .../route.tsx | 5 + .../route.tsx | 12 +- .../app/routes/account.tokens/route.tsx | 14 +- .../routes/api.v1.auth.user-actor-token.ts | 4 +- .../api.v1.dashboard-agent.eval-policy.ts | 40 + apps/webapp/app/routes/api.v1.orgs.ts | 66 +- .../api.v1.projects.$projectRef.$env.jwt.ts | 87 +- ...projects.$projectRef.$env.repo.snapshot.ts | 48 +- ...cts.$projectRef.$env.runs.$runId.commit.ts | 113 + ...jects.$projectRef.$env.workers.$tagName.ts | 35 +- ...pi.v1.projects.$projectRef.environments.ts | 13 +- .../api.v1.projects.$projectRef.runs.ts | 15 +- apps/webapp/app/routes/api.v1.projects.ts | 75 +- apps/webapp/app/routes/api.v1.query.ts | 12 +- .../api.v1.queues.$queueParam.metrics.ts | 144 ++ apps/webapp/app/routes/api.v1.reports.$key.ts | 65 +- .../routes/projects.$projectRef.ai-help.ts | 5 +- ...aram.env.$envParam.dashboard-agent.in.$.ts | 137 +- ...jectParam.env.$envParam.dashboard-agent.ts | 323 ++- .../app/routes/storybook.ai-agent/route.tsx | 10 +- .../app/routes/storybook.buttons/route.tsx | 12 +- apps/webapp/app/services/apiAuth.server.ts | 50 +- .../app/services/apiRateLimit.server.ts | 109 +- .../app/services/dashboardAgent.server.ts | 9 +- .../services/dashboardAgentBodyCap.server.ts | 73 + .../dashboardAgentChatRetention.server.ts | 74 + ...dashboardAgentEnvironmentAddress.server.ts | 37 + .../dashboardAgentEvalPolicy.server.ts | 54 + .../dashboardAgentEvalRetention.server.ts | 58 + .../dashboardAgentHeadStart.server.ts | 125 +- ...dashboardAgentInvestigationSweep.server.ts | 112 + .../app/services/deleteOrganization.server.ts | 18 + .../environmentVariableApiAccess.server.ts | 3 +- .../services/personalAccessToken.server.ts | 51 +- .../app/services/queryService.server.ts | 5 +- .../app/services/resolveTriggerUri.server.ts | 162 ++ .../routeBuilders/apiBuilder.server.ts | 83 +- .../app/services/tenantContext.server.ts | 15 +- .../app/services/uatRoutePreamble.server.ts | 49 + .../services/userActorEnvironment.server.ts | 126 + apps/webapp/app/tailwind.css | 31 +- .../utils/boundedRequestBody.server.test.ts | 45 + .../app/utils/boundedRequestBody.server.ts | 35 + apps/webapp/app/utils/cspImageOrigins.test.ts | 164 ++ apps/webapp/app/utils/cspImageOrigins.ts | 120 + .../app/v3/canAccessDashboardAgent.server.ts | 12 +- apps/webapp/app/v3/commonWorker.server.ts | 78 +- apps/webapp/app/v3/detectQueryTables.ts | 3 + apps/webapp/app/v3/featureFlags.server.ts | 24 +- apps/webapp/app/v3/featureFlags.ts | 31 + apps/webapp/app/v3/queryScope.ts | 42 + apps/webapp/app/v3/queueDepthSeries.ts | 49 + .../v3/services/alerts/deliverAlert.server.ts | 3 + apps/webapp/package.json | 3 +- apps/webapp/seed-queue-metrics.mts | 120 +- apps/webapp/server.ts | 5 + apps/webapp/test/apiAuthActorClaim.test.ts | 109 + apps/webapp/test/apiRateLimitJwtActor.test.ts | 104 + apps/webapp/test/contextlessPatRoutes.test.ts | 238 ++ .../webapp/test/dashboardAgentBodyCap.test.ts | 164 ++ .../test/dashboardAgentChatRetention.test.ts | 194 ++ .../test/dashboardAgentClientMetadata.test.ts | 162 ++ .../dashboardAgentCreateChatOrdering.test.ts | 209 ++ .../test/dashboardAgentEvalPolicyAuth.test.ts | 132 + .../test/dashboardAgentEvalRetention.test.ts | 135 + .../test/dashboardAgentHeadStart.test.ts | 67 + .../test/dashboardAgentImageCsp.test.ts | 45 + .../dashboardAgentInProxyMintFailure.test.ts | 98 + .../dashboardAgentInvestigationSweep.test.ts | 395 +++ ...dashboardAgentLegacyMessagesColumn.test.ts | 104 + ...shboardAgentQueriesTenantIsolation.test.ts | 75 + apps/webapp/test/dashboardAgentRoutes.test.ts | 281 +++ .../dashboardAgentTranscriptStore.test.ts | 698 ++++++ apps/webapp/test/detectQueryTables.test.ts | 11 + apps/webapp/test/envJwtActorClaim.test.ts | 116 + apps/webapp/test/featureFlags.test.ts | 119 + .../projectEnvironmentsBranchScope.test.ts | 179 ++ apps/webapp/test/queryScope.test.ts | 48 + apps/webapp/test/queueDepthSeries.test.ts | 58 + apps/webapp/test/rbacFallbackBranch.test.ts | 93 +- apps/webapp/test/reportCurationTrust.test.ts | 93 + apps/webapp/test/reportHealth.test.ts | 364 ++- apps/webapp/test/reportHealthData.test.ts | 295 ++- apps/webapp/test/reportMetricDelta.test.ts | 111 + apps/webapp/test/reportPresenter.test.ts | 203 ++ apps/webapp/test/reportTrust.test.ts | 114 + apps/webapp/test/reportsApiRoute.test.ts | 175 ++ apps/webapp/test/resolveTriggerUri.test.ts | 195 ++ apps/webapp/test/routeCspImgSrc.test.ts | 163 ++ .../test/runCommitAuthorization.test.ts | 153 ++ .../tenantContextFromAuthEnvironment.test.ts | 14 + .../patRouteBuilderIdentityOnly.types.ts | 27 + apps/webapp/test/uatEnvironmentClaim.test.ts | 707 ++++++ ...rActorEnvironmentScopeRouteBuilder.test.ts | 265 ++ .../test/userActorPatOnlyBoundary.test.ts | 245 ++ .../test/userActorProjectWideScope.test.ts | 346 +++ apps/webapp/test/userActorSourcePat.test.ts | 151 ++ .../userActorTokenClaimsAndScopes.test.ts | 252 ++ apps/webapp/vite.config.ts | 5 +- apps/webapp/vitest.config.ts | 3 + docs/self-hosting/env/webapp.mdx | 1 + .../dashboard-agent-contracts/package.json | 23 + .../src/blocks.test.ts | 589 +++++ .../dashboard-agent-contracts/src/blocks.ts | 638 +++++ .../src/contracts.test.ts | 183 ++ .../dashboard-agent-contracts/src/evidence.ts | 107 + .../dashboard-agent-contracts/src/index.ts | 11 + .../dashboard-agent-contracts/src/intent.ts | 22 + .../src/page-context.ts | 168 ++ .../src/run-filters.ts | 27 + .../src/suggested-prompts.ts | 17 + .../src/trigger-uri.test.ts | 214 ++ .../src/trigger-uri.ts | 300 +++ .../dashboard-agent-contracts/tsconfig.json | 18 + .../vitest.config.ts | 11 + .../dashboard-agent-db/README.md | 49 +- .../0002_watches_and_chat_messages.sql | 119 + .../drizzle/meta/0002_snapshot.json | 1293 ++++++++++ .../drizzle/meta/_journal.json | 7 + .../dashboard-agent-db/package.json | 1 + .../dashboard-agent-db/src/ids.ts | 17 + .../dashboard-agent-db/src/index.ts | 1 + .../dashboard-agent-db/src/internal.ts | 7 + .../dashboard-agent-db/src/queries.ts | 1023 +++++++- .../dashboard-agent-db/src/schema-base.ts | 4 + .../dashboard-agent-db/src/schema.ts | 148 +- .../dashboard-agent-db/src/watch-schema.ts | 250 ++ internal-packages/dashboard-agent/README.md | 36 + .../dashboard-agent/package.json | 7 +- .../__snapshots__/prompt-prefix.test.ts.snap | 38 + .../dashboard-agent/src/agent-runtime.ts | 401 +++ .../src/cache-breakpoint.test.ts | 75 + .../dashboard-agent/src/compaction.test.ts | 426 ++++ .../dashboard-agent/src/compaction.ts | 297 +++ .../src/dashboard-agent.eval.ts | 903 ++++++- .../src/dashboard-agent.test.ts | 2184 +++++++++++++++-- .../dashboard-agent/src/dashboard-agent.ts | 638 +++-- .../src/eval-error-category.test.ts | 175 ++ .../dashboard-agent/src/eval-policy.ts | 433 ++++ .../src/eval-redaction.test.ts | 211 ++ .../dashboard-agent/src/eval-turn.ts | 53 +- .../dashboard-agent/src/index.ts | 12 +- .../dashboard-agent/src/prompt-prefix.test.ts | 162 ++ .../dashboard-agent/src/prompt-prefix.ts | 132 + .../dashboard-agent/src/repo-tools.test.ts | 54 +- .../dashboard-agent/src/repo-tools.ts | 50 +- .../dashboard-agent/src/step-cache.test.ts | 199 ++ .../dashboard-agent/src/step-cache.ts | 139 ++ .../dashboard-agent/src/test-support.ts | 209 ++ .../src/tool-api-branch.test.ts | 207 ++ .../dashboard-agent/src/tool-api-client.ts | 246 ++ .../src/tool-api-paths.test.ts | 66 + .../src/tool-api-transport.test.ts | 101 + .../dashboard-agent/src/tool-api.ts | 447 ++++ .../src/tool-ask-support.test.ts | 72 + .../dashboard-agent/src/tool-context.ts | 32 + .../dashboard-agent/src/tool-curation.test.ts | 122 + .../dashboard-agent/src/tool-curation.ts | 317 +++ .../dashboard-agent/src/tool-docs.test.ts | 51 + .../dashboard-agent/src/tool-docs.ts | 126 + .../dashboard-agent/src/tool-evidence.ts | 161 ++ .../src/tool-investigations.ts | 224 ++ .../dashboard-agent/src/tool-navigation.ts | 93 + .../dashboard-agent/src/tool-schemas.ts | 421 ++-- .../dashboard-agent/src/tool-source-ledger.ts | 116 + .../dashboard-agent/src/tools.ts | 555 +---- .../dashboard-agent/vitest.eval.config.ts | 2 +- internal-packages/rbac/src/fallback.ts | 11 +- internal-packages/rbac/src/index.ts | 3 + internal-packages/tsql/src/read-only.test.ts | 90 + packages/cli-v3/src/apiClient.ts | 23 +- packages/cli-v3/src/commands/mint-token.ts | 4 +- packages/cli-v3/src/mcp/prompts.test.ts | 69 + packages/cli-v3/src/mcp/prompts.ts | 96 +- packages/cli-v3/src/mcp/schemas.ts | 22 +- packages/core/src/v3/apiClient/index.ts | 21 +- packages/core/src/v3/apiClient/runStream.ts | 8 + packages/core/src/v3/schemas/index.ts | 1 + packages/core/src/v3/schemas/reports.ts | 174 ++ packages/plugins/src/rbac.ts | 36 +- packages/trigger-sdk/src/v3/chat.test.ts | 164 ++ packages/trigger-sdk/src/v3/chat.ts | 97 +- pnpm-lock.yaml | 128 +- 228 files changed, 27251 insertions(+), 3175 deletions(-) create mode 100644 .changeset/chat-stream-mid-turn-reconnect.md create mode 100644 .gitattributes create mode 100644 .server-changes/dashboard-agent.md create mode 100644 apps/webapp/app/components/code/StreamdownRenderer.test.ts create mode 100644 apps/webapp/app/components/dashboard-agent/message-limits.test.ts create mode 100644 apps/webapp/app/components/dashboard-agent/message-limits.ts create mode 100644 apps/webapp/app/components/dashboard-agent/resolve-uris.test.ts create mode 100644 apps/webapp/app/components/dashboard-agent/resolve-uris.ts create mode 100644 apps/webapp/app/components/queues/queue-thresholds.ts create mode 100644 apps/webapp/app/hooks/useThemeMode.ts create mode 100644 apps/webapp/app/presenters/v3/reports/report-layout.test.ts create mode 100644 apps/webapp/app/presenters/v3/reports/report-layout.ts create mode 100644 apps/webapp/app/presenters/v3/reports/reportsApi.server.ts create mode 100644 apps/webapp/app/presenters/v3/reports/reportsApiAuth.server.ts create mode 100644 apps/webapp/app/routes/api.v1.dashboard-agent.eval-policy.ts create mode 100644 apps/webapp/app/routes/api.v1.projects.$projectRef.$env.runs.$runId.commit.ts create mode 100644 apps/webapp/app/routes/api.v1.queues.$queueParam.metrics.ts create mode 100644 apps/webapp/app/services/dashboardAgentBodyCap.server.ts create mode 100644 apps/webapp/app/services/dashboardAgentChatRetention.server.ts create mode 100644 apps/webapp/app/services/dashboardAgentEnvironmentAddress.server.ts create mode 100644 apps/webapp/app/services/dashboardAgentEvalPolicy.server.ts create mode 100644 apps/webapp/app/services/dashboardAgentEvalRetention.server.ts create mode 100644 apps/webapp/app/services/dashboardAgentInvestigationSweep.server.ts create mode 100644 apps/webapp/app/services/resolveTriggerUri.server.ts create mode 100644 apps/webapp/app/services/uatRoutePreamble.server.ts create mode 100644 apps/webapp/app/services/userActorEnvironment.server.ts create mode 100644 apps/webapp/app/utils/boundedRequestBody.server.test.ts create mode 100644 apps/webapp/app/utils/boundedRequestBody.server.ts create mode 100644 apps/webapp/app/utils/cspImageOrigins.test.ts create mode 100644 apps/webapp/app/utils/cspImageOrigins.ts create mode 100644 apps/webapp/app/v3/queryScope.ts create mode 100644 apps/webapp/app/v3/queueDepthSeries.ts create mode 100644 apps/webapp/test/apiAuthActorClaim.test.ts create mode 100644 apps/webapp/test/apiRateLimitJwtActor.test.ts create mode 100644 apps/webapp/test/contextlessPatRoutes.test.ts create mode 100644 apps/webapp/test/dashboardAgentBodyCap.test.ts create mode 100644 apps/webapp/test/dashboardAgentChatRetention.test.ts create mode 100644 apps/webapp/test/dashboardAgentClientMetadata.test.ts create mode 100644 apps/webapp/test/dashboardAgentCreateChatOrdering.test.ts create mode 100644 apps/webapp/test/dashboardAgentEvalPolicyAuth.test.ts create mode 100644 apps/webapp/test/dashboardAgentEvalRetention.test.ts create mode 100644 apps/webapp/test/dashboardAgentHeadStart.test.ts create mode 100644 apps/webapp/test/dashboardAgentImageCsp.test.ts create mode 100644 apps/webapp/test/dashboardAgentInProxyMintFailure.test.ts create mode 100644 apps/webapp/test/dashboardAgentInvestigationSweep.test.ts create mode 100644 apps/webapp/test/dashboardAgentLegacyMessagesColumn.test.ts create mode 100644 apps/webapp/test/dashboardAgentQueriesTenantIsolation.test.ts create mode 100644 apps/webapp/test/dashboardAgentRoutes.test.ts create mode 100644 apps/webapp/test/dashboardAgentTranscriptStore.test.ts create mode 100644 apps/webapp/test/envJwtActorClaim.test.ts create mode 100644 apps/webapp/test/featureFlags.test.ts create mode 100644 apps/webapp/test/projectEnvironmentsBranchScope.test.ts create mode 100644 apps/webapp/test/queryScope.test.ts create mode 100644 apps/webapp/test/queueDepthSeries.test.ts create mode 100644 apps/webapp/test/reportCurationTrust.test.ts create mode 100644 apps/webapp/test/reportMetricDelta.test.ts create mode 100644 apps/webapp/test/reportPresenter.test.ts create mode 100644 apps/webapp/test/reportTrust.test.ts create mode 100644 apps/webapp/test/reportsApiRoute.test.ts create mode 100644 apps/webapp/test/resolveTriggerUri.test.ts create mode 100644 apps/webapp/test/routeCspImgSrc.test.ts create mode 100644 apps/webapp/test/runCommitAuthorization.test.ts create mode 100644 apps/webapp/test/types/patRouteBuilderIdentityOnly.types.ts create mode 100644 apps/webapp/test/uatEnvironmentClaim.test.ts create mode 100644 apps/webapp/test/userActorEnvironmentScopeRouteBuilder.test.ts create mode 100644 apps/webapp/test/userActorPatOnlyBoundary.test.ts create mode 100644 apps/webapp/test/userActorProjectWideScope.test.ts create mode 100644 apps/webapp/test/userActorSourcePat.test.ts create mode 100644 apps/webapp/test/userActorTokenClaimsAndScopes.test.ts create mode 100644 internal-packages/dashboard-agent-contracts/package.json create mode 100644 internal-packages/dashboard-agent-contracts/src/blocks.test.ts create mode 100644 internal-packages/dashboard-agent-contracts/src/blocks.ts create mode 100644 internal-packages/dashboard-agent-contracts/src/contracts.test.ts create mode 100644 internal-packages/dashboard-agent-contracts/src/evidence.ts create mode 100644 internal-packages/dashboard-agent-contracts/src/index.ts create mode 100644 internal-packages/dashboard-agent-contracts/src/intent.ts create mode 100644 internal-packages/dashboard-agent-contracts/src/page-context.ts create mode 100644 internal-packages/dashboard-agent-contracts/src/run-filters.ts create mode 100644 internal-packages/dashboard-agent-contracts/src/suggested-prompts.ts create mode 100644 internal-packages/dashboard-agent-contracts/src/trigger-uri.test.ts create mode 100644 internal-packages/dashboard-agent-contracts/src/trigger-uri.ts create mode 100644 internal-packages/dashboard-agent-contracts/tsconfig.json create mode 100644 internal-packages/dashboard-agent-contracts/vitest.config.ts create mode 100644 internal-packages/dashboard-agent-db/drizzle/0002_watches_and_chat_messages.sql create mode 100644 internal-packages/dashboard-agent-db/drizzle/meta/0002_snapshot.json create mode 100644 internal-packages/dashboard-agent-db/src/ids.ts create mode 100644 internal-packages/dashboard-agent-db/src/internal.ts create mode 100644 internal-packages/dashboard-agent-db/src/schema-base.ts create mode 100644 internal-packages/dashboard-agent-db/src/watch-schema.ts create mode 100644 internal-packages/dashboard-agent/src/__snapshots__/prompt-prefix.test.ts.snap create mode 100644 internal-packages/dashboard-agent/src/agent-runtime.ts create mode 100644 internal-packages/dashboard-agent/src/cache-breakpoint.test.ts create mode 100644 internal-packages/dashboard-agent/src/compaction.test.ts create mode 100644 internal-packages/dashboard-agent/src/compaction.ts create mode 100644 internal-packages/dashboard-agent/src/eval-error-category.test.ts create mode 100644 internal-packages/dashboard-agent/src/eval-policy.ts create mode 100644 internal-packages/dashboard-agent/src/eval-redaction.test.ts create mode 100644 internal-packages/dashboard-agent/src/prompt-prefix.test.ts create mode 100644 internal-packages/dashboard-agent/src/prompt-prefix.ts create mode 100644 internal-packages/dashboard-agent/src/step-cache.test.ts create mode 100644 internal-packages/dashboard-agent/src/step-cache.ts create mode 100644 internal-packages/dashboard-agent/src/test-support.ts create mode 100644 internal-packages/dashboard-agent/src/tool-api-branch.test.ts create mode 100644 internal-packages/dashboard-agent/src/tool-api-client.ts create mode 100644 internal-packages/dashboard-agent/src/tool-api-paths.test.ts create mode 100644 internal-packages/dashboard-agent/src/tool-api-transport.test.ts create mode 100644 internal-packages/dashboard-agent/src/tool-api.ts create mode 100644 internal-packages/dashboard-agent/src/tool-ask-support.test.ts create mode 100644 internal-packages/dashboard-agent/src/tool-context.ts create mode 100644 internal-packages/dashboard-agent/src/tool-curation.test.ts create mode 100644 internal-packages/dashboard-agent/src/tool-curation.ts create mode 100644 internal-packages/dashboard-agent/src/tool-docs.test.ts create mode 100644 internal-packages/dashboard-agent/src/tool-docs.ts create mode 100644 internal-packages/dashboard-agent/src/tool-evidence.ts create mode 100644 internal-packages/dashboard-agent/src/tool-investigations.ts create mode 100644 internal-packages/dashboard-agent/src/tool-navigation.ts create mode 100644 internal-packages/dashboard-agent/src/tool-source-ledger.ts create mode 100644 internal-packages/tsql/src/read-only.test.ts create mode 100644 packages/cli-v3/src/mcp/prompts.test.ts create mode 100644 packages/core/src/v3/schemas/reports.ts diff --git a/.changeset/chat-stream-mid-turn-reconnect.md b/.changeset/chat-stream-mid-turn-reconnect.md new file mode 100644 index 000000000..5a929f3cd --- /dev/null +++ b/.changeset/chat-stream-mid-turn-reconnect.md @@ -0,0 +1,7 @@ +--- +"@trigger.dev/core": patch +"@trigger.dev/sdk": patch +"trigger.dev": patch +--- + +Chat in the browser now reconnects when the connection drops mid-turn, instead of leaving the reply stuck as if it were still generating. Reports can be fetched as structured data with the `json` format, and the shortest report period is now one minute (`30m`, `1h`, `7d`). The `mint-token` command's help is clearer too: a token minted without `--cap` is read-only, and `--ttl` shows the correct maximum lifetime of 7 days. diff --git a/.claude/skills/errors-api-e2e/SKILL.md b/.claude/skills/errors-api-e2e/SKILL.md index e74c50bf5..162526ee4 100644 --- a/.claude/skills/errors-api-e2e/SKILL.md +++ b/.claude/skills/errors-api-e2e/SKILL.md @@ -154,7 +154,7 @@ PASS: one run, `run_` (status maps to `FAILED`). Proves `filter[error]` -> ### 6. Attribution — `mint-token` -> JWT exchange records the acting user ```bash -TOKEN=$(cli mint-token --profile $PROFILE --client errors-api-e2e 2>/dev/null) # UAT +TOKEN=$(cli mint-token --profile $PROFILE --client errors-api-e2e --cap read:errors,write:errors 2>/dev/null) # UAT ENVJWT=$(curl -sS -X POST "$B/api/v1/projects/$REF/dev/jwt" -H "Authorization: Bearer $TOKEN" \ -H 'Content-Type: application/json' -d '{"claims":{"scopes":["read:errors","write:errors"]}}' \ | python3 -c "import sys,json;print(json.load(sys.stdin)['token'])") diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 000000000..9af426021 --- /dev/null +++ b/.gitattributes @@ -0,0 +1,5 @@ +# Generated, not hand-written: collapsed in diffs and excluded from language stats. +internal-packages/dashboard-agent-db/drizzle/meta/*.json linguist-generated=true +internal-packages/dashboard-agent-db/drizzle/meta/** linguist-generated=true +**/__snapshots__/*.snap linguist-generated=true +pnpm-lock.yaml linguist-generated=true diff --git a/.gitignore b/.gitignore index f540927e3..b11dded2b 100644 --- a/.gitignore +++ b/.gitignore @@ -85,3 +85,5 @@ ailogger-output.log # observability-map CLI output artifact, not committed observability-map.json + +.claude/worktrees/ diff --git a/.server-changes/dashboard-agent.md b/.server-changes/dashboard-agent.md new file mode 100644 index 000000000..617e60089 --- /dev/null +++ b/.server-changes/dashboard-agent.md @@ -0,0 +1,8 @@ +--- +area: webapp +type: feature +--- + +Meet the dashboard agent: a chat in every environment that answers questions about your runs, queues, errors and health with real data and links, replacing Ask AI everywhere it used to appear. Investigate a failed run, an error, a backed-up queue or a run that hasn't started to get a worked-through answer — what happened, why, and how to fix it, with every claim linked to the runs, errors and deploys behind it. It reads your data read-only, works on preview and dev branches with that branch's own data, and reads the same everywhere — dashboard, terminal, editor. A very long chat keeps working: the agent summarises the earlier part and carries on. + +A sample of conversations is scored automatically so the agent keeps getting better; only the score and a one-line summary are kept, never your messages, data or code, and we can switch it off for your organization on request. The Docs button is gone from page headers — ask the agent instead, or open Documentation from Help & Feedback. Separately, a queue's wait times, peak depth, throughput and throttling can now be read from the API. diff --git a/apps/webapp/.gitignore b/apps/webapp/.gitignore index 595ab180e..f825411d6 100644 --- a/apps/webapp/.gitignore +++ b/apps/webapp/.gitignore @@ -7,6 +7,9 @@ node_modules /cypress/screenshots /cypress/videos +# Output of `pnpm run agent-ui:screenshots` +/screenshots + /app/styles/tailwind.css # Ensure the .env symlink is not removed by accident @@ -20,4 +23,4 @@ storybook-static /prisma/seed.js /prisma/populate.js -.memory-snapshots \ No newline at end of file +.memory-snapshots diff --git a/apps/webapp/app/components/AskAI.tsx b/apps/webapp/app/components/AskAI.tsx index d61ea0055..389d5e9e5 100644 --- a/apps/webapp/app/components/AskAI.tsx +++ b/apps/webapp/app/components/AskAI.tsx @@ -1,3 +1,9 @@ +/** + * @deprecated Superseded by the dashboard agent (`components/dashboard-agent`). Nothing mounts + * this any more — every Ask AI entry point now opens Ask Trigger. Kept until the agent has + * shipped, then removed along with `@kapaai/react-sdk` and `KAPA_AI_WEBSITE_ID`. + */ + import { ArrowPathIcon, ArrowUpIcon, @@ -81,6 +87,8 @@ function useAskAIState() { * it around the popover, not inside, so the dialog and shortcut survive the popover closing. * `children` receives the open function, or undefined when Ask AI is unavailable (self-hosted, no * Kapa website id, or SSR). + * + * @deprecated See the note at the top of this file. */ export function AskAIRoot({ children, @@ -137,6 +145,7 @@ function AskAIRootProvider({ ); } +/** @deprecated See the note at the top of this file. */ export function AskAI({ isCollapsed = false }: { isCollapsed?: boolean }) { const { isManagedCloud } = useFeatures(); const websiteId = useKapaWebsiteId(); diff --git a/apps/webapp/app/components/code/StreamdownRenderer.test.ts b/apps/webapp/app/components/code/StreamdownRenderer.test.ts new file mode 100644 index 000000000..994f82ff2 --- /dev/null +++ b/apps/webapp/app/components/code/StreamdownRenderer.test.ts @@ -0,0 +1,80 @@ +import { createElement } from "react"; +import { renderToStaticMarkup } from "react-dom/server"; +import { describe, expect, it } from "vitest"; +import { restrictModelUrls, StreamdownRenderer } from "./StreamdownRenderer"; + +// streamdown calls urlTransform(url, key, node) to compute each url attribute; a +// returned undefined removes the attribute, so no request is ever issued. +const img = { tagName: "img" } as any; +const link = { tagName: "a" } as any; + +describe("restrictModelUrls (image src)", () => { + it("drops a remote model-authored image (the favicon beacon)", () => { + expect( + restrictModelUrls("https://www.google.com/s2/favicons?domain=evil", "src", img) + ).toBeUndefined(); + }); + + it("drops any absolute or protocol-relative remote image", () => { + expect(restrictModelUrls("http://evil.tld/pixel.gif", "src", img)).toBeUndefined(); + expect(restrictModelUrls("//evil.tld/pixel.gif", "src", img)).toBeUndefined(); + }); + + it("keeps inline and same-origin images", () => { + expect(restrictModelUrls("data:image/png;base64,AAAA", "src", img)).toBe( + "data:image/png;base64,AAAA" + ); + expect(restrictModelUrls("blob:abc", "src", img)).toBe("blob:abc"); + expect(restrictModelUrls("/local/pic.png", "src", img)).toBe("/local/pic.png"); + }); +}); + +describe("restrictModelUrls (link href)", () => { + it("keeps http(s), mailto and relative links", () => { + expect(restrictModelUrls("https://trigger.dev/docs", "href", link)).toBe( + "https://trigger.dev/docs" + ); + expect(restrictModelUrls("http://example.com", "href", link)).toBe("http://example.com"); + expect(restrictModelUrls("mailto:hi@trigger.dev", "href", link)).toBe("mailto:hi@trigger.dev"); + expect(restrictModelUrls("/runs/123", "href", link)).toBe("/runs/123"); + }); + + it("drops unsafe link schemes", () => { + expect(restrictModelUrls("javascript:alert(1)", "href", link)).toBeUndefined(); + expect(restrictModelUrls("data:text/html,