From 1984b34d5d9e000bd9073d74263f9a14670ff16a Mon Sep 17 00:00:00 2001 From: James Ritchie Date: Mon, 22 Jun 2026 12:57:49 +0100 Subject: [PATCH 1/3] feat(webapp,database): show a Test column for agent sessions Sessions started from the agent Test playground were tagged with a "playground" tag that rendered in the Sessions table. They are now flagged with a real Session.isTest boolean (mirroring TaskRun.isTest) and shown as a dedicated "Test" column with a check icon, to the left of Tags, on both the Sessions page and the Agent page, plus a matching property on the session detail page. isTest is a new Session column replicated to ClickHouse sessions_v1. The playground action sets isTest instead of writing the tag, and a migration backfills existing sessions (isTest where the legacy tag is present) and removes the now-redundant tag. --- .server-changes/sessions-test-column.md | 6 ++++++ .../app/components/sessions/v1/SessionsTable.tsx | 13 +++++++++++-- .../presenters/v3/SessionListPresenter.server.ts | 1 + .../app/presenters/v3/SessionPresenter.server.ts | 1 + .../route.tsx | 12 +++++++++++- ...projectParam.env.$envParam.playground.action.tsx | 5 ++++- .../services/sessionsReplicationService.server.ts | 1 + .../clickhouseSessionsRepository.server.ts | 1 + .../sessionsRepository/sessionsRepository.server.ts | 1 + apps/webapp/test/sessionsReplicationService.test.ts | 2 ++ .../schema/034_add_is_test_to_sessions_v1.sql | 11 +++++++++++ internal-packages/clickhouse/src/sessions.ts | 4 ++++ .../migration.sql | 12 ++++++++++++ internal-packages/database/prisma/schema.prisma | 4 ++++ 14 files changed, 70 insertions(+), 4 deletions(-) create mode 100644 .server-changes/sessions-test-column.md create mode 100644 internal-packages/clickhouse/schema/034_add_is_test_to_sessions_v1.sql create mode 100644 internal-packages/database/prisma/migrations/20260621000000_add_is_test_to_session/migration.sql diff --git a/.server-changes/sessions-test-column.md b/.server-changes/sessions-test-column.md new file mode 100644 index 00000000000..cde091ccfca --- /dev/null +++ b/.server-changes/sessions-test-column.md @@ -0,0 +1,6 @@ +--- +area: webapp +type: feature +--- + +Agent sessions started from the Test playground are now flagged with a real `Session.isTest` boolean instead of a `"playground"` tag, surfaced as a dedicated "Test" column (check icon) in the Sessions table on both the Sessions and Agent pages, plus a matching property on the session detail page. Existing sessions are backfilled and the now-redundant tag is removed. diff --git a/apps/webapp/app/components/sessions/v1/SessionsTable.tsx b/apps/webapp/app/components/sessions/v1/SessionsTable.tsx index ac9b5edbde8..5f5ffe1d89c 100644 --- a/apps/webapp/app/components/sessions/v1/SessionsTable.tsx +++ b/apps/webapp/app/components/sessions/v1/SessionsTable.tsx @@ -1,4 +1,5 @@ import { ArrowRightIcon } from "@heroicons/react/20/solid"; +import { CheckIcon } from "@heroicons/react/24/solid"; import { useLocation, useNavigation } from "@remix-run/react"; import { formatDuration } from "@trigger.dev/core/v3/utils/durations"; import { RunsIconExtraSmall } from "~/assets/icons/RunsIcon"; @@ -87,6 +88,7 @@ export function SessionsTable({ Type Agent ID + Test Tags Created Duration @@ -97,7 +99,7 @@ export function SessionsTable({ {sessions.length === 0 ? ( - +
{hasFilters @@ -144,6 +146,13 @@ export function SessionsTable({
+ + {session.isTest ? ( + + ) : ( + "–" + )} + {session.tags.length > 0 ? (
@@ -168,7 +177,7 @@ export function SessionsTable({ )} {isLoading && ( Loading… diff --git a/apps/webapp/app/presenters/v3/SessionListPresenter.server.ts b/apps/webapp/app/presenters/v3/SessionListPresenter.server.ts index 0586ab8eced..1cfeba60f0a 100644 --- a/apps/webapp/app/presenters/v3/SessionListPresenter.server.ts +++ b/apps/webapp/app/presenters/v3/SessionListPresenter.server.ts @@ -221,6 +221,7 @@ export class SessionListPresenter { externalId: session.externalId, type: session.type, taskIdentifier: session.taskIdentifier, + isTest: session.isTest, tags: session.tags ? [...session.tags].sort((a, b) => a.localeCompare(b)) : [], status, closedAt: session.closedAt ? session.closedAt.toISOString() : undefined, diff --git a/apps/webapp/app/presenters/v3/SessionPresenter.server.ts b/apps/webapp/app/presenters/v3/SessionPresenter.server.ts index c63f9e39a2a..3316c106b42 100644 --- a/apps/webapp/app/presenters/v3/SessionPresenter.server.ts +++ b/apps/webapp/app/presenters/v3/SessionPresenter.server.ts @@ -170,6 +170,7 @@ export class SessionPresenter { externalId: session.externalId, type: session.type, taskIdentifier: session.taskIdentifier, + isTest: session.isTest, tags: session.tags ? [...session.tags].sort((a, b) => a.localeCompare(b)) : [], metadata: session.metadata, triggerConfig: session.triggerConfig, diff --git a/apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.env.$envParam.sessions.$sessionParam/route.tsx b/apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.env.$envParam.sessions.$sessionParam/route.tsx index 2bf62b8cad2..ef38afaa211 100644 --- a/apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.env.$envParam.sessions.$sessionParam/route.tsx +++ b/apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.env.$envParam.sessions.$sessionParam/route.tsx @@ -1,5 +1,5 @@ import { BoltIcon, BoltSlashIcon } from "@heroicons/react/20/solid"; -import { BookOpenIcon } from "@heroicons/react/24/solid"; +import { BookOpenIcon, CheckIcon } from "@heroicons/react/24/solid"; import { type MetaFunction } from "@remix-run/react"; import { type LoaderFunctionArgs } from "@remix-run/server-runtime"; import { useVirtualizer } from "@tanstack/react-virtual"; @@ -818,6 +818,16 @@ function OverviewTab({ session, status }: { session: LoadedSession; status: Sess {session.taskIdentifier} + + Test + + {session.isTest ? ( + + ) : ( + + )} + + {session.currentRun ? ( Current run diff --git a/apps/webapp/app/routes/resources.orgs.$organizationSlug.projects.$projectParam.env.$envParam.playground.action.tsx b/apps/webapp/app/routes/resources.orgs.$organizationSlug.projects.$projectParam.env.$envParam.playground.action.tsx index 0fab90e1457..fdcd31d19c7 100644 --- a/apps/webapp/app/routes/resources.orgs.$organizationSlug.projects.$projectParam.env.$envParam.playground.action.tsx +++ b/apps/webapp/app/routes/resources.orgs.$organizationSlug.projects.$projectParam.env.$envParam.playground.action.tsx @@ -161,7 +161,10 @@ export const action = async ({ request, params }: ActionFunctionArgs) => { type: "chat.agent", taskIdentifier: agentSlug, triggerConfig: triggerConfig as unknown as Prisma.InputJsonValue, - tags: ["playground"], + // Mark as a Test session — surfaced via the Test column in the + // Sessions table. Session tags stay empty; the triggered run still + // carries "playground:true" via triggerConfig.tags. + isTest: true, projectId: project.id, runtimeEnvironmentId: environment.id, environmentType: environment.type, diff --git a/apps/webapp/app/services/sessionsReplicationService.server.ts b/apps/webapp/app/services/sessionsReplicationService.server.ts index 12b66e29dfb..da202458ca8 100644 --- a/apps/webapp/app/services/sessionsReplicationService.server.ts +++ b/apps/webapp/app/services/sessionsReplicationService.server.ts @@ -802,6 +802,7 @@ function toSessionInsertArray( session.expiresAt ? session.expiresAt.getTime() : null, session.createdAt.getTime(), session.updatedAt.getTime(), + session.isTest ?? false, version.toString(), isDeleted ? 1 : 0, ]; diff --git a/apps/webapp/app/services/sessionsRepository/clickhouseSessionsRepository.server.ts b/apps/webapp/app/services/sessionsRepository/clickhouseSessionsRepository.server.ts index aebf61628fa..b66faf4d3e0 100644 --- a/apps/webapp/app/services/sessionsRepository/clickhouseSessionsRepository.server.ts +++ b/apps/webapp/app/services/sessionsRepository/clickhouseSessionsRepository.server.ts @@ -93,6 +93,7 @@ export class ClickHouseSessionsRepository implements ISessionsRepository { externalId: true, type: true, taskIdentifier: true, + isTest: true, tags: true, metadata: true, closedAt: true, diff --git a/apps/webapp/app/services/sessionsRepository/sessionsRepository.server.ts b/apps/webapp/app/services/sessionsRepository/sessionsRepository.server.ts index 245f1df2295..67fdda60707 100644 --- a/apps/webapp/app/services/sessionsRepository/sessionsRepository.server.ts +++ b/apps/webapp/app/services/sessionsRepository/sessionsRepository.server.ts @@ -87,6 +87,7 @@ export type ListedSession = Prisma.SessionGetPayload<{ externalId: true; type: true; taskIdentifier: true; + isTest: true; tags: true; metadata: true; closedAt: true; diff --git a/apps/webapp/test/sessionsReplicationService.test.ts b/apps/webapp/test/sessionsReplicationService.test.ts index 1d3c761e813..cb171ed5b54 100644 --- a/apps/webapp/test/sessionsReplicationService.test.ts +++ b/apps/webapp/test/sessionsReplicationService.test.ts @@ -80,6 +80,7 @@ describe("SessionsReplicationService", () => { }, tags: ["user:42", "plan:pro"], metadata: { plan: "pro", seats: 3 }, + isTest: true, }, }); @@ -108,6 +109,7 @@ describe("SessionsReplicationService", () => { environment_type: "DEVELOPMENT", task_identifier: "my-agent", tags: ["user:42", "plan:pro"], + is_test: 1, _is_deleted: 0, }) ); diff --git a/internal-packages/clickhouse/schema/034_add_is_test_to_sessions_v1.sql b/internal-packages/clickhouse/schema/034_add_is_test_to_sessions_v1.sql new file mode 100644 index 00000000000..8d2f46e1249 --- /dev/null +++ b/internal-packages/clickhouse/schema/034_add_is_test_to_sessions_v1.sql @@ -0,0 +1,11 @@ +-- +goose Up +-- Existing rows default to 0 and are intentionally NOT backfilled: the Sessions +-- list reads isTest from Postgres (ClickHouse only supplies session IDs), so the +-- UI is correct without it. A backfill is only needed if a ClickHouse-side +-- isTest filter/aggregate over sessions_v1 is added later. +ALTER TABLE trigger_dev.sessions_v1 + ADD COLUMN IF NOT EXISTS is_test UInt8 DEFAULT 0; + +-- +goose Down +ALTER TABLE trigger_dev.sessions_v1 + DROP COLUMN IF EXISTS is_test; diff --git a/internal-packages/clickhouse/src/sessions.ts b/internal-packages/clickhouse/src/sessions.ts index 567fe65511e..995e98447e0 100644 --- a/internal-packages/clickhouse/src/sessions.ts +++ b/internal-packages/clickhouse/src/sessions.ts @@ -19,6 +19,7 @@ export const SessionV1 = z.object({ expires_at: z.number().int().nullish(), created_at: z.number().int(), updated_at: z.number().int(), + is_test: z.boolean().default(false), _version: z.string(), _is_deleted: z.number().int().default(0), }); @@ -43,6 +44,7 @@ export const SESSION_COLUMNS = [ "expires_at", "created_at", "updated_at", + "is_test", "_version", "_is_deleted", ] as const; @@ -70,6 +72,7 @@ export type SessionFieldTypes = { expires_at: number | null; created_at: number; updated_at: number; + is_test: boolean; _version: string; _is_deleted: number; }; @@ -95,6 +98,7 @@ export type SessionInsertArray = [ expires_at: number | null, created_at: number, updated_at: number, + is_test: boolean, _version: string, _is_deleted: number, ]; diff --git a/internal-packages/database/prisma/migrations/20260621000000_add_is_test_to_session/migration.sql b/internal-packages/database/prisma/migrations/20260621000000_add_is_test_to_session/migration.sql new file mode 100644 index 00000000000..2798ea2f9f2 --- /dev/null +++ b/internal-packages/database/prisma/migrations/20260621000000_add_is_test_to_session/migration.sql @@ -0,0 +1,12 @@ +-- AlterTable +ALTER TABLE "Session" ADD COLUMN "isTest" BOOLEAN NOT NULL DEFAULT false; + +-- Backfill: legacy Test sessions were marked with a "playground" tag. Flag them +-- as test and strip the now-redundant tag (the Test column replaces it), so the +-- list and detail views render consistently without read-time tag filtering. +-- Bounded one-shot over the playground subset; matches existing in-migration +-- backfill precedent. (Prisma wraps each migration in a single transaction, so +-- batching with intermediate commits isn't possible here.) +UPDATE "Session" +SET "isTest" = true, "tags" = array_remove("tags", 'playground') +WHERE 'playground' = ANY("tags"); diff --git a/internal-packages/database/prisma/schema.prisma b/internal-packages/database/prisma/schema.prisma index bb80da3a7ec..9658e664347 100644 --- a/internal-packages/database/prisma/schema.prisma +++ b/internal-packages/database/prisma/schema.prisma @@ -805,6 +805,10 @@ model Session { /// (chatId, messages, trigger) are merged at trigger time. triggerConfig Json + /// Whether this session was created from the Test/playground UI rather + /// than by a real chat.agent() trigger. Mirrors TaskRun.isTest. + isTest Boolean @default(false) + tags String[] @default([]) metadata Json? From 52c96dcd9492dd4d796582c7359106a32667eab4 Mon Sep 17 00:00:00 2001 From: James Ritchie Date: Mon, 22 Jun 2026 13:10:26 +0100 Subject: [PATCH 2/3] fix(webapp): add screen-reader text for the Test column cells The Test cell on the Sessions table and the Test property on the session detail page rendered an icon or dash with no text equivalent. Add an sr-only Yes/No value and mark the check icon and dash aria-hidden. --- .../app/components/sessions/v1/SessionsTable.tsx | 10 ++++++++-- .../route.tsx | 7 +++++-- 2 files changed, 13 insertions(+), 4 deletions(-) diff --git a/apps/webapp/app/components/sessions/v1/SessionsTable.tsx b/apps/webapp/app/components/sessions/v1/SessionsTable.tsx index 5f5ffe1d89c..eb6475d3e25 100644 --- a/apps/webapp/app/components/sessions/v1/SessionsTable.tsx +++ b/apps/webapp/app/components/sessions/v1/SessionsTable.tsx @@ -147,10 +147,16 @@ export function SessionsTable({
+ {session.isTest ? "Yes" : "No"} {session.isTest ? ( - + ) : ( - "–" + + – + )} diff --git a/apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.env.$envParam.sessions.$sessionParam/route.tsx b/apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.env.$envParam.sessions.$sessionParam/route.tsx index ef38afaa211..b9bd387159b 100644 --- a/apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.env.$envParam.sessions.$sessionParam/route.tsx +++ b/apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.env.$envParam.sessions.$sessionParam/route.tsx @@ -821,10 +821,13 @@ function OverviewTab({ session, status }: { session: LoadedSession; status: Sess Test + {session.isTest ? "Yes" : "No"} {session.isTest ? ( - + ) : ( - + + – + )} From 77623d86d59afb2f5fe22b760d2ba4a3dc9b285d Mon Sep 17 00:00:00 2001 From: James Ritchie Date: Mon, 22 Jun 2026 14:05:39 +0100 Subject: [PATCH 3/3] refactor(webapp,database): drop isTest backfill, hide legacy tag in UI The Session.isTest backfill (and the array_remove of the "playground" tag) is removed from the migration, which now only adds the column. Existing sessions keep isTest=false; only sessions created from the Test playground going forward set it. The legacy "playground" tag is hidden from the Tags display in both the sessions list and detail presenters (via LEGACY_PLAYGROUND_TAG) so it does not surface on pre-isTest rows, without mutating historical data. --- .server-changes/sessions-test-column.md | 2 +- .../app/presenters/v3/SessionListPresenter.server.ts | 8 +++++++- .../app/presenters/v3/SessionPresenter.server.ts | 8 +++++++- .../sessionsRepository/sessionsRepository.server.ts | 7 +++++++ .../migration.sql | 10 ---------- 5 files changed, 22 insertions(+), 13 deletions(-) diff --git a/.server-changes/sessions-test-column.md b/.server-changes/sessions-test-column.md index cde091ccfca..4e28034b2bc 100644 --- a/.server-changes/sessions-test-column.md +++ b/.server-changes/sessions-test-column.md @@ -3,4 +3,4 @@ area: webapp type: feature --- -Agent sessions started from the Test playground are now flagged with a real `Session.isTest` boolean instead of a `"playground"` tag, surfaced as a dedicated "Test" column (check icon) in the Sessions table on both the Sessions and Agent pages, plus a matching property on the session detail page. Existing sessions are backfilled and the now-redundant tag is removed. +Agent sessions started from the Test playground are now flagged with a real `Session.isTest` boolean instead of a `"playground"` tag, surfaced as a dedicated "Test" column (check icon) in the Sessions table on both the Sessions and Agent pages, plus a matching property on the session detail page. The legacy `"playground"` tag is hidden from the Tags display on pre-existing sessions. diff --git a/apps/webapp/app/presenters/v3/SessionListPresenter.server.ts b/apps/webapp/app/presenters/v3/SessionListPresenter.server.ts index ab194afc139..a5bcd37744b 100644 --- a/apps/webapp/app/presenters/v3/SessionListPresenter.server.ts +++ b/apps/webapp/app/presenters/v3/SessionListPresenter.server.ts @@ -7,6 +7,7 @@ import { findDisplayableEnvironment } from "~/models/runtimeEnvironment.server"; import { type SessionStatus, SessionsRepository, + LEGACY_PLAYGROUND_TAG, } from "~/services/sessionsRepository/sessionsRepository.server"; import { ServiceValidationError } from "~/v3/services/baseService.server"; import { findCurrentWorkerFromEnvironment } from "~/v3/models/workerDeployment.server"; @@ -226,7 +227,12 @@ export class SessionListPresenter { type: session.type, taskIdentifier: session.taskIdentifier, isTest: session.isTest, - tags: session.tags ? [...session.tags].sort((a, b) => a.localeCompare(b)) : [], + // Hide the legacy "playground" tag (pre-isTest sessions) from display. + tags: session.tags + ? [...session.tags] + .filter((t) => t !== LEGACY_PLAYGROUND_TAG) + .sort((a, b) => a.localeCompare(b)) + : [], status, closedAt: session.closedAt ? session.closedAt.toISOString() : undefined, closedReason: session.closedReason ?? undefined, diff --git a/apps/webapp/app/presenters/v3/SessionPresenter.server.ts b/apps/webapp/app/presenters/v3/SessionPresenter.server.ts index e2ec9abe9e2..7b4183426c8 100644 --- a/apps/webapp/app/presenters/v3/SessionPresenter.server.ts +++ b/apps/webapp/app/presenters/v3/SessionPresenter.server.ts @@ -4,6 +4,7 @@ import { env } from "~/env.server"; import { findDisplayableEnvironment } from "~/models/runtimeEnvironment.server"; import { chatSnapshotStorageKey } from "~/services/realtime/chatSnapshot.server"; import { resolveSessionByIdOrExternalId } from "~/services/realtime/sessions.server"; +import { LEGACY_PLAYGROUND_TAG } from "~/services/sessionsRepository/sessionsRepository.server"; import { logger } from "~/services/logger.server"; import { generatePresignedUrl } from "~/v3/objectStore.server"; import { runStore } from "~/v3/runStore.server"; @@ -178,7 +179,12 @@ export class SessionPresenter { type: session.type, taskIdentifier: session.taskIdentifier, isTest: session.isTest, - tags: session.tags ? [...session.tags].sort((a, b) => a.localeCompare(b)) : [], + // Hide the legacy "playground" tag (pre-isTest sessions) from display. + tags: session.tags + ? [...session.tags] + .filter((t) => t !== LEGACY_PLAYGROUND_TAG) + .sort((a, b) => a.localeCompare(b)) + : [], metadata: session.metadata, triggerConfig: session.triggerConfig, streamBasinName: session.streamBasinName, diff --git a/apps/webapp/app/services/sessionsRepository/sessionsRepository.server.ts b/apps/webapp/app/services/sessionsRepository/sessionsRepository.server.ts index 67fdda60707..5a808051035 100644 --- a/apps/webapp/app/services/sessionsRepository/sessionsRepository.server.ts +++ b/apps/webapp/app/services/sessionsRepository/sessionsRepository.server.ts @@ -24,6 +24,13 @@ export type SessionsRepositoryOptions = { export const SessionStatus = z.enum(["ACTIVE", "CLOSED", "EXPIRED"]); export type SessionStatus = z.infer; +/** + * Legacy marker tag for sessions created from the Test/playground before the + * `Session.isTest` boolean existed. New sessions set `isTest` instead; this tag + * is hidden from the Tags display so it doesn't surface on pre-isTest rows. + */ +export const LEGACY_PLAYGROUND_TAG = "playground"; + const SessionListInputOptionsSchema = z.object({ organizationId: z.string(), projectId: z.string(), diff --git a/internal-packages/database/prisma/migrations/20260621000000_add_is_test_to_session/migration.sql b/internal-packages/database/prisma/migrations/20260621000000_add_is_test_to_session/migration.sql index 2798ea2f9f2..5e50a167f21 100644 --- a/internal-packages/database/prisma/migrations/20260621000000_add_is_test_to_session/migration.sql +++ b/internal-packages/database/prisma/migrations/20260621000000_add_is_test_to_session/migration.sql @@ -1,12 +1,2 @@ -- AlterTable ALTER TABLE "Session" ADD COLUMN "isTest" BOOLEAN NOT NULL DEFAULT false; - --- Backfill: legacy Test sessions were marked with a "playground" tag. Flag them --- as test and strip the now-redundant tag (the Test column replaces it), so the --- list and detail views render consistently without read-time tag filtering. --- Bounded one-shot over the playground subset; matches existing in-migration --- backfill precedent. (Prisma wraps each migration in a single transaction, so --- batching with intermediate commits isn't possible here.) -UPDATE "Session" -SET "isTest" = true, "tags" = array_remove("tags", 'playground') -WHERE 'playground' = ANY("tags");