Skip to content

Commit 2397101

Browse files
committed
chore: merge main into feat/compute-workload-manager
2 parents 76e5715 + 8244ac6 commit 2397101

File tree

152 files changed

+13365
-919
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

152 files changed

+13365
-919
lines changed

.changeset/chilly-tips-explode.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"trigger.dev": patch
3+
---
4+
5+
Add platform notifications support to the CLI. The `trigger dev` and `trigger login` commands now fetch and display platform notifications (info, warn, error, success) from the server. Includes discovery-based filtering to conditionally show notifications based on project file patterns, color markup rendering for styled terminal output, and a non-blocking display flow with a spinner fallback for slow fetches. Use `--skip-platform-notifications` flag with `trigger dev` to disable the notification check.

.cursor/rules/writing-tasks.mdc

Lines changed: 19 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ alwaysApply: false
1414

1515
## Essential requirements when generating task code
1616

17-
1. You MUST use `@trigger.dev/sdk/v3`
17+
1. You MUST import from `@trigger.dev/sdk` (NEVER `@trigger.dev/sdk/v3`)
1818
2. You MUST NEVER use `client.defineJob`
1919
3. YOU MUST `export` every task, including subtasks
2020
4. If you are able to generate an example payload for a task, do so.
@@ -53,7 +53,7 @@ Instead, you MUST ALWAYS generate ONLY this pattern:
5353
```ts
5454
// ✅ ALWAYS GENERATE THIS EXACT PATTERN
5555

56-
import { task } from "@trigger.dev/sdk/v3";
56+
import { task } from "@trigger.dev/sdk";
5757

5858
//1. You need to export each task, even if it's a subtask
5959
export const helloWorld = task({
@@ -71,7 +71,7 @@ export const helloWorld = task({
7171
A task is a function that can run for a long time with resilience to failure:
7272

7373
```ts
74-
import { task } from "@trigger.dev/sdk/v3";
74+
import { task } from "@trigger.dev/sdk";
7575

7676
export const helloWorld = task({
7777
id: "hello-world",
@@ -271,7 +271,7 @@ Global lifecycle hooks can also be defined in `trigger.config.ts` to apply to al
271271
## Correct Schedules task (cron) implementations
272272

273273
```ts
274-
import { schedules } from "@trigger.dev/sdk/v3";
274+
import { schedules } from "@trigger.dev/sdk";
275275

276276
export const firstScheduledTask = schedules.task({
277277
id: "first-scheduled-task",
@@ -312,7 +312,7 @@ export const firstScheduledTask = schedules.task({
312312
### Attach a Declarative schedule
313313

314314
```ts
315-
import { schedules } from "@trigger.dev/sdk/v3";
315+
import { schedules } from "@trigger.dev/sdk";
316316

317317
// Sepcify a cron pattern (UTC)
318318
export const firstScheduledTask = schedules.task({
@@ -326,7 +326,7 @@ export const firstScheduledTask = schedules.task({
326326
```
327327

328328
```ts
329-
import { schedules } from "@trigger.dev/sdk/v3";
329+
import { schedules } from "@trigger.dev/sdk";
330330

331331
// Specify a specific timezone like this:
332332
export const secondScheduledTask = schedules.task({
@@ -375,7 +375,7 @@ const createdSchedule = await schedules.create({
375375
Schema tasks validate payloads against a schema before execution:
376376

377377
```ts
378-
import { schemaTask } from "@trigger.dev/sdk/v3";
378+
import { schemaTask } from "@trigger.dev/sdk";
379379
import { z } from "zod";
380380

381381
const myTask = schemaTask({
@@ -400,7 +400,7 @@ When you trigger a task from your backend code, you need to set the `TRIGGER_SEC
400400
Triggers a single run of a task with specified payload and options without importing the task. Use type-only imports for full type checking.
401401

402402
```ts
403-
import { tasks } from "@trigger.dev/sdk/v3";
403+
import { tasks } from "@trigger.dev/sdk";
404404
import type { emailSequence } from "~/trigger/emails";
405405

406406
export async function POST(request: Request) {
@@ -418,7 +418,7 @@ export async function POST(request: Request) {
418418
Triggers multiple runs of a single task with different payloads without importing the task.
419419

420420
```ts
421-
import { tasks } from "@trigger.dev/sdk/v3";
421+
import { tasks } from "@trigger.dev/sdk";
422422
import type { emailSequence } from "~/trigger/emails";
423423

424424
export async function POST(request: Request) {
@@ -436,7 +436,7 @@ export async function POST(request: Request) {
436436
Triggers multiple runs of different tasks at once, useful when you need to execute multiple tasks simultaneously.
437437

438438
```ts
439-
import { batch } from "@trigger.dev/sdk/v3";
439+
import { batch } from "@trigger.dev/sdk";
440440
import type { myTask1, myTask2 } from "~/trigger/myTasks";
441441

442442
export async function POST(request: Request) {
@@ -621,7 +621,7 @@ const handle = await myTask.trigger(
621621
Access metadata inside a run:
622622

623623
```ts
624-
import { task, metadata } from "@trigger.dev/sdk/v3";
624+
import { task, metadata } from "@trigger.dev/sdk";
625625

626626
export const myTask = task({
627627
id: "my-task",
@@ -713,7 +713,7 @@ Trigger.dev Realtime enables subscribing to runs for real-time updates on run st
713713
Subscribe to a run after triggering a task:
714714

715715
```ts
716-
import { runs, tasks } from "@trigger.dev/sdk/v3";
716+
import { runs, tasks } from "@trigger.dev/sdk";
717717

718718
async function myBackend() {
719719
const handle = await tasks.trigger("my-task", { some: "data" });
@@ -735,7 +735,7 @@ async function myBackend() {
735735
You can infer types of run's payload and output by passing the task type:
736736

737737
```ts
738-
import { runs } from "@trigger.dev/sdk/v3";
738+
import { runs } from "@trigger.dev/sdk";
739739
import type { myTask } from "./trigger/my-task";
740740

741741
for await (const run of runs.subscribeToRun<typeof myTask>(handle.id)) {
@@ -752,7 +752,7 @@ for await (const run of runs.subscribeToRun<typeof myTask>(handle.id)) {
752752
Stream data in realtime from inside your tasks using the metadata system:
753753

754754
```ts
755-
import { task, metadata } from "@trigger.dev/sdk/v3";
755+
import { task, metadata } from "@trigger.dev/sdk";
756756
import OpenAI from "openai";
757757

758758
export type STREAMS = {
@@ -947,7 +947,7 @@ For most use cases, Realtime hooks are preferred over SWR hooks with polling due
947947
For client-side usage, generate a public access token with appropriate scopes:
948948

949949
```ts
950-
import { auth } from "@trigger.dev/sdk/v3";
950+
import { auth } from "@trigger.dev/sdk";
951951

952952
const publicToken = await auth.createPublicToken({
953953
scopes: {
@@ -967,7 +967,7 @@ Idempotency ensures that an operation produces the same result when called multi
967967
Provide an `idempotencyKey` when triggering a task to ensure it runs only once with that key:
968968

969969
```ts
970-
import { idempotencyKeys, task } from "@trigger.dev/sdk/v3";
970+
import { idempotencyKeys, task } from "@trigger.dev/sdk";
971971

972972
export const myTask = task({
973973
id: "my-task",
@@ -1058,7 +1058,7 @@ function hash(payload: any): string {
10581058

10591059
```ts
10601060
// onFailure executes after all retries are exhausted; use for notifications, logging, or side effects on final failure:
1061-
import { task, logger } from "@trigger.dev/sdk/v3";
1061+
import { task, logger } from "@trigger.dev/sdk";
10621062

10631063
export const loggingExample = task({
10641064
id: "logging-example",
@@ -1078,7 +1078,7 @@ export const loggingExample = task({
10781078
The `trigger.config.ts` file configures your Trigger.dev project, specifying task locations, retry settings, telemetry, and build options.
10791079

10801080
```ts
1081-
import { defineConfig } from "@trigger.dev/sdk/v3";
1081+
import { defineConfig } from "@trigger.dev/sdk";
10821082

10831083
export default defineConfig({
10841084
project: "<project ref>",
@@ -1226,7 +1226,7 @@ await myTask.trigger({ name: "Alice", age: 30 });
12261226

12271227
Before generating any code, you MUST verify:
12281228

1229-
1. Are you importing from `@trigger.dev/sdk/v3`? If not, STOP and FIX.
1229+
1. Are you importing from `@trigger.dev/sdk` (NOT `@trigger.dev/sdk/v3`)? If not, STOP and FIX.
12301230
2. Have you exported every task? If not, STOP and FIX.
12311231
3. Have you generated any DEPRECATED code patterns? If yes, STOP and FIX.
12321232

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
---
2+
area: webapp
3+
type: fix
4+
---
5+
6+
Fix `OrganizationsPresenter.#getEnvironment` matching the wrong development environment on teams with multiple members. All dev environments share the slug `"dev"`, so the previous `find` by slug alone could return another member's environment. Now filters DEVELOPMENT environments by `orgMember.userId` to ensure the logged-in user's dev environment is selected.
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
---
2+
area: webapp
3+
type: feature
4+
---
5+
6+
Add platform notifications to inform users about new features, changelogs, and platform events directly in the dashboard.

apps/supervisor/src/env.ts

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -120,7 +120,11 @@ const Env = z
120120

121121
KUBERNETES_MEMORY_OVERHEAD_GB: z.coerce.number().min(0).optional(), // Optional memory overhead to add to the limit in GB
122122
KUBERNETES_SCHEDULER_NAME: z.string().optional(), // Custom scheduler name for pods
123-
KUBERNETES_LARGE_MACHINE_POOL_LABEL: z.string().optional(), // if set, large-* presets affinity for machinepool=<value>
123+
// Large machine affinity settings - large-* presets prefer a dedicated pool
124+
KUBERNETES_LARGE_MACHINE_AFFINITY_ENABLED: BoolEnv.default(false),
125+
KUBERNETES_LARGE_MACHINE_AFFINITY_POOL_LABEL_KEY: z.string().trim().min(1).default("node.cluster.x-k8s.io/machinepool"),
126+
KUBERNETES_LARGE_MACHINE_AFFINITY_POOL_LABEL_VALUE: z.string().trim().min(1).default("large-machines"),
127+
KUBERNETES_LARGE_MACHINE_AFFINITY_WEIGHT: z.coerce.number().int().min(1).max(100).default(100),
124128

125129
// Project affinity settings - pods from the same project prefer the same node
126130
KUBERNETES_PROJECT_AFFINITY_ENABLED: BoolEnv.default(false),
@@ -131,6 +135,13 @@ const Env = z
131135
.min(1)
132136
.default("kubernetes.io/hostname"),
133137

138+
// Schedule affinity settings - runs from schedule trees prefer a dedicated pool
139+
KUBERNETES_SCHEDULE_AFFINITY_ENABLED: BoolEnv.default(false),
140+
KUBERNETES_SCHEDULE_AFFINITY_POOL_LABEL_KEY: z.string().trim().min(1).default("node.cluster.x-k8s.io/machinepool"),
141+
KUBERNETES_SCHEDULE_AFFINITY_POOL_LABEL_VALUE: z.string().trim().min(1).default("scheduled-runs"),
142+
KUBERNETES_SCHEDULE_AFFINITY_WEIGHT: z.coerce.number().int().min(1).max(100).default(80),
143+
KUBERNETES_SCHEDULE_ANTI_AFFINITY_WEIGHT: z.coerce.number().int().min(1).max(100).default(20),
144+
134145
// Placement tags settings
135146
PLACEMENT_TAGS_ENABLED: BoolEnv.default(false),
136147
PLACEMENT_TAGS_PREFIX: z.string().default("node.cluster.x-k8s.io"),

apps/supervisor/src/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -339,6 +339,7 @@ class ManagedSupervisor {
339339
snapshotFriendlyId: message.snapshot.friendlyId,
340340
placementTags: message.placementTags,
341341
traceContext: message.run.traceContext,
342+
annotations: message.run.annotations,
342343
});
343344

344345
// Disabled for now

apps/supervisor/src/workloadManager/kubernetes.ts

Lines changed: 87 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -120,7 +120,7 @@ export class KubernetesWorkloadManager implements WorkloadManager {
120120
},
121121
spec: {
122122
...this.addPlacementTags(this.#defaultPodSpec, opts.placementTags),
123-
affinity: this.#getAffinity(opts.machine, opts.projectId),
123+
affinity: this.#getAffinity(opts),
124124
terminationGracePeriodSeconds: 60 * 60,
125125
containers: [
126126
{
@@ -335,13 +335,22 @@ export class KubernetesWorkloadManager implements WorkloadManager {
335335
};
336336
}
337337

338+
#isScheduledRun(opts: WorkloadManagerCreateOptions): boolean {
339+
return opts.annotations?.rootTriggerSource === "schedule";
340+
}
341+
338342
#getSharedLabels(opts: WorkloadManagerCreateOptions): Record<string, string> {
339343
return {
340344
env: opts.envId,
341345
envtype: this.#envTypeToLabelValue(opts.envType),
342346
org: opts.orgId,
343347
project: opts.projectId,
344348
machine: opts.machine.name,
349+
// We intentionally use a boolean label rather than exposing the full trigger source
350+
// (e.g. sdk, api, cli, mcp, schedule) to keep label cardinality low in metrics.
351+
// The schedule vs non-schedule distinction is all we need for the current metrics
352+
// and pool-level scheduling decisions; finer-grained source breakdowns live in run annotations.
353+
scheduled: String(this.#isScheduledRun(opts)),
345354
};
346355
}
347356

@@ -390,22 +399,43 @@ export class KubernetesWorkloadManager implements WorkloadManager {
390399
return preset.name.startsWith("large-");
391400
}
392401

393-
#getAffinity(preset: MachinePreset, projectId: string): k8s.V1Affinity | undefined {
394-
const nodeAffinity = this.#getNodeAffinityRules(preset);
395-
const podAffinity = this.#getProjectPodAffinity(projectId);
396-
397-
if (!nodeAffinity && !podAffinity) {
402+
#getAffinity(opts: WorkloadManagerCreateOptions): k8s.V1Affinity | undefined {
403+
const largeNodeAffinity = this.#getNodeAffinityRules(opts.machine);
404+
const scheduleNodeAffinity = this.#getScheduleNodeAffinityRules(this.#isScheduledRun(opts));
405+
const podAffinity = this.#getProjectPodAffinity(opts.projectId);
406+
407+
// Merge node affinity rules from multiple sources
408+
const preferred = [
409+
...(largeNodeAffinity?.preferredDuringSchedulingIgnoredDuringExecution ?? []),
410+
...(scheduleNodeAffinity?.preferredDuringSchedulingIgnoredDuringExecution ?? []),
411+
];
412+
// Only large machine affinity produces hard requirements (non-large runs must stay off the large pool).
413+
// Schedule affinity is soft both ways.
414+
const required = [
415+
...(largeNodeAffinity?.requiredDuringSchedulingIgnoredDuringExecution?.nodeSelectorTerms ?? []),
416+
];
417+
418+
const hasNodeAffinity = preferred.length > 0 || required.length > 0;
419+
420+
if (!hasNodeAffinity && !podAffinity) {
398421
return undefined;
399422
}
400423

401424
return {
402-
...(nodeAffinity && { nodeAffinity }),
425+
...(hasNodeAffinity && {
426+
nodeAffinity: {
427+
...(preferred.length > 0 && { preferredDuringSchedulingIgnoredDuringExecution: preferred }),
428+
...(required.length > 0 && {
429+
requiredDuringSchedulingIgnoredDuringExecution: { nodeSelectorTerms: required },
430+
}),
431+
},
432+
}),
403433
...(podAffinity && { podAffinity }),
404434
};
405435
}
406436

407437
#getNodeAffinityRules(preset: MachinePreset): k8s.V1NodeAffinity | undefined {
408-
if (!env.KUBERNETES_LARGE_MACHINE_POOL_LABEL) {
438+
if (!env.KUBERNETES_LARGE_MACHINE_AFFINITY_ENABLED) {
409439
return undefined;
410440
}
411441

@@ -414,13 +444,13 @@ export class KubernetesWorkloadManager implements WorkloadManager {
414444
return {
415445
preferredDuringSchedulingIgnoredDuringExecution: [
416446
{
417-
weight: 100,
447+
weight: env.KUBERNETES_LARGE_MACHINE_AFFINITY_WEIGHT,
418448
preference: {
419449
matchExpressions: [
420450
{
421-
key: "node.cluster.x-k8s.io/machinepool",
451+
key: env.KUBERNETES_LARGE_MACHINE_AFFINITY_POOL_LABEL_KEY,
422452
operator: "In",
423-
values: [env.KUBERNETES_LARGE_MACHINE_POOL_LABEL],
453+
values: [env.KUBERNETES_LARGE_MACHINE_AFFINITY_POOL_LABEL_VALUE],
424454
},
425455
],
426456
},
@@ -436,9 +466,9 @@ export class KubernetesWorkloadManager implements WorkloadManager {
436466
{
437467
matchExpressions: [
438468
{
439-
key: "node.cluster.x-k8s.io/machinepool",
469+
key: env.KUBERNETES_LARGE_MACHINE_AFFINITY_POOL_LABEL_KEY,
440470
operator: "NotIn",
441-
values: [env.KUBERNETES_LARGE_MACHINE_POOL_LABEL],
471+
values: [env.KUBERNETES_LARGE_MACHINE_AFFINITY_POOL_LABEL_VALUE],
442472
},
443473
],
444474
},
@@ -447,6 +477,50 @@ export class KubernetesWorkloadManager implements WorkloadManager {
447477
};
448478
}
449479

480+
#getScheduleNodeAffinityRules(isScheduledRun: boolean): k8s.V1NodeAffinity | undefined {
481+
if (!env.KUBERNETES_SCHEDULE_AFFINITY_ENABLED || !env.KUBERNETES_SCHEDULE_AFFINITY_POOL_LABEL_VALUE) {
482+
return undefined;
483+
}
484+
485+
if (isScheduledRun) {
486+
// soft preference for the schedule pool
487+
return {
488+
preferredDuringSchedulingIgnoredDuringExecution: [
489+
{
490+
weight: env.KUBERNETES_SCHEDULE_AFFINITY_WEIGHT,
491+
preference: {
492+
matchExpressions: [
493+
{
494+
key: env.KUBERNETES_SCHEDULE_AFFINITY_POOL_LABEL_KEY,
495+
operator: "In",
496+
values: [env.KUBERNETES_SCHEDULE_AFFINITY_POOL_LABEL_VALUE],
497+
},
498+
],
499+
},
500+
},
501+
],
502+
};
503+
}
504+
505+
// soft anti-affinity: non-schedule runs prefer to avoid the schedule pool
506+
return {
507+
preferredDuringSchedulingIgnoredDuringExecution: [
508+
{
509+
weight: env.KUBERNETES_SCHEDULE_ANTI_AFFINITY_WEIGHT,
510+
preference: {
511+
matchExpressions: [
512+
{
513+
key: env.KUBERNETES_SCHEDULE_AFFINITY_POOL_LABEL_KEY,
514+
operator: "NotIn",
515+
values: [env.KUBERNETES_SCHEDULE_AFFINITY_POOL_LABEL_VALUE],
516+
},
517+
],
518+
},
519+
},
520+
],
521+
};
522+
}
523+
450524
#getProjectPodAffinity(projectId: string): k8s.V1PodAffinity | undefined {
451525
if (!env.KUBERNETES_PROJECT_AFFINITY_ENABLED) {
452526
return undefined;

0 commit comments

Comments
 (0)