Skip to content

Commit d2b6250

Browse files
authored
Merge branch 'main' into samejr/org-menu-update
2 parents 1af8efb + e2d3b83 commit d2b6250

114 files changed

Lines changed: 1367 additions & 634 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
---
2+
"@trigger.dev/sdk": patch
3+
---
4+
5+
Custom chat agent loops get two ergonomic wins for owning the turn loop.
6+
7+
`chat.writeTurnComplete()` now returns the turn boundary's resume cursors (`lastEventId` for the output stream and `sessionInEventId` for the input stream), so you can persist them straight from the task instead of round-tripping them back from the client.
8+
9+
```ts
10+
const { lastEventId, sessionInEventId } = await chat.writeTurnComplete();
11+
await db.chats.update(chatId, { lastEventId, sessionInEventId });
12+
```
13+
14+
`chat.pipeAndCapture()` no longer throws when a stream is stopped or fails. It now returns a `PipeAndCaptureResult` whose `message` holds any partial output captured before the stop or failure, alongside a typed `status` (`"complete" | "aborted" | "error"`) and, on failure, the `error`. Read the message off the result:
15+
16+
```ts
17+
const { message, status, error } = await chat.pipeAndCapture(result, { signal });
18+
if (message) conversation.addResponse(message);
19+
if (status === "error") logger.error("turn failed", { error });
20+
```
21+
22+
Note: `pipeAndCapture` previously resolved to `UIMessage | undefined`. Update call sites to read `.message` from the returned result.
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"@trigger.dev/sdk": patch
3+
---
4+
5+
Suppress a build-time warning that could appear in Vite-based projects when the optional `@ai-sdk/otel` package is not installed.

.configs/tsconfig.base.json

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,6 @@
2626
"esModuleInterop": true,
2727
"emitDecoratorMetadata": false,
2828
"experimentalDecorators": false,
29-
"downlevelIteration": true,
3029
"isolatedModules": true,
3130
"noUncheckedIndexedAccess": true,
3231

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
---
2+
area: webapp
3+
type: fix
4+
---
5+
6+
Prevent duplicate Staging and Preview environments when account setup requests overlap

apps/webapp/app/components/StaleAssetRecovery.tsx

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
1-
// Recovers from a rolling deploy rotating the content-hashed /build assets out from
1+
// Recovers from a rolling deploy rotating the content-hashed /assets files out from
22
// under a page. Each image serves only its own build and hard-404s unknown hashes, so
33
// a client can request a hash the serving replica doesn't have and get missing styles
4-
// or a failed asset load. On such a /build load failure we do a bounded full document
4+
// or a failed asset load. On such an asset load failure we do a bounded full document
55
// reload: the fresh document (and, under sticky routing, all of its assets) lands on a
66
// single live build, so the asset resolves. Bounded via sessionStorage so it can never
77
// loop; when the budget is spent it stops rather than reloading forever.
@@ -39,7 +39,7 @@ export function staleAssetRecoveryScript() {
3939
}
4040

4141
function recover() {
42-
// One recovery per page: a broken load fails several /build assets at once and each
42+
// One recovery per page: a broken load fails several hashed assets at once and each
4343
// fires its own error event before location.reload() commits — without this guard a
4444
// single incident would burn the entire reload budget.
4545
if (recovering) return;
@@ -62,7 +62,9 @@ export function staleAssetRecoveryScript() {
6262
: el.tagName === "SCRIPT"
6363
? (el as HTMLScriptElement).src
6464
: null;
65-
if (url && url.indexOf("/build/") !== -1) recover();
65+
// Match the pathname, not the full URL — a query string or third-party
66+
// URL containing /assets/ must not burn the reload budget.
67+
if (url && new URL(url, location.href).pathname.indexOf("/assets/") !== -1) recover();
6668
},
6769
true
6870
);

apps/webapp/app/components/primitives/Avatar.tsx

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,6 @@ import {
1010
} from "@heroicons/react/20/solid";
1111
import type { Prisma } from "@trigger.dev/database";
1212
import { z } from "zod";
13-
import { logger } from "~/services/logger.server";
1413
import { cn } from "~/utils/cn";
1514

1615
export const AvatarType = z.enum(["icon", "letters", "image"]);
@@ -45,7 +44,7 @@ export function parseAvatar(json: Prisma.JsonValue, defaultAvatar: Avatar): Avat
4544
const parsed = AvatarData.safeParse(json);
4645

4746
if (!parsed.success) {
48-
logger.error("Invalid org avatar", { json, error: parsed.error });
47+
console.error("Invalid org avatar", { json, error: parsed.error });
4948
return defaultAvatar;
5049
}
5150

apps/webapp/app/presenters/v3/UsagePresenter.server.ts

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,8 @@
11
import type { DataPoint } from "regression";
2-
import { linear } from "regression";
2+
// Default-import: regression is CJS and its named exports aren't statically
3+
// analyzable under ESM interop.
4+
import regression from "regression";
5+
const { linear } = regression;
36
import type { PrismaClientOrTransaction } from "~/db.server";
47
import { env } from "~/env.server";
58
import { clickhouseFactory } from "~/services/clickhouse/clickhouseFactoryInstance.server";

apps/webapp/app/root.tsx

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,14 @@
11
import type { LinksFunction, LoaderFunctionArgs, MetaFunction } from "@remix-run/node";
22
import type { ShouldRevalidateFunction } from "@remix-run/react";
3-
import { Links, LiveReload, Meta, Outlet, Scripts, ScrollRestoration } from "@remix-run/react";
3+
import { Links, Meta, Outlet, Scripts, ScrollRestoration } from "@remix-run/react";
44
import { type UseDataFunctionReturn, typedjson, useTypedLoaderData } from "remix-typedjson";
55
import { ExternalScripts } from "remix-utils/external-scripts";
66
import type { ToastMessage } from "~/models/message.server";
77
import { commitSession, getSession } from "~/models/message.server";
8-
import tailwindStylesheetUrl from "~/tailwind.css";
8+
// Fonts imported here so Vite rebases the urls and emits the woff2 assets
9+
import "non.geist";
10+
import "non.geist/mono";
11+
import tailwindStylesheetUrl from "~/tailwind.css?url";
912
import { RouteErrorDisplay } from "./components/ErrorDisplay";
1013
import { GlobalShortcuts } from "./components/GlobalShortcuts";
1114
import { StaleAssetRecovery } from "./components/StaleAssetRecovery";
@@ -147,7 +150,6 @@ export default function App() {
147150
<ScrollRestoration />
148151
<ExternalScripts />
149152
<Scripts />
150-
<LiveReload />
151153
</body>
152154
</html>
153155
</>

apps/webapp/app/routes/admin.api.v1.orgs.$organizationId.environments.staging.ts

Lines changed: 28 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import { type ActionFunctionArgs, json } from "@remix-run/server-runtime";
22
import {
3+
Prisma,
34
type RuntimeEnvironment,
45
type Organization,
56
type Project,
@@ -61,9 +62,16 @@ async function upsertEnvironment(
6162
type: RuntimeEnvironmentType,
6263
isBranchableEnvironment: boolean
6364
) {
64-
const existingEnvironment = project.environments.find((env) => env.type === type);
65+
const existingEnvironment = project.environments.find(
66+
(env) => env.type === type && env.parentEnvironmentId === null
67+
);
6568

66-
if (!existingEnvironment) {
69+
if (existingEnvironment) {
70+
await updateEnvConcurrencyLimits({ ...existingEnvironment, organization, project });
71+
return { status: "updated", environment: existingEnvironment };
72+
}
73+
74+
try {
6775
const newEnvironment = await createEnvironment({
6876
organization,
6977
project,
@@ -72,8 +80,23 @@ async function upsertEnvironment(
7280
});
7381
await updateEnvConcurrencyLimits({ ...newEnvironment, organization, project });
7482
return { status: "created", environment: newEnvironment };
75-
} else {
76-
await updateEnvConcurrencyLimits({ ...existingEnvironment, organization, project });
77-
return { status: "updated", environment: existingEnvironment };
83+
} catch (error) {
84+
if (error instanceof Prisma.PrismaClientKnownRequestError && error.code === "P2002") {
85+
const existingAfterConflict = await prisma.runtimeEnvironment.findFirst({
86+
where: {
87+
organizationId: organization.id,
88+
projectId: project.id,
89+
type,
90+
parentEnvironmentId: null,
91+
},
92+
});
93+
94+
if (existingAfterConflict) {
95+
await updateEnvConcurrencyLimits({ ...existingAfterConflict, organization, project });
96+
return { status: "updated", environment: existingAfterConflict };
97+
}
98+
}
99+
100+
throw error;
78101
}
79102
}

apps/webapp/app/routes/api.v1.errors.$errorId.ignore.ts

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ const ParamsSchema = z.object({
99
errorId: z.string(),
1010
});
1111

12-
export const { action, loader } = createActionApiRoute(
12+
const route = createActionApiRoute(
1313
{
1414
params: ParamsSchema,
1515
body: IgnoreErrorRequestBody,
@@ -56,3 +56,6 @@ export const { action, loader } = createActionApiRoute(
5656
return json(updated);
5757
}
5858
);
59+
60+
export const action = route.action;
61+
export const loader = route.loader;

0 commit comments

Comments
 (0)