Skip to content

Commit 565ad2c

Browse files
committed
merge: integrate origin/main into ck virtual-time scheduling
Resolve the run-queue index.ts conflict from merging origin/main. Main rewrote the base ck scripts to add queue-gates and total-concurrency (new KEYS/ARGV, gate helpers, groupConcurrency SREM mirror); our branch added virtual-time variants of the ck commands via ckXLua template functions. Applied the five verified gate-integration specs (enqueueMessageCkVtimeTracked, enqueueMessageWithTtlCkVtimeTracked, acknowledgeMessageCkVtimeTracked, nackMessageCkVtimeTracked, moveToDeadLetterQueueCkVtimeTracked): each template literal, both defineCommand blocks, both ioredis overloads and the runtime call site now carry main's base with the vtime slots layered on top. Every flag-off ck script renders byte-identical to origin/main (verified) and every script SCRIPT LOADs in both builds. dequeue and expireTtlRuns take the interim split: the flag-off path is main's gate/total-concurrency version (un-templated), and the vtime variant is unchanged pending integration of gates through the two-pass loop (see the INTERIM comment on the vtime dequeue).
2 parents 43d0722 + c84bf7f commit 565ad2c

155 files changed

Lines changed: 12963 additions & 4241 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
---
2+
"@trigger.dev/sdk": minor
3+
"@trigger.dev/core": minor
4+
---
5+
6+
Chat agents can now scope concurrency per session. Pass `concurrencyKey` (for example, your chat ID or tenant ID) and trigger-time named limits via `triggerConfig.concurrency` when starting a chat session, from `chat.createStartSessionAction`, the `AgentChat` client, or a handover. Keys are never defaulted, so a session without one shares the task's keyless pool.
7+
8+
```ts
9+
const start = chat.createStartSessionAction("support-chat", {
10+
triggerConfig: { concurrencyKey: user.id },
11+
});
12+
```
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"@trigger.dev/sdk": patch
3+
---
4+
5+
Steering messages now remain in context across agent steps and keep their original position in saved conversations, including custom response data written between steps.

‎.changeset/task-concurrency.md‎

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
---
2+
"@trigger.dev/sdk": minor
3+
"@trigger.dev/core": minor
4+
"@trigger.dev/react-hooks": minor
5+
---
6+
7+
Control a task's concurrency with the new `concurrency` option, and share limits across tasks with named concurrency limits. An inline shape caps the task itself; `concurrencyLimit()` declares a limit any task can hold (up to two named limits per task), and a trigger call can switch a run's named limits with its own `concurrency` option.
8+
9+
```ts
10+
import { concurrencyLimit, task } from "@trigger.dev/sdk";
11+
12+
export const openaiLimit = concurrencyLimit({ name: "openai", total: 25 });
13+
14+
export const generateSummary = task({
15+
id: "generate-summary",
16+
concurrency: [{ perKey: 1, total: 5 }, openaiLimit],
17+
run: async (payload) => {},
18+
});
19+
```
20+
21+
`perKey` caps each `concurrencyKey` pool and `total` caps across everything, keys or not. The queue-level `concurrencyLimit` option keeps working unchanged and is deprecated in favor of `concurrency`. Enforcement happens server-side; servers without support accept the option but do not enforce it yet.
22+
23+
Manage limits at runtime with the new `concurrencyLimits` namespace: `list()` and `retrieve(name)` report each limit's bounds plus its live `running` and `queued` counts, `override(name, { perKey, total })` changes only the given bounds (overriding `total` to `0` pauses the limit), and `reset(name)` restores the declared values.
24+
25+
Queue reads (`queues.list()` and `queues.retrieve()`) now report a `version` that discriminates the shape: `V1` queues keep today's fields (their own `concurrencyLimit` and its override state), while `V2` queues (tasks declared with `concurrency`) carry no queue-level concurrency, since their limits are read and overridden through `concurrencyLimits` (a task's inline limit under its derived `task/<task-id>` name). Existing reads keep compiling: a `V2` queue reports `concurrencyLimit` as null and `concurrency` as undefined.

‎apps/webapp/app/components/billing/OrgBanner.tsx‎

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -21,10 +21,10 @@ import { useOptionalProject, useProject } from "~/hooks/useProject";
2121
import { useShowSelfServe } from "~/hooks/useShowSelfServe";
2222
import { useCurrentPlan } from "~/routes/_app.orgs.$organizationSlug/route";
2323
import {
24+
concurrencyPath,
2425
organizationProjectsPath,
2526
v3BillingLimitsPath,
2627
v3BillingPath,
27-
v3QueuesPath,
2828
} from "~/utils/pathBuilder";
2929
import { ENVIRONMENT_PAUSE_SOURCE_BILLING_LIMIT } from "~/utils/environmentPauseSource";
3030

@@ -71,7 +71,7 @@ export function OrgBanner() {
7171
showSelfServe,
7272
});
7373

74-
const hideQueuesButton = location.pathname.endsWith("/queues");
74+
const hideConcurrencyButton = location.pathname.endsWith("/concurrency");
7575
const hideBillingLimitBanner = location.pathname.endsWith("/settings/billing-limits");
7676

7777
switch (bannerKind) {
@@ -89,7 +89,7 @@ export function OrgBanner() {
8989
return isArchived ? (
9090
<ArchivedEnvironmentBanner />
9191
) : (
92-
<PausedEnvironmentBanner hideButton={hideQueuesButton} />
92+
<PausedEnvironmentBanner hideButton={hideConcurrencyButton} />
9393
);
9494
default:
9595
return null;
@@ -303,7 +303,7 @@ function PausedEnvironmentBanner({ hideButton }: { hideButton: boolean }) {
303303
hideButton ? undefined : (
304304
<LinkButton
305305
variant="tertiary/small"
306-
to={v3QueuesPath(organization, project, environment)}
306+
to={concurrencyPath(organization, project, environment)}
307307
>
308308
Manage
309309
</LinkButton>

‎apps/webapp/app/components/dashboard-agent/DashboardAgentPanel.tsx‎

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,7 @@ import {
5252
} from "./unread-counts";
5353
import { AgentPanelColumn, type DashboardAgentMode, type DragHandleProps } from "./panel-layout";
5454
import { markerAfterActiveChat, markerAfterActivity } from "./thinking-marker";
55-
import { concurrencyPath } from "~/utils/pathBuilder";
55+
import { concurrencyLimitsPath } from "~/utils/pathBuilder";
5656
import { scopeMatchesPath, sessionPathFor } from "./agent-scope";
5757

5858
function serializePageContext(pageContext: AgentPageContext): string | undefined {
@@ -149,7 +149,7 @@ export function DashboardAgentPanel({
149149
const entityId = agentPageEntityId(pageContext, location.pathname);
150150

151151
const pagePaths = useMemo<Record<string, string>>(
152-
() => ({ raise_env_limit: concurrencyPath(organization, project, environment) }),
152+
() => ({ raise_env_limit: concurrencyLimitsPath(organization, project, environment) }),
153153
[organization, project, environment]
154154
);
155155

‎apps/webapp/app/components/dashboard-agent/page-label.ts‎

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ const KIND_LABELS: Record<Exclude<AgentPage["kind"], "other">, string> = {
2424
alerts: "Alerts",
2525
apikeys: "API keys",
2626
envvars: "Environment variables",
27-
concurrency: "Concurrency",
27+
concurrency: "Concurrency limits",
2828
regions: "Regions",
2929
settings: "Settings",
3030
waitpoints: "Waitpoints",
@@ -50,6 +50,7 @@ const SECTION_LABELS: Record<string, string> = {
5050
"bulk-actions": "Bulk actions",
5151
branches: "Branches",
5252
concurrency: "Concurrency",
53+
"concurrency-limits": "Concurrency limits",
5354
dashboards: "Dashboards",
5455
deployments: "Deployments",
5556
"dev-branches": "Branches",

‎apps/webapp/app/components/navigation/favoritePages.tsx‎

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -223,7 +223,8 @@ const ENV_PAGE_META: Record<string, PageMeta> = {
223223
logs: { icon: "logs", name: "Logs" },
224224
errors: { icon: "errors", name: "Errors", singular: "Error" },
225225
query: { icon: "query", name: "Query" },
226-
queues: { icon: "queues", name: "Queues", singular: "Queue" },
226+
queues: { icon: "queues", name: "Concurrency" },
227+
concurrency: { icon: "queues", name: "Concurrency" },
227228
dashboards: { icon: "dashboards", name: "Dashboards", singular: "Dashboard" },
228229
deployments: { icon: "deployments", name: "Deploys", singular: "Deploy" },
229230
"environment-variables": { icon: "environment-variables", name: "Environment variables" },
@@ -234,7 +235,7 @@ const ENV_PAGE_META: Record<string, PageMeta> = {
234235
"bulk-actions": { icon: "bulk-actions", name: "Bulk actions", singular: "Bulk action" },
235236
apikeys: { icon: "apikeys", name: "API keys" },
236237
alerts: { icon: "alerts", name: "Alerts", singular: "Alert" },
237-
concurrency: { icon: "concurrency", name: "Concurrency" },
238+
"concurrency-limits": { icon: "concurrency", name: "Concurrency limits" },
238239
limits: { icon: "limits", name: "Limits" },
239240
schedules: { icon: "schedules", name: "Schedules", singular: "Schedule" },
240241
test: { icon: "test", name: "Test", singular: "Test" },

‎apps/webapp/app/components/navigation/sideMenuSections.tsx‎

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ import {
2323
type OrgForPath,
2424
type ProjectForPath,
2525
branchesPath,
26-
concurrencyPath,
26+
concurrencyLimitsPath,
2727
limitsPath,
2828
queryPath,
2929
regionsPath,
@@ -39,7 +39,7 @@ import {
3939
v3ProjectAlertsPath,
4040
v3ProjectSettingsIntegrationsPath,
4141
v3PromptsPath,
42-
v3QueuesPath,
42+
concurrencyPath,
4343
v3WaitpointTokensPath,
4444
} from "~/utils/pathBuilder";
4545
import { AlphaBadge, NewBadge } from "../FeatureBadges";
@@ -160,10 +160,10 @@ export function buildSideMenuSections({
160160
} satisfies SideMenuItemConfig,
161161
{
162162
id: "queues",
163-
name: "Queues",
163+
name: "Concurrency",
164164
icon: QueuesIcon,
165165
activeIconColor: "text-queues",
166-
to: v3QueuesPath(organization, project, environment),
166+
to: concurrencyPath(organization, project, environment),
167167
dataAction: "queues",
168168
} satisfies SideMenuItemConfig,
169169
{
@@ -269,10 +269,10 @@ export function buildSideMenuSections({
269269
? [
270270
{
271271
id: "concurrency",
272-
name: "Concurrency",
272+
name: "Concurrency limits",
273273
icon: ConcurrencyIcon,
274274
activeIconColor: "text-text-bright",
275-
to: concurrencyPath(organization, project, environment),
275+
to: concurrencyLimitsPath(organization, project, environment),
276276
dataAction: "concurrency",
277277
} satisfies SideMenuItemConfig,
278278
]

‎apps/webapp/app/components/primitives/charts/MetricChart.tsx‎

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,8 @@ type MetricChartProps = {
4141
* are config values that existed all along, so carry the first value backward instead.
4242
*/
4343
carryBackfill?: string[];
44+
/** Column that, when positive on a bucket, exempts it from the carryBackfill overwrite. */
45+
carryBackfillGuard?: string;
4446
/**
4547
* Line only. Recolour a series' stroke above a threshold with a gradient split (colour only
4648
* above the line). `value` sets a constant threshold; `valueFromSeries` reads a (roughly
@@ -78,13 +80,22 @@ export function MetricChart({
7880
timeRange,
7981
warningOverlay,
8082
carryBackfill,
83+
carryBackfillGuard,
8184
thresholdStroke,
8285
onHasDataChange,
8386
sampleCountColumn,
8487
}: MetricChartProps) {
8588
const { points, xKind: resolvedXKind } = useMemo(
86-
() => buildMetricPoints(rows, { series, xColumn, xKind, carryBackfill, sampleCountColumn }),
87-
[rows, series, xColumn, xKind, carryBackfill, sampleCountColumn]
89+
() =>
90+
buildMetricPoints(rows, {
91+
series,
92+
xColumn,
93+
xKind,
94+
carryBackfill,
95+
carryBackfillGuard,
96+
sampleCountColumn,
97+
}),
98+
[rows, series, xColumn, xKind, carryBackfill, carryBackfillGuard, sampleCountColumn]
8899
);
89100

90101
const chartConfig = useMemo(() => {

‎apps/webapp/app/components/primitives/charts/metricPoints.ts‎

Lines changed: 18 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -58,12 +58,22 @@ export type BuildMetricPointsOptions = {
5858
/** Defaults to `time` when every x is a datetime or epoch number, `category` otherwise. */
5959
xKind?: MetricXKind;
6060
carryBackfill?: string[];
61+
/** Column that, when positive on a bucket, exempts it from the carryBackfill overwrite:
62+
* the bucket has a real sampled value, so an earlier bucket's carry must not clobber it. */
63+
carryBackfillGuard?: string;
6164
sampleCountColumn?: string;
6265
};
6366

6467
export function buildMetricPoints(
6568
rows: MetricChartRow[],
66-
{ series, xColumn = "t", xKind, carryBackfill, sampleCountColumn }: BuildMetricPointsOptions
69+
{
70+
series,
71+
xColumn = "t",
72+
xKind,
73+
carryBackfill,
74+
carryBackfillGuard,
75+
sampleCountColumn,
76+
}: BuildMetricPointsOptions
6777
): { points: MetricPoint[]; xKind: MetricXKind } {
6878
// Rows built by seriesFromRows already carry the coordinate under the reserved key.
6979
const xOf = (row: MetricChartRow) => (METRIC_X_KEY in row ? row[METRIC_X_KEY] : row[xColumn]);
@@ -80,6 +90,9 @@ export function buildMetricPoints(
8090
// A bucket with no value for this series is a gap, not a zero.
8191
point[s.key] = hasSamples && value != null ? toNumber(value) : null;
8292
}
93+
if (carryBackfillGuard) {
94+
point[carryBackfillGuard] = toNumber(r[carryBackfillGuard]);
95+
}
8396
// Set last, so a series key colliding with the reserved key can't displace the coordinate.
8497
point[METRIC_X_KEY] = kind === "time" ? (timeValueMs(xOf(r)) ?? NaN) : String(xOf(r) ?? "");
8598
return point;
@@ -99,7 +112,10 @@ export function buildMetricPoints(
99112
const first = points.findIndex((p) => toNumber(p[key]) > 0);
100113
if (first > 0) {
101114
const value = points[first]![key]!;
102-
for (let i = 0; i < first; i++) points[i]![key] = value;
115+
for (let i = 0; i < first; i++) {
116+
if (carryBackfillGuard && toNumber(points[i]![carryBackfillGuard]) > 0) continue;
117+
points[i]![key] = value;
118+
}
103119
}
104120
}
105121
}

0 commit comments

Comments
 (0)