diff --git a/frontend/src/app/getting-started.tsx b/frontend/src/app/getting-started.tsx
index f7e5b6dde5..ed01299276 100644
--- a/frontend/src/app/getting-started.tsx
+++ b/frontend/src/app/getting-started.tsx
@@ -1103,8 +1103,7 @@ function useOtherAgentInstructionsCode(provider?: Provider) {
}
function CopyAgentInstructionsButton({ provider }: { provider?: Provider }) {
- // FIXME: after we bring back rivet compute
- if (/*provider === "rivet"*/ false) {
+ if (provider === "rivet") {
return ;
}
return ;
@@ -1277,7 +1276,7 @@ function BackendSetupRivet() {
return (
- {/*
*/}
+
+
+
+
+
Actors
diff --git a/frontend/src/app/provider-dropdown.tsx b/frontend/src/app/provider-dropdown.tsx
index a6d23f65cf..b667b460d8 100644
--- a/frontend/src/app/provider-dropdown.tsx
+++ b/frontend/src/app/provider-dropdown.tsx
@@ -101,6 +101,7 @@ export function ProviderDropdown({ children }: { children: React.ReactNode }) {
{children}
+
{externalClouds}
diff --git a/frontend/src/components/actors/actors-actor-details.tsx b/frontend/src/components/actors/actors-actor-details.tsx
index c93cf606a5..5df22eeede 100644
--- a/frontend/src/components/actors/actors-actor-details.tsx
+++ b/frontend/src/components/actors/actors-actor-details.tsx
@@ -45,6 +45,7 @@ import {
GuardConnectableInspector,
useInspectorGuard,
} from "./guard-connectable-inspector";
+import { features } from "@/lib/features";
import type { ActorId } from "./queries";
import { ActorWorkerContextProvider } from "./worker/actor-worker-context";
import { ActorWorkflowTab } from "./workflow/actor-workflow-tab";
@@ -114,16 +115,16 @@ const TAB_PRIORITY = [
type TabId = (typeof TAB_PRIORITY)[number];
-// FIXME: once we have back rivet cloud
function useManagedPool() {
- // if (__APP_TYPE__ !== "cloud") return false;
- // const provider = useCloudNamespaceDataProvider();
- // const { data: hasManagedPool } = useSuspenseQuery(
- // provider.currentNamespaceHasManagedPoolQueryOptions(),
- // );
+ if (!features.platform) return false;
+ // biome-ignore lint/correctness/useHookAtTopLevel: guarded by build constant
+ const provider = useCloudNamespaceDataProvider();
+ // biome-ignore lint/correctness/useHookAtTopLevel: guarded by build constant
+ const { data: hasManagedPool } = useSuspenseQuery(
+ provider.currentNamespaceHasManagedPoolQueryOptions(),
+ );
- // return hasManagedPool;
- return false;
+ return hasManagedPool;
}
function useActorTabVisibility(actorId: ActorId) {
diff --git a/frontend/src/components/deployment-logs.tsx b/frontend/src/components/deployment-logs.tsx
index 74b91a4600..7f32fa150c 100644
--- a/frontend/src/components/deployment-logs.tsx
+++ b/frontend/src/components/deployment-logs.tsx
@@ -1,5 +1,5 @@
import type { RivetSse } from "@rivet-gg/cloud";
-import { faTriangleExclamation, Icon } from "@rivet-gg/icons";
+import { faArrowDown, faTriangleExclamation, Icon } from "@rivet-gg/icons";
import type { Virtualizer } from "@tanstack/react-virtual";
import { useCallback, useEffect, useRef, useState } from "react";
import { ErrorDetails } from "@/components/actors";
@@ -144,12 +144,30 @@ export function DeploymentLogs({
// Track the log count before a load-more so we can restore scroll position.
const prevLogCountRef = useRef(0);
+ // Freeze displayed logs when not following so appended entries don't shift scroll.
+ // Always update when following, and also when history is prepended (logs grew
+ // from the front, detectable because the previously-first entry moved).
+ const frozenLogsRef = useRef(logs);
+ const frozenFirstIdRef = useRef(undefined);
+ if (follow) {
+ frozenLogsRef.current = logs;
+ frozenFirstIdRef.current = logs[0]?.data.insertId;
+ } else if (
+ logs.length > 0 &&
+ logs[0]?.data.insertId !== frozenFirstIdRef.current
+ ) {
+ // First entry changed — history was prepended. Accept the update.
+ frozenLogsRef.current = logs;
+ frozenFirstIdRef.current = logs[0]?.data.insertId;
+ }
+ const displayedLogs = follow ? logs : frozenLogsRef.current;
+
// When hasMore, index 0 is the sentinel row; real logs start at index 1.
const sentinelOffset = hasMore ? 1 : 0;
- const totalCount = logs.length + sentinelOffset;
+ const totalCount = displayedLogs.length + sentinelOffset;
useEffect(() => {
- if (follow && !isLoading && virtualizerRef.current && logs.length > 0) {
+ if (follow && !isLoading && virtualizerRef.current && displayedLogs.length > 0) {
// https://github.com/TanStack/virtual/issues/537
const rafId = requestAnimationFrame(() => {
virtualizerRef.current?.scrollToIndex(totalCount - 1, {
@@ -158,7 +176,7 @@ export function DeploymentLogs({
});
return () => cancelAnimationFrame(rafId);
}
- }, [totalCount, logs.length, follow, isLoading]);
+ }, [totalCount, displayedLogs.length, follow, isLoading]);
// After prepending older history, scroll to restore the previously-first row.
const wasLoadingMoreRef = useRef(false);
@@ -166,9 +184,9 @@ export function DeploymentLogs({
if (
wasLoadingMoreRef.current &&
!isLoadingMore &&
- logs.length > prevLogCountRef.current
+ displayedLogs.length > prevLogCountRef.current
) {
- const addedCount = logs.length - prevLogCountRef.current;
+ const addedCount = displayedLogs.length - prevLogCountRef.current;
const rafId = requestAnimationFrame(() => {
// +1 to skip sentinel row at index 0.
virtualizerRef.current?.scrollToIndex(
@@ -181,7 +199,7 @@ export function DeploymentLogs({
return () => cancelAnimationFrame(rafId);
}
wasLoadingMoreRef.current = isLoadingMore;
- }, [isLoadingMore, logs.length, sentinelOffset]);
+ }, [isLoadingMore, displayedLogs.length, sentinelOffset]);
useEffect(() => {
if (logsRef) {
@@ -204,12 +222,12 @@ export function DeploymentLogs({
hasMore &&
!isLoadingMore
) {
- prevLogCountRef.current = logs.length;
+ prevLogCountRef.current = displayedLogs.length;
loadMoreHistory();
}
}
},
- [totalCount, logs.length, hasMore, isLoadingMore, loadMoreHistory],
+ [totalCount, displayedLogs.length, hasMore, isLoadingMore, loadMoreHistory],
);
if (isLoading) {
@@ -265,25 +283,42 @@ export function DeploymentLogs({
Stream error: {streamError}
) : null}
-
- virtualizerRef={virtualizerRef}
- viewportRef={viewportRef}
- onChange={handleScrollChange}
- count={totalCount}
- estimateSize={() => 24}
- className="w-full flex-1 min-h-0"
- scrollerProps={{
- className: "w-full",
- }}
- viewportProps={{}}
- getRowData={(index) => {
- if (hasMore && index === 0) {
- return { isSentinel: true, isLoadingMore };
- }
- return { entry: logs[index - sentinelOffset] };
- }}
- row={LogRow}
- />
+
+
+ virtualizerRef={virtualizerRef}
+ viewportRef={viewportRef}
+ onChange={handleScrollChange}
+ count={totalCount}
+ estimateSize={() => 24}
+ className="w-full h-full"
+ scrollerProps={{
+ className: "w-full",
+ }}
+ viewportProps={{}}
+ getRowData={(index) => {
+ if (hasMore && index === 0) {
+ return { isSentinel: true, isLoadingMore };
+ }
+ return { entry: displayedLogs[index - sentinelOffset] };
+ }}
+ row={LogRow}
+ />
+ {!follow ? (
+
+
+
+ ) : null}
+
);
}
diff --git a/frontend/src/components/use-deployment-logs-stream.ts b/frontend/src/components/use-deployment-logs-stream.ts
index 02375be53e..1e06779d46 100644
--- a/frontend/src/components/use-deployment-logs-stream.ts
+++ b/frontend/src/components/use-deployment-logs-stream.ts
@@ -1,5 +1,5 @@
-import type { RivetSse } from "@rivet-gg/cloud";
-import { startTransition, useEffect, useRef, useState } from "react";
+import type { Rivet, RivetSse } from "@rivet-gg/cloud";
+import { startTransition, useCallback, useEffect, useRef, useState } from "react";
import { cloudEnv } from "@/lib/env";
const MAX_RETRIES = 8;
@@ -103,6 +103,42 @@ async function sleep(ms: number, signal: AbortSignal) {
}
+const HISTORY_PAGE_SIZE = 100;
+const INITIAL_HISTORY_SIZE = 50;
+
+async function fetchLogsHistory(
+ baseUrl: string,
+ project: string,
+ namespace: string,
+ pool: string,
+ params: { before?: string; limit?: number; region?: string; contains?: string },
+): Promise {
+ const qs = new URLSearchParams();
+ if (params.before) qs.set("before", params.before);
+ if (params.limit) qs.set("limit", String(params.limit));
+ if (params.region) qs.set("region", params.region);
+ if (params.contains) qs.set("contains", params.contains);
+ const query = qs.toString();
+ const url = `${baseUrl}/projects/${encodeURIComponent(project)}/namespaces/${encodeURIComponent(namespace)}/managed-pools/${encodeURIComponent(pool)}/logs/history${query ? `?${query}` : ""}`;
+
+ const response = await fetch(url, {
+ method: "GET",
+ headers: { Accept: "application/json" },
+ credentials: "include",
+ });
+
+ if (!response.ok) {
+ const body = await response.text();
+ throw new Error(`fetchLogsHistory failed with status ${response.status}: ${body}`);
+ }
+
+ return response.json();
+}
+
+function historyToLogEvent(item: Rivet.LogHistoryResponseItem): RivetSse.LogStreamEvent.Log {
+ return { event: "log", data: item };
+}
+
async function streamWithRetry(
project: string,
namespace: string,
@@ -173,8 +209,12 @@ export function useDeploymentLogsStream({
const [logs, setLogs] = useState([]);
const [isLoading, setIsLoading] = useState(true);
const [error, setError] = useState(null);
+ const [isLoadingMore, setIsLoadingMore] = useState(false);
+ const [hasMore, setHasMore] = useState(true);
const pendingRef = useRef([]);
const pausedRef = useRef(paused);
+ const logsRef = useRef(logs);
+ logsRef.current = logs;
useEffect(() => {
pausedRef.current = paused;
@@ -200,35 +240,64 @@ export function useDeploymentLogsStream({
}
}
- streamWithRetry(
- project,
- namespace,
- pool,
- filter,
- region,
- controller.signal,
- () => setIsLoading(false),
- onEntry,
- )
- .then((result) => {
- setIsLoading(false);
- if (result === "exhausted") {
- setError(
- "Failed to connect to log stream after multiple attempts.",
- );
- } else if (typeof result === "object") {
- setError(result.error);
- }
- })
- .catch((err) => {
- if ((err as Error).name !== "AbortError") {
- console.error("Log stream fatal error:", err);
- setIsLoading(false);
- setError(
- "An unexpected error occurred while streaming logs.",
- );
+ async function start() {
+ // Seed the view with recent historical logs so it isn't empty on load.
+ try {
+ const initial = await fetchLogsHistory(
+ cloudEnv().VITE_APP_CLOUD_API_URL,
+ project,
+ namespace,
+ pool,
+ {
+ limit: INITIAL_HISTORY_SIZE,
+ region: region || undefined,
+ contains: filter || undefined,
+ },
+ );
+ if (controller.signal.aborted) return;
+ if (initial.length > 0) {
+ const converted = initial.map(historyToLogEvent);
+ startTransition(() => {
+ setLogs(converted);
+ });
}
- });
+ } catch {
+ // Non-fatal. The stream will still start.
+ }
+
+ if (controller.signal.aborted) return;
+ setIsLoading(false);
+
+ const result = await streamWithRetry(
+ project,
+ namespace,
+ pool,
+ filter,
+ region,
+ controller.signal,
+ () => setIsLoading(false),
+ onEntry,
+ );
+
+ setIsLoading(false);
+ if (result === "exhausted") {
+ setError(
+ "Failed to connect to log stream after multiple attempts.",
+ );
+ } else if (typeof result === "object") {
+ setError(result.error);
+ }
+ }
+
+ start().catch((err) => {
+ if ((err as Error).name !== "AbortError") {
+ console.error("Log stream fatal error:", err);
+ setIsLoading(false);
+ setError(
+ "An unexpected error occurred while streaming logs.",
+ );
+ }
+ });
return () => controller.abort();
}, [project, namespace, pool, filter, region]);
@@ -243,13 +312,57 @@ export function useDeploymentLogsStream({
}
}, [paused]);
+ // Reset hasMore when filters change.
+ useEffect(() => {
+ setHasMore(true);
+ }, [project, namespace, pool, filter, region]);
+
+ const loadMoreHistory = useCallback(async () => {
+ if (isLoadingMore || !hasMore) return;
+ setIsLoadingMore(true);
+ try {
+ const currentLogs = logsRef.current;
+ const before = currentLogs.length > 0
+ ? currentLogs[0].data.timestamp
+ : new Date().toISOString();
+
+ const items = await fetchLogsHistory(
+ cloudEnv().VITE_APP_CLOUD_API_URL,
+ project,
+ namespace,
+ pool,
+ {
+ before,
+ limit: HISTORY_PAGE_SIZE,
+ region: region || undefined,
+ contains: filter || undefined,
+ },
+ );
+
+ if (items.length < HISTORY_PAGE_SIZE) {
+ setHasMore(false);
+ }
+
+ if (items.length > 0) {
+ const converted = items.map(historyToLogEvent);
+ startTransition(() => {
+ setLogs((prev) => [...converted, ...prev]);
+ });
+ }
+ } catch (err) {
+ console.error("Failed to load historical logs:", err);
+ } finally {
+ setIsLoadingMore(false);
+ }
+ }, [isLoadingMore, hasMore, project, namespace, pool, filter, region]);
+
return {
logs,
isLoading,
error,
streamError: null,
- isLoadingMore: false,
- hasMore: false,
- loadMoreHistory: () => { },
+ isLoadingMore,
+ hasMore,
+ loadMoreHistory,
};
}
diff --git a/package.json b/package.json
index a11337ea39..c1883a112f 100644
--- a/package.json
+++ b/package.json
@@ -47,7 +47,7 @@
"overrides": {
"react": "19.1.0",
"react-dom": "19.1.0",
- "@rivet-gg/cloud": "https://pkg.pr.new/rivet-dev/cloud/@rivet-gg/cloud@413534c",
+ "@rivet-gg/cloud": "https://pkg.pr.new/rivet-dev/engine-ee/@rivet-gg/cloud@299",
"rivetkit": "workspace:*",
"@rivetkit/engine-api-full": "workspace:*",
"@codemirror/state": "6.5.2",
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index 23472ae6e5..95854f5536 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -17,7 +17,7 @@ overrides:
'@types/react-dom': ^19
react: 19.1.0
react-dom: 19.1.0
- '@rivet-gg/cloud': https://pkg.pr.new/rivet-dev/cloud/@rivet-gg/cloud@413534c
+ '@rivet-gg/cloud': https://pkg.pr.new/rivet-dev/engine-ee/@rivet-gg/cloud@299
'@codemirror/state': 6.5.2
'@codemirror/view': 6.38.2
'@codemirror/autocomplete': 6.18.7
@@ -418,8 +418,8 @@ importers:
specifier: ^4.7.0
version: 4.7.0(monaco-editor@0.55.1)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)
'@rivet-gg/cloud':
- specifier: https://pkg.pr.new/rivet-dev/cloud/@rivet-gg/cloud@413534c
- version: https://pkg.pr.new/rivet-dev/cloud/@rivet-gg/cloud@413534c
+ specifier: https://pkg.pr.new/rivet-dev/engine-ee/@rivet-gg/cloud@299
+ version: https://pkg.pr.new/rivet-dev/engine-ee/@rivet-gg/cloud@299
'@rivetkit/engine-api-full':
specifier: workspace:*
version: link:../../engine/sdks/typescript/api-full
@@ -3414,8 +3414,8 @@ importers:
specifier: ^1.2.3
version: 1.2.3(@types/react-dom@19.2.3(@types/react@19.2.13))(@types/react@19.2.13)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)
'@rivet-gg/cloud':
- specifier: https://pkg.pr.new/rivet-dev/cloud/@rivet-gg/cloud@413534c
- version: https://pkg.pr.new/rivet-dev/cloud/@rivet-gg/cloud@413534c
+ specifier: https://pkg.pr.new/rivet-dev/engine-ee/@rivet-gg/cloud@299
+ version: https://pkg.pr.new/rivet-dev/engine-ee/@rivet-gg/cloud@299
'@rivet-gg/icons':
specifier: workspace:*
version: link:packages/icons
@@ -4576,8 +4576,8 @@ importers:
specifier: 25.5.3
version: 25.5.3
'@rivet-gg/cloud':
- specifier: https://pkg.pr.new/rivet-dev/cloud/@rivet-gg/cloud@413534c
- version: https://pkg.pr.new/rivet-dev/cloud/@rivet-gg/cloud@413534c
+ specifier: https://pkg.pr.new/rivet-dev/engine-ee/@rivet-gg/cloud@299
+ version: https://pkg.pr.new/rivet-dev/engine-ee/@rivet-gg/cloud@299
'@rivet-gg/components':
specifier: workspace:*
version: link:../frontend/packages/components
@@ -8912,8 +8912,8 @@ packages:
'@rivet-gg/api@25.5.3':
resolution: {integrity: sha512-pj8xYQ+I/aQDbThmicPxvR+TWAzGoLSE53mbJi4QZHF8VH2oMvU7CMWqy7OTFH30DIRyVzsnHHRJZKGwtmQL3g==}
- '@rivet-gg/cloud@https://pkg.pr.new/rivet-dev/cloud/@rivet-gg/cloud@413534c':
- resolution: {tarball: https://pkg.pr.new/rivet-dev/cloud/@rivet-gg/cloud@413534c}
+ '@rivet-gg/cloud@https://pkg.pr.new/rivet-dev/engine-ee/@rivet-gg/cloud@299':
+ resolution: {tarball: https://pkg.pr.new/rivet-dev/engine-ee/@rivet-gg/cloud@299}
version: 0.0.0
'@rivetkit/bare-ts@0.6.2':
@@ -21887,7 +21887,7 @@ snapshots:
react-simple-code-editor: 0.14.1(react-dom@19.1.0(react@19.1.0))(react@19.1.0)
serve-handler: 6.1.6
tailwind-merge: 2.6.0
- tailwindcss-animate: 1.0.7(tailwindcss@4.2.2)
+ tailwindcss-animate: 1.0.7(tailwindcss@3.4.18(tsx@4.21.0)(yaml@2.8.2))
zod: 3.25.76
transitivePeerDependencies:
- '@cfworker/json-schema'
@@ -23416,7 +23416,7 @@ snapshots:
transitivePeerDependencies:
- encoding
- '@rivet-gg/cloud@https://pkg.pr.new/rivet-dev/cloud/@rivet-gg/cloud@413534c':
+ '@rivet-gg/cloud@https://pkg.pr.new/rivet-dev/engine-ee/@rivet-gg/cloud@299':
dependencies:
cross-fetch: 4.1.0
form-data: 4.0.5
diff --git a/rivetkit-typescript/packages/rivetkit/src/client/utils.ts b/rivetkit-typescript/packages/rivetkit/src/client/utils.ts
index 39a6d2f01a..e2c3cd0e2b 100644
--- a/rivetkit-typescript/packages/rivetkit/src/client/utils.ts
+++ b/rivetkit-typescript/packages/rivetkit/src/client/utils.ts
@@ -3,7 +3,6 @@ import type { VersionedDataHandler } from "vbare";
import type { z } from "zod/v4";
import type { Encoding } from "@/common/encoding";
import { assertUnreachable } from "@/common/utils";
-import type { HttpResponseError } from "@/common/client-protocol";
import { HTTP_RESPONSE_ERROR_VERSIONED } from "@/common/client-protocol-versioned";
import {
type HttpResponseError as HttpResponseErrorJson,