c06005b353
## Summary Adds an in-dashboard AI agent: a chat panel, reachable from any environment page, that answers questions about your runs, errors, tasks, and analytics, diagnoses why a run failed, charts your data, reads your connected repo's source, and answers product and how-to questions. It is gated behind the `hasDashboardAgentAccess` feature flag (global or per-org, default off), so this PR ships disabled: the launcher is hidden unless the flag is enabled. ## Design The agent runs as a standalone `chat.agent` Trigger task in its own internal package, with no access to the webapp database, Prisma, or ClickHouse. It reads the user's data over the public API, acting as the user via a short-lived delegated user-actor token minted server-side each turn (never in the browser), building on [#3997](https://github.com/triggerdotdev/trigger.dev/pull/3997). The error and analytics tools use [#4005](https://github.com/triggerdotdev/trigger.dev/pull/4005) and the TRQL query API. The first turn of a new chat streams from a warm webapp route (Head Start) while the durable agent boots in parallel. Structured answers (a run-failure diagnosis card, a live chart) render through a small typed view catalog rather than arbitrary markup. A knowledge lane forwards product and how-to questions to the support assistant. Conversation history lives in a separate Drizzle-backed store on its own Postgres schema, kept as a display read-model so it can never corrupt the agent's model context. The SDK changes add an `apiClient` option to `chat.createStartSessionAction` and `chat.headStart`, and keep the Head Start tool-approval tail intact across a custom `prepareMessages` hook so prompt caching and Head Start compose.
34 lines
1.2 KiB
TypeScript
34 lines
1.2 KiB
TypeScript
// Load the monorepo root .env (the package's .env is a symlink to it) so the
|
|
// real-model evals pick up ANTHROPIC_API_KEY without the caller exporting it.
|
|
// Runs as a vitest setupFile before the eval modules are imported, so the
|
|
// `ANTHROPIC_API_KEY` gate in dashboard-agent.eval.ts sees the loaded value.
|
|
import { existsSync, readFileSync } from "node:fs";
|
|
import { dirname, resolve } from "node:path";
|
|
import { fileURLToPath } from "node:url";
|
|
|
|
const here = dirname(fileURLToPath(import.meta.url));
|
|
const candidates = [
|
|
resolve(here, ".env"), // package symlink to the root .env
|
|
resolve(here, "../../.env"), // monorepo root
|
|
resolve(process.cwd(), ".env"),
|
|
];
|
|
|
|
for (const path of candidates) {
|
|
if (!existsSync(path)) continue;
|
|
for (const line of readFileSync(path, "utf8").split("\n")) {
|
|
const match = /^\s*([A-Z0-9_]+)\s*=\s*(.*)$/.exec(line);
|
|
if (!match) continue;
|
|
const key = match[1]!;
|
|
if (process.env[key] !== undefined) continue;
|
|
let value = match[2]!.trim();
|
|
if (
|
|
(value.startsWith('"') && value.endsWith('"')) ||
|
|
(value.startsWith("'") && value.endsWith("'"))
|
|
) {
|
|
value = value.slice(1, -1);
|
|
}
|
|
process.env[key] = value;
|
|
}
|
|
break;
|
|
}
|