diff --git a/.changeset/agent-skills-bundled-in-sdk.md b/.changeset/agent-skills-bundled-in-sdk.md deleted file mode 100644 index 2c3ae96f521..00000000000 --- a/.changeset/agent-skills-bundled-in-sdk.md +++ /dev/null @@ -1,6 +0,0 @@ ---- -"@trigger.dev/sdk": patch -"trigger.dev": patch ---- - -`@trigger.dev/sdk` now bundles the Trigger.dev agent skills and a curated snapshot of the docs those skills reference. The skills that `trigger skills` installs into your coding agent read this content from node_modules, so the guidance your AI assistant follows is pinned to the SDK version installed in your project and stays current across upgrades instead of going stale until the next reinstall. diff --git a/.changeset/agent-skills.md b/.changeset/agent-skills.md deleted file mode 100644 index 5ed3b11fc2f..00000000000 --- a/.changeset/agent-skills.md +++ /dev/null @@ -1,16 +0,0 @@ ---- -"@trigger.dev/sdk": patch -"@trigger.dev/core": patch -"@trigger.dev/build": patch -"trigger.dev": patch ---- - -Add Agent Skills for `chat.agent`. Drop a folder with a `SKILL.md` and any helper scripts/references next to your task code, register it with `skills.define({ id, path })`, and the CLI bundles it into the deploy image automatically — no `trigger.config.ts` changes. The agent gets a one-line summary in its system prompt and discovers full instructions on demand via `loadSkill`, with `bash` and `readFile` tools scoped per-skill (path-traversal guards, output caps, abort-signal propagation). - -```ts -const pdfSkill = skills.define({ id: "pdf-extract", path: "./skills/pdf-extract" }); - -chat.skills.set([await pdfSkill.local()]); -``` - -Built on the [AI SDK cookbook pattern](https://ai-sdk.dev/cookbook/guides/agent-skills) — portable across providers. SDK + CLI only for now; dashboard-editable `SKILL.md` text is on the roadmap. diff --git a/.changeset/ai-prompts.md b/.changeset/ai-prompts.md deleted file mode 100644 index 511aa303097..00000000000 --- a/.changeset/ai-prompts.md +++ /dev/null @@ -1,52 +0,0 @@ ---- -"@trigger.dev/sdk": minor ---- - -**AI Prompts** — define prompt templates as code alongside your tasks, version them on deploy, and override the text or model from the dashboard without redeploying. Prompts integrate with the Vercel AI SDK via `toAISDKTelemetry()` (links every generation span back to the prompt) and with `chat.agent` via `chat.prompt.set()` + `chat.toStreamTextOptions()`. - -```ts -import { prompts } from "@trigger.dev/sdk"; -import { generateText } from "ai"; -import { openai } from "@ai-sdk/openai"; -import { z } from "zod"; - -export const supportPrompt = prompts.define({ - id: "customer-support", - model: "gpt-4o", - config: { temperature: 0.7 }, - variables: z.object({ - customerName: z.string(), - plan: z.string(), - issue: z.string(), - }), - content: `You are a support agent for Acme. - -Customer: {{customerName}} ({{plan}} plan) -Issue: {{issue}}`, -}); - -const resolved = await supportPrompt.resolve({ - customerName: "Alice", - plan: "Pro", - issue: "Can't access billing", -}); - -const result = await generateText({ - model: openai(resolved.model ?? "gpt-4o"), - system: resolved.text, - prompt: "Can't access billing", - ...resolved.toAISDKTelemetry(), -}); -``` - -**What you get:** - -- **Code-defined, deploy-versioned templates** — define with `prompts.define({ id, model, config, variables, content })`. Every deploy creates a new version visible in the dashboard. Mustache-style placeholders (`{{var}}`, `{{#cond}}...{{/cond}}`) with Zod / ArkType / Valibot-typed variables. -- **Dashboard overrides** — change a prompt's text or model from the dashboard without redeploying. Overrides take priority over the deployed "current" version and are environment-scoped (dev / staging / production independent). -- **Resolve API** — `prompt.resolve(vars, { version?, label? })` returns the compiled `text`, resolved `model`, `version`, and labels. Standalone `prompts.resolve(slug, vars)` for cross-file resolution with full type inference on slug and variable shape. -- **AI SDK integration** — spread `resolved.toAISDKTelemetry({ ...extra })` into any `generateText` / `streamText` call and every generation span links to the prompt in the dashboard alongside its input variables, model, tokens, and cost. -- **`chat.agent` integration** — `chat.prompt.set(resolved)` stores the resolved prompt run-scoped; `chat.toStreamTextOptions({ registry })` pulls `system`, `model` (resolved via the AI SDK provider registry), `temperature` / `maxTokens` / etc., and telemetry into a single spread for `streamText`. -- **Management SDK** — `prompts.list()`, `prompts.versions(slug)`, `prompts.promote(slug, version)`, `prompts.createOverride(slug, body)`, `prompts.updateOverride(slug, body)`, `prompts.removeOverride(slug)`, `prompts.reactivateOverride(slug, version)`. -- **Dashboard** — prompts list with per-prompt usage sparklines; per-prompt detail with Template / Details / Versions / Generations / Metrics tabs. AI generation spans get a custom inspector showing the linked prompt's metadata, input variables, and template content alongside model, tokens, cost, and the message thread. - -See [/docs/ai/prompts](https://trigger.dev/docs/ai/prompts) for the full reference — template syntax, version resolution order, override workflow, and type utilities (`PromptHandle`, `PromptIdentifier`, `PromptVariables`). diff --git a/.changeset/ai-sdk-7-support.md b/.changeset/ai-sdk-7-support.md deleted file mode 100644 index 9f81c50973f..00000000000 --- a/.changeset/ai-sdk-7-support.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -"@trigger.dev/sdk": patch ---- - -Adds AI SDK 7 support. The `ai` peer range now includes v7, and the `chat.agent` / chat surfaces work against v7's ESM-only build. On v7, install `@ai-sdk/otel` alongside `ai` and the SDK registers it for you so `experimental_telemetry` spans keep flowing into your run traces (v7 stopped emitting them from `ai` core). v5 and v6 keep working unchanged. diff --git a/.changeset/ai-tool-helpers.md b/.changeset/ai-tool-helpers.md deleted file mode 100644 index 09e3b612ada..00000000000 --- a/.changeset/ai-tool-helpers.md +++ /dev/null @@ -1,15 +0,0 @@ ---- -"@trigger.dev/sdk": patch ---- - -Add `ai.toolExecute(task)` so you can wire a Trigger subtask in as the `execute` handler of an AI SDK `tool()` while defining `description` and `inputSchema` yourself — useful when you want full control over the tool surface and just need Trigger's subtask machinery for the body. - -```ts -const myTool = tool({ - description: "...", - inputSchema: z.object({ ... }), - execute: ai.toolExecute(mySubtask), -}); -``` - -`ai.tool(task)` (`toolFromTask`) keeps doing the all-in-one wrap and now aligns its return type with AI SDK's `ToolSet`. Minimum `ai` peer raised to `^6.0.116` to avoid cross-version `ToolSet` mismatches in monorepos. diff --git a/.changeset/backpressure-scale-up-freeze.md b/.changeset/backpressure-scale-up-freeze.md deleted file mode 100644 index b69fad0f262..00000000000 --- a/.changeset/backpressure-scale-up-freeze.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -"@trigger.dev/core": patch ---- - -Add optional `shouldPauseScaling` to the supervisor consumer pool scaling options to freeze scale-up while it returns true (scale-down stays allowed). diff --git a/.changeset/bundle-skills-single-pass.md b/.changeset/bundle-skills-single-pass.md deleted file mode 100644 index 30b2c428b22..00000000000 --- a/.changeset/bundle-skills-single-pass.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -"trigger.dev": patch ---- - -Fix `chat.agent` skills silently missing in `trigger dev` for projects whose task files read `process.env` at module top level (e.g. a third-party SDK client initialized at import). Skill folders now bundle into `.trigger/skills/` reliably regardless of which env vars are set when the CLI launches. diff --git a/.changeset/cap-idempotency-key-length.md b/.changeset/cap-idempotency-key-length.md deleted file mode 100644 index d1360369148..00000000000 --- a/.changeset/cap-idempotency-key-length.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -"@trigger.dev/core": patch ---- - -Reject overlong `idempotencyKey` values at the API boundary so they no longer trip an internal size limit on the underlying unique index and surface as a generic 500. Inputs are capped at 2048 characters — well above what `idempotencyKeys.create()` produces (a 64-character hash) and above any realistic raw key. Applies to `tasks.trigger`, `tasks.batchTrigger`, `batch.create` (Phase 1 streaming batches), `wait.createToken`, `wait.forDuration`, and the input/session stream waitpoint endpoints. Over-limit requests now return a structured 400 instead. diff --git a/.changeset/chat-agent-hardening.md b/.changeset/chat-agent-hardening.md deleted file mode 100644 index 0ea82c0617e..00000000000 --- a/.changeset/chat-agent-hardening.md +++ /dev/null @@ -1,6 +0,0 @@ ---- -"@trigger.dev/sdk": patch -"@trigger.dev/core": patch ---- - -Reliability fixes for `chat.agent`. A user message sent while the agent is streaming is no longer delivered twice (which could run a duplicate turn), input appends now carry an idempotency key so a retried send can't duplicate a message, stopping a generation clears the streaming state so a page reload doesn't replay the stopped turn, and runs can now carry the full set of dashboard tags instead of being silently truncated. `onTurnComplete` now fires on errored turns (with the thrown error attached) and the failed turn's user message is persisted so it isn't lost on the next run. Custom agents and manual `chat.writeTurnComplete` callers now trim the output stream, sending a custom action no longer leaves a second stream reader running, and a long-lived `watch` subscription no longer grows its dedupe set without bound. diff --git a/.changeset/chat-agent-on-boot-hook.md b/.changeset/chat-agent-on-boot-hook.md deleted file mode 100644 index 5eaa078e65e..00000000000 --- a/.changeset/chat-agent-on-boot-hook.md +++ /dev/null @@ -1,21 +0,0 @@ ---- -"@trigger.dev/sdk": minor ---- - -Adds `onBoot` to `chat.agent` — a lifecycle hook that fires once per worker process picking up the chat. Runs for the initial run, preloaded runs, AND reactive continuation runs (post-cancel, crash, `endRun`, `requestUpgrade`, OOM retry), before any other hook. Use it to initialize `chat.local`, open per-process resources, or re-hydrate state from your DB on continuation — anywhere the SAME run picking up after suspend/resume isn't enough. - -```ts -const userContext = chat.local<{ name: string; plan: string }>({ id: "userContext" }); - -export const myChat = chat.agent({ - id: "my-chat", - onBoot: async ({ clientData, continuation }) => { - const user = await db.user.findUnique({ where: { id: clientData.userId } }); - userContext.init({ name: user.name, plan: user.plan }); - }, - run: async ({ messages, signal }) => - streamText({ model: openai("gpt-4o"), messages, abortSignal: signal }), -}); -``` - -Use `onBoot` (not `onChatStart`) for state setup that must run every time a worker picks up the chat — `onChatStart` fires once per chat and won't run on continuation, leaving `chat.local` uninitialized when `run()` tries to use it. diff --git a/.changeset/chat-agent-preview-branch.md b/.changeset/chat-agent-preview-branch.md deleted file mode 100644 index 0d5407b7dea..00000000000 --- a/.changeset/chat-agent-preview-branch.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -"@trigger.dev/sdk": patch ---- - -Fix `chat.agent` / `AgentChat` when the agent is deployed to a Trigger.dev preview branch. The realtime message-append and stream-subscribe calls now send the `x-trigger-branch` header (sourced from the same resolver `sessions.start` uses), so messaging a preview-branch chat agent no longer fails with `x-trigger-branch header required for preview env`. diff --git a/.changeset/chat-agent-tools.md b/.changeset/chat-agent-tools.md deleted file mode 100644 index 1d44ea2a659..00000000000 --- a/.changeset/chat-agent-tools.md +++ /dev/null @@ -1,15 +0,0 @@ ---- -"@trigger.dev/sdk": patch ---- - -Add a `tools` option to `chat.agent`. Declaring your tools here threads them into the SDK's internal `convertToModelMessages`, so each tool's `toModelOutput` is re-applied when prior-turn history is re-converted. - -```ts -chat.agent({ - tools: { readFile, search }, - run: async ({ messages, tools, signal }) => - streamText({ model, messages, tools, abortSignal: signal }), -}); -``` - -Also exports `InferChatUIMessageFromTools` to derive the chat `UIMessage` type (typed tool parts) directly from a tool set. diff --git a/.changeset/chat-agent.md b/.changeset/chat-agent.md deleted file mode 100644 index 733a8ab22e4..00000000000 --- a/.changeset/chat-agent.md +++ /dev/null @@ -1,44 +0,0 @@ ---- -"@trigger.dev/sdk": minor -"@trigger.dev/core": patch ---- - -**AI Agents** — run AI SDK chat completions as durable Trigger.dev agents instead of fragile API routes. Define an agent in one function, point `useChat` at it from React, and the conversation survives page refreshes, network blips, and process restarts. - -```ts -import { chat } from "@trigger.dev/sdk/ai"; -import { streamText } from "ai"; -import { openai } from "@ai-sdk/openai"; - -export const myChat = chat.agent({ - id: "my-chat", - run: async ({ messages, signal }) => - streamText({ model: openai("gpt-4o"), messages, abortSignal: signal }), -}); -``` - -```tsx -import { useChat } from "@ai-sdk/react"; -import { useTriggerChatTransport } from "@trigger.dev/sdk/chat/react"; - -const transport = useTriggerChatTransport({ task: "my-chat", accessToken, startSession }); -const { messages, sendMessage } = useChat({ transport }); -``` - -**What you get:** - -- **AI SDK `useChat` integration** — a custom [`ChatTransport`](https://sdk.vercel.ai/docs/ai-sdk-ui/transport) (`useTriggerChatTransport`) plugs straight into Vercel AI SDK's `useChat` hook. Text streaming, tool calls, reasoning, and `data-*` parts all work natively over Trigger.dev's realtime streams. No custom API routes needed. -- **First-turn fast path (`chat.headStart`)** — opt-in handler that runs the first turn's `streamText` step in your warm server process while the agent run boots in parallel, cutting cold-start TTFC by roughly half (measured 2801ms → 1218ms on `claude-sonnet-4-6`). The agent owns step 2+ (tool execution, persistence, hooks) so heavy deps stay where they belong. Web Fetch handler works natively in Next.js, Hono, SvelteKit, Remix, Workers, etc.; bridge to Express/Fastify/Koa via `chat.toNodeListener`. New `@trigger.dev/sdk/chat-server` subpath. -- **Multi-turn durability via Sessions** — every chat is backed by a durable Session that outlives any individual run. Conversations resume across page refreshes, idle timeout, crashes, and deploys; `resume: true` reconnects via `lastEventId` so clients only see new chunks. `sessions.list` enumerates chats for inbox-style UIs. -- **Auto-accumulated history, delta-only wire** — the backend accumulates the full conversation across turns; clients only ship the new message each turn. Long chats never hit the 512 KiB body cap. Register `hydrateMessages` to be the source of truth yourself. -- **Lifecycle hooks** — `onPreload`, `onChatStart`, `onValidateMessages`, `hydrateMessages`, `onTurnStart`, `onBeforeTurnComplete`, `onTurnComplete`, `onChatSuspend`, `onChatResume` — for persistence, validation, and post-turn work. -- **Stop generation** — client-driven `transport.stopGeneration(chatId)` aborts mid-stream; the run stays alive for the next message, partial response is captured, and aborted parts (stuck `partial-call` tools, in-progress reasoning) are auto-cleaned. -- **Tool approvals (HITL)** — tools with `needsApproval: true` pause until the user approves or denies via `addToolApprovalResponse`. The runtime reconciles the updated assistant message by ID and continues `streamText`. -- **Steering and background injection** — `pendingMessages` injects user messages between tool-call steps so users can steer the agent mid-execution; `chat.inject()` + `chat.defer()` adds context from background work (self-review, RAG, safety checks) between turns. -- **Actions** — non-turn frontend commands (undo, rollback, regenerate, edit) sent via `transport.sendAction`. Fire `hydrateMessages` + `onAction` only — no turn hooks, no `run()`. `onAction` can return a `StreamTextResult` for a model response, or `void` for side-effect-only. -- **Typed state primitives** — `chat.local` for per-run state accessible from hooks, `run()`, tools, and subtasks (auto-serialized through `ai.toolExecute`); `chat.store` for typed shared data between agent and client; `chat.history` for reading and mutating the message chain; `clientDataSchema` for typed `clientData` in every hook. -- **`chat.toStreamTextOptions()`** — one spread into `streamText` wires up versioned system [Prompts](https://trigger.dev/docs/ai/prompts), model resolution, telemetry metadata, compaction, steering, and background injection. -- **Multi-tab coordination** — `multiTab: true` + `useMultiTabChat` prevents duplicate sends and syncs state across browser tabs via `BroadcastChannel`. Non-active tabs go read-only with live updates. -- **Network resilience** — built-in indefinite retry with bounded backoff, reconnect on `online` / tab refocus / bfcache restore, `Last-Event-ID` mid-stream resume. No app code needed. - -See [/docs/ai-chat](https://trigger.dev/docs/ai-chat/overview) for the full surface — quick start, three backend approaches (`chat.agent`, `chat.createSession`, raw task), persistence and code-sandbox patterns, type-level guides, and API reference. diff --git a/.changeset/chat-boot-cursor.md b/.changeset/chat-boot-cursor.md deleted file mode 100644 index eb1b7a41c98..00000000000 --- a/.changeset/chat-boot-cursor.md +++ /dev/null @@ -1,6 +0,0 @@ ---- -"@trigger.dev/sdk": patch -"@trigger.dev/core": patch ---- - -Continuation chat boots no longer stall for around 10 seconds before the first turn. The `session.in` resume cursor is now found with a non-blocking records read instead of draining an SSE long-poll (which always waited out its full 5 second inactivity window, twice per boot), the boot reads run concurrently, and chat snapshots carry the cursor so subsequent boots skip the scan entirely. diff --git a/.changeset/chat-head-start-prepare-messages.md b/.changeset/chat-head-start-prepare-messages.md deleted file mode 100644 index d8473cb5df8..00000000000 --- a/.changeset/chat-head-start-prepare-messages.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -"@trigger.dev/sdk": patch ---- - -Fix Head Start handovers breaking when a `chat.agent` also defines a `prepareMessages` hook. A handover hands the first turn's pending tool call to the agent as a tool-approval round whose trailing tool message must reach the model untouched. A `prepareMessages` hook that rewrites the last message (for example the recommended prompt-caching breakpoint) could disturb it, so the turn failed with "tool_use ids were found without tool_result". The agent now preserves that approval tail across `prepareMessages`, so caching and Head Start compose cleanly. diff --git a/.changeset/chat-headstart-api-client.md b/.changeset/chat-headstart-api-client.md deleted file mode 100644 index 4bdce864bf9..00000000000 --- a/.changeset/chat-headstart-api-client.md +++ /dev/null @@ -1,14 +0,0 @@ ---- -"@trigger.dev/sdk": patch ---- - -`chat.headStart` now accepts an `apiClient` option (base URL + access token), so the head-start route can create the session and trigger the agent run against a different project/environment than the warm server's ambient Trigger config. Useful when your `chat.agent` lives in a separate project from the app serving the route. Mirrors the `apiClient` option on `chat.createStartSessionAction`; your LLM provider keys stay in the `run` callback and are unaffected. - -```ts -export const POST = chat.headStart({ - agentId: "my-agent", - apiClient: { baseURL, accessToken }, - run: async ({ chat }) => - streamText({ ...chat.toStreamTextOptions({ tools }), model: anthropic("claude-sonnet-4-6") }), -}); -``` diff --git a/.changeset/chat-headstart-custom-backends.md b/.changeset/chat-headstart-custom-backends.md deleted file mode 100644 index d5969bd2d8f..00000000000 --- a/.changeset/chat-headstart-custom-backends.md +++ /dev/null @@ -1,26 +0,0 @@ ---- -"@trigger.dev/sdk": patch ---- - -`chat.headStart` now works with the `chat.customAgent` and `chat.createSession` backends, not only `chat.agent`. The warm step-1 response hands over to your loop the same way it does for a managed agent. - -In a `chat.customAgent` loop, consume the handover on turn 0: - -```ts -const conversation = new chat.MessageAccumulator(); -const { isFinal, skipped } = await conversation.consumeHandover({ payload }); -if (skipped) return; // warm handler aborted, so exit without a turn -if (isFinal) { - await chat.writeTurnComplete(); // step 1 is the response, no streamText -} else { - const result = streamText({ model, messages: conversation.modelMessages, tools }); - // Pass originalMessages so the handed-over tool round merges into the - // step-1 assistant instead of starting a new message. - const response = await chat.pipeAndCapture(result, { - originalMessages: conversation.uiMessages, - }); - if (response) await conversation.addResponse(response); -} -``` - -With `chat.createSession`, the iterator surfaces it as `turn.handover`; call `turn.complete()` with no argument on a final handover. The lower-level `chat.waitForHandover()` and `accumulator.applyHandover()` are also exported for hand-rolled loops. diff --git a/.changeset/chat-headstart-hydrate.md b/.changeset/chat-headstart-hydrate.md deleted file mode 100644 index 49e9bb926ee..00000000000 --- a/.changeset/chat-headstart-hydrate.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -"@trigger.dev/sdk": patch ---- - -Fix `chat.headStart` when `hydrateMessages` is registered. The warm route's step-1 partial now reaches the agent's accumulator on the hydrate path, so `onTurnComplete` carries the full first turn (the head-start user message included), tool-call handovers resume from step 2 instead of re-running step 1, and the assistant `messageId` stays stable across the handover. diff --git a/.changeset/chat-headstart-reasoning.md b/.changeset/chat-headstart-reasoning.md deleted file mode 100644 index a53d388c561..00000000000 --- a/.changeset/chat-headstart-reasoning.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -"@trigger.dev/sdk": patch ---- - -Preserve reasoning parts across the `chat.headStart` handover. Extended-thinking models' step-1 reasoning now lands in the durable session history (and `onTurnComplete`) under the same assistant `messageId`, with provider metadata intact so Anthropic thinking signatures survive replays. diff --git a/.changeset/chat-headstart-trigger-config.md b/.changeset/chat-headstart-trigger-config.md deleted file mode 100644 index d629d2fe8a9..00000000000 --- a/.changeset/chat-headstart-trigger-config.md +++ /dev/null @@ -1,15 +0,0 @@ ---- -"@trigger.dev/sdk": patch ---- - -Add `triggerConfig` support to `chat.headStart()` and `chat.openSession()`, so the auto-triggered handover-prepare run inherits tags, queue, machine, and other session trigger options the same way `chat.createStartSessionAction()` does. The `chat:{chatId}` tag is prepended automatically. - -```ts -export const POST = chat.headStart({ - agentId: "my-agent", - triggerConfig: { tags: ["org:acme"], queue: "chat" }, - run: async ({ chat }) => streamText({ ...chat.toStreamTextOptions(), model }), -}); -``` - -Because the session is created once on the first head-start turn and is idempotent on the chat id, this is the only place to set those options for a head-start chat's lifetime. `chat.createStartSessionAction()` now also forwards `maxDuration`, `region`, and `lockToVersion` so both session entry points stay consistent. diff --git a/.changeset/chat-history-read-primitives.md b/.changeset/chat-history-read-primitives.md deleted file mode 100644 index fd26ad8548b..00000000000 --- a/.changeset/chat-history-read-primitives.md +++ /dev/null @@ -1,21 +0,0 @@ ---- -"@trigger.dev/sdk": minor ---- - -Add read primitives to `chat.history` for HITL flows: `getPendingToolCalls()`, `getResolvedToolCalls()`, `extractNewToolResults(message)`, `getChain()`, and `findMessage(messageId)`. These lift the accumulator-walking logic that customers building human-in-the-loop tools were re-implementing into the SDK. - -Use `getPendingToolCalls()` to gate fresh user turns while a tool call is awaiting an answer. Use `extractNewToolResults(message)` to dedup tool results when persisting to your own store — the helper returns only the parts whose `toolCallId` is not already resolved on the chain. - -```ts -const pending = chat.history.getPendingToolCalls(); -if (pending.length > 0) { - // an addToolOutput is expected before a new user message -} - -onTurnComplete: async ({ responseMessage }) => { - const newResults = chat.history.extractNewToolResults(responseMessage); - for (const r of newResults) { - await db.toolResults.upsert({ id: r.toolCallId, output: r.output, errorText: r.errorText }); - } -}; -``` diff --git a/.changeset/chat-session-attributes.md b/.changeset/chat-session-attributes.md deleted file mode 100644 index ec4c6a54076..00000000000 --- a/.changeset/chat-session-attributes.md +++ /dev/null @@ -1,6 +0,0 @@ ---- -"@trigger.dev/sdk": patch -"@trigger.dev/core": patch ---- - -Stamp `gen_ai.conversation.id` (the chat id) on every span and metric emitted from inside a `chat.task` or `chat.agent` run. Lets you filter dashboard spans, runs, and metrics by the chat conversation that produced them — independent of the run boundary, so multi-run chats correlate cleanly. No code changes required on the user side. diff --git a/.changeset/chat-slim-wire-merge.md b/.changeset/chat-slim-wire-merge.md deleted file mode 100644 index 19ea48a8cdd..00000000000 --- a/.changeset/chat-slim-wire-merge.md +++ /dev/null @@ -1,31 +0,0 @@ ---- -"@trigger.dev/sdk": patch ---- - -Fix `chat.agent` HITL continuations on reasoning-heavy turns. Two changes that work together: - -- The per-turn merge now overlays the wire copy's tool-part state advancement onto the agent's existing chain — `state` + the matching resolution field (`output` / `errorText` / `approval`) come from the wire, everything else (text, reasoning, tool `input`, provider metadata) stays whatever the snapshot or `hydrateMessages` returned. Previously a full-message replace overwrote those fields with whatever the client shipped, so a slimmed wire copy landed a tool call with no `arguments` on the next LLM call. Covers `output-available` / `output-error` (HITL `addToolOutput`) and `approval-responded` / `output-denied` (approval flow). -- `TriggerChatTransport.sendMessages` and `AgentChat.sendRaw` now slim assistant messages that carry advanced tool parts. The wire payload is just `{ id, role, parts: [] }` for `submit-message` continuations; everything else passes through. Reasoning blobs and full tool inputs no longer ride the wire on every `addToolOutput` / `addToolApproveResponse`, so continuation payloads stay well under the `.in/append` cap on long agent loops. - -Note: `onValidateMessages` receives the slim wire on HITL turns. If you call `validateUIMessages` from `ai` against the full `messages` array it will reject the slim assistant; filter to user messages (or skip on HITL turns) — see the updated docstring on `onValidateMessages` for the recommended pattern. - -For `hydrateMessages` hooks that persist the chain, this release also adds a small helper to the `@trigger.dev/sdk/ai` surface: - -```ts -import { chat, upsertIncomingMessage } from "@trigger.dev/sdk/ai"; - -chat.agent({ - hydrateMessages: async ({ chatId, trigger, incomingMessages }) => { - const record = await db.chat.findUnique({ where: { id: chatId } }); - const stored = record?.messages ?? []; - if (upsertIncomingMessage(stored, { trigger, incomingMessages })) { - await db.chat.update({ where: { id: chatId }, data: { messages: stored } }); - } - return stored; - }, -}); -``` - -It pushes fresh user messages by id, no-ops on HITL continuations (the incoming shares an id with the existing assistant — the runtime overlays the new tool-state advance), and skips on non-`submit-message` triggers. Returns `true` if it mutated `stored` so the caller knows whether to persist. - -Net effect: `chat.addToolOutput(...)` / `chat.addToolApproveResponse(...)` on multi-step reasoning agents (OpenAI Responses with `store: false`, Anthropic extended thinking, etc.) no longer blows the cap and no longer corrupts the LLM input. diff --git a/.changeset/chat-start-session-action-typed-client-data.md b/.changeset/chat-start-session-action-typed-client-data.md deleted file mode 100644 index acd75037caf..00000000000 --- a/.changeset/chat-start-session-action-typed-client-data.md +++ /dev/null @@ -1,22 +0,0 @@ ---- -"@trigger.dev/sdk": patch ---- - -Type `chat.createStartSessionAction` against your chat agent so `clientData` is typed end-to-end on the first turn: - -```ts -import { chat } from "@trigger.dev/sdk/ai"; -import type { myChat } from "@/trigger/chat"; - -export const startChatSession = chat.createStartSessionAction("my-chat"); - -// In the browser, threaded from the transport's typed startSession callback: -const transport = useTriggerChatTransport({ - task: "my-chat", - startSession: ({ chatId, clientData }) => - startChatSession({ chatId, clientData }), - // ... -}); -``` - -`ChatStartSessionParams` gains a typed `clientData` field — folded into the first run's `payload.metadata` so `onPreload` / `onChatStart` see the same shape per-turn `metadata` carries via the transport. The opaque session-level `metadata` field is unchanged. diff --git a/.changeset/chat-start-session-api-client.md b/.changeset/chat-start-session-api-client.md deleted file mode 100644 index b5c7d7d11dc..00000000000 --- a/.changeset/chat-start-session-api-client.md +++ /dev/null @@ -1,13 +0,0 @@ ---- -"@trigger.dev/sdk": patch ---- - -`chat.createStartSessionAction` now accepts an `apiClient` option, so you can scope a chat session start to a specific environment's API config (`baseURL` / `accessToken`) without setting a global `TRIGGER_SECRET_KEY`. Useful when one server starts chats across more than one environment. - -```ts -const startSession = chat.createStartSessionAction("my-chat", { - apiClient: { baseURL, accessToken }, -}); - -await startSession({ chatId, clientData }); -``` diff --git a/.changeset/chat-system-prompt-caching.md b/.changeset/chat-system-prompt-caching.md deleted file mode 100644 index 7e5ba97dcd9..00000000000 --- a/.changeset/chat-system-prompt-caching.md +++ /dev/null @@ -1,25 +0,0 @@ ---- -"@trigger.dev/sdk": patch ---- - -Cache your chat agent's system prompt with Anthropic prompt caching. `chat.toStreamTextOptions()` now emits the system prompt as a cacheable message when you opt in, so a large, stable system block is billed at cache-read rates on every turn instead of full price. - -```ts -// at the streamText call site (Anthropic sugar) -streamText({ - ...chat.toStreamTextOptions({ cacheControl: { type: "ephemeral" } }), - messages, -}); - -// provider-agnostic equivalent -chat.toStreamTextOptions({ - systemProviderOptions: { anthropic: { cacheControl: { type: "ephemeral" } } }, -}); - -// or where the prompt is defined -chat.prompt.set(SYSTEM_PROMPT, { - providerOptions: { anthropic: { cacheControl: { type: "ephemeral" } } }, -}); -``` - -Without an option, `system` stays a plain string. Pairs with a `prepareMessages` cache breakpoint to cache the conversation prefix across turns too. diff --git a/.changeset/chat-transport-recreate-missing-session.md b/.changeset/chat-transport-recreate-missing-session.md deleted file mode 100644 index dba916e9442..00000000000 --- a/.changeset/chat-transport-recreate-missing-session.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -"@trigger.dev/sdk": patch ---- - -`useTriggerChatTransport` now recovers when restored session state points at a session that no longer exists in the current environment diff --git a/.changeset/cli-deploy-skip-rewrite-timestamp.md b/.changeset/cli-deploy-skip-rewrite-timestamp.md deleted file mode 100644 index 60e82732dce..00000000000 --- a/.changeset/cli-deploy-skip-rewrite-timestamp.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -"trigger.dev": patch ---- - -Add `TRIGGER_BUILD_SKIP_REWRITE_TIMESTAMP=1` escape hatch for local self-hosted builds whose buildx driver doesn't support `rewrite-timestamp` alongside push (e.g. orbstack's default `docker` driver). diff --git a/.changeset/cli-dev-without-project.md b/.changeset/cli-dev-without-project.md deleted file mode 100644 index 4e5b74a7f68..00000000000 --- a/.changeset/cli-dev-without-project.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -"trigger.dev": patch ---- - -Running a CLI command like `dev`, `deploy`, `preview`, or `update` before initializing a project no longer crashes with a raw `Cannot find matching package.json` stack trace. The CLI now detects the missing project and points you to `npx trigger.dev@latest init` instead. diff --git a/.changeset/cli-init-ai-tooling.md b/.changeset/cli-init-ai-tooling.md deleted file mode 100644 index a7a4c8be14a..00000000000 --- a/.changeset/cli-init-ai-tooling.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -"trigger.dev": patch ---- - -`trigger init` now sets up your AI coding assistant as part of project setup: pick the MCP server, the agent skills, or both, then scaffold with the CLI or hand off to your assistant. Adds a new `getting-started` agent skill that teaches assistants how to bootstrap Trigger.dev (install the SDK, write `trigger.config.ts`, create a first task, run `trigger dev`), so the AI-driven setup path works end to end. It ships in the CLI alongside the existing skills, version-matched to your SDK. diff --git a/.changeset/coerce-concurrency-key-to-string.md b/.changeset/coerce-concurrency-key-to-string.md deleted file mode 100644 index faccf7a48bf..00000000000 --- a/.changeset/coerce-concurrency-key-to-string.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -"@trigger.dev/core": patch ---- - -Coerce numeric `concurrencyKey` values to string at the API boundary across `tasks.trigger`, `tasks.batchTrigger`, and the Phase-2 streaming batch endpoint. diff --git a/.changeset/create-session-stop-continuation.md b/.changeset/create-session-stop-continuation.md deleted file mode 100644 index 4ac472a0c06..00000000000 --- a/.changeset/create-session-stop-continuation.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -"@trigger.dev/sdk": patch ---- - -Fix two `chat.createSession()` bugs: stopping a generation no longer wedges the run (the turn loop raced a `totalUsage` promise that never settles after a stop-abort), and continuation runs now wait for the next message instead of invoking the model with an empty prompt. diff --git a/.changeset/custom-agent-loop-fixes.md b/.changeset/custom-agent-loop-fixes.md deleted file mode 100644 index 4d37fff535c..00000000000 --- a/.changeset/custom-agent-loop-fixes.md +++ /dev/null @@ -1,9 +0,0 @@ ---- -"@trigger.dev/sdk": patch ---- - -Three fixes for custom agent loops (`chat.customAgent`, `chat.createSession`, and hand-rolled `MessageAccumulator` loops): - -- Continuation runs no longer replay already-answered user messages into the first turn. The `.in` resume cursor is now seeded before any listener attaches (the same boot logic `chat.agent` uses), so a chat that continues after a cancel, crash, or upgrade only sees genuinely new messages. -- Steering a hand-rolled loop mid-stream no longer wipes the in-flight assistant response. `chat.pipeAndCapture` now stamps a server-generated message id on the stream, so a `prepareStep` injection keeps the partial text instead of replacing the message. -- Task-backed tools (`ai.toolExecute`) now work from custom agent loops: the parent's session is threaded to the child run, so child tasks can stream progress into the chat with `chat.stream.writer({ target: "root" })` instead of failing with "session handle is not initialized". diff --git a/.changeset/dequeue-latency-histogram.md b/.changeset/dequeue-latency-histogram.md deleted file mode 100644 index 0b69b8c98a9..00000000000 --- a/.changeset/dequeue-latency-histogram.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -"@trigger.dev/core": patch ---- - -Record client-side dequeue API latency in the supervisor consumer pool as a Prometheus histogram (`queue_consumer_pool_dequeue_duration_seconds`, labelled by `outcome`: success/empty/error). diff --git a/.changeset/dev-branches.md b/.changeset/dev-branches.md deleted file mode 100644 index e0d7e4a6628..00000000000 --- a/.changeset/dev-branches.md +++ /dev/null @@ -1,6 +0,0 @@ ---- -"trigger.dev": patch -"@trigger.dev/core": patch ---- - -Add support for dev branches to the webapp and CLI. This allows humans (and agents) to run multiple local dev servers simultaneously, with a separate dashboard for each one. diff --git a/.changeset/duplicate-task-ids.md b/.changeset/duplicate-task-ids.md deleted file mode 100644 index c68724bfedd..00000000000 --- a/.changeset/duplicate-task-ids.md +++ /dev/null @@ -1,6 +0,0 @@ ---- -"@trigger.dev/core": patch -"trigger.dev": patch ---- - -`dev` and `deploy` now fail with a clear error when two tasks are defined with the same id, including across different task types (e.g. a scheduled task and a regular task sharing an id). Previously the second definition silently overwrote the first, so one of the tasks would vanish with no warning. Task ids are detected as duplicates during indexing (naming each offending id and the files it was found in), and the same rule is enforced server-side when the background worker is registered. diff --git a/.changeset/env-vars-tracing-forceflush-typecheck.md b/.changeset/env-vars-tracing-forceflush-typecheck.md deleted file mode 100644 index 9d90c0383d7..00000000000 --- a/.changeset/env-vars-tracing-forceflush-typecheck.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -"@trigger.dev/core": patch ---- - -Fix `@trigger.dev/core` build: cast the underlying log record exporter when calling `forceFlush` so it typechecks against the updated OpenTelemetry `LogRecordExporter` type (which no longer declares `forceFlush`). diff --git a/.changeset/envvars-import-is-secret.md b/.changeset/envvars-import-is-secret.md deleted file mode 100644 index 5fbe70f43ae..00000000000 --- a/.changeset/envvars-import-is-secret.md +++ /dev/null @@ -1,12 +0,0 @@ ---- -"@trigger.dev/core": patch ---- - -`envvars.upload` now accepts an optional `isSecret` flag, letting you create the imported variables as secret (redacted) environment variables. When omitted, variables default to non-secret. - -```ts -await envvars.upload("proj_1234", "prod", { - variables: { STRIPE_SECRET_KEY: "sk_live_..." }, - isSecret: true, -}); -``` diff --git a/.changeset/errors-api-schemas.md b/.changeset/errors-api-schemas.md deleted file mode 100644 index 023ca825885..00000000000 --- a/.changeset/errors-api-schemas.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -"@trigger.dev/core": patch ---- - -Add request and response schemas for the new Errors API (error groups). These back the env-scoped HTTP endpoints for listing error groups, retrieving a single group, and changing its state (resolve, ignore, unresolve), plus a `filter[error]` option on the runs list to fetch the runs behind a group. Exported from `@trigger.dev/core/v3` so the SDK can reuse them. diff --git a/.changeset/httpserver-skip-body-parsing.md b/.changeset/httpserver-skip-body-parsing.md deleted file mode 100644 index 094faecc461..00000000000 --- a/.changeset/httpserver-skip-body-parsing.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -"@trigger.dev/core": patch ---- - -Add an optional `skipBodyParsing` flag to the internal HTTP server route definition, letting a route respond without reading or parsing the request body. diff --git a/.changeset/idempotency-key-catalog-eviction.md b/.changeset/idempotency-key-catalog-eviction.md deleted file mode 100644 index b14ce3efd9d..00000000000 --- a/.changeset/idempotency-key-catalog-eviction.md +++ /dev/null @@ -1,6 +0,0 @@ ---- -"@trigger.dev/core": patch -"trigger.dev": patch ---- - -Fix idempotency key metadata (original key + scope) being silently dropped when a single run creates more than 1000 idempotency keys. The in-process catalog that maps a key's hash back to its original key/scope is no longer bounded to 1000 entries, so `idempotencyKeys.create()` results retain their metadata regardless of how many are created in a run. The catalog is now cleared at each run boundary so it does not accumulate across warm-start runs. diff --git a/.changeset/large-trigger-payload-offload.md b/.changeset/large-trigger-payload-offload.md deleted file mode 100644 index e8d87947166..00000000000 --- a/.changeset/large-trigger-payload-offload.md +++ /dev/null @@ -1,8 +0,0 @@ ---- -"@trigger.dev/core": patch -"@trigger.dev/sdk": patch ---- - -Offload large trigger payloads to object storage before sending the trigger API request. The SDK uploads packets at or above the existing 128KB limit and sends an `application/store` pointer instead of embedding large JSON in the request body. `TriggerTaskRequestBody` now validates that `application/store` payloads are non-empty storage paths. - -Payload uploads use the same resolved `ApiClient` as the trigger call (including `requestOptions.clientConfig`), not only the global `apiClientManager.client` — so custom `baseURL`, access token, and preview branch apply to both presign and trigger. diff --git a/.changeset/locals-key-dual-package-fix.md b/.changeset/locals-key-dual-package-fix.md deleted file mode 100644 index 38d42e19dfb..00000000000 --- a/.changeset/locals-key-dual-package-fix.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -"@trigger.dev/core": patch ---- - -Fix `LocalsKey` type incompatibility across dual-package builds. The phantom value-type brand no longer uses a module-level `unique symbol`, so a single TypeScript compilation that resolves the type from both the ESM and CJS outputs (which can happen under certain pnpm hoisting layouts) no longer sees two structurally-incompatible variants of the same type. diff --git a/.changeset/mcp-agent-chat-sessions.md b/.changeset/mcp-agent-chat-sessions.md deleted file mode 100644 index c3f01aebf28..00000000000 --- a/.changeset/mcp-agent-chat-sessions.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -"trigger.dev": patch ---- - -The CLI MCP server's agent-chat tools (`start_agent_chat`, `send_agent_message`, `close_agent_chat`) now run on the new Sessions primitive, so AI assistants driving a `chat.agent` get the same idempotent-by-`chatId`, durable-across-runs behavior the browser transport gets. Required PAT scopes go from `write:inputStreams` to `read:sessions` + `write:sessions`. diff --git a/.changeset/mcp-list-runs-region.md b/.changeset/mcp-list-runs-region.md deleted file mode 100644 index b72cfb23c97..00000000000 --- a/.changeset/mcp-list-runs-region.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -"trigger.dev": patch ---- - -MCP `list_runs` tool: add a `region` filter input and surface each run's executing region in the formatted summary. diff --git a/.changeset/mcp-trigger-task-no-default-wait.md b/.changeset/mcp-trigger-task-no-default-wait.md deleted file mode 100644 index 396c68bd005..00000000000 --- a/.changeset/mcp-trigger-task-no-default-wait.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -"trigger.dev": patch ---- - -The MCP server no longer tells the AI agent to wait for a run to complete after every `trigger_task` call. Waiting is now opt-in: the agent only waits when you ask it to (for example "trigger and then wait for it to finish"). This avoids burning tokens polling runs you didn't need to block on and keeps responses clearer. diff --git a/.changeset/mint-token-command.md b/.changeset/mint-token-command.md deleted file mode 100644 index 7e405355f65..00000000000 --- a/.changeset/mint-token-command.md +++ /dev/null @@ -1,9 +0,0 @@ ---- -"trigger.dev": patch ---- - -Adds `trigger.dev mint-token`, which mints a short-lived delegated token from your stored personal access token. The token authenticates against the API as you, can be narrowed with `--cap` and given a lifetime with `--ttl`, and prints to stdout so it can be captured. - -```bash -UAT=$(trigger.dev mint-token --ttl 3600 --cap read:runs) -``` diff --git a/.changeset/mock-chat-agent-test-harness.md b/.changeset/mock-chat-agent-test-harness.md deleted file mode 100644 index 9876e56a9f7..00000000000 --- a/.changeset/mock-chat-agent-test-harness.md +++ /dev/null @@ -1,8 +0,0 @@ ---- -"@trigger.dev/sdk": patch -"@trigger.dev/core": patch ---- - -Unit-test `chat.agent` definitions offline with `mockChatAgent` from `@trigger.dev/sdk/ai/test`. Drives a real agent's turn loop in-process — no network, no task runtime — so you can send messages, actions, and stop signals via driver methods, inspect captured output chunks, and verify hooks fire. Pairs with `MockLanguageModelV3` from `ai/test` for model mocking. `setupLocals` lets you pre-seed `locals` (DB clients, service stubs) before `run()` starts. - -The broader `runInMockTaskContext` harness it's built on lives at `@trigger.dev/core/v3/test` — useful for unit-testing any task code, not just chat. diff --git a/.changeset/mollifier-buffer-pipeline-list-entries.md b/.changeset/mollifier-buffer-pipeline-list-entries.md deleted file mode 100644 index 2c55d9b18a8..00000000000 --- a/.changeset/mollifier-buffer-pipeline-list-entries.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -"@trigger.dev/redis-worker": patch ---- - -Pipeline the per-entry `HGETALL` fetches in `MollifierBuffer.listEntriesForEnv`. The previous serial implementation issued one Redis round-trip per runId returned by `LRANGE`, which dominated stale-sweep wall-time at any meaningful backlog (at the sweep's default maxCount=1000, this is ~1000 RTTs per env per pass). Behaviour is unchanged — entries are still skipped when the entry hash has been torn down by a concurrent drainer ack/fail between the LRANGE and the HGETALL. diff --git a/.changeset/mollifier-configurable-constants.md b/.changeset/mollifier-configurable-constants.md deleted file mode 100644 index e9943e9b190..00000000000 --- a/.changeset/mollifier-configurable-constants.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -"@trigger.dev/redis-worker": patch ---- - -Make mollifier buffer and drainer internals configurable. `MollifierBuffer` now accepts `ackGraceTtlSeconds`, `maxRetriesPerRequest`, `reconnectStepMs`, and `reconnectMaxMs` options, and `MollifierDrainer` accepts `maxBackoffMs` and `backoffFloorMs`. All default to their previous hardcoded values, so existing behaviour is unchanged. diff --git a/.changeset/mollifier-drain-batch-size.md b/.changeset/mollifier-drain-batch-size.md deleted file mode 100644 index 9e848b5011d..00000000000 --- a/.changeset/mollifier-drain-batch-size.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -"@trigger.dev/redis-worker": patch ---- - -`MollifierDrainer` accepts a `drainBatchSize` option (default 1) that controls how many entries are popped per env per tick — in-flight handlers remain capped by the global `concurrency`. `MollifierBuffer` also gains `getDrainingCount()` / `listStaleDraining()`, backed by a new `mollifier:draining` ZSET maintained atomically with pop/ack/fail/requeue (observability-only). diff --git a/.changeset/mollifier-redis-worker-primitives.md b/.changeset/mollifier-redis-worker-primitives.md deleted file mode 100644 index a209e530c24..00000000000 --- a/.changeset/mollifier-redis-worker-primitives.md +++ /dev/null @@ -1,9 +0,0 @@ ---- -"@trigger.dev/redis-worker": patch ---- - -Add MollifierBuffer and MollifierDrainer primitives for trigger burst smoothing. - -MollifierBuffer (`accept`, `pop`, `ack`, `requeue`, `fail`, `evaluateTrip`) is a per-env FIFO over Redis with atomic Lua transitions for status tracking. `evaluateTrip` is a sliding-window trip evaluator the webapp gate uses to detect per-env trigger bursts. - -MollifierDrainer pops entries through a polling loop with a user-supplied handler. The loop survives transient Redis errors via capped exponential backoff (up to 5s), and per-env pop failures don't poison the rest of the batch — one env's blip is logged and counted as failed for that tick. Rotation is two-level: orgs at the top, envs within each org. The buffer maintains `mollifier:orgs` and `mollifier:org-envs:${orgId}` atomically with per-env queues, so the drainer walks orgs → envs directly without an in-memory cache. The `maxOrgsPerTick` option (default 500) caps how many orgs are scheduled per tick; for each picked org, one env is popped (rotating round-robin within the org). An org with N envs gets the same per-tick scheduling slot as an org with 1 env, so tenant-level drainage throughput is determined by org count rather than env count. diff --git a/.changeset/mollifier-tag-cap.md b/.changeset/mollifier-tag-cap.md deleted file mode 100644 index b9057664fa7..00000000000 --- a/.changeset/mollifier-tag-cap.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -"@trigger.dev/redis-worker": patch ---- - -Mollifier `mutateSnapshot` now enforces a tag cap: an `append_tags` patch carrying `maxTags` returns `"limit_exceeded"` (writing nothing) when the deduped tag count would exceed the limit, so a buffered run can't accumulate more tags via the tags API than the trigger validator allows at creation. diff --git a/.changeset/otel-suite-0218.md b/.changeset/otel-suite-0218.md deleted file mode 100644 index 38b71ceeec1..00000000000 --- a/.changeset/otel-suite-0218.md +++ /dev/null @@ -1,7 +0,0 @@ ---- -"@trigger.dev/core": patch -"trigger.dev": patch -"@trigger.dev/sdk": patch ---- - -Update the bundled OpenTelemetry packages to their latest releases (`@opentelemetry/sdk-node` 0.218.0, `@opentelemetry/core` 2.7.1, `@opentelemetry/host-metrics` 0.38.3). diff --git a/.changeset/plugin-auth-path.md b/.changeset/plugin-auth-path.md deleted file mode 100644 index 7ce08b71a33..00000000000 --- a/.changeset/plugin-auth-path.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -"@trigger.dev/plugins": patch ---- - -The public interfaces for a plugin system. Initially consolidated authentication and authorization interfaces. diff --git a/.changeset/pre.json b/.changeset/pre.json deleted file mode 100644 index 7c0d538dc0c..00000000000 --- a/.changeset/pre.json +++ /dev/null @@ -1,82 +0,0 @@ -{ - "mode": "pre", - "tag": "rc", - "initialVersions": { - "coordinator": "0.0.1", - "docker-provider": "0.0.1", - "kubernetes-provider": "0.0.1", - "supervisor": "0.0.1", - "webapp": "1.0.0", - "@trigger.dev/build": "4.4.6", - "trigger.dev": "4.4.6", - "@trigger.dev/core": "4.4.6", - "@trigger.dev/plugins": "4.4.6", - "@trigger.dev/python": "4.4.6", - "@trigger.dev/react-hooks": "4.4.6", - "@trigger.dev/redis-worker": "4.4.6", - "@trigger.dev/rsc": "4.4.6", - "@trigger.dev/schema-to-json": "4.4.6", - "@trigger.dev/sdk": "4.4.6" - }, - "changesets": [ - "agent-skills-bundled-in-sdk", - "agent-skills", - "ai-prompts", - "ai-sdk-7-support", - "ai-tool-helpers", - "backpressure-scale-up-freeze", - "bundle-skills-single-pass", - "cap-idempotency-key-length", - "chat-agent-hardening", - "chat-agent-on-boot-hook", - "chat-agent-tools", - "chat-agent", - "chat-boot-cursor", - "chat-headstart-custom-backends", - "chat-headstart-hydrate", - "chat-headstart-reasoning", - "chat-headstart-trigger-config", - "chat-history-read-primitives", - "chat-session-attributes", - "chat-slim-wire-merge", - "chat-start-session-action-typed-client-data", - "chat-system-prompt-caching", - "chat-transport-recreate-missing-session", - "cli-deploy-skip-rewrite-timestamp", - "cli-dev-without-project", - "cli-init-ai-tooling", - "coerce-concurrency-key-to-string", - "create-session-stop-continuation", - "custom-agent-loop-fixes", - "dequeue-latency-histogram", - "duplicate-task-ids", - "env-vars-tracing-forceflush-typecheck", - "envvars-import-is-secret", - "large-trigger-payload-offload", - "locals-key-dual-package-fix", - "mcp-agent-chat-sessions", - "mcp-list-runs-region", - "mcp-trigger-task-no-default-wait", - "mock-chat-agent-test-harness", - "mollifier-buffer-pipeline-list-entries", - "mollifier-configurable-constants", - "mollifier-drain-batch-size", - "mollifier-redis-worker-primitives", - "mollifier-tag-cap", - "otel-suite-0218", - "plugin-auth-path", - "project-environments-endpoint", - "resource-catalog-runtime-registration", - "retry-middleware-errors", - "retry-sigsegv", - "runs-list-region-filter", - "s2-batch-transform-linger-fix", - "sessions-primitive", - "span-api-cached-cost", - "trigger-client", - "trigger-skill-namespace-and-docs", - "trigger-skills-installer", - "unflatten-attributes-conflict", - "warm-start-external-trace-context-leak" - ] -} diff --git a/.changeset/project-environments-endpoint.md b/.changeset/project-environments-endpoint.md deleted file mode 100644 index 9059c548245..00000000000 --- a/.changeset/project-environments-endpoint.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -"@trigger.dev/core": patch ---- - -Add `GetProjectEnvironmentsResponseBody` and `ProjectEnvironment` schemas for the new `GET /api/v1/projects/{projectRef}/environments` endpoint, which lists the parent environments (dev, staging, preview, prod) a personal access token can access for a project. Dev is scoped to the token owner and branch (preview child) environments are excluded. diff --git a/.changeset/redis-worker-oldest-message-age.md b/.changeset/redis-worker-oldest-message-age.md deleted file mode 100644 index f5f86f76e7f..00000000000 --- a/.changeset/redis-worker-oldest-message-age.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -"@trigger.dev/redis-worker": patch ---- - -Add a `redis_worker.queue.oldest_message_age` observable gauge (unit `ms`, labeled `worker_name`) reporting the age of the oldest overdue message in each queue. This is a generic queue-stall signal: it stays at 0 while a queue drains healthily and rises only when due work sits undrained (e.g. a blocked dequeue, a dead consumer, or backpressure), even when no items are being processed. Orphaned queue entries are resolved against the items hash so they don't report a phantom stall. Also exposes `SimpleQueue.oldestMessageAge()`. diff --git a/.changeset/resource-catalog-runtime-registration.md b/.changeset/resource-catalog-runtime-registration.md deleted file mode 100644 index 5046f09e1f1..00000000000 --- a/.changeset/resource-catalog-runtime-registration.md +++ /dev/null @@ -1,6 +0,0 @@ ---- -"@trigger.dev/core": patch -"trigger.dev": patch ---- - -Fix `COULD_NOT_FIND_EXECUTOR` when a task's definition is loaded via `await import(...)` from inside another task's `run()`. The runtime workers now register such tasks with a sentinel file context, and the catalog logs a one-time warning per task id. diff --git a/.changeset/retry-middleware-errors.md b/.changeset/retry-middleware-errors.md deleted file mode 100644 index 2267b4d724c..00000000000 --- a/.changeset/retry-middleware-errors.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -"@trigger.dev/core": patch ---- - -Retry `TASK_MIDDLEWARE_ERROR` under the task's retry policy instead of failing the run on the first attempt. The error was already classified as retryable by `shouldRetryError`, but `shouldLookupRetrySettings` did not include it, so the retry flow fell through to `fail_run`. Fixes #3231. diff --git a/.changeset/retry-sigsegv.md b/.changeset/retry-sigsegv.md deleted file mode 100644 index 5a53c351efe..00000000000 --- a/.changeset/retry-sigsegv.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -"@trigger.dev/core": patch ---- - -Retry `TASK_PROCESS_SIGSEGV` task crashes under the user's retry policy instead of failing the run on the first segfault. SIGSEGV in Node tasks is frequently non-deterministic (native addon races, JIT/GC interaction, near-OOM in native code, host issues), so retrying on a fresh process often succeeds. The retry is gated by the task's existing `retry` config + `maxAttempts` — same path `TASK_PROCESS_SIGTERM` and uncaught exceptions already use — so tasks without a retry policy still fail fast. diff --git a/.changeset/runner-send-debug-logs-gate.md b/.changeset/runner-send-debug-logs-gate.md deleted file mode 100644 index 64586069a88..00000000000 --- a/.changeset/runner-send-debug-logs-gate.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -"trigger.dev": patch ---- - -Runner debug logs are now disabled by default. Set `SEND_RUN_DEBUG_LOGS=true` on the supervisor to re-enable them. diff --git a/.changeset/runs-list-region-filter.md b/.changeset/runs-list-region-filter.md deleted file mode 100644 index c487e2d632c..00000000000 --- a/.changeset/runs-list-region-filter.md +++ /dev/null @@ -1,6 +0,0 @@ ---- -"@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]=`) and read each run's executing region from the new `region` field on the response. diff --git a/.changeset/s2-batch-transform-linger-fix.md b/.changeset/s2-batch-transform-linger-fix.md deleted file mode 100644 index f1e9bab34aa..00000000000 --- a/.changeset/s2-batch-transform-linger-fix.md +++ /dev/null @@ -1,6 +0,0 @@ ---- -"@trigger.dev/core": patch -"trigger.dev": patch ---- - -Bump `@s2-dev/streamstore` to `0.22.10` to fix a `TASK_RUN_UNCAUGHT_EXCEPTION` ("Invalid state: Unable to enqueue") when a `chat.agent` turn is aborted mid-stream. diff --git a/.changeset/sessions-primitive.md b/.changeset/sessions-primitive.md deleted file mode 100644 index 79a6ca48f65..00000000000 --- a/.changeset/sessions-primitive.md +++ /dev/null @@ -1,26 +0,0 @@ ---- -"@trigger.dev/sdk": minor -"@trigger.dev/core": patch ---- - -**Sessions** — a durable, run-aware stream channel keyed on a stable `externalId`. A Session is the unit of state that owns a multi-run conversation: messages flow through `.in`, responses through `.out`, both survive run boundaries. Sessions back the new `chat.agent` runtime, and you can build on them directly for any pattern that needs durable bi-directional streaming across runs. - -```ts -import { sessions, tasks } from "@trigger.dev/sdk"; - -// Trigger a task and subscribe to its session output in one call -const { runId, stream } = await tasks.triggerAndSubscribe("my-task", payload, { - externalId: "user-456", -}); - -for await (const chunk of stream) { - // ... -} - -// Enumerate existing sessions (powers inbox-style UIs without a separate index) -for await (const s of sessions.list({ type: "chat.agent", tag: "user:user-456" })) { - console.log(s.id, s.externalId, s.createdAt, s.closedAt); -} -``` - -See [/docs/ai-chat/overview](https://trigger.dev/docs/ai-chat/overview) for the full surface — Sessions powers the durable, resumable chat runtime described there. diff --git a/.changeset/span-api-cached-cost.md b/.changeset/span-api-cached-cost.md deleted file mode 100644 index d479fdf17d5..00000000000 --- a/.changeset/span-api-cached-cost.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -"@trigger.dev/core": patch ---- - -The run span API response now includes `cachedCost` and `cacheCreationCost` on the `ai` object, alongside the existing `inputCost` / `outputCost` / `totalCost`. `inputCost` reflects only the non-cached input, so these fields let you reconstruct the full cost breakdown for prompt-cached calls. diff --git a/.changeset/tidy-exec-arg-logging.md b/.changeset/tidy-exec-arg-logging.md deleted file mode 100644 index 6f96d23ff13..00000000000 --- a/.changeset/tidy-exec-arg-logging.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -"@trigger.dev/core": patch ---- - -Redact credential-bearing flag values (e.g. `--password`, `--token`) from `Exec` command debug logs diff --git a/.changeset/trigger-client.md b/.changeset/trigger-client.md deleted file mode 100644 index 75699471ba2..00000000000 --- a/.changeset/trigger-client.md +++ /dev/null @@ -1,18 +0,0 @@ ---- -"@trigger.dev/sdk": patch ---- - -Add `TriggerClient` for running multiple SDK clients side-by-side, each with its own auth, preview branch, and baseURL. Useful when a single process needs to trigger tasks or read runs across multiple projects, environments, or preview branches without mutating shared global state. - -```ts -import { TriggerClient } from "@trigger.dev/sdk"; - -const prod = new TriggerClient({ accessToken: process.env.TRIGGER_PROD_KEY }); -const preview = new TriggerClient({ - accessToken: process.env.TRIGGER_PREVIEW_KEY, - previewBranch: "signup-flow", -}); - -await prod.tasks.trigger("send-email", payload); -await preview.runs.list({ status: ["COMPLETED"] }); -``` diff --git a/.changeset/trigger-skill-namespace-and-docs.md b/.changeset/trigger-skill-namespace-and-docs.md deleted file mode 100644 index 96f2da9098e..00000000000 --- a/.changeset/trigger-skill-namespace-and-docs.md +++ /dev/null @@ -1,6 +0,0 @@ ---- -"@trigger.dev/sdk": patch -"trigger.dev": patch ---- - -The agent skills installed by `trigger skills` are now namespaced with a `trigger-` prefix (e.g. `trigger-authoring-tasks`, `trigger-getting-started`) so they don't collide with unrelated skills in your coding agent's skills directory. Adds a `trigger-cost-savings` skill for auditing and reducing compute spend (right-sizing machines, `maxDuration`, batching, debounce), and `@trigger.dev/sdk` now bundles the full Trigger.dev documentation so your agent can read the complete, version-pinned reference directly from node_modules. diff --git a/.changeset/trigger-skills-installer.md b/.changeset/trigger-skills-installer.md deleted file mode 100644 index 4e05b125919..00000000000 --- a/.changeset/trigger-skills-installer.md +++ /dev/null @@ -1,11 +0,0 @@ ---- -"trigger.dev": patch ---- - -`trigger skills` installs Trigger.dev agent skills into your coding agent so it knows how to write tasks, schedules, realtime, and chat.agent code. The skills ship with the CLI and are copied into each tool's native skills directory (Claude Code, Cursor, GitHub Copilot, and Codex / AGENTS.md), and `trigger dev` offers to install them on first run. - -```bash -trigger skills --target claude-code -``` - -Replaces the previous `install-rules` command, which stays as an alias. diff --git a/.changeset/unflatten-attributes-conflict.md b/.changeset/unflatten-attributes-conflict.md deleted file mode 100644 index 9df627f2630..00000000000 --- a/.changeset/unflatten-attributes-conflict.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -"@trigger.dev/core": patch ---- - -Fix `TypeError` in `unflattenAttributes` when the input attribute map contains conflicting dotted key paths (e.g. both `a.b` set to a scalar and `a.b.c` set to a value). The path-walk loop now applies last-write-wins when a prior key wrote a primitive, null, or array at an intermediate slot, matching the existing precedent in `AttributeFlattener.addAttribute`. Callers no longer crash when handed malformed external attribute inputs. diff --git a/.changeset/warm-start-external-trace-context-leak.md b/.changeset/warm-start-external-trace-context-leak.md deleted file mode 100644 index 84f91de7689..00000000000 --- a/.changeset/warm-start-external-trace-context-leak.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -"@trigger.dev/core": patch ---- - -Fix external trace context leaking across runs on warm-started workers with `processKeepAlive` enabled. Every subsequent run's attempt span was being exported with the first run's `traceId` and `parentSpanId`, breaking causal-chain navigation in external APM tools. Runs without an external trace context are unaffected. diff --git a/.server-changes/billing-limits.md b/.server-changes/billing-limits.md deleted file mode 100644 index ba40d03928b..00000000000 --- a/.server-changes/billing-limits.md +++ /dev/null @@ -1,24 +0,0 @@ ---- -area: webapp -type: feature ---- - -Add billing limits. Customers set a spend cap; when usage crosses it, billable -environments pause for a grace period, new triggers are rejected once it ends, -and a recovery flow resumes or cancels the queued backlog. Reconciliation keeps -the webapp converged to billing's state. - -## Manual pause during billing enforcement - -While `pauseSource=BILLING_LIMIT`, manual resume is rejected and manual pause is -a silent no-op (`PauseEnvironmentService` returns success with state `paused`). -We do not stack a manual pause on top of billing enforcement because resolve -converge unpauses all `BILLING_LIMIT`-paused environments for the org. - -API callers that pause during enforcement should expect the environment to -resume when the billing limit is resolved. The queues UI hides pause/resume in -this state; see `manualPauseEnvironmentGuard.server.ts`. - -The admin `runs.enable` endpoint skips billing-paused environments when -re-enabling or disabling org runs (returns them in `skipped`, not `failures` or -the update count). They resume only after the billing limit is resolved. diff --git a/.server-changes/bulk-replay-region-override.md b/.server-changes/bulk-replay-region-override.md deleted file mode 100644 index 000b86c0621..00000000000 --- a/.server-changes/bulk-replay-region-override.md +++ /dev/null @@ -1,6 +0,0 @@ ---- -area: webapp -type: feature ---- - -Add an "Override region" option to the bulk replay action so replayed runs can be routed to a chosen region, defaulting to keeping each run in its original region. diff --git a/.server-changes/cached-task-icon-svg.md b/.server-changes/cached-task-icon-svg.md deleted file mode 100644 index 7a675e01e13..00000000000 --- a/.server-changes/cached-task-icon-svg.md +++ /dev/null @@ -1,6 +0,0 @@ ---- -area: webapp -type: improvement ---- - -Refresh the task and cached-task span icons shown in the run trace view with new SVG artwork. diff --git a/.server-changes/clickhouse-array-of-dynamic-inference.md b/.server-changes/clickhouse-array-of-dynamic-inference.md deleted file mode 100644 index fad5f3ad813..00000000000 --- a/.server-changes/clickhouse-array-of-dynamic-inference.md +++ /dev/null @@ -1,6 +0,0 @@ ---- -area: webapp -type: fix ---- - -Infer mixed-type JSON arrays as Array(Dynamic) instead of nested tuples when writing run, event, metric, and session data to avoid ClickHouse type-complexity merge failures diff --git a/.server-changes/conform-v1.md b/.server-changes/conform-v1.md deleted file mode 100644 index 5e187829460..00000000000 --- a/.server-changes/conform-v1.md +++ /dev/null @@ -1,6 +0,0 @@ ---- -area: webapp -type: improvement ---- - -Upgrade the dashboard form layer from `@conform-to` 0.9 to 1.x. conform 1.x supports both zod 3 and zod 4, which unblocks the upcoming zod 4 upgrade. diff --git a/.server-changes/custom-chart-categorical-x-axis.md b/.server-changes/custom-chart-categorical-x-axis.md deleted file mode 100644 index 76f89c4a28c..00000000000 --- a/.server-changes/custom-chart-categorical-x-axis.md +++ /dev/null @@ -1,6 +0,0 @@ ---- -area: webapp -type: improvement ---- - -Custom (Query mode) and dashboard charts keep non-date x-axis labels like run IDs and task names readable with no configuration: width-aware label thinning, middle-truncation with the full value on hover, and auto-rotation only when labels are long. diff --git a/.server-changes/dashboard-line-chart-x-axis-density.md b/.server-changes/dashboard-line-chart-x-axis-density.md deleted file mode 100644 index 2b249717540..00000000000 --- a/.server-changes/dashboard-line-chart-x-axis-density.md +++ /dev/null @@ -1,6 +0,0 @@ ---- -area: webapp -type: improvement ---- - -Dashboard and custom query line charts now choose how many x-axis time labels to show based on the chart's rendered width, so wide charts show more labels and narrow widgets show fewer. diff --git a/.server-changes/dequeue-region-gate.md b/.server-changes/dequeue-region-gate.md deleted file mode 100644 index d4f9d6979c4..00000000000 --- a/.server-changes/dequeue-region-gate.md +++ /dev/null @@ -1,6 +0,0 @@ ---- -area: webapp -type: feature ---- - -Add a `RUN_ENGINE_DEQUEUE_DISABLED_WORKER_QUEUES` setting that refuses worker dequeue requests for the listed worker queues (or base regions), so their runs stay queued instead of being handed to workers that can't run them. diff --git a/.server-changes/dev-branches.md b/.server-changes/dev-branches.md deleted file mode 100644 index 634d4af1f15..00000000000 --- a/.server-changes/dev-branches.md +++ /dev/null @@ -1,6 +0,0 @@ ---- -area: webapp -type: feature ---- - -Adds support for dev branches similar to the preview branches already supported. diff --git a/.server-changes/fix-invite-accept-many-projects.md b/.server-changes/fix-invite-accept-many-projects.md deleted file mode 100644 index bfef9e15ce6..00000000000 --- a/.server-changes/fix-invite-accept-many-projects.md +++ /dev/null @@ -1,8 +0,0 @@ ---- -area: webapp -type: fix ---- - -Fixed invite acceptance failing for organizations with many projects. - -When environment provisioning failed after membership was created, users with a single pending invite were redirected away before seeing the error. They now land on the orgs page with a persistent error toast; users with other pending invites still see a FormError on the invites page. diff --git a/.server-changes/llm-spend-currency-label.md b/.server-changes/llm-spend-currency-label.md deleted file mode 100644 index 7084821f740..00000000000 --- a/.server-changes/llm-spend-currency-label.md +++ /dev/null @@ -1,6 +0,0 @@ ---- -area: webapp -type: improvement ---- - -Add a currency unit to the agent dashboard "LLM spend" chart label, so it now reads "LLM spend ($)". diff --git a/.server-changes/logs-search-memory-and-pagination.md b/.server-changes/logs-search-memory-and-pagination.md deleted file mode 100644 index 70d32b32bd1..00000000000 --- a/.server-changes/logs-search-memory-and-pagination.md +++ /dev/null @@ -1,6 +0,0 @@ ---- -area: webapp -type: fix ---- - -Keep logs search within bounded ClickHouse memory when browsing long time ranges, and fix pagination that could skip or duplicate entries sharing a timestamp. diff --git a/.server-changes/prisma-infrastructure-error-capture.md b/.server-changes/prisma-infrastructure-error-capture.md deleted file mode 100644 index 400cc9ddb66..00000000000 --- a/.server-changes/prisma-infrastructure-error-capture.md +++ /dev/null @@ -1,6 +0,0 @@ ---- -area: webapp -type: fix ---- - -Log Prisma infrastructure errors (P1xxx) centrally and obfuscate their messages (which carry the DB hostname) on API responses that previously returned the raw message, without changing status codes or headers. diff --git a/.server-changes/rbac-permission-enforcement.md b/.server-changes/rbac-permission-enforcement.md deleted file mode 100644 index 1d72b0d7b3f..00000000000 --- a/.server-changes/rbac-permission-enforcement.md +++ /dev/null @@ -1,6 +0,0 @@ ---- -area: webapp -type: feature ---- - -Enforce role-based permissions across the dashboard and API. New permission boundaries cover: runs (cancel, replay, bulk actions), deployments (rollback, promote, cancel), prompt versions, organization members (invite, resend, revoke), billing and seat purchases, integrations (GitHub and Vercel), and environment variables and API keys (restricted by environment tier). Roles without access can no longer read or change these, gated controls are disabled with a tooltip, and gated pages show a permission-denied panel instead of redirecting away. Behaviour is unchanged in the default configuration, where permissions stay permissive. diff --git a/.server-changes/remove-worker-create-endpoint.md b/.server-changes/remove-worker-create-endpoint.md deleted file mode 100644 index dd7c2041876..00000000000 --- a/.server-changes/remove-worker-create-endpoint.md +++ /dev/null @@ -1,6 +0,0 @@ ---- -area: webapp -type: breaking ---- - -Remove the unused worker group management API endpoints (GET and POST /api/v1/workers). diff --git a/.server-changes/route-taskrun-reads-through-run-store.md b/.server-changes/route-taskrun-reads-through-run-store.md deleted file mode 100644 index dad804e40ba..00000000000 --- a/.server-changes/route-taskrun-reads-through-run-store.md +++ /dev/null @@ -1,6 +0,0 @@ ---- -area: webapp -type: improvement ---- - -Route Postgres task run reads through the run store so they can be retargeted to a different backing store without changing call sites. diff --git a/.server-changes/scheduled-task-out-of-entitlements-warning.md b/.server-changes/scheduled-task-out-of-entitlements-warning.md deleted file mode 100644 index 0ee8e7f9729..00000000000 --- a/.server-changes/scheduled-task-out-of-entitlements-warning.md +++ /dev/null @@ -1,6 +0,0 @@ ---- -area: webapp -type: improvement ---- - -Treat a scheduled task trigger that fails because the organization is out of entitlements as an expected outcome: the schedule engine now logs it as a warning instead of an error, mirroring how environment queue-limit results are already handled. diff --git a/.server-changes/sessions-test-column.md b/.server-changes/sessions-test-column.md deleted file mode 100644 index 4e28034b2bc..00000000000 --- a/.server-changes/sessions-test-column.md +++ /dev/null @@ -1,6 +0,0 @@ ---- -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. The legacy `"playground"` tag is hidden from the Tags display on pre-existing sessions. diff --git a/.server-changes/sso.md b/.server-changes/sso.md deleted file mode 100644 index 67880748cca..00000000000 --- a/.server-changes/sso.md +++ /dev/null @@ -1,6 +0,0 @@ ---- -area: webapp -type: feature ---- - -SAML/OIDC single sign-on: SSO login with optional per-domain enforcement, JIT provisioning, and periodic re-validation against the IdP. diff --git a/.server-changes/supervisor-pod-count-backpressure.md b/.server-changes/supervisor-pod-count-backpressure.md deleted file mode 100644 index f45411b04a5..00000000000 --- a/.server-changes/supervisor-pod-count-backpressure.md +++ /dev/null @@ -1,6 +0,0 @@ ---- -area: supervisor -type: feature ---- - -The supervisor can pause dequeuing when the Kubernetes cluster is saturated, based on the cluster's total pod count. Opt-in and off by default. diff --git a/.server-changes/swap-task-icons.md b/.server-changes/swap-task-icons.md deleted file mode 100644 index 13605675e3d..00000000000 --- a/.server-changes/swap-task-icons.md +++ /dev/null @@ -1,6 +0,0 @@ ---- -area: webapp -type: improvement ---- - -Update the dashboard task icons with a new glyph design. diff --git a/.server-changes/task-landing-activity-charts.md b/.server-changes/task-landing-activity-charts.md deleted file mode 100644 index 8cb7e5f264d..00000000000 --- a/.server-changes/task-landing-activity-charts.md +++ /dev/null @@ -1,6 +0,0 @@ ---- -area: webapp -type: improvement ---- - -Improve the activity charts on the task landing pages (agent, standard, scheduled): bar density now adapts to the selected time range so short ranges no longer collapse to a single bar, x-axis labels are width-aware and non-overlapping, the agent charts share a synced hover line, each chart gets a maximize button, and dragging across a chart zooms the Time/Date filter. diff --git a/.server-changes/task-run-plan-type-clickhouse.md b/.server-changes/task-run-plan-type-clickhouse.md deleted file mode 100644 index c795e4f7b39..00000000000 --- a/.server-changes/task-run-plan-type-clickhouse.md +++ /dev/null @@ -1,6 +0,0 @@ ---- -area: webapp -type: improvement ---- - -Store the run's plan type on the runs analytics table so reporting can group runs by plan. diff --git a/.server-changes/task-type-filter-segmented-control.md b/.server-changes/task-type-filter-segmented-control.md deleted file mode 100644 index a9f5d68c55a..00000000000 --- a/.server-changes/task-type-filter-segmented-control.md +++ /dev/null @@ -1,6 +0,0 @@ ---- -area: webapp -type: improvement ---- - -Replace the Task type filter on the Tasks page with a segmented control: "All" plus icon-only Agent, Standard, and Scheduled segments (each with a tooltip showing its label and number-key shortcut). Filtering is now single-select (one task type at a time) instead of multi-select. Shortcut keys 0–3 select each segment. diff --git a/.server-changes/tasks-page-hydration-errors.md b/.server-changes/tasks-page-hydration-errors.md deleted file mode 100644 index ede865fa9e5..00000000000 --- a/.server-changes/tasks-page-hydration-errors.md +++ /dev/null @@ -1,6 +0,0 @@ ---- -area: webapp -type: fix ---- - -Stop the Tasks page from logging React hydration errors for the per-row running and activity stats. diff --git a/.server-changes/toast-message-length-clamp.md b/.server-changes/toast-message-length-clamp.md deleted file mode 100644 index c7e2ee2ae9c..00000000000 --- a/.server-changes/toast-message-length-clamp.md +++ /dev/null @@ -1,6 +0,0 @@ ---- -area: webapp -type: fix ---- - -Dashboard error toasts with very long messages no longer exceed the session cookie limit and break the request; over-long messages are truncated. diff --git a/.server-changes/trace-view-large-runs-subtree.md b/.server-changes/trace-view-large-runs-subtree.md deleted file mode 100644 index ab7b358aa6d..00000000000 --- a/.server-changes/trace-view-large-runs-subtree.md +++ /dev/null @@ -1,6 +0,0 @@ ---- -area: webapp -type: fix ---- - -Fix empty trace views for child and nested runs in very large traces. The dashboard and retrieve-trace API now return the requested run's span subtree, including ancestor spans outside the anchor run's time window (so a parent's cancellation/error state propagates down correctly). diff --git a/.server-changes/v3-engine-retirement-messaging.md b/.server-changes/v3-engine-retirement-messaging.md deleted file mode 100644 index ca7aceb4722..00000000000 --- a/.server-changes/v3-engine-retirement-messaging.md +++ /dev/null @@ -1,6 +0,0 @@ ---- -area: webapp -type: improvement ---- - -When the v3 engine is retired, triggering a v3 task and connecting the v3 dev CLI now fail with a clear message pointing to the v4 migration guide instead of failing opaquely. Enforcement is off by default, so self-hosted instances still running v3 are unaffected until they migrate. diff --git a/.server-changes/verify-deployment-image-before-finalize.md b/.server-changes/verify-deployment-image-before-finalize.md deleted file mode 100644 index f821da49443..00000000000 --- a/.server-changes/verify-deployment-image-before-finalize.md +++ /dev/null @@ -1,6 +0,0 @@ ---- -area: webapp -type: fix ---- - -Verify a deployment's image exists in the registry before marking it deployed, so a deploy whose image wasn't pushed fails instead of silently breaking runs (can be turned off via `DEPLOY_IMAGE_VERIFICATION_ENABLED=0` for setups that push images out of band) diff --git a/.server-changes/worker-queue-length-always-reported.md b/.server-changes/worker-queue-length-always-reported.md deleted file mode 100644 index ccce193d9f5..00000000000 --- a/.server-changes/worker-queue-length-always-reported.md +++ /dev/null @@ -1,6 +0,0 @@ ---- -area: webapp -type: improvement ---- - -Optionally report worker queue length metrics continuously (enabled per-service via the RUN_ENGINE_WORKER_QUEUE_OBSERVER_ENABLED env var) so a queue's depth keeps being emitted even when nothing is dequeuing from it. diff --git a/docs/ai-chat/actions.mdx b/docs/ai-chat/actions.mdx index e6b894a3059..956e5090aef 100644 --- a/docs/ai-chat/actions.mdx +++ b/docs/ai-chat/actions.mdx @@ -4,10 +4,6 @@ sidebarTitle: "Actions" description: "Custom commands sent from the frontend that mutate chat state without consuming a turn — undo, rollback, edit, regenerate." --- -import RcBanner from "/snippets/ai-chat-rc-banner.mdx"; - - - ## Overview Custom actions let the frontend send structured commands (undo, rollback, edit, regenerate) that modify the conversation state. **Actions are not turns**: they fire `hydrateMessages` (if set) and `onAction` only. No turn lifecycle hooks (`onTurnStart` / `prepareMessages` / `onBeforeTurnComplete` / `onTurnComplete`), no `run()`, no turn-counter increment. The trace span is named `chat action`. diff --git a/docs/ai-chat/anatomy.mdx b/docs/ai-chat/anatomy.mdx index 8990e19d458..3f7cf876cb8 100644 --- a/docs/ai-chat/anatomy.mdx +++ b/docs/ai-chat/anatomy.mdx @@ -4,10 +4,6 @@ sidebarTitle: "Anatomy" description: "The moving parts of a chat agent — the agent task, the session, the frontend transport — and which page covers each." --- -import RcBanner from "/snippets/ai-chat-rc-banner.mdx"; - - - **A chat agent is three parts: a long-lived agent task that runs the turn loop, a durable Session carrying messages in and the response stream out, and a frontend transport that plugs the session into `useChat`.** The pages in this section each own one part of that picture. This page is the map — if you'd rather read mechanics end to end, skip to [How it works](/ai-chat/how-it-works). ```mermaid diff --git a/docs/ai-chat/backend.mdx b/docs/ai-chat/backend.mdx index 084107d1502..c055570ef16 100644 --- a/docs/ai-chat/backend.mdx +++ b/docs/ai-chat/backend.mdx @@ -4,10 +4,6 @@ sidebarTitle: "Backend" description: "Three approaches to building your chat backend — chat.agent(), session iterator, or raw task primitives." --- -import RcBanner from "/snippets/ai-chat-rc-banner.mdx"; - - - There are three abstraction levels for a chat backend. All three speak the same wire protocol, so the [frontend transport](/ai-chat/frontend) works unchanged whichever you pick. | Capability | `chat.agent()` | `chat.createSession()` | Raw primitives | diff --git a/docs/ai-chat/background-injection.mdx b/docs/ai-chat/background-injection.mdx index 567da627f16..f84336ff4de 100644 --- a/docs/ai-chat/background-injection.mdx +++ b/docs/ai-chat/background-injection.mdx @@ -4,10 +4,6 @@ sidebarTitle: "Background injection" description: "Inject context from background work into the agent's conversation — self-review, RAG augmentation, or any async analysis." --- -import RcBanner from "/snippets/ai-chat-rc-banner.mdx"; - - - ## Overview `chat.inject()` queues model messages for injection into the conversation. Messages are picked up at the start of the next turn or at the next `prepareStep` boundary (between tool-call steps). diff --git a/docs/ai-chat/changelog.mdx b/docs/ai-chat/changelog.mdx index c4c90061b8c..4bd667a4148 100644 --- a/docs/ai-chat/changelog.mdx +++ b/docs/ai-chat/changelog.mdx @@ -1,9 +1,42 @@ --- title: "Changelog" sidebarTitle: "Changelog" -description: "Pre-release updates for AI chat agents." +description: "Changelog for the AI Agents SDK." --- + + +## Point chat sessions at a different project or environment + +[`chat.headStart`](/ai-chat/fast-starts#head-start) and [`chat.createStartSessionAction`](/ai-chat/sessions) now take an `apiClient` option (`baseURL` + `accessToken`). The server that creates the session and triggers the run can target a different project or environment than its ambient Trigger config, without setting a global `TRIGGER_SECRET_KEY`. Useful when your `chat.agent` lives in a separate project from the app serving the route, or when one server starts chats across more than one environment. Your LLM provider keys stay in the `run` callback and are unaffected. ([#4018](https://github.com/triggerdotdev/trigger.dev/pull/4018)) + +```ts +export const POST = chat.headStart({ + agentId: "my-agent", + apiClient: { baseURL, accessToken }, + run: async ({ chat }) => + streamText({ ...chat.toStreamTextOptions({ tools }), model: anthropic("claude-sonnet-4-6") }), +}); +``` + +```ts +const startSession = chat.createStartSessionAction("my-chat", { + apiClient: { baseURL, accessToken }, +}); + +await startSession({ chatId, clientData }); +``` + +## Fix: chat agents on preview branches + +Messaging a `chat.agent` (or `AgentChat`) deployed to a preview branch failed with `x-trigger-branch header required for preview env`. The realtime message-append and stream-subscribe calls now send the `x-trigger-branch` header, resolved the same way `sessions.start` resolves it, so preview-branch chat agents work. ([#4018](https://github.com/triggerdotdev/trigger.dev/pull/4018)) + +## Fix: Head Start handovers with a prepareMessages hook + +A [Head Start](/ai-chat/fast-starts#head-start) handover passes the first turn's pending tool call to the agent as a tool-approval round whose trailing tool message must reach the model untouched. A `prepareMessages` hook that rewrites the last message (for example the recommended [prompt-caching](/ai-chat/prompt-caching) breakpoint) could disturb that tail, so the turn failed with `tool_use ids were found without tool_result`. The agent now preserves the approval tail across `prepareMessages`, so prompt caching and Head Start compose cleanly. ([#4018](https://github.com/triggerdotdev/trigger.dev/pull/4018)) + + + ## chat.headStart now works for custom agents and sessions diff --git a/docs/ai-chat/chat-local.mdx b/docs/ai-chat/chat-local.mdx index 7317877b6cd..f84de816f2d 100644 --- a/docs/ai-chat/chat-local.mdx +++ b/docs/ai-chat/chat-local.mdx @@ -4,10 +4,6 @@ sidebarTitle: "chat.local" description: "Typed, run-scoped data accessible from hooks, run(), tools, and subtasks. Survives across turns, auto-cleared between runs, auto-hydrated into subtasks." --- -import RcBanner from "/snippets/ai-chat-rc-banner.mdx"; - - - Use `chat.local` to create typed, run-scoped data that persists across turns and is accessible from anywhere — the run function, tools, nested helpers. Each run gets its own isolated copy, and locals are automatically cleared between runs. Lifecycle hooks and **`run`** also receive **`ctx`** ([`TaskRunContext`](/ai-chat/reference#task-context-ctx)) — the same object as on a standard `task()` — for tags, metadata, and cleanup that needs the full run record. diff --git a/docs/ai-chat/client-protocol.mdx b/docs/ai-chat/client-protocol.mdx index e6796233945..f1c33ad6b26 100644 --- a/docs/ai-chat/client-protocol.mdx +++ b/docs/ai-chat/client-protocol.mdx @@ -4,10 +4,6 @@ sidebarTitle: "Client Protocol" description: "The wire protocol for building custom chat transports — how clients communicate with chat agents over Sessions and SSE." --- -import RcBanner from "/snippets/ai-chat-rc-banner.mdx"; - - - This page documents the protocol that chat clients use to communicate with `chat.agent()` tasks. Use this if you're building a custom transport (e.g., for a Slack bot, CLI tool, or native app) instead of using the built-in `TriggerChatTransport` or `AgentChat`. diff --git a/docs/ai-chat/compaction.mdx b/docs/ai-chat/compaction.mdx index 3ab280a44f0..02db7d7b13c 100644 --- a/docs/ai-chat/compaction.mdx +++ b/docs/ai-chat/compaction.mdx @@ -4,10 +4,6 @@ sidebarTitle: "Compaction" description: "Automatic context compaction to keep long conversations within token limits." --- -import RcBanner from "/snippets/ai-chat-rc-banner.mdx"; - - - ## Overview Long conversations accumulate tokens across turns. Eventually the context window fills up, causing errors or degraded responses. Compaction solves this by automatically summarizing the conversation when token usage exceeds a threshold, then using that summary as the context for future turns. diff --git a/docs/ai-chat/custom-agents.mdx b/docs/ai-chat/custom-agents.mdx index 6e2f374bcd9..1cac571ad42 100644 --- a/docs/ai-chat/custom-agents.mdx +++ b/docs/ai-chat/custom-agents.mdx @@ -4,10 +4,6 @@ sidebarTitle: "Custom agents" description: "Build chat agents without chat.agent()'s managed lifecycle: register with chat.customAgent(), then drive turns with the createSession iterator or a hand-rolled loop." --- -import RcBanner from "/snippets/ai-chat-rc-banner.mdx"; - - - **A custom agent is a task you register with `chat.customAgent()` and drive yourself — either with the managed turn iterator from `chat.createSession()`, or with a fully hand-rolled loop over the raw chat primitives.** You give up `chat.agent()`'s lifecycle hooks and automatic continuation recovery; you gain inline control over every turn, and (at the lowest level) full control over the stream conversion. See the [comparison table](/ai-chat/backend) before dropping down. The frontend is unchanged either way: all levels speak the same wire protocol, so [`useTriggerChatTransport`](/ai-chat/frontend) points at a custom agent exactly like a `chat.agent()`. diff --git a/docs/ai-chat/error-handling.mdx b/docs/ai-chat/error-handling.mdx index e93f3c81852..ed7abb0fc06 100644 --- a/docs/ai-chat/error-handling.mdx +++ b/docs/ai-chat/error-handling.mdx @@ -4,10 +4,6 @@ sidebarTitle: "Error handling" description: "How errors flow through chat.agent — stream errors, hook errors, run failures — and how to recover." --- -import RcBanner from "/snippets/ai-chat-rc-banner.mdx"; - - - `chat.agent` errors fall into four layers, each with different recovery semantics. The default behavior is **conversation-preserving**: a thrown error in a hook or `run()` does not kill the chat. The current turn ends with an error chunk, and the agent waits for the user's next message. ## Error layers at a glance diff --git a/docs/ai-chat/fast-starts.mdx b/docs/ai-chat/fast-starts.mdx index dc290ff06aa..40d7b62fd20 100644 --- a/docs/ai-chat/fast-starts.mdx +++ b/docs/ai-chat/fast-starts.mdx @@ -4,10 +4,6 @@ sidebarTitle: "Fast starts" description: "Two ways to cut first-turn TTFC: Preload eagerly triggers the run before the first message; Head Start runs step 1 in your warm server while the agent boots in parallel." --- -import RcBanner from "/snippets/ai-chat-rc-banner.mdx"; - - - The first turn of a brand-new conversation pays for the chat.agent run's cold start: dequeue, process boot, `onPreload` / `onChatStart` hooks, and only then the LLM call. Two features address this from different angles. ## Picking an approach diff --git a/docs/ai-chat/frontend.mdx b/docs/ai-chat/frontend.mdx index 3dac5ca5ade..a1cc93a09b6 100644 --- a/docs/ai-chat/frontend.mdx +++ b/docs/ai-chat/frontend.mdx @@ -4,10 +4,6 @@ sidebarTitle: "Frontend" description: "Transport setup, session management, client data, and frontend patterns for AI Chat." --- -import RcBanner from "/snippets/ai-chat-rc-banner.mdx"; - - - ## How the transport works Vanilla `useChat` expects an `api` URL — it POSTs the conversation to your own Next.js route handler, which terminates the stream. `useTriggerChatTransport` replaces that round-trip: instead of an `api` URL, you pass a custom [`ChatTransport`](https://ai-sdk.dev/docs/ai-sdk-ui/transport) that talks directly to the Trigger.dev cloud (or your self-hosted webapp) on behalf of `useChat`. diff --git a/docs/ai-chat/how-it-works.mdx b/docs/ai-chat/how-it-works.mdx index 9980d935ffc..2003bce8fd9 100644 --- a/docs/ai-chat/how-it-works.mdx +++ b/docs/ai-chat/how-it-works.mdx @@ -4,10 +4,6 @@ sidebarTitle: "How it works" description: "End-to-end mechanics of a chat.agent turn: the two durable channels per session, the long-lived task that reads and writes them, and how a chat survives refreshes, deploys, and idle gaps." --- -import RcBanner from "/snippets/ai-chat-rc-banner.mdx"; - - - This page explains how `chat.agent` is put together, what each piece does on a single turn, and how a chat survives across turns. It is not an API tour — for that, see [Backend](/ai-chat/backend), [Frontend](/ai-chat/frontend), and the [Reference](/ai-chat/reference). For the byte-level wire format, see [Client Protocol](/ai-chat/client-protocol). diff --git a/docs/ai-chat/lifecycle-hooks.mdx b/docs/ai-chat/lifecycle-hooks.mdx index 99491486769..1bb22ff3de7 100644 --- a/docs/ai-chat/lifecycle-hooks.mdx +++ b/docs/ai-chat/lifecycle-hooks.mdx @@ -4,10 +4,6 @@ sidebarTitle: "Lifecycle hooks" description: "Hook into every stage of a chat agent's run: preload, turn start, turn complete, suspend, resume, and more." --- -import RcBanner from "/snippets/ai-chat-rc-banner.mdx"; - - - `chat.agent({ ... })` accepts a set of lifecycle hooks for persisting state, validating input, transforming messages, and reacting to suspension and resumption. They fire at well-defined points in the chat agent's lifetime. **Once per worker process (every fresh run boot):** `onBoot` → `onPreload` (preloaded runs only). diff --git a/docs/ai-chat/mcp.mdx b/docs/ai-chat/mcp.mdx index 0c9f0019a07..78ddf489ec8 100644 --- a/docs/ai-chat/mcp.mdx +++ b/docs/ai-chat/mcp.mdx @@ -4,10 +4,6 @@ sidebarTitle: "MCP Server" description: "Chat with your agents from any AI coding tool using the Trigger.dev MCP server." --- -import RcBanner from "/snippets/ai-chat-rc-banner.mdx"; - - - The Trigger.dev MCP server includes tools for having conversations with your chat agents directly from AI coding tools like Claude Code, Cursor, Windsurf, and others. This lets your AI assistant interact with your agents without writing any code. ## Available tools diff --git a/docs/ai-chat/overview.mdx b/docs/ai-chat/overview.mdx index 8e038ba7913..3c37661eb4c 100644 --- a/docs/ai-chat/overview.mdx +++ b/docs/ai-chat/overview.mdx @@ -4,10 +4,6 @@ sidebarTitle: "Overview" description: "Durable multi-turn AI chats — one Trigger.dev task per conversation, surviving refreshes, deploys, and crashes." --- -import RcBanner from "/snippets/ai-chat-rc-banner.mdx"; - - - An AI chat isn't a request — it's a session. `chat.agent` runs every conversation as a single long-lived Trigger.dev task: you write the loop, it wakes up when a message arrives, freezes when none do, and the same in-memory state and on-disk workspace survive across page refreshes, deploys, idle gaps, and crashes. The substrate handles the parts most teams stitch together by hand — turn lifecycle, mid-stream resume, recovery from cancel/crash/OOM, HITL approvals, deploy upgrades — so your code is the loop you'd write anyway: messages in, `streamText` out. ## A minimal example diff --git a/docs/ai-chat/patterns/branching-conversations.mdx b/docs/ai-chat/patterns/branching-conversations.mdx index 8a313921f41..ab7a0491b25 100644 --- a/docs/ai-chat/patterns/branching-conversations.mdx +++ b/docs/ai-chat/patterns/branching-conversations.mdx @@ -4,10 +4,6 @@ sidebarTitle: "Branching conversations" description: "Build ChatGPT-style conversation trees with edit, regenerate, undo, and branch switching using hydrateMessages, chat.history, and actions." --- -import RcBanner from "/snippets/ai-chat-rc-banner.mdx"; - - - Most chat UIs treat conversations as linear sequences. But real conversations branch — users edit previous messages, regenerate responses, undo exchanges, and explore alternative paths. This pattern shows how to build a branching conversation system using `hydrateMessages`, `chat.history`, and custom actions. ## Data model diff --git a/docs/ai-chat/patterns/code-sandbox.mdx b/docs/ai-chat/patterns/code-sandbox.mdx index d4f17e271f9..1859d093524 100644 --- a/docs/ai-chat/patterns/code-sandbox.mdx +++ b/docs/ai-chat/patterns/code-sandbox.mdx @@ -4,10 +4,6 @@ sidebarTitle: "Code sandbox" description: "Warm an isolated sandbox on each chat turn, run an AI SDK executeCode tool, and tear down right before the run suspends — using chat.agent hooks and chat.local." --- -import RcBanner from "/snippets/ai-chat-rc-banner.mdx"; - - - Use a **hosted code sandbox** (for example [E2B](https://e2b.dev)) when the model should run short scripts to analyze tool output (PostHog queries, CSV-like data, math) without executing arbitrary code on the Trigger worker host. This page describes a **durable chat** pattern that fits `chat.agent()`: diff --git a/docs/ai-chat/patterns/database-persistence.mdx b/docs/ai-chat/patterns/database-persistence.mdx index b9d56ab6c7d..4e49ae81ebf 100644 --- a/docs/ai-chat/patterns/database-persistence.mdx +++ b/docs/ai-chat/patterns/database-persistence.mdx @@ -4,10 +4,6 @@ sidebarTitle: "Database persistence" description: "Split conversation state and live session metadata across hooks — preload, turn start, turn complete — without tying the pattern to a specific ORM or schema." --- -import RcBanner from "/snippets/ai-chat-rc-banner.mdx"; - - - Durable chat runs can span **hours** and **many turns**. You usually want: 1. **Conversation state** — full **`UIMessage[]`** (or equivalent) keyed by **`chatId`**, so reloads and history views work. diff --git a/docs/ai-chat/patterns/human-in-the-loop.mdx b/docs/ai-chat/patterns/human-in-the-loop.mdx index 843523f59d9..775ead43252 100644 --- a/docs/ai-chat/patterns/human-in-the-loop.mdx +++ b/docs/ai-chat/patterns/human-in-the-loop.mdx @@ -4,10 +4,6 @@ sidebarTitle: "Human-in-the-loop" description: "Pause the agent mid-response to ask the user a clarifying question, then resume with their answer." --- -import RcBanner from "/snippets/ai-chat-rc-banner.mdx"; - - - Some turns need to stop and ask the user something before they can finish — picking between options, confirming a destructive action, or clarifying an ambiguous request. The AI SDK calls this **human-in-the-loop** (HITL), and the building block is a tool with no `execute` function. When the LLM calls a tool that has no `execute`, `streamText` ends with the tool call still pending. The turn completes cleanly, the frontend renders UI to collect the answer, and when the user responds, a new turn resumes with the answer merged into the same assistant message. diff --git a/docs/ai-chat/patterns/large-payloads.mdx b/docs/ai-chat/patterns/large-payloads.mdx index 859c51c0d62..b3e20574fa2 100644 --- a/docs/ai-chat/patterns/large-payloads.mdx +++ b/docs/ai-chat/patterns/large-payloads.mdx @@ -4,10 +4,6 @@ sidebarTitle: "Large payloads" description: "Why a single chunk on the chat stream is capped at ~1 MiB, what error you'll see, and how to work around it with ID references." --- -import RcBanner from "/snippets/ai-chat-rc-banner.mdx"; - - - The realtime stream that backs `chat.agent` enforces a **per-record cap of ~1 MiB** (`1048576` bytes minus a small envelope reserve). Anything written through the chat output — auto-piped LLM chunks, `chat.response.write`, custom `writer.write` parts — counts as one record per chunk and is rejected if it crosses the cap. This is a platform-level limit and cannot be raised per project or per stream. diff --git a/docs/ai-chat/patterns/oom-resilience.mdx b/docs/ai-chat/patterns/oom-resilience.mdx index f7dc86de0be..1adf7339ca9 100644 --- a/docs/ai-chat/patterns/oom-resilience.mdx +++ b/docs/ai-chat/patterns/oom-resilience.mdx @@ -4,10 +4,6 @@ sidebarTitle: "OOM resilience" description: "Recover from out-of-memory errors mid-turn by automatically retrying the failed turn on a larger machine — without losing the in-flight user message or re-processing completed turns." --- -import RcBanner from "/snippets/ai-chat-rc-banner.mdx"; - - - When a `chat.agent` turn runs out of memory, the worker process dies and everything in it is gone: the in-flight LLM call, the accumulator, any tool execution mid-flight. By default, Trigger.dev surfaces the OOM as a run failure. Setting `oomMachine` opts the agent into automatic recovery: the failed turn re-runs on a larger machine, picks up the user message that triggered the OOM (without re-processing earlier completed turns), and produces a normal response. diff --git a/docs/ai-chat/patterns/persistence-and-replay.mdx b/docs/ai-chat/patterns/persistence-and-replay.mdx index 2af9cadfc93..de84088ff83 100644 --- a/docs/ai-chat/patterns/persistence-and-replay.mdx +++ b/docs/ai-chat/patterns/persistence-and-replay.mdx @@ -4,10 +4,6 @@ sidebarTitle: "Persistence and replay" description: "How chat.agent rebuilds conversation history at run boot — durable JSON snapshot in object storage plus session.out replay, with a hydrateMessages short-circuit for backend-owned history." --- -import RcBanner from "/snippets/ai-chat-rc-banner.mdx"; - - - `chat.agent` runs are processes — they boot, stream a turn, and either suspend (waiting for the next message) or exit. When the next message arrives at a session whose previous run already exited, a **fresh** run boots with no in-memory state. Something has to rebuild the conversation history before that turn can produce a coherent response. This page walks through the **snapshot + replay** model the runtime uses by default, and the [`hydrateMessages`](/ai-chat/lifecycle-hooks#hydratemessages) short-circuit that turns the whole thing off when the customer owns history. diff --git a/docs/ai-chat/patterns/recovery-boot.mdx b/docs/ai-chat/patterns/recovery-boot.mdx index a2b11efeb14..0e3509560f5 100644 --- a/docs/ai-chat/patterns/recovery-boot.mdx +++ b/docs/ai-chat/patterns/recovery-boot.mdx @@ -4,10 +4,6 @@ sidebarTitle: "Recovery boot" description: "Recover from cancel-mid-stream, crashes, and OOM kills with full conversational context. The smart default Just Works; the onRecoveryBoot hook is the override path for advanced policies." --- -import RcBanner from "/snippets/ai-chat-rc-banner.mdx"; - - - When a `chat.agent` run dies in the middle of streaming a response — the user cancels, the worker OOMs, or an unhandled exception kills the process — the durable streams hold what was in flight. The next run boots as a continuation, reads both stream tails, and reconstructs a chain that preserves the partial response so any follow-up (`keep going`, `actually do X instead`, a new question) has full context. The behavior is automatic. The `onRecoveryBoot` hook is opt-in for policies that need something different. diff --git a/docs/ai-chat/patterns/skills.mdx b/docs/ai-chat/patterns/skills.mdx index 4589a0daf7e..68930b89f1d 100644 --- a/docs/ai-chat/patterns/skills.mdx +++ b/docs/ai-chat/patterns/skills.mdx @@ -4,10 +4,6 @@ sidebarTitle: "Agent Skills" description: "Ship reusable capabilities (folders with SKILL.md + scripts) that a chat agent discovers and invokes on demand." --- -import RcBanner from "/snippets/ai-chat-rc-banner.mdx"; - - - Agent skills are reusable capabilities you ship as folders — a `SKILL.md` describing when and how to use them, plus optional scripts, references, and assets. The chat agent sees a short description of each skill in its system prompt, loads the full instructions on demand via a `loadSkill` tool, and invokes the bundled scripts via `bash` — all without you wiring anything up manually. Built on the [AI SDK cookbook pattern](https://ai-sdk.dev/cookbook/guides/agent-skills). Works with any provider (OpenAI, Anthropic, Gemini, etc.) — not tied to Anthropic's server-side skills. diff --git a/docs/ai-chat/patterns/sub-agents.mdx b/docs/ai-chat/patterns/sub-agents.mdx index 84a52b9479b..5ecfe41c4c0 100644 --- a/docs/ai-chat/patterns/sub-agents.mdx +++ b/docs/ai-chat/patterns/sub-agents.mdx @@ -4,10 +4,6 @@ sidebarTitle: "Sub-Agents" description: "Delegate work to durable sub-agents from within a parent agent's tool calls, with streaming preliminary results." --- -import RcBanner from "/snippets/ai-chat-rc-banner.mdx"; - - - Sub-agents let a parent agent delegate work to other agents running as durable Trigger.dev tasks. The sub-agent's response streams back through the parent as preliminary tool results, so the frontend sees the sub-agent working inside the parent's tool call card. This builds on the AI SDK's [async generator tool pattern](https://ai-sdk.dev/docs/agents/subagents) and Trigger.dev's [AgentChat](/ai-chat/server-chat) for server-side agent interaction. diff --git a/docs/ai-chat/patterns/tool-result-auditing.mdx b/docs/ai-chat/patterns/tool-result-auditing.mdx index cab6b421a70..f4f1795418c 100644 --- a/docs/ai-chat/patterns/tool-result-auditing.mdx +++ b/docs/ai-chat/patterns/tool-result-auditing.mdx @@ -4,10 +4,6 @@ sidebarTitle: "Tool result auditing" description: "Fire side effects exactly once per resolved tool call — audit logs, billing, notifications — using extractNewToolResults inside hydrateMessages or onTurnComplete." --- -import RcBanner from "/snippets/ai-chat-rc-banner.mdx"; - - - When a chat agent uses [tools](/ai-chat/tools) (especially [human-in-the-loop](/ai-chat/patterns/human-in-the-loop) tools that wait on `addToolOutput` from the frontend), you often need to fire side effects exactly once per resolved tool call: - **Audit logs** — record every tool result for compliance. diff --git a/docs/ai-chat/patterns/trusted-edge-signals.mdx b/docs/ai-chat/patterns/trusted-edge-signals.mdx index 181a3895474..f065733996a 100644 --- a/docs/ai-chat/patterns/trusted-edge-signals.mdx +++ b/docs/ai-chat/patterns/trusted-edge-signals.mdx @@ -4,10 +4,6 @@ sidebarTitle: "Trusted edge signals" description: "How to safely deliver server-trusted signals (bot scores, JA4, ASN, ReCAPTCHA verdicts) to a chat.agent run via an edge proxy." --- -import RcBanner from "/snippets/ai-chat-rc-banner.mdx"; - - - A common need for chat-style endpoints is to drive agent behavior from **server-trusted signals** that the browser cannot be allowed to declare itself — bot management scores, JA4 fingerprints, ASN, ReCAPTCHA verdicts, or any other anti-abuse data only the edge can see. The agent's [`clientData`](/ai-chat/reference#withclientdata) channel is the right delivery mechanism, but `clientData` set in the browser is by definition spoofable. The fix is to move the value population out of the browser and into a trusted edge proxy. This page documents the pattern using Cloudflare Workers as the proxy. The same shape applies to any edge layer (custom reverse proxy, Vercel Edge Middleware, AWS Lambda@Edge) — the trust comes from the deployment topology, not from Trigger.dev validating the source. diff --git a/docs/ai-chat/patterns/version-upgrades.mdx b/docs/ai-chat/patterns/version-upgrades.mdx index 75a29f4febb..830f673e9a3 100644 --- a/docs/ai-chat/patterns/version-upgrades.mdx +++ b/docs/ai-chat/patterns/version-upgrades.mdx @@ -4,10 +4,6 @@ sidebarTitle: "Version upgrades" description: "Gracefully migrate suspended chat agents to a new deployment using chat.requestUpgrade() and the continuation mechanism." --- -import RcBanner from "/snippets/ai-chat-rc-banner.mdx"; - - - Chat agent runs are pinned to the worker version they started on. When you deploy a new version, suspended runs resume on the **old** code. If your deploy includes breaking changes (new tools, changed schemas, updated API contracts), this can cause issues. `chat.requestUpgrade()` lets the agent opt out of the current run so the transport triggers a new one on the latest version. diff --git a/docs/ai-chat/pending-messages.mdx b/docs/ai-chat/pending-messages.mdx index 20ab098b9a2..3e7c00b26fe 100644 --- a/docs/ai-chat/pending-messages.mdx +++ b/docs/ai-chat/pending-messages.mdx @@ -4,10 +4,6 @@ sidebarTitle: "Pending Messages" description: "Inject user messages mid-execution to steer agents between tool-call steps." --- -import RcBanner from "/snippets/ai-chat-rc-banner.mdx"; - - - ## Overview When an AI agent is executing tool calls, users may want to send a message that **steers the agent mid-execution** — adding context, correcting course, or refining the request without waiting for the response to finish. diff --git a/docs/ai-chat/prompt-caching.mdx b/docs/ai-chat/prompt-caching.mdx index a0ee5f27884..49d43dc0397 100644 --- a/docs/ai-chat/prompt-caching.mdx +++ b/docs/ai-chat/prompt-caching.mdx @@ -4,10 +4,6 @@ sidebarTitle: "Prompt caching" description: "Cache the stable prefix of your agent's prompt with Anthropic prompt caching to cut token cost and latency on every turn." --- -import RcBanner from "/snippets/ai-chat-rc-banner.mdx"; - - - **Prompt caching lets a provider reuse the unchanged prefix of your prompt across requests, billing it at a fraction of the input price and skipping re-processing.** With Anthropic, cache reads cost ~10% of base input tokens, so a long, stable system prompt or a growing conversation history pays full price once and reads cheaply on every turn after. Caching is a **byte-exact prefix match**: any change in the prefix invalidates everything after it. A multi-turn agent is the ideal case — the system prompt, tools, and earlier turns are identical turn over turn, so the cacheable prefix only grows. `chat.agent` is built to keep that prefix stable across turns, suspends, and resumes; this page shows how to place the cache breakpoints and verify they're hitting. diff --git a/docs/ai-chat/quick-start.mdx b/docs/ai-chat/quick-start.mdx index 66f1a6a31ac..52526b6b602 100644 --- a/docs/ai-chat/quick-start.mdx +++ b/docs/ai-chat/quick-start.mdx @@ -4,10 +4,6 @@ sidebarTitle: "Quick Start" description: "Get a working AI agent in 3 steps — define an agent, generate a token, and wire up the frontend." --- -import RcBanner from "/snippets/ai-chat-rc-banner.mdx"; - - - These steps assume you already have a Trigger.dev project with the SDK installed and the CLI authenticated — if you don't, follow [Manual setup](/manual-setup) (or `npx trigger.dev@latest init` in an existing project) first. You should be able to run `pnpm exec trigger dev` from your project root before continuing. The chat surface works with Vercel AI SDK **v5, v6, or v7**; install whichever major you want. On **v7**, also install `@ai-sdk/otel` so your model calls are traced (the SDK registers it for you). See [compatibility](/ai-chat/reference#compatibility) for the full matrix. diff --git a/docs/ai-chat/reference.mdx b/docs/ai-chat/reference.mdx index e7fa3e28048..192684d4163 100644 --- a/docs/ai-chat/reference.mdx +++ b/docs/ai-chat/reference.mdx @@ -4,15 +4,11 @@ sidebarTitle: "API Reference" description: "Complete API reference for the AI Agents SDK — backend options, events, frontend transport, and hooks." --- -import RcBanner from "/snippets/ai-chat-rc-banner.mdx"; - - - ## Compatibility | Dependency | Supported | Notes | |---|---|---| -| `@trigger.dev/sdk` | `>=4.5.0-rc.0` | The chat agent surface lives in this SDK release. Install with `@trigger.dev/sdk@rc`. | +| `@trigger.dev/sdk` | `>=4.5.0` | The chat agent surface lives in this SDK release. Install with `@trigger.dev/sdk@latest`. | | `ai` (Vercel AI SDK) | `^5.0.0 \|\| ^6.0.0 \|\| >=7.0.0-canary <8` | Declared as a peer. v6 is what we develop against day to day; v5 and v7 work too (v7 is in canary/beta upstream). Your installed `ai` major drives the chat surface's types. | | `@ai-sdk/otel` | `1.x` (v7 only) | Optional. AI SDK 7 moved model-call span emission out of `ai` core into this adapter. Install it alongside `ai@7` and the SDK auto-registers it, so your model calls show up as spans in the run trace. Not needed on v5/v6, where `ai` core emits spans. See [AI SDK 7 telemetry](#ai-sdk-7-telemetry) below. | | `@ai-sdk/react` | matches your `ai` major | Pulled in by `useChat`. The transport works with whichever React hook ships in the same major as your `ai` version. | diff --git a/docs/ai-chat/server-chat.mdx b/docs/ai-chat/server-chat.mdx index c2c5928a640..9af3ee28a45 100644 --- a/docs/ai-chat/server-chat.mdx +++ b/docs/ai-chat/server-chat.mdx @@ -4,10 +4,6 @@ sidebarTitle: "Server-Side Chat" description: "Use AgentChat to interact with chat agents from server-side code — tasks, webhooks, scripts, or other agents." --- -import RcBanner from "/snippets/ai-chat-rc-banner.mdx"; - - - `AgentChat` lets you chat with agents from server-side code. It works inside tasks (agent-to-agent), request handlers, webhook processors, and scripts. ```ts diff --git a/docs/ai-chat/sessions.mdx b/docs/ai-chat/sessions.mdx index 65786f42e34..041fd3d99ee 100644 --- a/docs/ai-chat/sessions.mdx +++ b/docs/ai-chat/sessions.mdx @@ -4,10 +4,6 @@ sidebarTitle: "Sessions" description: "A Session is a stateful execution of an agent, with two-way streaming and durable compute. A single Session can have multiple runs associated with it." --- -import RcBanner from "/snippets/ai-chat-rc-banner.mdx"; - - - **A Session is a stateful execution of an agent.** It includes two-way streaming and durable compute, and a single Session can have multiple runs associated with it. The **two-way streaming** is a pair of durable streams. The input stream (`.in`) carries incoming user messages to your task. The output stream (`.out`) carries everything the agent produces back to your clients: AI generation parts (text, reasoning, tool calls) and any custom data parts you write. diff --git a/docs/ai-chat/testing.mdx b/docs/ai-chat/testing.mdx index 2a655b261ab..65e094ed530 100644 --- a/docs/ai-chat/testing.mdx +++ b/docs/ai-chat/testing.mdx @@ -4,10 +4,6 @@ sidebarTitle: "Testing" description: "Drive a chat.agent through real turns in unit tests — no network, no task runtime, no mocking the SDK." --- -import RcBanner from "/snippets/ai-chat-rc-banner.mdx"; - - - ## Overview `@trigger.dev/sdk/ai/test` exports `mockChatAgent`, an offline harness that runs your `chat.agent` definition's `run()` function inside an in-memory task runtime. You send messages, actions, and stop signals through driver methods and assert against the chunks the agent emits. diff --git a/docs/ai-chat/tools.mdx b/docs/ai-chat/tools.mdx index 92c3bbc06a7..7fa91d4beb8 100644 --- a/docs/ai-chat/tools.mdx +++ b/docs/ai-chat/tools.mdx @@ -4,10 +4,6 @@ sidebarTitle: "Tools" description: "Declare tools on chat.agent so toModelOutput survives across turns, get them back typed in run(), and type your messages from them." --- -import RcBanner from "/snippets/ai-chat-rc-banner.mdx"; - - - `chat.agent` doesn't call the model for you. Your tools still go to [`streamText`](https://sdk.vercel.ai/docs/ai-sdk-core/tools-and-tool-calling) inside `run()`. But you should **also declare them on the agent config**: ```ts diff --git a/docs/ai-chat/types.mdx b/docs/ai-chat/types.mdx index c966ce6bffd..4cbf12d16c0 100644 --- a/docs/ai-chat/types.mdx +++ b/docs/ai-chat/types.mdx @@ -4,10 +4,6 @@ sidebarTitle: "Types" description: "TypeScript types for AI Agents, UI messages, and the frontend transport." --- -import RcBanner from "/snippets/ai-chat-rc-banner.mdx"; - - - TypeScript patterns for [AI Chat](/ai-chat/overview). This page covers how to pin a custom AI SDK [`UIMessage`](https://sdk.vercel.ai/docs/reference/ai-sdk-core/ui-message) subtype with `chat.withUIMessage`, fix a typed `clientData` schema with `chat.withClientData`, chain builder-level hooks, and align types on the client. ## Custom `UIMessage` with `chat.withUIMessage` diff --git a/docs/ai-chat/upgrade-guide.mdx b/docs/ai-chat/upgrade-guide.mdx index fce47c0aa4b..a459faceaab 100644 --- a/docs/ai-chat/upgrade-guide.mdx +++ b/docs/ai-chat/upgrade-guide.mdx @@ -4,10 +4,6 @@ sidebarTitle: "Sessions Upgrade Guide" description: "Migrating chat.agent code from the prerelease API to the Sessions-as-run-manager release." --- -import RcBanner from "/snippets/ai-chat-rc-banner.mdx"; - - - This guide is for customers who tried `chat.agent` during the prerelease period. The public surface of `chat.agent({...})`, `useTriggerChatTransport`, `AgentChat`, `chat.defer`, and `chat.history` is largely diff --git a/docs/ai/prompts.mdx b/docs/ai/prompts.mdx index 556eb074942..c659156330d 100644 --- a/docs/ai/prompts.mdx +++ b/docs/ai/prompts.mdx @@ -4,10 +4,6 @@ sidebarTitle: "Prompts" description: "Define prompt templates as code, version them on deploy, and override from the dashboard without redeploying." --- -import RcBanner from "/snippets/ai-chat-rc-banner.mdx"; - - - ## Overview AI Prompts let you define prompt templates in your codebase alongside your tasks. When you deploy, Trigger.dev automatically versions your prompts. You can then: diff --git a/docs/building-with-ai.mdx b/docs/building-with-ai.mdx index 21cf5d4251f..7290a39b3e9 100644 --- a/docs/building-with-ai.mdx +++ b/docs/building-with-ai.mdx @@ -28,11 +28,6 @@ We provide multiple tools to help AI coding assistants write correct Trigger.dev npx trigger.dev@latest skills ``` - - The `skills` command is currently only available in the release candidate. Until it ships to - the stable release, run it with the `@rc` tag: `npx trigger.dev@rc skills`. - - [Learn more →](/skills) @@ -46,7 +41,7 @@ Skills and the MCP server do different jobs and work best together. Here's how t |:--|:-----------|:---------------| | **What it does** | Drops skill files into your project that teach Trigger.dev patterns | Runs a live server your AI connects to | | **Installs to** | `.claude/skills/`, `.cursor/skills/`, `.github/skills/`, `.agents/skills/` | `mcp.json`, `~/.claude.json`, etc. | -| **Updates** | Re-run `npx trigger.dev@latest skills` (`@rc` until it ships to stable), or auto-prompted on `trigger dev` | Always latest (uses `@latest`) | +| **Updates** | Re-run `npx trigger.dev@latest skills`, or auto-prompted on `trigger dev` | Always latest (uses `@latest`) | | **Best for** | Teaching patterns and best practices | Live project interaction (deploy, trigger, monitor) | | **Works offline** | Yes | No (calls Trigger.dev API) | diff --git a/docs/mcp-agent-rules.mdx b/docs/mcp-agent-rules.mdx index 1b97f3d95b1..6203be1b7e4 100644 --- a/docs/mcp-agent-rules.mdx +++ b/docs/mcp-agent-rules.mdx @@ -19,12 +19,7 @@ The install command is the same, and now installs skills: npx trigger.dev@latest skills ``` - - The `skills` command is currently only available in the release candidate. Until it ships to the - stable release, run it with the `@rc` tag: `npx trigger.dev@rc skills`. - - -`npx trigger.dev@latest install-rules` still works as an alias for `skills` (so the same `@rc` caveat applies for now), and `trigger dev` offers to install the skills on first run. +`npx trigger.dev@latest install-rules` still works as an alias for `skills`, and `trigger dev` offers to install the skills on first run. The old task and realtime guidance now lives in the `trigger-authoring-tasks` and `trigger-realtime-and-frontend` skills, alongside two new skills for building `chat.agent` AI agents. See [Skills](/skills) for the full list and supported assistants. diff --git a/docs/skills.mdx b/docs/skills.mdx index 6b58ce07215..43cfbe758a4 100644 --- a/docs/skills.mdx +++ b/docs/skills.mdx @@ -24,11 +24,6 @@ Run the installer with the CLI: npx trigger.dev@latest skills ``` - - The `skills` command is currently only available in the release candidate. Until it ships to the - stable release, run it with the `@rc` tag: `npx trigger.dev@rc skills`. - - The CLI detects your installed AI tools, lets you pick which skills to install, and writes each one into that tool's native skills directory (`.claude/skills/`, `.cursor/skills/`, `.github/skills/`, `.agents/skills/`). It also adds a one-line pointer to your primary instructions file (`CLAUDE.md`, `.cursor/rules`, etc.) so your assistant always knows the skills are there and loads the right one on demand. When you run `trigger dev` for the first time, the CLI offers to install the skills for you. @@ -43,8 +38,6 @@ Pass `--target` to choose tools and `-y` to install every skill without promptin npx trigger.dev@latest skills --target claude-code --target cursor -y ``` -While the command is still release-candidate only, swap `@latest` for `@rc`. - ## Available skills | Skill | Use for | Covers | @@ -72,7 +65,7 @@ Using a tool that does not support skills yet? Select "Unsupported target" in th ## Keeping skills updated -The API guidance updates on its own: it lives in `@trigger.dev/sdk` and is read from `node_modules`, so upgrading the SDK in your project upgrades the guidance with it. Re-run `npx trigger.dev@latest skills` (use `@rc` until the command ships to the stable release, or accept the prompt that `trigger dev` shows when a newer version is available) only to add skills or refresh the installed pointer files. Re-running overwrites them in place without creating duplicates. +The API guidance updates on its own: it lives in `@trigger.dev/sdk` and is read from `node_modules`, so upgrading the SDK in your project upgrades the guidance with it. Re-run `npx trigger.dev@latest skills` (or accept the prompt that `trigger dev` shows when a newer version is available) only to add skills or refresh the installed pointer files. Re-running overwrites them in place without creating duplicates. ## Next steps diff --git a/docs/snippets/ai-chat-rc-banner.mdx b/docs/snippets/ai-chat-rc-banner.mdx deleted file mode 100644 index 18a9dbcfe57..00000000000 --- a/docs/snippets/ai-chat-rc-banner.mdx +++ /dev/null @@ -1,3 +0,0 @@ - - The AI Agents and Prompts surface ships as part of the **v4.5 release candidate**. Install with `@trigger.dev/sdk@rc` (or pin `4.5.0-rc.0` or later) to use these features — they aren't yet on the latest stable, and APIs may still change before the 4.5.0 GA. See [supported AI SDK versions](/ai-chat/reference#compatibility) and the [AI chat changelog](/ai-chat/changelog) for details. - diff --git a/hosting/k8s/helm/Chart.yaml b/hosting/k8s/helm/Chart.yaml index 42383d2c65c..cd19753be22 100644 --- a/hosting/k8s/helm/Chart.yaml +++ b/hosting/k8s/helm/Chart.yaml @@ -2,8 +2,8 @@ apiVersion: v2 name: trigger description: The official Trigger.dev Helm chart type: application -version: 4.5.0-rc.7 -appVersion: v4.5.0-rc.7 +version: 4.5.0 +appVersion: v4.5.0 home: https://trigger.dev sources: - https://github.com/triggerdotdev/trigger.dev diff --git a/packages/build/CHANGELOG.md b/packages/build/CHANGELOG.md index 2e4aa60f036..5ab45411ba5 100644 --- a/packages/build/CHANGELOG.md +++ b/packages/build/CHANGELOG.md @@ -1,5 +1,25 @@ # @trigger.dev/build +## 4.5.0 + +### Patch Changes + +- Add Agent Skills for `chat.agent`. Drop a folder with a `SKILL.md` and any helper scripts/references next to your task code, register it with `skills.define({ id, path })`, and the CLI bundles it into the deploy image automatically — no `trigger.config.ts` changes. The agent gets a one-line summary in its system prompt and discovers full instructions on demand via `loadSkill`, with `bash` and `readFile` tools scoped per-skill (path-traversal guards, output caps, abort-signal propagation). ([#3543](https://github.com/triggerdotdev/trigger.dev/pull/3543)) + + ```ts + const pdfSkill = skills.define({ + id: "pdf-extract", + path: "./skills/pdf-extract", + }); + + chat.skills.set([await pdfSkill.local()]); + ``` + + Built on the [AI SDK cookbook pattern](https://ai-sdk.dev/cookbook/guides/agent-skills) — portable across providers. SDK + CLI only for now; dashboard-editable `SKILL.md` text is on the roadmap. + +- Updated dependencies: + - `@trigger.dev/core@4.5.0` + ## 4.5.0-rc.7 ### Patch Changes @@ -56,7 +76,10 @@ - Add Agent Skills for `chat.agent`. Drop a folder with a `SKILL.md` and any helper scripts/references next to your task code, register it with `skills.define({ id, path })`, and the CLI bundles it into the deploy image automatically — no `trigger.config.ts` changes. The agent gets a one-line summary in its system prompt and discovers full instructions on demand via `loadSkill`, with `bash` and `readFile` tools scoped per-skill (path-traversal guards, output caps, abort-signal propagation). ([#3543](https://github.com/triggerdotdev/trigger.dev/pull/3543)) ```ts - const pdfSkill = skills.define({ id: "pdf-extract", path: "./skills/pdf-extract" }); + const pdfSkill = skills.define({ + id: "pdf-extract", + path: "./skills/pdf-extract", + }); chat.skills.set([await pdfSkill.local()]); ``` @@ -166,7 +189,6 @@ - The `prismaExtension` has been completely redesigned to support multiple Prisma versions and deployment strategies. This update introduces **three distinct modes** to handle the evolving Prisma ecosystem, from legacy setups to the upcoming Prisma 7. ([#2689](https://github.com/triggerdotdev/trigger.dev/pull/2689)) **Highlights:** - - 🎯 Three modes: Legacy, Engine-Only, and Modern - 🎉 **NEW:** Support for `prisma.config.ts` files (Legacy Mode) - 🔍 **NEW:** Enhanced version detection with filesystem fallback @@ -215,7 +237,6 @@ **Use when:** You're using Prisma 6.x or earlier with the `prisma-client-js` provider. **Features:** - - Automatic `prisma generate` during deployment - Supports single-file schemas (`prisma/schema.prisma`) - Supports multi-file schemas (Prisma 6.7+, directory-based schemas) @@ -264,7 +285,6 @@ ``` **Tested Versions:** - - Prisma 6.14.0 ✅ - Prisma 6.7.0+ (multi-file schema support) ✅ - Prisma 5.x ✅ @@ -276,7 +296,6 @@ **Use when:** You have a custom Prisma client output path and want to manage `prisma generate` yourself. **Features:** - - Only installs Prisma engine binaries (no client generation) - Automatic version detection from `@prisma/client` - Manual override of version and binary target @@ -316,7 +335,6 @@ ``` **Important Notes:** - - You **must** run `prisma generate` yourself (typically in a prebuild script) - Your schema **must** include the correct `binaryTargets` for deployment to the trigger.dev cloud. The binary target is `debian-openssl-3.0.x`. - The extension sets `PRISMA_QUERY_ENGINE_LIBRARY` and `PRISMA_QUERY_ENGINE_SCHEMA_ENGINE` environment variables to the correct paths for the binary targets. @@ -334,7 +352,6 @@ ``` **Tested Versions:** - - Prisma 6.19.0 ✅ - Prisma 6.16.0+ ✅ @@ -345,7 +362,6 @@ **Use when:** You're using Prisma 6.16+ with the new `prisma-client` provider (with `engineType = "client"`) or preparing for Prisma 7. **Features:** - - Designed for the new Prisma architecture - Zero configuration required - Automatically marks `@prisma/client` as external @@ -409,14 +425,12 @@ ``` **Important Notes:** - - You **must** run `prisma generate` yourself - Requires Prisma 6.16.0+ or Prisma 7 beta - The new `prisma-client` provider generates plain TypeScript (no Rust binaries) - Requires database adapters (e.g., `@prisma/adapter-pg` for PostgreSQL) **Tested Versions:** - - Prisma 6.16.0 with `engineType = "client"` ✅ - Prisma 6.20.0-integration-next.8 (Prisma 7 beta) ✅ @@ -457,7 +471,6 @@ ### Preparing for Prisma 7 If you want to adopt the new Prisma architecture, use **Modern Mode**: - 1. Update your schema to use `prisma-client` provider 2. Add database adapters to your dependencies 3. Configure the extension: @@ -489,7 +502,6 @@ **Use when:** You want to use Prisma's new config file format (Prisma 6+) to centralize your Prisma configuration. **Benefits:** - - Single source of truth for Prisma configuration - Automatic extraction of schema location and migrations path - Type-safe configuration with TypeScript @@ -527,7 +539,6 @@ ``` **What gets extracted:** - - `schema` - The schema file or directory path - `migrations.path` - The migrations directory path (if specified) diff --git a/packages/build/package.json b/packages/build/package.json index 5c21745d4b6..9376fb2dc08 100644 --- a/packages/build/package.json +++ b/packages/build/package.json @@ -1,6 +1,6 @@ { "name": "@trigger.dev/build", - "version": "4.5.0-rc.7", + "version": "4.5.0", "description": "trigger.dev build extensions", "license": "MIT", "publishConfig": { @@ -78,7 +78,7 @@ }, "dependencies": { "@prisma/config": "^6.10.0", - "@trigger.dev/core": "workspace:4.5.0-rc.7", + "@trigger.dev/core": "workspace:4.5.0", "mlly": "^1.7.1", "pkg-types": "^1.1.3", "resolve": "^1.22.8", diff --git a/packages/cli-v3/CHANGELOG.md b/packages/cli-v3/CHANGELOG.md index 6cd084509d6..b0d207c29a3 100644 --- a/packages/cli-v3/CHANGELOG.md +++ b/packages/cli-v3/CHANGELOG.md @@ -1,5 +1,57 @@ # trigger.dev +## 4.5.0 + +### Patch Changes + +- `@trigger.dev/sdk` now bundles the Trigger.dev agent skills and a curated snapshot of the docs those skills reference. The skills that `trigger skills` installs into your coding agent read this content from node_modules, so the guidance your AI assistant follows is pinned to the SDK version installed in your project and stays current across upgrades instead of going stale until the next reinstall. ([#3937](https://github.com/triggerdotdev/trigger.dev/pull/3937)) +- Add Agent Skills for `chat.agent`. Drop a folder with a `SKILL.md` and any helper scripts/references next to your task code, register it with `skills.define({ id, path })`, and the CLI bundles it into the deploy image automatically — no `trigger.config.ts` changes. The agent gets a one-line summary in its system prompt and discovers full instructions on demand via `loadSkill`, with `bash` and `readFile` tools scoped per-skill (path-traversal guards, output caps, abort-signal propagation). ([#3543](https://github.com/triggerdotdev/trigger.dev/pull/3543)) + + ```ts + const pdfSkill = skills.define({ + id: "pdf-extract", + path: "./skills/pdf-extract", + }); + + chat.skills.set([await pdfSkill.local()]); + ``` + + Built on the [AI SDK cookbook pattern](https://ai-sdk.dev/cookbook/guides/agent-skills) — portable across providers. SDK + CLI only for now; dashboard-editable `SKILL.md` text is on the roadmap. + +- Fix `chat.agent` skills silently missing in `trigger dev` for projects whose task files read `process.env` at module top level (e.g. a third-party SDK client initialized at import). Skill folders now bundle into `.trigger/skills/` reliably regardless of which env vars are set when the CLI launches. ([#3690](https://github.com/triggerdotdev/trigger.dev/pull/3690)) +- Add `TRIGGER_BUILD_SKIP_REWRITE_TIMESTAMP=1` escape hatch for local self-hosted builds whose buildx driver doesn't support `rewrite-timestamp` alongside push (e.g. orbstack's default `docker` driver). ([#3618](https://github.com/triggerdotdev/trigger.dev/pull/3618)) +- Running a CLI command like `dev`, `deploy`, `preview`, or `update` before initializing a project no longer crashes with a raw `Cannot find matching package.json` stack trace. The CLI now detects the missing project and points you to `npx trigger.dev@latest init` instead. ([#3929](https://github.com/triggerdotdev/trigger.dev/pull/3929)) +- `trigger init` now sets up your AI coding assistant as part of project setup: pick the MCP server, the agent skills, or both, then scaffold with the CLI or hand off to your assistant. Adds a new `getting-started` agent skill that teaches assistants how to bootstrap Trigger.dev (install the SDK, write `trigger.config.ts`, create a first task, run `trigger dev`), so the AI-driven setup path works end to end. It ships in the CLI alongside the existing skills, version-matched to your SDK. ([#3872](https://github.com/triggerdotdev/trigger.dev/pull/3872)) +- Add support for dev branches to the webapp and CLI. This allows humans (and agents) to run multiple local dev servers simultaneously, with a separate dashboard for each one. ([#4023](https://github.com/triggerdotdev/trigger.dev/pull/4023)) +- `dev` and `deploy` now fail with a clear error when two tasks are defined with the same id, including across different task types (e.g. a scheduled task and a regular task sharing an id). Previously the second definition silently overwrote the first, so one of the tasks would vanish with no warning. Task ids are detected as duplicates during indexing (naming each offending id and the files it was found in), and the same rule is enforced server-side when the background worker is registered. ([#3865](https://github.com/triggerdotdev/trigger.dev/pull/3865)) +- Fix idempotency key metadata (original key + scope) being silently dropped when a single run creates more than 1000 idempotency keys. The in-process catalog that maps a key's hash back to its original key/scope is no longer bounded to 1000 entries, so `idempotencyKeys.create()` results retain their metadata regardless of how many are created in a run. The catalog is now cleared at each run boundary so it does not accumulate across warm-start runs. ([#4094](https://github.com/triggerdotdev/trigger.dev/pull/4094)) +- The CLI MCP server's agent-chat tools (`start_agent_chat`, `send_agent_message`, `close_agent_chat`) now run on the new Sessions primitive, so AI assistants driving a `chat.agent` get the same idempotent-by-`chatId`, durable-across-runs behavior the browser transport gets. Required PAT scopes go from `write:inputStreams` to `read:sessions` + `write:sessions`. ([#3546](https://github.com/triggerdotdev/trigger.dev/pull/3546)) +- MCP `list_runs` tool: add a `region` filter input and surface each run's executing region in the formatted summary. ([#3612](https://github.com/triggerdotdev/trigger.dev/pull/3612)) +- The MCP server no longer tells the AI agent to wait for a run to complete after every `trigger_task` call. Waiting is now opt-in: the agent only waits when you ask it to (for example "trigger and then wait for it to finish"). This avoids burning tokens polling runs you didn't need to block on and keeps responses clearer. ([#3838](https://github.com/triggerdotdev/trigger.dev/pull/3838)) +- Adds `trigger.dev mint-token`, which mints a short-lived delegated token from your stored personal access token. The token authenticates against the API as you, can be narrowed with `--cap` and given a lifetime with `--ttl`, and prints to stdout so it can be captured. ([#3997](https://github.com/triggerdotdev/trigger.dev/pull/3997)) + + ```bash + UAT=$(trigger.dev mint-token --ttl 3600 --cap read:runs) + ``` + +- Update the bundled OpenTelemetry packages to their latest releases (`@opentelemetry/sdk-node` 0.218.0, `@opentelemetry/core` 2.7.1, `@opentelemetry/host-metrics` 0.38.3). ([#3810](https://github.com/triggerdotdev/trigger.dev/pull/3810)) +- Fix `COULD_NOT_FIND_EXECUTOR` when a task's definition is loaded via `await import(...)` from inside another task's `run()`. The runtime workers now register such tasks with a sentinel file context, and the catalog logs a one-time warning per task id. ([#3688](https://github.com/triggerdotdev/trigger.dev/pull/3688)) +- Runner debug logs are now disabled by default. Set `SEND_RUN_DEBUG_LOGS=true` on the supervisor to re-enable them. ([#3992](https://github.com/triggerdotdev/trigger.dev/pull/3992)) +- Bump `@s2-dev/streamstore` to `0.22.10` to fix a `TASK_RUN_UNCAUGHT_EXCEPTION` ("Invalid state: Unable to enqueue") when a `chat.agent` turn is aborted mid-stream. ([#3792](https://github.com/triggerdotdev/trigger.dev/pull/3792)) +- The agent skills installed by `trigger skills` are now namespaced with a `trigger-` prefix (e.g. `trigger-authoring-tasks`, `trigger-getting-started`) so they don't collide with unrelated skills in your coding agent's skills directory. Adds a `trigger-cost-savings` skill for auditing and reducing compute spend (right-sizing machines, `maxDuration`, batching, debounce), and `@trigger.dev/sdk` now bundles the full Trigger.dev documentation so your agent can read the complete, version-pinned reference directly from node_modules. ([#3970](https://github.com/triggerdotdev/trigger.dev/pull/3970)) +- `trigger skills` installs Trigger.dev agent skills into your coding agent so it knows how to write tasks, schedules, realtime, and chat.agent code. The skills ship with the CLI and are copied into each tool's native skills directory (Claude Code, Cursor, GitHub Copilot, and Codex / AGENTS.md), and `trigger dev` offers to install them on first run. ([#3868](https://github.com/triggerdotdev/trigger.dev/pull/3868)) + + ```bash + trigger skills --target claude-code + ``` + + Replaces the previous `install-rules` command, which stays as an alias. + +- Updated dependencies: + - `@trigger.dev/core@4.5.0` + - `@trigger.dev/build@4.5.0` + - `@trigger.dev/schema-to-json@4.5.0` + ## 4.5.0-rc.7 ### Patch Changes @@ -88,7 +140,10 @@ - Add Agent Skills for `chat.agent`. Drop a folder with a `SKILL.md` and any helper scripts/references next to your task code, register it with `skills.define({ id, path })`, and the CLI bundles it into the deploy image automatically — no `trigger.config.ts` changes. The agent gets a one-line summary in its system prompt and discovers full instructions on demand via `loadSkill`, with `bash` and `readFile` tools scoped per-skill (path-traversal guards, output caps, abort-signal propagation). ([#3543](https://github.com/triggerdotdev/trigger.dev/pull/3543)) ```ts - const pdfSkill = skills.define({ id: "pdf-extract", path: "./skills/pdf-extract" }); + const pdfSkill = skills.define({ + id: "pdf-extract", + path: "./skills/pdf-extract", + }); chat.skills.set([await pdfSkill.local()]); ``` @@ -132,7 +187,6 @@ - Fix dev CLI leaking build directories on rebuild, causing disk space accumulation. Deprecated workers are now pruned (capped at 2 retained) when no active runs reference them. The watchdog process also cleans up `.trigger/tmp/` when the dev CLI is killed ungracefully (e.g. SIGKILL from pnpm). ([#3224](https://github.com/triggerdotdev/trigger.dev/pull/3224)) - Fix `--load` flag being silently ignored on local/self-hosted builds. ([#3114](https://github.com/triggerdotdev/trigger.dev/pull/3114)) - Add `get_span_details` MCP tool for inspecting individual spans within a run trace. ([#3255](https://github.com/triggerdotdev/trigger.dev/pull/3255)) - - New `get_span_details` tool returns full span attributes, timing, events, and AI enrichment (model, tokens, cost, speed) - Span IDs now shown in `get_run_details` trace output for easy discovery - New API endpoint `GET /api/v1/runs/:runId/spans/:spanId` @@ -141,7 +195,6 @@ - MCP server improvements: new tools, bug fixes, and new flags. ([#3224](https://github.com/triggerdotdev/trigger.dev/pull/3224)) **New tools:** - - `get_query_schema` — discover available TRQL tables and columns - `query` — execute TRQL queries against your data - `list_dashboards` — list built-in dashboards and their widgets @@ -154,19 +207,16 @@ - `dev_server_status` — check dev server status and view recent logs **New API endpoints:** - - `GET /api/v1/query/schema` — query table schema discovery - `GET /api/v1/query/dashboards` — list built-in dashboards **New features:** - - `--readonly` flag hides write tools (`deploy`, `trigger_task`, `cancel_run`) so the AI cannot make changes - `read:query` JWT scope for query endpoint authorization - `get_run_details` trace output is now paginated with cursor support - MCP tool annotations (`readOnlyHint`, `destructiveHint`) for all tools **Bug fixes:** - - Fixed `search_docs` tool failing due to renamed upstream Mintlify tool (`SearchTriggerDev` → `search_trigger_dev`) - Fixed `list_deploys` failing when deployments have null `runtime`/`runtimeVersion` fields (#3139) - Fixed `list_preview_branches` crashing due to incorrect response shape access @@ -174,7 +224,6 @@ - Fixed dev CLI leaking build directories on rebuild — deprecated workers now clean up their build dirs when their last run completes **Context optimizations:** - - `get_query_schema` now requires a table name and returns only one table's schema (was returning all tables) - `get_current_worker` no longer inlines payload schemas; use new `get_task_schema` tool instead - Query results formatted as text tables instead of JSON (~50% fewer tokens) @@ -1298,7 +1347,6 @@ All important socket.io RPCs will now be retried with backoff. Actions relying on checkpoints will be replayed if we haven't been checkpointed and restored as expected, e.g. after reconnect. Other changes: - - Fix retry check in shared queue - Fix env var sync spinner - Heartbeat between retries @@ -1426,7 +1474,6 @@ - e9a63a486: Lock SDK and CLI deps on exact core version - 8757fdcee: v3: [prod] force flush timeout should be 1s - 26093896d: When using idempotency keys, triggerAndWait and batchTriggerAndWait will still work even if the existing runs have already been completed (or even partially completed, in the case of batchTriggerAndWait) - - TaskRunExecutionResult.id is now the run friendlyId, not the attempt friendlyId - A single TaskRun can now have many batchItems, in the case of batchTriggerAndWait while using idempotency keys - A run’s idempotencyKey is now added to the ctx as well as the TaskEvent and displayed in the span view @@ -1683,7 +1730,6 @@ All important socket.io RPCs will now be retried with backoff. Actions relying on checkpoints will be replayed if we haven't been checkpointed and restored as expected, e.g. after reconnect. Other changes: - - Fix retry check in shared queue - Fix env var sync spinner - Heartbeat between retries @@ -2088,7 +2134,6 @@ ### Patch Changes - 26093896d: When using idempotency keys, triggerAndWait and batchTriggerAndWait will still work even if the existing runs have already been completed (or even partially completed, in the case of batchTriggerAndWait) - - TaskRunExecutionResult.id is now the run friendlyId, not the attempt friendlyId - A single TaskRun can now have many batchItems, in the case of batchTriggerAndWait while using idempotency keys - A run’s idempotencyKey is now added to the ctx as well as the TaskEvent and displayed in the span view diff --git a/packages/cli-v3/package.json b/packages/cli-v3/package.json index a8dc8908f60..21f67be693a 100644 --- a/packages/cli-v3/package.json +++ b/packages/cli-v3/package.json @@ -1,6 +1,6 @@ { "name": "trigger.dev", - "version": "4.5.0-rc.7", + "version": "4.5.0", "description": "A Command-Line Interface for Trigger.dev projects", "type": "module", "license": "MIT", @@ -97,9 +97,9 @@ "@opentelemetry/sdk-trace-node": "2.7.1", "@opentelemetry/semantic-conventions": "1.41.1", "@s2-dev/streamstore": "^0.22.10", - "@trigger.dev/build": "workspace:4.5.0-rc.7", - "@trigger.dev/core": "workspace:4.5.0-rc.7", - "@trigger.dev/schema-to-json": "workspace:4.5.0-rc.7", + "@trigger.dev/build": "workspace:4.5.0", + "@trigger.dev/core": "workspace:4.5.0", + "@trigger.dev/schema-to-json": "workspace:4.5.0", "ansi-escapes": "^7.0.0", "braces": "^3.0.3", "c12": "^1.11.1", diff --git a/packages/core/CHANGELOG.md b/packages/core/CHANGELOG.md index ab55cbb8736..f117413a82a 100644 --- a/packages/core/CHANGELOG.md +++ b/packages/core/CHANGELOG.md @@ -1,5 +1,137 @@ # internal-platform +## 4.5.0 + +### Patch Changes + +- Add Agent Skills for `chat.agent`. Drop a folder with a `SKILL.md` and any helper scripts/references next to your task code, register it with `skills.define({ id, path })`, and the CLI bundles it into the deploy image automatically — no `trigger.config.ts` changes. The agent gets a one-line summary in its system prompt and discovers full instructions on demand via `loadSkill`, with `bash` and `readFile` tools scoped per-skill (path-traversal guards, output caps, abort-signal propagation). ([#3543](https://github.com/triggerdotdev/trigger.dev/pull/3543)) + + ```ts + const pdfSkill = skills.define({ + id: "pdf-extract", + path: "./skills/pdf-extract", + }); + + chat.skills.set([await pdfSkill.local()]); + ``` + + Built on the [AI SDK cookbook pattern](https://ai-sdk.dev/cookbook/guides/agent-skills) — portable across providers. SDK + CLI only for now; dashboard-editable `SKILL.md` text is on the roadmap. + +- Add optional `shouldPauseScaling` to the supervisor consumer pool scaling options to freeze scale-up while it returns true (scale-down stays allowed). ([#3836](https://github.com/triggerdotdev/trigger.dev/pull/3836)) +- Reject overlong `idempotencyKey` values at the API boundary so they no longer trip an internal size limit on the underlying unique index and surface as a generic 500. Inputs are capped at 2048 characters — well above what `idempotencyKeys.create()` produces (a 64-character hash) and above any realistic raw key. Applies to `tasks.trigger`, `tasks.batchTrigger`, `batch.create` (Phase 1 streaming batches), `wait.createToken`, `wait.forDuration`, and the input/session stream waitpoint endpoints. Over-limit requests now return a structured 400 instead. ([#3560](https://github.com/triggerdotdev/trigger.dev/pull/3560)) +- Reliability fixes for `chat.agent`. A user message sent while the agent is streaming is no longer delivered twice (which could run a duplicate turn), input appends now carry an idempotency key so a retried send can't duplicate a message, stopping a generation clears the streaming state so a page reload doesn't replay the stopped turn, and runs can now carry the full set of dashboard tags instead of being silently truncated. `onTurnComplete` now fires on errored turns (with the thrown error attached) and the failed turn's user message is persisted so it isn't lost on the next run. Custom agents and manual `chat.writeTurnComplete` callers now trim the output stream, sending a custom action no longer leaves a second stream reader running, and a long-lived `watch` subscription no longer grows its dedupe set without bound. ([#3891](https://github.com/triggerdotdev/trigger.dev/pull/3891)) +- **AI Agents** — run AI SDK chat completions as durable Trigger.dev agents instead of fragile API routes. Define an agent in one function, point `useChat` at it from React, and the conversation survives page refreshes, network blips, and process restarts. ([#3543](https://github.com/triggerdotdev/trigger.dev/pull/3543)) + + ```ts + import { chat } from "@trigger.dev/sdk/ai"; + import { streamText } from "ai"; + import { openai } from "@ai-sdk/openai"; + + export const myChat = chat.agent({ + id: "my-chat", + run: async ({ messages, signal }) => + streamText({ model: openai("gpt-4o"), messages, abortSignal: signal }), + }); + ``` + + ```tsx + import { useChat } from "@ai-sdk/react"; + import { useTriggerChatTransport } from "@trigger.dev/sdk/chat/react"; + + const transport = useTriggerChatTransport({ + task: "my-chat", + accessToken, + startSession, + }); + const { messages, sendMessage } = useChat({ transport }); + ``` + + **What you get:** + - **AI SDK `useChat` integration** — a custom [`ChatTransport`](https://sdk.vercel.ai/docs/ai-sdk-ui/transport) (`useTriggerChatTransport`) plugs straight into Vercel AI SDK's `useChat` hook. Text streaming, tool calls, reasoning, and `data-*` parts all work natively over Trigger.dev's realtime streams. No custom API routes needed. + - **First-turn fast path (`chat.headStart`)** — opt-in handler that runs the first turn's `streamText` step in your warm server process while the agent run boots in parallel, cutting cold-start TTFC by roughly half (measured 2801ms → 1218ms on `claude-sonnet-4-6`). The agent owns step 2+ (tool execution, persistence, hooks) so heavy deps stay where they belong. Web Fetch handler works natively in Next.js, Hono, SvelteKit, Remix, Workers, etc.; bridge to Express/Fastify/Koa via `chat.toNodeListener`. New `@trigger.dev/sdk/chat-server` subpath. + - **Multi-turn durability via Sessions** — every chat is backed by a durable Session that outlives any individual run. Conversations resume across page refreshes, idle timeout, crashes, and deploys; `resume: true` reconnects via `lastEventId` so clients only see new chunks. `sessions.list` enumerates chats for inbox-style UIs. + - **Auto-accumulated history, delta-only wire** — the backend accumulates the full conversation across turns; clients only ship the new message each turn. Long chats never hit the 512 KiB body cap. Register `hydrateMessages` to be the source of truth yourself. + - **Lifecycle hooks** — `onPreload`, `onChatStart`, `onValidateMessages`, `hydrateMessages`, `onTurnStart`, `onBeforeTurnComplete`, `onTurnComplete`, `onChatSuspend`, `onChatResume` — for persistence, validation, and post-turn work. + - **Stop generation** — client-driven `transport.stopGeneration(chatId)` aborts mid-stream; the run stays alive for the next message, partial response is captured, and aborted parts (stuck `partial-call` tools, in-progress reasoning) are auto-cleaned. + - **Tool approvals (HITL)** — tools with `needsApproval: true` pause until the user approves or denies via `addToolApprovalResponse`. The runtime reconciles the updated assistant message by ID and continues `streamText`. + - **Steering and background injection** — `pendingMessages` injects user messages between tool-call steps so users can steer the agent mid-execution; `chat.inject()` + `chat.defer()` adds context from background work (self-review, RAG, safety checks) between turns. + - **Actions** — non-turn frontend commands (undo, rollback, regenerate, edit) sent via `transport.sendAction`. Fire `hydrateMessages` + `onAction` only — no turn hooks, no `run()`. `onAction` can return a `StreamTextResult` for a model response, or `void` for side-effect-only. + - **Typed state primitives** — `chat.local` for per-run state accessible from hooks, `run()`, tools, and subtasks (auto-serialized through `ai.toolExecute`); `chat.store` for typed shared data between agent and client; `chat.history` for reading and mutating the message chain; `clientDataSchema` for typed `clientData` in every hook. + - **`chat.toStreamTextOptions()`** — one spread into `streamText` wires up versioned system [Prompts](https://trigger.dev/docs/ai/prompts), model resolution, telemetry metadata, compaction, steering, and background injection. + - **Multi-tab coordination** — `multiTab: true` + `useMultiTabChat` prevents duplicate sends and syncs state across browser tabs via `BroadcastChannel`. Non-active tabs go read-only with live updates. + - **Network resilience** — built-in indefinite retry with bounded backoff, reconnect on `online` / tab refocus / bfcache restore, `Last-Event-ID` mid-stream resume. No app code needed. + + See [/docs/ai-chat](https://trigger.dev/docs/ai-chat/overview) for the full surface — quick start, three backend approaches (`chat.agent`, `chat.createSession`, raw task), persistence and code-sandbox patterns, type-level guides, and API reference. + +- Continuation chat boots no longer stall for around 10 seconds before the first turn. The `session.in` resume cursor is now found with a non-blocking records read instead of draining an SSE long-poll (which always waited out its full 5 second inactivity window, twice per boot), the boot reads run concurrently, and chat snapshots carry the cursor so subsequent boots skip the scan entirely. ([#3907](https://github.com/triggerdotdev/trigger.dev/pull/3907)) +- Stamp `gen_ai.conversation.id` (the chat id) on every span and metric emitted from inside a `chat.task` or `chat.agent` run. Lets you filter dashboard spans, runs, and metrics by the chat conversation that produced them — independent of the run boundary, so multi-run chats correlate cleanly. No code changes required on the user side. ([#3543](https://github.com/triggerdotdev/trigger.dev/pull/3543)) +- Coerce numeric `concurrencyKey` values to string at the API boundary across `tasks.trigger`, `tasks.batchTrigger`, and the Phase-2 streaming batch endpoint. ([#3789](https://github.com/triggerdotdev/trigger.dev/pull/3789)) +- Record client-side dequeue API latency in the supervisor consumer pool as a Prometheus histogram (`queue_consumer_pool_dequeue_duration_seconds`, labelled by `outcome`: success/empty/error). ([#3887](https://github.com/triggerdotdev/trigger.dev/pull/3887)) +- Add support for dev branches to the webapp and CLI. This allows humans (and agents) to run multiple local dev servers simultaneously, with a separate dashboard for each one. ([#4023](https://github.com/triggerdotdev/trigger.dev/pull/4023)) +- `dev` and `deploy` now fail with a clear error when two tasks are defined with the same id, including across different task types (e.g. a scheduled task and a regular task sharing an id). Previously the second definition silently overwrote the first, so one of the tasks would vanish with no warning. Task ids are detected as duplicates during indexing (naming each offending id and the files it was found in), and the same rule is enforced server-side when the background worker is registered. ([#3865](https://github.com/triggerdotdev/trigger.dev/pull/3865)) +- Fix `@trigger.dev/core` build: cast the underlying log record exporter when calling `forceFlush` so it typechecks against the updated OpenTelemetry `LogRecordExporter` type (which no longer declares `forceFlush`). ([#3829](https://github.com/triggerdotdev/trigger.dev/pull/3829)) +- `envvars.upload` now accepts an optional `isSecret` flag, letting you create the imported variables as secret (redacted) environment variables. When omitted, variables default to non-secret. ([#3809](https://github.com/triggerdotdev/trigger.dev/pull/3809)) + + ```ts + await envvars.upload("proj_1234", "prod", { + variables: { STRIPE_SECRET_KEY: "sk_live_..." }, + isSecret: true, + }); + ``` + +- Add request and response schemas for the new Errors API (error groups). These back the env-scoped HTTP endpoints for listing error groups, retrieving a single group, and changing its state (resolve, ignore, unresolve), plus a `filter[error]` option on the runs list to fetch the runs behind a group. Exported from `@trigger.dev/core/v3` so the SDK can reuse them. ([#4005](https://github.com/triggerdotdev/trigger.dev/pull/4005)) +- Add an optional `skipBodyParsing` flag to the internal HTTP server route definition, letting a route respond without reading or parsing the request body. ([#4009](https://github.com/triggerdotdev/trigger.dev/pull/4009)) +- Fix idempotency key metadata (original key + scope) being silently dropped when a single run creates more than 1000 idempotency keys. The in-process catalog that maps a key's hash back to its original key/scope is no longer bounded to 1000 entries, so `idempotencyKeys.create()` results retain their metadata regardless of how many are created in a run. The catalog is now cleared at each run boundary so it does not accumulate across warm-start runs. ([#4094](https://github.com/triggerdotdev/trigger.dev/pull/4094)) +- Offload large trigger payloads to object storage before sending the trigger API request. The SDK uploads packets at or above the existing 128KB limit and sends an `application/store` pointer instead of embedding large JSON in the request body. `TriggerTaskRequestBody` now validates that `application/store` payloads are non-empty storage paths. ([#3785](https://github.com/triggerdotdev/trigger.dev/pull/3785)) + + Payload uploads use the same resolved `ApiClient` as the trigger call (including `requestOptions.clientConfig`), not only the global `apiClientManager.client` — so custom `baseURL`, access token, and preview branch apply to both presign and trigger. + +- Fix `LocalsKey` type incompatibility across dual-package builds. The phantom value-type brand no longer uses a module-level `unique symbol`, so a single TypeScript compilation that resolves the type from both the ESM and CJS outputs (which can happen under certain pnpm hoisting layouts) no longer sees two structurally-incompatible variants of the same type. ([#3626](https://github.com/triggerdotdev/trigger.dev/pull/3626)) +- Unit-test `chat.agent` definitions offline with `mockChatAgent` from `@trigger.dev/sdk/ai/test`. Drives a real agent's turn loop in-process — no network, no task runtime — so you can send messages, actions, and stop signals via driver methods, inspect captured output chunks, and verify hooks fire. Pairs with `MockLanguageModelV3` from `ai/test` for model mocking. `setupLocals` lets you pre-seed `locals` (DB clients, service stubs) before `run()` starts. ([#3543](https://github.com/triggerdotdev/trigger.dev/pull/3543)) + + The broader `runInMockTaskContext` harness it's built on lives at `@trigger.dev/core/v3/test` — useful for unit-testing any task code, not just chat. + +- Update the bundled OpenTelemetry packages to their latest releases (`@opentelemetry/sdk-node` 0.218.0, `@opentelemetry/core` 2.7.1, `@opentelemetry/host-metrics` 0.38.3). ([#3810](https://github.com/triggerdotdev/trigger.dev/pull/3810)) +- Add `GetProjectEnvironmentsResponseBody` and `ProjectEnvironment` schemas for the new `GET /api/v1/projects/{projectRef}/environments` endpoint, which lists the parent environments (dev, staging, preview, prod) a personal access token can access for a project. Dev is scoped to the token owner and branch (preview child) environments are excluded. ([#3880](https://github.com/triggerdotdev/trigger.dev/pull/3880)) +- Fix `COULD_NOT_FIND_EXECUTOR` when a task's definition is loaded via `await import(...)` from inside another task's `run()`. The runtime workers now register such tasks with a sentinel file context, and the catalog logs a one-time warning per task id. ([#3688](https://github.com/triggerdotdev/trigger.dev/pull/3688)) +- Retry `TASK_MIDDLEWARE_ERROR` under the task's retry policy instead of failing the run on the first attempt. The error was already classified as retryable by `shouldRetryError`, but `shouldLookupRetrySettings` did not include it, so the retry flow fell through to `fail_run`. Fixes #3231. ([#3676](https://github.com/triggerdotdev/trigger.dev/pull/3676)) +- Retry `TASK_PROCESS_SIGSEGV` task crashes under the user's retry policy instead of failing the run on the first segfault. SIGSEGV in Node tasks is frequently non-deterministic (native addon races, JIT/GC interaction, near-OOM in native code, host issues), so retrying on a fresh process often succeeds. The retry is gated by the task's existing `retry` config + `maxAttempts` — same path `TASK_PROCESS_SIGTERM` and uncaught exceptions already use — so tasks without a retry policy still fail fast. ([#3552](https://github.com/triggerdotdev/trigger.dev/pull/3552)) +- Add `region` to the runs list / retrieve API: filter runs by region (`runs.list({ region: "..." })` / `filter[region]=`) and read each run's executing region from the new `region` field on the response. ([#3612](https://github.com/triggerdotdev/trigger.dev/pull/3612)) +- Bump `@s2-dev/streamstore` to `0.22.10` to fix a `TASK_RUN_UNCAUGHT_EXCEPTION` ("Invalid state: Unable to enqueue") when a `chat.agent` turn is aborted mid-stream. ([#3792](https://github.com/triggerdotdev/trigger.dev/pull/3792)) +- **Sessions** — a durable, run-aware stream channel keyed on a stable `externalId`. A Session is the unit of state that owns a multi-run conversation: messages flow through `.in`, responses through `.out`, both survive run boundaries. Sessions back the new `chat.agent` runtime, and you can build on them directly for any pattern that needs durable bi-directional streaming across runs. ([#3542](https://github.com/triggerdotdev/trigger.dev/pull/3542)) + + ```ts + import { sessions, tasks } from "@trigger.dev/sdk"; + + // Trigger a task and subscribe to its session output in one call + const { runId, stream } = await tasks.triggerAndSubscribe( + "my-task", + payload, + { + externalId: "user-456", + }, + ); + + for await (const chunk of stream) { + // ... + } + + // Enumerate existing sessions (powers inbox-style UIs without a separate index) + for await (const s of sessions.list({ + type: "chat.agent", + tag: "user:user-456", + })) { + console.log(s.id, s.externalId, s.createdAt, s.closedAt); + } + ``` + + See [/docs/ai-chat/overview](https://trigger.dev/docs/ai-chat/overview) for the full surface — Sessions powers the durable, resumable chat runtime described there. + +- The run span API response now includes `cachedCost` and `cacheCreationCost` on the `ai` object, alongside the existing `inputCost` / `outputCost` / `totalCost`. `inputCost` reflects only the non-cached input, so these fields let you reconstruct the full cost breakdown for prompt-cached calls. ([#3958](https://github.com/triggerdotdev/trigger.dev/pull/3958)) +- Redact credential-bearing flag values (e.g. `--password`, `--token`) from `Exec` command debug logs ([#4087](https://github.com/triggerdotdev/trigger.dev/pull/4087)) +- Fix `TypeError` in `unflattenAttributes` when the input attribute map contains conflicting dotted key paths (e.g. both `a.b` set to a scalar and `a.b.c` set to a value). The path-walk loop now applies last-write-wins when a prior key wrote a primitive, null, or array at an intermediate slot, matching the existing precedent in `AttributeFlattener.addAttribute`. Callers no longer crash when handed malformed external attribute inputs. ([#3762](https://github.com/triggerdotdev/trigger.dev/pull/3762)) +- Fix external trace context leaking across runs on warm-started workers with `processKeepAlive` enabled. Every subsequent run's attempt span was being exported with the first run's `traceId` and `parentSpanId`, breaking causal-chain navigation in external APM tools. Runs without an external trace context are unaffected. ([#3768](https://github.com/triggerdotdev/trigger.dev/pull/3768)) + ## 4.5.0-rc.7 ### Patch Changes @@ -67,7 +199,10 @@ - Add Agent Skills for `chat.agent`. Drop a folder with a `SKILL.md` and any helper scripts/references next to your task code, register it with `skills.define({ id, path })`, and the CLI bundles it into the deploy image automatically — no `trigger.config.ts` changes. The agent gets a one-line summary in its system prompt and discovers full instructions on demand via `loadSkill`, with `bash` and `readFile` tools scoped per-skill (path-traversal guards, output caps, abort-signal propagation). ([#3543](https://github.com/triggerdotdev/trigger.dev/pull/3543)) ```ts - const pdfSkill = skills.define({ id: "pdf-extract", path: "./skills/pdf-extract" }); + const pdfSkill = skills.define({ + id: "pdf-extract", + path: "./skills/pdf-extract", + }); chat.skills.set([await pdfSkill.local()]); ``` @@ -93,12 +228,15 @@ import { useChat } from "@ai-sdk/react"; import { useTriggerChatTransport } from "@trigger.dev/sdk/chat/react"; - const transport = useTriggerChatTransport({ task: "my-chat", accessToken, startSession }); + const transport = useTriggerChatTransport({ + task: "my-chat", + accessToken, + startSession, + }); const { messages, sendMessage } = useChat({ transport }); ``` **What you get:** - - **AI SDK `useChat` integration** — a custom [`ChatTransport`](https://sdk.vercel.ai/docs/ai-sdk-ui/transport) (`useTriggerChatTransport`) plugs straight into Vercel AI SDK's `useChat` hook. Text streaming, tool calls, reasoning, and `data-*` parts all work natively over Trigger.dev's realtime streams. No custom API routes needed. - **First-turn fast path (`chat.headStart`)** — opt-in handler that runs the first turn's `streamText` step in your warm server process while the agent run boots in parallel, cutting cold-start TTFC by roughly half (measured 2801ms → 1218ms on `claude-sonnet-4-6`). The agent owns step 2+ (tool execution, persistence, hooks) so heavy deps stay where they belong. Web Fetch handler works natively in Next.js, Hono, SvelteKit, Remix, Workers, etc.; bridge to Express/Fastify/Koa via `chat.toNodeListener`. New `@trigger.dev/sdk/chat-server` subpath. - **Multi-turn durability via Sessions** — every chat is backed by a durable Session that outlives any individual run. Conversations resume across page refreshes, idle timeout, crashes, and deploys; `resume: true` reconnects via `lastEventId` so clients only see new chunks. `sessions.list` enumerates chats for inbox-style UIs. @@ -129,16 +267,23 @@ import { sessions, tasks } from "@trigger.dev/sdk"; // Trigger a task and subscribe to its session output in one call - const { runId, stream } = await tasks.triggerAndSubscribe("my-task", payload, { - externalId: "user-456", - }); + const { runId, stream } = await tasks.triggerAndSubscribe( + "my-task", + payload, + { + externalId: "user-456", + }, + ); for await (const chunk of stream) { // ... } // Enumerate existing sessions (powers inbox-style UIs without a separate index) - for await (const s of sessions.list({ type: "chat.agent", tag: "user:user-456" })) { + for await (const s of sessions.list({ + type: "chat.agent", + tag: "user:user-456", + })) { console.log(s.id, s.externalId, s.createdAt, s.closedAt); } ``` @@ -168,7 +313,6 @@ - Fix `list_deploys` MCP tool failing when deployments have null `runtime` or `runtimeVersion` fields. ([#3224](https://github.com/triggerdotdev/trigger.dev/pull/3224)) - Propagate run tags to span attributes so they can be extracted server-side for LLM cost attribution metadata. ([#3213](https://github.com/triggerdotdev/trigger.dev/pull/3213)) - Add `get_span_details` MCP tool for inspecting individual spans within a run trace. ([#3255](https://github.com/triggerdotdev/trigger.dev/pull/3255)) - - New `get_span_details` tool returns full span attributes, timing, events, and AI enrichment (model, tokens, cost, speed) - Span IDs now shown in `get_run_details` trace output for easy discovery - New API endpoint `GET /api/v1/runs/:runId/spans/:spanId` @@ -177,7 +321,6 @@ - MCP server improvements: new tools, bug fixes, and new flags. ([#3224](https://github.com/triggerdotdev/trigger.dev/pull/3224)) **New tools:** - - `get_query_schema` — discover available TRQL tables and columns - `query` — execute TRQL queries against your data - `list_dashboards` — list built-in dashboards and their widgets @@ -190,19 +333,16 @@ - `dev_server_status` — check dev server status and view recent logs **New API endpoints:** - - `GET /api/v1/query/schema` — query table schema discovery - `GET /api/v1/query/dashboards` — list built-in dashboards **New features:** - - `--readonly` flag hides write tools (`deploy`, `trigger_task`, `cancel_run`) so the AI cannot make changes - `read:query` JWT scope for query endpoint authorization - `get_run_details` trace output is now paginated with cursor support - MCP tool annotations (`readOnlyHint`, `destructiveHint`) for all tools **Bug fixes:** - - Fixed `search_docs` tool failing due to renamed upstream Mintlify tool (`SearchTriggerDev` → `search_trigger_dev`) - Fixed `list_deploys` failing when deployments have null `runtime`/`runtimeVersion` fields (#3139) - Fixed `list_preview_branches` crashing due to incorrect response shape access @@ -210,7 +350,6 @@ - Fixed dev CLI leaking build directories on rebuild — deprecated workers now clean up their build dirs when their last run completes **Context optimizations:** - - `get_query_schema` now requires a table name and returns only one table's schema (was returning all tables) - `get_current_worker` no longer inlines payload schemas; use new `get_task_schema` tool instead - Query results formatted as text tables instead of JSON (~50% fewer tokens) @@ -260,7 +399,6 @@ ### Patch Changes - Add support for AI SDK v6 (Vercel AI SDK) ([#2919](https://github.com/triggerdotdev/trigger.dev/pull/2919)) - - Updated peer dependency to allow `ai@^6.0.0` alongside v4 and v5 - Updated internal code to handle async validation from AI SDK v6's Schema type @@ -562,8 +700,14 @@ await childTask.trigger({ message: "Hello, world!" }); // This will override the task's machine preset and any defaults. Works with all trigger functions. - await childTask.trigger({ message: "Hello, world!" }, { machine: "small-2x" }); - await childTask.triggerAndWait({ message: "Hello, world!" }, { machine: "small-2x" }); + await childTask.trigger( + { message: "Hello, world!" }, + { machine: "small-2x" }, + ); + await childTask.triggerAndWait( + { message: "Hello, world!" }, + { machine: "small-2x" }, + ); await childTask.batchTrigger([ { payload: { message: "Hello, world!" }, options: { machine: "micro" } }, @@ -577,7 +721,7 @@ await tasks.trigger( "child", { message: "Hello, world!" }, - { machine: "small-2x" } + { machine: "small-2x" }, ); await tasks.batchTrigger("child", [ { payload: { message: "Hello, world!" }, options: { machine: "micro" } }, @@ -639,7 +783,6 @@ ### Minor Changes - Improved Batch Triggering: ([#1502](https://github.com/triggerdotdev/trigger.dev/pull/1502)) - - The new Batch Trigger endpoint is now asynchronous and supports up to 500 runs per request. - The new endpoint also supports triggering multiple different tasks in a single batch request (support in the SDK coming soon). - The existing `batchTrigger` method now supports the new endpoint, and shouldn't require any changes to your code. @@ -653,14 +796,19 @@ }); // Works for individual items as well: await myTask.batchTrigger([ - { payload: { foo: "bar" }, options: { idempotencyKey: "my-key", idempotencyKeyTTL: "60s" } }, + { + payload: { foo: "bar" }, + options: { idempotencyKey: "my-key", idempotencyKeyTTL: "60s" }, + }, ]); // And `trigger`: - await myTask.trigger({ foo: "bar" }, { idempotencyKey: "my-key", idempotencyKeyTTL: "60s" }); + await myTask.trigger( + { foo: "bar" }, + { idempotencyKey: "my-key", idempotencyKeyTTL: "60s" }, + ); ``` ### Breaking Changes - - We've removed the `idempotencyKey` option from `triggerAndWait` and `batchTriggerAndWait`, because it can lead to permanently frozen runs in deployed tasks. We're working on upgrading our entire system to support idempotency keys on these methods, and we'll re-add the option once that's complete. ### Patch Changes @@ -892,7 +1040,6 @@ All important socket.io RPCs will now be retried with backoff. Actions relying on checkpoints will be replayed if we haven't been checkpointed and restored as expected, e.g. after reconnect. Other changes: - - Fix retry check in shared queue - Fix env var sync spinner - Heartbeat between retries @@ -1014,7 +1161,10 @@ Before: ```ts - await yourTask.trigger({ payload: { foo: "bar" }, options: { idempotencyKey: "key_1234" } }); + await yourTask.trigger({ + payload: { foo: "bar" }, + options: { idempotencyKey: "key_1234" }, + }); await yourTask.triggerAndWait({ payload: { foo: "bar" }, options: { idempotencyKey: "key_1234" }, @@ -1034,8 +1184,14 @@ await yourTask.trigger({ foo: "bar" }, { idempotencyKey: "key_1234" }); await yourTask.triggerAndWait({ foo: "bar" }, { idempotencyKey: "key_1234" }); - await yourTask.batchTrigger([{ payload: { foo: "bar" } }, { payload: { foo: "baz" } }]); - await yourTask.batchTriggerAndWait([{ payload: { foo: "bar" } }, { payload: { foo: "baz" } }]); + await yourTask.batchTrigger([ + { payload: { foo: "bar" } }, + { payload: { foo: "baz" } }, + ]); + await yourTask.batchTriggerAndWait([ + { payload: { foo: "bar" } }, + { payload: { foo: "baz" } }, + ]); ``` We've also changed the API of the `triggerAndWait` result. Before, if the subtask that was triggered finished with an error, we would automatically "rethrow" the error in the parent task. @@ -1069,7 +1225,6 @@ - e04d44866: v3: sanitize errors with null unicode characters in some places - 26093896d: When using idempotency keys, triggerAndWait and batchTriggerAndWait will still work even if the existing runs have already been completed (or even partially completed, in the case of batchTriggerAndWait) - - TaskRunExecutionResult.id is now the run friendlyId, not the attempt friendlyId - A single TaskRun can now have many batchItems, in the case of batchTriggerAndWait while using idempotency keys - A run’s idempotencyKey is now added to the ctx as well as the TaskEvent and displayed in the span view @@ -1188,7 +1343,6 @@ All important socket.io RPCs will now be retried with backoff. Actions relying on checkpoints will be replayed if we haven't been checkpointed and restored as expected, e.g. after reconnect. Other changes: - - Fix retry check in shared queue - Fix env var sync spinner - Heartbeat between retries @@ -1464,7 +1618,10 @@ Before: ```ts - await yourTask.trigger({ payload: { foo: "bar" }, options: { idempotencyKey: "key_1234" } }); + await yourTask.trigger({ + payload: { foo: "bar" }, + options: { idempotencyKey: "key_1234" }, + }); await yourTask.triggerAndWait({ payload: { foo: "bar" }, options: { idempotencyKey: "key_1234" }, @@ -1484,8 +1641,14 @@ await yourTask.trigger({ foo: "bar" }, { idempotencyKey: "key_1234" }); await yourTask.triggerAndWait({ foo: "bar" }, { idempotencyKey: "key_1234" }); - await yourTask.batchTrigger([{ payload: { foo: "bar" } }, { payload: { foo: "baz" } }]); - await yourTask.batchTriggerAndWait([{ payload: { foo: "bar" } }, { payload: { foo: "baz" } }]); + await yourTask.batchTrigger([ + { payload: { foo: "bar" } }, + { payload: { foo: "baz" } }, + ]); + await yourTask.batchTriggerAndWait([ + { payload: { foo: "bar" } }, + { payload: { foo: "baz" } }, + ]); ``` We've also changed the API of the `triggerAndWait` result. Before, if the subtask that was triggered finished with an error, we would automatically "rethrow" the error in the parent task. @@ -1518,7 +1681,6 @@ ``` - 26093896d: When using idempotency keys, triggerAndWait and batchTriggerAndWait will still work even if the existing runs have already been completed (or even partially completed, in the case of batchTriggerAndWait) - - TaskRunExecutionResult.id is now the run friendlyId, not the attempt friendlyId - A single TaskRun can now have many batchItems, in the case of batchTriggerAndWait while using idempotency keys - A run’s idempotencyKey is now added to the ctx as well as the TaskEvent and displayed in the span view diff --git a/packages/core/package.json b/packages/core/package.json index a3d220c6eac..9c0a0051581 100644 --- a/packages/core/package.json +++ b/packages/core/package.json @@ -1,6 +1,6 @@ { "name": "@trigger.dev/core", - "version": "4.5.0-rc.7", + "version": "4.5.0", "description": "Core code used across the Trigger.dev SDK and platform", "license": "MIT", "publishConfig": { diff --git a/packages/plugins/CHANGELOG.md b/packages/plugins/CHANGELOG.md index ae387c569b8..0afa025539f 100644 --- a/packages/plugins/CHANGELOG.md +++ b/packages/plugins/CHANGELOG.md @@ -1,5 +1,13 @@ # @trigger.dev/plugins +## 4.5.0 + +### Patch Changes + +- The public interfaces for a plugin system. Initially consolidated authentication and authorization interfaces. ([#3499](https://github.com/triggerdotdev/trigger.dev/pull/3499)) +- Updated dependencies: + - `@trigger.dev/core@4.5.0` + ## 4.5.0-rc.7 ### Patch Changes diff --git a/packages/plugins/package.json b/packages/plugins/package.json index dd3e8e9a639..cab8dd873f8 100644 --- a/packages/plugins/package.json +++ b/packages/plugins/package.json @@ -1,6 +1,6 @@ { "name": "@trigger.dev/plugins", - "version": "4.5.0-rc.7", + "version": "4.5.0", "description": "Plugin contracts and interfaces for Trigger.dev", "license": "MIT", "private": true, diff --git a/packages/python/CHANGELOG.md b/packages/python/CHANGELOG.md index 656fae1b620..adaabb287f0 100644 --- a/packages/python/CHANGELOG.md +++ b/packages/python/CHANGELOG.md @@ -1,5 +1,14 @@ # @trigger.dev/python +## 4.5.0 + +### Patch Changes + +- Updated dependencies: + - `@trigger.dev/sdk@4.5.0` + - `@trigger.dev/core@4.5.0` + - `@trigger.dev/build@4.5.0` + ## 4.5.0-rc.7 ### Patch Changes diff --git a/packages/python/package.json b/packages/python/package.json index 0ed3e7270a8..353d1c1eae4 100644 --- a/packages/python/package.json +++ b/packages/python/package.json @@ -1,6 +1,6 @@ { "name": "@trigger.dev/python", - "version": "4.5.0-rc.7", + "version": "4.5.0", "description": "Python runtime and build extension for Trigger.dev", "license": "MIT", "publishConfig": { @@ -45,7 +45,7 @@ "check-exports": "attw --pack ." }, "dependencies": { - "@trigger.dev/core": "workspace:4.5.0-rc.7", + "@trigger.dev/core": "workspace:4.5.0", "tinyexec": "^0.3.2" }, "devDependencies": { @@ -56,12 +56,12 @@ "tsx": "4.17.0", "esbuild": "^0.23.0", "@arethetypeswrong/cli": "^0.15.4", - "@trigger.dev/build": "workspace:4.5.0-rc.7", - "@trigger.dev/sdk": "workspace:4.5.0-rc.7" + "@trigger.dev/build": "workspace:4.5.0", + "@trigger.dev/sdk": "workspace:4.5.0" }, "peerDependencies": { - "@trigger.dev/sdk": "workspace:^4.5.0-rc.7", - "@trigger.dev/build": "workspace:^4.5.0-rc.7" + "@trigger.dev/sdk": "workspace:^4.5.0", + "@trigger.dev/build": "workspace:^4.5.0" }, "engines": { "node": ">=18.20.0" diff --git a/packages/react-hooks/CHANGELOG.md b/packages/react-hooks/CHANGELOG.md index ef31d170e6f..6b86646ea31 100644 --- a/packages/react-hooks/CHANGELOG.md +++ b/packages/react-hooks/CHANGELOG.md @@ -1,5 +1,12 @@ # @trigger.dev/react-hooks +## 4.5.0 + +### Patch Changes + +- Updated dependencies: + - `@trigger.dev/core@4.5.0` + ## 4.5.0-rc.7 ### Patch Changes @@ -604,7 +611,6 @@ ### Minor Changes - Improved Batch Triggering: ([#1502](https://github.com/triggerdotdev/trigger.dev/pull/1502)) - - The new Batch Trigger endpoint is now asynchronous and supports up to 500 runs per request. - The new endpoint also supports triggering multiple different tasks in a single batch request (support in the SDK coming soon). - The existing `batchTrigger` method now supports the new endpoint, and shouldn't require any changes to your code. @@ -618,14 +624,19 @@ }); // Works for individual items as well: await myTask.batchTrigger([ - { payload: { foo: "bar" }, options: { idempotencyKey: "my-key", idempotencyKeyTTL: "60s" } }, + { + payload: { foo: "bar" }, + options: { idempotencyKey: "my-key", idempotencyKeyTTL: "60s" }, + }, ]); // And `trigger`: - await myTask.trigger({ foo: "bar" }, { idempotencyKey: "my-key", idempotencyKeyTTL: "60s" }); + await myTask.trigger( + { foo: "bar" }, + { idempotencyKey: "my-key", idempotencyKeyTTL: "60s" }, + ); ``` ### Breaking Changes - - We've removed the `idempotencyKey` option from `triggerAndWait` and `batchTriggerAndWait`, because it can lead to permanently frozen runs in deployed tasks. We're working on upgrading our entire system to support idempotency keys on these methods, and we'll re-add the option once that's complete. ### Patch Changes diff --git a/packages/react-hooks/package.json b/packages/react-hooks/package.json index 15964513a98..bc3e92c1baf 100644 --- a/packages/react-hooks/package.json +++ b/packages/react-hooks/package.json @@ -1,6 +1,6 @@ { "name": "@trigger.dev/react-hooks", - "version": "4.5.0-rc.7", + "version": "4.5.0", "description": "trigger.dev react hooks", "license": "MIT", "publishConfig": { @@ -37,7 +37,7 @@ "check-exports": "attw --pack ." }, "dependencies": { - "@trigger.dev/core": "workspace:^4.5.0-rc.7", + "@trigger.dev/core": "workspace:^4.5.0", "swr": "^2.2.5" }, "devDependencies": { diff --git a/packages/redis-worker/CHANGELOG.md b/packages/redis-worker/CHANGELOG.md index e3fcc56aca5..80222cb4966 100644 --- a/packages/redis-worker/CHANGELOG.md +++ b/packages/redis-worker/CHANGELOG.md @@ -1,5 +1,23 @@ # @trigger.dev/redis-worker +## 4.5.0 + +### Patch Changes + +- Pipeline the per-entry `HGETALL` fetches in `MollifierBuffer.listEntriesForEnv`. The previous serial implementation issued one Redis round-trip per runId returned by `LRANGE`, which dominated stale-sweep wall-time at any meaningful backlog (at the sweep's default maxCount=1000, this is ~1000 RTTs per env per pass). Behaviour is unchanged — entries are still skipped when the entry hash has been torn down by a concurrent drainer ack/fail between the LRANGE and the HGETALL. ([#3752](https://github.com/triggerdotdev/trigger.dev/pull/3752)) +- Make mollifier buffer and drainer internals configurable. `MollifierBuffer` now accepts `ackGraceTtlSeconds`, `maxRetriesPerRequest`, `reconnectStepMs`, and `reconnectMaxMs` options, and `MollifierDrainer` accepts `maxBackoffMs` and `backoffFloorMs`. All default to their previous hardcoded values, so existing behaviour is unchanged. ([#3822](https://github.com/triggerdotdev/trigger.dev/pull/3822)) +- `MollifierDrainer` accepts a `drainBatchSize` option (default 1) that controls how many entries are popped per env per tick — in-flight handlers remain capped by the global `concurrency`. `MollifierBuffer` also gains `getDrainingCount()` / `listStaleDraining()`, backed by a new `mollifier:draining` ZSET maintained atomically with pop/ack/fail/requeue (observability-only). ([#3797](https://github.com/triggerdotdev/trigger.dev/pull/3797)) +- Add MollifierBuffer and MollifierDrainer primitives for trigger burst smoothing. ([#3614](https://github.com/triggerdotdev/trigger.dev/pull/3614)) + + MollifierBuffer (`accept`, `pop`, `ack`, `requeue`, `fail`, `evaluateTrip`) is a per-env FIFO over Redis with atomic Lua transitions for status tracking. `evaluateTrip` is a sliding-window trip evaluator the webapp gate uses to detect per-env trigger bursts. + + MollifierDrainer pops entries through a polling loop with a user-supplied handler. The loop survives transient Redis errors via capped exponential backoff (up to 5s), and per-env pop failures don't poison the rest of the batch — one env's blip is logged and counted as failed for that tick. Rotation is two-level: orgs at the top, envs within each org. The buffer maintains `mollifier:orgs` and `mollifier:org-envs:${orgId}` atomically with per-env queues, so the drainer walks orgs → envs directly without an in-memory cache. The `maxOrgsPerTick` option (default 500) caps how many orgs are scheduled per tick; for each picked org, one env is popped (rotating round-robin within the org). An org with N envs gets the same per-tick scheduling slot as an org with 1 env, so tenant-level drainage throughput is determined by org count rather than env count. + +- Mollifier `mutateSnapshot` now enforces a tag cap: an `append_tags` patch carrying `maxTags` returns `"limit_exceeded"` (writing nothing) when the deduped tag count would exceed the limit, so a buffered run can't accumulate more tags via the tags API than the trigger validator allows at creation. ([#3756](https://github.com/triggerdotdev/trigger.dev/pull/3756)) +- Add a `redis_worker.queue.oldest_message_age` observable gauge (unit `ms`, labeled `worker_name`) reporting the age of the oldest overdue message in each queue. This is a generic queue-stall signal: it stays at 0 while a queue drains healthily and rises only when due work sits undrained (e.g. a blocked dequeue, a dead consumer, or backpressure), even when no items are being processed. Orphaned queue entries are resolved against the items hash so they don't report a phantom stall. Also exposes `SimpleQueue.oldestMessageAge()`. ([#4086](https://github.com/triggerdotdev/trigger.dev/pull/4086)) +- Updated dependencies: + - `@trigger.dev/core@4.5.0` + ## 4.5.0-rc.7 ### Patch Changes diff --git a/packages/redis-worker/package.json b/packages/redis-worker/package.json index 9c4b228477e..bca8346f74f 100644 --- a/packages/redis-worker/package.json +++ b/packages/redis-worker/package.json @@ -1,6 +1,6 @@ { "name": "@trigger.dev/redis-worker", - "version": "4.5.0-rc.7", + "version": "4.5.0", "description": "Redis worker for trigger.dev", "license": "MIT", "publishConfig": { @@ -23,7 +23,7 @@ "test": "vitest --sequence.concurrent=false --no-file-parallelism" }, "dependencies": { - "@trigger.dev/core": "workspace:4.5.0-rc.7", + "@trigger.dev/core": "workspace:4.5.0", "lodash.omit": "^4.5.0", "nanoid": "^5.0.7", "p-limit": "^6.2.0", diff --git a/packages/rsc/CHANGELOG.md b/packages/rsc/CHANGELOG.md index b800119490b..b5fcb6bedb4 100644 --- a/packages/rsc/CHANGELOG.md +++ b/packages/rsc/CHANGELOG.md @@ -1,5 +1,12 @@ # @trigger.dev/rsc +## 4.5.0 + +### Patch Changes + +- Updated dependencies: + - `@trigger.dev/core@4.5.0` + ## 4.5.0-rc.7 ### Patch Changes diff --git a/packages/rsc/package.json b/packages/rsc/package.json index 62379932afa..7ccd4bc551e 100644 --- a/packages/rsc/package.json +++ b/packages/rsc/package.json @@ -1,6 +1,6 @@ { "name": "@trigger.dev/rsc", - "version": "4.5.0-rc.7", + "version": "4.5.0", "description": "trigger.dev rsc", "license": "MIT", "publishConfig": { @@ -37,14 +37,14 @@ "check-exports": "attw --pack ." }, "dependencies": { - "@trigger.dev/core": "workspace:^4.5.0-rc.7", + "@trigger.dev/core": "workspace:^4.5.0", "mlly": "^1.7.1", "react": "19.0.0-rc.1", "react-dom": "19.0.0-rc.1" }, "devDependencies": { "@arethetypeswrong/cli": "^0.15.4", - "@trigger.dev/build": "workspace:^4.5.0-rc.7", + "@trigger.dev/build": "workspace:^4.5.0", "@types/node": "^20.14.14", "@types/react": "*", "@types/react-dom": "*", diff --git a/packages/schema-to-json/CHANGELOG.md b/packages/schema-to-json/CHANGELOG.md index 1fb14cc02b2..d29a1d1d40a 100644 --- a/packages/schema-to-json/CHANGELOG.md +++ b/packages/schema-to-json/CHANGELOG.md @@ -1,5 +1,12 @@ # @trigger.dev/schema-to-json +## 4.5.0 + +### Patch Changes + +- Updated dependencies: + - `@trigger.dev/core@4.5.0` + ## 4.5.0-rc.7 ### Patch Changes diff --git a/packages/schema-to-json/package.json b/packages/schema-to-json/package.json index b1be66594a5..dda23c2fe2d 100644 --- a/packages/schema-to-json/package.json +++ b/packages/schema-to-json/package.json @@ -1,6 +1,6 @@ { "name": "@trigger.dev/schema-to-json", - "version": "4.5.0-rc.7", + "version": "4.5.0", "description": "Convert various schema validation libraries to JSON Schema", "license": "MIT", "publishConfig": { diff --git a/packages/trigger-sdk/CHANGELOG.md b/packages/trigger-sdk/CHANGELOG.md index 47dc203b12c..9c4c62e76e5 100644 --- a/packages/trigger-sdk/CHANGELOG.md +++ b/packages/trigger-sdk/CHANGELOG.md @@ -1,10 +1,232 @@ # @trigger.dev/sdk -## 4.5.0-rc.7 +## 4.5.0 + +### Minor Changes + +- **AI Prompts** — define prompt templates as code alongside your tasks, version them on deploy, and override the text or model from the dashboard without redeploying. Prompts integrate with the Vercel AI SDK via `toAISDKTelemetry()` (links every generation span back to the prompt) and with `chat.agent` via `chat.prompt.set()` + `chat.toStreamTextOptions()`. ([#3629](https://github.com/triggerdotdev/trigger.dev/pull/3629)) + + ```ts + import { prompts } from "@trigger.dev/sdk"; + import { generateText } from "ai"; + import { openai } from "@ai-sdk/openai"; + import { z } from "zod"; + + export const supportPrompt = prompts.define({ + id: "customer-support", + model: "gpt-4o", + config: { temperature: 0.7 }, + variables: z.object({ + customerName: z.string(), + plan: z.string(), + issue: z.string(), + }), + content: `You are a support agent for Acme. + + Customer: {{customerName}} ({{plan}} plan) + Issue: {{issue}}`, + }); + + const resolved = await supportPrompt.resolve({ + customerName: "Alice", + plan: "Pro", + issue: "Can't access billing", + }); + + const result = await generateText({ + model: openai(resolved.model ?? "gpt-4o"), + system: resolved.text, + prompt: "Can't access billing", + ...resolved.toAISDKTelemetry(), + }); + ``` + + **What you get:** + - **Code-defined, deploy-versioned templates** — define with `prompts.define({ id, model, config, variables, content })`. Every deploy creates a new version visible in the dashboard. Mustache-style placeholders (`{{var}}`, `{{#cond}}...{{/cond}}`) with Zod / ArkType / Valibot-typed variables. + - **Dashboard overrides** — change a prompt's text or model from the dashboard without redeploying. Overrides take priority over the deployed "current" version and are environment-scoped (dev / staging / production independent). + - **Resolve API** — `prompt.resolve(vars, { version?, label? })` returns the compiled `text`, resolved `model`, `version`, and labels. Standalone `prompts.resolve(slug, vars)` for cross-file resolution with full type inference on slug and variable shape. + - **AI SDK integration** — spread `resolved.toAISDKTelemetry({ ...extra })` into any `generateText` / `streamText` call and every generation span links to the prompt in the dashboard alongside its input variables, model, tokens, and cost. + - **`chat.agent` integration** — `chat.prompt.set(resolved)` stores the resolved prompt run-scoped; `chat.toStreamTextOptions({ registry })` pulls `system`, `model` (resolved via the AI SDK provider registry), `temperature` / `maxTokens` / etc., and telemetry into a single spread for `streamText`. + - **Management SDK** — `prompts.list()`, `prompts.versions(slug)`, `prompts.promote(slug, version)`, `prompts.createOverride(slug, body)`, `prompts.updateOverride(slug, body)`, `prompts.removeOverride(slug)`, `prompts.reactivateOverride(slug, version)`. + - **Dashboard** — prompts list with per-prompt usage sparklines; per-prompt detail with Template / Details / Versions / Generations / Metrics tabs. AI generation spans get a custom inspector showing the linked prompt's metadata, input variables, and template content alongside model, tokens, cost, and the message thread. + + See [/docs/ai/prompts](https://trigger.dev/docs/ai/prompts) for the full reference — template syntax, version resolution order, override workflow, and type utilities (`PromptHandle`, `PromptIdentifier`, `PromptVariables`). + +- Adds `onBoot` to `chat.agent` — a lifecycle hook that fires once per worker process picking up the chat. Runs for the initial run, preloaded runs, AND reactive continuation runs (post-cancel, crash, `endRun`, `requestUpgrade`, OOM retry), before any other hook. Use it to initialize `chat.local`, open per-process resources, or re-hydrate state from your DB on continuation — anywhere the SAME run picking up after suspend/resume isn't enough. ([#3543](https://github.com/triggerdotdev/trigger.dev/pull/3543)) + + ```ts + const userContext = chat.local<{ name: string; plan: string }>({ + id: "userContext", + }); + + export const myChat = chat.agent({ + id: "my-chat", + onBoot: async ({ clientData, continuation }) => { + const user = await db.user.findUnique({ + where: { id: clientData.userId }, + }); + userContext.init({ name: user.name, plan: user.plan }); + }, + run: async ({ messages, signal }) => + streamText({ model: openai("gpt-4o"), messages, abortSignal: signal }), + }); + ``` + + Use `onBoot` (not `onChatStart`) for state setup that must run every time a worker picks up the chat — `onChatStart` fires once per chat and won't run on continuation, leaving `chat.local` uninitialized when `run()` tries to use it. + +- **AI Agents** — run AI SDK chat completions as durable Trigger.dev agents instead of fragile API routes. Define an agent in one function, point `useChat` at it from React, and the conversation survives page refreshes, network blips, and process restarts. ([#3543](https://github.com/triggerdotdev/trigger.dev/pull/3543)) + + ```ts + import { chat } from "@trigger.dev/sdk/ai"; + import { streamText } from "ai"; + import { openai } from "@ai-sdk/openai"; + + export const myChat = chat.agent({ + id: "my-chat", + run: async ({ messages, signal }) => + streamText({ model: openai("gpt-4o"), messages, abortSignal: signal }), + }); + ``` + + ```tsx + import { useChat } from "@ai-sdk/react"; + import { useTriggerChatTransport } from "@trigger.dev/sdk/chat/react"; + + const transport = useTriggerChatTransport({ + task: "my-chat", + accessToken, + startSession, + }); + const { messages, sendMessage } = useChat({ transport }); + ``` + + **What you get:** + - **AI SDK `useChat` integration** — a custom [`ChatTransport`](https://sdk.vercel.ai/docs/ai-sdk-ui/transport) (`useTriggerChatTransport`) plugs straight into Vercel AI SDK's `useChat` hook. Text streaming, tool calls, reasoning, and `data-*` parts all work natively over Trigger.dev's realtime streams. No custom API routes needed. + - **First-turn fast path (`chat.headStart`)** — opt-in handler that runs the first turn's `streamText` step in your warm server process while the agent run boots in parallel, cutting cold-start TTFC by roughly half (measured 2801ms → 1218ms on `claude-sonnet-4-6`). The agent owns step 2+ (tool execution, persistence, hooks) so heavy deps stay where they belong. Web Fetch handler works natively in Next.js, Hono, SvelteKit, Remix, Workers, etc.; bridge to Express/Fastify/Koa via `chat.toNodeListener`. New `@trigger.dev/sdk/chat-server` subpath. + - **Multi-turn durability via Sessions** — every chat is backed by a durable Session that outlives any individual run. Conversations resume across page refreshes, idle timeout, crashes, and deploys; `resume: true` reconnects via `lastEventId` so clients only see new chunks. `sessions.list` enumerates chats for inbox-style UIs. + - **Auto-accumulated history, delta-only wire** — the backend accumulates the full conversation across turns; clients only ship the new message each turn. Long chats never hit the 512 KiB body cap. Register `hydrateMessages` to be the source of truth yourself. + - **Lifecycle hooks** — `onPreload`, `onChatStart`, `onValidateMessages`, `hydrateMessages`, `onTurnStart`, `onBeforeTurnComplete`, `onTurnComplete`, `onChatSuspend`, `onChatResume` — for persistence, validation, and post-turn work. + - **Stop generation** — client-driven `transport.stopGeneration(chatId)` aborts mid-stream; the run stays alive for the next message, partial response is captured, and aborted parts (stuck `partial-call` tools, in-progress reasoning) are auto-cleaned. + - **Tool approvals (HITL)** — tools with `needsApproval: true` pause until the user approves or denies via `addToolApprovalResponse`. The runtime reconciles the updated assistant message by ID and continues `streamText`. + - **Steering and background injection** — `pendingMessages` injects user messages between tool-call steps so users can steer the agent mid-execution; `chat.inject()` + `chat.defer()` adds context from background work (self-review, RAG, safety checks) between turns. + - **Actions** — non-turn frontend commands (undo, rollback, regenerate, edit) sent via `transport.sendAction`. Fire `hydrateMessages` + `onAction` only — no turn hooks, no `run()`. `onAction` can return a `StreamTextResult` for a model response, or `void` for side-effect-only. + - **Typed state primitives** — `chat.local` for per-run state accessible from hooks, `run()`, tools, and subtasks (auto-serialized through `ai.toolExecute`); `chat.store` for typed shared data between agent and client; `chat.history` for reading and mutating the message chain; `clientDataSchema` for typed `clientData` in every hook. + - **`chat.toStreamTextOptions()`** — one spread into `streamText` wires up versioned system [Prompts](https://trigger.dev/docs/ai/prompts), model resolution, telemetry metadata, compaction, steering, and background injection. + - **Multi-tab coordination** — `multiTab: true` + `useMultiTabChat` prevents duplicate sends and syncs state across browser tabs via `BroadcastChannel`. Non-active tabs go read-only with live updates. + - **Network resilience** — built-in indefinite retry with bounded backoff, reconnect on `online` / tab refocus / bfcache restore, `Last-Event-ID` mid-stream resume. No app code needed. + + See [/docs/ai-chat](https://trigger.dev/docs/ai-chat/overview) for the full surface — quick start, three backend approaches (`chat.agent`, `chat.createSession`, raw task), persistence and code-sandbox patterns, type-level guides, and API reference. + +- Add read primitives to `chat.history` for HITL flows: `getPendingToolCalls()`, `getResolvedToolCalls()`, `extractNewToolResults(message)`, `getChain()`, and `findMessage(messageId)`. These lift the accumulator-walking logic that customers building human-in-the-loop tools were re-implementing into the SDK. ([#3543](https://github.com/triggerdotdev/trigger.dev/pull/3543)) + + Use `getPendingToolCalls()` to gate fresh user turns while a tool call is awaiting an answer. Use `extractNewToolResults(message)` to dedup tool results when persisting to your own store — the helper returns only the parts whose `toolCallId` is not already resolved on the chain. + + ```ts + const pending = chat.history.getPendingToolCalls(); + if (pending.length > 0) { + // an addToolOutput is expected before a new user message + } + + onTurnComplete: async ({ responseMessage }) => { + const newResults = chat.history.extractNewToolResults(responseMessage); + for (const r of newResults) { + await db.toolResults.upsert({ + id: r.toolCallId, + output: r.output, + errorText: r.errorText, + }); + } + }; + ``` + +- **Sessions** — a durable, run-aware stream channel keyed on a stable `externalId`. A Session is the unit of state that owns a multi-run conversation: messages flow through `.in`, responses through `.out`, both survive run boundaries. Sessions back the new `chat.agent` runtime, and you can build on them directly for any pattern that needs durable bi-directional streaming across runs. ([#3542](https://github.com/triggerdotdev/trigger.dev/pull/3542)) + + ```ts + import { sessions, tasks } from "@trigger.dev/sdk"; + + // Trigger a task and subscribe to its session output in one call + const { runId, stream } = await tasks.triggerAndSubscribe( + "my-task", + payload, + { + externalId: "user-456", + }, + ); + + for await (const chunk of stream) { + // ... + } + + // Enumerate existing sessions (powers inbox-style UIs without a separate index) + for await (const s of sessions.list({ + type: "chat.agent", + tag: "user:user-456", + })) { + console.log(s.id, s.externalId, s.createdAt, s.closedAt); + } + ``` + + See [/docs/ai-chat/overview](https://trigger.dev/docs/ai-chat/overview) for the full surface — Sessions powers the durable, resumable chat runtime described there. ### Patch Changes - `@trigger.dev/sdk` now bundles the Trigger.dev agent skills and a curated snapshot of the docs those skills reference. The skills that `trigger skills` installs into your coding agent read this content from node_modules, so the guidance your AI assistant follows is pinned to the SDK version installed in your project and stays current across upgrades instead of going stale until the next reinstall. ([#3937](https://github.com/triggerdotdev/trigger.dev/pull/3937)) +- Add Agent Skills for `chat.agent`. Drop a folder with a `SKILL.md` and any helper scripts/references next to your task code, register it with `skills.define({ id, path })`, and the CLI bundles it into the deploy image automatically — no `trigger.config.ts` changes. The agent gets a one-line summary in its system prompt and discovers full instructions on demand via `loadSkill`, with `bash` and `readFile` tools scoped per-skill (path-traversal guards, output caps, abort-signal propagation). ([#3543](https://github.com/triggerdotdev/trigger.dev/pull/3543)) + + ```ts + const pdfSkill = skills.define({ + id: "pdf-extract", + path: "./skills/pdf-extract", + }); + + chat.skills.set([await pdfSkill.local()]); + ``` + + Built on the [AI SDK cookbook pattern](https://ai-sdk.dev/cookbook/guides/agent-skills) — portable across providers. SDK + CLI only for now; dashboard-editable `SKILL.md` text is on the roadmap. + +- Adds AI SDK 7 support. The `ai` peer range now includes v7, and the `chat.agent` / chat surfaces work against v7's ESM-only build. On v7, install `@ai-sdk/otel` alongside `ai` and the SDK registers it for you so `experimental_telemetry` spans keep flowing into your run traces (v7 stopped emitting them from `ai` core). v5 and v6 keep working unchanged. ([#3833](https://github.com/triggerdotdev/trigger.dev/pull/3833)) +- Add `ai.toolExecute(task)` so you can wire a Trigger subtask in as the `execute` handler of an AI SDK `tool()` while defining `description` and `inputSchema` yourself — useful when you want full control over the tool surface and just need Trigger's subtask machinery for the body. ([#3546](https://github.com/triggerdotdev/trigger.dev/pull/3546)) + + ```ts + const myTool = tool({ + description: "...", + inputSchema: z.object({ ... }), + execute: ai.toolExecute(mySubtask), + }); + ``` + + `ai.tool(task)` (`toolFromTask`) keeps doing the all-in-one wrap and now aligns its return type with AI SDK's `ToolSet`. Minimum `ai` peer raised to `^6.0.116` to avoid cross-version `ToolSet` mismatches in monorepos. + +- Reliability fixes for `chat.agent`. A user message sent while the agent is streaming is no longer delivered twice (which could run a duplicate turn), input appends now carry an idempotency key so a retried send can't duplicate a message, stopping a generation clears the streaming state so a page reload doesn't replay the stopped turn, and runs can now carry the full set of dashboard tags instead of being silently truncated. `onTurnComplete` now fires on errored turns (with the thrown error attached) and the failed turn's user message is persisted so it isn't lost on the next run. Custom agents and manual `chat.writeTurnComplete` callers now trim the output stream, sending a custom action no longer leaves a second stream reader running, and a long-lived `watch` subscription no longer grows its dedupe set without bound. ([#3891](https://github.com/triggerdotdev/trigger.dev/pull/3891)) +- Fix `chat.agent` / `AgentChat` when the agent is deployed to a Trigger.dev preview branch. The realtime message-append and stream-subscribe calls now send the `x-trigger-branch` header (sourced from the same resolver `sessions.start` uses), so messaging a preview-branch chat agent no longer fails with `x-trigger-branch header required for preview env`. ([#4018](https://github.com/triggerdotdev/trigger.dev/pull/4018)) +- Add a `tools` option to `chat.agent`. Declaring your tools here threads them into the SDK's internal `convertToModelMessages`, so each tool's `toModelOutput` is re-applied when prior-turn history is re-converted. ([#3790](https://github.com/triggerdotdev/trigger.dev/pull/3790)) + + ```ts + chat.agent({ + tools: { readFile, search }, + run: async ({ messages, tools, signal }) => + streamText({ model, messages, tools, abortSignal: signal }), + }); + ``` + + Also exports `InferChatUIMessageFromTools` to derive the chat `UIMessage` type (typed tool parts) directly from a tool set. + +- Continuation chat boots no longer stall for around 10 seconds before the first turn. The `session.in` resume cursor is now found with a non-blocking records read instead of draining an SSE long-poll (which always waited out its full 5 second inactivity window, twice per boot), the boot reads run concurrently, and chat snapshots carry the cursor so subsequent boots skip the scan entirely. ([#3907](https://github.com/triggerdotdev/trigger.dev/pull/3907)) +- Fix Head Start handovers breaking when a `chat.agent` also defines a `prepareMessages` hook. A handover hands the first turn's pending tool call to the agent as a tool-approval round whose trailing tool message must reach the model untouched. A `prepareMessages` hook that rewrites the last message (for example the recommended prompt-caching breakpoint) could disturb it, so the turn failed with "tool_use ids were found without tool_result". The agent now preserves that approval tail across `prepareMessages`, so caching and Head Start compose cleanly. ([#4018](https://github.com/triggerdotdev/trigger.dev/pull/4018)) +- `chat.headStart` now accepts an `apiClient` option (base URL + access token), so the head-start route can create the session and trigger the agent run against a different project/environment than the warm server's ambient Trigger config. Useful when your `chat.agent` lives in a separate project from the app serving the route. Mirrors the `apiClient` option on `chat.createStartSessionAction`; your LLM provider keys stay in the `run` callback and are unaffected. ([#4018](https://github.com/triggerdotdev/trigger.dev/pull/4018)) + + ```ts + export const POST = chat.headStart({ + agentId: "my-agent", + apiClient: { baseURL, accessToken }, + run: async ({ chat }) => + streamText({ + ...chat.toStreamTextOptions({ tools }), + model: anthropic("claude-sonnet-4-6"), + }), + }); + ``` + - `chat.headStart` now works with the `chat.customAgent` and `chat.createSession` backends, not only `chat.agent`. The warm step-1 response hands over to your loop the same way it does for a managed agent. ([#3963](https://github.com/triggerdotdev/trigger.dev/pull/3963)) In a `chat.customAgent` loop, consume the handover on turn 0: @@ -16,7 +238,11 @@ if (isFinal) { await chat.writeTurnComplete(); // step 1 is the response, no streamText } else { - const result = streamText({ model, messages: conversation.modelMessages, tools }); + const result = streamText({ + model, + messages: conversation.modelMessages, + tools, + }); // Pass originalMessages so the handed-over tool round merges into the // step-1 assistant instead of starting a new message. const response = await chat.pipeAndCapture(result, { @@ -28,18 +254,82 @@ With `chat.createSession`, the iterator surfaces it as `turn.handover`; call `turn.complete()` with no argument on a final handover. The lower-level `chat.waitForHandover()` and `accumulator.applyHandover()` are also exported for hand-rolled loops. +- Fix `chat.headStart` when `hydrateMessages` is registered. The warm route's step-1 partial now reaches the agent's accumulator on the hydrate path, so `onTurnComplete` carries the full first turn (the head-start user message included), tool-call handovers resume from step 2 instead of re-running step 1, and the assistant `messageId` stays stable across the handover. ([#3907](https://github.com/triggerdotdev/trigger.dev/pull/3907)) +- Preserve reasoning parts across the `chat.headStart` handover. Extended-thinking models' step-1 reasoning now lands in the durable session history (and `onTurnComplete`) under the same assistant `messageId`, with provider metadata intact so Anthropic thinking signatures survive replays. ([#3907](https://github.com/triggerdotdev/trigger.dev/pull/3907)) - Add `triggerConfig` support to `chat.headStart()` and `chat.openSession()`, so the auto-triggered handover-prepare run inherits tags, queue, machine, and other session trigger options the same way `chat.createStartSessionAction()` does. The `chat:{chatId}` tag is prepended automatically. ([#3963](https://github.com/triggerdotdev/trigger.dev/pull/3963)) ```ts export const POST = chat.headStart({ agentId: "my-agent", triggerConfig: { tags: ["org:acme"], queue: "chat" }, - run: async ({ chat }) => streamText({ ...chat.toStreamTextOptions(), model }), + run: async ({ chat }) => + streamText({ ...chat.toStreamTextOptions(), model }), }); ``` Because the session is created once on the first head-start turn and is idempotent on the chat id, this is the only place to set those options for a head-start chat's lifetime. `chat.createStartSessionAction()` now also forwards `maxDuration`, `region`, and `lockToVersion` so both session entry points stay consistent. +- Stamp `gen_ai.conversation.id` (the chat id) on every span and metric emitted from inside a `chat.task` or `chat.agent` run. Lets you filter dashboard spans, runs, and metrics by the chat conversation that produced them — independent of the run boundary, so multi-run chats correlate cleanly. No code changes required on the user side. ([#3543](https://github.com/triggerdotdev/trigger.dev/pull/3543)) +- Fix `chat.agent` HITL continuations on reasoning-heavy turns. Two changes that work together: ([#3719](https://github.com/triggerdotdev/trigger.dev/pull/3719)) + - The per-turn merge now overlays the wire copy's tool-part state advancement onto the agent's existing chain — `state` + the matching resolution field (`output` / `errorText` / `approval`) come from the wire, everything else (text, reasoning, tool `input`, provider metadata) stays whatever the snapshot or `hydrateMessages` returned. Previously a full-message replace overwrote those fields with whatever the client shipped, so a slimmed wire copy landed a tool call with no `arguments` on the next LLM call. Covers `output-available` / `output-error` (HITL `addToolOutput`) and `approval-responded` / `output-denied` (approval flow). + - `TriggerChatTransport.sendMessages` and `AgentChat.sendRaw` now slim assistant messages that carry advanced tool parts. The wire payload is just `{ id, role, parts: [] }` for `submit-message` continuations; everything else passes through. Reasoning blobs and full tool inputs no longer ride the wire on every `addToolOutput` / `addToolApproveResponse`, so continuation payloads stay well under the `.in/append` cap on long agent loops. + + Note: `onValidateMessages` receives the slim wire on HITL turns. If you call `validateUIMessages` from `ai` against the full `messages` array it will reject the slim assistant; filter to user messages (or skip on HITL turns) — see the updated docstring on `onValidateMessages` for the recommended pattern. + + For `hydrateMessages` hooks that persist the chain, this release also adds a small helper to the `@trigger.dev/sdk/ai` surface: + + ```ts + import { chat, upsertIncomingMessage } from "@trigger.dev/sdk/ai"; + + chat.agent({ + hydrateMessages: async ({ chatId, trigger, incomingMessages }) => { + const record = await db.chat.findUnique({ where: { id: chatId } }); + const stored = record?.messages ?? []; + if (upsertIncomingMessage(stored, { trigger, incomingMessages })) { + await db.chat.update({ + where: { id: chatId }, + data: { messages: stored }, + }); + } + return stored; + }, + }); + ``` + + It pushes fresh user messages by id, no-ops on HITL continuations (the incoming shares an id with the existing assistant — the runtime overlays the new tool-state advance), and skips on non-`submit-message` triggers. Returns `true` if it mutated `stored` so the caller knows whether to persist. + + Net effect: `chat.addToolOutput(...)` / `chat.addToolApproveResponse(...)` on multi-step reasoning agents (OpenAI Responses with `store: false`, Anthropic extended thinking, etc.) no longer blows the cap and no longer corrupts the LLM input. + +- Type `chat.createStartSessionAction` against your chat agent so `clientData` is typed end-to-end on the first turn: ([#3684](https://github.com/triggerdotdev/trigger.dev/pull/3684)) + + ```ts + import { chat } from "@trigger.dev/sdk/ai"; + import type { myChat } from "@/trigger/chat"; + + export const startChatSession = + chat.createStartSessionAction("my-chat"); + + // In the browser, threaded from the transport's typed startSession callback: + const transport = useTriggerChatTransport({ + task: "my-chat", + startSession: ({ chatId, clientData }) => + startChatSession({ chatId, clientData }), + // ... + }); + ``` + + `ChatStartSessionParams` gains a typed `clientData` field — folded into the first run's `payload.metadata` so `onPreload` / `onChatStart` see the same shape per-turn `metadata` carries via the transport. The opaque session-level `metadata` field is unchanged. + +- `chat.createStartSessionAction` now accepts an `apiClient` option, so you can scope a chat session start to a specific environment's API config (`baseURL` / `accessToken`) without setting a global `TRIGGER_SECRET_KEY`. Useful when one server starts chats across more than one environment. ([#4018](https://github.com/triggerdotdev/trigger.dev/pull/4018)) + + ```ts + const startSession = chat.createStartSessionAction("my-chat", { + apiClient: { baseURL, accessToken }, + }); + + await startSession({ chatId, clientData }); + ``` + - Cache your chat agent's system prompt with Anthropic prompt caching. `chat.toStreamTextOptions()` now emits the system prompt as a cacheable message when you opt in, so a large, stable system block is billed at cache-read rates on every turn instead of full price. ([#3952](https://github.com/triggerdotdev/trigger.dev/pull/3952)) ```ts @@ -51,7 +341,9 @@ // provider-agnostic equivalent chat.toStreamTextOptions({ - systemProviderOptions: { anthropic: { cacheControl: { type: "ephemeral" } } }, + systemProviderOptions: { + anthropic: { cacheControl: { type: "ephemeral" } }, + }, }); // or where the prompt is defined @@ -62,8 +354,112 @@ Without an option, `system` stays a plain string. Pairs with a `prepareMessages` cache breakpoint to cache the conversation prefix across turns too. +- `useTriggerChatTransport` now recovers when restored session state points at a session that no longer exists in the current environment ([#3816](https://github.com/triggerdotdev/trigger.dev/pull/3816)) +- Fix two `chat.createSession()` bugs: stopping a generation no longer wedges the run (the turn loop raced a `totalUsage` promise that never settles after a stop-abort), and continuation runs now wait for the next message instead of invoking the model with an empty prompt. ([#3920](https://github.com/triggerdotdev/trigger.dev/pull/3920)) - Three fixes for custom agent loops (`chat.customAgent`, `chat.createSession`, and hand-rolled `MessageAccumulator` loops): ([#3936](https://github.com/triggerdotdev/trigger.dev/pull/3936)) + - Continuation runs no longer replay already-answered user messages into the first turn. The `.in` resume cursor is now seeded before any listener attaches (the same boot logic `chat.agent` uses), so a chat that continues after a cancel, crash, or upgrade only sees genuinely new messages. + - Steering a hand-rolled loop mid-stream no longer wipes the in-flight assistant response. `chat.pipeAndCapture` now stamps a server-generated message id on the stream, so a `prepareStep` injection keeps the partial text instead of replacing the message. + - Task-backed tools (`ai.toolExecute`) now work from custom agent loops: the parent's session is threaded to the child run, so child tasks can stream progress into the chat with `chat.stream.writer({ target: "root" })` instead of failing with "session handle is not initialized". + +- Offload large trigger payloads to object storage before sending the trigger API request. The SDK uploads packets at or above the existing 128KB limit and sends an `application/store` pointer instead of embedding large JSON in the request body. `TriggerTaskRequestBody` now validates that `application/store` payloads are non-empty storage paths. ([#3785](https://github.com/triggerdotdev/trigger.dev/pull/3785)) + + Payload uploads use the same resolved `ApiClient` as the trigger call (including `requestOptions.clientConfig`), not only the global `apiClientManager.client` — so custom `baseURL`, access token, and preview branch apply to both presign and trigger. + +- Unit-test `chat.agent` definitions offline with `mockChatAgent` from `@trigger.dev/sdk/ai/test`. Drives a real agent's turn loop in-process — no network, no task runtime — so you can send messages, actions, and stop signals via driver methods, inspect captured output chunks, and verify hooks fire. Pairs with `MockLanguageModelV3` from `ai/test` for model mocking. `setupLocals` lets you pre-seed `locals` (DB clients, service stubs) before `run()` starts. ([#3543](https://github.com/triggerdotdev/trigger.dev/pull/3543)) + + The broader `runInMockTaskContext` harness it's built on lives at `@trigger.dev/core/v3/test` — useful for unit-testing any task code, not just chat. +- Update the bundled OpenTelemetry packages to their latest releases (`@opentelemetry/sdk-node` 0.218.0, `@opentelemetry/core` 2.7.1, `@opentelemetry/host-metrics` 0.38.3). ([#3810](https://github.com/triggerdotdev/trigger.dev/pull/3810)) +- Add `region` to the runs list / retrieve API: filter runs by region (`runs.list({ region: "..." })` / `filter[region]=`) and read each run's executing region from the new `region` field on the response. ([#3612](https://github.com/triggerdotdev/trigger.dev/pull/3612)) +- Add `TriggerClient` for running multiple SDK clients side-by-side, each with its own auth, preview branch, and baseURL. Useful when a single process needs to trigger tasks or read runs across multiple projects, environments, or preview branches without mutating shared global state. ([#3683](https://github.com/triggerdotdev/trigger.dev/pull/3683)) + + ```ts + import { TriggerClient } from "@trigger.dev/sdk"; + + const prod = new TriggerClient({ accessToken: process.env.TRIGGER_PROD_KEY }); + const preview = new TriggerClient({ + accessToken: process.env.TRIGGER_PREVIEW_KEY, + previewBranch: "signup-flow", + }); + + await prod.tasks.trigger("send-email", payload); + await preview.runs.list({ status: ["COMPLETED"] }); + ``` + +- The agent skills installed by `trigger skills` are now namespaced with a `trigger-` prefix (e.g. `trigger-authoring-tasks`, `trigger-getting-started`) so they don't collide with unrelated skills in your coding agent's skills directory. Adds a `trigger-cost-savings` skill for auditing and reducing compute spend (right-sizing machines, `maxDuration`, batching, debounce), and `@trigger.dev/sdk` now bundles the full Trigger.dev documentation so your agent can read the complete, version-pinned reference directly from node_modules. ([#3970](https://github.com/triggerdotdev/trigger.dev/pull/3970)) +- Updated dependencies: + - `@trigger.dev/core@4.5.0` + +## 4.5.0-rc.7 + +### Patch Changes + +- `@trigger.dev/sdk` now bundles the Trigger.dev agent skills and a curated snapshot of the docs those skills reference. The skills that `trigger skills` installs into your coding agent read this content from node_modules, so the guidance your AI assistant follows is pinned to the SDK version installed in your project and stays current across upgrades instead of going stale until the next reinstall. ([#3937](https://github.com/triggerdotdev/trigger.dev/pull/3937)) +- `chat.headStart` now works with the `chat.customAgent` and `chat.createSession` backends, not only `chat.agent`. The warm step-1 response hands over to your loop the same way it does for a managed agent. ([#3963](https://github.com/triggerdotdev/trigger.dev/pull/3963)) + + In a `chat.customAgent` loop, consume the handover on turn 0: + + ```ts + const conversation = new chat.MessageAccumulator(); + const { isFinal, skipped } = await conversation.consumeHandover({ payload }); + if (skipped) return; // warm handler aborted, so exit without a turn + if (isFinal) { + await chat.writeTurnComplete(); // step 1 is the response, no streamText + } else { + const result = streamText({ + model, + messages: conversation.modelMessages, + tools, + }); + // Pass originalMessages so the handed-over tool round merges into the + // step-1 assistant instead of starting a new message. + const response = await chat.pipeAndCapture(result, { + originalMessages: conversation.uiMessages, + }); + if (response) await conversation.addResponse(response); + } + ``` + + With `chat.createSession`, the iterator surfaces it as `turn.handover`; call `turn.complete()` with no argument on a final handover. The lower-level `chat.waitForHandover()` and `accumulator.applyHandover()` are also exported for hand-rolled loops. + +- Add `triggerConfig` support to `chat.headStart()` and `chat.openSession()`, so the auto-triggered handover-prepare run inherits tags, queue, machine, and other session trigger options the same way `chat.createStartSessionAction()` does. The `chat:{chatId}` tag is prepended automatically. ([#3963](https://github.com/triggerdotdev/trigger.dev/pull/3963)) + + ```ts + export const POST = chat.headStart({ + agentId: "my-agent", + triggerConfig: { tags: ["org:acme"], queue: "chat" }, + run: async ({ chat }) => + streamText({ ...chat.toStreamTextOptions(), model }), + }); + ``` + + Because the session is created once on the first head-start turn and is idempotent on the chat id, this is the only place to set those options for a head-start chat's lifetime. `chat.createStartSessionAction()` now also forwards `maxDuration`, `region`, and `lockToVersion` so both session entry points stay consistent. + +- Cache your chat agent's system prompt with Anthropic prompt caching. `chat.toStreamTextOptions()` now emits the system prompt as a cacheable message when you opt in, so a large, stable system block is billed at cache-read rates on every turn instead of full price. ([#3952](https://github.com/triggerdotdev/trigger.dev/pull/3952)) + + ```ts + // at the streamText call site (Anthropic sugar) + streamText({ + ...chat.toStreamTextOptions({ cacheControl: { type: "ephemeral" } }), + messages, + }); + + // provider-agnostic equivalent + chat.toStreamTextOptions({ + systemProviderOptions: { + anthropic: { cacheControl: { type: "ephemeral" } }, + }, + }); + + // or where the prompt is defined + chat.prompt.set(SYSTEM_PROMPT, { + providerOptions: { anthropic: { cacheControl: { type: "ephemeral" } } }, + }); + ``` + + Without an option, `system` stays a plain string. Pairs with a `prepareMessages` cache breakpoint to cache the conversation prefix across turns too. + +- Three fixes for custom agent loops (`chat.customAgent`, `chat.createSession`, and hand-rolled `MessageAccumulator` loops): ([#3936](https://github.com/triggerdotdev/trigger.dev/pull/3936)) - Continuation runs no longer replay already-answered user messages into the first turn. The `.in` resume cursor is now seeded before any listener attaches (the same boot logic `chat.agent` uses), so a chat that continues after a cancel, crash, or upgrade only sees genuinely new messages. - Steering a hand-rolled loop mid-stream no longer wipes the in-flight assistant response. `chat.pipeAndCapture` now stamps a server-generated message id on the stream, so a `prepareStep` injection keeps the partial text instead of replacing the message. - Task-backed tools (`ai.toolExecute`) now work from custom agent loops: the parent's session is threaded to the child run, so child tasks can stream progress into the chat with `chat.stream.writer({ target: "root" })` instead of failing with "session handle is not initialized". @@ -129,7 +525,6 @@ ### Patch Changes - Fix `chat.agent` HITL continuations on reasoning-heavy turns. Two changes that work together: ([#3719](https://github.com/triggerdotdev/trigger.dev/pull/3719)) - - The per-turn merge now overlays the wire copy's tool-part state advancement onto the agent's existing chain — `state` + the matching resolution field (`output` / `errorText` / `approval`) come from the wire, everything else (text, reasoning, tool `input`, provider metadata) stays whatever the snapshot or `hydrateMessages` returned. Previously a full-message replace overwrote those fields with whatever the client shipped, so a slimmed wire copy landed a tool call with no `arguments` on the next LLM call. Covers `output-available` / `output-error` (HITL `addToolOutput`) and `approval-responded` / `output-denied` (approval flow). - `TriggerChatTransport.sendMessages` and `AgentChat.sendRaw` now slim assistant messages that carry advanced tool parts. The wire payload is just `{ id, role, parts: [] }` for `submit-message` continuations; everything else passes through. Reasoning blobs and full tool inputs no longer ride the wire on every `addToolOutput` / `addToolApproveResponse`, so continuation payloads stay well under the `.in/append` cap on long agent loops. @@ -145,7 +540,10 @@ const record = await db.chat.findUnique({ where: { id: chatId } }); const stored = record?.messages ?? []; if (upsertIncomingMessage(stored, { trigger, incomingMessages })) { - await db.chat.update({ where: { id: chatId }, data: { messages: stored } }); + await db.chat.update({ + where: { id: chatId }, + data: { messages: stored }, + }); } return stored; }, @@ -223,7 +621,6 @@ ``` **What you get:** - - **Code-defined, deploy-versioned templates** — define with `prompts.define({ id, model, config, variables, content })`. Every deploy creates a new version visible in the dashboard. Mustache-style placeholders (`{{var}}`, `{{#cond}}...{{/cond}}`) with Zod / ArkType / Valibot-typed variables. - **Dashboard overrides** — change a prompt's text or model from the dashboard without redeploying. Overrides take priority over the deployed "current" version and are environment-scoped (dev / staging / production independent). - **Resolve API** — `prompt.resolve(vars, { version?, label? })` returns the compiled `text`, resolved `model`, `version`, and labels. Standalone `prompts.resolve(slug, vars)` for cross-file resolution with full type inference on slug and variable shape. @@ -237,12 +634,16 @@ - Adds `onBoot` to `chat.agent` — a lifecycle hook that fires once per worker process picking up the chat. Runs for the initial run, preloaded runs, AND reactive continuation runs (post-cancel, crash, `endRun`, `requestUpgrade`, OOM retry), before any other hook. Use it to initialize `chat.local`, open per-process resources, or re-hydrate state from your DB on continuation — anywhere the SAME run picking up after suspend/resume isn't enough. ([#3543](https://github.com/triggerdotdev/trigger.dev/pull/3543)) ```ts - const userContext = chat.local<{ name: string; plan: string }>({ id: "userContext" }); + const userContext = chat.local<{ name: string; plan: string }>({ + id: "userContext", + }); export const myChat = chat.agent({ id: "my-chat", onBoot: async ({ clientData, continuation }) => { - const user = await db.user.findUnique({ where: { id: clientData.userId } }); + const user = await db.user.findUnique({ + where: { id: clientData.userId }, + }); userContext.init({ name: user.name, plan: user.plan }); }, run: async ({ messages, signal }) => @@ -270,12 +671,15 @@ import { useChat } from "@ai-sdk/react"; import { useTriggerChatTransport } from "@trigger.dev/sdk/chat/react"; - const transport = useTriggerChatTransport({ task: "my-chat", accessToken, startSession }); + const transport = useTriggerChatTransport({ + task: "my-chat", + accessToken, + startSession, + }); const { messages, sendMessage } = useChat({ transport }); ``` **What you get:** - - **AI SDK `useChat` integration** — a custom [`ChatTransport`](https://sdk.vercel.ai/docs/ai-sdk-ui/transport) (`useTriggerChatTransport`) plugs straight into Vercel AI SDK's `useChat` hook. Text streaming, tool calls, reasoning, and `data-*` parts all work natively over Trigger.dev's realtime streams. No custom API routes needed. - **First-turn fast path (`chat.headStart`)** — opt-in handler that runs the first turn's `streamText` step in your warm server process while the agent run boots in parallel, cutting cold-start TTFC by roughly half (measured 2801ms → 1218ms on `claude-sonnet-4-6`). The agent owns step 2+ (tool execution, persistence, hooks) so heavy deps stay where they belong. Web Fetch handler works natively in Next.js, Hono, SvelteKit, Remix, Workers, etc.; bridge to Express/Fastify/Koa via `chat.toNodeListener`. New `@trigger.dev/sdk/chat-server` subpath. - **Multi-turn durability via Sessions** — every chat is backed by a durable Session that outlives any individual run. Conversations resume across page refreshes, idle timeout, crashes, and deploys; `resume: true` reconnects via `lastEventId` so clients only see new chunks. `sessions.list` enumerates chats for inbox-style UIs. @@ -305,7 +709,11 @@ onTurnComplete: async ({ responseMessage }) => { const newResults = chat.history.extractNewToolResults(responseMessage); for (const r of newResults) { - await db.toolResults.upsert({ id: r.toolCallId, output: r.output, errorText: r.errorText }); + await db.toolResults.upsert({ + id: r.toolCallId, + output: r.output, + errorText: r.errorText, + }); } }; ``` @@ -316,16 +724,23 @@ import { sessions, tasks } from "@trigger.dev/sdk"; // Trigger a task and subscribe to its session output in one call - const { runId, stream } = await tasks.triggerAndSubscribe("my-task", payload, { - externalId: "user-456", - }); + const { runId, stream } = await tasks.triggerAndSubscribe( + "my-task", + payload, + { + externalId: "user-456", + }, + ); for await (const chunk of stream) { // ... } // Enumerate existing sessions (powers inbox-style UIs without a separate index) - for await (const s of sessions.list({ type: "chat.agent", tag: "user:user-456" })) { + for await (const s of sessions.list({ + type: "chat.agent", + tag: "user:user-456", + })) { console.log(s.id, s.externalId, s.createdAt, s.closedAt); } ``` @@ -337,7 +752,10 @@ - Add Agent Skills for `chat.agent`. Drop a folder with a `SKILL.md` and any helper scripts/references next to your task code, register it with `skills.define({ id, path })`, and the CLI bundles it into the deploy image automatically — no `trigger.config.ts` changes. The agent gets a one-line summary in its system prompt and discovers full instructions on demand via `loadSkill`, with `bash` and `readFile` tools scoped per-skill (path-traversal guards, output caps, abort-signal propagation). ([#3543](https://github.com/triggerdotdev/trigger.dev/pull/3543)) ```ts - const pdfSkill = skills.define({ id: "pdf-extract", path: "./skills/pdf-extract" }); + const pdfSkill = skills.define({ + id: "pdf-extract", + path: "./skills/pdf-extract", + }); chat.skills.set([await pdfSkill.local()]); ``` @@ -363,12 +781,14 @@ import { chat } from "@trigger.dev/sdk/ai"; import type { myChat } from "@/trigger/chat"; - export const startChatSession = chat.createStartSessionAction("my-chat"); + export const startChatSession = + chat.createStartSessionAction("my-chat"); // In the browser, threaded from the transport's typed startSession callback: const transport = useTriggerChatTransport({ task: "my-chat", - startSession: ({ chatId, clientData }) => startChatSession({ chatId, clientData }), + startSession: ({ chatId, clientData }) => + startChatSession({ chatId, clientData }), // ... }); ``` @@ -446,12 +866,14 @@ import type { QueryTable } from "@trigger.dev/sdk"; // Basic untyped query - const result = await query.execute("SELECT run_id, status FROM runs LIMIT 10"); + const result = await query.execute( + "SELECT run_id, status FROM runs LIMIT 10", + ); // Type-safe query using QueryTable to pick specific columns - const typedResult = await query.execute>( - "SELECT run_id, status, triggered_at FROM runs LIMIT 10" - ); + const typedResult = await query.execute< + QueryTable<"runs", "run_id" | "status" | "triggered_at"> + >("SELECT run_id, status, triggered_at FROM runs LIMIT 10"); typedResult.results.forEach((row) => { console.log(row.run_id, row.status); // Fully typed }); @@ -459,7 +881,7 @@ // Aggregation query with inline types const stats = await query.execute<{ status: string; count: number }>( "SELECT status, COUNT(*) as count FROM runs GROUP BY status", - { scope: "project", period: "30d" } + { scope: "project", period: "30d" }, ); // CSV export @@ -495,7 +917,6 @@ ### Patch Changes - Add support for AI SDK v6 (Vercel AI SDK) ([#2919](https://github.com/triggerdotdev/trigger.dev/pull/2919)) - - Updated peer dependency to allow `ai@^6.0.0` alongside v4 and v5 - Updated internal code to handle async validation from AI SDK v6's Schema type @@ -575,7 +996,7 @@ tasks.onStartAttempt(({ ctx, payload, task }) => { console.log( `Run ${ctx.run.id} started on task ${task} attempt ${ctx.run.attempt.number}`, - ctx.run + ctx.run, ); }); ``` @@ -729,13 +1150,16 @@ // Now context.active() refers to your external trace context propagation.inject(context.active(), headersObject); - const result = await fetch("http://localhost:3000/api/demo-call-from-trigger", { - headers: new Headers(headersObject), - method: "POST", - body: JSON.stringify({ - message: "Hello from Trigger.dev", - }), - }); + const result = await fetch( + "http://localhost:3000/api/demo-call-from-trigger", + { + headers: new Headers(headersObject), + method: "POST", + body: JSON.stringify({ + message: "Hello from Trigger.dev", + }), + }, + ); return result.json(); }); @@ -752,7 +1176,6 @@ - Add jsonSchema support when indexing tasks ([#2353](https://github.com/triggerdotdev/trigger.dev/pull/2353)) - Fixed an issue with realtime streams that timeout and resume streaming dropping chunks ([#1993](https://github.com/triggerdotdev/trigger.dev/pull/1993)) - Added and cleaned up the run ctx param: ([#2322](https://github.com/triggerdotdev/trigger.dev/pull/2322)) - - New optional properties `ctx.run.parentTaskRunId` and `ctx.run.rootTaskRunId` reference the current run's root/parent ID. - Removed deprecated properties from `ctx` - Added a new `ctx.deployment` object that contains information about the deployment associated with the run. @@ -771,14 +1194,12 @@ - Deprecate toolTask and replace with `ai.tool(mySchemaTask)` ([#1863](https://github.com/triggerdotdev/trigger.dev/pull/1863)) - Display clickable links in Cursor terminal ([#1998](https://github.com/triggerdotdev/trigger.dev/pull/1998)) - Removes the `releaseConcurrencyOnWaitpoint` option on queues and the `releaseConcurrency` option on various wait functions. Replaced with the following default behavior: ([#2284](https://github.com/triggerdotdev/trigger.dev/pull/2284)) - - Concurrency is never released when a run is first blocked via a waitpoint, at either the env or queue level. - Concurrency is always released when a run is checkpointed and shutdown, at both the env and queue level. Additionally, environment concurrency limits now have a new "Burst Factor", defaulting to 2.0x. The "Burst Factor" allows the environment-wide concurrency limit to be higher than any individual queue's concurrency limit. For example, if you have an environment concurrency limit of 100, and a Burst Factor of 2.0x, then you can execute up to 200 runs concurrently, but any one task/queue can still only execute 100 runs concurrently. We've done some work cleaning up the run statuses. The new statuses are: - - `PENDING_VERSION`: Task is waiting for a version update because it cannot execute without additional information (task, queue, etc.) - `QUEUED`: Task is waiting to be executed by a worker - `DEQUEUED`: Task has been dequeued and is being sent to a worker to start executing. @@ -794,14 +1215,12 @@ - `TIMED_OUT`: Task has reached it's maxDuration and has been stopped We've removed the following statuses: - - `WAITING_FOR_DEPLOY`: This is no longer used, and is replaced by `PENDING_VERSION` - `FROZEN`: This is no longer used, and is replaced by `WAITING` - `INTERRUPTED`: This is no longer used - `REATTEMPTING`: This is no longer used, and is replaced by `EXECUTING` We've also added "boolean" helpers to runs returned via the API and from Realtime: - - `isQueued`: Returns true when the status is `QUEUED`, `PENDING_VERSION`, or `DELAYED` - `isExecuting`: Returns true when the status is `EXECUTING`, `DEQUEUED`. These count against your concurrency limits. - `isWaiting`: Returns true when the status is `WAITING`. These do not count against your concurrency limits. @@ -903,13 +1322,16 @@ // Now context.active() refers to your external trace context propagation.inject(context.active(), headersObject); - const result = await fetch("http://localhost:3000/api/demo-call-from-trigger", { - headers: new Headers(headersObject), - method: "POST", - body: JSON.stringify({ - message: "Hello from Trigger.dev", - }), - }); + const result = await fetch( + "http://localhost:3000/api/demo-call-from-trigger", + { + headers: new Headers(headersObject), + method: "POST", + body: JSON.stringify({ + message: "Hello from Trigger.dev", + }), + }, + ); return result.json(); }); @@ -936,7 +1358,6 @@ - fix: importing from runEngine/index.js breaks non-node runtimes ([#2328](https://github.com/triggerdotdev/trigger.dev/pull/2328)) - Added and cleaned up the run ctx param: ([#2322](https://github.com/triggerdotdev/trigger.dev/pull/2322)) - - New optional properties `ctx.run.parentTaskRunId` and `ctx.run.rootTaskRunId` reference the current run's root/parent ID. - Removed deprecated properties from `ctx` - Added a new `ctx.deployment` object that contains information about the deployment associated with the run. @@ -965,14 +1386,12 @@ ### Patch Changes - Removes the `releaseConcurrencyOnWaitpoint` option on queues and the `releaseConcurrency` option on various wait functions. Replaced with the following default behavior: ([#2284](https://github.com/triggerdotdev/trigger.dev/pull/2284)) - - Concurrency is never released when a run is first blocked via a waitpoint, at either the env or queue level. - Concurrency is always released when a run is checkpointed and shutdown, at both the env and queue level. Additionally, environment concurrency limits now have a new "Burst Factor", defaulting to 2.0x. The "Burst Factor" allows the environment-wide concurrency limit to be higher than any individual queue's concurrency limit. For example, if you have an environment concurrency limit of 100, and a Burst Factor of 2.0x, then you can execute up to 200 runs concurrently, but any one task/queue can still only execute 100 runs concurrently. We've done some work cleaning up the run statuses. The new statuses are: - - `PENDING_VERSION`: Task is waiting for a version update because it cannot execute without additional information (task, queue, etc.) - `QUEUED`: Task is waiting to be executed by a worker - `DEQUEUED`: Task has been dequeued and is being sent to a worker to start executing. @@ -988,14 +1407,12 @@ - `TIMED_OUT`: Task has reached it's maxDuration and has been stopped We've removed the following statuses: - - `WAITING_FOR_DEPLOY`: This is no longer used, and is replaced by `PENDING_VERSION` - `FROZEN`: This is no longer used, and is replaced by `WAITING` - `INTERRUPTED`: This is no longer used - `REATTEMPTING`: This is no longer used, and is replaced by `EXECUTING` We've also added "boolean" helpers to runs returned via the API and from Realtime: - - `isQueued`: Returns true when the status is `QUEUED`, `PENDING_VERSION`, or `DELAYED` - `isExecuting`: Returns true when the status is `EXECUTING`, `DEQUEUED`. These count against your concurrency limits. - `isWaiting`: Returns true when the status is `WAITING`. These do not count against your concurrency limits. @@ -1213,11 +1630,13 @@ The main change is that there's now an SDK function to verify and parse them (similar to Stripe SDK). ```ts - const event = await webhooks.constructEvent(request, process.env.ALERT_WEBHOOK_SECRET!); + const event = await webhooks.constructEvent( + request, + process.env.ALERT_WEBHOOK_SECRET!, + ); ``` If the signature you provide matches the one from the dashboard when you create the webhook, you will get a nicely typed object back for these three types: - - "alert.run.failed" - "alert.deployment.success" - "alert.deployment.failed" @@ -1267,8 +1686,14 @@ await childTask.trigger({ message: "Hello, world!" }); // This will override the task's machine preset and any defaults. Works with all trigger functions. - await childTask.trigger({ message: "Hello, world!" }, { machine: "small-2x" }); - await childTask.triggerAndWait({ message: "Hello, world!" }, { machine: "small-2x" }); + await childTask.trigger( + { message: "Hello, world!" }, + { machine: "small-2x" }, + ); + await childTask.triggerAndWait( + { message: "Hello, world!" }, + { machine: "small-2x" }, + ); await childTask.batchTrigger([ { payload: { message: "Hello, world!" }, options: { machine: "micro" } }, @@ -1282,7 +1707,7 @@ await tasks.trigger( "child", { message: "Hello, world!" }, - { machine: "small-2x" } + { machine: "small-2x" }, ); await tasks.batchTrigger("child", [ { payload: { message: "Hello, world!" }, options: { machine: "micro" } }, @@ -1379,7 +1804,6 @@ ### Minor Changes - Improved Batch Triggering: ([#1502](https://github.com/triggerdotdev/trigger.dev/pull/1502)) - - The new Batch Trigger endpoint is now asynchronous and supports up to 500 runs per request. - The new endpoint also supports triggering multiple different tasks in a single batch request (support in the SDK coming soon). - The existing `batchTrigger` method now supports the new endpoint, and shouldn't require any changes to your code. @@ -1393,14 +1817,19 @@ }); // Works for individual items as well: await myTask.batchTrigger([ - { payload: { foo: "bar" }, options: { idempotencyKey: "my-key", idempotencyKeyTTL: "60s" } }, + { + payload: { foo: "bar" }, + options: { idempotencyKey: "my-key", idempotencyKeyTTL: "60s" }, + }, ]); // And `trigger`: - await myTask.trigger({ foo: "bar" }, { idempotencyKey: "my-key", idempotencyKeyTTL: "60s" }); + await myTask.trigger( + { foo: "bar" }, + { idempotencyKey: "my-key", idempotencyKeyTTL: "60s" }, + ); ``` ### Breaking Changes - - We've removed the `idempotencyKey` option from `triggerAndWait` and `batchTriggerAndWait`, because it can lead to permanently frozen runs in deployed tasks. We're working on upgrading our entire system to support idempotency keys on these methods, and we'll re-add the option once that's complete. ### Patch Changes @@ -1675,7 +2104,10 @@ Before: ```ts - await yourTask.trigger({ payload: { foo: "bar" }, options: { idempotencyKey: "key_1234" } }); + await yourTask.trigger({ + payload: { foo: "bar" }, + options: { idempotencyKey: "key_1234" }, + }); await yourTask.triggerAndWait({ payload: { foo: "bar" }, options: { idempotencyKey: "key_1234" }, @@ -1695,8 +2127,14 @@ await yourTask.trigger({ foo: "bar" }, { idempotencyKey: "key_1234" }); await yourTask.triggerAndWait({ foo: "bar" }, { idempotencyKey: "key_1234" }); - await yourTask.batchTrigger([{ payload: { foo: "bar" } }, { payload: { foo: "baz" } }]); - await yourTask.batchTriggerAndWait([{ payload: { foo: "bar" } }, { payload: { foo: "baz" } }]); + await yourTask.batchTrigger([ + { payload: { foo: "bar" } }, + { payload: { foo: "baz" } }, + ]); + await yourTask.batchTriggerAndWait([ + { payload: { foo: "bar" } }, + { payload: { foo: "baz" } }, + ]); ``` We've also changed the API of the `triggerAndWait` result. Before, if the subtask that was triggered finished with an error, we would automatically "rethrow" the error in the parent task. @@ -1729,7 +2167,6 @@ ``` - 26093896d: When using idempotency keys, triggerAndWait and batchTriggerAndWait will still work even if the existing runs have already been completed (or even partially completed, in the case of batchTriggerAndWait) - - TaskRunExecutionResult.id is now the run friendlyId, not the attempt friendlyId - A single TaskRun can now have many batchItems, in the case of batchTriggerAndWait while using idempotency keys - A run’s idempotencyKey is now added to the ctx as well as the TaskEvent and displayed in the span view @@ -2204,7 +2641,10 @@ Before: ```ts - await yourTask.trigger({ payload: { foo: "bar" }, options: { idempotencyKey: "key_1234" } }); + await yourTask.trigger({ + payload: { foo: "bar" }, + options: { idempotencyKey: "key_1234" }, + }); await yourTask.triggerAndWait({ payload: { foo: "bar" }, options: { idempotencyKey: "key_1234" }, @@ -2224,8 +2664,14 @@ await yourTask.trigger({ foo: "bar" }, { idempotencyKey: "key_1234" }); await yourTask.triggerAndWait({ foo: "bar" }, { idempotencyKey: "key_1234" }); - await yourTask.batchTrigger([{ payload: { foo: "bar" } }, { payload: { foo: "baz" } }]); - await yourTask.batchTriggerAndWait([{ payload: { foo: "bar" } }, { payload: { foo: "baz" } }]); + await yourTask.batchTrigger([ + { payload: { foo: "bar" } }, + { payload: { foo: "baz" } }, + ]); + await yourTask.batchTriggerAndWait([ + { payload: { foo: "bar" } }, + { payload: { foo: "baz" } }, + ]); ``` We've also changed the API of the `triggerAndWait` result. Before, if the subtask that was triggered finished with an error, we would automatically "rethrow" the error in the parent task. @@ -2258,7 +2704,6 @@ ``` - 26093896d: When using idempotency keys, triggerAndWait and batchTriggerAndWait will still work even if the existing runs have already been completed (or even partially completed, in the case of batchTriggerAndWait) - - TaskRunExecutionResult.id is now the run friendlyId, not the attempt friendlyId - A single TaskRun can now have many batchItems, in the case of batchTriggerAndWait while using idempotency keys - A run’s idempotencyKey is now added to the ctx as well as the TaskEvent and displayed in the span view diff --git a/packages/trigger-sdk/package.json b/packages/trigger-sdk/package.json index 87591c52420..32566bf6ec4 100644 --- a/packages/trigger-sdk/package.json +++ b/packages/trigger-sdk/package.json @@ -1,6 +1,6 @@ { "name": "@trigger.dev/sdk", - "version": "4.5.0-rc.7", + "version": "4.5.0", "description": "trigger.dev Node.JS SDK", "license": "MIT", "publishConfig": { @@ -77,7 +77,7 @@ "dependencies": { "@opentelemetry/api": "1.9.1", "@opentelemetry/semantic-conventions": "1.41.1", - "@trigger.dev/core": "workspace:4.5.0-rc.7", + "@trigger.dev/core": "workspace:4.5.0", "chalk": "^5.2.0", "cronstrue": "^2.21.0", "debug": "^4.3.4", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 8815e9e2689..1708fb773d7 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -1572,7 +1572,7 @@ importers: specifier: ^6.10.0 version: 6.19.0(magicast@0.3.5) '@trigger.dev/core': - specifier: workspace:4.5.0-rc.7 + specifier: workspace:4.5.0 version: link:../core mlly: specifier: ^1.7.1 @@ -1648,13 +1648,13 @@ importers: specifier: ^0.22.10 version: 0.22.10(supports-color@10.0.0) '@trigger.dev/build': - specifier: workspace:4.5.0-rc.7 + specifier: workspace:4.5.0 version: link:../build '@trigger.dev/core': - specifier: workspace:4.5.0-rc.7 + specifier: workspace:4.5.0 version: link:../core '@trigger.dev/schema-to-json': - specifier: workspace:4.5.0-rc.7 + specifier: workspace:4.5.0 version: link:../schema-to-json ansi-escapes: specifier: ^7.0.0 @@ -2047,7 +2047,7 @@ importers: packages/python: dependencies: '@trigger.dev/core': - specifier: workspace:4.5.0-rc.7 + specifier: workspace:4.5.0 version: link:../core tinyexec: specifier: ^0.3.2 @@ -2057,10 +2057,10 @@ importers: specifier: ^0.15.4 version: 0.15.4 '@trigger.dev/build': - specifier: workspace:4.5.0-rc.7 + specifier: workspace:4.5.0 version: link:../build '@trigger.dev/sdk': - specifier: workspace:4.5.0-rc.7 + specifier: workspace:4.5.0 version: link:../trigger-sdk '@types/node': specifier: 20.14.14 @@ -2084,7 +2084,7 @@ importers: packages/react-hooks: dependencies: '@trigger.dev/core': - specifier: workspace:^4.5.0-rc.7 + specifier: workspace:^4.5.0 version: link:../core react: specifier: 18.3.1 @@ -2118,7 +2118,7 @@ importers: packages/redis-worker: dependencies: '@trigger.dev/core': - specifier: workspace:4.5.0-rc.7 + specifier: workspace:4.5.0 version: link:../core cron-parser: specifier: ^4.9.0 @@ -2167,7 +2167,7 @@ importers: packages/rsc: dependencies: '@trigger.dev/core': - specifier: workspace:^4.5.0-rc.7 + specifier: workspace:^4.5.0 version: link:../core mlly: specifier: ^1.7.1 @@ -2183,7 +2183,7 @@ importers: specifier: ^0.15.4 version: 0.15.4 '@trigger.dev/build': - specifier: workspace:^4.5.0-rc.7 + specifier: workspace:^4.5.0 version: link:../build '@types/node': specifier: 20.14.14 @@ -2262,7 +2262,7 @@ importers: specifier: 1.41.1 version: 1.41.1 '@trigger.dev/core': - specifier: workspace:4.5.0-rc.7 + specifier: workspace:4.5.0 version: link:../core chalk: specifier: ^5.2.0