feat(webapp,core,cli): filter runs by region in dashboard, API, and MCP (#3612)

## Summary

Adds a Region column and Region filter (under More filters) to the runs
list dashboard, the same filter on the public runs list API
(`filter[region]`), and a matching `region` input on the MCP `list_runs`
tool. Each run's executing region is also surfaced as a new optional
`region` field on the runs list and run retrieve responses, populated
from the worker instance group's `masterQueue` identifier.

Useful when you run tasks across multiple regions and want to slice the
runs list — or your existing run-querying scripts — by where the run
actually executed.

## Design

The filter value in the URL / API is the `masterQueue` identifier (the
same string already persisted on `TaskRun` and replicated to ClickHouse
as `worker_queue`), so the query just becomes `worker_queue IN (...)`
with no server-side translation. The Region dropdown options come from a
new resource loader backed by `RegionsPresenter`, which now also exposes
`masterQueue` alongside the existing region metadata.

```ts
// public API
const runs = await runs.list({ region: ["us-east-1", "eu-west-1"] });
// each item: { id, status, ..., region?: "us-east-1" }
```

```ts
// MCP
list_runs({ environment: "prod", region: "us-east-1" })
```
This commit is contained in:
Eric Allam
2026-05-15 16:32:13 +01:00
committed by GitHub
parent 454f0c949a
commit 4c42f6cc0b
22 changed files with 318 additions and 16 deletions
+5
View File
@@ -0,0 +1,5 @@
---
"trigger.dev": patch
---
MCP `list_runs` tool: add a `region` filter input and surface each run's executing region in the formatted summary.
+6
View File
@@ -0,0 +1,6 @@
---
"@trigger.dev/core": patch
"@trigger.dev/sdk": patch
---
Add `region` to the runs list / retrieve API: filter runs by region (`runs.list({ region: "..." })` / `filter[region]=<masterQueue>`) and read each run's executing region from the new `region` field on the response.
@@ -215,6 +215,19 @@ export function BulkActionFilterSummary({
/>
);
}
case "regions": {
const values = Array.isArray(value) ? value : [`${value}`];
return (
<AppliedFilter
variant="minimal/medium"
key={key}
label={filterTitle(key)}
icon={filterIcon(key)}
value={appliedSummary(values)}
removable={false}
/>
);
}
case "machines": {
const values = Array.isArray(value) ? value : [`${value}`];
return (
@@ -0,0 +1,22 @@
import { FlagIcon } from "~/assets/icons/RegionIcons";
import { cn } from "~/utils/cn";
type RegionLabelProps = {
region: {
name: string;
location?: string | null;
};
className?: string;
iconClassName?: string;
};
export function RegionLabel({ region, className, iconClassName }: RegionLabelProps) {
return (
<span className={cn("flex items-center gap-1", className)}>
{region.location ? (
<FlagIcon region={region.location} className={cn("size-5", iconClassName)} />
) : null}
{region.name}
</span>
);
}
@@ -4,6 +4,7 @@ import {
ClockIcon,
CpuChipIcon,
FingerPrintIcon,
GlobeAltIcon,
PlusIcon,
RectangleStackIcon,
Squares2X2Icon,
@@ -61,6 +62,8 @@ import { useShortcutKeys } from "~/hooks/useShortcutKeys";
import { ShortcutKey } from "~/components/primitives/ShortcutKey";
import { type loader as tagsLoader } from "~/routes/resources.environments.$envId.runs.tags";
import { type loader as queuesLoader } from "~/routes/resources.orgs.$organizationSlug.projects.$projectParam.env.$envParam.queues";
import { useRegions } from "~/hooks/useRegions";
import { RegionLabel } from "./RegionLabel";
import { type loader as versionsLoader } from "~/routes/resources.orgs.$organizationSlug.projects.$projectParam.env.$envParam.versions";
import { Button } from "../../primitives/Buttons";
import { AIFilterInput } from "./AIFilterInput";
@@ -187,6 +190,9 @@ export const TaskRunListSearchFilters = z.object({
"Schedule ID to filter by - shows runs from a specific schedule. They start with sched_"
),
queues: StringOrStringArray.describe("Queue names to filter by (these are user-defined names)"),
regions: StringOrStringArray.describe(
"Region master-queue identifiers to filter by (the worker instance group masterQueue values)"
),
machines: MachinePresetOrMachinePresetArray.describe(
`Machine presets to filter by (${machines.join(", ")})`
),
@@ -229,6 +235,8 @@ export function filterTitle(filterKey: string) {
return "Schedule ID";
case "queues":
return "Queues";
case "regions":
return "Region";
case "machines":
return "Machine";
case "versions":
@@ -271,6 +279,8 @@ export function filterIcon(filterKey: string): ReactNode | undefined {
return <ClockIcon className="size-4" />;
case "queues":
return <RectangleStackIcon className="size-4" />;
case "regions":
return <GlobeAltIcon className="size-4" />;
case "machines":
return <MachineDefaultIcon className="size-4" />;
case "versions":
@@ -317,6 +327,10 @@ export function getRunFiltersFromSearchParams(
searchParams.getAll("queues").filter((v) => v.length > 0).length > 0
? searchParams.getAll("queues")
: undefined,
regions:
searchParams.getAll("regions").filter((v) => v.length > 0).length > 0
? searchParams.getAll("regions")
: undefined,
machines:
searchParams.getAll("machines").filter((v) => v.length > 0).length > 0
? searchParams.getAll("machines")
@@ -369,6 +383,7 @@ export function RunsFilters(props: RunFiltersProps) {
searchParams.has("runId") ||
searchParams.has("scheduleId") ||
searchParams.has("queues") ||
searchParams.has("regions") ||
searchParams.has("machines") ||
searchParams.has("versions") ||
searchParams.has("errorId") ||
@@ -402,6 +417,7 @@ const filterTypes = [
{ name: "tags", title: "Tags", icon: <TagIcon className="size-4" /> },
{ name: "versions", title: "Versions", icon: <IconRotateClockwise2 className="size-4" /> },
{ name: "queues", title: "Queues", icon: <RectangleStackIcon className="size-4" /> },
{ name: "regions", title: "Region", icon: <GlobeAltIcon className="size-4" /> },
{ name: "machines", title: "Machines", icon: <MachineDefaultIcon className="size-4" /> },
{ name: "run", title: "Run ID", icon: <FingerPrintIcon className="size-4" /> },
{ name: "batch", title: "Batch ID", icon: <Squares2X2Icon className="size-4" /> },
@@ -456,6 +472,7 @@ function AppliedFilters({ bulkActions }: RunFiltersProps) {
<AppliedTagsFilter />
<AppliedVersionsFilter />
<AppliedQueuesFilter />
<AppliedRegionsFilter />
<AppliedMachinesFilter />
<AppliedRunIdFilter />
<AppliedBatchIdFilter />
@@ -485,6 +502,8 @@ function Menu(props: MenuProps) {
return <TagsDropdown onClose={() => props.setFilterType(undefined)} {...props} />;
case "queues":
return <QueuesDropdown onClose={() => props.setFilterType(undefined)} {...props} />;
case "regions":
return <RegionsDropdown onClose={() => props.setFilterType(undefined)} {...props} />;
case "machines":
return <MachinesDropdown onClose={() => props.setFilterType(undefined)} {...props} />;
case "run":
@@ -503,11 +522,14 @@ function Menu(props: MenuProps) {
}
function MainMenu({ searchValue, trigger, clearSearchValue, setFilterType }: MenuProps) {
const environment = useEnvironment();
const showRegion = environment.type !== "DEVELOPMENT";
const filtered = useMemo(() => {
return filterTypes.filter((item) => {
if (item.name === "regions" && !showRegion) return false;
return item.title.toLowerCase().includes(searchValue.toLowerCase());
});
}, [searchValue]);
}, [searchValue, showRegion]);
return (
<SelectProvider virtualFocus={true}>
@@ -1260,6 +1282,138 @@ function AppliedQueuesFilter() {
);
}
function RegionsDropdown({
trigger,
clearSearchValue,
searchValue,
onClose,
}: {
trigger: ReactNode;
clearSearchValue: () => void;
searchValue: string;
onClose?: () => void;
}) {
const { values, replace } = useSearchParams();
const regions = useRegions();
const handleChange = (values: string[]) => {
clearSearchValue();
replace({
regions: values.length > 0 ? values : undefined,
cursor: undefined,
direction: undefined,
});
};
const selected = values("regions").filter((v) => v !== "");
const filtered = useMemo(() => {
type RegionItem = { masterQueue: string; name: string; location?: string };
const items: RegionItem[] = [];
for (const masterQueue of selected) {
const known = regions.find((r) => r.masterQueue === masterQueue);
if (!known) {
items.push({ masterQueue, name: masterQueue });
}
}
for (const region of regions) {
if (!items.some((i) => i.masterQueue === region.masterQueue)) {
items.push({
masterQueue: region.masterQueue,
name: region.name,
location: region.location,
});
}
}
return matchSorter(items, searchValue, { keys: ["name", "masterQueue"] });
}, [searchValue, regions, selected.join(",")]);
return (
<SelectProvider value={selected} setValue={handleChange} virtualFocus={true}>
{trigger}
<SelectPopover
className="min-w-0 max-w-[min(320px,var(--popover-available-width))]"
hideOnEscape={() => {
if (onClose) {
onClose();
return false;
}
return true;
}}
>
<ComboBox
value={searchValue}
render={(props) => (
<div className="flex items-center justify-stretch">
<input {...props} placeholder={"Filter by region..."} />
</div>
)}
/>
<SelectList>
{filtered.length > 0
? filtered.map((region) => (
<SelectItem
key={region.masterQueue}
value={region.masterQueue}
className="text-text-bright"
>
<RegionLabel region={region} iconClassName="size-4" />
</SelectItem>
))
: null}
{filtered.length === 0 && <SelectItem disabled>No regions found</SelectItem>}
</SelectList>
</SelectPopover>
</SelectProvider>
);
}
function AppliedRegionsFilter() {
const { values, del } = useSearchParams();
const environment = useEnvironment();
const knownRegions = useRegions();
const regions = values("regions");
if (environment.type === "DEVELOPMENT") {
return null;
}
if (regions.length === 0 || regions.every((v) => v === "")) {
return null;
}
const labels = regions.map((mq) => {
const match = knownRegions.find((r) => r.masterQueue === mq);
return match?.name ?? mq;
});
return (
<FilterMenuProvider>
{(search, setSearch) => (
<RegionsDropdown
trigger={
<Ariakit.Select render={<div className="group cursor-pointer focus-custom" />}>
<AppliedFilter
label="Region"
icon={filterIcon("regions")}
value={appliedSummary(labels)}
onRemove={() => del(["regions", "cursor", "direction"])}
variant="secondary/small"
/>
</Ariakit.Select>
}
searchValue={search}
clearSearchValue={() => setSearch("")}
/>
)}
</FilterMenuProvider>
);
}
function MachinesDropdown({
trigger,
clearSearchValue,
@@ -23,6 +23,7 @@ import { useSelectedItems } from "~/components/primitives/SelectedItemsProvider"
import { SimpleTooltip } from "~/components/primitives/Tooltip";
import { TruncatedCopyableValue } from "~/components/primitives/TruncatedCopyableValue";
import { useEnvironment } from "~/hooks/useEnvironment";
import { useRegions } from "~/hooks/useRegions";
import { useFeatures } from "~/hooks/useFeatures";
import { useOrganization } from "~/hooks/useOrganizations";
import { useProject } from "~/hooks/useProject";
@@ -47,6 +48,7 @@ import {
type TableVariant,
} from "../../primitives/Table";
import { CancelRunDialog } from "./CancelRunDialog";
import { RegionLabel } from "./RegionLabel";
import { LiveTimer } from "./LiveTimer";
import { ReplayRunDialog } from "./ReplayRunDialog";
import { RunTag } from "./RunTag";
@@ -86,8 +88,11 @@ export function TaskRunsTable({
variant = "dimmed",
additionalTableState,
}: RunsTableProps) {
const regions = useRegions();
const regionByMasterQueue = new Map(regions.map((r) => [r.masterQueue, r] as const));
const organization = useOrganization();
const project = useProject();
const environment = useEnvironment();
const checkboxes = useRef<(HTMLInputElement | null)[]>([]);
const { has, hasAll, select, deselect, toggle } = useSelectedItems(allowSelection);
const { isManagedCloud } = useFeatures();
@@ -107,6 +112,7 @@ export function TaskRunsTable({
const tableStateParam = disableAdjacentRows ? "" : encodeURIComponent(search);
const showCompute = isManagedCloud;
const showRegion = environment.type !== "DEVELOPMENT";
const navigateCheckboxes = useCallback(
(event: React.KeyboardEvent<HTMLInputElement>, index: number) => {
@@ -233,6 +239,7 @@ export function TaskRunsTable({
Machine
</TableHeaderCell>
<TableHeaderCell>Queue</TableHeaderCell>
{showRegion && <TableHeaderCell>Region</TableHeaderCell>}
<TableHeaderCell>Test</TableHeaderCell>
<TableHeaderCell>Created at</TableHeaderCell>
<TableHeaderCell
@@ -312,11 +319,11 @@ export function TaskRunsTable({
</TableHeader>
<TableBody>
{total === 0 && !hasFilters ? (
<TableBlankRow colSpan={15}>
<TableBlankRow colSpan={showRegion ? 16 : 15}>
{!isLoading && <NoRuns title="No runs found" />}
</TableBlankRow>
) : runs.length === 0 ? (
<BlankState isLoading={isLoading} filters={filters} />
<BlankState isLoading={isLoading} filters={filters} showRegion={showRegion} />
) : (
runs.map((run, index) => {
const searchParams = new URLSearchParams();
@@ -441,6 +448,20 @@ export function TaskRunsTable({
<span>{run.queue.name}</span>
</span>
</TableCell>
{showRegion && (
<TableCell to={path}>
{run.region ? (
<RegionLabel
region={
regionByMasterQueue.get(run.region) ?? { name: run.region }
}
iconClassName="size-4"
/>
) : (
""
)}
</TableCell>
)}
<TableCell to={path}>
{run.isTest ? (
<CheckIcon className="size-4 text-charcoal-400 group-hover/table-row:text-text-bright" />
@@ -467,7 +488,7 @@ export function TaskRunsTable({
)}
{isLoading && (
<TableBlankRow
colSpan={15}
colSpan={showRegion ? 16 : 15}
className="absolute left-0 top-0 flex h-full w-full items-center justify-center gap-2 bg-background-dimmed"
>
<Spinner /> <span className="text-text-dimmed">Loading</span>
@@ -603,11 +624,16 @@ function NoRuns({ title }: { title: string }) {
);
}
function BlankState({ isLoading, filters }: Pick<RunsTableProps, "isLoading" | "filters">) {
function BlankState({
isLoading,
filters,
showRegion,
}: Pick<RunsTableProps, "isLoading" | "filters"> & { showRegion: boolean }) {
const organization = useOrganization();
const project = useProject();
const environment = useEnvironment();
if (isLoading) return <TableBlankRow colSpan={15}></TableBlankRow>;
const colSpan = showRegion ? 16 : 15;
if (isLoading) return <TableBlankRow colSpan={colSpan}></TableBlankRow>;
const { tasks, from, to, ...otherFilters } = filters;
const singleTaskFromFilters = filters.tasks.length === 1 ? filters.tasks[0] : null;
@@ -622,7 +648,7 @@ function BlankState({ isLoading, filters }: Pick<RunsTableProps, "isLoading" | "
Object.values(otherFilters).every((filterArray) => filterArray.length === 0)
) {
return (
<TableBlankRow colSpan={15}>
<TableBlankRow colSpan={colSpan}>
<Paragraph className="w-auto" variant="base/bright" spacing>
There are no runs for {filters.tasks[0]}
</Paragraph>
@@ -650,7 +676,7 @@ function BlankState({ isLoading, filters }: Pick<RunsTableProps, "isLoading" | "
}
return (
<TableBlankRow colSpan={15}>
<TableBlankRow colSpan={colSpan}>
<div className="flex flex-col items-center justify-center gap-6">
<Paragraph className="w-auto" variant="base/bright">
No runs match your filters. Try refreshing, modifying your filters or run a test.
+16
View File
@@ -0,0 +1,16 @@
import { type UIMatch } from "@remix-run/react";
import { type UseDataFunctionReturn } from "remix-typedjson";
import type { loader as orgLoader } from "~/routes/_app.orgs.$organizationSlug/route";
import { organizationMatchId } from "./useOrganizations";
import { useTypedMatchesData } from "./useTypedMatchData";
export type MatchedRegion = UseDataFunctionReturn<typeof orgLoader>["regions"][number];
export function useRegions(matches?: UIMatch[]): MatchedRegion[] {
const routeMatch = useTypedMatchesData<typeof orgLoader>({
id: organizationMatchId,
matches,
});
return routeMatch?.regions ?? [];
}
@@ -34,6 +34,7 @@ export async function getRunFiltersFromRequest(request: Request): Promise<Filter
batchId,
scheduleId,
queues,
regions,
machines,
errorId,
sources,
@@ -55,6 +56,7 @@ export async function getRunFiltersFromRequest(request: Request): Promise<Filter
direction: direction,
cursor: cursor,
queues,
regions,
machines,
errorId,
sources,
@@ -42,6 +42,7 @@ const commonRunSelect = {
isTest: true,
depth: true,
scheduleId: true,
workerQueue: true,
lockedToVersion: {
select: {
version: true,
@@ -463,6 +464,7 @@ async function createCommonRunStructure(run: CommonRelatedRun, apiVersion: API_V
triggerFunction: resolveTriggerFunction(run),
batchId: run.batch?.friendlyId,
metadata,
region: run.workerQueue || undefined,
};
}
@@ -116,6 +116,12 @@ export const ApiRunListSearchParams = z.object({
.transform((value) => {
return value ? value.split(",") : undefined;
}),
"filter[region]": z
.string()
.optional()
.transform((value) => {
return value ? value.split(",") : undefined;
}),
"filter[machine]": z
.string()
.optional()
@@ -255,6 +261,10 @@ export class ApiRunListPresenter extends BasePresenter {
options.queues = searchParams["filter[queue]"];
}
if (searchParams["filter[region]"]) {
options.regions = searchParams["filter[region]"];
}
if (searchParams["filter[machine]"]) {
options.machines = searchParams["filter[machine]"];
}
@@ -308,6 +318,7 @@ export class ApiRunListPresenter extends BasePresenter {
// Match `NextRunListPresenter`'s "STANDARD" fallback so API
// consumers and the dashboard see the same value.
taskKind: run.taskKind || "STANDARD",
region: run.region ?? undefined,
...ApiRetrieveRunPresenter.apiBooleanHelpersFromRunStatus(
ApiRetrieveRunPresenter.apiStatusFromRunStatus(run.status, apiVersion)
),
@@ -33,6 +33,7 @@ export type RunListOptions = {
batchId?: string;
runId?: string[];
queues?: string[];
regions?: string[];
machines?: MachinePresetName[];
errorId?: string;
sources?: string[];
@@ -72,6 +73,7 @@ export class NextRunListPresenter {
batchId,
runId,
queues,
regions,
machines,
errorId,
sources,
@@ -102,6 +104,7 @@ export class NextRunListPresenter {
batchId !== undefined ||
(runId !== undefined && runId.length > 0) ||
(queues !== undefined && queues.length > 0) ||
(regions !== undefined && regions.length > 0) ||
(machines !== undefined && machines.length > 0) ||
(errorId !== undefined && errorId !== "") ||
typeof isTest === "boolean" ||
@@ -188,6 +191,7 @@ export class NextRunListPresenter {
runId,
bulkId,
queues,
regions,
machines,
errorId,
taskKinds: sources,
@@ -255,6 +259,7 @@ export class NextRunListPresenter {
name: run.queue.replace("task/", ""),
type: run.queue.startsWith("task/") ? "task" : "custom",
},
region: run.workerQueue ? run.workerQueue : undefined,
taskKind: RunAnnotations.safeParse(run.annotations).data?.taskKind ?? "STANDARD",
};
}),
@@ -10,6 +10,7 @@ import { getCurrentPlan } from "~/services/platform.v3.server";
export type Region = {
id: string;
name: string;
masterQueue: string;
description?: string;
cloudProvider?: string;
location?: string;
@@ -73,6 +74,7 @@ export class RegionsPresenter extends BasePresenter {
select: {
id: true,
name: true,
masterQueue: true,
description: true,
cloudProvider: true,
location: true,
@@ -96,6 +98,7 @@ export class RegionsPresenter extends BasePresenter {
const regions: Region[] = visibleRegions.map((region) => ({
id: region.id,
name: region.name,
masterQueue: region.masterQueue,
description: region.description ?? undefined,
cloudProvider: region.cloudProvider ?? undefined,
location: region.location ?? undefined,
@@ -110,6 +113,7 @@ export class RegionsPresenter extends BasePresenter {
select: {
id: true,
name: true,
masterQueue: true,
description: true,
cloudProvider: true,
location: true,
@@ -130,6 +134,7 @@ export class RegionsPresenter extends BasePresenter {
regions.push({
id: defaultWorkerGroup.id,
name: defaultWorkerGroup.name,
masterQueue: defaultWorkerGroup.masterQueue,
description: defaultWorkerGroup.description ?? undefined,
cloudProvider: defaultWorkerGroup.cloudProvider ?? undefined,
location: defaultWorkerGroup.location ?? undefined,
@@ -7,6 +7,7 @@ import { prisma } from "~/db.server";
import { useOptionalOrganization } from "~/hooks/useOrganizations";
import { useTypedMatchesData } from "~/hooks/useTypedMatchData";
import { OrganizationsPresenter } from "~/presenters/OrganizationsPresenter.server";
import { RegionsPresenter, type Region } from "~/presenters/v3/RegionsPresenter.server";
import { getImpersonationId } from "~/services/impersonation.server";
import { getCachedUsage, getCurrentPlan } from "~/services/platform.v3.server";
import { requireUser } from "~/services/session.server";
@@ -88,7 +89,10 @@ export const loader = async ({ request, params }: LoaderFunctionArgs) => {
firstDayOfNextMonth.setUTCDate(1);
firstDayOfNextMonth.setUTCHours(0, 0, 0, 0);
const [plan, usage, customDashboards] = await Promise.all([
const shouldLoadRegions =
!!projectParam && !!environment && environment.type !== "DEVELOPMENT";
const [plan, usage, customDashboards, regions] = await Promise.all([
getCurrentPlan(organization.id),
getCachedUsage(organization.id, { from: firstDayOfMonth, to: firstDayOfNextMonth }),
prisma.metricsDashboard.findMany({
@@ -100,6 +104,12 @@ export const loader = async ({ request, params }: LoaderFunctionArgs) => {
},
orderBy: { createdAt: "desc" },
}),
shouldLoadRegions
? new RegionsPresenter()
.call({ userId: user.id, projectSlug: projectParam! })
.then(({ regions }) => regions)
.catch(() => [] as Region[])
: Promise.resolve([] as Region[]),
]);
let hasExceededFreeTier = false;
@@ -147,6 +157,7 @@ export const loader = async ({ request, params }: LoaderFunctionArgs) => {
organization,
project,
environment,
regions,
isImpersonating: !!impersonationId,
currentPlan: { ...plan, v3Usage: { ...usage, hasExceededFreeTier, usagePercentage } },
customDashboards: customDashboardsWithWidgetCount,
@@ -22,7 +22,7 @@ import { assertNever } from "assert-never";
import { useEffect, useState } from "react";
import { typedjson, useTypedFetcher } from "remix-typedjson";
import { ExitIcon } from "~/assets/icons/ExitIcon";
import { FlagIcon } from "~/assets/icons/RegionIcons";
import { RegionLabel } from "~/components/runs/v3/RegionLabel";
import { AdminDebugRun } from "~/components/admin/debugRun";
import { CodeBlock } from "~/components/code/CodeBlock";
import { EnvironmentCombo } from "~/components/environments/EnvironmentLabel";
@@ -972,12 +972,7 @@ function RunBody({
<Property.Item>
<Property.Label>Region</Property.Label>
<Property.Value>
<span className="flex items-center gap-1">
{run.region.location ? (
<FlagIcon region={run.region.location} className="size-5" />
) : null}
{run.region.name}
</span>
<RegionLabel region={run.region} />
</Property.Value>
</Property.Item>
)}
@@ -151,6 +151,7 @@ export class ClickHouseRunsRepository implements IRunsRepository {
metadataType: true,
machinePreset: true,
queue: true,
workerQueue: true,
annotations: true,
},
});
@@ -324,6 +325,10 @@ function applyRunFiltersToQueryBuilder<T>(
queryBuilder.where("queue IN {queues: Array(String)}", { queues: options.queues });
}
if (options.regions && options.regions.length > 0) {
queryBuilder.where("worker_queue IN {regions: Array(String)}", { regions: options.regions });
}
if (options.machines && options.machines.length > 0) {
queryBuilder.where("machine_preset IN {machines: Array(String)}", {
machines: options.machines,
@@ -40,6 +40,7 @@ const RunListInputOptionsSchema = z.object({
runId: z.array(z.string()).optional(),
bulkId: z.string().optional(),
queues: z.array(z.string()).optional(),
regions: z.array(z.string()).optional(),
machines: MachinePresetName.array().optional(),
errorId: z.string().optional(),
taskKinds: z.array(z.string()).optional(),
@@ -104,6 +105,7 @@ export type ListedRun = Prisma.TaskRunGetPayload<{
metadataType: true;
machinePreset: true;
queue: true;
workerQueue: true;
annotations: true;
};
}>;
+5
View File
@@ -427,6 +427,11 @@ function formatRunSummary(run: ListRunResponseItem): string {
parts.push(`v${run.version}`);
}
// Region if available
if (run.region) {
parts.push(`region:${run.region}`);
}
return parts.join(" | ");
}
+6
View File
@@ -183,6 +183,12 @@ export const ListRunsInput = CommonProjectsInput.extend({
.describe("Filter for runs created in the last N time period. e.g. 7d, 30d, 365d")
.optional(),
machine: MachinePresetName.describe("Filter for runs that match this machine preset").optional(),
region: z
.string()
.describe(
"Filter for runs that executed in this region (the worker instance group masterQueue identifier, e.g. 'us-east-1' or 'main')"
)
.optional(),
});
export type ListRunsInput = z.output<typeof ListRunsInput>;
+1
View File
@@ -374,6 +374,7 @@ export const listRunsTool = {
to: $to,
period: input.period,
machine: input.machine,
region: input.region,
});
const formattedRuns = formatRunList(result);
+7
View File
@@ -2080,6 +2080,13 @@ function createSearchQueryForListRuns(query?: ListRunsQueryParams): URLSearchPar
Array.isArray(query.machine) ? query.machine.join(",") : query.machine
);
}
if (query.region) {
searchParams.append(
"filter[region]",
Array.isArray(query.region) ? query.region.join(",") : query.region
);
}
}
return searchParams;
+2
View File
@@ -58,6 +58,8 @@ export interface ListRunsQueryParams extends CursorPageParams {
queue?: Array<QueueTypeName> | QueueTypeName;
/** The machine name, or multiple of them. */
machine?: Array<MachinePresetName> | MachinePresetName;
/** The region master-queue identifier, or multiple of them. */
region?: Array<string> | string;
}
export interface ListProjectRunsQueryParams extends CursorPageParams, ListRunsQueryParams {
+1
View File
@@ -1134,6 +1134,7 @@ const CommonRunFields = {
durationMs: z.number(),
metadata: z.record(z.any()).optional(),
taskKind: z.string().optional(),
region: z.string().optional(),
};
const RetrieveRunCommandFields = {