From 85ce7fe5019ef900f4ddf12787ecb1be566926c4 Mon Sep 17 00:00:00 2001 From: Eric Allam Date: Thu, 2 Jul 2026 23:11:19 +0100 Subject: [PATCH 1/3] feat(webapp): pin the dashboard agent to a deployed version The dashboard agent (a chat.agent in its own Trigger.dev project) can now be deployed independently of the app that talks to it. A new workflow deploys the agent with --skip-promotion, so a deploy never becomes current on its own, and the app pins its sessions to a specific version via DASHBOARD_AGENT_VERSION (unset falls back to the project env's current version). The pin flows through to session start, head start, and every continuation run, so cutting over is a config flip rather than a redeploy. --- .github/workflows/dashboard-agent-deploy.yml | 71 +++++++++++++++++++ apps/webapp/app/env.server.ts | 3 + .../app/services/dashboardAgent.server.ts | 11 ++- .../dashboardAgentHeadStart.server.ts | 3 +- 4 files changed, 86 insertions(+), 2 deletions(-) create mode 100644 .github/workflows/dashboard-agent-deploy.yml diff --git a/.github/workflows/dashboard-agent-deploy.yml b/.github/workflows/dashboard-agent-deploy.yml new file mode 100644 index 00000000000..ce1527e4d5c --- /dev/null +++ b/.github/workflows/dashboard-agent-deploy.yml @@ -0,0 +1,71 @@ +name: "🤖 Deploy dashboard agent" + +# Deploys the @internal/dashboard-agent chat.agent to its Trigger.dev project +# with --skip-promotion, so a deploy never becomes "current" on its own. The +# consuming app cuts over by pinning DASHBOARD_AGENT_VERSION to the new version. +# Pushes to main that touch the agent auto-deploy to staging; prod is a manual +# workflow_dispatch. The project ref + a scoped deploy PAT come from the target +# environment (dashboard-agent-staging / dashboard-agent-prod). + +on: + push: + branches: [main] + paths: + - "internal-packages/dashboard-agent/**" + workflow_dispatch: + inputs: + environment: + description: "Trigger.dev environment to deploy to" + type: choice + options: [staging, prod] + default: staging + +permissions: {} + +concurrency: + group: dashboard-agent-deploy-${{ github.event.inputs.environment || 'staging' }} + cancel-in-progress: false + +jobs: + deploy: + name: Deploy dashboard agent + runs-on: ubuntu-latest + environment: dashboard-agent-${{ github.event.inputs.environment || 'staging' }} + permissions: + contents: read + env: + TRIGGER_API_URL: https://api.trigger.dev + TRIGGER_DASHBOARD_AGENT_PROJECT_REF: ${{ vars.TRIGGER_DASHBOARD_AGENT_PROJECT_REF }} + steps: + - name: Checkout + uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 + with: + persist-credentials: false + + - name: Setup pnpm + uses: pnpm/action-setup@fc06bc1257f339d1d5d8b3a19a8cae5388b55320 # v5.0.0 + with: + version: 10.33.2 + + - name: Setup node + uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0 + with: + node-version: 20.20.2 + cache: "pnpm" + + - name: Install + build the CLI and the agent's deps + run: | + set -euo pipefail + pnpm install --frozen-lockfile + # Prisma client is needed because the build closure pulls in @trigger.dev/database. + pnpm run generate + # Config-time imports the agent's trigger.config.ts needs: defineConfig (sdk), aptGet (build). + pnpm run build --filter trigger.dev --filter @trigger.dev/build --filter @trigger.dev/sdk + + - name: Deploy (--skip-promotion) + working-directory: internal-packages/dashboard-agent + env: + TRIGGER_ACCESS_TOKEN: ${{ secrets.TRIGGER_DASHBOARD_AGENT_DEPLOY_TOKEN }} + # Invoke the built CLI directly (what the workspace .bin/trigger wrapper does), + # so a not-yet-linked bin after a fresh install can't break the deploy. + run: node ../../packages/cli-v3/dist/esm/index.js deploy --skip-promotion --env ${{ github.event.inputs.environment || 'staging' }} diff --git a/apps/webapp/app/env.server.ts b/apps/webapp/app/env.server.ts index 7c57733ad8f..2462a3fdd30 100644 --- a/apps/webapp/app/env.server.ts +++ b/apps/webapp/app/env.server.ts @@ -106,6 +106,9 @@ const EnvironmentSchema = z // standard chat.agent SDK flow. When unset, the live agent is disabled — the // conversation store / History still work, no chat can start. DASHBOARD_AGENT_SECRET_KEY: z.string().optional(), + // Pins agent sessions to a specific deployed version (paired with + // --skip-promotion deploys); unset => the project env's current version. + DASHBOARD_AGENT_VERSION: z.string().optional(), // Global default for the `hasDashboardAgentAccess` flag. "0" (off) ships the // agent dark; flip to "1" to enable it for everyone at GA. Per-org overrides // (org featureFlags) win regardless. diff --git a/apps/webapp/app/services/dashboardAgent.server.ts b/apps/webapp/app/services/dashboardAgent.server.ts index 8e8b8884e3e..a66882b72cc 100644 --- a/apps/webapp/app/services/dashboardAgent.server.ts +++ b/apps/webapp/app/services/dashboardAgent.server.ts @@ -57,13 +57,22 @@ export function isDashboardAgentConfigured(): boolean { return Boolean(env.DASHBOARD_AGENT_SECRET_KEY); } +// Pins every agent session (and its continuation runs) to a deployed version +// when DASHBOARD_AGENT_VERSION is set; unset runs on the env's current version. +export function dashboardAgentTriggerConfig(): { lockToVersion: string } | undefined { + return env.DASHBOARD_AGENT_VERSION ? { lockToVersion: env.DASHBOARD_AGENT_VERSION } : undefined; +} + export async function startDashboardAgentSession(params: { chatId: string; clientData?: Record; }): Promise<{ publicAccessToken: string }> { const config = dashboardAgentConfig(); if (!config) throw new Error("DASHBOARD_AGENT_SECRET_KEY is not set"); - const startSession = chat.createStartSessionAction(TASK_ID, { apiClient: config }); + const startSession = chat.createStartSessionAction(TASK_ID, { + apiClient: config, + triggerConfig: dashboardAgentTriggerConfig(), + }); return startSession({ chatId: params.chatId, clientData: params.clientData }); } diff --git a/apps/webapp/app/services/dashboardAgentHeadStart.server.ts b/apps/webapp/app/services/dashboardAgentHeadStart.server.ts index d30f8da7ab9..3e62fe73892 100644 --- a/apps/webapp/app/services/dashboardAgentHeadStart.server.ts +++ b/apps/webapp/app/services/dashboardAgentHeadStart.server.ts @@ -9,7 +9,7 @@ import { import { chat as chatServer } from "@trigger.dev/sdk/chat-server"; import { streamText, type UIMessage } from "ai"; import { env } from "~/env.server"; -import { dashboardAgentApiOrigin } from "~/services/dashboardAgent.server"; +import { dashboardAgentApiOrigin, dashboardAgentTriggerConfig } from "~/services/dashboardAgent.server"; import { logger } from "~/services/logger.server"; const TASK_ID = "dashboard-agent"; @@ -43,6 +43,7 @@ export async function startDashboardAgentHeadStart(params: { chatId: params.chatId, messages: params.messages, metadata: params.metadata, + triggerConfig: dashboardAgentTriggerConfig(), // Scope session creation + the agent trigger to the agent's project/env. The // Anthropic key here only powers the warm step-1 call. apiClient: { From 3cbff2ab7af03e02955115eef254bc45a09e1cd5 Mon Sep 17 00:00:00 2001 From: Eric Allam Date: Thu, 2 Jul 2026 23:31:13 +0100 Subject: [PATCH 2/3] chore: deploy the dashboard agent per environment with gates Deploy a leg per environment (staging + prod) via a matrix, each gated by its own environment, and trigger on pushes to main that touch the agent or its store. Both deploys are --skip-promotion, so they stage dormant versions the app pins to independently. --- .github/workflows/dashboard-agent-deploy.yml | 31 ++++++++++---------- 1 file changed, 15 insertions(+), 16 deletions(-) diff --git a/.github/workflows/dashboard-agent-deploy.yml b/.github/workflows/dashboard-agent-deploy.yml index ce1527e4d5c..6317fe7c61b 100644 --- a/.github/workflows/dashboard-agent-deploy.yml +++ b/.github/workflows/dashboard-agent-deploy.yml @@ -3,34 +3,33 @@ name: "🤖 Deploy dashboard agent" # Deploys the @internal/dashboard-agent chat.agent to its Trigger.dev project # with --skip-promotion, so a deploy never becomes "current" on its own. The # consuming app cuts over by pinning DASHBOARD_AGENT_VERSION to the new version. -# Pushes to main that touch the agent auto-deploy to staging; prod is a manual -# workflow_dispatch. The project ref + a scoped deploy PAT come from the target -# environment (dashboard-agent-staging / dashboard-agent-prod). +# Runs a leg per environment (staging + prod), each gated by its own environment; +# a push to main that touches the agent or its store triggers both. Version +# numbers are per-environment, so pin each environment to its own leg's version. on: push: branches: [main] paths: - "internal-packages/dashboard-agent/**" + - "internal-packages/dashboard-agent-db/**" workflow_dispatch: - inputs: - environment: - description: "Trigger.dev environment to deploy to" - type: choice - options: [staging, prod] - default: staging permissions: {} -concurrency: - group: dashboard-agent-deploy-${{ github.event.inputs.environment || 'staging' }} - cancel-in-progress: false - jobs: deploy: - name: Deploy dashboard agent + name: Deploy dashboard agent (${{ matrix.environment }}) runs-on: ubuntu-latest - environment: dashboard-agent-${{ github.event.inputs.environment || 'staging' }} + strategy: + fail-fast: false + matrix: + environment: [staging, prod] + # Per-environment reviewer gate + source of the scoped deploy PAT. + environment: dashboard-agent-${{ matrix.environment }} + concurrency: + group: dashboard-agent-deploy-${{ matrix.environment }} + cancel-in-progress: false permissions: contents: read env: @@ -68,4 +67,4 @@ jobs: TRIGGER_ACCESS_TOKEN: ${{ secrets.TRIGGER_DASHBOARD_AGENT_DEPLOY_TOKEN }} # Invoke the built CLI directly (what the workspace .bin/trigger wrapper does), # so a not-yet-linked bin after a fresh install can't break the deploy. - run: node ../../packages/cli-v3/dist/esm/index.js deploy --skip-promotion --env ${{ github.event.inputs.environment || 'staging' }} + run: node ../../packages/cli-v3/dist/esm/index.js deploy --skip-promotion --env ${{ matrix.environment }} From 994700045d7ff3176348268d05aad2f6a82f5a05 Mon Sep 17 00:00:00 2001 From: Eric Allam Date: Fri, 3 Jul 2026 09:11:21 +0100 Subject: [PATCH 3/3] chore(webapp): satisfy oxfmt on the head-start import --- apps/webapp/app/services/dashboardAgentHeadStart.server.ts | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/apps/webapp/app/services/dashboardAgentHeadStart.server.ts b/apps/webapp/app/services/dashboardAgentHeadStart.server.ts index 3e62fe73892..9c411c8040e 100644 --- a/apps/webapp/app/services/dashboardAgentHeadStart.server.ts +++ b/apps/webapp/app/services/dashboardAgentHeadStart.server.ts @@ -9,7 +9,10 @@ import { import { chat as chatServer } from "@trigger.dev/sdk/chat-server"; import { streamText, type UIMessage } from "ai"; import { env } from "~/env.server"; -import { dashboardAgentApiOrigin, dashboardAgentTriggerConfig } from "~/services/dashboardAgent.server"; +import { + dashboardAgentApiOrigin, + dashboardAgentTriggerConfig, +} from "~/services/dashboardAgent.server"; import { logger } from "~/services/logger.server"; const TASK_ID = "dashboard-agent";